diff --git a/crates/oxide_kernel/src/kernel.rs b/crates/oxide_kernel/src/kernel.rs index c018a8b0..fb237ad3 100644 --- a/crates/oxide_kernel/src/kernel.rs +++ b/crates/oxide_kernel/src/kernel.rs @@ -7,6 +7,7 @@ use crate::code_forge::CodeForge; use crate::prop_forge::PropForge; use crate::shape_forge::ShapeForge; use crate::string_forge::Interner; +use crate::vm_pool::{VmGuard, VmPool}; pub struct KernelConfig { pub min_pool_size: usize, @@ -58,10 +59,6 @@ impl Default for KernelConfig { } } -pub struct VmPool; - -pub struct VmGuard<'p>(std::marker::PhantomData<&'p ()>); - pub struct OxideKernel { pub config: KernelConfig, pub string_forge: Arc, @@ -70,7 +67,7 @@ pub struct OxideKernel { pub prop_forge: Arc, pub builtin_world: Arc, #[allow(dead_code)] - vm_pool: Mutex>, + vm_pool: Mutex>>, } impl OxideKernel { @@ -92,6 +89,24 @@ impl OxideKernel { } } + pub fn init_vm_pool(self: &Arc) { + let pool = VmPool::new( + Arc::clone(self), + self.config.min_pool_size, + self.config.max_pool_size, + ); + *self.vm_pool.lock().unwrap() = Some(pool); + } + + pub fn spawn(&self) -> VmGuard { + self.vm_pool + .lock() + .unwrap() + .as_ref() + .expect("vm_pool not initialized — call init_vm_pool() first") + .spawn() + } + pub fn string_forge(&self) -> &Arc { &self.string_forge } @@ -115,10 +130,6 @@ impl OxideKernel { pub fn config(&self) -> &KernelConfig { &self.config } - - pub fn spawn(&self) -> VmGuard<'_> { - todo!("implemented in 07-07") - } } #[cfg(test)] @@ -162,4 +173,14 @@ mod tests { assert!(KernelConfig::full().warmup_builtin_ic); assert_eq!(KernelConfig::full().max_pool_size, None); } + + #[test] + fn test_kernel_spawn() { + let kernel = Arc::new(OxideKernel::new(KernelConfig::minimal())); + kernel.init_vm_pool(); + let guard = kernel.spawn(); + drop(guard); + let guard2 = kernel.spawn(); + drop(guard2); + } } diff --git a/crates/oxide_kernel/src/lib.rs b/crates/oxide_kernel/src/lib.rs index 2804b468..9624635c 100644 --- a/crates/oxide_kernel/src/lib.rs +++ b/crates/oxide_kernel/src/lib.rs @@ -6,3 +6,4 @@ pub mod kernel; pub mod prop_forge; pub mod shape_forge; pub mod string_forge; +pub mod vm_pool; diff --git a/crates/oxide_kernel/src/vm_pool.rs b/crates/oxide_kernel/src/vm_pool.rs new file mode 100644 index 00000000..337715e5 --- /dev/null +++ b/crates/oxide_kernel/src/vm_pool.rs @@ -0,0 +1,176 @@ +#![allow(clippy::arc_with_non_send_sync)] + +use std::sync::{Arc, Condvar, Mutex}; + +use oxide_vm::mem::P; +use oxide_vm::vm::Vm; + +use crate::kernel::OxideKernel; + +struct VmPoolInner { + available: Vec, + total_count: usize, +} + +pub struct VmPool { + kernel: Arc, + inner: Mutex, + condvar: Condvar, + #[allow(dead_code)] + min_size: usize, + max_size: Option, +} + +pub struct VmGuard { + vm: Option, + pool: Arc, + dirty: bool, + interned_strings: Vec, +} + +impl VmPool { + pub fn new(kernel: Arc, min_size: usize, max_size: Option) -> Arc { + let pool = Arc::new(Self { + kernel, + inner: Mutex::new(VmPoolInner { + available: Vec::with_capacity(min_size), + total_count: 0, + }), + condvar: Condvar::new(), + min_size, + max_size, + }); + + { + let mut inner = pool.inner.lock().unwrap(); + for _ in 0..min_size { + let vm = Self::new_vm(&pool.kernel); + inner.available.push(vm); + inner.total_count += 1; + } + } + + pool + } + + fn new_vm(kernel: &Arc) -> Vm { + let mut vm = Vm::new(); + vm.object_prototype = P::clone(&kernel.builtin_world().object_proto); + vm + } + + fn replace_vm(&self) -> Vm { + Self::new_vm(&self.kernel) + } + + pub fn spawn(self: &Arc) -> VmGuard { + loop { + let mut inner = self.inner.lock().unwrap(); + + if let Some(vm) = inner.available.pop() { + return VmGuard { + vm: Some(vm), + pool: Arc::clone(self), + dirty: false, + interned_strings: Vec::new(), + }; + } + + let can_grow = match self.max_size { + Some(max) => inner.total_count < max, + None => true, + }; + + if can_grow { + inner.total_count += 1; + drop(inner); + let vm = Self::new_vm(&self.kernel); + return VmGuard { + vm: Some(vm), + pool: Arc::clone(self), + dirty: false, + interned_strings: Vec::new(), + }; + } + + inner = self.condvar.wait(inner).unwrap(); + } + } +} + +impl VmGuard { + pub fn vm(&self) -> &Vm { + self.vm.as_ref().expect("VmGuard has no VM") + } + + pub fn vm_mut(&mut self) -> &mut Vm { + self.vm.as_mut().expect("VmGuard has no VM") + } + + pub fn mark_dirty(&mut self) { + self.dirty = true; + } +} + +impl Drop for VmGuard { + fn drop(&mut self) { + let Some(vm) = self.vm.take() else { + return; + }; + + let mut inner = self.pool.inner.lock().unwrap(); + + if self.dirty { + let new_vm = self.pool.replace_vm(); + inner.available.push(new_vm); + } else { + for &idx in &self.interned_strings { + self.pool.kernel.string_forge().decref(idx); + } + + let mut returned_vm = vm; + returned_vm.reset(); + inner.available.push(returned_vm); + } + + self.pool.condvar.notify_one(); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::kernel::KernelConfig; + + fn test_kernel() -> Arc { + Arc::new(OxideKernel::new(KernelConfig::minimal())) + } + + #[test] + fn test_pool_spawn_returns_guard() { + let kernel = test_kernel(); + let pool = VmPool::new(kernel, 1, None); + let guard = pool.spawn(); + drop(guard); + } + + #[test] + fn test_pool_recycle_on_drop() { + let kernel = test_kernel(); + let pool = VmPool::new(kernel, 1, None); + let guard = pool.spawn(); + drop(guard); + let guard2 = pool.spawn(); + drop(guard2); + } + + #[test] + fn test_pool_grows_if_empty() { + let kernel = test_kernel(); + let pool = VmPool::new(Arc::clone(&kernel), 1, Some(3)); + let g1 = pool.spawn(); + let g2 = pool.spawn(); + drop(g1); + drop(g2); + } +} diff --git a/crates/oxide_vm/src/vm.rs b/crates/oxide_vm/src/vm.rs index fd8433b0..012d8618 100644 --- a/crates/oxide_vm/src/vm.rs +++ b/crates/oxide_vm/src/vm.rs @@ -43,6 +43,17 @@ impl Vm { } } + pub fn reset(&mut self) { + self.regs = [JsValue::undefined(); 256]; + self.pc = 0; + self.bytecode.clear(); + self.constants.clear(); + self.frames.clear(); + self.string_table.clear(); + self.string_reverse.clear(); + self.epoch.reset(); + } + pub fn intern(&mut self, s: &str) -> JsValue { if let Some(&idx) = self.string_table.get(s) { return JsValue::string(idx, hash16(s));