blob: 727095f827dea52b8174b820909cb7cca5f7a9f9 [file] [log] [blame]
// 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());
}
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>""");
}
}