phase29ao(p10): emit block params as phis (valuejoin wiring)
This commit is contained in:
@ -100,6 +100,97 @@ pub fn emit_wires(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn emit_block_params_as_phis(
|
||||
function: &mut crate::mir::MirFunction,
|
||||
frag: &super::frag::Frag,
|
||||
) -> Result<(), String> {
|
||||
use crate::ast::Span;
|
||||
use crate::mir::basic_block::{BasicBlockId, EdgeArgs};
|
||||
use crate::mir::ssot::cf_common::insert_phi_at_head_spanned;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
if frag.block_params.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let strict = crate::config::env::joinir_strict_enabled()
|
||||
|| crate::config::env::joinir_dev_enabled();
|
||||
|
||||
let mut incoming: BTreeMap<BasicBlockId, Vec<(BasicBlockId, EdgeArgs)>> = BTreeMap::new();
|
||||
for stub in &frag.wires {
|
||||
if let Some(target) = stub.target {
|
||||
incoming
|
||||
.entry(target)
|
||||
.or_default()
|
||||
.push((stub.from, stub.args.clone()));
|
||||
}
|
||||
}
|
||||
for branch in &frag.branches {
|
||||
incoming
|
||||
.entry(branch.then_target)
|
||||
.or_default()
|
||||
.push((branch.from, branch.then_args.clone()));
|
||||
incoming
|
||||
.entry(branch.else_target)
|
||||
.or_default()
|
||||
.push((branch.from, branch.else_args.clone()));
|
||||
}
|
||||
|
||||
for (target, params) in &frag.block_params {
|
||||
let edges = incoming.get(target).cloned().unwrap_or_default();
|
||||
if edges.is_empty() {
|
||||
if strict {
|
||||
return Err(format!(
|
||||
"[emit_frag] BlockParams target {:?} has no incoming edges",
|
||||
target
|
||||
));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if strict {
|
||||
let mut seen = BTreeSet::new();
|
||||
for (pred, _) in &edges {
|
||||
if !seen.insert(*pred) {
|
||||
return Err(format!(
|
||||
"[emit_frag] Duplicate incoming edge {:?}->{:?}",
|
||||
pred, target
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (index, dst) in params.params.iter().enumerate() {
|
||||
let mut inputs = Vec::with_capacity(edges.len());
|
||||
for (pred, args) in &edges {
|
||||
match args.values.get(index) {
|
||||
Some(value) => inputs.push((*pred, *value)),
|
||||
None => {
|
||||
if strict {
|
||||
return Err(format!(
|
||||
"[emit_frag] Missing edge arg for block_params {:?} index {}",
|
||||
target, index
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if inputs.is_empty() {
|
||||
if strict {
|
||||
return Err(format!(
|
||||
"[emit_frag] BlockParams target {:?} has no inputs for index {}",
|
||||
target, index
|
||||
));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
insert_phi_at_head_spanned(function, *target, *dst, inputs, Span::unknown());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Frag を MIR に emit(Phase 267 P0: SSOT)
|
||||
///
|
||||
/// # 責務
|
||||
@ -124,6 +215,9 @@ pub fn emit_frag(
|
||||
// Step 0: verify_frag_invariants_strict() で事前検証(SSOT)
|
||||
super::verify::verify_frag_invariants_strict(frag)?;
|
||||
|
||||
// Step 0.5: block_params → PHI 挿入(ValueJoin wiring SSOT)
|
||||
emit_block_params_as_phis(function, frag)?;
|
||||
|
||||
// Step 1: branches を from ごとにグループ化(1本だけ許可)
|
||||
let mut branches_by_block: BTreeMap<BasicBlockId, Vec<&BranchStub>> = BTreeMap::new();
|
||||
for branch in &frag.branches {
|
||||
@ -449,6 +543,86 @@ mod tests {
|
||||
assert!(block.successors.contains(&else_bb));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_emit_frag_block_params_inserts_phi() {
|
||||
use super::super::block_params::BlockParams;
|
||||
use super::super::frag::Frag;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
let mut function = create_test_function();
|
||||
let pred1 = BasicBlockId(1);
|
||||
let pred2 = BasicBlockId(2);
|
||||
let target = BasicBlockId(3);
|
||||
function.add_block(BasicBlock::new(pred1));
|
||||
function.add_block(BasicBlock::new(pred2));
|
||||
function.add_block(BasicBlock::new(target));
|
||||
|
||||
let args1 = EdgeArgs {
|
||||
layout: JumpArgsLayout::ExprResultPlusCarriers,
|
||||
values: vec![ValueId(10), ValueId(11)],
|
||||
};
|
||||
let args2 = EdgeArgs {
|
||||
layout: JumpArgsLayout::ExprResultPlusCarriers,
|
||||
values: vec![ValueId(12), ValueId(13)],
|
||||
};
|
||||
|
||||
let wires = vec![
|
||||
EdgeStub {
|
||||
from: pred1,
|
||||
kind: ExitKind::Normal,
|
||||
target: Some(target),
|
||||
args: args1,
|
||||
},
|
||||
EdgeStub {
|
||||
from: pred2,
|
||||
kind: ExitKind::Normal,
|
||||
target: Some(target),
|
||||
args: args2,
|
||||
},
|
||||
];
|
||||
|
||||
let mut block_params = BTreeMap::new();
|
||||
block_params.insert(
|
||||
target,
|
||||
BlockParams {
|
||||
layout: JumpArgsLayout::ExprResultPlusCarriers,
|
||||
params: vec![ValueId(100), ValueId(101)],
|
||||
},
|
||||
);
|
||||
|
||||
let frag = Frag {
|
||||
entry: pred1,
|
||||
block_params,
|
||||
exits: BTreeMap::new(),
|
||||
wires,
|
||||
branches: vec![],
|
||||
};
|
||||
|
||||
emit_frag(&mut function, &frag).expect("emit_frag should succeed");
|
||||
|
||||
let block = function.get_block(target).unwrap();
|
||||
match &block.instructions[0] {
|
||||
MirInstruction::Phi { dst, inputs, .. } => {
|
||||
assert_eq!(*dst, ValueId(100));
|
||||
assert_eq!(
|
||||
inputs,
|
||||
&vec![(pred1, ValueId(10)), (pred2, ValueId(12))]
|
||||
);
|
||||
}
|
||||
other => panic!("Expected Phi at head, got {:?}", other),
|
||||
}
|
||||
match &block.instructions[1] {
|
||||
MirInstruction::Phi { dst, inputs, .. } => {
|
||||
assert_eq!(*dst, ValueId(101));
|
||||
assert_eq!(
|
||||
inputs,
|
||||
&vec![(pred1, ValueId(11)), (pred2, ValueId(13))]
|
||||
);
|
||||
}
|
||||
other => panic!("Expected second Phi, got {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_emit_frag_branch_wire_conflict_fails() {
|
||||
use super::super::branch_stub::BranchStub;
|
||||
@ -583,4 +757,3 @@ mod tests {
|
||||
assert_eq!(branch.else_target, else_entry);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
*/
|
||||
|
||||
use super::frag::Frag;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
/// Frag の不変条件を検証(Phase 265 P2: wires/exits 分離契約)
|
||||
///
|
||||
@ -112,6 +113,7 @@ pub fn verify_frag_invariants_strict(frag: &Frag) -> Result<(), String> {
|
||||
use super::exit_kind::ExitKind;
|
||||
use crate::mir::basic_block::{BasicBlockId, EdgeArgs};
|
||||
use crate::mir::join_ir::lowering::inline_boundary::JumpArgsLayout;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
// 1. exits と wires の両方が空の場合は警告(非致命的)
|
||||
if frag.exits.is_empty() && frag.wires.is_empty() {
|
||||
@ -147,7 +149,10 @@ pub fn verify_frag_invariants_strict(frag: &Frag) -> Result<(), String> {
|
||||
}
|
||||
}
|
||||
|
||||
if crate::config::env::joinir_strict_enabled() || crate::config::env::joinir_dev_enabled() {
|
||||
let strict = crate::config::env::joinir_strict_enabled()
|
||||
|| crate::config::env::joinir_dev_enabled();
|
||||
|
||||
if strict {
|
||||
let check_edge_args = |target: Option<BasicBlockId>,
|
||||
args: &EdgeArgs,
|
||||
context: &str|
|
||||
@ -194,6 +199,66 @@ pub fn verify_frag_invariants_strict(frag: &Frag) -> Result<(), String> {
|
||||
}
|
||||
}
|
||||
|
||||
if strict && !frag.block_params.is_empty() {
|
||||
let mut incoming: BTreeMap<BasicBlockId, Vec<(BasicBlockId, &EdgeArgs)>> = BTreeMap::new();
|
||||
for stub in &frag.wires {
|
||||
if let Some(target) = stub.target {
|
||||
incoming
|
||||
.entry(target)
|
||||
.or_default()
|
||||
.push((stub.from, &stub.args));
|
||||
}
|
||||
}
|
||||
for branch in &frag.branches {
|
||||
incoming
|
||||
.entry(branch.then_target)
|
||||
.or_default()
|
||||
.push((branch.from, &branch.then_args));
|
||||
incoming
|
||||
.entry(branch.else_target)
|
||||
.or_default()
|
||||
.push((branch.from, &branch.else_args));
|
||||
}
|
||||
|
||||
for (target, params) in &frag.block_params {
|
||||
let Some(edges) = incoming.get(target) else {
|
||||
return Err(format!(
|
||||
"[verify_frag_strict] BlockParams target {:?} has no incoming edges",
|
||||
target
|
||||
));
|
||||
};
|
||||
if edges.is_empty() {
|
||||
return Err(format!(
|
||||
"[verify_frag_strict] BlockParams target {:?} has no incoming edges",
|
||||
target
|
||||
));
|
||||
}
|
||||
let mut seen = BTreeSet::new();
|
||||
for (pred, args) in edges {
|
||||
if !seen.insert(*pred) {
|
||||
return Err(format!(
|
||||
"[verify_frag_strict] Duplicate incoming edge {:?}->{:?}",
|
||||
pred, target
|
||||
));
|
||||
}
|
||||
if args.layout != params.layout {
|
||||
return Err(format!(
|
||||
"[verify_frag_strict] BlockParams layout mismatch at {:?} (block={:?}, edge={:?})",
|
||||
target, params.layout, args.layout
|
||||
));
|
||||
}
|
||||
if args.values.len() != params.params.len() {
|
||||
return Err(format!(
|
||||
"[verify_frag_strict] BlockParams length mismatch at {:?} (params={}, args={})",
|
||||
target,
|
||||
params.params.len(),
|
||||
args.values.len()
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user