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 lua_gc::{Marker, Trace};
12use crate::gc::GcRef;
13use crate::value::LuaValue;
14use crate::table::LuaTable;
15use crate::upval::{UpVal, UpValState};
16use crate::string::LuaString;
17use crate::proto::LuaProto;
18use crate::closure::{LuaClosure, LuaLClosure, LuaCClosure};
19use crate::userdata::LuaUserData;
20use crate::value::LuaThread;
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 fn trace(&self, m: &mut Marker) {
42 match self {
43 LuaValue::Nil
44 | LuaValue::Bool(_)
45 | LuaValue::Int(_)
46 | LuaValue::Float(_)
47 | LuaValue::LightUserData(_) => {}
48 LuaValue::Str(s) => s.trace(m),
49 LuaValue::Table(t) => t.trace(m),
50 LuaValue::Function(c) => c.trace(m),
51 LuaValue::UserData(u) => {
52 u.trace(m);
53 }
54 LuaValue::Thread(t) => {
55 // Mark the thread identity itself. lua-vm's GC post-mark hook
56 // uses the visited identities to trace only reachable
57 // suspended LuaState stacks.
58 t.trace(m);
59 }
60 }
61 }
62}
63
64/// LuaString — interned byte string. The `Rc<[u8]>` backing buffer is
65/// owned, not GC-managed, so this impl is intentionally empty.
66impl Trace for LuaString {
67 fn trace(&self, _m: &mut Marker) {}
68}
69
70/// UpVal — Open (refers to a thread stack slot by index) or Closed (owns a
71/// LuaValue). The Open variant carries no direct GC reference; the slot it
72/// points at is traced through the owning thread's stack walk.
73impl Trace for UpVal {
74 fn trace(&self, m: &mut Marker) {
75 let state = self.state.try_borrow();
76 if let Ok(state) = state {
77 match &*state {
78 UpValState::Open { .. } => {}
79 UpValState::Closed(v) => v.trace(m),
80 }
81 return;
82 }
83
84 // GC runs can happen while an upvalue is mutably borrowed in
85 // coroutine/close machinery. In that case a fallible borrow keeps
86 // the collector from panicking with a hard abort; open upvalues do not
87 // need tracing, and closed values may already be represented by
88 // stronger references while the mutable borrow is active.
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 fn trace(&self, m: &mut Marker) {
118 const WEAK_KEYS: u8 = 1;
119 const WEAK_VALUES: u8 = 1 << 1;
120 let mode = self.weak_mode();
121 let trace_keys = (mode & WEAK_KEYS) == 0;
122 let trace_values = (mode & WEAK_VALUES) == 0 && trace_keys;
123 self.for_each_entry(|k, v| {
124 if trace_keys { k.trace(m); }
125 if trace_values { v.trace(m); }
126 });
127 if let Some(mt) = self.metatable() {
128 mt.trace(m);
129 }
130 }
131}
132
133/// LuaProto — bytecode prototype. k (constants), p (child protos),
134/// source, upvalue names, locvar names.
135impl Trace for LuaProto {
136 fn trace(&self, m: &mut Marker) {
137 for v in self.k.iter() {
138 v.trace(m);
139 }
140 for p in self.p.iter() {
141 p.trace(m);
142 }
143 if let Some(src) = &self.source {
144 src.trace(m);
145 }
146 for uv in self.upvalues.iter() {
147 if let Some(name) = &uv.name {
148 name.trace(m);
149 }
150 }
151 for lv in self.locvars.iter() {
152 lv.varname.trace(m);
153 }
154 }
155}
156
157/// LuaLClosure — Lua closure carrying a Proto and its captured upvalues.
158impl Trace for LuaLClosure {
159 fn trace(&self, m: &mut Marker) {
160 self.proto.trace(m);
161 for uv in self.upvals.iter() {
162 uv.get().trace(m);
163 }
164 }
165}
166
167/// LuaClosure — dispatch to Lua/C variants; LightC is a bare function-ptr
168/// index with no payload.
169impl Trace for LuaClosure {
170 fn trace(&self, m: &mut Marker) {
171 match self {
172 LuaClosure::Lua(l) => l.trace(m),
173 LuaClosure::C(c) => c.trace(m),
174 LuaClosure::LightC(_) => {}
175 }
176 }
177}
178
179/// LuaCClosure — Rust-side C closure carrying captured upvalues.
180impl Trace for LuaCClosure {
181 fn trace(&self, m: &mut Marker) {
182 for v in self.upvalues.iter() {
183 v.trace(m);
184 }
185 }
186}
187
188/// LuaUserData — boxed payload + optional metatable + user values.
189impl Trace for LuaUserData {
190 fn trace(&self, m: &mut Marker) {
191 if let Some(mt) = self.metatable() {
192 mt.trace(m);
193 }
194 for v in self.uv.iter() {
195 v.trace(m);
196 }
197 }
198}
199
200/// LuaThread — value-side thread identity. Carries only a `ThreadId`
201/// (the registry key); the real per-thread `LuaState` lives in
202/// `lua-vm`'s `GlobalState::threads` map and is traced from
203/// `GlobalState::trace` as a root.
204impl Trace for LuaThread {
205 fn trace(&self, _m: &mut Marker) {}
206}
207
208// ──────────────────────────────────────────────────────────────────────────────
209// PORT STATUS
210// source: n/a (GC Trace impls scoped to lua-types public surface)
211// target_crate: lua-types
212// confidence: high
213// todos: 0
214// port_notes: 0
215// unsafe_blocks: 0
216// notes: Trace impls for GC visitor over the canonical type set. No C analogue;
217// the C GC walks struct fields directly via macros.
218// ──────────────────────────────────────────────────────────────────────────────