Skip to main content

shape_value/
tags.rs

1//! Shared NaN-boxing tag constants and helpers.
2//!
3//! This module is the single source of truth for the NaN-boxing bit layout used by
4//! `ValueWord` in the VM stack. The JIT, GC, and any other subsystem that needs to
5//! inspect or construct NaN-boxed values should import constants from here.
6//!
7//! ## NaN-boxing scheme
8//!
9//! All tagged values use sign bit = 1 with a quiet NaN exponent, giving us 51 bits
10//! for tag + payload. Normal f64 values (including NaN, which is canonicalized to a
11//! positive quiet NaN) are stored directly and never collide with our tagged range.
12//!
13//! ```text
14//! Tagged: 0xFFF[C-F]_XXXX_XXXX_XXXX
15//!   Bit 63    = 1 (sign, marks as tagged)
16//!   Bits 62-52 = 0x7FF (NaN exponent)
17//!   Bit 51    = 1 (quiet NaN bit)
18//!   Bits 50-48 = tag (3 bits)
19//!   Bits 47-0  = payload (48 bits)
20//! ```
21
22// ===== Bit layout constants =====
23
24/// Tagged value base: sign=1 + exponent all 1s + quiet NaN bit.
25/// Binary: 1_11111111111_1000...0 = 0xFFF8_0000_0000_0000
26/// All tagged values have this prefix, with the 3-bit tag in bits 50-48.
27pub const TAG_BASE: u64 = 0xFFF8_0000_0000_0000;
28
29/// Mask for extracting the 48-bit payload (bits 0-47).
30pub const PAYLOAD_MASK: u64 = 0x0000_FFFF_FFFF_FFFF;
31
32/// Mask for extracting the 3-bit tag (bits 48-50).
33pub const TAG_MASK: u64 = 0x0007_0000_0000_0000;
34
35/// Bit shift for the tag field.
36pub const TAG_SHIFT: u32 = 48;
37
38/// Canonical NaN value used when the original f64 is NaN.
39/// Positive quiet NaN: 0x7FF8_0000_0000_0000 (sign=0, exponent all 1s, quiet bit).
40/// This has sign=0 so it will NOT be detected as tagged (our tagged values have sign=1).
41pub const CANONICAL_NAN: u64 = 0x7FF8_0000_0000_0000;
42
43/// Maximum i48 value: 2^47 - 1
44pub const I48_MAX: i64 = (1_i64 << 47) - 1;
45
46/// Minimum i48 value: -2^47
47pub const I48_MIN: i64 = -(1_i64 << 47);
48
49// ===== Tag values =====
50
51/// Heap pointer to `Arc<HeapValue>` (48-bit pointer in payload).
52pub const TAG_HEAP: u64 = 0b000;
53
54/// Inline i48 (48-bit signed integer, sign-extended to i64).
55pub const TAG_INT: u64 = 0b001;
56
57/// Inline bool (payload bit 0: 0=false, 1=true).
58pub const TAG_BOOL: u64 = 0b010;
59
60/// None (Option::None / null).
61pub const TAG_NONE: u64 = 0b011;
62
63/// Unit (void return value).
64pub const TAG_UNIT: u64 = 0b100;
65
66/// Function reference (payload = u16 function_id).
67pub const TAG_FUNCTION: u64 = 0b101;
68
69/// Module function reference (payload = u32 index).
70pub const TAG_MODULE_FN: u64 = 0b110;
71
72/// Reference to a stack slot (payload = absolute slot index).
73pub const TAG_REF: u64 = 0b111;
74
75// ===== Inline helpers =====
76
77/// Build a tagged NaN-boxed u64 from a tag and payload.
78#[inline(always)]
79pub fn make_tagged(tag: u64, payload: u64) -> u64 {
80    debug_assert!(tag <= 0b111);
81    debug_assert!(payload & !PAYLOAD_MASK == 0, "payload exceeds 48 bits");
82    TAG_BASE | (tag << TAG_SHIFT) | payload
83}
84
85/// Check whether a u64 is a tagged NaN-boxed value (as opposed to a plain f64).
86#[inline(always)]
87pub fn is_tagged(bits: u64) -> bool {
88    (bits & TAG_BASE) == TAG_BASE
89}
90
91/// Check whether a u64 is a plain f64 (not tagged).
92#[inline(always)]
93pub fn is_number(bits: u64) -> bool {
94    !is_tagged(bits)
95}
96
97/// Extract the 3-bit tag from a tagged NaN-boxed u64.
98#[inline(always)]
99pub fn get_tag(bits: u64) -> u64 {
100    (bits & TAG_MASK) >> TAG_SHIFT
101}
102
103/// Extract the 48-bit payload from a NaN-boxed u64.
104#[inline(always)]
105pub fn get_payload(bits: u64) -> u64 {
106    bits & PAYLOAD_MASK
107}
108
109/// Sign-extend a 48-bit value to i64.
110#[inline(always)]
111pub fn sign_extend_i48(bits: u64) -> i64 {
112    let shifted = (bits as i64) << 16;
113    shifted >> 16
114}
115
116// ===== HeapKind discriminator constants (for JIT dispatch) =====
117//
118// These mirror the `HeapKind` enum in heap_value.rs as integer constants,
119// enabling the JIT to dispatch on heap value types without linking to the enum.
120
121pub const HEAP_KIND_STRING: u8 = 0;
122pub const HEAP_KIND_ARRAY: u8 = 1;
123pub const HEAP_KIND_TYPED_OBJECT: u8 = 2;
124pub const HEAP_KIND_CLOSURE: u8 = 3;
125pub const HEAP_KIND_DECIMAL: u8 = 4;
126pub const HEAP_KIND_BIG_INT: u8 = 5;
127pub const HEAP_KIND_HOST_CLOSURE: u8 = 6;
128pub const HEAP_KIND_DATATABLE: u8 = 7;
129pub const HEAP_KIND_TYPED_TABLE: u8 = 8;
130pub const HEAP_KIND_ROW_VIEW: u8 = 9;
131pub const HEAP_KIND_COLUMN_REF: u8 = 10;
132pub const HEAP_KIND_INDEXED_TABLE: u8 = 11;
133pub const HEAP_KIND_RANGE: u8 = 12;
134pub const HEAP_KIND_ENUM: u8 = 13;
135pub const HEAP_KIND_SOME: u8 = 14;
136pub const HEAP_KIND_OK: u8 = 15;
137pub const HEAP_KIND_ERR: u8 = 16;
138pub const HEAP_KIND_FUTURE: u8 = 17;
139pub const HEAP_KIND_TASK_GROUP: u8 = 18;
140pub const HEAP_KIND_TRAIT_OBJECT: u8 = 19;
141pub const HEAP_KIND_EXPR_PROXY: u8 = 20;
142pub const HEAP_KIND_FILTER_EXPR: u8 = 21;
143pub const HEAP_KIND_TIME: u8 = 22;
144pub const HEAP_KIND_DURATION: u8 = 23;
145pub const HEAP_KIND_TIMESPAN: u8 = 24;
146pub const HEAP_KIND_TIMEFRAME: u8 = 25;
147pub const HEAP_KIND_TIME_REFERENCE: u8 = 26;
148pub const HEAP_KIND_DATETIME_EXPR: u8 = 27;
149pub const HEAP_KIND_DATA_DATETIME_REF: u8 = 28;
150pub const HEAP_KIND_TYPE_ANNOTATION: u8 = 29;
151pub const HEAP_KIND_TYPE_ANNOTATED_VALUE: u8 = 30;
152pub const HEAP_KIND_PRINT_RESULT: u8 = 31;
153pub const HEAP_KIND_SIMULATION_CALL: u8 = 32;
154pub const HEAP_KIND_FUNCTION_REF: u8 = 33;
155pub const HEAP_KIND_DATA_REFERENCE: u8 = 34;
156pub const HEAP_KIND_NUMBER: u8 = 35;
157pub const HEAP_KIND_BOOL: u8 = 36;
158pub const HEAP_KIND_NONE: u8 = 37;
159pub const HEAP_KIND_UNIT: u8 = 38;
160pub const HEAP_KIND_FUNCTION: u8 = 39;
161pub const HEAP_KIND_MODULE_FUNCTION: u8 = 40;
162pub const HEAP_KIND_HASHMAP: u8 = 41;
163pub const HEAP_KIND_CONTENT: u8 = 42;
164pub const HEAP_KIND_INSTANT: u8 = 43;
165pub const HEAP_KIND_IO_HANDLE: u8 = 44;
166pub const HEAP_KIND_SHARED_CELL: u8 = 45;
167pub const HEAP_KIND_NATIVE_SCALAR: u8 = 46;
168pub const HEAP_KIND_NATIVE_VIEW: u8 = 47;
169pub const HEAP_KIND_INT_ARRAY: u8 = 48;
170pub const HEAP_KIND_FLOAT_ARRAY: u8 = 49;
171pub const HEAP_KIND_BOOL_ARRAY: u8 = 50;
172pub const HEAP_KIND_MATRIX: u8 = 51;
173pub const HEAP_KIND_ITERATOR: u8 = 52;
174pub const HEAP_KIND_GENERATOR: u8 = 53;
175pub const HEAP_KIND_MUTEX: u8 = 54;
176pub const HEAP_KIND_ATOMIC: u8 = 55;
177pub const HEAP_KIND_LAZY: u8 = 56;
178pub const HEAP_KIND_I8_ARRAY: u8 = 57;
179pub const HEAP_KIND_I16_ARRAY: u8 = 58;
180pub const HEAP_KIND_I32_ARRAY: u8 = 59;
181pub const HEAP_KIND_U8_ARRAY: u8 = 60;
182pub const HEAP_KIND_U16_ARRAY: u8 = 61;
183pub const HEAP_KIND_U32_ARRAY: u8 = 62;
184pub const HEAP_KIND_U64_ARRAY: u8 = 63;
185pub const HEAP_KIND_F32_ARRAY: u8 = 64;
186pub const HEAP_KIND_SET: u8 = 65;
187pub const HEAP_KIND_DEQUE: u8 = 66;
188pub const HEAP_KIND_PRIORITY_QUEUE: u8 = 67;
189pub const HEAP_KIND_CHANNEL: u8 = 68;
190pub const HEAP_KIND_CHAR: u8 = 69;
191pub const HEAP_KIND_PROJECTED_REF: u8 = 70;
192pub const HEAP_KIND_FLOAT_ARRAY_SLICE: u8 = 71;
193
194#[cfg(test)]
195mod tests {
196    use super::*;
197
198    #[test]
199    fn test_tag_round_trip() {
200        for tag in 0..=7u64 {
201            let payload = 0x1234_5678_ABCDu64;
202            let bits = make_tagged(tag, payload);
203            assert!(is_tagged(bits));
204            assert!(!is_number(bits));
205            assert_eq!(get_tag(bits), tag);
206            assert_eq!(get_payload(bits), payload);
207        }
208    }
209
210    #[test]
211    fn test_f64_not_tagged() {
212        let f = 3.14f64;
213        assert!(!is_tagged(f.to_bits()));
214        assert!(is_number(f.to_bits()));
215    }
216
217    #[test]
218    fn test_canonical_nan_not_tagged() {
219        assert!(!is_tagged(CANONICAL_NAN));
220    }
221
222    #[test]
223    fn test_sign_extend_positive() {
224        assert_eq!(sign_extend_i48(42), 42);
225    }
226
227    #[test]
228    fn test_sign_extend_negative() {
229        // -1 as 48 bits: 0x0000_FFFF_FFFF_FFFF
230        let neg1_48 = PAYLOAD_MASK; // all 48 bits set
231        assert_eq!(sign_extend_i48(neg1_48), -1);
232    }
233
234    #[test]
235    fn test_sign_extend_boundary() {
236        // i48 max = 2^47 - 1
237        let max_48 = I48_MAX as u64;
238        assert_eq!(sign_extend_i48(max_48), I48_MAX);
239
240        // i48 min = -2^47 (bit 47 set, all lower bits zero)
241        let min_48 = (I48_MIN as u64) & PAYLOAD_MASK;
242        assert_eq!(sign_extend_i48(min_48), I48_MIN);
243    }
244
245    #[test]
246    fn test_heap_kind_constants_match_enum_order() {
247        // Verify the HEAP_KIND constants match the HeapKind enum discriminant order.
248        use crate::heap_value::HeapKind;
249        assert_eq!(HEAP_KIND_STRING, HeapKind::String as u8);
250        assert_eq!(HEAP_KIND_ARRAY, HeapKind::Array as u8);
251        assert_eq!(HEAP_KIND_TYPED_OBJECT, HeapKind::TypedObject as u8);
252        assert_eq!(HEAP_KIND_CLOSURE, HeapKind::Closure as u8);
253        assert_eq!(HEAP_KIND_DECIMAL, HeapKind::Decimal as u8);
254        assert_eq!(HEAP_KIND_BIG_INT, HeapKind::BigInt as u8);
255        assert_eq!(HEAP_KIND_HOST_CLOSURE, HeapKind::HostClosure as u8);
256        assert_eq!(HEAP_KIND_DATATABLE, HeapKind::DataTable as u8);
257        assert_eq!(HEAP_KIND_TYPED_TABLE, HeapKind::TypedTable as u8);
258        assert_eq!(HEAP_KIND_ROW_VIEW, HeapKind::RowView as u8);
259        assert_eq!(HEAP_KIND_COLUMN_REF, HeapKind::ColumnRef as u8);
260        assert_eq!(HEAP_KIND_INDEXED_TABLE, HeapKind::IndexedTable as u8);
261        assert_eq!(HEAP_KIND_RANGE, HeapKind::Range as u8);
262        assert_eq!(HEAP_KIND_ENUM, HeapKind::Enum as u8);
263        assert_eq!(HEAP_KIND_SOME, HeapKind::Some as u8);
264        assert_eq!(HEAP_KIND_OK, HeapKind::Ok as u8);
265        assert_eq!(HEAP_KIND_ERR, HeapKind::Err as u8);
266        assert_eq!(HEAP_KIND_FUTURE, HeapKind::Future as u8);
267        assert_eq!(HEAP_KIND_TASK_GROUP, HeapKind::TaskGroup as u8);
268        assert_eq!(HEAP_KIND_TRAIT_OBJECT, HeapKind::TraitObject as u8);
269        assert_eq!(HEAP_KIND_EXPR_PROXY, HeapKind::ExprProxy as u8);
270        assert_eq!(HEAP_KIND_FILTER_EXPR, HeapKind::FilterExpr as u8);
271        assert_eq!(HEAP_KIND_TIME, HeapKind::Time as u8);
272        assert_eq!(HEAP_KIND_DURATION, HeapKind::Duration as u8);
273        assert_eq!(HEAP_KIND_TIMESPAN, HeapKind::TimeSpan as u8);
274        assert_eq!(HEAP_KIND_TIMEFRAME, HeapKind::Timeframe as u8);
275        assert_eq!(HEAP_KIND_TIME_REFERENCE, HeapKind::TimeReference as u8);
276        assert_eq!(HEAP_KIND_DATETIME_EXPR, HeapKind::DateTimeExpr as u8);
277        assert_eq!(HEAP_KIND_DATA_DATETIME_REF, HeapKind::DataDateTimeRef as u8);
278        assert_eq!(HEAP_KIND_TYPE_ANNOTATION, HeapKind::TypeAnnotation as u8);
279        assert_eq!(
280            HEAP_KIND_TYPE_ANNOTATED_VALUE,
281            HeapKind::TypeAnnotatedValue as u8
282        );
283        assert_eq!(HEAP_KIND_PRINT_RESULT, HeapKind::PrintResult as u8);
284        assert_eq!(HEAP_KIND_SIMULATION_CALL, HeapKind::SimulationCall as u8);
285        assert_eq!(HEAP_KIND_FUNCTION_REF, HeapKind::FunctionRef as u8);
286        assert_eq!(HEAP_KIND_DATA_REFERENCE, HeapKind::DataReference as u8);
287        assert_eq!(HEAP_KIND_NUMBER, HeapKind::Number as u8);
288        assert_eq!(HEAP_KIND_BOOL, HeapKind::Bool as u8);
289        assert_eq!(HEAP_KIND_NONE, HeapKind::None as u8);
290        assert_eq!(HEAP_KIND_UNIT, HeapKind::Unit as u8);
291        assert_eq!(HEAP_KIND_FUNCTION, HeapKind::Function as u8);
292        assert_eq!(HEAP_KIND_MODULE_FUNCTION, HeapKind::ModuleFunction as u8);
293        assert_eq!(HEAP_KIND_HASHMAP, HeapKind::HashMap as u8);
294        assert_eq!(HEAP_KIND_CONTENT, HeapKind::Content as u8);
295        assert_eq!(HEAP_KIND_INSTANT, HeapKind::Instant as u8);
296        assert_eq!(HEAP_KIND_IO_HANDLE, HeapKind::IoHandle as u8);
297        assert_eq!(HEAP_KIND_SHARED_CELL, HeapKind::SharedCell as u8);
298        assert_eq!(HEAP_KIND_NATIVE_SCALAR, HeapKind::NativeScalar as u8);
299        assert_eq!(HEAP_KIND_NATIVE_VIEW, HeapKind::NativeView as u8);
300        assert_eq!(HEAP_KIND_INT_ARRAY, HeapKind::IntArray as u8);
301        assert_eq!(HEAP_KIND_FLOAT_ARRAY, HeapKind::FloatArray as u8);
302        assert_eq!(HEAP_KIND_BOOL_ARRAY, HeapKind::BoolArray as u8);
303        assert_eq!(HEAP_KIND_MATRIX, HeapKind::Matrix as u8);
304        assert_eq!(HEAP_KIND_ITERATOR, HeapKind::Iterator as u8);
305        assert_eq!(HEAP_KIND_GENERATOR, HeapKind::Generator as u8);
306        assert_eq!(HEAP_KIND_MUTEX, HeapKind::Mutex as u8);
307        assert_eq!(HEAP_KIND_ATOMIC, HeapKind::Atomic as u8);
308        assert_eq!(HEAP_KIND_LAZY, HeapKind::Lazy as u8);
309        assert_eq!(HEAP_KIND_I8_ARRAY, HeapKind::I8Array as u8);
310        assert_eq!(HEAP_KIND_I16_ARRAY, HeapKind::I16Array as u8);
311        assert_eq!(HEAP_KIND_I32_ARRAY, HeapKind::I32Array as u8);
312        assert_eq!(HEAP_KIND_U8_ARRAY, HeapKind::U8Array as u8);
313        assert_eq!(HEAP_KIND_U16_ARRAY, HeapKind::U16Array as u8);
314        assert_eq!(HEAP_KIND_U32_ARRAY, HeapKind::U32Array as u8);
315        assert_eq!(HEAP_KIND_U64_ARRAY, HeapKind::U64Array as u8);
316        assert_eq!(HEAP_KIND_F32_ARRAY, HeapKind::F32Array as u8);
317        assert_eq!(HEAP_KIND_SET, HeapKind::Set as u8);
318        assert_eq!(HEAP_KIND_DEQUE, HeapKind::Deque as u8);
319        assert_eq!(HEAP_KIND_PRIORITY_QUEUE, HeapKind::PriorityQueue as u8);
320        assert_eq!(HEAP_KIND_CHANNEL, HeapKind::Channel as u8);
321        assert_eq!(HEAP_KIND_CHAR, HeapKind::Char as u8);
322        assert_eq!(HEAP_KIND_PROJECTED_REF, HeapKind::ProjectedRef as u8);
323        assert_eq!(
324            HEAP_KIND_FLOAT_ARRAY_SLICE,
325            HeapKind::FloatArraySlice as u8
326        );
327    }
328}