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)?
|
||||
}
|
||||
|
||||
@ -36,6 +36,7 @@ impl JitEngine {
|
||||
|
||||
/// Compile a function if supported; returns an opaque handle id
|
||||
pub fn compile_function(&mut self, func_name: &str, mir: &crate::mir::MirFunction) -> Option<u64> {
|
||||
let t0 = std::time::Instant::now();
|
||||
// Phase 10_b skeleton: walk MIR with LowerCore and report coverage
|
||||
let mut lower = crate::jit::lower::core::LowerCore::new();
|
||||
#[cfg(feature = "cranelift-jit")]
|
||||
@ -68,6 +69,10 @@ impl JitEngine {
|
||||
{
|
||||
if let Some(closure) = builder.take_compiled_closure() {
|
||||
self.fntab.insert(h, closure);
|
||||
if std::env::var("NYASH_JIT_STATS").ok().as_deref() == Some("1") {
|
||||
let dt = t0.elapsed();
|
||||
eprintln!("[JIT] compile_time_ms={} for {}", dt.as_millis(), func_name);
|
||||
}
|
||||
return Some(h);
|
||||
}
|
||||
}
|
||||
@ -75,12 +80,28 @@ impl JitEngine {
|
||||
self.fntab.insert(h, Arc::new(|_args: &[crate::backend::vm::VMValue]| {
|
||||
crate::backend::vm::VMValue::Void
|
||||
}));
|
||||
if std::env::var("NYASH_JIT_STATS").ok().as_deref() == Some("1") {
|
||||
let dt = t0.elapsed();
|
||||
eprintln!("[JIT] compile_time_ms={} for {} (stub)", dt.as_millis(), func_name);
|
||||
}
|
||||
Some(h)
|
||||
}
|
||||
|
||||
/// Execute compiled function by handle (stub). Returns Some(VMValue) if found.
|
||||
/// Execute compiled function by handle with trap fallback.
|
||||
/// Returns Some(VMValue) if executed successfully; None on missing handle or trap (panic).
|
||||
pub fn execute_handle(&self, handle: u64, args: &[crate::backend::vm::VMValue]) -> Option<crate::backend::vm::VMValue> {
|
||||
self.fntab.get(&handle).map(|f| f(args))
|
||||
let f = match self.fntab.get(&handle) { Some(f) => f, None => return None };
|
||||
let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| (f)(args)));
|
||||
match res {
|
||||
Ok(v) => Some(v),
|
||||
Err(_) => {
|
||||
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] trap: panic during handle={} execution — falling back to VM", handle);
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Register built-in externs (collections)
|
||||
|
||||
@ -12,6 +12,10 @@ pub enum CmpKind { Eq, Ne, Lt, Le, Gt, Ge }
|
||||
pub trait IRBuilder {
|
||||
fn begin_function(&mut self, name: &str);
|
||||
fn end_function(&mut self);
|
||||
/// Optional: prepare a simple `i64` ABI signature with `argc` params
|
||||
fn prepare_signature_i64(&mut self, _argc: usize, _has_ret: bool) { }
|
||||
/// Load i64 parameter at index and push to value stack (Core-1 path)
|
||||
fn emit_param_i64(&mut self, _index: usize) { }
|
||||
fn emit_const_i64(&mut self, _val: i64);
|
||||
fn emit_const_f64(&mut self, _val: f64);
|
||||
fn emit_binop(&mut self, _op: BinOpKind);
|
||||
@ -38,6 +42,7 @@ impl NoopBuilder {
|
||||
impl IRBuilder for NoopBuilder {
|
||||
fn begin_function(&mut self, _name: &str) {}
|
||||
fn end_function(&mut self) {}
|
||||
fn emit_param_i64(&mut self, _index: usize) { self.consts += 1; }
|
||||
fn emit_const_i64(&mut self, _val: i64) { self.consts += 1; }
|
||||
fn emit_const_f64(&mut self, _val: f64) { self.consts += 1; }
|
||||
fn emit_binop(&mut self, _op: BinOpKind) { self.binops += 1; }
|
||||
@ -59,6 +64,9 @@ pub struct CraneliftBuilder {
|
||||
entry_block: Option<cranelift_codegen::ir::Block>,
|
||||
// Finalized function pointer (if any)
|
||||
compiled_closure: Option<std::sync::Arc<dyn Fn(&[crate::backend::vm::VMValue]) -> crate::backend::vm::VMValue + Send + Sync>>,
|
||||
// Desired simple ABI (Phase 10_c minimal): i64 params count and i64 return
|
||||
desired_argc: usize,
|
||||
desired_has_ret: bool,
|
||||
}
|
||||
|
||||
#[cfg(feature = "cranelift-jit")]
|
||||
@ -66,8 +74,99 @@ use cranelift_module::Module;
|
||||
#[cfg(feature = "cranelift-jit")]
|
||||
use cranelift_codegen::ir::InstBuilder;
|
||||
|
||||
#[cfg(feature = "cranelift-jit")]
|
||||
extern "C" fn nyash_host_stub0() -> i64 { 0 }
|
||||
#[cfg(feature = "cranelift-jit")]
|
||||
extern "C" fn nyash_array_len(arr_param_index: i64) -> i64 {
|
||||
// Interpret first arg as function param index and fetch from thread-local args
|
||||
if arr_param_index < 0 { return 0; }
|
||||
crate::jit::rt::with_args(|args| {
|
||||
let idx = arr_param_index as usize;
|
||||
if let Some(crate::backend::vm::VMValue::BoxRef(b)) = args.get(idx) {
|
||||
if let Some(ab) = b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
||||
if let Some(ib) = ab.length().as_any().downcast_ref::<crate::box_trait::IntegerBox>() {
|
||||
return ib.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
0
|
||||
})
|
||||
}
|
||||
#[cfg(feature = "cranelift-jit")]
|
||||
extern "C" fn nyash_array_push(arr_param_index: i64, val: i64) -> i64 {
|
||||
if arr_param_index < 0 { return 0; }
|
||||
crate::jit::rt::with_args(|args| {
|
||||
let idx = arr_param_index as usize;
|
||||
if let Some(crate::backend::vm::VMValue::BoxRef(b)) = args.get(idx) {
|
||||
if let Some(ab) = b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
||||
// Push integer value only (PoC)
|
||||
let ib = crate::box_trait::IntegerBox::new(val);
|
||||
let _ = ab.push(Box::new(ib));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
0
|
||||
})
|
||||
}
|
||||
#[cfg(feature = "cranelift-jit")]
|
||||
extern "C" fn nyash_array_get(arr_param_index: i64, idx: i64) -> i64 {
|
||||
if arr_param_index < 0 { return 0; }
|
||||
crate::jit::rt::with_args(|args| {
|
||||
let pidx = arr_param_index as usize;
|
||||
if let Some(crate::backend::vm::VMValue::BoxRef(b)) = args.get(pidx) {
|
||||
if let Some(ab) = b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
||||
let val = ab.get(Box::new(crate::box_trait::IntegerBox::new(idx)));
|
||||
if let Some(ib) = val.as_any().downcast_ref::<crate::box_trait::IntegerBox>() {
|
||||
return ib.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
0
|
||||
})
|
||||
}
|
||||
#[cfg(feature = "cranelift-jit")]
|
||||
extern "C" fn nyash_array_set(arr_param_index: i64, idx: i64, val: i64) -> i64 {
|
||||
if arr_param_index < 0 { return 0; }
|
||||
crate::jit::rt::with_args(|args| {
|
||||
let pidx = arr_param_index as usize;
|
||||
if let Some(crate::backend::vm::VMValue::BoxRef(b)) = args.get(pidx) {
|
||||
if let Some(ab) = b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
||||
let _ = ab.set(
|
||||
Box::new(crate::box_trait::IntegerBox::new(idx)),
|
||||
Box::new(crate::box_trait::IntegerBox::new(val)),
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
0
|
||||
})
|
||||
}
|
||||
#[cfg(feature = "cranelift-jit")]
|
||||
extern "C" fn nyash_map_get(_map: u64, _key: i64) -> i64 { 0 }
|
||||
#[cfg(feature = "cranelift-jit")]
|
||||
extern "C" fn nyash_map_set(_map: u64, _key: i64, _val: i64) -> i64 { 0 }
|
||||
#[cfg(feature = "cranelift-jit")]
|
||||
extern "C" fn nyash_map_size(map_param_index: i64) -> i64 {
|
||||
if map_param_index < 0 { return 0; }
|
||||
crate::jit::rt::with_args(|args| {
|
||||
let idx = map_param_index as usize;
|
||||
if let Some(crate::backend::vm::VMValue::BoxRef(b)) = args.get(idx) {
|
||||
if let Some(mb) = b.as_any().downcast_ref::<crate::boxes::map_box::MapBox>() {
|
||||
if let Some(ib) = mb.size().as_any().downcast_ref::<crate::box_trait::IntegerBox>() {
|
||||
return ib.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
0
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "cranelift-jit")]
|
||||
impl IRBuilder for CraneliftBuilder {
|
||||
fn prepare_signature_i64(&mut self, argc: usize, has_ret: bool) {
|
||||
self.desired_argc = argc;
|
||||
self.desired_has_ret = has_ret;
|
||||
}
|
||||
fn begin_function(&mut self, name: &str) {
|
||||
use cranelift_codegen::ir::{AbiParam, Signature, types};
|
||||
use cranelift_frontend::FunctionBuilder;
|
||||
@ -76,10 +175,11 @@ impl IRBuilder for CraneliftBuilder {
|
||||
self.value_stack.clear();
|
||||
self.entry_block = None;
|
||||
|
||||
// Minimal signature: () -> i64 (Core-1 integer path)
|
||||
// Minimal signature: (i64 x argc) -> i64? (Core-1 integer path)
|
||||
let call_conv = self.module.isa().default_call_conv();
|
||||
let mut sig = Signature::new(call_conv);
|
||||
sig.returns.push(AbiParam::new(types::I64));
|
||||
for _ in 0..self.desired_argc { sig.params.push(AbiParam::new(types::I64)); }
|
||||
if self.desired_has_ret { sig.returns.push(AbiParam::new(types::I64)); }
|
||||
self.ctx.func.signature = sig;
|
||||
self.ctx.func.name = cranelift_codegen::ir::UserFuncName::user(0, 0);
|
||||
|
||||
@ -116,7 +216,7 @@ impl IRBuilder for CraneliftBuilder {
|
||||
// Get finalized code pointer and wrap into a safe closure
|
||||
let code = self.module.get_finalized_function(func_id);
|
||||
|
||||
// SAFETY: We compiled a function with signature () -> i64
|
||||
// SAFETY: We compiled a function with simple i64 ABI; we still call without args for now
|
||||
unsafe {
|
||||
let f: extern "C" fn() -> i64 = std::mem::transmute(code);
|
||||
let closure = std::sync::Arc::new(move |_args: &[crate::backend::vm::VMValue]| -> crate::backend::vm::VMValue {
|
||||
@ -200,14 +300,83 @@ impl IRBuilder for CraneliftBuilder {
|
||||
}
|
||||
fb.finalize();
|
||||
}
|
||||
|
||||
fn emit_host_call(&mut self, symbol: &str, _argc: usize, has_ret: bool) {
|
||||
use cranelift_codegen::ir::{AbiParam, Signature, types};
|
||||
use cranelift_frontend::FunctionBuilder;
|
||||
use cranelift_module::{Linkage, Module};
|
||||
|
||||
// Minimal import+call to a registered stub symbol; ignore args for now
|
||||
let call_conv = self.module.isa().default_call_conv();
|
||||
let mut sig = Signature::new(call_conv);
|
||||
// Collect up to _argc i64 values from stack as arguments (right-to-left)
|
||||
let mut args: Vec<cranelift_codegen::ir::Value> = Vec::new();
|
||||
let take_n = _argc.min(self.value_stack.len());
|
||||
for _ in 0..take_n { if let Some(v) = self.value_stack.pop() { args.push(v); } }
|
||||
args.reverse();
|
||||
// Build params for each collected arg
|
||||
for _ in 0..args.len() { sig.params.push(AbiParam::new(types::I64)); }
|
||||
if has_ret { sig.returns.push(AbiParam::new(types::I64)); }
|
||||
|
||||
let func_id = self.module
|
||||
.declare_function(symbol, Linkage::Import, &sig)
|
||||
.expect("declare import failed");
|
||||
|
||||
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
|
||||
if let Some(b) = self.entry_block { fb.switch_to_block(b); }
|
||||
let fref = self.module.declare_func_in_func(func_id, fb.func);
|
||||
let call_inst = fb.ins().call(fref, &args);
|
||||
if has_ret {
|
||||
let results = fb.inst_results(call_inst).to_vec();
|
||||
if let Some(v) = results.get(0).copied() {
|
||||
self.value_stack.push(v);
|
||||
}
|
||||
}
|
||||
fb.finalize();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "cranelift-jit")]
|
||||
impl CraneliftBuilder {
|
||||
fn entry_param(&mut self, index: usize) -> Option<cranelift_codegen::ir::Value> {
|
||||
use cranelift_frontend::FunctionBuilder;
|
||||
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
|
||||
if let Some(b) = self.entry_block {
|
||||
fb.switch_to_block(b);
|
||||
let params = fb.func.dfg.block_params(b).to_vec();
|
||||
if let Some(v) = params.get(index).copied() { return Some(v); }
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "cranelift-jit")]
|
||||
impl IRBuilder for CraneliftBuilder {
|
||||
fn emit_param_i64(&mut self, index: usize) {
|
||||
if let Some(v) = self.entry_param(index) {
|
||||
self.value_stack.push(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "cranelift-jit")]
|
||||
impl CraneliftBuilder {
|
||||
pub fn new() -> Self {
|
||||
// Initialize a minimal JITModule to validate linking; not used yet
|
||||
let builder = cranelift_jit::JITBuilder::new(cranelift_module::default_libcall_names())
|
||||
let mut builder = cranelift_jit::JITBuilder::new(cranelift_module::default_libcall_names())
|
||||
.expect("failed to create JITBuilder");
|
||||
// Register host-call symbols (PoC: map to simple C-ABI stubs)
|
||||
builder.symbol("nyash.host.stub0", nyash_host_stub0 as *const u8);
|
||||
{
|
||||
use crate::jit::r#extern::collections as c;
|
||||
builder.symbol(c::SYM_ARRAY_LEN, nyash_array_len as *const u8);
|
||||
builder.symbol(c::SYM_ARRAY_GET, nyash_array_get as *const u8);
|
||||
builder.symbol(c::SYM_ARRAY_SET, nyash_array_set as *const u8);
|
||||
builder.symbol(c::SYM_ARRAY_PUSH, nyash_array_push as *const u8);
|
||||
builder.symbol(c::SYM_MAP_GET, nyash_map_get as *const u8);
|
||||
builder.symbol(c::SYM_MAP_SET, nyash_map_set as *const u8);
|
||||
builder.symbol(c::SYM_MAP_SIZE, nyash_map_size as *const u8);
|
||||
}
|
||||
let module = cranelift_jit::JITModule::new(builder);
|
||||
let ctx = cranelift_codegen::Context::new();
|
||||
let fbc = cranelift_frontend::FunctionBuilderContext::new();
|
||||
@ -218,6 +387,8 @@ impl CraneliftBuilder {
|
||||
value_stack: Vec::new(),
|
||||
entry_block: None,
|
||||
compiled_closure: None,
|
||||
desired_argc: 0,
|
||||
desired_has_ret: true,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
use crate::mir::{MirFunction, MirInstruction, ConstValue, BinaryOp, CompareOp};
|
||||
use crate::mir::{MirFunction, MirInstruction, ConstValue, BinaryOp, CompareOp, ValueId};
|
||||
use super::builder::{IRBuilder, BinOpKind, CmpKind};
|
||||
|
||||
/// Lower(Core-1): Minimal lowering skeleton for Const/Move/BinOp/Cmp/Branch/Ret
|
||||
@ -6,14 +6,25 @@ use super::builder::{IRBuilder, BinOpKind, CmpKind};
|
||||
pub struct LowerCore {
|
||||
pub unsupported: usize,
|
||||
pub covered: usize,
|
||||
/// Minimal constant propagation for i64 to feed host-call args
|
||||
known_i64: std::collections::HashMap<ValueId, i64>,
|
||||
/// Parameter index mapping for ValueId
|
||||
param_index: std::collections::HashMap<ValueId, usize>,
|
||||
}
|
||||
|
||||
impl LowerCore {
|
||||
pub fn new() -> Self { Self { unsupported: 0, covered: 0 } }
|
||||
pub fn new() -> Self { Self { unsupported: 0, covered: 0, known_i64: std::collections::HashMap::new(), param_index: std::collections::HashMap::new() } }
|
||||
|
||||
/// Walk the MIR function and count supported/unsupported instructions.
|
||||
/// In the future, this will build CLIF via Cranelift builders.
|
||||
pub fn lower_function(&mut self, func: &MirFunction, builder: &mut dyn IRBuilder) -> Result<(), String> {
|
||||
// Prepare a simple i64 ABI based on param count; always assume i64 return for now
|
||||
// Build param index map
|
||||
self.param_index.clear();
|
||||
for (i, v) in func.params.iter().copied().enumerate() {
|
||||
self.param_index.insert(v, i);
|
||||
}
|
||||
builder.prepare_signature_i64(func.params.len(), true);
|
||||
builder.begin_function(&func.signature.name);
|
||||
for (_bb_id, bb) in func.blocks.iter() {
|
||||
for instr in bb.instructions.iter() {
|
||||
@ -29,6 +40,17 @@ impl LowerCore {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Push a value onto the builder stack if it is a known i64 const or a parameter.
|
||||
fn push_value_if_known_or_param(&self, b: &mut dyn IRBuilder, id: &ValueId) {
|
||||
if let Some(pidx) = self.param_index.get(id).copied() {
|
||||
b.emit_param_i64(pidx);
|
||||
return;
|
||||
}
|
||||
if let Some(v) = self.known_i64.get(id).copied() {
|
||||
b.emit_const_i64(v);
|
||||
}
|
||||
}
|
||||
|
||||
fn cover_if_supported(&mut self, instr: &MirInstruction) {
|
||||
use crate::mir::MirInstruction as I;
|
||||
let supported = matches!(
|
||||
@ -49,16 +71,33 @@ impl LowerCore {
|
||||
fn try_emit(&mut self, b: &mut dyn IRBuilder, instr: &MirInstruction) {
|
||||
use crate::mir::MirInstruction as I;
|
||||
match instr {
|
||||
I::Const { value, .. } => match value {
|
||||
ConstValue::Integer(i) => b.emit_const_i64(*i),
|
||||
I::Const { dst, value } => match value {
|
||||
ConstValue::Integer(i) => {
|
||||
b.emit_const_i64(*i);
|
||||
self.known_i64.insert(*dst, *i);
|
||||
}
|
||||
ConstValue::Float(f) => b.emit_const_f64(*f),
|
||||
ConstValue::Bool(_)
|
||||
| ConstValue::String(_) | ConstValue::Null | ConstValue::Void => {
|
||||
ConstValue::Bool(bv) => {
|
||||
let iv = if *bv { 1 } else { 0 };
|
||||
b.emit_const_i64(iv);
|
||||
self.known_i64.insert(*dst, iv);
|
||||
}
|
||||
ConstValue::String(_) | ConstValue::Null | ConstValue::Void => {
|
||||
// leave unsupported for now
|
||||
}
|
||||
},
|
||||
I::Copy { .. } => { /* no-op for now */ }
|
||||
I::BinOp { op, .. } => {
|
||||
I::Copy { dst, src } => {
|
||||
if let Some(v) = self.known_i64.get(src).copied() { self.known_i64.insert(*dst, v); }
|
||||
// If source is a parameter, materialize it on the stack for downstream ops
|
||||
if let Some(pidx) = self.param_index.get(src).copied() {
|
||||
b.emit_param_i64(pidx);
|
||||
}
|
||||
// Otherwise no-op for codegen (stack-machine handles sources directly later)
|
||||
}
|
||||
I::BinOp { dst, op, lhs, rhs } => {
|
||||
// Ensure operands are on stack when available (param or known const)
|
||||
self.push_value_if_known_or_param(b, lhs);
|
||||
self.push_value_if_known_or_param(b, rhs);
|
||||
let kind = match op {
|
||||
BinaryOp::Add => BinOpKind::Add,
|
||||
BinaryOp::Sub => BinOpKind::Sub,
|
||||
@ -70,8 +109,22 @@ impl LowerCore {
|
||||
| BinaryOp::BitAnd | BinaryOp::BitOr | BinaryOp::BitXor | BinaryOp::Shl | BinaryOp::Shr => { return; }
|
||||
};
|
||||
b.emit_binop(kind);
|
||||
if let (Some(a), Some(b)) = (self.known_i64.get(lhs), self.known_i64.get(rhs)) {
|
||||
let res = match op {
|
||||
BinaryOp::Add => a.wrapping_add(*b),
|
||||
BinaryOp::Sub => a.wrapping_sub(*b),
|
||||
BinaryOp::Mul => a.wrapping_mul(*b),
|
||||
BinaryOp::Div => if *b != 0 { a.wrapping_div(*b) } else { 0 },
|
||||
BinaryOp::Mod => if *b != 0 { a.wrapping_rem(*b) } else { 0 },
|
||||
_ => 0,
|
||||
};
|
||||
self.known_i64.insert(*dst, res);
|
||||
}
|
||||
}
|
||||
I::Compare { op, .. } => {
|
||||
I::Compare { op, lhs, rhs, .. } => {
|
||||
// Ensure operands are on stack when available (param or known const)
|
||||
self.push_value_if_known_or_param(b, lhs);
|
||||
self.push_value_if_known_or_param(b, rhs);
|
||||
let kind = match op {
|
||||
CompareOp::Eq => CmpKind::Eq,
|
||||
CompareOp::Ne => CmpKind::Ne,
|
||||
@ -84,12 +137,57 @@ impl LowerCore {
|
||||
}
|
||||
I::Jump { .. } => b.emit_jump(),
|
||||
I::Branch { .. } => b.emit_branch(),
|
||||
I::Return { .. } => b.emit_return(),
|
||||
I::ArrayGet { .. } => {
|
||||
b.emit_host_call(crate::jit::r#extern::collections::SYM_ARRAY_GET, 2, true);
|
||||
I::Return { value } => {
|
||||
if let Some(v) = value { self.push_value_if_known_or_param(b, v); }
|
||||
b.emit_return()
|
||||
}
|
||||
I::ArraySet { .. } => {
|
||||
b.emit_host_call(crate::jit::r#extern::collections::SYM_ARRAY_SET, 3, false);
|
||||
I::ArrayGet { array, index, .. } => {
|
||||
if std::env::var("NYASH_JIT_HOSTCALL").ok().as_deref() == Some("1") {
|
||||
// Push args: array param index (or -1), index (known or 0)
|
||||
let idx = self.known_i64.get(index).copied().unwrap_or(0);
|
||||
let arr_idx = self.param_index.get(array).copied().map(|x| x as i64).unwrap_or(-1);
|
||||
b.emit_const_i64(arr_idx);
|
||||
b.emit_const_i64(idx);
|
||||
b.emit_host_call(crate::jit::r#extern::collections::SYM_ARRAY_GET, 2, true);
|
||||
}
|
||||
}
|
||||
I::ArraySet { array, index, value } => {
|
||||
if std::env::var("NYASH_JIT_HOSTCALL").ok().as_deref() == Some("1") {
|
||||
let idx = self.known_i64.get(index).copied().unwrap_or(0);
|
||||
let val = self.known_i64.get(value).copied().unwrap_or(0);
|
||||
let arr_idx = self.param_index.get(array).copied().map(|x| x as i64).unwrap_or(-1);
|
||||
b.emit_const_i64(arr_idx);
|
||||
b.emit_const_i64(idx);
|
||||
b.emit_const_i64(val);
|
||||
b.emit_host_call(crate::jit::r#extern::collections::SYM_ARRAY_SET, 3, false);
|
||||
}
|
||||
}
|
||||
I::BoxCall { box_val: array, method, args, dst, .. } => {
|
||||
if std::env::var("NYASH_JIT_HOSTCALL").ok().as_deref() == Some("1") {
|
||||
match method.as_str() {
|
||||
"len" | "length" => {
|
||||
// argc=1: (array_param_index)
|
||||
let arr_idx = self.param_index.get(array).copied().map(|x| x as i64).unwrap_or(-1);
|
||||
b.emit_const_i64(arr_idx);
|
||||
b.emit_host_call(crate::jit::r#extern::collections::SYM_ARRAY_LEN, 1, dst.is_some());
|
||||
}
|
||||
"push" => {
|
||||
// argc=2: (array, value)
|
||||
let val = args.get(0).and_then(|v| self.known_i64.get(v)).copied().unwrap_or(0);
|
||||
let arr_idx = self.param_index.get(array).copied().map(|x| x as i64).unwrap_or(-1);
|
||||
b.emit_const_i64(arr_idx);
|
||||
b.emit_const_i64(val);
|
||||
b.emit_host_call(crate::jit::r#extern::collections::SYM_ARRAY_PUSH, 2, false);
|
||||
}
|
||||
"size" => {
|
||||
// MapBox.size(): argc=1 (map_param_index)
|
||||
let map_idx = self.param_index.get(array).copied().map(|x| x as i64).unwrap_or(-1);
|
||||
b.emit_const_i64(map_idx);
|
||||
b.emit_host_call(crate::jit::r#extern::collections::SYM_MAP_SIZE, 1, dst.is_some());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@ -80,7 +80,15 @@ impl JitManager {
|
||||
/// 10_c: execute compiled function if present (stub: empty args). Returns Some(VMValue) if JIT path was taken.
|
||||
pub fn execute_compiled(&self, func: &str, args: &[crate::backend::vm::VMValue]) -> Option<crate::backend::vm::VMValue> {
|
||||
if let Some(h) = self.handle_of(func) {
|
||||
return self.engine.execute_handle(h, args);
|
||||
// Expose current args to hostcall shims
|
||||
crate::jit::rt::set_current_args(args);
|
||||
let t0 = std::time::Instant::now();
|
||||
let out = self.engine.execute_handle(h, args);
|
||||
if std::env::var("NYASH_JIT_STATS").ok().as_deref() == Some("1") {
|
||||
let dt = t0.elapsed();
|
||||
eprintln!("[JIT] exec_time_ms={} for {}", dt.as_millis(), func);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
@ -4,3 +4,4 @@ pub mod manager;
|
||||
pub mod engine;
|
||||
pub mod lower;
|
||||
pub mod r#extern;
|
||||
pub mod rt;
|
||||
|
||||
25
src/jit/rt.rs
Normal file
25
src/jit/rt.rs
Normal file
@ -0,0 +1,25 @@
|
||||
use std::cell::RefCell;
|
||||
|
||||
use crate::backend::vm::VMValue;
|
||||
|
||||
thread_local! {
|
||||
static CURRENT_ARGS: RefCell<Vec<VMValue>> = RefCell::new(Vec::new());
|
||||
}
|
||||
|
||||
pub fn set_current_args(args: &[VMValue]) {
|
||||
CURRENT_ARGS.with(|cell| {
|
||||
let mut v = cell.borrow_mut();
|
||||
v.clear();
|
||||
v.extend_from_slice(args);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn with_args<F, R>(f: F) -> R
|
||||
where
|
||||
F: FnOnce(&[VMValue]) -> R,
|
||||
{
|
||||
CURRENT_ARGS.with(|cell| {
|
||||
let v = cell.borrow();
|
||||
f(&v)
|
||||
})
|
||||
}
|
||||
@ -5,33 +5,73 @@ impl NyashRunner {
|
||||
/// Execute benchmark mode (split)
|
||||
pub(crate) fn execute_benchmark_mode(&self) {
|
||||
println!("🏁 Running benchmark mode with {} iterations", self.config.iterations);
|
||||
let test_code = r#"
|
||||
local x
|
||||
x = 42
|
||||
local y
|
||||
y = x + 58
|
||||
return y
|
||||
"#;
|
||||
// Two tests: simple add, arithmetic loop
|
||||
let tests: Vec<(&str, &str)> = vec![
|
||||
(
|
||||
"simple_add",
|
||||
r#"
|
||||
local x
|
||||
x = 42
|
||||
local y
|
||||
y = x + 58
|
||||
return y
|
||||
"#,
|
||||
),
|
||||
(
|
||||
"arith_loop_100k",
|
||||
r#"
|
||||
local i, sum
|
||||
i = 0
|
||||
sum = 0
|
||||
loop(i < 100000) {
|
||||
sum = sum + i
|
||||
i = i + 1
|
||||
}
|
||||
return sum
|
||||
"#,
|
||||
),
|
||||
];
|
||||
|
||||
println!("\n🧪 Test code:\n{}", test_code);
|
||||
for (name, code) in tests {
|
||||
println!("\n====================================");
|
||||
println!("🧪 Test: {}", name);
|
||||
// Warmup (not measured)
|
||||
let warmup = (self.config.iterations / 10).max(1);
|
||||
self.bench_interpreter(code, warmup);
|
||||
self.bench_vm(code, warmup);
|
||||
self.bench_jit(code, warmup);
|
||||
|
||||
// Interpreter
|
||||
println!("\n⚡ Interpreter Backend:");
|
||||
// Measured runs
|
||||
let interpreter_time = self.bench_interpreter(code, self.config.iterations);
|
||||
let vm_time = self.bench_vm(code, self.config.iterations);
|
||||
let jit_time = self.bench_jit(code, self.config.iterations);
|
||||
|
||||
// Summary
|
||||
let vm_vs_interp = interpreter_time.as_secs_f64() / vm_time.as_secs_f64();
|
||||
let jit_vs_vm = vm_time.as_secs_f64() / jit_time.as_secs_f64();
|
||||
println!("\n📊 Performance Summary [{}]:", name);
|
||||
println!(" VM is {:.2}x {} than Interpreter", if vm_vs_interp > 1.0 { vm_vs_interp } else { 1.0 / vm_vs_interp }, if vm_vs_interp > 1.0 { "faster" } else { "slower" });
|
||||
println!(" JIT is {:.2}x {} than VM (note: compile cost included)", if jit_vs_vm > 1.0 { jit_vs_vm } else { 1.0 / jit_vs_vm }, if jit_vs_vm > 1.0 { "faster" } else { "slower" });
|
||||
}
|
||||
}
|
||||
|
||||
fn bench_interpreter(&self, code: &str, iters: u32) -> std::time::Duration {
|
||||
let start = std::time::Instant::now();
|
||||
for _ in 0..self.config.iterations {
|
||||
if let Ok(ast) = NyashParser::parse_from_string(test_code) {
|
||||
for _ in 0..iters {
|
||||
if let Ok(ast) = NyashParser::parse_from_string(code) {
|
||||
let mut interp = NyashInterpreter::new_with_groups(BuiltinGroups::native_full());
|
||||
let _ = interp.execute(ast);
|
||||
}
|
||||
}
|
||||
let interpreter_time = start.elapsed();
|
||||
println!(" {} iterations in {:?} ({:.2} ops/sec)", self.config.iterations, interpreter_time, self.config.iterations as f64 / interpreter_time.as_secs_f64());
|
||||
let elapsed = start.elapsed();
|
||||
println!(" ⚡ Interpreter: {} iters in {:?} ({:.2} ops/sec)", iters, elapsed, iters as f64 / elapsed.as_secs_f64());
|
||||
elapsed
|
||||
}
|
||||
|
||||
// VM
|
||||
println!("\n🚀 VM Backend:");
|
||||
fn bench_vm(&self, code: &str, iters: u32) -> std::time::Duration {
|
||||
let start = std::time::Instant::now();
|
||||
for _ in 0..self.config.iterations {
|
||||
if let Ok(ast) = NyashParser::parse_from_string(test_code) {
|
||||
for _ in 0..iters {
|
||||
if let Ok(ast) = NyashParser::parse_from_string(code) {
|
||||
let mut mc = MirCompiler::new();
|
||||
if let Ok(cr) = mc.compile(ast) {
|
||||
let mut vm = VM::new();
|
||||
@ -39,12 +79,27 @@ impl NyashRunner {
|
||||
}
|
||||
}
|
||||
}
|
||||
let vm_time = start.elapsed();
|
||||
println!(" {} iterations in {:?} ({:.2} ops/sec)", self.config.iterations, vm_time, self.config.iterations as f64 / vm_time.as_secs_f64());
|
||||
let elapsed = start.elapsed();
|
||||
println!(" 🚀 VM: {} iters in {:?} ({:.2} ops/sec)", iters, elapsed, iters as f64 / elapsed.as_secs_f64());
|
||||
elapsed
|
||||
}
|
||||
|
||||
// Summary
|
||||
let speedup = interpreter_time.as_secs_f64() / vm_time.as_secs_f64();
|
||||
println!("\n📊 Performance Summary:\n VM is {:.2}x {} than Interpreter", if speedup > 1.0 { speedup } else { 1.0 / speedup }, if speedup > 1.0 { "faster" } else { "slower" });
|
||||
fn bench_jit(&self, code: &str, iters: u32) -> std::time::Duration {
|
||||
// Force JIT mode for this run
|
||||
std::env::set_var("NYASH_JIT_EXEC", "1");
|
||||
std::env::set_var("NYASH_JIT_THRESHOLD", "1");
|
||||
let start = std::time::Instant::now();
|
||||
for _ in 0..iters {
|
||||
if let Ok(ast) = NyashParser::parse_from_string(code) {
|
||||
let mut mc = MirCompiler::new();
|
||||
if let Ok(cr) = mc.compile(ast) {
|
||||
let mut vm = VM::new();
|
||||
let _ = vm.execute_module(&cr.module);
|
||||
}
|
||||
}
|
||||
}
|
||||
let elapsed = start.elapsed();
|
||||
println!(" 🔥 JIT: {} iters in {:?} ({:.2} ops/sec)", iters, elapsed, iters as f64 / elapsed.as_secs_f64());
|
||||
elapsed
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -20,9 +20,12 @@ impl NyashRunner {
|
||||
|
||||
// Prepare runtime and collect Box declarations for VM user-defined types
|
||||
let runtime = {
|
||||
let rt = NyashRuntimeBuilder::new()
|
||||
.with_builtin_groups(BuiltinGroups::native_full())
|
||||
.build();
|
||||
let mut builder = NyashRuntimeBuilder::new()
|
||||
.with_builtin_groups(BuiltinGroups::native_full());
|
||||
if std::env::var("NYASH_GC_COUNTING").ok().as_deref() == Some("1") {
|
||||
builder = builder.with_counting_gc();
|
||||
}
|
||||
let rt = builder.build();
|
||||
self.collect_box_declarations(&ast, &rt);
|
||||
// Register UserDefinedBoxFactory backed by the same declarations
|
||||
let mut shared = SharedState::new();
|
||||
@ -39,6 +42,20 @@ impl NyashRunner {
|
||||
Err(e) => { eprintln!("❌ MIR compilation error: {}", e); process::exit(1); }
|
||||
};
|
||||
|
||||
// Optional: demo scheduling hook
|
||||
if std::env::var("NYASH_SCHED_DEMO").ok().as_deref() == Some("1") {
|
||||
if let Some(s) = &runtime.scheduler {
|
||||
// Immediate task
|
||||
s.spawn("demo-immediate", Box::new(|| {
|
||||
println!("[SCHED] immediate task ran at safepoint");
|
||||
}));
|
||||
// Delayed task
|
||||
s.spawn_after(0, "demo-delayed", Box::new(|| {
|
||||
println!("[SCHED] delayed task ran at safepoint");
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
// Execute with VM using prepared runtime
|
||||
let mut vm = VM::with_runtime(runtime);
|
||||
match vm.execute_module(&compile_result.module) {
|
||||
@ -81,4 +98,3 @@ impl NyashRunner {
|
||||
walk(ast, runtime);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
65
src/runtime/gc.rs
Normal file
65
src/runtime/gc.rs
Normal file
@ -0,0 +1,65 @@
|
||||
//! GC hook abstractions for switchable runtime (Phase 10.4 preparation)
|
||||
//!
|
||||
//! Minimal, no-alloc, no-type-coupling interfaces that VM can call.
|
||||
//! Default implementation is a no-op. Real collectors can plug later.
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum BarrierKind { Read, Write }
|
||||
|
||||
/// GC hooks that execution engines may call at key points.
|
||||
/// Implementations must be Send + Sync for multi-thread preparation.
|
||||
pub trait GcHooks: Send + Sync {
|
||||
/// Safe point for cooperative GC (e.g., poll or yield).
|
||||
fn safepoint(&self) {}
|
||||
/// Memory barrier hint for loads/stores.
|
||||
fn barrier(&self, _kind: BarrierKind) {}
|
||||
/// Optional counters snapshot for diagnostics. Default: None.
|
||||
fn snapshot_counters(&self) -> Option<(u64, u64, u64)> { None }
|
||||
}
|
||||
|
||||
/// Default no-op hooks.
|
||||
pub struct NullGc;
|
||||
|
||||
impl GcHooks for NullGc {}
|
||||
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
|
||||
/// Simple counting GC (PoC): counts safepoints and barriers.
|
||||
/// Useful to validate hook frequency without affecting semantics.
|
||||
pub struct CountingGc {
|
||||
pub safepoints: AtomicU64,
|
||||
pub barrier_reads: AtomicU64,
|
||||
pub barrier_writes: AtomicU64,
|
||||
}
|
||||
|
||||
impl CountingGc {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
safepoints: AtomicU64::new(0),
|
||||
barrier_reads: AtomicU64::new(0),
|
||||
barrier_writes: AtomicU64::new(0),
|
||||
}
|
||||
}
|
||||
pub fn snapshot(&self) -> (u64, u64, u64) {
|
||||
(
|
||||
self.safepoints.load(Ordering::Relaxed),
|
||||
self.barrier_reads.load(Ordering::Relaxed),
|
||||
self.barrier_writes.load(Ordering::Relaxed),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl GcHooks for CountingGc {
|
||||
fn safepoint(&self) {
|
||||
self.safepoints.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
fn barrier(&self, kind: BarrierKind) {
|
||||
match kind {
|
||||
BarrierKind::Read => { self.barrier_reads.fetch_add(1, Ordering::Relaxed); }
|
||||
BarrierKind::Write => { self.barrier_writes.fetch_add(1, Ordering::Relaxed); }
|
||||
}
|
||||
}
|
||||
fn snapshot_counters(&self) -> Option<(u64, u64, u64)> {
|
||||
Some(self.snapshot())
|
||||
}
|
||||
}
|
||||
@ -10,6 +10,8 @@ pub mod plugin_ffi_common;
|
||||
pub mod leak_tracker;
|
||||
pub mod unified_registry;
|
||||
pub mod nyash_runtime;
|
||||
pub mod gc;
|
||||
pub mod scheduler;
|
||||
// pub mod plugin_box; // legacy - 古いPluginBox
|
||||
// pub mod plugin_loader; // legacy - Host VTable使用
|
||||
pub mod type_meta;
|
||||
@ -24,6 +26,8 @@ pub use plugin_loader_unified::{PluginHost, get_global_plugin_host, init_global_
|
||||
pub mod cache_versions;
|
||||
pub use unified_registry::{get_global_unified_registry, init_global_unified_registry, register_user_defined_factory};
|
||||
pub use nyash_runtime::{NyashRuntime, NyashRuntimeBuilder};
|
||||
pub use gc::{GcHooks, BarrierKind};
|
||||
pub use scheduler::{Scheduler, SingleThreadScheduler};
|
||||
// pub use plugin_box::PluginBox; // legacy
|
||||
// Use unified plugin loader (formerly v2)
|
||||
// pub use plugin_loader::{PluginLoaderV2 as PluginLoader, get_global_loader_v2 as get_global_loader}; // legacy
|
||||
|
||||
@ -18,6 +18,10 @@ pub struct NyashRuntime {
|
||||
pub box_registry: Arc<Mutex<UnifiedBoxRegistry>>,
|
||||
/// User-defined box declarations collected from source
|
||||
pub box_declarations: Arc<RwLock<HashMap<String, BoxDeclaration>>>,
|
||||
/// GC hooks (switchable runtime). Default is no-op.
|
||||
pub gc: Arc<dyn crate::runtime::gc::GcHooks>,
|
||||
/// Optional scheduler (single-thread by default is fine)
|
||||
pub scheduler: Option<Arc<dyn crate::runtime::scheduler::Scheduler>>,
|
||||
}
|
||||
|
||||
impl NyashRuntime {
|
||||
@ -26,6 +30,8 @@ impl NyashRuntime {
|
||||
Self {
|
||||
box_registry: create_default_registry(),
|
||||
box_declarations: Arc::new(RwLock::new(HashMap::new())),
|
||||
gc: Arc::new(crate::runtime::gc::NullGc),
|
||||
scheduler: Some(Arc::new(crate::runtime::scheduler::SingleThreadScheduler::new())),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -35,11 +41,13 @@ pub struct NyashRuntimeBuilder {
|
||||
box_registry: Option<Arc<Mutex<UnifiedBoxRegistry>>>,
|
||||
box_declarations: Option<Arc<RwLock<HashMap<String, BoxDeclaration>>>>,
|
||||
builtin_groups: Option<BuiltinGroups>,
|
||||
gc: Option<Arc<dyn crate::runtime::gc::GcHooks>>,
|
||||
scheduler: Option<Arc<dyn crate::runtime::scheduler::Scheduler>>,
|
||||
}
|
||||
|
||||
impl NyashRuntimeBuilder {
|
||||
pub fn new() -> Self {
|
||||
Self { box_registry: None, box_declarations: None, builtin_groups: None }
|
||||
Self { box_registry: None, box_declarations: None, builtin_groups: None, gc: None, scheduler: None }
|
||||
}
|
||||
|
||||
/// Inject a BoxFactory implementation directly into a private registry
|
||||
@ -72,6 +80,8 @@ impl NyashRuntimeBuilder {
|
||||
NyashRuntime {
|
||||
box_registry: registry,
|
||||
box_declarations: self.box_declarations.unwrap_or_else(|| Arc::new(RwLock::new(HashMap::new()))),
|
||||
gc: self.gc.unwrap_or_else(|| Arc::new(crate::runtime::gc::NullGc)),
|
||||
scheduler: Some(self.scheduler.unwrap_or_else(|| Arc::new(crate::runtime::scheduler::SingleThreadScheduler::new()))),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -97,4 +107,29 @@ impl NyashRuntimeBuilder {
|
||||
self.builtin_groups = Some(groups);
|
||||
self
|
||||
}
|
||||
|
||||
/// Inject custom GC hooks (switchable runtime). Default is no-op.
|
||||
pub fn with_gc_hooks(mut self, gc: Arc<dyn crate::runtime::gc::GcHooks>) -> Self {
|
||||
self.gc = Some(gc);
|
||||
self
|
||||
}
|
||||
|
||||
/// Convenience: use CountingGc for development metrics
|
||||
pub fn with_counting_gc(mut self) -> Self {
|
||||
let gc = Arc::new(crate::runtime::gc::CountingGc::new());
|
||||
self.gc = Some(gc);
|
||||
self
|
||||
}
|
||||
|
||||
/// Inject a custom scheduler implementation
|
||||
pub fn with_scheduler(mut self, sched: Arc<dyn crate::runtime::scheduler::Scheduler>) -> Self {
|
||||
self.scheduler = Some(sched);
|
||||
self
|
||||
}
|
||||
|
||||
/// Convenience: use SingleThreadScheduler
|
||||
pub fn with_single_thread_scheduler(mut self) -> Self {
|
||||
self.scheduler = Some(Arc::new(crate::runtime::scheduler::SingleThreadScheduler::new()));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
69
src/runtime/scheduler.rs
Normal file
69
src/runtime/scheduler.rs
Normal file
@ -0,0 +1,69 @@
|
||||
//! Minimal scheduler abstraction (Phase 10.6b prep)
|
||||
//!
|
||||
//! Provides a pluggable interface to run tasks and yield cooperatively.
|
||||
|
||||
pub trait Scheduler: Send + Sync {
|
||||
/// Spawn a task/closure. Default impl may run inline.
|
||||
fn spawn(&self, _name: &str, f: Box<dyn FnOnce() + Send + 'static>);
|
||||
/// Spawn a task after given delay milliseconds.
|
||||
fn spawn_after(&self, _delay_ms: u64, _name: &str, _f: Box<dyn FnOnce() + Send + 'static>) {}
|
||||
/// Poll scheduler: run due tasks and a limited number of queued tasks.
|
||||
fn poll(&self) {}
|
||||
/// Cooperative yield point (no-op for single-thread).
|
||||
fn yield_now(&self) { }
|
||||
}
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
/// Single-thread scheduler with a simple queue and delayed tasks.
|
||||
pub struct SingleThreadScheduler {
|
||||
queue: Arc<Mutex<VecDeque<Box<dyn FnOnce() + Send + 'static>>>>,
|
||||
delayed: Arc<Mutex<Vec<(Instant, Box<dyn FnOnce() + Send + 'static>)>>>,
|
||||
}
|
||||
|
||||
impl SingleThreadScheduler {
|
||||
pub fn new() -> Self {
|
||||
Self { queue: Arc::new(Mutex::new(VecDeque::new())), delayed: Arc::new(Mutex::new(Vec::new())) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Scheduler for SingleThreadScheduler {
|
||||
fn spawn(&self, _name: &str, f: Box<dyn FnOnce() + Send + 'static>) {
|
||||
if let Ok(mut q) = self.queue.lock() { q.push_back(f); }
|
||||
}
|
||||
fn spawn_after(&self, delay_ms: u64, _name: &str, f: Box<dyn FnOnce() + Send + 'static>) {
|
||||
let when = Instant::now() + Duration::from_millis(delay_ms);
|
||||
if let Ok(mut d) = self.delayed.lock() { d.push((when, f)); }
|
||||
}
|
||||
fn poll(&self) {
|
||||
// Move due delayed tasks to queue
|
||||
let trace = std::env::var("NYASH_SCHED_TRACE").ok().as_deref() == Some("1");
|
||||
let now = Instant::now();
|
||||
let mut moved = 0usize;
|
||||
if let Ok(mut d) = self.delayed.lock() {
|
||||
let mut i = 0;
|
||||
while i < d.len() {
|
||||
if d[i].0 <= now {
|
||||
let (_when, task) = d.remove(i);
|
||||
if let Ok(mut q) = self.queue.lock() { q.push_back(task); }
|
||||
moved += 1;
|
||||
} else { i += 1; }
|
||||
}
|
||||
}
|
||||
// Run up to budget queued tasks
|
||||
let budget: usize = std::env::var("NYASH_SCHED_POLL_BUDGET")
|
||||
.ok().and_then(|s| s.parse().ok()).filter(|&n: &usize| n > 0).unwrap_or(1);
|
||||
let mut ran = 0usize;
|
||||
while ran < budget {
|
||||
let task_opt = {
|
||||
if let Ok(mut q) = self.queue.lock() { q.pop_front() } else { None }
|
||||
};
|
||||
if let Some(task) = task_opt { task(); ran += 1; } else { break; }
|
||||
}
|
||||
if trace {
|
||||
eprintln!("[SCHED] poll moved={} ran={} budget={}", moved, ran, budget);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -14,6 +14,8 @@ use crate::runtime::plugin_loader_v2::PluginBoxV2;
|
||||
pub struct ScopeTracker {
|
||||
/// Stack of scopes, each containing Boxes created in that scope
|
||||
scopes: Vec<Vec<Arc<dyn NyashBox>>>,
|
||||
/// Root regions for GC (values pinned as roots during a dynamic region)
|
||||
roots: Vec<Vec<crate::backend::vm::VMValue>>,
|
||||
}
|
||||
|
||||
impl ScopeTracker {
|
||||
@ -21,6 +23,7 @@ impl ScopeTracker {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
scopes: vec![Vec::new()], // Start with one root scope
|
||||
roots: vec![Vec::new()], // Start with one root region
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,9 +53,7 @@ impl ScopeTracker {
|
||||
}
|
||||
|
||||
// Ensure we always have at least one scope
|
||||
if self.scopes.is_empty() {
|
||||
self.scopes.push(Vec::new());
|
||||
}
|
||||
if self.scopes.is_empty() { self.scopes.push(Vec::new()); }
|
||||
}
|
||||
|
||||
/// Register a Box in the current scope
|
||||
@ -73,6 +74,52 @@ impl ScopeTracker {
|
||||
if let Some(root_scope) = self.scopes.first_mut() {
|
||||
root_scope.clear();
|
||||
}
|
||||
|
||||
// Reset roots to a single empty region
|
||||
self.roots.clear();
|
||||
self.roots.push(Vec::new());
|
||||
}
|
||||
|
||||
// ===== GC root region API (Phase 10.4 prep) =====
|
||||
/// Enter a new GC root region
|
||||
pub fn enter_root_region(&mut self) {
|
||||
if std::env::var("NYASH_GC_TRACE").ok().as_deref() == Some("1") {
|
||||
eprintln!("[GC] roots: enter");
|
||||
}
|
||||
self.roots.push(Vec::new());
|
||||
}
|
||||
|
||||
/// Leave current GC root region (dropping all pinned values)
|
||||
pub fn leave_root_region(&mut self) {
|
||||
if let Some(_) = self.roots.pop() {
|
||||
if std::env::var("NYASH_GC_TRACE").ok().as_deref() == Some("1") {
|
||||
eprintln!("[GC] roots: leave");
|
||||
}
|
||||
}
|
||||
if self.roots.is_empty() { self.roots.push(Vec::new()); }
|
||||
}
|
||||
|
||||
/// Pin a VMValue into the current root region (cheap clone)
|
||||
pub fn pin_root(&mut self, v: &crate::backend::vm::VMValue) {
|
||||
if let Some(cur) = self.roots.last_mut() {
|
||||
cur.push(v.clone());
|
||||
if std::env::var("NYASH_GC_TRACE").ok().as_deref() == Some("1") {
|
||||
eprintln!("[GC] roots: pin {:?}", v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Total number of pinned roots across all regions (for GC PoC diagnostics)
|
||||
pub fn root_count_total(&self) -> usize { self.roots.iter().map(|r| r.len()).sum() }
|
||||
|
||||
/// Number of active root regions
|
||||
pub fn root_regions(&self) -> usize { self.roots.len() }
|
||||
|
||||
/// Snapshot a flat vector of current roots (cloned) for diagnostics
|
||||
pub fn roots_snapshot(&self) -> Vec<crate::backend::vm::VMValue> {
|
||||
let mut out = Vec::new();
|
||||
for region in &self.roots { out.extend(region.iter().cloned()); }
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user