use crate::gc::trace::{Trace, Tracer};
use crate::objects::heap_object::HeapObject;
use crate::objects::value::JsValue;
const SMI_TAG: u64 = 1;
const SMI_TAG_MASK: u64 = 1;
const SMI_SHIFT: u32 = 1;
const FALSE_BITS: u64 = 0x06;
const TRUE_BITS: u64 = 0x16;
const NULL_BITS: u64 = 0x26;
const UNDEFINED_BITS: u64 = 0x36;
const HOLE_BITS: u64 = 0x46;
const SPECIAL_TAG: u64 = 0x06;
const SPECIAL_MASK: u64 = 0x0F;
const HEAP_TAG_SHIFT: u32 = 60;
const HEAP_TAG_MASK: u64 = 0x7_u64 << HEAP_TAG_SHIFT;
const POINTER_MASK: u64 = 0x0000_FFFF_FFFF_FFFF;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum HeapTypeTag {
Object = 0,
String = 1,
Symbol = 2,
BigInt = 3,
Function = 4,
Array = 5,
}
impl HeapTypeTag {
const fn from_bits(bits: u8) -> Option<Self> {
match bits {
0 => Some(Self::Object),
1 => Some(Self::String),
2 => Some(Self::Symbol),
3 => Some(Self::BigInt),
4 => Some(Self::Function),
5 => Some(Self::Array),
_ => None,
}
}
}
#[repr(transparent)]
#[derive(Copy, Clone, PartialEq, Eq)]
pub struct NanBoxedValue(u64);
impl NanBoxedValue {
#[inline]
pub fn from_smi(value: i32) -> Self {
Self(((value as i64 as u64) << SMI_SHIFT) | SMI_TAG)
}
#[inline]
pub const fn undefined() -> Self {
Self(UNDEFINED_BITS)
}
#[inline]
pub const fn null() -> Self {
Self(NULL_BITS)
}
#[inline]
pub const fn from_bool(b: bool) -> Self {
if b { Self(TRUE_BITS) } else { Self(FALSE_BITS) }
}
#[inline]
pub const fn hole() -> Self {
Self(HOLE_BITS)
}
#[inline]
pub unsafe fn from_heap_ptr(ptr: *mut HeapObject, tag: HeapTypeTag) -> Self {
let addr = ptr as u64;
debug_assert!(addr & 0x07 == 0, "pointer must be 8-byte aligned");
debug_assert!(addr & !POINTER_MASK == 0, "pointer must fit in 48 bits");
Self(((tag as u64) << HEAP_TAG_SHIFT) | addr)
}
}
impl NanBoxedValue {
#[inline]
pub const fn is_smi(self) -> bool {
self.0 & SMI_TAG_MASK == SMI_TAG
}
#[inline]
pub const fn is_special(self) -> bool {
self.0 & SPECIAL_MASK == SPECIAL_TAG && self.0 <= HOLE_BITS
}
#[inline]
pub const fn is_heap_ptr(self) -> bool {
!self.is_smi() && !self.is_special()
}
#[inline]
pub const fn is_undefined(self) -> bool {
self.0 == UNDEFINED_BITS
}
#[inline]
pub const fn is_null(self) -> bool {
self.0 == NULL_BITS
}
#[inline]
pub const fn is_true(self) -> bool {
self.0 == TRUE_BITS
}
#[inline]
pub const fn is_false(self) -> bool {
self.0 == FALSE_BITS
}
#[inline]
pub const fn is_boolean(self) -> bool {
self.is_true() || self.is_false()
}
#[inline]
pub const fn is_hole(self) -> bool {
self.0 == HOLE_BITS
}
#[inline]
pub const fn is_nullish(self) -> bool {
self.is_null() || self.is_undefined()
}
}
impl NanBoxedValue {
#[inline]
pub const fn as_smi(self) -> Option<i32> {
if self.is_smi() {
Some(((self.0 as i64) >> SMI_SHIFT) as i32)
} else {
None
}
}
#[inline]
pub const fn as_bool(self) -> Option<bool> {
if self.is_true() {
Some(true)
} else if self.is_false() {
Some(false)
} else {
None
}
}
#[inline]
pub fn heap_type_tag(self) -> Option<HeapTypeTag> {
if self.is_heap_ptr() {
let bits = ((self.0 & HEAP_TAG_MASK) >> HEAP_TAG_SHIFT) as u8;
HeapTypeTag::from_bits(bits)
} else {
None
}
}
#[inline]
pub unsafe fn as_heap_ptr(self) -> Option<*mut HeapObject> {
if self.is_heap_ptr() {
Some((self.0 & POINTER_MASK) as *mut HeapObject)
} else {
None
}
}
#[inline]
pub const fn raw(self) -> u64 {
self.0
}
#[inline]
pub const unsafe fn from_raw(bits: u64) -> Self {
Self(bits)
}
}
impl NanBoxedValue {
pub fn try_from_js_value(value: &JsValue) -> Option<Self> {
match value {
JsValue::Undefined => Some(Self::undefined()),
JsValue::Null => Some(Self::null()),
JsValue::Boolean(b) => Some(Self::from_bool(*b)),
JsValue::Smi(i) => Some(Self::from_smi(*i)),
JsValue::Object(ptr) => {
Some(unsafe { Self::from_heap_ptr(*ptr, HeapTypeTag::Object) })
}
_ => None,
}
}
pub unsafe fn to_js_value(self) -> Option<JsValue> {
if self.is_undefined() {
Some(JsValue::Undefined)
} else if self.is_null() {
Some(JsValue::Null)
} else if let Some(b) = self.as_bool() {
Some(JsValue::Boolean(b))
} else if let Some(i) = self.as_smi() {
Some(JsValue::Smi(i))
} else if self.is_hole() {
None
} else if self.is_heap_ptr() {
let ptr = unsafe { self.as_heap_ptr().unwrap_unchecked() };
Some(JsValue::Object(ptr))
} else {
None
}
}
}
impl std::fmt::Debug for NanBoxedValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.is_undefined() {
write!(f, "NanBoxedValue(undefined)")
} else if self.is_null() {
write!(f, "NanBoxedValue(null)")
} else if let Some(b) = self.as_bool() {
write!(f, "NanBoxedValue({b})")
} else if self.is_hole() {
write!(f, "NanBoxedValue(hole)")
} else if let Some(i) = self.as_smi() {
write!(f, "NanBoxedValue(Smi({i}))")
} else if let Some(tag) = self.heap_type_tag() {
write!(
f,
"NanBoxedValue(HeapPtr({tag:?}, 0x{:012x}))",
self.0 & POINTER_MASK
)
} else {
write!(f, "NanBoxedValue(0x{:016x})", self.0)
}
}
}
impl Trace for NanBoxedValue {
fn trace(&self, tracer: &mut Tracer) {
if self.is_heap_ptr() {
let ptr = (self.0 & POINTER_MASK) as *mut u8;
unsafe { tracer.mark_raw(ptr) };
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_size_is_8_bytes() {
assert_eq!(std::mem::size_of::<NanBoxedValue>(), 8);
}
#[test]
fn test_smi_zero() {
let v = NanBoxedValue::from_smi(0);
assert!(v.is_smi());
assert!(!v.is_special());
assert!(!v.is_heap_ptr());
assert_eq!(v.as_smi(), Some(0));
}
#[test]
fn test_smi_positive() {
let v = NanBoxedValue::from_smi(42);
assert!(v.is_smi());
assert_eq!(v.as_smi(), Some(42));
}
#[test]
fn test_smi_negative() {
let v = NanBoxedValue::from_smi(-1);
assert!(v.is_smi());
assert_eq!(v.as_smi(), Some(-1));
}
#[test]
fn test_smi_i32_max() {
let v = NanBoxedValue::from_smi(i32::MAX);
assert!(v.is_smi());
assert_eq!(v.as_smi(), Some(i32::MAX));
}
#[test]
fn test_smi_i32_min() {
let v = NanBoxedValue::from_smi(i32::MIN);
assert!(v.is_smi());
assert_eq!(v.as_smi(), Some(i32::MIN));
}
#[test]
fn test_smi_i31_max() {
let max = i32::MAX >> 1; let v = NanBoxedValue::from_smi(max);
assert_eq!(v.as_smi(), Some(max));
}
#[test]
fn test_smi_i31_min() {
let min = i32::MIN >> 1; let v = NanBoxedValue::from_smi(min);
assert_eq!(v.as_smi(), Some(min));
}
#[test]
fn test_undefined() {
let v = NanBoxedValue::undefined();
assert!(v.is_undefined());
assert!(v.is_special());
assert!(v.is_nullish());
assert!(!v.is_smi());
assert!(!v.is_null());
assert!(!v.is_boolean());
assert!(!v.is_heap_ptr());
assert_eq!(v.raw(), UNDEFINED_BITS);
}
#[test]
fn test_null() {
let v = NanBoxedValue::null();
assert!(v.is_null());
assert!(v.is_special());
assert!(v.is_nullish());
assert!(!v.is_undefined());
assert!(!v.is_smi());
assert!(!v.is_boolean());
assert!(!v.is_heap_ptr());
assert_eq!(v.raw(), NULL_BITS);
}
#[test]
fn test_true() {
let v = NanBoxedValue::from_bool(true);
assert!(v.is_true());
assert!(v.is_boolean());
assert!(v.is_special());
assert!(!v.is_false());
assert!(!v.is_smi());
assert!(!v.is_nullish());
assert!(!v.is_heap_ptr());
assert_eq!(v.as_bool(), Some(true));
assert_eq!(v.raw(), TRUE_BITS);
}
#[test]
fn test_false() {
let v = NanBoxedValue::from_bool(false);
assert!(v.is_false());
assert!(v.is_boolean());
assert!(v.is_special());
assert!(!v.is_true());
assert!(!v.is_smi());
assert!(!v.is_heap_ptr());
assert_eq!(v.as_bool(), Some(false));
assert_eq!(v.raw(), FALSE_BITS);
}
#[test]
fn test_hole() {
let v = NanBoxedValue::hole();
assert!(v.is_hole());
assert!(v.is_special());
assert!(!v.is_smi());
assert!(!v.is_boolean());
assert!(!v.is_heap_ptr());
assert_eq!(v.raw(), HOLE_BITS);
}
#[test]
fn test_heap_ptr_object_tag() {
let mut obj = HeapObject::new_null();
let ptr = &mut obj as *mut HeapObject;
let v = unsafe { NanBoxedValue::from_heap_ptr(ptr, HeapTypeTag::Object) };
assert!(v.is_heap_ptr());
assert!(!v.is_smi());
assert!(!v.is_special());
assert_eq!(v.heap_type_tag(), Some(HeapTypeTag::Object));
let recovered = unsafe { v.as_heap_ptr() };
assert_eq!(recovered, Some(ptr));
}
#[test]
fn test_heap_ptr_all_tags() {
let tags = [
HeapTypeTag::Object,
HeapTypeTag::String,
HeapTypeTag::Symbol,
HeapTypeTag::BigInt,
HeapTypeTag::Function,
HeapTypeTag::Array,
];
for &tag in &tags {
let mut obj = HeapObject::new_null();
let ptr = &mut obj as *mut HeapObject;
let v = unsafe { NanBoxedValue::from_heap_ptr(ptr, tag) };
assert!(v.is_heap_ptr(), "tag {tag:?} should be heap ptr");
assert_eq!(v.heap_type_tag(), Some(tag), "tag {tag:?} round-trip");
let recovered = unsafe { v.as_heap_ptr() };
assert_eq!(recovered, Some(ptr), "pointer {tag:?} round-trip");
}
}
#[test]
fn test_smi_is_not_special_or_heap() {
for i in [0, 1, -1, 42, -42, i32::MAX, i32::MIN] {
let v = NanBoxedValue::from_smi(i);
assert!(v.is_smi(), "Smi({i}) must be smi");
assert!(!v.is_special(), "Smi({i}) must not be special");
assert!(!v.is_heap_ptr(), "Smi({i}) must not be heap ptr");
assert_eq!(v.as_bool(), None, "Smi({i}) as_bool must be None");
assert!(!v.is_undefined());
assert!(!v.is_null());
}
}
#[test]
fn test_specials_are_not_smi_or_heap() {
let specials = [
NanBoxedValue::undefined(),
NanBoxedValue::null(),
NanBoxedValue::from_bool(true),
NanBoxedValue::from_bool(false),
NanBoxedValue::hole(),
];
for v in specials {
assert!(v.is_special(), "{v:?} must be special");
assert!(!v.is_smi(), "{v:?} must not be smi");
assert!(!v.is_heap_ptr(), "{v:?} must not be heap ptr");
assert_eq!(v.as_smi(), None, "{v:?} as_smi must be None");
}
}
#[test]
fn test_heap_ptr_is_not_smi_or_special() {
let mut obj = HeapObject::new_null();
let ptr = &mut obj as *mut HeapObject;
let v = unsafe { NanBoxedValue::from_heap_ptr(ptr, HeapTypeTag::Function) };
assert!(v.is_heap_ptr());
assert!(!v.is_smi());
assert!(!v.is_special());
assert_eq!(v.as_smi(), None);
assert_eq!(v.as_bool(), None);
}
#[test]
fn test_all_specials_distinct() {
let values = [
NanBoxedValue::undefined(),
NanBoxedValue::null(),
NanBoxedValue::from_bool(true),
NanBoxedValue::from_bool(false),
NanBoxedValue::hole(),
];
for (i, a) in values.iter().enumerate() {
for (j, b) in values.iter().enumerate() {
if i == j {
assert_eq!(a, b);
} else {
assert_ne!(a, b, "specials at index {i} and {j} must differ");
}
}
}
}
#[test]
fn test_jsvalue_undefined_round_trip() {
let orig = JsValue::Undefined;
let boxed = NanBoxedValue::try_from_js_value(&orig).unwrap();
assert!(boxed.is_undefined());
let back = unsafe { boxed.to_js_value() }.unwrap();
assert_eq!(back, JsValue::Undefined);
}
#[test]
fn test_jsvalue_null_round_trip() {
let orig = JsValue::Null;
let boxed = NanBoxedValue::try_from_js_value(&orig).unwrap();
assert!(boxed.is_null());
let back = unsafe { boxed.to_js_value() }.unwrap();
assert_eq!(back, JsValue::Null);
}
#[test]
fn test_jsvalue_bool_round_trip() {
for b in [true, false] {
let orig = JsValue::Boolean(b);
let boxed = NanBoxedValue::try_from_js_value(&orig).unwrap();
assert_eq!(boxed.as_bool(), Some(b));
let back = unsafe { boxed.to_js_value() }.unwrap();
assert_eq!(back, JsValue::Boolean(b));
}
}
#[test]
fn test_jsvalue_smi_round_trip() {
for i in [0, 1, -1, 42, -42, i32::MAX, i32::MIN] {
let orig = JsValue::Smi(i);
let boxed = NanBoxedValue::try_from_js_value(&orig).unwrap();
assert_eq!(boxed.as_smi(), Some(i));
let back = unsafe { boxed.to_js_value() }.unwrap();
assert_eq!(back, JsValue::Smi(i));
}
}
#[test]
fn test_jsvalue_object_round_trip() {
let mut obj = HeapObject::new_null();
let ptr = &mut obj as *mut HeapObject;
let orig = JsValue::Object(ptr);
let boxed = NanBoxedValue::try_from_js_value(&orig).unwrap();
assert!(boxed.is_heap_ptr());
assert_eq!(boxed.heap_type_tag(), Some(HeapTypeTag::Object));
let back = unsafe { boxed.to_js_value() }.unwrap();
assert_eq!(back, JsValue::Object(ptr));
}
#[test]
fn test_jsvalue_unsupported_returns_none() {
let unsupported = [
JsValue::HeapNumber(3.14),
JsValue::String("hello".into()),
JsValue::Symbol(42),
JsValue::BigInt(Box::new(123)),
];
for v in &unsupported {
assert!(
NanBoxedValue::try_from_js_value(v).is_none(),
"{v:?} should not be convertible"
);
}
}
#[test]
fn test_raw_round_trip() {
let v = NanBoxedValue::from_smi(12345);
let v2 = unsafe { NanBoxedValue::from_raw(v.raw()) };
assert_eq!(v, v2);
assert_eq!(v2.as_smi(), Some(12345));
}
#[test]
fn test_debug_formatting() {
assert_eq!(
format!("{:?}", NanBoxedValue::undefined()),
"NanBoxedValue(undefined)"
);
assert_eq!(
format!("{:?}", NanBoxedValue::null()),
"NanBoxedValue(null)"
);
assert_eq!(
format!("{:?}", NanBoxedValue::from_bool(true)),
"NanBoxedValue(true)"
);
assert_eq!(
format!("{:?}", NanBoxedValue::from_bool(false)),
"NanBoxedValue(false)"
);
assert_eq!(
format!("{:?}", NanBoxedValue::hole()),
"NanBoxedValue(hole)"
);
assert!(format!("{:?}", NanBoxedValue::from_smi(7)).contains("Smi(7)"));
}
#[test]
fn test_trace_smi_does_nothing() {
let mut tracer = Tracer::new();
let v = NanBoxedValue::from_smi(99);
v.trace(&mut tracer);
assert!(tracer.gray_stack.is_empty());
}
#[test]
fn test_trace_special_does_nothing() {
let mut tracer = Tracer::new();
NanBoxedValue::undefined().trace(&mut tracer);
NanBoxedValue::null().trace(&mut tracer);
NanBoxedValue::from_bool(true).trace(&mut tracer);
NanBoxedValue::hole().trace(&mut tracer);
assert!(tracer.gray_stack.is_empty());
}
#[test]
fn test_trace_heap_ptr_marks() {
let mut obj = HeapObject::new_null();
let ptr = &mut obj as *mut HeapObject;
let v = unsafe { NanBoxedValue::from_heap_ptr(ptr, HeapTypeTag::Object) };
let mut tracer = Tracer::new();
v.trace(&mut tracer);
assert_eq!(tracer.gray_stack.len(), 1);
assert_eq!(tracer.gray_stack[0], ptr as *mut u8);
}
}