phase29aa(p4): propagate rc state along jump chain to return

This commit is contained in:
2025-12-28 01:56:36 +09:00
parent d70d9e3b89
commit bf7b203586
5 changed files with 193 additions and 37 deletions

View File

@ -2,14 +2,15 @@
## Current Focus: Phase 29aa P4Jump-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 新設なし)

View File

@ -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 29aaP3 COMPLETE / P4 planned: RC insertion safety expansionCFG-aware**
- **Phase 29aaP4 COMPLETE / P5 planned: RC insertion safety expansionCFG-aware**
- 進捗: P2 ✅ 完了Jump/Branch 終端で cleanup を入れない契約の SSOT 化)
- 進捗: P3 ✅ 完了Jump→Return single-predecessor state 伝播)
- : P4Jump-chain propagation to Return
- 進捗: P4 ✅ 完了Jump-chain propagation to Return
- 次: P5multi-predecessor/PHI/loop/early-exit の安全設計)
- 入口: `docs/development/current/main/phases/phase-29aa/README.md`
- **Phase 29xplanned, post self-host: De-Rust runtime for LLVM execution**

View File

@ -1,6 +1,6 @@
# Phase 29aa: RC insertion safety expansionCFG-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 伝播を許可
- cycleloop検出時は 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

View File

@ -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);

View File

@ -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(&current) {
break;
}
if let Some(&pos) = index.get(&current) {
for node in &path[pos..] {
cycles.insert(*node);
}
break;
}
index.insert(current, path.len());
path.push(current);
let Some(next) = jump_chain_next.get(&current) 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],