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:
2025-12-21 16:47:47 +09:00
parent cda034fe8f
commit 21387f3816
6 changed files with 569 additions and 92 deletions

View File

@ -1,5 +1,36 @@
# Self Current Task — Now (main)
## 2025-12-21Phase 265 P2seq/if_ 実装 - wires/exits 分離)✅
**目的**: 「解決済み配線wires」と「未解決 exitexits」を分離し、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-21Phase 263 P0.2Pattern2 promotion API SSOT
- **Goal**: Pattern2 の “Reject/continue/fallback の揺れ” を **型 + 入口SSOT**で封じ、部分続行(後段で落ちる)を構造で不可能にする

View File

@ -109,7 +109,22 @@ Related:
- **制約**:
- MIR 命令生成なしFrag 層のみ)
- NormalizedShadow 未適用Phase 266 に繰り越し)
- **次**: Phase 265 P2 で seq/if_ 実装
- **(✅ 完了Phase 265 P2: seq/if_ 実装wires/exits 分離)**
- **目的**: 「解決済み配線wires」と「未解決 exitexits」を分離し、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 分離契約、警告のみ)
- 全テスト PASS13個: 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 で固定する(段階投入)。

View File

@ -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 分離契約追加(警告のみ)
- ✅ 全テスト 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 は未改変(合成能力の証明のみ)。

View File

@ -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.exitsjoin 以降の外へ出る 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 = Noneexits へ
///
/// # 引数
/// - `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);
}
}

View File

@ -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 を追加

View File

@ -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(())