summaryrefslogtreecommitdiffstats
path: root/js/src/devtools/rootAnalysis/loadCallgraph.js
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/devtools/rootAnalysis/loadCallgraph.js')
-rw-r--r--js/src/devtools/rootAnalysis/loadCallgraph.js203
1 files changed, 203 insertions, 0 deletions
diff --git a/js/src/devtools/rootAnalysis/loadCallgraph.js b/js/src/devtools/rootAnalysis/loadCallgraph.js
new file mode 100644
index 000000000..9ee8d7628
--- /dev/null
+++ b/js/src/devtools/rootAnalysis/loadCallgraph.js
@@ -0,0 +1,203 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- */
+
+"use strict";
+
+loadRelativeToScript('utility.js');
+
+// Functions come out of sixgill in the form "mangled|readable". The mangled
+// name is Truth. One mangled name might correspond to multiple readable names,
+// for multiple reasons, including (1) sixgill/gcc doesn't always qualify types
+// the same way or de-typedef the same amount; (2) sixgill's output treats
+// references and pointers the same, and so doesn't distinguish them, but C++
+// treats them as separate for overloading and linking; (3) (identical)
+// destructors sometimes have an int32 parameter, sometimes not.
+//
+// The readable names are useful because they're far more meaningful to the
+// user, and are what should show up in reports and questions to mrgiggles. At
+// least in most cases, it's fine to have the extra mangled name tacked onto
+// the beginning for these.
+//
+// The strategy used is to separate out the pieces whenever they are read in,
+// create a table mapping mangled names to (one of the) readable names, and
+// use the mangled names in all computation.
+//
+// Note that callgraph.txt uses a compressed representation -- each name is
+// mapped to an integer, and those integers are what is recorded in the edges.
+// But the integers depend on the full name, whereas the true edge should only
+// consider the mangled name. And some of the names encoded in callgraph.txt
+// are FieldCalls, not just function names.
+
+var readableNames = {}; // map from mangled name => list of readable names
+var mangledName = {}; // map from demangled names => mangled names. Could be eliminated.
+var calleeGraph = {}; // map from mangled => list of tuples of {'callee':mangled, 'suppressed':bool}
+var callerGraph = {}; // map from mangled => list of tuples of {'caller':mangled, 'suppressed':bool}
+var gcFunctions = {}; // map from mangled callee => reason
+var suppressedFunctions = {}; // set of mangled names (map from mangled name => true)
+var gcEdges = {};
+
+function addGCFunction(caller, reason)
+{
+ if (caller in suppressedFunctions)
+ return false;
+
+ if (ignoreGCFunction(caller))
+ return false;
+
+ if (!(caller in gcFunctions)) {
+ gcFunctions[caller] = reason;
+ return true;
+ }
+
+ return false;
+}
+
+function addCallEdge(caller, callee, suppressed)
+{
+ addToKeyedList(calleeGraph, caller, {callee:callee, suppressed:suppressed});
+ addToKeyedList(callerGraph, callee, {caller:caller, suppressed:suppressed});
+}
+
+// Map from identifier to full "mangled|readable" name. Or sometimes to a
+// Class.Field name.
+var functionNames = [""];
+
+// Map from identifier to mangled name (or to a Class.Field)
+var idToMangled = [""];
+
+function loadCallgraph(file)
+{
+ var suppressedFieldCalls = {};
+ var resolvedFunctions = {};
+
+ var numGCCalls = 0;
+
+ for (var line of readFileLines_gen(file)) {
+ line = line.replace(/\n/, "");
+
+ var match;
+ if (match = line.charAt(0) == "#" && /^\#(\d+) (.*)/.exec(line)) {
+ assert(functionNames.length == match[1]);
+ functionNames.push(match[2]);
+ var [ mangled, readable ] = splitFunction(match[2]);
+ if (mangled in readableNames)
+ readableNames[mangled].push(readable);
+ else
+ readableNames[mangled] = [ readable ];
+ mangledName[readable] = mangled;
+ idToMangled.push(mangled);
+ continue;
+ }
+ var suppressed = false;
+ if (line.indexOf("SUPPRESS_GC") != -1) {
+ match = /^(..)SUPPRESS_GC (.*)/.exec(line);
+ line = match[1] + match[2];
+ suppressed = true;
+ }
+ var tag = line.charAt(0);
+ if (match = tag == 'I' && /^I (\d+) VARIABLE ([^\,]*)/.exec(line)) {
+ var mangledCaller = idToMangled[match[1]];
+ var name = match[2];
+ if (!indirectCallCannotGC(functionNames[match[1]], name) && !suppressed)
+ addGCFunction(mangledCaller, "IndirectCall: " + name);
+ } else if (match = tag == 'F' && /^F (\d+) CLASS (.*?) FIELD (.*)/.exec(line)) {
+ var caller = idToMangled[match[1]];
+ var csu = match[2];
+ var fullfield = csu + "." + match[3];
+ if (suppressed)
+ suppressedFieldCalls[fullfield] = true;
+ else if (!fieldCallCannotGC(csu, fullfield))
+ addGCFunction(caller, "FieldCall: " + fullfield);
+ } else if (match = tag == 'D' && /^D (\d+) (\d+)/.exec(line)) {
+ var caller = idToMangled[match[1]];
+ var callee = idToMangled[match[2]];
+ addCallEdge(caller, callee, suppressed);
+ } else if (match = tag == 'R' && /^R (\d+) (\d+)/.exec(line)) {
+ var callerField = idToMangled[match[1]];
+ var callee = idToMangled[match[2]];
+ addCallEdge(callerField, callee, false);
+ resolvedFunctions[callerField] = true;
+ } else if (match = tag == 'T' && /^T (\d+) (.*)/.exec(line)) {
+ var mangled = idToMangled[match[1]];
+ var tag = match[2];
+ if (tag == 'GC Call') {
+ addGCFunction(mangled, "GC");
+ numGCCalls++;
+ }
+ }
+ }
+
+ // mess up the id <-> name correspondence. Also, we need to know if the
+ // functions even exist in the first place.)
+ for (var func of extraGCFunctions()) {
+ addGCFunction(func, "annotation");
+ }
+
+ // Initialize suppressedFunctions to the set of all functions, and the
+ // worklist to all toplevel callers.
+ var worklist = [];
+ 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.
+ var top = worklist.length;
+ while (top > 0) {
+ name = worklist[--top];
+ if (!(name in suppressedFunctions))
+ continue;
+ delete suppressedFunctions[name];
+ if (!(name in calleeGraph))
+ continue;
+ for (var entry of calleeGraph[name]) {
+ if (!entry.suppressed)
+ worklist[top++] = 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;
+ }
+
+ // Sanity check to make sure the callgraph has some functions annotated as
+ // GC Calls. This is mostly a check to be sure the earlier processing
+ // succeeded (as opposed to, say, running on empty xdb files because you
+ // didn't actually compile anything interesting.)
+ assert(numGCCalls > 0, "No GC functions found!");
+
+ // 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.shift();
+ 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;
+ }
+}