fix(mir/exit_phi): Pass branch_source_block to build_exit_phis()

## Problem
Exit PHI generation was using header_id as predecessor, but when
build_expression(condition) creates new blocks, the actual branch
instruction is emitted from a different block, causing:
"phi pred mismatch: no input for predecessor BasicBlockId(X)"

## Solution
- Modified build_exit_phis() to accept branch_source_block parameter
- Capture actual block after condition evaluation in loop_builder.rs
- Use branch_source_block instead of header_id for PHI inputs

## Progress
- Error changed from ValueId(5941)/BasicBlockId(4674) to
  ValueId(5927)/BasicBlockId(4672), showing partial fix
- Added comprehensive test suite in mir_loopform_exit_phi.rs
- Added debug logging to trace condition block creation

## Status
Partial fix - unit tests pass, but Test 2 (Stage-B compilation) still
has errors. Needs further investigation of complex nested compilation
scenarios.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
nyash-codex
2025-11-18 04:26:50 +09:00
parent 5bb094d58f
commit f92779cfe8
3 changed files with 251 additions and 6 deletions

View File

@ -222,29 +222,50 @@ impl LoopFormBuilder {
/// Similar to header PHIs, but merges:
/// - Header fallthrough (normal loop exit)
/// - Break snapshots (early exit from loop body)
///
/// # Parameters
/// - `branch_source_block`: The ACTUAL block that emitted the branch to exit
/// (might differ from header_id if condition evaluation created new blocks)
pub fn build_exit_phis<O: LoopFormOps>(
&self,
ops: &mut O,
exit_id: BasicBlockId,
branch_source_block: BasicBlockId,
exit_snapshots: &[(BasicBlockId, HashMap<String, ValueId>)],
) -> Result<(), String> {
ops.set_current_block(exit_id)?;
let debug = std::env::var("NYASH_LOOPFORM_DEBUG").is_ok();
if debug {
eprintln!("[DEBUG/exit_phi] ====== Exit PHI Generation ======");
eprintln!("[DEBUG/exit_phi] exit_id = {:?}, header_id = {:?}, branch_source = {:?}",
exit_id, self.header_id, branch_source_block);
eprintln!("[DEBUG/exit_phi] exit_snapshots.len() = {}", exit_snapshots.len());
for (i, (bb, snap)) in exit_snapshots.iter().enumerate() {
eprintln!("[DEBUG/exit_phi] snapshot[{}]: block = {:?}, num_vars = {}",
i, bb, snap.len());
}
eprintln!("[DEBUG/exit_phi] pinned.len() = {}, carriers.len() = {}",
self.pinned.len(), self.carriers.len());
}
// Collect all variables that need exit PHIs
let mut all_vars: HashMap<String, Vec<(BasicBlockId, ValueId)>> = HashMap::new();
// Add header fallthrough values (pinned + carriers)
// Use branch_source_block instead of header_id because condition evaluation
// might create new blocks, and the branch is emitted from the LAST block
for pinned in &self.pinned {
all_vars
.entry(pinned.name.clone())
.or_default()
.push((self.header_id, pinned.header_phi));
.push((branch_source_block, pinned.header_phi));
}
for carrier in &self.carriers {
all_vars
.entry(carrier.name.clone())
.or_default()
.push((self.header_id, carrier.header_phi));
.push((branch_source_block, carrier.header_phi));
}
// Add break snapshot values
@ -262,15 +283,33 @@ impl LoopFormBuilder {
// Deduplicate inputs by predecessor block
sanitize_phi_inputs(&mut inputs);
if debug {
eprintln!("[DEBUG/exit_phi] Variable '{}': {} inputs before dedup", var_name, inputs.len());
for (bb, val) in &inputs {
eprintln!("[DEBUG/exit_phi] pred={:?} val={:?}", bb, val);
}
}
match inputs.len() {
0 => {} // No inputs, skip
1 => {
// Single predecessor: direct binding
if debug {
eprintln!("[DEBUG/exit_phi] Variable '{}': single predecessor, direct binding to {:?}",
var_name, inputs[0].1);
}
ops.update_var(var_name, inputs[0].1);
}
_ => {
// Multiple predecessors: create PHI node
let phi_id = ops.new_value();
if debug {
eprintln!("[DEBUG/exit_phi] Creating PHI {:?} for var '{}' with {} inputs",
phi_id, var_name, inputs.len());
for (bb, val) in &inputs {
eprintln!("[DEBUG/exit_phi] PHI input: pred={:?} val={:?}", bb, val);
}
}
ops.emit_phi(phi_id, inputs)?;
ops.update_var(var_name, phi_id);
}