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>
This commit is contained in:
@ -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<EdgeStub>` フィールド追加
|
||||
- ✅ 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**で封じ、部分続行(後段で落ちる)を構造で不可能にする
|
||||
|
||||
@ -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<EdgeStub>` 追加
|
||||
- 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 で固定する(段階投入)。
|
||||
|
||||
@ -164,6 +164,48 @@ Frag = { entry_block, exits: Map<ExitKind, Vec<EdgeStub>> }
|
||||
- ❌ 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<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個)
|
||||
|
||||
**設計判断の記録**:
|
||||
|
||||
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 は未改変(合成能力の証明のみ)。
|
||||
|
||||
|
||||
@ -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<EdgeStub> = 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<EdgeStub> = 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<EdgeStub> = 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<EdgeStub> = 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<EdgeStub> = 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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<ExitKind, Vec<EdgeStub>>,
|
||||
|
||||
/// 配線済みの内部配線(Phase 265 P2 追加)
|
||||
///
|
||||
/// target = Some(...) のみ(断片内部で解決された配線)
|
||||
/// Phase 266 で MIR terminator に落とす
|
||||
pub wires: Vec<EdgeStub>,
|
||||
}
|
||||
|
||||
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 を追加
|
||||
|
||||
@ -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(())
|
||||
|
||||
Reference in New Issue
Block a user