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, resume_pc: 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, resume_pc: usize, ) -> Step { vm.force_slot_to_pc(base_depth, reader, mc, resume_pc) } #[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, resume_pc: usize, ) -> Step { vm.force_slot_to_pc(base_depth, reader, mc, resume_pc)?; 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, resume_pc: usize, ) -> Step { vm.force_slot_to_pc(base_depth, reader, mc, resume_pc)?; 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, resume_pc: usize, ) -> Step { vm.force_slot_to_pc(base_depth, reader, mc, resume_pc)?; 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, resume_pc: usize, ) -> Step { vm.force_slot_to_pc(base_depth, reader, mc, resume_pc)?; 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, resume_pc: usize, ) -> Step { A::force_and_check(vm, reader, mc, base + B::WIDTH, resume_pc)?; B::force_and_check(vm, reader, mc, base, resume_pc) } #[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, resume_pc: usize, ) -> Step { A::force_and_check(vm, reader, mc, base + B::WIDTH + C::WIDTH, resume_pc)?; B::force_and_check(vm, reader, mc, base + C::WIDTH, resume_pc)?; C::force_and_check(vm, reader, mc, base, resume_pc) } #[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, resume_pc: usize, ) -> Step { A::force_and_check( vm, reader, mc, base + B::WIDTH + C::WIDTH + D::WIDTH, resume_pc, )?; B::force_and_check(vm, reader, mc, base + C::WIDTH + D::WIDTH, resume_pc)?; C::force_and_check(vm, reader, mc, base + D::WIDTH, resume_pc)?; D::force_and_check(vm, reader, mc, base, resume_pc) } #[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) } }