phase29ao(p10): emit block params as phis (valuejoin wiring)

This commit is contained in:
2025-12-30 06:58:50 +09:00
parent a8bdc0e587
commit cd2a7b9fe7
6 changed files with 257 additions and 10 deletions

View File

@ -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 に emitPhase 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);
}
}

View File

@ -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(())
}