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