fix(phi): if-block PHI reassignment のSSA違反を解消

**問題**: If-block PHI nodes がグローバルValueIdアロケーター使用 → 関数ローカルIDと衝突
- insert_phi() が value_gen.next() 使用
- 関数内で後から割り当てられるValueIdと重複 → SSA違反

**根本原因**:
```rust
//  Before: グローバルアロケーター
let phi_val = self.value_gen.next();

//  After: 関数ローカルアロケーター
let phi_val = if let Some(ref mut f) = self.current_function {
    f.next_value_id()  // 関数コンテキスト
} else {
    self.value_gen.next()  // モジュールコンテキスト
};
```

**修正内容**:

1. **insert_phi() 修正** (src/mir/utils/phi_helpers.rs:63-70)
   - 関数コンテキストでは f.next_value_id() 使用
   - pin_to_slot() / loop builder (e2d061d1) と同じパターン

2. **insert_phi_with_dst() ドキュメント強化** (lines 94-114)
   - 正しいValueId割り当て方法を例示
   -  間違い: 常に value_gen.next() 使用
   -  正しい: 関数コンテキスト判定

**テスト結果**:
 Simple if-param-method: PASS (SSA違反なし)
 Test1/Test3: PASS (regression なし)
⚠️ Test2 (Stage-B): ValueId(22) 残存(別問題: receiver materialization)

**残存問題** (別issue):
- ValueId(22) = receiver materialization 問題
- pin_to_slot / emit_guard / BlockScheduleBox が分散
- 責務の集約が必要(次タスク)

🤖 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:54:48 +09:00
parent e2d061d113
commit cc9fb2f654
2 changed files with 33 additions and 7 deletions

View File

