Bug 1147403 part 5 - Add Debugger::onIonCompilation hook. r=shu

This commit is contained in:
Nicolas B. Pierron 2015-05-28 19:26:56 +02:00
parent 560346fb43
commit 125c415c24
10 changed files with 534 additions and 92 deletions

View File

@ -225,6 +225,45 @@ compartment.
thereby escaping the capability-based limits. For this reason,
`onNewGlobalObject` is only available to privileged code.
<code>onIonCompilation(<i>graph</i>)</code>
: A new IonMonkey compilation result is attached to a script instance of
the Debuggee, the <i>graph</i> contains the internal intermediate
representations of the compiler.
The value <i>graph</i> is an object composed of the following properties:
`json`
: String containing a JSON of the intermediate representation used by
the compiler. This JSON string content is composed of 2 intermediate
representation of the graph, a `mir` (Middle-level IR), and a
`lir` (Low-level IR).
Both have a property `blocks`, which is an array of basic
blocks in [SSA form][ssa-form] which are used to construct the
control flow graph. All elements of these arrays are objects which
have a `number`, and an `instructions` properties.
The MIR blocks have additional properties such as the
`predecessors` and `successors` of each block, which can
be used to reconstruct the control flow graph, with the
`number` properties of the blocks.
The `instructions` properties are array of objects which have
an `id` and an `opcode`. The `id` corresponds to the
[SSA form][ssa-form] identifier (number) of each instruction, and the
`opcode` is a string which represents the instruction.
This JSON string contains even more detailed internal information
which remains undocummented, as it is potentially subject to
frequent modifications.
`scripts`
: Array of [`Debugger.Script`][script] instances. For a block at
`mir.blocks[i]` or `lir.blocks[i]` in the JSON, `scripts[i]` is the
[`Debugger.Script`][script] containing that block's code.
This method's return value is ignored.
## Function Properties of the Debugger Prototype Object

View File

@ -64,3 +64,4 @@ resource 'img-alloc-plot' alloc-plot-console.png $RBASE/8461
absolute-label 'protocol' https://wiki.mozilla.org/Remote_Debugging_Protocol "Remote Debugging Protocol"
absolute-label 'saved-frame' https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/SavedFrame "SavedFrame"
absolute-label 'bernoulli-trial' https://en.wikipedia.org/wiki/Bernoulli_trial "Bernoulli Trial"
absolute-label 'ssa-form' https://en.wikipedia.org/wiki/Static_single_assignment_form "SSA form"

View File

