blob: bc3d0c060149528bf703b79ec0310058cc4a715a [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 'dart:convert'
show ChunkedConversionSink, JsonEncoder, StringConversionSink;
import 'package:dart2js_info/info.dart';
import '../compiler_new.dart';
import 'common/names.dart';
import 'common/tasks.dart' show CompilerTask;
import 'common.dart';
import 'common_elements.dart';
import 'compiler.dart' show Compiler;
import 'constants/values.dart' show ConstantValue, InterceptorConstantValue;
import 'deferred_load.dart' show OutputUnit;
import 'elements/elements.dart';
import 'elements/entities.dart';
import 'js/js.dart' as jsAst;
import 'js_backend/js_backend.dart' show JavaScriptBackend;
import 'types/types.dart'
show
GlobalTypeInferenceElementResult,
GlobalTypeInferenceMemberResult,
TypeMask;
import 'universe/world_builder.dart'
show CodegenWorldBuilder, ReceiverConstraint;
import 'universe/world_impact.dart'
show ImpactUseCase, WorldImpact, WorldImpactVisitorImpl;
import 'world.dart' show ClosedWorld;
class ElementInfoCollector {
final Compiler compiler;
final ClosedWorld closedWorld;
ElementEnvironment get environment => closedWorld.elementEnvironment;
CodegenWorldBuilder get codegenWorldBuilder => compiler.codegenWorldBuilder;
final AllInfo result = new AllInfo();
final Map<Entity, Info> _entityToInfo = <Entity, Info>{};
final Map<ConstantValue, Info> _constantToInfo = <ConstantValue, Info>{};
final Map<OutputUnit, OutputUnitInfo> _outputToInfo = {};
ElementInfoCollector(this.compiler, this.closedWorld);
void run() {
compiler.dumpInfoTask._constantToNode.forEach((constant, node) {
// TODO(sigmund): add dependencies on other constants
var size = compiler.dumpInfoTask._nodeToSize[node];
var code = jsAst.prettyPrint(node,
enableMinification: compiler.options.enableMinification);
var info = new ConstantInfo(
size: size, code: code, outputUnit: _unitInfoForConstant(constant));
_constantToInfo[constant] = info;
result.constants.add(info);
});
environment.libraries.forEach(visitLibrary);
closedWorld.allTypedefs.forEach(visitTypedef);
}
/// Whether to emit information about [entity].
///
/// By default we emit information for any entity that contributes to the
/// output size. Either because it is a function being emitted or inlined,
/// or because it is an entity that holds dependencies to other entities.
bool shouldKeep(Entity entity) {
return compiler.dumpInfoTask.impacts.containsKey(entity) ||
compiler.dumpInfoTask.inlineCount.containsKey(entity);
}
LibraryInfo visitLibrary(LibraryEntity lib) {
String libname = environment.getLibraryName(lib);
if (libname.isEmpty) {
libname = '<unnamed>';
}
int size = compiler.dumpInfoTask.sizeOf(lib);
LibraryInfo info = new LibraryInfo(libname, lib.canonicalUri, null, size);
_entityToInfo[lib] = info;
environment.forEachLibraryMember(lib, (MemberEntity member) {
if (member.isFunction || member.isGetter || member.isSetter) {
FunctionInfo functionInfo = visitFunction(member);
if (functionInfo != null) {
info.topLevelFunctions.add(functionInfo);
functionInfo.parent = info;
}
} else if (member.isField) {
FieldInfo fieldInfo = visitField(member);
if (fieldInfo != null) {
info.topLevelVariables.add(fieldInfo);
fieldInfo.parent = info;
}
}
});
environment.forEachClass(lib, (ClassEntity clazz) {
ClassInfo classInfo = visitClass(clazz);
if (classInfo != null) {
info.classes.add(classInfo);
classInfo.parent = info;
}
});
if (info.isEmpty && !shouldKeep(lib)) return null;
result.libraries.add(info);
return info;
}
TypedefInfo visitTypedef(TypedefEntity typdef) {
var type = environment.getFunctionTypeOfTypedef(typdef);
TypedefInfo info =
new TypedefInfo(typdef.name, '$type', _unitInfoForEntity(typdef));
_entityToInfo[typdef] = info;
LibraryInfo lib = _entityToInfo[typdef.library];
lib.typedefs.add(info);
info.parent = lib;
result.typedefs.add(info);
return info;
}
GlobalTypeInferenceMemberResult _resultOfMember(MemberEntity e) =>
compiler.globalInference.results.resultOfMember(e);
GlobalTypeInferenceElementResult _resultOfParameter(Local e) =>
compiler.globalInference.results.resultOfParameter(e);
FieldInfo visitField(FieldEntity field) {
if (!_hasBeenResolved(field)) return null;
TypeMask inferredType = _resultOfMember(field).type;
// If a field has an empty inferred type it is never used.
if (inferredType == null || inferredType.isEmpty) return null;
int size = compiler.dumpInfoTask.sizeOf(field);
String code = compiler.dumpInfoTask.codeOf(field);
if (code != null) size += code.length;
FieldInfo info = new FieldInfo(
name: field.name,
type: '${environment.getFieldType(field)}',
inferredType: '$inferredType',
code: code,
outputUnit: _unitInfoForEntity(field),
isConst: field.isConst);
_entityToInfo[field] = info;
if (codegenWorldBuilder.hasConstantFieldInitializer(field)) {
info.initializer = _constantToInfo[
codegenWorldBuilder.getConstantFieldInitializer(field)];
}
if (JavaScriptBackend.TRACE_METHOD == 'post') {
// We use field.hashCode because it is globally unique and it is
// available while we are doing codegen.
info.coverageId = '${field.hashCode}';
}
int closureSize = _addClosureInfo(info, field);
info.size = size + closureSize;
result.fields.add(info);
return info;
}
ClassInfo visitClass(ClassEntity clazz) {
// Omit class if it is not needed.
if (!_hasClassBeenResolved(clazz)) return null;
ClassInfo classInfo = new ClassInfo(
name: clazz.name,
isAbstract: clazz.isAbstract,
outputUnit: _unitInfoForEntity(clazz));
_entityToInfo[clazz] = classInfo;
int size = compiler.dumpInfoTask.sizeOf(clazz);
environment.forEachLocalClassMember(clazz, (member) {
if (member.isFunction || member.isGetter || member.isSetter) {
FunctionInfo functionInfo = visitFunction(member);
if (functionInfo != null) {
classInfo.functions.add(functionInfo);
functionInfo.parent = classInfo;
for (var closureInfo in functionInfo.closures) {
size += closureInfo.size;
}
}
} else if (member.isField) {
FieldInfo fieldInfo = visitField(member);
if (fieldInfo != null) {
classInfo.fields.add(fieldInfo);
fieldInfo.parent = classInfo;
for (var closureInfo in fieldInfo.closures) {
size += closureInfo.size;
}
}
} else {
throw new StateError('Class member not a function or field');
}
});
environment.forEachConstructor(clazz, (constructor) {
FunctionInfo functionInfo = visitFunction(constructor);
if (functionInfo != null) {
classInfo.functions.add(functionInfo);
functionInfo.parent = classInfo;
for (var closureInfo in functionInfo.closures) {
size += closureInfo.size;
}
}
}, ensureResolved: false);
classInfo.size = size;
if (!compiler.backend.emitter.neededClasses.contains(clazz) &&
classInfo.fields.isEmpty &&
classInfo.functions.isEmpty) {
return null;
}
result.classes.add(classInfo);
return classInfo;
}
ClosureInfo visitClosureClass(ClassEntity element) {
ClosureInfo closureInfo = new ClosureInfo(
name: element.name,
outputUnit: _unitInfoForEntity(element),
size: compiler.dumpInfoTask.sizeOf(element));
_entityToInfo[element] = closureInfo;
FunctionEntity callMethod = closedWorld.elementEnvironment
.lookupClassMember(element, Identifiers.call);
FunctionInfo functionInfo = visitFunction(callMethod);
if (functionInfo == null) return null;
closureInfo.function = functionInfo;
functionInfo.parent = closureInfo;
result.closures.add(closureInfo);
return closureInfo;
}
FunctionInfo visitFunction(FunctionEntity function) {
int size = compiler.dumpInfoTask.sizeOf(function);
// TODO(sigmund): consider adding a small info to represent unreachable
// code here.
if (size == 0 && !shouldKeep(function)) return null;
// TODO(het): use 'toString' instead of 'text'? It will add '=' for setters
String name = function.memberName.text;
int kind;
if (function.isStatic || function.isTopLevel) {
kind = FunctionInfo.TOP_LEVEL_FUNCTION_KIND;
} else if (function.enclosingClass != null) {
kind = FunctionInfo.METHOD_FUNCTION_KIND;
}
if (function.isConstructor) {
name = name == ""
? "${function.enclosingClass.name}"
: "${function.enclosingClass.name}.${function.name}";
kind = FunctionInfo.CONSTRUCTOR_FUNCTION_KIND;
}
assert(kind != null);
FunctionModifiers modifiers = new FunctionModifiers(
isStatic: function.isStatic,
isConst: function.isConst,
isFactory: function.isConstructor
? (function as ConstructorEntity).isFactoryConstructor
: false,
isExternal: function.isExternal,
);
String code = compiler.dumpInfoTask.codeOf(function);
List<ParameterInfo> parameters = <ParameterInfo>[];
List<String> inferredParameterTypes = <String>[];
codegenWorldBuilder.forEachParameterAsLocal(function, (parameter) {
inferredParameterTypes.add('${_resultOfParameter(parameter).type}');
});
int parameterIndex = 0;
codegenWorldBuilder.forEachParameter(function, (type, name, _) {
parameters.add(new ParameterInfo(
name, inferredParameterTypes[parameterIndex++], '$type'));
});
var functionType = environment.getFunctionType(function);
String returnType = '${functionType.returnType}';
String inferredReturnType = '${_resultOfMember(function).returnType}';
String sideEffects = '${closedWorld.getSideEffectsOfElement(function)}';
int inlinedCount = compiler.dumpInfoTask.inlineCount[function];
if (inlinedCount == null) inlinedCount = 0;
FunctionInfo info = new FunctionInfo(
name: name,
functionKind: kind,
modifiers: modifiers,
returnType: returnType,
inferredReturnType: inferredReturnType,
parameters: parameters,
sideEffects: sideEffects,
inlinedCount: inlinedCount,
code: code,
type: functionType.toString(),
outputUnit: _unitInfoForEntity(function));
_entityToInfo[function] = info;
int closureSize = _addClosureInfo(info, function);
size += closureSize;
if (JavaScriptBackend.TRACE_METHOD == 'post') {
// We use function.hashCode because it is globally unique and it is
// available while we are doing codegen.
info.coverageId = '${function.hashCode}';
}
info.size = size;
result.functions.add(info);
return info;
}
/// Adds closure information to [info], using all nested closures in [member].
///
/// Returns the total size of the nested closures, to add to the info size.
int _addClosureInfo(Info info, MemberEntity member) {
assert(info is FunctionInfo || info is FieldInfo);
int size = 0;
List<ClosureInfo> nestedClosures = <ClosureInfo>[];
environment.forEachNestedClosure(member, (closure) {
ClosureInfo closureInfo = visitClosureClass(closure.enclosingClass);
if (closureInfo != null) {
closureInfo.parent = info;
nestedClosures.add(closureInfo);
size += closureInfo.size;
}
});
if (info is FunctionInfo) info.closures = nestedClosures;
if (info is FieldInfo) info.closures = nestedClosures;
return size;
}
OutputUnitInfo _infoFromOutputUnit(OutputUnit outputUnit) {
return _outputToInfo.putIfAbsent(outputUnit, () {
// Dump-info currently only works with the full emitter. If another
// emitter is used it will fail here.
JavaScriptBackend backend = compiler.backend;
assert(outputUnit.name != null || outputUnit.isMainOutput);
OutputUnitInfo info = new OutputUnitInfo(
outputUnit.name, backend.emitter.emitter.generatedSize(outputUnit));
info.imports.addAll(compiler.deferredLoadTask.getImportNames(outputUnit));
result.outputUnits.add(info);
return info;
});
}
OutputUnitInfo _unitInfoForEntity(Entity entity) {
return _infoFromOutputUnit(
compiler.backend.outputUnitData.outputUnitForEntity(entity));
}
OutputUnitInfo _unitInfoForConstant(ConstantValue constant) {
OutputUnit outputUnit =
compiler.backend.outputUnitData.outputUnitForConstant(constant);
if (outputUnit == null) {
assert(constant is InterceptorConstantValue);
return null;
}
return _infoFromOutputUnit(outputUnit);
}
bool _hasBeenResolved(Entity entity) {
return compiler.enqueuer.codegenEnqueuerForTesting.processedEntities
.contains(entity);
}
bool _hasClassBeenResolved(ClassEntity cls) {
return compiler.backend.mirrorsData.isClassResolved(cls);
}
}
class Selection {
final Entity selectedEntity;
final ReceiverConstraint mask;
Selection(this.selectedEntity, this.mask);
}
/// Interface used to record information from different parts of the compiler so
/// we can emit them in the dump-info task.
// TODO(sigmund,het): move more features here. Ideally the dump-info task
// shouldn't reach into internals of other parts of the compiler. For example,
// we currently reach into the full emitter and as a result we don't support
// dump-info when using the startup-emitter (issue #24190).
abstract class InfoReporter {
void reportInlined(FunctionEntity element, MemberEntity inlinedFrom);
}
class DumpInfoTask extends CompilerTask implements InfoReporter {
static const ImpactUseCase IMPACT_USE = const ImpactUseCase('Dump info');
final Compiler compiler;
DumpInfoTask(Compiler compiler)
: compiler = compiler,
super(compiler.measurer);
String get name => "Dump Info";
ElementInfoCollector infoCollector;
/// The size of the generated output.
int _programSize;
// A set of javascript AST nodes that we care about the size of.
// This set is automatically populated when registerEntityAst()
// is called.
final Set<jsAst.Node> _tracking = new Set<jsAst.Node>();
// A mapping from Dart Entities to Javascript AST Nodes.
final Map<Entity, List<jsAst.Node>> _entityToNodes =
<Entity, List<jsAst.Node>>{};
final Map<ConstantValue, jsAst.Node> _constantToNode =
<ConstantValue, jsAst.Node>{};
// A mapping from Javascript AST Nodes to the size of their
// pretty-printed contents.
final Map<jsAst.Node, int> _nodeToSize = <jsAst.Node, int>{};
final Map<Entity, int> inlineCount = <Entity, int>{};
// A mapping from an entity to a list of entities that are
// inlined inside of it.
final Map<Entity, List<Entity>> inlineMap = <Entity, List<Entity>>{};
final Map<MemberEntity, WorldImpact> impacts = <MemberEntity, WorldImpact>{};
/// Register the size of the generated output.
void reportSize(int programSize) {
_programSize = programSize;
}
void reportInlined(FunctionEntity element, MemberEntity inlinedFrom) {
assert(!(element is MethodElement && !element.isDeclaration));
assert(!(inlinedFrom is MemberElement && !inlinedFrom.isDeclaration));
inlineCount.putIfAbsent(element, () => 0);
inlineCount[element] += 1;
inlineMap.putIfAbsent(inlinedFrom, () => new List<Entity>());
inlineMap[inlinedFrom].add(element);
}
void registerImpact(MemberEntity member, WorldImpact impact) {
if (compiler.options.dumpInfo) {
impacts[member] = impact;
}
}
void unregisterImpact(var impactSource) {
impacts.remove(impactSource);
}
/// Returns an iterable of [Selection]s that are used by [entity]. Each
/// [Selection] contains an entity that is used and the selector that
/// selected the entity.
Iterable<Selection> getRetaining(Entity entity, ClosedWorld closedWorld) {
WorldImpact impact = impacts[entity];
if (impact == null) return const <Selection>[];
var selections = <Selection>[];
compiler.impactStrategy.visitImpact(
entity,
impact,
new WorldImpactVisitorImpl(visitDynamicUse: (dynamicUse) {
selections.addAll(closedWorld
.locateMembers(dynamicUse.selector, dynamicUse.mask)
.map((MemberEntity e) => new Selection(e, dynamicUse.mask)));
}, visitStaticUse: (staticUse) {
selections.add(new Selection(staticUse.element, null));
}),
IMPACT_USE);
return selections;
}
// Returns true if we care about tracking the size of
// this node.
bool isTracking(jsAst.Node code) {
if (compiler.options.dumpInfo) {
return _tracking.contains(code);
} else {
return false;
}
}
/// Registers that a javascript AST node [code] was produced by the dart
/// Entity [entity].
void registerEntityAst(Entity entity, jsAst.Node code) {
if (compiler.options.dumpInfo) {
_entityToNodes
.putIfAbsent(entity, () => new List<jsAst.Node>())
.add(code);
_tracking.add(code);
}
}
void registerConstantAst(ConstantValue constant, jsAst.Node code) {
if (compiler.options.dumpInfo) {
assert(_constantToNode[constant] == null ||
_constantToNode[constant] == code);
_constantToNode[constant] = code;
_tracking.add(code);
}
}
/// Records the size of a dart AST node after it has been pretty-printed into
/// the output buffer.
void recordAstSize(jsAst.Node node, int size) {
if (isTracking(node)) {
//TODO: should I be incrementing here instead?
_nodeToSize[node] = size;
}
}
/// Returns the size of the source code that was generated for an entity.
/// If no source code was produced, return 0.
int sizeOf(Entity entity) {
if (_entityToNodes.containsKey(entity)) {
return _entityToNodes[entity].map(sizeOfNode).fold(0, (a, b) => a + b);
} else {
return 0;
}
}
int sizeOfNode(jsAst.Node node) {
// TODO(sigmund): switch back to null aware operators (issue #24136)
var size = _nodeToSize[node];
return size == null ? 0 : size;
}
String codeOf(Entity entity) {
List<jsAst.Node> code = _entityToNodes[entity];
if (code == null) return null;
// Concatenate rendered ASTs.
StringBuffer sb = new StringBuffer();
for (jsAst.Node ast in code) {
sb.writeln(jsAst.prettyPrint(ast,
enableMinification: compiler.options.enableMinification));
}
return sb.toString();
}
void dumpInfo(ClosedWorld closedWorld) {
measure(() {
infoCollector = new ElementInfoCollector(compiler, closedWorld)..run();
StringBuffer jsonBuffer = new StringBuffer();
dumpInfoJson(jsonBuffer, closedWorld);
compiler.outputProvider.createOutputSink(
compiler.options.outputUri.pathSegments.last,
'info.json',
OutputType.info)
..add(jsonBuffer.toString())
..close();
});
}
void dumpInfoJson(StringSink buffer, ClosedWorld closedWorld) {
JsonEncoder encoder = const JsonEncoder.withIndent(' ');
Stopwatch stopwatch = new Stopwatch();
stopwatch.start();
AllInfo result = infoCollector.result;
// Recursively build links to function uses
Iterable<Entity> functionEntities =
infoCollector._entityToInfo.keys.where((k) => k is FunctionEntity);
for (FunctionEntity entity in functionEntities) {
FunctionInfo info = infoCollector._entityToInfo[entity];
Iterable<Selection> uses = getRetaining(entity, closedWorld);
// Don't bother recording an empty list of dependencies.
for (Selection selection in uses) {
// Don't register dart2js builtin functions that are not recorded.
Info useInfo = infoCollector._entityToInfo[selection.selectedEntity];
if (useInfo == null) continue;
info.uses.add(new DependencyInfo(useInfo, '${selection.mask}'));
}
}
// Recursively build links to field uses
Iterable<Entity> fieldEntity =
infoCollector._entityToInfo.keys.where((k) => k is FieldEntity);
for (FieldEntity entity in fieldEntity) {
FieldInfo info = infoCollector._entityToInfo[entity];
Iterable<Selection> uses = getRetaining(entity, closedWorld);
// Don't bother recording an empty list of dependencies.
for (Selection selection in uses) {
Info useInfo = infoCollector._entityToInfo[selection.selectedEntity];
if (useInfo == null) continue;
info.uses.add(new DependencyInfo(useInfo, '${selection.mask}'));
}
}
// Notify the impact strategy impacts are no longer needed for dump info.
compiler.impactStrategy.onImpactUsed(IMPACT_USE);
// Track dependencies that come from inlining.
for (Entity entity in inlineMap.keys) {
CodeInfo outerInfo = infoCollector._entityToInfo[entity];
if (outerInfo == null) continue;
for (Entity inlined in inlineMap[entity]) {
Info inlinedInfo = infoCollector._entityToInfo[inlined];
if (inlinedInfo == null) continue;
outerInfo.uses.add(new DependencyInfo(inlinedInfo, 'inlined'));
}
}
result.deferredFiles = compiler.deferredLoadTask.computeDeferredMap();
stopwatch.stop();
result.program = new ProgramInfo(
entrypoint: infoCollector
._entityToInfo[closedWorld.elementEnvironment.mainFunction],
size: _programSize,
dart2jsVersion:
compiler.options.hasBuildId ? compiler.options.buildId : null,
compilationMoment: new DateTime.now(),
compilationDuration: compiler.measurer.wallClock.elapsed,
toJsonDuration:
new Duration(milliseconds: stopwatch.elapsedMilliseconds),
dumpInfoDuration: new Duration(milliseconds: this.timing),
noSuchMethodEnabled: closedWorld.backendUsage.isNoSuchMethodUsed,
minified: compiler.options.enableMinification);
ChunkedConversionSink<Object> sink = encoder.startChunkedConversion(
new StringConversionSink.fromStringSink(buffer));
sink.add(new AllInfoJsonCodec().encode(result));
compiler.reporter.reportInfo(NO_LOCATION_SPANNABLE, MessageKind.GENERIC, {
'text': "View the dumped .info.json file at "
"https://dart-lang.github.io/dump-info-visualizer"
});
}
}