feat: add experimental tailcall vm backend
This commit is contained in:
@@ -2,8 +2,7 @@ use std::cmp::Ordering;
|
||||
|
||||
use gc_arena::{Gc, Mutation};
|
||||
|
||||
use crate::{BytecodeReader, NixNum, StepResult, StrictValue, VmError, Value};
|
||||
use crate::VmContextExt;
|
||||
use crate::{BytecodeReader, NixNum, StepResult, StrictValue, Value, VmContextExt, VmError};
|
||||
|
||||
impl<'gc> crate::Vm<'gc> {
|
||||
#[inline(always)]
|
||||
@@ -21,7 +20,10 @@ impl<'gc> crate::Vm<'gc> {
|
||||
}
|
||||
let rhs = 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}")));
|
||||
self.push_stack(Value::new_gc(ns));
|
||||
return StepResult::Continue;
|
||||
@@ -300,7 +302,10 @@ impl<'gc> crate::Vm<'gc> {
|
||||
if lhs.is::<crate::Null>() && rhs.is::<crate::Null>() {
|
||||
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);
|
||||
}
|
||||
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)) {
|
||||
let ord = match (a, b) {
|
||||
(NixNum::Int(a), NixNum::Int(b)) => a.cmp(&b),
|
||||
(NixNum::Float(a), NixNum::Float(b)) => {
|
||||
a.partial_cmp(&b).unwrap_or(Ordering::Less)
|
||||
(NixNum::Float(a), NixNum::Float(b)) => 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)));
|
||||
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))));
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use crate::{BytecodeReader, PrimOp, StepResult, Value};
|
||||
use fix_builtins::BuiltinId;
|
||||
use num_enum::TryFromPrimitive;
|
||||
|
||||
use crate::{BytecodeReader, PrimOp, StepResult, Value};
|
||||
|
||||
impl<'gc> crate::Vm<'gc> {
|
||||
#[inline(always)]
|
||||
pub(crate) fn op_load_builtins(&mut self) -> StepResult<'gc> {
|
||||
@@ -27,13 +28,19 @@ impl<'gc> crate::Vm<'gc> {
|
||||
}
|
||||
|
||||
#[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();
|
||||
todo!("LoadReplBinding");
|
||||
}
|
||||
|
||||
#[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();
|
||||
todo!("LoadScopedBinding");
|
||||
}
|
||||
@@ -59,4 +66,4 @@ impl<'gc> crate::Vm<'gc> {
|
||||
pub(crate) fn op_resolve_path(&mut self, _ctx: &mut impl crate::VmContext) -> StepResult<'gc> {
|
||||
todo!("implement ResolvePath");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
use fix_error::Error;
|
||||
|
||||
use crate::{
|
||||
BytecodeReader, CallFrame, Closure, Env, StepResult, ThunkState,
|
||||
};
|
||||
use crate::VmContextExt;
|
||||
use gc_arena::{Gc, Mutation, RefLock};
|
||||
|
||||
use crate::{BytecodeReader, CallFrame, Closure, Env, StepResult, ThunkState, VmContextExt};
|
||||
|
||||
impl<'gc> crate::Vm<'gc> {
|
||||
#[inline(always)]
|
||||
pub(crate) fn op_call(
|
||||
@@ -18,9 +15,7 @@ impl<'gc> crate::Vm<'gc> {
|
||||
return step;
|
||||
}
|
||||
if self.call_depth > 10000 {
|
||||
return self.finish_err(Error::eval_error(
|
||||
"stack overflow; max-call-depth exceeded",
|
||||
));
|
||||
return self.finish_err(Error::eval_error("stack overflow; max-call-depth exceeded"));
|
||||
}
|
||||
self.call_depth += 1;
|
||||
let func = self.pop_stack();
|
||||
@@ -83,11 +78,7 @@ impl<'gc> crate::Vm<'gc> {
|
||||
match val.restrict() {
|
||||
Ok(val) => {
|
||||
*outer_thunk.borrow_mut(mc) = ThunkState::Evaluated(val);
|
||||
if reader
|
||||
.bytecode()
|
||||
.get(ret_pc)
|
||||
.copied()
|
||||
== Some(fix_codegen::Op::Return as u8)
|
||||
if reader.bytecode().get(ret_pc).copied() == Some(fix_codegen::Op::Return as u8)
|
||||
{
|
||||
self.push_stack(val.relax());
|
||||
}
|
||||
@@ -122,10 +113,7 @@ impl<'gc> crate::Vm<'gc> {
|
||||
}
|
||||
ThunkState::Evaluated(val) => {
|
||||
*outer_thunk.borrow_mut(mc) = ThunkState::Evaluated(val);
|
||||
if reader
|
||||
.bytecode()
|
||||
.get(ret_pc)
|
||||
.copied()
|
||||
if reader.bytecode().get(ret_pc).copied()
|
||||
== Some(fix_codegen::Op::Return as u8)
|
||||
{
|
||||
self.push_stack(val.relax());
|
||||
@@ -133,9 +121,8 @@ impl<'gc> crate::Vm<'gc> {
|
||||
}
|
||||
ThunkState::Apply { func: _, arg: _ } => todo!("force Apply thunk"),
|
||||
ThunkState::Blackhole => {
|
||||
return self.finish_err(Error::eval_error(
|
||||
"infinite recursion encountered",
|
||||
));
|
||||
return self
|
||||
.finish_err(Error::eval_error("infinite recursion encountered"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -147,4 +134,4 @@ impl<'gc> crate::Vm<'gc> {
|
||||
self.with_env = with_env;
|
||||
StepResult::Continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,11 @@ use crate::{BytecodeReader, StepResult, ThunkState, Value};
|
||||
|
||||
impl<'gc> crate::Vm<'gc> {
|
||||
#[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 thunk = Gc::new(
|
||||
mc,
|
||||
@@ -19,7 +23,11 @@ impl<'gc> crate::Vm<'gc> {
|
||||
}
|
||||
|
||||
#[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 n_locals = reader.read_u32();
|
||||
let closure = Gc::new(
|
||||
@@ -36,7 +44,11 @@ impl<'gc> crate::Vm<'gc> {
|
||||
}
|
||||
|
||||
#[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 n_locals = reader.read_u32();
|
||||
let req_count = reader.read_u16() as usize;
|
||||
@@ -80,4 +92,4 @@ impl<'gc> crate::Vm<'gc> {
|
||||
self.push_stack(Value::new_gc(closure));
|
||||
StepResult::Continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,25 +72,22 @@ impl<'gc> crate::Vm<'gc> {
|
||||
Some(v) => {
|
||||
self.replace_stack(0, v);
|
||||
}
|
||||
None => {
|
||||
loop {
|
||||
let byte = reader.bytecode()[reader.pc()];
|
||||
if byte == fix_codegen::Op::SelectStatic as u8 {
|
||||
reader.set_pc(reader.pc() + 1 + 4 + 4);
|
||||
} else if byte == fix_codegen::Op::SelectDynamic as u8 {
|
||||
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_stack();
|
||||
break;
|
||||
} else {
|
||||
let name = ctx.resolve_string(key);
|
||||
return self.finish_err(Error::eval_error(format!(
|
||||
"attribute '{name}' missing"
|
||||
)));
|
||||
}
|
||||
None => loop {
|
||||
let byte = reader.bytecode()[reader.pc()];
|
||||
if byte == fix_codegen::Op::SelectStatic as u8 {
|
||||
reader.set_pc(reader.pc() + 1 + 4 + 4);
|
||||
} else if byte == fix_codegen::Op::SelectDynamic as u8 {
|
||||
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_stack();
|
||||
break;
|
||||
} else {
|
||||
let name = ctx.resolve_string(key);
|
||||
return self
|
||||
.finish_err(Error::eval_error(format!("attribute '{name}' missing")));
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
StepResult::Continue
|
||||
}
|
||||
@@ -119,9 +116,7 @@ impl<'gc> crate::Vm<'gc> {
|
||||
} else if let Some(ns) = key_val.as_gc::<NixString>() {
|
||||
ctx.intern_string(ns.as_str())
|
||||
} else {
|
||||
return self.finish_err(Error::eval_error(
|
||||
"dynamic select key must be a string",
|
||||
));
|
||||
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");
|
||||
@@ -138,16 +133,17 @@ impl<'gc> crate::Vm<'gc> {
|
||||
}
|
||||
None => {
|
||||
let name = ctx.resolve_string(key_sid);
|
||||
return self.finish_err(Error::eval_error(format!(
|
||||
"attribute '{name}' missing"
|
||||
)));
|
||||
return self.finish_err(Error::eval_error(format!("attribute '{name}' missing")));
|
||||
}
|
||||
}
|
||||
StepResult::Continue
|
||||
}
|
||||
|
||||
#[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();
|
||||
reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize);
|
||||
StepResult::Continue
|
||||
@@ -186,4 +182,4 @@ impl<'gc> crate::Vm<'gc> {
|
||||
pub(crate) struct AttrEntry {
|
||||
pub(crate) key: AttrKeyData,
|
||||
pub(crate) val: OperandData,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,11 @@ impl<'gc> crate::Vm<'gc> {
|
||||
}
|
||||
|
||||
#[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();
|
||||
self.push_stack(Value::new_gc(Gc::new(mc, val)));
|
||||
StepResult::Continue
|
||||
@@ -48,4 +52,4 @@ impl<'gc> crate::Vm<'gc> {
|
||||
self.push_stack(Value::new_inline(false));
|
||||
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 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 builtins_misc;
|
||||
@@ -23,7 +23,11 @@ impl<'gc> crate::Vm<'gc> {
|
||||
}
|
||||
|
||||
#[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 val = self.pop_stack();
|
||||
self.env.borrow_mut(mc).locals[idx] = val;
|
||||
@@ -31,7 +35,11 @@ impl<'gc> crate::Vm<'gc> {
|
||||
}
|
||||
|
||||
#[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;
|
||||
self.env
|
||||
.borrow_mut(mc)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use fix_error::Error;
|
||||
use fix_common::Symbol;
|
||||
use fix_error::Error;
|
||||
use gc_arena::Gc;
|
||||
|
||||
use crate::{BytecodeReader, CallFrame, StepResult, WithEnv};
|
||||
use gc_arena::Gc;
|
||||
|
||||
impl<'gc> crate::Vm<'gc> {
|
||||
#[inline(always)]
|
||||
@@ -83,4 +83,4 @@ impl<'gc> crate::Vm<'gc> {
|
||||
self.with_env = with_env;
|
||||
StepResult::Continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user