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

65
src/runtime/gc.rs Normal file
View 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())
}
}

View File

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

View File

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