[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,