wip(mir/loop): partial fix for ValueId use-before-def in loop PHIs

**Problem**: Loop body PHIs reference ValueIds that don't exist at header exit.
Example: bb6 uses %14 from bb3, but %14 is only defined in bb6.

**Partial Fix**:
1. Create header_exit_snapshot from PHI values + new pinned variables
2. Use snapshot for loop body PHI creation instead of current variable_map
3. Use snapshot for exit PHI generation

**Progress**: Error moved from BasicBlockId(4) to BasicBlockId(3)

**Remaining**: Circular dependency - PHIs reference other PHIs in same block.
Need to ensure snapshot contains only values defined before header branch.

🤖 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-17 04:41:49 +09:00
parent c8bbe389da
commit 2b6c2716e2

View File

@ -185,8 +185,63 @@ impl<'a> LoopBuilder<'a> {
} }
let condition_value = self.build_expression_with_phis(condition)?; let condition_value = self.build_expression_with_phis(condition)?;
// Fix for ValueId(17) bug: Add PHI nodes for any pinned variables created during condition evaluation
// When method calls occur in loop conditions (e.g., i < args.length()), pin_to_slot creates
// pinned receiver variables like __pin$*@recv. These must have PHI nodes in the loop header.
let post_cond_vars = self.get_current_variable_map();
let mut new_pinned_vars: Vec<(String, ValueId)> = Vec::new();
for (name, &value) in post_cond_vars.iter() {
if !name.starts_with("__pin$") { continue; }
// Check if this pinned variable existed before condition compilation
let was_in_incs = incs.iter().any(|inc| inc.var_name == *name);
if !was_in_incs {
// This is a new pinned variable created during condition evaluation
new_pinned_vars.push((name.clone(), value));
}
}
// Add PHI nodes for new pinned variables in header block
for (name, value) in new_pinned_vars {
let phi_id = self.new_value();
// Get the value from preheader (same as the current value since it was just created)
let preheader_value = value;
self.emit_phi_at_block_start(header_id, phi_id, vec![(preheader_id, preheader_value)])?;
// Update variable map to use PHI value
self.update_variable(name.clone(), phi_id);
// Add to incomplete PHIs for later sealing with latch edges
if let Some(ref mut header_incs) = self.incomplete_phis.get_mut(&header_id) {
header_incs.push(crate::mir::phi_core::loop_phi::IncompletePhi {
phi_id,
var_name: name,
known_inputs: vec![(preheader_id, preheader_value)],
});
}
}
// 6. 条件分岐 // 6. 条件分岐
let pre_branch_bb = self.current_block()?; let pre_branch_bb = self.current_block()?;
// Fix for ValueId UseBeforeDef bug: Build header snapshot from PHI values
// The exit PHI must reference values that are defined AT THE HEADER BLOCK'S EXIT.
// We can't use the current variable_map directly because it might contain values
// that are only partially defined. Instead, use the PHI values from incomplete_phis
// and any new pinned variables created during condition evaluation.
let mut header_exit_snapshot = std::collections::HashMap::new();
// First, collect all PHI values (these are defined at the header entry)
for inc in &incs {
header_exit_snapshot.insert(inc.var_name.clone(), inc.phi_id);
}
// Then, add any new pinned variables created during condition evaluation
// (these were added as PHIs in lines 204-219)
let post_cond_vars = self.get_current_variable_map();
for (name, &value) in post_cond_vars.iter() {
if !header_exit_snapshot.contains_key(name) {
header_exit_snapshot.insert(name.clone(), value);
}
}
self.emit_branch(condition_value, body_id, after_loop_id)?; self.emit_branch(condition_value, body_id, after_loop_id)?;
let _ = crate::mir::builder::loops::add_predecessor(self.parent_builder, body_id, header_id); let _ = crate::mir::builder::loops::add_predecessor(self.parent_builder, body_id, header_id);
let _ = crate::mir::builder::loops::add_predecessor(self.parent_builder, after_loop_id, header_id); let _ = crate::mir::builder::loops::add_predecessor(self.parent_builder, after_loop_id, header_id);
@ -197,6 +252,13 @@ impl<'a> LoopBuilder<'a> {
); );
} }
// Save the header snapshot for exit PHI generation
crate::mir::phi_core::loop_phi::save_block_snapshot(
&mut self.block_var_maps,
header_id,
&header_exit_snapshot,
);
// Rebind loop-carried variables to their PHI IDs now that condition is emitted // Rebind loop-carried variables to their PHI IDs now that condition is emitted
for inc in &incs { for inc in &incs {
self.update_variable(inc.var_name.clone(), inc.phi_id); self.update_variable(inc.var_name.clone(), inc.phi_id);
@ -208,10 +270,12 @@ impl<'a> LoopBuilder<'a> {
self.parent_builder self.parent_builder
.debug_replace_region(format!("loop#{}", loop_id) + "/body"); .debug_replace_region(format!("loop#{}", loop_id) + "/body");
// Materialize pinned slots at entry via single-pred Phi // Materialize pinned slots at entry via single-pred Phi
let names: Vec<String> = self.parent_builder.variable_map.keys().cloned().collect(); // IMPORTANT: Use header_exit_snapshot, not current variable_map, to avoid
// referencing values that are defined after the header's branch instruction.
let names: Vec<String> = header_exit_snapshot.keys().cloned().collect();
for name in names { for name in names {
if !name.starts_with("__pin$") { continue; } if !name.starts_with("__pin$") { continue; }
if let Some(&pre_v) = self.parent_builder.variable_map.get(&name) { if let Some(&pre_v) = header_exit_snapshot.get(&name) {
let phi_val = self.new_value(); let phi_val = self.new_value();
self.emit_phi_at_block_start(body_id, phi_val, vec![(pre_branch_bb, pre_v)])?; self.emit_phi_at_block_start(body_id, phi_val, vec![(pre_branch_bb, pre_v)])?;
self.update_variable(name, phi_val); self.update_variable(name, phi_val);
@ -366,7 +430,13 @@ impl<'a> LoopBuilder<'a> {
/// Exitブロックで変数のPHIを生成breakポイントでの値を統一 /// Exitブロックで変数のPHIを生成breakポイントでの値を統一
fn create_exit_phis(&mut self, header_id: BasicBlockId, exit_id: BasicBlockId) -> Result<(), String> { fn create_exit_phis(&mut self, header_id: BasicBlockId, exit_id: BasicBlockId) -> Result<(), String> {
let header_vars = self.get_current_variable_map(); // Use the saved header block snapshot instead of current variable map
// The current block at this point is the exit block, not the header,
// so we must retrieve the header's snapshot from block_var_maps.
let header_vars = self.block_var_maps
.get(&header_id)
.cloned()
.unwrap_or_else(|| self.get_current_variable_map());
let exit_snaps = self.exit_snapshots.clone(); let exit_snaps = self.exit_snapshots.clone();
crate::mir::phi_core::loop_phi::build_exit_phis_with( crate::mir::phi_core::loop_phi::build_exit_phis_with(
self, self,