| Index: pkg/compiler/lib/src/cps_ir/use_field_initializers.dart
|
| diff --git a/pkg/compiler/lib/src/cps_ir/use_field_initializers.dart b/pkg/compiler/lib/src/cps_ir/use_field_initializers.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..a8944c627372264fa38aa1f5da118445ad169a18
|
| --- /dev/null
|
| +++ b/pkg/compiler/lib/src/cps_ir/use_field_initializers.dart
|
| @@ -0,0 +1,212 @@
|
| +// Copyright (c) 2016, 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.
|
| +library dart2js.cps_ir.use_field_initializers;
|
| +
|
| +import 'cps_ir_nodes.dart';
|
| +import 'optimizers.dart';
|
| +import '../elements/elements.dart';
|
| +import '../js_backend/js_backend.dart';
|
| +
|
| +/// Eliminates [SetField] instructions when the value can instead be passed into
|
| +/// the field initializer of a [CreateInstance] instruction.
|
| +///
|
| +/// This compensates for a somewhat common pattern where fields are initialized
|
| +/// in the constructor body instead of using intializers. For example:
|
| +///
|
| +/// class Foo {
|
| +/// var x, y;
|
| +/// Foo(x, y) {
|
| +/// this.x = x;
|
| +/// this.y = y;
|
| +/// }
|
| +/// }
|
| +///
|
| +/// ==> (IR for Foo constructor)
|
| +///
|
| +/// foo = new D.Foo(null, null);
|
| +/// foo.x = 'a';
|
| +/// foo.y = 'b';
|
| +///
|
| +/// ==> (after this pass)
|
| +///
|
| +/// foo = new D.Foo('a', 'b');
|
| +//
|
| +// TODO(asgerf): Store forwarding and load elimination could most likely
|
| +// handle this more generally.
|
| +//
|
| +class UseFieldInitializers extends BlockVisitor implements Pass {
|
| + String get passName => 'Use field initializers';
|
| +
|
| + final JavaScriptBackend backend;
|
| +
|
| + final Set<CreateInstance> unescaped = new Set<CreateInstance>();
|
| +
|
| + /// Continuation bindings separating the current traversal position from an
|
| + /// unescaped [CreateInstance]. When [CreateInstance] is sunk, these
|
| + /// continuations must sink as well to ensure the object remains in scope
|
| + /// inside the bound continuations.
|
| + final List<LetCont> letConts = <LetCont>[];
|
| +
|
| + /// If non-null, the bindings in [letConts] should sink to immediately below
|
| + /// this node.
|
| + InteriorNode letContSinkTarget = null;
|
| + EscapeVisitor escapeVisitor;
|
| +
|
| + UseFieldInitializers(this.backend);
|
| +
|
| + void rewrite(FunctionDefinition node) {
|
| + escapeVisitor = new EscapeVisitor(this);
|
| + BlockVisitor.traverseInPreOrder(node, this);
|
| + }
|
| +
|
| + void escape(Reference ref) {
|
| + Definition def = ref.definition;
|
| + if (def is CreateInstance) {
|
| + unescaped.remove(def);
|
| + if (unescaped.isEmpty) {
|
| + sinkLetConts();
|
| + letConts.clear();
|
| + }
|
| + }
|
| + }
|
| +
|
| + void visitContinuation(Continuation node) {
|
| + endBasicBlock();
|
| + }
|
| + void visitLetHandler(LetHandler node) {
|
| + endBasicBlock();
|
| + }
|
| + void visitInvokeContinuation(InvokeContinuation node) {
|
| + endBasicBlock();
|
| + }
|
| + void visitBranch(Branch node) {
|
| + endBasicBlock();
|
| + }
|
| + void visitRethrow(Rethrow node) {
|
| + endBasicBlock();
|
| + }
|
| + void visitThrow(Throw node) {
|
| + endBasicBlock();
|
| + }
|
| + void visitUnreachable(Unreachable node) {
|
| + endBasicBlock();
|
| + }
|
| +
|
| + void visitLetMutable(LetMutable node) {
|
| + escape(node.value);
|
| + }
|
| +
|
| + void visitLetCont(LetCont node) {
|
| + if (unescaped.isNotEmpty) {
|
| + // Ensure we do not lift a LetCont if there is a sink target set above
|
| + // the current node.
|
| + sinkLetConts();
|
| + letConts.add(node);
|
| + }
|
| + }
|
| +
|
| + void sinkLetConts() {
|
| + if (letContSinkTarget != null) {
|
| + for (LetCont letCont in letConts.reversed) {
|
| + letCont..remove()..insertBelow(letContSinkTarget);
|
| + }
|
| + letContSinkTarget = null;
|
| + }
|
| + }
|
| +
|
| + void endBasicBlock() {
|
| + sinkLetConts();
|
| + letConts.clear();
|
| + unescaped.clear();
|
| + }
|
| +
|
| + void visitLetPrim(LetPrim node) {
|
| + Primitive prim = node.primitive;
|
| + if (prim is CreateInstance) {
|
| + unescaped.add(prim);
|
| + prim.arguments.forEach(escape);
|
| + return;
|
| + }
|
| + if (unescaped.isEmpty) return;
|
| + if (prim is SetField) {
|
| + escape(prim.value);
|
| + Primitive object = prim.object.definition;
|
| + if (object is CreateInstance && unescaped.contains(object)) {
|
| + int index = getFieldIndex(object.classElement, prim.field);
|
| + if (index == -1) {
|
| + // This field is not initialized at creation time, so we cannot pull
|
| + // set SetField into the CreateInstance instruction. We have to
|
| + // leave the instruction here, and this counts as a use of the object.
|
| + escape(prim.object);
|
| + } else {
|
| + // Replace the field initializer with the new value. There are no uses
|
| + // of the object before this, so the old value cannot have been seen.
|
| + object.arguments[index].changeTo(prim.value.definition);
|
| + prim.destroy();
|
| + // The right-hand side might not be in scope at the CreateInstance.
|
| + // Sink the creation down to this point.
|
| + rebindCreateInstanceAt(object, node);
|
| + letContSinkTarget = node;
|
| + }
|
| + }
|
| + return;
|
| + }
|
| + if (prim is GetField) {
|
| + // When reading the field of a newly created object, just use the initial
|
| + // value and destroy the GetField. This can unblock the other optimization
|
| + // since we remove a use of the object.
|
| + Primitive object = prim.object.definition;
|
| + if (object is CreateInstance && unescaped.contains(object)) {
|
| + int index = getFieldIndex(object.classElement, prim.field);
|
| + if (index == -1) {
|
| + escape(prim.object);
|
| + } else {
|
| + prim.replaceUsesWith(object.arguments[index].definition);
|
| + prim.destroy();
|
| + node.remove();
|
| + }
|
| + }
|
| + return;
|
| + }
|
| + escapeVisitor.visit(node.primitive);
|
| + }
|
| +
|
| + void rebindCreateInstanceAt(CreateInstance prim, LetPrim newBinding) {
|
| + removeBinding(prim);
|
| + newBinding.primitive = prim;
|
| + prim.parent = newBinding;
|
| + }
|
| +
|
| + /// Returns the index of [field] in the canonical initialization order in
|
| + /// [classElement], or -1 if the field is not initialized at creation time
|
| + /// for that class.
|
| + int getFieldIndex(ClassElement classElement, FieldElement field) {
|
| + // There is no stored map from a field to its index in a given class, so we
|
| + // have to iterate over all instance fields until we find it.
|
| + int current = -1, index = -1;
|
| + classElement.forEachInstanceField((host, currentField) {
|
| + if (!backend.isNativeOrExtendsNative(host)) {
|
| + ++current;
|
| + if (currentField == field) {
|
| + index = current;
|
| + }
|
| + }
|
| + }, includeSuperAndInjectedMembers: true);
|
| + return index;
|
| + }
|
| +
|
| + void removeBinding(Primitive prim) {
|
| + LetPrim node = prim.parent;
|
| + node.remove();
|
| + }
|
| +}
|
| +
|
| +class EscapeVisitor extends DeepRecursiveVisitor {
|
| + final UseFieldInitializers main;
|
| + EscapeVisitor(this.main);
|
| +
|
| + processReference(Reference ref) {
|
| + main.escape(ref);
|
| + }
|
| +}
|
|
|