From 272d99f3dee31ffa152bd6785cb1152c8b9dd337 Mon Sep 17 00:00:00 2001 From: nyash-codex Date: Wed, 10 Dec 2025 01:12:03 +0900 Subject: [PATCH] feat(joinir): Phase 214 Pattern 3 join_inputs dynamic generation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../joinir/patterns/pattern3_with_if_phi.rs | 102 ++++++++++++------ 1 file changed, 67 insertions(+), 35 deletions(-) diff --git a/src/mir/builder/control_flow/joinir/patterns/pattern3_with_if_phi.rs b/src/mir/builder/control_flow/joinir/patterns/pattern3_with_if_phi.rs index 7f82e540..365dd75a 100644 --- a/src/mir/builder/control_flow/joinir/patterns/pattern3_with_if_phi.rs +++ b/src/mir/builder/control_flow/joinir/patterns/pattern3_with_if_phi.rs @@ -138,23 +138,40 @@ impl MirBuilder { // Build boundary with carrier inputs use crate::mir::join_ir::lowering::JoinInlineBoundaryBuilder; - use crate::mir::builder::emission::constant; - // Phase 213: Build join_inputs and host_inputs based on carriers - let join_inputs = vec![ValueId(0), ValueId(1), ValueId(2)]; + // Phase 214: Dynamically generate join_inputs based on exit_bindings + // 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 = (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]; - - // Add accumulator carriers (sum, optionally count) - for carrier in &ctx.carrier_info.carriers { - if carrier.name != ctx.loop_var_name { - host_inputs.push(carrier.host_id); - } + for binding in &exit_bindings { + host_inputs.push(binding.host_slot); } - // Pad to 3 inputs if needed (for legacy compatibility) - while host_inputs.len() < 3 { - host_inputs.push(constant::emit_void(self)); - } + // Phase 214: Verify length consistency (fail-fast assertion) + debug_assert_eq!( + 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() .with_inputs(join_inputs, host_inputs) @@ -173,6 +190,7 @@ impl MirBuilder { )?; // Return Void (loop doesn't produce values) + use crate::mir::builder::emission::constant; let void_val = constant::emit_void(self); trace::trace().debug("pattern3/if-sum", &format!("Loop complete, returning Void {:?}", void_val)); Ok(Some(void_val)) @@ -188,20 +206,13 @@ impl MirBuilder { ) -> Result, String> { 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 - let sum_carrier = ctx.carrier_info.carriers.iter() - .find(|c| c.name == "sum") - .ok_or_else(|| { - format!( - "[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(); + let _sum_exists = ctx.carrier_info.carriers.iter() + .any(|c| c.name == "sum"); + if !_sum_exists { + return Err("[cf_loop/pattern3] Accumulator variable 'sum' not found in variable_map".to_string()); + } // Phase 195: Use unified trace trace::trace().varmap("pattern3_start", &self.variable_map); @@ -247,18 +258,39 @@ impl MirBuilder { debug, ); - // Build join_inputs and host_inputs (retain existing logic) - let join_inputs = vec![ValueId(0), ValueId(1), ValueId(2)]; - let mut host_inputs = vec![ctx.loop_var_id, sum_var_id]; + // Phase 214: Dynamically generate join_inputs based on exit_bindings + // 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(); - if has_count { - host_inputs.push(count_carrier_opt.unwrap().host_id); - } else { - use crate::mir::builder::emission::constant; - let dummy_count_id = constant::emit_void(self); - host_inputs.push(dummy_count_id); + // Allocate join_inputs dynamically from JoinValueSpace + let join_inputs: Vec = (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]; + 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() .with_inputs(join_inputs, host_inputs) .with_exit_bindings(exit_bindings)