Skip to main content

gc_lang/
trace.rs

1//! Reachability tracing: the [`Trace`] contract and the [`Tracer`] sink.
2
3extern crate alloc;
4
5use alloc::vec::Vec;
6
7use crate::Gc;
8
9/// Reports the outgoing [`Gc`] edges of a heap object so the collector can follow
10/// them.
11///
12/// A mark-and-sweep collector keeps an object alive by reaching it from a root.
13/// "Reaching" means walking the graph of handles one object holds to the next, and
14/// only the object itself knows which handles those are — they might be struct
15/// fields, vector elements, map values, or buried in an enum variant. `Trace` is
16/// how the object hands that list to the collector: [`Heap::collect`] calls
17/// [`trace`](Trace::trace) on every object it visits, and the object calls
18/// [`Tracer::mark`] once per handle it stores.
19///
20/// The contract is small but load-bearing:
21///
22/// - **Mark every owned handle.** A handle you hold but do not mark is invisible to
23///   the collector; the object it points at can be swept while you still hold a
24///   handle to it. That is not unsound — the stale handle simply resolves to `None`
25///   afterwards — but it is almost never what you want. Marking is how you say
26///   "keep this alive".
27/// - **Marking more than you own is safe.** Extra marks at worst keep an object
28///   alive one cycle longer. The collector already ignores handles that do not name
29///   a live object, so a stale handle you mark costs nothing.
30/// - **`trace` must not allocate into the heap or mutate the object.** It is a
31///   read-only enumeration; it takes `&self` for exactly that reason.
32///
33/// Leaf objects that hold no handles implement `trace` as an empty body.
34///
35/// [`Heap::collect`]: crate::Heap::collect
36///
37/// # Examples
38///
39/// A cons-cell value type for a small interpreter. Each variant reports the handles
40/// it owns and nothing else:
41///
42/// ```
43/// use gc_lang::{Gc, Trace, Tracer};
44///
45/// enum Value {
46///     Nil,
47///     Int(i64),
48///     Pair(Gc<Value>, Gc<Value>),
49///     List(Vec<Gc<Value>>),
50/// }
51///
52/// impl Trace for Value {
53///     fn trace(&self, tracer: &mut Tracer<'_>) {
54///         match self {
55///             // Leaves own no handles.
56///             Value::Nil | Value::Int(_) => {}
57///             // A pair owns its two children.
58///             Value::Pair(car, cdr) => {
59///                 tracer.mark(*car);
60///                 tracer.mark(*cdr);
61///             }
62///             // A list owns each element.
63///             Value::List(items) => {
64///                 for item in items {
65///                     tracer.mark(*item);
66///                 }
67///             }
68///         }
69///     }
70/// }
71/// ```
72pub trait Trace {
73    /// Reports each [`Gc`] handle this object owns by calling [`Tracer::mark`] on it.
74    ///
75    /// Called by the collector during the mark phase. See the [trait
76    /// documentation](Trace) for the contract this method must uphold.
77    fn trace(&self, tracer: &mut Tracer<'_>);
78}
79
80/// The sink a [`Trace`] implementation reports its outgoing edges to.
81///
82/// A `Tracer` is handed to [`Trace::trace`] during a collection. Its one job is to
83/// receive handles: each call to [`mark`](Tracer::mark) records that the object
84/// being traced points at another, so the collector can visit it in turn. It holds
85/// a borrow of the collector's work queue and nothing else, so recording an edge is
86/// a single push — no allocation on the steady-state path, since the queue is
87/// reused across collections.
88///
89/// You never construct a `Tracer`; the heap builds one and passes it in.
90pub struct Tracer<'a> {
91    /// The collector's mark-phase work queue, as `(index, generation)` pairs. The
92    /// generation travels with the index so a stale handle can be rejected at pop
93    /// time rather than silently resolving to whatever now occupies the slot.
94    worklist: &'a mut Vec<(u32, u32)>,
95}
96
97impl<'a> Tracer<'a> {
98    /// Wraps the collector's work queue. Internal: only [`Heap::collect`] builds a
99    /// tracer.
100    ///
101    /// [`Heap::collect`]: crate::Heap::collect
102    #[inline]
103    pub(crate) fn new(worklist: &'a mut Vec<(u32, u32)>) -> Self {
104        Self { worklist }
105    }
106
107    /// Records that the object being traced holds `handle`, so the collector will
108    /// visit — and thereby keep alive — the object it names.
109    ///
110    /// Call this once for every handle the object owns. Marking a handle that does
111    /// not name a live object is harmless: the collector rejects it when it reaches
112    /// the front of the queue. The generic parameter lets a value mix handles into
113    /// several heaps; each is validated against its own heap when that heap collects.
114    ///
115    /// # Examples
116    ///
117    /// ```
118    /// use gc_lang::{Gc, Trace, Tracer};
119    ///
120    /// struct Cell {
121    ///     next: Option<Gc<Cell>>,
122    /// }
123    ///
124    /// impl Trace for Cell {
125    ///     fn trace(&self, tracer: &mut Tracer<'_>) {
126    ///         if let Some(next) = self.next {
127    ///             tracer.mark(next);
128    ///         }
129    ///     }
130    /// }
131    /// ```
132    #[inline]
133    pub fn mark<T>(&mut self, handle: Gc<T>) {
134        self.worklist.push((handle.index(), handle.generation()));
135    }
136}