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