Files
hakorune/docs/development/current/main/design/edgecfg-fragments.md

12 KiB
Raw Blame History

EdgeCFG Flow FragmentsFrag / ExitKind— Structured→CFG lowering SSOT

Status: Draftdesign SSOT candidate
Last updated: 2025-12-21

Related:

  • North starCFG/ABI: docs/development/current/main/design/join-explicit-cfg-construction.md
  • Catch/Cleanup/Async: docs/development/current/main/design/exception-cleanup-async.md

目的(なぜ必要?)

EdgeCFGblock-parameterized CFG / edge-args SSOTが固まると、次に残る “泥沼” はここだけになる:

  • 構造化制御if/loop + catch/cleanup→ CFG の lowering で起きる exit 配線問題
  • 「pattern番号で推測分岐」が増殖しやすい領域長期的には消したい

この文書は「pattern番号の列挙」を設計の中心にしないために、Structured→CFG の lowering を **合成代数fragment composition**として SSOT 化する。

結論(本書の北極星):

  • “分岐の中心” は pattern番号ではなく ExitKindFragfragment に置く
  • 値の合流は EdgeCFG の block params + edge-args で表し、PHI/推測/メタに逃げない
  • pattern は「Extractor形の認識/ Plan最小要件の抽出」までに縮退し、merge/配線層へ逆流させない

“フロー” は 2 層ある(混ぜると崩れる)

  1. CFG層EdgeCFG / plumbing

    • terminator 語彙: Jump/Branch/Return/Invoke
    • edge-args: terminator operand が SSOT
    • out_edges の参照点が SSOT複数 edge 前提)
  2. Structured→CFG lowering 層flow composition

    • if/loop/catch/cleanup/seq を “Frag の合成” として書く
    • 難しさの本体は exit脱出の種類ネスト合流

コア概念(最小の強い箱)

ExitKind脱出の種類を一次概念にする

最低限の ExitKind:

  • Normalfallthrough
  • Break(loop_id) / Continue(loop_id)
  • Return
  • UnwindInvoke.err / catch へ)
  • Cancelasync の drop/cancel 用。今は予約)

EdgeStub未配線の脱出エッジ

“どこへ飛ぶべきか未確定” な edge を表す。最終的に EdgeCFG の terminator edge に落ちる。

例(概念):

  • from: BlockId
  • kind: ExitKind
  • args: EdgeArgs(ターゲット params に対応する値。target が未確定でも “役割” はここで決める)

Fragfragment

Frag = { entry_block, exits: Map<ExitKind, Vec<EdgeStub>> }
  • entry_block: 断片の入口
  • exits: 断片から外へ出る未配線 edge の集合

合成則pattern列挙を写像へ落とす

seq(a, b)

  • a.exits[Normal]b.entry へ接続するedge-args を必要に応じて写像)
  • それ以外の exit は上位へ伝搬する

if(cond, t, e)

  • header に Branch(cond, t.entry, e.entry) を置く
  • t.Normale.Normal は join へ集める(必要なら join block params を作る)
  • Break/Continue/Return/Unwind は上位へ伝搬

loop(body)

  • header / latch / after を組み、Continue を header に戻す
  • Break を after へ出す
  • Return/Unwind は上位へ伝搬

cleanup(body, cleanup_block)finally の後継)

狙い: “脱出 edge 正規化”

  • body の全 exitNormal/Break/Continue/Return/Unwind/Cancelを cleanup 経由へリライトする
  • cleanup 後に “元の exit” を再発射するExitTag + payload を block params で運ぶ)

重要: 例外 edgeInvoke.errも漏れなく cleanup に寄せる。

pattern は最終的に消える?(設計としての答え)

消える(実装の中心概念から降格する)。

  • pattern番号は 回帰テスト名/症状ラベルとしては残して良い
  • 実装の中心は Frag/ExitKind/join(block params) の合成則になる
  • 各 pattern 実装は “Extractor形の認識→ Frag 合成呼び出し” の薄い層へ縮退する

実装の入口SSOT API を先に作る)

目的: “どこを触ればいいか” を 1 箇所に固定し、推測・部分続行・場当たり分岐を減らす。

