phase-29y.1: add lifecycle pilot plumbing (ABI shim, rc pass skeleton, root summary)

This commit is contained in:
2025-12-27 02:07:10 +09:00
parent 8a5218a9ad
commit 2e99b14e24
11 changed files with 334 additions and 11 deletions

View File

@ -0,0 +1,40 @@
// Phase 29y.1 Task 1: Handle ABI shim smoke test
// Tests nyrt_handle_retain_h and nyrt_handle_release_h via Box lifecycle
// SSOT: docs/development/current/main/phases/phase-29y/10-ABI-SSOT.md
box Counter {
value: IntegerBox
birth() {
me.value = 0
}
inc() {
me.value = me.value + 1
}
get() {
return me.value
}
}
static box Main {
main() {
// Create a Box - runtime allocates handle
local obj = new Counter()
// Overwrite triggers release of old value (tests release path)
obj = new Counter()
obj.inc()
// Verify we can still use the new object
local result = obj.get()
if result == 1 {
print("ok: handle_abi overwrite release works")
return 0
}
print("ng: unexpected result")
return 1
}
}

View File

@ -0,0 +1,74 @@
//! Phase 29y.1 Task 1: Handle ABI shim for lifecycle operations
//!
//! This module provides unified handle lifecycle FFI functions.
//! The naming uses `_h` suffix to distinguish from NyBox structure ABI.
//!
//! SSOT: docs/development/current/main/phases/phase-29y/10-ABI-SSOT.md
//!
//! Handle ABI contract:
//! - retain_h(h): h == 0 → return 0 (no-op), else return new handle to same object
//! - release_h(h): h == 0 → no-op, else decrement reference count
/// nyrt_handle_retain_h: Retain strong reference (increment ref count)
///
/// # Arguments
/// * `handle` - Strong Box handle (i64)
///
/// # Returns
/// * New handle to same object on success (reference count +1)
/// * 0 if input was 0 or invalid
///
/// # Contract (10-ABI-SSOT.md)
/// - h == 0 → return 0 (no-op)
/// - h != 0 → return new handle to same object
#[no_mangle]
pub extern "C" fn nyrt_handle_retain_h(handle: i64) -> i64 {
use nyash_rust::runtime::host_handles;
if handle <= 0 {
return 0;
}
// Get Arc from handle (this clones the Arc, incrementing ref count)
if let Some(arc) = host_handles::get(handle as u64) {
// Allocate new handle for the cloned Arc
let new_handle = host_handles::to_handle_arc(arc);
return new_handle as i64;
}
0 // Invalid handle
}
/// nyrt_handle_release_h: Release strong reference (decrement ref count)
///
/// # Arguments
/// * `handle` - Strong Box handle (i64)
///
/// # Contract (10-ABI-SSOT.md)
/// - h == 0 → no-op
/// - h != 0 → decrement reference count (may trigger deallocation)
#[no_mangle]
pub extern "C" fn nyrt_handle_release_h(handle: i64) {
use nyash_rust::runtime::host_handles;
if handle > 0 {
host_handles::drop_handle(handle as u64);
}
}
// ============================================================================
// Compatibility shim for existing code
// ============================================================================
/// ny_release_strong: Compatibility shim
///
/// Legacy name for nyrt_handle_release_h.
/// New code should use nyrt_handle_release_h directly.
///
/// # Deprecation
/// This function is kept for backward compatibility with existing LLVM lowering.
/// Future phases will migrate callers to nyrt_handle_release_h.
#[no_mangle]
pub extern "C" fn ny_release_strong_shim(handle: i64) {
nyrt_handle_release_h(handle);
}

View File

@ -1,4 +1,6 @@
// FFI module coordinator
// Phase 287 P4: Gradual modularization of nyash_kernel FFI functions
// Phase 29y.1: Added lifecycle (handle ABI shim)
pub mod lifecycle;
pub mod weak;

View File

