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