Move assignment chaining to own pass
- We get simplification from running after removal of HTypeKnown.
- Added chaining when the chain is used several times.
The best size improvement on apps I tested is 0.25% (minified).
Change-Id: If2c50dea5899efb402782ea92c76c4fe628e1c1d
Reviewed-on: https://dart-review.googlesource.com/c/93800
Reviewed-by: Sigmund Cherem <sigmund@google.com>
Commit-Queue: Stephen Adams <sra@google.com>
diff --git a/pkg/compiler/lib/src/ssa/codegen.dart b/pkg/compiler/lib/src/ssa/codegen.dart
index 66e3253..7efacf8 100644
--- a/pkg/compiler/lib/src/ssa/codegen.dart
+++ b/pkg/compiler/lib/src/ssa/codegen.dart
@@ -356,6 +356,7 @@
.visitGraph(graph);
new SsaTypeKnownRemover().visitGraph(graph);
new SsaTrustedCheckRemover(_options).visitGraph(graph);
+ new SsaAssignmentChaining(_options, _closedWorld).visitGraph(graph);
new SsaInstructionMerger(
_abstractValueDomain, generateAtUseSite, _superMemberData)
.visitGraph(graph);
diff --git a/pkg/compiler/lib/src/ssa/codegen_helpers.dart b/pkg/compiler/lib/src/ssa/codegen_helpers.dart
index c45b071..ab2684b 100644
--- a/pkg/compiler/lib/src/ssa/codegen_helpers.dart
+++ b/pkg/compiler/lib/src/ssa/codegen_helpers.dart
@@ -20,8 +20,6 @@
final CompilerOptions _options;
HGraph graph;
- Set<HInstruction> _processedSetters = Set();
-
SsaInstructionSelection(
this._options, this._closedWorld, this._interceptorData);
@@ -196,99 +194,6 @@
return false;
}
- void tryChainAssignment(HInstruction setter, HInstruction value) {
- // Try to use result of field or static 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 (_processedSetters.contains(setter)) return;
- _processedSetters.add(setter);
-
- // Single use is this setter so there will be no other uses to chain.
- if (value.usedBy.length <= 1) return;
-
- HInstruction 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) {
- _processedSetters.add(nextSetter);
- nextSetter.changeUse(value, chain);
- nextSetter.instructionType = value.instructionType;
- chain = nextSetter;
- current = nextSetter.next;
- continue;
- }
- } else if (current is HStaticStore) {
- HStaticStore nextStore = current;
- if (nextStore.value == value) {
- _processedSetters.add(nextStore);
- nextStore.changeUse(value, chain);
- nextStore.instructionType = value.instructionType;
- chain = nextStore;
- current = nextStore.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;
-
- bool simpleSource =
- value is HConstant || value.nonCheck() is HParameterValue;
-
- 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;
- // Try to avoid the variable allocator creating a new name.
- if (simpleSource || value.usedBy.length <= 2) {
- use.changeUse(value, chain);
- return;
- }
- }
- }
-
- if (simpleSource) 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;
- }
-
- HInstruction visitStaticStore(HStaticStore store) {
- tryChainAssignment(store, store.inputs.single);
- return null;
- }
-
HInstruction visitFieldSet(HFieldSet setter) {
// Pattern match
// t1 = x.f; t2 = t1 + 1; x.f = t2; use(t2) --> ++x.f
@@ -314,7 +219,6 @@
}
HInstruction noMatchingRead() {
- tryChainAssignment(setter, setter.value);
// If we have other HFieldSet optimizations, they go here.
return null;
}
@@ -491,6 +395,169 @@
}
}
+/// Use the result of static and field assignments where it is profitable to use
+/// the result of the assignment instead of the value.
+///
+/// a.x = v;
+/// b.y = v;
+/// -->
+/// b.y = a.x = v;
+class SsaAssignmentChaining extends HBaseVisitor {
+ final JClosedWorld _closedWorld;
+ final CompilerOptions _options;
+ //HGraph graph;
+
+ SsaAssignmentChaining(this._options, this._closedWorld);
+
+ AbstractValueDomain get _abstractValueDomain =>
+ _closedWorld.abstractValueDomain;
+
+ void visitGraph(HGraph graph) {
+ //this.graph = graph;
+ visitDominatorTree(graph);
+ }
+
+ void visitBasicBlock(HBasicBlock block) {
+ HInstruction instruction = block.first;
+ while (instruction != null) {
+ instruction = instruction.accept(this);
+ }
+ }
+
+ /// Returns the next instruction.
+ HInstruction visitInstruction(HInstruction node) {
+ return node.next;
+ }
+
+ HInstruction visitFieldSet(HFieldSet setter) {
+ return tryChainAssignment(setter, setter.value);
+ }
+
+ HInstruction visitStaticStore(HStaticStore store) {
+ return tryChainAssignment(store, store.inputs.single);
+ }
+
+ HInstruction tryChainAssignment(HInstruction setter, HInstruction value) {
+ // Try to use result of field or static assignment
+ //
+ // t1 = v; x.f = t1; ... t1 ...
+ // -->
+ // t1 = x.f = v; ... t1 ...
+ //
+
+ // Single use is this setter so there will be no other uses to chain to.
+ if (value.usedBy.length <= 1) return setter.next;
+
+ // It is always worthwhile chaining into another setter, since it reduces
+ // the number of references to [value].
+ HInstruction 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) {
+ nextSetter.changeUse(value, chain);
+ nextSetter.instructionType = value.instructionType;
+ chain = nextSetter;
+ current = nextSetter.next;
+ continue;
+ }
+ } else if (current is HStaticStore) {
+ HStaticStore nextStore = current;
+ if (nextStore.value == value) {
+ nextStore.changeUse(value, chain);
+ nextStore.instructionType = value.instructionType;
+ chain = nextStore;
+ current = nextStore.next;
+ continue;
+ }
+ } else if (current is HReturn) {
+ if (current.inputs.single == value) {
+ current.changeUse(value, chain);
+ return current.next;
+ }
+ }
+ break;
+ }
+
+ final HInstruction next = chain.next;
+
+ if (value.usedBy.length <= 1) return next; // setter is only remaining use.
+
+ // Chain to other places.
+ var uses = DominatedUses.of(value, chain, excludeDominator: true);
+
+ if (uses.isEmpty) return next;
+
+ bool simpleSource = value is HConstant || value is HParameterValue;
+
+ 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 next;
+ if (use.usedBy.any((node) => node is HPhi)) return next;
+
+ // A forward phi often has a new name. We want to avoid [value] having a
+ // name at the same time, so chain into the phi only if [value] (1) will
+ // never have a name (like a constant) (2) unavoidably has a name
+ // (e.g. a parameter) or (3) chaining will result in a single use that
+ // variable allocator can try to name consistently with the other phi
+ // inputs.
+ if (simpleSource || value.usedBy.length <= 2) {
+ if (value.isPure(_abstractValueDomain) ||
+ setter.previous == value ||
+ // the following tests for immediately previous phi.
+ (setter.previous == null && value.block == setter.block)) {
+ use.changeUse(value, chain);
+ }
+ }
+ return next;
+ }
+ // TODO(sra): Chains with one remaining potential use have the potential
+ // to generate huge expression containing many nested assignments. This
+ // will be smaller but nearly impossible to read. Are there interior
+ // positions that we should chain into because they are not too difficult
+ // to read?
+ return next;
+ }
+
+ if (simpleSource) return next;
+
+ // If there are many remaining uses, but they are all dominated by [chain],
+ // the variable allocator will try to give them all the same name.
+ if (uses.length >= 2 &&
+ value.usedBy.length - uses.length <= 1 &&
+ value == value.nonCheck()) {
+ // If [value] is a phi, it might have a name. Exceptions are phis that can
+ // be compiled into expressions like `a?b:c` and `a&&b`. We can't tell
+ // here if the phi is an expression, which we could chain, or an
+ // if-then-else with assignments to a variable. If we try to chain an
+ // if-then-else phi we will end up generating code like
+ //
+ // t2 = this.x = t1; // t1 is the phi, t2 is the chained name
+ //
+ if (value is HPhi) return next;
+
+ // We need [value] to be generate-at-use in the setter to avoid it having
+ // a name. As a quick check for generate-at-use, accept pure and
+ // immediately preceding instructions.
+ if (value.isPure(_abstractValueDomain) || setter.previous == value) {
+ // TODO(sra): We can tolerate a few non-throwing loads between setter
+ // and value.
+ uses.replaceWith(chain);
+ chain.sourceElement ??= value.sourceElement;
+ }
+ return next;
+ }
+
+ return next;
+ }
+}
+
/// Instead of emitting each SSA instruction with a temporary variable
/// mark instructions that can be emitted at their use-site.
/// For example, in: