| // 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 'common/backend_api.dart' show Backend; |
| import 'common/tasks.dart' show CompilerTask; |
| import 'common.dart'; |
| import 'compiler.dart' show Compiler; |
| import 'constants/expressions.dart' show ConstantExpression; |
| import 'constants/values.dart' |
| show |
| ConstantValue, |
| ConstructedConstantValue, |
| DeferredConstantValue, |
| StringConstantValue; |
| import 'dart_types.dart'; |
| import 'elements/elements.dart' |
| show |
| AccessorElement, |
| AstElement, |
| ClassElement, |
| Element, |
| Elements, |
| ExportElement, |
| FunctionElement, |
| ImportElement, |
| LibraryElement, |
| MetadataAnnotation, |
| PrefixElement, |
| ResolvedAstKind, |
| TypedefElement; |
| import 'js_backend/js_backend.dart' show JavaScriptBackend; |
| import 'resolution/resolution.dart' show AnalyzableElementX; |
| import 'resolution/tree_elements.dart' show TreeElements; |
| import 'tree/tree.dart' as ast; |
| import 'universe/use.dart' show StaticUse, StaticUseKind, TypeUse, TypeUseKind; |
| import 'universe/world_impact.dart' |
| show ImpactUseCase, WorldImpact, WorldImpactVisitorImpl; |
| import 'util/setlet.dart' show Setlet; |
| import 'util/uri_extras.dart' as uri_extras; |
| import 'util/util.dart' show Link, makeUnique; |
| |
| /// 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<_DeferredImport> imports = new Setlet<_DeferredImport>(); |
| |
| /// `true` if this output unit is for the main output file. |
| final bool isMainOutput; |
| |
| /// A unique name representing this [OutputUnit]. |
| String name; |
| |
| OutputUnit({this.isMainOutput: false}); |
| |
| 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 (_DeferredImport 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.commonElements.deferredLibraryClass; |
| |
| /// A synthetic import representing the loading of the main program. |
| final _DeferredImport _fakeMainImport = const _DeferredImport(); |
| |
| /// The OutputUnit that will be loaded when the program starts. |
| final OutputUnit mainOutputUnit = new OutputUnit(isMainOutput: true); |
| |
| /// 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 isProgramSplit = false; |
| |
| static const ImpactUseCase IMPACT_USE = const ImpactUseCase('Deferred load'); |
| |
| /// A mapping from the name of a defer import to all the output units it |
| /// depends on in a list of lists to be loaded in the order they appear. |
| /// |
| /// For example {"lib1": [[lib1_lib2_lib3], [lib1_lib2, lib1_lib3], |
| /// [lib1]]} would mean that in order to load "lib1" first the hunk |
| /// lib1_lib2_lib2 should be loaded, then the hunks lib1_lib2 and lib1_lib3 |
| /// can be loaded in parallel. And finally lib1 can be loaded. |
| final Map<String, List<OutputUnit>> hunksToLoad = |
| new Map<String, List<OutputUnit>>(); |
| |
| /// A cache of the result of calling `computeImportDeferName` on the keys of |
| /// this map. |
| final Map<_DeferredImport, String> importDeferName = |
| <_DeferredImport, String>{}; |
| |
| /// 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<ConstantValue, OutputUnit> _constantToOutputUnit = |
| new Map<ConstantValue, 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<_DeferredImport, LibraryElement> _allDeferredImports = |
| new Map<_DeferredImport, LibraryElement>(); |
| |
| /// Because the token-stream is forgotten later in the program, we cache a |
| /// description of each deferred import. |
| final Map<_DeferredImport, ImportDescription> _deferredImportDescriptions = |
| <_DeferredImport, ImportDescription>{}; |
| |
| // For each deferred import we want to know exactly what elements have to |
| // be loaded. |
| Map<_DeferredImport, Set<Element>> _importedDeferredBy = null; |
| Map<_DeferredImport, Set<ConstantValue>> _constantsDeferredBy = null; |
| |
| Set<Element> _mainElements = new Set<Element>(); |
| |
| final Compiler compiler; |
| DeferredLoadTask(Compiler compiler) |
| : compiler = compiler, |
| super(compiler.measurer) { |
| mainOutputUnit.imports.add(_fakeMainImport); |
| } |
| |
| Backend get backend => compiler.backend; |
| DiagnosticReporter get reporter => compiler.reporter; |
| |
| /// Returns the [OutputUnit] where [element] belongs. |
| OutputUnit outputUnitForElement(Element element) { |
| if (!isProgramSplit) return mainOutputUnit; |
| |
| element = element.implementation; |
| while (!_elementToOutputUnit.containsKey(element)) { |
| // TODO(21051): workaround: it looks like we output annotation constants |
| // for classes that we don't include in the output. This seems to happen |
| // when we have reflection but can see that some classes are not needed. |
| // We still add the annotation but don't run through it below (where we |
| // assign every element to its output unit). |
| if (element.enclosingElement == null) { |
| _elementToOutputUnit[element] = mainOutputUnit; |
| break; |
| } |
| element = element.enclosingElement.implementation; |
| } |
| return _elementToOutputUnit[element]; |
| } |
| |
| /// Direct access to the output-unit to element relation used for testing. |
| OutputUnit getOutputUnitForElementForTesting(Element element) { |
| return _elementToOutputUnit[element]; |
| } |
| |
| /// Returns the [OutputUnit] where [constant] belongs. |
| OutputUnit outputUnitForConstant(ConstantValue constant) { |
| if (!isProgramSplit) return mainOutputUnit; |
| return _constantToOutputUnit[constant]; |
| } |
| |
| /// Direct access to the output-unit to constants map used for testing. |
| Map<ConstantValue, OutputUnit> get outputUnitForConstantsForTesting { |
| return _constantToOutputUnit; |
| } |
| |
| bool isDeferred(Element element) { |
| return outputUnitForElement(element) != mainOutputUnit; |
| } |
| |
| /// Returns the unique name for the deferred import of [prefix]. |
| String getImportDeferName(Spannable node, PrefixElement prefix) { |
| String name = |
| importDeferName[new _DeclaredDeferredImport(prefix.deferredImport)]; |
| if (name == null) { |
| reporter.internalError(node, "No deferred name for $prefix."); |
| } |
| return name; |
| } |
| |
| /// Returns `true` if element [to] is reachable from element [from] without |
| /// crossing a deferred import. |
| /// |
| /// For example, if we have two deferred libraries `A` and `B` that both |
| /// import a library `C`, then even though elements from `A` and `C` end up in |
| /// different output units, there is a non-deferred path between `A` and `C`. |
| bool hasOnlyNonDeferredImportPaths(Element from, Element to) { |
| OutputUnit outputUnitFrom = outputUnitForElement(from); |
| OutputUnit outputUnitTo = outputUnitForElement(to); |
| return outputUnitTo.imports.containsAll(outputUnitFrom.imports); |
| } |
| |
| // TODO(het): use a union-find to canonicalize output units |
| OutputUnit _getCanonicalUnit(OutputUnit outputUnit) { |
| OutputUnit representative = allOutputUnits.lookup(outputUnit); |
| if (representative == null) { |
| representative = outputUnit; |
| allOutputUnits.add(representative); |
| } |
| return representative; |
| } |
| |
| void registerConstantDeferredUse( |
| DeferredConstantValue constant, PrefixElement prefix) { |
| OutputUnit outputUnit = new OutputUnit(); |
| outputUnit.imports.add(new _DeclaredDeferredImport(prefix.deferredImport)); |
| |
| // Check to see if there is already a canonical output unit registered. |
| _constantToOutputUnit[constant] = _getCanonicalUnit(outputUnit); |
| } |
| |
| /// Answers whether [element] is explicitly deferred when referred to from |
| /// [library]. |
| bool _isExplicitlyDeferred(Element element, LibraryElement library) { |
| Iterable<ImportElement> 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((ImportElement import) => import.isDeferred); |
| } |
| |
| /// Returns every [ImportElement] that imports [element] into [library]. |
| Iterable<ImportElement> _getImports(Element element, LibraryElement library) { |
| if (element.isClassMember) { |
| element = element.enclosingClass; |
| } |
| if (element.isAccessor) { |
| element = (element as AccessorElement).abstractField; |
| } |
| return library.getImportsFor(element); |
| } |
| |
| /// 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<ConstantValue> constants, isMirrorUsage) { |
| if (element.isMalformed) { |
| // Malformed elements are ignored. |
| return; |
| } |
| |
| /// Recursively collects all the dependencies of [type]. |
| void collectTypeDependencies(DartType type) { |
| // TODO(het): we would like to separate out types that are only needed for |
| // rti from types that are needed for their members. |
| if (type is GenericType) { |
| type.typeArguments.forEach(collectTypeDependencies); |
| } |
| if (type is FunctionType) { |
| for (DartType argumentType in type.parameterTypes) { |
| collectTypeDependencies(argumentType); |
| } |
| for (DartType argumentType in type.optionalParameterTypes) { |
| collectTypeDependencies(argumentType); |
| } |
| for (DartType argumentType in type.namedParameterTypes) { |
| collectTypeDependencies(argumentType); |
| } |
| collectTypeDependencies(type.returnType); |
| } else if (type is TypedefType) { |
| elements.add(type.element); |
| collectTypeDependencies(type.unaliased); |
| } else if (type is InterfaceType) { |
| elements.add(type.element); |
| } |
| } |
| |
| /// Collects all direct dependencies of [element]. |
| /// |
| /// The collected dependent elements and constants are are added to |
| /// [elements] and [constants] respectively. |
| void collectDependencies(Element element) { |
| // TODO(johnniwinther): Remove this when [AbstractFieldElement] has been |
| // removed. |
| if (element is! AstElement) return; |
| |
| if (element.isTypedef) { |
| TypedefElement typdef = element; |
| collectTypeDependencies(typdef.thisType); |
| } else { |
| // TODO(sigurdm): We want to be more specific about this - need a better |
| // way to query "liveness". |
| AstElement analyzableElement = element.analyzableElement.declaration; |
| if (!compiler.enqueuer.resolution.hasBeenProcessed(analyzableElement)) { |
| return; |
| } |
| |
| WorldImpact worldImpact = |
| compiler.resolution.getWorldImpact(analyzableElement); |
| compiler.impactStrategy.visitImpact( |
| analyzableElement, |
| worldImpact, |
| new WorldImpactVisitorImpl(visitStaticUse: (StaticUse staticUse) { |
| elements.add(staticUse.element); |
| switch (staticUse.kind) { |
| case StaticUseKind.CONSTRUCTOR_INVOKE: |
| case StaticUseKind.CONST_CONSTRUCTOR_INVOKE: |
| collectTypeDependencies(staticUse.type); |
| break; |
| default: |
| } |
| }, visitTypeUse: (TypeUse typeUse) { |
| DartType type = typeUse.type; |
| switch (typeUse.kind) { |
| case TypeUseKind.TYPE_LITERAL: |
| if (type.isTypedef || type.isInterfaceType) { |
| elements.add(type.element); |
| } |
| break; |
| case TypeUseKind.INSTANTIATION: |
| case TypeUseKind.MIRROR_INSTANTIATION: |
| case TypeUseKind.NATIVE_INSTANTIATION: |
| case TypeUseKind.IS_CHECK: |
| case TypeUseKind.AS_CAST: |
| case TypeUseKind.CATCH_TYPE: |
| collectTypeDependencies(type); |
| break; |
| case TypeUseKind.CHECKED_MODE_CHECK: |
| if (compiler.options.enableTypeAssertions) { |
| collectTypeDependencies(type); |
| } |
| break; |
| } |
| }), |
| IMPACT_USE); |
| |
| if (analyzableElement.resolvedAst.kind != ResolvedAstKind.PARSED) { |
| return; |
| } |
| |
| TreeElements treeElements = analyzableElement.resolvedAst.elements; |
| assert(treeElements != null); |
| |
| // TODO(johnniwinther): Add only expressions that are actually needed. |
| // Currently we have some noise here: Some potential expressions are |
| // seen that should never be added (for instance field initializers |
| // in constant constructors, like `this.field = parameter`). And some |
| // implicit constant expression are seen that we should be able to add |
| // (like primitive constant literals like `true`, `"foo"` and `0`). |
| // See dartbug.com/26406 for context. |
| treeElements.forEachConstantNode( |
| (ast.Node node, ConstantExpression expression) { |
| if (compiler.serialization.isDeserialized(analyzableElement)) { |
| if (!expression.isPotential) { |
| // Enforce evaluation of [expression]. |
| backend.constants.getConstantValue(expression); |
| } |
| } |
| |
| // Explicitly depend on the backend constants. |
| if (backend.constants.hasConstantValue(expression)) { |
| ConstantValue value = |
| backend.constants.getConstantValue(expression); |
| assert(invariant(node, value != null, |
| message: "Constant expression without value: " |
| "${expression.toStructuredText()}.")); |
| constants.add(value); |
| } else { |
| assert( |
| invariant(node, expression.isImplicit || expression.isPotential, |
| message: "Unexpected unevaluated constant expression: " |
| "${expression.toStructuredText()}.")); |
| } |
| }); |
| } |
| } |
| |
| // TODO(sigurdm): How is metadata on a patch-class handled? |
| for (MetadataAnnotation metadata in element.metadata) { |
| ConstantValue constant = |
| backend.constants.getConstantValueForMetadata(metadata); |
| if (constant != null) { |
| constants.add(constant); |
| } |
| } |
| |
| if (element is FunctionElement) { |
| collectTypeDependencies(element.type); |
| } |
| |
| if (element.isClass) { |
| // If we see a class, add everything its live instance members refer |
| // to. Static members are not relevant, unless we are processing |
| // extra dependencies due to mirrors. |
| void addLiveInstanceMember(_, Element element) { |
| if (!compiler.enqueuer.resolution.hasBeenProcessed(element)) return; |
| if (!isMirrorUsage && !element.isInstanceMember) return; |
| elements.add(element); |
| collectDependencies(element); |
| } |
| |
| ClassElement cls = element.declaration; |
| cls.implementation.forEachMember(addLiveInstanceMember); |
| for (var type in cls.implementation.allSupertypes) { |
| elements.add(type.element.implementation); |
| } |
| elements.add(cls.implementation); |
| } else if (Elements.isStaticOrTopLevel(element) || element.isConstructor) { |
| elements.add(element); |
| collectDependencies(element); |
| } |
| 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.enclosingClass.implementation; |
| _collectAllElementsAndConstantsResolvedFrom( |
| implementation, elements, constants, isMirrorUsage); |
| } |
| |
| // 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); |
| |
| iterateTags(LibraryElement library) { |
| // TODO(sigurdm): Make helper getLibraryDependencyTags when tags is |
| // changed to be a List instead of a Link. |
| for (ImportElement import in library.imports) { |
| if (!import.isDeferred) { |
| LibraryElement importedLibrary = import.importedLibrary; |
| traverseLibrary(importedLibrary); |
| } |
| } |
| for (ExportElement export in library.exports) { |
| LibraryElement exportedLibrary = export.exportedLibrary; |
| traverseLibrary(exportedLibrary); |
| } |
| } |
| |
| iterateTags(library); |
| if (library.isPatched) { |
| iterateTags(library.implementation); |
| } |
| } |
| |
| traverseLibrary(root); |
| result.add(compiler.commonElements.coreLibrary); |
| return result; |
| } |
| |
| /// Add all dependencies of [constant] to the mapping of [import]. |
| void _mapConstantDependencies( |
| ConstantValue constant, _DeferredImport import) { |
| Set<ConstantValue> constants = _constantsDeferredBy.putIfAbsent( |
| import, () => new Set<ConstantValue>()); |
| if (constants.contains(constant)) return; |
| constants.add(constant); |
| if (constant is ConstructedConstantValue) { |
| _mapDependencies(element: constant.type.element, import: import); |
| } |
| constant.getDependencies().forEach((ConstantValue dependency) { |
| _mapConstantDependencies(dependency, import); |
| }); |
| } |
| |
| /// Recursively traverses the graph of dependencies from one of [element] |
| /// or [constant], mapping deferred imports to each dependency it needs in the |
| /// sets [_importedDeferredBy] and [_constantsDeferredBy]. |
| /// Only one of [element] and [constant] should be given. |
| void _mapDependencies( |
| {Element element, _DeferredImport import, isMirrorUsage: false}) { |
| Set<Element> elements = |
| _importedDeferredBy.putIfAbsent(import, () => new Set<Element>()); |
| |
| Set<Element> dependentElements = new Set<Element>(); |
| Set<ConstantValue> dependentConstants = new Set<ConstantValue>(); |
| |
| LibraryElement library; |
| |
| if (element != null) { |
| // Only process elements once, unless we are doing dependencies due to |
| // mirrors, which are added in additional traversals. |
| if (!isMirrorUsage && 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; |
| elements.add(element); |
| |
| // This call can modify [dependentElements] and [dependentConstants]. |
| _collectAllElementsAndConstantsResolvedFrom( |
| element, dependentElements, dependentConstants, isMirrorUsage); |
| |
| library = element.library; |
| } |
| |
| for (Element dependency in dependentElements) { |
| if (_isExplicitlyDeferred(dependency, library)) { |
| for (ImportElement deferredImport in _getImports(dependency, library)) { |
| _mapDependencies( |
| element: dependency, |
| import: new _DeclaredDeferredImport(deferredImport)); |
| } |
| } else { |
| _mapDependencies(element: dependency, import: import); |
| } |
| } |
| |
| for (ConstantValue dependency in dependentConstants) { |
| if (dependency is DeferredConstantValue) { |
| _mapConstantDependencies(dependency, |
| new _DeclaredDeferredImport(dependency.prefix.deferredImport)); |
| } else { |
| _mapConstantDependencies(dependency, import); |
| } |
| } |
| } |
| |
| /// Adds extra dependencies coming from mirror usage. |
| /// |
| /// The elements are added with [_mapDependencies]. |
| void _addMirrorElements() { |
| void mapDependenciesIfResolved( |
| Element element, _DeferredImport deferredImport) { |
| // If an element is the target of a MirrorsUsed annotation but never used |
| // It will not be resolved, and we should not call isNeededForReflection. |
| // TODO(sigurdm): Unresolved elements should just answer false when |
| // asked isNeededForReflection. Instead an internal error is triggered. |
| // So we have to filter them out here. |
| if (element is AnalyzableElementX && !element.hasTreeElements) return; |
| if (compiler.backend.isAccessibleByReflection(element)) { |
| _mapDependencies( |
| element: element, import: deferredImport, isMirrorUsage: true); |
| } |
| } |
| |
| // For each deferred import we analyze all elements reachable from the |
| // imported library through non-deferred imports. |
| void handleLibrary(LibraryElement library, _DeferredImport deferredImport) { |
| library.implementation.forEachLocalMember((Element element) { |
| mapDependenciesIfResolved(element, deferredImport); |
| }); |
| |
| void processMetadata(Element element) { |
| for (MetadataAnnotation metadata in element.metadata) { |
| ConstantValue constant = |
| backend.constants.getConstantValueForMetadata(metadata); |
| if (constant != null) { |
| _mapConstantDependencies(constant, deferredImport); |
| } |
| } |
| } |
| |
| processMetadata(library); |
| library.imports.forEach(processMetadata); |
| library.exports.forEach(processMetadata); |
| } |
| |
| for (_DeferredImport deferredImport in _allDeferredImports.keys) { |
| LibraryElement deferredLibrary = _allDeferredImports[deferredImport]; |
| for (LibraryElement library |
| in _nonDeferredReachableLibraries(deferredLibrary)) { |
| handleLibrary(library, deferredImport); |
| } |
| } |
| } |
| |
| /// Computes a unique string for the name field for each outputUnit. |
| /// |
| /// Also sets up the [hunksToLoad] mapping. |
| void _assignNamesToOutputUnits(Set<OutputUnit> allOutputUnits) { |
| Set<String> usedImportNames = new Set<String>(); |
| |
| void computeImportDeferName(_DeferredImport import) { |
| String result = import.computeImportDeferName(compiler); |
| assert(result != null); |
| importDeferName[import] = makeUnique(result, usedImportNames); |
| } |
| |
| int counter = 1; |
| |
| for (_DeferredImport import in _allDeferredImports.keys) { |
| computeImportDeferName(import); |
| } |
| |
| for (OutputUnit outputUnit in allOutputUnits) { |
| if (outputUnit == mainOutputUnit) { |
| outputUnit.name = "main"; |
| } else { |
| outputUnit.name = "$counter"; |
| ++counter; |
| } |
| } |
| |
| List sortedOutputUnits = new List.from(allOutputUnits); |
| // Sort the output units in descending order of the number of imports they |
| // include. |
| |
| // The loading of the output units must be ordered because a superclass |
| // needs to be initialized before its subclass. |
| // But a class can only depend on another class in an output unit shared by |
| // a strict superset of the imports: |
| // By contradiction: Assume a class C in output unit shared by imports in |
| // the set S1 = (lib1,.., lib_n) depends on a class D in an output unit |
| // shared by S2 such that S2 not a superset of S1. Let lib_s be a library in |
| // S1 not in S2. lib_s must depend on C, and then in turn on D. Therefore D |
| // is not in the right output unit. |
| sortedOutputUnits.sort((a, b) => b.imports.length - a.imports.length); |
| |
| // For each deferred import we find out which outputUnits to load. |
| for (_DeferredImport import in _allDeferredImports.keys) { |
| if (import == _fakeMainImport) continue; |
| hunksToLoad[importDeferName[import]] = new List<OutputUnit>(); |
| for (OutputUnit outputUnit in sortedOutputUnits) { |
| if (outputUnit == mainOutputUnit) continue; |
| if (outputUnit.imports.contains(import)) { |
| hunksToLoad[importDeferName[import]].add(outputUnit); |
| } |
| } |
| } |
| } |
| |
| void onResolutionComplete(FunctionElement main) { |
| if (!isProgramSplit) { |
| allOutputUnits.add(mainOutputUnit); |
| return; |
| } |
| if (main == null) return; |
| LibraryElement mainLibrary = main.library; |
| _importedDeferredBy = new Map<_DeferredImport, Set<Element>>(); |
| _constantsDeferredBy = new Map<_DeferredImport, Set<ConstantValue>>(); |
| _importedDeferredBy[_fakeMainImport] = _mainElements; |
| |
| reporter.withCurrentElement( |
| mainLibrary, |
| () => measure(() { |
| // Starting from main, traverse the program and find all |
| // dependencies. |
| _mapDependencies( |
| element: compiler.mainFunction, import: _fakeMainImport); |
| |
| // Also add "global" dependencies to the main OutputUnit. These |
| // are things that the backend needs 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: element, import: _fakeMainImport); |
| } |
| |
| // Now check to see if we have to add more elements due to |
| // mirrors. |
| if (compiler.commonElements.mirrorsLibrary != null) { |
| _addMirrorElements(); |
| } |
| |
| // Build the OutputUnits using these two maps. |
| Map<Element, OutputUnit> elementToOutputUnitBuilder = |
| new Map<Element, OutputUnit>(); |
| Map<ConstantValue, OutputUnit> constantToOutputUnitBuilder = |
| new Map<ConstantValue, OutputUnit>(); |
| |
| // Add all constants that may have been registered during |
| // resolution with [registerConstantDeferredUse]. |
| constantToOutputUnitBuilder.addAll(_constantToOutputUnit); |
| _constantToOutputUnit.clear(); |
| |
| // Reverse the mappings. For each element record an OutputUnit |
| // collecting all deferred imports mapped to this element. Same |
| // for constants. |
| for (_DeferredImport import in _importedDeferredBy.keys) { |
| for (Element element in _importedDeferredBy[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) { |
| elementToOutputUnitBuilder[element] = mainOutputUnit; |
| } else { |
| elementToOutputUnitBuilder |
| .putIfAbsent(element, () => new OutputUnit()) |
| .imports |
| .add(import); |
| } |
| } |
| } |
| for (_DeferredImport import in _constantsDeferredBy.keys) { |
| for (ConstantValue constant in _constantsDeferredBy[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) { |
| constantToOutputUnitBuilder[constant] = mainOutputUnit; |
| } else { |
| constantToOutputUnitBuilder |
| .putIfAbsent(constant, () => new OutputUnit()) |
| .imports |
| .add(import); |
| } |
| } |
| } |
| |
| // Release maps; |
| _importedDeferredBy = null; |
| _constantsDeferredBy = null; |
| |
| // Find all the output units elements/constants have been mapped |
| // to, and canonicalize them. |
| elementToOutputUnitBuilder |
| .forEach((Element element, OutputUnit outputUnit) { |
| _elementToOutputUnit[element] = _getCanonicalUnit(outputUnit); |
| }); |
| constantToOutputUnitBuilder |
| .forEach((ConstantValue constant, OutputUnit outputUnit) { |
| _constantToOutputUnit[constant] = _getCanonicalUnit(outputUnit); |
| }); |
| |
| // Generate a unique name for each OutputUnit. |
| _assignNamesToOutputUnits(allOutputUnits); |
| })); |
| // Notify the impact strategy impacts are no longer needed for deferred |
| // load. |
| compiler.impactStrategy.onImpactUsed(IMPACT_USE); |
| } |
| |
| void beforeResolution(Compiler compiler) { |
| if (compiler.mainApp == null) return; |
| _allDeferredImports[_fakeMainImport] = compiler.mainApp; |
| var lastDeferred; |
| // When detecting duplicate prefixes of deferred libraries there are 4 |
| // cases of duplicate prefixes: |
| // 1. |
| // import "lib.dart" deferred as a; |
| // import "lib2.dart" deferred as a; |
| // 2. |
| // import "lib.dart" deferred as a; |
| // import "lib2.dart" as a; |
| // 3. |
| // import "lib.dart" as a; |
| // import "lib2.dart" deferred as a; |
| // 4. |
| // import "lib.dart" as a; |
| // import "lib2.dart" as a; |
| // We must be able to signal error for case 1, 2, 3, but accept case 4. |
| |
| // The prefixes that have been used by any imports in this library. |
| Setlet<String> usedPrefixes = new Setlet<String>(); |
| // The last deferred import we saw with a given prefix (if any). |
| Map<String, ImportElement> prefixDeferredImport = |
| new Map<String, ImportElement>(); |
| for (LibraryElement library in compiler.libraryLoader.libraries) { |
| reporter.withCurrentElement(library, () { |
| prefixDeferredImport.clear(); |
| usedPrefixes.clear(); |
| // TODO(sigurdm): Make helper getLibraryImportTags when tags is a List |
| // instead of a Link. |
| for (ImportElement import in library.imports) { |
| /// Give an error if the old annotation-based syntax has been used. |
| List<MetadataAnnotation> metadataList = import.metadata; |
| if (metadataList != null) { |
| for (MetadataAnnotation metadata in metadataList) { |
| metadata.ensureResolved(compiler.resolution); |
| ConstantValue value = |
| compiler.constants.getConstantValue(metadata.constant); |
| Element element = value.getType(compiler.commonElements).element; |
| if (element == deferredLibraryClass) { |
| reporter.reportErrorMessage( |
| import, MessageKind.DEFERRED_OLD_SYNTAX); |
| } |
| } |
| } |
| |
| String prefix = (import.prefix != null) ? import.prefix.name : null; |
| // The last import we saw with the same prefix. |
| ImportElement previousDeferredImport = prefixDeferredImport[prefix]; |
| if (import.isDeferred) { |
| _DeferredImport key = new _DeclaredDeferredImport(import); |
| LibraryElement importedLibrary = import.importedLibrary; |
| _allDeferredImports[key] = importedLibrary; |
| |
| if (prefix == null) { |
| reporter.reportErrorMessage( |
| import, MessageKind.DEFERRED_LIBRARY_WITHOUT_PREFIX); |
| } else { |
| prefixDeferredImport[prefix] = import; |
| _deferredImportDescriptions[key] = |
| new ImportDescription(import, library, compiler); |
| } |
| isProgramSplit = true; |
| lastDeferred = import; |
| } |
| if (prefix != null) { |
| if (previousDeferredImport != null || |
| (import.isDeferred && usedPrefixes.contains(prefix))) { |
| ImportElement failingImport = (previousDeferredImport != null) |
| ? previousDeferredImport |
| : import; |
| reporter.reportErrorMessage(failingImport.prefix, |
| MessageKind.DEFERRED_LIBRARY_DUPLICATE_PREFIX); |
| } |
| usedPrefixes.add(prefix); |
| } |
| } |
| }); |
| } |
| if (isProgramSplit) { |
| isProgramSplit = |
| compiler.backend.enableDeferredLoadingIfSupported(lastDeferred); |
| } |
| } |
| |
| /// If [send] is a static send with a deferred element, returns the |
| /// [PrefixElement] that the first prefix of the send resolves to. |
| /// Otherwise returns null. |
| /// |
| /// Precondition: send must be static. |
| /// |
| /// Example: |
| /// |
| /// import "a.dart" deferred as a; |
| /// |
| /// main() { |
| /// print(a.loadLibrary.toString()); |
| /// a.loadLibrary().then((_) { |
| /// a.run(); |
| /// a.foo.method(); |
| /// }); |
| /// } |
| /// |
| /// Returns null for a.loadLibrary() (the special |
| /// function loadLibrary is not deferred). And returns the PrefixElement for |
| /// a.run() and a.foo. |
| /// a.loadLibrary.toString() and a.foo.method() are dynamic sends - and |
| /// this functions should not be called on them. |
| PrefixElement deferredPrefixElement(ast.Send send, TreeElements elements) { |
| Element element = elements[send]; |
| // The DeferredLoaderGetter is not deferred, therefore we do not return the |
| // prefix. |
| if (element != null && element.isDeferredLoaderGetter) return null; |
| |
| ast.Node firstNode(ast.Node node) { |
| if (node is! ast.Send) { |
| return node; |
| } else { |
| ast.Send send = node; |
| ast.Node receiver = send.receiver; |
| ast.Node receiverFirst = firstNode(receiver); |
| if (receiverFirst != null) { |
| return receiverFirst; |
| } else { |
| return firstNode(send.selector); |
| } |
| } |
| } |
| |
| ast.Node first = firstNode(send); |
| ast.Node identifier = first.asIdentifier(); |
| if (identifier == null) return null; |
| Element maybePrefix = elements[identifier]; |
| if (maybePrefix != null && maybePrefix.isPrefix) { |
| PrefixElement prefixElement = maybePrefix; |
| if (prefixElement.isDeferred) { |
| return prefixElement; |
| } |
| } |
| return null; |
| } |
| |
| /// Returns a json-style map for describing what files that are loaded by a |
| /// given deferred import. |
| /// The mapping is structured as: |
| /// library uri -> {"name": library name, "files": (prefix -> list of files)} |
| /// Where |
| /// |
| /// - <library uri> is the relative uri of the library making a deferred |
| /// import. |
| /// - <library name> is the name of the library, and "<unnamed>" if it is |
| /// unnamed. |
| /// - <prefix> is the `as` prefix used for a given deferred import. |
| /// - <list of files> is a list of the filenames the must be loaded when that |
| /// import is loaded. |
| Map<String, Map<String, dynamic>> computeDeferredMap() { |
| JavaScriptBackend backend = compiler.backend; |
| Map<String, Map<String, dynamic>> mapping = |
| new Map<String, Map<String, dynamic>>(); |
| _deferredImportDescriptions.keys.forEach((_DeferredImport import) { |
| List<OutputUnit> outputUnits = hunksToLoad[importDeferName[import]]; |
| ImportDescription description = _deferredImportDescriptions[import]; |
| Map<String, dynamic> libraryMap = mapping.putIfAbsent( |
| description.importingUri, |
| () => <String, dynamic>{ |
| "name": description.importingLibraryName, |
| "imports": <String, List<String>>{} |
| }); |
| |
| libraryMap["imports"][importDeferName[import]] = |
| outputUnits.map((OutputUnit outputUnit) { |
| return backend.deferredPartFileName(outputUnit.name); |
| }).toList(); |
| }); |
| return mapping; |
| } |
| |
| /// Creates a textual representation of the output unit content. |
| String dump() { |
| Map<OutputUnit, List<String>> elementMap = <OutputUnit, List<String>>{}; |
| Map<OutputUnit, List<String>> constantMap = <OutputUnit, List<String>>{}; |
| _elementToOutputUnit.forEach((Element element, OutputUnit output) { |
| elementMap.putIfAbsent(output, () => <String>[]).add('$element'); |
| }); |
| _constantToOutputUnit.forEach((ConstantValue value, OutputUnit output) { |
| constantMap |
| .putIfAbsent(output, () => <String>[]) |
| .add(value.toStructuredText()); |
| }); |
| |
| StringBuffer sb = new StringBuffer(); |
| for (OutputUnit outputUnit in allOutputUnits) { |
| sb.write('\n-------------------------------\n'); |
| sb.write('Output unit: ${outputUnit.name}'); |
| List<String> elements = elementMap[outputUnit]; |
| if (elements != null) { |
| sb.write('\n elements:'); |
| for (String element in elements..sort()) { |
| sb.write('\n $element'); |
| } |
| } |
| List<String> constants = constantMap[outputUnit]; |
| if (constants != null) { |
| sb.write('\n constants:'); |
| for (String value in constants..sort()) { |
| sb.write('\n $value'); |
| } |
| } |
| } |
| return sb.toString(); |
| } |
| } |
| |
| class ImportDescription { |
| /// Relative uri to the importing library. |
| final String importingUri; |
| |
| /// The prefix this import is imported as. |
| final String prefix; |
| final LibraryElement _importingLibrary; |
| |
| ImportDescription( |
| ImportElement import, LibraryElement importingLibrary, Compiler compiler) |
| : importingUri = uri_extras.relativize(compiler.mainApp.canonicalUri, |
| importingLibrary.canonicalUri, false), |
| prefix = import.prefix.name, |
| _importingLibrary = importingLibrary; |
| |
| String get importingLibraryName { |
| return _importingLibrary.hasLibraryName |
| ? _importingLibrary.libraryName |
| : "<unnamed>"; |
| } |
| } |
| |
| /// A node in the deferred import graph. |
| /// |
| /// This class serves as the root node; the 'import' of the main library. |
| class _DeferredImport { |
| const _DeferredImport(); |
| |
| /// Computes a suggestive name for this import. |
| String computeImportDeferName(Compiler compiler) => 'main'; |
| |
| ImportElement get declaration => null; |
| |
| String toString() => 'main'; |
| } |
| |
| /// A node in the deferred import graph defined by a deferred import directive. |
| class _DeclaredDeferredImport implements _DeferredImport { |
| final ImportElement declaration; |
| |
| _DeclaredDeferredImport(this.declaration); |
| |
| @override |
| String computeImportDeferName(Compiler compiler) { |
| String result; |
| if (declaration.isDeferred) { |
| if (declaration.prefix != null) { |
| result = declaration.prefix.name; |
| } else { |
| // This happens when the deferred import isn't declared with a prefix. |
| assert(compiler.compilationFailed); |
| result = ''; |
| } |
| } else { |
| // Finds the first argument to the [DeferredLibrary] annotation |
| List<MetadataAnnotation> metadatas = declaration.metadata; |
| assert(metadatas != null); |
| for (MetadataAnnotation metadata in metadatas) { |
| metadata.ensureResolved(compiler.resolution); |
| ConstantValue value = |
| compiler.constants.getConstantValue(metadata.constant); |
| Element element = value.getType(compiler.commonElements).element; |
| if (element == compiler.commonElements.deferredLibraryClass) { |
| ConstructedConstantValue constant = value; |
| StringConstantValue s = constant.fields.values.single; |
| result = s.primitiveValue.slowToString(); |
| break; |
| } |
| } |
| } |
| assert(result != null); |
| return result; |
| } |
| |
| bool operator ==(other) { |
| if (other is! _DeclaredDeferredImport) return false; |
| return declaration == other.declaration; |
| } |
| |
| int get hashCode => declaration.hashCode * 17; |
| |
| String toString() => '$declaration'; |
| } |