@ -229,15 +229,15 @@ Update (2025-11-16 — Phase 25.1b: selfhost builder multi-carrier & BoxTypeInsp
- 追加メモStageB / selfhost CLI canary 周りの現状把握):
- `tools/smokes/v2/profiles/quick/core/phase251/selfhost_cli_run_basic_vm.sh` で使っている簡易 HakoCli.run サンプルに対しては、以前は StageB (`compiler_stageb.hako`) 実行中に VM エラー:
- `❌ VM error: Invalid value: use of undefined value ValueId(N)` %0 / 97842 / 22 などが発生し、Program(JSON v0) が一行としては出力されなかった。
- 現在Task先生修正後の状況:
- 現在Task先生Claude code 君修正後)の状況:
- Rust MIR builder 側で ValueId 割り当てを全面的に見直し(関数ローカル ID への統一+`next_value_id()` 導入)、`NYASH_VM_VERIFY_MIR=1` で報告されていた `%0` 由来の Undefined value は 0 件になった。
- さらに `src/mir/phi_core/loop_phi.rs` の loop header PHI 準備処理から「`__pin$*` 変数を PHI 対象から除外する」ロジックを削除し、ピン済み変数にも header/exit 両方で PHI を張るようにしたことで、`ValueId(22)` に起因する「ループ後レシーバー未定義」エラーも解消されたpinned recv が正しく loop exit まで SSA で運ばれるようになった)。
- さらに `src/mir/phi_core/loop_phi.rs` の loop header PHI 準備処理から「`__pin$*` 変数を PHI 対象から除外する」ロジックを削除し、ピン済み変数にも header/exit 両方で PHI を張るようにしたことで、ループ後レシーバー未定義ValueId(22))問題は解消されたpinned recv が正しく loop exit まで SSA で運ばれるようになった)。
- `src/runner/modes/vm.rs` 側では、`static_box_decls` を InlineUserBoxFactory の `decls` にも統合し、`NewBox` から静的 BoxHakoCli など)も user factory 経由で生成できるようにしたplugins disabled でも HakoCli の NewBox 自体は成功する)。
- これにより、StageB / selfhost CLI canary に関する「Undefined value: %0 / 97842 / 22」系の SSA バグと「NewBox HakoCli が plugin 経路に落ちる」問題は解消済みとみなせる。
- さらに Rust VM 側の `MirInterpreter::reg_load` に開発用の追加情報を付けたことで、`Invalid value: use of undefined value ValueId(N)` が発生した際に `fn` / `last_block` / `last_inst` がエラーメッセージに含まれるようになり、StageB Main.main 内の `ParserBox.length()` 呼び出しが recv 未定義で落ちていることを特定できるようになったNYASH_VM_TRACE/NYASH_VM_TRACE_EXEC 未設定時でも場所が分かる)。
- Ifblock 側の PHI 再割り当て問題についても、`src/mir/utils/phi_helpers.rs` の `insert_phi()` がグローバルアロケーター(`value_gen.next()`)を直接叩いていた箇所を関数ローカルアロケーター(`MirFunction::next_value_id()` 経由)に切り替えることで、`%0` など既存 ValueId との衝突再定義が発生しないよう修正済み。これにより「if 内でメソッド呼び出し前に PHI が既存 ValueId を上書きする」タイプの SSA 破綻も消えている。
- さらに Rust VM 側の `MirInterpreter::reg_load` に開発用の追加情報を付けたことで、`Invalid value: use of undefined value ValueId(N)` が発生した際に `fn` / `last_block` / `last_inst` がエラーメッセージに含まれるようになり、StageB / StageB 用最小ハーネス内の `ParserBox.length()` 呼び出しが recv 未定義で落ちていることを特定できるようになったNYASH_VM_TRACE/NYASH_VM_TRACE_EXEC 未設定時でも場所が分かる)。
- なお、StageB を selfhost CLI サンプルに対して実行した際に現時点で見えている残存課題は次の 2 点:
- 1) `if args { ... }` まわりの truthy 判定ArrayBox を boolean 条件に使っている部分)の扱いに起因する型エラーであり、これは SSA ではなく「条件式の型truthy 規約」をどう定義するかという別問題として扱うPhase 25.1c 以降の型システム整理タスクで扱う想定)。
- 2) Rust VM 実行時に `❌ VM error: Invalid value: use of undefined value ValueId(17)` が発生しており、拡張したエラーメッセージ(初期段階では `fn=Main.main`、StageB 箱分割後は `fn=StageBArgsBox.resolve_src/1` として報告から「StageB パスにおける `ParserBox.length()` 呼び出しで Method recvValueId(17)) が適切に定義されていない」ことが判明している。Task先生Claude code 君の調査では、loop header/body の PHI 生成まわりで pinned スロットに対して循環依存する入力(例: あるループで header から body への PHI が `%17 = phi[%14,bb3,...]` を持つ一方で `%14` 自体は同じ `bb6` 内の後続で定義されるになっているケースがあり、Use-before-def というより「PHI 入力の cycle」に近い構造バグであることが分かっている。また、LoopForm v2 の prototype では preheader 専用ブロックを導入し、`prepare_structure` 時点で `current_vars` における `args` が preheader 手前では `ValueId(0)`(関数パラメータの本来の値)、`prepare_structure` 呼び出し直前には `ValueId(1)` に変わってしまっていることも確認されており、`new_block` / `emit_jump` / variable_map 更新のどこかで pinned/キャリア変数の初期値がずれている疑いが強い。この一連の問題は verifier (`NYASH_VM_VERIFY_MIR=1`) が現状まだ検出していない「Callee.receiver 側PHI 配線」の問題であり、Phase 25.1c の StageB / LoopBuilder / LocalSSA 整理タスクの中で (a) MethodCall の recv に対しても `Undefined value` 検査を掛ける、(b) loop_phi / LoopFormBuilder / pinned 変数の PHI 配線を修正して循環入力を構造的に防ぐ、(c) preheader 専用ブロックと variable_map スナップショットを使った LoopForm v2 の統合方針を検討する、という流れで扱う想定
- 2) StageB 用最小ハーネス(`lang/src/compiler/tests/stageb_min_sample.hako` + `tools/test_stageb_min.sh` の Test2を StageB 経由で実行した際に、依然として `❌ VM error: Invalid value: use of undefined value ValueId(22)` が報告されるケースが残っており、これは loop/if とは別に「Method recv の materializationpin_to_slot で割り当てたレシーバー ID に対して実際の Copy が emit されていない)」経路の問題であることが分かってきている。`pin_to_slot(%13,"@recv")` が `%22` を割り当てる一方で `%22 = copy %13` がブロック中に存在しないため、`Callee::Method { receiver: Some(%22) }` が未定義値を参照してしまう。関連コードとしては `emit_guard` / BlockScheduleBox の「PHI 直後に Copy を挿入する materialization ロジック現在は無効化気味」があり、この系統の「Receiver materialization」を別タスクとして切り分けて再調査する必要があるPhase 25.1c: Loop/PHI 修正と並行して、Method recv 用の afterPHIs Copy 復活/整理を検討する)
- Next tasks (Phase 25.1b → 25.1c handoff / Codex):
1. Rust 層 Call/ExternCall 契約のドキュメント固定Step 4.1
- `src/mir/builder/builder_calls.rs` / `src/backend/mir_interpreter/handlers/{calls,externs,extern_provider}.rs` / `src/runtime/plugin_loader_v2/enabled/extern_functions.rs` をベースに、「MethodCall/ExternCall/hostbridge.extern_invoke/ env.codegen/env.mirbuilder」の SSOT を Phase 25.1b README に記録(実施済み)。