Skip to content

Commit 3f2a696

Browse files
rakudramaCommit Queue
authored andcommitted
[dart2js] Improve algorithm for condition targets
1. Use a work queue to avoid recursion on deep trees. 2. Use a visited set to avoid cycles and repeated work on conditions like `b && b`. Bug: #60801 Change-Id: If20de27b1ddd157feee2391289ec781c03f741ae Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/431704 Reviewed-by: Mayank Patke <[email protected]> Commit-Queue: Stephen Adams <[email protected]>
1 parent d34f769 commit 3f2a696

File tree

2 files changed

+140
-47
lines changed

2 files changed

+140
-47
lines changed

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

Lines changed: 110 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5+
import 'dart:collection' show Queue;
6+
57
import 'package:js_runtime/synced/array_flags.dart' show ArrayFlags;
68

79
import '../common.dart';
@@ -4150,15 +4152,11 @@ class SsaTypeConversionInserter extends HBaseVisitor<void>
41504152
HInstruction input = node.checkedInput;
41514153
if (input.usedBy.length <= 1) return; // No other uses to refine.
41524154

4153-
List<HBasicBlock> trueTargets = [];
4154-
List<HBasicBlock> falseTargets = [];
4155-
4156-
collectTargets(node, trueTargets, falseTargets);
4157-
4158-
if (trueTargets.isEmpty && falseTargets.isEmpty) return;
4155+
final targets = ConditionTargets(node);
4156+
if (targets.isEmpty) return;
41594157

41604158
AbstractValue whenTrueType = node.checkedAbstractValue.abstractValue;
4161-
insertTypeRefinements(trueTargets, input, whenTrueType);
4159+
insertTypeRefinements(targets.whenTrue, input, whenTrueType);
41624160
// TODO(sra): Also strengthen uses for when the condition is precise and
41634161
// known false (e.g. int? x; ... if (x is! int) use(x)). Avoid strengthening
41644162
// to `null`.
@@ -4169,15 +4167,11 @@ class SsaTypeConversionInserter extends HBaseVisitor<void>
41694167
HInstruction input = node.checkedInput;
41704168
if (input.usedBy.length <= 1) return; // No other uses to refine.
41714169

4172-
List<HBasicBlock> trueTargets = [];
4173-
List<HBasicBlock> falseTargets = [];
4174-
4175-
collectTargets(node, trueTargets, falseTargets);
4176-
4177-
if (trueTargets.isEmpty && falseTargets.isEmpty) return;
4170+
final targets = ConditionTargets(node);
4171+
if (targets.isEmpty) return;
41784172

41794173
AbstractValue whenTrueType = node.checkedAbstractValue.abstractValue;
4180-
insertTypeRefinements(trueTargets, input, whenTrueType);
4174+
insertTypeRefinements(targets.whenTrue, input, whenTrueType);
41814175
// TODO(sra): Also strengthen uses for when the condition is precise and
41824176
// known false (e.g. int? x; ... if (x is! int) use(x)). Avoid strengthening
41834177
// to `null`.
@@ -4204,17 +4198,13 @@ class SsaTypeConversionInserter extends HBaseVisitor<void>
42044198
return;
42054199
}
42064200

4207-
List<HBasicBlock> trueTargets = [];
4208-
List<HBasicBlock> falseTargets = [];
4209-
4210-
collectTargets(node, trueTargets, falseTargets);
4211-
4212-
if (trueTargets.isEmpty && falseTargets.isEmpty) return;
4201+
final targets = ConditionTargets(node);
4202+
if (targets.isEmpty) return;
42134203

42144204
AbstractValue nonNullType = _abstractValueDomain.excludeNull(
42154205
input.instructionType,
42164206
);
4217-
insertTypeRefinements(falseTargets, input, nonNullType);
4207+
insertTypeRefinements(targets.whenFalse, input, nonNullType);
42184208
// We don't strengthen the known-true references. It doesn't happen often
42194209
// and we don't want "if (x==null) return x;" to convert between JavaScript
42204210
// 'null' and 'undefined'.
@@ -4225,26 +4215,92 @@ class SsaTypeConversionInserter extends HBaseVisitor<void>
42254215
final input = node.inputs.single;
42264216
if (input.usedBy.length <= 1) return; // No other uses to refine.
42274217

4228-
List<HBasicBlock> trueTargets = [];
4229-
List<HBasicBlock> falseTargets = [];
4230-
4231-
collectTargets(node, trueTargets, falseTargets);
4232-
4233-
if (trueTargets.isEmpty && falseTargets.isEmpty) return;
4218+
final targets = ConditionTargets(node);
4219+
if (targets.isEmpty) return;
42344220

