From 02c01758b30ada21887d9b711ddadba43944bc73 Mon Sep 17 00:00:00 2001 From: tomoaki Date: Sun, 28 Dec 2025 04:43:19 +0900 Subject: [PATCH] phase29aa(p5): multi-pred return join when states match --- docs/development/current/main/10-Now.md | 10 +- .../current/main/phases/phase-29aa/README.md | 25 ++++- src/bin/rc_insertion_selfcheck.rs | 95 +++++++++++++++++++ src/mir/passes/rc_insertion.rs | 59 +++++++++++- 4 files changed, 184 insertions(+), 5 deletions(-) diff --git a/docs/development/current/main/10-Now.md b/docs/development/current/main/10-Now.md index d5eaa77c..ce6d493e 100644 --- a/docs/development/current/main/10-Now.md +++ b/docs/development/current/main/10-Now.md @@ -1,11 +1,17 @@ # Self Current Task — Now (main) -## Current Focus: Phase 29aa P4(Jump-chain propagation to Return) +## Current Focus: Phase 29aa P5(Multi-predecessor Return join) + +**2025-12-28: Phase 29aa P5 完了** ✅ +- 目的: Return block が複数 predecessor のとき、incoming state が完全一致する場合のみ ReturnCleanup を成立させる +- 入口: `docs/development/current/main/phases/phase-29aa/README.md` +- Safety Contract: state 不一致なら join state を作らない(誤 release 防止) +- Selfcheck: Case 3.7(state一致→cleanup)/ Case 3.8(state不一致→no cleanup)PASS +- 検証: quick 154/154 PASS / selfcheck PASS **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 を成立 diff --git a/docs/development/current/main/phases/phase-29aa/README.md b/docs/development/current/main/phases/phase-29aa/README.md index 0601cf17..ecc01936 100644 --- a/docs/development/current/main/phases/phase-29aa/README.md +++ b/docs/development/current/main/phases/phase-29aa/README.md @@ -1,6 +1,6 @@ # Phase 29aa: RC insertion safety expansion(CFG-aware design) -Status: P4 Complete (Jump-chain propagation to Return) +Status: P5 Complete (Multi-predecessor Return join) Scope: Phase 29z の単一block限定実装から、誤releaseを起こさない形で CFG-aware に拡張するための設計を固める。 Entry: @@ -24,6 +24,7 @@ Progress: - P2: Jump/Branch 終端では cleanup を入れない契約を SSOT 化(Fail-Fast guard) - P3: Jump→Return(単一 predecessor)で state 伝播し ReturnCleanup を成立させる(P2維持) - P4: Jump-chain(単一 predecessor 直列)で state 伝播し ReturnCleanup を成立させる(P2/P3 維持) +- P5: Multi-predecessor Return で incoming state が完全一致する場合のみ ReturnCleanup を成立させる(P2/P3/P4 維持) P3 SSOT: - Contract: @@ -52,3 +53,25 @@ P4 SSOT: - quick 154/154 PASS 維持 - `cargo run --bin rc_insertion_selfcheck --features rc-insertion-minimal` PASS - 既定OFF維持(featureなしは no-op) + +P5 SSOT: +- Contract: + - cleanup は Return block の BeforeTerminator のみ(Jump/Branch block には入れない) + - Return の multi-predecessor 合流は次の条件を全部満たすときだけ許可: + 1. 対象 block の terminator は Return + 2. predecessor が 2 以上 + 3. 全 predecessor の end_state が完全一致(同じ ptr→value map) + 4. 一致した state が非 empty + - 条件を満たさない場合は initial_state を作らない(ReturnCleanup なし) + - 条件NG時に古い initial_state が残らないよう、毎回再計算して remove する(落とし穴1対策) + - initial_state を set/remove したら changed フラグを更新(落とし穴2対策) +- Non-goals: + - 合流 state の "部分一致" / subset / merge(PHI相当なので禁止) + - Branch/PHI/loop/early-exit の cleanup + - Jump block への release 挿入(P2維持) +- Acceptance: + - quick 154/154 PASS 維持 + - `cargo run --bin rc_insertion_selfcheck --features rc-insertion-minimal` PASS + - selfcheck Case 3.7(state一致 → Return block に 1 cleanup)PASS + - selfcheck Case 3.8(state不一致 → 全ブロック 0 cleanup)PASS + - 既定OFF維持(featureなしは no-op) diff --git a/src/bin/rc_insertion_selfcheck.rs b/src/bin/rc_insertion_selfcheck.rs index a6ef3c9e..519c1f3f 100644 --- a/src/bin/rc_insertion_selfcheck.rs +++ b/src/bin/rc_insertion_selfcheck.rs @@ -280,6 +280,101 @@ fn main() { "jump_chain_single_pred", ); + // Case 3.7: Multi-predecessor Return with MATCHING states + // P5: すべての incoming end_state が完全一致する場合のみ ReturnCleanup を成立させる + let ptr = ValueId::new(370); + let v_shared = ValueId::new(23); // 同じValueIdを両predecessorで使用 + + let mut block_a = BasicBlock::new(BasicBlockId::new(0)); + block_a.instructions = vec![MirInstruction::Store { value: v_shared, ptr }]; + block_a.instruction_spans = vec![Span::unknown()]; + block_a.terminator = Some(MirInstruction::Jump { + target: BasicBlockId::new(2), + edge_args: None, + }); + block_a.terminator_span = Some(Span::unknown()); + + let mut block_b = BasicBlock::new(BasicBlockId::new(1)); + block_b.instructions = vec![MirInstruction::Store { value: v_shared, ptr }]; + block_b.instruction_spans = vec![Span::unknown()]; + block_b.terminator = Some(MirInstruction::Jump { + target: BasicBlockId::new(2), + edge_args: None, + }); + block_b.terminator_span = Some(Span::unknown()); + + let mut block_ret = BasicBlock::new(BasicBlockId::new(2)); + block_ret.instructions = vec![]; + block_ret.instruction_spans = vec![]; + block_ret.terminator = Some(MirInstruction::Return { value: None }); + block_ret.terminator_span = Some(Span::unknown()); + + let module = build_module_with_blocks( + vec![block_a, block_b, block_ret], + BasicBlockId::new(0), + "selfcheck_multi_pred_return_match", + "selfcheck_mod3p7", + ); + assert_release_counts_in_blocks( + module, + "selfcheck_multi_pred_return_match", + 1, // 全体で1(Return blockのみ) + &[ + (BasicBlockId::new(0), 0), // Jump block: cleanup禁止 + (BasicBlockId::new(1), 0), // Jump block: cleanup禁止 + (BasicBlockId::new(2), 1), // Return block: multi-pred join成功 + ], + "multi_pred_return_match", + ); + + // Case 3.8: Multi-predecessor Return with MISMATCHING states (negative test) + // P5: state不一致 → join state を作らない → ReturnCleanup なし + let ptr = ValueId::new(380); + let v1 = ValueId::new(24); + let v2 = ValueId::new(25); // 異なるValueId → state不一致 + + 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(2), + edge_args: None, + }); + block_a.terminator_span = Some(Span::unknown()); + + let mut block_b = BasicBlock::new(BasicBlockId::new(1)); + block_b.instructions = vec![MirInstruction::Store { value: v2, ptr }]; // v2 != v1 → 不一致 + block_b.instruction_spans = vec![Span::unknown()]; + block_b.terminator = Some(MirInstruction::Jump { + target: BasicBlockId::new(2), + edge_args: None, + }); + block_b.terminator_span = Some(Span::unknown()); + + let mut block_ret = BasicBlock::new(BasicBlockId::new(2)); + block_ret.instructions = vec![]; + block_ret.instruction_spans = vec![]; + block_ret.terminator = Some(MirInstruction::Return { value: None }); + block_ret.terminator_span = Some(Span::unknown()); + + let module = build_module_with_blocks( + vec![block_a, block_b, block_ret], + BasicBlockId::new(0), + "selfcheck_multi_pred_return_mismatch", + "selfcheck_mod3p8", + ); + assert_release_counts_in_blocks( + module, + "selfcheck_multi_pred_return_mismatch", + 0, // 全体で0(join stateを作らないのでcleanupなし) + &[ + (BasicBlockId::new(0), 0), // Jump block: cleanup禁止 + (BasicBlockId::new(1), 0), // Jump block: cleanup禁止 + (BasicBlockId::new(2), 0), // Return block: state不一致 → join失敗 + ], + "multi_pred_return_mismatch", + ); + // Case 4: Jump terminator should NOT inject block-end cleanup (unsafe cross-block) let ptr = ValueId::new(400); let v1 = ValueId::new(30); diff --git a/src/mir/passes/rc_insertion.rs b/src/mir/passes/rc_insertion.rs index b7704da0..611337c5 100644 --- a/src/mir/passes/rc_insertion.rs +++ b/src/mir/passes/rc_insertion.rs @@ -154,6 +154,8 @@ pub fn insert_rc_instructions(module: &mut MirModule) -> RcInsertionStats { let empty_state: HashMap = HashMap::new(); let mut initial_state: HashMap> = HashMap::new(); + // P5: end_states を保持して multi-pred join に使う + let mut end_states: HashMap> = HashMap::new(); let max_iters = func.blocks.len().max(1); for iter in 0..max_iters { let mut changed = false; @@ -165,6 +167,9 @@ pub fn insert_rc_instructions(module: &mut MirModule) -> RcInsertionStats { state_in, ); + // P5: end_state を保存(multi-pred join で使う) + end_states.insert(*bid, end_state.clone()); + let Some(target) = jump_chain_next.get(bid).copied() else { continue; }; @@ -188,6 +193,53 @@ pub fn insert_rc_instructions(module: &mut MirModule) -> RcInsertionStats { } } + // P5: multi-predecessor Return join 判定 + // すべての incoming end_state が完全一致する場合のみ ReturnCleanup を成立させる + for (bid, block) in &func.blocks { + // Return block のみ対象 + let Some(MirInstruction::Return { .. }) = block.terminator.as_ref() else { + continue; + }; + + let preds = predecessors.get(bid).cloned().unwrap_or_default(); + if preds.len() < 2 { + // 単一predecessor は既存ロジック(jump_chain_next)で処理済み + continue; + } + + // 全predecessorのend_stateを収集 + let mut pred_end_states: Vec<&HashMap> = Vec::new(); + for pred_bid in &preds { + if let Some(end_st) = end_states.get(pred_bid) { + pred_end_states.push(end_st); + } + } + + // 全部一致かつ非empty なら採用、そうでなければ削除 + let should_join = pred_end_states.len() == preds.len() + && !pred_end_states.is_empty() + && pred_end_states.iter().all(|s| *s == pred_end_states[0]) + && !pred_end_states[0].is_empty(); + + if should_join { + let new_state = pred_end_states[0].clone(); + // Fail-Fast ガード + debug_assert!( + matches!(block.terminator.as_ref(), Some(MirInstruction::Return { .. })), + "rc_insertion: multi-pred join only for Return blocks" + ); + if initial_state.get(bid) != Some(&new_state) { + initial_state.insert(*bid, new_state); + changed = true; // 収束フラグ更新 + } + } else { + // 条件NGなら古い値を残さない(落とし穴1対策) + if initial_state.remove(bid).is_some() { + changed = true; // 収束フラグ更新 + } + } + } + if !changed { break; } @@ -218,9 +270,12 @@ pub fn insert_rc_instructions(module: &mut MirModule) -> RcInsertionStats { 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); + // P5: multi-pred Return も許可(pred_count >= 2 で Return の場合) + let is_multi_pred_return = pred_count >= 2 + && matches!(terminator.as_ref(), Some(MirInstruction::Return { .. })); debug_assert!( - pred_count == 1, - "rc_insertion: initial state requires single predecessor" + pred_count == 1 || is_multi_pred_return, + "rc_insertion: initial state requires single predecessor or multi-pred Return" ); debug_assert!( matches!(terminator.as_ref(), Some(MirInstruction::Return { .. }))