phase29aa(p8): propagate null_values across jump chain
This commit is contained in:
@ -549,6 +549,71 @@ fn main() {
|
||||
assert_release_inserted(module.clone(), "selfcheck_sorted_values", entry, 1, "sorted_values_count");
|
||||
assert_all_release_values_sorted(module, "selfcheck_sorted_values", "sorted_values_order");
|
||||
|
||||
// Case 3.12: Null propagation across Jump-chain
|
||||
// P8: null_val が Block A から Block B に伝播され、Block B で Store null_val が explicit drop として認識される
|
||||
// Block A: Store real_val → ptr1, Const null_val = Null, Store null_val → ptr1 (explicit drop)
|
||||
// Block B (Jump from A): Store real_val → ptr2, Store null_val → ptr2
|
||||
// → null_val は A から B に伝播
|
||||
// → Block B で ptr2 に null_val を Store すると explicit drop 扱い
|
||||
//
|
||||
// 期待:
|
||||
// - Block A: ReleaseStrong 1 個(Store null_val → ptr1 の explicit drop で old(real_val) を release)
|
||||
// - Block B: ReleaseStrong 1 個(Store null_val → ptr2 の explicit drop で old(real_val) を release)
|
||||
// - ReturnCleanup: 0 個(ptr2 は null store で消えている)
|
||||
// - 合計: 2 個
|
||||
// - ❌ 失敗時: B=2(ReturnCleanup が残る → null 伝播が効いていない)
|
||||
let ptr1 = ValueId::new(3120);
|
||||
let ptr2 = ValueId::new(3121);
|
||||
let null_val = ValueId::new(3122);
|
||||
let real_val = ValueId::new(3123);
|
||||
|
||||
let block_a_id = BasicBlockId::new(0);
|
||||
let block_b_id = BasicBlockId::new(1);
|
||||
|
||||
// Block A
|
||||
let mut block_a = BasicBlock::new(block_a_id);
|
||||
block_a.instructions = vec![
|
||||
MirInstruction::Store { value: real_val, ptr: ptr1 },
|
||||
MirInstruction::Const { dst: null_val, value: ConstValue::Null },
|
||||
MirInstruction::Store { value: null_val, ptr: ptr1 }, // explicit drop → ReleaseStrong 1
|
||||
];
|
||||
block_a.instruction_spans = vec![Span::unknown(); 3];
|
||||
block_a.terminator = Some(MirInstruction::Jump {
|
||||
target: block_b_id,
|
||||
edge_args: None,
|
||||
});
|
||||
block_a.terminator_span = Some(Span::unknown());
|
||||
|
||||
// Block B
|
||||
let mut block_b = BasicBlock::new(block_b_id);
|
||||
block_b.instructions = vec![
|
||||
MirInstruction::Store { value: real_val, ptr: ptr2 },
|
||||
MirInstruction::Store { value: null_val, ptr: ptr2 }, // null_val from A (propagated) → ReleaseStrong 1
|
||||
];
|
||||
block_b.instruction_spans = vec![Span::unknown(); 2];
|
||||
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],
|
||||
block_a_id,
|
||||
"selfcheck_null_propagation",
|
||||
"selfcheck_mod3p12",
|
||||
);
|
||||
|
||||
// 検証: Block A = 1, Block B = 1(ReturnCleanup なし)
|
||||
// 失敗時は Block B = 2(ReturnCleanup が残る = null 伝播が効いていない)
|
||||
assert_release_counts_in_blocks(
|
||||
module,
|
||||
"selfcheck_null_propagation",
|
||||
2, // 合計 2(Block A=1 + Block B=1)
|
||||
&[
|
||||
(block_a_id, 1), // explicit drop
|
||||
(block_b_id, 1), // explicit drop (null propagated)
|
||||
],
|
||||
"null_propagation",
|
||||
);
|
||||
|
||||
println!("[PASS] rc_insertion_selfcheck");
|
||||
}
|
||||
|
||||
|
||||
@ -153,22 +153,28 @@ pub fn insert_rc_instructions(module: &mut MirModule) -> RcInsertionStats {
|
||||
}
|
||||
|
||||
let empty_state: HashMap<ValueId, ValueId> = HashMap::new();
|
||||
let empty_null: HashSet<ValueId> = HashSet::new(); // P8: null 伝播用
|
||||
let mut initial_state: HashMap<BasicBlockId, HashMap<ValueId, ValueId>> = HashMap::new();
|
||||
let mut initial_null_values: HashMap<BasicBlockId, HashSet<ValueId>> = HashMap::new(); // P8
|
||||
// P5: end_states を保持して multi-pred join に使う
|
||||
let mut end_states: HashMap<BasicBlockId, HashMap<ValueId, ValueId>> = HashMap::new();
|
||||
let mut end_null_states: HashMap<BasicBlockId, HashSet<ValueId>> = HashMap::new(); // P8
|
||||
let max_iters = func.blocks.len().max(1);
|
||||
for iter in 0..max_iters {
|
||||
let mut changed = false;
|
||||
for (bid, block) in &func.blocks {
|
||||
let state_in = initial_state.get(bid).unwrap_or(&empty_state);
|
||||
let (_plan, end_state) = plan_rc_insertion_for_block(
|
||||
let null_in = initial_null_values.get(bid).unwrap_or(&empty_null); // P8
|
||||
let (_plan, end_state, end_null) = plan_rc_insertion_for_block(
|
||||
&block.instructions,
|
||||
block.terminator.as_ref(),
|
||||
state_in,
|
||||
null_in, // P8
|
||||
);
|
||||
|
||||
// P5: end_state を保存(multi-pred join で使う)
|
||||
end_states.insert(*bid, end_state.clone());
|
||||
end_null_states.insert(*bid, end_null); // P8
|
||||
|
||||
let Some(target) = jump_chain_next.get(bid).copied() else {
|
||||
continue;
|
||||
@ -176,6 +182,23 @@ pub fn insert_rc_instructions(module: &mut MirModule) -> RcInsertionStats {
|
||||
if jump_chain_cycles.contains(bid) || jump_chain_cycles.contains(&target) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// P8: null_values 伝播(ptr_to_value が空でも null は伝播する)
|
||||
if let Some(end_null) = end_null_states.get(bid) {
|
||||
if !end_null.is_empty() {
|
||||
let needs_null_update = match initial_null_values.get(&target) {
|
||||
Some(existing) => existing != end_null,
|
||||
None => true,
|
||||
};
|
||||
if needs_null_update {
|
||||
initial_null_values.insert(target, end_null.clone());
|
||||
changed = true;
|
||||
}
|
||||
} else if initial_null_values.remove(&target).is_some() {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if end_state.is_empty() {
|
||||
if initial_state.remove(&target).is_some() {
|
||||
changed = true;
|
||||
@ -207,6 +230,12 @@ pub fn insert_rc_instructions(module: &mut MirModule) -> RcInsertionStats {
|
||||
continue;
|
||||
}
|
||||
|
||||
// P8: multi-pred Return では null_values を合流しない(保守的に空集合)
|
||||
// 古い状態が残らないよう明示的に remove(P5/P6 の initial_state.remove と同じ残留バグ対策)
|
||||
if initial_null_values.remove(bid).is_some() {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// 全predecessorのend_stateを収集
|
||||
let mut pred_end_states: Vec<&HashMap<ValueId, ValueId>> = Vec::new();
|
||||
for pred_bid in &preds {
|
||||
@ -310,10 +339,12 @@ pub fn insert_rc_instructions(module: &mut MirModule) -> RcInsertionStats {
|
||||
}
|
||||
}
|
||||
|
||||
let (plan, _end_state) = plan_rc_insertion_for_block(
|
||||
let null_in = initial_null_values.get(bid).unwrap_or(&empty_null); // P8
|
||||
let (plan, _end_state, _end_null) = plan_rc_insertion_for_block(
|
||||
&insts,
|
||||
terminator.as_ref(),
|
||||
initial_state_for_block.unwrap_or(&empty_state),
|
||||
null_in, // P8
|
||||
);
|
||||
let (new_insts, new_spans, new_terminator, new_terminator_span) =
|
||||
apply_rc_plan(insts, spans, terminator, terminator_span, plan, &mut stats);
|
||||
@ -377,11 +408,12 @@ fn plan_rc_insertion_for_block(
|
||||
insts: &[MirInstruction],
|
||||
terminator: Option<&MirInstruction>,
|
||||
initial_ptr_to_value: &HashMap<ValueId, ValueId>,
|
||||
) -> (RcPlan, HashMap<ValueId, ValueId>) {
|
||||
initial_null_values: &HashSet<ValueId>, // P8: CFG越し null 伝播
|
||||
) -> (RcPlan, HashMap<ValueId, ValueId>, HashSet<ValueId>) {
|
||||
let mut plan = RcPlan { drops: Vec::new() };
|
||||
|
||||
let mut ptr_to_value: HashMap<ValueId, ValueId> = initial_ptr_to_value.clone();
|
||||
let mut null_values: HashSet<ValueId> = HashSet::new();
|
||||
let mut null_values: HashSet<ValueId> = initial_null_values.clone(); // P8: 初期状態から開始
|
||||
|
||||
for (idx, inst) in insts.iter().enumerate() {
|
||||
match inst {
|
||||
@ -440,7 +472,7 @@ fn plan_rc_insertion_for_block(
|
||||
}
|
||||
}
|
||||
|
||||
(plan, ptr_to_value)
|
||||
(plan, ptr_to_value, null_values) // P8: end_null_values も返す
|
||||
}
|
||||
|
||||
/// P7: ReleaseStrong の values を決定的順序(ValueId 昇順)にする
|
||||
|
||||
Reference in New Issue
Block a user