I tentatively believe the garbage collector is working, having hit one minor hiccup along the way.
I'm tentative about it because I haven't properly instrumented the code to make sure it's accomplishing anything. It's certainly getting called, and doing stuff, and I want to believe there's no reason it should fail, but I want certainty for this.
The minor hiccup I hit was due to the fact that, until like half an hour ago, the interpreter code didn't have a proper stack, and instead relied on Python locals to store some of the frame references. Which meant that those scopes were invisible to the marker. I put a stack on my State struct, wrote a property to expose the same value as the old "index" field, removed the methods that manipulated that field directly, and rewrote the consumers, in a way that I think makes more sense, so that's nice. All of the tests pass with the same level of coverage as before, so if I broke anything, it's really subtle.
The breakage in that last paragraph manifested as a failure in garbage collection, due to an earlier garbage collection pass that occurred inside a function call. Because the dynamically enclosing scope was only referred to in Python locals, it didn't survive collection, and after the function returned, it had an invalid scope reference. I don't want to think about what would have happened if I were trying to do this in C.
This is all very educational, but most people, myself included, probably shouldn't be hand-rolling garbage collectors for any serious purpose. I can't say anything about anybody else in particular, but this feels to me like code that should be left to a library when possible.
Anyway, I'll try to develop some kind of strategy for garbage collection that's better than "as often as I think it's possible", and tests for it, and then I want to work on tooling, like I've been talking about.
Anyway, I want to wrap up now; my head kind of hurts.