Skip to main content

oxide_core/
value.rs

1//! The 8-byte NaN-boxed `Value` and the heap identifiers it carries.
2//!
3//! Any bit pattern below `0xFFF9_0000_0000_0000` is an `f64`. The boxed range is
4//! the quiet-NaN space with the sign bit set: the top 16 bits select the kind
5//! and the low 32 bits carry the payload.
6//!
7//! | Top 16 bits | Kind | Payload |
8//! | --- | --- | --- |
9//! | `0xFFF9` | Int32 | `i32` |
10//! | `0xFFFA` | Bool, Undefined, Null, Empty | bits 0–1: 0 Bool, 1 Undefined, 2 Null, 3 Empty; bit 2: the boolean |
11//! | `0xFFFB` | Object | `u32` object index |
12//! | `0xFFFC` | String | `u32` string index |
13//! | `0xFFFD` | Symbol | `u32` symbol index |
14//! | `0xFFFE` | BigInt | `u32` BigInt index |
15//! | `0xFFFF` | reserved | |
16
17use std::fmt;
18
19const TAG_SHIFT: u32 = 48;
20const TAG_INT32: u64 = 0xFFF9;
21const TAG_SPECIAL: u64 = 0xFFFA;
22const TAG_OBJECT: u64 = 0xFFFB;
23const TAG_STRING: u64 = 0xFFFC;
24const TAG_SYMBOL: u64 = 0xFFFD;
25const TAG_BIGINT: u64 = 0xFFFE;
26
27/// Lowest boxed bit pattern; everything below it is a double.
28const BOXED_MIN: u64 = TAG_INT32 << TAG_SHIFT;
29/// The NaN a NaN in the boxed range is rewritten to.
30pub const CANONICAL_NAN: u64 = 0x7FF8_0000_0000_0000;
31
32const SPECIAL_BOOL: u64 = 0;
33const SPECIAL_UNDEFINED: u64 = 1;
34const SPECIAL_NULL: u64 = 2;
35const SPECIAL_EMPTY: u64 = 3;
36const SPECIAL_TRUE_BIT: u64 = 1 << 2;
37
38const fn boxed(tag: u64, payload: u32) -> u64 {
39    (tag << TAG_SHIFT) | payload as u64
40}
41
42/// The kind of a [`Value`].
43#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
44#[repr(u8)]
45pub enum Kind {
46    Number,
47    Int32,
48    Bool,
49    Undefined,
50    Null,
51    Empty,
52    Object,
53    String,
54    Symbol,
55    BigInt,
56}
57
58/// `kind()` lookup: index 0 is every double, 1..=6 are the boxed tags `0xFFF9`
59/// to `0xFFFE`, 7 is the reserved tag, 8..=11 are the special payloads.
60/// Indices 12..=15 are never produced.
61const KIND_TABLE: [Kind; 16] = [
62    Kind::Number,
63    Kind::Int32,
64    Kind::Bool, // replaced by the special payload index, never read
65    Kind::Object,
66    Kind::String,
67    Kind::Symbol,
68    Kind::BigInt,
69    Kind::Number, // reserved tag `0xFFFF`; never constructed
70    Kind::Bool,
71    Kind::Undefined,
72    Kind::Null,
73    Kind::Empty,
74    Kind::Number, // 12..=15: padding so the masked index needs no bounds check
75    Kind::Number,
76    Kind::Number,
77    Kind::Number,
78];
79
80/// A JavaScript value, or the internal hole marker [`Value::EMPTY`].
81///
82/// `Copy`, no `Drop`, no ownership protocol. Heap kinds carry a 32-bit arena
83/// index; holding one across an allocation requires rooting it (see the crate
84/// README).
85#[repr(transparent)]
86#[derive(Clone, Copy, PartialEq, Eq, Hash)]
87pub struct Value(u64);
88
89const _: () = assert!(size_of::<Value>() == 8);
90const _: () = assert!(size_of::<[Value; 4]>() == 32);
91
92impl Value {
93    pub const UNDEFINED: Value = Value(boxed(TAG_SPECIAL, SPECIAL_UNDEFINED as u32));
94    pub const NULL: Value = Value(boxed(TAG_SPECIAL, SPECIAL_NULL as u32));
95    pub const TRUE: Value = Value(boxed(TAG_SPECIAL, (SPECIAL_BOOL | SPECIAL_TRUE_BIT) as u32));
96    pub const FALSE: Value = Value(boxed(TAG_SPECIAL, SPECIAL_BOOL as u32));
97    /// The internal hole marker: array holes, uninitialized bindings, empty
98    /// slots. Never a JavaScript value.
99    pub const EMPTY: Value = Value(boxed(TAG_SPECIAL, SPECIAL_EMPTY as u32));
100
101    /// A double; no attempt is made to produce an Int32. A NaN keeps its
102    /// bits, as QuickJS's unboxed doubles do (x86's `0.0 / 0.0` is
103    /// `0xFFF8_0000_0000_0000`, which a `Float64Array` shows), unless they
104    /// fall in the boxed range: those become [`CANONICAL_NAN`].
105    #[inline]
106    pub fn number(value: f64) -> Value {
107        if value.is_nan() {
108            return Value::nan(value);
109        }
110        Value(value.to_bits())
111    }
112
113    /// [`Value::number`] of a NaN, out of line so the common case stays a
114    /// float compare.
115    #[cold]
116    #[inline(never)]
117    fn nan(value: f64) -> Value {
118        let bits = value.to_bits();
119        if bits >= BOXED_MIN {
120            Value(CANONICAL_NAN)
121        } else {
122            Value(bits)
123        }
124    }
125
126    #[inline]
127    pub const fn int32(value: i32) -> Value {
128        Value(boxed(TAG_INT32, value as u32))
129    }
130
131    #[inline]
132    pub const fn bool(value: bool) -> Value {
133        if value { Value::TRUE } else { Value::FALSE }
134    }
135
136    #[inline]
137    pub const fn object(id: ObjectId) -> Value {
138        Value(boxed(TAG_OBJECT, id.0))
139    }
140
141    #[inline]
142    pub const fn string(id: StringId) -> Value {
143        Value(boxed(TAG_STRING, id.0))
144    }
145
146    #[inline]
147    pub const fn symbol(id: SymbolId) -> Value {
148        Value(boxed(TAG_SYMBOL, id.0))
149    }
150
151    #[inline]
152    pub const fn bigint(id: BigIntId) -> Value {
153        Value(boxed(TAG_BIGINT, id.0))
154    }
155
156    /// The raw bit pattern.
157    #[inline]
158    pub const fn bits(self) -> u64 {
159        self.0
160    }
161
162    #[inline]
163    pub fn kind(self) -> Kind {
164        let tag = (self.0 >> TAG_SHIFT) as u32;
165        // 0 for every double, 1..=7 for the boxed tags.
166        let index = tag.saturating_sub(TAG_INT32 as u32 - 1) as usize;
167        // The special tag sits at index 2; move it to 8 + the special payload.
168        let special = usize::from(tag == TAG_SPECIAL as u32) * (6 + (self.0 & 3) as usize);
169        KIND_TABLE[(index + special) & 15]
170    }
171
172    /// Int32 or double.
173    #[inline]
174    pub const fn is_number(self) -> bool {
175        self.0 < (TAG_SPECIAL << TAG_SHIFT)
176    }
177
178    #[inline]
179    pub const fn is_double(self) -> bool {
180        self.0 < BOXED_MIN
181    }
182
183    #[inline]
184    pub const fn is_int32(self) -> bool {
185        self.0 >> TAG_SHIFT == TAG_INT32
186    }
187
188    #[inline]
189    pub const fn is_empty(self) -> bool {
190        self.0 == Value::EMPTY.0
191    }
192
193    #[inline]
194    pub const fn is_undefined(self) -> bool {
195        self.0 == Value::UNDEFINED.0
196    }
197
198    #[inline]
199    pub const fn is_null(self) -> bool {
200        self.0 == Value::NULL.0
201    }
202
203    /// The numeric value, Int32 widened.
204    #[inline]
205    pub fn as_f64(self) -> Option<f64> {
206        if self.is_double() {
207            Some(f64::from_bits(self.0))
208        } else if self.is_int32() {
209            Some(f64::from(self.0 as u32 as i32))
210        } else {
211            None
212        }
213    }
214
215    #[inline]
216    pub const fn as_int32(self) -> Option<i32> {
217        if self.is_int32() {
218            Some(self.0 as u32 as i32)
219        } else {
220            None
221        }
222    }
223
224    #[inline]
225    pub const fn as_bool(self) -> Option<bool> {
226        if self.0 >> 3 == Value::FALSE.0 >> 3 && self.0 & 3 == SPECIAL_BOOL {
227            Some(self.0 & SPECIAL_TRUE_BIT != 0)
228        } else {
229            None
230        }
231    }
232
233    #[inline]
234    const fn payload_if(self, tag: u64) -> Option<u32> {
235        if self.0 >> TAG_SHIFT == tag {
236            Some(self.0 as u32)
237        } else {
238            None
239        }
240    }
241
242    #[inline]
243    pub const fn as_object(self) -> Option<ObjectId> {
244        match self.payload_if(TAG_OBJECT) {
245            Some(index) => Some(ObjectId(index)),
246            None => None,
247        }
248    }
249
250    #[inline]
251    pub const fn as_string(self) -> Option<StringId> {
252        match self.payload_if(TAG_STRING) {
253            Some(index) => Some(StringId(index)),
254            None => None,
255        }
256    }
257
258    #[inline]
259    pub const fn as_symbol(self) -> Option<SymbolId> {
260        match self.payload_if(TAG_SYMBOL) {
261            Some(index) => Some(SymbolId(index)),
262            None => None,
263        }
264    }
265
266    #[inline]
267    pub const fn as_bigint(self) -> Option<BigIntId> {
268        match self.payload_if(TAG_BIGINT) {
269            Some(index) => Some(BigIntId(index)),
270            None => None,
271        }
272    }
273
274    /// True for the kinds whose payload is a heap index.
275    #[inline]
276    pub const fn is_heap(self) -> bool {
277        let tag = self.0 >> TAG_SHIFT;
278        tag >= TAG_OBJECT && tag <= TAG_BIGINT
279    }
280}
281
282impl fmt::Debug for Value {
283    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
284        match self.kind() {
285            Kind::Number => write!(f, "Number({:?})", f64::from_bits(self.0)),
286            Kind::Int32 => write!(f, "Int32({})", self.0 as u32 as i32),
287            Kind::Bool => write!(f, "Bool({})", self.0 & SPECIAL_TRUE_BIT != 0),
288            Kind::Undefined => f.write_str("Undefined"),
289            Kind::Null => f.write_str("Null"),
290            Kind::Empty => f.write_str("Empty"),
291            Kind::Object => write!(f, "Object({})", self.0 as u32),
292            Kind::String => write!(f, "String({})", self.0 as u32),
293            Kind::Symbol => write!(f, "Symbol({})", self.0 as u32),
294            Kind::BigInt => write!(f, "BigInt({})", self.0 as u32),
295        }
296    }
297}
298
299macro_rules! identifier {
300    ($(#[$doc:meta])* $name:ident) => {
301        $(#[$doc])*
302        #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
303        pub struct $name(pub(crate) u32);
304
305        impl $name {
306            /// The arena index.
307            #[inline]
308            pub const fn index(self) -> u32 {
309                self.0
310            }
311        }
312
313        impl fmt::Debug for $name {
314            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
315                write!(f, concat!(stringify!($name), "({})"), self.0)
316            }
317        }
318    };
319}
320
321identifier!(
322    /// Index of an [`ObjectHeader`](crate::heap::object::ObjectHeader).
323    ObjectId
324);
325identifier!(
326    /// Index of a string record.
327    StringId
328);
329identifier!(
330    /// Index of a shape.
331    ShapeId
332);
333identifier!(
334    /// Index of a symbol record.
335    SymbolId
336);
337identifier!(
338    /// Index of a BigInt record.
339    BigIntId
340);
341identifier!(
342    /// An object of kind `Scope`; the same index space as [`ObjectId`].
343    ScopeId
344);
345identifier!(
346    /// Index of a validity cell.
347    ValidityCellId
348);
349identifier!(
350    /// Index of a [`FunctionBytecode`](crate::heap::object::FunctionBytecode)
351    /// record: the heap side of one published code unit.
352    BytecodeId
353);
354
355impl ScopeId {
356    #[inline]
357    pub const fn object(self) -> ObjectId {
358        ObjectId(self.0)
359    }
360}
361
362#[cfg(test)]
363mod tests {
364    use super::*;
365
366    #[test]
367    fn sizes() {
368        assert_eq!(size_of::<Value>(), 8);
369        assert_eq!(size_of::<[Value; 4]>(), 32);
370    }
371
372    #[test]
373    fn nan_keeps_its_bits_below_the_boxed_range() {
374        assert_eq!(Value::number(f64::NAN).0, 0x7FF8_0000_0000_0000);
375        for bits in [
376            0x7FF8_0000_0000_0001u64,
377            0x7FF0_0000_0000_0001, // signalling
378            0xFFF8_0000_0000_0000, // negative quiet
379        ] {
380            let value = Value::number(f64::from_bits(bits));
381            assert_eq!(value.0, bits, "{bits:#x}");
382            assert_eq!(value.kind(), Kind::Number);
383            assert!(value.as_f64().unwrap().is_nan());
384        }
385        for bits in [
386            0xFFF9_0000_0000_0000u64, // would alias Int32
387            0xFFFB_0000_0000_0007,    // would alias Object(7)
388            0xFFFF_FFFF_FFFF_FFFF,
389        ] {
390            let value = Value::number(f64::from_bits(bits));
391            assert_eq!(value.0, CANONICAL_NAN, "{bits:#x}");
392            assert_eq!(value.kind(), Kind::Number);
393            assert!(value.as_f64().unwrap().is_nan());
394        }
395    }
396
397    #[test]
398    fn doubles_round_trip() {
399        let cases = [
400            0.0,
401            -0.0,
402            1.0,
403            -1.5,
404            f64::INFINITY,
405            f64::NEG_INFINITY,
406            f64::MAX,
407            f64::MIN,
408            f64::MIN_POSITIVE,
409            f64::from_bits(1),                      // smallest subnormal
410            -f64::from_bits(0x000F_FFFF_FFFF_FFFF), // largest negative subnormal
411            f64::EPSILON,
412            2f64.powi(53),
413        ];
414        for case in cases {
415            let value = Value::number(case);
416            assert_eq!(value.kind(), Kind::Number);
417            assert!(value.is_number() && value.is_double() && !value.is_int32());
418            assert_eq!(value.as_f64().unwrap().to_bits(), case.to_bits());
419            assert_eq!(value.as_int32(), None);
420            assert_eq!(value.as_object(), None);
421        }
422        assert_ne!(Value::number(0.0), Value::number(-0.0));
423    }
424
425    #[test]
426    fn int32_round_trip() {
427        for case in [0, 1, -1, i32::MIN, i32::MAX, 42, -123_456] {
428            let value = Value::int32(case);
429            assert_eq!(value.kind(), Kind::Int32);
430            assert!(value.is_number() && value.is_int32() && !value.is_double());
431            assert_eq!(value.as_int32(), Some(case));
432            assert_eq!(value.as_f64(), Some(f64::from(case)));
433            assert_eq!(value.as_bool(), None);
434        }
435    }
436
437    #[test]
438    fn specials_round_trip() {
439        let cases = [
440            (Value::TRUE, Kind::Bool),
441            (Value::FALSE, Kind::Bool),
442            (Value::UNDEFINED, Kind::Undefined),
443            (Value::NULL, Kind::Null),
444            (Value::EMPTY, Kind::Empty),
445        ];
446        for (index, (value, kind)) in cases.into_iter().enumerate() {
447            assert_eq!(value.kind(), kind);
448            assert!(!value.is_number() && !value.is_heap());
449            assert_eq!(value.as_f64(), None);
450            for (other, (other_value, _)) in cases.into_iter().enumerate() {
451                assert_eq!(index == other, value == other_value);
452            }
453        }
454        assert_eq!(Value::TRUE.as_bool(), Some(true));
455        assert_eq!(Value::FALSE.as_bool(), Some(false));
456        assert_eq!(Value::bool(true), Value::TRUE);
457        assert_eq!(Value::UNDEFINED.as_bool(), None);
458        assert_eq!(Value::EMPTY.as_bool(), None);
459        assert!(Value::EMPTY.is_empty() && !Value::UNDEFINED.is_empty());
460        assert!(Value::UNDEFINED.is_undefined() && Value::NULL.is_null());
461    }
462
463    #[test]
464    fn heap_kinds_round_trip() {
465        for index in [0, 1, 0x7FFF_FFFF, u32::MAX] {
466            let object = Value::object(ObjectId(index));
467            let string = Value::string(StringId(index));
468            let symbol = Value::symbol(SymbolId(index));
469            let bigint = Value::bigint(BigIntId(index));
470            assert_eq!(object.kind(), Kind::Object);
471            assert_eq!(string.kind(), Kind::String);
472            assert_eq!(symbol.kind(), Kind::Symbol);
473            assert_eq!(bigint.kind(), Kind::BigInt);
474            assert_eq!(object.as_object(), Some(ObjectId(index)));
475            assert_eq!(string.as_string(), Some(StringId(index)));
476            assert_eq!(symbol.as_symbol(), Some(SymbolId(index)));
477            assert_eq!(bigint.as_bigint(), Some(BigIntId(index)));
478            for value in [object, string, symbol, bigint] {
479                assert!(value.is_heap() && !value.is_number());
480                assert_eq!(value.as_f64(), None);
481            }
482            assert_eq!(object.as_string(), None);
483            assert_eq!(string.as_object(), None);
484            assert_eq!(symbol.as_bigint(), None);
485            assert_eq!(bigint.as_symbol(), None);
486        }
487    }
488}