mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1147403 part 5 - Add Debugger::onIonCompilation hook. r=shu
This commit is contained in:
parent
560346fb43
commit
125c415c24
@ -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
|
||||
|
@ -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"
|
||||
|
118
js/src/jit-test/tests/debug/Debugger-onIonCompilation.js
Normal file
118
js/src/jit-test/tests/debug/Debugger-onIonCompilation.js
Normal 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);
|
@ -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;
|
||||
|
@ -388,3 +388,12 @@ JSONSpewer::endFunction()
|
||||
endList();
|
||||
endObject();
|
||||
}
|
||||
|
||||
void
|
||||
JSONSpewer::spewDebuggerGraph(MIRGraph* graph)
|
||||
{
|
||||
beginObject();
|
||||
spewMIR(graph);
|
||||
spewLIR(graph);
|
||||
endObject();
|
||||
}
|
||||
|
@ -58,6 +58,8 @@ class JSONSpewer
|
||||
void spewRanges(BacktrackingAllocator* regalloc);
|
||||
void endPass();
|
||||
void endFunction();
|
||||
|
||||
void spewDebuggerGraph(MIRGraph* mir);
|
||||
};
|
||||
|
||||
} // namespace jit
|
||||
|
@ -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());
|
||||
|
@ -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 */
|
||||
|
@ -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[] = {
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user