[dart2js] Refine types based on test with late sentinel

A small number of getters for `late final` instance fields with
initializers have better code.

When the initializing expression provably does not write to the backing
field*, the field value is re-used rather than re-loaded for the 'final'
check (that the field has not been assigned during the evaluation of the
initializer). We are testing both `value === $` and `value !== $`:

```
      value = _this.___LayoutWidgetState_firstChild_FI;
      if (value === $) {
        result = A._LayoutWidgetState__buildChild(B.ValueKey_1, _this._widget.node.firstChild);
        value !== $ && A.throwUnnamedLateFieldADI();
        _this.___LayoutWidgetState_firstChild_FI = result;
        value = result;
      }
```

In this change we refine the type of the tested value to late sentinel,
allowing the check to be removed:

```
      if (value === $)
        value = _this.___LayoutWidgetState_firstChild_FI = A._LayoutWidgetState__buildChild(B.ValueKey_1, _this._widget.node.firstChild);
```

*This effect analysis is pretty simple ('writes some field'), but if
improved, we might expect more `late final` getters to benefit.

Change-Id: I444ed157083663b848fb2f115d0575e544b47727
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/430962
Reviewed-by: Mayank Patke <fishythefish@google.com>
Commit-Queue: Stephen Adams <sra@google.com>
diff --git a/pkg/compiler/lib/src/ssa/optimize.dart b/pkg/compiler/lib/src/ssa/optimize.dart
index 56d0941..4936e8a 100644
--- a/pkg/compiler/lib/src/ssa/optimize.dart
+++ b/pkg/compiler/lib/src/ssa/optimize.dart
@@ -4100,7 +4100,7 @@
 class SsaTypeConversionInserter extends HBaseVisitor<void>
     implements OptimizationPhase {
   @override
-  final String name = "SsaTypeconversionInserter";
+  final String name = "SsaTypeConversionInserter";
   final JClosedWorld closedWorld;
 
   SsaTypeConversionInserter(this.closedWorld);
@@ -4116,16 +4116,16 @@
   @override
   bool validPostcondition(HGraph graph) => true;
 
-  // Update users of [input] that are dominated by [:dominator.first:]
-  // to use [TypeKnown] of [input] instead. As the type information depends
-  // on the control flow, we mark the inserted [HTypeKnown] nodes as
-  // non-movable.
-  void insertTypePropagationForDominatedUsers(
+  /// Update users of [value] that are dominated by the start of the [dominator]
+  /// block to use [TypeKnown] of [value] instead. As the type refinement
+  /// depends on the control flow, we mark the inserted [HTypeKnown] nodes as
+  /// non-movable.
+  void insertTypeRefinement(
     HBasicBlock dominator,
-    HInstruction input,
+    HInstruction value,
     AbstractValue convertedType,
   ) {
-    DominatedUses dominatedUses = DominatedUses.of(input, dominator.first!);
+    DominatedUses dominatedUses = DominatedUses.of(value, dominator.first!);
     if (dominatedUses.isEmpty) return;
 
     // Check to avoid adding a duplicate HTypeKnown node.
@@ -4134,18 +4134,31 @@
       if (user is HTypeKnown &&
           user.isPinned &&
           user.knownType == convertedType &&
-          user.checkedInput == input) {
+          user.checkedInput == value) {
         return;
       }
     }
 
-    HTypeKnown newInput = HTypeKnown.pinned(convertedType, input);
-    dominator.addBefore(dominator.first, newInput);
-    dominatedUses.replaceWith(newInput);
+    final replacement = HTypeKnown.pinned(convertedType, value);
+    dominator.addBefore(dominator.first, replacement);
+    dominatedUses.replaceWith(replacement);
+  }
+
+  void insertTypeRefinements(
+    List<HBasicBlock> targets,
+    HInstruction value,
+    AbstractValue convertedType,
+  ) {
+    for (final block in targets) {
+      insertTypeRefinement(block, value, convertedType);
+    }
   }
 
   @override
   void visitIsTest(HIsTest node) {
+    HInstruction input = node.checkedInput;
+    if (input.usedBy.length <= 1) return; // No other uses to refine.
+
     List<HBasicBlock> trueTargets = [];
     List<HBasicBlock> falseTargets = [];
 
@@ -4153,12 +4166,8 @@
 
     if (trueTargets.isEmpty && falseTargets.isEmpty) return;
 
-    AbstractValue convertedType = node.checkedAbstractValue.abstractValue;
-    HInstruction input = node.checkedInput;
-
-    for (HBasicBlock block in trueTargets) {
-      insertTypePropagationForDominatedUsers(block, input, convertedType);
-    }
+    AbstractValue whenTrueType = node.checkedAbstractValue.abstractValue;
+    insertTypeRefinements(trueTargets, input, whenTrueType);
     // TODO(sra): Also strengthen uses for when the condition is precise and
     // known false (e.g. int? x; ... if (x is! int) use(x)). Avoid strengthening
     // to `null`.
@@ -4166,6 +4175,9 @@
 
   @override
   void visitIsTestSimple(HIsTestSimple node) {
+    HInstruction input = node.checkedInput;
+    if (input.usedBy.length <= 1) return; // No other uses to refine.
+
     List<HBasicBlock> trueTargets = [];
     List<HBasicBlock> falseTargets = [];
 
@@ -4173,12 +4185,8 @@
 
     if (trueTargets.isEmpty && falseTargets.isEmpty) return;
 
-    AbstractValue convertedType = node.checkedAbstractValue.abstractValue;
-    HInstruction input = node.checkedInput;
-
-    for (HBasicBlock block in trueTargets) {
-      insertTypePropagationForDominatedUsers(block, input, convertedType);
-    }
+    AbstractValue whenTrueType = node.checkedAbstractValue.abstractValue;
+    insertTypeRefinements(trueTargets, input, whenTrueType);
     // TODO(sra): Also strengthen uses for when the condition is precise and
     // known false (e.g. int? x; ... if (x is! int) use(x)). Avoid strengthening
     // to `null`.
@@ -4199,6 +4207,8 @@
       return;
     }
 
+    if (input.usedBy.length <= 1) return; // No other uses to refine.
+
     if (_abstractValueDomain.isNull(input.instructionType).isDefinitelyFalse) {
       return;
     }
@@ -4213,15 +4223,32 @@
     AbstractValue nonNullType = _abstractValueDomain.excludeNull(
       input.instructionType,
     );
-
-    for (HBasicBlock block in falseTargets) {
-      insertTypePropagationForDominatedUsers(block, input, nonNullType);
-    }
+    insertTypeRefinements(falseTargets, input, nonNullType);
     // We don't strengthen the known-true references. It doesn't happen often
     // and we don't want "if (x==null) return x;" to convert between JavaScript
     // 'null' and 'undefined'.
   }
 
+  @override
+  void visitIsLateSentinel(HIsLateSentinel node) {
+    final input = node.inputs.single;
+    if (input.usedBy.length <= 1) return; // No other uses to refine.
+
+    List<HBasicBlock> trueTargets = [];
+    List<HBasicBlock> falseTargets = [];
+
+    collectTargets(node, trueTargets, falseTargets);
+
+    if (trueTargets.isEmpty && falseTargets.isEmpty) return;
+
+    final sentinelType = _abstractValueDomain.lateSentinelType;
+    final nonSentinelType = _abstractValueDomain.excludeLateSentinel(
+      input.instructionType,
+    );
+    insertTypeRefinements(trueTargets, input, sentinelType);
+    insertTypeRefinements(falseTargets, input, nonSentinelType);
+  }
+
   void collectTargets(
     HInstruction instruction,
     List<HBasicBlock>? trueTargets,