[dart2js] Basic chaining of field assignments
t4 = t3.tooltip;
t2._tooltipText = t4;
this.currenttooltip = t4;
--->
this.currenttooltip = t2._tooltipText = t3.tooltip;
future = new P._Future(0, $.Zone__current, [P.bool]);
this._stateData = future;
return future;
--->
return this._stateData = new P._Future(0, $.Zone__current, [P.bool]);
Change-Id: I8a1c4dae85b8a10f8b2c099668033f0c1ea9b6c4
Reviewed-on: https://dart-review.googlesource.com/c/93342
Commit-Queue: Stephen Adams <sra@google.com>
Reviewed-by: Sigmund Cherem <sigmund@google.com>
diff --git a/pkg/compiler/lib/src/ssa/codegen.dart b/pkg/compiler/lib/src/ssa/codegen.dart
index f93696b..9136155 100644
--- a/pkg/compiler/lib/src/ssa/codegen.dart
+++ b/pkg/compiler/lib/src/ssa/codegen.dart
@@ -521,11 +521,9 @@
if (current.isControlFlow()) {
return TYPE_STATEMENT;
}
- // HFieldSet generates code on the form x.y = ..., which isn't
- // valid in a declaration, but it also always have no uses, so
- // it's caught by that test too.
- assert(current is! HFieldSet || current.usedBy.isEmpty);
- if (current.usedBy.isEmpty) {
+ // HFieldSet generates code on the form "x.y = ...", which isn't valid
+ // in a declaration.
+ if (current.usedBy.isEmpty || current is HFieldSet) {
result = TYPE_EXPRESSION;
}
current = current.next;
diff --git a/pkg/compiler/lib/src/ssa/codegen_helpers.dart b/pkg/compiler/lib/src/ssa/codegen_helpers.dart
index 78ddc04..34fe26e 100644
--- a/pkg/compiler/lib/src/ssa/codegen_helpers.dart
+++ b/pkg/compiler/lib/src/ssa/codegen_helpers.dart
@@ -20,6 +20,8 @@
final CompilerOptions _options;
HGraph graph;
+ Set<HFieldSet> _processedFieldSetters = Set();
+
SsaInstructionSelection(
this._options, this._closedWorld, this._interceptorData);
@@ -195,6 +197,81 @@
}
HInstruction visitFieldSet(HFieldSet setter) {
+ void tryChainAssignment() {
+ // Try to use result of field assignment
+ //
+ // t1 = v; x.f = t1; ... t1 ... --> t1 = x.f = v; ... t1 ...
+ //
+
+ // We grow the chain ahead of the block-scan, so we may have already
+ // processed the chain.
+ if (_processedFieldSetters.contains(setter)) return;
+ _processedFieldSetters.add(setter);
+
+ final value = setter.value;
+
+ // Single use is this setter so there will be no other uses to chain.
+ if (value.usedBy.length <= 1) return;
+
+ HFieldSet chain = setter;
+ setter.instructionType = value.instructionType;
+ for (HInstruction current = setter.next;;) {
+ if (current is HFieldSet) {
+ HFieldSet nextSetter = current;
+ if (nextSetter.value == value && nextSetter.receiver != value) {
+ _processedFieldSetters.add(nextSetter);
+ nextSetter.changeUse(value, chain);
+ nextSetter.instructionType = value.instructionType;
+ chain = nextSetter;
+ current = nextSetter.next;
+ continue;
+ }
+ } else if (current is HReturn) {
+ if (current.inputs.single == value) {
+ current.changeUse(value, chain);
+ return;
+ }
+ }
+ break;
+ }
+
+ if (value.usedBy.length <= 1) return; // [setter] is only remaining use.
+
+ // Chain to other places.
+ var uses = DominatedUses.of(value, chain, excludeDominator: true);
+
+ if (uses.isEmpty) return;
+
+ if (uses.isSingleton) {
+ var use = uses.single;
+ if (use is HPhi) {
+ // Filter out back-edges - that causes problems for variable
+ // assignment.
+ // TODO(sra): Better analysis to permit phis that are part of a
+ // forwards-only tree.
+ if (use.block.id < chain.block.id) return;
+ if (use.usedBy.any((node) => node is HPhi)) return;
+ use.changeUse(value, chain);
+ return;
+ }
+ }
+
+ if (value is HConstant) return;
+ if (value.nonCheck() is HParameterValue) return;
+
+ // TODO(sra): Consider chaining to other places.
+ //
+ // 1. If there are many remaining uses, all of them dominated by [chain],
+ // we should replace them with [chain] and let that value get the
+ // variable name.
+ //
+ // 2. Chains with one remaining potential use have the potential to
+ // generate huge expression containing many assignments. This will be
+ // smaller but nearly impossible to read. What interior positions
+ // should we chain into?
+ return;
+ }
+
// Pattern match
// t1 = x.f; t2 = t1 + 1; x.f = t2; use(t2) --> ++x.f
// t1 = x.f; t2 = t1 op y; x.f = t2; use(t2) --> x.f op= y
@@ -219,6 +296,7 @@
}
HInstruction noMatchingRead() {
+ tryChainAssignment();
// If we have other HFieldSet optimizations, they go here.
return null;
}
diff --git a/pkg/compiler/lib/src/ssa/nodes.dart b/pkg/compiler/lib/src/ssa/nodes.dart
index 5b0006f..cc28749 100644
--- a/pkg/compiler/lib/src/ssa/nodes.dart
+++ b/pkg/compiler/lib/src/ssa/nodes.dart
@@ -1327,6 +1327,7 @@
bool get isEmpty => _instructions.isEmpty;
bool get isNotEmpty => !isEmpty;
+ int get length => _instructions.length;
/// Changes all the uses in the set to [newInstruction].
void replaceWith(HInstruction newInstruction) {
@@ -1350,6 +1351,8 @@
HInstruction get single => _instructions.single;
+ Iterable<HInstruction> get instructions => _instructions;
+
void _addUse(HInstruction user, int inputIndex) {
_instructions.add(user);
_indexes.add(inputIndex);
@@ -1921,7 +1924,9 @@
HInstruction get value => inputs[1];
accept(HVisitor visitor) => visitor.visitFieldSet(this);
- bool isJsStatement() => true;
+ // HFieldSet is an expression if it has a user.
+ bool isJsStatement() => usedBy.isEmpty;
+
String toString() => "FieldSet(element=$element,type=$instructionType)";
}
diff --git a/tests/compiler/dart2js/codegen/load_elimination_test.dart b/tests/compiler/dart2js/codegen/load_elimination_test.dart
index fa16f0f..eb5c0a9 100644
--- a/tests/compiler/dart2js/codegen/load_elimination_test.dart
+++ b/tests/compiler/dart2js/codegen/load_elimination_test.dart
@@ -235,7 +235,7 @@
main() {
runTests() async {
- test(String code, String expected) async {
+ test(String code, Pattern expected) async {
String generated = await compile(code,
disableInlining: false, disableTypeInference: false);
Expect.isTrue(
@@ -250,7 +250,7 @@
await test(TEST_4, 'return t1 + t1');
await test(TEST_5, 'return 84');
await test(TEST_6, 'return 84');
- await test(TEST_7, 'return 32');
+ await test(TEST_7, RegExp('return( .* =)? 32'));
await test(TEST_8, 'return a.a');
await test(TEST_9, 'return a.a');
await test(TEST_10, 'return 2');