feat(edgecfg): Phase 265 P0 - compose/verify 最小実装(入口SSOT迷子防止)

🎯 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 13:07:17 +09:00
parent 923a442326
commit ab1510920c
5 changed files with 174 additions and 20 deletions

View File

@ -37,6 +37,24 @@
**詳細**: `docs/development/current/main/phases/phase-264/README.md` + `docs/development/current/main/design/edgecfg-fragments.md`
## 2025-12-21Phase 265 P0compose/verify 最小実装)✅
**目的**: 入口SSOTを触って迷子防止compose/verify の形を固める)
**完了内容**:
- `compose::loop_()` 最小実装exit集合の分類のみ、配線はP1以降
- `verify_frag_invariants()` 最小実装(デバッグガード付き)
- compose::loop_() のユニットテスト 2個追加
**重要**: Pattern8への適用はP0ではやらない偽Fragを避ける
配線ロジックはP1で実装、Pattern8適用もP1から。
**次のステップ**:
- Phase 265 P1: 配線ロジック実装 + Pattern8適用
- Phase 265 P2: seq/if_ 実装pattern番号分岐削減の見通し
**詳細**: `docs/development/current/main/phases/phase-265/README.md`
## Next (planned)
- Phase 265planned: Pattern8 を Frag 合成に移行し、ExitKind+Frag の実装適用を開始(`compose::loop_` 実装)

View File

@ -84,6 +84,17 @@ Related:
- Phase 265 で Pattern8 適用時に `compose::loop_` を実装
- 再利用確認後、pattern番号分岐を段階的に削減
- **Phase 265 P0✅ 完了): compose/verify 最小実装**
- **目的**: 入口SSOTの形を固める迷子防止
- **実装**:
- compose::loop_() 最小実装exit集合分類のみ、配線なし
- verify_frag_invariants() 最小実装(デバッグガード付き)
- compose::loop_() ユニットテスト 2個追加
- **制約**:
- Pattern8 未改変P0では触らない、偽Frag回避
- 配線ロジックは P1 以降
- **次**: Phase 265 P1 で配線ロジック + Pattern8適用
- **real-app loop regression の横展開VM + LLVM EXE**
- ねらい: 実コード由来ループを 1 本ずつ最小抽出して fixture/smoke で固定する(段階投入)。
- 現状: Phase 107find_balanced_array/object / json_cur 由来)まで固定済み。

View File

@ -137,6 +137,12 @@ Frag = { entry_block, exits: Map<ExitKind, Vec<EdgeStub>> }
- 合成関数: `seq`, `if_`, `loop_`, `cleanup`シグネチャのみ、中身TODO
- 検証: `verify_frag_invariants`(空実装)
次フェーズ(Phase 265+)で既存 pattern への適用を開始。
**Phase 265 P0 で最小実装完了**
- `compose::loop_()`: exit集合の分類実装配線なし、P1以降
- `verify_frag_invariants()`: 最小検証追加(デバッグガード付き)
- Pattern8適用: P0ではやらない偽Frag回避、P1から実戦投入
次フェーズPhase 265 P1+)で配線ロジック + Pattern8適用 + seq/if_ 実装へ。
現時点では既存 pattern6/7/8 や merge/EdgeCFG は未改変(入口だけ用意)。

View File

