From b6e80943a3ba82f83f6ce40ed79659b017096cfd Mon Sep 17 00:00:00 2001 From: tomoaki Date: Sat, 27 Dec 2025 16:02:32 +0900 Subject: [PATCH] phase29aa(p1): rc insertion plan/apply refactor --- docs/development/current/main/10-Now.md | 13 +- docs/development/current/main/30-Backlog.md | 6 +- .../current/main/phases/phase-29aa/README.md | 4 +- .../current/main/phases/phase-29z/README.md | 2 +- src/mir/passes/rc_insertion.rs | 268 ++++++++++++------ 5 files changed, 191 insertions(+), 102 deletions(-) diff --git a/docs/development/current/main/10-Now.md b/docs/development/current/main/10-Now.md index 8ff639dc..458681f6 100644 --- a/docs/development/current/main/10-Now.md +++ b/docs/development/current/main/10-Now.md @@ -1,18 +1,17 @@ # Self Current Task — Now (main) -## Current Focus: Phase 29aa P0(RC insertion safety expansion, design-first) +## Current Focus: Phase 29aa P1(RcPlan 2-stage, meaning-preserving) -**2025-12-27: Phase 29aa P0 Ready(design-first)** -- 目的: rc_insertion を CFG-aware に拡張する前提設計を固定(誤release防止) -- 入口: `docs/development/current/main/phases/phase-29aa/P0-RC_INSERTION_CFG_AWARE_DESIGN-INSTRUCTIONS.md` -- 次: Phase 29aa P1(RcPlan 2-stage, meaning-preserving)※実装反映待ち +**2025-12-27: Phase 29aa P1 完了** ✅ +- 目的: rc_insertion を RcPlan に閉じ込め、Plan→Apply 2-stage 化(挙動不変) +- 入口: `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 新設なし) - 検証: quick 154/154 PASS 維持 + `cargo run --bin rc_insertion_selfcheck --features rc-insertion-minimal` - 入口: `docs/development/current/main/phases/phase-29z/README.md` -- 次: Phase 29aa P0(CFG-aware design) +- 次: Phase 29aa P2(CFG-aware の最小ターゲット選定) **2025-12-27: Phase 29y P0 完了** ✅ - docs-first SSOT finalized(ABI/RC insertion/Observability) @@ -20,7 +19,7 @@ - pilot 実装(Phase 29y.1)の入口を固定 - Next steps(実装タスク3つ)を明文化 - 入口: `docs/development/current/main/phases/phase-29y/README.md` -- **Next**: Phase 29z(RC insertion minimal)または Phase 29x(De-Rust runtime)候補 +- **Next**: Phase 29aa(CFG-aware RC insertion)または Phase 29x(De-Rust runtime)候補 **2025-12-27: Phase 287 完了** ✅ - P0-P9完了(big files modularization / facade pattern / SSOT establishment / closeout) diff --git a/docs/development/current/main/30-Backlog.md b/docs/development/current/main/30-Backlog.md index bf6c220a..1e909af6 100644 --- a/docs/development/current/main/30-Backlog.md +++ b/docs/development/current/main/30-Backlog.md @@ -68,9 +68,9 @@ 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(P0 design-first): RC insertion safety expansion(CFG-aware)** - - 目的: rc_insertion を CFG-aware に拡張する前提設計を固定(誤release防止) - - P0最小成果: RcPlan(block/edge)構造、PHI/loop/early-exit 危険パターン整理、release安全条件の契約 +- **Phase 29aa(P1 COMPLETE): RC insertion safety expansion(CFG-aware)** + - 進捗: P1 ✅ 完了(RcPlan 導入+Plan→Apply 2-stage化、挙動不変) + - 次(P2): CFG-aware の最小ターゲットを 1 個に絞る(例: edge cleanup を Return 以外でどう扱うか) - 入口: `docs/development/current/main/phases/phase-29aa/README.md` - **Phase 29x(planned, post self-host): De-Rust runtime for LLVM execution** diff --git a/docs/development/current/main/phases/phase-29aa/README.md b/docs/development/current/main/phases/phase-29aa/README.md index ea423e5d..8e21e622 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: P0 Ready (design-first) +Status: P1 Complete (RcPlan 2-stage) Scope: Phase 29z の単一block限定実装から、誤releaseを起こさない形で CFG-aware に拡張するための設計を固める。 Entry: @@ -20,4 +20,4 @@ Deliverables (P0): Progress: - P0: CFG-aware 設計の固定(RcPlan/危険パターン/安全条件の契約) -- P1: rc_insertion を RcPlan の Plan→Apply 2-stage へ分離(挙動不変、実装待ち) +- P1: rc_insertion を RcPlan の Plan→Apply 2-stage へ分離(挙動不変) diff --git a/docs/development/current/main/phases/phase-29z/README.md b/docs/development/current/main/phases/phase-29z/README.md index caaf2a9a..07bf3198 100644 --- a/docs/development/current/main/phases/phase-29z/README.md +++ b/docs/development/current/main/phases/phase-29z/README.md @@ -24,5 +24,5 @@ Next Phase: Residuals(分類): - Design: CFG-aware RcPlan(block/edge 単位)の契約と Fail-Fast 方針、PHI/loop/early-exit の危険パターン整理、Branch/Jump 終端 cleanup 設計 -- Implementation: rc_insertion の解析→挿入(二段階)分離、null 伝搬の精度向上(copy以外の伝搬パターン追加)、Return 終端 cleanup の RcPlan 経由化 +- Implementation: null 伝搬の精度向上(copy以外の伝搬パターン追加) - Verification: quick 154/154 PASS 維持(既定OFF)+ `rc_insertion_selfcheck` opt-in 確認 diff --git a/src/mir/passes/rc_insertion.rs b/src/mir/passes/rc_insertion.rs index 6d3fdb8a..a9bab751 100644 --- a/src/mir/passes/rc_insertion.rs +++ b/src/mir/passes/rc_insertion.rs @@ -8,7 +8,7 @@ //! Design constraints: //! - SSA last-use 禁止: weak_to_strong() で観測できるため //! - 許可される drop 点: explicit drop, 上書き, スコープ終端のみ -//! - Default OFF: cfg(debug_assertions) only (no impact on release builds) +//! - Default OFF: feature-gated (no impact unless enabled) //! //! Phase 29z P0 scope (minimal): //! - ✅ Overwrite release (Store instruction): x = triggers ReleaseStrong for old value @@ -27,6 +27,35 @@ use crate::mir::types::ConstValue; #[cfg(feature = "rc-insertion-minimal")] use std::collections::{HashMap, HashSet}; +#[cfg(feature = "rc-insertion-minimal")] +#[derive(Debug, Clone)] +struct RcPlan { + drops: Vec, +} + +#[cfg(feature = "rc-insertion-minimal")] +#[derive(Debug, Clone)] +enum DropPoint { + BeforeInstr(usize), + BeforeTerminator, +} + +#[cfg(feature = "rc-insertion-minimal")] +#[derive(Debug, Clone)] +enum DropReason { + Overwrite, + ExplicitNull, + ReturnCleanup, +} + +#[cfg(feature = "rc-insertion-minimal")] +#[derive(Debug, Clone)] +struct DropSite { + at: DropPoint, + values: Vec, + reason: DropReason, +} + /// Statistics from RC insertion pass #[derive(Debug, Default, Clone)] pub struct RcInsertionStats { @@ -83,11 +112,6 @@ pub fn insert_rc_instructions(module: &mut MirModule) -> RcInsertionStats { for (_bid, block) in &mut func.blocks { stats.blocks_visited += 1; - // Track what value each ptr currently holds (single-block scope) - let mut ptr_to_value: HashMap = HashMap::new(); - // Track which ValueIds are known null (explicit drop detection) - let mut null_values: HashSet = HashSet::new(); - // Take ownership of instructions to rebuild with inserted releases let insts = std::mem::take(&mut block.instructions); let mut spans = std::mem::take(&mut block.instruction_spans); @@ -100,95 +124,161 @@ pub fn insert_rc_instructions(module: &mut MirModule) -> RcInsertionStats { spans.push(Span::unknown()); } - let mut new_insts = Vec::with_capacity(insts.len() * 2); // Extra space for releases - let mut new_spans = Vec::with_capacity(spans.len() * 2); + let plan = plan_rc_insertion_for_block(&insts, terminator.as_ref()); + let (new_insts, new_spans, new_terminator, new_terminator_span) = + apply_rc_plan(insts, spans, terminator, terminator_span, plan, &mut stats); - for (inst, span) in insts.into_iter().zip(spans.into_iter()) { - match &inst { - MirInstruction::Const { - dst, - value: ConstValue::Null, - } => { - null_values.insert(*dst); - } - MirInstruction::Const { dst, .. } => { - null_values.remove(dst); - } - MirInstruction::Copy { dst, src } => { - if null_values.contains(src) { - null_values.insert(*dst); - } else { - null_values.remove(dst); - } - } - _ => {} - } - - // Detect overwrite: Store to ptr that already holds a value - if let MirInstruction::Store { value, ptr } = &inst { - // Check if ptr already holds a value (potential overwrite) - if let Some(old_value) = ptr_to_value.get(ptr) { - // SAFETY GUARD: Don't release if old_value == new value (same Arc) - // ReleaseStrong does SSA alias cleanup, so releasing "still-needed" values breaks semantics - // If old_value == value, this is a no-op overwrite (x = x), no release needed - if old_value != value { - // Insert ReleaseStrong for old value BEFORE the Store - new_insts.push(MirInstruction::ReleaseStrong { - values: vec![*old_value], - }); - new_spans.push(span.clone()); // Reuse span from Store instruction - stats.release_inserted += 1; - } - } - - // Update tracking: ptr now holds new value (null clears tracking) - if null_values.contains(value) { - ptr_to_value.remove(ptr); - } else { - ptr_to_value.insert(*ptr, *value); - } - } - - // Add original instruction (Store or any other instruction) - new_insts.push(inst); - new_spans.push(span); - } - - // Block-end cleanup (single-block assumption): release remaining tracked values - if let Some(term) = terminator { - if matches!(term, MirInstruction::Return { .. }) && !ptr_to_value.is_empty() { - let release_values: Vec = { - let mut set = HashSet::new(); - for v in ptr_to_value.values() { - set.insert(*v); - } - set.into_iter().collect() - }; - if !release_values.is_empty() { - let span = terminator_span.clone().unwrap_or_else(Span::unknown); - new_insts.push(MirInstruction::ReleaseStrong { - values: release_values, - }); - new_spans.push(span); - stats.release_inserted += 1; - } - } - - // Re-attach terminator (with span alignment) - if let Some(span) = terminator_span { - new_spans.push(span); - } else { - new_spans.push(Span::unknown()); - } - new_insts.push(term); - } - - // Replace block instructions with new sequence block.instructions = new_insts; block.instruction_spans = new_spans; + block.terminator = new_terminator; + block.terminator_span = new_terminator_span; } } stats } } + +#[cfg(feature = "rc-insertion-minimal")] +fn plan_rc_insertion_for_block( + insts: &[MirInstruction], + terminator: Option<&MirInstruction>, +) -> RcPlan { + let mut plan = RcPlan { drops: Vec::new() }; + + let mut ptr_to_value: HashMap = HashMap::new(); + let mut null_values: HashSet = HashSet::new(); + + for (idx, inst) in insts.iter().enumerate() { + match inst { + MirInstruction::Const { + dst, + value: ConstValue::Null, + } => { + null_values.insert(*dst); + } + MirInstruction::Const { dst, .. } => { + null_values.remove(dst); + } + MirInstruction::Copy { dst, src } => { + if null_values.contains(src) { + null_values.insert(*dst); + } else { + null_values.remove(dst); + } + } + _ => {} + } + + if let MirInstruction::Store { value, ptr } = inst { + if let Some(old_value) = ptr_to_value.get(ptr) { + if old_value != value { + let reason = if null_values.contains(value) { + DropReason::ExplicitNull + } else { + DropReason::Overwrite + }; + plan.drops.push(DropSite { + at: DropPoint::BeforeInstr(idx), + values: vec![*old_value], + reason, + }); + } + } + + if null_values.contains(value) { + ptr_to_value.remove(ptr); + } else { + ptr_to_value.insert(*ptr, *value); + } + } + } + + if matches!(terminator, Some(MirInstruction::Return { .. })) && !ptr_to_value.is_empty() { + let release_values: Vec = { + let mut set = HashSet::new(); + for v in ptr_to_value.values() { + set.insert(*v); + } + set.into_iter().collect() + }; + if !release_values.is_empty() { + plan.drops.push(DropSite { + at: DropPoint::BeforeTerminator, + values: release_values, + reason: DropReason::ReturnCleanup, + }); + } + } + + plan +} + +#[cfg(feature = "rc-insertion-minimal")] +fn apply_rc_plan( + insts: Vec, + spans: Vec, + terminator: Option, + terminator_span: Option, + plan: RcPlan, + stats: &mut RcInsertionStats, +) -> ( + Vec, + Vec, + Option, + Option, +) { + let mut drops_before_instr: Vec> = vec![Vec::new(); insts.len()]; + let mut drops_before_terminator: Vec = Vec::new(); + + for drop_site in plan.drops { + match drop_site.at { + DropPoint::BeforeInstr(idx) => { + if idx < drops_before_instr.len() { + drops_before_instr[idx].push(drop_site); + } else { + debug_assert!( + idx < drops_before_instr.len(), + "rc_insertion plan references out-of-range instruction index" + ); + } + } + DropPoint::BeforeTerminator => { + drops_before_terminator.push(drop_site); + } + } + } + + let mut new_insts = Vec::with_capacity(insts.len() * 2); + let mut new_spans = Vec::with_capacity(spans.len() * 2); + + for (idx, (inst, span)) in insts.into_iter().zip(spans.into_iter()).enumerate() { + for drop_site in drops_before_instr[idx].drain(..) { + let _ = drop_site.reason; + new_insts.push(MirInstruction::ReleaseStrong { + values: drop_site.values, + }); + new_spans.push(span.clone()); + stats.release_inserted += 1; + } + + new_insts.push(inst); + new_spans.push(span); + } + + if !drops_before_terminator.is_empty() { + if !drops_before_terminator.is_empty() { + let span = terminator_span.clone().unwrap_or_else(Span::unknown); + for drop_site in drops_before_terminator { + let _ = drop_site.reason; + new_insts.push(MirInstruction::ReleaseStrong { + values: drop_site.values, + }); + new_spans.push(span.clone()); + stats.release_inserted += 1; + } + } + } + + (new_insts, new_spans, terminator, terminator_span) +}