diff --git a/docs/development/current/main/10-Now.md b/docs/development/current/main/10-Now.md index 5dc66f56..bf9b9e4b 100644 --- a/docs/development/current/main/10-Now.md +++ b/docs/development/current/main/10-Now.md @@ -1,24 +1,46 @@ # Self Current Task — Now (main) -## 2025-12-21:Phase 263 P0(Pattern2 LoopBodyLocal fallback 修正)✅ +## 2025-12-21:Phase 263 P0.2(Pattern2 promotion API SSOT)✅ -- **Goal**: Pattern2 で処理できない LoopBodyLocal を検出したら Pattern2 全体を早期終了(部分的な処理続行は禁止) -- **修正内容**: - - `promote_step_box.rs`: 戻り値を `Result, String>` に変更、Reject を二分化(対象外 → Ok(None)、対象だが未対応 → Err) - - `pattern2_lowering_orchestrator.rs`: Ok(None) 検出で早期 return - - Fail-Fast 原則: 対象外は Ok(None) で後続経路へ、対象だが未対応は Err で即座に失敗 +- **Goal**: Pattern2 の “Reject/continue/fallback の揺れ” を **型 + 入口SSOT**で封じ、部分続行(後段で落ちる)を構造で不可能にする +- **実装**: + - `PromoteDecision::{Promoted, NotApplicable, Freeze}`(Option 多重を撤去) + - `pattern2/api/` を入口SSOTとして新設し、`try_promote(...)` を **単一参照点**に固定 +- **効果**: + - `NotApplicable` は **必ず** `Ok(None)` で Pattern2 全体を抜ける(後続経路へ) + - `Freeze` は **必ず** Fail-Fast(close-but-unsupported のみ即死) - **検証結果**: - - cargo test --lib: **1368/1368 PASS** ✅ (1367→1368 に改善) - - quick smoke: **45/46 PASS** ✅ (大幅改善!) - - エラーメッセージ変化: `[cf_loop/pattern2] Variable not found: seg` → `[joinir/freeze] Loop lowering failed` (Pattern2 が正しく abort) -- **Commit**: `93022e7e1` - fix(pattern2): abort entire Pattern2 on unpromoted LoopBodyLocal instead of partial execution + - cargo test --lib: **1368/1368 PASS** ✅ + - quick smoke: **45/46 PASS** ✅(既知 1 件は別論点) +- **Commits**: + - `abdb860e7`(P0.1): PromoteDecision 導入(Option 揺れの撤去) + - `e17902a44`(P0.2): `pattern2/api/` で入口SSOT物理固定 - **詳細**: `docs/development/current/main/phases/phase-263/README.md` +## 2025-12-21:Phase 264 P0(EdgeCFG Fragment 入口作成)✅ + +**目的**: Frag/ExitKind を一次概念にする入口APIを用意(実装置換は次フェーズ) + +**完了内容**: +- 入口フォルダ作成: `src/mir/builder/control_flow/edgecfg/api/` +- コア型定義: `ExitKind`, `EdgeStub`, `Frag` +- 合成関数シグネチャ: `seq`, `if_`, `loop_`, `cleanup`(中身TODO) +- 最小テスト: 3個のユニットテスト追加 +- ドキュメント連動: `edgecfg-fragments.md` に入口情報追記 + +**重要**: 既存実装(pattern6/7/8, merge/EdgeCFG)は未改変。 +入口だけ固定し、適用は quick 復旧後の次フェーズで実施。 + +**次のステップ**: +- Phase 265: Pattern8 を Frag 合成に移行(最小適用) +- Phase 266: Pattern6/7 への展開(再利用確認) + +**詳細**: `docs/development/current/main/phases/phase-264/README.md` + `docs/development/current/main/design/edgecfg-fragments.md` + ## Next (planned) -- Phase 259: `StringUtils.is_integer/1`(nested-if + loop)を JoinIR で受理して `--profile quick` を進める -- Phase 260: block-parameterized CFG へ向けた "edge-args terminator 併存導入"(大工事 / Strangler) -- Phase 259.x: Me receiver SSOT(`variable_map["me"]`)を API 化して `"this"`/`"me"` 混同を構造で潰す +- Phase 265(planned): Pattern8 を Frag 合成に移行し、ExitKind+Frag の実装適用を開始(`compose::loop_` 実装) +- Phase 266(planned): catch/cleanup / cleanup/defer / async を "exit-edge 正規化" で追加できる形へ(設計: `docs/development/current/main/design/exception-cleanup-async.md`) - Phase 141 P2+: Call/MethodCall 対応(effects + typing を分離して段階投入、ANF を前提に順序固定) - Phase 143-loopvocab P3+: 条件スコープ拡張(impure conditions 対応) - 詳細: `docs/development/current/main/30-Backlog.md` diff --git a/docs/development/current/main/30-Backlog.md b/docs/development/current/main/30-Backlog.md index 830a8673..a8220566 100644 --- a/docs/development/current/main/30-Backlog.md +++ b/docs/development/current/main/30-Backlog.md @@ -64,14 +64,25 @@ Related: - 最小再現 fixture + smoke で固定(先に失敗を SSOT 化) - Pattern2 が不成立のときは “部分続行” せず `Ok(None)` で fallback(既定挙動不変) -- **Phase 263+(planned / refactor): Pattern2 PromoteDecision API hardening** - - ねらい: “Reject でも続行して後段で落ちる” を構造で不可能にする(迷子防止) - - 形(最小): - - `PromoteStepBox::try_promote(...) -> Result` - - `PromoteDecision::{Promoted, NotApplicable, Freeze}` - - 受け入れ条件: - - orchestrator が `NotApplicable` を受け取ったら Pattern2 を `Ok(None)` で抜けて fallback(SSOT) - - “Reject=continue” のような曖昧挙動がコードから消える +- **(DONE)Phase 263 P0.2: Pattern2 PromoteDecision API hardening** + - 入口SSOT: `src/mir/builder/control_flow/joinir/patterns/pattern2/api/` + - `PromoteDecision::{Promoted, NotApplicable, Freeze}` と `try_promote(...)` に参照点を収束(Option揺れを撤去) + +- **Phase 264(✅ 入口作成完了): EdgeCFG Fragment 入口作成(design-first)** + - **ステータス**: ✅ 入口作成完了(適用は次フェーズ) + - **実装内容**: + - `edgecfg/api/` フォルダに SSOT 入口作成 + - `ExitKind`, `EdgeStub`, `Frag` の型定義 + - `seq`, `if_`, `loop_`, `cleanup` のシグネチャ固定(pub(crate)) + - 最小ユニットテスト 3個 + - ドキュメント連動(edgecfg-fragments.md) + - **制約遵守**: + - 既存 pattern6/7/8 未改変 + - merge/EdgeCFG 未改変 + - cargo test -p nyash-rust --lib --no-run 成功確認 + - **次フェーズへの橋渡し**: + - Phase 265 で Pattern8 適用時に `compose::loop_` を実装 + - 再利用確認後、pattern番号分岐を段階的に削減 - **real-app loop regression の横展開(VM + LLVM EXE)** - ねらい: 実コード由来ループを 1 本ずつ最小抽出して fixture/smoke で固定する(段階投入)。 diff --git a/docs/development/current/main/design/edgecfg-fragments.md b/docs/development/current/main/design/edgecfg-fragments.md new file mode 100644 index 00000000..b5d3420f --- /dev/null +++ b/docs/development/current/main/design/edgecfg-fragments.md @@ -0,0 +1,142 @@ +# 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 層ある(混ぜると崩れる) + +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: + +- `Normal`(fallthrough) +- `Break(loop_id)` / `Continue(loop_id)` +- `Return` +- `Unwind`(Invoke.err / catch へ) +- `Cancel`(async の drop/cancel 用。今は予約) + +### EdgeStub(未配線の脱出エッジ) + +“どこへ飛ぶべきか未確定” な edge を表す。最終的に EdgeCFG の terminator edge に落ちる。 + +例(概念): + +- `from: BlockId` +- `kind: ExitKind` +- `args: EdgeArgs`(ターゲット params に対応する値。target が未確定でも “役割” はここで決める) + +### Frag(fragment) + +```text +Frag = { entry_block, exits: Map> } +``` + +- `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 の長さ一致を常設 + +## 直近の導入ステップ(最小で始める) + +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+)で既存 pattern への適用を開始。 +現時点では既存 pattern6/7/8 や merge/EdgeCFG は未改変(入口だけ用意)。 + diff --git a/src/mir/builder/control_flow/edgecfg/api/compose.rs b/src/mir/builder/control_flow/edgecfg/api/compose.rs new file mode 100644 index 00000000..966103d2 --- /dev/null +++ b/src/mir/builder/control_flow/edgecfg/api/compose.rs @@ -0,0 +1,102 @@ +/*! + * Frag 合成関数群(Phase 264: シグネチャのみ + TODO実装) + * + * 設計原則: + * - 各関数は「入口の形」だけを固定 + * - 中身は TODO!() で次フェーズに委譲 + * - シグネチャ変更は破壊的変更として扱う + * - pub(crate) にして外部から誤って触れないようにする + * (実装フェーズ Phase 265+ で pub に昇格) + */ + +use crate::mir::basic_block::BasicBlockId; +use crate::mir::control_form::LoopId; +use crate::mir::value_id::ValueId; +use super::frag::Frag; + +/// 順次合成: `a; b` +/// +/// # 配線ルール(TODO実装) +/// - `a.exits[Normal]` を `b.entry` へ接続 +/// - それ以外の exit は上位へ伝搬 +/// +/// # 引数 +/// - `a`: 前段の断片 +/// - `b`: 後段の断片 +/// +/// # Phase 264 +/// - シグネチャのみ固定、中身は TODO +/// - pub(crate) で外部から触れないようにする +pub(crate) fn seq(_a: Frag, _b: Frag) -> Frag { + todo!("Phase 264: seq 合成は次フェーズで実装") +} + +/// 条件分岐合成: `if (cond) { t } else { e }` +/// +/// # 配線ルール(TODO実装) +/// - header に Branch(cond, t.entry, e.entry) を配置 +/// - `t.Normal` と `e.Normal` を join へ集約 +/// - Break/Continue/Return/Unwind は上位へ伝搬 +/// +/// # 引数 +/// - `header`: 条件判定を行うブロック +/// - `cond`: 条件値 +/// - `t`: then 分岐の断片 +/// - `e`: else 分岐の断片 +/// +/// # Phase 264 +/// - シグネチャのみ固定、中身は TODO +/// - pub(crate) で外部から触れないようにする +pub(crate) fn if_( + _header: BasicBlockId, + _cond: ValueId, + _t: Frag, + _e: Frag, +) -> Frag { + todo!("Phase 264: if_ 合成は次フェーズで実装") +} + +/// ループ合成: `loop (cond) { body }` +/// +/// # 配線ルール(TODO実装) +/// - header / latch / after を組む +/// - Continue → header へ戻す +/// - Break → after へ出す +/// - Return/Unwind は上位へ伝搬 +/// +/// # 引数 +/// - `loop_id`: ループ識別子 +/// - `header`: ループヘッダー +/// - `body`: ループ本体の断片 +/// +/// # Phase 264 +/// - シグネチャのみ固定、中身は TODO +/// - pub(crate) で外部から触れないようにする +pub(crate) fn loop_( + _loop_id: LoopId, + _header: BasicBlockId, + _body: Frag, +) -> Frag { + todo!("Phase 264: loop_ 合成は次フェーズで実装") +} + +/// cleanup 合成: finally の後継(すべての exit を正規化) +/// +/// # 配線ルール(TODO実装) +/// - body の全 exit を cleanup 経由へリライト +/// - cleanup 後に元の exit を再発射(ExitTag + payload を block params で運ぶ) +/// - Invoke.err も cleanup に寄せる +/// +/// # 引数 +/// - `body`: 本体の断片 +/// - `cleanup_block`: cleanup 処理を行うブロック +/// +/// # Phase 264 +/// - シグネチャのみ固定、中身は TODO +/// - pub(crate) で外部から触れないようにする +pub(crate) fn cleanup( + _body: Frag, + _cleanup_block: BasicBlockId, +) -> Frag { + todo!("Phase 264: cleanup 合成は次フェーズで実装") +} diff --git a/src/mir/builder/control_flow/edgecfg/api/edge_stub.rs b/src/mir/builder/control_flow/edgecfg/api/edge_stub.rs new file mode 100644 index 00000000..343095eb --- /dev/null +++ b/src/mir/builder/control_flow/edgecfg/api/edge_stub.rs @@ -0,0 +1,58 @@ +/*! + * EdgeStub - 未配線の脱出エッジ(Phase 264: EdgeCFG Fragment) + * + * Frag 合成時に「どこへ飛ぶべきか未確定」な edge を表現。 + * 最終的に EdgeCFG の terminator edge に解決される。 + */ + +use crate::mir::basic_block::{BasicBlockId, EdgeArgs}; +use crate::mir::join_ir::lowering::inline_boundary::JumpArgsLayout; +use super::exit_kind::ExitKind; + +/// 未配線の脱出エッジ +/// +/// # 責務 +/// - `from`: 脱出元のブロックID +/// - `kind`: 脱出の種別(配線先の決定に使う) +/// - `args`: edge-args(target が未確定でも「役割」は確定) +/// +/// # 既存型の使用 +/// - `BasicBlockId`: `crate::mir::basic_block::BasicBlockId` を使用 +/// - 定義場所: `src/mir/basic_block.rs:16` +/// - `EdgeArgs`: `crate::mir::basic_block::EdgeArgs` を使用(**MIR側のEdgeArgs**) +/// - 定義場所: `src/mir/basic_block.rs:46-51`(Phase 260 P0) +/// - EdgeCFG の terminator operand で使ってる型と同じ(混線回避) +/// - 構成: `{ layout: JumpArgsLayout, values: Vec }` +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct EdgeStub { + /// 脱出元ブロック + pub from: BasicBlockId, + + /// 脱出種別(配線ルール決定に使用) + pub kind: ExitKind, + + /// エッジ引数(Phase 260 EdgeArgs SSOT) + /// + /// target が未確定でも、このエッジが運ぶ値の「役割」は + /// ここで確定する(合成則で args を写像する設計) + pub args: EdgeArgs, +} + +impl EdgeStub { + /// EdgeStub を生成 + pub fn new(from: BasicBlockId, kind: ExitKind, args: EdgeArgs) -> Self { + Self { from, kind, args } + } + + /// EdgeArgs を持たない EdgeStub を生成(利便性) + pub fn without_args(from: BasicBlockId, kind: ExitKind) -> Self { + Self { + from, + kind, + args: EdgeArgs { + layout: JumpArgsLayout::CarriersOnly, + values: vec![], + }, + } + } +} diff --git a/src/mir/builder/control_flow/edgecfg/api/exit_kind.rs b/src/mir/builder/control_flow/edgecfg/api/exit_kind.rs new file mode 100644 index 00000000..9a031d39 --- /dev/null +++ b/src/mir/builder/control_flow/edgecfg/api/exit_kind.rs @@ -0,0 +1,66 @@ +/*! + * ExitKind - 制御フローの脱出種別(Phase 264: EdgeCFG Fragment入口) + * + * 構造化制御(if/loop/catch/cleanup)から CFG への lowering において、 + * 脱出エッジの配線先を決定するための一次概念。 + */ + +use crate::mir::control_form::LoopId; + +/// 制御フローの脱出種別 +/// +/// # 設計原則 +/// - ExitKind は pattern番号より優先される一次概念 +/// - 各 Frag は ExitKind ごとに未配線の EdgeStub を保持 +/// - 合成則(seq/if_/loop_/cleanup)で配線ルールを統一 +/// +/// # 既存型の使用 +/// - `LoopId`: `crate::mir::control_form::LoopId` を使用(既存の安定な型) +/// - 定義場所: `src/mir/control_form.rs:23` +/// - 後で差し替える必要なし(既に control_form で SSOT 化済み) +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum ExitKind { + /// 通常のフォールスルー(次の文・ループ継続など) + Normal, + + /// 関数からのreturn + Return, + + /// 指定ループからの脱出 + Break(LoopId), + + /// 指定ループの次回イテレーション + Continue(LoopId), + + /// 例外のunwind(Invoke.err → catch) + Unwind, + + /// 非同期タスクのキャンセル(予約:将来の async/drop 用) + #[allow(dead_code)] + Cancel, +} + +impl ExitKind { + /// ループ関連の脱出か判定 + pub fn is_loop_exit(&self) -> bool { + matches!(self, ExitKind::Break(_) | ExitKind::Continue(_)) + } + + /// 関数全体からの脱出か判定 + pub fn is_function_exit(&self) -> bool { + matches!(self, ExitKind::Return | ExitKind::Unwind) + } +} + +impl std::fmt::Display for ExitKind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ExitKind::Normal => write!(f, "Normal"), + ExitKind::Return => write!(f, "Return"), + ExitKind::Break(id) => write!(f, "Break({})", id.0), + ExitKind::Continue(id) => write!(f, "Continue({})", id.0), + ExitKind::Unwind => write!(f, "Unwind"), + ExitKind::Cancel => write!(f, "Cancel"), + } + } +} diff --git a/src/mir/builder/control_flow/edgecfg/api/frag.rs b/src/mir/builder/control_flow/edgecfg/api/frag.rs new file mode 100644 index 00000000..9e16bb27 --- /dev/null +++ b/src/mir/builder/control_flow/edgecfg/api/frag.rs @@ -0,0 +1,129 @@ +/*! + * Frag - CFG Fragment(Phase 264: 構造化制御の合成単位) + * + * 構造化制御(if/loop/catch/cleanup)から CFG への lowering において、 + * 未配線の脱出エッジを持つ CFG 断片を表現する。 + */ + +use std::collections::BTreeMap; +use crate::mir::basic_block::BasicBlockId; +use super::exit_kind::ExitKind; +use super::edge_stub::EdgeStub; + +/// CFG Fragment(構造化制御の合成単位) +/// +/// # 責務 +/// - `entry`: 断片の入口ブロック +/// - `exits`: 断片から外へ出る未配線 edge の集合(ExitKind → EdgeStub*) +/// +/// # 設計原則 +/// - 各 Frag は「入口1つ、出口複数(種別ごと)」を持つ +/// - 合成則(seq/if_/loop_/cleanup)で複数 Frag を組み合わせる +/// - pattern番号は「形の認識」までに留め、配線層へ逆流させない +/// +/// # 不変条件(verify で検証) +/// - entry は有効な BasicBlockId +/// - 同一 ExitKind に対する EdgeStub は from が一意(重複禁止) +/// - EdgeStub.kind と Map のキーが一致 +/// +/// # BTreeMap の使用理由 +/// - Phase 69-3 の教訓: HashMap は非決定的イテレーションを起こす +/// - ExitKind の順序を決定的にすることで、デバッグ出力・テストが安定 +#[derive(Debug, Clone)] +pub struct Frag { + /// 断片の入口ブロック + pub entry: BasicBlockId, + + /// 断片からの未配線脱出エッジ(ExitKind → EdgeStub のリスト) + /// + /// BTreeMap を使用して決定的順序を保証(Phase 69-3 の教訓) + pub exits: BTreeMap>, +} + +impl Frag { + /// 新規 Frag を生成(出口なし) + pub fn new(entry: BasicBlockId) -> Self { + Self { + entry, + exits: BTreeMap::new(), + } + } + + /// 単一出口を持つ Frag を生成 + pub fn with_single_exit(entry: BasicBlockId, stub: EdgeStub) -> Self { + let mut exits = BTreeMap::new(); + exits.insert(stub.kind, vec![stub]); + Self { entry, exits } + } + + /// 特定 ExitKind の未配線 edge を追加 + pub fn add_exit(&mut self, stub: EdgeStub) { + self.exits.entry(stub.kind).or_insert_with(Vec::new).push(stub); + } + + /// 特定 ExitKind の未配線 edge を取得 + pub fn get_exits(&self, kind: &ExitKind) -> Option<&Vec> { + self.exits.get(kind) + } + + /// すべての ExitKind を列挙 + pub fn exit_kinds(&self) -> impl Iterator { + self.exits.keys() + } +} + +// ============================================================================ +// ユニットテスト(Phase 264: 最小3個) +// ============================================================================ + +#[cfg(test)] +mod tests { + use super::*; + use crate::mir::basic_block::EdgeArgs; + use crate::mir::join_ir::lowering::inline_boundary::JumpArgsLayout; + + #[test] + fn test_frag_basic_construction() { + // 最小のテスト: Frag が ExitKind::Normal を保持できる + let entry = BasicBlockId::new(0); + let from = BasicBlockId::new(1); + + let stub = EdgeStub::without_args(from, ExitKind::Normal); + let frag = Frag::with_single_exit(entry, stub.clone()); + + assert_eq!(frag.entry, entry); + + let exits = frag.get_exits(&ExitKind::Normal).unwrap(); + assert_eq!(exits.len(), 1); + assert_eq!(exits[0].from, from); + assert_eq!(exits[0].kind, ExitKind::Normal); + } + + #[test] + fn test_frag_multiple_exits() { + // 複数 ExitKind のテスト + let entry = BasicBlockId::new(0); + let mut frag = Frag::new(entry); + + frag.add_exit(EdgeStub::without_args(BasicBlockId::new(1), ExitKind::Normal)); + frag.add_exit(EdgeStub::without_args(BasicBlockId::new(2), ExitKind::Return)); + + assert_eq!(frag.exits.len(), 2); + assert!(frag.get_exits(&ExitKind::Normal).is_some()); + assert!(frag.get_exits(&ExitKind::Return).is_some()); + assert!(frag.get_exits(&ExitKind::Unwind).is_none()); + } + + #[test] + fn test_exit_kind_helpers() { + use crate::mir::control_form::LoopId; + + assert!(!ExitKind::Normal.is_loop_exit()); + assert!(ExitKind::Break(LoopId(0)).is_loop_exit()); + assert!(ExitKind::Continue(LoopId(0)).is_loop_exit()); + + assert!(ExitKind::Return.is_function_exit()); + assert!(ExitKind::Unwind.is_function_exit()); + assert!(!ExitKind::Normal.is_function_exit()); + } +} diff --git a/src/mir/builder/control_flow/edgecfg/api/mod.rs b/src/mir/builder/control_flow/edgecfg/api/mod.rs new file mode 100644 index 00000000..7dd45496 --- /dev/null +++ b/src/mir/builder/control_flow/edgecfg/api/mod.rs @@ -0,0 +1,33 @@ +//! EdgeCFG Fragment API(Phase 264: 入口SSOT) +//! +//! # コア概念 +//! - [`ExitKind`]: 脱出種別(一次概念) +//! - [`EdgeStub`]: 未配線エッジ +//! - [`Frag`]: CFG断片 +//! - [`compose`]: 合成関数群(Phase 264: TODO実装、pub(crate)) +//! +//! # 設計原則 +//! - ExitKind を一次概念にし、pattern番号は「形の認識」までに縮退 +//! - 値の合流は EdgeCFG の block params + edge-args で表す(PHI/推測/メタに逃げない) +//! - Fail-Fast: verify で不変条件を早期検証 +//! +//! # 関連文書 +//! - 北極星設計: `docs/development/current/main/design/edgecfg-fragments.md` +//! - EdgeCFG 基盤: `docs/development/current/main/design/join-explicit-cfg-construction.md` + +pub mod exit_kind; +pub mod edge_stub; +pub mod frag; +pub mod compose; +pub mod verify; + +// 公開型(安定) +pub use exit_kind::ExitKind; +pub use edge_stub::EdgeStub; +pub use frag::Frag; + +// 合成関数(Phase 264: crate内のみ公開、Phase 265+でpub化) +pub(crate) use compose::{seq, if_, loop_, cleanup}; + +// 検証関数 +pub use verify::verify_frag_invariants; diff --git a/src/mir/builder/control_flow/edgecfg/api/verify.rs b/src/mir/builder/control_flow/edgecfg/api/verify.rs new file mode 100644 index 00000000..e773b292 --- /dev/null +++ b/src/mir/builder/control_flow/edgecfg/api/verify.rs @@ -0,0 +1,28 @@ +/*! + * Frag 検証関数(Phase 264: Fail-Fast の置き場所) + * + * Phase 264 では空実装。 + * 実装適用時(Phase 265+)に検証項目を段階的に追加。 + */ + +use super::frag::Frag; + +/// Frag の不変条件を検証(Phase 264: 空実装) +/// +/// # 検証項目(TODO) +/// - entry は有効な BasicBlockId +/// - 同一 ExitKind に対する EdgeStub.from が一意 +/// - EdgeStub.kind と Map のキーが一致 +/// - EdgeArgs の layout と values の長さが一致 +/// +/// # 戻り値 +/// - Ok(()): 検証成功 +/// - Err(String): 検証失敗(エラーメッセージ) +/// +/// # Phase 264 +/// - 空実装(次フェーズで Fail-Fast 検証を追加) +/// - 不変条件は edgecfg-fragments.md に文書化済み +pub fn verify_frag_invariants(_frag: &Frag) -> Result<(), String> { + // Phase 264: 空実装(次フェーズで検証項目追加) + Ok(()) +} diff --git a/src/mir/builder/control_flow/edgecfg/mod.rs b/src/mir/builder/control_flow/edgecfg/mod.rs new file mode 100644 index 00000000..5a37b944 --- /dev/null +++ b/src/mir/builder/control_flow/edgecfg/mod.rs @@ -0,0 +1,12 @@ +//! EdgeCFG 関連モジュール(Phase 264) +//! +//! # 構成 +//! - [`api`]: Fragment API 入口(SSOT) + +pub mod api; + +// 公開型(安定) +pub use api::{ExitKind, EdgeStub, Frag, verify_frag_invariants}; + +// 合成関数(Phase 264: crate内のみ公開) +pub(crate) use api::{seq, if_, loop_, cleanup}; diff --git a/src/mir/builder/control_flow/mod.rs b/src/mir/builder/control_flow/mod.rs index d744e479..767a57a9 100644 --- a/src/mir/builder/control_flow/mod.rs +++ b/src/mir/builder/control_flow/mod.rs @@ -57,6 +57,9 @@ pub(in crate::mir::builder) mod utils; // Phase 134 P0: Normalization entry point consolidation pub(in crate::mir::builder) mod normalization; +// Phase 264: EdgeCFG Fragment API (入口SSOT) +pub(in crate::mir::builder) mod edgecfg; + // Phase 140-P4-A: Re-export for loop_canonicalizer SSOT (crate-wide visibility) pub(crate) use joinir::detect_skip_whitespace_pattern; diff --git a/src/mir/control_form.rs b/src/mir/control_form.rs index acbadc14..791b3edf 100644 --- a/src/mir/control_form.rs +++ b/src/mir/control_form.rs @@ -19,7 +19,9 @@ use std::collections::BTreeSet; // ============================================================================ /// ループを一意に識別する ID -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +/// +/// Phase 264: PartialOrd, Ord を追加(ExitKind の BTreeMap キーとして使用) +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)] pub struct LoopId(pub u32); /// 出口辺を一意に識別する ID