phase29aa(p8): propagate null_values across jump chain

This commit is contained in:
2025-12-28 06:32:31 +09:00
parent 6d17a0f988
commit d743503603
2 changed files with 102 additions and 5 deletions

View File

@ -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=2ReturnCleanup が残る → 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 = 1ReturnCleanup なし)
// 失敗時は Block B = 2ReturnCleanup が残る = null 伝播が効いていない)
assert_release_counts_in_blocks(
module,
"selfcheck_null_propagation",
2, // 合計 2Block 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");
}

View File

@ -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 を合流しない(保守的に空集合)
// 古い状態が残らないよう明示的に removeP5/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 昇順)にする