Skip to main content

oxide_core/heap/
mod.rs

1//! The heap: typed arenas, the value heap, the collector and the root set.
2//!
3//! Every allocating operation takes `&mut Heap` and passes exactly one
4//! safepoint before it allocates, rooting its own arguments for that
5//! collection. A caller that holds an index in a Rust local across a second
6//! allocating call must root it on [`RootStack`](gc::RootStack) first; see
7//! `core/README.md`.
8
9pub mod arena;
10pub mod atom;
11pub mod bigint;
12pub mod buffer;
13pub mod code;
14pub mod gc;
15pub(crate) mod hash;
16pub mod object;
17pub mod report;
18pub mod string;
19pub mod symbol;
20pub mod table;
21pub mod values;
22
23use std::fmt;
24
25use self::arena::Arena;
26use self::atom::{AtomTable, KEY_INDEX_LIMIT, PropertyKey};
27use self::bigint::BigIntRecord;
28use self::gc::{GcState, Handles, HeapLimits, RootStack, Roots};
29use self::hash::FxHashMap;
30use self::object::{
31    FunctionBytecode, FunctionRecord, Internal, InternalData, InternalTag, NONE, ObjectHeader,
32    ObjectKind, PayloadArenas, flags,
33};
34use self::string::{AtomKeep, StringRecord, StringRepr};
35use self::symbol::SymbolRecord;
36use self::values::{Range, ValueHeap};
37use crate::bytecode::CodeUnitId;
38use crate::shape::{Dictionary, PrototypeInfo, STATIC_CELL, Shape, ValidityCell};
39use crate::value::{BigIntId, BytecodeId, ObjectId, ScopeId, ShapeId, StringId, SymbolId, Value};
40use crate::vm::VmStack;
41use crate::vm::ic::IcStore;
42
43#[derive(Clone, Copy, Debug, PartialEq, Eq)]
44pub enum HeapError {
45    /// The heap cap would be crossed even after a collection. The caller turns
46    /// it into the JavaScript `InternalError`.
47    OutOfMemory,
48    /// `add_property` on a key the object already has.
49    DuplicateKey,
50    /// `set_prototype` would create a cycle.
51    PrototypeCycle,
52}
53
54impl fmt::Display for HeapError {
55    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56        f.write_str(match self {
57            HeapError::OutOfMemory => "out of memory",
58            HeapError::DuplicateKey => "duplicate property key",
59            HeapError::PrototypeCycle => "cyclic prototype chain",
60        })
61    }
62}
63
64impl std::error::Error for HeapError {}
65
66/// One unit of the tree [`Heap::alloc_bytecode_function`] creates.
67#[derive(Clone, Copy, Debug)]
68pub struct BytecodeTemplate<'a> {
69    pub unit: CodeUnitId,
70    pub constants: &'a [Value],
71    /// Indices into the tree, in `MakeClosure` order.
72    pub children: &'a [u32],
73}
74
75/// What one operation will allocate, for the trigger and the cap.
76#[derive(Clone, Copy, Debug, Default)]
77pub(crate) struct Need {
78    pub objects: usize,
79    pub shapes: usize,
80    pub strings: usize,
81    pub symbols: usize,
82    pub bigints: usize,
83    pub cells: usize,
84    pub functions: usize,
85    pub bytecodes: usize,
86    pub internals: usize,
87    /// Up to two value-heap ranges.
88    pub values: [u32; 2],
89    /// Bytes owned outside the arenas (string contents, limbs).
90    pub extra: usize,
91}
92
93impl Need {
94    /// Bytes charged to the collection trigger. Shapes and validity cells are
95    /// reserved conservatively (an operation that might create one asks for
96    /// one), so they count toward the cap through [`Heap::growth_bytes`] but
97    /// not toward the trigger.
98    pub fn nominal_bytes(&self) -> usize {
99        self.objects * size_of::<ObjectHeader>()
100            + self.strings * size_of::<StringRecord>()
101            + self.symbols * size_of::<SymbolRecord>()
102            + self.bigints * size_of::<BigIntRecord>()
103            + self.functions * size_of::<FunctionRecord>()
104            + self.bytecodes * size_of::<FunctionBytecode>()
105            + self.internals * size_of::<Internal>()
106            + (values::class_len(self.values[0]) + values::class_len(self.values[1])) as usize * 8
107            + self.extra
108    }
109}
110
111pub struct Heap {
112    pub(crate) objects: Arena<ObjectHeader>,
113    pub(crate) shapes: Arena<Shape>,
114    pub(crate) strings: Arena<StringRecord>,
115    /// `WeakRef` targets kept alive until the current job ends.
116    pub(crate) kept: Vec<Value>,
117    /// The rejected promises without a handler, in rejection order, while the
118    /// embedder tracks rejections. Roots.
119    pub(crate) rejections: Vec<Value>,
120    /// Arrays that have had a hole: QuickJS prints them by their index
121    /// properties even after the hole is gone. Weak.
122    pub(crate) holey: crate::heap::hash::FxHashSet<u32>,
123    /// Every module record: modules live as long as the runtime.
124    pub(crate) modules: Vec<Value>,
125    /// Bytes of flattened ropes (the arena counts a rope as empty).
126    pub(crate) rope_bytes: std::cell::Cell<usize>,
127    /// Each unflattened rope's two halves, by string index (kept out of
128    /// [`StringRecord`], which every string pays for).
129    pub(crate) ropes: FxHashMap<u32, (StringId, StringId)>,
130    /// Whether `$262.IsHTMLDDA` exists, so ToBoolean of an object skips
131    /// looking for it otherwise.
132    pub(crate) html_dda: bool,
133    pub(crate) symbols: Arena<SymbolRecord>,
134    pub(crate) bigints: Arena<BigIntRecord>,
135    pub(crate) cells: Arena<ValidityCell>,
136    pub(crate) values: ValueHeap,
137    pub(crate) payloads: PayloadArenas,
138    pub(crate) atoms: AtomTable,
139    /// What [`Heap::intern`] does with the atoms it returns.
140    pub(crate) atom_mode: AtomMode,
141    /// The atoms interned in [`AtomMode::Pin`]: roots for the heap's
142    /// lifetime.
143    pub(crate) pinned_atoms: Vec<StringId>,
144    /// The atoms interned in [`AtomMode::Hold`]: roots until the
145    /// compilation that interned them ends.
146    pub(crate) held_atoms: Vec<StringId>,
147    /// The code units compiled at run time.
148    pub(crate) code: code::DynamicUnits,
149    /// The regular-expression programs, by `RegExp` object payload.
150    pub(crate) regexps: crate::vm::regexp::RegExpStore,
151    /// Transition-tree roots by `(prototype index or NONE, kind)`. Weak.
152    pub(crate) shape_roots: FxHashMap<(u32, ObjectKind), ShapeId>,
153    /// One-entry cache in front of `shape_roots`; cleared by every collection.
154    pub(crate) last_root: Option<((u32, ObjectKind), ShapeId)>,
155    /// Weak in key and dependents.
156    pub(crate) prototypes: FxHashMap<ObjectId, PrototypeInfo>,
157    pub(crate) roots: Roots,
158    pub(crate) gc: GcState,
159    /// The register file and the frame table: fixed roots, so a safepoint marks
160    /// the running frames with no parameter.
161    pub(crate) vm: VmStack,
162    /// The inline caches: beside the code store in spirit, inside the heap so
163    /// that every collection prunes them.
164    pub(crate) ics: IcStore,
165    /// The global object's dictionary generation: bumped by every insert,
166    /// remove and reflag of its properties, never by a value write. A global
167    /// cache entry hits while it is unchanged.
168    pub(crate) global_generation: u32,
169    /// No object used as a prototype has an index property or is exotic: a
170    /// write into an element hole cannot meet an inherited setter or read-only
171    /// index, so the loop's dense write may fill it. Cleared for good the first
172    /// time that stops holding.
173    pub(crate) holes_writable: bool,
174    /// The shapes `MakeClosure` gives a new function (`length`, `name`, and
175    /// `prototype` for the third), per function kind: learned from the first
176    /// closure of each kind and marked as roots.
177    pub(crate) closure_shapes: [Option<ShapeId>; CLOSURE_SHAPES],
178    /// The shapes of a new `arguments` object (`length`, `@@iterator`,
179    /// `callee`), sloppy then strict: learned from the first of each and
180    /// marked as roots.
181    pub(crate) arguments_shapes: [Option<ShapeId>; 2],
182    /// The job queue: roots until each runs.
183    pub(crate) jobs: std::collections::VecDeque<crate::vm::jobs::Job>,
184}
185
186/// How long an atom [`Heap::intern`] returns is kept. Atoms are otherwise
187/// collected like any string: a property key, a shape, a dynamic unit or a
188/// value keeps one alive.
189#[derive(Clone, Copy, Debug, PartialEq, Eq)]
190pub enum AtomMode {
191    /// For the heap's lifetime: the realm's names and the builtins'
192    /// properties, interned while the runtime is created, and the keys of
193    /// the units published before the program runs.
194    Pin,
195    /// Until the running compilation ends: the keys, names and string
196    /// constants of code compiled at run time, before its units are
197    /// published and instantiated.
198    Hold,
199    /// No longer than its holders.
200    Weak,
201}
202
203/// The function kinds with a cached closure shape: plain, with a
204/// `prototype`, async, generator, async generator.
205pub(crate) const CLOSURE_SHAPES: usize = 5;
206
207impl Default for Heap {
208    fn default() -> Self {
209        Self::new(HeapLimits::default())
210    }
211}
212
213impl Heap {
214    pub fn new(limits: HeapLimits) -> Self {
215        let mut heap = Heap {
216            objects: Arena::new(),
217            shapes: Arena::new(),
218            strings: Arena::new(),
219            kept: Vec::new(),
220            rejections: Vec::new(),
221            holey: Default::default(),
222            modules: Vec::new(),
223            rope_bytes: std::cell::Cell::new(0),
224            ropes: FxHashMap::default(),
225            html_dda: false,
226            symbols: Arena::new(),
227            bigints: Arena::new(),
228            cells: Arena::new(),
229            values: ValueHeap::new(),
230            payloads: PayloadArenas::default(),
231            atoms: AtomTable::default(),
232            atom_mode: AtomMode::Pin,
233            pinned_atoms: Vec::new(),
234            held_atoms: Vec::new(),
235            code: code::DynamicUnits::default(),
236            regexps: Default::default(),
237            shape_roots: FxHashMap::default(),
238            last_root: None,
239            prototypes: FxHashMap::default(),
240            roots: Roots::default(),
241            gc: GcState::new(limits),
242            vm: VmStack::default(),
243            ics: IcStore::default(),
244            global_generation: 0,
245            holes_writable: true,
246            closure_shapes: [None; CLOSURE_SHAPES],
247            arguments_shapes: [None; 2],
248            jobs: std::collections::VecDeque::new(),
249        };
250        let cell = heap
251            .cells
252            .alloc(ValidityCell { valid: true }, &mut heap.gc.room);
253        debug_assert_eq!(cell, STATIC_CELL.0);
254        heap
255    }
256
257    // ---- roots ----
258
259    pub fn roots(&mut self) -> &mut RootStack {
260        &mut self.roots.stack
261    }
262
263    pub fn handles(&mut self) -> &mut Handles {
264        &mut self.roots.handles
265    }
266
267    pub fn handles_ref(&self) -> &Handles {
268        &self.roots.handles
269    }
270
271    /// Adds a runtime root; returns its slot.
272    pub fn add_fixed_root(&mut self, value: Value) -> usize {
273        self.roots.fixed.push(value);
274        self.roots.fixed.len() - 1
275    }
276
277    pub fn set_fixed_root(&mut self, slot: usize, value: Value) {
278        self.roots.fixed[slot] = value;
279    }
280
281    pub fn fixed_root(&self, slot: usize) -> Value {
282        self.roots.fixed[slot]
283    }
284
285    // ---- collector state ----
286
287    pub fn gc(&self) -> &GcState {
288        &self.gc
289    }
290
291    pub fn gc_mut(&mut self) -> &mut GcState {
292        &mut self.gc
293    }
294
295    /// Bytes reserved by every arena, the value heap and the tables beside
296    /// them (the ropes' halves, the atom table, the inline caches): what the
297    /// cap limits and the budget bounds.
298    pub fn bytes(&self) -> usize {
299        self.objects.bytes()
300            + self.shapes.bytes()
301            + self.strings.bytes()
302            + self.rope_bytes.get()
303            + self.symbols.bytes()
304            + self.bigints.bytes()
305            + self.cells.bytes()
306            + self.payloads.functions.bytes()
307            + self.payloads.bytecode.bytes()
308            + self.payloads.internals.bytes()
309            + self.values.bytes()
310            + self.code.live_bytes()
311            + self.regexps.bytes()
312            + self.table_bytes()
313    }
314
315    /// Bytes of the tables beside the arenas: the ropes' halves, the atom
316    /// table and the inline caches.
317    pub fn table_bytes(&self) -> usize {
318        self.ropes.capacity() * (size_of::<(u32, (StringId, StringId))>() + 1)
319            + self.atoms.bytes()
320            + self.ics.bytes()
321    }
322
323    /// Bytes held by live records and live value ranges.
324    pub fn used_bytes(&self) -> usize {
325        self.objects.used_bytes()
326            + self.shapes.used_bytes()
327            + self.strings.used_bytes()
328            + self.rope_bytes.get()
329            + self.symbols.used_bytes()
330            + self.bigints.used_bytes()
331            + self.cells.used_bytes()
332            + self.payloads.functions.used_bytes()
333            + self.payloads.bytecode.used_bytes()
334            + self.payloads.internals.used_bytes()
335            + self.values.used_slots() * 8
336            + self.code.live_bytes()
337            + self.regexps.bytes()
338    }
339
340    pub fn ics(&self) -> &IcStore {
341        &self.ics
342    }
343
344    pub fn ics_mut(&mut self) -> &mut IcStore {
345        &mut self.ics
346    }
347
348    pub fn global_generation(&self) -> u32 {
349        self.global_generation
350    }
351
352    /// A new global generation. The value an empty entry holds is skipped:
353    /// on wrapping to it every global entry is cleared, so no entry filled
354    /// 2^32 generations ago can hit.
355    pub(crate) fn bump_global_generation(&mut self) {
356        self.global_generation = self.global_generation.wrapping_add(1);
357        if self.global_generation == crate::vm::ic::EMPTY_TAG {
358            self.global_generation = 0;
359            self.ics.clear_kind(crate::vm::ic::IcKind::Global);
360        }
361    }
362
363    pub fn value_heap(&self) -> &ValueHeap {
364        &self.values
365    }
366
367    /// The regular-expression programs.
368    pub fn regexps(&self) -> &crate::vm::regexp::RegExpStore {
369        &self.regexps
370    }
371
372    pub fn atom_table(&self) -> &AtomTable {
373        &self.atoms
374    }
375
376    pub fn live_objects(&self) -> usize {
377        self.objects.live_count()
378    }
379
380    pub fn live_shapes(&self) -> usize {
381        self.shapes.live_count()
382    }
383
384    pub fn live_strings(&self) -> usize {
385        self.strings.live_count()
386    }
387
388    pub fn live_cells(&self) -> usize {
389        self.cells.live_count()
390    }
391
392    pub fn is_live(&self, value: Value) -> bool {
393        if let Some(id) = value.as_object() {
394            self.objects.is_live(id.0)
395        } else if let Some(id) = value.as_string() {
396            self.strings.is_live(id.0)
397        } else if let Some(id) = value.as_symbol() {
398            self.symbols.is_live(id.0)
399        } else if let Some(id) = value.as_bigint() {
400            self.bigints.is_live(id.0)
401        } else {
402            true
403        }
404    }
405
406    // ---- objects ----
407
408    /// A new object with room for `slot_capacity` named properties (rounded up
409    /// to a size class) before its first growth. The global object kind is
410    /// created in dictionary mode.
411    pub fn alloc_object(
412        &mut self,
413        kind: ObjectKind,
414        prototype: Option<ObjectId>,
415        slot_capacity: u32,
416    ) -> Result<ObjectId, HeapError> {
417        match kind {
418            ObjectKind::Ordinary
419            | ObjectKind::Array
420            | ObjectKind::Native
421            | ObjectKind::Arguments
422            | ObjectKind::ForInIterator => self.alloc_plain(kind, prototype, slot_capacity, 0),
423            // No program until `vm::regexp::set_program` names one.
424            ObjectKind::RegExp => {
425                self.alloc_object_with(kind, prototype, slot_capacity, 0, NONE, Need::default())
426            }
427            ObjectKind::Scope | ObjectKind::GlobalObject => {
428                self.alloc_object_with(kind, prototype, slot_capacity, 0, NONE, Need::default())
429            }
430            ObjectKind::Function => panic!("function objects are created by alloc_function"),
431            ObjectKind::Free => panic!("ObjectKind::Free is the poison value"),
432            _ => panic!("{kind:?} objects are created by alloc_internal"),
433        }
434    }
435
436    /// An object of a kind with internal slots: an [`Internal`] record holding
437    /// `slots` (rooted for the call) and `data`, with room for `slot_capacity`
438    /// named properties. `extra_flags` adds `CALLABLE` and `CONSTRUCTOR` for
439    /// the callable kinds.
440    #[allow(clippy::too_many_arguments)]
441    pub fn alloc_internal(
442        &mut self,
443        kind: ObjectKind,
444        tag: InternalTag,
445        prototype: Option<ObjectId>,
446        slot_capacity: u32,
447        slots: &[Value],
448        data: InternalData,
449        extra_flags: u8,
450    ) -> Result<ObjectId, HeapError> {
451        debug_assert!(kind.has_internal());
452        let mut keep = Vec::with_capacity(slots.len() + 1);
453        keep.push(prototype.map_or(Value::UNDEFINED, Value::object));
454        keep.extend_from_slice(slots);
455        let need = Need {
456            objects: 1,
457            shapes: 1,
458            internals: 1,
459            values: [slot_capacity, 0],
460            extra: slots.len() * 8,
461            ..Need::default()
462        };
463        self.safepoint(&need, &keep)?;
464        let shape = self.root_shape(prototype, kind);
465        let range = self.values.alloc_slots(slot_capacity, &mut self.gc.room);
466        let payload = self.payloads.internals.alloc(
467            Internal {
468                tag,
469                slots: slots.to_vec(),
470                data,
471            },
472            &mut self.gc.room,
473        );
474        let header = ObjectHeader::new(
475            kind,
476            shape,
477            range,
478            Range::EMPTY,
479            payload,
480            flags::EXTENSIBLE | extra_flags,
481        );
482        Ok(ObjectId(self.objects.alloc(header, &mut self.gc.room)))
483    }
484
485    /// The internal record of an object of a kind with internal slots.
486    #[inline]
487    pub fn internal(&self, object: ObjectId) -> &Internal {
488        let header = self.objects.get(object.0);
489        debug_assert!(header.kind.has_internal(), "{:?}", header.kind);
490        self.payloads.internals.get(header.payload)
491    }
492
493    #[inline]
494    pub fn internal_mut(&mut self, object: ObjectId) -> &mut Internal {
495        let header = self.objects.get(object.0);
496        debug_assert!(header.kind.has_internal(), "{:?}", header.kind);
497        self.payloads.internals.get_mut(header.payload)
498    }
499
500    /// The tag of a [`ObjectKind::Builtin`] object, or `None` for any other.
501    #[inline]
502    pub fn internal_tag(&self, object: ObjectId) -> InternalTag {
503        let header = self.objects.get(object.0);
504        if header.kind.has_internal() {
505            self.payloads.internals.get(header.payload).tag
506        } else {
507            InternalTag::None
508        }
509    }
510
511    #[inline]
512    pub fn internal_slot(&self, object: ObjectId, slot: usize) -> Value {
513        self.internal(object).slots[slot]
514    }
515
516    #[inline]
517    pub fn set_internal_slot(&mut self, object: ObjectId, slot: usize, value: Value) {
518        self.internal_mut(object).slots[slot] = value;
519    }
520
521    /// Appends `value` to the internal slots of `object` (a collection's
522    /// entries, a promise's reactions), charging the growth to the trigger.
523    pub fn push_internal_slot(&mut self, object: ObjectId, value: Value) -> Result<(), HeapError> {
524        let internal = self.internal(object);
525        if internal.slots.len() == internal.slots.capacity() {
526            let extra = internal.slots.capacity().max(4) * 8;
527            self.safepoint(
528                &Need {
529                    extra,
530                    ..Need::default()
531                },
532                &[Value::object(object), value],
533            )?;
534        }
535        self.internal_mut(object).slots.push(value);
536        Ok(())
537    }
538
539    /// The common path: a transition-tree root shape, no payload.
540    #[inline]
541    fn alloc_plain(
542        &mut self,
543        kind: ObjectKind,
544        prototype: Option<ObjectId>,
545        slot_capacity: u32,
546        element_capacity: u32,
547    ) -> Result<ObjectId, HeapError> {
548        let need = Need {
549            objects: 1,
550            shapes: 1,
551            values: [slot_capacity, element_capacity],
552            ..Need::default()
553        };
554        self.safepoint(&need, &[prototype.map_or(Value::UNDEFINED, Value::object)])?;
555        let shape = self.root_shape(prototype, kind);
556        let slots = self.values.alloc_slots(slot_capacity, &mut self.gc.room);
557        let elements = self.values.alloc(element_capacity, &mut self.gc.room);
558        let header = ObjectHeader::new(kind, shape, slots, elements, 0, flags::EXTENSIBLE);
559        Ok(ObjectId(self.objects.alloc(header, &mut self.gc.room)))
560    }
561
562    #[inline]
563    fn alloc_object_with(
564        &mut self,
565        kind: ObjectKind,
566        prototype: Option<ObjectId>,
567        slot_capacity: u32,
568        element_capacity: u32,
569        payload: u32,
570        extra: Need,
571    ) -> Result<ObjectId, HeapError> {
572        debug_assert!(kind != ObjectKind::Free);
573        let need = Need {
574            objects: 1,
575            shapes: 1,
576            values: [slot_capacity, element_capacity],
577            ..extra
578        };
579        let proto_value = prototype.map_or(Value::UNDEFINED, Value::object);
580        let payload_value = if kind == ObjectKind::Scope && payload != NONE {
581            Value::object(ObjectId(payload))
582        } else {
583            Value::UNDEFINED
584        };
585        self.safepoint(&need, &[proto_value, payload_value])?;
586        let mut object_flags = flags::EXTENSIBLE;
587        let shape = if kind == ObjectKind::GlobalObject {
588            object_flags |= flags::DICTIONARY;
589            self.dictionary_shape(prototype, kind, Dictionary::default())
590        } else {
591            self.root_shape(prototype, kind)
592        };
593        let slots = self.values.alloc_slots(slot_capacity, &mut self.gc.room);
594        let elements = self.values.alloc(element_capacity, &mut self.gc.room);
595        if kind == ObjectKind::Function {
596            object_flags |= flags::CALLABLE;
597        }
598        let header = ObjectHeader::new(kind, shape, slots, elements, payload, object_flags);
599        Ok(ObjectId(self.objects.alloc(header, &mut self.gc.room)))
600    }
601
602    pub fn object_kind(&self, object: ObjectId) -> ObjectKind {
603        self.objects.get(object.0).kind
604    }
605
606    pub fn header(&self, object: ObjectId) -> &ObjectHeader {
607        self.objects.get(object.0)
608    }
609
610    /// An object with room for `slot_capacity` named properties and
611    /// `element_capacity` elements, for the kinds without a side record.
612    pub fn alloc_object_with_elements(
613        &mut self,
614        kind: ObjectKind,
615        prototype: Option<ObjectId>,
616        slot_capacity: u32,
617        element_capacity: u32,
618    ) -> Result<ObjectId, HeapError> {
619        debug_assert!(matches!(
620            kind,
621            ObjectKind::Ordinary | ObjectKind::Arguments | ObjectKind::ForInIterator
622        ));
623        self.alloc_plain(kind, prototype, slot_capacity, element_capacity)
624    }
625
626    /// Sets the inline payload of an object of a kind that keeps one (a
627    /// mapped `arguments` object's scope, a `for-in` cursor, a regular
628    /// expression's program). The caller keeps anything it names alive.
629    pub(crate) fn set_payload(&mut self, object: ObjectId, payload: u32) {
630        self.objects.get_mut(object.0).payload = payload;
631    }
632
633    /// A slot of the range itself, below any shape (the raw slots of a
634    /// `for-in` iterator).
635    pub(crate) fn set_raw_slot(&mut self, object: ObjectId, slot: u32, value: Value) {
636        let slots = self.objects.get(object.0).slots();
637        self.values.set(slots, slot, value);
638    }
639
640    pub(crate) fn raw_slot(&self, object: ObjectId, slot: u32) -> Value {
641        self.values.get(self.objects.get(object.0).slots(), slot)
642    }
643
644    // ---- arrays and elements ----
645
646    /// A dense array with room for `capacity` elements; length 0.
647    pub fn alloc_array(
648        &mut self,
649        prototype: Option<ObjectId>,
650        capacity: u32,
651    ) -> Result<ObjectId, HeapError> {
652        self.alloc_object_with(
653            ObjectKind::Array,
654            prototype,
655            0,
656            capacity,
657            0,
658            Need::default(),
659        )
660    }
661
662    pub fn array_length(&self, array: ObjectId) -> u32 {
663        let header = self.objects.get(array.0);
664        debug_assert_eq!(header.kind, ObjectKind::Array);
665        header.payload
666    }
667
668    /// The element at `index`: `Value::EMPTY` for a hole or past the end.
669    #[inline]
670    pub fn get_element(&self, object: ObjectId, index: u32) -> Value {
671        let header = self.objects.get(object.0);
672        let elements = header.elements();
673        if index < elements.len && (header.kind != ObjectKind::Array || index < header.payload) {
674            self.values.get(elements, index)
675        } else {
676            Value::EMPTY
677        }
678    }
679
680    /// Stores an element, growing the dense range; indices skipped over become
681    /// holes. An array's length follows. There is no sparse fallback here: the
682    /// heap cap bounds a far index, and an object goes sparse only through
683    /// [`Heap::make_sparse`].
684    pub fn set_element(
685        &mut self,
686        object: ObjectId,
687        index: u32,
688        value: Value,
689    ) -> Result<(), HeapError> {
690        let header = *self.objects.get(object.0);
691        let mut elements = header.elements();
692        if index >= elements.len {
693            // Doubling until 1,024 elements, then the next size class (1.5×
694            // from a power of two, 4/3× from three times one).
695            let grown = if elements.len < 1024 {
696                elements.len * 2
697            } else {
698                values::next_class_len(elements.len)
699            };
700            let new_len = grown.max(index + 1).max(4);
701            self.safepoint(
702                &Need {
703                    values: [new_len, 0],
704                    ..Need::default()
705                },
706                &[Value::object(object), value],
707            )?;
708            elements = self.values.grow(elements, new_len, &mut self.gc.room);
709            self.objects.get_mut(object.0).set_elements(elements);
710        }
711        // An array that gets a hole stays a "slow" array for the value
712        // printer, as QuickJS's does once it leaves the fast form.
713        if header.kind == ObjectKind::Array
714            && (value.is_empty()
715                || index > header.payload
716                || (index > 0 && self.values.get(elements, index - 1).is_empty()))
717        {
718            self.holey.insert(object.0);
719        }
720        self.values.set(elements, index, value);
721        let header = self.objects.get_mut(object.0);
722        if header.kind == ObjectKind::Array && index >= header.payload {
723            header.payload = index + 1;
724        }
725        if header.has(flags::USED_AS_PROTOTYPE) {
726            self.holes_writable = false;
727        }
728        Ok(())
729    }
730
731    /// Moves every element of `object` into a named property with an index key
732    /// and marks it [`flags::SPARSE`], so element attributes and extensibility
733    /// follow the ordinary rules. The element range is freed.
734    pub fn make_sparse(&mut self, object: ObjectId) -> Result<(), HeapError> {
735        let header = *self.objects.get(object.0);
736        if header.has(flags::SPARSE) {
737            return Ok(());
738        }
739        let elements = header.elements();
740        let count = if header.kind == ObjectKind::Array {
741            header.payload.min(elements.len)
742        } else {
743            elements.len
744        };
745        let mut index = 0;
746        while index < count {
747            let value = self
748                .values
749                .get(self.objects.get(object.0).elements(), index);
750            if !value.is_empty() {
751                let key = PropertyKey::index(index).ok_or(HeapError::OutOfMemory)?;
752                // The element stays in the range (so rooted) until the range
753                // is freed below.
754                self.add_property(object, key, crate::shape::PropertyFlags::DEFAULT, value)?;
755            }
756            index += 1;
757        }
758        let header = self.objects.get_mut(object.0);
759        let elements = header.elements();
760        header.set_elements(Range::EMPTY);
761        header.flags |= flags::SPARSE;
762        self.values.free(elements);
763        Ok(())
764    }
765
766    /// Records that a write into an element hole may meet an inherited index
767    /// property from now on (`Heap::holes_writable`).
768    #[inline]
769    pub(crate) fn note_prototype_index(&mut self, object: ObjectId) {
770        if self.holes_writable && self.objects.get(object.0).has(flags::USED_AS_PROTOTYPE) {
771            self.holes_writable = false;
772        }
773    }
774
775    /// Whether a write into an element hole may skip the prototype chain.
776    #[inline]
777    pub fn holes_writable(&self) -> bool {
778        self.holes_writable
779    }
780
781    pub fn push_element(&mut self, array: ObjectId, value: Value) -> Result<(), HeapError> {
782        let length = self.array_length(array);
783        self.set_element(array, length, value)
784    }
785
786    /// Sets an array's length. Shrinking clears the cut elements; growing adds
787    /// holes without allocating.
788    pub fn set_array_length(&mut self, array: ObjectId, length: u32) {
789        let header = *self.objects.get(array.0);
790        debug_assert_eq!(header.kind, ObjectKind::Array);
791        let elements = header.elements();
792        let cut = length.min(elements.len)..header.payload.min(elements.len);
793        if !cut.is_empty() {
794            self.values.slice_mut(elements)[cut.start as usize..cut.end as usize]
795                .fill(Value::EMPTY);
796        }
797        self.objects.get_mut(array.0).payload = length;
798    }
799
800    // ---- scopes and functions ----
801
802    /// A scope with `slots` variables (initialized to `Value::EMPTY`, the TDZ
803    /// marker) whose parent is `parent`.
804    pub fn alloc_scope(
805        &mut self,
806        parent: Option<ScopeId>,
807        slots: u32,
808    ) -> Result<ScopeId, HeapError> {
809        let payload = parent.map_or(NONE, |scope| scope.0);
810        let object =
811            self.alloc_object_with(ObjectKind::Scope, None, slots, 0, payload, Need::default())?;
812        Ok(ScopeId(object.0))
813    }
814
815    pub fn scope_parent(&self, scope: ScopeId) -> Option<ScopeId> {
816        let header = self.objects.get(scope.0);
817        debug_assert_eq!(header.kind, ObjectKind::Scope);
818        (header.payload != NONE).then_some(ScopeId(header.payload))
819    }
820
821    #[inline]
822    fn scope_at(&self, mut scope: ScopeId, depth: u32) -> &ObjectHeader {
823        for _ in 0..depth {
824            scope = ScopeId(self.objects.get(scope.0).payload);
825        }
826        self.objects.get(scope.0)
827    }
828
829    /// `LoadScope(depth, index)`: `depth` parent hops and one load.
830    #[inline]
831    pub fn scope_get(&self, scope: ScopeId, depth: u32, index: u32) -> Value {
832        self.values.get(self.scope_at(scope, depth).slots(), index)
833    }
834
835    #[inline]
836    pub fn scope_set(&mut self, scope: ScopeId, depth: u32, index: u32, value: Value) {
837        let slots = self.scope_at(scope, depth).slots();
838        self.values.set(slots, index, value);
839    }
840
841    /// A function object capturing `scope`.
842    pub fn alloc_function(
843        &mut self,
844        prototype: Option<ObjectId>,
845        scope: Option<ScopeId>,
846    ) -> Result<ObjectId, HeapError> {
847        self.alloc_function_with(prototype, scope, 2)
848    }
849
850    /// [`Heap::alloc_function`] with room for `slot_capacity` properties
851    /// (`length`, `name` and a constructor's `prototype`).
852    pub fn alloc_function_with(
853        &mut self,
854        prototype: Option<ObjectId>,
855        scope: Option<ScopeId>,
856        slot_capacity: u32,
857    ) -> Result<ObjectId, HeapError> {
858        let scope_value = scope.map_or(Value::UNDEFINED, |scope| Value::object(scope.object()));
859        let mark = self.roots.stack.mark();
860        self.roots.stack.push(scope_value);
861        let need = Need {
862            functions: 1,
863            ..Need::default()
864        };
865        let result = self.alloc_object_with(
866            ObjectKind::Function,
867            prototype,
868            slot_capacity,
869            0,
870            NONE,
871            need,
872        );
873        self.roots.stack.truncate(mark);
874        let object = result?;
875        let record = FunctionRecord {
876            scope: scope.map_or(NONE, |scope| scope.0),
877            bytecode: NONE,
878            native: NONE,
879            home_object: NONE,
880            fields: NONE,
881        };
882        let payload = self.payloads.functions.alloc(record, &mut self.gc.room);
883        self.objects.get_mut(object.0).payload = payload;
884        Ok(object)
885    }
886
887    pub(crate) fn function_record_mut(&mut self, function: ObjectId) -> &mut FunctionRecord {
888        let header = self.objects.get(function.0);
889        debug_assert_eq!(header.kind, ObjectKind::Function);
890        self.payloads.functions.get_mut(header.payload)
891    }
892
893    /// Sets the captures of a function made without them (a module's root,
894    /// whose environment is made after it is compiled).
895    pub(crate) fn set_function_scope(&mut self, function: ObjectId, scope: ScopeId) {
896        let header = self.objects.get(function.0);
897        debug_assert_eq!(header.kind, ObjectKind::Function);
898        let payload = header.payload;
899        self.payloads.functions.get_mut(payload).scope = scope.0;
900    }
901
902    pub fn function_scope(&self, function: ObjectId) -> Option<ScopeId> {
903        let header = self.objects.get(function.0);
904        debug_assert_eq!(header.kind, ObjectKind::Function);
905        let scope = self.payloads.functions.get(header.payload).scope;
906        (scope != NONE).then_some(ScopeId(scope))
907    }
908
909    /// A function object over the root of a published code-unit tree, with one
910    /// bytecode record per unit (the constant tables and the child links).
911    /// `tree[0]` is the root; `children` index into `tree`. The constants are
912    /// rooted for the call.
913    pub fn alloc_bytecode_function(
914        &mut self,
915        prototype: Option<ObjectId>,
916        scope: Option<ScopeId>,
917        tree: &[BytecodeTemplate<'_>],
918    ) -> Result<ObjectId, HeapError> {
919        assert!(!tree.is_empty(), "a bytecode tree has a root");
920        let mark = self.roots.stack.mark();
921        for template in tree {
922            for &constant in template.constants {
923                self.roots.stack.push(constant);
924            }
925        }
926        let result = self.alloc_bytecode_function_rooted(prototype, scope, tree);
927        self.roots.stack.truncate(mark);
928        result
929    }
930
931    fn alloc_bytecode_function_rooted(
932        &mut self,
933        prototype: Option<ObjectId>,
934        scope: Option<ScopeId>,
935        tree: &[BytecodeTemplate<'_>],
936    ) -> Result<ObjectId, HeapError> {
937        let function = self.alloc_function(prototype, scope)?;
938        let count = |len: usize| u32::try_from(len).map_err(|_| HeapError::OutOfMemory);
939        let mut constant_slots = 0u32;
940        let mut child_slots = 0u32;
941        for template in tree {
942            let constants = values::class_len(count(template.constants.len())?);
943            let children = values::class_len(count(template.children.len())?);
944            constant_slots = constant_slots
945                .checked_add(constants)
946                .ok_or(HeapError::OutOfMemory)?;
947            child_slots = child_slots
948                .checked_add(children)
949                .ok_or(HeapError::OutOfMemory)?;
950        }
951        // One safepoint for the whole tree: the records are unreachable until
952        // the root is attached below, so nothing between may collect.
953        self.safepoint(
954            &Need {
955                bytecodes: tree.len(),
956                values: [constant_slots, child_slots],
957                ..Need::default()
958            },
959            &[Value::object(function)],
960        )?;
961        let mut ids = Vec::with_capacity(tree.len());
962        for template in tree {
963            let constant_count = template.constants.len() as u32;
964            let constants = self.values.alloc(constant_count, &mut self.gc.room);
965            self.values.slice_mut(constants)[..template.constants.len()]
966                .copy_from_slice(template.constants);
967            let record = FunctionBytecode {
968                unit: template.unit.0,
969                constants,
970                constant_count,
971                children: Range::EMPTY,
972                child_count: 0,
973            };
974            ids.push(self.payloads.bytecode.alloc(record, &mut self.gc.room));
975        }
976        for (template, &id) in tree.iter().zip(&ids) {
977            let child_count = template.children.len() as u32;
978            let children = self.values.alloc(child_count, &mut self.gc.room);
979            for (slot, &child) in template.children.iter().enumerate() {
980                let child = Value::int32(ids[child as usize] as i32);
981                self.values.set(children, slot as u32, child);
982            }
983            let record = self.payloads.bytecode.get_mut(id);
984            record.children = children;
985            record.child_count = child_count;
986        }
987        let payload = self.objects.get(function.0).payload;
988        self.payloads.functions.get_mut(payload).bytecode = ids[0];
989        Ok(function)
990    }
991
992    /// A closure over `bytecode`, for `MakeClosure`. The record must be
993    /// reachable through a rooted function (it is a child of the running
994    /// function's record), since a record is not a value the root stack can
995    /// hold.
996    pub fn alloc_closure(
997        &mut self,
998        prototype: Option<ObjectId>,
999        scope: Option<ScopeId>,
1000        bytecode: BytecodeId,
1001    ) -> Result<ObjectId, HeapError> {
1002        let function = self.alloc_function_with(prototype, scope, 3)?;
1003        debug_assert!(self.payloads.bytecode.is_live(bytecode.0));
1004        let payload = self.objects.get(function.0).payload;
1005        self.payloads.functions.get_mut(payload).bytecode = bytecode.0;
1006        Ok(function)
1007    }
1008
1009    /// A host function object: `native` indexes the runtime's native table.
1010    pub fn alloc_native_function(
1011        &mut self,
1012        prototype: Option<ObjectId>,
1013        native: u32,
1014    ) -> Result<ObjectId, HeapError> {
1015        self.alloc_native_closure(prototype, native, None)
1016    }
1017
1018    /// A host function object with captured state: `data` (an object whose
1019    /// internal slots the native reads, rooted by the function) is held in
1020    /// the function record's scope.
1021    pub fn alloc_native_closure(
1022        &mut self,
1023        prototype: Option<ObjectId>,
1024        native: u32,
1025        data: Option<ObjectId>,
1026    ) -> Result<ObjectId, HeapError> {
1027        let function = self.alloc_function(prototype, data.map(|data| ScopeId(data.0)))?;
1028        let payload = self.objects.get(function.0).payload;
1029        self.payloads.functions.get_mut(payload).native = native;
1030        Ok(function)
1031    }
1032
1033    /// Gives a fresh object a cached shape `shape` and its slot values: no
1034    /// transition lookups (`MakeClosure`'s and `NewArguments`' fast path).
1035    pub(crate) fn apply_closure_shape(
1036        &mut self,
1037        function: ObjectId,
1038        shape: ShapeId,
1039        values: &[Value],
1040    ) {
1041        let header = self.objects.get_mut(function.0);
1042        debug_assert_eq!(self.shapes.get(shape.0).width as usize, values.len());
1043        debug_assert!(header.slots().len as usize >= values.len());
1044        header.shape = shape;
1045        let slots = header.slots();
1046        for (slot, &value) in values.iter().enumerate() {
1047            self.values.set(slots, slot as u32, value);
1048        }
1049    }
1050
1051    /// The function record of a `Function` object.
1052    #[inline]
1053    pub fn function_record(&self, function: ObjectId) -> FunctionRecord {
1054        let header = self.objects.get(function.0);
1055        debug_assert_eq!(header.kind, ObjectKind::Function);
1056        *self.payloads.functions.get(header.payload)
1057    }
1058
1059    pub fn function_bytecode(&self, function: ObjectId) -> Option<BytecodeId> {
1060        let header = self.objects.get(function.0);
1061        debug_assert_eq!(header.kind, ObjectKind::Function);
1062        let bytecode = self.payloads.functions.get(header.payload).bytecode;
1063        (bytecode != NONE).then_some(BytecodeId(bytecode))
1064    }
1065
1066    #[inline]
1067    pub fn bytecode_unit(&self, bytecode: BytecodeId) -> CodeUnitId {
1068        CodeUnitId(self.payloads.bytecode.get(bytecode.0).unit)
1069    }
1070
1071    pub fn bytecode_constants(&self, bytecode: BytecodeId) -> &[Value] {
1072        let record = self.payloads.bytecode.get(bytecode.0);
1073        &self.values.slice(record.constants)[..record.constant_count as usize]
1074    }
1075
1076    #[inline]
1077    pub fn bytecode_constant(&self, bytecode: BytecodeId, index: u32) -> Value {
1078        let record = self.payloads.bytecode.get(bytecode.0);
1079        debug_assert!(index < record.constant_count);
1080        self.values.get(record.constants, index)
1081    }
1082
1083    /// The record of child `index` of `bytecode`'s unit.
1084    #[inline]
1085    pub fn bytecode_child(&self, bytecode: BytecodeId, index: u32) -> BytecodeId {
1086        let record = self.payloads.bytecode.get(bytecode.0);
1087        debug_assert!(index < record.child_count);
1088        let child = self.values.get(record.children, index);
1089        BytecodeId(child.as_int32().expect("child link") as u32)
1090    }
1091
1092    pub fn live_bytecodes(&self) -> usize {
1093        self.payloads.bytecode.live_count()
1094    }
1095
1096    // ---- strings, atoms, symbols, BigInts ----
1097
1098    fn alloc_string_record(&mut self, record: StringRecord) -> Result<StringId, HeapError> {
1099        let need = Need {
1100            strings: 1,
1101            extra: record.flat().map_or(0, StringRepr::content_bytes),
1102            ..Need::default()
1103        };
1104        self.safepoint(&need, &[])?;
1105        Ok(StringId(self.strings.alloc(record, &mut self.gc.room)))
1106    }
1107
1108    /// A string with a representation the caller made canonical.
1109    pub fn alloc_string_repr(&mut self, repr: StringRepr) -> Result<StringId, HeapError> {
1110        self.alloc_string_record(StringRecord::new(repr))
1111    }
1112
1113    /// Sets an object flag (`flags::ERROR`).
1114    pub(crate) fn set_object_flag(&mut self, object: ObjectId, flag: u8) {
1115        self.objects.get_mut(object.0).flags |= flag;
1116    }
1117
1118    /// Clears an object flag (`flags::EXTENSIBLE`).
1119    pub(crate) fn clear_object_flag(&mut self, object: ObjectId, flag: u8) {
1120        self.objects.get_mut(object.0).flags &= !flag;
1121    }
1122
1123    /// A string that is not an atom.
1124    pub fn alloc_string(&mut self, text: &str) -> Result<StringId, HeapError> {
1125        self.alloc_string_record(StringRecord::new(StringRepr::from_text(text)))
1126    }
1127
1128    pub fn alloc_string_units(&mut self, units: &[u16]) -> Result<StringId, HeapError> {
1129        self.alloc_string_record(StringRecord::new(StringRepr::from_units(units)))
1130    }
1131
1132    /// The record of `id`, flattened if it was a rope.
1133    #[inline]
1134    pub fn string(&self, id: StringId) -> &StringRecord {
1135        let record = self.strings.get(id.0);
1136        if record.flat().is_none() {
1137            self.flatten(id, record);
1138        }
1139        record
1140    }
1141
1142    /// The length of `id` without flattening it.
1143    #[inline]
1144    pub fn string_len(&self, id: StringId) -> usize {
1145        self.strings.get(id.0).len()
1146    }
1147
1148    /// Whether every unit of `id` fits in a byte, without flattening it.
1149    #[inline]
1150    pub fn string_is_one_byte(&self, id: StringId) -> bool {
1151        self.strings.get(id.0).is_one_byte()
1152    }
1153
1154    /// Flattens a rope in place: an explicit stack walks the tree left to
1155    /// right, so a left-deep rope of any depth flattens without recursion.
1156    #[cold]
1157    #[inline(never)]
1158    fn flatten(&self, id: StringId, record: &StringRecord) {
1159        let halves = |id: StringId| self.ropes.get(&id.0).copied();
1160        let (left, right) = halves(id).expect("a rope");
1161        let mut stack = vec![right, left];
1162        let repr = if record.is_one_byte() {
1163            let mut bytes = Vec::with_capacity(record.len());
1164            while let Some(id) = stack.pop() {
1165                let part = self.strings.get(id.0);
1166                match (part.flat(), halves(id)) {
1167                    (Some(StringRepr::OneByte(flat)), _) => bytes.extend_from_slice(flat),
1168                    (Some(StringRepr::TwoByte(_)), _) => unreachable!("a one-byte rope"),
1169                    (None, Some((left, right))) => {
1170                        stack.push(right);
1171                        stack.push(left);
1172                    }
1173                    (None, None) => unreachable!("a string with neither units nor halves"),
1174                }
1175            }
1176            StringRepr::OneByte(bytes.into())
1177        } else {
1178            let mut units = Vec::with_capacity(record.len());
1179            while let Some(id) = stack.pop() {
1180                let part = self.strings.get(id.0);
1181                match (part.flat(), halves(id)) {
1182                    (Some(flat), _) => units.extend(flat.units()),
1183                    (None, Some((left, right))) => {
1184                        stack.push(right);
1185                        stack.push(left);
1186                    }
1187                    (None, None) => unreachable!("a string with neither units nor halves"),
1188                }
1189            }
1190            StringRepr::TwoByte(units.into())
1191        };
1192        self.rope_bytes
1193            .set(self.rope_bytes.get() + repr.content_bytes());
1194        record.set_flat(repr);
1195    }
1196
1197    /// The concatenation of `left` and `right` as a rope (`len` units).
1198    pub fn alloc_rope(&mut self, left: StringId, right: StringId) -> Result<StringId, HeapError> {
1199        let len = self.string_len(left) + self.string_len(right);
1200        let one_byte = self.string_is_one_byte(left) && self.string_is_one_byte(right);
1201        let need = Need {
1202            strings: 1,
1203            ..Need::default()
1204        };
1205        self.safepoint(&need, &[Value::string(left), Value::string(right)])?;
1206        let record = StringRecord::rope(len as u32, one_byte);
1207        let id = self.strings.alloc(record, &mut self.gc.room);
1208        self.ropes.insert(id, (left, right));
1209        Ok(StringId(id))
1210    }
1211
1212    pub fn string_text(&self, id: StringId) -> String {
1213        self.string(id).repr().to_rust_string()
1214    }
1215
1216    fn intern_repr(&mut self, repr: StringRepr) -> Result<StringId, HeapError> {
1217        let hash = repr.content_hash();
1218        if let Some(id) = self.atoms.find(&self.strings, &repr, hash) {
1219            self.retain_atom(id);
1220            return Ok(id);
1221        }
1222        self.insert_atom(repr, hash)
1223    }
1224
1225    /// A new atom for `repr`, whose content hash is `hash`.
1226    fn insert_atom(&mut self, repr: StringRepr, hash: u32) -> Result<StringId, HeapError> {
1227        let array_index = repr.array_index().unwrap_or(NONE);
1228        let mut record = StringRecord::new(repr);
1229        record.is_atom = true;
1230        record.array_index = array_index;
1231        let id = self.alloc_string_record(record)?;
1232        if id.0 >= KEY_INDEX_LIMIT {
1233            self.strings.free(id.0);
1234            return Err(HeapError::OutOfMemory);
1235        }
1236        self.atoms.insert(hash, id);
1237        self.retain_atom(id);
1238        Ok(id)
1239    }
1240
1241    /// Keeps an interned atom as [`Heap::atom_mode`] says.
1242    #[inline]
1243    fn retain_atom(&mut self, id: StringId) {
1244        match self.atom_mode {
1245            AtomMode::Weak => {}
1246            AtomMode::Hold => self.hold_atom(id),
1247            AtomMode::Pin => self.pin_atom(id),
1248        }
1249    }
1250
1251    /// Keeps atom `id` until the compilation holding it releases it; an atom
1252    /// already held (or pinned) is not held twice.
1253    fn hold_atom(&mut self, id: StringId) {
1254        let record = self.strings.get_mut(id.0);
1255        if record.keep == AtomKeep::Weak {
1256            record.keep = AtomKeep::Held;
1257            self.held_atoms.push(id);
1258        }
1259    }
1260
1261    /// Keeps atom `id` for the heap's lifetime.
1262    pub fn pin_atom(&mut self, id: StringId) {
1263        let record = self.strings.get_mut(id.0);
1264        debug_assert!(record.is_atom, "pinning a string that is not an atom");
1265        if record.keep != AtomKeep::Pinned {
1266            record.keep = AtomKeep::Pinned;
1267            self.pinned_atoms.push(id);
1268        }
1269    }
1270
1271    /// Sets how [`Heap::intern`] keeps its atoms; returns the mode before.
1272    pub fn set_atom_mode(&mut self, mode: AtomMode) -> AtomMode {
1273        std::mem::replace(&mut self.atom_mode, mode)
1274    }
1275
1276    pub fn atom_mode(&self) -> AtomMode {
1277        self.atom_mode
1278    }
1279
1280    /// The atom with this content, kept for the heap's lifetime: a name the
1281    /// natives spell as a literal and hold across allocations.
1282    pub fn intern_pinned(&mut self, text: &str) -> Result<StringId, HeapError> {
1283        let mode = self.set_atom_mode(AtomMode::Pin);
1284        let result = self.intern(text);
1285        self.atom_mode = mode;
1286        result
1287    }
1288
1289    /// [`Heap::property_key`] kept for the heap's lifetime, as
1290    /// [`Heap::intern_pinned`].
1291    pub fn property_key_pinned(&mut self, text: &str) -> Result<PropertyKey, HeapError> {
1292        let mode = self.set_atom_mode(AtomMode::Pin);
1293        let result = self.property_key(text);
1294        self.atom_mode = mode;
1295        result
1296    }
1297
1298    /// The position the atoms held for a compilation start at.
1299    pub(crate) fn held_atoms_mark(&self) -> usize {
1300        self.held_atoms.len()
1301    }
1302
1303    pub(crate) fn release_held_atoms(&mut self, mark: usize) {
1304        for position in mark..self.held_atoms.len() {
1305            let id = self.held_atoms[position];
1306            let record = self.strings.get_mut(id.0);
1307            if record.keep == AtomKeep::Held {
1308                record.keep = AtomKeep::Weak;
1309            }
1310        }
1311        self.held_atoms.truncate(mark);
1312    }
1313
1314    /// The atom with this content.
1315    pub fn intern(&mut self, text: &str) -> Result<StringId, HeapError> {
1316        self.intern_repr(StringRepr::from_text(text))
1317    }
1318
1319    pub fn intern_units(&mut self, units: &[u16]) -> Result<StringId, HeapError> {
1320        self.intern_repr(StringRepr::from_units(units))
1321    }
1322
1323    /// The atom of the Latin-1 string `bytes`; it allocates only when the
1324    /// atom is new.
1325    pub fn intern_latin1(&mut self, bytes: &[u8]) -> Result<StringId, HeapError> {
1326        let hash = string::content_hash(bytes.iter().map(|&byte| u16::from(byte)));
1327        if let Some(id) = self.atoms.find_one_byte(&self.strings, bytes, hash) {
1328            self.retain_atom(id);
1329            return Ok(id);
1330        }
1331        self.insert_atom(StringRepr::OneByte(bytes.into()), hash)
1332    }
1333
1334    /// The atom with the content of `string` (itself, if it is one).
1335    pub fn intern_string(&mut self, string: StringId) -> Result<StringId, HeapError> {
1336        let record = self.string(string);
1337        if record.is_atom {
1338            self.retain_atom(string);
1339            return Ok(string);
1340        }
1341        let repr = record.repr().clone();
1342        self.intern_repr(repr)
1343    }
1344
1345    fn key_for_repr(&mut self, repr: StringRepr) -> Result<PropertyKey, HeapError> {
1346        if let Some(key) = repr.array_index().and_then(PropertyKey::index) {
1347            return Ok(key);
1348        }
1349        Ok(PropertyKey::atom(self.intern_repr(repr)?))
1350    }
1351
1352    /// The property key spelled by `text`: an immediate index for a canonical
1353    /// array index below 2^31 (which never enters the atom table), otherwise
1354    /// an atom.
1355    pub fn property_key(&mut self, text: &str) -> Result<PropertyKey, HeapError> {
1356        self.key_for_repr(StringRepr::from_text(text))
1357    }
1358
1359    pub fn property_key_units(&mut self, units: &[u16]) -> Result<PropertyKey, HeapError> {
1360        self.key_for_repr(StringRepr::from_units(units))
1361    }
1362
1363    /// [`Self::property_key`] for the Latin-1 string `bytes`.
1364    pub fn property_key_latin1(&mut self, bytes: &[u8]) -> Result<PropertyKey, HeapError> {
1365        let units = || bytes.iter().map(|&byte| u16::from(byte));
1366        if let Some(key) = string::array_index(bytes.len(), units()).and_then(PropertyKey::index) {
1367            return Ok(key);
1368        }
1369        Ok(PropertyKey::atom(self.intern_latin1(bytes)?))
1370    }
1371
1372    pub fn alloc_symbol(
1373        &mut self,
1374        description: Option<StringId>,
1375        private: bool,
1376    ) -> Result<SymbolId, HeapError> {
1377        let keep = description.map_or(Value::UNDEFINED, Value::string);
1378        self.safepoint(
1379            &Need {
1380                symbols: 1,
1381                ..Need::default()
1382            },
1383            &[keep],
1384        )?;
1385        let record = SymbolRecord {
1386            description: description.map_or(NONE, |id| id.0),
1387            private,
1388        };
1389        let id = self.symbols.alloc(record, &mut self.gc.room);
1390        if id >= KEY_INDEX_LIMIT {
1391            self.symbols.free(id);
1392            return Err(HeapError::OutOfMemory);
1393        }
1394        Ok(SymbolId(id))
1395    }
1396
1397    pub fn symbol(&self, id: SymbolId) -> &SymbolRecord {
1398        self.symbols.get(id.0)
1399    }
1400
1401    pub fn symbol_description(&self, id: SymbolId) -> Option<StringId> {
1402        let description = self.symbols.get(id.0).description;
1403        (description != NONE).then_some(StringId(description))
1404    }
1405
1406    pub fn alloc_bigint(&mut self, negative: bool, limbs: &[u64]) -> Result<BigIntId, HeapError> {
1407        let used = limbs.len() - limbs.iter().rev().take_while(|&&limb| limb == 0).count();
1408        let limbs: Box<[u64]> = limbs[..used].into();
1409        let need = Need {
1410            bigints: 1,
1411            extra: limbs.len() * 8,
1412            ..Need::default()
1413        };
1414        self.safepoint(&need, &[])?;
1415        let record = BigIntRecord {
1416            negative: negative && !limbs.is_empty(),
1417            limbs,
1418        };
1419        Ok(BigIntId(self.bigints.alloc(record, &mut self.gc.room)))
1420    }
1421
1422    pub fn bigint(&self, id: BigIntId) -> &BigIntRecord {
1423        self.bigints.get(id.0)
1424    }
1425}