diff --git a/pkg/compiler/lib/src/inferrer/abstract_value_domain.dart b/pkg/compiler/lib/src/inferrer/abstract_value_domain.dart index b4ba15bb0e4..28fbbb142c0 100644 --- a/pkg/compiler/lib/src/inferrer/abstract_value_domain.dart +++ b/pkg/compiler/lib/src/inferrer/abstract_value_domain.dart @@ -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); diff --git a/pkg/compiler/lib/src/inferrer/trivial.dart b/pkg/compiler/lib/src/inferrer/trivial.dart index 218962b0453..07c10d43aaf 100644 --- a/pkg/compiler/lib/src/inferrer/trivial.dart +++ b/pkg/compiler/lib/src/inferrer/trivial.dart @@ -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(); diff --git a/pkg/compiler/lib/src/inferrer/typemasks/masks.dart b/pkg/compiler/lib/src/inferrer/typemasks/masks.dart index a24356ab075..22664623882 100644 --- a/pkg/compiler/lib/src/inferrer/typemasks/masks.dart +++ b/pkg/compiler/lib/src/inferrer/typemasks/masks.dart @@ -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); } } diff --git a/pkg/compiler/lib/src/ssa/builder_kernel.dart b/pkg/compiler/lib/src/ssa/builder_kernel.dart index a7fea25a618..5f35e74b057 100644 --- a/pkg/compiler/lib/src/ssa/builder_kernel.dart +++ b/pkg/compiler/lib/src/ssa/builder_kernel.dart @@ -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 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; } diff --git a/pkg/compiler/lib/src/ssa/nodes.dart b/pkg/compiler/lib/src/ssa/nodes.dart index 89a7adf4246..495f6291fbc 100644 --- a/pkg/compiler/lib/src/ssa/nodes.dart +++ b/pkg/compiler/lib/src/ssa/nodes.dart @@ -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(); } diff --git a/pkg/compiler/lib/src/ssa/optimize.dart b/pkg/compiler/lib/src/ssa/optimize.dart index ee39ddb7552..56af89d98e3 100644 --- a/pkg/compiler/lib/src/ssa/optimize.dart +++ b/pkg/compiler/lib/src/ssa/optimize.dart @@ -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 trueTargets = []; + List falseTargets = []; + + 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. diff --git a/pkg/compiler/lib/src/ssa/type_builder.dart b/pkg/compiler/lib/src/ssa/type_builder.dart index 7a0eaed347f..aac509ae05c 100644 --- a/pkg/compiler/lib/src/ssa/type_builder.dart +++ b/pkg/compiler/lib/src/ssa/type_builder.dart @@ -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( diff --git a/tests/compiler/dart2js/codegen/new_rti_is_test.dart b/tests/compiler/dart2js/codegen/new_rti_is_test.dart new file mode 100644 index 00000000000..06a3604cbcd --- /dev/null +++ b/tests/compiler/dart2js/codegen/new_rti_is_test.dart @@ -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(); + }); +} diff --git a/tests/compiler/dart2js/helpers/compiler_helper.dart b/tests/compiler/dart2js/helpers/compiler_helper.dart index a6c1deb4a29..215609c0800 100644 --- a/tests/compiler/dart2js/helpers/compiler_helper.dart +++ b/tests/compiler/dart2js/helpers/compiler_helper.dart @@ -37,6 +37,7 @@ Future 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 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.