refactor(mir): phase260 p0 edge-args plumbing (strangler) + ssot api + docs

This commit is contained in:
2025-12-21 04:34:22 +09:00
parent 4496b6243d
commit 4dfe3349bf
42 changed files with 1044 additions and 187 deletions

View File

@ -6,6 +6,7 @@
use super::{EffectMask, MirInstruction, SpannedInstRef, SpannedInstruction, ValueId};
use crate::ast::Span;
use crate::mir::join_ir::lowering::inline_boundary::JumpArgsLayout;
use crate::runtime::get_global_ring0;
use std::collections::BTreeSet; // Phase 69-3: HashSet → BTreeSet for determinism
use std::fmt;
@ -42,6 +43,20 @@ impl fmt::Display for BasicBlockId {
}
}
/// Edge arguments for CFG edges (Phase 260 P0)
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EdgeArgs {
pub layout: JumpArgsLayout,
pub values: Vec<ValueId>,
}
/// Outgoing edge from a basic block
#[derive(Debug, Clone)]
pub struct OutEdge {
pub target: BasicBlockId,
pub args: Option<EdgeArgs>,
}
/// A basic block in SSA form
#[derive(Debug, Clone)]
pub struct BasicBlock {
@ -82,6 +97,8 @@ pub struct BasicBlock {
/// all the Jump args (not just the first one) so that exit PHI can correctly
/// merge carrier values from multiple exit paths.
pub jump_args: Option<Vec<ValueId>>,
/// Phase 260 P0: Layout for legacy jump_args (for consistency checks)
pub jump_args_layout: Option<JumpArgsLayout>,
}
impl BasicBlock {
@ -99,6 +116,7 @@ impl BasicBlock {
reachable: false,
sealed: false,
jump_args: None, // Phase 246-EX: No jump args by default
jump_args_layout: None, // Phase 260 P0: Unknown by default
}
}
@ -161,18 +179,23 @@ impl BasicBlock {
/// Update successors based on the terminator instruction
fn update_successors_from_terminator(&mut self) {
self.successors.clear();
self.successors = self.successors_from_terminator();
}
/// Compute successors from the terminator (SSOT for CFG verification)
pub fn successors_from_terminator(&self) -> BTreeSet<BasicBlockId> {
let mut successors = BTreeSet::new();
if let Some(ref terminator) = self.terminator {
match terminator {
MirInstruction::Branch {
then_bb, else_bb, ..
} => {
self.successors.insert(*then_bb);
self.successors.insert(*else_bb);
successors.insert(*then_bb);
successors.insert(*else_bb);
}
MirInstruction::Jump { target } => {
self.successors.insert(*target);
MirInstruction::Jump { target, .. } => {
successors.insert(*target);
}
MirInstruction::Return { .. } => {
// No successors for return
@ -184,6 +207,140 @@ impl BasicBlock {
_ => unreachable!("Non-terminator instruction in terminator position"),
}
}
successors
}
/// Enumerate all outgoing CFG edges
pub fn out_edges(&self) -> Vec<OutEdge> {
match self.terminator {
Some(MirInstruction::Branch {
then_bb,
else_bb,
ref then_edge_args,
ref else_edge_args,
..
}) => vec![
OutEdge {
target: then_bb,
args: then_edge_args.clone(),
},
OutEdge {
target: else_bb,
args: else_edge_args.clone(),
},
],
Some(MirInstruction::Jump {
target,
ref edge_args,
..
}) => vec![OutEdge {
target,
args: edge_args.clone().or_else(|| self.legacy_edge_args()),
}],
_ => Vec::new(),
}
}
/// Get edge args for a specific target (if present)
pub fn edge_args_to(&self, target: BasicBlockId) -> Option<EdgeArgs> {
self.out_edges()
.into_iter()
.find(|edge| edge.target == target)
.and_then(|edge| edge.args)
}
/// Set legacy jump args metadata (migration helper)
pub fn set_legacy_jump_args(&mut self, values: Vec<ValueId>, layout: Option<JumpArgsLayout>) {
self.jump_args = Some(values);
self.jump_args_layout = layout;
}
/// Clear legacy jump args metadata (migration helper)
pub fn clear_legacy_jump_args(&mut self) {
self.jump_args = None;
self.jump_args_layout = None;
}
/// Check if legacy jump args metadata exists
pub fn has_legacy_jump_args(&self) -> bool {
self.jump_args.is_some()
}
/// Access legacy jump args values (read-only, migration helper)
pub fn legacy_jump_args_values(&self) -> Option<&[ValueId]> {
self.jump_args.as_deref()
}
/// Access legacy jump args layout (read-only, migration helper)
pub fn legacy_jump_args_layout(&self) -> Option<JumpArgsLayout> {
self.jump_args_layout
}
/// Build edge args from legacy values with an explicit layout (migration helper)
pub fn legacy_edge_args_with_layout(&self, layout: JumpArgsLayout) -> Option<EdgeArgs> {
self.jump_args.as_ref().map(|values| EdgeArgs {
layout,
values: values.clone(),
})
}
/// Set jump terminator with edge args and legacy metadata (SSOT write helper)
pub fn set_jump_with_edge_args(&mut self, target: BasicBlockId, edge_args: Option<EdgeArgs>) {
let terminator = MirInstruction::Jump {
target,
edge_args: edge_args.clone(),
};
if !self.is_terminator(&terminator) {
panic!("Instruction is not a valid terminator: {:?}", terminator);
}
self.effects = self.effects | terminator.effects();
self.terminator = Some(terminator);
self.terminator_span = Some(Span::unknown());
if let Some(args) = edge_args {
self.jump_args = Some(args.values.clone());
self.jump_args_layout = Some(args.layout);
} else {
self.clear_legacy_jump_args();
}
self.update_successors_from_terminator();
}
/// Set branch terminator with per-edge args (clears legacy metadata)
pub fn set_branch_with_edge_args(
&mut self,
condition: ValueId,
then_bb: BasicBlockId,
then_edge_args: Option<EdgeArgs>,
else_bb: BasicBlockId,
else_edge_args: Option<EdgeArgs>,
) {
let terminator = MirInstruction::Branch {
condition,
then_bb,
else_bb,
then_edge_args,
else_edge_args,
};
if !self.is_terminator(&terminator) {
panic!("Instruction is not a valid terminator: {:?}", terminator);
}
self.effects = self.effects | terminator.effects();
self.terminator = Some(terminator);
self.terminator_span = Some(Span::unknown());
self.clear_legacy_jump_args();
self.update_successors_from_terminator();
}
fn legacy_edge_args(&self) -> Option<EdgeArgs> {
let values = self.jump_args.as_ref()?;
let layout = self.jump_args_layout?;
Some(EdgeArgs {
layout,
values: values.clone(),
})
}
/// Add a predecessor
@ -540,6 +697,8 @@ mod tests {
condition: ValueId::new(0),
then_bb,
else_bb,
then_edge_args: None,
else_edge_args: None,
};
bb.add_instruction(branch_inst);

View File

@ -670,7 +670,7 @@ impl MirBuilder {
MirInstruction::Branch {
then_bb, else_bb, ..
} => (Some(*then_bb), Some(*else_bb), None),
MirInstruction::Jump { target } => (None, None, Some(*target)),
MirInstruction::Jump { target, .. } => (None, None, Some(*target)),
_ => (None, None, None),
};
@ -784,8 +784,9 @@ impl MirBuilder {
condition,
then_bb,
else_bb,
..
} => format!("br {}, {}, {}", condition, then_bb, else_bb),
MirInstruction::Jump { target } => format!("br {}", target),
MirInstruction::Jump { target, .. } => format!("br {}", target),
_ => format!("{:?}", instruction),
}
);

View File

@ -0,0 +1,14 @@
//! Entry function helpers (SSOT)
use crate::mir::join_ir::{JoinFunction, JoinModule};
/// Extract entry function from JoinModule (SSOT).
///
/// Priority: `join_module.entry` → fallback to `"main"`.
pub(in crate::mir::builder) fn get_entry_function<'a>(
join_module: &'a JoinModule,
context: &str,
) -> Result<&'a JoinFunction, String> {
// Re-exported from patterns/common as the SSOT entry point.
super::super::patterns::common::get_entry_function(join_module, context)
}

View File

@ -0,0 +1,14 @@
//! JoinIR integration API (SSOT entry points)
//!
//! Purpose: provide a small, stable surface for pattern lowerers and merge code.
//! This reduces "where should I call this from?" drift and avoids re-implementing
//! contract logic (SSOT, fail-fast checks) in each pattern.
//!
//! Policy:
//! - Prefer SSOT helpers over ad-hoc logic in patterns.
//! - Avoid guessing (order/layout/name) in callers; callers pass explicit intent.
//! - Keep this module thin: mostly wrappers/re-exports with clear naming.
pub(in crate::mir::builder) mod entry;
pub(in crate::mir::builder) mod pipeline_contracts;
pub(in crate::mir::builder) mod receiver;

View File

@ -0,0 +1,16 @@
//! Pipeline contract helpers (SSOT)
use crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary;
use crate::mir::join_ir::JoinModule;
/// Run all JoinIR pipeline contract checks (SSOT).
///
/// This is the preferred entry point for patterns that need to validate boundary
/// assumptions before bridge/merge.
pub(in crate::mir::builder) fn run_all_pipeline_checks(
join_module: &JoinModule,
boundary: &JoinInlineBoundary,
) -> Result<(), String> {
super::super::merge::contract_checks::run_all_pipeline_checks(join_module, boundary)
}

View File

@ -0,0 +1,20 @@
//! Receiver helpers (SSOT)
//!
//! Motivation: surface syntax uses `this` / `me`, but the MIR builder's `variable_map`
//! registers the receiver under a specific key. Patterns must not guess that key.
use crate::mir::builder::MirBuilder;
use crate::mir::ValueId;
/// Return the host ValueId for `me` receiver (SSOT).
///
/// Patterns should use this instead of hardcoding `"me"` / `"this"`.
pub(in crate::mir::builder) fn get_me_host_id(builder: &MirBuilder, context: &str) -> Result<ValueId, String> {
builder
.variable_ctx
.variable_map
.get("me")
.copied()
.ok_or_else(|| format!("[{}] receiver 'me' not found in variable_map", context))
}

View File

@ -33,7 +33,7 @@ pub(super) fn verify_all_terminator_targets_exist(
let Some(term) = &block.terminator else { continue };
match term {
MirInstruction::Jump { target } => {
MirInstruction::Jump { target, .. } => {
if !func.blocks.contains_key(target)
&& !contracts.allowed_missing_jump_targets.contains(target)
{
@ -749,13 +749,21 @@ mod tests {
let bb2 = BasicBlockId(2);
let func = create_test_function(vec![
(bb0, Some(MirInstruction::Jump { target: bb1 })),
(
bb0,
Some(MirInstruction::Jump {
target: bb1,
edge_args: None,
}),
),
(
bb1,
Some(MirInstruction::Branch {
condition: ValueId(0),
then_bb: bb2,
else_bb: bb2,
then_edge_args: None,
else_edge_args: None,
}),
),
(bb2, Some(MirInstruction::Return { value: None })),
@ -775,7 +783,13 @@ mod tests {
let bb0 = BasicBlockId(0);
let bb99 = BasicBlockId(99); // Missing block
let func = create_test_function(vec![(bb0, Some(MirInstruction::Jump { target: bb99 }))]);
let func = create_test_function(vec![(
bb0,
Some(MirInstruction::Jump {
target: bb99,
edge_args: None,
}),
)]);
let contracts = MergeContracts {
allowed_missing_jump_targets: vec![],
@ -795,7 +809,13 @@ mod tests {
let bb0 = BasicBlockId(0);
let bb_exit = BasicBlockId(100); // Missing but allowed
let func = create_test_function(vec![(bb0, Some(MirInstruction::Jump { target: bb_exit }))]);
let func = create_test_function(vec![(
bb0,
Some(MirInstruction::Jump {
target: bb_exit,
edge_args: None,
}),
)]);
let contracts = MergeContracts {
allowed_missing_jump_targets: vec![bb_exit],

View File

@ -391,7 +391,7 @@ pub(super) fn merge_and_rewrite(
// We skip merging continuation functions, so any tail-call to k_exit must be
// lowered as an exit jump to `exit_block_id` (and contribute exit values).
let mut k_exit_lowering_decision: Option<LoweringDecision> = None;
let mut k_exit_jump_args: Option<Vec<ValueId>> = None;
let mut k_exit_edge_args: Option<crate::mir::EdgeArgs> = None;
// Phase 177-3: Check if this block is the loop header with PHI nodes
let is_loop_header_with_phi =
@ -488,7 +488,10 @@ pub(super) fn merge_and_rewrite(
if let Some(decision) = tail_call_policy.classify_tail_call(callee_name, &remapped_args) {
// This is a k_exit tail call - policy says normalize to exit jump
k_exit_lowering_decision = Some(decision);
k_exit_jump_args = old_block.jump_args.clone();
if let Some(b) = boundary {
k_exit_edge_args =
old_block.legacy_edge_args_with_layout(b.jump_args_layout);
}
found_tail_call = true;
if debug {
log!(
@ -554,6 +557,8 @@ pub(super) fn merge_and_rewrite(
condition,
then_bb,
else_bb,
then_edge_args,
else_edge_args,
} => {
let remapped_then = skipped_entry_redirects
.get(&then_bb)
@ -569,6 +574,8 @@ pub(super) fn merge_and_rewrite(
condition,
then_bb: remapped_then,
else_bb: remapped_else,
then_edge_args,
else_edge_args,
}
}
MirInstruction::Phi { dst, inputs, type_hint } => {
@ -950,8 +957,9 @@ pub(super) fn merge_and_rewrite(
}
};
new_block.terminator = Some(MirInstruction::Jump {
new_block.set_terminator(MirInstruction::Jump {
target: actual_target,
edge_args: None,
});
// DEBUG: Print final state after adding parameter bindings
@ -979,7 +987,18 @@ pub(super) fn merge_and_rewrite(
// Remap terminator (convert Return → Jump to exit) if not already set by tail call
if !found_tail_call {
if let Some(ref term) = old_block.terminator {
let remapped_term = match term {
let remap_edge_args = |edge_args: &Option<crate::mir::EdgeArgs>| {
edge_args.as_ref().map(|args| crate::mir::EdgeArgs {
layout: args.layout,
values: args
.values
.iter()
.map(|&v| remapper.remap_value(v))
.collect(),
})
};
let mut remapped_term: Option<MirInstruction> = None;
match term {
MirInstruction::Return { value } => {
// Convert Return to Jump to exit block
// All functions return to same exit block (Phase 189)
@ -988,43 +1007,54 @@ pub(super) fn merge_and_rewrite(
//
// The JoinIR Jump instruction passes ALL carrier values in its args,
// but the JoinIR→MIR conversion in joinir_block_converter only preserved
// the first arg in the Return value. We now use the jump_args metadata
// the first arg in the Return value. We now use the legacy jump_args metadata
// to recover all the original Jump args.
//
if let Some(_ret_val) = value {
// Phase 246-EX: Check if this block has jump_args metadata
if let Some(ref jump_args) = old_block.jump_args {
log!(
verbose,
"[DEBUG-177] Phase 246-EX: Block {:?} has jump_args metadata: {:?}",
old_block.id, jump_args
);
let mut exit_edge_args: Option<crate::mir::EdgeArgs> = None;
if value.is_some() {
if let Some(b) = boundary {
// Phase 246-EX: Check if this block has legacy jump_args metadata
if let Some(edge_args) =
old_block.legacy_edge_args_with_layout(b.jump_args_layout)
{
log!(
verbose,
"[DEBUG-177] Phase 246-EX: Block {:?} has legacy jump_args metadata: {:?}",
old_block.id, edge_args.values
);
// The jump_args are in JoinIR value space, remap them to HOST
let remapped_args: Vec<ValueId> = jump_args
.iter()
.map(|&arg| remapper.remap_value(arg))
.collect();
// The jump_args are in JoinIR value space, remap them to HOST
let remapped_args: Vec<ValueId> = edge_args
.values
.iter()
.map(|&arg| remapper.remap_value(arg))
.collect();
log!(
verbose,
"[DEBUG-177] Phase 246-EX: Remapped jump_args: {:?}",
remapped_args
);
log!(
verbose,
"[DEBUG-177] Phase 246-EX: Remapped jump_args: {:?}",
remapped_args
);
// Phase 118 P2: Use ExitArgsCollectorBox to collect exit values
let edge_args = crate::mir::EdgeArgs {
layout: edge_args.layout,
values: remapped_args,
};
exit_edge_args = Some(edge_args.clone());
// Phase 118 P2: Use ExitArgsCollectorBox to collect exit values
if let Some(b) = boundary {
let collector = ExitArgsCollectorBox::new();
let collection_result = collector.collect(
&b.exit_bindings,
&remapped_args,
&edge_args.values,
new_block_id,
strict_exit,
b.jump_args_layout,
edge_args.layout,
)?;
// Add expr_result to exit_phi_inputs (if present)
if let Some(expr_result_val) = collection_result.expr_result_value {
if let Some(expr_result_val) =
collection_result.expr_result_value
{
exit_phi_inputs.push((new_block_id, expr_result_val));
log!(
verbose,
@ -1034,7 +1064,9 @@ pub(super) fn merge_and_rewrite(
}
// Add carrier values to carrier_inputs
for (carrier_name, (block_id, value_id)) in collection_result.carrier_values {
for (carrier_name, (block_id, value_id)) in
collection_result.carrier_values
{
carrier_inputs
.entry(carrier_name.clone())
.or_insert_with(Vec::new)
@ -1045,16 +1077,14 @@ pub(super) fn merge_and_rewrite(
carrier_name, block_id, value_id
);
}
}
} else {
// Fallback: Use header PHI dst (old behavior for blocks without jump_args)
log!(
verbose,
"[DEBUG-177] Phase 246-EX: Block {:?} has NO jump_args, using header PHI fallback",
old_block.id
);
} else {
// Fallback: Use header PHI dst (old behavior for blocks without jump_args)
log!(
verbose,
"[DEBUG-177] Phase 246-EX: Block {:?} has NO jump_args, using header PHI fallback",
old_block.id
);
if let Some(b) = boundary {
if let Some(loop_var_name) = &b.loop_var_name {
if let Some(phi_dst) =
loop_header_phi_info.get_carrier_phi(loop_var_name)
@ -1102,11 +1132,16 @@ pub(super) fn merge_and_rewrite(
}
}
MirInstruction::Jump {
target: exit_block_id,
if let Some(edge_args) = exit_edge_args {
new_block.set_jump_with_edge_args(exit_block_id, Some(edge_args));
} else {
new_block.set_terminator(MirInstruction::Jump {
target: exit_block_id,
edge_args: None,
});
}
}
MirInstruction::Jump { target } => {
MirInstruction::Jump { target, edge_args } => {
// Phase 189 FIX: Remap block ID for Jump
// Phase 259 P0 FIX: Check skipped_entry_redirects first (for k_exit blocks)
let remapped_target = skipped_entry_redirects
@ -1114,14 +1149,17 @@ pub(super) fn merge_and_rewrite(
.or_else(|| local_block_map.get(target))
.copied()
.unwrap_or(*target);
MirInstruction::Jump {
remapped_term = Some(MirInstruction::Jump {
target: remapped_target,
}
edge_args: remap_edge_args(edge_args),
});
}
MirInstruction::Branch {
condition,
then_bb,
else_bb,
then_edge_args,
else_edge_args,
} => {
// Phase 189 FIX: Remap block IDs AND condition ValueId for Branch
// Phase 259 P0 FIX: Check skipped_entry_redirects first (for k_exit blocks)
@ -1135,15 +1173,46 @@ pub(super) fn merge_and_rewrite(
.or_else(|| local_block_map.get(else_bb))
.copied()
.unwrap_or(*else_bb);
MirInstruction::Branch {
remapped_term = Some(MirInstruction::Branch {
condition: remapper.remap_value(*condition),
then_bb: remapped_then,
else_bb: remapped_else,
}
then_edge_args: remap_edge_args(then_edge_args),
else_edge_args: remap_edge_args(else_edge_args),
});
}
_ => remapper.remap_instruction(term),
};
new_block.terminator = Some(remapped_term);
_ => remapped_term = Some(remapper.remap_instruction(term)),
}
if let Some(term) = remapped_term {
match term {
MirInstruction::Jump { target, edge_args } => {
if edge_args.is_some() {
new_block.set_jump_with_edge_args(target, edge_args);
} else {
new_block.set_terminator(MirInstruction::Jump {
target,
edge_args: None,
});
}
}
MirInstruction::Branch {
condition,
then_bb,
then_edge_args,
else_bb,
else_edge_args,
} => {
new_block.set_branch_with_edge_args(
condition,
then_bb,
then_edge_args,
else_bb,
else_edge_args,
);
}
_ => new_block.set_terminator(term),
}
}
}
}
@ -1153,23 +1222,31 @@ pub(super) fn merge_and_rewrite(
match decision {
LoweringDecision::NormalizeToExitJump { args } => {
// Collect exit values from k_exit arguments
let mut exit_edge_args: Option<crate::mir::EdgeArgs> = None;
if let Some(b) = boundary {
let collector = ExitArgsCollectorBox::new();
let exit_args: Vec<ValueId> = if let Some(ref jump_args) = k_exit_jump_args {
jump_args
.iter()
.map(|&arg| remapper.remap_value(arg))
.collect()
} else {
args.clone()
let exit_values: Vec<ValueId> =
if let Some(ref edge_args) = k_exit_edge_args {
edge_args
.values
.iter()
.map(|&arg| remapper.remap_value(arg))
.collect()
} else {
args.clone()
};
let edge_args = crate::mir::EdgeArgs {
layout: b.jump_args_layout,
values: exit_values,
};
exit_edge_args = Some(edge_args.clone());
let collection_result =
collector.collect(
&b.exit_bindings,
&exit_args,
&edge_args.values,
new_block_id,
strict_exit,
b.jump_args_layout,
edge_args.layout,
)?;
if let Some(expr_result_value) = collection_result.expr_result_value {
exit_phi_inputs.push((collection_result.block_id, expr_result_value));
@ -1189,8 +1266,17 @@ pub(super) fn merge_and_rewrite(
}
// Generate exit jump using policy box
let exit_jump = tail_call_policy.rewrite_to_exit_jump(exit_block_id);
new_block.terminator = Some(exit_jump.clone());
let exit_jump = if let Some(edge_args) = exit_edge_args {
new_block.set_jump_with_edge_args(exit_block_id, Some(edge_args));
new_block.terminator.clone().unwrap_or(MirInstruction::Jump {
target: exit_block_id,
edge_args: None,
})
} else {
let exit_jump = tail_call_policy.rewrite_to_exit_jump(exit_block_id);
new_block.set_terminator(exit_jump.clone());
exit_jump
};
// Strict mode: verify the generated terminator matches contract
if strict_exit {

View File

@ -361,7 +361,7 @@ impl LoopHeaderPhiBuilder {
);
}
}
crate::mir::MirInstruction::Jump { target } => {
crate::mir::MirInstruction::Jump { target, .. } => {
block.successors.insert(*target);
if dev_debug {
trace.stderr_if(

View File

@ -96,6 +96,7 @@ impl TailCallLoweringPolicyBox {
pub fn rewrite_to_exit_jump(&self, exit_block_id: BasicBlockId) -> MirInstruction {
MirInstruction::Jump {
target: exit_block_id,
edge_args: None,
}
}
@ -121,8 +122,8 @@ impl TailCallLoweringPolicyBox {
exit_block_id: BasicBlockId,
) -> Result<(), String> {
match terminator {
MirInstruction::Jump { target } if *target == exit_block_id => Ok(()),
MirInstruction::Jump { target } => Err(crate::mir::join_ir::lowering::error_tags::freeze_with_hint(
MirInstruction::Jump { target, .. } if *target == exit_block_id => Ok(()),
MirInstruction::Jump { target, .. } => Err(crate::mir::join_ir::lowering::error_tags::freeze_with_hint(
"phase131/k_exit/wrong_jump_target",
&format!(
"skippable continuation tail call lowered to Jump {:?}, expected exit_block_id {:?}",
@ -184,7 +185,7 @@ mod tests {
let jump = policy.rewrite_to_exit_jump(exit_block);
assert!(matches!(
jump,
MirInstruction::Jump { target } if target == exit_block
MirInstruction::Jump { target, .. } if target == exit_block
));
}
@ -194,6 +195,7 @@ mod tests {
let exit_block = BasicBlockId(42);
let jump = MirInstruction::Jump {
target: exit_block,
edge_args: None,
};
let result = policy.verify_exit_jump(&jump, exit_block);
@ -206,6 +208,7 @@ mod tests {
let exit_block = BasicBlockId(42);
let wrong_jump = MirInstruction::Jump {
target: BasicBlockId(99),
edge_args: None,
};
let result = policy.verify_exit_jump(&wrong_jump, exit_block);

View File

@ -33,7 +33,7 @@ fn case_a_pure_k_exit_return_is_skippable() {
let mut func = make_function("join_func_2");
let block = func.blocks.get_mut(&func.entry_block).unwrap();
block.instructions.clear();
block.terminator = Some(MirInstruction::Return { value: None });
block.set_terminator(MirInstruction::Return { value: None });
assert!(is_skippable_continuation(&func));
}
@ -52,8 +52,9 @@ fn case_b_k_exit_tailcall_post_k_is_not_skippable() {
args: vec![],
effects: EffectMask::CONTROL,
});
block.terminator = Some(MirInstruction::Jump {
block.set_terminator(MirInstruction::Jump {
target: BasicBlockId(1),
edge_args: None,
});
assert!(!is_skippable_continuation(&func));
}
@ -67,14 +68,15 @@ fn case_c_multi_block_continuation_is_not_skippable() {
// Add a second block
let second_block_id = BasicBlockId(1);
let mut second_block = crate::mir::BasicBlock::new(second_block_id);
second_block.terminator = Some(MirInstruction::Return { value: None });
second_block.set_terminator(MirInstruction::Return { value: None });
func.blocks.insert(second_block_id, second_block);
// Entry block jumps to second block
let entry_block = func.blocks.get_mut(&func.entry_block).unwrap();
entry_block.instructions.clear();
entry_block.terminator = Some(MirInstruction::Jump {
entry_block.set_terminator(MirInstruction::Jump {
target: second_block_id,
edge_args: None,
});
assert!(!is_skippable_continuation(&func));
@ -91,6 +93,6 @@ fn case_d_continuation_with_instructions_is_not_skippable() {
dst: ValueId(0),
value: crate::mir::types::ConstValue::Integer(42),
});
block.terminator = Some(MirInstruction::Return { value: None });
block.set_terminator(MirInstruction::Return { value: None });
assert!(!is_skippable_continuation(&func));
}

View File

@ -10,6 +10,7 @@
//! - Control tree capability guard (control_tree_capability_guard.rs) ✅ Phase 112
pub(in crate::mir::builder) mod control_tree_capability_guard;
pub(in crate::mir::builder) mod api;
pub(in crate::mir::builder) mod legacy; // Phase 132-R0 Task 4: Legacy routing isolation
pub(in crate::mir::builder) mod loop_context;
pub(in crate::mir::builder) mod merge;

View File

@ -18,6 +18,8 @@ pub fn emit_conditional(
condition: cond,
then_bb,
else_bb,
then_edge_args: None,
else_edge_args: None,
})
}
}
@ -28,6 +30,9 @@ pub fn emit_jump(b: &mut MirBuilder, target: BasicBlockId) -> Result<(), String>
crate::mir::ssot::cf_common::set_jump(func, cur_bb, target);
Ok(())
} else {
b.emit_instruction(MirInstruction::Jump { target })
b.emit_instruction(MirInstruction::Jump {
target,
edge_args: None,
})
}
}

View File

@ -102,7 +102,25 @@ impl JoinIrIdRemapper {
vals.extend(args.iter().copied());
vals
}
Branch { condition, .. } => vec![*condition],
Branch {
condition,
then_edge_args,
else_edge_args,
..
} => {
let mut vals = vec![*condition];
if let Some(args) = then_edge_args {
vals.extend(args.values.iter().copied());
}
if let Some(args) = else_edge_args {
vals.extend(args.values.iter().copied());
}
vals
}
Jump { edge_args, .. } => edge_args
.as_ref()
.map(|args| args.values.clone())
.unwrap_or_default(),
Return { value } => value.iter().copied().collect(),
Phi { dst, inputs, .. } => {
let mut vals = vec![*dst];
@ -177,8 +195,15 @@ impl JoinIrIdRemapper {
/// 命令を新しい ID空間にリマップ
pub fn remap_instruction(&self, inst: &MirInstruction) -> MirInstruction {
use crate::mir::MirInstruction::*;
use crate::mir::EdgeArgs;
let remap = |v: ValueId| self.value_map.get(&v).copied().unwrap_or(v);
let remap_edge_args = |edge_args: &Option<EdgeArgs>| {
edge_args.as_ref().map(|args| EdgeArgs {
layout: args.layout,
values: args.values.iter().map(|&v| remap(v)).collect(),
})
};
match inst {
Const { dst, value } => Const {
@ -431,8 +456,28 @@ impl JoinIrIdRemapper {
then_val: remap(*then_val),
else_val: remap(*else_val),
},
// Pass through unchanged (Branch/Jump/Return handled separately)
Branch { .. } | Jump { .. } | Return { .. } | Nop | Safepoint => inst.clone(),
Branch {
condition,
then_bb,
else_bb,
then_edge_args,
else_edge_args,
} => Branch {
condition: remap(*condition),
then_bb: *then_bb,
else_bb: *else_bb,
then_edge_args: remap_edge_args(then_edge_args),
else_edge_args: remap_edge_args(else_edge_args),
},
Jump { target, edge_args } => Jump {
target: *target,
edge_args: remap_edge_args(edge_args),
},
Return { value } => Return {
value: value.map(remap),
},
// Pass through unchanged
Nop | Safepoint => inst.clone(),
}
}

View File

@ -728,7 +728,7 @@ mod tests {
"k_exit must return Some(value)"
);
// Bridge sanity: tail-call blocks must carry jump_args metadata for merge collection.
// Bridge sanity: tail-call blocks must carry legacy jump_args metadata for merge collection.
// This is required for DirectValue mode (no PHI) to reconnect carriers safely.
let mir_module = crate::mir::join_ir_vm_bridge::bridge_joinir_to_mir(&module)
.expect("bridge_joinir_to_mir failed");
@ -744,8 +744,8 @@ mod tests {
.get(&entry)
.expect("missing loop_body entry block");
assert!(
entry_block.jump_args.is_some(),
"loop_body entry block must have jump_args metadata in bridged MIR"
entry_block.has_legacy_jump_args(),
"loop_body entry block must have legacy jump_args metadata in bridged MIR"
);
// Loop-only (the routing path in real lowering): still must encode loop_step as a tail-call.

View File

@ -335,7 +335,10 @@ impl MirFunction {
) -> Result<(), String> {
if let Some(bb) = self.get_block_mut(bb_id) {
if !bb.is_terminated() {
bb.set_terminator(MirInstruction::Jump { target });
bb.set_terminator(MirInstruction::Jump {
target,
edge_args: None,
});
}
Ok(())
} else {
@ -357,6 +360,8 @@ impl MirFunction {
condition,
then_bb,
else_bb,
then_edge_args: None,
else_edge_args: None,
});
Ok(())
} else {

View File

@ -63,6 +63,9 @@ pub fn emit_return_value(f: &mut MirFunction, bb: BasicBlockId, value: ValueId)
#[inline]
pub fn emit_jump(f: &mut MirFunction, bb: BasicBlockId, target: BasicBlockId) {
if let Some(block) = f.get_block_mut(bb) {
block.add_instruction(MirInstruction::Jump { target });
block.add_instruction(MirInstruction::Jump {
target,
edge_args: None,
});
}
}

View File

@ -4,7 +4,7 @@
* SSA-form instructions with effect tracking for optimization
*/
use super::{EffectMask, ValueId};
use super::{EdgeArgs, EffectMask, ValueId};
use crate::mir::definitions::Callee; // Import Callee from unified definitions
use crate::mir::types::{
BarrierOp, BinaryOp, CompareOp, ConstValue, MirType, TypeOpKind, UnaryOp, WeakRefOp,
@ -116,11 +116,19 @@ pub enum MirInstruction {
condition: ValueId,
then_bb: super::BasicBlockId,
else_bb: super::BasicBlockId,
/// Optional edge args for then branch (Phase 260 P0)
then_edge_args: Option<EdgeArgs>,
/// Optional edge args for else branch (Phase 260 P0)
else_edge_args: Option<EdgeArgs>,
},
/// Unconditional jump
/// `jmp %target_bb`
Jump { target: super::BasicBlockId },
Jump {
target: super::BasicBlockId,
/// Optional edge args for jump (Phase 260 P0)
edge_args: Option<EdgeArgs>,
},
/// Return from function
/// `ret %value` or `ret void`

View File

@ -176,6 +176,31 @@ impl MirInstruction {
return used;
}
match self {
MirInstruction::Branch {
condition,
then_edge_args,
else_edge_args,
..
} => {
let mut used = vec![*condition];
if let Some(args) = then_edge_args {
used.extend(args.values.iter().copied());
}
if let Some(args) = else_edge_args {
used.extend(args.values.iter().copied());
}
return used;
}
MirInstruction::Jump { edge_args, .. } => {
return edge_args
.as_ref()
.map(|args| args.values.clone())
.unwrap_or_default();
}
_ => {}
}
if let Some(used) = inst_meta::used_via_meta(self) {
return used;
}

View File

@ -132,6 +132,7 @@ impl IfMergeLowerer {
condition,
then_bb,
else_bb,
..
} => IfBranch {
cond: *condition,
then_block: *then_bb,

View File

@ -142,6 +142,7 @@ impl IfSelectLowerer {
condition,
then_bb,
else_bb,
..
} => IfBranch {
cond: *condition,
then_block: *then_bb,
@ -263,13 +264,13 @@ impl IfSelectLowerer {
) -> Option<BasicBlockId> {
// then が Jump で終わるか確認
let then_target = match then_block.terminator.as_ref()? {
MirInstruction::Jump { target } => *target,
MirInstruction::Jump { target, .. } => *target,
_ => return None,
};
// else が Jump で終わるか確認
let else_target = match else_block.terminator.as_ref()? {
MirInstruction::Jump { target } => *target,
MirInstruction::Jump { target, .. } => *target,
_ => return None,
};
@ -312,7 +313,7 @@ impl IfSelectLowerer {
// then ブロックが Jump で終わるか確認
let merge_block_id = match then_block.terminator.as_ref()? {
MirInstruction::Jump { target } => *target,
MirInstruction::Jump { target, .. } => *target,
_ => return None,
};
@ -339,7 +340,7 @@ impl IfSelectLowerer {
// else ブロックも同じ merge ブロックに Jump するか確認
let else_merge = match else_block.terminator.as_ref()? {
MirInstruction::Jump { target } => *target,
MirInstruction::Jump { target, .. } => *target,
_ => return None,
};

View File

@ -268,6 +268,8 @@ impl JoinIrBlockConverter {
condition: *cond,
then_bb: then_block,
else_bb: else_block,
then_edge_args: None,
else_edge_args: None,
};
Self::finalize_block(
mir_func,
@ -290,8 +292,9 @@ impl JoinIrBlockConverter {
effects: EffectMask::WRITE,
});
then_block_obj.instruction_spans.push(Span::unknown());
then_block_obj.terminator = Some(MirInstruction::Jump {
then_block_obj.set_terminator(MirInstruction::Jump {
target: merge_block,
edge_args: None,
});
mir_func.blocks.insert(then_block, then_block_obj);
@ -302,8 +305,9 @@ impl JoinIrBlockConverter {
src: *receiver,
});
else_block_obj.instruction_spans.push(Span::unknown());
else_block_obj.terminator = Some(MirInstruction::Jump {
else_block_obj.set_terminator(MirInstruction::Jump {
target: merge_block,
edge_args: None,
});
mir_func.blocks.insert(else_block, else_block_obj);
@ -428,8 +432,8 @@ impl JoinIrBlockConverter {
// Without this, tail-call blocks look like "no args", forcing fallbacks that can
// produce undefined ValueIds in DirectValue mode.
if let Some(block) = mir_func.blocks.get_mut(&self.current_block_id) {
if block.jump_args.is_none() {
block.jump_args = Some(args.to_vec());
if !block.has_legacy_jump_args() {
block.set_legacy_jump_args(args.to_vec(), None);
}
}
@ -484,6 +488,8 @@ impl JoinIrBlockConverter {
condition: *cond_var,
then_bb: exit_block_id,
else_bb: continue_block_id,
then_edge_args: None,
else_edge_args: None,
};
Self::finalize_block(
@ -497,7 +503,7 @@ impl JoinIrBlockConverter {
let mut exit_block = crate::mir::BasicBlock::new(exit_block_id);
// Phase 246-EX: Store Jump args in metadata for exit PHI construction
exit_block.jump_args = Some(args.to_vec());
exit_block.set_legacy_jump_args(args.to_vec(), None);
// Phase 256 P1.9: Generate tail call to continuation
exit_block.instructions.push(MirInstruction::Const {
@ -513,7 +519,7 @@ impl JoinIrBlockConverter {
effects: EffectMask::PURE,
});
exit_block.instruction_spans.push(Span::unknown());
exit_block.terminator = Some(MirInstruction::Return { value: Some(call_result_id) });
exit_block.set_terminator(MirInstruction::Return { value: Some(call_result_id) });
mir_func.blocks.insert(exit_block_id, exit_block);
// Continue block
@ -539,8 +545,8 @@ impl JoinIrBlockConverter {
// Preserve jump args as metadata (SSOT for ExitLine/jump_args wiring).
if let Some(block) = mir_func.blocks.get_mut(&self.current_block_id) {
if block.jump_args.is_none() {
block.jump_args = Some(args.to_vec());
if !block.has_legacy_jump_args() {
block.set_legacy_jump_args(args.to_vec(), None);
}
}
@ -631,6 +637,8 @@ impl JoinIrBlockConverter {
condition: *cond,
then_bb: then_block,
else_bb: else_block,
then_edge_args: None,
else_edge_args: None,
};
Self::finalize_block(
mir_func,
@ -648,8 +656,9 @@ impl JoinIrBlockConverter {
});
then_block_obj.instruction_spans.push(Span::unknown());
}
then_block_obj.terminator = Some(MirInstruction::Jump {
then_block_obj.set_terminator(MirInstruction::Jump {
target: merge_block,
edge_args: None,
});
mir_func.blocks.insert(then_block, then_block_obj);
@ -662,8 +671,9 @@ impl JoinIrBlockConverter {
});
else_block_obj.instruction_spans.push(Span::unknown());
}
else_block_obj.terminator = Some(MirInstruction::Jump {
else_block_obj.set_terminator(MirInstruction::Jump {
target: merge_block,
edge_args: None,
});
mir_func.blocks.insert(else_block, else_block_obj);
@ -752,6 +762,8 @@ impl JoinIrBlockConverter {
condition: *cond_var,
then_bb: next_true_block,
else_bb: final_else_block,
then_edge_args: None,
else_edge_args: None,
};
if level == 0 {
@ -775,8 +787,9 @@ impl JoinIrBlockConverter {
});
then_block_obj.instruction_spans.push(Span::unknown());
}
then_block_obj.terminator = Some(MirInstruction::Jump {
then_block_obj.set_terminator(MirInstruction::Jump {
target: merge_block,
edge_args: None,
});
mir_func.blocks.insert(then_block, then_block_obj);
@ -789,8 +802,9 @@ impl JoinIrBlockConverter {
});
else_block_obj.instruction_spans.push(Span::unknown());
}
else_block_obj.terminator = Some(MirInstruction::Jump {
else_block_obj.set_terminator(MirInstruction::Jump {
target: merge_block,
edge_args: None,
});
mir_func.blocks.insert(final_else_block, else_block_obj);
@ -842,7 +856,7 @@ impl JoinIrBlockConverter {
block.instructions = instructions;
block.instruction_spans = vec![Span::unknown(); inst_count];
}
block.terminator = Some(terminator);
block.set_terminator(terminator);
}
}

View File

@ -140,32 +140,40 @@ fn lower_normalized_function_direct(
let merge_bb = BasicBlockId(next_block_id);
next_block_id += 1;
finalize_block(
&mut mir_func,
current_block_id,
mem::take(&mut current_insts),
MirInstruction::Branch {
condition: cond,
then_bb,
else_bb,
},
None,
);
finalize_block(
&mut mir_func,
current_block_id,
mem::take(&mut current_insts),
MirInstruction::Branch {
condition: cond,
then_bb,
else_bb,
then_edge_args: None,
else_edge_args: None,
},
None,
);
finalize_block(
&mut mir_func,
then_bb,
Vec::new(),
MirInstruction::Jump { target: merge_bb },
None,
);
finalize_block(
&mut mir_func,
else_bb,
Vec::new(),
MirInstruction::Jump { target: merge_bb },
None,
);
finalize_block(
&mut mir_func,
then_bb,
Vec::new(),
MirInstruction::Jump {
target: merge_bb,
edge_args: None,
},
None,
);
finalize_block(
&mut mir_func,
else_bb,
Vec::new(),
MirInstruction::Jump {
target: merge_bb,
edge_args: None,
},
None,
);
current_block_id = merge_bb;
current_insts = vec![MirInstruction::Phi {
@ -240,6 +248,8 @@ fn lower_normalized_function_direct(
condition: cond_remapped,
then_bb,
else_bb,
then_edge_args: None,
else_edge_args: None,
},
None,
);
@ -298,7 +308,7 @@ fn lower_normalized_function_direct(
block.instruction_spans = vec![Span::unknown(); block.instructions.len()];
}
if block.terminator.is_none() {
block.terminator = Some(MirInstruction::Return { value: None });
block.set_terminator(MirInstruction::Return { value: None });
}
}
@ -398,7 +408,7 @@ fn build_exit_or_tail_branch(
.or_insert_with(|| BasicBlock::new(block_id));
block.instructions = insts;
block.instruction_spans = vec![Span::unknown(); block.instructions.len()];
block.terminator = Some(term);
block.set_terminator(term);
return Ok(());
}
@ -410,8 +420,8 @@ fn build_exit_or_tail_branch(
.or_insert_with(|| BasicBlock::new(block_id));
block.instructions.clear();
block.instruction_spans.clear();
block.terminator = Some(MirInstruction::Return { value: ret_val });
block.jump_args = Some(env.to_vec());
block.set_terminator(MirInstruction::Return { value: ret_val });
block.set_legacy_jump_args(env.to_vec(), None);
Ok(())
}
@ -455,6 +465,10 @@ fn finalize_block(
.or_insert_with(|| BasicBlock::new(block_id));
block.instructions = instructions;
block.instruction_spans = vec![Span::unknown(); block.instructions.len()];
block.terminator = Some(terminator);
block.jump_args = jump_args;
block.set_terminator(terminator);
if let Some(args) = jump_args {
block.set_legacy_jump_args(args, None);
} else {
block.clear_legacy_jump_args();
}
}

View File

@ -55,7 +55,10 @@ pub fn build_simple_loop<L: LoopBuilderApi>(
let after = lb.new_block();
// Jump to header
lb.emit(MirInstruction::Jump { target: header })?;
lb.emit(MirInstruction::Jump {
target: header,
edge_args: None,
})?;
// Header: branch on provided condition
lb.start_new_block(header)?;
@ -63,12 +66,17 @@ pub fn build_simple_loop<L: LoopBuilderApi>(
condition,
then_bb: body,
else_bb: after,
then_edge_args: None,
else_edge_args: None,
})?;
// Body
lb.start_new_block(body)?;
build_body(lb)?;
lb.emit(MirInstruction::Jump { target: header })?;
lb.emit(MirInstruction::Jump {
target: header,
edge_args: None,
})?;
// After: return void value
lb.start_new_block(after)?;

View File

@ -53,7 +53,7 @@ pub mod verification;
pub mod verification_types; // extracted error types // Optimization subpasses (e.g., type_hints) // Phase 25.1f: Loop/If 共通ビューControlForm
// Re-export main types for easy access
pub use basic_block::{BasicBlock, BasicBlockId, BasicBlockIdGenerator};
pub use basic_block::{BasicBlock, BasicBlockId, BasicBlockIdGenerator, EdgeArgs, OutEdge};
pub use binding_id::BindingId; // Phase 74: BindingId infrastructure
pub use builder::MirBuilder;

View File

@ -524,7 +524,7 @@ mod tests {
}
#[test]
fn test_dce_keeps_jump_args_values() {
fn test_dce_keeps_edge_args_values() {
let signature = FunctionSignature {
name: "main".to_string(),
params: vec![],
@ -533,6 +533,7 @@ mod tests {
};
let mut func = MirFunction::new(signature, BasicBlockId::new(0));
let bb0 = BasicBlockId::new(0);
let bb1 = BasicBlockId::new(1);
let mut b0 = BasicBlock::new(bb0);
let v0 = ValueId::new(0);
let v1 = ValueId::new(1);
@ -541,9 +542,17 @@ mod tests {
value: ConstValue::Integer(1),
});
b0.add_instruction(MirInstruction::Copy { dst: v1, src: v0 });
b0.add_instruction(MirInstruction::Return { value: None });
b0.jump_args = Some(vec![v1]);
b0.set_jump_with_edge_args(
bb1,
Some(crate::mir::EdgeArgs {
layout: crate::mir::join_ir::lowering::inline_boundary::JumpArgsLayout::CarriersOnly,
values: vec![v1],
}),
);
func.add_block(b0);
let mut exit_block = BasicBlock::new(bb1);
exit_block.set_terminator(MirInstruction::Return { value: None });
func.add_block(exit_block);
let mut module = MirModule::new("test".to_string());
module.add_function(func);
@ -555,7 +564,7 @@ mod tests {
.instructions
.iter()
.any(|inst| matches!(inst, MirInstruction::Copy { .. }));
assert!(has_copy, "Copy used only by jump_args should not be eliminated");
assert!(has_copy, "Copy used only by edge args should not be eliminated");
}
#[test]

View File

@ -37,9 +37,11 @@ fn eliminate_dead_code_in_function(function: &mut MirFunction) -> usize {
used_values.insert(u);
}
}
if let Some(args) = &block.jump_args {
for &u in args {
used_values.insert(u);
for edge in block.out_edges() {
if let Some(args) = edge.args {
for u in args.values {
used_values.insert(u);
}
}
}
}
@ -108,7 +110,7 @@ mod tests {
use crate::mir::{BasicBlockId, ConstValue, EffectMask, FunctionSignature, MirInstruction, MirType};
#[test]
fn test_dce_keeps_jump_args_values() {
fn test_dce_keeps_edge_args_values() {
let mut module = MirModule::new("dce_test".to_string());
let sig = FunctionSignature {
@ -122,6 +124,7 @@ mod tests {
let v1 = ValueId(1);
let v2 = ValueId(2);
let v_dead = ValueId(3);
let bb1 = BasicBlockId(1);
{
let bb0 = func.blocks.get_mut(&BasicBlockId(0)).unwrap();
@ -141,10 +144,20 @@ mod tests {
});
bb0.instruction_spans.push(Span::unknown());
// SSOT: jump_args is a semantic use-site (ExitLine, continuation args).
bb0.jump_args = Some(vec![v2]);
// SSOT: edge args are semantic use-sites (ExitLine, continuation args).
bb0.set_jump_with_edge_args(
bb1,
Some(crate::mir::EdgeArgs {
layout: crate::mir::join_ir::lowering::inline_boundary::JumpArgsLayout::CarriersOnly,
values: vec![v2],
}),
);
}
let mut exit_block = crate::mir::BasicBlock::new(bb1);
exit_block.set_terminator(MirInstruction::Return { value: None });
func.add_block(exit_block);
module.add_function(func);
let eliminated = eliminate_dead_code(&mut module);
@ -156,7 +169,7 @@ mod tests {
// Contract: spans must stay aligned with instructions.
assert_eq!(bb0.instructions.len(), bb0.instruction_spans.len());
// Contract: values that appear only in jump_args must be kept (and their deps).
// Contract: values that appear only in edge args must be kept (and their deps).
assert!(bb0
.instructions
.iter()

View File

@ -232,11 +232,12 @@ pub fn format_instruction(
condition,
then_bb,
else_bb,
..
} => {
format!("br {}, label {}, label {}", condition, then_bb, else_bb)
}
MirInstruction::Jump { target } => {
MirInstruction::Jump { target, .. } => {
format!("br label {}", target)
}

View File

@ -30,6 +30,8 @@ pub fn set_branch(
condition,
then_bb,
else_bb,
then_edge_args: None,
else_edge_args: None,
});
}
if let Some(tb) = f.get_block_mut(then_bb) {
@ -43,7 +45,10 @@ pub fn set_branch(
/// Set an unconditional jump terminator and register predecessor on target block.
pub fn set_jump(f: &mut MirFunction, cur_bb: BasicBlockId, target: BasicBlockId) {
if let Some(bb) = f.get_block_mut(cur_bb) {
bb.set_terminator(MirInstruction::Jump { target });
bb.set_terminator(MirInstruction::Jump {
target,
edge_args: None,
});
}
if let Some(tb) = f.get_block_mut(target) {
tb.add_predecessor(cur_bb);

View File

@ -63,6 +63,7 @@ pub fn capture_actual_predecessor_and_jump(
// 既存control_flowモジュールと同じパターンを使用
builder.emit_instruction(super::super::MirInstruction::Jump {
target: target_block,
edge_args: None,
})?;
Ok(Some(cur_id))
} else {

View File

@ -1,13 +1,23 @@
use crate::mir::function::MirFunction;
use crate::mir::verification::utils;
use crate::mir::verification_types::VerificationError;
use crate::mir::{BasicBlockId, ValueId};
use crate::mir::{BasicBlockId, MirInstruction, ValueId};
use std::collections::{HashMap, HashSet};
/// Verify CFG references and reachability
pub fn check_control_flow(function: &MirFunction) -> Result<(), Vec<VerificationError>> {
let mut errors = Vec::new();
for (block_id, block) in &function.blocks {
let expected_successors = block.successors_from_terminator();
if expected_successors != block.successors {
errors.push(VerificationError::ControlFlowError {
block: *block_id,
reason: format!(
"Successors cache mismatch: cached={:?}, expected={:?}",
block.successors, expected_successors
),
});
}
for successor in &block.successors {
if !function.blocks.contains_key(successor) {
errors.push(VerificationError::ControlFlowError {
@ -16,6 +26,62 @@ pub fn check_control_flow(function: &MirFunction) -> Result<(), Vec<Verification
});
}
}
// Phase 260 P0: Fail-fast if terminator edge-args and legacy jump_args diverge.
if let Some(term) = &block.terminator {
match term {
MirInstruction::Jump {
edge_args: Some(edge_args),
..
} => {
if block.has_legacy_jump_args() {
let Some(legacy_layout) = block.legacy_jump_args_layout() else {
errors.push(VerificationError::ControlFlowError {
block: *block_id,
reason: "Legacy jump_args layout missing with edge-args present"
.to_string(),
});
continue;
};
let legacy_values = block.legacy_jump_args_values().unwrap_or_default();
if edge_args.values.as_slice() != legacy_values {
errors.push(VerificationError::ControlFlowError {
block: *block_id,
reason: format!(
"Edge-args values mismatch: edge_args={:?}, legacy={:?}",
edge_args.values, legacy_values
),
});
}
if edge_args.layout != legacy_layout {
errors.push(VerificationError::ControlFlowError {
block: *block_id,
reason: format!(
"Edge-args layout mismatch: edge_args={:?}, legacy={:?}",
edge_args.layout, legacy_layout
),
});
}
}
}
MirInstruction::Branch {
then_edge_args,
else_edge_args,
..
} => {
if block.has_legacy_jump_args()
&& (then_edge_args.is_some() || else_edge_args.is_some())
{
errors.push(VerificationError::ControlFlowError {
block: *block_id,
reason: "Legacy jump_args present on multi-edge terminator with edge-args"
.to_string(),
});
}
}
_ => {}
}
}
}
// Unreachable blocks are allowed in MIR.
// They are created intentionally by break/continue/return statements via