Chromium Code Reviews| Index: sdk/lib/_internal/compiler/implementation/dump_info.dart |
| diff --git a/sdk/lib/_internal/compiler/implementation/dump_info.dart b/sdk/lib/_internal/compiler/implementation/dump_info.dart |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..b43e9adab7218b9001217e9602fad6ecac2765d9 |
| --- /dev/null |
| +++ b/sdk/lib/_internal/compiler/implementation/dump_info.dart |
| @@ -0,0 +1,471 @@ |
| +// 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"); |
| +} |
| + |
| +abstract class InfoNode { |
| + String get name; |
| + |
| + int get size; |
| + |
| + void emitHtml(ProgramInfo programInfo, StringSink buffer); |
| +} |
| + |
| +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.
|
| + final String name; |
| + |
| + final String kind; |
| + |
| + final String type; |
| + |
| + final String extra; |
| + |
| + final String modifiers; |
| + |
| + /// Describes how many bytes the code for this element takes up in the |
| + /// generated output. |
| + final int size; |
| + |
| + 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); |
| + } |
| + } |
| +} |
| + |
| +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.
|
| + 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))); |
| + } |
| +} |
| + |
| +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.
|
| + final String description; |
| + |
| + final String name; |
| + |
| + 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.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.
|
| + } |
| +} |
| + |
| +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.
|
| + final String name; |
| + |
| + final String presentation; |
| + |
| + final List<InfoNode> libraries; |
| + |
| + // How many bytes is the output |
| + final int size; |
| + |
| + final DateTime compilationMoment; |
| + |
| + final int compilationDuration; |
| + |
| + final String dart2jsVersion; |
| + |
| + ProgramInfo({String this.name, |
| + 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.
|
| + this.libraries, |
| + 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.
|
| + 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.
|
| +} |
| + |
| +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>(); |
| + |
| + InfoDumpVisitor(Compiler this.compiler); |
| + |
| + InfoNode visitElement(Element element) { |
| + compiler.internalError("This element of kind ${element.kind} " |
| + "does not support --dump-info", |
| + token: element.position()); |
| + } |
| + |
| + 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.toString(), |
|
Johnni Winther
2013/12/10 10:40:53
Maybe use '${element.canonicalUri}' instead?
sigurdm
2013/12/10 11:50:39
Done.
|
| + 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 (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.
|
| + List<InfoNode> contents = new List<InfoNode>(); |
| + if (emittedCode != null) { |
| + contents.add(new CodeInfoNode( |
| + description: "Generated initializer", |
| + generatedCode: emittedCode.getText())); |
| + size = emittedCode.length; |
| + } |
| + 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.toString(), |
| + name: element.name, |
| + size: size, |
| + modifiers: element.modifiers.toString(), |
| + contents: contents); |
| + } |
| + |
| + InfoNode visitClassElement(ClassElement element) { |
| + 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.
|
| + ? "" |
| + : 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.
|
| + 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.
|
| + 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) { |
| + 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.
|
| + } |
| + 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.toString(); |
|
Johnni Winther
2013/12/10 10:40:53
Change `element.modifiers.toString()` to '${elemen
sigurdm
2013/12/10 11:50:39
Done.
|
| + String kindString = "function"; |
| + if (stack.last.isClass()) { |
| + kindString = "method"; |
| + } else if (stack.last.isField() || |
| + 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.
|
| + stack.last.isConstructor()) { |
|
Johnni Winther
2013/12/10 10:40:53
Ditto.
sigurdm
2013/12/10 11:50:39
Done.
|
| + 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((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.
|
| + |
| + return new ProgramInfo( |
| + compilationDuration: compiler.totalCompileTime.elapsedTicks, |
| + // 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/1000000} 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>"""); |
| + } |
| +} |