From 41ec5a74ac200f5fd38afa9338710d3c72aad524 Mon Sep 17 00:00:00 2001 From: Jonathan Johnson Date: Wed, 27 Mar 2024 08:43:54 -0700 Subject: [PATCH] Yielding to the collector The Vm now has a setting for how many steps to count per each budget charge, and each budget charge is when we also yield to the garbage collector. This yielding slowed execution down. One area where extra collection was happening that didn't need to was how the registered code was being stored as a weak reference. By upgrading it to a root, we can avoid constantly dropping the root count to 0 when calling Function::call(). Roots dropping to 0 will notify the collector to run, which is the overhead we really are avoiding. This ended up being a net swap for performance in the fibonacci vm example. --- Cargo.lock | 7 +-- Cargo.toml | 1 + src/list.rs | 17 ++++--- src/map.rs | 14 +++--- src/string.rs | 36 +++++++-------- src/symbol.rs | 2 +- src/tests.rs | 8 ++-- src/value.rs | 20 ++++++--- src/vm.rs | 117 ++++++++++++++++++++++++++++++------------------ tests/hosted.rs | 6 +-- 10 files changed, 131 insertions(+), 97 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2c3aefd..3cc7835 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1637,6 +1637,7 @@ dependencies = [ "dirs", "flume", "kempt", + "parking_lot", "pollster", "refuse", "refuse-pool", @@ -2379,7 +2380,7 @@ dependencies = [ [[package]] name = "refuse" version = "0.0.1" -source = "git+https://github.com/khonsulabs/refuse#40f8853ff4d2606f9e450e9635b5463162e6419a" +source = "git+https://github.com/khonsulabs/refuse#bc969f555fad0f7d2a1ca796475490e40d77415d" dependencies = [ "crossbeam-utils", "flume", @@ -2392,7 +2393,7 @@ dependencies = [ [[package]] name = "refuse-macros" version = "0.1.0" -source = "git+https://github.com/khonsulabs/refuse#40f8853ff4d2606f9e450e9635b5463162e6419a" +source = "git+https://github.com/khonsulabs/refuse#bc969f555fad0f7d2a1ca796475490e40d77415d" dependencies = [ "attribute-derive 0.9.1", "manyhow 0.11.1", @@ -2404,7 +2405,7 @@ dependencies = [ [[package]] name = "refuse-pool" version = "0.1.0" -source = "git+https://github.com/khonsulabs/refuse#40f8853ff4d2606f9e450e9635b5463162e6419a" +source = "git+https://github.com/khonsulabs/refuse#bc969f555fad0f7d2a1ca796475490e40d77415d" dependencies = [ "ahash", "hashbrown", diff --git a/Cargo.toml b/Cargo.toml index 4db2019..c789485 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,7 @@ serde = { version = "1.0.195", features = ["derive", "rc"] } unicode-ident = "1.0.12" refuse = { git = "https://github.com/khonsulabs/refuse" } refuse-pool = { git = "https://github.com/khonsulabs/refuse" } +parking_lot = "0.12.1" [dev-dependencies] rsn = "0.1" diff --git a/src/list.rs b/src/list.rs index 1dd1f51..a618afb 100644 --- a/src/list.rs +++ b/src/list.rs @@ -1,5 +1,4 @@ -use std::sync::Mutex; - +use parking_lot::Mutex; use refuse::Trace; use crate::symbol::Symbol; @@ -28,7 +27,7 @@ pub static LIST_TYPE: RustType = RustType::new("List", |t| { Symbol::len_symbol(), 0, |_vm: &mut VmContext<'_, '_>, this: &Rooted| { - Value::try_from(this.0.lock().expect("poisoned").len()) + Value::try_from(this.0.lock().len()) }, ) .with_fn(Symbol::set_symbol(), 2, |vm, this| { @@ -64,7 +63,7 @@ impl List { let Some(index) = index.as_usize() else { return Err(Fault::OutOfBounds); }; - let contents = self.0.lock().expect("poisoned"); + let contents = self.0.lock(); contents.get(index).copied().ok_or(Fault::OutOfBounds) } @@ -73,13 +72,13 @@ impl List { let Some(index) = index.as_usize() else { return Err(Fault::OutOfBounds); }; - let mut contents = self.0.lock().expect("poisoned"); + let mut contents = self.0.lock(); contents.insert(index, value); Ok(()) } pub fn push(&self, value: Value) -> Result<(), Fault> { - let mut contents = self.0.lock().expect("poisoned"); + let mut contents = self.0.lock(); contents.push(value); Ok(()) } @@ -88,7 +87,7 @@ impl List { let Some(index) = index.as_usize() else { return Err(Fault::OutOfBounds); }; - let mut contents = self.0.lock().expect("poisoned"); + let mut contents = self.0.lock(); if let Some(entry) = contents.get_mut(index) { Ok(Some(std::mem::replace(entry, value))) @@ -98,7 +97,7 @@ impl List { } pub fn to_vec(&self) -> Vec { - self.0.lock().expect("poisoned").clone() + self.0.lock().clone() } } @@ -124,6 +123,6 @@ impl Trace for List { const MAY_CONTAIN_REFERENCES: bool = true; fn trace(&self, tracer: &mut refuse::Tracer) { - self.0.lock().expect("poisoned").trace(tracer); + self.0.lock().trace(tracer); } } diff --git a/src/map.rs b/src/map.rs index e2c0120..9efa2d9 100644 --- a/src/map.rs +++ b/src/map.rs @@ -1,6 +1,6 @@ use std::cmp::Ordering; -use std::sync::Mutex; +use parking_lot::Mutex; use refuse::{CollectionGuard, Trace}; use crate::list::List; @@ -50,7 +50,7 @@ pub static MAP_TYPE: RustType = RustType::new("Map", |t| { this.nth(&index, vm.as_ref()) }) .with_fn(Symbol::len_symbol(), 0, |_vm, this| { - let contents = this.0.lock().expect("poisoned"); + let contents = this.0.lock(); Value::try_from(contents.len()) }) }); @@ -63,7 +63,7 @@ impl Trace for Map { const MAY_CONTAIN_REFERENCES: bool = true; fn trace(&self, tracer: &mut refuse::Tracer) { - for field in &*self.0.lock().expect("poisoned") { + for field in &*self.0.lock() { field.key.value.trace(tracer); field.value.trace(tracer); } @@ -93,7 +93,7 @@ impl Map { pub fn get(&self, vm: &mut VmContext<'_, '_>, key: &Value) -> Result, Fault> { let hash = key.hash(vm); - let contents = self.0.lock().expect("poisoned"); + let contents = self.0.lock(); for field in &*contents { match hash.cmp(&field.key.hash) { Ordering::Less => continue, @@ -113,7 +113,7 @@ impl Map { let Some(index) = index.as_usize() else { return Err(Fault::OutOfBounds); }; - let contents = self.0.lock().expect("poisoned"); + let contents = self.0.lock(); contents .get(index) .map(|field| Value::dynamic(List::from_iter([field.key.value, field.value]), guard)) @@ -127,7 +127,7 @@ impl Map { value: Value, ) -> Result, Fault> { let key = MapKey::new(vm, key); - let mut contents = self.0.lock().expect("poisoned"); + let mut contents = self.0.lock(); let mut insert_at = contents.len(); for (index, field) in contents.iter_mut().enumerate() { match key.hash.cmp(&field.key.hash) { @@ -153,7 +153,7 @@ impl Map { } pub fn to_vec(&self) -> Vec { - self.0.lock().expect("poisoned").clone() + self.0.lock().clone() } } diff --git a/src/string.rs b/src/string.rs index fd05c09..47d5627 100644 --- a/src/string.rs +++ b/src/string.rs @@ -1,7 +1,7 @@ use std::fmt::Display; use std::hash::Hash; -use std::sync::{Mutex, MutexGuard}; +use parking_lot::{Mutex, MutexGuard}; use refuse::ContainsNoRefs; use crate::list::List; @@ -17,7 +17,7 @@ pub struct MuseString(Mutex); impl MuseString { pub(crate) fn lock(&self) -> MutexGuard<'_, String> { - self.0.lock().expect("poisoned") + self.0.lock() } } @@ -75,15 +75,15 @@ pub static STRING_TYPE: RustType = RustType::new("String", |t| { }) .with_hash(|_| { |this, _vm, hasher| { - this.0.lock().expect("poisoned").hash(hasher); + this.0.lock().hash(hasher); } }) .with_eq(|_| { |this, vm, rhs| { if let Some(rhs) = rhs.as_downcast_ref::(vm.as_ref()) { - Ok(*this.0.lock().expect("poisoned") == *rhs.0.lock().expect("poisoned")) + Ok(*this.0.lock() == *rhs.0.lock()) } else if let Some(rhs) = rhs.as_symbol(vm.as_ref()) { - Ok(*this.0.lock().expect("poisoned") == *rhs) + Ok(*this.0.lock() == *rhs) } else { Ok(false) } @@ -92,11 +92,7 @@ pub static STRING_TYPE: RustType = RustType::new("String", |t| { .with_total_cmp(|_| { |this, vm, rhs| { if let Some(rhs) = rhs.as_downcast_ref::(vm.as_ref()) { - Ok(this - .0 - .lock() - .expect("poisoned") - .cmp(&rhs.0.lock().expect("poisoned"))) + Ok(this.0.lock().cmp(&rhs.0.lock())) } else if rhs.as_any_dynamic().is_none() { // Dynamics sort after primitive values Ok(std::cmp::Ordering::Greater) @@ -114,7 +110,7 @@ pub static STRING_TYPE: RustType = RustType::new("String", |t| { Symbol::len_symbol(), 0, |_vm: &mut VmContext<'_, '_>, this: &Rooted| { - Value::try_from(this.0.lock().expect("poisoned").len()) + Value::try_from(this.0.lock().len()) }, ) .with_fn( @@ -169,7 +165,7 @@ pub static STRING_TYPE: RustType = RustType::new("String", |t| { }) .with_add(|_| { |this, vm, rhs| { - let lhs = this.0.lock().expect("poisoned"); + let lhs = this.0.lock(); if let Some(rhs) = rhs.as_downcast_ref::(vm.as_ref()) { if std::ptr::eq(&this.0, &rhs.0) { let mut repeated = @@ -178,7 +174,7 @@ pub static STRING_TYPE: RustType = RustType::new("String", |t| { repeated.push_str(&lhs); Ok(Value::dynamic(MuseString::from(repeated), vm)) } else { - let rhs = rhs.0.lock().expect("poisoned"); + let rhs = rhs.0.lock(); let mut combined = String::with_capacity(lhs.len() + rhs.len()); combined.push_str(&lhs); combined.push_str(&rhs); @@ -196,7 +192,7 @@ pub static STRING_TYPE: RustType = RustType::new("String", |t| { .with_add_right(|_| { |this, vm, lhs| { let lhs = lhs.to_string(vm)?.try_upgrade(vm.guard())?; - let rhs = this.0.lock().expect("poisoned"); + let rhs = this.0.lock(); let mut combined = String::with_capacity(rhs.len() + lhs.len()); combined.push_str(&lhs); combined.push_str(&rhs); @@ -208,7 +204,7 @@ pub static STRING_TYPE: RustType = RustType::new("String", |t| { let Some(times) = rhs.as_usize() else { return Err(Fault::ExpectedInteger); }; - let this = this.0.lock().expect("poisoned"); + let this = this.0.lock(); Ok(Value::dynamic(MuseString::from(this.repeat(times)), vm)) } @@ -218,17 +214,17 @@ pub static STRING_TYPE: RustType = RustType::new("String", |t| { let Some(times) = lhs.as_usize() else { return Err(Fault::ExpectedInteger); }; - let this = this.0.lock().expect("poisoned"); + let this = this.0.lock(); Ok(Value::dynamic(MuseString::from(this.repeat(times)), vm)) } }) - .with_truthy(|_| |this, _vm| !this.0.lock().expect("poisoned").is_empty()) - .with_to_string(|_| |this, _vm| Ok(SymbolRef::from(&*this.0.lock().expect("poisoned")))) + .with_truthy(|_| |this, _vm| !this.0.lock().is_empty()) + .with_to_string(|_| |this, _vm| Ok(SymbolRef::from(&*this.0.lock()))) .with_deep_clone(|_| { |this, guard| { Some(AnyDynamic::new( - MuseString(Mutex::new(this.0.lock().expect("poisoned").clone())), + MuseString(Mutex::new(this.0.lock().clone())), guard, )) } @@ -244,6 +240,6 @@ impl CustomType for MuseString { impl Display for MuseString { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.0.lock().expect("poisioned").fmt(f) + self.0.lock().fmt(f) } } diff --git a/src/symbol.rs b/src/symbol.rs index abaa34e..e7d9851 100644 --- a/src/symbol.rs +++ b/src/symbol.rs @@ -36,7 +36,7 @@ impl SymbolRef { impl kempt::Sort for Symbol { fn compare(&self, other: &SymbolRef) -> std::cmp::Ordering { - self.0.as_any().cmp(&other.0.as_any()) + self.0.downgrade_any().cmp(&other.0.as_any()) } } diff --git a/src/tests.rs b/src/tests.rs index 7ae372b..5324317 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -21,8 +21,9 @@ fn budgeting() { code.copy(value, Register(0)); } let code = code.to_code(&guard); - let mut vm = Vm::new(&guard); + let vm = Vm::new(&guard); // Turn on budgeting, but don't give any budget. + vm.set_steps_per_charge(1); vm.increase_budget(0); assert_eq!( vm.execute(&code, &mut guard).unwrap_err(), @@ -59,8 +60,9 @@ fn module_budgeting() { &guard, ) .unwrap(); - let mut vm = Vm::new(&guard); + let vm = Vm::new(&guard); // Turn on budgeting, but don't give any budget. + vm.set_steps_per_charge(1); vm.increase_budget(0); assert_eq!( vm.execute(&code, &mut guard).unwrap_err(), @@ -97,7 +99,7 @@ fn invoke() { &guard, ) .unwrap(); - let mut vm = Vm::new(&guard); + let vm = Vm::new(&guard); vm.execute(&code, &mut guard).unwrap(); let Value::Int(result) = vm diff --git a/src/value.rs b/src/value.rs index 09a8fbb..a1c563c 100644 --- a/src/value.rs +++ b/src/value.rs @@ -6,13 +6,14 @@ use std::hash::{Hash, Hasher}; use std::marker::PhantomData; use std::ops::Deref; use std::pin::Pin; -use std::sync::{Arc, Mutex, OnceLock}; +use std::sync::{Arc, OnceLock}; use std::task::{Context, Poll}; pub type ValueHasher = ahash::AHasher; use kempt::Map; -use refuse::{AnyRef, CollectionGuard, ContainsNoRefs, MapAs, Ref, Root, SimpleType, Trace}; +use parking_lot::Mutex; +use refuse::{AnyRef, AnyRoot, CollectionGuard, ContainsNoRefs, MapAs, Ref, Root, Trace}; use crate::string::MuseString; use crate::symbol::{Symbol, SymbolList, SymbolRef}; @@ -1443,7 +1444,12 @@ where #[must_use] pub fn as_any_dynamic(&self) -> AnyDynamic { - AnyDynamic(self.0.as_any()) + AnyDynamic(self.0.downgrade_any()) + } + + #[must_use] + pub fn into_any_root(self) -> AnyRoot { + self.0.into_any_root() } } @@ -1634,7 +1640,7 @@ where } } -// #[derive(Debug)] +#[derive(Trace)] pub struct Type { pub name: Symbol, pub vtable: TypeVtable, @@ -2362,8 +2368,6 @@ impl Debug for Type { } } -impl SimpleType for Type {} - pub struct TypedTypeBuilder { t: Type, _t: PhantomData, @@ -3105,6 +3109,8 @@ impl Default for TypeVtable { } } +impl ContainsNoRefs for TypeVtable {} + pub trait CustomType: Send + Sync + Debug + 'static { fn muse_type(&self) -> &TypeRef; } @@ -3315,7 +3321,7 @@ impl CustomType for AsyncFunction { unreachable!("missing future") }; - let mut future = future.0.lock().expect("poisoned"); + let mut future = future.0.lock(); let mut cx = Context::from_waker(vm.waker()); match Future::poll(std::pin::pin!(&mut *future), &mut cx) { Poll::Ready(Ok(result)) => { diff --git a/src/vm.rs b/src/vm.rs index 06dd6f5..07b9b2f 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -5,7 +5,7 @@ use std::hash::Hash; use std::num::{NonZeroUsize, TryFromIntError}; use std::ops::{Deref, DerefMut, Index, IndexMut}; use std::pin::Pin; -use std::sync::{Arc, Mutex, MutexGuard, OnceLock}; +use std::sync::{Arc, OnceLock}; use std::task::{Poll, Wake, Waker}; use std::time::{Duration, Instant}; use std::{array, task}; @@ -14,6 +14,7 @@ use ahash::AHashMap; use crossbeam_utils::sync::{Parker, Unparker}; use kempt::map::Entry; use kempt::Map; +use parking_lot::{Mutex, MutexGuard}; use refuse::{CollectionGuard, ContainsNoRefs, NoMapping, Root, Trace}; use serde::{Deserialize, Serialize}; @@ -31,7 +32,7 @@ use crate::syntax::token::RegexLiteral; use crate::syntax::{BitwiseKind, CompareKind, SourceRange}; #[cfg(not(feature = "dispatched"))] use crate::value::ContextOrGuard; -use crate::value::{AnyDynamic, CustomType, Dynamic, RustType, StaticRustFunctionTable, Value}; +use crate::value::{CustomType, Dynamic, Rooted, RustType, StaticRustFunctionTable, Value}; pub mod bitcode; @@ -73,6 +74,8 @@ impl Vm { has_anonymous_frame: false, max_stack: usize::MAX, max_depth: usize::MAX, + counter: 16, + steps_per_charge: 16, budget: Budget::default(), execute_until: None, modules: vec![Module::with_core(guard)], @@ -127,21 +130,16 @@ impl Vm { self.context(guard).resume_async() } - pub fn increase_budget(&mut self, amount: usize) { - self.memory - .0 - .lock() - .expect("poisoned") - .budget - .allocate(amount); + pub fn increase_budget(&self, amount: usize) { + self.memory.0.lock().budget.allocate(amount); } - pub fn resume(&mut self, guard: &mut CollectionGuard) -> Result { + pub fn resume(&self, guard: &mut CollectionGuard) -> Result { self.context(guard).resume() } pub fn invoke( - &mut self, + &self, name: &SymbolRef, params: impl InvokeArgs, guard: &mut CollectionGuard, @@ -155,31 +153,32 @@ impl Vm { ) -> VmContext<'context, 'guard> { VmContext { guard, - vm: self.memory.0.lock().expect("poisoned"), + vm: self.memory.0.lock(), } } + pub fn set_steps_per_charge(&self, steps: u16) { + self.memory.0.lock().set_steps_per_charge(steps); + } + #[must_use] pub fn register(&self, register: Register) -> Value { - self.memory.0.lock().expect("poisoned")[register] + self.memory.0.lock()[register] } #[allow(clippy::must_use_candidate)] pub fn set_register(&self, register: Register, value: Value) -> Value { - std::mem::replace( - &mut self.memory.0.lock().expect("poisoned")[register], - value, - ) + std::mem::replace(&mut self.memory.0.lock()[register], value) } #[must_use] pub fn stack(&self, index: usize) -> Value { - self.memory.0.lock().expect("poisoned")[index] + self.memory.0.lock()[index] } #[must_use] pub fn set_stack(&self, index: usize, value: Value) -> Value { - std::mem::replace(&mut self.memory.0.lock().expect("poisoned")[index], value) + std::mem::replace(&mut self.memory.0.lock()[index], value) } pub fn declare_variable( @@ -221,7 +220,7 @@ impl Vm { #[derive(Clone)] struct RegisteredCode { code: Code, - owner: Option, + owner: Option>, } #[derive(Debug, Clone, Copy, Eq, PartialEq)] @@ -233,7 +232,7 @@ impl refuse::Trace for VmMemory { const MAY_CONTAIN_REFERENCES: bool = true; fn trace(&self, tracer: &mut refuse::Tracer) { - let state = self.0.lock().expect("poisoned"); + let state = self.0.lock(); for register in state .registers .iter() @@ -248,6 +247,10 @@ impl refuse::Trace for VmMemory { key.trace(tracer); } } + + for module in &state.modules { + tracer.mark(*module); + } } } @@ -261,6 +264,8 @@ pub struct VmState { current_frame: usize, has_anonymous_frame: bool, max_depth: usize, + counter: u16, + steps_per_charge: u16, budget: Budget, execute_until: Option, modules: Vec>, @@ -270,16 +275,23 @@ pub struct VmState { code_map: Map, } +impl VmState { + pub fn set_steps_per_charge(&mut self, steps: u16) { + self.steps_per_charge = steps; + self.counter = self.counter.min(steps); + } +} + impl<'context, 'guard> VmContext<'context, 'guard> { - fn push_code(&mut self, code: &Code, owner: Option) -> CodeIndex { - let vm = &mut *self.vm; + fn push_code(&mut self, code: &Code, owner: Option<&Rooted>) -> CodeIndex { + let vm = self.vm(); *vm.code_map .entry(Arc::as_ptr(&code.data) as usize) .or_insert_with(|| { let index = CodeIndex(vm.code.len()); vm.code.push(RegisteredCode { code: code.clone(), - owner, + owner: owner.cloned(), }); index }) @@ -314,7 +326,7 @@ impl<'context, 'guard> VmContext<'context, 'guard> { } fn prepare_owned(&mut self, code: CodeIndex) -> Result<(), ExecutionError> { - let vm = &mut *self.vm; + let vm = self.vm(); vm.frames[vm.current_frame].code = Some(code); vm.frames[vm.current_frame].instruction = 0; @@ -405,7 +417,7 @@ impl<'context, 'guard> VmContext<'context, 'guard> { .code .clone(); loop { - self.budget.charge()?; + self.budget_and_yield()?; let instruction = self.frames[code_frame].instruction; match self.step(&code, instruction) { Ok(StepResult::Complete) if code_frame >= base_frame && code_frame > 0 => { @@ -486,12 +498,12 @@ impl<'context, 'guard> VmContext<'context, 'guard> { fn execute_function( &mut self, body: &Code, - function: AnyDynamic, + function: &Rooted, module: usize, ) -> Result { let body_index = self.push_code(body, Some(function)); self.enter_frame(Some(body_index))?; - let vm = &mut *self.vm; + let vm = self.vm(); vm.frames[vm.current_frame].module = module; self.allocate(body.data.stack_requirement)?; @@ -505,12 +517,14 @@ impl<'context, 'guard> VmContext<'context, 'guard> { .expect("missing function") .0] .owner + .as_ref() + .map(Rooted::as_any_dynamic) .ok_or(Fault::NotAFunction)?; current_function.call(self, arity) } pub fn declare_variable(&mut self, name: SymbolRef, mutable: bool) -> Result { - let vm = &mut *self.vm; + let vm = self.vm(); let current_frame = &mut vm.frames[vm.current_frame]; if current_frame.end < vm.max_stack { Ok(current_frame @@ -676,7 +690,7 @@ impl<'context, 'guard> VmContext<'context, 'guard> { } fn enter_frame(&mut self, code: Option) -> Result<(), Fault> { - let vm = &mut *self.vm; + let vm = self.vm(); if vm.current_frame < vm.max_depth { let current_frame_end = vm.frames[vm.current_frame].end; let current_frame_module = vm.frames[vm.current_frame].module; @@ -708,7 +722,7 @@ impl<'context, 'guard> VmContext<'context, 'guard> { } pub fn exit_frame(&mut self) -> Result<(), Fault> { - let vm = &mut *self.vm; + let vm = self.vm(); if vm.current_frame >= 1 { vm.has_anonymous_frame = false; let current_frame = &vm.frames[vm.current_frame]; @@ -721,7 +735,7 @@ impl<'context, 'guard> VmContext<'context, 'guard> { } pub fn allocate(&mut self, count: usize) -> Result { - let vm = &mut *self.vm; + let vm = self.vm(); let current_frame = &mut vm.frames[vm.current_frame]; let index = Stack(current_frame.end - current_frame.start); match current_frame.end.checked_add(count) { @@ -749,7 +763,7 @@ impl<'context, 'guard> VmContext<'context, 'guard> { } pub fn jump_to(&mut self, instruction: usize) { - let vm = &mut *self.vm; + let vm = self.vm(); vm.frames[vm.current_frame].instruction = instruction; } @@ -760,7 +774,7 @@ impl<'context, 'guard> VmContext<'context, 'guard> { #[must_use] pub fn current_frame_mut(&mut self) -> &mut [Value] { - let vm = &mut *self.vm; + let vm = self.vm(); &mut vm.stack[vm.frames[vm.current_frame].start..vm.frames[vm.current_frame].end] } @@ -798,7 +812,7 @@ impl<'context, 'guard> VmContext<'context, 'guard> { while self.current_frame >= base_frame { if let Some(exception_target) = self.frames[self.current_frame].exception_handler { self[Register(0)] = err.as_exception(self); - let vm = &mut *self.vm; + let vm = self.vm(); vm.frames[vm.current_frame].instruction = exception_target.get(); handled = true; break; @@ -856,7 +870,7 @@ impl<'a, 'guard> VmContext<'a, 'guard> { pub fn new(vm: &'a Vm, guard: &'a mut CollectionGuard<'guard>) -> Self { Self { guard, - vm: vm.memory.0.lock().expect("poisoned"), + vm: vm.memory.0.lock(), } } @@ -869,6 +883,24 @@ impl<'a, 'guard> VmContext<'a, 'guard> { pub fn guard(&self) -> &CollectionGuard<'guard> { self.guard } + + pub fn vm(&mut self) -> &mut VmState { + &mut self.vm + } + + fn budget_and_yield(&mut self) -> Result<(), Fault> { + let next_count = self.counter - 1; + if next_count > 0 { + self.counter = next_count; + Ok(()) + } else { + self.counter = self.steps_per_charge; + self.budget.charge()?; + self.guard + .coordinated_yield(|yielder| MutexGuard::unlocked(&mut self.vm, || yielder.wait())); + self.check_timeout() + } + } } impl<'guard> AsRef> for VmContext<'_, 'guard> { @@ -887,7 +919,7 @@ impl Deref for VmContext<'_, '_> { impl DerefMut for VmContext<'_, '_> { fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.vm + self.vm() } } @@ -900,7 +932,7 @@ impl VmContext<'_, '_> { let mut code_frame = self.current_frame; let mut code = self.frames[code_frame].code.expect("missing frame code"); loop { - self.budget.charge()?; + self.budget_and_yield()?; let instruction = self.frames[code_frame].instruction; match self.step(code.0, instruction) { Ok(StepResult::Complete) if code_frame >= base_frame && code_frame > 0 => { @@ -923,7 +955,6 @@ impl VmContext<'_, '_> { } if code_frame != self.current_frame { - self.check_timeout()?; code_frame = self.current_frame; code = self.frames[self.current_frame] .code @@ -1411,7 +1442,7 @@ impl VmContext<'_, '_> { .as_usize() .and_then(NonZeroUsize::new); - let vm = &mut *self.vm; + let vm = self.vm(); let previous_handler_address = std::mem::replace(&mut vm.frames[vm.current_frame].exception_handler, handler); self.op_store( @@ -1606,7 +1637,7 @@ impl Frame { } } -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Clone, Trace)] pub enum ExecutionError { NoBudget, Waiting, @@ -1755,7 +1786,7 @@ impl CustomType for Function { .find_map(|va| (va.key() <= &arity).then_some(&va.value)) }) { let module = this.module.ok_or(Fault::MissingModule)?; - vm.execute_function(body, this.as_any_dynamic(), module) + vm.execute_function(body, &this, module) } else { Err(Fault::IncorrectNumberOfArguments) } @@ -2131,7 +2162,7 @@ impl Module { } fn declarations(&self) -> MutexGuard<'_, Map> { - self.declarations.lock().expect("poisoned") + self.declarations.lock() } } diff --git a/tests/hosted.rs b/tests/hosted.rs index 022d9f2..7c95cb5 100644 --- a/tests/hosted.rs +++ b/tests/hosted.rs @@ -9,7 +9,7 @@ use muse::syntax::token::{Paired, Token}; use muse::syntax::{Ranged, SourceCode}; use muse::value::{CustomType, RustFunction, RustType, Value}; use muse::vm::{ExecutionError, Fault, Register, Vm, VmContext}; -use refuse::{CollectionGuard, SimpleType}; +use refuse::{CollectionGuard, Trace}; fn main() { let filter = std::env::args().nth(1).unwrap_or_default(); @@ -195,7 +195,7 @@ fn run_test_cases(path: &Path, filter: &str) { } } -#[derive(Debug)] +#[derive(Debug, Trace)] struct TestError { err: ExecutionError, name: Value, @@ -210,8 +210,6 @@ impl CustomType for TestError { } } -impl SimpleType for TestError {} - // fn execute_test_cases(path: &str, filter: &str, contents: &str) -> Result<(), TestError> {} // struct TestError {