Skip to content

Commit b3502f1

Browse files
rakudramaCommit Queue
authored andcommitted
[dart2js] Reduce redundant phis with refinements
Redundant phi elimination removes phis that join the same value. However, this does not work when one or more of the inputs has a refinement (HTypeKnown). This change adds redundant phi elimination when the phi inputs have refinements. The need for this optimization shows up when static js_interop needs a dispatch on type for conversion: ``` final JSAny? jsValue; if (value is String) { jsValue = value.toJS; } else if (value is bool) { jsValue = value.toJS; ... ``` (The `.toJS` calls become no-ops since, for dart2js, we are already in JavaScript, and so leave an otherwise pointless if-then-else chain). Change-Id: If1a94856592163a81ac36c686cee04232c16d197 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/403950 Reviewed-by: Nate Biggs <[email protected]> Commit-Queue: Stephen Adams <[email protected]>
1 parent de2131c commit b3502f1

File tree

2 files changed

+98
-8
lines changed

2 files changed

+98
-8
lines changed

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

Lines changed: 65 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,8 @@ class SsaOptimizerTask extends CompilerTask {
103103

104104
measure(() {
105105
List<OptimizationPhase> phases = [
106-
// Run trivial instruction simplification first to optimize
107-
// some patterns useful for type conversion.
106+
// Run trivial instruction simplification first to optimize some
107+
// patterns useful for type conversion.
108108
SsaInstructionSimplifier(
109109
globalInferenceResults,
110110
_options,
@@ -113,6 +113,7 @@ class SsaOptimizerTask extends CompilerTask {
113113
registry,
114114
log,
115115
metrics,
116+
beforeTypePropagation: true,
116117
),
117118
SsaTypeConversionInserter(closedWorld),
118119
SsaRedundantPhiEliminator(),
@@ -123,8 +124,7 @@ class SsaOptimizerTask extends CompilerTask {
123124
closedWorld,
124125
log,
125126
),
126-
// After type propagation, more instructions can be
127-
// simplified.
127+
// After type propagation, more instructions can be simplified.
128128
SsaInstructionSimplifier(
129129
globalInferenceResults,
130130
_options,
@@ -309,6 +309,13 @@ class SsaInstructionSimplifier extends HBaseVisitor<HInstruction>
309309
final CodegenRegistry _registry;
310310
final OptimizationTestLog? _log;
311311
final SsaMetrics _metrics;
312+
313+
/// Most simplifications become enabled when the types are refined by type
314+
/// propagation. Some simplifications remove code that helps type progagation
315+
/// produce a better result. These simplifications are inhibited when
316+
/// [beforeTypePropagation] is `true` to ensure they are seeing the propagated
317+
/// types.
318+
final bool beforeTypePropagation;
312319
late final HGraph _graph;
313320

314321
SsaInstructionSimplifier(
@@ -318,8 +325,9 @@ class SsaInstructionSimplifier extends HBaseVisitor<HInstruction>
318325
this._typeRecipeDomain,
319326
this._registry,
320327
this._log,
321-
this._metrics,
322-
);
328+
this._metrics, {
329+
this.beforeTypePropagation = false,
330+
});
323331

324332
JCommonElements get commonElements => _closedWorld.commonElements;
325333

@@ -418,8 +426,6 @@ class SsaInstructionSimplifier extends HBaseVisitor<HInstruction>
418426

419427
// Simplify some CFG diamonds to equivalent expressions.
420428
void simplifyPhis(HBasicBlock block) {
421-
if (block.predecessors.length != 2) return;
422-
423429
// Do 'statement' simplifications first, as they might reduce the number of
424430
// phis to one, enabling an 'expression' simplification.
425431
var phi = block.phis.firstPhi;
@@ -429,6 +435,7 @@ class SsaInstructionSimplifier extends HBaseVisitor<HInstruction>
429435
phi = next;
430436
}
431437

438+
if (block.predecessors.length != 2) return;
432439
phi = block.phis.firstPhi;
433440
if (phi != null && phi.next == null) {
434441
simplifyExpressionPhi(block, phi);
@@ -438,6 +445,10 @@ class SsaInstructionSimplifier extends HBaseVisitor<HInstruction>
438445
/// Simplify a single phi when there are possibly other phis (i.e. the result
439446
/// might not be an expression).
440447
void simplifyStatementPhi(HBasicBlock block, HPhi phi) {
448+
if (simplifyStatementPhiToCommonInput(block, phi)) return;
449+
450+
if (block.predecessors.length != 2) return;
451+
441452
HBasicBlock dominator = block.dominator!;
442453

443454
// Extract the controlling condition.
@@ -478,6 +489,52 @@ class SsaInstructionSimplifier extends HBaseVisitor<HInstruction>
478489
}
479490
}
480491

492+
bool simplifyStatementPhiToCommonInput(HBasicBlock block, HPhi phi) {
493+
// Replace phis that produce the same value on all arms. The test(s) for
494+
// control flow often results in a refinement instruction (HTypeKnown), so
495+
// we recognize that, allowing, e.g.,
496+
//
497+
// condition ? HTypeKnown(x) : x --> x
498+
// condition ? x : HTypeKnown(x) --> x
499+
//
500+
// We don't remove loop phis here. SsaRedundantPhiEliminator will eliminate
501+
// redundant phis without HTypeKnown refinements, including loop phis.
502+
503+
// There may be control flow that exits early, leaving refinements that
504+
// cause the type of the phi to be stronger than the source. Don't attempt
505+
// this simplification until the type of the phi is calculated.
506+
if (beforeTypePropagation) return false;
507+
508+
HBasicBlock dominator = block.dominator!;
509+
510+
/// Find the input, skipping refinements that do not dominate the condition,
511+
/// e.g., skipping refinements in the arm of the if-then-else.
512+
HInstruction? dominatingRefinementInput(HInstruction input) {
513+
while (true) {
514+
if (input.block!.dominates(dominator)) return input;
515+
if (input is! HTypeKnown) return null;
516+
input = input.checkedInput;
517+
}
518+
}
519+
520+
final commonInput = dominatingRefinementInput(phi.inputs.first);
521+
if (commonInput == null) return false;
522+
523+
for (int i = 1; i < phi.inputs.length; i++) {
524+
final next = dominatingRefinementInput(phi.inputs[i]);
525+
if (!identical(next, commonInput)) return false;
526+
}
527+
528+
HTypeKnown replacement = HTypeKnown.pinned(
529+
phi.instructionType,
530+
commonInput,
531+
);
532+
block.addBefore(block.first, replacement);
533+
block.rewrite(phi, replacement);
534+
block.removePhi(phi);
535+
return true;
536+
}
537+
481538
/// Simplify some CFG diamonds to equivalent expressions.
482539
void simplifyExpressionPhi(HBasicBlock block, HPhi phi) {
483540
// Is [block] the join point for a simple diamond?
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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+
if (Date.now() > 0) {
8+
if (typeof x != "string")
9+
return "bad1";
10+
} else if (typeof x != "string")
11+
return "bad2";
12+
return x;
13+
}*/
14+
String foo1(Object x) {
15+
final Object y;
16+
if (DateTime.now().millisecondsSinceEpoch > 0) {
17+
if (x is! String) return 'bad1';
18+
y = x;
19+
} else {
20+
if (x is! String) return 'bad2';
21+
y = x;
22+
}
23+
// The phi for y has refinements to String on both branches, so the return
24+
// should not need stringification.
25+
return '$y';
26+
}
27+
28+
/*member: main:ignore*/
29+
main() {
30+
print(foo1('a'));
31+
print(foo1('b'));
32+
print(foo1(123));
33+
}

0 commit comments

Comments
 (0)