Skip to main content

aver/
nan_value.rs

1//! NaN-boxed compact Value representation (8 bytes per value).
2//!
3//! Layout: every value is a `u64` interpreted as an IEEE 754 `f64`.
4//!
5//! - **Float**: any f64 that is NOT a quiet NaN with our marker -> stored directly.
6//! - **Everything else**: encoded as a quiet NaN with tag + payload in the mantissa.
7//!
8//! IEEE 754 quiet NaN: exponent=0x7FF (all 1s), quiet bit=1, plus our marker bit.
9//! We use `0x7FFC` as 14-bit prefix (bits 63-50), leaving bits 49-0 free.
10//!
11//! ```text
12//! 63      50 49  46 45                    0
13//! ┌────────┬──────┬────────────────────────┐
14//! │ 0x7FFC │ tag  │       payload          │
15//! │ 14 bits│ 4 bit│       46 bits          │
16//! └────────┴──────┴────────────────────────┘
17//! ```
18//!
19//! Tag map:
20//!   0  = Int        payload bit45: 0=inline(45-bit signed), 1=arena index
21//!   1  = Immediate  payload bits 0-1: 00=false, 01=true, 10=unit, 11=none
22//!   2  = Wrapper    payload bits 0-1: 00=some, 01=ok, 10=err; rest=arena index
23//!   3  = String     payload = arena index
24//!   4  = List       payload = arena index
25//!   5  = Tuple      payload = arena index
26//!   6  = Map        payload = arena index
27//!   7  = Record     payload = arena index
28//!   8  = Variant    payload = arena index
29//!   9  = Fn         payload = arena index
30//!   10 = Builtin    payload = arena index
31//!   11 = Namespace  payload = arena index
32//!   12-15 = (reserved)
33
34use std::rc::Rc;
35
36use crate::value::FunctionValue;
37
38/// Persistent immutable map -> O(1) clone via structural sharing.
39pub type PersistentMap = im::HashMap<u64, (NanValue, NanValue)>;
40
41// ---------------------------------------------------------------------------
42// Bit layout constants
43// ---------------------------------------------------------------------------
44
45const QNAN: u64 = 0x7FFC_0000_0000_0000;
46const QNAN_MASK: u64 = 0xFFFC_0000_0000_0000;
47const TAG_SHIFT: u32 = 46;
48const TAG_MASK: u64 = 0xF;
49const PAYLOAD_MASK: u64 = (1u64 << 46) - 1;
50
51const TAG_INT: u64 = 0;
52const TAG_IMMEDIATE: u64 = 1;
53const TAG_WRAPPER: u64 = 2;
54const TAG_STRING: u64 = 3;
55const TAG_LIST: u64 = 4;
56const TAG_TUPLE: u64 = 5;
57const TAG_MAP: u64 = 6;
58const TAG_RECORD: u64 = 7;
59const TAG_VARIANT: u64 = 8;
60const TAG_FN: u64 = 9;
61const TAG_BUILTIN: u64 = 10;
62const TAG_NAMESPACE: u64 = 11;
63
64const IMM_FALSE: u64 = 0;
65const IMM_TRUE: u64 = 1;
66const IMM_UNIT: u64 = 2;
67const IMM_NONE: u64 = 3;
68
69const WRAP_SOME: u64 = 0;
70const WRAP_OK: u64 = 1;
71const WRAP_ERR: u64 = 2;
72
73const INT_BIG_BIT: u64 = 1u64 << 45;
74const INT_INLINE_MASK: u64 = (1u64 << 45) - 1;
75const INT_INLINE_MAX: i64 = (1i64 << 44) - 1;
76const INT_INLINE_MIN: i64 = -(1i64 << 44);
77
78// ---------------------------------------------------------------------------
79// NanValue - the 8-byte compact value
80// ---------------------------------------------------------------------------
81
82#[derive(Clone, Copy)]
83pub struct NanValue(u64);
84
85// -- Encoding / decoding ---------------------------------------------------
86
87impl NanValue {
88    #[inline]
89    fn encode(tag: u64, payload: u64) -> Self {
90        debug_assert!(tag <= TAG_MASK);
91        debug_assert!(payload <= PAYLOAD_MASK);
92        NanValue(QNAN | (tag << TAG_SHIFT) | payload)
93    }
94
95    #[inline]
96    fn is_nan_boxed(self) -> bool {
97        (self.0 & QNAN_MASK) == QNAN
98    }
99
100    #[inline]
101    fn tag(self) -> u64 {
102        (self.0 >> TAG_SHIFT) & TAG_MASK
103    }
104
105    #[inline]
106    fn payload(self) -> u64 {
107        self.0 & PAYLOAD_MASK
108    }
109
110    // -- Constructors ------------------------------------------------------
111
112    #[inline]
113    pub fn new_float(f: f64) -> Self {
114        let bits = f.to_bits();
115        if (bits & QNAN_MASK) == QNAN {
116            NanValue(bits ^ 1)
117        } else {
118            NanValue(bits)
119        }
120    }
121
122    #[inline]
123    pub fn as_float(self) -> f64 {
124        f64::from_bits(self.0)
125    }
126
127    #[inline]
128    pub fn new_int_inline(i: i64) -> Self {
129        debug_assert!((INT_INLINE_MIN..=INT_INLINE_MAX).contains(&i));
130        let payload = (i as u64) & INT_INLINE_MASK;
131        Self::encode(TAG_INT, payload)
132    }
133
134    #[inline]
135    pub fn new_int_arena(arena_index: u32) -> Self {
136        Self::encode(TAG_INT, INT_BIG_BIT | (arena_index as u64))
137    }
138
139    #[inline]
140    pub fn new_int(i: i64, arena: &mut Arena) -> Self {
141        if (INT_INLINE_MIN..=INT_INLINE_MAX).contains(&i) {
142            Self::new_int_inline(i)
143        } else {
144            let idx = arena.push_i64(i);
145            Self::new_int_arena(idx)
146        }
147    }
148
149    #[inline]
150    pub fn as_int(self, arena: &Arena) -> i64 {
151        let p = self.payload();
152        if p & INT_BIG_BIT != 0 {
153            let idx = (p & !INT_BIG_BIT) as u32;
154            arena.get_i64(idx)
155        } else {
156            let raw = p & INT_INLINE_MASK;
157            if raw & (1u64 << 44) != 0 {
158                (raw | !INT_INLINE_MASK) as i64
159            } else {
160                raw as i64
161            }
162        }
163    }
164
165    // -- Immediates --------------------------------------------------------
166
167    pub const FALSE: NanValue = NanValue(QNAN | (TAG_IMMEDIATE << TAG_SHIFT) | IMM_FALSE);
168    pub const TRUE: NanValue = NanValue(QNAN | (TAG_IMMEDIATE << TAG_SHIFT) | IMM_TRUE);
169    pub const UNIT: NanValue = NanValue(QNAN | (TAG_IMMEDIATE << TAG_SHIFT) | IMM_UNIT);
170    pub const NONE: NanValue = NanValue(QNAN | (TAG_IMMEDIATE << TAG_SHIFT) | IMM_NONE);
171
172    #[inline]
173    pub fn new_bool(b: bool) -> Self {
174        if b { Self::TRUE } else { Self::FALSE }
175    }
176
177    #[inline]
178    pub fn as_bool(self) -> bool {
179        self.0 == Self::TRUE.0
180    }
181
182    // -- Wrappers (Some/Ok/Err) -------------------------------------------
183
184    #[inline]
185    pub fn new_some(inner_index: u32) -> Self {
186        Self::encode(TAG_WRAPPER, WRAP_SOME | ((inner_index as u64) << 2))
187    }
188
189    #[inline]
190    pub fn new_ok(inner_index: u32) -> Self {
191        Self::encode(TAG_WRAPPER, WRAP_OK | ((inner_index as u64) << 2))
192    }
193
194    #[inline]
195    pub fn new_err(inner_index: u32) -> Self {
196        Self::encode(TAG_WRAPPER, WRAP_ERR | ((inner_index as u64) << 2))
197    }
198
199    #[inline]
200    pub fn wrapper_kind(self) -> u64 {
201        self.payload() & 3
202    }
203
204    #[inline]
205    pub fn wrapper_index(self) -> u32 {
206        (self.payload() >> 2) as u32
207    }
208
209    // -- Arena-backed constructors -----------------------------------------
210
211    #[inline]
212    pub fn new_string(arena_index: u32) -> Self {
213        Self::encode(TAG_STRING, arena_index as u64)
214    }
215
216    #[inline]
217    pub fn new_list(arena_index: u32) -> Self {
218        Self::encode(TAG_LIST, arena_index as u64)
219    }
220
221    #[inline]
222    pub fn new_tuple(arena_index: u32) -> Self {
223        Self::encode(TAG_TUPLE, arena_index as u64)
224    }
225
226    #[inline]
227    pub fn new_map(arena_index: u32) -> Self {
228        Self::encode(TAG_MAP, arena_index as u64)
229    }
230
231    #[inline]
232    pub fn new_record(arena_index: u32) -> Self {
233        Self::encode(TAG_RECORD, arena_index as u64)
234    }
235
236    #[inline]
237    pub fn new_variant(arena_index: u32) -> Self {
238        Self::encode(TAG_VARIANT, arena_index as u64)
239    }
240
241    #[inline]
242    pub fn new_fn(arena_index: u32) -> Self {
243        Self::encode(TAG_FN, arena_index as u64)
244    }
245
246    #[inline]
247    pub fn new_builtin(arena_index: u32) -> Self {
248        Self::encode(TAG_BUILTIN, arena_index as u64)
249    }
250
251    #[inline]
252    pub fn new_namespace(arena_index: u32) -> Self {
253        Self::encode(TAG_NAMESPACE, arena_index as u64)
254    }
255
256    #[inline]
257    pub fn arena_index(self) -> u32 {
258        self.payload() as u32
259    }
260
261    #[inline]
262    pub fn heap_index(self) -> Option<u32> {
263        if !self.is_nan_boxed() {
264            return None;
265        }
266        match self.tag() {
267            TAG_INT => {
268                let p = self.payload();
269                if p & INT_BIG_BIT != 0 {
270                    Some((p & !INT_BIG_BIT) as u32)
271                } else {
272                    None
273                }
274            }
275            TAG_WRAPPER => Some(self.wrapper_index()),
276            TAG_STRING | TAG_LIST | TAG_TUPLE | TAG_MAP | TAG_RECORD | TAG_VARIANT | TAG_FN
277            | TAG_BUILTIN | TAG_NAMESPACE => Some(self.arena_index()),
278            _ => None,
279        }
280    }
281
282    #[inline]
283    pub fn with_heap_index(self, index: u32) -> Self {
284        if !self.is_nan_boxed() {
285            return self;
286        }
287        match self.tag() {
288            TAG_INT => {
289                debug_assert!(self.payload() & INT_BIG_BIT != 0);
290                Self::new_int_arena(index)
291            }
292            TAG_WRAPPER => match self.wrapper_kind() {
293                WRAP_SOME => Self::new_some(index),
294                WRAP_OK => Self::new_ok(index),
295                WRAP_ERR => Self::new_err(index),
296                _ => self,
297            },
298            TAG_STRING => Self::new_string(index),
299            TAG_LIST => Self::new_list(index),
300            TAG_TUPLE => Self::new_tuple(index),
301            TAG_MAP => Self::new_map(index),
302            TAG_RECORD => Self::new_record(index),
303            TAG_VARIANT => Self::new_variant(index),
304            TAG_FN => Self::new_fn(index),
305            TAG_BUILTIN => Self::new_builtin(index),
306            TAG_NAMESPACE => Self::new_namespace(index),
307            _ => self,
308        }
309    }
310
311    // -- Type checks -------------------------------------------------------
312
313    #[inline]
314    pub fn is_float(self) -> bool {
315        !self.is_nan_boxed()
316    }
317
318    #[inline]
319    pub fn is_int(self) -> bool {
320        self.is_nan_boxed() && self.tag() == TAG_INT
321    }
322
323    #[inline]
324    pub fn is_bool(self) -> bool {
325        self.is_nan_boxed()
326            && self.tag() == TAG_IMMEDIATE
327            && (self.payload() == IMM_TRUE || self.payload() == IMM_FALSE)
328    }
329
330    #[inline]
331    pub fn is_unit(self) -> bool {
332        self.0 == Self::UNIT.0
333    }
334
335    #[inline]
336    pub fn is_none(self) -> bool {
337        self.0 == Self::NONE.0
338    }
339
340    #[inline]
341    pub fn is_some(self) -> bool {
342        self.is_nan_boxed() && self.tag() == TAG_WRAPPER && self.wrapper_kind() == WRAP_SOME
343    }
344
345    #[inline]
346    pub fn is_ok(self) -> bool {
347        self.is_nan_boxed() && self.tag() == TAG_WRAPPER && self.wrapper_kind() == WRAP_OK
348    }
349
350    #[inline]
351    pub fn is_err(self) -> bool {
352        self.is_nan_boxed() && self.tag() == TAG_WRAPPER && self.wrapper_kind() == WRAP_ERR
353    }
354
355    #[inline]
356    pub fn is_string(self) -> bool {
357        self.is_nan_boxed() && self.tag() == TAG_STRING
358    }
359
360    #[inline]
361    pub fn is_list(self) -> bool {
362        self.is_nan_boxed() && self.tag() == TAG_LIST
363    }
364
365    #[inline]
366    pub fn is_record(self) -> bool {
367        self.is_nan_boxed() && self.tag() == TAG_RECORD
368    }
369
370    #[inline]
371    pub fn is_fn(self) -> bool {
372        self.is_nan_boxed() && self.tag() == TAG_FN
373    }
374
375    #[inline]
376    pub fn is_variant(self) -> bool {
377        self.is_nan_boxed() && self.tag() == TAG_VARIANT
378    }
379
380    #[inline]
381    pub fn is_map(self) -> bool {
382        self.is_nan_boxed() && self.tag() == TAG_MAP
383    }
384
385    #[inline]
386    pub fn is_tuple(self) -> bool {
387        self.is_nan_boxed() && self.tag() == TAG_TUPLE
388    }
389
390    #[inline]
391    pub fn is_builtin(self) -> bool {
392        self.is_nan_boxed() && self.tag() == TAG_BUILTIN
393    }
394
395    #[inline]
396    pub fn is_namespace(self) -> bool {
397        self.is_nan_boxed() && self.tag() == TAG_NAMESPACE
398    }
399
400    pub fn type_name(self) -> &'static str {
401        if self.is_float() {
402            return "Float";
403        }
404        match self.tag() {
405            TAG_INT => "Int",
406            TAG_IMMEDIATE => match self.payload() {
407                IMM_FALSE | IMM_TRUE => "Bool",
408                IMM_UNIT => "Unit",
409                IMM_NONE => "Option.None",
410                _ => "Unknown",
411            },
412            TAG_WRAPPER => match self.wrapper_kind() {
413                WRAP_SOME => "Option.Some",
414                WRAP_OK => "Result.Ok",
415                WRAP_ERR => "Result.Err",
416                _ => "Unknown",
417            },
418            TAG_STRING => "String",
419            TAG_LIST => "List",
420            TAG_TUPLE => "Tuple",
421            TAG_MAP => "Map",
422            TAG_RECORD => "Record",
423            TAG_VARIANT => "Variant",
424            TAG_FN => "Fn",
425            TAG_BUILTIN => "Builtin",
426            TAG_NAMESPACE => "Namespace",
427            _ => "Unknown",
428        }
429    }
430
431    /// Raw bits - useful for using as HashMap key (inline values only).
432    #[inline]
433    pub fn bits(self) -> u64 {
434        self.0
435    }
436
437    /// Content-based hash for use as map key. For inline values (int, float, bool),
438    /// uses bits(). For arena-backed strings, hashes the string content so that
439    /// two NanValues for the same string content produce the same key regardless
440    /// of arena index.
441    pub fn map_key_hash(self, arena: &Arena) -> u64 {
442        if self.is_string() {
443            use std::hash::{Hash, Hasher};
444            let mut hasher = std::collections::hash_map::DefaultHasher::new();
445            3u8.hash(&mut hasher);
446            arena.get_string(self.arena_index()).hash(&mut hasher);
447            hasher.finish()
448        } else {
449            self.bits()
450        }
451    }
452}
453
454// -- Debug -----------------------------------------------------------------
455
456impl std::fmt::Debug for NanValue {
457    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
458        if self.is_float() {
459            return write!(f, "Float({})", self.as_float());
460        }
461        match self.tag() {
462            TAG_INT => {
463                if self.payload() & INT_BIG_BIT != 0 {
464                    write!(f, "Int(arena:{})", (self.payload() & !INT_BIG_BIT) as u32)
465                } else {
466                    let raw = self.payload() & INT_INLINE_MASK;
467                    let val = if raw & (1u64 << 44) != 0 {
468                        (raw | !INT_INLINE_MASK) as i64
469                    } else {
470                        raw as i64
471                    };
472                    write!(f, "Int({})", val)
473                }
474            }
475            TAG_IMMEDIATE => match self.payload() {
476                IMM_FALSE => write!(f, "False"),
477                IMM_TRUE => write!(f, "True"),
478                IMM_UNIT => write!(f, "Unit"),
479                IMM_NONE => write!(f, "None"),
480                _ => write!(f, "Immediate({})", self.payload()),
481            },
482            TAG_WRAPPER => {
483                let kind = match self.wrapper_kind() {
484                    WRAP_SOME => "Some",
485                    WRAP_OK => "Ok",
486                    WRAP_ERR => "Err",
487                    _ => "?",
488                };
489                write!(f, "{}(arena:{})", kind, self.wrapper_index())
490            }
491            _ => write!(f, "{}(arena:{})", self.type_name(), self.arena_index()),
492        }
493    }
494}
495
496// ---------------------------------------------------------------------------
497// Arena
498// ---------------------------------------------------------------------------
499
500#[derive(Debug, Clone)]
501pub struct Arena {
502    young_entries: Vec<ArenaEntry>,
503    yard_entries: Vec<ArenaEntry>,
504    handoff_entries: Vec<ArenaEntry>,
505    stable_entries: Vec<ArenaEntry>,
506    scratch_young: Vec<u32>,
507    scratch_yard: Vec<u32>,
508    scratch_handoff: Vec<u32>,
509    scratch_stable: Vec<u32>,
510    peak_usage: ArenaUsage,
511    alloc_space: AllocSpace,
512    pub(crate) type_names: Vec<String>,
513    pub(crate) type_field_names: Vec<Vec<String>>,
514    pub(crate) type_variant_names: Vec<Vec<String>>,
515}
516
517#[derive(Debug, Clone)]
518pub enum ArenaEntry {
519    Int(i64),
520    String(Rc<str>),
521    List(ArenaList),
522    Tuple(Vec<NanValue>),
523    Map(PersistentMap),
524    Record {
525        type_id: u32,
526        fields: Vec<NanValue>,
527    },
528    Variant {
529        type_id: u32,
530        variant_id: u16,
531        fields: Vec<NanValue>,
532    },
533    Fn(Rc<FunctionValue>),
534    Builtin(Rc<str>),
535    Namespace {
536        name: Rc<str>,
537        members: Vec<(Rc<str>, NanValue)>,
538    },
539    Boxed(NanValue),
540}
541
542#[derive(Debug, Clone)]
543pub enum ArenaList {
544    Flat {
545        items: Rc<Vec<NanValue>>,
546        start: usize,
547    },
548    Prepend {
549        head: NanValue,
550        tail: NanValue,
551        len: usize,
552    },
553    Concat {
554        left: NanValue,
555        right: NanValue,
556        len: usize,
557    },
558    Segments {
559        current: NanValue,
560        rest: Rc<Vec<NanValue>>,
561        start: usize,
562        len: usize,
563    },
564}
565
566const LIST_APPEND_CHUNK_LIMIT: usize = 128;
567
568#[derive(Debug, Clone, Copy, PartialEq, Eq)]
569enum HeapSpace {
570    Young = 0,
571    Yard = 1,
572    Handoff = 2,
573    Stable = 3,
574}
575
576#[derive(Debug, Clone, Copy, PartialEq, Eq)]
577pub enum AllocSpace {
578    Young,
579    Yard,
580    Handoff,
581}
582
583#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
584pub struct ArenaUsage {
585    pub young: usize,
586    pub yard: usize,
587    pub handoff: usize,
588    pub stable: usize,
589}
590
591impl ArenaUsage {
592    pub fn total(self) -> usize {
593        self.young + self.yard + self.handoff + self.stable
594    }
595}
596
597const HEAP_SPACE_SHIFT: u32 = 30;
598const HEAP_SPACE_MASK_U32: u32 = 0b11 << HEAP_SPACE_SHIFT;
599const HEAP_INDEX_MASK_U32: u32 = (1 << HEAP_SPACE_SHIFT) - 1;
600
601mod arena;
602mod compare;
603mod convert;
604mod lists;
605mod memory;
606
607#[cfg(test)]
608#[allow(clippy::approx_constant)]
609mod tests;