dart2js cps: Pull SetFields into field initializer arguments.
For example:
foo = new D.Foo(null, null);
foo.x = 'a';
foo.y = 'b';
becomes:
foo = new D.Foo('a', 'b');
BUG=
R=sigmund@google.com
Review URL: https://codereview.chromium.org/1671073002 .
This commit is contained in:
@@ -431,7 +431,7 @@ class IrBuilderVisitor extends ast.Visitor<ir.Primitive>
|
||||
|
||||
/// Maps each field from this class or a superclass to its initial value.
|
||||
Map<FieldElement, ir.Primitive> fieldValues =
|
||||
<FieldElement, ir.Primitive>{};
|
||||
<FieldElement, ir.Primitive>{};
|
||||
|
||||
// -- Evaluate field initializers ---
|
||||
// Evaluate field initializers in constructor and super constructors.
|
||||
|
||||
@@ -2011,23 +2011,25 @@ abstract class BlockVisitor<T> {
|
||||
|
||||
/// Visits block-level nodes in lexical pre-order.
|
||||
///
|
||||
/// The IR may be transformed during the traversal, but the currently
|
||||
/// visited node should not be removed, as its 'body' pointer is needed
|
||||
/// for the traversal.
|
||||
/// Traversal continues at the original success for the current node, so:
|
||||
/// - The current node can safely be removed.
|
||||
/// - Nodes inserted immediately below the current node will not be seen.
|
||||
/// - The body of the current node should not be moved/removed, as traversal
|
||||
/// would otherwise continue into an orphaned or relocated node.
|
||||
static void traverseInPreOrder(FunctionDefinition root, BlockVisitor v) {
|
||||
List<Continuation> stack = <Continuation>[];
|
||||
void walkBlock(InteriorNode block) {
|
||||
v.visit(block);
|
||||
Expression node = block.body;
|
||||
v.visit(node);
|
||||
while (node.next != null) {
|
||||
while (node != null) {
|
||||
if (node is LetCont) {
|
||||
stack.addAll(node.continuations);
|
||||
} else if (node is LetHandler) {
|
||||
stack.add(node.handler);
|
||||
}
|
||||
node = node.next;
|
||||
Expression next = node.next;
|
||||
v.visit(node);
|
||||
node = next;
|
||||
}
|
||||
}
|
||||
walkBlock(root);
|
||||
|
||||
@@ -26,6 +26,7 @@ export 'inline.dart' show Inliner;
|
||||
export 'eagerly_load_statics.dart' show EagerlyLoadStatics;
|
||||
export 'loop_invariant_branch.dart' show LoopInvariantBranchMotion;
|
||||
export 'duplicate_branch.dart' show DuplicateBranchEliminator;
|
||||
export 'use_field_initializers.dart' show UseFieldInitializers;
|
||||
export 'parent_visitor.dart' show ParentVisitor;
|
||||
|
||||
/// An optimization pass over the CPS IR.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -281,6 +281,7 @@ class CpsFunctionCompiler implements FunctionCompiler {
|
||||
applyCpsPass(new LoopInvariantBranchMotion(), cpsFunction);
|
||||
applyCpsPass(new ShrinkingReducer(), cpsFunction);
|
||||
applyCpsPass(new ScalarReplacer(compiler), cpsFunction);
|
||||
applyCpsPass(new UseFieldInitializers(backend), cpsFunction);
|
||||
applyCpsPass(new MutableVariableEliminator(), cpsFunction);
|
||||
applyCpsPass(new RedundantJoinEliminator(), cpsFunction);
|
||||
applyCpsPass(new RedundantPhiEliminator(), cpsFunction);
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
// ---- AUTO-GENERATED -------------------
|
||||
// This file was autogenerated by running:
|
||||
//
|
||||
// dart path/to/up_to_date_test.dart update
|
||||
//
|
||||
// Do not edit this file by hand.
|
||||
// ---------------------------------------
|
||||
|
||||
library tests.compiler.dart2js.cps_ir.constructor_15.dart;
|
||||
|
||||
import 'runner.dart';
|
||||
|
||||
main(args) {
|
||||
runTest("constructor_15.dart", update: args.length > 0 && args[0] == "update");
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// Expectation for test:
|
||||
// // Method to test: generative_constructor(A#)
|
||||
// class A {
|
||||
// var x, y, z;
|
||||
// A(x, y) {
|
||||
// this.x = x;
|
||||
// this.y = y;
|
||||
// this.z = this.x / 2;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// main() {
|
||||
// print(new A(123, 'sdf').y);
|
||||
// try {} finally {} // Do not inline into main.
|
||||
// }
|
||||
|
||||
function(x, y) {
|
||||
return new V.A(x, y, x / 2);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// Method to test: generative_constructor(A#)
|
||||
class A {
|
||||
var x, y, z;
|
||||
A(x, y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = this.x / 2;
|
||||
}
|
||||
}
|
||||
|
||||
main() {
|
||||
print(new A(123, 'sdf').y);
|
||||
try {} finally {} // Do not inline into main.
|
||||
}
|
||||
@@ -96,6 +96,7 @@ main(args) {
|
||||
runTest('constructor_12.dart', update: true);
|
||||
runTest('constructor_13.dart', update: true);
|
||||
runTest('constructor_14.dart', update: true);
|
||||
runTest('constructor_15.dart', update: true);
|
||||
runTest('constructor_2.dart', update: true);
|
||||
runTest('constructor_3.dart', update: true);
|
||||
runTest('constructor_4.dart', update: true);
|
||||
|
||||
Reference in New Issue
Block a user