use std::path::PathBuf; use fix_builtins::{BUILTINS, BuiltinId}; use fix_codegen::{AttrKeyType, InstructionPtr, OperandType}; use fix_common::StringId; use fix_error::{Error, Result, Source}; use gc_arena::arena::CollectionPhase; use gc_arena::{Arena, Collect, Gc, Mutation, RefLock, Rootable}; use hashbrown::HashMap; use num_enum::TryFromPrimitive; use smallvec::SmallVec; use string_interner::Symbol as _; mod boxing; mod value; use value::*; mod helpers; use helpers::*; pub use value::StaticValue; type VmResult = std::result::Result; 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 enum ForceMode { #[default] AsIs, Shallow, Deep, } pub trait VmContext { fn intern_string(&mut self, s: impl AsRef) -> StringId; fn resolve_string(&self, id: StringId) -> &str; fn bytecode(&self) -> &[u8]; fn get_const(&self, id: u32) -> StaticValue; fn compile(&mut self, source: Source); } trait VmContextExt: VmContext { fn get_string<'a, 'gc: 'a>(&'a self, val: StrictValue<'gc>) -> Option<&'a str>; fn convert_value(&self, val: Value) -> fix_common::Value; } impl VmContextExt for T { fn get_string<'a, 'gc: 'a>(&'a self, val: StrictValue<'gc>) -> Option<&'a str> { if let Some(sid) = val.as_inline::() { Some(self.resolve_string(sid)) } else { val.as_gc::().map(|ns| ns.as_ref().as_str()) } } fn convert_value(&self, val: Value) -> fix_common::Value { use fix_common::Value; if let Some(i) = val.as_inline::() { Value::Int(i as i64) } else if let Some(gc_i) = val.as_gc::() { Value::Int(*gc_i) } else if let Some(f) = val.as_float() { Value::Float(f) } else if let Some(b) = val.as_inline::() { Value::Bool(b) } else if val.is::() { Value::Null } else if let Some(sid) = val.as_inline::() { let s = self.resolve_string(sid).to_owned(); Value::String(s) } else if let Some(ns) = val.as_gc::() { 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 = self.resolve_string(key).to_owned(); let converted = self.convert_value(val); map.insert(fix_common::Symbol::from(key), converted); } Value::AttrSet(fix_common::AttrSet::new(map)) } else if let Some(list) = val.as_gc::() { let items: Vec<_> = list .inner .iter() .copied() .map(|v| self.convert_value(v)) .collect(); Value::List(fix_common::List::new(items)) } else if val.is::() { Value::Func } else if val.is::() { Value::Thunk } else if val.as_inline::().is_some() { Value::PrimOp("primop".into()) } else if val.is::() { Value::PrimOpApp("primop-app".into()) } else { Value::Null } } } pub struct Vm { arena: Arena]>, error_context: Vec, ctx: C, pc: usize, force_mode: ForceMode, } #[derive(Collect)] #[collect(no_drop)] struct GcRoot<'gc> { stack: Vec>, call_stack: Vec>, call_depth: usize, with_env: Option>>, builtins: Value<'gc>, empty_list: Value<'gc>, empty_attrs: Value<'gc>, import_cache: HashMap>, current_env: Option>, } fn init_builtins<'gc>(mc: &Mutation<'gc>, ctx: &mut impl VmContext) -> Value<'gc> { let mut entries = SmallVec::with_capacity(BUILTINS.len()); for (idx, &(name, arity)) in BUILTINS.iter().enumerate() { let id = BuiltinId::try_from_primitive(idx as u8).expect("infallible"); let name = name.strip_prefix("__").unwrap_or(name); let name = ctx.intern_string(name); entries.push((name, Value::new_inline(PrimOp { id, arity }))); } let consts = [ ( "__currentSystem", Value::new_inline(ctx.intern_string("x86_64-linux")), ), ("__langVersion", Value::new_inline(6i32)), ( "__nixVersion", Value::new_inline(ctx.intern_string("2.24.0")), ), ( "__storeDir", Value::new_inline(ctx.intern_string("/nix/store")), ), ( "__nixPath", Value::new_gc(Gc::new( mc, List { inner: SmallVec::new(), }, )), ), ("null", Value::new_inline(Null)), ("true", Value::new_inline(true)), ("false", Value::new_inline(false)), ]; for (name, val) in consts { let name = name.strip_prefix("__").unwrap_or(name); let name = ctx.intern_string(name); entries.push((name, val)); } let self_ref_thunk: Gc<'gc, Thunk<'gc>> = Gc::new(mc, RefLock::new(ThunkState::Blackhole)); let sym = ctx.intern_string("builtins"); entries.push((sym, Value::new_gc(self_ref_thunk))); entries.sort_by_key(|(k, _)| *k); let builtins_set = Gc::new(mc, AttrSet::from_sorted_unchecked(entries)); Value::new_gc(builtins_set) } impl<'gc> GcRoot<'gc> { fn new(mc: &Mutation<'gc>, ctx: &mut impl VmContext) -> Self { let builtins = init_builtins(mc, ctx); GcRoot { stack: Vec::with_capacity(8192), call_stack: Vec::with_capacity(1024), call_depth: 0, with_env: 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, } } #[inline(always)] fn env(&self) -> Gc<'gc, RefLock>> { self.current_env.expect("no current env") } #[inline(always)] fn push_stack(&mut self, val: Value<'gc>) { self.stack.push(val); } #[inline(always)] fn pop_stack(&mut self) -> Value<'gc> { self.stack.pop().expect("stack underflow") } #[inline(always)] fn peek_stack(&mut self, depth: usize) -> Value<'gc> { *self .stack .get(self.stack.len() - depth - 1) .expect("stack underflow") } #[inline(always)] fn replace_stack(&mut self, depth: usize, val: Value<'gc>) { let len = self.stack.len(); *self .stack .get_mut(len - depth - 1) .expect("stack underflow") = val; } #[inline(always)] #[cfg_attr(debug_assertions, track_caller)] fn pop_stack_forced(&mut self) -> StrictValue<'gc> { self.stack .pop() .expect("stack underflow") .restrict() .expect("forced") } } struct ErrorFrame { span_id: u32, message: Option, } #[derive(Collect, Debug)] #[collect(no_drop)] struct CallFrame<'gc> { pc: usize, stack_depth: usize, thunk: Option>>, env: Gc<'gc, RefLock>>, with_env: Option>>, } pub(crate) enum Action { Continue, Done(Result), } 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 GcRoot::handle_vm_error($self, e), } }; } impl Vm { pub fn new(mut ctx: C, ip: InstructionPtr, force_mode: ForceMode) -> Self { Self { arena: Arena::new(|mc| GcRoot::new(mc, &mut ctx)), ctx, force_mode, pc: ip.0, error_context: Vec::new(), } } pub fn run(mut self) -> Result { const COLLECTOR_GRANULARITY: f64 = 1024.0; self.arena.mutate_root(|mc, root| { if root.current_env.is_none() { root.current_env = Some(Gc::new(mc, RefLock::new(Env::empty()))); } }); let mut pc = self.pc; loop { match self .arena .mutate_root(|mc, root| root.execute_batch(&mut self.ctx, &mut pc, mc)) { Action::Continue => { 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(); } } } Action::Done(done) => break done, } } } } impl<'gc> GcRoot<'gc> { #[inline(always)] fn execute_batch( &mut self, ctx: &mut impl VmContext, pc: &mut usize, mc: &Mutation<'gc>, ) -> Action { use fix_codegen::Op::{self, *}; const DEFAULT_FUEL_AMOUNT: usize = 1024; #[inline(always)] #[cfg_attr(debug_assertions, track_caller)] fn read_array(bytecode: &[u8], pc: &mut usize) -> [u8; N] { let ret = bytecode[*pc..*pc + N] .try_into() .expect("read_array failed"); *pc += N; ret } macro_rules! read { (StringId) => {{ let raw = read!(u32); StringId(string_interner::symbol::SymbolU32::try_from_usize(raw as usize).unwrap()) }}; (OperandData) => {{ let tag = read!(u8); let Ok(ty) = OperandType::try_from_primitive(tag) .map_err(|err| panic!("unknown operand tag: {:#04x}", err.number)); match ty { OperandType::Const => { let id = read!(u32); #[allow(clippy::unwrap_used)] OperandData::Const(ctx.get_const(id)) } OperandType::Local => { let layer = read!(u8); let idx = read!(u32); OperandData::Local { layer, idx } } OperandType::Builtins => OperandData::Builtins, OperandType::BigInt => { let val = read!(i64); OperandData::BigInt(val) } } }}; (SelectKeyData, $n:expr) => {{ let mut keys = SmallVec::<[SelectKeyData; 4]>::with_capacity($n as usize); for _ in 0..$n { let tag = read!(u8); let Ok(ty) = AttrKeyType::try_from_primitive(tag) .map_err(|err| panic!("unknown key tag: {:#04x}", err.number)); match ty { AttrKeyType::Static => keys.push(SelectKeyData::Static(read!(StringId))), AttrKeyType::Dynamic => keys.push(SelectKeyData::Dynamic), }; } keys }}; ($type:ty) => { <$type>::from_le_bytes(read_array(ctx.bytecode(), pc)) }; } let mut fuel = DEFAULT_FUEL_AMOUNT; 'dispatch: loop { macro_rules! try_force { ($depth:expr, $inst_start:expr) => {{ let val = self.peek_stack($depth); if let Some(thunk) = val.as_gc::() { let mut state = thunk.borrow_mut(mc); match *state { ThunkState::Pending { ip, env, with_env } => { // retry self.call_stack.push(CallFrame { thunk: Some(thunk), stack_depth: $depth, pc: $inst_start, env: self.env(), with_env: self.with_env, }); *pc = ip; self.current_env = Some(env); self.with_env = with_env; *state = ThunkState::Blackhole; continue 'dispatch; } ThunkState::Evaluated(v) => { self.replace_stack($depth, v.relax()); } ThunkState::Apply { .. } => todo!("force apply"), ThunkState::Blackhole => { return Action::Done(Err(Error::eval_error( "infinite recursion encountered", ))); } } } }}; } if fuel == 0 { return Action::Continue; } fuel -= 1; // Save PC for Instruction Retry let inst_start_pc = *pc; let byte = ctx.bytecode()[*pc]; if !likely_stable::likely((0..Op::Illegal as u8).contains(&byte)) { panic!("unknown opcode: {byte:#04x}") } let op = unsafe { std::mem::transmute::(byte) }; *pc += 1; match op { PushSmi => { let val = read!(i32); self.push_stack(Value::new_inline(val)); } PushBigInt => { let val = read!(i64); self.push_stack(Value::new_gc(Gc::new(mc, val))); } PushFloat => { let val = read!(f64); self.push_stack(Value::new_float(val)); } PushString => { let sid = read!(StringId); 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 = read!(u32) as usize; self.push_stack(self.env().borrow().locals[idx]); } LoadOuter => { let layer = read!(u8); let idx = read!(u32) as usize; let mut cur = self.env(); for _ in 0..layer { let prev = cur.borrow().prev.expect("LoadOuter: env chain too short"); cur = prev; } let val = cur.borrow().locals[idx]; self.push_stack(val); } StoreLocal => { let idx = read!(u32) as usize; let val = self.pop_stack(); self.env().borrow_mut(mc).locals[idx] = val; } AllocLocals => { let count = read!(u32) as usize; self.env() .borrow_mut(mc) .locals .extend(std::iter::repeat_n(Value::default(), count)); } MakeThunk => { let entry_point = read!(u32); let thunk = Gc::new( mc, RefLock::new(ThunkState::Pending { ip: entry_point as usize, env: self.env(), with_env: self.with_env, }), ); self.push_stack(Value::new_gc(thunk)); } MakeClosure => { let entry_point = read!(u32); let n_locals = read!(u32); let closure = Gc::new( mc, Closure { ip: entry_point, n_locals, env: self.env(), pattern: None, }, ); self.push_stack(Value::new_gc(closure)); } MakePatternClosure => { let entry_point = read!(u32); let n_locals = read!(u32); let req_count = read!(u16) as usize; let opt_count = read!(u16) as usize; let has_ellipsis = read!(u8) != 0; let mut required = SmallVec::new(); for _ in 0..req_count { required.push(read!(StringId)); } let mut optional = SmallVec::new(); for _ in 0..opt_count { optional.push(read!(StringId)); } let total = req_count + opt_count; let mut param_spans = Vec::with_capacity(total); for _ in 0..total { let name = read!(StringId); let span_id = read!(u32); param_spans.push((name, span_id)); } let pattern = Gc::new( mc, PatternInfo { required, optional, ellipsis: has_ellipsis, param_spans: param_spans.into_boxed_slice(), }, ); let closure = Gc::new( mc, Closure { ip: entry_point, n_locals, env: self.env(), pattern: Some(pattern), }, ); self.push_stack(Value::new_gc(closure)); } Call => { try_force!(0, inst_start_pc); if self.call_depth > 10000 { return Action::Done(Err(Error::eval_error( "stack overflow; max-call-depth exceeded", ))); } self.call_depth += 1; let func = self.pop_stack(); let arg = read!(OperandData).resolve(mc, self); 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))); self.call_stack.push(CallFrame { pc: *pc, stack_depth: 0, thunk: None, env: self.env(), with_env: self.with_env, }); *pc = ip as usize; self.current_env = Some(new_env); } } else { todo!("call other types: {func:?}") } } MakeAttrs => { let count = read!(u32) as usize; let mut entries: SmallVec<[AttrEntry; 4]> = SmallVec::with_capacity(count); for _ in 0..count { let key_tag = read!(u8); let Ok(ty) = AttrKeyType::try_from_primitive(key_tag) .map_err(|err| panic!("unknown key tag: {:#04x}", err.number)); let key = match ty { AttrKeyType::Static => AttrKeyData::Static(read!(StringId)), AttrKeyType::Dynamic => AttrKeyData::Dynamic(read!(OperandData)), }; let val = read!(OperandData); let _span_id = read!(u32); entries.push(AttrEntry { key, val }); } 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, self); v.as_inline::() .expect("dynamic attr key must be a string") } }; let val = entry.val.resolve(mc, self); kv.push((key_sid, val)); } kv.sort_by_key(|(k, _)| *k); let attrs = Gc::new(mc, AttrSet::from_sorted_unchecked(kv)); self.push_stack(Value::new_gc(attrs)); } MakeEmptyAttrs => { self.push_stack(self.empty_attrs); } Select => { let n = read!(u16) as usize; let _span_id = read!(u32); let keys = read!(SelectKeyData, n); let dyn_count = keys .iter() .filter(|k| matches!(k, SelectKeyData::Dynamic)) .count(); // Force target (at depth `dyn_count`) and all dynamic keys on top of it. for i in 0..=dyn_count { try_force!(i, inst_start_pc); } // Stack Layout: [..., target, dyn1, dyn2, ..., dyn_m] let target_idx = self.stack.len() - dyn_count - 1; let mut current_dyn_key_idx = target_idx + 1; let mut current_val = self.stack[target_idx].restrict().expect("forced"); let mut result_val = None; let mut error = None; for (i, key) in keys.iter().enumerate() { let key_sid = match key { SelectKeyData::Static(sid) => *sid, SelectKeyData::Dynamic => { let v = self.stack[current_dyn_key_idx] .restrict() .expect("dynamic key must be forced"); current_dyn_key_idx += 1; if let Some(sid) = v.as_inline::() { sid } else if let Some(ns) = v.as_gc::() { ctx.intern_string(ns.as_str()) } else { panic!("dynamic select key must be a string") } } }; let Some(attrset) = current_val.as_gc::() else { error = Some(vm_err("value is not a set while a set was expected")); break; }; match attrset.lookup(key_sid) { Some(v) => { if i < n - 1 { // FIXME: Proper async force hook inside select chain for nested thunks current_val = v.restrict().unwrap_or_else(|_| { panic!("intermediate select values must be forced") }); } else { result_val = Some(v); } } None => { let name = ctx.resolve_string(key_sid); error = Some(vm_err(format!("attribute '{name}' missing"))); break; } } } // Clean up the target and all dynamic keys self.stack.truncate(target_idx); if let Some(e) = error { return self.handle_vm_error(e); } if let Some(v) = result_val { self.push_stack(v); } } SelectDefault => { let n = read!(u16) as usize; let _span_id = read!(u32); let keys = read!(SelectKeyData, n); let dyn_count = keys .iter() .filter(|k| matches!(k, SelectKeyData::Dynamic)) .count(); // Stack layout: [..., target, default_val, dyn1, dyn2, ..., dyn_m] for i in 0..=dyn_count + 1 { try_force!(i, inst_start_pc); } let target_idx = self.stack.len() - dyn_count - 2; let default_idx = target_idx + 1; let mut current_dyn_key_idx = default_idx + 1; let mut current_val = self.stack[target_idx].restrict().expect("forced"); let mut result_val = None; let mut use_default = false; for (i, key) in keys.iter().enumerate() { let key_sid = match key { SelectKeyData::Static(sid) => *sid, SelectKeyData::Dynamic => { let v = self.stack[current_dyn_key_idx] .restrict() .expect("dynamic key must be forced"); current_dyn_key_idx += 1; if let Some(sid) = v.as_inline::() { sid } else if let Some(ns) = v.as_gc::() { ctx.intern_string(ns.as_str()) } else { panic!("dynamic select key must be a string") } } }; if let Some(attrset) = current_val.as_gc::() && let Some(v) = attrset.lookup(key_sid) { if i < n - 1 { current_val = v.restrict().unwrap_or_else(|_| { panic!("intermediate select values must be forced") }); } else { result_val = Some(v); } continue; } use_default = true; break; } let def = self.stack[default_idx]; self.stack.truncate(target_idx); if use_default { self.push_stack(def); } else if let Some(v) = result_val { self.push_stack(v); } } HasAttr => { let _n = read!(u16) as usize; todo!("HasAttr"); } MakeList => { let count = read!(u32) as usize; let mut items: SmallVec<[Value; 4]> = SmallVec::with_capacity(count); for _ in 0..count { items.push(read!(OperandData).resolve(mc, self)); } let list = Gc::new(mc, List { inner: items }); self.push_stack(Value::new_gc(list)); } MakeEmptyList => { self.push_stack(self.empty_list); } OpAdd => { try_force!(1, inst_start_pc); try_force!(0, inst_start_pc); let res = (|| { let rhs = self.pop_stack_forced(); let lhs = self.pop_stack_forced(); // FIXME: path & string context if let (Some(ls), Some(rs)) = (ctx.get_string(lhs), ctx.get_string(rhs)) { let ns = Gc::new(mc, NixString::new(format!("{ls}{rs}"))); self.push_stack(Value::new_gc(ns)); return Ok(()); } let res = numeric_binop(lhs, rhs, mc, i64::wrapping_add, |a, b| a + b)?; self.push_stack(res); VmResult::Ok(()) })(); try_vm!(self; res); } OpSub | OpMul => { try_force!(1, inst_start_pc); try_force!(0, inst_start_pc); #[allow(clippy::type_complexity)] let func: (fn(i64, i64) -> i64, fn(f64, f64) -> f64) = match op { OpSub => (i64::wrapping_sub, |a, b| a - b), OpMul => (i64::wrapping_mul, |a, b| a * b), _ => unreachable!(), }; let res = (|| { let rhs = self.pop_stack_forced(); let lhs = self.pop_stack_forced(); let res = numeric_binop(lhs, rhs, mc, func.0, func.1)?; self.push_stack(res); VmResult::Ok(()) })(); try_vm!(self; res); } OpDiv => { try_force!(1, inst_start_pc); try_force!(0, inst_start_pc); let res = (|| { let rhs = self.pop_stack_forced(); let lhs = self.pop_stack_forced(); match (get_num(lhs), get_num(rhs)) { (_, Some(NixNum::Int(0))) => Err(vm_err("division by zero")), (_, Some(NixNum::Float(0.))) => Err(vm_err("division by zero")), _ => Ok(()), }?; let res = numeric_binop(lhs, rhs, mc, |a, b| a / b, |a, b| a / b)?; self.push_stack(res); VmResult::Ok(()) })(); try_vm!(self; res); } OpEq | OpNeq => { try_force!(1, inst_start_pc); try_force!(0, inst_start_pc); let map: fn(bool) -> bool = match op { OpEq => |a| a, OpNeq => |a| !a, _ => unreachable!(), }; let eq = try_vm!(self; self.values_equal(ctx)); self.push_stack(Value::new_inline(map(eq))); } OpLt | OpGt | OpLeq | OpGeq => { use std::cmp::Ordering; try_force!(1, inst_start_pc); try_force!(0, inst_start_pc); let pred: fn(Ordering) -> bool = match op { OpLt => Ordering::is_lt, OpGt => Ordering::is_gt, OpLeq => Ordering::is_le, OpGeq => Ordering::is_ge, _ => unreachable!(), }; try_vm!(self; self.compare_values(ctx, pred)); } OpConcat => { try_force!(1, inst_start_pc); try_force!(0, inst_start_pc); let res = (|| { let rhs = self.pop_stack_forced(); let lhs = self.pop_stack_forced(); // TODO: better type-assert ergonomic 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_from_slice(&l); items.extend_from_slice(&r); self.push_stack(Value::new_gc(Gc::new(mc, List { inner: items }))); VmResult::Ok(()) })(); try_vm!(self; res); } OpUpdate => { try_force!(1, inst_start_pc); try_force!(0, inst_start_pc); let res = (|| { let rhs = self.pop_stack_forced(); let lhs = self.pop_stack_forced(); // TODO: better type-assert ergonomic 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")); }; self.push_stack(Value::new_gc(l.merge(&r, mc))); VmResult::Ok(()) })(); try_vm!(self; res); } OpNeg => { todo!("implement unary operation"); } OpNot => { todo!("implement unary operation"); } JumpIfFalse => { let offset = read!(i32); try_force!(0, inst_start_pc); let cond = self.pop_stack(); if cond.as_inline::() == Some(false) { *pc = ((*pc as isize) + (offset as isize)) as usize; } } JumpIfTrue => { let offset = read!(i32); try_force!(0, inst_start_pc); let cond = self.pop_stack(); if cond.as_inline::() == Some(true) { *pc = ((*pc as isize) + (offset as isize)) as usize; } } Jump => { let offset = read!(i32); *pc = ((*pc as isize) + (offset as isize)) as usize; } ConcatStrings => { let parts_count = read!(u16) as usize; let _force_string = read!(u8) != 0; let mut operands: SmallVec<[OperandData; 4]> = SmallVec::with_capacity(parts_count); for _ in 0..parts_count { operands.push(read!(OperandData)); } todo!("implement ConcatStrings (force parts, coerce to string, concatenate)"); } ResolvePath => { todo!("implement ResolvePath"); } Assert => { let _raw_idx = read!(u32); let _span_id = read!(u32); todo!("implement Assert (force TOS)"); } PushWith => { let env = self.pop_stack(); let scope = Gc::new( mc, WithEnv { env, prev: self.with_env, }, ); self.with_env = Some(scope); } PopWith => { let Some(scope) = self.with_env else { unreachable!("no with_scope to pop"); }; self.with_env = scope.prev; } WithLookup => { let name = read!(StringId); let mut cur = self.with_env; let mut found_val = None; while let Some(scope) = cur { // Using restrict() to force extraction sync due to CPS structure. let env_val = scope .env .restrict() .unwrap_or_else(|_| panic!("with scope env must be forced")); let Some(attrs) = env_val.as_gc::() else { return self .handle_vm_error(vm_err("value in 'with' scope must be a set")); }; if let Some(v) = attrs.lookup(name) { found_val = Some(v); break; } cur = scope.prev; } if let Some(v) = found_val { self.push_stack(v); } else { let name_str = ctx.resolve_string(name); return self .handle_vm_error(vm_err(format!("undefined variable '{name_str}'"))); } } LoadBuiltins => { self.push_stack(self.builtins); } LoadBuiltin => { let Ok(id) = BuiltinId::try_from_primitive(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 = read!(u32); todo!("MkPos") } LoadReplBinding => { let _name = read!(StringId); todo!("LoadReplBinding") } LoadScopedBinding => { let _name = read!(StringId); todo!("LoadScopedBinding") } Return => { if let Some(result) = self.handle_return(pc, ctx, mc) { return Action::Done(result); } } Illegal => unreachable!(), } } } #[inline] fn values_equal(&mut self, ctx: &impl VmContext) -> VmResult { let rhs = self.pop_stack_forced(); let lhs = self.pop_stack_forced(); if let (Some(a), Some(b)) = (get_num(lhs), get_num(rhs)) { return Ok(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 Ok(a == b); } if lhs.is::() && rhs.is::() { return Ok(true); } if let (Some(a), Some(b)) = (ctx.get_string(lhs), ctx.get_string(rhs)) { return Ok(a == b); } if let (Some(a), Some(b)) = (lhs.as_gc::(), rhs.as_gc::()) { if a.inner.len() != b.inner.len() { return Ok(false); } let len = a.inner.len(); for (x, y) in a.inner.iter().zip(b.inner.iter()).rev() { self.push_stack(*x); self.push_stack(*y); } for i in 0..len { let eq = self.values_equal(ctx)?; if !eq { let rem = len - 1 - i; self.stack.truncate(self.stack.len() - rem * 2); return Ok(false); } } return Ok(true); } if let (Some(a), Some(b)) = (lhs.as_gc::(), rhs.as_gc::()) { if a.len() != b.len() { return Ok(false); } let len = a.len(); for ((k1, v1), (k2, v2)) in a.iter().zip(b.iter()).rev() { if k1 != k2 { return Ok(false); } self.push_stack(*v1); self.push_stack(*v2); } for i in 0..len { let eq = self.values_equal(ctx)?; if !eq { let rem = len - 1 - i; self.stack.truncate(self.stack.len() - rem * 2); return Ok(false); } } return Ok(true); } Ok(false) } #[inline] fn compare_values( &mut self, ctx: &impl VmContext, pred: fn(std::cmp::Ordering) -> bool, ) -> VmResult<()> { let rhs = self.pop_stack_forced(); let lhs = self.pop_stack_forced(); if let (Some(a), Some(b)) = (get_num(lhs), get_num(rhs)) { let ord = match (a, b) { (NixNum::Int(a), NixNum::Int(b)) => a.cmp(&b), (NixNum::Float(a), NixNum::Float(b)) => { a.partial_cmp(&b).unwrap_or(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), }; self.push_stack(Value::new_inline(pred(ord))); return Ok(()); } if let (Some(a), Some(b)) = (ctx.get_string(lhs), ctx.get_string(rhs)) { self.push_stack(Value::new_inline(pred(a.cmp(b)))); return Ok(()); } Err(vm_err("cannot compare these types")) } #[inline(always)] fn handle_return( &mut self, pc: &mut usize, ctx: &impl VmContext, mc: &Mutation<'gc>, ) -> Option> { let ret_inst_pc = *pc - 1; #[deny(unused_variables)] if let Some(CallFrame { pc: ret_pc, stack_depth, thunk, env, with_env, }) = self.call_stack.pop() { *pc = ret_pc; if let Some(outer_thunk) = thunk { let val = self.pop_stack(); match val.restrict() { Ok(val) => { *outer_thunk.borrow_mut(mc) = ThunkState::Evaluated(val); if ctx.bytecode().get(ret_pc).copied() == Some(fix_codegen::Op::Return as u8) { self.push_stack(val.relax()); } } Err(inner_thunk) => { let mut state = inner_thunk.borrow_mut(mc); match *state { ThunkState::Pending { ip: inner_ip, env: inner_env, with_env: inner_with_env, } => { self.call_stack.push(CallFrame { pc: ret_pc, stack_depth, thunk: Some(outer_thunk), env, with_env, }); self.call_stack.push(CallFrame { pc: ret_inst_pc, stack_depth: 0, thunk: Some(inner_thunk), env: inner_env, with_env: inner_with_env, }); *state = ThunkState::Blackhole; *pc = inner_ip; self.current_env = Some(inner_env); self.with_env = inner_with_env; return None; } ThunkState::Evaluated(val) => { *outer_thunk.borrow_mut(mc) = ThunkState::Evaluated(val); if ctx.bytecode().get(ret_pc).copied() == Some(fix_codegen::Op::Return as u8) { self.push_stack(val.relax()); } } ThunkState::Apply { func: _, arg: _ } => todo!("force Apply thunk"), ThunkState::Blackhole => { return Some(Err(Error::eval_error( "infinite recursion encountered", ))); } } } } } else { self.call_depth -= 1; } self.current_env = Some(env); self.with_env = with_env; return None; } // FIXME: ForceMode self.current_env = None; self.with_env = None; let val = self.pop_stack(); Some(Ok(ctx.convert_value(val))) } 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)), } } } #[inline] 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 (get_num(lhs), 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")), } }