OLD | NEW |
| (Empty) |
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | |
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. | |
4 | |
5 import 'dart:collection' show HashMap; | |
6 | |
7 import 'package:analyzer/dart/ast/ast.dart'; | |
8 import 'package:analyzer/dart/element/element.dart'; | |
9 | |
10 import '../compiler.dart' show corelibOrder; | |
11 | |
12 typedef void ModuleItemEmitter(AstNode item); | |
13 | |
14 /// Helper that tracks order of elements visited by the compiler, detecting | |
15 /// if the top level item can be loaded eagerly or not. | |
16 class ModuleItemLoadOrder { | |
17 /// The order that elements should be emitted in, with a bit indicating if | |
18 /// the element should be generated lazily. The value will be `false` if | |
19 /// the item could not be loaded, and needs to be made lazy. | |
20 /// | |
21 /// The order will match the original source order, unless something needed to | |
22 /// be moved sooner to satisfy dependencies. | |
23 /// | |
24 /// Sometimes it's impossible to ensure an ordering when confronting library | |
25 /// cycles. In that case, we mark the definition as lazy. | |
26 final _loaded = new Map<Element, bool>(); | |
27 | |
28 final _declarationNodes = new HashMap<Element, AstNode>(); | |
29 | |
30 /// The stack of currently emitting elements, if generating top-level code | |
31 /// for them. This is not used when inside method bodies, because order does | |
32 /// not matter for those. | |
33 final _topLevelElements = new List<Element>(); | |
34 | |
35 /// The current element being loaded. | |
36 /// We can use this to determine if we're loading top-level code or not: | |
37 /// | |
38 /// _currentElements.last == _topLevelElements.last | |
39 final _currentElements = new List<Element>(); | |
40 | |
41 /// Memoized results of [_inLibraryCycle]. | |
42 final _libraryCycleMemo = new HashMap<LibraryElement, bool>(); | |
43 | |
44 bool _checkReferences; | |
45 | |
46 final ModuleItemEmitter _emitModuleItem; | |
47 | |
48 LibraryElement _currentLibrary; | |
49 | |
50 ModuleItemLoadOrder(this._emitModuleItem); | |
51 | |
52 bool isLoaded(Element e) => (e.library == _currentLibrary) | |
53 ? _loaded[e] == true | |
54 : libraryIsLoaded(e.library); | |
55 | |
56 /// True if the element is currently being loaded. | |
57 bool _isLoading(Element e) => _currentElements.contains(e); | |
58 | |
59 /// Collect top-level elements and nodes we need to emit. | |
60 void collectElements( | |
61 LibraryElement library, Iterable<CompilationUnit> partsThenLibrary) { | |
62 assert(_currentLibrary == null); | |
63 _currentLibrary = library; | |
64 | |
65 for (var unit in partsThenLibrary) { | |
66 for (var decl in unit.declarations) { | |
67 _declarationNodes[decl.element] = decl; | |
68 | |
69 if (decl is TopLevelVariableDeclaration) { | |
70 _collectElementsForVariable(decl.variables); | |
71 } | |
72 } | |
73 } | |
74 } | |
75 | |
76 void _collectElementsForVariable(VariableDeclarationList fields) { | |
77 for (var field in fields.variables) { | |
78 _declarationNodes[field.element] = field; | |
79 } | |
80 } | |
81 | |
82 /// Start generating top-level code for the element [e]. | |
83 /// Subsequent [loadElement] calls will cause those elements to be generated | |
84 /// before this one, until [finishElement] is called. | |
85 void startTopLevel(Element e) { | |
86 assert(isCurrentElement(e)); | |
87 _topLevelElements.add(e); | |
88 } | |
89 | |
90 /// Finishes the top-level code for the element [e]. | |
91 void finishTopLevel(Element e) { | |
92 var last = _topLevelElements.removeLast(); | |
93 assert(identical(e, last)); | |
94 } | |
95 | |
96 /// Starts recording calls to [declareBeforeUse], until | |
97 /// [finishCheckingReferences] is called. | |
98 void startCheckingReferences() { | |
99 // This function should not be reentrant, and we should not current be | |
100 // emitting top-level code. | |
101 assert(_checkReferences == null); | |
102 assert( | |
103 _topLevelElements.isEmpty || !isCurrentElement(_topLevelElements.last)); | |
104 // Assume true until proven otherwise | |
105 _checkReferences = true; | |
106 } | |
107 | |
108 /// Finishes recording references, and returns `true` if all referenced | |
109 /// items were loaded (or if no items were referenced). | |
110 bool finishCheckingReferences() { | |
111 var result = _checkReferences; | |
112 _checkReferences = null; | |
113 return result; | |
114 } | |
115 | |
116 // Starts generating code for the declaration element [e]. | |
117 // | |
118 // Normally this is called implicitly by [loadDeclaration] and/or | |
119 // [loadElement]. However, for synthetic elements (like temporary variables) | |
120 // it must be called explicitly, and paired with a call to [finishElement]. | |
121 void startDeclaration(Element e) { | |
122 // Assume load will succeed until proven otherwise. | |
123 _loaded[e] = true; | |
124 _currentElements.add(e); | |
125 } | |
126 | |
127 /// Returns true if all dependencies were loaded successfully. | |
128 bool finishDeclaration(Element e) { | |
129 var last = _currentElements.removeLast(); | |
130 assert(identical(e, last)); | |
131 return _loaded[e]; | |
132 } | |
133 | |
134 /// Loads a top-level declaration. This is similar to [loadElement], but it | |
135 /// ensures we always visit the node if it has not been emitted yet. In other | |
136 /// words, it handles nodes that do not need dependency resolution like | |
137 /// top-level functions. | |
138 bool loadDeclaration(AstNode node, Element e) { | |
139 assert(e.library == _currentLibrary); | |
140 | |
141 // If we already tried to load it, see if we succeeded or not. | |
142 // Otherwise try and load now. | |
143 var loaded = _loaded[e]; | |
144 if (loaded != null) return loaded; | |
145 | |
146 // Otherwise, try to load it. | |
147 startDeclaration(e); | |
148 _emitModuleItem(node); | |
149 return finishDeclaration(e); | |
150 } | |
151 | |
152 bool isCurrentElement(Element e) => | |
153 _currentElements.isNotEmpty && identical(e, _currentElements.last); | |
154 | |
155 /// To emit top-level module items, we sometimes need to reorder them. | |
156 /// | |
157 /// This function takes care of that, and also detects cases where reordering | |
158 /// failed, and we need to resort to lazy loading, by marking the element as | |
159 /// lazy. All elements need to be aware of this possibility and generate code | |
160 /// accordingly. | |
161 /// | |
162 /// If we are not emitting top-level code, this does nothing, because all | |
163 /// declarations are assumed to be available before we start execution. | |
164 /// See [startTopLevel]. | |
165 void declareBeforeUse(Element e) { | |
166 if (e == null) return; | |
167 | |
168 if (_checkReferences != null) { | |
169 _checkReferences = _checkReferences && isLoaded(e) && !_isLoading(e); | |
170 return; | |
171 } | |
172 | |
173 if (_topLevelElements.isEmpty || | |
174 !isCurrentElement(_topLevelElements.last)) { | |
175 return; | |
176 } | |
177 | |
178 // If the item is from our library, try to emit it now. | |
179 bool loaded; | |
180 if (e.library == _currentLibrary) { | |
181 // Type parameters are not in scope when generating hoisted fields. | |
182 if (e is TypeParameterElement && | |
183 _currentElements.last is VariableElement) { | |
184 loaded = false; | |
185 } else { | |
186 var node = _declarationNodes[e]; | |
187 loaded = node == null || loadDeclaration(node, e); | |
188 } | |
189 } else { | |
190 // We can't force things from other libraries to be emitted in a different | |
191 // order. Instead, we see if the library itself can be loaded before the | |
192 // current library. Generally that is possible, unless we have cyclic | |
193 // imports. | |
194 loaded = libraryIsLoaded(e.library); | |
195 } | |
196 | |
197 if (loaded) return; | |
198 | |
199 // If we failed to emit it, then we need to make sure all currently emitting | |
200 // elements are generated in a lazy way. | |
201 for (var current in _topLevelElements) { | |
202 // TODO(jmesserly): if we want to log what triggered laziness, this is | |
203 // the place to do so. | |
204 _loaded[current] = false; | |
205 } | |
206 } | |
207 | |
208 bool libraryIsLoaded(LibraryElement library) { | |
209 assert(library != _currentLibrary); | |
210 | |
211 // DynamicElementImpl has no library. | |
212 if (library == null) return true; | |
213 | |
214 // The SDK is a special case: we optimize the order to prevent laziness. | |
215 if (library.source.isInSystemLibrary) { | |
216 // SDK is loaded before non-SDK libraries | |
217 if (!_currentLibrary.source.isInSystemLibrary) return true; | |
218 | |
219 // Compute the order of both SDK libraries. If unknown, assume it's after. | |
220 var order = corelibOrder.indexOf(library.source.uri); | |
221 if (order == -1) order = corelibOrder.length; | |
222 | |
223 var currentOrder = corelibOrder.indexOf(_currentLibrary.source.uri); | |
224 if (currentOrder == -1) currentOrder = corelibOrder.length; | |
225 | |
226 // If the dart:* library we are currently compiling is loaded after the | |
227 // class's library, then we know the class is available. | |
228 if (order != currentOrder) return currentOrder > order; | |
229 | |
230 // If we don't know the order of the class's library or the current | |
231 // library, do the normal cycle check. (Not all SDK libs are cycles.) | |
232 } | |
233 | |
234 return !_inLibraryCycle(library); | |
235 } | |
236 | |
237 /// Returns true if [library] depends on the [currentLibrary] via some | |
238 /// transitive import. | |
239 bool _inLibraryCycle(LibraryElement library) { | |
240 // SDK libs don't depend on things outside the SDK. | |
241 // (We can reach this via the recursive call below.) | |
242 if (library.source.isInSystemLibrary && | |
243 !_currentLibrary.source.isInSystemLibrary) return false; | |
244 | |
245 var result = _libraryCycleMemo[library]; | |
246 if (result != null) return result; | |
247 | |
248 result = library == _currentLibrary; | |
249 _libraryCycleMemo[library] = result; | |
250 for (var e in library.imports) { | |
251 if (result) break; | |
252 result = _inLibraryCycle(e.importedLibrary); | |
253 } | |
254 for (var e in library.exports) { | |
255 if (result) break; | |
256 result = _inLibraryCycle(e.exportedLibrary); | |
257 } | |
258 return _libraryCycleMemo[library] = result; | |
259 } | |
260 } | |
OLD | NEW |