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 = std::result::Result; pub(super) enum VmError { Catchable(String), Uncatchable(Box), } impl From> for VmError { fn from(e: Box) -> 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>, frames: Stack<8192, CallFrame<'gc>>, with_scope: Option>>, builtins: Value<'gc>, empty_list: Value<'gc>, empty_attrs: Value<'gc>, import_cache: HashMap>, current_env: Option>>>, } pub(super) fn new_gc_root<'gc>( mc: &Mutation<'gc>, strings: &mut DefaultStringInterner, ) -> ( GcRoot<'gc>, HashMap>>, ) { 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) { 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>> { 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, } #[derive(Collect, Debug)] #[collect(no_drop)] pub(super) struct WithScope<'gc> { env: Value<'gc>, prev: Option>>, } #[derive(Collect, Debug)] #[collect(no_drop)] struct CallFrame<'gc> { pc: usize, env: Gc<'gc, RefLock>>, } #[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>>, thunk: Gc<'gc, Thunk<'gc>>, }, NeedApply(Gc<'gc, Thunk<'gc>>), } pub(crate) enum Action { Continue, Return, Done(Result), } 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(&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::() { 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::() .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 = self.arena.mutate_root(|_, root| { let v = root.pop_stack(); if let Some(sid) = v.as_inline::() { Ok(sid) } else if let Some(ns) = v.as_gc::() { 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::>() 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 = self.arena.mutate_root(|_, root| { let v = root.pop_stack(); if let Some(sid) = v.as_inline::() { Ok(sid) } else if let Some(ns) = v.as_gc::() { 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::>() { 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::() == 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::() == 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::() { Some(strings.resolve(sid.0)?) } else { val.as_gc::().map(|ns| ns.as_ref().as_str()) } } pub(super) fn get_num(val: StrictValue<'_>) -> Option { if let Some(i) = val.as_inline::() { Some(NixNum::Int(i as i64)) } else if let Some(gc_i) = val.as_gc::() { 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::>() else { return Err(vm_err("cannot concatenate: left operand is not a list")); }; let Some(r) = rhs.as_gc::>() 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::>() else { return Err(vm_err("cannot update: left operand is not a set")); }; let Some(r) = rhs.as_gc::>() 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> { 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 { 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::(), rhs.as_inline::()) { return State::Bool(a == b); } if lhs.is::() && rhs.is::() { 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::>(), rhs.as_gc::>()) { 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::>(), rhs.as_gc::>()) { 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 { 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::() 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::().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::>().is_some(), tos.as_gc::>().is_some()) }); if is_list { let len = self.arena.mutate_root(|_, root| { let list = root.pop_stack().as_gc::>().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::>().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::().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::>().is_some(), tos.as_gc::>().is_some()) }); if is_list { let len = self.arena.mutate_root(|_, root| { let list = root.pop_stack().as_gc::>().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::>().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::().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::() { crate::value::Value::Int(i as i64) } else if let Some(gc_i) = val.as_gc::() { 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::() { crate::value::Value::Bool(b) } else if val.is::() { crate::value::Value::Null } else if let Some(sid) = val.as_inline::() { let s = strings.resolve(sid.0).unwrap_or("").to_owned(); crate::value::Value::String(s) } else if let Some(ns) = val.as_gc::() { crate::value::Value::String(ns.as_str().to_owned()) } else if let Some(attrs) = val.as_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::>() { 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::>() { crate::value::Value::Func } else if val.is::>() { crate::value::Value::Thunk } else if val.as_inline::().is_some() { crate::value::Value::PrimOp("primop".into()) } else if val.is::>() { crate::value::Value::PrimOpApp("primop-app".into()) } else { crate::value::Value::Null } } pub(super) fn vm_err(msg: impl Into) -> VmError { VmError::Uncatchable(Error::eval_error(msg.into())) }