Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(81)

Unified Diff: sdk/lib/_internal/compiler/implementation/dump_info.dart

Issue 90713003: Dart2js option to dump info about compilation (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 7 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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..ae3eece896b6830e2fae8c874d9cbfe752d5d339
--- /dev/null
+++ b/sdk/lib/_internal/compiler/implementation/dump_info.dart
@@ -0,0 +1,501 @@
+// 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 complite.
floitsch 2013/12/11 13:04:19 complete
sigurdm 2013/12/11 14:01:08 Done.
+ final int 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());
+ }
+
+ 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.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'));
floitsch 2013/12/11 13:04:19 the compilationDuration is stored in ticks. A tick
sigurdm 2013/12/11 14:01:08 Good idea
+ 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>""");
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698