refactor: Extract JoinIR routing logic from control_flow.rs (Phase 3)
- Created joinir/routing.rs - Moved try_cf_loop_joinir() and cf_loop_joinir_impl() - Updated joinir/mod.rs to include routing module - Removed ~320 lines from main control_flow.rs - Zero breaking changes, all tests pass Phase 1-3 complete: - control_flow.rs: 1,632 → ~900 lines (45% reduction) - Extracted 3 modules: debug, patterns (3 files), routing - All functionality preserved and verified
This commit is contained in:
@ -2,6 +2,7 @@
|
|||||||
//!
|
//!
|
||||||
//! This module contains JoinIR-related control flow logic:
|
//! This module contains JoinIR-related control flow logic:
|
||||||
//! - Pattern lowerers (patterns/)
|
//! - Pattern lowerers (patterns/)
|
||||||
//! - Routing logic (future Phase 3)
|
//! - Routing logic (routing.rs) ✅
|
||||||
|
|
||||||
pub(in crate::mir::builder) mod patterns;
|
pub(in crate::mir::builder) mod patterns;
|
||||||
|
pub(in crate::mir::builder) mod routing;
|
||||||
|
|||||||
343
src/mir/builder/control_flow/joinir/routing.rs
Normal file
343
src/mir/builder/control_flow/joinir/routing.rs
Normal file
@ -0,0 +1,343 @@
|
|||||||
|
//! 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<Option<ValueId>, 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<Option<ValueId>, 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 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<serde_json::Value> = 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<serde_json::Value> = 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::<String>() {
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,7 +3,7 @@
|
|||||||
//! This module is being modularized in phases:
|
//! This module is being modularized in phases:
|
||||||
//! - Phase 1: Debug utilities (debug.rs) ✅
|
//! - Phase 1: Debug utilities (debug.rs) ✅
|
||||||
//! - Phase 2: Pattern lowerers (joinir/patterns/) ✅
|
//! - Phase 2: Pattern lowerers (joinir/patterns/) ✅
|
||||||
//! - Phase 3: JoinIR routing (future)
|
//! - Phase 3: JoinIR routing (joinir/routing.rs) ✅
|
||||||
//! - Phase 4-19: Additional modularization (future)
|
//! - Phase 4-19: Additional modularization (future)
|
||||||
|
|
||||||
use super::{Effect, EffectMask, MirInstruction, ValueId};
|
use super::{Effect, EffectMask, MirInstruction, ValueId};
|
||||||
@ -94,327 +94,6 @@ impl super::MirBuilder {
|
|||||||
/// Note: Arity in function names does NOT include implicit `me` receiver.
|
/// Note: Arity in function names does NOT include implicit `me` receiver.
|
||||||
/// - Instance method `print_tokens()` → `/0` (no explicit params)
|
/// - Instance method `print_tokens()` → `/0` (no explicit params)
|
||||||
/// - Static method `filter(arr, pred)` → `/2` (two params)
|
/// - Static method `filter(arr, pred)` → `/2` (two params)
|
||||||
fn try_cf_loop_joinir(
|
|
||||||
&mut self,
|
|
||||||
condition: &ASTNode,
|
|
||||||
body: &[ASTNode],
|
|
||||||
) -> Result<Option<ValueId>, 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.
|
|
||||||
fn cf_loop_joinir_impl(
|
|
||||||
&mut self,
|
|
||||||
condition: &ASTNode,
|
|
||||||
body: &[ASTNode],
|
|
||||||
func_name: &str,
|
|
||||||
debug: bool,
|
|
||||||
) -> Result<Option<ValueId>, 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<serde_json::Value> = 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<serde_json::Value> = 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::<String>() {
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Phase 49-3.2: Merge JoinIR-generated MIR blocks into current_function
|
/// Phase 49-3.2: Merge JoinIR-generated MIR blocks into current_function
|
||||||
///
|
///
|
||||||
|
|||||||
Reference in New Issue
Block a user