/*! * MIR Builder - Converts AST to MIR/SSA form * * Implements AST → MIR conversion with SSA construction */ use super::slot_registry::resolve_slot_by_type_name; use super::{ BasicBlock, BasicBlockId, BasicBlockIdGenerator, CompareOp, ConstValue, Effect, EffectMask, FunctionSignature, MirFunction, MirInstruction, MirModule, MirType, ValueId, ValueIdGenerator, }; use crate::ast::{ASTNode, LiteralValue, Span}; use crate::mir::builder::builder_calls::CallTarget; use crate::mir::region::function_slot_registry::FunctionSlotRegistry; use crate::mir::region::RegionId; use std::collections::HashSet; use std::collections::{BTreeMap, HashMap}; mod binding_context; // Phase 136 follow-up (Step 4/7): BindingContext extraction mod builder_calls; mod call_resolution; // ChatGPT5 Pro: Type-safe call resolution utilities mod calls; // Call system modules (refactored from builder_calls) mod compilation_context; // Phase 136 follow-up (Step 7/7): CompilationContext extraction mod context; // BoxCompilationContext - 箱理論による静的Boxコンパイル時のコンテキスト分離 mod core_context; // Phase 136 follow-up (Step 2/7): CoreContext extraction mod decls; // declarations lowering split mod exprs; // expression lowering split mod exprs_call; mod metadata_context; // Phase 136 follow-up (Step 6/7): MetadataContext extraction mod method_call_handlers; mod variable_context; // Phase 136 follow-up (Step 5/7): VariableContext extraction // Method call handler separation (Phase 3) // call(expr) // include lowering removed (using is handled in runner) mod control_flow; // thin wrappers to centralize control-flow entrypoints // Phase 140-P4-A: Re-export for loop_canonicalizer SSOT (crate-wide visibility) pub(crate) use control_flow::{detect_skip_whitespace_pattern, SkipWhitespaceInfo}; // Phase 142-P1: Re-export continue pattern detection for loop_canonicalizer pub(crate) use control_flow::{detect_continue_pattern, ContinuePatternInfo}; // Phase 143-P0: Re-export parse_number pattern detection for loop_canonicalizer pub(crate) use control_flow::{detect_parse_number_pattern, ParseNumberInfo}; pub(crate) use control_flow::{detect_parse_string_pattern, ParseStringInfo}; // Phase 91 P5b: Re-export escape skip pattern detection for loop_canonicalizer pub(crate) use control_flow::{detect_escape_skip_pattern, EscapeSkipPatternInfo}; mod exprs_lambda; // lambda lowering mod exprs_peek; // peek expression mod exprs_qmark; // ?-propagate mod fields; // field access/assignment lowering split mod if_form; mod joinir_id_remapper; // Phase 189: JoinIR ID remapping (ValueId/BlockId translation) mod joinir_inline_boundary_injector; // Phase 189: JoinInlineBoundary Copy instruction injector mod lifecycle; mod loop_frontend_binding; // Phase 50: Loop Frontend Binding (JoinIR variable mapping) pub(crate) mod loops; mod ops; mod phi; mod phi_merge; // Phase 25.1q: Unified PHI merge helper // prepare/lower_root/finalize split // legacy large-match remains inline for now (planned extraction) mod emission; // emission::*(Const/Compare/Branch の薄い発行箱) mod emit_guard; // EmitGuardBox(emit直前の最終関所) mod metadata; // MetadataPropagationBox(type/originの伝播) mod name_const; // NameConstBox(関数名Const生成) mod observe; // P0: dev-only observability helpers(ssa/resolve) mod origin; // P0: origin inference(me/Known)と PHI 伝播(軽量) mod plugin_sigs; // plugin signature loader mod receiver; // ReceiverMaterializationBox(Method recv の pin+LocalSSA 集約) mod rewrite; // P1: Known rewrite & special consolidation mod router; // RouterPolicyBox(Unified vs BoxCall) mod schedule; // BlockScheduleBox(物理順序: PHI→materialize→body) mod scope_context; // Phase 136 follow-up (Step 3/7): ScopeContext extraction mod ssa; // LocalSSA helpers (in-block materialization) mod stmts; mod type_context; // Phase 136 follow-up: TypeContext extraction mod type_facts; // Phase 136 follow-up: Type inference facts box pub(crate) mod type_registry; mod types; // types::annotation / inference(型注釈/推論の箱: 推論は後段) mod utils; mod vars; // variables/scope helpers // small loop helpers (header/exit context) // TypeRegistryBox(型情報管理の一元化) // Unified member property kinds for computed/once/birth_once #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub(crate) enum PropertyKind { Computed, Once, BirthOnce, } /// MIR builder for converting AST to SSA form pub struct MirBuilder { /// Current module being built pub(super) current_module: Option, /// Current basic block being built pub(super) current_block: Option, /// Phase 136 follow-up (Step 2/7): Core ID generation context /// Consolidates value_gen, block_gen, next_binding_id, temp_slot_counter, debug_join_counter. /// Direct field access for backward compatibility (migration in progress). pub(super) core_ctx: core_context::CoreContext, /// Phase 136 follow-up: Type information context /// Consolidates value_types, value_kinds, value_origin_newbox for better organization. /// Direct field access for backward compatibility (migration in progress). pub(super) type_ctx: type_context::TypeContext, /// Phase 136 follow-up (Step 3/7): Scope and control flow context /// Consolidates lexical_scope_stack, loop stacks, if_merge_stack, current_function, /// function_param_names, debug_scope_stack for better organization. /// Direct field access for backward compatibility (migration in progress). pub(super) scope_ctx: scope_context::ScopeContext, /// Phase 136 follow-up (Step 4/7): Binding context /// Consolidates binding_map (String -> BindingId mapping). /// Direct field access for backward compatibility (migration in progress). pub(super) binding_ctx: binding_context::BindingContext, /// Phase 136 follow-up (Step 5/7): Variable context /// Consolidates variable_map (String -> ValueId mapping for SSA conversion). /// Direct field access for backward compatibility (migration in progress). pub(super) variable_ctx: variable_context::VariableContext, /// Phase 136 follow-up (Step 6/7): Metadata context /// Consolidates current_span, source_file, hint_sink, current_region_stack. /// Direct field access for backward compatibility (migration in progress). pub(super) metadata_ctx: metadata_context::MetadataContext, /// Phase 136 follow-up (Step 7/7): Compilation context /// Consolidates compilation_context, current_static_box, user_defined_boxes, reserved_value_ids, /// fn_body_ast, weak_fields_by_box, property_getters_by_box, field_origin_class, field_origin_by_box, /// static_method_index, method_tail_index, type_registry, current_slot_registry, plugin_method_sigs. /// Direct field access for backward compatibility (migration in progress). pub(super) comp_ctx: compilation_context::CompilationContext, /// Pending phi functions to be inserted #[allow(dead_code)] pub(super) pending_phis: Vec<(BasicBlockId, ValueId, String)>, // Phase 2-5: binding_map removed - use binding_ctx.binding_map instead // include guards removed // フェーズM: no_phi_modeフィールド削除(常にPHI使用) // ---- Try/Catch/Cleanup lowering context ---- /// When true, `return` statements are deferred: they assign to `return_defer_slot` /// and jump to `return_defer_target` (typically the cleanup/exit block). pub(super) return_defer_active: bool, /// Slot value to receive deferred return values (edge-copy mode friendly). pub(super) return_defer_slot: Option, /// Target block to jump to on deferred return. pub(super) return_defer_target: Option, /// Set to true when a deferred return has been emitted in the current context. pub(super) return_deferred_emitted: bool, /// True while lowering the cleanup block. pub(super) in_cleanup_block: bool, /// Policy flags (snapshotted at entry of try/catch lowering) pub(super) cleanup_allow_return: bool, pub(super) cleanup_allow_throw: bool, /// If true, skip entry materialization of pinned slots on the next start_new_block call. suppress_pin_entry_copy_next: bool, // ---------------------- // Debug scope context (dev only; zero-cost when unused) // ---------------------- /// Local SSA cache: ensure per-block materialization for critical operands (e.g., recv) /// Key: (bb, original ValueId, kind) -> local ValueId /// kind: 0=recv, 1+ reserved for future (args etc.) pub(super) local_ssa_map: HashMap<(BasicBlockId, ValueId, u8), ValueId>, /// BlockSchedule cache: deduplicate materialize copies per (bb, src) pub(super) schedule_mat_map: HashMap<(BasicBlockId, ValueId), ValueId>, /// Mapping from ValueId to its pin slot name (e.g., "__pin$3$@recv") /// Used by LocalSSA to redirect old pinned values to the latest slot value. pub(super) pin_slot_names: HashMap, /// Guard flag to prevent re-entering emit_unified_call from BoxCall fallback. /// Used when RouterPolicyBox in emit_unified_call has already decided to /// route a given Method call to BoxCall; emit_box_or_plugin_call must not /// bounce back into the unified path for the same call, otherwise an /// infinite recursion (emit_unified_call → emit_box_or_plugin_call → /// emit_unified_call …) can occur when routing decisions disagree. pub(super) in_unified_boxcall_fallback: bool, /// Recursion depth counter for debugging stack overflow /// Tracks the depth of build_expression calls to detect infinite loops pub(super) recursion_depth: usize, /// Root lowering mode: how to treat top-level Program /// - None: not decided yet (lower_root not called) /// - Some(true): App mode (static box Main.main is entry) /// - Some(false): Script/Test mode (top-level Program runs sequentially) pub(super) root_is_app_mode: Option, /// 🎯 Phase 21.7: Static box singleton instances for methodization /// Maps BoxName → ValueId of singleton instance (created on demand) /// Used when HAKO_MIR_BUILDER_METHODIZE=1 to convert Global("BoxName.method/arity") /// to Method{receiver=singleton} calls pub(super) static_box_singletons: HashMap, } impl MirBuilder { /// Create a new MIR builder pub fn new() -> Self { let plugin_method_sigs = plugin_sigs::load_plugin_method_sigs(); let core_ctx = core_context::CoreContext::new(); // Phase 136 Step 7/7: Compilation context (new SSOT) let comp_ctx = compilation_context::CompilationContext::with_plugin_sigs(plugin_method_sigs.clone()); // フェーズM: no_phi_mode初期化削除 #[allow(deprecated)] Self { current_module: None, current_block: None, // Phase 136 Step 2/7: Core context (new SSOT) core_ctx, type_ctx: type_context::TypeContext::new(), // Phase 136: Type context scope_ctx: scope_context::ScopeContext::new(), // Phase 136 Step 3/7: Scope context binding_ctx: binding_context::BindingContext::new(), // Phase 136 Step 4/7: Binding context variable_ctx: variable_context::VariableContext::new(), // Phase 136 Step 5/7: Variable context metadata_ctx: metadata_context::MetadataContext::new(), // Phase 136 Step 6/7: Metadata context comp_ctx, // Phase 136 Step 7/7: Compilation context pending_phis: Vec::new(), // Phase 2-5: binding_map initialization removed // フェーズM: no_phi_modeフィールド削除 return_defer_active: false, return_defer_slot: None, return_defer_target: None, return_deferred_emitted: false, in_cleanup_block: false, cleanup_allow_return: false, cleanup_allow_throw: false, suppress_pin_entry_copy_next: false, local_ssa_map: HashMap::new(), schedule_mat_map: HashMap::new(), pin_slot_names: HashMap::new(), in_unified_boxcall_fallback: false, recursion_depth: 0, root_is_app_mode: None, static_box_singletons: HashMap::new(), // Phase 21.7: methodization support } } // Phase 2-5: BindingContext sync helpers removed - binding_ctx is now SSOT // Phase 2-6: VariableContext sync helpers removed - variable_ctx is now SSOT /// Push/pop helpers for If merge context (best-effort; optional usage) pub(super) fn push_if_merge(&mut self, bb: BasicBlockId) { // Phase 2-4: Use scope_ctx only (legacy field removed) self.scope_ctx.push_if_merge(bb); } pub(super) fn pop_if_merge(&mut self) { // Phase 2-4: Use scope_ctx only (legacy field removed) let _ = self.scope_ctx.pop_if_merge(); } /// Suppress entry pin copy for the next start_new_block (used for merge blocks). pub(super) fn suppress_next_entry_pin_copy(&mut self) { self.suppress_pin_entry_copy_next = true; } // ---- Phase 74: BindingId allocation ---- /// Allocate a new BindingId (parallel to ValueId allocation) /// /// ## Parallel ValueId/BindingId Allocation /// /// BindingId allocation is completely independent from ValueId allocation: /// - `next_value_id()` increments `value_gen` counter /// - `allocate_binding_id()` increments `next_binding_id` counter /// /// This parallelism enables: /// 1. **Stable binding identity** across SSA transformations /// 2. **Independent shadowing tracking** separate from SSA renaming /// 3. **Future ScopeManager migration** (Phase 75+) without breaking SSA /// /// Example: /// ```ignore /// // local x = 1; <- allocate_binding_id() -> BindingId(0) /// // next_value_id() -> ValueId(10) /// // { /// // local x = 2; <- allocate_binding_id() -> BindingId(1) /// // next_value_id() -> ValueId(20) /// // } /// ``` pub fn allocate_binding_id(&mut self) -> super::BindingId { // Phase 136 Step 2/7 + Phase 2-2: Use core_ctx as SSOT (no sync needed) self.core_ctx.next_binding() } // ---- Hint helpers (no-op by default) ---- // Phase 136 Step 6/7: Delegate to metadata_ctx with legacy sync #[inline] pub(crate) fn hint_scope_enter(&mut self, id: u32) { self.metadata_ctx.hint_scope_enter(id); } #[inline] pub(crate) fn hint_scope_leave(&mut self, id: u32) { self.metadata_ctx.hint_scope_leave(id); } #[inline] pub(crate) fn hint_join_result>(&mut self, var: S) { self.metadata_ctx.hint_join_result(var); } // ---------------------- // Debug scope helpers (region_id for DebugHub events) // ---------------------- #[inline] pub(crate) fn debug_next_join_id(&mut self) -> u32 { // Phase 136 Step 2/7 + Phase 2-2: Use core_ctx as SSOT (no sync needed) self.core_ctx.next_debug_join() } #[inline] pub(crate) fn debug_push_region>(&mut self, region: S) { // Phase 2-4: Use scope_ctx only (legacy field removed) let region = region.into(); self.scope_ctx.debug_push_region(region); } #[inline] pub(crate) fn debug_pop_region(&mut self) { // Phase 2-4: Use scope_ctx only (legacy field removed) self.scope_ctx.debug_pop_region(); } #[inline] #[allow(deprecated)] pub(crate) fn debug_current_region_id(&self) -> Option { // Phase 136 Step 3/7: Read from scope_ctx (SSOT) self.scope_ctx.debug_current_region_id() } /// Hint for downstream metadata: set the logical source file name/path for the next build. /// Phase 136 Step 6/7: Delegate to metadata_ctx pub fn set_source_file_hint>(&mut self, source: S) { self.metadata_ctx.set_source_file(source); } /// Clear the source file hint (used when reusing the builder across modules). /// Phase 136 Step 6/7: Delegate to metadata_ctx pub fn clear_source_file_hint(&mut self) { self.metadata_ctx.clear_source_file(); } /// Resolve current source file hint (builder field or env fallback). /// Phase 136 Step 6/7: Delegate to metadata_ctx fn current_source_file(&self) -> Option { self.metadata_ctx .current_source_file() .or_else(|| std::env::var("NYASH_SOURCE_FILE_HINT").ok()) } /// Create a new MirFunction with source metadata applied. fn new_function_with_metadata( &self, signature: FunctionSignature, entry_block: BasicBlockId, ) -> MirFunction { let mut f = MirFunction::new(signature, entry_block); f.metadata.source_file = self.current_source_file(); f } // ---------------------- // Compile trace helpers (dev only; env-gated) // ---------------------- #[inline] pub(super) fn compile_trace_enabled() -> bool { std::env::var("NYASH_MIR_COMPILE_TRACE").ok().as_deref() == Some("1") } #[inline] pub(super) fn trace_compile>(&self, msg: S) { if Self::compile_trace_enabled() { eprintln!("[mir-compile] {}", msg.as_ref()); } } // ---------------------- // Method tail index (performance helper) // ---------------------- fn rebuild_method_tail_index(&mut self) { self.comp_ctx.method_tail_index.clear(); if let Some(ref module) = self.current_module { for name in module.functions.keys() { if let (Some(dot), Some(slash)) = (name.rfind('.'), name.rfind('/')) { if slash > dot { let tail = &name[dot..]; self.comp_ctx .method_tail_index .entry(tail.to_string()) .or_insert_with(Vec::new) .push(name.clone()); } } } self.comp_ctx.method_tail_index_source_len = module.functions.len(); } else { self.comp_ctx.method_tail_index_source_len = 0; } } fn ensure_method_tail_index(&mut self) { let need_rebuild = match self.current_module { Some(ref refmod) => { self.comp_ctx.method_tail_index_source_len != refmod.functions.len() } None => self.comp_ctx.method_tail_index_source_len != 0, }; if need_rebuild { self.rebuild_method_tail_index(); } } pub(super) fn method_candidates(&mut self, method: &str, arity: usize) -> Vec { self.ensure_method_tail_index(); let tail = format!(".{}{}", method, format!("/{}", arity)); self.comp_ctx .method_tail_index .get(&tail) .cloned() .unwrap_or_default() } pub(super) fn method_candidates_tail>(&mut self, tail: S) -> Vec { self.ensure_method_tail_index(); self.comp_ctx .method_tail_index .get(tail.as_ref()) .cloned() .unwrap_or_default() } /// Build a complete MIR module from AST pub fn build_module(&mut self, ast: ASTNode) -> Result { self.prepare_module()?; let result_value = self.lower_root(ast)?; self.finalize_module(result_value) } /// Build an expression and return its value ID pub(super) fn build_expression(&mut self, ast: ASTNode) -> Result { // Delegated to exprs.rs to keep this file lean // Debug: Track recursion depth to detect infinite loops const MAX_RECURSION_DEPTH: usize = 200; self.recursion_depth += 1; if self.recursion_depth > MAX_RECURSION_DEPTH { eprintln!("\n[FATAL] ============================================"); eprintln!( "[FATAL] Recursion depth exceeded {} in build_expression", MAX_RECURSION_DEPTH ); eprintln!("[FATAL] Current depth: {}", self.recursion_depth); eprintln!("[FATAL] AST node type: {:?}", std::mem::discriminant(&ast)); eprintln!("[FATAL] ============================================\n"); return Err(format!( "Recursion depth exceeded: {} (possible infinite loop)", self.recursion_depth )); } let result = self.build_expression_impl(ast); self.recursion_depth -= 1; result } /// Build a literal value pub(super) fn build_literal(&mut self, literal: LiteralValue) -> Result { // Determine type without moving literal let ty_for_dst = match &literal { LiteralValue::Integer(_) => Some(super::MirType::Integer), LiteralValue::Float(_) => Some(super::MirType::Float), LiteralValue::Bool(_) => Some(super::MirType::Bool), LiteralValue::String(_) => Some(super::MirType::String), _ => None, }; // Emit via ConstantEmissionBox(仕様不変の統一ルート) let dst = match literal { LiteralValue::Integer(n) => { crate::mir::builder::emission::constant::emit_integer(self, n) } LiteralValue::Float(f) => crate::mir::builder::emission::constant::emit_float(self, f), LiteralValue::String(s) => { crate::mir::builder::emission::constant::emit_string(self, s) } LiteralValue::Bool(b) => crate::mir::builder::emission::constant::emit_bool(self, b), LiteralValue::Null => crate::mir::builder::emission::constant::emit_null(self), LiteralValue::Void => crate::mir::builder::emission::constant::emit_void(self), }; // Annotate type if let Some(ty) = ty_for_dst { self.type_ctx.value_types.insert(dst, ty); } Ok(dst) } /// Build variable access pub(super) fn build_variable_access(&mut self, name: String) -> Result { // Step 5-5-G: __pin$ variables should NEVER be accessed from variable_map // They are transient temporaries created during expression building and // should not persist across blocks. If we see one here, it's a compiler bug. if name.starts_with("__pin$") { return Err(format!( "COMPILER BUG: Attempt to access __pin$ temporary '{}' from variable_map. \ __pin$ variables should only exist as direct SSA values, not as named variables.", name )); } if let Some(&value_id) = self.variable_ctx.variable_map.get(&name) { Ok(value_id) } else { Err(self.undefined_variable_message(&name)) } } pub(in crate::mir::builder) fn undefined_variable_message(&self, name: &str) -> String { // Enhance diagnostics using Using simple registry (Phase 1) let mut msg = format!("Undefined variable: {}", name); // Stage-3 keyword diagnostic (local/flow/try/catch/throw) if name == "local" && !crate::config::env::parser_stage3_enabled() { msg.push_str("\nHint: 'local' is a Stage-3 keyword. Prefer NYASH_FEATURES=stage3 (legacy: NYASH_PARSER_STAGE3=1 / HAKO_PARSER_STAGE3=1 for Stage-B)."); msg.push_str("\nFor AotPrep verification, use tools/hakorune_emit_mir.sh which sets these automatically."); } else if (name == "flow" || name == "try" || name == "catch" || name == "throw") && !crate::config::env::parser_stage3_enabled() { msg.push_str(&format!("\nHint: '{}' is a Stage-3 keyword. Prefer NYASH_FEATURES=stage3 (legacy: NYASH_PARSER_STAGE3=1 / HAKO_PARSER_STAGE3=1 for Stage-B).", name)); } let suggest = crate::using::simple_registry::suggest_using_for_symbol(name); if !suggest.is_empty() { msg.push_str("\nHint: symbol appears in using module(s): "); msg.push_str(&suggest.join(", ")); msg.push_str( "\nConsider adding 'using [as Alias]' or check nyash.toml [using].", ); } msg } /// Build assignment pub(super) fn build_assignment( &mut self, var_name: String, value: ASTNode, ) -> Result { // SSOT (LANGUAGE_REFERENCE_2025 / syntax-cheatsheet): // - Assignment to an undeclared name is an error. // - Use `local name = ...` (or `local name; name = ...`) to declare. vars::assignment_resolver::AssignmentResolverBox::ensure_declared(self, &var_name)?; let value_id = self.build_expression(value)?; // Step 5-5-E: FIX variable map corruption bug // REMOVED pin_to_slot() call - it was causing __pin$ temporaries to overwrite // real variable names in the variable map. // // Root cause: pin_to_slot(raw_value_id, "@assign") would sometimes return // a ValueId from a previous __pin$ temporary (e.g., __pin$767$@binop_lhs), // causing variable_map["m"] to point to the wrong ValueId. // // SSA + PHI merges work correctly without explicit pinning here. // The expression building already creates necessary temporaries. // Step 5-5-F: NEVER insert __pin$ temporaries into variable_map // __pin$ variables are transient compiler-generated temporaries that should // never be tracked as real variables. They are used only within expression // building and should not persist across blocks or loops. // // BUG FIX: Previously, __pin$ variables would be inserted into variable_map, // causing stale references after LoopForm transformation renumbers blocks. // Result: VM would try to read undefined ValueIds (e.g., ValueId(270) at bb303). if !var_name.starts_with("__pin$") { // In SSA form, each assignment creates a new value self.variable_ctx .variable_map .insert(var_name.clone(), value_id); } Ok(value_id) } /// Emit an instruction to the current basic block pub(super) fn emit_instruction(&mut self, instruction: MirInstruction) -> Result<(), String> { let block_id = self.current_block.ok_or("No current basic block")?; // Make instruction mutable for potential receiver materialization let mut instruction = instruction; // Precompute debug metadata to avoid borrow conflicts later let _dbg_fn_name = self .scope_ctx .current_function .as_ref() .map(|f| f.signature.name.clone()); let _dbg_region_id = self.debug_current_region_id(); // P0: PHI の軽量補強と観測は、関数ブロック取得前に実施して借用競合を避ける if let MirInstruction::Phi { dst, inputs, .. } = &instruction { origin::phi::propagate_phi_meta(self, *dst, inputs); observe::ssa::emit_phi(self, *dst, inputs); } // CRITICAL: Final receiver materialization for MethodCall // This ensures the receiver has an in-block definition in the same block as the Call. // Must happen BEFORE function mutable borrow to avoid borrowck conflicts. if let MirInstruction::Call { callee: Some(callee), dst, args, effects, .. } = &instruction { use crate::mir::definitions::call_unified::Callee; if let Callee::Method { box_name, method, receiver: Some(r), certainty, box_kind, } = callee.clone() { // LocalSSA: ensure receiver has a Copy in current_block let r_local = crate::mir::builder::ssa::local::recv(self, r); // Update instruction with materialized receiver let new_callee = Callee::Method { box_name: box_name.clone(), method: method.clone(), receiver: Some(r_local), certainty, box_kind, }; instruction = MirInstruction::Call { dst: *dst, func: crate::mir::ValueId::INVALID, // Legacy dummy (not a real SSA use) callee: Some(new_callee), args: args.clone(), effects: *effects, }; } } if let Some(ref mut function) = self.scope_ctx.current_function { // Pre-capture branch/jump targets for predecessor update after we finish // mutably borrowing the current block. let (then_t, else_t, jump_t) = match &instruction { MirInstruction::Branch { then_bb, else_bb, .. } => (Some(*then_bb), Some(*else_bb), None), MirInstruction::Jump { target } => (None, None, Some(*target)), _ => (None, None, None), }; // Extract function name before mutable borrow to avoid borrowck error let current_fn_name = function.signature.name.clone(); if let Some(block) = function.get_block_mut(block_id) { // CRITICAL: Copy専用トレース(LocalSSA調査用) if let MirInstruction::Copy { dst, src } = &instruction { if std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") { eprintln!( "[emit-inst] fn={} bb={:?} COPY %{} <- %{}", current_fn_name, self.current_block.map(|b| b.0).unwrap_or(0), dst.0, src.0 ); } } // Invariant: Call must always carry a Callee (unified path). if let MirInstruction::Call { callee, .. } = &instruction { if callee.is_none() { return Err("builder invariant violated: MirInstruction::Call.callee must be Some (unified call)".into()); } else if std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") { use crate::mir::definitions::call_unified::Callee; if let Some(Callee::Method { box_name, method, receiver: Some(r), .. }) = callee { eprintln!( "[emit-inst] fn={} bb={:?} Call {}.{} recv=%{}", current_fn_name, self.current_block.map(|b| b.0).unwrap_or(0), box_name, method, r.0 ); } } else if std::env::var("NYASH_BUILDER_TRACE_RECV").ok().as_deref() == Some("1") { use crate::mir::definitions::call_unified::Callee; if let Some(Callee::Method { box_name, method, receiver: Some(r), .. }) = callee { let names: Vec = self .variable_ctx .variable_map .iter() .filter(|(_, &vid)| vid == *r) .map(|(k, _)| k.clone()) .collect(); eprintln!( "[builder/recv-trace] fn={} bb={:?} method={}.{} recv=%{} aliases={:?}", current_fn_name, self.current_block, box_name, method, r.0, names ); } } } if utils::builder_debug_enabled() { eprintln!( "[BUILDER] emit @bb{} -> {}", block_id, match &instruction { MirInstruction::TypeOp { dst, op, value, ty } => format!("typeop {:?} {} {:?} -> {}", op, value, ty, dst), MirInstruction::Print { value, .. } => format!("print {}", value), MirInstruction::BoxCall { box_val, method, method_id, args, dst, .. } => { if let Some(mid) = method_id { format!( "boxcall {}.{}[#{}]({:?}) -> {:?}", box_val, method, mid, args, dst ) } else { format!( "boxcall {}.{}({:?}) -> {:?}", box_val, method, args, dst ) } } MirInstruction::Call { func, args, dst, .. } => format!("call {}({:?}) -> {:?}", func, args, dst), MirInstruction::NewBox { dst, box_type, args, } => format!("new {}({:?}) -> {}", box_type, args, dst), MirInstruction::Const { dst, value } => format!("const {:?} -> {}", value, dst), MirInstruction::Branch { condition, then_bb, else_bb, } => format!("br {}, {}, {}", condition, then_bb, else_bb), MirInstruction::Jump { target } => format!("br {}", target), _ => format!("{:?}", instruction), } ); } // Phase 136 Step 6/7: Use metadata_ctx for span block.add_instruction_with_span( instruction.clone(), self.metadata_ctx.current_span(), ); // Drop the mutable borrow of `block` before updating other blocks } // Update predecessor sets for branch/jump immediately so that // debug_verify_phi_inputs can observe a consistent CFG without // requiring a full function.update_cfg() pass. if let Some(t) = then_t { if let Some(succ) = function.get_block_mut(t) { succ.add_predecessor(block_id); } } if let Some(t) = else_t { if let Some(succ) = function.get_block_mut(t) { succ.add_predecessor(block_id); } } if let Some(t) = jump_t { if let Some(succ) = function.get_block_mut(t) { succ.add_predecessor(block_id); } } Ok(()) } else { Err(format!("Basic block {} does not exist", block_id)) } } /// Update an existing PHI instruction's inputs (for loop sealing) /// Used by LoopFormBuilder to complete incomplete PHI nodes #[allow(dead_code)] pub(super) fn update_phi_instruction( &mut self, block: BasicBlockId, phi_id: ValueId, new_inputs: Vec<(BasicBlockId, ValueId)>, ) -> Result<(), String> { if let Some(ref mut function) = self.scope_ctx.current_function { if let Some(block_data) = function.get_block_mut(block) { // Find PHI instruction with matching dst for inst in &mut block_data.instructions { if let MirInstruction::Phi { dst, inputs, .. } = inst { if *dst == phi_id { *inputs = new_inputs; return Ok(()); } } } Err(format!( "PHI instruction {} not found in block {}", phi_id, block )) } else { Err(format!("Block {} not found", block)) } } else { Err("No current function".to_string()) } } // フェーズM: is_no_phi_mode()メソッド削除 // フェーズM: insert_edge_copy()メソッド削除(no_phi_mode撤廃により不要) /// Build new expression: new ClassName(arguments) pub(super) fn build_new_expression( &mut self, class: String, arguments: Vec, ) -> Result { // Phase 9.78a: Unified Box creation using NewBox instruction // Core-13 pure mode: emit ExternCall(env.box.new) with type name const only if crate::config::env::mir_core13_pure() { // Emit Const String for type name(ConstantEmissionBox) let ty_id = crate::mir::builder::emission::constant::emit_string(self, class.clone()); // Evaluate arguments (pass through to env.box.new shim) let mut arg_vals: Vec = Vec::with_capacity(arguments.len()); for a in arguments { arg_vals.push(self.build_expression(a)?); } // Build arg list: [type, a1, a2, ...] let mut args: Vec = Vec::with_capacity(1 + arg_vals.len()); args.push(ty_id); args.extend(arg_vals); // Call env.box.new // 📦 Hotfix 3: Use next_value_id() to respect function parameter reservation let dst = self.next_value_id(); self.emit_instruction(MirInstruction::ExternCall { dst: Some(dst), iface_name: "env.box".to_string(), method_name: "new".to_string(), args, effects: EffectMask::PURE, })?; // 型注釈(最小) self.type_ctx .value_types .insert(dst, super::MirType::Box(class.clone())); return Ok(dst); } // Optimization: Primitive wrappers → emit Const directly when possible if class == "IntegerBox" && arguments.len() == 1 { if let ASTNode::Literal { value: LiteralValue::Integer(n), .. } = arguments[0].clone() { // 📦 Hotfix 3: Use next_value_id() to respect function parameter reservation let dst = self.next_value_id(); self.emit_instruction(MirInstruction::Const { dst, value: ConstValue::Integer(n), })?; self.type_ctx .value_types .insert(dst, super::MirType::Integer); return Ok(dst); } } // First, evaluate all arguments to get their ValueIds let mut arg_values = Vec::new(); for arg in arguments { let arg_value = self.build_expression(arg)?; arg_values.push(arg_value); } // Generate the destination ValueId // 📦 Hotfix 3: Use next_value_id() to respect function parameter reservation let dst = self.next_value_id(); // Emit NewBox instruction for all Box types // VM will handle optimization for basic types internally self.emit_instruction(MirInstruction::NewBox { dst, box_type: class.clone(), args: arg_values.clone(), })?; // Phase 15.5: Unified box type handling // All boxes (including former core boxes) are treated uniformly as Box types self.type_ctx .value_types .insert(dst, super::MirType::Box(class.clone())); // Record origin for optimization: dst was created by NewBox of class self.type_ctx.value_origin_newbox.insert(dst, class.clone()); // birth 呼び出し(Builder 正規化) // 優先: 低下済みグローバル関数 `.birth/Arity`(Arity は me を含まない) // 代替: 既存互換として BoxCall("birth")(プラグイン/ビルトインの初期化に対応) if class != "StringBox" { let arity = arg_values.len(); let lowered = crate::mir::builder::calls::function_lowering::generate_method_function_name( &class, "birth", arity, ); let use_lowered = if let Some(ref module) = self.current_module { module.functions.contains_key(&lowered) } else { false }; if use_lowered { // Call Global("Class.birth/Arity") with argv = [me, args...] let mut argv: Vec = Vec::with_capacity(1 + arity); argv.push(dst); argv.extend(arg_values.iter().copied()); self.emit_legacy_call(None, CallTarget::Global(lowered), argv)?; } else { // Fallback policy: // - For user-defined boxes (no explicit constructor), do NOT emit BoxCall("birth"). // VM will treat plain NewBox as constructed; dev verify warns if needed. // - For builtins/plugins, keep BoxCall("birth") fallback to preserve legacy init. let is_user_box = self.comp_ctx.user_defined_boxes.contains(&class); // Dev safety: allow disabling birth() injection for builtins to avoid // unified-call method dispatch issues while migrating. Off by default unless explicitly enabled. let allow_builtin_birth = std::env::var("NYASH_DEV_BIRTH_INJECT_BUILTINS") .ok() .as_deref() == Some("1"); if !is_user_box && allow_builtin_birth { let birt_mid = resolve_slot_by_type_name(&class, "birth"); self.emit_box_or_plugin_call( None, dst, "birth".to_string(), birt_mid, arg_values, EffectMask::READ.add(Effect::ReadHeap), )?; } } } Ok(dst) } /// Check if the current basic block is terminated fn is_current_block_terminated(&self) -> bool { if let (Some(block_id), Some(ref function)) = (self.current_block, &self.scope_ctx.current_function) { if let Some(block) = function.get_block(block_id) { return block.is_terminated(); } } false } // ============================================================================ // Phase 26-A: ValueId型安全化メソッド // ============================================================================ /// 型付きValueIdを発行(新API) /// Phase 136 P0: Use SSOT allocator (next_value_id) to respect function context pub fn new_typed_value(&mut self, kind: super::MirValueKind) -> super::TypedValueId { let id = self.next_value_id(); self.type_ctx.value_kinds.insert(id, kind); super::TypedValueId::new(id, kind) } /// 既存ValueIdの型情報を取得 pub fn get_value_kind(&self, id: ValueId) -> Option { self.type_ctx.value_kinds.get(&id).copied() } /// 既存ValueIdに型情報を後付け(レガシー互換用) pub fn register_value_kind(&mut self, id: ValueId, kind: super::MirValueKind) { self.type_ctx.value_kinds.insert(id, kind); } /// 型安全なパラメータ判定(ValueIdベース) - GUARD Bug Prevention pub fn is_value_parameter(&self, id: ValueId) -> bool { self.get_value_kind(id) .map(|kind| kind.is_parameter()) .unwrap_or(false) } /// 型安全なローカル変数判定(ValueIdベース) pub fn is_value_local(&self, id: ValueId) -> bool { self.get_value_kind(id) .map(|kind| kind.is_local()) .unwrap_or(false) } /// 型安全なLoopCarrier判定(ValueIdベース) pub fn is_value_loop_carrier(&self, id: ValueId) -> bool { self.get_value_kind(id) .map(|kind| kind.is_loop_carrier()) .unwrap_or(false) } } impl Default for MirBuilder { fn default() -> Self { Self::new() } } // Phase 79: BindingMapProvider implementation // Centralizes feature-gated binding_map access for promoters use crate::mir::loop_pattern_detection::BindingMapProvider; impl BindingMapProvider for MirBuilder { #[cfg(feature = "normalized_dev")] fn get_binding_map( &self, ) -> Option<&std::collections::BTreeMap> { // Phase 136 Step 4/7: Use binding_ctx (SSOT) Some(self.binding_ctx.binding_map()) } #[cfg(not(feature = "normalized_dev"))] fn get_binding_map( &self, ) -> Option<&std::collections::BTreeMap> { None } } #[cfg(test)] mod binding_id_tests { use super::*; #[test] fn test_binding_map_initialization() { let builder = MirBuilder::new(); assert_eq!(builder.core_ctx.next_binding_id, 0); // Phase 2-6: binding_ctx is now SSOT (legacy field removed) assert!(builder.binding_ctx.is_empty()); } #[test] fn test_binding_allocation_sequential() { let mut builder = MirBuilder::new(); let bid0 = builder.allocate_binding_id(); let bid1 = builder.allocate_binding_id(); let bid2 = builder.allocate_binding_id(); assert_eq!(bid0.raw(), 0); assert_eq!(bid1.raw(), 1); assert_eq!(bid2.raw(), 2); assert_eq!(builder.core_ctx.next_binding_id, 3); } #[test] fn test_shadowing_binding_restore() { let mut builder = MirBuilder::new(); // Simulate function entry scope builder.push_lexical_scope(); // Declare outer x // Phase 136 P0: Use SSOT allocator for function scope simulation let outer_vid = builder.next_value_id(); builder .declare_local_in_current_scope("x", outer_vid) .unwrap(); // Phase 2-6: Check binding_ctx (SSOT) let outer_bid = builder.binding_ctx.lookup("x").unwrap(); assert_eq!(outer_bid.raw(), 0); // Enter inner scope and shadow x builder.push_lexical_scope(); // Phase 136 P0: Use SSOT allocator for function scope simulation let inner_vid = builder.next_value_id(); builder .declare_local_in_current_scope("x", inner_vid) .unwrap(); // Phase 2-6: Check binding_ctx (SSOT) let inner_bid = builder.binding_ctx.lookup("x").unwrap(); assert_eq!(inner_bid.raw(), 1); // Exit inner scope - should restore outer binding builder.pop_lexical_scope(); // Phase 2-6: Check binding_ctx (SSOT) let restored_bid = builder.binding_ctx.lookup("x").unwrap(); assert_eq!(restored_bid, outer_bid); assert_eq!(restored_bid.raw(), 0); // Cleanup builder.pop_lexical_scope(); } #[test] fn test_valueid_binding_parallel_allocation() { let mut builder = MirBuilder::new(); // Phase 136 P0: Use SSOT allocator (next_value_id) // Note: Without current_function, next_value_id() falls back to value_gen.next() // so this test still validates ValueId/BindingId independence // Allocate ValueIds and BindingIds in parallel let vid0 = builder.next_value_id(); let bid0 = builder.allocate_binding_id(); let vid1 = builder.next_value_id(); let bid1 = builder.allocate_binding_id(); // ValueId and BindingId should be independent assert_eq!(vid0.0, 0); assert_eq!(bid0.raw(), 0); assert_eq!(vid1.0, 1); assert_eq!(bid1.raw(), 1); // Allocating more ValueIds should not affect BindingId counter let _ = builder.next_value_id(); let _ = builder.next_value_id(); let bid2 = builder.allocate_binding_id(); assert_eq!(bid2.raw(), 2); // Still sequential // Allocating more BindingIds should not affect ValueId counter let _ = builder.allocate_binding_id(); let _ = builder.allocate_binding_id(); let vid2 = builder.next_value_id(); assert_eq!(vid2.0, 4); // Continues from where we left off } }