[dart2js] new-rti: Simplification and branch-strengthening for 'is'
Change-Id: I49d712b303400fe2f2109b6c9f8fb684917df0ef Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/112701 Reviewed-by: Mayank Patke <fishythefish@google.com> Commit-Queue: Stephen Adams <sra@google.com>
This commit is contained in:
committed by
commit-bot@chromium.org
parent
f444374bb7
commit
bd47ec2f37
@@ -207,7 +207,7 @@ abstract class AbstractValueDomain {
|
||||
/// reasoning, for example, that a dominating check uses the same type
|
||||
/// expression.
|
||||
AbstractValueWithPrecision createFromStaticType(DartType type,
|
||||
[ClassRelation classRelation = ClassRelation.subtype]);
|
||||
{ClassRelation classRelation = ClassRelation.subtype, bool nullable});
|
||||
|
||||
/// Creates an [AbstractValue] for a non-null exact instance of [cls].
|
||||
AbstractValue createNonNullExact(ClassEntity cls);
|
||||
|
||||
@@ -360,8 +360,11 @@ class TrivialAbstractValueDomain implements AbstractValueDomain {
|
||||
|
||||
@override
|
||||
AbstractValueWithPrecision createFromStaticType(DartType type,
|
||||
[ClassRelation classRelation = ClassRelation.subtype]) =>
|
||||
const AbstractValueWithPrecision(const TrivialAbstractValue(), false);
|
||||
{ClassRelation classRelation = ClassRelation.subtype, bool nullable}) {
|
||||
assert(nullable != null);
|
||||
return const AbstractValueWithPrecision(
|
||||
const TrivialAbstractValue(), false);
|
||||
}
|
||||
|
||||
@override
|
||||
AbstractValue get asyncStarStreamType => const TrivialAbstractValue();
|
||||
|
||||
@@ -265,7 +265,13 @@ class CommonMasks implements AbstractValueDomain {
|
||||
|
||||
@override
|
||||
AbstractValueWithPrecision createFromStaticType(DartType type,
|
||||
[ClassRelation classRelation = ClassRelation.subtype]) {
|
||||
{ClassRelation classRelation = ClassRelation.subtype, bool nullable}) {
|
||||
assert(nullable != null);
|
||||
AbstractValueWithPrecision finish(TypeMask value, bool isPrecise) {
|
||||
return AbstractValueWithPrecision(
|
||||
nullable ? value : value.nonNullable(), isPrecise);
|
||||
}
|
||||
|
||||
bool isPrecise = true;
|
||||
while (type is TypeVariableType) {
|
||||
TypeVariableType typeVariable = type;
|
||||
@@ -274,6 +280,7 @@ class CommonMasks implements AbstractValueDomain {
|
||||
classRelation = ClassRelation.subtype;
|
||||
isPrecise = false;
|
||||
}
|
||||
|
||||
if (type is InterfaceType) {
|
||||
if (isPrecise) {
|
||||
// TODO(sra): Could be precise if instantiated-to-bounds.
|
||||
@@ -284,24 +291,22 @@ class CommonMasks implements AbstractValueDomain {
|
||||
}
|
||||
switch (classRelation) {
|
||||
case ClassRelation.exact:
|
||||
return AbstractValueWithPrecision(
|
||||
TypeMask.exact(type.element, _closedWorld), isPrecise);
|
||||
return finish(TypeMask.exact(type.element, _closedWorld), isPrecise);
|
||||
case ClassRelation.thisExpression:
|
||||
if (!_closedWorld.isUsedAsMixin(type.element)) {
|
||||
return AbstractValueWithPrecision(
|
||||
return finish(
|
||||
TypeMask.subclass(type.element, _closedWorld), isPrecise);
|
||||
}
|
||||
break;
|
||||
case ClassRelation.subtype:
|
||||
break;
|
||||
}
|
||||
return AbstractValueWithPrecision(
|
||||
TypeMask.subtype(type.element, _closedWorld), isPrecise);
|
||||
return finish(TypeMask.subtype(type.element, _closedWorld), isPrecise);
|
||||
} else if (type is FunctionType) {
|
||||
return AbstractValueWithPrecision(
|
||||
return finish(
|
||||
TypeMask.subtype(commonElements.functionClass, _closedWorld), false);
|
||||
} else {
|
||||
return AbstractValueWithPrecision(dynamicType, false);
|
||||
return finish(dynamicType, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4055,8 +4055,8 @@ class KernelSsaGraphBuilder extends ir.Visitor {
|
||||
StaticType receiverStaticType =
|
||||
_getStaticType(invocation.arguments.positional[1]);
|
||||
AbstractValue receiverType = _abstractValueDomain
|
||||
.createFromStaticType(
|
||||
receiverStaticType.type, receiverStaticType.relation)
|
||||
.createFromStaticType(receiverStaticType.type,
|
||||
classRelation: receiverStaticType.relation, nullable: true)
|
||||
.abstractValue;
|
||||
push(new HInvokeClosure(selector, receiverType, inputs,
|
||||
_abstractValueDomain.dynamicType, typeArguments));
|
||||
@@ -4811,8 +4811,8 @@ class KernelSsaGraphBuilder extends ir.Visitor {
|
||||
List<DartType> typeArguments,
|
||||
SourceInformation sourceInformation) {
|
||||
AbstractValue typeBound = _abstractValueDomain
|
||||
.createFromStaticType(
|
||||
staticReceiverType.type, staticReceiverType.relation)
|
||||
.createFromStaticType(staticReceiverType.type,
|
||||
classRelation: staticReceiverType.relation, nullable: true)
|
||||
.abstractValue;
|
||||
receiverType = receiverType == null
|
||||
? typeBound
|
||||
@@ -5404,7 +5404,11 @@ class KernelSsaGraphBuilder extends ir.Visitor {
|
||||
if (options.experimentNewRti) {
|
||||
HInstruction rti =
|
||||
_typeBuilder.analyzeTypeArgumentNewRti(typeValue, sourceElement);
|
||||
push(HIsTest(typeValue, expression, rti, _abstractValueDomain.boolType));
|
||||
AbstractValueWithPrecision checkedType =
|
||||
_abstractValueDomain.createFromStaticType(typeValue, nullable: false);
|
||||
|
||||
push(HIsTest(typeValue, checkedType, expression, rti,
|
||||
_abstractValueDomain.boolType));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -4352,10 +4352,11 @@ class HTypeInfoExpression extends HInstruction {
|
||||
/// lowered to other instructions, so this instruction remains for types that
|
||||
/// depend on type variables and complex types.
|
||||
class HIsTest extends HInstruction {
|
||||
final AbstractValueWithPrecision checkedAbstractValue;
|
||||
final DartType dartType;
|
||||
|
||||
HIsTest(
|
||||
this.dartType, HInstruction checked, HInstruction rti, AbstractValue type)
|
||||
HIsTest(this.dartType, this.checkedAbstractValue, HInstruction checked,
|
||||
HInstruction rti, AbstractValue type)
|
||||
: super([rti, checked], type) {
|
||||
setUseGvn();
|
||||
}
|
||||
|
||||
@@ -761,7 +761,9 @@ class SsaInstructionSimplifier extends HBaseVisitor
|
||||
_closedWorld.elementEnvironment.getFieldType(field);
|
||||
HInstruction closureCall = new HInvokeClosure(
|
||||
callSelector,
|
||||
_abstractValueDomain.createFromStaticType(fieldType).abstractValue,
|
||||
_abstractValueDomain
|
||||
.createFromStaticType(fieldType, nullable: true)
|
||||
.abstractValue,
|
||||
inputs,
|
||||
node.instructionType,
|
||||
node.typeArguments)
|
||||
@@ -1877,7 +1879,7 @@ class SsaInstructionSimplifier extends HBaseVisitor
|
||||
dartType, node.isTypeError, _closedWorld.commonElements);
|
||||
if (specializedCheck != null) {
|
||||
AbstractValueWithPrecision checkedType =
|
||||
_abstractValueDomain.createFromStaticType(dartType);
|
||||
_abstractValueDomain.createFromStaticType(dartType, nullable: true);
|
||||
return HAsCheckSimple(node.checkedInput, dartType, checkedType,
|
||||
node.isTypeError, specializedCheck, node.instructionType);
|
||||
}
|
||||
@@ -1891,6 +1893,27 @@ class SsaInstructionSimplifier extends HBaseVisitor
|
||||
return node;
|
||||
}
|
||||
|
||||
@override
|
||||
HInstruction visitIsTest(HIsTest node) {
|
||||
AbstractValueWithPrecision checkedAbstractValue = node.checkedAbstractValue;
|
||||
HInstruction checkedInput = node.checkedInput;
|
||||
AbstractValue inputType = checkedInput.instructionType;
|
||||
|
||||
AbstractBool isIn = _abstractValueDomain.isIn(
|
||||
inputType, checkedAbstractValue.abstractValue);
|
||||
|
||||
if (isIn.isDefinitelyFalse) {
|
||||
return _graph.addConstantBool(false, _closedWorld);
|
||||
}
|
||||
if (!checkedAbstractValue.isPrecise) return node;
|
||||
|
||||
if (isIn.isDefinitelyTrue) {
|
||||
return _graph.addConstantBool(true, _closedWorld);
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
@override
|
||||
HInstruction visitInstanceEnvironment(HInstanceEnvironment node) {
|
||||
HInstruction instance = node.inputs.single;
|
||||
@@ -2954,6 +2977,27 @@ class SsaTypeConversionInserter extends HBaseVisitor
|
||||
// false. Avoid strengthening to `null`.
|
||||
}
|
||||
|
||||
@override
|
||||
void visitIsTest(HIsTest instruction) {
|
||||
List<HBasicBlock> trueTargets = <HBasicBlock>[];
|
||||
List<HBasicBlock> falseTargets = <HBasicBlock>[];
|
||||
|
||||
collectTargets(instruction, trueTargets, falseTargets);
|
||||
|
||||
if (trueTargets.isEmpty && falseTargets.isEmpty) return;
|
||||
|
||||
AbstractValue convertedType =
|
||||
instruction.checkedAbstractValue.abstractValue;
|
||||
HInstruction input = instruction.checkedInput;
|
||||
|
||||
for (HBasicBlock block in trueTargets) {
|
||||
insertTypePropagationForDominatedUsers(block, input, convertedType);
|
||||
}
|
||||
// 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`.
|
||||
}
|
||||
|
||||
@override
|
||||
void visitIdentity(HIdentity instruction) {
|
||||
// At HIf(HIdentity(x, null)) strengthens x to non-null on else branch.
|
||||
|
||||
@@ -480,7 +480,7 @@ abstract class TypeBuilder {
|
||||
type, builder.sourceElement,
|
||||
sourceInformation: sourceInformation);
|
||||
AbstractValueWithPrecision checkedType =
|
||||
_abstractValueDomain.createFromStaticType(type);
|
||||
_abstractValueDomain.createFromStaticType(type, nullable: true);
|
||||
AbstractValue instructionType = _abstractValueDomain.intersection(
|
||||
original.instructionType, checkedType.abstractValue);
|
||||
return HAsCheck(
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
library new_rti_is_test;
|
||||
|
||||
import 'dart:async';
|
||||
import 'package:async_helper/async_helper.dart';
|
||||
import '../helpers/compiler_helper.dart';
|
||||
|
||||
// 'N' tests all have a nullable input so should not reduce is-test.
|
||||
// TODO(NNBD): Add tests with non-nullable input types.
|
||||
|
||||
const TEST1N = r"""
|
||||
foo(int a) {
|
||||
return a is double;
|
||||
// absent: 'return true'
|
||||
// absent: 'return false'
|
||||
}
|
||||
""";
|
||||
|
||||
const TEST2N = r"""
|
||||
foo(int a) {
|
||||
return a is num;
|
||||
// absent: 'return true'
|
||||
// absent: 'return false'
|
||||
}
|
||||
""";
|
||||
|
||||
const TEST3N = r"""
|
||||
foo(double a) {
|
||||
return a is int;
|
||||
// absent: 'return true'
|
||||
// absent: 'return false'
|
||||
}
|
||||
""";
|
||||
|
||||
const TEST4N = r"""
|
||||
foo(double a) {
|
||||
return a is num;
|
||||
// absent: 'return true'
|
||||
// absent: 'return false'
|
||||
}
|
||||
""";
|
||||
|
||||
const TEST5N = r"""
|
||||
foo(num a) {
|
||||
return a is int;
|
||||
// absent: 'return true'
|
||||
// absent: 'return false'
|
||||
}
|
||||
""";
|
||||
|
||||
const TEST6N = r"""
|
||||
foo(num a) {
|
||||
return a is double;
|
||||
// absent: 'return true'
|
||||
// absent: 'return false'
|
||||
}
|
||||
""";
|
||||
|
||||
const TEST1I = r"""
|
||||
foo(a) {
|
||||
if (a is int) return a is double;
|
||||
// present: 'return true'
|
||||
}
|
||||
""";
|
||||
|
||||
const TEST2I = r"""
|
||||
foo(a) {
|
||||
if (a is int) return a is num;
|
||||
// present: 'return true'
|
||||
}
|
||||
""";
|
||||
|
||||
const TEST3I = r"""
|
||||
foo(a) {
|
||||
if (a is double) return a is int;
|
||||
// absent: 'return true'
|
||||
// absent: 'return false'
|
||||
}
|
||||
""";
|
||||
|
||||
const TEST4I = r"""
|
||||
foo(a) {
|
||||
if (a is double) return a is num;
|
||||
// present: 'return true'
|
||||
}
|
||||
""";
|
||||
|
||||
const TEST5I = r"""
|
||||
foo(a) {
|
||||
if (a is num) return a is int;
|
||||
// absent: 'return true'
|
||||
// absent: 'return false'
|
||||
}
|
||||
""";
|
||||
|
||||
const TEST6I = r"""
|
||||
foo(a) {
|
||||
if (a is num) return a is double;
|
||||
// present: 'return true'
|
||||
}
|
||||
""";
|
||||
|
||||
main() {
|
||||
runTests() async {
|
||||
Future check(String test) {
|
||||
return compile(test,
|
||||
entry: 'foo', check: checkerForAbsentPresent(test), newRti: true);
|
||||
}
|
||||
|
||||
await check(TEST1N);
|
||||
await check(TEST2N);
|
||||
await check(TEST3N);
|
||||
await check(TEST4N);
|
||||
await check(TEST5N);
|
||||
await check(TEST6N);
|
||||
|
||||
await check(TEST1I);
|
||||
await check(TEST2I);
|
||||
await check(TEST3I);
|
||||
await check(TEST4I);
|
||||
await check(TEST5I);
|
||||
await check(TEST6I);
|
||||
}
|
||||
|
||||
asyncTest(() async {
|
||||
print('--test from kernel------------------------------------------------');
|
||||
await runTests();
|
||||
});
|
||||
}
|
||||
@@ -37,6 +37,7 @@ Future<String> compile(String code,
|
||||
bool trustJSInteropTypeAnnotations: false,
|
||||
bool disableTypeInference: true,
|
||||
bool omitImplicitChecks: true,
|
||||
bool newRti: false,
|
||||
void check(String generatedEntry),
|
||||
bool returnAll: false}) async {
|
||||
OutputCollector outputCollector = returnAll ? new OutputCollector() : null;
|
||||
@@ -59,6 +60,9 @@ Future<String> compile(String code,
|
||||
if (disableInlining) {
|
||||
options.add(Flags.disableInlining);
|
||||
}
|
||||
if (newRti) {
|
||||
options.add(Flags.experimentNewRti);
|
||||
}
|
||||
|
||||
// Pretend this is a dart2js_native test to allow use of 'native' keyword
|
||||
// and import of private libraries.
|
||||
|
||||
Reference in New Issue
Block a user