Files
nix-js/fix/src/runtime/vm.rs
T
2026-04-04 10:04:58 +08:00

1619 lines
58 KiB
Rust

use std::path::PathBuf;
use gc_arena::arena::CollectionPhase;
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::Runtime;
use super::builtins::{BUILTINS, BuiltinId, PrimOpArgs, PrimOpStrictArgs};
use super::stack::Stack;
use super::value::*;
use crate::codegen::{
InstructionPtr, KEY_DYNAMIC, KEY_STATIC, OPERAND_BIGINT, OPERAND_BUILTINS, OPERAND_CONST,
OPERAND_LOCAL,
};
use crate::error::{Error, Result};
use crate::ir::{Ir, RawIrRef, StringId};
use crate::runtime::init_builtins;
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, Clone, Copy, Debug, PartialEq, Eq, Default)]
#[collect(require_static)]
pub(super) enum ForceMode {
#[default]
AsIs,
Shallow,
Deep,
}
#[derive(Collect)]
#[collect(no_drop)]
pub(super) struct GcRoot<'gc> {
stack: Stack<65536, Value<'gc>>,
temp_stack: Vec<Value<'gc>>,
frames: Stack<8192, CallFrame<'gc>>,
with_scope: Option<Gc<'gc, WithScope<'gc>>>,
builtins: Value<'gc>,
empty_list: Value<'gc>,
empty_attrs: Value<'gc>,
import_cache: HashMap<PathBuf, Value<'gc>>,
current_env: Option<Gc<'gc, RefLock<Env<'gc>>>>,
}
pub(super) fn new_gc_root<'gc>(
mc: &Mutation<'gc>,
strings: &mut DefaultStringInterner,
) -> (
GcRoot<'gc>,
HashMap<StringId, Ir<'static, RawIrRef<'static>>>,
) {
let (global_env, builtins) = init_builtins(mc, strings);
let root = GcRoot {
stack: Stack::new(),
temp_stack: Vec::new(),
frames: Stack::new(),
with_scope: None,
builtins,
empty_list: Value::new_gc(Gc::new(mc, List::default())),
empty_attrs: Value::new_gc(Gc::new(mc, AttrSet::default())),
import_cache: HashMap::new(),
current_env: None,
};
(root, global_env)
}
impl<'gc> GcRoot<'gc> {
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);
// Regular primop
if arity != 0 {
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!("null", Value::new_inline(Null));
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 env(&self) -> Gc<'gc, RefLock<Env<'gc>>> {
self.current_env.expect("no current env")
}
#[inline(always)]
pub(super) fn push_stack(&mut self, val: Value<'gc>) {
self.stack.push(val).expect("stack overflow");
}
#[inline(always)]
pub(super) fn pop_stack(&mut self) -> Value<'gc> {
self.stack.pop().expect("stack underflow")
}
#[inline(always)]
pub(super) fn pop_stack_forced(&mut self) -> StrictValue<'gc> {
self.stack.pop().expect("stack underflow").restrict().expect("forced")
}
}
pub(super) 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>>>,
}
#[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,
Return,
Done(Result<crate::value::Value>),
}
pub(super) enum NixNum {
Int(i64),
Float(f64),
}
enum OperandData {
Const(StaticValue),
Local { layer: u8, idx: u32 },
Builtins,
BigInt(i64),
}
impl OperandData {
fn resolve<'gc>(&self, mc: &Mutation<'gc>, root: &GcRoot<'gc>) -> Value<'gc> {
match *self {
OperandData::Const(sv) => sv.into(),
OperandData::Local { layer, idx } => {
let mut cur = root.env();
for _ in 0..layer {
let prev = cur.borrow().prev.expect("env chain too short");
cur = prev;
}
cur.borrow().locals[idx as usize]
}
OperandData::Builtins => root.builtins,
OperandData::BigInt(val) => Value::new_gc(Gc::new(mc, val)),
}
}
}
enum AttrKeyData {
Static(StringId),
Dynamic(OperandData),
}
struct AttrEntry {
key: AttrKeyData,
val: OperandData,
}
enum SelectKeyData {
Static(StringId),
Dynamic,
}
macro_rules! try_vm {
($self:ident, $expr:expr) => {
match $expr {
Ok(v) => v,
Err(e) => return Runtime::handle_vm_error($self, e),
}
};
}
impl Runtime {
#[inline(always)]
fn read_array<const N: usize>(&mut self) -> [u8; N] {
let ret = self.bytecode[self.pc..self.pc + N]
.try_into()
.expect("read_array failed");
self.pc += N;
ret
}
#[inline(always)]
fn read_u8(&mut self) -> u8 {
u8::from_le_bytes(self.read_array())
}
#[inline(always)]
fn read_u16(&mut self) -> u16 {
u16::from_le_bytes(self.read_array())
}
#[inline(always)]
fn read_u32(&mut self) -> u32 {
u32::from_le_bytes(self.read_array())
}
#[inline(always)]
fn read_i32(&mut self) -> i32 {
i32::from_le_bytes(self.read_array())
}
#[inline(always)]
fn read_i64(&mut self) -> i64 {
i64::from_le_bytes(self.read_array())
}
#[inline(always)]
fn read_f64(&mut self) -> f64 {
f64::from_le_bytes(self.read_array())
}
#[inline(always)]
fn read_string_id(&mut self) -> StringId {
let raw = self.read_u32();
StringId(unsafe {
string_interner::symbol::SymbolU32::try_from_usize(raw as usize).unwrap_unchecked()
})
}
fn read_operand(&mut self) -> OperandData {
let tag = self.read_u8();
match tag {
OPERAND_CONST => {
let idx = self.read_u32();
OperandData::Const(self.constants[idx as usize])
}
OPERAND_LOCAL => {
let layer = self.read_u8();
let idx = self.read_u32();
OperandData::Local { layer, idx }
}
OPERAND_BUILTINS => OperandData::Builtins,
OPERAND_BIGINT => {
let val = self.read_i64();
OperandData::BigInt(val)
}
_ => panic!("unknown operand tag: {tag:#04x}"),
}
}
fn read_attr_keys(&mut self, n: usize) -> SmallVec<[SelectKeyData; 4]> {
let mut keys = SmallVec::with_capacity(n);
for _ in 0..n {
let tag = self.read_u8();
match tag {
KEY_STATIC => {
let sid = self.read_string_id();
keys.push(SelectKeyData::Static(sid));
}
KEY_DYNAMIC => {
keys.push(SelectKeyData::Dynamic);
}
_ => panic!("unknown key tag: {tag:#04x}"),
}
}
keys
}
#[inline(always)]
fn execute_one(&mut self) -> Action {
use crate::codegen::Op::{self, *};
let Ok(op) = Op::try_from_primitive(self.bytecode[self.pc])
.map_err(|err| panic!("unknown opcode: {:#04x}", err.number));
self.pc += 1;
match op {
PushSmi => {
let val = self.read_i32();
self.push_stack(|_| Value::new_inline(val));
}
PushBigInt => {
let val = self.read_i64();
self.push_stack(|mc| Value::new_gc(Gc::new(mc, val)));
}
PushFloat => {
let val = self.read_f64();
self.push_stack(|_| Value::new_float(val));
}
PushString => {
let sid = self.read_string_id();
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() as usize;
self.arena
.mutate_root(|mc, root| root.push_stack(root.env().borrow().locals[idx]))
}
LoadOuter => {
let layer = self.read_u8();
let idx = self.read_u32() as usize;
self.arena.mutate_root(|mc, root| {
let mut cur = root.env();
for _ in 0..layer {
let prev = cur.borrow().prev.expect("LoadOuter: env chain too short");
cur = prev;
}
let val = cur.borrow().locals[idx];
root.push_stack(val);
});
}
StoreLocal => {
let idx = self.read_u32() as usize;
self.arena.mutate_root(|mc, root| {
let val = root.pop_stack();
root.env().borrow_mut(mc).locals[idx] = val;
})
}
AllocLocals => {
let count = self.read_u32() as usize;
self.arena.mutate_root(|mc, root| {
root.env()
.borrow_mut(mc)
.locals
.extend(std::iter::repeat_n(Value::default(), count));
});
}
MakeThunk => {
let entry_point = self.read_u32();
let _label = self.read_string_id();
self.arena.mutate_root(|mc, root| {
let thunk = Gc::new(
mc,
RefLock::new(ThunkState::Pending {
ip: entry_point as usize,
env: root.env(),
}),
);
root.push_stack(Value::new_gc(thunk));
})
}
MakeClosure => {
let entry_point = self.read_u32();
let n_locals = self.read_u32();
self.arena.mutate_root(|mc, root| {
let closure = Gc::new(
mc,
Closure {
ip: entry_point,
n_locals,
env: root.env(),
pattern: None,
},
);
root.push_stack(Value::new_gc(closure));
});
}
MakePatternClosure => {
let entry_point = self.read_u32();
let n_locals = self.read_u32();
let req_count = self.read_u16() as usize;
let opt_count = self.read_u16() as usize;
let has_ellipsis = self.read_u8() != 0;
let mut required = SmallVec::new();
for _ in 0..req_count {
required.push(self.read_string_id());
}
let mut optional = SmallVec::new();
for _ in 0..opt_count {
optional.push(self.read_string_id());
}
let total = req_count + opt_count;
let mut param_spans = Vec::with_capacity(total);
for _ in 0..total {
let name = self.read_string_id();
let span_id = self.read_u32();
param_spans.push((name, span_id));
}
self.arena.mutate_root(|mc, root| {
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: root.env(),
pattern: Some(pattern),
},
);
root.push_stack(Value::new_gc(closure));
})
}
Call => {
// force func
let _span = self.read_u32();
self.force_tos();
self.arena.mutate_root(|mc, root| {
let func = root.pop_stack();
let arg = root.pop_stack();
if let Some(closure) = func.as_gc::<Closure>() {
let ip = closure.ip;
let n_locals = closure.n_locals;
let env = closure.env;
if let Some(ref _pattern) = closure.pattern {
todo!("pattern call")
} else {
let new_env =
Gc::new(mc, RefLock::new(Env::with_arg(arg, n_locals, env)));
root.frames
.push(CallFrame {
pc: self.pc,
env: root.env(),
})
.expect("frame stack overflow");
self.pc = ip as usize;
root.current_env = Some(new_env);
}
} else {
todo!("call other types: {func:?}")
}
});
}
CallNoSpan => {
todo!("implement CallNoSpan");
}
MakeAttrs => {
let count = self.read_u32() as usize;
let mut entries: SmallVec<[AttrEntry; 4]> = SmallVec::with_capacity(count);
for _ in 0..count {
let key_tag = self.read_u8();
let key = match key_tag {
KEY_STATIC => AttrKeyData::Static(self.read_string_id()),
KEY_DYNAMIC => AttrKeyData::Dynamic(self.read_operand()),
_ => panic!("unknown key tag: {key_tag:#04x}"),
};
let val = self.read_operand();
let _span_id = self.read_u32();
entries.push(AttrEntry { key, val });
}
self.arena.mutate_root(|mc, root| {
let mut kv: SmallVec<[(StringId, Value); 4]> = SmallVec::with_capacity(count);
for entry in &entries {
let key_sid = match &entry.key {
&AttrKeyData::Static(sid) => sid,
AttrKeyData::Dynamic(op) => {
let v = op.resolve(mc, root);
v.as_inline::<StringId>()
.expect("dynamic attr key must be a string")
}
};
let val = entry.val.resolve(mc, root);
kv.push((key_sid, val));
}
kv.sort_by_key(|(k, _)| *k);
let attrs = Gc::new(mc, unsafe { AttrSet::from_sorted_unchecked(kv) });
root.push_stack(Value::new_gc(attrs));
});
}
MakeEmptyAttrs => {
self.push_empty_attrs();
}
Select => {
let n = self.read_u16() as usize;
let _span_id = self.read_u32();
let keys = self.read_attr_keys(n);
// Move dynamic key values from stack to temp_stack
let dyn_count = keys
.iter()
.filter(|k| matches!(k, SelectKeyData::Dynamic))
.count();
let temp_base = self.arena.mutate_root(|_, root| {
let base = root.temp_stack.len();
for _ in 0..dyn_count {
let v = root.pop_stack();
root.temp_stack.push(v);
}
base
});
// Force the expr (now TOS)
self.force_tos();
for (i, key) in keys.iter().enumerate() {
let key_sid = match key {
&SelectKeyData::Static(sid) => sid,
SelectKeyData::Dynamic => {
self.arena.mutate_root(|_, root| {
let v =
root.temp_stack.pop().expect("missing dynamic key");
root.push_stack(v);
});
self.force_tos();
let key_data: std::result::Result<StringId, String> =
self.arena.mutate_root(|_, root| {
let v = root.pop_stack();
if let Some(sid) = v.as_inline::<StringId>() {
Ok(sid)
} else if let Some(ns) = v.as_gc::<NixString>() {
Err(ns.as_str().to_owned())
} else {
panic!("dynamic select key must be a string")
}
});
match key_data {
Ok(sid) => sid,
Err(s) => StringId(self.strings.get_or_intern(&s)),
}
}
};
let result = self.arena.mutate_root(|_, root| {
let val = root.pop_stack();
let Some(attrset) = val.as_gc::<AttrSet<'_>>() else {
return Err(vm_err(
"value is not a set while a set was expected",
));
};
match attrset.lookup(key_sid) {
Some(v) => {
root.push_stack(v);
Ok(true)
}
None => Ok(false),
}
});
match result {
Err(e) => {
self.arena
.mutate_root(|_, root| root.temp_stack.truncate(temp_base));
return Runtime::handle_vm_error(self, e);
}
Ok(false) => {
self.arena
.mutate_root(|_, root| root.temp_stack.truncate(temp_base));
let name =
self.strings.resolve(key_sid.0).unwrap_or("«unknown»");
return Runtime::handle_vm_error(
self,
vm_err(format!("attribute '{name}' missing")),
);
}
Ok(true) => {
if i < n - 1 {
self.force_tos();
}
}
}
}
self.arena
.mutate_root(|_, root| root.temp_stack.truncate(temp_base));
}
SelectDefault => {
let n = self.read_u16() as usize;
let _span_id = self.read_u32();
let keys = self.read_attr_keys(n);
let dyn_count = keys
.iter()
.filter(|k| matches!(k, SelectKeyData::Dynamic))
.count();
let temp_base = self.arena.mutate_root(|_, root| {
let base = root.temp_stack.len();
// Default value is on top of the stack (pushed last by codegen)
let default_val = root.pop_stack();
root.temp_stack.push(default_val);
// Then dynamic keys
for _ in 0..dyn_count {
let v = root.pop_stack();
root.temp_stack.push(v);
}
base
});
// Force the expr (now TOS)
self.force_tos();
let mut use_default = false;
for (i, key) in keys.iter().enumerate() {
let key_sid = match key {
&SelectKeyData::Static(sid) => sid,
SelectKeyData::Dynamic => {
self.arena.mutate_root(|_, root| {
let v =
root.temp_stack.pop().expect("missing dynamic key");
root.push_stack(v);
});
self.force_tos();
let key_data: std::result::Result<StringId, String> =
self.arena.mutate_root(|_, root| {
let v = root.pop_stack();
if let Some(sid) = v.as_inline::<StringId>() {
Ok(sid)
} else if let Some(ns) = v.as_gc::<NixString>() {
Err(ns.as_str().to_owned())
} else {
panic!("dynamic select key must be a string")
}
});
match key_data {
Ok(sid) => sid,
Err(s) => StringId(self.strings.get_or_intern(&s)),
}
}
};
let found = self.arena.mutate_root(|_, root| {
let val = root.pop_stack();
if let Some(attrset) = val.as_gc::<AttrSet<'_>>() {
if let Some(v) = attrset.lookup(key_sid) {
root.push_stack(v);
return true;
}
}
// Not a set or key missing → use default
false
});
if !found {
use_default = true;
break;
}
if i < n - 1 {
self.force_tos();
}
}
if use_default {
self.arena.mutate_root(|_, root| {
let default_val = root.temp_stack[temp_base];
root.temp_stack.truncate(temp_base);
root.push_stack(default_val);
});
} else {
self.arena
.mutate_root(|_, root| root.temp_stack.truncate(temp_base));
}
}
HasAttr => {
let n = self.read_u16() as usize;
let keys = self.read_attr_keys(n);
todo!("implement HasAttr (force + check)");
}
MakeList => {
let count = self.read_u32() as usize;
let mut operands: SmallVec<[OperandData; 4]> = SmallVec::with_capacity(count);
for _ in 0..count {
operands.push(self.read_operand());
}
self.arena.mutate_root(|mc, root| {
let mut items: SmallVec<[Value; 4]> = SmallVec::with_capacity(count);
for op in &operands {
items.push(op.resolve(mc, root));
}
let list = Gc::new(mc, List { inner: items });
root.push_stack(Value::new_gc(list));
});
}
MakeEmptyList => {
self.push_empty_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!(),
};
try_vm!(self, self.compute_binop(tag))
}
OpNeg => {
todo!("implement unary operation");
}
OpNot => {
todo!("implement unary operation");
}
JumpIfFalse => {
let offset = self.read_i32();
self.force_tos();
self.arena.mutate_root(|_, arena| {
let cond = arena.pop_stack();
if cond.as_inline::<bool>() == Some(false) {
self.pc = ((self.pc as isize) + (offset as isize)) as usize;
}
});
}
JumpIfTrue => {
let offset = self.read_i32();
self.force_tos();
self.arena.mutate_root(|_, arena| {
let cond = arena.pop_stack();
if cond.as_inline::<bool>() == Some(true) {
self.pc = ((self.pc as isize) + (offset as isize)) as usize;
}
});
}
Jump => {
let offset = self.read_i32();
self.pc = ((self.pc as isize) + (offset as isize)) as usize;
}
ConcatStrings => {
let parts_count = self.read_u16() as usize;
let _force_string = self.read_u8() != 0;
let mut operands: SmallVec<[OperandData; 4]> = SmallVec::with_capacity(parts_count);
for _ in 0..parts_count {
operands.push(self.read_operand());
}
todo!("implement ConcatStrings (force parts, coerce to string, concatenate)");
}
ResolvePath => {
todo!("implement ResolvePath");
}
Assert => {
let raw_idx = self.read_u32();
let span_id = self.read_u32();
todo!("implement Assert (force TOS)");
}
PushWith => {
self.arena.mutate_root(|mc, root| {
let env = root.pop_stack();
let scope = Gc::new(
mc,
WithScope {
env,
prev: root.with_scope,
},
);
root.with_scope = Some(scope);
});
}
PopWith => self.arena.mutate_root(|_, root| {
let Some(scope) = root.with_scope else {
unreachable!("no with_scope to pop");
};
root.with_scope = scope.prev;
}),
WithLookup => {
let name = self.read_string_id();
todo!("implement WithLookup (force with_scope)");
}
LoadBuiltins => {
self.push_builtins();
}
LoadBuiltin => {
let Ok(id) = BuiltinId::try_from_primitive(self.read_u8())
.map_err(|err| panic!("unknown builtin id: {}", err.number));
self.push_stack(|_| {
Value::new_inline(PrimOp {
id,
arity: BUILTINS[id as usize].1,
})
});
}
MkPos => {
let _span_id = self.read_u32();
todo!("MkPos")
}
LoadReplBinding => {
let _name = self.read_string_id();
todo!("LoadReplBinding")
}
LoadScopedBinding => {
let _name = self.read_string_id();
todo!("LoadScopedBinding")
}
Return => {
return self.handle_return();
}
}
Action::Continue
}
pub(super) fn get_string<'a, 'gc: 'a>(
strings: &'a DefaultStringInterner,
val: StrictValue<'gc>,
) -> 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 get_num(val: StrictValue<'_>) -> 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)
}
}
fn compute_binop(&mut self, op: BinOpTag) -> VmResult<()> {
self.force_n(2);
match op {
BinOpTag::Add => {
let strings = &self.strings;
self.arena.mutate_root(|mc, root| {
let rhs = root.pop_stack_forced();
let lhs = root.pop_stack_forced();
// FIXME: path & string context
if let (Some(ls), Some(rs)) = (
Self::get_string(strings, lhs),
Self::get_string(strings, rhs),
) {
let ns = Gc::new(mc, NixString::new(format!("{ls}{rs}")));
root.push_stack(Value::new_gc(ns));
return Ok(());
}
let res = Self::numeric_binop(lhs, rhs, mc, i64::wrapping_add, |a, b| a + b)?;
root.push_stack(res);
VmResult::Ok(())
})?;
}
BinOpTag::Sub => {
self.arena.mutate_root(|mc, root| {
let rhs = root.pop_stack_forced();
let lhs = root.pop_stack_forced();
let res = Self::numeric_binop(lhs, rhs, mc, i64::wrapping_sub, |a, b| a - b)?;
root.push_stack(res);
VmResult::Ok(())
})?;
}
BinOpTag::Mul => {
self.arena.mutate_root(|mc, root| {
let rhs = root.pop_stack_forced();
let lhs = root.pop_stack_forced();
let res = Self::numeric_binop(lhs, rhs, mc, i64::wrapping_mul, |a, b| a * b)?;
root.push_stack(res);
VmResult::Ok(())
})?;
}
BinOpTag::Div => {
self.arena.mutate_root(|mc, root| {
let rhs = root.pop_stack_forced();
let lhs = root.pop_stack_forced();
let res = match (Self::get_num(lhs), Self::get_num(rhs)) {
(_, Some(NixNum::Int(0))) => Err(vm_err("division by zero")),
(_, Some(NixNum::Float(0.))) => Err(vm_err("division by zero")),
(Some(NixNum::Int(a)), Some(NixNum::Int(b))) => {
Ok(Value::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(vm_err("cannot divide non-numbers")),
}?;
root.push_stack(res);
VmResult::Ok(())
})?;
}
BinOpTag::Eq => {
let eq = self.values_equal()?;
self.push_stack(|_| Value::new_inline(eq));
}
BinOpTag::Neq => {
let eq = self.values_equal()?;
self.push_stack(|_| Value::new_inline(!eq));
}
BinOpTag::Lt => self.compare_values(|o| o.is_lt())?,
BinOpTag::Gt => self.compare_values(|o| o.is_gt())?,
BinOpTag::Leq => self.compare_values(|o| o.is_le())?,
BinOpTag::Geq => self.compare_values(|o| o.is_ge())?,
BinOpTag::Concat => {
self.arena.mutate_root(|mc, root| {
let rhs = root.pop_stack_forced();
let lhs = root.pop_stack_forced();
let Some(l) = lhs.as_gc::<List<'_>>() else {
return Err(vm_err("cannot concatenate: left operand is not a list"));
};
let Some(r) = rhs.as_gc::<List<'_>>() else {
return Err(vm_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());
root.push_stack(Value::new_gc(Gc::new(mc, List { inner: items })));
VmResult::Ok(())
})?;
}
BinOpTag::Update => {
self.arena.mutate_root(|mc, root| {
let rhs = root.pop_stack_forced();
let lhs = root.pop_stack_forced();
let Some(l) = lhs.as_gc::<AttrSet<'_>>() else {
return Err(vm_err("cannot update: left operand is not a set"));
};
let Some(r) = rhs.as_gc::<AttrSet<'_>>() else {
return Err(vm_err("cannot update: right operand is not a set"));
};
root.push_stack(Value::new_gc(l.merge(&r, mc)));
VmResult::Ok(())
})?;
}
}
Ok(())
}
fn numeric_binop<'gc>(
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::get_num(lhs), Self::get_num(rhs)) {
(Some(NixNum::Int(a)), Some(NixNum::Int(b))) => Ok(Value::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(vm_err("cannot perform arithmetic on non-numbers")),
}
}
pub(super) fn values_equal(&mut self) -> VmResult<bool> {
enum State {
Bool(bool),
List(usize),
AttrSet(usize),
}
let strings = &self.strings;
let state = self.arena.mutate_root(|_, root| {
let rhs = root.pop_stack_forced();
let lhs = root.pop_stack_forced();
if let (Some(a), Some(b)) = (Self::get_num(lhs), Self::get_num(rhs)) {
return State::Bool(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 State::Bool(a == b);
}
if lhs.is::<Null>() && rhs.is::<Null>() {
return State::Bool(true);
}
if let (Some(a), Some(b)) = (
Self::get_string(strings, lhs),
Self::get_string(strings, rhs),
) {
return State::Bool(a == b);
}
if let (Some(a), Some(b)) = (lhs.as_gc::<List<'_>>(), rhs.as_gc::<List<'_>>()) {
if a.inner.len() != b.inner.len() {
return State::Bool(false);
}
for (x, y) in a.inner.iter().zip(b.inner.iter()).rev() {
root.temp_stack.push(*x);
root.temp_stack.push(*y);
}
return State::List(a.inner.len());
}
if let (Some(a), Some(b)) = (lhs.as_gc::<AttrSet<'_>>(), rhs.as_gc::<AttrSet<'_>>()) {
if a.len() != b.len() {
return State::Bool(false);
}
for ((k1, v1), (k2, v2)) in a.iter().zip(b.iter()).rev() {
if k1 != k2 {
return State::Bool(false);
}
root.temp_stack.push(*v1);
root.temp_stack.push(*v2);
}
return State::AttrSet(a.len());
}
State::Bool(false)
});
match state {
State::Bool(b) => Ok(b),
State::List(len) | State::AttrSet(len) => {
for i in 0..len {
self.arena.mutate_root(|_, root| {
let y = root.temp_stack.pop().unwrap();
let x = root.temp_stack.pop().unwrap();
root.push_stack(x);
root.push_stack(y);
});
self.force_n(2);
let eq = self.values_equal()?;
if !eq {
self.arena.mutate_root(|_, root| {
let rem = len - 1 - i;
for _ in 0..rem * 2 {
root.temp_stack.pop();
}
});
return Ok(false);
}
}
Ok(true)
}
}
}
pub(super) fn compare_values(
&mut self,
pred: impl FnOnce(std::cmp::Ordering) -> bool,
) -> VmResult<()> {
let strings = &self.strings;
self.arena.mutate_root(|_, root| {
let rhs = root.pop_stack_forced();
let lhs = root.pop_stack_forced();
if let (Some(a), Some(b)) = (Self::get_num(lhs), Self::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(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),
};
root.push_stack(Value::new_inline(pred(ord)));
return VmResult::Ok(());
}
if let (Some(a), Some(b)) = (
Self::get_string(strings, lhs),
Self::get_string(strings, rhs),
) {
root.push_stack(Value::new_inline(pred(a.cmp(b))));
return VmResult::Ok(());
}
Err(vm_err("cannot compare these types"))
})?;
Ok(())
}
#[inline(always)]
fn check_gc(&mut self) {
const COLLECTOR_GRANULARITY: f64 = 1024.0;
if self.fuel == 0 {
if self.arena.metrics().allocation_debt() > COLLECTOR_GRANULARITY {
if self.arena.collection_phase() == CollectionPhase::Sweeping {
self.arena.collect_debt();
} else if let Some(marked) = self.arena.mark_debt() {
marked.start_sweeping();
}
}
self.fuel = Self::DEFAULT_FUEL_AMOUNT;
}
self.fuel -= 1;
}
pub(super) fn run(
&mut self,
ip: InstructionPtr,
mode: ForceMode,
) -> Result<crate::value::Value> {
self.pc = ip.0;
self.force_mode = mode;
self.arena.mutate_root(|mc, root| {
if root.current_env.is_none() {
root.current_env = Some(Gc::new(mc, RefLock::new(Env::empty())));
}
});
loop {
self.check_gc();
match self.execute_one() {
Action::Continue => (),
Action::Return => (),
Action::Done(done) => break done,
}
}
}
#[inline(always)]
pub(super) fn push_stack(&mut self, f: impl for<'gc> FnOnce(&Mutation<'gc>) -> Value<'gc>) {
self.arena.mutate_root(|mc, root| {
root.stack.push(f(mc)).expect("stack overflow");
})
}
#[inline(always)]
pub(super) fn push_empty_list(&mut self) {
self.arena.mutate_root(|_, root| {
root.push_stack(root.empty_list);
});
}
#[inline(always)]
pub(super) fn push_empty_attrs(&mut self) {
self.arena.mutate_root(|_, root| {
root.push_stack(root.empty_attrs);
});
}
#[inline(always)]
pub(super) fn push_builtins(&mut self) {
self.arena.mutate_root(|_, root| {
root.push_stack(root.builtins);
});
}
pub(super) fn force_tos(&mut self) -> Action {
loop {
let run = self.arena.mutate_root(|_mc, root| {
let thunk = root
.stack
.tos_mut()
.expect("stack underflow");
let Some(thunk_state) = thunk.as_gc::<Thunk>() else {
return false
};
match *thunk_state.borrow() {
ThunkState::Pending { ip, env } => {
root.frames
.push(CallFrame {
pc: self.pc,
env: root.env(),
})
.expect("call stack overflow");
self.pc = ip;
root.current_env = Some(env);
true
}
ThunkState::Apply { .. } => todo!("force_tos"),
ThunkState::Evaluated(val) => {
*thunk = val;
false
},
ThunkState::Blackhole => todo!("force_tos"),
}
});
if !run {
return Action::Continue;
}
loop {
self.check_gc();
match self.execute_one() {
Action::Continue => (),
Action::Return => break,
// FIXME: poison thunk
Action::Done(err @ Err(_)) => return Action::Done(err),
Action::Done(Ok(_)) => unreachable!(),
}
}
self.arena.mutate_root(|mc, root| {
let val = root.pop_stack();
let thunk = root.stack.tos_mut().expect("stack underflow");
{
let thunk = thunk.as_gc::<Thunk>().expect("expected thunk");
*thunk.borrow_mut(mc) = ThunkState::Evaluated(val);
}
*thunk = val;
});
}
}
pub(super) fn force_n(&mut self, n: usize) {
if n == 0 {
return;
}
self.arena.mutate_root(|_, root| {
for _ in 0..n - 1 {
let tos = root.pop_stack();
root.temp_stack.push(tos);
}
});
self.force_tos();
for _ in 0..n - 1 {
self.arena.mutate_root(|_, root| {
let next = root.temp_stack.pop().expect("temp stack underflow");
root.push_stack(next);
});
self.force_tos();
}
}
pub(super) fn handle_return(&mut self) -> Action {
self.force_tos();
let done= self.arena.mutate_root(|_, root| {
let Some(frame) = root.frames.pop() else {
return true;
};
self.pc = frame.pc;
root.current_env = Some(frame.env);
false
});
if !done {
return Action::Return;
}
match self.force_mode {
ForceMode::AsIs => (),
ForceMode::Shallow => {
if let done @ Action::Done(_) = self.force_tos_shallow() {
return done
}
}
ForceMode::Deep => {
if let done @ Action::Done(_) = self.force_tos_shallow() {
return done
}
}
}
let val = self.arena.mutate_root(|_, root| {
root.current_env = None;
convert_value(
root.stack.pop().expect("stack underflow"),
&self.strings,
)
});
Action::Done(Ok(val))
}
pub(super) fn force_tos_shallow(&mut self) -> Action {
if let err @ Action::Done(Err(_)) = self.force_tos() {
return err;
}
let (is_list, is_attrs) = self.arena.mutate_root(|_, root| {
let tos = *root.stack.tos().expect("stack underflow");
(tos.as_gc::<List<'_>>().is_some(), tos.as_gc::<AttrSet<'_>>().is_some())
});
if is_list {
let len = self.arena.mutate_root(|_, root| {
let list = root.pop_stack().as_gc::<List<'_>>().unwrap();
for &item in list.inner.iter() {
root.temp_stack.push(item);
}
list.inner.len()
});
let eval_base = self.arena.mutate_root(|_, root| root.temp_stack.len());
for i in 0..len {
self.arena.mutate_root(|_, root| {
let item = root.temp_stack[eval_base - len + i];
root.push_stack(item);
});
if let err @ Action::Done(Err(_)) = self.force_tos() {
self.arena.mutate_root(|_, root| root.temp_stack.truncate(eval_base - len));
return err;
}
self.arena.mutate_root(|_, root| {
let eval_item = root.pop_stack();
root.temp_stack[eval_base - len + i] = eval_item;
});
}
self.arena.mutate_root(|mc, root| {
let items: SmallVec<[Value; 4]> = root.temp_stack[eval_base - len..eval_base].iter().copied().collect();
root.temp_stack.truncate(eval_base - len);
// Reconstruct List
let new_list = Gc::new(mc, List { inner: items });
root.push_stack(Value::new_gc(new_list));
});
} else if is_attrs {
let len = self.arena.mutate_root(|_, root| {
let attrs = root.pop_stack().as_gc::<AttrSet<'_>>().unwrap();
for &(key, item) in attrs.iter() {
root.temp_stack.push(Value::new_inline(key));
root.temp_stack.push(item);
}
attrs.len()
});
let eval_base = self.arena.mutate_root(|_, root| root.temp_stack.len());
for i in 0..len {
self.arena.mutate_root(|_, root| {
let item = root.temp_stack[eval_base - len * 2 + i * 2 + 1];
root.push_stack(item);
});
if let err @ Action::Done(Err(_)) = self.force_tos() {
self.arena.mutate_root(|_, root| root.temp_stack.truncate(eval_base - len * 2));
return err;
}
self.arena.mutate_root(|_, root| {
let eval_item = root.pop_stack();
root.temp_stack[eval_base - len * 2 + i * 2 + 1] = eval_item;
});
}
self.arena.mutate_root(|mc, root| {
let mut kv = SmallVec::with_capacity(len);
let mut i = eval_base - len * 2;
while i < eval_base {
let key = root.temp_stack[i].as_inline::<StringId>().unwrap();
let val = root.temp_stack[i + 1];
kv.push((key, val));
i += 2;
}
kv.sort_by_key(|(k, _)| *k);
root.temp_stack.truncate(eval_base - len * 2);
let new_attrs = Gc::new(mc, unsafe { AttrSet::from_sorted_unchecked(kv) });
root.push_stack(Value::new_gc(new_attrs));
});
}
Action::Continue
}
pub(super) fn force_tos_deep(&mut self) -> Action {
if let err @ Action::Done(Err(_)) = self.force_tos() {
return err;
}
let (is_list, is_attrs) = self.arena.mutate_root(|_, root| {
let tos = *root.stack.tos().expect("stack underflow");
(tos.as_gc::<List<'_>>().is_some(), tos.as_gc::<AttrSet<'_>>().is_some())
});
if is_list {
let len = self.arena.mutate_root(|_, root| {
let list = root.pop_stack().as_gc::<List<'_>>().unwrap();
for &item in list.inner.iter() {
root.temp_stack.push(item);
}
list.inner.len()
});
let eval_base = self.arena.mutate_root(|_, root| root.temp_stack.len());
for i in 0..len {
self.arena.mutate_root(|_, root| {
let item = root.temp_stack[eval_base - len + i];
root.push_stack(item);
});
if let err @ Action::Done(Err(_)) = self.force_tos_deep() {
self.arena.mutate_root(|_, root| root.temp_stack.truncate(eval_base - len));
return err;
}
self.arena.mutate_root(|_, root| {
let eval_item = root.pop_stack();
root.temp_stack[eval_base - len + i] = eval_item;
});
}
self.arena.mutate_root(|mc, root| {
let items: SmallVec<[Value; 4]> = root.temp_stack[eval_base - len..eval_base].iter().copied().collect();
root.temp_stack.truncate(eval_base - len);
let new_list = Gc::new(mc, List { inner: items });
root.push_stack(Value::new_gc(new_list));
});
} else if is_attrs {
let len = self.arena.mutate_root(|_, root| {
let attrs = root.pop_stack().as_gc::<AttrSet<'_>>().unwrap();
for &(key, item) in attrs.iter() {
root.temp_stack.push(Value::new_inline(key));
root.temp_stack.push(item);
}
attrs.len()
});
let eval_base = self.arena.mutate_root(|_, root| root.temp_stack.len());
for i in 0..len {
self.arena.mutate_root(|_, root| {
let item = root.temp_stack[eval_base - len * 2 + i * 2 + 1];
root.push_stack(item);
});
if let err @ Action::Done(Err(_)) = self.force_tos_deep() {
self.arena.mutate_root(|_, root| root.temp_stack.truncate(eval_base - len * 2));
return err;
}
self.arena.mutate_root(|_, root| {
let eval_item = root.pop_stack();
root.temp_stack[eval_base - len * 2 + i * 2 + 1] = eval_item;
});
}
self.arena.mutate_root(|mc, root| {
let mut kv = SmallVec::with_capacity(len);
let mut i = eval_base - len * 2;
while i < eval_base {
let key = root.temp_stack[i].as_inline::<StringId>().unwrap();
let val = root.temp_stack[i + 1];
kv.push((key, val));
i += 2;
}
kv.sort_by_key(|(k, _)| *k);
root.temp_stack.truncate(eval_base - len * 2);
let new_attrs = Gc::new(mc, unsafe { AttrSet::from_sorted_unchecked(kv) });
root.push_stack(Value::new_gc(new_attrs));
});
}
Action::Continue
}
fn handle_vm_error(&mut self, e: VmError) -> Action {
match e {
VmError::Catchable(_) => {
todo!("Check for tryEval catch frames");
}
VmError::Uncatchable(e) => Action::Done(Err(e)),
}
}
}
fn convert_value<'gc>(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 = 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()
.copied()
.map(|v| 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
}
}
pub(super) fn vm_err(msg: impl Into<String>) -> VmError {
VmError::Uncatchable(Error::eval_error(msg.into()))
}