feat: add experimental tailcall vm backend
This commit is contained in:
@@ -16,6 +16,9 @@ vim.lsp.config("rust_analyzer", {
|
|||||||
settings = {
|
settings = {
|
||||||
["rust-analyzer"] = {
|
["rust-analyzer"] = {
|
||||||
cargo = {
|
cargo = {
|
||||||
|
features = {
|
||||||
|
"tailcall"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::fmt::Write;
|
|||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use num_enum::TryFromPrimitive;
|
use num_enum::TryFromPrimitive;
|
||||||
|
|
||||||
use crate::{AttrKeyType, InstructionPtr, OperandType, Op};
|
use crate::{AttrKeyType, InstructionPtr, Op, OperandType};
|
||||||
|
|
||||||
pub trait DisassemblerContext {
|
pub trait DisassemblerContext {
|
||||||
fn resolve_string(&self, id: u32) -> &str;
|
fn resolve_string(&self, id: u32) -> &str;
|
||||||
@@ -287,12 +287,13 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
|||||||
let mut args = format!("size={}", count);
|
let mut args = format!("size={}", count);
|
||||||
for _ in 0..count {
|
for _ in 0..count {
|
||||||
let key_tag = self.read_u8();
|
let key_tag = self.read_u8();
|
||||||
let key_ty = AttrKeyType::try_from_primitive(key_tag)
|
let key_ty =
|
||||||
.expect("invalid attr key type");
|
AttrKeyType::try_from_primitive(key_tag).expect("invalid attr key type");
|
||||||
match key_ty {
|
match key_ty {
|
||||||
AttrKeyType::Static => {
|
AttrKeyType::Static => {
|
||||||
let key_id = self.read_u32();
|
let key_id = self.read_u32();
|
||||||
let _ = write!(args, " [{}={}", self.ctx.resolve_string(key_id), key_id);
|
let _ =
|
||||||
|
write!(args, " [{}={}", self.ctx.resolve_string(key_id), key_id);
|
||||||
}
|
}
|
||||||
AttrKeyType::Dynamic => {
|
AttrKeyType::Dynamic => {
|
||||||
let _ = write!(args, " [dyn");
|
let _ = write!(args, " [dyn");
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ name = "fix-vm"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
tailcall = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
gc-arena = { workspace = true }
|
gc-arena = { workspace = true }
|
||||||
hashbrown = { workspace = true }
|
hashbrown = { workspace = true }
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ pub(crate) struct BytecodeReader<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> BytecodeReader<'a> {
|
impl<'a> BytecodeReader<'a> {
|
||||||
|
#[cfg_attr(feature = "tailcall", allow(dead_code))]
|
||||||
pub(crate) fn new(bytecode: &'a [u8], pc: usize) -> Self {
|
pub(crate) fn new(bytecode: &'a [u8], pc: usize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
bytecode,
|
bytecode,
|
||||||
@@ -20,6 +21,16 @@ impl<'a> BytecodeReader<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
#[cfg_attr(not(feature = "tailcall"), allow(dead_code))]
|
||||||
|
pub(crate) fn from_after_op(bytecode: &'a [u8], inst_start_pc: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
bytecode,
|
||||||
|
pc: inst_start_pc + 1,
|
||||||
|
inst_start_pc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[cfg_attr(debug_assertions, track_caller)]
|
#[cfg_attr(debug_assertions, track_caller)]
|
||||||
fn read_array<const N: usize>(&mut self) -> [u8; N] {
|
fn read_array<const N: usize>(&mut self) -> [u8; N] {
|
||||||
@@ -31,6 +42,7 @@ impl<'a> BytecodeReader<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
#[cfg_attr(feature = "tailcall", allow(dead_code))]
|
||||||
pub(crate) fn read_op(&mut self) -> fix_codegen::Op {
|
pub(crate) fn read_op(&mut self) -> fix_codegen::Op {
|
||||||
use fix_codegen::Op;
|
use fix_codegen::Op;
|
||||||
self.inst_start_pc = self.pc;
|
self.inst_start_pc = self.pc;
|
||||||
@@ -105,16 +117,17 @@ impl<'a> BytecodeReader<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn read_attr_key_data<C: crate::VmContext>(&mut self, ctx: &C) -> crate::AttrKeyData {
|
pub(crate) fn read_attr_key_data<C: crate::VmContext>(
|
||||||
|
&mut self,
|
||||||
|
ctx: &C,
|
||||||
|
) -> crate::AttrKeyData {
|
||||||
use fix_codegen::AttrKeyType;
|
use fix_codegen::AttrKeyType;
|
||||||
let tag = self.read_u8();
|
let tag = self.read_u8();
|
||||||
let ty = AttrKeyType::try_from_primitive(tag)
|
let ty = AttrKeyType::try_from_primitive(tag)
|
||||||
.unwrap_or_else(|err| panic!("unknown key tag: {:#04x}", err.number));
|
.unwrap_or_else(|err| panic!("unknown key tag: {:#04x}", err.number));
|
||||||
match ty {
|
match ty {
|
||||||
AttrKeyType::Static => crate::AttrKeyData::Static(self.read_string_id()),
|
AttrKeyType::Static => crate::AttrKeyData::Static(self.read_string_id()),
|
||||||
AttrKeyType::Dynamic => {
|
AttrKeyType::Dynamic => crate::AttrKeyData::Dynamic(self.read_operand_data(ctx)),
|
||||||
crate::AttrKeyData::Dynamic(self.read_operand_data(ctx))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,311 @@
|
|||||||
|
#![cfg(feature = "tailcall")]
|
||||||
|
|
||||||
|
use gc_arena::Mutation;
|
||||||
|
|
||||||
|
use crate::{BytecodeReader, ForceInfo, StepResult, Vm, VmContext};
|
||||||
|
|
||||||
|
pub(crate) enum TailResult<'gc> {
|
||||||
|
YieldFuel(u32),
|
||||||
|
ForceThunk(ForceInfo<'gc>),
|
||||||
|
Done,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) type OpFn<'gc, C> = extern "rust-preserve-none" fn(
|
||||||
|
&mut Vm<'gc>,
|
||||||
|
&Mutation<'gc>,
|
||||||
|
&mut C,
|
||||||
|
&[u8],
|
||||||
|
&DispatchTable<'gc, C>,
|
||||||
|
u32,
|
||||||
|
u32,
|
||||||
|
) -> TailResult<'gc>;
|
||||||
|
|
||||||
|
pub(crate) struct DispatchTable<'gc, C: VmContext>(pub(crate) [OpFn<'gc, C>; 256]);
|
||||||
|
|
||||||
|
extern "rust-preserve-none" fn op_illegal<'gc, C: VmContext>(
|
||||||
|
_vm: &mut Vm<'gc>,
|
||||||
|
_mc: &Mutation<'gc>,
|
||||||
|
_ctx: &mut C,
|
||||||
|
_bc: &[u8],
|
||||||
|
_table: &DispatchTable<'gc, C>,
|
||||||
|
pc: u32,
|
||||||
|
_fuel: u32,
|
||||||
|
) -> TailResult<'gc> {
|
||||||
|
panic!("illegal opcode at pc = {pc}");
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! tail_dispatch_after {
|
||||||
|
($result:expr, $new_pc:expr, $vm:ident, $mc:ident, $ctx:ident, $bc:ident, $table:ident, $fuel:ident) => {{
|
||||||
|
match $result {
|
||||||
|
StepResult::Continue => {}
|
||||||
|
StepResult::ForceThunk(info) => return TailResult::ForceThunk(info),
|
||||||
|
StepResult::Done => return TailResult::Done,
|
||||||
|
}
|
||||||
|
let new_pc: u32 = $new_pc;
|
||||||
|
if $fuel == 0 {
|
||||||
|
return TailResult::YieldFuel(new_pc);
|
||||||
|
}
|
||||||
|
let next_op = $bc[new_pc as usize] as usize;
|
||||||
|
become $table.0[next_op]($vm, $mc, $ctx, $bc, $table, new_pc, $fuel - 1)
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! tail_fn {
|
||||||
|
($name:ident, ()) => {
|
||||||
|
extern "rust-preserve-none" fn $name<'gc, C: VmContext>(
|
||||||
|
vm: &mut Vm<'gc>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
ctx: &mut C,
|
||||||
|
bc: &[u8],
|
||||||
|
table: &DispatchTable<'gc, C>,
|
||||||
|
pc: u32,
|
||||||
|
fuel: u32,
|
||||||
|
) -> TailResult<'gc> {
|
||||||
|
let result = vm.$name();
|
||||||
|
tail_dispatch_after!(result, pc + 1, vm, mc, ctx, bc, table, fuel)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
($name:ident, (reader)) => {
|
||||||
|
extern "rust-preserve-none" fn $name<'gc, C: VmContext>(
|
||||||
|
vm: &mut Vm<'gc>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
ctx: &mut C,
|
||||||
|
bc: &[u8],
|
||||||
|
table: &DispatchTable<'gc, C>,
|
||||||
|
pc: u32,
|
||||||
|
fuel: u32,
|
||||||
|
) -> TailResult<'gc> {
|
||||||
|
let mut reader = BytecodeReader::from_after_op(bc, pc as usize);
|
||||||
|
let result = vm.$name(&mut reader);
|
||||||
|
tail_dispatch_after!(result, reader.pc() as u32, vm, mc, ctx, bc, table, fuel)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
($name:ident, (reader, mc)) => {
|
||||||
|
extern "rust-preserve-none" fn $name<'gc, C: VmContext>(
|
||||||
|
vm: &mut Vm<'gc>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
ctx: &mut C,
|
||||||
|
bc: &[u8],
|
||||||
|
table: &DispatchTable<'gc, C>,
|
||||||
|
pc: u32,
|
||||||
|
fuel: u32,
|
||||||
|
) -> TailResult<'gc> {
|
||||||
|
let mut reader = BytecodeReader::from_after_op(bc, pc as usize);
|
||||||
|
let result = vm.$name(&mut reader, mc);
|
||||||
|
tail_dispatch_after!(result, reader.pc() as u32, vm, mc, ctx, bc, table, fuel)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
($name:ident, (ctx, reader, mc)) => {
|
||||||
|
extern "rust-preserve-none" fn $name<'gc, C: VmContext>(
|
||||||
|
vm: &mut Vm<'gc>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
ctx: &mut C,
|
||||||
|
bc: &[u8],
|
||||||
|
table: &DispatchTable<'gc, C>,
|
||||||
|
pc: u32,
|
||||||
|
fuel: u32,
|
||||||
|
) -> TailResult<'gc> {
|
||||||
|
let mut reader = BytecodeReader::from_after_op(bc, pc as usize);
|
||||||
|
let result = vm.$name(ctx, &mut reader, mc);
|
||||||
|
tail_dispatch_after!(result, reader.pc() as u32, vm, mc, ctx, bc, table, fuel)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
($name:ident, (mc, inst_start_pc)) => {
|
||||||
|
extern "rust-preserve-none" fn $name<'gc, C: VmContext>(
|
||||||
|
vm: &mut Vm<'gc>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
ctx: &mut C,
|
||||||
|
bc: &[u8],
|
||||||
|
table: &DispatchTable<'gc, C>,
|
||||||
|
pc: u32,
|
||||||
|
fuel: u32,
|
||||||
|
) -> TailResult<'gc> {
|
||||||
|
let result = vm.$name(mc, pc as usize);
|
||||||
|
tail_dispatch_after!(result, pc + 1, vm, mc, ctx, bc, table, fuel)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
($name:ident, (ctx)) => {
|
||||||
|
extern "rust-preserve-none" fn $name<'gc, C: VmContext>(
|
||||||
|
vm: &mut Vm<'gc>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
ctx: &mut C,
|
||||||
|
bc: &[u8],
|
||||||
|
table: &DispatchTable<'gc, C>,
|
||||||
|
pc: u32,
|
||||||
|
fuel: u32,
|
||||||
|
) -> TailResult<'gc> {
|
||||||
|
let result = vm.$name(ctx);
|
||||||
|
tail_dispatch_after!(result, pc + 1, vm, mc, ctx, bc, table, fuel)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
tail_fn!(op_push_smi, (reader));
|
||||||
|
tail_fn!(op_push_bigint, (reader, mc));
|
||||||
|
tail_fn!(op_push_float, (reader));
|
||||||
|
tail_fn!(op_push_string, (reader));
|
||||||
|
tail_fn!(op_push_null, ());
|
||||||
|
tail_fn!(op_push_true, ());
|
||||||
|
tail_fn!(op_push_false, ());
|
||||||
|
|
||||||
|
tail_fn!(op_load_local, (reader));
|
||||||
|
tail_fn!(op_load_outer, (reader));
|
||||||
|
tail_fn!(op_store_local, (reader, mc));
|
||||||
|
tail_fn!(op_alloc_locals, (reader, mc));
|
||||||
|
|
||||||
|
tail_fn!(op_make_thunk, (reader, mc));
|
||||||
|
tail_fn!(op_make_closure, (reader, mc));
|
||||||
|
tail_fn!(op_make_pattern_closure, (reader, mc));
|
||||||
|
|
||||||
|
tail_fn!(op_call, (ctx, reader, mc));
|
||||||
|
tail_fn!(op_return, (ctx, reader, mc));
|
||||||
|
|
||||||
|
tail_fn!(op_make_attrs, (ctx, reader, mc));
|
||||||
|
tail_fn!(op_make_empty_attrs, ());
|
||||||
|
tail_fn!(op_select_static, (ctx, reader, mc));
|
||||||
|
tail_fn!(op_select_dynamic, (ctx, reader, mc));
|
||||||
|
tail_fn!(op_jump_if_select_succeeded, (reader));
|
||||||
|
tail_fn!(op_has_attr, (reader));
|
||||||
|
|
||||||
|
tail_fn!(op_make_list, (ctx, reader, mc));
|
||||||
|
tail_fn!(op_make_empty_list, ());
|
||||||
|
|
||||||
|
tail_fn!(op_add, (ctx, reader, mc));
|
||||||
|
tail_fn!(op_sub, (reader, mc));
|
||||||
|
tail_fn!(op_mul, (reader, mc));
|
||||||
|
tail_fn!(op_div, (reader, mc));
|
||||||
|
tail_fn!(op_eq, (ctx, reader, mc));
|
||||||
|
tail_fn!(op_neq, (ctx, reader, mc));
|
||||||
|
tail_fn!(op_lt, (ctx, reader, mc));
|
||||||
|
tail_fn!(op_gt, (ctx, reader, mc));
|
||||||
|
tail_fn!(op_leq, (ctx, reader, mc));
|
||||||
|
tail_fn!(op_geq, (ctx, reader, mc));
|
||||||
|
tail_fn!(op_concat, (reader, mc));
|
||||||
|
tail_fn!(op_update, (mc, inst_start_pc));
|
||||||
|
|
||||||
|
tail_fn!(op_neg, ());
|
||||||
|
tail_fn!(op_not, ());
|
||||||
|
|
||||||
|
tail_fn!(op_jump_if_false, (reader, mc));
|
||||||
|
tail_fn!(op_jump_if_true, (reader, mc));
|
||||||
|
tail_fn!(op_jump, (reader));
|
||||||
|
|
||||||
|
tail_fn!(op_concat_strings, (ctx, reader, mc));
|
||||||
|
tail_fn!(op_resolve_path, (ctx));
|
||||||
|
|
||||||
|
tail_fn!(op_assert, (reader));
|
||||||
|
|
||||||
|
tail_fn!(op_push_with, (ctx, reader, mc));
|
||||||
|
tail_fn!(op_pop_with, ());
|
||||||
|
tail_fn!(op_lookup_with, (ctx, reader, mc));
|
||||||
|
tail_fn!(op_prepare_with, ());
|
||||||
|
|
||||||
|
tail_fn!(op_load_builtins, ());
|
||||||
|
tail_fn!(op_load_builtin, (reader));
|
||||||
|
|
||||||
|
tail_fn!(op_mk_pos, (reader));
|
||||||
|
tail_fn!(op_load_repl_binding, (reader));
|
||||||
|
tail_fn!(op_load_scoped_binding, (reader));
|
||||||
|
|
||||||
|
macro_rules! table {
|
||||||
|
($($variant:ident => $fn:ident),* $(,)?) => {
|
||||||
|
impl<'gc, C: VmContext> DispatchTable<'gc, C> {
|
||||||
|
pub(crate) const NEW: Self = {
|
||||||
|
let mut arr: [OpFn<'gc, C>; 256] = [op_illegal; 256];
|
||||||
|
$( arr[fix_codegen::Op::$variant as usize] = $fn; )*
|
||||||
|
DispatchTable(arr)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exhaustiveness check: fails to compile if `fix_codegen::Op` gains,
|
||||||
|
// loses, or renames a variant that isn't wired up above.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
const _: fn(fix_codegen::Op) = |op| match op {
|
||||||
|
$( fix_codegen::Op::$variant => (), )*
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
PushSmi => op_push_smi,
|
||||||
|
PushBigInt => op_push_bigint,
|
||||||
|
PushFloat => op_push_float,
|
||||||
|
PushString => op_push_string,
|
||||||
|
PushNull => op_push_null,
|
||||||
|
PushTrue => op_push_true,
|
||||||
|
PushFalse => op_push_false,
|
||||||
|
|
||||||
|
LoadLocal => op_load_local,
|
||||||
|
LoadOuter => op_load_outer,
|
||||||
|
StoreLocal => op_store_local,
|
||||||
|
AllocLocals => op_alloc_locals,
|
||||||
|
|
||||||
|
MakeThunk => op_make_thunk,
|
||||||
|
MakeClosure => op_make_closure,
|
||||||
|
MakePatternClosure => op_make_pattern_closure,
|
||||||
|
|
||||||
|
Call => op_call,
|
||||||
|
Return => op_return,
|
||||||
|
|
||||||
|
MakeAttrs => op_make_attrs,
|
||||||
|
MakeEmptyAttrs => op_make_empty_attrs,
|
||||||
|
SelectStatic => op_select_static,
|
||||||
|
SelectDynamic => op_select_dynamic,
|
||||||
|
JumpIfSelectSucceeded => op_jump_if_select_succeeded,
|
||||||
|
HasAttr => op_has_attr,
|
||||||
|
|
||||||
|
MakeList => op_make_list,
|
||||||
|
MakeEmptyList => op_make_empty_list,
|
||||||
|
|
||||||
|
OpAdd => op_add,
|
||||||
|
OpSub => op_sub,
|
||||||
|
OpMul => op_mul,
|
||||||
|
OpDiv => op_div,
|
||||||
|
OpEq => op_eq,
|
||||||
|
OpNeq => op_neq,
|
||||||
|
OpLt => op_lt,
|
||||||
|
OpGt => op_gt,
|
||||||
|
OpLeq => op_leq,
|
||||||
|
OpGeq => op_geq,
|
||||||
|
OpConcat => op_concat,
|
||||||
|
OpUpdate => op_update,
|
||||||
|
|
||||||
|
OpNeg => op_neg,
|
||||||
|
OpNot => op_not,
|
||||||
|
|
||||||
|
JumpIfFalse => op_jump_if_false,
|
||||||
|
JumpIfTrue => op_jump_if_true,
|
||||||
|
Jump => op_jump,
|
||||||
|
|
||||||
|
ConcatStrings => op_concat_strings,
|
||||||
|
ResolvePath => op_resolve_path,
|
||||||
|
|
||||||
|
Assert => op_assert,
|
||||||
|
|
||||||
|
PushWith => op_push_with,
|
||||||
|
PopWith => op_pop_with,
|
||||||
|
LookupWith => op_lookup_with,
|
||||||
|
PrepareWith => op_prepare_with,
|
||||||
|
|
||||||
|
LoadBuiltins => op_load_builtins,
|
||||||
|
LoadBuiltin => op_load_builtin,
|
||||||
|
|
||||||
|
MkPos => op_mk_pos,
|
||||||
|
LoadReplBinding => op_load_repl_binding,
|
||||||
|
LoadScopedBinding => op_load_scoped_binding,
|
||||||
|
|
||||||
|
Illegal => op_illegal,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn run_tailcall<'gc, C: VmContext>(
|
||||||
|
vm: &mut Vm<'gc>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
ctx: &mut C,
|
||||||
|
bc: &[u8],
|
||||||
|
pc: u32,
|
||||||
|
) -> TailResult<'gc> {
|
||||||
|
const FUEL: u32 = 1024;
|
||||||
|
let table = &DispatchTable::<'gc, C>::NEW;
|
||||||
|
let op = bc[pc as usize] as usize;
|
||||||
|
table.0[op](vm, mc, ctx, bc, table, pc, FUEL)
|
||||||
|
}
|
||||||
@@ -2,8 +2,7 @@ use std::cmp::Ordering;
|
|||||||
|
|
||||||
use gc_arena::{Gc, Mutation};
|
use gc_arena::{Gc, Mutation};
|
||||||
|
|
||||||
use crate::{BytecodeReader, NixNum, StepResult, StrictValue, VmError, Value};
|
use crate::{BytecodeReader, NixNum, StepResult, StrictValue, Value, VmContextExt, VmError};
|
||||||
use crate::VmContextExt;
|
|
||||||
|
|
||||||
impl<'gc> crate::Vm<'gc> {
|
impl<'gc> crate::Vm<'gc> {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
@@ -21,7 +20,10 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
}
|
}
|
||||||
let rhs = self.pop_stack_forced();
|
let rhs = self.pop_stack_forced();
|
||||||
let lhs = self.pop_stack_forced();
|
let lhs = self.pop_stack_forced();
|
||||||
if let (Some(ls), Some(rs)) = (VmContextExt::get_string(ctx, lhs), VmContextExt::get_string(ctx, rhs)) {
|
if let (Some(ls), Some(rs)) = (
|
||||||
|
VmContextExt::get_string(ctx, lhs),
|
||||||
|
VmContextExt::get_string(ctx, rhs),
|
||||||
|
) {
|
||||||
let ns = Gc::new(mc, crate::NixString::new(format!("{ls}{rs}")));
|
let ns = Gc::new(mc, crate::NixString::new(format!("{ls}{rs}")));
|
||||||
self.push_stack(Value::new_gc(ns));
|
self.push_stack(Value::new_gc(ns));
|
||||||
return StepResult::Continue;
|
return StepResult::Continue;
|
||||||
@@ -300,7 +302,10 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
if lhs.is::<crate::Null>() && rhs.is::<crate::Null>() {
|
if lhs.is::<crate::Null>() && rhs.is::<crate::Null>() {
|
||||||
return Ok(true);
|
return Ok(true);
|
||||||
}
|
}
|
||||||
if let (Some(a), Some(b)) = (VmContextExt::get_string(ctx, lhs), VmContextExt::get_string(ctx, rhs)) {
|
if let (Some(a), Some(b)) = (
|
||||||
|
VmContextExt::get_string(ctx, lhs),
|
||||||
|
VmContextExt::get_string(ctx, rhs),
|
||||||
|
) {
|
||||||
return Ok(a == b);
|
return Ok(a == b);
|
||||||
}
|
}
|
||||||
if let (Some(a), Some(b)) = (lhs.as_gc::<crate::List>(), rhs.as_gc::<crate::List>()) {
|
if let (Some(a), Some(b)) = (lhs.as_gc::<crate::List>(), rhs.as_gc::<crate::List>()) {
|
||||||
@@ -358,20 +363,21 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
if let (Some(a), Some(b)) = (get_num(lhs), get_num(rhs)) {
|
if let (Some(a), Some(b)) = (get_num(lhs), get_num(rhs)) {
|
||||||
let ord = match (a, b) {
|
let ord = match (a, b) {
|
||||||
(NixNum::Int(a), NixNum::Int(b)) => a.cmp(&b),
|
(NixNum::Int(a), NixNum::Int(b)) => a.cmp(&b),
|
||||||
(NixNum::Float(a), NixNum::Float(b)) => {
|
(NixNum::Float(a), NixNum::Float(b)) => a.partial_cmp(&b).unwrap_or(Ordering::Less),
|
||||||
a.partial_cmp(&b).unwrap_or(Ordering::Less)
|
(NixNum::Int(a), NixNum::Float(b)) => {
|
||||||
|
(a as f64).partial_cmp(&b).unwrap_or(Ordering::Less)
|
||||||
|
}
|
||||||
|
(NixNum::Float(a), NixNum::Int(b)) => {
|
||||||
|
a.partial_cmp(&(b as f64)).unwrap_or(Ordering::Less)
|
||||||
}
|
}
|
||||||
(NixNum::Int(a), NixNum::Float(b)) => (a as f64)
|
|
||||||
.partial_cmp(&b)
|
|
||||||
.unwrap_or(Ordering::Less),
|
|
||||||
(NixNum::Float(a), NixNum::Int(b)) => a
|
|
||||||
.partial_cmp(&(b as f64))
|
|
||||||
.unwrap_or(Ordering::Less),
|
|
||||||
};
|
};
|
||||||
self.push_stack(Value::new_inline(pred(ord)));
|
self.push_stack(Value::new_inline(pred(ord)));
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
if let (Some(a), Some(b)) = (VmContextExt::get_string(ctx, lhs), VmContextExt::get_string(ctx, rhs)) {
|
if let (Some(a), Some(b)) = (
|
||||||
|
VmContextExt::get_string(ctx, lhs),
|
||||||
|
VmContextExt::get_string(ctx, rhs),
|
||||||
|
) {
|
||||||
self.push_stack(Value::new_inline(pred(a.cmp(b))));
|
self.push_stack(Value::new_inline(pred(a.cmp(b))));
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
use crate::{BytecodeReader, PrimOp, StepResult, Value};
|
|
||||||
use fix_builtins::BuiltinId;
|
use fix_builtins::BuiltinId;
|
||||||
use num_enum::TryFromPrimitive;
|
use num_enum::TryFromPrimitive;
|
||||||
|
|
||||||
|
use crate::{BytecodeReader, PrimOp, StepResult, Value};
|
||||||
|
|
||||||
impl<'gc> crate::Vm<'gc> {
|
impl<'gc> crate::Vm<'gc> {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn op_load_builtins(&mut self) -> StepResult<'gc> {
|
pub(crate) fn op_load_builtins(&mut self) -> StepResult<'gc> {
|
||||||
@@ -27,13 +28,19 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn op_load_repl_binding(&mut self, reader: &mut BytecodeReader<'_>) -> StepResult<'gc> {
|
pub(crate) fn op_load_repl_binding(
|
||||||
|
&mut self,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
) -> StepResult<'gc> {
|
||||||
let _name = reader.read_string_id();
|
let _name = reader.read_string_id();
|
||||||
todo!("LoadReplBinding");
|
todo!("LoadReplBinding");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn op_load_scoped_binding(&mut self, reader: &mut BytecodeReader<'_>) -> StepResult<'gc> {
|
pub(crate) fn op_load_scoped_binding(
|
||||||
|
&mut self,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
) -> StepResult<'gc> {
|
||||||
let _name = reader.read_string_id();
|
let _name = reader.read_string_id();
|
||||||
todo!("LoadScopedBinding");
|
todo!("LoadScopedBinding");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
use fix_error::Error;
|
use fix_error::Error;
|
||||||
|
|
||||||
use crate::{
|
|
||||||
BytecodeReader, CallFrame, Closure, Env, StepResult, ThunkState,
|
|
||||||
};
|
|
||||||
use crate::VmContextExt;
|
|
||||||
use gc_arena::{Gc, Mutation, RefLock};
|
use gc_arena::{Gc, Mutation, RefLock};
|
||||||
|
|
||||||
|
use crate::{BytecodeReader, CallFrame, Closure, Env, StepResult, ThunkState, VmContextExt};
|
||||||
|
|
||||||
impl<'gc> crate::Vm<'gc> {
|
impl<'gc> crate::Vm<'gc> {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn op_call(
|
pub(crate) fn op_call(
|
||||||
@@ -18,9 +15,7 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
return step;
|
return step;
|
||||||
}
|
}
|
||||||
if self.call_depth > 10000 {
|
if self.call_depth > 10000 {
|
||||||
return self.finish_err(Error::eval_error(
|
return self.finish_err(Error::eval_error("stack overflow; max-call-depth exceeded"));
|
||||||
"stack overflow; max-call-depth exceeded",
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
self.call_depth += 1;
|
self.call_depth += 1;
|
||||||
let func = self.pop_stack();
|
let func = self.pop_stack();
|
||||||
@@ -83,11 +78,7 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
match val.restrict() {
|
match val.restrict() {
|
||||||
Ok(val) => {
|
Ok(val) => {
|
||||||
*outer_thunk.borrow_mut(mc) = ThunkState::Evaluated(val);
|
*outer_thunk.borrow_mut(mc) = ThunkState::Evaluated(val);
|
||||||
if reader
|
if reader.bytecode().get(ret_pc).copied() == Some(fix_codegen::Op::Return as u8)
|
||||||
.bytecode()
|
|
||||||
.get(ret_pc)
|
|
||||||
.copied()
|
|
||||||
== Some(fix_codegen::Op::Return as u8)
|
|
||||||
{
|
{
|
||||||
self.push_stack(val.relax());
|
self.push_stack(val.relax());
|
||||||
}
|
}
|
||||||
@@ -122,10 +113,7 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
}
|
}
|
||||||
ThunkState::Evaluated(val) => {
|
ThunkState::Evaluated(val) => {
|
||||||
*outer_thunk.borrow_mut(mc) = ThunkState::Evaluated(val);
|
*outer_thunk.borrow_mut(mc) = ThunkState::Evaluated(val);
|
||||||
if reader
|
if reader.bytecode().get(ret_pc).copied()
|
||||||
.bytecode()
|
|
||||||
.get(ret_pc)
|
|
||||||
.copied()
|
|
||||||
== Some(fix_codegen::Op::Return as u8)
|
== Some(fix_codegen::Op::Return as u8)
|
||||||
{
|
{
|
||||||
self.push_stack(val.relax());
|
self.push_stack(val.relax());
|
||||||
@@ -133,9 +121,8 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
}
|
}
|
||||||
ThunkState::Apply { func: _, arg: _ } => todo!("force Apply thunk"),
|
ThunkState::Apply { func: _, arg: _ } => todo!("force Apply thunk"),
|
||||||
ThunkState::Blackhole => {
|
ThunkState::Blackhole => {
|
||||||
return self.finish_err(Error::eval_error(
|
return self
|
||||||
"infinite recursion encountered",
|
.finish_err(Error::eval_error("infinite recursion encountered"));
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,11 @@ use crate::{BytecodeReader, StepResult, ThunkState, Value};
|
|||||||
|
|
||||||
impl<'gc> crate::Vm<'gc> {
|
impl<'gc> crate::Vm<'gc> {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn op_make_thunk(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> StepResult<'gc> {
|
pub(crate) fn op_make_thunk(
|
||||||
|
&mut self,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> StepResult<'gc> {
|
||||||
let entry_point = reader.read_u32();
|
let entry_point = reader.read_u32();
|
||||||
let thunk = Gc::new(
|
let thunk = Gc::new(
|
||||||
mc,
|
mc,
|
||||||
@@ -19,7 +23,11 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn op_make_closure(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> StepResult<'gc> {
|
pub(crate) fn op_make_closure(
|
||||||
|
&mut self,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> StepResult<'gc> {
|
||||||
let entry_point = reader.read_u32();
|
let entry_point = reader.read_u32();
|
||||||
let n_locals = reader.read_u32();
|
let n_locals = reader.read_u32();
|
||||||
let closure = Gc::new(
|
let closure = Gc::new(
|
||||||
@@ -36,7 +44,11 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn op_make_pattern_closure(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> StepResult<'gc> {
|
pub(crate) fn op_make_pattern_closure(
|
||||||
|
&mut self,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> StepResult<'gc> {
|
||||||
let entry_point = reader.read_u32();
|
let entry_point = reader.read_u32();
|
||||||
let n_locals = reader.read_u32();
|
let n_locals = reader.read_u32();
|
||||||
let req_count = reader.read_u16() as usize;
|
let req_count = reader.read_u16() as usize;
|
||||||
|
|||||||
@@ -72,8 +72,7 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
Some(v) => {
|
Some(v) => {
|
||||||
self.replace_stack(0, v);
|
self.replace_stack(0, v);
|
||||||
}
|
}
|
||||||
None => {
|
None => loop {
|
||||||
loop {
|
|
||||||
let byte = reader.bytecode()[reader.pc()];
|
let byte = reader.bytecode()[reader.pc()];
|
||||||
if byte == fix_codegen::Op::SelectStatic as u8 {
|
if byte == fix_codegen::Op::SelectStatic as u8 {
|
||||||
reader.set_pc(reader.pc() + 1 + 4 + 4);
|
reader.set_pc(reader.pc() + 1 + 4 + 4);
|
||||||
@@ -85,12 +84,10 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
let name = ctx.resolve_string(key);
|
let name = ctx.resolve_string(key);
|
||||||
return self.finish_err(Error::eval_error(format!(
|
return self
|
||||||
"attribute '{name}' missing"
|
.finish_err(Error::eval_error(format!("attribute '{name}' missing")));
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
StepResult::Continue
|
StepResult::Continue
|
||||||
}
|
}
|
||||||
@@ -119,9 +116,7 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
} else if let Some(ns) = key_val.as_gc::<NixString>() {
|
} else if let Some(ns) = key_val.as_gc::<NixString>() {
|
||||||
ctx.intern_string(ns.as_str())
|
ctx.intern_string(ns.as_str())
|
||||||
} else {
|
} else {
|
||||||
return self.finish_err(Error::eval_error(
|
return self.finish_err(Error::eval_error("dynamic select key must be a string"));
|
||||||
"dynamic select key must be a string",
|
|
||||||
));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let attrset_val = self.stack[self.stack.len() - 2].restrict().expect("forced");
|
let attrset_val = self.stack[self.stack.len() - 2].restrict().expect("forced");
|
||||||
@@ -138,16 +133,17 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
let name = ctx.resolve_string(key_sid);
|
let name = ctx.resolve_string(key_sid);
|
||||||
return self.finish_err(Error::eval_error(format!(
|
return self.finish_err(Error::eval_error(format!("attribute '{name}' missing")));
|
||||||
"attribute '{name}' missing"
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
StepResult::Continue
|
StepResult::Continue
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn op_jump_if_select_succeeded(&mut self, reader: &mut BytecodeReader<'_>) -> StepResult<'gc> {
|
pub(crate) fn op_jump_if_select_succeeded(
|
||||||
|
&mut self,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
) -> StepResult<'gc> {
|
||||||
let offset = reader.read_i32();
|
let offset = reader.read_i32();
|
||||||
reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize);
|
reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize);
|
||||||
StepResult::Continue
|
StepResult::Continue
|
||||||
|
|||||||
@@ -11,7 +11,11 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn op_push_bigint(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> StepResult<'gc> {
|
pub(crate) fn op_push_bigint(
|
||||||
|
&mut self,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> StepResult<'gc> {
|
||||||
let val = reader.read_i64();
|
let val = reader.read_i64();
|
||||||
self.push_stack(Value::new_gc(Gc::new(mc, val)));
|
self.push_stack(Value::new_gc(Gc::new(mc, val)));
|
||||||
StepResult::Continue
|
StepResult::Continue
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
|
pub(crate) mod arithmetic;
|
||||||
|
pub(crate) mod builtins_misc;
|
||||||
|
pub(crate) mod calls;
|
||||||
|
pub(crate) mod closures;
|
||||||
|
pub(crate) mod collections;
|
||||||
|
pub(crate) mod control;
|
||||||
pub(crate) mod literals;
|
pub(crate) mod literals;
|
||||||
pub(crate) mod variables;
|
pub(crate) mod variables;
|
||||||
pub(crate) mod closures;
|
|
||||||
pub(crate) mod calls;
|
|
||||||
pub(crate) mod collections;
|
|
||||||
pub(crate) mod arithmetic;
|
|
||||||
pub(crate) mod control;
|
|
||||||
pub(crate) mod with_scope;
|
pub(crate) mod with_scope;
|
||||||
pub(crate) mod builtins_misc;
|
|
||||||
@@ -23,7 +23,11 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn op_store_local(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> StepResult<'gc> {
|
pub(crate) fn op_store_local(
|
||||||
|
&mut self,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> StepResult<'gc> {
|
||||||
let idx = reader.read_u32() as usize;
|
let idx = reader.read_u32() as usize;
|
||||||
let val = self.pop_stack();
|
let val = self.pop_stack();
|
||||||
self.env.borrow_mut(mc).locals[idx] = val;
|
self.env.borrow_mut(mc).locals[idx] = val;
|
||||||
@@ -31,7 +35,11 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn op_alloc_locals(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> StepResult<'gc> {
|
pub(crate) fn op_alloc_locals(
|
||||||
|
&mut self,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> StepResult<'gc> {
|
||||||
let count = reader.read_u32() as usize;
|
let count = reader.read_u32() as usize;
|
||||||
self.env
|
self.env
|
||||||
.borrow_mut(mc)
|
.borrow_mut(mc)
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
use fix_error::Error;
|
|
||||||
use fix_common::Symbol;
|
use fix_common::Symbol;
|
||||||
|
use fix_error::Error;
|
||||||
|
use gc_arena::Gc;
|
||||||
|
|
||||||
use crate::{BytecodeReader, CallFrame, StepResult, WithEnv};
|
use crate::{BytecodeReader, CallFrame, StepResult, WithEnv};
|
||||||
use gc_arena::Gc;
|
|
||||||
|
|
||||||
impl<'gc> crate::Vm<'gc> {
|
impl<'gc> crate::Vm<'gc> {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
|||||||
+60
-21
@@ -1,4 +1,9 @@
|
|||||||
#![warn(clippy::unwrap_used)]
|
#![warn(clippy::unwrap_used)]
|
||||||
|
#![cfg_attr(feature = "tailcall", expect(incomplete_features))]
|
||||||
|
#![cfg_attr(
|
||||||
|
feature = "tailcall",
|
||||||
|
feature(explicit_tail_calls, rust_preserve_none_cc)
|
||||||
|
)]
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
@@ -12,16 +17,17 @@ use hashbrown::HashMap;
|
|||||||
use num_enum::TryFromPrimitive;
|
use num_enum::TryFromPrimitive;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
mod bytecode_reader;
|
|
||||||
mod boxing;
|
mod boxing;
|
||||||
|
mod bytecode_reader;
|
||||||
|
#[cfg(feature = "tailcall")]
|
||||||
|
mod dispatch_tailcall;
|
||||||
mod value;
|
mod value;
|
||||||
use value::*;
|
|
||||||
pub use value::StaticValue;
|
pub use value::StaticValue;
|
||||||
pub(crate) mod instructions;
|
use value::*;
|
||||||
mod helpers;
|
mod helpers;
|
||||||
use helpers::*;
|
pub(crate) mod instructions;
|
||||||
|
|
||||||
pub(crate) use bytecode_reader::BytecodeReader;
|
pub(crate) use bytecode_reader::BytecodeReader;
|
||||||
|
use helpers::*;
|
||||||
|
|
||||||
type VmResult<T> = std::result::Result<T, VmError>;
|
type VmResult<T> = std::result::Result<T, VmError>;
|
||||||
|
|
||||||
@@ -339,11 +345,7 @@ impl<'gc> Vm<'gc> {
|
|||||||
if let Some(thunk) = val.as_gc::<Thunk>() {
|
if let Some(thunk) = val.as_gc::<Thunk>() {
|
||||||
let mut state = thunk.borrow_mut(mc);
|
let mut state = thunk.borrow_mut(mc);
|
||||||
match *state {
|
match *state {
|
||||||
ThunkState::Pending {
|
ThunkState::Pending { ip, env, with_env } => {
|
||||||
ip,
|
|
||||||
env,
|
|
||||||
with_env,
|
|
||||||
} => {
|
|
||||||
*state = ThunkState::Blackhole;
|
*state = ThunkState::Blackhole;
|
||||||
drop(state);
|
drop(state);
|
||||||
StepResult::ForceThunk(ForceInfo {
|
StepResult::ForceThunk(ForceInfo {
|
||||||
@@ -381,6 +383,20 @@ impl<'gc> Vm<'gc> {
|
|||||||
other => Some(other),
|
other => Some(other),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn apply_force_thunk(&mut self, info: ForceInfo<'gc>) -> usize {
|
||||||
|
self.call_stack.push(CallFrame {
|
||||||
|
thunk: Some(info.thunk),
|
||||||
|
stack_depth: info.stack_depth,
|
||||||
|
pc: info.inst_start_pc,
|
||||||
|
env: self.env,
|
||||||
|
with_env: self.with_env,
|
||||||
|
});
|
||||||
|
self.env = info.env;
|
||||||
|
self.with_env = info.with_env;
|
||||||
|
info.ip
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@@ -423,7 +439,7 @@ impl Vm<'_> {
|
|||||||
let mut pc = ip.0;
|
let mut pc = ip.0;
|
||||||
let bytecode: Vec<u8> = ctx.bytecode().to_vec();
|
let bytecode: Vec<u8> = ctx.bytecode().to_vec();
|
||||||
loop {
|
loop {
|
||||||
match arena.mutate_root(|mc, root| root.execute_batch(&bytecode, &mut ctx, pc, mc)) {
|
match arena.mutate_root(|mc, root| root.dispatch_batch(&bytecode, &mut ctx, pc, mc)) {
|
||||||
Action::Continue { pc: new_pc } => {
|
Action::Continue { pc: new_pc } => {
|
||||||
pc = new_pc;
|
pc = new_pc;
|
||||||
if arena.metrics().allocation_debt() > COLLECTOR_GRANULARITY {
|
if arena.metrics().allocation_debt() > COLLECTOR_GRANULARITY {
|
||||||
@@ -442,6 +458,37 @@ impl Vm<'_> {
|
|||||||
|
|
||||||
impl<'gc> Vm<'gc> {
|
impl<'gc> Vm<'gc> {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
fn dispatch_batch<C: VmContext>(
|
||||||
|
&mut self,
|
||||||
|
bytecode: &[u8],
|
||||||
|
ctx: &mut C,
|
||||||
|
pc: usize,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Action {
|
||||||
|
#[cfg(not(feature = "tailcall"))]
|
||||||
|
{
|
||||||
|
self.execute_batch(bytecode, ctx, pc, mc)
|
||||||
|
}
|
||||||
|
#[cfg(feature = "tailcall")]
|
||||||
|
{
|
||||||
|
use crate::dispatch_tailcall::{TailResult, run_tailcall};
|
||||||
|
match run_tailcall(self, mc, ctx, bytecode, pc as u32) {
|
||||||
|
TailResult::YieldFuel(new_pc) => Action::Continue {
|
||||||
|
pc: new_pc as usize,
|
||||||
|
},
|
||||||
|
TailResult::ForceThunk(info) => {
|
||||||
|
let new_pc = self.apply_force_thunk(info);
|
||||||
|
Action::Continue { pc: new_pc }
|
||||||
|
}
|
||||||
|
TailResult::Done => {
|
||||||
|
Action::Done(self.result.take().expect("TailResult::Done without result"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
#[cfg(not(feature = "tailcall"))]
|
||||||
fn execute_batch(
|
fn execute_batch(
|
||||||
&mut self,
|
&mut self,
|
||||||
bytecode: &[u8],
|
bytecode: &[u8],
|
||||||
@@ -537,16 +584,8 @@ impl<'gc> Vm<'gc> {
|
|||||||
match result {
|
match result {
|
||||||
StepResult::Continue => {}
|
StepResult::Continue => {}
|
||||||
StepResult::ForceThunk(info) => {
|
StepResult::ForceThunk(info) => {
|
||||||
self.call_stack.push(CallFrame {
|
let new_pc = self.apply_force_thunk(info);
|
||||||
thunk: Some(info.thunk),
|
reader.set_pc(new_pc);
|
||||||
stack_depth: info.stack_depth,
|
|
||||||
pc: info.inst_start_pc,
|
|
||||||
env: self.env,
|
|
||||||
with_env: self.with_env,
|
|
||||||
});
|
|
||||||
reader.set_pc(info.ip);
|
|
||||||
self.env = info.env;
|
|
||||||
self.with_env = info.with_env;
|
|
||||||
}
|
}
|
||||||
StepResult::Done => {
|
StepResult::Done => {
|
||||||
return Action::Done(
|
return Action::Done(
|
||||||
|
|||||||
Reference in New Issue
Block a user