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:
@ -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_` 実装)
|
||||
|
||||
@ -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 由来)まで固定済み。
|
||||
|
||||
@ -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 は未改変(入口だけ用意)。
|
||||
|
||||
|
||||
@ -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)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -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(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user