@ -0,0 +1,118 @@
function test() {
// Force Ion compilation with OSR.
for (var res = false; !res; res = inIon()) {};
if (typeof res == "string")
throw "Skipping test: Ion compilation is disabled/prevented.";
};
// Skip this test if we cannot reliably compile with Ion.
try {
test();
} catch (x) {
if (typeof x == "string")
quit();
}
// Functions used to assert the representation of the graph which is exported in
// the JSON string argument.
function assertInstruction(ins) {
assertEq(typeof ins.id, "number");
assertEq(ins.id | 0, ins.id);
assertEq(typeof ins.opcode, "string");
}
function assertBlock(block) {
assertEq(typeof block, "object");
assertEq(typeof block.number, "number");
assertEq(block.number | 0, block.number);
assertEq(typeof block.instructions, "object");
for (var ins of block.instructions)
assertInstruction(ins);
}
function assertGraph(graph, scripts) {
assertEq(typeof graph, "object");
assertEq(typeof graph.blocks, "object");
assertEq(graph.blocks.length, scripts.length);
for (var block of graph.blocks)
assertBlock(block);
}
function assertJSON(str, scripts) {
assertEq(typeof str, "string");
var json = JSON.parse(str);
assertGraph(json.mir, scripts);
assertGraph(json.lir, scripts);
}
function assertOnIonCompilationArgument(obj) {
assertEq(typeof obj, "object");
assertEq(typeof obj.scripts, "object");
assertJSON(obj.json, obj.scripts);
}
// Attach the current global to a debugger.
var hits = 0;
var g = newGlobal();
g.parent = this;
g.eval(`
var dbg = new Debugger();
var parentw = dbg.addDebuggee(parent);
var testw = parentw.makeDebuggeeValue(parent.test);
var scriptw = testw.script;
`);
// Wrap the testing function.
function check() {
// print('reset compilation counter.');
with ({}) { // Prevent Ion compilation.
gc(); // Flush previous compilation.
hits = 0; // Synchronized hit counts.
test(); // Wait until the next Ion compilation.
}
}
// With the compilation graph inhibited, we should have no output.
g.eval(`
dbg.onIonCompilation = function (graph) {
// print('Compiled ' + graph.scripts[0].displayName + ':' + graph.scripts[0].startLine);
if (graph.scripts[0] !== scriptw)
return;
parent.assertOnIonCompilationArgument(graph);
parent.hits++;
};
`);
check();
// '>= 1' is needed because --ion-eager is too eager.
assertEq(hits >= 1, true);
// Try re-entering the same compartment as the compiled script.
g.dbg.onIonCompilation = function (graph) {
// print('Compiled ' + graph.scripts[0].displayName + ':' + graph.scripts[0].startLine);
if (graph.scripts[0] !== g.scriptw)
return;
assertOnIonCompilationArgument(graph);
hits++;
};
check();
assertEq(hits >= 1, true);
// Disable the debugger, and redo the last 2 tests.
g.eval(`
dbg.enabled = false;
dbg.onIonCompilation = function (graph) {
parent.hits++;
};
`);
check();
assertEq(hits, 0);
g.dbg.enabled = false;
g.dbg.onIonCompilation = function (graph) {
hits++;
};
check();
assertEq(hits, 0);

View File

