From 2b6c2716e2b7b0bbca61eeee7c480c0df1240123 Mon Sep 17 00:00:00 2001 From: nyash-codex Date: Mon, 17 Nov 2025 04:41:49 +0900 Subject: [PATCH] wip(mir/loop): partial fix for ValueId use-before-def in loop PHIs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **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 --- src/mir/loop_builder.rs | 76 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 73 insertions(+), 3 deletions(-) diff --git a/src/mir/loop_builder.rs b/src/mir/loop_builder.rs index 8a8be63b..da0fa5d7 100644 --- a/src/mir/loop_builder.rs +++ b/src/mir/loop_builder.rs @@ -185,8 +185,63 @@ impl<'a> LoopBuilder<'a> { } 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. 条件分岐 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)?; 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); @@ -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 for inc in &incs { self.update_variable(inc.var_name.clone(), inc.phi_id); @@ -208,10 +270,12 @@ impl<'a> LoopBuilder<'a> { self.parent_builder .debug_replace_region(format!("loop#{}", loop_id) + "/body"); // Materialize pinned slots at entry via single-pred Phi - let names: Vec = 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 = header_exit_snapshot.keys().cloned().collect(); for name in names { 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(); self.emit_phi_at_block_start(body_id, phi_val, vec![(pre_branch_bb, pre_v)])?; self.update_variable(name, phi_val); @@ -366,7 +430,13 @@ impl<'a> LoopBuilder<'a> { /// Exitブロックで変数のPHIを生成(breakポイントでの値を統一) 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(); crate::mir::phi_core::loop_phi::build_exit_phis_with( self,