use crate::{
bytecode::{Instruction, OpCode},
executor::vm_impl::stack::drop_with_kind,
executor::VirtualMachine,
};
use shape_value::{NativeKind, VMError, heap_value::{HeapKind, HeapValue}, ValueSlot};
use std::cmp::Ordering;
use std::sync::Arc;
use crate::constants::EXACT_F64_INT_LIMIT;
impl VirtualMachine {
#[inline(always)]
fn i128_to_lossless_f64(v: i128) -> Option<f64> {
if (-EXACT_F64_INT_LIMIT..=EXACT_F64_INT_LIMIT).contains(&v) {
Some(v as f64)
} else {
None
}
}
#[inline(always)]
fn nb_compare_numeric_kinded(
a_bits: u64,
a_kind: NativeKind,
b_bits: u64,
b_kind: NativeKind,
) -> Option<Ordering> {
let a_int = numeric_as_i128(a_bits, a_kind);
let b_int = numeric_as_i128(b_bits, b_kind);
if let (Some(ai), Some(bi)) = (a_int, b_int) {
return Some(ai.cmp(&bi));
}
let a_dec = numeric_as_decimal_ref(a_bits, a_kind);
let b_dec = numeric_as_decimal_ref(b_bits, b_kind);
match (a_dec, b_dec) {
(Some(ad), Some(bd)) => return Some(ad.cmp(bd)),
(Some(ad), None) => {
if let Some(bi) = b_int {
let b_dec = rust_decimal::Decimal::from_i128_with_scale(bi, 0);
return Some(ad.cmp(&b_dec));
}
if let Some(bf) = numeric_as_f64(b_bits, b_kind) {
let b_dec = rust_decimal::Decimal::from_f64_retain(bf)?;
return Some(ad.cmp(&b_dec));
}
}
(None, Some(bd)) => {
if let Some(ai) = a_int {
let a_dec = rust_decimal::Decimal::from_i128_with_scale(ai, 0);
return Some(a_dec.cmp(bd));
}
if let Some(af) = numeric_as_f64(a_bits, a_kind) {
let a_dec = rust_decimal::Decimal::from_f64_retain(af)?;
return Some(a_dec.cmp(bd));
}
}
_ => {}
}
let a_f = numeric_as_f64(a_bits, a_kind);
let b_f = numeric_as_f64(b_bits, b_kind);
if let (Some(af), Some(bf)) = (a_f, b_f) {
return af.partial_cmp(&bf);
}
if let (Some(ai), Some(bf)) = (a_int, b_f) {
let af = Self::i128_to_lossless_f64(ai)?;
return af.partial_cmp(&bf);
}
if let (Some(af), Some(bi)) = (a_f, b_int) {
let bf = Self::i128_to_lossless_f64(bi)?;
return af.partial_cmp(&bf);
}
None
}
#[inline(always)]
pub(in crate::executor) fn exec_typed_comparison(
&mut self,
instruction: &Instruction,
) -> Result<(), VMError> {
if let Some(ref mut metrics) = self.metrics {
if instruction.opcode.is_trusted() {
metrics.record_trusted_op();
} else {
metrics.record_guarded_op();
}
}
use OpCode::*;
match instruction.opcode {
GtInt => {
let (b_bits, b_kind) = self.pop_kinded()?;
let (a_bits, a_kind) = self.pop_kinded()?;
let result = if int_cmp_is_unsigned(a_kind, b_kind) {
a_bits > b_bits
} else {
(a_bits as i64) > (b_bits as i64)
};
self.push_kinded(result as u64, NativeKind::Bool)?;
}
LtInt => {
let (b_bits, b_kind) = self.pop_kinded()?;
let (a_bits, a_kind) = self.pop_kinded()?;
let result = if int_cmp_is_unsigned(a_kind, b_kind) {
a_bits < b_bits
} else {
(a_bits as i64) < (b_bits as i64)
};
self.push_kinded(result as u64, NativeKind::Bool)?;
}
GteInt => {
let (b_bits, b_kind) = self.pop_kinded()?;
let (a_bits, a_kind) = self.pop_kinded()?;
let result = if int_cmp_is_unsigned(a_kind, b_kind) {
a_bits >= b_bits
} else {
(a_bits as i64) >= (b_bits as i64)
};
self.push_kinded(result as u64, NativeKind::Bool)?;
}
LteInt => {
let (b_bits, b_kind) = self.pop_kinded()?;
let (a_bits, a_kind) = self.pop_kinded()?;
let result = if int_cmp_is_unsigned(a_kind, b_kind) {
a_bits <= b_bits
} else {
(a_bits as i64) <= (b_bits as i64)
};
self.push_kinded(result as u64, NativeKind::Bool)?;
}
EqInt => {
let (b_bits, _b_kind) = self.pop_kinded()?;
let (a_bits, _a_kind) = self.pop_kinded()?;
self.push_kinded(((a_bits as i64) == (b_bits as i64)) as u64, NativeKind::Bool)?;
}
NeqInt => {
let (b_bits, _b_kind) = self.pop_kinded()?;
let (a_bits, _a_kind) = self.pop_kinded()?;
self.push_kinded(((a_bits as i64) != (b_bits as i64)) as u64, NativeKind::Bool)?;
}
GtNumber => self.cmp_number_kinded(|a, b| a > b)?,
LtNumber => self.cmp_number_kinded(|a, b| a < b)?,
GteNumber => self.cmp_number_kinded(|a, b| a >= b)?,
LteNumber => self.cmp_number_kinded(|a, b| a <= b)?,
EqNumber => self.cmp_number_kinded(|a, b| a == b)?,
NeqNumber => self.cmp_number_kinded(|a, b| a != b)?,
GtDecimal => self.cmp_decimal_kinded(|a, b| a > b)?,
LtDecimal => self.cmp_decimal_kinded(|a, b| a < b)?,
GteDecimal => self.cmp_decimal_kinded(|a, b| a >= b)?,
LteDecimal => self.cmp_decimal_kinded(|a, b| a <= b)?,
EqDecimal => self.cmp_decimal_kinded(|a, b| a == b)?,
GtString => self.cmp_string_kinded(|a, b| a > b)?,
LtString => self.cmp_string_kinded(|a, b| a < b)?,
GteString => self.cmp_string_kinded(|a, b| a >= b)?,
LteString => self.cmp_string_kinded(|a, b| a <= b)?,
EqString => self.cmp_string_eq_kinded()?,
IsNull => {
let (bits, kind) = self.pop_kinded()?;
let is_absent = is_null_kinded(bits, kind);
drop_with_kind(bits, kind);
self.push_kinded(is_absent as u64, NativeKind::Bool)?;
}
_ => unreachable!(
"exec_typed_comparison called with non-typed-comparison opcode: {:?}",
instruction.opcode
),
}
Ok(())
}
#[inline(always)]
fn cmp_number_kinded(&mut self, cmp: impl FnOnce(f64, f64) -> bool) -> Result<(), VMError> {
let (b_bits, b_kind) = self.pop_kinded()?;
let (a_bits, a_kind) = self.pop_kinded()?;
let af = numeric_as_f64(a_bits, a_kind).ok_or_else(|| VMError::TypeError {
expected: "number",
got: kind_type_name(a_kind),
});
let bf = numeric_as_f64(b_bits, b_kind).ok_or_else(|| VMError::TypeError {
expected: "number",
got: kind_type_name(b_kind),
});
drop_with_kind(a_bits, a_kind);
drop_with_kind(b_bits, b_kind);
let result = cmp(af?, bf?);
self.push_kinded(result as u64, NativeKind::Bool)
}
#[inline(always)]
fn cmp_decimal_kinded(
&mut self,
cmp: impl FnOnce(&rust_decimal::Decimal, &rust_decimal::Decimal) -> bool,
) -> Result<(), VMError> {
let (b_bits, b_kind) = self.pop_kinded()?;
let (a_bits, a_kind) = self.pop_kinded()?;
let result = match (decimal_ref(a_bits, a_kind), decimal_ref(b_bits, b_kind)) {
(Some(ad), Some(bd)) => cmp(ad, bd),
_ => false,
};
drop_with_kind(a_bits, a_kind);
drop_with_kind(b_bits, b_kind);
self.push_kinded(result as u64, NativeKind::Bool)
}
#[inline(always)]
fn cmp_string_kinded(&mut self, cmp: impl FnOnce(&str, &str) -> bool) -> Result<(), VMError> {
let (b_bits, b_kind) = self.pop_kinded()?;
let (a_bits, a_kind) = self.pop_kinded()?;
let result = cmp(
str_ref(a_bits, a_kind).unwrap_or(""),
str_ref(b_bits, b_kind).unwrap_or(""),
);
drop_with_kind(a_bits, a_kind);
drop_with_kind(b_bits, b_kind);
self.push_kinded(result as u64, NativeKind::Bool)
}
#[inline(always)]
fn cmp_string_eq_kinded(&mut self) -> Result<(), VMError> {
let (b_bits, b_kind) = self.pop_kinded()?;
let (a_bits, a_kind) = self.pop_kinded()?;
let a_str = str_ref(a_bits, a_kind);
let b_str = str_ref(b_bits, b_kind);
let a_char = char_value(a_bits, a_kind);
let b_char = char_value(b_bits, b_kind);
let eq = match (a_str, b_str) {
(Some(asr), Some(bsr)) => asr == bsr,
(Some(asr), None) => b_char.is_some_and(|c| {
let mut buf = [0u8; 4];
asr == c.encode_utf8(&mut buf)
}),
(None, Some(bsr)) => a_char.is_some_and(|c| {
let mut buf = [0u8; 4];
c.encode_utf8(&mut buf) == bsr
}),
(None, None) => match (a_char, b_char) {
(Some(ac), Some(bc)) => ac == bc,
_ => false,
},
};
drop_with_kind(a_bits, a_kind);
drop_with_kind(b_bits, b_kind);
self.push_kinded(eq as u64, NativeKind::Bool)
}
}
#[inline]
fn int_cmp_is_unsigned(a_kind: NativeKind, b_kind: NativeKind) -> bool {
matches!(a_kind, NativeKind::UInt64 | NativeKind::NullableUInt64)
|| matches!(b_kind, NativeKind::UInt64 | NativeKind::NullableUInt64)
}
#[inline]
fn numeric_as_i128(bits: u64, kind: NativeKind) -> Option<i128> {
match kind {
NativeKind::Int8
| NativeKind::Int16
| NativeKind::Int32
| NativeKind::Int64
| NativeKind::IntSize => Some((bits as i64) as i128),
NativeKind::UInt8
| NativeKind::UInt16
| NativeKind::UInt32
| NativeKind::UInt64
| NativeKind::UIntSize => Some(bits as i128),
NativeKind::Ptr(HeapKind::BigInt) => {
let hv = unsafe { &*(bits as *const HeapValue) };
if let HeapValue::BigInt(arc) = hv {
Some(**arc as i128)
} else {
None
}
}
_ => None,
}
}
#[inline]
fn numeric_as_f64(bits: u64, kind: NativeKind) -> Option<f64> {
match kind {
NativeKind::Float64 | NativeKind::NullableFloat64 => Some(f64::from_bits(bits)),
NativeKind::Int8
| NativeKind::Int16
| NativeKind::Int32
| NativeKind::Int64
| NativeKind::IntSize => Some(bits as i64 as f64),
NativeKind::UInt8
| NativeKind::UInt16
| NativeKind::UInt32
| NativeKind::UInt64
| NativeKind::UIntSize => Some(bits as f64),
_ => None,
}
}
#[inline]
fn decimal_ref<'a>(bits: u64, kind: NativeKind) -> Option<&'a rust_decimal::Decimal> {
if !matches!(kind, NativeKind::Ptr(HeapKind::Decimal)) || bits == 0 {
return None;
}
let ptr = bits as *const rust_decimal::Decimal;
Some(unsafe { &*ptr })
}
#[inline]
fn numeric_as_decimal_ref<'a>(bits: u64, kind: NativeKind) -> Option<&'a rust_decimal::Decimal> {
decimal_ref(bits, kind)
}
#[inline]
fn str_ref<'a>(bits: u64, kind: NativeKind) -> Option<&'a str> {
if bits == 0 {
return None;
}
match kind {
NativeKind::String => {
let ptr = bits as *const String;
Some(unsafe { (*ptr).as_str() })
}
NativeKind::StringV2 => {
let ptr = bits as usize as *const shape_value::v2::string_obj::StringObj;
Some(unsafe { shape_value::v2::string_obj::StringObj::as_str(ptr) })
}
_ => None,
}
}
#[inline]
fn char_value(bits: u64, kind: NativeKind) -> Option<char> {
if !matches!(kind, NativeKind::Ptr(HeapKind::Char)) {
return None;
}
char::from_u32(bits as u32)
}
#[inline]
fn is_null_kinded(bits: u64, kind: NativeKind) -> bool {
match kind {
NativeKind::Null => true,
NativeKind::Bool => false,
NativeKind::String | NativeKind::Ptr(_) => bits == 0,
NativeKind::NullableFloat64 => f64::from_bits(bits).is_nan(),
NativeKind::NullableInt8
| NativeKind::NullableInt16
| NativeKind::NullableInt32
| NativeKind::NullableInt64
| NativeKind::NullableIntSize
| NativeKind::NullableUInt8
| NativeKind::NullableUInt16
| NativeKind::NullableUInt32
| NativeKind::NullableUInt64
| NativeKind::NullableUIntSize => bits == 0,
_ => false,
}
}
#[inline]
fn kind_type_name(kind: NativeKind) -> &'static str {
match kind {
NativeKind::Null => "null",
NativeKind::Bool => "bool",
NativeKind::Float64 | NativeKind::NullableFloat64 => "number",
NativeKind::Float32 => "f32",
NativeKind::Char => "char",
NativeKind::StringV2 => "string",
NativeKind::DecimalV2 => "decimal",
NativeKind::Int8 | NativeKind::NullableInt8 => "i8",
NativeKind::Int16 | NativeKind::NullableInt16 => "i16",
NativeKind::Int32 | NativeKind::NullableInt32 => "i32",
NativeKind::Int64 | NativeKind::NullableInt64 => "int",
NativeKind::IntSize | NativeKind::NullableIntSize => "isize",
NativeKind::UInt8 | NativeKind::NullableUInt8 => "u8",
NativeKind::UInt16 | NativeKind::NullableUInt16 => "u16",
NativeKind::UInt32 | NativeKind::NullableUInt32 => "u32",
NativeKind::UInt64 | NativeKind::NullableUInt64 => "u64",
NativeKind::UIntSize | NativeKind::NullableUIntSize => "usize",
NativeKind::String => "string",
NativeKind::Ptr(HeapKind::String) => "string",
NativeKind::Ptr(HeapKind::TypedArray) => "array",
NativeKind::Ptr(HeapKind::TypedObject) => "object",
NativeKind::Ptr(HeapKind::HashMap) => "map",
NativeKind::Ptr(HeapKind::Decimal) => "decimal",
NativeKind::Ptr(HeapKind::BigInt) => "bigint",
NativeKind::Ptr(HeapKind::DataTable) => "table",
NativeKind::Ptr(HeapKind::IoHandle) => "io_handle",
NativeKind::Ptr(HeapKind::NativeView) => "native_view",
NativeKind::Ptr(HeapKind::Content) => "content",
NativeKind::Ptr(HeapKind::Instant) => "instant",
NativeKind::Ptr(HeapKind::Temporal) => "temporal",
NativeKind::Ptr(HeapKind::TableView) => "table_view",
NativeKind::Ptr(HeapKind::TaskGroup) => "task_group",
NativeKind::Ptr(HeapKind::Char) => "char",
NativeKind::Ptr(HeapKind::Closure) => "closure",
NativeKind::Ptr(HeapKind::Future) => "future",
NativeKind::Ptr(HeapKind::NativeScalar) => "native_scalar",
NativeKind::Ptr(HeapKind::FilterExpr) => "filter_expr",
NativeKind::Ptr(HeapKind::Reference) => "ref",
NativeKind::Ptr(HeapKind::SharedCell) => "shared_cell",
NativeKind::Ptr(HeapKind::HashSet) => "set",
NativeKind::Ptr(HeapKind::Iterator) => "iterator",
NativeKind::Ptr(HeapKind::Deque) => "deque",
NativeKind::Ptr(HeapKind::Channel) => "channel",
NativeKind::Ptr(HeapKind::PriorityQueue) => "priority_queue",
NativeKind::Ptr(HeapKind::Range) => "range",
NativeKind::Ptr(HeapKind::Result) => "result",
NativeKind::Ptr(HeapKind::Option) => "option",
NativeKind::Ptr(HeapKind::Mutex) => "mutex",
NativeKind::Ptr(HeapKind::Atomic) => "atomic",
NativeKind::Ptr(HeapKind::Lazy) => "lazy",
NativeKind::Ptr(HeapKind::TraitObject) => "trait_object",
NativeKind::Ptr(HeapKind::ModuleFn) => "module_fn",
NativeKind::Ptr(HeapKind::Matrix) => "matrix",
NativeKind::Ptr(HeapKind::MatrixSlice) => "matrix_slice",
}
}
#[allow(dead_code)]
fn _expose(
a_bits: u64,
a_kind: NativeKind,
b_bits: u64,
b_kind: NativeKind,
) -> Option<Ordering> {
VirtualMachine::nb_compare_numeric_kinded(a_bits, a_kind, b_bits, b_kind)
}
#[allow(unused_imports)]
use Arc as _Arc;
#[allow(unused_imports)]
use ValueSlot as _ValueSlot;
#[cfg(test)]
mod tests {
use super::*;
use crate::bytecode::Instruction;
use crate::executor::{VMConfig, VirtualMachine};
fn make_vm() -> VirtualMachine {
VirtualMachine::new(VMConfig::default())
}
fn run_typed_cmp(vm: &mut VirtualMachine, opcode: OpCode) -> bool {
let instr = Instruction { opcode, operand: None };
vm.exec_typed_comparison(&instr).unwrap();
let (bits, kind) = vm.pop_kinded().unwrap();
assert_eq!(kind, NativeKind::Bool, "comparison must produce Bool kind");
bits != 0
}
#[test]
fn typed_int_eq() {
let mut vm = make_vm();
vm.push_kinded(42u64, NativeKind::Int64).unwrap();
vm.push_kinded(42u64, NativeKind::Int64).unwrap();
assert!(run_typed_cmp(&mut vm, OpCode::EqInt));
}
#[test]
fn typed_int_neq() {
let mut vm = make_vm();
vm.push_kinded(1u64, NativeKind::Int64).unwrap();
vm.push_kinded(2u64, NativeKind::Int64).unwrap();
assert!(run_typed_cmp(&mut vm, OpCode::NeqInt));
}
#[test]
fn typed_int_lt() {
let mut vm = make_vm();
vm.push_kinded((-5i64) as u64, NativeKind::Int64).unwrap();
vm.push_kinded(3u64, NativeKind::Int64).unwrap();
assert!(run_typed_cmp(&mut vm, OpCode::LtInt));
}
#[test]
fn typed_int_gt() {
let mut vm = make_vm();
vm.push_kinded(7u64, NativeKind::Int64).unwrap();
vm.push_kinded(3u64, NativeKind::Int64).unwrap();
assert!(run_typed_cmp(&mut vm, OpCode::GtInt));
}
#[test]
fn typed_int_gte_lte_boundary_equal() {
let mut vm = make_vm();
vm.push_kinded(10u64, NativeKind::Int64).unwrap();
vm.push_kinded(10u64, NativeKind::Int64).unwrap();
assert!(run_typed_cmp(&mut vm, OpCode::GteInt));
let mut vm = make_vm();
vm.push_kinded(10u64, NativeKind::Int64).unwrap();
vm.push_kinded(10u64, NativeKind::Int64).unwrap();
assert!(run_typed_cmp(&mut vm, OpCode::LteInt));
}
#[test]
fn typed_number_eq() {
let mut vm = make_vm();
vm.push_kinded(1.5f64.to_bits(), NativeKind::Float64).unwrap();
vm.push_kinded(1.5f64.to_bits(), NativeKind::Float64).unwrap();
assert!(run_typed_cmp(&mut vm, OpCode::EqNumber));
}
#[test]
fn typed_number_lt() {
let mut vm = make_vm();
vm.push_kinded((-1.0f64).to_bits(), NativeKind::Float64).unwrap();
vm.push_kinded(0.5f64.to_bits(), NativeKind::Float64).unwrap();
assert!(run_typed_cmp(&mut vm, OpCode::LtNumber));
}
#[test]
fn typed_number_gt() {
let mut vm = make_vm();
vm.push_kinded(3.14f64.to_bits(), NativeKind::Float64).unwrap();
vm.push_kinded(2.71f64.to_bits(), NativeKind::Float64).unwrap();
assert!(run_typed_cmp(&mut vm, OpCode::GtNumber));
}
#[test]
fn typed_number_eq_nan_is_false() {
let mut vm = make_vm();
vm.push_kinded(f64::NAN.to_bits(), NativeKind::Float64).unwrap();
vm.push_kinded(f64::NAN.to_bits(), NativeKind::Float64).unwrap();
assert!(!run_typed_cmp(&mut vm, OpCode::EqNumber));
}
#[test]
fn typed_number_neq_nan_is_true() {
let mut vm = make_vm();
vm.push_kinded(f64::NAN.to_bits(), NativeKind::Float64).unwrap();
vm.push_kinded(f64::NAN.to_bits(), NativeKind::Float64).unwrap();
assert!(run_typed_cmp(&mut vm, OpCode::NeqNumber));
}
#[test]
fn typed_number_lt_nan_is_false() {
let mut vm = make_vm();
vm.push_kinded(1.0f64.to_bits(), NativeKind::Float64).unwrap();
vm.push_kinded(f64::NAN.to_bits(), NativeKind::Float64).unwrap();
assert!(!run_typed_cmp(&mut vm, OpCode::LtNumber));
}
#[test]
fn typed_number_gt_nan_is_false() {
let mut vm = make_vm();
vm.push_kinded(1.0f64.to_bits(), NativeKind::Float64).unwrap();
vm.push_kinded(f64::NAN.to_bits(), NativeKind::Float64).unwrap();
assert!(!run_typed_cmp(&mut vm, OpCode::GtNumber));
}
#[test]
fn typed_number_eq_treats_neg_zero_as_zero() {
let mut vm = make_vm();
vm.push_kinded((-0.0f64).to_bits(), NativeKind::Float64).unwrap();
vm.push_kinded((0.0f64).to_bits(), NativeKind::Float64).unwrap();
assert!(run_typed_cmp(&mut vm, OpCode::EqNumber));
}
fn run_is_null(vm: &mut VirtualMachine) -> bool {
let instr = Instruction { opcode: OpCode::IsNull, operand: None };
vm.exec_typed_comparison(&instr).unwrap();
let (bits, kind) = vm.pop_kinded().unwrap();
assert_eq!(kind, NativeKind::Bool);
bits != 0
}
#[test]
fn is_null_on_null_kind_returns_true() {
let mut vm = make_vm();
vm.push_kinded(0u64, NativeKind::Null).unwrap();
assert!(run_is_null(&mut vm));
}
#[test]
fn is_null_on_int_returns_false() {
let mut vm = make_vm();
vm.push_kinded(42u64, NativeKind::Int64).unwrap();
assert!(!run_is_null(&mut vm));
}
#[test]
fn is_null_on_zero_int_returns_false() {
let mut vm = make_vm();
vm.push_kinded(0u64, NativeKind::Int64).unwrap();
assert!(!run_is_null(&mut vm));
}
#[test]
fn is_null_on_false_bool_returns_false_post_r5b2() {
let mut vm = make_vm();
vm.push_kinded(0u64, NativeKind::Bool).unwrap();
assert!(!run_is_null(&mut vm));
}
#[test]
fn is_null_on_true_bool_returns_false() {
let mut vm = make_vm();
vm.push_kinded(1u64, NativeKind::Bool).unwrap();
assert!(!run_is_null(&mut vm));
}
#[test]
fn compare_numeric_kinded_handles_int_int() {
assert_eq!(
VirtualMachine::nb_compare_numeric_kinded(
7u64,
NativeKind::Int64,
3u64,
NativeKind::Int64
),
Some(Ordering::Greater),
);
}
#[test]
fn compare_numeric_kinded_handles_float_float() {
assert_eq!(
VirtualMachine::nb_compare_numeric_kinded(
1.0f64.to_bits(),
NativeKind::Float64,
2.0f64.to_bits(),
NativeKind::Float64
),
Some(Ordering::Less),
);
}
#[test]
fn compare_numeric_kinded_int_vs_float_lossless() {
assert_eq!(
VirtualMachine::nb_compare_numeric_kinded(
5u64,
NativeKind::Int64,
5.0f64.to_bits(),
NativeKind::Float64
),
Some(Ordering::Equal),
);
}
fn run_u64_cmp(a: u64, b: u64, opcode: OpCode) -> bool {
let mut vm = make_vm();
vm.push_kinded(a, NativeKind::UInt64).unwrap();
vm.push_kinded(b, NativeKind::UInt64).unwrap();
run_typed_cmp(&mut vm, opcode)
}
#[test]
fn u64_gt_above_i64_max_is_greater() {
assert!(run_u64_cmp(u64::MAX, 2, OpCode::GtInt));
}
#[test]
fn u64_lt_above_i64_max_is_not_less() {
assert!(!run_u64_cmp(u64::MAX, 2, OpCode::LtInt));
}
#[test]
fn u64_gte_equal_full_range() {
assert!(run_u64_cmp(u64::MAX, u64::MAX, OpCode::GteInt));
}
#[test]
fn u64_lte_full_range() {
assert!(run_u64_cmp(1u64 << 63, u64::MAX, OpCode::LteInt));
}
#[test]
fn u64_eq_full_range_is_bit_exact() {
assert!(run_u64_cmp(u64::MAX, u64::MAX, OpCode::EqInt));
assert!(run_u64_cmp(u64::MAX, u64::MAX - 1, OpCode::NeqInt));
}
#[test]
fn u64_mixed_with_int64_literal_operand_uses_unsigned() {
let mut vm = make_vm();
vm.push_kinded(u64::MAX, NativeKind::UInt64).unwrap();
vm.push_kinded(2u64, NativeKind::Int64).unwrap();
assert!(run_typed_cmp(&mut vm, OpCode::GtInt));
}
#[test]
fn signed_int_comparison_unaffected() {
let mut vm = make_vm();
vm.push_kinded((-1i64) as u64, NativeKind::Int64).unwrap();
vm.push_kinded(2u64, NativeKind::Int64).unwrap();
assert!(run_typed_cmp(&mut vm, OpCode::LtInt));
}
}