| 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..eadf6716f5578d1803c6d9a92bf87b085bf49435
|
| --- /dev/null
|
| +++ b/lib/src/codegen/js_module_item_order.dart
|
| @@ -0,0 +1,227 @@
|
| +// 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>();
|
| +
|
| + final _declarationNodes = new HashMap<Element, AstNode>();
|
| +
|
| + /// 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 declaration element [e].
|
| + //
|
| + // Normally this is called implicitly by [loadDeclaration] and/or
|
| + // [loadElement]. 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);
|
| +
|
| + // 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;
|
| + }
|
| +}
|
|
|