Skip to main content

oxide_core/heap/
gc.rs

1//! Root set, mark-sweep, trigger policy, stress mode and the heap cap.
2//!
3//! Non-moving mark-sweep. Mark bits live in each arena's side bitmap; marking
4//! uses an explicit stack, never recursion. Sweep is eager and scans every
5//! arena. Collection happens only at an allocation safepoint
6//! ([`Heap::safepoint`]) or on an explicit [`Heap::collect`].
7
8use std::mem;
9use std::time::Duration;
10
11use super::hash::FxHashMap;
12use super::object::{InternalTag, NONE, ObjectKind};
13use super::{Heap, HeapError, Need};
14use crate::shape::STATIC_CELL;
15use crate::value::{Kind, ShapeId, Value};
16use crate::vm::ic::Record;
17
18/// A position on the [`RootStack`].
19#[derive(Clone, Copy, Debug, PartialEq, Eq)]
20pub struct RootMark(usize);
21
22/// Values Rust code holds across an allocation. The only legal way to keep an
23/// index alive across a safepoint.
24#[derive(Default)]
25pub struct RootStack {
26    values: Vec<Value>,
27}
28
29impl RootStack {
30    #[inline]
31    pub fn mark(&self) -> RootMark {
32        RootMark(self.values.len())
33    }
34
35    #[inline]
36    pub fn push(&mut self, value: Value) {
37        self.values.push(value);
38    }
39
40    #[inline]
41    /// Drops the last root.
42    pub fn pop_one(&mut self) {
43        self.values.pop();
44    }
45
46    pub fn truncate(&mut self, mark: RootMark) {
47        debug_assert!(mark.0 <= self.values.len(), "root mark above the stack");
48        self.values.truncate(mark.0);
49    }
50
51    #[inline]
52    pub fn get(&self, index: usize) -> Value {
53        self.values[index]
54    }
55
56    #[inline]
57    pub fn set(&mut self, index: usize, value: Value) {
58        self.values[index] = value;
59    }
60
61    pub fn len(&self) -> usize {
62        self.values.len()
63    }
64
65    pub fn is_empty(&self) -> bool {
66        self.values.is_empty()
67    }
68
69    pub fn values(&self) -> &[Value] {
70        &self.values
71    }
72}
73
74/// An embedder-held value, released explicitly.
75#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
76pub struct Handle(u32);
77
78#[derive(Default)]
79pub struct Handles {
80    slots: Vec<Value>,
81    free: Vec<u32>,
82}
83
84impl Handles {
85    pub fn create(&mut self, value: Value) -> Handle {
86        debug_assert!(!value.is_empty(), "EMPTY marks a released handle");
87        if let Some(index) = self.free.pop() {
88            self.slots[index as usize] = value;
89            return Handle(index);
90        }
91        self.slots.push(value);
92        Handle(self.slots.len() as u32 - 1)
93    }
94
95    #[track_caller]
96    pub fn get(&self, handle: Handle) -> Value {
97        let value = self.slots[handle.0 as usize];
98        debug_assert!(!value.is_empty(), "released handle");
99        value
100    }
101
102    pub fn set(&mut self, handle: Handle, value: Value) {
103        debug_assert!(!self.slots[handle.0 as usize].is_empty(), "released handle");
104        self.slots[handle.0 as usize] = value;
105    }
106
107    pub fn release(&mut self, handle: Handle) {
108        let slot = &mut self.slots[handle.0 as usize];
109        assert!(!slot.is_empty(), "handle released twice");
110        *slot = Value::EMPTY;
111        self.free.push(handle.0);
112    }
113
114    pub fn live_count(&self) -> usize {
115        self.slots.len() - self.free.len()
116    }
117
118    pub fn values(&self) -> impl Iterator<Item = Value> + '_ {
119        self.slots.iter().copied().filter(|value| !value.is_empty())
120    }
121}
122
123/// The root set owned by the heap, so that a safepoint inside `Heap::alloc_*`
124/// sees every root without extra parameters.
125#[derive(Default)]
126pub struct Roots {
127    pub stack: RootStack,
128    pub handles: Handles,
129    /// Runtime roots: the realm's global object and intrinsics, and from later
130    /// phases the job queue and module registry.
131    pub fixed: Vec<Value>,
132}
133
134#[derive(Clone, Copy, Debug, PartialEq, Eq)]
135pub struct HeapLimits {
136    /// Reserved bytes the heap may not exceed; `usize::MAX` for no cap.
137    pub max_bytes: usize,
138}
139
140impl Default for HeapLimits {
141    fn default() -> Self {
142        HeapLimits {
143            max_bytes: usize::MAX,
144        }
145    }
146}
147
148#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
149pub struct GcStats {
150    pub collections: u64,
151    pub total_pause: Duration,
152    pub max_pause: Duration,
153    pub freed_records: u64,
154    pub freed_bytes: u64,
155    pub live_bytes: usize,
156}
157
158/// The trigger's threshold and what growth steps may still add to reserved
159/// memory before the budget is reached. Every arena and the value heap charge
160/// their growth steps here; a step past the budget zeroes the threshold, so
161/// the next safepoint collects.
162#[derive(Clone, Copy, Debug, PartialEq, Eq)]
163pub struct Room {
164    /// Bytes of growth steps left below the budget.
165    left: isize,
166    /// Nominal bytes that may be allocated before the next collection.
167    threshold: usize,
168}
169
170impl Room {
171    /// No budget and no trigger (`GcPolicy::Manual`, the arenas' tests).
172    pub const fn unbounded() -> Room {
173        Room {
174            left: isize::MAX,
175            threshold: usize::MAX,
176        }
177    }
178
179    /// `left` bytes of growth below the budget and `threshold` nominal bytes
180    /// before the next collection.
181    pub fn new(left: usize, threshold: usize) -> Room {
182        Room {
183            left: left.min(isize::MAX as usize) as isize,
184            threshold,
185        }
186    }
187
188    /// Charges a growth step of `bytes`.
189    #[inline]
190    pub fn take(&mut self, bytes: usize) {
191        self.left = self
192            .left
193            .saturating_sub(bytes.min(isize::MAX as usize) as isize);
194        if self.left < 0 {
195            self.threshold = 0;
196        }
197    }
198
199    #[inline]
200    pub fn threshold(&self) -> usize {
201        self.threshold
202    }
203}
204
205/// The growth step the budget keeps room for above reserved memory: a
206/// quarter of the heap, at least 64 KiB and at most 2 MiB.
207pub fn budget_step(reserved: usize) -> usize {
208    (reserved / 4).clamp(64 << 10, 2 << 20)
209}
210
211pub struct GcState {
212    /// Nominal bytes allocated since the last collection.
213    pub(crate) allocated: usize,
214    pub(crate) room: Room,
215    /// Reserved bytes the heap is kept at or below between collections:
216    /// `max(min_heap, (1 + growth_factor) × live)` after the last one, raised
217    /// to one growth step above reserved memory when that is more.
218    pub(crate) budget: usize,
219    pub min_heap: usize,
220    pub growth_factor: f64,
221    pub limits: HeapLimits,
222    /// No growth trigger: collections only at the cap and when the embedder
223    /// asks (`GcPolicy::Manual` of the API).
224    pub(crate) manual: bool,
225    pub stats: GcStats,
226    /// When set, every pause is appended to `pauses`.
227    pub record_pauses: bool,
228    pub pauses: Vec<Duration>,
229    mark_stack: Vec<Value>,
230    shape_stack: Vec<ShapeId>,
231    /// Internal records with weak references reached by this collection.
232    weak: Vec<u32>,
233    /// The values of weak map entries whose key is not yet marked, by key:
234    /// marking the key pushes them (the ephemeron fixpoint in one pass).
235    ephemerons: FxHashMap<Value, Vec<Value>>,
236    bytecode_stack: Vec<u32>,
237    #[cfg(feature = "gc-stress")]
238    pub stress: bool,
239    /// Collect before every N-th allocating operation.
240    #[cfg(feature = "gc-stress")]
241    pub stress_interval: u32,
242    #[cfg(feature = "gc-stress")]
243    stress_counter: u32,
244}
245
246pub const DEFAULT_MIN_HEAP: usize = 1 << 20;
247
248impl GcState {
249    pub fn new(limits: HeapLimits) -> Self {
250        GcState {
251            allocated: 0,
252            room: Room::new(DEFAULT_MIN_HEAP, DEFAULT_MIN_HEAP),
253            budget: DEFAULT_MIN_HEAP,
254            min_heap: DEFAULT_MIN_HEAP,
255            growth_factor: 2.0,
256            limits,
257            manual: false,
258            stats: GcStats::default(),
259            record_pauses: false,
260            pauses: Vec::new(),
261            mark_stack: Vec::new(),
262            weak: Vec::new(),
263            ephemerons: FxHashMap::default(),
264            shape_stack: Vec::new(),
265            bytecode_stack: Vec::new(),
266            #[cfg(feature = "gc-stress")]
267            stress: true,
268            #[cfg(feature = "gc-stress")]
269            stress_interval: 1,
270            #[cfg(feature = "gc-stress")]
271            stress_counter: 0,
272        }
273    }
274
275    /// The trigger of a heap that has not collected yet and reserves
276    /// `reserved` bytes: `min_heap` bytes of budget, `growth_factor` for the
277    /// budgets after collections.
278    pub fn configure(&mut self, min_heap: usize, growth_factor: f64, reserved: usize) {
279        self.min_heap = min_heap;
280        self.growth_factor = growth_factor;
281        self.budget = min_heap;
282        self.room = Room::new(min_heap.saturating_sub(reserved), min_heap);
283    }
284
285    /// Nominal bytes that may be allocated before the next collection.
286    pub fn threshold(&self) -> usize {
287        self.room.threshold
288    }
289
290    /// The reserved-bytes budget set by the last collection.
291    pub fn budget(&self) -> usize {
292        self.budget
293    }
294
295    pub fn is_manual(&self) -> bool {
296        self.manual
297    }
298
299    /// Whether every safepoint takes the slow path: under a cap (to check it)
300    /// or under stress.
301    #[inline]
302    fn always_slow(&self) -> bool {
303        #[cfg(feature = "gc-stress")]
304        if self.stress {
305            return true;
306        }
307        self.limits.max_bytes != usize::MAX
308    }
309
310    #[cfg(feature = "gc-stress")]
311    fn stress_tick(&mut self) -> bool {
312        if !self.stress {
313            return false;
314        }
315        self.stress_counter += 1;
316        if self.stress_counter >= self.stress_interval.max(1) {
317            self.stress_counter = 0;
318            true
319        } else {
320            false
321        }
322    }
323}
324
325impl Heap {
326    /// The allocation safepoint. Collects when the trigger fires (or always
327    /// under stress), and enforces the heap cap: an allocation that would cross
328    /// it collects first and fails with `OutOfMemory` if that does not free
329    /// enough. `keep` are values rooted for the duration of the collection.
330    #[inline]
331    pub(crate) fn safepoint(&mut self, need: &Need, keep: &[Value]) -> Result<(), HeapError> {
332        let nominal = need.nominal_bytes();
333        if self.gc.allocated + nominal < self.gc.room.threshold && !self.gc.always_slow() {
334            self.gc.allocated += nominal;
335            return Ok(());
336        }
337        self.safepoint_slow(need, keep)
338    }
339
340    #[cold]
341    #[inline(never)]
342    fn safepoint_slow(&mut self, need: &Need, keep: &[Value]) -> Result<(), HeapError> {
343        let nominal = need.nominal_bytes();
344        #[allow(unused_mut)]
345        let mut collect = self.gc.allocated + nominal >= self.gc.room.threshold;
346        #[cfg(feature = "gc-stress")]
347        {
348            collect |= self.gc.stress_tick();
349        }
350        let capped = self.gc.limits.max_bytes != usize::MAX;
351        let over = |heap: &Heap| {
352            heap.bytes() + heap.growth_bytes(need) + need.extra > heap.gc.limits.max_bytes
353        };
354        let over_cap = capped && over(self);
355        if collect || over_cap {
356            let mark = self.roots.stack.mark();
357            for &value in keep {
358                self.roots.stack.push(value);
359            }
360            self.collect();
361            self.roots.stack.truncate(mark);
362            if capped && over(self) {
363                return Err(HeapError::OutOfMemory);
364            }
365        }
366        self.gc.allocated += nominal;
367        Ok(())
368    }
369
370    /// Bytes the arenas and the value heap grow by to satisfy `need`.
371    pub(crate) fn growth_bytes(&self, need: &Need) -> usize {
372        // Two ranges are approximated as one of the combined size.
373        let values = self.values.growth_bytes(need.values[0] + need.values[1]);
374        self.objects.growth_bytes(need.objects)
375            + self.shapes.growth_bytes(need.shapes)
376            + self.strings.growth_bytes(need.strings)
377            + self.symbols.growth_bytes(need.symbols)
378            + self.bigints.growth_bytes(need.bigints)
379            + self.cells.growth_bytes(need.cells)
380            + self.payloads.functions.growth_bytes(need.functions)
381            + self.payloads.bytecode.growth_bytes(need.bytecodes)
382            + self.payloads.internals.growth_bytes(need.internals)
383            + values
384    }
385
386    /// A full collection.
387    pub fn collect(&mut self) {
388        let start = crate::clock::Stopwatch::start();
389        let before = self.used_bytes();
390        self.mark_from_roots();
391        self.process_weak();
392        self.prune_caches();
393        let freed = self.sweep();
394        let live = self.used_bytes();
395        self.set_budget(live);
396        let gc = &mut self.gc;
397        let pause = start.elapsed();
398        let stats = &mut gc.stats;
399        stats.collections += 1;
400        stats.total_pause += pause;
401        stats.max_pause = stats.max_pause.max(pause);
402        stats.freed_records += freed as u64;
403        stats.freed_bytes += before.saturating_sub(live) as u64;
404        stats.live_bytes = live;
405        if gc.record_pauses {
406            gc.pauses.push(pause);
407        }
408        #[cfg(feature = "gc-stress")]
409        self.verify();
410    }
411
412    /// Turns the growth trigger off (`manual`) or back on, with the budget
413    /// of the live bytes the last collection left.
414    pub fn set_gc_manual(&mut self, manual: bool) {
415        self.gc.manual = manual;
416        let allocated = self.gc.allocated;
417        self.set_budget(self.gc.stats.live_bytes);
418        self.gc.allocated = allocated;
419    }
420
421    /// The budget and the trigger after a collection that left `live` bytes.
422    /// The threshold is what can be allocated before the bytes in use reach
423    /// the budget; while the free space matches the requests, that is also
424    /// when reserved memory reaches it. A growth step past the budget
425    /// collects early.
426    fn set_budget(&mut self, live: usize) {
427        let reserved = self.bytes();
428        let gc = &mut self.gc;
429        gc.allocated = 0;
430        let step = budget_step(reserved);
431        let mut budget = gc
432            .min_heap
433            .max((live as f64 * (1.0 + gc.growth_factor)) as usize);
434        // The cap clamps the room for growth steps, not the threshold: a live
435        // set within a step of the cap would otherwise collect at every
436        // safepoint, where the cap check already collects when it must.
437        let threshold = budget - live.min(budget);
438        if gc.limits.max_bytes != usize::MAX {
439            budget = budget.min(gc.limits.max_bytes.saturating_sub(step));
440        }
441        // Free space of the wrong shape, or a live set that grew: the heap
442        // takes one more step before the next collection.
443        gc.budget = budget.max(reserved + step);
444        gc.room = if gc.manual {
445            Room::unbounded()
446        } else {
447            Room::new(gc.budget - reserved, threshold)
448        };
449    }
450
451    fn clear_marks(&mut self) {
452        self.objects.clear_marks();
453        self.shapes.clear_marks();
454        self.strings.clear_marks();
455        self.symbols.clear_marks();
456        self.bigints.clear_marks();
457        self.cells.clear_marks();
458        self.payloads.functions.clear_marks();
459        self.payloads.bytecode.clear_marks();
460        self.payloads.internals.clear_marks();
461        self.code.clear_marks();
462        self.regexps.clear_marks();
463    }
464
465    /// Marks everything reachable from the root set. A reference to a freed
466    /// record panics here in debug and `gc-stress` builds.
467    fn mark_from_roots(&mut self) {
468        self.clear_marks();
469        let mut stack = mem::take(&mut self.gc.mark_stack);
470        stack.extend_from_slice(self.roots.stack.values());
471        stack.extend(self.roots.handles.values());
472        stack.extend_from_slice(&self.roots.fixed);
473        for job in &self.jobs {
474            job.trace(&mut stack);
475        }
476        // Registers above `top` belong to returned frames and may name freed
477        // records; a call clears its window before it can be marked again.
478        let top = self.vm.top as usize;
479        self.vm.stale_from = self.vm.stale_from.min(self.vm.top);
480        push_heap_values(&mut stack, &self.vm.registers[..top]);
481        stack.extend(
482            self.vm
483                .frames
484                .iter()
485                .map(|frame| Value::object(frame.function)),
486        );
487        for &id in self.pinned_atoms.iter().chain(&self.held_atoms) {
488            self.strings.mark(id.0);
489        }
490        // Units published but not yet instantiated.
491        for position in 0..self.code.pinned().len() {
492            let index = self.code.pinned()[position];
493            let id = crate::bytecode::CodeUnitId(index | crate::bytecode::DYNAMIC);
494            if let Some(unit) = self.code.mark(id) {
495                stack.extend(unit.atoms.iter().map(|&atom| Value::string(atom)));
496            }
497        }
498        let mut shapes = mem::take(&mut self.gc.shape_stack);
499        shapes.extend(self.closure_shapes.iter().flatten());
500        shapes.extend(self.arguments_shapes.iter().flatten());
501        self.mark_shapes(&mut shapes, &mut stack);
502        self.gc.shape_stack = shapes;
503        self.cells.mark(STATIC_CELL.0);
504        stack.extend_from_slice(&self.kept);
505        stack.extend_from_slice(&self.rejections);
506        stack.extend_from_slice(&self.modules);
507        self.drain::<false>(&mut stack);
508        // Ephemerons: a weak map's value is live once its key is. Each weak
509        // map is scanned once: a live key's value is pushed, an unmarked
510        // key's waits in `ephemerons` until `drain` marks the key. Draining
511        // may reach further weak maps, scanned in the next round.
512        let mut scanned = 0;
513        loop {
514            let mut ephemerons = mem::take(&mut self.gc.ephemerons);
515            for position in scanned..self.gc.weak.len() {
516                let internal = self.payloads.internals.get(self.gc.weak[position]);
517                if internal.tag != InternalTag::WeakMap {
518                    continue;
519                }
520                for entry in internal.slots.chunks(2) {
521                    if entry[0].is_empty() || self.value_marked(entry[1]) {
522                        continue;
523                    }
524                    if self.value_marked(entry[0]) {
525                        stack.push(entry[1]);
526                    } else {
527                        ephemerons.entry(entry[0]).or_default().push(entry[1]);
528                    }
529                }
530            }
531            scanned = self.gc.weak.len();
532            self.gc.ephemerons = ephemerons;
533            if stack.is_empty() {
534                break;
535            }
536            self.drain::<true>(&mut stack);
537        }
538        self.gc.ephemerons.clear();
539        // Cells owned by live prototypes; the table itself is weak.
540        for (object, info) in &self.prototypes {
541            if self.objects.is_marked(object.0)
542                && let Some(cell) = info.cell
543            {
544                self.cells.mark(cell.0);
545            }
546        }
547        stack.clear();
548        self.gc.mark_stack = stack;
549    }
550
551    /// Marks everything reachable from `stack`. With `EPHEMERONS`, marking
552    /// a key with waiting weak map values pushes them; the first pass has
553    /// none waiting and skips the check.
554    fn drain<const EPHEMERONS: bool>(&mut self, stack: &mut Vec<Value>) {
555        let mut shapes = mem::take(&mut self.gc.shape_stack);
556        while let Some(value) = stack.pop() {
557            match value.kind() {
558                Kind::Object => {
559                    let index = value.as_object().unwrap().0;
560                    if !self.objects.mark(index) {
561                        continue;
562                    }
563                    if EPHEMERONS
564                        && !self.gc.ephemerons.is_empty()
565                        && let Some(values) = self.gc.ephemerons.remove(&value)
566                    {
567                        stack.extend(values);
568                    }
569                    let header = *self.objects.get(index);
570                    // Most objects share a shape already marked.
571                    if !self.shapes.is_marked(header.shape.0) {
572                        shapes.push(header.shape);
573                        self.mark_shapes(&mut shapes, stack);
574                    }
575                    push_heap_values(stack, self.values.slice(header.slots()));
576                    push_heap_values(stack, self.values.slice(header.elements()));
577                    match header.kind {
578                        ObjectKind::Scope | ObjectKind::Arguments if header.payload != NONE => {
579                            stack.push(Value::object(crate::ObjectId(header.payload)));
580                        }
581                        kind if kind.has_internal() => {
582                            if self.payloads.internals.mark(header.payload) {
583                                let internal = self.payloads.internals.get(header.payload);
584                                match internal.tag {
585                                    // Ephemerons and weak targets.
586                                    InternalTag::WeakMap
587                                    | InternalTag::WeakSet
588                                    | InternalTag::WeakRef => {
589                                        self.gc.weak.push(header.payload);
590                                    }
591                                    InternalTag::FinalizationRegistry => {
592                                        // The callback and each cell's held
593                                        // value are strong.
594                                        stack.push(internal.slots[0]);
595                                        for cell in internal.slots[1..].chunks(3) {
596                                            stack.push(cell[1]);
597                                        }
598                                        self.gc.weak.push(header.payload);
599                                    }
600                                    _ => push_heap_values(stack, &internal.slots),
601                                }
602                                // A module namespace's export keys.
603                                if let super::object::InternalData::Exports(keys) = &internal.data {
604                                    stack.extend(keys.iter().filter_map(|key| key.heap_value()));
605                                }
606                            }
607                        }
608                        ObjectKind::Function => {
609                            if self.payloads.functions.mark(header.payload) {
610                                let record = *self.payloads.functions.get(header.payload);
611                                for link in [record.scope, record.home_object, record.fields] {
612                                    if link != NONE {
613                                        stack.push(Value::object(crate::ObjectId(link)));
614                                    }
615                                }
616                                if record.bytecode != NONE {
617                                    self.mark_bytecode(record.bytecode, stack);
618                                }
619                            }
620                        }
621                        ObjectKind::RegExp if header.payload != NONE => {
622                            if let Some(record) = self.regexps.mark(header.payload) {
623                                stack.push(Value::string(record.source));
624                                stack.push(Value::string(record.flags));
625                            }
626                        }
627                        _ => {}
628                    }
629                }
630                Kind::String => {
631                    let index = value.as_string().unwrap().0;
632                    if self.strings.mark(index)
633                        && !self.ropes.is_empty()
634                        && self.strings.get(index).flat().is_none()
635                        && let Some(&(left, right)) = self.ropes.get(&index)
636                    {
637                        stack.push(Value::string(left));
638                        stack.push(Value::string(right));
639                    }
640                }
641                Kind::Symbol => {
642                    let index = value.as_symbol().unwrap().0;
643                    if self.symbols.mark(index) {
644                        if EPHEMERONS
645                            && !self.gc.ephemerons.is_empty()
646                            && let Some(values) = self.gc.ephemerons.remove(&value)
647                        {
648                            stack.extend(values);
649                        }
650                        let description = self.symbols.get(index).description;
651                        if description != NONE {
652                            self.strings.mark(description);
653                        }
654                    }
655                }
656                Kind::BigInt => {
657                    self.bigints.mark(value.as_bigint().unwrap().0);
658                }
659                _ => {}
660            }
661        }
662        self.gc.shape_stack = shapes;
663    }
664
665    /// Marks a bytecode record and the records of its children; pushes their
666    /// constants.
667    fn mark_bytecode(&mut self, root: u32, stack: &mut Vec<Value>) {
668        let mut pending = mem::take(&mut self.gc.bytecode_stack);
669        pending.push(root);
670        while let Some(index) = pending.pop() {
671            if !self.payloads.bytecode.mark(index) {
672                continue;
673            }
674            let record = *self.payloads.bytecode.get(index);
675            let unit = crate::bytecode::CodeUnitId(record.unit);
676            if unit.is_dynamic()
677                && let Some(unit) = self.code.mark(unit)
678            {
679                stack.extend(unit.atoms.iter().map(|&atom| Value::string(atom)));
680            }
681            push_heap_values(stack, self.values.slice(record.constants));
682            let children = &self.values.slice(record.children)[..record.child_count as usize];
683            pending.extend(
684                children
685                    .iter()
686                    .map(|child| child.as_int32().expect("child link") as u32),
687            );
688        }
689        self.gc.bytecode_stack = pending;
690    }
691
692    /// Marks shapes and their parent chains; pushes the keys and prototypes
693    /// they hold.
694    fn mark_shapes(&mut self, shapes: &mut Vec<ShapeId>, stack: &mut Vec<Value>) {
695        while let Some(mut id) = shapes.pop() {
696            loop {
697                if !self.shapes.mark(id.0) {
698                    break;
699                }
700                let shape = self.shapes.get(id.0);
701                if shape.count > 0
702                    && let Some(key) = shape.key.heap_value()
703                {
704                    stack.push(key);
705                }
706                if let Some(prototype) = shape.prototype {
707                    stack.push(Value::object(prototype));
708                }
709                if shape.validity.0 != NONE {
710                    self.cells.mark(shape.validity.0);
711                }
712                if let Some(dictionary) = &shape.dictionary {
713                    stack.extend(
714                        dictionary
715                            .entries()
716                            .filter_map(|entry| entry.key.heap_value()),
717                    );
718                }
719                match shape.parent {
720                    Some(parent) => id = parent,
721                    None => break,
722                }
723            }
724        }
725    }
726
727    /// The weak-table hook, run after marking: shape roots, transitions and
728    /// prototype infos drop entries whose target died.
729    /// Whether `value` survives this collection (a primitive always does).
730    fn value_marked(&self, value: Value) -> bool {
731        match value.kind() {
732            Kind::Object => self.objects.is_marked(value.as_object().unwrap().0),
733            Kind::Symbol => self.symbols.is_marked(value.as_symbol().unwrap().0),
734            Kind::String => self.strings.is_marked(value.as_string().unwrap().0),
735            Kind::BigInt => self.bigints.is_marked(value.as_bigint().unwrap().0),
736            _ => true,
737        }
738    }
739
740    /// Clears the weak references to records about to be swept: dead keys
741    /// leave weak collections, dead targets leave `WeakRef`s, and a dead
742    /// registry target queues its cleanup job.
743    fn clear_weak(&mut self) {
744        let weak = mem::take(&mut self.gc.weak);
745        for &payload in &weak {
746            let tag = self.payloads.internals.get(payload).tag;
747            match tag {
748                InternalTag::WeakMap | InternalTag::WeakSet => {
749                    let internal = self.payloads.internals.get(payload);
750                    let width = if tag == InternalTag::WeakMap { 2 } else { 1 };
751                    let dead: Vec<usize> = internal
752                        .slots
753                        .chunks(width)
754                        .enumerate()
755                        .filter(|(_, entry)| !entry[0].is_empty() && !self.value_marked(entry[0]))
756                        .map(|(index, _)| index)
757                        .collect();
758                    let internal = self.payloads.internals.get_mut(payload);
759                    for entry in dead {
760                        super::table::tombstone(internal, entry);
761                    }
762                }
763                InternalTag::WeakRef => {
764                    let target = self.payloads.internals.get(payload).slots[0];
765                    if !self.value_marked(target) {
766                        self.payloads.internals.get_mut(payload).slots[0] = Value::UNDEFINED;
767                    }
768                }
769                InternalTag::FinalizationRegistry => {
770                    let internal = self.payloads.internals.get(payload);
771                    let callback = internal.slots[0];
772                    let mut jobs = Vec::new();
773                    let mut dead_targets = Vec::new();
774                    let mut dead_tokens = Vec::new();
775                    for (index, cell) in internal.slots[1..].chunks(3).enumerate() {
776                        if cell[0].is_empty() {
777                            continue;
778                        }
779                        if !self.value_marked(cell[0]) {
780                            dead_targets.push(index);
781                            jobs.push(crate::vm::jobs::Job::Call {
782                                function: callback,
783                                this: Value::UNDEFINED,
784                                args: vec![cell[1]],
785                            });
786                        } else if !cell[2].is_empty() && !self.value_marked(cell[2]) {
787                            dead_tokens.push(index);
788                        }
789                    }
790                    let internal = self.payloads.internals.get_mut(payload);
791                    for index in dead_targets {
792                        for slot in &mut internal.slots[1 + index * 3..4 + index * 3] {
793                            *slot = Value::EMPTY;
794                        }
795                    }
796                    for index in dead_tokens {
797                        internal.slots[3 + index * 3] = Value::EMPTY;
798                    }
799                    self.jobs.extend(jobs);
800                }
801                _ => {}
802            }
803        }
804        self.gc.weak = weak;
805        self.gc.weak.clear();
806    }
807
808    fn process_weak(&mut self) {
809        self.clear_weak();
810        // A rope's halves are dropped once it dies or flattens.
811        let strings = &self.strings;
812        self.ropes
813            .retain(|&index, _| strings.is_marked(index) && strings.get(index).flat().is_none());
814        let objects = &self.objects;
815        self.holey.retain(|&index| objects.is_marked(index));
816        if let Some((error, _)) = &self.vm.unpositioned
817            && !objects.is_marked(error.0)
818        {
819            self.vm.unpositioned = None;
820        }
821        self.prototypes.retain(|object, info| {
822            if !objects.is_marked(object.0) {
823                return false;
824            }
825            info.dependents
826                .retain(|dependent| objects.is_marked(dependent.0));
827            true
828        });
829        self.last_root = None;
830        let shapes = &mut self.shapes;
831        self.shape_roots
832            .retain(|_, shape| shapes.is_marked(shape.0));
833        let live: Vec<u32> = shapes
834            .live_indices()
835            .filter(|&index| shapes.is_marked(index))
836            .collect();
837        for index in live {
838            if shapes.get(index).transitions.is_empty() {
839                continue;
840            }
841            let mut transitions = mem::take(&mut shapes.get_mut(index).transitions);
842            transitions.retain(|child| shapes.is_marked(child.0));
843            shapes.get_mut(index).transitions = transitions;
844        }
845    }
846
847    /// Clears the cache entries that name a record about to be swept, before
848    /// its index can be recycled.
849    fn prune_caches(&mut self) {
850        let (objects, shapes, cells, strings) =
851            (&self.objects, &self.shapes, &self.cells, &self.strings);
852        self.ics.prune(|record| match record {
853            Record::Shape(index) => shapes.is_marked(index),
854            Record::Object(index) => objects.is_marked(index),
855            Record::Cell(index) => cells.is_marked(index),
856            Record::Atom(index) => strings.is_marked(index),
857        });
858    }
859
860    fn sweep(&mut self) -> usize {
861        let values = &mut self.values;
862        values.begin_sweep();
863        let mut freed = self.objects.sweep(|_, header| {
864            values.release(header.slots());
865            values.release(header.elements());
866        });
867        freed += self.payloads.bytecode.sweep(|_, bytecode| {
868            values.release(bytecode.constants);
869            values.release(bytecode.children);
870        });
871        values.finish_sweep();
872        freed += self.payloads.functions.sweep(|_, _| {});
873        freed += self.payloads.internals.sweep(|_, _| {});
874        freed += self.shapes.sweep(|_, _| {});
875        let ics = &mut self.ics;
876        self.code.sweep(|unit| ics.release(unit));
877        self.regexps.sweep();
878        let (rope_bytes, atoms) = (&self.rope_bytes, &mut self.atoms);
879        freed += self.strings.sweep(|index, record| {
880            rope_bytes.set(rope_bytes.get() - record.flattened_rope_bytes());
881            if record.is_atom {
882                atoms.remove(record.repr().content_hash(), crate::value::StringId(index));
883            }
884        });
885        freed += self.symbols.sweep(|_, _| {});
886        freed += self.bigints.sweep(|_, _| {});
887        freed += self.cells.sweep(|_, _| {});
888        freed
889    }
890
891    /// Post-collection verification: every live record is reachable and every
892    /// reachable record is live; no free slot is marked and every arena's free
893    /// count agrees with its bitmap; every value-heap slot has exactly one
894    /// owner, a live range or the free space.
895    pub fn verify(&mut self) {
896        self.mark_from_roots();
897        fn check<T: super::arena::Record>(name: &str, arena: &super::arena::Arena<T>) {
898            if let Some(index) = arena.marks_equal_live() {
899                panic!(
900                    "gc verify: {name} {index} is {}",
901                    if arena.is_live(index) {
902                        "live but unreachable"
903                    } else {
904                        "reachable but freed"
905                    }
906                );
907            }
908            for index in arena.free_indices() {
909                assert!(
910                    !arena.is_marked(index),
911                    "gc verify: free {name} {index} is marked"
912                );
913            }
914            assert!(
915                arena.free_count_matches(),
916                "gc verify: {name}'s free count or cursor disagrees with its bitmap"
917            );
918        }
919        check("object", &self.objects);
920        check("shape", &self.shapes);
921        check("string", &self.strings);
922        check("symbol", &self.symbols);
923        check("bigint", &self.bigints);
924        check("cell", &self.cells);
925        check("function", &self.payloads.functions);
926        check("bytecode", &self.payloads.bytecode);
927        check("internal", &self.payloads.internals);
928        self.code.verify();
929        self.regexps.verify();
930        self.ics.verify_free();
931        for index in self.payloads.bytecode.live_indices() {
932            let unit = crate::bytecode::CodeUnitId(self.payloads.bytecode.get(index).unit);
933            assert!(
934                !unit.is_dynamic() || self.code.lookup(unit).is_some(),
935                "gc verify: bytecode {index} names a freed dynamic unit"
936            );
937        }
938
939        let mut owner = vec![false; self.values.total_slots()];
940        let mut claim = |range: super::values::Range, what: &str| {
941            for slot in range.start..range.start + range.len {
942                assert!(
943                    !owner[slot as usize],
944                    "gc verify: value slot {slot} has two owners ({what})"
945                );
946                owner[slot as usize] = true;
947            }
948        };
949        for index in self.objects.live_indices() {
950            let header = self.objects.get(index);
951            claim(header.slots(), "object slots");
952            claim(header.elements(), "object elements");
953        }
954        for index in self.payloads.bytecode.live_indices() {
955            let record = self.payloads.bytecode.get(index);
956            claim(record.constants, "bytecode constants");
957            claim(record.children, "bytecode children");
958        }
959        for block in self.values.free_blocks() {
960            claim(block, "free block");
961        }
962        for (start, end) in self.values.free_runs() {
963            claim(
964                super::values::Range {
965                    start,
966                    len: end - start,
967                },
968                "free run",
969            );
970        }
971        // After a collection's rebuild every slot is owned or free.
972        if let Some(slot) = owner.iter().position(|&owned| !owned) {
973            panic!("gc verify: value slot {slot} is neither owned nor free");
974        }
975
976        // Every filled cache entry names live records.
977        for record in self.ics.named_records() {
978            let live = match record {
979                Record::Shape(index) => self.shapes.is_live(index),
980                Record::Object(index) => self.objects.is_live(index),
981                Record::Cell(index) => self.cells.is_live(index),
982                Record::Atom(index) => self.strings.is_live(index),
983            };
984            assert!(live, "gc verify: a cache entry names freed {record:?}");
985        }
986        self.clear_marks();
987    }
988}
989
990#[inline]
991fn push_heap_values(stack: &mut Vec<Value>, values: &[Value]) {
992    stack.extend(values.iter().copied().filter(|value| value.is_heap()));
993}