fix(joinir): Phase 196 Select double-remap bug in instruction_rewriter
Root cause: PHI inputs were being remapped twice in instruction_rewriter.rs - Line 304: remap_instruction() already remapped JoinIR → Host ValueIds - Line 328: remap_value() attempted to remap again → undefined ValueIds Fix: Only remap block IDs, use already-remapped ValueIds as-is Test results: - phase195_sum_count.hako → 93 ✅ (multi-carrier P3) - loop_if_phi.hako → sum=9 ✅ (single-carrier P3) - loop_min_while.hako → 0,1,2 ✅ (Pattern 1) - joinir_min_loop.hako → RC:0 ✅ (Pattern 2) - No [joinir/freeze], no regressions 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -320,13 +320,15 @@ pub(super) fn merge_and_rewrite(
|
||||
type_hint: None,
|
||||
} => MirInstruction::Phi {
|
||||
dst,
|
||||
// Phase 172: Fix P0 - Remap BOTH block ID AND value ID for PHI incoming
|
||||
// Phase 196: Fix Select expansion PHI - ValueIds are ALREADY remapped by remap_instruction()
|
||||
// We only need to remap block IDs here (not ValueIds!)
|
||||
inputs: inputs
|
||||
.iter()
|
||||
.map(|(bb, val)| {
|
||||
let remapped_bb = local_block_map.get(bb).copied().unwrap_or(*bb);
|
||||
let remapped_val = remapper.remap_value(*val);
|
||||
(remapped_bb, remapped_val)
|
||||
// Phase 196 FIX: Don't double-remap values!
|
||||
// remapper.remap_instruction() already remapped *val
|
||||
(remapped_bb, *val)
|
||||
})
|
||||
.collect(),
|
||||
type_hint: None,
|
||||
|
||||
@ -5,12 +5,12 @@ use crate::mir::builder::MirBuilder;
|
||||
use crate::mir::ValueId;
|
||||
use super::super::trace;
|
||||
|
||||
/// Phase 179-A: Expected ValueId for k_exit parameter (sum_final) in Pattern 3
|
||||
/// This corresponds to the exit PHI input in the JoinIR lowering for loop_with_if_phi_minimal
|
||||
/// Phase 179-A / Phase 195: Expected ValueIds for k_exit parameters in Pattern 3
|
||||
/// These correspond to the exit PHI inputs in the JoinIR lowering for loop_with_if_phi_minimal
|
||||
///
|
||||
/// # TODO (Phase 179 Task 2): Convert to ExitMeta-based exit binding generation
|
||||
///
|
||||
/// **Current State**: Hardcoded ValueId(18) - fragile and non-reusable
|
||||
/// **Current State**: Hardcoded ValueIds - fragile and non-reusable
|
||||
///
|
||||
/// **Why it's hardcoded**:
|
||||
/// - Pattern 3's lowerer (`lower_loop_with_if_phi_pattern`) returns `Option<JoinModule>`
|
||||
@ -19,12 +19,15 @@ use super::super::trace;
|
||||
///
|
||||
/// **Migration Path** (when Pattern 3 lowerer is updated):
|
||||
/// 1. Change `lower_loop_with_if_phi_pattern` to return `(JoinModule, JoinFragmentMeta)`
|
||||
/// 2. Remove this constant
|
||||
/// 2. Remove these constants
|
||||
/// 3. Use ExitMeta loop (like Pattern 4 lines 350-378) to generate exit_bindings dynamically
|
||||
/// 4. See: pattern4_with_continue.rs lines 350-378 for reference implementation
|
||||
///
|
||||
/// **Impact**: Low priority - Pattern 3 is test-only and works correctly with hardcoded value
|
||||
const PATTERN3_K_EXIT_SUM_FINAL_ID: ValueId = ValueId(18);
|
||||
/// **Impact**: Low priority - Pattern 3 is test-only and works correctly with hardcoded values
|
||||
///
|
||||
/// Phase 195: Multi-carrier support - now includes both sum_final and count_final
|
||||
const PATTERN3_K_EXIT_SUM_FINAL_ID: ValueId = ValueId(24); // Phase 195: Updated from ValueId(18)
|
||||
const PATTERN3_K_EXIT_COUNT_FINAL_ID: ValueId = ValueId(25); // Phase 195: New count carrier
|
||||
|
||||
/// Phase 194: Detection function for Pattern 3
|
||||
///
|
||||
@ -80,16 +83,20 @@ impl MirBuilder {
|
||||
PatternVariant::Pattern3,
|
||||
)?;
|
||||
|
||||
// Phase 179-B: Extract sum_var_id from context
|
||||
// Pattern 3 specifically needs the "sum" carrier
|
||||
let sum_var_id = ctx.carrier_info.carriers.iter()
|
||||
// Phase 195: Extract carrier var_ids dynamically based on what exists
|
||||
// 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"
|
||||
)
|
||||
})?
|
||||
.host_id;
|
||||
})?;
|
||||
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
|
||||
trace::trace().varmap("pattern3_start", &self.variable_map);
|
||||
@ -104,26 +111,57 @@ impl MirBuilder {
|
||||
}
|
||||
};
|
||||
|
||||
// Phase 179-B: Create boundary from context
|
||||
// Phase 195: Create boundary from context (multi-carrier support with backward compatibility)
|
||||
// Phase 201: Use JoinInlineBoundaryBuilder for clean construction
|
||||
// Canonical Builder pattern - see docs/development/current/main/joinir-boundary-builder-pattern.md
|
||||
self.trace_varmap("pattern3_before_merge");
|
||||
use crate::mir::join_ir::lowering::JoinInlineBoundaryBuilder;
|
||||
use crate::mir::join_ir::lowering::inline_boundary::LoopExitBinding;
|
||||
let boundary = JoinInlineBoundaryBuilder::new()
|
||||
.with_inputs(
|
||||
vec![ValueId(0), ValueId(1)], // JoinIR's main() parameters (i, sum init)
|
||||
vec![ctx.loop_var_id, sum_var_id], // Host's loop variables
|
||||
|
||||
// Phase 195: Build inputs and exit_bindings dynamically based on available carriers
|
||||
let (join_inputs, host_inputs, exit_bindings) = if has_count {
|
||||
// Multi-carrier: i, sum, count
|
||||
let count_var_id = count_carrier_opt.unwrap().host_id;
|
||||
(
|
||||
vec![ValueId(0), ValueId(1), ValueId(2)], // JoinIR's main() parameters
|
||||
vec![ctx.loop_var_id, sum_var_id, count_var_id], // Host's loop variables
|
||||
vec![
|
||||
LoopExitBinding {
|
||||
carrier_name: "sum".to_string(),
|
||||
join_exit_value: PATTERN3_K_EXIT_SUM_FINAL_ID, // ValueId(24)
|
||||
host_slot: sum_var_id,
|
||||
},
|
||||
LoopExitBinding {
|
||||
carrier_name: "count".to_string(),
|
||||
join_exit_value: PATTERN3_K_EXIT_COUNT_FINAL_ID, // ValueId(25)
|
||||
host_slot: count_var_id,
|
||||
}
|
||||
]
|
||||
)
|
||||
.with_exit_bindings(vec![
|
||||
// Phase 33-16: Only include non-loop-variable carriers in exit_bindings
|
||||
// The loop variable is handled separately via boundary.loop_var_name
|
||||
LoopExitBinding {
|
||||
carrier_name: "sum".to_string(),
|
||||
join_exit_value: PATTERN3_K_EXIT_SUM_FINAL_ID, // k_exit's parameter (sum_final)
|
||||
host_slot: sum_var_id, // variable_map["sum"]
|
||||
}
|
||||
])
|
||||
} else {
|
||||
// Single-carrier (backward compatibility): i, sum only
|
||||
// Phase 195: JoinIR lowerer now always generates 3 parameters (i, sum, count)
|
||||
// For backward compat, we create a dummy count variable that will be discarded
|
||||
use crate::mir::builder::emission::constant;
|
||||
let dummy_count_id = constant::emit_void(self); // Use void as dummy value
|
||||
|
||||
(
|
||||
vec![ValueId(0), ValueId(1), ValueId(2)], // JoinIR's main() parameters (i, sum, count)
|
||||
vec![ctx.loop_var_id, sum_var_id, dummy_count_id], // Host's loop variables (count is dummy)
|
||||
vec![
|
||||
LoopExitBinding {
|
||||
carrier_name: "sum".to_string(),
|
||||
join_exit_value: PATTERN3_K_EXIT_SUM_FINAL_ID, // ValueId(24)
|
||||
host_slot: sum_var_id,
|
||||
}
|
||||
// Don't bind count in single-carrier mode - it's just discarded
|
||||
]
|
||||
)
|
||||
};
|
||||
|
||||
let boundary = JoinInlineBoundaryBuilder::new()
|
||||
.with_inputs(join_inputs, host_inputs)
|
||||
.with_exit_bindings(exit_bindings)
|
||||
.with_loop_var_name(Some(ctx.loop_var_name.clone())) // Phase 33-16: Enable header PHI generation for SSA correctness
|
||||
.build();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user