phase29z(p0): minimal RC insertion overwrite release

This commit is contained in:
2025-12-27 15:03:05 +09:00
parent 2223c1309b
commit 977f105e4e
7 changed files with 207 additions and 31 deletions

View File

@ -41,6 +41,8 @@ plugins = ["dep:libloading"]
# MIR instruction diet PoC flags (scaffolding only; off by default)
mir_typeop_poc = []
mir_refbarrier_unify_poc = []
# Phase 29z P0: RC insertion pass opt-in (default OFF)
rc-insertion-minimal = []
# LLVM features split
# - llvm-harness: Python/llvmlite harness onlyinkwell不要
# - llvm-inkwell-legacy: historical Rust/inkwell backend参照用
@ -88,6 +90,11 @@ required-features = ["dynamic-file"]
name = "ny_mir_builder"
path = "src/bin/ny_mir_builder.rs"
[[bin]]
name = "rc_insertion_selfcheck"
path = "src/bin/rc_insertion_selfcheck.rs"
required-features = ["rc-insertion-minimal"]
# Examples for development - only available as examples, not bins
[[example]]
name = "gui_simple_notepad"

View File

@ -2,6 +2,12 @@
## Current Focus: Phase 29z P0RC insertion minimal
**2025-12-27: Phase 29z P0 完了**
- `src/mir/passes/rc_insertion.rs`: `Store` 上書きの最小 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`
**2025-12-27: Phase 29y P0 完了**
- docs-first SSOT finalizedABI/RC insertion/Observability
- 3つのSSOT文書10/20/30を Ready に確定

View File

@ -57,7 +57,10 @@ Related:
- 入口: `docs/development/current/main/phases/phase-29y/README.md`
- 次: Phase 29zRC insertion minimalまたは Phase 29xDe-Rust runtime候補
- **Phase 29znext, implementation-minimal: RC insertion minimal**
- **Phase 29z✅ P0 COMPLETE, implementation-minimal: RC insertion minimal**
- 成果: `MirInstruction::Store` の上書きで `ReleaseStrong` を挿入する最小pass単一block・安全ガード付き
- ガード: Cargo feature `rc-insertion-minimal`既定OFF、env var 新設なし)
- 検証: quick 154/154 PASS 維持 + `rc_insertion_selfcheck`opt-in
- 入口: `docs/development/current/main/phases/phase-29z/README.md`
- 指示書: `docs/development/current/main/phases/phase-29z/P0-RC_INSERTION_MINIMAL-INSTRUCTIONS.md`

View File

@ -22,7 +22,7 @@ SSOT:
既定OFFで導入する互換維持
推奨ガードenv var を増やさない):
- 既存の “profile/strict” の枠に載せるか、`cfg(debug_assertions)` 限定で有効化
- Cargo feature で opt-in例: `--features rc-insertion-minimal`
- どうしてもトグルが必要なら `src/config/env` 集約& docs に登録(撤去計画つき)
---
@ -59,3 +59,10 @@ cargo build --release
- Build: 0 errors
- quick: 154/154 PASS
- 既定挙動不変トグルOFFで完全に影響なし
追加opt-in の動作確認):
```bash
# RC insertion minimal を有効化して、passの最小ケースが動くことを自己診断バイナリで確認
cargo run --bin rc_insertion_selfcheck --features rc-insertion-minimal
```

View File

@ -1,9 +1,15 @@
# Phase 29z: RC insertion minimalPhase 29y follow-up
Status: Draft
Status: P0 Complete
Scope: Phase 29y の SSOTRC insertion / ABI / observabilityを前提に、**RC insertion pass を最小動作**まで進める。
Entry:
- SSOT: `docs/development/current/main/phases/phase-29y/20-RC-INSERTION-SSOT.md`
- 指示書: `docs/development/current/main/phases/phase-29z/P0-RC_INSERTION_MINIMAL-INSTRUCTIONS.md`
Opt-in:
- `rc-insertion-minimal` Cargo feature既定OFF
Verification:
- `cargo build --release` / `./tools/smokes/v2/run.sh --profile quick`
- `cargo run --bin rc_insertion_selfcheck --features rc-insertion-minimal`

View 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");
}

View File

@ -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
}
}