Skip to main content

lua_vm/
trace_impls.rs

1//! `Trace` implementations for GC-rooted types defined in this crate. Types
2//! in `lua-types` (LuaValue, LuaString, UpVal) have their Trace impls in
3//! `lua-types/src/trace_impls.rs` because of Rust's orphan rule.
4//!
5//! Guidance for adding a new impl (Trace impls have subtle invariants, so
6//! change one type at a time):
7//!   1. Read the type definition; enumerate every field
8//!   2. For every `Gc<T>`, `GcRef<T>`, or container (Vec/Option/HashMap)
9//!      thereof, call `m.mark(field)` or `field.trace(m)` appropriately
10//!   3. Skip non-GC fields (primitives, `String`, `Vec<u8>`)
11//!   4. Skip "intentionally not traced" fields (weak refs)
12//!   5. Reference `lgc.c`'s `reallymarkobject` for the C original's approach
13
14use crate::state::{FinalizerObject, GlobalState, LuaState};
15use lua_gc::{Marker, Trace};
16
17impl Trace for FinalizerObject {
18
19    fn type_name(&self) -> &'static str {
20        std::any::type_name::<Self>()
21    }
22
23    fn trace(&self, m: &mut Marker) {
24        match self {
25            FinalizerObject::Table(t) => t.trace(m),
26            FinalizerObject::UserData(u) => u.trace(m),
27        }
28    }
29}
30
31impl Trace for LuaState {
32
33    fn type_name(&self) -> &'static str {
34        std::any::type_name::<Self>()
35    }
36
37    fn trace(&self, m: &mut Marker) {
38        // C's traversethread (lgc.c) walks [stack .. top) and relies on two
39        // companion invariants this port mirrors via `gc_trace_bound` (the
40        // savestate half — widen to ci.top only for a Lua current frame)
41        // and `clear_dead_stack_tail` (the atomic-clear half, run before
42        // every collect). Every slot below the bound is therefore
43        // valid-or-nil; the old frame-bounded range walk and the saved_pc
44        // debug-local heuristic (#140 bug B's two faces) are gone.
45        let bound = self.gc_trace_bound();
46        for slot in &self.stack[..bound] {
47            slot.val.trace(m);
48        }
49
50        for uv in self.openupval.iter() {
51            uv.trace(m);
52        }
53
54        // `global` (Rc<RefCell<GlobalState>>) is reached from the heap's root
55        // via GlobalState::trace; tracing it from each thread would re-enter
56        // the root and is explicitly excluded.
57        // `call_info` entries carry pc offsets and stack indices but no direct
58        // GcRef fields. The active closure is reached through the stack slot
59        // at `ci.func`, already covered by the stack walk.
60        // `tbclist` holds StackIdx values only; the to-be-closed objects
61        // themselves live on the stack and are traced there.
62    }
63}
64
65impl Trace for GlobalState {
66
67    fn type_name(&self) -> &'static str {
68        std::any::type_name::<Self>()
69    }
70
71    fn trace(&self, m: &mut Marker) {
72        // per-type metatables, and pending finalizers. We expand the set to
73        // include preallocated short strings (memerrmsg, tmname[]) and the
74        // open-upvalue thread list, both of which the panic-driven Phase-D
75        // mega-loop expects to see at the root.
76
77        self.l_registry.trace(m);
78
79        // Values held by Rust-side embedding handles are rooted outside the
80        // Lua registry table so handle Drop can unroot without touching the
81        // Lua stack/API. They are still ordinary GC roots during marking.
82        for value in self.external_roots.iter_values() {
83            value.trace(m);
84        }
85
86        // Cross-thread open-upvalue mirrors are live roots while a coroutine
87        // resume holds the home thread's stack behind an outer mutable borrow.
88        for value in self.cross_thread_upvals.values() {
89            value.trace(m);
90        }
91
92        // `globals` and `loaded` are kept as direct GlobalState fields rather
93        // than inside the registry table (see `init_registry`), so they must
94        // be traced explicitly as roots here.
95        self.globals.trace(m);
96        self.loaded.trace(m);
97
98        // Lua 5.1 per-thread and per-closure environments are GC roots: a
99        // coroutine's global table and a no-`_ENV`-upvalue closure's
100        // environment are reachable from the live thread / closure but stored
101        // off to the side, so they must be traced here to survive collection.
102        // Both maps are empty on 5.2–5.5, making these loops no-ops there.
103        // Dead-thread / dead-closure keys are pruned after each collection.
104        for value in self.thread_globals.values() {
105            value.trace(m);
106        }
107        for value in self.closure_envs.values() {
108            value.trace(m);
109        }
110
111        if let Some(t) = &self.mainthread {
112            t.trace(m);
113        }
114
115        self.main_thread_value.trace(m);
116
117        if self.current_thread_id != self.main_thread_id {
118            if let Some(entry) = self.threads.get(&self.current_thread_id) {
119                entry.value.trace(m);
120            }
121        }
122
123        // Registered coroutines are not roots by registration alone. The
124        // post-mark hook traces stacks only for thread handles that were
125        // reached from a real root, matching Lua's collectable coroutine
126        // semantics.
127
128        for slot in self.mt.iter() {
129            if let Some(t) = slot {
130                t.trace(m);
131            }
132        }
133
134        for s in self.tmname.iter() {
135            s.trace(m);
136        }
137
138        self.memerrmsg.trace(m);
139
140        for th in self.twups.iter() {
141            th.trace(m);
142        }
143
144        // `interned_lt` is a weak short-string cache. The collector prunes
145        // unmarked entries from the post-mark hook instead of tracing them as
146        // roots here.
147        for row in self.strcache.iter() {
148            for s in row.iter() {
149                s.trace(m);
150            }
151        }
152
153        // Pending finalizers are NOT traced here — that's what lets the mark
154        // phase distinguish "still reachable from the user program" from
155        // "only kept alive by the finalizer registry". `collect_via_heap`'s
156        // post-mark hook checks each entry against the visited set; an
157        // unvisited entry is moved to `to_be_finalized` and explicitly
158        // marked there so it survives the sweep.
159        //
160        // `to_be_finalized` IS traced as a strong root: objects in this list
161        // are awaiting their `__gc` call but are otherwise dead, and the
162        // object (plus its descendants) must survive long enough for the
163        // finalizer to run.
164        for object in self.finalizers.to_be_finalized().iter() {
165            object.trace(m);
166        }
167
168        // Trace suspended parent stacks. When a coroutine is running, any
169        // parent threads are suspended and their stacks are not reachable from
170        // `threads` (which only holds coroutines, not the main thread). Before
171        // `aux_resume` resumes a coroutine it pushes a snapshot of the parent's
172        // live stack onto `suspended_parent_stacks` so those GC-managed values
173        // remain marked during collections triggered from inside the coroutine.
174        for stack_snapshot in self.suspended_parent_stacks.iter() {
175            for v in stack_snapshot.iter() {
176                v.trace(m);
177            }
178        }
179        for upval_snapshot in self.suspended_parent_open_upvals.iter() {
180            for uv in upval_snapshot.iter() {
181                uv.trace(m);
182            }
183        }
184
185        // `fixedgc` holds objects pre-marked fixed/black at allocation
186        // (`luaC_fix`); the mark phase never re-visits them, and
187        // `dyn Collectable` does not implement `Trace` here.
188        // `allgc`, `finobj`, `gray`, `grayagain`, `tobefnz`, `weak`,
189        // `ephemeron`, `allweak` are GC bookkeeping lists owned by `heap` —
190        // they are the universe of allocated objects, not roots.
191    }
192}