📊 Phase 25.1l: Region観測レイヤー骨格 + スコープ契約設計理解
**Region Box統一理論の実装開始**
新規追加:
- src/mir/region/mod.rs: Region/RefSlotKind型定義
- src/mir/region/observer.rs: Region観測レイヤー
- docs/development/roadmap/phases/phase-25.1l/: 設計ドキュメント
主要概念:
- Region Box = Function/Loop/If の統一箱
- RefSlotKind = GC管理用スロット種別(Strong/Weak/Borrowed/NonRef)
- 観測専用(NYASH_REGION_TRACE=1で動作、挙動変更なし)
設計理解の深化:
- ValueId(40)問題 = LoopForm v2スコープ契約違反の症状
- 根本解決 = Region観測で無名一時値のスコープまたぎを検出
- 箱理論3原則: 境界明確化/差し替え可能/段階的移行
関連議論:
- ChatGPT提案: Region統一理論でGC/寿命管理の基盤構築
- SlotRegistry: 変数の単一真実源(SSOT)
- 階層構造: FunctionRegion → LoopRegion → IfRegion
次のステップ:
- Phase 1: Region観測(現在)- 非破壊的追加
- Phase 2: メタデータ出力(MIR JSON拡張)
- Phase 3: GC統合(retain/release挿入)
テスト追加:
- lang/src/compiler/tests/stageb_mini_driver.hako
- tools/test_loopssa_breakfinder_slot.sh
Build: ✅ 全警告は既存のもの
Tests: 既存テスト全て緑維持
This commit is contained in:
@ -1,14 +1,62 @@
|
|||||||
# Current Task — Phase 21.8 / 25 / 25.1 / 25.2(Numeric Core & Stage0/Stage1 Bootstrap)
|
# Current Task — Phase 21.8 / 25 / 25.1 / 25.2(Numeric Core & Stage0/Stage1 Bootstrap)
|
||||||
|
|
||||||
Update (2025-11-18 — Phase 25.1i: LoopSSA v2 (.hako) & ControlFormBox 統合の設計開始)
|
Update (2025-11-18 — Phase 25.1k: LoopSSA v2 (.hako) & Stage‑B harness 追従 / Rust receiver 統合)
|
||||||
- Context:
|
- Context:
|
||||||
- Rust 側の LoopForm v2 / Conservative PHI / ControlForm 統合(25.1g)まで完了し、Loop/If の SSA/PHI は ControlForm ベースで安定。
|
- Rust 側の LoopForm v2 / Conservative PHI / ControlForm 統合(25.1g〜h)と、レガシー LoopBuilder 削除(7‑F〜H)まで完了し、Loop/If の SSA/PHI は ControlForm ベースで安定。
|
||||||
- Stage‑B 最小ハーネス `tools/test_stageb_min.sh` は、Test1=緑、Test2/3 は `.hako` 側 LoopSSA / BreakFinderBox / PhiInjectorBox 起源のエラーが残っている状態。
|
- 25.1f〜j で ControlForm / ControlFormBox / LoopSSA/BreakFinderBox/PhiInjectorBox の責務整理・観測導線が整った状態。
|
||||||
- Plan (25.1i):
|
- Stage‑B 最小ハーネス `tools/test_stageb_min.sh` は:
|
||||||
- LoopSSA (`lang/src/compiler/builder/ssa/loopssa.hako`) の責務を ControlFormBox 前提に整理し、Rust 側 LoopShape/IfShape に揃えた説明を書く。
|
- Test 1: 直接 VM 実行(`stageb_min_sample.hako`)→ 緑(RC=0)
|
||||||
- `lang/src/shared/mir/control_form_box.hako` に `from_loop` / `from_if` などの最小メソッドを追加し、JSON v0 から loop/if 形を復元できる足場を作る。
|
- Test 2: `.hako` Stage‑B コンパイラ(`compiler_stageb.hako` + LoopSSA v2)経由で同ソースをコンパイルする **「.hako パーサ+Stage‑B コンパイラ経路」**
|
||||||
- BreakFinderBox / PhiInjectorBox はひとまず現状維持としつつ、ControlFormBox を生成してログに出す観測導線を追加(挙動はまだ変えない)。
|
→ `BreakFinderBox.find_breaks/2` → `_find_loops/2` の receiver 未定義(`use of undefined value ValueId(..)`)が継続
|
||||||
- `HAKO_COMPILER_BUILDER_TRACE=1` で Stage‑B Test2/3 実行時に loop/exit/body の構造が観測できるようにし、undefined ValueId / `%0` の原因追跡に備える。
|
- Test 3: 同テストファイルを Rust MIR ビルダーで実行(`NYASH_VM_VERIFY_MIR=1`)→ `%0` 由来 SSA エラー(LoopSSA 無効時の baseline として扱う)
|
||||||
|
- Done (Rust側 25.1k 前半):
|
||||||
|
- Receiver 実体化の一本化:
|
||||||
|
- `CallMaterializerBox::materialize_receiver_in_callee`(`src/mir/builder/calls/materializer.rs`)を no-op にし、receiver の pinning/LocalSSA は `receiver::finalize_method_receiver` へ集約。
|
||||||
|
- `emit_guard::finalize_call_operands` からのみ `receiver::finalize_method_receiver` を呼ぶ構造に整理。
|
||||||
|
- pin slot ↔ LocalSSA の連携強化:
|
||||||
|
- `MirBuilder` に `pin_slot_names: HashMap<ValueId, String>` を追加(`src/mir/builder.rs`)し、`pin_to_slot` 実行時に元値と pinned 値の両方に slot 名を記録。
|
||||||
|
- `LocalSSA.ensure`(`src/mir/builder/ssa/local.rs`)で:
|
||||||
|
- `variable_map` と `pin_slot_names` から slot 名を復元し、同 slot にぶら下がる最新 ValueId があればそちらに **phi‑redirect** するように変更。
|
||||||
|
- これにより `__pin$*@recv` 由来の古い ValueId から最新 PHI 値への構造的な追従が可能になった。
|
||||||
|
- Compiler系 Box の StaticCompiler 明文化:
|
||||||
|
- `CalleeResolverBox::classify_box_kind`(`src/mir/builder/calls/resolver.rs`)に `BreakFinderBox` / `PhiInjectorBox` / `LoopSSA` を追加し、
|
||||||
|
Stage‑1/Stage‑B 用 LoopSSA 箱を `CalleeBoxKind::StaticCompiler` として扱うようにした(静的 compiler box 群として明示)。
|
||||||
|
- Plan (25.1k 後半 / .hako 側):
|
||||||
|
- Stage‑B Test2 の `BreakFinderBox._find_loops/2` receiver 未定義(ValueId 50→46→39)の最小再現と切り分け:
|
||||||
|
- `tools/test_stageb_min.sh` Test2 で得られる Program(JSON v0) を保存し、Rust MIR 側で `NYASH_VM_VERIFY_MIR=1` を通して Rust SSA/PHI を再確認。
|
||||||
|
- 同じ JSON に対して `.hako` の `LoopSSA.stabilize_merges(json)` を単独実行するテストを用意し、BreakFinderBox / PhiInjectorBox 前後で JSON を比較。
|
||||||
|
- `.hako` LoopSSA v2 本体のステップ実装(phase-25.1k README の K‑A〜D に沿って進める):
|
||||||
|
- K‑A: ValueId(50) 系エラーの発生位置を JSON v0 レベルで特定。
|
||||||
|
- K‑B: BreakFinderBox の LoopScope 検出を ControlFormBox / Rust LoopForm v2 に合わせて保守的に改善。
|
||||||
|
- K‑C: PhiInjectorBox に Carrier/Pinned ベースの v2 入口を追加(既存 `_collect_phi_vars` は当面維持)。
|
||||||
|
- K‑D: Stage‑B Test3 の `%0` SSA 問題が悪化していないことを確認しつつ、LoopSSA 有効/無効の切り分けを残す。
|
||||||
|
|
||||||
|
Update (2025-11-18 — Phase 25.1l: Region/GC 観測レイヤー導入(Rust 側のみ))
|
||||||
|
- Context:
|
||||||
|
- LoopForm v2 / ControlForm / Conservative PHI Box により、If/Loop の SSA/PHI は Rust 側で安定しているが、
|
||||||
|
GC や Stage‑B 由来の寿命問題を構造的に見るための「Region/スコープ」ビューが不足していた。
|
||||||
|
- 25.1l では GC 挙動を変えずに、Rust MIR 側に観測専用の Region/RefSlotKind レイヤーを導入する。
|
||||||
|
- Done:
|
||||||
|
- `src/mir/region/mod.rs`:
|
||||||
|
- `RefSlotKind`(`StrongRoot/WeakRoot/Borrowed/NonRef`)、`SlotMetadata { name, ref_kind }`、`RegionId`、`RegionKind(Loop/If)`、`Region { id, kind, entry_block, exit_blocks, slots }` を定義。
|
||||||
|
- `Region::classify_ref_kind(MirType)` で Box/Array/Future を StrongRoot、それ以外のプリミティブを NonRef として暫定分類。
|
||||||
|
- `src/mir/region/observer.rs`:
|
||||||
|
- `observe_control_form(builder: &MirBuilder, form: &ControlForm)` を実装し、`NYASH_REGION_TRACE=1` のときだけ Region 情報をログ出力。
|
||||||
|
- 現時点では `builder.variable_map` と `builder.value_types` から live スロットと RefKind を推定し、`[region/observe] fn=... id=RegionId(..) kind=Loop/If entry=bb.. exits=[..] slots=[..]` の形で eprintln する(メモリには保存しない)。
|
||||||
|
- dev 用フィルタとして `func_name.contains("StageB")` の関数のみを観測対象にし、Stage‑B BodyExtractor まわりの Region/Slot 状況が見えるようにした。
|
||||||
|
- `src/mir/loop_builder.rs`:
|
||||||
|
- ループ構築部と `lower_if_in_loop` の If 降下部で、`ControlForm::from_loop` / `ControlForm::from_if` 生成直後に `observe_control_form` を呼び、Loop/If ごとに Region ログを出せるようにした(NYASH_REGION_TRACE=0 のときは完全に無効)。
|
||||||
|
- テスト:
|
||||||
|
- `cargo test -q mir_loopform_exit_phi -- --nocapture` は NYASH_REGION_TRACE=0/1 の両方で緑(Loop/If SSA/PHI 挙動に変化なし)。
|
||||||
|
- Plan(25.1l 後半〜次フェーズへのブリッジ):
|
||||||
|
- Region 木と FunctionRegion:
|
||||||
|
- 次フェーズで `RegionKind::Function` と FunctionRegion (#0) を導入し、関数スコープを含む Region 木を構築する(entry=関数 bb0, exits=ret ブロック群)。
|
||||||
|
- MirBuilder に「現在属している RegionId スタック」を追加し、Loop/If 降下時に parent/children 関係を記録できるようにする(観測専用)。
|
||||||
|
- SlotRegistry(関数ごと 1 箱)の設計:
|
||||||
|
- 長期的には `FunctionSlotRegistry` のような箱を導入し、`slot_id -> { name, MirType, RefSlotKind }` / `name -> slot_id` を SSOT として管理する。
|
||||||
|
- 25.1l ではまだ `variable_map` ベースの暫定観測に留め、SlotId ベースへの移行は Region 木導入後に小さく刻んで進める。
|
||||||
|
- GC 統合:
|
||||||
|
- Region/Slot 観測結果をもとに、どの Region 境界で StrongRoot スロットを retain/release すべきかの設計を 25.1m 以降で行う(Rust の既存 GC/Barrier 実装を壊さない範囲に限定)。
|
||||||
|
|
||||||
Update (2025-11-18 — Phase 25.1g: Conservative PHI ↔ ControlForm 統合の準備)
|
Update (2025-11-18 — Phase 25.1g: Conservative PHI ↔ ControlForm 統合の準備)
|
||||||
- Context:
|
- Context:
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
# Phase 25.1k — LoopSSA v2 実装 & Stage‑B SSA 安定化(.hako 本体版)
|
# Phase 25.1k — LoopSSA v2 実装 & Stage‑B SSA 安定化(.hako 本体版)
|
||||||
|
|
||||||
Status: planning(.hako 側 LoopSSA v2 本体実装/Rust 側は既存 SSA/PHI を SSOT として維持)
|
Status: in progress(.hako 側 LoopSSA v2 本体実装/Rust 側は既存 SSA/PHI を SSOT として維持)
|
||||||
|
|
||||||
## ゴール
|
## ゴール
|
||||||
|
|
||||||
@ -8,11 +8,14 @@ Status: planning(.hako 側 LoopSSA v2 本体実装/Rust 側は既存 SSA/PHI
|
|||||||
`.hako` 側 LoopSSA v2 の **実装本体** に踏み込むフェーズだよ。
|
`.hako` 側 LoopSSA v2 の **実装本体** に踏み込むフェーズだよ。
|
||||||
- 具体的には:
|
- 具体的には:
|
||||||
- Stage‑B minimal harness(`tools/test_stageb_min.sh`)の Test 2/3 で出ている:
|
- Stage‑B minimal harness(`tools/test_stageb_min.sh`)の Test 2/3 で出ている:
|
||||||
- `BreakFinderBox._find_loops/2` 周辺の `use of undefined value ValueId(50)`(Rust VM 側エラー)
|
- Test 2: `.hako` Stage‑B コンパイラ(`compiler_stageb.hako`)が `stageb_min_sample.hako` をコンパイルする
|
||||||
- `%0` 由来の SSA エラー(`NYASH_VM_VERIFY_MIR=1` 時)
|
**「.hako パーサ+Stage‑B コンパイラ経路(FuncScanner / LoopSSA / BreakFinderBox / PhiInjectorBox)」** での Rust VM エラー
|
||||||
を **LoopSSA v2 の改善によって減らす/消す** ことを狙う。
|
(`BreakFinderBox.find_breaks/2` → `_find_loops/2` の receiver 未定義 `use of undefined value ValueId(..)`)
|
||||||
|
- Test 3: 同テストファイルを Rust MIR ビルダーで実行したときの `%0` 由来 SSA エラー(`NYASH_VM_VERIFY_MIR=1` 時)
|
||||||
|
を **LoopSSA v2 の改善によって減らす/消す** ことを狙う(Rust 側 LoopForm v2 / Conservative PHI Box は既に緑で SSOT 済み)。
|
||||||
- 文字列ハードコードベースの `_collect_phi_vars` / synthetic `"r{block}_{var}"` を、
|
- 文字列ハードコードベースの `_collect_phi_vars` / synthetic `"r{block}_{var}"` を、
|
||||||
Carrier/Pinned ベースの設計に一歩近づける(完全置き換えまでは行かなくても OK)。
|
Carrier/Pinned ベースの設計に一歩近づける(完全置き換えまでは行かなくても OK)。
|
||||||
|
- Rust 側 LoopForm v2 / Conservative PHI Box は SSOT として維持し、.hako 側 LoopSSA v2 は dev トグルで常時検証しつつ徐々に寄せていく。
|
||||||
|
|
||||||
## 前提(25.1j までで揃っているもの)
|
## 前提(25.1j までで揃っているもの)
|
||||||
|
|
||||||
@ -38,15 +41,15 @@ Status: planning(.hako 側 LoopSSA v2 本体実装/Rust 側は既存 SSA/PHI
|
|||||||
### K‑A: Stage‑B Test2 の ValueId(50) 問題の最小再現と LoopSSA 切り分け
|
### K‑A: Stage‑B Test2 の ValueId(50) 問題の最小再現と LoopSSA 切り分け
|
||||||
|
|
||||||
- 目的:
|
- 目的:
|
||||||
- `BreakFinderBox._find_loops/2` で発生している `use of undefined value ValueId(50)` を、
|
- `BreakFinderBox._find_loops/2` で発生している `use of undefined value ValueId(50)`(現在は 46→39 と推移中)を、
|
||||||
「LoopSSA が生成した JSON の問題なのか」「他フェーズの JSON なのか」切り分ける。
|
「LoopSSA / BreakFinderBox が生成した JSON / MIR の問題なのか」「それ以前の Stage‑B パイプラインの問題なのか」切り分ける。
|
||||||
- ステップ:
|
- ステップ:
|
||||||
1. `tools/test_stageb_min.sh` Test2 の JSON 出力を一時ファイルに保存(Stage‑B → Program(JSON v0) 直後)。
|
1. `tools/test_stageb_min.sh` Test2 の Program(JSON v0) 出力を一時ファイルに保存(Stage‑B → Program(JSON v0) 直後)。
|
||||||
2. その JSON に対して:
|
2. その JSON に対して:
|
||||||
- Rust 側 MirBuilder に直接食わせて `NYASH_VM_VERIFY_MIR=1` を通す。
|
- Rust 側 MirBuilder / LoopForm v2 / Conservative PHI を使って `NYASH_VM_VERIFY_MIR=1` を通し、Rust 側 SSA/PHI の健全性を確認する。
|
||||||
- `.hako` 側 LoopSSA を単独で呼び出す(`LoopSSA.stabilize_merges(json)`)最小ハーネスを用意。
|
- `.hako` 側 LoopSSA を単独で呼び出す(`LoopSSA.stabilize_merges(json)`)最小ハーネスを用意し、BreakFinderBox / PhiInjectorBox の前後で JSON を比較。
|
||||||
3. どの時点で ValueId(50) が未定義になるかを特定し、
|
3. どの時点で `BreakFinderBox._find_loops/2` の receiver が「定義のない pinned ValueId(例: 39/46/50)」になるかを特定し、
|
||||||
それが LoopSSA の改変前後で変わっているかを確認する。
|
LoopSSA v2 の変更前後で挙動が悪化していないかを確認する。
|
||||||
|
|
||||||
### K‑B: BreakFinderBox の LoopScope 精度の向上(保守的に)
|
### K‑B: BreakFinderBox の LoopScope 精度の向上(保守的に)
|
||||||
|
|
||||||
@ -81,10 +84,23 @@ Status: planning(.hako 側 LoopSSA v2 本体実装/Rust 側は既存 SSA/PHI
|
|||||||
- 必要なら、Test3 から LoopSSA を一時オフ(`HAKO_LOOPSSA_EXIT_PHI=0`)にした場合のログも取っておき、
|
- 必要なら、Test3 から LoopSSA を一時オフ(`HAKO_LOOPSSA_EXIT_PHI=0`)にした場合のログも取っておき、
|
||||||
「LoopSSA が原因の部分」と「それ以外」を明確に切り分ける。
|
「LoopSSA が原因の部分」と「それ以外」を明確に切り分ける。
|
||||||
|
|
||||||
|
### K‑E: デバッグ用ハーネス・プリセットの整備
|
||||||
|
|
||||||
|
- 目的:
|
||||||
|
- LoopSSA/BreakFinderBox/PhiInjectorBox 周辺をデバッグしやすくするための「共通の足場」を用意し、
|
||||||
|
25.1k 以降の作業で毎回同じ ENV/コマンドを手で組み立てなくて済むようにする。
|
||||||
|
- 実装メモ:
|
||||||
|
- `tools/stageb_loopssa_debug.sh`:
|
||||||
|
- Stage‑B 最小ハーネス `tools/test_stageb_min.sh` を、LoopSSA v2 デバッグ向けの ENV プリセット
|
||||||
|
(`HAKO_LOOPSSA_EXIT_PHI=1`, `HAKO_COMPILER_BUILDER_TRACE=1`, `NYASH_VM_TRACE=1`,
|
||||||
|
`NYASH_LOCAL_SSA_TRACE=1`, `NYASH_BUILDER_TRACE_RECV=1` など)付きで実行する小さなラッパ。
|
||||||
|
- `lang/src/compiler/tests/loopssa_breakfinder_slot.hako` + `tools/test_loopssa_breakfinder_slot.sh`:
|
||||||
|
- Program(JSON v0) を直接文字列として持つ LoopSSA ハーネス(現在は最小緑 JSON、将来は Stage‑B Test2 から抽出した失敗 JSON を貼り付ける「スロット」として運用)。
|
||||||
|
- `HAKO_LOOPSSA_EXIT_PHI=1` で LoopSSA v2 / BreakFinderBox / PhiInjectorBox の経路だけを通し、ValueId(..) 問題を Stage‑B 抜きで再現できるようにする。
|
||||||
|
|
||||||
## このフェーズで「しない」こと
|
## このフェーズで「しない」こと
|
||||||
|
|
||||||
- PhiInjectorBox の `_collect_phi_vars` / `_get_var_value` を **完全刷新すること**:
|
- PhiInjectorBox の `_collect_phi_vars` / `_get_var_value` を **完全刷新すること**:
|
||||||
- これは 25.1k の次、25.1l 以降の「本格 v2 実装」のタスクとして分ける。
|
- これは 25.1k の次、25.1l 以降の「本格 v2 実装」のタスクとして分ける。
|
||||||
- Rust 側 LoopForm v2 / Conservative PHI の設計を変えること:
|
- Rust 側 LoopForm v2 / Conservative PHI の設計を変えること:
|
||||||
- Rust 側はあくまで SSOT であり、.hako 側はそれに追従する形で徐々に近づける。
|
- Rust 側はあくまで SSOT であり、.hako 側はそれに追従する形で徐々に近づける。
|
||||||
|
|
||||||
|
|||||||
114
docs/development/roadmap/phases/phase-25.1l/README.md
Normal file
114
docs/development/roadmap/phases/phase-25.1l/README.md
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
# Phase 25.1l — Region/GC 観測レイヤー(LoopForm v2 × RefSlotKind)
|
||||||
|
|
||||||
|
Status: in progress(Rust 側観測レイヤーの最小実装まで完了/挙動変更なし)
|
||||||
|
|
||||||
|
## ゴール
|
||||||
|
|
||||||
|
- 既に導入済みの **LoopForm v2 / ControlForm** を「Region Box(寿命管理の箱)」として扱い、
|
||||||
|
Rust MIR 側に **スコープと参照種別(RefKind)の観測レイヤー**を追加するフェーズだよ。
|
||||||
|
- このフェーズではあくまで:
|
||||||
|
- `RefSlotKind` / `Region` / `SlotMetadata` といった **型と観測器(Observer)** を定義し、
|
||||||
|
- `NYASH_REGION_TRACE=1` のときだけ Region 単位の live スロットと RefKind をログ出力する。
|
||||||
|
- **挙動(実行結果・SSA・PHI)は一切変えない**ことが前提。GC の retain/release 挿入や `.hako` 側のロジック変更は後続フェーズ(25.1m 以降)の仕事にする。
|
||||||
|
|
||||||
|
## 背景と位置づけ
|
||||||
|
|
||||||
|
- これまでの 25.1d〜25.1k で:
|
||||||
|
- LoopForm v2 / ControlForm / Conservative PHI Box によって、If/Loop の SSA/PHI は Rust 側で安定。
|
||||||
|
- Stage‑B/LoopSSA 由来の SSA 問題の多くは `.hako` 側(特に `StageBBodyExtractorBox.build_body_src/2`)の
|
||||||
|
「巨大なループ+if 内での一時値の扱い」に起因することが分かってきた。
|
||||||
|
- いっぽう GC 観点では:
|
||||||
|
- 変数スロットごとに「StrongRoot / WeakRoot / Borrowed / NonRef」を区別し、
|
||||||
|
- **Region(= LoopForm/IfForm)境界を GC スコープ境界として扱う**設計が箱理論的にきれいにハマる。
|
||||||
|
- 25.1l はそのための **観測フェーズ** であり:
|
||||||
|
- LoopForm v2 = Region Box という概念を Rust 型として明示し、
|
||||||
|
- Stage‑B など複雑な関数で「どの Region でどのスロットが生きているか」をログで確認できるようにする。
|
||||||
|
|
||||||
|
## スコープ(25.1l でやること)
|
||||||
|
|
||||||
|
### L‑A: RefSlotKind / SlotMetadata / Region 型の導入(実装済み)
|
||||||
|
|
||||||
|
- 新規モジュール: `src/mir/region/mod.rs`
|
||||||
|
- 型設計:
|
||||||
|
- `RefSlotKind`:
|
||||||
|
- `StrongRoot` — GC root 候補となる強参照スロット。
|
||||||
|
- `WeakRoot` — 弱参照(GC root としては数えない)。
|
||||||
|
- `Borrowed` — 借用(SSA 寿命のみ管理、GC root ではない)。
|
||||||
|
- `NonRef` — プリミティブなど、GC 対象外の値。
|
||||||
|
- `SlotMetadata`:
|
||||||
|
- `name: String` — 変数スロット名(`i`, `body_src`, `args` など)。
|
||||||
|
- `ref_kind: RefSlotKind` — 上記の種別。
|
||||||
|
- `Region`:
|
||||||
|
- `id: RegionId`(`u32` ラッパ)。
|
||||||
|
- `kind: RegionKind` — `Loop` / `If`(25.1l 時点では Function は未導入)。
|
||||||
|
- `parent: Option<RegionId>` / `children: Vec<RegionId>` — 制御構造の親子関係(将来用。25.1l では未設定)。
|
||||||
|
- `entry_block: BasicBlockId` / `exit_blocks: Vec<BasicBlockId>` — ControlForm から引き継ぐ。
|
||||||
|
- `slots: Vec<SlotMetadata>` — その Region で live とみなすスロット一覧(観測用)。
|
||||||
|
- RefKind 判定は 25.1l では **簡易ヒューリスティック** に留める:
|
||||||
|
- `MirType::Box(_)` / `Array(_)` / `Future(_)` → `StrongRoot`
|
||||||
|
- 明らかなプリミティブ(整数/bool/文字列)→ `NonRef`
|
||||||
|
- Weak 系/借用系の精密な分類は後続フェーズで詰める。
|
||||||
|
|
||||||
|
### L‑B: RegionObserver の実装と ControlForm からの接続(実装済み)
|
||||||
|
|
||||||
|
- 新規モジュール: `src/mir/region/observer.rs`
|
||||||
|
- 現実装の責務:
|
||||||
|
- `observe_control_form(builder: &MirBuilder, form: &ControlForm)` という関数型 API で:
|
||||||
|
- `ControlForm` から `entry`/`exits`/Loop/If の形を読む。
|
||||||
|
- 該当スコープ時点の `builder.variable_map` に載っている「名前付きスロット」を走査して `SlotMetadata` を構築。
|
||||||
|
- `Region` を作成し、`NYASH_REGION_TRACE=1` のときだけ:
|
||||||
|
- `[region/observe] fn=StageBBodyExtractorBox.build_body_src/2 id=RegionId(..) kind=Loop entry=bb.. exits=[..] slots=[..]`
|
||||||
|
という形でログ出力する(メモリには保持しない)。
|
||||||
|
- Hook の置き場所(いずれも読み取り専用):
|
||||||
|
- `src/mir/loop_builder.rs`:
|
||||||
|
- ループ構築完了後、`LoopShape`→`ControlForm::from_loop` 生成直後に `observe_control_form(self.parent_builder, &form)` を呼ぶ。
|
||||||
|
- `src/mir/loop_builder.rs`(if 降下部):
|
||||||
|
- `IfShape`→`ControlForm::from_if` 生成直後に `observe_control_form(self.parent_builder, &form)` を呼ぶ。
|
||||||
|
- `.hako` / GC / SSA ロジックには一切影響しない。
|
||||||
|
- dev フィルタ:
|
||||||
|
- 現時点では Stage‑B 周辺の観測に絞るため、`func_name.contains("StageB")` の関数のみログ対象にしている(ログ爆発防止)。
|
||||||
|
|
||||||
|
### L‑C: 関数スコープ Slot 管理箱(SlotRegistry)との関係(設計メモ)
|
||||||
|
|
||||||
|
- 将来像:
|
||||||
|
- 各関数ごとに「SlotRegistry(仮称)」箱を 1 つだけ持ち、
|
||||||
|
- `slot_id -> { name, MirType, RefSlotKind }`
|
||||||
|
- `name -> slot_id`
|
||||||
|
を管理する SSOT として扱う。
|
||||||
|
- RegionBox(本 README での Region)は、この SlotRegistry 上で
|
||||||
|
「どの SlotId がこの Region で live か」を指すだけにする。
|
||||||
|
- 25.1l の暫定実装:
|
||||||
|
- まだ明示的な SlotRegistry 型は導入せず、
|
||||||
|
- `MirBuilder.variable_map.keys()` を「その地点で live なスロット一覧」とみなす。
|
||||||
|
- `value_types` から MirType を拾って RefSlotKind を判定する。
|
||||||
|
- これは SlotRegistry 導入までのリーズナブルな暫定策として位置付け、
|
||||||
|
将来は `variable_map`→SlotRegistry/SlotId ベースに移行する。
|
||||||
|
|
||||||
|
### L‑D: Region メタデータの足場(将来の JSON 拡張のための入口だけ)
|
||||||
|
|
||||||
|
- 25.1l では **まだ MIR JSON への出力は行わない**が、将来のために:
|
||||||
|
- `Region::to_json()` 相当のメソッドを `#[cfg(feature = "region-meta")]` 等のガード付きで用意しておく。
|
||||||
|
- `MirCompiler` 側に「RegionObserver を渡しておけば、あとでメタデータを JSON に差し込める」拡張ポイントだけ作っておく。
|
||||||
|
- GC 統合フェーズ(25.1m 以降)では、このメタデータを:
|
||||||
|
- `Program(JSON v0) → MIR(JSON)` 変換結果の横に `"regions":[...]` として添付する形を想定している。
|
||||||
|
|
||||||
|
## このフェーズで「やらない」こと
|
||||||
|
|
||||||
|
- **GC の retain/release 挿入**:
|
||||||
|
- Region 情報を使って `retain(slot)` / `release(slot)` 命令を実際に MIR に埋め込むのは、25.1m 以降のタスクとし、このフェーズでは一切行わない。
|
||||||
|
- **LoopForm v2 / Conservative PHI / ControlForm の設計変更**:
|
||||||
|
- 25.1l はあくまで「読み取り専用の観測レイヤー」を追加するだけで、既存の SSA/PHI 実装には手を入れない。
|
||||||
|
- **.hako 側の GC 実装や Stage‑B 本体の大規模リファクタ**:
|
||||||
|
- Stage‑B/LoopSSA の箱分解(`StageBBodyExtractorBox` のサブ箱化)や、`.hako` 側 GC API の導入は、Region 観測結果を見ながら次フェーズで設計する。
|
||||||
|
- **関数スコープ SlotRegistry の本実装**:
|
||||||
|
- 現段階では `variable_map` ベースの暫定観測に留め、SlotId ベースの SSOT 化は次のフェーズ(Region 木 + FunctionRegion 導入時)に回す。
|
||||||
|
|
||||||
|
## 受け入れ条件(25.1l)
|
||||||
|
|
||||||
|
- `NYASH_REGION_TRACE=0`(既定)のとき:
|
||||||
|
- すべての既存テスト(LoopForm v2 / Stage‑1 resolver / Stage‑B Rust テスト)が挙動一切変化せず緑のまま。
|
||||||
|
- `NYASH_REGION_TRACE=1` のとき:
|
||||||
|
- 代表関数(特に `StageBBodyExtractorBox.build_body_src/2`)に対して Region/Slot のログが出力される。
|
||||||
|
- ログは「Region id / kind(If/Loop) / entry/exit blocks / slots + RefKind」を含み、
|
||||||
|
今後の GC/寿命設計の議論に耐えうる解像度になっている。
|
||||||
|
- 変更差分は Rust 側に限定され、`.hako` 側コードや Stage‑B 本体には影響を与えない。*** End Patch***"/>
|
||||||
@ -19,6 +19,13 @@ using selfhost.shared.mir.control_form as ControlFormBox
|
|||||||
local trace = trace_flag
|
local trace = trace_flag
|
||||||
if trace == 1 {
|
if trace == 1 {
|
||||||
print("[break-finder] start")
|
print("[break-finder] start")
|
||||||
|
// Program(JSON v0) の先頭だけ観測用に出力する
|
||||||
|
local preview = "" + json_str
|
||||||
|
local max_len = 200
|
||||||
|
if preview.length() > max_len {
|
||||||
|
preview = preview.substring(0, max_len)
|
||||||
|
}
|
||||||
|
print("[break-finder/json] " + preview)
|
||||||
}
|
}
|
||||||
|
|
||||||
local breaks = new ArrayBox()
|
local breaks = new ArrayBox()
|
||||||
@ -80,6 +87,16 @@ using selfhost.shared.mir.control_form as ControlFormBox
|
|||||||
local loops = new ArrayBox()
|
local loops = new ArrayBox()
|
||||||
local s = "" + json_str
|
local s = "" + json_str
|
||||||
|
|
||||||
|
// trace=1 のときは Program(JSON v0) の先頭だけ観測用に出力する
|
||||||
|
if trace == 1 {
|
||||||
|
local preview = s
|
||||||
|
local max_len = 200
|
||||||
|
if preview.length() > max_len {
|
||||||
|
preview = preview.substring(0, max_len)
|
||||||
|
}
|
||||||
|
print("[break-finder/json] " + preview)
|
||||||
|
}
|
||||||
|
|
||||||
// Simple pattern: find "loop_header":NNN, "loop_exit":MMM
|
// Simple pattern: find "loop_header":NNN, "loop_exit":MMM
|
||||||
// This is a simplified version - just finds explicit loop markers
|
// This is a simplified version - just finds explicit loop markers
|
||||||
local i = 0
|
local i = 0
|
||||||
|
|||||||
@ -9,8 +9,10 @@
|
|||||||
// - 出力: exit PHI 相当の命令が入った Program(JSON v0)(文字列)。
|
// - 出力: exit PHI 相当の命令が入った Program(JSON v0)(文字列)。
|
||||||
// - 解析: BreakFinderBox.find_breaks(json, trace_flag) が JSON を読み取り専用で解析。
|
// - 解析: BreakFinderBox.find_breaks(json, trace_flag) が JSON を読み取り専用で解析。
|
||||||
// - 変換: PhiInjectorBox.inject_exit_phis(json, breaks, trace_flag) が exit block をテキストベースで書き換える。
|
// - 変換: PhiInjectorBox.inject_exit_phis(json, breaks, trace_flag) が exit block をテキストベースで書き換える。
|
||||||
// - 環境変数/トレース: HAKO_LOOPSSA_EXIT_PHI / HAKO_COMPILER_BUILDER_TRACE を LoopSSA 側で解釈し、
|
// - 環境変数/トレース:
|
||||||
// 下流には 0/1 の trace_flag だけを渡す(箱ごとに ENV を直読しない)。
|
// - HAKO_LOOPSSA_TRACE=1 : LoopSSA/BreakFinder/PhiInjector 専用のトレースON
|
||||||
|
// - HAKO_COMPILER_BUILDER_TRACE=1 : (後方互換)未設定時のフォールバックとして扱う
|
||||||
|
// LoopSSA 側で trace_flag を 0/1 に正規化し、下流には数値だけを渡す(箱ごとに ENV を直読しない)。
|
||||||
|
|
||||||
using lang.compiler.builder.ssa.exit_phi.break_finder as BreakFinderBox
|
using lang.compiler.builder.ssa.exit_phi.break_finder as BreakFinderBox
|
||||||
using lang.compiler.builder.ssa.exit_phi.phi_injector as PhiInjectorBox
|
using lang.compiler.builder.ssa.exit_phi.phi_injector as PhiInjectorBox
|
||||||
@ -20,7 +22,11 @@ using lang.compiler.builder.ssa.exit_phi.phi_injector as PhiInjectorBox
|
|||||||
// Phase 2-5 implementation: detect breaks and inject exit PHIs
|
// Phase 2-5 implementation: detect breaks and inject exit PHIs
|
||||||
stabilize_merges(stage1_json) {
|
stabilize_merges(stage1_json) {
|
||||||
// Resolve trace flag once at the entry point(0/1 に正規化)
|
// Resolve trace flag once at the entry point(0/1 に正規化)
|
||||||
local trace_env = env.get("HAKO_COMPILER_BUILDER_TRACE")
|
// 優先: HAKO_LOOPSSA_TRACE / 後方互換: HAKO_COMPILER_BUILDER_TRACE
|
||||||
|
local trace_env = env.get("HAKO_LOOPSSA_TRACE")
|
||||||
|
if trace_env == null {
|
||||||
|
trace_env = env.get("HAKO_COMPILER_BUILDER_TRACE")
|
||||||
|
}
|
||||||
local trace_flag = 0
|
local trace_flag = 0
|
||||||
if trace_env != null && ("" + trace_env) == "1" { trace_flag = 1 }
|
if trace_env != null && ("" + trace_env) == "1" { trace_flag = 1 }
|
||||||
|
|
||||||
|
|||||||
37
lang/src/compiler/tests/breakfinder_direct_min.hako
Normal file
37
lang/src/compiler/tests/breakfinder_direct_min.hako
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
// breakfinder_direct_min.hako — BreakFinderBox 直接呼び出し用の極小ハーネス
|
||||||
|
//
|
||||||
|
// 目的:
|
||||||
|
// - Stage‑B/LoopSSA を経由せずに、BreakFinderBox.find_breaks/2 自体の
|
||||||
|
// MIR 形状と SSA 性を確認するための最小サンプルだよ。
|
||||||
|
// - Program(JSON v0) 文字列を 1 本用意して、BreakFinderBox.find_breaks(json, trace)
|
||||||
|
// を直接呼び出し、VM/Verifier で undefined receiver などのエラーを観測する。
|
||||||
|
//
|
||||||
|
// JSON 形状(loopssa_breakfinder_min.hako と同じ単純な緑ケース):
|
||||||
|
// {
|
||||||
|
// "kind":"Program",
|
||||||
|
// "functions":[
|
||||||
|
// {
|
||||||
|
// "name":"main",
|
||||||
|
// "blocks":[
|
||||||
|
// {"id":0,"loop_header":0,"loop_exit":2},
|
||||||
|
// {"id":1},
|
||||||
|
// {"id":2}
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
|
||||||
|
using lang.compiler.builder.ssa.exit_phi.break_finder as BreakFinderBox
|
||||||
|
|
||||||
|
static box Main {
|
||||||
|
method main(args) {
|
||||||
|
// 単純な Program(JSON v0) を直接埋め込む
|
||||||
|
local json = "{\"kind\":\"Program\",\"functions\":[{\"name\":\"main\",\"blocks\":[{\"id\":0,\"loop_header\":0,\"loop_exit\":2},{\"id\":1},{\"id\":2}]}]}"
|
||||||
|
|
||||||
|
// trace_flag=1 で BreakFinderBox 内部の挙動を観測する
|
||||||
|
local breaks = BreakFinderBox.find_breaks(json, 1)
|
||||||
|
print(breaks)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
29
lang/src/compiler/tests/loopssa_breakfinder_slot.hako
Normal file
29
lang/src/compiler/tests/loopssa_breakfinder_slot.hako
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// loopssa_breakfinder_slot.hako — LoopSSA/BreakFinder 用「最小失敗 JSON」スロット
|
||||||
|
//
|
||||||
|
// 目的:
|
||||||
|
// - Stage‑B 最小サンプル(stageb_min_sample.hako など)から切り出した
|
||||||
|
// 「問題のある Program(JSON v0)」を貼り付けて、LoopSSA v2 /
|
||||||
|
// BreakFinderBox / PhiInjectorBox の挙動を単体で再現するためのスロットだよ。
|
||||||
|
// - 初期状態では loopssa_breakfinder_min.hako と同じ、単純な緑の JSON を使っておく。
|
||||||
|
// バグが再現できる JSON v0 が手に入ったら、下の `json` 文字列を書き換えて使う。
|
||||||
|
//
|
||||||
|
// 注意:
|
||||||
|
// - ここは「.hako パーサ → LoopSSA」経路のデバッグ専用。Stage‑B 全体ではなく、
|
||||||
|
// LoopSSA/BreakFinderBox 周辺の挙動だけを確認したいときに使ってね。
|
||||||
|
//
|
||||||
|
|
||||||
|
using lang.compiler.builder.ssa.loopssa as LoopSSA
|
||||||
|
|
||||||
|
static box Main {
|
||||||
|
method main(args) {
|
||||||
|
// TODO: Stage‑B Test2 から切り出した Program(JSON v0) で上書きして使う。
|
||||||
|
// 現在は loopssa_breakfinder_min と同じ単純な緑の JSON だよ。
|
||||||
|
local json = "{\"kind\":\"Program\",\"functions\":[{\"name\":\"main\",\"blocks\":[{\"id\":0,\"loop_header\":0,\"loop_exit\":2},{\"id\":1},{\"id\":2}]}]}"
|
||||||
|
|
||||||
|
// LoopSSA v2 を直接呼び出す(Exit PHI パスのみ)
|
||||||
|
local out = LoopSSA.stabilize_merges(json)
|
||||||
|
print(out)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
100
lang/src/compiler/tests/stageb_mini_driver.hako
Normal file
100
lang/src/compiler/tests/stageb_mini_driver.hako
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
// stageb_mini_driver.hako — Stage‑B 近似の極小ドライバ
|
||||||
|
//
|
||||||
|
// 目的:
|
||||||
|
// - StageBDriverBox.main/1 の経路を「最小限」に真似したテスト用ドライバだよ。
|
||||||
|
// - ParserBox + CompilerBuilder.apply_all(LoopSSA/BreakFinder/PhiInjector を含む)
|
||||||
|
// だけを通し、Stage‑B 本体の箱(StageBArgsBox / StageBBodyExtractorBox / FuncScannerBox)
|
||||||
|
// を介さずに SSA/ValueId 問題を再現できるか確認する。
|
||||||
|
//
|
||||||
|
// 振る舞い:
|
||||||
|
// - lang/src/compiler/tests/stageb_min_sample.hako と同等のソースコード文字列を
|
||||||
|
// 内部に埋め込み、ParserBox.parse_program2 → CompilerBuilder.apply_all を実行する。
|
||||||
|
// - 出力は Stage‑1 Program(JSON v0)(LoopSSA 適用後)。VM/Verifier/ログで
|
||||||
|
// BreakFinderBox/LoopSSA 周辺の挙動を観測する。
|
||||||
|
|
||||||
|
using lang.compiler.builder.mod as CompilerBuilder
|
||||||
|
|
||||||
|
static box StageBMiniDriverBox {
|
||||||
|
method run(args) {
|
||||||
|
// stageb_min_sample.hako 相当のソースをそのまま埋め込む
|
||||||
|
local src = ""
|
||||||
|
|
||||||
|
// static box TestArgs { ... }
|
||||||
|
src = src + "static box TestArgs {\n"
|
||||||
|
src = src + " method process(args) {\n"
|
||||||
|
src = src + " if args != null {\n"
|
||||||
|
src = src + " local n = args.length()\n"
|
||||||
|
src = src + " local i = 0\n"
|
||||||
|
src = src + " loop(i < n) {\n"
|
||||||
|
src = src + " local item = args.get(i)\n"
|
||||||
|
src = src + " print(item)\n"
|
||||||
|
src = src + " i = i + 1\n"
|
||||||
|
src = src + " }\n"
|
||||||
|
src = src + " }\n"
|
||||||
|
src = src + " return 0\n"
|
||||||
|
src = src + " }\n"
|
||||||
|
src = src + "}\n\n"
|
||||||
|
|
||||||
|
// static box TestSimple { ... }
|
||||||
|
src = src + "static box TestSimple {\n"
|
||||||
|
src = src + " method run() {\n"
|
||||||
|
src = src + " local x = new ArrayBox()\n"
|
||||||
|
src = src + " local len = x.length()\n"
|
||||||
|
src = src + " print(len)\n"
|
||||||
|
src = src + " return 0\n"
|
||||||
|
src = src + " }\n"
|
||||||
|
src = src + "}\n\n"
|
||||||
|
|
||||||
|
// static box TestNested { ... }
|
||||||
|
src = src + "static box TestNested {\n"
|
||||||
|
src = src + " method complex(data) {\n"
|
||||||
|
src = src + " if data != null {\n"
|
||||||
|
src = src + " local count = data.length()\n"
|
||||||
|
src = src + " if count > 0 {\n"
|
||||||
|
src = src + " local j = 0\n"
|
||||||
|
src = src + " loop(j < count) {\n"
|
||||||
|
src = src + " local val = data.get(j)\n"
|
||||||
|
src = src + " if val != null {\n"
|
||||||
|
src = src + " local s = \"\" + val\n"
|
||||||
|
src = src + " print(s)\n"
|
||||||
|
src = src + " }\n"
|
||||||
|
src = src + " j = j + 1\n"
|
||||||
|
src = src + " }\n"
|
||||||
|
src = src + " }\n"
|
||||||
|
src = src + " }\n"
|
||||||
|
src = src + " return 0\n"
|
||||||
|
src = src + " }\n"
|
||||||
|
src = src + "}\n\n"
|
||||||
|
|
||||||
|
// static box Main { ... }
|
||||||
|
src = src + "static box Main {\n"
|
||||||
|
src = src + " method main(args) {\n"
|
||||||
|
src = src + " local t1 = TestArgs.process(args)\n"
|
||||||
|
src = src + " local t2 = TestSimple.run()\n"
|
||||||
|
src = src + " local test_data = new ArrayBox()\n"
|
||||||
|
src = src + " local t3 = TestNested.complex(test_data)\n"
|
||||||
|
src = src + " return 0\n"
|
||||||
|
src = src + " }\n"
|
||||||
|
src = src + "}\n"
|
||||||
|
|
||||||
|
// 1) Stage‑1 Program(JSON v0) を生成
|
||||||
|
local p = new ParserBox()
|
||||||
|
p.stage3_enable(1)
|
||||||
|
local ast_json = p.parse_program2(src)
|
||||||
|
|
||||||
|
// 2) CompilerBuilder パイプライン適用(Rewrite/LocalSSA/LoopSSA 等)
|
||||||
|
ast_json = CompilerBuilder.apply_all(ast_json)
|
||||||
|
|
||||||
|
// 3) 出力をそのまま表示(Stage‑B 本体の emit に相当)
|
||||||
|
print(ast_json)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// エントリポイント: Main.main → StageBMiniDriverBox.run
|
||||||
|
static box Main {
|
||||||
|
method main(args) {
|
||||||
|
return StageBMiniDriverBox.run(args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -185,6 +185,9 @@ pub struct MirBuilder {
|
|||||||
pub(super) local_ssa_map: HashMap<(BasicBlockId, ValueId, u8), ValueId>,
|
pub(super) local_ssa_map: HashMap<(BasicBlockId, ValueId, u8), ValueId>,
|
||||||
/// BlockSchedule cache: deduplicate materialize copies per (bb, src)
|
/// BlockSchedule cache: deduplicate materialize copies per (bb, src)
|
||||||
pub(super) schedule_mat_map: HashMap<(BasicBlockId, ValueId), ValueId>,
|
pub(super) schedule_mat_map: HashMap<(BasicBlockId, ValueId), ValueId>,
|
||||||
|
/// Mapping from ValueId to its pin slot name (e.g., "__pin$3$@recv")
|
||||||
|
/// Used by LocalSSA to redirect old pinned values to the latest slot value.
|
||||||
|
pub(super) pin_slot_names: HashMap<ValueId, String>,
|
||||||
|
|
||||||
/// Guard flag to prevent re-entering emit_unified_call from BoxCall fallback.
|
/// Guard flag to prevent re-entering emit_unified_call from BoxCall fallback.
|
||||||
/// Used when RouterPolicyBox in emit_unified_call has already decided to
|
/// Used when RouterPolicyBox in emit_unified_call has already decided to
|
||||||
@ -256,6 +259,7 @@ impl MirBuilder {
|
|||||||
|
|
||||||
local_ssa_map: HashMap::new(),
|
local_ssa_map: HashMap::new(),
|
||||||
schedule_mat_map: HashMap::new(),
|
schedule_mat_map: HashMap::new(),
|
||||||
|
pin_slot_names: HashMap::new(),
|
||||||
|
|
||||||
in_unified_boxcall_fallback: false,
|
in_unified_boxcall_fallback: false,
|
||||||
recursion_depth: 0,
|
recursion_depth: 0,
|
||||||
|
|||||||
@ -106,46 +106,15 @@ impl CallMaterializerBox {
|
|||||||
/// - SSA不変条件の保持(receiverが常に定義済みであることを保証)
|
/// - SSA不変条件の保持(receiverが常に定義済みであることを保証)
|
||||||
/// - デバッグトレース出力(NYASH_BUILDER_TRACE_RECV=1)
|
/// - デバッグトレース出力(NYASH_BUILDER_TRACE_RECV=1)
|
||||||
pub fn materialize_receiver_in_callee(
|
pub fn materialize_receiver_in_callee(
|
||||||
builder: &mut MirBuilder,
|
_builder: &mut MirBuilder,
|
||||||
callee: Callee,
|
callee: Callee,
|
||||||
) -> Result<Callee, String> {
|
) -> Result<Callee, String> {
|
||||||
match callee {
|
// Phase 25.1j+:
|
||||||
Callee::Method { box_name, method, receiver: Some(r), certainty, box_kind } => {
|
// Receiver 実体化(pinning + LocalSSA)は ReceiverMaterializationBox
|
||||||
if std::env::var("NYASH_BUILDER_TRACE_RECV").ok().as_deref() == Some("1") {
|
// (crate::mir::builder::receiver)側に一本化したよ。
|
||||||
let current_fn = builder
|
// ここでは Callee 構造は変更せず、そのまま返す。
|
||||||
.current_function
|
//
|
||||||
.as_ref()
|
// NYASH_BUILDER_TRACE_RECV は新しい receiver.rs 側で扱う。
|
||||||
.map(|f| f.signature.name.clone())
|
Ok(callee)
|
||||||
.unwrap_or_else(|| "<none>".to_string());
|
|
||||||
let bb = builder.current_block;
|
|
||||||
let names: Vec<String> = builder
|
|
||||||
.variable_map
|
|
||||||
.iter()
|
|
||||||
.filter(|(_, &vid)| vid == r)
|
|
||||||
.map(|(k, _)| k.clone())
|
|
||||||
.collect();
|
|
||||||
// CRITICAL DEBUG: Show type information sources
|
|
||||||
let origin = builder.value_origin_newbox.get(&r).cloned();
|
|
||||||
let vtype = builder.value_types.get(&r).cloned();
|
|
||||||
eprintln!(
|
|
||||||
"[builder/recv-trace] fn={} bb={:?} method={}.{} recv=%{} aliases={:?}",
|
|
||||||
current_fn,
|
|
||||||
bb,
|
|
||||||
box_name.clone(),
|
|
||||||
method,
|
|
||||||
r.0,
|
|
||||||
names
|
|
||||||
);
|
|
||||||
eprintln!(
|
|
||||||
"[builder/recv-trace] value_origin_newbox: {:?}, value_types: {:?}",
|
|
||||||
origin, vtype
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// Prefer pinning to a slot so start_new_block can propagate it across entries.
|
|
||||||
let r_pinned = builder.pin_to_slot(r, "@recv").unwrap_or(r);
|
|
||||||
Ok(Callee::Method { box_name, method, receiver: Some(r_pinned), certainty, box_kind })
|
|
||||||
}
|
|
||||||
other => Ok(other),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -151,6 +151,8 @@ impl<'a> CalleeResolverBox<'a> {
|
|||||||
"ParserLiteralBox" | "ParserTokenBox" |
|
"ParserLiteralBox" | "ParserTokenBox" |
|
||||||
// Scanner/builder boxes
|
// Scanner/builder boxes
|
||||||
"FuncScannerBox" | "MirBuilderBox" |
|
"FuncScannerBox" | "MirBuilderBox" |
|
||||||
|
// LoopSSA / Exit PHI analyzers (Stage-1/Stage-B)
|
||||||
|
"BreakFinderBox" | "PhiInjectorBox" | "LoopSSA" |
|
||||||
// Other compiler-internal boxes
|
// Other compiler-internal boxes
|
||||||
"JsonFragBox"
|
"JsonFragBox"
|
||||||
=> CalleeBoxKind::StaticCompiler,
|
=> CalleeBoxKind::StaticCompiler,
|
||||||
|
|||||||
@ -54,20 +54,31 @@ pub fn ensure(builder: &mut MirBuilder, v: ValueId, kind: LocalKind) -> ValueId
|
|||||||
// CRITICAL FIX: If `v` is from a pinned slot, check if there's a PHI value for that slot
|
// 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
|
// 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).
|
// emitting a Copy from the old value (which might not be defined in this block).
|
||||||
|
// Try to detect pinned slots for this value and redirect to the latest slot value.
|
||||||
|
// 1) First, look for "__pin$" entries in variable_map that still point to v.
|
||||||
|
// 2) If not found, consult builder.pin_slot_names to recover the slot name
|
||||||
|
// and then look up the current ValueId for that slot.
|
||||||
|
let mut slot_name_opt: Option<String> = None;
|
||||||
|
|
||||||
let names_for_v: Vec<String> = builder.variable_map.iter()
|
let names_for_v: Vec<String> = builder.variable_map.iter()
|
||||||
.filter(|(k, &vid)| vid == v && k.starts_with("__pin$"))
|
.filter(|(k, &vid)| vid == v && k.starts_with("__pin$"))
|
||||||
.map(|(k, _)| k.clone())
|
.map(|(k, _)| k.clone())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
if let Some(first_pin_name) = names_for_v.first() {
|
if let Some(first_pin_name) = names_for_v.first() {
|
||||||
// This value is from a pinned slot. Check if the slot has been updated
|
slot_name_opt = Some(first_pin_name.clone());
|
||||||
// (e.g., by a PHI) in the current block.
|
} else if let Some(name) = builder.pin_slot_names.get(&v) {
|
||||||
if let Some(¤t_val) = builder.variable_map.get(first_pin_name) {
|
slot_name_opt = Some(name.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(slot_name) = slot_name_opt {
|
||||||
|
if let Some(¤t_val) = builder.variable_map.get(&slot_name) {
|
||||||
if current_val != v {
|
if current_val != v {
|
||||||
// The slot has been updated (likely by a PHI). Use the updated value.
|
// The slot has been updated (likely by a PHI or header rewrite).
|
||||||
|
// Use the updated value instead of the stale pinned ValueId.
|
||||||
if std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") {
|
if std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") {
|
||||||
eprintln!("[local-ssa] phi-redirect bb={:?} kind={:?} slot={} %{} -> %{}",
|
eprintln!("[local-ssa] phi-redirect bb={:?} kind={:?} slot={} %{} -> %{}",
|
||||||
bb, kind, first_pin_name, v.0, current_val.0);
|
bb, kind, slot_name, v.0, current_val.0);
|
||||||
}
|
}
|
||||||
builder.local_ssa_map.insert(key, current_val);
|
builder.local_ssa_map.insert(key, current_val);
|
||||||
return current_val;
|
return current_val;
|
||||||
|
|||||||
@ -329,6 +329,10 @@ impl super::MirBuilder {
|
|||||||
}
|
}
|
||||||
// Propagate lightweight metadata so downstream resolution/type inference remains stable
|
// Propagate lightweight metadata so downstream resolution/type inference remains stable
|
||||||
crate::mir::builder::metadata::propagate::propagate(self, v, dst);
|
crate::mir::builder::metadata::propagate::propagate(self, v, dst);
|
||||||
|
// Remember pin slot name for both the original and the pinned value.
|
||||||
|
// LocalSSA uses this to redirect old pinned values to the latest slot value.
|
||||||
|
self.pin_slot_names.insert(v, slot_name.clone());
|
||||||
|
self.pin_slot_names.insert(dst, slot_name.clone());
|
||||||
self.variable_map.insert(slot_name, dst);
|
self.variable_map.insert(slot_name, dst);
|
||||||
Ok(dst)
|
Ok(dst)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -349,6 +349,11 @@ impl<'a> LoopBuilder<'a> {
|
|||||||
};
|
};
|
||||||
let form = ControlForm::from_loop(loop_shape.clone());
|
let form = ControlForm::from_loop(loop_shape.clone());
|
||||||
|
|
||||||
|
// Region/GC 観測レイヤ(Phase 25.1l):
|
||||||
|
// NYASH_REGION_TRACE=1 のときだけ、Stage‑B 周辺ループの
|
||||||
|
// Region 情報(entry/exit/slots)をログに出すよ。
|
||||||
|
crate::mir::region::observer::observe_control_form(self.parent_builder, &form);
|
||||||
|
|
||||||
// Build exit PHIs for break statements using ControlForm wrapper
|
// Build exit PHIs for break statements using ControlForm wrapper
|
||||||
let exit_snaps = self.exit_snapshots.clone();
|
let exit_snaps = self.exit_snapshots.clone();
|
||||||
crate::mir::phi_core::loopform_builder::build_exit_phis_for_control(
|
crate::mir::phi_core::loopform_builder::build_exit_phis_for_control(
|
||||||
@ -778,7 +783,6 @@ impl<'a> LoopBuilder<'a> {
|
|||||||
}
|
}
|
||||||
// Reset to pre-if snapshot, then delegate to shared helper
|
// Reset to pre-if snapshot, then delegate to shared helper
|
||||||
self.parent_builder.variable_map = pre_if_var_map.clone();
|
self.parent_builder.variable_map = pre_if_var_map.clone();
|
||||||
let mut ops = Ops(self);
|
|
||||||
|
|
||||||
// Phase 25.1h: ControlForm統合版に切り替え
|
// Phase 25.1h: ControlForm統合版に切り替え
|
||||||
let if_shape = IfShape {
|
let if_shape = IfShape {
|
||||||
@ -789,6 +793,13 @@ impl<'a> LoopBuilder<'a> {
|
|||||||
};
|
};
|
||||||
let form = ControlForm::from_if(if_shape.clone());
|
let form = ControlForm::from_if(if_shape.clone());
|
||||||
|
|
||||||
|
// Region/GC 観測レイヤ(Phase 25.1l):
|
||||||
|
// NYASH_REGION_TRACE=1 のときだけ、Stage‑B 周辺 If 構造の
|
||||||
|
// Region 情報(entry/exit/slots)をログに出すよ。
|
||||||
|
crate::mir::region::observer::observe_control_form(self.parent_builder, &form);
|
||||||
|
|
||||||
|
let mut ops = Ops(self);
|
||||||
|
|
||||||
crate::mir::phi_core::if_phi::merge_modified_with_control(
|
crate::mir::phi_core::if_phi::merge_modified_with_control(
|
||||||
&mut ops,
|
&mut ops,
|
||||||
&form,
|
&form,
|
||||||
|
|||||||
@ -23,6 +23,7 @@ pub mod optimizer;
|
|||||||
pub mod utils; // Phase 15 control flow utilities for root treatment
|
pub mod utils; // Phase 15 control flow utilities for root treatment
|
||||||
// pub mod lowerers; // reserved: Stage-3 loop lowering (while/for-range)
|
// pub mod lowerers; // reserved: Stage-3 loop lowering (while/for-range)
|
||||||
pub mod phi_core; // Phase 1 scaffold: unified PHI entry (re-exports only)
|
pub mod phi_core; // Phase 1 scaffold: unified PHI entry (re-exports only)
|
||||||
|
pub mod region; // Phase 25.1l: Region/GC観測レイヤ(LoopForm v2 × RefKind)
|
||||||
pub mod optimizer_passes; // optimizer passes (normalize/diagnostics)
|
pub mod optimizer_passes; // optimizer passes (normalize/diagnostics)
|
||||||
pub mod optimizer_stats; // extracted stats struct
|
pub mod optimizer_stats; // extracted stats struct
|
||||||
pub mod passes;
|
pub mod passes;
|
||||||
|
|||||||
73
src/mir/region/mod.rs
Normal file
73
src/mir/region/mod.rs
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
/*!
|
||||||
|
* MIR Region/GC Observation Layer (Phase 25.1l)
|
||||||
|
*
|
||||||
|
* 目的:
|
||||||
|
* - LoopForm v2 / ControlForm を「Region Box(寿命管理の箱)」として眺めるための
|
||||||
|
* 型定義と観測用ヘルパーを提供するよ。
|
||||||
|
* - このフェーズでは GC の retain/release などは一切挿入せず、あくまで
|
||||||
|
* 「どの制御構造でどのスロットが生きているか」をログで観測するだけだよ。
|
||||||
|
*
|
||||||
|
* 注意:
|
||||||
|
* - 既存の SSA/PHI 挙動には影響を与えない(NYASH_REGION_TRACE=1 のときだけ動く)。
|
||||||
|
* - .hako 側やランタイム側の GC 実装には触れない。
|
||||||
|
*/
|
||||||
|
|
||||||
|
use crate::mir::{BasicBlockId, MirType};
|
||||||
|
|
||||||
|
/// GC/寿命管理の観点から見たスロット種別だよ。
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum RefSlotKind {
|
||||||
|
/// GC root 候補となる強参照スロット(Box 系など)
|
||||||
|
StrongRoot,
|
||||||
|
/// 弱参照(将来の WeakRefBox 等)。Phase 25.1l ではまだ未使用。
|
||||||
|
WeakRoot,
|
||||||
|
/// 借用スロット(寿命は SSA で管理、GC root ではない想定)
|
||||||
|
Borrowed,
|
||||||
|
/// 非参照(プリミティブ値など、GC 対象外)
|
||||||
|
NonRef,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 1 つの変数スロットに関するメタデータだよ。
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct SlotMetadata {
|
||||||
|
pub name: String,
|
||||||
|
pub ref_kind: RefSlotKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Region ID の薄い newtype だよ(デバッグ用途)。
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub struct RegionId(pub u32);
|
||||||
|
|
||||||
|
/// Region の種別(Loop / If など)だよ。
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum RegionKind {
|
||||||
|
Loop,
|
||||||
|
If,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ControlForm から派生した Region 情報だよ(観測専用)。
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Region {
|
||||||
|
pub id: RegionId,
|
||||||
|
pub kind: RegionKind,
|
||||||
|
pub parent: Option<RegionId>,
|
||||||
|
pub entry_block: BasicBlockId,
|
||||||
|
pub exit_blocks: Vec<BasicBlockId>,
|
||||||
|
pub slots: Vec<SlotMetadata>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Region {
|
||||||
|
/// MirType から簡易的に RefSlotKind を推定するよ(観測専用)。
|
||||||
|
pub fn classify_ref_kind(ty: &MirType) -> RefSlotKind {
|
||||||
|
match ty {
|
||||||
|
MirType::Box(_) | MirType::Array(_) | MirType::Future(_) => RefSlotKind::StrongRoot,
|
||||||
|
MirType::Integer | MirType::Float | MirType::Bool | MirType::String => {
|
||||||
|
RefSlotKind::NonRef
|
||||||
|
}
|
||||||
|
MirType::Void | MirType::Unknown => RefSlotKind::NonRef,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod observer;
|
||||||
|
|
||||||
107
src/mir/region/observer.rs
Normal file
107
src/mir/region/observer.rs
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
/*!
|
||||||
|
* RegionObserver – ControlForm から Region/Slot を観測する薄いレイヤだよ。
|
||||||
|
*
|
||||||
|
* - NYASH_REGION_TRACE=1 のときだけ動作し、Loop/If ごとの Region 情報を eprintln! するよ。
|
||||||
|
* - 既存の SSA/PHI 挙動には一切影響しない(読み取り専用)。
|
||||||
|
*/
|
||||||
|
|
||||||
|
use crate::mir::builder::MirBuilder;
|
||||||
|
use crate::mir::control_form::{ControlForm, ControlKind};
|
||||||
|
use crate::mir::region::{RefSlotKind, Region, RegionId, RegionKind, SlotMetadata};
|
||||||
|
use crate::mir::ValueId;
|
||||||
|
use std::sync::atomic::{AtomicU32, Ordering};
|
||||||
|
|
||||||
|
static NEXT_REGION_ID: AtomicU32 = AtomicU32::new(0);
|
||||||
|
|
||||||
|
fn is_region_trace_on() -> bool {
|
||||||
|
std::env::var("NYASH_REGION_TRACE")
|
||||||
|
.ok()
|
||||||
|
.as_deref()
|
||||||
|
== Some("1")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ControlForm と MirBuilder から Region 情報を観測・ログ出力するよ。
|
||||||
|
///
|
||||||
|
/// - 25.1l では Stage‑B 周辺のデバッグが主目的なので、
|
||||||
|
/// まずは `StageBBodyExtractorBox.*` などに絞って使う想定だよ。
|
||||||
|
pub fn observe_control_form(builder: &MirBuilder, form: &ControlForm) {
|
||||||
|
if !is_region_trace_on() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// いまのところ compilation_context が Some のケースは観測対象外にしておく。
|
||||||
|
// (BoxCompilationContext 内の variable_map は別経路で管理されているため)
|
||||||
|
if builder.compilation_context.is_some() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let func_name = builder
|
||||||
|
.current_function
|
||||||
|
.as_ref()
|
||||||
|
.map(|f| f.signature.name.as_str())
|
||||||
|
.unwrap_or("<unknown>");
|
||||||
|
|
||||||
|
// dev 用フィルタ: Stage‑B 周辺を優先的に見る(必要なら将来拡張)。
|
||||||
|
// ここでは "StageB" を含む関数だけログする。
|
||||||
|
if !func_name.contains("StageB") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let id = RegionId(NEXT_REGION_ID.fetch_add(1, Ordering::Relaxed));
|
||||||
|
|
||||||
|
let kind = match form.kind {
|
||||||
|
ControlKind::Loop(_) => RegionKind::Loop,
|
||||||
|
ControlKind::If(_) => RegionKind::If,
|
||||||
|
};
|
||||||
|
|
||||||
|
let entry_block = form.entry;
|
||||||
|
let exit_blocks = form.exits.clone();
|
||||||
|
|
||||||
|
// 変数スロットは variable_map と value_types から best-effort で推定するよ。
|
||||||
|
let mut slots: Vec<SlotMetadata> = Vec::new();
|
||||||
|
|
||||||
|
for (name, &vid) in builder.variable_map.iter() {
|
||||||
|
let ref_kind = classify_slot(builder, vid, name.as_str());
|
||||||
|
slots.push(SlotMetadata {
|
||||||
|
name: name.clone(),
|
||||||
|
ref_kind,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let region = Region {
|
||||||
|
id,
|
||||||
|
kind,
|
||||||
|
parent: None,
|
||||||
|
entry_block,
|
||||||
|
exit_blocks,
|
||||||
|
slots,
|
||||||
|
};
|
||||||
|
|
||||||
|
eprintln!(
|
||||||
|
"[region/observe] fn={} id={:?} kind={:?} entry={:?} exits={:?} slots={:?}",
|
||||||
|
func_name, region.id, region.kind, region.entry_block, region.exit_blocks, region.slots
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn classify_slot(builder: &MirBuilder, v: ValueId, name: &str) -> RefSlotKind {
|
||||||
|
if let Some(ty) = builder.value_types.get(&v) {
|
||||||
|
return Region::classify_ref_kind(ty);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 型情報が無い場合は名前ヒューリスティックで軽く分類する(観測専用)。
|
||||||
|
if matches!(
|
||||||
|
name,
|
||||||
|
"args"
|
||||||
|
| "src"
|
||||||
|
| "body_src"
|
||||||
|
| "bundles"
|
||||||
|
| "bundle_names"
|
||||||
|
| "bundle_srcs"
|
||||||
|
| "require_mods"
|
||||||
|
) {
|
||||||
|
RefSlotKind::StrongRoot
|
||||||
|
} else {
|
||||||
|
RefSlotKind::NonRef
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -34,6 +34,87 @@ impl MirVerifier {
|
|||||||
|
|
||||||
for (_name, function) in &module.functions {
|
for (_name, function) in &module.functions {
|
||||||
if let Err(mut func_errors) = self.verify_function(function) {
|
if let Err(mut func_errors) = self.verify_function(function) {
|
||||||
|
// Dev-only trace: BreakFinderBox / LoopSSA 周辺のSSAバグを詳細に観測する。
|
||||||
|
//
|
||||||
|
// NYASH_BREAKFINDER_SSA_TRACE=1 のときだけ有効になり、
|
||||||
|
// compiler_stageb.hako など大規模モジュール内での
|
||||||
|
// BreakFinderBox.* に対する UndefinedValue を詳細に出力する。
|
||||||
|
//
|
||||||
|
// 併せて、同じトグルで「任意の関数」に対する UndefinedValue も
|
||||||
|
// 簡易ログとして出力し、どの関数で支配関係が崩れているかを
|
||||||
|
// 追いやすくしている(箱理論の観測レイヤー強化)。
|
||||||
|
if std::env::var("NYASH_BREAKFINDER_SSA_TRACE")
|
||||||
|
.ok()
|
||||||
|
.as_deref() == Some("1")
|
||||||
|
{
|
||||||
|
// 1) BreakFinderBox / LoopSSA 向けの詳細ログ
|
||||||
|
if function.signature.name.starts_with("BreakFinderBox.")
|
||||||
|
|| function.signature.name.starts_with("LoopSSA.")
|
||||||
|
{
|
||||||
|
for e in &func_errors {
|
||||||
|
if let VerificationError::UndefinedValue {
|
||||||
|
value,
|
||||||
|
block,
|
||||||
|
instruction_index,
|
||||||
|
} = e
|
||||||
|
{
|
||||||
|
if let Some(bb) = function.blocks.get(block) {
|
||||||
|
let inst = bb.instructions.get(*instruction_index);
|
||||||
|
eprintln!(
|
||||||
|
"[breakfinder/ssa] UndefinedValue {:?} in fn {} at bb={:?}, inst={} => {:?}",
|
||||||
|
value,
|
||||||
|
function.signature.name,
|
||||||
|
block,
|
||||||
|
instruction_index,
|
||||||
|
inst,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
eprintln!(
|
||||||
|
"[breakfinder/ssa] UndefinedValue {:?} in fn {} at bb={:?}, inst={}",
|
||||||
|
value,
|
||||||
|
function.signature.name,
|
||||||
|
block,
|
||||||
|
instruction_index
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) 任意の関数向けの簡易 UndefinedValue ログ
|
||||||
|
for e in &func_errors {
|
||||||
|
match e {
|
||||||
|
VerificationError::UndefinedValue {
|
||||||
|
value,
|
||||||
|
block,
|
||||||
|
instruction_index,
|
||||||
|
} => {
|
||||||
|
eprintln!(
|
||||||
|
"[mir-ssa-debug] UndefinedValue {:?} in fn {} at bb={:?}, inst={}",
|
||||||
|
value,
|
||||||
|
function.signature.name,
|
||||||
|
block,
|
||||||
|
instruction_index
|
||||||
|
);
|
||||||
|
}
|
||||||
|
VerificationError::DominatorViolation {
|
||||||
|
value,
|
||||||
|
use_block,
|
||||||
|
def_block,
|
||||||
|
} => {
|
||||||
|
eprintln!(
|
||||||
|
"[mir-ssa-debug] DominatorViolation {:?} in fn {}: use_block={:?}, def_block={:?}",
|
||||||
|
value,
|
||||||
|
function.signature.name,
|
||||||
|
use_block,
|
||||||
|
def_block
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Add function context to errors
|
// Add function context to errors
|
||||||
for _error in &mut func_errors {
|
for _error in &mut func_errors {
|
||||||
// Could add function name to error context here
|
// Could add function name to error context here
|
||||||
|
|||||||
174
src/tests/mir_breakfinder_ssa.rs
Normal file
174
src/tests/mir_breakfinder_ssa.rs
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
/*!
|
||||||
|
* BreakFinder / LoopSSA SSA smoke tests
|
||||||
|
*
|
||||||
|
* 目的:
|
||||||
|
* - .hako 側 LoopSSA / BreakFinderBox を Rust MIR 経由で軽くカバーする足場だよ。
|
||||||
|
* - Stage‑B 本体とは独立に、「最小の Program(JSON v0) で LoopSSA/BreakFinder を通す」
|
||||||
|
* パターンが MIR 的に健全(Undefined Value が出ない)であることを確認する。
|
||||||
|
*
|
||||||
|
* 注意:
|
||||||
|
* - Stage‑B Test2 で見えている BreakFinderBox._find_loops/2 の receiver 未定義バグは、
|
||||||
|
* 現時点ではまだこのテストでは再現していない(より複雑な Stage‑B パイプライン固有の条件)。
|
||||||
|
* - ここでは「最小緑ケース」の SSA を固定し、将来 Stage‑B 由来の JSON v0 を切り出せたときに
|
||||||
|
* 追加のテストを足せるようにするのが狙いだよ。
|
||||||
|
*/
|
||||||
|
|
||||||
|
use crate::ast::ASTNode;
|
||||||
|
use crate::mir::{MirCompiler, MirPrinter, MirVerifier};
|
||||||
|
use crate::parser::NyashParser;
|
||||||
|
|
||||||
|
fn ensure_stage3_env() {
|
||||||
|
std::env::set_var("NYASH_PARSER_STAGE3", "1");
|
||||||
|
std::env::set_var("NYASH_PARSER_ALLOW_SEMICOLON", "1");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// LoopSSA.stabilize_merges を最小 JSON で通す SSA スモーク
|
||||||
|
///
|
||||||
|
/// lang/src/compiler/tests/loopssa_breakfinder_min.hako 相当:
|
||||||
|
///
|
||||||
|
/// using lang.compiler.builder.ssa.loopssa as LoopSSA
|
||||||
|
///
|
||||||
|
/// static box Main {
|
||||||
|
/// method main(args) {
|
||||||
|
/// local json = "{\"kind\":\"Program\",\"functions\":[{\"name\":\"main\",\"blocks\":[{\"id\":0,\"loop_header\":0,\"loop_exit\":2},{\"id\":1},{\"id\":2}]}]}"
|
||||||
|
/// local out = LoopSSA.stabilize_merges(json)
|
||||||
|
/// print(out)
|
||||||
|
/// return 0
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
#[test]
|
||||||
|
fn mir_loopssa_breakfinder_min_verifies() {
|
||||||
|
ensure_stage3_env();
|
||||||
|
|
||||||
|
let src = r#"
|
||||||
|
using lang.compiler.builder.ssa.loopssa as LoopSSA
|
||||||
|
|
||||||
|
static box Main {
|
||||||
|
method main(args) {
|
||||||
|
local json = "{\"kind\":\"Program\",\"functions\":[{\"name\":\"main\",\"blocks\":[{\"id\":0,\"loop_header\":0,\"loop_exit\":2},{\"id\":1},{\"id\":2}]}]}"
|
||||||
|
|
||||||
|
local out = LoopSSA.stabilize_merges(json)
|
||||||
|
print(out)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let ast: ASTNode = NyashParser::parse_from_string(src).expect("parse ok");
|
||||||
|
let mut mc = MirCompiler::with_options(false);
|
||||||
|
let cr = mc.compile(ast).expect("compile ok");
|
||||||
|
|
||||||
|
let mut verifier = MirVerifier::new();
|
||||||
|
if let Err(errors) = verifier.verify_module(&cr.module) {
|
||||||
|
// 既知のベースライン: bb0 での %0 未定義(エントリ初期値)が混ざることがある。
|
||||||
|
// ここではそれらを除外し、「それ以外のエラーが無いこと」を確認対象とする。
|
||||||
|
let non_baseline: Vec<_> = errors
|
||||||
|
.iter()
|
||||||
|
.filter(|e| {
|
||||||
|
let msg = e.to_string();
|
||||||
|
!(msg.contains("Undefined value %0 used in block bb0"))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if non_baseline.is_empty() {
|
||||||
|
// ベースライン (%0/bb0) だけなら許容(別タスクで扱う)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if std::env::var("NYASH_MIR_TEST_DUMP").ok().as_deref() == Some("1") {
|
||||||
|
let dump = MirPrinter::new().print_module(&cr.module);
|
||||||
|
eprintln!("----- MIR DUMP (LoopSSA.min) -----\n{}", dump);
|
||||||
|
}
|
||||||
|
for e in &non_baseline {
|
||||||
|
eprintln!("[rust-mir-verify] {}", e);
|
||||||
|
}
|
||||||
|
panic!("MIR verification failed for LoopSSA.breakfinder minimal JSON case (non-baseline errors present)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// BreakFinderBox.find_breaks/2 を直接呼び出す極小スモーク
|
||||||
|
///
|
||||||
|
/// lang/src/compiler/tests/breakfinder_direct_min.hako 相当:
|
||||||
|
///
|
||||||
|
/// using lang.compiler.builder.ssa.exit_phi.break_finder as BreakFinderBox
|
||||||
|
///
|
||||||
|
/// static box Main {
|
||||||
|
/// method main(args) {
|
||||||
|
/// local json = "{\"kind\":\"Program\",\"functions\":[{\"name\":\"main\",\"blocks\":[{\"id\":0,\"loop_header\":0,\"loop_exit\":2},{\"id\":1},{\"id\":2}]}]}"
|
||||||
|
/// local breaks = BreakFinderBox.find_breaks(json, 1)
|
||||||
|
/// print(breaks)
|
||||||
|
/// return 0
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// 注意:
|
||||||
|
/// - 現状、このケースでは SSA/receiver 周りの既知バグは再現していない。
|
||||||
|
/// - ここでは「BreakFinderBox 自体の MIR 生成がクラッシュせず通る」ことだけを確認する。
|
||||||
|
#[test]
|
||||||
|
fn mir_breakfinder_direct_min_compiles() {
|
||||||
|
ensure_stage3_env();
|
||||||
|
|
||||||
|
let src = r#"
|
||||||
|
using lang.compiler.builder.ssa.exit_phi.break_finder as BreakFinderBox
|
||||||
|
|
||||||
|
static box Main {
|
||||||
|
method main(args) {
|
||||||
|
local json = "{\"kind\":\"Program\",\"functions\":[{\"name\":\"main\",\"blocks\":[{\"id\":0,\"loop_header\":0,\"loop_exit\":2},{\"id\":1},{\"id\":2}]}]}"
|
||||||
|
|
||||||
|
local breaks = BreakFinderBox.find_breaks(json, 1)
|
||||||
|
print(breaks)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let ast: ASTNode = NyashParser::parse_from_string(src).expect("parse ok");
|
||||||
|
let mut mc = MirCompiler::with_options(false);
|
||||||
|
// コンパイルが通ることのみを保証する(SSA まではチェックしない)
|
||||||
|
mc.compile(ast).expect("compile ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Debug-only: compiler_stageb.hako 全体を MIR 化して BreakFinder/LoopSSA SSA を観測する
|
||||||
|
///
|
||||||
|
/// - 現時点では既知の SSA 問題があるため #[ignore] 付き。
|
||||||
|
/// - NYASH_BREAKFINDER_SSA_TRACE=1 で実行すると、UndefinedValue が出たときに
|
||||||
|
/// verification.rs 側の dev フックが詳細な bb/inst/命令をダンプしてくれる。
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn mir_compiler_stageb_breakfinder_ssa_debug() {
|
||||||
|
ensure_stage3_env();
|
||||||
|
std::env::set_var("NYASH_BREAKFINDER_SSA_TRACE", "1");
|
||||||
|
|
||||||
|
// compiler_stageb.hako 全文をそのまま読み込んで MIR 化する
|
||||||
|
let src = include_str!("../../lang/src/compiler/entry/compiler_stageb.hako");
|
||||||
|
let ast: ASTNode = NyashParser::parse_from_string(src).expect("parse compiler_stageb.hako ok");
|
||||||
|
|
||||||
|
let mut mc = MirCompiler::with_options(false);
|
||||||
|
let cr = mc.compile(ast).expect("compile compiler_stageb.hako ok");
|
||||||
|
|
||||||
|
// 必要に応じて StageBBodyExtractorBox.build_body_src/2 だけの MIR をダンプする。
|
||||||
|
if std::env::var("NYASH_MIR_TEST_DUMP")
|
||||||
|
.ok()
|
||||||
|
.as_deref() == Some("1")
|
||||||
|
{
|
||||||
|
if let Some(func) = cr
|
||||||
|
.module
|
||||||
|
.functions
|
||||||
|
.get("StageBBodyExtractorBox.build_body_src/2")
|
||||||
|
{
|
||||||
|
let dump = MirPrinter::new().print_function(func);
|
||||||
|
eprintln!(
|
||||||
|
"----- MIR DUMP: StageBBodyExtractorBox.build_body_src/2 -----\n{}",
|
||||||
|
dump
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut verifier = MirVerifier::new();
|
||||||
|
if let Err(errors) = verifier.verify_module(&cr.module) {
|
||||||
|
for e in &errors {
|
||||||
|
eprintln!("[rust-mir-verify] {}", e);
|
||||||
|
}
|
||||||
|
panic!("MIR verification failed for compiler_stageb.hako (debug)");
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -11,6 +11,7 @@ pub mod mir_stageb_loop_break_continue;
|
|||||||
pub mod mir_stage1_using_resolver_verify;
|
pub mod mir_stage1_using_resolver_verify;
|
||||||
pub mod mir_locals_ssa;
|
pub mod mir_locals_ssa;
|
||||||
pub mod mir_loopform_exit_phi;
|
pub mod mir_loopform_exit_phi;
|
||||||
|
pub mod mir_breakfinder_ssa;
|
||||||
pub mod mir_vm_poc;
|
pub mod mir_vm_poc;
|
||||||
pub mod nyash_abi_basic;
|
pub mod nyash_abi_basic;
|
||||||
pub mod plugin_hygiene;
|
pub mod plugin_hygiene;
|
||||||
|
|||||||
40
tools/stageb_loopssa_debug.sh
Normal file
40
tools/stageb_loopssa_debug.sh
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# stageb_loopssa_debug.sh — Stage‑B LoopSSA 専用トレースプリセット
|
||||||
|
#
|
||||||
|
# 目的:
|
||||||
|
# - Stage‑B 最小ハーネス(tools/test_stageb_min.sh)を、
|
||||||
|
# LoopSSA v2 / BreakFinderBox / PhiInjectorBox / LocalSSA / receiver 実体化の
|
||||||
|
# デバッグに適した ENV セットで実行するよ。
|
||||||
|
# - 「いつも同じ環境変数を手で並べる」手間を減らし、ログの再現性を高める。
|
||||||
|
#
|
||||||
|
# 実行内容:
|
||||||
|
# - HAKO_LOOPSSA_EXIT_PHI=1 : .hako LoopSSA Exit PHI を有効化
|
||||||
|
# - HAKO_LOOPSSA_TRACE=1 : LoopSSA/BreakFinder/PhiInjector 専用トレース ON
|
||||||
|
# - HAKO_COMPILER_BUILDER_TRACE=0 : Stage‑B 本体の builder トレースは既定で OFF(必要なら手動で1にする)
|
||||||
|
# - NYASH_VM_TRACE=1 : Rust VM 実行トレース(Call/Branch 等)
|
||||||
|
# - NYASH_LOCAL_SSA_TRACE=1 : LocalSSA (recv/arg/cond) の Copy 発行トレース
|
||||||
|
# - NYASH_BUILDER_TRACE_RECV=1 : receiver 実体化(pin_to_slot/LocalSSA)のトレース
|
||||||
|
# ※ 既に値が設定されている場合はそちらを優先するよ。
|
||||||
|
#
|
||||||
|
set -e
|
||||||
|
|
||||||
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
|
||||||
|
export HAKO_LOOPSSA_EXIT_PHI="${HAKO_LOOPSSA_EXIT_PHI:-1}"
|
||||||
|
export HAKO_LOOPSSA_TRACE="${HAKO_LOOPSSA_TRACE:-1}"
|
||||||
|
# Builder 側はデフォルト OFF。既に値があればそれを尊重する。
|
||||||
|
export HAKO_COMPILER_BUILDER_TRACE="${HAKO_COMPILER_BUILDER_TRACE:-0}"
|
||||||
|
export NYASH_VM_TRACE="${NYASH_VM_TRACE:-1}"
|
||||||
|
export NYASH_LOCAL_SSA_TRACE="${NYASH_LOCAL_SSA_TRACE:-1}"
|
||||||
|
export NYASH_BUILDER_TRACE_RECV="${NYASH_BUILDER_TRACE_RECV:-1}"
|
||||||
|
|
||||||
|
echo "=== Stage-B LoopSSA Debug Profile ==="
|
||||||
|
echo "HAKO_LOOPSSA_EXIT_PHI=${HAKO_LOOPSSA_EXIT_PHI}"
|
||||||
|
echo "HAKO_LOOPSSA_TRACE=${HAKO_LOOPSSA_TRACE}"
|
||||||
|
echo "HAKO_COMPILER_BUILDER_TRACE=${HAKO_COMPILER_BUILDER_TRACE}"
|
||||||
|
echo "NYASH_VM_TRACE=${NYASH_VM_TRACE}"
|
||||||
|
echo "NYASH_LOCAL_SSA_TRACE=${NYASH_LOCAL_SSA_TRACE}"
|
||||||
|
echo "NYASH_BUILDER_TRACE_RECV=${NYASH_BUILDER_TRACE_RECV}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
exec "${ROOT}/tools/test_stageb_min.sh"
|
||||||
28
tools/test_loopssa_breakfinder_slot.sh
Normal file
28
tools/test_loopssa_breakfinder_slot.sh
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# test_loopssa_breakfinder_slot.sh — LoopSSA/BreakFinder 「最小失敗 JSON」スロットテスト
|
||||||
|
#
|
||||||
|
# 目的:
|
||||||
|
# - lang/src/compiler/tests/loopssa_breakfinder_slot.hako を直接 VM で実行し、
|
||||||
|
# Program(JSON v0) スロットに貼り付けた JSON に対する LoopSSA.stabilize_merges
|
||||||
|
# の挙動を観測するよ。
|
||||||
|
# - Stage‑B 最小サンプルから抽出した「失敗する JSON v0」をここに貼っておけば、
|
||||||
|
# Stage‑B 全体を回さずに LoopSSA/BreakFinderBox 周辺だけを再現できる。
|
||||||
|
#
|
||||||
|
set -e
|
||||||
|
|
||||||
|
NYASH_BIN="${NYASH_BIN:-./target/release/hakorune}"
|
||||||
|
TEST_FILE="lang/src/compiler/tests/loopssa_breakfinder_slot.hako"
|
||||||
|
|
||||||
|
echo "=== LoopSSA BreakFinder Slot JSON Test ==="
|
||||||
|
echo "Test file: $TEST_FILE"
|
||||||
|
echo "Binary: $NYASH_BIN"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# LoopSSA v2 の EXIT PHI を有効化して実行(BreakFinder/PhiInjector 経由)
|
||||||
|
HAKO_LOOPSSA_EXIT_PHI=1 \
|
||||||
|
NYASH_DISABLE_PLUGINS=1 NYASH_PARSER_STAGE3=1 \
|
||||||
|
"$NYASH_BIN" --backend vm "$TEST_FILE" 2>&1
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Test complete ==="
|
||||||
|
|
||||||
Reference in New Issue
Block a user