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