//! Control-flow entrypoints (if/loop/try/throw) centralized here. use super::{Effect, EffectMask, MirInstruction, ValueId}; use crate::ast::ASTNode; impl super::MirBuilder { /// Control-flow: block pub(super) fn cf_block(&mut self, statements: Vec) -> Result { // identical to build_block; kept here for future policy hooks self.build_block(statements) } /// Control-flow: if /// /// # Phase 124: JoinIR-Only (hako_check専用化完了) /// /// If statements are now always routed through the canonical lowering path /// (lower_if_form), which internally uses JoinIR-based PHI generation. /// /// Phase 123 の環境変数による分岐は削除済み。 pub(super) fn cf_if( &mut self, condition: ASTNode, then_branch: ASTNode, else_branch: Option, ) -> Result { // Phase 124: JoinIR-only path (環境変数分岐削除) // lower_if_form は JoinIR ベースの PHI 生成を使用 self.lower_if_form(condition, then_branch, else_branch) } /// Control-flow: loop /// /// # Phase 49: JoinIR Frontend Mainline Integration /// /// This is the unified entry point for all loop lowering. Specific functions /// are routed through JoinIR Frontend instead of the traditional LoopBuilder path /// when enabled via dev flags (Phase 49) or Core policy (Phase 80): /// /// - Core ON (`joinir_core_enabled()`): print_tokens / ArrayExt.filter はまず JoinIR Frontend を試す /// - Dev フラグ(既存): /// - `HAKO_JOINIR_PRINT_TOKENS_MAIN=1`: JsonTokenizer.print_tokens/0 /// - `HAKO_JOINIR_ARRAY_FILTER_MAIN=1`: ArrayExtBox.filter/2 /// /// Note: Arity does NOT include implicit `me` receiver. pub(super) fn cf_loop( &mut self, condition: ASTNode, body: Vec, ) -> Result { // Phase 49/80: Try JoinIR Frontend route for mainline targets if let Some(result) = self.try_cf_loop_joinir(&condition, &body)? { return Ok(result); } if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { eprintln!("[cf_loop] CALLED from somewhere"); eprintln!("[cf_loop] Current stack (simulated): check build_statement vs build_expression_impl"); } // Phase 186: LoopBuilder Hard Freeze - Legacy path disabled // Phase 187-2: LoopBuilder module removed - all loops must use JoinIR return Err(format!( "[joinir/freeze] Loop lowering failed: JoinIR does not support this pattern, and LoopBuilder has been removed.\n\ Function: {}\n\ Hint: This loop pattern is not supported. All loops must use JoinIR lowering.", self.current_function.as_ref().map(|f| f.signature.name.as_str()).unwrap_or("") )); } /// Phase 49: Try JoinIR Frontend for mainline integration /// /// Returns `Ok(Some(value))` if the current function should use JoinIR Frontend, /// `Ok(None)` to fall through to the legacy LoopBuilder path. /// /// # Phase 49-4: Multi-target support /// /// Targets are enabled via separate dev flags: /// - `HAKO_JOINIR_PRINT_TOKENS_MAIN=1`: JsonTokenizer.print_tokens/0 /// - `HAKO_JOINIR_ARRAY_FILTER_MAIN=1`: ArrayExtBox.filter/2 /// /// Note: Arity in function names does NOT include implicit `me` receiver. /// - Instance method `print_tokens()` → `/0` (no explicit params) /// - Static method `filter(arr, pred)` → `/2` (two params) fn try_cf_loop_joinir( &mut self, condition: &ASTNode, body: &[ASTNode], ) -> Result, String> { // Get current function name let func_name = self .current_function .as_ref() .map(|f| f.signature.name.clone()) .unwrap_or_default(); // Phase 188-Impl-3: Debug logging for function name routing if std::env::var("NYASH_JOINIR_MAINLINE_DEBUG").is_ok() { eprintln!("[cf_loop/joinir/router] Current function name: '{}'", func_name); } // Phase 49-4 + Phase 80: Multi-target routing // - Core ON なら代表2本(print_tokens / ArrayExt.filter)は JoinIR を優先し、失敗したら LoopBuilder へフォールバック // - Core OFF では従来通り dev フラグで opt-in // Note: Arity does NOT include implicit `me` receiver // Phase 188: Add "main" routing for loop pattern expansion let core_on = crate::config::env::joinir_core_enabled(); let is_target = match func_name.as_str() { "main" => true, // Phase 188-Impl-1: Enable JoinIR for main function (Pattern 1) "JoinIrMin.main/0" => true, // Phase 188-Impl-2: Enable JoinIR for JoinIrMin.main/0 (Pattern 2) "JsonTokenizer.print_tokens/0" => { if core_on { true } else { std::env::var("HAKO_JOINIR_PRINT_TOKENS_MAIN") .ok() .as_deref() == Some("1") } } "ArrayExtBox.filter/2" => { if core_on { true } else { std::env::var("HAKO_JOINIR_ARRAY_FILTER_MAIN") .ok() .as_deref() == Some("1") } } _ => false, }; if !is_target { return Ok(None); } // Debug log when routing through JoinIR Frontend let debug = std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() || std::env::var("NYASH_JOINIR_MAINLINE_DEBUG").is_ok(); if debug { eprintln!( "[cf_loop/joinir] Routing {} through JoinIR Frontend mainline", func_name ); } // Phase 49-3: Implement JoinIR Frontend integration self.cf_loop_joinir_impl(condition, body, &func_name, debug) } /// Phase 49-3: JoinIR Frontend integration implementation /// /// # Pipeline /// 1. Build Loop AST → JSON v0 format (with "defs" array) /// 2. AstToJoinIrLowerer::lower_program_json() → JoinModule /// 3. convert_join_module_to_mir_with_meta() → MirModule /// 4. Merge MIR blocks into current_function /// /// # Phase 49-4 Note /// /// JoinIR Frontend expects a complete function definition with: /// - local variable initializations /// - loop body /// - return statement /// /// Since cf_loop only has access to the loop condition and body, /// we construct a minimal JSON v0 wrapper with function name "simple" /// to match the JoinIR Frontend's expected pattern. fn cf_loop_joinir_impl( &mut self, condition: &ASTNode, body: &[ASTNode], func_name: &str, debug: bool, ) -> Result, String> { use super::loop_frontend_binding::LoopFrontendBinding; use crate::mir::join_ir::frontend::{AstToJoinIrLowerer, JoinFuncMetaMap}; use crate::mir::join_ir_vm_bridge::convert_join_module_to_mir_with_meta; use crate::mir::types::ConstValue; use crate::r#macro::ast_json::ast_to_json; // Phase 188-Impl-3: Route Pattern 3 (If-Else PHI) - detect by 'sum' variable presence // This MUST come before Pattern 1 check to avoid incorrect routing // Pattern 3 test (loop_if_phi.hako) uses "main" with 'sum' accumulator variable if func_name == "main" && self.variable_map.contains_key("sum") { if debug { eprintln!("[cf_loop/joinir] Routing 'main' (with sum var) through Pattern 3 minimal lowerer"); } return self.cf_loop_pattern3_with_if_phi(condition, body, func_name, debug); } // Phase 188-Impl-1-F: Route Pattern 1 (Simple While) - "main" without 'sum' variable if func_name == "main" { if debug { eprintln!("[cf_loop/joinir] Routing '{}' through Pattern 1 minimal lowerer", func_name); } return self.cf_loop_pattern1_minimal(condition, body, func_name, debug); } // Phase 188-Impl-2: Route "JoinIrMin.main/0" through Pattern 2 minimal lowerer (Loop with Break) if func_name == "JoinIrMin.main/0" { if debug { eprintln!("[cf_loop/joinir] Routing 'JoinIrMin.main/0' through Pattern 2 minimal lowerer"); } return self.cf_loop_pattern2_with_break(condition, body, func_name, debug); } // Phase 50: Create appropriate binding based on function name let binding = match func_name { "JsonTokenizer.print_tokens/0" => LoopFrontendBinding::for_print_tokens(), "ArrayExtBox.filter/2" => LoopFrontendBinding::for_array_filter(), _ => { if debug { eprintln!( "[cf_loop/joinir] No binding defined for {}, falling back", func_name ); } return Ok(None); } }; if debug { eprintln!( "[cf_loop/joinir] Using binding: counter={}, acc={:?}, pattern={:?}", binding.counter_var, binding.accumulator_var, binding.pattern ); } // Step 1: Convert condition and body to JSON let condition_json = ast_to_json(condition); let mut body_json: Vec = body.iter().map(|s| ast_to_json(s)).collect(); // Phase 50: Rename variables in body (e.g., "out" → "acc" for filter) binding.rename_body_variables(&mut body_json); // Phase 50: Generate Local declarations from binding let (i_local, acc_local, n_local) = binding.generate_local_declarations(); // Phase 52/56: Build params from external_refs // Instance methods need `me`, static methods need their parameters (arr, pred, etc.) let mut params: Vec = Vec::new(); // Phase 52: Add 'me' for instance methods if binding.needs_me_receiver() { if debug { eprintln!("[cf_loop/joinir] Adding 'me' to params (instance method)"); } params.push(serde_json::json!("me")); } // Phase 56: Add external_refs as parameters (arr, pred for filter) for ext_ref in &binding.external_refs { // Skip "me" and "me.*" as they're handled above if ext_ref == "me" || ext_ref.starts_with("me.") { continue; } if debug { eprintln!( "[cf_loop/joinir] Adding '{}' to params (external_ref)", ext_ref ); } params.push(serde_json::json!(ext_ref)); } // Step 2: Construct JSON v0 format with "defs" array // The function is named "simple" to match JoinIR Frontend's pattern matching // Phase 50: Include i/acc/n Local declarations to satisfy JoinIR Frontend expectations let program_json = serde_json::json!({ "defs": [ { "name": "simple", "params": params, "body": { "type": "Block", "body": [ // Phase 50: Inject i/acc/n Local declarations i_local, acc_local, n_local, { "type": "Loop", "cond": condition_json, // JoinIR Frontend expects "cond" not "condition" "body": body_json }, // Return the accumulator (or null for side-effect loops) { "type": "Return", "value": { "kind": "Variable", "name": "acc" } } ] } } ] }); if debug { eprintln!( "[cf_loop/joinir] Generated JSON v0 for {}: {}", func_name, serde_json::to_string_pretty(&program_json).unwrap_or_default() ); } // Step 3: Lower to JoinIR // Phase 49-4: Use catch_unwind for graceful fallback on unsupported patterns // The JoinIR Frontend may panic if the loop doesn't match expected patterns // (e.g., missing variable initializations like "i must be initialized") let join_module = { let json_clone = program_json.clone(); let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { let mut lowerer = AstToJoinIrLowerer::new(); lowerer.lower_program_json(&json_clone) })); match result { Ok(module) => module, Err(e) => { // Extract panic message for debugging let panic_msg = if let Some(s) = e.downcast_ref::<&str>() { s.to_string() } else if let Some(s) = e.downcast_ref::() { s.clone() } else { "unknown panic".to_string() }; if debug { eprintln!( "[cf_loop/joinir] JoinIR lowering failed for {}: {}, falling back to legacy", func_name, panic_msg ); } // Return None to fall back to legacy LoopBuilder return Ok(None); } } }; // Phase 49-3 MVP: Use empty meta map (full if-analysis is Phase 40+ territory) let join_meta = JoinFuncMetaMap::new(); if debug { eprintln!( "[cf_loop/joinir] JoinModule has {} functions, entry={:?}", join_module.functions.len(), join_module.entry ); } // Step 4: Convert JoinModule to MIR let mir_module = convert_join_module_to_mir_with_meta(&join_module, &join_meta) .map_err(|e| format!("JoinIR→MIR conversion failed: {}", e.message))?; if debug { eprintln!( "[cf_loop/joinir] MirModule has {} functions", mir_module.functions.len() ); for (name, func) in &mir_module.functions { eprintln!( "[cf_loop/joinir] - {}: {} blocks, entry={:?}", name, func.blocks.len(), func.entry_block ); // Phase 189: Debug - show block contents for (block_id, block) in &func.blocks { eprintln!("[cf_loop/joinir] Block {:?}: {} instructions", block_id, block.instructions.len()); for (i, inst) in block.instructions.iter().enumerate() { eprintln!("[cf_loop/joinir] [{}] {:?}", i, inst); } if let Some(ref term) = block.terminator { eprintln!("[cf_loop/joinir] terminator: {:?}", term); } } } } // Step 5: Merge MIR blocks into current_function // For Phase 49-3, we'll use a simplified approach: // - Add generated blocks to current_function // - Jump from current_block to the entry of generated loop // - The loop exit becomes the new current_block // Phase 188-Impl-3: Pass None for boundary (legacy path without boundary) self.merge_joinir_mir_blocks(&mir_module, None, debug)?; // Return void for now (loop doesn't have a meaningful return value in this context) let void_val = self.next_value_id(); self.emit_instruction(MirInstruction::Const { dst: void_val, value: ConstValue::Void, })?; Ok(Some(void_val)) } /// Phase 188-Impl-1-F: Pattern 1 (Simple While Loop) minimal lowerer /// /// This bypasses the LoopFrontendBinding JSON path and directly calls /// the Pattern 1 minimal lowerer for apps/tests/loop_min_while.hako /// /// # Phase 188-Impl-2: Host Variable Integration /// /// Extracts the loop variable from the host function (e.g., `i` from `i < 3`) /// and passes it to the Pattern 1 lowerer along with a ValueId allocator. /// /// # Pipeline /// 1. Extract loop variable name from condition /// 2. Look up ValueId in host variable_map /// 3. Create Pattern1Context with host variable + allocator /// 4. Call simple_while_minimal::lower_simple_while_minimal() → JoinModule /// 5. convert_join_module_to_mir_with_meta() → MirModule /// 6. Merge MIR blocks into current_function fn cf_loop_pattern1_minimal( &mut self, condition: &ASTNode, _body: &[ASTNode], _func_name: &str, debug: bool, ) -> Result, String> { use crate::mir::join_ir::lowering::simple_while_minimal::{ lower_simple_while_minimal, Pattern1Context, }; use crate::mir::join_ir_vm_bridge::convert_join_module_to_mir_with_meta; use crate::mir::BasicBlockId; use std::collections::{BTreeMap, BTreeSet}; if debug { eprintln!("[cf_loop/joinir/pattern1] Calling Pattern 1 minimal lowerer"); } // Phase 188-Impl-2: Extract loop variable from condition // For `i < 3`, extract `i` and look up its ValueId in variable_map let loop_var_name = self.extract_loop_variable_from_condition(condition)?; let loop_var_id = self .variable_map .get(&loop_var_name) .copied() .ok_or_else(|| { format!( "[cf_loop/pattern1] Loop variable '{}' not found in variable_map", loop_var_name ) })?; if debug { eprintln!( "[cf_loop/joinir/pattern1] Loop variable '{}' → {:?}", loop_var_name, loop_var_id ); } // Create a minimal LoopScopeShape (Phase 188: hardcoded for loop_min_while.hako) // Pattern 1 lowerer ignores the scope anyway, so this is just a placeholder use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape; let scope = LoopScopeShape { header: BasicBlockId(0), body: BasicBlockId(0), latch: BasicBlockId(0), exit: BasicBlockId(0), pinned: BTreeSet::new(), carriers: BTreeSet::new(), body_locals: BTreeSet::new(), exit_live: BTreeSet::new(), progress_carrier: None, variable_definitions: BTreeMap::new(), }; // Phase 188-Impl-2: Create Pattern1Context with host variable + allocator // Clone value_gen to move into closure let mut value_gen_clone = self.value_gen.clone(); let ctx = Pattern1Context { loop_var: loop_var_id, value_allocator: Box::new(move || value_gen_clone.next()), }; // Call Pattern 1 lowerer with host context let join_module = match lower_simple_while_minimal(scope, Some(ctx)) { Some(module) => module, None => { if debug { eprintln!("[cf_loop/joinir/pattern1] Pattern 1 lowerer returned None"); } return Ok(None); } }; if debug { eprintln!( "[cf_loop/joinir/pattern1] JoinModule generated with {} functions", join_module.functions.len() ); } // Convert JoinModule to MirModule // Phase 188: Pass empty meta map since Pattern 1 lowerer doesn't use metadata use crate::mir::join_ir::frontend::JoinFuncMetaMap; let empty_meta: JoinFuncMetaMap = BTreeMap::new(); let mir_module = convert_join_module_to_mir_with_meta(&join_module, &empty_meta) .map_err(|e| format!("[cf_loop/joinir/pattern1] MIR conversion failed: {:?}", e))?; if debug { eprintln!( "[cf_loop/joinir/pattern1] MirModule generated with {} functions", mir_module.functions.len() ); } // Merge JoinIR blocks into current function // Phase 188-Impl-3: Create and pass JoinInlineBoundary for Pattern 1 let boundary = crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary::new_inputs_only( vec![ValueId(0)], // JoinIR's main() parameter (loop variable) vec![loop_var_id], // Host's loop variable ); self.merge_joinir_mir_blocks(&mir_module, Some(&boundary), debug)?; // Phase 188-Impl-4-FIX: Return Void instead of trying to emit Const // // PROBLEM: Emitting instructions after merge_joinir_mir_blocks is fragile because: // 1. merge creates exit block and switches to it // 2. We try to add Const to exit block // 3. But subsequent code (return statement) might overwrite the block // // SOLUTION: Loops don't produce values - they return Void. // The subsequent "return 0" statement will emit its own Const + Return. // // This is cleaner because: // - Loop lowering doesn't need to know about the return value // - The return statement handles its own code generation // - No risk of instructions being lost due to block management issues let void_val = crate::mir::builder::emission::constant::emit_void(self); if debug { eprintln!( "[cf_loop/joinir/pattern1] Loop complete, returning Void {:?}", void_val ); } Ok(Some(void_val)) } /// Phase 188-Impl-2: Pattern 2 (Loop with Conditional Break) minimal lowerer /// /// Similar to Pattern 1, but handles loops with break statements. /// /// # Steps /// 1. Extract loop variable from condition /// 2. Generate JoinIR using loop_with_break_minimal /// 3. Convert JoinModule → MirModule /// 4. Create JoinInlineBoundary for input mapping /// 5. Merge MIR blocks into current_function /// 6. Return Void (loop doesn't produce values) fn cf_loop_pattern2_with_break( &mut self, condition: &ASTNode, _body: &[ASTNode], _func_name: &str, debug: bool, ) -> Result, String> { use crate::mir::join_ir::lowering::loop_with_break_minimal::lower_loop_with_break_minimal; use crate::mir::join_ir_vm_bridge::convert_join_module_to_mir_with_meta; use crate::mir::BasicBlockId; use std::collections::{BTreeMap, BTreeSet}; if debug { eprintln!("[cf_loop/joinir/pattern2] Calling Pattern 2 minimal lowerer"); } // Phase 188-Impl-2: Extract loop variable from condition // For `i < 3`, extract `i` and look up its ValueId in variable_map let loop_var_name = self.extract_loop_variable_from_condition(condition)?; let loop_var_id = self .variable_map .get(&loop_var_name) .copied() .ok_or_else(|| { format!( "[cf_loop/pattern2] Loop variable '{}' not found in variable_map", loop_var_name ) })?; if debug { eprintln!( "[cf_loop/joinir/pattern2] Loop variable '{}' → {:?}", loop_var_name, loop_var_id ); } // Create a minimal LoopScopeShape (Phase 188: hardcoded for joinir_min_loop.hako) // Pattern 2 lowerer ignores the scope anyway, so this is just a placeholder use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape; let scope = LoopScopeShape { header: BasicBlockId(0), body: BasicBlockId(0), latch: BasicBlockId(0), exit: BasicBlockId(0), pinned: BTreeSet::new(), carriers: BTreeSet::new(), body_locals: BTreeSet::new(), exit_live: BTreeSet::new(), progress_carrier: None, variable_definitions: BTreeMap::new(), }; // Call Pattern 2 lowerer let join_module = match lower_loop_with_break_minimal(scope) { Some(module) => module, None => { if debug { eprintln!("[cf_loop/joinir/pattern2] Pattern 2 lowerer returned None"); } return Ok(None); } }; if debug { eprintln!( "[cf_loop/joinir/pattern2] JoinModule generated with {} functions", join_module.functions.len() ); } // Convert JoinModule to MirModule // Phase 188: Pass empty meta map since Pattern 2 lowerer doesn't use metadata use crate::mir::join_ir::frontend::JoinFuncMetaMap; let empty_meta: JoinFuncMetaMap = BTreeMap::new(); let mir_module = convert_join_module_to_mir_with_meta(&join_module, &empty_meta) .map_err(|e| format!("[cf_loop/joinir/pattern2] MIR conversion failed: {:?}", e))?; if debug { eprintln!( "[cf_loop/joinir/pattern2] MirModule generated with {} functions", mir_module.functions.len() ); } // Merge JoinIR blocks into current function // Phase 188-Impl-2: Create and pass JoinInlineBoundary for Pattern 2 let boundary = crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary::new_inputs_only( vec![ValueId(0)], // JoinIR's main() parameter (loop variable init) vec![loop_var_id], // Host's loop variable ); self.merge_joinir_mir_blocks(&mir_module, Some(&boundary), debug)?; // Phase 188-Impl-2: Return Void (loops don't produce values) // The subsequent "return i" statement will emit its own Load + Return let void_val = crate::mir::builder::emission::constant::emit_void(self); if debug { eprintln!( "[cf_loop/joinir/pattern2] Loop complete, returning Void {:?}", void_val ); } Ok(Some(void_val)) } /// Phase 188-Impl-3: Pattern 3 (Loop with If-Else PHI) minimal lowerer /// /// Handles loops with if-else statements that assign to carrier variables. /// /// # Steps /// 1. Extract loop variables (carriers: i + sum) /// 2. Generate JoinIR using loop_with_if_phi_minimal /// 3. Convert JoinModule → MirModule /// 4. Create JoinInlineBoundary for input mapping /// 5. Merge MIR blocks into current_function /// 6. Return Void (loop doesn't produce values) fn cf_loop_pattern3_with_if_phi( &mut self, condition: &ASTNode, _body: &[ASTNode], _func_name: &str, debug: bool, ) -> Result, String> { use crate::mir::join_ir::lowering::loop_with_if_phi_minimal::lower_loop_with_if_phi_pattern; use crate::mir::join_ir_vm_bridge::convert_join_module_to_mir_with_meta; use crate::mir::BasicBlockId; use std::collections::{BTreeMap, BTreeSet}; if debug { eprintln!("[cf_loop/joinir/pattern3] Calling Pattern 3 minimal lowerer"); } // Phase 188-Impl-3: Extract loop variable from condition // For `i <= 5`, extract `i` and look up its ValueId in variable_map let loop_var_name = self.extract_loop_variable_from_condition(condition)?; let loop_var_id = self .variable_map .get(&loop_var_name) .copied() .ok_or_else(|| { format!( "[cf_loop/pattern3] Loop variable '{}' not found in variable_map", loop_var_name ) })?; // Phase 188-Impl-3: Also get the accumulator variable (sum) // For Pattern 3, we need both i and sum let sum_var_id = self .variable_map .get("sum") .copied() .ok_or_else(|| { format!( "[cf_loop/pattern3] Accumulator variable 'sum' not found in variable_map" ) })?; if debug { eprintln!( "[cf_loop/joinir/pattern3] Loop variables: '{}' → {:?}, 'sum' → {:?}", loop_var_name, loop_var_id, sum_var_id ); } // Create a minimal LoopScopeShape (Phase 188: hardcoded for loop_if_phi.hako) // Pattern 3 lowerer ignores the scope anyway, so this is just a placeholder use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape; let scope = LoopScopeShape { header: BasicBlockId(0), body: BasicBlockId(0), latch: BasicBlockId(0), exit: BasicBlockId(0), pinned: BTreeSet::new(), carriers: BTreeSet::new(), body_locals: BTreeSet::new(), exit_live: BTreeSet::new(), progress_carrier: None, variable_definitions: BTreeMap::new(), }; // Call Pattern 3 lowerer let join_module = match lower_loop_with_if_phi_pattern(scope) { Some(module) => module, None => { if debug { eprintln!("[cf_loop/joinir/pattern3] Pattern 3 lowerer returned None"); } return Ok(None); } }; if debug { eprintln!( "[cf_loop/joinir/pattern3] JoinModule generated with {} functions", join_module.functions.len() ); } // Convert JoinModule to MirModule // Phase 188: Pass empty meta map since Pattern 3 lowerer doesn't use metadata use crate::mir::join_ir::frontend::JoinFuncMetaMap; let empty_meta: JoinFuncMetaMap = BTreeMap::new(); let mir_module = convert_join_module_to_mir_with_meta(&join_module, &empty_meta) .map_err(|e| format!("[cf_loop/joinir/pattern3] MIR conversion failed: {:?}", e))?; if debug { eprintln!( "[cf_loop/joinir/pattern3] MirModule generated with {} functions", mir_module.functions.len() ); } // Merge JoinIR blocks into current function // Phase 188-Impl-3: Create and pass JoinInlineBoundary for Pattern 3 // Pattern 3 has TWO carriers: i and sum let boundary = crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary::new_inputs_only( vec![ValueId(0), ValueId(1)], // JoinIR's main() parameters (i, sum init) vec![loop_var_id, sum_var_id], // Host's loop variables ); self.merge_joinir_mir_blocks(&mir_module, Some(&boundary), debug)?; // Phase 188-Impl-3: Return Void (loops don't produce values) let void_val = crate::mir::builder::emission::constant::emit_void(self); if debug { eprintln!( "[cf_loop/joinir/pattern3] Loop complete, returning Void {:?}", void_val ); } Ok(Some(void_val)) } /// Phase 49-3.2: Merge JoinIR-generated MIR blocks into current_function /// /// # Phase 189: Multi-Function MIR Merge /// /// This merges JoinIR-generated blocks by: /// 1. Remapping all block IDs across ALL functions to avoid conflicts /// 2. Remapping all value IDs across ALL functions to avoid conflicts /// 3. Adding all blocks from all functions to current_function /// 4. Jumping from current_block to the entry block /// 5. Converting Return → Jump to exit block for all functions /// /// **Multi-Function Support** (Phase 189): /// - Pattern 1 (Simple While) generates 3 functions: entry + loop_step + k_exit /// - All functions are flattened into current_function with global ID remapping /// - Single exit block receives all Return instructions from all functions /// /// # Phase 188-Impl-3: JoinInlineBoundary Support /// /// When `boundary` is provided, injects Copy instructions at the entry block /// to connect host ValueIds to JoinIR local ValueIds: /// /// ```text /// entry_block: /// // Injected by boundary /// ValueId(100) = Copy ValueId(4) // join_input → host_input /// // Original JoinIR instructions follow... /// ``` /// /// This enables clean separation: JoinIR uses local IDs (0,1,2...), /// host uses its own IDs, and Copy instructions bridge the gap. fn merge_joinir_mir_blocks( &mut self, mir_module: &crate::mir::MirModule, boundary: Option<&crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary>, debug: bool, ) -> Result<(), String> { use crate::mir::{BasicBlock, BasicBlockId, MirInstruction, ValueId}; use std::collections::HashMap; // Phase 189: Use new ID remapper Box use super::joinir_id_remapper::JoinIrIdRemapper; if debug { eprintln!( "[cf_loop/joinir] merge_joinir_mir_blocks called with {} functions", mir_module.functions.len() ); } // Phase 189: Create ID remapper for ValueId/BlockId translation let mut remapper = JoinIrIdRemapper::new(); // Phase 189: Map function names to their entry blocks (for Call→Jump conversion) let mut function_entry_map: HashMap = HashMap::new(); // 1. Allocate new block IDs for ALL functions (Phase 189) // DETERMINISM FIX: Sort functions by name to ensure consistent iteration order if debug { eprintln!("[cf_loop/joinir] Phase 189: Allocating block IDs for all functions"); } let mut functions: Vec<_> = mir_module.functions.iter().collect(); functions.sort_by_key(|(name, _)| name.as_str()); for (func_name, func) in functions { if debug { eprintln!("[cf_loop/joinir] Function: {}", func_name); } // DETERMINISM FIX: Sort blocks by ID to ensure consistent iteration order let mut blocks: Vec<_> = func.blocks.iter().collect(); blocks.sort_by_key(|(id, _)| id.0); for (old_block_id, _) in blocks { let new_block_id = self.block_gen.next(); // Use remapper to store composite key mapping remapper.set_block(func_name.clone(), *old_block_id, new_block_id); if debug { eprintln!( "[cf_loop/joinir] Block remap: {}:{:?} → {:?}", func_name, old_block_id, new_block_id ); } } // Map function entry blocks for Call→Jump conversion let entry_block_new = remapper.get_block(func_name, func.entry_block) .ok_or_else(|| format!("Entry block not found for {}", func_name))?; function_entry_map.insert(func_name.clone(), entry_block_new); if debug { eprintln!( "[cf_loop/joinir] Entry map: {} → {:?}", func_name, entry_block_new ); } } // 2. Create exit block for Return conversion (single for all functions) let exit_block_id = self.block_gen.next(); if debug { eprintln!("[cf_loop/joinir] Exit block: {:?}", exit_block_id); } // 3. Collect all ValueIds used across ALL functions (Phase 189) // Also build a map of ValueId → function name for Call→Jump conversion // Phase 188-Impl-3: Also collect function parameters for tail call conversion if debug { eprintln!("[cf_loop/joinir] Phase 189: Collecting value IDs from all functions"); } let mut used_values: std::collections::BTreeSet = std::collections::BTreeSet::new(); let mut value_to_func_name: HashMap = HashMap::new(); let mut function_params: HashMap> = HashMap::new(); for (func_name, func) in &mir_module.functions { // Phase 188-Impl-3: Collect function parameters for tail call conversion function_params.insert(func_name.clone(), func.params.clone()); for block in func.blocks.values() { // Phase 189: Use remapper to collect values let block_values = remapper.collect_values_in_block(block); used_values.extend(block_values); // Phase 189: Track Const String instructions that define function names for inst in &block.instructions { if let MirInstruction::Const { dst, value } = inst { if let crate::mir::types::ConstValue::String(s) = value { if function_entry_map.contains_key(s) { value_to_func_name.insert(*dst, s.clone()); if debug { eprintln!( "[cf_loop/joinir] Found function name constant: {:?} = '{}'", dst, s ); } } } } } } // Also collect parameter ValueIds for param in &func.params { used_values.insert(*param); } } // 4. Allocate new ValueIds for all collected values for old_value in used_values { let new_value = self.next_value_id(); remapper.set_value(old_value, new_value); if debug { eprintln!( "[cf_loop/joinir] Value remap: {:?} → {:?}", old_value, new_value ); } } // 5. Merge ALL functions (Phase 189: iterate over all, not just first) // DETERMINISM FIX: Sort functions by name to ensure consistent iteration order if debug { eprintln!("[cf_loop/joinir] Phase 189: Merging {} functions", mir_module.functions.len()); } // Phase 189: Iterate over both names and functions (need name for composite keys) let mut functions_merge: Vec<_> = mir_module.functions.iter().collect(); functions_merge.sort_by_key(|(name, _)| name.as_str()); for (func_name, func) in functions_merge { if debug { eprintln!( "[cf_loop/joinir] Merging function '{}' with {} blocks, entry={:?}", func_name, func.blocks.len(), func.entry_block ); } // Build a local block map for this function (for remap_instruction compatibility) let mut local_block_map: HashMap = HashMap::new(); for old_block_id in func.blocks.keys() { let new_block_id = remapper.get_block(func_name, *old_block_id) .ok_or_else(|| format!("Block {:?} not found for {}", old_block_id, func_name))?; local_block_map.insert(*old_block_id, new_block_id); } // Clone and remap all blocks from this function // DETERMINISM FIX: Sort blocks by ID to ensure consistent iteration order let mut blocks_merge: Vec<_> = func.blocks.iter().collect(); blocks_merge.sort_by_key(|(id, _)| id.0); for (old_block_id, old_block) in blocks_merge { // Use remapper to get correct mapping for this function's block let new_block_id = remapper.get_block(func_name, *old_block_id) .ok_or_else(|| format!("Block {:?} not found for {}", old_block_id, func_name))?; let mut new_block = BasicBlock::new(new_block_id); // Remap instructions (Phase 189: Convert inter-function Calls to control flow) let mut found_tail_call = false; let mut tail_call_target: Option<(BasicBlockId, Vec)> = None; // First pass: Process all instructions, identify tail calls for inst in &old_block.instructions { // Phase 189: Skip Const String instructions that define function names if let MirInstruction::Const { dst, value } = inst { if let crate::mir::types::ConstValue::String(_) = value { if value_to_func_name.contains_key(dst) { if debug { eprintln!("[cf_loop/joinir] Skipping function name const: {:?}", inst); } continue; // Skip this instruction } } } // Phase 189: Detect tail calls and save parameters if let MirInstruction::Call { func, args, .. } = inst { if let Some(func_name) = value_to_func_name.get(func) { if let Some(&target_block) = function_entry_map.get(func_name) { // This is a tail call - save info and skip the Call instruction itself let remapped_args: Vec = args .iter() .map(|&v| remapper.get_value(v).unwrap_or(v)) .collect(); tail_call_target = Some((target_block, remapped_args)); found_tail_call = true; if debug { eprintln!( "[cf_loop/joinir] Detected tail call to '{}' (args={:?}), will convert to Jump", func_name, args ); } continue; // Skip the Call instruction itself } } } // Process regular instructions - Phase 189: Use remapper.remap_instruction() + manual block remapping let remapped = remapper.remap_instruction(inst); // Phase 189 FIX: Manual block remapping for Branch/Phi (JoinIrIdRemapper doesn't know func_name) let remapped_with_blocks = match remapped { MirInstruction::Branch { condition, then_bb, else_bb } => { MirInstruction::Branch { condition, then_bb: local_block_map.get(&then_bb).copied().unwrap_or(then_bb), else_bb: local_block_map.get(&else_bb).copied().unwrap_or(else_bb), } } MirInstruction::Phi { dst, inputs, type_hint: None } => { MirInstruction::Phi { dst, inputs: inputs.iter().map(|(bb, val)| { (local_block_map.get(bb).copied().unwrap_or(*bb), *val) }).collect(), type_hint: None, } } other => other, }; if debug { match inst { MirInstruction::BoxCall { .. } => { eprintln!("[cf_loop/joinir] Adding BoxCall to block {:?}: {:?}", new_block_id, inst); } _ => {} } } new_block.instructions.push(remapped_with_blocks); } // Second pass: Insert parameter bindings for tail calls // Phase 188-Impl-3: Use actual parameter ValueIds from target function if let Some((target_block, args)) = tail_call_target { if debug { eprintln!( "[cf_loop/joinir] Inserting param bindings for tail call to {:?}", target_block ); } // Find the target function name from the target_block // We need to reverse-lookup the function name from the entry block let mut target_func_name: Option = None; for (fname, &entry_block) in &function_entry_map { if entry_block == target_block { target_func_name = Some(fname.clone()); break; } } if let Some(target_func_name) = target_func_name { if let Some(target_params) = function_params.get(&target_func_name) { // Insert Copy instructions for parameter binding for (i, arg_val_remapped) in args.iter().enumerate() { if i < target_params.len() { let param_val_original = target_params[i]; if let Some(param_val_remapped) = remapper.get_value(param_val_original) { new_block.instructions.push(MirInstruction::Copy { dst: param_val_remapped, src: *arg_val_remapped, }); if debug { eprintln!( "[cf_loop/joinir] Param binding: arg {:?} → param {:?}", arg_val_remapped, param_val_remapped ); } } } } } } // Set terminator to Jump new_block.terminator = Some(MirInstruction::Jump { target: target_block, }); } new_block.instruction_spans = old_block.instruction_spans.clone(); // Remap terminator (convert Return → Jump to exit) if not already set by tail call if !found_tail_call { if let Some(ref term) = old_block.terminator { let remapped_term = match term { MirInstruction::Return { value } => { // Convert Return to Jump to exit block // All functions return to same exit block (Phase 189) if let Some(ret_val) = value { let remapped_val = remapper.get_value(*ret_val).unwrap_or(*ret_val); if debug { eprintln!( "[cf_loop/joinir] Return({:?}) → Jump to exit", remapped_val ); } } MirInstruction::Jump { target: exit_block_id, } } MirInstruction::Jump { target } => { // Phase 189 FIX: Remap block ID for Jump MirInstruction::Jump { target: local_block_map.get(target).copied().unwrap_or(*target), } } MirInstruction::Branch { condition, then_bb, else_bb } => { // Phase 189 FIX: Remap block IDs for Branch let remapped = remapper.remap_instruction(term); match remapped { MirInstruction::Branch { condition, then_bb, else_bb } => { MirInstruction::Branch { condition, then_bb: local_block_map.get(&then_bb).copied().unwrap_or(then_bb), else_bb: local_block_map.get(&else_bb).copied().unwrap_or(else_bb), } } other => other, } } _ => remapper.remap_instruction(term), }; new_block.terminator = Some(remapped_term); } } // Add block to current function if let Some(ref mut current_func) = self.current_function { current_func.add_block(new_block); } } } // Phase 188-Impl-3: Inject Copy instructions for boundary inputs using BoundaryInjector if let Some(boundary) = boundary { use super::joinir_inline_boundary_injector::BoundaryInjector; // Get entry function's entry block (first function by convention) let (entry_func_name, entry_func) = mir_module .functions .iter() .next() .ok_or("JoinIR module has no functions")?; let entry_block_remapped = remapper.get_block(entry_func_name, entry_func.entry_block) .ok_or_else(|| format!("Entry block not found for {}", entry_func_name))?; // Create HashMap from remapper for BoundaryInjector (temporary adapter) let mut value_map_for_injector = HashMap::new(); for join_in in &boundary.join_inputs { if let Some(remapped) = remapper.get_value(*join_in) { value_map_for_injector.insert(*join_in, remapped); } } // Use BoundaryInjector to inject Copy instructions if let Some(ref mut current_func) = self.current_function { BoundaryInjector::inject_boundary_copies( current_func, entry_block_remapped, boundary, &value_map_for_injector, debug, )?; } } // 6. Create exit block (empty for now, will be populated after loop) if let Some(ref mut func) = self.current_function { let exit_block = BasicBlock::new(exit_block_id); func.add_block(exit_block); } // 7. Jump from current block to entry function's entry block // Entry function is first function by convention let (entry_func_name, entry_func) = mir_module .functions .iter() .next() .ok_or("JoinIR module has no functions")?; // Use remapper to get entry block mapping let entry_block = remapper.get_block(entry_func_name, entry_func.entry_block) .ok_or_else(|| format!("Entry block not found for {}", entry_func_name))?; if debug { eprintln!("[cf_loop/joinir] Jumping to entry block: {:?}", entry_block); } crate::mir::builder::emission::branch::emit_jump(self, entry_block)?; // 8. Switch to exit block for subsequent code self.start_new_block(exit_block_id)?; if debug { eprintln!( "[cf_loop/joinir] Phase 189: Merge complete: {} functions merged, continuing from {:?}", mir_module.functions.len(), exit_block_id ); } Ok(()) } // Phase 189: collect_values_in_block/collect_values_in_instruction removed // These functions are now provided by JoinIrIdRemapper::collect_values_in_block() // Phase 189: remap_joinir_instruction/remap_instruction removed // These functions are now provided by JoinIrIdRemapper::remap_instruction() /// Control-flow: try/catch/finally pub(super) fn cf_try_catch( &mut self, try_body: Vec, catch_clauses: Vec, finally_body: Option>, ) -> Result { if std::env::var("NYASH_BUILDER_DISABLE_TRYCATCH") .ok() .as_deref() == Some("1") { let try_ast = ASTNode::Program { statements: try_body, span: crate::ast::Span::unknown(), }; let result = self.build_expression(try_ast)?; return Ok(result); } let try_block = self.block_gen.next(); let catch_block = self.block_gen.next(); let finally_block = if finally_body.is_some() { Some(self.block_gen.next()) } else { None }; let exit_block = self.block_gen.next(); // Snapshot deferred-return state let saved_defer_active = self.return_defer_active; let saved_defer_slot = self.return_defer_slot; let saved_defer_target = self.return_defer_target; let saved_deferred_flag = self.return_deferred_emitted; let saved_in_cleanup = self.in_cleanup_block; let saved_allow_ret = self.cleanup_allow_return; let saved_allow_throw = self.cleanup_allow_throw; let ret_slot = self.next_value_id(); self.return_defer_active = true; self.return_defer_slot = Some(ret_slot); self.return_deferred_emitted = false; self.return_defer_target = Some(finally_block.unwrap_or(exit_block)); if let Some(catch_clause) = catch_clauses.first() { if std::env::var("NYASH_DEBUG_TRYCATCH").ok().as_deref() == Some("1") { eprintln!( "[BUILDER] Emitting catch handler for {:?}", catch_clause.exception_type ); } let exception_value = self.next_value_id(); self.emit_instruction(MirInstruction::Catch { exception_type: catch_clause.exception_type.clone(), exception_value, handler_bb: catch_block, })?; } // Enter try block crate::mir::builder::emission::branch::emit_jump(self, try_block)?; self.start_new_block(try_block)?; let try_ast = ASTNode::Program { statements: try_body, span: crate::ast::Span::unknown(), }; let _try_result = self.build_expression(try_ast)?; if !self.is_current_block_terminated() { let next_target = finally_block.unwrap_or(exit_block); crate::mir::builder::emission::branch::emit_jump(self, next_target)?; } // Catch block self.start_new_block(catch_block)?; if std::env::var("NYASH_DEBUG_TRYCATCH").ok().as_deref() == Some("1") { eprintln!("[BUILDER] Enter catch block {:?}", catch_block); } if let Some(catch_clause) = catch_clauses.first() { let catch_ast = ASTNode::Program { statements: catch_clause.body.clone(), span: crate::ast::Span::unknown(), }; self.build_expression(catch_ast)?; } if !self.is_current_block_terminated() { let next_target = finally_block.unwrap_or(exit_block); crate::mir::builder::emission::branch::emit_jump(self, next_target)?; } // Finally let mut cleanup_terminated = false; if let (Some(finally_block_id), Some(finally_statements)) = (finally_block, finally_body) { self.start_new_block(finally_block_id)?; self.in_cleanup_block = true; self.cleanup_allow_return = crate::config::env::cleanup_allow_return(); self.cleanup_allow_throw = crate::config::env::cleanup_allow_throw(); self.return_defer_active = false; // do not defer inside cleanup let finally_ast = ASTNode::Program { statements: finally_statements, span: crate::ast::Span::unknown(), }; self.build_expression(finally_ast)?; cleanup_terminated = self.is_current_block_terminated(); if !cleanup_terminated { crate::mir::builder::emission::branch::emit_jump(self, exit_block)?; } self.in_cleanup_block = false; } // Exit block self.start_new_block(exit_block)?; let result = if self.return_deferred_emitted && !cleanup_terminated { self.emit_instruction(MirInstruction::Return { value: Some(ret_slot), })?; crate::mir::builder::emission::constant::emit_void(self) } else { crate::mir::builder::emission::constant::emit_void(self) }; // Restore context self.return_defer_active = saved_defer_active; self.return_defer_slot = saved_defer_slot; self.return_defer_target = saved_defer_target; self.return_deferred_emitted = saved_deferred_flag; self.in_cleanup_block = saved_in_cleanup; self.cleanup_allow_return = saved_allow_ret; self.cleanup_allow_throw = saved_allow_throw; Ok(result) } /// Phase 188-Impl-2: Extract loop variable name from condition /// /// For `i < 3`, extracts `i`. /// For `arr.length() > 0`, extracts `arr`. /// /// This is a minimal implementation that handles simple comparison patterns. fn extract_loop_variable_from_condition(&self, condition: &ASTNode) -> Result { use crate::ast::BinaryOperator; match condition { ASTNode::BinaryOp { operator, left, .. } if matches!( operator, BinaryOperator::Less | BinaryOperator::Greater | BinaryOperator::LessEqual | BinaryOperator::GreaterEqual ) => { // Binary comparison: extract variable from left side match &**left { ASTNode::Variable { name, .. } => Ok(name.clone()), _ => Err(format!( "[cf_loop/pattern1] Cannot extract loop variable from condition: {:?}", condition )), } } _ => Err(format!( "[cf_loop/pattern1] Unsupported loop condition pattern: {:?}", condition )), } } /// Control-flow: throw pub(super) fn cf_throw(&mut self, expression: ASTNode) -> Result { if self.in_cleanup_block && !self.cleanup_allow_throw { return Err("throw is not allowed inside cleanup block (enable NYASH_CLEANUP_ALLOW_THROW=1 to permit)".to_string()); } if std::env::var("NYASH_BUILDER_DISABLE_THROW").ok().as_deref() == Some("1") { let v = self.build_expression(expression)?; self.emit_instruction(MirInstruction::ExternCall { dst: None, iface_name: "env.debug".to_string(), method_name: "trace".to_string(), args: vec![v], effects: EffectMask::PURE.add(Effect::Debug), })?; return Ok(v); } let exception_value = self.build_expression(expression)?; self.emit_instruction(MirInstruction::Throw { exception: exception_value, effects: EffectMask::PANIC, })?; Ok(exception_value) } }