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

@ -105,6 +105,8 @@ impl<'a> LoopBuilder<'a> {
// Snapshot variables at break point for exit PHI generation
let snapshot = self.get_current_variable_map();
let cur_block = self.current_block()?;
eprintln!("[DEBUG/do_break] Saved snapshot from block {:?}, vars: {:?}",
cur_block, snapshot.keys().collect::<Vec<_>>());
self.exit_snapshots.push((cur_block, snapshot));
if let Some(exit_bb) = crate::mir::builder::loops::current_exit(self.parent_builder) {
@ -237,8 +239,20 @@ impl<'a> LoopBuilder<'a> {
self.exit_snapshots.clear();
// Emit condition check in header
if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() {
eprintln!("[loopform/condition] BEFORE build_expression: current_block={:?}", self.current_block()?);
}
let cond_value = self.parent_builder.build_expression(condition)?;
// Capture the ACTUAL block that emits the branch (might differ from header_id
// if build_expression created new blocks)
let branch_source_block = self.current_block()?;
if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() {
eprintln!("[loopform/condition] AFTER build_expression: branch_source_block={:?}", branch_source_block);
}
self.emit_branch(cond_value, body_id, exit_id)?;
if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() {
eprintln!("[loopform/condition] AFTER emit_branch: current_block={:?}", self.current_block()?);
}
// Lower loop body
self.set_current_block(body_id)?;
@ -280,7 +294,7 @@ impl<'a> LoopBuilder<'a> {
// Build exit PHIs for break statements
let exit_snaps = self.exit_snapshots.clone();
loopform.build_exit_phis(self, exit_id, &exit_snaps)?;
loopform.build_exit_phis(self, exit_id, branch_source_block, &exit_snaps)?;
// Pop loop context
crate::mir::builder::loops::pop_loop_context(self.parent_builder);
@ -341,7 +355,7 @@ impl<'a> LoopBuilder<'a> {
// 4. ループ変数のPhi nodeを準備
// ここでは、ループ内で変更される可能性のある変数を事前に検出するか、
// または変数アクセス時に遅延生成する(再束縛は条件式構築後に行う)
let incs = self.prepare_loop_variables(header_id, preheader_id, &pre_vars_snapshot)?;
let incs = self.prepare_loop_variables(header_id, preheader_id, &pre_vars_snapshot, &assigned_vars)?;
// 5. 条件評価Phi nodeの結果を使用
// Heuristic pre-pin: if condition is a comparison, evaluate its operands and pin them
@ -566,18 +580,36 @@ impl<'a> LoopBuilder<'a> {
// PHI Helpers — prepare/finalize PHIs and block sealing
// =============================================================
/// ループ変数の準備(事前検出または遅延生成)
///
/// ポリシー:
/// - ループキャリア(ループ本体で再代入される変数)と pinned 変数のみを PHI 対象とする。
/// - ループ不変のローカルtext_len / pattern_len など)は preheader 値をそのまま使い、
/// 不要な PHI を張らないことで SSA 破綻(同一 ValueId の二重定義)を防ぐ。
fn prepare_loop_variables(
&mut self,
header_id: BasicBlockId,
preheader_id: BasicBlockId,
pre_vars_snapshot: &std::collections::HashMap<String, ValueId>,
assigned_vars: &[String],
) -> Result<Vec<crate::mir::phi_core::loop_phi::IncompletePhi>, String> {
use std::sync::atomic::{AtomicUsize, Ordering};
static CALL_COUNT: AtomicUsize = AtomicUsize::new(0);
let count = CALL_COUNT.fetch_add(1, Ordering::SeqCst);
// Use the variable map captured at preheader (before switching to header)
let current_vars = pre_vars_snapshot.clone();
// Use the variable map captured at preheader (before switching to header),
// but filter to:
// - ループキャリアassigned_vars に含まれる変数)
// - pinned 変数__pin$*: 受信箱など、ループをまたいで値を運ぶ必要があるもの
let mut current_vars = std::collections::HashMap::new();
for (name, &val) in pre_vars_snapshot.iter() {
if name.starts_with("__pin$") {
current_vars.insert(name.clone(), val);
continue;
}
if assigned_vars.iter().any(|v| v == name) {
current_vars.insert(name.clone(), val);
}
}
// Debug: print current_vars before prepare (guarded by env)
let dbg = std::env::var("NYASH_BUILDER_DEBUG").ok().as_deref() == Some("1");
if dbg {