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