Skip to main content

lua_types/
trace_impls.rs

1//! Phase-D `Trace` implementations for types defined in this crate.
2//!
3//! Each impl enumerates the type's GC-bearing fields and either calls
4//! `field.trace(m)` (delegating to the field's own `Trace` impl) or
5//! `m.mark(field)` (when the field is a `Gc<T>` from `lua-gc`). During the
6//! Phase A/B/C/D-0 window `GcRef<T>` is still an `Rc<T>` newtype rather
7//! than the real `Gc<T>`, so the mark-queue path is not yet reachable —
8//! method resolution dispatches through `Deref` to each underlying type's
9//! own `trace` method.
10
11use crate::closure::{LuaCClosure, LuaClosure, LuaLClosure};
12use crate::gc::GcRef;
13use crate::proto::LuaProto;
14use crate::string::LuaString;
15use crate::table::LuaTable;
16use crate::upval::UpVal;
17use crate::userdata::LuaUserData;
18use crate::value::LuaThread;
19use crate::value::LuaValue;
20use lua_gc::{Marker, Trace};
21
22/// Forwarder for `GcRef<T>`. Now that `GcRef` wraps a real `lua_gc::Gc<T>`
23/// (D-1e), tracing must enqueue the box onto the gray queue via
24/// `Marker::mark` — that is what flips its header color from White to Gray
25/// and ultimately to Black during gray-queue drainage. The previous
26/// `try_visit` short-circuit was a Phase A-D-0 workaround for the
27/// `Rc`-backed handle (no header, no color), and produced a silent bug
28/// post-D-1e: every GC-tracked allocation stayed White and was freed in
29/// the sweep on the first `collectgarbage()`. Cycles are now handled
30/// natively by the heap's gray-queue (Color::Gray check in `mark` makes
31/// re-visits idempotent).
32impl<T: Trace + 'static> Trace for GcRef<T> {
33    fn trace(&self, m: &mut Marker) {
34        m.mark(self.0);
35    }
36}
37
38/// LuaValue — central enum. Variants Nil/Bool/Int/Float/LightUserData carry
39/// no GC; Str/Table/Function/UserData/Thread carry collectable payloads.
40impl Trace for LuaValue {
41
42    fn type_name(&self) -> &'static str {
43        std::any::type_name::<Self>()
44    }
45
46    fn trace(&self, m: &mut Marker) {
47        match self {
48            LuaValue::Nil
49            | LuaValue::Bool(_)
50            | LuaValue::Int(_)
51            | LuaValue::Float(_)
52            | LuaValue::LightUserData(_) => {}
53            LuaValue::Str(s) => s.trace(m),
54            LuaValue::Table(t) => t.trace(m),
55            LuaValue::Function(c) => c.trace(m),
56            LuaValue::UserData(u) => {
57                u.trace(m);
58            }
59            LuaValue::Thread(t) => {
60                // Mark the thread identity itself. lua-vm's GC post-mark hook
61                // uses the visited identities to trace only reachable
62                // suspended LuaState stacks.
63                t.trace(m);
64            }
65        }
66    }
67}
68
69/// LuaString — interned byte string. The `Rc<[u8]>` backing buffer is
70/// owned, not GC-managed, so this impl is intentionally empty.
71impl Trace for LuaString {
72
73    fn type_name(&self) -> &'static str {
74        std::any::type_name::<Self>()
75    }
76
77    fn trace(&self, _m: &mut Marker) {}
78}
79
80/// UpVal — Open (refers to a thread stack slot by index) or Closed (owns a
81/// LuaValue). The Open variant carries no direct GC reference; the slot it
82/// points at is traced through the owning thread's stack walk.
83impl Trace for UpVal {
84
85    fn type_name(&self) -> &'static str {
86        std::any::type_name::<Self>()
87    }
88
89    fn trace(&self, m: &mut Marker) {
90        if self.try_open_payload().is_some() {
91            return;
92        }
93        if let Some(v) = self.try_closed_value() {
94            v.trace(m);
95        }
96    }
97}
98
99/// LuaTable — array+hash entries plus optional metatable.
100///
101/// Weak-table semantics (matches `lgc.c::traversetable`):
102///   * `__mode = "v"` — strong keys, weak values. Trace keys here; value
103///     side is deferred — string values get marked in `prune_weak_dead`'s
104///     surviving-entry pass (Lua's `iscleared`), non-string dead values
105///     trigger entry removal.
106///   * `__mode = "kv"` — both sides weak. Trace NEITHER here; everything
107///     is handled by `prune_weak_dead` (matches Lua's "just add to allweak,
108///     traverse nothing" path).
109///   * `__mode = "k"` — weak keys, strong values. Trace NEITHER here. The
110///     post-mark ephemeron convergence pass walks each weak-key table's
111///     entries and marks values only for entries whose keys are
112///     independently reachable. String keys get marked in `prune_weak_dead`.
113///   * No `__mode` — trace both unconditionally.
114///
115/// Marking strings inline for weak slots (the previous behavior) would
116/// pin them alive even when their containing entry is about to be cleared
117/// because the other side died — breaking the `gc.lua` weak-string-key
118/// block, which expects unreferenced long strings to free their bytes
119/// after a single `collectgarbage()` cycle.
120impl Trace for LuaTable {
121
122    fn type_name(&self) -> &'static str {
123        std::any::type_name::<Self>()
124    }
125
126    fn trace(&self, m: &mut Marker) {
127        const WEAK_KEYS: u8 = 1;
128        const WEAK_VALUES: u8 = 1 << 1;
129        let mode = self.weak_mode();
130        let trace_keys = (mode & WEAK_KEYS) == 0;
131        let trace_values = (mode & WEAK_VALUES) == 0 && trace_keys;
132        if trace_keys && trace_values {
133            self.trace_entries_with_clearkey(|v| v.trace(m));
134        } else {
135            self.for_each_entry(|k, v| {
136                if trace_keys {
137                    k.trace(m);
138                }
139                if trace_values {
140                    v.trace(m);
141                }
142            });
143        }
144        if let Some(mt) = self.metatable() {
145            mt.trace(m);
146        }
147    }
148}
149
150/// LuaProto — bytecode prototype. k (constants), p (child protos),
151/// source, upvalue names, locvar names.
152///
153/// The `cache` field is the last closure instantiated from this proto
154/// (5.2/5.3 `LUA_COMPAT` closure caching). It is a non-owning weak edge,
155/// mirroring C's `traverseproto` (`lgc.c` 5.3.6:481-482): `if (f->cache &&
156/// iswhite(f->cache)) f->cache = NULL;`. C drops a white cache during the
157/// proto traversal so the cached closure is free to be collected; it never
158/// marks the cache. We do the same — drop the cache when its target has not
159/// been independently reached (`!is_visited`) and never `mark` it. The cache
160/// is purely an optimization, so an early drop only costs a later
161/// `OP_CLOSURE` cache miss (it rebuilds a fresh closure), never correctness.
162/// Marking it strongly (the previous behavior) over-rooted the last closure
163/// of every proto, which kept a `__gc` closure stored as a weak metatable
164/// value alive past the cycle that should have cleared it — making the
165/// `gc.lua` `__gc x weak tables` finalizer fire when it must not.
166impl Trace for LuaProto {
167
168    fn type_name(&self) -> &'static str {
169        std::any::type_name::<Self>()
170    }
171
172    fn trace(&self, m: &mut Marker) {
173        for v in self.k.iter() {
174            v.trace(m);
175        }
176        for p in self.p.iter() {
177            p.trace(m);
178        }
179        if let Some(src) = &self.source {
180            src.trace(m);
181        }
182        for uv in self.upvalues.iter() {
183            if let Some(name) = &uv.name {
184                name.trace(m);
185            }
186        }
187        for lv in self.locvars.iter() {
188            lv.varname.trace(m);
189        }
190        let drop_cache = self
191            .cache
192            .borrow()
193            .as_ref()
194            .map_or(false, |c| !m.is_visited(c.identity()));
195        if drop_cache {
196            *self.cache.borrow_mut() = None;
197        }
198    }
199}
200
201/// LuaLClosure — Lua closure carrying a Proto and its captured upvalues.
202impl Trace for LuaLClosure {
203
204    fn type_name(&self) -> &'static str {
205        std::any::type_name::<Self>()
206    }
207
208    fn trace(&self, m: &mut Marker) {
209        self.proto.trace(m);
210        for uv in self.upvals.iter() {
211            uv.get().trace(m);
212        }
213    }
214}
215
216/// LuaClosure — dispatch to Lua/C variants; LightC is a bare function-ptr
217/// index with no payload.
218impl Trace for LuaClosure {
219
220    fn type_name(&self) -> &'static str {
221        std::any::type_name::<Self>()
222    }
223
224    fn trace(&self, m: &mut Marker) {
225        match self {
226            LuaClosure::Lua(l) => l.trace(m),
227            LuaClosure::C(c) => c.trace(m),
228            LuaClosure::LightC(_) => {}
229        }
230    }
231}
232
233/// LuaCClosure — Rust-side C closure carrying captured upvalues.
234impl Trace for LuaCClosure {
235
236    fn type_name(&self) -> &'static str {
237        std::any::type_name::<Self>()
238    }
239
240    fn trace(&self, m: &mut Marker) {
241        for v in self.upvalues.borrow().iter() {
242            v.trace(m);
243        }
244    }
245}
246
247/// LuaUserData — boxed payload + optional metatable + user values.
248impl Trace for LuaUserData {
249
250    fn type_name(&self) -> &'static str {
251        std::any::type_name::<Self>()
252    }
253
254    fn trace(&self, m: &mut Marker) {
255        if let Some(mt) = self.metatable() {
256            mt.trace(m);
257        }
258        for v in self.uv.borrow().iter() {
259            v.trace(m);
260        }
261    }
262}
263
264/// LuaThread — value-side thread identity. Carries only a `ThreadId`
265/// (the registry key); the real per-thread `LuaState` lives in
266/// `lua-vm`'s `GlobalState::threads` map and is traced from
267/// `GlobalState::trace` as a root.
268impl Trace for LuaThread {
269
270    fn type_name(&self) -> &'static str {
271        std::any::type_name::<Self>()
272    }
273
274    fn trace(&self, _m: &mut Marker) {}
275}
276
277// ──────────────────────────────────────────────────────────────────────────────
278// PORT STATUS
279//   source:        n/a (GC Trace impls scoped to lua-types public surface)
280//   target_crate:  lua-types
281//   confidence:    high
282//   todos:         0
283//   port_notes:    0
284//   unsafe_blocks: 0
285//   notes:         Trace impls for GC visitor over the canonical type set. No C analogue;
286//                  the C GC walks struct fields directly via macros.
287// ──────────────────────────────────────────────────────────────────────────────