271 lines
7.7 KiB
Rust
271 lines
7.7 KiB
Rust
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)
|
|
}
|
|
}
|