Bug 947400 - Make the analysis consider whether a virtual call can GC when all targets are known, r=bhackett
authorSteve Fink <sfink@mozilla.com>
Fri, 06 Dec 2013 17:00:48 -0800
changeset 175012 5c34885094e4f9e7c85a074da9d900d71e8ad21d
parent 175011 26fb7e60d2880a08278c1d6c99e0f5b98af8c044
child 175013 a55439a67e05cb3cb7076ac4cee6039102607c93
push id445
push userffxbld
push dateMon, 10 Mar 2014 22:05:19 +0000
treeherdermozilla-release@dc38b741b04e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbhackett
bugs947400
milestone28.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 947400 - Make the analysis consider whether a virtual call can GC when all targets are known, r=bhackett
js/src/devtools/rootAnalysis/computeCallgraph.js
js/src/devtools/rootAnalysis/computeGCFunctions.js
js/src/devtools/rootAnalysis/loadCallgraph.js
--- a/js/src/devtools/rootAnalysis/computeCallgraph.js
+++ b/js/src/devtools/rootAnalysis/computeCallgraph.js
@@ -5,16 +5,18 @@
 loadRelativeToScript('utility.js');
 loadRelativeToScript('annotations.js');
 loadRelativeToScript('CFG.js');
 
 var subclasses = {};
 var superclasses = {};
 var classFunctions = {};
 