@ -6,6 +6,7 @@ mod plugin;
mod ffi;
pub use plugin::*;
pub use ffi::lifecycle::*;
pub use ffi::weak::*;
// Phase 285LLVM-1.1: Global registry for user box field declarations

View File

@ -26,6 +26,24 @@ DocsPhase 29y 内の SSOT:
- RC insertion SSOT: `docs/development/current/main/phases/phase-29y/20-RC-INSERTION-SSOT.md`
- Observability SSOT: `docs/development/current/main/phases/phase-29y/30-OBSERVABILITY-SSOT.md`
## Implementation Pilot (Phase 29y.1)
Phase 29y は docs-first が主目的だが、後続の実装フェーズへ迷わず切るために “最小の導線” を先に用意する。
- Task 1: NyRT handle ABI shimlifecycle
- `crates/nyash_kernel/src/ffi/lifecycle.rs`
- `crates/nyash_kernel/src/ffi/mod.rs`
- `crates/nyash_kernel/src/lib.rs`
- fixture: `apps/tests/phase29y_handle_abi.hako`
- smokes: `tools/smokes/v2/profiles/integration/apps/phase29y_handle_abi_{vm,llvm}.sh`
- Task 2: RC insertion pass 入口skeleton, no-op
- `src/mir/passes/rc_insertion.rs`
- `src/mir/passes/mod.rs`
- `src/mir/mod.rs`compiler pipeline に接続)
- Task 3: Observability MVProot categories: handles のみ)
- `src/runtime/leak_tracker.rs`
- Phase 1 limitation を明示locals/temps/heap_fields/singletons は exit-time では未可視)
## Current Recommendation (consultation summary)
- **実体**: RC の値と Alive/Dead/Freed 判定は runtimeNyRTに置く

View File

