Skip to content

Commit 9062cf4

Browse files
rakudramaCommit Queue
authored andcommitted
[dart2js] Improve DominatedUses.replaceWith
When the instruction being replaced has many uses, it is beneficial to update them together, rather than one at a time. Also: - Make value_range_analyer output intermediate state for `--dump-ssa`. - Remove bounds checks all together at end of range analysis. - Don't add RangeConversion for the length when the length is a constant. Compiling a method with 5000 assignments of the form `a[N] = N;` is reduced 20x, from 123 seconds to 6 seconds. Bug: #56919 Change-Id: I9ca7b41ca156ee40b96cfed9e18369662053b3b7 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/390824 Reviewed-by: Nate Biggs <[email protected]> Commit-Queue: Stephen Adams <[email protected]>
1 parent 88ae929 commit 9062cf4

File tree

4 files changed

+106
-35
lines changed

4 files changed

+106
-35
lines changed

pkg/compiler/lib/src/ssa/nodes.dart

Lines changed: 73 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1138,6 +1138,14 @@ abstract class HInstruction implements SpannableWithEntity {
11381138
// devirtualized.
11391139
final List<HInstruction> inputs;
11401140

1141+
// Instructions that uses this instruction. A user is [usedBy] once per input
1142+
// that is this instruction.
1143+
//
1144+
// y = [x, x + 1, x];
1145+
//
1146+
// The [usedBy] for the instruction `x` has three elements, one for the
1147+
// addition instruction and two for the list literal instruction, in no
1148+
// particilar order.
11411149
final List<HInstruction> usedBy = [];
11421150

11431151
HBasicBlock? block;
@@ -1503,19 +1511,63 @@ class DominatedUses {
15031511
bool get isNotEmpty => !isEmpty;
15041512
int get length => _instructions.length;
15051513

1506-
/// Changes all the uses in the set to [newInstruction].
1507-
void replaceWith(HInstruction newInstruction) {
1508-
assert(!identical(newInstruction, _source));
1514+
/// Changes all the uses in the set to [replacement].
1515+
void replaceWith(HInstruction replacement) {
1516+
assert(replacement.isInBasicBlock());
1517+
assert(!identical(replacement, _source));
15091518
if (isEmpty) return;
1519+
15101520
for (int i = 0; i < _instructions.length; i++) {
15111521
HInstruction user = _instructions[i];
15121522
int index = _indexes[i];
1513-
HInstruction oldInstruction = user.inputs[index];
15141523
assert(
1515-
identical(oldInstruction, _source),
1524+
identical(user.inputs[index], _source),
15161525
'Input ${index} of ${user} changed.'
1517-
'\n Found: ${oldInstruction}\n Expected: ${_source}');
1518-
user.replaceInput(index, newInstruction);
1526+
'\n Found: ${user.inputs[index]}\n Expected: ${_source}');
1527+
user.inputs[index] = replacement;
1528+
replacement.usedBy.add(user);
1529+
}
1530+
1531+
// The following loop is a more efficient implementation of:
1532+
//
1533+
// for (final user in _instructions) {
1534+
// _source.usedBy.remove(user);
1535+
// }
1536+
//
1537+
// `List.remove` searches the list to find the key, and then scans the rest
1538+
// of the list to move the elements up one position. Repeating this is
1539+
// quadratic.
1540+
//
1541+
// The code below combines searching for the next element with move-up
1542+
// scanning for the previous element(s) to remove several elements in one
1543+
// pass, provided elements of `_instructions` are in the same order as in
1544+
// `usedBy`. This is usually the case since the DominatedUses set is
1545+
// constructed from `_source.usedBy`.
1546+
1547+
final usedBy = _source.usedBy;
1548+
int instructionsIndex = 0;
1549+
while (instructionsIndex < _instructions.length) {
1550+
HInstruction nextToRemove = _instructions[instructionsIndex];
1551+
int readIndex = 0, writeIndex = 0;
1552+
while (readIndex < usedBy.length) {
1553+
final user = usedBy[readIndex++];
1554+
if (identical(user, nextToRemove)) {
1555+
instructionsIndex++;
1556+
if (instructionsIndex < _instructions.length) {
1557+
nextToRemove = _instructions[instructionsIndex];
1558+
} else {
1559+
// Copy rest of the list elements up as-is.
1560+
while (readIndex < usedBy.length) {
1561+
usedBy[writeIndex++] = usedBy[readIndex++];
1562+
}
1563+
break;
1564+
}
1565+
} else {
1566+
usedBy[writeIndex++] = user;
1567+
}
1568+
}
1569+
assert(writeIndex < readIndex, 'Should remove at least one per pass');
1570+
usedBy.length = writeIndex;
15191571
}
15201572
}
15211573

@@ -1532,11 +1584,13 @@ class DominatedUses {
15321584

15331585
void _compute(HInstruction source, HInstruction dominator,
15341586
bool excludeDominator, bool excludePhiOutEdges) {
1587+
assert(dominator is! HPhi);
1588+
15351589
// Keep track of all instructions that we have to deal with later and count
1536-
// the number of them that are in the current block.
1590+
// the number of them that are in the dominator's block.
15371591
Set<HInstruction> users = Setlet();
15381592
Set<HInstruction> seen = Setlet();
1539-
int usersInCurrentBlock = 0;
1593+
int usersInDominatorBlock = 0;
15401594

15411595
HBasicBlock dominatorBlock = dominator.block!;
15421596

@@ -1547,9 +1601,15 @@ class DominatedUses {
15471601
for (HInstruction current in source.usedBy) {
15481602
if (!seen.add(current)) continue;
15491603
HBasicBlock currentBlock = current.block!;
1550-
if (dominatorBlock.dominates(currentBlock)) {
1604+
if (identical(currentBlock, dominatorBlock)) {
1605+
// Ignore phi nodes of the dominator instruction block, they come before
1606+
// the dominator instruction.
1607+
if (current is! HPhi) {
1608+
users.add(current);
1609+
usersInDominatorBlock++;
1610+
}
1611+
} else if (dominatorBlock.dominates(currentBlock)) {
15511612
users.add(current);
1552-
if (identical(currentBlock, dominatorBlock)) usersInCurrentBlock++;
15531613
} else if (!excludePhiOutEdges && current is HPhi) {
15541614
// A non-dominated HPhi.
15551615
// See if there a dominated edge into the phi. The input must be
@@ -1565,25 +1625,14 @@ class DominatedUses {
15651625
}
15661626
}
15671627

1568-
// Run through all the phis in the same block as [dominator] and remove them
1569-
// from the users set. These come before [dominator].
1570-
// TODO(sra): Could we simply not add them in the first place?
1571-
if (usersInCurrentBlock > 0) {
1572-
for (var phi = dominatorBlock.phis.first; phi != null; phi = phi.next) {
1573-
if (users.remove(phi)) {
1574-
if (--usersInCurrentBlock == 0) break;
1575-
}
1576-
}
1577-
}
1578-
15791628
// Run through all the instructions before [dominator] and remove them from
15801629
// the users set.
1581-
if (usersInCurrentBlock > 0) {
1630+
if (usersInDominatorBlock > 0) {
15821631
for (var current = dominatorBlock.first;
15831632
!identical(current, dominator);
15841633
current = current!.next) {
15851634
if (users.remove(current)) {
1586-
if (--usersInCurrentBlock == 0) break;
1635+
if (--usersInDominatorBlock == 0) break;
15871636
}
15881637
}
15891638
if (excludeDominator) {

pkg/compiler/lib/src/ssa/optimize.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ class SsaOptimizerTask extends CompilerTask {
132132
SsaTypeConversionInserter(closedWorld),
133133
SsaTypePropagator(globalInferenceResults, closedWorld.commonElements,
134134
closedWorld, log),
135-
SsaValueRangeAnalyzer(closedWorld, this),
135+
SsaValueRangeAnalyzer(closedWorld, this, codegen.tracer),
136136
// Previous optimizations may have generated new
137137
// opportunities for instruction simplification.
138138
SsaInstructionSimplifier(globalInferenceResults, _options, closedWorld,
@@ -156,7 +156,7 @@ class SsaOptimizerTask extends CompilerTask {
156156
closedWorld, log),
157157
SsaGlobalValueNumberer(closedWorld.abstractValueDomain),
158158
SsaCodeMotion(closedWorld.abstractValueDomain),
159-
SsaValueRangeAnalyzer(closedWorld, this),
159+
SsaValueRangeAnalyzer(closedWorld, this, codegen.tracer),
160160
SsaInstructionSimplifier(globalInferenceResults, _options,
161161
closedWorld, typeRecipeDomain, registry, log, metrics),
162162
SsaSimplifyInterceptors(closedWorld, member.enclosingClass),

pkg/compiler/lib/src/ssa/tracer.dart

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,8 @@ class HInstructionStringifier implements HVisitor<String> {
242242
String visitBoundsCheck(HBoundsCheck node) {
243243
String lengthId = temporaryId(node.length);
244244
String indexId = temporaryId(node.index);
245-
return "BoundsCheck: length = $lengthId, index = $indexId";
245+
return 'BoundsCheck: length = $lengthId, index = $indexId'
246+
', ${node.staticChecks.name}';
246247
}
247248

248249
@override
@@ -713,7 +714,8 @@ class HInstructionStringifier implements HVisitor<String> {
713714

714715
@override
715716
String visitRangeConversion(HRangeConversion node) {
716-
return "RangeConversion: ${node.checkedInput}";
717+
var inputs = node.inputs.map(temporaryId).join(', ');
718+
return 'RangeConversion: $inputs';
717719
}
718720

719721
@override

pkg/compiler/lib/src/ssa/value_range_analyzer.dart

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'dart:collection' show UnmodifiableMapView;
77
import '../constants/constant_system.dart' as constant_system;
88
import '../constants/values.dart';
99
import '../js_model/js_world.dart' show JClosedWorld;
10+
import '../tracer.dart';
1011
import 'nodes.dart';
1112
import 'optimize.dart' show OptimizationPhase, SsaOptimizerTask;
1213

@@ -720,17 +721,21 @@ class SsaValueRangeAnalyzer extends HBaseVisitor<Range>
720721
/// save them here in order to remove them once the phase is done.
721722
final List<HRangeConversion> conversions = [];
722723

724+
/// List of [HBoundsCheck] instructions collected during visit.
725+
final List<HBoundsCheck> boundsChecks = [];
726+
723727
/// Value ranges for integer instructions. This map gets populated by
724728
/// the dominator tree visit.
725729
final Map<HInstruction, Range> ranges = {};
726730

727731
final JClosedWorld closedWorld;
728732
final ValueRangeInfo info = ValueRangeInfo();
729733
final SsaOptimizerTask optimizer;
734+
final Tracer tracer;
730735

731736
late HGraph graph;
732737

733-
SsaValueRangeAnalyzer(this.closedWorld, this.optimizer);
738+
SsaValueRangeAnalyzer(this.closedWorld, this.optimizer, this.tracer);
734739

735740
@override
736741
void visitGraph(HGraph graph) {
@@ -740,23 +745,39 @@ class SsaValueRangeAnalyzer extends HBaseVisitor<Range>
740745

741746
this.graph = graph;
742747
visitDominatorTree(graph);
748+
749+
tracer.traceGraph(name + '.analysis', graph);
750+
751+
optimizeBoundsChecks();
752+
743753
// We remove the range conversions after visiting the graph so
744754
// that the graph does not get polluted with these instructions
745755
// only necessary for this phase.
746756
removeRangeConversion();
757+
747758
// TODO(herhut): Find a cleaner way to pass around ranges.
748759
optimizer.ranges = ranges;
749760
}
750761

751762
@override
752763
bool validPostcondition(HGraph graph) => true;
753764

765+
void optimizeBoundsChecks() {
766+
for (final check in boundsChecks) {
767+
if (check.staticChecks == StaticBoundsChecks.alwaysTrue) {
768+
final block = check.block!;
769+
block.rewrite(check, check.index);
770+
block.remove(check);
771+
}
772+
}
773+
}
774+
754775
void removeRangeConversion() {
755-
conversions.forEach((HRangeConversion instruction) {
776+
for (final instruction in conversions) {
756777
final block = instruction.block!;
757778
block.rewrite(instruction, instruction.inputs[0]);
758779
block.remove(instruction);
759-
});
780+
}
760781
}
761782

762783
@override
@@ -867,6 +888,7 @@ class SsaValueRangeAnalyzer extends HBaseVisitor<Range>
867888

868889
@override
869890
Range visitBoundsCheck(HBoundsCheck check) {
891+
boundsChecks.add(check);
870892
// Save the next instruction, in case the check gets removed.
871893
final next = check.next;
872894
Range? indexRange = ranges[check.index];
@@ -899,9 +921,7 @@ class SsaValueRangeAnalyzer extends HBaseVisitor<Range>
899921
(indexRange.upper != lengthRange.lower &&
900922
indexRange.upper.min(lengthRange.lower) == indexRange.upper);
901923
if (indexRange.isPositive && belowLength) {
902-
final checkBlock = check.block!;
903-
checkBlock.rewrite(check, check.index);
904-
checkBlock.remove(check);
924+
check.staticChecks = StaticBoundsChecks.alwaysTrue;
905925
} else if (indexRange.isNegative || lengthRange < indexRange) {
906926
check.staticChecks = StaticBoundsChecks.alwaysFalse;
907927
// The check is always false, and whatever instruction it
@@ -917,7 +937,7 @@ class SsaValueRangeAnalyzer extends HBaseVisitor<Range>
917937
// If the test passes, we know the lower bound of the length is
918938
// greater or equal than the lower bound of the index.
919939
Value low = lengthRange.lower.max(indexRange.lower);
920-
if (low != info.unknownValue) {
940+
if (low != info.unknownValue && check.length is! HConstant) {
921941
HInstruction instruction = createRangeConversion(next!, check.length);
922942
ranges[instruction] = info.newNormalizedRange(low, lengthRange.upper);
923943
}

0 commit comments

Comments
 (0)