| // 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; |
| } |
| } |
| } |