fix(loop/phi): loop header pinned receiver PHI の未定義ValueId解消

**問題**: TestArgs.process/1 で `Invalid value: use of undefined value ValueId(14)`
- loop条件でのmethod call(args.length())がpin_to_slotで pinned receiver作成
- header PHIが未定義のValueIdを参照していた

**根本原因**:
- pinned変数のheader PHI作成時、`preheader_value = value` として
  header blockで作成された値を使用
- 正しくは preheader block の元値を参照すべき

**修正内容**:

1. **find_copy_source ヘルパー追加** (src/mir/loop_builder.rs:50-80)
   - Copy命令を遡ってpreheaderの元値を特定
   - NYASH_LOOP_TRACE=1 でデバッグ出力

2. **pinned変数PHI作成ロジック強化** (lines 368-410)
   - NEW pinned変数: find_copy_source()で正しいpreheader値取得
   - INHERITED pinned変数: pre_vars_snapshot から値取得
   - PHI inputs に正しい preheader_value を設定

3. **LoopFormOps::new_value修正** (lines 1122-1127)
   - value_gen.next() → next_value_id() に統一
   - 関数ローカルアロケーター使用でValueId整合性確保

4. **next_value_id可視性拡大** (src/mir/builder/utils.rs:33)
   - pub(super) → pub(crate) でループビルダーから使用可能に

**テスト結果**:
 Test1 (Direct VM): PASS
 Test3 (MIR verify): PASS
⚠️ Test2 (Stage-B): ValueId(17)はif-block PHI問題(別issue)

**残存問題**:
- Stage-B の ValueId(17) はループ前のif-blockで発生
- if-block PHI reassignment (%0 = phi [%2, bb3]) の構造的問題
- 別タスクで対処予定

🤖 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 06:31:31 +09:00
parent 47071f2755
commit e2d061d113
2 changed files with 58 additions and 10 deletions

View File

@ -30,7 +30,7 @@ impl super::MirBuilder {
/// - Inside function: uses function-local allocator /// - Inside function: uses function-local allocator
/// - Outside function: uses module-global allocator /// - Outside function: uses module-global allocator
#[inline] #[inline]
pub(super) fn next_value_id(&mut self) -> super::ValueId { pub(crate) fn next_value_id(&mut self) -> super::ValueId {
if let Some(ref mut f) = self.current_function { if let Some(ref mut f) = self.current_function {
f.next_value_id() // Function context f.next_value_id() // Function context
} else { } else {

View File

@ -46,7 +46,39 @@ pub struct LoopBuilder<'a> {
impl<'a> LoopBuilder<'a> { impl<'a> LoopBuilder<'a> {
// Implement phi_core LoopPhiOps on LoopBuilder for in-place delegation // Implement phi_core LoopPhiOps on LoopBuilder for in-place delegation
/// Find the source value of a Copy instruction in a given block
/// If `dst` is defined by a Copy instruction `dst = copy src`, return Some(src)
/// Otherwise return None
fn find_copy_source(&self, block_id: BasicBlockId, dst: ValueId) -> Option<ValueId> {
let func = self.parent_builder.current_function.as_ref()?;
let block = func.blocks.get(&block_id)?;
let trace = std::env::var("NYASH_LOOP_TRACE").ok().as_deref() == Some("1");
if trace {
eprintln!("[loop/copy-trace] searching for dst={:?} in block={:?}, {} instructions",
dst, block_id, block.instructions.len());
}
for (idx, inst) in block.instructions.iter().enumerate() {
if let MirInstruction::Copy { dst: inst_dst, src } = inst {
if trace {
eprintln!("[loop/copy-trace] inst#{}: %{} = copy %{}", idx, inst_dst.0, src.0);
}
if *inst_dst == dst {
if trace {
eprintln!("[loop/copy-trace] FOUND! dst={:?} src={:?}", dst, src);
}
return Some(*src);
}
}
}
if trace {
eprintln!("[loop/copy-trace] NOT FOUND for dst={:?}", dst);
}
None
}
// ============================================================= // =============================================================
// Control Helpers — break/continue/jumps/unreachable handling // Control Helpers — break/continue/jumps/unreachable handling
// ============================================================= // =============================================================
@ -336,22 +368,36 @@ impl<'a> LoopBuilder<'a> {
// When method calls occur in loop conditions (e.g., i < args.length()), pin_to_slot creates // 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. // 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 post_cond_vars = self.get_current_variable_map();
let mut new_pinned_vars: Vec<(String, ValueId)> = Vec::new(); let mut new_pinned_vars: Vec<(String, ValueId, ValueId)> = Vec::new();
if trace {
eprintln!("[loop] post_cond_vars has {} entries", post_cond_vars.len());
}
for (name, &value) in post_cond_vars.iter() { for (name, &value) in post_cond_vars.iter() {
if !name.starts_with("__pin$") { continue; } if !name.starts_with("__pin$") { continue; }
// Check if this pinned variable existed before condition compilation // Check if this pinned variable existed before condition compilation
let was_in_incs = incs.iter().any(|inc| inc.var_name == *name); let was_in_incs = incs.iter().any(|inc| inc.var_name == *name);
if !was_in_incs { let was_in_preheader = pre_vars_snapshot.contains_key(name);
// This is a new pinned variable created during condition evaluation if !was_in_incs && !was_in_preheader {
new_pinned_vars.push((name.clone(), value)); // This is a NEW pinned variable created during condition evaluation (not inherited from preheader)
// We need to find the source of the copy instruction that created this value
let preheader_value = self.find_copy_source(header_id, value).unwrap_or(value);
if trace {
eprintln!("[loop] NEW pinned var: {} value={:?} preheader={:?}", name, value, preheader_value);
}
new_pinned_vars.push((name.clone(), value, preheader_value));
} else if !was_in_incs && was_in_preheader {
// This pinned variable existed in preheader, so it needs a PHI but we should use the preheader value
let preheader_value = pre_vars_snapshot.get(name).copied().unwrap_or(value);
if trace {
eprintln!("[loop] INHERITED pinned var: {} value={:?} preheader={:?}", name, value, preheader_value);
}
new_pinned_vars.push((name.clone(), value, preheader_value));
} }
} }
// Add PHI nodes for new pinned variables in header block // Add PHI nodes for new pinned variables in header block
for (name, value) in new_pinned_vars { for (name, value, preheader_value) in new_pinned_vars {
let phi_id = self.new_value(); 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)])?; self.emit_phi_at_block_start(header_id, phi_id, vec![(preheader_id, preheader_value)])?;
// Update variable map to use PHI value // Update variable map to use PHI value
self.update_variable(name.clone(), phi_id); self.update_variable(name.clone(), phi_id);
@ -1076,7 +1122,9 @@ impl crate::mir::phi_core::loop_phi::LoopPhiOps for LoopBuilder<'_> {
// Implement LoopFormOps trait for LoopBuilder to support LoopFormBuilder integration // Implement LoopFormOps trait for LoopBuilder to support LoopFormBuilder integration
impl<'a> LoopFormOps for LoopBuilder<'a> { impl<'a> LoopFormOps for LoopBuilder<'a> {
fn new_value(&mut self) -> ValueId { fn new_value(&mut self) -> ValueId {
self.parent_builder.value_gen.next() // Use function-local allocator via MirBuilder helper to keep
// ValueId ranges consistent with the current function's value_count.
self.parent_builder.next_value_id()
} }
fn is_parameter(&self, name: &str) -> bool { fn is_parameter(&self, name: &str) -> bool {