feat(edgecfg): Phase 266 emit_wires PoC (wires→MIR Jump/Return)

This commit is contained in:
2025-12-21 17:20:48 +09:00
parent 21387f3816
commit f8779df5a6
3 changed files with 361 additions and 0 deletions

View 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-FastReturn を除く)
///
/// # 引数
/// - `function`: MIR functionBasicBlock アクセス用)
/// - `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 terminatorPhase 260 ルール: set_jump_with_edge_args を使用)
block.set_jump_with_edge_args(target.unwrap(), Some(stub.args.clone()));
}
ExitKind::Return => {
// Return terminator + metadataPhase 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 wiretarget=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=NoneNormal は 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本の 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 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
);
}
}

View File

@ -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;

View File

@ -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 があったら ErrReturn を除く)
///
/// # 戻り値
/// - 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 がいたら ErrReturn を除く、厳格)
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::*;