推奨の入口:

  • EdgeCFG の plumbing API既存: BasicBlock::out_edges()
  • Structured→CFG の入口 API新規: Frag / ExitKind / compose::{seq, if_, loop_ , cleanup}

物理配置(案):

  • src/mir/builder/control_flow/edgecfg/api/(または .../joinir/api/ に併設してもよい)
    • frag.rs / exit_kind.rs / compose.rs / patch.rs

verifyFail-Fast の置き場所)

  • NormalizeBox 直後: terminator 語彙固定・edge-args 長さ一致・cond付きJump禁止など “意味SSOT” を確定
  • merge直前: boundary/ABI/edge-args の矛盾を即死させ “配線SSOT” を確定
  • --verify: PHI predecessor / CFG cache 整合 / edge-args の長さ一致を常設

直近の導入ステップ(最小で始める)

  1. Frag/ExitKind/EdgeStub の型を追加docs+code 入口 SSOT
  2. seq/if/loop の合成だけ実装cleanup/Invoke は後段)
  3. 既存 pattern のうち 1 本だけ Frag 合成に寄せるPattern8 推奨)
  4. 2 本目で再利用が見えたら "pattern番号での枝刈り" を削って合成側へ寄せる

実装入口(コード SSOT

Phase 264 で入口API を作成完了

  • 物理配置: src/mir/builder/control_flow/edgecfg/api/
  • コア型: ExitKind, EdgeStub, Frag
  • 合成関数: seq, if_, loop_, cleanupシグネチャのみ、中身TODO
  • 検証: verify_frag_invariants(空実装)

Phase 265 P0 で最小実装完了

  • compose::loop_(): exit集合の分類実装配線なし、P1以降
  • verify_frag_invariants(): 最小検証追加(デバッグガード付き)
  • Pattern8適用: P0ではやらない偽Frag回避、P1から実戦投入

Phase 265 P1: 配線ロジック実装完了

目的: Frag/ExitKind が BasicBlockId 層で配線できることを証明

実装完了内容:

  • EdgeStub に target: Option<BasicBlockId> 追加
  • compose::loop_() が Continue → header, Break → after への配線を実行
  • verify_frag_invariants() が配線契約を検証(デバッグモード)
  • test-only PoC で配線の実証完了5個のテスト

配線契約:

  • Continue(loop_id) の EdgeStub.target = Some(header)
  • Break(loop_id) の EdgeStub.target = Some(after)
  • Normal/Return/Unwind の EdgeStub.target = None上位へ伝搬

Phase 265 P1 のスコープ:

  • Frag 層での配線ロジック
  • BasicBlockId 層でのテスト証明
  • MIR 命令生成Phase 266+
  • NormalizedShadow/JoinIR層への適用Phase 266+、JoinIR-VM Bridge 改修後)

Phase 265 P2 完了2025-12-21

実装完了内容:

  • Frag に wires: Vec<EdgeStub> フィールド追加
  • wires/exits 分離設計確立
    • exits: target = None のみ(未配線、外へ出る exit
    • wires: target = Some(...) のみ(配線済み、内部配線)
  • loop_() を wires 対応に更新Break/Continue → wires
  • seq(a, b) 実装完了a.Normal → wires
  • if_(header, cond, t, e, join_frag) 実装完了t/e.Normal → wires
  • verify_frag_invariants() に wires/exits 分離契約追加(警告のみ)
  • 全テスト PASS13個: frag 3個 + compose 9個 + verify 1個

設計判断の記録:

  1. なぜ wires/exits を分離するか?

    • 問題: 解決済み配線と未解決 exit を混ぜると、次の合成で内部配線が再度配線対象になる
    • 決定: wires/exits を分離し、不変条件を強化
    • 理由: 合成の意味が素直になり、Phase 266 で wires を MIR terminator に落とすだけ
  2. なぜ if_ は join_frag を受け取るか?

    • 問題: join: BasicBlockId だと、if の Normal exit が「join block」か「join 以降」か曖昧
    • 決定: join_frag: Frag を受け取る
    • 理由: if の Normal exit = join 以降join_frag.exitsが明確、PHI 生成の柔軟性確保
  3. なぜ verify は警告のみか?

    • P2 の役割: wires/exits 分離の証明に集中MIR 命令生成なし)
    • Phase 266 で MIR 生成時に verify を厳格化target 違反 → Err

