1619 lines
58 KiB
Rust
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()))
|
|
}
|