@ -9,6 +9,7 @@
* (実装フェーズ Phase 265+ で pub に昇格)
*/
use std::collections::BTreeMap;
use crate::mir::basic_block::BasicBlockId;
use crate::mir::control_form::LoopId;
use crate::mir::value_id::ValueId;
@ -58,26 +59,40 @@ pub(crate) fn if_(
/// ループ合成: `loop (cond) { body }`
///
/// # 配線ルールTODO実装
/// # Phase 265 P0: exit集合の分類のみ配線はP1以降
/// - body の全 exit をそのまま伝搬(分類するだけ)
/// - 配線ロジックContinue → header, Break → afterは P1 で実装
///
/// # Phase 265 P1+ 配線ルール(未実装)
/// - header / latch / after を組む
/// - Continue → header へ戻す
/// - Break → after へ出す
/// - Return/Unwind は上位へ伝搬
///
/// # 引数
/// - `loop_id`: ループ識別子
/// - `loop_id`: ループ識別子P0では未使用、P1で配線時に使用
/// - `header`: ループヘッダー
/// - `body`: ループ本体の断片
///
/// # Phase 264
/// - シグネチャのみ固定、中身は TODO
/// - pub(crate) で外部から触れないようにする
pub(crate) fn loop_(
_loop_id: LoopId,
_header: BasicBlockId,
_body: Frag,
header: BasicBlockId,
body: Frag,
) -> Frag {
todo!("Phase 264: loop_ 合成は次フェーズで実装")
// Phase 265 P0: exit集合の分類だけ配線・変換はP1以降
let mut exits = BTreeMap::new();
// body の全 exit をそのまま伝搬(分類するだけ)
for (kind, stubs) in body.exits.iter() {
// P0: 存在する exit をそのまま記録(変換しない)
exits.insert(*kind, stubs.clone());
}
// P1+: ここで Continue → header, Break → after への配線を追加
Frag {
entry: header, // ループの入口
exits, // 分類された exit 集合
}
}
/// cleanup 合成: finally の後継(すべての exit を正規化)
@ -100,3 +115,75 @@ pub(crate) fn cleanup(
) -> Frag {
todo!("Phase 264: cleanup 合成は次フェーズで実装")
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mir::basic_block::BasicBlockId;
use super::super::exit_kind::ExitKind;
use super::super::edge_stub::EdgeStub;
#[test]
fn test_loop_preserves_exits() {
// Setup: body with Normal and Return exits
let loop_id = LoopId(0);
let header = BasicBlockId(10);
let body_entry = BasicBlockId(20);
let mut body_exits = BTreeMap::new();
body_exits.insert(
ExitKind::Normal,
vec![EdgeStub::without_args(body_entry, ExitKind::Normal)],
);
body_exits.insert(
ExitKind::Return,
vec![EdgeStub::without_args(body_entry, ExitKind::Return)],
);
let body_frag = Frag {
entry: body_entry,
exits: body_exits,
};
// Execute: compose::loop_()
let loop_frag = loop_(loop_id, header, body_frag);
// Verify: entry is header, exits are preserved
assert_eq!(loop_frag.entry, header);
assert_eq!(loop_frag.exits.len(), 2);
assert!(loop_frag.exits.contains_key(&ExitKind::Normal));
assert!(loop_frag.exits.contains_key(&ExitKind::Return));
}
#[test]
fn test_loop_with_break_continue() {
// Setup: body with Break and Continue
let loop_id = LoopId(1);
let header = BasicBlockId(30);
let body_entry = BasicBlockId(40);
let mut body_exits = BTreeMap::new();
body_exits.insert(
ExitKind::Break(loop_id),
vec![EdgeStub::without_args(body_entry, ExitKind::Break(loop_id))],
);
body_exits.insert(
ExitKind::Continue(loop_id),
vec![EdgeStub::without_args(body_entry, ExitKind::Continue(loop_id))],
);
let body_frag = Frag {
entry: body_entry,
exits: body_exits,
};
// Execute: compose::loop_()
let loop_frag = loop_(loop_id, header, body_frag);
// Verify: Break/Continue are preserved (P0 doesn't remap yet)
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)));
}
}

View File

@ -7,22 +7,54 @@
use super::frag::Frag;
/// Frag の不変条件を検証Phase 264: 空実装
/// Frag の不変条件を検証Phase 265 P0: 最小チェック
///
/// # 検証項目TODO
/// - entry は有効な BasicBlockId
/// - 同一 ExitKind に対する EdgeStub.from が一意
/// - EdgeStub.kind と Map のキーが一致
/// - EdgeArgs の layout と values の長さが一致
/// # 検証項目
/// - Phase 265 P0: exits が空でないか(最低限の健全性)
/// - Phase 265 P1+: EdgeStub.from の有効性、edge-args の整合性
///
/// # 戻り値
/// - Ok(()): 検証成功
/// - Err(String): 検証失敗(エラーメッセージ)
///
/// # Phase 264
/// - 空実装(次フェーズで Fail-Fast 検証を追加
/// # Phase 265 P0
/// - デバッグガード付き最小実装(デフォルト出力は汚さない
/// - 不変条件は edgecfg-fragments.md に文書化済み
pub fn verify_frag_invariants(_frag: &Frag) -> Result<(), String> {
// Phase 264: 空実装(次フェーズで検証項目追加
pub fn verify_frag_invariants(frag: &Frag) -> Result<(), String> {
// Phase 265 P0: 最小チェック(デバッグビルドのみ出力
// 1. exits が空でないか(最低限の健全性)
if frag.exits.is_empty() {
#[cfg(debug_assertions)]
eprintln!(
"[verify_frag] Warning: Frag entry={:?} has no exits (dead end?)",
frag.entry
);
}
// 2. entry の有効性(デバッグビルドのみ)
#[cfg(debug_assertions)]
{
if crate::config::env::is_joinir_debug() {
eprintln!(
"[verify_frag] Frag entry={:?}, exits={} kinds",
frag.entry,
frag.exits.len()
);
for (kind, stubs) in &frag.exits {
eprintln!(
"[verify_frag] {:?}: {} stubs",
kind,
stubs.len()
);
}
}
}
// P1+: より厳格な検証を追加
// - EdgeStub.from の有効性実際のブロックID範囲チェック
// - edge-args の長さ一致
// - terminator 語彙との整合性
Ok(())
}