fix(joinir): Phase 177-3 ValueId collision fix for multi-carrier loops
Root cause: JoinIR ValueId collision between function parameters and condition bindings - Same ValueId used for both `result_init` (carrier param) and `limit` (condition var) - Phase 33-21 was overwriting condition bindings when remapping carrier PHIs Fix implemented (Option B - immediate protection): 1. Phase 177-3: Protect condition-only variables from Phase 33-21 override - Collect condition_bindings that are NOT carriers (by checking exit_bindings) - Skip remapping for these protected ValueIds 2. Phase 177-3-B: Handle body-only carriers explicitly - Carriers that appear in condition_bindings (added by Phase 176-5) - Map them to correct PHI dsts by name lookup Investigation tools added: - [DEBUG-177] trace logs for remapper state tracking - Phase 177-3 protection logging - BoundaryInjector PHI collision detection Test results: - ✅ Integer multi-carrier test: Output 3 (expected) - ⚠️ String test: RC=0 but empty output (separate issue - string concat emit) Design docs created: - phase177-parse-string-design.md: _parse_string loop analysis - phase177-carrier-evolution.md: Carrier progression Phase 174-179 Next: Investigate string concatenation emit for full _parse_string support
This commit is contained in:
@ -39,13 +39,26 @@ impl BoundaryInjector {
|
||||
/// When `boundary.loop_var_name` is set, ALL carriers (loop var + other carriers
|
||||
/// from exit_bindings) are handled by header PHIs. We skip ALL join_inputs
|
||||
/// Copy instructions to avoid overwriting the PHI results.
|
||||
///
|
||||
/// # Phase 177-3: PHI Collision Avoidance (Option B)
|
||||
///
|
||||
/// When `phi_dst_ids` contains ValueIds of existing PHI dsts in the header block,
|
||||
/// we allocate a NEW ValueId for condition_bindings instead of skipping the copy.
|
||||
/// This ensures the condition variable is available even when its remapped ValueId
|
||||
/// collides with a PHI dst.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns a HashMap mapping original ValueIds to their reallocated ValueIds
|
||||
/// (only for condition_bindings that had collisions).
|
||||
pub fn inject_boundary_copies(
|
||||
func: &mut MirFunction,
|
||||
entry_block_id: BasicBlockId,
|
||||
boundary: &JoinInlineBoundary,
|
||||
value_map: &HashMap<ValueId, ValueId>,
|
||||
phi_dst_ids: &std::collections::HashSet<ValueId>,
|
||||
debug: bool,
|
||||
) -> Result<(), String> {
|
||||
) -> Result<HashMap<ValueId, ValueId>, String> {
|
||||
// Phase 33-20: When loop_var_name is set, ALL join_inputs are handled by header PHIs
|
||||
// This includes the loop variable AND all other carriers from exit_bindings.
|
||||
// We skip ALL join_inputs Copy instructions, only condition_bindings remain.
|
||||
@ -59,7 +72,8 @@ impl BoundaryInjector {
|
||||
};
|
||||
let total_inputs = effective_join_inputs + boundary.condition_bindings.len();
|
||||
if total_inputs == 0 {
|
||||
return Ok(());
|
||||
// No inputs to process, return empty reallocations map
|
||||
return Ok(HashMap::new());
|
||||
}
|
||||
|
||||
if debug {
|
||||
@ -73,7 +87,38 @@ impl BoundaryInjector {
|
||||
);
|
||||
}
|
||||
|
||||
// Entry block を取得
|
||||
// Phase 177-3: Use PHI dst IDs passed from caller
|
||||
if debug && !phi_dst_ids.is_empty() {
|
||||
eprintln!(
|
||||
"[BoundaryInjector] Phase 177-3: Received {} PHI dst IDs to avoid: {:?}",
|
||||
phi_dst_ids.len(),
|
||||
phi_dst_ids
|
||||
);
|
||||
}
|
||||
|
||||
// Phase 177-3 Option B: First pass - allocate all new ValueIds for PHI collisions
|
||||
// We need to do this BEFORE acquiring the entry_block reference to avoid borrow conflicts
|
||||
let mut reallocations = HashMap::new();
|
||||
|
||||
for binding in &boundary.condition_bindings {
|
||||
let remapped_join = value_map.get(&binding.join_value).copied().unwrap_or(binding.join_value);
|
||||
|
||||
if phi_dst_ids.contains(&remapped_join) {
|
||||
// Collision detected! Allocate a fresh ValueId
|
||||
let fresh_dst = func.next_value_id();
|
||||
|
||||
if debug {
|
||||
eprintln!(
|
||||
"[BoundaryInjector] Phase 177-3 Option B: PHI collision for condition binding '{}': {:?} → reallocated to {:?}",
|
||||
binding.name, remapped_join, fresh_dst
|
||||
);
|
||||
}
|
||||
|
||||
reallocations.insert(binding.join_value, fresh_dst);
|
||||
}
|
||||
}
|
||||
|
||||
// Now get entry block reference (after all ValueId allocations are done)
|
||||
let entry_block = func
|
||||
.get_block_mut(entry_block_id)
|
||||
.ok_or(format!("Entry block {:?} not found", entry_block_id))?;
|
||||
@ -110,17 +155,34 @@ impl BoundaryInjector {
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 177-3 DEBUG: Check value_map in BoundaryInjector
|
||||
if debug {
|
||||
eprintln!("[DEBUG-177] === BoundaryInjector value_map ===");
|
||||
for binding in &boundary.condition_bindings {
|
||||
let lookup = value_map.get(&binding.join_value);
|
||||
eprintln!(
|
||||
"[DEBUG-177] '{}': JoinIR {:?} → {:?}",
|
||||
binding.name, binding.join_value, lookup
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 171-fix: Inject Copy instructions for condition_bindings (condition-only variables)
|
||||
// These variables are read-only and used ONLY in the loop condition.
|
||||
// Each binding explicitly specifies HOST ValueId → JoinIR ValueId mapping.
|
||||
// We inject Copy: remapped_join_value = Copy host_value
|
||||
//
|
||||
// Phase 177-3 Option B: Use pre-allocated reallocations for PHI collision cases
|
||||
for binding in &boundary.condition_bindings {
|
||||
// Look up the remapped JoinIR ValueId from value_map
|
||||
let remapped_join = value_map.get(&binding.join_value).copied().unwrap_or(binding.join_value);
|
||||
|
||||
// Copy instruction: remapped_join_value = Copy host_value
|
||||
// Phase 177-3 Option B: Check if this binding was reallocated (PHI collision case)
|
||||
let final_dst = reallocations.get(&binding.join_value).copied().unwrap_or(remapped_join);
|
||||
|
||||
// Copy instruction: final_dst = Copy host_value
|
||||
let copy_inst = MirInstruction::Copy {
|
||||
dst: remapped_join,
|
||||
dst: final_dst,
|
||||
src: binding.host_value,
|
||||
};
|
||||
|
||||
@ -128,8 +190,9 @@ impl BoundaryInjector {
|
||||
|
||||
if debug {
|
||||
eprintln!(
|
||||
"[BoundaryInjector] Condition binding '{}': Copy {:?} = Copy {:?} (JoinIR {:?} → remapped {:?})",
|
||||
binding.name, remapped_join, binding.host_value, binding.join_value, remapped_join
|
||||
"[BoundaryInjector] Condition binding '{}': Copy {:?} = Copy {:?} (JoinIR {:?} → remapped {:?}{})",
|
||||
binding.name, final_dst, binding.host_value, binding.join_value, remapped_join,
|
||||
if final_dst != remapped_join { format!(" → reallocated {:?}", final_dst) } else { String::new() }
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -145,7 +208,8 @@ impl BoundaryInjector {
|
||||
entry_block.instruction_spans.insert(0, default_span);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
// Return reallocations map for condition_bindings that had PHI collisions
|
||||
Ok(reallocations)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user