phase29aa(p4): propagate rc state along jump chain to return
This commit is contained in:
@ -2,14 +2,15 @@
|
||||
|
||||
## Current Focus: Phase 29aa P4(Jump-chain propagation to Return)
|
||||
|
||||
**2025-12-27: Phase 29aa P4 完了** ✅
|
||||
- 目的: Jump の直列チェーン(単一 predecessor)を通して ReturnCleanup を成立させる(cleanup は Return block のみ)
|
||||
- 入口: `docs/development/current/main/phases/phase-29aa/README.md`
|
||||
- 次: Phase 29aa P5(候補: multi-predecessor/PHI/loop/early-exit の安全設計)
|
||||
|
||||
**2025-12-27: Phase 29aa P3 完了** ✅
|
||||
- 目的: Jump→Return(単一 predecessor)で state 伝播し ReturnCleanup を成立
|
||||
- 入口: `docs/development/current/main/phases/phase-29aa/README.md`
|
||||
|
||||
**2025-12-27: Phase 29aa P4 進行中**
|
||||
- 目的: Jump の直列チェーン(単一 predecessor)を通して ReturnCleanup を成立させる(cleanup は Return block のみ)
|
||||
- 入口: `docs/development/current/main/phases/phase-29aa/README.md`
|
||||
|
||||
**2025-12-27: Phase 29z P2 closeout** ✅
|
||||
- `src/mir/passes/rc_insertion.rs`: `Store` 上書き + `Store null`(explicit drop)+ Return終端cleanup の最小 release 挿入(単一block・安全ガード)
|
||||
- 既定OFF: Cargo feature `rc-insertion-minimal`(env var 新設なし)
|
||||
|
||||
@ -68,10 +68,11 @@ Related:
|
||||
- 入口: `docs/development/current/main/phases/phase-29z/README.md`
|
||||
- 指示書: `docs/development/current/main/phases/phase-29z/P0-RC_INSERTION_MINIMAL-INSTRUCTIONS.md`
|
||||
|
||||
- **Phase 29aa(P3 COMPLETE / P4 planned): RC insertion safety expansion(CFG-aware)**
|
||||
- **Phase 29aa(P4 COMPLETE / P5 planned): RC insertion safety expansion(CFG-aware)**
|
||||
- 進捗: P2 ✅ 完了(Jump/Branch 終端で cleanup を入れない契約の SSOT 化)
|
||||
- 進捗: P3 ✅ 完了(Jump→Return single-predecessor state 伝播)
|
||||
- 次: P4(Jump-chain propagation to Return)
|
||||
- 進捗: P4 ✅ 完了(Jump-chain propagation to Return)
|
||||
- 次: P5(multi-predecessor/PHI/loop/early-exit の安全設計)
|
||||
- 入口: `docs/development/current/main/phases/phase-29aa/README.md`
|
||||
|
||||
- **Phase 29x(planned, post self-host): De-Rust runtime for LLVM execution**
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# Phase 29aa: RC insertion safety expansion(CFG-aware design)
|
||||
|
||||
Status: P3 Complete (Jump→Return single-predecessor propagation)
|
||||
Status: P4 Complete (Jump-chain propagation to Return)
|
||||
Scope: Phase 29z の単一block限定実装から、誤releaseを起こさない形で CFG-aware に拡張するための設計を固める。
|
||||
|
||||
Entry:
|
||||
@ -23,6 +23,7 @@ Progress:
|
||||
- P1: rc_insertion を RcPlan の Plan→Apply 2-stage へ分離(挙動不変)
|
||||
- P2: Jump/Branch 終端では cleanup を入れない契約を SSOT 化(Fail-Fast guard)
|
||||
- P3: Jump→Return(単一 predecessor)で state 伝播し ReturnCleanup を成立させる(P2維持)
|
||||
- P4: Jump-chain(単一 predecessor 直列)で state 伝播し ReturnCleanup を成立させる(P2/P3 維持)
|
||||
|
||||
P3 SSOT:
|
||||
- Contract:
|
||||
@ -37,3 +38,17 @@ P3 SSOT:
|
||||
- quick 154/154 PASS 維持
|
||||
- `cargo run --bin rc_insertion_selfcheck --features rc-insertion-minimal` PASS
|
||||
- 既定OFF維持(featureなしは no-op)
|
||||
|
||||
P4 SSOT:
|
||||
- Contract:
|
||||
- cleanup は Return block の BeforeTerminator のみ(Jump/Branch block には入れない)
|
||||
- Jump-chain(単一 predecessor の Jump 直列)のみ state 伝播を許可
|
||||
- cycle(loop)検出時は debug_assert! で Fail-Fast、伝播は停止
|
||||
- Non-goals:
|
||||
- Branch/PHI/loop/early-exit の cleanup
|
||||
- multi-predecessor の合流(PHI 問題回避)
|
||||
- Jump block への release 挿入(P2維持)
|
||||
- Acceptance:
|
||||
- quick 154/154 PASS 維持
|
||||
- `cargo run --bin rc_insertion_selfcheck --features rc-insertion-minimal` PASS
|
||||
- 既定OFF維持(featureなしは no-op)
|
||||
|
||||
@ -234,6 +234,52 @@ fn main() {
|
||||
"jump_return_single_pred",
|
||||
);
|
||||
|
||||
// Case 3.6: Jump chain -> Return (single-predecessor only)
|
||||
let ptr = ValueId::new(360);
|
||||
let v1 = ValueId::new(22);
|
||||
|
||||
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::Jump {
|
||||
target: BasicBlockId::new(2),
|
||||
edge_args: None,
|
||||
});
|
||||
block_b.terminator_span = Some(Span::unknown());
|
||||
|
||||
let mut block_c = BasicBlock::new(BasicBlockId::new(2));
|
||||
block_c.instructions = vec![];
|
||||
block_c.instruction_spans = vec![];
|
||||
block_c.terminator = Some(MirInstruction::Return { value: None });
|
||||
block_c.terminator_span = Some(Span::unknown());
|
||||
|
||||
let module = build_module_with_blocks(
|
||||
vec![block_a, block_b, block_c],
|
||||
BasicBlockId::new(0),
|
||||
"selfcheck_jump_chain_single_pred",
|
||||
"selfcheck_mod3p6",
|
||||
);
|
||||
assert_release_counts_in_blocks(
|
||||
module,
|
||||
"selfcheck_jump_chain_single_pred",
|
||||
1,
|
||||
&[
|
||||
(BasicBlockId::new(0), 0),
|
||||
(BasicBlockId::new(1), 0),
|
||||
(BasicBlockId::new(2), 1),
|
||||
],
|
||||
"jump_chain_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);
|
||||
|
||||
@ -126,33 +126,77 @@ pub fn insert_rc_instructions(module: &mut MirModule) -> RcInsertionStats {
|
||||
}
|
||||
}
|
||||
|
||||
let mut propagated_initial: HashMap<BasicBlockId, HashMap<ValueId, ValueId>> =
|
||||
HashMap::new();
|
||||
let mut jump_chain_next: HashMap<BasicBlockId, BasicBlockId> = 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 {
|
||||
let Some(MirInstruction::Jump { target, .. }) = b.terminator.as_ref() 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);
|
||||
}
|
||||
if !func.blocks.contains_key(target) {
|
||||
continue;
|
||||
}
|
||||
let preds = predecessors.get(target).map(|p| p.as_slice()).unwrap_or(&[]);
|
||||
if preds.len() == 1 {
|
||||
debug_assert!(
|
||||
preds[0] == *bid,
|
||||
"rc_insertion: predecessor map mismatch for jump chain"
|
||||
);
|
||||
jump_chain_next.insert(*bid, *target);
|
||||
}
|
||||
}
|
||||
|
||||
let jump_chain_cycles = detect_jump_chain_cycles(&jump_chain_next);
|
||||
if !jump_chain_cycles.is_empty() {
|
||||
debug_assert!(
|
||||
false,
|
||||
"rc_insertion: jump-chain cycle detected; propagation disabled for cycle nodes"
|
||||
);
|
||||
}
|
||||
|
||||
let empty_state: HashMap<ValueId, ValueId> = HashMap::new();
|
||||
let mut initial_state: HashMap<BasicBlockId, HashMap<ValueId, ValueId>> = HashMap::new();
|
||||
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(
|
||||
&block.instructions,
|
||||
block.terminator.as_ref(),
|
||||
state_in,
|
||||
);
|
||||
|
||||
let Some(target) = jump_chain_next.get(bid).copied() else {
|
||||
continue;
|
||||
};
|
||||
if jump_chain_cycles.contains(bid) || jump_chain_cycles.contains(&target) {
|
||||
continue;
|
||||
}
|
||||
_ => {}
|
||||
if end_state.is_empty() {
|
||||
if initial_state.remove(&target).is_some() {
|
||||
changed = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
let needs_update = match initial_state.get(&target) {
|
||||
Some(existing) => existing != &end_state,
|
||||
None => true,
|
||||
};
|
||||
if needs_update {
|
||||
initial_state.insert(target, end_state);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if !changed {
|
||||
break;
|
||||
}
|
||||
if iter + 1 == max_iters {
|
||||
debug_assert!(
|
||||
false,
|
||||
"rc_insertion: jump-chain propagation did not converge; possible cycle"
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -171,28 +215,34 @@ pub fn insert_rc_instructions(module: &mut MirModule) -> RcInsertionStats {
|
||||
spans.push(Span::unknown());
|
||||
}
|
||||
|
||||
let empty_state: HashMap<ValueId, ValueId> = HashMap::new();
|
||||
let initial_state = propagated_initial.get(bid);
|
||||
if let Some(state) = initial_state {
|
||||
let initial_state_for_block = initial_state.get(bid);
|
||||
if let Some(state) = initial_state_for_block {
|
||||
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"
|
||||
matches!(terminator.as_ref(), Some(MirInstruction::Return { .. }))
|
||||
|| matches!(terminator.as_ref(), Some(MirInstruction::Jump { .. })),
|
||||
"rc_insertion: initial state only allowed for Jump/Return blocks"
|
||||
);
|
||||
debug_assert!(
|
||||
!state.is_empty(),
|
||||
"rc_insertion: initial state for Return block must be non-empty"
|
||||
"rc_insertion: initial state must be non-empty"
|
||||
);
|
||||
if matches!(terminator.as_ref(), Some(MirInstruction::Jump { .. })) {
|
||||
debug_assert!(
|
||||
jump_chain_next.contains_key(bid),
|
||||
"rc_insertion: jump-chain propagation requires single-predecessor Jump"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let (plan, _end_state) = plan_rc_insertion_for_block(
|
||||
&insts,
|
||||
terminator.as_ref(),
|
||||
initial_state.unwrap_or(&empty_state),
|
||||
initial_state_for_block.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);
|
||||
@ -208,6 +258,49 @@ pub fn insert_rc_instructions(module: &mut MirModule) -> RcInsertionStats {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rc-insertion-minimal")]
|
||||
fn detect_jump_chain_cycles(
|
||||
jump_chain_next: &HashMap<BasicBlockId, BasicBlockId>,
|
||||
) -> HashSet<BasicBlockId> {
|
||||
let mut cycles: HashSet<BasicBlockId> = HashSet::new();
|
||||
let mut done: HashSet<BasicBlockId> = HashSet::new();
|
||||
|
||||
for start in jump_chain_next.keys() {
|
||||
if done.contains(start) {
|
||||
continue;
|
||||
}
|
||||
let mut path: Vec<BasicBlockId> = Vec::new();
|
||||
let mut index: HashMap<BasicBlockId, usize> = HashMap::new();
|
||||
let mut current = *start;
|
||||
|
||||
loop {
|
||||
if done.contains(¤t) {
|
||||
break;
|
||||
}
|
||||
if let Some(&pos) = index.get(¤t) {
|
||||
for node in &path[pos..] {
|
||||
cycles.insert(*node);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
index.insert(current, path.len());
|
||||
path.push(current);
|
||||
|
||||
let Some(next) = jump_chain_next.get(¤t) else {
|
||||
break;
|
||||
};
|
||||
current = *next;
|
||||
}
|
||||
|
||||
for node in path {
|
||||
done.insert(node);
|
||||
}
|
||||
}
|
||||
|
||||
cycles
|
||||
}
|
||||
|
||||
#[cfg(feature = "rc-insertion-minimal")]
|
||||
fn plan_rc_insertion_for_block(
|
||||
insts: &[MirInstruction],
|
||||
|
||||
Reference in New Issue
Block a user