+var fieldCallSeen = {};
+
 function addClassEntry(index, name, other)
 {
     if (!(name in index)) {
         index[name] = [other];
         return;
     }
 
     for (var entry of index[name]) {
@@ -140,19 +142,28 @@ function getCallees(edge)
                 if (suppressed[0]) {
                     // Field call known to not GC; mark it as suppressed so
                     // direct invocations will be ignored
                     callees.push({'kind': "field", 'csu': csuName, 'field': fieldName,
                                   'suppressed': true});
                 }
             }
             if (functions) {
-                // Known set of virtual call targets.
-                for (var name of functions)
+                // Known set of virtual call targets. Treat them as direct
+                // calls to all possible resolved types, but also record edges
+                // from this field call to each final callee. When the analysis
+                // is checking whether an edge can GC and it sees an unrooted
+                // pointer held live across this field call, it will know
+                // whether any of the direct callers can GC or not.
+                var targets = [];
+                for (var name of functions) {
                     callees.push({'kind': "direct", 'name': name});
+                    targets.push({'kind': "direct", 'name': name});
+                }
+                callees.push({'kind': "resolved-field", 'csu': csuName, 'field': fieldName, 'callees': targets});
             } else {
                 // Unknown set of call targets. Non-virtual field call,
                 // or virtual call on an nsISupports object.
                 callees.push({'kind': "field", 'csu': csuName, 'field': fieldName});
             }
         } else if (callee.Exp[0].Kind == "Var") {
             // indirect call through a variable.
             callees.push({'kind': "indirect", 'variable': callee.Exp[0].Variable.Name[0]});
@@ -195,16 +206,31 @@ function processBody(caller, body)
             if (callee.kind == 'direct') {
                 if (!(callee.name in seen)) {
                     seen[name] = true;
                     printOnce("D " + prologue + memo(callee.name));
                 }
             } else if (callee.kind == 'field') {
                 var { csu, field } = callee;
                 printOnce("F " + prologue + "CLASS " + csu + " FIELD " + field);
+            } else if (callee.kind == 'resolved-field') {
+                // Fully-resolved field call (usually a virtual method). Record
+                // the callgraph edges. Do not consider suppression, since it
+                // is local to this callsite and we are writing out a global
+                // record here.
+                //
+                // Any field call that does *not* have an R entry must be
+                // assumed to call anything.
+                var { csu, field, callees } = callee;
+                var fullFieldName = csu + "." + field;
+                if (!(fullFieldName in fieldCallSeen)) {
+                    fieldCallSeen[fullFieldName] = true;
+                    for (var target of callees)
+                        printOnce("R " + memo(fullFieldName) + " " + memo(target.name));
+                }
             } else if (callee.kind == 'indirect') {
                 printOnce("I " + prologue + "VARIABLE " + callee.variable);
             } else if (callee.kind == 'unknown') {
                 printOnce("I " + prologue + "VARIABLE UNKNOWN");
             } else {
                 printErr("invalid " + callee.kind + " callee");
                 debugger;
             }
--- a/js/src/devtools/rootAnalysis/computeGCFunctions.js
+++ b/js/src/devtools/rootAnalysis/computeGCFunctions.js
@@ -34,20 +34,20 @@ printErr("Writing " + gcFunctionsList_fi
 redirect(gcFunctionsList_filename);
 for (var name in gcFunctions) {
     print(name);
 }
 
 // gcEdges is a list of edges that can GC for more specific reasons than just
 // calling a function that is in gcFunctions.txt.
 //
-// Right now, it is unused. It was mean for ~AutoCompartment when it might wrap
-// an exception, but anything held live across ~AC will have to be held live
-// across the corresponding constructor (and hence the whole scope of the AC),
-// and in that case it'll be held live across whatever could create an
+// Right now, it is unused. It was meant for ~AutoCompartment when it might
+// wrap an exception, but anything held live across ~AC will have to be held
+// live across the corresponding constructor (and hence the whole scope of the
+// AC), and in that case it'll be held live across whatever could create an
 // exception within the AC scope. So ~AC edges are redundant. I will leave the
 // stub machinery here for now.
 printErr("Writing " + gcEdges_filename);
 redirect(gcEdges_filename);
 for (var block in gcEdges) {
   for (var edge in gcEdges[block]) {
       var func = gcEdges[block][edge];
     print([ block, edge, func ].join(" || "));
--- a/js/src/devtools/rootAnalysis/loadCallgraph.js
+++ b/js/src/devtools/rootAnalysis/loadCallgraph.js
@@ -35,16 +35,17 @@ function addCallEdge(caller, callee, sup
     callerGraph[callee].push({caller:caller, suppressed:suppressed});
 }
 
 var functionNames = [""];
 
 function loadCallgraph(file)
 {
     var suppressedFieldCalls = {};
+    var resolvedFunctions = {};
 
     var textLines = snarf(file).split('\n');
     for (var line of textLines) {
         var match;
         if (match = /^\#(\d+) (.*)/.exec(line)) {
             assert(functionNames.length == match[1]);
             functionNames.push(match[2]);
             continue;
@@ -67,66 +68,89 @@ function loadCallgraph(file)
             if (suppressed)
                 suppressedFieldCalls[fullfield] = true;
             else if (!fieldCallCannotGC(csu, fullfield))
                 addGCFunction(caller, "FieldCall: " + fullfield);
         } else if (match = /^D (\d+) (\d+)/.exec(line)) {
             var caller = functionNames[match[1]];
             var callee = functionNames[match[2]];
             addCallEdge(caller, callee, suppressed);
+        } else if (match = /^R (\d+) (\d+)/.exec(line)) {
+            var callerField = functionNames[match[1]];
+            var callee = functionNames[match[2]];
+            addCallEdge(callerField, callee, false);
+            resolvedFunctions[callerField] = true;
         }
     }
 
+    // Initialize suppressedFunctions to the set of all functions, and the
+    // worklist to all toplevel callers.
     var worklist = [];
-    for (var name in callerGraph)
-        suppressedFunctions[name] = true;
-    for (var name in calleeGraph) {
-        if (!(name in callerGraph)) {
-            suppressedFunctions[name] = true;
-            worklist.push(name);
+    for (var callee in callerGraph)
+        suppressedFunctions[callee] = true;
+    for (var caller in calleeGraph) {
+        if (!(caller in callerGraph)) {
+            suppressedFunctions[caller] = true;
+            worklist.push(caller);
         }
     }
+
+    // Find all functions reachable via an unsuppressed call chain, and remove
+    // them from the suppressedFunctions set. Everything remaining is only
+    // reachable when GC is suppressed.
     while (worklist.length) {
         name = worklist.pop();
         if (shouldSuppressGC(name))
             continue;
         if (!(name in suppressedFunctions))
             continue;
         delete suppressedFunctions[name];
         if (!(name in calleeGraph))
             continue;
         for (var entry of calleeGraph[name]) {
             if (!entry.suppressed)
                 worklist.push(entry.callee);
         }
     }
 
+    // Such functions are known to not GC.
     for (var name in gcFunctions) {
         if (name in suppressedFunctions)
             delete gcFunctions[name];
     }
 
     for (var name in suppressedFieldCalls) {
         suppressedFunctions[name] = true;
     }
 
     for (var gcName of [ 'jsgc.cpp:void Collect(JSRuntime*, uint8, int64, uint32, uint32)',
                          'void js::MinorGC(JSRuntime*, uint32)' ])
     {
         assert(gcName in callerGraph);
         addGCFunction(gcName, "GC");
     }
 
+    // Initialize the worklist to all known gcFunctions.
     var worklist = [];
     for (var name in gcFunctions)
         worklist.push(name);
 
+    // Recursively find all callers and add them to the set of gcFunctions.
     while (worklist.length) {
         name = worklist.pop();
         assert(name in gcFunctions);
         if (!(name in callerGraph))
             continue;
         for (var entry of callerGraph[name]) {
             if (!entry.suppressed && addGCFunction(entry.caller, name))
                 worklist.push(entry.caller);
         }
     }
+
+    // Any field call that has been resolved to all possible callees can be
+    // trusted to not GC if all of those callees are known to not GC.
+    for (var name in resolvedFunctions) {
+        if (!(name in gcFunctions)) {
+            suppressedFunctions[name] = true;
+            printErr("Adding " + name);
+        }
+    }
 }