Bug 1479961 - Synthesize call edges from Interpret and EnterJit to all JSNatives, r=jonco
☠☠ backed out by 06ffaf98e1e9 ☠ ☠
authorSteve Fink <sfink@mozilla.com>
Tue, 24 Jul 2018 11:53:42 -0700
changeset 500018 c22b643cdf5168be413948a2437584426bfd8c06
parent 500017 c7b32ffa822e353b5479b6224194e3cdfd135e65
child 500019 66d022f720b698c8fced2c8628866445ddea6262
push id1864
push userffxbld-merge
push dateMon, 03 Dec 2018 15:51:40 +0000
treeherdermozilla-release@f040763d99ad [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjonco
bugs1479961
milestone64.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1479961 - Synthesize call edges from Interpret and EnterJit to all JSNatives, r=jonco
js/public/GCAnnotations.h
js/src/devtools/rootAnalysis/annotations.js
js/src/devtools/rootAnalysis/computeCallgraph.js
js/src/jit/Jit.cpp
js/src/vm/Interpreter.cpp
--- a/js/public/GCAnnotations.h
+++ b/js/public/GCAnnotations.h
@@ -50,23 +50,29 @@
 
 // Mark an RAII class as suppressing GC within its scope.
 # define JS_HAZ_GC_SUPPRESSED __attribute__((annotate("Suppress GC")))
 
 // Mark a function as one that can run script if called.  This obviously
 // subsumes JS_HAZ_GC_CALL, since anything that can run script can GC.`
 # define JS_HAZ_CAN_RUN_SCRIPT __attribute__((annotate("Can run script")))
 
+// Mark a function as able to call JSNatives. Otherwise, JSNatives don't show
+// up in the callgraph. This doesn't matter for the can-GC analysis, but it is
+// very nice for other uses of the callgraph.
+# define JS_HAZ_JSNATIVE_CALLER __attribute__((annotate("Calls JSNatives")))
+
 #else
 
 # define JS_HAZ_GC_THING
 # define JS_HAZ_GC_POINTER
 # define JS_HAZ_ROOTED
 # define JS_HAZ_GC_INVALIDATED
 # define JS_HAZ_ROOTED_BASE
 # define JS_HAZ_NON_GC_POINTER
 # define JS_HAZ_GC_CALL
 # define JS_HAZ_GC_SUPPRESSED
 # define JS_HAZ_CAN_RUN_SCRIPT
+# define JS_HAZ_JSNATIVE_CALLER
 
 #endif
 
 #endif /* js_GCAnnotations_h */
--- a/js/src/devtools/rootAnalysis/annotations.js
+++ b/js/src/devtools/rootAnalysis/annotations.js
@@ -422,8 +422,20 @@ function isOverridableField(initialCSU, 
 }
 
 function listNonGCPointers() {
     return [
         // Safe only because jsids are currently only made from pinned strings.
         'NPIdentifier',
     ];
 }
+
+function isJSNative(mangled)
+{
+    // _Z...E = function
+    // 9JSContext = JSContext*
+    // j = uint32
+    // PN2JS5Value = JS::Value*
+    //   P = pointer
+    //   N2JS = JS::
+    //   5Value = Value
+    return mangled.endsWith("P9JSContextjPN2JS5ValueE") && mangled.startsWith("_Z");
+}
--- a/js/src/devtools/rootAnalysis/computeCallgraph.js
+++ b/js/src/devtools/rootAnalysis/computeCallgraph.js
@@ -13,16 +13,19 @@ if (scriptArgs[0] == '--function') {
 var typeInfo_filename = scriptArgs[0] || "typeInfo.txt";
 var callgraphOut_filename = scriptArgs[1] || "callgraph.txt";
 
 var origOut = os.file.redirect(callgraphOut_filename);
 
 var memoized = new Map();
 var memoizedCount = 0;
 
+var JSNativeCaller = Object.create(null);
+var JSNatives = [];
+
 var unmangled2id = new Set();
 
 function getId(name)
 {
     let id = memoized.get(name);
     if (id !== undefined)
         return id;
 
@@ -97,18 +100,21 @@ function getAnnotations(functionName, bo
 // Scan through a function body, pulling out all annotations and calls and
 // recording them in callgraph.txt.
 function processBody(functionName, body)
 {
     if (!('PEdge' in body))
         return;
 
 
-    for (var tag of getAnnotations(functionName, body).values())
+    for (var tag of getAnnotations(functionName, body).values()) {
         print("T " + functionId(functionName) + " " + tag);
+        if (tag == "Calls JSNatives")
+            JSNativeCaller[functionName] = true;
+    }
 
     // Set of all callees that have been output so far, in order to suppress
     // repeated callgraph edges from being recorded. This uses a Map from
     // callees to limit sets, because we don't want a limited edge to prevent
     // an unlimited edge from being recorded later. (So an edge will be skipped
     // if it exists and is at least as limited as the previously seen edge.)
     //
     // Limit sets are implemented as integers interpreted as bitfields.
@@ -217,16 +223,18 @@ function process(functionName, functionB
     // This is slightly conservative in the case where they are *not*
     // identical, but that should be rare enough that we don't care.
     var markerPos = functionName.indexOf(internalMarker);
     if (markerPos > 0) {
         var inChargeXTor = functionName.replace(internalMarker, "");
         printOnce("D " + functionId(inChargeXTor) + " " + functionId(functionName));
     }
 
+    const [ mangled, unmangled ] = splitFunction(functionName);
+
     // Further note: from https://itanium-cxx-abi.github.io/cxx-abi/abi.html the
     // different kinds of constructors/destructors are:
     // C1	# complete object constructor
     // C2	# base object constructor
     // C3	# complete object allocating constructor
     // D0	# deleting destructor
     // D1	# complete object destructor
     // D2	# base object destructor
@@ -251,17 +259,16 @@ function process(functionName, functionB
     //                ::= D2 # base object (not-in-charge) destructor
     // <special-name> ::= C1   # complete object constructor
     //                ::= C2   # base object constructor
     //                ::= C3   # complete object allocating constructor
     //
     // Currently, allocating constructors are never used.
     //
     if (functionName.indexOf("C4") != -1) {
-        var [ mangled, unmangled ] = splitFunction(functionName);
         // E terminates the method name (and precedes the method parameters).
         // If eg "C4E" shows up in the mangled name for another reason, this
         // will create bogus edges in the callgraph. But it will affect little
         // and is somewhat difficult to avoid, so we will live with it.
         //
         // Another possibility! A templatized constructor will contain C4I...E
         // for template arguments.
         //
@@ -300,19 +307,32 @@ function process(functionName, functionB
         const not_in_charge_dtor = functionName.replace("(int32)", "()");
         const D0 = not_in_charge_dtor.replace("D4Ev", "D0Ev") + " [[deleting_dtor]]";
         const D1 = not_in_charge_dtor.replace("D4Ev", "D1Ev") + " [[complete_dtor]]";
         const D2 = not_in_charge_dtor.replace("D4Ev", "D2Ev") + " [[base_dtor]]";
         printOnce("D " + functionId(D0) + " " + functionId(D1));
         printOnce("D " + functionId(D1) + " " + functionId(D2));
         printOnce("D " + functionId(D2) + " " + functionId(functionName));
     }
+
+    if (isJSNative(mangled))
+        JSNatives.push(functionName);
+}
+
+function postprocess_callgraph() {
+    for (const caller of Object.keys(JSNativeCaller)) {
+        const caller_id = functionId(caller);
+        for (const callee of JSNatives)
+            printOnce(`D ${caller_id} ${functionId(callee)}`);
+    }
 }
 
 for (var nameIndex = minStream; nameIndex <= maxStream; nameIndex++) {
     var name = xdb.read_key(nameIndex);
     var data = xdb.read_entry(name);
     process(name.readString(), JSON.parse(data.readString()));
     xdb.free_string(name);
     xdb.free_string(data);
 }
 
+postprocess_callgraph();
+
 os.file.close(os.file.redirect(origOut));
--- a/js/src/jit/Jit.cpp
+++ b/js/src/jit/Jit.cpp
@@ -12,17 +12,17 @@
 #include "jit/JitRealm.h"
 #include "vm/Interpreter.h"
 
 #include "vm/Stack-inl.h"
 
 using namespace js;
 using namespace js::jit;
 
-static EnterJitStatus
+static EnterJitStatus JS_HAZ_JSNATIVE_CALLER
 EnterJit(JSContext* cx, RunState& state, uint8_t* code)
 {
     MOZ_ASSERT(state.script()->hasBaselineScript());
     MOZ_ASSERT(code);
     MOZ_ASSERT(IsBaselineEnabled(cx));
 
     if (!CheckRecursionLimit(cx)) {
         return EnterJitStatus::Error;
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -363,17 +363,17 @@ MaybeCreateThisForConstructor(JSContext*
 
     RootedObject callee(cx, &args.callee());
     RootedObject newTarget(cx, &args.newTarget().toObject());
     NewObjectKind newKind = createSingleton ? SingletonObject : GenericObject;
 
     return CreateThis(cx, callee, calleeScript, newTarget, newKind, args.mutableThisv());
 }
 
-static MOZ_NEVER_INLINE bool
+static MOZ_NEVER_INLINE JS_HAZ_JSNATIVE_CALLER bool
 Interpret(JSContext* cx, RunState& state);
 
 InterpreterFrame*
 InvokeState::pushInterpreterFrame(JSContext* cx)
 {
     return cx->interpreterStack().pushInvokeFrame(cx, args_, construct_);
 }
 
@@ -1987,17 +1987,17 @@ js::ReportInNotObjectError(JSContext* cx
                                  lbytes.get(), rbytes.get());
         return;
     }
 
     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_IN_NOT_OBJECT,
                               InformalValueTypeName(rref));
 }
 
-static MOZ_NEVER_INLINE bool
+static MOZ_NEVER_INLINE JS_HAZ_JSNATIVE_CALLER bool
 Interpret(JSContext* cx, RunState& state)
 {
 /*
  * Define macros for an interpreter loop. Opcode dispatch may be either by a
  * switch statement or by indirect goto (aka a threaded interpreter), depending
  * on compiler support.
  *
  * Threaded interpretation appears to be well-supported by GCC 3 and higher.