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