diff --git a/fix/src/runtime.rs b/fix/src/runtime.rs index 66d593a..69fe602 100644 --- a/fix/src/runtime.rs +++ b/fix/src/runtime.rs @@ -12,6 +12,7 @@ use crate::downgrade::{Downgrade as _, DowngradeContext}; use crate::error::{Error, Result, Source}; use crate::ir::{ArgId, Ir, IrKey, IrRef, RawIrRef, StringId, ThunkId, ir_content_eq}; use crate::runtime::builtins::new_builtins_env; +use crate::runtime::vm::ForceMode; use crate::store::{DaemonStore, StoreConfig}; use crate::value::Symbol; @@ -56,20 +57,15 @@ impl Runtime { } pub fn eval(&mut self, source: Source) -> Result { - let root = self.downgrade(source, None)?; - let ip = crate::codegen::compile_bytecode(root.as_ref(), self); - self.run(ip) + self.do_eval(source, None, ForceMode::Normal) } - pub fn eval_shallow(&mut self, _source: Source) -> Result { - todo!() + pub fn eval_shallow(&mut self, source: Source) -> Result { + self.do_eval(source, None, ForceMode::Shallow) } pub fn eval_deep(&mut self, source: Source) -> Result { - // FIXME: deep - let root = self.downgrade(source, None)?; - let ip = crate::codegen::compile_bytecode(root.as_ref(), self); - self.run(ip) + self.do_eval(source, None, ForceMode::Deep) } pub fn eval_repl( @@ -77,10 +73,18 @@ impl Runtime { source: Source, scope: &HashSet, ) -> Result { - // FIXME: shallow - let root = self.downgrade(source, Some(Scope::Repl(scope)))?; + self.do_eval(source, Some(Scope::Repl(scope)), ForceMode::Shallow) + } + + fn do_eval<'ctx>( + &'ctx mut self, + source: Source, + extra_scope: Option>, + force_mode: ForceMode, + ) -> Result { + let root = self.downgrade(source, extra_scope)?; let ip = crate::codegen::compile_bytecode(root.as_ref(), self); - self.run(ip) + self.run(ip, force_mode) } pub fn add_binding( @@ -148,8 +152,10 @@ impl Runtime { }) } - fn run(&mut self, ip: InstructionPtr) -> Result { + fn run(&mut self, ip: InstructionPtr, force_mode: ForceMode) -> Result { let mut pc = ip.0; + self.arena + .mutate_root(|_, vm| vm.set_force_mode(force_mode)); loop { let Runtime { bytecode, @@ -157,8 +163,7 @@ impl Runtime { arena, .. } = self; - let action = - arena.mutate_root(|mc, root| root.run_batch(bytecode, &mut pc, mc, strings)); + let action = arena.mutate_root(|mc, vm| vm.run_batch(bytecode, &mut pc, mc, strings)); match action { Action::NeedGc => { if self.arena.collection_phase() == CollectionPhase::Sweeping { diff --git a/fix/src/runtime/vm.rs b/fix/src/runtime/vm.rs index 316f503..44767a4 100644 --- a/fix/src/runtime/vm.rs +++ b/fix/src/runtime/vm.rs @@ -26,6 +26,14 @@ impl From> for VmError { } } +#[derive(Collect, Clone, Copy, Debug, PartialEq, Eq)] +#[collect(require_static)] +pub(super) enum ForceMode { + Normal, + Shallow, + Deep, +} + #[derive(Collect)] #[collect(no_drop)] pub(super) struct VM<'gc> { @@ -38,6 +46,7 @@ pub(super) struct VM<'gc> { pc: usize, current_env: Option>>>, started: bool, + force_mode: ForceMode, } #[derive(Collect)] @@ -139,6 +148,16 @@ pub(super) enum AfterForce<'gc> { DiscardAndPush { value: Value<'gc>, }, + ShallowForce, + ShallowForceList { + forced: SmallVec<[Value<'gc>; 4]>, + remaining: SmallVec<[Value<'gc>; 4]>, + }, + ShallowForceAttrs { + keys: SmallVec<[StringId; 4]>, + forced: SmallVec<[Value<'gc>; 4]>, + remaining: SmallVec<[Value<'gc>; 4]>, + }, Builtin(BuiltinState<'gc>), BuiltinArgForce { #[collect(require_static)] @@ -217,6 +236,7 @@ impl<'gc> VM<'gc> { pc: 0, current_env: None, started: false, + force_mode: ForceMode::Normal, } } @@ -293,6 +313,10 @@ impl<'gc> VM<'gc> { (builtins_val, builtin_lookup) } + pub(super) fn set_force_mode(&mut self, mode: ForceMode) { + self.force_mode = mode; + } + #[inline(always)] fn read_array(&mut self, bc: &[u8]) -> [u8; N] { #[cfg(debug_assertions)] @@ -511,7 +535,13 @@ impl<'gc> VM<'gc> { ) -> Action { let Some(frame) = self.frames.pop() else { return match self.force_inline(ret_val) { - Ok(ForceResult::Ready(v)) => Action::Done(Ok(self.convert_value(&v, strings))), + Ok(ForceResult::Ready(v)) => { + if self.force_mode == ForceMode::Normal { + Action::Done(Ok(self.convert_value(v.relax(), strings))) + } else { + self.force_children(v, mc, strings) + } + } Ok(other) => { try_vm!(self, self.setup_force(other, AfterForce::TopLevelForce, mc, strings)); Action::Continue @@ -748,11 +778,35 @@ impl<'gc> VM<'gc> { Err(e) => self.handle_vm_error(e), } } - AfterForce::TopLevelForce => Action::Done(Ok(self.convert_value(&val, strings))), + AfterForce::TopLevelForce => { + if self.force_mode == ForceMode::Normal { + Action::Done(Ok(self.convert_value(val.relax(), strings))) + } else { + self.force_children(val, mc, strings) + } + } AfterForce::DiscardAndPush { value } => { self.push_stack(value); Action::Continue } + AfterForce::ShallowForce => { + self.force_children(val, mc, strings) + } + AfterForce::ShallowForceList { + mut forced, + remaining, + } => { + forced.push(val.relax()); + self.continue_force_list(forced, remaining, mc, strings) + } + AfterForce::ShallowForceAttrs { + keys, + mut forced, + remaining, + } => { + forced.push(val.relax()); + self.continue_force_attrs(keys, forced, remaining, mc, strings) + } AfterForce::Builtin(state) => { let ctx = PrimOpCtx { vm: self, @@ -812,6 +866,123 @@ impl<'gc> VM<'gc> { } } + fn force_children( + &mut self, + val: StrictValue<'gc>, + mc: &Mutation<'gc>, + strings: &DefaultStringInterner, + ) -> Action { + if let Some(list) = val.as_gc::>() { + if list.inner.is_empty() { + return Action::Done(Ok(self.convert_value(val.relax(), strings))); + } + let remaining: SmallVec<[Value<'gc>; 4]> = list.inner.iter().copied().collect(); + let forced = SmallVec::new(); + self.continue_force_list(forced, remaining, mc, strings) + } else if let Some(attrs) = val.as_gc::>() { + if attrs.is_empty() { + return Action::Done(Ok(self.convert_value(val.relax(), strings))); + } + let keys: SmallVec<[StringId; 4]> = attrs.iter().map(|(k, _)| *k).collect(); + let remaining: SmallVec<[Value<'gc>; 4]> = attrs.iter().map(|(_, v)| *v).collect(); + let forced = SmallVec::new(); + self.continue_force_attrs(keys, forced, remaining, mc, strings) + } else { + Action::Done(Ok(self.convert_value(val.relax(), strings))) + } + } + + fn continue_force_list( + &mut self, + mut forced: SmallVec<[Value<'gc>; 4]>, + mut remaining: SmallVec<[Value<'gc>; 4]>, + mc: &Mutation<'gc>, + strings: &DefaultStringInterner, + ) -> Action { + while let Some(item) = remaining.pop() { + match self.force_inline(item) { + Ok(ForceResult::Ready(v)) => { + forced.push(v.relax()); + } + Ok(other) => { + try_vm!( + self, + self.setup_force( + other, + AfterForce::ShallowForceList { + forced, + remaining, + }, + mc, + strings, + ) + ); + return Action::Continue; + } + Err(e) => return self.handle_vm_error(e), + } + } + forced.reverse(); + let list = Gc::new(mc, List { inner: forced }); + let val = Value::new_gc(list); + Action::Done(Ok(self.convert_value(val, strings))) + } + + fn continue_force_attrs( + &mut self, + keys: SmallVec<[StringId; 4]>, + mut forced: SmallVec<[Value<'gc>; 4]>, + mut remaining: SmallVec<[Value<'gc>; 4]>, + mc: &Mutation<'gc>, + strings: &DefaultStringInterner, + ) -> Action { + while !remaining.is_empty() { + let item = remaining.remove(0); + match self.force_inline(item) { + Ok(ForceResult::Ready(v)) => { + forced.push(v.relax()); + } + Ok(other) => { + try_vm!( + self, + self.setup_force( + other, + AfterForce::ShallowForceAttrs { + keys, + forced, + remaining, + }, + mc, + strings, + ) + ); + return Action::Continue; + } + Err(e) => return self.handle_vm_error(e), + } + } + let entries: SmallVec<[(StringId, Value<'gc>); 4]> = keys + .iter() + .zip(forced.iter()) + .map(|(k, v)| (*k, *v)) + .collect(); + let attrs = Gc::new(mc, unsafe { AttrSet::from_sorted_unchecked(entries) }); + let val = Value::new_gc(attrs); + Action::Done(Ok(self.convert_value(val, strings))) + } + + fn convert_top_of_stack_list( + &mut self, + strings: &DefaultStringInterner, + ) -> crate::value::Value { + let mut items = Vec::new(); + while let Some(val) = self.stack.pop() { + items.push(self.convert_value(val, strings)); + } + items.reverse(); + crate::value::Value::List(crate::value::List::new(items)) + } + fn process_builtin_result( &mut self, result: BuiltinResult<'gc>, @@ -1488,7 +1659,7 @@ impl<'gc> VM<'gc> { fn convert_value( &self, - val: &Value<'gc>, + val: Value<'gc>, strings: &DefaultStringInterner, ) -> crate::value::Value { if let Some(i) = val.as_inline::() { @@ -1508,7 +1679,7 @@ impl<'gc> VM<'gc> { crate::value::Value::String(ns.as_str().to_owned()) } else if let Some(attrs) = val.as_gc::>() { let mut map = std::collections::BTreeMap::new(); - for (key, val) in attrs.iter() { + for &(key, val) in attrs.iter() { let key_str = strings.resolve(key.0).unwrap_or("").to_owned(); let converted = self.convert_value(val, strings); map.insert(crate::value::Symbol::from(key_str), converted); @@ -1518,13 +1689,17 @@ impl<'gc> VM<'gc> { let items: Vec<_> = list .inner .iter() + .copied() .map(|v| self.convert_value(v, strings)) .collect(); crate::value::Value::List(crate::value::List::new(items)) } else if val.is::>() { crate::value::Value::Func } else if val.is::>() { - crate::value::Value::Thunk + match self.force_inline(val) { + Ok(ForceResult::Ready(forced)) => self.convert_value(forced.relax(), strings), + _ => crate::value::Value::Thunk, + } } else if val.as_inline::().is_some() { crate::value::Value::PrimOp("primop".into()) } else if val.is::>() { @@ -1758,6 +1933,7 @@ impl<'gc> VM<'gc> { if key.is::() { continue; } + // FIXME: register? let Some(key_sid) = Self::get_string_id(key) else { return Action::Done(Err(Error::eval_error( "dynamic attribute name must be a string",