Bug 1050500: Add SpiderMonkey API for reporting JavaScript entry points. r=shu, a=sledru
authorJim Blandy <jimb@mozilla.com>
Thu, 07 May 2015 11:34:03 -0700
changeset 274827 50a6675e974b2008915025872c7e35cf1ed0d71e
parent 274826 1d26316f101540752358c65a16150163adb5e95a
child 274828 1ff6d58a4e0aa2ee19c1f7912ad25b429ba29248
push id863
push userraliiev@mozilla.com
push dateMon, 03 Aug 2015 13:22:43 +0000
treeherdermozilla-release@f6321b14228d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersshu, sledru
bugs1050500
milestone40.0a2
Bug 1050500: Add SpiderMonkey API for reporting JavaScript entry points. r=shu, a=sledru
js/public/Debug.h
js/src/asmjs/AsmJSLink.cpp
js/src/jit-test/tests/profiler/AutoEntryMonitor-01.js
js/src/jit-test/tests/profiler/AutoEntryMonitor-02.js
js/src/jit/BaselineJIT.cpp
js/src/jit/Ion.cpp
js/src/jit/JitFrameIterator.h
js/src/jit/JitFrames.h
js/src/shell/js.cpp
js/src/vm/Debugger.cpp
js/src/vm/DebuggerMemory.cpp
js/src/vm/Runtime.cpp
js/src/vm/Runtime.h
js/src/vm/Stack-inl.h
js/src/vm/Stack.cpp
js/src/vm/Stack.h
--- 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;