## 目的 「解決済み配線(wires)」と「未解決 exit(exits)」を分離し、 Frag 合成の基本パターンを完成させる。 ## 実装内容 ### 1. Frag 構造体の変更 - `wires: Vec<EdgeStub>` フィールド追加 - 不変条件: - exits: target = None のみ(未配線、外へ出る exit) - wires: target = Some(...) のみ(配線済み、内部配線) ### 2. loop_() の wires 対応 - Break/Continue を exits から wires に移動 - P1 テスト 3個を wires 検証に更新 ### 3. seq(a, b) 実装 - a.Normal → b.entry を wires に追加(内部配線) - seq の exits[Normal] は b の Normal のみ - 新規テスト 2個追加 ### 4. if_(header, cond, t, e, join_frag) 実装 - シグネチャ変更: join: BasicBlockId → join_frag: Frag - t/e.Normal → join_frag.entry を wires に追加 - if の exits は join_frag.exits - 新規テスト 2個追加 ### 5. verify_frag_invariants() 強化 - wires/exits 分離契約の検証追加(警告のみ) - Err 化は Phase 266 で実施 ## テスト結果 - edgecfg::api: 13/13 PASS(frag 3 + compose 9 + verify 1) - 全 lib テスト: 1388/1388 PASS(退行なし) ## 核心的な設計判断 1. **wires/exits 分離**: - 問題: 解決済み配線と未解決 exit を混ぜると再配線バグ - 解決: 分離して不変条件を強化 - 効果: Phase 266 で wires を MIR terminator に落とすだけ 2. **if_ は join_frag 受け取り**: - 問題: join: BasicBlockId では「join block」か「join 以降」か曖昧 - 解決: join_frag: Frag で「join 以降」を明確化 - 効果: PHI 生成の柔軟性確保 3. **verify は警告のみ**: - P2 の役割: wires/exits 分離の証明に集中 - Phase 266 で MIR 生成時に厳格化 ## 次フェーズへの橋渡し - Phase 266: wires を MIR terminator に落とす - Phase 267: Pattern6/7/8 を Frag 化 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
8.8 KiB
EdgeCFG Flow Fragments(Frag / ExitKind)— Structured→CFG lowering SSOT
Status: Draft(design SSOT candidate)
Last updated: 2025-12-21
Related:
- North star(CFG/ABI):
docs/development/current/main/design/join-explicit-cfg-construction.md - Catch/Cleanup/Async:
docs/development/current/main/design/exception-cleanup-async.md
目的(なぜ必要?)
EdgeCFG(block-parameterized CFG / edge-args SSOT)が固まると、次に残る “泥沼” はここだけになる:
- 構造化制御(if/loop + catch/cleanup)→ CFG の lowering で起きる exit 配線問題
- 「pattern番号で推測分岐」が増殖しやすい領域(長期的には消したい)
この文書は「pattern番号の列挙」を設計の中心にしないために、Structured→CFG の lowering を **合成代数(fragment composition)**として SSOT 化する。
結論(本書の北極星):
- “分岐の中心” は pattern番号ではなく ExitKind と Frag(fragment) に置く
- 値の合流は EdgeCFG の block params + edge-args で表し、PHI/推測/メタに逃げない
- pattern は「Extractor(形の認識)/ Plan(最小要件の抽出)」までに縮退し、merge/配線層へ逆流させない
“フロー” は 2 層ある(混ぜると崩れる)
-
CFG層(EdgeCFG / plumbing)
- terminator 語彙:
Jump/Branch/Return/Invoke - edge-args: terminator operand が SSOT
- out_edges の参照点が SSOT(複数 edge 前提)
- terminator 語彙:
-
Structured→CFG lowering 層(flow composition)
if/loop/catch/cleanup/seqを “Frag の合成” として書く- 難しさの本体は exit(脱出)の種類 と ネスト と 合流
コア概念(最小の強い箱)
ExitKind(脱出の種類を一次概念にする)
最低限の ExitKind:
Normal(fallthrough)Break(loop_id)/Continue(loop_id)ReturnUnwind(Invoke.err / catch へ)Cancel(async の drop/cancel 用。今は予約)
EdgeStub(未配線の脱出エッジ)
“どこへ飛ぶべきか未確定” な edge を表す。最終的に EdgeCFG の terminator edge に落ちる。
例(概念):
from: BlockIdkind: ExitKindargs: EdgeArgs(ターゲット params に対応する値。target が未確定でも “役割” はここで決める)
Frag(fragment)
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.Normalとe.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 の全 exit(Normal/Break/Continue/Return/Unwind/Cancel)を cleanup 経由へリライトする
- cleanup 後に “元の exit” を再発射する(ExitTag + payload を block params で運ぶ)
重要: 例外 edge(Invoke.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
verify(Fail-Fast の置き場所)
- NormalizeBox 直後: terminator 語彙固定・edge-args 長さ一致・cond付きJump禁止など “意味SSOT” を確定
- merge直前: boundary/ABI/edge-args の矛盾を即死させ “配線SSOT” を確定
- --verify: PHI predecessor / CFG cache 整合 / edge-args の長さ一致を常設
直近の導入ステップ(最小で始める)
Frag/ExitKind/EdgeStubの型を追加(docs+code 入口 SSOT)seq/if/loopの合成だけ実装(cleanup/Invoke は後段)- 既存 pattern のうち 1 本だけ
Frag合成に寄せる(Pattern8 推奨) - 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 分離契約追加(警告のみ)
- ✅ 全テスト PASS(13個: frag 3個 + compose 9個 + verify 1個)
設計判断の記録:
-
なぜ wires/exits を分離するか?
- 問題: 解決済み配線と未解決 exit を混ぜると、次の合成で内部配線が再度配線対象になる
- 決定: wires/exits を分離し、不変条件を強化
- 理由: 合成の意味が素直になり、Phase 266 で wires を MIR terminator に落とすだけ
-
なぜ if_ は join_frag を受け取るか?
- 問題: join: BasicBlockId だと、if の Normal exit が「join block」か「join 以降」か曖昧
- 決定: join_frag: Frag を受け取る
- 理由: if の Normal exit = join 以降(join_frag.exits)が明確、PHI 生成の柔軟性確保
-
なぜ verify は警告のみか?
- P2 の役割: wires/exits 分離の証明に集中(MIR 命令生成なし)
- Phase 266 で MIR 生成時に verify を厳格化(target 違反 → Err)
次フェーズへの橋渡し:
次フェーズ(Phase 266): JoinIR-VM Bridge 改修 + MIR 命令生成
- wires を MIR terminator に落とす(EdgeStub.target → set_terminator_jump)
- BlockAllocator と Frag 配線の連動
- NormalizedShadow への Frag 適用
- verify の Err 化(wires/exits 契約違反 → Err)
Phase 267: Pattern6/7/8 への展開
- Pattern6 (ScanWithInit) を Frag 化
- Pattern7 (SplitScan) を Frag 化
- Pattern8 (BoolPredicateScan) を Frag 化
- 再利用性の確認(pattern番号分岐削減)
現時点では既存 pattern6/7/8 や merge/EdgeCFG は未改変(合成能力の証明のみ)。