次フェーズへの橋渡し:

次フェーズPhase 266: wires → MIR terminator 生成test-only PoC

  • wires を MIR terminator に落とす SSOT を追加(emit_wires
  • verify の strict 版を追加(verify_frag_invariants_strict、段階導入)

次フェーズPhase 267: JoinIR/NormalizedShadow への適用 + Branch 生成

  • NormalizedShadow/JoinIR で Frag/wires を実戦投入(層境界を守って段階的に)
  • Branch の terminator 生成wires → MIRを追加

Phase 267: Pattern6/7/8 への展開

  • Pattern6 (ScanWithInit) を Frag 化
  • Pattern7 (SplitScan) を Frag 化
  • Pattern8 (BoolPredicateScan) を Frag 化
  • 再利用性の確認pattern番号分岐削減

現時点では既存 pattern6/7/8 や merge/EdgeCFG は未改変(合成能力の証明のみ)。

Phase 266 P0-P2 完了2025-12-21

実装完了内容:

  • emit.rs 作成wires → MIR terminator 変換の SSOT
    • emit_wires() 実装from グループ化 + Return の target=None 許可)
    • unit test 4個jump/return/unwired/multiple_from_same_block
  • verify_frag_invariants_strict() 追加(段階導入を壊さない)
    • 既存の verify_frag_invariants() は変更なし(警告のまま)
    • wires/exits 分離契約を Err 化Return の target=None は許可)
  • mod.rs 更新emit module エクスポート)
  • 全テスト PASS1392 passed: 既存 1388 + 新規 4個

実装の核心原則:

  1. from ごとにグループ化して1本だけ許可

    • BTreeMap で from ごとにグループ化
    • 1 block = 1 terminator 制約を厳格に強制
  2. Return は target=None を許可

    • Return は target が意味を持たないemit_wires で無視される)
    • Fail-Fast 対象は Normal/Break/Continue/Unwind の target=None のみ
  3. verify_frag_invariants_strict() 別名で用意

    • 既存の verify_frag_invariants() は警告のまま維持
    • 新規 verify_frag_invariants_strict() で Err 化
    • PoC/emit 側だけ strict を使用(段階導入を壊さない)
  4. Phase 260 terminator 語彙ルールを厳守

    • Jump: set_jump_with_edge_args() を使用
    • Return: set_terminator() + set_return_env() を使用

設計判断の記録:

  1. なぜ from グループ化が必要か?

    • 問題: 同じ block に複数 terminator を設定すると上書きになる
    • 決定: from ごとにグループ化し、1本だけ許可Fail-Fast
    • 理由: 1 block = 1 terminator は MIR の不変条件
  2. なぜ Return は target=None を許可するか?

    • 問題: Return は呼び出し元に戻るので、target が意味を持たない
    • 決定: Return のみ target=None を許可
    • 理由: Normal/Break/Continue/Unwind は明確な target が必要
  3. なぜ verify_frag_invariants_strict() を別名にしたか?

    • 問題: 既存の verify_frag_invariants() を Err 化すると、既存コードが壊れる
    • 決定: 新規に strict 版を追加し、段階導入
    • 理由: Phase 267+ で既存コードを段階的に strict へ移行

次フェーズへの橋渡し:

Phase 267P0完了: Branch の第一級化BranchStub + emit_frag

  • 目的: Frag に Branch を第一級で追加し、wiresJump/Returnと同様に MIR terminator へ落とす入口を作る。
  • 追加:
    • BranchStubheader→then/else の分岐を表現)
    • Frag.branches: Vec<BranchStub>Branch 専用、wires と分離)
    • emit_frag(function, frag)SSOT: emit_wires + set_branch_with_edge_args、1 block=1 terminator を Fail-Fast
  • スコープ:
    • BasicBlockId 層で unit test により PoC 証明
    • NormalizedShadow/JoinIR への実適用は Phase 268 に繰り越し(層境界維持)

詳細: docs/development/current/main/phases/phase-267/README.md

Phase 267: JoinIR Pattern への適用

  • NormalizedShadow への Frag 適用
  • Pattern6/7/8 を Frag 化
  • Branch 生成 + pattern番号分岐削減
  • fixture + smoke test