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

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

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