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+
57import 'package:js_runtime/synced/array_flags.dart' show ArrayFlags;
68
79import '../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.
0 commit comments