Files
nix-js/fix/src/runtime/vm.rs
T
2026-03-22 16:50:08 +08:00

2275 lines
80 KiB
Rust

use std::path::PathBuf;
use gc_arena::{Collect, Gc, Mutation, RefLock};
use hashbrown::HashMap;
use num_enum::TryFromPrimitive;
use smallvec::SmallVec;
use string_interner::{DefaultStringInterner, Symbol as _};
use super::builtins::{BUILTINS, BuiltinId, PrimOpArgs, PrimOpStrictArgs, is_lazy_builtin};
use super::primops::{BuiltinResult, BuiltinState, PrimOpCtx, dispatch_lazy_builtin, dispatch_strict_builtin};
use super::stack::Stack;
use super::value::*;
use crate::error::{Error, Result};
use crate::ir::StringId;
pub(super) type VmResult<T> = std::result::Result<T, VmError>;
pub(super) enum VmError {
Catchable(String),
Uncatchable(Box<Error>),
}
impl From<Box<Error>> for VmError {
fn from(e: Box<Error>) -> Self {
VmError::Uncatchable(e)
}
}
#[derive(Collect)]
#[collect(no_drop)]
pub(super) struct VM<'gc> {
stack: Stack<65536, Value<'gc>>,
frames: Stack<8192, CallFrame<'gc>>,
with_scope: Option<Gc<'gc, WithScope<'gc>>>,
error_contexts: Stack<8192, ErrorFrame>,
globals: GlobalState<'gc>,
import_cache: HashMap<PathBuf, Value<'gc>>,
pc: usize,
current_env: Option<Gc<'gc, RefLock<Env<'gc>>>>,
started: bool,
}
#[derive(Collect)]
#[collect(no_drop)]
struct GlobalState<'gc> {
builtins: Value<'gc>,
builtin_lookup: HashMap<StringId, PrimOp>,
empty_list: Value<'gc>,
empty_attrs: Value<'gc>,
}
#[derive(Collect)]
#[collect(require_static)]
struct ErrorFrame {
span_id: u32,
message: Option<String>,
}
#[derive(Collect, Debug)]
#[collect(no_drop)]
pub(super) struct WithScope<'gc> {
env: Value<'gc>,
prev: Option<Gc<'gc, WithScope<'gc>>>,
}
#[derive(Collect, Debug)]
#[collect(no_drop)]
struct CallFrame<'gc> {
pc: usize,
env: Gc<'gc, RefLock<Env<'gc>>>,
continuation: Continuation<'gc>,
span: Option<u32>,
}
#[derive(Collect, Debug)]
#[collect(no_drop)]
enum Continuation<'gc> {
Return,
ForceThunk {
thunk: Gc<'gc, Thunk<'gc>>,
after: AfterForce<'gc>,
},
BuiltinReturn(BuiltinState<'gc>),
BuiltinCallAndForce(BuiltinState<'gc>),
}
#[derive(Collect, Debug)]
#[collect(no_drop)]
pub(super) enum AfterForce<'gc> {
Identity,
ForceBool,
BinOpLhs {
rhs: Value<'gc>,
#[collect(require_static)]
op: BinOpTag,
},
BinOpRhs {
lhs_forced: StrictValue<'gc>,
#[collect(require_static)]
op: BinOpTag,
},
UnNeg,
UnNot,
Call {
arg: Value<'gc>,
span: Option<u32>,
},
PatternCallArgForce {
func: StrictValue<'gc>,
span: Option<u32>,
},
Select {
keys: SmallVec<[Value<'gc>; 4]>,
remaining: u16,
span: u32,
default: Option<Value<'gc>>,
},
HasAttr {
keys: SmallVec<[Value<'gc>; 4]>,
remaining: u16,
},
Assert {
expr: Value<'gc>,
raw_idx: u32,
span_id: u32,
},
ConcatStrings {
forced: SmallVec<[StrictValue<'gc>; 8]>,
remaining: SmallVec<[Value<'gc>; 8]>,
force_string: bool,
},
PushWith,
WithLookup {
#[collect(require_static)]
name: StringId,
next: Option<Gc<'gc, WithScope<'gc>>>,
},
TopLevelForce,
DiscardAndPush {
value: Value<'gc>,
},
Builtin(BuiltinState<'gc>),
BuiltinArgForce {
#[collect(require_static)]
id: BuiltinId,
#[collect(require_static)]
arity: u8,
#[collect(require_static)]
forced_count: u8,
args: PrimOpStrictArgs<'gc>,
remaining: PrimOpArgs<'gc>,
},
}
#[derive(Clone, Copy, Debug)]
pub(super) enum BinOpTag {
Add,
Sub,
Mul,
Div,
Eq,
Neq,
Lt,
Gt,
Leq,
Geq,
Concat,
Update,
}
pub(super) enum ForceResult<'gc> {
Ready(StrictValue<'gc>),
NeedEval {
ip: u32,
env: Gc<'gc, RefLock<Env<'gc>>>,
thunk: Gc<'gc, Thunk<'gc>>,
},
NeedApply(Gc<'gc, Thunk<'gc>>),
}
pub(crate) enum Action {
Continue,
Done(Result<crate::value::Value>),
NeedGc,
IoRequest(()),
}
pub(super) enum NixNum {
Int(i64),
Float(f64),
}
macro_rules! try_vm {
($self:ident, $expr:expr) => {
match $expr {
Ok(v) => v,
Err(e) => return VM::handle_vm_error($self, e),
}
};
}
impl<'gc> VM<'gc> {
pub(super) fn new(mc: &Mutation<'gc>, strings: &mut DefaultStringInterner) -> Self {
let (builtins, builtin_lookup) = Self::init_builtins(mc, strings);
Self {
stack: Stack::new(),
frames: Stack::new(),
with_scope: None,
error_contexts: Stack::new(),
globals: GlobalState {
builtins,
builtin_lookup,
empty_list: Value::new_gc(Gc::new(mc, List::default())),
empty_attrs: Value::new_gc(Gc::new(mc, AttrSet::default())),
},
import_cache: HashMap::new(),
pc: 0,
current_env: None,
started: false,
}
}
fn init_builtins(mc: &Mutation<'gc>, strings: &mut DefaultStringInterner) -> (Value<'gc>, HashMap<StringId, PrimOp>) {
let mut builtin_lookup = HashMap::new();
let mut entries = SmallVec::new();
for (id, &(name, arity)) in BUILTINS.iter().enumerate() {
let Some(sym) = strings.get(name) else {
continue;
};
let sid = StringId(sym);
let primop = PrimOp {
id: BuiltinId::try_from_primitive(id as u8).expect("invalid BuiltinId??"),
arity,
};
builtin_lookup.insert(sid, primop);
if arity == 0 {
// "null" constant
entries.push((sid, Value::new_inline(Null)));
} else {
entries.push((sid, Value::new_inline(primop)));
}
}
// Add constant entries
macro_rules! add_const {
($name:expr, $val:expr) => {{
let sym = strings.get_or_intern($name);
entries.push((StringId(sym), $val));
}};
}
add_const!(
"currentSystem",
Value::new_gc(Gc::new(mc, NixString::new("x86_64-linux")))
);
add_const!("langVersion", Value::new_inline(6i32));
add_const!(
"nixVersion",
Value::new_gc(Gc::new(mc, NixString::new("2.24.0")))
);
add_const!(
"storeDir",
Value::new_gc(Gc::new(mc, NixString::new("/nix/store")))
);
add_const!(
"nixPath",
Value::new_gc(Gc::new(
mc,
List {
inner: SmallVec::new()
}
))
);
add_const!("true", Value::new_inline(true));
add_const!("false", Value::new_inline(false));
// Self-reference thunk for builtins.builtins
let self_ref_thunk: Gc<'gc, Thunk<'gc>> = Gc::new(mc, RefLock::new(ThunkState::Blackhole));
let sym = strings.get_or_intern("builtins");
entries.push((StringId(sym), Value::new_gc(self_ref_thunk)));
entries.sort_by_key(|(k, _)| *k);
let builtins_set = Gc::new(mc, unsafe {
AttrSet::from_sorted_unchecked(entries)
});
let builtins_val = Value::new_gc(builtins_set);
// Populate the self-reference
*self_ref_thunk.borrow_mut(mc) = ThunkState::Evaluated(builtins_val);
(builtins_val, builtin_lookup)
}
#[inline(always)]
fn read_array<const N: usize>(&mut self, bc: &[u8]) -> [u8; N] {
#[cfg(debug_assertions)]
let ret = bc[self.pc..self.pc + N]
.try_into()
.expect("read_array failed");
#[cfg(not(debug_assertions))]
let ret = unsafe { bc[self.pc..self.pc + N].try_into().unwrap_unchecked() };
self.pc += N;
ret
}
#[inline(always)]
fn read_u8(&mut self, bc: &[u8]) -> u8 {
u8::from_le_bytes(self.read_array(bc))
}
#[inline(always)]
fn read_u16(&mut self, bc: &[u8]) -> u16 {
u16::from_le_bytes(self.read_array(bc))
}
#[inline(always)]
fn read_u32(&mut self, bc: &[u8]) -> u32 {
u32::from_le_bytes(self.read_array(bc))
}
#[inline(always)]
fn read_i32(&mut self, bc: &[u8]) -> i32 {
i32::from_le_bytes(self.read_array(bc))
}
#[inline(always)]
fn read_i64(&mut self, bc: &[u8]) -> i64 {
i64::from_le_bytes(self.read_array(bc))
}
#[inline(always)]
fn read_f64(&mut self, bc: &[u8]) -> f64 {
f64::from_le_bytes(self.read_array(bc))
}
#[inline(always)]
fn read_string_id(&mut self, bc: &[u8]) -> StringId {
let raw = self.read_u32(bc);
StringId(unsafe {
string_interner::symbol::SymbolU32::try_from_usize(raw as usize).unwrap_unchecked()
})
}
#[inline(always)]
fn env(&self) -> Gc<'gc, RefLock<Env<'gc>>> {
self.current_env.expect("no current env")
}
pub(super) fn force_inline(&self, val: Value<'gc>) -> VmResult<ForceResult<'gc>> {
let mut current = val;
loop {
let Some(thunk) = current.as_gc::<Thunk<'gc>>() else {
return Ok(ForceResult::Ready(unsafe {
StrictValue::try_from_forced(current).unwrap_unchecked()
}));
};
let thunk_ref = thunk.borrow();
match &*thunk_ref {
ThunkState::Evaluated(v) => {
current = *v;
drop(thunk_ref);
}
&ThunkState::Pending { ip, env } => {
return Ok(ForceResult::NeedEval { ip, env, thunk });
}
ThunkState::Apply { .. } => {
return Ok(ForceResult::NeedApply(thunk));
}
ThunkState::Blackhole => {
return Err(VmError::Uncatchable(Error::eval_error(
"infinite recursion encountered",
)));
}
}
}
}
pub(super) fn push_force_frame(
&mut self,
thunk: Gc<'gc, Thunk<'gc>>,
after: AfterForce<'gc>,
ip: u32,
env: Gc<'gc, RefLock<Env<'gc>>>,
mc: &Mutation<'gc>,
) {
self.frames
.push(CallFrame {
pc: self.pc,
env: self.env(),
continuation: Continuation::ForceThunk { thunk, after },
span: None,
})
.expect("frame stack overflow");
*thunk.borrow_mut(mc) = ThunkState::Blackhole;
self.pc = ip as usize;
self.current_env = Some(env);
}
fn push_apply_force_frame(
&mut self,
thunk: Gc<'gc, Thunk<'gc>>,
after: AfterForce<'gc>,
mc: &Mutation<'gc>,
strings: &DefaultStringInterner,
) -> VmResult<()> {
let (func, arg) = match &*thunk.borrow() {
&ThunkState::Apply { func, arg } => (func, arg),
_ => unreachable!(),
};
*thunk.borrow_mut(mc) = ThunkState::Blackhole;
self.frames
.push(CallFrame {
pc: self.pc,
env: self.env(),
continuation: Continuation::ForceThunk { thunk, after },
span: None,
})
.expect("frame stack overflow");
match self.force_inline(func)? {
ForceResult::Ready(f) => {
self.do_call(f, arg, None, mc, strings)?;
}
ForceResult::NeedEval {
ip,
env,
thunk: func_thunk,
} => {
self.push_force_frame(
func_thunk,
AfterForce::Call { arg, span: None },
ip,
env,
mc,
);
}
ForceResult::NeedApply(func_thunk) => {
self.push_apply_force_frame(
func_thunk,
AfterForce::Call { arg, span: None },
mc,
strings,
)?;
}
}
Ok(())
}
fn setup_force(
&mut self,
result: ForceResult<'gc>,
after: AfterForce<'gc>,
mc: &Mutation<'gc>,
strings: &DefaultStringInterner,
) -> VmResult<()> {
match result {
ForceResult::Ready(_) => unreachable!(),
ForceResult::NeedEval { ip, env, thunk } => {
self.push_force_frame(thunk, after, ip, env, mc);
Ok(())
}
ForceResult::NeedApply(thunk) => self.push_apply_force_frame(thunk, after, mc, strings),
}
}
pub(super) fn make_int(val: i64, mc: &Mutation<'gc>) -> Value<'gc> {
if val >= i32::MIN as i64 && val <= i32::MAX as i64 {
Value::new_inline(val as i32)
} else {
Value::new_gc(Gc::new(mc, val))
}
}
pub(super) fn as_num(val: StrictValue<'gc>) -> Option<NixNum> {
if let Some(i) = val.as_inline::<i32>() {
Some(NixNum::Int(i as i64))
} else if let Some(gc_i) = val.as_gc::<i64>() {
Some(NixNum::Int(*gc_i))
} else {
val.as_float().map(NixNum::Float)
}
}
pub(super) fn get_string_id(val: &Value<'gc>) -> Option<StringId> {
val.as_inline::<StringId>()
}
pub(super) fn get_string<'a, 'gc1: 'gc + 'a>(
val: StrictValue<'gc1>,
strings: &'a DefaultStringInterner,
) -> Option<&'a str> {
if let Some(sid) = val.as_inline::<StringId>() {
Some(strings.resolve(sid.0)?)
} else {
val.as_gc::<NixString>().map(|ns| ns.as_ref().as_str())
}
}
pub(super) fn err(msg: impl Into<String>) -> VmError {
VmError::Uncatchable(Error::eval_error(msg.into()))
}
pub(super) fn handle_return(
&mut self,
ret_val: Value<'gc>,
mc: &Mutation<'gc>,
strings: &DefaultStringInterner,
) -> Action {
let Some(frame) = self.frames.pop() else {
return match self.force_inline(ret_val) {
Ok(ForceResult::Ready(v)) => Action::Done(Ok(self.convert_value(&v, strings))),
Ok(other) => {
try_vm!(self, self.setup_force(other, AfterForce::TopLevelForce, mc, strings));
Action::Continue
}
Err(e) => self.handle_vm_error(e),
};
};
self.pc = frame.pc;
self.current_env = Some(frame.env);
match frame.continuation {
Continuation::Return => {
self.push_stack(ret_val);
Action::Continue
}
Continuation::ForceThunk { thunk, after } => {
*thunk.borrow_mut(mc) = ThunkState::Evaluated(ret_val);
match self.force_inline(ret_val) {
Ok(ForceResult::Ready(strict)) => {
self.resume_after_force(strict, after, mc, strings)
}
Ok(other) => {
try_vm!(self, self.setup_force(other, after, mc, strings));
Action::Continue
}
Err(e) => self.handle_vm_error(e),
}
}
Continuation::BuiltinReturn(state) => {
let ctx = PrimOpCtx {
vm: self,
mc,
strings,
};
match ctx.vm.force_inline(ret_val) {
Ok(ForceResult::Ready(strict)) => {
let result = state.resume(strict, &ctx);
self.process_builtin_result(result, mc, strings)
}
Ok(other) => {
try_vm!(
self,
self.setup_force(other, AfterForce::Builtin(state), mc, strings)
);
Action::Continue
}
Err(e) => self.handle_vm_error(e),
}
}
Continuation::BuiltinCallAndForce(state) => {
match self.force_inline(ret_val) {
Ok(ForceResult::Ready(strict)) => {
let ctx = PrimOpCtx {
vm: self,
mc,
strings,
};
let result = state.resume(strict, &ctx);
self.process_builtin_result(result, mc, strings)
}
Ok(other) => {
try_vm!(
self,
self.setup_force(other, AfterForce::Builtin(state), mc, strings)
);
Action::Continue
}
Err(e) => self.handle_vm_error(e),
}
}
}
}
pub(super) fn resume_after_force(
&mut self,
val: StrictValue<'gc>,
after: AfterForce<'gc>,
mc: &Mutation<'gc>,
strings: &DefaultStringInterner,
) -> Action {
match after {
AfterForce::Identity => {
self.push_stack(val.relax());
Action::Continue
}
AfterForce::ForceBool => {
if val.as_inline::<bool>().is_none() {
return Action::Done(Err(Error::eval_error("value is not a boolean")));
}
self.push_stack(val.relax());
Action::Continue
}
AfterForce::BinOpLhs { rhs, op } => match self.force_inline(rhs) {
Ok(ForceResult::Ready(rhs_forced)) => {
match self.compute_binop(op, val, rhs_forced, mc, strings) {
Ok(result) => {
self.push_stack(result);
Action::Continue
}
Err(e) => self.handle_vm_error(e),
}
}
Ok(other) => {
try_vm!(
self,
self.setup_force(
other,
AfterForce::BinOpRhs {
lhs_forced: val,
op,
},
mc,
strings,
)
);
Action::Continue
}
Err(e) => self.handle_vm_error(e),
},
AfterForce::BinOpRhs { lhs_forced, op } => {
match self.compute_binop(op, lhs_forced, val, mc, strings) {
Ok(result) => {
self.push_stack(result);
Action::Continue
}
Err(e) => self.handle_vm_error(e),
}
}
AfterForce::UnNeg => match Self::as_num(val) {
Some(NixNum::Int(i)) => {
self.push_stack(Self::make_int(-i, mc));
Action::Continue
}
Some(NixNum::Float(f)) => {
self.push_stack(Value::new_float(-f));
Action::Continue
}
None => Action::Done(Err(Error::eval_error("cannot negate non-number"))),
},
AfterForce::UnNot => match val.as_inline::<bool>() {
Some(b) => {
self.push_stack(Value::new_inline(!b));
Action::Continue
}
None => Action::Done(Err(Error::eval_error("value is not a boolean"))),
},
AfterForce::Call { arg, span } => match self.do_call(val, arg, span, mc, strings) {
Ok(()) => Action::Continue,
Err(e) => self.handle_vm_error(e),
},
AfterForce::PatternCallArgForce { func, span } => {
if let Some(closure_gc) = func.as_gc::<Closure<'gc>>() {
let pattern = closure_gc
.pattern
.as_ref()
.expect("internal: pattern call on non-pattern-closure");
match self.setup_pattern_call(
closure_gc.ip,
closure_gc.n_locals,
closure_gc.env,
pattern,
val,
span,
mc,
) {
Ok(()) => Action::Continue,
Err(e) => self.handle_vm_error(e),
}
} else {
Action::Done(Err(Error::eval_error(
"internal: pattern call on non-closure",
)))
}
}
AfterForce::Select {
keys,
remaining,
span,
default,
} => match self.do_select_step(val, keys, remaining, span, default, mc, strings) {
Ok(()) => Action::Continue,
Err(e) => self.handle_vm_error(e),
},
AfterForce::HasAttr { keys, remaining } => {
match self.do_has_attr_step(val, keys, remaining, mc, strings) {
Ok(()) => Action::Continue,
Err(e) => self.handle_vm_error(e),
}
}
AfterForce::Assert {
expr,
raw_idx,
span_id: _,
} => match val.as_inline::<bool>() {
Some(true) => {
self.push_stack(expr);
Action::Continue
}
Some(false) => {
let sym = string_interner::symbol::SymbolU32::try_from_usize(raw_idx as usize);
let msg = sym.and_then(|s| strings.resolve(s)).unwrap_or("<unknown>");
Action::Done(Err(Error::eval_error(format!("assertion '{msg}' failed"))))
}
None => Action::Done(Err(Error::eval_error(
"assertion condition must be a boolean",
))),
},
AfterForce::ConcatStrings {
mut forced,
remaining,
force_string,
} => {
forced.push(val);
match self.concat_strings_continue(forced, remaining, force_string, mc, strings) {
Ok(()) => Action::Continue,
Err(e) => self.handle_vm_error(e),
}
}
AfterForce::PushWith => {
let scope = Gc::new(
mc,
WithScope {
env: val.relax(),
prev: self.with_scope,
},
);
self.with_scope = Some(scope);
Action::Continue
}
AfterForce::WithLookup { name, next } => {
match self.do_with_lookup_step(val, name, next, mc, strings) {
Ok(()) => Action::Continue,
Err(e) => self.handle_vm_error(e),
}
}
AfterForce::TopLevelForce => Action::Done(Ok(self.convert_value(&val, strings))),
AfterForce::DiscardAndPush { value } => {
self.push_stack(value);
Action::Continue
}
AfterForce::Builtin(state) => {
let ctx = PrimOpCtx {
vm: self,
mc,
strings,
};
let result = state.resume(val, &ctx);
self.process_builtin_result(result, mc, strings)
}
AfterForce::BuiltinArgForce {
id,
arity,
mut forced_count,
mut args,
remaining,
} => {
args[forced_count as usize] = val;
forced_count += 1;
while forced_count < arity {
let next = remaining[forced_count as usize];
match self.force_inline(next) {
Ok(ForceResult::Ready(v)) => {
args[forced_count as usize] = v;
forced_count += 1;
}
Ok(other) => {
try_vm!(
self,
self.setup_force(
other,
AfterForce::BuiltinArgForce {
id,
arity,
forced_count,
args,
remaining,
},
mc,
strings,
)
);
return Action::Continue;
}
Err(e) => return self.handle_vm_error(e),
}
}
let ctx = PrimOpCtx {
vm: self,
mc,
strings,
};
let result = dispatch_strict_builtin(id, args, arity, &ctx);
self.process_builtin_result(result, mc, strings)
}
}
}
fn process_builtin_result(
&mut self,
result: BuiltinResult<'gc>,
mc: &Mutation<'gc>,
strings: &DefaultStringInterner,
) -> Action {
match result {
BuiltinResult::Done(v) => {
self.push_stack(v);
Action::Continue
}
BuiltinResult::Force(state, val) => {
match self.force_inline(val) {
Ok(ForceResult::Ready(strict)) => {
let ctx = PrimOpCtx {
vm: self,
mc,
strings,
};
let result = state.resume(strict, &ctx);
self.process_builtin_result(result, mc, strings)
}
Ok(other) => {
try_vm!(
self,
self.setup_force(other, AfterForce::Builtin(state), mc, strings)
);
Action::Continue
}
Err(e) => self.handle_vm_error(e),
}
}
BuiltinResult::Call(state, func, arg) => {
self.frames
.push(CallFrame {
pc: self.pc,
env: self.env(),
continuation: Continuation::BuiltinReturn(state),
span: None,
})
.expect("frame stack overflow");
match self.do_call(func, arg, None, mc, strings) {
Ok(()) => Action::Continue,
Err(e) => self.handle_vm_error(e),
}
}
BuiltinResult::CallAndForce(state, func, arg) => {
self.frames
.push(CallFrame {
pc: self.pc,
env: self.env(),
continuation: Continuation::BuiltinCallAndForce(state),
span: None,
})
.expect("frame stack overflow");
match self.do_call(func, arg, None, mc, strings) {
Ok(()) => Action::Continue,
Err(e) => self.handle_vm_error(e),
}
}
BuiltinResult::Error(e) => self.handle_vm_error(e),
}
}
fn dispatch_primop(
&mut self,
id: BuiltinId,
arity: u8,
args: PrimOpArgs<'gc>,
mc: &Mutation<'gc>,
strings: &DefaultStringInterner,
) -> VmResult<()> {
let ctx = PrimOpCtx {
vm: self,
mc,
strings,
};
let result = if is_lazy_builtin(id) {
dispatch_lazy_builtin(id, &args, arity, &ctx)
} else {
let mut strict_args: PrimOpStrictArgs<'gc> = [StrictValue::default(); 3];
for i in 0..arity as usize {
match self.force_inline(args[i])? {
ForceResult::Ready(v) => {
strict_args[i] = v;
}
other => {
self.setup_force(
other,
AfterForce::BuiltinArgForce {
id,
arity,
forced_count: i as u8,
args: strict_args,
remaining: args,
},
mc,
strings,
)?;
return Ok(());
}
}
}
let ctx = PrimOpCtx {
vm: self,
mc,
strings,
};
dispatch_strict_builtin(id, strict_args, arity, &ctx)
};
match result {
BuiltinResult::Done(v) => {
self.push_stack(v);
Ok(())
}
other => {
let action = self.process_builtin_result(other, mc, strings);
match action {
Action::Continue => Ok(()),
Action::Done(Err(e)) => Err(VmError::Uncatchable(e)),
_ => Ok(()),
}
}
}
}
pub(super) fn do_call(
&mut self,
func: StrictValue<'gc>,
arg: Value<'gc>,
span: Option<u32>,
mc: &Mutation<'gc>,
strings: &DefaultStringInterner,
) -> VmResult<()> {
if let Some(closure_gc) = func.as_gc::<Closure<'gc>>() {
let ip = closure_gc.ip;
let n_locals = closure_gc.n_locals;
let closure_env = closure_gc.env;
if let Some(ref pattern) = closure_gc.pattern {
match self.force_inline(arg)? {
ForceResult::Ready(forced_arg) => {
self.setup_pattern_call(
ip,
n_locals,
closure_env,
pattern,
forced_arg,
span,
mc,
)?;
}
other => {
self.setup_force(
other,
AfterForce::PatternCallArgForce { func, span },
mc,
strings,
)?;
}
}
} else {
let new_env = Gc::new(mc, RefLock::new(Env::with_arg(arg, n_locals, closure_env)));
self.frames
.push(CallFrame {
pc: self.pc,
env: self.env(),
continuation: Continuation::Return,
span,
})
.expect("frame stack overflow");
self.pc = ip as usize;
self.current_env = Some(new_env);
}
Ok(())
} else if let Some(po) = func.as_inline::<PrimOp>() {
if po.arity <= 1 {
let mut primop_args: PrimOpArgs<'gc> = [Value::default(); 3];
if po.arity == 1 {
primop_args[0] = arg;
}
self.dispatch_primop(po.id, po.arity, primop_args, mc, strings)
} else {
let app = Gc::new(
mc,
PrimOpApp {
primop: po,
args: SmallVec::from_elem(arg, 1),
},
);
self.push_stack(Value::new_gc(app));
Ok(())
}
} else if let Some(poa) = func.as_gc::<PrimOpApp<'gc>>() {
let mut args = poa.args.clone();
args.push(arg);
if args.len() >= poa.primop.arity as usize {
let mut primop_args: PrimOpArgs<'gc> = [Value::default(); 3];
for (i, a) in args.iter().enumerate() {
if i < 3 {
primop_args[i] = *a;
}
}
self.dispatch_primop(poa.primop.id, poa.primop.arity, primop_args, mc, strings)
} else {
let app = Gc::new(
mc,
PrimOpApp {
primop: poa.primop,
args,
},
);
self.push_stack(Value::new_gc(app));
Ok(())
}
} else {
Err(Self::err(
"attempt to call something which is not a function",
))
}
}
pub(super) fn setup_pattern_call(
&mut self,
ip: u32,
n_locals: u32,
closure_env: Gc<'gc, RefLock<Env<'gc>>>,
pattern: &PatternInfo,
arg: StrictValue<'gc>,
span: Option<u32>,
mc: &Mutation<'gc>,
) -> VmResult<()> {
let Some(attrs) = arg.as_gc::<AttrSet<'gc>>() else {
return Err(Self::err(
"function that expected a set received a non-set argument",
));
};
for &req in &pattern.required {
if !attrs.has(req) {
return Err(Self::err("function argument missing required attribute"));
}
}
if !pattern.ellipsis {
for (key, _) in attrs.iter() {
let is_known = pattern.required.contains(key) || pattern.optional.contains(key);
if !is_known {
return Err(Self::err("function received unexpected attribute"));
}
}
}
let new_env = Gc::new(
mc,
RefLock::new(Env::with_arg(arg.relax(), n_locals, closure_env)),
);
self.frames
.push(CallFrame {
pc: self.pc,
env: self.env(),
continuation: Continuation::Return,
span,
})
.expect("frame stack overflow");
self.pc = ip as usize;
self.current_env = Some(new_env);
Ok(())
}
pub(super) fn compute_binop(
&self,
op: BinOpTag,
lhs: StrictValue<'gc>,
rhs: StrictValue<'gc>,
mc: &Mutation<'gc>,
strings: &DefaultStringInterner,
) -> VmResult<Value<'gc>> {
match op {
BinOpTag::Add => {
if let (Some(ls), Some(rs)) = (
Self::get_string(lhs, strings),
Self::get_string(rhs, strings),
) {
let ns = Gc::new(mc, NixString::new(format!("{ls}{rs}")));
return Ok(Value::new_gc(ns));
}
self.numeric_binop(lhs, rhs, mc, i64::wrapping_add, |a, b| a + b)
}
BinOpTag::Sub => self.numeric_binop(lhs, rhs, mc, i64::wrapping_sub, |a, b| a - b),
BinOpTag::Mul => self.numeric_binop(lhs, rhs, mc, i64::wrapping_mul, |a, b| a * b),
BinOpTag::Div => match (Self::as_num(lhs), Self::as_num(rhs)) {
(_, Some(NixNum::Int(0))) => Err(Self::err("division by zero")),
(_, Some(NixNum::Float(0.))) => Err(Self::err("division by zero")),
(Some(NixNum::Int(a)), Some(NixNum::Int(b))) => {
Ok(Self::make_int(a.wrapping_div(b), mc))
}
(Some(NixNum::Float(a)), Some(NixNum::Float(b))) => Ok(Value::new_float(a / b)),
(Some(NixNum::Int(a)), Some(NixNum::Float(b))) => {
Ok(Value::new_float(a as f64 / b))
}
(Some(NixNum::Float(a)), Some(NixNum::Int(b))) => {
Ok(Value::new_float(a / b as f64))
}
_ => Err(Self::err("cannot divide non-numbers")),
},
BinOpTag::Eq => Ok(Value::new_inline(self.values_equal(lhs, rhs, strings))),
BinOpTag::Neq => Ok(Value::new_inline(!self.values_equal(lhs, rhs, strings))),
BinOpTag::Lt => self.compare_values(lhs, rhs, strings, |o| o.is_lt()),
BinOpTag::Gt => self.compare_values(lhs, rhs, strings, |o| o.is_gt()),
BinOpTag::Leq => self.compare_values(lhs, rhs, strings, |o| o.is_le()),
BinOpTag::Geq => self.compare_values(lhs, rhs, strings, |o| o.is_ge()),
BinOpTag::Concat => {
let Some(l) = lhs.as_gc::<List<'gc>>() else {
return Err(Self::err("cannot concatenate: left operand is not a list"));
};
let Some(r) = rhs.as_gc::<List<'gc>>() else {
return Err(Self::err("cannot concatenate: right operand is not a list"));
};
let mut items = SmallVec::new();
items.extend(l.inner.iter().cloned());
items.extend(r.inner.iter().cloned());
Ok(Value::new_gc(Gc::new(mc, List { inner: items })))
}
BinOpTag::Update => {
let Some(l) = lhs.as_gc::<AttrSet<'gc>>() else {
return Err(Self::err("cannot update: left operand is not a set"));
};
let Some(r) = rhs.as_gc::<AttrSet<'gc>>() else {
return Err(Self::err("cannot update: right operand is not a set"));
};
Ok(Value::new_gc(l.merge(&r, mc)))
}
}
}
pub(super) fn numeric_binop(
&self,
lhs: StrictValue<'gc>,
rhs: StrictValue<'gc>,
mc: &Mutation<'gc>,
int_op: fn(i64, i64) -> i64,
float_op: fn(f64, f64) -> f64,
) -> VmResult<Value<'gc>> {
match (Self::as_num(lhs), Self::as_num(rhs)) {
(Some(NixNum::Int(a)), Some(NixNum::Int(b))) => Ok(Self::make_int(int_op(a, b), mc)),
(Some(NixNum::Float(a)), Some(NixNum::Float(b))) => {
Ok(Value::new_float(float_op(a, b)))
}
(Some(NixNum::Int(a)), Some(NixNum::Float(b))) => {
Ok(Value::new_float(float_op(a as f64, b)))
}
(Some(NixNum::Float(a)), Some(NixNum::Int(b))) => {
Ok(Value::new_float(float_op(a, b as f64)))
}
_ => Err(Self::err("cannot perform arithmetic on non-numbers")),
}
}
pub(super) fn values_equal(
&self,
lhs: StrictValue<'gc>,
rhs: StrictValue<'gc>,
strings: &DefaultStringInterner,
) -> bool {
if let (Some(a), Some(b)) = (Self::as_num(lhs), Self::as_num(rhs)) {
return match (a, b) {
(NixNum::Int(a), NixNum::Int(b)) => a == b,
(NixNum::Float(a), NixNum::Float(b)) => a == b,
(NixNum::Int(a), NixNum::Float(b)) => a as f64 == b,
(NixNum::Float(a), NixNum::Int(b)) => a == b as f64,
};
}
if let (Some(a), Some(b)) = (lhs.as_inline::<bool>(), rhs.as_inline::<bool>()) {
return a == b;
}
if lhs.is::<Null>() && rhs.is::<Null>() {
return true;
}
if let (Some(a), Some(b)) = (
Self::get_string(lhs, strings),
Self::get_string(rhs, strings),
) {
return a == b;
}
if let (Some(a), Some(b)) = (lhs.as_gc::<List<'gc>>(), rhs.as_gc::<List<'gc>>()) {
if a.inner.len() != b.inner.len() {
return false;
}
return a.inner.iter().zip(b.inner.iter()).all(|(x, y)| {
let (Ok(ForceResult::Ready(x)), Ok(ForceResult::Ready(y))) =
(self.force_inline(*x), self.force_inline(*y))
else {
return false;
};
self.values_equal(x, y, strings)
});
}
if let (Some(a), Some(b)) = (lhs.as_gc::<AttrSet<'gc>>(), rhs.as_gc::<AttrSet<'gc>>()) {
if a.len() != b.len() {
return false;
}
return a
.iter()
.zip(b.iter())
.all(|((k1, v1), (k2, v2))| {
if k1 != k2 {
return false;
}
let (Ok(ForceResult::Ready(v1)), Ok(ForceResult::Ready(v2))) =
(self.force_inline(*v1), self.force_inline(*v2))
else {
return false;
};
self.values_equal(v1, v2, strings)
});
}
false
}
pub(super) fn compare_values(
&self,
lhs: StrictValue<'gc>,
rhs: StrictValue<'gc>,
strings: &DefaultStringInterner,
pred: impl FnOnce(std::cmp::Ordering) -> bool,
) -> VmResult<Value<'gc>> {
if let (Some(a), Some(b)) = (Self::as_num(lhs), Self::as_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(std::cmp::Ordering::Less)
}
(NixNum::Int(a), NixNum::Float(b)) => (a as f64)
.partial_cmp(&b)
.unwrap_or(std::cmp::Ordering::Less),
(NixNum::Float(a), NixNum::Int(b)) => a
.partial_cmp(&(b as f64))
.unwrap_or(std::cmp::Ordering::Less),
};
return Ok(Value::new_inline(pred(ord)));
}
if let (Some(a), Some(b)) = (
Self::get_string(lhs, strings),
Self::get_string(rhs, strings),
) {
return Ok(Value::new_inline(pred(a.cmp(b))));
}
Err(Self::err("cannot compare these types"))
}
pub(super) fn do_select_step(
&mut self,
set_val: StrictValue<'gc>,
keys: SmallVec<[Value<'gc>; 4]>,
remaining: u16,
span: u32,
default: Option<Value<'gc>>,
mc: &Mutation<'gc>,
strings: &DefaultStringInterner,
) -> VmResult<()> {
let Some(attrs) = set_val.as_gc::<AttrSet<'gc>>() else {
if let Some(def) = default {
self.push_stack(def);
return Ok(());
}
return Err(Self::err("cannot select from non-set"));
};
let key_idx = keys.len() - remaining as usize;
let key = &keys[key_idx];
let Some(key_sid) = Self::get_string_id(key) else {
return Err(Self::err("attribute name must be a string"));
};
let found = attrs.lookup(key_sid);
if remaining <= 1 {
match (found, default) {
(Some(v), _) => {
self.push_stack(v);
Ok(())
}
(None, Some(default)) => {
self.push_stack(default);
Ok(())
}
(None, None) => {
let name = strings.resolve(key_sid.0).unwrap_or("<unknown>");
Err(Self::err(format!("attribute '{name}' missing")))
}
}
} else {
match (found, default) {
(Some(v), default) => match self.force_inline(v)? {
ForceResult::Ready(forced) => {
self.do_select_step(forced, keys, remaining - 1, span, default, mc, strings)
}
other => {
self.setup_force(
other,
AfterForce::Select {
keys,
remaining: remaining - 1,
span,
default,
},
mc,
strings,
)?;
Ok(())
}
},
(None, Some(default)) => {
self.push_stack(default);
Ok(())
}
(None, None) => {
let name = strings.resolve(key_sid.0).unwrap_or("<unknown>");
Err(Self::err(format!("attribute '{name}' missing")))
}
}
}
}
pub(super) fn do_has_attr_step(
&mut self,
set_val: StrictValue<'gc>,
keys: SmallVec<[Value<'gc>; 4]>,
remaining: u16,
mc: &Mutation<'gc>,
strings: &DefaultStringInterner,
) -> VmResult<()> {
let Some(attrs) = set_val.as_gc::<AttrSet<'gc>>() else {
self.push_stack(Value::new_inline(false));
return Ok(());
};
let key_idx = keys.len() - remaining as usize;
let Some(key_sid) = Self::get_string_id(&keys[key_idx]) else {
self.push_stack(Value::new_inline(false));
return Ok(());
};
if remaining <= 1 {
self.push_stack(Value::new_inline(attrs.has(key_sid)));
Ok(())
} else {
match attrs.lookup(key_sid) {
Some(v) => match self.force_inline(v)? {
ForceResult::Ready(forced) => {
self.do_has_attr_step(forced, keys, remaining - 1, mc, strings)
}
other => {
self.setup_force(
other,
AfterForce::HasAttr {
keys,
remaining: remaining - 1,
},
mc,
strings,
)?;
Ok(())
}
},
None => {
self.push_stack(Value::new_inline(false));
Ok(())
}
}
}
}
fn concat_strings_continue(
&mut self,
mut forced: SmallVec<[StrictValue<'gc>; 8]>,
mut remaining: SmallVec<[Value<'gc>; 8]>,
force_string: bool,
mc: &Mutation<'gc>,
strings: &DefaultStringInterner,
) -> VmResult<()> {
while let Some(part) = remaining.pop() {
match self.force_inline(part)? {
ForceResult::Ready(v) => forced.push(v),
other => {
self.setup_force(
other,
AfterForce::ConcatStrings {
forced,
remaining,
force_string,
},
mc,
strings,
)?;
return Ok(());
}
}
}
let mut result = String::new();
for part in &forced {
if let Some(s) = Self::get_string(*part, strings) {
result.push_str(s);
} else if let Some(n) = Self::as_num(*part) {
match n {
NixNum::Int(i) => result.push_str(&i.to_string()),
NixNum::Float(f) => result.push_str(&format!("{f}")),
}
} else if part.is::<Null>() {
} else if let Some(b) = part.as_inline::<bool>() {
if force_string {
result.push_str(if b { "1" } else { "" });
} else {
return Err(Self::err("cannot coerce a boolean to a string"));
}
} else {
return Err(Self::err("cannot coerce value to string"));
}
}
let ns = Gc::new(mc, NixString::new(result));
self.push_stack(Value::new_gc(ns));
Ok(())
}
fn do_with_lookup_step(
&mut self,
scope_val: StrictValue<'gc>,
name: StringId,
next: Option<Gc<'gc, WithScope<'gc>>>,
mc: &Mutation<'gc>,
strings: &DefaultStringInterner,
) -> VmResult<()> {
if let Some(attrs) = scope_val.as_gc::<AttrSet<'gc>>()
&& let Some(v) = attrs.lookup(name)
{
self.push_stack(v);
return Ok(());
}
match next {
Some(scope) => {
let env_val = scope.env;
let next_prev = scope.prev;
match self.force_inline(env_val)? {
ForceResult::Ready(forced) => {
self.do_with_lookup_step(forced, name, next_prev, mc, strings)
}
other => {
self.setup_force(
other,
AfterForce::WithLookup {
name,
next: next_prev,
},
mc,
strings,
)?;
Ok(())
}
}
}
None => {
let name_str = strings.resolve(name.0).unwrap_or("<unknown>");
Err(Self::err(format!("undefined variable '{name_str}'")))
}
}
}
fn convert_value(
&self,
val: &Value<'gc>,
strings: &DefaultStringInterner,
) -> crate::value::Value {
if let Some(i) = val.as_inline::<i32>() {
crate::value::Value::Int(i as i64)
} else if let Some(gc_i) = val.as_gc::<i64>() {
crate::value::Value::Int(*gc_i)
} else if let Some(f) = val.as_float() {
crate::value::Value::Float(f)
} else if let Some(b) = val.as_inline::<bool>() {
crate::value::Value::Bool(b)
} else if val.is::<Null>() {
crate::value::Value::Null
} else if let Some(sid) = val.as_inline::<StringId>() {
let s = strings.resolve(sid.0).unwrap_or("").to_owned();
crate::value::Value::String(s)
} else if let Some(ns) = val.as_gc::<NixString>() {
crate::value::Value::String(ns.as_str().to_owned())
} else if let Some(attrs) = val.as_gc::<AttrSet<'gc>>() {
let mut map = std::collections::BTreeMap::new();
for (key, val) in attrs.iter() {
let key_str = strings.resolve(key.0).unwrap_or("").to_owned();
let converted = self.convert_value(val, strings);
map.insert(crate::value::Symbol::from(key_str), converted);
}
crate::value::Value::AttrSet(crate::value::AttrSet::new(map))
} else if let Some(list) = val.as_gc::<List<'gc>>() {
let items: Vec<_> = list
.inner
.iter()
.map(|v| self.convert_value(v, strings))
.collect();
crate::value::Value::List(crate::value::List::new(items))
} else if val.is::<Closure<'gc>>() {
crate::value::Value::Func
} else if val.is::<Thunk<'gc>>() {
crate::value::Value::Thunk
} else if val.as_inline::<PrimOp>().is_some() {
crate::value::Value::PrimOp("primop".into())
} else if val.is::<PrimOpApp<'gc>>() {
crate::value::Value::PrimOpApp("primop-app".into())
} else {
crate::value::Value::Null
}
}
fn execute_one(
&mut self,
bc: &[u8],
mc: &Mutation<'gc>,
strings: &DefaultStringInterner,
) -> Action {
use crate::codegen::Op::{self, *};
#[cfg(debug_assertions)]
let opcode_byte = bc[self.pc];
#[cfg(not(debug_assertions))]
let opcode_byte = unsafe { *bc.get_unchecked(self.pc) };
self.pc += 1;
#[cfg(debug_assertions)]
let Ok(op) = Op::try_from_primitive(opcode_byte) else {
return Action::Done(Err(Error::eval_error(format!(
"unknown opcode: {opcode_byte:#04x}"
))));
};
#[cfg(not(debug_assertions))]
let op = unsafe { Op::try_from_primitive(opcode_byte).unwrap_unchecked() };
match op {
PushSmi => {
let val = self.read_i32(bc);
self.push_stack(Value::new_inline(val));
}
PushBigInt => {
let val = self.read_i64(bc);
self.push_stack(Value::new_gc(Gc::new(mc, val)));
}
PushFloat => {
let val = self.read_f64(bc);
self.push_stack(Value::new_float(val));
}
PushString => {
let sid = self.read_string_id(bc);
self.push_stack(Value::new_inline(sid));
}
PushNull => self.push_stack(Value::new_inline(Null)),
PushTrue => self.push_stack(Value::new_inline(true)),
PushFalse => self.push_stack(Value::new_inline(false)),
LoadLocal => {
let idx = self.read_u32(bc) as usize;
let val = self.env().borrow().locals[idx];
self.push_stack(val);
}
LoadOuter => {
let layer = self.read_u8(bc);
let idx = self.read_u32(bc) as usize;
let mut env = self.env();
for _ in 0..layer {
let prev = env.borrow().prev.expect("LoadOuter: env chain too short");
env = prev;
}
let val = env.borrow().locals[idx];
self.push_stack(val);
}
StoreLocal => {
let idx = self.read_u32(bc) as usize;
let val = self.stack.pop().expect("stack underflow");
self.env().borrow_mut(mc).locals[idx] = val;
}
AllocLocals => {
let count = self.read_u32(bc) as usize;
self.env()
.borrow_mut(mc)
.locals
.resize(count, Value::default());
}
MakeThunk => {
let entry_point = self.read_u32(bc);
let _label = self.read_string_id(bc);
let thunk = Gc::new(
mc,
RefLock::new(ThunkState::Pending {
ip: entry_point,
env: self.env(),
}),
);
self.push_stack(Value::new_gc(thunk));
}
MakeClosure => {
let entry_point = self.read_u32(bc);
let n_locals = self.read_u32(bc);
let closure = Gc::new(
mc,
Closure {
ip: entry_point,
n_locals,
env: self.env(),
pattern: None,
},
);
self.push_stack(Value::new_gc(closure));
}
MakePatternClosure => {
let entry_point = self.read_u32(bc);
let n_locals = self.read_u32(bc);
let req_count = self.read_u16(bc) as usize;
let opt_count = self.read_u16(bc) as usize;
let has_ellipsis = self.read_u8(bc) != 0;
let mut required = SmallVec::new();
for _ in 0..req_count {
required.push(self.read_string_id(bc));
}
let mut optional = SmallVec::new();
for _ in 0..opt_count {
optional.push(self.read_string_id(bc));
}
let total = req_count + opt_count;
let mut param_spans = Vec::with_capacity(total);
for _ in 0..total {
let name = self.read_string_id(bc);
let span_id = self.read_u32(bc);
param_spans.push((name, span_id));
}
let pattern = Gc::new(
mc,
PatternInfo {
required,
optional,
ellipsis: has_ellipsis,
param_spans: param_spans.into_boxed_slice(),
},
);
let closure = Gc::new(
mc,
Closure {
ip: entry_point,
n_locals,
env: self.env(),
pattern: Some(pattern),
},
);
self.push_stack(Value::new_gc(closure));
}
Call => {
let span_id = self.read_u32(bc);
let arg = self.stack.pop().expect("stack underflow");
let func = self.stack.pop().expect("stack underflow");
match self.force_inline(func) {
Ok(ForceResult::Ready(f)) => {
try_vm!(self, self.do_call(f, arg, Some(span_id), mc, strings))
}
Ok(other) => {
try_vm!(
self,
self.setup_force(
other,
AfterForce::Call {
arg,
span: Some(span_id),
},
mc,
strings,
)
);
}
Err(e) => return self.handle_vm_error(e),
}
}
CallNoSpan => {
let arg = self.stack.pop().expect("stack underflow");
let func = self.stack.pop().expect("stack underflow");
match self.force_inline(func) {
Ok(ForceResult::Ready(f)) => {
try_vm!(self, self.do_call(f, arg, None, mc, strings))
}
Ok(other) => {
try_vm!(
self,
self.setup_force(other, AfterForce::Call { arg, span: None }, mc, strings)
);
}
Err(e) => return self.handle_vm_error(e),
}
}
MakeAttrs => {
let count = self.read_u32(bc) as usize;
let total = 3 * count;
let items = self.stack.pop_n::<16>(total);
let mut entries = SmallVec::<[(StringId, Value<'gc>); 4]>::new();
for i in 0..count {
let key = &items[2 * i];
let val = items[2 * i + 1];
let key_sid =
Self::get_string_id(key).expect("MakeAttrs: key must be StringId");
entries.push((key_sid, val));
}
entries.sort_by_key(|(k, _)| *k);
let attrs = Gc::new(mc, unsafe { AttrSet::from_sorted_unchecked(entries) });
self.push_stack(Value::new_gc(attrs));
}
MakeAttrsDyn => {
let static_count = self.read_u32(bc) as usize;
let dynamic_count = self.read_u32(bc) as usize;
let total = 3 * static_count + 3 * dynamic_count;
let items = self.stack.pop_n::<16>(total);
let mut entries = SmallVec::<[(StringId, Value<'gc>); 4]>::new();
for i in 0..static_count {
let key_sid = Self::get_string_id(&items[2 * i])
.expect("MakeAttrsDyn: static key must be StringId");
entries.push((key_sid, items[2 * i + 1]));
}
let dyn_base = 3 * static_count;
for i in 0..dynamic_count {
let key = &items[dyn_base + 3 * i];
let val = items[dyn_base + 3 * i + 1];
if key.is::<Null>() {
continue;
}
let Some(key_sid) = Self::get_string_id(key) else {
return Action::Done(Err(Error::eval_error(
"dynamic attribute name must be a string",
)));
};
entries.push((key_sid, val));
}
entries.sort_by_key(|(k, _)| *k);
// FIXME: incorrect!!!
entries.dedup_by_key(|(k, _)| *k);
let attrs = Gc::new(mc, unsafe { AttrSet::from_sorted_unchecked(entries) });
self.push_stack(Value::new_gc(attrs));
}
MakeEmptyAttrs => {
let attrs = Gc::new(mc, unsafe {
AttrSet::from_sorted_unchecked(SmallVec::new())
});
self.push_stack(Value::new_gc(attrs));
}
Select => {
let n = self.read_u16(bc) as usize;
let span_id = self.read_u32(bc);
let mut keys = self.stack.pop_n::<4>(n);
let expr = self.stack.pop().expect("stack underflow");
keys.reverse();
let remaining = keys.len() as u16;
match self.force_inline(expr) {
Ok(ForceResult::Ready(forced)) => {
try_vm!(
self,
self.do_select_step(
forced, keys, remaining, span_id, None, mc, strings,
)
);
}
Ok(other) => {
try_vm!(
self,
self.setup_force(
other,
AfterForce::Select {
keys,
remaining,
span: span_id,
default: None,
},
mc,
strings,
)
);
}
Err(e) => return self.handle_vm_error(e),
}
}
SelectDefault => {
let n = self.read_u16(bc) as usize;
let span_id = self.read_u32(bc);
let default = self.stack.pop().expect("stack underflow");
let mut keys = self.stack.pop_n::<4>(n);
let expr = self.stack.pop().expect("stack underflow");
keys.reverse();
let remaining = keys.len() as u16;
match self.force_inline(expr) {
Ok(ForceResult::Ready(forced)) => {
try_vm!(
self,
self.do_select_step(
forced,
keys,
remaining,
span_id,
Some(default),
mc,
strings,
)
);
}
Ok(other) => {
try_vm!(
self,
self.setup_force(
other,
AfterForce::Select {
keys,
remaining,
span: span_id,
default: Some(default),
},
mc,
strings,
)
);
}
Err(e) => return self.handle_vm_error(e),
}
}
HasAttr => {
let n = self.read_u16(bc) as usize;
let mut keys = self.stack.pop_n::<4>(n);
let expr = self.stack.pop().expect("stack underflow");
keys.reverse();
let remaining = keys.len() as u16;
match self.force_inline(expr) {
Ok(ForceResult::Ready(forced)) => {
try_vm!(self, self.do_has_attr_step(forced, keys, remaining, mc, strings));
}
Ok(ForceResult::NeedEval { ip, env, thunk }) => {
self.push_force_frame(
thunk,
AfterForce::HasAttr { keys, remaining },
ip,
env,
mc,
);
}
Ok(ForceResult::NeedApply(thunk)) => {
try_vm!(
self,
self.push_apply_force_frame(
thunk,
AfterForce::HasAttr { keys, remaining },
mc,
strings,
)
);
}
Err(e) => return self.handle_vm_error(e),
}
}
MakeList => {
let count = self.read_u32(bc) as usize;
let items = self.stack.pop_n::<4>(count);
let list = Gc::new(mc, List { inner: items });
self.push_stack(Value::new_gc(list));
}
OpAdd | OpSub | OpMul | OpDiv | OpEq | OpNeq | OpLt | OpGt | OpLeq | OpGeq
| OpConcat | OpUpdate => {
let tag = match op {
OpAdd => BinOpTag::Add,
OpSub => BinOpTag::Sub,
OpMul => BinOpTag::Mul,
OpDiv => BinOpTag::Div,
OpEq => BinOpTag::Eq,
OpNeq => BinOpTag::Neq,
OpLt => BinOpTag::Lt,
OpGt => BinOpTag::Gt,
OpLeq => BinOpTag::Leq,
OpGeq => BinOpTag::Geq,
OpConcat => BinOpTag::Concat,
OpUpdate => BinOpTag::Update,
_ => unreachable!(),
};
let rhs = self.stack.pop().expect("stack underflow");
let lhs = self.stack.pop().expect("stack underflow");
match self.force_inline(lhs) {
Ok(ForceResult::Ready(lhs_f)) => match self.force_inline(rhs) {
Ok(ForceResult::Ready(rhs_f)) => {
match self.compute_binop(tag, lhs_f, rhs_f, mc, strings) {
Ok(r) => self.push_stack(r),
Err(e) => return self.handle_vm_error(e),
}
}
Ok(other) => {
try_vm!(
self,
self.setup_force(
other,
AfterForce::BinOpRhs {
lhs_forced: lhs_f,
op: tag,
},
mc,
strings,
)
);
}
Err(e) => return self.handle_vm_error(e),
},
Ok(other) => {
try_vm!(
self,
self.setup_force(other, AfterForce::BinOpLhs { rhs, op: tag }, mc, strings)
);
}
Err(e) => return self.handle_vm_error(e),
}
}
OpNeg => {
let val = self.stack.pop().expect("stack underflow");
match self.force_inline(val) {
Ok(ForceResult::Ready(f)) => match Self::as_num(f) {
Some(NixNum::Int(i)) => self
.push_stack(Self::make_int(-i, mc)),
Some(NixNum::Float(fl)) => self
.push_stack(Value::new_float(-fl)),
None => {
return Action::Done(Err(Error::eval_error(
"cannot negate non-number",
)));
}
},
Ok(other) => {
try_vm!(self, self.setup_force(other, AfterForce::UnNeg, mc, strings));
}
Err(e) => return self.handle_vm_error(e),
}
}
OpNot => {
let val = self.stack.pop().expect("stack underflow");
match self.force_inline(val) {
Ok(ForceResult::Ready(f)) => match f.as_inline::<bool>() {
Some(b) => self
.push_stack(Value::new_inline(!b)),
None => {
return Action::Done(Err(Error::eval_error("value is not a boolean")));
}
},
Ok(other) => {
try_vm!(self, self.setup_force(other, AfterForce::UnNot, mc, strings));
}
Err(e) => return self.handle_vm_error(e),
}
}
ForceBool => {
let val = self.stack.pop().expect("stack underflow");
match self.force_inline(val) {
Ok(ForceResult::Ready(f)) => {
if f.as_inline::<bool>().is_none() {
return Action::Done(Err(Error::eval_error("value is not a boolean")));
}
self.push_stack(f.relax());
}
Ok(ForceResult::NeedEval { ip, env, thunk }) => {
self.push_force_frame(thunk, AfterForce::ForceBool, ip, env, mc);
}
Ok(ForceResult::NeedApply(thunk)) => {
try_vm!(
self,
self.push_apply_force_frame(thunk, AfterForce::ForceBool, mc, strings)
);
}
Err(e) => return self.handle_vm_error(e),
}
}
JumpIfFalse => {
let offset = self.read_i32(bc);
let val = self.stack.pop().expect("stack underflow");
if let Some(false) = val.as_inline::<bool>() {
self.pc = ((self.pc as isize) + (offset as isize)) as usize;
}
}
JumpIfTrue => {
let offset = self.read_i32(bc);
let val = self.stack.pop().expect("stack underflow");
if let Some(true) = val.as_inline::<bool>() {
self.pc = ((self.pc as isize) + (offset as isize)) as usize;
}
}
Jump => {
let offset = self.read_i32(bc);
self.pc = ((self.pc as isize) + (offset as isize)) as usize;
}
ConcatStrings => {
let parts_count = self.read_u16(bc) as usize;
let force_string = self.read_u8(bc) != 0;
let parts = self.stack.pop_n::<8>(parts_count);
let mut remaining = parts;
remaining.reverse();
let forced = SmallVec::new();
try_vm!(
self,
self.concat_strings_continue(forced, remaining, force_string, mc, strings,)
);
}
ResolvePath => {
let _val = self.stack.pop().expect("stack underflow");
self.push_stack(Value::new_inline(Null));
}
Assert => {
let raw_idx = self.read_u32(bc);
let span_id = self.read_u32(bc);
let expr = self.stack.pop().expect("stack underflow");
let assertion = self.stack.pop().expect("stack underflow");
match self.force_inline(assertion) {
Ok(ForceResult::Ready(f)) => match f.as_inline::<bool>() {
Some(true) => self.push_stack(expr),
Some(false) => {
let sym = string_interner::symbol::SymbolU32::try_from_usize(
raw_idx as usize,
);
let msg = sym.and_then(|s| strings.resolve(s)).unwrap_or("<unknown>");
return self.handle_vm_error(VmError::Uncatchable(Error::eval_error(
format!("assertion '{msg}' failed"),
)));
}
None => {
return self.handle_vm_error(Self::err(
"assertion condition must be a boolean",
));
}
},
Ok(other) => {
try_vm!(
self,
self.setup_force(
other,
AfterForce::Assert {
expr,
raw_idx,
span_id,
},
mc,
strings,
)
);
}
Err(e) => return self.handle_vm_error(e),
}
}
PushWith => {
let ns = self.stack.pop().expect("stack underflow");
let scope = Gc::new(
mc,
WithScope {
env: ns,
prev: self.with_scope,
},
);
self.with_scope = Some(scope);
}
PopWith => {
if let Some(scope) = self.with_scope {
self.with_scope = scope.prev;
}
}
WithLookup => {
let name = self.read_string_id(bc);
match self.with_scope {
Some(scope_gc) => {
let env_val = scope_gc.env;
let next = scope_gc.prev;
match self.force_inline(env_val) {
Ok(ForceResult::Ready(forced)) => {
try_vm!(
self,
self.do_with_lookup_step(forced, name, next, mc, strings)
);
}
Ok(other) => {
try_vm!(
self,
self.setup_force(
other,
AfterForce::WithLookup { name, next },
mc,
strings,
)
);
}
Err(e) => return self.handle_vm_error(e),
}
}
None => {
let name_str = strings.resolve(name.0).unwrap_or("<unknown>");
return self.handle_vm_error(Self::err(format!(
"undefined variable '{name_str}'"
)));
}
}
}
LoadBuiltins => {
self.push_stack(self.globals.builtins);
}
LoadBuiltin => {
let name_raw = self.read_string_id(bc);
if let Some(&primop) = self.globals.builtin_lookup.get(&name_raw) {
self.push_stack(Value::new_inline(primop));
} else {
return Action::Done(Err(Error::eval_error(format!(
"unknown builtin (id {:?})",
name_raw
))));
}
}
MkPos => {
let _span_id = self.read_u32(bc);
todo!("MkPos")
}
LoadReplBinding => {
let _name = self.read_string_id(bc);
todo!("LoadReplBinding")
}
LoadScopedBinding => {
let _name = self.read_string_id(bc);
todo!("LoadScopedBinding")
}
Return => {
let ret_val = self.stack.pop().expect("stack underflow");
return self.handle_return(ret_val, mc, strings);
}
}
Action::Continue
}
pub(super) fn run_batch(
&mut self,
bytecode: &[u8],
pc: &mut usize,
mc: &Mutation<'gc>,
strings: &DefaultStringInterner,
) -> Action {
const COLLECTOR_GRANULARITY: f64 = 1024.0;
const BATCH_SIZE: usize = 1024;
if !self.started {
self.pc = *pc;
self.current_env = Some(Gc::new(mc, RefLock::new(Env::empty())));
self.started = true;
}
for _ in 0..BATCH_SIZE {
let action = self.execute_one(bytecode, mc, strings);
match action {
Action::Continue => {}
other => {
*pc = self.pc;
if matches!(other, Action::Done(_)) {
self.started = false;
}
return other;
}
}
}
*pc = self.pc;
if mc.metrics().allocation_debt() > COLLECTOR_GRANULARITY {
Action::NeedGc
} else {
Action::Continue
}
}
fn vm_try<T>(&mut self, result: VmResult<T>) -> std::result::Result<T, Action> {
match result {
Ok(ok) => Ok(ok),
Err(err) => Err(self.handle_vm_error(err)),
}
}
fn handle_vm_error(&mut self, e: VmError) -> Action {
match e {
VmError::Catchable(msg) => {
// Check for tryEval catch frames
if let Some(catch) = self.find_catch_frame() {
self.restore_catch_frame(catch);
return Action::Continue;
}
Action::Done(Err(Error::catchable(msg)))
}
VmError::Uncatchable(e) => Action::Done(Err(e)),
}
}
fn find_catch_frame(&mut self) -> Option<CatchFrameInfo> {
todo!("find catch frame")
}
fn restore_catch_frame(&mut self, _catch: CatchFrameInfo) {
todo!("restore catch frame")
}
#[inline(always)]
pub(super) fn push_stack(&mut self, val: Value<'gc>) {
#[cfg(debug_assertions)]
self.stack.push(val).expect("stack overflow");
#[cfg(not(debug_assertions))]
unsafe {
self.stack.push_unchecked(val);
}
}
#[inline(always)]
pub(super) fn push_empty_list(&mut self) {
self.push_stack(self.globals.empty_list);
}
#[inline(always)]
pub(super) fn push_empty_attrs(&mut self) {
self.push_stack(self.globals.empty_attrs);
}
}
struct CatchFrameInfo;