//! 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 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: Enable JoinIR for main function "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-1-F: Route "main" through Pattern 1 minimal lowerer if func_name == "main" { if debug { eprintln!("[cf_loop/joinir] Routing 'main' through Pattern 1 minimal lowerer"); } return self.cf_loop_pattern1_minimal(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 self.merge_joinir_mir_blocks(&mir_module, 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 self.merge_joinir_mir_blocks(&mir_module, debug)?; // Return void/0 as loop result (Pattern 1 loops return 0) // Use the current block to emit the constant let zero_val = self.value_gen.next(); use crate::mir::types::ConstValue; let current_block = self.current_block.ok_or_else(|| { "[cf_loop/joinir/pattern1] No current block available".to_string() })?; if let Some(ref mut func) = self.current_function { if let Some(block) = func.get_block_mut(current_block) { block.instructions.push(crate::mir::MirInstruction::Const { dst: zero_val, value: ConstValue::Integer(0), }); } } Ok(Some(zero_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 fn merge_joinir_mir_blocks( &mut self, mir_module: &crate::mir::MirModule, debug: bool, ) -> Result<(), String> { use crate::mir::{BasicBlock, BasicBlockId, MirInstruction, ValueId}; use std::collections::HashMap; if debug { eprintln!( "[cf_loop/joinir] merge_joinir_mir_blocks called with {} functions", mir_module.functions.len() ); } // Phase 189: Global ID maps for ALL functions (not just first) // CRITICAL: Use composite keys (func_name, BasicBlockId) to avoid collisions! // Different functions can have blocks with same BasicBlockId (e.g., both have bb0) let mut block_map: HashMap<(String, BasicBlockId), BasicBlockId> = HashMap::new(); let mut value_map: HashMap = HashMap::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 composite key to avoid collision between functions block_map.insert((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 = block_map[&(func_name.clone(), func.entry_block)]; 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 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(); for func in mir_module.functions.values() { for block in func.blocks.values() { Self::collect_values_in_block(block, &mut used_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(); value_map.insert(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 = block_map[&(func_name.clone(), *old_block_id)]; 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 composite key to get correct mapping for this function's block let new_block_id = block_map[&(func_name.clone(), *old_block_id)]; 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| value_map.get(&v).copied().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 let remapped = Self::remap_joinir_instruction( inst, &value_map, &local_block_map, // Use local map for this function &function_entry_map, &value_to_func_name, debug, ); if debug { match inst { MirInstruction::BoxCall { .. } => { eprintln!("[cf_loop/joinir] Adding BoxCall to block {:?}: {:?}", new_block_id, inst); } _ => {} } } new_block.instructions.push(remapped); } // Second pass: Insert parameter bindings for tail calls if let Some((target_block, args)) = tail_call_target { if debug { eprintln!( "[cf_loop/joinir] Inserting param bindings for tail call to {:?}", target_block ); } // Insert Copy instructions for parameter binding for (i, arg_val_remapped) in args.iter().enumerate() { let param_val_original = ValueId(i as u32); if let Some(¶m_val_remapped) = value_map.get(¶m_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 = value_map.get(ret_val).copied().unwrap_or(*ret_val); if debug { eprintln!( "[cf_loop/joinir] Return({:?}) → Jump to exit", remapped_val ); } } MirInstruction::Jump { target: exit_block_id, } } _ => Self::remap_joinir_instruction( term, &value_map, &local_block_map, // Use local map for this function &function_entry_map, &value_to_func_name, debug, ), }; 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); } } } // 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 composite key to get entry block mapping let entry_block = block_map[&(entry_func_name.clone(), entry_func.entry_block)]; 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(()) } /// Collect all ValueIds used in a block fn collect_values_in_block( block: &crate::mir::BasicBlock, values: &mut std::collections::BTreeSet, ) { for inst in &block.instructions { Self::collect_values_in_instruction(inst, values); } if let Some(ref term) = block.terminator { Self::collect_values_in_instruction(term, values); } } /// Collect all ValueIds used in an instruction fn collect_values_in_instruction( inst: &crate::mir::MirInstruction, values: &mut std::collections::BTreeSet, ) { use crate::mir::MirInstruction; match inst { MirInstruction::Const { dst, .. } => { values.insert(*dst); } MirInstruction::BinOp { dst, lhs, rhs, .. } => { values.insert(*dst); values.insert(*lhs); values.insert(*rhs); } MirInstruction::UnaryOp { dst, operand, .. } => { values.insert(*dst); values.insert(*operand); } MirInstruction::Compare { dst, lhs, rhs, .. } => { values.insert(*dst); values.insert(*lhs); values.insert(*rhs); } MirInstruction::Load { dst, ptr } => { values.insert(*dst); values.insert(*ptr); } MirInstruction::Store { value, ptr } => { values.insert(*value); values.insert(*ptr); } MirInstruction::Call { dst, func, args, .. } => { if let Some(d) = dst { values.insert(*d); } values.insert(*func); for arg in args { values.insert(*arg); } } MirInstruction::BoxCall { dst, box_val, args, .. } => { if let Some(d) = dst { values.insert(*d); } values.insert(*box_val); for arg in args { values.insert(*arg); } } MirInstruction::Branch { condition, .. } => { values.insert(*condition); } MirInstruction::Return { value } => { if let Some(v) = value { values.insert(*v); } } MirInstruction::Phi { dst, inputs, type_hint: None, } => { values.insert(*dst); for (_, val) in inputs { values.insert(*val); } } MirInstruction::Copy { dst, src } => { values.insert(*dst); values.insert(*src); } MirInstruction::NewBox { dst, args, .. } => { values.insert(*dst); for arg in args { values.insert(*arg); } } MirInstruction::Print { value, .. } => { values.insert(*value); } _ => { // Other instructions: skip for now } } } /// Phase 189: Remap JoinIR-generated MIR instructions /// /// For now, we use standard instruction remapping. Call instructions between /// merged functions are preserved as Call instructions, relying on VM support /// for handling these calls. /// /// **Future Enhancement (Phase 189.1)**: Convert tail calls to jumps for optimization. fn remap_joinir_instruction( inst: &crate::mir::MirInstruction, value_map: &std::collections::HashMap, block_map: &std::collections::HashMap, _function_entry_map: &std::collections::HashMap, _value_to_func_name: &std::collections::HashMap, _debug: bool, ) -> crate::mir::MirInstruction { // Use standard remapping Self::remap_instruction(inst, value_map, block_map) } /// Remap an instruction's ValueIds and BlockIds fn remap_instruction( inst: &crate::mir::MirInstruction, value_map: &std::collections::HashMap, block_map: &std::collections::HashMap, ) -> crate::mir::MirInstruction { use crate::mir::MirInstruction; let remap_value = |v: super::ValueId| value_map.get(&v).copied().unwrap_or(v); let remap_block = |b: crate::mir::BasicBlockId| block_map.get(&b).copied().unwrap_or(b); match inst { MirInstruction::Const { dst, value } => MirInstruction::Const { dst: remap_value(*dst), value: value.clone(), }, MirInstruction::BinOp { dst, op, lhs, rhs } => MirInstruction::BinOp { dst: remap_value(*dst), op: *op, lhs: remap_value(*lhs), rhs: remap_value(*rhs), }, MirInstruction::UnaryOp { dst, op, operand } => MirInstruction::UnaryOp { dst: remap_value(*dst), op: *op, operand: remap_value(*operand), }, MirInstruction::Compare { dst, op, lhs, rhs } => MirInstruction::Compare { dst: remap_value(*dst), op: *op, lhs: remap_value(*lhs), rhs: remap_value(*rhs), }, MirInstruction::Load { dst, ptr } => MirInstruction::Load { dst: remap_value(*dst), ptr: remap_value(*ptr), }, MirInstruction::Store { value, ptr } => MirInstruction::Store { value: remap_value(*value), ptr: remap_value(*ptr), }, MirInstruction::Call { dst, func, callee, args, effects, } => MirInstruction::Call { dst: dst.map(remap_value), func: remap_value(*func), callee: callee.clone(), args: args.iter().map(|a| remap_value(*a)).collect(), effects: *effects, }, MirInstruction::BoxCall { dst, box_val, method, method_id, args, effects, } => MirInstruction::BoxCall { dst: dst.map(remap_value), box_val: remap_value(*box_val), method: method.clone(), method_id: *method_id, args: args.iter().map(|a| remap_value(*a)).collect(), effects: *effects, }, MirInstruction::Branch { condition, then_bb, else_bb, } => MirInstruction::Branch { condition: remap_value(*condition), then_bb: remap_block(*then_bb), else_bb: remap_block(*else_bb), }, MirInstruction::Jump { target } => MirInstruction::Jump { target: remap_block(*target), }, MirInstruction::Return { value } => MirInstruction::Return { value: value.map(remap_value), }, MirInstruction::Phi { dst, inputs, type_hint: None, } => MirInstruction::Phi { dst: remap_value(*dst), inputs: inputs .iter() .map(|(bb, val)| (remap_block(*bb), remap_value(*val))) .collect(), type_hint: None, // Phase 63-6: Preserve no type hint during remapping }, MirInstruction::Copy { dst, src } => MirInstruction::Copy { dst: remap_value(*dst), src: remap_value(*src), }, MirInstruction::NewBox { dst, box_type, args, } => MirInstruction::NewBox { dst: remap_value(*dst), box_type: box_type.clone(), args: args.iter().map(|a| remap_value(*a)).collect(), }, MirInstruction::Print { value, effects } => MirInstruction::Print { value: remap_value(*value), effects: *effects, }, // Pass through other instructions unchanged other => other.clone(), } } /// 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) } }