| // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| library dump_info; |
| |
| import 'elements/elements.dart'; |
| import 'elements/visitor.dart'; |
| import 'dart:convert' show HtmlEscape; |
| import 'dart2jslib.dart' show |
| Compiler, |
| CompilerTask, |
| CodeBuffer; |
| import 'dart_types.dart' show DartType; |
| import 'types/types.dart' show TypeMask; |
| |
| // TODO (sigurdm): A search function. |
| // TODO (sigurdm): Output size of classes. |
| // TODO (sigurdm): Print that we dumped the HTML-file. |
| // TODO (sigurdm): Include why a given element was included in the output. |
| // TODO (sigurdm): Include how much output grew because of mirror support. |
| // TODO (sigurdm): Write each function with parameter names. |
| // TODO (sigurdm): Write how much space the boilerplate takes. |
| // TODO (sigurdm): Include javascript names of entities in the output. |
| |
| class CodeSizeCounter { |
| final Map<Element, int> generatedSize = new Map<Element, int>(); |
| |
| int getGeneratedSizeOf(Element element) { |
| int result = generatedSize[element]; |
| return result == null ? 0 : result; |
| } |
| |
| void countCode(Element element, int added) { |
| int before = generatedSize.putIfAbsent(element, () => 0); |
| generatedSize[element] = before + added; |
| } |
| } |
| |
| tag(String element) { |
| return (String content, {String cls}) { |
| String classString = cls == null ? '' : ' class="$cls"'; |
| return '<$element$classString>$content</$element>'; |
| }; |
| } |
| |
| var div = tag('div'); |
| var span = tag('span'); |
| var code = tag('code'); |
| var h2 = tag('h2'); |
| |
| var esc = const HtmlEscape().convert; |
| |
| String sizeDescription(int size, ProgramInfo programInfo) { |
| return size == null |
| ? '' |
| : span('${size} bytes ' |
| '(${size * 100 ~/ programInfo.size}%)', cls: "size"); |
| } |
| |
| /// An [InfoNode] holds information about a part the program. |
| abstract class InfoNode { |
| String get name; |
| |
| int get size; |
| |
| void emitHtml(ProgramInfo programInfo, StringSink buffer); |
| } |
| |
| /// An [ElementNode] holds information about an [Element] |
| class ElementInfoNode implements InfoNode { |
| /// The name of the represented [Element]. |
| final String name; |
| |
| /// The kind of the [Element] represented. This is presented to the |
| /// user, so it might be more specific than [element.kind]. |
| final String kind; |
| |
| /// The static type of the represented [Element]. |
| /// [:null:] if this kind of element has no type. |
| final String type; |
| |
| /// Any extra information to display about the represented [Element]. |
| final String extra; |
| |
| /// A textual description of the modifiers (such as "static", "abstract") of |
| /// the represented [Element]. |
| final String modifiers; |
| |
| /// Describes how many bytes the code for the represented [Element] takes up |
| /// in the output. |
| final int size; |
| |
| /// Subnodes containing more detailed information about the represented |
| /// [Element], and its members. |
| List<InfoNode> contents; |
| |
| ElementInfoNode({this.name: "", |
| this.kind: "", |
| this.type, |
| this.modifiers: "", |
| this.size, |
| this.contents, |
| this.extra: ""}); |
| |
| void emitHtml(ProgramInfo programInfo, StringSink buffer) { |
| String kindString = span(esc(kind), cls: 'kind'); |
| String modifiersString = span(esc(modifiers), cls: "modifiers"); |
| |
| String nameString = span(esc(name), cls: 'name'); |
| String typeString = type == null |
| ? '' |
| : span('/* ' + esc(type) + ' */', cls: 'type'); |
| String extraString = span(esc(extra), cls: 'type'); |
| String describe = [ |
| kindString, |
| typeString, |
| modifiersString, |
| nameString, |
| sizeDescription(size, programInfo), |
| extraString].join(' '); |
| |
| if (contents != null) { |
| buffer.write(div("+$describe", cls: "container")); |
| buffer.write('<div class="contained">'); |
| if (contents.isEmpty) { |
| buffer.writeln("No members"); |
| } |
| for (InfoNode subElementDescription in contents) { |
| subElementDescription.emitHtml(programInfo, buffer); |
| } |
| buffer.write("</div>"); |
| } else { |
| buffer.writeln(describe); |
| } |
| } |
| } |
| |
| /// A [CodeInfoNode] holds information about a piece of code. |
| class CodeInfoNode implements InfoNode { |
| /// A short description of the code. |
| final String description; |
| |
| final String generatedCode; |
| |
| get size => generatedCode.length; |
| |
| get name => ""; |
| |
| CodeInfoNode({this.description: "", this.generatedCode}); |
| |
| void emitHtml(ProgramInfo programInfo, StringBuffer buffer) { |
| buffer.write(div(description + ' ' + |
| sizeDescription(generatedCode.length, programInfo), |
| cls: 'kind') + |
| code(esc(generatedCode))); |
| } |
| } |
| |
| /// Instances represent information inferred about the program such as |
| /// inferred type information or inferred side effects. |
| class InferredInfoNode implements InfoNode { |
| /// Text describing the represented information. |
| final String description; |
| |
| /// The name of the entity this information is inferred about (for example the |
| /// name of a parameter). |
| final String name; |
| |
| /// The inferred type/side effect. |
| final String type; |
| |
| get size => 0; |
| |
| InferredInfoNode({this.name: "", this.description, this.type}); |
| |
| void emitHtml(ProgramInfo programInfo, StringSink buffer) { |
| buffer.write(div('${span("Inferred " + description, cls: "kind")} ' |
| '${span(esc(name), |
| cls: "name")} ' |
| '${span(esc(type), cls: 'type')} ')); |
| } |
| } |
| |
| /// Instances represent information about a program. |
| class ProgramInfo { |
| /// A list of all the libraries in the program to show information about. |
| final List<InfoNode> libraries; |
| |
| /// The size of the whole program in bytes. |
| final int size; |
| |
| /// The time the compilation took place. |
| final DateTime compilationMoment; |
| |
| /// The time the compilation took to complete. |
| final Duration compilationDuration; |
| |
| /// The version of dart2js used to compile the program. |
| final String dart2jsVersion; |
| |
| ProgramInfo({this.libraries, |
| this.size, |
| this.compilationMoment, |
| this.compilationDuration, |
| this.dart2jsVersion}); |
| } |
| |
| class InfoDumpVisitor extends ElementVisitor<InfoNode> { |
| final Compiler compiler; |
| |
| /// Contains the elements visited on the path from the library to here. |
| List<Element> stack = new List<Element>(); |
| |
| Element get currentElement => stack.last; |
| |
| InfoDumpVisitor(Compiler this.compiler); |
| |
| InfoNode visitElement(Element element) { |
| compiler.internalError("This element of kind ${element.kind} " |
| "does not support --dump-info", |
| token: element.position()); |
| return null; |
| } |
| |
| InfoNode visitLibraryElement(LibraryElement element) { |
| List<InfoNode> contents = new List<InfoNode>(); |
| int size = compiler.dumpInfoTask.codeSizeCounter |
| .getGeneratedSizeOf(element); |
| if (size == 0) return null; |
| stack.add(element); |
| element.forEachLocalMember((Element member) { |
| InfoNode info = member.accept(this); |
| if (info != null) { |
| contents.add(info); |
| } |
| }); |
| stack.removeLast(); |
| String nameString = element.getLibraryName() == "" |
| ? "<unnamed>" |
| : element.getLibraryName(); |
| contents.sort((InfoNode e1, InfoNode e2) { |
| return e1.name.compareTo(e2.name); |
| }); |
| return new ElementInfoNode( |
| extra: "${element.canonicalUri}", |
| kind: "library", |
| name: nameString, |
| size: size, |
| modifiers: "", |
| contents: contents); |
| } |
| |
| InfoNode visitTypedefElement(TypedefElement element) { |
| return element.alias == null |
| ? null |
| : new ElementInfoNode( |
| type: element.alias.toString(), |
| kind: "typedef", |
| name: element.name); |
| } |
| |
| InfoNode visitFieldElement(FieldElement element) { |
| CodeBuffer emittedCode = compiler.backend.codeOf(element); |
| int size = 0; |
| DartType type = element.computeType(compiler); |
| TypeMask inferredType = compiler.typesTask |
| .getGuaranteedTypeOfElement(element); |
| // If a field has an empty inferred type it is never used. |
| if ((inferredType == null || inferredType.isEmpty) && emittedCode == null) { |
| return null; |
| } |
| List<InfoNode> contents = new List<InfoNode>(); |
| if (emittedCode != null) { |
| contents.add(new CodeInfoNode( |
| description: "Generated initializer", |
| generatedCode: emittedCode.getText())); |
| size = emittedCode.length; |
| } |
| if (inferredType != null) { |
| contents.add(new InferredInfoNode( |
| description: "type", |
| type: inferredType.toString())); |
| stack.add(element); |
| } |
| for (Element closure in element.nestedClosures) { |
| InfoNode info = closure.accept(this); |
| if (info != null) { |
| contents.add(info); |
| size += info.size; |
| } |
| } |
| stack.removeLast(); |
| |
| return new ElementInfoNode( |
| kind: "field", |
| type: "$type", |
| name: element.name, |
| size: size, |
| modifiers: "${element.modifiers}", |
| contents: contents); |
| } |
| |
| InfoNode visitClassElement(ClassElement element) { |
| // If the element is not resolved it is not used in the program, and we omit |
| // it from the output. |
| if (!element.isResolved) return null; |
| String modifiersString = "${element.modifiers}"; |
| String supersString = element.allSupertypes == null ? "" : |
| "implements ${element.allSupertypes}"; |
| List contents = []; |
| stack.add(element); |
| element.forEachLocalMember((Element member) { |
| InfoNode info = member.accept(this); |
| if (info != null) { |
| contents.add(info); |
| } |
| }); |
| stack.removeLast(); |
| if (contents.isEmpty) { |
| // TODO (sigurdm): Only return here if the class is never used in type |
| // checks. |
| return null; |
| } |
| contents.sort((InfoNode n1, InfoNode n2) { |
| return n1.name.compareTo(n2.name); |
| }); |
| return new ElementInfoNode( |
| kind: "class", |
| name: element.name, |
| extra: supersString, |
| modifiers: modifiersString, |
| contents: contents); |
| } |
| |
| InfoNode visitFunctionElement(FunctionElement element) { |
| CodeBuffer emittedCode = compiler.backend.codeOf(element); |
| int size = 0; |
| String nameString = element.name; |
| String modifiersString = "${element.modifiers}"; |
| String kindString = "function"; |
| if (currentElement.isClass()) { |
| kindString = "method"; |
| } else if (currentElement.isField() || |
| currentElement.isFunction() || |
| currentElement.isConstructor()) { |
| kindString = "closure"; |
| nameString = "<unnamed>"; |
| } |
| if (element.isConstructor()) { |
| nameString = element.name == "" |
| ? "${element.enclosingElement.name}" |
| : "${element.enclosingElement.name}.${element.name}"; |
| kindString = "constructor"; |
| } |
| List contents = []; |
| if (emittedCode != null) { |
| FunctionSignature signature = element.computeSignature(compiler); |
| signature.forEachParameter((parameter) { |
| contents.add(new InferredInfoNode( |
| description: "parameter", |
| name: parameter.name, |
| type: compiler.typesTask |
| .getGuaranteedTypeOfElement(parameter).toString())); |
| }); |
| contents.add(new InferredInfoNode( |
| description: "return type", |
| type: compiler.typesTask |
| .getGuaranteedReturnTypeOfElement(element).toString())); |
| contents.add(new InferredInfoNode( |
| description: "side effects", |
| type: compiler.world |
| .getSideEffectsOfElement(element).toString())); |
| contents.add(new CodeInfoNode( |
| description: "Generated code", |
| generatedCode: emittedCode.getText())); |
| size += emittedCode.length; |
| } |
| stack.add(element); |
| for (Element closure in element.nestedClosures) { |
| InfoNode info = closure.accept(this); |
| if (info != null) { |
| contents.add(info); |
| size += info.size; |
| } |
| } |
| stack.removeLast(); |
| if (size == 0) { |
| return null; |
| } |
| return new ElementInfoNode( |
| type: element.type.toString(), |
| kind: kindString, |
| name: nameString, |
| size: size, |
| modifiers: modifiersString, |
| contents: contents); |
| } |
| } |
| |
| class DumpInfoTask extends CompilerTask { |
| DumpInfoTask(Compiler compiler) |
| : infoDumpVisitor = new InfoDumpVisitor(compiler), |
| super(compiler); |
| |
| String name = "Dump Info"; |
| |
| final CodeSizeCounter codeSizeCounter = new CodeSizeCounter(); |
| |
| final InfoDumpVisitor infoDumpVisitor; |
| |
| void dumpInfo() { |
| measure(() { |
| ProgramInfo info = collectDumpInfo(); |
| StringBuffer buffer = new StringBuffer(); |
| dumpInfoHtml(info, buffer); |
| compiler.outputProvider('', 'info.html') |
| ..add(buffer.toString()) |
| ..close(); |
| }); |
| } |
| |
| ProgramInfo collectDumpInfo() { |
| List<LibraryElement> sortedLibraries = compiler.libraries.values.toList(); |
| sortedLibraries.sort((LibraryElement l1, LibraryElement l2) { |
| if (l1.isPlatformLibrary && !l2.isPlatformLibrary) { |
| return 1; |
| } else if (!l1.isPlatformLibrary && l2.isPlatformLibrary) { |
| return -1; |
| } |
| return l1.getLibraryName().compareTo(l2.getLibraryName()); |
| }); |
| |
| List<InfoNode> libraryInfos = new List<InfoNode>(); |
| libraryInfos.addAll(sortedLibraries |
| .map((library) => infoDumpVisitor.visit(library)) |
| .where((info) => info != null)); |
| |
| return new ProgramInfo( |
| compilationDuration: compiler.totalCompileTime.elapsed, |
| // TODO (sigurdm): Also count the size of deferred code |
| size: compiler.assembledCode.length, |
| libraries: libraryInfos, |
| compilationMoment: new DateTime.now(), |
| dart2jsVersion: compiler.hasBuildId ? compiler.buildId : null); |
| } |
| |
| void dumpInfoHtml(ProgramInfo info, StringSink buffer) { |
| int totalSize = info.size; |
| |
| buffer.writeln(""" |
| <html> |
| <head> |
| <title>Dart2JS compilation information</title> |
| <style> |
| code {margin-left: 20px; display: block;} |
| div.contained {margin-left: 20px;} |
| div {margin-top:0px; |
| margin-bottom: 0px; |
| white-space: pre; /*border: 1px solid;*/} |
| span.kind {} |
| span.modifiers {font-weight:bold;} |
| span.name {font-weight:bold; font-family: monospace;} |
| span.type {font-family: monospace; color:blue;} |
| </style> |
| </head> |
| <body> |
| <h1>Dart2js compilation information</h1>"""); |
| buffer.writeln(h2('Compilation took place: ' |
| '${info.compilationMoment}')); |
| buffer.writeln(h2('Compilation took: ' |
| '${info.compilationDuration.inSeconds} seconds')); |
| buffer.writeln(h2('Output size: ${info.size} bytes')); |
| if (info.dart2jsVersion != null) { |
| buffer.writeln(h2('Dart2js version: ${info.dart2jsVersion}')); |
| } |
| |
| info.libraries.forEach((InfoNode node) { |
| node.emitHtml(info, buffer); |
| }); |
| |
| |
| // TODO (sigurdm): This script should be written in dart |
| buffer.writeln(r""" |
| <script type="text/javascript"> |
| function toggler(element) { |
| return function(e) { |
| element.hidden = !element.hidden; |
| }; |
| } |
| var containers = document.getElementsByClassName('container'); |
| for (var i = 0; i < containers.length; i++) { |
| var container = containers[i]; |
| container.addEventListener('click', |
| toggler(container.nextElementSibling), false); |
| container.nextElementSibling.hidden = true; |
| }; |
| </script> |
| </body> |
| </html>"""); |
| } |
| } |