phase-29y.1: add lifecycle pilot plumbing (ABI shim, rc pass skeleton, root summary)
This commit is contained in:
40
apps/tests/phase29y_handle_abi.hako
Normal file
40
apps/tests/phase29y_handle_abi.hako
Normal 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
|
||||
}
|
||||
}
|
||||
74
crates/nyash_kernel/src/ffi/lifecycle.rs
Normal file
74
crates/nyash_kernel/src/ffi/lifecycle.rs
Normal 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);
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -26,6 +26,24 @@ Docs(Phase 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 shim(lifecycle)
|
||||
- `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 MVP(root 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 判定は runtime(NyRT)に置く
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
|
||||
65
src/mir/passes/rc_insertion.rs
Normal file
65
src/mir/passes/rc_insertion.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
Reference in New Issue
Block a user