diff --git a/fix-codegen/src/disassembler.rs b/fix-codegen/src/disassembler.rs index bd6b8fa..042eefc 100644 --- a/fix-codegen/src/disassembler.rs +++ b/fix-codegen/src/disassembler.rs @@ -25,12 +25,14 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> { } } + #[inline(always)] fn read_u8(&mut self) -> u8 { let b = self.code[self.pc]; self.pc += 1; b } + #[inline(always)] fn read_u16(&mut self) -> u16 { let bytes = self.code[self.pc..self.pc + 2] .try_into() @@ -39,6 +41,7 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> { u16::from_le_bytes(bytes) } + #[inline(always)] fn read_u32(&mut self) -> u32 { let bytes = self.code[self.pc..self.pc + 4] .try_into() @@ -47,6 +50,7 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> { u32::from_le_bytes(bytes) } + #[inline(always)] fn read_i32(&mut self) -> i32 { let bytes = self.code[self.pc..self.pc + 4] .try_into() @@ -55,6 +59,7 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> { i32::from_le_bytes(bytes) } + #[inline(always)] fn read_i64(&mut self) -> i64 { let bytes = self.code[self.pc..self.pc + 8] .try_into() @@ -63,6 +68,7 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> { i64::from_le_bytes(bytes) } + #[inline(always)] fn read_f64(&mut self) -> f64 { let bytes = self.code[self.pc..self.pc + 8] .try_into() @@ -71,6 +77,7 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> { f64::from_le_bytes(bytes) } + #[inline(always)] fn read_operand_data(&mut self) { let tag = self.read_u8(); let ty = OperandType::try_from_primitive(tag).expect("invalid operand type"); diff --git a/fix-vm/src/bytecode_reader.rs b/fix-vm/src/bytecode_reader.rs new file mode 100644 index 0000000..6f3ea9e --- /dev/null +++ b/fix-vm/src/bytecode_reader.rs @@ -0,0 +1,135 @@ +use fix_codegen::OperandType; +use fix_common::StringId; +use num_enum::TryFromPrimitive; +use string_interner::Symbol as _; + +use crate::OperandData; + +pub(crate) struct BytecodeReader<'a> { + bytecode: &'a [u8], + pc: usize, + inst_start_pc: usize, +} + +impl<'a> BytecodeReader<'a> { + pub(crate) fn new(bytecode: &'a [u8], pc: usize) -> Self { + Self { + bytecode, + pc, + inst_start_pc: pc, + } + } + + #[inline(always)] + #[cfg_attr(debug_assertions, track_caller)] + fn read_array(&mut self) -> [u8; N] { + let ret = self.bytecode[self.pc..self.pc + N] + .try_into() + .expect("read_array failed"); + self.pc += N; + ret + } + + #[inline(always)] + pub(crate) fn read_op(&mut self) -> fix_codegen::Op { + use fix_codegen::Op; + self.inst_start_pc = self.pc; + let byte = self.bytecode[self.pc]; + if !likely_stable::likely((0..Op::Illegal as u8).contains(&byte)) { + panic!("unknown opcode: {byte:#04x}") + } + self.pc += 1; + unsafe { std::mem::transmute::(byte) } + } + + #[inline(always)] + pub(crate) fn read_u8(&mut self) -> u8 { + let val = self.bytecode[self.pc]; + self.pc += 1; + val + } + + #[inline(always)] + pub(crate) fn read_u16(&mut self) -> u16 { + u16::from_le_bytes(self.read_array()) + } + + #[inline(always)] + pub(crate) fn read_u32(&mut self) -> u32 { + u32::from_le_bytes(self.read_array()) + } + + #[inline(always)] + pub(crate) fn read_i32(&mut self) -> i32 { + i32::from_le_bytes(self.read_array()) + } + + #[inline(always)] + pub(crate) fn read_i64(&mut self) -> i64 { + i64::from_le_bytes(self.read_array()) + } + + #[inline(always)] + pub(crate) fn read_f64(&mut self) -> f64 { + f64::from_le_bytes(self.read_array()) + } + + #[inline(always)] + pub(crate) fn read_string_id(&mut self) -> StringId { + let raw = self.read_u32(); + StringId(string_interner::symbol::SymbolU32::try_from_usize(raw as usize).unwrap()) + } + + #[inline(always)] + pub(crate) fn read_operand_data(&mut self, ctx: &C) -> OperandData { + let tag = self.read_u8(); + let Ok(ty) = OperandType::try_from_primitive(tag) + .map_err(|err| panic!("unknown operand tag: {:#04x}", err.number)); + match ty { + OperandType::Const => { + let id = self.read_u32(); + OperandData::Const(ctx.get_const(id)) + } + OperandType::Local => { + let layer = self.read_u8(); + let idx = self.read_u32(); + OperandData::Local { layer, idx } + } + OperandType::Builtins => OperandData::Builtins, + OperandType::BigInt => { + let val = self.read_i64(); + OperandData::BigInt(val) + } + } + } + + #[inline(always)] + pub(crate) fn read_attr_key_data(&mut self, ctx: &C) -> crate::AttrKeyData { + use fix_codegen::AttrKeyType; + let tag = self.read_u8(); + let ty = AttrKeyType::try_from_primitive(tag) + .unwrap_or_else(|err| panic!("unknown key tag: {:#04x}", err.number)); + match ty { + AttrKeyType::Static => crate::AttrKeyData::Static(self.read_string_id()), + AttrKeyType::Dynamic => { + crate::AttrKeyData::Dynamic(self.read_operand_data(ctx)) + } + } + } + + pub(crate) fn pc(&self) -> usize { + self.pc + } + + pub(crate) fn set_pc(&mut self, pc: usize) { + self.pc = pc; + } + + pub(crate) fn inst_start_pc(&self) -> usize { + self.inst_start_pc + } + + pub(crate) fn bytecode(&self) -> &'a [u8] { + self.bytecode + } +} diff --git a/fix-vm/src/helpers.rs b/fix-vm/src/helpers.rs index d13a79b..d466c01 100644 --- a/fix-vm/src/helpers.rs +++ b/fix-vm/src/helpers.rs @@ -1,18 +1,7 @@ use fix_error::Error; -use crate::value::StrictValue; -use crate::{NixNum, VmError}; +use crate::VmError; -pub(super) fn vm_err(msg: impl Into) -> VmError { +pub(crate) fn vm_err(msg: impl Into) -> VmError { VmError::Uncatchable(Error::eval_error(msg.into())) -} - -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) - } -} +} \ No newline at end of file diff --git a/fix-vm/src/instructions/arithmetic.rs b/fix-vm/src/instructions/arithmetic.rs new file mode 100644 index 0000000..5fe98bb --- /dev/null +++ b/fix-vm/src/instructions/arithmetic.rs @@ -0,0 +1,416 @@ +use std::cmp::Ordering; + +use gc_arena::{Gc, Mutation}; + +use crate::{BytecodeReader, NixNum, StepResult, StrictValue, VmError, Value}; +use crate::VmContextExt; + +impl<'gc> crate::Vm<'gc> { + #[inline(always)] + pub(crate) fn op_add( + &mut self, + ctx: &mut impl crate::VmContext, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, + ) -> StepResult<'gc> { + if let Some(step) = self.try_force_resolved(1, reader.inst_start_pc(), mc) { + return step; + } + if let Some(step) = self.try_force_resolved(0, reader.inst_start_pc(), mc) { + return step; + } + let rhs = self.pop_stack_forced(); + let lhs = self.pop_stack_forced(); + if let (Some(ls), Some(rs)) = (VmContextExt::get_string(ctx, lhs), VmContextExt::get_string(ctx, rhs)) { + let ns = Gc::new(mc, crate::NixString::new(format!("{ls}{rs}"))); + self.push_stack(Value::new_gc(ns)); + return StepResult::Continue; + } + let res = numeric_binop(lhs, rhs, mc, i64::wrapping_add, |a, b| a + b); + match res { + Ok(val) => { + self.push_stack(val); + StepResult::Continue + } + Err(e) => e.into_step_result(), + } + } + + #[inline(always)] + pub(crate) fn op_sub( + &mut self, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, + ) -> StepResult<'gc> { + self.op_arith(mc, i64::wrapping_sub, |a, b| a - b, reader.inst_start_pc()) + } + + #[inline(always)] + pub(crate) fn op_mul( + &mut self, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, + ) -> StepResult<'gc> { + self.op_arith(mc, i64::wrapping_mul, |a, b| a * b, reader.inst_start_pc()) + } + + #[inline(always)] + fn op_arith( + &mut self, + mc: &Mutation<'gc>, + int_op: fn(i64, i64) -> i64, + float_op: fn(f64, f64) -> f64, + inst_start_pc: usize, + ) -> StepResult<'gc> { + if let Some(step) = self.try_force_resolved(1, inst_start_pc, mc) { + return step; + } + if let Some(step) = self.try_force_resolved(0, inst_start_pc, mc) { + return step; + } + let rhs = self.pop_stack_forced(); + let lhs = self.pop_stack_forced(); + let res = numeric_binop(lhs, rhs, mc, int_op, float_op); + match res { + Ok(val) => { + self.push_stack(val); + StepResult::Continue + } + Err(e) => e.into_step_result(), + } + } + + #[inline(always)] + pub(crate) fn op_div( + &mut self, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, + ) -> StepResult<'gc> { + if let Some(step) = self.try_force_resolved(1, reader.inst_start_pc(), mc) { + return step; + } + if let Some(step) = self.try_force_resolved(0, reader.inst_start_pc(), mc) { + return step; + } + let rhs = self.pop_stack_forced(); + let lhs = self.pop_stack_forced(); + match (get_num(rhs), get_num(lhs)) { + (_, Some(NixNum::Int(0))) | (_, Some(NixNum::Float(0.))) => { + return VmError::Uncatchable(fix_error::Error::eval_error( + "division by zero", + )) + .into_step_result(); + } + _ => {} + } + let res = numeric_binop(lhs, rhs, mc, |a, b| a / b, |a, b| a / b); + match res { + Ok(val) => { + self.push_stack(val); + StepResult::Continue + } + Err(e) => e.into_step_result(), + } + } + + #[inline(always)] + pub(crate) fn op_eq( + &mut self, + ctx: &mut impl crate::VmContext, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, + ) -> StepResult<'gc> { + if let Some(step) = self.try_force_resolved(1, reader.inst_start_pc(), mc) { + return step; + } + if let Some(step) = self.try_force_resolved(0, reader.inst_start_pc(), mc) { + return step; + } + let eq = match self.values_equal(ctx) { + Ok(eq) => eq, + Err(e) => return e.into_step_result(), + }; + self.push_stack(Value::new_inline(eq)); + StepResult::Continue + } + + #[inline(always)] + pub(crate) fn op_neq( + &mut self, + ctx: &mut impl crate::VmContext, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, + ) -> StepResult<'gc> { + if let Some(step) = self.try_force_resolved(1, reader.inst_start_pc(), mc) { + return step; + } + if let Some(step) = self.try_force_resolved(0, reader.inst_start_pc(), mc) { + return step; + } + let eq = match self.values_equal(ctx) { + Ok(eq) => eq, + Err(e) => return e.into_step_result(), + }; + self.push_stack(Value::new_inline(!eq)); + StepResult::Continue + } + + #[inline(always)] + pub(crate) fn op_lt( + &mut self, + ctx: &mut impl crate::VmContext, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, + ) -> StepResult<'gc> { + self.compare_values(ctx, reader, mc, Ordering::is_lt) + } + + #[inline(always)] + pub(crate) fn op_gt( + &mut self, + ctx: &mut impl crate::VmContext, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, + ) -> StepResult<'gc> { + self.compare_values(ctx, reader, mc, Ordering::is_gt) + } + + #[inline(always)] + pub(crate) fn op_leq( + &mut self, + ctx: &mut impl crate::VmContext, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, + ) -> StepResult<'gc> { + self.compare_values(ctx, reader, mc, Ordering::is_le) + } + + #[inline(always)] + pub(crate) fn op_geq( + &mut self, + ctx: &mut impl crate::VmContext, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, + ) -> StepResult<'gc> { + self.compare_values(ctx, reader, mc, Ordering::is_ge) + } + + fn compare_values( + &mut self, + ctx: &impl crate::VmContext, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, + pred: fn(Ordering) -> bool, + ) -> StepResult<'gc> { + if let Some(step) = self.try_force_resolved(1, reader.inst_start_pc(), mc) { + return step; + } + if let Some(step) = self.try_force_resolved(0, reader.inst_start_pc(), mc) { + return step; + } + match self.compare_values_inner(ctx, pred) { + Ok(()) => StepResult::Continue, + Err(e) => e.into_step_result(), + } + } + + #[inline(always)] + pub(crate) fn op_concat( + &mut self, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, + ) -> StepResult<'gc> { + if let Some(step) = self.try_force_resolved(1, reader.inst_start_pc(), mc) { + return step; + } + if let Some(step) = self.try_force_resolved(0, reader.inst_start_pc(), mc) { + return step; + } + let rhs = self.pop_stack_forced(); + let lhs = self.pop_stack_forced(); + let Some(l) = lhs.as_gc::() else { + return VmError::Uncatchable(fix_error::Error::eval_error( + "cannot concatenate: left operand is not a list", + )) + .into_step_result(); + }; + let Some(r) = rhs.as_gc::() else { + return VmError::Uncatchable(fix_error::Error::eval_error( + "cannot concatenate: right operand is not a list", + )) + .into_step_result(); + }; + let mut items = smallvec::SmallVec::new(); + items.extend_from_slice(&l); + items.extend_from_slice(&r); + self.push_stack(Value::new_gc(Gc::new(mc, crate::List { inner: items }))); + StepResult::Continue + } + + #[inline(always)] + pub(crate) fn op_update( + &mut self, + mc: &Mutation<'gc>, + inst_start_pc: usize, + ) -> StepResult<'gc> { + if let Some(step) = self.try_force_resolved(1, inst_start_pc, mc) { + return step; + } + if let Some(step) = self.try_force_resolved(0, inst_start_pc, mc) { + return step; + } + let rhs = self.pop_stack_forced(); + let lhs = self.pop_stack_forced(); + let Some(l) = lhs.as_gc::() else { + return VmError::Uncatchable(fix_error::Error::eval_error( + "cannot update: left operand is not a set", + )) + .into_step_result(); + }; + let Some(r) = rhs.as_gc::() else { + return VmError::Uncatchable(fix_error::Error::eval_error( + "cannot update: right operand is not a set", + )) + .into_step_result(); + }; + self.push_stack(Value::new_gc(l.merge(&r, mc))); + StepResult::Continue + } + + #[inline(always)] + pub(crate) fn op_neg(&mut self) -> StepResult<'gc> { + todo!("implement unary operation"); + } + + #[inline(always)] + pub(crate) fn op_not(&mut self) -> StepResult<'gc> { + todo!("implement unary operation"); + } + + pub(crate) fn values_equal(&mut self, ctx: &impl crate::VmContext) -> crate::VmResult { + let rhs = self.pop_stack_forced(); + let lhs = self.pop_stack_forced(); + + if let (Some(a), Some(b)) = (get_num(lhs), get_num(rhs)) { + return Ok(match (a, b) { + (NixNum::Int(a), NixNum::Int(b)) => a == b, + (NixNum::Float(a), NixNum::Float(b)) => a == b, + (NixNum::Int(a), NixNum::Float(b)) => a as f64 == b, + (NixNum::Float(a), NixNum::Int(b)) => a == b as f64, + }); + } + if let (Some(a), Some(b)) = (lhs.as_inline::(), rhs.as_inline::()) { + return Ok(a == b); + } + if lhs.is::() && rhs.is::() { + return Ok(true); + } + if let (Some(a), Some(b)) = (VmContextExt::get_string(ctx, lhs), VmContextExt::get_string(ctx, rhs)) { + return Ok(a == b); + } + if let (Some(a), Some(b)) = (lhs.as_gc::(), rhs.as_gc::()) { + if a.inner.len() != b.inner.len() { + return Ok(false); + } + let len = a.inner.len(); + for (x, y) in a.inner.iter().zip(b.inner.iter()).rev() { + self.push_stack(*x); + self.push_stack(*y); + } + for i in 0..len { + let eq = self.values_equal(ctx)?; + if !eq { + let rem = len - 1 - i; + self.stack.truncate(self.stack.len() - rem * 2); + return Ok(false); + } + } + return Ok(true); + } + if let (Some(a), Some(b)) = (lhs.as_gc::(), rhs.as_gc::()) { + if a.len() != b.len() { + return Ok(false); + } + let len = a.len(); + for ((k1, v1), (k2, v2)) in a.iter().zip(b.iter()).rev() { + if k1 != k2 { + return Ok(false); + } + self.push_stack(*v1); + self.push_stack(*v2); + } + for i in 0..len { + let eq = self.values_equal(ctx)?; + if !eq { + let rem = len - 1 - i; + self.stack.truncate(self.stack.len() - rem * 2); + return Ok(false); + } + } + return Ok(true); + } + Ok(false) + } + + fn compare_values_inner( + &mut self, + ctx: &impl crate::VmContext, + pred: fn(Ordering) -> bool, + ) -> crate::VmResult<()> { + let rhs = self.pop_stack_forced(); + let lhs = self.pop_stack_forced(); + + if let (Some(a), Some(b)) = (get_num(lhs), get_num(rhs)) { + let ord = match (a, b) { + (NixNum::Int(a), NixNum::Int(b)) => a.cmp(&b), + (NixNum::Float(a), NixNum::Float(b)) => { + a.partial_cmp(&b).unwrap_or(Ordering::Less) + } + (NixNum::Int(a), NixNum::Float(b)) => (a as f64) + .partial_cmp(&b) + .unwrap_or(Ordering::Less), + (NixNum::Float(a), NixNum::Int(b)) => a + .partial_cmp(&(b as f64)) + .unwrap_or(Ordering::Less), + }; + self.push_stack(Value::new_inline(pred(ord))); + return Ok(()); + } + if let (Some(a), Some(b)) = (VmContextExt::get_string(ctx, lhs), VmContextExt::get_string(ctx, rhs)) { + self.push_stack(Value::new_inline(pred(a.cmp(b)))); + return Ok(()); + } + Err(crate::vm_err("cannot compare these types")) + } +} + +pub(crate) 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) + } +} + +#[inline] +fn numeric_binop<'gc>( + lhs: StrictValue<'gc>, + rhs: StrictValue<'gc>, + mc: &Mutation<'gc>, + int_op: fn(i64, i64) -> i64, + float_op: fn(f64, f64) -> f64, +) -> crate::VmResult> { + match (get_num(lhs), get_num(rhs)) { + (Some(NixNum::Int(a)), Some(NixNum::Int(b))) => Ok(Value::make_int(int_op(a, b), mc)), + (Some(NixNum::Float(a)), Some(NixNum::Float(b))) => Ok(Value::new_float(float_op(a, b))), + (Some(NixNum::Int(a)), Some(NixNum::Float(b))) => { + Ok(Value::new_float(float_op(a as f64, b))) + } + (Some(NixNum::Float(a)), Some(NixNum::Int(b))) => { + Ok(Value::new_float(float_op(a, b as f64))) + } + _ => Err(crate::vm_err("cannot perform arithmetic on non-numbers")), + } +} diff --git a/fix-vm/src/instructions/builtins_misc.rs b/fix-vm/src/instructions/builtins_misc.rs new file mode 100644 index 0000000..bdea43f --- /dev/null +++ b/fix-vm/src/instructions/builtins_misc.rs @@ -0,0 +1,62 @@ +use crate::{BytecodeReader, PrimOp, StepResult, Value}; +use fix_builtins::BuiltinId; +use num_enum::TryFromPrimitive; + +impl<'gc> crate::Vm<'gc> { + #[inline(always)] + pub(crate) fn op_load_builtins(&mut self) -> StepResult<'gc> { + self.push_stack(self.builtins); + StepResult::Continue + } + + #[inline(always)] + pub(crate) fn op_load_builtin(&mut self, reader: &mut BytecodeReader<'_>) -> StepResult<'gc> { + let Ok(id) = BuiltinId::try_from_primitive(reader.read_u8()) + .map_err(|err| panic!("unknown builtin id: {}", err.number)); + self.push_stack(Value::new_inline(PrimOp { + id, + arity: fix_builtins::BUILTINS[id as usize].1, + })); + StepResult::Continue + } + + #[inline(always)] + pub(crate) fn op_mk_pos(&mut self, reader: &mut BytecodeReader<'_>) -> StepResult<'gc> { + let _span_id = reader.read_u32(); + todo!("MkPos"); + } + + #[inline(always)] + pub(crate) fn op_load_repl_binding(&mut self, reader: &mut BytecodeReader<'_>) -> StepResult<'gc> { + let _name = reader.read_string_id(); + todo!("LoadReplBinding"); + } + + #[inline(always)] + pub(crate) fn op_load_scoped_binding(&mut self, reader: &mut BytecodeReader<'_>) -> StepResult<'gc> { + let _name = reader.read_string_id(); + todo!("LoadScopedBinding"); + } + + #[inline(always)] + pub(crate) fn op_concat_strings( + &mut self, + ctx: &mut impl crate::VmContext, + reader: &mut BytecodeReader<'_>, + _mc: &gc_arena::Mutation<'gc>, + ) -> StepResult<'gc> { + let _parts_count = reader.read_u16() as usize; + let _force_string = reader.read_u8() != 0; + let mut _operands: smallvec::SmallVec<[crate::OperandData; 4]> = + smallvec::SmallVec::with_capacity(_parts_count); + for _ in 0.._parts_count { + _operands.push(reader.read_operand_data(ctx)); + } + todo!("implement ConcatStrings (force parts, coerce to string, concatenate)"); + } + + #[inline(always)] + pub(crate) fn op_resolve_path(&mut self, _ctx: &mut impl crate::VmContext) -> StepResult<'gc> { + todo!("implement ResolvePath"); + } +} \ No newline at end of file diff --git a/fix-vm/src/instructions/calls.rs b/fix-vm/src/instructions/calls.rs new file mode 100644 index 0000000..42207e1 --- /dev/null +++ b/fix-vm/src/instructions/calls.rs @@ -0,0 +1,153 @@ +use fix_error::Error; + +use crate::{ + BytecodeReader, CallFrame, Closure, Env, StepResult, ThunkState, +}; +use crate::VmContextExt; +use gc_arena::{Gc, Mutation, RefLock}; + +impl<'gc> crate::Vm<'gc> { + #[inline(always)] + pub(crate) fn op_call( + &mut self, + ctx: &mut impl crate::VmContext, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, + ) -> StepResult<'gc> { + if let Some(step) = self.try_force_resolved(0, reader.inst_start_pc(), mc) { + return step; + } + if self.call_depth > 10000 { + return StepResult::Done(Err(Error::eval_error( + "stack overflow; max-call-depth exceeded", + ))); + } + self.call_depth += 1; + let func = self.pop_stack(); + let arg = reader.read_operand_data(ctx).resolve(mc, self); + if let Some(closure) = func.as_gc::() { + let ip = closure.ip; + let n_locals = closure.n_locals; + let env = closure.env; + if let Some(ref _pattern) = closure.pattern { + todo!("pattern call") + } else { + let new_env = Gc::new(mc, RefLock::new(Env::with_arg(arg, n_locals, env))); + self.call_stack.push(CallFrame { + pc: reader.pc(), + stack_depth: 0, + thunk: None, + env: self.env, + with_env: self.with_env, + }); + reader.set_pc(ip as usize); + self.env = new_env; + } + } else { + todo!("call other types: {func:?}") + } + StepResult::Continue + } + + #[inline(always)] + pub(crate) fn op_return( + &mut self, + ctx: &mut impl crate::VmContext, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, + ) -> StepResult<'gc> { + match self.handle_return(reader, ctx, mc) { + Some(result) => StepResult::Done(result), + None => StepResult::Continue, + } + } + + pub(crate) fn handle_return( + &mut self, + reader: &mut BytecodeReader<'_>, + ctx: &C, + mc: &Mutation<'gc>, + ) -> Option>> { + let ret_inst_pc = reader.pc() - 1; + let Some(CallFrame { + pc: ret_pc, + stack_depth, + thunk, + env, + with_env, + }) = self.call_stack.pop() + else { + let val = self.pop_stack(); + return Some(Ok(ctx.convert_value(val))); + }; + reader.set_pc(ret_pc); + if let Some(outer_thunk) = thunk { + let val = self.pop_stack(); + match val.restrict() { + Ok(val) => { + *outer_thunk.borrow_mut(mc) = ThunkState::Evaluated(val); + if reader + .bytecode() + .get(ret_pc) + .copied() + == Some(fix_codegen::Op::Return as u8) + { + self.push_stack(val.relax()); + } + } + Err(inner_thunk) => { + let mut state = inner_thunk.borrow_mut(mc); + match *state { + ThunkState::Pending { + ip: inner_ip, + env: inner_env, + with_env: inner_with_env, + } => { + self.call_stack.push(CallFrame { + pc: ret_pc, + stack_depth, + thunk: Some(outer_thunk), + env, + with_env, + }); + self.call_stack.push(CallFrame { + pc: ret_inst_pc, + stack_depth: 0, + thunk: Some(inner_thunk), + env: inner_env, + with_env: inner_with_env, + }); + *state = ThunkState::Blackhole; + reader.set_pc(inner_ip); + self.env = inner_env; + self.with_env = inner_with_env; + return None; + } + ThunkState::Evaluated(val) => { + *outer_thunk.borrow_mut(mc) = ThunkState::Evaluated(val); + if reader + .bytecode() + .get(ret_pc) + .copied() + == Some(fix_codegen::Op::Return as u8) + { + self.push_stack(val.relax()); + } + } + ThunkState::Apply { func: _, arg: _ } => todo!("force Apply thunk"), + ThunkState::Blackhole => { + return Some(Err(Error::eval_error( + "infinite recursion encountered", + ))); + } + } + } + } + } else { + self.call_depth -= 1; + } + self.env = env; + self.with_env = with_env; + None + } +} \ No newline at end of file diff --git a/fix-vm/src/instructions/closures.rs b/fix-vm/src/instructions/closures.rs new file mode 100644 index 0000000..14a3e94 --- /dev/null +++ b/fix-vm/src/instructions/closures.rs @@ -0,0 +1,83 @@ +use gc_arena::{Gc, Mutation, RefLock}; + +use crate::{BytecodeReader, StepResult, ThunkState, Value}; + +impl<'gc> crate::Vm<'gc> { + #[inline(always)] + pub(crate) fn op_make_thunk(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> StepResult<'gc> { + let entry_point = reader.read_u32(); + let thunk = Gc::new( + mc, + RefLock::new(ThunkState::Pending { + ip: entry_point as usize, + env: self.env, + with_env: self.with_env, + }), + ); + self.push_stack(Value::new_gc(thunk)); + StepResult::Continue + } + + #[inline(always)] + pub(crate) fn op_make_closure(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> StepResult<'gc> { + let entry_point = reader.read_u32(); + let n_locals = reader.read_u32(); + let closure = Gc::new( + mc, + crate::Closure { + ip: entry_point, + n_locals, + env: self.env, + pattern: None, + }, + ); + self.push_stack(Value::new_gc(closure)); + StepResult::Continue + } + + #[inline(always)] + pub(crate) fn op_make_pattern_closure(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> StepResult<'gc> { + let entry_point = reader.read_u32(); + let n_locals = reader.read_u32(); + let req_count = reader.read_u16() as usize; + let opt_count = reader.read_u16() as usize; + let has_ellipsis = reader.read_u8() != 0; + + let mut required = smallvec::SmallVec::new(); + for _ in 0..req_count { + required.push(reader.read_string_id()); + } + let mut optional = smallvec::SmallVec::new(); + for _ in 0..opt_count { + optional.push(reader.read_string_id()); + } + let total = req_count + opt_count; + let mut param_spans = Vec::with_capacity(total); + for _ in 0..total { + let name = reader.read_string_id(); + let span_id = reader.read_u32(); + param_spans.push((name, span_id)); + } + + let pattern = Gc::new( + mc, + crate::PatternInfo { + required, + optional, + ellipsis: has_ellipsis, + param_spans: param_spans.into_boxed_slice(), + }, + ); + let closure = Gc::new( + mc, + crate::Closure { + ip: entry_point, + n_locals, + env: self.env, + pattern: Some(pattern), + }, + ); + self.push_stack(Value::new_gc(closure)); + StepResult::Continue + } +} \ No newline at end of file diff --git a/fix-vm/src/instructions/collections.rs b/fix-vm/src/instructions/collections.rs new file mode 100644 index 0000000..cc01879 --- /dev/null +++ b/fix-vm/src/instructions/collections.rs @@ -0,0 +1,189 @@ +use fix_error::Error; +use gc_arena::Gc; +use smallvec::SmallVec; + +use crate::{ + AttrKeyData, AttrSet, BytecodeReader, List, NixString, OperandData, StepResult, Value, +}; + +impl<'gc> crate::Vm<'gc> { + #[inline(always)] + pub(crate) fn op_make_attrs( + &mut self, + ctx: &mut impl crate::VmContext, + reader: &mut BytecodeReader<'_>, + mc: &gc_arena::Mutation<'gc>, + ) -> StepResult<'gc> { + let count = reader.read_u32() as usize; + let mut entries: SmallVec<[AttrEntry; 4]> = SmallVec::with_capacity(count); + for _ in 0..count { + let key = reader.read_attr_key_data(ctx); + let val = reader.read_operand_data(ctx); + let _span_id = reader.read_u32(); + entries.push(AttrEntry { key, val }); + } + let mut kv: SmallVec<[(crate::StringId, Value); 4]> = SmallVec::with_capacity(count); + for entry in &entries { + let key_sid = match &entry.key { + AttrKeyData::Static(sid) => *sid, + AttrKeyData::Dynamic(op) => { + let v = op.resolve(mc, self); + v.as_inline::() + .expect("dynamic attr key must be a string") + } + }; + let val = entry.val.resolve(mc, self); + kv.push((key_sid, val)); + } + kv.sort_by_key(|(k, _)| *k); + let attrs = Gc::new(mc, AttrSet::from_sorted_unchecked(kv)); + self.push_stack(Value::new_gc(attrs)); + StepResult::Continue + } + + #[inline(always)] + pub(crate) fn op_make_empty_attrs(&mut self) -> StepResult<'gc> { + self.push_stack(self.empty_attrs); + StepResult::Continue + } + + #[inline(always)] + pub(crate) fn op_select_static( + &mut self, + ctx: &mut impl crate::VmContext, + reader: &mut BytecodeReader<'_>, + mc: &gc_arena::Mutation<'gc>, + ) -> StepResult<'gc> { + let _span_id = reader.read_u32(); + let key = reader.read_string_id(); + + if let Some(step) = self.try_force_resolved(0, reader.inst_start_pc(), mc) { + return step; + } + + let attrs = self.peek_stack(0).restrict().expect("forced"); + let Some(attrset) = attrs.as_gc::() else { + return StepResult::Done(Err(Error::eval_error( + "value is not a set while a set was expected", + ))); + }; + + match attrset.lookup(key) { + Some(v) => { + self.replace_stack(0, v); + } + None => { + loop { + let byte = reader.bytecode()[reader.pc()]; + if byte == fix_codegen::Op::SelectStatic as u8 { + reader.set_pc(reader.pc() + 1 + 4 + 4); + } else if byte == fix_codegen::Op::SelectDynamic as u8 { + reader.set_pc(reader.pc() + 1 + 4); + } else if byte == fix_codegen::Op::JumpIfSelectSucceeded as u8 { + reader.set_pc(reader.pc() + 1 + 4); + let _ = self.pop_stack(); + break; + } else { + let name = ctx.resolve_string(key); + return StepResult::Done(Err(Error::eval_error(format!( + "attribute '{name}' missing" + )))); + } + } + } + } + StepResult::Continue + } + + #[inline(always)] + pub(crate) fn op_select_dynamic( + &mut self, + ctx: &mut impl crate::VmContext, + reader: &mut BytecodeReader<'_>, + mc: &gc_arena::Mutation<'gc>, + ) -> StepResult<'gc> { + let _span_id = reader.read_u32(); + + if let Some(step) = self.try_force_resolved(0, reader.inst_start_pc(), mc) { + return step; + } + if let Some(step) = self.try_force_resolved(1, reader.inst_start_pc(), mc) { + return step; + } + + let key_val = self.stack[self.stack.len() - 1] + .restrict() + .expect("dynamic key must be forced"); + let key_sid = if let Some(sid) = key_val.as_inline::() { + sid + } else if let Some(ns) = key_val.as_gc::() { + ctx.intern_string(ns.as_str()) + } else { + return StepResult::Done(Err(Error::eval_error( + "dynamic select key must be a string", + ))); + }; + + let attrset_val = self.stack[self.stack.len() - 2].restrict().expect("forced"); + let Some(attrset) = attrset_val.as_gc::() else { + return StepResult::Done(Err(Error::eval_error( + "value is not a set while a set was expected", + ))); + }; + + match attrset.lookup(key_sid) { + Some(v) => { + self.stack.truncate(self.stack.len() - 2); + self.push_stack(v); + } + None => { + let name = ctx.resolve_string(key_sid); + return StepResult::Done(Err(Error::eval_error(format!( + "attribute '{name}' missing" + )))); + } + } + StepResult::Continue + } + + #[inline(always)] + pub(crate) fn op_jump_if_select_succeeded(&mut self, reader: &mut BytecodeReader<'_>) -> StepResult<'gc> { + let offset = reader.read_i32(); + reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize); + StepResult::Continue + } + + #[inline(always)] + pub(crate) fn op_has_attr(&mut self, reader: &mut BytecodeReader<'_>) -> StepResult<'gc> { + let _n = reader.read_u16() as usize; + todo!("HasAttr"); + } + + #[inline(always)] + pub(crate) fn op_make_list( + &mut self, + ctx: &mut impl crate::VmContext, + reader: &mut BytecodeReader<'_>, + mc: &gc_arena::Mutation<'gc>, + ) -> StepResult<'gc> { + let count = reader.read_u32() as usize; + let mut items: SmallVec<[Value; 4]> = SmallVec::with_capacity(count); + for _ in 0..count { + items.push(reader.read_operand_data(ctx).resolve(mc, self)); + } + let list = Gc::new(mc, List { inner: items }); + self.push_stack(Value::new_gc(list)); + StepResult::Continue + } + + #[inline(always)] + pub(crate) fn op_make_empty_list(&mut self) -> StepResult<'gc> { + self.push_stack(self.empty_list); + StepResult::Continue + } +} + +pub(crate) struct AttrEntry { + pub(crate) key: AttrKeyData, + pub(crate) val: OperandData, +} \ No newline at end of file diff --git a/fix-vm/src/instructions/control.rs b/fix-vm/src/instructions/control.rs new file mode 100644 index 0000000..387e78d --- /dev/null +++ b/fix-vm/src/instructions/control.rs @@ -0,0 +1,51 @@ +use crate::{BytecodeReader, StepResult}; + +impl<'gc> crate::Vm<'gc> { + #[inline(always)] + pub(crate) fn op_jump_if_false( + &mut self, + reader: &mut BytecodeReader<'_>, + mc: &gc_arena::Mutation<'gc>, + ) -> StepResult<'gc> { + let offset = reader.read_i32(); + if let Some(step) = self.try_force_resolved(0, reader.inst_start_pc(), mc) { + return step; + } + let cond = self.pop_stack(); + if cond.as_inline::() == Some(false) { + reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize); + } + StepResult::Continue + } + + #[inline(always)] + pub(crate) fn op_jump_if_true( + &mut self, + reader: &mut BytecodeReader<'_>, + mc: &gc_arena::Mutation<'gc>, + ) -> StepResult<'gc> { + let offset = reader.read_i32(); + if let Some(step) = self.try_force_resolved(0, reader.inst_start_pc(), mc) { + return step; + } + let cond = self.pop_stack(); + if cond.as_inline::() == Some(true) { + reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize); + } + StepResult::Continue + } + + #[inline(always)] + pub(crate) fn op_jump(&mut self, reader: &mut BytecodeReader<'_>) -> StepResult<'gc> { + let offset = reader.read_i32(); + reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize); + StepResult::Continue + } + + #[inline(always)] + pub(crate) fn op_assert(&mut self, reader: &mut BytecodeReader<'_>) -> StepResult<'gc> { + let _raw_idx = reader.read_u32(); + let _span_id = reader.read_u32(); + todo!("implement Assert (force TOS)"); + } +} diff --git a/fix-vm/src/instructions/literals.rs b/fix-vm/src/instructions/literals.rs new file mode 100644 index 0000000..25a73aa --- /dev/null +++ b/fix-vm/src/instructions/literals.rs @@ -0,0 +1,51 @@ +use gc_arena::{Gc, Mutation}; + +use crate::{BytecodeReader, StepResult, Value}; + +impl<'gc> crate::Vm<'gc> { + #[inline(always)] + pub(crate) fn op_push_smi(&mut self, reader: &mut BytecodeReader<'_>) -> StepResult<'gc> { + let val = reader.read_i32(); + self.push_stack(Value::new_inline(val)); + StepResult::Continue + } + + #[inline(always)] + pub(crate) fn op_push_bigint(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> StepResult<'gc> { + let val = reader.read_i64(); + self.push_stack(Value::new_gc(Gc::new(mc, val))); + StepResult::Continue + } + + #[inline(always)] + pub(crate) fn op_push_float(&mut self, reader: &mut BytecodeReader<'_>) -> StepResult<'gc> { + let val = reader.read_f64(); + self.push_stack(Value::new_float(val)); + StepResult::Continue + } + + #[inline(always)] + pub(crate) fn op_push_string(&mut self, reader: &mut BytecodeReader<'_>) -> StepResult<'gc> { + let sid = reader.read_string_id(); + self.push_stack(Value::new_inline(sid)); + StepResult::Continue + } + + #[inline(always)] + pub(crate) fn op_push_null(&mut self) -> StepResult<'gc> { + self.push_stack(Value::new_inline(crate::Null)); + StepResult::Continue + } + + #[inline(always)] + pub(crate) fn op_push_true(&mut self) -> StepResult<'gc> { + self.push_stack(Value::new_inline(true)); + StepResult::Continue + } + + #[inline(always)] + pub(crate) fn op_push_false(&mut self) -> StepResult<'gc> { + self.push_stack(Value::new_inline(false)); + StepResult::Continue + } +} \ No newline at end of file diff --git a/fix-vm/src/instructions/mod.rs b/fix-vm/src/instructions/mod.rs new file mode 100644 index 0000000..d0c8da5 --- /dev/null +++ b/fix-vm/src/instructions/mod.rs @@ -0,0 +1,9 @@ +pub(crate) mod literals; +pub(crate) mod variables; +pub(crate) mod closures; +pub(crate) mod calls; +pub(crate) mod collections; +pub(crate) mod arithmetic; +pub(crate) mod control; +pub(crate) mod with_scope; +pub(crate) mod builtins_misc; \ No newline at end of file diff --git a/fix-vm/src/instructions/variables.rs b/fix-vm/src/instructions/variables.rs new file mode 100644 index 0000000..175a5f5 --- /dev/null +++ b/fix-vm/src/instructions/variables.rs @@ -0,0 +1,42 @@ +use crate::{BytecodeReader, Mutation, StepResult, Value}; + +impl<'gc> crate::Vm<'gc> { + #[inline(always)] + pub(crate) fn op_load_local(&mut self, reader: &mut BytecodeReader<'_>) -> StepResult<'gc> { + let idx = reader.read_u32() as usize; + self.push_stack(self.env.borrow().locals[idx]); + StepResult::Continue + } + + #[inline(always)] + pub(crate) fn op_load_outer(&mut self, reader: &mut BytecodeReader<'_>) -> StepResult<'gc> { + let layer = reader.read_u8(); + let idx = reader.read_u32() as usize; + let mut cur = self.env; + for _ in 0..layer { + let prev = cur.borrow().prev.expect("LoadOuter: env chain too short"); + cur = prev; + } + let val = cur.borrow().locals[idx]; + self.push_stack(val); + StepResult::Continue + } + + #[inline(always)] + pub(crate) fn op_store_local(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> StepResult<'gc> { + let idx = reader.read_u32() as usize; + let val = self.pop_stack(); + self.env.borrow_mut(mc).locals[idx] = val; + StepResult::Continue + } + + #[inline(always)] + pub(crate) fn op_alloc_locals(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> StepResult<'gc> { + let count = reader.read_u32() as usize; + self.env + .borrow_mut(mc) + .locals + .extend(std::iter::repeat_n(Value::default(), count)); + StepResult::Continue + } +} diff --git a/fix-vm/src/instructions/with_scope.rs b/fix-vm/src/instructions/with_scope.rs new file mode 100644 index 0000000..e49bf5f --- /dev/null +++ b/fix-vm/src/instructions/with_scope.rs @@ -0,0 +1,86 @@ +use fix_error::Error; +use fix_common::Symbol; + +use crate::{BytecodeReader, CallFrame, StepResult, WithEnv}; +use gc_arena::Gc; + +impl<'gc> crate::Vm<'gc> { + #[inline(always)] + pub(crate) fn op_push_with( + &mut self, + ctx: &mut impl crate::VmContext, + reader: &mut BytecodeReader<'_>, + mc: &gc_arena::Mutation<'gc>, + ) -> StepResult<'gc> { + let env = reader.read_operand_data(ctx).resolve(mc, self); + let scope = Gc::new( + mc, + WithEnv { + env, + prev: self.with_env, + }, + ); + self.with_env = Some(scope); + StepResult::Continue + } + + #[inline(always)] + pub(crate) fn op_pop_with(&mut self) -> StepResult<'gc> { + let Some(scope) = self.with_env else { + unreachable!("no with_scope to pop"); + }; + self.with_env = scope.prev; + StepResult::Continue + } + + #[inline(always)] + pub(crate) fn op_prepare_with(&mut self) -> StepResult<'gc> { + self.call_stack.push(CallFrame { + pc: usize::MAX, + stack_depth: 0, + thunk: None, + env: self.env, + with_env: self.with_env, + }); + StepResult::Continue + } + + #[inline(always)] + pub(crate) fn op_lookup_with( + &mut self, + ctx: &mut impl crate::VmContext, + reader: &mut BytecodeReader<'_>, + mc: &gc_arena::Mutation<'gc>, + ) -> StepResult<'gc> { + let name = reader.read_string_id(); + + let Some(&WithEnv { env, prev }) = self.with_env.as_deref() else { + let Some(CallFrame { with_env, .. }) = self.call_stack.pop() else { + unreachable!() + }; + self.with_env = with_env; + return StepResult::Done(Err(Error::eval_error(format!( + "undefined variable '{}'", + Symbol::from(ctx.resolve_string(name)) + )))); + }; + self.push_stack(env); + if let Some(step) = self.try_force_resolved(0, reader.inst_start_pc(), mc) { + return step; + } + + let env = self.pop_stack().as_gc::().unwrap(); + let Some(val) = env.lookup(name) else { + reader.set_pc(reader.inst_start_pc()); + self.with_env = prev; + return StepResult::Continue; + }; + + self.push_stack(val); + let Some(CallFrame { with_env, .. }) = self.call_stack.pop() else { + unreachable!() + }; + self.with_env = with_env; + StepResult::Continue + } +} \ No newline at end of file diff --git a/fix-vm/src/lib.rs b/fix-vm/src/lib.rs index af1f4de..9b4c0b1 100644 --- a/fix-vm/src/lib.rs +++ b/fix-vm/src/lib.rs @@ -3,21 +3,25 @@ use std::path::PathBuf; use fix_builtins::{BUILTINS, BuiltinId}; -use fix_codegen::{AttrKeyType, InstructionPtr, OperandType}; -use fix_common::{StringId, Symbol}; +use fix_codegen::InstructionPtr; +use fix_common::StringId; use fix_error::{Error, Result, Source}; use gc_arena::arena::CollectionPhase; use gc_arena::{Arena, Collect, Gc, Mutation, RefLock, Rootable}; use hashbrown::HashMap; use num_enum::TryFromPrimitive; use smallvec::SmallVec; -use string_interner::Symbol as _; + +mod bytecode_reader; mod boxing; mod value; use value::*; +pub use value::StaticValue; +pub(crate) mod instructions; mod helpers; use helpers::*; -pub use value::StaticValue; + +pub(crate) use bytecode_reader::BytecodeReader; type VmResult = std::result::Result; @@ -33,6 +37,15 @@ impl From> for VmError { } } +impl VmError { + fn into_step_result<'gc>(self) -> StepResult<'gc> { + match self { + VmError::Catchable(_) => todo!("Check for tryEval catch frames"), + VmError::Uncatchable(e) => StepResult::Done(Err(e)), + } + } +} + #[derive(Collect, Clone, Copy, Debug, PartialEq, Eq, Default)] #[collect(require_static)] pub enum ForceMode { @@ -51,7 +64,7 @@ pub trait VmContext { fn compile(&mut self, source: Source); } -trait VmContextExt: VmContext { +pub(crate) trait VmContextExt: VmContext { fn get_string<'a, 'gc: 'a>(&'a self, val: StrictValue<'gc>) -> Option<&'a str>; fn convert_value(&self, val: Value) -> fix_common::Value; } @@ -112,25 +125,70 @@ impl VmContextExt for T { } } +pub(crate) enum StepResult<'gc> { + Continue, + ForceThunk(ForceInfo<'gc>), + Done(Result), +} + +pub(crate) struct ForceInfo<'gc> { + pub(crate) thunk: Gc<'gc, Thunk<'gc>>, + pub(crate) stack_depth: usize, + pub(crate) inst_start_pc: usize, + pub(crate) ip: usize, + pub(crate) env: GcEnv<'gc>, + pub(crate) with_env: Option>, +} + #[derive(Collect)] #[collect(no_drop)] pub struct Vm<'gc> { - stack: Vec>, - call_stack: Vec>, - call_depth: usize, + pub(crate) stack: Vec>, + pub(crate) call_stack: Vec>, + pub(crate) call_depth: usize, #[collect(require_static)] - error_context: Vec, + pub(crate) error_context: Vec, - env: GcEnv<'gc>, - with_env: Option>>, + pub(crate) env: GcEnv<'gc>, + pub(crate) with_env: Option>, - import_cache: HashMap>, + pub(crate) import_cache: HashMap>, - builtins: Value<'gc>, - empty_list: Value<'gc>, - empty_attrs: Value<'gc>, + pub(crate) builtins: Value<'gc>, + pub(crate) empty_list: Value<'gc>, + pub(crate) empty_attrs: Value<'gc>, - force_mode: ForceMode, + pub(crate) force_mode: ForceMode, +} + +pub(crate) enum OperandData { + Const(StaticValue), + Local { layer: u8, idx: u32 }, + Builtins, + BigInt(i64), +} + +impl OperandData { + pub(crate) fn resolve<'gc>(&self, mc: &Mutation<'gc>, root: &Vm<'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)), + } + } +} + +pub(crate) enum AttrKeyData { + Static(StringId), + Dynamic(OperandData), } fn init_builtins<'gc>(mc: &Mutation<'gc>, ctx: &mut impl VmContext) -> Value<'gc> { @@ -210,19 +268,19 @@ impl<'gc> Vm<'gc> { } #[inline(always)] - fn push_stack(&mut self, val: Value<'gc>) { + pub(crate) fn push_stack(&mut self, val: Value<'gc>) { self.stack.push(val); } #[inline(always)] #[must_use] - fn pop_stack(&mut self) -> Value<'gc> { + pub(crate) fn pop_stack(&mut self) -> Value<'gc> { self.stack.pop().expect("stack underflow") } #[inline(always)] #[must_use] - fn peek_stack(&mut self, depth: usize) -> Value<'gc> { + pub(crate) fn peek_stack(&mut self, depth: usize) -> Value<'gc> { *self .stack .get(self.stack.len() - depth - 1) @@ -230,7 +288,7 @@ impl<'gc> Vm<'gc> { } #[inline(always)] - fn replace_stack(&mut self, depth: usize, val: Value<'gc>) { + pub(crate) fn replace_stack(&mut self, depth: usize, val: Value<'gc>) { let len = self.stack.len(); *self .stack @@ -240,13 +298,67 @@ impl<'gc> Vm<'gc> { #[inline(always)] #[cfg_attr(debug_assertions, track_caller)] - fn pop_stack_forced(&mut self) -> StrictValue<'gc> { + pub(crate) fn pop_stack_forced(&mut self) -> StrictValue<'gc> { self.stack .pop() .expect("stack underflow") .restrict() .expect("forced") } + + #[inline(always)] + pub(crate) fn try_force( + &mut self, + depth: usize, + inst_start_pc: usize, + mc: &Mutation<'gc>, + ) -> StepResult<'gc> { + let val = self.peek_stack(depth); + if let Some(thunk) = val.as_gc::() { + let mut state = thunk.borrow_mut(mc); + match *state { + ThunkState::Pending { + ip, + env, + with_env, + } => { + *state = ThunkState::Blackhole; + drop(state); + StepResult::ForceThunk(ForceInfo { + thunk, + stack_depth: depth, + inst_start_pc, + ip, + env, + with_env, + }) + } + ThunkState::Evaluated(v) => { + self.replace_stack(depth, v.relax()); + StepResult::Continue + } + ThunkState::Apply { .. } => todo!("force apply"), + ThunkState::Blackhole => { + StepResult::Done(Err(Error::eval_error("infinite recursion encountered"))) + } + } + } else { + StepResult::Continue + } + } + + #[inline(always)] + pub(crate) fn try_force_resolved( + &mut self, + depth: usize, + inst_start_pc: usize, + mc: &Mutation<'gc>, + ) -> Option> { + match self.try_force(depth, inst_start_pc, mc) { + StepResult::Continue => None, + other => Some(other), + } + } } #[allow(dead_code)] @@ -257,12 +369,12 @@ struct ErrorFrame { #[derive(Collect, Debug)] #[collect(no_drop)] -struct CallFrame<'gc> { - pc: usize, - stack_depth: usize, - thunk: Option>>, - env: Gc<'gc, RefLock>>, - with_env: Option>>, +pub(crate) struct CallFrame<'gc> { + pub(crate) pc: usize, + pub(crate) stack_depth: usize, + pub(crate) thunk: Option>>, + pub(crate) env: Gc<'gc, RefLock>>, + pub(crate) with_env: Option>>, } pub(crate) enum Action { @@ -270,55 +382,11 @@ pub(crate) enum Action { Done(Result), } -enum NixNum { +pub(crate) 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: &Vm<'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, -} - -macro_rules! try_vm { - ($self:ident; $expr:expr) => { - match $expr { - Ok(v) => v, - Err(e) => return Vm::handle_vm_error($self, e), - } - }; -} - impl Vm<'_> { pub fn run( mut ctx: C, @@ -331,8 +399,9 @@ impl Vm<'_> { const COLLECTOR_GRANULARITY: f64 = 1024.0; let mut pc = ip.0; + let bytecode: Vec = ctx.bytecode().to_vec(); loop { - match arena.mutate_root(|mc, root| root.execute_batch(&mut ctx, pc, mc)) { + match arena.mutate_root(|mc, root| root.execute_batch(&bytecode, &mut ctx, pc, mc)) { Action::Continue { pc: new_pc } => { pc = new_pc; if arena.metrics().allocation_debt() > COLLECTOR_GRANULARITY { @@ -353,856 +422,112 @@ impl<'gc> Vm<'gc> { #[inline(always)] fn execute_batch( &mut self, + bytecode: &[u8], ctx: &mut impl VmContext, - mut pc: usize, + pc: usize, mc: &Mutation<'gc>, ) -> Action { - use fix_codegen::Op::{self, *}; + use fix_codegen::Op::*; const DEFAULT_FUEL_AMOUNT: usize = 1024; - #[inline(always)] - #[cfg_attr(debug_assertions, track_caller)] - fn read_array(bytecode: &[u8], pc: &mut usize) -> [u8; N] { - let ret = bytecode[*pc..*pc + N] - .try_into() - .expect("read_array failed"); - *pc += N; - ret - } - macro_rules! read { - (StringId) => {{ - let raw = read!(u32); - StringId(string_interner::symbol::SymbolU32::try_from_usize(raw as usize).unwrap()) - }}; - (OperandData) => {{ - let tag = read!(u8); - let Ok(ty) = OperandType::try_from_primitive(tag) - .map_err(|err| panic!("unknown operand tag: {:#04x}", err.number)); - match ty { - OperandType::Const => { - let id = read!(u32); - OperandData::Const(ctx.get_const(id)) - } - OperandType::Local => { - let layer = read!(u8); - let idx = read!(u32); - OperandData::Local { layer, idx } - } - OperandType::Builtins => OperandData::Builtins, - OperandType::BigInt => { - let val = read!(i64); - OperandData::BigInt(val) - } - } - }}; - ($type:ty) => { - <$type>::from_le_bytes(read_array(ctx.bytecode(), &mut pc)) - }; - } - + let mut reader = BytecodeReader::new(bytecode, pc); let mut fuel = DEFAULT_FUEL_AMOUNT; - 'dispatch: loop { - macro_rules! try_force { - ($depth:expr, $inst_start:expr) => {{ - let val = self.peek_stack($depth); - if let Some(thunk) = val.as_gc::() { - let mut state = thunk.borrow_mut(mc); - match *state { - ThunkState::Pending { ip, env, with_env } => { - // retry - self.call_stack.push(CallFrame { - thunk: Some(thunk), - stack_depth: $depth, - pc: $inst_start, - env: self.env, - with_env: self.with_env, - }); - - pc = ip; - self.env = env; - self.with_env = with_env; - *state = ThunkState::Blackhole; - continue 'dispatch; - } - ThunkState::Evaluated(v) => { - self.replace_stack($depth, v.relax()); - } - ThunkState::Apply { .. } => todo!("force apply"), - ThunkState::Blackhole => { - return Action::Done(Err(Error::eval_error( - "infinite recursion encountered", - ))); - } - } - } - }}; - } + loop { if fuel == 0 { - return Action::Continue { pc }; + return Action::Continue { pc: reader.pc() }; } fuel -= 1; - // Save PC for Instruction Retry - let inst_start_pc = pc; - let byte = ctx.bytecode()[pc]; - if !likely_stable::likely((0..Op::Illegal as u8).contains(&byte)) { - panic!("unknown opcode: {byte:#04x}") - } - let op = unsafe { std::mem::transmute::(byte) }; - pc += 1; + let op = reader.read_op(); - match op { - PushSmi => { - let val = read!(i32); - self.push_stack(Value::new_inline(val)); - } - PushBigInt => { - let val = read!(i64); - self.push_stack(Value::new_gc(Gc::new(mc, val))); - } - PushFloat => { - let val = read!(f64); - self.push_stack(Value::new_float(val)); - } - PushString => { - let sid = read!(StringId); - self.push_stack(Value::new_inline(sid)); - } - PushNull => self.push_stack(Value::new_inline(Null)), - PushTrue => self.push_stack(Value::new_inline(true)), - PushFalse => self.push_stack(Value::new_inline(false)), + let result = match op { + PushSmi => self.op_push_smi(&mut reader), + PushBigInt => self.op_push_bigint(&mut reader, mc), + PushFloat => self.op_push_float(&mut reader), + PushString => self.op_push_string(&mut reader), + PushNull => self.op_push_null(), + PushTrue => self.op_push_true(), + PushFalse => self.op_push_false(), - LoadLocal => { - let idx = read!(u32) as usize; - self.push_stack(self.env.borrow().locals[idx]); - } - LoadOuter => { - let layer = read!(u8); - let idx = read!(u32) as usize; - let mut cur = self.env; - for _ in 0..layer { - let prev = cur.borrow().prev.expect("LoadOuter: env chain too short"); - cur = prev; - } - let val = cur.borrow().locals[idx]; - self.push_stack(val); - } - StoreLocal => { - let idx = read!(u32) as usize; - let val = self.pop_stack(); - self.env.borrow_mut(mc).locals[idx] = val; - } - AllocLocals => { - let count = read!(u32) as usize; - self.env - .borrow_mut(mc) - .locals - .extend(std::iter::repeat_n(Value::default(), count)); - } + LoadLocal => self.op_load_local(&mut reader), + LoadOuter => self.op_load_outer(&mut reader), + StoreLocal => self.op_store_local(&mut reader, mc), + AllocLocals => self.op_alloc_locals(&mut reader, mc), - MakeThunk => { - let entry_point = read!(u32); - let thunk = Gc::new( - mc, - RefLock::new(ThunkState::Pending { - ip: entry_point as usize, - env: self.env, - with_env: self.with_env, - }), - ); - self.push_stack(Value::new_gc(thunk)); - } - MakeClosure => { - let entry_point = read!(u32); - let n_locals = read!(u32); - let closure = Gc::new( - mc, - Closure { - ip: entry_point, - n_locals, - env: self.env, - pattern: None, - }, - ); - self.push_stack(Value::new_gc(closure)); - } - MakePatternClosure => { - let entry_point = read!(u32); - let n_locals = read!(u32); - let req_count = read!(u16) as usize; - let opt_count = read!(u16) as usize; - let has_ellipsis = read!(u8) != 0; + MakeThunk => self.op_make_thunk(&mut reader, mc), + MakeClosure => self.op_make_closure(&mut reader, mc), + MakePatternClosure => self.op_make_pattern_closure(&mut reader, mc), - let mut required = SmallVec::new(); - for _ in 0..req_count { - required.push(read!(StringId)); - } - let mut optional = SmallVec::new(); - for _ in 0..opt_count { - optional.push(read!(StringId)); - } - let total = req_count + opt_count; - let mut param_spans = Vec::with_capacity(total); - for _ in 0..total { - let name = read!(StringId); - let span_id = read!(u32); - param_spans.push((name, span_id)); - } + Call => self.op_call(ctx, &mut reader, mc), + Return => self.op_return(ctx, &mut reader, mc), - 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)); - } + MakeAttrs => self.op_make_attrs(ctx, &mut reader, mc), + MakeEmptyAttrs => self.op_make_empty_attrs(), + SelectStatic => self.op_select_static(ctx, &mut reader, mc), + SelectDynamic => self.op_select_dynamic(ctx, &mut reader, mc), + JumpIfSelectSucceeded => self.op_jump_if_select_succeeded(&mut reader), + HasAttr => self.op_has_attr(&mut reader), - Call => { - try_force!(0, inst_start_pc); - if self.call_depth > 10000 { - return Action::Done(Err(Error::eval_error( - "stack overflow; max-call-depth exceeded", - ))); - } - self.call_depth += 1; - let func = self.pop_stack(); - let arg = read!(OperandData).resolve(mc, self); - if let Some(closure) = func.as_gc::() { - let ip = closure.ip; - let n_locals = closure.n_locals; - let env = closure.env; - if let Some(ref _pattern) = closure.pattern { - todo!("pattern call") - } else { - let new_env = - Gc::new(mc, RefLock::new(Env::with_arg(arg, n_locals, env))); - self.call_stack.push(CallFrame { - pc, - stack_depth: 0, - thunk: None, - env: self.env, - with_env: self.with_env, - }); - pc = ip as usize; - self.env = new_env; - } - } else { - todo!("call other types: {func:?}") - } - } + MakeList => self.op_make_list(ctx, &mut reader, mc), + MakeEmptyList => self.op_make_empty_list(), - MakeAttrs => { - let count = read!(u32) as usize; - let mut entries: SmallVec<[AttrEntry; 4]> = SmallVec::with_capacity(count); - for _ in 0..count { - let key_tag = read!(u8); - let Ok(ty) = AttrKeyType::try_from_primitive(key_tag) - .map_err(|err| panic!("unknown key tag: {:#04x}", err.number)); - let key = match ty { - AttrKeyType::Static => AttrKeyData::Static(read!(StringId)), - AttrKeyType::Dynamic => AttrKeyData::Dynamic(read!(OperandData)), - }; - let val = read!(OperandData); - let _span_id = read!(u32); - entries.push(AttrEntry { key, val }); - } - let mut kv: SmallVec<[(StringId, Value); 4]> = SmallVec::with_capacity(count); - for entry in &entries { - let key_sid = match &entry.key { - &AttrKeyData::Static(sid) => sid, - AttrKeyData::Dynamic(op) => { - let v = op.resolve(mc, self); - v.as_inline::() - .expect("dynamic attr key must be a string") - } - }; - let val = entry.val.resolve(mc, self); - kv.push((key_sid, val)); - } - kv.sort_by_key(|(k, _)| *k); - let attrs = Gc::new(mc, AttrSet::from_sorted_unchecked(kv)); - self.push_stack(Value::new_gc(attrs)); - } - MakeEmptyAttrs => { - self.push_stack(self.empty_attrs); - } + OpAdd => self.op_add(ctx, &mut reader, mc), + OpSub => self.op_sub(&mut reader, mc), + OpMul => self.op_mul(&mut reader, mc), + OpDiv => self.op_div(&mut reader, mc), + OpEq => self.op_eq(ctx, &mut reader, mc), + OpNeq => self.op_neq(ctx, &mut reader, mc), + OpLt => self.op_lt(ctx, &mut reader, mc), + OpGt => self.op_gt(ctx, &mut reader, mc), + OpLeq => self.op_leq(ctx, &mut reader, mc), + OpGeq => self.op_geq(ctx, &mut reader, mc), + OpConcat => self.op_concat(&mut reader, mc), + OpUpdate => self.op_update(mc, reader.inst_start_pc()), - SelectStatic => { - let _span_id = read!(u32); - let key = read!(StringId); + OpNeg => self.op_neg(), + OpNot => self.op_not(), - try_force!(0, inst_start_pc); + JumpIfFalse => self.op_jump_if_false(&mut reader, mc), + JumpIfTrue => self.op_jump_if_true(&mut reader, mc), + Jump => self.op_jump(&mut reader), - let attrs = self.peek_stack(0).restrict().expect("forced"); - let Some(attrset) = attrs.as_gc::() else { - return Action::Done(Err(Error::eval_error("value is not a set while a set was expected"))); - }; + ConcatStrings => self.op_concat_strings(ctx, &mut reader, mc), + ResolvePath => self.op_resolve_path(ctx), - match attrset.lookup(key) { - Some(v) => { - self.replace_stack(0, v); - } - None => { - loop { - let byte = ctx.bytecode()[pc]; - if byte == SelectStatic as u8 { - pc += 1 + 4 + 4; - } else if byte == SelectDynamic as u8 { - pc += 1 + 4; - } else if byte == JumpIfSelectSucceeded as u8 { - pc += 1 + 4; - let _ = self.pop_stack(); - break; - } else { - let name = ctx.resolve_string(key); - return Action::Done(Err(Error::eval_error(format!("attribute '{name}' missing")))); - } - } - } - } - } - SelectDynamic => { - let _span_id = read!(u32); + Assert => self.op_assert(&mut reader), - try_force!(0, inst_start_pc); - try_force!(1, inst_start_pc); + PushWith => self.op_push_with(ctx, &mut reader, mc), + PopWith => self.op_pop_with(), + LookupWith => self.op_lookup_with(ctx, &mut reader, mc), + PrepareWith => self.op_prepare_with(), - let key_val = self.stack[self.stack.len() - 1] - .restrict() - .expect("dynamic key must be forced"); - let key_sid = if let Some(sid) = key_val.as_inline::() { - sid - } else if let Some(ns) = key_val.as_gc::() { - ctx.intern_string(ns.as_str()) - } else { - return self.handle_vm_error(vm_err("dynamic select key must be a string")); - }; + LoadBuiltins => self.op_load_builtins(), + LoadBuiltin => self.op_load_builtin(&mut reader), - let attrset_val = self.stack[self.stack.len() - 2].restrict().expect("forced"); - let Some(attrset) = attrset_val.as_gc::() else { - return self.handle_vm_error(vm_err( - "value is not a set while a set was expected", - )); - }; - - match attrset.lookup(key_sid) { - Some(v) => { - self.stack.truncate(self.stack.len() - 2); - self.push_stack(v); - } - None => { - let name = ctx.resolve_string(key_sid); - return self - .handle_vm_error(vm_err(format!("attribute '{name}' missing"))); - } - } - } - JumpIfSelectSucceeded => { - let offset = read!(i32); - pc = ((pc as isize) + (offset as isize)) as usize; - } - - HasAttr => { - let _n = read!(u16) as usize; - todo!("HasAttr"); - } - - MakeList => { - let count = read!(u32) as usize; - let mut items: SmallVec<[Value; 4]> = SmallVec::with_capacity(count); - for _ in 0..count { - items.push(read!(OperandData).resolve(mc, self)); - } - let list = Gc::new(mc, List { inner: items }); - self.push_stack(Value::new_gc(list)); - } - MakeEmptyList => { - self.push_stack(self.empty_list); - } - - OpAdd => { - try_force!(1, inst_start_pc); - try_force!(0, inst_start_pc); - let res = (|| { - let rhs = self.pop_stack_forced(); - let lhs = self.pop_stack_forced(); - // FIXME: path & string context - if let (Some(ls), Some(rs)) = (ctx.get_string(lhs), ctx.get_string(rhs)) { - let ns = Gc::new(mc, NixString::new(format!("{ls}{rs}"))); - self.push_stack(Value::new_gc(ns)); - return Ok(()); - } - let res = numeric_binop(lhs, rhs, mc, i64::wrapping_add, |a, b| a + b)?; - self.push_stack(res); - VmResult::Ok(()) - })(); - try_vm!(self; res); - } - OpSub | OpMul => { - try_force!(1, inst_start_pc); - try_force!(0, inst_start_pc); - - #[allow(clippy::type_complexity)] - let func: (fn(i64, i64) -> i64, fn(f64, f64) -> f64) = match op { - OpSub => (i64::wrapping_sub, |a, b| a - b), - OpMul => (i64::wrapping_mul, |a, b| a * b), - _ => unreachable!(), - }; - let res = (|| { - let rhs = self.pop_stack_forced(); - let lhs = self.pop_stack_forced(); - let res = numeric_binop(lhs, rhs, mc, func.0, func.1)?; - self.push_stack(res); - VmResult::Ok(()) - })(); - try_vm!(self; res); - } - OpDiv => { - try_force!(1, inst_start_pc); - try_force!(0, inst_start_pc); - let res = (|| { - let rhs = self.pop_stack_forced(); - let lhs = self.pop_stack_forced(); - match (get_num(lhs), get_num(rhs)) { - (_, Some(NixNum::Int(0))) => Err(vm_err("division by zero")), - (_, Some(NixNum::Float(0.))) => Err(vm_err("division by zero")), - _ => Ok(()), - }?; - let res = numeric_binop(lhs, rhs, mc, |a, b| a / b, |a, b| a / b)?; - self.push_stack(res); - VmResult::Ok(()) - })(); - try_vm!(self; res); - } - OpEq | OpNeq => { - try_force!(1, inst_start_pc); - try_force!(0, inst_start_pc); - let map: fn(bool) -> bool = match op { - OpEq => |a| a, - OpNeq => |a| !a, - _ => unreachable!(), - }; - let eq = try_vm!(self; self.values_equal(ctx)); - self.push_stack(Value::new_inline(map(eq))); - } - OpLt | OpGt | OpLeq | OpGeq => { - use std::cmp::Ordering; - try_force!(1, inst_start_pc); - try_force!(0, inst_start_pc); - let pred: fn(Ordering) -> bool = match op { - OpLt => Ordering::is_lt, - OpGt => Ordering::is_gt, - OpLeq => Ordering::is_le, - OpGeq => Ordering::is_ge, - _ => unreachable!(), - }; - try_vm!(self; self.compare_values(ctx, pred)); - } - OpConcat => { - try_force!(1, inst_start_pc); - try_force!(0, inst_start_pc); - let res = (|| { - let rhs = self.pop_stack_forced(); - let lhs = self.pop_stack_forced(); - // TODO: better type-assert ergonomic - let Some(l) = lhs.as_gc::() else { - return Err(vm_err("cannot concatenate: left operand is not a list")); - }; - let Some(r) = rhs.as_gc::() else { - return Err(vm_err("cannot concatenate: right operand is not a list")); - }; - let mut items = SmallVec::new(); - items.extend_from_slice(&l); - items.extend_from_slice(&r); - self.push_stack(Value::new_gc(Gc::new(mc, List { inner: items }))); - VmResult::Ok(()) - })(); - try_vm!(self; res); - } - OpUpdate => { - try_force!(1, inst_start_pc); - try_force!(0, inst_start_pc); - let res = (|| { - let rhs = self.pop_stack_forced(); - let lhs = self.pop_stack_forced(); - // TODO: better type-assert ergonomic - let Some(l) = lhs.as_gc::() else { - return Err(vm_err("cannot update: left operand is not a set")); - }; - let Some(r) = rhs.as_gc::() else { - return Err(vm_err("cannot update: right operand is not a set")); - }; - self.push_stack(Value::new_gc(l.merge(&r, mc))); - VmResult::Ok(()) - })(); - try_vm!(self; res); - } - - OpNeg => { - todo!("implement unary operation"); - } - OpNot => { - todo!("implement unary operation"); - } - - JumpIfFalse => { - let offset = read!(i32); - try_force!(0, inst_start_pc); - let cond = self.pop_stack(); - if cond.as_inline::() == Some(false) { - pc = ((pc as isize) + (offset as isize)) as usize; - } - } - JumpIfTrue => { - let offset = read!(i32); - try_force!(0, inst_start_pc); - let cond = self.pop_stack(); - if cond.as_inline::() == Some(true) { - pc = ((pc as isize) + (offset as isize)) as usize; - } - } - Jump => { - let offset = read!(i32); - pc = ((pc as isize) + (offset as isize)) as usize; - } - - ConcatStrings => { - let parts_count = read!(u16) as usize; - let _force_string = read!(u8) != 0; - let mut operands: SmallVec<[OperandData; 4]> = - SmallVec::with_capacity(parts_count); - for _ in 0..parts_count { - operands.push(read!(OperandData)); - } - todo!("implement ConcatStrings (force parts, coerce to string, concatenate)"); - } - ResolvePath => { - todo!("implement ResolvePath"); - } - - Assert => { - let _raw_idx = read!(u32); - let _span_id = read!(u32); - todo!("implement Assert (force TOS)"); - } - - PushWith => { - let env = read!(OperandData).resolve(mc, self); - let scope = Gc::new( - mc, - WithEnv { - env, - prev: self.with_env, - }, - ); - self.with_env = Some(scope); - } - PopWith => { - let Some(scope) = self.with_env else { - unreachable!("no with_scope to pop"); - }; - self.with_env = scope.prev; - } - PrepareWith => { - self.call_stack.push(CallFrame { - // sentinel value - pc: usize::MAX, - stack_depth: 0, - thunk: None, - env: self.env, - with_env: self.with_env, - }) - } - LookupWith => { - let name = read!(StringId); - - let Some(&WithEnv { env, prev }) = self.with_env.as_deref() else { - let Some(CallFrame { with_env, .. }) = self.call_stack.pop() else { - unreachable!() - }; - self.with_env = with_env; - return Action::Done(Err(Error::eval_error(format!( - "undefined variable '{}'", - Symbol::from(ctx.resolve_string(name)) - )))); - }; - self.push_stack(env); - try_force!(0, inst_start_pc); - - let env = self.pop_stack().as_gc::().unwrap(); - let Some(val) = env.lookup(name) else { - pc = inst_start_pc; - self.with_env = prev; - continue 'dispatch; - }; - - self.push_stack(val); - let Some(CallFrame { with_env, .. }) = self.call_stack.pop() else { - unreachable!() - }; - self.with_env = with_env; - } - - LoadBuiltins => { - self.push_stack(self.builtins); - } - LoadBuiltin => { - let Ok(id) = BuiltinId::try_from_primitive(read!(u8)) - .map_err(|err| panic!("unknown builtin id: {}", err.number)); - self.push_stack(Value::new_inline(PrimOp { - id, - arity: BUILTINS[id as usize].1, - })); - } - - MkPos => { - let _span_id = read!(u32); - todo!("MkPos") - } - - LoadReplBinding => { - let _name = read!(StringId); - todo!("LoadReplBinding") - } - LoadScopedBinding => { - let _name = read!(StringId); - todo!("LoadScopedBinding") - } - - Return => { - if let Some(result) = self.handle_return(&mut pc, ctx, mc) { - return Action::Done(result); - } - } + MkPos => self.op_mk_pos(&mut reader), + LoadReplBinding => self.op_load_repl_binding(&mut reader), + LoadScopedBinding => self.op_load_scoped_binding(&mut reader), Illegal => unreachable!(), - } - } - } - - #[inline] - fn values_equal(&mut self, ctx: &impl VmContext) -> VmResult { - let rhs = self.pop_stack_forced(); - let lhs = self.pop_stack_forced(); - - if let (Some(a), Some(b)) = (get_num(lhs), get_num(rhs)) { - return Ok(match (a, b) { - (NixNum::Int(a), NixNum::Int(b)) => a == b, - (NixNum::Float(a), NixNum::Float(b)) => a == b, - (NixNum::Int(a), NixNum::Float(b)) => a as f64 == b, - (NixNum::Float(a), NixNum::Int(b)) => a == b as f64, - }); - } - if let (Some(a), Some(b)) = (lhs.as_inline::(), rhs.as_inline::()) { - return Ok(a == b); - } - if lhs.is::() && rhs.is::() { - return Ok(true); - } - if let (Some(a), Some(b)) = (ctx.get_string(lhs), ctx.get_string(rhs)) { - return Ok(a == b); - } - if let (Some(a), Some(b)) = (lhs.as_gc::(), rhs.as_gc::()) { - if a.inner.len() != b.inner.len() { - return Ok(false); - } - let len = a.inner.len(); - for (x, y) in a.inner.iter().zip(b.inner.iter()).rev() { - self.push_stack(*x); - self.push_stack(*y); - } - for i in 0..len { - let eq = self.values_equal(ctx)?; - if !eq { - let rem = len - 1 - i; - self.stack.truncate(self.stack.len() - rem * 2); - return Ok(false); - } - } - return Ok(true); - } - if let (Some(a), Some(b)) = (lhs.as_gc::(), rhs.as_gc::()) { - if a.len() != b.len() { - return Ok(false); - } - let len = a.len(); - for ((k1, v1), (k2, v2)) in a.iter().zip(b.iter()).rev() { - if k1 != k2 { - return Ok(false); - } - self.push_stack(*v1); - self.push_stack(*v2); - } - for i in 0..len { - let eq = self.values_equal(ctx)?; - if !eq { - let rem = len - 1 - i; - self.stack.truncate(self.stack.len() - rem * 2); - return Ok(false); - } - } - return Ok(true); - } - Ok(false) - } - - #[inline] - fn compare_values( - &mut self, - ctx: &impl VmContext, - pred: fn(std::cmp::Ordering) -> bool, - ) -> VmResult<()> { - let rhs = self.pop_stack_forced(); - let lhs = self.pop_stack_forced(); - - if let (Some(a), Some(b)) = (get_num(lhs), get_num(rhs)) { - let ord = match (a, b) { - (NixNum::Int(a), NixNum::Int(b)) => a.cmp(&b), - (NixNum::Float(a), NixNum::Float(b)) => { - a.partial_cmp(&b).unwrap_or(std::cmp::Ordering::Less) - } - (NixNum::Int(a), NixNum::Float(b)) => (a as f64) - .partial_cmp(&b) - .unwrap_or(std::cmp::Ordering::Less), - (NixNum::Float(a), NixNum::Int(b)) => a - .partial_cmp(&(b as f64)) - .unwrap_or(std::cmp::Ordering::Less), }; - self.push_stack(Value::new_inline(pred(ord))); - return Ok(()); - } - if let (Some(a), Some(b)) = (ctx.get_string(lhs), ctx.get_string(rhs)) { - self.push_stack(Value::new_inline(pred(a.cmp(b)))); - return Ok(()); - } - Err(vm_err("cannot compare these types")) - } - #[inline(always)] - fn handle_return( - &mut self, - pc: &mut usize, - ctx: &impl VmContext, - mc: &Mutation<'gc>, - ) -> Option> { - let ret_inst_pc = *pc - 1; - let Some(CallFrame { - pc: ret_pc, - stack_depth, - thunk, - env, - with_env, - }) = self.call_stack.pop() - else { - // Evaluation complete, return value - // FIXME: ForceMode - let val = self.pop_stack(); - return Some(Ok(ctx.convert_value(val))); - }; - *pc = ret_pc; - if let Some(outer_thunk) = thunk { - let val = self.pop_stack(); - match val.restrict() { - Ok(val) => { - *outer_thunk.borrow_mut(mc) = ThunkState::Evaluated(val); - if ctx.bytecode().get(ret_pc).copied() == Some(fix_codegen::Op::Return as u8) { - self.push_stack(val.relax()); - } - } - // Recursively force the thunk - // TODO: extract forcing logic - Err(inner_thunk) => { - let mut state = inner_thunk.borrow_mut(mc); - match *state { - ThunkState::Pending { - ip: inner_ip, - env: inner_env, - with_env: inner_with_env, - } => { - self.call_stack.push(CallFrame { - pc: ret_pc, - stack_depth, - thunk: Some(outer_thunk), - env, - with_env, - }); - self.call_stack.push(CallFrame { - pc: ret_inst_pc, - stack_depth: 0, - thunk: Some(inner_thunk), - env: inner_env, - with_env: inner_with_env, - }); - *state = ThunkState::Blackhole; - *pc = inner_ip; - self.env = inner_env; - self.with_env = inner_with_env; - return None; - } - ThunkState::Evaluated(val) => { - *outer_thunk.borrow_mut(mc) = ThunkState::Evaluated(val); - if ctx.bytecode().get(ret_pc).copied() - == Some(fix_codegen::Op::Return as u8) - { - self.push_stack(val.relax()); - } - } - ThunkState::Apply { func: _, arg: _ } => todo!("force Apply thunk"), - ThunkState::Blackhole => { - return Some(Err(Error::eval_error("infinite recursion encountered"))); - } - } + match result { + StepResult::Continue => {} + StepResult::ForceThunk(info) => { + self.call_stack.push(CallFrame { + thunk: Some(info.thunk), + stack_depth: info.stack_depth, + pc: info.inst_start_pc, + env: self.env, + with_env: self.with_env, + }); + reader.set_pc(info.ip); + self.env = info.env; + self.with_env = info.with_env; } + StepResult::Done(result) => return Action::Done(result), } - } else { - self.call_depth -= 1; - } - self.env = env; - self.with_env = with_env; - None - } - - fn handle_vm_error(&mut self, e: VmError) -> Action { - match e { - VmError::Catchable(_) => { - todo!("Check for tryEval catch frames"); - } - VmError::Uncatchable(e) => Action::Done(Err(e)), } } } - -#[inline] -fn numeric_binop<'gc>( - lhs: StrictValue<'gc>, - rhs: StrictValue<'gc>, - mc: &Mutation<'gc>, - int_op: fn(i64, i64) -> i64, - float_op: fn(f64, f64) -> f64, -) -> VmResult> { - match (get_num(lhs), get_num(rhs)) { - (Some(NixNum::Int(a)), Some(NixNum::Int(b))) => Ok(Value::make_int(int_op(a, b), mc)), - (Some(NixNum::Float(a)), Some(NixNum::Float(b))) => Ok(Value::new_float(float_op(a, b))), - (Some(NixNum::Int(a)), Some(NixNum::Float(b))) => { - Ok(Value::new_float(float_op(a as f64, b))) - } - (Some(NixNum::Float(a)), Some(NixNum::Int(b))) => { - Ok(Value::new_float(float_op(a, b as f64))) - } - _ => Err(vm_err("cannot perform arithmetic on non-numbers")), - } -}