Files
nix-js/fix-vm/src/instructions/arithmetic.rs
T

418 lines
14 KiB
Rust

use std::cmp::Ordering;
use gc_arena::{Gc, Mutation};
use crate::{BytecodeReader, NixNum, StepResult, StrictValue, Value, VmContextExt, VmError};
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::<crate::List>() else {
return self.finish_err(fix_error::Error::eval_error(
"cannot concatenate: left operand is not a list",
));
};
let Some(r) = rhs.as_gc::<crate::List>() 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::<crate::AttrSet>() else {
return self.finish_err(fix_error::Error::eval_error(
"cannot update: left operand is not a set",
));
};
let Some(r) = rhs.as_gc::<crate::AttrSet>() 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<bool> {
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::<bool>(), rhs.as_inline::<bool>()) {
return Ok(a == b);
}
if lhs.is::<crate::Null>() && rhs.is::<crate::Null>() {
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::<crate::List>(), rhs.as_gc::<crate::List>()) {
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::<crate::AttrSet>(), rhs.as_gc::<crate::AttrSet>()) {
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<NixNum> {
if let Some(i) = val.as_inline::<i32>() {
Some(NixNum::Int(i as i64))
} else if let Some(gc_i) = val.as_gc::<i64>() {
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<Value<'gc>> {
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")),
}
}