Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 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 | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 library dump_info; | |
| 6 | |
| 7 import 'elements/elements.dart'; | |
| 8 import 'elements/visitor.dart'; | |
| 9 import 'dart:convert' show HtmlEscape; | |
| 10 import 'dart2jslib.dart' show | |
| 11 Compiler, | |
| 12 CompilerTask, | |
| 13 CodeBuffer; | |
| 14 import 'dart_types.dart' show DartType; | |
| 15 import 'types/types.dart' show TypeMask; | |
| 16 | |
| 17 // TODO (sigurdm): A search function. | |
| 18 // TODO (sigurdm): Output size of classes. | |
| 19 // TODO (sigurdm): Print that we dumped the HTML-file. | |
| 20 // TODO (sigurdm): Include why a given element was included in the output. | |
| 21 // TODO (sigurdm): Include how much output grew because of mirror support. | |
| 22 // TODO (sigurdm): Write each function with parameter names. | |
| 23 // TODO (sigurdm): Write how much space the boilerplate takes. | |
| 24 // TODO (sigurdm): Include javascript names of entities in the output. | |
| 25 | |
| 26 class CodeSizeCounter { | |
| 27 final Map<Element, int> generatedSize = new Map<Element, int>(); | |
| 28 | |
| 29 int getGeneratedSizeOf(Element element) { | |
| 30 int result = generatedSize[element]; | |
| 31 return result == null ? 0 : result; | |
| 32 } | |
| 33 | |
| 34 void countCode(Element element, int added) { | |
| 35 int before = generatedSize.putIfAbsent(element, () => 0); | |
| 36 generatedSize[element] = before + added; | |
| 37 } | |
| 38 } | |
| 39 | |
| 40 tag(String element) { | |
| 41 return (String content, {String cls}) { | |
| 42 String classString = cls == null ? '' : ' class="$cls"'; | |
| 43 return '<$element$classString>$content</$element>'; | |
| 44 }; | |
| 45 } | |
| 46 | |
| 47 var div = tag('div'); | |
| 48 var span = tag('span'); | |
| 49 var code = tag('code'); | |
| 50 var h2 = tag('h2'); | |
| 51 | |
| 52 var esc = const HtmlEscape().convert; | |
| 53 | |
| 54 String sizeDescription(int size, ProgramInfo programInfo) { | |
| 55 return size == null | |
| 56 ? '' | |
| 57 : span('${size} bytes ' | |
| 58 '(${size * 100 ~/ programInfo.size}%)', cls: "size"); | |
| 59 } | |
| 60 | |
| 61 abstract class InfoNode { | |
| 62 String get name; | |
| 63 | |
| 64 int get size; | |
| 65 | |
| 66 void emitHtml(ProgramInfo programInfo, StringSink buffer); | |
| 67 } | |
| 68 | |
| 69 class ElementInfoNode implements InfoNode { | |
|
Johnni Winther
2013/12/10 10:40:53
Add documentation to the class and its fields.
sigurdm
2013/12/10 11:50:39
Done.
| |
| 70 final String name; | |
| 71 | |
| 72 final String kind; | |
| 73 | |
| 74 final String type; | |
| 75 | |
| 76 final String extra; | |
| 77 | |
| 78 final String modifiers; | |
| 79 | |
| 80 /// Describes how many bytes the code for this element takes up in the | |
| 81 /// generated output. | |
| 82 final int size; | |
| 83 | |
| 84 List<InfoNode> contents; | |
| 85 | |
| 86 ElementInfoNode({this.name: "", | |
| 87 this.kind: "", | |
| 88 this.type, | |
| 89 this.modifiers: "", | |
| 90 this.size, | |
| 91 this.contents, | |
| 92 this.extra: ""}); | |
| 93 | |
| 94 void emitHtml(ProgramInfo programInfo, StringSink buffer) { | |
| 95 String kindString = span(esc(kind), cls: 'kind'); | |
| 96 String modifiersString = span(esc(modifiers), cls: "modifiers"); | |
| 97 | |
| 98 String nameString = span(esc(name), cls: 'name'); | |
| 99 String typeString = type == null | |
| 100 ? '' | |
| 101 : span('/* ' + esc(type) + ' */', cls: 'type'); | |
| 102 String extraString = span(esc(extra), cls: 'type'); | |
| 103 String describe = [ | |
| 104 kindString, | |
| 105 typeString, | |
| 106 modifiersString, | |
| 107 nameString, | |
| 108 sizeDescription(size, programInfo), | |
| 109 extraString].join(' '); | |
| 110 | |
| 111 if (contents != null) { | |
| 112 buffer.write(div("+$describe", cls: "container")); | |
| 113 buffer.write('<div class="contained">'); | |
| 114 if (contents.isEmpty) { | |
| 115 buffer.writeln("No members"); | |
| 116 } | |
| 117 for (InfoNode subElementDescription in contents) { | |
| 118 subElementDescription.emitHtml(programInfo, buffer); | |
| 119 } | |
| 120 buffer.write("</div>"); | |
| 121 } else { | |
| 122 buffer.writeln(describe); | |
| 123 } | |
| 124 } | |
| 125 } | |
| 126 | |
| 127 class CodeInfoNode implements InfoNode { | |
|
Johnni Winther
2013/12/10 10:40:53
Add documentation to the class and its fields.
sigurdm
2013/12/10 11:50:39
Done.
| |
| 128 final String description; | |
| 129 | |
| 130 final String generatedCode; | |
| 131 | |
| 132 get size => generatedCode.length; | |
| 133 | |
| 134 get name => ""; | |
| 135 | |
| 136 CodeInfoNode({this.description: "", this.generatedCode}); | |
| 137 | |
| 138 void emitHtml(ProgramInfo programInfo, StringBuffer buffer) { | |
| 139 buffer.write(div(description + ' ' + | |
| 140 sizeDescription(generatedCode.length, programInfo), | |
| 141 cls: 'kind') + | |
| 142 code(esc(generatedCode))); | |
| 143 } | |
| 144 } | |
| 145 | |
| 146 class InferredInfoNode implements InfoNode { | |
|
Johnni Winther
2013/12/10 10:40:53
Add documentation to the class and its fields.
sigurdm
2013/12/10 11:50:39
Done.
| |
| 147 final String description; | |
| 148 | |
| 149 final String name; | |
| 150 | |
| 151 final String type; | |
| 152 | |
| 153 get size => 0; | |
| 154 | |
| 155 InferredInfoNode({this.name: "", this.description, this.type}); | |
| 156 | |
| 157 void emitHtml(ProgramInfo programInfo, StringSink buffer) { | |
| 158 buffer.write(div('${span("Inferred " + description, cls: "kind")} ' | |
| 159 '${span(esc(name), | |
| 160 cls: "name")} ' | |
| 161 '${span(esc(type.toString()), cls: 'type')} ')); | |
|
Johnni Winther
2013/12/10 10:40:53
Remove .toString() -- [type] is already a String.
sigurdm
2013/12/10 11:50:39
Done.
| |
| 162 } | |
| 163 } | |
| 164 | |
| 165 class ProgramInfo { | |
|
Johnni Winther
2013/12/10 10:40:53
Add documentation to the class and its fields.
sigurdm
2013/12/10 11:50:39
Done.
| |
| 166 final String name; | |
| 167 | |
| 168 final String presentation; | |
| 169 | |
| 170 final List<InfoNode> libraries; | |
| 171 | |
| 172 // How many bytes is the output | |
| 173 final int size; | |
| 174 | |
| 175 final DateTime compilationMoment; | |
| 176 | |
| 177 final int compilationDuration; | |
| 178 | |
| 179 final String dart2jsVersion; | |
| 180 | |
| 181 ProgramInfo({String this.name, | |
| 182 this.presentation, | |
|
Johnni Winther
2013/12/10 10:40:53
Indent this and the next lines to fit under `Strin
sigurdm
2013/12/10 11:50:39
Done.
| |
| 183 this.libraries, | |
| 184 this.size, DateTime this.compilationMoment, | |
|
Johnni Winther
2013/12/10 10:40:53
Put `DateTime this.compilationMoment` on a separat
sigurdm
2013/12/10 11:50:39
Done.
| |
| 185 this.compilationDuration, String this.dart2jsVersion}); | |
|
Johnni Winther
2013/12/10 10:40:53
Ditto for `String this.dart2jsVersion`.
sigurdm
2013/12/10 11:50:39
Done.
| |
| 186 } | |
| 187 | |
| 188 class InfoDumpVisitor extends ElementVisitor<InfoNode> { | |
| 189 final Compiler compiler; | |
| 190 | |
| 191 /// Contains the elements visited on the path from the library to here. | |
| 192 List<Element> stack = new List<Element>(); | |
| 193 | |
| 194 InfoDumpVisitor(Compiler this.compiler); | |
| 195 | |
| 196 InfoNode visitElement(Element element) { | |
| 197 compiler.internalError("This element of kind ${element.kind} " | |
| 198 "does not support --dump-info", | |
| 199 token: element.position()); | |
| 200 } | |
| 201 | |
| 202 InfoNode visitLibraryElement(LibraryElement element) { | |
| 203 List<InfoNode> contents = new List<InfoNode>(); | |
| 204 int size = compiler.dumpInfoTask.codeSizeCounter | |
| 205 .getGeneratedSizeOf(element); | |
| 206 if (size == 0) return null; | |
| 207 stack.add(element); | |
| 208 element.forEachLocalMember((Element member) { | |
| 209 InfoNode info = member.accept(this); | |
| 210 if (info != null) { | |
| 211 contents.add(info); | |
| 212 } | |
| 213 }); | |
| 214 stack.removeLast(); | |
| 215 String nameString = element.getLibraryName() == "" | |
| 216 ? "<unnamed>" | |
| 217 : element.getLibraryName(); | |
| 218 contents.sort((InfoNode e1, InfoNode e2) { | |
| 219 return e1.name.compareTo(e2.name); | |
| 220 }); | |
| 221 return new ElementInfoNode( | |
| 222 extra: element.canonicalUri.toString(), | |
|
Johnni Winther
2013/12/10 10:40:53
Maybe use '${element.canonicalUri}' instead?
sigurdm
2013/12/10 11:50:39
Done.
| |
| 223 kind: "library", | |
| 224 name: nameString, | |
| 225 size: size, | |
| 226 modifiers: "", | |
| 227 contents: contents); | |
| 228 } | |
| 229 | |
| 230 InfoNode visitTypedefElement(TypedefElement element) { | |
| 231 return element.alias == null | |
| 232 ? null | |
| 233 : new ElementInfoNode( | |
| 234 type: element.alias.toString(), | |
| 235 kind: "typedef", | |
| 236 name: element.name); | |
| 237 } | |
| 238 | |
| 239 InfoNode visitFieldElement(FieldElement element) { | |
| 240 CodeBuffer emittedCode = compiler.backend.codeOf(element); | |
| 241 int size = 0; | |
| 242 DartType type = element.computeType(compiler); | |
| 243 TypeMask inferredType = compiler.typesTask | |
| 244 .getGuaranteedTypeOfElement(element); | |
| 245 if (inferredType == TypeMask) return null; | |
|
Johnni Winther
2013/12/10 10:40:53
Why do you compare to `TypeMask`, is that ever tru
sigurdm
2013/12/10 11:50:39
Done.
| |
| 246 List<InfoNode> contents = new List<InfoNode>(); | |
| 247 if (emittedCode != null) { | |
| 248 contents.add(new CodeInfoNode( | |
| 249 description: "Generated initializer", | |
| 250 generatedCode: emittedCode.getText())); | |
| 251 size = emittedCode.length; | |
| 252 } | |
| 253 contents.add(new InferredInfoNode( | |
| 254 description: "type", | |
| 255 type: inferredType.toString())); | |
| 256 stack.add(element); | |
| 257 for (Element closure in element.nestedClosures) { | |
| 258 InfoNode info = closure.accept(this); | |
| 259 if (info != null) { | |
| 260 contents.add(info); | |
| 261 size += info.size; | |
| 262 } | |
| 263 } | |
| 264 stack.removeLast(); | |
| 265 | |
| 266 return new ElementInfoNode( | |
| 267 kind: "field", | |
| 268 type: type.toString(), | |
| 269 name: element.name, | |
| 270 size: size, | |
| 271 modifiers: element.modifiers.toString(), | |
| 272 contents: contents); | |
| 273 } | |
| 274 | |
| 275 InfoNode visitClassElement(ClassElement element) { | |
| 276 String modifiersString = element.modifiers.toString() == "" | |
|
Johnni Winther
2013/12/10 10:40:53
Change `element.modifiers.toString() == ""` to `el
sigurdm
2013/12/10 11:50:39
Done.
| |
| 277 ? "" | |
| 278 : element.modifiers.toString()+" "; | |
|
Johnni Winther
2013/12/10 10:40:53
Change `element.modifiers.toString()+" "` to `'${e
sigurdm
2013/12/10 11:50:39
Done.
| |
| 279 if (!element.isResolved) return null; | |
|
Johnni Winther
2013/12/10 10:40:53
Check this as the first thing in the method and ad
sigurdm
2013/12/10 11:50:39
Done.
| |
| 280 String supersString = element.allSupertypes == null ? "" : | |
| 281 "implements ${element.allSupertypes}"; | |
| 282 List contents = []; | |
| 283 stack.add(element); | |
| 284 element.forEachLocalMember((Element member) { | |
| 285 InfoNode info = member.accept(this); | |
| 286 if (info != null) { | |
| 287 contents.add(info); | |
| 288 } | |
| 289 }); | |
| 290 stack.removeLast(); | |
| 291 if (contents.isEmpty) { | |
| 292 return null; | |
|
Johnni Winther
2013/12/10 10:40:53
Why exclude a class with no members. We might stil
sigurdm
2013/12/10 11:50:39
Done.
| |
| 293 } | |
| 294 contents.sort((InfoNode n1, InfoNode n2) { | |
| 295 return n1.name.compareTo(n2.name); | |
| 296 }); | |
| 297 return new ElementInfoNode( | |
| 298 kind: "class", | |
| 299 name: element.name, | |
| 300 extra: supersString, | |
| 301 modifiers: modifiersString, | |
| 302 contents: contents); | |
| 303 } | |
| 304 | |
| 305 InfoNode visitFunctionElement(FunctionElement element) { | |
| 306 CodeBuffer emittedCode = compiler.backend.codeOf(element); | |
| 307 int size = 0; | |
| 308 String nameString = element.name; | |
| 309 String modifiersString = element.modifiers.toString(); | |
|
Johnni Winther
2013/12/10 10:40:53
Change `element.modifiers.toString()` to '${elemen
sigurdm
2013/12/10 11:50:39
Done.
| |
| 310 String kindString = "function"; | |
| 311 if (stack.last.isClass()) { | |
| 312 kindString = "method"; | |
| 313 } else if (stack.last.isField() || | |
| 314 stack.last.isFunction() || | |
|
Johnni Winther
2013/12/10 10:40:53
Indent `stack.last.isFunction()` to fit under `sta
sigurdm
2013/12/10 11:50:39
Done.
| |
| 315 stack.last.isConstructor()) { | |
|
Johnni Winther
2013/12/10 10:40:53
Ditto.
sigurdm
2013/12/10 11:50:39
Done.
| |
| 316 kindString = "closure"; | |
| 317 nameString = "<unnamed>"; | |
| 318 } | |
| 319 if (element.isConstructor()) { | |
| 320 nameString = element.name == "" | |
| 321 ? "${element.enclosingElement.name}" | |
| 322 : "${element.enclosingElement.name}.${element.name}"; | |
| 323 kindString = "constructor"; | |
| 324 } | |
| 325 List contents = []; | |
| 326 if (emittedCode != null) { | |
| 327 FunctionSignature signature = element.computeSignature(compiler); | |
| 328 signature.forEachParameter((parameter) { | |
| 329 contents.add(new InferredInfoNode( | |
| 330 description: "parameter", | |
| 331 name: parameter.name, | |
| 332 type: compiler.typesTask | |
| 333 .getGuaranteedTypeOfElement(parameter).toString())); | |
| 334 }); | |
| 335 contents.add(new InferredInfoNode( | |
| 336 description: "return type", | |
| 337 type: compiler.typesTask | |
| 338 .getGuaranteedReturnTypeOfElement(element).toString())); | |
| 339 contents.add(new InferredInfoNode( | |
| 340 description: "side effects", | |
| 341 type: compiler.world | |
| 342 .getSideEffectsOfElement(element).toString())); | |
| 343 contents.add(new CodeInfoNode( | |
| 344 description: "Generated code", | |
| 345 generatedCode: emittedCode.getText())); | |
| 346 size += emittedCode.length; | |
| 347 } | |
| 348 stack.add(element); | |
| 349 for (Element closure in element.nestedClosures) { | |
| 350 InfoNode info = closure.accept(this); | |
| 351 if (info != null) { | |
| 352 contents.add(info); | |
| 353 size += info.size; | |
| 354 } | |
| 355 } | |
| 356 stack.removeLast(); | |
| 357 if (size == 0) { | |
| 358 return null; | |
| 359 } | |
| 360 return new ElementInfoNode( | |
| 361 type: element.type.toString(), | |
| 362 kind: kindString, | |
| 363 name: nameString, | |
| 364 size: size, | |
| 365 modifiers: modifiersString, | |
| 366 contents: contents); | |
| 367 } | |
| 368 } | |
| 369 | |
| 370 class DumpInfoTask extends CompilerTask { | |
| 371 DumpInfoTask(Compiler compiler) | |
| 372 : infoDumpVisitor = new InfoDumpVisitor(compiler), | |
| 373 super(compiler); | |
| 374 | |
| 375 String name = "Dump Info"; | |
| 376 | |
| 377 final CodeSizeCounter codeSizeCounter = new CodeSizeCounter(); | |
| 378 | |
| 379 final InfoDumpVisitor infoDumpVisitor; | |
| 380 | |
| 381 void dumpInfo() { | |
| 382 measure(() { | |
| 383 ProgramInfo info = collectDumpInfo(); | |
| 384 StringBuffer buffer = new StringBuffer(); | |
| 385 dumpInfoHtml(info, buffer); | |
| 386 compiler.outputProvider('', 'info.html') | |
| 387 ..add(buffer.toString()) | |
| 388 ..close(); | |
| 389 }); | |
| 390 } | |
| 391 | |
| 392 ProgramInfo collectDumpInfo() { | |
| 393 List<LibraryElement> sortedLibraries = compiler.libraries.values.toList(); | |
| 394 sortedLibraries.sort((LibraryElement l1, LibraryElement l2) { | |
| 395 if (l1.isPlatformLibrary && !l2.isPlatformLibrary) { | |
| 396 return 1; | |
| 397 } else if (!l1.isPlatformLibrary && l2.isPlatformLibrary) { | |
| 398 return -1; | |
| 399 } | |
| 400 return l1.getLibraryName().compareTo(l2.getLibraryName()); | |
| 401 }); | |
| 402 | |
| 403 List<InfoNode> libraryInfos = new List<InfoNode>(); | |
| 404 libraryInfos.addAll(sortedLibraries | |
| 405 .map((library) => infoDumpVisitor.visit(library)) | |
| 406 .where((library) => library != null)); | |
|
Johnni Winther
2013/12/10 10:40:53
Rename [library] to [info] -- it's not the library
sigurdm
2013/12/10 11:50:39
Done.
| |
| 407 | |
| 408 return new ProgramInfo( | |
| 409 compilationDuration: compiler.totalCompileTime.elapsedTicks, | |
| 410 // TODO (sigurdm): Also count the size of deferred code | |
| 411 size: compiler.assembledCode.length, | |
| 412 libraries: libraryInfos, | |
| 413 compilationMoment: new DateTime.now(), | |
| 414 dart2jsVersion: compiler.hasBuildId ? compiler.buildId : null); | |
| 415 } | |
| 416 | |
| 417 void dumpInfoHtml(ProgramInfo info, StringSink buffer) { | |
| 418 int totalSize = info.size; | |
| 419 | |
| 420 buffer.writeln(""" | |
| 421 <html> | |
| 422 <head> | |
| 423 <title>Dart2JS compilation information</title> | |
| 424 <style> | |
| 425 code {margin-left: 20px; display: block;} | |
| 426 div.contained {margin-left: 20px;} | |
| 427 div {margin-top:0px; | |
| 428 margin-bottom: 0px; | |
| 429 white-space: pre; /*border: 1px solid;*/} | |
| 430 span.kind {} | |
| 431 span.modifiers {font-weight:bold;} | |
| 432 span.name {font-weight:bold; font-family: monospace;} | |
| 433 span.type {font-family: monospace; color:blue;} | |
| 434 </style> | |
| 435 </head> | |
| 436 <body> | |
| 437 <h1>Dart2js compilation information</h1>"""); | |
| 438 buffer.writeln(h2('Compilation took place: ' | |
| 439 '${info.compilationMoment}')); | |
| 440 buffer.writeln(h2('Compilation took: ' | |
| 441 '${info.compilationDuration/1000000} seconds')); | |
| 442 buffer.writeln(h2('Output size: ${info.size} bytes')); | |
| 443 if (info.dart2jsVersion != null) { | |
| 444 buffer.writeln(h2('Dart2js version: ${info.dart2jsVersion}')); | |
| 445 } | |
| 446 | |
| 447 info.libraries.forEach((InfoNode node) { | |
| 448 node.emitHtml(info, buffer); | |
| 449 }); | |
| 450 | |
| 451 | |
| 452 // TODO (sigurdm): This script should be written in dart | |
| 453 buffer.writeln(r""" | |
| 454 <script type="text/javascript"> | |
| 455 function toggler(element) { | |
| 456 return function(e) { | |
| 457 element.hidden = !element.hidden; | |
| 458 }; | |
| 459 } | |
| 460 var containers = document.getElementsByClassName('container'); | |
| 461 for (var i = 0; i < containers.length; i++) { | |
| 462 var container = containers[i]; | |
| 463 container.addEventListener('click', | |
| 464 toggler(container.nextElementSibling), false); | |
| 465 container.nextElementSibling.hidden = true; | |
| 466 }; | |
| 467 </script> | |
| 468 </body> | |
| 469 </html>"""); | |
| 470 } | |
| 471 } | |
| OLD | NEW |