Skip to content

Commit a1c5485

Browse files
rakudramaCommit Queue
authored andcommitted
[dart2js] Detect larger no-op regions
Change-Id: Id4ced6f3a54d40a3775b77fde8453e45a6383ee2 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/404800 Commit-Queue: Stephen Adams <[email protected]> Reviewed-by: Nate Biggs <[email protected]>
1 parent b92ef7e commit a1c5485

File tree

2 files changed

+254
-42
lines changed

2 files changed

+254
-42
lines changed

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

Lines changed: 71 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3309,6 +3309,7 @@ class SsaDeadCodeEliminator extends HGraphVisitor implements OptimizationPhase {
33093309
}
33103310
block.forEachPhi(simplifyPhi);
33113311
evacuateTakenBranch(block);
3312+
updateEmptyRegions(block);
33123313
}
33133314

33143315
void simplifyPhi(HPhi phi) {
@@ -3391,24 +3392,20 @@ class SsaDeadCodeEliminator extends HGraphVisitor implements OptimizationPhase {
33913392
// We want to remove an if-then-else diamond when the then- and else-
33923393
// branches are empty and the condition does not control a HPhi. We cannot
33933394
// change the CFG structure so we replace the HIf condition with a
3394-
// constant. This may leave the original condition unused. i.e. a
3395-
// candidate for being dead code.
3395+
// constant. This may leave the original condition unused and a candidate
3396+
// for being dead code.
33963397
//
33973398
// TODO(http://dartbug.com/29475): Remove empty blocks so that recognizing
33983399
// no-op control flow is trivial.
33993400

3400-
List<HBasicBlock> dominated = block.dominatedBlocks;
3401-
// Diamond-like control flow dominates the then-, else- and join- blocks.
3402-
if (dominated.length != 3) return;
3403-
HBasicBlock join = dominated.last;
3404-
if (!join.phis.isEmpty) return; // condition controls a phi.
3405-
// Ignore exit block - usually the join in `if (...) return ...`
3406-
if (join.isExitBlock()) return;
3401+
final thenBlock = block.successors[0];
3402+
final (thenContinuation, thenSize) = _emptyRegion[thenBlock]!;
34073403

3408-
final thenSize = measureEmptyInterval(instruction.thenBlock, join);
3409-
if (thenSize == null) return;
3410-
final elseSize = measureEmptyInterval(instruction.elseBlock, join);
3411-
if (elseSize == null) return;
3404+
final elseBlock = block.successors[1];
3405+
final (elseContinuation, elseSize) = _emptyRegion[elseBlock]!;
3406+
3407+
if (thenContinuation != elseContinuation) return;
3408+
if (!thenContinuation.phis.isEmpty) return;
34123409

34133410
// Pick the 'live' branch to be the smallest subgraph.
34143411
bool value = thenSize <= elseSize;
@@ -3417,38 +3414,70 @@ class SsaDeadCodeEliminator extends HGraphVisitor implements OptimizationPhase {
34173414
}
34183415
}
34193416

3420-
/// Returns the number of blocks from [start] up to but not including [end].
3421-
/// Returns `null` if any of the blocks is non-empty (other than control
3422-
/// flow). Returns `null` if there is an exit from the region other than
3423-
/// [end] or via control flow other than HGoto and HIf.
3424-
int? measureEmptyInterval(HBasicBlock start, HBasicBlock end) {
3425-
const int maxCount = 10;
3426-
int count = 0;
3427-
for (HBasicBlock currentBlock = start; currentBlock != end;) {
3428-
final instruction = currentBlock.first;
3429-
if (instruction!.next != null) return null; // Block not empty.
3430-
count++;
3431-
// K-limit the search for `end`.
3432-
if (count > maxCount) return null;
3433-
HBasicBlock? next;
3434-
if (instruction is HGoto) {
3435-
next = currentBlock.successors.single;
3436-
} else if (instruction is HIf) {
3437-
// If no-op control flow in the subgraph was simplified, the condition
3438-
// will be constant, and point us down the shorter side of the diamond.
3439-
final condition = instruction.inputs.single;
3440-
if (condition.isConstantTrue()) {
3441-
next = instruction.thenBlock;
3442-
} else if (condition.isConstantFalse()) {
3443-
next = instruction.elseBlock;
3417+
/// Map from block to the continuation, and, if all paths to the continuation
3418+
/// are empty, the number of blocks in that region. The block is either (1)
3419+
/// the continuation of the block (a join point of a single-entry, single-exit
3420+
/// region) or (2a) an earlier block if some the path to the continuation is
3421+
/// not empty, or (2b) there is path that avoids the continuation (like a loop
3422+
/// exit or a return). A block `B` that is non-empty has the entry `(B, 0)`. A
3423+
/// block that dominates a region that has an exit or a path that is non-empty
3424+
/// is also marked as `(B, 0)`.
3425+
final Map<HBasicBlock, (HBasicBlock, int)> _emptyRegion = {};
3426+
3427+
void updateEmptyRegions(HBasicBlock block) {
3428+
// Default is to consider this block to be the entry to an non-empty region.
3429+
_emptyRegion[block] = (block, 0);
3430+
3431+
// To be empty, this block should have nothing except a single terminating
3432+
// HControlFlow instruction.
3433+
final instruction = block.first;
3434+
if (instruction == null || instruction.next != null) return;
3435+
if (!block.phis.isEmpty) return;
3436+
3437+
if (!(instruction is HGoto ||
3438+
instruction is HIf ||
3439+
instruction is HBreak ||
3440+
instruction is HSwitch)) {
3441+
// Other control flow instructions either generate code (e.g. HReturn),
3442+
// or are part of some structure like a loop or try-catch.
3443+
return;
3444+
}
3445+
3446+
// If all paths from this block reach the same continuation, we have an
3447+
// empty single-entry / single-exit region. Create a new empty interval
3448+
// from this block to the continuation, and extend it to the continuation of
3449+
// the continuation.
3450+
3451+
if (block.successors.isEmpty) return;
3452+
3453+
int size = 1; // Size of empty region includes this block.
3454+
3455+
HBasicBlock? continuation;
3456+
if (block.successors.length == 1) {
3457+
continuation = block.successors.single;
3458+
} else {
3459+
for (final successor in block.successors) {
3460+
if (successor.id < block.id) return; // Back-edge
3461+
final (successorContinuation, successorSize) = _emptyRegion[successor]!;
3462+
size += successorSize;
3463+
if (continuation == null) {
3464+
continuation = successorContinuation;
3465+
} else if (continuation != successorContinuation) {
3466+
// This happens when (1) some successor is not empty, (2) some
3467+
// successor is, or contains, an edge that exits the nested
3468+
// forward-edge single-entry-single-exit regions.
3469+
return;
34443470
}
34453471
}
3446-
if (next == null) return null;
3447-
// Check [next] is within [start,end) region.
3448-
if (next.id < start.id || next.id > end.id) return null;
3449-
currentBlock = next;
34503472
}
3451-
return count;
3473+
3474+
if (continuation!.dominator == block) {
3475+
final int continuationSize;
3476+
(continuation, continuationSize) = _emptyRegion[continuation]!;
3477+
size += continuationSize;
3478+
}
3479+
3480+
_emptyRegion[block] = (continuation, size);
34523481
}
34533482

34543483
/// If [block] is an always-taken branch, move the code from the taken branch
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
@pragma('dart2js:never-inline')
6+
/*member: foo1:function(x) {
7+
return x;
8+
}*/
9+
Object? foo1(Object? x) {
10+
final Object? y;
11+
if (x is int) {
12+
y = x;
13+
} else if (x is String) {
14+
y = x;
15+
} else if (x is List) {
16+
y = x;
17+
} else {
18+
y = x;
19+
}
20+
return y;
21+
}
22+
23+
@pragma('dart2js:never-inline')
24+
/*member: foo2:function(x) {
25+
$label0$0: {
26+
break $label0$0;
27+
}
28+
return x;
29+
}*/
30+
Object? foo2(Object? x) {
31+
final Object? y;
32+
L:
33+
{
34+
if (x is int) {
35+
y = x;
36+
break L;
37+
}
38+
if (x is String) {
39+
if (x == 'hello') {
40+
y = x;
41+
break L;
42+
} else {
43+
y = x;
44+
}
45+
break L;
46+
}
47+
if (x is List) {
48+
y = x;
49+
break L;
50+
}
51+
y = x;
52+
}
53+
return y;
54+
}
55+
56+
@pragma('dart2js:never-inline')
57+
/*member: foo3:function(x) {
58+
A.inscrutableBool(x);
59+
return x;
60+
}*/
61+
Object? foo3(Object? x) {
62+
final Object? y;
63+
if (inscrutableBool(x)) {
64+
switch (x) {
65+
case 1:
66+
case 2:
67+
case 3:
68+
y = x;
69+
case 4:
70+
case 5:
71+
y = x;
72+
case 6:
73+
y = x;
74+
default:
75+
y = x;
76+
}
77+
} else {
78+
y = x;
79+
}
80+
return y;
81+
}
82+
83+
@pragma('dart2js:never-inline')
84+
/*member: foo4:function(x) {
85+
if (A._isInt(x))
86+
switch (x) {
87+
case 1:
88+
case 2:
89+
case 3:
90+
break;
91+
case 4:
92+
case 5:
93+
break;
94+
case 6:
95+
break;
96+
}
97+
return x;
98+
}*/
99+
Object? foo4(Object? x) {
100+
final Object? y;
101+
if (x is int) {
102+
switch (x) {
103+
case 1:
104+
case 2:
105+
case 3:
106+
y = x;
107+
case 4:
108+
case 5:
109+
y = x;
110+
case 6:
111+
y = x;
112+
default:
113+
y = x;
114+
}
115+
} else {
116+
y = x;
117+
}
118+
return y;
119+
}
120+
121+
@pragma('dart2js:never-inline')
122+
/*member: foo5:function(x) {
123+
switch (x) {
124+
case 1:
125+
case 2:
126+
case 3:
127+
break;
128+
case 4:
129+
case 5:
130+
break;
131+
case 6:
132+
break;
133+
}
134+
return x;
135+
}*/
136+
Object? foo5(Object? x) {
137+
final Object? y;
138+
switch (x) {
139+
case 1:
140+
case 2:
141+
case 3:
142+
if (2 == x) {
143+
y = x;
144+
} else {
145+
y = x;
146+
}
147+
case 4:
148+
case 5:
149+
y = x;
150+
case 6:
151+
y = x;
152+
default:
153+
y = x;
154+
}
155+
return y;
156+
}
157+
158+
/*member: inscrutableBool:ignore*/
159+
@pragma('dart2js:never-inline')
160+
bool inscrutableBool(Object? x) {
161+
return x is int || x is bool;
162+
}
163+
164+
/*member: main:ignore*/
165+
main() {
166+
for (final value in [
167+
null,
168+
1,
169+
3.5,
170+
true,
171+
'a',
172+
[],
173+
{},
174+
{1},
175+
'x'.codeUnits,
176+
]) {
177+
print(foo1(value));
178+
print(foo2(value));
179+
print(foo3(value));
180+
print(foo4(value));
181+
print(foo5(value));
182+
}
183+
}

0 commit comments

Comments
 (0)