phase29aa(p1): rc insertion plan/apply refactor
This commit is contained in:
@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user