42354221
final sentinelType = _abstractValueDomain.lateSentinelType;
42364222
final nonSentinelType = _abstractValueDomain.excludeLateSentinel(
42374223
input.instructionType,
42384224
);
4239-
insertTypeRefinements(trueTargets, input, sentinelType);
4240-
insertTypeRefinements(falseTargets, input, nonSentinelType);
4225+
insertTypeRefinements(targets.whenTrue, input, sentinelType);
4226+
insertTypeRefinements(targets.whenFalse, input, nonSentinelType);
42414227
}
4228+
}
4229+
4230+
typedef _Step = (HInstruction, {_Targets? whenTrue, _Targets? whenFalse});
4231+
4232+
/// [ConditionTargets] collects the target blocks for a condition. The target
4233+
/// blocks are blocks reachable only when the condition is true (`whenTrue`) and
4234+
/// blocks reachable only when the condition is false (`whenFalse`).
4235+
///
4236+
/// The algorithm starts with an initial condition instruction and two empty
4237+
/// sets of targets, [_trueTargets] and [_falseTargets]. If the condition is
4238+
/// used in HIf control flow, we know the condition is true in the then-block
4239+
/// and false in the else-block.
4240+
///
4241+
/// If the condition is used by HNot, we can infer that when the HNot is true,
4242+
/// the condition was false and the trueTargets of the HNot are the falseTargets
4243+
/// of the original condition. The HNot is added to a work queue with flipped
4244+
/// targets.
4245+
///
4246+
/// If the condition is used in a phi, or a HIf controlling a phi, depending on
4247+
/// how the condition is used, sometimes when the phi is true or false, we can
4248+
/// infer something about the original condition's targets.
4249+
///
4250+
/// `whenTrue:` and `whenFalse` each have three possible values, (1)
4251+
/// _trueTargets, (2) _falseTargets, and (3) `null`.
4252+
class ConditionTargets {
4253+
/// For the visited set, only one of `whenTrue:` and `whenFalse:` used, the
4254+
/// other is `null`. It is unusual, but possible to visit both of:
4255+
///
4256+
/// (cond, whenTrue: _trueTargets, whenFalse: null)
4257+
/// (cond, whenTrue: _falseTargets, whenFalse: null)
4258+
///
4259+
/// This is a contradition (cond both must be true and also must be
4260+
/// false). Contradictions happen only in unreachable code.
4261+
final Set<_Step> _visited = {};
4262+
4263+
final Queue<_Step> _queue = Queue();
4264+
4265+
final _Targets _trueTargets = _Targets();
4266+
final _Targets _falseTargets = _Targets();
4267+
4268+
ConditionTargets(HInstruction condition) {
4269+
_add(condition, _trueTargets, _falseTargets);
4270+
while (_queue.isNotEmpty) {
4271+
_step();
4272+
}
4273+
}
4274+
4275+
bool get isEmpty => _trueTargets.isEmpty && _falseTargets.isEmpty;
4276+
4277+
List<HBasicBlock> get whenTrue => _trueTargets.blocks;
4278+
List<HBasicBlock> get whenFalse => _falseTargets.blocks;
4279+
4280+
void _add(HInstruction node, _Targets? trueTargets, _Targets? falseTargets) {
4281+
if (trueTargets == null && falseTargets == null) return;
4282+
_queue.add((node, whenTrue: trueTargets, whenFalse: falseTargets));
4283+
}
4284+
4285+
void _step() {
4286+
var (instruction, whenTrue: trueTargets, whenFalse: falseTargets) = _queue
4287+
.removeFirst();
4288+
4289+
// The same instruction (I) can be reached on different paths with different
4290+
// combinations of trueTargets (T) and falseTargets (F). Filling in the
4291+
// targets for problem (I,T,F) is separable into processing (I,T,null) and
4292+
// (I,null,F), so we check if we have visited this instruction before by
4293+
// querying trueTargets and falseTargets independently, and remove the
4294+
// separable subproblem that already has been solved.
4295+
if (trueTargets != null &&
4296+
!_visited.add((instruction, whenTrue: trueTargets, whenFalse: null))) {
4297+
trueTargets = null;
4298+
}
4299+
if (falseTargets != null &&
4300+
!_visited.add((instruction, whenTrue: null, whenFalse: falseTargets))) {
4301+
falseTargets = null;
4302+
}
42424303

4243-
void collectTargets(
4244-
HInstruction instruction,
4245-
List<HBasicBlock>? trueTargets,
4246-
List<HBasicBlock>? falseTargets,
4247-
) {
42484304
if (trueTargets == null && falseTargets == null) return;
42494305

42504306
for (HInstruction user in instruction.usedBy) {
@@ -4278,37 +4334,37 @@ class SsaTypeConversionInserter extends HBaseVisitor<void>
42784334
if (right.isConstantFalse()) {
42794335
// When `c ? x : false` is true, `c` must be true.
42804336
// So pass `c`'s trueTargets as the phi's trueTargets.
4281-
collectTargets(phi, trueTargets, null);
4337+
_add(phi, trueTargets, null);
42824338
} else if (right.isConstantTrue()) {
42834339
// When `c ? x : true` is false, `c` must be true.
42844340
// So pass `c`'s trueTargets as the phi's falseTargets.
4285-
collectTargets(phi, null, trueTargets);
4341+
_add(phi, null, trueTargets);
42864342
}
42874343

42884344
final left = phi.inputs[0];
42894345
if (left.isConstantFalse()) {
42904346
// When `c ? false : x` is true, `c` must be false.
42914347
// So pass `c`'s falseTargets as the phi's trueTargets.
4292-
collectTargets(phi, falseTargets, null);
4348+
_add(phi, falseTargets, null);
42934349
} else if (left.isConstantTrue()) {
42944350
// When `c ? true : x` is false, `c` must be false.
42954351
// So pass `c`'s falseTargets as the phi's falseTargets.
4296-
collectTargets(phi, null, falseTargets);
4352+
_add(phi, null, falseTargets);
42974353
}
42984354

42994355
// Sanity checks:
43004356
//
43014357
// For `c ? true : false`, we pass both `c`'s trueTargets and
43024358
// falseTargets as the same targets of the phi.
43034359
//
4304-
// For `c ? false : true`, we pass the targets reversed, like we
4305-
// for `HNot`.
4360+
// For `c ? false : true`, we pass the targets reversed, like
4361+
// we do for `HNot`.
43064362
//
43074363
// For `c ? false : false`, we pass both `c`'s trueTargets and
43084364
// falseTargets to the unreachable trueTargets of the phi. We
4309-
// might insert contradictory strengthenings, which might refine
4310-
// a value to Never, i.e. we potentially 'prove' the code is
4311-
// unreachable.
4365+
// might insert contradictory strengthenings, which might
4366+
// refine a value to Never, i.e. we potentially 'prove' the
4367+
// code is unreachable.
43124368
}
43134369
}
43144370
}
@@ -4318,28 +4374,35 @@ class SsaTypeConversionInserter extends HBaseVisitor<void>
43184374
// Don't insert refinements on else-branch - may be a critical edge
43194375
// block which we currently need to keep empty (except for phis).
43204376
} else if (user is HNot) {
4321-
collectTargets(user, falseTargets, trueTargets);
4377+
_add(user, falseTargets, trueTargets);
43224378
} else if (user is HPhi) {
43234379
List<HInstruction> inputs = user.inputs;
43244380
if (inputs.length == 2) {
43254381
assert(inputs.contains(instruction));
43264382
HInstruction other = inputs[(inputs[0] == instruction) ? 1 : 0];
43274383
if (other.isConstantTrue()) {
4328-
// The condition flows to `HPhi(true, user)` or `HPhi(user, true)`,
4384+
// The condition flows to `HPhi(true,user)` or `HPhi(user,true)`,
43294385
// which means that a downstream HIf has true-branch control flow
43304386
// that does not depend on the original instruction, so stop
43314387
// collecting [trueTargets].
4332-
collectTargets(user, null, falseTargets);
4388+
_add(user, null, falseTargets);
43334389
} else if (other.isConstantFalse()) {
43344390
// Ditto for false.
4335-
collectTargets(user, trueTargets, null);
4391+
_add(user, trueTargets, null);
43364392
}
43374393
}
43384394
}
43394395
}
43404396
}
43414397
}
43424398

