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

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

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