Chromium Code Reviews| 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 |