|
1 /* -*- Mode: Javascript; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
|
2 |
|
3 "use strict"; |
|
4 |
|
5 loadRelativeToScript('utility.js'); |
|
6 |
|
7 // Functions come out of sixgill in the form "mangled|readable". The mangled |
|
8 // name is Truth. One mangled name might correspond to multiple readable names, |
|
9 // for multiple reasons, including (1) sixgill/gcc doesn't always qualify types |
|
10 // the same way or de-typedef the same amount; (2) sixgill's output treats |
|
11 // references and pointers the same, and so doesn't distinguish them, but C++ |
|
12 // treats them as separate for overloading and linking; (3) (identical) |
|
13 // destructors sometimes have an int32 parameter, sometimes not. |
|
14 // |
|
15 // The readable names are useful because they're far more meaningful to the |
|
16 // user, and are what should show up in reports and questions to mrgiggles. At |
|
17 // least in most cases, it's fine to have the extra mangled name tacked onto |
|
18 // the beginning for these. |
|
19 // |
|
20 // The strategy used is to separate out the pieces whenever they are read in, |
|
21 // create a table mapping mangled names to (one of the) readable names, and |
|
22 // use the mangled names in all computation. |
|
23 // |
|
24 // Note that callgraph.txt uses a compressed representation -- each name is |
|
25 // mapped to an integer, and those integers are what is recorded in the edges. |
|
26 // But the integers depend on the full name, whereas the true edge should only |
|
27 // consider the mangled name. And some of the names encoded in callgraph.txt |
|
28 // are FieldCalls, not just function names. |
|
29 |
|
30 var readableNames = {}; // map from mangled name => list of readable names |
|
31 var mangledName = {}; // map from demangled names => mangled names. Could be eliminated. |
|
32 var calleeGraph = {}; // map from mangled => list of tuples of {'callee':mangled, 'suppressed':bool} |
|
33 var callerGraph = {}; // map from mangled => list of tuples of {'caller':mangled, 'suppressed':bool} |
|
34 var gcFunctions = {}; // map from mangled callee => reason |
|
35 var suppressedFunctions = {}; // set of mangled names (map from mangled name => true) |
|
36 var gcEdges = {}; |
|
37 |
|
38 function addGCFunction(caller, reason) |
|
39 { |
|
40 if (caller in suppressedFunctions) |
|
41 return false; |
|
42 |
|
43 if (ignoreGCFunction(caller)) |
|
44 return false; |
|
45 |
|
46 if (!(caller in gcFunctions)) { |
|
47 gcFunctions[caller] = reason; |
|
48 return true; |
|
49 } |
|
50 |
|
51 return false; |
|
52 } |
|
53 |
|
54 function addCallEdge(caller, callee, suppressed) |
|
55 { |
|
56 if (!(caller in calleeGraph)) |
|
57 calleeGraph[caller] = []; |
|
58 calleeGraph[caller].push({callee:callee, suppressed:suppressed}); |
|
59 |
|
60 if (!(callee in callerGraph)) |
|
61 callerGraph[callee] = []; |
|
62 callerGraph[callee].push({caller:caller, suppressed:suppressed}); |
|
63 } |
|
64 |
|
65 // Map from identifier to full "mangled|readable" name. Or sometimes to a |
|
66 // Class.Field name. |
|
67 var functionNames = [""]; |
|
68 |
|
69 // Map from identifier to mangled name (or to a Class.Field) |
|
70 var idToMangled = [""]; |
|
71 |
|
72 function loadCallgraph(file) |
|
73 { |
|
74 var suppressedFieldCalls = {}; |
|
75 var resolvedFunctions = {}; |
|
76 |
|
77 var textLines = snarf(file).split('\n'); |
|
78 for (var line of textLines) { |
|
79 var match; |
|
80 if (match = line.charAt(0) == "#" && /^\#(\d+) (.*)/.exec(line)) { |
|
81 assert(functionNames.length == match[1]); |
|
82 functionNames.push(match[2]); |
|
83 var [ mangled, readable ] = splitFunction(match[2]); |
|
84 if (mangled in readableNames) |
|
85 readableNames[mangled].push(readable); |
|
86 else |
|
87 readableNames[mangled] = [ readable ]; |
|
88 mangledName[readable] = mangled; |
|
89 idToMangled.push(mangled); |
|
90 continue; |
|
91 } |
|
92 var suppressed = false; |
|
93 if (line.indexOf("SUPPRESS_GC") != -1) { |
|
94 match = /^(..)SUPPRESS_GC (.*)/.exec(line); |
|
95 line = match[1] + match[2]; |
|
96 suppressed = true; |
|
97 } |
|
98 var tag = line.charAt(0); |
|
99 if (match = tag == 'I' && /^I (\d+) VARIABLE ([^\,]*)/.exec(line)) { |
|
100 var mangledCaller = idToMangled[match[1]]; |
|
101 var name = match[2]; |
|
102 if (!indirectCallCannotGC(functionNames[match[1]], name) && !suppressed) |
|
103 addGCFunction(mangledCaller, "IndirectCall: " + name); |
|
104 } else if (match = tag == 'F' && /^F (\d+) CLASS (.*?) FIELD (.*)/.exec(line)) { |
|
105 var caller = idToMangled[match[1]]; |
|
106 var csu = match[2]; |
|
107 var fullfield = csu + "." + match[3]; |
|
108 if (suppressed) |
|
109 suppressedFieldCalls[fullfield] = true; |
|
110 else if (!fieldCallCannotGC(csu, fullfield)) |
|
111 addGCFunction(caller, "FieldCall: " + fullfield); |
|
112 } else if (match = tag == 'D' && /^D (\d+) (\d+)/.exec(line)) { |
|
113 var caller = idToMangled[match[1]]; |
|
114 var callee = idToMangled[match[2]]; |
|
115 addCallEdge(caller, callee, suppressed); |
|
116 } else if (match = tag == 'R' && /^R (\d+) (\d+)/.exec(line)) { |
|
117 var callerField = idToMangled[match[1]]; |
|
118 var callee = idToMangled[match[2]]; |
|
119 addCallEdge(callerField, callee, false); |
|
120 resolvedFunctions[callerField] = true; |
|
121 } |
|
122 } |
|
123 |
|
124 // Initialize suppressedFunctions to the set of all functions, and the |
|
125 // worklist to all toplevel callers. |
|
126 var worklist = []; |
|
127 for (var callee in callerGraph) |
|
128 suppressedFunctions[callee] = true; |
|
129 for (var caller in calleeGraph) { |
|
130 if (!(caller in callerGraph)) { |
|
131 suppressedFunctions[caller] = true; |
|
132 worklist.push(caller); |
|
133 } |
|
134 } |
|
135 |
|
136 // Find all functions reachable via an unsuppressed call chain, and remove |
|
137 // them from the suppressedFunctions set. Everything remaining is only |
|
138 // reachable when GC is suppressed. |
|
139 var top = worklist.length; |
|
140 while (top > 0) { |
|
141 name = worklist[--top]; |
|
142 if (!(name in suppressedFunctions)) |
|
143 continue; |
|
144 delete suppressedFunctions[name]; |
|
145 if (!(name in calleeGraph)) |
|
146 continue; |
|
147 for (var entry of calleeGraph[name]) { |
|
148 if (!entry.suppressed) |
|
149 worklist[top++] = entry.callee; |
|
150 } |
|
151 } |
|
152 |
|
153 // Such functions are known to not GC. |
|
154 for (var name in gcFunctions) { |
|
155 if (name in suppressedFunctions) |
|
156 delete gcFunctions[name]; |
|
157 } |
|
158 |
|
159 for (var name in suppressedFieldCalls) { |
|
160 suppressedFunctions[name] = true; |
|
161 } |
|
162 |
|
163 for (var gcName of [ 'jsgc.cpp:void Collect(JSRuntime*, uint8, int64, uint32, uint32)', |
|
164 'void js::MinorGC(JSRuntime*, uint32)' ]) |
|
165 { |
|
166 assert(gcName in mangledName); |
|
167 addGCFunction(mangledName[gcName], "GC"); |
|
168 } |
|
169 |
|
170 // Initialize the worklist to all known gcFunctions. |
|
171 var worklist = []; |
|
172 for (var name in gcFunctions) |
|
173 worklist.push(name); |
|
174 |
|
175 // Recursively find all callers and add them to the set of gcFunctions. |
|
176 while (worklist.length) { |
|
177 name = worklist.shift(); |
|
178 assert(name in gcFunctions); |
|
179 if (!(name in callerGraph)) |
|
180 continue; |
|
181 for (var entry of callerGraph[name]) { |
|
182 if (!entry.suppressed && addGCFunction(entry.caller, name)) |
|
183 worklist.push(entry.caller); |
|
184 } |
|
185 } |
|
186 |
|
187 // Any field call that has been resolved to all possible callees can be |
|
188 // trusted to not GC if all of those callees are known to not GC. |
|
189 for (var name in resolvedFunctions) { |
|
190 if (!(name in gcFunctions)) |
|
191 suppressedFunctions[name] = true; |
|
192 } |
|
193 } |