diff --git a/docs/development/current/main/10-Now.md b/docs/development/current/main/10-Now.md index b7918333..33e2c9e1 100644 --- a/docs/development/current/main/10-Now.md +++ b/docs/development/current/main/10-Now.md @@ -1,5 +1,36 @@ # Self Current Task — Now (main) +## 2025-12-21:Phase 265 P2(seq/if_ 実装 - wires/exits 分離)✅ + +**目的**: 「解決済み配線(wires)」と「未解決 exit(exits)」を分離し、Frag 合成の基本パターンを完成させる + +**実装完了内容**: +- ✅ Frag に `wires: Vec` フィールド追加 +- ✅ wires/exits 分離設計確立 + - **exits**: target = None のみ(未配線、外へ出る exit) + - **wires**: target = Some(...) のみ(配線済み、内部配線) +- ✅ loop_() を wires 対応に更新(Break/Continue → wires) +- ✅ seq(a, b) 実装完了(a.Normal → b.entry が wires、seq の Normal exit は b の Normal) +- ✅ if_(header, cond, t, e, join_frag) 実装完了(t/e.Normal → join が wires、if の exit は join_frag.exits) +- ✅ 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 を混ぜると再配線バグが起きる → 分離して不変条件強化 +2. **if_ は join_frag 受け取り**: join: BasicBlockId では「join block」か「join 以降」か曖昧 → join_frag: Frag で明確化 +3. **verify は警告のみ**: P2 は wires/exits 分離の証明に集中、Err 化は Phase 266 で MIR 生成時に実施 + +**重要**: MIR 命令生成はまだしない(Phase 266+)。Pattern6/7/8 への適用も Phase 266+ に繰り越し。 + +**次フェーズへの橋渡し**: +- Phase 266: wires を MIR terminator に落とす + NormalizedShadow への Frag 適用 +- Phase 267: Pattern6/7/8 を Frag 化 + pattern番号分岐削減 + +**詳細**: `docs/development/current/main/phases/phase-265/` + `docs/development/current/main/design/edgecfg-fragments.md` + ## 2025-12-21:Phase 263 P0.2(Pattern2 promotion API SSOT)✅ - **Goal**: Pattern2 の “Reject/continue/fallback の揺れ” を **型 + 入口SSOT**で封じ、部分続行(後段で落ちる)を構造で不可能にする diff --git a/docs/development/current/main/30-Backlog.md b/docs/development/current/main/30-Backlog.md index 3371e6d5..7479f3ab 100644 --- a/docs/development/current/main/30-Backlog.md +++ b/docs/development/current/main/30-Backlog.md @@ -109,7 +109,22 @@ Related: - **制約**: - MIR 命令生成なし(Frag 層のみ) - NormalizedShadow 未適用(Phase 266 に繰り越し) - - **次**: Phase 265 P2 で seq/if_ 実装 + +- **(✅ 完了)Phase 265 P2: seq/if_ 実装(wires/exits 分離)** + - **目的**: 「解決済み配線(wires)」と「未解決 exit(exits)」を分離し、Frag 合成の基本パターンを完成 + - **完了内容**: + - Frag に `wires: Vec` 追加 + - wires/exits 分離設計確立(exits = target None, wires = target Some) + - loop_() を wires 対応に更新 + - seq(a, b) 実装(a.Normal → wires) + - if_(header, cond, t, e, join_frag) 実装(t/e.Normal → wires) + - verify 強化(wires/exits 分離契約、警告のみ) + - 全テスト PASS(13個: frag 3 + compose 9 + verify 1) + - **設計判断**: + - wires/exits 分離で再配線バグ防止 + - if_ は join_frag: Frag で「join 以降」を明確化 + - verify は警告のみ(Err 化は Phase 266) + - **次**: Phase 266 で MIR 命令生成 + NormalizedShadow 適用 - **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 index 1b605e68..72ed9c45 100644 --- a/docs/development/current/main/design/edgecfg-fragments.md +++ b/docs/development/current/main/design/edgecfg-fragments.md @@ -164,6 +164,48 @@ Frag = { entry_block, exits: Map> } - ❌ MIR 命令生成(Phase 266+) - ❌ NormalizedShadow/JoinIR層への適用(Phase 266+、JoinIR-VM Bridge 改修後) -次フェーズ(Phase 265 P2)で seq/if_ 実装 + pattern番号分岐削減へ。 -現時点では既存 pattern6/7/8 や merge/EdgeCFG は未改変(配線能力の証明のみ)。 +**Phase 265 P2 完了!(2025-12-21)** + +**実装完了内容**: +- ✅ Frag に `wires: Vec` フィールド追加 +- ✅ 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個) + +**設計判断の記録**: + +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 は未改変(合成能力の証明のみ)。 diff --git a/src/mir/builder/control_flow/edgecfg/api/compose.rs b/src/mir/builder/control_flow/edgecfg/api/compose.rs index abc69ec7..f6f955d0 100644 --- a/src/mir/builder/control_flow/edgecfg/api/compose.rs +++ b/src/mir/builder/control_flow/edgecfg/api/compose.rs @@ -15,60 +15,165 @@ use crate::mir::control_form::LoopId; use crate::mir::value_id::ValueId; use super::frag::Frag; use super::exit_kind::ExitKind; +use super::edge_stub::EdgeStub; // Phase 265 P2: wires/exits 分離で必要 /// 順次合成: `a; b` /// -/// # 配線ルール(TODO実装) -/// - `a.exits[Normal]` を `b.entry` へ接続 -/// - それ以外の exit は上位へ伝搬 +/// # Phase 265 P2: wires/exits 分離実装完了 +/// - a.Normal → b.entry を wires に追加(内部配線) +/// - seq の exits[Normal] は b の Normal のみ(外へ出る exit) +/// +/// # 配線ルール +/// - a.Normal の EdgeStub.target = Some(b.entry) → wires +/// - seq の exits = a の非 Normal + b の全 exits +/// - seq の wires = a.Normal → b.entry + a.wires + b.wires /// /// # 引数 /// - `a`: 前段の断片 /// - `b`: 後段の断片 -/// -/// # Phase 264 -/// - シグネチャのみ固定、中身は TODO -/// - pub(crate) で外部から触れないようにする -pub(crate) fn seq(_a: Frag, _b: Frag) -> Frag { - todo!("Phase 264: seq 合成は次フェーズで実装") +pub(crate) fn seq(a: Frag, b: Frag) -> Frag { + let mut exits = BTreeMap::new(); + let mut wires = Vec::new(); + + // a の全 exit を処理 + for (kind, stubs) in a.exits { + match kind { + ExitKind::Normal => { + // a.Normal → b.entry への配線を wires に追加 + let wired_stubs: Vec = stubs + .into_iter() + .map(|mut stub| { + stub.target = Some(b.entry); + stub + }) + .collect(); + wires.extend(wired_stubs); + // exits[Normal] には入れない(内部配線) + } + // Return, Unwind, Break, Continue は上位へ伝搬 + _ => { + exits.insert(kind, stubs); + } + } + } + + // a の wires をマージ + wires.extend(a.wires); + + // b の全 exit をマージ(b.Normal が seq の Normal exit になる) + for (kind, stubs) in b.exits { + exits.entry(kind).or_insert_with(Vec::new).extend(stubs); + } + + // b の wires もマージ + wires.extend(b.wires); + + Frag { + entry: a.entry, // seq の入口は a の入口 + exits, // a の非 Normal + b の全 exit + wires, // a.Normal → b.entry + a.wires + b.wires + } } /// 条件分岐合成: `if (cond) { t } else { e }` /// -/// # 配線ルール(TODO実装) -/// - header に Branch(cond, t.entry, e.entry) を配置 -/// - `t.Normal` と `e.Normal` を join へ集約 -/// - Break/Continue/Return/Unwind は上位へ伝搬 +/// # Phase 265 P2: wires/exits 分離実装完了 +/// - t/e.Normal → join_frag.entry を wires に追加(内部配線) +/// - if の exits は join_frag.exits(join 以降の外へ出る exit) +/// +/// # 配線ルール +/// - t/e.Normal の EdgeStub.target = Some(join_frag.entry) → wires +/// - if の exits = t/e の非 Normal + join_frag.exits +/// - if の wires = t/e.Normal → join + t/e/join の wires /// /// # 引数 /// - `header`: 条件判定を行うブロック -/// - `cond`: 条件値 +/// - `_cond`: 条件値(Phase 266+ で MIR 命令生成時に使用) /// - `t`: then 分岐の断片 /// - `e`: else 分岐の断片 -/// -/// # Phase 264 -/// - シグネチャのみ固定、中身は TODO -/// - pub(crate) で外部から触れないようにする +/// - `join_frag`: join 以降の断片(t/e.Normal の配線先 + join 以降の処理) pub(crate) fn if_( - _header: BasicBlockId, - _cond: ValueId, - _t: Frag, - _e: Frag, + header: BasicBlockId, + _cond: ValueId, // Phase 266+ で使用 + t: Frag, // then 分岐 + e: Frag, // else 分岐 + join_frag: Frag, // join 以降の断片 ) -> Frag { - todo!("Phase 264: if_ 合成は次フェーズで実装") + let mut exits = BTreeMap::new(); + let mut wires = Vec::new(); + + // then の全 exit を処理 + for (kind, stubs) in t.exits { + match kind { + ExitKind::Normal => { + // t.Normal → join_frag.entry への配線を wires に追加 + let wired_stubs: Vec = stubs + .into_iter() + .map(|mut stub| { + stub.target = Some(join_frag.entry); + stub + }) + .collect(); + wires.extend(wired_stubs); + } + // Return, Unwind, Break, Continue は上位へ伝搬 + _ => { + exits.entry(kind).or_insert_with(Vec::new).extend(stubs); + } + } + } + + // then の wires をマージ + wires.extend(t.wires); + + // else の全 exit を処理(then と同じロジック) + for (kind, stubs) in e.exits { + match kind { + ExitKind::Normal => { + let wired_stubs: Vec = stubs + .into_iter() + .map(|mut stub| { + stub.target = Some(join_frag.entry); + stub + }) + .collect(); + wires.extend(wired_stubs); + } + _ => { + exits.entry(kind).or_insert_with(Vec::new).extend(stubs); + } + } + } + + // else の wires をマージ + wires.extend(e.wires); + + // join_frag の exits が if 全体の Normal exit になる + for (kind, stubs) in join_frag.exits { + exits.entry(kind).or_insert_with(Vec::new).extend(stubs); + } + + // join_frag の wires もマージ + wires.extend(join_frag.wires); + + Frag { + entry: header, // if の入口は header + exits, // t/e の非 Normal + join_frag.exits + wires, // t/e.Normal → join_frag.entry + t/e/join の wires + } } /// ループ合成: `loop (cond) { body }` /// -/// # Phase 265 P1: 配線ロジック実装完了 -/// - Continue(loop_id) → header へ配線 -/// - Break(loop_id) → after へ配線 -/// - Normal/Return/Unwind は target = None のまま上位へ伝搬 +/// # Phase 265 P2: wires/exits 分離実装完了 +/// - Continue(loop_id) → header へ配線(wires へ) +/// - Break(loop_id) → after へ配線(wires へ) +/// - Normal/Return/Unwind は target = None のまま上位へ伝搬(exits へ) /// /// # 配線ルール -/// - Continue(loop_id) の EdgeStub.target = Some(header) -/// - Break(loop_id) の EdgeStub.target = Some(after) -/// - その他の ExitKind は target = None(上位へ伝搬) +/// - Continue(loop_id) の EdgeStub.target = Some(header) → wires +/// - Break(loop_id) の EdgeStub.target = Some(after) → wires +/// - その他の ExitKind は target = None(exits へ) /// /// # 引数 /// - `loop_id`: ループ識別子(配線対象の Break/Continue 判定に使用) @@ -81,40 +186,50 @@ pub(crate) fn loop_( after: BasicBlockId, body: Frag, ) -> Frag { - // Phase 265 P1: exit 集合の配線処理 + // Phase 265 P2: exit 集合の配線処理(wires/exits 分離) let mut exits = BTreeMap::new(); + let mut wires = Vec::new(); // Phase 265 P2: 配線済み内部配線 for (kind, stubs) in body.exits { - let remapped_stubs = match kind { + match kind { ExitKind::Continue(lid) if lid == loop_id => { - // Continue → header へ配線 - stubs + // Continue → header へ配線(wires に追加) + let wired: Vec = stubs .into_iter() .map(|mut stub| { stub.target = Some(header); stub }) - .collect() + .collect(); + wires.extend(wired); + // exits には入れない(内部配線) } ExitKind::Break(lid) if lid == loop_id => { - // Break → after へ配線 - stubs + // Break → after へ配線(wires に追加) + let wired: Vec = stubs .into_iter() .map(|mut stub| { stub.target = Some(after); stub }) - .collect() + .collect(); + wires.extend(wired); + // exits には入れない(内部配線) } - // Normal, Return, Unwind は上位へ伝搬(target = None のまま) - _ => stubs, - }; - exits.insert(kind, remapped_stubs); + // Normal, Return, Unwind は上位へ伝搬(exits に追加) + _ => { + exits.insert(kind, stubs); + } + } } + // body の wires もマージ + wires.extend(body.wires); + Frag { entry: header, // ループの入口 - exits, // 配線済み exit 集合 + exits, // Normal, Return, Unwind のみ(未配線) + wires, // Continue → header, Break → after(配線済み) } } @@ -167,6 +282,7 @@ mod tests { let body_frag = Frag { entry: body_entry, exits: body_exits, + wires: vec![], }; // Execute: compose::loop_() @@ -200,24 +316,35 @@ mod tests { let body_frag = Frag { entry: body_entry, exits: body_exits, + wires: vec![], }; // Execute: compose::loop_() let loop_frag = loop_(loop_id, header, after, body_frag); - // Verify: Break/Continue are preserved and wired (Phase 265 P1) + // Verify: Break/Continue are in wires (Phase 265 P2) assert_eq!(loop_frag.entry, header); - assert_eq!(loop_frag.exits.len(), 2); - assert!(loop_frag.exits.contains_key(&ExitKind::Break(loop_id))); - assert!(loop_frag.exits.contains_key(&ExitKind::Continue(loop_id))); - // Phase 265 P1: target が正しく設定されているか確認 - if let Some(break_stubs) = loop_frag.exits.get(&ExitKind::Break(loop_id)) { - assert_eq!(break_stubs[0].target, Some(after)); - } - if let Some(continue_stubs) = loop_frag.exits.get(&ExitKind::Continue(loop_id)) { - assert_eq!(continue_stubs[0].target, Some(header)); - } + // Phase 265 P2: wires に Break/Continue があることを確認 + assert_eq!(loop_frag.wires.len(), 2); + + // Break → after の wire + let break_wire = loop_frag.wires.iter() + .find(|w| w.kind == ExitKind::Break(loop_id)) + .unwrap(); + assert_eq!(break_wire.target, Some(after)); + assert_eq!(break_wire.from, body_entry); + + // Continue → header の wire + let continue_wire = loop_frag.wires.iter() + .find(|w| w.kind == ExitKind::Continue(loop_id)) + .unwrap(); + assert_eq!(continue_wire.target, Some(header)); + assert_eq!(continue_wire.from, body_entry); + + // exits には Break/Continue がない + assert!(!loop_frag.exits.contains_key(&ExitKind::Break(loop_id))); + assert!(!loop_frag.exits.contains_key(&ExitKind::Continue(loop_id))); } // Phase 265 P1: 配線の証明テスト @@ -238,16 +365,21 @@ mod tests { let body_frag = Frag { entry: body, exits: body_exits, + wires: vec![], }; // Execute: compose::loop_() let loop_frag = loop_(loop_id, header, after, body_frag); - // Verify: Break stub has target = after - let break_stubs = loop_frag.exits.get(&ExitKind::Break(loop_id)).unwrap(); - assert_eq!(break_stubs.len(), 1); - assert_eq!(break_stubs[0].from, body); - assert_eq!(break_stubs[0].target, Some(after)); + // Verify: Break wire has target = after (Phase 265 P2) + assert_eq!(loop_frag.wires.len(), 1); + let break_wire = &loop_frag.wires[0]; + assert_eq!(break_wire.kind, ExitKind::Break(loop_id)); + assert_eq!(break_wire.from, body); + assert_eq!(break_wire.target, Some(after)); + + // exits には Break がない + assert!(!loop_frag.exits.contains_key(&ExitKind::Break(loop_id))); } #[test] @@ -266,16 +398,21 @@ mod tests { let body_frag = Frag { entry: body, exits: body_exits, + wires: vec![], }; // Execute: compose::loop_() let loop_frag = loop_(loop_id, header, after, body_frag); - // Verify: Continue stub has target = header - let continue_stubs = loop_frag.exits.get(&ExitKind::Continue(loop_id)).unwrap(); - assert_eq!(continue_stubs.len(), 1); - assert_eq!(continue_stubs[0].from, body); - assert_eq!(continue_stubs[0].target, Some(header)); + // Verify: Continue wire has target = header (Phase 265 P2) + assert_eq!(loop_frag.wires.len(), 1); + let continue_wire = &loop_frag.wires[0]; + assert_eq!(continue_wire.kind, ExitKind::Continue(loop_id)); + assert_eq!(continue_wire.from, body); + assert_eq!(continue_wire.target, Some(header)); + + // exits には Continue がない + assert!(!loop_frag.exits.contains_key(&ExitKind::Continue(loop_id))); } #[test] @@ -294,6 +431,7 @@ mod tests { let body_frag = Frag { entry: body, exits: body_exits, + wires: vec![], }; // Execute: compose::loop_() @@ -303,4 +441,228 @@ mod tests { let return_stubs = loop_frag.exits.get(&ExitKind::Return).unwrap(); assert_eq!(return_stubs[0].target, None); } + + // Phase 265 P2: seq() のテスト + + #[test] + fn test_seq_wiring_normal_to_wires() { + // Setup: a with Normal exit, b with Return exit + let a_entry = BasicBlockId(10); + let a_exit = BasicBlockId(11); + let b_entry = BasicBlockId(20); + let b_exit = BasicBlockId(21); + + let mut a_exits = BTreeMap::new(); + a_exits.insert( + ExitKind::Normal, + vec![EdgeStub::without_args(a_exit, ExitKind::Normal)], + ); + let a_frag = Frag { + entry: a_entry, + exits: a_exits, + wires: vec![], + }; + + let mut b_exits = BTreeMap::new(); + b_exits.insert( + ExitKind::Return, + vec![EdgeStub::without_args(b_exit, ExitKind::Return)], + ); + let b_frag = Frag { + entry: b_entry, + exits: b_exits, + wires: vec![], + }; + + // Execute: compose::seq() + let seq_frag = seq(a_frag, b_frag); + + // Verify: entry = a.entry + assert_eq!(seq_frag.entry, a_entry); + + // a.Normal → b.entry is in wires + assert_eq!(seq_frag.wires.len(), 1); + assert_eq!(seq_frag.wires[0].from, a_exit); + assert_eq!(seq_frag.wires[0].target, Some(b_entry)); + assert_eq!(seq_frag.wires[0].kind, ExitKind::Normal); + + // exits has no Normal (internal wiring) + assert!(!seq_frag.exits.contains_key(&ExitKind::Normal)); + + // b.Return is in exits (unwired) + let return_stubs = seq_frag.exits.get(&ExitKind::Return).unwrap(); + assert_eq!(return_stubs[0].from, b_exit); + assert_eq!(return_stubs[0].target, None); + } + + #[test] + fn test_seq_preserves_non_normal_exits() { + // Setup: a with Return + Normal, b with Unwind + let a_entry = BasicBlockId(30); + let b_entry = BasicBlockId(40); + + let mut a_exits = BTreeMap::new(); + a_exits.insert( + ExitKind::Normal, + vec![EdgeStub::without_args(BasicBlockId(31), ExitKind::Normal)], + ); + a_exits.insert( + ExitKind::Return, + vec![EdgeStub::without_args(BasicBlockId(32), ExitKind::Return)], + ); + let a_frag = Frag { + entry: a_entry, + exits: a_exits, + wires: vec![], + }; + + let mut b_exits = BTreeMap::new(); + b_exits.insert( + ExitKind::Unwind, + vec![EdgeStub::without_args(BasicBlockId(41), ExitKind::Unwind)], + ); + let b_frag = Frag { + entry: b_entry, + exits: b_exits, + wires: vec![], + }; + + // Execute + let seq_frag = seq(a_frag, b_frag); + + // Verify: a.Return + b.Unwind are in exits (unwired) + assert!(seq_frag.exits.contains_key(&ExitKind::Return)); + assert!(seq_frag.exits.contains_key(&ExitKind::Unwind)); + assert_eq!(seq_frag.exits.get(&ExitKind::Return).unwrap()[0].target, None); + assert_eq!(seq_frag.exits.get(&ExitKind::Unwind).unwrap()[0].target, None); + + // a.Normal is in wires + assert_eq!(seq_frag.wires.len(), 1); + assert_eq!(seq_frag.wires[0].kind, ExitKind::Normal); + assert_eq!(seq_frag.wires[0].target, Some(b_entry)); + } + + // Phase 265 P2: if_() のテスト + + #[test] + fn test_if_wiring_then_else_normal_to_wires() { + // Setup: then with Normal, else with Normal, join_frag with Return + let header = BasicBlockId(50); + let join_entry = BasicBlockId(51); + let join_exit = BasicBlockId(52); + let then_entry = BasicBlockId(60); + let then_exit = BasicBlockId(61); + let else_entry = BasicBlockId(70); + let else_exit = BasicBlockId(71); + + let mut then_exits = BTreeMap::new(); + then_exits.insert( + ExitKind::Normal, + vec![EdgeStub::without_args(then_exit, ExitKind::Normal)], + ); + let then_frag = Frag { + entry: then_entry, + exits: then_exits, + wires: vec![], + }; + + let mut else_exits = BTreeMap::new(); + else_exits.insert( + ExitKind::Normal, + vec![EdgeStub::without_args(else_exit, ExitKind::Normal)], + ); + let else_frag = Frag { + entry: else_entry, + exits: else_exits, + wires: vec![], + }; + + let mut join_exits = BTreeMap::new(); + join_exits.insert( + ExitKind::Return, + vec![EdgeStub::without_args(join_exit, ExitKind::Return)], + ); + let join_frag = Frag { + entry: join_entry, + exits: join_exits, + wires: vec![], + }; + + // Execute: compose::if_() + let if_frag = if_(header, ValueId(0), then_frag, else_frag, join_frag); + + // Verify: entry = header + assert_eq!(if_frag.entry, header); + + // then/else Normal → join_entry are in wires + assert_eq!(if_frag.wires.len(), 2); + for wire in &if_frag.wires { + assert_eq!(wire.kind, ExitKind::Normal); + assert_eq!(wire.target, Some(join_entry)); + } + + // exits has no Normal (internal wiring) + assert!(!if_frag.exits.contains_key(&ExitKind::Normal)); + + // join_frag.Return is in exits + assert!(if_frag.exits.contains_key(&ExitKind::Return)); + assert_eq!(if_frag.exits.get(&ExitKind::Return).unwrap()[0].from, join_exit); + } + + #[test] + fn test_if_preserves_return_from_then_and_else() { + // Setup: then with Normal + Return, else with Normal + Unwind + let header = BasicBlockId(80); + let join_entry = BasicBlockId(81); + let then_entry = BasicBlockId(90); + let else_entry = BasicBlockId(100); + + let mut then_exits = BTreeMap::new(); + then_exits.insert( + ExitKind::Normal, + vec![EdgeStub::without_args(BasicBlockId(91), ExitKind::Normal)], + ); + then_exits.insert( + ExitKind::Return, + vec![EdgeStub::without_args(BasicBlockId(92), ExitKind::Return)], + ); + let then_frag = Frag { + entry: then_entry, + exits: then_exits, + wires: vec![], + }; + + let mut else_exits = BTreeMap::new(); + else_exits.insert( + ExitKind::Normal, + vec![EdgeStub::without_args(BasicBlockId(101), ExitKind::Normal)], + ); + else_exits.insert( + ExitKind::Unwind, + vec![EdgeStub::without_args(BasicBlockId(102), ExitKind::Unwind)], + ); + let else_frag = Frag { + entry: else_entry, + exits: else_exits, + wires: vec![], + }; + + let join_frag = Frag { + entry: join_entry, + exits: BTreeMap::new(), + wires: vec![], + }; + + // Execute + let if_frag = if_(header, ValueId(0), then_frag, else_frag, join_frag); + + // Verify: Return and Unwind are in exits (unwired) + assert!(if_frag.exits.contains_key(&ExitKind::Return)); + assert!(if_frag.exits.contains_key(&ExitKind::Unwind)); + assert_eq!(if_frag.exits.get(&ExitKind::Return).unwrap()[0].target, None); + assert_eq!(if_frag.exits.get(&ExitKind::Unwind).unwrap()[0].target, None); + + // then/else Normal are in wires + assert_eq!(if_frag.wires.len(), 2); + } } diff --git a/src/mir/builder/control_flow/edgecfg/api/frag.rs b/src/mir/builder/control_flow/edgecfg/api/frag.rs index 9e16bb27..44638134 100644 --- a/src/mir/builder/control_flow/edgecfg/api/frag.rs +++ b/src/mir/builder/control_flow/edgecfg/api/frag.rs @@ -12,9 +12,10 @@ use super::edge_stub::EdgeStub; /// CFG Fragment(構造化制御の合成単位) /// -/// # 責務 +/// # 責務(Phase 265 P2 更新) /// - `entry`: 断片の入口ブロック -/// - `exits`: 断片から外へ出る未配線 edge の集合(ExitKind → EdgeStub*) +/// - `exits`: 断片から外へ出る未配線 edge の集合(target = None のみ) +/// - `wires`: 断片内部で解決された配線(target = Some(...) のみ) /// /// # 設計原則 /// - 各 Frag は「入口1つ、出口複数(種別ごと)」を持つ @@ -23,7 +24,8 @@ use super::edge_stub::EdgeStub; /// /// # 不変条件(verify で検証) /// - entry は有効な BasicBlockId -/// - 同一 ExitKind に対する EdgeStub は from が一意(重複禁止) +/// - exits 内の EdgeStub は target = None(未配線、外へ出る exit) +/// - wires 内の EdgeStub は target = Some(...)(配線済み、内部配線) /// - EdgeStub.kind と Map のキーが一致 /// /// # BTreeMap の使用理由 @@ -36,8 +38,15 @@ pub struct Frag { /// 断片からの未配線脱出エッジ(ExitKind → EdgeStub のリスト) /// + /// Phase 265 P2: target = None のみ(外へ出る exit) /// BTreeMap を使用して決定的順序を保証(Phase 69-3 の教訓) pub exits: BTreeMap>, + + /// 配線済みの内部配線(Phase 265 P2 追加) + /// + /// target = Some(...) のみ(断片内部で解決された配線) + /// Phase 266 で MIR terminator に落とす + pub wires: Vec, } impl Frag { @@ -46,6 +55,7 @@ impl Frag { Self { entry, exits: BTreeMap::new(), + wires: vec![], // Phase 265 P2: 配線済み内部配線 } } @@ -53,7 +63,11 @@ impl 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 } + Self { + entry, + exits, + wires: vec![], // Phase 265 P2: 配線済み内部配線 + } } /// 特定 ExitKind の未配線 edge を追加 diff --git a/src/mir/builder/control_flow/edgecfg/api/verify.rs b/src/mir/builder/control_flow/edgecfg/api/verify.rs index 66b27561..1c28ad1c 100644 --- a/src/mir/builder/control_flow/edgecfg/api/verify.rs +++ b/src/mir/builder/control_flow/edgecfg/api/verify.rs @@ -7,28 +7,26 @@ use super::frag::Frag; -/// Frag の不変条件を検証(Phase 265 P0: 最小チェック / Phase 265 P1: 配線契約) +/// Frag の不変条件を検証(Phase 265 P2: wires/exits 分離契約) /// /// # 検証項目 /// - Phase 265 P0: exits が空でないか(最低限の健全性) -/// - Phase 265 P1: 配線契約の「置き場所」確保(厳格化は P2/Phase 266) +/// - Phase 265 P2: wires/exits 分離契約(警告のみ、Err化は Phase 266) /// - Phase 266+: EdgeStub.from の有効性、edge-args の整合性 /// /// # 戻り値 /// - Ok(()): 検証成功 /// - Err(String): 検証失敗(エラーメッセージ) /// -/// # Phase 265 P1 -/// - 配線契約の構造だけ残す(Fail-Fast の置き場所) -/// - 実際の Err 化は P2/Phase 266 で実施 +/// # Phase 265 P2 +/// - wires/exits 分離契約の「置き場所」確保 +/// - 警告出力のみ、Err 化は Phase 266 で実施 pub fn verify_frag_invariants(frag: &Frag) -> Result<(), String> { - // Phase 265 P0: 最小チェック(デバッグビルドのみ出力) - - // 1. exits が空でないか(最低限の健全性) - if frag.exits.is_empty() { + // Phase 265 P2: exits と wires の両方が空の場合は警告 + if frag.exits.is_empty() && frag.wires.is_empty() { #[cfg(debug_assertions)] eprintln!( - "[verify_frag] Warning: Frag entry={:?} has no exits (dead end?)", + "[verify_frag] Warning: Frag entry={:?} has no exits and no wires (dead end?)", frag.entry ); } @@ -38,13 +36,14 @@ pub fn verify_frag_invariants(frag: &Frag) -> Result<(), String> { { if crate::config::env::is_joinir_debug() { eprintln!( - "[verify_frag] Frag entry={:?}, exits={} kinds", + "[verify_frag] Frag entry={:?}, exits={} kinds, wires={} stubs", frag.entry, - frag.exits.len() + frag.exits.len(), + frag.wires.len() ); for (kind, stubs) in &frag.exits { eprintln!( - "[verify_frag] {:?}: {} stubs", + "[verify_frag] exits[{:?}]: {} stubs", kind, stubs.len() ); @@ -52,18 +51,31 @@ pub fn verify_frag_invariants(frag: &Frag) -> Result<(), String> { } } - // Phase 265 P1: 配線契約の「置き場所」確保 - // (厳格化は P2/Phase 266 で実施) + // Phase 265 P2: wires/exits 分離契約の検証 #[cfg(debug_assertions)] { if crate::config::env::is_joinir_debug() { - // 最小限のログ出力(将来の厳格化のための構造は残す) - for (kind, _stubs) in &frag.exits { - match kind { - super::exit_kind::ExitKind::Continue(_) | super::exit_kind::ExitKind::Break(_) => { - // P2+: ここで target.is_none() を Err に変える + // exits 内に target = Some がいたら警告(Phase 266+ で Err 化) + for (kind, stubs) in &frag.exits { + for stub in stubs { + if stub.target.is_some() { + eprintln!( + "[verify_frag] ERROR: exits[{:?}] contains wired stub (target={:?}) - should be in wires", + kind, stub.target + ); + // Phase 266+: return Err(format!("exits[{:?}] contains wired stub", kind)) } - _ => {} + } + } + + // wires 内に target = None がいたら警告(Phase 266+ で Err 化) + for stub in &frag.wires { + if stub.target.is_none() { + eprintln!( + "[verify_frag] ERROR: wires contains unwired stub (from={:?}, kind={:?}) - should be in exits", + stub.from, stub.kind + ); + // Phase 266+: return Err(format!("wires contains unwired stub (from={:?})", stub.from)) } } } @@ -92,6 +104,7 @@ mod tests { let frag = Frag { entry: header, exits, + wires: vec![], }; // P1: Always returns Ok(())