Phase 10_6b scheduler complete; 10_4 GC hooks + counting/strict tracing; 10_c minimal JIT path (i64/bool consts, binop/compare/return, hostcall opt-in); docs & examples; add Phase 10.7 roadmap (JIT branch wiring + minimal ABI).
This commit is contained in:
@ -88,9 +88,31 @@ pub(super) fn execute_instruction(vm: &mut VM, instruction: &MirInstruction, deb
|
||||
},
|
||||
|
||||
// Barriers
|
||||
MirInstruction::BarrierRead { .. } => Ok(ControlFlow::Continue),
|
||||
MirInstruction::BarrierWrite { .. } => Ok(ControlFlow::Continue),
|
||||
MirInstruction::Barrier { .. } => Ok(ControlFlow::Continue),
|
||||
MirInstruction::BarrierRead { .. } => {
|
||||
if std::env::var("NYASH_GC_TRACE").ok().as_deref() == Some("1") {
|
||||
let (func, bb, pc) = vm.gc_site_info();
|
||||
eprintln!("[GC] barrier: Read @{} bb={} pc={}", func, bb, pc);
|
||||
}
|
||||
vm.runtime.gc.barrier(crate::runtime::gc::BarrierKind::Read);
|
||||
Ok(ControlFlow::Continue)
|
||||
}
|
||||
MirInstruction::BarrierWrite { .. } => {
|
||||
if std::env::var("NYASH_GC_TRACE").ok().as_deref() == Some("1") {
|
||||
let (func, bb, pc) = vm.gc_site_info();
|
||||
eprintln!("[GC] barrier: Write @{} bb={} pc={}", func, bb, pc);
|
||||
}
|
||||
vm.runtime.gc.barrier(crate::runtime::gc::BarrierKind::Write);
|
||||
Ok(ControlFlow::Continue)
|
||||
}
|
||||
MirInstruction::Barrier { op, .. } => {
|
||||
let k = match op { crate::mir::BarrierOp::Read => crate::runtime::gc::BarrierKind::Read, crate::mir::BarrierOp::Write => crate::runtime::gc::BarrierKind::Write };
|
||||
if std::env::var("NYASH_GC_TRACE").ok().as_deref() == Some("1") {
|
||||
let (func, bb, pc) = vm.gc_site_info();
|
||||
eprintln!("[GC] barrier: {:?} @{} bb={} pc={}", k, func, bb, pc);
|
||||
}
|
||||
vm.runtime.gc.barrier(k);
|
||||
Ok(ControlFlow::Continue)
|
||||
}
|
||||
|
||||
// Exceptions
|
||||
MirInstruction::Throw { exception, .. } => vm.execute_throw(*exception),
|
||||
@ -123,7 +145,16 @@ pub(super) fn execute_instruction(vm: &mut VM, instruction: &MirInstruction, deb
|
||||
MirInstruction::Cast { dst, value, .. } => { let val = vm.get_value(*value)?; vm.set_value(*dst, val); Ok(ControlFlow::Continue) }
|
||||
MirInstruction::Debug { .. } => Ok(ControlFlow::Continue),
|
||||
MirInstruction::Nop => Ok(ControlFlow::Continue),
|
||||
MirInstruction::Safepoint => Ok(ControlFlow::Continue),
|
||||
MirInstruction::Safepoint => {
|
||||
if std::env::var("NYASH_GC_TRACE").ok().as_deref() == Some("1") {
|
||||
let (func, bb, pc) = vm.gc_site_info();
|
||||
eprintln!("[GC] safepoint @{} bb={} pc={}", func, bb, pc);
|
||||
}
|
||||
vm.runtime.gc.safepoint();
|
||||
// Cooperative scheduling: poll single-thread scheduler
|
||||
if let Some(s) = &vm.runtime.scheduler { s.poll(); }
|
||||
Ok(ControlFlow::Continue)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
48
src/backend/gc_helpers.rs
Normal file
48
src/backend/gc_helpers.rs
Normal file
@ -0,0 +1,48 @@
|
||||
//! GC-related small helpers for VM-side use
|
||||
|
||||
use crate::backend::vm::VMValue;
|
||||
|
||||
/// Return true if the BoxCall is a known mutating builtin call (e.g., Array/Map set/push)
|
||||
pub fn is_mutating_builtin_call(recv: &VMValue, method: &str) -> bool {
|
||||
// Lightweight table of mutating methods by builtin box type
|
||||
// Array: set, push
|
||||
// Map: set, put, insert, remove (superset to future-proof)
|
||||
const ARRAY_METHODS: &[&str] = &["set", "push"];
|
||||
const MAP_METHODS: &[&str] = &["set", "put", "insert", "remove"]; // tolerate aliases
|
||||
|
||||
match recv {
|
||||
VMValue::BoxRef(b) => {
|
||||
if b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>().is_some() {
|
||||
return ARRAY_METHODS.iter().any(|m| *m == method);
|
||||
}
|
||||
if b.as_any().downcast_ref::<crate::boxes::map_box::MapBox>().is_some() {
|
||||
return MAP_METHODS.iter().any(|m| *m == method);
|
||||
}
|
||||
false
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Unified trigger for GC Write-Barrier with site logging
|
||||
pub fn gc_write_barrier_site(runtime: &crate::runtime::NyashRuntime, site: &str) {
|
||||
let trace = std::env::var("NYASH_GC_TRACE").ok().as_deref() == Some("1");
|
||||
let strict = std::env::var("NYASH_GC_BARRIER_STRICT").ok().as_deref() == Some("1");
|
||||
let before = if strict { runtime.gc.snapshot_counters() } else { None };
|
||||
if trace {
|
||||
eprintln!("[GC] barrier: Write @{}", site);
|
||||
}
|
||||
runtime.gc.barrier(crate::runtime::gc::BarrierKind::Write);
|
||||
if strict {
|
||||
let after = runtime.gc.snapshot_counters();
|
||||
match (before, after) {
|
||||
(Some((_, _, bw)), Some((_, _, aw))) if aw > bw => {}
|
||||
(Some(_), Some(_)) => {
|
||||
panic!("[GC][STRICT] write barrier did not increment at site='{}'", site);
|
||||
}
|
||||
_ => {
|
||||
panic!("[GC][STRICT] CountingGc required for strict verification at site='{}'", site);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -12,6 +12,7 @@ pub mod vm_stats;
|
||||
pub mod control_flow;
|
||||
pub mod dispatch;
|
||||
pub mod frame;
|
||||
pub mod gc_helpers;
|
||||
|
||||
#[cfg(feature = "wasm-backend")]
|
||||
pub mod wasm;
|
||||
|
||||
@ -234,6 +234,90 @@ pub struct VM {
|
||||
}
|
||||
|
||||
impl VM {
|
||||
/// Enter a GC root region and return a guard that leaves on drop
|
||||
pub(super) fn enter_root_region(&mut self) {
|
||||
self.scope_tracker.enter_root_region();
|
||||
}
|
||||
|
||||
/// Pin a slice of VMValue as roots in the current region
|
||||
pub(super) fn pin_roots<'a>(&mut self, values: impl IntoIterator<Item = &'a VMValue>) {
|
||||
for v in values {
|
||||
self.scope_tracker.pin_root(v);
|
||||
}
|
||||
}
|
||||
|
||||
/// Leave current GC root region
|
||||
pub(super) fn leave_root_region(&mut self) { self.scope_tracker.leave_root_region(); }
|
||||
|
||||
/// Site info for GC logs: (func, bb, pc)
|
||||
pub(super) fn gc_site_info(&self) -> (String, i64, i64) {
|
||||
let func = self.current_function.as_deref().unwrap_or("<none>").to_string();
|
||||
let bb = self.frame.current_block.map(|b| b.0 as i64).unwrap_or(-1);
|
||||
let pc = self.frame.pc as i64;
|
||||
(func, bb, pc)
|
||||
}
|
||||
|
||||
/// Print a simple breakdown of root VMValue kinds and top BoxRef types
|
||||
fn gc_print_roots_breakdown(&self) {
|
||||
use std::collections::HashMap;
|
||||
let roots = self.scope_tracker.roots_snapshot();
|
||||
let mut kinds: HashMap<&'static str, u64> = HashMap::new();
|
||||
let mut box_types: HashMap<String, u64> = HashMap::new();
|
||||
for v in &roots {
|
||||
match v {
|
||||
VMValue::Integer(_) => *kinds.entry("Integer").or_insert(0) += 1,
|
||||
VMValue::Float(_) => *kinds.entry("Float").or_insert(0) += 1,
|
||||
VMValue::Bool(_) => *kinds.entry("Bool").or_insert(0) += 1,
|
||||
VMValue::String(_) => *kinds.entry("String").or_insert(0) += 1,
|
||||
VMValue::Future(_) => *kinds.entry("Future").or_insert(0) += 1,
|
||||
VMValue::Void => *kinds.entry("Void").or_insert(0) += 1,
|
||||
VMValue::BoxRef(b) => {
|
||||
let tn = b.type_name().to_string();
|
||||
*box_types.entry(tn).or_insert(0) += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
eprintln!("[GC] roots_breakdown: kinds={:?}", kinds);
|
||||
let mut top: Vec<(String, u64)> = box_types.into_iter().collect();
|
||||
top.sort_by(|a, b| b.1.cmp(&a.1));
|
||||
top.truncate(5);
|
||||
eprintln!("[GC] roots_boxref_top5: {:?}", top);
|
||||
}
|
||||
fn gc_print_reachability_depth2(&self) {
|
||||
use std::collections::HashMap;
|
||||
let roots = self.scope_tracker.roots_snapshot();
|
||||
let mut child_types: HashMap<String, u64> = HashMap::new();
|
||||
let mut child_count = 0u64;
|
||||
for v in &roots {
|
||||
if let VMValue::BoxRef(b) = v {
|
||||
if let Some(arr) = b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
||||
if let Ok(items) = arr.items.read() {
|
||||
for item in items.iter() {
|
||||
let tn = item.type_name().to_string();
|
||||
*child_types.entry(tn).or_insert(0) += 1;
|
||||
child_count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(map) = b.as_any().downcast_ref::<crate::boxes::map_box::MapBox>() {
|
||||
let vals = map.values();
|
||||
if let Some(arr2) = vals.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
||||
if let Ok(items) = arr2.items.read() {
|
||||
for item in items.iter() {
|
||||
let tn = item.type_name().to_string();
|
||||
*child_types.entry(tn).or_insert(0) += 1;
|
||||
child_count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut top: Vec<(String, u64)> = child_types.into_iter().collect();
|
||||
top.sort_by(|a, b| b.1.cmp(&a.1));
|
||||
top.truncate(5);
|
||||
eprintln!("[GC] depth2_children: total={} top5={:?}", child_count, top);
|
||||
}
|
||||
fn jit_threshold_from_env() -> u32 {
|
||||
std::env::var("NYASH_JIT_THRESHOLD")
|
||||
.ok()
|
||||
@ -379,6 +463,21 @@ impl VM {
|
||||
// Optional: print JIT stats summary (Phase 10_a)
|
||||
if let Some(jm) = &self.jit_manager { jm.print_summary(); }
|
||||
|
||||
// Optional: GC diagnostics if enabled
|
||||
if let Ok(val) = std::env::var("NYASH_GC_TRACE") {
|
||||
if val == "1" || val == "2" || val == "3" {
|
||||
if let Some((sp, rd, wr)) = self.runtime.gc.snapshot_counters() {
|
||||
eprintln!("[GC] counters: safepoints={} read_barriers={} write_barriers={}", sp, rd, wr);
|
||||
}
|
||||
let roots_total = self.scope_tracker.root_count_total();
|
||||
let root_regions = self.scope_tracker.root_regions();
|
||||
let field_slots: usize = self.object_fields.values().map(|m| m.len()).sum();
|
||||
eprintln!("[GC] mock_mark: roots_total={} regions={} object_field_slots={}", roots_total, root_regions, field_slots);
|
||||
if val == "2" || val == "3" { self.gc_print_roots_breakdown(); }
|
||||
if val == "3" { self.gc_print_reachability_depth2(); }
|
||||
}
|
||||
}
|
||||
|
||||
// Convert result to NyashBox
|
||||
Ok(result.to_nyash_box())
|
||||
}
|
||||
@ -404,6 +503,9 @@ impl VM {
|
||||
|
||||
/// Call a MIR function by name with VMValue arguments
|
||||
pub(super) fn call_function_by_name(&mut self, func_name: &str, args: Vec<VMValue>) -> Result<VMValue, VMError> {
|
||||
// Root region: ensure args stay rooted during nested call
|
||||
self.enter_root_region();
|
||||
self.pin_roots(args.iter());
|
||||
let module_ref = self.module.as_ref().ok_or_else(|| VMError::InvalidInstruction("No active module".to_string()))?;
|
||||
let function_ref = module_ref.get_function(func_name)
|
||||
.ok_or_else(|| VMError::InvalidInstruction(format!("Function '{}' not found", func_name)))?;
|
||||
@ -445,7 +547,8 @@ impl VM {
|
||||
self.previous_block = saved_previous_block;
|
||||
self.frame.pc = saved_pc;
|
||||
self.frame.last_result = saved_last_result;
|
||||
|
||||
// Leave GC root region
|
||||
self.scope_tracker.leave_root_region();
|
||||
result
|
||||
}
|
||||
|
||||
@ -477,18 +580,29 @@ impl VM {
|
||||
.iter()
|
||||
.filter_map(|pid| self.get_value(*pid).ok())
|
||||
.collect();
|
||||
if let Some(jm) = &mut self.jit_manager {
|
||||
if std::env::var("NYASH_JIT_EXEC").ok().as_deref() == Some("1") {
|
||||
if jm.is_compiled(&function.signature.name) {
|
||||
if let Some(val) = jm.execute_compiled(&function.signature.name, &args_vec) {
|
||||
if std::env::var("NYASH_JIT_EXEC").ok().as_deref() == Some("1") {
|
||||
// Root regionize args for JIT call
|
||||
self.enter_root_region();
|
||||
self.pin_roots(args_vec.iter());
|
||||
if let Some(jm_ref) = self.jit_manager.as_ref() {
|
||||
if jm_ref.is_compiled(&function.signature.name) {
|
||||
if let Some(val) = jm_ref.execute_compiled(&function.signature.name, &args_vec) {
|
||||
// Exit scope before returning
|
||||
self.leave_root_region();
|
||||
self.scope_tracker.pop_scope();
|
||||
return Ok(val);
|
||||
} else if std::env::var("NYASH_JIT_STATS").ok().as_deref() == Some("1") ||
|
||||
std::env::var("NYASH_JIT_TRAP_LOG").ok().as_deref() == Some("1") {
|
||||
eprintln!("[JIT] fallback: VM path taken for {}", function.signature.name);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
}
|
||||
// Leave root region if not compiled or after fallback
|
||||
self.leave_root_region();
|
||||
} else {
|
||||
if let Some(jm_mut) = &mut self.jit_manager {
|
||||
let argc = function.params.len();
|
||||
let _would = jm.maybe_dispatch(&function.signature.name, argc);
|
||||
let _would = jm_mut.maybe_dispatch(&function.signature.name, argc);
|
||||
}
|
||||
}
|
||||
|
||||
@ -922,6 +1036,9 @@ impl VM {
|
||||
}
|
||||
}
|
||||
|
||||
/// RAII guard for GC root regions
|
||||
// Root region guard removed in favor of explicit enter/leave to avoid borrow conflicts
|
||||
|
||||
/// Control flow result from instruction execution
|
||||
pub(super) enum ControlFlow {
|
||||
Continue,
|
||||
|
||||
@ -15,6 +15,7 @@ use super::{VM, VMValue, VMError};
|
||||
use super::vm::ControlFlow;
|
||||
|
||||
impl VM {
|
||||
// moved helpers to backend::gc_helpers
|
||||
/// Build a PIC key from receiver and method identity
|
||||
fn build_pic_key(&self, recv: &VMValue, method: &str, method_id: Option<u16>) -> String {
|
||||
let label = self.cache_label_for_recv(recv);
|
||||
@ -420,6 +421,8 @@ impl VM {
|
||||
|
||||
if let VMValue::BoxRef(array_box) = &array_val {
|
||||
if let Some(array) = array_box.as_any().downcast_ref::<ArrayBox>() {
|
||||
// GC write barrier (array contents mutation)
|
||||
crate::backend::gc_helpers::gc_write_barrier_site(&self.runtime, "ArraySet");
|
||||
// ArrayBox expects Box<dyn NyashBox> for index
|
||||
let index_box = index_val.to_nyash_box();
|
||||
let box_value = value_val.to_nyash_box();
|
||||
@ -506,7 +509,8 @@ impl VM {
|
||||
self.object_fields.insert(reference, std::collections::HashMap::new());
|
||||
}
|
||||
|
||||
// Set the field
|
||||
// Set the field (with GC write barrier)
|
||||
crate::backend::gc_helpers::gc_write_barrier_site(&self.runtime, "RefSet");
|
||||
if let Some(fields) = self.object_fields.get_mut(&reference) {
|
||||
fields.insert(field.to_string(), new_value);
|
||||
if debug_ref { eprintln!("[VM] RefSet stored: {}", field); }
|
||||
@ -647,16 +651,34 @@ impl VM {
|
||||
if std::env::var("NYASH_VM_VT_STATS").ok().as_deref() == Some("1") {
|
||||
eprintln!("[VT] hit class={} slot={} -> {}", label, mid, func_name);
|
||||
}
|
||||
// 実行: 受け取り→VM引数並べ→関数呼出
|
||||
let mut vm_args = Vec::with_capacity(1 + args.len());
|
||||
vm_args.push(recv.clone());
|
||||
for a in args { vm_args.push(self.get_value(*a)?); }
|
||||
let res = self.call_function_by_name(&func_name, vm_args)?;
|
||||
|
||||
// 10_e: Thunk経路でもPIC/VTableを直結更新するにゃ
|
||||
// - Poly-PIC: 直ちに記録(最大4件ローカルLRU)
|
||||
self.record_poly_pic(&pic_key, &recv, &func_name);
|
||||
// - HotならMono-PICにも格納(しきい値=8)
|
||||
const PIC_THRESHOLD: u32 = 8;
|
||||
if self.pic_hits(&pic_key) >= PIC_THRESHOLD {
|
||||
self.boxcall_pic_funcname.insert(pic_key.clone(), func_name.clone());
|
||||
}
|
||||
// - InstanceBoxならVTableキーにも登録(method_id/arity直結)
|
||||
if is_instance {
|
||||
let vkey = self.build_vtable_key(&label, mid, args.len());
|
||||
self.boxcall_vtable_funcname.entry(vkey).or_insert(func_name.clone());
|
||||
}
|
||||
|
||||
if let Some(dst_id) = dst { self.set_value(dst_id, res); }
|
||||
return Ok(ControlFlow::Continue);
|
||||
}
|
||||
crate::runtime::type_meta::ThunkTarget::PluginInvoke { method_id: mid2 } => {
|
||||
if is_plugin {
|
||||
if let Some(p) = arc_box.as_any().downcast_ref::<crate::runtime::plugin_loader_v2::PluginBoxV2>() {
|
||||
// Root region for plugin call (pin recv + args)
|
||||
self.enter_root_region();
|
||||
// Convert args prepared earlier (we need NyashBox args)
|
||||
let nyash_args: Vec<Box<dyn NyashBox>> = args.iter()
|
||||
.map(|arg| {
|
||||
@ -664,6 +686,10 @@ impl VM {
|
||||
Ok(val.to_nyash_box())
|
||||
})
|
||||
.collect::<Result<Vec<_>, VMError>>()?;
|
||||
// Pin roots: receiver and VMValue args
|
||||
self.pin_roots(std::iter::once(&recv));
|
||||
let pinned_args: Vec<VMValue> = args.iter().filter_map(|a| self.get_value(*a).ok()).collect();
|
||||
self.pin_roots(pinned_args.iter());
|
||||
// Encode minimal TLV (int/string/handle) same as fast-path
|
||||
let mut tlv = crate::runtime::plugin_ffi_common::encode_tlv_header(nyash_args.len() as u16);
|
||||
let mut enc_failed = false;
|
||||
@ -706,8 +732,12 @@ impl VM {
|
||||
VMValue::Void
|
||||
};
|
||||
if let Some(dst_id) = dst { self.set_value(dst_id, vm_out); }
|
||||
// Leave root region
|
||||
self.leave_root_region();
|
||||
return Ok(ControlFlow::Continue);
|
||||
}
|
||||
// Leave root region also on error path
|
||||
self.leave_root_region();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -721,6 +751,10 @@ impl VM {
|
||||
Ok(val.to_nyash_box())
|
||||
})
|
||||
.collect::<Result<Vec<_>, VMError>>()?;
|
||||
// Write barrier for known mutating builtins
|
||||
if crate::backend::gc_helpers::is_mutating_builtin_call(&recv, m) {
|
||||
crate::backend::gc_helpers::gc_write_barrier_site(&self.runtime, "BoxCall.builtin");
|
||||
}
|
||||
let cloned_box = arc_box.share_box();
|
||||
let out = self.call_box_method(cloned_box, m, nyash_args)?;
|
||||
let vm_out = VMValue::from_nyash_box(out);
|
||||
@ -787,13 +821,15 @@ impl VM {
|
||||
})
|
||||
.collect::<Result<Vec<_>, VMError>>()?;
|
||||
|
||||
// PluginBoxV2 fast-path via method_id -> direct invoke_fn (skip name->id resolution)
|
||||
if let (Some(mid), VMValue::BoxRef(arc_box)) = (method_id, &recv) {
|
||||
if let Some(p) = arc_box.as_any().downcast_ref::<crate::runtime::plugin_loader_v2::PluginBoxV2>() {
|
||||
// Encode TLV args (support: int, string, plugin handle)
|
||||
let mut tlv = crate::runtime::plugin_ffi_common::encode_tlv_header(nyash_args.len() as u16);
|
||||
let mut enc_failed = false;
|
||||
for a in &nyash_args {
|
||||
// PluginBoxV2 fast-path via method_id -> direct invoke_fn (skip name->id resolution)
|
||||
if let (Some(mid), VMValue::BoxRef(arc_box)) = (method_id, &recv) {
|
||||
if let Some(p) = arc_box.as_any().downcast_ref::<crate::runtime::plugin_loader_v2::PluginBoxV2>() {
|
||||
// Root region for plugin call
|
||||
self.enter_root_region();
|
||||
// Encode TLV args (support: int, string, plugin handle)
|
||||
let mut tlv = crate::runtime::plugin_ffi_common::encode_tlv_header(nyash_args.len() as u16);
|
||||
let mut enc_failed = false;
|
||||
for a in &nyash_args {
|
||||
// Prefer BufferBox → bytes
|
||||
if let Some(buf) = a.as_any().downcast_ref::<crate::boxes::buffer::BufferBox>() {
|
||||
let snapshot = buf.to_vec();
|
||||
@ -864,8 +900,11 @@ impl VM {
|
||||
VMValue::Void
|
||||
};
|
||||
if let Some(dst_id) = dst { self.set_value(dst_id, vm_out); }
|
||||
self.leave_root_region();
|
||||
return Ok(ControlFlow::Continue);
|
||||
}
|
||||
// Leave root region also on non-zero code path
|
||||
self.leave_root_region();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -915,6 +954,10 @@ impl VM {
|
||||
let tm = crate::runtime::type_meta::get_or_create_type_meta(&label);
|
||||
tm.set_thunk_builtin(mid as usize, method.to_string());
|
||||
}
|
||||
// Write barrier for known mutating builtins
|
||||
if crate::backend::gc_helpers::is_mutating_builtin_call(&recv, method) {
|
||||
crate::backend::gc_helpers::gc_write_barrier_site(&self.runtime, "BoxCall");
|
||||
}
|
||||
let cloned_box = arc_box.share_box();
|
||||
self.call_box_method(cloned_box, method, nyash_args)?
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user