Phase 25.1b: VM undefined-value diagnostics and builder SSA helpers
This commit is contained in:
@ -226,16 +226,18 @@ Update (2025-11-16 — Phase 25.1b: selfhost builder multi-carrier & BoxTypeInsp
|
|||||||
- `BoxTypeInspectorBox._describe` → `hostbridge.extern_invoke("env.box_introspect","kind",[value])` → `env.box_introspect.kind` provider → plugin loader v2 の BoxIntrospect 実装までの経路を Stage‑3 VM で確認済み(MapBox / ArrayBox に対して kind / is_map / is_array が正しく返る)。
|
- `BoxTypeInspectorBox._describe` → `hostbridge.extern_invoke("env.box_introspect","kind",[value])` → `env.box_introspect.kind` provider → plugin loader v2 の BoxIntrospect 実装までの経路を Stage‑3 VM で確認済み(MapBox / ArrayBox に対して kind / is_map / is_array が正しく返る)。
|
||||||
- selfhost builder 経由の fib multi-carrier emit(`tools/hakorune_emit_mir.sh tmp/fib_multi.hako tmp/fib_multi_mir.json`)では、`env.box_introspect.kind` / `JSON.stringify` 起因の Invalid instruction が消え、child rc=0 で完走する。ログには multi-carrier detection タグと `[funcs/basic:loop.multi_carrier] -> TestBox.fib/1` が出力される。
|
- selfhost builder 経由の fib multi-carrier emit(`tools/hakorune_emit_mir.sh tmp/fib_multi.hako tmp/fib_multi_mir.json`)では、`env.box_introspect.kind` / `JSON.stringify` 起因の Invalid instruction が消え、child rc=0 で完走する。ログには multi-carrier detection タグと `[funcs/basic:loop.multi_carrier] -> TestBox.fib/1` が出力される。
|
||||||
- 出力 MIR(JSON) には `"name":"TestBox.fib/1"` を持つ関数が含まれており、selfhost-first multi-carrier 経路が Box 型 API 依存で起動できることを確認済み。ただしトップレベル JSON 形(`{"functions":[{...main...},{...kind:\"MIR\"...}]}` と provider 側の module 形の差)は残っており、これは 25.1c 以降の構造タスクとして整理予定。
|
- 出力 MIR(JSON) には `"name":"TestBox.fib/1"` を持つ関数が含まれており、selfhost-first multi-carrier 経路が Box 型 API 依存で起動できることを確認済み。ただしトップレベル JSON 形(`{"functions":[{...main...},{...kind:\"MIR\"...}]}` と provider 側の module 形の差)は残っており、これは 25.1c 以降の構造タスクとして整理予定。
|
||||||
- 追加メモ(Stage‑B / selfhost CLI canary 周りの現状把握):
|
- 追加メモ(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 エラー:
|
- `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)` が発生し、Program(JSON v0) が一行としては出力されない(`tools/hakorune_emit_mir.sh` 側でも Program 抽出に失敗している)。
|
- `❌ VM error: Invalid value: use of undefined value ValueId(N)` (%0 / 97842 / 22 など)が発生し、Program(JSON v0) が一行としては出力されなかった。
|
||||||
- `NYASH_VM_VERIFY_MIR=1` を立てて `compiler_stageb.hako` を直接実行すると、Stage‑B が生成した MIR に対して多数の `Undefined value %0 used in block ...` 診断が出る:
|
- 現在(Task先生修正後)の状況:
|
||||||
- 例: `Stage1UsingResolverBox._collect_using_entries/1`, `ParserStringUtilsBox.skip_ws/2`, `ParserIdentScanBox.scan_ident/2`, `ParserBox.parse_stmt2/2` など。
|
- Rust MIR builder 側で ValueId 割り当てを全面的に見直し(関数ローカル ID への統一+`next_value_id()` 導入)、`NYASH_VM_VERIFY_MIR=1` で報告されていた `%0` 由来の Undefined value は 0 件になった。
|
||||||
- これは BoxTypeInspector / env.box_introspect.kind とは独立した、Stage‑B パイプライン(ParserBox→Stage1UsingResolver→FuncScannerBox 等)の MIR 生成側の既知問題として切り出す。
|
- さらに `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 自体は成功する)。
|
||||||
- `.hako → Program(JSON v0) → MIR(JSON)` のうち、multi-carrier fib などの core ケースについては selfhost builder 経路が PASS 済みであり、Stage‑B の不整合は「Stage1 CLI 相当の大きめ入力でのみ顕在化している VM/MIR バグ」として扱う。
|
- これにより、Stage‑B / selfhost CLI canary に関する「Undefined value: %0 / 97842 / 22」系の SSA バグと「NewBox HakoCli が plugin 経路に落ちる」問題は解消済みとみなせる。
|
||||||
- `tools/hakorune_emit_mir.sh` の `diagnose_stageb_failure()` に `Invalid value: use of undefined value` 向けの診断メッセージを追加し、今後同様の症状が出たときに `NYASH_VM_VERIFY_MIR=1`+`docs/private/roadmap/phases/phase-20.33/DEBUG.md` へ誘導できるようにした(構造バグとして Stage‑B/MIR 側で追う)。
|
- さらに 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 未設定時でも場所が分かる)。
|
||||||
- selfhost CLI canary(HakoCli.run/2 lowering)は、Stage‑B 側の MIR 整合性が改善されるまでは「Stage‑B 側に起因する未解決バグ」として保留し、BoxIntrospect / multi‑carrier 経路の進行とは切り離して扱う。
|
- なお、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, last_block=Some(BasicBlockId(3419)), last_inst=Some(Call { ... ParserBox.length() [recv: %17] ... })`)から「Stage‑B Main.main 内の `ParserBox.length()` 呼び出しにおいて recv スロット(ValueId(17)) が定義されていない」ことが判明している。これは verifier (`NYASH_VM_VERIFY_MIR=1`) では検出されない「Callee.receiver 側の SSA 漏れ」であり、Phase 25.1c の Stage‑B / LoopBuilder / LocalSSA 整理タスクの中で「MethodCall の recv に対しても `Undefined value` 検査を掛ける+MirBuilder 側で未定義 recv を構造的に防ぐ」方針で扱う想定。
|
||||||
- Next tasks (Phase 25.1b → 25.1c handoff / Codex):
|
- Next tasks (Phase 25.1b → 25.1c handoff / Codex):
|
||||||
1. Rust 層 Call/ExternCall 契約のドキュメント固定(Step 4.1)
|
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 に記録(実施済み)。
|
- `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 に記録(実施済み)。
|
||||||
|
|||||||
@ -40,19 +40,24 @@ Status: Step0〜3 実装済み・Step4(Method/Extern)実装フェーズ
|
|||||||
|
|
||||||
#### Stage‑B と selfhost CLI canary(HakoCli.run/2)の現状
|
#### Stage‑B と selfhost CLI canary(HakoCli.run/2)の現状
|
||||||
|
|
||||||
- selfhost CLI の最小ケース(`tools/smokes/v2/profiles/quick/core/phase251/selfhost_cli_run_basic_vm.sh` が生成する HakoCli.run サンプル)に対しては、現状 Stage‑B 実行中に VM エラー:
|
- selfhost CLI の最小ケース(`tools/smokes/v2/profiles/quick/core/phase251/selfhost_cli_run_basic_vm.sh` が生成する HakoCli.run サンプル)に対しては、修正前は Stage‑B 実行中に VM エラー:
|
||||||
- `❌ VM error: Invalid value: use of undefined value ValueId(N)` が発生し、Program(JSON v0) が 1 行としては出力されない(`tools/hakorune_emit_mir.sh` が Program 抽出に失敗する)。
|
- `❌ VM error: Invalid value: use of undefined value ValueId(N)`(%0 / 97842 / 22 など)が発生し、Program(JSON v0) が 1 行としては出力されなかった(`tools/hakorune_emit_mir.sh` が Program 抽出に失敗する)。
|
||||||
- `NYASH_VM_VERIFY_MIR=1` を立てて `lang/src/compiler/entry/compiler_stageb.hako` を直接叩くと、Stage‑B が生成した MIR に対して:
|
- `NYASH_VM_VERIFY_MIR=1` を立てて `lang/src/compiler/entry/compiler_stageb.hako` を直接叩くと、修正前は Stage‑B が生成した MIR に対して:
|
||||||
- `Stage1UsingResolverBox._collect_using_entries/1`
|
- `Stage1UsingResolverBox._collect_using_entries/1`
|
||||||
- `ParserStringUtilsBox.skip_ws/2`
|
- `ParserStringUtilsBox.skip_ws/2`
|
||||||
- `ParserIdentScanBox.scan_ident/2`
|
- `ParserIdentScanBox.scan_ident/2`
|
||||||
- `ParserBox.parse_stmt2/2`
|
- `ParserBox.parse_stmt2/2`
|
||||||
- などに `Undefined value %0 used in block ...` が多数報告されることが確認できる(詳細は `docs/private/roadmap/phases/phase-20.33/DEBUG.md` の「Invalid value: use of undefined value ValueId(N)」節を参照)。
|
- などに `Undefined value %0 used in block ...` が多数報告されていた(詳細は `docs/private/roadmap/phases/phase-20.33/DEBUG.md` の「Invalid value: use of undefined value ValueId(N)」節を参照)。
|
||||||
|
- Task先生による Rust MIR builder 側の修正(ValueId 割り当て統一+loop PHI/pinned 変数の扱い修正)後は:
|
||||||
|
- `%0` / 97842 / 22 に起因する Undefined value / Invalid value エラーは `NYASH_VM_VERIFY_MIR=1` / 実行時ともに解消済み。
|
||||||
|
- pinned 変数(`__pin$*@recv` など)も loop header/exit で正しく PHI に乗るようになり、ループ後のメソッドレシーバーが未定義になる問題も再現しなくなった。
|
||||||
|
- 現時点で selfhost CLI サンプルに対して残っている課題は:
|
||||||
|
- 1) Rust VM 実行時に `❌ VM error: Invalid value: use of undefined value ValueId(17)` が発生しており、拡張済みエラーメッセージ(`fn=Main.main, last_block=Some(BasicBlockId(3419)), last_inst=Some(Call { ... ParserBox.length() [recv: %17] ... })`)から「Stage‑B Main.main 内の `ParserBox.length()` 呼び出しにおいて recv スロット(ValueId(17)) が定義されていない」ことが分かっている。これは verifier がまだチェックしていない「Callee.receiver 側の SSA 漏れ」であり、Phase 25.1c の Stage‑B / LoopBuilder / LocalSSA 整理タスクで修正する前提。
|
||||||
|
- 2) `if args { ... }` まわりの truthy 判定(ArrayBox を boolean 条件に使っている部分)の扱いに起因する型/意味論の揺れが残っており、こちらも 25.1c の型システム整理タスクで `ArrayBox` の truthy 規約を明文化した上で揃える想定。
|
||||||
|
- 3) `NewBox HakoCli` が plugin 前提で解決されてしまう問題は、VM 側の static box factory 統合(静的 Box を User factory にも広告する)により解消済みであり、`NYASH_DISABLE_PLUGINS=1` でも静的 Box として HakoCli を生成できるようになっている(selfhost CLI canary では NewBox 自体はもはやブロッカーではない)。
|
||||||
- 対応方針(Phase 25.1b 時点):
|
- 対応方針(Phase 25.1b 時点):
|
||||||
- BoxTypeInspector / multi‑carrier LoopForm 経路とは独立した **Stage‑B/MIR 側の構造バグ** として扱い、selfhost CLI canary(HakoCli.run/2 lowering)はこの問題が解消されるまで「Stage‑B 側の既知の未修正バグ」として保留する。
|
- BoxTypeInspector / multi‑carrier LoopForm 経路とは独立した **Stage‑B/MIR 側の SSA/型システム/Box 解決の構造問題** として扱い、selfhost CLI canary(HakoCli.run/2 lowering)はこれらが片付くまでは「25.1c の構造タスク待ち」として扱う。
|
||||||
- `tools/hakorune_emit_mir.sh` には `diagnose_stageb_failure()` を追加し、Stage‑B の標準出力に `Invalid value: use of undefined value` が含まれている場合に:
|
- `tools/hakorune_emit_mir.sh` の `diagnose_stageb_failure()` は維持し、Stage‑B の標準出力に `Invalid value: use of undefined value` が含まれている場合には `NYASH_VM_VERIFY_MIR=1`+`compiler_stageb.hako` 直叩き、および `docs/private/roadmap/phases/phase-20.33/DEBUG.md` への導線を表示する。加えて、VM 側の `MirInterpreter::reg_load` が `fn` / `last_block` / `last_inst` を含めてエラー文字列を出すようになったため、Stage‑B 由来の undefined value は「どの関数のどの Call(どの recv)」で発生しているかを 1 行で特定できる。
|
||||||
- `[stageb/diagnose] VM reported 'use of undefined value' during Stage‑B execution.` などのタグを出しつつ、
|
|
||||||
- `NYASH_VM_VERIFY_MIR=1`+`compiler_stageb.hako` 直叩き、および `docs/private/roadmap/phases/phase-20.33/DEBUG.md` への導線を表示するようにした。
|
|
||||||
|
|
||||||
### Rust provider (`env.mirbuilder.emit`)
|
### Rust provider (`env.mirbuilder.emit`)
|
||||||
|
|
||||||
|
|||||||
@ -38,11 +38,50 @@ Status: planning(構造整理フェーズ・挙動は変えない)
|
|||||||
- Hako 側: `string_helpers` / `BoxHelpers` / `MirSchemaBox` / `JsonEmitBox` / `LoopOptsBox` に散っている i64 unwrap ロジックを、小さなユーティリティ(仮: `box_numeric_view.hako`)に寄せる。
|
- Hako 側: `string_helpers` / `BoxHelpers` / `MirSchemaBox` / `JsonEmitBox` / `LoopOptsBox` に散っている i64 unwrap ロジックを、小さなユーティリティ(仮: `box_numeric_view.hako`)に寄せる。
|
||||||
- Rust 側: `NyashBox` から i64 を取り出す `as_i64` 的な関数を 1 箇所に置き、extern / BoxIntrospect 経路からはそれを使う。
|
- Rust 側: `NyashBox` から i64 を取り出す `as_i64` 的な関数を 1 箇所に置き、extern / BoxIntrospect 経路からはそれを使う。
|
||||||
|
|
||||||
|
5. **Stage‑B Main を箱に分割して SSA/デバッグを軽くする**
|
||||||
|
- 現状の `compiler_stageb.hako: Main.main` は:
|
||||||
|
- CLI 引数パース (`--source` / `--bundle-*` / `--require-mod`)
|
||||||
|
- bundle/require 解決 (`BundleResolver`)
|
||||||
|
- body 抽出 (`body_src` の抽出ロジック)
|
||||||
|
- ParserBox 呼び出し (`parse_program2` → emit JSON)
|
||||||
|
- defs スキャン (`FuncScannerBox.scan_all_boxes`)
|
||||||
|
が 1 関数に詰め込まれており、MIR 上でも巨大な `Main.main` になっている。
|
||||||
|
- 25.1c ではこれを「箱理論」に沿って分割する:
|
||||||
|
- `StageBArgsBox`(CLI 引数と bundle/require の扱いだけを担当)
|
||||||
|
- `StageBBodyExtractorBox`(`body_src` 抽出ロジックだけを担当)
|
||||||
|
- `StageBDriverBox`(ParserBox/FuncScannerBox を呼んで Program(JSON v0) を emit)
|
||||||
|
- Rust 側 MirBuilder には、この箱ごとの小さな関数をそのまま MIR に落とさせることで、`Main.main` の SSA/Loop の複雑さを減らし、今回のような ValueId 追跡をしやすくする。
|
||||||
|
- 併せて、現状 selfhost CLI サンプルで観測されている `Main.main` 内の `ParserBox.length()` 呼び出しに対する recv 未定義エラー(`Invalid value: use of undefined value ValueId(17)`)を、Stage‑B Main 分割+LocalSSA/LoopBuilder 整理の一環として根本修正する(MethodCall の recv にも SSA/verify を適用する)。
|
||||||
|
|
||||||
|
6. **LoopBuilder / pin スロットの型付け・箱化**
|
||||||
|
- いまの LoopBuilder は `__pin$*$@recv` のような文字列ベースの「内部変数名」を `variable_map` に直接突っ込んで、SSA/phi/pin を管理している。
|
||||||
|
- 25.1c では、Loop 状態を「箱」として切り出して型付けする:
|
||||||
|
- 例: `LoopStateBox`(Rust 側構造体)に
|
||||||
|
- `recv_slots`(Method receiver 用)
|
||||||
|
- `index_slots`(ループカウンタ用)
|
||||||
|
- `limit_slots`(limit/上限 expr 用)
|
||||||
|
を明示的に持たせる。
|
||||||
|
- `LoopBuilder::emit_phi_at_block_start` / `update_variable` は、この LoopStateBox を通じてのみ pin/phi を操作し、「recv に Null/未定義が混ざらない」ことを構造レベルで保証する。
|
||||||
|
|
||||||
|
7. **ビルダー観測用の専用レイヤ(デバッグ箱)**
|
||||||
|
- すでに `NYASH_BUILDER_TRACE_RECV` / `NYASH_BUILDER_DEBUG` などで ad-hoc に eprintln を入れているが、出力箇所が複数ファイルに散っていて再利用しにくい。
|
||||||
|
- 25.1c ではこれを `builder.observe` 的なモジュール(箱)に集約する:
|
||||||
|
- 例: `observe::recv::log(fn, bb, name, src, dst)`、`observe::phi::log(fn, bb, dst, inputs)` など。
|
||||||
|
- ポリシー:
|
||||||
|
- すべて dev トグル(NYASH_BUILDER_TRACE_*)越しに呼ぶ。
|
||||||
|
- 本番挙動は変えず、「どこをどうトレースできるか」を構造として明示する。
|
||||||
|
|
||||||
|
8. **Stage‑B 向けの極小 MIR 再現ハーネス**
|
||||||
|
- `docs/private/roadmap/phases/phase-20.33/DEBUG.md` にあるような Stage‑B 向けメモを踏まえ、Stage‑B/MirBuilder 用の「極小 Hako → MIR テスト」を 1 つ用意する。
|
||||||
|
- 例:
|
||||||
|
- 100〜200 行程度の `.hako` を `lang/src/compiler/tests/stageb_min_sample.hako` のようなファイルに固定。
|
||||||
|
- Rust 側で MirBuilder に直接その AST を食わせて MIR を生成し、`NYASH_VM_VERIFY_MIR=1` で「Undefined value」が出ないことを確認するユニット/スモークを足す(構造バグ検知用)。
|
||||||
|
- これにより、Stage‑B/LoopBuilder に関する修正が `.hako` 本番コード全体に依存せず、小さな再現ケースで検証できるようにする。
|
||||||
|
|
||||||
## 進め方メモ
|
## 進め方メモ
|
||||||
|
|
||||||
- 先にドキュメントを書く(env extern / BoxIntrospect / numeric view の仕様を `docs/specs` 配下に整理)→ そのあとで Bridge / VM / Hako を小さく揃える。
|
- 先にドキュメントを書く(env extern / BoxIntrospect / numeric view の仕様を `docs/specs` 配下に整理)→ そのあとで Bridge / VM / Hako を小さく揃える。
|
||||||
- 既存フェーズとの関係:
|
- 既存フェーズとの関係:
|
||||||
- Phase 25.1b: selfhost builder / multi‑carrier / BoxTypeInspector 実装フェーズ(機能側)。
|
- Phase 25.1b: selfhost builder / multi‑carrier / BoxTypeInspector 実装フェーズ(機能側)。
|
||||||
- Phase 25.1c: そのうち「env.* / hostbridge.* / BoxIntrospect の構造と責務」を整理するメタフェーズ(構造側)。
|
- Phase 25.1c: そのうち「env.* / hostbridge.* / BoxIntrospect」に加えて、Stage‑B Main / LoopBuilder / builder 観測レイヤの構造と責務も整理するメタフェーズ(構造側)。
|
||||||
- 挙動を変えないこと(Fail‑Fast / default path は現状維持)を前提に、小さな差分で進める。
|
- 挙動を変えないこと(Fail‑Fast / default path は現状維持)を前提に、小さな差分で進める。
|
||||||
|
|
||||||
|
|||||||
@ -29,8 +29,9 @@ impl MirInterpreter {
|
|||||||
.map(|k| format!("{:?}", k))
|
.map(|k| format!("{:?}", k))
|
||||||
.collect();
|
.collect();
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"[vm-trace] reg_load undefined id={:?} last_block={:?} last_inst={:?} regs={}",
|
"[vm-trace] reg_load undefined id={:?} fn={} last_block={:?} last_inst={:?} regs={}",
|
||||||
id,
|
id,
|
||||||
|
self.cur_fn.as_deref().unwrap_or("<unknown>"),
|
||||||
self.last_block,
|
self.last_block,
|
||||||
self.last_inst,
|
self.last_inst,
|
||||||
keys.join(", ")
|
keys.join(", ")
|
||||||
@ -43,9 +44,13 @@ impl MirInterpreter {
|
|||||||
if tolerate {
|
if tolerate {
|
||||||
return Ok(VMValue::Void);
|
return Ok(VMValue::Void);
|
||||||
}
|
}
|
||||||
|
let fn_name = self.cur_fn.as_deref().unwrap_or("<unknown>");
|
||||||
Err(VMError::InvalidValue(format!(
|
Err(VMError::InvalidValue(format!(
|
||||||
"use of undefined value {:?}",
|
"use of undefined value {:?} (fn={}, last_block={:?}, last_inst={:?})",
|
||||||
id
|
id,
|
||||||
|
fn_name,
|
||||||
|
self.last_block,
|
||||||
|
self.last_inst,
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -215,6 +215,12 @@ impl BasicBlock {
|
|||||||
/// Insert instruction at the beginning (after phi instructions)
|
/// Insert instruction at the beginning (after phi instructions)
|
||||||
pub fn insert_instruction_after_phis(&mut self, instruction: MirInstruction) {
|
pub fn insert_instruction_after_phis(&mut self, instruction: MirInstruction) {
|
||||||
let phi_count = self.phi_instructions().count();
|
let phi_count = self.phi_instructions().count();
|
||||||
|
if std::env::var("NYASH_SCHEDULE_TRACE").ok().as_deref() == Some("1") {
|
||||||
|
if let MirInstruction::Copy { dst, src } = &instruction {
|
||||||
|
eprintln!("[insert-after-phis] bb={:?} phi_count={} inserting Copy dst=%{} src=%{} total_inst={}",
|
||||||
|
self.id, phi_count, dst.0, src.0, self.instructions.len());
|
||||||
|
}
|
||||||
|
}
|
||||||
self.effects = self.effects | instruction.effects();
|
self.effects = self.effects | instruction.effects();
|
||||||
self.instructions.insert(phi_count, instruction);
|
self.instructions.insert(phi_count, instruction);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -451,11 +451,33 @@ impl MirBuilder {
|
|||||||
_ => (None, None, None),
|
_ => (None, None, None),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Extract function name before mutable borrow to avoid borrowck error
|
||||||
|
let current_fn_name = function.signature.name.clone();
|
||||||
|
|
||||||
if let Some(block) = function.get_block_mut(block_id) {
|
if let Some(block) = function.get_block_mut(block_id) {
|
||||||
// Invariant: Call must always carry a Callee (unified path).
|
// Invariant: Call must always carry a Callee (unified path).
|
||||||
if let MirInstruction::Call { callee, .. } = &instruction {
|
if let MirInstruction::Call { callee, .. } = &instruction {
|
||||||
if callee.is_none() {
|
if callee.is_none() {
|
||||||
return Err("builder invariant violated: MirInstruction::Call.callee must be Some (unified call)".into());
|
return Err("builder invariant violated: MirInstruction::Call.callee must be Some (unified call)".into());
|
||||||
|
} else if std::env::var("NYASH_BUILDER_TRACE_RECV").ok().as_deref() == Some("1") {
|
||||||
|
use crate::mir::definitions::call_unified::Callee;
|
||||||
|
if let Some(Callee::Method { box_name, method, receiver: Some(r), .. }) = callee {
|
||||||
|
let names: Vec<String> = self
|
||||||
|
.variable_map
|
||||||
|
.iter()
|
||||||
|
.filter(|(_, &vid)| vid == *r)
|
||||||
|
.map(|(k, _)| k.clone())
|
||||||
|
.collect();
|
||||||
|
eprintln!(
|
||||||
|
"[builder/recv-trace] fn={} bb={:?} method={}.{} recv=%{} aliases={:?}",
|
||||||
|
current_fn_name,
|
||||||
|
self.current_block,
|
||||||
|
box_name,
|
||||||
|
method,
|
||||||
|
r.0,
|
||||||
|
names
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if utils::builder_debug_enabled() {
|
if utils::builder_debug_enabled() {
|
||||||
|
|||||||
@ -41,6 +41,29 @@ impl super::MirBuilder {
|
|||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(_) => receiver,
|
Err(_) => receiver,
|
||||||
};
|
};
|
||||||
|
if std::env::var("NYASH_BUILDER_TRACE_RECV").ok().as_deref() == Some("1") {
|
||||||
|
let current_fn = self
|
||||||
|
.current_function
|
||||||
|
.as_ref()
|
||||||
|
.map(|f| f.signature.name.clone())
|
||||||
|
.unwrap_or_else(|| "<none>".to_string());
|
||||||
|
let bb = self.current_block;
|
||||||
|
let names: Vec<String> = self
|
||||||
|
.variable_map
|
||||||
|
.iter()
|
||||||
|
.filter(|(_, &vid)| vid == receiver)
|
||||||
|
.map(|(k, _)| k.clone())
|
||||||
|
.collect();
|
||||||
|
eprintln!(
|
||||||
|
"[builder/recv-trace] fn={} bb={:?} method={}.{} recv=%{} aliases={:?}",
|
||||||
|
current_fn,
|
||||||
|
bb,
|
||||||
|
box_type.clone().unwrap_or_else(|| "<?>".to_string()),
|
||||||
|
method,
|
||||||
|
receiver.0,
|
||||||
|
names
|
||||||
|
);
|
||||||
|
}
|
||||||
target = CallTarget::Method { box_type, method, receiver: receiver_pinned };
|
target = CallTarget::Method { box_type, method, receiver: receiver_pinned };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,6 +186,29 @@ impl super::MirBuilder {
|
|||||||
// (covers rare paths where earlier pin did not take effect)
|
// (covers rare paths where earlier pin did not take effect)
|
||||||
callee = match callee {
|
callee = match callee {
|
||||||
Callee::Method { box_name, method, receiver: Some(r), certainty } => {
|
Callee::Method { box_name, method, receiver: Some(r), certainty } => {
|
||||||
|
if std::env::var("NYASH_BUILDER_TRACE_RECV").ok().as_deref() == Some("1") {
|
||||||
|
let current_fn = self
|
||||||
|
.current_function
|
||||||
|
.as_ref()
|
||||||
|
.map(|f| f.signature.name.clone())
|
||||||
|
.unwrap_or_else(|| "<none>".to_string());
|
||||||
|
let bb = self.current_block;
|
||||||
|
let names: Vec<String> = self
|
||||||
|
.variable_map
|
||||||
|
.iter()
|
||||||
|
.filter(|(_, &vid)| vid == r)
|
||||||
|
.map(|(k, _)| k.clone())
|
||||||
|
.collect();
|
||||||
|
eprintln!(
|
||||||
|
"[builder/recv-trace] fn={} bb={:?} method={}.{} recv=%{} aliases={:?}",
|
||||||
|
current_fn,
|
||||||
|
bb,
|
||||||
|
box_name.clone(),
|
||||||
|
method,
|
||||||
|
r.0,
|
||||||
|
names
|
||||||
|
);
|
||||||
|
}
|
||||||
// Prefer pinning to a slot so start_new_block can propagate it across entries.
|
// Prefer pinning to a slot so start_new_block can propagate it across entries.
|
||||||
let r_pinned = self.pin_to_slot(r, "@recv").unwrap_or(r);
|
let r_pinned = self.pin_to_slot(r, "@recv").unwrap_or(r);
|
||||||
Callee::Method { box_name, method, receiver: Some(r_pinned), certainty }
|
Callee::Method { box_name, method, receiver: Some(r_pinned), certainty }
|
||||||
|
|||||||
@ -7,11 +7,15 @@ pub fn finalize_call_operands(builder: &mut MirBuilder, callee: &mut Callee, arg
|
|||||||
// Step 1: LocalSSA materialization for receiver/args
|
// Step 1: LocalSSA materialization for receiver/args
|
||||||
crate::mir::builder::ssa::local::finalize_callee_and_args(builder, callee, args);
|
crate::mir::builder::ssa::local::finalize_callee_and_args(builder, callee, args);
|
||||||
|
|
||||||
// Step 2: Stabilize receiver placement within the current block
|
// Step 2: Disabled - BlockScheduleBox insert-after-phis doesn't work correctly
|
||||||
// Ensure a Copy right after PHIs and a tail Copy just before Call emission, so that
|
// The Copy instructions are being inserted but then lost when blocks are finalized.
|
||||||
// dominance/order invariants are satisfied without duplicating logic at call sites.
|
// Instead, rely solely on LocalSSA which uses emit_instruction (the normal path).
|
||||||
if let Callee::Method { box_name, method, receiver: Some(r0), certainty } = callee.clone() {
|
//
|
||||||
if let Ok(r_after) = crate::mir::builder::schedule::block::BlockScheduleBox::ensure_after_phis_copy(builder, r0) {
|
// TODO: Fix BlockScheduleBox or remove it entirely if LocalSSA is sufficient.
|
||||||
|
|
||||||
|
/* DISABLED - causes ValueId(22) undefined error
|
||||||
|
if let Callee::Method { box_name, method, receiver: Some(r_local), certainty } = callee.clone() {
|
||||||
|
if let Ok(r_after) = crate::mir::builder::schedule::block::BlockScheduleBox::ensure_after_phis_copy(builder, r_local) {
|
||||||
if let Ok(r_tail) = crate::mir::builder::schedule::block::BlockScheduleBox::emit_before_call_copy(builder, r_after) {
|
if let Ok(r_tail) = crate::mir::builder::schedule::block::BlockScheduleBox::emit_before_call_copy(builder, r_after) {
|
||||||
*callee = Callee::Method { box_name, method, receiver: Some(r_tail), certainty };
|
*callee = Callee::Method { box_name, method, receiver: Some(r_tail), certainty };
|
||||||
} else {
|
} else {
|
||||||
@ -19,6 +23,7 @@ pub fn finalize_call_operands(builder: &mut MirBuilder, callee: &mut Callee, arg
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verify block schedule invariants after emitting a call (dev-only WARNs inside).
|
/// Verify block schedule invariants after emitting a call (dev-only WARNs inside).
|
||||||
|
|||||||
@ -10,9 +10,16 @@ impl BlockScheduleBox {
|
|||||||
pub fn ensure_after_phis_copy(builder: &mut MirBuilder, src: ValueId) -> Result<ValueId, String> {
|
pub fn ensure_after_phis_copy(builder: &mut MirBuilder, src: ValueId) -> Result<ValueId, String> {
|
||||||
if let Some(bb) = builder.current_block {
|
if let Some(bb) = builder.current_block {
|
||||||
if let Some(&cached) = builder.schedule_mat_map.get(&(bb, src)) {
|
if let Some(&cached) = builder.schedule_mat_map.get(&(bb, src)) {
|
||||||
|
if std::env::var("NYASH_SCHEDULE_TRACE").ok().as_deref() == Some("1") {
|
||||||
|
eprintln!("[schedule/after-phis] bb={:?} src=%{} cached dst=%{}", bb, src.0, cached.0);
|
||||||
|
}
|
||||||
return Ok(cached);
|
return Ok(cached);
|
||||||
}
|
}
|
||||||
let dst = builder.next_value_id();
|
let dst = builder.next_value_id();
|
||||||
|
if std::env::var("NYASH_SCHEDULE_TRACE").ok().as_deref() == Some("1") {
|
||||||
|
eprintln!("[schedule/after-phis] bb={:?} src=%{} new dst=%{} (inserting Copy) builder.current_block={:?}",
|
||||||
|
bb, src.0, dst.0, builder.current_block);
|
||||||
|
}
|
||||||
builder.insert_copy_after_phis(dst, src)?;
|
builder.insert_copy_after_phis(dst, src)?;
|
||||||
builder.schedule_mat_map.insert((bb, src), dst);
|
builder.schedule_mat_map.insert((bb, src), dst);
|
||||||
return Ok(dst);
|
return Ok(dst);
|
||||||
@ -26,6 +33,10 @@ impl BlockScheduleBox {
|
|||||||
// Prefer to reuse the after-phis materialized id for this src in this block
|
// Prefer to reuse the after-phis materialized id for this src in this block
|
||||||
let base = Self::ensure_after_phis_copy(builder, src)?;
|
let base = Self::ensure_after_phis_copy(builder, src)?;
|
||||||
let dst = builder.next_value_id();
|
let dst = builder.next_value_id();
|
||||||
|
if std::env::var("NYASH_SCHEDULE_TRACE").ok().as_deref() == Some("1") {
|
||||||
|
eprintln!("[schedule/before-call] bb={:?} src=%{} base=%{} dst=%{} (emitting Copy)",
|
||||||
|
builder.current_block, src.0, base.0, dst.0);
|
||||||
|
}
|
||||||
builder.emit_instruction(MirInstruction::Copy { dst, src: base })?;
|
builder.emit_instruction(MirInstruction::Copy { dst, src: base })?;
|
||||||
// Propagate metadata to keep dst consistent with base
|
// Propagate metadata to keep dst consistent with base
|
||||||
crate::mir::builder::metadata::propagate::propagate(builder, base, dst);
|
crate::mir::builder::metadata::propagate::propagate(builder, base, dst);
|
||||||
|
|||||||
@ -37,6 +37,31 @@ pub fn ensure(builder: &mut MirBuilder, v: ValueId, kind: LocalKind) -> ValueId
|
|||||||
if let Some(&loc) = builder.local_ssa_map.get(&key) {
|
if let Some(&loc) = builder.local_ssa_map.get(&key) {
|
||||||
return loc;
|
return loc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CRITICAL FIX: If `v` is from a pinned slot, check if there's a PHI value for that slot
|
||||||
|
// in the current block's variable_map. If so, use the PHI value directly instead of
|
||||||
|
// emitting a Copy from the old value (which might not be defined in this block).
|
||||||
|
let names_for_v: Vec<String> = builder.variable_map.iter()
|
||||||
|
.filter(|(k, &vid)| vid == v && k.starts_with("__pin$"))
|
||||||
|
.map(|(k, _)| k.clone())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if let Some(first_pin_name) = names_for_v.first() {
|
||||||
|
// This value is from a pinned slot. Check if the slot has been updated
|
||||||
|
// (e.g., by a PHI) in the current block.
|
||||||
|
if let Some(¤t_val) = builder.variable_map.get(first_pin_name) {
|
||||||
|
if current_val != v {
|
||||||
|
// The slot has been updated (likely by a PHI). Use the updated value.
|
||||||
|
if std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") {
|
||||||
|
eprintln!("[local-ssa] phi-redirect bb={:?} kind={:?} slot={} %{} -> %{}",
|
||||||
|
bb, kind, first_pin_name, v.0, current_val.0);
|
||||||
|
}
|
||||||
|
builder.local_ssa_map.insert(key, current_val);
|
||||||
|
return current_val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let loc = builder.next_value_id();
|
let loc = builder.next_value_id();
|
||||||
// Best-effort: errors are propagated by caller; we ignore here to keep helper infallible
|
// Best-effort: errors are propagated by caller; we ignore here to keep helper infallible
|
||||||
let _ = builder.emit_instruction(crate::mir::MirInstruction::Copy { dst: loc, src: v });
|
let _ = builder.emit_instruction(crate::mir::MirInstruction::Copy { dst: loc, src: v });
|
||||||
|
|||||||
@ -76,9 +76,22 @@ impl super::MirBuilder {
|
|||||||
self.local_ssa_map.clear();
|
self.local_ssa_map.clear();
|
||||||
// BlockSchedule materialize cache is per-block as well
|
// BlockSchedule materialize cache is per-block as well
|
||||||
self.schedule_mat_map.clear();
|
self.schedule_mat_map.clear();
|
||||||
// Entry materialization for pinned slots only when not suppressed.
|
// Entry materialization for pinned slots: re-read from variable_map after PHIs are emitted.
|
||||||
// This provides block-local defs in single-predecessor flows without touching user vars.
|
// This ensures pinned slots reflect the correct PHI values in merge blocks.
|
||||||
|
//
|
||||||
|
// Strategy: Instead of emitting Copy instructions (which would be before PHIs),
|
||||||
|
// we simply update the variable_map to point to the current block's values.
|
||||||
|
// LoopBuilder and IfBuilder already update variable_map with PHI values, so
|
||||||
|
// pinned slots will automatically pick up the correct values.
|
||||||
|
//
|
||||||
|
// No action needed here - just clear caches.
|
||||||
if !self.suppress_pin_entry_copy_next {
|
if !self.suppress_pin_entry_copy_next {
|
||||||
|
// Cache clearing is already done above, so nothing more to do here.
|
||||||
|
// The key insight: pinned slot variables are part of variable_map,
|
||||||
|
// and LoopBuilder/IfBuilder already manage PHIs for ALL variables in variable_map,
|
||||||
|
// including pinned slots.
|
||||||
|
}
|
||||||
|
if false && !self.suppress_pin_entry_copy_next { // Keep old code for reference
|
||||||
// First pass: copy all pin slots and remember old->new mapping
|
// First pass: copy all pin slots and remember old->new mapping
|
||||||
let names: Vec<String> = self.variable_map.keys().cloned().collect();
|
let names: Vec<String> = self.variable_map.keys().cloned().collect();
|
||||||
let mut pin_renames: Vec<(super::ValueId, super::ValueId)> = Vec::new();
|
let mut pin_renames: Vec<(super::ValueId, super::ValueId)> = Vec::new();
|
||||||
@ -328,12 +341,25 @@ impl super::MirBuilder {
|
|||||||
/// Insert a Copy immediately after PHI nodes in the current block (position-stable).
|
/// Insert a Copy immediately after PHI nodes in the current block (position-stable).
|
||||||
pub(crate) fn insert_copy_after_phis(&mut self, dst: super::ValueId, src: super::ValueId) -> Result<(), String> {
|
pub(crate) fn insert_copy_after_phis(&mut self, dst: super::ValueId, src: super::ValueId) -> Result<(), String> {
|
||||||
if let (Some(ref mut function), Some(bb)) = (&mut self.current_function, self.current_block) {
|
if let (Some(ref mut function), Some(bb)) = (&mut self.current_function, self.current_block) {
|
||||||
|
if std::env::var("NYASH_SCHEDULE_TRACE").ok().as_deref() == Some("1") {
|
||||||
|
eprintln!("[utils/insert-copy-after-phis] bb={:?} dst=%{} src=%{} attempting...",
|
||||||
|
bb, dst.0, src.0);
|
||||||
|
}
|
||||||
if let Some(block) = function.get_block_mut(bb) {
|
if let Some(block) = function.get_block_mut(bb) {
|
||||||
|
if std::env::var("NYASH_SCHEDULE_TRACE").ok().as_deref() == Some("1") {
|
||||||
|
eprintln!("[utils/insert-copy-after-phis] bb={:?} dst=%{} src=%{} phi_count={} SUCCESS",
|
||||||
|
bb, dst.0, src.0, block.phi_instructions().count());
|
||||||
|
}
|
||||||
// Propagate effects on the block
|
// Propagate effects on the block
|
||||||
block.insert_instruction_after_phis(super::MirInstruction::Copy { dst, src });
|
block.insert_instruction_after_phis(super::MirInstruction::Copy { dst, src });
|
||||||
// Lightweight metadata propagation (unified)
|
// Lightweight metadata propagation (unified)
|
||||||
crate::mir::builder::metadata::propagate::propagate(self, src, dst);
|
crate::mir::builder::metadata::propagate::propagate(self, src, dst);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
} else {
|
||||||
|
if std::env::var("NYASH_SCHEDULE_TRACE").ok().as_deref() == Some("1") {
|
||||||
|
eprintln!("[utils/insert-copy-after-phis] bb={:?} dst=%{} src=%{} FAILED: block not found",
|
||||||
|
bb, dst.0, src.0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err("No current function/block to insert copy".to_string())
|
Err("No current function/block to insert copy".to_string())
|
||||||
|
|||||||
@ -52,11 +52,9 @@ pub fn register_user_defined_factory(factory: Arc<dyn crate::box_factory::BoxFac
|
|||||||
let registry = get_global_unified_registry();
|
let registry = get_global_unified_registry();
|
||||||
let mut registry_lock = registry.lock().unwrap();
|
let mut registry_lock = registry.lock().unwrap();
|
||||||
|
|
||||||
// Insert at position 1 (after builtin, before plugin)
|
// Phase 25.1b: delegate to policy-aware register() so that
|
||||||
// This maintains priority: builtin > user > plugin
|
// type_cache is rebuilt and user-defined Box types (HakoCli など)
|
||||||
if registry_lock.factories.len() >= 2 {
|
// are correctly advertised to the registry. Priorityは
|
||||||
registry_lock.factories.insert(1, factory);
|
// FactoryPolicy + factory_type に従って決まる。
|
||||||
} else {
|
|
||||||
registry_lock.register(factory);
|
registry_lock.register(factory);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user