4399+
class _Targets {
4400+
final List<HBasicBlock> _blocks = [];
4401+
bool get isEmpty => _blocks.isEmpty;
4402+
void add(HBasicBlock block) => _blocks.add(block);
4403+
List<HBasicBlock> get blocks => _blocks;
4404+
}
4405+
43434406
/// Optimization phase that tries to eliminate memory loads (for example
43444407
/// [HFieldGet]), when it knows the value stored in that memory location, and
43454408
/// stores that overwrite with the same value.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
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+
// Examples where null checks should be removed.
6+
7+
/*member: test1:function(a) {
8+
var b, b0, i;
9+
for (b = a != null, b0 = false, i = 0; ++i, i < 3; b0 = b)
10+
if (b0)
11+
B.JSArray_methods.get$first(a);
12+
}*/
13+
void test1(List<Object>? a) {
14+
bool b = false;
15+
int i = 0;
16+
// The null check is guarded by `b = false;` in the initial iteration.
17+
while (++i < 3) {
18+
if (b) sink = a!.first;
19+
b = a != null;
20+
}
21+
}
22+
23+
Object? sink;
24+
25+
/*member: main:ignore*/
26+
main() {
27+
test1(null);
28+
test1([1, 2]);
29+
test1(['x']);
30+
}

0 commit comments

Comments
 (0)