phase29aa(p1): rc insertion plan/apply refactor

This commit is contained in:
2025-12-27 16:02:32 +09:00
parent d32b72653c
commit b6e80943a3
5 changed files with 191 additions and 102 deletions

View File

@ -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 = <new> 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<DropSite>,
}
#[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<ValueId>,
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<ValueId, ValueId> = HashMap::new();
// Track which ValueIds are known null (explicit drop detection)
let mut null_values: HashSet<ValueId> = 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<ValueId> = {
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<ValueId, ValueId> = HashMap::new();
let mut null_values: HashSet<ValueId> = 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<ValueId> = {
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<MirInstruction>,
spans: Vec<Span>,
terminator: Option<MirInstruction>,
terminator_span: Option<Span>,
plan: RcPlan,
stats: &mut RcInsertionStats,
) -> (
Vec<MirInstruction>,
Vec<Span>,
Option<MirInstruction>,
Option<Span>,
) {
let mut drops_before_instr: Vec<Vec<DropSite>> = vec![Vec::new(); insts.len()];
let mut drops_before_terminator: Vec<DropSite> = 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)
}