From 9983458b31ad935fc12ee2466b5370f34abe3360 Mon Sep 17 00:00:00 2001 From: imxyy_soope_ Date: Sat, 11 Apr 2026 10:30:03 +0800 Subject: [PATCH] feat(vm): threaded VM --- Cargo.lock | 10 + fix-codegen/Cargo.toml | 1 + fix-codegen/src/disassembler.rs | 366 ++++++ fix-codegen/src/lib.rs | 2 + fix-vm/src/lib.rs | 2023 +++++++++++++------------------ fix-vm/src/stack.rs | 76 -- fix-vm/src/value.rs | 10 +- fix/src/lib.rs | 19 +- fix/src/main.rs | 10 +- 9 files changed, 1253 insertions(+), 1264 deletions(-) create mode 100644 fix-codegen/src/disassembler.rs delete mode 100644 fix-vm/src/stack.rs diff --git a/Cargo.lock b/Cargo.lock index 457098e..6736184 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -258,6 +258,15 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" +[[package]] +name = "colored" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" +dependencies = [ + "windows-sys", +] + [[package]] name = "countme" version = "3.0.1" @@ -472,6 +481,7 @@ dependencies = [ name = "fix-codegen" version = "0.1.0" dependencies = [ + "colored", "fix-builtins", "fix-common", "fix-ir", diff --git a/fix-codegen/Cargo.toml b/fix-codegen/Cargo.toml index e3e01bc..926ba45 100644 --- a/fix-codegen/Cargo.toml +++ b/fix-codegen/Cargo.toml @@ -9,6 +9,7 @@ hashbrown = { workspace = true } num_enum = { workspace = true } rnix = { workspace = true } string-interner = { workspace = true } +colored = "3.1.1" fix-builtins = { path = "../fix-builtins" } fix-common = { path = "../fix-common" } diff --git a/fix-codegen/src/disassembler.rs b/fix-codegen/src/disassembler.rs new file mode 100644 index 0000000..e8052ff --- /dev/null +++ b/fix-codegen/src/disassembler.rs @@ -0,0 +1,366 @@ +use std::fmt::Write; + +use colored::Colorize; +use num_enum::TryFromPrimitive; + +use crate::{InstructionPtr, Op}; + +pub trait DisassemblerContext { + fn resolve_string(&self, id: u32) -> &str; + fn get_code(&self) -> &[u8]; +} + +pub struct Disassembler<'a, Ctx> { + code: &'a [u8], + ctx: &'a Ctx, + pc: usize, +} + +impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> { + pub fn new(ip: InstructionPtr, ctx: &'a Ctx) -> Self { + Self { + code: ctx.get_code(), + ctx, + pc: ip.0, + } + } + + fn read_u8(&mut self) -> u8 { + let b = self.code[self.pc]; + self.pc += 1; + b + } + + fn read_u16(&mut self) -> u16 { + let bytes = self.code[self.pc..self.pc + 2] + .try_into() + .expect("no enough bytes"); + self.pc += 2; + u16::from_le_bytes(bytes) + } + + fn read_u32(&mut self) -> u32 { + let bytes = self.code[self.pc..self.pc + 4] + .try_into() + .expect("no enough bytes"); + self.pc += 4; + u32::from_le_bytes(bytes) + } + + fn read_i32(&mut self) -> i32 { + let bytes = self.code[self.pc..self.pc + 4] + .try_into() + .expect("no enough bytes"); + self.pc += 4; + i32::from_le_bytes(bytes) + } + + fn read_i64(&mut self) -> i64 { + let bytes = self.code[self.pc..self.pc + 8] + .try_into() + .expect("no enough bytes"); + self.pc += 8; + i64::from_le_bytes(bytes) + } + + fn read_f64(&mut self) -> f64 { + let bytes = self.code[self.pc..self.pc + 8] + .try_into() + .expect("no enough bytes"); + self.pc += 8; + f64::from_le_bytes(bytes) + } + + pub fn disassemble(&mut self) -> String { + self.disassemble_impl(false) + } + + pub fn disassemble_colored(&mut self) -> String { + self.disassemble_impl(true) + } + + fn disassemble_impl(&mut self, color: bool) -> String { + let mut out = String::new(); + if color { + let _ = writeln!(out, "{}", "=== Bytecode Disassembly ===".bold().white()); + let _ = writeln!( + out, + "{} {}", + "Length:".white(), + format!("{} bytes", self.code.len()).cyan() + ); + } else { + let _ = writeln!(out, "=== Bytecode Disassembly ==="); + let _ = writeln!(out, "Length: {} bytes", self.code.len()); + } + + while self.pc < self.code.len() { + let start_pos = self.pc; + let op_byte = self.read_u8(); + let (mnemonic, args) = self.decode_instruction(op_byte, start_pos); + + let bytes_slice = &self.code[start_pos + 1..self.pc]; + let mut chunks = bytes_slice.chunks(4); + + let first_chunk = chunks.next().unwrap_or(&[]); + let bytes_str = { + let mut temp = format!("{:02x}", self.code[start_pos]); + for b in first_chunk { + let _ = write!(&mut temp, " {:02x}", b); + } + temp + }; + + if color { + let sep = if args.is_empty() { "" } else { " " }; + let _ = writeln!( + out, + "{} {:<14} | {}{}{}", + format!("{:04x}", start_pos).dimmed(), + bytes_str.green(), + mnemonic.yellow().bold(), + sep, + args.cyan() + ); + } else { + let op_str = if args.is_empty() { + mnemonic.to_string() + } else { + format!("{} {}", mnemonic, args) + }; + let _ = writeln!(out, "{:04x} {:<14} | {}", start_pos, bytes_str, op_str); + } + + for chunk in chunks { + let bytes_str = { + let mut temp = String::from(" "); + for b in chunk { + let _ = write!(&mut temp, " {:02x}", b); + } + temp + }; + + let extra_width = if start_pos > 0 { + start_pos.ilog2() >> 4 + } else { + 0 + }; + + if color { + let _ = write!(out, " "); + for _ in 0..extra_width { + let _ = write!(out, " "); + } + let _ = writeln!(out, " {:<14} |", bytes_str.green()); + } else { + let _ = write!(out, " "); + for _ in 0..extra_width { + let _ = write!(out, " "); + } + let _ = writeln!(out, " {:<14} |", bytes_str); + } + } + } + out + } + + fn decode_instruction(&mut self, op_byte: u8, current_pc: usize) -> (&'static str, String) { + let op = Op::try_from_primitive(op_byte).expect("invalid op code"); + + match op { + Op::PushSmi => { + let val = self.read_i32(); + ("PushSmi", format!("{}", val)) + } + Op::PushBigInt => { + let val = self.read_i64(); + ("PushBigInt", format!("{}", val)) + } + Op::PushFloat => { + let val = self.read_f64(); + ("PushFloat", format!("{}", val)) + } + Op::PushString => { + let idx = self.read_u32(); + let s = self.ctx.resolve_string(idx); + let len = s.len(); + let mut s_fmt = format!("{:?}", s); + if s_fmt.len() > 60 { + s_fmt.truncate(57); + #[allow(clippy::unwrap_used)] + write!(s_fmt, "...\" (total {len} bytes)").unwrap(); + } + ("PushString", format!("@{} {}", idx, s_fmt)) + } + Op::PushNull => ("PushNull", String::new()), + Op::PushTrue => ("PushTrue", String::new()), + Op::PushFalse => ("PushFalse", String::new()), + + Op::LoadLocal => { + let idx = self.read_u32(); + ("LoadLocal", format!("[{}]", idx)) + } + Op::LoadOuter => { + let depth = self.read_u8(); + let idx = self.read_u32(); + ("LoadOuter", format!("depth={} [{}]", depth, idx)) + } + Op::StoreLocal => { + let idx = self.read_u32(); + ("StoreLocal", format!("[{}]", idx)) + } + Op::AllocLocals => { + let count = self.read_u32(); + ("AllocLocals", format!("count={}", count)) + } + + Op::MakeThunk => { + let offset = self.read_u32(); + ("MakeThunk", format!("-> {:04x}", offset)) + } + Op::MakeClosure => { + let offset = self.read_u32(); + let slots = self.read_u32(); + ("MakeClosure", format!("-> {:04x} slots={}", offset, slots)) + } + Op::MakePatternClosure => { + let offset = self.read_u32(); + let slots = self.read_u32(); + let req_count = self.read_u16(); + let opt_count = self.read_u16(); + let ellipsis = self.read_u8() != 0; + + let mut arg_str = format!( + "-> {:04x} slots={} req={} opt={} ...={})", + offset, slots, req_count, opt_count, ellipsis + ); + + arg_str.push_str(" Args=["); + for _ in 0..req_count { + let idx = self.read_u32(); + arg_str.push_str(&format!("Req({}) ", self.ctx.resolve_string(idx))); + } + for _ in 0..opt_count { + let idx = self.read_u32(); + arg_str.push_str(&format!("Opt({}) ", self.ctx.resolve_string(idx))); + } + + let total_args = req_count + opt_count; + for _ in 0..total_args { + let _name_idx = self.read_u32(); + let _span_id = self.read_u32(); + } + arg_str.push(']'); + + ("MakePatternClosure", arg_str) + } + + Op::Call => ("Call", String::new()), + + Op::MakeAttrs => { + let count = self.read_u32(); + ("MakeAttrs", format!("size={}", count)) + } + Op::MakeEmptyAttrs => ("MakeEmptyAttrs", String::new()), + + Op::Select => { + let path_len = self.read_u16(); + let span_id = self.read_u32(); + ("Select", format!("path_len={} span={}", path_len, span_id)) + } + Op::SelectDefault => { + let path_len = self.read_u16(); + let span_id = self.read_u32(); + ( + "SelectDefault", + format!("path_len={} span={}", path_len, span_id), + ) + } + Op::HasAttr => { + let path_len = self.read_u16(); + ("HasAttr", format!("path_len={}", path_len)) + } + + Op::MakeList => { + let count = self.read_u32(); + ("MakeList", format!("size={}", count)) + } + Op::MakeEmptyList => ("MakeEmptyList", String::new()), + + Op::OpAdd => ("OpAdd", String::new()), + Op::OpSub => ("OpSub", String::new()), + Op::OpMul => ("OpMul", String::new()), + Op::OpDiv => ("OpDiv", String::new()), + Op::OpEq => ("OpEq", String::new()), + Op::OpNeq => ("OpNeq", String::new()), + Op::OpLt => ("OpLt", String::new()), + Op::OpGt => ("OpGt", String::new()), + Op::OpLeq => ("OpLeq", String::new()), + Op::OpGeq => ("OpGeq", String::new()), + Op::OpConcat => ("OpConcat", String::new()), + Op::OpUpdate => ("OpUpdate", String::new()), + Op::OpNeg => ("OpNeg", String::new()), + Op::OpNot => ("OpNot", String::new()), + + Op::JumpIfFalse => { + let offset = self.read_i32(); + let target = (current_pc as isize + 1 + 4 + offset as isize) as usize; + ( + "JumpIfFalse", + format!("-> {:04x} offset={}", target, offset), + ) + } + Op::JumpIfTrue => { + let offset = self.read_i32(); + let target = (current_pc as isize + 1 + 4 + offset as isize) as usize; + ("JumpIfTrue", format!("-> {:04x} offset={}", target, offset)) + } + Op::Jump => { + let offset = self.read_i32(); + let target = (current_pc as isize + 1 + 4 + offset as isize) as usize; + ("Jump", format!("-> {:04x} offset={}", target, offset)) + } + + Op::ConcatStrings => { + let count = self.read_u16(); + let force = self.read_u8(); + ("ConcatStrings", format!("count={} force={}", count, force)) + } + Op::ResolvePath => ("ResolvePath", String::new()), + Op::Assert => { + let raw_idx = self.read_u32(); + let span_id = self.read_u32(); + ("Assert", format!("text_id={} span={}", raw_idx, span_id)) + } + Op::PushWith => ("PushWith", String::new()), + Op::PopWith => ("PopWith", String::new()), + Op::WithLookup => { + let idx = self.read_u32(); + let name = self.ctx.resolve_string(idx); + ("WithLookup", format!("{:?}", name)) + } + + Op::LoadBuiltins => ("LoadBuiltins", String::new()), + Op::LoadBuiltin => { + let id = self.read_u8(); + ("LoadBuiltin", format!("id={}", id)) + } + Op::MkPos => { + let span_id = self.read_u32(); + ("MkPos", format!("id={}", span_id)) + } + Op::LoadReplBinding => { + let idx = self.read_u32(); + let name = self.ctx.resolve_string(idx); + ("LoadReplBinding", format!("{:?}", name)) + } + Op::LoadScopedBinding => { + let idx = self.read_u32(); + let name = self.ctx.resolve_string(idx); + ("LoadScopedBinding", format!("{:?}", name)) + } + Op::Return => ("Return", String::new()), + Op::Illegal => ("Illegal", String::new()), + } + } +} diff --git a/fix-codegen/src/lib.rs b/fix-codegen/src/lib.rs index 66c950b..af9eab2 100644 --- a/fix-codegen/src/lib.rs +++ b/fix-codegen/src/lib.rs @@ -8,6 +8,8 @@ use num_enum::TryFromPrimitive; use rnix::TextRange; use string_interner::Symbol as _; +pub mod disassembler; + pub struct InstructionPtr(pub usize); pub trait BytecodeContext { diff --git a/fix-vm/src/lib.rs b/fix-vm/src/lib.rs index 76d14ab..899975c 100644 --- a/fix-vm/src/lib.rs +++ b/fix-vm/src/lib.rs @@ -11,9 +11,7 @@ use num_enum::TryFromPrimitive; use smallvec::SmallVec; use string_interner::Symbol as _; mod boxing; -mod stack; mod value; -use stack::Stack; use value::*; mod helpers; use helpers::*; @@ -116,16 +114,14 @@ pub struct Vm { error_context: Vec, ctx: C, pc: usize, - fuel: usize, force_mode: ForceMode, } #[derive(Collect)] #[collect(no_drop)] struct GcRoot<'gc> { - stack: Stack<65536, Value<'gc>>, - temp_stack: Vec>, - frames: Stack<8192, CallFrame<'gc>>, + stack: Vec>, + call_stack: Vec>, with_env: Option>>, builtins: Value<'gc>, empty_list: Value<'gc>, @@ -134,7 +130,6 @@ struct GcRoot<'gc> { current_env: Option>, } -// TODO: refactor fn init_builtins<'gc>(mc: &Mutation<'gc>, ctx: &mut impl VmContext) -> Value<'gc> { let mut entries = SmallVec::with_capacity(BUILTINS.len()); @@ -148,7 +143,6 @@ fn init_builtins<'gc>(mc: &Mutation<'gc>, ctx: &mut impl VmContext) -> Value<'gc let consts = [ ( "__currentSystem", - // FIXME: detect currentSystem Value::new_gc(Gc::new(mc, NixString::new("x86_64-linux"))), ), ("__langVersion", Value::new_inline(6i32)), @@ -162,7 +156,6 @@ fn init_builtins<'gc>(mc: &Mutation<'gc>, ctx: &mut impl VmContext) -> Value<'gc ), ( "__nixPath", - // FIXME: get from config Value::new_gc(Gc::new( mc, List { @@ -181,7 +174,6 @@ fn init_builtins<'gc>(mc: &Mutation<'gc>, ctx: &mut impl VmContext) -> Value<'gc entries.push((name, val)); } - // Self-reference thunk for builtins.builtins 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))); @@ -196,9 +188,8 @@ impl<'gc> GcRoot<'gc> { fn new(mc: &Mutation<'gc>, ctx: &mut impl VmContext) -> Self { let builtins = init_builtins(mc, ctx); GcRoot { - stack: Stack::new(), - temp_stack: Vec::new(), - frames: Stack::new(), + stack: Vec::with_capacity(8192), + call_stack: Vec::with_capacity(1024), with_env: None, builtins, empty_list: Value::new_gc(Gc::new(mc, List::default())), @@ -215,7 +206,7 @@ impl<'gc> GcRoot<'gc> { #[inline(always)] fn push_stack(&mut self, val: Value<'gc>) { - self.stack.push(val).expect("stack overflow"); + self.stack.push(val); } #[inline(always)] @@ -224,7 +215,24 @@ impl<'gc> GcRoot<'gc> { } #[inline(always)] - #[track_caller] + 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() @@ -243,13 +251,14 @@ struct ErrorFrame { #[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, - Return, Done(Result), } @@ -302,244 +311,254 @@ macro_rules! try_vm { ($self:ident; $expr:expr) => { match $expr { Ok(v) => v, - Err(e) => return Vm::handle_vm_error($self, e), + Err(e) => return GcRoot::handle_vm_error($self, e), } }; } impl Vm { - const DEFAULT_FUEL_AMOUNT: usize = 2048; - 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, - fuel: Self::DEFAULT_FUEL_AMOUNT, error_context: Vec::new(), } } - #[inline(always)] - fn read_array(&mut self) -> [u8; N] { - let ret = self.ctx.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(string_interner::symbol::SymbolU32::try_from_usize(raw as usize).unwrap()) - } - - fn read_operand(&mut self) -> OperandData { - let tag = self.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 = self.read_u32(); - #[allow(clippy::unwrap_used)] - OperandData::Const(self.ctx.get_const(id)) - } - OperandType::Local => { - let layer = self.read_u8(); - let idx = self.read_u32(); - OperandData::Local { layer, idx } - } - OperandType::Builtins => OperandData::Builtins, - OperandType::BigInt => { - let val = self.read_i64(); - OperandData::BigInt(val) - } - } - } - - 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(); - 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(self.read_string_id())), - AttrKeyType::Dynamic => keys.push(SelectKeyData::Dynamic), - }; - } - keys - } - - #[inline(always)] - fn execute_one(&mut self) -> Action { - use fix_codegen::Op::{self, *}; - + pub fn run(mut self) -> Result { const COLLECTOR_GRANULARITY: f64 = 1024.0; - if likely_stable::unlikely(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.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, } - self.fuel = Self::DEFAULT_FUEL_AMOUNT; } - self.fuel -= 1; + } +} - let byte = self.ctx.bytecode()[self.pc]; - if !likely_stable::likely((0..Op::Illegal as u8).contains(&byte)) { - panic!("unknown opcode: {byte:#04x}") +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) + } + } + }}; + ($type:ty) => { + <$type>::from_le_bytes(read_array(ctx.bytecode(), pc)) + }; } - let op = unsafe { std::mem::transmute::(byte) }; - // let Ok(op) = Op::try_from_primitive(self.ctx.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)), + 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, + }); - LoadLocal => { - let idx = self.read_u32() as usize; - self.arena - .mutate_root(|_, root| root.push_stack(root.env().borrow().locals[idx])) + *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::Blackhole => { + return Action::Done(Err(Error::eval_error( + "infinite recursion encountered", + ))); + } + ThunkState::Apply { .. } => todo!("force apply"), + } + } + }}; } - LoadOuter => { - let layer = self.read_u8(); - let idx = self.read_u32() as usize; - self.arena.mutate_root(|_, root| { - let mut cur = root.env(); + + 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]; - 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() + 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 = self.read_u32(); - self.arena.mutate_root(|mc, root| { + MakeThunk => { + let entry_point = read!(u32); let thunk = Gc::new( mc, RefLock::new(ThunkState::Pending { ip: entry_point as usize, - env: root.env(), - with_env: root.with_env, + env: self.env(), + with_env: self.with_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| { + 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: root.env(), + env: self.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; + 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(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)); - } + 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)); + } - self.arena.mutate_root(|mc, root| { let pattern = Gc::new( mc, PatternInfo { @@ -554,20 +573,18 @@ impl Vm { Closure { ip: entry_point, n_locals, - env: root.env(), + env: self.env(), pattern: Some(pattern), }, ); - root.push_stack(Value::new_gc(closure)); - }) - } + self.push_stack(Value::new_gc(closure)); + } - Call => { - // force func - self.force_tos(); - self.arena.mutate_root(|mc, root| { - let func = root.pop_stack(); - let arg = root.pop_stack(); + Call => { + // force func + try_force!(0, inst_start_pc); + let func = self.pop_stack(); + let arg = self.pop_stack(); if let Some(closure) = func.as_gc::() { let ip = closure.ip; let n_locals = closure.n_locals; @@ -577,1044 +594,675 @@ impl Vm { } 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(), - with_env: root.with_env, - }) - .expect("frame stack overflow"); - self.pc = ip as usize; - root.current_env = Some(new_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 = 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 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(self.read_string_id()), - AttrKeyType::Dynamic => AttrKeyData::Dynamic(self.read_operand()), - }; - let val = self.read_operand(); - let _span_id = self.read_u32(); - entries.push(AttrEntry { key, val }); } - self.arena.mutate_root(|mc, root| { + + 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, root); + let v = op.resolve(mc, self); v.as_inline::() .expect("dynamic attr key must be a string") } }; - let val = entry.val.resolve(mc, root); + 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)); - 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) => self.ctx.intern_string(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 Vm::handle_vm_error(self, e); - } - Ok(false) => { - self.arena - .mutate_root(|_, root| root.temp_stack.truncate(temp_base)); - let name = self.ctx.resolve_string(key_sid); - return Vm::handle_vm_error( - self, - vm_err(format!("attribute '{name}' missing")), - ); - } - Ok(true) => { - if i < n - 1 { - self.force_tos(); - } - } - } + self.push_stack(Value::new_gc(attrs)); + } + MakeEmptyAttrs => { + self.push_stack(self.empty_attrs); } - 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); + Select => { + let n = read!(u16) as usize; + let _span_id = read!(u32); - 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); + let mut keys = SmallVec::<[SelectKeyData; 4]>::with_capacity(n); + 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), + }; } - base - }); - // Force the expr (now TOS) - self.force_tos(); + let dyn_count = keys + .iter() + .filter(|k| matches!(k, SelectKeyData::Dynamic)) + .count(); - 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) => self.ctx.intern_string(s), + // 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; } } - }; + } - let found = self.arena.mutate_root(|_, root| { - let val = root.pop_stack(); - if let Some(attrset) = val.as_gc::() - && let Some(v) = attrset.lookup(key_sid) - { - root.push_stack(v); - return true; - } - // Not a set or key missing: use default - false - }); + // 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 mut keys = SmallVec::<[SelectKeyData; 4]>::with_capacity(n); + 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), + }; + } + + 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; + } - if !found { use_default = true; break; } - if i < n - 1 { - self.force_tos(); + 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); } } - - 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 = read!(u16) as usize; + todo!("HasAttr"); } - } - 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| { + MakeList => { + let count = read!(u32) as usize; let mut items: SmallVec<[Value; 4]> = SmallVec::with_capacity(count); - for op in &operands { - items.push(op.resolve(mc, root)); + for _ in 0..count { + items.push(read!(OperandData).resolve(mc, self)); } let list = Gc::new(mc, List { inner: items }); - root.push_stack(Value::new_gc(list)); - }); - } - MakeEmptyList => { - self.push_empty_list(); - } - - OpAdd => { - self.force_n(2); - let res = 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.ctx.get_string(lhs), self.ctx.get_string(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(()) - }); - try_vm!(self; res); - } - OpSub | OpMul => { - self.force_n(2); - #[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 = 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, func.0, func.1)?; - root.push_stack(res); - VmResult::Ok(()) - }); - try_vm!(self; res); - } - OpDiv => { - self.force_n(2); - let res = self.arena.mutate_root(|mc, root| { - let rhs = root.pop_stack_forced(); - let lhs = root.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 = Self::numeric_binop(lhs, rhs, mc, |a, b| a / b, |a, b| a / b)?; - root.push_stack(res); - VmResult::Ok(()) - }); - try_vm!(self; res); - } - OpEq | OpNeq => { - self.force_n(2); - let map: fn(bool) -> bool = match op { - OpEq => |a| a, - OpNeq => |a| !a, - _ => unreachable!(), - }; - let eq = try_vm!(self; self.values_equal()); - self.push_stack(|_| Value::new_inline(map(eq))); - } - OpLt | OpGt | OpLeq | OpGeq => { - use std::cmp::Ordering; - self.force_n(2); - 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(pred)); - } - OpConcat => { - self.force_n(2); - let res = self.arena.mutate_root(|mc, root| { - let rhs = root.pop_stack_forced(); - let lhs = root.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); - root.push_stack(Value::new_gc(Gc::new(mc, List { inner: items }))); - VmResult::Ok(()) - }); - try_vm!(self; res); - } - OpUpdate => { - self.force_n(2); - let res = self.arena.mutate_root(|mc, root| { - let rhs = root.pop_stack_forced(); - let lhs = root.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")); - }; - root.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 = 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()); + self.push_stack(Value::new_gc(list)); + } + MakeEmptyList => { + self.push_stack(self.empty_list); } - 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)"); - } + 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); - PushWith => { - self.arena.mutate_root(|mc, root| { - let env = root.pop_stack(); + #[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: root.with_env, + prev: self.with_env, }, ); - root.with_env = Some(scope); - }); - } - PopWith => self.arena.mutate_root(|_, root| { - let Some(scope) = root.with_env else { - unreachable!("no with_scope to pop"); - }; - root.with_env = scope.prev; - }), - WithLookup => { - let name = self.read_string_id(); - let mut depth = 0; + 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); - // FIXME: this implementation is garbage - loop { - let found_scope = self.arena.mutate_root(|_, root| { - let mut cur = root.with_env; - for _ in 0..depth { - if let Some(s) = cur { - cur = s.prev; - } else { - break; - } - } + let mut cur = self.with_env; + let mut found_val = None; - if let Some(scope) = cur { - root.push_stack(scope.env); - true - } else { - false - } - }); - - if !found_scope { - let name_str = self.ctx.resolve_string(name); - return Vm::handle_vm_error( - self, - vm_err(format!("undefined variable '{name_str}'")), - ); - } - - if let err @ Action::Done(Err(_)) = self.force_tos() { - return err; - } - - let lookup_result = self.arena.mutate_root(|_, root| { - let val = root.pop_stack(); - let Some(attrs) = val.as_gc::() else { - return Err(vm_err("value in 'with' scope must be a set")); + 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) { - root.push_stack(v); - Ok(true) // Found it - } else { - Ok(false) // Not in this scope, try the next one - } - }); - - match lookup_result { - Ok(true) => break, // Successfully resolved and pushed to stack - Ok(false) => depth += 1, // Move to the parent 'with' scope - Err(e) => return Vm::handle_vm_error(self, e), - } - } - } - - 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(); - } - - Illegal => unreachable!(), - } - - Action::Continue - } - - #[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")), - } - } - - #[inline] - fn values_equal(&mut self) -> VmResult { - enum State { - Bool(bool), - List(usize), - AttrSet(usize), - } - - 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)) = (get_num(lhs), 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.ctx.get_string(lhs), self.ctx.get_string(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().expect("stack underflow"); - let x = root.temp_stack.pop().expect("stack underflow"); - 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) - } - } - } - - #[inline] - fn compare_values(&mut self, pred: impl FnOnce(std::cmp::Ordering) -> bool) -> VmResult<()> { - self.arena.mutate_root(|_, root| { - let rhs = root.pop_stack_forced(); - let lhs = root.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), - }; - root.push_stack(Value::new_inline(pred(ord))); - return VmResult::Ok(()); - } - if let (Some(a), Some(b)) = (self.ctx.get_string(lhs), self.ctx.get_string(rhs)) { - root.push_stack(Value::new_inline(pred(a.cmp(b)))); - return VmResult::Ok(()); - } - Err(vm_err("cannot compare these types")) - })?; - Ok(()) - } - - pub fn run(mut self) -> Result { - self.arena.mutate_root(|mc, root| { - if root.current_env.is_none() { - root.current_env = Some(Gc::new(mc, RefLock::new(Env::empty()))); - } - }); - - loop { - match self.execute_one() { - Action::Continue => (), - Action::Return => (), - Action::Done(done) => break done, - } - } - } - - #[inline(always)] - 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)] - fn push_empty_list(&mut self) { - self.arena.mutate_root(|_, root| { - root.push_stack(root.empty_list); - }); - } - - #[inline(always)] - fn push_empty_attrs(&mut self) { - self.arena.mutate_root(|_, root| { - root.push_stack(root.empty_attrs); - }); - } - - #[inline(always)] - fn push_builtins(&mut self) { - self.arena.mutate_root(|_, root| { - root.push_stack(root.builtins); - }); - } - - #[inline] - fn force_tos(&mut self) -> Action { - loop { - let (run, target_depth) = 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, 0); - }; - let ret = match *thunk_state.borrow() { - ThunkState::Pending { ip, env, with_env } => { - root.frames - .push(CallFrame { - pc: self.pc, - env: root.env(), - with_env: root.with_env, - }) - .expect("call stack overflow"); - self.pc = ip; - root.current_env = Some(env); - root.with_env = with_env; - (true, root.frames.len()) - } - ThunkState::Apply { .. } => todo!("force_tos"), - ThunkState::Evaluated(val) => { - *thunk = val; - (false, 0) - } - ThunkState::Blackhole => todo!("force_tos"), - }; - if ret.0 { - *thunk_state.borrow_mut(mc) = ThunkState::Blackhole; - } - ret - }); - if likely_stable::likely(!run) { - return Action::Continue; - } - loop { - match self.execute_one() { - Action::Continue => (), - Action::Return => { - let current_depth = self.arena.mutate_root(|_, root| root.frames.len()); - if current_depth < target_depth { + 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}'"))); } - // FIXME: poison thunk - Action::Done(err @ Err(_)) => return Action::Done(err), - Action::Done(Ok(_)) => unreachable!(), } + + 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!(), } - 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; - }); } } #[inline] - 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); + 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, }); - self.force_tos(); } + 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 handle_return(&mut self) -> Action { - self.force_tos(); - let done = self.arena.mutate_root(|_, root| { - let Some(frame) = root.frames.pop() else { - return true; + 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.pc = frame.pc; - root.current_env = Some(frame.env); - false - }); - if !done { - return Action::Return; + self.push_stack(Value::new_inline(pred(ord))); + return Ok(()); } - 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_deep() { - return done; - } - } + 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(()); } - let val = self.arena.mutate_root(|_, root| { - root.current_env = None; - self.ctx.convert_value(root.pop_stack()) - }); - Action::Done(Ok(val)) + Err(vm_err("cannot compare these types")) } - 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| { - #[allow(clippy::unwrap_used)] - let list = root.pop_stack().as_gc::().unwrap(); - for &item in list.inner.iter() { - root.temp_stack.push(item); + #[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", + ))); + } + } + } } - 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| { - #[allow(clippy::unwrap_used)] - 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 { - #[allow(clippy::unwrap_used)] - 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, AttrSet::from_sorted_unchecked(kv)); - root.push_stack(Value::new_gc(new_attrs)); - }); + self.current_env = Some(env); + self.with_env = with_env; + return None; } - - Action::Continue - } - - 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| { - #[allow(clippy::unwrap_used)] - 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| { - #[allow(clippy::unwrap_used)] - 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 { - #[allow(clippy::unwrap_used)] - 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, AttrSet::from_sorted_unchecked(kv)); - root.push_stack(Value::new_gc(new_attrs)); - }); - } - - Action::Continue + // 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 { @@ -1626,3 +1274,24 @@ impl Vm { } } } + +#[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")), + } +} diff --git a/fix-vm/src/stack.rs b/fix-vm/src/stack.rs deleted file mode 100644 index 41da55a..0000000 --- a/fix-vm/src/stack.rs +++ /dev/null @@ -1,76 +0,0 @@ -use std::mem::MaybeUninit; - -use gc_arena::Collect; - -pub(super) struct Stack { - inner: Box<[MaybeUninit; N]>, - len: usize, -} - -unsafe impl<'gc, const N: usize, T: Collect<'gc> + 'gc> Collect<'gc> for Stack { - const NEEDS_TRACE: bool = T::NEEDS_TRACE; - fn trace>(&self, cc: &mut U) { - for item in self.inner[..self.len].iter() { - unsafe { - item.assume_init_ref().trace(cc); - } - } - } -} - -impl Default for Stack { - fn default() -> Self { - Self::new() - } -} - -impl Stack { - pub(super) fn new() -> Self { - Self { - inner: Box::new([const { MaybeUninit::uninit() }; N]), - len: 0, - } - } - - pub(super) fn push(&mut self, val: T) -> Result<(), T> { - if self.len == N { - return Err(val); - } - unsafe { - self.inner.get_unchecked_mut(self.len).write(val); - } - self.len += 1; - Ok(()) - } - - pub(super) fn pop(&mut self) -> Option { - if self.len == 0 { - return None; - } - let ret = unsafe { - self.inner - .get_unchecked_mut(self.len - 1) - .assume_init_read() - }; - self.len -= 1; - Some(ret) - } - - pub(super) fn tos(&self) -> Option<&T> { - if self.len == 0 { - return None; - } - Some(unsafe { self.inner.get_unchecked(self.len - 1).assume_init_ref() }) - } - - pub(super) fn tos_mut(&mut self) -> Option<&mut T> { - if self.len == 0 { - return None; - } - Some(unsafe { self.inner.get_unchecked_mut(self.len - 1).assume_init_mut() }) - } - - pub(super) fn len(&self) -> usize { - self.len - } -} diff --git a/fix-vm/src/value.rs b/fix-vm/src/value.rs index 3ac07d9..106f276 100644 --- a/fix-vm/src/value.rs +++ b/fix-vm/src/value.rs @@ -263,11 +263,11 @@ impl<'gc> Value<'gc> { } #[inline] - pub(crate) fn restrict(self) -> Option> { - if !self.is::>() { - Some(StrictValue(self)) + pub(crate) fn restrict(self) -> Result, Gc<'gc, Thunk<'gc>>> { + if let Some(thunk) = self.as_gc::>() { + Err(thunk) } else { - None + Ok(StrictValue(self)) } } } @@ -462,7 +462,7 @@ pub(crate) enum ThunkState<'gc> { arg: Value<'gc>, }, Blackhole, - Evaluated(Value<'gc>), + Evaluated(StrictValue<'gc>), } #[derive(Collect, Debug)] diff --git a/fix/src/lib.rs b/fix/src/lib.rs index 7cb4c60..cabd6b6 100644 --- a/fix/src/lib.rs +++ b/fix/src/lib.rs @@ -2,6 +2,7 @@ #![allow(dead_code)] use bumpalo::Bump; +use fix_codegen::disassembler::{Disassembler, DisassemblerContext}; use fix_codegen::{BytecodeContext, InstructionPtr}; use fix_common::{StringId, Symbol}; use fix_error::{Error, Result, Source}; @@ -10,7 +11,7 @@ use fix_ir::{Ir, IrRef, RawIrRef, ThunkId}; use fix_vm::{ForceMode, StaticValue, Vm, VmContext}; use ghost_cell::{GhostCell, GhostToken}; use hashbrown::{HashMap, HashSet}; -use string_interner::DefaultStringInterner; +use string_interner::{DefaultStringInterner, Symbol as _}; // mod fetcher; // mod nar; @@ -105,6 +106,10 @@ impl Evaluator { Ok(ip) } + pub fn disassemble_colored(&self, ip: InstructionPtr) -> String { + Disassembler::new(ip, self).disassemble_colored() + } + fn downgrade_ctx<'a, 'bump, 'id>( &'a mut self, bump: &'bump Bump, @@ -553,3 +558,15 @@ impl BytecodeContext for Evaluator { self.constants.insert(val) } } + +impl DisassemblerContext for Evaluator { + fn get_code(&self) -> &[u8] { + &self.bytecode + } + + #[allow(clippy::unwrap_used)] + fn resolve_string(&self, id: u32) -> &str { + let id = string_interner::symbol::SymbolU32::try_from_usize(id as usize).unwrap(); + self.strings.resolve(id).unwrap() + } +} diff --git a/fix/src/main.rs b/fix/src/main.rs index 674902e..a154bf2 100644 --- a/fix/src/main.rs +++ b/fix/src/main.rs @@ -40,7 +40,7 @@ struct ExprSource { file: Option, } -fn run_compile(eval: &mut Evaluator, src: ExprSource, _silent: bool) -> Result<()> { +fn run_compile(eval: &mut Evaluator, src: ExprSource, silent: bool) -> Result<()> { let src = if let Some(expr) = src.expr { Source::new_eval(expr)? } else if let Some(file) = src.file { @@ -49,10 +49,10 @@ fn run_compile(eval: &mut Evaluator, src: ExprSource, _silent: bool) -> Result<( unreachable!() }; match eval.compile_bytecode(src) { - Ok(_ip) => { - // if !silent { - // println!("{}", eval.disassemble_colored(ip)); - // } + Ok(ip) => { + if !silent { + println!("{}", eval.disassemble_colored(ip)); + } } Err(err) => { eprintln!("{:?}", miette::Report::new(*err));