From 6d3d9db8dfafc8c24b065915440604ece1ae709c Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Thu, 19 Dec 2024 13:34:40 -0800 Subject: [PATCH 01/24] Start of nan boxing values --- core/engine/src/value/inner.rs | 139 +++++++++++++++++++++++++++++++++ core/engine/src/value/mod.rs | 1 + 2 files changed, 140 insertions(+) create mode 100644 core/engine/src/value/inner.rs diff --git a/core/engine/src/value/inner.rs b/core/engine/src/value/inner.rs new file mode 100644 index 00000000000..7e4bb83ce6f --- /dev/null +++ b/core/engine/src/value/inner.rs @@ -0,0 +1,139 @@ +//! Module implementing the operations for the inner value of a `[super::JsValue]`. +//! The [InnerValue] type is a NaN-boxed value, which is a 64-bits value that +//! can represent any JavaScript value. If the integer is a non-NaN value, it +//! will be stored as a 64-bits float. If it is a f64::NAN value, it will be +//! stored as a quiet NaN value. Subnormal numbers are regular float. +//! +//! For any other type of values, the value will be stored as a 51-bits non-zero +//! integer. +//! +//! In short, the memory layout of a NaN-boxed value is as follows: +//! +//! | Type of | Bit Layout | Comment | +//! |-----------|------------|---------| +//! | +Infinity | `7FF0:0000:0000:0000` | | +//! | -Infinity | `FFF0:0000:0000:0000` | | +//! | Undefined | `7FF4:0000:0000:0000` | | +//! | Null | `7FF5:0000:0000:0000` | | +//! | False | `7FF6:0000:0000:0000` | | +//! | True | `7FF6:0000:0000:0001` | | +//! | Integer32 | `7FF7:0000:IIII:IIII` | 32-bits integer. | +//! | BigInt | `7FF[8-F]:PPPP:PPPP:PPPP | 0` | 51-bits pointer. | +//! | Object | `7FF[8-F]:PPPP:PPPP:PPPP | 1` | 51-bits pointer. | +//! | Symbol | `7FF[8-F]:PPPP:PPPP:PPPP | 2` | 51-bits pointer. | +//! | String | `7FF[8-F]:PPPP:PPPP:PPPP | 3` | 51-bits pointer. | +//! | Float64 | Any other values. | | +//! +//! Pointers have the highest bit (in the NaN tag) set to 1, so they +//! can represent any value from `0x8000_0000_0000` to `0xFFFF_FFFF_FFFF`. +//! The last 2 bits of the pointer is used to store the type of the value. +//! +//! This only works on 4-bits aligned values, which is asserted when the +//! `NanBox` is created. + +use crate::JsObject; + +/// The bit mask for a quiet NaN in f64. +const QUIET_NAN: u64 = 0x7FF8_0000_0000_0000; + +/// The bit tag for NaN-boxed values. Masks are applied when creating +/// the value. +#[derive(Copy)] +#[repr(u64)] +enum NanBitTag { + Undefined = 0x7FF4_0000_0000_0000, + Null = 0x7FF5_0000_0000_0000, + False = 0x7FF6_0000_0000_0000, + True = 0x7FF6_0000_0000_0001, + Integer32 = 0x7FF7_0000_0000_0000, + BigInt = 0x7FF8_0000_0000_0000, + Object = 0x7FF8_0000_0000_0001, + Symbol = 0x7FF8_0000_0000_0002, + String = 0x7FF8_0000_0000_0003, +} + +impl NanBitTag { + /// Checks if the value is a specific tagged value. + #[inline] + fn is(self, value: u64) -> bool { + (value & self as u64) == self as u64 + } + + /// Returns a tagged u64 of a 32-bits integer. + #[inline] + fn tag_i32(value: i32) -> u64 { + // Get the 32-bits integer value inside a u64 as is. + let value: u64 = ((value as i64) & 0xFFFF_FFFFi64) as u64; + Self::Integer32 as u64 | value + } + + /// Returns a tagged u64 of a boxed JsObject. + /// + /// # Safety + /// The pointer must be 4-bits aligned and cannot exceed 51-bits. This will + /// result in a panic. Also, the object is not checked for validity. + /// + /// The object is forgotten after this operation. It must be dropped + /// separately. + #[inline] + unsafe fn tag_object(value: Box) -> u64 { + let value = Box::into_raw(value) as u64; + let value_masked: u64 = value & 0x0007_FFFF_FFFF_FFFC_u64; + + // Assert alignment and location of the pointer. + assert_eq!(value, value_masked); + + // Simply cast for bits. + Self::Object as u64 | value + } + + /// Drops a value if it is a pointer, otherwise do nothing. + #[inline] + fn drop_pointer(value: u64) { + let value = value & 0x0007_FFFF_FFFF_FFFC_u64; + let _ = Box::from_raw(value as *mut JsObject); + } +} + +// We cannot NaN-box pointers larger than 64 bits. +static_assertions::const_assert!(size_of::() <= size_of::()); + +/// A NaN-boxed [super::JsValue]'s inner. +pub(super) struct InnerValue { + inner: u64, + // Forces invariance of T. + _marker: std::marker::PhantomData<*mut T>, +} + +impl InnerValue { + /// Creates a new `NanBox` from an inner without checking the validity + /// of the value. + #[must_use] + #[inline] + fn from_inner_unchecked(inner: u64) -> Self { + Self { + inner, + _marker: std::marker::PhantomData, + } + } + + /// Returns a `NanBox` from a 64-bits float. If the float is NaN, + /// it will be reduced to a canonical NaN representation. + #[must_use] + #[inline] + pub(super) fn float64(value: f64) -> Self { + // Reduce any NAN to a canonical NAN representation. + if value.is_nan() { + Self::from_inner_unchecked(QUIET_NAN) + } else { + Self::from_inner_unchecked(value.to_bits()) + } + } + + /// Returns a `NanBox` from a 32-bits integer. + #[must_use] + #[inline] + pub(super) fn integer32(value: i32) -> Self { + Self::from_inner_unchecked(NanBitTag::Integer32 as u64 | (value as u32 as u64)) + } +} diff --git a/core/engine/src/value/mod.rs b/core/engine/src/value/mod.rs index 0d55e3fc781..bec7848bfd9 100644 --- a/core/engine/src/value/mod.rs +++ b/core/engine/src/value/mod.rs @@ -47,6 +47,7 @@ mod conversions; pub(crate) mod display; mod equality; mod hash; +mod inner; mod integer; mod operations; mod r#type; From c64417d6b07a0ea27097af37eb7da9d1594a01d8 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Thu, 19 Dec 2024 16:16:10 -0800 Subject: [PATCH 02/24] Continuing, but this endian thing is not helping --- core/engine/src/value/inner.rs | 430 ++++++++++++++++++++++++++++----- 1 file changed, 373 insertions(+), 57 deletions(-) diff --git a/core/engine/src/value/inner.rs b/core/engine/src/value/inner.rs index 7e4bb83ce6f..e5724170ff1 100644 --- a/core/engine/src/value/inner.rs +++ b/core/engine/src/value/inner.rs @@ -1,44 +1,58 @@ //! Module implementing the operations for the inner value of a `[super::JsValue]`. -//! The [InnerValue] type is a NaN-boxed value, which is a 64-bits value that +//! The `[InnerValue]` type is a NaN-boxed value, which is a 64-bits value that //! can represent any JavaScript value. If the integer is a non-NaN value, it -//! will be stored as a 64-bits float. If it is a f64::NAN value, it will be -//! stored as a quiet NaN value. Subnormal numbers are regular float. +//! will be stored as a 64-bits float. If it is a `f64::NAN` value, it will be +//! stored as a quiet `NaN` value. Subnormal numbers are regular float. //! //! For any other type of values, the value will be stored as a 51-bits non-zero //! integer. //! //! In short, the memory layout of a NaN-boxed value is as follows: //! -//! | Type of | Bit Layout | Comment | -//! |-----------|------------|---------| -//! | +Infinity | `7FF0:0000:0000:0000` | | -//! | -Infinity | `FFF0:0000:0000:0000` | | -//! | Undefined | `7FF4:0000:0000:0000` | | -//! | Null | `7FF5:0000:0000:0000` | | -//! | False | `7FF6:0000:0000:0000` | | -//! | True | `7FF6:0000:0000:0001` | | -//! | Integer32 | `7FF7:0000:IIII:IIII` | 32-bits integer. | -//! | BigInt | `7FF[8-F]:PPPP:PPPP:PPPP | 0` | 51-bits pointer. | -//! | Object | `7FF[8-F]:PPPP:PPPP:PPPP | 1` | 51-bits pointer. | -//! | Symbol | `7FF[8-F]:PPPP:PPPP:PPPP | 2` | 51-bits pointer. | -//! | String | `7FF[8-F]:PPPP:PPPP:PPPP | 3` | 51-bits pointer. | -//! | Float64 | Any other values. | | +//! | Type of | Bit Layout | Comment | +//! |-------------------|------------|---------| +//! | `+Infinity` | `7FF0:0000:0000:0000` | | +//! | `-Infinity` | `FFF0:0000:0000:0000` | | +//! | `NAN` (quiet) | `7FF8:0000:0000:0000` | | +//! | `NAN` (signaling) | `FFF8:0000:0000:0000` | | +//! | `Undefined` | `7FF4:0000:0000:0000` | | +//! | `Null` | `7FF5:0000:0000:0000` | | +//! | `False` | `7FF6:0000:0000:0000` | | +//! | `True` | `7FF6:0000:0000:0001` | | +//! | `Integer32` | `7FF7:0000:IIII:IIII` | 32-bits integer. | +//! | `BigInt` | `7FF[8-F]:PPPP:PPPP:PPPP \| 0` | 51-bits pointer. Assumes non-null pointer. | +//! | `Object` | `7FF[8-F]:PPPP:PPPP:PPPP \| 1` | 51-bits pointer. | +//! | `Symbol` | `7FF[8-F]:PPPP:PPPP:PPPP \| 2` | 51-bits pointer. | +//! | `String` | `7FF[8-F]:PPPP:PPPP:PPPP \| 3` | 51-bits pointer. | +//! | `Float64` | Any other values. | | //! -//! Pointers have the highest bit (in the NaN tag) set to 1, so they +//! Pointers have the highest bit (in the `NaN` tag) set to 1, so they //! can represent any value from `0x8000_0000_0000` to `0xFFFF_FFFF_FFFF`. //! The last 2 bits of the pointer is used to store the type of the value. //! +//! The pointers are assumed to never be NULL, and as such no clash +//! with regular NAN should happen. +//! //! This only works on 4-bits aligned values, which is asserted when the -//! `NanBox` is created. +//! `InnerValue` is created. + +use crate::{JsBigInt, JsObject, JsSymbol}; +use boa_string::JsString; +use num_traits::ToBytes; +use static_assertions::const_assert; -use crate::JsObject; +// We cannot NaN-box pointers larger than 64 bits. +const_assert!(size_of::() <= size_of::()); -/// The bit mask for a quiet NaN in f64. -const QUIET_NAN: u64 = 0x7FF8_0000_0000_0000; +// We cannot NaN-box pointers that are not 4-bytes aligned. +const_assert!(align_of::<*mut ()>() >= 4); -/// The bit tag for NaN-boxed values. Masks are applied when creating +/// The bit tags and masks for NaN-boxed values. Masks are applied when creating /// the value. -#[derive(Copy)] +/// +/// This is a utility type that allows to create NaN-boxed values, and to check +/// the type of a NaN-boxed value. +#[derive(Copy, Clone)] #[repr(u64)] enum NanBitTag { Undefined = 0x7FF4_0000_0000_0000, @@ -50,90 +64,392 @@ enum NanBitTag { Object = 0x7FF8_0000_0000_0001, Symbol = 0x7FF8_0000_0000_0002, String = 0x7FF8_0000_0000_0003, + + // Masks + TaggedMask = 0x7FFF_0000_0000_0000, + PointerMask = 0x0007_FFFF_FFFF_FFFC, + PointerTypeMask = 0x0000_0000_0000_0003, } +// Verify that all representations of NanBitTag ARE NAN, but don't match static NAN. +// The only exception to this rule is BigInt, which assumes that the pointer is +// non-null. The static f64::NAN is equal to BigInt. +const_assert!(f64::from_bits(NanBitTag::Undefined as u64).is_nan()); + impl NanBitTag { /// Checks if the value is a specific tagged value. #[inline] - fn is(self, value: u64) -> bool { + const fn is(self, value: u64) -> bool { (value & self as u64) == self as u64 } + /// Checks that a value is a valid boolean (either true or false). + #[inline] + const fn is_bool(value: u64) -> bool { + // We know that if the tag matches false, it is a boolean. + (value & NanBitTag::False as u64) == NanBitTag::False as u64 + } + + /// Checks that a value is a valid float, not a tagged nan boxed value. + #[inline] + const fn is_float(value: u64) -> bool { + (value & NanBitTag::TaggedMask as u64) != NanBitTag::TaggedMask as u64 + } + + /// Return the tag of this value. + #[inline] + const fn tag_of(value: u64) -> Option { + match value & NanBitTag::TaggedMask as u64 { + 0x7FF4_0000_0000_0000 => Some(NanBitTag::Undefined), + 0x7FF5_0000_0000_0000 => Some(NanBitTag::Null), + 0x7FF6_0000_0000_0000 => Some(NanBitTag::False), + 0x7FF6_0000_0000_0001 => Some(NanBitTag::True), + 0x7FF7_0000_0000_0000 => Some(NanBitTag::Integer32), + // Verify this is not a NULL pointer. + 0x7FF8_0000_0000_0000 if (value & NanBitTag::PointerMask as u64) != 0 => { + Some(NanBitTag::BigInt) + } + 0x7FF8_0000_0000_0001 => Some(NanBitTag::Object), + 0x7FF8_0000_0000_0002 => Some(NanBitTag::Symbol), + 0x7FF8_0000_0000_0003 => Some(NanBitTag::String), + _ => None, + } + } + + /// Returns a tagged u64 of a 64-bits float. + #[inline] + const fn tag_f64(value: f64) -> u64 { + if value.is_nan() { + // Reduce any NAN to a canonical NAN representation. + f64::NAN.to_bits() + } else { + value.to_bits() + } + } + /// Returns a tagged u64 of a 32-bits integer. #[inline] - fn tag_i32(value: i32) -> u64 { - // Get the 32-bits integer value inside a u64 as is. - let value: u64 = ((value as i64) & 0xFFFF_FFFFi64) as u64; - Self::Integer32 as u64 | value + const fn tag_i32(value: i32) -> u64 { + // Get the 32-bits integer value inside an u64 as is, in native endian. + let mut tagged = (Self::Integer32 as u64).to_ne_bytes(); + let bytes = value.to_ne_bytes(); + + tagged[4] = bytes[0]; + tagged[5] = bytes[1]; + tagged[6] = bytes[2]; + tagged[7] = bytes[3]; + + u64::from_ne_bytes(tagged) + } + + /// Returns a i32-bits from a tagged integer. + #[inline] + const fn untag_i32(value: u64) -> Option { + if value & NanBitTag::Integer32 as u64 == NanBitTag::Integer32 as u64 { + // Get the 32-bits integer value inside an u64 as is. + let bytes = value.to_ne_bytes(); + Some(i32::from_ne_bytes([bytes[4], bytes[5], bytes[6], bytes[7]])) + } else { + None + } + } + + /// Returns a tagged u64 of a boolean. + #[inline] + const fn tag_bool(value: bool) -> u64 { + if value { + Self::True as u64 + } else { + Self::False as u64 + } } - /// Returns a tagged u64 of a boxed JsObject. + /// Returns a tagged u64 of a boxed `[JsBigInt]`. /// /// # Safety /// The pointer must be 4-bits aligned and cannot exceed 51-bits. This will /// result in a panic. Also, the object is not checked for validity. /// - /// The object is forgotten after this operation. It must be dropped - /// separately. + /// The box is forgotten after this operation. It must be dropped separately, + /// by calling `[Self::drop_pointer]`. + #[inline] + unsafe fn tag_bigint(value: Box) -> u64 { + let value = Box::into_raw(value) as u64; + let value_masked: u64 = value & Self::PointerMask as u64; + + // Assert alignment and location of the pointer. + assert_eq!( + value_masked, value, + "Pointer is not 4-bits aligned or over 51-bits." + ); + // Cannot have a null pointer for bigint. + assert_ne!(value & Self::PointerMask as u64, 0, "Pointer is NULL."); + + // Simply cast for bits. + Self::BigInt as u64 | value + } + + /// Returns a tagged u64 of a boxed `[JsObject]`. + /// + /// # Safety + /// The pointer must be 4-bits aligned and cannot exceed 51-bits. This will + /// result in a panic. Also, the object is not checked for validity. + /// + /// The box is forgotten after this operation. It must be dropped separately, + /// by calling `[Self::drop_pointer]`. #[inline] unsafe fn tag_object(value: Box) -> u64 { let value = Box::into_raw(value) as u64; - let value_masked: u64 = value & 0x0007_FFFF_FFFF_FFFC_u64; + let value_masked: u64 = value & Self::PointerMask as u64; // Assert alignment and location of the pointer. - assert_eq!(value, value_masked); + assert_eq!( + value_masked, value, + "Pointer is not 4-bits aligned or over 51-bits." + ); // Simply cast for bits. Self::Object as u64 | value } + /// Returns a tagged u64 of a boxed `[JsSymbol]`. + /// + /// # Safety + /// The pointer must be 4-bits aligned and cannot exceed 51-bits. This will + /// result in a panic. Also, the object is not checked for validity. + /// + /// The box is forgotten after this operation. It must be dropped separately, + /// by calling `[Self::drop_pointer]`. + #[inline] + unsafe fn tag_symbol(value: Box) -> u64 { + let value = Box::into_raw(value) as u64; + let value_masked: u64 = value & Self::PointerMask as u64; + + // Assert alignment and location of the pointer. + assert_eq!( + value_masked, value, + "Pointer is not 4-bits aligned or over 51-bits." + ); + + // Simply cast for bits. + Self::Symbol as u64 | value + } + + /// Returns a tagged u64 of a boxed `[JsString]`. + /// + /// # Safety + /// The pointer must be 4-bits aligned and cannot exceed 51-bits. This will + /// result in a panic. Also, the object is not checked for validity. + /// + /// The box is forgotten after this operation. It must be dropped separately, + /// by calling `[Self::drop_pointer]`. + #[inline] + unsafe fn tag_string(value: Box) -> u64 { + let value = Box::into_raw(value) as u64; + let value_masked: u64 = value & Self::PointerMask as u64; + + // Assert alignment and location of the pointer. + assert_eq!( + value_masked, value, + "Pointer is not 4-bits aligned or over 51-bits." + ); + + // Simply cast for bits. + Self::String as u64 | value + } + /// Drops a value if it is a pointer, otherwise do nothing. #[inline] - fn drop_pointer(value: u64) { - let value = value & 0x0007_FFFF_FFFF_FFFC_u64; - let _ = Box::from_raw(value as *mut JsObject); + unsafe fn drop_pointer(value: u64) { + let value_ptr = value & Self::PointerMask as u64; + + match Self::tag_of(value) { + Some(Self::BigInt) => { + drop(unsafe { Box::from_raw(value_ptr as *mut JsBigInt) }); + } + Some(Self::Object) => { + drop(unsafe { Box::from_raw(value_ptr as *mut JsObject) }); + } + Some(Self::Symbol) => { + drop(unsafe { Box::from_raw(value_ptr as *mut JsSymbol) }); + } + Some(Self::String) => { + drop(unsafe { Box::from_raw(value_ptr as *mut JsString) }); + } + _ => {} + } } } -// We cannot NaN-box pointers larger than 64 bits. -static_assertions::const_assert!(size_of::() <= size_of::()); - -/// A NaN-boxed [super::JsValue]'s inner. +/// A NaN-boxed `[super::JsValue]`'s inner. pub(super) struct InnerValue { inner: u64, - // Forces invariance of T. - _marker: std::marker::PhantomData<*mut T>, } impl InnerValue { - /// Creates a new `NanBox` from an inner without checking the validity + /// Creates a new `InnerValue` from an u64 value without checking the validity /// of the value. #[must_use] #[inline] fn from_inner_unchecked(inner: u64) -> Self { - Self { - inner, - _marker: std::marker::PhantomData, - } + Self { inner } + } + + /// Returns a `InnerValue` from a Null. + #[must_use] + #[inline] + pub(super) fn null() -> Self { + Self::from_inner_unchecked(NanBitTag::Null as u64) + } + + /// Returns a `InnerValue` from an undefined. + #[must_use] + #[inline] + pub(super) fn undefined() -> Self { + Self::from_inner_unchecked(NanBitTag::Undefined as u64) } - /// Returns a `NanBox` from a 64-bits float. If the float is NaN, - /// it will be reduced to a canonical NaN representation. + /// Returns a `InnerValue` from a 64-bits float. If the float is `NaN`, + /// it will be reduced to a canonical `NaN` representation. #[must_use] #[inline] pub(super) fn float64(value: f64) -> Self { - // Reduce any NAN to a canonical NAN representation. - if value.is_nan() { - Self::from_inner_unchecked(QUIET_NAN) + Self::from_inner_unchecked(NanBitTag::tag_f64(value)) + } + + /// Returns a `InnerValue` from a 32-bits integer. + #[must_use] + #[inline] + pub(super) fn integer32(value: i32) -> Self { + Self::from_inner_unchecked(NanBitTag::tag_i32(value)) + } + + /// Returns a `InnerValue` from a boolean. + #[must_use] + #[inline] + pub(super) fn boolean(value: bool) -> Self { + Self::from_inner_unchecked(NanBitTag::tag_bool(value)) + } + + /// Returns a `InnerValue` from a boxed `[JsBigInt]`. + #[must_use] + #[inline] + pub(super) fn bigint(value: JsBigInt) -> Self { + Self::from_inner_unchecked(unsafe { NanBitTag::tag_bigint(Box::new(value)) }) + } + + /// Returns a `InnerValue` from a boxed `[JsObject]`. + #[must_use] + #[inline] + pub(super) fn object(value: JsObject) -> Self { + Self::from_inner_unchecked(unsafe { NanBitTag::tag_object(Box::new(value)) }) + } + + /// Returns a `InnerValue` from a boxed `[JsSymbol]`. + #[must_use] + #[inline] + pub(super) fn symbol(value: JsSymbol) -> Self { + Self::from_inner_unchecked(unsafe { NanBitTag::tag_symbol(Box::new(value)) }) + } + + /// Returns a `InnerValue` from a boxed `[JsString]`. + #[must_use] + #[inline] + pub(super) fn string(value: JsString) -> Self { + Self::from_inner_unchecked(unsafe { NanBitTag::tag_string(Box::new(value)) }) + } + + /// Returns true if a value is undefined. + #[must_use] + #[inline] + pub(super) fn is_undefined(&self) -> bool { + NanBitTag::Undefined.is(self.inner) + } + + /// Returns true if a value is null. + #[must_use] + #[inline] + pub(super) fn is_null(&self) -> bool { + NanBitTag::Null.is(self.inner) + } + + /// Returns true if a value is a boolean. + #[must_use] + #[inline] + pub(super) fn is_bool(&self) -> bool { + NanBitTag::is_bool(self.inner) + } + + /// Returns true if a value is a 64-bits float. + #[must_use] + #[inline] + pub(super) fn is_float64(&self) -> bool { + NanBitTag::is_float(self.inner) + } + + /// Returns true if a value is a 32-bits integer. + #[must_use] + #[inline] + pub(super) fn is_integer32(&self) -> bool { + NanBitTag::Integer32.is(self.inner) + } + + /// Returns true if a value is a `[JsBigInt]`. A `NaN` will not match here. + #[must_use] + #[inline] + pub(super) fn is_bigint(&self) -> bool { + NanBitTag::BigInt.is(self.inner) && (self.inner & NanBitTag::PointerMask as u64) != 0 + } + + /// Returns true if a value is a boxed Object. + #[must_use] + #[inline] + pub(super) fn is_object(&self) -> bool { + NanBitTag::Object.is(self.inner) + } + + /// Returns true if a value is a boxed Symbol. + #[must_use] + #[inline] + pub(super) fn is_symbol(&self) -> bool { + NanBitTag::Symbol.is(self.inner) + } + + /// Returns true if a value is a boxed String. + #[must_use] + #[inline] + pub(super) fn is_string(&self) -> bool { + NanBitTag::String.is(self.inner) + } + + /// Returns the value as an f64 if it is a float. + #[must_use] + #[inline] + pub(super) fn as_float64(&self) -> Option { + if self.is_float64() { + Some(f64::from_bits(self.inner)) } else { - Self::from_inner_unchecked(value.to_bits()) + None } } - /// Returns a `NanBox` from a 32-bits integer. + /// Returns the value as an i32 if it is an integer. #[must_use] #[inline] - pub(super) fn integer32(value: i32) -> Self { - Self::from_inner_unchecked(NanBitTag::Integer32 as u64 | (value as u32 as u64)) + pub(super) fn as_integer32(&self) -> Option { + if self.is_integer32() { + Some(self.inner as i32) + } else { + None + } + } +} + +impl Drop for InnerValue { + fn drop(&mut self) { + // Drop the pointer if it is a pointer. + unsafe { + NanBitTag::drop_pointer(self.inner); + } } } From e35790875dc73ad0aae89015a55e23a675a8059a Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Thu, 19 Dec 2024 22:15:08 -0800 Subject: [PATCH 03/24] Finish the boxing and add tests --- core/engine/src/builtins/symbol/mod.rs | 1 + core/engine/src/value/conversions/mod.rs | 20 +- .../src/value/conversions/serde_json.rs | 24 +- .../src/value/conversions/try_from_js.rs | 67 +- core/engine/src/value/equality.rs | 122 ++-- core/engine/src/value/hash.rs | 23 +- core/engine/src/value/inner.rs | 627 +++++++++++++++--- core/engine/src/value/mod.rs | 284 +++----- core/engine/src/value/operations.rs | 291 ++++---- core/engine/src/value/type.rs | 21 +- core/engine/src/value/variant.rs | 19 +- 11 files changed, 912 insertions(+), 587 deletions(-) diff --git a/core/engine/src/builtins/symbol/mod.rs b/core/engine/src/builtins/symbol/mod.rs index 43e3651c2fb..06153c5e5cc 100644 --- a/core/engine/src/builtins/symbol/mod.rs +++ b/core/engine/src/builtins/symbol/mod.rs @@ -236,6 +236,7 @@ impl Symbol { fn this_symbol_value(value: &JsValue) -> JsResult { value .as_symbol() + .cloned() .or_else(|| { value .as_object() diff --git a/core/engine/src/value/conversions/mod.rs b/core/engine/src/value/conversions/mod.rs index daee6456064..2d81fdd159c 100644 --- a/core/engine/src/value/conversions/mod.rs +++ b/core/engine/src/value/conversions/mod.rs @@ -1,9 +1,9 @@ //! Conversions from JavaScript values into Rust values, and the other way around. +use super::{JsBigInt, JsObject, JsString, JsSymbol, JsValue, Profiler}; +use crate::value::inner::InnerValue; use crate::{js_string, string::JsStr}; -use super::{InnerValue, JsBigInt, JsObject, JsString, JsSymbol, JsValue, Profiler}; - mod either; mod serde_json; pub(super) mod try_from_js; @@ -15,7 +15,7 @@ impl From> for JsValue { fn from(value: JsStr<'_>) -> Self { let _timer = Profiler::global().start_event("From>", "value"); - Self::from_inner(InnerValue::String(value.into())) + Self::from_inner(InnerValue::string(value.into())) } } @@ -23,7 +23,7 @@ impl From for JsValue { fn from(value: JsString) -> Self { let _timer = Profiler::global().start_event("From", "value"); - Self::from_inner(InnerValue::String(value)) + Self::from_inner(InnerValue::string(value)) } } @@ -45,7 +45,7 @@ impl From for JsValue { fn from(value: JsSymbol) -> Self { let _timer = Profiler::global().start_event("From", "value"); - Self::from_inner(InnerValue::Symbol(value)) + Self::from_inner(InnerValue::symbol(value)) } } @@ -63,7 +63,7 @@ impl From for JsValue { fn from(value: f64) -> Self { let _timer = Profiler::global().start_event("From", "value"); - Self::from_inner(InnerValue::Float64(value)) + Self::from_inner(InnerValue::float64(value)) } } @@ -79,7 +79,7 @@ macro_rules! impl_from_integer { i32::try_from(value) .map_or_else( |_| Self::from(value as f64), - |value| Self::from_inner(InnerValue::Integer32(value)), + |value| Self::from_inner(InnerValue::integer32(value)), ) } } @@ -94,7 +94,7 @@ impl From for JsValue { fn from(value: JsBigInt) -> Self { let _timer = Profiler::global().start_event("From", "value"); - Self::from_inner(InnerValue::BigInt(value)) + Self::from_inner(InnerValue::bigint(value)) } } @@ -103,7 +103,7 @@ impl From for JsValue { fn from(value: bool) -> Self { let _timer = Profiler::global().start_event("From", "value"); - Self::from_inner(InnerValue::Boolean(value)) + Self::from_inner(InnerValue::boolean(value)) } } @@ -112,7 +112,7 @@ impl From for JsValue { fn from(object: JsObject) -> Self { let _timer = Profiler::global().start_event("From", "value"); - Self::from_inner(InnerValue::Object(object)) + Self::from_inner(InnerValue::object(object)) } } diff --git a/core/engine/src/value/conversions/serde_json.rs b/core/engine/src/value/conversions/serde_json.rs index cad9e9933cf..ddc6cf91746 100644 --- a/core/engine/src/value/conversions/serde_json.rs +++ b/core/engine/src/value/conversions/serde_json.rs @@ -1,13 +1,13 @@ //! This module implements the conversions from and into [`serde_json::Value`]. -use super::{InnerValue, JsValue}; +use super::JsValue; use crate::{ builtins::Array, error::JsNativeError, js_string, object::JsObject, property::{PropertyDescriptor, PropertyKey}, - Context, JsResult, + Context, JsResult, JsVariant, }; use serde_json::{Map, Value}; @@ -113,17 +113,17 @@ impl JsValue { /// /// Panics if the `JsValue` is `Undefined`. pub fn to_json(&self, context: &mut Context) -> JsResult { - match &self.inner { - InnerValue::Null => Ok(Value::Null), - InnerValue::Undefined => todo!("undefined to JSON"), - InnerValue::Boolean(b) => Ok(Value::from(*b)), - InnerValue::String(string) => Ok(string.to_std_string_escaped().into()), - InnerValue::Float64(rat) => Ok(Value::from(*rat)), - InnerValue::Integer32(int) => Ok(Value::from(*int)), - InnerValue::BigInt(_bigint) => Err(JsNativeError::typ() + match self.variant() { + JsVariant::Null => Ok(Value::Null), + JsVariant::Undefined => todo!("undefined to JSON"), + JsVariant::Boolean(b) => Ok(Value::from(b)), + JsVariant::String(string) => Ok(string.to_std_string_escaped().into()), + JsVariant::Float64(rat) => Ok(Value::from(rat)), + JsVariant::Integer32(int) => Ok(Value::from(int)), + JsVariant::BigInt(_bigint) => Err(JsNativeError::typ() .with_message("cannot convert bigint to JSON") .into()), - InnerValue::Object(obj) => { + JsVariant::Object(obj) => { let value_by_prop_key = |property_key, context: &mut Context| { obj.borrow() .properties() @@ -168,7 +168,7 @@ impl JsValue { Ok(Value::Object(map)) } } - InnerValue::Symbol(_sym) => Err(JsNativeError::typ() + JsVariant::Symbol(_sym) => Err(JsNativeError::typ() .with_message("cannot convert Symbol to JSON") .into()), } diff --git a/core/engine/src/value/conversions/try_from_js.rs b/core/engine/src/value/conversions/try_from_js.rs index 63844269612..4a5214e088e 100644 --- a/core/engine/src/value/conversions/try_from_js.rs +++ b/core/engine/src/value/conversions/try_from_js.rs @@ -3,7 +3,6 @@ use num_bigint::BigInt; use num_traits::AsPrimitive; -use crate::value::InnerValue; use crate::{js_string, Context, JsBigInt, JsNativeError, JsObject, JsResult, JsString, JsValue}; mod collections; @@ -62,11 +61,12 @@ impl TryFromJs for String { impl TryFromJs for JsString { fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult { - match &value.inner { - InnerValue::String(s) => Ok(s.clone()), - _ => Err(JsNativeError::typ() - .with_message("cannot convert value to a String") - .into()), + if let Some(s) = value.as_string() { + Ok(s.clone()) + } else { + Err(JsNativeError::typ() + .with_message("cannot convert value to a JsString") + .into()) } } } @@ -90,7 +90,7 @@ where T: TryFromJs, { fn try_from_js(value: &JsValue, context: &mut Context) -> JsResult { - let InnerValue::Object(object) = &value.inner else { + let Some(object) = &value.as_object() else { return Err(JsNativeError::typ() .with_message("cannot convert value to a Vec") .into()); @@ -119,33 +119,36 @@ where impl TryFromJs for JsObject { fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult { - match &value.inner { - InnerValue::Object(o) => Ok(o.clone()), - _ => Err(JsNativeError::typ() + if let Some(o) = value.as_object() { + Ok(o.clone()) + } else { + Err(JsNativeError::typ() .with_message("cannot convert value to a Object") - .into()), + .into()) } } } impl TryFromJs for JsBigInt { fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult { - match &value.inner { - InnerValue::BigInt(b) => Ok(b.clone()), - _ => Err(JsNativeError::typ() + if let Some(b) = value.as_bigint() { + Ok(b.clone()) + } else { + Err(JsNativeError::typ() .with_message("cannot convert value to a BigInt") - .into()), + .into()) } } } impl TryFromJs for BigInt { fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult { - match &value.inner { - InnerValue::BigInt(b) => Ok(b.as_inner().clone()), - _ => Err(JsNativeError::typ() + if let Some(b) = value.as_bigint() { + Ok(b.as_inner().clone()) + } else { + Err(JsNativeError::typ() .with_message("cannot convert value to a BigInt") - .into()), + .into()) } } } @@ -158,12 +161,12 @@ impl TryFromJs for JsValue { impl TryFromJs for f64 { fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult { - match &value.inner { - InnerValue::Integer32(i) => Ok((*i).into()), - InnerValue::Float64(r) => Ok(*r), - _ => Err(JsNativeError::typ() + if let Some(f) = value.as_number() { + Ok(f) + } else { + Err(JsNativeError::typ() .with_message("cannot convert value to a f64") - .into()), + .into()) } } } @@ -184,23 +187,25 @@ macro_rules! impl_try_from_js_integer { $( impl TryFromJs for $type { fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult { - match &value.inner { - InnerValue::Integer32(i) => (*i).try_into().map_err(|e| { + if let Some(i) = value.as_i32() { + i.try_into().map_err(|e| { JsNativeError::typ() .with_message(format!( concat!("cannot convert value to a ", stringify!($type), ": {}"), e) ) .into() - }), - InnerValue::Float64(f) => from_f64(*f).ok_or_else(|| { + }) + } else if let Some(f) = value.as_number() { + from_f64(f).ok_or_else(|| { JsNativeError::typ() .with_message(concat!("cannot convert value to a ", stringify!($type))) .into() - }), - _ => Err(JsNativeError::typ() + }) + } else { + Err(JsNativeError::typ() .with_message(concat!("cannot convert value to a ", stringify!($type))) - .into()), + .into()) } } } diff --git a/core/engine/src/value/equality.rs b/core/engine/src/value/equality.rs index 58f664ac8bc..c4c517ae3a4 100644 --- a/core/engine/src/value/equality.rs +++ b/core/engine/src/value/equality.rs @@ -1,5 +1,5 @@ -use super::{InnerValue, JsBigInt, JsObject, JsResult, JsValue, PreferredType}; -use crate::{builtins::Number, Context}; +use super::{JsBigInt, JsObject, JsResult, JsValue, PreferredType}; +use crate::{builtins::Number, Context, JsVariant}; impl JsValue { /// Strict equality comparison. @@ -13,20 +13,20 @@ impl JsValue { return false; } - match (&self.inner, &other.inner) { + match (self.variant(), other.variant()) { // 2. If Type(x) is Number or BigInt, then // a. Return ! Type(x)::equal(x, y). - (InnerValue::BigInt(x), InnerValue::BigInt(y)) => JsBigInt::equal(x, y), - (InnerValue::Float64(x), InnerValue::Float64(y)) => Number::equal(*x, *y), - (InnerValue::Float64(x), InnerValue::Integer32(y)) => Number::equal(*x, f64::from(*y)), - (InnerValue::Integer32(x), InnerValue::Float64(y)) => Number::equal(f64::from(*x), *y), - (InnerValue::Integer32(x), InnerValue::Integer32(y)) => x == y, + (JsVariant::BigInt(x), JsVariant::BigInt(y)) => JsBigInt::equal(x, y), + (JsVariant::Float64(x), JsVariant::Float64(y)) => Number::equal(x, y), + (JsVariant::Float64(x), JsVariant::Integer32(y)) => Number::equal(x, f64::from(y)), + (JsVariant::Integer32(x), JsVariant::Float64(y)) => Number::equal(f64::from(x), y), + (JsVariant::Integer32(x), JsVariant::Integer32(y)) => x == y, //Null has to be handled specially because "typeof null" returns object and if we managed //this without a special case we would compare self and other as if they were actually //objects which unfortunately fails //Specification Link: https://tc39.es/ecma262/#sec-typeof-operator - (InnerValue::Null, InnerValue::Null) => true, + (JsVariant::Null, JsVariant::Null) => true, // 3. Return ! SameValueNonNumeric(x, y). (_, _) => Self::same_value_non_numeric(self, other), @@ -45,21 +45,22 @@ impl JsValue { return Ok(self.strict_equals(other)); } - Ok(match (&self.inner, &other.inner) { + Ok(match (self.variant(), other.variant()) { // 2. If x is null and y is undefined, return true. // 3. If x is undefined and y is null, return true. - (InnerValue::Null, InnerValue::Undefined) - | (InnerValue::Undefined, InnerValue::Null) => true, + (JsVariant::Null, JsVariant::Undefined) | (JsVariant::Undefined, JsVariant::Null) => { + true + } // 3. If Type(x) is Number and Type(y) is String, return the result of the comparison x == ! ToNumber(y). // 4. If Type(x) is String and Type(y) is Number, return the result of the comparison ! ToNumber(x) == y. // // https://github.com/rust-lang/rust/issues/54883 ( - InnerValue::Integer32(_) | InnerValue::Float64(_), - InnerValue::String(_) | InnerValue::Boolean(_), + JsVariant::Integer32(_) | JsVariant::Float64(_), + JsVariant::String(_) | JsVariant::Boolean(_), ) - | (InnerValue::String(_), InnerValue::Integer32(_) | InnerValue::Float64(_)) => { + | (JsVariant::String(_), JsVariant::Integer32(_) | JsVariant::Float64(_)) => { let x = self.to_number(context)?; let y = other.to_number(context)?; Number::equal(x, y) @@ -69,34 +70,32 @@ impl JsValue { // a. Let n be ! StringToBigInt(y). // b. If n is NaN, return false. // c. Return the result of the comparison x == n. - (InnerValue::BigInt(ref a), InnerValue::String(ref b)) => JsBigInt::from_js_string(b) + (JsVariant::BigInt(a), JsVariant::String(b)) => JsBigInt::from_js_string(b) .as_ref() .map_or(false, |b| a == b), // 7. If Type(x) is String and Type(y) is BigInt, return the result of the comparison y == x. - (InnerValue::String(ref a), InnerValue::BigInt(ref b)) => JsBigInt::from_js_string(a) + (JsVariant::String(a), JsVariant::BigInt(b)) => JsBigInt::from_js_string(a) .as_ref() .map_or(false, |a| a == b), // 8. If Type(x) is Boolean, return the result of the comparison ! ToNumber(x) == y. - (InnerValue::Boolean(x), _) => { - return other.equals(&JsValue::new(i32::from(*x)), context) + (JsVariant::Boolean(x), _) => { + return other.equals(&JsValue::new(i32::from(x)), context) } // 9. If Type(y) is Boolean, return the result of the comparison x == ! ToNumber(y). - (_, InnerValue::Boolean(y)) => { - return self.equals(&JsValue::new(i32::from(*y)), context) - } + (_, JsVariant::Boolean(y)) => return self.equals(&JsValue::new(i32::from(y)), context), // 10. If Type(x) is either String, Number, BigInt, or Symbol and Type(y) is Object, return the result // of the comparison x == ? ToPrimitive(y). ( - InnerValue::Object(_), - InnerValue::String(_) - | InnerValue::Float64(_) - | InnerValue::Integer32(_) - | InnerValue::BigInt(_) - | InnerValue::Symbol(_), + JsVariant::Object(_), + JsVariant::String(_) + | JsVariant::Float64(_) + | JsVariant::Integer32(_) + | JsVariant::BigInt(_) + | JsVariant::Symbol(_), ) => { let primitive = self.to_primitive(context, PreferredType::Default)?; return Ok(primitive @@ -107,12 +106,12 @@ impl JsValue { // 11. If Type(x) is Object and Type(y) is either String, Number, BigInt, or Symbol, return the result // of the comparison ? ToPrimitive(x) == y. ( - InnerValue::String(_) - | InnerValue::Float64(_) - | InnerValue::Integer32(_) - | InnerValue::BigInt(_) - | InnerValue::Symbol(_), - InnerValue::Object(_), + JsVariant::String(_) + | JsVariant::Float64(_) + | JsVariant::Integer32(_) + | JsVariant::BigInt(_) + | JsVariant::Symbol(_), + JsVariant::Object(_), ) => { let primitive = other.to_primitive(context, PreferredType::Default)?; return Ok(primitive @@ -123,10 +122,10 @@ impl JsValue { // 12. If Type(x) is BigInt and Type(y) is Number, or if Type(x) is Number and Type(y) is BigInt, then // a. If x or y are any of NaN, +∞, or -∞, return false. // b. If the mathematical value of x is equal to the mathematical value of y, return true; otherwise return false. - (InnerValue::BigInt(ref a), InnerValue::Float64(ref b)) => a == b, - (InnerValue::Float64(ref a), InnerValue::BigInt(ref b)) => a == b, - (InnerValue::BigInt(ref a), InnerValue::Integer32(ref b)) => a == b, - (InnerValue::Integer32(ref a), InnerValue::BigInt(ref b)) => a == b, + (JsVariant::BigInt(a), JsVariant::Float64(ref b)) => a == b, + (JsVariant::Float64(ref a), JsVariant::BigInt(b)) => a == b, + (JsVariant::BigInt(a), JsVariant::Integer32(ref b)) => a == b, + (JsVariant::Integer32(ref a), JsVariant::BigInt(b)) => a == b, // 13. Return false. _ => false, @@ -147,18 +146,14 @@ impl JsValue { return false; } - match (&x.inner, &y.inner) { + match (x.variant(), y.variant()) { // 2. If Type(x) is Number or BigInt, then // a. Return ! Type(x)::SameValue(x, y). - (InnerValue::BigInt(x), InnerValue::BigInt(y)) => JsBigInt::same_value(x, y), - (InnerValue::Float64(x), InnerValue::Float64(y)) => Number::same_value(*x, *y), - (InnerValue::Float64(x), InnerValue::Integer32(y)) => { - Number::same_value(*x, f64::from(*y)) - } - (InnerValue::Integer32(x), InnerValue::Float64(y)) => { - Number::same_value(f64::from(*x), *y) - } - (InnerValue::Integer32(x), InnerValue::Integer32(y)) => x == y, + (JsVariant::BigInt(x), JsVariant::BigInt(y)) => JsBigInt::same_value(x, y), + (JsVariant::Float64(x), JsVariant::Float64(y)) => Number::same_value(x, y), + (JsVariant::Float64(x), JsVariant::Integer32(y)) => Number::same_value(x, f64::from(y)), + (JsVariant::Integer32(x), JsVariant::Float64(y)) => Number::same_value(f64::from(x), y), + (JsVariant::Integer32(x), JsVariant::Integer32(y)) => x == y, // 3. Return ! SameValueNonNumeric(x, y). (_, _) => Self::same_value_non_numeric(x, y), @@ -180,19 +175,19 @@ impl JsValue { return false; } - match (&x.inner, &y.inner) { + match (x.variant(), y.variant()) { // 2. If Type(x) is Number or BigInt, then // a. Return ! Type(x)::SameValueZero(x, y). - (InnerValue::BigInt(x), InnerValue::BigInt(y)) => JsBigInt::same_value_zero(x, y), + (JsVariant::BigInt(x), JsVariant::BigInt(y)) => JsBigInt::same_value_zero(x, y), - (InnerValue::Float64(x), InnerValue::Float64(y)) => Number::same_value_zero(*x, *y), - (InnerValue::Float64(x), InnerValue::Integer32(y)) => { - Number::same_value_zero(*x, f64::from(*y)) + (JsVariant::Float64(x), JsVariant::Float64(y)) => Number::same_value_zero(x, y), + (JsVariant::Float64(x), JsVariant::Integer32(y)) => { + Number::same_value_zero(x, f64::from(y)) } - (InnerValue::Integer32(x), InnerValue::Float64(y)) => { - Number::same_value_zero(f64::from(*x), *y) + (JsVariant::Integer32(x), JsVariant::Float64(y)) => { + Number::same_value_zero(f64::from(x), y) } - (InnerValue::Integer32(x), InnerValue::Integer32(y)) => x == y, + (JsVariant::Integer32(x), JsVariant::Integer32(y)) => x == y, // 3. Return ! SameValueNonNumeric(x, y). (_, _) => Self::same_value_non_numeric(x, y), @@ -201,13 +196,14 @@ impl JsValue { fn same_value_non_numeric(x: &Self, y: &Self) -> bool { debug_assert!(x.get_type() == y.get_type()); - match (&x.inner, &y.inner) { - (InnerValue::Null, InnerValue::Null) - | (InnerValue::Undefined, InnerValue::Undefined) => true, - (InnerValue::String(x), InnerValue::String(y)) => x == y, - (InnerValue::Boolean(x), InnerValue::Boolean(y)) => x == y, - (InnerValue::Object(x), InnerValue::Object(y)) => JsObject::equals(x, y), - (InnerValue::Symbol(x), InnerValue::Symbol(y)) => x == y, + match (x.variant(), y.variant()) { + (JsVariant::Null, JsVariant::Null) | (JsVariant::Undefined, JsVariant::Undefined) => { + true + } + (JsVariant::String(x), JsVariant::String(y)) => x == y, + (JsVariant::Boolean(x), JsVariant::Boolean(y)) => x == y, + (JsVariant::Object(x), JsVariant::Object(y)) => JsObject::equals(x, y), + (JsVariant::Symbol(x), JsVariant::Symbol(y)) => x == y, _ => false, } } diff --git a/core/engine/src/value/hash.rs b/core/engine/src/value/hash.rs index 3becb2fcdab..0efac634fdc 100644 --- a/core/engine/src/value/hash.rs +++ b/core/engine/src/value/hash.rs @@ -1,5 +1,6 @@ -use super::{InnerValue, JsValue}; +use super::JsValue; use crate::builtins::Number; +use crate::JsVariant; use std::hash::{Hash, Hasher}; impl PartialEq for JsValue { @@ -36,16 +37,16 @@ impl Hash for RationalHashable { impl Hash for JsValue { fn hash(&self, state: &mut H) { - match self.inner { - InnerValue::Undefined => UndefinedHashable.hash(state), - InnerValue::Null => NullHashable.hash(state), - InnerValue::String(ref string) => string.hash(state), - InnerValue::Boolean(boolean) => boolean.hash(state), - InnerValue::Integer32(integer) => RationalHashable(f64::from(integer)).hash(state), - InnerValue::BigInt(ref bigint) => bigint.hash(state), - InnerValue::Float64(rational) => RationalHashable(rational).hash(state), - InnerValue::Symbol(ref symbol) => Hash::hash(symbol, state), - InnerValue::Object(ref object) => object.hash(state), + match self.variant() { + JsVariant::Undefined => UndefinedHashable.hash(state), + JsVariant::Null => NullHashable.hash(state), + JsVariant::String(string) => string.hash(state), + JsVariant::Boolean(boolean) => boolean.hash(state), + JsVariant::Integer32(integer) => RationalHashable(f64::from(integer)).hash(state), + JsVariant::BigInt(bigint) => bigint.hash(state), + JsVariant::Float64(rational) => RationalHashable(rational).hash(state), + JsVariant::Symbol(symbol) => Hash::hash(symbol, state), + JsVariant::Object(object) => object.hash(state), } } } diff --git a/core/engine/src/value/inner.rs b/core/engine/src/value/inner.rs index e5724170ff1..10ebeb03905 100644 --- a/core/engine/src/value/inner.rs +++ b/core/engine/src/value/inner.rs @@ -36,9 +36,10 @@ //! This only works on 4-bits aligned values, which is asserted when the //! `InnerValue` is created. -use crate::{JsBigInt, JsObject, JsSymbol}; +use crate::{JsBigInt, JsObject, JsSymbol, JsVariant}; +use boa_gc::{custom_trace, Finalize, Trace}; use boa_string::JsString; -use num_traits::ToBytes; +use core::fmt; use static_assertions::const_assert; // We cannot NaN-box pointers larger than 64 bits. @@ -52,7 +53,9 @@ const_assert!(align_of::<*mut ()>() >= 4); /// /// This is a utility type that allows to create NaN-boxed values, and to check /// the type of a NaN-boxed value. -#[derive(Copy, Clone)] +/// +/// All the bit masking, tagging and untagging is done in this type. +#[derive(Copy, Clone, Debug)] #[repr(u64)] enum NanBitTag { Undefined = 0x7FF4_0000_0000_0000, @@ -60,15 +63,17 @@ enum NanBitTag { False = 0x7FF6_0000_0000_0000, True = 0x7FF6_0000_0000_0001, Integer32 = 0x7FF7_0000_0000_0000, - BigInt = 0x7FF8_0000_0000_0000, - Object = 0x7FF8_0000_0000_0001, - Symbol = 0x7FF8_0000_0000_0002, - String = 0x7FF8_0000_0000_0003, + + /// A generic pointer. + Pointer = 0x7FF8_0000_0000_0000, + BigInt = 0x0000_0000_0000_0000, + Object = 0x0000_0000_0000_0001, + Symbol = 0x0000_0000_0000_0002, + String = 0x0000_0000_0000_0003, // Masks - TaggedMask = 0x7FFF_0000_0000_0000, + TaggedMask = 0x7FFC_0000_0000_0000, PointerMask = 0x0007_FFFF_FFFF_FFFC, - PointerTypeMask = 0x0000_0000_0000_0003, } // Verify that all representations of NanBitTag ARE NAN, but don't match static NAN. @@ -77,12 +82,6 @@ enum NanBitTag { const_assert!(f64::from_bits(NanBitTag::Undefined as u64).is_nan()); impl NanBitTag { - /// Checks if the value is a specific tagged value. - #[inline] - const fn is(self, value: u64) -> bool { - (value & self as u64) == self as u64 - } - /// Checks that a value is a valid boolean (either true or false). #[inline] const fn is_bool(value: u64) -> bool { @@ -93,29 +92,82 @@ impl NanBitTag { /// Checks that a value is a valid float, not a tagged nan boxed value. #[inline] const fn is_float(value: u64) -> bool { - (value & NanBitTag::TaggedMask as u64) != NanBitTag::TaggedMask as u64 - } + // Either it is a constant float value, + if value == f64::INFINITY.to_bits() + || value == f64::NEG_INFINITY.to_bits() + // or it is exactly a NaN value, which is the same as the BigInt tag. + // Reminder that pointers cannot be null, so this is safe. + || value == NanBitTag::Pointer as u64 + { + return true; + } - /// Return the tag of this value. - #[inline] - const fn tag_of(value: u64) -> Option { + // Or it is not tagged, match value & NanBitTag::TaggedMask as u64 { - 0x7FF4_0000_0000_0000 => Some(NanBitTag::Undefined), - 0x7FF5_0000_0000_0000 => Some(NanBitTag::Null), - 0x7FF6_0000_0000_0000 => Some(NanBitTag::False), - 0x7FF6_0000_0000_0001 => Some(NanBitTag::True), - 0x7FF7_0000_0000_0000 => Some(NanBitTag::Integer32), - // Verify this is not a NULL pointer. - 0x7FF8_0000_0000_0000 if (value & NanBitTag::PointerMask as u64) != 0 => { - Some(NanBitTag::BigInt) - } - 0x7FF8_0000_0000_0001 => Some(NanBitTag::Object), - 0x7FF8_0000_0000_0002 => Some(NanBitTag::Symbol), - 0x7FF8_0000_0000_0003 => Some(NanBitTag::String), - _ => None, + 0x7FF4_0000_0000_0000 => false, + 0x7FF5_0000_0000_0000 => false, + 0x7FF6_0000_0000_0000 => false, + 0x7FF6_0000_0000_0001 => false, + 0x7FF7_0000_0000_0000 => false, + 0x7FF8_0000_0000_0000 => false, + 0x7FF9_0000_0000_0000 => false, + 0x7FFA_0000_0000_0000 => false, + 0x7FFB_0000_0000_0000 => false, + 0x7FFC_0000_0000_0000 => false, + 0x7FFD_0000_0000_0000 => false, + 0x7FFE_0000_0000_0000 => false, + 0x7FFF_0000_0000_0000 => false, + _ => true, } } + /// Checks that a value is a valid undefined. + #[inline] + const fn is_undefined(value: u64) -> bool { + value == NanBitTag::Undefined as u64 + } + + /// Checks that a value is a valid null. + #[inline] + const fn is_null(value: u64) -> bool { + value == NanBitTag::Null as u64 + } + + /// Checks that a value is a valid integer32. + #[inline] + const fn is_integer32(value: u64) -> bool { + value & NanBitTag::Integer32 as u64 == NanBitTag::Integer32 as u64 + } + + /// Checks that a value is a valid BigInt. + #[inline] + const fn is_bigint(value: u64) -> bool { + (value & NanBitTag::TaggedMask as u64 == NanBitTag::Pointer as u64) + && (value & 0x3 == Self::BigInt as u64) + && (value & NanBitTag::PointerMask as u64) != 0 + } + + /// Checks that a value is a valid Object. + #[inline] + const fn is_object(value: u64) -> bool { + (value & NanBitTag::TaggedMask as u64 == NanBitTag::Pointer as u64) + && (value & 0x3 == Self::Object as u64) + } + + /// Checks that a value is a valid Symbol. + #[inline] + const fn is_symbol(value: u64) -> bool { + (value & NanBitTag::TaggedMask as u64 == NanBitTag::Pointer as u64) + && (value & 0x3 == Self::Symbol as u64) + } + + /// Checks that a value is a valid String. + #[inline] + const fn is_string(value: u64) -> bool { + (value & NanBitTag::TaggedMask as u64 == NanBitTag::Pointer as u64) + && (value & 0x3 == Self::String as u64) + } + /// Returns a tagged u64 of a 64-bits float. #[inline] const fn tag_f64(value: f64) -> u64 { @@ -130,25 +182,14 @@ impl NanBitTag { /// Returns a tagged u64 of a 32-bits integer. #[inline] const fn tag_i32(value: i32) -> u64 { - // Get the 32-bits integer value inside an u64 as is, in native endian. - let mut tagged = (Self::Integer32 as u64).to_ne_bytes(); - let bytes = value.to_ne_bytes(); - - tagged[4] = bytes[0]; - tagged[5] = bytes[1]; - tagged[6] = bytes[2]; - tagged[7] = bytes[3]; - - u64::from_ne_bytes(tagged) + Self::Integer32 as u64 | value as u64 & 0xFFFF_FFFFu64 } /// Returns a i32-bits from a tagged integer. #[inline] const fn untag_i32(value: u64) -> Option { - if value & NanBitTag::Integer32 as u64 == NanBitTag::Integer32 as u64 { - // Get the 32-bits integer value inside an u64 as is. - let bytes = value.to_ne_bytes(); - Some(i32::from_ne_bytes([bytes[4], bytes[5], bytes[6], bytes[7]])) + if Self::is_integer32(value) { + Some(((value & 0xFFFF_FFFFu64) | 0xFFFF_FFFF_0000_0000u64) as i32) } else { None } @@ -186,7 +227,7 @@ impl NanBitTag { assert_ne!(value & Self::PointerMask as u64, 0, "Pointer is NULL."); // Simply cast for bits. - Self::BigInt as u64 | value + Self::Pointer as u64 | Self::BigInt as u64 | value } /// Returns a tagged u64 of a boxed `[JsObject]`. @@ -209,7 +250,7 @@ impl NanBitTag { ); // Simply cast for bits. - Self::Object as u64 | value + Self::Pointer as u64 | Self::Object as u64 | value } /// Returns a tagged u64 of a boxed `[JsSymbol]`. @@ -232,7 +273,7 @@ impl NanBitTag { ); // Simply cast for bits. - Self::Symbol as u64 | value + Self::Pointer as u64 | Self::Symbol as u64 | value } /// Returns a tagged u64 of a boxed `[JsString]`. @@ -255,7 +296,7 @@ impl NanBitTag { ); // Simply cast for bits. - Self::String as u64 | value + Self::Pointer as u64 | Self::String as u64 | value } /// Drops a value if it is a pointer, otherwise do nothing. @@ -263,27 +304,61 @@ impl NanBitTag { unsafe fn drop_pointer(value: u64) { let value_ptr = value & Self::PointerMask as u64; - match Self::tag_of(value) { - Some(Self::BigInt) => { - drop(unsafe { Box::from_raw(value_ptr as *mut JsBigInt) }); - } - Some(Self::Object) => { - drop(unsafe { Box::from_raw(value_ptr as *mut JsObject) }); - } - Some(Self::Symbol) => { - drop(unsafe { Box::from_raw(value_ptr as *mut JsSymbol) }); - } - Some(Self::String) => { - drop(unsafe { Box::from_raw(value_ptr as *mut JsString) }); - } - _ => {} + if value & NanBitTag::Pointer as u64 != 0 || value == NanBitTag::Pointer as u64 { + return; + } + + match value & 0x3 { + 0 => drop(unsafe { Box::from_raw(value_ptr as *mut JsBigInt) }), + 1 => drop(unsafe { Box::from_raw(value_ptr as *mut JsObject) }), + 2 => drop(unsafe { Box::from_raw(value_ptr as *mut JsSymbol) }), + 3 => drop(unsafe { Box::from_raw(value_ptr as *mut JsString) }), + _ => unreachable!(), } } } /// A NaN-boxed `[super::JsValue]`'s inner. -pub(super) struct InnerValue { - inner: u64, +#[derive(PartialEq)] +pub(super) struct InnerValue(u64); + +impl fmt::Debug for InnerValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.as_variant() { + JsVariant::Undefined => f.debug_tuple("Undefined").finish(), + JsVariant::Null => f.debug_tuple("Null").finish(), + JsVariant::Boolean(b) => f.debug_tuple("Boolean").field(&b).finish(), + JsVariant::Float64(n) => f.debug_tuple("Float64").field(&n).finish(), + JsVariant::Integer32(n) => f.debug_tuple("Integer32").field(&n).finish(), + JsVariant::BigInt(n) => f.debug_tuple("BigInt").field(&n).finish(), + JsVariant::Object(n) => f.debug_tuple("Object").field(&n).finish(), + JsVariant::Symbol(n) => f.debug_tuple("Symbol").field(&n).finish(), + JsVariant::String(n) => f.debug_tuple("String").field(&n).finish(), + } + } +} + +impl Finalize for InnerValue {} + +#[allow(unsafe_op_in_unsafe_fn)] +unsafe impl Trace for InnerValue { + custom_trace! {this, mark, { + if let JsVariant::Object(o) = this.as_variant() { + mark(o); + } + }} +} + +impl Clone for InnerValue { + fn clone(&self) -> Self { + match self.as_variant() { + JsVariant::BigInt(n) => Self::bigint(n.clone()), + JsVariant::Object(n) => Self::object(n.clone()), + JsVariant::Symbol(n) => Self::symbol(n.clone()), + JsVariant::String(n) => Self::string(n.clone()), + _ => Self(self.0), + } + } } impl InnerValue { @@ -291,21 +366,21 @@ impl InnerValue { /// of the value. #[must_use] #[inline] - fn from_inner_unchecked(inner: u64) -> Self { - Self { inner } + const fn from_inner_unchecked(inner: u64) -> Self { + Self(inner) } /// Returns a `InnerValue` from a Null. #[must_use] #[inline] - pub(super) fn null() -> Self { + pub(super) const fn null() -> Self { Self::from_inner_unchecked(NanBitTag::Null as u64) } /// Returns a `InnerValue` from an undefined. #[must_use] #[inline] - pub(super) fn undefined() -> Self { + pub(super) const fn undefined() -> Self { Self::from_inner_unchecked(NanBitTag::Undefined as u64) } @@ -313,21 +388,21 @@ impl InnerValue { /// it will be reduced to a canonical `NaN` representation. #[must_use] #[inline] - pub(super) fn float64(value: f64) -> Self { + pub(super) const fn float64(value: f64) -> Self { Self::from_inner_unchecked(NanBitTag::tag_f64(value)) } /// Returns a `InnerValue` from a 32-bits integer. #[must_use] #[inline] - pub(super) fn integer32(value: i32) -> Self { + pub(super) const fn integer32(value: i32) -> Self { Self::from_inner_unchecked(NanBitTag::tag_i32(value)) } /// Returns a `InnerValue` from a boolean. #[must_use] #[inline] - pub(super) fn boolean(value: bool) -> Self { + pub(super) const fn boolean(value: bool) -> Self { Self::from_inner_unchecked(NanBitTag::tag_bool(value)) } @@ -362,72 +437,72 @@ impl InnerValue { /// Returns true if a value is undefined. #[must_use] #[inline] - pub(super) fn is_undefined(&self) -> bool { - NanBitTag::Undefined.is(self.inner) + pub(super) const fn is_undefined(&self) -> bool { + NanBitTag::is_undefined(self.0) } /// Returns true if a value is null. #[must_use] #[inline] - pub(super) fn is_null(&self) -> bool { - NanBitTag::Null.is(self.inner) + pub(super) const fn is_null(&self) -> bool { + NanBitTag::is_null(self.0) } /// Returns true if a value is a boolean. #[must_use] #[inline] - pub(super) fn is_bool(&self) -> bool { - NanBitTag::is_bool(self.inner) + pub(super) const fn is_bool(&self) -> bool { + NanBitTag::is_bool(self.0) } /// Returns true if a value is a 64-bits float. #[must_use] #[inline] - pub(super) fn is_float64(&self) -> bool { - NanBitTag::is_float(self.inner) + pub(super) const fn is_float64(&self) -> bool { + NanBitTag::is_float(self.0) } /// Returns true if a value is a 32-bits integer. #[must_use] #[inline] - pub(super) fn is_integer32(&self) -> bool { - NanBitTag::Integer32.is(self.inner) + pub(super) const fn is_integer32(&self) -> bool { + NanBitTag::is_integer32(self.0) } /// Returns true if a value is a `[JsBigInt]`. A `NaN` will not match here. #[must_use] #[inline] - pub(super) fn is_bigint(&self) -> bool { - NanBitTag::BigInt.is(self.inner) && (self.inner & NanBitTag::PointerMask as u64) != 0 + pub(super) const fn is_bigint(&self) -> bool { + NanBitTag::is_bigint(self.0) } /// Returns true if a value is a boxed Object. #[must_use] #[inline] - pub(super) fn is_object(&self) -> bool { - NanBitTag::Object.is(self.inner) + pub(super) const fn is_object(&self) -> bool { + NanBitTag::is_object(self.0) } /// Returns true if a value is a boxed Symbol. #[must_use] #[inline] - pub(super) fn is_symbol(&self) -> bool { - NanBitTag::Symbol.is(self.inner) + pub(super) const fn is_symbol(&self) -> bool { + NanBitTag::is_symbol(self.0) } /// Returns true if a value is a boxed String. #[must_use] #[inline] - pub(super) fn is_string(&self) -> bool { - NanBitTag::String.is(self.inner) + pub(super) const fn is_string(&self) -> bool { + NanBitTag::is_string(self.0) } /// Returns the value as an f64 if it is a float. #[must_use] #[inline] - pub(super) fn as_float64(&self) -> Option { + pub(super) const fn as_float64(&self) -> Option { if self.is_float64() { - Some(f64::from_bits(self.inner)) + Some(f64::from_bits(self.0)) } else { None } @@ -436,20 +511,376 @@ impl InnerValue { /// Returns the value as an i32 if it is an integer. #[must_use] #[inline] - pub(super) fn as_integer32(&self) -> Option { - if self.is_integer32() { - Some(self.inner as i32) + pub(super) const fn as_integer32(&self) -> Option { + NanBitTag::untag_i32(self.0) + } + + /// Returns the value as a boolean if it is a boolean. + #[must_use] + #[inline] + pub(super) const fn as_bool(&self) -> Option { + if self.0 == NanBitTag::False as u64 { + Some(false) + } else if self.0 == NanBitTag::True as u64 { + Some(true) } else { None } } + + /// Returns the value as a boxed `[JsBigInt]`. + #[must_use] + #[inline] + pub(super) const fn as_bigint(&self) -> Option<&JsBigInt> { + if self.is_bigint() { + // This is safe because the boxed object will always be on the heap. + let ptr = self.0 & NanBitTag::PointerMask as u64; + unsafe { Some(&*(ptr as *const _)) } + } else { + None + } + } + + /// Returns the value as a boxed `[JsObject]`. + #[must_use] + #[inline] + pub(super) const fn as_object(&self) -> Option<&JsObject> { + if self.is_object() { + // This is safe because the boxed object will always be on the heap. + let ptr = self.0 & NanBitTag::PointerMask as u64; + unsafe { Some(&*(ptr as *const _)) } + } else { + None + } + } + + /// Returns the value as a boxed `[JsSymbol]`. + #[must_use] + #[inline] + pub(super) const fn as_symbol(&self) -> Option<&JsSymbol> { + if self.is_symbol() { + // This is safe because the boxed object will always be on the heap. + let ptr = self.0 & NanBitTag::PointerMask as u64; + unsafe { Some(&*(ptr as *const _)) } + } else { + None + } + } + + /// Returns the value as a boxed `[JsString]`. + #[must_use] + #[inline] + pub(super) const fn as_string(&self) -> Option<&JsString> { + if self.is_string() { + // This is safe because the boxed object will always be on the heap. + let ptr = self.0 & NanBitTag::PointerMask as u64; + unsafe { Some(&*(ptr as *const _)) } + } else { + None + } + } + + /// Returns the `[JsVariant]` of this inner value. + #[must_use] + #[inline] + pub(super) const fn as_variant(&self) -> JsVariant<'_> { + if self.is_undefined() { + JsVariant::Undefined + } else if self.is_null() { + JsVariant::Null + } else if let Some(b) = self.as_bool() { + JsVariant::Boolean(b) + } else if let Some(f) = self.as_float64() { + JsVariant::Float64(f) + } else if let Some(i) = self.as_integer32() { + JsVariant::Integer32(i) + } else if let Some(bigint) = self.as_bigint() { + JsVariant::BigInt(bigint) + } else if let Some(obj) = self.as_object() { + JsVariant::Object(obj) + } else if let Some(sym) = self.as_symbol() { + JsVariant::Symbol(sym) + } else if let Some(str) = self.as_string() { + JsVariant::String(str) + } else { + unreachable!() + } + } } impl Drop for InnerValue { fn drop(&mut self) { // Drop the pointer if it is a pointer. unsafe { - NanBitTag::drop_pointer(self.inner); + NanBitTag::drop_pointer(self.0); } } } + +#[test] +fn float() { + fn assert_float(f: f64) { + let v = InnerValue::float64(f); + + assert!(!v.is_undefined()); + assert!(!v.is_null()); + assert!(!v.is_bool()); + assert!(!v.is_integer32()); + assert!(v.is_float64()); + assert!(!v.is_bigint()); + assert!(!v.is_string()); + assert!(!v.is_object()); + assert!(!v.is_symbol()); + + assert_eq!(v.as_bool(), None); + assert_eq!(v.as_integer32(), None); + assert_eq!(v.as_float64(), Some(f)); + assert_eq!(v.as_bigint(), None); + assert_eq!(v.as_object(), None); + assert_eq!(v.as_string(), None); + assert_eq!(v.as_symbol(), None); + } + + assert_float(0.0); + assert_float(-0.0); + assert_float(3.14); + assert_float(-3.14); + assert_float(f64::INFINITY); + assert_float(f64::NEG_INFINITY); + + // Special care has to be taken for NaN, because NaN != NaN. + let v = InnerValue::float64(f64::NAN); + assert!(!v.is_undefined()); + assert!(!v.is_null()); + assert!(!v.is_bool()); + assert!(!v.is_integer32()); + assert!(v.is_float64()); + assert!(!v.is_bigint()); + assert!(!v.is_string()); + assert!(!v.is_object()); + assert!(!v.is_symbol()); + + assert_eq!(v.as_bool(), None); + assert_eq!(v.as_integer32(), None); + assert!(v.as_float64().unwrap().is_nan()); + assert_eq!(v.as_bigint(), None); + assert_eq!(v.as_object(), None); + assert_eq!(v.as_string(), None); + assert_eq!(v.as_symbol(), None); +} + +#[test] +fn integer() { + let int = 42; + let v = InnerValue::integer32(int); + assert!(!v.is_undefined()); + assert!(!v.is_null()); + assert!(!v.is_bool()); + assert!(v.is_integer32()); + assert!(!v.is_float64()); + assert!(!v.is_bigint()); + assert!(!v.is_string()); + assert!(!v.is_object()); + assert!(!v.is_symbol()); + + assert_eq!(v.as_bool(), None); + assert_eq!(v.as_integer32(), Some(int)); + assert_eq!(v.as_float64(), None); + assert_eq!(v.as_bigint(), None); + assert_eq!(v.as_object(), None); + assert_eq!(v.as_string(), None); + assert_eq!(v.as_symbol(), None); + + let int = -42; + let v = InnerValue::integer32(int); + assert!(!v.is_undefined()); + assert!(!v.is_null()); + assert!(!v.is_bool()); + assert!(v.is_integer32()); + assert!(!v.is_float64()); + assert!(!v.is_bigint()); + assert!(!v.is_string()); + assert!(!v.is_object()); + assert!(!v.is_symbol()); + + assert_eq!(v.as_bool(), None); + assert_eq!(v.as_integer32(), Some(int)); + assert_eq!(v.as_float64(), None); + assert_eq!(v.as_bigint(), None); + assert_eq!(v.as_object(), None); + assert_eq!(v.as_string(), None); + assert_eq!(v.as_symbol(), None); + + let int = 0; + let v = InnerValue::integer32(int); + assert!(!v.is_undefined()); + assert!(!v.is_null()); + assert!(!v.is_bool()); + assert!(v.is_integer32()); + assert!(!v.is_float64()); + assert!(!v.is_bigint()); + assert!(!v.is_string()); + assert!(!v.is_object()); + assert!(!v.is_symbol()); + + assert_eq!(v.as_bool(), None); + assert_eq!(v.as_integer32(), Some(int)); + assert_eq!(v.as_float64(), None); + assert_eq!(v.as_bigint(), None); + assert_eq!(v.as_object(), None); + assert_eq!(v.as_string(), None); + assert_eq!(v.as_symbol(), None); +} + +#[test] +fn boolean() { + let v = InnerValue::boolean(true); + assert!(!v.is_undefined()); + assert!(!v.is_null()); + assert!(v.is_bool()); + assert!(!v.is_integer32()); + assert!(!v.is_float64()); + assert!(!v.is_bigint()); + assert!(!v.is_string()); + assert!(!v.is_object()); + assert!(!v.is_symbol()); + + assert_eq!(v.as_bool(), Some(true)); + assert_eq!(v.as_integer32(), None); + assert_eq!(v.as_float64(), None); + assert_eq!(v.as_bigint(), None); + assert_eq!(v.as_object(), None); + assert_eq!(v.as_string(), None); + assert_eq!(v.as_symbol(), None); + + let v = InnerValue::boolean(false); + assert!(!v.is_undefined()); + assert!(!v.is_null()); + assert!(v.is_bool()); + assert!(!v.is_integer32()); + assert!(!v.is_float64()); + assert!(!v.is_bigint()); + assert!(!v.is_string()); + assert!(!v.is_object()); + assert!(!v.is_symbol()); + + assert_eq!(v.as_bool(), Some(false)); + assert_eq!(v.as_integer32(), None); + assert_eq!(v.as_float64(), None); + assert_eq!(v.as_bigint(), None); + assert_eq!(v.as_object(), None); + assert_eq!(v.as_string(), None); + assert_eq!(v.as_symbol(), None); +} + +#[test] +fn bigint() { + let bigint = JsBigInt::from(42); + let v = InnerValue::bigint(bigint.clone()); + assert!(!v.is_undefined()); + assert!(!v.is_null()); + assert!(!v.is_bool()); + assert!(!v.is_integer32()); + assert!(!v.is_float64()); + assert!(v.is_bigint()); + assert!(!v.is_string()); + assert!(!v.is_object()); + assert!(!v.is_symbol()); + + assert_eq!(v.as_bool(), None); + assert_eq!(v.as_integer32(), None); + assert_eq!(v.as_float64(), None); + assert_eq!(v.as_bigint(), Some(&bigint)); + assert_eq!(v.as_object(), None); + assert_eq!(v.as_string(), None); + assert_eq!(v.as_symbol(), None); +} + +#[test] +fn object() { + let object = JsObject::with_null_proto(); + let v = InnerValue::object(object.clone()); + assert!(!v.is_undefined()); + assert!(!v.is_null()); + assert!(!v.is_bool()); + assert!(!v.is_integer32()); + assert!(!v.is_float64()); + assert!(!v.is_bigint()); + assert!(!v.is_string()); + assert!(v.is_object()); + assert!(!v.is_symbol()); + + assert_eq!(v.as_bool(), None); + assert_eq!(v.as_integer32(), None); + assert_eq!(v.as_float64(), None); + assert_eq!(v.as_bigint(), None); + assert_eq!(v.as_object(), Some(&object)); + assert_eq!(v.as_string(), None); + assert_eq!(v.as_symbol(), None); +} + +#[test] +fn string() { + let str = crate::js_string!("Hello World"); + let v = InnerValue::string(str.clone()); + assert!(!v.is_undefined()); + assert!(!v.is_null()); + assert!(!v.is_bool()); + assert!(!v.is_integer32()); + assert!(!v.is_float64()); + assert!(!v.is_bigint()); + assert!(v.is_string()); + assert!(!v.is_object()); + assert!(!v.is_symbol()); + + assert_eq!(v.as_bool(), None); + assert_eq!(v.as_integer32(), None); + assert_eq!(v.as_float64(), None); + assert_eq!(v.as_bigint(), None); + assert_eq!(v.as_object(), None); + assert_eq!(v.as_string(), Some(&str)); + assert_eq!(v.as_symbol(), None); +} + +#[test] +fn symbol() { + let sym = JsSymbol::new(Some(JsString::from("Hello World"))).unwrap(); + let v = InnerValue::symbol(sym.clone()); + assert!(!v.is_undefined()); + assert!(!v.is_null()); + assert!(!v.is_bool()); + assert!(!v.is_integer32()); + assert!(!v.is_float64()); + assert!(!v.is_bigint()); + assert!(!v.is_string()); + assert!(!v.is_object()); + assert!(v.is_symbol()); + + assert_eq!(v.as_bool(), None); + assert_eq!(v.as_integer32(), None); + assert_eq!(v.as_float64(), None); + assert_eq!(v.as_bigint(), None); + assert_eq!(v.as_object(), None); + assert_eq!(v.as_string(), None); + assert_eq!(v.as_symbol(), Some(&sym)); + + let sym = JsSymbol::new(None).unwrap(); + let v = InnerValue::symbol(sym.clone()); + assert!(!v.is_undefined()); + assert!(!v.is_null()); + assert!(!v.is_bool()); + assert!(!v.is_integer32()); + assert!(!v.is_float64()); + assert!(!v.is_bigint()); + assert!(!v.is_string()); + assert!(!v.is_object()); + assert!(v.is_symbol()); + + assert_eq!(v.as_bool(), None); + assert_eq!(v.as_integer32(), None); + assert_eq!(v.as_float64(), None); + assert_eq!(v.as_bigint(), None); + assert_eq!(v.as_object(), None); + assert_eq!(v.as_string(), None); + assert_eq!(v.as_symbol(), Some(&sym)); +} diff --git a/core/engine/src/value/mod.rs b/core/engine/src/value/mod.rs index bec7848bfd9..b4d5b7b1e26 100644 --- a/core/engine/src/value/mod.rs +++ b/core/engine/src/value/mod.rs @@ -13,7 +13,7 @@ use num_integer::Integer; use num_traits::{ToPrimitive, Zero}; use once_cell::sync::Lazy; -use boa_gc::{custom_trace, Finalize, Trace}; +use boa_gc::{Finalize, Trace}; #[doc(inline)] pub use boa_macros::TryFromJs; pub use boa_macros::TryIntoJs; @@ -66,32 +66,6 @@ static TWO_E_63: Lazy = Lazy::new(|| { BigInt::from(TWO_E_63) }); -/// The Inner type of [`JsValue`]. This is the actual value that the `JsValue` holds. -/// This is not a public API and should not be used directly. -/// -/// If you need access to the variant, use [`JsValue::variant`] instead. -#[derive(Finalize, Debug, Clone, PartialEq)] -enum InnerValue { - Null, - Undefined, - Boolean(bool), - Float64(f64), - Integer32(i32), - BigInt(JsBigInt), - String(JsString), - Symbol(JsSymbol), - Object(JsObject), -} - -#[allow(unsafe_op_in_unsafe_fn)] -unsafe impl Trace for InnerValue { - custom_trace! {this, mark, { - if let Self::Object(o) = this { - mark(o); - } - }} -} - /// A generic Javascript value. This can be any ECMAScript language valid value. /// /// This is a wrapper around the actual value, which is stored in an opaque type. @@ -104,14 +78,12 @@ unsafe impl Trace for InnerValue { /// assert_eq!(value.to_string(&mut context), Ok(js_string!("3"))); /// ``` #[derive(Finalize, Debug, Clone, Trace)] -pub struct JsValue { - inner: InnerValue, -} +pub struct JsValue(inner::InnerValue); impl JsValue { /// Create a new [`JsValue`] from an inner value. - const fn from_inner(inner: InnerValue) -> Self { - Self { inner } + const fn from_inner(inner: inner::InnerValue) -> Self { + Self(inner) } /// Create a new [`JsValue`]. @@ -125,79 +97,71 @@ impl JsValue { /// Return the variant of this value. #[inline] #[must_use] - pub fn variant(&self) -> JsVariant<'_> { - (&self.inner).into() + pub const fn variant(&self) -> JsVariant<'_> { + self.0.as_variant() } /// Creates a new `undefined` value. #[inline] #[must_use] pub const fn undefined() -> Self { - Self::from_inner(InnerValue::Undefined) + Self::from_inner(inner::InnerValue::undefined()) } /// Creates a new `null` value. #[inline] #[must_use] pub const fn null() -> Self { - Self::from_inner(InnerValue::Null) + Self::from_inner(inner::InnerValue::null()) } /// Creates a new number with `NaN` value. #[inline] #[must_use] pub const fn nan() -> Self { - Self::from_inner(InnerValue::Float64(f64::NAN)) + Self::from_inner(inner::InnerValue::float64(f64::NAN)) } /// Creates a new number with `Infinity` value. #[inline] #[must_use] pub const fn positive_infinity() -> Self { - Self::from_inner(InnerValue::Float64(f64::INFINITY)) + Self::from_inner(inner::InnerValue::float64(f64::INFINITY)) } /// Creates a new number with `-Infinity` value. #[inline] #[must_use] pub const fn negative_infinity() -> Self { - Self::from_inner(InnerValue::Float64(f64::NEG_INFINITY)) + Self::from_inner(inner::InnerValue::float64(f64::NEG_INFINITY)) } /// Creates a new number from a float. #[inline] #[must_use] pub const fn rational(rational: f64) -> Self { - Self::from_inner(InnerValue::Float64(rational)) + Self::from_inner(inner::InnerValue::float64(rational)) } /// Returns true if the value is an object. #[inline] #[must_use] pub const fn is_object(&self) -> bool { - matches!(self.inner, InnerValue::Object(_)) + self.0.is_object() } /// Returns the object if the value is object, otherwise `None`. #[inline] #[must_use] pub const fn as_object(&self) -> Option<&JsObject> { - if let InnerValue::Object(obj) = &self.inner { - Some(obj) - } else { - None - } + self.0.as_object() } /// Consumes the value and return the inner object if it was an object. #[inline] #[must_use] pub fn into_object(self) -> Option { - if let InnerValue::Object(ref obj) = self.inner { - Some(obj.clone()) - } else { - None - } + self.0.as_object().cloned() } /// It determines if the value is a callable function with a `[[Call]]` internal method. @@ -209,22 +173,14 @@ impl JsValue { #[inline] #[must_use] pub fn is_callable(&self) -> bool { - if let InnerValue::Object(obj) = &self.inner { - obj.is_callable() - } else { - false - } + self.as_object().map_or(false, |obj| obj.is_callable()) } /// Returns the callable value if the value is callable, otherwise `None`. #[inline] #[must_use] pub fn as_callable(&self) -> Option<&JsObject> { - if let InnerValue::Object(obj) = &self.inner { - obj.is_callable().then_some(obj) - } else { - None - } + self.as_object().filter(|obj| obj.is_callable()) } /// Returns a [`JsFunction`] if the value is callable, otherwise `None`. @@ -241,7 +197,7 @@ impl JsValue { #[inline] #[must_use] pub fn is_constructor(&self) -> bool { - matches!(&self.inner, InnerValue::Object(obj) if obj.is_constructor()) + self.as_object().map_or(false, |obj| obj.is_constructor()) } /// Returns the constructor if the value is a constructor, otherwise `None`. @@ -255,22 +211,14 @@ impl JsValue { #[inline] #[must_use] pub fn is_promise(&self) -> bool { - if let InnerValue::Object(obj) = &self.inner { - obj.is::() - } else { - false - } + self.as_object().map_or(false, |obj| obj.is::()) } /// Returns the value as an object if the value is a promise, otherwise `None`. #[inline] #[must_use] pub(crate) fn as_promise_object(&self) -> Option<&JsObject> { - if let InnerValue::Object(obj) = &self.inner { - obj.is::().then_some(obj) - } else { - None - } + self.as_object().filter(|obj| obj.is::()) } /// Returns the value as a promise if the value is a promise, otherwise `None`. @@ -286,11 +234,7 @@ impl JsValue { #[inline] #[must_use] pub fn is_regexp(&self) -> bool { - if let InnerValue::Object(obj) = &self.inner { - obj.is::() - } else { - false - } + self.as_object().map_or(false, |obj| obj.is::()) } /// Returns the value as a regular expression if the value is a regexp, otherwise `None`. @@ -307,32 +251,28 @@ impl JsValue { #[inline] #[must_use] pub const fn is_symbol(&self) -> bool { - matches!(self.inner, InnerValue::Symbol(_)) + self.0.is_symbol() } /// Returns the symbol if the value is a symbol, otherwise `None`. #[inline] #[must_use] - pub fn as_symbol(&self) -> Option { - if let InnerValue::Symbol(symbol) = &self.inner { - Some(symbol.clone()) - } else { - None - } + pub fn as_symbol(&self) -> Option<&JsSymbol> { + self.0.as_symbol() } /// Returns true if the value is undefined. #[inline] #[must_use] pub const fn is_undefined(&self) -> bool { - matches!(&self.inner, InnerValue::Undefined) + self.0.is_undefined() } /// Returns true if the value is null. #[inline] #[must_use] pub const fn is_null(&self) -> bool { - matches!(self.inner, InnerValue::Null) + self.0.is_null() } /// Returns true if the value is null or undefined. @@ -352,14 +292,17 @@ impl JsValue { #[must_use] #[allow(clippy::float_cmp)] pub const fn as_i32(&self) -> Option { - match self.inner { - InnerValue::Integer32(integer) => Some(integer), - // If it can fit in a i32 and the truncated version is - // equal to the original then it is an integer. - InnerValue::Float64(rational) if rational == ((rational as i32) as f64) => { + if let Some(integer) = self.0.as_integer32() { + Some(integer) + } else if let Some(rational) = self.0.as_float64() { + // Use this poor-man's check as `[f64::fract]` isn't const. + if rational == ((rational as i32) as f64) { Some(rational as i32) + } else { + None } - _ => None, + } else { + None } } @@ -367,20 +310,17 @@ impl JsValue { #[inline] #[must_use] pub const fn is_number(&self) -> bool { - matches!( - self.inner, - InnerValue::Float64(_) | InnerValue::Integer32(_) - ) + self.0.is_integer32() || self.0.is_float64() } /// Returns the number if the value is a number, otherwise `None`. #[inline] #[must_use] pub fn as_number(&self) -> Option { - match self.inner { - InnerValue::Integer32(integer) => Some(integer.into()), - InnerValue::Float64(rational) => Some(rational), - _ => None, + if let Some(i) = self.as_i32() { + Some(i as f64) + } else { + self.0.as_float64() } } @@ -388,52 +328,42 @@ impl JsValue { #[inline] #[must_use] pub const fn is_string(&self) -> bool { - matches!(self.inner, InnerValue::String(_)) + self.0.is_string() } /// Returns the string if the value is a string, otherwise `None`. #[inline] #[must_use] pub const fn as_string(&self) -> Option<&JsString> { - if let InnerValue::String(string) = &self.inner { - Some(string) - } else { - None - } + self.0.as_string() } /// Returns true if the value is a boolean. #[inline] #[must_use] pub const fn is_boolean(&self) -> bool { - matches!(self.inner, InnerValue::Boolean(_)) + self.0.is_bool() } /// Returns the boolean if the value is a boolean, otherwise `None`. #[inline] #[must_use] pub const fn as_boolean(&self) -> Option { - match &self.inner { - InnerValue::Boolean(boolean) => Some(*boolean), - _ => None, - } + self.0.as_bool() } /// Returns true if the value is a bigint. #[inline] #[must_use] pub const fn is_bigint(&self) -> bool { - matches!(self.inner, InnerValue::BigInt(_)) + self.0.is_bigint() } /// Returns an optional reference to a `BigInt` if the value is a `BigInt` primitive. #[inline] #[must_use] pub const fn as_bigint(&self) -> Option<&JsBigInt> { - match &self.inner { - InnerValue::BigInt(bigint) => Some(bigint), - _ => None, - } + self.0.as_bigint() } /// Converts the value to a `bool` type. @@ -444,13 +374,13 @@ impl JsValue { /// [spec]: https://tc39.es/ecma262/#sec-toboolean #[must_use] pub fn to_boolean(&self) -> bool { - match self.inner { - InnerValue::Symbol(_) | InnerValue::Object(_) => true, - InnerValue::String(ref s) if !s.is_empty() => true, - InnerValue::Float64(n) if n != 0.0 && !n.is_nan() => true, - InnerValue::Integer32(n) if n != 0 => true, - InnerValue::BigInt(ref n) if !n.is_zero() => true, - InnerValue::Boolean(v) => v, + match self.variant() { + JsVariant::Symbol(_) | JsVariant::Object(_) => true, + JsVariant::String(s) if !s.is_empty() => true, + JsVariant::Float64(n) if n != 0.0 && !n.is_nan() => true, + JsVariant::Integer32(n) if n != 0 => true, + JsVariant::BigInt(n) if !n.is_zero() => true, + JsVariant::Boolean(v) => v, _ => false, } } @@ -518,14 +448,14 @@ impl JsValue { /// /// [spec]: https://tc39.es/ecma262/#sec-tobigint pub fn to_bigint(&self, context: &mut Context) -> JsResult { - match &self.inner { - InnerValue::Null => Err(JsNativeError::typ() + match self.variant() { + JsVariant::Null => Err(JsNativeError::typ() .with_message("cannot convert null to a BigInt") .into()), - InnerValue::Undefined => Err(JsNativeError::typ() + JsVariant::Undefined => Err(JsNativeError::typ() .with_message("cannot convert undefined to a BigInt") .into()), - InnerValue::String(ref string) => JsBigInt::from_js_string(string).map_or_else( + JsVariant::String(ref string) => JsBigInt::from_js_string(string).map_or_else( || { Err(JsNativeError::syntax() .with_message(format!( @@ -536,17 +466,17 @@ impl JsValue { }, Ok, ), - InnerValue::Boolean(true) => Ok(JsBigInt::one()), - InnerValue::Boolean(false) => Ok(JsBigInt::zero()), - InnerValue::Integer32(_) | InnerValue::Float64(_) => Err(JsNativeError::typ() + JsVariant::Boolean(true) => Ok(JsBigInt::one()), + JsVariant::Boolean(false) => Ok(JsBigInt::zero()), + JsVariant::Integer32(_) | JsVariant::Float64(_) => Err(JsNativeError::typ() .with_message("cannot convert Number to a BigInt") .into()), - InnerValue::BigInt(b) => Ok(b.clone()), - InnerValue::Object(_) => { + JsVariant::BigInt(b) => Ok(b.clone()), + JsVariant::Object(_) => { let primitive = self.to_primitive(context, PreferredType::Number)?; primitive.to_bigint(context) } - InnerValue::Symbol(_) => Err(JsNativeError::typ() + JsVariant::Symbol(_) => Err(JsNativeError::typ() .with_message("cannot convert Symbol to a BigInt") .into()), } @@ -579,19 +509,19 @@ impl JsValue { /// /// This function is equivalent to `String(value)` in JavaScript. pub fn to_string(&self, context: &mut Context) -> JsResult { - match self.inner { - InnerValue::Null => Ok(js_string!("null")), - InnerValue::Undefined => Ok(js_string!("undefined")), - InnerValue::Boolean(true) => Ok(js_string!("true")), - InnerValue::Boolean(false) => Ok(js_string!("false")), - InnerValue::Float64(rational) => Ok(JsString::from(rational)), - InnerValue::Integer32(integer) => Ok(JsString::from(integer)), - InnerValue::String(ref string) => Ok(string.clone()), - InnerValue::Symbol(_) => Err(JsNativeError::typ() + match self.variant() { + JsVariant::Null => Ok(js_string!("null")), + JsVariant::Undefined => Ok(js_string!("undefined")), + JsVariant::Boolean(true) => Ok(js_string!("true")), + JsVariant::Boolean(false) => Ok(js_string!("false")), + JsVariant::Float64(rational) => Ok(JsString::from(rational)), + JsVariant::Integer32(integer) => Ok(JsString::from(integer)), + JsVariant::String(string) => Ok(string.clone()), + JsVariant::Symbol(_) => Err(JsNativeError::typ() .with_message("can't convert symbol to string") .into()), - InnerValue::BigInt(ref bigint) => Ok(bigint.to_string().into()), - InnerValue::Object(_) => { + JsVariant::BigInt(bigint) => Ok(bigint.to_string().into()), + JsVariant::Object(_) => { let primitive = self.to_primitive(context, PreferredType::String)?; primitive.to_string(context) } @@ -604,41 +534,41 @@ impl JsValue { /// /// See: pub fn to_object(&self, context: &mut Context) -> JsResult { - match &self.inner { - InnerValue::Undefined | InnerValue::Null => Err(JsNativeError::typ() + match self.variant() { + JsVariant::Undefined | JsVariant::Null => Err(JsNativeError::typ() .with_message("cannot convert 'null' or 'undefined' to object") .into()), - InnerValue::Boolean(boolean) => Ok(context + JsVariant::Boolean(boolean) => Ok(context .intrinsics() .templates() .boolean() - .create(*boolean, Vec::default())), - InnerValue::Integer32(integer) => Ok(context + .create(boolean, Vec::default())), + JsVariant::Integer32(integer) => Ok(context .intrinsics() .templates() .number() - .create(f64::from(*integer), Vec::default())), - InnerValue::Float64(rational) => Ok(context + .create(f64::from(integer), Vec::default())), + JsVariant::Float64(rational) => Ok(context .intrinsics() .templates() .number() - .create(*rational, Vec::default())), - InnerValue::String(ref string) => Ok(context + .create(rational, Vec::default())), + JsVariant::String(string) => Ok(context .intrinsics() .templates() .string() .create(string.clone(), vec![string.len().into()])), - InnerValue::Symbol(ref symbol) => Ok(context + JsVariant::Symbol(symbol) => Ok(context .intrinsics() .templates() .symbol() .create(symbol.clone(), Vec::default())), - InnerValue::BigInt(ref bigint) => Ok(context + JsVariant::BigInt(bigint) => Ok(context .intrinsics() .templates() .bigint() .create(bigint.clone(), Vec::default())), - InnerValue::Object(jsobject) => Ok(jsobject.clone()), + JsVariant::Object(jsobject) => Ok(jsobject.clone()), } } @@ -646,18 +576,18 @@ impl JsValue { /// /// See pub fn to_property_key(&self, context: &mut Context) -> JsResult { - Ok(match &self.inner { + Ok(match self.variant() { // Fast path: - InnerValue::String(string) => string.clone().into(), - InnerValue::Symbol(symbol) => symbol.clone().into(), - InnerValue::Integer32(integer) => (*integer).into(), + JsVariant::String(string) => string.clone().into(), + JsVariant::Symbol(symbol) => symbol.clone().into(), + JsVariant::Integer32(integer) => integer.into(), // Slow path: - InnerValue::Object(_) => { + JsVariant::Object(_) => { let primitive = self.to_primitive(context, PreferredType::String)?; - match primitive.inner { - InnerValue::String(ref string) => string.clone().into(), - InnerValue::Symbol(ref symbol) => symbol.clone().into(), - InnerValue::Integer32(integer) => integer.into(), + match primitive.variant() { + JsVariant::String(string) => string.clone().into(), + JsVariant::Symbol(symbol) => symbol.clone().into(), + JsVariant::Integer32(integer) => integer.into(), _ => primitive.to_string(context)?.into(), } } @@ -688,7 +618,7 @@ impl JsValue { /// See: pub fn to_u32(&self, context: &mut Context) -> JsResult { // This is the fast path, if the value is Integer we can just return it. - if let InnerValue::Integer32(number) = self.inner { + if let Some(number) = self.0.as_integer32() { if let Ok(number) = u32::try_from(number) { return Ok(number); } @@ -703,7 +633,7 @@ impl JsValue { /// See: pub fn to_i32(&self, context: &mut Context) -> JsResult { // This is the fast path, if the value is Integer we can just return it. - if let InnerValue::Integer32(number) = self.inner { + if let Some(number) = self.0.as_integer32() { return Ok(number); } let number = self.to_number(context)?; @@ -975,20 +905,20 @@ impl JsValue { /// /// See: pub fn to_number(&self, context: &mut Context) -> JsResult { - match self.inner { - InnerValue::Null => Ok(0.0), - InnerValue::Undefined => Ok(f64::NAN), - InnerValue::Boolean(b) => Ok(if b { 1.0 } else { 0.0 }), - InnerValue::String(ref string) => Ok(string.to_number()), - InnerValue::Float64(number) => Ok(number), - InnerValue::Integer32(integer) => Ok(f64::from(integer)), - InnerValue::Symbol(_) => Err(JsNativeError::typ() + match self.variant() { + JsVariant::Null => Ok(0.0), + JsVariant::Undefined => Ok(f64::NAN), + JsVariant::Boolean(b) => Ok(if b { 1.0 } else { 0.0 }), + JsVariant::String(string) => Ok(string.to_number()), + JsVariant::Float64(number) => Ok(number), + JsVariant::Integer32(integer) => Ok(f64::from(integer)), + JsVariant::Symbol(_) => Err(JsNativeError::typ() .with_message("argument must not be a symbol") .into()), - InnerValue::BigInt(_) => Err(JsNativeError::typ() + JsVariant::BigInt(_) => Err(JsNativeError::typ() .with_message("argument must not be a bigint") .into()), - InnerValue::Object(_) => { + JsVariant::Object(_) => { let primitive = self.to_primitive(context, PreferredType::Number)?; primitive.to_number(context) } diff --git a/core/engine/src/value/operations.rs b/core/engine/src/value/operations.rs index 2b0d659c4d2..c82bab759a3 100644 --- a/core/engine/src/value/operations.rs +++ b/core/engine/src/value/operations.rs @@ -1,4 +1,3 @@ -use crate::value::InnerValue; use crate::{ builtins::{ number::{f64_to_int32, f64_to_uint32}, @@ -7,37 +6,33 @@ use crate::{ error::JsNativeError, js_string, value::{JsSymbol, Numeric, PreferredType}, - Context, JsBigInt, JsResult, JsValue, + Context, JsBigInt, JsResult, JsValue, JsVariant, }; impl JsValue { /// Perform the binary `+` operator on the value and return the result. pub fn add(&self, other: &Self, context: &mut Context) -> JsResult { - Ok(match (&self.inner, &other.inner) { + Ok(match (self.variant(), other.variant()) { // Fast path: // Numeric add - (InnerValue::Integer32(x), InnerValue::Integer32(y)) => x - .checked_add(*y) - .map_or_else(|| Self::new(f64::from(*x) + f64::from(*y)), Self::new), - (InnerValue::Float64(x), InnerValue::Float64(y)) => Self::new(x + y), - (InnerValue::Integer32(x), InnerValue::Float64(y)) => Self::new(f64::from(*x) + y), - (InnerValue::Float64(x), InnerValue::Integer32(y)) => Self::new(x + f64::from(*y)), - (InnerValue::BigInt(ref x), InnerValue::BigInt(ref y)) => { - Self::new(JsBigInt::add(x, y)) - } + (JsVariant::Integer32(x), JsVariant::Integer32(y)) => x + .checked_add(y) + .map_or_else(|| Self::new(f64::from(x) + f64::from(y)), Self::new), + (JsVariant::Float64(x), JsVariant::Float64(y)) => Self::new(x + y), + (JsVariant::Integer32(x), JsVariant::Float64(y)) => Self::new(f64::from(x) + y), + (JsVariant::Float64(x), JsVariant::Integer32(y)) => Self::new(x + f64::from(y)), + (JsVariant::BigInt(x), JsVariant::BigInt(y)) => Self::new(JsBigInt::add(x, y)), // String concat - (InnerValue::String(ref x), InnerValue::String(ref y)) => Self::from(js_string!(x, y)), + (JsVariant::String(x), JsVariant::String(y)) => Self::from(js_string!(x, y)), // Slow path: (_, _) => { let x = self.to_primitive(context, PreferredType::Default)?; let y = other.to_primitive(context, PreferredType::Default)?; - match (&x.inner, &y.inner) { - (InnerValue::String(ref x), _) => { - Self::from(js_string!(x, &y.to_string(context)?)) - } - (_, InnerValue::String(y)) => Self::from(js_string!(&x.to_string(context)?, y)), + match (x.variant(), y.variant()) { + (JsVariant::String(x), _) => Self::from(js_string!(x, &y.to_string(context)?)), + (_, JsVariant::String(y)) => Self::from(js_string!(&x.to_string(context)?, y)), (_, _) => { match (x.to_numeric(context)?, y.to_numeric(context)?) { (Numeric::Number(x), Numeric::Number(y)) => Self::new(x + y), @@ -58,18 +53,16 @@ impl JsValue { /// Perform the binary `-` operator on the value and return the result. pub fn sub(&self, other: &Self, context: &mut Context) -> JsResult { - Ok(match (&self.inner, &other.inner) { + Ok(match (self.variant(), other.variant()) { // Fast path: - (InnerValue::Integer32(x), InnerValue::Integer32(y)) => x - .checked_sub(*y) - .map_or_else(|| Self::new(f64::from(*x) - f64::from(*y)), Self::new), - (InnerValue::Float64(x), InnerValue::Float64(y)) => Self::new(x - y), - (InnerValue::Integer32(x), InnerValue::Float64(y)) => Self::new(f64::from(*x) - y), - (InnerValue::Float64(x), InnerValue::Integer32(y)) => Self::new(x - f64::from(*y)), - - (InnerValue::BigInt(ref x), InnerValue::BigInt(ref y)) => { - Self::new(JsBigInt::sub(x, y)) - } + (JsVariant::Integer32(x), JsVariant::Integer32(y)) => x + .checked_sub(y) + .map_or_else(|| Self::new(f64::from(x) - f64::from(y)), Self::new), + (JsVariant::Float64(x), JsVariant::Float64(y)) => Self::new(x - y), + (JsVariant::Integer32(x), JsVariant::Float64(y)) => Self::new(f64::from(x) - y), + (JsVariant::Float64(x), JsVariant::Integer32(y)) => Self::new(x - f64::from(y)), + + (JsVariant::BigInt(x), JsVariant::BigInt(y)) => Self::new(JsBigInt::sub(x, y)), // Slow path: (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) { @@ -86,18 +79,16 @@ impl JsValue { /// Perform the binary `*` operator on the value and return the result. pub fn mul(&self, other: &Self, context: &mut Context) -> JsResult { - Ok(match (&self.inner, &other.inner) { + Ok(match (self.variant(), other.variant()) { // Fast path: - (InnerValue::Integer32(x), InnerValue::Integer32(y)) => x - .checked_mul(*y) - .map_or_else(|| Self::new(f64::from(*x) * f64::from(*y)), Self::new), - (InnerValue::Float64(x), InnerValue::Float64(y)) => Self::new(x * y), - (InnerValue::Integer32(x), InnerValue::Float64(y)) => Self::new(f64::from(*x) * y), - (InnerValue::Float64(x), InnerValue::Integer32(y)) => Self::new(x * f64::from(*y)), - - (InnerValue::BigInt(ref x), InnerValue::BigInt(ref y)) => { - Self::new(JsBigInt::mul(x, y)) - } + (JsVariant::Integer32(x), JsVariant::Integer32(y)) => x + .checked_mul(y) + .map_or_else(|| Self::new(f64::from(x) * f64::from(y)), Self::new), + (JsVariant::Float64(x), JsVariant::Float64(y)) => Self::new(x * y), + (JsVariant::Integer32(x), JsVariant::Float64(y)) => Self::new(f64::from(x) * y), + (JsVariant::Float64(x), JsVariant::Integer32(y)) => Self::new(x * f64::from(y)), + + (JsVariant::BigInt(x), JsVariant::BigInt(y)) => Self::new(JsBigInt::mul(x, y)), // Slow path: (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) { @@ -114,17 +105,17 @@ impl JsValue { /// Perform the binary `/` operator on the value and return the result. pub fn div(&self, other: &Self, context: &mut Context) -> JsResult { - Ok(match (&self.inner, &other.inner) { + Ok(match (self.variant(), other.variant()) { // Fast path: - (InnerValue::Integer32(x), InnerValue::Integer32(y)) => x - .checked_div(*y) - .filter(|div| *y * div == *x) - .map_or_else(|| Self::new(f64::from(*x) / f64::from(*y)), Self::new), - (InnerValue::Float64(x), InnerValue::Float64(y)) => Self::new(x / y), - (InnerValue::Integer32(x), InnerValue::Float64(y)) => Self::new(f64::from(*x) / y), - (InnerValue::Float64(x), InnerValue::Integer32(y)) => Self::new(x / f64::from(*y)), - - (InnerValue::BigInt(ref x), InnerValue::BigInt(ref y)) => { + (JsVariant::Integer32(x), JsVariant::Integer32(y)) => x + .checked_div(y) + .filter(|div| y * div == x) + .map_or_else(|| Self::new(f64::from(x) / f64::from(y)), Self::new), + (JsVariant::Float64(x), JsVariant::Float64(y)) => Self::new(x / y), + (JsVariant::Integer32(x), JsVariant::Float64(y)) => Self::new(f64::from(x) / y), + (JsVariant::Float64(x), JsVariant::Integer32(y)) => Self::new(x / f64::from(y)), + + (JsVariant::BigInt(x), JsVariant::BigInt(y)) => { if y.is_zero() { return Err(JsNativeError::range() .with_message("BigInt division by zero") @@ -155,29 +146,29 @@ impl JsValue { /// Perform the binary `%` operator on the value and return the result. pub fn rem(&self, other: &Self, context: &mut Context) -> JsResult { - Ok(match (&self.inner, &other.inner) { + Ok(match (self.variant(), other.variant()) { // Fast path: - (InnerValue::Integer32(x), InnerValue::Integer32(y)) => { - if *y == 0 { + (JsVariant::Integer32(x), JsVariant::Integer32(y)) => { + if y == 0 { Self::nan() } else { - match x % *y { - rem if rem == 0 && *x < 0 => Self::new(-0.0), + match x % y { + rem if rem == 0 && x < 0 => Self::new(-0.0), rem => Self::new(rem), } } } - (InnerValue::Float64(x), InnerValue::Float64(y)) => Self::new((x % y).copysign(*x)), - (InnerValue::Integer32(x), InnerValue::Float64(y)) => { - let x = f64::from(*x); + (JsVariant::Float64(x), JsVariant::Float64(y)) => Self::new((x % y).copysign(x)), + (JsVariant::Integer32(x), JsVariant::Float64(y)) => { + let x = f64::from(x); Self::new((x % y).copysign(x)) } - (InnerValue::Float64(x), InnerValue::Integer32(y)) => { - Self::new((x % f64::from(*y)).copysign(*x)) + (JsVariant::Float64(x), JsVariant::Integer32(y)) => { + Self::new((x % f64::from(y)).copysign(x)) } - (InnerValue::BigInt(x), InnerValue::BigInt(y)) => { + (JsVariant::BigInt(x), JsVariant::BigInt(y)) => { if y.is_zero() { return Err(JsNativeError::range() .with_message("BigInt division by zero") @@ -210,30 +201,28 @@ impl JsValue { // NOTE: There are some cases in the spec where we have to compare floats #[allow(clippy::float_cmp)] pub fn pow(&self, other: &Self, context: &mut Context) -> JsResult { - Ok(match (&self.inner, &other.inner) { + Ok(match (self.variant(), other.variant()) { // Fast path: - (InnerValue::Integer32(x), InnerValue::Integer32(y)) => u32::try_from(*y) + (JsVariant::Integer32(x), JsVariant::Integer32(y)) => u32::try_from(y) .ok() .and_then(|y| x.checked_pow(y)) - .map_or_else(|| Self::new(f64::from(*x).powi(*y)), Self::new), - (InnerValue::Float64(x), InnerValue::Float64(y)) => { + .map_or_else(|| Self::new(f64::from(x).powi(y)), Self::new), + (JsVariant::Float64(x), JsVariant::Float64(y)) => { if x.abs() == 1.0 && y.is_infinite() { Self::nan() } else { - Self::new(x.powf(*y)) + Self::new(x.powf(y)) } } - (InnerValue::Integer32(x), InnerValue::Float64(y)) => { + (JsVariant::Integer32(x), JsVariant::Float64(y)) => { if x.wrapping_abs() == 1 && y.is_infinite() { Self::nan() } else { - Self::new(f64::from(*x).powf(*y)) + Self::new(f64::from(x).powf(y)) } } - (InnerValue::Float64(x), InnerValue::Integer32(y)) => Self::new(x.powi(*y)), - (InnerValue::BigInt(ref a), InnerValue::BigInt(ref b)) => { - Self::new(JsBigInt::pow(a, b)?) - } + (JsVariant::Float64(x), JsVariant::Integer32(y)) => Self::new(x.powi(y)), + (JsVariant::BigInt(a), JsVariant::BigInt(b)) => Self::new(JsBigInt::pow(a, b)?), // Slow path: (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) { @@ -256,16 +245,16 @@ impl JsValue { /// Perform the binary `&` operator on the value and return the result. pub fn bitand(&self, other: &Self, context: &mut Context) -> JsResult { - Ok(match (&self.inner, &other.inner) { + Ok(match (self.variant(), other.variant()) { // Fast path: - (InnerValue::Integer32(x), InnerValue::Integer32(y)) => Self::new(x & y), - (InnerValue::Float64(x), InnerValue::Float64(y)) => { - Self::new(f64_to_int32(*x) & f64_to_int32(*y)) + (JsVariant::Integer32(x), JsVariant::Integer32(y)) => Self::new(x & y), + (JsVariant::Float64(x), JsVariant::Float64(y)) => { + Self::new(f64_to_int32(x) & f64_to_int32(y)) } - (InnerValue::Integer32(x), InnerValue::Float64(y)) => Self::new(x & f64_to_int32(*y)), - (InnerValue::Float64(x), InnerValue::Integer32(y)) => Self::new(f64_to_int32(*x) & y), + (JsVariant::Integer32(x), JsVariant::Float64(y)) => Self::new(x & f64_to_int32(y)), + (JsVariant::Float64(x), JsVariant::Integer32(y)) => Self::new(f64_to_int32(x) & y), - (InnerValue::BigInt(x), InnerValue::BigInt(y)) => Self::new(JsBigInt::bitand(x, y)), + (JsVariant::BigInt(x), JsVariant::BigInt(y)) => Self::new(JsBigInt::bitand(x, y)), // Slow path: (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) { @@ -286,16 +275,16 @@ impl JsValue { /// Perform the binary `|` operator on the value and return the result. pub fn bitor(&self, other: &Self, context: &mut Context) -> JsResult { - Ok(match (&self.inner, &other.inner) { + Ok(match (self.variant(), other.variant()) { // Fast path: - (InnerValue::Integer32(x), InnerValue::Integer32(y)) => Self::new(x | y), - (InnerValue::Float64(x), InnerValue::Float64(y)) => { - Self::new(f64_to_int32(*x) | f64_to_int32(*y)) + (JsVariant::Integer32(x), JsVariant::Integer32(y)) => Self::new(x | y), + (JsVariant::Float64(x), JsVariant::Float64(y)) => { + Self::new(f64_to_int32(x) | f64_to_int32(y)) } - (InnerValue::Integer32(x), InnerValue::Float64(y)) => Self::new(x | f64_to_int32(*y)), - (InnerValue::Float64(x), InnerValue::Integer32(y)) => Self::new(f64_to_int32(*x) | y), + (JsVariant::Integer32(x), JsVariant::Float64(y)) => Self::new(x | f64_to_int32(y)), + (JsVariant::Float64(x), JsVariant::Integer32(y)) => Self::new(f64_to_int32(x) | y), - (InnerValue::BigInt(x), InnerValue::BigInt(y)) => Self::new(JsBigInt::bitor(x, y)), + (JsVariant::BigInt(x), JsVariant::BigInt(y)) => Self::new(JsBigInt::bitor(x, y)), // Slow path: (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) { @@ -316,16 +305,16 @@ impl JsValue { /// Perform the binary `^` operator on the value and return the result. pub fn bitxor(&self, other: &Self, context: &mut Context) -> JsResult { - Ok(match (&self.inner, &other.inner) { + Ok(match (self.variant(), other.variant()) { // Fast path: - (InnerValue::Integer32(x), InnerValue::Integer32(y)) => Self::new(x ^ y), - (InnerValue::Float64(x), InnerValue::Float64(y)) => { - Self::new(f64_to_int32(*x) ^ f64_to_int32(*y)) + (JsVariant::Integer32(x), JsVariant::Integer32(y)) => Self::new(x ^ y), + (JsVariant::Float64(x), JsVariant::Float64(y)) => { + Self::new(f64_to_int32(x) ^ f64_to_int32(y)) } - (InnerValue::Integer32(x), InnerValue::Float64(y)) => Self::new(x ^ f64_to_int32(*y)), - (InnerValue::Float64(x), InnerValue::Integer32(y)) => Self::new(f64_to_int32(*x) ^ y), + (JsVariant::Integer32(x), JsVariant::Float64(y)) => Self::new(x ^ f64_to_int32(y)), + (JsVariant::Float64(x), JsVariant::Integer32(y)) => Self::new(f64_to_int32(x) ^ y), - (InnerValue::BigInt(x), InnerValue::BigInt(y)) => Self::new(JsBigInt::bitxor(x, y)), + (JsVariant::BigInt(x), JsVariant::BigInt(y)) => Self::new(JsBigInt::bitxor(x, y)), // Slow path: (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) { @@ -346,24 +335,22 @@ impl JsValue { /// Perform the binary `<<` operator on the value and return the result. pub fn shl(&self, other: &Self, context: &mut Context) -> JsResult { - Ok(match (&self.inner, &other.inner) { + Ok(match (self.variant(), other.variant()) { // Fast path: - (InnerValue::Integer32(x), InnerValue::Integer32(y)) => { - Self::new(x.wrapping_shl(*y as u32)) + (JsVariant::Integer32(x), JsVariant::Integer32(y)) => { + Self::new(x.wrapping_shl(y as u32)) } - (InnerValue::Float64(x), InnerValue::Float64(y)) => { - Self::new(f64_to_int32(*x).wrapping_shl(f64_to_uint32(*y))) + (JsVariant::Float64(x), JsVariant::Float64(y)) => { + Self::new(f64_to_int32(x).wrapping_shl(f64_to_uint32(y))) } - (InnerValue::Integer32(x), InnerValue::Float64(y)) => { - Self::new(x.wrapping_shl(f64_to_uint32(*y))) + (JsVariant::Integer32(x), JsVariant::Float64(y)) => { + Self::new(x.wrapping_shl(f64_to_uint32(y))) } - (InnerValue::Float64(x), InnerValue::Integer32(y)) => { - Self::new(f64_to_int32(*x).wrapping_shl(*y as u32)) + (JsVariant::Float64(x), JsVariant::Integer32(y)) => { + Self::new(f64_to_int32(x).wrapping_shl(y as u32)) } - (InnerValue::BigInt(a), InnerValue::BigInt(b)) => { - Self::new(JsBigInt::shift_left(a, b)?) - } + (JsVariant::BigInt(a), JsVariant::BigInt(b)) => Self::new(JsBigInt::shift_left(a, b)?), // Slow path: (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) { @@ -384,24 +371,22 @@ impl JsValue { /// Perform the binary `>>` operator on the value and return the result. pub fn shr(&self, other: &Self, context: &mut Context) -> JsResult { - Ok(match (&self.inner, &other.inner) { + Ok(match (self.variant(), other.variant()) { // Fast path: - (InnerValue::Integer32(x), InnerValue::Integer32(y)) => { - Self::new(x.wrapping_shr(*y as u32)) + (JsVariant::Integer32(x), JsVariant::Integer32(y)) => { + Self::new(x.wrapping_shr(y as u32)) } - (InnerValue::Float64(x), InnerValue::Float64(y)) => { - Self::new(f64_to_int32(*x).wrapping_shr(f64_to_uint32(*y))) + (JsVariant::Float64(x), JsVariant::Float64(y)) => { + Self::new(f64_to_int32(x).wrapping_shr(f64_to_uint32(y))) } - (InnerValue::Integer32(x), InnerValue::Float64(y)) => { - Self::new(x.wrapping_shr(f64_to_uint32(*y))) + (JsVariant::Integer32(x), JsVariant::Float64(y)) => { + Self::new(x.wrapping_shr(f64_to_uint32(y))) } - (InnerValue::Float64(x), InnerValue::Integer32(y)) => { - Self::new(f64_to_int32(*x).wrapping_shr(*y as u32)) + (JsVariant::Float64(x), JsVariant::Integer32(y)) => { + Self::new(f64_to_int32(x).wrapping_shr(y as u32)) } - (InnerValue::BigInt(a), InnerValue::BigInt(b)) => { - Self::new(JsBigInt::shift_right(a, b)?) - } + (JsVariant::BigInt(a), JsVariant::BigInt(b)) => Self::new(JsBigInt::shift_right(a, b)?), // Slow path: (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) { @@ -422,19 +407,19 @@ impl JsValue { /// Perform the binary `>>>` operator on the value and return the result. pub fn ushr(&self, other: &Self, context: &mut Context) -> JsResult { - Ok(match (&self.inner, &other.inner) { + Ok(match (self.variant(), other.variant()) { // Fast path: - (InnerValue::Integer32(x), InnerValue::Integer32(y)) => { - Self::new((*x as u32).wrapping_shr(*y as u32)) + (JsVariant::Integer32(x), JsVariant::Integer32(y)) => { + Self::new((x as u32).wrapping_shr(y as u32)) } - (InnerValue::Float64(x), InnerValue::Float64(y)) => { - Self::new(f64_to_uint32(*x).wrapping_shr(f64_to_uint32(*y))) + (JsVariant::Float64(x), JsVariant::Float64(y)) => { + Self::new(f64_to_uint32(x).wrapping_shr(f64_to_uint32(y))) } - (InnerValue::Integer32(x), InnerValue::Float64(y)) => { - Self::new((*x as u32).wrapping_shr(f64_to_uint32(*y))) + (JsVariant::Integer32(x), JsVariant::Float64(y)) => { + Self::new((x as u32).wrapping_shr(f64_to_uint32(y))) } - (InnerValue::Float64(x), InnerValue::Integer32(y)) => { - Self::new(f64_to_uint32(*x).wrapping_shr(*y as u32)) + (JsVariant::Float64(x), JsVariant::Integer32(y)) => { + Self::new(f64_to_uint32(x).wrapping_shr(y as u32)) } // Slow path: @@ -497,20 +482,20 @@ impl JsValue { /// Returns the negated value. pub fn neg(&self, context: &mut Context) -> JsResult { - Ok(match self.inner { - InnerValue::Symbol(_) | InnerValue::Undefined => Self::new(f64::NAN), - InnerValue::Object(_) => Self::new( + Ok(match self.variant() { + JsVariant::Symbol(_) | JsVariant::Undefined => Self::new(f64::NAN), + JsVariant::Object(_) => Self::new( self.to_numeric_number(context) .map_or(f64::NAN, std::ops::Neg::neg), ), - InnerValue::String(ref str) => Self::new(-str.to_number()), - InnerValue::Float64(num) => Self::new(-num), - InnerValue::Integer32(0) | InnerValue::Boolean(false) | InnerValue::Null => { + JsVariant::String(str) => Self::new(-str.to_number()), + JsVariant::Float64(num) => Self::new(-num), + JsVariant::Integer32(0) | JsVariant::Boolean(false) | JsVariant::Null => { Self::new(-f64::from(0)) } - InnerValue::Integer32(num) => Self::new(-num), - InnerValue::Boolean(true) => Self::new(-f64::from(1)), - InnerValue::BigInt(ref x) => Self::new(JsBigInt::neg(x)), + JsVariant::Integer32(num) => Self::new(-num), + JsVariant::Boolean(true) => Self::new(-f64::from(1)), + JsVariant::BigInt(x) => Self::new(JsBigInt::neg(x)), }) } @@ -543,17 +528,13 @@ impl JsValue { left_first: bool, context: &mut Context, ) -> JsResult { - Ok(match (&self.inner, &other.inner) { + Ok(match (self.variant(), other.variant()) { // Fast path (for some common operations): - (InnerValue::Integer32(x), InnerValue::Integer32(y)) => (x < y).into(), - (InnerValue::Integer32(x), InnerValue::Float64(y)) => { - Number::less_than(f64::from(*x), *y) - } - (InnerValue::Float64(x), InnerValue::Integer32(y)) => { - Number::less_than(*x, f64::from(*y)) - } - (InnerValue::Float64(x), InnerValue::Float64(y)) => Number::less_than(*x, *y), - (InnerValue::BigInt(x), InnerValue::BigInt(y)) => (x < y).into(), + (JsVariant::Integer32(x), JsVariant::Integer32(y)) => (x < y).into(), + (JsVariant::Integer32(x), JsVariant::Float64(y)) => Number::less_than(f64::from(x), y), + (JsVariant::Float64(x), JsVariant::Integer32(y)) => Number::less_than(x, f64::from(y)), + (JsVariant::Float64(x), JsVariant::Float64(y)) => Number::less_than(x, y), + (JsVariant::BigInt(x), JsVariant::BigInt(y)) => (x < y).into(), // Slow path: (_, _) => { @@ -568,16 +549,12 @@ impl JsValue { (px, py) }; - match (&px.inner, &py.inner) { - (InnerValue::String(x), InnerValue::String(ref y)) => (x < y).into(), - (InnerValue::BigInt(x), InnerValue::String(ref y)) => { - JsBigInt::from_js_string(y) - .map_or(AbstractRelation::Undefined, |y| (*x < y).into()) - } - (InnerValue::String(ref x), InnerValue::BigInt(ref y)) => { - JsBigInt::from_js_string(x) - .map_or(AbstractRelation::Undefined, |x| (x < *y).into()) - } + match (px.variant(), py.variant()) { + (JsVariant::String(x), JsVariant::String(y)) => (x < y).into(), + (JsVariant::BigInt(x), JsVariant::String(y)) => JsBigInt::from_js_string(y) + .map_or(AbstractRelation::Undefined, |ref y| (x < y).into()), + (JsVariant::String(x), JsVariant::BigInt(y)) => JsBigInt::from_js_string(x) + .map_or(AbstractRelation::Undefined, |ref x| (x < y).into()), (_, _) => match (px.to_numeric(context)?, py.to_numeric(context)?) { (Numeric::Number(x), Numeric::Number(y)) => Number::less_than(x, y), (Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => (x < y).into(), @@ -590,7 +567,7 @@ impl JsValue { } if let Ok(y) = JsBigInt::try_from(y) { - return Ok((*x < y).into()); + return Ok((x < &y).into()); } (x.to_f64() < y).into() @@ -604,7 +581,7 @@ impl JsValue { } if let Ok(x) = JsBigInt::try_from(x) { - return Ok((x < *y).into()); + return Ok((&x < y).into()); } (x < y.to_f64()).into() diff --git a/core/engine/src/value/type.rs b/core/engine/src/value/type.rs index 6d4b6d9c184..1ae4566c743 100644 --- a/core/engine/src/value/type.rs +++ b/core/engine/src/value/type.rs @@ -1,4 +1,5 @@ -use super::{InnerValue, JsValue}; +use super::JsValue; +use crate::JsVariant; /// Possible types of values as defined at . #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -37,15 +38,15 @@ impl JsValue { /// Check [`JsValue::type_of`] if you need to call the `typeof` operator. #[must_use] pub const fn get_type(&self) -> Type { - match self.inner { - InnerValue::Float64(_) | InnerValue::Integer32(_) => Type::Number, - InnerValue::String(_) => Type::String, - InnerValue::Boolean(_) => Type::Boolean, - InnerValue::Symbol(_) => Type::Symbol, - InnerValue::Null => Type::Null, - InnerValue::Undefined => Type::Undefined, - InnerValue::BigInt(_) => Type::BigInt, - InnerValue::Object(_) => Type::Object, + match self.variant() { + JsVariant::Float64(_) | JsVariant::Integer32(_) => Type::Number, + JsVariant::String(_) => Type::String, + JsVariant::Boolean(_) => Type::Boolean, + JsVariant::Symbol(_) => Type::Symbol, + JsVariant::Null => Type::Null, + JsVariant::Undefined => Type::Undefined, + JsVariant::BigInt(_) => Type::BigInt, + JsVariant::Object(_) => Type::Object, } } } diff --git a/core/engine/src/value/variant.rs b/core/engine/src/value/variant.rs index dec2827e264..fa8a81a3d18 100644 --- a/core/engine/src/value/variant.rs +++ b/core/engine/src/value/variant.rs @@ -1,4 +1,3 @@ -use super::InnerValue; use crate::{JsBigInt, JsObject, JsSymbol, JsValue}; use boa_engine::js_string; use boa_string::JsString; @@ -6,7 +5,7 @@ use boa_string::JsString; /// A non-mutable variant of a `JsValue`. /// Represents either a primitive value ([`bool`], [`f64`], [`i32`]) or a reference /// to a heap allocated value ([`JsString`], [`JsSymbol`]). -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub enum JsVariant<'a> { /// `null` - A null value, for when a value doesn't exist. Null, @@ -30,22 +29,6 @@ pub enum JsVariant<'a> { Symbol(&'a JsSymbol), } -impl<'a> From<&'a InnerValue> for JsVariant<'a> { - fn from(value: &'a InnerValue) -> Self { - match value { - InnerValue::Null => JsVariant::Null, - InnerValue::Undefined => JsVariant::Undefined, - InnerValue::Integer32(i) => JsVariant::Integer32(*i), - InnerValue::Float64(d) => JsVariant::Float64(*d), - InnerValue::Boolean(b) => JsVariant::Boolean(*b), - InnerValue::Object(inner) => JsVariant::Object(inner), - InnerValue::String(inner) => JsVariant::String(inner), - InnerValue::Symbol(inner) => JsVariant::Symbol(inner), - InnerValue::BigInt(inner) => JsVariant::BigInt(inner), - } - } -} - impl<'a> From> for JsValue { fn from(value: JsVariant<'a>) -> Self { match value { From d16a546d09972e83470b29235249ee5946740a49 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Fri, 20 Dec 2024 11:52:40 -0800 Subject: [PATCH 04/24] Better tests and fixing a couple of bugs --- core/engine/src/value/inner.rs | 398 +++++++++++++-------------------- 1 file changed, 151 insertions(+), 247 deletions(-) diff --git a/core/engine/src/value/inner.rs b/core/engine/src/value/inner.rs index 10ebeb03905..f28672b7037 100644 --- a/core/engine/src/value/inner.rs +++ b/core/engine/src/value/inner.rs @@ -85,8 +85,7 @@ impl NanBitTag { /// Checks that a value is a valid boolean (either true or false). #[inline] const fn is_bool(value: u64) -> bool { - // We know that if the tag matches false, it is a boolean. - (value & NanBitTag::False as u64) == NanBitTag::False as u64 + value == NanBitTag::False as u64 || value == NanBitTag::True as u64 } /// Checks that a value is a valid float, not a tagged nan boxed value. @@ -103,22 +102,21 @@ impl NanBitTag { } // Or it is not tagged, - match value & NanBitTag::TaggedMask as u64 { - 0x7FF4_0000_0000_0000 => false, - 0x7FF5_0000_0000_0000 => false, - 0x7FF6_0000_0000_0000 => false, - 0x7FF6_0000_0000_0001 => false, - 0x7FF7_0000_0000_0000 => false, - 0x7FF8_0000_0000_0000 => false, - 0x7FF9_0000_0000_0000 => false, - 0x7FFA_0000_0000_0000 => false, - 0x7FFB_0000_0000_0000 => false, - 0x7FFC_0000_0000_0000 => false, - 0x7FFD_0000_0000_0000 => false, - 0x7FFE_0000_0000_0000 => false, - 0x7FFF_0000_0000_0000 => false, - _ => true, - } + !matches!( + value & NanBitTag::TaggedMask as u64, + 0x7FF4_0000_0000_0000 + | 0x7FF5_0000_0000_0000 + | 0x7FF6_0000_0000_0000 + | 0x7FF7_0000_0000_0000 + | 0x7FF8_0000_0000_0000 + | 0x7FF9_0000_0000_0000 + | 0x7FFA_0000_0000_0000 + | 0x7FFB_0000_0000_0000 + | 0x7FFC_0000_0000_0000 + | 0x7FFD_0000_0000_0000 + | 0x7FFE_0000_0000_0000 + | 0x7FFF_0000_0000_0000 + ) } /// Checks that a value is a valid undefined. @@ -304,7 +302,10 @@ impl NanBitTag { unsafe fn drop_pointer(value: u64) { let value_ptr = value & Self::PointerMask as u64; - if value & NanBitTag::Pointer as u64 != 0 || value == NanBitTag::Pointer as u64 { + if value_ptr == 0 + || value & NanBitTag::Pointer as u64 != 0 + || value == NanBitTag::Pointer as u64 + { return; } @@ -338,7 +339,11 @@ impl fmt::Debug for InnerValue { } } -impl Finalize for InnerValue {} +impl Finalize for InnerValue { + fn finalize(&self) { + eprintln!("Finalizing InnerValue: {:?}", self.as_variant()); + } +} #[allow(unsafe_op_in_unsafe_fn)] unsafe impl Trace for InnerValue { @@ -617,270 +622,169 @@ impl Drop for InnerValue { } } +#[cfg(test)] +macro_rules! assert_type { + (@@is $value: ident, $u: literal, $n: literal, $b: literal, $i: literal, $f: literal, $bi: literal, $s: literal, $o: literal, $sy: literal) => { + assert_eq!($u != 0, $value.is_undefined()); + assert_eq!($n != 0, $value.is_null()); + assert_eq!($b != 0, $value.is_bool()); + assert_eq!($i != 0, $value.is_integer32()); + assert_eq!($f != 0, $value.is_float64()); + assert_eq!($bi != 0, $value.is_bigint()); + assert_eq!($s != 0, $value.is_string()); + assert_eq!($o != 0, $value.is_object()); + assert_eq!($sy != 0, $value.is_symbol()); + }; + (@@as $value: ident, $u: literal, $n: literal, $b: literal, $i: literal, $f: literal, $bi: literal, $s: literal, $o: literal, $sy: literal) => { + if $b == 0 { assert_eq!($value.as_bool(), None); } + if $i == 0 { assert_eq!($value.as_integer32(), None); } + if $f == 0 { assert_eq!($value.as_float64(), None); } + if $bi == 0 { assert_eq!($value.as_bigint(), None); } + if $s == 0 { assert_eq!($value.as_string(), None); } + if $o == 0 { assert_eq!($value.as_object(), None); } + if $sy == 0 { assert_eq!($value.as_symbol(), None); } + }; + ($value: ident is undefined) => { + assert_type!(@@is $value, 1, 0, 0, 0, 0, 0, 0, 0, 0); + assert_eq!($value.as_variant(), JsVariant::Undefined); + }; + ($value: ident is null) => { + assert_type!(@@is $value, 0, 1, 0, 0, 0, 0, 0, 0, 0); + assert_eq!($value.as_variant(), JsVariant::Null); + }; + ($value: ident is bool($scalar: ident)) => { + assert_type!(@@is $value, 0, 0, 1, 0, 0, 0, 0, 0, 0); + assert_type!(@@as $value, 0, 0, 1, 0, 0, 0, 0, 0, 0); + assert_eq!(Some($scalar), $value.as_bool()); + assert_eq!($value.as_variant(), JsVariant::Boolean($scalar)); + }; + ($value: ident is integer($scalar: ident)) => { + assert_type!(@@is $value, 0, 0, 0, 1, 0, 0, 0, 0, 0); + assert_type!(@@as $value, 0, 0, 0, 1, 0, 0, 0, 0, 0); + assert_eq!(Some($scalar), $value.as_integer32()); + assert_eq!($value.as_variant(), JsVariant::Integer32($scalar)); + }; + ($value: ident is float($scalar: ident)) => { + assert_type!(@@is $value, 0, 0, 0, 0, 1, 0, 0, 0, 0); + assert_type!(@@as $value, 0, 0, 0, 0, 1, 0, 0, 0, 0); + assert_eq!(Some($scalar), $value.as_float64()); + assert_eq!($value.as_variant(), JsVariant::Float64($scalar)); + }; + ($value: ident is nan) => { + assert_type!(@@is $value, 0, 0, 0, 0, 1, 0, 0, 0, 0); + assert_type!(@@as $value, 0, 0, 0, 0, 1, 0, 0, 0, 0); + assert!($value.as_float64().unwrap().is_nan()); + assert!(matches!($value.as_variant(), JsVariant::Float64(f) if f.is_nan())); + }; + ($value: ident is bigint($scalar: ident)) => { + assert_type!(@@is $value, 0, 0, 0, 0, 0, 1, 0, 0, 0); + assert_type!(@@as $value, 0, 0, 0, 0, 0, 1, 0, 0, 0); + assert_eq!(Some(&$scalar), $value.as_bigint()); + assert_eq!($value.as_variant(), JsVariant::BigInt(&$scalar)); + }; + ($value: ident is object($scalar: ident)) => { + assert_type!(@@is $value, 0, 0, 0, 0, 0, 0, 0, 1, 0); + assert_type!(@@as $value, 0, 0, 0, 0, 0, 0, 0, 1, 0); + assert_eq!(Some(&$scalar), $value.as_object()); + assert_eq!($value.as_variant(), JsVariant::Object(&$scalar)); + }; + ($value: ident is symbol($scalar: ident)) => { + assert_type!(@@is $value, 0, 0, 0, 0, 0, 0, 0, 0, 1); + assert_type!(@@as $value, 0, 0, 0, 0, 0, 0, 0, 0, 1); + assert_eq!(Some(&$scalar), $value.as_symbol()); + assert_eq!($value.as_variant(), JsVariant::Symbol(&$scalar)); + }; + ($value: ident is string($scalar: ident)) => { + assert_type!(@@is $value, 0, 0, 0, 0, 0, 0, 1, 0, 0); + assert_type!(@@as $value, 0, 0, 0, 0, 0, 0, 1, 0, 0); + assert_eq!(Some(&$scalar), $value.as_string()); + assert_eq!($value.as_variant(), JsVariant::String(&$scalar)); + }; +} + #[test] -fn float() { - fn assert_float(f: f64) { - let v = InnerValue::float64(f); +fn null() { + let v = InnerValue::null(); + assert_type!(v is null); +} - assert!(!v.is_undefined()); - assert!(!v.is_null()); - assert!(!v.is_bool()); - assert!(!v.is_integer32()); - assert!(v.is_float64()); - assert!(!v.is_bigint()); - assert!(!v.is_string()); - assert!(!v.is_object()); - assert!(!v.is_symbol()); - - assert_eq!(v.as_bool(), None); - assert_eq!(v.as_integer32(), None); - assert_eq!(v.as_float64(), Some(f)); - assert_eq!(v.as_bigint(), None); - assert_eq!(v.as_object(), None); - assert_eq!(v.as_string(), None); - assert_eq!(v.as_symbol(), None); - } +#[test] +fn undefined() { + let v = InnerValue::undefined(); + assert_type!(v is undefined); +} - assert_float(0.0); - assert_float(-0.0); - assert_float(3.14); - assert_float(-3.14); - assert_float(f64::INFINITY); - assert_float(f64::NEG_INFINITY); +#[test] +fn boolean() { + let v = InnerValue::boolean(true); + assert_type!(v is bool(true)); - // Special care has to be taken for NaN, because NaN != NaN. - let v = InnerValue::float64(f64::NAN); - assert!(!v.is_undefined()); - assert!(!v.is_null()); - assert!(!v.is_bool()); - assert!(!v.is_integer32()); - assert!(v.is_float64()); - assert!(!v.is_bigint()); - assert!(!v.is_string()); - assert!(!v.is_object()); - assert!(!v.is_symbol()); - - assert_eq!(v.as_bool(), None); - assert_eq!(v.as_integer32(), None); - assert!(v.as_float64().unwrap().is_nan()); - assert_eq!(v.as_bigint(), None); - assert_eq!(v.as_object(), None); - assert_eq!(v.as_string(), None); - assert_eq!(v.as_symbol(), None); + let v = InnerValue::boolean(false); + assert_type!(v is bool(false)); } #[test] fn integer() { - let int = 42; - let v = InnerValue::integer32(int); - assert!(!v.is_undefined()); - assert!(!v.is_null()); - assert!(!v.is_bool()); - assert!(v.is_integer32()); - assert!(!v.is_float64()); - assert!(!v.is_bigint()); - assert!(!v.is_string()); - assert!(!v.is_object()); - assert!(!v.is_symbol()); - - assert_eq!(v.as_bool(), None); - assert_eq!(v.as_integer32(), Some(int)); - assert_eq!(v.as_float64(), None); - assert_eq!(v.as_bigint(), None); - assert_eq!(v.as_object(), None); - assert_eq!(v.as_string(), None); - assert_eq!(v.as_symbol(), None); - - let int = -42; - let v = InnerValue::integer32(int); - assert!(!v.is_undefined()); - assert!(!v.is_null()); - assert!(!v.is_bool()); - assert!(v.is_integer32()); - assert!(!v.is_float64()); - assert!(!v.is_bigint()); - assert!(!v.is_string()); - assert!(!v.is_object()); - assert!(!v.is_symbol()); - - assert_eq!(v.as_bool(), None); - assert_eq!(v.as_integer32(), Some(int)); - assert_eq!(v.as_float64(), None); - assert_eq!(v.as_bigint(), None); - assert_eq!(v.as_object(), None); - assert_eq!(v.as_string(), None); - assert_eq!(v.as_symbol(), None); - - let int = 0; - let v = InnerValue::integer32(int); - assert!(!v.is_undefined()); - assert!(!v.is_null()); - assert!(!v.is_bool()); - assert!(v.is_integer32()); - assert!(!v.is_float64()); - assert!(!v.is_bigint()); - assert!(!v.is_string()); - assert!(!v.is_object()); - assert!(!v.is_symbol()); - - assert_eq!(v.as_bool(), None); - assert_eq!(v.as_integer32(), Some(int)); - assert_eq!(v.as_float64(), None); - assert_eq!(v.as_bigint(), None); - assert_eq!(v.as_object(), None); - assert_eq!(v.as_string(), None); - assert_eq!(v.as_symbol(), None); + fn assert_integer(i: i32) { + let v = InnerValue::integer32(i); + assert_type!(v is integer(i)); + } + + assert_integer(0); + assert_integer(1); + assert_integer(-1); + assert_integer(42); + assert_integer(-42); + assert_integer(i32::MAX); + assert_integer(i32::MIN); } #[test] -fn boolean() { - let v = InnerValue::boolean(true); - assert!(!v.is_undefined()); - assert!(!v.is_null()); - assert!(v.is_bool()); - assert!(!v.is_integer32()); - assert!(!v.is_float64()); - assert!(!v.is_bigint()); - assert!(!v.is_string()); - assert!(!v.is_object()); - assert!(!v.is_symbol()); - - assert_eq!(v.as_bool(), Some(true)); - assert_eq!(v.as_integer32(), None); - assert_eq!(v.as_float64(), None); - assert_eq!(v.as_bigint(), None); - assert_eq!(v.as_object(), None); - assert_eq!(v.as_string(), None); - assert_eq!(v.as_symbol(), None); +fn float() { + fn assert_float(f: f64) { + let v = InnerValue::float64(f); + assert_type!(v is float(f)); + } - let v = InnerValue::boolean(false); - assert!(!v.is_undefined()); - assert!(!v.is_null()); - assert!(v.is_bool()); - assert!(!v.is_integer32()); - assert!(!v.is_float64()); - assert!(!v.is_bigint()); - assert!(!v.is_string()); - assert!(!v.is_object()); - assert!(!v.is_symbol()); - - assert_eq!(v.as_bool(), Some(false)); - assert_eq!(v.as_integer32(), None); - assert_eq!(v.as_float64(), None); - assert_eq!(v.as_bigint(), None); - assert_eq!(v.as_object(), None); - assert_eq!(v.as_string(), None); - assert_eq!(v.as_symbol(), None); + assert_float(0.0); + assert_float(-0.0); + assert_float(0.1 + 0.2); + assert_float(-42.123); + assert_float(f64::INFINITY); + assert_float(f64::NEG_INFINITY); + + let nan = InnerValue::float64(f64::NAN); + assert_type!(nan is nan); } #[test] fn bigint() { let bigint = JsBigInt::from(42); let v = InnerValue::bigint(bigint.clone()); - assert!(!v.is_undefined()); - assert!(!v.is_null()); - assert!(!v.is_bool()); - assert!(!v.is_integer32()); - assert!(!v.is_float64()); - assert!(v.is_bigint()); - assert!(!v.is_string()); - assert!(!v.is_object()); - assert!(!v.is_symbol()); - - assert_eq!(v.as_bool(), None); - assert_eq!(v.as_integer32(), None); - assert_eq!(v.as_float64(), None); - assert_eq!(v.as_bigint(), Some(&bigint)); - assert_eq!(v.as_object(), None); - assert_eq!(v.as_string(), None); - assert_eq!(v.as_symbol(), None); + assert_type!(v is bigint(bigint)); } #[test] fn object() { let object = JsObject::with_null_proto(); let v = InnerValue::object(object.clone()); - assert!(!v.is_undefined()); - assert!(!v.is_null()); - assert!(!v.is_bool()); - assert!(!v.is_integer32()); - assert!(!v.is_float64()); - assert!(!v.is_bigint()); - assert!(!v.is_string()); - assert!(v.is_object()); - assert!(!v.is_symbol()); - - assert_eq!(v.as_bool(), None); - assert_eq!(v.as_integer32(), None); - assert_eq!(v.as_float64(), None); - assert_eq!(v.as_bigint(), None); - assert_eq!(v.as_object(), Some(&object)); - assert_eq!(v.as_string(), None); - assert_eq!(v.as_symbol(), None); + assert_type!(v is object(object)); } #[test] fn string() { let str = crate::js_string!("Hello World"); let v = InnerValue::string(str.clone()); - assert!(!v.is_undefined()); - assert!(!v.is_null()); - assert!(!v.is_bool()); - assert!(!v.is_integer32()); - assert!(!v.is_float64()); - assert!(!v.is_bigint()); - assert!(v.is_string()); - assert!(!v.is_object()); - assert!(!v.is_symbol()); - - assert_eq!(v.as_bool(), None); - assert_eq!(v.as_integer32(), None); - assert_eq!(v.as_float64(), None); - assert_eq!(v.as_bigint(), None); - assert_eq!(v.as_object(), None); - assert_eq!(v.as_string(), Some(&str)); - assert_eq!(v.as_symbol(), None); + assert_type!(v is string(str)); } #[test] fn symbol() { let sym = JsSymbol::new(Some(JsString::from("Hello World"))).unwrap(); let v = InnerValue::symbol(sym.clone()); - assert!(!v.is_undefined()); - assert!(!v.is_null()); - assert!(!v.is_bool()); - assert!(!v.is_integer32()); - assert!(!v.is_float64()); - assert!(!v.is_bigint()); - assert!(!v.is_string()); - assert!(!v.is_object()); - assert!(v.is_symbol()); - - assert_eq!(v.as_bool(), None); - assert_eq!(v.as_integer32(), None); - assert_eq!(v.as_float64(), None); - assert_eq!(v.as_bigint(), None); - assert_eq!(v.as_object(), None); - assert_eq!(v.as_string(), None); - assert_eq!(v.as_symbol(), Some(&sym)); + assert_type!(v is symbol(sym)); let sym = JsSymbol::new(None).unwrap(); let v = InnerValue::symbol(sym.clone()); - assert!(!v.is_undefined()); - assert!(!v.is_null()); - assert!(!v.is_bool()); - assert!(!v.is_integer32()); - assert!(!v.is_float64()); - assert!(!v.is_bigint()); - assert!(!v.is_string()); - assert!(!v.is_object()); - assert!(v.is_symbol()); - - assert_eq!(v.as_bool(), None); - assert_eq!(v.as_integer32(), None); - assert_eq!(v.as_float64(), None); - assert_eq!(v.as_bigint(), None); - assert_eq!(v.as_object(), None); - assert_eq!(v.as_string(), None); - assert_eq!(v.as_symbol(), Some(&sym)); + assert_type!(v is symbol(sym)); } From e2496c33d1605ff8a441e3c15d309036138b1a37 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Fri, 20 Dec 2024 15:07:11 -0800 Subject: [PATCH 05/24] Rollback changes outside of core/engine/src/value/... --- core/engine/src/builtins/symbol/mod.rs | 1 - core/engine/src/value/mod.rs | 8 ++++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/core/engine/src/builtins/symbol/mod.rs b/core/engine/src/builtins/symbol/mod.rs index 06153c5e5cc..43e3651c2fb 100644 --- a/core/engine/src/builtins/symbol/mod.rs +++ b/core/engine/src/builtins/symbol/mod.rs @@ -236,7 +236,6 @@ impl Symbol { fn this_symbol_value(value: &JsValue) -> JsResult { value .as_symbol() - .cloned() .or_else(|| { value .as_object() diff --git a/core/engine/src/value/mod.rs b/core/engine/src/value/mod.rs index b4d5b7b1e26..8363ff0fdf5 100644 --- a/core/engine/src/value/mod.rs +++ b/core/engine/src/value/mod.rs @@ -257,8 +257,12 @@ impl JsValue { /// Returns the symbol if the value is a symbol, otherwise `None`. #[inline] #[must_use] - pub fn as_symbol(&self) -> Option<&JsSymbol> { - self.0.as_symbol() + pub fn as_symbol(&self) -> Option { + if let Some(symbol) = self.0.as_symbol() { + Some(symbol.clone()) + } else { + None + } } /// Returns true if the value is undefined. From 683444e7a5c1110d08c24f825fca0f1803dab4c1 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Fri, 20 Dec 2024 15:21:26 -0800 Subject: [PATCH 06/24] Fix Drop::drop and clippies --- core/engine/src/value/inner.rs | 43 +++++++++++++--------------------- core/engine/src/value/mod.rs | 14 ++++------- 2 files changed, 21 insertions(+), 36 deletions(-) diff --git a/core/engine/src/value/inner.rs b/core/engine/src/value/inner.rs index f28672b7037..69a2d8393aa 100644 --- a/core/engine/src/value/inner.rs +++ b/core/engine/src/value/inner.rs @@ -94,7 +94,7 @@ impl NanBitTag { // Either it is a constant float value, if value == f64::INFINITY.to_bits() || value == f64::NEG_INFINITY.to_bits() - // or it is exactly a NaN value, which is the same as the BigInt tag. + // or it is exactly a NaN value, which is the same as the `BigInt` tag. // Reminder that pointers cannot be null, so this is safe. || value == NanBitTag::Pointer as u64 { @@ -137,7 +137,7 @@ impl NanBitTag { value & NanBitTag::Integer32 as u64 == NanBitTag::Integer32 as u64 } - /// Checks that a value is a valid BigInt. + /// Checks that a value is a valid `BigInt`. #[inline] const fn is_bigint(value: u64) -> bool { (value & NanBitTag::TaggedMask as u64 == NanBitTag::Pointer as u64) @@ -296,27 +296,6 @@ impl NanBitTag { // Simply cast for bits. Self::Pointer as u64 | Self::String as u64 | value } - - /// Drops a value if it is a pointer, otherwise do nothing. - #[inline] - unsafe fn drop_pointer(value: u64) { - let value_ptr = value & Self::PointerMask as u64; - - if value_ptr == 0 - || value & NanBitTag::Pointer as u64 != 0 - || value == NanBitTag::Pointer as u64 - { - return; - } - - match value & 0x3 { - 0 => drop(unsafe { Box::from_raw(value_ptr as *mut JsBigInt) }), - 1 => drop(unsafe { Box::from_raw(value_ptr as *mut JsObject) }), - 2 => drop(unsafe { Box::from_raw(value_ptr as *mut JsSymbol) }), - 3 => drop(unsafe { Box::from_raw(value_ptr as *mut JsString) }), - _ => unreachable!(), - } - } } /// A NaN-boxed `[super::JsValue]`'s inner. @@ -341,14 +320,16 @@ impl fmt::Debug for InnerValue { impl Finalize for InnerValue { fn finalize(&self) { - eprintln!("Finalizing InnerValue: {:?}", self.as_variant()); + if let Some(o) = self.as_object() { + o.finalize(); + } } } #[allow(unsafe_op_in_unsafe_fn)] unsafe impl Trace for InnerValue { custom_trace! {this, mark, { - if let JsVariant::Object(o) = this.as_variant() { + if let Some(o) = this.as_object() { mark(o); } }} @@ -615,9 +596,17 @@ impl InnerValue { impl Drop for InnerValue { fn drop(&mut self) { + let maybe_ptr = self.0 & NanBitTag::PointerMask as u64; + // Drop the pointer if it is a pointer. - unsafe { - NanBitTag::drop_pointer(self.0); + if self.is_object() { + drop(unsafe { Box::from_raw(maybe_ptr as *mut JsObject) }); + } else if self.is_bigint() { + drop(unsafe { Box::from_raw(maybe_ptr as *mut JsBigInt) }); + } else if self.is_symbol() { + drop(unsafe { Box::from_raw(maybe_ptr as *mut JsSymbol) }); + } else if self.is_string() { + drop(unsafe { Box::from_raw(maybe_ptr as *mut JsString) }); } } } diff --git a/core/engine/src/value/mod.rs b/core/engine/src/value/mod.rs index 8363ff0fdf5..9e976b168ff 100644 --- a/core/engine/src/value/mod.rs +++ b/core/engine/src/value/mod.rs @@ -173,7 +173,7 @@ impl JsValue { #[inline] #[must_use] pub fn is_callable(&self) -> bool { - self.as_object().map_or(false, |obj| obj.is_callable()) + self.as_object().map_or(false, JsObject::is_callable) } /// Returns the callable value if the value is callable, otherwise `None`. @@ -197,7 +197,7 @@ impl JsValue { #[inline] #[must_use] pub fn is_constructor(&self) -> bool { - self.as_object().map_or(false, |obj| obj.is_constructor()) + self.as_object().map_or(false, JsObject::is_constructor) } /// Returns the constructor if the value is a constructor, otherwise `None`. @@ -258,11 +258,7 @@ impl JsValue { #[inline] #[must_use] pub fn as_symbol(&self) -> Option { - if let Some(symbol) = self.0.as_symbol() { - Some(symbol.clone()) - } else { - None - } + self.0.as_symbol().cloned() } /// Returns true if the value is undefined. @@ -322,7 +318,7 @@ impl JsValue { #[must_use] pub fn as_number(&self) -> Option { if let Some(i) = self.as_i32() { - Some(i as f64) + Some(f64::from(i)) } else { self.0.as_float64() } @@ -459,7 +455,7 @@ impl JsValue { JsVariant::Undefined => Err(JsNativeError::typ() .with_message("cannot convert undefined to a BigInt") .into()), - JsVariant::String(ref string) => JsBigInt::from_js_string(string).map_or_else( + JsVariant::String(string) => JsBigInt::from_js_string(string).map_or_else( || { Err(JsNativeError::syntax() .with_message(format!( From 9d1ac9f62996db32e5bd328d71828d944ffc1de4 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Fri, 20 Dec 2024 15:26:16 -0800 Subject: [PATCH 07/24] Move MSRV to 1.83 --- .github/workflows/rust.yml | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index b7689fb3299..4ed54ee3a21 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -10,7 +10,7 @@ on: - main - releases/** merge_group: - types: [checks_requested] + types: [ checks_requested ] concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} @@ -34,7 +34,7 @@ jobs: with: # TODO: There seems to be an issue with the 1.83.0 toolchain and tarpaulin. # See: https://github.com/xd009642/tarpaulin/issues/1642 - toolchain: 1.82.0 + toolchain: 1.83.0 - uses: Swatinem/rust-cache@v2 with: diff --git a/Cargo.toml b/Cargo.toml index fc619a70f4d..8887c5d8698 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ exclude = [ [workspace.package] edition = "2021" version = "0.20.0" -rust-version = "1.82.0" +rust-version = "1.83.0" authors = ["boa-dev"] repository = "https://github.com/boa-dev/boa" license = "Unlicense OR MIT" From 167d6340c07c3dc29a63113ef0d29a757739942a Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Sat, 21 Dec 2024 13:04:53 -0800 Subject: [PATCH 08/24] Move MSRV back to 1.82 and impl missing const fn --- .github/workflows/rust.yml | 2 +- Cargo.toml | 2 +- core/engine/src/value/inner.rs | 43 +++++++++++++++++++++++++++------- 3 files changed, 36 insertions(+), 11 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 4ed54ee3a21..376df4fd7a3 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -34,7 +34,7 @@ jobs: with: # TODO: There seems to be an issue with the 1.83.0 toolchain and tarpaulin. # See: https://github.com/xd009642/tarpaulin/issues/1642 - toolchain: 1.83.0 + toolchain: 1.82.0 - uses: Swatinem/rust-cache@v2 with: diff --git a/Cargo.toml b/Cargo.toml index 8887c5d8698..fc619a70f4d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ exclude = [ [workspace.package] edition = "2021" version = "0.20.0" -rust-version = "1.83.0" +rust-version = "1.82.0" authors = ["boa-dev"] repository = "https://github.com/boa-dev/boa" license = "Unlicense OR MIT" diff --git a/core/engine/src/value/inner.rs b/core/engine/src/value/inner.rs index 69a2d8393aa..1b20b97f560 100644 --- a/core/engine/src/value/inner.rs +++ b/core/engine/src/value/inner.rs @@ -42,6 +42,26 @@ use boa_string::JsString; use core::fmt; use static_assertions::const_assert; +/// Transform an `u64` into `f64`, by its bytes. This is necessary for +/// keeping the MSRV at 1.82, as `f64::from_bits` is not const until +/// 1.83. +const fn f64_from_bits(bits: u64) -> f64 { + unsafe { std::mem::transmute(bits) } +} + +/// Transform a `f64` into `u64`, by its bytes. This is necessary for +/// keeping the MSRV at 1.82, as `f64::to_bits` is not const until +/// 1.83. +const fn f64_to_bits(bits: f64) -> u64 { + unsafe { std::mem::transmute(bits) } +} + +/// Check that a float is a NaN. This is necessary for keeping the MSRV +/// at 1.82, as `f64::is_nan` is not const until 1.53. +const fn f64_is_nan(f: f64) -> bool { + f != f +} + // We cannot NaN-box pointers larger than 64 bits. const_assert!(size_of::() <= size_of::()); @@ -79,7 +99,12 @@ enum NanBitTag { // Verify that all representations of NanBitTag ARE NAN, but don't match static NAN. // The only exception to this rule is BigInt, which assumes that the pointer is // non-null. The static f64::NAN is equal to BigInt. -const_assert!(f64::from_bits(NanBitTag::Undefined as u64).is_nan()); +const_assert!(f64_is_nan(f64_from_bits(NanBitTag::Undefined as u64))); +const_assert!(f64_is_nan(f64_from_bits(NanBitTag::Null as u64))); +const_assert!(f64_is_nan(f64_from_bits(NanBitTag::False as u64))); +const_assert!(f64_is_nan(f64_from_bits(NanBitTag::True as u64))); +const_assert!(f64_is_nan(f64_from_bits(NanBitTag::Integer32 as u64))); +const_assert!(f64_is_nan(f64_from_bits(NanBitTag::Pointer as u64))); impl NanBitTag { /// Checks that a value is a valid boolean (either true or false). @@ -92,11 +117,11 @@ impl NanBitTag { #[inline] const fn is_float(value: u64) -> bool { // Either it is a constant float value, - if value == f64::INFINITY.to_bits() - || value == f64::NEG_INFINITY.to_bits() - // or it is exactly a NaN value, which is the same as the `BigInt` tag. + if value == f64_to_bits(f64::INFINITY) + || value == f64_to_bits(f64::NEG_INFINITY) + // or it is exactly a NaN value, which is the same as the `Pointer` tag. // Reminder that pointers cannot be null, so this is safe. - || value == NanBitTag::Pointer as u64 + || value == f64_to_bits(f64::NAN) { return true; } @@ -169,11 +194,11 @@ impl NanBitTag { /// Returns a tagged u64 of a 64-bits float. #[inline] const fn tag_f64(value: f64) -> u64 { - if value.is_nan() { + if f64_is_nan(value) { // Reduce any NAN to a canonical NAN representation. - f64::NAN.to_bits() + f64_to_bits(f64::NAN) } else { - value.to_bits() + f64_to_bits(value) } } @@ -488,7 +513,7 @@ impl InnerValue { #[inline] pub(super) const fn as_float64(&self) -> Option { if self.is_float64() { - Some(f64::from_bits(self.0)) + Some(f64_from_bits(self.0)) } else { None } From 2a6885536427791a5b2b70959a4a086b064e5434 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Sat, 21 Dec 2024 13:09:30 -0800 Subject: [PATCH 09/24] Fix a few clippies and ignore the one thats wrong --- core/engine/src/value/inner.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/engine/src/value/inner.rs b/core/engine/src/value/inner.rs index 1b20b97f560..9fad7d980cf 100644 --- a/core/engine/src/value/inner.rs +++ b/core/engine/src/value/inner.rs @@ -52,12 +52,15 @@ const fn f64_from_bits(bits: u64) -> f64 { /// Transform a `f64` into `u64`, by its bytes. This is necessary for /// keeping the MSRV at 1.82, as `f64::to_bits` is not const until /// 1.83. +#[inline] const fn f64_to_bits(bits: f64) -> u64 { unsafe { std::mem::transmute(bits) } } -/// Check that a float is a NaN. This is necessary for keeping the MSRV +/// Check that a float is a `NaN`. This is necessary for keeping the MSRV /// at 1.82, as `f64::is_nan` is not const until 1.53. +#[inline] +#[allow(clippy::eq_op, clippy::float_cmp)] const fn f64_is_nan(f: f64) -> bool { f != f } From c7cd8b0e825d3ae1f7c3de466ff619ebb425bfec Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Mon, 23 Dec 2024 11:15:52 -0800 Subject: [PATCH 10/24] WIP --- core/engine/src/builtins/array/tests.rs | 13 +++++ core/engine/src/value/conversions/mod.rs | 9 ++- .../src/value/conversions/try_from_js.rs | 5 +- core/engine/src/value/inner.rs | 57 ++++++++++++++++--- core/engine/src/value/mod.rs | 11 +++- core/engine/src/value/operations.rs | 9 ++- 6 files changed, 86 insertions(+), 18 deletions(-) diff --git a/core/engine/src/builtins/array/tests.rs b/core/engine/src/builtins/array/tests.rs index 738bd93512f..eaaf7d9d294 100644 --- a/core/engine/src/builtins/array/tests.rs +++ b/core/engine/src/builtins/array/tests.rs @@ -953,3 +953,16 @@ fn array_sort() { "#}), ]); } + +#[test] +fn array_of_neg_zero() { + run_test_actions([ + TestAction::assert_context(|_| { + eprintln!("test start..."); + true + }), + TestAction::run("let arr = [-0, -0, -0, -0];"), + // Assert the parity of all items of the list. + TestAction::assert("arr.every(x => (1/x) === -Infinity)"), + ]); +} diff --git a/core/engine/src/value/conversions/mod.rs b/core/engine/src/value/conversions/mod.rs index 2d81fdd159c..ff823ea0539 100644 --- a/core/engine/src/value/conversions/mod.rs +++ b/core/engine/src/value/conversions/mod.rs @@ -54,7 +54,8 @@ impl From for JsValue { fn from(value: f32) -> Self { let _timer = Profiler::global().start_event("From", "value"); - JsValue::from(f64::from(value)) + eprintln!("from(f32)... {}", value); + Self::rational(f64::from(value)) } } @@ -63,7 +64,8 @@ impl From for JsValue { fn from(value: f64) -> Self { let _timer = Profiler::global().start_event("From", "value"); - Self::from_inner(InnerValue::float64(value)) + eprintln!("from(f64)... {}", value); + Self::rational(value) } } @@ -76,9 +78,10 @@ macro_rules! impl_from_integer { fn from(value: $type_) -> Self { let _timer = Profiler::global().start_event(concat!("From<", stringify!($type_), ">"), "value"); + eprintln!("from({})... {}", stringify!($type_), value); i32::try_from(value) .map_or_else( - |_| Self::from(value as f64), + |_| Self::rational(value as f64), |value| Self::from_inner(InnerValue::integer32(value)), ) } diff --git a/core/engine/src/value/conversions/try_from_js.rs b/core/engine/src/value/conversions/try_from_js.rs index 4a5214e088e..9e8499765c8 100644 --- a/core/engine/src/value/conversions/try_from_js.rs +++ b/core/engine/src/value/conversions/try_from_js.rs @@ -161,7 +161,9 @@ impl TryFromJs for JsValue { impl TryFromJs for f64 { fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult { - if let Some(f) = value.as_number() { + if let Some(i) = value.0.as_integer32() { + Ok(i as f64) + } else if let Some(f) = value.0.as_float64() { Ok(f) } else { Err(JsNativeError::typ() @@ -187,6 +189,7 @@ macro_rules! impl_try_from_js_integer { $( impl TryFromJs for $type { fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult { + eprintln!("from_js... {:?} i32: {:?} number: {:?}", value, value.as_i32(), value.as_number()); if let Some(i) = value.as_i32() { i.try_into().map_err(|e| { JsNativeError::typ() diff --git a/core/engine/src/value/inner.rs b/core/engine/src/value/inner.rs index 9fad7d980cf..b23b564cad6 100644 --- a/core/engine/src/value/inner.rs +++ b/core/engine/src/value/inner.rs @@ -87,6 +87,8 @@ enum NanBitTag { True = 0x7FF6_0000_0000_0001, Integer32 = 0x7FF7_0000_0000_0000, + NegativeZero = 0x8000_0000_0000_0000, + /// A generic pointer. Pointer = 0x7FF8_0000_0000_0000, BigInt = 0x0000_0000_0000_0000, @@ -125,6 +127,9 @@ impl NanBitTag { // or it is exactly a NaN value, which is the same as the `Pointer` tag. // Reminder that pointers cannot be null, so this is safe. || value == f64_to_bits(f64::NAN) + // or it is negative/positive zero. + || value == NanBitTag::NegativeZero as u64 + || value == 0 { return true; } @@ -327,8 +332,7 @@ impl NanBitTag { } /// A NaN-boxed `[super::JsValue]`'s inner. -#[derive(PartialEq)] -pub(super) struct InnerValue(u64); +pub(super) struct InnerValue(pub u64); impl fmt::Debug for InnerValue { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -365,12 +369,16 @@ unsafe impl Trace for InnerValue { impl Clone for InnerValue { fn clone(&self) -> Self { - match self.as_variant() { - JsVariant::BigInt(n) => Self::bigint(n.clone()), - JsVariant::Object(n) => Self::object(n.clone()), - JsVariant::Symbol(n) => Self::symbol(n.clone()), - JsVariant::String(n) => Self::string(n.clone()), - _ => Self(self.0), + if let Some(o) = self.as_object() { + Self::object(o.clone()) + } else if let Some(b) = self.as_bigint() { + Self::bigint(b.clone()) + } else if let Some(s) = self.as_symbol() { + Self::symbol(s.clone()) + } else if let Some(s) = self.as_string() { + Self::string(s.clone()) + } else { + Self(self.0) } } } @@ -685,7 +693,27 @@ macro_rules! assert_type { assert_type!(@@is $value, 0, 0, 0, 0, 1, 0, 0, 0, 0); assert_type!(@@as $value, 0, 0, 0, 0, 1, 0, 0, 0, 0); assert_eq!(Some($scalar), $value.as_float64()); + // Verify parity. + assert_eq!(Some(1.0 / $scalar), $value.as_float64().map(|f| 1.0 / f)); assert_eq!($value.as_variant(), JsVariant::Float64($scalar)); + + // Verify that the clone is still the same. + let new_value = $value.clone(); + + assert_eq!(Some($scalar), new_value.as_float64()); + assert_eq!($value.as_float64(), new_value.as_float64()); + // Verify parity. + assert_eq!(Some(1.0 / $scalar), new_value.as_float64().map(|f| 1.0 / f)); + assert_eq!(new_value.as_variant(), JsVariant::Float64($scalar)); + + let JsVariant::Float64(new_scalar) = new_value.as_variant() else { + panic!("Expected Float64, got {:?}", new_value.as_variant()); + }; + assert_eq!(Some(new_scalar), new_value.as_float64()); + assert_eq!($value.as_float64(), new_value.as_float64()); + // Verify parity. + assert_eq!(Some(1.0 / new_scalar), new_value.as_float64().map(|f| 1.0 / f)); + assert_eq!(new_value.as_variant(), JsVariant::Float64(new_scalar)); }; ($value: ident is nan) => { assert_type!(@@is $value, 0, 0, 0, 0, 1, 0, 0, 0, 0); @@ -754,6 +782,8 @@ fn integer() { assert_integer(-42); assert_integer(i32::MAX); assert_integer(i32::MIN); + assert_integer(i32::MAX - 1); + assert_integer(i32::MIN + 1); } #[test] @@ -770,6 +800,17 @@ fn float() { assert_float(f64::INFINITY); assert_float(f64::NEG_INFINITY); + // Some edge cases around zeroes. + let neg_zero = InnerValue::float64(-0.0); + assert!(neg_zero.as_float64().unwrap().is_sign_negative()); + assert_eq!(neg_zero.as_float64().unwrap(), 0.0); + + let pos_zero = InnerValue::float64(0.0); + assert!(!pos_zero.as_float64().unwrap().is_sign_negative()); + assert_eq!(pos_zero.as_float64().unwrap(), 0.0); + + assert_eq!(pos_zero.as_float64(), neg_zero.as_float64()); + let nan = InnerValue::float64(f64::NAN); assert_type!(nan is nan); } diff --git a/core/engine/src/value/mod.rs b/core/engine/src/value/mod.rs index 9e976b168ff..e744b97185c 100644 --- a/core/engine/src/value/mod.rs +++ b/core/engine/src/value/mod.rs @@ -82,11 +82,14 @@ pub struct JsValue(inner::InnerValue); impl JsValue { /// Create a new [`JsValue`] from an inner value. + #[inline] const fn from_inner(inner: inner::InnerValue) -> Self { Self(inner) } /// Create a new [`JsValue`]. + #[inline] + #[must_use] pub fn new(value: T) -> Self where T: Into, @@ -137,10 +140,12 @@ impl JsValue { } /// Creates a new number from a float. - #[inline] + // #[inline] #[must_use] - pub const fn rational(rational: f64) -> Self { - Self::from_inner(inner::InnerValue::float64(rational)) + pub fn rational(rational: f64) -> Self { + let inner = inner::InnerValue::float64(rational); + eprintln!("rational: {} = {:?} .. 0x{:x}", rational, inner, inner.0); + Self::from_inner(inner) } /// Returns true if the value is an object. diff --git a/core/engine/src/value/operations.rs b/core/engine/src/value/operations.rs index c82bab759a3..00154433c10 100644 --- a/core/engine/src/value/operations.rs +++ b/core/engine/src/value/operations.rs @@ -489,12 +489,15 @@ impl JsValue { .map_or(f64::NAN, std::ops::Neg::neg), ), JsVariant::String(str) => Self::new(-str.to_number()), - JsVariant::Float64(num) => Self::new(-num), + JsVariant::Float64(num) => { + eprintln!("negating float: {}", num); + Self::new(-num) + } JsVariant::Integer32(0) | JsVariant::Boolean(false) | JsVariant::Null => { - Self::new(-f64::from(0)) + Self::new(-0.0) } JsVariant::Integer32(num) => Self::new(-num), - JsVariant::Boolean(true) => Self::new(-f64::from(1)), + JsVariant::Boolean(true) => Self::new(-1), JsVariant::BigInt(x) => Self::new(JsBigInt::neg(x)), }) } From 52fc10342f5b5af8f256d9f2bf60e11a177e6053 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Wed, 25 Dec 2024 21:19:38 -0500 Subject: [PATCH 11/24] Duh! --- .github/workflows/rust.yml | 2 +- core/engine/src/value/inner.rs | 4 +++- core/engine/src/value/mod.rs | 8 ++++---- core/engine/src/value/operations.rs | 6 ++---- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 376df4fd7a3..b7689fb3299 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -10,7 +10,7 @@ on: - main - releases/** merge_group: - types: [ checks_requested ] + types: [checks_requested] concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} diff --git a/core/engine/src/value/inner.rs b/core/engine/src/value/inner.rs index b23b564cad6..ba4836c3523 100644 --- a/core/engine/src/value/inner.rs +++ b/core/engine/src/value/inner.rs @@ -625,7 +625,9 @@ impl InnerValue { } else if let Some(str) = self.as_string() { JsVariant::String(str) } else { - unreachable!() + // This will optimize away this branch, resulting in UB if it is + // actually used, but it won't. It shouldn't. Really. + unsafe { std::hint::unreachable_unchecked() } } } } diff --git a/core/engine/src/value/mod.rs b/core/engine/src/value/mod.rs index e744b97185c..826ce38cd7f 100644 --- a/core/engine/src/value/mod.rs +++ b/core/engine/src/value/mod.rs @@ -322,10 +322,10 @@ impl JsValue { #[inline] #[must_use] pub fn as_number(&self) -> Option { - if let Some(i) = self.as_i32() { - Some(f64::from(i)) - } else { - self.0.as_float64() + match self.variant() { + JsVariant::Integer32(i) => Some(f64::from(i)), + JsVariant::Float64(f) => Some(f), + _ => None, } } diff --git a/core/engine/src/value/operations.rs b/core/engine/src/value/operations.rs index 00154433c10..08cb319930a 100644 --- a/core/engine/src/value/operations.rs +++ b/core/engine/src/value/operations.rs @@ -482,6 +482,7 @@ impl JsValue { /// Returns the negated value. pub fn neg(&self, context: &mut Context) -> JsResult { + eprintln!("neg({:?})", self.variant()); Ok(match self.variant() { JsVariant::Symbol(_) | JsVariant::Undefined => Self::new(f64::NAN), JsVariant::Object(_) => Self::new( @@ -489,10 +490,7 @@ impl JsValue { .map_or(f64::NAN, std::ops::Neg::neg), ), JsVariant::String(str) => Self::new(-str.to_number()), - JsVariant::Float64(num) => { - eprintln!("negating float: {}", num); - Self::new(-num) - } + JsVariant::Float64(num) => Self::new(-num), JsVariant::Integer32(0) | JsVariant::Boolean(false) | JsVariant::Null => { Self::new(-0.0) } From 345cd69293d8346799219b0a41ec4b62d2f12d7e Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Wed, 25 Dec 2024 21:30:01 -0500 Subject: [PATCH 12/24] Oops --- core/engine/src/value/conversions/mod.rs | 3 --- core/engine/src/value/conversions/try_from_js.rs | 3 +-- core/engine/src/value/mod.rs | 4 +--- core/engine/src/value/operations.rs | 1 - 4 files changed, 2 insertions(+), 9 deletions(-) diff --git a/core/engine/src/value/conversions/mod.rs b/core/engine/src/value/conversions/mod.rs index ff823ea0539..982e2adf9a2 100644 --- a/core/engine/src/value/conversions/mod.rs +++ b/core/engine/src/value/conversions/mod.rs @@ -54,7 +54,6 @@ impl From for JsValue { fn from(value: f32) -> Self { let _timer = Profiler::global().start_event("From", "value"); - eprintln!("from(f32)... {}", value); Self::rational(f64::from(value)) } } @@ -64,7 +63,6 @@ impl From for JsValue { fn from(value: f64) -> Self { let _timer = Profiler::global().start_event("From", "value"); - eprintln!("from(f64)... {}", value); Self::rational(value) } } @@ -78,7 +76,6 @@ macro_rules! impl_from_integer { fn from(value: $type_) -> Self { let _timer = Profiler::global().start_event(concat!("From<", stringify!($type_), ">"), "value"); - eprintln!("from({})... {}", stringify!($type_), value); i32::try_from(value) .map_or_else( |_| Self::rational(value as f64), diff --git a/core/engine/src/value/conversions/try_from_js.rs b/core/engine/src/value/conversions/try_from_js.rs index 9e8499765c8..77533f6a9f8 100644 --- a/core/engine/src/value/conversions/try_from_js.rs +++ b/core/engine/src/value/conversions/try_from_js.rs @@ -162,7 +162,7 @@ impl TryFromJs for JsValue { impl TryFromJs for f64 { fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult { if let Some(i) = value.0.as_integer32() { - Ok(i as f64) + Ok(f64::from(i)) } else if let Some(f) = value.0.as_float64() { Ok(f) } else { @@ -189,7 +189,6 @@ macro_rules! impl_try_from_js_integer { $( impl TryFromJs for $type { fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult { - eprintln!("from_js... {:?} i32: {:?} number: {:?}", value, value.as_i32(), value.as_number()); if let Some(i) = value.as_i32() { i.try_into().map_err(|e| { JsNativeError::typ() diff --git a/core/engine/src/value/mod.rs b/core/engine/src/value/mod.rs index 826ce38cd7f..251c79d61cf 100644 --- a/core/engine/src/value/mod.rs +++ b/core/engine/src/value/mod.rs @@ -143,9 +143,7 @@ impl JsValue { // #[inline] #[must_use] pub fn rational(rational: f64) -> Self { - let inner = inner::InnerValue::float64(rational); - eprintln!("rational: {} = {:?} .. 0x{:x}", rational, inner, inner.0); - Self::from_inner(inner) + Self::from_inner(inner::InnerValue::float64(rational)) } /// Returns true if the value is an object. diff --git a/core/engine/src/value/operations.rs b/core/engine/src/value/operations.rs index 08cb319930a..eb717967150 100644 --- a/core/engine/src/value/operations.rs +++ b/core/engine/src/value/operations.rs @@ -482,7 +482,6 @@ impl JsValue { /// Returns the negated value. pub fn neg(&self, context: &mut Context) -> JsResult { - eprintln!("neg({:?})", self.variant()); Ok(match self.variant() { JsVariant::Symbol(_) | JsVariant::Undefined => Self::new(f64::NAN), JsVariant::Object(_) => Self::new( From 710d8f10c877823842821bbd70f9056b53df3f84 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Wed, 25 Dec 2024 21:36:59 -0500 Subject: [PATCH 13/24] Clippies --- core/engine/src/value/inner.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/engine/src/value/inner.rs b/core/engine/src/value/inner.rs index ba4836c3523..d7b38f80ce3 100644 --- a/core/engine/src/value/inner.rs +++ b/core/engine/src/value/inner.rs @@ -789,6 +789,7 @@ fn integer() { } #[test] +#[allow(clippy::float_cmp)] fn float() { fn assert_float(f: f64) { let v = InnerValue::float64(f); @@ -805,11 +806,11 @@ fn float() { // Some edge cases around zeroes. let neg_zero = InnerValue::float64(-0.0); assert!(neg_zero.as_float64().unwrap().is_sign_negative()); - assert_eq!(neg_zero.as_float64().unwrap(), 0.0); + assert_eq!(0.0f64, neg_zero.as_float64().unwrap()); let pos_zero = InnerValue::float64(0.0); assert!(!pos_zero.as_float64().unwrap().is_sign_negative()); - assert_eq!(pos_zero.as_float64().unwrap(), 0.0); + assert_eq!(0.0f64, pos_zero.as_float64().unwrap()); assert_eq!(pos_zero.as_float64(), neg_zero.as_float64()); From c6d0669f6f5704f92be9efce43457b389796006a Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Sat, 28 Dec 2024 20:15:14 -0500 Subject: [PATCH 14/24] refactor bit-masks and ranges --- core/engine/src/value/inner.rs | 310 +++++++++++++++------------------ 1 file changed, 144 insertions(+), 166 deletions(-) diff --git a/core/engine/src/value/inner.rs b/core/engine/src/value/inner.rs index d7b38f80ce3..15dc3ebe040 100644 --- a/core/engine/src/value/inner.rs +++ b/core/engine/src/value/inner.rs @@ -71,137 +71,103 @@ const_assert!(size_of::() <= size_of::()); // We cannot NaN-box pointers that are not 4-bytes aligned. const_assert!(align_of::<*mut ()>() >= 4); -/// The bit tags and masks for NaN-boxed values. Masks are applied when creating -/// the value. +/// Internal module for bit masks and constants. /// -/// This is a utility type that allows to create NaN-boxed values, and to check -/// the type of a NaN-boxed value. /// -/// All the bit masking, tagging and untagging is done in this type. -#[derive(Copy, Clone, Debug)] -#[repr(u64)] -enum NanBitTag { - Undefined = 0x7FF4_0000_0000_0000, - Null = 0x7FF5_0000_0000_0000, - False = 0x7FF6_0000_0000_0000, - True = 0x7FF6_0000_0000_0001, - Integer32 = 0x7FF7_0000_0000_0000, - - NegativeZero = 0x8000_0000_0000_0000, - - /// A generic pointer. - Pointer = 0x7FF8_0000_0000_0000, - BigInt = 0x0000_0000_0000_0000, - Object = 0x0000_0000_0000_0001, - Symbol = 0x0000_0000_0000_0002, - String = 0x0000_0000_0000_0003, - - // Masks - TaggedMask = 0x7FFC_0000_0000_0000, - PointerMask = 0x0007_FFFF_FFFF_FFFC, -} +mod bits { + use boa_engine::value::inner::{f64_from_bits, f64_is_nan, f64_to_bits}; + use boa_engine::{JsBigInt, JsObject, JsSymbol}; + use boa_string::JsString; + use std::ops::RangeInclusive; + + /// Undefined value in `u64`. + pub(super) const UNDEFINED: u64 = 0x7FF4_0000_0000_0000; + + /// Null value in `u64`. + pub(super) const NULL: u64 = 0x7FF5_0000_0000_0000; + + /// False value in `u64`. + pub(super) const FALSE: u64 = 0x7FF6_0000_0000_0000; + + /// True value in `u64`. + pub(super) const TRUE: u64 = 0x7FF6_0000_0000_0001; + + /// Integer32 value in `u64`. + pub(super) const INTEGER32_ZERO: u64 = 0x7FF7_0000_0000_0000; + + /// Pointer starting point in `u64`. + pub(super) const POINTER: u64 = 0x7FF8_0000_0000_0000; + + /// Pointer types mask in `u64`. + pub(super) const POINTER_MASK: u64 = 0x0007_FFFF_FFFF_FFFC; + + /// NAN value in `u64`. + pub(super) const NAN: u64 = 0x7FF8_0000_0000_0000; -// Verify that all representations of NanBitTag ARE NAN, but don't match static NAN. -// The only exception to this rule is BigInt, which assumes that the pointer is -// non-null. The static f64::NAN is equal to BigInt. -const_assert!(f64_is_nan(f64_from_bits(NanBitTag::Undefined as u64))); -const_assert!(f64_is_nan(f64_from_bits(NanBitTag::Null as u64))); -const_assert!(f64_is_nan(f64_from_bits(NanBitTag::False as u64))); -const_assert!(f64_is_nan(f64_from_bits(NanBitTag::True as u64))); -const_assert!(f64_is_nan(f64_from_bits(NanBitTag::Integer32 as u64))); -const_assert!(f64_is_nan(f64_from_bits(NanBitTag::Pointer as u64))); - -impl NanBitTag { /// Checks that a value is a valid boolean (either true or false). #[inline] - const fn is_bool(value: u64) -> bool { - value == NanBitTag::False as u64 || value == NanBitTag::True as u64 + pub(super) const fn is_bool(value: u64) -> bool { + value == TRUE || value == FALSE } /// Checks that a value is a valid float, not a tagged nan boxed value. #[inline] - const fn is_float(value: u64) -> bool { - // Either it is a constant float value, - if value == f64_to_bits(f64::INFINITY) - || value == f64_to_bits(f64::NEG_INFINITY) - // or it is exactly a NaN value, which is the same as the `Pointer` tag. - // Reminder that pointers cannot be null, so this is safe. - || value == f64_to_bits(f64::NAN) - // or it is negative/positive zero. - || value == NanBitTag::NegativeZero as u64 - || value == 0 - { - return true; - } - - // Or it is not tagged, - !matches!( - value & NanBitTag::TaggedMask as u64, - 0x7FF4_0000_0000_0000 - | 0x7FF5_0000_0000_0000 - | 0x7FF6_0000_0000_0000 - | 0x7FF7_0000_0000_0000 - | 0x7FF8_0000_0000_0000 - | 0x7FF9_0000_0000_0000 - | 0x7FFA_0000_0000_0000 - | 0x7FFB_0000_0000_0000 - | 0x7FFC_0000_0000_0000 - | 0x7FFD_0000_0000_0000 - | 0x7FFE_0000_0000_0000 - | 0x7FFF_0000_0000_0000 - ) + pub(super) const fn is_float(value: u64) -> bool { + let as_float = f64_from_bits(value); + !f64_is_nan(as_float) || value == NAN } /// Checks that a value is a valid undefined. #[inline] - const fn is_undefined(value: u64) -> bool { - value == NanBitTag::Undefined as u64 + pub(super) const fn is_undefined(value: u64) -> bool { + value == UNDEFINED } /// Checks that a value is a valid null. #[inline] - const fn is_null(value: u64) -> bool { - value == NanBitTag::Null as u64 + pub(super) const fn is_null(value: u64) -> bool { + value == NULL } /// Checks that a value is a valid integer32. #[inline] - const fn is_integer32(value: u64) -> bool { - value & NanBitTag::Integer32 as u64 == NanBitTag::Integer32 as u64 + pub(super) const fn is_integer32(value: u64) -> bool { + value & INTEGER32_ZERO == INTEGER32_ZERO + } + + /// Untag a value as a pointer. + #[inline] + pub(super) const fn is_pointer(value: u64) -> bool { + value & POINTER == POINTER } /// Checks that a value is a valid `BigInt`. #[inline] - const fn is_bigint(value: u64) -> bool { - (value & NanBitTag::TaggedMask as u64 == NanBitTag::Pointer as u64) - && (value & 0x3 == Self::BigInt as u64) - && (value & NanBitTag::PointerMask as u64) != 0 + pub(super) const fn is_bigint(value: u64) -> bool { + is_pointer(value) && (value & 0x3 == 0) && (value & POINTER_MASK) != 0 } /// Checks that a value is a valid Object. #[inline] - const fn is_object(value: u64) -> bool { - (value & NanBitTag::TaggedMask as u64 == NanBitTag::Pointer as u64) - && (value & 0x3 == Self::Object as u64) + pub(super) const fn is_object(value: u64) -> bool { + is_pointer(value) && (value & 0x3 == 1) && (value & POINTER_MASK) != 0 } /// Checks that a value is a valid Symbol. #[inline] - const fn is_symbol(value: u64) -> bool { - (value & NanBitTag::TaggedMask as u64 == NanBitTag::Pointer as u64) - && (value & 0x3 == Self::Symbol as u64) + pub(super) const fn is_symbol(value: u64) -> bool { + is_pointer(value) && (value & 0x3 == 2) && (value & POINTER_MASK) != 0 } /// Checks that a value is a valid String. #[inline] - const fn is_string(value: u64) -> bool { - (value & NanBitTag::TaggedMask as u64 == NanBitTag::Pointer as u64) - && (value & 0x3 == Self::String as u64) + pub(super) const fn is_string(value: u64) -> bool { + is_pointer(value) && (value & 0x3 == 3) && (value & POINTER_MASK) != 0 } /// Returns a tagged u64 of a 64-bits float. #[inline] - const fn tag_f64(value: f64) -> u64 { + pub(super) const fn tag_f64(value: f64) -> u64 { if f64_is_nan(value) { // Reduce any NAN to a canonical NAN representation. f64_to_bits(f64::NAN) @@ -212,27 +178,23 @@ impl NanBitTag { /// Returns a tagged u64 of a 32-bits integer. #[inline] - const fn tag_i32(value: i32) -> u64 { - Self::Integer32 as u64 | value as u64 & 0xFFFF_FFFFu64 + pub(super) const fn tag_i32(value: i32) -> u64 { + INTEGER32_ZERO | value as u64 & 0xFFFF_FFFFu64 } /// Returns a i32-bits from a tagged integer. #[inline] - const fn untag_i32(value: u64) -> Option { - if Self::is_integer32(value) { - Some(((value & 0xFFFF_FFFFu64) | 0xFFFF_FFFF_0000_0000u64) as i32) - } else { - None - } + pub(super) const fn untag_i32(value: u64) -> i32 { + ((value & 0xFFFF_FFFFu64) | 0xFFFF_FFFF_0000_0000u64) as i32 } /// Returns a tagged u64 of a boolean. #[inline] - const fn tag_bool(value: bool) -> u64 { + pub(super) const fn tag_bool(value: bool) -> u64 { if value { - Self::True as u64 + TRUE } else { - Self::False as u64 + FALSE } } @@ -245,9 +207,9 @@ impl NanBitTag { /// The box is forgotten after this operation. It must be dropped separately, /// by calling `[Self::drop_pointer]`. #[inline] - unsafe fn tag_bigint(value: Box) -> u64 { + pub(super) unsafe fn tag_bigint(value: Box) -> u64 { let value = Box::into_raw(value) as u64; - let value_masked: u64 = value & Self::PointerMask as u64; + let value_masked: u64 = value & POINTER_MASK; // Assert alignment and location of the pointer. assert_eq!( @@ -255,10 +217,10 @@ impl NanBitTag { "Pointer is not 4-bits aligned or over 51-bits." ); // Cannot have a null pointer for bigint. - assert_ne!(value & Self::PointerMask as u64, 0, "Pointer is NULL."); + assert_ne!(value_masked, 0, "Pointer is NULL."); // Simply cast for bits. - Self::Pointer as u64 | Self::BigInt as u64 | value + POINTER | 0 | value_masked } /// Returns a tagged u64 of a boxed `[JsObject]`. @@ -270,18 +232,20 @@ impl NanBitTag { /// The box is forgotten after this operation. It must be dropped separately, /// by calling `[Self::drop_pointer]`. #[inline] - unsafe fn tag_object(value: Box) -> u64 { + pub(super) unsafe fn tag_object(value: Box) -> u64 { let value = Box::into_raw(value) as u64; - let value_masked: u64 = value & Self::PointerMask as u64; + let value_masked: u64 = value & POINTER_MASK; // Assert alignment and location of the pointer. assert_eq!( value_masked, value, "Pointer is not 4-bits aligned or over 51-bits." ); + // Cannot have a null pointer for bigint. + assert_ne!(value_masked, 0, "Pointer is NULL."); // Simply cast for bits. - Self::Pointer as u64 | Self::Object as u64 | value + POINTER | 1 | value_masked } /// Returns a tagged u64 of a boxed `[JsSymbol]`. @@ -293,18 +257,20 @@ impl NanBitTag { /// The box is forgotten after this operation. It must be dropped separately, /// by calling `[Self::drop_pointer]`. #[inline] - unsafe fn tag_symbol(value: Box) -> u64 { + pub(super) unsafe fn tag_symbol(value: Box) -> u64 { let value = Box::into_raw(value) as u64; - let value_masked: u64 = value & Self::PointerMask as u64; + let value_masked: u64 = value & POINTER_MASK; // Assert alignment and location of the pointer. assert_eq!( value_masked, value, "Pointer is not 4-bits aligned or over 51-bits." ); + // Cannot have a null pointer for bigint. + assert_ne!(value_masked, 0, "Pointer is NULL."); // Simply cast for bits. - Self::Pointer as u64 | Self::Symbol as u64 | value + POINTER | 2 | value_masked } /// Returns a tagged u64 of a boxed `[JsString]`. @@ -316,21 +282,34 @@ impl NanBitTag { /// The box is forgotten after this operation. It must be dropped separately, /// by calling `[Self::drop_pointer]`. #[inline] - unsafe fn tag_string(value: Box) -> u64 { + pub(super) unsafe fn tag_string(value: Box) -> u64 { let value = Box::into_raw(value) as u64; - let value_masked: u64 = value & Self::PointerMask as u64; + let value_masked: u64 = value & POINTER_MASK; // Assert alignment and location of the pointer. assert_eq!( value_masked, value, "Pointer is not 4-bits aligned or over 51-bits." ); + // Cannot have a null pointer for bigint. + assert_ne!(value_masked, 0, "Pointer is NULL."); // Simply cast for bits. - Self::Pointer as u64 | Self::String as u64 | value + POINTER | 3 | value_masked } } +// Verify that all representations of NanBitTag ARE NAN, but don't match static NAN. +// The only exception to this rule is BigInt, which assumes that the pointer is +// non-null. The static f64::NAN is equal to BigInt. +const_assert!(f64_is_nan(f64_from_bits(bits::UNDEFINED))); +const_assert!(f64_is_nan(f64_from_bits(bits::NULL))); +const_assert!(f64_is_nan(f64_from_bits(bits::FALSE))); +const_assert!(f64_is_nan(f64_from_bits(bits::TRUE))); +const_assert!(f64_is_nan(f64_from_bits(bits::INTEGER32_ZERO))); +const_assert!(f64_is_nan(f64_from_bits(*bits::POINTER))); +const_assert!(f64_is_nan(f64_from_bits(*0x7FFF_FFFF_FFFF_FFFF))); + /// A NaN-boxed `[super::JsValue]`'s inner. pub(super) struct InnerValue(pub u64); @@ -396,14 +375,14 @@ impl InnerValue { #[must_use] #[inline] pub(super) const fn null() -> Self { - Self::from_inner_unchecked(NanBitTag::Null as u64) + Self::from_inner_unchecked(bits::NULL) } /// Returns a `InnerValue` from an undefined. #[must_use] #[inline] pub(super) const fn undefined() -> Self { - Self::from_inner_unchecked(NanBitTag::Undefined as u64) + Self::from_inner_unchecked(bits::UNDEFINED) } /// Returns a `InnerValue` from a 64-bits float. If the float is `NaN`, @@ -411,112 +390,112 @@ impl InnerValue { #[must_use] #[inline] pub(super) const fn float64(value: f64) -> Self { - Self::from_inner_unchecked(NanBitTag::tag_f64(value)) + Self::from_inner_unchecked(bits::tag_f64(value)) } /// Returns a `InnerValue` from a 32-bits integer. #[must_use] #[inline] pub(super) const fn integer32(value: i32) -> Self { - Self::from_inner_unchecked(NanBitTag::tag_i32(value)) + Self::from_inner_unchecked(bits::tag_i32(value)) } /// Returns a `InnerValue` from a boolean. #[must_use] #[inline] pub(super) const fn boolean(value: bool) -> Self { - Self::from_inner_unchecked(NanBitTag::tag_bool(value)) + Self::from_inner_unchecked(bits::tag_bool(value)) } /// Returns a `InnerValue` from a boxed `[JsBigInt]`. #[must_use] #[inline] pub(super) fn bigint(value: JsBigInt) -> Self { - Self::from_inner_unchecked(unsafe { NanBitTag::tag_bigint(Box::new(value)) }) + Self::from_inner_unchecked(unsafe { bits::tag_bigint(Box::new(value)) }) } /// Returns a `InnerValue` from a boxed `[JsObject]`. #[must_use] #[inline] pub(super) fn object(value: JsObject) -> Self { - Self::from_inner_unchecked(unsafe { NanBitTag::tag_object(Box::new(value)) }) + Self::from_inner_unchecked(unsafe { bits::tag_object(Box::new(value)) }) } /// Returns a `InnerValue` from a boxed `[JsSymbol]`. #[must_use] #[inline] pub(super) fn symbol(value: JsSymbol) -> Self { - Self::from_inner_unchecked(unsafe { NanBitTag::tag_symbol(Box::new(value)) }) + Self::from_inner_unchecked(unsafe { bits::tag_symbol(Box::new(value)) }) } /// Returns a `InnerValue` from a boxed `[JsString]`. #[must_use] #[inline] pub(super) fn string(value: JsString) -> Self { - Self::from_inner_unchecked(unsafe { NanBitTag::tag_string(Box::new(value)) }) + Self::from_inner_unchecked(unsafe { bits::tag_string(Box::new(value)) }) } /// Returns true if a value is undefined. #[must_use] #[inline] pub(super) const fn is_undefined(&self) -> bool { - NanBitTag::is_undefined(self.0) + bits::is_undefined(self.0) } /// Returns true if a value is null. #[must_use] #[inline] pub(super) const fn is_null(&self) -> bool { - NanBitTag::is_null(self.0) + bits::is_null(self.0) } /// Returns true if a value is a boolean. #[must_use] #[inline] pub(super) const fn is_bool(&self) -> bool { - NanBitTag::is_bool(self.0) + bits::is_bool(self.0) } /// Returns true if a value is a 64-bits float. #[must_use] #[inline] pub(super) const fn is_float64(&self) -> bool { - NanBitTag::is_float(self.0) + bits::is_float(self.0) } /// Returns true if a value is a 32-bits integer. #[must_use] #[inline] pub(super) const fn is_integer32(&self) -> bool { - NanBitTag::is_integer32(self.0) + bits::is_integer32(self.0) } /// Returns true if a value is a `[JsBigInt]`. A `NaN` will not match here. #[must_use] #[inline] pub(super) const fn is_bigint(&self) -> bool { - NanBitTag::is_bigint(self.0) + bits::is_bigint(self.0) } /// Returns true if a value is a boxed Object. #[must_use] #[inline] pub(super) const fn is_object(&self) -> bool { - NanBitTag::is_object(self.0) + bits::is_object(self.0) } /// Returns true if a value is a boxed Symbol. #[must_use] #[inline] pub(super) const fn is_symbol(&self) -> bool { - NanBitTag::is_symbol(self.0) + bits::is_symbol(self.0) } /// Returns true if a value is a boxed String. #[must_use] #[inline] pub(super) const fn is_string(&self) -> bool { - NanBitTag::is_string(self.0) + bits::is_string(self.0) } /// Returns the value as an f64 if it is a float. @@ -534,19 +513,21 @@ impl InnerValue { #[must_use] #[inline] pub(super) const fn as_integer32(&self) -> Option { - NanBitTag::untag_i32(self.0) + if self.is_integer32() { + Some(bits::untag_i32(self.0)) + } else { + None + } } /// Returns the value as a boolean if it is a boolean. #[must_use] #[inline] pub(super) const fn as_bool(&self) -> Option { - if self.0 == NanBitTag::False as u64 { - Some(false) - } else if self.0 == NanBitTag::True as u64 { - Some(true) - } else { - None + match self.0 { + bits::FALSE => Some(false), + bits::TRUE => Some(true), + _ => None, } } @@ -556,7 +537,7 @@ impl InnerValue { pub(super) const fn as_bigint(&self) -> Option<&JsBigInt> { if self.is_bigint() { // This is safe because the boxed object will always be on the heap. - let ptr = self.0 & NanBitTag::PointerMask as u64; + let ptr = self.0 & bits::POINTER_MASK; unsafe { Some(&*(ptr as *const _)) } } else { None @@ -569,7 +550,7 @@ impl InnerValue { pub(super) const fn as_object(&self) -> Option<&JsObject> { if self.is_object() { // This is safe because the boxed object will always be on the heap. - let ptr = self.0 & NanBitTag::PointerMask as u64; + let ptr = self.0 & bits::POINTER_MASK; unsafe { Some(&*(ptr as *const _)) } } else { None @@ -582,7 +563,7 @@ impl InnerValue { pub(super) const fn as_symbol(&self) -> Option<&JsSymbol> { if self.is_symbol() { // This is safe because the boxed object will always be on the heap. - let ptr = self.0 & NanBitTag::PointerMask as u64; + let ptr = self.0 & bits::POINTER_MASK; unsafe { Some(&*(ptr as *const _)) } } else { None @@ -595,7 +576,7 @@ impl InnerValue { pub(super) const fn as_string(&self) -> Option<&JsString> { if self.is_string() { // This is safe because the boxed object will always be on the heap. - let ptr = self.0 & NanBitTag::PointerMask as u64; + let ptr = self.0 & bits::POINTER_MASK; unsafe { Some(&*(ptr as *const _)) } } else { None @@ -606,35 +587,32 @@ impl InnerValue { #[must_use] #[inline] pub(super) const fn as_variant(&self) -> JsVariant<'_> { - if self.is_undefined() { - JsVariant::Undefined - } else if self.is_null() { - JsVariant::Null - } else if let Some(b) = self.as_bool() { - JsVariant::Boolean(b) - } else if let Some(f) = self.as_float64() { - JsVariant::Float64(f) - } else if let Some(i) = self.as_integer32() { - JsVariant::Integer32(i) - } else if let Some(bigint) = self.as_bigint() { - JsVariant::BigInt(bigint) - } else if let Some(obj) = self.as_object() { - JsVariant::Object(obj) - } else if let Some(sym) = self.as_symbol() { - JsVariant::Symbol(sym) - } else if let Some(str) = self.as_string() { - JsVariant::String(str) - } else { - // This will optimize away this branch, resulting in UB if it is - // actually used, but it won't. It shouldn't. Really. - unsafe { std::hint::unreachable_unchecked() } + match self.0 { + bits::UNDEFINED => JsVariant::Undefined, + bits::NULL => JsVariant::Null, + bits::FALSE => JsVariant::Boolean(false), + bits::TRUE => JsVariant::Boolean(true), + bits::INTEGER32_ZERO..=0x7FF7_0000_FFFF_FFFF => { + JsVariant::Integer32(bits::untag_i32(self.0)) + } + bits::POINTER..=0x7FFF_FFFF_FFFF_FFFF => { + let ptr = self.0 & bits::POINTER_MASK; + match self.0 & 0x3 { + 0 => JsVariant::BigInt(unsafe { &*(ptr as *const _) }), + 1 => JsVariant::Object(unsafe { &*(ptr as *const _) }), + 2 => JsVariant::Symbol(unsafe { &*(ptr as *const _) }), + 3 => JsVariant::String(unsafe { &*(ptr as *const _) }), + _ => unreachable!(), + } + } + _ => JsVariant::Float64(f64_from_bits(self.0)), } } } impl Drop for InnerValue { fn drop(&mut self) { - let maybe_ptr = self.0 & NanBitTag::PointerMask as u64; + let maybe_ptr = self.0 & bits::POINTER_MASK; // Drop the pointer if it is a pointer. if self.is_object() { From ad64fd977901115fff8a3335af616f54ca9f8154 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Sat, 28 Dec 2024 20:31:12 -0500 Subject: [PATCH 15/24] NAN is not null_ptr and clippies --- core/engine/src/value/inner.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/core/engine/src/value/inner.rs b/core/engine/src/value/inner.rs index 15dc3ebe040..d3bdf582380 100644 --- a/core/engine/src/value/inner.rs +++ b/core/engine/src/value/inner.rs @@ -78,7 +78,6 @@ mod bits { use boa_engine::value::inner::{f64_from_bits, f64_is_nan, f64_to_bits}; use boa_engine::{JsBigInt, JsObject, JsSymbol}; use boa_string::JsString; - use std::ops::RangeInclusive; /// Undefined value in `u64`. pub(super) const UNDEFINED: u64 = 0x7FF4_0000_0000_0000; @@ -143,6 +142,7 @@ mod bits { /// Checks that a value is a valid `BigInt`. #[inline] + #[allow(clippy::verbose_bit_mask)] pub(super) const fn is_bigint(value: u64) -> bool { is_pointer(value) && (value & 0x3 == 0) && (value & POINTER_MASK) != 0 } @@ -207,6 +207,7 @@ mod bits { /// The box is forgotten after this operation. It must be dropped separately, /// by calling `[Self::drop_pointer]`. #[inline] + #[allow(clippy::identity_op)] pub(super) unsafe fn tag_bigint(value: Box) -> u64 { let value = Box::into_raw(value) as u64; let value_masked: u64 = value & POINTER_MASK; @@ -307,8 +308,8 @@ const_assert!(f64_is_nan(f64_from_bits(bits::NULL))); const_assert!(f64_is_nan(f64_from_bits(bits::FALSE))); const_assert!(f64_is_nan(f64_from_bits(bits::TRUE))); const_assert!(f64_is_nan(f64_from_bits(bits::INTEGER32_ZERO))); -const_assert!(f64_is_nan(f64_from_bits(*bits::POINTER))); -const_assert!(f64_is_nan(f64_from_bits(*0x7FFF_FFFF_FFFF_FFFF))); +const_assert!(f64_is_nan(f64_from_bits(bits::POINTER))); +const_assert!(f64_is_nan(f64_from_bits(0x7FFF_FFFF_FFFF_FFFF))); /// A NaN-boxed `[super::JsValue]`'s inner. pub(super) struct InnerValue(pub u64); @@ -595,6 +596,7 @@ impl InnerValue { bits::INTEGER32_ZERO..=0x7FF7_0000_FFFF_FFFF => { JsVariant::Integer32(bits::untag_i32(self.0)) } + bits::NAN => JsVariant::Float64(f64::NAN), bits::POINTER..=0x7FFF_FFFF_FFFF_FFFF => { let ptr = self.0 & bits::POINTER_MASK; match self.0 & 0x3 { From ac171fe37e1675338738c094123b740d5f639a33 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Sat, 28 Dec 2024 21:03:07 -0500 Subject: [PATCH 16/24] Move all magic numbers and bits to the bits private module --- core/engine/src/value/inner.rs | 103 ++++++++++++++++++++++----------- 1 file changed, 68 insertions(+), 35 deletions(-) diff --git a/core/engine/src/value/inner.rs b/core/engine/src/value/inner.rs index d3bdf582380..ae9a3c7a4aa 100644 --- a/core/engine/src/value/inner.rs +++ b/core/engine/src/value/inner.rs @@ -73,7 +73,7 @@ const_assert!(align_of::<*mut ()>() >= 4); /// Internal module for bit masks and constants. /// -/// +/// All bit magic is done here. mod bits { use boa_engine::value::inner::{f64_from_bits, f64_is_nan, f64_to_bits}; use boa_engine::{JsBigInt, JsObject, JsSymbol}; @@ -91,15 +91,36 @@ mod bits { /// True value in `u64`. pub(super) const TRUE: u64 = 0x7FF6_0000_0000_0001; - /// Integer32 value in `u64`. + /// Integer32 start (zero) value in `u64`. pub(super) const INTEGER32_ZERO: u64 = 0x7FF7_0000_0000_0000; + /// Integer32 end (MAX) value in `u64`. + pub(super) const INTEGER32_MAX: u64 = 0x7FF7_0000_FFFF_FFFF; + /// Pointer starting point in `u64`. - pub(super) const POINTER: u64 = 0x7FF8_0000_0000_0000; + pub(super) const POINTER_START: u64 = 0x7FF8_0000_0000_0000; + + /// Pointer starting point in `u64`. + pub(super) const POINTER_END: u64 = 0x7FFF_FFFF_FFFF_FFFF; /// Pointer types mask in `u64`. pub(super) const POINTER_MASK: u64 = 0x0007_FFFF_FFFF_FFFC; + /// Pointer mask for the type of the pointer. + pub(super) const POINTER_TYPE_MASK: u64 = 0x0003; + + /// Pointer value for `BigInt`. + pub(super) const BIGINT: u64 = 0x0000; + + /// Pointer value for `JsObject`. + pub(super) const OBJECT: u64 = 0x0001; + + /// Pointer value for `JsSymbol`. + pub(super) const SYMBOL: u64 = 0x0002; + + /// Pointer value for `JsString`. + pub(super) const STRING: u64 = 0x0003; + /// NAN value in `u64`. pub(super) const NAN: u64 = 0x7FF8_0000_0000_0000; @@ -137,32 +158,32 @@ mod bits { /// Untag a value as a pointer. #[inline] pub(super) const fn is_pointer(value: u64) -> bool { - value & POINTER == POINTER + value & POINTER_START == POINTER_START } /// Checks that a value is a valid `BigInt`. #[inline] #[allow(clippy::verbose_bit_mask)] pub(super) const fn is_bigint(value: u64) -> bool { - is_pointer(value) && (value & 0x3 == 0) && (value & POINTER_MASK) != 0 + is_pointer(value) && (value & POINTER_TYPE_MASK == BIGINT) && (value & POINTER_MASK) != 0 } /// Checks that a value is a valid Object. #[inline] pub(super) const fn is_object(value: u64) -> bool { - is_pointer(value) && (value & 0x3 == 1) && (value & POINTER_MASK) != 0 + is_pointer(value) && (value & POINTER_TYPE_MASK == OBJECT) && (value & POINTER_MASK) != 0 } /// Checks that a value is a valid Symbol. #[inline] pub(super) const fn is_symbol(value: u64) -> bool { - is_pointer(value) && (value & 0x3 == 2) && (value & POINTER_MASK) != 0 + is_pointer(value) && (value & POINTER_TYPE_MASK == SYMBOL) && (value & POINTER_MASK) != 0 } /// Checks that a value is a valid String. #[inline] pub(super) const fn is_string(value: u64) -> bool { - is_pointer(value) && (value & 0x3 == 3) && (value & POINTER_MASK) != 0 + is_pointer(value) && (value & POINTER_TYPE_MASK == STRING) && (value & POINTER_MASK) != 0 } /// Returns a tagged u64 of a 64-bits float. @@ -221,7 +242,7 @@ mod bits { assert_ne!(value_masked, 0, "Pointer is NULL."); // Simply cast for bits. - POINTER | 0 | value_masked + POINTER_START | 0 | value_masked } /// Returns a tagged u64 of a boxed `[JsObject]`. @@ -246,7 +267,7 @@ mod bits { assert_ne!(value_masked, 0, "Pointer is NULL."); // Simply cast for bits. - POINTER | 1 | value_masked + POINTER_START | 1 | value_masked } /// Returns a tagged u64 of a boxed `[JsSymbol]`. @@ -271,7 +292,7 @@ mod bits { assert_ne!(value_masked, 0, "Pointer is NULL."); // Simply cast for bits. - POINTER | 2 | value_masked + POINTER_START | 2 | value_masked } /// Returns a tagged u64 of a boxed `[JsString]`. @@ -296,7 +317,31 @@ mod bits { assert_ne!(value_masked, 0, "Pointer is NULL."); // Simply cast for bits. - POINTER | 3 | value_masked + POINTER_START | 3 | value_masked + } + + /// Returns an Option of a boxed `[JsBigInt]` from a tagged value. + #[inline] + pub(super) const fn as_bigint<'a>(value: u64) -> Option<&'a JsBigInt> { + if is_bigint(value) { + // This is safe because the boxed object will always be on the heap. + let ptr = value & POINTER_MASK; + unsafe { Some(&*(ptr as *const _)) } + } else { + None + } + } + + /// Returns an Option of a boxed `[JsObject]` from a tagged value. + #[inline] + pub(super) const fn as_object<'a>(value: u64) -> Option<&'a JsObject> { + if is_object(value) { + // This is safe because the boxed object will always be on the heap. + let ptr = value & POINTER_MASK; + unsafe { Some(&*(ptr as *const _)) } + } else { + None + } } } @@ -308,8 +353,8 @@ const_assert!(f64_is_nan(f64_from_bits(bits::NULL))); const_assert!(f64_is_nan(f64_from_bits(bits::FALSE))); const_assert!(f64_is_nan(f64_from_bits(bits::TRUE))); const_assert!(f64_is_nan(f64_from_bits(bits::INTEGER32_ZERO))); -const_assert!(f64_is_nan(f64_from_bits(bits::POINTER))); -const_assert!(f64_is_nan(f64_from_bits(0x7FFF_FFFF_FFFF_FFFF))); +const_assert!(f64_is_nan(f64_from_bits(bits::POINTER_START))); +const_assert!(f64_is_nan(f64_from_bits(bits::POINTER_END))); /// A NaN-boxed `[super::JsValue]`'s inner. pub(super) struct InnerValue(pub u64); @@ -536,26 +581,14 @@ impl InnerValue { #[must_use] #[inline] pub(super) const fn as_bigint(&self) -> Option<&JsBigInt> { - if self.is_bigint() { - // This is safe because the boxed object will always be on the heap. - let ptr = self.0 & bits::POINTER_MASK; - unsafe { Some(&*(ptr as *const _)) } - } else { - None - } + bits::as_bigint::<'_>(self.0) } /// Returns the value as a boxed `[JsObject]`. #[must_use] #[inline] pub(super) const fn as_object(&self) -> Option<&JsObject> { - if self.is_object() { - // This is safe because the boxed object will always be on the heap. - let ptr = self.0 & bits::POINTER_MASK; - unsafe { Some(&*(ptr as *const _)) } - } else { - None - } + bits::as_object::<'_>(self.0) } /// Returns the value as a boxed `[JsSymbol]`. @@ -593,17 +626,17 @@ impl InnerValue { bits::NULL => JsVariant::Null, bits::FALSE => JsVariant::Boolean(false), bits::TRUE => JsVariant::Boolean(true), - bits::INTEGER32_ZERO..=0x7FF7_0000_FFFF_FFFF => { + bits::INTEGER32_ZERO..=bits::INTEGER32_MAX => { JsVariant::Integer32(bits::untag_i32(self.0)) } bits::NAN => JsVariant::Float64(f64::NAN), - bits::POINTER..=0x7FFF_FFFF_FFFF_FFFF => { + bits::POINTER_START..=bits::POINTER_END => { let ptr = self.0 & bits::POINTER_MASK; - match self.0 & 0x3 { - 0 => JsVariant::BigInt(unsafe { &*(ptr as *const _) }), - 1 => JsVariant::Object(unsafe { &*(ptr as *const _) }), - 2 => JsVariant::Symbol(unsafe { &*(ptr as *const _) }), - 3 => JsVariant::String(unsafe { &*(ptr as *const _) }), + match self.0 & bits::POINTER_TYPE_MASK { + bits::BIGINT => JsVariant::BigInt(unsafe { &*(ptr as *const _) }), + bits::OBJECT => JsVariant::Object(unsafe { &*(ptr as *const _) }), + bits::SYMBOL => JsVariant::Symbol(unsafe { &*(ptr as *const _) }), + bits::STRING => JsVariant::String(unsafe { &*(ptr as *const _) }), _ => unreachable!(), } } From ec32abd9fd46633e0effdc25c9d3826e78092e83 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Mon, 30 Dec 2024 17:58:52 -0500 Subject: [PATCH 17/24] Implement both enum-based and nan-boxed JsValue inner behind a flag --- cli/Cargo.toml | 1 + core/engine/Cargo.toml | 1 + core/engine/src/value/inner.rs | 870 +--------------------- core/engine/src/value/inner/enum_value.rs | 254 +++++++ core/engine/src/value/inner/nan_boxed.rs | 864 +++++++++++++++++++++ 5 files changed, 1132 insertions(+), 858 deletions(-) create mode 100644 core/engine/src/value/inner/enum_value.rs create mode 100644 core/engine/src/value/inner/nan_boxed.rs diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 883a3882f27..5900475ecd5 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -28,6 +28,7 @@ dhat = { workspace = true, optional = true } [features] default = ["boa_engine/annex-b", "boa_engine/experimental", "boa_engine/intl_bundled"] dhat = ["dep:dhat"] +nan-box-jsvalue = ["boa_engine/nan-box-jsvalue"] [target.x86_64-unknown-linux-gnu.dependencies] jemallocator.workspace = true diff --git a/core/engine/Cargo.toml b/core/engine/Cargo.toml index 9a0d603e427..39ce719f63f 100644 --- a/core/engine/Cargo.toml +++ b/core/engine/Cargo.toml @@ -15,6 +15,7 @@ rust-version.workspace = true profiler = ["boa_profiler/profiler"] deser = ["boa_interner/serde", "boa_ast/serde"] either = ["dep:either"] +nan-box-jsvalue = [] # Enables the `Intl` builtin object and bundles a default ICU4X data provider. # Prefer this over `intl` if you just want to enable `Intl` without dealing with the diff --git a/core/engine/src/value/inner.rs b/core/engine/src/value/inner.rs index ae9a3c7a4aa..243200eca12 100644 --- a/core/engine/src/value/inner.rs +++ b/core/engine/src/value/inner.rs @@ -1,864 +1,18 @@ //! Module implementing the operations for the inner value of a `[super::JsValue]`. -//! The `[InnerValue]` type is a NaN-boxed value, which is a 64-bits value that -//! can represent any JavaScript value. If the integer is a non-NaN value, it -//! will be stored as a 64-bits float. If it is a `f64::NAN` value, it will be -//! stored as a quiet `NaN` value. Subnormal numbers are regular float. //! -//! For any other type of values, the value will be stored as a 51-bits non-zero -//! integer. -//! -//! In short, the memory layout of a NaN-boxed value is as follows: -//! -//! | Type of | Bit Layout | Comment | -//! |-------------------|------------|---------| -//! | `+Infinity` | `7FF0:0000:0000:0000` | | -//! | `-Infinity` | `FFF0:0000:0000:0000` | | -//! | `NAN` (quiet) | `7FF8:0000:0000:0000` | | -//! | `NAN` (signaling) | `FFF8:0000:0000:0000` | | -//! | `Undefined` | `7FF4:0000:0000:0000` | | -//! | `Null` | `7FF5:0000:0000:0000` | | -//! | `False` | `7FF6:0000:0000:0000` | | -//! | `True` | `7FF6:0000:0000:0001` | | -//! | `Integer32` | `7FF7:0000:IIII:IIII` | 32-bits integer. | -//! | `BigInt` | `7FF[8-F]:PPPP:PPPP:PPPP \| 0` | 51-bits pointer. Assumes non-null pointer. | -//! | `Object` | `7FF[8-F]:PPPP:PPPP:PPPP \| 1` | 51-bits pointer. | -//! | `Symbol` | `7FF[8-F]:PPPP:PPPP:PPPP \| 2` | 51-bits pointer. | -//! | `String` | `7FF[8-F]:PPPP:PPPP:PPPP \| 3` | 51-bits pointer. | -//! | `Float64` | Any other values. | | -//! -//! Pointers have the highest bit (in the `NaN` tag) set to 1, so they -//! can represent any value from `0x8000_0000_0000` to `0xFFFF_FFFF_FFFF`. -//! The last 2 bits of the pointer is used to store the type of the value. -//! -//! The pointers are assumed to never be NULL, and as such no clash -//! with regular NAN should happen. -//! -//! This only works on 4-bits aligned values, which is asserted when the -//! `InnerValue` is created. - -use crate::{JsBigInt, JsObject, JsSymbol, JsVariant}; -use boa_gc::{custom_trace, Finalize, Trace}; -use boa_string::JsString; -use core::fmt; -use static_assertions::const_assert; - -/// Transform an `u64` into `f64`, by its bytes. This is necessary for -/// keeping the MSRV at 1.82, as `f64::from_bits` is not const until -/// 1.83. -const fn f64_from_bits(bits: u64) -> f64 { - unsafe { std::mem::transmute(bits) } -} - -/// Transform a `f64` into `u64`, by its bytes. This is necessary for -/// keeping the MSRV at 1.82, as `f64::to_bits` is not const until -/// 1.83. -#[inline] -const fn f64_to_bits(bits: f64) -> u64 { - unsafe { std::mem::transmute(bits) } -} - -/// Check that a float is a `NaN`. This is necessary for keeping the MSRV -/// at 1.82, as `f64::is_nan` is not const until 1.53. -#[inline] -#[allow(clippy::eq_op, clippy::float_cmp)] -const fn f64_is_nan(f: f64) -> bool { - f != f -} - -// We cannot NaN-box pointers larger than 64 bits. -const_assert!(size_of::() <= size_of::()); - -// We cannot NaN-box pointers that are not 4-bytes aligned. -const_assert!(align_of::<*mut ()>() >= 4); - -/// Internal module for bit masks and constants. -/// -/// All bit magic is done here. -mod bits { - use boa_engine::value::inner::{f64_from_bits, f64_is_nan, f64_to_bits}; - use boa_engine::{JsBigInt, JsObject, JsSymbol}; - use boa_string::JsString; - - /// Undefined value in `u64`. - pub(super) const UNDEFINED: u64 = 0x7FF4_0000_0000_0000; - - /// Null value in `u64`. - pub(super) const NULL: u64 = 0x7FF5_0000_0000_0000; - - /// False value in `u64`. - pub(super) const FALSE: u64 = 0x7FF6_0000_0000_0000; - - /// True value in `u64`. - pub(super) const TRUE: u64 = 0x7FF6_0000_0000_0001; - - /// Integer32 start (zero) value in `u64`. - pub(super) const INTEGER32_ZERO: u64 = 0x7FF7_0000_0000_0000; - - /// Integer32 end (MAX) value in `u64`. - pub(super) const INTEGER32_MAX: u64 = 0x7FF7_0000_FFFF_FFFF; - - /// Pointer starting point in `u64`. - pub(super) const POINTER_START: u64 = 0x7FF8_0000_0000_0000; - - /// Pointer starting point in `u64`. - pub(super) const POINTER_END: u64 = 0x7FFF_FFFF_FFFF_FFFF; - - /// Pointer types mask in `u64`. - pub(super) const POINTER_MASK: u64 = 0x0007_FFFF_FFFF_FFFC; - - /// Pointer mask for the type of the pointer. - pub(super) const POINTER_TYPE_MASK: u64 = 0x0003; - - /// Pointer value for `BigInt`. - pub(super) const BIGINT: u64 = 0x0000; - - /// Pointer value for `JsObject`. - pub(super) const OBJECT: u64 = 0x0001; - - /// Pointer value for `JsSymbol`. - pub(super) const SYMBOL: u64 = 0x0002; - - /// Pointer value for `JsString`. - pub(super) const STRING: u64 = 0x0003; - - /// NAN value in `u64`. - pub(super) const NAN: u64 = 0x7FF8_0000_0000_0000; - - /// Checks that a value is a valid boolean (either true or false). - #[inline] - pub(super) const fn is_bool(value: u64) -> bool { - value == TRUE || value == FALSE - } - - /// Checks that a value is a valid float, not a tagged nan boxed value. - #[inline] - pub(super) const fn is_float(value: u64) -> bool { - let as_float = f64_from_bits(value); - !f64_is_nan(as_float) || value == NAN - } - - /// Checks that a value is a valid undefined. - #[inline] - pub(super) const fn is_undefined(value: u64) -> bool { - value == UNDEFINED - } - - /// Checks that a value is a valid null. - #[inline] - pub(super) const fn is_null(value: u64) -> bool { - value == NULL - } - - /// Checks that a value is a valid integer32. - #[inline] - pub(super) const fn is_integer32(value: u64) -> bool { - value & INTEGER32_ZERO == INTEGER32_ZERO - } - - /// Untag a value as a pointer. - #[inline] - pub(super) const fn is_pointer(value: u64) -> bool { - value & POINTER_START == POINTER_START - } - - /// Checks that a value is a valid `BigInt`. - #[inline] - #[allow(clippy::verbose_bit_mask)] - pub(super) const fn is_bigint(value: u64) -> bool { - is_pointer(value) && (value & POINTER_TYPE_MASK == BIGINT) && (value & POINTER_MASK) != 0 - } - - /// Checks that a value is a valid Object. - #[inline] - pub(super) const fn is_object(value: u64) -> bool { - is_pointer(value) && (value & POINTER_TYPE_MASK == OBJECT) && (value & POINTER_MASK) != 0 - } - - /// Checks that a value is a valid Symbol. - #[inline] - pub(super) const fn is_symbol(value: u64) -> bool { - is_pointer(value) && (value & POINTER_TYPE_MASK == SYMBOL) && (value & POINTER_MASK) != 0 - } - - /// Checks that a value is a valid String. - #[inline] - pub(super) const fn is_string(value: u64) -> bool { - is_pointer(value) && (value & POINTER_TYPE_MASK == STRING) && (value & POINTER_MASK) != 0 - } - - /// Returns a tagged u64 of a 64-bits float. - #[inline] - pub(super) const fn tag_f64(value: f64) -> u64 { - if f64_is_nan(value) { - // Reduce any NAN to a canonical NAN representation. - f64_to_bits(f64::NAN) - } else { - f64_to_bits(value) - } - } - - /// Returns a tagged u64 of a 32-bits integer. - #[inline] - pub(super) const fn tag_i32(value: i32) -> u64 { - INTEGER32_ZERO | value as u64 & 0xFFFF_FFFFu64 - } - - /// Returns a i32-bits from a tagged integer. - #[inline] - pub(super) const fn untag_i32(value: u64) -> i32 { - ((value & 0xFFFF_FFFFu64) | 0xFFFF_FFFF_0000_0000u64) as i32 - } - - /// Returns a tagged u64 of a boolean. - #[inline] - pub(super) const fn tag_bool(value: bool) -> u64 { - if value { - TRUE - } else { - FALSE - } - } - - /// Returns a tagged u64 of a boxed `[JsBigInt]`. - /// - /// # Safety - /// The pointer must be 4-bits aligned and cannot exceed 51-bits. This will - /// result in a panic. Also, the object is not checked for validity. - /// - /// The box is forgotten after this operation. It must be dropped separately, - /// by calling `[Self::drop_pointer]`. - #[inline] - #[allow(clippy::identity_op)] - pub(super) unsafe fn tag_bigint(value: Box) -> u64 { - let value = Box::into_raw(value) as u64; - let value_masked: u64 = value & POINTER_MASK; - - // Assert alignment and location of the pointer. - assert_eq!( - value_masked, value, - "Pointer is not 4-bits aligned or over 51-bits." - ); - // Cannot have a null pointer for bigint. - assert_ne!(value_masked, 0, "Pointer is NULL."); - - // Simply cast for bits. - POINTER_START | 0 | value_masked - } - - /// Returns a tagged u64 of a boxed `[JsObject]`. - /// - /// # Safety - /// The pointer must be 4-bits aligned and cannot exceed 51-bits. This will - /// result in a panic. Also, the object is not checked for validity. - /// - /// The box is forgotten after this operation. It must be dropped separately, - /// by calling `[Self::drop_pointer]`. - #[inline] - pub(super) unsafe fn tag_object(value: Box) -> u64 { - let value = Box::into_raw(value) as u64; - let value_masked: u64 = value & POINTER_MASK; - - // Assert alignment and location of the pointer. - assert_eq!( - value_masked, value, - "Pointer is not 4-bits aligned or over 51-bits." - ); - // Cannot have a null pointer for bigint. - assert_ne!(value_masked, 0, "Pointer is NULL."); - - // Simply cast for bits. - POINTER_START | 1 | value_masked - } - - /// Returns a tagged u64 of a boxed `[JsSymbol]`. - /// - /// # Safety - /// The pointer must be 4-bits aligned and cannot exceed 51-bits. This will - /// result in a panic. Also, the object is not checked for validity. - /// - /// The box is forgotten after this operation. It must be dropped separately, - /// by calling `[Self::drop_pointer]`. - #[inline] - pub(super) unsafe fn tag_symbol(value: Box) -> u64 { - let value = Box::into_raw(value) as u64; - let value_masked: u64 = value & POINTER_MASK; - - // Assert alignment and location of the pointer. - assert_eq!( - value_masked, value, - "Pointer is not 4-bits aligned or over 51-bits." - ); - // Cannot have a null pointer for bigint. - assert_ne!(value_masked, 0, "Pointer is NULL."); - - // Simply cast for bits. - POINTER_START | 2 | value_masked - } - - /// Returns a tagged u64 of a boxed `[JsString]`. - /// - /// # Safety - /// The pointer must be 4-bits aligned and cannot exceed 51-bits. This will - /// result in a panic. Also, the object is not checked for validity. - /// - /// The box is forgotten after this operation. It must be dropped separately, - /// by calling `[Self::drop_pointer]`. - #[inline] - pub(super) unsafe fn tag_string(value: Box) -> u64 { - let value = Box::into_raw(value) as u64; - let value_masked: u64 = value & POINTER_MASK; - - // Assert alignment and location of the pointer. - assert_eq!( - value_masked, value, - "Pointer is not 4-bits aligned or over 51-bits." - ); - // Cannot have a null pointer for bigint. - assert_ne!(value_masked, 0, "Pointer is NULL."); - - // Simply cast for bits. - POINTER_START | 3 | value_masked - } - - /// Returns an Option of a boxed `[JsBigInt]` from a tagged value. - #[inline] - pub(super) const fn as_bigint<'a>(value: u64) -> Option<&'a JsBigInt> { - if is_bigint(value) { - // This is safe because the boxed object will always be on the heap. - let ptr = value & POINTER_MASK; - unsafe { Some(&*(ptr as *const _)) } - } else { - None - } - } - - /// Returns an Option of a boxed `[JsObject]` from a tagged value. - #[inline] - pub(super) const fn as_object<'a>(value: u64) -> Option<&'a JsObject> { - if is_object(value) { - // This is safe because the boxed object will always be on the heap. - let ptr = value & POINTER_MASK; - unsafe { Some(&*(ptr as *const _)) } - } else { - None - } - } -} - -// Verify that all representations of NanBitTag ARE NAN, but don't match static NAN. -// The only exception to this rule is BigInt, which assumes that the pointer is -// non-null. The static f64::NAN is equal to BigInt. -const_assert!(f64_is_nan(f64_from_bits(bits::UNDEFINED))); -const_assert!(f64_is_nan(f64_from_bits(bits::NULL))); -const_assert!(f64_is_nan(f64_from_bits(bits::FALSE))); -const_assert!(f64_is_nan(f64_from_bits(bits::TRUE))); -const_assert!(f64_is_nan(f64_from_bits(bits::INTEGER32_ZERO))); -const_assert!(f64_is_nan(f64_from_bits(bits::POINTER_START))); -const_assert!(f64_is_nan(f64_from_bits(bits::POINTER_END))); - -/// A NaN-boxed `[super::JsValue]`'s inner. -pub(super) struct InnerValue(pub u64); - -impl fmt::Debug for InnerValue { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.as_variant() { - JsVariant::Undefined => f.debug_tuple("Undefined").finish(), - JsVariant::Null => f.debug_tuple("Null").finish(), - JsVariant::Boolean(b) => f.debug_tuple("Boolean").field(&b).finish(), - JsVariant::Float64(n) => f.debug_tuple("Float64").field(&n).finish(), - JsVariant::Integer32(n) => f.debug_tuple("Integer32").field(&n).finish(), - JsVariant::BigInt(n) => f.debug_tuple("BigInt").field(&n).finish(), - JsVariant::Object(n) => f.debug_tuple("Object").field(&n).finish(), - JsVariant::Symbol(n) => f.debug_tuple("Symbol").field(&n).finish(), - JsVariant::String(n) => f.debug_tuple("String").field(&n).finish(), - } - } -} - -impl Finalize for InnerValue { - fn finalize(&self) { - if let Some(o) = self.as_object() { - o.finalize(); - } - } -} - -#[allow(unsafe_op_in_unsafe_fn)] -unsafe impl Trace for InnerValue { - custom_trace! {this, mark, { - if let Some(o) = this.as_object() { - mark(o); - } - }} -} - -impl Clone for InnerValue { - fn clone(&self) -> Self { - if let Some(o) = self.as_object() { - Self::object(o.clone()) - } else if let Some(b) = self.as_bigint() { - Self::bigint(b.clone()) - } else if let Some(s) = self.as_symbol() { - Self::symbol(s.clone()) - } else if let Some(s) = self.as_string() { - Self::string(s.clone()) - } else { - Self(self.0) - } - } -} - -impl InnerValue { - /// Creates a new `InnerValue` from an u64 value without checking the validity - /// of the value. - #[must_use] - #[inline] - const fn from_inner_unchecked(inner: u64) -> Self { - Self(inner) - } - - /// Returns a `InnerValue` from a Null. - #[must_use] - #[inline] - pub(super) const fn null() -> Self { - Self::from_inner_unchecked(bits::NULL) - } - - /// Returns a `InnerValue` from an undefined. - #[must_use] - #[inline] - pub(super) const fn undefined() -> Self { - Self::from_inner_unchecked(bits::UNDEFINED) - } - - /// Returns a `InnerValue` from a 64-bits float. If the float is `NaN`, - /// it will be reduced to a canonical `NaN` representation. - #[must_use] - #[inline] - pub(super) const fn float64(value: f64) -> Self { - Self::from_inner_unchecked(bits::tag_f64(value)) - } - - /// Returns a `InnerValue` from a 32-bits integer. - #[must_use] - #[inline] - pub(super) const fn integer32(value: i32) -> Self { - Self::from_inner_unchecked(bits::tag_i32(value)) - } - - /// Returns a `InnerValue` from a boolean. - #[must_use] - #[inline] - pub(super) const fn boolean(value: bool) -> Self { - Self::from_inner_unchecked(bits::tag_bool(value)) - } - - /// Returns a `InnerValue` from a boxed `[JsBigInt]`. - #[must_use] - #[inline] - pub(super) fn bigint(value: JsBigInt) -> Self { - Self::from_inner_unchecked(unsafe { bits::tag_bigint(Box::new(value)) }) - } - - /// Returns a `InnerValue` from a boxed `[JsObject]`. - #[must_use] - #[inline] - pub(super) fn object(value: JsObject) -> Self { - Self::from_inner_unchecked(unsafe { bits::tag_object(Box::new(value)) }) - } - - /// Returns a `InnerValue` from a boxed `[JsSymbol]`. - #[must_use] - #[inline] - pub(super) fn symbol(value: JsSymbol) -> Self { - Self::from_inner_unchecked(unsafe { bits::tag_symbol(Box::new(value)) }) - } - - /// Returns a `InnerValue` from a boxed `[JsString]`. - #[must_use] - #[inline] - pub(super) fn string(value: JsString) -> Self { - Self::from_inner_unchecked(unsafe { bits::tag_string(Box::new(value)) }) - } - - /// Returns true if a value is undefined. - #[must_use] - #[inline] - pub(super) const fn is_undefined(&self) -> bool { - bits::is_undefined(self.0) - } - - /// Returns true if a value is null. - #[must_use] - #[inline] - pub(super) const fn is_null(&self) -> bool { - bits::is_null(self.0) - } - - /// Returns true if a value is a boolean. - #[must_use] - #[inline] - pub(super) const fn is_bool(&self) -> bool { - bits::is_bool(self.0) - } - - /// Returns true if a value is a 64-bits float. - #[must_use] - #[inline] - pub(super) const fn is_float64(&self) -> bool { - bits::is_float(self.0) - } - - /// Returns true if a value is a 32-bits integer. - #[must_use] - #[inline] - pub(super) const fn is_integer32(&self) -> bool { - bits::is_integer32(self.0) - } - - /// Returns true if a value is a `[JsBigInt]`. A `NaN` will not match here. - #[must_use] - #[inline] - pub(super) const fn is_bigint(&self) -> bool { - bits::is_bigint(self.0) - } - - /// Returns true if a value is a boxed Object. - #[must_use] - #[inline] - pub(super) const fn is_object(&self) -> bool { - bits::is_object(self.0) - } - - /// Returns true if a value is a boxed Symbol. - #[must_use] - #[inline] - pub(super) const fn is_symbol(&self) -> bool { - bits::is_symbol(self.0) - } - - /// Returns true if a value is a boxed String. - #[must_use] - #[inline] - pub(super) const fn is_string(&self) -> bool { - bits::is_string(self.0) - } - - /// Returns the value as an f64 if it is a float. - #[must_use] - #[inline] - pub(super) const fn as_float64(&self) -> Option { - if self.is_float64() { - Some(f64_from_bits(self.0)) - } else { - None - } - } - - /// Returns the value as an i32 if it is an integer. - #[must_use] - #[inline] - pub(super) const fn as_integer32(&self) -> Option { - if self.is_integer32() { - Some(bits::untag_i32(self.0)) - } else { - None - } - } - - /// Returns the value as a boolean if it is a boolean. - #[must_use] - #[inline] - pub(super) const fn as_bool(&self) -> Option { - match self.0 { - bits::FALSE => Some(false), - bits::TRUE => Some(true), - _ => None, - } - } - - /// Returns the value as a boxed `[JsBigInt]`. - #[must_use] - #[inline] - pub(super) const fn as_bigint(&self) -> Option<&JsBigInt> { - bits::as_bigint::<'_>(self.0) - } - - /// Returns the value as a boxed `[JsObject]`. - #[must_use] - #[inline] - pub(super) const fn as_object(&self) -> Option<&JsObject> { - bits::as_object::<'_>(self.0) - } - - /// Returns the value as a boxed `[JsSymbol]`. - #[must_use] - #[inline] - pub(super) const fn as_symbol(&self) -> Option<&JsSymbol> { - if self.is_symbol() { - // This is safe because the boxed object will always be on the heap. - let ptr = self.0 & bits::POINTER_MASK; - unsafe { Some(&*(ptr as *const _)) } - } else { - None - } - } - - /// Returns the value as a boxed `[JsString]`. - #[must_use] - #[inline] - pub(super) const fn as_string(&self) -> Option<&JsString> { - if self.is_string() { - // This is safe because the boxed object will always be on the heap. - let ptr = self.0 & bits::POINTER_MASK; - unsafe { Some(&*(ptr as *const _)) } - } else { - None - } - } - - /// Returns the `[JsVariant]` of this inner value. - #[must_use] - #[inline] - pub(super) const fn as_variant(&self) -> JsVariant<'_> { - match self.0 { - bits::UNDEFINED => JsVariant::Undefined, - bits::NULL => JsVariant::Null, - bits::FALSE => JsVariant::Boolean(false), - bits::TRUE => JsVariant::Boolean(true), - bits::INTEGER32_ZERO..=bits::INTEGER32_MAX => { - JsVariant::Integer32(bits::untag_i32(self.0)) - } - bits::NAN => JsVariant::Float64(f64::NAN), - bits::POINTER_START..=bits::POINTER_END => { - let ptr = self.0 & bits::POINTER_MASK; - match self.0 & bits::POINTER_TYPE_MASK { - bits::BIGINT => JsVariant::BigInt(unsafe { &*(ptr as *const _) }), - bits::OBJECT => JsVariant::Object(unsafe { &*(ptr as *const _) }), - bits::SYMBOL => JsVariant::Symbol(unsafe { &*(ptr as *const _) }), - bits::STRING => JsVariant::String(unsafe { &*(ptr as *const _) }), - _ => unreachable!(), - } - } - _ => JsVariant::Float64(f64_from_bits(self.0)), - } - } -} - -impl Drop for InnerValue { - fn drop(&mut self) { - let maybe_ptr = self.0 & bits::POINTER_MASK; - - // Drop the pointer if it is a pointer. - if self.is_object() { - drop(unsafe { Box::from_raw(maybe_ptr as *mut JsObject) }); - } else if self.is_bigint() { - drop(unsafe { Box::from_raw(maybe_ptr as *mut JsBigInt) }); - } else if self.is_symbol() { - drop(unsafe { Box::from_raw(maybe_ptr as *mut JsSymbol) }); - } else if self.is_string() { - drop(unsafe { Box::from_raw(maybe_ptr as *mut JsString) }); - } - } -} - -#[cfg(test)] -macro_rules! assert_type { - (@@is $value: ident, $u: literal, $n: literal, $b: literal, $i: literal, $f: literal, $bi: literal, $s: literal, $o: literal, $sy: literal) => { - assert_eq!($u != 0, $value.is_undefined()); - assert_eq!($n != 0, $value.is_null()); - assert_eq!($b != 0, $value.is_bool()); - assert_eq!($i != 0, $value.is_integer32()); - assert_eq!($f != 0, $value.is_float64()); - assert_eq!($bi != 0, $value.is_bigint()); - assert_eq!($s != 0, $value.is_string()); - assert_eq!($o != 0, $value.is_object()); - assert_eq!($sy != 0, $value.is_symbol()); - }; - (@@as $value: ident, $u: literal, $n: literal, $b: literal, $i: literal, $f: literal, $bi: literal, $s: literal, $o: literal, $sy: literal) => { - if $b == 0 { assert_eq!($value.as_bool(), None); } - if $i == 0 { assert_eq!($value.as_integer32(), None); } - if $f == 0 { assert_eq!($value.as_float64(), None); } - if $bi == 0 { assert_eq!($value.as_bigint(), None); } - if $s == 0 { assert_eq!($value.as_string(), None); } - if $o == 0 { assert_eq!($value.as_object(), None); } - if $sy == 0 { assert_eq!($value.as_symbol(), None); } - }; - ($value: ident is undefined) => { - assert_type!(@@is $value, 1, 0, 0, 0, 0, 0, 0, 0, 0); - assert_eq!($value.as_variant(), JsVariant::Undefined); - }; - ($value: ident is null) => { - assert_type!(@@is $value, 0, 1, 0, 0, 0, 0, 0, 0, 0); - assert_eq!($value.as_variant(), JsVariant::Null); - }; - ($value: ident is bool($scalar: ident)) => { - assert_type!(@@is $value, 0, 0, 1, 0, 0, 0, 0, 0, 0); - assert_type!(@@as $value, 0, 0, 1, 0, 0, 0, 0, 0, 0); - assert_eq!(Some($scalar), $value.as_bool()); - assert_eq!($value.as_variant(), JsVariant::Boolean($scalar)); - }; - ($value: ident is integer($scalar: ident)) => { - assert_type!(@@is $value, 0, 0, 0, 1, 0, 0, 0, 0, 0); - assert_type!(@@as $value, 0, 0, 0, 1, 0, 0, 0, 0, 0); - assert_eq!(Some($scalar), $value.as_integer32()); - assert_eq!($value.as_variant(), JsVariant::Integer32($scalar)); - }; - ($value: ident is float($scalar: ident)) => { - assert_type!(@@is $value, 0, 0, 0, 0, 1, 0, 0, 0, 0); - assert_type!(@@as $value, 0, 0, 0, 0, 1, 0, 0, 0, 0); - assert_eq!(Some($scalar), $value.as_float64()); - // Verify parity. - assert_eq!(Some(1.0 / $scalar), $value.as_float64().map(|f| 1.0 / f)); - assert_eq!($value.as_variant(), JsVariant::Float64($scalar)); - - // Verify that the clone is still the same. - let new_value = $value.clone(); - - assert_eq!(Some($scalar), new_value.as_float64()); - assert_eq!($value.as_float64(), new_value.as_float64()); - // Verify parity. - assert_eq!(Some(1.0 / $scalar), new_value.as_float64().map(|f| 1.0 / f)); - assert_eq!(new_value.as_variant(), JsVariant::Float64($scalar)); - - let JsVariant::Float64(new_scalar) = new_value.as_variant() else { - panic!("Expected Float64, got {:?}", new_value.as_variant()); - }; - assert_eq!(Some(new_scalar), new_value.as_float64()); - assert_eq!($value.as_float64(), new_value.as_float64()); - // Verify parity. - assert_eq!(Some(1.0 / new_scalar), new_value.as_float64().map(|f| 1.0 / f)); - assert_eq!(new_value.as_variant(), JsVariant::Float64(new_scalar)); - }; - ($value: ident is nan) => { - assert_type!(@@is $value, 0, 0, 0, 0, 1, 0, 0, 0, 0); - assert_type!(@@as $value, 0, 0, 0, 0, 1, 0, 0, 0, 0); - assert!($value.as_float64().unwrap().is_nan()); - assert!(matches!($value.as_variant(), JsVariant::Float64(f) if f.is_nan())); - }; - ($value: ident is bigint($scalar: ident)) => { - assert_type!(@@is $value, 0, 0, 0, 0, 0, 1, 0, 0, 0); - assert_type!(@@as $value, 0, 0, 0, 0, 0, 1, 0, 0, 0); - assert_eq!(Some(&$scalar), $value.as_bigint()); - assert_eq!($value.as_variant(), JsVariant::BigInt(&$scalar)); - }; - ($value: ident is object($scalar: ident)) => { - assert_type!(@@is $value, 0, 0, 0, 0, 0, 0, 0, 1, 0); - assert_type!(@@as $value, 0, 0, 0, 0, 0, 0, 0, 1, 0); - assert_eq!(Some(&$scalar), $value.as_object()); - assert_eq!($value.as_variant(), JsVariant::Object(&$scalar)); - }; - ($value: ident is symbol($scalar: ident)) => { - assert_type!(@@is $value, 0, 0, 0, 0, 0, 0, 0, 0, 1); - assert_type!(@@as $value, 0, 0, 0, 0, 0, 0, 0, 0, 1); - assert_eq!(Some(&$scalar), $value.as_symbol()); - assert_eq!($value.as_variant(), JsVariant::Symbol(&$scalar)); - }; - ($value: ident is string($scalar: ident)) => { - assert_type!(@@is $value, 0, 0, 0, 0, 0, 0, 1, 0, 0); - assert_type!(@@as $value, 0, 0, 0, 0, 0, 0, 1, 0, 0); - assert_eq!(Some(&$scalar), $value.as_string()); - assert_eq!($value.as_variant(), JsVariant::String(&$scalar)); - }; -} - -#[test] -fn null() { - let v = InnerValue::null(); - assert_type!(v is null); -} - -#[test] -fn undefined() { - let v = InnerValue::undefined(); - assert_type!(v is undefined); -} - -#[test] -fn boolean() { - let v = InnerValue::boolean(true); - assert_type!(v is bool(true)); - - let v = InnerValue::boolean(false); - assert_type!(v is bool(false)); -} - -#[test] -fn integer() { - fn assert_integer(i: i32) { - let v = InnerValue::integer32(i); - assert_type!(v is integer(i)); - } - - assert_integer(0); - assert_integer(1); - assert_integer(-1); - assert_integer(42); - assert_integer(-42); - assert_integer(i32::MAX); - assert_integer(i32::MIN); - assert_integer(i32::MAX - 1); - assert_integer(i32::MIN + 1); -} - -#[test] -#[allow(clippy::float_cmp)] -fn float() { - fn assert_float(f: f64) { - let v = InnerValue::float64(f); - assert_type!(v is float(f)); - } - - assert_float(0.0); - assert_float(-0.0); - assert_float(0.1 + 0.2); - assert_float(-42.123); - assert_float(f64::INFINITY); - assert_float(f64::NEG_INFINITY); - - // Some edge cases around zeroes. - let neg_zero = InnerValue::float64(-0.0); - assert!(neg_zero.as_float64().unwrap().is_sign_negative()); - assert_eq!(0.0f64, neg_zero.as_float64().unwrap()); - - let pos_zero = InnerValue::float64(0.0); - assert!(!pos_zero.as_float64().unwrap().is_sign_negative()); - assert_eq!(0.0f64, pos_zero.as_float64().unwrap()); - - assert_eq!(pos_zero.as_float64(), neg_zero.as_float64()); - - let nan = InnerValue::float64(f64::NAN); - assert_type!(nan is nan); -} - -#[test] -fn bigint() { - let bigint = JsBigInt::from(42); - let v = InnerValue::bigint(bigint.clone()); - assert_type!(v is bigint(bigint)); -} +//! The `[InnerValue]` type is an opaque type that can be either an enum of possible +//! JavaScript value types, or a 64-bits float that represents a NaN-boxed JavaScript +//! value, depending on feature flags. By default, the behaviour is to use the +//! enumeration. -#[test] -fn object() { - let object = JsObject::with_null_proto(); - let v = InnerValue::object(object.clone()); - assert_type!(v is object(object)); -} +#[cfg(feature = "nan-box-jsvalue")] +mod nan_boxed; -#[test] -fn string() { - let str = crate::js_string!("Hello World"); - let v = InnerValue::string(str.clone()); - assert_type!(v is string(str)); -} +#[cfg(feature = "nan-box-jsvalue")] +pub(crate) use nan_boxed::NanBoxedValue as InnerValue; -#[test] -fn symbol() { - let sym = JsSymbol::new(Some(JsString::from("Hello World"))).unwrap(); - let v = InnerValue::symbol(sym.clone()); - assert_type!(v is symbol(sym)); +#[cfg(not(feature = "nan-box-jsvalue"))] +mod enum_value; - let sym = JsSymbol::new(None).unwrap(); - let v = InnerValue::symbol(sym.clone()); - assert_type!(v is symbol(sym)); -} +#[cfg(not(feature = "nan-box-jsvalue"))] +pub(crate) use enum_value::EnumBasedValue as InnerValue; diff --git a/core/engine/src/value/inner/enum_value.rs b/core/engine/src/value/inner/enum_value.rs new file mode 100644 index 00000000000..c526d0ca58d --- /dev/null +++ b/core/engine/src/value/inner/enum_value.rs @@ -0,0 +1,254 @@ +//! This `[JsValue]` inner type is an opaque enum implementing the same +//! interface as the NanBoxedValue type, but using an enum instead of +//! a 64-bits float. + +use crate::{JsBigInt, JsObject, JsSymbol}; +use boa_engine::JsVariant; +use boa_gc::{custom_trace, Finalize, Trace}; +use boa_string::JsString; + +#[derive(Clone, Debug)] +pub(crate) enum EnumBasedValue { + Undefined, + Null, + Boolean(bool), + Integer32(i32), + Float64(f64), + BigInt(JsBigInt), + Object(JsObject), + Symbol(JsSymbol), + String(JsString), +} + +impl Finalize for EnumBasedValue { + fn finalize(&self) { + if let Some(o) = self.as_object() { + o.finalize(); + } + } +} + +#[allow(unsafe_op_in_unsafe_fn)] +unsafe impl Trace for EnumBasedValue { + custom_trace! {this, mark, { + if let Some(o) = this.as_object() { + mark(o); + } + }} +} + +impl EnumBasedValue { + /// Returns a `InnerValue` from a Null. + #[must_use] + #[inline] + pub(crate) const fn null() -> Self { + Self::Null + } + + /// Returns a `InnerValue` from an undefined. + #[must_use] + #[inline] + pub(crate) const fn undefined() -> Self { + Self::Undefined + } + + /// Returns a `InnerValue` from a 64-bits float. If the float is `NaN`, + /// it will be reduced to a canonical `NaN` representation. + #[must_use] + #[inline] + pub(crate) const fn float64(value: f64) -> Self { + Self::Float64(value) + } + + /// Returns a `InnerValue` from a 32-bits integer. + #[must_use] + #[inline] + pub(crate) const fn integer32(value: i32) -> Self { + Self::Integer32(value) + } + + /// Returns a `InnerValue` from a boolean. + #[must_use] + #[inline] + pub(crate) const fn boolean(value: bool) -> Self { + Self::Boolean(value) + } + + /// Returns a `InnerValue` from a boxed `[JsBigInt]`. + #[must_use] + #[inline] + pub(crate) fn bigint(value: JsBigInt) -> Self { + Self::BigInt(value) + } + + /// Returns a `InnerValue` from a boxed `[JsObject]`. + #[must_use] + #[inline] + pub(crate) fn object(value: JsObject) -> Self { + Self::Object(value) + } + + /// Returns a `InnerValue` from a boxed `[JsSymbol]`. + #[must_use] + #[inline] + pub(crate) fn symbol(value: JsSymbol) -> Self { + Self::Symbol(value) + } + + /// Returns a `InnerValue` from a boxed `[JsString]`. + #[must_use] + #[inline] + pub(crate) fn string(value: JsString) -> Self { + Self::String(value) + } + + /// Returns true if a value is undefined. + #[must_use] + #[inline] + pub(crate) const fn is_undefined(&self) -> bool { + matches!(self, Self::Undefined) + } + + /// Returns true if a value is null. + #[must_use] + #[inline] + pub(crate) const fn is_null(&self) -> bool { + matches!(self, Self::Null) + } + + /// Returns true if a value is a boolean. + #[must_use] + #[inline] + pub(crate) const fn is_bool(&self) -> bool { + matches!(self, Self::Boolean(_)) + } + + /// Returns true if a value is a 64-bits float. + #[must_use] + #[inline] + pub(crate) const fn is_float64(&self) -> bool { + matches!(self, Self::Float64(_)) + } + + /// Returns true if a value is a 32-bits integer. + #[must_use] + #[inline] + pub(crate) const fn is_integer32(&self) -> bool { + matches!(self, Self::Integer32(_)) + } + + /// Returns true if a value is a `[JsBigInt]`. A `NaN` will not match here. + #[must_use] + #[inline] + pub(crate) const fn is_bigint(&self) -> bool { + matches!(self, Self::BigInt(_)) + } + + /// Returns true if a value is a boxed Object. + #[must_use] + #[inline] + pub(crate) const fn is_object(&self) -> bool { + matches!(self, Self::Object(_)) + } + + /// Returns true if a value is a boxed Symbol. + #[must_use] + #[inline] + pub(crate) const fn is_symbol(&self) -> bool { + matches!(self, Self::Symbol(_)) + } + + /// Returns true if a value is a boxed String. + #[must_use] + #[inline] + pub(crate) const fn is_string(&self) -> bool { + matches!(self, Self::String(_)) + } + + /// Returns the value as an f64 if it is a float. + #[must_use] + #[inline] + pub(crate) const fn as_float64(&self) -> Option { + match self { + Self::Float64(value) => Some(*value), + _ => None, + } + } + + /// Returns the value as an i32 if it is an integer. + #[must_use] + #[inline] + pub(crate) const fn as_integer32(&self) -> Option { + match self { + Self::Integer32(value) => Some(*value), + _ => None, + } + } + + /// Returns the value as a boolean if it is a boolean. + #[must_use] + #[inline] + pub(crate) const fn as_bool(&self) -> Option { + match self { + Self::Boolean(value) => Some(*value), + _ => None, + } + } + + /// Returns the value as a boxed `[JsBigInt]`. + #[must_use] + #[inline] + pub(crate) const fn as_bigint(&self) -> Option<&JsBigInt> { + match self { + Self::BigInt(value) => Some(value), + _ => None, + } + } + + /// Returns the value as a boxed `[JsObject]`. + #[must_use] + #[inline] + pub(crate) const fn as_object(&self) -> Option<&JsObject> { + match self { + Self::Object(value) => Some(value), + _ => None, + } + } + + /// Returns the value as a boxed `[JsSymbol]`. + #[must_use] + #[inline] + pub(crate) const fn as_symbol(&self) -> Option<&JsSymbol> { + match self { + Self::Symbol(value) => Some(value), + _ => None, + } + } + + /// Returns the value as a boxed `[JsString]`. + #[must_use] + #[inline] + pub(crate) const fn as_string(&self) -> Option<&JsString> { + match self { + Self::String(value) => Some(value), + _ => None, + } + } + + /// Returns the `[JsVariant]` of this inner value. + #[must_use] + #[inline] + pub(crate) const fn as_variant(&self) -> JsVariant<'_> { + match self { + Self::Undefined => JsVariant::Undefined, + Self::Null => JsVariant::Null, + Self::Boolean(v) => JsVariant::Boolean(*v), + Self::Integer32(v) => JsVariant::Integer32(*v), + Self::Float64(v) => JsVariant::Float64(*v), + Self::BigInt(v) => JsVariant::BigInt(v), + Self::Object(v) => JsVariant::Object(v), + Self::Symbol(v) => JsVariant::Symbol(v), + Self::String(v) => JsVariant::String(v), + } + } +} diff --git a/core/engine/src/value/inner/nan_boxed.rs b/core/engine/src/value/inner/nan_boxed.rs new file mode 100644 index 00000000000..41050f35606 --- /dev/null +++ b/core/engine/src/value/inner/nan_boxed.rs @@ -0,0 +1,864 @@ +//! This `[JsValue]` inner type is a NaN-boxed value, which is a 64-bits value +//! that can represent any JavaScript value. If the integer is a non-NaN value, +//! it will be stored as a 64-bits float. If it is a `f64::NAN` value, it will +//! be stored as a quiet `NaN` value. Subnormal numbers are regular float. +//! +//! For any other type of values, the value will be stored as a 51-bits non-zero +//! integer. +//! +//! In short, the memory layout of a NaN-boxed value is as follows: +//! +//! | Type of | Bit Layout | Comment | +//! |-------------------|------------|---------| +//! | `+Infinity` | `7FF0:0000:0000:0000` | | +//! | `-Infinity` | `FFF0:0000:0000:0000` | | +//! | `NAN` (quiet) | `7FF8:0000:0000:0000` | | +//! | `NAN` (signaling) | `FFF8:0000:0000:0000` | | +//! | `Undefined` | `7FF4:0000:0000:0000` | | +//! | `Null` | `7FF5:0000:0000:0000` | | +//! | `False` | `7FF6:0000:0000:0000` | | +//! | `True` | `7FF6:0000:0000:0001` | | +//! | `Integer32` | `7FF7:0000:IIII:IIII` | 32-bits integer. | +//! | `BigInt` | `7FF[8-F]:PPPP:PPPP:PPPP \| 0` | 51-bits pointer. Assumes non-null pointer. | +//! | `Object` | `7FF[8-F]:PPPP:PPPP:PPPP \| 1` | 51-bits pointer. | +//! | `Symbol` | `7FF[8-F]:PPPP:PPPP:PPPP \| 2` | 51-bits pointer. | +//! | `String` | `7FF[8-F]:PPPP:PPPP:PPPP \| 3` | 51-bits pointer. | +//! | `Float64` | Any other values. | | +//! +//! Pointers have the highest bit (in the `NaN` tag) set to 1, so they +//! can represent any value from `0x8000_0000_0000` to `0xFFFF_FFFF_FFFF`. +//! The last 2 bits of the pointer is used to store the type of the value. +//! +//! The pointers are assumed to never be NULL, and as such no clash +//! with regular NAN should happen. +//! +//! This only works on 4-bits aligned values, which is asserted when the +//! `InnerValue` is created. + +use crate::{JsBigInt, JsObject, JsSymbol, JsVariant}; +use boa_gc::{custom_trace, Finalize, Trace}; +use boa_string::JsString; +use core::fmt; +use static_assertions::const_assert; + +/// Transform an `u64` into `f64`, by its bytes. This is necessary for +/// keeping the MSRV at 1.82, as `f64::from_bits` is not const until +/// 1.83. +#[inline] +const fn f64_from_bits(bits: u64) -> f64 { + unsafe { std::mem::transmute(bits) } +} + +/// Transform a `f64` into `u64`, by its bytes. This is necessary for +/// keeping the MSRV at 1.82, as `f64::to_bits` is not const until +/// 1.83. +#[inline] +const fn f64_to_bits(bits: f64) -> u64 { + unsafe { std::mem::transmute(bits) } +} + +/// Check that a float is a `NaN`. This is necessary for keeping the MSRV +/// at 1.82, as `f64::is_nan` is not const until 1.53. +#[inline] +#[allow(clippy::eq_op, clippy::float_cmp)] +const fn f64_is_nan(f: f64) -> bool { + f != f +} + +// We cannot NaN-box pointers larger than 64 bits. +const_assert!(size_of::() <= size_of::()); + +// We cannot NaN-box pointers that are not 4-bytes aligned. +const_assert!(align_of::<*mut ()>() >= 4); + +/// Internal module for bit masks and constants. +/// +/// All bit magic is done here. +mod bits { + use super::{f64_from_bits, f64_is_nan, f64_to_bits}; + use boa_engine::{JsBigInt, JsObject, JsSymbol}; + use boa_string::JsString; + + /// Undefined value in `u64`. + pub(super) const UNDEFINED: u64 = 0x7FF4_0000_0000_0000; + + /// Null value in `u64`. + pub(super) const NULL: u64 = 0x7FF5_0000_0000_0000; + + /// False value in `u64`. + pub(super) const FALSE: u64 = 0x7FF6_0000_0000_0000; + + /// True value in `u64`. + pub(super) const TRUE: u64 = 0x7FF6_0000_0000_0001; + + /// Integer32 start (zero) value in `u64`. + pub(super) const INTEGER32_ZERO: u64 = 0x7FF7_0000_0000_0000; + + /// Integer32 end (MAX) value in `u64`. + pub(super) const INTEGER32_MAX: u64 = 0x7FF7_0000_FFFF_FFFF; + + /// Pointer starting point in `u64`. + pub(super) const POINTER_START: u64 = 0x7FF8_0000_0000_0000; + + /// Pointer starting point in `u64`. + pub(super) const POINTER_END: u64 = 0x7FFF_FFFF_FFFF_FFFF; + + /// Pointer types mask in `u64`. + pub(super) const POINTER_MASK: u64 = 0x0007_FFFF_FFFF_FFFC; + + /// Pointer mask for the type of the pointer. + pub(super) const POINTER_TYPE_MASK: u64 = 0x0003; + + /// Pointer value for `BigInt`. + pub(super) const BIGINT: u64 = 0x0000; + + /// Pointer value for `JsObject`. + pub(super) const OBJECT: u64 = 0x0001; + + /// Pointer value for `JsSymbol`. + pub(super) const SYMBOL: u64 = 0x0002; + + /// Pointer value for `JsString`. + pub(super) const STRING: u64 = 0x0003; + + /// NAN value in `u64`. + pub(super) const NAN: u64 = 0x7FF8_0000_0000_0000; + + /// Checks that a value is a valid boolean (either true or false). + #[inline] + pub(super) const fn is_bool(value: u64) -> bool { + value == TRUE || value == FALSE + } + + /// Checks that a value is a valid float, not a tagged nan boxed value. + #[inline] + pub(super) const fn is_float(value: u64) -> bool { + let as_float = f64_from_bits(value); + !f64_is_nan(as_float) || value == NAN + } + + /// Checks that a value is a valid undefined. + #[inline] + pub(super) const fn is_undefined(value: u64) -> bool { + value == UNDEFINED + } + + /// Checks that a value is a valid null. + #[inline] + pub(super) const fn is_null(value: u64) -> bool { + value == NULL + } + + /// Checks that a value is a valid integer32. + #[inline] + pub(super) const fn is_integer32(value: u64) -> bool { + value & INTEGER32_ZERO == INTEGER32_ZERO + } + + /// Untag a value as a pointer. + #[inline] + pub(super) const fn is_pointer(value: u64) -> bool { + value & POINTER_START == POINTER_START + } + + /// Checks that a value is a valid `BigInt`. + #[inline] + #[allow(clippy::verbose_bit_mask)] + pub(super) const fn is_bigint(value: u64) -> bool { + is_pointer(value) && (value & POINTER_TYPE_MASK == BIGINT) && (value & POINTER_MASK) != 0 + } + + /// Checks that a value is a valid Object. + #[inline] + pub(super) const fn is_object(value: u64) -> bool { + is_pointer(value) && (value & POINTER_TYPE_MASK == OBJECT) && (value & POINTER_MASK) != 0 + } + + /// Checks that a value is a valid Symbol. + #[inline] + pub(super) const fn is_symbol(value: u64) -> bool { + is_pointer(value) && (value & POINTER_TYPE_MASK == SYMBOL) && (value & POINTER_MASK) != 0 + } + + /// Checks that a value is a valid String. + #[inline] + pub(super) const fn is_string(value: u64) -> bool { + is_pointer(value) && (value & POINTER_TYPE_MASK == STRING) && (value & POINTER_MASK) != 0 + } + + /// Returns a tagged u64 of a 64-bits float. + #[inline] + pub(super) const fn tag_f64(value: f64) -> u64 { + if f64_is_nan(value) { + // Reduce any NAN to a canonical NAN representation. + f64_to_bits(f64::NAN) + } else { + f64_to_bits(value) + } + } + + /// Returns a tagged u64 of a 32-bits integer. + #[inline] + pub(super) const fn tag_i32(value: i32) -> u64 { + INTEGER32_ZERO | value as u64 & 0xFFFF_FFFFu64 + } + + /// Returns a i32-bits from a tagged integer. + #[inline] + pub(super) const fn untag_i32(value: u64) -> i32 { + ((value & 0xFFFF_FFFFu64) | 0xFFFF_FFFF_0000_0000u64) as i32 + } + + /// Returns a tagged u64 of a boolean. + #[inline] + pub(super) const fn tag_bool(value: bool) -> u64 { + if value { + TRUE + } else { + FALSE + } + } + + /// Returns a tagged u64 of a boxed `[JsBigInt]`. + /// + /// # Safety + /// The pointer must be 4-bits aligned and cannot exceed 51-bits. This will + /// result in a panic. Also, the object is not checked for validity. + /// + /// The box is forgotten after this operation. It must be dropped separately, + /// by calling `[Self::drop_pointer]`. + #[inline] + #[allow(clippy::identity_op)] + pub(super) unsafe fn tag_bigint(value: Box) -> u64 { + let value = Box::into_raw(value) as u64; + let value_masked: u64 = value & POINTER_MASK; + + // Assert alignment and location of the pointer. + assert_eq!( + value_masked, value, + "Pointer is not 4-bits aligned or over 51-bits." + ); + // Cannot have a null pointer for bigint. + assert_ne!(value_masked, 0, "Pointer is NULL."); + + // Simply cast for bits. + POINTER_START | 0 | value_masked + } + + /// Returns a tagged u64 of a boxed `[JsObject]`. + /// + /// # Safety + /// The pointer must be 4-bits aligned and cannot exceed 51-bits. This will + /// result in a panic. Also, the object is not checked for validity. + /// + /// The box is forgotten after this operation. It must be dropped separately, + /// by calling `[Self::drop_pointer]`. + #[inline] + pub(super) unsafe fn tag_object(value: Box) -> u64 { + let value = Box::into_raw(value) as u64; + let value_masked: u64 = value & POINTER_MASK; + + // Assert alignment and location of the pointer. + assert_eq!( + value_masked, value, + "Pointer is not 4-bits aligned or over 51-bits." + ); + // Cannot have a null pointer for bigint. + assert_ne!(value_masked, 0, "Pointer is NULL."); + + // Simply cast for bits. + POINTER_START | 1 | value_masked + } + + /// Returns a tagged u64 of a boxed `[JsSymbol]`. + /// + /// # Safety + /// The pointer must be 4-bits aligned and cannot exceed 51-bits. This will + /// result in a panic. Also, the object is not checked for validity. + /// + /// The box is forgotten after this operation. It must be dropped separately, + /// by calling `[Self::drop_pointer]`. + #[inline] + pub(super) unsafe fn tag_symbol(value: Box) -> u64 { + let value = Box::into_raw(value) as u64; + let value_masked: u64 = value & POINTER_MASK; + + // Assert alignment and location of the pointer. + assert_eq!( + value_masked, value, + "Pointer is not 4-bits aligned or over 51-bits." + ); + // Cannot have a null pointer for bigint. + assert_ne!(value_masked, 0, "Pointer is NULL."); + + // Simply cast for bits. + POINTER_START | 2 | value_masked + } + + /// Returns a tagged u64 of a boxed `[JsString]`. + /// + /// # Safety + /// The pointer must be 4-bits aligned and cannot exceed 51-bits. This will + /// result in a panic. Also, the object is not checked for validity. + /// + /// The box is forgotten after this operation. It must be dropped separately, + /// by calling `[Self::drop_pointer]`. + #[inline] + pub(super) unsafe fn tag_string(value: Box) -> u64 { + let value = Box::into_raw(value) as u64; + let value_masked: u64 = value & POINTER_MASK; + + // Assert alignment and location of the pointer. + assert_eq!( + value_masked, value, + "Pointer is not 4-bits aligned or over 51-bits." + ); + // Cannot have a null pointer for bigint. + assert_ne!(value_masked, 0, "Pointer is NULL."); + + // Simply cast for bits. + POINTER_START | 3 | value_masked + } + + /// Returns an Option of a boxed `[JsBigInt]` from a tagged value. + #[inline] + pub(super) const fn as_bigint<'a>(value: u64) -> Option<&'a JsBigInt> { + if is_bigint(value) { + // This is safe because the boxed object will always be on the heap. + let ptr = value & POINTER_MASK; + unsafe { Some(&*(ptr as *const _)) } + } else { + None + } + } + + /// Returns an Option of a boxed `[JsObject]` from a tagged value. + #[inline] + pub(super) const fn as_object<'a>(value: u64) -> Option<&'a JsObject> { + if is_object(value) { + // This is safe because the boxed object will always be on the heap. + let ptr = value & POINTER_MASK; + unsafe { Some(&*(ptr as *const _)) } + } else { + None + } + } +} + +// Verify that all representations of NanBitTag ARE NAN, but don't match static NAN. +// The only exception to this rule is BigInt, which assumes that the pointer is +// non-null. The static f64::NAN is equal to BigInt. +const_assert!(f64_is_nan(f64_from_bits(bits::UNDEFINED))); +const_assert!(f64_is_nan(f64_from_bits(bits::NULL))); +const_assert!(f64_is_nan(f64_from_bits(bits::FALSE))); +const_assert!(f64_is_nan(f64_from_bits(bits::TRUE))); +const_assert!(f64_is_nan(f64_from_bits(bits::INTEGER32_ZERO))); +const_assert!(f64_is_nan(f64_from_bits(bits::POINTER_START))); +const_assert!(f64_is_nan(f64_from_bits(bits::POINTER_END))); + +/// A NaN-boxed `[JsValue]`'s inner. +pub(crate) struct NanBoxedValue(pub u64); + +impl fmt::Debug for NanBoxedValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.as_variant() { + JsVariant::Undefined => f.debug_tuple("Undefined").finish(), + JsVariant::Null => f.debug_tuple("Null").finish(), + JsVariant::Boolean(b) => f.debug_tuple("Boolean").field(&b).finish(), + JsVariant::Float64(n) => f.debug_tuple("Float64").field(&n).finish(), + JsVariant::Integer32(n) => f.debug_tuple("Integer32").field(&n).finish(), + JsVariant::BigInt(n) => f.debug_tuple("BigInt").field(&n).finish(), + JsVariant::Object(n) => f.debug_tuple("Object").field(&n).finish(), + JsVariant::Symbol(n) => f.debug_tuple("Symbol").field(&n).finish(), + JsVariant::String(n) => f.debug_tuple("String").field(&n).finish(), + } + } +} + +impl Finalize for NanBoxedValue { + fn finalize(&self) { + if let Some(o) = self.as_object() { + o.finalize(); + } + } +} + +#[allow(unsafe_op_in_unsafe_fn)] +unsafe impl Trace for NanBoxedValue { + custom_trace! {this, mark, { + if let Some(o) = this.as_object() { + mark(o); + } + }} +} + +impl Clone for NanBoxedValue { + fn clone(&self) -> Self { + if let Some(o) = self.as_object() { + Self::object(o.clone()) + } else if let Some(b) = self.as_bigint() { + Self::bigint(b.clone()) + } else if let Some(s) = self.as_symbol() { + Self::symbol(s.clone()) + } else if let Some(s) = self.as_string() { + Self::string(s.clone()) + } else { + Self(self.0) + } + } +} + +impl NanBoxedValue { + /// Creates a new `InnerValue` from an u64 value without checking the validity + /// of the value. + #[must_use] + #[inline] + const fn from_inner_unchecked(inner: u64) -> Self { + Self(inner) + } + + /// Returns a `InnerValue` from a Null. + #[must_use] + #[inline] + pub(crate) const fn null() -> Self { + Self::from_inner_unchecked(bits::NULL) + } + + /// Returns a `InnerValue` from an undefined. + #[must_use] + #[inline] + pub(crate) const fn undefined() -> Self { + Self::from_inner_unchecked(bits::UNDEFINED) + } + + /// Returns a `InnerValue` from a 64-bits float. If the float is `NaN`, + /// it will be reduced to a canonical `NaN` representation. + #[must_use] + #[inline] + pub(crate) const fn float64(value: f64) -> Self { + Self::from_inner_unchecked(bits::tag_f64(value)) + } + + /// Returns a `InnerValue` from a 32-bits integer. + #[must_use] + #[inline] + pub(crate) const fn integer32(value: i32) -> Self { + Self::from_inner_unchecked(bits::tag_i32(value)) + } + + /// Returns a `InnerValue` from a boolean. + #[must_use] + #[inline] + pub(crate) const fn boolean(value: bool) -> Self { + Self::from_inner_unchecked(bits::tag_bool(value)) + } + + /// Returns a `InnerValue` from a boxed `[JsBigInt]`. + #[must_use] + #[inline] + pub(crate) fn bigint(value: JsBigInt) -> Self { + Self::from_inner_unchecked(unsafe { bits::tag_bigint(Box::new(value)) }) + } + + /// Returns a `InnerValue` from a boxed `[JsObject]`. + #[must_use] + #[inline] + pub(crate) fn object(value: JsObject) -> Self { + Self::from_inner_unchecked(unsafe { bits::tag_object(Box::new(value)) }) + } + + /// Returns a `InnerValue` from a boxed `[JsSymbol]`. + #[must_use] + #[inline] + pub(crate) fn symbol(value: JsSymbol) -> Self { + Self::from_inner_unchecked(unsafe { bits::tag_symbol(Box::new(value)) }) + } + + /// Returns a `InnerValue` from a boxed `[JsString]`. + #[must_use] + #[inline] + pub(crate) fn string(value: JsString) -> Self { + Self::from_inner_unchecked(unsafe { bits::tag_string(Box::new(value)) }) + } + + /// Returns true if a value is undefined. + #[must_use] + #[inline] + pub(crate) const fn is_undefined(&self) -> bool { + bits::is_undefined(self.0) + } + + /// Returns true if a value is null. + #[must_use] + #[inline] + pub(crate) const fn is_null(&self) -> bool { + bits::is_null(self.0) + } + + /// Returns true if a value is a boolean. + #[must_use] + #[inline] + pub(crate) const fn is_bool(&self) -> bool { + bits::is_bool(self.0) + } + + /// Returns true if a value is a 64-bits float. + #[must_use] + #[inline] + pub(crate) const fn is_float64(&self) -> bool { + bits::is_float(self.0) + } + + /// Returns true if a value is a 32-bits integer. + #[must_use] + #[inline] + pub(crate) const fn is_integer32(&self) -> bool { + bits::is_integer32(self.0) + } + + /// Returns true if a value is a `[JsBigInt]`. A `NaN` will not match here. + #[must_use] + #[inline] + pub(crate) const fn is_bigint(&self) -> bool { + bits::is_bigint(self.0) + } + + /// Returns true if a value is a boxed Object. + #[must_use] + #[inline] + pub(crate) const fn is_object(&self) -> bool { + bits::is_object(self.0) + } + + /// Returns true if a value is a boxed Symbol. + #[must_use] + #[inline] + pub(crate) const fn is_symbol(&self) -> bool { + bits::is_symbol(self.0) + } + + /// Returns true if a value is a boxed String. + #[must_use] + #[inline] + pub(crate) const fn is_string(&self) -> bool { + bits::is_string(self.0) + } + + /// Returns the value as a f64 if it is a float. + #[must_use] + #[inline] + pub(crate) const fn as_float64(&self) -> Option { + if self.is_float64() { + Some(f64_from_bits(self.0)) + } else { + None + } + } + + /// Returns the value as an i32 if it is an integer. + #[must_use] + #[inline] + pub(crate) const fn as_integer32(&self) -> Option { + if self.is_integer32() { + Some(bits::untag_i32(self.0)) + } else { + None + } + } + + /// Returns the value as a boolean if it is a boolean. + #[must_use] + #[inline] + pub(crate) const fn as_bool(&self) -> Option { + match self.0 { + bits::FALSE => Some(false), + bits::TRUE => Some(true), + _ => None, + } + } + + /// Returns the value as a boxed `[JsBigInt]`. + #[must_use] + #[inline] + pub(crate) const fn as_bigint(&self) -> Option<&JsBigInt> { + bits::as_bigint::<'_>(self.0) + } + + /// Returns the value as a boxed `[JsObject]`. + #[must_use] + #[inline] + pub(crate) const fn as_object(&self) -> Option<&JsObject> { + bits::as_object::<'_>(self.0) + } + + /// Returns the value as a boxed `[JsSymbol]`. + #[must_use] + #[inline] + pub(crate) const fn as_symbol(&self) -> Option<&JsSymbol> { + if self.is_symbol() { + // This is safe because the boxed object will always be on the heap. + let ptr = self.0 & bits::POINTER_MASK; + unsafe { Some(&*(ptr as *const _)) } + } else { + None + } + } + + /// Returns the value as a boxed `[JsString]`. + #[must_use] + #[inline] + pub(crate) const fn as_string(&self) -> Option<&JsString> { + if self.is_string() { + // This is safe because the boxed object will always be on the heap. + let ptr = self.0 & bits::POINTER_MASK; + unsafe { Some(&*(ptr as *const _)) } + } else { + None + } + } + + /// Returns the `[JsVariant]` of this inner value. + #[must_use] + #[inline] + pub(crate) const fn as_variant(&self) -> JsVariant<'_> { + match self.0 { + bits::UNDEFINED => JsVariant::Undefined, + bits::NULL => JsVariant::Null, + bits::FALSE => JsVariant::Boolean(false), + bits::TRUE => JsVariant::Boolean(true), + bits::INTEGER32_ZERO..=bits::INTEGER32_MAX => { + JsVariant::Integer32(bits::untag_i32(self.0)) + } + bits::NAN => JsVariant::Float64(f64::NAN), + bits::POINTER_START..=bits::POINTER_END => { + let ptr = self.0 & bits::POINTER_MASK; + match self.0 & bits::POINTER_TYPE_MASK { + bits::BIGINT => JsVariant::BigInt(unsafe { &*(ptr as *const _) }), + bits::OBJECT => JsVariant::Object(unsafe { &*(ptr as *const _) }), + bits::SYMBOL => JsVariant::Symbol(unsafe { &*(ptr as *const _) }), + bits::STRING => JsVariant::String(unsafe { &*(ptr as *const _) }), + _ => unreachable!(), + } + } + _ => JsVariant::Float64(f64_from_bits(self.0)), + } + } +} + +impl Drop for NanBoxedValue { + fn drop(&mut self) { + let maybe_ptr = self.0 & bits::POINTER_MASK; + + // Drop the pointer if it is a pointer. + if self.is_object() { + drop(unsafe { Box::from_raw(maybe_ptr as *mut JsObject) }); + } else if self.is_bigint() { + drop(unsafe { Box::from_raw(maybe_ptr as *mut JsBigInt) }); + } else if self.is_symbol() { + drop(unsafe { Box::from_raw(maybe_ptr as *mut JsSymbol) }); + } else if self.is_string() { + drop(unsafe { Box::from_raw(maybe_ptr as *mut JsString) }); + } + } +} + +#[cfg(test)] +macro_rules! assert_type { + (@@is $value: ident, $u: literal, $n: literal, $b: literal, $i: literal, $f: literal, $bi: literal, $s: literal, $o: literal, $sy: literal) => { + assert_eq!($u != 0, $value.is_undefined()); + assert_eq!($n != 0, $value.is_null()); + assert_eq!($b != 0, $value.is_bool()); + assert_eq!($i != 0, $value.is_integer32()); + assert_eq!($f != 0, $value.is_float64()); + assert_eq!($bi != 0, $value.is_bigint()); + assert_eq!($s != 0, $value.is_string()); + assert_eq!($o != 0, $value.is_object()); + assert_eq!($sy != 0, $value.is_symbol()); + }; + (@@as $value: ident, $u: literal, $n: literal, $b: literal, $i: literal, $f: literal, $bi: literal, $s: literal, $o: literal, $sy: literal) => { + if $b == 0 { assert_eq!($value.as_bool(), None); } + if $i == 0 { assert_eq!($value.as_integer32(), None); } + if $f == 0 { assert_eq!($value.as_float64(), None); } + if $bi == 0 { assert_eq!($value.as_bigint(), None); } + if $s == 0 { assert_eq!($value.as_string(), None); } + if $o == 0 { assert_eq!($value.as_object(), None); } + if $sy == 0 { assert_eq!($value.as_symbol(), None); } + }; + ($value: ident is undefined) => { + assert_type!(@@is $value, 1, 0, 0, 0, 0, 0, 0, 0, 0); + assert_eq!($value.as_variant(), JsVariant::Undefined); + }; + ($value: ident is null) => { + assert_type!(@@is $value, 0, 1, 0, 0, 0, 0, 0, 0, 0); + assert_eq!($value.as_variant(), JsVariant::Null); + }; + ($value: ident is bool($scalar: ident)) => { + assert_type!(@@is $value, 0, 0, 1, 0, 0, 0, 0, 0, 0); + assert_type!(@@as $value, 0, 0, 1, 0, 0, 0, 0, 0, 0); + assert_eq!(Some($scalar), $value.as_bool()); + assert_eq!($value.as_variant(), JsVariant::Boolean($scalar)); + }; + ($value: ident is integer($scalar: ident)) => { + assert_type!(@@is $value, 0, 0, 0, 1, 0, 0, 0, 0, 0); + assert_type!(@@as $value, 0, 0, 0, 1, 0, 0, 0, 0, 0); + assert_eq!(Some($scalar), $value.as_integer32()); + assert_eq!($value.as_variant(), JsVariant::Integer32($scalar)); + }; + ($value: ident is float($scalar: ident)) => { + assert_type!(@@is $value, 0, 0, 0, 0, 1, 0, 0, 0, 0); + assert_type!(@@as $value, 0, 0, 0, 0, 1, 0, 0, 0, 0); + assert_eq!(Some($scalar), $value.as_float64()); + // Verify parity. + assert_eq!(Some(1.0 / $scalar), $value.as_float64().map(|f| 1.0 / f)); + assert_eq!($value.as_variant(), JsVariant::Float64($scalar)); + + // Verify that the clone is still the same. + let new_value = $value.clone(); + + assert_eq!(Some($scalar), new_value.as_float64()); + assert_eq!($value.as_float64(), new_value.as_float64()); + // Verify parity. + assert_eq!(Some(1.0 / $scalar), new_value.as_float64().map(|f| 1.0 / f)); + assert_eq!(new_value.as_variant(), JsVariant::Float64($scalar)); + + let JsVariant::Float64(new_scalar) = new_value.as_variant() else { + panic!("Expected Float64, got {:?}", new_value.as_variant()); + }; + assert_eq!(Some(new_scalar), new_value.as_float64()); + assert_eq!($value.as_float64(), new_value.as_float64()); + // Verify parity. + assert_eq!(Some(1.0 / new_scalar), new_value.as_float64().map(|f| 1.0 / f)); + assert_eq!(new_value.as_variant(), JsVariant::Float64(new_scalar)); + }; + ($value: ident is nan) => { + assert_type!(@@is $value, 0, 0, 0, 0, 1, 0, 0, 0, 0); + assert_type!(@@as $value, 0, 0, 0, 0, 1, 0, 0, 0, 0); + assert!($value.as_float64().unwrap().is_nan()); + assert!(matches!($value.as_variant(), JsVariant::Float64(f) if f.is_nan())); + }; + ($value: ident is bigint($scalar: ident)) => { + assert_type!(@@is $value, 0, 0, 0, 0, 0, 1, 0, 0, 0); + assert_type!(@@as $value, 0, 0, 0, 0, 0, 1, 0, 0, 0); + assert_eq!(Some(&$scalar), $value.as_bigint()); + assert_eq!($value.as_variant(), JsVariant::BigInt(&$scalar)); + }; + ($value: ident is object($scalar: ident)) => { + assert_type!(@@is $value, 0, 0, 0, 0, 0, 0, 0, 1, 0); + assert_type!(@@as $value, 0, 0, 0, 0, 0, 0, 0, 1, 0); + assert_eq!(Some(&$scalar), $value.as_object()); + assert_eq!($value.as_variant(), JsVariant::Object(&$scalar)); + }; + ($value: ident is symbol($scalar: ident)) => { + assert_type!(@@is $value, 0, 0, 0, 0, 0, 0, 0, 0, 1); + assert_type!(@@as $value, 0, 0, 0, 0, 0, 0, 0, 0, 1); + assert_eq!(Some(&$scalar), $value.as_symbol()); + assert_eq!($value.as_variant(), JsVariant::Symbol(&$scalar)); + }; + ($value: ident is string($scalar: ident)) => { + assert_type!(@@is $value, 0, 0, 0, 0, 0, 0, 1, 0, 0); + assert_type!(@@as $value, 0, 0, 0, 0, 0, 0, 1, 0, 0); + assert_eq!(Some(&$scalar), $value.as_string()); + assert_eq!($value.as_variant(), JsVariant::String(&$scalar)); + }; +} + +#[test] +fn null() { + let v = NanBoxedValue::null(); + assert_type!(v is null); +} + +#[test] +fn undefined() { + let v = NanBoxedValue::undefined(); + assert_type!(v is undefined); +} + +#[test] +fn boolean() { + let v = NanBoxedValue::boolean(true); + assert_type!(v is bool(true)); + + let v = NanBoxedValue::boolean(false); + assert_type!(v is bool(false)); +} + +#[test] +fn integer() { + fn assert_integer(i: i32) { + let v = NanBoxedValue::integer32(i); + assert_type!(v is integer(i)); + } + + assert_integer(0); + assert_integer(1); + assert_integer(-1); + assert_integer(42); + assert_integer(-42); + assert_integer(i32::MAX); + assert_integer(i32::MIN); + assert_integer(i32::MAX - 1); + assert_integer(i32::MIN + 1); +} + +#[test] +#[allow(clippy::float_cmp)] +fn float() { + fn assert_float(f: f64) { + let v = NanBoxedValue::float64(f); + assert_type!(v is float(f)); + } + + assert_float(0.0); + assert_float(-0.0); + assert_float(0.1 + 0.2); + assert_float(-42.123); + assert_float(f64::INFINITY); + assert_float(f64::NEG_INFINITY); + + // Some edge cases around zeroes. + let neg_zero = NanBoxedValue::float64(-0.0); + assert!(neg_zero.as_float64().unwrap().is_sign_negative()); + assert_eq!(0.0f64, neg_zero.as_float64().unwrap()); + + let pos_zero = NanBoxedValue::float64(0.0); + assert!(!pos_zero.as_float64().unwrap().is_sign_negative()); + assert_eq!(0.0f64, pos_zero.as_float64().unwrap()); + + assert_eq!(pos_zero.as_float64(), neg_zero.as_float64()); + + let nan = NanBoxedValue::float64(f64::NAN); + assert_type!(nan is nan); +} + +#[test] +fn bigint() { + let bigint = JsBigInt::from(42); + let v = NanBoxedValue::bigint(bigint.clone()); + assert_type!(v is bigint(bigint)); +} + +#[test] +fn object() { + let object = JsObject::with_null_proto(); + let v = NanBoxedValue::object(object.clone()); + assert_type!(v is object(object)); +} + +#[test] +fn string() { + let str = crate::js_string!("Hello World"); + let v = NanBoxedValue::string(str.clone()); + assert_type!(v is string(str)); +} + +#[test] +fn symbol() { + let sym = JsSymbol::new(Some(JsString::from("Hello World"))).unwrap(); + let v = NanBoxedValue::symbol(sym.clone()); + assert_type!(v is symbol(sym)); + + let sym = JsSymbol::new(None).unwrap(); + let v = NanBoxedValue::symbol(sym.clone()); + assert_type!(v is symbol(sym)); +} From 40d5e5cc461102531ced2329e225868fcfd054c8 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Mon, 30 Dec 2024 18:08:39 -0500 Subject: [PATCH 18/24] Clippies --- core/engine/src/value/inner/enum_value.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/engine/src/value/inner/enum_value.rs b/core/engine/src/value/inner/enum_value.rs index c526d0ca58d..eb85f314c01 100644 --- a/core/engine/src/value/inner/enum_value.rs +++ b/core/engine/src/value/inner/enum_value.rs @@ -1,6 +1,6 @@ //! This `[JsValue]` inner type is an opaque enum implementing the same -//! interface as the NanBoxedValue type, but using an enum instead of -//! a 64-bits float. +//! interface as the `NanBoxedValue` type, but using an enum instead of +//! a 64-bits NAN-boxed float. use crate::{JsBigInt, JsObject, JsSymbol}; use boa_engine::JsVariant; From e6473853f24123f2027aaff887a3eb8ca6f7e213 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Wed, 1 Jan 2025 17:02:10 -0500 Subject: [PATCH 19/24] Remove debug logging --- core/engine/src/builtins/array/tests.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/core/engine/src/builtins/array/tests.rs b/core/engine/src/builtins/array/tests.rs index eaaf7d9d294..5903f93d93f 100644 --- a/core/engine/src/builtins/array/tests.rs +++ b/core/engine/src/builtins/array/tests.rs @@ -957,10 +957,6 @@ fn array_sort() { #[test] fn array_of_neg_zero() { run_test_actions([ - TestAction::assert_context(|_| { - eprintln!("test start..."); - true - }), TestAction::run("let arr = [-0, -0, -0, -0];"), // Assert the parity of all items of the list. TestAction::assert("arr.every(x => (1/x) === -Infinity)"), From 69bbf82c934d1b6e8ac3fea7ebff2c90d17ab99f Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Sat, 4 Jan 2025 19:38:14 -0800 Subject: [PATCH 20/24] Some simple attempts to improve performance with NonNull Seems to work --- core/engine/src/value/inner/nan_boxed.rs | 102 +++++++++++------------ 1 file changed, 48 insertions(+), 54 deletions(-) diff --git a/core/engine/src/value/inner/nan_boxed.rs b/core/engine/src/value/inner/nan_boxed.rs index 41050f35606..2e8630441d3 100644 --- a/core/engine/src/value/inner/nan_boxed.rs +++ b/core/engine/src/value/inner/nan_boxed.rs @@ -34,6 +34,7 @@ //! //! This only works on 4-bits aligned values, which is asserted when the //! `InnerValue` is created. +#![allow(clippy::inline_always)] use crate::{JsBigInt, JsObject, JsSymbol, JsVariant}; use boa_gc::{custom_trace, Finalize, Trace}; @@ -44,7 +45,7 @@ use static_assertions::const_assert; /// Transform an `u64` into `f64`, by its bytes. This is necessary for /// keeping the MSRV at 1.82, as `f64::from_bits` is not const until /// 1.83. -#[inline] +#[inline(always)] const fn f64_from_bits(bits: u64) -> f64 { unsafe { std::mem::transmute(bits) } } @@ -52,14 +53,14 @@ const fn f64_from_bits(bits: u64) -> f64 { /// Transform a `f64` into `u64`, by its bytes. This is necessary for /// keeping the MSRV at 1.82, as `f64::to_bits` is not const until /// 1.83. -#[inline] +#[inline(always)] const fn f64_to_bits(bits: f64) -> u64 { unsafe { std::mem::transmute(bits) } } /// Check that a float is a `NaN`. This is necessary for keeping the MSRV /// at 1.82, as `f64::is_nan` is not const until 1.53. -#[inline] +#[inline(always)] #[allow(clippy::eq_op, clippy::float_cmp)] const fn f64_is_nan(f: f64) -> bool { f != f @@ -78,6 +79,7 @@ mod bits { use super::{f64_from_bits, f64_is_nan, f64_to_bits}; use boa_engine::{JsBigInt, JsObject, JsSymbol}; use boa_string::JsString; + use std::ptr::NonNull; /// Undefined value in `u64`. pub(super) const UNDEFINED: u64 = 0x7FF4_0000_0000_0000; @@ -125,69 +127,70 @@ mod bits { pub(super) const NAN: u64 = 0x7FF8_0000_0000_0000; /// Checks that a value is a valid boolean (either true or false). - #[inline] + #[inline(always)] pub(super) const fn is_bool(value: u64) -> bool { value == TRUE || value == FALSE } /// Checks that a value is a valid float, not a tagged nan boxed value. - #[inline] + #[inline(always)] pub(super) const fn is_float(value: u64) -> bool { let as_float = f64_from_bits(value); !f64_is_nan(as_float) || value == NAN } /// Checks that a value is a valid undefined. - #[inline] + #[inline(always)] pub(super) const fn is_undefined(value: u64) -> bool { value == UNDEFINED } /// Checks that a value is a valid null. - #[inline] + #[inline(always)] pub(super) const fn is_null(value: u64) -> bool { value == NULL } /// Checks that a value is a valid integer32. - #[inline] + #[inline(always)] pub(super) const fn is_integer32(value: u64) -> bool { value & INTEGER32_ZERO == INTEGER32_ZERO } /// Untag a value as a pointer. - #[inline] + #[inline(always)] pub(super) const fn is_pointer(value: u64) -> bool { value & POINTER_START == POINTER_START } /// Checks that a value is a valid `BigInt`. - #[inline] + #[inline(always)] #[allow(clippy::verbose_bit_mask)] pub(super) const fn is_bigint(value: u64) -> bool { + // If `(value & POINTER_MASK)` is zero, then it is NaN. is_pointer(value) && (value & POINTER_TYPE_MASK == BIGINT) && (value & POINTER_MASK) != 0 } /// Checks that a value is a valid Object. - #[inline] + #[inline(always)] pub(super) const fn is_object(value: u64) -> bool { - is_pointer(value) && (value & POINTER_TYPE_MASK == OBJECT) && (value & POINTER_MASK) != 0 + is_pointer(value) && (value & POINTER_TYPE_MASK == OBJECT) } /// Checks that a value is a valid Symbol. - #[inline] + #[inline(always)] pub(super) const fn is_symbol(value: u64) -> bool { - is_pointer(value) && (value & POINTER_TYPE_MASK == SYMBOL) && (value & POINTER_MASK) != 0 + is_pointer(value) && (value & POINTER_TYPE_MASK == SYMBOL) } /// Checks that a value is a valid String. - #[inline] + #[inline(always)] pub(super) const fn is_string(value: u64) -> bool { - is_pointer(value) && (value & POINTER_TYPE_MASK == STRING) && (value & POINTER_MASK) != 0 + is_pointer(value) && (value & POINTER_TYPE_MASK == STRING) } /// Returns a tagged u64 of a 64-bits float. - #[inline] + #[inline(always)] pub(super) const fn tag_f64(value: f64) -> u64 { if f64_is_nan(value) { // Reduce any NAN to a canonical NAN representation. @@ -198,19 +201,19 @@ mod bits { } /// Returns a tagged u64 of a 32-bits integer. - #[inline] + #[inline(always)] pub(super) const fn tag_i32(value: i32) -> u64 { INTEGER32_ZERO | value as u64 & 0xFFFF_FFFFu64 } /// Returns a i32-bits from a tagged integer. - #[inline] + #[inline(always)] pub(super) const fn untag_i32(value: u64) -> i32 { ((value & 0xFFFF_FFFFu64) | 0xFFFF_FFFF_0000_0000u64) as i32 } /// Returns a tagged u64 of a boolean. - #[inline] + #[inline(always)] pub(super) const fn tag_bool(value: bool) -> u64 { if value { TRUE @@ -227,7 +230,7 @@ mod bits { /// /// The box is forgotten after this operation. It must be dropped separately, /// by calling `[Self::drop_pointer]`. - #[inline] + #[inline(always)] #[allow(clippy::identity_op)] pub(super) unsafe fn tag_bigint(value: Box) -> u64 { let value = Box::into_raw(value) as u64; @@ -253,7 +256,7 @@ mod bits { /// /// The box is forgotten after this operation. It must be dropped separately, /// by calling `[Self::drop_pointer]`. - #[inline] + #[inline(always)] pub(super) unsafe fn tag_object(value: Box) -> u64 { let value = Box::into_raw(value) as u64; let value_masked: u64 = value & POINTER_MASK; @@ -278,7 +281,7 @@ mod bits { /// /// The box is forgotten after this operation. It must be dropped separately, /// by calling `[Self::drop_pointer]`. - #[inline] + #[inline(always)] pub(super) unsafe fn tag_symbol(value: Box) -> u64 { let value = Box::into_raw(value) as u64; let value_masked: u64 = value & POINTER_MASK; @@ -303,7 +306,7 @@ mod bits { /// /// The box is forgotten after this operation. It must be dropped separately, /// by calling `[Self::drop_pointer]`. - #[inline] + #[inline(always)] pub(super) unsafe fn tag_string(value: Box) -> u64 { let value = Box::into_raw(value) as u64; let value_masked: u64 = value & POINTER_MASK; @@ -320,28 +323,15 @@ mod bits { POINTER_START | 3 | value_masked } - /// Returns an Option of a boxed `[JsBigInt]` from a tagged value. - #[inline] - pub(super) const fn as_bigint<'a>(value: u64) -> Option<&'a JsBigInt> { - if is_bigint(value) { - // This is safe because the boxed object will always be on the heap. - let ptr = value & POINTER_MASK; - unsafe { Some(&*(ptr as *const _)) } - } else { - None - } - } - - /// Returns an Option of a boxed `[JsObject]` from a tagged value. - #[inline] - pub(super) const fn as_object<'a>(value: u64) -> Option<&'a JsObject> { - if is_object(value) { - // This is safe because the boxed object will always be on the heap. - let ptr = value & POINTER_MASK; - unsafe { Some(&*(ptr as *const _)) } - } else { - None - } + /// Returns a reference to T from a tagged value. + /// + /// # Safety + /// The pointer must be a valid pointer to a T on the heap, otherwise this + /// will result in undefined behavior. + #[inline(always)] + pub(super) const unsafe fn untag_pointer<'a, T>(value: u64) -> &'a T { + // This is safe since we already checked the pointer is not null as this point. + unsafe { NonNull::new_unchecked((value & POINTER_MASK) as *mut T).as_ref() } } } @@ -581,14 +571,22 @@ impl NanBoxedValue { #[must_use] #[inline] pub(crate) const fn as_bigint(&self) -> Option<&JsBigInt> { - bits::as_bigint::<'_>(self.0) + if self.is_bigint() { + Some(unsafe { bits::untag_pointer::<'_, JsBigInt>(self.0) }) + } else { + None + } } /// Returns the value as a boxed `[JsObject]`. #[must_use] #[inline] pub(crate) const fn as_object(&self) -> Option<&JsObject> { - bits::as_object::<'_>(self.0) + if self.is_object() { + Some(unsafe { bits::untag_pointer::<'_, JsObject>(self.0) }) + } else { + None + } } /// Returns the value as a boxed `[JsSymbol]`. @@ -596,9 +594,7 @@ impl NanBoxedValue { #[inline] pub(crate) const fn as_symbol(&self) -> Option<&JsSymbol> { if self.is_symbol() { - // This is safe because the boxed object will always be on the heap. - let ptr = self.0 & bits::POINTER_MASK; - unsafe { Some(&*(ptr as *const _)) } + Some(unsafe { bits::untag_pointer::<'_, JsSymbol>(self.0) }) } else { None } @@ -609,9 +605,7 @@ impl NanBoxedValue { #[inline] pub(crate) const fn as_string(&self) -> Option<&JsString> { if self.is_string() { - // This is safe because the boxed object will always be on the heap. - let ptr = self.0 & bits::POINTER_MASK; - unsafe { Some(&*(ptr as *const _)) } + Some(unsafe { bits::untag_pointer::<'_, JsString>(self.0) }) } else { None } From ca9f65794739082a9a04f5e8d683e016a67c2cc9 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Sat, 4 Jan 2025 21:43:33 -0800 Subject: [PATCH 21/24] Use top 2 bits for pointer tagging, speeding up as_variant --- core/engine/src/value/inner/nan_boxed.rs | 103 +++++++++++++---------- 1 file changed, 60 insertions(+), 43 deletions(-) diff --git a/core/engine/src/value/inner/nan_boxed.rs b/core/engine/src/value/inner/nan_boxed.rs index 2e8630441d3..c488f2fe37f 100644 --- a/core/engine/src/value/inner/nan_boxed.rs +++ b/core/engine/src/value/inner/nan_boxed.rs @@ -19,21 +19,14 @@ //! | `False` | `7FF6:0000:0000:0000` | | //! | `True` | `7FF6:0000:0000:0001` | | //! | `Integer32` | `7FF7:0000:IIII:IIII` | 32-bits integer. | -//! | `BigInt` | `7FF[8-F]:PPPP:PPPP:PPPP \| 0` | 51-bits pointer. Assumes non-null pointer. | -//! | `Object` | `7FF[8-F]:PPPP:PPPP:PPPP \| 1` | 51-bits pointer. | -//! | `Symbol` | `7FF[8-F]:PPPP:PPPP:PPPP \| 2` | 51-bits pointer. | -//! | `String` | `7FF[8-F]:PPPP:PPPP:PPPP \| 3` | 51-bits pointer. | +//! | `BigInt` | `7FF8:PPPP:PPPP:PPPP` | 49-bits pointer. Assumes non-null pointer. | +//! | `Object` | `7FFA:PPPP:PPPP:PPPP` | 49-bits pointer. | +//! | `Symbol` | `7FFC:PPPP:PPPP:PPPP` | 49-bits pointer. | +//! | `String` | `7FFE:PPPP:PPPP:PPPP` | 49-bits pointer. | //! | `Float64` | Any other values. | | //! -//! Pointers have the highest bit (in the `NaN` tag) set to 1, so they -//! can represent any value from `0x8000_0000_0000` to `0xFFFF_FFFF_FFFF`. -//! The last 2 bits of the pointer is used to store the type of the value. -//! //! The pointers are assumed to never be NULL, and as such no clash //! with regular NAN should happen. -//! -//! This only works on 4-bits aligned values, which is asserted when the -//! `InnerValue` is created. #![allow(clippy::inline_always)] use crate::{JsBigInt, JsObject, JsSymbol, JsVariant}; @@ -102,26 +95,40 @@ mod bits { /// Pointer starting point in `u64`. pub(super) const POINTER_START: u64 = 0x7FF8_0000_0000_0000; - /// Pointer starting point in `u64`. - pub(super) const POINTER_END: u64 = 0x7FFF_FFFF_FFFF_FFFF; - /// Pointer types mask in `u64`. - pub(super) const POINTER_MASK: u64 = 0x0007_FFFF_FFFF_FFFC; + pub(super) const POINTER_MASK: u64 = 0x0000_FFFF_FFFF_FFFF; + + /// Pointer start point for `BigInt` in `u64`. + pub(super) const POINTER_BIGINT_START: u64 = POINTER_START | POINTER_TYPE_BIGINT; + /// Pointer end point for `BigInt` in `u64`. + pub(super) const POINTER_BIGINT_END: u64 = POINTER_START | POINTER_MASK | POINTER_TYPE_BIGINT; + /// Pointer start point for `JsObject` in `u64`. + pub(super) const POINTER_OBJECT_START: u64 = POINTER_START | POINTER_TYPE_OBJECT; + /// Pointer end point for `JsObject` in `u64`. + pub(super) const POINTER_OBJECT_END: u64 = POINTER_START | POINTER_MASK | POINTER_TYPE_OBJECT; + /// Pointer start point for `JsSymbol` in `u64`. + pub(super) const POINTER_SYMBOL_START: u64 = POINTER_START | POINTER_TYPE_SYMBOL; + /// Pointer end point for `JsSymbol` in `u64`. + pub(super) const POINTER_SYMBOL_END: u64 = POINTER_START | POINTER_MASK | POINTER_TYPE_SYMBOL; + /// Pointer start point for `JsString` in `u64`. + pub(super) const POINTER_STRING_START: u64 = POINTER_START | POINTER_TYPE_STRING; + /// Pointer end point for `JsString` in `u64`. + pub(super) const POINTER_STRING_END: u64 = POINTER_START | POINTER_MASK | POINTER_TYPE_STRING; /// Pointer mask for the type of the pointer. - pub(super) const POINTER_TYPE_MASK: u64 = 0x0003; + pub(super) const POINTER_TYPE_MASK: u64 = 0x0007_0000_0000_0000; - /// Pointer value for `BigInt`. - pub(super) const BIGINT: u64 = 0x0000; + /// Pointer type value for `BigInt`. + pub(super) const POINTER_TYPE_BIGINT: u64 = 0x0000_0000_0000_0000; - /// Pointer value for `JsObject`. - pub(super) const OBJECT: u64 = 0x0001; + /// Pointer type value for `JsObject`. + pub(super) const POINTER_TYPE_OBJECT: u64 = 0x0004_0000_0000_0000; - /// Pointer value for `JsSymbol`. - pub(super) const SYMBOL: u64 = 0x0002; + /// Pointer type value for `JsSymbol`. + pub(super) const POINTER_TYPE_SYMBOL: u64 = 0x0005_0000_0000_0000; - /// Pointer value for `JsString`. - pub(super) const STRING: u64 = 0x0003; + /// Pointer type value for `JsString`. + pub(super) const POINTER_TYPE_STRING: u64 = 0x0006_0000_0000_0000; /// NAN value in `u64`. pub(super) const NAN: u64 = 0x7FF8_0000_0000_0000; @@ -168,25 +175,27 @@ mod bits { #[allow(clippy::verbose_bit_mask)] pub(super) const fn is_bigint(value: u64) -> bool { // If `(value & POINTER_MASK)` is zero, then it is NaN. - is_pointer(value) && (value & POINTER_TYPE_MASK == BIGINT) && (value & POINTER_MASK) != 0 + is_pointer(value) + && (value & POINTER_TYPE_MASK == POINTER_TYPE_BIGINT) + && (value & POINTER_MASK) != 0 } /// Checks that a value is a valid Object. #[inline(always)] pub(super) const fn is_object(value: u64) -> bool { - is_pointer(value) && (value & POINTER_TYPE_MASK == OBJECT) + is_pointer(value) && (value & POINTER_TYPE_MASK == POINTER_TYPE_OBJECT) } /// Checks that a value is a valid Symbol. #[inline(always)] pub(super) const fn is_symbol(value: u64) -> bool { - is_pointer(value) && (value & POINTER_TYPE_MASK == SYMBOL) + is_pointer(value) && (value & POINTER_TYPE_MASK == POINTER_TYPE_SYMBOL) } /// Checks that a value is a valid String. #[inline(always)] pub(super) const fn is_string(value: u64) -> bool { - is_pointer(value) && (value & POINTER_TYPE_MASK == STRING) + is_pointer(value) && (value & POINTER_TYPE_MASK == POINTER_TYPE_STRING) } /// Returns a tagged u64 of a 64-bits float. @@ -245,7 +254,7 @@ mod bits { assert_ne!(value_masked, 0, "Pointer is NULL."); // Simply cast for bits. - POINTER_START | 0 | value_masked + POINTER_BIGINT_START | value_masked } /// Returns a tagged u64 of a boxed `[JsObject]`. @@ -270,7 +279,7 @@ mod bits { assert_ne!(value_masked, 0, "Pointer is NULL."); // Simply cast for bits. - POINTER_START | 1 | value_masked + POINTER_OBJECT_START | value_masked } /// Returns a tagged u64 of a boxed `[JsSymbol]`. @@ -295,7 +304,7 @@ mod bits { assert_ne!(value_masked, 0, "Pointer is NULL."); // Simply cast for bits. - POINTER_START | 2 | value_masked + POINTER_SYMBOL_START | value_masked } /// Returns a tagged u64 of a boxed `[JsString]`. @@ -320,7 +329,7 @@ mod bits { assert_ne!(value_masked, 0, "Pointer is NULL."); // Simply cast for bits. - POINTER_START | 3 | value_masked + POINTER_STRING_START | value_masked } /// Returns a reference to T from a tagged value. @@ -343,8 +352,14 @@ const_assert!(f64_is_nan(f64_from_bits(bits::NULL))); const_assert!(f64_is_nan(f64_from_bits(bits::FALSE))); const_assert!(f64_is_nan(f64_from_bits(bits::TRUE))); const_assert!(f64_is_nan(f64_from_bits(bits::INTEGER32_ZERO))); -const_assert!(f64_is_nan(f64_from_bits(bits::POINTER_START))); -const_assert!(f64_is_nan(f64_from_bits(bits::POINTER_END))); +const_assert!(f64_is_nan(f64_from_bits(bits::POINTER_BIGINT_START))); +const_assert!(f64_is_nan(f64_from_bits(bits::POINTER_BIGINT_END))); +const_assert!(f64_is_nan(f64_from_bits(bits::POINTER_OBJECT_START))); +const_assert!(f64_is_nan(f64_from_bits(bits::POINTER_OBJECT_END))); +const_assert!(f64_is_nan(f64_from_bits(bits::POINTER_SYMBOL_START))); +const_assert!(f64_is_nan(f64_from_bits(bits::POINTER_SYMBOL_END))); +const_assert!(f64_is_nan(f64_from_bits(bits::POINTER_STRING_START))); +const_assert!(f64_is_nan(f64_from_bits(bits::POINTER_STRING_END))); /// A NaN-boxed `[JsValue]`'s inner. pub(crate) struct NanBoxedValue(pub u64); @@ -624,15 +639,17 @@ impl NanBoxedValue { JsVariant::Integer32(bits::untag_i32(self.0)) } bits::NAN => JsVariant::Float64(f64::NAN), - bits::POINTER_START..=bits::POINTER_END => { - let ptr = self.0 & bits::POINTER_MASK; - match self.0 & bits::POINTER_TYPE_MASK { - bits::BIGINT => JsVariant::BigInt(unsafe { &*(ptr as *const _) }), - bits::OBJECT => JsVariant::Object(unsafe { &*(ptr as *const _) }), - bits::SYMBOL => JsVariant::Symbol(unsafe { &*(ptr as *const _) }), - bits::STRING => JsVariant::String(unsafe { &*(ptr as *const _) }), - _ => unreachable!(), - } + bits::POINTER_BIGINT_START..=bits::POINTER_BIGINT_END => { + JsVariant::BigInt(unsafe { bits::untag_pointer(self.0) }) + } + bits::POINTER_OBJECT_START..=bits::POINTER_OBJECT_END => { + JsVariant::Object(unsafe { bits::untag_pointer(self.0) }) + } + bits::POINTER_SYMBOL_START..=bits::POINTER_SYMBOL_END => { + JsVariant::Symbol(unsafe { bits::untag_pointer(self.0) }) + } + bits::POINTER_STRING_START..=bits::POINTER_STRING_END => { + JsVariant::String(unsafe { bits::untag_pointer(self.0) }) } _ => JsVariant::Float64(f64_from_bits(self.0)), } From e29cedbd6f6fe0802e3b2e85943f79a89e04e509 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Sat, 4 Jan 2025 21:49:36 -0800 Subject: [PATCH 22/24] Remove the feature flag for NaN boxing now that were closer in perf --- cli/Cargo.toml | 1 - core/engine/Cargo.toml | 1 - core/engine/src/value/inner.rs | 15 -- core/engine/src/value/inner/enum_value.rs | 254 ---------------------- 4 files changed, 271 deletions(-) delete mode 100644 core/engine/src/value/inner/enum_value.rs diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 5900475ecd5..883a3882f27 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -28,7 +28,6 @@ dhat = { workspace = true, optional = true } [features] default = ["boa_engine/annex-b", "boa_engine/experimental", "boa_engine/intl_bundled"] dhat = ["dep:dhat"] -nan-box-jsvalue = ["boa_engine/nan-box-jsvalue"] [target.x86_64-unknown-linux-gnu.dependencies] jemallocator.workspace = true diff --git a/core/engine/Cargo.toml b/core/engine/Cargo.toml index 39ce719f63f..9a0d603e427 100644 --- a/core/engine/Cargo.toml +++ b/core/engine/Cargo.toml @@ -15,7 +15,6 @@ rust-version.workspace = true profiler = ["boa_profiler/profiler"] deser = ["boa_interner/serde", "boa_ast/serde"] either = ["dep:either"] -nan-box-jsvalue = [] # Enables the `Intl` builtin object and bundles a default ICU4X data provider. # Prefer this over `intl` if you just want to enable `Intl` without dealing with the diff --git a/core/engine/src/value/inner.rs b/core/engine/src/value/inner.rs index 243200eca12..bf6c00a31d3 100644 --- a/core/engine/src/value/inner.rs +++ b/core/engine/src/value/inner.rs @@ -1,18 +1,3 @@ //! Module implementing the operations for the inner value of a `[super::JsValue]`. -//! -//! The `[InnerValue]` type is an opaque type that can be either an enum of possible -//! JavaScript value types, or a 64-bits float that represents a NaN-boxed JavaScript -//! value, depending on feature flags. By default, the behaviour is to use the -//! enumeration. - -#[cfg(feature = "nan-box-jsvalue")] mod nan_boxed; - -#[cfg(feature = "nan-box-jsvalue")] pub(crate) use nan_boxed::NanBoxedValue as InnerValue; - -#[cfg(not(feature = "nan-box-jsvalue"))] -mod enum_value; - -#[cfg(not(feature = "nan-box-jsvalue"))] -pub(crate) use enum_value::EnumBasedValue as InnerValue; diff --git a/core/engine/src/value/inner/enum_value.rs b/core/engine/src/value/inner/enum_value.rs deleted file mode 100644 index eb85f314c01..00000000000 --- a/core/engine/src/value/inner/enum_value.rs +++ /dev/null @@ -1,254 +0,0 @@ -//! This `[JsValue]` inner type is an opaque enum implementing the same -//! interface as the `NanBoxedValue` type, but using an enum instead of -//! a 64-bits NAN-boxed float. - -use crate::{JsBigInt, JsObject, JsSymbol}; -use boa_engine::JsVariant; -use boa_gc::{custom_trace, Finalize, Trace}; -use boa_string::JsString; - -#[derive(Clone, Debug)] -pub(crate) enum EnumBasedValue { - Undefined, - Null, - Boolean(bool), - Integer32(i32), - Float64(f64), - BigInt(JsBigInt), - Object(JsObject), - Symbol(JsSymbol), - String(JsString), -} - -impl Finalize for EnumBasedValue { - fn finalize(&self) { - if let Some(o) = self.as_object() { - o.finalize(); - } - } -} - -#[allow(unsafe_op_in_unsafe_fn)] -unsafe impl Trace for EnumBasedValue { - custom_trace! {this, mark, { - if let Some(o) = this.as_object() { - mark(o); - } - }} -} - -impl EnumBasedValue { - /// Returns a `InnerValue` from a Null. - #[must_use] - #[inline] - pub(crate) const fn null() -> Self { - Self::Null - } - - /// Returns a `InnerValue` from an undefined. - #[must_use] - #[inline] - pub(crate) const fn undefined() -> Self { - Self::Undefined - } - - /// Returns a `InnerValue` from a 64-bits float. If the float is `NaN`, - /// it will be reduced to a canonical `NaN` representation. - #[must_use] - #[inline] - pub(crate) const fn float64(value: f64) -> Self { - Self::Float64(value) - } - - /// Returns a `InnerValue` from a 32-bits integer. - #[must_use] - #[inline] - pub(crate) const fn integer32(value: i32) -> Self { - Self::Integer32(value) - } - - /// Returns a `InnerValue` from a boolean. - #[must_use] - #[inline] - pub(crate) const fn boolean(value: bool) -> Self { - Self::Boolean(value) - } - - /// Returns a `InnerValue` from a boxed `[JsBigInt]`. - #[must_use] - #[inline] - pub(crate) fn bigint(value: JsBigInt) -> Self { - Self::BigInt(value) - } - - /// Returns a `InnerValue` from a boxed `[JsObject]`. - #[must_use] - #[inline] - pub(crate) fn object(value: JsObject) -> Self { - Self::Object(value) - } - - /// Returns a `InnerValue` from a boxed `[JsSymbol]`. - #[must_use] - #[inline] - pub(crate) fn symbol(value: JsSymbol) -> Self { - Self::Symbol(value) - } - - /// Returns a `InnerValue` from a boxed `[JsString]`. - #[must_use] - #[inline] - pub(crate) fn string(value: JsString) -> Self { - Self::String(value) - } - - /// Returns true if a value is undefined. - #[must_use] - #[inline] - pub(crate) const fn is_undefined(&self) -> bool { - matches!(self, Self::Undefined) - } - - /// Returns true if a value is null. - #[must_use] - #[inline] - pub(crate) const fn is_null(&self) -> bool { - matches!(self, Self::Null) - } - - /// Returns true if a value is a boolean. - #[must_use] - #[inline] - pub(crate) const fn is_bool(&self) -> bool { - matches!(self, Self::Boolean(_)) - } - - /// Returns true if a value is a 64-bits float. - #[must_use] - #[inline] - pub(crate) const fn is_float64(&self) -> bool { - matches!(self, Self::Float64(_)) - } - - /// Returns true if a value is a 32-bits integer. - #[must_use] - #[inline] - pub(crate) const fn is_integer32(&self) -> bool { - matches!(self, Self::Integer32(_)) - } - - /// Returns true if a value is a `[JsBigInt]`. A `NaN` will not match here. - #[must_use] - #[inline] - pub(crate) const fn is_bigint(&self) -> bool { - matches!(self, Self::BigInt(_)) - } - - /// Returns true if a value is a boxed Object. - #[must_use] - #[inline] - pub(crate) const fn is_object(&self) -> bool { - matches!(self, Self::Object(_)) - } - - /// Returns true if a value is a boxed Symbol. - #[must_use] - #[inline] - pub(crate) const fn is_symbol(&self) -> bool { - matches!(self, Self::Symbol(_)) - } - - /// Returns true if a value is a boxed String. - #[must_use] - #[inline] - pub(crate) const fn is_string(&self) -> bool { - matches!(self, Self::String(_)) - } - - /// Returns the value as an f64 if it is a float. - #[must_use] - #[inline] - pub(crate) const fn as_float64(&self) -> Option { - match self { - Self::Float64(value) => Some(*value), - _ => None, - } - } - - /// Returns the value as an i32 if it is an integer. - #[must_use] - #[inline] - pub(crate) const fn as_integer32(&self) -> Option { - match self { - Self::Integer32(value) => Some(*value), - _ => None, - } - } - - /// Returns the value as a boolean if it is a boolean. - #[must_use] - #[inline] - pub(crate) const fn as_bool(&self) -> Option { - match self { - Self::Boolean(value) => Some(*value), - _ => None, - } - } - - /// Returns the value as a boxed `[JsBigInt]`. - #[must_use] - #[inline] - pub(crate) const fn as_bigint(&self) -> Option<&JsBigInt> { - match self { - Self::BigInt(value) => Some(value), - _ => None, - } - } - - /// Returns the value as a boxed `[JsObject]`. - #[must_use] - #[inline] - pub(crate) const fn as_object(&self) -> Option<&JsObject> { - match self { - Self::Object(value) => Some(value), - _ => None, - } - } - - /// Returns the value as a boxed `[JsSymbol]`. - #[must_use] - #[inline] - pub(crate) const fn as_symbol(&self) -> Option<&JsSymbol> { - match self { - Self::Symbol(value) => Some(value), - _ => None, - } - } - - /// Returns the value as a boxed `[JsString]`. - #[must_use] - #[inline] - pub(crate) const fn as_string(&self) -> Option<&JsString> { - match self { - Self::String(value) => Some(value), - _ => None, - } - } - - /// Returns the `[JsVariant]` of this inner value. - #[must_use] - #[inline] - pub(crate) const fn as_variant(&self) -> JsVariant<'_> { - match self { - Self::Undefined => JsVariant::Undefined, - Self::Null => JsVariant::Null, - Self::Boolean(v) => JsVariant::Boolean(*v), - Self::Integer32(v) => JsVariant::Integer32(*v), - Self::Float64(v) => JsVariant::Float64(*v), - Self::BigInt(v) => JsVariant::BigInt(v), - Self::Object(v) => JsVariant::Object(v), - Self::Symbol(v) => JsVariant::Symbol(v), - Self::String(v) => JsVariant::String(v), - } - } -} From c949461f76c792846f88bf8feb92bdd6455fc1c4 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Sun, 5 Jan 2025 20:59:56 -0800 Subject: [PATCH 23/24] Add a bits section to explain the scheme --- core/engine/src/value/inner/nan_boxed.rs | 38 +++++++++++++++++++++--- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/core/engine/src/value/inner/nan_boxed.rs b/core/engine/src/value/inner/nan_boxed.rs index c488f2fe37f..33b02c6f15a 100644 --- a/core/engine/src/value/inner/nan_boxed.rs +++ b/core/engine/src/value/inner/nan_boxed.rs @@ -19,12 +19,42 @@ //! | `False` | `7FF6:0000:0000:0000` | | //! | `True` | `7FF6:0000:0000:0001` | | //! | `Integer32` | `7FF7:0000:IIII:IIII` | 32-bits integer. | -//! | `BigInt` | `7FF8:PPPP:PPPP:PPPP` | 49-bits pointer. Assumes non-null pointer. | -//! | `Object` | `7FFA:PPPP:PPPP:PPPP` | 49-bits pointer. | -//! | `Symbol` | `7FFC:PPPP:PPPP:PPPP` | 49-bits pointer. | -//! | `String` | `7FFE:PPPP:PPPP:PPPP` | 49-bits pointer. | +//! | `BigInt` | `7FF8:PPPP:PPPP:PPPP` | 48-bits pointer. Assumes non-null pointer. | +//! | `Object` | `7FFA:PPPP:PPPP:PPPP` | 48-bits pointer. | +//! | `Symbol` | `7FFC:PPPP:PPPP:PPPP` | 48-bits pointer. | +//! | `String` | `7FFE:PPPP:PPPP:PPPP` | 48-bits pointer. | //! | `Float64` | Any other values. | | //! +//! Another way to vizualize this is by looking at the bit layout of a NaN-boxed +//! value: +//! ```text +//! ....--<| The type of inner value is represented by this. +//! |..| | 1??0 - Pointer, where ?? is the subtype of pointer: +//! |..| | b00 - BigInt, b01 - Object, +//! |..| | b10 - Symbol, b11 - String. +//! |..| | If the pointer is null, then it is a NaN value. +//! |..| | 0??? - Non-pointer, where ??? is the subtype: +//! |..| | b100 - Undefined, b101 - Null, +//! |..| | b011 - Boolean, b110 - Integer32. +//! vvvv +//! bit index: 63 59 55 51 47 43 39 35 31 .. 3 0 +//! 0000 0000 0000 0000 0000 0000 0000 0000 0000 .. 0000 +//! +Inf 0111 1111 1111 0000 0000 0000 0000 0000 0000 .. 0000 +//! -Inf 1111 1111 1111 0000 0000 0000 0000 0000 0000 .. 0000 +//! NaN (q) 0111 1111 1111 1000 0000 0000 0000 0000 0000 .. 0000 +//! NaN (s) 1111 1111 1111 1000 0000 0000 0000 0000 0000 .. 0000 +//! Undefined 0111 1111 1111 0100 0000 0000 0000 0000 0000 .. 0000 +//! Null 0111 1111 1111 0101 0000 0000 0000 0000 0000 .. 0000 +//! False 0111 1111 1111 0110 0000 0000 0000 0000 0000 .. 0000 +//! True 0111 1111 1111 0110 0000 0000 0000 0000 0000 .. 0001 +//! Integer32 0111 1111 1111 0111 0000 0000 0000 0000 IIII .. IIII +//! BigInt 0111 1111 1111 1000 PPPP PPPP PPPP PPPP PPPP .. PPPP +//! Object 0111 1111 1111 1010 PPPP PPPP PPPP PPPP PPPP .. PPPP +//! Symbol 0111 1111 1111 1100 PPPP PPPP PPPP PPPP PPPP .. PPPP +//! String 0111 1111 1111 1110 PPPP PPPP PPPP PPPP PPPP .. PPPP +//! Float64 Any other value. +//! ``` +//! //! The pointers are assumed to never be NULL, and as such no clash //! with regular NAN should happen. #![allow(clippy::inline_always)] From c21c886c3b3ca2e0256c6580e6b5502579a9a289 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Tue, 7 Jan 2025 09:31:02 -0800 Subject: [PATCH 24/24] Inline ALL THE THINGS - slight increase in perf --- core/engine/src/value/inner/nan_boxed.rs | 55 ++++++++++++------------ 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/core/engine/src/value/inner/nan_boxed.rs b/core/engine/src/value/inner/nan_boxed.rs index 33b02c6f15a..ab55d7f144f 100644 --- a/core/engine/src/value/inner/nan_boxed.rs +++ b/core/engine/src/value/inner/nan_boxed.rs @@ -428,6 +428,7 @@ unsafe impl Trace for NanBoxedValue { } impl Clone for NanBoxedValue { + #[inline(always)] fn clone(&self) -> Self { if let Some(o) = self.as_object() { Self::object(o.clone()) @@ -447,21 +448,21 @@ impl NanBoxedValue { /// Creates a new `InnerValue` from an u64 value without checking the validity /// of the value. #[must_use] - #[inline] + #[inline(always)] const fn from_inner_unchecked(inner: u64) -> Self { Self(inner) } /// Returns a `InnerValue` from a Null. #[must_use] - #[inline] + #[inline(always)] pub(crate) const fn null() -> Self { Self::from_inner_unchecked(bits::NULL) } /// Returns a `InnerValue` from an undefined. #[must_use] - #[inline] + #[inline(always)] pub(crate) const fn undefined() -> Self { Self::from_inner_unchecked(bits::UNDEFINED) } @@ -469,119 +470,119 @@ impl NanBoxedValue { /// Returns a `InnerValue` from a 64-bits float. If the float is `NaN`, /// it will be reduced to a canonical `NaN` representation. #[must_use] - #[inline] + #[inline(always)] pub(crate) const fn float64(value: f64) -> Self { Self::from_inner_unchecked(bits::tag_f64(value)) } /// Returns a `InnerValue` from a 32-bits integer. #[must_use] - #[inline] + #[inline(always)] pub(crate) const fn integer32(value: i32) -> Self { Self::from_inner_unchecked(bits::tag_i32(value)) } /// Returns a `InnerValue` from a boolean. #[must_use] - #[inline] + #[inline(always)] pub(crate) const fn boolean(value: bool) -> Self { Self::from_inner_unchecked(bits::tag_bool(value)) } /// Returns a `InnerValue` from a boxed `[JsBigInt]`. #[must_use] - #[inline] + #[inline(always)] pub(crate) fn bigint(value: JsBigInt) -> Self { Self::from_inner_unchecked(unsafe { bits::tag_bigint(Box::new(value)) }) } /// Returns a `InnerValue` from a boxed `[JsObject]`. #[must_use] - #[inline] + #[inline(always)] pub(crate) fn object(value: JsObject) -> Self { Self::from_inner_unchecked(unsafe { bits::tag_object(Box::new(value)) }) } /// Returns a `InnerValue` from a boxed `[JsSymbol]`. #[must_use] - #[inline] + #[inline(always)] pub(crate) fn symbol(value: JsSymbol) -> Self { Self::from_inner_unchecked(unsafe { bits::tag_symbol(Box::new(value)) }) } /// Returns a `InnerValue` from a boxed `[JsString]`. #[must_use] - #[inline] + #[inline(always)] pub(crate) fn string(value: JsString) -> Self { Self::from_inner_unchecked(unsafe { bits::tag_string(Box::new(value)) }) } /// Returns true if a value is undefined. #[must_use] - #[inline] + #[inline(always)] pub(crate) const fn is_undefined(&self) -> bool { bits::is_undefined(self.0) } /// Returns true if a value is null. #[must_use] - #[inline] + #[inline(always)] pub(crate) const fn is_null(&self) -> bool { bits::is_null(self.0) } /// Returns true if a value is a boolean. #[must_use] - #[inline] + #[inline(always)] pub(crate) const fn is_bool(&self) -> bool { bits::is_bool(self.0) } /// Returns true if a value is a 64-bits float. #[must_use] - #[inline] + #[inline(always)] pub(crate) const fn is_float64(&self) -> bool { bits::is_float(self.0) } /// Returns true if a value is a 32-bits integer. #[must_use] - #[inline] + #[inline(always)] pub(crate) const fn is_integer32(&self) -> bool { bits::is_integer32(self.0) } /// Returns true if a value is a `[JsBigInt]`. A `NaN` will not match here. #[must_use] - #[inline] + #[inline(always)] pub(crate) const fn is_bigint(&self) -> bool { bits::is_bigint(self.0) } /// Returns true if a value is a boxed Object. #[must_use] - #[inline] + #[inline(always)] pub(crate) const fn is_object(&self) -> bool { bits::is_object(self.0) } /// Returns true if a value is a boxed Symbol. #[must_use] - #[inline] + #[inline(always)] pub(crate) const fn is_symbol(&self) -> bool { bits::is_symbol(self.0) } /// Returns true if a value is a boxed String. #[must_use] - #[inline] + #[inline(always)] pub(crate) const fn is_string(&self) -> bool { bits::is_string(self.0) } /// Returns the value as a f64 if it is a float. #[must_use] - #[inline] + #[inline(always)] pub(crate) const fn as_float64(&self) -> Option { if self.is_float64() { Some(f64_from_bits(self.0)) @@ -592,7 +593,7 @@ impl NanBoxedValue { /// Returns the value as an i32 if it is an integer. #[must_use] - #[inline] + #[inline(always)] pub(crate) const fn as_integer32(&self) -> Option { if self.is_integer32() { Some(bits::untag_i32(self.0)) @@ -603,7 +604,7 @@ impl NanBoxedValue { /// Returns the value as a boolean if it is a boolean. #[must_use] - #[inline] + #[inline(always)] pub(crate) const fn as_bool(&self) -> Option { match self.0 { bits::FALSE => Some(false), @@ -614,7 +615,7 @@ impl NanBoxedValue { /// Returns the value as a boxed `[JsBigInt]`. #[must_use] - #[inline] + #[inline(always)] pub(crate) const fn as_bigint(&self) -> Option<&JsBigInt> { if self.is_bigint() { Some(unsafe { bits::untag_pointer::<'_, JsBigInt>(self.0) }) @@ -625,7 +626,7 @@ impl NanBoxedValue { /// Returns the value as a boxed `[JsObject]`. #[must_use] - #[inline] + #[inline(always)] pub(crate) const fn as_object(&self) -> Option<&JsObject> { if self.is_object() { Some(unsafe { bits::untag_pointer::<'_, JsObject>(self.0) }) @@ -636,7 +637,7 @@ impl NanBoxedValue { /// Returns the value as a boxed `[JsSymbol]`. #[must_use] - #[inline] + #[inline(always)] pub(crate) const fn as_symbol(&self) -> Option<&JsSymbol> { if self.is_symbol() { Some(unsafe { bits::untag_pointer::<'_, JsSymbol>(self.0) }) @@ -647,7 +648,7 @@ impl NanBoxedValue { /// Returns the value as a boxed `[JsString]`. #[must_use] - #[inline] + #[inline(always)] pub(crate) const fn as_string(&self) -> Option<&JsString> { if self.is_string() { Some(unsafe { bits::untag_pointer::<'_, JsString>(self.0) }) @@ -658,7 +659,7 @@ impl NanBoxedValue { /// Returns the `[JsVariant]` of this inner value. #[must_use] - #[inline] + #[inline(always)] pub(crate) const fn as_variant(&self) -> JsVariant<'_> { match self.0 { bits::UNDEFINED => JsVariant::Undefined,