dart2js cps: Scalar replacement of aggregates

Aggregates which do not escape can be replaced with local variables.

This applies to regular objects, closures and boxes.  Closures need
their body inlined first.

Fields that are written are replaced with a MutableVariable.  Fields
that are only initialized have the reads replaced with references to
the initial value.

R=asgerf@google.com

Review URL: https://codereview.chromium.org//1319303002 .
This commit is contained in:
Stephen Adams
2015-09-01 22:25:04 -07:00
parent 98384272a5
commit 383f809030
6 changed files with 261 additions and 13 deletions
View File
@@ -723,6 +723,8 @@ class GetField extends Primitive {
bool get isSafeForElimination => objectIsNotNull;
bool get isSafeForReordering => false;
toString() => 'GetField($field)';
}
/// Get the length of a string or native list.
@@ -872,6 +874,8 @@ class CreateInstance extends Primitive {
bool get isSafeForElimination => true;
bool get isSafeForReordering => true;
toString() => 'CreateInstance($classElement)';
}
class Interceptor extends Primitive {
@@ -8,6 +8,7 @@ import 'cps_ir_nodes.dart';
import '../constants/values.dart';
export 'type_propagation.dart' show TypePropagator;
export 'scalar_replacement.dart' show ScalarReplacer;
export 'redundant_phi.dart' show RedundantPhiEliminator;
export 'redundant_join.dart' show RedundantJoinEliminator;
export 'shrinking_reductions.dart' show ShrinkingReducer, ParentVisitor;
@@ -0,0 +1,251 @@
// 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 'optimizers.dart';
import 'dart:collection' show Queue;
import '../closure.dart' show
ClosureClassElement, Identifiers;
import '../common/names.dart' show
Selectors, Identifiers;
import '../compiler.dart' as dart2js show
Compiler;
import '../constants/constant_system.dart';
import '../constants/values.dart';
import '../dart_types.dart' as types;
import '../diagnostics/invariant.dart' as dart2js show
InternalErrorFunction;
import '../elements/elements.dart';
import '../io/source_information.dart' show SourceInformation;
import '../resolution/access_semantics.dart';
import '../resolution/operators.dart';
import '../resolution/send_structure.dart';
import '../tree/tree.dart' as ast;
import '../types/types.dart';
import '../types/constants.dart' show computeTypeMask;
import '../universe/universe.dart';
import '../world.dart' show World;
import 'cps_fragment.dart';
import 'cps_ir_nodes.dart';
import 'cps_ir_nodes_sexpr.dart' show SExpressionStringifier;
/**
* Replaces aggregates with a set of local values. Performs inlining of
* single-use closures to generate more replacable aggregates.
*/
class ScalarReplacer extends Pass {
String get passName => 'Scalar replacement';
final dart2js.InternalErrorFunction _internalError;
ScalarReplacer(dart2js.Compiler compiler)
: _internalError = compiler.internalError;
@override
void rewrite(FunctionDefinition root) {
// Set all parent pointers.
new ParentVisitor().visit(root);
ScalarReplacementVisitor analyzer =
new ScalarReplacementVisitor(_internalError);
analyzer.analyze(root);
analyzer.process();
}
}
/**
* Do scalar replacement of aggregates on instances. Since scalar replacement
* can create new candidiates, iterate until all scalar replacements are done.
*/
class ScalarReplacementVisitor extends RecursiveVisitor {
final dart2js.InternalErrorFunction internalError;
ScalarReplacementRemovalVisitor removalVisitor;
Primitive _current = null;
Set<Primitive> _allocations = new Set<Primitive>();
Queue<Primitive> _queue = new Queue<Primitive>();
ScalarReplacementVisitor(this.internalError) {
removalVisitor = new ScalarReplacementRemovalVisitor(this);
}
void analyze(FunctionDefinition root) {
visit(root);
}
void process() {
while (_queue.isNotEmpty) {
Primitive allocation = _queue.removeFirst();
_allocations.remove(allocation);
_current = allocation;
tryScalarReplacement(allocation);
}
}
void tryScalarReplacement(Primitive allocation) {
// We can do scalar replacement of an aggregate if all uses of an allocation
// are reads or writes.
for (Reference ref = allocation.firstRef; ref != null; ref = ref.next) {
Node use = ref.parent;
if (use is GetField) continue;
if (use is SetField && use.object == ref) continue;
return;
}
Set<FieldElement> reads = new Set<FieldElement>();
Set<FieldElement> writes = new Set<FieldElement>();
for (Reference ref = allocation.firstRef; ref != null; ref = ref.next) {
Node use = ref.parent;
if (use is GetField) {
reads.add(use.field);
} else if (use is SetField) {
writes.add(use.field);
} else {
assert(false);
}
}
// Find the initial values of the fields. A CreateBox has no initial
// values. CreateInstance has initial values in the order of the fields.
Map<FieldElement, Primitive> fieldInitialValues =
<FieldElement, Primitive>{};
if (allocation is CreateInstance) {
int i = 0;
allocation.classElement.forEachInstanceField(
(ClassElement enclosingClass, FieldElement field) {
Primitive argument = allocation.arguments[i++].definition;
fieldInitialValues[field] = argument;
});
}
// Create [MutableVariable]s for each written field. Initialize the
// MutableVariable with the value from the allocator, or initialize with a
// `null` constant if there is not initial value.
Map<FieldElement, MutableVariable> cells =
<FieldElement, MutableVariable>{};
InteriorNode insertionPoint = allocation.parent; // LetPrim
for (FieldElement field in writes) {
MutableVariable variable = new MutableVariable(field);
cells[field] = variable;
Primitive initialValue = fieldInitialValues[field];
if (initialValue == null) {
assert(allocation is CreateBox);
initialValue = new Constant(new NullConstantValue());
LetPrim let = new LetPrim(initialValue);
let.primitive.parent = let;
insertionPoint = insertAtBody(insertionPoint, let);
}
LetMutable let = new LetMutable(variable, initialValue);
let.value.parent = let;
insertionPoint = insertAtBody(insertionPoint, let);
}
// Replace references with MutableVariable operations or references to the
// field's value.
for (Reference ref = allocation.firstRef; ref != null; ref = ref.next) {
Node use = ref.parent;
if (use is GetField) {
GetField getField = use;
MutableVariable variable = cells[getField.field];
if (variable != null) {
GetMutable getter = new GetMutable(variable);
getter.variable.parent = getter;
getter.substituteFor(getField);
replacePrimitive(getField, getter);
deletePrimitive(getField);
} else {
Primitive value = fieldInitialValues[getField.field];
value.substituteFor(getField);
deleteLetPrimOf(getField);
}
} else if (use is SetField && use.object == ref) {
SetField setField = use;
MutableVariable variable = cells[setField.field];
Primitive value = setField.value.definition;
SetMutable setter = new SetMutable(variable, value);
setter.variable.parent = setter;
setter.value.parent = setter;
setter.substituteFor(setField);
replacePrimitive(setField, setter);
deletePrimitive(setField);
} else {
assert(false);
}
}
// Delete [allocation] since that might 'free' another scalar replacement
// candidate by deleting the last non-field-access.
deleteLetPrimOf(allocation);
}
InteriorNode insertAtBody(
InteriorNode insertionPoint, InteriorExpression let) {
let.parent = insertionPoint;
let.body = insertionPoint.body;
let.body.parent = let;
insertionPoint.body = let;
return let;
}
/// Replaces [old] with [primitive] in [old]'s parent [LetPrim].
void replacePrimitive(Primitive old, Primitive primitive) {
LetPrim letPrim = old.parent;
letPrim.primitive = primitive;
}
void deleteLetPrimOf(Primitive primitive) {
assert(primitive.hasNoUses);
LetPrim letPrim = primitive.parent;
Node child = letPrim.body;
InteriorNode parent = letPrim.parent;
child.parent = parent;
parent.body = child;
deletePrimitive(primitive);
}
void deletePrimitive(Primitive primitive) {
assert(primitive.hasNoUses);
removalVisitor.visit(primitive);
}
void reconsider(Definition node) {
if (node is CreateInstance || node is CreateBox) {
if (node == _current) return;
enqueue(node);
}
}
void enqueue(Primitive node) {
assert(node is CreateInstance || node is CreateBox);
if (_allocations.contains(node)) return;
_allocations.add(node);
_queue.add(node);
}
// -------------------------- Visitor overrides ------------------------------
void visitCreateInstance(CreateInstance node) {
enqueue(node);
}
void visitCreateBox(CreateBox node) {
enqueue(node);
}
}
/// Visit a just-deleted subterm and unlink all [Reference]s in it. Reconsider
/// allocations for scalar replacement.
class ScalarReplacementRemovalVisitor extends RecursiveVisitor {
ScalarReplacementVisitor process;
ScalarReplacementRemovalVisitor(this.process);
processReference(Reference reference) {
process.reconsider(reference.definition);
reference.unlink();
}
}
@@ -175,6 +175,7 @@ class CpsFunctionCompiler implements FunctionCompiler {
dumpTypedIR(cpsNode, typePropagator);
applyCpsPass(new LoopInvariantCodeMotion());
applyCpsPass(new ShareInterceptors());
applyCpsPass(new ScalarReplacer(compiler));
applyCpsPass(new ShrinkingReducer());
applyCpsPass(new MutableVariableEliminator());
applyCpsPass(new RedundantJoinEliminator());
@@ -20,10 +20,7 @@ main(x) {
""",
r"""
function(x) {
var _box_0 = {};
_box_0._captured_x_0 = x;
_box_0._captured_x_0 = J.getInterceptor$ns(x = _box_0._captured_x_0).$add(x, "1");
P.print(new V.main_a(_box_0)._box_0._captured_x_0);
P.print(J.getInterceptor$ns(x).$add(x, "1"));
}"""),
const TestEntry("""
@@ -55,7 +52,7 @@ main(x) {
""",
r"""
function(x) {
P.print(new V.main_a(x)._captured_x_0);
P.print(x);
}"""),
const TestEntry("""
@@ -84,10 +81,7 @@ main() {
""",
r"""
function() {
var _box_0 = {};
_box_0._captured_x_0 = 122;
_box_0._captured_x_0 = _box_0._captured_x_0 + 1;
P.print(new V.main_closure(_box_0)._box_0._captured_x_0);
P.print(122 + 1);
}"""),
const TestEntry("""
@@ -121,10 +115,7 @@ main() {
""",
r"""
function() {
var _box_0 = {};
_box_0._captured_x_0 = 122;
_box_0._captured_x_0 = _box_0._captured_x_0 + 1;
P.print(new V.main__closure(new V.main_closure(_box_0)._box_0._captured_x_0)._captured_y_1);
P.print(122 + 1);
}"""),
const TestEntry("""