//! JoinIR routing logic for loop lowering use crate::ast::ASTNode; use crate::mir::builder::MirBuilder; use crate::mir::ValueId; impl MirBuilder { /// 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) pub(in crate::mir::builder) 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(); eprintln!("[cf_loop/joinir/router] try_cf_loop_joinir called for function: '{}'", func_name); // 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(); eprintln!("[cf_loop/joinir] DEBUG FLAG STATUS: debug={}, NYASH_LOOPFORM_DEBUG={:?}, NYASH_JOINIR_MAINLINE_DEBUG={:?}", debug, std::env::var("NYASH_LOOPFORM_DEBUG"), std::env::var("NYASH_JOINIR_MAINLINE_DEBUG")); 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. pub(in crate::mir::builder) fn cf_loop_joinir_impl( &mut self, condition: &ASTNode, body: &[ASTNode], func_name: &str, debug: bool, ) -> Result, String> { use super::super::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::mir::MirInstruction; use crate::r#macro::ast_json::ast_to_json; // Phase 194: Use table-driven router instead of if/else chain // This makes adding new patterns trivial - just add an entry to LOOP_PATTERNS table use super::patterns::{route_loop_pattern, LoopPatternContext}; let ctx = LoopPatternContext::new(condition, body, &func_name, debug); if let Some(result) = route_loop_pattern(self, &ctx)? { if debug { eprintln!("[cf_loop/joinir] Pattern router succeeded for '{}'", func_name); } return Ok(Some(result)); } if debug { eprintln!("[cf_loop/joinir] Pattern router found no match for '{}', continuing to legacy path", func_name); } // 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) // Phase 189: Discard exit PHI result (legacy path doesn't need it) let _ = 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)) } }