I am working on extending the C implementation of the Lox language described in the online book Crafting Interpreters  as part of a university independent study project (I'm in my senior year of my undergrad). I am currently working on converting Lox to use register-based bytecode instead of stack-based. In that process, I am taking heavy inspiration from Lua's implementation of the same (helped by the fact that the Lox code is under the same MIT license as Lua). However, Lua's source code is much more complex, befitting its relative maturity.
Currently, I have a working register-based bytecode set in Lox, except that the GC is rather sloppy; the entire register set of a call frame remains "alive" until the call returns, leading to many objects not being collected until well after they could be. (The collector is stop-the-world; an incremental or generational GC was outside the book's scope, but is a possible project for me in the upcoming fall semester. My current GC can run anytime heap memory is (re)allocated.)
I have been tracing through the Lua VM source code and see how it tracks the top. I see a number of places where L->top is set and adjusted, and it looks like lgc.c uses that same value. However, it seems that L->top is invalidated at the top of the VM switch when assertions are turned on, so I'm not entirely sure that is the correct thing to follow.
Am I correct in thinking that setting and tracking L->top is all there is to correct GC behavior, or am I missing another important piece of the puzzle? If I am missing something, can you point me in the direction of what might that be (e.g. is the GC more limited in when it can run, or something like that)?