@ -88,6 +88,8 @@ pub use value_id::{LocalId, ValueId, ValueIdGenerator};
pub use value_kind::{MirValueKind, TypedValueId}; // Phase 26-A: ValueId型安全化
pub use verification::MirVerifier;
pub use verification_types::VerificationError;
// Phase 29y.1: RC insertion pass (skeleton)
pub use passes::rc_insertion::{insert_rc_instructions, RcInsertionStats};
// Phase 15 control flow utilities (段階的根治戦略)
pub use utils::{
capture_actual_predecessor_and_jump, collect_phi_incoming_if_reachable,
@ -166,6 +168,10 @@ impl MirCompiler {
// Verify the generated MIR
let verification_result = self.verifier.verify_module(&module);
// Phase 29y.1: RC insertion pass (skeleton - no-op for now)
// Runs after optimization and verification, before backend codegen
let _rc_stats = insert_rc_instructions(&mut module);
Ok(MirCompileResult {
module,
verification_result,

View File

@ -5,6 +5,7 @@ pub mod cse;
pub mod dce;
pub mod escape;
pub mod method_id_inject;
pub mod rc_insertion;
pub mod type_hints;
/// Minimal pass trait for future expansion. Currently unused by the main

View File

@ -0,0 +1,65 @@
//! Phase 29y.1 Task 2: RC insertion pass (entry point only)
//!
//! This pass runs after MIR optimization and verification, before backend codegen.
//! Currently a no-op skeleton - actual RC insertion logic will be added later.
//!
//! SSOT: docs/development/current/main/phases/phase-29y/20-RC-INSERTION-SSOT.md
//!
//! Design constraints:
//! - SSA last-use 禁止: weak_to_strong() で観測できるため
//! - 許可される drop 点: explicit drop, 上書き, スコープ終端のみ
use crate::mir::MirModule;
/// Statistics from RC insertion pass
#[derive(Debug, Default, Clone)]
pub struct RcInsertionStats {
/// Number of KeepAlive instructions inserted
pub keepalive_inserted: usize,
/// Number of Release instructions inserted
pub release_inserted: usize,
/// Number of functions processed
pub functions_processed: usize,
/// Number of blocks visited
pub blocks_visited: usize,
}
/// Phase 29y.1: RC insertion pass (skeleton only)
///
/// This pass is called after MIR optimization and verification.
/// Currently a no-op that returns empty statistics.
///
/// Future: Will insert retain/release instructions at:
/// - explicit drop points
/// - variable overwrite points
/// - scope exit points
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 29y.1: No actual insertion yet (no-op pass)
// Future phases will add:
// - Drop point detection
// - Scope/binding tracking
// - PHI input liveness analysis
stats
}
#[cfg(test)]
mod tests {
use super::*;
#[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);
}
}

View File

@ -1,10 +1,11 @@
//! Leak Tracker - Exit-time diagnostics for strong references still held
//!
//! Phase 285: Extended to report all global roots (modules, host_handles, plugin boxes).
//! Phase 29y.1: Added root category summary (SSOT: 30-OBSERVABILITY-SSOT.md)
//!
//! ## Environment Variable
//!
//! - `NYASH_LEAK_LOG=1` - Summary counts only
//! - `NYASH_LEAK_LOG=1` - Summary counts only (with category breakdown)
//! - `NYASH_LEAK_LOG=2` - Verbose (include names/entries, truncated to first 10)
//!
//! ## Output Format
@ -14,6 +15,9 @@
//! [lifecycle/leak] modules: 3
//! [lifecycle/leak] host_handles: 5
//! [lifecycle/leak] plugin_boxes: 2
//! [lifecycle/leak] Root categories:
//! [lifecycle/leak] handles: 8
//! [lifecycle/leak] (Phase 1 limitation: locals/temps/heap_fields/singletons=0)
//! ```
use crate::runtime::get_global_ring0;
@ -22,16 +26,7 @@ use std::collections::HashMap;
use std::sync::Mutex;
/// Leak log level: 0 = off, 1 = summary, 2 = verbose
static LEVEL: Lazy<u8> = Lazy::new(|| {
match std::env::var("NYASH_LEAK_LOG")
.unwrap_or_default()
.as_str()
{
"1" => 1,
"2" => 2,
_ => 0,
}
});
static LEVEL: Lazy<u8> = Lazy::new(crate::config::env::leak_log_level);
/// Backward compatibility: enabled if level >= 1
static ENABLED: Lazy<bool> = Lazy::new(|| *LEVEL >= 1);
@ -39,6 +34,40 @@ static ENABLED: Lazy<bool> = Lazy::new(|| *LEVEL >= 1);
static LEAKS: Lazy<Mutex<HashMap<(String, u32), &'static str>>> =
Lazy::new(|| Mutex::new(HashMap::new()));
/// Phase 29y.1: Root category summary for observability
///
/// Categories are defined in docs/development/current/main/phases/phase-29y/30-OBSERVABILITY-SSOT.md
#[derive(Debug, Default, Clone)]
pub struct RootSummary {
/// host-visible registry/handle table
pub handles: usize,
/// runtime singleton/globals (Phase 1: always 0)
pub singletons: usize,
/// object strong-owned fields (Phase 1: always 0 - VM teardown)
pub heap_fields: usize,
/// temporary values/VM registers (Phase 1: always 0 - VM teardown)
pub temps: usize,
/// local variable bindings (Phase 1: always 0 - VM teardown)
pub locals: usize,
}
/// Phase 29y.1: Collect root summary from available sources
///
/// Phase 1 limitation: Only handles category is observable at exit time.
/// locals/temps/heap_fields/singletons require VM state which is unavailable after teardown.
fn collect_root_summary() -> RootSummary {
let host_handles = crate::runtime::host_handles::snapshot();
let modules = crate::runtime::modules_registry::snapshot_names_and_strings();
RootSummary {
handles: host_handles.len() + modules.len(),
singletons: 0, // Phase 1 limitation: VM state not available
heap_fields: 0, // Phase 1 limitation: VM state not available
temps: 0, // Phase 1 limitation: VM state not available
locals: 0, // Phase 1 limitation: VM state not available
}
}
pub fn init() {
let _ = &*REPORTER;
}
@ -123,6 +152,12 @@ pub fn emit_leak_report() {
.warn(&format!("[lifecycle/leak] plugin_boxes: {}", plugin_boxes));
}
// Phase 29y.1: Root category summary
let summary = collect_root_summary();
ring0.log.warn("[lifecycle/leak] Root categories:");
ring0.log.warn(&format!("[lifecycle/leak] handles: {}", summary.handles));
ring0.log.warn("[lifecycle/leak] (Phase 1 limitation: locals/temps/heap_fields/singletons=0)");
// Verbose details (level 2)
if level >= 2 {
const MAX_ENTRIES: usize = 10;

View File

@ -0,0 +1,42 @@
#!/bin/bash
# Phase 29y.1 Task 1: Handle ABI shim smoke test (LLVM harness)
#
# Purpose:
# - Ensure the LLVM backend can execute the fixture end-to-end.
# - Use exit-code SSOT (stdout may include harness/runtime logs).
#
# SSOT: docs/development/current/main/phases/phase-29y/10-ABI-SSOT.md
source "$(dirname "$0")/../../../lib/test_runner.sh"
require_env || exit 2
if ! check_llvm_available; then
test_skip "phase29y_handle_abi_llvm: LLVM backend not available"
exit 0
fi
INPUT="$NYASH_ROOT/apps/tests/phase29y_handle_abi.hako"
RUN_TIMEOUT_SECS=${RUN_TIMEOUT_SECS:-60}
set +e
OUTPUT=$(timeout "$RUN_TIMEOUT_SECS" NYASH_LLVM_USE_HARNESS=1 NYASH_DISABLE_PLUGINS=1 "$NYASH_BIN" --backend llvm "$INPUT" 2>&1)
EXIT_CODE=$?
set -e
if [ "$EXIT_CODE" -eq 124 ]; then
test_fail "phase29y_handle_abi_llvm: timed out (>${RUN_TIMEOUT_SECS}s)"
exit 1
fi
if [ "$EXIT_CODE" -eq 0 ]; then
test_pass "phase29y_handle_abi_llvm: PASS (exit 0)"
exit 0
fi
echo "[FAIL] Expected exit 0"
echo "[INFO] Exit code: $EXIT_CODE"
echo "[INFO] Output:"
echo "$OUTPUT" | head -n 60 || true
test_fail "phase29y_handle_abi_llvm: exit code mismatch"
exit 1

View File

@ -0,0 +1,39 @@
#!/bin/bash
# Phase 29y.1 Task 1: Handle ABI shim smoke test (VM)
#
# Purpose:
# - Establish a stable, exit-code-based smoke for handle lifecycle behavior.
# - Keep this in integration (not quick): Phase 29y is docs-first and this is a pilot.
#
# SSOT: docs/development/current/main/phases/phase-29y/10-ABI-SSOT.md
source "$(dirname "$0")/../../../lib/test_runner.sh"
export SMOKES_USE_PYVM=0
require_env || exit 2
INPUT="$NYASH_ROOT/apps/tests/phase29y_handle_abi.hako"
RUN_TIMEOUT_SECS=${RUN_TIMEOUT_SECS:-30}
set +e
OUTPUT=$(timeout "$RUN_TIMEOUT_SECS" "$NYASH_BIN" --backend vm "$INPUT" 2>&1)
EXIT_CODE=$?
set -e
if [ "$EXIT_CODE" -eq 124 ]; then
test_fail "phase29y_handle_abi_vm: timed out (>${RUN_TIMEOUT_SECS}s)"
exit 1
fi
# Exit code SSOT (stdout may include logs in some environments)
if [ "$EXIT_CODE" -eq 0 ]; then
test_pass "phase29y_handle_abi_vm: PASS (exit 0)"
exit 0
fi
echo "[FAIL] Expected exit 0"
echo "[INFO] Exit code: $EXIT_CODE"
echo "[INFO] Output:"
echo "$OUTPUT" | head -n 40 || true
test_fail "phase29y_handle_abi_vm: exit code mismatch"
exit 1