🎉 Conservative PHI Box理論による完全SSA構築 **Phase 7-B: Conservative PHI実装** - 片方branchのみ定義変数に対応(emit_void使用) - 全変数にPHI生成(Conservative Box理論) - Stage-1 resolver全テスト緑化(3/3 PASS) **Phase 25.1f: ControlForm観測レイヤー** - LoopShape/IfShape/ControlForm構造定義 - Loop/If統一インターフェース実装 - debug_dump/debug_validate機能追加 - NYASH_CONTROL_FORM_TRACE環境変数対応 **主な変更**: - src/mir/builder/phi.rs: Conservative PHI実装 - src/mir/control_form.rs: ControlForm構造(NEW) - src/mir/loop_builder.rs: LoopForm v2デフォルト化 **テスト結果**: ✅ mir_stage1_using_resolver_min_fragment_verifies ✅ mir_stage1_using_resolver_full_collect_entries_verifies ✅ mir_parserbox_parse_program2_harness_parses_minimal_source 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: ChatGPT <chatgpt@openai.com>
Phase 25.1f — ControlForm(Loop/If 共通ビュー)& Stage‑B ハーネス準備
Status: planning(設計+観測レイヤ追加/挙動は変えない)
ゴール
- LoopForm v2(ループ)と If まわり(
phi_core::if_phi)に対して、制御構造の共通ビューControlFormを用意する。 - すでに導入済みの Conservative PHI Box(If/Loop 用 SSA/PHI ロジック)を、
- ループ専用/If 専用でバラバラに持つのではなく、
ControlFormを単一の SSOT(Single Source of Truth)として参照する方向に寄せる。
- Stage‑B 最小ハーネスや Stage‑1 Resolver など、「複雑な if + loop」を含む経路で、
- ループと条件分岐の形を 構造として観測・検証できる足場 を整える(いきなり本番導線は切り替えない)。
※ このフェーズでは Rust の挙動は変えず、「制御構造の箱(LoopShape / IfShape)+ ControlForm の定義」と、 デバッグ/テスト用の観測導線(トレース)までに留める。
背景(25.1d / 25.1e までの位置づけ)
- 25.1d:
- Rust MIR ビルダーの SSA/PHI バグ(ValueId 二重定義/non‑dominating use/undefined Value)を、
小さな Rust テスト(mir_*_verifies)で炙り出して修正済み。 - CalleeBoxKind / BoxCompilationContext によって Stage‑B / Stage‑1 の静的 Box とランタイム Box の混線も構造的に解消。
- Rust MIR ビルダーの SSA/PHI バグ(ValueId 二重定義/non‑dominating use/undefined Value)を、
- 25.1e:
- ループの PHI 生成の SSOT を LoopForm v2 +
phi_coreに寄せ、- Exit PHI、break/continue、pinned/carrier/invariant 変数を整理。
- Conservative PHI Box(If/Loop 両方で「安全側に倒す PHI 生成」を行い、不要な PHI は将来の削除で最適化する方針)を実装。
- Stage‑1 UsingResolver 系テストと Program v0 PHI スモーク、Stage‑B 風ループテストはすべて緑。
- ループの PHI 生成の SSOT を LoopForm v2 +
ここまでで「LoopForm v2 + Conservative PHI」の根本バグはだいたい取り切れたが、
- PHI ロジックが If 用と Loop 用で散在している。
- Stage‑B ハーネス側で「どの loop/if がどう組み合わさっているか」を横断的に眺める手段がまだ弱い。
→ 25.1f では、Loop / If を一段上から包む ControlForm レイヤを定義し、Stage‑B ハーネスに進む前準備として構造を固める。
方針 — ControlForm / Shape の設計
型と場所
- 新規モジュール案:
src/mir/control_form.rs
- 基本構造:
LoopShape:preheader: BasicBlockIdheader: BasicBlockIdbody: BasicBlockId(代表 body ブロック)latch: BasicBlockIdexit: BasicBlockIdcontinue_targets: Vec<BasicBlockId>break_targets: Vec<BasicBlockId>
IfShape:cond_block: BasicBlockIdthen_block: BasicBlockIdelse_block: Option<BasicBlockId>merge_block: BasicBlockId
ControlKind:Loop(LoopShape)/If(IfShape)(将来Switch/Matchも追加可能な余地だけ確保)
ControlForm:entry: BasicBlockId(構造全体の入口)exits: Vec<BasicBlockId>(構造を抜けた先のブロック群)kind: ControlKind
生成パス(from_loop / from_if)
LoopForm→ControlForm:- 既存の LoopForm v2(
LoopFormBuilderが返す構造)からLoopShapeを構築し、 ControlForm::from_loop(loop_form: &LoopForm)で共通ビューに変換する。entry = preheader,exits = [exit]というルールで統一。
- 既存の LoopForm v2(
- If 用:
- 既存の
phi_core::if_phi/ builder 側の if lowering から、必要な情報をまとめた薄いIfForm(もしくはIfShape直構築)を定義。 ControlForm::from_if(if_form: &IfForm)で共通ビューに変換。entry = cond_block,exits = [merge_block]を基本とし、将来else ifや switch なども扱える形で設計。
- 既存の
Invariant / Debug 用の Trait
- 25.1f では「挙動を変えない」ため、ControlForm 自体は 観測・検証専用 として設計する:
debug_dump(&self):- Loop / If の各ブロック ID を一括でログ出力し、Stage‑1 / Stage‑B テストから CFG の形を視覚的に確認できるようにする。
CfgLikeトレイト(任意):fn has_edge(&self, from: BasicBlockId, to: BasicBlockId) -> bool;fn predecessors_len(&self, block: BasicBlockId) -> usize;LoopShape::debug_validate<C: CfgLike>(&self, cfg: &C)などを#[cfg(debug_assertions)]で用意し、 Loop/If の形が想定どおりかを assert できるようにする。
タスク粒度(25.1f TODO)
F‑1: ControlForm / Shape の設計ドキュメント
- 本 README で:
LoopShape/IfShape/ControlKind/ControlFormの責務とフィールドを明文化。- LoopForm v2 との対応関係(preheader/header/latch/exit/continue/break)を図解レベルで通るところまで整理。
- 25.1e の LoopForm 仕様(Carrier/Pinned/Invariant / break/continue / exit PHI)と整合していることを確認。
F‑2: Rust 側のスケルトン実装(導線のみ)
src/mir/control_form.rsを追加し、以下のみ実装:- 型定義(
LoopShape/IfShape/ControlKind/ControlForm)。 ControlForm::from_loop(&LoopForm)/ControlForm::from_if(&IfForm)の雛形。debug_dump()とdebug_validate()(#[cfg(debug_assertions)]前提、もしくはテスト専用)。
- 型定義(
- 既存のビルダー/PHI ロジックは まだ ControlForm を使わない:
- 25.1e で安定した Conservative PHI 実装を壊さないため、Phase 25.1f では「観測オンリー」に留める。
F‑3: Stage‑1 / Stage‑B テストへの観測フック
- 対象テスト:
src/tests/mir_stage1_using_resolver_verify.rs系(Stage‑1 UsingResolver の min/full)。- Stage‑B 風ループ/Program v0 PHI スモーク(
mir_loopform_exit_phi.rs/mir_stageb_loop_break_continue.rs/tools/smokes/v2/...)。
- 方針:
NYASH_CONTROL_FORM_TRACE=1などの dev フラグが立っているときだけ、- LoopForm v2 から
ControlFormを生成し、 debug_dump()で構造をトレースログに出す。
- LoopForm v2 から
- これにより、Stage‑B ハーネスを組む前に「実際にどんな Loop/If 形が発生しているか」を CFG レベルで確認できる。
F‑4: Conservative PHI との接続計画(設計のみ)
- 25.1e で実装した Conservative PHI Box(If/Loop)のロジックを整理し、将来像を設計として固定する:
- 目標:
merge_modified_at_merge_with/ LoopForm v2 Exit PHI 生成などを、ControlFormベースの API で呼べるようにする(例:build_phi_for_control(form: &ControlForm, ...))。
- 25.1f では:
- どの関数をどこまで ControlForm 受けにするか、
- 既存 API との互換性をどう保つか、 を README 上で設計するところまで。
- 実際の実装切り替え(PHI ロジックを ControlForm ベースに差し替える)は、Stage‑B ハーネスと合わせて 別フェーズ(25.1g または 25.2 以降) に送る。
- 目標:
F‑5: Stage‑B ハーネスへの橋渡し準備
- Stage‑B 最小ハーネス(
tools/test_stageb_min.sh/lang/src/compiler/tests/stageb_min_sample.hako)における:- ループパターン(
skip_wsなどの単純ループ、複数 break/continue を含むループ)。 - if + loop のネスト構造。 を洗い出し、LoopForm v2 + IfShape + ControlForm ですべて表現できることを確認する。
- ループパターン(
- 将来のタスク(このフェーズでは設計のみ):
- Stage‑B ハーネスの SSA/PHI 検証を
ControlForm経由で行う Rust テストをどう設計するかをまとめる。 - Stage‑B 側の .hako LoopSSA(
lang/src/compiler/builder/ssa/loopssa.hako)が、LoopForm v2 / ControlForm の設計と齟齬を起こさないように、必要な制約・前提条件を明記する。
- Stage‑B ハーネスの SSA/PHI 検証を
F‑6: .hako 側 ControlFormBox(Layer 2 の箱)定義
- 目的:
- Rust 側
LoopShape/IfShape/ControlFormに対応する Hakorune 実装版の箱 を用意し、
Stage‑B / LoopSSA が Rust と同じモデルでループ/if を扱えるようにする。 - 将来の LoopSSA 実装は、この箱経由で構造を受け取ることを前提に設計する。
- Rust 側
- 現行構文版(Layer 2):
- ファイル案:
lang/src/shared/mir/control_form_box.hako - 定義案(BlockId は現状 i64/IntegerBox で表現):
static box ControlFormBox { kind_name: StringBox // "loop" or "if" entry: IntegerBox // BasicBlockId 相当 exits: ArrayBox // of IntegerBox (BlockId) // Loop fields (kind_name == "loop" の時のみ有効) loop_preheader: IntegerBox loop_header: IntegerBox loop_body: IntegerBox loop_latch: IntegerBox loop_exit: IntegerBox // If fields (kind_name == "if" の時のみ有効) if_cond: IntegerBox if_then: IntegerBox if_else: IntegerBox if_merge: IntegerBox birth(kind) { me.kind_name = kind me.exits = new ArrayBox() } is_loop() { return me.kind_name == "loop" } is_if() { return me.kind_name == "if" } } - 対応表(Rust ↔ .hako):
- Rust
LoopShape.preheader/header/body/latch/exit
↔ Hakoloop_preheader/loop_header/loop_body/loop_latch/loop_exit - Rust
IfShape.cond_block/then_block/else_block/merge_block
↔ Hakoif_cond/if_then/if_else/if_merge - Rust
ControlForm.entry/exits
↔ Hakoentry/exits(exitsは BlockId の配列)
- Rust
- ファイル案:
- 将来構文版(Layer 3 の理想形・variant 導入前提):
- 将来の Nyash variant 構文が入ったら、
ControlKindを enum 的に表現し、のような形に寄せる(現段階では設計メモのみ)。box ControlKind { variant Loop { preheader: BlockIdBox, header: BlockIdBox, ... } variant If { cond_block: BlockIdBox, then_block: BlockIdBox, ... } } static box ControlFormBox { kind: ControlKind entry: BlockIdBox exits: ArrayBox }
- 将来の Nyash variant 構文が入ったら、
- 25.1f でのスコープ:
control_form_box.hakoに上記 Layer 2 版ControlFormBoxを実装する。- ただし Stage‑B / LoopSSA からはまだ使用しない(コンパイルが通る最小限の実装+将来用の箱)。
- Stage‑B LoopSSA 統合(ControlFormBox を実際に使って JSON v0→MIR を整理する)は、次フェーズに回す。
移行ステップ(Option A → B → C のロードマップ)
このフェーズ(25.1f)では、あくまで Option A = 観測レイヤーの完成 までをスコープにするよ。その上で、将来の B/C も含めて合意しておきたい移行順序はこうだよ:
-
Option A: 観測レイヤーを完璧にする(Phase 25.1f)
- Rust:
LoopShape/IfShape/ControlForm実装済み。LoopBuilderから LoopForm v2 /lower_if_in_loopの出口でControlForm::Loop/Ifを生成してdebug_dump済み。NYASH_CONTROL_FORM_TRACEは「未設定=ON, 0/false=OFF」のヘルパーに統一済み。
- .hako:
ControlFormBox(kind_name + loop_* / if_* + entry/exits)の箱だけ定義(まだ未使用)。
- テスト/スモーク:
mir_stage1_using_resolver_*/mir_loopform_exit_phi.rs/mir_stageb_loop_break_continue.rs/tools/test_stageb_min.shを回しつつ、 ControlForm トレースが panic せずに構造ログだけを出すことを確認する。
- Rust:
-
Option B: Conservative PHI ↔ ControlForm の統合(別フェーズ: 25.1g 想定)
- ねらい:
- いま
merge_modified_at_merge_with(If)や LoopForm v2 Exit PHI が直接 BlockId/ValueId を持っている部分を、ControlForm経由で読めるように段階的に寄せていく。
- いま
- 方針:
- まず If から(
ControlKind::If)→ ループ Exit PHI(ControlKind::Loop)の順に小さく進める。 - 各ステップごとに:
- 対応するテスト(Stage‑1 / Program v0 / Stage‑B 風)+
test_stageb_min.shを回し、
SSA/PHI の赤ログが増えていないことを確認しながら移行する。
- 対応するテスト(Stage‑1 / Program v0 / Stage‑B 風)+
- まず If から(
- 注意:
- これは 25.1f のスコープ外。Phase 25.1g(仮)として小さな差分に分割しながら実装する。
- ねらい:
-
Option C: LoopBuilder/If 降下の ControlForm ベース正規化(さらに後ろのフェーズ)
- ねらい:
- 最終的には「LoopBuilder/If 降下がまず ControlForm を組み立てる」スタイルに寄せ、
Conservative PHI / LoopSSA / .hako 側 LoopSSA が同じ ControlForm を揃って見る状態にする。
- 最終的には「LoopBuilder/If 降下がまず ControlForm を組み立てる」スタイルに寄せ、
- 規模:
- 数百行単位のリファクタになるため、Phase 25.2 以降(Selfhost/Stage‑B の安定を見ながら)に分割してやる。
- ガード:
- 各ステップで代表スモーク(Stage‑1 / Stage‑B / Program v0 /
test_stageb_min.sh)を流しながら進める。 NYASH_CONTROL_FORM_TRACEと.hako ControlFormBoxを使って、常に「形」が崩れていないかを観測できるようにしておく。
- 各ステップで代表スモーク(Stage‑1 / Stage‑B / Program v0 /
- ねらい:
このフェーズ(25.1f)は「Option A をやり切って足場を固める」ことに専念し、その上で 25.1g 以降で Option B/C に進む、というロードマップで進めるよ。
このフェーズで「やらないこと」
- if を loop に書き換えるような、意味論レベルの正規化は 行わない:
- If は IfShape、Loop は LoopShape として、それぞれの形を尊重する。
- Stage‑B ハーネス本体の導入・既存パイプラインの切り替え:
test_stageb_min.shの経路変更や.hakoコンパイラ側の LoopSSA 実装変更は、25.1f では触らず、
ControlForm の設計と観測レイヤ整備が完了してから別フェーズで扱う。
- Conservative PHI ロジックの大規模リライト:
- 25.1e で安定させた If/Loop PHI 実装を、いきなり ControlForm ベースに書き換えることはしない。
- まずは「どう書き換えるのが構造的に美しいか」を、このフェーズの README に設計として落とす。