Skip to content

Instantly share code, notes, and snippets.

@jimblandy
Created October 27, 2018 22:40
Show Gist options
  • Save jimblandy/c4c97483fb64ce6a8e6c1e34b55f38a7 to your computer and use it in GitHub Desktop.
Save jimblandy/c4c97483fb64ce6a8e6c1e34b55f38a7 to your computer and use it in GitHub Desktop.
Notes on the SpiderMonkey Debugger API, its hooks, generators, and garbage collection

One principle of Debugger's design is that its behavior should not be affected by the garbage collector. Like the rest of JavaScript, the programmer should be able to assume that objects have infinite lifetimes, and that the garbage collector recycles objects only when doing so would have no visible effect on the execution of the program. (Methods like findScripts and findObjects that scan the heap are exceptions; their behavior is sensitive to the garbage collector's activity—and cause plenty of trouble because of it.)

There is also a complementary principle, which is that Debugger should not impede the garbage collector's work more than necessary. For example, if the presence of a Debugger could have no observable effect on the future of the program, the garbage collector should be able to recycle it. The same applies to subsidiary objects like Debugger.Frame: if their hooks will never fire, and their referents are gone for good, then they should be recycleable.

These rules give Debugger hooks unusual significance to the garbage collector. If a Debugger has hooks that could run in the future, they could have visible effects. It follows, then, the the garbage collector must never recycle a Debugger whose hooks might run, even if the Debugger is not reachable by JavaScript until they do.

As part of the outer loop of its iterate-to-closure algorithm, the garbage collector calls js::Debugger's static markIteratively method. This searches all realms to find debuggees, and searches their debuggers for those that have live hooks, according to the Debugger::hasAnyLiveHooks method. That method checks for hooks not only on the Debugger itself, but also for Debugger.Frames and breakpoints, which may have hooks of their own.

Retaining Debugger.Frames for generators

Generators are interesting to support in the Debugger API because they introduce stack frames whose lifetimes are not stack-like: without generators, once a frame leaves the stack, the Debugger has no reason to retain it. But a generator's stack frame may be resumed in the future, and Debugger is expected to produce the same Debugger.Frame for it if it does.

This means that there are live Debugger.Frame objects that are not present in the Debugger's usual table mapping abstract frame pointers to Debugger.Frames. Instead, there must be an additional table mapping GeneratorObjects to Debugger.Frames, which can refer to the frames even when they are not on the stack. The hasAnyLiveHooks function must scan this table for frames with live hooks, just as it does the usual frame table, and mark the Debugger if any are found.

Tests

  • SpiderMonkey must iterate correctly to find all objects reachable through Debugger weakmaps.
    • Suppose a GeneratorObject G1 is rooted. It has a D.F F1 with an onStep handler that refers to another GeneratorObject G2. That has a D.F F2 with an onStep handler. GC must not drop F2, and its handler should run.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment