--- a/js/public/Debug.h
+++ b/js/public/Debug.h
@@ -313,13 +313,55 @@ JS_PUBLIC_API(void)
onPromiseSettled(JSContext* cx, HandleObject promise);
// Return true if the given value is a Debugger object, false otherwise.
JS_PUBLIC_API(bool)
IsDebugger(JS::Value val);
+
+// Hooks for reporting where JavaScript execution began.
+//
+// Our performance tools would like to be able to label blocks of JavaScript
+// execution with the function name and source location where execution began:
+// the event handler, the callback, etc.
+//
+// Construct an instance of this class on the stack, providing a JSContext
+// belonging to the runtime in which execution will occur. Each time we enter
+// JavaScript --- specifically, each time we push a JavaScript stack frame that
+// has no older JS frames younger than this AutoEntryMonitor --- we will
+// call the appropriate |Entry| member function to indicate where we've begun
+// execution.
+
+class MOZ_STACK_CLASS AutoEntryMonitor {
+ JSRuntime* runtime_;
+ AutoEntryMonitor* savedMonitor_;
+
+ public:
+ explicit AutoEntryMonitor(JSContext* cx);
+ ~AutoEntryMonitor();
+
+ // SpiderMonkey reports the JavaScript entry points occuring within this
+ // AutoEntryMonitor's scope to the following member functions, which the
+ // embedding is expected to override.
+
+ // We have begun executing |function|. Note that |function| may not be the
+ // actual closure we are running, but only the canonical function object to
+ // which the script refers.
+ virtual void Entry(JSContext* cx, JSFunction* function) = 0;
+
+ // Execution has begun at the entry point of |script|, which is not a
+ // function body. (This is probably being executed by 'eval' or some
+ // JSAPI equivalent.)
+ virtual void Entry(JSContext* cx, JSScript* script) = 0;
+
+ // Execution of the function or script has ended.
+ virtual void Exit(JSContext* cx) { }
+};
+
+
+
} // namespace dbg
} // namespace JS
#endif /* js_Debug_h */
--- a/js/src/asmjs/AsmJSLink.cpp
+++ b/js/src/asmjs/AsmJSLink.cpp
@@ -739,18 +739,22 @@ CallAsmJS(JSContext* cx, unsigned argc,
}
{
// Push an AsmJSActivation to describe the asm.js frames we're about to
// push when running this module. Additionally, push a JitActivation so
// that the optimized asm.js-to-Ion FFI call path (which we want to be
// very fast) can avoid doing so. The JitActivation is marked as
// inactive so stack iteration will skip over it.
+ //
+ // We needn't provide an entry script pointer; that's only used for
+ // reporting entry points to performance-monitoring tools, and asm.js ->
+ // Ion calls will never be entry points.
AsmJSActivation activation(cx, module);
- JitActivation jitActivation(cx, /* active */ false);
+ JitActivation jitActivation(cx, /* entryScript */ nullptr, /* active */ false);
// Call the per-exported-function trampoline created by GenerateEntry.
AsmJSModule::CodePtr enter = module.entryTrampoline(func);
if (!CALL_GENERATED_2(enter, coercedArgs.begin(), module.globalData()))
return false;
}
if (callArgs.isConstructing()) {
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/profiler/AutoEntryMonitor-01.js
@@ -0,0 +1,50 @@
+// AutoEntryMonitor should catch all entry points into JavaScript.
+
+load(libdir + 'array-compare.js');
+
+function cold_and_warm(f, params, expected) {
+ print(uneval(params));
+ print(uneval(entryPoints(params)));
+ assertEq(arraysEqual(entryPoints(params), expected), true);
+
+ // Warm up the function a bit, so the JITs will compile it, and try again.
+ if (f)
+ for (i = 0; i < 10; i++)
+ f();
+
+ assertEq(arraysEqual(entryPoints(params), expected), true);
+}
+
+function entry1() { }
+cold_and_warm(entry1, { function: entry1 }, [ "entry1" ]);
+
+var getx = { get x() { } };
+cold_and_warm(Object.getOwnPropertyDescriptor(getx, 'x').get,
+ { object: getx, property: 'x' }, [ "getx.x" ]);
+
+var sety = { set y(v) { } };
+cold_and_warm(Object.getOwnPropertyDescriptor(sety, 'y').set,
+ { object: sety, property: 'y', value: 'glerk' }, [ "sety.y" ]);
+
+cold_and_warm(Object.prototype.toString, { ToString: {} }, []);
+
+var toS = { toString: function myToString() { return "string"; } };
+cold_and_warm(toS.toString, { ToString: toS }, [ "myToString" ]);
+
+cold_and_warm(undefined, { ToNumber: {} }, []);
+
+var vOf = { valueOf: function myValueOf() { return 42; } };
+cold_and_warm(vOf.valueOf, { ToNumber: vOf }, [ "myValueOf" ]);
+
+var toSvOf = { toString: function relations() { return Object; },
+ valueOf: function wallpaper() { return 17; } };
+cold_and_warm(() => { toSvOf.toString(); toSvOf.valueOf(); },
+ { ToString: toSvOf }, [ "relations", "wallpaper" ]);
+
+var vOftoS = { toString: function ettes() { return "6-inch lengths"; },
+ valueOf: function deathBy() { return Math; } };
+cold_and_warm(() => { vOftoS.valueOf(); vOftoS.toString(); },
+ { ToNumber: vOftoS }, [ "deathBy", "ettes" ]);
+
+
+cold_and_warm(() => 0, { eval: "Math.atan2(1,1);" }, [ "eval:entryPoint eval" ]);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/profiler/AutoEntryMonitor-02.js
@@ -0,0 +1,12 @@
+// Nested uses of AutoEntryMonitor should behave with decorum.
+
+load(libdir + 'array-compare.js');
+
+function Cobb() {
+ var twoShot = { toString: function Saito() { return Object; },
+ valueOf: function Fischer() { return "Ariadne"; } };
+ assertEq(arraysEqual(entryPoints({ ToString: twoShot }),
+ [ "Saito", "Fischer" ]), true);
+}
+
+assertEq(arraysEqual(entryPoints({ function: Cobb }), ["Cobb"]), true);
--- a/js/src/jit/BaselineJIT.cpp
+++ b/js/src/jit/BaselineJIT.cpp
@@ -110,17 +110,17 @@ EnterBaseline(JSContext* cx, EnterJitDat
EnterJitCode enter = cx->runtime()->jitRuntime()->enterBaseline();
// Caller must construct |this| before invoking the Ion function.
MOZ_ASSERT_IF(data.constructing, data.maxArgv[0].isObject());
data.result.setInt32(data.numActualArgs);
{
AssertCompartmentUnchanged pcc(cx);
- JitActivation activation(cx);
+ JitActivation activation(cx, data.calleeToken);
if (data.osrFrame)
data.osrFrame->setRunningInJit();
// Single transition point from Interpreter to Baseline.
CALL_GENERATED_CODE(enter, data.jitcode, data.maxArgc, data.maxArgv, data.osrFrame, data.calleeToken,
data.scopeChain.get(), data.osrNumStackValues, data.result.address());
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -2379,17 +2379,17 @@ EnterIon(JSContext* cx, EnterJitData& da
EnterJitCode enter = cx->runtime()->jitRuntime()->enterIon();
// Caller must construct |this| before invoking the Ion function.
MOZ_ASSERT_IF(data.constructing, data.maxArgv[0].isObject());
data.result.setInt32(data.numActualArgs);
{
AssertCompartmentUnchanged pcc(cx);
- JitActivation activation(cx);
+ JitActivation activation(cx, data.calleeToken);
CALL_GENERATED_CODE(enter, data.jitcode, data.maxArgc, data.maxArgv, /* osrFrame = */nullptr, data.calleeToken,
/* scopeChain = */ nullptr, 0, data.result.address());
}
MOZ_ASSERT(!cx->runtime()->jitRuntime()->hasIonReturnOverride());
// Jit callers wrap primitive constructor return.
@@ -2476,24 +2476,25 @@ jit::IonCannon(JSContext* cx, RunState&
return status;
}
JitExecStatus
jit::FastInvoke(JSContext* cx, HandleFunction fun, CallArgs& args)
{
JS_CHECK_RECURSION(cx, return JitExec_Error);
- IonScript* ion = fun->nonLazyScript()->ionScript();
+ RootedScript script(cx, fun->nonLazyScript());
+ IonScript* ion = script->ionScript();
JitCode* code = ion->method();
void* jitcode = code->raw();
MOZ_ASSERT(jit::IsIonEnabled(cx));
MOZ_ASSERT(!ion->bailoutExpected());
- JitActivation activation(cx);
+ JitActivation activation(cx, CalleeToToken(script));
EnterJitCode enter = cx->runtime()->jitRuntime()->enterIon();
void* calleeToken = CalleeToToken(fun, /* constructing = */ false);
RootedValue result(cx, Int32Value(args.length()));
MOZ_ASSERT(args.length() >= fun->nargs());
CALL_GENERATED_CODE(enter, jitcode, args.length() + 1, args.array() - 1, /* osrFrame = */nullptr,
--- a/js/src/jit/JitFrameIterator.h
+++ b/js/src/jit/JitFrameIterator.h
@@ -18,16 +18,18 @@
namespace js {
class ActivationIterator;
};
namespace js {
namespace jit {
+typedef void * CalleeToken;
+
enum FrameType
{
// A JS frame is analagous to a js::InterpreterFrame, representing one scripted
// functon activation. IonJS frames are used by the optimizing compiler.
JitFrame_IonJS,
// JS frame used by the baseline JIT.
JitFrame_BaselineJS,
--- a/js/src/jit/JitFrames.h
+++ b/js/src/jit/JitFrames.h
@@ -13,18 +13,16 @@
#include "jsfun.h"
#include "jit/JitFrameIterator.h"
#include "jit/Safepoints.h"
namespace js {
namespace jit {
-typedef void * CalleeToken;
-
enum CalleeTokenTag
{
CalleeToken_Function = 0x0, // untagged
CalleeToken_FunctionConstructing = 0x1,
CalleeToken_Script = 0x2
};
static const uintptr_t CalleeTokenMask = ~uintptr_t(0x3);
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -4507,16 +4507,215 @@ ReflectTrackedOptimizations(JSContext* c
RootedValue jsonVal(cx);
if (!JS_ParseJSON(cx, str, &jsonVal))
return false;
args.rval().set(jsonVal);
return true;
}
+namespace shell {
+
+class ShellAutoEntryMonitor : JS::dbg::AutoEntryMonitor {
+ Vector<UniqueChars, 1, js::SystemAllocPolicy> log;
+ bool oom;
+ bool enteredWithoutExit;
+
+ public:
+ explicit ShellAutoEntryMonitor(JSContext *cx)
+ : AutoEntryMonitor(cx),
+ oom(false),
+ enteredWithoutExit(false)
+ { }
+
+ ~ShellAutoEntryMonitor() {
+ MOZ_ASSERT(!enteredWithoutExit);
+ }
+
+ void Entry(JSContext* cx, JSFunction* function) override {
+ MOZ_ASSERT(!enteredWithoutExit);
+ enteredWithoutExit = true;
+
+ RootedString displayId(cx, JS_GetFunctionDisplayId(function));
+ if (displayId) {
+ UniqueChars displayIdStr(JS_EncodeStringToUTF8(cx, displayId));
+ oom = !displayIdStr || !log.append(mozilla::Move(displayIdStr));
+ return;
+ }
+
+ oom = !log.append(make_string_copy("anonymous"));
+ }
+
+ void Entry(JSContext* cx, JSScript* script) override {
+ MOZ_ASSERT(!enteredWithoutExit);
+ enteredWithoutExit = true;
+
+ UniqueChars label(JS_smprintf("eval:%s", JS_GetScriptFilename(script)));
+ oom = !label || !log.append(mozilla::Move(label));
+ }
+
+ void Exit(JSContext* cx) {
+ MOZ_ASSERT(enteredWithoutExit);
+ enteredWithoutExit = false;
+ }
+
+ bool buildResult(JSContext *cx, MutableHandleValue resultValue) {
+ if (oom) {
+ JS_ReportOutOfMemory(cx);
+ return false;
+ }
+
+ RootedObject result(cx, JS_NewArrayObject(cx, log.length()));
+ if (!result)
+ return false;
+
+ for (size_t i = 0; i < log.length(); i++) {
+ char *name = log[i].get();
+ RootedString string(cx, Atomize(cx, name, strlen(name)));
+ if (!string)
+ return false;
+ RootedValue value(cx, StringValue(string));
+ if (!JS_SetElement(cx, result, i, value))
+ return false;
+ }
+
+ resultValue.setObject(*result.get());
+ return true;
+ }
+};
+
+} // namespace shell
+
+static bool
+EntryPoints(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() != 1) {
+ JS_ReportError(cx, "Wrong number of arguments");
+ return false;
+ }
+
+ RootedObject opts(cx, ToObject(cx, args[0]));
+ if (!opts)
+ return false;
+
+ // { function: f } --- Call f.
+ {
+ RootedValue fun(cx), dummy(cx);
+
+ if (!JS_GetProperty(cx, opts, "function", &fun))
+ return false;
+ if (!fun.isUndefined()) {
+ shell::ShellAutoEntryMonitor sarep(cx);
+ if (!Call(cx, UndefinedHandleValue, fun, JS::HandleValueArray::empty(), &dummy))
+ return false;
+ return sarep.buildResult(cx, args.rval());
+ }
+ }
+
+ // { object: o, property: p, value: v } --- Fetch o[p], or if
+ // v is present, assign o[p] = v.
+ {
+ RootedValue objectv(cx), propv(cx), valuev(cx);
+
+ if (!JS_GetProperty(cx, opts, "object", &objectv) ||
+ !JS_GetProperty(cx, opts, "property", &propv))
+ return false;
+ if (!objectv.isUndefined() && !propv.isUndefined()) {
+ RootedObject object(cx, ToObject(cx, objectv));
+ if (!object)
+ return false;
+
+ RootedString string(cx, ToString(cx, propv));
+ if (!string)
+ return false;
+ RootedId id(cx);
+ if (!JS_StringToId(cx, string, &id))
+ return false;
+
+ if (!JS_GetProperty(cx, opts, "value", &valuev))
+ return false;
+
+ shell::ShellAutoEntryMonitor sarep(cx);
+
+ if (!valuev.isUndefined()) {
+ if (!JS_SetPropertyById(cx, object, id, valuev))
+ return false;
+ } else {
+ if (!JS_GetPropertyById(cx, object, id, &valuev))
+ return false;
+ }
+
+ return sarep.buildResult(cx, args.rval());
+ }
+ }
+
+ // { ToString: v } --- Apply JS::ToString to v.
+ {
+ RootedValue v(cx);
+
+ if (!JS_GetProperty(cx, opts, "ToString", &v))
+ return false;
+ if (!v.isUndefined()) {
+ shell::ShellAutoEntryMonitor sarep(cx);
+ if (!JS::ToString(cx, v))
+ return false;
+ return sarep.buildResult(cx, args.rval());
+ }
+ }
+
+ // { ToNumber: v } --- Apply JS::ToNumber to v.
+ {
+ RootedValue v(cx);
+ double dummy;
+
+ if (!JS_GetProperty(cx, opts, "ToNumber", &v))
+ return false;
+ if (!v.isUndefined()) {
+ shell::ShellAutoEntryMonitor sarep(cx);
+ if (!JS::ToNumber(cx, v, &dummy))
+ return false;
+ return sarep.buildResult(cx, args.rval());
+ }
+ }
+
+ // { eval: code } --- Apply ToString and then Evaluate to code.
+ {
+ RootedValue code(cx), dummy(cx);
+
+ if (!JS_GetProperty(cx, opts, "eval", &code))
+ return false;
+ if (!code.isUndefined()) {
+ RootedString codeString(cx, ToString(cx, code));
+ if (!codeString || !codeString->ensureFlat(cx))
+ return false;
+
+ AutoStableStringChars stableChars(cx);
+ if (!stableChars.initTwoByte(cx, codeString))
+ return false;
+ const char16_t* chars = stableChars.twoByteRange().start().get();
+ size_t length = codeString->length();
+
+ CompileOptions options(cx);
+ options.setIntroductionType("entryPoint eval")
+ .setFileAndLine("entryPoint eval", 1);
+
+ shell::ShellAutoEntryMonitor sarep(cx);
+ if (!JS::Evaluate(cx, options, chars, length, &dummy))
+ return false;
+ return sarep.buildResult(cx, args.rval());
+ }
+ }
+
+ JS_ReportError(cx, "bad 'params' object");
+ return false;
+}
+
+
static const JSFunctionSpecWithHelp shell_functions[] = {
JS_FN_HELP("version", Version, 0, 0,
"version([number])",
" Get or force a script compilation version number."),
JS_FN_HELP("options", Options, 0, 0,
"options([option ...])",
" Get or toggle JavaScript options."),
@@ -4862,16 +5061,40 @@ static const JSFunctionSpecWithHelp shel
"isLatin1(s)",
" Return true iff the string's characters are stored as Latin1."),
JS_FN_HELP("stackPointerInfo", StackPointerInfo, 0, 0,
"stackPointerInfo()",
" Return an int32 value which corresponds to the offset of the latest stack\n"
" pointer, such that one can take the differences of 2 to estimate a frame-size."),
+ JS_FN_HELP("entryPoints", EntryPoints, 1, 0,
+"entryPoints(params)",
+"Carry out some JSAPI operation as directed by |params|, and return an array of\n"
+"objects describing which JavaScript entry points were invoked as a result.\n"
+"|params| is an object whose properties indicate what operation to perform. Here\n"
+"are the recognized groups of properties:\n"
+"\n"
+"{ function }: Call the object |params.function| with no arguments.\n"
+"\n"
+"{ object, property }: Fetch the property named |params.property| of\n"
+"|params.object|.\n"
+"\n"
+"{ ToString }: Apply JS::ToString to |params.toString|.\n"
+"\n"
+"{ ToNumber }: Apply JS::ToNumber to |params.toNumber|.\n"
+"\n"
+"{ eval }: Apply JS::Evaluate to |params.eval|.\n"
+"\n"
+"The return value is an array of strings, with one element for each\n"
+"JavaScript invocation that occurred as a result of the given\n"
+"operation. Each element is the name of the function invoked, or the\n"
+"string 'eval:FILENAME' if the code was invoked by 'eval' or something\n"
+"similar.\n"),
+
JS_FS_HELP_END
};
static const JSFunctionSpecWithHelp fuzzing_unsafe_functions[] = {
JS_FN_HELP("clone", Clone, 1, 0,
"clone(fun[, scope])",
" Clone function object."),
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -34,16 +34,17 @@
#include "jsopcodeinlines.h"
#include "jsscriptinlines.h"
#include "vm/NativeObject-inl.h"
#include "vm/Stack-inl.h"
using namespace js;
+using JS::dbg::AutoEntryMonitor;
using JS::dbg::Builder;
using js::frontend::IsIdentifier;
using mozilla::ArrayLength;
using mozilla::DebugOnly;
using mozilla::Maybe;
using mozilla::UniquePtr;
@@ -7797,16 +7798,31 @@ Builder::newObject(JSContext* cx)
RootedPlainObject obj(cx, NewBuiltinClassInstance<PlainObject>(cx));
// If the allocation failed, this will return a false Object, as the spec promises.
return Object(cx, *this, obj);
}
+/*** JS::dbg::AutoEntryMonitor ******************************************************************/
+
+AutoEntryMonitor::AutoEntryMonitor(JSContext* cx)
+ : runtime_(cx->runtime()),
+ savedMonitor_(cx->runtime()->entryMonitor)
+{
+ runtime_->entryMonitor = this;
+}
+
+AutoEntryMonitor::~AutoEntryMonitor()
+{
+ runtime_->entryMonitor = savedMonitor_;
+}
+
+
/*** Glue ****************************************************************************************/
extern JS_PUBLIC_API(bool)
JS_DefineDebuggerObject(JSContext* cx, HandleObject obj)
{
RootedNativeObject
objProto(cx),
debugCtor(cx),
--- a/js/src/vm/DebuggerMemory.cpp
+++ b/js/src/vm/DebuggerMemory.cpp
@@ -186,17 +186,17 @@ DebuggerMemory::drainAllocationsLog(JSCo
result->ensureDenseInitializedLength(cx, 0, length);
for (size_t i = 0; i < length; i++) {
RootedPlainObject obj(cx, NewBuiltinClassInstance<PlainObject>(cx));
if (!obj)
return false;
// Don't pop the AllocationSite yet. The queue's links are followed by
- // the GC to find the AllocationSite, but are not barried, so we must
+ // the GC to find the AllocationSite, but are not barriered, so we must
// edit them with great care. Use the queue entry in place, and then
// pop and delete together.
Debugger::AllocationSite* allocSite = dbg->allocationsLog.getFirst();
RootedValue frame(cx, ObjectOrNullValue(allocSite->frame));
if (!DefineProperty(cx, obj, cx->names().frame, frame))
return false;
RootedValue timestampValue(cx, NumberValue(allocSite->when));
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -121,16 +121,17 @@ JSRuntime::JSRuntime(JSRuntime* parentRu
jitStackLimit_(0xbad),
activation_(nullptr),
profilingActivation_(nullptr),
profilerSampleBufferGen_(0),
profilerSampleBufferLapCount_(1),
asmJSActivationStack_(nullptr),
asyncStackForNewActivations(nullptr),
asyncCauseForNewActivations(nullptr),
+ entryMonitor(nullptr),
parentRuntime(parentRuntime),
interrupt_(false),
telemetryCallback(nullptr),
handlingSignal(false),
interruptCallback(nullptr),
exclusiveAccessLock(nullptr),
exclusiveAccessOwner(nullptr),
mainThreadHasExclusiveAccess(false),
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -675,16 +675,19 @@ struct JSRuntime : public JS::shadow::Ru
*/
js::SavedFrame* asyncStackForNewActivations;
/*
* Value of asyncCause to be attached to asyncStackForNewActivations.
*/
JSString* asyncCauseForNewActivations;
+ /* If non-null, report JavaScript entry points to this monitor. */
+ JS::dbg::AutoEntryMonitor* entryMonitor;
+
js::Activation* const* addressOfActivation() const {
return &activation_;
}
static unsigned offsetOfActivation() {
return offsetof(JSRuntime, activation_);
}
js::Activation* profilingActivation() const {
--- a/js/src/vm/Stack-inl.h
+++ b/js/src/vm/Stack-inl.h
@@ -11,16 +11,17 @@
#include "mozilla/PodOperations.h"
#include "jscntxt.h"
#include "jsscript.h"
#include "jit/BaselineFrame.h"
#include "jit/RematerializedFrame.h"
+#include "js/Debug.h"
#include "vm/GeneratorObject.h"
#include "vm/ScopeObject.h"
#include "jsobjinlines.h"
#include "jsscriptinlines.h"
#include "jit/BaselineFrame-inl.h"
@@ -841,29 +842,32 @@ Activation::Activation(JSContext* cx, Ki
: cx_(cx),
compartment_(cx->compartment()),
prev_(cx->runtime_->activation_),
prevProfiling_(prev_ ? prev_->mostRecentProfiling() : nullptr),
savedFrameChain_(0),
hideScriptedCallerCount_(0),
asyncStack_(cx, cx->runtime_->asyncStackForNewActivations),
asyncCause_(cx, cx->runtime_->asyncCauseForNewActivations),
+ entryMonitor_(cx->runtime_->entryMonitor),
kind_(kind)
{
cx->runtime_->asyncStackForNewActivations = nullptr;
cx->runtime_->asyncCauseForNewActivations = nullptr;
+ cx->runtime_->entryMonitor = nullptr;
cx->runtime_->activation_ = this;
}
Activation::~Activation()
{
MOZ_ASSERT_IF(isProfiling(), this != cx_->runtime()->profilingActivation_);
MOZ_ASSERT(cx_->runtime_->activation_ == this);
MOZ_ASSERT(hideScriptedCallerCount_ == 0);
cx_->runtime_->activation_ = prev_;
+ cx_->runtime_->entryMonitor = entryMonitor_;
cx_->runtime_->asyncCauseForNewActivations = asyncCause_;
cx_->runtime_->asyncStackForNewActivations = asyncStack_;
}
bool
Activation::isProfiling() const
{
if (isInterpreter())
@@ -891,28 +895,38 @@ InterpreterActivation::InterpreterActiva
opMask_(0)
#ifdef DEBUG
, oldFrameCount_(cx->runtime()->interpreterStack().frameCount_)
#endif
{
regs_.prepareToRun(*entryFrame, state.script());
MOZ_ASSERT(regs_.pc == state.script()->code());
MOZ_ASSERT_IF(entryFrame_->isEvalFrame(), state.script()->isActiveEval());
+
+ if (entryMonitor_) {
+ if (entryFrame->isFunctionFrame())
+ entryMonitor_->Entry(cx_, entryFrame->fun());
+ else
+ entryMonitor_->Entry(cx_, entryFrame->script());
+ }
}
InterpreterActivation::~InterpreterActivation()
{
// Pop all inline frames.
while (regs_.fp() != entryFrame_)
popInlineFrame(regs_.fp());
JSContext* cx = cx_->asJSContext();
MOZ_ASSERT(oldFrameCount_ == cx->runtime()->interpreterStack().frameCount_);
MOZ_ASSERT_IF(oldFrameCount_ == 0, cx->runtime()->interpreterStack().allocator_.used() == 0);
+ if (entryMonitor_)
+ entryMonitor_->Exit(cx_);
+
if (entryFrame_)
cx->runtime()->interpreterStack().releaseFrame(entryFrame_);
}
inline bool
InterpreterActivation::pushInlineFrame(const CallArgs& args, HandleScript script,
InitialFrameFlags initial)
{
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -1383,17 +1383,17 @@ bool
AbstractFramePtr::hasPushedSPSFrame() const
{
if (isInterpreterFrame())
return asInterpreterFrame()->hasPushedSPSFrame();
MOZ_ASSERT(isBaselineFrame());
return false;
}
-jit::JitActivation::JitActivation(JSContext* cx, bool active)
+jit::JitActivation::JitActivation(JSContext* cx, CalleeToken entryPoint, bool active)
: Activation(cx, Jit),
active_(active),
isLazyLinkExitFrame_(false),
rematerializedFrames_(nullptr),
ionRecovery_(cx),
bailoutData_(nullptr),
lastProfilingFrame_(nullptr),
lastProfilingCallSite_(nullptr)
@@ -1406,20 +1406,32 @@ jit::JitActivation::JitActivation(JSCont
cx->runtime()->jitActivation = this;
registerProfiling();
} else {
prevJitTop_ = nullptr;
prevJitJSContext_ = nullptr;
prevJitActivation_ = nullptr;
}
+
+ if (entryMonitor_) {
+ MOZ_ASSERT(entryPoint);
+
+ if (CalleeTokenIsFunction(entryPoint))
+ entryMonitor_->Entry(cx_, CalleeTokenToFunction(entryPoint));
+ else
+ entryMonitor_->Entry(cx_, CalleeTokenToScript(entryPoint));
+ }
}
jit::JitActivation::~JitActivation()
{
+ if (entryMonitor_)
+ entryMonitor_->Exit(cx_);
+
if (active_) {
if (isProfiling())
unregisterProfiling();
cx_->runtime()->jitTop = prevJitTop_;
cx_->runtime()->jitJSContext = prevJitJSContext_;
cx_->runtime()->jitActivation = prevJitActivation_;
}
--- a/js/src/vm/Stack.h
+++ b/js/src/vm/Stack.h
@@ -16,16 +16,22 @@
#include "asmjs/AsmJSFrameIterator.h"
#include "jit/JitFrameIterator.h"
#ifdef CHECK_OSIPOINT_REGISTERS
#include "jit/Registers.h" // for RegisterDump
#endif
struct JSCompartment;
+namespace JS {
+namespace dbg {
+class AutoEntryMonitor;
+}
+}
+
namespace js {
class ArgumentsObject;
class AsmJSModule;
class InterpreterRegs;
class CallObject;
class ScopeObject;
class ScriptFrameIter;
@@ -1086,16 +1092,21 @@ class Activation
//
// Usually this is nullptr, meaning that normal stack capture will occur.
// When this is set, the stack of any previous activation is ignored.
Rooted<SavedFrame*> asyncStack_;
// Value of asyncCause to be attached to asyncStack_.
RootedString asyncCause_;
+ // The entry point monitor that was set on cx_->runtime() when this
+ // Activation was created. Subclasses should report their entry frame's
+ // function or script here.
+ JS::dbg::AutoEntryMonitor* entryMonitor_;
+
enum Kind { Interpreter, Jit, AsmJS };
Kind kind_;
inline Activation(JSContext* cx, Kind kind_);
inline ~Activation();
public:
JSContext* cx() const {
@@ -1335,17 +1346,21 @@ class JitActivation : public Activation
protected:
// Used to verify that live registers don't change between a VM call and
// the OsiPoint that follows it. Protected to silence Clang warning.
uint32_t checkRegs_;
RegisterDump regs_;
#endif
public:
- explicit JitActivation(JSContext* cx, bool active = true);
+ // If non-null, |entryScript| should be the script we're about to begin
+ // executing, for the benefit of performance tooling. We can pass null for
+ // entryScript when we know we couldn't possibly be entering JS directly
+ // from the JSAPI: OSR, asm.js -> Ion transitions, and so on.
+ explicit JitActivation(JSContext* cx, CalleeToken entryPoint, bool active = true);
~JitActivation();
bool isActive() const {
return active_;
}
void setActive(JSContext* cx, bool active = true);
bool isProfiling() const;