OLD | NEW |
---|---|
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 library dump_info; | 5 library dump_info; |
6 | 6 |
7 import 'dart:convert' show | 7 import 'dart:convert' show |
8 HtmlEscape, | 8 HtmlEscape, |
9 JsonEncoder, | 9 JsonEncoder, |
10 StringConversionSink, | 10 StringConversionSink, |
11 ChunkedConversionSink; | 11 ChunkedConversionSink; |
12 | 12 |
13 import 'elements/elements.dart'; | 13 import 'elements/elements.dart'; |
14 import 'elements/visitor.dart'; | 14 import 'elements/visitor.dart'; |
15 import 'dart2jslib.dart' show | 15 import 'dart2jslib.dart' show |
16 Compiler, | 16 Compiler, |
17 CompilerTask, | 17 CompilerTask, |
18 CodeBuffer; | 18 CodeBuffer; |
19 import 'dart_types.dart' show DartType; | 19 import 'dart_types.dart' show DartType; |
20 import 'types/types.dart' show TypeMask; | 20 import 'types/types.dart' show TypeMask; |
21 import 'util/util.dart' show modifiersToString; | 21 import 'util/util.dart' show modifiersToString; |
22 import 'deferred_load.dart' show OutputUnit; | 22 import 'deferred_load.dart' show OutputUnit; |
23 import 'js_backend/js_backend.dart' show JavaScriptBackend; | 23 import 'js_backend/js_backend.dart' show JavaScriptBackend; |
24 import 'js/js.dart' as jsAst; | 24 import 'js/js.dart' as jsAst; |
25 | 25 import 'compilation_info.dart' show CompilationInformation; |
26 // TODO (sigurdm): A search function. | |
27 // TODO (sigurdm): Output size of classes. | |
28 // TODO (sigurdm): Print that we dumped the HTML-file. | |
29 // TODO (sigurdm): Include why a given element was included in the output. | |
30 // TODO (sigurdm): Include how much output grew because of mirror support. | |
31 // TODO (sigurdm): Write each function with parameter names. | |
32 // TODO (sigurdm): Write how much space the boilerplate takes. | |
33 // TODO (sigurdm): Include javascript names of entities in the output. | |
34 | |
35 const List<String> COLORS = const [ | |
36 "#fff", | |
37 "#8dd3c7", | |
38 "#ffffb3", | |
39 "#bebada", | |
40 "#fb8072", | |
41 "#80b1d3", | |
42 "#fdb462", | |
43 "#b3de69", | |
44 "#fccde5", | |
45 "#d9d9d9", | |
46 "#bc80bd", | |
47 "#ccebc5", | |
48 "#ffed6f"]; | |
49 | 26 |
50 class CodeSizeCounter { | 27 class CodeSizeCounter { |
51 final Map<Element, int> generatedSize = new Map<Element, int>(); | 28 final Map<Element, int> generatedSize = new Map<Element, int>(); |
52 | 29 |
53 int getGeneratedSizeOf(Element element) { | 30 int getGeneratedSizeOf(Element element) { |
54 int result = generatedSize[element]; | 31 int result = generatedSize[element]; |
55 return result == null ? 0 : result; | 32 return result == null ? 0: result; |
56 } | 33 } |
57 | 34 |
58 void countCode(Element element, int added) { | 35 void countCode(Element element, int added) { |
59 int before = generatedSize.putIfAbsent(element, () => 0); | 36 int before = generatedSize.putIfAbsent(element, () => 0); |
60 generatedSize[element] = before + added; | 37 generatedSize[element] = before + added; |
61 } | 38 } |
62 } | 39 } |
63 | 40 |
64 tag(String element) { | 41 /// Maps elements to an id. Supports lookups in |
65 return (String content, {String cls}) { | 42 /// both directions. |
66 String classString = cls == null ? '' : ' class="$cls"'; | 43 class ElementMapper { |
67 return '<$element$classString>$content</$element>'; | 44 Map<int, Element> _idToElement = {}; |
68 }; | 45 Map<Element, int> _elementToId = {}; |
46 int _idCounter = 0; | |
47 String name; | |
48 | |
49 ElementMapper(this.name); | |
50 | |
51 String add(Element e) { | |
52 if (_elementToId.containsKey(e)) { | |
53 return name + "/${_elementToId[e]}"; | |
54 } | |
55 | |
56 _idToElement[_idCounter] = e; | |
57 _elementToId[e] = _idCounter; | |
58 _idCounter += 1; | |
59 return name + "/${_idCounter - 1}"; | |
60 } | |
69 } | 61 } |
70 | 62 |
71 var div = tag('div'); | 63 class DividedElementMapper { |
72 var span = tag('span'); | 64 // Mappers for specific kinds of elements. |
73 var code = tag('code'); | 65 ElementMapper _library = new ElementMapper('library'); |
74 var h2 = tag('h2'); | 66 ElementMapper _typedef = new ElementMapper('typedef'); |
75 | 67 ElementMapper _field = new ElementMapper('field'); |
76 var esc = const HtmlEscape().convert; | 68 ElementMapper _class = new ElementMapper('class'); |
77 | 69 ElementMapper _function = new ElementMapper('function'); |
78 String sizeDescription(int size, ProgramInfo programInfo) { | 70 |
79 if (size == null) { | 71 // Convert this database of elements into JSON for rendering |
80 return ''; | 72 Map<String, dynamic> _toJson(ElementToJsonVisitor elementToJson) { |
81 } | 73 Map<String, dynamic> json = {}; |
82 return span(span(size.toString(), cls: 'value') + | 74 var m = [_library, _typedef, _field, _class, _function]; |
83 ' bytes (${size * 100 ~/ programInfo.size}%)', cls: 'size'); | 75 for (ElementMapper mapper in m) { |
76 Map<String, dynamic> innerMapper = {}; | |
77 mapper._idToElement.forEach((k, v) { | |
78 // All these elements are already cached in the | |
79 // jsonCache, so this is just an access. | |
80 var elementJson = elementToJson.process(v); | |
81 if (elementJson != null) { | |
82 innerMapper["$k"] = elementJson; | |
83 } | |
84 }); | |
85 json[mapper.name] = innerMapper; | |
86 } | |
87 return json; | |
88 } | |
84 } | 89 } |
85 | 90 |
86 String sizePercent(int size, ProgramInfo programInfo) { | 91 class ElementToJsonVisitor extends ElementVisitor<Map<String, dynamic>> { |
87 if (size == null) { | 92 DividedElementMapper mapper = new DividedElementMapper(); |
88 return "0.000%"; | 93 Compiler compiler; |
89 } else { | 94 |
90 return (100 * size / programInfo.size).toStringAsFixed(3) + "%"; | 95 CompilationInformation compilationInfo; |
91 } | 96 |
92 } | 97 Map<Element, Map<String, dynamic>> jsonCache = {}; |
93 | 98 Map<Element, jsAst.Expression> codeCache; |
94 /// An [InfoNode] holds information about a part the program. | 99 |
95 abstract class InfoNode { | 100 int programSize; |
96 String get name; | 101 DateTime compilationMoment; |
97 | 102 String dart2jsVersion; |
98 int get size; | 103 Duration compilationDuration; |
99 | 104 Duration dumpInfoDuration; |
100 void emitHtml(ProgramInfo programInfo, StringSink buffer, | 105 |
101 [String indentation = '']); | 106 ElementToJsonVisitor(Compiler compiler) { |
102 | 107 this.compiler = compiler; |
ahe
2014/08/18 15:31:14
Please use Dart constructor syntax. For example:
Ty Overby (Google)
2014/08/19 19:44:13
Done.
| |
103 Map<String, dynamic> toJson(ProgramInfo programInfo); | 108 this.compilationInfo = compiler.enqueuer.codegen.compilationInfo; |
104 } | 109 |
105 | 110 programSize = compiler.assembledCode.length; |
106 /// An [ElementNode] holds information about an [Element] | 111 compilationMoment = new DateTime.now(); |
107 class ElementInfoNode implements InfoNode { | 112 dart2jsVersion = compiler.hasBuildId ? compiler.buildId : null; |
108 /// The name of the represented [Element]. | 113 compilationDuration = compiler.totalCompileTime.elapsed; |
109 final String name; | 114 |
110 | 115 for (var library in compiler.libraryLoader.libraries.toList()) { |
111 /// The kind of the [Element] represented. This is presented to the | 116 library.accept(this); |
112 /// user, so it might be more specific than [element.kind]. | 117 } |
113 final String kind; | 118 |
114 | 119 dumpInfoDuration = new DateTime.now().difference(compilationMoment); |
ahe
2014/08/18 15:31:14
We have CompilerTask to measure timing.
Ty Overby (Google)
2014/08/19 19:44:13
I can't seem to find out how to extract time measu
ahe
2014/08/20 08:33:19
Create a subclass of CompilerTask and add it to co
| |
115 /// The static type of the represented [Element]. | 120 } |
116 /// [:null:] if this kind of element has no type. | 121 |
117 final String type; | 122 // If keeping the element is in question (like if a function has a size |
118 | 123 // of zero), only keep it if it holds dependencies to elsewhere. |
119 /// Any extra information to display about the represented [Element]. | 124 bool shouldKeep(Element element) { |
120 final String extra; | 125 return compilationInfo.relations['addsToWorklist'].containsKey(element) || |
121 | 126 compilationInfo.relations['enqueues'].containsKey(element); |
122 /// A textual description of the modifiers (such as "static", "abstract") of | 127 } |
123 /// the represented [Element]. | 128 |
124 final String modifiers; | 129 Map<String, dynamic> toJson() { |
125 | 130 return mapper._toJson(this); |
126 /// Describes how many bytes the code for the represented [Element] takes up | 131 } |
127 /// in the output. | 132 |
128 final int size; | 133 // Memoization of the JSON creating process. |
129 | 134 Map<String, dynamic> process(Element element) { |
130 /// Subnodes containing more detailed information about the represented | 135 return jsonCache.putIfAbsent(element, () => element.accept(this)); |
131 /// [Element], and its members. | 136 } |
132 List<InfoNode> contents; | 137 |
133 | 138 Map<String, dynamic> visitElement(Element element) { |
134 /// Subnodes containing more detailed information about the represented | 139 return null; |
135 /// [Element], and its members. | 140 } |
136 int outputUnitId; | 141 |
137 | 142 Map<String, dynamic> visitConstructorBodyElement(ConstructorBodyElement e) { |
138 ElementInfoNode({this.name: "", | 143 return visitFunctionElement(e.constructor); |
139 this.kind: "", | 144 } |
140 this.type, | 145 |
141 this.modifiers: "", | 146 Map<String, dynamic> visitLibraryElement(LibraryElement element) { |
142 this.size, | 147 var id = mapper._library.add(element); |
143 this.contents, | 148 List<String> children = <String>[]; |
144 this.extra: "", | 149 |
145 this.outputUnitId}); | 150 String libname = element.getLibraryName(); |
146 | 151 libname = libname == "" ? "<unnamed>" : libname; |
147 Map<String, dynamic> toJson(ProgramInfo programInfo) { | 152 |
148 Map<String, dynamic> json = <String, dynamic>{ | 153 int size = |
149 'kind': this.kind, | 154 compiler.dumpInfoTask.codeSizeCounter.getGeneratedSizeOf(element); |
150 'modifiers': this.modifiers, | 155 |
151 'name': this.name, | 156 LibraryElement contentsOfLibrary = element.isPatched |
152 'type': this.type, | 157 ? element.patch : element; |
153 'size': this.size, | 158 contentsOfLibrary.forEachLocalMember((Element member) { |
154 'sizePercent': sizePercent(this.size, programInfo), | 159 Map<String, dynamic> childJson = this.process(member); |
155 'extra': this.extra | 160 if (childJson == null) return; |
161 children.add(childJson['id']); | |
162 }); | |
163 | |
164 if (children.length == 0 && !shouldKeep(element)) { | |
165 return null; | |
166 } | |
167 | |
168 return { | |
169 'kind': 'library', | |
170 'name': libname, | |
171 'size': size, | |
172 'id': id, | |
173 'children': children | |
156 }; | 174 }; |
157 | 175 } |
158 if (this.contents != null) { | 176 |
159 json['children'] = | 177 Map<String, dynamic> visitTypedefElement(TypedefElement element) { |
160 this.contents.map((c) => c.toJson(programInfo)).toList(); | 178 String id = mapper._typedef.add(element); |
161 } | 179 return element.alias == null |
162 | 180 ? null |
163 return json; | 181 : { |
164 } | 182 'id': id, |
165 | 183 'type': element.alias.toString(), |
166 void emitHtml(ProgramInfo programInfo, StringSink buffer, | 184 'kind': 'typedef', |
167 [String indentation = '']) { | 185 'name': element.name |
168 String kindString = span(esc(kind), cls: 'kind'); | 186 }; |
169 String modifiersString = span(esc(modifiers), cls: "modifiers"); | 187 } |
170 | 188 |
171 String nameString = span(esc(name), cls: 'name'); | 189 Map<String, dynamic> visitFieldElement(FieldElement element) { |
172 String typeString = type == null | 190 String id = mapper._field.add(element); |
173 ? '' | 191 List<String> children = []; |
174 : span('/* ' + esc(type) + ' */', cls: 'type'); | 192 CodeBuffer emittedCode = compiler.dumpInfoTask.codeOf(element); |
175 String extraString = span(esc(extra), cls: 'type'); | 193 |
176 String describe = [ | 194 // If a field has an empty inferred type it is never used. |
177 kindString, | 195 TypeMask inferredType = |
178 typeString, | 196 compiler.typesTask.getGuaranteedTypeOfElement(element); |
179 modifiersString, | 197 if (inferredType == null || inferredType.isEmpty || element.isConst) { |
180 nameString, | 198 return null; |
181 sizeDescription(size, programInfo), | 199 } |
182 extraString].join(' '); | 200 |
183 | 201 int size = 0; |
184 if (contents != null) { | 202 String code; |
185 String outputUnitClass = outputUnitId == null | 203 |
186 ? "" | 204 if (emittedCode != null) { |
187 : " outputUnit${outputUnitId % COLORS.length}"; | 205 size += emittedCode.length; |
188 buffer.write(indentation); | 206 code = emittedCode.getText(); |
189 buffer.write('<div class="container$outputUnitClass">\n'); | 207 } |
190 buffer.write('$indentation '); | 208 |
191 buffer.write(div('+$describe', cls: "details")); | 209 for (Element closure in element.nestedClosures) { |
192 buffer.write('\n'); | 210 var childJson = this.process(closure); |
193 buffer.write('$indentation <div class="contents">'); | 211 if (childJson != null) { |
194 if (contents.isEmpty) { | 212 children.add(childJson['id']); |
195 buffer.write('No members</div>'); | 213 if (childJson.containsKey('size')) { |
196 } else { | 214 size += childJson['size']; |
197 buffer.write('\n'); | |
198 for (InfoNode subElementDescription in contents) { | |
199 subElementDescription.emitHtml(programInfo, buffer, | |
200 indentation + ' '); | |
201 } | 215 } |
202 buffer.write("\n$indentation </div>"); | |
203 } | 216 } |
204 buffer.write("\n$indentation</div>\n"); | 217 } |
205 } else { | 218 |
206 buffer.writeln(div('$describe', cls: "element")); | 219 return { |
207 } | 220 'id': id, |
208 } | 221 'kind': 'field', |
209 } | 222 'name': element.name, |
210 | 223 'children': children, |
211 /// A [CodeInfoNode] holds information about a piece of code. | 224 'size': size, |
212 class CodeInfoNode implements InfoNode { | 225 'code': code |
213 /// A short description of the code. | |
214 final String description; | |
215 | |
216 final String generatedCode; | |
217 | |
218 get size => generatedCode.length; | |
219 | |
220 get name => ""; | |
221 | |
222 CodeInfoNode({this.description: "", this.generatedCode}); | |
223 | |
224 void emitHtml(ProgramInfo programInfo, StringBuffer buffer, | |
225 [String indentation = '']) { | |
226 buffer.write(indentation); | |
227 buffer.write(div(description + ' ' + | |
228 sizeDescription(generatedCode.length, programInfo), | |
229 cls: 'kind') + | |
230 code(esc(generatedCode))); | |
231 buffer.write('\n'); | |
232 } | |
233 | |
234 Map<String, dynamic> toJson(ProgramInfo programInfo) { | |
235 return <String, dynamic>{ | |
236 'kind': 'code', | |
237 'description': description, | |
238 'code': generatedCode, | |
239 'size': generatedCode.length, | |
240 'sizePercent': sizePercent(generatedCode.length, programInfo) | |
241 }; | 226 }; |
242 } | 227 } |
243 } | 228 |
244 | 229 Map<String, dynamic> visitClassElement(ClassElement element) { |
245 /// Instances represent information inferred about the program such as | 230 String id = mapper._class.add(element); |
246 /// inferred type information or inferred side effects. | 231 List<String> children = []; |
247 class InferredInfoNode implements InfoNode { | 232 |
248 /// Text describing the represented information. | 233 int size = compiler.dumpInfoTask.codeSizeCounter.getGeneratedSizeOf(element) ; |
249 final String description; | 234 |
250 | 235 // Omit element if it is not needed. |
251 /// The name of the entity this information is inferred about (for example the | |
252 /// name of a parameter). | |
253 final String name; | |
254 | |
255 /// The inferred type/side effect. | |
256 final String type; | |
257 | |
258 get size => 0; | |
259 | |
260 InferredInfoNode({this.name: "", this.description, this.type}); | |
261 | |
262 Map<String, dynamic> toJson(ProgramInfo programInfo) { | |
263 return <String, dynamic>{ | |
264 'kind': 'inferred', | |
265 'name': name, | |
266 'type': type, | |
267 'desc': description | |
268 }; | |
269 } | |
270 | |
271 void emitHtml(ProgramInfo programInfo, StringBuffer buffer, | |
272 [String indentation = '']) { | |
273 buffer.write(indentation); | |
274 buffer.write( | |
275 div('${span("Inferred " + description, cls: "kind")} ' | |
276 '${span(esc(name), cls: "name")} ' | |
277 '${span(esc(type), cls: "type")} ', | |
278 cls: "attr")); | |
279 buffer.write('\n'); | |
280 } | |
281 } | |
282 | |
283 /// Instances represent information about a program. | |
284 class ProgramInfo { | |
285 /// A list of all the libraries in the program to show information about. | |
286 final List<InfoNode> libraries; | |
287 | |
288 /// The size of the whole program in bytes. | |
289 final int size; | |
290 | |
291 /// The time the compilation took place. | |
292 final DateTime compilationMoment; | |
293 | |
294 /// The time the compilation took to complete. | |
295 final Duration compilationDuration; | |
296 | |
297 /// The version of dart2js used to compile the program. | |
298 final String dart2jsVersion; | |
299 | |
300 final Map<OutputUnit, int> outputUnitNumbering; | |
301 | |
302 ProgramInfo({this.libraries, | |
303 this.size, | |
304 this.compilationMoment, | |
305 this.compilationDuration, | |
306 this.dart2jsVersion, | |
307 this.outputUnitNumbering: null}); | |
308 | |
309 Map<String, dynamic> toJson() { | |
310 return <String, dynamic>{ | |
311 'program_size': size, | |
312 'compile_time': compilationMoment.toString(), | |
313 'compile_duration': compilationDuration.toString(), | |
314 'dart2js_version': dart2jsVersion | |
315 }; | |
316 } | |
317 } | |
318 | |
319 class InfoDumpVisitor extends ElementVisitor<InfoNode> { | |
320 final Compiler compiler; | |
321 | |
322 /// Contains the elements visited on the path from the library to here. | |
323 final List<Element> stack = new List<Element>(); | |
324 | |
325 final Map<OutputUnit, int> outputUnitNumbering = new Map<OutputUnit, int>(); | |
326 | |
327 Element get currentElement => stack.last; | |
328 | |
329 InfoDumpVisitor(Compiler this.compiler); | |
330 | |
331 ProgramInfo collectDumpInfo() { | |
332 JavaScriptBackend backend = compiler.backend; | 236 JavaScriptBackend backend = compiler.backend; |
333 | 237 if (!backend.emitter.neededClasses.contains(element)) return null; |
334 int counter = 0; | 238 Map<String, dynamic> modifiers = { 'abstract': element.isAbstract }; |
335 for (OutputUnit outputUnit in compiler.deferredLoadTask.allOutputUnits) { | 239 |
336 outputUnitNumbering[outputUnit] = counter; | 240 element.forEachLocalMember((Element member) { |
337 counter += 1; | 241 Map<String, dynamic> childJson = this.process(member); |
338 } | 242 if (childJson != null) { |
339 | 243 children.add(childJson['id']); |
340 List<LibraryElement> sortedLibraries = | |
341 compiler.libraryLoader.libraries.toList(); | |
342 sortedLibraries.sort((LibraryElement l1, LibraryElement l2) { | |
343 if (l1.isPlatformLibrary && !l2.isPlatformLibrary) { | |
344 return 1; | |
345 } else if (!l1.isPlatformLibrary && l2.isPlatformLibrary) { | |
346 return -1; | |
347 } | |
348 return l1.getLibraryName().compareTo(l2.getLibraryName()); | |
349 }); | |
350 | |
351 List<InfoNode> libraryInfos = new List<InfoNode>(); | |
352 libraryInfos.addAll(sortedLibraries | |
353 .map((library) => visit(library)) | |
354 .where((info) => info != null)); | |
355 | |
356 return new ProgramInfo( | |
357 compilationDuration: compiler.totalCompileTime.elapsed, | |
358 // TODO (sigurdm): Also count the size of deferred code | |
359 size: compiler.assembledCode.length, | |
360 libraries: libraryInfos, | |
361 compilationMoment: new DateTime.now(), | |
362 dart2jsVersion: compiler.hasBuildId ? compiler.buildId : null, | |
363 outputUnitNumbering: outputUnitNumbering); | |
364 } | |
365 | |
366 InfoNode visitElement(Element element) { | |
367 compiler.internalError(element, | |
368 "This element of kind ${element.kind} " | |
369 "does not support --dump-info"); | |
370 return null; | |
371 } | |
372 | |
373 InfoNode visitLibraryElement(LibraryElement element) { | |
374 List<InfoNode> contents = new List<InfoNode>(); | |
375 int size = compiler.dumpInfoTask.codeSizeCounter | |
376 .getGeneratedSizeOf(element); | |
377 if (size == 0) return null; | |
378 stack.add(element); | |
379 // For some reason the patch library contains the origin libraries members, | |
380 // but the origin library does not contain the patch members. | |
381 LibraryElement contentsLibrary = element.isPatched | |
382 ? element.patch | |
383 : element; | |
384 contentsLibrary.forEachLocalMember((Element member) { | |
385 InfoNode info = member.accept(this); | |
386 if (info != null) { | |
387 contents.add(info); | |
388 } | 244 } |
389 }); | 245 }); |
390 stack.removeLast(); | 246 |
391 String nameString = element.getLibraryName() == "" | 247 return { |
392 ? "<unnamed>" | 248 'name': element.name, |
393 : element.getLibraryName(); | 249 'size': size, |
394 contents.sort((InfoNode e1, InfoNode e2) { | 250 'kind': 'class', |
395 return e1.name.compareTo(e2.name); | 251 'modifiers': modifiers, |
396 }); | 252 'children': children, |
397 return new ElementInfoNode( | 253 'id': id |
398 extra: "${element.canonicalUri}", | 254 }; |
399 kind: "library", | 255 } |
400 name: nameString, | 256 |
401 size: size, | 257 Map<String, dynamic> visitFunctionElement(FunctionElement element) { |
402 modifiers: "", | 258 String id = mapper._function.add(element); |
403 contents: contents); | 259 String name = element.name; |
404 } | 260 String kind = "function"; |
405 | 261 List<String> children = []; |
406 InfoNode visitTypedefElement(TypedefElement element) { | 262 List<Map<String, dynamic>> parameters = []; |
407 return element.alias == null | 263 String returnType = null; |
408 ? null | 264 String sideEffects = null; |
409 : new ElementInfoNode( | 265 String code = ""; |
410 type: element.alias.toString(), | 266 |
411 kind: "typedef", | |
412 name: element.name); | |
413 } | |
414 | |
415 InfoNode visitFieldElement(FieldElement element) { | |
416 CodeBuffer emittedCode = compiler.dumpInfoTask.codeOf(element); | |
417 TypeMask inferredType = compiler.typesTask | |
418 .getGuaranteedTypeOfElement(element); | |
419 // If a field has an empty inferred type it is never used. | |
420 // Also constant fields do not get output as fields. | |
421 if (inferredType == null || inferredType.isEmpty || element.isConst) { | |
422 return null; | |
423 } | |
424 int size = 0; | |
425 DartType type = element.type; | |
426 List<InfoNode> contents = new List<InfoNode>(); | |
427 if (emittedCode != null) { | |
428 contents.add(new CodeInfoNode( | |
429 description: "Generated initializer", | |
430 generatedCode: emittedCode.getText())); | |
431 size = emittedCode.length; | |
432 } | |
433 if (inferredType != null) { | |
434 contents.add(new InferredInfoNode( | |
435 description: "type", | |
436 type: inferredType.toString())); | |
437 stack.add(element); | |
438 } | |
439 for (Element closure in element.nestedClosures) { | |
440 InfoNode info = closure.accept(this); | |
441 if (info != null) { | |
442 contents.add(info); | |
443 size += info.size; | |
444 } | |
445 } | |
446 stack.removeLast(); | |
447 | |
448 return new ElementInfoNode( | |
449 kind: "field", | |
450 type: "$type", | |
451 name: element.name, | |
452 size: size, | |
453 modifiers: modifiersToString(isStatic: element.isStatic, | |
454 isFinal: element.isFinal, | |
455 isConst: element.isConst), | |
456 contents: contents, | |
457 outputUnitId: outputUnitId(element)); | |
458 } | |
459 | |
460 int outputUnitId(Element element) { | |
461 OutputUnit outputUnit = | |
462 compiler.deferredLoadTask.outputUnitForElement(element); | |
463 return outputUnitNumbering[outputUnit]; | |
464 } | |
465 | |
466 InfoNode visitClassElement(ClassElement element) { | |
467 // If the element is not emitted in the program, we omit it from the output. | |
468 JavaScriptBackend backend = compiler.backend; | |
469 if (!backend.emitter.neededClasses.contains(element)) return null; | |
470 String modifiersString = modifiersToString(isAbstract: element.isAbstract); | |
471 String supersString = element.allSupertypes == null ? "" : | |
472 "implements ${element.allSupertypes}"; | |
473 List contents = []; | |
474 stack.add(element); | |
475 element.forEachLocalMember((Element member) { | |
476 InfoNode info = member.accept(this); | |
477 if (info != null) { | |
478 contents.add(info); | |
479 } | |
480 }); | |
481 stack.removeLast(); | |
482 contents.sort((InfoNode n1, InfoNode n2) { | |
483 return n1.name.compareTo(n2.name); | |
484 }); | |
485 return new ElementInfoNode( | |
486 kind: "class", | |
487 name: element.name, | |
488 extra: supersString, | |
489 modifiers: modifiersString, | |
490 contents: contents, | |
491 outputUnitId: outputUnitId(element)); | |
492 } | |
493 | |
494 InfoNode visitFunctionElement(FunctionElement element) { | |
495 CodeBuffer emittedCode = compiler.dumpInfoTask.codeOf(element); | 267 CodeBuffer emittedCode = compiler.dumpInfoTask.codeOf(element); |
496 int size = 0; | 268 int size = 0; |
497 String nameString = element.name; | 269 |
498 String modifiersString = modifiersToString( | 270 Map<String, dynamic> modifiers = { |
499 isStatic: element.isStatic, | 271 'static': element.isStatic, |
500 isConst: element.isConst, | 272 'const': element.isConst, |
501 isFactory: element.isFactoryConstructor, | 273 'factory': element.isFactoryConstructor, |
502 isExternal: element.isPatched); | 274 'external': element.isPatched |
503 String kindString = "function"; | 275 }; |
504 if (currentElement.isClass) { | 276 |
505 kindString = "method"; | 277 var enclosingElement = element.enclosingElement; |
506 } else if (currentElement.isField || | 278 if (enclosingElement.isField || |
507 currentElement.isFunction || | 279 enclosingElement.isFunction || |
508 currentElement.isConstructor) { | 280 element.isClosure || |
509 kindString = "closure"; | 281 enclosingElement.isConstructor) { |
510 nameString = "<unnamed>"; | 282 kind = "closure"; |
511 } | 283 name = "<unnamed>"; |
284 } else if (enclosingElement.isClass) { | |
285 kind = 'method'; | |
286 } | |
287 | |
512 if (element.isConstructor) { | 288 if (element.isConstructor) { |
513 nameString = element.name == "" | 289 name == "" |
514 ? "${element.enclosingClass.name}" | 290 ? "${element.enclosingElement.name}" |
515 : "${element.enclosingClass.name}.${element.name}"; | 291 : "${element.enclosingElement.name}.${element.name}"; |
516 kindString = "constructor"; | 292 kind = "constructor"; |
517 } | 293 } |
518 List contents = []; | 294 |
519 if (emittedCode != null) { | 295 if (emittedCode != null) { |
520 FunctionSignature signature = element.functionSignature; | 296 FunctionSignature signature = element.functionSignature; |
521 signature.forEachParameter((parameter) { | 297 signature.forEachParameter((parameter) { |
522 contents.add(new InferredInfoNode( | 298 parameters.add({ |
523 description: "parameter", | 299 'name': parameter.name, |
524 name: parameter.name, | 300 'type': compiler.typesTask |
525 type: compiler.typesTask | 301 .getGuaranteedTypeOfElement(parameter).toString() |
526 .getGuaranteedTypeOfElement(parameter).toString())); | 302 }); |
527 }); | 303 }); |
528 contents.add(new InferredInfoNode( | 304 returnType = compiler.typesTask |
529 description: "return type", | 305 .getGuaranteedReturnTypeOfElement(element).toString(); |
530 type: compiler.typesTask | 306 sideEffects = compiler.world.getSideEffectsOfElement(element).toString(); |
531 .getGuaranteedReturnTypeOfElement(element).toString())); | 307 code = emittedCode.getText(); |
532 contents.add(new InferredInfoNode( | 308 size += code.length; |
533 description: "side effects", | 309 } |
534 type: compiler.world | 310 |
535 .getSideEffectsOfElement(element).toString())); | |
536 contents.add(new CodeInfoNode( | |
537 description: "Generated code", | |
538 generatedCode: emittedCode.getText())); | |
539 size += emittedCode.length; | |
540 } | |
541 stack.add(element); | |
542 for (Element closure in element.nestedClosures) { | 311 for (Element closure in element.nestedClosures) { |
543 InfoNode info = closure.accept(this); | 312 Map<String, dynamic> child = this.process(closure); |
544 if (info != null) { | 313 if (child != null) { |
545 contents.add(info); | 314 children.add(child['id']); |
546 size += info.size; | 315 size += child['size']; |
547 } | 316 } |
548 } | 317 } |
549 stack.removeLast(); | 318 |
550 if (size == 0) { | 319 if (size == 0 && !shouldKeep(element)) { |
551 return null; | 320 return null; |
552 } | 321 } |
553 | 322 |
554 return new ElementInfoNode( | 323 return { |
555 type: element.computeType(compiler).toString(), | 324 'kind': kind, |
556 kind: kindString, | 325 'name': name, |
557 name: nameString, | 326 'id': id, |
558 size: size, | 327 'modifiers': modifiers, |
559 modifiers: modifiersString, | 328 'children': children, |
560 contents: contents, | 329 'size': size, |
561 outputUnitId: outputUnitId(element)); | 330 'returnType': returnType, |
331 'parameters': parameters, | |
332 'sideEffects': sideEffects, | |
333 'code': code, | |
334 'type': element.computeType(compiler).toString() | |
335 }; | |
562 } | 336 } |
563 } | 337 } |
564 | 338 |
339 | |
565 class DumpInfoTask extends CompilerTask { | 340 class DumpInfoTask extends CompilerTask { |
566 DumpInfoTask(Compiler compiler) | 341 DumpInfoTask(Compiler compiler) |
567 : infoDumpVisitor = new InfoDumpVisitor(compiler), | 342 : super(compiler); |
568 super(compiler); | |
569 | 343 |
570 String name = "Dump Info"; | 344 String name = "Dump Info"; |
571 | 345 |
572 final CodeSizeCounter codeSizeCounter = new CodeSizeCounter(); | 346 final CodeSizeCounter codeSizeCounter = new CodeSizeCounter(); |
573 | 347 |
574 final InfoDumpVisitor infoDumpVisitor; | 348 ElementToJsonVisitor infoCollector; |
575 | 349 |
576 final Map<Element, jsAst.Expression>_generatedCode = | 350 final Map<Element, jsAst.Expression> _generatedCode = {}; |
577 new Map<Element, jsAst.Expression>(); | 351 |
578 | |
579 /// Registers that [code] has been generated for [element] so that it can be | |
580 /// emitted in the info.html. | |
581 void registerGeneratedCode(Element element, jsAst.Expression code) { | 352 void registerGeneratedCode(Element element, jsAst.Expression code) { |
582 if (compiler.dumpInfo) { | 353 if (compiler.dumpInfo) { |
583 _generatedCode[element] = code; | 354 _generatedCode[element] = code; |
584 } | 355 } |
585 } | 356 } |
586 | 357 |
587 CodeBuffer codeOf(Element element) { | 358 |
588 jsAst.Expression code = _generatedCode[element]; | 359 void collectInfo() { |
589 return code != null | 360 infoCollector = new ElementToJsonVisitor(compiler); |
590 ? jsAst.prettyPrint(code, compiler) | |
591 : compiler.backend.codeOf(element); | |
592 } | 361 } |
593 | 362 |
594 void dumpInfo() { | 363 void dumpInfo() { |
595 measure(() { | 364 measure(() { |
596 ProgramInfo info = infoDumpVisitor.collectDumpInfo(); | 365 if (infoCollector == null) { |
597 | 366 collectInfo(); |
598 StringBuffer htmlBuffer = new StringBuffer(); | 367 } |
599 dumpInfoHtml(info, htmlBuffer); | |
600 compiler.outputProvider('', 'info.html') | |
601 ..add(htmlBuffer.toString()) | |
602 ..close(); | |
603 | 368 |
604 StringBuffer jsonBuffer = new StringBuffer(); | 369 StringBuffer jsonBuffer = new StringBuffer(); |
605 dumpInfoJson(info, jsonBuffer); | 370 dumpInfoJson(jsonBuffer); |
606 compiler.outputProvider('', 'info.json') | 371 compiler.outputProvider('', 'info.json') |
607 ..add(jsonBuffer.toString()) | 372 ..add(jsonBuffer.toString()) |
608 ..close(); | 373 ..close(); |
609 }); | 374 }); |
610 } | 375 } |
611 | 376 |
612 void dumpInfoJson(ProgramInfo info, StringSink buffer) { | 377 CodeBuffer codeOf(Element element) { |
613 Map<String, dynamic> entire = <String, dynamic>{ | 378 jsAst.Expression code = _generatedCode[element]; |
614 'program': info.toJson(), | 379 return code != null |
615 'libs': info.libraries.map((lib) => lib.toJson(info)).toList() | 380 ? jsAst.prettyPrint(code, compiler) |
616 }; | 381 : compiler.backend.codeOf(element); |
617 | 382 } |
383 | |
384 void dumpInfoJson(StringSink buffer) { | |
618 JsonEncoder encoder = const JsonEncoder(); | 385 JsonEncoder encoder = const JsonEncoder(); |
386 | |
387 // `A` uses and depends on the functions `Bs`. | |
388 // A Bs | |
389 Map<String, List<String>> holding = <String, List<String>>{}; | |
390 | |
391 DateTime startToJsonTime = new DateTime.now(); | |
392 | |
393 CompilationInformation compilationInfo = | |
394 infoCollector.compiler.enqueuer.codegen.compilationInfo; | |
395 var relations = compilationInfo.relations; | |
396 relations['addsToWorklist'].forEach((func, deps) { | |
397 if (func != null) { | |
398 var funcJson = infoCollector.process(func); | |
399 if (funcJson != null) { | |
400 var funcId = funcJson['id']; | |
401 | |
402 List<String> heldList = <String>[]; | |
403 | |
404 for (var held in deps) { | |
405 // "process" to get the ids of the elements. | |
406 var heldJson = infoCollector.process(held); | |
407 if (heldJson != null) { | |
408 var heldId = heldJson['id']; | |
409 heldList.add(heldId); | |
410 } | |
411 } | |
412 holding[funcId] = heldList; | |
413 } | |
414 } | |
415 }); | |
416 | |
417 Map<String, dynamic> outJson = {}; | |
418 outJson['elements'] = infoCollector.toJson(); | |
419 outJson['holding'] = holding; | |
420 outJson['dump_version'] = 1; | |
421 | |
422 Duration toJsonDuration = new DateTime.now().difference(startToJsonTime); | |
423 | |
424 Map<String, dynamic> generalProgramInfo = <String, dynamic>{}; | |
425 generalProgramInfo['size'] = infoCollector.programSize; | |
426 generalProgramInfo['dart2jsVersion'] = infoCollector.dart2jsVersion; | |
427 generalProgramInfo['compilationMoment'] = infoCollector.compilationMoment.to String(); | |
428 generalProgramInfo['compilationDuration'] = infoCollector.compilationDuratio n.toString(); | |
429 generalProgramInfo['toJsonDuration'] = toJsonDuration.toString(); | |
430 generalProgramInfo['dumpInfoDuration'] = infoCollector.dumpInfoDuration.toSt ring(); | |
431 | |
432 outJson['program'] = generalProgramInfo; | |
433 | |
619 ChunkedConversionSink<Object> sink = | 434 ChunkedConversionSink<Object> sink = |
620 encoder.startChunkedConversion( | 435 encoder.startChunkedConversion( |
621 new StringConversionSink.fromStringSink(buffer)); | 436 new StringConversionSink.fromStringSink(buffer)); |
622 sink.add(entire); | 437 sink.add(outJson); |
623 } | |
624 | |
625 void dumpInfoHtml(ProgramInfo info, StringSink buffer) { | |
626 int totalSize = info.size; | |
627 | |
628 buffer.writeln(""" | |
629 <html> | |
630 <head> | |
631 <title>Dart2JS compilation information</title> | |
632 <style> | |
633 code {margin-left: 20px; display: block; white-space: pre; } | |
634 div.container, div.contained, div.element, div.attr { | |
635 margin-top:0px; | |
636 margin-bottom: 0px; | |
637 } | |
638 div.container, div.element, div.attr { | |
639 white-space: nowrap; | |
640 } | |
641 .contents { | |
642 margin-left: 20px; | |
643 } | |
644 div.contained {margin-left: 20px;} | |
645 div {/*border: 1px solid;*/} | |
646 span.kind {} | |
647 span.modifiers {font-weight:bold;} | |
648 span.name {font-weight:bold; font-family: monospace;} | |
649 span.type {font-family: monospace; color:blue;} | |
650 """); | |
651 for (int i = 0; i < COLORS.length; i++) { | |
652 buffer.writeln(" .outputUnit$i " | |
653 "{border-left: 4px solid ${COLORS[i]}}"); | |
654 } | |
655 buffer.writeln(""" | |
656 </style> | |
657 </head> | |
658 <body> | |
659 <h1>Dart2js compilation information</h1>"""); | |
660 if (info.outputUnitNumbering.length > 1) { | |
661 for (OutputUnit outputUnit in info.outputUnitNumbering.keys) { | |
662 String color = COLORS[info.outputUnitNumbering[outputUnit] | |
663 % COLORS.length]; | |
664 JavaScriptBackend backend = compiler.backend; | |
665 int size = backend.emitter.outputBuffers[outputUnit].length; | |
666 buffer.writeln('<div style=' | |
667 '"background:$color;">' | |
668 '${outputUnit.partFileName(compiler)} $size bytes</div>'); | |
669 } | |
670 } | |
671 buffer.writeln(h2('Compilation took place: ' | |
672 '${info.compilationMoment}')); | |
673 buffer.writeln(h2('Compilation took: ' | |
674 '${info.compilationDuration.inSeconds} seconds')); | |
675 buffer.writeln(h2('Output size: ${info.size} bytes')); | |
676 if (info.dart2jsVersion != null) { | |
677 buffer.writeln(h2('Dart2js version: ${info.dart2jsVersion}')); | |
678 } | |
679 | |
680 buffer.writeln('<a href="#" class="sort_by_size">Sort by size</a>\n'); | |
681 | |
682 buffer.writeln('<div class="contents">'); | |
683 info.libraries.forEach((InfoNode node) { | |
684 node.emitHtml(info, buffer); | |
685 }); | |
686 buffer.writeln('</div>'); | |
687 | |
688 // TODO (sigurdm): This script should be written in dart | |
689 buffer.writeln(r""" | |
690 <script type="text/javascript"> | |
691 function toggler(element) { | |
692 return function(e) { | |
693 element.hidden = !element.hidden; | |
694 }; | |
695 } | |
696 var containers = document.getElementsByClassName('container'); | |
697 for (var i = 0; i < containers.length; i++) { | |
698 var container = containers[i]; | |
699 container.querySelector('.details').addEventListener('click', | |
700 toggler(container.querySelector('.contents')), false); | |
701 container.querySelector('.contents').hidden = true; | |
702 } | |
703 | |
704 function sortBySize() { | |
705 var toSort = document.querySelectorAll('.contents'); | |
706 for (var i = 0; i < toSort.length; ++i) { | |
707 sortNodes(toSort[i], function(a, b) { | |
708 if (a[1] !== b[1]) { | |
709 return a[1] > b[1] ? -1 : 1; | |
710 } | |
711 return a[2] === b[2] ? 0 : a[2] > b[2] ? 1 : -1; | |
712 }); | |
713 } | |
714 } | |
715 | |
716 function findSize(node) { | |
717 var size = 0; | |
718 var details = node.querySelector('.details'); | |
719 if (details) { | |
720 var sizeElement = details.querySelector('.size'); | |
721 if (sizeElement) { | |
722 size = parseInt(sizeElement.textContent); | |
723 } else { | |
724 // For classes, sum up the contents for sorting purposes. | |
725 var kind = details.querySelector('.kind'); | |
726 if (kind && kind.textContent === 'class') { | |
727 var contents = node.querySelector('.contents'); | |
728 if (contents) { | |
729 var child = contents.firstElementChild; | |
730 while (child) { | |
731 size += findSize(child); | |
732 child = child.nextElementSibling; | |
733 } | |
734 } | |
735 } | |
736 } | |
737 } | |
738 return size; | |
739 } | |
740 | |
741 function findName(node) { | |
742 var name = ''; | |
743 var nameNode = node.querySelector('.name'); | |
744 if (nameNode) { | |
745 return nameNode.textContent; | |
746 } | |
747 return node.textContent; | |
748 } | |
749 function sortNodes(node, fn) { | |
750 var items = []; | |
751 var child = node.firstElementChild; | |
752 while (child) { | |
753 items.push([child, findSize(child), findName(child)]); | |
754 child = child.nextElementSibling; | |
755 } | |
756 items.sort(fn); | |
757 for (var i = 0; i < items.length; ++i) { | |
758 node.appendChild(items[i][0]); | |
759 } | |
760 } | |
761 document.querySelector('.sort_by_size').addEventListener('click', | |
762 function() { | |
763 sortBySize(); | |
764 }, false); | |
765 </script> | |
766 </body> | |
767 </html>"""); | |
768 } | 438 } |
769 } | 439 } |
OLD | NEW |