@ -41,6 +41,7 @@
#include "jit/Sink.h"
#include "jit/StupidAllocator.h"
#include "jit/ValueNumbering.h"
#include "vm/Debugger.h"
#include "vm/HelperThreads.h"
#include "vm/TraceLogging.h"
@ -49,6 +50,7 @@
#include "jsscriptinlines.h"
#include "jit/JitFrames-inl.h"
#include "vm/Debugger-inl.h"
#include "vm/ScopeObject-inl.h"
using namespace js;
@ -396,6 +398,67 @@ JitCompartment::ensureIonStubsExist(JSContext* cx)
return true;
}
struct OnIonCompilationInfo {
size_t numBlocks;
size_t scriptIndex;
LSprinter graph;
explicit OnIonCompilationInfo(LifoAlloc* alloc)
: numBlocks(0),
scriptIndex(0),
graph(alloc)
{ }
bool filled() const {
return numBlocks != 0;
}
};
typedef Vector<OnIonCompilationInfo> OnIonCompilationVector;
// This function initializes the values which are given to the Debugger
// onIonCompilation hook, if the compilation was successful, and if Ion
// compilations of this compartment are watched by any debugger.
//
// This function must be called in the same AutoEnterAnalysis section as the
// CodeGenerator::link. Failing to do so might leave room to interleave other
// allocations which can invalidate any JSObject / JSFunction referenced by the
// MIRGraph.
//
// This function ignores any allocation failure and returns whether the
// Debugger::onIonCompilation should be called.
static inline void
PrepareForDebuggerOnIonCompilationHook(JSContext* cx, jit::MIRGraph& graph,
AutoScriptVector* scripts, OnIonCompilationInfo* info)
{
info->numBlocks = 0;
if (!Debugger::observesIonCompilation(cx))
return;
// fireOnIonCompilation failures are ignored, do the same here.
info->scriptIndex = scripts->length();
if (!scripts->reserve(graph.numBlocks() + scripts->length())) {
cx->clearPendingException();
return;
}
// Collect the list of scripts which are inlined in the MIRGraph.
info->numBlocks = graph.numBlocks();
for (jit::MBasicBlockIterator block(graph.begin()); block != graph.end(); block++)
scripts->infallibleAppend(block->info().script());
// Spew the JSON graph made for the Debugger at the end of the LifoAlloc
// used by the compiler. This would not prevent unexpected GC from the
// compartment of the Debuggee, but do them as part of the compartment of
// the Debugger when the content is copied over to a JSString.
jit::JSONSpewer spewer(info->graph);
spewer.spewDebuggerGraph(&graph);
if (info->graph.hadOutOfMemory()) {
scripts->resize(info->scriptIndex);
info->numBlocks = 0;
}
}
void
jit::FinishOffThreadBuilder(JSContext* cx, IonBuilder* builder)
{
@ -461,6 +524,41 @@ class AutoLazyLinkExitFrame
}
};
static bool
LinkCodeGen(JSContext* cx, IonBuilder* builder, CodeGenerator *codegen,
AutoScriptVector* scripts, OnIonCompilationInfo* info)
{
RootedScript script(cx, builder->script());
TraceLoggerThread* logger = TraceLoggerForMainThread(cx->runtime());
TraceLoggerEvent event(logger, TraceLogger_AnnotateScripts, script);
AutoTraceLog logScript(logger, event);
AutoTraceLog logLink(logger, TraceLogger_IonLinking);
if (!codegen->link(cx, builder->constraints()))
return false;
PrepareForDebuggerOnIonCompilationHook(cx, builder->graph(), scripts, info);
return true;
}
static bool
LinkBackgroundCodeGen(JSContext* cx, IonBuilder* builder,
AutoScriptVector* scripts, OnIonCompilationInfo* info)
{
CodeGenerator* codegen = builder->backgroundCodegen();
if (!codegen)
return false;
JitContext jctx(cx, &builder->alloc());
// Root the assembler until the builder is finished below. As it was
// constructed off thread, the assembler has not been rooted previously,
// though any GC activity would discard the builder.
codegen->masm.constructRoot(cx);
return LinkCodeGen(cx, builder, codegen, scripts, info);
}
uint8_t*
jit::LazyLinkTopActivation(JSContext* cx)
{
@ -470,32 +568,23 @@ jit::LazyLinkTopActivation(JSContext* cx)
// First frame should be an exit frame.
JitFrameIterator it(iter);
LazyLinkExitFrameLayout* ll = it.exitFrame()->as<LazyLinkExitFrameLayout>();
JSScript* calleeScript = ScriptFromCalleeToken(ll->jsFrame()->calleeToken());
RootedScript calleeScript(cx, ScriptFromCalleeToken(ll->jsFrame()->calleeToken()));
// Get the pending builder from the Ion frame.
IonBuilder* builder = calleeScript->ionScript()->pendingBuilder();
calleeScript->setPendingIonBuilder(cx, nullptr);
AutoEnterAnalysis enterTypes(cx);
RootedScript script(cx, builder->script());
// See PrepareForDebuggerOnIonCompilationHook
AutoScriptVector debugScripts(cx);
OnIonCompilationInfo info(builder->alloc().lifoAlloc());
// Remove from pending.
builder->remove();
if (CodeGenerator* codegen = builder->backgroundCodegen()) {
js::TraceLoggerThread* logger = TraceLoggerForMainThread(cx->runtime());
TraceLoggerEvent event(logger, TraceLogger_AnnotateScripts, script);
AutoTraceLog logScript(logger, event);
AutoTraceLog logLink(logger, TraceLogger_IonLinking);
JitContext jctx(cx, &builder->alloc());
// Root the assembler until the builder is finished below. As it
// was constructed off thread, the assembler has not been rooted
// previously, though any GC activity would discard the builder.
codegen->masm.constructRoot(cx);
if (!codegen->link(cx, builder->constraints())) {
{
AutoEnterAnalysis enterTypes(cx);
if (!LinkBackgroundCodeGen(cx, builder, &debugScripts, &info)) {
// Silently ignore OOM during code generation. The assembly code
// doesn't has code to handle it after linking happened. So it's
// not OK to throw a catchable exception from there.
@ -503,12 +592,15 @@ jit::LazyLinkTopActivation(JSContext* cx)
}
}
if (info.filled())
Debugger::onIonCompilation(cx, debugScripts, info.graph);
FinishOffThreadBuilder(cx, builder);
MOZ_ASSERT(script->hasBaselineScript());
MOZ_ASSERT(script->baselineOrIonRawPointer());
MOZ_ASSERT(calleeScript->hasBaselineScript());
MOZ_ASSERT(calleeScript->baselineOrIonRawPointer());
return script->baselineOrIonRawPointer();
return calleeScript->baselineOrIonRawPointer();
}
/* static */ void
@ -1629,6 +1721,40 @@ CompileBackEnd(MIRGenerator* mir)
return GenerateCode(mir, lir);
}
// Find a finished builder for the compartment.
static IonBuilder*
GetFinishedBuilder(JSContext* cx, GlobalHelperThreadState::IonBuilderVector& finished)
{
for (size_t i = 0; i < finished.length(); i++) {
IonBuilder* testBuilder = finished[i];
if (testBuilder->compartment == CompileCompartment::get(cx->compartment())) {
HelperThreadState().remove(finished, &i);
return testBuilder;
}
}
return nullptr;
}
static bool
IsBuilderScriptOnStack(JSContext* cx, IonBuilder* builder)
{
for (JitActivationIterator iter(cx->runtime()); !iter.done(); ++iter) {
for (JitFrameIterator it(iter); !it.done(); ++it) {
if (!it.isIonJS())
continue;
if (it.checkInvalidation())
continue;
JSScript* script = it.script();
if (builder->script() == script)
return true;
}
}
return false;
}
void
AttachFinishedCompilations(JSContext* cx)
{
@ -1636,87 +1762,79 @@ AttachFinishedCompilations(JSContext* cx)
if (!ion)
return;
AutoEnterAnalysis enterTypes(cx);
AutoLockHelperThreadState lock;
LifoAlloc* debuggerAlloc = cx->new_<LifoAlloc>(TempAllocator::PreferredLifoChunkSize);
if (!debuggerAlloc) {
// Silently ignore OOM during code generation. The caller is
// InvokeInterruptCallback, which always runs at a nondeterministic
// time. It's not OK to throw a catchable exception from there.
cx->clearPendingException();
return;
}
GlobalHelperThreadState::IonBuilderVector& finished = HelperThreadState().ionFinishedList();
// See PrepareForDebuggerOnIonCompilationHook
AutoScriptVector debugScripts(cx);
OnIonCompilationVector onIonCompilationVector(cx);
TraceLoggerThread* logger = TraceLoggerForMainThread(cx->runtime());
{
AutoEnterAnalysis enterTypes(cx);
AutoLockHelperThreadState lock;
// Incorporate any off thread compilations for the compartment which have
// finished, failed or have been cancelled.
while (true) {
IonBuilder* builder = nullptr;
GlobalHelperThreadState::IonBuilderVector& finished = HelperThreadState().ionFinishedList();
// Find a finished builder for the compartment.
for (size_t i = 0; i < finished.length(); i++) {
IonBuilder* testBuilder = finished[i];
if (testBuilder->compartment == CompileCompartment::get(cx->compartment())) {
builder = testBuilder;
HelperThreadState().remove(finished, &i);
// Incorporate any off thread compilations for the compartment which have
// finished, failed or have been cancelled.
while (true) {
// Find a finished builder for the compartment.
IonBuilder* builder = GetFinishedBuilder(cx, finished);
if (!builder)
break;
}
}
if (!builder)
break;
// Try to defer linking if the script is on the stack, to postpone
// invalidating them.
if (builder->script()->hasIonScript()) {
bool onStack = false;
for (JitActivationIterator iter(cx->runtime()); !iter.done(); ++iter) {
for (JitFrameIterator it(iter); !it.done(); ++it) {
if (!it.isIonJS())
continue;
if (it.checkInvalidation())
continue;
JSScript* script = it.script();
if (builder->script() == script) {
onStack = true;
break;
}
}
if (onStack)
break;
}
if (onStack) {
// Try to defer linking if the script is on the stack, to postpone
// invalidating them.
if (builder->script()->hasIonScript() && IsBuilderScriptOnStack(cx, builder)) {
builder->script()->setPendingIonBuilder(cx, builder);
HelperThreadState().ionLazyLinkList().insertFront(builder);
continue;
}
}
if (CodeGenerator* codegen = builder->backgroundCodegen()) {
RootedScript script(cx, builder->script());
JitContext jctx(cx, &builder->alloc());
TraceLoggerEvent event(logger, TraceLogger_AnnotateScripts, script);
AutoTraceLog logScript(logger, event);
AutoTraceLog logLink(logger, TraceLogger_IonLinking);
AutoUnlockHelperThreadState unlock;
// Root the assembler until the builder is finished below. As it
// was constructed off thread, the assembler has not been rooted
// previously, though any GC activity would discard the builder.
codegen->masm.constructRoot(cx);
bool success;
{
AutoUnlockHelperThreadState unlock;
success = codegen->link(cx, builder->constraints());
}
if (!success) {
OnIonCompilationInfo info(debuggerAlloc);
if (!LinkBackgroundCodeGen(cx, builder, &debugScripts, &info)) {
// Silently ignore OOM during code generation. The caller is
// InvokeInterruptCallback, which always runs at a
// nondeterministic time. It's not OK to throw a catchable
// exception from there.
cx->clearPendingException();
}
if (info.filled()) {
if (!onIonCompilationVector.append(info))
cx->clearPendingException();
}
FinishOffThreadBuilder(cx, builder);
}
}
for (size_t i = 0; i < onIonCompilationVector.length(); i++) {
OnIonCompilationInfo& info = onIonCompilationVector[i];
// As it is easier to root a vector, instead of a vector of vector, we
// slice for each compilation.
AutoScriptVector sliceScripts(cx);
if (!sliceScripts.reserve(info.numBlocks)) {
cx->clearPendingException();
continue;
}
FinishOffThreadBuilder(cx, builder);
for (size_t b = 0; b < info.numBlocks; b++)
sliceScripts.infallibleAppend(debugScripts[info.scriptIndex + b]);
Debugger::onIonCompilation(cx, sliceScripts, info.graph);
}
js_delete(debuggerAlloc);
}
void
@ -1865,8 +1983,6 @@ IonCompile(JSContext* cx, JSScript* script,
JitContext jctx(cx, temp);
AutoEnterAnalysis enter(cx);
if (!cx->compartment()->ensureJitCompartmentExists(cx))
return AbortReason_Alloc;
@ -1923,8 +2039,12 @@ IonCompile(JSContext* cx, JSScript* script,
SpewBeginFunction(builder, builderScript);
bool succeeded = builder->build();
builder->clearForBackEnd();
bool succeeded;
{
AutoEnterAnalysis enter(cx);
succeeded = builder->build();
builder->clearForBackEnd();
}
if (!succeeded) {
AbortReason reason = builder->abortReason();
@ -1988,15 +2108,26 @@ IonCompile(JSContext* cx, JSScript* script,
return AbortReason_NoAbort;
}
ScopedJSDeletePtr<CodeGenerator> codegen(CompileBackEnd(builder));
if (!codegen) {
JitSpew(JitSpew_IonAbort, "Failed during back-end compilation.");
return AbortReason_Disable;
// See PrepareForDebuggerOnIonCompilationHook
AutoScriptVector debugScripts(cx);
OnIonCompilationInfo debugInfo(alloc);
ScopedJSDeletePtr<CodeGenerator> codegen;
{
AutoEnterAnalysis enter(cx);
codegen = CompileBackEnd(builder);
if (!codegen) {
JitSpew(JitSpew_IonAbort, "Failed during back-end compilation.");
return AbortReason_Disable;
}
succeeded = LinkCodeGen(cx, builder, codegen, &debugScripts, &debugInfo);
}
bool success = codegen->link(cx, builder->constraints());
if (debugInfo.filled())
Debugger::onIonCompilation(cx, debugScripts, debugInfo.graph);
if (success)
if (succeeded)
return AbortReason_NoAbort;
if (cx->isExceptionPending())
return AbortReason_Error;

View File

@ -388,3 +388,12 @@ JSONSpewer::endFunction()
endList();
endObject();
}
void
JSONSpewer::spewDebuggerGraph(MIRGraph* graph)
{
beginObject();
spewMIR(graph);
spewLIR(graph);
endObject();
}

View File

@ -58,6 +58,8 @@ class JSONSpewer
void spewRanges(BacktrackingAllocator* regalloc);
void endPass();
void endFunction();
void spewDebuggerGraph(MIRGraph* mir);
};
} // namespace jit

View File

@ -756,9 +756,7 @@ class MDefinition : public MNode
void setVirtualRegister(uint32_t vreg) {
virtualRegister_ = vreg;
#ifdef DEBUG
setLoweredUnchecked();
#endif
}
uint32_t virtualRegister() const {
MOZ_ASSERT(isLowered());

View File

@ -58,4 +58,27 @@ js::Debugger::onExceptionUnwind(JSContext* cx, AbstractFramePtr frame)
return slowPathOnExceptionUnwind(cx, frame);
}
/* static */ bool
js::Debugger::observesIonCompilation(JSContext* cx)
{
// If the current compartment is observed by any Debugger.
if (!cx->compartment()->isDebuggee())
return false;
// If any attached Debugger watch for Jit compilation results.
if (!Debugger::hasLiveHook(cx->global(), Debugger::OnIonCompilation))
return false;
return true;
}
/* static */ void
js::Debugger::onIonCompilation(JSContext* cx, AutoScriptVector& scripts, LSprinter& graph)
{
if (!observesIonCompilation(cx))
return;
slowPathOnIonCompilation(cx, scripts, graph);
}
#endif /* vm_Debugger_inl_h */

View File

@ -20,6 +20,8 @@
#include "gc/Marking.h"
#include "jit/BaselineDebugModeOSR.h"
#include "jit/BaselineJIT.h"
#include "jit/JSONSpewer.h"
#include "jit/MIRGraph.h"
#include "js/GCAPI.h"
#include "js/UbiNodeTraverse.h"
#include "js/Vector.h"
@ -1284,6 +1286,75 @@ Debugger::fireOnGarbageCollectionHook(JSContext* cx,
handleUncaughtException(ac, true);
}
JSTrapStatus
Debugger::fireOnIonCompilationHook(JSContext* cx, AutoScriptVector& scripts, LSprinter& graph)
{
RootedObject hook(cx, getHook(OnIonCompilation));
MOZ_ASSERT(hook);
MOZ_ASSERT(hook->isCallable());
Maybe<AutoCompartment> ac;
ac.emplace(cx, object);
// Copy the vector of scripts to a JS Array of Debugger.Script
RootedObject tmpObj(cx);
RootedValue tmpVal(cx);
AutoValueVector dbgScripts(cx);
for (size_t i = 0; i < scripts.length(); i++) {
tmpObj = wrapScript(cx, scripts[i]);
if (!tmpObj)
return handleUncaughtException(ac, false);
tmpVal.setObject(*tmpObj);
if (!dbgScripts.append(tmpVal))
return handleUncaughtException(ac, false);
}
RootedObject dbgScriptsArray(cx, JS_NewArrayObject(cx, dbgScripts));
if (!dbgScriptsArray)
return handleUncaughtException(ac, false);
// Copy the JSON compilation graph to a JS String which is allocated as part
// of the Debugger compartment.
Sprinter jsonPrinter(cx);
if (!jsonPrinter.init())
return handleUncaughtException(ac, false);
graph.exportInto(jsonPrinter);
if (jsonPrinter.hadOutOfMemory())
return handleUncaughtException(ac, false);
RootedString json(cx, JS_NewStringCopyZ(cx, jsonPrinter.string()));
if (!json)
return handleUncaughtException(ac, false);
// Create a JS Object which has the array of scripts, and the string of the
// JSON graph.
const char* names[] = { "scripts", "json" };
JS::AutoValueArray<2> values(cx);
values[0].setObject(*dbgScriptsArray);
values[1].setString(json);
RootedObject obj(cx, JS_NewObject(cx, nullptr));
if (!obj)
return handleUncaughtException(ac, false);
MOZ_ASSERT(mozilla::ArrayLength(names) == values.length());
for (size_t i = 0; i < mozilla::ArrayLength(names); i++) {
if (!JS_DefineProperty(cx, obj, names[i], values[i], JSPROP_ENUMERATE, nullptr, nullptr))
return handleUncaughtException(ac, false);
}
// Call Debugger.onIonCompilation hook.
JS::AutoValueArray<1> argv(cx);
argv[0].setObject(*obj);
RootedValue rv(cx);
if (!Invoke(cx, ObjectValue(*object), ObjectValue(*hook), 1, argv.begin(), &rv))
return handleUncaughtException(ac, true);
return JSTRAP_CONTINUE;
}
template <typename HookIsEnabledFun /* bool (Debugger*) */,
typename FireHookFun /* JSTrapStatus (Debugger*) */>
/* static */ JSTrapStatus
@ -1602,6 +1673,25 @@ Debugger::slowPathOnLogAllocationSite(JSContext* cx, HandleObject obj, HandleSav
return true;
}
/* static */ void
Debugger::slowPathOnIonCompilation(JSContext* cx, AutoScriptVector& scripts, LSprinter& graph)
{
JSTrapStatus status = dispatchHook(
cx,
[](Debugger* dbg) -> bool { return dbg->getHook(OnIonCompilation); },
[&](Debugger* dbg) -> JSTrapStatus {
(void) dbg->fireOnIonCompilationHook(cx, scripts, graph);
return JSTRAP_CONTINUE;
});
if (status == JSTRAP_ERROR) {
cx->clearPendingException();
return;
}
MOZ_ASSERT(status == JSTRAP_CONTINUE);
}
bool
Debugger::isDebuggee(const JSCompartment* compartment) const
{
@ -2815,6 +2905,20 @@ Debugger::getMemory(JSContext* cx, unsigned argc, Value* vp)
return true;
}
/* static */ bool
Debugger::getOnIonCompilation(JSContext* cx, unsigned argc, Value* vp)
{
THIS_DEBUGGER(cx, argc, vp, "(get onIonCompilation)", args, dbg);
return getHookImpl(cx, args, *dbg, OnIonCompilation);
}
/* static */ bool
Debugger::setOnIonCompilation(JSContext* cx, unsigned argc, Value* vp)
{
THIS_DEBUGGER(cx, argc, vp, "(set onIonCompilation)", args, dbg);
return setHookImpl(cx, args, *dbg, OnIonCompilation);
}
/*
* Given a value used to designate a global (there's quite a variety; see the
* docs), return the actual designee.
@ -4230,6 +4334,7 @@ const JSPropertySpec Debugger::properties[] = {
JS_PSGS("allowUnobservedAsmJS", Debugger::getAllowUnobservedAsmJS,
Debugger::setAllowUnobservedAsmJS, 0),
JS_PSG("memory", Debugger::getMemory, 0),
JS_PSGS("onIonCompilation", Debugger::getOnIonCompilation, Debugger::setOnIonCompilation, 0),
JS_PS_END
};
const JSFunctionSpec Debugger::methods[] = {

View File

@ -35,6 +35,8 @@ enum JSTrapStatus {
namespace js {
class LSprinter;
class Breakpoint;
class DebuggerMemory;
@ -205,6 +207,7 @@ class Debugger : private mozilla::LinkedListElement<Debugger>
OnNewPromise,
OnPromiseSettled,
OnGarbageCollection,
OnIonCompilation,
HookCount
};
enum {
@ -480,6 +483,8 @@ class Debugger : private mozilla::LinkedListElement<Debugger>
static bool getAllowUnobservedAsmJS(JSContext* cx, unsigned argc, Value* vp);
static bool setAllowUnobservedAsmJS(JSContext* cx, unsigned argc, Value* vp);
static bool getMemory(JSContext* cx, unsigned argc, Value* vp);
static bool getOnIonCompilation(JSContext* cx, unsigned argc, Value* vp);
static bool setOnIonCompilation(JSContext* cx, unsigned argc, Value* vp);
static bool addDebuggee(JSContext* cx, unsigned argc, Value* vp);
static bool addAllGlobalsAsDebuggees(JSContext* cx, unsigned argc, Value* vp);
static bool removeDebuggee(JSContext* cx, unsigned argc, Value* vp);
@ -547,6 +552,8 @@ class Debugger : private mozilla::LinkedListElement<Debugger>
static bool slowPathOnLogAllocationSite(JSContext* cx, HandleObject obj, HandleSavedFrame frame,
int64_t when, GlobalObject::DebuggerVector& dbgs);
static void slowPathPromiseHook(JSContext* cx, Hook hook, HandleObject promise);
static void slowPathOnIonCompilation(JSContext* cx, AutoScriptVector& scripts, LSprinter& graph);
template <typename HookIsEnabledFun /* bool (Debugger*) */,
typename FireHookFun /* JSTrapStatus (Debugger*) */>
static JSTrapStatus dispatchHook(JSContext* cx, HookIsEnabledFun hookIsEnabled,
@ -583,6 +590,13 @@ class Debugger : private mozilla::LinkedListElement<Debugger>
void fireOnGarbageCollectionHook(JSContext* cx,
const JS::dbg::GarbageCollectionEvent::Ptr& gcData);
/*
* Receive a "Ion compilation" event from the engine. An Ion compilation with
* the given summary just got linked.
*/
JSTrapStatus fireOnIonCompilationHook(JSContext* cx, AutoScriptVector& scripts,
LSprinter& graph);
/*
* Gets a Debugger.Frame object. If maybeIter is non-null, we eagerly copy
* its data if we need to make a new Debugger.Frame.
@ -700,6 +714,8 @@ class Debugger : private mozilla::LinkedListElement<Debugger>
static inline void onNewGlobalObject(JSContext* cx, Handle<GlobalObject*> global);
static inline bool onLogAllocationSite(JSContext* cx, JSObject* obj, HandleSavedFrame frame,
int64_t when);
static inline bool observesIonCompilation(JSContext* cx);
static inline void onIonCompilation(JSContext* cx, AutoScriptVector& scripts, LSprinter& graph);
static JSTrapStatus onTrap(JSContext* cx, MutableHandleValue vp);
static JSTrapStatus onSingleStep(JSContext* cx, MutableHandleValue vp);
static bool handleBaselineOsr(JSContext* cx, InterpreterFrame* from, jit::BaselineFrame* to);