blob: 47ea3a1bc0c4f059188e0f53938d5992644f837a [file] [log] [blame]
// Copyright (c) 2014, 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 deferred_load;
import 'dart2jslib.dart' show
Compiler,
CompilerTask,
Constant,
ConstructedConstant,
MessageKind,
StringConstant,
invariant;
import 'elements/elements.dart' show
Element,
ClassElement,
ElementKind,
Elements,
FunctionElement,
LibraryElement,
MetadataAnnotation,
ScopeContainerElement,
ClosureContainer;
import 'util/util.dart' show
Link;
import 'util/setlet.dart' show
Setlet;
import 'tree/tree.dart' show
LibraryTag,
Node,
NewExpression,
Import,
LiteralString,
LiteralDartString;
import 'resolution/resolution.dart' show
TreeElements;
import 'mirrors_used.dart' show
MirrorUsageAnalyzer,
MirrorUsageAnalyzerTask,
MirrorUsage;
/// A "hunk" of the program that will be loaded whenever one of its [imports]
/// are loaded.
///
/// Elements that are only used in one deferred import, is in an OutputUnit with
/// the deferred import as single element in the [imports] set.
///
/// Whenever a deferred Element is shared between several deferred imports it is
/// in an output unit with those imports in the [imports] Set.
///
/// OutputUnits are equal if their [imports] are equal.
class OutputUnit {
/// The deferred imports that will load this output unit when one of them is
/// loaded.
final Setlet<Import> imports = new Setlet<Import>();
/// A unique name representing this [OutputUnit].
/// Based on the set of [imports].
String name;
String toString() => "OutputUnit($name)";
bool operator==(OutputUnit other) {
return imports.length == other.imports.length &&
imports.containsAll(other.imports);
}
int get hashCode {
int sum = 0;
for (Import import in imports) {
sum = (sum + import.hashCode) & 0x3FFFFFFF; // Stay in 30 bit range.
}
return sum;
}
}
/// For each deferred import, find elements and constants to be loaded when that
/// import is loaded. Elements that are used by several deferred imports are in
/// shared OutputUnits.
class DeferredLoadTask extends CompilerTask {
/// The name of this task.
String get name => 'Deferred Loading';
/// DeferredLibrary from dart:async
ClassElement get deferredLibraryClass => compiler.deferredLibraryClass;
/// A synthetic [Import] representing the loading of the main
/// program.
final Import _fakeMainImport = new Import(null, new LiteralString(null,
new LiteralDartString("main")), null, null, null);
/// The OutputUnit that will be loaded when the program starts.
final OutputUnit mainOutputUnit = new OutputUnit();
/// A set containing (eventually) all output units that will result from the
/// program.
final Set<OutputUnit> allOutputUnits = new Set<OutputUnit>();
/// Will be `true` if the program contains deferred libraries.
bool splitProgram = false;
/// A mapping from the name of a [DeferredLibrary] annotation to all dependent
/// output units.
final Map<String, Set<OutputUnit>> hunksToLoad =
new Map<String, Set<OutputUnit>>();
/// A mapping from elements and constants to their output unit. Query this via
/// [outputUnitForElement]
final Map<Element, OutputUnit> _elementToOutputUnit =
new Map<Element, OutputUnit>();
/// A mapping from constants to their output unit. Query this via
/// [outputUnitForConstant]
final Map<Constant, OutputUnit> _constantToOutputUnit =
new Map<Constant, OutputUnit>();
/// All the imports with a [DeferredLibrary] annotation, mapped to the
/// [LibraryElement] they import.
/// The main library is included in this set for convenience.
final Map<Import, LibraryElement> _allDeferredImports =
new Map<Import, LibraryElement>();
// For each deferred import we want to know exactly what elements have to
// be loaded.
Map<Import, Set<Element>> _importedDeferredBy = null;
Map<Import, Set<Constant>> _constantsDeferredBy = null;
Set<Element> _mainElements = new Set<Element>();
DeferredLoadTask(Compiler compiler) : super(compiler);
/// Returns the [OutputUnit] where [element] belongs.
OutputUnit outputUnitForElement(Element element) {
if (!splitProgram) return mainOutputUnit;
element = element.implementation;
while (!_elementToOutputUnit.containsKey(element)) {
element = element.enclosingElement.implementation;
}
return _elementToOutputUnit[element];
}
/// Returns the [OutputUnit] where [constant] belongs.
OutputUnit outputUnitForConstant(Constant constant) {
if (!splitProgram) return mainOutputUnit;
return _constantToOutputUnit[constant];
}
bool isDeferred(Element element) {
return outputUnitForElement(element) != mainOutputUnit;
}
/// Mark that [import] is part of the [OutputputUnit] for [element].
///
/// [element] can be either a [Constant] or an [Element].
void _addImportToOutputUnitOfElement(Element element, Import import) {
// Only one file should be loaded when the program starts, so make
// sure that only one OutputUnit is created for [fakeMainImport].
if (import == _fakeMainImport) {
_elementToOutputUnit[element] = mainOutputUnit;
}
_elementToOutputUnit.putIfAbsent(element, () => new OutputUnit())
.imports.add(import);
}
/// Mark that [import] is part of the [OutputputUnit] for [constant].
///
/// [constant] can be either a [Constant] or an [Element].
void _addImportToOutputUnitOfConstant(Constant constant, Import import) {
// Only one file should be loaded when the program starts, so make
// sure that only one OutputUnit is created for [fakeMainImport].
if (import == _fakeMainImport) {
_constantToOutputUnit[constant] = mainOutputUnit;
}
_constantToOutputUnit.putIfAbsent(constant, () => new OutputUnit())
.imports.add(import);
}
/// Answers whether the [import] has a [DeferredLibrary] annotation.
bool _isImportDeferred(Import import) {
Link<MetadataAnnotation> metadatalist = import.metadata;
if (metadatalist == null) return false;
for (MetadataAnnotation metadata in metadatalist) {
metadata.ensureResolved(compiler);
Element element = metadata.value.computeType(compiler).element;
if (metadata.value.computeType(compiler).element
== deferredLibraryClass) {
return true;
}
}
return false;
}
/// Answers whether [element] is explicitly deferred when referred to from
/// [library].
bool _isExplicitlyDeferred(Element element, LibraryElement library) {
Link<Import> imports = _getImports(element, library);
// If the element is not imported explicitly, it is implicitly imported
// not deferred.
if (imports.isEmpty) return false;
// An element could potentially be loaded by several imports. If all of them
// is explicitly deferred, we say the element is explicitly deferred.
// TODO(sigurdm): We might want to give a warning if the imports do not
// agree.
return imports.every(_isImportDeferred);
}
/// Returns a [Link] of every [Import] that imports [element] into [library].
Link<Import> _getImports(Element element, LibraryElement library) {
if (!element.isTopLevel()) {
element = element.getEnclosingClass();
}
return library.getImportsFor(element);
}
/// Replaces the imports of [outputUnit] with those in
/// [replacementImports]. Because mainOutputUnit has a special handling we
/// create a new outputUnit instead, and update the mapping from the
/// dependency to its outputUnit.
void _replaceOutputUnitImports(dynamic dependency,
OutputUnit outputUnit,
Iterable<Import> replacementImports) {
Map<dynamic, OutputUnit> dependencyToOutputUnit = dependency is Element
? _elementToOutputUnit
: _constantToOutputUnit;
assert(outputUnit == dependencyToOutputUnit[dependency]);
if (outputUnit == mainOutputUnit) {
outputUnit = new OutputUnit();
dependencyToOutputUnit[dependency] = outputUnit;
} else {
outputUnit.imports.clear();
}
outputUnit.imports.addAll(replacementImports);
}
/// Collects all direct dependencies of [element].
///
/// The collected dependent elements and constants are are added to
/// [elementDependencies] and [constantDependencies] respectively.
void _collectDependencies(Element element,
Set<Element> elementDependencies,
Set<Constant> constantDependencies) {
TreeElements elements =
compiler.enqueuer.resolution.getCachedElements(element);
if (elements == null) return;
for (Element dependency in elements.allElements) {
if (Elements.isLocal(dependency) && !dependency.isFunction()) continue;
if (Elements.isUnresolved(dependency)) continue;
if (dependency.isStatement()) continue;
elementDependencies.add(dependency);
}
constantDependencies.addAll(elements.allConstants);
elementDependencies.addAll(elements.otherDependencies);
}
/// Finds all elements and constants that [element] depends directly on.
/// (not the transitive closure.)
///
/// Adds the results to [elements] and [constants].
void _collectAllElementsAndConstantsResolvedFrom(Element element,
Set<Element> elements,
Set<Constant> constants) {
element = element.implementation;
for (MetadataAnnotation metadata in element.metadata) {
if (metadata.value != null) constants.add(metadata.value);
}
if (element.isClass()) {
// If we see a class, add everything its instance members refer
// to. Static members are not relevant.
ClassElement cls = element.declaration;
cls.forEachLocalMember((Element e) {
if (!e.isInstanceMember()) return;
_collectDependencies(e.implementation, elements, constants);
});
if (cls.implementation != cls) {
// TODO(ahe): Why doesn't ClassElement.forEachLocalMember do this?
cls.implementation.forEachLocalMember((Element e) {
if (!e.isInstanceMember()) return;
_collectDependencies(e.implementation, elements, constants);
});
}
for (var type in cls.implementation.allSupertypes) {
elements.add(type.element.implementation);
}
elements.add(cls.implementation);
} else if (Elements.isStaticOrTopLevel(element) ||
element.isConstructor()) {
_collectDependencies(element, elements, constants);
}
if (element.isGenerativeConstructor()) {
// When instantiating a class, we record a reference to the
// constructor, not the class itself. We must add all the
// instance members of the constructor's class.
ClassElement implementation =
element.getEnclosingClass().implementation;
_collectAllElementsAndConstantsResolvedFrom(
implementation, elements, constants);
}
// Other elements, in particular instance members, are ignored as
// they are processed as part of the class.
}
/// Returns the transitive closure of all libraries that are imported
/// from root without DeferredLibrary annotations.
Set<LibraryElement> _nonDeferredReachableLibraries(LibraryElement root) {
Set<LibraryElement> result = new Set<LibraryElement>();
void traverseLibrary(LibraryElement library) {
if (result.contains(library)) return;
result.add(library);
// TODO(sigurdm): Make helper getLibraryImportTags when tags is changed to
// be a List instead of a Link.
for (LibraryTag tag in library.tags) {
if (tag is! Import) continue;
Import import = tag;
if (!_isImportDeferred(import)) {
LibraryElement importedLibrary = library.getLibraryFromTag(tag);
traverseLibrary(importedLibrary);
}
}
}
traverseLibrary(root);
return result;
}
/// Recursively traverses the graph of dependencies from [element], mapping
/// deferred imports to each dependency it needs in the sets
/// [_importedDeferredBy] and [_constantsDeferredBy].
void _mapDependencies(Element element, Import import) {
Set<Element> elements = _importedDeferredBy.putIfAbsent(import,
() => new Set<Element>());
Set<Constant> constants = _constantsDeferredBy.putIfAbsent(import,
() => new Set<Constant>());
if (elements.contains(element)) return;
// Anything used directly by main will be loaded from the start
// We do not need to traverse it again.
if (import != _fakeMainImport && _mainElements.contains(element)) return;
// Here we modify [_importedDeferredBy].
elements.add(element);
Set<Element> dependentElements = new Set<Element>();
// This call can modify [_importedDeferredBy] and [_constantsDeferredBy].
_collectAllElementsAndConstantsResolvedFrom(
element, dependentElements, constants);
LibraryElement library = element.getLibrary();
for (Element dependency in dependentElements) {
if (_isExplicitlyDeferred(dependency, library)) {
for (Import deferredImport in _getImports(dependency, library)) {
_mapDependencies(dependency, deferredImport);
};
} else {
_mapDependencies(dependency, import);
}
}
}
/// Adds extra dependencies coming from mirror usage.
///
/// The elements are added with [_mapDependencies].
void _addMirrorElements() {
MirrorUsageAnalyzerTask mirrorTask = compiler.mirrorUsageAnalyzerTask;
// For each import we record all mirrors-used elements from all the
// libraries reached directly from that import.
for (Import deferredImport in _allDeferredImports.keys) {
LibraryElement deferredLibrary = _allDeferredImports[deferredImport];
for (LibraryElement library in
_nonDeferredReachableLibraries(deferredLibrary)) {
if (mirrorTask.librariesWithUsage.contains(library)) {
Map<LibraryElement, List<MirrorUsage>> mirrorsResult =
mirrorTask.analyzer.collectMirrorsUsedAnnotation();
// If there is a MirrorsUsed annotation we add only the needed
// things to the output units for the library.
List<MirrorUsage> mirrorUsages = mirrorsResult[library];
if (mirrorUsages == null) continue;
for (MirrorUsage usage in mirrorUsages) {
if (usage.targets != null) {
for (Element dependency in usage.targets) {
_mapDependencies(dependency, deferredImport);
}
}
if (usage.metaTargets != null) {
for (Element dependency in usage.metaTargets) {
_mapDependencies(dependency, deferredImport);
}
}
}
} else {
// If there is no MirrorsUsed annotation we add _everything_ to
// the output units for the library.
// TODO(sigurdm): This is too expensive.
// Plan: If mirrors are used without MirrorsUsed, create an
// "EverythingElse" library that contains all elements that are
// not referred by main or deferred libraries that don't contain
// mirrors (without MirrorsUsed).
//
// So basically we want:
// mainImport
// deferredA
// deferredB
// deferredCwithMirrorsUsed
// deferredEverythingElse
//
// Where deferredEverythingElse will be loaded for *all* libraries
// that contain a mirror usage without MirrorsUsed.
// When loading the deferredEverythingElse also load all other
// deferred libraries at the same time.
bool usesMirrors = false;
for (LibraryTag tag in library.tags) {
if (tag is! Import) continue;
if (library.getLibraryFromTag(tag) == compiler.mirrorsLibrary) {
usesMirrors = true;
break;
}
}
if (usesMirrors) {
for (Link link in compiler.enqueuer.allElementsByName.values) {
for (Element dependency in link) {
_mapDependencies(dependency, deferredImport);
}
}
}
}
}
}
}
/// Goes through [allConstants] and adjusts their outputUnits.
void _adjustConstantsOutputUnit(Set<Constant> allConstants) {
// A constant has three dependencies:
// 1- the libraries it is used in.
// 2- its class.
// 3- its arguments.
// The constant should only be loaded if all three dependencies are
// loaded.
// TODO(floitsch): only load constants when all three dependencies are
// satisfied.
//
// So far we only looked at where the constants were used. For now, we
// use a simplified approach to fix this (partially): if the current
// library is not deferred, only look at the class (2). Otherwise store
// the constant in the current (deferred) library.
for (Constant constant in allConstants) {
// If the constant is not a "constructed" constant, it can stay where
// it is.
if (!constant.isConstructedObject()) continue;
OutputUnit constantUnit = _constantToOutputUnit[constant];
Setlet<Import> constantImports = constantUnit.imports;
ConstructedConstant constructed = constant;
Element classElement = constructed.type.element;
OutputUnit classUnit = _elementToOutputUnit[classElement];
// This happens with classes that are only used as annotations.
// TODO(sigurdm): Find out if we can use a specific check for this.
if (classUnit == null) continue;
Setlet<Import> classImports = classUnit.imports;
// The class exists in the main-unit. Just leave the constant where it
// is. We know that the constructor will be available.
if (classImports.length == 1 && classImports.single == _fakeMainImport) {
continue;
}
// The class is loaded for all imports in the classImport-set.
// If the constant's imports are included in the class' set, we can
// keep the constant unit as is.
// If the constant is used otherwise, we need to make sure that the
// class is available before constructing the constant.
if (classImports.containsAll(constantImports)) continue;
// We could now just copy the OutputUnit from the class to the output
// unit of the constant, but we prefer separate instances.
// Replace the imports of the constant to match the ones of the class.
_replaceOutputUnitImports(constant, constantUnit, classImports);
}
}
/// Computes a unique string for the name field for each outputUnit.
///
/// Also sets up the [hunksToLoad] mapping.
void _assignNamesToOutputUnits(Set<OutputUnit> allOutputUnits) {
Map<Import, String> deferNameCache = new Map<Import, String>();
// Finds the first argument to the [DeferredLibrary] annotation
String importDeferName(Import import) {
if (deferNameCache.containsKey(import)) return deferNameCache[import];
if (import == _fakeMainImport) return "main";
Link<MetadataAnnotation> metadatas = import.metadata;
assert(metadatas != null);
String result;
for (MetadataAnnotation metadata in metadatas) {
metadata.ensureResolved(compiler);
Element element = metadata.value.computeType(compiler).element;
if (metadata.value.computeType(compiler).element ==
deferredLibraryClass) {
ConstructedConstant constant = metadata.value;
StringConstant s = constant.fields[0];
result = s.value.slowToString();
break;
}
}
assert(result != null);
deferNameCache[import] = result;
return result;
}
Set<String> usedNames = new Set<String>();
Map<OutputUnit, String> generatedNames = new Map<OutputUnit, String>();
void computeOutputUnitName(OutputUnit outputUnit) {
if (generatedNames[outputUnit] != null) return;
String suggestedName = outputUnit.imports.map((import) {
return importDeferName(import);
}).join('_');
if (!usedNames.contains(suggestedName)) {
outputUnit.name = suggestedName;
} else {
int counter = 0;
while (usedNames.contains("$suggestedName$counter")) {
counter++;
}
outputUnit.name = "$suggestedName$counter";
}
generatedNames[outputUnit] = outputUnit.name;
}
for (OutputUnit outputUnit in allOutputUnits) {
computeOutputUnitName(outputUnit);
}
// For each deferred import we find out which outputUnits to load.
for (Import import in _allDeferredImports.keys) {
if (import == _fakeMainImport) continue;
hunksToLoad[importDeferName(import)] = new Set<OutputUnit>();
for (OutputUnit outputUnit in allOutputUnits) {
if (outputUnit == mainOutputUnit) continue;
if (outputUnit.imports.contains(import)) {
hunksToLoad[importDeferName(import)].add(outputUnit);
}
}
}
}
void onResolutionComplete(FunctionElement main) {
if (!splitProgram) {
allOutputUnits.add(mainOutputUnit);
return;
}
if (main == null) return;
LibraryElement mainLibrary = main.getLibrary();
_importedDeferredBy = new Map<Import, Set<Element>>();
_constantsDeferredBy = new Map<Import, Set<Constant>>();
_importedDeferredBy[_fakeMainImport] = _mainElements;
measureElement(mainLibrary, () {
// Starting from main, traverse the program and find all dependencies.
_mapDependencies(compiler.mainFunction, _fakeMainImport);
// Also add "global" dependencies to the main OutputUnit. These are
// things that the backend need but cannot associate with a particular
// element, for example, startRootIsolate. This set also contains
// elements for which we lack precise information.
for (Element element in compiler.globalDependencies.otherDependencies) {
_mapDependencies(element, _fakeMainImport);
}
// Now check to see if we have to add more elements due to mirrors.
if (compiler.mirrorsLibrary != null) {
_addMirrorElements();
}
Set<Constant> allConstants = new Set<Constant>();
// Reverse the mapping. For each element record an OutputUnit collecting
// all deferred imports using this element. Same for constants.
for (Import import in _importedDeferredBy.keys) {
for (Element element in _importedDeferredBy[import]) {
_addImportToOutputUnitOfElement(element, import);
}
for (Constant constant in _constantsDeferredBy[import]) {
allConstants.add(constant);
_addImportToOutputUnitOfConstant(constant, import);
}
}
// Release maps;
_importedDeferredBy = null;
_constantsDeferredBy = null;
_adjustConstantsOutputUnit(allConstants);
// Find all the output units we have used.
// Also generate a unique name for each OutputUnit.
for (OutputUnit outputUnit in _elementToOutputUnit.values) {
allOutputUnits.add(outputUnit);
}
for (OutputUnit outputUnit in _constantToOutputUnit.values) {
allOutputUnits.add(outputUnit);
}
_assignNamesToOutputUnits(allOutputUnits);
});
}
void ensureMetadataResolved(Compiler compiler) {
_allDeferredImports[_fakeMainImport] = compiler.mainApp;
bool deferredUsedFromMain = false;
var lastDeferred;
for (LibraryElement library in compiler.libraries.values) {
// TODO(sigurdm): Make helper getLibraryImportTags when tags is a List
// instead of a Link.
for (LibraryTag tag in library.tags) {
if (tag is! Import) continue;
Import import = tag;
if (_isImportDeferred(import)) {
splitProgram = true;
_allDeferredImports[tag] = library.getLibraryFromTag(tag);
lastDeferred = import.metadata.first;
if (library == compiler.mainApp) {
deferredUsedFromMain = true;
}
}
}
}
if (splitProgram && !deferredUsedFromMain) {
compiler.reportInfo(
lastDeferred,
MessageKind.DEFERRED_LIBRARY_NOT_FROM_MAIN);
splitProgram = false;
}
}
}