diff --git a/Cargo.lock b/Cargo.lock index 6a66e0f..080622b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -442,7 +442,7 @@ dependencies = [ "ciborium", "clap", "criterion-plot", - "itertools 0.13.0", + "itertools", "num-traits", "oorandom", "page_size", @@ -462,7 +462,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8d80a2f4f5b554395e47b5d8305bc3d27813bacb73493eb1001e8f76dae29ea" dependencies = [ "cast", - "itertools 0.13.0", + "itertools", ] [[package]] @@ -804,7 +804,6 @@ dependencies = [ "ghost-cell", "hashbrown 0.16.1", "hex", - "itertools 0.14.0", "md5", "miette", "mimalloc", @@ -1380,15 +1379,6 @@ dependencies = [ "either", ] -[[package]] -name = "itertools" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" -dependencies = [ - "either", -] - [[package]] name = "itoa" version = "1.0.17" diff --git a/fix/Cargo.toml b/fix/Cargo.toml index 7e81134..72ec57c 100644 --- a/fix/Cargo.toml +++ b/fix/Cargo.toml @@ -42,8 +42,6 @@ bumpalo = { version = "3.20", features = [ rust-embed = "8.11" -itertools = "0.14" - regex = "1.11" nix-nar = "0.3" diff --git a/fix/src/boxing.rs b/fix/src/boxing.rs index 7bd70bb..d5e80d2 100644 --- a/fix/src/boxing.rs +++ b/fix/src/boxing.rs @@ -459,6 +459,12 @@ impl RawBox { pub(crate) fn into_float_unchecked(self) -> f64 { unsafe { self.float } } + + #[inline] + #[must_use] + pub(crate) fn to_bits(self) -> u64 { + unsafe { self.bits } + } } impl fmt::Debug for RawBox { diff --git a/fix/src/codegen.rs b/fix/src/codegen.rs index 9ca1375..d8d9882 100644 --- a/fix/src/codegen.rs +++ b/fix/src/codegen.rs @@ -7,8 +7,10 @@ use rnix::TextRange; use string_interner::Symbol as _; use crate::ir::{ArgId, Attr, BinOpKind, Ir, Param, RawIrRef, StringId, ThunkId, UnOpKind}; +use crate::runtime::{BUILTINS, BuiltinId}; +use crate::runtime::value::{Null, PrimOp, StaticValue}; -pub(crate) struct InstructionPtr(pub usize); +pub struct InstructionPtr(pub(crate) usize); #[derive(Collect)] #[collect(require_static)] @@ -22,6 +24,7 @@ pub(crate) trait BytecodeContext { fn register_span(&mut self, range: TextRange) -> u32; fn get_code(&self) -> &[u8]; fn get_code_mut(&mut self) -> &mut Vec; + fn add_constant(&mut self, val: crate::runtime::value::StaticValue) -> u32; } #[repr(u8)] @@ -49,13 +52,13 @@ pub enum Op { CallNoSpan, MakeAttrs, - MakeAttrsDyn, MakeEmptyAttrs, Select, SelectDefault, HasAttr, MakeList, + MakeEmptyList, OpAdd, OpSub, @@ -73,7 +76,6 @@ pub enum Op { OpNeg, OpNot, - ForceBool, JumpIfFalse, JumpIfTrue, Jump, @@ -109,6 +111,21 @@ struct BytecodeEmitter<'a, Ctx: BytecodeContext> { scope_stack: Vec, } +pub(crate) const OPERAND_CONST: u8 = 0; +pub(crate) const OPERAND_LOCAL: u8 = 1; +pub(crate) const OPERAND_BUILTINS: u8 = 2; +pub(crate) const OPERAND_BIGINT: u8 = 3; + +pub(crate) const KEY_STATIC: u8 = 0; +pub(crate) const KEY_DYNAMIC: u8 = 1; + +enum InlineOperand { + Const(StaticValue), + Local { layer: u16, local: u32 }, + Builtins, + BigInt(i64), +} + pub(crate) fn compile_bytecode(ir: RawIrRef<'_>, ctx: &mut impl BytecodeContext) -> InstructionPtr { let ip = ctx.get_code().len(); let mut emitter = BytecodeEmitter::new(ctx); @@ -124,6 +141,61 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> { } } + fn classify_value(&mut self, ir: RawIrRef<'_>) -> InlineOperand { + match ir.deref() { + &Ir::Int(x) => { + if x <= i32::MAX as i64 { + InlineOperand::Const(StaticValue::new_inline(x as i32)) + } else { + InlineOperand::BigInt(x) + } + } + &Ir::Float(x) => InlineOperand::Const(StaticValue::new_float(x)), + &Ir::Bool(b) => InlineOperand::Const(StaticValue::new_inline(b)), + Ir::Null => InlineOperand::Const(StaticValue::new_inline(Null)), + Ir::Str(s) => { + let sid = self.ctx.intern_string(s.deref()); + InlineOperand::Const(StaticValue::new_inline(sid)) + } + &Ir::Thunk(id) => { + let (layer, local) = self.resolve_thunk(id); + InlineOperand::Local { layer, local } + } + &Ir::Arg(id) => { + let (layer, local) = self.resolve_arg(id); + InlineOperand::Local { layer, local } + } + &Ir::Builtin(id) => { + let arity = BUILTINS[id as usize].1; + InlineOperand::Const(StaticValue::new_inline(PrimOp { id, arity })) + } + Ir::Builtins => InlineOperand::Builtins, + _ => panic!("cannot classify IR node as inline operand"), + } + } + + fn emit_inline_operand(&mut self, operand: InlineOperand) { + match operand { + InlineOperand::Const(val) => { + let idx = self.ctx.add_constant(val); + self.emit_u8(OPERAND_CONST); + self.emit_u32(idx); + } + InlineOperand::Local { layer, local } => { + self.emit_u8(OPERAND_LOCAL); + self.emit_u8(layer as u8); + self.emit_u32(local); + } + InlineOperand::Builtins => { + self.emit_u8(OPERAND_BUILTINS); + } + InlineOperand::BigInt(val) => { + self.emit_u8(OPERAND_BIGINT); + self.emit_i64(val); + } + } + } + #[inline] fn emit_op(&mut self, op: Op) { self.ctx.get_code_mut().push(op as u8); @@ -382,11 +454,11 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> { fn emit_toplevel(&mut self, ir: RawIrRef<'_>) { match ir.deref() { - Ir::TopLevel { body, thunks } => { - let with_thunk_count = self.count_with_thunks(*body); + &Ir::TopLevel { body, ref thunks } => { + let with_thunk_count = self.count_with_thunks(body); let total_slots = thunks.len() + with_thunk_count; - let all_thunks = self.collect_all_thunks(thunks, *body); + let all_thunks = self.collect_all_thunks(thunks, body); let thunk_ids: Vec = all_thunks.iter().map(|&(id, _)| id).collect(); self.push_scope(false, None, &thunk_ids); @@ -397,7 +469,7 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> { } self.emit_scope_thunks(thunks); - self.emit_expr(*body); + self.emit_expr(body); self.emit_op(Op::Return); self.pop_scope(); @@ -459,7 +531,6 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> { } &Ir::If { cond, consq, alter } => { self.emit_expr(cond); - self.emit_op(Op::ForceBool); self.emit_op(Op::JumpIfFalse); let else_placeholder = self.emit_i32_placeholder(); @@ -504,15 +575,20 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> { self.emit_attrset(stcs, dyns); } Ir::List { items } => { - for &item in items.iter() { - self.emit_expr(item); + if items.is_empty() { + self.emit_op(Op::MakeEmptyList); + } else { + self.emit_op(Op::MakeList); + self.emit_u32(items.len() as u32); + for &item in items.iter() { + let operand = self.classify_value(item); + self.emit_inline_operand(operand); + } } - self.emit_op(Op::MakeList); - self.emit_u32(items.len() as u32); } &Ir::Call { func, arg, span } => { - self.emit_expr(func); self.emit_expr(arg); + self.emit_expr(func); let span_id = self.ctx.register_span(span); self.emit_op(Op::Call); self.emit_u32(span_id); @@ -539,20 +615,29 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> { Ir::Builtins => { self.emit_op(Op::LoadBuiltins); } - &Ir::Builtin(name) => { + &Ir::Builtin(id) => { self.emit_op(Op::LoadBuiltin); - self.emit_u32(name.0.to_usize() as u32); + self.emit_u8(id as u8); + } + &Ir::BuiltinConst(id) => { + self.emit_select( + RawIrRef(&Ir::Builtins), + &[Attr::Str(id, TextRange::default())], + None, + TextRange::default(), + ); } &Ir::ConcatStrings { ref parts, force_string, } => { - for &part in parts.iter() { - self.emit_expr(part); - } self.emit_op(Op::ConcatStrings); self.emit_u16(parts.len() as u16); self.emit_u8(if force_string { 1 } else { 0 }); + for &part in parts.iter() { + let operand = self.classify_value(part); + self.emit_inline_operand(operand); + } } &Ir::HasAttr { lhs, ref rhs } => { self.emit_has_attr(lhs, rhs); @@ -603,13 +688,11 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> { match kind { And => { self.emit_expr(lhs); - self.emit_op(Op::ForceBool); self.emit_op(Op::JumpIfFalse); let skip_placeholder = self.emit_i32_placeholder(); let after_jif = self.ctx.get_code_mut().len(); self.emit_expr(rhs); - self.emit_op(Op::ForceBool); self.emit_op(Op::Jump); let end_placeholder = self.emit_i32_placeholder(); let after_jump = self.ctx.get_code_mut().len(); @@ -624,13 +707,11 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> { } Or => { self.emit_expr(lhs); - self.emit_op(Op::ForceBool); self.emit_op(Op::JumpIfTrue); let skip_placeholder = self.emit_i32_placeholder(); let after_jit = self.ctx.get_code_mut().len(); self.emit_expr(rhs); - self.emit_op(Op::ForceBool); self.emit_op(Op::Jump); let end_placeholder = self.emit_i32_placeholder(); let after_jump = self.ctx.get_code_mut().len(); @@ -645,13 +726,11 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> { } Impl => { self.emit_expr(lhs); - self.emit_op(Op::ForceBool); self.emit_op(Op::JumpIfFalse); let skip_placeholder = self.emit_i32_placeholder(); let after_jif = self.ctx.get_code_mut().len(); self.emit_expr(rhs); - self.emit_op(Op::ForceBool); self.emit_op(Op::Jump); let end_placeholder = self.emit_i32_placeholder(); let after_jump = self.ctx.get_code_mut().len(); @@ -759,40 +838,26 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> { return; } - if !dyns.is_empty() { - for (&sym, &(val, _)) in stcs.iter() { - self.emit_op(Op::PushString); - self.emit_str_id(sym); - self.emit_expr(val); - } - for (_, &(_, span)) in stcs.iter() { - let span_id = self.ctx.register_span(span); - self.emit_op(Op::PushSmi); - self.emit_u32(span_id); - } - for &(key, val, span) in dyns.iter() { - self.emit_expr(key); - self.emit_expr(val); - let span_id = self.ctx.register_span(span); - self.emit_op(Op::PushSmi); - self.emit_u32(span_id); - } - self.emit_op(Op::MakeAttrsDyn); - self.emit_u32(stcs.len() as u32); - self.emit_u32(dyns.len() as u32); - } else { - for (&sym, &(val, _)) in stcs.iter() { - self.emit_op(Op::PushString); - self.emit_str_id(sym); - self.emit_expr(val); - } - for (_, &(_, span)) in stcs.iter() { - let span_id = self.ctx.register_span(span); - self.emit_op(Op::PushSmi); - self.emit_u32(span_id); - } - self.emit_op(Op::MakeAttrs); - self.emit_u32(stcs.len() as u32); + let total = stcs.len() + dyns.len(); + self.emit_op(Op::MakeAttrs); + self.emit_u32(total as u32); + + for (&sym, &(val, span)) in stcs.iter() { + self.emit_u8(KEY_STATIC); + self.emit_str_id(sym); + let val_operand = self.classify_value(val); + self.emit_inline_operand(val_operand); + let span_id = self.ctx.register_span(span); + self.emit_u32(span_id); + } + for &(key, val, span) in dyns.iter() { + self.emit_u8(KEY_DYNAMIC); + let key_operand = self.classify_value(key); + self.emit_inline_operand(key_operand); + let val_operand = self.classify_value(val); + self.emit_inline_operand(val_operand); + let span_id = self.ctx.register_span(span); + self.emit_u32(span_id); } } @@ -805,17 +870,10 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> { ) { self.emit_expr(expr); for attr in attrpath.iter() { - match *attr { - Attr::Str(sym, _) => { - self.emit_op(Op::PushString); - self.emit_str_id(sym); - } - Attr::Dynamic(expr, _) => { - self.emit_expr(expr); - } + if let Attr::Dynamic(expr, _) = *attr { + self.emit_expr(expr); } } - if let Some(default) = default { self.emit_expr(default); let span_id = self.ctx.register_span(span); @@ -828,23 +886,39 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> { self.emit_u16(attrpath.len() as u16); self.emit_u32(span_id); } + for attr in attrpath.iter() { + match *attr { + Attr::Str(sym, _) => { + self.emit_u8(KEY_STATIC); + self.emit_str_id(sym); + } + Attr::Dynamic(_, _) => { + self.emit_u8(KEY_DYNAMIC); + } + } + } } fn emit_has_attr(&mut self, lhs: RawIrRef<'_>, rhs: &[Attr>]) { self.emit_expr(lhs); for attr in rhs.iter() { - match *attr { - Attr::Str(sym, _) => { - self.emit_op(Op::PushString); - self.emit_str_id(sym); - } - Attr::Dynamic(expr, _) => { - self.emit_expr(expr); - } + if let Attr::Dynamic(expr, _) = *attr { + self.emit_expr(expr); } } self.emit_op(Op::HasAttr); self.emit_u16(rhs.len() as u16); + for attr in rhs.iter() { + match *attr { + Attr::Str(sym, _) => { + self.emit_u8(KEY_STATIC); + self.emit_str_id(sym); + } + Attr::Dynamic(_, _) => { + self.emit_u8(KEY_DYNAMIC); + } + } + } } fn emit_with( diff --git a/fix/src/disassembler.rs b/fix/src/disassembler.rs index afd9770..b1bbb1d 100644 --- a/fix/src/disassembler.rs +++ b/fix/src/disassembler.rs @@ -3,70 +3,71 @@ use std::fmt::Write; use colored::Colorize; use num_enum::TryFromPrimitive; -use crate::codegen::{Bytecode, Op}; +use crate::codegen::{InstructionPtr, Op}; pub(crate) trait DisassemblerContext { fn lookup_string(&self, id: u32) -> &str; + fn get_code(&self) -> &[u8]; } pub(crate) struct Disassembler<'a, Ctx> { code: &'a [u8], ctx: &'a Ctx, - pos: usize, + pc: usize, } impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> { - pub fn new(bytecode: &'a Bytecode, ctx: &'a Ctx) -> Self { + pub fn new(ip: InstructionPtr, ctx: &'a Ctx) -> Self { Self { - code: &bytecode.code, + code: ctx.get_code(), ctx, - pos: 0, + pc: ip.0, } } fn read_u8(&mut self) -> u8 { - let b = self.code[self.pos]; - self.pos += 1; + let b = self.code[self.pc]; + self.pc += 1; b } fn read_u16(&mut self) -> u16 { - let bytes = self.code[self.pos..self.pos + 2] + let bytes = self.code[self.pc..self.pc + 2] .try_into() .expect("no enough bytes"); - self.pos += 2; + self.pc += 2; u16::from_le_bytes(bytes) } fn read_u32(&mut self) -> u32 { - let bytes = self.code[self.pos..self.pos + 4] + let bytes = self.code[self.pc..self.pc + 4] .try_into() .expect("no enough bytes"); - self.pos += 4; + self.pc += 4; u32::from_le_bytes(bytes) } fn read_i32(&mut self) -> i32 { - let bytes = self.code[self.pos..self.pos + 4] + let bytes = self.code[self.pc..self.pc + 4] .try_into() .expect("no enough bytes"); - self.pos += 4; + self.pc += 4; i32::from_le_bytes(bytes) } fn read_i64(&mut self) -> i64 { - let bytes = self.code[self.pos..self.pos + 8] + let bytes = self.code[self.pc..self.pc + 8] .try_into() .expect("no enough bytes"); - self.pos += 8; + self.pc += 8; i64::from_le_bytes(bytes) } fn read_f64(&mut self) -> f64 { - let bytes = self.code[self.pos..self.pos + 8] + let bytes = self.code[self.pc..self.pc + 8] .try_into() .expect("no enough bytes"); - self.pos += 8; + self.pc += 8; f64::from_le_bytes(bytes) } @@ -93,62 +94,62 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> { let _ = writeln!(out, "Length: {} bytes", self.code.len()); } - while self.pos < self.code.len() { - let start_pos = self.pos; + 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.pos]; + let bytes_slice = &self.code[start_pos + 1..self.pc]; + let mut chunks = bytes_slice.chunks(4); - for (i, chunk) in bytes_slice.chunks(4).enumerate() { + 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::new(); - if i == 0 { - let _ = write!(&mut temp, "{:02x}", self.code[start_pos]); - } else { - let _ = write!(&mut temp, " "); - } - for b in chunk.iter() { + let mut temp = String::from(" "); + for b in chunk { let _ = write!(&mut temp, " {:02x}", b); } temp }; - if i == 0 { - 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); - } + 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 extra_width = start_pos.ilog2() >> 4; - 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); - } + let _ = write!(out, " "); + for _ in 0..extra_width { let _ = write!(out, " "); } + let _ = writeln!(out, " {:<14} |", bytes_str); } } } @@ -258,14 +259,6 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> { let count = self.read_u32(); ("MakeAttrs", format!("size={}", count)) } - Op::MakeAttrsDyn => { - let static_count = self.read_u32(); - let dyn_count = self.read_u32(); - ( - "MakeAttrsDyn", - format!("static={} dyn={}", static_count, dyn_count), - ) - } Op::MakeEmptyAttrs => ("MakeEmptyAttrs", String::new()), Op::Select => { @@ -290,6 +283,7 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> { let count = self.read_u32(); ("MakeList", format!("size={}", count)) } + Op::MakeEmptyList => ("MakeEmptyList", String::new()), Op::OpAdd => ("OpAdd", String::new()), Op::OpSub => ("OpSub", String::new()), @@ -306,8 +300,6 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> { Op::OpNeg => ("OpNeg", String::new()), Op::OpNot => ("OpNot", String::new()), - Op::ForceBool => ("ForceBool", String::new()), - Op::JumpIfFalse => { let offset = self.read_i32(); let target = (current_pc as isize + 1 + 4 + offset as isize) as usize; @@ -348,9 +340,8 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> { Op::LoadBuiltins => ("LoadBuiltins", String::new()), Op::LoadBuiltin => { - let idx = self.read_u32(); - let name = self.ctx.lookup_string(idx); - ("LoadBuiltin", format!("{:?}", name)) + let id = self.read_u8(); + ("LoadBuiltin", format!("id={}", id)) } Op::MkPos => { let span_id = self.read_u32(); diff --git a/fix/src/downgrade.rs b/fix/src/downgrade.rs index 1d5c1dc..c1e8998 100644 --- a/fix/src/downgrade.rs +++ b/fix/src/downgrade.rs @@ -8,6 +8,7 @@ use rowan::ast::AstNode; use crate::error::{Error, Result, Source}; use crate::ir::*; +use crate::runtime::BuiltinId; use crate::value::Symbol; trait Require<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>, T> { @@ -170,10 +171,10 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo text[1..text.len() - 1].to_string().box_in(ctx.bump()), )) }; - let sym = ctx.new_sym("findFile".into()); - let find_file = ctx.new_expr(Ir::Builtin(sym)); + // HACK: disgusting eww + let find_file = ctx.new_expr(Ir::Builtin(BuiltinId::FindFile)); let sym = ctx.new_sym("nixPath".into()); - let nix_path = ctx.new_expr(Ir::Builtin(sym)); + let nix_path = ctx.new_expr(Ir::BuiltinConst(sym)); let call = ctx.new_expr(Ir::Call { func: find_file, arg: nix_path, diff --git a/fix/src/ir.rs b/fix/src/ir.rs index 117ede5..10da383 100644 --- a/fix/src/ir.rs +++ b/fix/src/ir.rs @@ -1,14 +1,16 @@ -use std::{ - hash::{Hash, Hasher}, - ops::Deref, -}; +use std::hash::{Hash, Hasher}; +use std::ops::Deref; -use bumpalo::{Bump, boxed::Box, collections::Vec}; +use bumpalo::Bump; +use bumpalo::boxed::Box; +use bumpalo::collections::Vec; use gc_arena::Collect; use ghost_cell::{GhostCell, GhostToken}; use rnix::{TextRange, ast}; use string_interner::symbol::SymbolU32; +use crate::runtime::BuiltinId; + pub type HashMap<'ir, K, V> = hashbrown::HashMap; #[repr(transparent)] @@ -48,8 +50,8 @@ impl<'id, 'ir> IrRef<'id, 'ir> { } #[repr(transparent)] -#[derive(Clone, Copy)] -pub struct RawIrRef<'ir>(&'ir Ir<'ir, Self>); +#[derive(Clone, Copy, Debug)] +pub struct RawIrRef<'ir>(pub &'ir Ir<'ir, Self>); impl<'ir> Deref for RawIrRef<'ir> { type Target = Ir<'ir, RawIrRef<'ir>>; @@ -59,6 +61,7 @@ impl<'ir> Deref for RawIrRef<'ir> { } #[repr(C)] +#[derive(Debug)] pub enum Ir<'ir, Ref> { Int(i64), Float(f64), @@ -135,7 +138,8 @@ pub enum Ir<'ir, Ref> { // Builtins Builtins, - Builtin(StringId), + Builtin(BuiltinId), + BuiltinConst(StringId), // Misc TopLevel { @@ -463,6 +467,7 @@ fn ir_content_hash<'id>(ir: IrRef<'id, '_>, token: &GhostToken<'id>, state: &mut Ir::Thunk(x) => x.hash(state), Ir::Builtins => {} Ir::Builtin(x) => x.hash(state), + Ir::BuiltinConst(x) => x.hash(state), Ir::CurPos(x) => x.hash(state), Ir::ReplBinding(x) => x.hash(state), Ir::ScopedImportBinding(x) => x.hash(state), diff --git a/fix/src/logging.rs b/fix/src/logging.rs index 7110edb..20ea887 100644 --- a/fix/src/logging.rs +++ b/fix/src/logging.rs @@ -1,7 +1,9 @@ use std::env; use std::io::IsTerminal; -use tracing_subscriber::{EnvFilter, Layer, fmt, layer::SubscriberExt, util::SubscriberInitExt}; +use tracing_subscriber::layer::SubscriberExt; +use tracing_subscriber::util::SubscriberInitExt; +use tracing_subscriber::{EnvFilter, Layer, fmt}; pub fn init_logging() { let is_terminal = std::io::stderr().is_terminal(); diff --git a/fix/src/main.rs b/fix/src/main.rs index c2b92da..1ffa236 100644 --- a/fix/src/main.rs +++ b/fix/src/main.rs @@ -40,19 +40,18 @@ struct ExprSource { file: Option, } -fn run_compile(_runtime: &mut Runtime, src: ExprSource, _silent: bool) -> Result<()> { - let _src = if let Some(expr) = src.expr { +fn run_compile(runtime: &mut Runtime, src: ExprSource, silent: bool) -> Result<()> { + let src = if let Some(expr) = src.expr { Source::new_eval(expr)? } else if let Some(file) = src.file { Source::new_file(file)? } else { unreachable!() }; - todo!() - /* match runtime.compile_bytecode(src) { - Ok(compiled) => { + match runtime.compile_bytecode(src) { + Ok(ip) => { if !silent { - println!("{}", runtime.disassemble_colored(&compiled)); + println!("{}", runtime.disassemble_colored(ip)); } } Err(err) => { @@ -60,7 +59,7 @@ fn run_compile(_runtime: &mut Runtime, src: ExprSource, _silent: bool) -> Result exit(1); } }; - Ok(()) */ + Ok(()) } fn run_eval(runtime: &mut Runtime, src: ExprSource) -> Result<()> { diff --git a/fix/src/runtime.rs b/fix/src/runtime.rs index 66d593a..0cc0bb4 100644 --- a/fix/src/runtime.rs +++ b/fix/src/runtime.rs @@ -1,75 +1,99 @@ use std::hash::BuildHasher; use bumpalo::Bump; -use gc_arena::{Arena, Rootable, arena::CollectionPhase}; +use gc_arena::{Arena, Rootable}; use ghost_cell::{GhostCell, GhostToken}; use hashbrown::{DefaultHashBuilder, HashMap, HashSet, HashTable}; use rnix::TextRange; -use string_interner::DefaultStringInterner; +use string_interner::symbol::SymbolU32; +use string_interner::{DefaultStringInterner, Symbol as _}; use crate::codegen::{BytecodeContext, InstructionPtr}; +use crate::disassembler::{Disassembler, DisassemblerContext}; use crate::downgrade::{Downgrade as _, DowngradeContext}; use crate::error::{Error, Result, Source}; use crate::ir::{ArgId, Ir, IrKey, IrRef, RawIrRef, StringId, ThunkId, ir_content_eq}; -use crate::runtime::builtins::new_builtins_env; +use crate::runtime::builtins::init_builtins; +use crate::runtime::value::StaticValue; +use crate::runtime::vm::{ForceMode, new_gc_root}; use crate::store::{DaemonStore, StoreConfig}; use crate::value::Symbol; mod builtins; -mod primops; mod stack; -mod value; +pub(crate) mod value; mod vm; -use vm::{Action, VM}; +pub(crate) use builtins::{BUILTINS, BuiltinId}; +use stack::Stack; +use vm::{ErrorFrame, GcRoot}; pub struct Runtime { - bytecode: Vec, - global_env: HashMap>>, + // global sources: Vec, - store: DaemonStore, spans: Vec<(usize, TextRange)>, - thunk_count: usize, + store: DaemonStore, strings: DefaultStringInterner, - arena: Arena]>, + // FIXME: remove? + thunk_count: usize, + bytecode: Vec, + pub(crate) constants: Vec, + constant_dedup: HashMap, + + // downgrade + global_env: HashMap>>, + + // eval + pc: usize, + fuel: usize, + error_contexts: Stack<8192, ErrorFrame>, + arena: Arena]>, } impl Runtime { - const COLLECTOR_GRANULARITY: f64 = 1024.0; + const DEFAULT_FUEL_AMOUNT: usize = 2048; pub fn new() -> Result { + // HACK: what the heck... + let mut global_env = HashMap::new(); let mut strings = DefaultStringInterner::new(); - let global_env = new_builtins_env(&mut strings); + let arena = Arena::new(|mc| { + let (root, env) = new_gc_root(mc, &mut strings); + global_env = env; + root + }); let config = StoreConfig::from_env(); let store = DaemonStore::connect(&config.daemon_socket)?; Ok(Self { - arena: Arena::new(|mc| VM::new(mc, &mut strings)), - global_env, + sources: Vec::new(), + spans: Vec::new(), store, strings, thunk_count: 0, bytecode: Vec::new(), - sources: Vec::new(), - spans: Vec::new(), + constants: Vec::new(), + constant_dedup: HashMap::new(), + + global_env, + + pc: 0, + fuel: Self::DEFAULT_FUEL_AMOUNT, + error_contexts: Stack::new(), + arena, }) } pub fn eval(&mut self, source: Source) -> Result { - let root = self.downgrade(source, None)?; - let ip = crate::codegen::compile_bytecode(root.as_ref(), self); - self.run(ip) + self.do_eval(source, None, ForceMode::AsIs) } - pub fn eval_shallow(&mut self, _source: Source) -> Result { - todo!() + pub fn eval_shallow(&mut self, source: Source) -> Result { + self.do_eval(source, None, ForceMode::Shallow) } pub fn eval_deep(&mut self, source: Source) -> Result { - // FIXME: deep - let root = self.downgrade(source, None)?; - let ip = crate::codegen::compile_bytecode(root.as_ref(), self); - self.run(ip) + self.do_eval(source, None, ForceMode::Deep) } pub fn eval_repl( @@ -77,10 +101,18 @@ impl Runtime { source: Source, scope: &HashSet, ) -> Result { - // FIXME: shallow - let root = self.downgrade(source, Some(Scope::Repl(scope)))?; + self.do_eval(source, Some(Scope::Repl(scope)), ForceMode::Shallow) + } + + fn do_eval<'ctx>( + &'ctx mut self, + source: Source, + extra_scope: Option>, + force_mode: ForceMode, + ) -> Result { + let root = self.downgrade(source, extra_scope)?; let ip = crate::codegen::compile_bytecode(root.as_ref(), self); - self.run(ip) + self.run(ip, force_mode) } pub fn add_binding( @@ -92,6 +124,20 @@ impl Runtime { todo!() } + pub fn compile_bytecode(&mut self, source: Source) -> Result { + let root = self.downgrade(source, None)?; + let ip = crate::codegen::compile_bytecode(root.as_ref(), self); + Ok(ip) + } + + pub fn disassemble_colored(&mut self, ip: InstructionPtr) -> String { + Disassembler::new(ip, self).disassemble_colored() + } + + pub fn disassemble(&mut self, ip: InstructionPtr) -> String { + Disassembler::new(ip, self).disassemble() + } + fn downgrade_ctx<'a, 'bump, 'id>( &'a mut self, bump: &'bump Bump, @@ -147,34 +193,6 @@ impl Runtime { Ok(OwnedIr { _bump: bump, ir }) }) } - - fn run(&mut self, ip: InstructionPtr) -> Result { - let mut pc = ip.0; - loop { - let Runtime { - bytecode, - strings, - arena, - .. - } = self; - let action = - arena.mutate_root(|mc, root| root.run_batch(bytecode, &mut pc, mc, strings)); - match action { - Action::NeedGc => { - 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; - } - Action::Continue => (), - Action::IoRequest(_) => todo!(), - } - } - } } fn parse_error_span(error: &rnix::ParseError) -> Option { @@ -536,4 +554,28 @@ impl BytecodeContext for Runtime { fn get_code_mut(&mut self) -> &mut Vec { &mut self.bytecode } + + fn add_constant(&mut self, val: StaticValue) -> u32 { + let bits = val.to_bits(); + *self.constant_dedup.entry(bits).or_insert_with(|| { + let idx = self.constants.len() as u32; + self.constants.push(val); + idx + }) + } +} + +impl DisassemblerContext for Runtime { + fn lookup_string(&self, id: u32) -> &str { + self.strings + .resolve(SymbolU32::try_from_usize(id as usize).expect("invalid string id")) + .expect("string not found") + } + fn get_code(&self) -> &[u8] { + &self.bytecode + } +} + +struct WellKnownSymbols { + // TODO: } diff --git a/fix/src/runtime/builtins.rs b/fix/src/runtime/builtins.rs index 381d885..2060c04 100644 --- a/fix/src/runtime/builtins.rs +++ b/fix/src/runtime/builtins.rs @@ -1,7 +1,9 @@ -use gc_arena::Collect; +use gc_arena::{Collect, Gc, Mutation, RefLock}; use hashbrown::HashMap; use num_enum::TryFromPrimitive; +use smallvec::SmallVec; use string_interner::DefaultStringInterner; +use string_interner::symbol::SymbolU32; use super::value::*; use crate::ir::{Ir, RawIrRef, StringId}; @@ -12,14 +14,14 @@ macro_rules! define_builtins { ($(($name:literal, $variant:ident, $arity:expr)),* $(,)?) => { /// Builtin function registry. /// Array index IS the PrimOp id. (name, arity) pairs. - pub(super) const BUILTINS: &[(&str, u8)] = &[ + pub(crate) const BUILTINS: &[(&str, u8)] = &[ $(($name, $arity),)* ]; - #[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive, Collect)] + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, TryFromPrimitive, Collect)] #[repr(u8)] #[collect(require_static)] - pub(super) enum BuiltinId { + pub(crate) enum BuiltinId { $($variant,)* } }; @@ -27,108 +29,108 @@ macro_rules! define_builtins { define_builtins! { ("abort", Abort, 1), - ("add", Add, 2), - ("addErrorContext", AddErrorContext, 2), - ("all", All, 2), - ("any", Any, 2), - ("appendContext", AppendContext, 2), - ("attrNames", AttrNames, 1), - ("attrValues", AttrValues, 1), + ("__add", Add, 2), + ("__addErrorContext", AddErrorContext, 2), + ("__all", All, 2), + ("__any", Any, 2), + ("__appendContext", AppendContext, 2), + ("__attrNames", AttrNames, 1), + ("__attrValues", AttrValues, 1), ("baseNameOf", BaseNameOf, 1), - ("bitAnd", BitAnd, 2), - ("bitOr", BitOr, 2), - ("bitXor", BitXor, 2), - ("catAttrs", CatAttrs, 2), - ("ceil", Ceil, 1), - ("compareVersions", CompareVersions, 2), - ("concatLists", ConcatLists, 1), - ("concatMap", ConcatMap, 2), - ("concatStringsSep", ConcatStringsSep, 2), - ("convertHash", ConvertHash, 1), - ("deepSeq", DeepSeq, 2), + ("__bitAnd", BitAnd, 2), + ("__bitOr", BitOr, 2), + ("__bitXor", BitXor, 2), + ("break", Break, 1), + ("__catAttrs", CatAttrs, 2), + ("__ceil", Ceil, 1), + ("__compareVersions", CompareVersions, 2), + ("__concatLists", ConcatLists, 1), + ("__concatMap", ConcatMap, 2), + ("__concatStringsSep", ConcatStringsSep, 2), + ("__convertHash", ConvertHash, 1), + ("__deepSeq", DeepSeq, 2), ("derivation", Derivation, 1), ("derivationStrict", DerivationStrict, 1), ("dirOf", DirOf, 1), - ("div", Div, 2), - ("elem", Elem, 2), - ("elemAt", ElemAt, 2), + ("__div", Div, 2), + ("__elem", Elem, 2), + ("__elemAt", ElemAt, 2), ("fetchGit", FetchGit, 1), ("fetchMercurial", FetchMercurial, 1), ("fetchTarball", FetchTarball, 1), ("fetchTree", FetchTree, 1), - ("fetchurl", FetchUrl, 1), - ("filter", Filter, 2), - ("filterSource", FilterSource, 2), - ("findFile", FindFile, 2), - ("floor", Floor, 1), - ("foldl'", FoldlStrict, 3), - ("fromJSON", FromJSON, 1), + ("__fetchurl", FetchUrl, 1), + ("__filter", Filter, 2), + ("__filterSource", FilterSource, 2), + ("__findFile", FindFile, 2), + ("__floor", Floor, 1), + ("__foldl'", FoldlStrict, 3), + ("__fromJSON", FromJSON, 1), ("fromTOML", FromTOML, 1), - ("functionArgs", FunctionArgs, 1), - ("genList", GenList, 2), - ("genericClosure", GenericClosure, 1), - ("getAttr", GetAttr, 2), - ("getContext", GetContext, 1), - ("getEnv", GetEnv, 1), - ("groupBy", GroupBy, 2), - ("hasAttr", HasAttr, 2), - ("hasContext", HasContext, 1), - ("hashFile", HashFile, 2), - ("hashString", HashString, 2), - ("head", Head, 1), + ("__functionArgs", FunctionArgs, 1), + ("__genList", GenList, 2), + ("__genericClosure", GenericClosure, 1), + ("__getAttr", GetAttr, 2), + ("__getContext", GetContext, 1), + ("__getEnv", GetEnv, 1), + ("__groupBy", GroupBy, 2), + ("__hasAttr", HasAttr, 2), + ("__hasContext", HasContext, 1), + ("__hashFile", HashFile, 2), + ("__hashString", HashString, 2), + ("__head", Head, 1), ("import", Import, 1), - ("intersectAttrs", IntersectAttrs, 2), - ("isAttrs", IsAttrs, 1), - ("isBool", IsBool, 1), - ("isFloat", IsFloat, 1), - ("isFunction", IsFunction, 1), - ("isInt", IsInt, 1), - ("isList", IsList, 1), + ("__intersectAttrs", IntersectAttrs, 2), + ("__isAttrs", IsAttrs, 1), + ("__isBool", IsBool, 1), + ("__isFloat", IsFloat, 1), + ("__isFunction", IsFunction, 1), + ("__isInt", IsInt, 1), + ("__isList", IsList, 1), ("isNull", IsNull, 1), - ("isPath", IsPath, 1), - ("isString", IsString, 1), - ("length", Length, 1), - ("lessThan", LessThan, 2), - ("listToAttrs", ListToAttrs, 1), + ("__isPath", IsPath, 1), + ("__isString", IsString, 1), + ("__length", Length, 1), + ("__lessThan", LessThan, 2), + ("__listToAttrs", ListToAttrs, 1), ("map", Map, 2), - ("mapAttrs", MapAttrs, 2), - ("match", Match, 2), - ("mul", Mul, 2), + ("__mapAttrs", MapAttrs, 2), + ("__match", Match, 2), + ("__mul", Mul, 2), ("null", Null, 0), // constant, not a function - ("parseDrvName", ParseDrvName, 1), - ("partition", Partition, 2), - ("path", Path, 1), - ("pathExists", PathExists, 1), + ("__parseDrvName", ParseDrvName, 1), + ("__partition", Partition, 2), + ("__path", Path, 1), + ("__pathExists", PathExists, 1), ("placeholder", Placeholder, 1), - ("readDir", ReadDir, 1), - ("readFile", ReadFile, 1), - ("readFileType", ReadFileType, 1), + ("__readDir", ReadDir, 1), + ("__readFile", ReadFile, 1), + ("__readFileType", ReadFileType, 1), ("removeAttrs", RemoveAttrs, 2), - ("replaceStrings", ReplaceStrings, 3), + ("__replaceStrings", ReplaceStrings, 3), ("scopedImport", ScopedImport, 2), - ("seq", Seq, 2), - ("sort", Sort, 2), - ("split", Split, 2), - ("splitVersion", SplitVersion, 1), - ("storePath", StorePath, 1), - ("stringLength", StringLength, 1), - ("sub", Sub, 2), - ("substring", Substring, 3), - ("tail", Tail, 1), + ("__seq", Seq, 2), + ("__sort", Sort, 2), + ("__split", Split, 2), + ("__splitVersion", SplitVersion, 1), + ("__storePath", StorePath, 1), + ("__stringLength", StringLength, 1), + ("__sub", Sub, 2), + ("__substring", Substring, 3), + ("__tail", Tail, 1), ("throw", Throw, 1), - ("toFile", ToFile, 2), - ("toJSON", ToJSON, 1), - ("toPath", ToPath, 1), + ("__toFile", ToFile, 2), + ("__toJSON", ToJSON, 1), + ("__toPath", ToPath, 1), ("toString", ToString, 1), - ("toXML", ToXML, 1), - ("trace", Trace, 2), - ("tryEval", TryEval, 1), - ("typeOf", TypeOf, 1), - ("unsafeDiscardStringContext", UnsafeDiscardStringContext, 1), - ("unsafeGetAttrPos", UnsafeGetAttrPos, 2), - ("warn", Warn, 2), - ("zipAttrsWith", ZipAttrsWith, 2), - ("break", Break, 1), + ("__toXML", ToXML, 1), + ("__trace", Trace, 2), + ("__tryEval", TryEval, 1), + ("__typeOf", TypeOf, 1), + ("__unsafeDiscardStringContext", UnsafeDiscardStringContext, 1), + ("__unsafeGetAttrPos", UnsafeGetAttrPos, 2), + ("__warn", Warn, 2), + ("__zipAttrsWith", ZipAttrsWith, 2), } /// Names that need to be pre-interned for builtin implementations. @@ -194,54 +196,215 @@ fn intern_all_builtins(interner: &mut DefaultStringInterner) { } } -pub(super) fn new_builtins_env( - interner: &mut DefaultStringInterner, -) -> HashMap>> { - intern_all_builtins(interner); +pub(super) fn init_builtins<'gc>( + mc: &Mutation<'gc>, + strings: &mut DefaultStringInterner, +) -> ( + HashMap>>, + Value<'gc>, +) { + let mut global_env = HashMap::new(); + let builtins_sym = StringId(strings.get_or_intern("builtins")); + global_env.insert(builtins_sym, Ir::Builtins); + let mut entries = SmallVec::with_capacity(BUILTINS.len()); - let mut builtins = HashMap::new(); - let builtins_sym = StringId(interner.get_or_intern("builtins")); - builtins.insert(builtins_sym, Ir::Builtins); + for (idx, &(name, arity)) in BUILTINS.iter().enumerate() { + let id = BuiltinId::try_from_primitive(idx as u8).expect("infallible"); + if let Some(local_name) = name.strip_prefix("__") { + // scoped builtins + let name = StringId(strings.get_or_intern(name)); + let local_name = StringId(strings.get_or_intern(local_name)); + global_env.insert(name, Ir::Builtin(id)); + entries.push((local_name, Value::new_inline(PrimOp { id, arity }))); + } else { + // global builtins + let name = StringId(strings.get_or_intern(name)); + global_env.insert(name, Ir::Builtin(id)); + entries.push((name, Value::new_inline(PrimOp { id, arity }))); + } + } - let free_globals = [ - "abort", - "baseNameOf", - "break", - "dirOf", - "derivation", - "derivationStrict", - "fetchGit", - "fetchMercurial", - "fetchTarball", - "fetchTree", - "fromTOML", - "import", - "isNull", - "map", - "placeholder", - "removeAttrs", - "scopedImport", - "throw", - "toString", - ]; let consts = [ - ("true", Ir::Bool(true)), - ("false", Ir::Bool(false)), - ("null", Ir::Null), + ( + "__currentSystem", + Ir::BuiltinConst(StringId(strings.get_or_intern("currentSystem"))), + // FIXME: detect currentSystem + Value::new_gc(Gc::new(mc, NixString::new("x86_64-linux"))), + ), + ("__langVersion", Ir::Int(6), Value::new_inline(6i32)), + ( + "__nixVersion", + Ir::BuiltinConst(StringId(strings.get_or_intern("nixVersion"))), + Value::new_gc(Gc::new(mc, NixString::new("2.24.0"))), + ), + ( + "__storeDir", + Ir::BuiltinConst(StringId(strings.get_or_intern("storeDir"))), + Value::new_gc(Gc::new(mc, NixString::new("/nix/store"))), + ), + ( + "__nixPath", + Ir::BuiltinConst(StringId(strings.get_or_intern("nixPath"))), + // FIXME: get from config + Value::new_gc(Gc::new( + mc, + List { + inner: SmallVec::new(), + }, + )), + ), + ("null", Ir::Null, Value::new_inline(Null)), + ("true", Ir::Bool(true), Value::new_inline(true)), + ("false", Ir::Bool(false), Value::new_inline(false)), ]; - for name in free_globals { - let name = StringId(interner.get_or_intern(name)); - let value = Ir::Builtin(name); - builtins.insert(name, value); + for (idx, &(name, arity)) in BUILTINS.iter().enumerate() { + let id = BuiltinId::try_from_primitive(idx as u8).expect("infallible"); + if let Some(local_name) = name.strip_prefix("__") { + // scoped builtins + let name = StringId(strings.get_or_intern(name)); + let local_name = StringId(strings.get_or_intern(local_name)); + global_env.insert(name, Ir::Builtin(id)); + entries.push((local_name, Value::new_inline(PrimOp { id, arity }))); + } else { + // global builtins + let name = StringId(strings.get_or_intern(name)); + global_env.insert(name, Ir::Builtin(id)); + entries.push((name, Value::new_inline(PrimOp { id, arity }))); + } } - for (name, value) in consts { - let name = StringId(interner.get_or_intern(name)); - builtins.insert(name, value); + for (name, ir, val) in consts { + if let Some(local_name) = name.strip_prefix("__") { + let name = StringId(strings.get_or_intern(name)); + let local_name = StringId(strings.get_or_intern(local_name)); + global_env.insert(name, ir); + entries.push((local_name, val)); + } else { + let name = StringId(strings.get_or_intern(name)); + global_env.insert(name, ir); + entries.push((name, val)); + } } - builtins + // Self-reference thunk for builtins.builtins + let self_ref_thunk: Gc<'gc, Thunk<'gc>> = Gc::new(mc, RefLock::new(ThunkState::Blackhole)); + let sym = strings.get_or_intern("builtins"); + entries.push((StringId(sym), Value::new_gc(self_ref_thunk))); + + entries.sort_by_key(|(k, _)| *k); + + let builtins_set = Gc::new(mc, unsafe { AttrSet::from_sorted_unchecked(entries) }); + let builtins_val = Value::new_gc(builtins_set); + + (global_env, builtins_val) } pub(super) type PrimOpArgs<'gc> = [Value<'gc>; 3]; pub(super) type PrimOpStrictArgs<'gc> = [StrictValue<'gc>; 3]; + +#[allow(non_snake_case)] +pub(super) struct WellKnownSymbols { + abort: SymbolU32, + add: SymbolU32, + addErrorContext: SymbolU32, + all: SymbolU32, + any: SymbolU32, + appendContext: SymbolU32, + attrNames: SymbolU32, + attrValues: SymbolU32, + baseNameOf: SymbolU32, + bitAnd: SymbolU32, + bitOr: SymbolU32, + bitXor: SymbolU32, + catAttrs: SymbolU32, + ceil: SymbolU32, + compareVersions: SymbolU32, + concatLists: SymbolU32, + concatMap: SymbolU32, + concatStringsSep: SymbolU32, + convertHash: SymbolU32, + deepSeq: SymbolU32, + derivation: SymbolU32, + derivationStrict: SymbolU32, + dirOf: SymbolU32, + div: SymbolU32, + elem: SymbolU32, + elemAt: SymbolU32, + fetchGit: SymbolU32, + fetchMercurial: SymbolU32, + fetchTarball: SymbolU32, + fetchTree: SymbolU32, + fetchurl: SymbolU32, + filter: SymbolU32, + filterSource: SymbolU32, + findFile: SymbolU32, + floor: SymbolU32, + foldl: SymbolU32, + fromJSON: SymbolU32, + fromTOML: SymbolU32, + functionArgs: SymbolU32, + genList: SymbolU32, + genericClosure: SymbolU32, + getAttr: SymbolU32, + getContext: SymbolU32, + getEnv: SymbolU32, + groupBy: SymbolU32, + hasAttr: SymbolU32, + hasContext: SymbolU32, + hashFile: SymbolU32, + hashString: SymbolU32, + head: SymbolU32, + import: SymbolU32, + intersectAttrs: SymbolU32, + isAttrs: SymbolU32, + isBool: SymbolU32, + isFloat: SymbolU32, + isFunction: SymbolU32, + isInt: SymbolU32, + isList: SymbolU32, + isNull: SymbolU32, + isPath: SymbolU32, + isString: SymbolU32, + length: SymbolU32, + lessThan: SymbolU32, + listToAttrs: SymbolU32, + map: SymbolU32, + mapAttrs: SymbolU32, + match_: SymbolU32, + mul: SymbolU32, + null: SymbolU32, + parseDrvName: SymbolU32, + partition: SymbolU32, + path: SymbolU32, + pathExists: SymbolU32, + placeholder: SymbolU32, + readDir: SymbolU32, + readFile: SymbolU32, + readFileType: SymbolU32, + removeAttrs: SymbolU32, + replaceStrings: SymbolU32, + scopedImport: SymbolU32, + seq: SymbolU32, + sort: SymbolU32, + split: SymbolU32, + splitVersion: SymbolU32, + storePath: SymbolU32, + stringLength: SymbolU32, + sub: SymbolU32, + substring: SymbolU32, + tail: SymbolU32, + throw: SymbolU32, + toFile: SymbolU32, + toJSON: SymbolU32, + toPath: SymbolU32, + toString: SymbolU32, + toXML: SymbolU32, + trace: SymbolU32, + tryEval: SymbolU32, + typeOf: SymbolU32, + unsafeDiscardStringContext: SymbolU32, + unsafeGetAttrPos: SymbolU32, + warn: SymbolU32, + zipAttrsWith: SymbolU32, + break_: SymbolU32, +} diff --git a/fix/src/runtime/primops.rs b/fix/src/runtime/primops.rs deleted file mode 100644 index 1e5d034..0000000 --- a/fix/src/runtime/primops.rs +++ /dev/null @@ -1,929 +0,0 @@ -use gc_arena::{Collect, Gc, Mutation, RefLock}; -use smallvec::SmallVec; -use string_interner::DefaultStringInterner; - -use super::builtins::{BuiltinId, PrimOpArgs, PrimOpStrictArgs}; -use super::value::*; -use super::vm::{ForceResult, VM, VmError}; -use crate::ir::StringId; - -pub(super) enum BuiltinResult<'gc> { - Done(Value<'gc>), - Force(BuiltinState<'gc>, Value<'gc>), - Call(BuiltinState<'gc>, StrictValue<'gc>, Value<'gc>), - CallAndForce(BuiltinState<'gc>, StrictValue<'gc>, Value<'gc>), - Error(VmError), -} - -#[derive(Collect, Debug)] -#[collect(no_drop)] -pub(super) enum BuiltinState<'gc> { - FoldlStrict(FoldlStrict<'gc>), - // future: Filter, GenericClosure, Sort, All, Any, ConcatMap, ... -} - -impl<'gc> BuiltinState<'gc> { - pub(super) fn resume( - self, - val: StrictValue<'gc>, - ctx: &PrimOpCtx<'_, 'gc>, - ) -> BuiltinResult<'gc> { - match self { - BuiltinState::FoldlStrict(s) => s.resume(val, ctx), - } - } -} - -pub(super) struct PrimOpCtx<'a, 'gc> { - pub(super) vm: &'a VM<'gc>, - pub(super) mc: &'a Mutation<'gc>, - pub(super) strings: &'a DefaultStringInterner, -} - -macro_rules! force_inline_or_err { - ($ctx:expr, $val:expr) => {{ - let val = $val; - match $ctx.vm.force_inline(val) { - Ok(ForceResult::Ready(v)) => v, - Ok(_) => { - return BuiltinResult::Error(VM::err( - "value requires evaluation in non-stateful builtin context", - )); - } - Err(e) => return BuiltinResult::Error(e), - } - }}; -} - -macro_rules! force { - ($ctx:expr, $state:expr, $val:expr) => {{ - let val = $val; - match $ctx.vm.force_inline(val) { - Ok(ForceResult::Ready(v)) => v, - Ok(ForceResult::NeedEval { .. } | ForceResult::NeedApply(_)) => { - return BuiltinResult::Force($state, val); - } - Err(e) => return BuiltinResult::Error(e), - } - }}; -} - -macro_rules! call { - ($ctx:expr, $state:expr, $func:expr, $arg:expr) => {{ - let func = $func; - let arg = $arg; - return BuiltinResult::Call($state, func, arg); - }}; -} - -macro_rules! call_and_force { - ($ctx:expr, $state:expr, $func:expr, $arg:expr) => {{ - let func = $func; - let arg = $arg; - return BuiltinResult::CallAndForce($state, func, arg); - }}; -} - -pub(super) fn dispatch_strict_builtin<'gc>( - id: BuiltinId, - args: PrimOpStrictArgs<'gc>, - _arity: u8, - ctx: &PrimOpCtx<'_, 'gc>, -) -> BuiltinResult<'gc> { - match id { - BuiltinId::TypeOf => { - let val = args[0]; - let name = if val.as_inline::().is_some() || val.as_gc::().is_some() { - "int" - } else if val.as_float().is_some() { - "float" - } else if val.as_inline::().is_some() { - "bool" - } else if VM::get_string(val, ctx.strings).is_some() { - "string" - } else if val.is::() { - "null" - } else if val.as_gc::>().is_some() { - "set" - } else if val.as_gc::>().is_some() { - "list" - } else if val.as_gc::>().is_some() - || val.as_inline::().is_some() - || val.as_gc::>().is_some() - { - "lambda" - } else { - return BuiltinResult::Error(VM::err("typeOf: unknown type")); - }; - let sid = ctx.strings.get(name).expect("typeOf string not interned"); - BuiltinResult::Done(Value::new_inline(StringId(sid))) - } - - BuiltinId::IsNull => BuiltinResult::Done(Value::new_inline(args[0].is::())), - BuiltinId::IsAttrs => { - BuiltinResult::Done(Value::new_inline(args[0].as_gc::>().is_some())) - } - BuiltinId::IsBool => { - BuiltinResult::Done(Value::new_inline(args[0].as_inline::().is_some())) - } - BuiltinId::IsFloat => BuiltinResult::Done(Value::new_inline(args[0].as_float().is_some())), - BuiltinId::IsFunction => { - let v = args[0]; - let is_func = v.as_gc::>().is_some() - || v.as_inline::().is_some() - || v.as_gc::>().is_some(); - BuiltinResult::Done(Value::new_inline(is_func)) - } - BuiltinId::IsInt => { - let v = args[0]; - let is_int = v.as_inline::().is_some() || v.as_gc::().is_some(); - BuiltinResult::Done(Value::new_inline(is_int)) - } - BuiltinId::IsList => { - BuiltinResult::Done(Value::new_inline(args[0].as_gc::>().is_some())) - } - BuiltinId::IsString => { - let v = args[0]; - let is_str = v.as_inline::().is_some() || v.as_gc::().is_some(); - BuiltinResult::Done(Value::new_inline(is_str)) - } - BuiltinId::IsPath => BuiltinResult::Done(Value::new_inline(false)), - - BuiltinId::Length => { - let Some(list) = args[0].as_gc::>() else { - return BuiltinResult::Error(VM::err("builtins.length: not a list")); - }; - BuiltinResult::Done(VM::make_int(list.inner.len() as i64, ctx.mc)) - } - BuiltinId::Head => { - let Some(list) = args[0].as_gc::>() else { - return BuiltinResult::Error(VM::err("builtins.head: not a list")); - }; - if list.inner.is_empty() { - return BuiltinResult::Error(VM::err("builtins.head: empty list")); - } - BuiltinResult::Done(list.inner[0]) - } - BuiltinId::Tail => { - let Some(list) = args[0].as_gc::>() else { - return BuiltinResult::Error(VM::err("builtins.tail: not a list")); - }; - if list.inner.is_empty() { - return BuiltinResult::Error(VM::err("builtins.tail: empty list")); - } - let tail = List { - inner: SmallVec::from_slice(&list.inner[1..]), - }; - BuiltinResult::Done(Value::new_gc(Gc::new(ctx.mc, tail))) - } - - BuiltinId::AttrNames => { - let Some(attrs) = args[0].as_gc::>() else { - return BuiltinResult::Error(VM::err("builtins.attrNames: not a set")); - }; - let items: SmallVec<[Value<'gc>; 4]> = - attrs.iter().map(|(k, _)| Value::new_inline(*k)).collect(); - BuiltinResult::Done(Value::new_gc(Gc::new(ctx.mc, List { inner: items }))) - } - BuiltinId::AttrValues => { - let Some(attrs) = args[0].as_gc::>() else { - return BuiltinResult::Error(VM::err("builtins.attrValues: not a set")); - }; - let items: SmallVec<[Value<'gc>; 4]> = attrs.iter().map(|(_, v)| *v).collect(); - BuiltinResult::Done(Value::new_gc(Gc::new(ctx.mc, List { inner: items }))) - } - - BuiltinId::Map => { - let f = args[0]; - let Some(list) = args[1].as_gc::>() else { - return BuiltinResult::Error(VM::err( - "builtins.map: second argument is not a list", - )); - }; - if list.inner.is_empty() { - return BuiltinResult::Done(Value::new_gc(Gc::new( - ctx.mc, - List { - inner: SmallVec::new(), - }, - ))); - } - let new_elems: SmallVec<[Value<'gc>; 4]> = list - .inner - .iter() - .map(|elem| { - let thunk: Gc<'gc, Thunk<'gc>> = Gc::new( - ctx.mc, - RefLock::new(ThunkState::Apply { - func: f.relax(), - arg: *elem, - }), - ); - Value::new_gc(thunk) - }) - .collect(); - BuiltinResult::Done(Value::new_gc(Gc::new(ctx.mc, List { inner: new_elems }))) - } - - BuiltinId::GenList => { - let f = args[0]; - let len_val = args[1]; - let Some(len) = VM::as_num(len_val) else { - return BuiltinResult::Error(VM::err( - "builtins.genList: second argument is not a number", - )); - }; - let super::vm::NixNum::Int(len) = len else { - return BuiltinResult::Error(VM::err( - "builtins.genList: second argument is not an integer", - )); - }; - if len < 0 { - return BuiltinResult::Error(VM::err("builtins.genList: negative length")); - } - let items: SmallVec<[Value<'gc>; 4]> = (0..len) - .map(|i| { - let arg = VM::make_int(i, ctx.mc); - let thunk: Gc<'gc, Thunk<'gc>> = Gc::new( - ctx.mc, - RefLock::new(ThunkState::Apply { - func: f.relax(), - arg, - }), - ); - Value::new_gc(thunk) - }) - .collect(); - BuiltinResult::Done(Value::new_gc(Gc::new(ctx.mc, List { inner: items }))) - } - - BuiltinId::ElemAt => { - let Some(list) = args[0].as_gc::>() else { - return BuiltinResult::Error(VM::err("builtins.elemAt: not a list")); - }; - let Some(idx) = VM::as_num(args[1]) else { - return BuiltinResult::Error(VM::err("builtins.elemAt: index is not a number")); - }; - let super::vm::NixNum::Int(idx) = idx else { - return BuiltinResult::Error(VM::err("builtins.elemAt: index is not an integer")); - }; - if idx < 0 || idx as usize >= list.inner.len() { - return BuiltinResult::Error(VM::err(format!( - "builtins.elemAt: index {} out of bounds for list of length {}", - idx, - list.inner.len() - ))); - } - BuiltinResult::Done(list.inner[idx as usize]) - } - - BuiltinId::GetAttr => { - let Some(name) = VM::get_string(args[0], ctx.strings) else { - return BuiltinResult::Error(VM::err( - "builtins.getAttr: first argument is not a string", - )); - }; - let Some(sid) = ctx.strings.get(name) else { - return BuiltinResult::Error(VM::err(format!( - "builtins.getAttr: attribute '{}' not found", - name - ))); - }; - let Some(attrs) = args[1].as_gc::>() else { - return BuiltinResult::Error(VM::err( - "builtins.getAttr: second argument is not a set", - )); - }; - match attrs.lookup(StringId(sid)) { - Some(v) => BuiltinResult::Done(v), - None => BuiltinResult::Error(VM::err(format!("attribute '{}' missing", name))), - } - } - - BuiltinId::HasAttr => { - let Some(name) = VM::get_string(args[0], ctx.strings) else { - return BuiltinResult::Error(VM::err( - "builtins.hasAttr: first argument is not a string", - )); - }; - let Some(attrs) = args[1].as_gc::>() else { - return BuiltinResult::Error(VM::err( - "builtins.hasAttr: second argument is not a set", - )); - }; - let has = ctx - .strings - .get(name) - .map(|sid| attrs.has(StringId(sid))) - .unwrap_or(false); - BuiltinResult::Done(Value::new_inline(has)) - } - - BuiltinId::RemoveAttrs => { - let Some(attrs) = args[0].as_gc::>() else { - return BuiltinResult::Error(VM::err( - "builtins.removeAttrs: first argument is not a set", - )); - }; - let Some(remove_list) = args[1].as_gc::>() else { - return BuiltinResult::Error(VM::err( - "builtins.removeAttrs: second argument is not a list", - )); - }; - let mut to_remove = Vec::new(); - for item in remove_list.inner.iter() { - let sv = force_inline_or_err!(ctx, *item); - if let Some(s) = VM::get_string(sv, ctx.strings) - && let Some(sid) = ctx.strings.get(s) - { - to_remove.push(StringId(sid)); - } - } - let entries: SmallVec<[(StringId, Value<'gc>); 4]> = attrs - .iter() - .filter(|(k, _)| !to_remove.contains(k)) - .cloned() - .collect(); - let new_attrs = Gc::new(ctx.mc, unsafe { AttrSet::from_sorted_unchecked(entries) }); - BuiltinResult::Done(Value::new_gc(new_attrs)) - } - - BuiltinId::IntersectAttrs => { - let Some(a) = args[0].as_gc::>() else { - return BuiltinResult::Error(VM::err( - "builtins.intersectAttrs: first argument is not a set", - )); - }; - let Some(b) = args[1].as_gc::>() else { - return BuiltinResult::Error(VM::err( - "builtins.intersectAttrs: second argument is not a set", - )); - }; - let entries: SmallVec<[(StringId, Value<'gc>); 4]> = - b.iter().filter(|(k, _)| a.has(*k)).cloned().collect(); - let new_attrs = Gc::new(ctx.mc, unsafe { AttrSet::from_sorted_unchecked(entries) }); - BuiltinResult::Done(Value::new_gc(new_attrs)) - } - - BuiltinId::ListToAttrs => { - let Some(list) = args[0].as_gc::>() else { - return BuiltinResult::Error(VM::err("builtins.listToAttrs: not a list")); - }; - let name_sid = ctx.strings.get("name").expect("'name' not interned"); - let value_sid = ctx.strings.get("value").expect("'value' not interned"); - let mut entries = SmallVec::<[(StringId, Value<'gc>); 4]>::new(); - for item in list.inner.iter() { - let sv = force_inline_or_err!(ctx, *item); - let Some(attr_set) = sv.as_gc::>() else { - return BuiltinResult::Error(VM::err( - "builtins.listToAttrs: element is not a set", - )); - }; - let Some(name_val) = attr_set.lookup(StringId(name_sid)) else { - return BuiltinResult::Error(VM::err( - "builtins.listToAttrs: element missing 'name'", - )); - }; - let name_sv = force_inline_or_err!(ctx, name_val); - let Some(name_str) = VM::get_string(name_sv, ctx.strings) else { - return BuiltinResult::Error(VM::err( - "builtins.listToAttrs: 'name' is not a string", - )); - }; - let Some(value_val) = attr_set.lookup(StringId(value_sid)) else { - return BuiltinResult::Error(VM::err( - "builtins.listToAttrs: element missing 'value'", - )); - }; - let Some(key_sym) = ctx.strings.get(name_str) else { - return BuiltinResult::Error(VM::err( - "builtins.listToAttrs: name not interned", - )); - }; - entries.push((StringId(key_sym), value_val)); - } - entries.sort_by_key(|(k, _)| *k); - entries.dedup_by_key(|(k, _)| *k); - let new_attrs = Gc::new(ctx.mc, unsafe { AttrSet::from_sorted_unchecked(entries) }); - BuiltinResult::Done(Value::new_gc(new_attrs)) - } - - BuiltinId::ConcatLists => { - let Some(list) = args[0].as_gc::>() else { - return BuiltinResult::Error(VM::err("builtins.concatLists: not a list")); - }; - let mut result = SmallVec::<[Value<'gc>; 4]>::new(); - for item in list.inner.iter() { - let sv = force_inline_or_err!(ctx, *item); - let Some(inner) = sv.as_gc::>() else { - return BuiltinResult::Error(VM::err( - "builtins.concatLists: element is not a list", - )); - }; - result.extend(inner.inner.iter().cloned()); - } - BuiltinResult::Done(Value::new_gc(Gc::new(ctx.mc, List { inner: result }))) - } - - BuiltinId::LessThan => { - match ctx - .vm - .compare_values(args[0], args[1], ctx.strings, |o| o.is_lt()) - { - Ok(v) => BuiltinResult::Done(v), - Err(e) => BuiltinResult::Error(e), - } - } - - BuiltinId::Add => { - match ctx.vm.compute_binop( - super::vm::BinOpTag::Add, - args[0], - args[1], - ctx.mc, - ctx.strings, - ) { - Ok(v) => BuiltinResult::Done(v), - Err(e) => BuiltinResult::Error(e), - } - } - BuiltinId::Sub => { - match ctx - .vm - .numeric_binop(args[0], args[1], ctx.mc, i64::wrapping_sub, |a, b| a - b) - { - Ok(v) => BuiltinResult::Done(v), - Err(e) => BuiltinResult::Error(e), - } - } - BuiltinId::Mul => { - match ctx - .vm - .numeric_binop(args[0], args[1], ctx.mc, i64::wrapping_mul, |a, b| a * b) - { - Ok(v) => BuiltinResult::Done(v), - Err(e) => BuiltinResult::Error(e), - } - } - BuiltinId::Div => { - match ctx.vm.compute_binop( - super::vm::BinOpTag::Div, - args[0], - args[1], - ctx.mc, - ctx.strings, - ) { - Ok(v) => BuiltinResult::Done(v), - Err(e) => BuiltinResult::Error(e), - } - } - - BuiltinId::BitAnd => { - let (Some(super::vm::NixNum::Int(a)), Some(super::vm::NixNum::Int(b))) = - (VM::as_num(args[0]), VM::as_num(args[1])) - else { - return BuiltinResult::Error(VM::err( - "builtins.bitAnd: arguments must be integers", - )); - }; - BuiltinResult::Done(VM::make_int(a & b, ctx.mc)) - } - BuiltinId::BitOr => { - let (Some(super::vm::NixNum::Int(a)), Some(super::vm::NixNum::Int(b))) = - (VM::as_num(args[0]), VM::as_num(args[1])) - else { - return BuiltinResult::Error(VM::err("builtins.bitOr: arguments must be integers")); - }; - BuiltinResult::Done(VM::make_int(a | b, ctx.mc)) - } - BuiltinId::BitXor => { - let (Some(super::vm::NixNum::Int(a)), Some(super::vm::NixNum::Int(b))) = - (VM::as_num(args[0]), VM::as_num(args[1])) - else { - return BuiltinResult::Error(VM::err( - "builtins.bitXor: arguments must be integers", - )); - }; - BuiltinResult::Done(VM::make_int(a ^ b, ctx.mc)) - } - - BuiltinId::Ceil => { - if let Some(f) = args[0].as_float() { - BuiltinResult::Done(VM::make_int(f.ceil() as i64, ctx.mc)) - } else if VM::as_num(args[0]).is_some() { - BuiltinResult::Done(args[0].relax()) - } else { - BuiltinResult::Error(VM::err("builtins.ceil: not a number")) - } - } - BuiltinId::Floor => { - if let Some(f) = args[0].as_float() { - BuiltinResult::Done(VM::make_int(f.floor() as i64, ctx.mc)) - } else if VM::as_num(args[0]).is_some() { - BuiltinResult::Done(args[0].relax()) - } else { - BuiltinResult::Error(VM::err("builtins.floor: not a number")) - } - } - - BuiltinId::StringLength => { - let Some(s) = VM::get_string(args[0], ctx.strings) else { - return BuiltinResult::Error(VM::err("builtins.stringLength: not a string")); - }; - BuiltinResult::Done(VM::make_int(s.len() as i64, ctx.mc)) - } - - BuiltinId::Substring => { - let Some(super::vm::NixNum::Int(start)) = VM::as_num(args[0]) else { - return BuiltinResult::Error(VM::err( - "builtins.substring: start is not an integer", - )); - }; - let Some(super::vm::NixNum::Int(len)) = VM::as_num(args[1]) else { - return BuiltinResult::Error(VM::err( - "builtins.substring: length is not an integer", - )); - }; - let Some(s) = VM::get_string(args[2], ctx.strings) else { - return BuiltinResult::Error(VM::err( - "builtins.substring: third argument is not a string", - )); - }; - let start = start.max(0) as usize; - if start >= s.len() { - let ns = Gc::new(ctx.mc, NixString::new("")); - return BuiltinResult::Done(Value::new_gc(ns)); - } - let end = if len < 0 { - s.len() - } else { - (start + len as usize).min(s.len()) - }; - let result = &s[start..end]; - let ns = Gc::new(ctx.mc, NixString::new(result)); - BuiltinResult::Done(Value::new_gc(ns)) - } - - BuiltinId::ToString => { - let v = args[0]; - if let Some(s) = VM::get_string(v, ctx.strings) { - let ns = Gc::new(ctx.mc, NixString::new(s)); - BuiltinResult::Done(Value::new_gc(ns)) - } else if let Some(b) = v.as_inline::() { - let s = if b { "1" } else { "" }; - let ns = Gc::new(ctx.mc, NixString::new(s)); - BuiltinResult::Done(Value::new_gc(ns)) - } else if v.is::() { - let ns = Gc::new(ctx.mc, NixString::new("")); - BuiltinResult::Done(Value::new_gc(ns)) - } else if let Some(n) = VM::as_num(v) { - let s = match n { - super::vm::NixNum::Int(i) => i.to_string(), - super::vm::NixNum::Float(f) => format!("{f}"), - }; - let ns = Gc::new(ctx.mc, NixString::new(s)); - BuiltinResult::Done(Value::new_gc(ns)) - } else { - BuiltinResult::Error(VM::err("builtins.toString: cannot coerce to string")) - } - } - - BuiltinId::Abort => { - let Some(msg) = VM::get_string(args[0], ctx.strings) else { - return BuiltinResult::Error(VM::err("builtins.abort: argument is not a string")); - }; - BuiltinResult::Error(VmError::Uncatchable(crate::error::Error::eval_error( - format!("evaluation aborted with the following error message: '{msg}'"), - ))) - } - - BuiltinId::Throw => { - let Some(msg) = VM::get_string(args[0], ctx.strings) else { - return BuiltinResult::Error(VM::err("builtins.throw: argument is not a string")); - }; - BuiltinResult::Error(VmError::Catchable(msg.to_owned())) - } - - BuiltinId::FunctionArgs => { - let v = args[0]; - if let Some(closure) = v.as_gc::>() { - if let Some(ref pattern) = closure.pattern { - let mut entries = SmallVec::<[(StringId, Value<'gc>); 4]>::new(); - for &name in &pattern.required { - entries.push((name, Value::new_inline(false))); - } - for &name in &pattern.optional { - entries.push((name, Value::new_inline(true))); - } - entries.sort_by_key(|(k, _)| *k); - let attrs = Gc::new(ctx.mc, unsafe { AttrSet::from_sorted_unchecked(entries) }); - BuiltinResult::Done(Value::new_gc(attrs)) - } else { - BuiltinResult::Done(Value::new_gc(Gc::new(ctx.mc, AttrSet::default()))) - } - } else { - BuiltinResult::Done(Value::new_gc(Gc::new(ctx.mc, AttrSet::default()))) - } - } - - BuiltinId::FoldlStrict => FoldlStrict::call(args, ctx), - - BuiltinId::Elem => { - let Some(list) = args[1].as_gc::>() else { - return BuiltinResult::Error(VM::err( - "builtins.elem: second argument is not a list", - )); - }; - let needle = args[0]; - for item in list.inner.iter() { - let sv = force_inline_or_err!(ctx, *item); - if ctx.vm.values_equal(needle, sv, ctx.strings) { - return BuiltinResult::Done(Value::new_inline(true)); - } - } - BuiltinResult::Done(Value::new_inline(false)) - } - - BuiltinId::ReplaceStrings => { - let Some(from_list) = args[0].as_gc::>() else { - return BuiltinResult::Error(VM::err( - "builtins.replaceStrings: first argument is not a list", - )); - }; - let Some(to_list) = args[1].as_gc::>() else { - return BuiltinResult::Error(VM::err( - "builtins.replaceStrings: second argument is not a list", - )); - }; - let Some(s) = VM::get_string(args[2], ctx.strings) else { - return BuiltinResult::Error(VM::err( - "builtins.replaceStrings: third argument is not a string", - )); - }; - if from_list.inner.len() != to_list.inner.len() { - return BuiltinResult::Error(VM::err( - "builtins.replaceStrings: lists must have same length", - )); - } - - let mut from_strs = Vec::new(); - let mut to_strs = Vec::new(); - for (f, t) in from_list.inner.iter().zip(to_list.inner.iter()) { - let fv = force_inline_or_err!(ctx, *f); - let tv = force_inline_or_err!(ctx, *t); - let Some(fs) = VM::get_string(fv, ctx.strings) else { - return BuiltinResult::Error(VM::err( - "builtins.replaceStrings: from element is not a string", - )); - }; - let Some(ts) = VM::get_string(tv, ctx.strings) else { - return BuiltinResult::Error(VM::err( - "builtins.replaceStrings: to element is not a string", - )); - }; - from_strs.push(fs.to_owned()); - to_strs.push(ts.to_owned()); - } - - let s = s.to_owned(); - let mut result = String::new(); - let mut i = 0; - while i < s.len() { - let mut found = false; - for (j, from) in from_strs.iter().enumerate() { - if from.is_empty() { - result.push_str(&to_strs[j]); - result.push(s.as_bytes()[i] as char); - i += 1; - found = true; - break; - } - if s[i..].starts_with(from.as_str()) { - result.push_str(&to_strs[j]); - i += from.len(); - found = true; - break; - } - } - if !found { - result.push(s.as_bytes()[i] as char); - i += 1; - } - } - if from_strs.iter().any(|f| f.is_empty()) { - let j = from_strs - .iter() - .position(|f| f.is_empty()) - .expect("just checked"); - result.push_str(&to_strs[j]); - } - - let ns = Gc::new(ctx.mc, NixString::new(result)); - BuiltinResult::Done(Value::new_gc(ns)) - } - - BuiltinId::ConcatStringsSep => { - let Some(sep) = VM::get_string(args[0], ctx.strings) else { - return BuiltinResult::Error(VM::err( - "builtins.concatStringsSep: first argument is not a string", - )); - }; - let sep = sep.to_owned(); - let Some(list) = args[1].as_gc::>() else { - return BuiltinResult::Error(VM::err( - "builtins.concatStringsSep: second argument is not a list", - )); - }; - let mut result = String::new(); - for (i, item) in list.inner.iter().enumerate() { - if i > 0 { - result.push_str(&sep); - } - let sv = force_inline_or_err!(ctx, *item); - let Some(s) = VM::get_string(sv, ctx.strings) else { - return BuiltinResult::Error(VM::err( - "builtins.concatStringsSep: element is not a string", - )); - }; - result.push_str(s); - } - let ns = Gc::new(ctx.mc, NixString::new(result)); - BuiltinResult::Done(Value::new_gc(ns)) - } - - BuiltinId::FromJSON => { - let Some(s) = VM::get_string(args[0], ctx.strings) else { - return BuiltinResult::Error(VM::err("builtins.fromJSON: not a string")); - }; - match serde_json::from_str::(s) { - Ok(json) => { - let v = json_to_nix(&json, ctx.mc, ctx.strings); - BuiltinResult::Done(v) - } - Err(e) => BuiltinResult::Error(VM::err(format!("builtins.fromJSON: {e}"))), - } - } - - BuiltinId::ToJSON => BuiltinResult::Error(VM::err("builtins.toJSON: not yet implemented")), - - BuiltinId::Null => BuiltinResult::Done(Value::new_inline(Null)), - - _ => BuiltinResult::Error(VM::err(format!("builtin {:?} not yet implemented", id))), - } -} - -pub(super) fn dispatch_lazy_builtin<'gc>( - id: BuiltinId, - args: &PrimOpArgs<'gc>, - _arity: u8, - ctx: &PrimOpCtx<'_, 'gc>, -) -> BuiltinResult<'gc> { - match id { - BuiltinId::Seq => { - let _ = force_inline_or_err!(ctx, args[0]); - BuiltinResult::Done(args[1]) - } - BuiltinId::DeepSeq => { - // TODO: deep force - let _ = force_inline_or_err!(ctx, args[0]); - BuiltinResult::Done(args[1]) - } - BuiltinId::Trace => { - let sv = force_inline_or_err!(ctx, args[0]); - if let Some(s) = VM::get_string(sv, ctx.strings) { - eprintln!("trace: {s}"); - } else { - eprintln!("trace: "); - } - BuiltinResult::Done(args[1]) - } - BuiltinId::Warn => { - let sv = force_inline_or_err!(ctx, args[0]); - if let Some(s) = VM::get_string(sv, ctx.strings) { - eprintln!("warning: {s}"); - } else { - eprintln!("warning: "); - } - BuiltinResult::Done(args[1]) - } - BuiltinId::TryEval => BuiltinResult::Error(VM::err( - "builtins.tryEval: requires catch frame support (TODO)", - )), - BuiltinId::AddErrorContext => BuiltinResult::Done(args[1]), - BuiltinId::Break => BuiltinResult::Done(args[0]), - _ => BuiltinResult::Error(VM::err(format!( - "lazy builtin {:?} not yet implemented", - id - ))), - } -} - -fn json_to_nix<'gc>( - json: &serde_json::Value, - mc: &Mutation<'gc>, - strings: &DefaultStringInterner, -) -> Value<'gc> { - match json { - serde_json::Value::Null => Value::new_inline(Null), - serde_json::Value::Bool(b) => Value::new_inline(*b), - serde_json::Value::Number(n) => { - if let Some(i) = n.as_i64() { - VM::make_int(i, mc) - } else if let Some(f) = n.as_f64() { - Value::new_float(f) - } else { - Value::new_inline(Null) - } - } - serde_json::Value::String(s) => { - let ns = Gc::new(mc, NixString::new(s.as_str())); - Value::new_gc(ns) - } - serde_json::Value::Array(arr) => { - let items: SmallVec<[Value<'gc>; 4]> = - arr.iter().map(|v| json_to_nix(v, mc, strings)).collect(); - Value::new_gc(Gc::new(mc, List { inner: items })) - } - serde_json::Value::Object(obj) => { - let mut entries = SmallVec::<[(StringId, Value<'gc>); 4]>::new(); - for (k, v) in obj { - if let Some(sym) = strings.get(k.as_str()) { - entries.push((StringId(sym), json_to_nix(v, mc, strings))); - } - } - entries.sort_by_key(|(k, _)| *k); - let attrs = Gc::new(mc, unsafe { AttrSet::from_sorted_unchecked(entries) }); - Value::new_gc(attrs) - } - } -} - -#[derive(Collect, Debug)] -#[collect(no_drop)] -pub(super) struct FoldlStrict<'gc> { - op: StrictValue<'gc>, - list: Gc<'gc, List<'gc>>, - acc: StrictValue<'gc>, - index: usize, - phase: FoldlPhase<'gc>, -} - -#[derive(Collect, Debug)] -#[collect(no_drop)] -enum FoldlPhase<'gc> { - CallOp, - CallPartial(StrictValue<'gc>), -} - -impl<'gc> FoldlStrict<'gc> { - fn call(args: PrimOpStrictArgs<'gc>, ctx: &PrimOpCtx<'_, 'gc>) -> BuiltinResult<'gc> { - let op = args[0]; - let nul = args[1]; - let Some(list) = args[2].as_gc::>() else { - return BuiltinResult::Error(VM::err("builtins.foldl': third argument is not a list")); - }; - if list.inner.is_empty() { - return BuiltinResult::Done(nul.relax()); - } - let state = FoldlStrict { - op, - list, - acc: nul, - index: 0, - phase: FoldlPhase::CallOp, - }; - state.step(ctx) - } - - fn step(self, _ctx: &PrimOpCtx<'_, 'gc>) -> BuiltinResult<'gc> { - let state = BuiltinState::FoldlStrict(FoldlStrict { - op: self.op, - list: self.list, - acc: self.acc, - index: self.index, - phase: FoldlPhase::CallOp, - }); - call!(ctx, state, self.op, self.acc.relax()) - } - - fn resume(mut self, val: StrictValue<'gc>, ctx: &PrimOpCtx<'_, 'gc>) -> BuiltinResult<'gc> { - match self.phase { - FoldlPhase::CallOp => { - let partial = val; - let elem = self.list.inner[self.index]; - self.phase = FoldlPhase::CallPartial(partial); - let state = BuiltinState::FoldlStrict(self); - call_and_force!(ctx, state, partial, elem) - } - FoldlPhase::CallPartial(_) => { - self.acc = val; - self.index += 1; - self.phase = FoldlPhase::CallOp; - if self.index >= self.list.inner.len() { - return BuiltinResult::Done(self.acc.relax()); - } - self.step(ctx) - } - } - } -} diff --git a/fix/src/runtime/stack.rs b/fix/src/runtime/stack.rs index 24bc9c2..278ac47 100644 --- a/fix/src/runtime/stack.rs +++ b/fix/src/runtime/stack.rs @@ -33,13 +33,6 @@ impl Stack { } } - pub(super) unsafe fn push_unchecked(&mut self, val: T) { - unsafe { - self.inner.get_unchecked_mut(self.len).write(val); - } - self.len += 1; - } - pub(super) fn push(&mut self, val: T) -> Result<(), T> { if self.len == N { return Err(val); diff --git a/fix/src/runtime/value.rs b/fix/src/runtime/value.rs index 9e94cbe..ddd7f1a 100644 --- a/fix/src/runtime/value.rs +++ b/fix/src/runtime/value.rs @@ -3,18 +3,22 @@ use std::marker::PhantomData; use std::mem::size_of; use std::ops::Deref; -use gc_arena::{Collect, Gc, Mutation, RefLock, collect::Trace}; +use gc_arena::collect::Trace; +use gc_arena::{Collect, Gc, Mutation, RefLock}; use num_enum::TryFromPrimitive; use sealed::sealed; use smallvec::SmallVec; -use string_interner::{Symbol, symbol::SymbolU32}; +use string_interner::Symbol; +use string_interner::symbol::SymbolU32; use crate::boxing::{RawBox, RawStore, RawTag, Value as RawValue}; -use crate::{ir::StringId, runtime::builtins::BuiltinId}; +use crate::ir::StringId; +use crate::runtime::builtins::BuiltinId; #[sealed] /// # Safety -/// TAG must be unique among all implementors. +/// 1. TAG must be unique among all implementors. +/// 2. TAG must be within 1..=7 pub(crate) unsafe trait Storable { const TAG: (bool, u8); } @@ -89,7 +93,21 @@ macro_rules! define_value_types { self.as_inline::<$itype>().unwrap_unchecked() }),)* $(Some(<$gtype as Storable>::TAG) => - write!(f, "{}({:?})", $gname, unsafe { self.as_gc::<$gtype>().unwrap_unchecked() }),)* + write!(f, "{}(..)", $gname),)* + Some((neg, val)) => write!(f, "Unknown(neg={neg}, val={val})"), + } + } + } + + impl fmt::Debug for StaticValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.tag() { + None => write!(f, "Float({:?})", unsafe { + self.raw.float().unwrap_unchecked() + }), + $(Some(<$itype as Storable>::TAG) => write!(f, "{}({:?})", $iname, unsafe { + self.as_inline::<$itype>().unwrap_unchecked() + }),)* Some((neg, val)) => write!(f, "Unknown(neg={neg}, val={val})"), } } @@ -134,12 +152,6 @@ impl Default for Value<'_> { } impl<'gc> Value<'gc> { - #[inline(always)] - fn mk_tag(neg: bool, val: u8) -> RawTag { - // Safety: val is asserted to be in 1..=7. - unsafe { RawTag::new_unchecked(neg, val) } - } - #[inline(always)] fn from_raw_value(rv: RawValue) -> Self { Self { @@ -180,13 +192,28 @@ impl<'gc> Value<'gc> { #[inline] pub(crate) fn new_inline(val: T) -> Self { - Self::from_raw_value(RawValue::store(Self::mk_tag(T::TAG.0, T::TAG.1), val)) + Self::from_raw_value(RawValue::store( + unsafe { RawTag::new_unchecked(T::TAG.0, T::TAG.1) }, + val, + )) } #[inline] pub(crate) fn new_gc(gc: Gc<'gc, T>) -> Self { let ptr = Gc::as_ptr(gc); - Self::from_raw_value(RawValue::store(Self::mk_tag(T::TAG.0, T::TAG.1), ptr)) + Self::from_raw_value(RawValue::store( + unsafe { RawTag::new_unchecked(T::TAG.0, T::TAG.1) }, + ptr, + )) + } + + #[inline] + pub(crate) fn make_int(val: i64, mc: &Mutation<'gc>) -> Self { + if val >= i32::MIN as i64 && val <= i32::MAX as i64 { + Value::new_inline(val as i32) + } else { + Value::new_gc(Gc::new(mc, val)) + } } } @@ -232,6 +259,100 @@ impl<'gc> Value<'gc> { None } } + + #[inline] + pub(crate) fn restrict(self) -> Option> { + if !self.is::>() { + Some(StrictValue(self)) + } else { + None + } + } +} + +#[derive(Copy, Clone)] +#[repr(transparent)] +pub(crate) struct StaticValue { + raw: RawBox, +} + +impl Default for StaticValue { + #[inline(always)] + fn default() -> Self { + Self::new_inline(Null) + } +} + +impl From for Value<'_> { + #[inline] + fn from(value: StaticValue) -> Self { + Self { + raw: value.raw, + _marker: PhantomData, + } + } +} + +impl StaticValue { + #[inline(always)] + fn from_raw_value(rv: RawValue) -> Self { + Self { + raw: RawBox::from_value(rv), + } + } + + #[inline] + pub(crate) fn to_bits(self) -> u64 { + self.raw.to_bits() + } + + #[inline] + pub(crate) fn new_float(val: f64) -> Self { + Self { + raw: RawBox::from_float(val), + } + } + + #[inline] + pub(crate) fn new_inline(val: T) -> Self { + Self::from_raw_value(RawValue::store( + unsafe { RawTag::new_unchecked(T::TAG.0, T::TAG.1) }, + val, + )) + } + + #[inline] + pub(crate) fn is_float(&self) -> bool { + self.raw.is_float() + } + + #[inline] + pub(crate) fn is(&self) -> bool { + self.tag() == Some(T::TAG) + } + + #[inline] + pub(crate) fn as_float(&self) -> Option { + self.raw.float().copied() + } + + #[inline] + pub(crate) fn as_inline(&self) -> Option { + if self.is::() { + Some(unsafe { + let rv = self.raw.value().unwrap_unchecked(); + T::from_val(rv) + }) + } else { + None + } + } + + /// Returns the `(negative, val)` tag, or `None` for a float. + #[inline(always)] + fn tag(&self) -> Option<(bool, u8)> { + self.raw.tag().map(|t| t.neg_val()) + } } #[derive(Clone, Copy, Debug)] @@ -364,7 +485,7 @@ pub(crate) type Thunk<'gc> = RefLock>; #[collect(no_drop)] pub(crate) enum ThunkState<'gc> { Pending { - ip: u32, + ip: usize, env: Gc<'gc, RefLock>>, }, Apply { @@ -454,15 +575,6 @@ pub(crate) struct PrimOpApp<'gc> { pub(crate) struct StrictValue<'gc>(Value<'gc>); impl<'gc> StrictValue<'gc> { - #[inline] - pub(crate) fn try_from_forced(val: Value<'gc>) -> Option { - if !val.is::>() { - Some(Self(val)) - } else { - None - } - } - #[inline] pub(crate) fn relax(self) -> Value<'gc> { self.0 diff --git a/fix/src/runtime/vm.rs b/fix/src/runtime/vm.rs index 316f503..9c7f57b 100644 --- a/fix/src/runtime/vm.rs +++ b/fix/src/runtime/vm.rs @@ -1,17 +1,23 @@ use std::path::PathBuf; +use gc_arena::arena::CollectionPhase; use gc_arena::{Collect, Gc, Mutation, RefLock}; use hashbrown::HashMap; use num_enum::TryFromPrimitive; use smallvec::SmallVec; use string_interner::{DefaultStringInterner, Symbol as _}; -use super::builtins::{BUILTINS, BuiltinId, PrimOpArgs, PrimOpStrictArgs, is_lazy_builtin}; -use super::primops::{BuiltinResult, BuiltinState, PrimOpCtx, dispatch_lazy_builtin, dispatch_strict_builtin}; +use super::Runtime; +use super::builtins::{BUILTINS, BuiltinId, PrimOpArgs, PrimOpStrictArgs}; use super::stack::Stack; use super::value::*; +use crate::codegen::{ + InstructionPtr, KEY_DYNAMIC, KEY_STATIC, OPERAND_BIGINT, OPERAND_BUILTINS, OPERAND_CONST, + OPERAND_LOCAL, +}; use crate::error::{Error, Result}; -use crate::ir::StringId; +use crate::ir::{Ir, RawIrRef, StringId}; +use crate::runtime::init_builtins; pub(super) type VmResult = std::result::Result; @@ -26,201 +32,55 @@ impl From> for VmError { } } +#[derive(Collect, Clone, Copy, Debug, PartialEq, Eq)] +#[collect(require_static)] +pub(super) enum ForceMode { + AsIs, + Shallow, + Deep, +} + #[derive(Collect)] #[collect(no_drop)] -pub(super) struct VM<'gc> { +pub(super) struct GcRoot<'gc> { stack: Stack<65536, Value<'gc>>, + temp_stack: Vec>, frames: Stack<8192, CallFrame<'gc>>, with_scope: Option>>, - error_contexts: Stack<8192, ErrorFrame>, - globals: GlobalState<'gc>, - import_cache: HashMap>, - pc: usize, - current_env: Option>>>, - started: bool, -} - -#[derive(Collect)] -#[collect(no_drop)] -struct GlobalState<'gc> { builtins: Value<'gc>, - builtin_lookup: HashMap, empty_list: Value<'gc>, empty_attrs: Value<'gc>, + import_cache: HashMap>, + current_env: Option>>>, } -#[derive(Collect)] -#[collect(require_static)] -struct ErrorFrame { - span_id: u32, - message: Option, -} - -#[derive(Collect, Debug)] -#[collect(no_drop)] -pub(super) struct WithScope<'gc> { - env: Value<'gc>, - prev: Option>>, -} - -#[derive(Collect, Debug)] -#[collect(no_drop)] -struct CallFrame<'gc> { - pc: usize, - env: Gc<'gc, RefLock>>, - continuation: Continuation<'gc>, - span: Option, -} - -#[derive(Collect, Debug)] -#[collect(no_drop)] -enum Continuation<'gc> { - Return, - ForceThunk { - thunk: Gc<'gc, Thunk<'gc>>, - after: AfterForce<'gc>, - }, - BuiltinReturn(BuiltinState<'gc>), - BuiltinCallAndForce(BuiltinState<'gc>), -} - -#[derive(Collect, Debug)] -#[collect(no_drop)] -pub(super) enum AfterForce<'gc> { - Identity, - ForceBool, - BinOpLhs { - rhs: Value<'gc>, - #[collect(require_static)] - op: BinOpTag, - }, - BinOpRhs { - lhs_forced: StrictValue<'gc>, - #[collect(require_static)] - op: BinOpTag, - }, - UnNeg, - UnNot, - Call { - arg: Value<'gc>, - span: Option, - }, - PatternCallArgForce { - func: StrictValue<'gc>, - span: Option, - }, - Select { - keys: SmallVec<[Value<'gc>; 4]>, - remaining: u16, - span: u32, - default: Option>, - }, - HasAttr { - keys: SmallVec<[Value<'gc>; 4]>, - remaining: u16, - }, - Assert { - expr: Value<'gc>, - raw_idx: u32, - span_id: u32, - }, - ConcatStrings { - forced: SmallVec<[StrictValue<'gc>; 8]>, - remaining: SmallVec<[Value<'gc>; 8]>, - force_string: bool, - }, - PushWith, - WithLookup { - #[collect(require_static)] - name: StringId, - next: Option>>, - }, - TopLevelForce, - DiscardAndPush { - value: Value<'gc>, - }, - Builtin(BuiltinState<'gc>), - BuiltinArgForce { - #[collect(require_static)] - id: BuiltinId, - #[collect(require_static)] - arity: u8, - #[collect(require_static)] - forced_count: u8, - args: PrimOpStrictArgs<'gc>, - remaining: PrimOpArgs<'gc>, - }, -} - -#[derive(Clone, Copy, Debug)] -pub(super) enum BinOpTag { - Add, - Sub, - Mul, - Div, - Eq, - Neq, - Lt, - Gt, - Leq, - Geq, - Concat, - Update, -} - -pub(super) enum ForceResult<'gc> { - Ready(StrictValue<'gc>), - NeedEval { - ip: u32, - env: Gc<'gc, RefLock>>, - thunk: Gc<'gc, Thunk<'gc>>, - }, - NeedApply(Gc<'gc, Thunk<'gc>>), -} - -pub(crate) enum Action { - Continue, - Done(Result), - NeedGc, - IoRequest(()), -} - -pub(super) enum NixNum { - Int(i64), - Float(f64), -} - -macro_rules! try_vm { - ($self:ident, $expr:expr) => { - match $expr { - Ok(v) => v, - Err(e) => return VM::handle_vm_error($self, e), - } +pub(super) fn new_gc_root<'gc>( + mc: &Mutation<'gc>, + strings: &mut DefaultStringInterner, +) -> ( + GcRoot<'gc>, + HashMap>>, +) { + let (global_env, builtins) = init_builtins(mc, strings); + let root = GcRoot { + stack: Stack::new(), + temp_stack: Vec::new(), + frames: Stack::new(), + with_scope: None, + builtins, + empty_list: Value::new_gc(Gc::new(mc, List::default())), + empty_attrs: Value::new_gc(Gc::new(mc, AttrSet::default())), + import_cache: HashMap::new(), + current_env: None, }; + (root, global_env) } -impl<'gc> VM<'gc> { - pub(super) fn new(mc: &Mutation<'gc>, strings: &mut DefaultStringInterner) -> Self { - let (builtins, builtin_lookup) = Self::init_builtins(mc, strings); - Self { - stack: Stack::new(), - frames: Stack::new(), - with_scope: None, - error_contexts: Stack::new(), - globals: GlobalState { - builtins, - builtin_lookup, - empty_list: Value::new_gc(Gc::new(mc, List::default())), - empty_attrs: Value::new_gc(Gc::new(mc, AttrSet::default())), - }, - import_cache: HashMap::new(), - pc: 0, - current_env: None, - started: false, - } - } - - fn init_builtins(mc: &Mutation<'gc>, strings: &mut DefaultStringInterner) -> (Value<'gc>, HashMap) { +impl<'gc> GcRoot<'gc> { + fn init_builtins( + mc: &Mutation<'gc>, + strings: &mut DefaultStringInterner, + ) -> (Value<'gc>, HashMap) { let mut builtin_lookup = HashMap::new(); let mut entries = SmallVec::new(); @@ -235,10 +95,8 @@ impl<'gc> VM<'gc> { }; builtin_lookup.insert(sid, primop); - if arity == 0 { - // "null" constant - entries.push((sid, Value::new_inline(Null))); - } else { + // Regular primop + if arity != 0 { entries.push((sid, Value::new_inline(primop))); } } @@ -272,6 +130,7 @@ impl<'gc> VM<'gc> { } )) ); + add_const!("null", Value::new_inline(Null)); add_const!("true", Value::new_inline(true)); add_const!("false", Value::new_inline(false)); @@ -282,9 +141,7 @@ impl<'gc> VM<'gc> { entries.sort_by_key(|(k, _)| *k); - let builtins_set = Gc::new(mc, unsafe { - AttrSet::from_sorted_unchecked(entries) - }); + let builtins_set = Gc::new(mc, unsafe { AttrSet::from_sorted_unchecked(entries) }); let builtins_val = Value::new_gc(builtins_set); // Populate the self-reference @@ -294,1611 +151,464 @@ impl<'gc> VM<'gc> { } #[inline(always)] - fn read_array(&mut self, bc: &[u8]) -> [u8; N] { - #[cfg(debug_assertions)] - let ret = bc[self.pc..self.pc + N] + fn env(&self) -> Gc<'gc, RefLock>> { + self.current_env.expect("no current env") + } + + #[inline(always)] + pub(super) fn push_stack(&mut self, val: Value<'gc>) { + self.stack.push(val).expect("stack overflow"); + } + + #[inline(always)] + pub(super) fn pop_stack(&mut self) -> Value<'gc> { + self.stack.pop().expect("stack underflow") + } + + #[inline(always)] + pub(super) fn pop_stack_forced(&mut self) -> StrictValue<'gc> { + self.stack.pop().expect("stack underflow").restrict().expect("forced") + } +} + +pub(super) struct ErrorFrame { + span_id: u32, + message: Option, +} + +#[derive(Collect, Debug)] +#[collect(no_drop)] +pub(super) struct WithScope<'gc> { + env: Value<'gc>, + prev: Option>>, +} + +#[derive(Collect, Debug)] +#[collect(no_drop)] +struct CallFrame<'gc> { + pc: usize, + env: Gc<'gc, RefLock>>, +} + +#[derive(Clone, Copy, Debug)] +pub(super) enum BinOpTag { + Add, + Sub, + Mul, + Div, + Eq, + Neq, + Lt, + Gt, + Leq, + Geq, + Concat, + Update, +} + +pub(super) enum ForceResult<'gc> { + Ready(StrictValue<'gc>), + NeedEval { + ip: u32, + env: Gc<'gc, RefLock>>, + thunk: Gc<'gc, Thunk<'gc>>, + }, + NeedApply(Gc<'gc, Thunk<'gc>>), +} + +pub(crate) enum Action { + Continue, + Return, + Done(Result), +} + +pub(super) enum NixNum { + Int(i64), + Float(f64), +} + +enum OperandData { + Const(StaticValue), + Local { layer: u8, idx: u32 }, + Builtins, + BigInt(i64), +} + +impl OperandData { + fn resolve<'gc>(&self, mc: &Mutation<'gc>, root: &GcRoot<'gc>) -> Value<'gc> { + match *self { + OperandData::Const(sv) => sv.into(), + OperandData::Local { layer, idx } => { + let mut cur = root.env(); + for _ in 0..layer { + let prev = cur.borrow().prev.expect("env chain too short"); + cur = prev; + } + cur.borrow().locals[idx as usize] + } + OperandData::Builtins => root.builtins, + OperandData::BigInt(val) => Value::new_gc(Gc::new(mc, val)), + } + } +} + +enum AttrKeyData { + Static(StringId), + Dynamic(OperandData), +} + +struct AttrEntry { + key: AttrKeyData, + val: OperandData, +} + +enum SelectKeyData { + Static(StringId), + Dynamic, +} + +macro_rules! try_vm { + ($self:ident, $expr:expr) => { + match $expr { + Ok(v) => v, + Err(e) => return Runtime::handle_vm_error($self, e), + } + }; +} + +impl Runtime { + #[inline(always)] + fn read_array(&mut self) -> [u8; N] { + let ret = self.bytecode[self.pc..self.pc + N] .try_into() .expect("read_array failed"); - #[cfg(not(debug_assertions))] - let ret = unsafe { bc[self.pc..self.pc + N].try_into().unwrap_unchecked() }; self.pc += N; ret } #[inline(always)] - fn read_u8(&mut self, bc: &[u8]) -> u8 { - u8::from_le_bytes(self.read_array(bc)) + fn read_u8(&mut self) -> u8 { + u8::from_le_bytes(self.read_array()) } #[inline(always)] - fn read_u16(&mut self, bc: &[u8]) -> u16 { - u16::from_le_bytes(self.read_array(bc)) + fn read_u16(&mut self) -> u16 { + u16::from_le_bytes(self.read_array()) } #[inline(always)] - fn read_u32(&mut self, bc: &[u8]) -> u32 { - u32::from_le_bytes(self.read_array(bc)) + fn read_u32(&mut self) -> u32 { + u32::from_le_bytes(self.read_array()) } #[inline(always)] - fn read_i32(&mut self, bc: &[u8]) -> i32 { - i32::from_le_bytes(self.read_array(bc)) + fn read_i32(&mut self) -> i32 { + i32::from_le_bytes(self.read_array()) } #[inline(always)] - fn read_i64(&mut self, bc: &[u8]) -> i64 { - i64::from_le_bytes(self.read_array(bc)) + fn read_i64(&mut self) -> i64 { + i64::from_le_bytes(self.read_array()) } #[inline(always)] - fn read_f64(&mut self, bc: &[u8]) -> f64 { - f64::from_le_bytes(self.read_array(bc)) + fn read_f64(&mut self) -> f64 { + f64::from_le_bytes(self.read_array()) } #[inline(always)] - fn read_string_id(&mut self, bc: &[u8]) -> StringId { - let raw = self.read_u32(bc); + fn read_string_id(&mut self) -> StringId { + let raw = self.read_u32(); StringId(unsafe { string_interner::symbol::SymbolU32::try_from_usize(raw as usize).unwrap_unchecked() }) } + fn read_operand(&mut self) -> OperandData { + let tag = self.read_u8(); + match tag { + OPERAND_CONST => { + let idx = self.read_u32(); + OperandData::Const(self.constants[idx as usize]) + } + OPERAND_LOCAL => { + let layer = self.read_u8(); + let idx = self.read_u32(); + OperandData::Local { layer, idx } + } + OPERAND_BUILTINS => OperandData::Builtins, + OPERAND_BIGINT => { + let val = self.read_i64(); + OperandData::BigInt(val) + } + _ => panic!("unknown operand tag: {tag:#04x}"), + } + } + + fn read_attr_keys(&mut self, n: usize) -> SmallVec<[SelectKeyData; 4]> { + let mut keys = SmallVec::with_capacity(n); + for _ in 0..n { + let tag = self.read_u8(); + match tag { + KEY_STATIC => { + let sid = self.read_string_id(); + keys.push(SelectKeyData::Static(sid)); + } + KEY_DYNAMIC => { + keys.push(SelectKeyData::Dynamic); + } + _ => panic!("unknown key tag: {tag:#04x}"), + } + } + keys + } + #[inline(always)] - fn env(&self) -> Gc<'gc, RefLock>> { - self.current_env.expect("no current env") - } - - pub(super) fn force_inline(&self, val: Value<'gc>) -> VmResult> { - let mut current = val; - loop { - let Some(thunk) = current.as_gc::>() else { - return Ok(ForceResult::Ready(unsafe { - StrictValue::try_from_forced(current).unwrap_unchecked() - })); - }; - let thunk_ref = thunk.borrow(); - match &*thunk_ref { - ThunkState::Evaluated(v) => { - current = *v; - drop(thunk_ref); - } - &ThunkState::Pending { ip, env } => { - return Ok(ForceResult::NeedEval { ip, env, thunk }); - } - ThunkState::Apply { .. } => { - return Ok(ForceResult::NeedApply(thunk)); - } - ThunkState::Blackhole => { - return Err(VmError::Uncatchable(Error::eval_error( - "infinite recursion encountered", - ))); - } - } - } - } - - pub(super) fn push_force_frame( - &mut self, - thunk: Gc<'gc, Thunk<'gc>>, - after: AfterForce<'gc>, - ip: u32, - env: Gc<'gc, RefLock>>, - mc: &Mutation<'gc>, - ) { - self.frames - .push(CallFrame { - pc: self.pc, - env: self.env(), - continuation: Continuation::ForceThunk { thunk, after }, - span: None, - }) - .expect("frame stack overflow"); - *thunk.borrow_mut(mc) = ThunkState::Blackhole; - self.pc = ip as usize; - self.current_env = Some(env); - } - - fn push_apply_force_frame( - &mut self, - thunk: Gc<'gc, Thunk<'gc>>, - after: AfterForce<'gc>, - mc: &Mutation<'gc>, - strings: &DefaultStringInterner, - ) -> VmResult<()> { - let (func, arg) = match &*thunk.borrow() { - &ThunkState::Apply { func, arg } => (func, arg), - _ => unreachable!(), - }; - *thunk.borrow_mut(mc) = ThunkState::Blackhole; - - self.frames - .push(CallFrame { - pc: self.pc, - env: self.env(), - continuation: Continuation::ForceThunk { thunk, after }, - span: None, - }) - .expect("frame stack overflow"); - - match self.force_inline(func)? { - ForceResult::Ready(f) => { - self.do_call(f, arg, None, mc, strings)?; - } - ForceResult::NeedEval { - ip, - env, - thunk: func_thunk, - } => { - self.push_force_frame( - func_thunk, - AfterForce::Call { arg, span: None }, - ip, - env, - mc, - ); - } - ForceResult::NeedApply(func_thunk) => { - self.push_apply_force_frame( - func_thunk, - AfterForce::Call { arg, span: None }, - mc, - strings, - )?; - } - } - Ok(()) - } - - fn setup_force( - &mut self, - result: ForceResult<'gc>, - after: AfterForce<'gc>, - mc: &Mutation<'gc>, - strings: &DefaultStringInterner, - ) -> VmResult<()> { - match result { - ForceResult::Ready(_) => unreachable!(), - ForceResult::NeedEval { ip, env, thunk } => { - self.push_force_frame(thunk, after, ip, env, mc); - Ok(()) - } - ForceResult::NeedApply(thunk) => self.push_apply_force_frame(thunk, after, mc, strings), - } - } - - pub(super) fn make_int(val: i64, mc: &Mutation<'gc>) -> Value<'gc> { - if val >= i32::MIN as i64 && val <= i32::MAX as i64 { - Value::new_inline(val as i32) - } else { - Value::new_gc(Gc::new(mc, val)) - } - } - - pub(super) fn as_num(val: StrictValue<'gc>) -> Option { - if let Some(i) = val.as_inline::() { - Some(NixNum::Int(i as i64)) - } else if let Some(gc_i) = val.as_gc::() { - Some(NixNum::Int(*gc_i)) - } else { - val.as_float().map(NixNum::Float) - } - } - - pub(super) fn get_string_id(val: &Value<'gc>) -> Option { - val.as_inline::() - } - - pub(super) fn get_string<'a, 'gc1: 'gc + 'a>( - val: StrictValue<'gc1>, - strings: &'a DefaultStringInterner, - ) -> Option<&'a str> { - if let Some(sid) = val.as_inline::() { - Some(strings.resolve(sid.0)?) - } else { - val.as_gc::().map(|ns| ns.as_ref().as_str()) - } - } - - pub(super) fn err(msg: impl Into) -> VmError { - VmError::Uncatchable(Error::eval_error(msg.into())) - } - - pub(super) fn handle_return( - &mut self, - ret_val: Value<'gc>, - mc: &Mutation<'gc>, - strings: &DefaultStringInterner, - ) -> Action { - let Some(frame) = self.frames.pop() else { - return match self.force_inline(ret_val) { - Ok(ForceResult::Ready(v)) => Action::Done(Ok(self.convert_value(&v, strings))), - Ok(other) => { - try_vm!(self, self.setup_force(other, AfterForce::TopLevelForce, mc, strings)); - Action::Continue - } - Err(e) => self.handle_vm_error(e), - }; - }; - - self.pc = frame.pc; - self.current_env = Some(frame.env); - - match frame.continuation { - Continuation::Return => { - self.push_stack(ret_val); - Action::Continue - } - Continuation::ForceThunk { thunk, after } => { - *thunk.borrow_mut(mc) = ThunkState::Evaluated(ret_val); - match self.force_inline(ret_val) { - Ok(ForceResult::Ready(strict)) => { - self.resume_after_force(strict, after, mc, strings) - } - Ok(other) => { - try_vm!(self, self.setup_force(other, after, mc, strings)); - Action::Continue - } - Err(e) => self.handle_vm_error(e), - } - } - Continuation::BuiltinReturn(state) => { - let ctx = PrimOpCtx { - vm: self, - mc, - strings, - }; - match ctx.vm.force_inline(ret_val) { - Ok(ForceResult::Ready(strict)) => { - let result = state.resume(strict, &ctx); - self.process_builtin_result(result, mc, strings) - } - Ok(other) => { - try_vm!( - self, - self.setup_force(other, AfterForce::Builtin(state), mc, strings) - ); - Action::Continue - } - Err(e) => self.handle_vm_error(e), - } - } - Continuation::BuiltinCallAndForce(state) => { - match self.force_inline(ret_val) { - Ok(ForceResult::Ready(strict)) => { - let ctx = PrimOpCtx { - vm: self, - mc, - strings, - }; - let result = state.resume(strict, &ctx); - self.process_builtin_result(result, mc, strings) - } - Ok(other) => { - try_vm!( - self, - self.setup_force(other, AfterForce::Builtin(state), mc, strings) - ); - Action::Continue - } - Err(e) => self.handle_vm_error(e), - } - } - } - } - - pub(super) fn resume_after_force( - &mut self, - val: StrictValue<'gc>, - after: AfterForce<'gc>, - mc: &Mutation<'gc>, - strings: &DefaultStringInterner, - ) -> Action { - match after { - AfterForce::Identity => { - self.push_stack(val.relax()); - Action::Continue - } - AfterForce::ForceBool => { - if val.as_inline::().is_none() { - return Action::Done(Err(Error::eval_error("value is not a boolean"))); - } - self.push_stack(val.relax()); - Action::Continue - } - AfterForce::BinOpLhs { rhs, op } => match self.force_inline(rhs) { - Ok(ForceResult::Ready(rhs_forced)) => { - match self.compute_binop(op, val, rhs_forced, mc, strings) { - Ok(result) => { - self.push_stack(result); - Action::Continue - } - Err(e) => self.handle_vm_error(e), - } - } - Ok(other) => { - try_vm!( - self, - self.setup_force( - other, - AfterForce::BinOpRhs { - lhs_forced: val, - op, - }, - mc, - strings, - ) - ); - Action::Continue - } - Err(e) => self.handle_vm_error(e), - }, - AfterForce::BinOpRhs { lhs_forced, op } => { - match self.compute_binop(op, lhs_forced, val, mc, strings) { - Ok(result) => { - self.push_stack(result); - Action::Continue - } - Err(e) => self.handle_vm_error(e), - } - } - AfterForce::UnNeg => match Self::as_num(val) { - Some(NixNum::Int(i)) => { - self.push_stack(Self::make_int(-i, mc)); - Action::Continue - } - Some(NixNum::Float(f)) => { - self.push_stack(Value::new_float(-f)); - Action::Continue - } - None => Action::Done(Err(Error::eval_error("cannot negate non-number"))), - }, - AfterForce::UnNot => match val.as_inline::() { - Some(b) => { - self.push_stack(Value::new_inline(!b)); - Action::Continue - } - None => Action::Done(Err(Error::eval_error("value is not a boolean"))), - }, - AfterForce::Call { arg, span } => match self.do_call(val, arg, span, mc, strings) { - Ok(()) => Action::Continue, - Err(e) => self.handle_vm_error(e), - }, - AfterForce::PatternCallArgForce { func, span } => { - if let Some(closure_gc) = func.as_gc::>() { - let pattern = closure_gc - .pattern - .as_ref() - .expect("internal: pattern call on non-pattern-closure"); - match self.setup_pattern_call( - closure_gc.ip, - closure_gc.n_locals, - closure_gc.env, - pattern, - val, - span, - mc, - ) { - Ok(()) => Action::Continue, - Err(e) => self.handle_vm_error(e), - } - } else { - Action::Done(Err(Error::eval_error( - "internal: pattern call on non-closure", - ))) - } - } - AfterForce::Select { - keys, - remaining, - span, - default, - } => match self.do_select_step(val, keys, remaining, span, default, mc, strings) { - Ok(()) => Action::Continue, - Err(e) => self.handle_vm_error(e), - }, - AfterForce::HasAttr { keys, remaining } => { - match self.do_has_attr_step(val, keys, remaining, mc, strings) { - Ok(()) => Action::Continue, - Err(e) => self.handle_vm_error(e), - } - } - AfterForce::Assert { - expr, - raw_idx, - span_id: _, - } => match val.as_inline::() { - Some(true) => { - self.push_stack(expr); - Action::Continue - } - Some(false) => { - let sym = string_interner::symbol::SymbolU32::try_from_usize(raw_idx as usize); - let msg = sym.and_then(|s| strings.resolve(s)).unwrap_or(""); - Action::Done(Err(Error::eval_error(format!("assertion '{msg}' failed")))) - } - None => Action::Done(Err(Error::eval_error( - "assertion condition must be a boolean", - ))), - }, - AfterForce::ConcatStrings { - mut forced, - remaining, - force_string, - } => { - forced.push(val); - match self.concat_strings_continue(forced, remaining, force_string, mc, strings) { - Ok(()) => Action::Continue, - Err(e) => self.handle_vm_error(e), - } - } - AfterForce::PushWith => { - let scope = Gc::new( - mc, - WithScope { - env: val.relax(), - prev: self.with_scope, - }, - ); - self.with_scope = Some(scope); - Action::Continue - } - AfterForce::WithLookup { name, next } => { - match self.do_with_lookup_step(val, name, next, mc, strings) { - Ok(()) => Action::Continue, - Err(e) => self.handle_vm_error(e), - } - } - AfterForce::TopLevelForce => Action::Done(Ok(self.convert_value(&val, strings))), - AfterForce::DiscardAndPush { value } => { - self.push_stack(value); - Action::Continue - } - AfterForce::Builtin(state) => { - let ctx = PrimOpCtx { - vm: self, - mc, - strings, - }; - let result = state.resume(val, &ctx); - self.process_builtin_result(result, mc, strings) - } - AfterForce::BuiltinArgForce { - id, - arity, - mut forced_count, - mut args, - remaining, - } => { - args[forced_count as usize] = val; - forced_count += 1; - - while forced_count < arity { - let next = remaining[forced_count as usize]; - match self.force_inline(next) { - Ok(ForceResult::Ready(v)) => { - args[forced_count as usize] = v; - forced_count += 1; - } - Ok(other) => { - try_vm!( - self, - self.setup_force( - other, - AfterForce::BuiltinArgForce { - id, - arity, - forced_count, - args, - remaining, - }, - mc, - strings, - ) - ); - return Action::Continue; - } - Err(e) => return self.handle_vm_error(e), - } - } - - let ctx = PrimOpCtx { - vm: self, - mc, - strings, - }; - let result = dispatch_strict_builtin(id, args, arity, &ctx); - self.process_builtin_result(result, mc, strings) - } - } - } - - fn process_builtin_result( - &mut self, - result: BuiltinResult<'gc>, - mc: &Mutation<'gc>, - strings: &DefaultStringInterner, - ) -> Action { - match result { - BuiltinResult::Done(v) => { - self.push_stack(v); - Action::Continue - } - BuiltinResult::Force(state, val) => { - match self.force_inline(val) { - Ok(ForceResult::Ready(strict)) => { - let ctx = PrimOpCtx { - vm: self, - mc, - strings, - }; - let result = state.resume(strict, &ctx); - self.process_builtin_result(result, mc, strings) - } - Ok(other) => { - try_vm!( - self, - self.setup_force(other, AfterForce::Builtin(state), mc, strings) - ); - Action::Continue - } - Err(e) => self.handle_vm_error(e), - } - } - BuiltinResult::Call(state, func, arg) => { - self.frames - .push(CallFrame { - pc: self.pc, - env: self.env(), - continuation: Continuation::BuiltinReturn(state), - span: None, - }) - .expect("frame stack overflow"); - match self.do_call(func, arg, None, mc, strings) { - Ok(()) => Action::Continue, - Err(e) => self.handle_vm_error(e), - } - } - BuiltinResult::CallAndForce(state, func, arg) => { - self.frames - .push(CallFrame { - pc: self.pc, - env: self.env(), - continuation: Continuation::BuiltinCallAndForce(state), - span: None, - }) - .expect("frame stack overflow"); - match self.do_call(func, arg, None, mc, strings) { - Ok(()) => Action::Continue, - Err(e) => self.handle_vm_error(e), - } - } - BuiltinResult::Error(e) => self.handle_vm_error(e), - } - } - - fn dispatch_primop( - &mut self, - id: BuiltinId, - arity: u8, - args: PrimOpArgs<'gc>, - mc: &Mutation<'gc>, - strings: &DefaultStringInterner, - ) -> VmResult<()> { - let ctx = PrimOpCtx { - vm: self, - mc, - strings, - }; - - let result = if is_lazy_builtin(id) { - dispatch_lazy_builtin(id, &args, arity, &ctx) - } else { - let mut strict_args: PrimOpStrictArgs<'gc> = [StrictValue::default(); 3]; - for i in 0..arity as usize { - match self.force_inline(args[i])? { - ForceResult::Ready(v) => { - strict_args[i] = v; - } - other => { - self.setup_force( - other, - AfterForce::BuiltinArgForce { - id, - arity, - forced_count: i as u8, - args: strict_args, - remaining: args, - }, - mc, - strings, - )?; - return Ok(()); - } - } - } - let ctx = PrimOpCtx { - vm: self, - mc, - strings, - }; - dispatch_strict_builtin(id, strict_args, arity, &ctx) - }; - - match result { - BuiltinResult::Done(v) => { - self.push_stack(v); - Ok(()) - } - other => { - let action = self.process_builtin_result(other, mc, strings); - match action { - Action::Continue => Ok(()), - Action::Done(Err(e)) => Err(VmError::Uncatchable(e)), - _ => Ok(()), - } - } - } - } - - pub(super) fn do_call( - &mut self, - func: StrictValue<'gc>, - arg: Value<'gc>, - span: Option, - mc: &Mutation<'gc>, - strings: &DefaultStringInterner, - ) -> VmResult<()> { - if let Some(closure_gc) = func.as_gc::>() { - let ip = closure_gc.ip; - let n_locals = closure_gc.n_locals; - let closure_env = closure_gc.env; - - if let Some(ref pattern) = closure_gc.pattern { - match self.force_inline(arg)? { - ForceResult::Ready(forced_arg) => { - self.setup_pattern_call( - ip, - n_locals, - closure_env, - pattern, - forced_arg, - span, - mc, - )?; - } - other => { - self.setup_force( - other, - AfterForce::PatternCallArgForce { func, span }, - mc, - strings, - )?; - } - } - } else { - let new_env = Gc::new(mc, RefLock::new(Env::with_arg(arg, n_locals, closure_env))); - self.frames - .push(CallFrame { - pc: self.pc, - env: self.env(), - continuation: Continuation::Return, - span, - }) - .expect("frame stack overflow"); - self.pc = ip as usize; - self.current_env = Some(new_env); - } - Ok(()) - } else if let Some(po) = func.as_inline::() { - if po.arity <= 1 { - let mut primop_args: PrimOpArgs<'gc> = [Value::default(); 3]; - if po.arity == 1 { - primop_args[0] = arg; - } - self.dispatch_primop(po.id, po.arity, primop_args, mc, strings) - } else { - let app = Gc::new( - mc, - PrimOpApp { - primop: po, - args: SmallVec::from_elem(arg, 1), - }, - ); - self.push_stack(Value::new_gc(app)); - Ok(()) - } - } else if let Some(poa) = func.as_gc::>() { - let mut args = poa.args.clone(); - args.push(arg); - if args.len() >= poa.primop.arity as usize { - let mut primop_args: PrimOpArgs<'gc> = [Value::default(); 3]; - for (i, a) in args.iter().enumerate() { - if i < 3 { - primop_args[i] = *a; - } - } - self.dispatch_primop(poa.primop.id, poa.primop.arity, primop_args, mc, strings) - } else { - let app = Gc::new( - mc, - PrimOpApp { - primop: poa.primop, - args, - }, - ); - self.push_stack(Value::new_gc(app)); - Ok(()) - } - } else { - Err(Self::err( - "attempt to call something which is not a function", - )) - } - } - - pub(super) fn setup_pattern_call( - &mut self, - ip: u32, - n_locals: u32, - closure_env: Gc<'gc, RefLock>>, - pattern: &PatternInfo, - arg: StrictValue<'gc>, - span: Option, - mc: &Mutation<'gc>, - ) -> VmResult<()> { - let Some(attrs) = arg.as_gc::>() else { - return Err(Self::err( - "function that expected a set received a non-set argument", - )); - }; - - for &req in &pattern.required { - if !attrs.has(req) { - return Err(Self::err("function argument missing required attribute")); - } - } - - if !pattern.ellipsis { - for (key, _) in attrs.iter() { - let is_known = pattern.required.contains(key) || pattern.optional.contains(key); - if !is_known { - return Err(Self::err("function received unexpected attribute")); - } - } - } - - let new_env = Gc::new( - mc, - RefLock::new(Env::with_arg(arg.relax(), n_locals, closure_env)), - ); - - self.frames - .push(CallFrame { - pc: self.pc, - env: self.env(), - continuation: Continuation::Return, - span, - }) - .expect("frame stack overflow"); - self.pc = ip as usize; - self.current_env = Some(new_env); - Ok(()) - } - - pub(super) fn compute_binop( - &self, - op: BinOpTag, - lhs: StrictValue<'gc>, - rhs: StrictValue<'gc>, - mc: &Mutation<'gc>, - strings: &DefaultStringInterner, - ) -> VmResult> { - match op { - BinOpTag::Add => { - if let (Some(ls), Some(rs)) = ( - Self::get_string(lhs, strings), - Self::get_string(rhs, strings), - ) { - let ns = Gc::new(mc, NixString::new(format!("{ls}{rs}"))); - return Ok(Value::new_gc(ns)); - } - self.numeric_binop(lhs, rhs, mc, i64::wrapping_add, |a, b| a + b) - } - BinOpTag::Sub => self.numeric_binop(lhs, rhs, mc, i64::wrapping_sub, |a, b| a - b), - BinOpTag::Mul => self.numeric_binop(lhs, rhs, mc, i64::wrapping_mul, |a, b| a * b), - BinOpTag::Div => match (Self::as_num(lhs), Self::as_num(rhs)) { - (_, Some(NixNum::Int(0))) => Err(Self::err("division by zero")), - (_, Some(NixNum::Float(0.))) => Err(Self::err("division by zero")), - (Some(NixNum::Int(a)), Some(NixNum::Int(b))) => { - Ok(Self::make_int(a.wrapping_div(b), mc)) - } - (Some(NixNum::Float(a)), Some(NixNum::Float(b))) => Ok(Value::new_float(a / b)), - (Some(NixNum::Int(a)), Some(NixNum::Float(b))) => { - Ok(Value::new_float(a as f64 / b)) - } - (Some(NixNum::Float(a)), Some(NixNum::Int(b))) => { - Ok(Value::new_float(a / b as f64)) - } - _ => Err(Self::err("cannot divide non-numbers")), - }, - BinOpTag::Eq => Ok(Value::new_inline(self.values_equal(lhs, rhs, strings))), - BinOpTag::Neq => Ok(Value::new_inline(!self.values_equal(lhs, rhs, strings))), - BinOpTag::Lt => self.compare_values(lhs, rhs, strings, |o| o.is_lt()), - BinOpTag::Gt => self.compare_values(lhs, rhs, strings, |o| o.is_gt()), - BinOpTag::Leq => self.compare_values(lhs, rhs, strings, |o| o.is_le()), - BinOpTag::Geq => self.compare_values(lhs, rhs, strings, |o| o.is_ge()), - BinOpTag::Concat => { - let Some(l) = lhs.as_gc::>() else { - return Err(Self::err("cannot concatenate: left operand is not a list")); - }; - let Some(r) = rhs.as_gc::>() else { - return Err(Self::err("cannot concatenate: right operand is not a list")); - }; - let mut items = SmallVec::new(); - items.extend(l.inner.iter().cloned()); - items.extend(r.inner.iter().cloned()); - Ok(Value::new_gc(Gc::new(mc, List { inner: items }))) - } - BinOpTag::Update => { - let Some(l) = lhs.as_gc::>() else { - return Err(Self::err("cannot update: left operand is not a set")); - }; - let Some(r) = rhs.as_gc::>() else { - return Err(Self::err("cannot update: right operand is not a set")); - }; - Ok(Value::new_gc(l.merge(&r, mc))) - } - } - } - - pub(super) fn numeric_binop( - &self, - lhs: StrictValue<'gc>, - rhs: StrictValue<'gc>, - mc: &Mutation<'gc>, - int_op: fn(i64, i64) -> i64, - float_op: fn(f64, f64) -> f64, - ) -> VmResult> { - match (Self::as_num(lhs), Self::as_num(rhs)) { - (Some(NixNum::Int(a)), Some(NixNum::Int(b))) => Ok(Self::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(Self::err("cannot perform arithmetic on non-numbers")), - } - } - - pub(super) fn values_equal( - &self, - lhs: StrictValue<'gc>, - rhs: StrictValue<'gc>, - strings: &DefaultStringInterner, - ) -> bool { - if let (Some(a), Some(b)) = (Self::as_num(lhs), Self::as_num(rhs)) { - return 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 a == b; - } - if lhs.is::() && rhs.is::() { - return true; - } - if let (Some(a), Some(b)) = ( - Self::get_string(lhs, strings), - Self::get_string(rhs, strings), - ) { - return a == b; - } - if let (Some(a), Some(b)) = (lhs.as_gc::>(), rhs.as_gc::>()) { - if a.inner.len() != b.inner.len() { - return false; - } - return a.inner.iter().zip(b.inner.iter()).all(|(x, y)| { - let (Ok(ForceResult::Ready(x)), Ok(ForceResult::Ready(y))) = - (self.force_inline(*x), self.force_inline(*y)) - else { - return false; - }; - self.values_equal(x, y, strings) - }); - } - if let (Some(a), Some(b)) = (lhs.as_gc::>(), rhs.as_gc::>()) { - if a.len() != b.len() { - return false; - } - return a - .iter() - .zip(b.iter()) - .all(|((k1, v1), (k2, v2))| { - if k1 != k2 { - return false; - } - let (Ok(ForceResult::Ready(v1)), Ok(ForceResult::Ready(v2))) = - (self.force_inline(*v1), self.force_inline(*v2)) - else { - return false; - }; - self.values_equal(v1, v2, strings) - }); - } - false - } - - pub(super) fn compare_values( - &self, - lhs: StrictValue<'gc>, - rhs: StrictValue<'gc>, - strings: &DefaultStringInterner, - pred: impl FnOnce(std::cmp::Ordering) -> bool, - ) -> VmResult> { - if let (Some(a), Some(b)) = (Self::as_num(lhs), Self::as_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), - }; - return Ok(Value::new_inline(pred(ord))); - } - if let (Some(a), Some(b)) = ( - Self::get_string(lhs, strings), - Self::get_string(rhs, strings), - ) { - return Ok(Value::new_inline(pred(a.cmp(b)))); - } - Err(Self::err("cannot compare these types")) - } - - pub(super) fn do_select_step( - &mut self, - set_val: StrictValue<'gc>, - keys: SmallVec<[Value<'gc>; 4]>, - remaining: u16, - span: u32, - default: Option>, - mc: &Mutation<'gc>, - strings: &DefaultStringInterner, - ) -> VmResult<()> { - let Some(attrs) = set_val.as_gc::>() else { - if let Some(def) = default { - self.push_stack(def); - return Ok(()); - } - return Err(Self::err("cannot select from non-set")); - }; - - let key_idx = keys.len() - remaining as usize; - let key = &keys[key_idx]; - let Some(key_sid) = Self::get_string_id(key) else { - return Err(Self::err("attribute name must be a string")); - }; - - let found = attrs.lookup(key_sid); - - if remaining <= 1 { - match (found, default) { - (Some(v), _) => { - self.push_stack(v); - Ok(()) - } - (None, Some(default)) => { - self.push_stack(default); - Ok(()) - } - (None, None) => { - let name = strings.resolve(key_sid.0).unwrap_or(""); - Err(Self::err(format!("attribute '{name}' missing"))) - } - } - } else { - match (found, default) { - (Some(v), default) => match self.force_inline(v)? { - ForceResult::Ready(forced) => { - self.do_select_step(forced, keys, remaining - 1, span, default, mc, strings) - } - other => { - self.setup_force( - other, - AfterForce::Select { - keys, - remaining: remaining - 1, - span, - default, - }, - mc, - strings, - )?; - Ok(()) - } - }, - (None, Some(default)) => { - self.push_stack(default); - Ok(()) - } - (None, None) => { - let name = strings.resolve(key_sid.0).unwrap_or(""); - Err(Self::err(format!("attribute '{name}' missing"))) - } - } - } - } - - pub(super) fn do_has_attr_step( - &mut self, - set_val: StrictValue<'gc>, - keys: SmallVec<[Value<'gc>; 4]>, - remaining: u16, - mc: &Mutation<'gc>, - strings: &DefaultStringInterner, - ) -> VmResult<()> { - let Some(attrs) = set_val.as_gc::>() else { - self.push_stack(Value::new_inline(false)); - return Ok(()); - }; - - let key_idx = keys.len() - remaining as usize; - let Some(key_sid) = Self::get_string_id(&keys[key_idx]) else { - self.push_stack(Value::new_inline(false)); - return Ok(()); - }; - - if remaining <= 1 { - self.push_stack(Value::new_inline(attrs.has(key_sid))); - Ok(()) - } else { - match attrs.lookup(key_sid) { - Some(v) => match self.force_inline(v)? { - ForceResult::Ready(forced) => { - self.do_has_attr_step(forced, keys, remaining - 1, mc, strings) - } - other => { - self.setup_force( - other, - AfterForce::HasAttr { - keys, - remaining: remaining - 1, - }, - mc, - strings, - )?; - Ok(()) - } - }, - None => { - self.push_stack(Value::new_inline(false)); - Ok(()) - } - } - } - } - - fn concat_strings_continue( - &mut self, - mut forced: SmallVec<[StrictValue<'gc>; 8]>, - mut remaining: SmallVec<[Value<'gc>; 8]>, - force_string: bool, - mc: &Mutation<'gc>, - strings: &DefaultStringInterner, - ) -> VmResult<()> { - while let Some(part) = remaining.pop() { - match self.force_inline(part)? { - ForceResult::Ready(v) => forced.push(v), - other => { - self.setup_force( - other, - AfterForce::ConcatStrings { - forced, - remaining, - force_string, - }, - mc, - strings, - )?; - return Ok(()); - } - } - } - - let mut result = String::new(); - for part in &forced { - if let Some(s) = Self::get_string(*part, strings) { - result.push_str(s); - } else if let Some(n) = Self::as_num(*part) { - match n { - NixNum::Int(i) => result.push_str(&i.to_string()), - NixNum::Float(f) => result.push_str(&format!("{f}")), - } - } else if part.is::() { - } else if let Some(b) = part.as_inline::() { - if force_string { - result.push_str(if b { "1" } else { "" }); - } else { - return Err(Self::err("cannot coerce a boolean to a string")); - } - } else { - return Err(Self::err("cannot coerce value to string")); - } - } - - let ns = Gc::new(mc, NixString::new(result)); - self.push_stack(Value::new_gc(ns)); - Ok(()) - } - - fn do_with_lookup_step( - &mut self, - scope_val: StrictValue<'gc>, - name: StringId, - next: Option>>, - mc: &Mutation<'gc>, - strings: &DefaultStringInterner, - ) -> VmResult<()> { - if let Some(attrs) = scope_val.as_gc::>() - && let Some(v) = attrs.lookup(name) - { - self.push_stack(v); - return Ok(()); - } - - match next { - Some(scope) => { - let env_val = scope.env; - let next_prev = scope.prev; - match self.force_inline(env_val)? { - ForceResult::Ready(forced) => { - self.do_with_lookup_step(forced, name, next_prev, mc, strings) - } - other => { - self.setup_force( - other, - AfterForce::WithLookup { - name, - next: next_prev, - }, - mc, - strings, - )?; - Ok(()) - } - } - } - None => { - let name_str = strings.resolve(name.0).unwrap_or(""); - Err(Self::err(format!("undefined variable '{name_str}'"))) - } - } - } - - fn convert_value( - &self, - val: &Value<'gc>, - strings: &DefaultStringInterner, - ) -> crate::value::Value { - if let Some(i) = val.as_inline::() { - crate::value::Value::Int(i as i64) - } else if let Some(gc_i) = val.as_gc::() { - crate::value::Value::Int(*gc_i) - } else if let Some(f) = val.as_float() { - crate::value::Value::Float(f) - } else if let Some(b) = val.as_inline::() { - crate::value::Value::Bool(b) - } else if val.is::() { - crate::value::Value::Null - } else if let Some(sid) = val.as_inline::() { - let s = strings.resolve(sid.0).unwrap_or("").to_owned(); - crate::value::Value::String(s) - } else if let Some(ns) = val.as_gc::() { - crate::value::Value::String(ns.as_str().to_owned()) - } else if let Some(attrs) = val.as_gc::>() { - let mut map = std::collections::BTreeMap::new(); - for (key, val) in attrs.iter() { - let key_str = strings.resolve(key.0).unwrap_or("").to_owned(); - let converted = self.convert_value(val, strings); - map.insert(crate::value::Symbol::from(key_str), converted); - } - crate::value::Value::AttrSet(crate::value::AttrSet::new(map)) - } else if let Some(list) = val.as_gc::>() { - let items: Vec<_> = list - .inner - .iter() - .map(|v| self.convert_value(v, strings)) - .collect(); - crate::value::Value::List(crate::value::List::new(items)) - } else if val.is::>() { - crate::value::Value::Func - } else if val.is::>() { - crate::value::Value::Thunk - } else if val.as_inline::().is_some() { - crate::value::Value::PrimOp("primop".into()) - } else if val.is::>() { - crate::value::Value::PrimOpApp("primop-app".into()) - } else { - crate::value::Value::Null - } - } - - fn execute_one( - &mut self, - bc: &[u8], - mc: &Mutation<'gc>, - strings: &DefaultStringInterner, - ) -> Action { + fn execute_one(&mut self) -> Action { use crate::codegen::Op::{self, *}; - #[cfg(debug_assertions)] - let opcode_byte = bc[self.pc]; - #[cfg(not(debug_assertions))] - let opcode_byte = unsafe { *bc.get_unchecked(self.pc) }; + let Ok(op) = Op::try_from_primitive(self.bytecode[self.pc]) + .map_err(|err| panic!("unknown opcode: {:#04x}", err.number)); self.pc += 1; - #[cfg(debug_assertions)] - let Ok(op) = Op::try_from_primitive(opcode_byte) else { - return Action::Done(Err(Error::eval_error(format!( - "unknown opcode: {opcode_byte:#04x}" - )))); - }; - #[cfg(not(debug_assertions))] - let op = unsafe { Op::try_from_primitive(opcode_byte).unwrap_unchecked() }; - match op { PushSmi => { - let val = self.read_i32(bc); - self.push_stack(Value::new_inline(val)); + let val = self.read_i32(); + self.push_stack(|_| Value::new_inline(val)); } PushBigInt => { - let val = self.read_i64(bc); - self.push_stack(Value::new_gc(Gc::new(mc, val))); + let val = self.read_i64(); + self.push_stack(|mc| Value::new_gc(Gc::new(mc, val))); } PushFloat => { - let val = self.read_f64(bc); - self.push_stack(Value::new_float(val)); + let val = self.read_f64(); + self.push_stack(|_| Value::new_float(val)); } PushString => { - let sid = self.read_string_id(bc); - self.push_stack(Value::new_inline(sid)); + 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)), + PushNull => self.push_stack(|_| Value::new_inline(Null)), + PushTrue => self.push_stack(|_| Value::new_inline(true)), + PushFalse => self.push_stack(|_| Value::new_inline(false)), LoadLocal => { - let idx = self.read_u32(bc) as usize; - let val = self.env().borrow().locals[idx]; - self.push_stack(val); + let idx = self.read_u32() as usize; + self.arena + .mutate_root(|mc, root| root.push_stack(root.env().borrow().locals[idx])) } LoadOuter => { - let layer = self.read_u8(bc); - let idx = self.read_u32(bc) as usize; - let mut env = self.env(); - for _ in 0..layer { - let prev = env.borrow().prev.expect("LoadOuter: env chain too short"); - env = prev; - } - let val = env.borrow().locals[idx]; - self.push_stack(val); + let layer = self.read_u8(); + let idx = self.read_u32() as usize; + self.arena.mutate_root(|mc, root| { + let mut cur = root.env(); + for _ in 0..layer { + let prev = cur.borrow().prev.expect("LoadOuter: env chain too short"); + cur = prev; + } + let val = cur.borrow().locals[idx]; + root.push_stack(val); + }); } StoreLocal => { - let idx = self.read_u32(bc) as usize; - let val = self.stack.pop().expect("stack underflow"); - self.env().borrow_mut(mc).locals[idx] = val; + 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(bc) as usize; - self.env() - .borrow_mut(mc) - .locals - .resize(count, Value::default()); + let count = self.read_u32() as usize; + self.arena.mutate_root(|mc, root| { + root.env() + .borrow_mut(mc) + .locals + .extend(std::iter::repeat_n(Value::default(), count)); + }); } MakeThunk => { - let entry_point = self.read_u32(bc); - let _label = self.read_string_id(bc); - let thunk = Gc::new( - mc, - RefLock::new(ThunkState::Pending { - ip: entry_point, - env: self.env(), - }), - ); - self.push_stack(Value::new_gc(thunk)); + let entry_point = self.read_u32(); + let _label = self.read_string_id(); + self.arena.mutate_root(|mc, root| { + let thunk = Gc::new( + mc, + RefLock::new(ThunkState::Pending { + ip: entry_point as usize, + env: root.env(), + }), + ); + root.push_stack(Value::new_gc(thunk)); + }) } MakeClosure => { - let entry_point = self.read_u32(bc); - let n_locals = self.read_u32(bc); - let closure = Gc::new( - mc, - Closure { - ip: entry_point, - n_locals, - env: self.env(), - pattern: None, - }, - ); - self.push_stack(Value::new_gc(closure)); + let entry_point = self.read_u32(); + let n_locals = self.read_u32(); + self.arena.mutate_root(|mc, root| { + let closure = Gc::new( + mc, + Closure { + ip: entry_point, + n_locals, + env: root.env(), + pattern: None, + }, + ); + root.push_stack(Value::new_gc(closure)); + }); } MakePatternClosure => { - let entry_point = self.read_u32(bc); - let n_locals = self.read_u32(bc); - let req_count = self.read_u16(bc) as usize; - let opt_count = self.read_u16(bc) as usize; - let has_ellipsis = self.read_u8(bc) != 0; + let entry_point = self.read_u32(); + let n_locals = self.read_u32(); + let req_count = self.read_u16() as usize; + let opt_count = self.read_u16() as usize; + let has_ellipsis = self.read_u8() != 0; let mut required = SmallVec::new(); for _ in 0..req_count { - required.push(self.read_string_id(bc)); + required.push(self.read_string_id()); } let mut optional = SmallVec::new(); for _ in 0..opt_count { - optional.push(self.read_string_id(bc)); + 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(bc); - let span_id = self.read_u32(bc); + let name = self.read_string_id(); + let span_id = self.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)); + self.arena.mutate_root(|mc, root| { + let pattern = Gc::new( + mc, + PatternInfo { + required, + optional, + ellipsis: has_ellipsis, + param_spans: param_spans.into_boxed_slice(), + }, + ); + let closure = Gc::new( + mc, + Closure { + ip: entry_point, + n_locals, + env: root.env(), + pattern: Some(pattern), + }, + ); + root.push_stack(Value::new_gc(closure)); + }) } Call => { - let span_id = self.read_u32(bc); - let arg = self.stack.pop().expect("stack underflow"); - let func = self.stack.pop().expect("stack underflow"); - match self.force_inline(func) { - Ok(ForceResult::Ready(f)) => { - try_vm!(self, self.do_call(f, arg, Some(span_id), mc, strings)) + // force func + let _span = self.read_u32(); + self.force_tos(); + self.arena.mutate_root(|mc, root| { + let func = root.pop_stack(); + let arg = root.pop_stack(); + if let Some(closure) = func.as_gc::() { + let ip = closure.ip; + let n_locals = closure.n_locals; + let env = closure.env; + if let Some(ref _pattern) = closure.pattern { + todo!("pattern call") + } else { + let new_env = + Gc::new(mc, RefLock::new(Env::with_arg(arg, n_locals, env))); + root.frames + .push(CallFrame { + pc: self.pc, + env: root.env(), + }) + .expect("frame stack overflow"); + self.pc = ip as usize; + root.current_env = Some(new_env); + } + } else { + todo!("call other types: {func:?}") } - Ok(other) => { - try_vm!( - self, - self.setup_force( - other, - AfterForce::Call { - arg, - span: Some(span_id), - }, - mc, - strings, - ) - ); - } - Err(e) => return self.handle_vm_error(e), - } + }); } CallNoSpan => { - let arg = self.stack.pop().expect("stack underflow"); - let func = self.stack.pop().expect("stack underflow"); - match self.force_inline(func) { - Ok(ForceResult::Ready(f)) => { - try_vm!(self, self.do_call(f, arg, None, mc, strings)) - } - Ok(other) => { - try_vm!( - self, - self.setup_force(other, AfterForce::Call { arg, span: None }, mc, strings) - ); - } - Err(e) => return self.handle_vm_error(e), - } + todo!("implement CallNoSpan"); } MakeAttrs => { - let count = self.read_u32(bc) as usize; - let total = 3 * count; - let items = self.stack.pop_n::<16>(total); - - let mut entries = SmallVec::<[(StringId, Value<'gc>); 4]>::new(); - for i in 0..count { - let key = &items[2 * i]; - let val = items[2 * i + 1]; - let key_sid = - Self::get_string_id(key).expect("MakeAttrs: key must be StringId"); - entries.push((key_sid, val)); - } - entries.sort_by_key(|(k, _)| *k); - - let attrs = Gc::new(mc, unsafe { AttrSet::from_sorted_unchecked(entries) }); - self.push_stack(Value::new_gc(attrs)); - } - MakeAttrsDyn => { - let static_count = self.read_u32(bc) as usize; - let dynamic_count = self.read_u32(bc) as usize; - let total = 3 * static_count + 3 * dynamic_count; - let items = self.stack.pop_n::<16>(total); - - let mut entries = SmallVec::<[(StringId, Value<'gc>); 4]>::new(); - - for i in 0..static_count { - let key_sid = Self::get_string_id(&items[2 * i]) - .expect("MakeAttrsDyn: static key must be StringId"); - entries.push((key_sid, items[2 * i + 1])); - } - - let dyn_base = 3 * static_count; - for i in 0..dynamic_count { - let key = &items[dyn_base + 3 * i]; - let val = items[dyn_base + 3 * i + 1]; - if key.is::() { - continue; - } - let Some(key_sid) = Self::get_string_id(key) else { - return Action::Done(Err(Error::eval_error( - "dynamic attribute name must be a string", - ))); + let count = self.read_u32() as usize; + let mut entries: SmallVec<[AttrEntry; 4]> = SmallVec::with_capacity(count); + for _ in 0..count { + let key_tag = self.read_u8(); + let key = match key_tag { + KEY_STATIC => AttrKeyData::Static(self.read_string_id()), + KEY_DYNAMIC => AttrKeyData::Dynamic(self.read_operand()), + _ => panic!("unknown key tag: {key_tag:#04x}"), }; - entries.push((key_sid, val)); + let val = self.read_operand(); + let _span_id = self.read_u32(); + entries.push(AttrEntry { key, val }); } - - entries.sort_by_key(|(k, _)| *k); - // FIXME: incorrect!!! - entries.dedup_by_key(|(k, _)| *k); - - let attrs = Gc::new(mc, unsafe { AttrSet::from_sorted_unchecked(entries) }); - self.push_stack(Value::new_gc(attrs)); + self.arena.mutate_root(|mc, root| { + let mut kv: SmallVec<[(StringId, Value); 4]> = SmallVec::with_capacity(count); + for entry in &entries { + let key_sid = match &entry.key { + &AttrKeyData::Static(sid) => sid, + AttrKeyData::Dynamic(op) => { + let v = op.resolve(mc, root); + v.as_inline::() + .expect("dynamic attr key must be a string") + } + }; + let val = entry.val.resolve(mc, root); + kv.push((key_sid, val)); + } + kv.sort_by_key(|(k, _)| *k); + let attrs = Gc::new(mc, unsafe { AttrSet::from_sorted_unchecked(kv) }); + root.push_stack(Value::new_gc(attrs)); + }); } MakeEmptyAttrs => { - let attrs = Gc::new(mc, unsafe { - AttrSet::from_sorted_unchecked(SmallVec::new()) - }); - self.push_stack(Value::new_gc(attrs)); + self.push_empty_attrs(); } Select => { - let n = self.read_u16(bc) as usize; - let span_id = self.read_u32(bc); - let mut keys = self.stack.pop_n::<4>(n); - let expr = self.stack.pop().expect("stack underflow"); - keys.reverse(); - - let remaining = keys.len() as u16; - match self.force_inline(expr) { - Ok(ForceResult::Ready(forced)) => { - try_vm!( - self, - self.do_select_step( - forced, keys, remaining, span_id, None, mc, strings, - ) - ); - } - Ok(other) => { - try_vm!( - self, - self.setup_force( - other, - AfterForce::Select { - keys, - remaining, - span: span_id, - default: None, - }, - mc, - strings, - ) - ); - } - Err(e) => return self.handle_vm_error(e), - } + let n = self.read_u16() as usize; + let _span_id = self.read_u32(); + let keys = self.read_attr_keys(n); + todo!("implement Select (force + lookup)"); } SelectDefault => { - let n = self.read_u16(bc) as usize; - let span_id = self.read_u32(bc); - let default = self.stack.pop().expect("stack underflow"); - let mut keys = self.stack.pop_n::<4>(n); - let expr = self.stack.pop().expect("stack underflow"); - keys.reverse(); - - let remaining = keys.len() as u16; - match self.force_inline(expr) { - Ok(ForceResult::Ready(forced)) => { - try_vm!( - self, - self.do_select_step( - forced, - keys, - remaining, - span_id, - Some(default), - mc, - strings, - ) - ); - } - Ok(other) => { - try_vm!( - self, - self.setup_force( - other, - AfterForce::Select { - keys, - remaining, - span: span_id, - default: Some(default), - }, - mc, - strings, - ) - ); - } - Err(e) => return self.handle_vm_error(e), - } + let n = self.read_u16() as usize; + let _span_id = self.read_u32(); + let keys = self.read_attr_keys(n); + todo!("implement SelectDefault (force + lookup with default)"); } HasAttr => { - let n = self.read_u16(bc) as usize; - let mut keys = self.stack.pop_n::<4>(n); - let expr = self.stack.pop().expect("stack underflow"); - keys.reverse(); - - let remaining = keys.len() as u16; - match self.force_inline(expr) { - Ok(ForceResult::Ready(forced)) => { - try_vm!(self, self.do_has_attr_step(forced, keys, remaining, mc, strings)); - } - Ok(ForceResult::NeedEval { ip, env, thunk }) => { - self.push_force_frame( - thunk, - AfterForce::HasAttr { keys, remaining }, - ip, - env, - mc, - ); - } - Ok(ForceResult::NeedApply(thunk)) => { - try_vm!( - self, - self.push_apply_force_frame( - thunk, - AfterForce::HasAttr { keys, remaining }, - mc, - strings, - ) - ); - } - Err(e) => return self.handle_vm_error(e), - } + 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(bc) as usize; - let items = self.stack.pop_n::<4>(count); - let list = Gc::new(mc, List { inner: items }); - self.push_stack(Value::new_gc(list)); + let count = self.read_u32() as usize; + let mut operands: SmallVec<[OperandData; 4]> = SmallVec::with_capacity(count); + for _ in 0..count { + operands.push(self.read_operand()); + } + self.arena.mutate_root(|mc, root| { + let mut items: SmallVec<[Value; 4]> = SmallVec::with_capacity(count); + for op in &operands { + items.push(op.resolve(mc, root)); + } + let list = Gc::new(mc, List { inner: items }); + root.push_stack(Value::new_gc(list)); + }); + } + MakeEmptyList => { + self.push_empty_list(); } OpAdd | OpSub | OpMul | OpDiv | OpEq | OpNeq | OpLt | OpGt | OpLeq | OpGeq @@ -1918,357 +628,613 @@ impl<'gc> VM<'gc> { OpUpdate => BinOpTag::Update, _ => unreachable!(), }; - let rhs = self.stack.pop().expect("stack underflow"); - let lhs = self.stack.pop().expect("stack underflow"); - - match self.force_inline(lhs) { - Ok(ForceResult::Ready(lhs_f)) => match self.force_inline(rhs) { - Ok(ForceResult::Ready(rhs_f)) => { - match self.compute_binop(tag, lhs_f, rhs_f, mc, strings) { - Ok(r) => self.push_stack(r), - Err(e) => return self.handle_vm_error(e), - } - } - Ok(other) => { - try_vm!( - self, - self.setup_force( - other, - AfterForce::BinOpRhs { - lhs_forced: lhs_f, - op: tag, - }, - mc, - strings, - ) - ); - } - Err(e) => return self.handle_vm_error(e), - }, - Ok(other) => { - try_vm!( - self, - self.setup_force(other, AfterForce::BinOpLhs { rhs, op: tag }, mc, strings) - ); - } - Err(e) => return self.handle_vm_error(e), - } + try_vm!(self, self.compute_binop(tag)) } OpNeg => { - let val = self.stack.pop().expect("stack underflow"); - match self.force_inline(val) { - Ok(ForceResult::Ready(f)) => match Self::as_num(f) { - Some(NixNum::Int(i)) => self - .push_stack(Self::make_int(-i, mc)), - Some(NixNum::Float(fl)) => self - .push_stack(Value::new_float(-fl)), - None => { - return Action::Done(Err(Error::eval_error( - "cannot negate non-number", - ))); - } - }, - Ok(other) => { - try_vm!(self, self.setup_force(other, AfterForce::UnNeg, mc, strings)); - } - Err(e) => return self.handle_vm_error(e), - } + todo!("implement unary operation"); } OpNot => { - let val = self.stack.pop().expect("stack underflow"); - match self.force_inline(val) { - Ok(ForceResult::Ready(f)) => match f.as_inline::() { - Some(b) => self - .push_stack(Value::new_inline(!b)), - None => { - return Action::Done(Err(Error::eval_error("value is not a boolean"))); - } - }, - Ok(other) => { - try_vm!(self, self.setup_force(other, AfterForce::UnNot, mc, strings)); - } - Err(e) => return self.handle_vm_error(e), - } + todo!("implement unary operation"); } - ForceBool => { - let val = self.stack.pop().expect("stack underflow"); - match self.force_inline(val) { - Ok(ForceResult::Ready(f)) => { - if f.as_inline::().is_none() { - return Action::Done(Err(Error::eval_error("value is not a boolean"))); - } - self.push_stack(f.relax()); - } - Ok(ForceResult::NeedEval { ip, env, thunk }) => { - self.push_force_frame(thunk, AfterForce::ForceBool, ip, env, mc); - } - Ok(ForceResult::NeedApply(thunk)) => { - try_vm!( - self, - self.push_apply_force_frame(thunk, AfterForce::ForceBool, mc, strings) - ); - } - Err(e) => return self.handle_vm_error(e), - } - } JumpIfFalse => { - let offset = self.read_i32(bc); - let val = self.stack.pop().expect("stack underflow"); - if let Some(false) = val.as_inline::() { - self.pc = ((self.pc as isize) + (offset as isize)) as usize; - } + 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(bc); - let val = self.stack.pop().expect("stack underflow"); - if let Some(true) = val.as_inline::() { - self.pc = ((self.pc as isize) + (offset as isize)) as usize; - } + 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(bc); + let offset = self.read_i32(); self.pc = ((self.pc as isize) + (offset as isize)) as usize; } ConcatStrings => { - let parts_count = self.read_u16(bc) as usize; - let force_string = self.read_u8(bc) != 0; - let parts = self.stack.pop_n::<8>(parts_count); - let mut remaining = parts; - remaining.reverse(); - let forced = SmallVec::new(); - try_vm!( - self, - self.concat_strings_continue(forced, remaining, force_string, mc, strings,) - ); + let parts_count = self.read_u16() as usize; + let _force_string = self.read_u8() != 0; + let mut operands: SmallVec<[OperandData; 4]> = SmallVec::with_capacity(parts_count); + for _ in 0..parts_count { + operands.push(self.read_operand()); + } + todo!("implement ConcatStrings (force parts, coerce to string, concatenate)"); } ResolvePath => { - let _val = self.stack.pop().expect("stack underflow"); - self.push_stack(Value::new_inline(Null)); + todo!("implement ResolvePath"); } Assert => { - let raw_idx = self.read_u32(bc); - let span_id = self.read_u32(bc); - let expr = self.stack.pop().expect("stack underflow"); - let assertion = self.stack.pop().expect("stack underflow"); - - match self.force_inline(assertion) { - Ok(ForceResult::Ready(f)) => match f.as_inline::() { - Some(true) => self.push_stack(expr), - Some(false) => { - let sym = string_interner::symbol::SymbolU32::try_from_usize( - raw_idx as usize, - ); - let msg = sym.and_then(|s| strings.resolve(s)).unwrap_or(""); - return self.handle_vm_error(VmError::Uncatchable(Error::eval_error( - format!("assertion '{msg}' failed"), - ))); - } - None => { - return self.handle_vm_error(Self::err( - "assertion condition must be a boolean", - )); - } - }, - Ok(other) => { - try_vm!( - self, - self.setup_force( - other, - AfterForce::Assert { - expr, - raw_idx, - span_id, - }, - mc, - strings, - ) - ); - } - Err(e) => return self.handle_vm_error(e), - } + let raw_idx = self.read_u32(); + let span_id = self.read_u32(); + todo!("implement Assert (force TOS)"); } PushWith => { - let ns = self.stack.pop().expect("stack underflow"); - let scope = Gc::new( - mc, - WithScope { - env: ns, - prev: self.with_scope, - }, - ); - self.with_scope = Some(scope); - } - PopWith => { - if let Some(scope) = self.with_scope { - self.with_scope = scope.prev; - } + self.arena.mutate_root(|mc, root| { + let env = root.pop_stack(); + let scope = Gc::new( + mc, + WithScope { + env, + prev: root.with_scope, + }, + ); + root.with_scope = Some(scope); + }); } + PopWith => self.arena.mutate_root(|_, root| { + let Some(scope) = root.with_scope else { + unreachable!("no with_scope to pop"); + }; + root.with_scope = scope.prev; + }), WithLookup => { - let name = self.read_string_id(bc); - match self.with_scope { - Some(scope_gc) => { - let env_val = scope_gc.env; - let next = scope_gc.prev; - match self.force_inline(env_val) { - Ok(ForceResult::Ready(forced)) => { - try_vm!( - self, - self.do_with_lookup_step(forced, name, next, mc, strings) - ); - } - Ok(other) => { - try_vm!( - self, - self.setup_force( - other, - AfterForce::WithLookup { name, next }, - mc, - strings, - ) - ); - } - Err(e) => return self.handle_vm_error(e), - } - } - None => { - let name_str = strings.resolve(name.0).unwrap_or(""); - return self.handle_vm_error(Self::err(format!( - "undefined variable '{name_str}'" - ))); - } - } + let name = self.read_string_id(); + todo!("implement WithLookup (force with_scope)"); } LoadBuiltins => { - self.push_stack(self.globals.builtins); + self.push_builtins(); } LoadBuiltin => { - let name_raw = self.read_string_id(bc); - if let Some(&primop) = self.globals.builtin_lookup.get(&name_raw) { - self.push_stack(Value::new_inline(primop)); - } else { - return Action::Done(Err(Error::eval_error(format!( - "unknown builtin (id {:?})", - name_raw - )))); - } + 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(bc); + let _span_id = self.read_u32(); todo!("MkPos") } LoadReplBinding => { - let _name = self.read_string_id(bc); + let _name = self.read_string_id(); todo!("LoadReplBinding") } LoadScopedBinding => { - let _name = self.read_string_id(bc); + let _name = self.read_string_id(); todo!("LoadScopedBinding") } Return => { - let ret_val = self.stack.pop().expect("stack underflow"); - return self.handle_return(ret_val, mc, strings); + return self.handle_return(); } } Action::Continue } - pub(super) fn run_batch( - &mut self, - bytecode: &[u8], - pc: &mut usize, - mc: &Mutation<'gc>, - strings: &DefaultStringInterner, - ) -> Action { - const COLLECTOR_GRANULARITY: f64 = 1024.0; - const BATCH_SIZE: usize = 1024; - - if !self.started { - self.pc = *pc; - self.current_env = Some(Gc::new(mc, RefLock::new(Env::empty()))); - self.started = true; - } - - for _ in 0..BATCH_SIZE { - let action = self.execute_one(bytecode, mc, strings); - match action { - Action::Continue => {} - other => { - *pc = self.pc; - if matches!(other, Action::Done(_)) { - self.started = false; - } - return other; - } - } - } - - *pc = self.pc; - - if mc.metrics().allocation_debt() > COLLECTOR_GRANULARITY { - Action::NeedGc + pub(super) fn get_string<'a, 'gc: 'a>( + strings: &'a DefaultStringInterner, + val: StrictValue<'gc>, + ) -> Option<&'a str> { + if let Some(sid) = val.as_inline::() { + Some(strings.resolve(sid.0)?) } else { - Action::Continue + val.as_gc::().map(|ns| ns.as_ref().as_str()) } } - fn vm_try(&mut self, result: VmResult) -> std::result::Result { - match result { - Ok(ok) => Ok(ok), - Err(err) => Err(self.handle_vm_error(err)), + pub(super) fn get_num(val: StrictValue<'_>) -> Option { + if let Some(i) = val.as_inline::() { + Some(NixNum::Int(i as i64)) + } else if let Some(gc_i) = val.as_gc::() { + Some(NixNum::Int(*gc_i)) + } else { + val.as_float().map(NixNum::Float) } } - fn handle_vm_error(&mut self, e: VmError) -> Action { - match e { - VmError::Catchable(msg) => { - // Check for tryEval catch frames - if let Some(catch) = self.find_catch_frame() { - self.restore_catch_frame(catch); - return Action::Continue; - } - Action::Done(Err(Error::catchable(msg))) + fn compute_binop(&mut self, op: BinOpTag) -> VmResult<()> { + self.force_n(2); + + match op { + BinOpTag::Add => { + let strings = &self.strings; + self.arena.mutate_root(|mc, root| { + let rhs = root.pop_stack_forced(); + let lhs = root.pop_stack_forced(); + // FIXME: path & string context + if let (Some(ls), Some(rs)) = ( + Self::get_string(strings, lhs), + Self::get_string(strings, rhs), + ) { + let ns = Gc::new(mc, NixString::new(format!("{ls}{rs}"))); + root.push_stack(Value::new_gc(ns)); + return Ok(()); + } + let res = Self::numeric_binop(lhs, rhs, mc, i64::wrapping_add, |a, b| a + b)?; + root.push_stack(res); + VmResult::Ok(()) + })?; } - VmError::Uncatchable(e) => Action::Done(Err(e)), + BinOpTag::Sub => { + self.arena.mutate_root(|mc, root| { + let rhs = root.pop_stack_forced(); + let lhs = root.pop_stack_forced(); + let res = Self::numeric_binop(lhs, rhs, mc, i64::wrapping_sub, |a, b| a - b)?; + root.push_stack(res); + VmResult::Ok(()) + })?; + } + BinOpTag::Mul => { + self.arena.mutate_root(|mc, root| { + let rhs = root.pop_stack_forced(); + let lhs = root.pop_stack_forced(); + let res = Self::numeric_binop(lhs, rhs, mc, i64::wrapping_mul, |a, b| a * b)?; + root.push_stack(res); + VmResult::Ok(()) + })?; + } + BinOpTag::Div => { + self.arena.mutate_root(|mc, root| { + let rhs = root.pop_stack_forced(); + let lhs = root.pop_stack_forced(); + let res = match (Self::get_num(lhs), Self::get_num(rhs)) { + (_, Some(NixNum::Int(0))) => Err(vm_err("division by zero")), + (_, Some(NixNum::Float(0.))) => Err(vm_err("division by zero")), + (Some(NixNum::Int(a)), Some(NixNum::Int(b))) => { + Ok(Value::make_int(a.wrapping_div(b), mc)) + } + (Some(NixNum::Float(a)), Some(NixNum::Float(b))) => { + Ok(Value::new_float(a / b)) + } + (Some(NixNum::Int(a)), Some(NixNum::Float(b))) => { + Ok(Value::new_float(a as f64 / b)) + } + (Some(NixNum::Float(a)), Some(NixNum::Int(b))) => { + Ok(Value::new_float(a / b as f64)) + } + _ => Err(vm_err("cannot divide non-numbers")), + }?; + root.push_stack(res); + VmResult::Ok(()) + })?; + } + BinOpTag::Eq => { + let eq = self.values_equal()?; + self.push_stack(|_| Value::new_inline(eq)); + } + BinOpTag::Neq => { + let eq = self.values_equal()?; + self.push_stack(|_| Value::new_inline(!eq)); + } + BinOpTag::Lt => self.compare_values(|o| o.is_lt())?, + BinOpTag::Gt => self.compare_values(|o| o.is_gt())?, + BinOpTag::Leq => self.compare_values(|o| o.is_le())?, + BinOpTag::Geq => self.compare_values(|o| o.is_ge())?, + BinOpTag::Concat => { + self.arena.mutate_root(|mc, root| { + let rhs = root.pop_stack_forced(); + let lhs = root.pop_stack_forced(); + let Some(l) = lhs.as_gc::>() else { + return Err(vm_err("cannot concatenate: left operand is not a list")); + }; + let Some(r) = rhs.as_gc::>() else { + return Err(vm_err("cannot concatenate: right operand is not a list")); + }; + let mut items = SmallVec::new(); + items.extend(l.inner.iter().cloned()); + items.extend(r.inner.iter().cloned()); + root.push_stack(Value::new_gc(Gc::new(mc, List { inner: items }))); + VmResult::Ok(()) + })?; + } + BinOpTag::Update => { + self.arena.mutate_root(|mc, root| { + let rhs = root.pop_stack_forced(); + let lhs = root.pop_stack_forced(); + let Some(l) = lhs.as_gc::>() else { + return Err(vm_err("cannot update: left operand is not a set")); + }; + let Some(r) = rhs.as_gc::>() else { + return Err(vm_err("cannot update: right operand is not a set")); + }; + root.push_stack(Value::new_gc(l.merge(&r, mc))); + VmResult::Ok(()) + })?; + } + } + Ok(()) + } + + fn numeric_binop<'gc>( + lhs: StrictValue<'gc>, + rhs: StrictValue<'gc>, + mc: &Mutation<'gc>, + int_op: fn(i64, i64) -> i64, + float_op: fn(f64, f64) -> f64, + ) -> VmResult> { + match (Self::get_num(lhs), Self::get_num(rhs)) { + (Some(NixNum::Int(a)), Some(NixNum::Int(b))) => Ok(Value::make_int(int_op(a, b), mc)), + (Some(NixNum::Float(a)), Some(NixNum::Float(b))) => { + Ok(Value::new_float(float_op(a, b))) + } + (Some(NixNum::Int(a)), Some(NixNum::Float(b))) => { + Ok(Value::new_float(float_op(a as f64, b))) + } + (Some(NixNum::Float(a)), Some(NixNum::Int(b))) => { + Ok(Value::new_float(float_op(a, b as f64))) + } + _ => Err(vm_err("cannot perform arithmetic on non-numbers")), } } - fn find_catch_frame(&mut self) -> Option { - todo!("find catch frame") + pub(super) fn values_equal(&mut self) -> VmResult { + enum State { + Bool(bool), + List(usize), + AttrSet(usize), + } + + let strings = &self.strings; + let state = self.arena.mutate_root(|_, root| { + let rhs = root.pop_stack_forced(); + let lhs = root.pop_stack_forced(); + + if let (Some(a), Some(b)) = (Self::get_num(lhs), Self::get_num(rhs)) { + return State::Bool(match (a, b) { + (NixNum::Int(a), NixNum::Int(b)) => a == b, + (NixNum::Float(a), NixNum::Float(b)) => a == b, + (NixNum::Int(a), NixNum::Float(b)) => a as f64 == b, + (NixNum::Float(a), NixNum::Int(b)) => a == b as f64, + }); + } + if let (Some(a), Some(b)) = (lhs.as_inline::(), rhs.as_inline::()) { + return State::Bool(a == b); + } + if lhs.is::() && rhs.is::() { + return State::Bool(true); + } + if let (Some(a), Some(b)) = ( + Self::get_string(strings, lhs), + Self::get_string(strings, rhs), + ) { + return State::Bool(a == b); + } + if let (Some(a), Some(b)) = (lhs.as_gc::>(), rhs.as_gc::>()) { + if a.inner.len() != b.inner.len() { + return State::Bool(false); + } + for (x, y) in a.inner.iter().zip(b.inner.iter()).rev() { + root.temp_stack.push(*x); + root.temp_stack.push(*y); + } + return State::List(a.inner.len()); + } + if let (Some(a), Some(b)) = (lhs.as_gc::>(), rhs.as_gc::>()) { + if a.len() != b.len() { + return State::Bool(false); + } + for ((k1, v1), (k2, v2)) in a.iter().zip(b.iter()).rev() { + if k1 != k2 { + return State::Bool(false); + } + root.temp_stack.push(*v1); + root.temp_stack.push(*v2); + } + return State::AttrSet(a.len()); + } + State::Bool(false) + }); + + match state { + State::Bool(b) => Ok(b), + State::List(len) | State::AttrSet(len) => { + for i in 0..len { + self.arena.mutate_root(|_, root| { + let y = root.temp_stack.pop().unwrap(); + let x = root.temp_stack.pop().unwrap(); + root.push_stack(x); + root.push_stack(y); + }); + self.force_n(2); + let eq = self.values_equal()?; + if !eq { + self.arena.mutate_root(|_, root| { + let rem = len - 1 - i; + for _ in 0..rem * 2 { + root.temp_stack.pop(); + } + }); + return Ok(false); + } + } + Ok(true) + } + } } - fn restore_catch_frame(&mut self, _catch: CatchFrameInfo) { - todo!("restore catch frame") + pub(super) fn compare_values( + &mut self, + pred: impl FnOnce(std::cmp::Ordering) -> bool, + ) -> VmResult<()> { + let strings = &self.strings; + self.arena.mutate_root(|_, root| { + let rhs = root.pop_stack_forced(); + let lhs = root.pop_stack_forced(); + + if let (Some(a), Some(b)) = (Self::get_num(lhs), Self::get_num(rhs)) { + let ord = match (a, b) { + (NixNum::Int(a), NixNum::Int(b)) => a.cmp(&b), + (NixNum::Float(a), NixNum::Float(b)) => { + a.partial_cmp(&b).unwrap_or(std::cmp::Ordering::Less) + } + (NixNum::Int(a), NixNum::Float(b)) => (a as f64) + .partial_cmp(&b) + .unwrap_or(std::cmp::Ordering::Less), + (NixNum::Float(a), NixNum::Int(b)) => a + .partial_cmp(&(b as f64)) + .unwrap_or(std::cmp::Ordering::Less), + }; + root.push_stack(Value::new_inline(pred(ord))); + return VmResult::Ok(()); + } + if let (Some(a), Some(b)) = ( + Self::get_string(strings, lhs), + Self::get_string(strings, rhs), + ) { + root.push_stack(Value::new_inline(pred(a.cmp(b)))); + return VmResult::Ok(()); + } + Err(vm_err("cannot compare these types")) + })?; + Ok(()) } #[inline(always)] - pub(super) fn push_stack(&mut self, val: Value<'gc>) { - #[cfg(debug_assertions)] - self.stack.push(val).expect("stack overflow"); - #[cfg(not(debug_assertions))] - unsafe { - self.stack.push_unchecked(val); + fn check_gc(&mut self) { + const COLLECTOR_GRANULARITY: f64 = 1024.0; + + if self.fuel == 0 { + if self.arena.metrics().allocation_debt() > COLLECTOR_GRANULARITY { + if self.arena.collection_phase() == CollectionPhase::Sweeping { + self.arena.collect_debt(); + } else if let Some(marked) = self.arena.mark_debt() { + marked.start_sweeping(); + } + } + self.fuel = Self::DEFAULT_FUEL_AMOUNT; } + self.fuel -= 1; + } + + pub(super) fn run( + &mut self, + ip: InstructionPtr, + _mode: ForceMode, + ) -> Result { + self.pc = ip.0; + self.arena.mutate_root(|mc, root| { + if root.current_env.is_none() { + root.current_env = Some(Gc::new(mc, RefLock::new(Env::empty()))); + } + }); + + loop { + self.check_gc(); + match self.execute_one() { + Action::Continue => (), + Action::Return => (), + Action::Done(done) => break done, + } + } + } + + #[inline(always)] + pub(super) fn push_stack(&mut self, f: impl for<'gc> FnOnce(&Mutation<'gc>) -> Value<'gc>) { + self.arena.mutate_root(|mc, root| { + root.stack.push(f(mc)).expect("stack overflow"); + }) } #[inline(always)] pub(super) fn push_empty_list(&mut self) { - self.push_stack(self.globals.empty_list); + self.arena.mutate_root(|_, root| { + root.push_stack(root.empty_list); + }); } #[inline(always)] pub(super) fn push_empty_attrs(&mut self) { - self.push_stack(self.globals.empty_attrs); + self.arena.mutate_root(|_, root| { + root.push_stack(root.empty_attrs); + }); + } + + #[inline(always)] + pub(super) fn push_builtins(&mut self) { + self.arena.mutate_root(|_, root| { + root.push_stack(root.builtins); + }); + } + + pub(super) fn force_tos(&mut self) { + loop { + let run = self.arena.mutate_root(|_mc, root| { + let thunk = root + .stack + .tos_mut() + .expect("stack underflow"); + + let Some(thunk_state) = thunk.as_gc::() else { + return false + }; + match *thunk_state.borrow() { + ThunkState::Pending { ip, env } => { + root.frames + .push(CallFrame { + pc: self.pc, + env: root.env(), + }) + .expect("call stack overflow"); + self.pc = ip; + root.current_env = Some(env); + true + } + ThunkState::Apply { .. } => todo!("force_tos"), + ThunkState::Evaluated(val) => { + *thunk = val; + false + }, + ThunkState::Blackhole => todo!("force_tos"), + } + }); + if !run { + return; + } + loop { + self.check_gc(); + match self.execute_one() { + Action::Continue => (), + Action::Return => break, + Action::Done(_) => unreachable!(), + } + } + self.arena.mutate_root(|mc, root| { + let val = root.pop_stack(); + let thunk = root.stack.tos_mut().expect("stack underflow"); + { + let thunk = thunk.as_gc::().expect("expected thunk"); + *thunk.borrow_mut(mc) = ThunkState::Evaluated(val); + } + *thunk = val; + }) + } + } + + pub(super) fn force_n(&mut self, n: usize) { + if n == 0 { + return; + } + self.arena.mutate_root(|_, root| { + for _ in 0..n - 1 { + let tos = root.pop_stack(); + root.temp_stack.push(tos); + } + }); + self.force_tos(); + for _ in 0..n - 1 { + self.arena.mutate_root(|_, root| { + let next = root.temp_stack.pop().expect("temp stack underflow"); + root.push_stack(next); + }); + self.force_tos(); + } + } + + pub(super) fn handle_return(&mut self) -> Action { + self.force_tos(); + self.arena.mutate_root(|_, root| { + let Some(frame) = root.frames.pop() else { + // FIXME: ForceMode + root.current_env = None; + return Action::Done(Ok(convert_value( + root.stack.pop().expect("what the heck"), + &self.strings, + ))); + }; + + self.pc = frame.pc; + root.current_env = Some(frame.env); + + Action::Return + }) + } + + 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)), + } } } -struct CatchFrameInfo; +fn convert_value<'gc>(val: Value<'gc>, strings: &DefaultStringInterner) -> crate::value::Value { + if let Some(i) = val.as_inline::() { + crate::value::Value::Int(i as i64) + } else if let Some(gc_i) = val.as_gc::() { + crate::value::Value::Int(*gc_i) + } else if let Some(f) = val.as_float() { + crate::value::Value::Float(f) + } else if let Some(b) = val.as_inline::() { + crate::value::Value::Bool(b) + } else if val.is::() { + crate::value::Value::Null + } else if let Some(sid) = val.as_inline::() { + let s = strings.resolve(sid.0).unwrap_or("").to_owned(); + crate::value::Value::String(s) + } else if let Some(ns) = val.as_gc::() { + crate::value::Value::String(ns.as_str().to_owned()) + } else if let Some(attrs) = val.as_gc::>() { + let mut map = std::collections::BTreeMap::new(); + for &(key, val) in attrs.iter() { + let key_str = strings.resolve(key.0).unwrap_or("").to_owned(); + let converted = convert_value(val, strings); + map.insert(crate::value::Symbol::from(key_str), converted); + } + crate::value::Value::AttrSet(crate::value::AttrSet::new(map)) + } else if let Some(list) = val.as_gc::>() { + let items: Vec<_> = list + .inner + .iter() + .copied() + .map(|v| convert_value(v, strings)) + .collect(); + crate::value::Value::List(crate::value::List::new(items)) + } else if val.is::>() { + crate::value::Value::Func + } else if val.is::>() { + crate::value::Value::Thunk + } else if val.as_inline::().is_some() { + crate::value::Value::PrimOp("primop".into()) + } else if val.is::>() { + crate::value::Value::PrimOpApp("primop-app".into()) + } else { + crate::value::Value::Null + } +} + +pub(super) fn vm_err(msg: impl Into) -> VmError { + VmError::Uncatchable(Error::eval_error(msg.into())) +} diff --git a/fix/tests/tests/lang.rs b/fix/tests/tests/lang.rs index 2c768f6..fd86e00 100644 --- a/fix/tests/tests/lang.rs +++ b/fix/tests/tests/lang.rs @@ -14,12 +14,11 @@ fn eval_file(name: &str) -> Result<(Value, Source), String> { let lang_dir = get_lang_dir(); let nix_path = lang_dir.join(format!("{name}.nix")); - let expr = format!(r#"import "{}""#, nix_path.display()); - let mut ctx = Runtime::new().map_err(|e| e.to_string())?; + let content = std::fs::read_to_string(&nix_path).unwrap(); let source = Source { ty: fix::error::SourceType::File(nix_path.into()), - src: expr.into(), + src: content.into(), }; ctx.eval_deep(source.clone()) .map(|val| (val, source)) diff --git a/flake.lock b/flake.lock index bc74035..c66f71c 100644 --- a/flake.lock +++ b/flake.lock @@ -8,11 +8,11 @@ "rust-analyzer-src": "rust-analyzer-src" }, "locked": { - "lastModified": 1774076307, - "narHash": "sha256-v8axK9HGgVERw9oG3SKdsuE+ri0GPUZDyRBN4GLqQ1c=", + "lastModified": 1774596377, + "narHash": "sha256-DiSLMxyTwIUAlhOe34r6kKNQRv6PTF+vf0MG45mAyn4=", "owner": "nix-community", "repo": "fenix", - "rev": "556198cc6c69c0a13228a15e33b2360f333b0092", + "rev": "a88a1c8cf2f094da6347fcec54089f4bcb518409", "type": "github" }, "original": { @@ -37,11 +37,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1773821835, - "narHash": "sha256-TJ3lSQtW0E2JrznGVm8hOQGVpXjJyXY2guAxku2O9A4=", + "lastModified": 1774386573, + "narHash": "sha256-4hAV26quOxdC6iyG7kYaZcM3VOskcPUrdCQd/nx8obc=", "owner": "nixos", "repo": "nixpkgs", - "rev": "b40629efe5d6ec48dd1efba650c797ddbd39ace0", + "rev": "46db2e09e1d3f113a13c0d7b81e2f221c63b8ce9", "type": "github" }, "original": { @@ -61,11 +61,11 @@ "rust-analyzer-src": { "flake": false, "locked": { - "lastModified": 1774036669, - "narHash": "sha256-EWhsBSh/h1VcyLKXuTEyH8lNVB2ktuKVkqx8dkQ6hxk=", + "lastModified": 1774569884, + "narHash": "sha256-E8iWEPzg7OnE0XXXjo75CX7xFauqzJuGZ5wSO9KS8Ek=", "owner": "rust-lang", "repo": "rust-analyzer", - "rev": "0cf3e8a07f0e29825f5db78840e646a4bb519742", + "rev": "443ddcddd0c73b07b799d052f5ef3b448c2f3508", "type": "github" }, "original": { diff --git a/rustfmt.toml b/rustfmt.toml index bf96e77..64d94de 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1 +1,2 @@ group_imports = "StdExternalCrate" +imports_granularity = "Module"