phase29z(p0): minimal RC insertion overwrite release
This commit is contained in:
77
src/bin/rc_insertion_selfcheck.rs
Normal file
77
src/bin/rc_insertion_selfcheck.rs
Normal file
@ -0,0 +1,77 @@
|
||||
//! Phase 29z P0: RC insertion minimal selfcheck (opt-in)
|
||||
//!
|
||||
//! This binary constructs a tiny synthetic MIR module containing Store overwrites
|
||||
//! and asserts that the RC insertion pass inserts ReleaseStrong in the expected place.
|
||||
//!
|
||||
//! Build/run (opt-in):
|
||||
//! - `cargo run --bin rc_insertion_selfcheck --features rc-insertion-minimal`
|
||||
|
||||
use nyash_rust::ast::Span;
|
||||
use nyash_rust::mir::passes::rc_insertion::insert_rc_instructions;
|
||||
use nyash_rust::mir::{
|
||||
BasicBlock, BasicBlockId, EffectMask, FunctionSignature, MirFunction, MirInstruction, MirModule,
|
||||
MirType, ValueId,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
|
||||
fn main() {
|
||||
let ptr = ValueId::new(100);
|
||||
let v1 = ValueId::new(1);
|
||||
let v2 = ValueId::new(2);
|
||||
|
||||
let mut block = BasicBlock::new(BasicBlockId::new(0));
|
||||
block.instructions = vec![
|
||||
MirInstruction::Store { value: v1, ptr },
|
||||
MirInstruction::Store { value: v2, ptr },
|
||||
];
|
||||
block.instruction_spans = vec![Span::unknown()];
|
||||
|
||||
let signature = FunctionSignature {
|
||||
name: "selfcheck_fn".to_string(),
|
||||
params: vec![],
|
||||
return_type: MirType::Void,
|
||||
effects: EffectMask::PURE,
|
||||
};
|
||||
let entry = block.id;
|
||||
let mut func = MirFunction::new(signature, entry);
|
||||
func.blocks = HashMap::from([(entry, block)]);
|
||||
|
||||
let mut module = MirModule::new("selfcheck_mod".to_string());
|
||||
module.add_function(func);
|
||||
|
||||
let stats = insert_rc_instructions(&mut module);
|
||||
if stats.release_inserted != 1 {
|
||||
eprintln!(
|
||||
"[FAIL] Expected release_inserted=1, got {}",
|
||||
stats.release_inserted
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
let func = module
|
||||
.get_function("selfcheck_fn")
|
||||
.expect("selfcheck function exists");
|
||||
let bb = func.blocks.get(&entry).expect("entry block exists");
|
||||
if bb.instructions.len() != 3 {
|
||||
eprintln!(
|
||||
"[FAIL] Expected 3 instructions after rewrite, got {}",
|
||||
bb.instructions.len()
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
if !matches!(bb.instructions[1], MirInstruction::ReleaseStrong { .. }) {
|
||||
eprintln!("[FAIL] Expected ReleaseStrong inserted at index=1");
|
||||
std::process::exit(1);
|
||||
}
|
||||
if bb.instructions.len() != bb.instruction_spans.len() {
|
||||
eprintln!(
|
||||
"[FAIL] Span count mismatch: insts={}, spans={}",
|
||||
bb.instructions.len(),
|
||||
bb.instruction_spans.len()
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
println!("[PASS] rc_insertion_selfcheck");
|
||||
}
|
||||
|
||||
@ -1,16 +1,30 @@
|
||||
//! Phase 29y.1 Task 2: RC insertion pass (entry point only)
|
||||
//! Phase 29z P0: RC insertion pass - Minimal overwrite release
|
||||
//!
|
||||
//! This pass runs after MIR optimization and verification, before backend codegen.
|
||||
//! Currently a no-op skeleton - actual RC insertion logic will be added later.
|
||||
//! Implements minimal RC insertion: overwrite release only (x = <new> releases old value).
|
||||
//!
|
||||
//! SSOT: docs/development/current/main/phases/phase-29y/20-RC-INSERTION-SSOT.md
|
||||
//!
|
||||
//! Design constraints:
|
||||
//! - SSA last-use 禁止: weak_to_strong() で観測できるため
|
||||
//! - 許可される drop 点: explicit drop, 上書き, スコープ終端のみ
|
||||
//! - Default OFF: cfg(debug_assertions) only (no impact on release builds)
|
||||
//!
|
||||
//! Phase 29z P0 scope (minimal):
|
||||
//! - ✅ Overwrite release (Store instruction): x = <new> triggers ReleaseStrong for old value
|
||||
//! - ❌ PHI handling (out of scope)
|
||||
//! - ❌ Loop/early-exit cleanup (out of scope)
|
||||
//! - ❌ Cross-block tracking (single-block only)
|
||||
|
||||
use crate::mir::MirModule;
|
||||
|
||||
#[cfg(feature = "rc-insertion-minimal")]
|
||||
use crate::ast::Span;
|
||||
#[cfg(feature = "rc-insertion-minimal")]
|
||||
use crate::mir::{MirInstruction, ValueId};
|
||||
#[cfg(feature = "rc-insertion-minimal")]
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Statistics from RC insertion pass
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct RcInsertionStats {
|
||||
@ -24,42 +38,98 @@ pub struct RcInsertionStats {
|
||||
pub blocks_visited: usize,
|
||||
}
|
||||
|
||||
/// Phase 29y.1: RC insertion pass (skeleton only)
|
||||
/// Phase 29z P0: RC insertion pass - Minimal overwrite release
|
||||
///
|
||||
/// This pass is called after MIR optimization and verification.
|
||||
/// Currently a no-op that returns empty statistics.
|
||||
/// Implements minimal case: overwrite release (x = <new> releases old value).
|
||||
///
|
||||
/// Future: Will insert retain/release instructions at:
|
||||
/// - explicit drop points
|
||||
/// - variable overwrite points
|
||||
/// - scope exit points
|
||||
/// **CRITICAL SAFETY NOTES**:
|
||||
/// 1. ReleaseStrong does SSA alias cleanup (releases all SSA values sharing same Arc)
|
||||
/// - MUST NOT release values still in use
|
||||
/// - Safety guard: Skip release if `old_value == value` (same Arc, no-op overwrite)
|
||||
/// 2. Span mismatch: `instruction_spans` may not match `instructions` length
|
||||
/// - Fill missing spans with `Span::unknown()` to prevent panic
|
||||
///
|
||||
/// **Scope** (Phase 29z P0 minimal):
|
||||
/// - ✅ Single-block overwrite detection
|
||||
/// - ✅ Safety guards (SSA alias cleanup protection)
|
||||
/// - ❌ PHI/loop/early-exit (out of scope)
|
||||
/// - ❌ Cross-block tracking (out of scope)
|
||||
///
|
||||
/// **Opt-in**: Default OFF, enabled only with Cargo feature `rc-insertion-minimal`
|
||||
pub fn insert_rc_instructions(module: &mut MirModule) -> RcInsertionStats {
|
||||
let mut stats = RcInsertionStats::default();
|
||||
|
||||
// Skeleton: iterate module structure without modification
|
||||
for (_name, func) in &module.functions {
|
||||
stats.functions_processed += 1;
|
||||
stats.blocks_visited += func.blocks.len();
|
||||
// Phase 29z P0: Default OFF unless explicitly enabled.
|
||||
// No new environment variables (per Phase 29z P0 constraints).
|
||||
#[cfg(not(feature = "rc-insertion-minimal"))]
|
||||
{
|
||||
// No-op pass (just count structures)
|
||||
for (_name, func) in &module.functions {
|
||||
stats.functions_processed += 1;
|
||||
stats.blocks_visited += func.blocks.len();
|
||||
}
|
||||
return stats;
|
||||
}
|
||||
|
||||
// Phase 29y.1: No actual insertion yet (no-op pass)
|
||||
// Future phases will add:
|
||||
// - Drop point detection
|
||||
// - Scope/binding tracking
|
||||
// - PHI input liveness analysis
|
||||
#[cfg(feature = "rc-insertion-minimal")]
|
||||
{
|
||||
// Implement minimal overwrite release
|
||||
for (_name, func) in &mut module.functions {
|
||||
stats.functions_processed += 1;
|
||||
|
||||
stats
|
||||
}
|
||||
for (_bid, block) in &mut func.blocks {
|
||||
stats.blocks_visited += 1;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
// Track what value each ptr currently holds (single-block scope)
|
||||
let mut ptr_to_value: HashMap<ValueId, ValueId> = HashMap::new();
|
||||
|
||||
#[test]
|
||||
fn test_skeleton_returns_stats() {
|
||||
let mut module = MirModule::default();
|
||||
let stats = insert_rc_instructions(&mut module);
|
||||
assert_eq!(stats.keepalive_inserted, 0);
|
||||
assert_eq!(stats.release_inserted, 0);
|
||||
// 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);
|
||||
|
||||
// SAFETY: Ensure spans match instructions length (fill with Span::unknown() if needed)
|
||||
// instruction_spans and instructions may not always match in length
|
||||
while spans.len() < insts.len() {
|
||||
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);
|
||||
|
||||
for (inst, span) in insts.into_iter().zip(spans.into_iter()) {
|
||||
// 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
|
||||
ptr_to_value.insert(*ptr, *value);
|
||||
}
|
||||
|
||||
// Add original instruction (Store or any other instruction)
|
||||
new_insts.push(inst);
|
||||
new_spans.push(span);
|
||||
}
|
||||
|
||||
// Replace block instructions with new sequence
|
||||
block.instructions = new_insts;
|
||||
block.instruction_spans = new_spans;
|
||||
}
|
||||
}
|
||||
|
||||
stats
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user