diff --git a/src/mir/builder/control_flow/edgecfg/api/emit.rs b/src/mir/builder/control_flow/edgecfg/api/emit.rs new file mode 100644 index 00000000..b4b0fed9 --- /dev/null +++ b/src/mir/builder/control_flow/edgecfg/api/emit.rs @@ -0,0 +1,298 @@ +/*! + * wires → MIR terminator 変換(Phase 266: SSOT) + * + * # 目的 + * - EdgeStub の wires を MIR terminator に変換する唯一の入口 + * - Phase 260 の terminator 語彙ルールを厳守 + * - 1 block = 1 terminator 制約を強制 + * + * # Phase 266 制約 + * - Jump/Return のみ実装(Branch は Phase 267) + * - Return は target=None を許可(意味を持たない) + * - from ごとにグループ化して1本だけ許可 + */ + +use super::edge_stub::EdgeStub; +use super::exit_kind::ExitKind; +use crate::mir::basic_block::BasicBlockId; +use crate::mir::instruction::MirInstruction; +use std::collections::BTreeMap; + +/// wires → MIR terminator 変換(Phase 266 P1: SSOT) +/// +/// # 責務 +/// - EdgeStub の target=Some(...) を MIR terminator に変換 +/// - BasicBlock::set_*_with_edge_args() を使って terminator + successor を同期 +/// - target=None の EdgeStub が混入したら Fail-Fast(Return を除く) +/// +/// # 引数 +/// - `function`: MIR function(BasicBlock アクセス用) +/// - `wires`: 配線済み EdgeStub のリスト(target=Some のみを期待、Return は target=None OK) +/// +/// # 戻り値 +/// - `Ok(())`: 全 wire を MIR terminator に変換成功 +/// - `Err(String)`: target=None の EdgeStub を検出、または不正な kind、または複数 wire +pub fn emit_wires( + function: &mut crate::mir::MirFunction, + wires: &[EdgeStub], +) -> Result<(), String> { + // Step 1: from ごとにグループ化(1 block = 1 terminator 制約) + let mut by_block: BTreeMap> = BTreeMap::new(); + for stub in wires { + by_block.entry(stub.from).or_default().push(stub); + } + + // Step 2: 各 block に対して1本だけ wire を許可 + for (block_id, stubs) in by_block { + if stubs.len() > 1 { + return Err(format!( + "[emit_wires] Multiple wires from same block {:?} (count={}). \ + 1 block = 1 terminator constraint violated.", + block_id, + stubs.len() + )); + } + + let stub = stubs[0]; + + // Fail-Fast: target=None 検出(Return 以外) + let target = match stub.kind { + ExitKind::Return => None, // Return は target 不要 + _ => { + // Normal/Break/Continue/Unwind は target 必須 + Some(stub.target.ok_or_else(|| { + format!( + "[emit_wires] Unwired EdgeStub detected: from={:?}, kind={:?}. \ + Wires (except Return) must have target=Some(...). This is a contract violation.", + stub.from, stub.kind + ) + })?) + } + }; + + // Block 取得 + let block = function + .get_block_mut(stub.from) + .ok_or_else(|| format!("[emit_wires] Block {:?} not found", stub.from))?; + + // ExitKind 別に terminator 生成 + match stub.kind { + ExitKind::Normal | ExitKind::Break(_) | ExitKind::Continue(_) | ExitKind::Unwind => { + // Jump terminator(Phase 260 ルール: set_jump_with_edge_args を使用) + block.set_jump_with_edge_args(target.unwrap(), Some(stub.args.clone())); + } + ExitKind::Return => { + // Return terminator + metadata(Phase 260 例外ルール: set_terminator + set_return_env) + block.set_terminator(MirInstruction::Return { + value: stub.args.values.first().copied(), + }); + block.set_return_env(stub.args.clone()); + } + _ => { + return Err(format!( + "[emit_wires] Unsupported ExitKind: {:?}", + stub.kind + )); + } + } + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::mir::basic_block::EdgeArgs; + use crate::mir::function::{FunctionSignature, MirFunction}; + use crate::mir::join_ir::lowering::inline_boundary::JumpArgsLayout; + use crate::mir::types::MirType; + use crate::mir::{BasicBlock, EffectMask, ValueId}; + + /// テスト用の MirFunction を作成(最小構成) + fn create_test_function() -> MirFunction { + let signature = FunctionSignature { + name: "test_func".to_string(), + params: vec![], + return_type: MirType::Void, + effects: EffectMask::PURE, + }; + let entry_block = BasicBlockId(0); + MirFunction::new(signature, entry_block) + } + + #[test] + fn test_emit_wires_jump_basic() { + // Setup: MirFunction with 2 blocks + let mut function = create_test_function(); + let bb0 = BasicBlockId(0); // entry + let bb1 = BasicBlockId(1); + function.add_block(BasicBlock::new(bb1)); + + // Setup: wire (bb0 → bb1) + let stub = EdgeStub::with_target( + bb0, + ExitKind::Normal, + bb1, + EdgeArgs { + layout: JumpArgsLayout::CarriersOnly, + values: vec![ValueId(100)], + }, + ); + + let wires = vec![stub]; + + // Execute + let result = emit_wires(&mut function, &wires); + + // Verify: success + assert!(result.is_ok(), "emit_wires failed: {:?}", result.err()); + + // Verify: bb0 has Jump terminator + let block0 = function.get_block(bb0).unwrap(); + assert!( + block0.is_terminated(), + "bb0 should have a terminator" + ); + + match &block0.terminator { + Some(MirInstruction::Jump { target, edge_args }) => { + assert_eq!(*target, bb1, "Jump target should be bb1"); + assert!(edge_args.is_some(), "Jump should have edge_args"); + let args = edge_args.as_ref().unwrap(); + assert_eq!( + args.values, + vec![ValueId(100)], + "Edge args values mismatch" + ); + } + other => panic!("Expected Jump terminator, got {:?}", other), + } + + // Verify: successors updated + assert!( + block0.successors.contains(&bb1), + "bb0 successors should contain bb1" + ); + } + + #[test] + fn test_emit_wires_return_basic() { + // Setup: MirFunction with 1 block + let mut function = create_test_function(); + let bb0 = BasicBlockId(0); // entry + + // Setup: Return wire(target=None OK、意味を持たない) + let stub = EdgeStub { + from: bb0, + kind: ExitKind::Return, + target: None, // Return は target 不要(emit_wires で無視される) + args: EdgeArgs { + layout: JumpArgsLayout::CarriersOnly, + values: vec![ValueId(200)], + }, + }; + + let wires = vec![stub]; + + // Execute + let result = emit_wires(&mut function, &wires); + + // Verify: success + assert!(result.is_ok(), "emit_wires failed: {:?}", result.err()); + + // Verify: bb0 has Return terminator + let block0 = function.get_block(bb0).unwrap(); + match &block0.terminator { + Some(MirInstruction::Return { value }) => { + assert_eq!( + *value, + Some(ValueId(200)), + "Return value mismatch" + ); + } + other => panic!("Expected Return terminator, got {:?}", other), + } + + // Verify: return_env set + let return_env = block0.return_env().expect("return_env should be set"); + assert_eq!( + return_env.values, + vec![ValueId(200)], + "return_env values mismatch" + ); + } + + #[test] + fn test_emit_wires_unwired_stub_fails() { + // Setup: EdgeStub with target=None(Normal は target 必須) + let mut function = create_test_function(); + let bb0 = BasicBlockId(0); + + let stub = EdgeStub::without_args(bb0, ExitKind::Normal); + // stub.target = None(未配線) + + let wires = vec![stub]; + + // Execute + let result = emit_wires(&mut function, &wires); + + // Verify: failure + assert!(result.is_err(), "Expected error for unwired Normal stub"); + let err_msg = result.unwrap_err(); + assert!( + err_msg.contains("Unwired EdgeStub"), + "Error message should mention 'Unwired EdgeStub', got: {}", + err_msg + ); + } + + #[test] + fn test_emit_wires_multiple_from_same_block_fails() { + // Setup: 同じ from に2本の wire(1 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 stub1 = EdgeStub { + from: bb0, + kind: ExitKind::Normal, + target: Some(bb1), + args: EdgeArgs { + layout: JumpArgsLayout::CarriersOnly, + values: vec![], + }, + }; + + let stub2 = EdgeStub { + from: bb0, // 同じ from + kind: ExitKind::Normal, + target: Some(bb2), + args: EdgeArgs { + layout: JumpArgsLayout::CarriersOnly, + values: vec![], + }, + }; + + let wires = vec![stub1, stub2]; + + // Execute + let result = emit_wires(&mut function, &wires); + + // Verify: failure + assert!( + result.is_err(), + "Expected error for multiple wires from same block" + ); + let err_msg = result.unwrap_err(); + assert!( + err_msg.contains("Multiple wires from same block"), + "Error message should mention 'Multiple wires from same block', got: {}", + err_msg + ); + } +} + diff --git a/src/mir/builder/control_flow/edgecfg/api/mod.rs b/src/mir/builder/control_flow/edgecfg/api/mod.rs index 7dd45496..0ee73e79 100644 --- a/src/mir/builder/control_flow/edgecfg/api/mod.rs +++ b/src/mir/builder/control_flow/edgecfg/api/mod.rs @@ -20,6 +20,7 @@ pub mod edge_stub; pub mod frag; pub mod compose; pub mod verify; +pub mod emit; // Phase 266: 追加 // 公開型(安定) pub use exit_kind::ExitKind; @@ -31,3 +32,7 @@ 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; diff --git a/src/mir/builder/control_flow/edgecfg/api/verify.rs b/src/mir/builder/control_flow/edgecfg/api/verify.rs index 1c28ad1c..ca58b11a 100644 --- a/src/mir/builder/control_flow/edgecfg/api/verify.rs +++ b/src/mir/builder/control_flow/edgecfg/api/verify.rs @@ -89,6 +89,64 @@ pub fn verify_frag_invariants(frag: &Frag) -> Result<(), String> { Ok(()) } +/// Frag の不変条件を厳格検証(Phase 266: strict 版、警告→Err 化) +/// +/// # 検証項目 +/// - wires/exits 分離契約(厳格) +/// - exits に target=Some があったら Err +/// - wires に target=None があったら Err(Return を除く) +/// +/// # 戻り値 +/// - Ok(()): 検証成功 +/// - Err(String): 検証失敗(エラーメッセージ) +/// +/// # 使用箇所 +/// - Phase 266 の emit_wires() と PoC テストのみ +/// - Phase 267+ で段階的に既存コードへ適用 +/// +/// # Phase 266 の設計判断 +/// - 既存の `verify_frag_invariants()` は警告のまま維持(段階導入を壊さない) +/// - 新規に `verify_frag_invariants_strict()` を追加し、P266 の PoC/emit 側だけ strict を使う +pub fn verify_frag_invariants_strict(frag: &Frag) -> Result<(), String> { + use super::exit_kind::ExitKind; + + // 1. exits と wires の両方が空の場合は警告(非致命的) + if frag.exits.is_empty() && frag.wires.is_empty() { + #[cfg(debug_assertions)] + eprintln!( + "[verify_frag_strict] Warning: Frag entry={:?} has no exits and no wires (dead end?)", + frag.entry + ); + } + + // 2. exits 内に target=Some がいたら Err(厳格) + for (kind, stubs) in &frag.exits { + for stub in stubs { + if stub.target.is_some() { + return Err(format!( + "[verify_frag_strict] Exits[{:?}] contains wired EdgeStub (target={:?}). \ + Wired stubs must be in wires instead.", + kind, stub.target + )); + } + } + } + + // 3. wires 内に target=None がいたら Err(Return を除く、厳格) + for stub in &frag.wires { + // Return は target 不要なので許可 + if stub.target.is_none() && !matches!(stub.kind, ExitKind::Return) { + return Err(format!( + "[verify_frag_strict] Wires contains unwired EdgeStub (from={:?}, kind={:?}). \ + Wires (except Return) must have target=Some(...). This should be in exits instead.", + stub.from, stub.kind + )); + } + } + + Ok(()) +} + #[cfg(test)] mod tests { use super::*;