From 125c415c243ae57e3340eb832a6c27741f75452f Mon Sep 17 00:00:00 2001 From: "Nicolas B. Pierron" Date: Thu, 28 May 2015 19:26:56 +0200 Subject: [PATCH] Bug 1147403 part 5 - Add Debugger::onIonCompilation hook. r=shu --- js/src/doc/Debugger/Debugger.md | 39 +++ js/src/doc/Debugger/config.sh | 1 + .../tests/debug/Debugger-onIonCompilation.js | 118 +++++++ js/src/jit/Ion.cpp | 311 +++++++++++++----- js/src/jit/JSONSpewer.cpp | 9 + js/src/jit/JSONSpewer.h | 2 + js/src/jit/MIR.h | 2 - js/src/vm/Debugger-inl.h | 23 ++ js/src/vm/Debugger.cpp | 105 ++++++ js/src/vm/Debugger.h | 16 + 10 files changed, 534 insertions(+), 92 deletions(-) create mode 100644 js/src/jit-test/tests/debug/Debugger-onIonCompilation.js diff --git a/js/src/doc/Debugger/Debugger.md b/js/src/doc/Debugger/Debugger.md index bcac1a39504..42bc4746ad7 100644 --- a/js/src/doc/Debugger/Debugger.md +++ b/js/src/doc/Debugger/Debugger.md @@ -225,6 +225,45 @@ compartment. thereby escaping the capability-based limits. For this reason, `onNewGlobalObject` is only available to privileged code. +onIonCompilation(graph) +: A new IonMonkey compilation result is attached to a script instance of + the Debuggee, the graph contains the internal intermediate + representations of the compiler. + + The value graph 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 diff --git a/js/src/doc/Debugger/config.sh b/js/src/doc/Debugger/config.sh index a15fa87e0bc..077f69cc535 100644 --- a/js/src/doc/Debugger/config.sh +++ b/js/src/doc/Debugger/config.sh @@ -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" diff --git a/js/src/jit-test/tests/debug/Debugger-onIonCompilation.js b/js/src/jit-test/tests/debug/Debugger-onIonCompilation.js new file mode 100644 index 00000000000..ec5f46b0104 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-onIonCompilation.js @@ -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); diff --git a/js/src/jit/Ion.cpp b/js/src/jit/Ion.cpp index ef4e0791ea0..36f18651ce2 100644 --- a/js/src/jit/Ion.cpp +++ b/js/src/jit/Ion.cpp @@ -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 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(); - 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_(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 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 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; diff --git a/js/src/jit/JSONSpewer.cpp b/js/src/jit/JSONSpewer.cpp index d9abff8f48f..9501bacaea4 100644 --- a/js/src/jit/JSONSpewer.cpp +++ b/js/src/jit/JSONSpewer.cpp @@ -388,3 +388,12 @@ JSONSpewer::endFunction() endList(); endObject(); } + +void +JSONSpewer::spewDebuggerGraph(MIRGraph* graph) +{ + beginObject(); + spewMIR(graph); + spewLIR(graph); + endObject(); +} diff --git a/js/src/jit/JSONSpewer.h b/js/src/jit/JSONSpewer.h index 58f0b3f086b..311f3ddf96c 100644 --- a/js/src/jit/JSONSpewer.h +++ b/js/src/jit/JSONSpewer.h @@ -58,6 +58,8 @@ class JSONSpewer void spewRanges(BacktrackingAllocator* regalloc); void endPass(); void endFunction(); + + void spewDebuggerGraph(MIRGraph* mir); }; } // namespace jit diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h index 46a07b10941..5130eabc918 100644 --- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -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()); diff --git a/js/src/vm/Debugger-inl.h b/js/src/vm/Debugger-inl.h index 73ca6b2caf9..c174fddbdfe 100644 --- a/js/src/vm/Debugger-inl.h +++ b/js/src/vm/Debugger-inl.h @@ -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 */ diff --git a/js/src/vm/Debugger.cpp b/js/src/vm/Debugger.cpp index 3184e4466a3..28f4f556b86 100644 --- a/js/src/vm/Debugger.cpp +++ b/js/src/vm/Debugger.cpp @@ -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 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 /* 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[] = { diff --git a/js/src/vm/Debugger.h b/js/src/vm/Debugger.h index 7c69a9b9b4f..1ed90305e9e 100644 --- a/js/src/vm/Debugger.h +++ b/js/src/vm/Debugger.h @@ -35,6 +35,8 @@ enum JSTrapStatus { namespace js { +class LSprinter; + class Breakpoint; class DebuggerMemory; @@ -205,6 +207,7 @@ class Debugger : private mozilla::LinkedListElement OnNewPromise, OnPromiseSettled, OnGarbageCollection, + OnIonCompilation, HookCount }; enum { @@ -480,6 +483,8 @@ class Debugger : private mozilla::LinkedListElement 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 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 static JSTrapStatus dispatchHook(JSContext* cx, HookIsEnabledFun hookIsEnabled, @@ -583,6 +590,13 @@ class Debugger : private mozilla::LinkedListElement 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 static inline void onNewGlobalObject(JSContext* cx, Handle 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);