From ab1510920cf1bde5cbd355afe6c9f65596e1757a Mon Sep 17 00:00:00 2001 From: tomoaki Date: Sun, 21 Dec 2025 13:07:17 +0900 Subject: [PATCH] =?UTF-8?q?feat(edgecfg):=20Phase=20265=20P0=20-=20compose?= =?UTF-8?q?/verify=20=E6=9C=80=E5=B0=8F=E5=AE=9F=E8=A3=85=EF=BC=88?= =?UTF-8?q?=E5=85=A5=E5=8F=A3SSOT=E8=BF=B7=E5=AD=90=E9=98=B2=E6=AD=A2?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🎯 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- docs/development/current/main/10-Now.md | 18 +++ docs/development/current/main/30-Backlog.md | 11 ++ .../current/main/design/edgecfg-fragments.md | 8 +- .../control_flow/edgecfg/api/compose.rs | 105 ++++++++++++++++-- .../control_flow/edgecfg/api/verify.rs | 52 +++++++-- 5 files changed, 174 insertions(+), 20 deletions(-) diff --git a/docs/development/current/main/10-Now.md b/docs/development/current/main/10-Now.md index bf9b9e4b..80a5c0c9 100644 --- a/docs/development/current/main/10-Now.md +++ b/docs/development/current/main/10-Now.md @@ -37,6 +37,24 @@ **詳細**: `docs/development/current/main/phases/phase-264/README.md` + `docs/development/current/main/design/edgecfg-fragments.md` +## 2025-12-21:Phase 265 P0(compose/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 265(planned): Pattern8 を Frag 合成に移行し、ExitKind+Frag の実装適用を開始(`compose::loop_` 実装) diff --git a/docs/development/current/main/30-Backlog.md b/docs/development/current/main/30-Backlog.md index a8220566..c2287490 100644 --- a/docs/development/current/main/30-Backlog.md +++ b/docs/development/current/main/30-Backlog.md @@ -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 107(find_balanced_array/object / json_cur 由来)まで固定済み。 diff --git a/docs/development/current/main/design/edgecfg-fragments.md b/docs/development/current/main/design/edgecfg-fragments.md index b5d3420f..68fd924d 100644 --- a/docs/development/current/main/design/edgecfg-fragments.md +++ b/docs/development/current/main/design/edgecfg-fragments.md @@ -137,6 +137,12 @@ Frag = { entry_block, exits: Map> } - 合成関数: `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 は未改変(入口だけ用意)。 diff --git a/src/mir/builder/control_flow/edgecfg/api/compose.rs b/src/mir/builder/control_flow/edgecfg/api/compose.rs index 966103d2..77a27338 100644 --- a/src/mir/builder/control_flow/edgecfg/api/compose.rs +++ b/src/mir/builder/control_flow/edgecfg/api/compose.rs @@ -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))); + } +} diff --git a/src/mir/builder/control_flow/edgecfg/api/verify.rs b/src/mir/builder/control_flow/edgecfg/api/verify.rs index e773b292..41fbb689 100644 --- a/src/mir/builder/control_flow/edgecfg/api/verify.rs +++ b/src/mir/builder/control_flow/edgecfg/api/verify.rs @@ -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(()) }