refactor(joinir): Extract legacy binding path to routing_legacy_binding.rs

Phase 179-A Step 2: Separate LoopFrontendBinding JSON construction logic
into dedicated module for better organization.

Changes:
- New file: routing_legacy_binding.rs (223 lines)
- routing.rs: cf_loop_joinir_impl() simplified to 15 lines (delegates to legacy path)
- Routing now clearly separates pattern-based vs. legacy binding paths

Benefits:
- Clear separation of concerns (pattern router vs. legacy whitelist)
- routing.rs reduced from 364 to 146 lines (60% reduction)
- Legacy path isolated for future deprecation
This commit is contained in:
nyash-codex
2025-12-08 18:36:13 +09:00
parent 7a01ffe522
commit 95f3aa429e
25 changed files with 626 additions and 755 deletions

View File

@ -79,6 +79,13 @@ impl ExitLineReconnector {
carrier_phis: &BTreeMap<String, ValueId>,
debug: bool,
) -> Result<(), String> {
// Phase 177-STRUCT: Always log for debugging
eprintln!(
"[DEBUG-177/reconnect] ExitLineReconnector: {} exit bindings, {} carrier PHIs",
boundary.exit_bindings.len(),
carrier_phis.len()
);
// Early return for empty exit_bindings
if boundary.exit_bindings.is_empty() {
if debug {
@ -110,12 +117,11 @@ impl ExitLineReconnector {
// Update variable_map with PHI dst
if let Some(&phi_value) = phi_dst {
if let Some(var_vid) = builder.variable_map.get_mut(&binding.carrier_name) {
if debug {
eprintln!(
"[cf_loop/joinir/exit_line] ExitLineReconnector: Updated variable_map['{}'] {:?}{:?} (PHI dst)",
binding.carrier_name, var_vid, phi_value
);
}
// Phase 177-STRUCT: Always log for debugging
eprintln!(
"[DEBUG-177/reconnect] Updated variable_map['{}'] {:?}{:?}",
binding.carrier_name, *var_vid, phi_value
);
*var_vid = phi_value;
} else if debug {
eprintln!(

View File

@ -74,12 +74,11 @@ pub(super) fn build_exit_phi(
carrier_phis.insert(carrier_name.clone(), phi_dst);
if debug {
eprintln!(
"[cf_loop/joinir] Exit block PHI (carrier '{}'): {:?} = phi {:?}",
carrier_name, phi_dst, inputs
);
}
// DEBUG-177: Always log exit block PHI creation for carrier debugging
eprintln!(
"[DEBUG-177] Exit block PHI (carrier '{}'): {:?} = phi {:?}",
carrier_name, phi_dst, inputs
);
}
func.add_block(exit_block);

View File

@ -604,12 +604,11 @@ pub(super) fn merge_and_rewrite(
carrier_inputs.entry(binding.carrier_name.clone())
.or_insert_with(Vec::new)
.push((new_block_id, phi_dst));
if debug {
eprintln!(
"[cf_loop/joinir] Phase 33-16: Using header PHI dst {:?} for carrier '{}'",
phi_dst, binding.carrier_name
);
}
// DEBUG-177: Always log carrier collection
eprintln!(
"[DEBUG-177] Phase 33-16: Collecting carrier '{}': from {:?} using header PHI {:?}",
binding.carrier_name, new_block_id, phi_dst
);
}
}
}

View File

@ -84,6 +84,8 @@ impl LoopHeaderPhiBuilder {
latch_incoming: None,
},
);
// Phase 177-STRUCT-2: Record insertion order
info.carrier_order.push(loop_var_name.to_string());
if debug {
eprintln!(
@ -103,6 +105,8 @@ impl LoopHeaderPhiBuilder {
latch_incoming: None,
},
);
// Phase 177-STRUCT-2: Record insertion order
info.carrier_order.push(name.clone());
if debug {
eprintln!(

View File

@ -29,6 +29,14 @@ pub struct LoopHeaderPhiInfo {
/// of this carrier during loop iteration.
pub carrier_phis: BTreeMap<String, CarrierPhiEntry>,
/// Phase 177-STRUCT-2: Carrier names in insertion order
///
/// Preserves the order in which carriers were added (matches exit_bindings order).
/// Used for index-based matching with loop_step params.
///
/// Order: [loop_var, carrier1, carrier2, ...]
pub carrier_order: Vec<String>,
/// Expression result PHI dst (if loop is used as expression)
///
/// For Pattern 2 (joinir_min_loop), this is the same as the loop
@ -55,10 +63,25 @@ impl LoopHeaderPhiInfo {
Self {
header_block,
carrier_phis: BTreeMap::new(),
carrier_order: Vec::new(),
expr_result_phi: None,
}
}
/// Phase 177-STRUCT-2: Get carrier name at index (in insertion order)
///
/// Used for matching loop_step params by index.
pub fn get_carrier_at_index(&self, idx: usize) -> Option<&str> {
self.carrier_order.get(idx).map(|s| s.as_str())
}
/// Phase 177-STRUCT-2: Get PHI entry at index (in insertion order)
pub fn get_entry_at_index(&self, idx: usize) -> Option<&CarrierPhiEntry> {
self.carrier_order
.get(idx)
.and_then(|name| self.carrier_phis.get(name))
}
/// Get the PHI dst for a carrier variable
pub fn get_carrier_phi(&self, name: &str) -> Option<ValueId> {
self.carrier_phis.get(name).map(|e| e.phi_dst)

View File

@ -340,12 +340,11 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks(
}
// Map loop_step's parameters
if debug {
eprintln!(
"[cf_loop/joinir] Phase 33-21: function_params keys: {:?}",
function_params.keys().collect::<Vec<_>>()
);
}
// DEBUG-177: Always log function_params keys to diagnose multi-carrier issue
eprintln!(
"[DEBUG-177] Phase 33-21: function_params keys: {:?}",
function_params.keys().collect::<Vec<_>>()
);
if function_params.get(loop_step_func_name).is_none() {
eprintln!(
"[cf_loop/joinir] WARNING: function_params.get('{}') returned None. Available keys: {:?}",
@ -354,32 +353,56 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks(
);
}
if let Some(loop_step_params) = function_params.get(loop_step_func_name) {
if debug {
eprintln!(
"[cf_loop/joinir] Phase 33-21: loop_step ({}) params: {:?}",
loop_step_func_name, loop_step_params
);
}
// Map loop_step's parameters to header PHI dsts
// loop_step params: [i_param, carrier1_param, ...]
// carrier_phis: [("i", entry), ("sum", entry), ...]
for (idx, (carrier_name, entry)) in phi_info.carrier_phis.iter().enumerate() {
if let Some(&loop_step_param) = loop_step_params.get(idx) {
// Phase 177-3: Don't override condition_bindings
if condition_binding_ids.contains(&loop_step_param) {
// DEBUG-177: Always log loop_step params
eprintln!(
"[DEBUG-177] Phase 33-21: loop_step ({}) params: {:?}",
loop_step_func_name, loop_step_params
);
// Phase 177-FIX: Process loop_step params but skip if already mapped
//
// We use a name-based approach: for each carrier_phi, check if
// its join_value was already set in Phase 177-3-B (body-only carriers).
// Only process loop_step params for carriers NOT already handled.
for loop_step_param in loop_step_params {
// Phase 177-3: Don't override condition_bindings
if condition_binding_ids.contains(loop_step_param) {
eprintln!(
"[DEBUG-177] Phase 177-FIX: Skipping condition_binding {:?}",
loop_step_param
);
continue;
}
// Find which carrier this param belongs to by matching join_value
// Check if this param was already handled by Phase 177-3-B
let already_mapped = boundary.condition_bindings.iter().any(|cb| {
cb.join_value == *loop_step_param &&
phi_info.carrier_phis.iter().any(|(name, _)| name == &cb.name)
});
if already_mapped {
eprintln!(
"[DEBUG-177] Phase 177-FIX: Skipping {:?} (already mapped by Phase 177-3-B)",
loop_step_param
);
continue;
}
// Phase 177-STRUCT-2: Use carrier_order for index-based matching
//
// Problem: BTreeMap iterates in alphabetical order, but JoinIR
// generates params in exit_bindings order.
//
// Solution: Use carrier_order (Vec<String>) which preserves insertion order.
if let Some(param_idx) = loop_step_params.iter().position(|p| p == loop_step_param) {
// Map params[i] to carrier_order[i]
if let (Some(carrier_name), Some(entry)) = (
phi_info.get_carrier_at_index(param_idx),
phi_info.get_entry_at_index(param_idx),
) {
eprintln!(
"[cf_loop/joinir] Phase 177-3: Skipping override for condition_binding {:?} ('{}')",
loop_step_param, carrier_name
"[DEBUG-177] Phase 177-STRUCT-2: REMAP loop_step param[{}] {:?} {:?} (carrier '{}')",
param_idx, loop_step_param, entry.phi_dst, carrier_name
);
continue;
remapper.set_value(*loop_step_param, entry.phi_dst);
}
if debug {
eprintln!(
"[cf_loop/joinir] Phase 33-21: REMAP loop_step param {:?}{:?} ('{}')",
loop_step_param, entry.phi_dst, carrier_name
);
}
remapper.set_value(loop_step_param, entry.phi_dst);
}
}
}
@ -403,10 +426,15 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks(
);
}
}
for (idx, (carrier_name, entry)) in phi_info.carrier_phis.iter().enumerate() {
// Phase 177-STRUCT-2: Use carrier_order for deterministic iteration
for (idx, carrier_name) in phi_info.carrier_order.iter().enumerate() {
if carrier_name == loop_var_name {
continue;
}
let entry = match phi_info.carrier_phis.get(carrier_name) {
Some(e) => e,
None => continue,
};
let join_value_id = ValueId(idx as u32);
// Phase 177-3: Don't override condition_bindings
if !condition_binding_ids.contains(&join_value_id) {

View File

@ -8,5 +8,6 @@
pub(in crate::mir::builder) mod patterns;
pub(in crate::mir::builder) mod routing;
pub(in crate::mir::builder) mod routing_legacy_binding;
pub(in crate::mir::builder) mod merge;
pub(in crate::mir::builder) mod trace;

View File

@ -102,6 +102,7 @@ impl CommonPatternInitializer {
carriers.push(CarrierVar {
name: var_name.clone(),
host_id: var_id,
join_id: None, // Phase 177-STRUCT-1
});
}
}

View File

@ -205,6 +205,7 @@ mod tests {
vec![CarrierVar {
name: "sum".to_string(),
host_id: ValueId(10),
join_id: None,
}],
);
@ -243,10 +244,12 @@ mod tests {
CarrierVar {
name: "printed".to_string(),
host_id: ValueId(11),
join_id: None,
},
CarrierVar {
name: "sum".to_string(),
host_id: ValueId(10),
join_id: None,
},
],
);
@ -288,6 +291,7 @@ mod tests {
vec![CarrierVar {
name: "sum".to_string(),
host_id: ValueId(10),
join_id: None,
}],
);
@ -315,6 +319,7 @@ mod tests {
vec![CarrierVar {
name: "sum".to_string(),
host_id: ValueId(10),
join_id: None,
}],
);
@ -342,6 +347,7 @@ mod tests {
vec![CarrierVar {
name: "sum".to_string(),
host_id: ValueId(10),
join_id: None,
}],
);
@ -372,6 +378,7 @@ mod tests {
vec![CarrierVar {
name: "sum".to_string(),
host_id: ValueId(10),
join_id: None,
}],
);

View File

@ -10,12 +10,58 @@ use super::trim_pattern_lowerer::TrimPatternLowerer;
/// Phase 194: Detection function for Pattern 2
///
/// Phase 192: Updated to structure-based detection
/// Phase 178: Added string carrier rejection (unsupported by Pattern 2)
/// Phase 187-2: No legacy fallback - rejection means error
///
/// Pattern 2 matches:
/// - Pattern kind is Pattern2Break (has break, no continue)
/// - No string/complex carrier updates (JoinIR doesn't support string concat)
pub fn can_lower(_builder: &MirBuilder, ctx: &super::router::LoopPatternContext) -> bool {
use crate::mir::loop_pattern_detection::LoopPatternKind;
ctx.pattern_kind == LoopPatternKind::Pattern2Break
use crate::mir::join_ir::lowering::loop_update_analyzer::{LoopUpdateAnalyzer, UpdateExpr, UpdateRhs};
use crate::mir::join_ir::lowering::carrier_info::CarrierVar;
use crate::mir::ValueId;
// Basic pattern check
if ctx.pattern_kind != LoopPatternKind::Pattern2Break {
return false;
}
// Phase 178: Check for string/complex carrier updates
// Create dummy carriers from body assignment targets for analysis
let dummy_carriers: Vec<CarrierVar> = ctx.body.iter().filter_map(|node| {
match node {
crate::ast::ASTNode::Assignment { target, .. } => {
if let crate::ast::ASTNode::Variable { name, .. } = target.as_ref() {
Some(CarrierVar {
name: name.clone(),
host_id: ValueId(0), // Dummy
join_id: None,
})
} else {
None
}
}
_ => None,
}
}).collect();
let updates = LoopUpdateAnalyzer::analyze_carrier_updates(ctx.body, &dummy_carriers);
// Check if any update is string/complex
for update in updates.values() {
if let UpdateExpr::BinOp { rhs, .. } = update {
match rhs {
UpdateRhs::StringLiteral(_) | UpdateRhs::Other => {
eprintln!("[pattern2/can_lower] Phase 178: String/complex update detected, rejecting Pattern 2 (unsupported)");
return false;
}
_ => {}
}
}
}
true
}
/// Phase 194: Lowering function for Pattern 2

View File

@ -271,14 +271,17 @@ mod tests {
CarrierVar {
name: "i".to_string(),
host_id: ValueId(1),
join_id: None,
},
CarrierVar {
name: "sum".to_string(),
host_id: ValueId(2),
join_id: None,
},
CarrierVar {
name: "M".to_string(),
host_id: ValueId(3),
join_id: None,
},
],
trim_helper: None,

View File

@ -39,6 +39,8 @@ use super::super::trace;
/// Phase 194+: Detection function for Pattern 4
///
/// Phase 192: Updated to use pattern_kind for consistency
/// Phase 178: Added string carrier rejection (unsupported by Pattern 4)
/// Phase 187-2: No legacy fallback - rejection means error
///
/// Pattern 4 matches loops with continue statements.
///
@ -54,12 +56,55 @@ use super::super::trace;
///
/// 1. **Must have continue**: `ctx.has_continue == true`
/// 2. **No break statements**: `ctx.has_break == false` (for simplicity in Pattern 4)
/// 3. **Phase 178**: No string/complex carrier updates (JoinIR doesn't support string concat)
///
/// If both conditions are met, Pattern 4 is detected.
/// If all conditions are met, Pattern 4 is detected.
pub fn can_lower(_builder: &MirBuilder, ctx: &super::router::LoopPatternContext) -> bool {
// Phase 192: Use pattern_kind for consistency with other patterns
use crate::mir::loop_pattern_detection::LoopPatternKind;
ctx.pattern_kind == LoopPatternKind::Pattern4Continue
use crate::mir::join_ir::lowering::loop_update_analyzer::{LoopUpdateAnalyzer, UpdateExpr, UpdateRhs};
use crate::mir::join_ir::lowering::carrier_info::CarrierVar;
use crate::mir::ValueId;
// Basic pattern check
if ctx.pattern_kind != LoopPatternKind::Pattern4Continue {
return false;
}
// Phase 178: Check for string/complex carrier updates
// Create dummy carriers from body assignment targets for analysis
let dummy_carriers: Vec<CarrierVar> = ctx.body.iter().filter_map(|node| {
match node {
crate::ast::ASTNode::Assignment { target, .. } => {
if let crate::ast::ASTNode::Variable { name, .. } = target.as_ref() {
Some(CarrierVar {
name: name.clone(),
host_id: ValueId(0), // Dummy
join_id: None,
})
} else {
None
}
}
_ => None,
}
}).collect();
let updates = LoopUpdateAnalyzer::analyze_carrier_updates(ctx.body, &dummy_carriers);
// Check if any update is string/complex
for update in updates.values() {
if let UpdateExpr::BinOp { rhs, .. } = update {
match rhs {
UpdateRhs::StringLiteral(_) | UpdateRhs::Other => {
eprintln!("[pattern4/can_lower] Phase 178: String/complex update detected, rejecting Pattern 4 (unsupported)");
return false;
}
_ => {}
}
}
}
true
}
/// Phase 33-19: Lowering function for Pattern 4

View File

@ -184,9 +184,10 @@ pub fn route_loop_pattern(
}
}
// No pattern matched - fall through to legacy path
// No pattern matched - return None (caller will handle error)
// Phase 187-2: Legacy LoopBuilder removed, all loops must use JoinIR
if ctx.debug {
trace::trace().debug("route", &format!("No pattern matched for function '{}', falling back to legacy", ctx.func_name));
trace::trace().debug("route", &format!("No pattern matched for function '{}'", ctx.func_name));
}
Ok(None)
}

View File

@ -8,8 +8,9 @@ use super::trace;
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.
/// Returns `Ok(Some(value))` if the loop is successfully lowered via JoinIR,
/// `Ok(None)` if no JoinIR pattern matched (unsupported loop structure).
/// Phase 187-2: Legacy LoopBuilder removed - all loops must use JoinIR.
///
/// # Phase 49-4: Multi-target support
///
@ -116,22 +117,9 @@ impl MirBuilder {
/// 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.
/// Routes loop compilation through either:
/// 1. Pattern-based router (Phase 194+) - preferred for new patterns
/// 2. Legacy binding path (Phase 49-3) - for whitelisted functions only
pub(in crate::mir::builder) fn cf_loop_joinir_impl(
&mut self,
condition: &ASTNode,
@ -139,222 +127,19 @@ impl MirBuilder {
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 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)? {
// Phase 195: Use unified trace
trace::trace().routing("router", func_name, "Pattern router succeeded");
return Ok(Some(result));
}
// Phase 195: Use unified trace
trace::trace().routing("router", func_name, "Pattern router found no match, continuing to legacy path");
// Phase 187-2: Pattern router failed, try legacy whitelist
trace::trace().routing("router", func_name, "Pattern router found no match, trying legacy whitelist");
// 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(),
_ => {
// Phase 195: Use unified trace
trace::trace().routing("router", func_name, "No binding defined, falling back");
return Ok(None);
}
};
// Phase 195: Use unified trace
trace::trace().debug(
"router",
&format!(
"Using binding for '{}': counter={}, acc={:?}, pattern={:?}",
func_name, 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() {
// Phase 195: Use unified trace
trace::trace().debug("router", "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;
}
// Phase 195: Use unified trace
trace::trace().debug("router", &format!("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" }
}
]
}
}
]
});
// Phase 195: Use unified trace
trace::trace().debug(
"router",
&format!(
"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()
};
// Phase 195: Use unified trace
trace::trace().debug(
"router",
&format!(
"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();
// Phase 195: Use unified trace
trace::trace().joinir_stats(
"router",
join_module.functions.len(),
join_module
.functions
.values()
.map(|f| f.body.len())
.sum(),
);
// 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))?;
// Phase 195: Use unified trace for MIR module stats
if trace::trace().is_joinir_enabled() {
trace::trace().debug(
"router",
&format!("MirModule has {} functions", mir_module.functions.len()),
);
for (name, func) in &mir_module.functions {
trace::trace().debug(
"router",
&format!(
" - {}: {} blocks, entry={:?}",
name,
func.blocks.len(),
func.entry_block
),
);
// Phase 189: Debug - show block contents
for (block_id, block) in &func.blocks {
trace::trace().blocks(
"router",
&format!("Block {:?}: {} instructions", block_id, block.instructions.len()),
);
for (i, inst) in block.instructions.iter().enumerate() {
trace::trace().instructions("router", &format!("[{}] {:?}", i, inst));
}
if let Some(ref term) = block.terminator {
trace::trace().instructions("router", &format!("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))
// Delegate to legacy binding path (routing_legacy_binding.rs)
self.cf_loop_joinir_legacy_binding(condition, body, func_name, debug)
}
}

View File

@ -0,0 +1,215 @@
//! Legacy LoopFrontendBinding path (Phase 49-3)
//!
//! This module contains the legacy JSON v0 construction logic for specific
//! whitelisted functions (print_tokens, array_filter) that use the old
//! LoopFrontendBinding system.
//!
//! Phase 194+ uses the pattern-based router instead. This legacy path is
//! kept for backward compatibility with existing whitelist entries.
use crate::ast::ASTNode;
use crate::mir::builder::MirBuilder;
use crate::mir::ValueId;
use super::trace;
impl MirBuilder {
/// Phase 49-3: Legacy JoinIR Frontend integration via LoopFrontendBinding
///
/// This implements the old JSON v0 construction path for whitelisted functions.
/// New patterns should use the pattern router instead (route_loop_pattern).
///
/// # 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
pub(in crate::mir::builder) fn cf_loop_joinir_legacy_binding(
&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 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(),
_ => {
trace::trace().routing("router", func_name, "No legacy binding defined, falling back");
return Ok(None);
}
};
trace::trace().debug(
"router",
&format!(
"Using legacy binding for '{}': counter={}, acc={:?}, pattern={:?}",
func_name, 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
let mut params: Vec<serde_json::Value> = Vec::new();
// Phase 52: Add 'me' for instance methods
if binding.needs_me_receiver() {
trace::trace().debug("router", "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 {
if ext_ref == "me" || ext_ref.starts_with("me.") {
continue;
}
trace::trace().debug("router", &format!("Adding '{}' to params (external_ref)", ext_ref));
params.push(serde_json::json!(ext_ref));
}
// Step 2: Construct JSON v0 format with "defs" array
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,
"body": body_json
},
// Return the accumulator
{
"type": "Return",
"value": { "kind": "Variable", "name": "acc" }
}
]
}
}
]
});
trace::trace().debug(
"router",
&format!(
"Generated JSON v0 for {}: {}",
func_name,
serde_json::to_string_pretty(&program_json).unwrap_or_default()
),
);
// Step 3: Lower to JoinIR with panic catch
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) => {
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()
};
trace::trace().debug(
"router",
&format!(
"JoinIR lowering failed for {}: {} (unsupported pattern)",
func_name, panic_msg
),
);
return Ok(None);
}
}
};
let join_meta = JoinFuncMetaMap::new();
trace::trace().joinir_stats(
"router",
join_module.functions.len(),
join_module
.functions
.values()
.map(|f| f.body.len())
.sum(),
);
// 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))?;
// Debug MIR module if trace enabled
if trace::trace().is_joinir_enabled() {
trace::trace().debug(
"router",
&format!("MirModule has {} functions", mir_module.functions.len()),
);
for (name, func) in &mir_module.functions {
trace::trace().debug(
"router",
&format!(
" - {}: {} blocks, entry={:?}",
name,
func.blocks.len(),
func.entry_block
),
);
for (block_id, block) in &func.blocks {
trace::trace().blocks(
"router",
&format!("Block {:?}: {} instructions", block_id, block.instructions.len()),
);
for (i, inst) in block.instructions.iter().enumerate() {
trace::trace().instructions("router", &format!("[{}] {:?}", i, inst));
}
if let Some(ref term) = block.terminator {
trace::trace().instructions("router", &format!("terminator: {:?}", term));
}
}
}
}
// Step 5: Merge MIR blocks into current_function
// Phase 188-Impl-3: Pass None for boundary (whitelist path without boundary tracking)
let _ = self.merge_joinir_mir_blocks(&mir_module, None, debug)?;
// Return void
let void_val = self.next_value_id();
self.emit_instruction(MirInstruction::Const {
dst: void_val,
value: ConstValue::Void,
})?;
Ok(Some(void_val))
}
}

View File

@ -84,9 +84,9 @@ impl super::MirBuilder {
///
/// # 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):
/// This is the unified entry point for all loop lowering. All loops are processed
/// via JoinIR Frontend (Phase 187-2: LoopBuilder removed).
/// Specific functions are enabled via dev flags (Phase 49) or Core policy (Phase 80):
///
/// - Core ON (`joinir_core_enabled()`): print_tokens / ArrayExt.filter はまず JoinIR Frontend を試す
/// - Dev フラグ(既存):
@ -121,8 +121,9 @@ impl super::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.
/// Returns `Ok(Some(value))` if the loop is successfully lowered via JoinIR,
/// `Ok(None)` if no JoinIR pattern matched (unsupported loop structure).
/// Phase 187-2: Legacy LoopBuilder removed - all loops must use JoinIR.
///
/// # Phase 49-4: Multi-target support
///