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:
@ -229,15 +229,15 @@ Update (2025-11-16 — Phase 25.1b: selfhost builder multi-carrier & BoxTypeInsp
|
||||
- 追加メモ(Stage‑B / selfhost CLI canary 周りの現状把握):
|
||||
- `tools/smokes/v2/profiles/quick/core/phase251/selfhost_cli_run_basic_vm.sh` で使っている簡易 HakoCli.run サンプルに対しては、以前は Stage‑B (`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` から静的 Box(HakoCli など)も user factory 経由で生成できるようにした(plugins disabled でも HakoCli の NewBox 自体は成功する)。
|
||||
- これにより、Stage‑B / 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` がエラーメッセージに含まれるようになり、Stage‑B Main.main 内の `ParserBox.length()` 呼び出しが recv 未定義で落ちていることを特定できるようになった(NYASH_VM_TRACE/NYASH_VM_TRACE_EXEC 未設定時でも場所が分かる)。
|
||||
- If‑block 側の 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` がエラーメッセージに含まれるようになり、Stage‑B / Stage‑B 用最小ハーネス内の `ParserBox.length()` 呼び出しが recv 未定義で落ちていることを特定できるようになった(NYASH_VM_TRACE/NYASH_VM_TRACE_EXEC 未設定時でも場所が分かる)。
|
||||
- なお、Stage‑B を 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`、Stage‑B 箱分割後は `fn=StageBArgsBox.resolve_src/1` として報告)から「Stage‑B パスにおける `ParserBox.length()` 呼び出しで Method recv(ValueId(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 の Stage‑B / LoopBuilder / LocalSSA 整理タスクの中で (a) MethodCall の recv に対しても `Undefined value` 検査を掛ける、(b) loop_phi / LoopFormBuilder / pinned 変数の PHI 配線を修正して循環入力を構造的に防ぐ、(c) preheader 専用ブロックと variable_map スナップショットを使った LoopForm v2 の統合方針を検討する、という流れで扱う想定。
|
||||
- 2) Stage‑B 用最小ハーネス(`lang/src/compiler/tests/stageb_min_sample.hako` + `tools/test_stageb_min.sh` の Test2)を Stage‑B 経由で実行した際に、依然として `❌ VM error: Invalid value: use of undefined value ValueId(22)` が報告されるケースが残っており、これは loop/if とは別に「Method recv の materialization(pin_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 用の after‑PHIs 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 に記録(実施済み)。
|
||||
|
||||
@ -60,7 +60,14 @@ impl MirBuilder {
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn insert_phi(&mut self, inputs: Vec<(BasicBlockId, ValueId)>) -> Result<ValueId, String> {
|
||||
let phi_val = self.value_gen.next();
|
||||
// Phase 25.1b fix: Use function-local ID allocator to avoid SSA verification failures
|
||||
// This prevents PHI dst ValueIds from colliding with function-local IDs allocated later.
|
||||
// Same pattern as pin_to_slot() and the loop builder fix in e2d061d1.
|
||||
let phi_val = if let Some(ref mut f) = self.current_function {
|
||||
f.next_value_id() // Function context: use local ID allocator
|
||||
} else {
|
||||
self.value_gen.next() // Module context: use global ID allocator
|
||||
};
|
||||
|
||||
// 統一された挿入ロジック(既存パターンと完全互換)
|
||||
if let (Some(func), Some(cur_bb)) = (self.current_function.as_mut(), self.current_block) {
|
||||
@ -84,9 +91,28 @@ impl MirBuilder {
|
||||
/// - 複雑なif式で結果ValueIdを先に確保している場合
|
||||
/// - PHI命令の結果を複数箇所で参照する必要がある場合
|
||||
///
|
||||
/// ## 重要: ValueId割り当てルール
|
||||
/// `dst`は必ず関数コンテキストに適したアロケーターで確保すること:
|
||||
/// ```rust
|
||||
/// // ✅ 正しい: 関数ローカルアロケーター使用
|
||||
/// let result_val = if let Some(ref mut f) = self.current_function {
|
||||
/// f.next_value_id()
|
||||
/// } else {
|
||||
/// self.value_gen.next()
|
||||
/// };
|
||||
/// self.insert_phi_with_dst(result_val, vec![...])?;
|
||||
///
|
||||
/// // ❌ 間違い: 常にグローバルアロケーター使用(SSA違反の原因)
|
||||
/// let result_val = self.value_gen.next();
|
||||
/// ```
|
||||
///
|
||||
/// ## 例
|
||||
/// ```rust
|
||||
/// let result_val = self.value_gen.next();
|
||||
/// let result_val = if let Some(ref mut f) = self.current_function {
|
||||
/// f.next_value_id() // 関数コンテキスト: ローカルID
|
||||
/// } else {
|
||||
/// self.value_gen.next() // モジュールコンテキスト: グローバルID
|
||||
/// };
|
||||
/// // ... 複雑なロジック ...
|
||||
/// self.insert_phi_with_dst(result_val, vec![
|
||||
/// (then_block, then_value),
|
||||
|
||||
Reference in New Issue
Block a user