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:
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user