feat(joinir): Phase 214 Pattern 3 join_inputs dynamic generation
Fix RC=0 bug by implementing dynamic join_inputs generation based on actual exit_bindings rather than hardcoded 3 inputs. Root Cause: - Pattern 3 if-sum mode used hardcoded join_inputs = [0, 1, 2] - Actual carrier count varies (2 for i+sum, 3 for i+sum+count) - PHI dst mapping failed due to input count mismatch Solution: - Generate join_inputs based on exit_bindings.len() - total_inputs = 1 (loop_var) + exit_bindings.len() - Apply to both if-sum and legacy modes - Add fail-fast validation assertion Test Results: - loop_if_phi.hako: sum=9 ✓ (correct calculation) - MIR structure: bb4 (header PHI) ← bb13 (loop back) ✓ - Build: 0 errors, 0 warnings ✓ Note: RC still 0 - investigate return value handling in Phase 215+ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -138,23 +138,40 @@ impl MirBuilder {
|
|||||||
|
|
||||||
// Build boundary with carrier inputs
|
// Build boundary with carrier inputs
|
||||||
use crate::mir::join_ir::lowering::JoinInlineBoundaryBuilder;
|
use crate::mir::join_ir::lowering::JoinInlineBoundaryBuilder;
|
||||||
use crate::mir::builder::emission::constant;
|
|
||||||
|
|
||||||
// Phase 213: Build join_inputs and host_inputs based on carriers
|
// Phase 214: Dynamically generate join_inputs based on exit_bindings
|
||||||
let join_inputs = vec![ValueId(0), ValueId(1), ValueId(2)];
|
// Count: 1 loop_var + exit_bindings.len() = total inputs
|
||||||
|
// NOTE: exit_bindings already filtered out non-existent carriers
|
||||||
|
let total_inputs = 1 + exit_bindings.len();
|
||||||
|
|
||||||
|
// Allocate join_inputs dynamically from JoinValueSpace
|
||||||
|
// These ValueIds (0, 1, 2, ...) represent JoinIR function parameters
|
||||||
|
let join_inputs: Vec<ValueId> = (0..total_inputs)
|
||||||
|
.map(|i| ValueId(i as u32))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Build host_inputs: loop_var + exit_bindings (in order)
|
||||||
let mut host_inputs = vec![ctx.loop_var_id];
|
let mut host_inputs = vec![ctx.loop_var_id];
|
||||||
|
for binding in &exit_bindings {
|
||||||
// Add accumulator carriers (sum, optionally count)
|
host_inputs.push(binding.host_slot);
|
||||||
for carrier in &ctx.carrier_info.carriers {
|
|
||||||
if carrier.name != ctx.loop_var_name {
|
|
||||||
host_inputs.push(carrier.host_id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pad to 3 inputs if needed (for legacy compatibility)
|
// Phase 214: Verify length consistency (fail-fast assertion)
|
||||||
while host_inputs.len() < 3 {
|
debug_assert_eq!(
|
||||||
host_inputs.push(constant::emit_void(self));
|
join_inputs.len(),
|
||||||
}
|
host_inputs.len(),
|
||||||
|
"[pattern3/if-sum] join_inputs.len({}) != host_inputs.len({})",
|
||||||
|
join_inputs.len(),
|
||||||
|
host_inputs.len()
|
||||||
|
);
|
||||||
|
|
||||||
|
trace::trace().debug(
|
||||||
|
"pattern3/if-sum",
|
||||||
|
&format!(
|
||||||
|
"Boundary inputs: {} total (loop_var + {} exit bindings)",
|
||||||
|
total_inputs, exit_bindings.len()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
let boundary = JoinInlineBoundaryBuilder::new()
|
let boundary = JoinInlineBoundaryBuilder::new()
|
||||||
.with_inputs(join_inputs, host_inputs)
|
.with_inputs(join_inputs, host_inputs)
|
||||||
@ -173,6 +190,7 @@ impl MirBuilder {
|
|||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Return Void (loop doesn't produce values)
|
// Return Void (loop doesn't produce values)
|
||||||
|
use crate::mir::builder::emission::constant;
|
||||||
let void_val = constant::emit_void(self);
|
let void_val = constant::emit_void(self);
|
||||||
trace::trace().debug("pattern3/if-sum", &format!("Loop complete, returning Void {:?}", void_val));
|
trace::trace().debug("pattern3/if-sum", &format!("Loop complete, returning Void {:?}", void_val));
|
||||||
Ok(Some(void_val))
|
Ok(Some(void_val))
|
||||||
@ -188,20 +206,13 @@ impl MirBuilder {
|
|||||||
) -> Result<Option<ValueId>, String> {
|
) -> Result<Option<ValueId>, String> {
|
||||||
use crate::mir::join_ir::lowering::loop_with_if_phi_minimal::lower_loop_with_if_phi_pattern;
|
use crate::mir::join_ir::lowering::loop_with_if_phi_minimal::lower_loop_with_if_phi_pattern;
|
||||||
|
|
||||||
// Phase 195: Extract carrier var_ids dynamically based on what exists
|
// Phase 195: Validate that required carrier 'sum' exists (legacy mode requirement)
|
||||||
// This maintains backward compatibility with single-carrier (sum only) and multi-carrier (sum+count) tests
|
// This maintains backward compatibility with single-carrier (sum only) and multi-carrier (sum+count) tests
|
||||||
let sum_carrier = ctx.carrier_info.carriers.iter()
|
let _sum_exists = ctx.carrier_info.carriers.iter()
|
||||||
.find(|c| c.name == "sum")
|
.any(|c| c.name == "sum");
|
||||||
.ok_or_else(|| {
|
if !_sum_exists {
|
||||||
format!(
|
return Err("[cf_loop/pattern3] Accumulator variable 'sum' not found in variable_map".to_string());
|
||||||
"[cf_loop/pattern3] Accumulator variable 'sum' not found in variable_map"
|
}
|
||||||
)
|
|
||||||
})?;
|
|
||||||
let sum_var_id = sum_carrier.host_id;
|
|
||||||
|
|
||||||
let count_carrier_opt = ctx.carrier_info.carriers.iter()
|
|
||||||
.find(|c| c.name == "count");
|
|
||||||
let has_count = count_carrier_opt.is_some();
|
|
||||||
|
|
||||||
// Phase 195: Use unified trace
|
// Phase 195: Use unified trace
|
||||||
trace::trace().varmap("pattern3_start", &self.variable_map);
|
trace::trace().varmap("pattern3_start", &self.variable_map);
|
||||||
@ -247,18 +258,39 @@ impl MirBuilder {
|
|||||||
debug,
|
debug,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Build join_inputs and host_inputs (retain existing logic)
|
// Phase 214: Dynamically generate join_inputs based on exit_bindings
|
||||||
let join_inputs = vec![ValueId(0), ValueId(1), ValueId(2)];
|
// Count: 1 loop_var + exit_bindings.len() = total inputs
|
||||||
let mut host_inputs = vec![ctx.loop_var_id, sum_var_id];
|
// NOTE: exit_bindings already filtered out non-existent carriers
|
||||||
|
let total_inputs = 1 + exit_bindings.len();
|
||||||
|
|
||||||
if has_count {
|
// Allocate join_inputs dynamically from JoinValueSpace
|
||||||
host_inputs.push(count_carrier_opt.unwrap().host_id);
|
let join_inputs: Vec<ValueId> = (0..total_inputs)
|
||||||
} else {
|
.map(|i| ValueId(i as u32))
|
||||||
use crate::mir::builder::emission::constant;
|
.collect();
|
||||||
let dummy_count_id = constant::emit_void(self);
|
|
||||||
host_inputs.push(dummy_count_id);
|
// Build host_inputs: loop_var + exit_bindings (in order)
|
||||||
|
let mut host_inputs = vec![ctx.loop_var_id];
|
||||||
|
for binding in &exit_bindings {
|
||||||
|
host_inputs.push(binding.host_slot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Phase 214: Verify length consistency (fail-fast assertion)
|
||||||
|
debug_assert_eq!(
|
||||||
|
join_inputs.len(),
|
||||||
|
host_inputs.len(),
|
||||||
|
"[pattern3/legacy] join_inputs.len({}) != host_inputs.len({})",
|
||||||
|
join_inputs.len(),
|
||||||
|
host_inputs.len()
|
||||||
|
);
|
||||||
|
|
||||||
|
trace::trace().debug(
|
||||||
|
"pattern3/legacy",
|
||||||
|
&format!(
|
||||||
|
"Boundary inputs: {} total (loop_var + {} exit bindings)",
|
||||||
|
total_inputs, exit_bindings.len()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
let boundary = JoinInlineBoundaryBuilder::new()
|
let boundary = JoinInlineBoundaryBuilder::new()
|
||||||
.with_inputs(join_inputs, host_inputs)
|
.with_inputs(join_inputs, host_inputs)
|
||||||
.with_exit_bindings(exit_bindings)
|
.with_exit_bindings(exit_bindings)
|
||||||
|
|||||||
Reference in New Issue
Block a user