Garbage collection, upside down.
Wax does not stop your code to look for dead objects. It waits for the frame to end, keeps what lived, and throws away everything else.
Garbage collection
Garbage collectors... yeah. Love them or hate them, everyone has an opinion. They bring a bunch of goodness you'd really rather have, but their timing and cost are usually terrible. Your program is halfway through doing something useful, the collector decides it needs the memory back, and everything stops while it goes looking for garbage. You usually can't control much beyond the basics, which makes something you should be happy about, automatic memory management, feel like your worst enemy instead of your best friend.
Wax flips the script on this a bit by taking an unconventional approach to reclaiming memory while your code is running: it just doesn't.
The trick is that Wax always divides units of work into host defined "frames". The host begins a frame, calls into Wax as many times as it needs, and then ends the frame. Wax waits until all of that is over before it goes looking for garbage.
This turns out to be a pretty neat advantage. At that point we know for certain that nothing is on a Wax stack, no other threads are running Wax code, and every object worth keeping must be reachable from persistent program state. There are no stacks to unwind, no stack references to keep as roots, and no need to make collection incremental or concurrent just to avoid stopping user code at an unfortunate moment.
While the frame is running, new objects go into a nursery. Allocation there is just a bump of a pointer. Once the frame ends, Wax follows references from persistent state into the nursery and moves that surviving object graph into persistent memory. Then it resets the nursery in one shot.
That is the inversion. Instead of searching for dead objects while your code is running, Wax waits for the frame to end and promotes the objects that are still alive. Everything else was garbage by default.
Nobody can make garbage collection totally free, but Wax's constraints make it pretty fast and put it in a perfectly predictable place. The one quirk is that temporary memory cannot be reclaimed until the frame ends. If one frame allocates 100 MB, Wax needs room for all 100 MB even if most of it became garbage almost immediately.
What happens after the frame
Promoting an object is still pretty fast, but it is not free. The object has to be copied into persistent memory, and anything it points at has to come along with it. The more state a frame keeps, the more work Wax has to do before the next frame begins.
Once an object is persistent, it might live for one more frame or another million. Wax needs to know when the last reference to it disappears, but it does not update reference counts every time a reference is assigned. The compiler marks which persistent objects changed during the frame, then the runtime reconciles their old and new references in one batch after the frame is over. Nothing is ever freed in the middle of a frame, and the entire cost of tracking old versus new references during the frame (ref counting lite) is setting a single byte per reference assignment.
When an object's reference count reaches zero, Wax can reclaim it and follow the same process through anything it was keeping alive. That takes care of ordinary garbage.
Cycles are the annoying exception. Two dead objects can still point at each other, which keeps both reference counts above zero. Wax handles those with a separate incremental cycle collector. The compiler looks at the relationships between types ahead of time and proves which ones cannot possibly participate in a cycle. Objects of those types never become cycle candidates, so the collector does not waste time walking them.
Cycle collection does not have to happen after every frame. An unreachable cycle may sit in memory until a later frame triggers it. The important part is that the work still happens at the boundary, never in the middle of Wax code.
So the real costs are pretty easy to reason about: how much temporary memory one frame allocates, how many objects survive it, how many persistent references changed, and whether that frame also runs the cycle collector. There is machinery here, but it is not a mysterious pause that can land wherever it wants.