feat(edgecfg): Phase 267 P0 BranchStub + emit_frag (Branch→MIR)

This commit is contained in:
2025-12-21 20:33:11 +09:00
parent 655a8efbc6
commit bd84fdf78f
6 changed files with 378 additions and 9 deletions

View File

@ -0,0 +1,32 @@
use crate::mir::basic_block::{BasicBlockId, EdgeArgs};
use crate::mir::ValueId;
/// 条件分岐の未配線エッジペアPhase 267 P0
///
/// # 責務
/// - header → then/else の分岐を表現
/// - set_branch_with_edge_args() へのマッピング用
///
/// # 制約
/// - 1 block = 1 terminator: from は重複禁止
/// - then_target, else_target は必ず Some未配線 Branch は存在しない)
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BranchStub {
/// 分岐元ブロック
pub from: BasicBlockId,
/// 分岐条件の値
pub cond: ValueId,
/// then ブランチの配線先
pub then_target: BasicBlockId,
/// then ブランチの引数
pub then_args: EdgeArgs,
/// else ブランチの配線先
pub else_target: BasicBlockId,
/// else ブランチの引数
pub else_args: EdgeArgs,
}

View File

@ -10,12 +10,14 @@
*/
use std::collections::BTreeMap;
use crate::mir::basic_block::BasicBlockId;
use crate::mir::basic_block::{BasicBlockId, EdgeArgs};
use crate::mir::control_form::LoopId;
use crate::mir::value_id::ValueId;
use crate::mir::join_ir::lowering::inline_boundary::JumpArgsLayout;
use super::frag::Frag;
use super::exit_kind::ExitKind;
use super::edge_stub::EdgeStub; // Phase 265 P2: wires/exits 分離で必要
use super::branch_stub::BranchStub; // Phase 267 P0: Branch 生成に必要
/// 順次合成: `a; b`
///
@ -68,37 +70,62 @@ pub(crate) fn seq(a: Frag, b: Frag) -> Frag {
// b の wires もマージ
wires.extend(b.wires);
// Phase 267 P0: branches もマージ
let mut branches = Vec::new();
branches.extend(a.branches);
branches.extend(b.branches);
Frag {
entry: a.entry, // seq の入口は a の入口
exits, // a の非 Normal + b の全 exit
wires, // a.Normal → b.entry + a.wires + b.wires
branches, // Phase 267 P0: a.branches + b.branches
}
}
/// 条件分岐合成: `if (cond) { t } else { e }`
///
/// # Phase 265 P2: wires/exits 分離実装完了
/// # Phase 267 P0: Branch 生成実装完了
/// - header → then/else の BranchStub を branches に追加
/// - t/e.Normal → join_frag.entry を wires に追加(内部配線)
/// - if の exits は join_frag.exitsjoin 以降の外へ出る exit
///
/// # 配線ルール
/// - header → t.entry / e.entry を BranchStub として branches に追加Phase 267 P0
/// - 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
/// - if の branches = header の BranchStub + t/e/join の branches
///
/// # 引数
/// - `header`: 条件判定を行うブロック
/// - `_cond`: 条件値Phase 266+ で MIR 命令生成時に使用
/// - `cond`: 条件値Phase 267 P0 で使用開始
/// - `t`: then 分岐の断片
/// - `e`: else 分岐の断片
/// - `join_frag`: join 以降の断片t/e.Normal の配線先 + join 以降の処理)
pub(crate) fn if_(
header: BasicBlockId,
_cond: ValueId, // Phase 266+ で使用
cond: ValueId, // Phase 267 P0 で使用開始
t: Frag, // then 分岐
e: Frag, // else 分岐
join_frag: Frag, // join 以降の断片
) -> Frag {
// Phase 267 P0: header → then/else の BranchStub を作成
let branch = BranchStub {
from: header,
cond,
then_target: t.entry,
then_args: EdgeArgs {
layout: JumpArgsLayout::CarriersOnly,
values: vec![], // TODO: Phase 267 P2+ で then の入口引数計算
},
else_target: e.entry,
else_args: EdgeArgs {
layout: JumpArgsLayout::CarriersOnly,
values: vec![], // TODO: Phase 267 P2+ で else の入口引数計算
},
};
let mut exits = BTreeMap::new();
let mut wires = Vec::new();
@ -156,10 +183,17 @@ pub(crate) fn if_(
// join_frag の wires もマージ
wires.extend(join_frag.wires);
// Phase 267 P0: branches を統合
let mut branches = vec![branch];
branches.extend(t.branches);
branches.extend(e.branches);
branches.extend(join_frag.branches);
Frag {
entry: header, // if の入口は header
exits, // t/e の非 Normal + join_frag.exits
wires, // t/e.Normal → join_frag.entry + t/e/join の wires
branches, // Phase 267 P0: header の BranchStub + t/e/join の branches
}
}
@ -226,10 +260,14 @@ pub(crate) fn loop_(
// body の wires もマージ
wires.extend(body.wires);
// Phase 267 P0: body の branches もマージ
let branches = body.branches;
Frag {
entry: header, // ループの入口
exits, // Normal, Return, Unwind のみ(未配線)
wires, // Continue → header, Break → after配線済み
branches, // Phase 267 P0: body の branches
}
}
@ -283,6 +321,7 @@ mod tests {
entry: body_entry,
exits: body_exits,
wires: vec![],
branches: vec![],
};
// Execute: compose::loop_()
@ -317,6 +356,7 @@ mod tests {
entry: body_entry,
exits: body_exits,
wires: vec![],
branches: vec![],
};
// Execute: compose::loop_()
@ -366,6 +406,7 @@ mod tests {
entry: body,
exits: body_exits,
wires: vec![],
branches: vec![],
};
// Execute: compose::loop_()
@ -399,6 +440,7 @@ mod tests {
entry: body,
exits: body_exits,
wires: vec![],
branches: vec![],
};
// Execute: compose::loop_()
@ -432,6 +474,7 @@ mod tests {
entry: body,
exits: body_exits,
wires: vec![],
branches: vec![],
};
// Execute: compose::loop_()
@ -461,6 +504,7 @@ mod tests {
entry: a_entry,
exits: a_exits,
wires: vec![],
branches: vec![],
};
let mut b_exits = BTreeMap::new();
@ -472,6 +516,7 @@ mod tests {
entry: b_entry,
exits: b_exits,
wires: vec![],
branches: vec![],
};
// Execute: compose::seq()
@ -514,6 +559,7 @@ mod tests {
entry: a_entry,
exits: a_exits,
wires: vec![],
branches: vec![],
};
let mut b_exits = BTreeMap::new();
@ -525,6 +571,7 @@ mod tests {
entry: b_entry,
exits: b_exits,
wires: vec![],
branches: vec![],
};
// Execute
@ -564,6 +611,7 @@ mod tests {
entry: then_entry,
exits: then_exits,
wires: vec![],
branches: vec![],
};
let mut else_exits = BTreeMap::new();
@ -575,6 +623,7 @@ mod tests {
entry: else_entry,
exits: else_exits,
wires: vec![],
branches: vec![],
};
let mut join_exits = BTreeMap::new();
@ -586,6 +635,7 @@ mod tests {
entry: join_entry,
exits: join_exits,
wires: vec![],
branches: vec![],
};
// Execute: compose::if_()
@ -630,6 +680,7 @@ mod tests {
entry: then_entry,
exits: then_exits,
wires: vec![],
branches: vec![],
};
let mut else_exits = BTreeMap::new();
@ -645,12 +696,14 @@ mod tests {
entry: else_entry,
exits: else_exits,
wires: vec![],
branches: vec![],
};
let join_frag = Frag {
entry: join_entry,
exits: BTreeMap::new(),
wires: vec![],
branches: vec![],
};
// Execute

View File

@ -100,6 +100,80 @@ pub fn emit_wires(
Ok(())
}
/// Frag を MIR に emitPhase 267 P0: SSOT
///
/// # 責務
/// - verify_frag_invariants_strict() で事前検証Fail-Fast
/// - wires → Jump/Return terminatoremit_wires を呼ぶ)
/// - branches → Branch terminatorset_branch_with_edge_args を使う)
/// - 1 block = 1 terminator 制約を強制
///
/// # 引数
/// - `function`: MIR function
/// - `frag`: 配線済み Frag
///
/// # 戻り値
/// - `Ok(())`: 成功
/// - `Err(String)`: 同一 block に複数 terminator、または不正な配線
pub fn emit_frag(
function: &mut crate::mir::MirFunction,
frag: &super::frag::Frag,
) -> Result<(), String> {
use super::branch_stub::BranchStub;
// Step 0: verify_frag_invariants_strict() で事前検証SSOT
super::verify::verify_frag_invariants_strict(frag)?;
// Step 1: branches を from ごとにグループ化1本だけ許可
let mut branches_by_block: BTreeMap<BasicBlockId, Vec<&BranchStub>> = BTreeMap::new();
for branch in &frag.branches {
branches_by_block.entry(branch.from).or_default().push(branch);
}
for (block_id, branches) in &branches_by_block {
if branches.len() > 1 {
return Err(format!(
"[emit_frag] Multiple branches from same block {:?} (count={}). \
1 block = 1 terminator constraint violated.",
block_id,
branches.len()
));
}
}
// Step 2: wires と branches の from 重複チェック1 block = 1 terminator
for wire in &frag.wires {
if branches_by_block.contains_key(&wire.from) {
return Err(format!(
"[emit_frag] Block {:?} has both wire and branch. \
1 block = 1 terminator constraint violated.",
wire.from
));
}
}
// Step 3: wires を emit既存の emit_wires を呼ぶ)
emit_wires(function, &frag.wires)?;
// Step 4: branches を emit
for branch in &frag.branches {
let block = function
.get_block_mut(branch.from)
.ok_or_else(|| format!("[emit_frag] Block {:?} not found", branch.from))?;
// Phase 260 API を使用terminator + successors 同期)
block.set_branch_with_edge_args(
branch.cond,
branch.then_target,
Some(branch.then_args.clone()),
branch.else_target,
Some(branch.else_args.clone()),
);
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
@ -294,5 +368,200 @@ mod tests {
err_msg
);
}
// ========================================================================
// Phase 267 P0: emit_frag() テスト3個
// ========================================================================
#[test]
fn test_emit_frag_branch_basic() {
use super::super::branch_stub::BranchStub;
use super::super::frag::Frag;
use std::collections::BTreeMap;
// Setup: MirFunction with 3 blocks (header, then, else)
let mut function = create_test_function();
let header = BasicBlockId(0);
let then_bb = BasicBlockId(1);
let else_bb = BasicBlockId(2);
function.add_block(BasicBlock::new(then_bb));
function.add_block(BasicBlock::new(else_bb));
// Setup: BranchStub (header → then/else)
let branch = BranchStub {
from: header,
cond: ValueId(100),
then_target: then_bb,
then_args: EdgeArgs {
layout: JumpArgsLayout::CarriersOnly,
values: vec![ValueId(101)],
},
else_target: else_bb,
else_args: EdgeArgs {
layout: JumpArgsLayout::CarriersOnly,
values: vec![ValueId(102)],
},
};
let frag = Frag {
entry: header,
exits: BTreeMap::new(),
wires: vec![],
branches: vec![branch],
};
// Execute
let result = emit_frag(&mut function, &frag);
// Verify: success
assert!(result.is_ok(), "emit_frag failed: {:?}", result.err());
// Verify: header has Branch terminator
let block = function.get_block(header).unwrap();
match &block.terminator {
Some(MirInstruction::Branch {
condition,
then_bb: t,
else_bb: e,
then_edge_args,
else_edge_args,
}) => {
assert_eq!(*condition, ValueId(100));
assert_eq!(*t, then_bb);
assert_eq!(*e, else_bb);
assert!(then_edge_args.is_some());
assert!(else_edge_args.is_some());
assert_eq!(
then_edge_args.as_ref().unwrap().values,
vec![ValueId(101)]
);
assert_eq!(
else_edge_args.as_ref().unwrap().values,
vec![ValueId(102)]
);
}
other => panic!("Expected Branch, got {:?}", other),
}
// Verify: successors updated
assert!(block.successors.contains(&then_bb));
assert!(block.successors.contains(&else_bb));
}
#[test]
fn test_emit_frag_branch_wire_conflict_fails() {
use super::super::branch_stub::BranchStub;
use super::super::frag::Frag;
use std::collections::BTreeMap;
// Setup: 同じ block に branch と wire1 block = 1 terminator 違反)
let mut function = create_test_function();
let bb0 = BasicBlockId(0);
let bb1 = BasicBlockId(1);
let bb2 = BasicBlockId(2);
function.add_block(BasicBlock::new(bb1));
function.add_block(BasicBlock::new(bb2));
let branch = BranchStub {
from: bb0,
cond: ValueId(100),
then_target: bb1,
then_args: EdgeArgs {
layout: JumpArgsLayout::CarriersOnly,
values: vec![],
},
else_target: bb2,
else_args: EdgeArgs {
layout: JumpArgsLayout::CarriersOnly,
values: vec![],
},
};
let wire = EdgeStub::with_target(
bb0, // 同じ from
ExitKind::Normal,
bb1,
EdgeArgs {
layout: JumpArgsLayout::CarriersOnly,
values: vec![],
},
);
let frag = Frag {
entry: bb0,
exits: BTreeMap::new(),
wires: vec![wire],
branches: vec![branch],
};
// Execute
let result = emit_frag(&mut function, &frag);
// Verify: failure
assert!(result.is_err());
assert!(result.unwrap_err().contains("both wire and branch"));
}
#[test]
fn test_compose_if_creates_branch() {
use super::super::compose::if_;
use super::super::frag::Frag;
use std::collections::BTreeMap;
// Setup: header, then, else, join blocks
let header = BasicBlockId(0);
let then_entry = BasicBlockId(1);
let else_entry = BasicBlockId(2);
let join_entry = BasicBlockId(3);
let then_frag = Frag {
entry: then_entry,
exits: {
let mut exits = BTreeMap::new();
exits.insert(
ExitKind::Normal,
vec![EdgeStub::without_args(then_entry, ExitKind::Normal)],
);
exits
},
wires: vec![],
branches: vec![],
};
let else_frag = Frag {
entry: else_entry,
exits: {
let mut exits = BTreeMap::new();
exits.insert(
ExitKind::Normal,
vec![EdgeStub::without_args(else_entry, ExitKind::Normal)],
);
exits
},
wires: vec![],
branches: vec![],
};
let join_frag = Frag {
entry: join_entry,
exits: BTreeMap::new(),
wires: vec![],
branches: vec![],
};
let cond = ValueId(100);
// Execute
let result = if_(header, cond, then_frag, else_frag, join_frag);
// Verify: 1本の BranchStub が生成された
assert_eq!(result.branches.len(), 1);
let branch = &result.branches[0];
assert_eq!(branch.from, header);
assert_eq!(branch.cond, cond);
assert_eq!(branch.then_target, then_entry);
assert_eq!(branch.else_target, else_entry);
}
}

View File

@ -9,13 +9,15 @@ use std::collections::BTreeMap;
use crate::mir::basic_block::BasicBlockId;
use super::exit_kind::ExitKind;
use super::edge_stub::EdgeStub;
use super::branch_stub::BranchStub;
/// CFG Fragment構造化制御の合成単位
///
/// # 責務Phase 265 P2 更新)
/// # 責務Phase 267 P0 更新)
/// - `entry`: 断片の入口ブロック
/// - `exits`: 断片から外へ出る未配線 edge の集合target = None のみ)
/// - `wires`: 断片内部で解決された配線target = Some(...) のみ)
/// - `wires`: 断片内部で解決された配線target = Some(...) のみ、Jump/Return 専用
/// - `branches`: 断片内部の Branch 配線Phase 267 P0 追加、Branch 専用)
///
/// # 設計原則
/// - 各 Frag は「入口1つ、出口複数種別ごと」を持つ
@ -25,7 +27,8 @@ use super::edge_stub::EdgeStub;
/// # 不変条件verify で検証)
/// - entry は有効な BasicBlockId
/// - exits 内の EdgeStub は target = None未配線、外へ出る exit
/// - wires 内の EdgeStub は target = Some(...)(配線済み、内部配線)
/// - wires 内の EdgeStub は target = Some(...)(配線済み、内部配線、Jump/Return のみ
/// - branches 内の BranchStub は Branch 専用配線Phase 267 P0
/// - EdgeStub.kind と Map のキーが一致
///
/// # BTreeMap の使用理由
@ -46,7 +49,14 @@ pub struct Frag {
///
/// target = Some(...) のみ(断片内部で解決された配線)
/// Phase 266 で MIR terminator に落とす
/// Jump/Return 専用Branch は branches フィールドへ)
pub wires: Vec<EdgeStub>,
/// 配線済みの分岐Phase 267 P0 追加)
///
/// Branch 専用の配線
/// wires は Jump/Return 専用のまま維持(分離を保つ)
pub branches: Vec<BranchStub>,
}
impl Frag {
@ -56,6 +66,7 @@ impl Frag {
entry,
exits: BTreeMap::new(),
wires: vec![], // Phase 265 P2: 配線済み内部配線
branches: vec![], // Phase 267 P0: 配線済み分岐
}
}
@ -67,6 +78,7 @@ impl Frag {
entry,
exits,
wires: vec![], // Phase 265 P2: 配線済み内部配線
branches: vec![], // Phase 267 P0: 配線済み分岐
}
}

View File

@ -21,11 +21,13 @@ pub mod frag;
pub mod compose;
pub mod verify;
pub mod emit; // Phase 266: 追加
pub mod branch_stub; // Phase 267 P0: 追加
// 公開型(安定)
pub use exit_kind::ExitKind;
pub use edge_stub::EdgeStub;
pub use frag::Frag;
pub use branch_stub::BranchStub; // Phase 267 P0: 追加
// 合成関数Phase 264: crate内のみ公開、Phase 265+でpub化
pub(crate) use compose::{seq, if_, loop_, cleanup};
@ -34,5 +36,5 @@ pub(crate) use compose::{seq, if_, loop_, cleanup};
pub use verify::verify_frag_invariants;
pub use verify::verify_frag_invariants_strict; // Phase 266: strict 版追加
// Phase 266: wires → MIR terminator 変換
pub use emit::emit_wires;
// Phase 267 P0: wires + branches → MIR terminator 変換
pub use emit::{emit_wires, emit_frag};

View File

@ -163,6 +163,7 @@ mod tests {
entry: header,
exits,
wires: vec![],
branches: vec![],
};
// P1: Always returns Ok(())