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

Side by Side Diff: pkg/compiler/lib/src/deferred_load.dart

Issue 2997233002: Update defer loading algorithm: (Closed)
Patch Set: follow up comments + ensure sorted output Created 3 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | pkg/compiler/lib/src/dump_info.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 library deferred_load; 5 library deferred_load;
6 6
7 import 'dart:collection' show Queue;
8
7 import 'common/tasks.dart' show CompilerTask; 9 import 'common/tasks.dart' show CompilerTask;
8 import 'common.dart'; 10 import 'common.dart';
9 import 'compiler.dart' show Compiler; 11 import 'compiler.dart' show Compiler;
10 import 'constants/expressions.dart' show ConstantExpression; 12 import 'constants/expressions.dart' show ConstantExpression;
11 import 'constants/values.dart' 13 import 'constants/values.dart'
12 show 14 show
13 ConstantValue, 15 ConstantValue,
14 ConstructedConstantValue, 16 ConstructedConstantValue,
15 DeferredConstantValue, 17 DeferredConstantValue,
16 StringConstantValue; 18 StringConstantValue;
(...skipping 30 matching lines...) Expand all
47 49
48 /// A "hunk" of the program that will be loaded whenever one of its [imports] 50 /// A "hunk" of the program that will be loaded whenever one of its [imports]
49 /// are loaded. 51 /// are loaded.
50 /// 52 ///
51 /// Elements that are only used in one deferred import, is in an OutputUnit with 53 /// Elements that are only used in one deferred import, is in an OutputUnit with
52 /// the deferred import as single element in the [imports] set. 54 /// the deferred import as single element in the [imports] set.
53 /// 55 ///
54 /// Whenever a deferred Element is shared between several deferred imports it is 56 /// Whenever a deferred Element is shared between several deferred imports it is
55 /// in an output unit with those imports in the [imports] Set. 57 /// in an output unit with those imports in the [imports] Set.
56 /// 58 ///
57 /// OutputUnits are equal if their [imports] are equal. 59 /// We never create two OutputUnits sharing the same set of [imports].
58 class OutputUnit { 60 class OutputUnit implements Comparable<OutputUnit> {
59 /// The deferred imports that will load this output unit when one of them is
60 /// loaded.
61 final Setlet<_DeferredImport> imports = new Setlet<_DeferredImport>();
62
63 /// `true` if this output unit is for the main output file. 61 /// `true` if this output unit is for the main output file.
64 final bool isMainOutput; 62 final bool isMainOutput;
65 63
66 /// A unique name representing this [OutputUnit]. 64 /// A unique name representing this [OutputUnit].
67 String name; 65 final String name;
68 66
69 OutputUnit({this.isMainOutput: false}); 67 /// The deferred imports that use the elements in this output unit.
68 final Set<ImportElement> _imports;
70 69
71 String toString() => "OutputUnit($name)"; 70 OutputUnit(this.isMainOutput, this.name, this._imports);
72 71
73 bool operator ==(other) { 72 int compareTo(OutputUnit other) {
74 return other is OutputUnit && 73 if (identical(this, other)) return 0;
75 imports.length == other.imports.length && 74 if (isMainOutput && !other.isMainOutput) return -1;
76 imports.containsAll(other.imports); 75 if (!isMainOutput && other.isMainOutput) return 1;
76 var size = _imports.length;
77 var otherSize = other._imports.length;
78 if (size != otherSize) return size.compareTo(otherSize);
79 var imports = _imports.toList();
80 var otherImports = other._imports.toList();
81 for (var i = 0; i < size; i++) {
82 if (imports[i] == otherImports[i]) continue;
83 var a = imports[i].uri.path;
84 var b = otherImports[i].uri.path;
85 var cmp = a.compareTo(b);
86 if (cmp != 0) return cmp;
87 }
88 // TODO(sigmund): make compare stable. If we hit this point, all imported
89 // libraries are the same, however [this] and [other] use different deferred
90 // imports in the program. We can make this stable if we sort based on the
91 // deferred imports themselves (e.g. their declaration location).
92 return name.compareTo(other.name);
77 } 93 }
78 94
79 int get hashCode { 95 String toString() => "OutputUnit($name, $_imports)";
80 int sum = 0;
81 for (_DeferredImport import in imports) {
82 sum = (sum + import.hashCode) & 0x3FFFFFFF; // Stay in 30 bit range.
83 }
84 return sum;
85 }
86 } 96 }
87 97
88 /// For each deferred import, find elements and constants to be loaded when that 98 /// For each deferred import, find elements and constants to be loaded when that
89 /// import is loaded. Elements that are used by several deferred imports are in 99 /// import is loaded. Elements that are used by several deferred imports are in
90 /// shared OutputUnits. 100 /// shared OutputUnits.
91 class DeferredLoadTask extends CompilerTask { 101 class DeferredLoadTask extends CompilerTask {
92 /// The name of this task. 102 /// The name of this task.
93 String get name => 'Deferred Loading'; 103 String get name => 'Deferred Loading';
94 104
95 /// DeferredLibrary from dart:async 105 /// DeferredLibrary from dart:async
96 ClassElement get deferredLibraryClass => 106 ClassElement get deferredLibraryClass =>
97 compiler.resolution.commonElements.deferredLibraryClass; 107 compiler.resolution.commonElements.deferredLibraryClass;
98 108
99 /// A synthetic import representing the loading of the main program.
100 final _DeferredImport _fakeMainImport = const _DeferredImport();
101
102 /// The OutputUnit that will be loaded when the program starts. 109 /// The OutputUnit that will be loaded when the program starts.
103 final OutputUnit mainOutputUnit = new OutputUnit(isMainOutput: true); 110 OutputUnit mainOutputUnit;
104 111
105 /// A set containing (eventually) all output units that will result from the 112 /// A set containing (eventually) all output units that will result from the
106 /// program. 113 /// program.
107 final Set<OutputUnit> allOutputUnits = new Set<OutputUnit>(); 114 final List<OutputUnit> allOutputUnits = new List<OutputUnit>();
108 115
109 /// Will be `true` if the program contains deferred libraries. 116 /// Will be `true` if the program contains deferred libraries.
110 bool isProgramSplit = false; 117 bool isProgramSplit = false;
111 118
112 static const ImpactUseCase IMPACT_USE = const ImpactUseCase('Deferred load'); 119 static const ImpactUseCase IMPACT_USE = const ImpactUseCase('Deferred load');
113 120
114 /// A mapping from the name of a defer import to all the output units it 121 /// A mapping from the name of a defer import to all the output units it
115 /// depends on in a list of lists to be loaded in the order they appear. 122 /// depends on in a list of lists to be loaded in the order they appear.
116 /// 123 ///
117 /// For example {"lib1": [[lib1_lib2_lib3], [lib1_lib2, lib1_lib3], 124 /// For example {"lib1": [[lib1_lib2_lib3], [lib1_lib2, lib1_lib3],
118 /// [lib1]]} would mean that in order to load "lib1" first the hunk 125 /// [lib1]]} would mean that in order to load "lib1" first the hunk
119 /// lib1_lib2_lib2 should be loaded, then the hunks lib1_lib2 and lib1_lib3 126 /// lib1_lib2_lib2 should be loaded, then the hunks lib1_lib2 and lib1_lib3
120 /// can be loaded in parallel. And finally lib1 can be loaded. 127 /// can be loaded in parallel. And finally lib1 can be loaded.
121 final Map<String, List<OutputUnit>> hunksToLoad = 128 final Map<String, List<OutputUnit>> hunksToLoad =
122 new Map<String, List<OutputUnit>>(); 129 new Map<String, List<OutputUnit>>();
123 130
124 /// A cache of the result of calling `computeImportDeferName` on the keys of 131 /// A cache of the result of calling `computeImportDeferName` on the keys of
125 /// this map. 132 /// this map.
126 final Map<_DeferredImport, String> importDeferName = 133 final Map<ImportElement, String> _importDeferName = <ImportElement, String>{};
127 <_DeferredImport, String>{};
128 134
129 /// A mapping from elements and constants to their output unit. Query this via 135 /// A mapping from elements and constants to their output unit. Query this via
130 /// [outputUnitForElement] 136 /// [outputUnitForElement]
131 final Map<Element, OutputUnit> _elementToOutputUnit = 137 final Map<Entity, ImportSet> _elementToSet = new Map<Entity, ImportSet>();
132 new Map<Element, OutputUnit>();
133 138
134 /// A mapping from constants to their output unit. Query this via 139 /// A mapping from constants to their output unit. Query this via
135 /// [outputUnitForConstant] 140 /// [outputUnitForConstant]
136 final Map<ConstantValue, OutputUnit> _constantToOutputUnit = 141 final Map<ConstantValue, ImportSet> _constantToSet =
137 new Map<ConstantValue, OutputUnit>(); 142 new Map<ConstantValue, ImportSet>();
138 143
139 /// All the imports with a [DeferredLibrary] annotation, mapped to the 144 Iterable<ImportElement> get _allDeferredImports =>
140 /// [LibraryElement] they import. 145 _deferredImportDescriptions.keys;
141 /// The main library is included in this set for convenience.
142 final Map<_DeferredImport, LibraryElement> _allDeferredImports =
143 new Map<_DeferredImport, LibraryElement>();
144 146
145 /// Because the token-stream is forgotten later in the program, we cache a 147 /// Because the token-stream is forgotten later in the program, we cache a
146 /// description of each deferred import. 148 /// description of each deferred import.
147 final Map<_DeferredImport, ImportDescription> _deferredImportDescriptions = 149 final Map<ImportElement, ImportDescription> _deferredImportDescriptions =
148 <_DeferredImport, ImportDescription>{}; 150 <ImportElement, ImportDescription>{};
149 151
150 // For each deferred import we want to know exactly what elements have to 152 /// A lattice to compactly represent multiple subsets of imports.
151 // be loaded. 153 final ImportSetLattice importSets = new ImportSetLattice();
152 Map<_DeferredImport, Set<Element>> _importedDeferredBy = null;
153 Map<_DeferredImport, Set<ConstantValue>> _constantsDeferredBy = null;
154
155 Set<Element> _mainElements = new Set<Element>();
156 154
157 final Compiler compiler; 155 final Compiler compiler;
158 DeferredLoadTask(Compiler compiler) 156 DeferredLoadTask(Compiler compiler)
159 : compiler = compiler, 157 : compiler = compiler,
160 super(compiler.measurer) { 158 super(compiler.measurer) {
161 mainOutputUnit.imports.add(_fakeMainImport); 159 mainOutputUnit = new OutputUnit(true, 'main', new Set<ImportElement>());
160 importSets.mainSet.unit = mainOutputUnit;
161 allOutputUnits.add(mainOutputUnit);
162 } 162 }
163 163
164 JavaScriptBackend get backend => compiler.backend; 164 JavaScriptBackend get backend => compiler.backend;
165 DiagnosticReporter get reporter => compiler.reporter; 165 DiagnosticReporter get reporter => compiler.reporter;
166 166
167 /// Returns the [OutputUnit] where [element] belongs. 167 /// Returns the [OutputUnit] where [element] belongs.
168 OutputUnit outputUnitForElement(Entity entity) { 168 OutputUnit outputUnitForElement(Entity entity) {
169 // TODO(johnniwinther): Support use of entities by splitting maps by 169 // TODO(johnniwinther): Support use of entities by splitting maps by
170 // entity kind. 170 // entity kind.
171 if (!isProgramSplit) return mainOutputUnit; 171 if (!isProgramSplit) return mainOutputUnit;
172 Element element = entity; 172 Element element = entity;
173 element = element.implementation; 173 element = element.implementation;
174 while (!_elementToOutputUnit.containsKey(element)) { 174 while (!_elementToSet.containsKey(element)) {
175 // TODO(21051): workaround: it looks like we output annotation constants 175 // TODO(21051): workaround: it looks like we output annotation constants
176 // for classes that we don't include in the output. This seems to happen 176 // for classes that we don't include in the output. This seems to happen
177 // when we have reflection but can see that some classes are not needed. 177 // when we have reflection but can see that some classes are not needed.
178 // We still add the annotation but don't run through it below (where we 178 // We still add the annotation but don't run through it below (where we
179 // assign every element to its output unit). 179 // assign every element to its output unit).
180 if (element.enclosingElement == null) { 180 if (element.enclosingElement == null) {
181 _elementToOutputUnit[element] = mainOutputUnit; 181 _elementToSet[element] = importSets.mainSet;
182 break; 182 break;
183 } 183 }
184 element = element.enclosingElement.implementation; 184 element = element.enclosingElement.implementation;
185 } 185 }
186 return _elementToOutputUnit[element]; 186 return _elementToSet[element].unit;
187 } 187 }
188 188
189 /// Returns the [OutputUnit] where [element] belongs. 189 /// Returns the [OutputUnit] where [element] belongs.
190 OutputUnit outputUnitForClass(ClassEntity element) { 190 OutputUnit outputUnitForClass(ClassEntity element) {
191 return outputUnitForElement(element); 191 return outputUnitForElement(element);
192 } 192 }
193 193
194 /// Returns the [OutputUnit] where [element] belongs. 194 /// Returns the [OutputUnit] where [element] belongs.
195 OutputUnit outputUnitForMember(MemberEntity element) { 195 OutputUnit outputUnitForMember(MemberEntity element) {
196 return outputUnitForElement(element); 196 return outputUnitForElement(element);
197 } 197 }
198 198
199 /// Direct access to the output-unit to element relation used for testing. 199 /// Direct access to the output-unit to element relation used for testing.
200 OutputUnit getOutputUnitForElementForTesting(Element element) { 200 OutputUnit getOutputUnitForElementForTesting(Entity element) {
201 return _elementToOutputUnit[element]; 201 return _elementToSet[element]?.unit;
202 } 202 }
203 203
204 /// Returns the [OutputUnit] where [constant] belongs. 204 /// Returns the [OutputUnit] where [constant] belongs.
205 OutputUnit outputUnitForConstant(ConstantValue constant) { 205 OutputUnit outputUnitForConstant(ConstantValue constant) {
206 if (!isProgramSplit) return mainOutputUnit; 206 if (!isProgramSplit) return mainOutputUnit;
207 return _constantToOutputUnit[constant]; 207 return _constantToSet[constant]?.unit;
208 } 208 }
209 209
210 /// Direct access to the output-unit to constants map used for testing. 210 /// Direct access to the output-unit to constants map used for testing.
211 Map<ConstantValue, OutputUnit> get outputUnitForConstantsForTesting { 211 Iterable<ConstantValue> get constantsForTesting => _constantToSet.keys;
212 return _constantToOutputUnit;
213 }
214 212
215 bool isDeferred(Entity element) { 213 bool isDeferred(Entity element) {
216 return outputUnitForElement(element) != mainOutputUnit; 214 return outputUnitForElement(element) != mainOutputUnit;
217 } 215 }
218 216
219 bool isDeferredClass(ClassEntity element) { 217 bool isDeferredClass(ClassEntity element) {
220 return outputUnitForElement(element) != mainOutputUnit; 218 return outputUnitForElement(element) != mainOutputUnit;
221 } 219 }
222 220
223 /// Returns the unique name for the deferred import of [prefix]. 221 /// Returns the unique name for the deferred import of [prefix].
224 String getImportDeferName(Spannable node, PrefixElement prefix) { 222 String getImportDeferName(Spannable node, PrefixElement prefix) {
225 String name = 223 String name = _importDeferName[prefix.deferredImport];
226 importDeferName[new _DeclaredDeferredImport(prefix.deferredImport)];
227 if (name == null) { 224 if (name == null) {
228 reporter.internalError(node, "No deferred name for $prefix."); 225 reporter.internalError(node, "No deferred name for $prefix.");
229 } 226 }
230 return name; 227 return name;
231 } 228 }
232 229
230 /// Returns the names associated with each deferred import in [unit].
231 Iterable<String> getImportNames(OutputUnit unit) {
232 return unit._imports.map((i) => _importDeferName[i]);
233 }
234
233 /// Returns `true` if element [to] is reachable from element [from] without 235 /// Returns `true` if element [to] is reachable from element [from] without
234 /// crossing a deferred import. 236 /// crossing a deferred import.
235 /// 237 ///
236 /// For example, if we have two deferred libraries `A` and `B` that both 238 /// For example, if we have two deferred libraries `A` and `B` that both
237 /// import a library `C`, then even though elements from `A` and `C` end up in 239 /// import a library `C`, then even though elements from `A` and `C` end up in
238 /// different output units, there is a non-deferred path between `A` and `C`. 240 /// different output units, there is a non-deferred path between `A` and `C`.
239 bool hasOnlyNonDeferredImportPaths(Element from, Element to) { 241 bool hasOnlyNonDeferredImportPaths(Entity from, Entity to) {
240 OutputUnit outputUnitFrom = outputUnitForElement(from); 242 OutputUnit outputUnitFrom = outputUnitForElement(from);
241 OutputUnit outputUnitTo = outputUnitForElement(to); 243 OutputUnit outputUnitTo = outputUnitForElement(to);
242 return outputUnitTo.imports.containsAll(outputUnitFrom.imports); 244 if (outputUnitTo == mainOutputUnit) return true;
243 } 245 if (outputUnitFrom == mainOutputUnit) return false;
244 246 return outputUnitTo._imports.containsAll(outputUnitFrom._imports);
245 // TODO(het): use a union-find to canonicalize output units
246 OutputUnit _getCanonicalUnit(OutputUnit outputUnit) {
247 OutputUnit representative = allOutputUnits.lookup(outputUnit);
248 if (representative == null) {
249 representative = outputUnit;
250 allOutputUnits.add(representative);
251 }
252 return representative;
253 } 247 }
254 248
255 void registerConstantDeferredUse( 249 void registerConstantDeferredUse(
256 DeferredConstantValue constant, PrefixElement prefix) { 250 DeferredConstantValue constant, PrefixElement prefix) {
257 OutputUnit outputUnit = new OutputUnit(); 251 var newSet = importSets.singleton(prefix.deferredImport);
258 outputUnit.imports.add(new _DeclaredDeferredImport(prefix.deferredImport)); 252 assert(
259 253 _constantToSet[constant] == null || _constantToSet[constant] == newSet);
260 // Check to see if there is already a canonical output unit registered. 254 _constantToSet[constant] = newSet;
261 _constantToOutputUnit[constant] = _getCanonicalUnit(outputUnit);
262 } 255 }
263 256
264 /// Given [imports] that refer to an element from a library, determine whether 257 /// Given [imports] that refer to an element from a library, determine whether
265 /// the element is explicitly deferred. 258 /// the element is explicitly deferred.
266 static bool _isExplicitlyDeferred(Iterable<ImportElement> imports) { 259 static bool _isExplicitlyDeferred(Iterable<ImportElement> imports) {
267 // If the element is not imported explicitly, it is implicitly imported 260 // If the element is not imported explicitly, it is implicitly imported
268 // not deferred. 261 // not deferred.
269 if (imports.isEmpty) return false; 262 if (imports.isEmpty) return false;
270 // An element could potentially be loaded by several imports. If all of them 263 // An element could potentially be loaded by several imports. If all of them
271 // is explicitly deferred, we say the element is explicitly deferred. 264 // is explicitly deferred, we say the element is explicitly deferred.
(...skipping 231 matching lines...) Expand 10 before | Expand all | Expand 10 after
503 if (library.isPatched) { 496 if (library.isPatched) {
504 iterateTags(library.implementation); 497 iterateTags(library.implementation);
505 } 498 }
506 } 499 }
507 500
508 traverseLibrary(root); 501 traverseLibrary(root);
509 result.add(compiler.resolution.commonElements.coreLibrary); 502 result.add(compiler.resolution.commonElements.coreLibrary);
510 return result; 503 return result;
511 } 504 }
512 505
513 /// Add all dependencies of [constant] to the mapping of [import]. 506 /// Update the import set of all constants reachable from [constant], as long
514 void _mapConstantDependencies( 507 /// as they had the [oldSet]. As soon as we see a constant with a different
515 ConstantValue constant, _DeferredImport import) { 508 /// import set, we stop and enqueue a new recursive update in [queue].
516 Set<ConstantValue> constants = _constantsDeferredBy.putIfAbsent( 509 ///
517 import, () => new Set<ConstantValue>()); 510 /// Invariants: oldSet is either null or a subset of newSet.
518 if (constants.contains(constant)) return; 511 void _updateConstantRecursive(ConstantValue constant, ImportSet oldSet,
519 constants.add(constant); 512 ImportSet newSet, WorkQueue queue) {
520 if (constant is ConstructedConstantValue) { 513 if (constant == null) return;
521 ClassElement cls = constant.type.element; 514 var currentSet = _constantToSet[constant];
522 _mapDependencies(element: cls, import: import); 515
516 // Already visited.
517 if (currentSet == newSet) return;
518
519 // Elements in the main output unit always remain there.
520 if (currentSet == importSets.mainSet) return;
521
522 if (currentSet == oldSet) {
523 _constantToSet[constant] = newSet;
524 if (constant is ConstructedConstantValue) {
525 ClassElement cls = constant.type.element;
526 _updateElementRecursive(cls, oldSet, newSet, queue);
527 }
528 constant.getDependencies().forEach((ConstantValue dependency) {
529 if (dependency is DeferredConstantValue) {
530 /// New deferred-imports are only discovered when we are visiting the
531 /// main output unit (size == 0) or code reachable from a deferred
532 /// import (size == 1). After that, we are rediscovering the
533 /// same nodes we have already seen.
534 if (newSet.length <= 1) {
535 PrefixElement prefix = dependency.prefix;
536 queue.addConstant(
537 dependency, importSets.singleton(prefix.deferredImport));
538 }
539 } else {
540 _updateConstantRecursive(dependency, oldSet, newSet, queue);
541 }
542 });
543 } else {
544 assert(
545 // Invariant: we must mark main before we mark any deferred import.
546 newSet != importSets.mainSet || oldSet != null,
547 failedAt(
548 NO_LOCATION_SPANNABLE,
549 "Tried to assign ${constant.toDartText()} to the main output "
550 "unit, but it was assigned to $currentSet."));
551 queue.addConstant(constant, newSet);
523 } 552 }
524 constant.getDependencies().forEach((ConstantValue dependency) {
525 _mapConstantDependencies(dependency, import);
526 });
527 } 553 }
528 554
529 /// Recursively traverses the graph of dependencies from [element], mapping 555 /// Update the import set of all elements reachable from [element], as long as
530 /// deferred imports to each dependency it needs in the sets 556 /// they had the [oldSet]. As soon as we see an element with a different
531 /// [_importedDeferredBy] and [_constantsDeferredBy]. 557 /// import set, we stop and enqueue a new recursive update in [queue].
532 void _mapDependencies( 558 void _updateElementRecursive(
533 {Element element, _DeferredImport import, isMirrorUsage: false}) { 559 Element element, ImportSet oldSet, ImportSet newSet, WorkQueue queue,
534 Set<Element> elements = _importedDeferredBy[import] ??= new Set<Element>(); 560 {bool isMirrorUsage: false}) {
561 if (element == null) return;
562 var currentSet = _elementToSet[element];
535 563
536 Set<Element> dependentElements = new Set<Element>(); 564 // Already visited. We may visit some root nodes a second time with
537 Set<ConstantValue> dependentConstants = new Set<ConstantValue>(); 565 // [isMirrorUsage] in order to mark static members used reflectively.
566 if (currentSet == newSet && !isMirrorUsage) return;
538 567
539 LibraryElement library; 568 // Elements in the main output unit always remain there.
569 if (currentSet == importSets.mainSet) return;
540 570
541 if (element != null) { 571 if (currentSet == oldSet) {
542 // Only process elements once, unless we are doing dependencies due to 572 // Continue recursively updating from [oldSet] to [newSet].
543 // mirrors, which are added in additional traversals. 573 _elementToSet[element] = newSet;
544 if (!isMirrorUsage && elements.contains(element)) return;
545 // Anything used directly by main will be loaded from the start
546 // We do not need to traverse it again.
547 if (import != _fakeMainImport && _mainElements.contains(element)) return;
548 // This adds [element] to [_mainElements] because [_mainElements] is
549 // aliased with `_importedDeferredBy[_fakeMainImport]]`.
550 elements.add(element);
551 574
552 // This call can modify [dependentElements] and [dependentConstants]. 575 Set<Element> dependentElements = new Set<Element>();
576 Set<ConstantValue> dependentConstants = new Set<ConstantValue>();
553 _collectAllElementsAndConstantsResolvedFrom( 577 _collectAllElementsAndConstantsResolvedFrom(
554 element, dependentElements, dependentConstants, isMirrorUsage); 578 element, dependentElements, dependentConstants, isMirrorUsage);
555 579
556 library = element.library; 580 LibraryElement library = element.library;
557 } 581 for (Element dependency in dependentElements) {
582 Iterable<ImportElement> imports = _getImports(dependency, library);
583 if (_isExplicitlyDeferred(imports)) {
584 /// New deferred-imports are only discovered when we are visiting the
585 /// main output unit (size == 0) or code reachable from a deferred
586 /// import (size == 1). After that, we are rediscovering the
587 /// same nodes we have already seen.
588 if (newSet.length <= 1) {
589 for (ImportElement deferredImport in imports) {
590 queue.addElement(
591 dependency, importSets.singleton(deferredImport));
592 }
593 }
594 } else {
595 _updateElementRecursive(dependency, oldSet, newSet, queue);
596 }
597 }
558 598
559 for (Element dependency in dependentElements) { 599 for (ConstantValue dependency in dependentConstants) {
560 Iterable<ImportElement> imports = _getImports(dependency, library); 600 if (dependency is DeferredConstantValue) {
561 if (_isExplicitlyDeferred(imports)) { 601 if (newSet.length <= 1) {
562 for (ImportElement deferredImport in imports) { 602 PrefixElement prefix = dependency.prefix;
563 _mapDependencies( 603 queue.addConstant(
564 element: dependency, 604 dependency, importSets.singleton(prefix.deferredImport));
565 import: new _DeclaredDeferredImport(deferredImport)); 605 }
606 } else {
607 _updateConstantRecursive(dependency, oldSet, newSet, queue);
566 } 608 }
567 } else {
568 _mapDependencies(element: dependency, import: import);
569 } 609 }
570 } 610 } else {
571 611 queue.addElement(element, newSet);
572 for (ConstantValue dependency in dependentConstants) {
573 if (dependency is DeferredConstantValue) {
574 PrefixElement prefix = dependency.prefix;
575 _mapConstantDependencies(
576 dependency, new _DeclaredDeferredImport(prefix.deferredImport));
577 } else {
578 _mapConstantDependencies(dependency, import);
579 }
580 } 612 }
581 } 613 }
582 614
583 /// Adds extra dependencies coming from mirror usage. 615 /// Adds extra dependencies coming from mirror usage.
584 /// 616 void _addDeferredMirrorElements(WorkQueue queue) {
585 /// The elements are added with [_mapDependencies]. 617 for (ImportElement deferredImport in _allDeferredImports) {
586 void _addMirrorElements() { 618 _addMirrorElementsForLibrary(queue, deferredImport.importedLibrary,
587 void mapDependenciesIfResolved( 619 importSets.singleton(deferredImport));
588 Element element, _DeferredImport deferredImport) { 620 }
621 }
622
623 void _addMirrorElementsForLibrary(
624 WorkQueue queue, LibraryElement root, ImportSet newSet) {
625 void handleElementIfResolved(Element element) {
589 // If an element is the target of a MirrorsUsed annotation but never used 626 // If an element is the target of a MirrorsUsed annotation but never used
590 // It will not be resolved, and we should not call isNeededForReflection. 627 // It will not be resolved, and we should not call isNeededForReflection.
591 // TODO(sigurdm): Unresolved elements should just answer false when 628 // TODO(sigurdm): Unresolved elements should just answer false when
592 // asked isNeededForReflection. Instead an internal error is triggered. 629 // asked isNeededForReflection. Instead an internal error is triggered.
593 // So we have to filter them out here. 630 // So we have to filter them out here.
594 if (element is AnalyzableElementX && !element.hasTreeElements) return; 631 if (element is AnalyzableElementX && !element.hasTreeElements) return;
595 632
596 bool isAccessibleByReflection(Element element) { 633 bool isAccessibleByReflection(Element element) {
597 if (element.isLibrary) { 634 if (element.isLibrary) {
598 return false; 635 return false;
599 } else if (element.isClass) { 636 } else if (element.isClass) {
600 ClassElement cls = element; 637 ClassElement cls = element;
601 return compiler.backend.mirrorsData 638 return compiler.backend.mirrorsData
602 .isClassAccessibleByReflection(cls); 639 .isClassAccessibleByReflection(cls);
603 } else if (element.isTypedef) { 640 } else if (element.isTypedef) {
604 TypedefElement typedef = element; 641 TypedefElement typedef = element;
605 return compiler.backend.mirrorsData 642 return compiler.backend.mirrorsData
606 .isTypedefAccessibleByReflection(typedef); 643 .isTypedefAccessibleByReflection(typedef);
607 } else { 644 } else {
608 MemberElement member = element; 645 MemberElement member = element;
609 return compiler.backend.mirrorsData 646 return compiler.backend.mirrorsData
610 .isMemberAccessibleByReflection(member); 647 .isMemberAccessibleByReflection(member);
611 } 648 }
612 } 649 }
613 650
614 if (isAccessibleByReflection(element)) { 651 if (isAccessibleByReflection(element)) {
615 _mapDependencies( 652 queue.addElement(element, newSet, isMirrorUsage: true);
616 element: element, import: deferredImport, isMirrorUsage: true);
617 } 653 }
618 } 654 }
619 655
620 // For each deferred import we analyze all elements reachable from the 656 // For each deferred import we analyze all elements reachable from the
621 // imported library through non-deferred imports. 657 // imported library through non-deferred imports.
622 void handleLibrary(LibraryElement library, _DeferredImport deferredImport) { 658 void handleLibrary(LibraryElement library) {
623 library.implementation.forEachLocalMember((Element element) { 659 library.implementation.forEachLocalMember((Element element) {
624 mapDependenciesIfResolved(element, deferredImport); 660 handleElementIfResolved(element);
625 }); 661 });
626 662
627 void processMetadata(Element element) { 663 void processMetadata(Element element) {
628 for (MetadataAnnotation metadata in element.metadata) { 664 for (MetadataAnnotation metadata in element.metadata) {
629 ConstantValue constant = 665 ConstantValue constant =
630 backend.constants.getConstantValueForMetadata(metadata); 666 backend.constants.getConstantValueForMetadata(metadata);
631 if (constant != null) { 667 if (constant != null) {
632 _mapConstantDependencies(constant, deferredImport); 668 queue.addConstant(constant, newSet);
633 } 669 }
634 } 670 }
635 } 671 }
636 672
637 processMetadata(library); 673 processMetadata(library);
638 library.imports.forEach(processMetadata); 674 library.imports.forEach(processMetadata);
639 library.exports.forEach(processMetadata); 675 library.exports.forEach(processMetadata);
640 } 676 }
641 677
642 for (_DeferredImport deferredImport in _allDeferredImports.keys) { 678 _nonDeferredReachableLibraries(root).forEach(handleLibrary);
643 LibraryElement deferredLibrary = _allDeferredImports[deferredImport];
644 for (LibraryElement library
645 in _nonDeferredReachableLibraries(deferredLibrary)) {
646 handleLibrary(library, deferredImport);
647 }
648 }
649 } 679 }
650 680
651 /// Computes a unique string for the name field for each outputUnit. 681 /// Computes a unique string for the name field for each outputUnit.
652 /// 682 void _createOutputUnits() {
653 /// Also sets up the [hunksToLoad] mapping. 683 int counter = 1;
654 void _assignNamesToOutputUnits(Set<OutputUnit> allOutputUnits) { 684 void addUnit(ImportSet importSet) {
685 if (importSet.unit != null) return;
686 var unit = new OutputUnit(false, '$counter',
687 importSet._imports.map((i) => i.declaration).toSet());
688 counter++;
689 importSet.unit = unit;
690 allOutputUnits.add(unit);
691 }
692
693 // Generate an output unit for all import sets that are associated with an
694 // element or constant.
695 _elementToSet.values.forEach(addUnit);
696 _constantToSet.values.forEach(addUnit);
697
698 // Sort output units to make the output of the compiler more stable.
699 allOutputUnits.sort();
700 }
701
702 void _setupHunksToLoad() {
655 Set<String> usedImportNames = new Set<String>(); 703 Set<String> usedImportNames = new Set<String>();
656 704
657 void computeImportDeferName(_DeferredImport import) { 705 void computeImportDeferName(ImportElement import) {
658 String result = import.computeImportDeferName(compiler); 706 String result = _computeImportDeferName(import, compiler);
659 assert(result != null); 707 assert(result != null);
660 importDeferName[import] = makeUnique(result, usedImportNames); 708 _importDeferName[import] = makeUnique(result, usedImportNames);
661 } 709 }
662 710
663 int counter = 1; 711 for (ImportElement import in _allDeferredImports) {
664
665 for (_DeferredImport import in _allDeferredImports.keys) {
666 computeImportDeferName(import); 712 computeImportDeferName(import);
667 } 713 }
668 714
669 for (OutputUnit outputUnit in allOutputUnits) {
670 if (outputUnit == mainOutputUnit) {
671 outputUnit.name = "main";
672 } else {
673 outputUnit.name = "$counter";
674 ++counter;
675 }
676 }
677
678 List sortedOutputUnits = new List.from(allOutputUnits);
679 // Sort the output units in descending order of the number of imports they 715 // Sort the output units in descending order of the number of imports they
680 // include. 716 // include.
681 717
682 // The loading of the output units must be ordered because a superclass 718 // The loading of the output units must be ordered because a superclass
683 // needs to be initialized before its subclass. 719 // needs to be initialized before its subclass.
684 // But a class can only depend on another class in an output unit shared by 720 // But a class can only depend on another class in an output unit shared by
685 // a strict superset of the imports: 721 // a strict superset of the imports:
686 // By contradiction: Assume a class C in output unit shared by imports in 722 // By contradiction: Assume a class C in output unit shared by imports in
687 // the set S1 = (lib1,.., lib_n) depends on a class D in an output unit 723 // the set S1 = (lib1,.., lib_n) depends on a class D in an output unit
688 // shared by S2 such that S2 not a superset of S1. Let lib_s be a library in 724 // shared by S2 such that S2 not a superset of S1. Let lib_s be a library in
689 // S1 not in S2. lib_s must depend on C, and then in turn on D. Therefore D 725 // S1 not in S2. lib_s must depend on C, and then in turn on D. Therefore D
690 // is not in the right output unit. 726 // is not in the right output unit.
691 sortedOutputUnits.sort((a, b) => b.imports.length - a.imports.length); 727 List sortedOutputUnits = allOutputUnits.reversed.toList();
692 728
693 // For each deferred import we find out which outputUnits to load. 729 // For each deferred import we find out which outputUnits to load.
694 for (_DeferredImport import in _allDeferredImports.keys) { 730 for (ImportElement import in _allDeferredImports) {
695 if (import == _fakeMainImport) continue; 731 // We expect to find an entry for any call to `loadLibrary`, even if
696 hunksToLoad[importDeferName[import]] = new List<OutputUnit>(); 732 // there is no code to load. In that case, the entry will be an empty
733 // list.
734 hunksToLoad[_importDeferName[import]] = new List<OutputUnit>();
697 for (OutputUnit outputUnit in sortedOutputUnits) { 735 for (OutputUnit outputUnit in sortedOutputUnits) {
698 if (outputUnit == mainOutputUnit) continue; 736 if (outputUnit == mainOutputUnit) continue;
699 if (outputUnit.imports.contains(import)) { 737 if (outputUnit._imports.contains(import)) {
700 hunksToLoad[importDeferName[import]].add(outputUnit); 738 hunksToLoad[_importDeferName[import]].add(outputUnit);
701 } 739 }
702 } 740 }
703 } 741 }
704 } 742 }
705 743
744 /// Performs the deferred loading algorithm.
745 ///
746 /// The deferred loading algorithm maps elements and constants to an output
747 /// unit. Each output unit is identified by a subset of deferred imports (an
748 /// [ImportSet]), and they will contain the elements that are inheretly used
749 /// by all those deferred imports. An element is used by a deferred import if
750 /// it is either loaded by that import or transitively accessed by an element
751 /// that the import loads. An empty set represents the main output unit,
752 /// which contains any elements that are accessed directly and are not
753 /// deferred.
754 ///
755 /// The algorithm traverses the element model recursively looking for
756 /// dependencies between elements. These dependencies may be deferred or
757 /// non-deferred. Deferred dependencies are mainly used to discover the root
758 /// elements that are loaded from deferred imports, while non-deferred
759 /// dependencies are used to recursively associate more elements to output
760 /// units.
761 ///
762 /// Naively, the algorithm traverses each root of a deferred import and marks
763 /// everything it can reach as being used by that import. To reduce how many
764 /// times we visit an element, we use an algorithm that works in segments: it
765 /// marks elements with a subset of deferred imports at a time, until it
766 /// detects a merge point where more deferred imports could be considered at
767 /// once.
768 ///
769 /// For example, consider this dependency graph (ignoring elements in the main
770 /// output unit):
771 ///
772 /// deferred import A: a1 ---> s1 ---> s2 -> s3
773 /// ^ ^
774 /// | |
775 /// deferred import B: b1 -----+ |
776 /// |
777 /// deferred import C: c1 ---> c2 ---> c3
778 ///
779 /// The algorithm will compute a result with 5 deferred output units:
780 //
781 /// * unit {A}: contains a1
782 /// * unit {B}: contains b1
783 /// * unit {C}: contains c1, c2, and c3
784 /// * unit {A, B}: contains s1
785 /// * unit {A, B, C}: contains s2, and s3
786 ///
787 /// After marking everything reachable from main as part of the main output
788 /// unit, our algorithm will work as follows:
789 ///
790 /// * Initially all deferred elements have no mapping.
791 /// * We make note of work to do, initially to mark the root of each
792 /// deferred import:
793 /// * a1 with A, and recurse from there.
794 /// * b1 with B, and recurse from there.
795 /// * c1 with C, and recurse from there.
796 /// * we update a1, s1, s2, s3 from no mapping to {A}
797 /// * we update b1 from no mapping to {B}, and when we find s1 we notice
798 /// that s1 is already associated with another import set {A}, so we make
799 /// note of additional work for later to mark s1 with {A, B}
800 /// * we update c1, c2, c3 to {C}, and make a note to update s2 with {A, C}
801 /// * we update s1 to {A, B}, and update the existing note to update s2, now
802 /// with {A, B, C}
803 /// * finally we update s2 and s3 with {A, B, C} in one go, without ever
804 /// updating them to the intermediate state {A, C}.
805 ///
806 /// The implementation below does atomic updates from one import-set to
807 /// another. At first we add one deferred import at a time, but as the
808 /// algorithm progesses it may update a small import-set with a larger
809 /// import-set in one go. The key of this algorithm is to detect when sharing
810 /// begins, so we can update those elements more efficently.
811 ///
812 /// To detect these merge points where sharing begins, the implementation
813 /// below uses `a swap operation`: we first compare what the old import-set
814 /// is, and if it matches our expectation, the swap is done and we recurse,
815 /// otherwise a merge root was detected and we enqueue a new segment of
816 /// updates for later.
817 ///
818 /// TODO(sigmund): investigate different heuristics for how to select the next
819 /// work item (e.g. we might converge faster if we pick first the update that
820 /// contains a bigger delta.)
706 void onResolutionComplete(FunctionEntity main, ClosedWorld closedWorld) { 821 void onResolutionComplete(FunctionEntity main, ClosedWorld closedWorld) {
707 if (!isProgramSplit) { 822 if (!isProgramSplit) return;
708 allOutputUnits.add(mainOutputUnit);
709 return;
710 }
711 if (main == null) return; 823 if (main == null) return;
712 MethodElement mainMethod = main;
713 LibraryElement mainLibrary = mainMethod.library;
714 _importedDeferredBy = new Map<_DeferredImport, Set<Element>>();
715 _constantsDeferredBy = new Map<_DeferredImport, Set<ConstantValue>>();
716 _importedDeferredBy[_fakeMainImport] = _mainElements;
717 824
718 work() { 825 work() {
719 // Starting from main, traverse the program and find all dependencies. 826 var queue = new WorkQueue(this.importSets);
720 _mapDependencies(element: mainMethod, import: _fakeMainImport);
721 827
722 // Also add "global" dependencies to the main OutputUnit. These are 828 // Add `main` and their recursive dependencies to the main output unit.
829 // We do this upfront to avoid wasting time visiting these elements when
830 // analyzing deferred imports.
831 queue.addElement(main, importSets.mainSet);
832
833 // Also add "global" dependencies to the main output unit. These are
723 // things that the backend needs but cannot associate with a particular 834 // things that the backend needs but cannot associate with a particular
724 // element, for example, startRootIsolate. This set also contains 835 // element, for example, startRootIsolate. This set also contains
725 // elements for which we lack precise information. 836 // elements for which we lack precise information.
726 for (MethodElement element 837 for (MethodElement element
727 in closedWorld.backendUsage.globalFunctionDependencies) { 838 in closedWorld.backendUsage.globalFunctionDependencies) {
728 _mapDependencies( 839 queue.addElement(element.implementation, importSets.mainSet);
729 element: element.implementation, import: _fakeMainImport);
730 } 840 }
731 for (ClassElement element 841 for (ClassElement element
732 in closedWorld.backendUsage.globalClassDependencies) { 842 in closedWorld.backendUsage.globalClassDependencies) {
733 _mapDependencies( 843 queue.addElement(element.implementation, importSets.mainSet);
734 element: element.implementation, import: _fakeMainImport); 844 }
845 if (closedWorld.backendUsage.isMirrorsUsed) {
846 _addMirrorElementsForLibrary(queue, main.library, importSets.mainSet);
735 } 847 }
736 848
737 // Now check to see if we have to add more elements due to mirrors. 849 void emptyQueue() {
738 if (closedWorld.backendUsage.isMirrorsUsed) { 850 while (queue.isNotEmpty) {
739 _addMirrorElements(); 851 var item = queue.nextItem();
740 } 852 if (item.element != null) {
741 853 var oldSet = _elementToSet[item.element];
742 // Build the OutputUnits using these two maps. 854 var newSet = importSets.union(oldSet, item.newSet);
743 Map<Element, OutputUnit> elementToOutputUnitBuilder = 855 _updateElementRecursive(item.element, oldSet, newSet, queue,
744 new Map<Element, OutputUnit>(); 856 isMirrorUsage: item.isMirrorUsage);
745 Map<ConstantValue, OutputUnit> constantToOutputUnitBuilder = 857 } else if (item.value != null) {
746 new Map<ConstantValue, OutputUnit>(); 858 var oldSet = _constantToSet[item.value];
747 859 var newSet = importSets.union(oldSet, item.newSet);
748 // Add all constants that may have been registered during resolution with 860 _updateConstantRecursive(item.value, oldSet, newSet, queue);
749 // [registerConstantDeferredUse].
750 constantToOutputUnitBuilder.addAll(_constantToOutputUnit);
751 _constantToOutputUnit.clear();
752
753 // Reverse the mappings. For each element record an OutputUnit collecting
754 // all deferred imports mapped to this element. Same for constants.
755 for (_DeferredImport import in _importedDeferredBy.keys) {
756 for (Element element in _importedDeferredBy[import]) {
757 // Only one file should be loaded when the program starts, so make
758 // sure that only one OutputUnit is created for [fakeMainImport].
759 if (import == _fakeMainImport) {
760 elementToOutputUnitBuilder[element] = mainOutputUnit;
761 } else {
762 elementToOutputUnitBuilder
763 .putIfAbsent(element, () => new OutputUnit())
764 .imports
765 .add(import);
766 }
767 }
768 }
769 for (_DeferredImport import in _constantsDeferredBy.keys) {
770 for (ConstantValue constant in _constantsDeferredBy[import]) {
771 // Only one file should be loaded when the program starts, so make
772 // sure that only one OutputUnit is created for [fakeMainImport].
773 if (import == _fakeMainImport) {
774 constantToOutputUnitBuilder[constant] = mainOutputUnit;
775 } else {
776 constantToOutputUnitBuilder
777 .putIfAbsent(constant, () => new OutputUnit())
778 .imports
779 .add(import);
780 } 861 }
781 } 862 }
782 } 863 }
783 864
784 // Release maps; 865 emptyQueue();
785 _importedDeferredBy = null; 866 if (closedWorld.backendUsage.isMirrorsUsed) {
786 _constantsDeferredBy = null; 867 _addDeferredMirrorElements(queue);
868 emptyQueue();
869 }
787 870
788 // Find all the output units elements/constants have been mapped 871 _createOutputUnits();
789 // to, and canonicalize them. 872 _setupHunksToLoad();
790 elementToOutputUnitBuilder
791 .forEach((Element element, OutputUnit outputUnit) {
792 _elementToOutputUnit[element] = _getCanonicalUnit(outputUnit);
793 });
794 constantToOutputUnitBuilder
795 .forEach((ConstantValue constant, OutputUnit outputUnit) {
796 _constantToOutputUnit[constant] = _getCanonicalUnit(outputUnit);
797 });
798
799 // Generate a unique name for each OutputUnit.
800 _assignNamesToOutputUnits(allOutputUnits);
801 } 873 }
802 874
803 reporter.withCurrentElement(mainLibrary, () => measure(work)); 875 reporter.withCurrentElement(main.library, () => measure(work));
804 876
805 // Notify the impact strategy impacts are no longer needed for deferred 877 // Notify that we no longer need impacts for deferred load, so they can be
806 // load. 878 // discarded at this time.
807 compiler.impactStrategy.onImpactUsed(IMPACT_USE); 879 compiler.impactStrategy.onImpactUsed(IMPACT_USE);
808 } 880 }
809 881
810 void beforeResolution(LibraryEntity mainLibrary) { 882 void beforeResolution(LibraryEntity mainLibrary) {
811 if (mainLibrary == null) return; 883 if (mainLibrary == null) return;
812 // TODO(johnniwinther): Support deferred load for kernel based elements. 884 // TODO(johnniwinther): Support deferred load for kernel based elements.
813 if (compiler.options.useKernel) return; 885 if (compiler.options.useKernel) return;
814 _allDeferredImports[_fakeMainImport] = mainLibrary;
815 var lastDeferred; 886 var lastDeferred;
816 // When detecting duplicate prefixes of deferred libraries there are 4 887 // When detecting duplicate prefixes of deferred libraries there are 4
817 // cases of duplicate prefixes: 888 // cases of duplicate prefixes:
818 // 1. 889 // 1.
819 // import "lib.dart" deferred as a; 890 // import "lib.dart" deferred as a;
820 // import "lib2.dart" deferred as a; 891 // import "lib2.dart" deferred as a;
821 // 2. 892 // 2.
822 // import "lib.dart" deferred as a; 893 // import "lib.dart" deferred as a;
823 // import "lib2.dart" as a; 894 // import "lib2.dart" as a;
824 // 3. 895 // 3.
(...skipping 30 matching lines...) Expand all
855 reporter.reportErrorMessage( 926 reporter.reportErrorMessage(
856 import, MessageKind.DEFERRED_OLD_SYNTAX); 927 import, MessageKind.DEFERRED_OLD_SYNTAX);
857 } 928 }
858 } 929 }
859 } 930 }
860 931
861 String prefix = (import.prefix != null) ? import.prefix.name : null; 932 String prefix = (import.prefix != null) ? import.prefix.name : null;
862 // The last import we saw with the same prefix. 933 // The last import we saw with the same prefix.
863 ImportElement previousDeferredImport = prefixDeferredImport[prefix]; 934 ImportElement previousDeferredImport = prefixDeferredImport[prefix];
864 if (import.isDeferred) { 935 if (import.isDeferred) {
865 _DeferredImport key = new _DeclaredDeferredImport(import);
866 LibraryElement importedLibrary = import.importedLibrary;
867 _allDeferredImports[key] = importedLibrary;
868
869 if (prefix == null) { 936 if (prefix == null) {
870 reporter.reportErrorMessage( 937 reporter.reportErrorMessage(
871 import, MessageKind.DEFERRED_LIBRARY_WITHOUT_PREFIX); 938 import, MessageKind.DEFERRED_LIBRARY_WITHOUT_PREFIX);
872 } else { 939 } else {
873 prefixDeferredImport[prefix] = import; 940 prefixDeferredImport[prefix] = import;
874 Uri mainLibraryUri = compiler.mainLibraryUri; 941 Uri mainLibraryUri = compiler.mainLibraryUri;
875 _deferredImportDescriptions[key] = 942 _deferredImportDescriptions[import] =
876 new ImportDescription(import, library, mainLibraryUri); 943 new ImportDescription(import, library, mainLibraryUri);
877 } 944 }
878 isProgramSplit = true; 945 isProgramSplit = true;
879 lastDeferred = import; 946 lastDeferred = import;
880 } 947 }
881 if (prefix != null) { 948 if (prefix != null) {
882 if (previousDeferredImport != null || 949 if (previousDeferredImport != null ||
883 (import.isDeferred && usedPrefixes.contains(prefix))) { 950 (import.isDeferred && usedPrefixes.contains(prefix))) {
884 ImportElement failingImport = (previousDeferredImport != null) 951 ImportElement failingImport = (previousDeferredImport != null)
885 ? previousDeferredImport 952 ? previousDeferredImport
(...skipping 78 matching lines...) Expand 10 before | Expand all | Expand 10 after
964 /// - <library uri> is the relative uri of the library making a deferred 1031 /// - <library uri> is the relative uri of the library making a deferred
965 /// import. 1032 /// import.
966 /// - <library name> is the name of the library, and "<unnamed>" if it is 1033 /// - <library name> is the name of the library, and "<unnamed>" if it is
967 /// unnamed. 1034 /// unnamed.
968 /// - <prefix> is the `as` prefix used for a given deferred import. 1035 /// - <prefix> is the `as` prefix used for a given deferred import.
969 /// - <list of files> is a list of the filenames the must be loaded when that 1036 /// - <list of files> is a list of the filenames the must be loaded when that
970 /// import is loaded. 1037 /// import is loaded.
971 Map<String, Map<String, dynamic>> computeDeferredMap() { 1038 Map<String, Map<String, dynamic>> computeDeferredMap() {
972 Map<String, Map<String, dynamic>> mapping = 1039 Map<String, Map<String, dynamic>> mapping =
973 new Map<String, Map<String, dynamic>>(); 1040 new Map<String, Map<String, dynamic>>();
974 _deferredImportDescriptions.keys.forEach((_DeferredImport import) { 1041 _deferredImportDescriptions.keys.forEach((ImportElement import) {
975 List<OutputUnit> outputUnits = hunksToLoad[importDeferName[import]]; 1042 List<OutputUnit> outputUnits = hunksToLoad[_importDeferName[import]];
976 ImportDescription description = _deferredImportDescriptions[import]; 1043 ImportDescription description = _deferredImportDescriptions[import];
977 Map<String, dynamic> libraryMap = mapping.putIfAbsent( 1044 Map<String, dynamic> libraryMap = mapping.putIfAbsent(
978 description.importingUri, 1045 description.importingUri,
979 () => <String, dynamic>{ 1046 () => <String, dynamic>{
980 "name": description.importingLibraryName, 1047 "name": description.importingLibraryName,
981 "imports": <String, List<String>>{} 1048 "imports": <String, List<String>>{}
982 }); 1049 });
983 1050
984 libraryMap["imports"][importDeferName[import]] = 1051 libraryMap["imports"][_importDeferName[import]] =
985 outputUnits.map((OutputUnit outputUnit) { 1052 outputUnits.map((OutputUnit outputUnit) {
986 return deferredPartFileName(outputUnit.name); 1053 return deferredPartFileName(outputUnit.name);
987 }).toList(); 1054 }).toList();
988 }); 1055 });
989 return mapping; 1056 return mapping;
990 } 1057 }
991 1058
992 /// Returns the filename for the output-unit named [name]. 1059 /// Returns the filename for the output-unit named [name].
993 /// 1060 ///
994 /// The filename is of the form "<main output file>_<name>.part.js". 1061 /// The filename is of the form "<main output file>_<name>.part.js".
995 /// If [addExtension] is false, the ".part.js" suffix is left out. 1062 /// If [addExtension] is false, the ".part.js" suffix is left out.
996 String deferredPartFileName(String name, {bool addExtension: true}) { 1063 String deferredPartFileName(String name, {bool addExtension: true}) {
997 assert(name != ""); 1064 assert(name != "");
998 String outPath = compiler.options.outputUri != null 1065 String outPath = compiler.options.outputUri != null
999 ? compiler.options.outputUri.path 1066 ? compiler.options.outputUri.path
1000 : "out"; 1067 : "out";
1001 String outName = outPath.substring(outPath.lastIndexOf('/') + 1); 1068 String outName = outPath.substring(outPath.lastIndexOf('/') + 1);
1002 String extension = addExtension ? ".part.js" : ""; 1069 String extension = addExtension ? ".part.js" : "";
1003 return "${outName}_$name$extension"; 1070 return "${outName}_$name$extension";
1004 } 1071 }
1005 1072
1006 /// Creates a textual representation of the output unit content. 1073 /// Creates a textual representation of the output unit content.
1007 String dump() { 1074 String dump() {
1008 Map<OutputUnit, List<String>> elementMap = <OutputUnit, List<String>>{}; 1075 Map<OutputUnit, List<String>> elementMap = <OutputUnit, List<String>>{};
1009 Map<OutputUnit, List<String>> constantMap = <OutputUnit, List<String>>{}; 1076 Map<OutputUnit, List<String>> constantMap = <OutputUnit, List<String>>{};
1010 _elementToOutputUnit.forEach((Element element, OutputUnit output) { 1077 _elementToSet.forEach((Entity element, ImportSet importSet) {
1011 elementMap.putIfAbsent(output, () => <String>[]).add('$element'); 1078 elementMap.putIfAbsent(importSet.unit, () => <String>[]).add('$element');
1012 }); 1079 });
1013 _constantToOutputUnit.forEach((ConstantValue value, OutputUnit output) { 1080 _constantToSet.forEach((ConstantValue value, ImportSet importSet) {
1014 constantMap 1081 constantMap
1015 .putIfAbsent(output, () => <String>[]) 1082 .putIfAbsent(importSet.unit, () => <String>[])
1016 .add(value.toStructuredText()); 1083 .add(value.toStructuredText());
1017 }); 1084 });
1018 1085
1019 StringBuffer sb = new StringBuffer(); 1086 Map<OutputUnit, String> text = {};
1020 for (OutputUnit outputUnit in allOutputUnits) { 1087 for (OutputUnit outputUnit in allOutputUnits) {
1021 sb.write('\n-------------------------------\n'); 1088 StringBuffer unitText = new StringBuffer();
1022 sb.write('Output unit: ${outputUnit.name}'); 1089 if (outputUnit.isMainOutput) {
1090 unitText.write(' <MAIN UNIT>');
1091 } else {
1092 unitText.write(' imports:');
1093 var imports = outputUnit._imports.map((i) => '${i.uri}').toList()
1094 ..sort();
1095 for (var i in imports) {
1096 unitText.write('\n $i:');
1097 }
1098 }
1023 List<String> elements = elementMap[outputUnit]; 1099 List<String> elements = elementMap[outputUnit];
1024 if (elements != null) { 1100 if (elements != null) {
1025 sb.write('\n elements:'); 1101 unitText.write('\n elements:');
1026 for (String element in elements..sort()) { 1102 for (String element in elements..sort()) {
1027 sb.write('\n $element'); 1103 unitText.write('\n $element');
1028 } 1104 }
1029 } 1105 }
1030 List<String> constants = constantMap[outputUnit]; 1106 List<String> constants = constantMap[outputUnit];
1031 if (constants != null) { 1107 if (constants != null) {
1032 sb.write('\n constants:'); 1108 unitText.write('\n constants:');
1033 for (String value in constants..sort()) { 1109 for (String value in constants..sort()) {
1034 sb.write('\n $value'); 1110 unitText.write('\n $value');
1035 } 1111 }
1036 } 1112 }
1113 text[outputUnit] = '$unitText';
1114 }
1115
1116 StringBuffer sb = new StringBuffer();
1117 for (OutputUnit outputUnit in allOutputUnits.toList()
1118 ..sort((a, b) => text[a].compareTo(text[b]))) {
1119 sb.write('\n\n-------------------------------\n');
1120 sb.write('Output unit: ${outputUnit.name}');
1121 sb.write('\n ${text[outputUnit]}');
1037 } 1122 }
1038 return sb.toString(); 1123 return sb.toString();
1039 } 1124 }
1040 } 1125 }
1041 1126
1042 class ImportDescription { 1127 class ImportDescription {
1043 /// Relative uri to the importing library. 1128 /// Relative uri to the importing library.
1044 final String importingUri; 1129 final String importingUri;
1045 1130
1046 /// The prefix this import is imported as. 1131 /// The prefix this import is imported as.
1047 final String prefix; 1132 final String prefix;
1048 final LibraryElement _importingLibrary; 1133 final LibraryElement _importingLibrary;
1049 1134
1050 ImportDescription( 1135 ImportDescription(
1051 ImportElement import, LibraryElement importingLibrary, Uri mainLibraryUri) 1136 ImportElement import, LibraryElement importingLibrary, Uri mainLibraryUri)
1052 : importingUri = uri_extras.relativize( 1137 : importingUri = uri_extras.relativize(
1053 mainLibraryUri, importingLibrary.canonicalUri, false), 1138 mainLibraryUri, importingLibrary.canonicalUri, false),
1054 prefix = import.prefix.name, 1139 prefix = import.prefix.name,
1055 _importingLibrary = importingLibrary; 1140 _importingLibrary = importingLibrary;
1056 1141
1057 String get importingLibraryName { 1142 String get importingLibraryName {
1058 return _importingLibrary.hasLibraryName 1143 return _importingLibrary.hasLibraryName
1059 ? _importingLibrary.libraryName 1144 ? _importingLibrary.libraryName
1060 : "<unnamed>"; 1145 : "<unnamed>";
1061 } 1146 }
1062 } 1147 }
1063 1148
1064 /// A node in the deferred import graph. 1149 /// Indirectly represents a deferred import in an [ImportSet].
1065 /// 1150 ///
1066 /// This class serves as the root node; the 'import' of the main library. 1151 /// We could directly store the [declaration] in [ImportSet], but adding this
1152 /// class makes some of the import set operations more efficient.
1067 class _DeferredImport { 1153 class _DeferredImport {
1068 const _DeferredImport();
1069
1070 /// Computes a suggestive name for this import.
1071 String computeImportDeferName(Compiler compiler) => 'main';
1072
1073 ImportElement get declaration => null;
1074
1075 String toString() => 'main';
1076 }
1077
1078 /// A node in the deferred import graph defined by a deferred import directive.
1079 class _DeclaredDeferredImport implements _DeferredImport {
1080 final ImportElement declaration; 1154 final ImportElement declaration;
1081 1155
1082 _DeclaredDeferredImport(this.declaration); 1156 /// Canonical index associated with [declaration]. This is used to efficiently
1083 1157 /// implement [ImportSetLattice.union].
1084 @override 1158 final int index;
1085 String computeImportDeferName(Compiler compiler) { 1159
1086 String result; 1160 _DeferredImport(this.declaration, this.index);
1087 if (declaration.isDeferred) { 1161 }
1088 if (declaration.prefix != null) { 1162
1089 result = declaration.prefix.name; 1163 /// A compact lattice representation of import sets and subsets.
1164 ///
1165 /// We use a graph of nodes to represent elements of the lattice, but only
1166 /// create new nodes on-demand as they are needed by the deferred loading
1167 /// algorithm.
1168 ///
1169 /// The constructions of nodes is carefully done by storing imports in a
1170 /// specific order. This ensures that we have a unique and canonical
1171 /// representation for each subset.
1172 class ImportSetLattice {
1173 /// Index of deferred imports that defines the canonical order used by the
1174 /// operations below.
1175 Map<ImportElement, _DeferredImport> _importIndex = {};
1176
1177 /// The canonical instance representing the empty import set.
1178 ImportSet _emptySet = new ImportSet();
1179
1180 /// The import set representing the main output unit, which happens to be
1181 /// implemented as an empty set in our algorithm.
1182 ImportSet get mainSet => _emptySet;
1183
1184 /// Get the singleton import set that only contains [import].
1185 ImportSet singleton(ImportElement import) {
1186 // Ensure we have import in the index.
1187 return _emptySet._add(_wrap(import));
1188 }
1189
1190 /// Get the import set that includes the union of [a] and [b].
1191 ImportSet union(ImportSet a, ImportSet b) {
1192 if (a == null || a == _emptySet) return b;
1193 if (b == null || b == _emptySet) return a;
1194
1195 // We create the union by merging the imports in canonical order first, and
1196 // then getting (or creating) the canonical sets by adding an import at a
1197 // time.
1198 List<_DeferredImport> aImports = a._imports;
1199 List<_DeferredImport> bImports = b._imports;
1200 int i = 0, j = 0, lastAIndex = 0, lastBIndex = 0;
1201 var result = _emptySet;
1202 while (i < aImports.length && j < bImports.length) {
1203 var importA = aImports[i];
1204 var importB = bImports[j];
1205 assert(lastAIndex <= importA.index);
1206 assert(lastBIndex <= importB.index);
1207 if (importA.index < importB.index) {
1208 result = result._add(importA);
1209 i++;
1090 } else { 1210 } else {
1091 // This happens when the deferred import isn't declared with a prefix. 1211 result = result._add(importB);
1092 assert(compiler.compilationFailed); 1212 j++;
1093 result = '';
1094 } 1213 }
1214 }
1215 for (; i < aImports.length; i++) {
1216 result = result._add(aImports[i]);
1217 }
1218 for (; j < bImports.length; j++) {
1219 result = result._add(bImports[j]);
1220 }
1221
1222 return result;
1223 }
1224
1225 /// Get the index for an [import] according to the canonical order.
1226 _DeferredImport _wrap(ImportElement import) {
1227 return _importIndex.putIfAbsent(
1228 import, () => new _DeferredImport(import, _importIndex.length));
1229 }
1230 }
1231
1232 /// A canonical set of deferred imports.
1233 class ImportSet {
1234 /// Imports that are part of this set.
1235 ///
1236 /// Invariant: the order in which elements are added must respect the
1237 /// canonical order of all imports in [ImportSetLattice].
1238 final List<_DeferredImport> _imports;
1239
1240 /// Links to other import sets in the lattice by adding one import.
1241 final Map<_DeferredImport, ImportSet> _transitions =
1242 <_DeferredImport, ImportSet>{};
1243
1244 ImportSet([this._imports = const <_DeferredImport>[]]);
1245
1246 /// The output unit corresponding to this set of imports, if any.
1247 OutputUnit unit;
1248
1249 int get length => _imports.length;
1250
1251 /// Create an import set that adds [import] to all the imports on this set.
1252 /// This assumes that import's canonical order comes after all imports in
1253 /// this current set. This should only be called from [ImportSetLattice],
1254 /// since it is where we preserve this invariant.
1255 ImportSet _add(_DeferredImport import) {
1256 return _transitions.putIfAbsent(import, () {
1257 var result = new ImportSet(new List.from(_imports)..add(import));
1258 result._transitions[import] = result;
1259 return result;
1260 });
1261 }
1262
1263 String toString() {
1264 StringBuffer sb = new StringBuffer();
1265 sb.write('ImportSet(size: $length, ');
1266 for (var import in _imports) {
1267 sb.write('${import.declaration.prefix} ');
1268 }
1269 sb.write(')');
1270 return '$sb';
1271 }
1272 }
1273
1274 /// The algorithm work queue.
1275 class WorkQueue {
1276 /// The actual queue of work that needs to be done.
1277 final Queue<WorkItem> queue = new Queue<WorkItem>();
1278
1279 /// An index to find work items in the queue corresponding to an entity.
1280 final Map<Entity, WorkItem> pendingElements = <Entity, WorkItem>{};
1281
1282 /// An index to find work items in the queue corresponding to a constant.
1283 final Map<ConstantValue, WorkItem> pendingConstants =
1284 <ConstantValue, WorkItem>{};
1285
1286 /// Lattice used to compute unions of [ImportSet]s.
1287 final ImportSetLattice _importSets;
1288
1289 WorkQueue(this._importSets);
1290
1291 /// Whether there are no more work items in the queue.
1292 bool get isNotEmpty => queue.isNotEmpty;
1293
1294 /// Pop the next element in the queue.
1295 WorkItem nextItem() {
1296 assert(isNotEmpty);
1297 var item = queue.removeFirst();
1298 if (item.element != null) pendingElements.remove(item.element);
1299 if (item.value != null) pendingConstants.remove(item.value);
1300 return item;
1301 }
1302
1303 /// Add to the queue that [element] should be updated to include all imports
1304 /// in [importSet]. If there is already a work item in the queue for
1305 /// [element], this makes sure that the work item now includes the union of
1306 /// [importSet] and the existing work item's import set.
1307 void addElement(Entity element, ImportSet importSet, {isMirrorUsage: false}) {
1308 var item = pendingElements[element];
1309 if (item == null) {
1310 item = new WorkItem(element, importSet);
1311 pendingElements[element] = item;
1312 queue.add(item);
1095 } else { 1313 } else {
1096 // Finds the first argument to the [DeferredLibrary] annotation 1314 item.newSet = _importSets.union(item.newSet, importSet);
1097 List<MetadataAnnotation> metadatas = declaration.metadata; 1315 }
1098 assert(metadatas != null); 1316 if (isMirrorUsage) item.isMirrorUsage = true;
1099 for (MetadataAnnotation metadata in metadatas) { 1317 }
1100 metadata.ensureResolved(compiler.resolution); 1318
1101 ConstantValue value = 1319 /// Add to the queue that [constant] should be updated to include all imports
1102 compiler.constants.getConstantValue(metadata.constant); 1320 /// in [importSet]. If there is already a work item in the queue for
1103 ResolutionDartType type = 1321 /// [constant], this makes sure that the work item now includes the union of
1104 value.getType(compiler.resolution.commonElements); 1322 /// [importSet] and the existing work item's import set.
1105 Element element = type.element; 1323 void addConstant(ConstantValue constant, ImportSet importSet) {
1106 if (element == 1324 var item = pendingConstants[constant];
1107 compiler.resolution.commonElements.deferredLibraryClass) { 1325 if (item == null) {
1108 ConstructedConstantValue constant = value; 1326 item = new WorkItem.constant(constant, importSet);
1109 StringConstantValue s = constant.fields.values.single; 1327 pendingConstants[constant] = item;
1110 result = s.primitiveValue; 1328 queue.add(item);
1111 break; 1329 } else {
1112 } 1330 item.newSet = _importSets.union(item.newSet, importSet);
1331 }
1332 }
1333 }
1334
1335 /// Summary of the work that needs to be done on an entity or constant.
1336 class WorkItem {
1337 /// Entity to be recursively updated.
1338 final Entity element;
1339
1340 /// Constant to be recursively updated.
1341 final ConstantValue value;
1342
1343 /// Additional imports that use [element] or [value] and need to be added by
1344 /// the algorithm.
1345 ///
1346 /// This is non-final in case we add more deferred imports to the set before
1347 /// the work item is applied (see [WorkQueue.addElement] and
1348 /// [WorkQueue.addConstant]).
1349 ImportSet newSet;
1350
1351 /// Whether [element] is used via mirrors.
1352 ///
1353 /// This is non-final in case we later discover that the same [element] is
1354 /// used via mirrors (but before the work item is applied).
1355 bool isMirrorUsage = false;
1356
1357 WorkItem(this.element, this.newSet) : value = null;
1358 WorkItem.constant(this.value, this.newSet) : element = null;
1359 }
1360
1361 /// Returns a name for a deferred import.
1362 // TODO(sigmund): delete support for the old annotation-style syntax.
1363 String _computeImportDeferName(ImportElement declaration, Compiler compiler) {
1364 String result;
1365 if (declaration.isDeferred) {
1366 if (declaration.prefix != null) {
1367 result = declaration.prefix.name;
1368 } else {
1369 // This happens when the deferred import isn't declared with a prefix.
1370 assert(compiler.compilationFailed);
1371 result = '';
1372 }
1373 } else {
1374 // Finds the first argument to the [DeferredLibrary] annotation
1375 List<MetadataAnnotation> metadatas = declaration.metadata;
1376 assert(metadatas != null);
1377 for (MetadataAnnotation metadata in metadatas) {
1378 metadata.ensureResolved(compiler.resolution);
1379 ConstantValue value =
1380 compiler.constants.getConstantValue(metadata.constant);
1381 ResolutionDartType type =
1382 value.getType(compiler.resolution.commonElements);
1383 Element element = type.element;
1384 if (element == compiler.resolution.commonElements.deferredLibraryClass) {
1385 ConstructedConstantValue constant = value;
1386 StringConstantValue s = constant.fields.values.single;
1387 result = s.primitiveValue;
1388 break;
1113 } 1389 }
1114 } 1390 }
1115 assert(result != null); 1391 }
1116 return result; 1392 assert(result != null);
1117 } 1393 return result;
1118 1394 }
1119 bool operator ==(other) {
1120 if (other is! _DeclaredDeferredImport) return false;
1121 return declaration == other.declaration;
1122 }
1123
1124 int get hashCode => declaration.hashCode * 17;
1125
1126 String toString() => '$declaration';
1127 }
OLDNEW
« no previous file with comments | « no previous file | pkg/compiler/lib/src/dump_info.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698