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) => self.finish_vm_err(e), } } #[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) => self.finish_vm_err(e), } } #[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 self.finish_vm_err(VmError::Uncatchable(fix_error::Error::eval_error( "division by zero", ))); } _ => {} } 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) => self.finish_vm_err(e), } } #[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 self.finish_vm_err(e), }; 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 self.finish_vm_err(e), }; 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) => self.finish_vm_err(e), } } #[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 self.finish_err(fix_error::Error::eval_error( "cannot concatenate: left operand is not a list", )); }; let Some(r) = rhs.as_gc::() else { return self.finish_err(fix_error::Error::eval_error( "cannot concatenate: right operand is not a list", )); }; 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 self.finish_err(fix_error::Error::eval_error( "cannot update: left operand is not a set", )); }; let Some(r) = rhs.as_gc::() else { return self.finish_err(fix_error::Error::eval_error( "cannot update: right operand is not a set", )); }; 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")), } }