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:
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user