After talking with jandem on IRC, I think we need to take a different tack.
There are several straightforward fixes for the interpreter, so leave that aside
and consider only Baseline.
Stack frames may be marked as debuggees individually: even if two frames are
executing the same script, one may be a debuggee while the other is not. Among
other things, if a frame has a Debugger.Frame referring to it, it is a
Scripts may be marked as debuggees. Among other things, setting breakpoints in
a script or single-stepping in it makes it a debuggee. Any frame executing a
debuggee script is itself marked as a debuggee. But the converse is not true:
debuggee frames can run non-debuggee scripts.
Each realm has a
DebuggerObservesAllExecution flag, set if it is a debuggee
of a Debugger with an
onEnterFrame hook. All scripts in such a realm are
marked as debuggees. But the converse is not true: debuggee scripts can exist
The realm debuggee flag is the broadest category: debuggee frames and scripts
can only exist in debuggee realms. And only debuggee realms can have their
DebuggerObservesAllExecution flag set.
If a debuggee frame is executing Baseline code, that code must always be
instrumented as necessary to keep Debugger informed of the frame's behavior.
Since debuggee frames can run non-debuggee scripts, this means that even
non-debuggee scripts may have instrumentation.
To make sure the debugger doesn't affect the performance of the ~100% of cases
in which the page is not being debugged, Baseline code in non-debuggee realms
must never include debugging instrumentation.
The sole inputs to baseline code generation are frame and script debuggee
flags. The Realm's debuggee flag is not considered.
A Debugger.Frame referring to a generator or async frame must continue to
refer to any and all resumptions of that call after an await or yield. Thus,
newly pushed frames for resumptions may need to be marked as debuggees.
The bug is that newly pushed frames for resumed generator/async calls are not,
in fact, marked as debuggees.
At present, when we resume a generator or async call, neither the calling frame
nor the script being resumed are necessarily debuggees, so we have no
opportunity to insert instrumentation to check whether the resuming frame should
be a debuggee.
We could avoid affecting non-debuggee realms by instrumenting resumption only in
debuggee realms, but the realm's debuggee flag, per se, is not currently an
influence on code generation, and it would be nice to avoid new forms of
We could avoid introducing a branch by having generator objects hold a copy of
the right initial value for the BaselineFrame flags field. Resumption must
initialize this field in any case, and initializing it from a field loaded from
the generator object is not that much slower than initializing it with a
constant. Unfortunately, resumption of a debuggee frame requires recompilation
if the extant Baseline script was not compiled with instrumentation, so in the
general case a VM call is required. A branch will certainly be needed to avoid
The solution jandem and I settled on was to mark generator/async scripts as
debuggees if any Debugger.Frame refers to them, much as we do new for scripts
and onStep handlers. Marking the script as a debuggee makes Baseline compile
JSOP_AFTERYIELD as a call to js::jit::DebugAfterYield, which sets the frame's
debuggee flag and calls Debugger::onResumeFrame. That function takes care of
recompiling the script with the needed instrumentation.