**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: 既存テスト全て緑維持
7.8 KiB
7.8 KiB
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")の関数のみログ対象にしている(ログ爆発防止)。
- 現時点では Stage‑B 周辺の観測に絞るため、
L‑C: 関数スコープ Slot 管理箱(SlotRegistry)との関係(設計メモ)
- 将来像:
- 各関数ごとに「SlotRegistry(仮称)」箱を 1 つだけ持ち、
slot_id -> { name, MirType, RefSlotKind }name -> slot_idを管理する SSOT として扱う。
- RegionBox(本 README での Region)は、この SlotRegistry 上で 「どの SlotId がこの Region で live か」を指すだけにする。
- 各関数ごとに「SlotRegistry(仮称)」箱を 1 つだけ持ち、
- 25.1l の暫定実装:
- まだ明示的な SlotRegistry 型は導入せず、
MirBuilder.variable_map.keys()を「その地点で live なスロット一覧」とみなす。value_typesから MirType を拾って RefSlotKind を判定する。
- これは SlotRegistry 導入までのリーズナブルな暫定策として位置付け、
将来は
variable_map→SlotRegistry/SlotId ベースに移行する。
- まだ明示的な SlotRegistry 型は導入せず、
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 以降のタスクとし、このフェーズでは一切行わない。
- Region 情報を使って
- LoopForm v2 / Conservative PHI / ControlForm の設計変更:
- 25.1l はあくまで「読み取り専用の観測レイヤー」を追加するだけで、既存の SSA/PHI 実装には手を入れない。
- .hako 側の GC 実装や Stage‑B 本体の大規模リファクタ:
- Stage‑B/LoopSSA の箱分解(
StageBBodyExtractorBoxのサブ箱化)や、.hako側 GC API の導入は、Region 観測結果を見ながら次フェーズで設計する。
- Stage‑B/LoopSSA の箱分解(
- 関数スコープ 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***"/>