Phase 29aa P3: Jump→Return single-pred rc propagation
This commit is contained in:
@ -31,6 +31,26 @@ fn build_module_with_block(block: BasicBlock, func_name: &str, mod_name: &str) -
|
||||
module
|
||||
}
|
||||
|
||||
fn build_module_with_blocks(
|
||||
blocks: Vec<BasicBlock>,
|
||||
entry: BasicBlockId,
|
||||
func_name: &str,
|
||||
mod_name: &str,
|
||||
) -> MirModule {
|
||||
let signature = FunctionSignature {
|
||||
name: func_name.to_string(),
|
||||
params: vec![],
|
||||
return_type: MirType::Void,
|
||||
effects: EffectMask::PURE,
|
||||
};
|
||||
let mut func = MirFunction::new(signature, entry);
|
||||
func.blocks = blocks.into_iter().map(|b| (b.id, b)).collect();
|
||||
|
||||
let mut module = MirModule::new(mod_name.to_string());
|
||||
module.add_function(func);
|
||||
module
|
||||
}
|
||||
|
||||
fn assert_release_inserted(
|
||||
mut module: MirModule,
|
||||
func_name: &str,
|
||||
@ -74,6 +94,53 @@ fn assert_release_inserted(
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_release_counts_in_blocks(
|
||||
mut module: MirModule,
|
||||
func_name: &str,
|
||||
expected_release: usize,
|
||||
block_expectations: &[(BasicBlockId, usize)],
|
||||
label: &str,
|
||||
) {
|
||||
let stats = insert_rc_instructions(&mut module);
|
||||
if stats.release_inserted != expected_release {
|
||||
eprintln!(
|
||||
"[FAIL] {}: expected release_inserted={}, got {}",
|
||||
label, expected_release, stats.release_inserted
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
let func = module
|
||||
.get_function(func_name)
|
||||
.expect("selfcheck function exists");
|
||||
|
||||
for (bid, expected_count) in block_expectations {
|
||||
let bb = func.blocks.get(bid).expect("block exists");
|
||||
let release_count = bb
|
||||
.instructions
|
||||
.iter()
|
||||
.filter(|inst| matches!(inst, MirInstruction::ReleaseStrong { .. }))
|
||||
.count();
|
||||
if release_count != *expected_count {
|
||||
eprintln!(
|
||||
"[FAIL] {}: block {:?} expected ReleaseStrong count={}, got {}",
|
||||
label, bid, expected_count, release_count
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
if bb.instructions.len() != bb.instruction_spans.len() {
|
||||
eprintln!(
|
||||
"[FAIL] {}: block {:?} span count mismatch: insts={}, spans={}",
|
||||
label,
|
||||
bid,
|
||||
bb.instructions.len(),
|
||||
bb.instruction_spans.len()
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Case 1: Overwrite release (Store -> Store)
|
||||
let ptr = ValueId::new(100);
|
||||
@ -131,6 +198,42 @@ fn main() {
|
||||
"return_cleanup",
|
||||
);
|
||||
|
||||
// Case 3.5: Jump -> Return single-predecessor propagation
|
||||
let ptr = ValueId::new(350);
|
||||
let v1 = ValueId::new(21);
|
||||
|
||||
let mut block_a = BasicBlock::new(BasicBlockId::new(0));
|
||||
block_a.instructions = vec![MirInstruction::Store { value: v1, ptr }];
|
||||
block_a.instruction_spans = vec![Span::unknown()];
|
||||
block_a.terminator = Some(MirInstruction::Jump {
|
||||
target: BasicBlockId::new(1),
|
||||
edge_args: None,
|
||||
});
|
||||
block_a.terminator_span = Some(Span::unknown());
|
||||
|
||||
let mut block_b = BasicBlock::new(BasicBlockId::new(1));
|
||||
block_b.instructions = vec![];
|
||||
block_b.instruction_spans = vec![];
|
||||
block_b.terminator = Some(MirInstruction::Return { value: None });
|
||||
block_b.terminator_span = Some(Span::unknown());
|
||||
|
||||
let module = build_module_with_blocks(
|
||||
vec![block_a, block_b],
|
||||
BasicBlockId::new(0),
|
||||
"selfcheck_jump_return_single_pred",
|
||||
"selfcheck_mod3p5",
|
||||
);
|
||||
assert_release_counts_in_blocks(
|
||||
module,
|
||||
"selfcheck_jump_return_single_pred",
|
||||
1,
|
||||
&[
|
||||
(BasicBlockId::new(0), 0),
|
||||
(BasicBlockId::new(1), 1),
|
||||
],
|
||||
"jump_return_single_pred",
|
||||
);
|
||||
|
||||
// Case 4: Jump terminator should NOT inject block-end cleanup (unsafe cross-block)
|
||||
let ptr = ValueId::new(400);
|
||||
let v1 = ValueId::new(30);
|
||||
|
||||
@ -21,7 +21,7 @@ use crate::mir::MirModule;
|
||||
#[cfg(feature = "rc-insertion-minimal")]
|
||||
use crate::ast::Span;
|
||||
#[cfg(feature = "rc-insertion-minimal")]
|
||||
use crate::mir::{MirInstruction, ValueId};
|
||||
use crate::mir::{BasicBlockId, MirInstruction, ValueId};
|
||||
#[cfg(feature = "rc-insertion-minimal")]
|
||||
use crate::mir::types::ConstValue;
|
||||
#[cfg(feature = "rc-insertion-minimal")]
|
||||
@ -109,7 +109,54 @@ pub fn insert_rc_instructions(module: &mut MirModule) -> RcInsertionStats {
|
||||
for (_name, func) in &mut module.functions {
|
||||
stats.functions_processed += 1;
|
||||
|
||||
for (_bid, block) in &mut func.blocks {
|
||||
let mut predecessors: HashMap<BasicBlockId, Vec<BasicBlockId>> = HashMap::new();
|
||||
for (bid, b) in &func.blocks {
|
||||
let Some(term) = b.terminator.as_ref() else {
|
||||
continue;
|
||||
};
|
||||
match term {
|
||||
MirInstruction::Jump { target, .. } => {
|
||||
predecessors.entry(*target).or_default().push(*bid);
|
||||
}
|
||||
MirInstruction::Branch { then_bb, else_bb, .. } => {
|
||||
predecessors.entry(*then_bb).or_default().push(*bid);
|
||||
predecessors.entry(*else_bb).or_default().push(*bid);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
let mut propagated_initial: HashMap<BasicBlockId, HashMap<ValueId, ValueId>> =
|
||||
HashMap::new();
|
||||
for (bid, b) in &func.blocks {
|
||||
if !matches!(b.terminator.as_ref(), Some(MirInstruction::Return { .. })) {
|
||||
continue;
|
||||
}
|
||||
let preds = predecessors.get(bid).map(|p| p.as_slice()).unwrap_or(&[]);
|
||||
if preds.len() != 1 {
|
||||
continue;
|
||||
}
|
||||
let pred_id = preds[0];
|
||||
let Some(pred_block) = func.blocks.get(&pred_id) else {
|
||||
continue;
|
||||
};
|
||||
match pred_block.terminator.as_ref() {
|
||||
Some(MirInstruction::Jump { target, .. }) if *target == *bid => {
|
||||
let empty_state: HashMap<ValueId, ValueId> = HashMap::new();
|
||||
let (_plan, out_state) = plan_rc_insertion_for_block(
|
||||
&pred_block.instructions,
|
||||
pred_block.terminator.as_ref(),
|
||||
&empty_state,
|
||||
);
|
||||
if !out_state.is_empty() {
|
||||
propagated_initial.insert(*bid, out_state);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
for (bid, block) in &mut func.blocks {
|
||||
stats.blocks_visited += 1;
|
||||
|
||||
// Take ownership of instructions to rebuild with inserted releases
|
||||
@ -124,7 +171,29 @@ pub fn insert_rc_instructions(module: &mut MirModule) -> RcInsertionStats {
|
||||
spans.push(Span::unknown());
|
||||
}
|
||||
|
||||
let plan = plan_rc_insertion_for_block(&insts, terminator.as_ref());
|
||||
let empty_state: HashMap<ValueId, ValueId> = HashMap::new();
|
||||
let initial_state = propagated_initial.get(bid);
|
||||
if let Some(state) = initial_state {
|
||||
let pred_count = predecessors.get(bid).map(|p| p.len()).unwrap_or(0);
|
||||
debug_assert!(
|
||||
pred_count == 1,
|
||||
"rc_insertion: initial state requires single predecessor"
|
||||
);
|
||||
debug_assert!(
|
||||
matches!(terminator.as_ref(), Some(MirInstruction::Return { .. })),
|
||||
"rc_insertion: initial state must target Return block"
|
||||
);
|
||||
debug_assert!(
|
||||
!state.is_empty(),
|
||||
"rc_insertion: initial state for Return block must be non-empty"
|
||||
);
|
||||
}
|
||||
|
||||
let (plan, _end_state) = plan_rc_insertion_for_block(
|
||||
&insts,
|
||||
terminator.as_ref(),
|
||||
initial_state.unwrap_or(&empty_state),
|
||||
);
|
||||
let (new_insts, new_spans, new_terminator, new_terminator_span) =
|
||||
apply_rc_plan(insts, spans, terminator, terminator_span, plan, &mut stats);
|
||||
|
||||
@ -143,10 +212,11 @@ pub fn insert_rc_instructions(module: &mut MirModule) -> RcInsertionStats {
|
||||
fn plan_rc_insertion_for_block(
|
||||
insts: &[MirInstruction],
|
||||
terminator: Option<&MirInstruction>,
|
||||
) -> RcPlan {
|
||||
initial_ptr_to_value: &HashMap<ValueId, ValueId>,
|
||||
) -> (RcPlan, HashMap<ValueId, ValueId>) {
|
||||
let mut plan = RcPlan { drops: Vec::new() };
|
||||
|
||||
let mut ptr_to_value: HashMap<ValueId, ValueId> = HashMap::new();
|
||||
let mut ptr_to_value: HashMap<ValueId, ValueId> = initial_ptr_to_value.clone();
|
||||
let mut null_values: HashSet<ValueId> = HashSet::new();
|
||||
|
||||
for (idx, inst) in insts.iter().enumerate() {
|
||||
@ -211,7 +281,7 @@ fn plan_rc_insertion_for_block(
|
||||
}
|
||||
}
|
||||
|
||||
plan
|
||||
(plan, ptr_to_value)
|
||||
}
|
||||
|
||||
#[cfg(feature = "rc-insertion-minimal")]
|
||||
|
||||
Reference in New Issue
Block a user