feat: add experimental tailcall vm backend

This commit is contained in:
2026-04-19 22:13:54 +08:00
parent 800249cb1e
commit 98b07f00e4
16 changed files with 501 additions and 111 deletions
+19 -13
View File
@@ -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(());
}
+11 -4
View File
@@ -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");
}
}
}
+8 -21
View File
@@ -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
}
}
}
+16 -4
View File
@@ -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
}
}
}
+22 -26
View File
@@ -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,
}
}
+6 -2
View File
@@ -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
}
}
}
+6 -6
View File
@@ -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;
+10 -2
View File
@@ -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)
+3 -3
View File
@@ -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
}
}
}