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:
Moe Charm
2025-08-27 17:06:46 +09:00
parent de03514085
commit ddae7fe1fc
67 changed files with 4618 additions and 268 deletions

View File

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

View File

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

View File

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

View File

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