feat(edgecfg): Phase 266 emit_wires PoC (wires→MIR Jump/Return)
This commit is contained in:
298
src/mir/builder/control_flow/edgecfg/api/emit.rs
Normal file
298
src/mir/builder/control_flow/edgecfg/api/emit.rs
Normal file
@ -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<BasicBlockId, Vec<&EdgeStub>> = 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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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::*;
|
||||
|
||||
Reference in New Issue
Block a user