Chromium Code Reviews| Index: lib/src/codegen/js_module_item_order.dart |
| diff --git a/lib/src/codegen/js_module_item_order.dart b/lib/src/codegen/js_module_item_order.dart |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..182038af4588ffbef63b4dce19485837f66d0247 |
| --- /dev/null |
| +++ b/lib/src/codegen/js_module_item_order.dart |
| @@ -0,0 +1,229 @@ |
| +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
| +// for details. All rights reserved. Use of this source code is governed by a |
| +// BSD-style license that can be found in the LICENSE file. |
| + |
| +import 'dart:collection' show HashMap; |
| +import 'package:analyzer/src/generated/ast.dart'; |
| +import 'package:analyzer/src/generated/element.dart'; |
| +import 'package:dev_compiler/src/dependency_graph.dart' show corelibOrder; |
| + |
| +typedef void ModuleItemEmitter(AstNode item); |
| + |
| +/// Helper that tracks order of elements visited by the compiler, detecting |
| +/// if the top level item can be loaded eagerly or not. |
| +class ModuleItemLoadOrder { |
| + |
| + /// The order that elements should be emitted in, with a bit indicating if |
| + /// the element should be generated lazily. The value will be `false` if |
| + /// the item could not be loaded, and needs to be made lazy. |
| + /// |
| + /// The order will match the original source order, unless something needed to |
| + /// be moved sooner to satisfy dependencies. |
| + /// |
| + /// Sometimes it's impossible to ensure an ordering when confronting library |
| + /// cycles. In that case, we mark the definition as lazy. |
| + final _loaded = new Map<Element, bool>.identity(); |
|
vsm
2015/05/12 18:00:11
Can you add a comment on why identity? Should all
Jennifer Messerly
2015/05/12 18:47:04
ah, I can remove. This was to workaround the temp-
|
| + |
| + final _declarationNodes = new HashMap<Element, AstNode>.identity(); |
| + |
| + /// The stack of currently emitting elements, if generating top-level code |
| + /// for them. This is not used when inside method bodies, because order does |
| + /// not matter for those. |
| + final _topLevelElements = new List<Element>(); |
| + |
| + /// The current element being loaded. |
| + /// We can use this to determine if we're loading top-level code or not: |
| + /// |
| + /// _currentElements.last == _topLevelElements.last |
| + final _currentElements = new List<Element>(); |
| + |
| + /// Memoized results of [_inLibraryCycle]. |
| + final _libraryCycleMemo = new HashMap<LibraryElement, bool>(); |
| + |
| + final ModuleItemEmitter _emitModuleItem; |
| + |
| + LibraryElement _currentLibrary; |
| + |
| + ModuleItemLoadOrder(this._emitModuleItem); |
| + |
| + bool isLoaded(Element e) => _loaded[e] == true; |
| + |
| + /// Collect top-level elements and nodes we need to emit. |
| + void collectElements( |
| + LibraryElement library, Iterable<CompilationUnit> partsThenLibrary) { |
| + assert(_currentLibrary == null); |
| + _currentLibrary = library; |
| + |
| + for (var unit in partsThenLibrary) { |
| + for (var decl in unit.declarations) { |
| + _declarationNodes[decl.element] = decl; |
| + |
| + if (decl is ClassDeclaration) { |
| + for (var member in decl.members) { |
| + if (member is FieldDeclaration && member.isStatic) { |
| + _collectElementsForVariable(member.fields); |
| + } |
| + } |
| + } else if (decl is TopLevelVariableDeclaration) { |
| + _collectElementsForVariable(decl.variables); |
| + } |
| + } |
| + } |
| + } |
| + |
| + void _collectElementsForVariable(VariableDeclarationList fields) { |
| + for (var field in fields.variables) { |
| + _declarationNodes[field.element] = field; |
| + } |
| + } |
| + |
| + /// Start generating top-level code for the element [e]. |
| + /// Subsequent [loadElement] calls will cause those elements to be generated |
| + /// before this one, until [finishElement] is called. |
| + void startTopLevel(Element e) { |
| + assert(isCurrentElement(e)); |
| + // Assume loading will succeed until proven otherwise. |
| + _loaded[e] = true; |
| + _topLevelElements.add(e); |
| + } |
| + |
| + /// Finishes the top-level code for the element [e]. |
| + void finishTopLevel(Element e) { |
| + var last = _topLevelElements.removeLast(); |
| + assert(identical(e, last)); |
| + } |
| + |
| + // Starts generating code for the element [e]. |
| + // |
| + // Normally this is called implicitly by [loadDeclaration] and/or [loadElement]. |
|
vsm
2015/05/12 18:00:11
line len?
Jennifer Messerly
2015/05/12 18:47:04
Done.
|
| + // However, for synthetic elements (like temporary variables) it must be |
| + // called explicitly, and paired with a call to [finishElement]. |
| + void startDeclaration(Element e) { |
| + // Assume load will succeed until proven otherwise. |
| + _loaded[e] = true; |
| + _currentElements.add(e); |
| + } |
| + |
| + /// Returns true if all dependencies were loaded successfully. |
| + bool finishDeclaration(Element e) { |
| + var last = _currentElements.removeLast(); |
| + assert(identical(e, last)); |
| + return _loaded[e]; |
| + } |
| + |
| + /// Loads a top-level declaration. This is similar to [loadElement], but it |
| + /// ensures we always visit the node if it has not been emitted yet. In other |
| + /// words, it handles nodes that do not need dependency resolution like |
| + /// top-level functions. |
| + bool loadDeclaration(AstNode node, Element e) { |
| + assert(e.library == _currentLibrary); |
| + |
| + // If we already tried to load it, see if we succeeded or not. |
| + // Otherwise try and load now. |
| + var loaded = _loaded[e]; |
| + if (loaded != null) return loaded; |
| + |
| + // Otherwise, try to load it. |
| + startDeclaration(e); |
| + _emitModuleItem(node); |
| + return finishDeclaration(e); |
| + } |
| + |
| + bool isCurrentElement(Element e) => |
| + _currentElements.isNotEmpty && identical(e, _currentElements.last); |
| + |
| + /// To emit top-level module items, we sometimes need to reorder them. |
| + /// |
| + /// This function takes care of that, and also detects cases where reordering |
| + /// failed, and we need to resort to lazy loading, by marking the element as |
| + /// lazy. All elements need to be aware of this possibility and generate code |
| + /// accordingly. |
| + /// |
| + /// If we are not emitting top-level code, this does nothing, because all |
| + /// declarations are assumed to be available before we start execution. |
| + /// See [startTopLevel]. |
| + void declareBeforeUse(Element e) { |
| + if (e == null || _topLevelElements.isEmpty) return; |
| + if (!isCurrentElement(_topLevelElements.last)) return; |
| + |
| + // If the item is from our library, try to emit it now. |
| + bool loaded; |
| + if (e.library == _currentLibrary) { |
| + // Type parameters are not in scope when generating hoisted fields. |
| + if (e is TypeParameterElement && |
| + _currentElements.last is VariableElement) { |
| + loaded = false; |
| + } else { |
| + var node = _declarationNodes[e]; |
| + loaded = node == null || loadDeclaration(node, e); |
| + } |
| + } else { |
| + // We can't force things from other libraries to be emitted in a different |
| + // order. Instead, we see if the library itself can be loaded before the |
| + // current library. Generally that is possible, unless we have cyclic |
| + // imports. |
| + loaded = libraryIsLoaded(e.library); |
| + } |
| + |
| + if (loaded) return; |
| + |
| + // If we failed to emit it, then we need to make sure all currently emitting |
| + // elements are generated in a lazy way. |
| + for (var current in _topLevelElements) { |
| + // TODO(jmesserly): if we want to log what triggered laziness, this is |
| + // the place to do so. |
| + _loaded[current] = false; |
| + } |
| + } |
| + |
| + bool libraryIsLoaded(LibraryElement library) { |
| + assert(library != _currentLibrary); |
| + |
| + if (library == null) return true; |
|
vsm
2015/05/12 18:00:11
When would this be null?
Jennifer Messerly
2015/05/12 18:47:04
good catch. This showed up in the code at one poin
|
| + |
| + // The SDK is a special case: we optimize the order to prevent laziness. |
| + if (library.isInSdk) { |
| + // SDK is loaded before non-SDK libraries |
| + if (!_currentLibrary.isInSdk) return true; |
| + |
| + // Compute the order of both SDK libraries. If unknown, assume it's after. |
| + var order = corelibOrder.indexOf(library.name); |
| + if (order == -1) order = corelibOrder.length; |
| + |
| + var currentOrder = corelibOrder.indexOf(_currentLibrary.name); |
| + if (currentOrder == -1) currentOrder = corelibOrder.length; |
| + |
| + // If the dart:* library we are currently compiling is loaded after the |
| + // class's library, then we know the class is available. |
| + if (order != currentOrder) return currentOrder > order; |
| + |
| + // If we don't know the order of the class's library or the current |
| + // library, do the normal cycle check. (Not all SDK libs are cycles.) |
| + } |
| + |
| + return !_inLibraryCycle(library); |
| + } |
| + |
| + /// Returns true if [library] depends on the [currentLibrary] via some |
| + /// transitive import. |
| + bool _inLibraryCycle(LibraryElement library) { |
| + // SDK libs don't depend on things outside the SDK. |
| + // (We can reach this via the recursive call below.) |
| + if (library.isInSdk && !_currentLibrary.isInSdk) return false; |
| + |
| + var result = _libraryCycleMemo[library]; |
| + if (result != null) return result; |
| + |
| + result = library == _currentLibrary; |
| + _libraryCycleMemo[library] = result; |
| + for (var e in library.imports) { |
| + if (result) break; |
| + result = _inLibraryCycle(e.importedLibrary); |
| + } |
| + for (var e in library.exports) { |
| + if (result) break; |
| + result = _inLibraryCycle(e.exportedLibrary); |
| + } |
| + return _libraryCycleMemo[library] = result; |
| + } |
| +} |