phase29z(p0): minimal RC insertion overwrite release
This commit is contained in:
@ -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 only(inkwell不要)
|
||||
# - 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"
|
||||
|
||||
@ -2,6 +2,12 @@
|
||||
|
||||
## Current Focus: Phase 29z P0(RC 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 finalized(ABI/RC insertion/Observability)
|
||||
- 3つのSSOT文書(10/20/30)を Ready に確定
|
||||
|
||||
@ -57,7 +57,10 @@ Related:
|
||||
- 入口: `docs/development/current/main/phases/phase-29y/README.md`
|
||||
- 次: Phase 29z(RC insertion minimal)または Phase 29x(De-Rust runtime)候補
|
||||
|
||||
- **Phase 29z(next, 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`
|
||||
|
||||
|
||||
@ -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
|
||||
```
|
||||
|
||||
@ -1,9 +1,15 @@
|
||||
# Phase 29z: RC insertion minimal(Phase 29y follow-up)
|
||||
|
||||
Status: Draft
|
||||
Status: P0 Complete
|
||||
Scope: Phase 29y の SSOT(RC 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`
|
||||
|
||||
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