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

This commit is contained in:
Nicolas B. Pierron 2015-05-15 20:19:03 +02:00
Родитель 2b8e638ee8
Коммит ee77187f06
10 изменённых файлов: 423 добавлений и 22 удалений

Просмотреть файл

@ -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"

Просмотреть файл

@ -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,11 +41,13 @@
#include "jit/Sink.h"
#include "jit/StupidAllocator.h"
#include "jit/ValueNumbering.h"
#include "vm/Debugger.h"
#include "vm/HelperThreads.h"
#include "vm/TraceLogging.h"
#include "jscompartmentinlines.h"
#include "jsobjinlines.h"
#include "vm/Debugger-inl.h"
using namespace js;
using namespace js::jit;
@ -392,6 +394,49 @@ JitCompartment::ensureIonStubsExist(JSContext* cx)
return true;
}
// 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 bool
PrepareForDebuggerOnIonCompilationHook(JSContext* cx, bool success, jit::MIRGraph& graph,
AutoScriptVector* scripts, LSprinter* spew)
{
if (!success)
return false;
if (!Debugger::observesIonCompilation(cx))
return false;
// fireOnIonCompilation failures are ignored, do the same here.
if (!scripts->reserve(graph.numBlocks())) {
cx->clearPendingException();
return false;
}
// Collect the list of scripts which are inlined in the MIRGraph.
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(*spew);
spewer.spewDebuggerGraph(&graph);
if (spew->hadOutOfMemory())
return false;
return true;
}
void
jit::FinishOffThreadBuilder(JSContext* cx, IonBuilder* builder)
{
@ -472,33 +517,47 @@ jit::LazyLinkTopActivation(JSContext* cx)
IonBuilder* builder = calleeScript->ionScript()->pendingBuilder();
calleeScript->setPendingIonBuilder(cx, nullptr);
AutoEnterAnalysis enterTypes(cx);
RootedScript script(cx, builder->script());
// See PrepareForDebuggerOnIonCompilationHook
bool callOnIonCompilation = false;
AutoScriptVector debugScripts(cx);
LSprinter debugPrinter(builder->alloc().lifoAlloc());
// Remove from pending.
builder->remove();
if (CodeGenerator* codegen = builder->backgroundCodegen()) {
CodeGenerator* codegen = builder->backgroundCodegen();
JitContext jctx(cx, &builder->alloc());
bool success = false;
if (codegen) {
AutoEnterAnalysis enterTypes(cx);
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())) {
success = codegen->link(cx, builder->constraints());
if (!success) {
// 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.
cx->clearPendingException();
}
callOnIonCompilation = PrepareForDebuggerOnIonCompilationHook(
cx, success, builder->graph(), &debugScripts, &debugPrinter);
}
// Without AutoEnterAnalysis scope.
if (callOnIonCompilation)
Debugger::onIonCompilation(cx, debugScripts, debugPrinter);
FinishOffThreadBuilder(cx, builder);
MOZ_ASSERT(script->hasBaselineScript());
@ -1633,7 +1692,6 @@ AttachFinishedCompilations(JSContext* cx)
if (!ion)
return;
AutoEnterAnalysis enterTypes(cx);
AutoLockHelperThreadState lock;
GlobalHelperThreadState::IonBuilderVector& finished = HelperThreadState().ionFinishedList();
@ -1685,9 +1743,17 @@ AttachFinishedCompilations(JSContext* cx)
}
}
if (CodeGenerator* codegen = builder->backgroundCodegen()) {
RootedScript script(cx, builder->script());
JitContext jctx(cx, &builder->alloc());
// See PrepareForDebuggerOnIonCompilationHook
bool callOnIonCompilation = false;
AutoScriptVector debugScripts(cx);
LSprinter debugPrinter(builder->alloc().lifoAlloc());
RootedScript script(cx, builder->script());
CodeGenerator* codegen = builder->backgroundCodegen();
JitContext jctx(cx, &builder->alloc());
bool success = false;
if (codegen) {
AutoEnterAnalysis enterTypes(cx);
TraceLoggerEvent event(logger, TraceLogger_AnnotateScripts, script);
AutoTraceLog logScript(logger, event);
AutoTraceLog logLink(logger, TraceLogger_IonLinking);
@ -1697,7 +1763,6 @@ AttachFinishedCompilations(JSContext* cx)
// previously, though any GC activity would discard the builder.
codegen->masm.constructRoot(cx);
bool success;
{
AutoUnlockHelperThreadState unlock;
success = codegen->link(cx, builder->constraints());
@ -1710,6 +1775,15 @@ AttachFinishedCompilations(JSContext* cx)
// exception from there.
cx->clearPendingException();
}
callOnIonCompilation = PrepareForDebuggerOnIonCompilationHook(
cx, success, builder->graph(), &debugScripts, &debugPrinter);
}
if (callOnIonCompilation) {
// Without AutoEnterAnalysis scope.
AutoUnlockHelperThreadState unlock;
Debugger::onIonCompilation(cx, debugScripts, debugPrinter);
}
FinishOffThreadBuilder(cx, builder);
@ -1862,8 +1936,6 @@ IonCompile(JSContext* cx, JSScript* script,
JitContext jctx(cx, temp);
AutoEnterAnalysis enter(cx);
if (!cx->compartment()->ensureJitCompartmentExists(cx))
return AbortReason_Alloc;
@ -1920,8 +1992,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();
@ -1985,15 +2061,29 @@ 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
bool callOnIonCompilation = false;
AutoScriptVector debugScripts(cx);
LSprinter debugPrinter(builder->alloc().lifoAlloc());
ScopedJSDeletePtr<CodeGenerator> codegen;
{
AutoEnterAnalysis enter(cx);
codegen = CompileBackEnd(builder);
if (!codegen) {
JitSpew(JitSpew_IonAbort, "Failed during back-end compilation.");
return AbortReason_Disable;
}
succeeded = codegen->link(cx, builder->constraints());
callOnIonCompilation = PrepareForDebuggerOnIonCompilationHook(
cx, succeeded, builder->graph(), &debugScripts, &debugPrinter);
}
bool success = codegen->link(cx, builder->constraints());
if (callOnIonCompilation)
Debugger::onIonCompilation(cx, debugScripts, debugPrinter);
if (success)
if (succeeded)
return AbortReason_NoAbort;
if (cx->isExceptionPending())
return AbortReason_Error;

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

@ -753,9 +753,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;
@ -204,6 +206,7 @@ class Debugger : private mozilla::LinkedListElement<Debugger>
OnNewPromise,
OnPromiseSettled,
OnGarbageCollection,
OnIonCompilation,
HookCount
};
enum {
@ -479,6 +482,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);
@ -546,6 +551,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,
@ -582,6 +589,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.
@ -699,6 +713,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);