diff --git a/fix-vm/src/forced.rs b/fix-vm/src/forced.rs new file mode 100644 index 0000000..81e3bf2 --- /dev/null +++ b/fix-vm/src/forced.rs @@ -0,0 +1,255 @@ +use fix_common::StringId; +use gc_arena::{Gc, Mutation}; + +use crate::value::*; +use crate::{Break, BytecodeReader, NixNum, Step, Vm}; + +pub(crate) trait Forced<'gc>: Sized { + const WIDTH: usize; + + /// Force and type-check the `WIDTH` slots starting at `base_depth` from + /// TOS, deepest-first. If a slot holds a thunk, enter it and return + /// `Break::Force`. If a slot holds a value of the wrong type, call + /// `finish_type_err` and return `Break::Done`. + fn force_and_check( + vm: &mut Vm<'gc>, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, + base_depth: usize, + ) -> Step; + + /// After `force_and_check` returned `Continue`, pop `WIDTH` slots + /// (TOS first) and convert. Type assertions are infallible because + /// `force_and_check` already validated every slot. + fn pop_converted(vm: &mut Vm<'gc>) -> Self; +} + +impl<'gc> Forced<'gc> for StrictValue<'gc> { + const WIDTH: usize = 1; + + #[inline(always)] + fn force_and_check( + vm: &mut Vm<'gc>, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, + base_depth: usize, + ) -> Step { + vm.force_slot(base_depth, reader, mc) + } + + #[inline(always)] + fn pop_converted(vm: &mut Vm<'gc>) -> Self { + vm.pop_forced() + } +} + +macro_rules! impl_forced_inline { + ($($ty:ty => $nix_ty:expr),* $(,)?) => { + $( + impl<'gc> Forced<'gc> for $ty { + const WIDTH: usize = 1; + + #[inline(always)] + fn force_and_check( + vm: &mut Vm<'gc>, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, + base_depth: usize, + ) -> Step { + vm.force_slot(base_depth, reader, mc)?; + let v = vm.peek_forced(base_depth); + if v.as_inline::<$ty>().is_none() { + let _: Step = vm.finish_type_err($nix_ty, v.ty()); + return Step::Break(Break::Done); + } + Step::Continue(()) + } + + #[inline(always)] + fn pop_converted(vm: &mut Vm<'gc>) -> Self { + vm.pop_forced() + .as_inline::<$ty>() + .expect("type checked in force_and_check") + } + } + )* + }; +} + +macro_rules! impl_forced_gc { + ($($ty:ty => $nix_ty:expr),* $(,)?) => { + $( + impl<'gc> Forced<'gc> for Gc<'gc, $ty> { + const WIDTH: usize = 1; + + #[inline(always)] + fn force_and_check( + vm: &mut Vm<'gc>, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, + base_depth: usize, + ) -> Step { + vm.force_slot(base_depth, reader, mc)?; + let v = vm.peek_forced(base_depth); + if v.as_gc::<$ty>().is_none() { + let _: Step = vm.finish_type_err($nix_ty, v.ty()); + return Step::Break(Break::Done); + } + Step::Continue(()) + } + + #[inline(always)] + fn pop_converted(vm: &mut Vm<'gc>) -> Self { + vm.pop_forced() + .as_gc::<$ty>() + .expect("type checked in force_and_check") + } + } + )* + }; +} + +impl_forced_inline! { + i32 => NixType::Int, + bool => NixType::Bool, + Null => NixType::Null, + StringId => NixType::String, + PrimOp => NixType::PrimOp, +} + +impl_forced_gc! { + i64 => NixType::Int, + NixString => NixType::String, + AttrSet<'gc> => NixType::AttrSet, + List<'gc> => NixType::List, + Closure<'gc> => NixType::Closure, + PrimOpApp<'gc> => NixType::PrimOpApp, +} + +impl<'gc> Forced<'gc> for NixNum { + const WIDTH: usize = 1; + + #[inline(always)] + fn force_and_check( + vm: &mut Vm<'gc>, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, + base_depth: usize, + ) -> Step { + vm.force_slot(base_depth, reader, mc)?; + let v = vm.peek_forced(base_depth); + if v.as_num().is_none() { + let _: Step = vm.finish_type_err(NixType::Int, v.ty()); + return Step::Break(Break::Done); + } + Step::Continue(()) + } + + #[inline(always)] + fn pop_converted(vm: &mut Vm<'gc>) -> Self { + vm.pop_forced() + .as_num() + .expect("type checked in force_and_check") + } +} + +impl<'gc> Forced<'gc> for f64 { + const WIDTH: usize = 1; + + #[inline(always)] + fn force_and_check( + vm: &mut Vm<'gc>, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, + base_depth: usize, + ) -> Step { + vm.force_slot(base_depth, reader, mc)?; + let v = vm.peek_forced(base_depth); + if v.as_float().is_none() { + let _: Step = vm.finish_type_err(NixType::Float, v.ty()); + return Step::Break(Break::Done); + } + Step::Continue(()) + } + + #[inline(always)] + fn pop_converted(vm: &mut Vm<'gc>) -> Self { + vm.pop_forced() + .as_float() + .expect("type checked in force_and_check") + } +} + +impl<'gc, A: Forced<'gc>, B: Forced<'gc>> Forced<'gc> for (A, B) { + const WIDTH: usize = A::WIDTH + B::WIDTH; + + #[inline(always)] + fn force_and_check( + vm: &mut Vm<'gc>, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, + base: usize, + ) -> Step { + A::force_and_check(vm, reader, mc, base + B::WIDTH)?; + B::force_and_check(vm, reader, mc, base) + } + + #[inline(always)] + fn pop_converted(vm: &mut Vm<'gc>) -> Self { + let b = B::pop_converted(vm); + let a = A::pop_converted(vm); + (a, b) + } +} + +impl<'gc, A: Forced<'gc>, B: Forced<'gc>, C: Forced<'gc>> Forced<'gc> for (A, B, C) { + const WIDTH: usize = A::WIDTH + B::WIDTH + C::WIDTH; + + #[inline(always)] + fn force_and_check( + vm: &mut Vm<'gc>, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, + base: usize, + ) -> Step { + A::force_and_check(vm, reader, mc, base + B::WIDTH + C::WIDTH)?; + B::force_and_check(vm, reader, mc, base + C::WIDTH)?; + C::force_and_check(vm, reader, mc, base) + } + + #[inline(always)] + fn pop_converted(vm: &mut Vm<'gc>) -> Self { + let c = C::pop_converted(vm); + let b = B::pop_converted(vm); + let a = A::pop_converted(vm); + (a, b, c) + } +} + +impl<'gc, A: Forced<'gc>, B: Forced<'gc>, C: Forced<'gc>, D: Forced<'gc>> Forced<'gc> + for (A, B, C, D) +{ + const WIDTH: usize = A::WIDTH + B::WIDTH + C::WIDTH + D::WIDTH; + + #[inline(always)] + fn force_and_check( + vm: &mut Vm<'gc>, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, + base: usize, + ) -> Step { + A::force_and_check(vm, reader, mc, base + B::WIDTH + C::WIDTH + D::WIDTH)?; + B::force_and_check(vm, reader, mc, base + C::WIDTH + D::WIDTH)?; + C::force_and_check(vm, reader, mc, base + D::WIDTH)?; + D::force_and_check(vm, reader, mc, base) + } + + #[inline(always)] + fn pop_converted(vm: &mut Vm<'gc>) -> Self { + let d = D::pop_converted(vm); + let c = C::pop_converted(vm); + let b = B::pop_converted(vm); + let a = A::pop_converted(vm); + (a, b, c, d) + } +} diff --git a/fix-vm/src/instructions/arithmetic.rs b/fix-vm/src/instructions/arithmetic.rs index a02399e..c289e46 100644 --- a/fix-vm/src/instructions/arithmetic.rs +++ b/fix-vm/src/instructions/arithmetic.rs @@ -2,8 +2,8 @@ use std::cmp::Ordering; use gc_arena::{Gc, Mutation}; -use crate::{BytecodeReader, NixNum, Step, VmContextExt, VmError}; use crate::value::*; +use crate::{BytecodeReader, NixNum, Step, VmContextExt, VmError}; impl<'gc> crate::Vm<'gc> { #[inline(always)] @@ -13,10 +13,7 @@ impl<'gc> crate::Vm<'gc> { reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>, ) -> Step { - self.try_force(1, reader, mc)?; - self.try_force(0, reader, mc)?; - let rhs = self.pop_forced(); - let lhs = self.pop_forced(); + let (lhs, rhs) = self.try_force::<(StrictValue, StrictValue)>(reader, mc)?; if let (Some(ls), Some(rs)) = ( VmContextExt::get_string(ctx, lhs), VmContextExt::get_string(ctx, rhs), @@ -61,10 +58,7 @@ impl<'gc> crate::Vm<'gc> { int_op: fn(i64, i64) -> i64, float_op: fn(f64, f64) -> f64, ) -> Step { - self.try_force(1, reader, mc)?; - self.try_force(0, reader, mc)?; - let rhs = self.pop_forced(); - let lhs = self.pop_forced(); + let (lhs, rhs) = self.try_force::<(StrictValue, StrictValue)>(reader, mc)?; let res = numeric_binop(lhs, rhs, mc, int_op, float_op); match res { Ok(val) => { @@ -81,10 +75,7 @@ impl<'gc> crate::Vm<'gc> { reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>, ) -> Step { - self.try_force(1, reader, mc)?; - self.try_force(0, reader, mc)?; - let rhs = self.pop_forced(); - let lhs = self.pop_forced(); + let (lhs, rhs) = self.try_force::<(StrictValue, StrictValue)>(reader, mc)?; match (get_num(rhs), get_num(lhs)) { (_, Some(NixNum::Int(0))) | (_, Some(NixNum::Float(0.))) => { return self.finish_vm_err(VmError::Uncatchable(fix_error::Error::eval_error( @@ -110,9 +101,8 @@ impl<'gc> crate::Vm<'gc> { reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>, ) -> Step { - self.try_force(1, reader, mc)?; - self.try_force(0, reader, mc)?; - let eq = match self.values_equal(ctx) { + let (lhs, rhs) = self.try_force::<(StrictValue, StrictValue)>(reader, mc)?; + let eq = match self.values_equal(ctx, lhs, rhs) { Ok(eq) => eq, Err(e) => return self.finish_vm_err(e), }; @@ -127,9 +117,8 @@ impl<'gc> crate::Vm<'gc> { reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>, ) -> Step { - self.try_force(1, reader, mc)?; - self.try_force(0, reader, mc)?; - let eq = match self.values_equal(ctx) { + let (lhs, rhs) = self.try_force::<(StrictValue, StrictValue)>(reader, mc)?; + let eq = match self.values_equal(ctx, lhs, rhs) { Ok(eq) => eq, Err(e) => return self.finish_vm_err(e), }; @@ -184,9 +173,8 @@ impl<'gc> crate::Vm<'gc> { mc: &Mutation<'gc>, pred: fn(Ordering) -> bool, ) -> Step { - self.try_force(1, reader, mc)?; - self.try_force(0, reader, mc)?; - match self.compare_values_inner(ctx, pred) { + let (lhs, rhs) = self.try_force::<(StrictValue, StrictValue)>(reader, mc)?; + match self.compare_values_inner(ctx, pred, lhs, rhs) { Ok(()) => Step::Continue(()), Err(e) => self.finish_vm_err(e), } @@ -198,16 +186,7 @@ impl<'gc> crate::Vm<'gc> { reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>, ) -> Step { - self.try_force(1, reader, mc)?; - self.try_force(0, reader, mc)?; - let r = match self.pop_forced_expect_gc::() { - Ok(val) => val, - Err(got) => return self.finish_type_err(NixType::List, got) - }; - let l = match self.pop_forced_expect_gc::() { - Ok(val) => val, - Err(got) => return self.finish_type_err(NixType::List, got) - }; + let (l, r) = self.try_force::<(Gc, Gc)>(reader, mc)?; let mut items = smallvec::SmallVec::new(); items.extend_from_slice(&l); items.extend_from_slice(&r); @@ -221,16 +200,7 @@ impl<'gc> crate::Vm<'gc> { reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>, ) -> Step { - self.try_force(1, reader, mc)?; - self.try_force(0, reader, mc)?; - let r = match self.pop_forced_expect_gc::() { - Ok(val) => val, - Err(got) => return self.finish_type_err(NixType::AttrSet, got) - }; - let l = match self.pop_forced_expect_gc::() { - Ok(val) => val, - Err(got) => return self.finish_type_err(NixType::AttrSet, got) - }; + let (l, r) = self.try_force::<(Gc, Gc)>(reader, mc)?; self.push(Value::new_gc(l.merge(&r, mc))); Step::Continue(()) } @@ -245,10 +215,12 @@ impl<'gc> crate::Vm<'gc> { todo!("implement unary operation"); } - pub(crate) fn values_equal(&mut self, ctx: &impl crate::VmContext) -> crate::VmResult { - let rhs = self.pop_forced(); - let lhs = self.pop_forced(); - + pub(crate) fn values_equal( + &mut self, + ctx: &impl crate::VmContext, + lhs: StrictValue<'gc>, + rhs: StrictValue<'gc>, + ) -> crate::VmResult { if let (Some(a), Some(b)) = (get_num(lhs), get_num(rhs)) { return Ok(match (a, b) { (NixNum::Int(a), NixNum::Int(b)) => a == b, @@ -273,16 +245,10 @@ impl<'gc> crate::Vm<'gc> { if a.inner.len() != b.inner.len() { return Ok(false); } - let len = a.inner.len(); - for (x, y) in a.inner.iter().zip(b.inner.iter()).rev() { - self.push(*x); - self.push(*y); - } - for i in 0..len { - let eq = self.values_equal(ctx)?; - if !eq { - let rem = len - 1 - i; - self.stack.truncate(self.stack.len() - rem * 2); + for (x, y) in a.inner.iter().zip(b.inner.iter()) { + let lx = x.restrict().expect("forced"); + let ly = y.restrict().expect("forced"); + if !self.values_equal(ctx, lx, ly)? { return Ok(false); } } @@ -292,19 +258,13 @@ impl<'gc> crate::Vm<'gc> { if a.len() != b.len() { return Ok(false); } - let len = a.len(); - for ((k1, v1), (k2, v2)) in a.iter().zip(b.iter()).rev() { + for ((k1, v1), (k2, v2)) in a.iter().zip(b.iter()) { if k1 != k2 { return Ok(false); } - self.push(*v1); - self.push(*v2); - } - for i in 0..len { - let eq = self.values_equal(ctx)?; - if !eq { - let rem = len - 1 - i; - self.stack.truncate(self.stack.len() - rem * 2); + let lv1 = v1.restrict().expect("forced"); + let lv2 = v2.restrict().expect("forced"); + if !self.values_equal(ctx, lv1, lv2)? { return Ok(false); } } @@ -317,10 +277,9 @@ impl<'gc> crate::Vm<'gc> { &mut self, ctx: &impl crate::VmContext, pred: fn(Ordering) -> bool, + lhs: StrictValue<'gc>, + rhs: StrictValue<'gc>, ) -> crate::VmResult<()> { - let rhs = self.pop_forced(); - let lhs = self.pop_forced(); - if let (Some(a), Some(b)) = (get_num(lhs), get_num(rhs)) { let ord = match (a, b) { (NixNum::Int(a), NixNum::Int(b)) => a.cmp(&b), diff --git a/fix-vm/src/instructions/calls.rs b/fix-vm/src/instructions/calls.rs index afe4432..8df2d89 100644 --- a/fix-vm/src/instructions/calls.rs +++ b/fix-vm/src/instructions/calls.rs @@ -1,6 +1,7 @@ use fix_error::Error; use gc_arena::{Gc, Mutation, RefLock}; +use crate::value::*; use crate::{BytecodeReader, CallFrame, Closure, Env, Step, ThunkState, VmContextExt}; impl<'gc> crate::Vm<'gc> { @@ -11,12 +12,11 @@ impl<'gc> crate::Vm<'gc> { reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>, ) -> Step { - self.try_force(0, reader, mc)?; + let func = self.try_force::(reader, mc)?; if self.call_depth > 10000 { return self.finish_err(Error::eval_error("stack overflow; max-call-depth exceeded")); } self.call_depth += 1; - let func = self.pop(); let arg = reader.read_operand_data(ctx).resolve(mc, self); if let Some(closure) = func.as_gc::() { let ip = closure.ip; diff --git a/fix-vm/src/instructions/collections.rs b/fix-vm/src/instructions/collections.rs index 8490a1e..92d9d1b 100644 --- a/fix-vm/src/instructions/collections.rs +++ b/fix-vm/src/instructions/collections.rs @@ -3,7 +3,7 @@ use gc_arena::Gc; use smallvec::SmallVec; use crate::{ - AttrKeyData, AttrSet, BytecodeReader, List, NixString, OperandData, Step, Value, + AttrKeyData, AttrSet, BytecodeReader, List, NixString, OperandData, Step, StrictValue, Value, }; impl<'gc> crate::Vm<'gc> { @@ -57,18 +57,11 @@ impl<'gc> crate::Vm<'gc> { let _span_id = reader.read_u32(); let key = reader.read_string_id(); - self.try_force(0, reader, mc)?; - - let attrs = self.peek(0).restrict().expect("forced"); - let Some(attrset) = attrs.as_gc::() else { - return self.finish_err(Error::eval_error( - "value is not a set while a set was expected", - )); - }; + let attrset = self.try_force::>(reader, mc)?; match attrset.lookup(key) { Some(v) => { - self.replace(0, v); + self.push(v); } None => loop { let byte = reader.bytecode()[reader.pc()]; @@ -78,7 +71,6 @@ impl<'gc> crate::Vm<'gc> { reader.set_pc(reader.pc() + 1 + 4); } else if byte == fix_codegen::Op::JumpIfSelectSucceeded as u8 { reader.set_pc(reader.pc() + 1 + 4); - let _ = self.pop(); break; } else { let name = ctx.resolve_string(key); @@ -99,12 +91,8 @@ impl<'gc> crate::Vm<'gc> { ) -> Step { let _span_id = reader.read_u32(); - self.try_force(0, reader, mc)?; - self.try_force(1, reader, mc)?; + let (attrset, key_val) = self.try_force::<(Gc, StrictValue)>(reader, mc)?; - let key_val = self.stack[self.stack.len() - 1] - .restrict() - .expect("dynamic key must be forced"); let key_sid = if let Some(sid) = key_val.as_inline::() { sid } else if let Some(ns) = key_val.as_gc::() { @@ -113,16 +101,8 @@ impl<'gc> crate::Vm<'gc> { return self.finish_err(Error::eval_error("dynamic select key must be a string")); }; - let attrset_val = self.stack[self.stack.len() - 2].restrict().expect("forced"); - let Some(attrset) = attrset_val.as_gc::() else { - return self.finish_err(Error::eval_error( - "value is not a set while a set was expected", - )); - }; - match attrset.lookup(key_sid) { Some(v) => { - self.stack.truncate(self.stack.len() - 2); self.push(v); } None => { diff --git a/fix-vm/src/instructions/control.rs b/fix-vm/src/instructions/control.rs index e1a371a..605269b 100644 --- a/fix-vm/src/instructions/control.rs +++ b/fix-vm/src/instructions/control.rs @@ -1,3 +1,4 @@ +use crate::value::*; use crate::{BytecodeReader, Step}; impl<'gc> crate::Vm<'gc> { @@ -8,8 +9,7 @@ impl<'gc> crate::Vm<'gc> { mc: &gc_arena::Mutation<'gc>, ) -> Step { let offset = reader.read_i32(); - self.try_force(0, reader, mc)?; - let cond = self.pop(); + let cond = self.try_force::(reader, mc)?; if cond.as_inline::() == Some(false) { reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize); } @@ -23,8 +23,7 @@ impl<'gc> crate::Vm<'gc> { mc: &gc_arena::Mutation<'gc>, ) -> Step { let offset = reader.read_i32(); - self.try_force(0, reader, mc)?; - let cond = self.pop(); + let cond = self.try_force::(reader, mc)?; if cond.as_inline::() == Some(true) { reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize); } diff --git a/fix-vm/src/instructions/with_scope.rs b/fix-vm/src/instructions/with_scope.rs index b8c926e..86191ea 100644 --- a/fix-vm/src/instructions/with_scope.rs +++ b/fix-vm/src/instructions/with_scope.rs @@ -66,12 +66,7 @@ impl<'gc> crate::Vm<'gc> { ))); }; self.push(env); - self.try_force(0, reader, mc)?; - - let env = match self.pop_forced_expect_gc::() { - Ok(val) => val, - Err(got) => return self.finish_type_err(NixType::List, got) - }; + let env = self.try_force::>(reader, mc)?; let Some(val) = env.lookup(name) else { reader.set_pc(reader.inst_start_pc()); self.with_env = prev; diff --git a/fix-vm/src/lib.rs b/fix-vm/src/lib.rs index 0a22dab..3c5bae7 100644 --- a/fix-vm/src/lib.rs +++ b/fix-vm/src/lib.rs @@ -21,12 +21,14 @@ mod boxing; mod bytecode_reader; #[cfg(feature = "tailcall")] mod dispatch_tailcall; +mod forced; mod value; pub use value::StaticValue; use value::*; mod helpers; pub(crate) mod instructions; pub(crate) use bytecode_reader::BytecodeReader; +pub(crate) use forced::Forced; use helpers::*; type VmResult = std::result::Result; @@ -346,27 +348,17 @@ impl<'gc> Vm<'gc> { } #[inline(always)] - pub(crate) fn pop_forced_expect_inline(&mut self) -> std::result::Result { - self.pop_forced().expect_inline::() + pub(crate) fn try_force>( + &mut self, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, + ) -> std::ops::ControlFlow { + T::force_and_check(self, reader, mc, 0)?; + std::ops::ControlFlow::Continue(T::pop_converted(self)) } #[inline(always)] - pub(crate) fn pop_forced_expect_gc(&mut self) -> std::result::Result, NixType> { - self.pop_forced().expect_gc::() - } - - #[inline(always)] - pub(crate) fn pop_forced_expect_num(&mut self) -> std::result::Result { - self.pop_forced().expect_num() - } - - #[inline(always)] - pub(crate) fn pop_forced_expect_bool(&mut self) -> std::result::Result { - self.pop_forced().expect_bool() - } - - #[inline(always)] - pub(crate) fn try_force( + pub(crate) fn force_slot( &mut self, depth: usize, reader: &mut BytecodeReader<'_>,