Files
hakorune/docs/development/current/main/design/edgecfg-fragments.md
tomoaki 21387f3816 feat(edgecfg): Phase 265 P2 - seq/if_ 実装(wires/exits 分離)
## 目的

「解決済み配線(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>
2025-12-21 16:47:47 +09:00

8.8 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: 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 は未改変(合成能力の証明のみ)。