115 lines
7.8 KiB
Markdown
115 lines
7.8 KiB
Markdown
|
|
# 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***"/>
|