Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(114)

Unified Diff: pkg/compiler/lib/src/deferred_load.dart

Issue 2997233002: Update defer loading algorithm: (Closed)
Patch Set: follow up comments + ensure sorted output Created 3 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | pkg/compiler/lib/src/dump_info.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: pkg/compiler/lib/src/deferred_load.dart
diff --git a/pkg/compiler/lib/src/deferred_load.dart b/pkg/compiler/lib/src/deferred_load.dart
index 43ad9971fae26dd65f4555cb0f57186685f3d02e..95633ad1b41f004de1470e826b2d7cc055967881 100644
--- a/pkg/compiler/lib/src/deferred_load.dart
+++ b/pkg/compiler/lib/src/deferred_load.dart
@@ -4,6 +4,8 @@
library deferred_load;
+import 'dart:collection' show Queue;
+
import 'common/tasks.dart' show CompilerTask;
import 'common.dart';
import 'compiler.dart' show Compiler;
@@ -54,35 +56,43 @@ import 'world.dart' show ClosedWorld;
/// 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>();
-
+/// We never create two OutputUnits sharing the same set of [imports].
+class OutputUnit implements Comparable<OutputUnit> {
/// `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 ==(other) {
- return other is OutputUnit &&
- 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.
+ final String name;
+
+ /// The deferred imports that use the elements in this output unit.
+ final Set<ImportElement> _imports;
+
+ OutputUnit(this.isMainOutput, this.name, this._imports);
+
+ int compareTo(OutputUnit other) {
+ if (identical(this, other)) return 0;
+ if (isMainOutput && !other.isMainOutput) return -1;
+ if (!isMainOutput && other.isMainOutput) return 1;
+ var size = _imports.length;
+ var otherSize = other._imports.length;
+ if (size != otherSize) return size.compareTo(otherSize);
+ var imports = _imports.toList();
+ var otherImports = other._imports.toList();
+ for (var i = 0; i < size; i++) {
+ if (imports[i] == otherImports[i]) continue;
+ var a = imports[i].uri.path;
+ var b = otherImports[i].uri.path;
+ var cmp = a.compareTo(b);
+ if (cmp != 0) return cmp;
}
- return sum;
+ // TODO(sigmund): make compare stable. If we hit this point, all imported
+ // libraries are the same, however [this] and [other] use different deferred
+ // imports in the program. We can make this stable if we sort based on the
+ // deferred imports themselves (e.g. their declaration location).
+ return name.compareTo(other.name);
}
+
+ String toString() => "OutputUnit($name, $_imports)";
}
/// For each deferred import, find elements and constants to be loaded when that
@@ -96,15 +106,12 @@ class DeferredLoadTask extends CompilerTask {
ClassElement get deferredLibraryClass =>
compiler.resolution.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);
+ OutputUnit mainOutputUnit;
/// A set containing (eventually) all output units that will result from the
/// program.
- final Set<OutputUnit> allOutputUnits = new Set<OutputUnit>();
+ final List<OutputUnit> allOutputUnits = new List<OutputUnit>();
/// Will be `true` if the program contains deferred libraries.
bool isProgramSplit = false;
@@ -123,42 +130,35 @@ class DeferredLoadTask extends CompilerTask {
/// A cache of the result of calling `computeImportDeferName` on the keys of
/// this map.
- final Map<_DeferredImport, String> importDeferName =
- <_DeferredImport, String>{};
+ final Map<ImportElement, String> _importDeferName = <ImportElement, String>{};
/// A mapping from elements and constants to their output unit. Query this via
/// [outputUnitForElement]
- final Map<Element, OutputUnit> _elementToOutputUnit =
- new Map<Element, OutputUnit>();
+ final Map<Entity, ImportSet> _elementToSet = new Map<Entity, ImportSet>();
/// A mapping from constants to their output unit. Query this via
/// [outputUnitForConstant]
- final Map<ConstantValue, OutputUnit> _constantToOutputUnit =
- new Map<ConstantValue, OutputUnit>();
+ final Map<ConstantValue, ImportSet> _constantToSet =
+ new Map<ConstantValue, ImportSet>();
- /// 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>();
+ Iterable<ImportElement> get _allDeferredImports =>
+ _deferredImportDescriptions.keys;
/// 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;
+ final Map<ImportElement, ImportDescription> _deferredImportDescriptions =
+ <ImportElement, ImportDescription>{};
- Set<Element> _mainElements = new Set<Element>();
+ /// A lattice to compactly represent multiple subsets of imports.
+ final ImportSetLattice importSets = new ImportSetLattice();
final Compiler compiler;
DeferredLoadTask(Compiler compiler)
: compiler = compiler,
super(compiler.measurer) {
- mainOutputUnit.imports.add(_fakeMainImport);
+ mainOutputUnit = new OutputUnit(true, 'main', new Set<ImportElement>());
+ importSets.mainSet.unit = mainOutputUnit;
+ allOutputUnits.add(mainOutputUnit);
}
JavaScriptBackend get backend => compiler.backend;
@@ -171,19 +171,19 @@ class DeferredLoadTask extends CompilerTask {
if (!isProgramSplit) return mainOutputUnit;
Element element = entity;
element = element.implementation;
- while (!_elementToOutputUnit.containsKey(element)) {
+ while (!_elementToSet.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;
+ _elementToSet[element] = importSets.mainSet;
break;
}
element = element.enclosingElement.implementation;
}
- return _elementToOutputUnit[element];
+ return _elementToSet[element].unit;
}
/// Returns the [OutputUnit] where [element] belongs.
@@ -197,20 +197,18 @@ class DeferredLoadTask extends CompilerTask {
}
/// Direct access to the output-unit to element relation used for testing.
- OutputUnit getOutputUnitForElementForTesting(Element element) {
- return _elementToOutputUnit[element];
+ OutputUnit getOutputUnitForElementForTesting(Entity element) {
+ return _elementToSet[element]?.unit;
}
/// Returns the [OutputUnit] where [constant] belongs.
OutputUnit outputUnitForConstant(ConstantValue constant) {
if (!isProgramSplit) return mainOutputUnit;
- return _constantToOutputUnit[constant];
+ return _constantToSet[constant]?.unit;
}
/// Direct access to the output-unit to constants map used for testing.
- Map<ConstantValue, OutputUnit> get outputUnitForConstantsForTesting {
- return _constantToOutputUnit;
- }
+ Iterable<ConstantValue> get constantsForTesting => _constantToSet.keys;
bool isDeferred(Entity element) {
return outputUnitForElement(element) != mainOutputUnit;
@@ -222,43 +220,38 @@ class DeferredLoadTask extends CompilerTask {
/// Returns the unique name for the deferred import of [prefix].
String getImportDeferName(Spannable node, PrefixElement prefix) {
- String name =
- importDeferName[new _DeclaredDeferredImport(prefix.deferredImport)];
+ String name = _importDeferName[prefix.deferredImport];
if (name == null) {
reporter.internalError(node, "No deferred name for $prefix.");
}
return name;
}
+ /// Returns the names associated with each deferred import in [unit].
+ Iterable<String> getImportNames(OutputUnit unit) {
+ return unit._imports.map((i) => _importDeferName[i]);
+ }
+
/// 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) {
+ bool hasOnlyNonDeferredImportPaths(Entity from, Entity 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;
+ if (outputUnitTo == mainOutputUnit) return true;
+ if (outputUnitFrom == mainOutputUnit) return false;
+ return outputUnitTo._imports.containsAll(outputUnitFrom._imports);
}
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);
+ var newSet = importSets.singleton(prefix.deferredImport);
+ assert(
+ _constantToSet[constant] == null || _constantToSet[constant] == newSet);
+ _constantToSet[constant] = newSet;
}
/// Given [imports] that refer to an element from a library, determine whether
@@ -510,82 +503,126 @@ class DeferredLoadTask extends CompilerTask {
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) {
- ClassElement cls = constant.type.element;
- _mapDependencies(element: cls, import: import);
+ /// Update the import set of all constants reachable from [constant], as long
+ /// as they had the [oldSet]. As soon as we see a constant with a different
+ /// import set, we stop and enqueue a new recursive update in [queue].
+ ///
+ /// Invariants: oldSet is either null or a subset of newSet.
+ void _updateConstantRecursive(ConstantValue constant, ImportSet oldSet,
+ ImportSet newSet, WorkQueue queue) {
+ if (constant == null) return;
+ var currentSet = _constantToSet[constant];
+
+ // Already visited.
+ if (currentSet == newSet) return;
+
+ // Elements in the main output unit always remain there.
+ if (currentSet == importSets.mainSet) return;
+
+ if (currentSet == oldSet) {
+ _constantToSet[constant] = newSet;
+ if (constant is ConstructedConstantValue) {
+ ClassElement cls = constant.type.element;
+ _updateElementRecursive(cls, oldSet, newSet, queue);
+ }
+ constant.getDependencies().forEach((ConstantValue dependency) {
+ if (dependency is DeferredConstantValue) {
+ /// New deferred-imports are only discovered when we are visiting the
+ /// main output unit (size == 0) or code reachable from a deferred
+ /// import (size == 1). After that, we are rediscovering the
+ /// same nodes we have already seen.
+ if (newSet.length <= 1) {
+ PrefixElement prefix = dependency.prefix;
+ queue.addConstant(
+ dependency, importSets.singleton(prefix.deferredImport));
+ }
+ } else {
+ _updateConstantRecursive(dependency, oldSet, newSet, queue);
+ }
+ });
+ } else {
+ assert(
+ // Invariant: we must mark main before we mark any deferred import.
+ newSet != importSets.mainSet || oldSet != null,
+ failedAt(
+ NO_LOCATION_SPANNABLE,
+ "Tried to assign ${constant.toDartText()} to the main output "
+ "unit, but it was assigned to $currentSet."));
+ queue.addConstant(constant, newSet);
}
- constant.getDependencies().forEach((ConstantValue dependency) {
- _mapConstantDependencies(dependency, import);
- });
}
- /// 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, _DeferredImport import, isMirrorUsage: false}) {
- Set<Element> elements = _importedDeferredBy[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;
- // This adds [element] to [_mainElements] because [_mainElements] is
- // aliased with `_importedDeferredBy[_fakeMainImport]]`.
- elements.add(element);
+ /// Update the import set of all elements reachable from [element], as long as
+ /// they had the [oldSet]. As soon as we see an element with a different
+ /// import set, we stop and enqueue a new recursive update in [queue].
+ void _updateElementRecursive(
+ Element element, ImportSet oldSet, ImportSet newSet, WorkQueue queue,
+ {bool isMirrorUsage: false}) {
+ if (element == null) return;
+ var currentSet = _elementToSet[element];
+
+ // Already visited. We may visit some root nodes a second time with
+ // [isMirrorUsage] in order to mark static members used reflectively.
+ if (currentSet == newSet && !isMirrorUsage) return;
+
+ // Elements in the main output unit always remain there.
+ if (currentSet == importSets.mainSet) return;
+
+ if (currentSet == oldSet) {
+ // Continue recursively updating from [oldSet] to [newSet].
+ _elementToSet[element] = newSet;
- // This call can modify [dependentElements] and [dependentConstants].
+ Set<Element> dependentElements = new Set<Element>();
+ Set<ConstantValue> dependentConstants = new Set<ConstantValue>();
_collectAllElementsAndConstantsResolvedFrom(
element, dependentElements, dependentConstants, isMirrorUsage);
- library = element.library;
- }
-
- for (Element dependency in dependentElements) {
- Iterable<ImportElement> imports = _getImports(dependency, library);
- if (_isExplicitlyDeferred(imports)) {
- for (ImportElement deferredImport in imports) {
- _mapDependencies(
- element: dependency,
- import: new _DeclaredDeferredImport(deferredImport));
+ LibraryElement library = element.library;
+ for (Element dependency in dependentElements) {
+ Iterable<ImportElement> imports = _getImports(dependency, library);
+ if (_isExplicitlyDeferred(imports)) {
+ /// New deferred-imports are only discovered when we are visiting the
+ /// main output unit (size == 0) or code reachable from a deferred
+ /// import (size == 1). After that, we are rediscovering the
+ /// same nodes we have already seen.
+ if (newSet.length <= 1) {
+ for (ImportElement deferredImport in imports) {
+ queue.addElement(
+ dependency, importSets.singleton(deferredImport));
+ }
+ }
+ } else {
+ _updateElementRecursive(dependency, oldSet, newSet, queue);
}
- } else {
- _mapDependencies(element: dependency, import: import);
}
- }
- for (ConstantValue dependency in dependentConstants) {
- if (dependency is DeferredConstantValue) {
- PrefixElement prefix = dependency.prefix;
- _mapConstantDependencies(
- dependency, new _DeclaredDeferredImport(prefix.deferredImport));
- } else {
- _mapConstantDependencies(dependency, import);
+ for (ConstantValue dependency in dependentConstants) {
+ if (dependency is DeferredConstantValue) {
+ if (newSet.length <= 1) {
+ PrefixElement prefix = dependency.prefix;
+ queue.addConstant(
+ dependency, importSets.singleton(prefix.deferredImport));
+ }
+ } else {
+ _updateConstantRecursive(dependency, oldSet, newSet, queue);
+ }
}
+ } else {
+ queue.addElement(element, newSet);
}
}
/// Adds extra dependencies coming from mirror usage.
- ///
- /// The elements are added with [_mapDependencies].
- void _addMirrorElements() {
- void mapDependenciesIfResolved(
- Element element, _DeferredImport deferredImport) {
+ void _addDeferredMirrorElements(WorkQueue queue) {
+ for (ImportElement deferredImport in _allDeferredImports) {
+ _addMirrorElementsForLibrary(queue, deferredImport.importedLibrary,
+ importSets.singleton(deferredImport));
+ }
+ }
+
+ void _addMirrorElementsForLibrary(
+ WorkQueue queue, LibraryElement root, ImportSet newSet) {
+ void handleElementIfResolved(Element element) {
// 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
@@ -612,16 +649,15 @@ class DeferredLoadTask extends CompilerTask {
}
if (isAccessibleByReflection(element)) {
- _mapDependencies(
- element: element, import: deferredImport, isMirrorUsage: true);
+ queue.addElement(element, newSet, 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) {
+ void handleLibrary(LibraryElement library) {
library.implementation.forEachLocalMember((Element element) {
- mapDependenciesIfResolved(element, deferredImport);
+ handleElementIfResolved(element);
});
void processMetadata(Element element) {
@@ -629,7 +665,7 @@ class DeferredLoadTask extends CompilerTask {
ConstantValue constant =
backend.constants.getConstantValueForMetadata(metadata);
if (constant != null) {
- _mapConstantDependencies(constant, deferredImport);
+ queue.addConstant(constant, newSet);
}
}
}
@@ -639,43 +675,43 @@ class DeferredLoadTask extends CompilerTask {
library.exports.forEach(processMetadata);
}
- for (_DeferredImport deferredImport in _allDeferredImports.keys) {
- LibraryElement deferredLibrary = _allDeferredImports[deferredImport];
- for (LibraryElement library
- in _nonDeferredReachableLibraries(deferredLibrary)) {
- handleLibrary(library, deferredImport);
- }
- }
+ _nonDeferredReachableLibraries(root).forEach(handleLibrary);
}
/// Computes a unique string for the name field for each outputUnit.
- ///
- /// Also sets up the [hunksToLoad] mapping.
- void _assignNamesToOutputUnits(Set<OutputUnit> allOutputUnits) {
+ void _createOutputUnits() {
+ int counter = 1;
+ void addUnit(ImportSet importSet) {
+ if (importSet.unit != null) return;
+ var unit = new OutputUnit(false, '$counter',
+ importSet._imports.map((i) => i.declaration).toSet());
+ counter++;
+ importSet.unit = unit;
+ allOutputUnits.add(unit);
+ }
+
+ // Generate an output unit for all import sets that are associated with an
+ // element or constant.
+ _elementToSet.values.forEach(addUnit);
+ _constantToSet.values.forEach(addUnit);
+
+ // Sort output units to make the output of the compiler more stable.
+ allOutputUnits.sort();
+ }
+
+ void _setupHunksToLoad() {
Set<String> usedImportNames = new Set<String>();
- void computeImportDeferName(_DeferredImport import) {
- String result = import.computeImportDeferName(compiler);
+ void computeImportDeferName(ImportElement import) {
+ String result = _computeImportDeferName(import, compiler);
assert(result != null);
- importDeferName[import] = makeUnique(result, usedImportNames);
+ _importDeferName[import] = makeUnique(result, usedImportNames);
}
- int counter = 1;
-
- for (_DeferredImport import in _allDeferredImports.keys) {
+ for (ImportElement import in _allDeferredImports) {
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.
@@ -688,122 +724,158 @@ class DeferredLoadTask extends CompilerTask {
// 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);
+ List sortedOutputUnits = allOutputUnits.reversed.toList();
// 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 (ImportElement import in _allDeferredImports) {
+ // We expect to find an entry for any call to `loadLibrary`, even if
+ // there is no code to load. In that case, the entry will be an empty
+ // list.
+ 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);
+ if (outputUnit._imports.contains(import)) {
+ hunksToLoad[_importDeferName[import]].add(outputUnit);
}
}
}
}
+ /// Performs the deferred loading algorithm.
+ ///
+ /// The deferred loading algorithm maps elements and constants to an output
+ /// unit. Each output unit is identified by a subset of deferred imports (an
+ /// [ImportSet]), and they will contain the elements that are inheretly used
+ /// by all those deferred imports. An element is used by a deferred import if
+ /// it is either loaded by that import or transitively accessed by an element
+ /// that the import loads. An empty set represents the main output unit,
+ /// which contains any elements that are accessed directly and are not
+ /// deferred.
+ ///
+ /// The algorithm traverses the element model recursively looking for
+ /// dependencies between elements. These dependencies may be deferred or
+ /// non-deferred. Deferred dependencies are mainly used to discover the root
+ /// elements that are loaded from deferred imports, while non-deferred
+ /// dependencies are used to recursively associate more elements to output
+ /// units.
+ ///
+ /// Naively, the algorithm traverses each root of a deferred import and marks
+ /// everything it can reach as being used by that import. To reduce how many
+ /// times we visit an element, we use an algorithm that works in segments: it
+ /// marks elements with a subset of deferred imports at a time, until it
+ /// detects a merge point where more deferred imports could be considered at
+ /// once.
+ ///
+ /// For example, consider this dependency graph (ignoring elements in the main
+ /// output unit):
+ ///
+ /// deferred import A: a1 ---> s1 ---> s2 -> s3
+ /// ^ ^
+ /// | |
+ /// deferred import B: b1 -----+ |
+ /// |
+ /// deferred import C: c1 ---> c2 ---> c3
+ ///
+ /// The algorithm will compute a result with 5 deferred output units:
+ //
+ /// * unit {A}: contains a1
+ /// * unit {B}: contains b1
+ /// * unit {C}: contains c1, c2, and c3
+ /// * unit {A, B}: contains s1
+ /// * unit {A, B, C}: contains s2, and s3
+ ///
+ /// After marking everything reachable from main as part of the main output
+ /// unit, our algorithm will work as follows:
+ ///
+ /// * Initially all deferred elements have no mapping.
+ /// * We make note of work to do, initially to mark the root of each
+ /// deferred import:
+ /// * a1 with A, and recurse from there.
+ /// * b1 with B, and recurse from there.
+ /// * c1 with C, and recurse from there.
+ /// * we update a1, s1, s2, s3 from no mapping to {A}
+ /// * we update b1 from no mapping to {B}, and when we find s1 we notice
+ /// that s1 is already associated with another import set {A}, so we make
+ /// note of additional work for later to mark s1 with {A, B}
+ /// * we update c1, c2, c3 to {C}, and make a note to update s2 with {A, C}
+ /// * we update s1 to {A, B}, and update the existing note to update s2, now
+ /// with {A, B, C}
+ /// * finally we update s2 and s3 with {A, B, C} in one go, without ever
+ /// updating them to the intermediate state {A, C}.
+ ///
+ /// The implementation below does atomic updates from one import-set to
+ /// another. At first we add one deferred import at a time, but as the
+ /// algorithm progesses it may update a small import-set with a larger
+ /// import-set in one go. The key of this algorithm is to detect when sharing
+ /// begins, so we can update those elements more efficently.
+ ///
+ /// To detect these merge points where sharing begins, the implementation
+ /// below uses `a swap operation`: we first compare what the old import-set
+ /// is, and if it matches our expectation, the swap is done and we recurse,
+ /// otherwise a merge root was detected and we enqueue a new segment of
+ /// updates for later.
+ ///
+ /// TODO(sigmund): investigate different heuristics for how to select the next
+ /// work item (e.g. we might converge faster if we pick first the update that
+ /// contains a bigger delta.)
void onResolutionComplete(FunctionEntity main, ClosedWorld closedWorld) {
- if (!isProgramSplit) {
- allOutputUnits.add(mainOutputUnit);
- return;
- }
+ if (!isProgramSplit) return;
if (main == null) return;
- MethodElement mainMethod = main;
- LibraryElement mainLibrary = mainMethod.library;
- _importedDeferredBy = new Map<_DeferredImport, Set<Element>>();
- _constantsDeferredBy = new Map<_DeferredImport, Set<ConstantValue>>();
- _importedDeferredBy[_fakeMainImport] = _mainElements;
work() {
- // Starting from main, traverse the program and find all dependencies.
- _mapDependencies(element: mainMethod, import: _fakeMainImport);
+ var queue = new WorkQueue(this.importSets);
+
+ // Add `main` and their recursive dependencies to the main output unit.
+ // We do this upfront to avoid wasting time visiting these elements when
+ // analyzing deferred imports.
+ queue.addElement(main, importSets.mainSet);
- // Also add "global" dependencies to the main OutputUnit. These are
+ // Also add "global" dependencies to the main output unit. 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 (MethodElement element
in closedWorld.backendUsage.globalFunctionDependencies) {
- _mapDependencies(
- element: element.implementation, import: _fakeMainImport);
+ queue.addElement(element.implementation, importSets.mainSet);
}
for (ClassElement element
in closedWorld.backendUsage.globalClassDependencies) {
- _mapDependencies(
- element: element.implementation, import: _fakeMainImport);
+ queue.addElement(element.implementation, importSets.mainSet);
}
-
- // Now check to see if we have to add more elements due to mirrors.
if (closedWorld.backendUsage.isMirrorsUsed) {
- _addMirrorElements();
+ _addMirrorElementsForLibrary(queue, main.library, importSets.mainSet);
}
- // 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);
+ void emptyQueue() {
+ while (queue.isNotEmpty) {
+ var item = queue.nextItem();
+ if (item.element != null) {
+ var oldSet = _elementToSet[item.element];
+ var newSet = importSets.union(oldSet, item.newSet);
+ _updateElementRecursive(item.element, oldSet, newSet, queue,
+ isMirrorUsage: item.isMirrorUsage);
+ } else if (item.value != null) {
+ var oldSet = _constantToSet[item.value];
+ var newSet = importSets.union(oldSet, item.newSet);
+ _updateConstantRecursive(item.value, oldSet, newSet, queue);
}
}
}
- // 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);
- });
+ emptyQueue();
+ if (closedWorld.backendUsage.isMirrorsUsed) {
+ _addDeferredMirrorElements(queue);
+ emptyQueue();
+ }
- // Generate a unique name for each OutputUnit.
- _assignNamesToOutputUnits(allOutputUnits);
+ _createOutputUnits();
+ _setupHunksToLoad();
}
- reporter.withCurrentElement(mainLibrary, () => measure(work));
+ reporter.withCurrentElement(main.library, () => measure(work));
- // Notify the impact strategy impacts are no longer needed for deferred
- // load.
+ // Notify that we no longer need impacts for deferred load, so they can be
+ // discarded at this time.
compiler.impactStrategy.onImpactUsed(IMPACT_USE);
}
@@ -811,7 +883,6 @@ class DeferredLoadTask extends CompilerTask {
if (mainLibrary == null) return;
// TODO(johnniwinther): Support deferred load for kernel based elements.
if (compiler.options.useKernel) return;
- _allDeferredImports[_fakeMainImport] = mainLibrary;
var lastDeferred;
// When detecting duplicate prefixes of deferred libraries there are 4
// cases of duplicate prefixes:
@@ -862,17 +933,13 @@ class DeferredLoadTask extends CompilerTask {
// 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;
Uri mainLibraryUri = compiler.mainLibraryUri;
- _deferredImportDescriptions[key] =
+ _deferredImportDescriptions[import] =
new ImportDescription(import, library, mainLibraryUri);
}
isProgramSplit = true;
@@ -971,8 +1038,8 @@ class DeferredLoadTask extends CompilerTask {
Map<String, Map<String, dynamic>> computeDeferredMap() {
Map<String, Map<String, dynamic>> mapping =
new Map<String, Map<String, dynamic>>();
- _deferredImportDescriptions.keys.forEach((_DeferredImport import) {
- List<OutputUnit> outputUnits = hunksToLoad[importDeferName[import]];
+ _deferredImportDescriptions.keys.forEach((ImportElement import) {
+ List<OutputUnit> outputUnits = hunksToLoad[_importDeferName[import]];
ImportDescription description = _deferredImportDescriptions[import];
Map<String, dynamic> libraryMap = mapping.putIfAbsent(
description.importingUri,
@@ -981,7 +1048,7 @@ class DeferredLoadTask extends CompilerTask {
"imports": <String, List<String>>{}
});
- libraryMap["imports"][importDeferName[import]] =
+ libraryMap["imports"][_importDeferName[import]] =
outputUnits.map((OutputUnit outputUnit) {
return deferredPartFileName(outputUnit.name);
}).toList();
@@ -1007,33 +1074,51 @@ class DeferredLoadTask extends CompilerTask {
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');
+ _elementToSet.forEach((Entity element, ImportSet importSet) {
+ elementMap.putIfAbsent(importSet.unit, () => <String>[]).add('$element');
});
- _constantToOutputUnit.forEach((ConstantValue value, OutputUnit output) {
+ _constantToSet.forEach((ConstantValue value, ImportSet importSet) {
constantMap
- .putIfAbsent(output, () => <String>[])
+ .putIfAbsent(importSet.unit, () => <String>[])
.add(value.toStructuredText());
});
- StringBuffer sb = new StringBuffer();
+ Map<OutputUnit, String> text = {};
for (OutputUnit outputUnit in allOutputUnits) {
- sb.write('\n-------------------------------\n');
- sb.write('Output unit: ${outputUnit.name}');
+ StringBuffer unitText = new StringBuffer();
+ if (outputUnit.isMainOutput) {
+ unitText.write(' <MAIN UNIT>');
+ } else {
+ unitText.write(' imports:');
+ var imports = outputUnit._imports.map((i) => '${i.uri}').toList()
+ ..sort();
+ for (var i in imports) {
+ unitText.write('\n $i:');
+ }
+ }
List<String> elements = elementMap[outputUnit];
if (elements != null) {
- sb.write('\n elements:');
+ unitText.write('\n elements:');
for (String element in elements..sort()) {
- sb.write('\n $element');
+ unitText.write('\n $element');
}
}
List<String> constants = constantMap[outputUnit];
if (constants != null) {
- sb.write('\n constants:');
+ unitText.write('\n constants:');
for (String value in constants..sort()) {
- sb.write('\n $value');
+ unitText.write('\n $value');
}
}
+ text[outputUnit] = '$unitText';
+ }
+
+ StringBuffer sb = new StringBuffer();
+ for (OutputUnit outputUnit in allOutputUnits.toList()
+ ..sort((a, b) => text[a].compareTo(text[b]))) {
+ sb.write('\n\n-------------------------------\n');
+ sb.write('Output unit: ${outputUnit.name}');
+ sb.write('\n ${text[outputUnit]}');
}
return sb.toString();
}
@@ -1061,67 +1146,249 @@ class ImportDescription {
}
}
-/// A node in the deferred import graph.
+/// Indirectly represents a deferred import in an [ImportSet].
///
-/// This class serves as the root node; the 'import' of the main library.
+/// We could directly store the [declaration] in [ImportSet], but adding this
+/// class makes some of the import set operations more efficient.
class _DeferredImport {
- const _DeferredImport();
-
- /// Computes a suggestive name for this import.
- String computeImportDeferName(Compiler compiler) => 'main';
+ final ImportElement declaration;
- ImportElement get declaration => null;
+ /// Canonical index associated with [declaration]. This is used to efficiently
+ /// implement [ImportSetLattice.union].
+ final int index;
- String toString() => 'main';
+ _DeferredImport(this.declaration, this.index);
}
-/// A node in the deferred import graph defined by a deferred import directive.
-class _DeclaredDeferredImport implements _DeferredImport {
- final ImportElement declaration;
-
- _DeclaredDeferredImport(this.declaration);
+/// A compact lattice representation of import sets and subsets.
+///
+/// We use a graph of nodes to represent elements of the lattice, but only
+/// create new nodes on-demand as they are needed by the deferred loading
+/// algorithm.
+///
+/// The constructions of nodes is carefully done by storing imports in a
+/// specific order. This ensures that we have a unique and canonical
+/// representation for each subset.
+class ImportSetLattice {
+ /// Index of deferred imports that defines the canonical order used by the
+ /// operations below.
+ Map<ImportElement, _DeferredImport> _importIndex = {};
+
+ /// The canonical instance representing the empty import set.
+ ImportSet _emptySet = new ImportSet();
+
+ /// The import set representing the main output unit, which happens to be
+ /// implemented as an empty set in our algorithm.
+ ImportSet get mainSet => _emptySet;
+
+ /// Get the singleton import set that only contains [import].
+ ImportSet singleton(ImportElement import) {
+ // Ensure we have import in the index.
+ return _emptySet._add(_wrap(import));
+ }
- @override
- String computeImportDeferName(Compiler compiler) {
- String result;
- if (declaration.isDeferred) {
- if (declaration.prefix != null) {
- result = declaration.prefix.name;
+ /// Get the import set that includes the union of [a] and [b].
+ ImportSet union(ImportSet a, ImportSet b) {
+ if (a == null || a == _emptySet) return b;
+ if (b == null || b == _emptySet) return a;
+
+ // We create the union by merging the imports in canonical order first, and
+ // then getting (or creating) the canonical sets by adding an import at a
+ // time.
+ List<_DeferredImport> aImports = a._imports;
+ List<_DeferredImport> bImports = b._imports;
+ int i = 0, j = 0, lastAIndex = 0, lastBIndex = 0;
+ var result = _emptySet;
+ while (i < aImports.length && j < bImports.length) {
+ var importA = aImports[i];
+ var importB = bImports[j];
+ assert(lastAIndex <= importA.index);
+ assert(lastBIndex <= importB.index);
+ if (importA.index < importB.index) {
+ result = result._add(importA);
+ i++;
} 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);
- ResolutionDartType type =
- value.getType(compiler.resolution.commonElements);
- Element element = type.element;
- if (element ==
- compiler.resolution.commonElements.deferredLibraryClass) {
- ConstructedConstantValue constant = value;
- StringConstantValue s = constant.fields.values.single;
- result = s.primitiveValue;
- break;
- }
+ result = result._add(importB);
+ j++;
}
}
- assert(result != null);
+ for (; i < aImports.length; i++) {
+ result = result._add(aImports[i]);
+ }
+ for (; j < bImports.length; j++) {
+ result = result._add(bImports[j]);
+ }
+
return result;
}
- bool operator ==(other) {
- if (other is! _DeclaredDeferredImport) return false;
- return declaration == other.declaration;
+ /// Get the index for an [import] according to the canonical order.
+ _DeferredImport _wrap(ImportElement import) {
+ return _importIndex.putIfAbsent(
+ import, () => new _DeferredImport(import, _importIndex.length));
}
+}
- int get hashCode => declaration.hashCode * 17;
+/// A canonical set of deferred imports.
+class ImportSet {
+ /// Imports that are part of this set.
+ ///
+ /// Invariant: the order in which elements are added must respect the
+ /// canonical order of all imports in [ImportSetLattice].
+ final List<_DeferredImport> _imports;
+
+ /// Links to other import sets in the lattice by adding one import.
+ final Map<_DeferredImport, ImportSet> _transitions =
+ <_DeferredImport, ImportSet>{};
+
+ ImportSet([this._imports = const <_DeferredImport>[]]);
+
+ /// The output unit corresponding to this set of imports, if any.
+ OutputUnit unit;
+
+ int get length => _imports.length;
+
+ /// Create an import set that adds [import] to all the imports on this set.
+ /// This assumes that import's canonical order comes after all imports in
+ /// this current set. This should only be called from [ImportSetLattice],
+ /// since it is where we preserve this invariant.
+ ImportSet _add(_DeferredImport import) {
+ return _transitions.putIfAbsent(import, () {
+ var result = new ImportSet(new List.from(_imports)..add(import));
+ result._transitions[import] = result;
+ return result;
+ });
+ }
+
+ String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.write('ImportSet(size: $length, ');
+ for (var import in _imports) {
+ sb.write('${import.declaration.prefix} ');
+ }
+ sb.write(')');
+ return '$sb';
+ }
+}
+
+/// The algorithm work queue.
+class WorkQueue {
+ /// The actual queue of work that needs to be done.
+ final Queue<WorkItem> queue = new Queue<WorkItem>();
+
+ /// An index to find work items in the queue corresponding to an entity.
+ final Map<Entity, WorkItem> pendingElements = <Entity, WorkItem>{};
+
+ /// An index to find work items in the queue corresponding to a constant.
+ final Map<ConstantValue, WorkItem> pendingConstants =
+ <ConstantValue, WorkItem>{};
+
+ /// Lattice used to compute unions of [ImportSet]s.
+ final ImportSetLattice _importSets;
- String toString() => '$declaration';
+ WorkQueue(this._importSets);
+
+ /// Whether there are no more work items in the queue.
+ bool get isNotEmpty => queue.isNotEmpty;
+
+ /// Pop the next element in the queue.
+ WorkItem nextItem() {
+ assert(isNotEmpty);
+ var item = queue.removeFirst();
+ if (item.element != null) pendingElements.remove(item.element);
+ if (item.value != null) pendingConstants.remove(item.value);
+ return item;
+ }
+
+ /// Add to the queue that [element] should be updated to include all imports
+ /// in [importSet]. If there is already a work item in the queue for
+ /// [element], this makes sure that the work item now includes the union of
+ /// [importSet] and the existing work item's import set.
+ void addElement(Entity element, ImportSet importSet, {isMirrorUsage: false}) {
+ var item = pendingElements[element];
+ if (item == null) {
+ item = new WorkItem(element, importSet);
+ pendingElements[element] = item;
+ queue.add(item);
+ } else {
+ item.newSet = _importSets.union(item.newSet, importSet);
+ }
+ if (isMirrorUsage) item.isMirrorUsage = true;
+ }
+
+ /// Add to the queue that [constant] should be updated to include all imports
+ /// in [importSet]. If there is already a work item in the queue for
+ /// [constant], this makes sure that the work item now includes the union of
+ /// [importSet] and the existing work item's import set.
+ void addConstant(ConstantValue constant, ImportSet importSet) {
+ var item = pendingConstants[constant];
+ if (item == null) {
+ item = new WorkItem.constant(constant, importSet);
+ pendingConstants[constant] = item;
+ queue.add(item);
+ } else {
+ item.newSet = _importSets.union(item.newSet, importSet);
+ }
+ }
+}
+
+/// Summary of the work that needs to be done on an entity or constant.
+class WorkItem {
+ /// Entity to be recursively updated.
+ final Entity element;
+
+ /// Constant to be recursively updated.
+ final ConstantValue value;
+
+ /// Additional imports that use [element] or [value] and need to be added by
+ /// the algorithm.
+ ///
+ /// This is non-final in case we add more deferred imports to the set before
+ /// the work item is applied (see [WorkQueue.addElement] and
+ /// [WorkQueue.addConstant]).
+ ImportSet newSet;
+
+ /// Whether [element] is used via mirrors.
+ ///
+ /// This is non-final in case we later discover that the same [element] is
+ /// used via mirrors (but before the work item is applied).
+ bool isMirrorUsage = false;
+
+ WorkItem(this.element, this.newSet) : value = null;
+ WorkItem.constant(this.value, this.newSet) : element = null;
+}
+
+/// Returns a name for a deferred import.
+// TODO(sigmund): delete support for the old annotation-style syntax.
+String _computeImportDeferName(ImportElement declaration, 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);
+ ResolutionDartType type =
+ value.getType(compiler.resolution.commonElements);
+ Element element = type.element;
+ if (element == compiler.resolution.commonElements.deferredLibraryClass) {
+ ConstructedConstantValue constant = value;
+ StringConstantValue s = constant.fields.values.single;
+ result = s.primitiveValue;
+ break;
+ }
+ }
+ }
+ assert(result != null);
+ return result;
}
« no previous file with comments | « no previous file | pkg/compiler/lib/src/dump_info.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698