Version 2.13.0-188.0.dev
Merge commit '2a14259fd0ec065f8e09e5e910d3d6ce28ef4280' into 'dev'
diff --git a/pkg/front_end/lib/src/fasta/kernel/body_builder.dart b/pkg/front_end/lib/src/fasta/kernel/body_builder.dart
index 927f9e8..ccf323b 100644
--- a/pkg/front_end/lib/src/fasta/kernel/body_builder.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/body_builder.dart
@@ -1301,11 +1301,13 @@
void _unaliasTypeAliasedConstructorInvocations() {
for (TypeAliasedConstructorInvocationJudgment invocation
in typeAliasedConstructorInvocations) {
- DartType unaliasedType = new TypedefType(
- invocation.typeAliasBuilder.typedef,
- Nullability.nonNullable,
- invocation.arguments.types)
- .unalias;
+ DartType aliasedType = new TypedefType(
+ invocation.typeAliasBuilder.typedef,
+ Nullability.nonNullable,
+ invocation.arguments.types);
+ libraryBuilder.checkBoundsInType(
+ aliasedType, typeEnvironment, uri, invocation.fileOffset);
+ DartType unaliasedType = aliasedType.unalias;
List<DartType> invocationTypeArguments = null;
if (unaliasedType is InterfaceType) {
invocationTypeArguments = unaliasedType.typeArguments;
@@ -1323,11 +1325,13 @@
void _unaliasTypeAliasedFactoryInvocations() {
for (TypeAliasedFactoryInvocationJudgment invocation
in typeAliasedFactoryInvocations) {
- DartType unaliasedType = new TypedefType(
- invocation.typeAliasBuilder.typedef,
- Nullability.nonNullable,
- invocation.arguments.types)
- .unalias;
+ DartType aliasedType = new TypedefType(
+ invocation.typeAliasBuilder.typedef,
+ Nullability.nonNullable,
+ invocation.arguments.types);
+ libraryBuilder.checkBoundsInType(
+ aliasedType, typeEnvironment, uri, invocation.fileOffset);
+ DartType unaliasedType = aliasedType.unalias;
List<DartType> invocationTypeArguments = null;
if (unaliasedType is InterfaceType) {
invocationTypeArguments = unaliasedType.typeArguments;
@@ -4500,7 +4504,8 @@
List<UnresolvedType> typeArguments,
int charOffset,
Constness constness,
- {bool isTypeArgumentsInForest = false}) {
+ {bool isTypeArgumentsInForest = false,
+ TypeDeclarationBuilder typeAliasBuilder}) {
if (arguments == null) {
return buildProblem(fasta.messageMissingArgumentList,
nameToken.charOffset, nameToken.length);
@@ -4727,7 +4732,8 @@
invocation = buildStaticInvocation(target, arguments,
constness: constness,
charOffset: nameToken.charOffset,
- charLength: nameToken.length);
+ charLength: nameToken.length,
+ typeAliasBuilder: typeAliasBuilder);
if (invocation is StaticInvocation &&
isRedirectingFactory(target, helper: this)) {
diff --git a/pkg/front_end/lib/src/fasta/kernel/expression_generator.dart b/pkg/front_end/lib/src/fasta/kernel/expression_generator.dart
index a6e54b2..19f3191 100644
--- a/pkg/front_end/lib/src/fasta/kernel/expression_generator.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/expression_generator.dart
@@ -3058,8 +3058,9 @@
Arguments arguments = send.arguments;
TypeDeclarationBuilder declarationBuilder = declaration;
+ TypeAliasBuilder aliasBuilder;
if (declarationBuilder is TypeAliasBuilder) {
- TypeAliasBuilder aliasBuilder = declarationBuilder;
+ aliasBuilder = declarationBuilder;
declarationBuilder = aliasBuilder.unaliasDeclaration(null,
isUsedAsClass: true,
usedAsClassCharOffset: this.fileOffset,
@@ -3085,7 +3086,8 @@
send.typeArguments,
token.charOffset,
Constness.implicit,
- isTypeArgumentsInForest: send.isTypeArgumentsInForest);
+ isTypeArgumentsInForest: send.isTypeArgumentsInForest,
+ typeAliasBuilder: aliasBuilder);
}
} else if (member is AmbiguousBuilder) {
return _helper.buildProblem(
diff --git a/pkg/front_end/lib/src/fasta/kernel/expression_generator_helper.dart b/pkg/front_end/lib/src/fasta/kernel/expression_generator_helper.dart
index 83bd4ab..2faf2ed 100644
--- a/pkg/front_end/lib/src/fasta/kernel/expression_generator_helper.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/expression_generator_helper.dart
@@ -126,7 +126,8 @@
List<UnresolvedType> typeArguments,
int charOffset,
Constness constness,
- {bool isTypeArgumentsInForest = false});
+ {bool isTypeArgumentsInForest = false,
+ TypeDeclarationBuilder typeAliasBuilder});
UnresolvedType validateTypeUse(UnresolvedType unresolved,
{bool nonInstanceAccessIsError, bool allowPotentiallyConstantType});
diff --git a/pkg/front_end/test/explicit_creation_git_test.dart b/pkg/front_end/test/explicit_creation_git_test.dart
index a0378d2..9229e49 100644
--- a/pkg/front_end/test/explicit_creation_git_test.dart
+++ b/pkg/front_end/test/explicit_creation_git_test.dart
@@ -237,7 +237,8 @@
List<UnresolvedType> typeArguments,
int charOffset,
Constness constness,
- {bool isTypeArgumentsInForest = false}) {
+ {bool isTypeArgumentsInForest = false,
+ TypeDeclarationBuilder typeAliasBuilder}) {
Token maybeNewOrConst = nameToken.previous;
bool doReport = true;
if (maybeNewOrConst is KeywordToken) {
diff --git a/pkg/front_end/testcases/nonfunction_type_aliases/issue45519.dart b/pkg/front_end/testcases/nonfunction_type_aliases/issue45519.dart
new file mode 100644
index 0000000..9a5768c
--- /dev/null
+++ b/pkg/front_end/testcases/nonfunction_type_aliases/issue45519.dart
@@ -0,0 +1,13 @@
+// Copyright (c) 2021, 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.
+
+class C<X> {}
+typedef G<X> = X Function(X);
+typedef A<X extends G<C<X>>> = C<X>;
+
+test() {
+ A a = throw 42; // Error.
+}
+
+main() {}
diff --git a/pkg/front_end/testcases/nonfunction_type_aliases/issue45519.dart.strong.expect b/pkg/front_end/testcases/nonfunction_type_aliases/issue45519.dart.strong.expect
new file mode 100644
index 0000000..20cf2a0a
--- /dev/null
+++ b/pkg/front_end/testcases/nonfunction_type_aliases/issue45519.dart.strong.expect
@@ -0,0 +1,31 @@
+library /*isNonNullableByDefault*/;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519.dart:10:3: Error: Inferred type argument 'C<dynamic> Function(C<dynamic>)' doesn't conform to the bound 'C<X> Function(C<X>)' of the type variable 'X' on 'A'.
+// - 'C' is from 'pkg/front_end/testcases/nonfunction_type_aliases/issue45519.dart'.
+// Try specifying type arguments explicitly so that they conform to the bounds.
+// A a = throw 42; // Error.
+// ^
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519.dart:7:11: Context: This is the type variable whose bound isn't conformed to.
+// typedef A<X extends G<C<X>>> = C<X>;
+// ^
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519.dart:10:3: Context: If you want 'A<C<dynamic> Function(C<dynamic>)>' to be a super-bounded type, note that the inverted type 'A<G<C<Never>>>' must then satisfy its bounds, which it does not.
+// - 'C' is from 'pkg/front_end/testcases/nonfunction_type_aliases/issue45519.dart'.
+// A a = throw 42; // Error.
+// ^
+//
+import self as self;
+import "dart:core" as core;
+
+typedef G<invariant X extends core::Object? = dynamic> = (X%) → X%;
+typedef A<X extends (self::C<X>) → self::C<X> = (self::C<dynamic>) → self::C<dynamic>> = self::C<X>;
+class C<X extends core::Object? = dynamic> extends core::Object {
+ synthetic constructor •() → self::C<self::C::X%>
+ : super core::Object::•()
+ ;
+}
+static method test() → dynamic {
+ self::C<(self::C<dynamic>) → self::C<dynamic>> a = throw 42;
+}
+static method main() → dynamic {}
diff --git a/pkg/front_end/testcases/nonfunction_type_aliases/issue45519.dart.strong.transformed.expect b/pkg/front_end/testcases/nonfunction_type_aliases/issue45519.dart.strong.transformed.expect
new file mode 100644
index 0000000..20cf2a0a
--- /dev/null
+++ b/pkg/front_end/testcases/nonfunction_type_aliases/issue45519.dart.strong.transformed.expect
@@ -0,0 +1,31 @@
+library /*isNonNullableByDefault*/;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519.dart:10:3: Error: Inferred type argument 'C<dynamic> Function(C<dynamic>)' doesn't conform to the bound 'C<X> Function(C<X>)' of the type variable 'X' on 'A'.
+// - 'C' is from 'pkg/front_end/testcases/nonfunction_type_aliases/issue45519.dart'.
+// Try specifying type arguments explicitly so that they conform to the bounds.
+// A a = throw 42; // Error.
+// ^
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519.dart:7:11: Context: This is the type variable whose bound isn't conformed to.
+// typedef A<X extends G<C<X>>> = C<X>;
+// ^
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519.dart:10:3: Context: If you want 'A<C<dynamic> Function(C<dynamic>)>' to be a super-bounded type, note that the inverted type 'A<G<C<Never>>>' must then satisfy its bounds, which it does not.
+// - 'C' is from 'pkg/front_end/testcases/nonfunction_type_aliases/issue45519.dart'.
+// A a = throw 42; // Error.
+// ^
+//
+import self as self;
+import "dart:core" as core;
+
+typedef G<invariant X extends core::Object? = dynamic> = (X%) → X%;
+typedef A<X extends (self::C<X>) → self::C<X> = (self::C<dynamic>) → self::C<dynamic>> = self::C<X>;
+class C<X extends core::Object? = dynamic> extends core::Object {
+ synthetic constructor •() → self::C<self::C::X%>
+ : super core::Object::•()
+ ;
+}
+static method test() → dynamic {
+ self::C<(self::C<dynamic>) → self::C<dynamic>> a = throw 42;
+}
+static method main() → dynamic {}
diff --git a/pkg/front_end/testcases/nonfunction_type_aliases/issue45519.dart.textual_outline.expect b/pkg/front_end/testcases/nonfunction_type_aliases/issue45519.dart.textual_outline.expect
new file mode 100644
index 0000000..0d8eec5
--- /dev/null
+++ b/pkg/front_end/testcases/nonfunction_type_aliases/issue45519.dart.textual_outline.expect
@@ -0,0 +1,5 @@
+class C<X> {}
+typedef G<X> = X Function(X);
+typedef A<X extends G<C<X>>> = C<X>;
+test() {}
+main() {}
diff --git a/pkg/front_end/testcases/nonfunction_type_aliases/issue45519.dart.weak.expect b/pkg/front_end/testcases/nonfunction_type_aliases/issue45519.dart.weak.expect
new file mode 100644
index 0000000..20cf2a0a
--- /dev/null
+++ b/pkg/front_end/testcases/nonfunction_type_aliases/issue45519.dart.weak.expect
@@ -0,0 +1,31 @@
+library /*isNonNullableByDefault*/;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519.dart:10:3: Error: Inferred type argument 'C<dynamic> Function(C<dynamic>)' doesn't conform to the bound 'C<X> Function(C<X>)' of the type variable 'X' on 'A'.
+// - 'C' is from 'pkg/front_end/testcases/nonfunction_type_aliases/issue45519.dart'.
+// Try specifying type arguments explicitly so that they conform to the bounds.
+// A a = throw 42; // Error.
+// ^
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519.dart:7:11: Context: This is the type variable whose bound isn't conformed to.
+// typedef A<X extends G<C<X>>> = C<X>;
+// ^
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519.dart:10:3: Context: If you want 'A<C<dynamic> Function(C<dynamic>)>' to be a super-bounded type, note that the inverted type 'A<G<C<Never>>>' must then satisfy its bounds, which it does not.
+// - 'C' is from 'pkg/front_end/testcases/nonfunction_type_aliases/issue45519.dart'.
+// A a = throw 42; // Error.
+// ^
+//
+import self as self;
+import "dart:core" as core;
+
+typedef G<invariant X extends core::Object? = dynamic> = (X%) → X%;
+typedef A<X extends (self::C<X>) → self::C<X> = (self::C<dynamic>) → self::C<dynamic>> = self::C<X>;
+class C<X extends core::Object? = dynamic> extends core::Object {
+ synthetic constructor •() → self::C<self::C::X%>
+ : super core::Object::•()
+ ;
+}
+static method test() → dynamic {
+ self::C<(self::C<dynamic>) → self::C<dynamic>> a = throw 42;
+}
+static method main() → dynamic {}
diff --git a/pkg/front_end/testcases/nonfunction_type_aliases/issue45519.dart.weak.outline.expect b/pkg/front_end/testcases/nonfunction_type_aliases/issue45519.dart.weak.outline.expect
new file mode 100644
index 0000000..2c7e28a
--- /dev/null
+++ b/pkg/front_end/testcases/nonfunction_type_aliases/issue45519.dart.weak.outline.expect
@@ -0,0 +1,14 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+
+typedef G<invariant X extends core::Object? = dynamic> = (X%) → X%;
+typedef A<X extends (self::C<X>) → self::C<X> = (self::C<dynamic>) → self::C<dynamic>> = self::C<X>;
+class C<X extends core::Object? = dynamic> extends core::Object {
+ synthetic constructor •() → self::C<self::C::X%>
+ ;
+}
+static method test() → dynamic
+ ;
+static method main() → dynamic
+ ;
diff --git a/pkg/front_end/testcases/nonfunction_type_aliases/issue45519.dart.weak.transformed.expect b/pkg/front_end/testcases/nonfunction_type_aliases/issue45519.dart.weak.transformed.expect
new file mode 100644
index 0000000..20cf2a0a
--- /dev/null
+++ b/pkg/front_end/testcases/nonfunction_type_aliases/issue45519.dart.weak.transformed.expect
@@ -0,0 +1,31 @@
+library /*isNonNullableByDefault*/;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519.dart:10:3: Error: Inferred type argument 'C<dynamic> Function(C<dynamic>)' doesn't conform to the bound 'C<X> Function(C<X>)' of the type variable 'X' on 'A'.
+// - 'C' is from 'pkg/front_end/testcases/nonfunction_type_aliases/issue45519.dart'.
+// Try specifying type arguments explicitly so that they conform to the bounds.
+// A a = throw 42; // Error.
+// ^
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519.dart:7:11: Context: This is the type variable whose bound isn't conformed to.
+// typedef A<X extends G<C<X>>> = C<X>;
+// ^
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519.dart:10:3: Context: If you want 'A<C<dynamic> Function(C<dynamic>)>' to be a super-bounded type, note that the inverted type 'A<G<C<Never>>>' must then satisfy its bounds, which it does not.
+// - 'C' is from 'pkg/front_end/testcases/nonfunction_type_aliases/issue45519.dart'.
+// A a = throw 42; // Error.
+// ^
+//
+import self as self;
+import "dart:core" as core;
+
+typedef G<invariant X extends core::Object? = dynamic> = (X%) → X%;
+typedef A<X extends (self::C<X>) → self::C<X> = (self::C<dynamic>) → self::C<dynamic>> = self::C<X>;
+class C<X extends core::Object? = dynamic> extends core::Object {
+ synthetic constructor •() → self::C<self::C::X%>
+ : super core::Object::•()
+ ;
+}
+static method test() → dynamic {
+ self::C<(self::C<dynamic>) → self::C<dynamic>> a = throw 42;
+}
+static method main() → dynamic {}
diff --git a/pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart b/pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart
new file mode 100644
index 0000000..de364ca
--- /dev/null
+++ b/pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart
@@ -0,0 +1,28 @@
+// Copyright (c) 2021, 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.
+
+class C<X> {
+ factory C() => new C.foo();
+ C.foo() {}
+ factory C.bar() = C;
+}
+class D<X> {
+ D();
+ factory D.foo() => new D();
+ factory D.bar() = D;
+}
+typedef G<X> = X Function(X);
+typedef A<X extends G<C<X>>> = C<X>;
+typedef B<X extends G<D<X>>> = D<X>;
+
+test() {
+ A(); // Error.
+ A.foo(); // Error.
+ A.bar(); // Error.
+ B(); // Error.
+ B.foo(); // Error.
+ B.bar(); // Error.
+}
+
+main() {}
diff --git a/pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart.strong.expect b/pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart.strong.expect
new file mode 100644
index 0000000..2e82e80
--- /dev/null
+++ b/pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart.strong.expect
@@ -0,0 +1,128 @@
+library /*isNonNullableByDefault*/;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:21:5: Error: Type argument 'C<Object?> Function(C<Never>)' doesn't conform to the bound 'C<X> Function(C<X>)' of the type variable 'X' on 'A'.
+// - 'C' is from 'pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart'.
+// - 'Object' is from 'dart:core'.
+// Try changing type arguments so that they conform to the bounds.
+// A.foo(); // Error.
+// ^
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:16:11: Context: This is the type variable whose bound isn't conformed to.
+// typedef A<X extends G<C<X>>> = C<X>;
+// ^
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:21:5: Context: If you want 'A<C<Object?> Function(C<Never>)>' to be a super-bounded type, note that the inverted type 'A<G<C<Never>>>' must then satisfy its bounds, which it does not.
+// - 'C' is from 'pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart'.
+// - 'Object' is from 'dart:core'.
+// A.foo(); // Error.
+// ^
+//
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:23:3: Error: Type argument 'D<Object?> Function(D<Never>)' doesn't conform to the bound 'D<X> Function(D<X>)' of the type variable 'X' on 'B'.
+// - 'D' is from 'pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart'.
+// - 'Object' is from 'dart:core'.
+// Try changing type arguments so that they conform to the bounds.
+// B(); // Error.
+// ^
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:17:11: Context: This is the type variable whose bound isn't conformed to.
+// typedef B<X extends G<D<X>>> = D<X>;
+// ^
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:23:3: Context: If you want 'B<D<Object?> Function(D<Never>)>' to be a super-bounded type, note that the inverted type 'B<G<D<Never>>>' must then satisfy its bounds, which it does not.
+// - 'D' is from 'pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart'.
+// - 'Object' is from 'dart:core'.
+// B(); // Error.
+// ^
+//
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:20:3: Error: Type argument 'C<Object?> Function(C<Never>)' doesn't conform to the bound 'C<X> Function(C<X>)' of the type variable 'X' on 'A'.
+// - 'C' is from 'pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart'.
+// - 'Object' is from 'dart:core'.
+// Try changing type arguments so that they conform to the bounds.
+// A(); // Error.
+// ^
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:16:11: Context: This is the type variable whose bound isn't conformed to.
+// typedef A<X extends G<C<X>>> = C<X>;
+// ^
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:20:3: Context: If you want 'A<C<Object?> Function(C<Never>)>' to be a super-bounded type, note that the inverted type 'A<G<C<Never>>>' must then satisfy its bounds, which it does not.
+// - 'C' is from 'pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart'.
+// - 'Object' is from 'dart:core'.
+// A(); // Error.
+// ^
+//
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:22:5: Error: Type argument 'C<Object?> Function(C<Never>)' doesn't conform to the bound 'C<X> Function(C<X>)' of the type variable 'X' on 'A'.
+// - 'C' is from 'pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart'.
+// - 'Object' is from 'dart:core'.
+// Try changing type arguments so that they conform to the bounds.
+// A.bar(); // Error.
+// ^
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:16:11: Context: This is the type variable whose bound isn't conformed to.
+// typedef A<X extends G<C<X>>> = C<X>;
+// ^
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:22:5: Context: If you want 'A<C<Object?> Function(C<Never>)>' to be a super-bounded type, note that the inverted type 'A<G<C<Never>>>' must then satisfy its bounds, which it does not.
+// - 'C' is from 'pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart'.
+// - 'Object' is from 'dart:core'.
+// A.bar(); // Error.
+// ^
+//
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:24:5: Error: Type argument 'D<Object?> Function(D<Never>)' doesn't conform to the bound 'D<X> Function(D<X>)' of the type variable 'X' on 'B'.
+// - 'D' is from 'pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart'.
+// - 'Object' is from 'dart:core'.
+// Try changing type arguments so that they conform to the bounds.
+// B.foo(); // Error.
+// ^
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:17:11: Context: This is the type variable whose bound isn't conformed to.
+// typedef B<X extends G<D<X>>> = D<X>;
+// ^
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:24:5: Context: If you want 'B<D<Object?> Function(D<Never>)>' to be a super-bounded type, note that the inverted type 'B<G<D<Never>>>' must then satisfy its bounds, which it does not.
+// - 'D' is from 'pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart'.
+// - 'Object' is from 'dart:core'.
+// B.foo(); // Error.
+// ^
+//
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:25:5: Error: Type argument 'D<Object?> Function(D<Never>)' doesn't conform to the bound 'D<X> Function(D<X>)' of the type variable 'X' on 'B'.
+// - 'D' is from 'pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart'.
+// - 'Object' is from 'dart:core'.
+// Try changing type arguments so that they conform to the bounds.
+// B.bar(); // Error.
+// ^
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:17:11: Context: This is the type variable whose bound isn't conformed to.
+// typedef B<X extends G<D<X>>> = D<X>;
+// ^
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:25:5: Context: If you want 'B<D<Object?> Function(D<Never>)>' to be a super-bounded type, note that the inverted type 'B<G<D<Never>>>' must then satisfy its bounds, which it does not.
+// - 'D' is from 'pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart'.
+// - 'Object' is from 'dart:core'.
+// B.bar(); // Error.
+// ^
+//
+import self as self;
+import "dart:core" as core;
+
+typedef G<invariant X extends core::Object? = dynamic> = (X%) → X%;
+typedef A<X extends (self::C<X>) → self::C<X> = (self::C<dynamic>) → self::C<dynamic>> = self::C<X>;
+typedef B<X extends (self::D<X>) → self::D<X> = (self::D<dynamic>) → self::D<dynamic>> = self::D<X>;
+class C<X extends core::Object? = dynamic> extends core::Object {
+ static final field dynamic _redirecting# = <dynamic>[self::C::bar]/*isLegacy*/;
+ constructor foo() → self::C<self::C::X%>
+ : super core::Object::•() {}
+ static factory •<X extends core::Object? = dynamic>() → self::C<self::C::•::X%>
+ return new self::C::foo<self::C::•::X%>();
+ static factory bar<X extends core::Object? = dynamic>() → self::C<self::C::bar::X%>
+ let dynamic #redirecting_factory = self::C::• in let self::C::bar::X% #typeArg0 = null in invalid-expression;
+}
+class D<X extends core::Object? = dynamic> extends core::Object {
+ static final field dynamic _redirecting# = <dynamic>[self::D::bar]/*isLegacy*/;
+ constructor •() → self::D<self::D::X%>
+ : super core::Object::•()
+ ;
+ static factory foo<X extends core::Object? = dynamic>() → self::D<self::D::foo::X%>
+ return new self::D::•<self::D::foo::X%>();
+ static factory bar<X extends core::Object? = dynamic>() → self::D<self::D::bar::X%>
+ let dynamic #redirecting_factory = self::D::• in let self::D::bar::X% #typeArg0 = null in invalid-expression;
+}
+static method test() → dynamic {
+ self::C::•<(self::C<Never>) → self::C<core::Object?>>();
+ new self::C::foo<(self::C<Never>) → self::C<core::Object?>>();
+ self::C::•<(self::C<Never>) → self::C<core::Object?>>();
+ new self::D::•<(self::D<Never>) → self::D<core::Object?>>();
+ self::D::foo<(self::D<Never>) → self::D<core::Object?>>();
+ new self::D::•<(self::D<Never>) → self::D<core::Object?>>();
+}
+static method main() → dynamic {}
diff --git a/pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart.strong.transformed.expect b/pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart.strong.transformed.expect
new file mode 100644
index 0000000..e2f8dde
--- /dev/null
+++ b/pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart.strong.transformed.expect
@@ -0,0 +1,128 @@
+library /*isNonNullableByDefault*/;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:21:5: Error: Type argument 'C<Object?> Function(C<Never>)' doesn't conform to the bound 'C<X> Function(C<X>)' of the type variable 'X' on 'A'.
+// - 'C' is from 'pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart'.
+// - 'Object' is from 'dart:core'.
+// Try changing type arguments so that they conform to the bounds.
+// A.foo(); // Error.
+// ^
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:16:11: Context: This is the type variable whose bound isn't conformed to.
+// typedef A<X extends G<C<X>>> = C<X>;
+// ^
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:21:5: Context: If you want 'A<C<Object?> Function(C<Never>)>' to be a super-bounded type, note that the inverted type 'A<G<C<Never>>>' must then satisfy its bounds, which it does not.
+// - 'C' is from 'pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart'.
+// - 'Object' is from 'dart:core'.
+// A.foo(); // Error.
+// ^
+//
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:23:3: Error: Type argument 'D<Object?> Function(D<Never>)' doesn't conform to the bound 'D<X> Function(D<X>)' of the type variable 'X' on 'B'.
+// - 'D' is from 'pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart'.
+// - 'Object' is from 'dart:core'.
+// Try changing type arguments so that they conform to the bounds.
+// B(); // Error.
+// ^
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:17:11: Context: This is the type variable whose bound isn't conformed to.
+// typedef B<X extends G<D<X>>> = D<X>;
+// ^
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:23:3: Context: If you want 'B<D<Object?> Function(D<Never>)>' to be a super-bounded type, note that the inverted type 'B<G<D<Never>>>' must then satisfy its bounds, which it does not.
+// - 'D' is from 'pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart'.
+// - 'Object' is from 'dart:core'.
+// B(); // Error.
+// ^
+//
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:20:3: Error: Type argument 'C<Object?> Function(C<Never>)' doesn't conform to the bound 'C<X> Function(C<X>)' of the type variable 'X' on 'A'.
+// - 'C' is from 'pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart'.
+// - 'Object' is from 'dart:core'.
+// Try changing type arguments so that they conform to the bounds.
+// A(); // Error.
+// ^
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:16:11: Context: This is the type variable whose bound isn't conformed to.
+// typedef A<X extends G<C<X>>> = C<X>;
+// ^
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:20:3: Context: If you want 'A<C<Object?> Function(C<Never>)>' to be a super-bounded type, note that the inverted type 'A<G<C<Never>>>' must then satisfy its bounds, which it does not.
+// - 'C' is from 'pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart'.
+// - 'Object' is from 'dart:core'.
+// A(); // Error.
+// ^
+//
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:22:5: Error: Type argument 'C<Object?> Function(C<Never>)' doesn't conform to the bound 'C<X> Function(C<X>)' of the type variable 'X' on 'A'.
+// - 'C' is from 'pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart'.
+// - 'Object' is from 'dart:core'.
+// Try changing type arguments so that they conform to the bounds.
+// A.bar(); // Error.
+// ^
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:16:11: Context: This is the type variable whose bound isn't conformed to.
+// typedef A<X extends G<C<X>>> = C<X>;
+// ^
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:22:5: Context: If you want 'A<C<Object?> Function(C<Never>)>' to be a super-bounded type, note that the inverted type 'A<G<C<Never>>>' must then satisfy its bounds, which it does not.
+// - 'C' is from 'pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart'.
+// - 'Object' is from 'dart:core'.
+// A.bar(); // Error.
+// ^
+//
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:24:5: Error: Type argument 'D<Object?> Function(D<Never>)' doesn't conform to the bound 'D<X> Function(D<X>)' of the type variable 'X' on 'B'.
+// - 'D' is from 'pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart'.
+// - 'Object' is from 'dart:core'.
+// Try changing type arguments so that they conform to the bounds.
+// B.foo(); // Error.
+// ^
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:17:11: Context: This is the type variable whose bound isn't conformed to.
+// typedef B<X extends G<D<X>>> = D<X>;
+// ^
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:24:5: Context: If you want 'B<D<Object?> Function(D<Never>)>' to be a super-bounded type, note that the inverted type 'B<G<D<Never>>>' must then satisfy its bounds, which it does not.
+// - 'D' is from 'pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart'.
+// - 'Object' is from 'dart:core'.
+// B.foo(); // Error.
+// ^
+//
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:25:5: Error: Type argument 'D<Object?> Function(D<Never>)' doesn't conform to the bound 'D<X> Function(D<X>)' of the type variable 'X' on 'B'.
+// - 'D' is from 'pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart'.
+// - 'Object' is from 'dart:core'.
+// Try changing type arguments so that they conform to the bounds.
+// B.bar(); // Error.
+// ^
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:17:11: Context: This is the type variable whose bound isn't conformed to.
+// typedef B<X extends G<D<X>>> = D<X>;
+// ^
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:25:5: Context: If you want 'B<D<Object?> Function(D<Never>)>' to be a super-bounded type, note that the inverted type 'B<G<D<Never>>>' must then satisfy its bounds, which it does not.
+// - 'D' is from 'pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart'.
+// - 'Object' is from 'dart:core'.
+// B.bar(); // Error.
+// ^
+//
+import self as self;
+import "dart:core" as core;
+
+typedef G<invariant X extends core::Object? = dynamic> = (X%) → X%;
+typedef A<X extends (self::C<X>) → self::C<X> = (self::C<dynamic>) → self::C<dynamic>> = self::C<X>;
+typedef B<X extends (self::D<X>) → self::D<X> = (self::D<dynamic>) → self::D<dynamic>> = self::D<X>;
+class C<X extends core::Object? = dynamic> extends core::Object {
+ static final field dynamic _redirecting# = <dynamic>[self::C::bar]/*isLegacy*/;
+ constructor foo() → self::C<self::C::X%>
+ : super core::Object::•() {}
+ static factory •<X extends core::Object? = dynamic>() → self::C<self::C::•::X%>
+ return new self::C::foo<self::C::•::X%>();
+ static factory bar<X extends core::Object? = dynamic>() → self::C<self::C::bar::X%>
+ let <X extends core::Object? = dynamic>() → self::C<X%> #redirecting_factory = self::C::• in let self::C::bar::X% #typeArg0 = null in invalid-expression;
+}
+class D<X extends core::Object? = dynamic> extends core::Object {
+ static final field dynamic _redirecting# = <dynamic>[self::D::bar]/*isLegacy*/;
+ constructor •() → self::D<self::D::X%>
+ : super core::Object::•()
+ ;
+ static factory foo<X extends core::Object? = dynamic>() → self::D<self::D::foo::X%>
+ return new self::D::•<self::D::foo::X%>();
+ static factory bar<X extends core::Object? = dynamic>() → self::D<self::D::bar::X%>
+ let Never #redirecting_factory = self::D::• in let self::D::bar::X% #typeArg0 = null in invalid-expression;
+}
+static method test() → dynamic {
+ self::C::•<(self::C<Never>) → self::C<core::Object?>>();
+ new self::C::foo<(self::C<Never>) → self::C<core::Object?>>();
+ self::C::•<(self::C<Never>) → self::C<core::Object?>>();
+ new self::D::•<(self::D<Never>) → self::D<core::Object?>>();
+ self::D::foo<(self::D<Never>) → self::D<core::Object?>>();
+ new self::D::•<(self::D<Never>) → self::D<core::Object?>>();
+}
+static method main() → dynamic {}
diff --git a/pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart.textual_outline.expect b/pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart.textual_outline.expect
new file mode 100644
index 0000000..329c512
--- /dev/null
+++ b/pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart.textual_outline.expect
@@ -0,0 +1,15 @@
+class C<X> {
+ factory C() => new C.foo();
+ C.foo() {}
+ factory C.bar() = C;
+}
+class D<X> {
+ D();
+ factory D.foo() => new D();
+ factory D.bar() = D;
+}
+typedef G<X> = X Function(X);
+typedef A<X extends G<C<X>>> = C<X>;
+typedef B<X extends G<D<X>>> = D<X>;
+test() {}
+main() {}
diff --git a/pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart.weak.expect b/pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart.weak.expect
new file mode 100644
index 0000000..2e82e80
--- /dev/null
+++ b/pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart.weak.expect
@@ -0,0 +1,128 @@
+library /*isNonNullableByDefault*/;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:21:5: Error: Type argument 'C<Object?> Function(C<Never>)' doesn't conform to the bound 'C<X> Function(C<X>)' of the type variable 'X' on 'A'.
+// - 'C' is from 'pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart'.
+// - 'Object' is from 'dart:core'.
+// Try changing type arguments so that they conform to the bounds.
+// A.foo(); // Error.
+// ^
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:16:11: Context: This is the type variable whose bound isn't conformed to.
+// typedef A<X extends G<C<X>>> = C<X>;
+// ^
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:21:5: Context: If you want 'A<C<Object?> Function(C<Never>)>' to be a super-bounded type, note that the inverted type 'A<G<C<Never>>>' must then satisfy its bounds, which it does not.
+// - 'C' is from 'pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart'.
+// - 'Object' is from 'dart:core'.
+// A.foo(); // Error.
+// ^
+//
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:23:3: Error: Type argument 'D<Object?> Function(D<Never>)' doesn't conform to the bound 'D<X> Function(D<X>)' of the type variable 'X' on 'B'.
+// - 'D' is from 'pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart'.
+// - 'Object' is from 'dart:core'.
+// Try changing type arguments so that they conform to the bounds.
+// B(); // Error.
+// ^
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:17:11: Context: This is the type variable whose bound isn't conformed to.
+// typedef B<X extends G<D<X>>> = D<X>;
+// ^
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:23:3: Context: If you want 'B<D<Object?> Function(D<Never>)>' to be a super-bounded type, note that the inverted type 'B<G<D<Never>>>' must then satisfy its bounds, which it does not.
+// - 'D' is from 'pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart'.
+// - 'Object' is from 'dart:core'.
+// B(); // Error.
+// ^
+//
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:20:3: Error: Type argument 'C<Object?> Function(C<Never>)' doesn't conform to the bound 'C<X> Function(C<X>)' of the type variable 'X' on 'A'.
+// - 'C' is from 'pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart'.
+// - 'Object' is from 'dart:core'.
+// Try changing type arguments so that they conform to the bounds.
+// A(); // Error.
+// ^
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:16:11: Context: This is the type variable whose bound isn't conformed to.
+// typedef A<X extends G<C<X>>> = C<X>;
+// ^
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:20:3: Context: If you want 'A<C<Object?> Function(C<Never>)>' to be a super-bounded type, note that the inverted type 'A<G<C<Never>>>' must then satisfy its bounds, which it does not.
+// - 'C' is from 'pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart'.
+// - 'Object' is from 'dart:core'.
+// A(); // Error.
+// ^
+//
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:22:5: Error: Type argument 'C<Object?> Function(C<Never>)' doesn't conform to the bound 'C<X> Function(C<X>)' of the type variable 'X' on 'A'.
+// - 'C' is from 'pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart'.
+// - 'Object' is from 'dart:core'.
+// Try changing type arguments so that they conform to the bounds.
+// A.bar(); // Error.
+// ^
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:16:11: Context: This is the type variable whose bound isn't conformed to.
+// typedef A<X extends G<C<X>>> = C<X>;
+// ^
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:22:5: Context: If you want 'A<C<Object?> Function(C<Never>)>' to be a super-bounded type, note that the inverted type 'A<G<C<Never>>>' must then satisfy its bounds, which it does not.
+// - 'C' is from 'pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart'.
+// - 'Object' is from 'dart:core'.
+// A.bar(); // Error.
+// ^
+//
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:24:5: Error: Type argument 'D<Object?> Function(D<Never>)' doesn't conform to the bound 'D<X> Function(D<X>)' of the type variable 'X' on 'B'.
+// - 'D' is from 'pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart'.
+// - 'Object' is from 'dart:core'.
+// Try changing type arguments so that they conform to the bounds.
+// B.foo(); // Error.
+// ^
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:17:11: Context: This is the type variable whose bound isn't conformed to.
+// typedef B<X extends G<D<X>>> = D<X>;
+// ^
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:24:5: Context: If you want 'B<D<Object?> Function(D<Never>)>' to be a super-bounded type, note that the inverted type 'B<G<D<Never>>>' must then satisfy its bounds, which it does not.
+// - 'D' is from 'pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart'.
+// - 'Object' is from 'dart:core'.
+// B.foo(); // Error.
+// ^
+//
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:25:5: Error: Type argument 'D<Object?> Function(D<Never>)' doesn't conform to the bound 'D<X> Function(D<X>)' of the type variable 'X' on 'B'.
+// - 'D' is from 'pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart'.
+// - 'Object' is from 'dart:core'.
+// Try changing type arguments so that they conform to the bounds.
+// B.bar(); // Error.
+// ^
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:17:11: Context: This is the type variable whose bound isn't conformed to.
+// typedef B<X extends G<D<X>>> = D<X>;
+// ^
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:25:5: Context: If you want 'B<D<Object?> Function(D<Never>)>' to be a super-bounded type, note that the inverted type 'B<G<D<Never>>>' must then satisfy its bounds, which it does not.
+// - 'D' is from 'pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart'.
+// - 'Object' is from 'dart:core'.
+// B.bar(); // Error.
+// ^
+//
+import self as self;
+import "dart:core" as core;
+
+typedef G<invariant X extends core::Object? = dynamic> = (X%) → X%;
+typedef A<X extends (self::C<X>) → self::C<X> = (self::C<dynamic>) → self::C<dynamic>> = self::C<X>;
+typedef B<X extends (self::D<X>) → self::D<X> = (self::D<dynamic>) → self::D<dynamic>> = self::D<X>;
+class C<X extends core::Object? = dynamic> extends core::Object {
+ static final field dynamic _redirecting# = <dynamic>[self::C::bar]/*isLegacy*/;
+ constructor foo() → self::C<self::C::X%>
+ : super core::Object::•() {}
+ static factory •<X extends core::Object? = dynamic>() → self::C<self::C::•::X%>
+ return new self::C::foo<self::C::•::X%>();
+ static factory bar<X extends core::Object? = dynamic>() → self::C<self::C::bar::X%>
+ let dynamic #redirecting_factory = self::C::• in let self::C::bar::X% #typeArg0 = null in invalid-expression;
+}
+class D<X extends core::Object? = dynamic> extends core::Object {
+ static final field dynamic _redirecting# = <dynamic>[self::D::bar]/*isLegacy*/;
+ constructor •() → self::D<self::D::X%>
+ : super core::Object::•()
+ ;
+ static factory foo<X extends core::Object? = dynamic>() → self::D<self::D::foo::X%>
+ return new self::D::•<self::D::foo::X%>();
+ static factory bar<X extends core::Object? = dynamic>() → self::D<self::D::bar::X%>
+ let dynamic #redirecting_factory = self::D::• in let self::D::bar::X% #typeArg0 = null in invalid-expression;
+}
+static method test() → dynamic {
+ self::C::•<(self::C<Never>) → self::C<core::Object?>>();
+ new self::C::foo<(self::C<Never>) → self::C<core::Object?>>();
+ self::C::•<(self::C<Never>) → self::C<core::Object?>>();
+ new self::D::•<(self::D<Never>) → self::D<core::Object?>>();
+ self::D::foo<(self::D<Never>) → self::D<core::Object?>>();
+ new self::D::•<(self::D<Never>) → self::D<core::Object?>>();
+}
+static method main() → dynamic {}
diff --git a/pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart.weak.outline.expect b/pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart.weak.outline.expect
new file mode 100644
index 0000000..5bcbed2
--- /dev/null
+++ b/pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart.weak.outline.expect
@@ -0,0 +1,29 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+
+typedef G<invariant X extends core::Object? = dynamic> = (X%) → X%;
+typedef A<X extends (self::C<X>) → self::C<X> = (self::C<dynamic>) → self::C<dynamic>> = self::C<X>;
+typedef B<X extends (self::D<X>) → self::D<X> = (self::D<dynamic>) → self::D<dynamic>> = self::D<X>;
+class C<X extends core::Object? = dynamic> extends core::Object {
+ static final field dynamic _redirecting# = <dynamic>[self::C::bar]/*isLegacy*/;
+ constructor foo() → self::C<self::C::X%>
+ ;
+ static factory •<X extends core::Object? = dynamic>() → self::C<self::C::•::X%>
+ ;
+ static factory bar<X extends core::Object? = dynamic>() → self::C<self::C::bar::X%>
+ let dynamic #redirecting_factory = self::C::• in let self::C::bar::X% #typeArg0 = null in invalid-expression;
+}
+class D<X extends core::Object? = dynamic> extends core::Object {
+ static final field dynamic _redirecting# = <dynamic>[self::D::bar]/*isLegacy*/;
+ constructor •() → self::D<self::D::X%>
+ ;
+ static factory foo<X extends core::Object? = dynamic>() → self::D<self::D::foo::X%>
+ ;
+ static factory bar<X extends core::Object? = dynamic>() → self::D<self::D::bar::X%>
+ let dynamic #redirecting_factory = self::D::• in let self::D::bar::X% #typeArg0 = null in invalid-expression;
+}
+static method test() → dynamic
+ ;
+static method main() → dynamic
+ ;
diff --git a/pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart.weak.transformed.expect b/pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart.weak.transformed.expect
new file mode 100644
index 0000000..e2f8dde
--- /dev/null
+++ b/pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart.weak.transformed.expect
@@ -0,0 +1,128 @@
+library /*isNonNullableByDefault*/;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:21:5: Error: Type argument 'C<Object?> Function(C<Never>)' doesn't conform to the bound 'C<X> Function(C<X>)' of the type variable 'X' on 'A'.
+// - 'C' is from 'pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart'.
+// - 'Object' is from 'dart:core'.
+// Try changing type arguments so that they conform to the bounds.
+// A.foo(); // Error.
+// ^
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:16:11: Context: This is the type variable whose bound isn't conformed to.
+// typedef A<X extends G<C<X>>> = C<X>;
+// ^
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:21:5: Context: If you want 'A<C<Object?> Function(C<Never>)>' to be a super-bounded type, note that the inverted type 'A<G<C<Never>>>' must then satisfy its bounds, which it does not.
+// - 'C' is from 'pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart'.
+// - 'Object' is from 'dart:core'.
+// A.foo(); // Error.
+// ^
+//
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:23:3: Error: Type argument 'D<Object?> Function(D<Never>)' doesn't conform to the bound 'D<X> Function(D<X>)' of the type variable 'X' on 'B'.
+// - 'D' is from 'pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart'.
+// - 'Object' is from 'dart:core'.
+// Try changing type arguments so that they conform to the bounds.
+// B(); // Error.
+// ^
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:17:11: Context: This is the type variable whose bound isn't conformed to.
+// typedef B<X extends G<D<X>>> = D<X>;
+// ^
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:23:3: Context: If you want 'B<D<Object?> Function(D<Never>)>' to be a super-bounded type, note that the inverted type 'B<G<D<Never>>>' must then satisfy its bounds, which it does not.
+// - 'D' is from 'pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart'.
+// - 'Object' is from 'dart:core'.
+// B(); // Error.
+// ^
+//
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:20:3: Error: Type argument 'C<Object?> Function(C<Never>)' doesn't conform to the bound 'C<X> Function(C<X>)' of the type variable 'X' on 'A'.
+// - 'C' is from 'pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart'.
+// - 'Object' is from 'dart:core'.
+// Try changing type arguments so that they conform to the bounds.
+// A(); // Error.
+// ^
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:16:11: Context: This is the type variable whose bound isn't conformed to.
+// typedef A<X extends G<C<X>>> = C<X>;
+// ^
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:20:3: Context: If you want 'A<C<Object?> Function(C<Never>)>' to be a super-bounded type, note that the inverted type 'A<G<C<Never>>>' must then satisfy its bounds, which it does not.
+// - 'C' is from 'pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart'.
+// - 'Object' is from 'dart:core'.
+// A(); // Error.
+// ^
+//
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:22:5: Error: Type argument 'C<Object?> Function(C<Never>)' doesn't conform to the bound 'C<X> Function(C<X>)' of the type variable 'X' on 'A'.
+// - 'C' is from 'pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart'.
+// - 'Object' is from 'dart:core'.
+// Try changing type arguments so that they conform to the bounds.
+// A.bar(); // Error.
+// ^
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:16:11: Context: This is the type variable whose bound isn't conformed to.
+// typedef A<X extends G<C<X>>> = C<X>;
+// ^
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:22:5: Context: If you want 'A<C<Object?> Function(C<Never>)>' to be a super-bounded type, note that the inverted type 'A<G<C<Never>>>' must then satisfy its bounds, which it does not.
+// - 'C' is from 'pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart'.
+// - 'Object' is from 'dart:core'.
+// A.bar(); // Error.
+// ^
+//
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:24:5: Error: Type argument 'D<Object?> Function(D<Never>)' doesn't conform to the bound 'D<X> Function(D<X>)' of the type variable 'X' on 'B'.
+// - 'D' is from 'pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart'.
+// - 'Object' is from 'dart:core'.
+// Try changing type arguments so that they conform to the bounds.
+// B.foo(); // Error.
+// ^
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:17:11: Context: This is the type variable whose bound isn't conformed to.
+// typedef B<X extends G<D<X>>> = D<X>;
+// ^
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:24:5: Context: If you want 'B<D<Object?> Function(D<Never>)>' to be a super-bounded type, note that the inverted type 'B<G<D<Never>>>' must then satisfy its bounds, which it does not.
+// - 'D' is from 'pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart'.
+// - 'Object' is from 'dart:core'.
+// B.foo(); // Error.
+// ^
+//
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:25:5: Error: Type argument 'D<Object?> Function(D<Never>)' doesn't conform to the bound 'D<X> Function(D<X>)' of the type variable 'X' on 'B'.
+// - 'D' is from 'pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart'.
+// - 'Object' is from 'dart:core'.
+// Try changing type arguments so that they conform to the bounds.
+// B.bar(); // Error.
+// ^
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:17:11: Context: This is the type variable whose bound isn't conformed to.
+// typedef B<X extends G<D<X>>> = D<X>;
+// ^
+// pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart:25:5: Context: If you want 'B<D<Object?> Function(D<Never>)>' to be a super-bounded type, note that the inverted type 'B<G<D<Never>>>' must then satisfy its bounds, which it does not.
+// - 'D' is from 'pkg/front_end/testcases/nonfunction_type_aliases/issue45519_2.dart'.
+// - 'Object' is from 'dart:core'.
+// B.bar(); // Error.
+// ^
+//
+import self as self;
+import "dart:core" as core;
+
+typedef G<invariant X extends core::Object? = dynamic> = (X%) → X%;
+typedef A<X extends (self::C<X>) → self::C<X> = (self::C<dynamic>) → self::C<dynamic>> = self::C<X>;
+typedef B<X extends (self::D<X>) → self::D<X> = (self::D<dynamic>) → self::D<dynamic>> = self::D<X>;
+class C<X extends core::Object? = dynamic> extends core::Object {
+ static final field dynamic _redirecting# = <dynamic>[self::C::bar]/*isLegacy*/;
+ constructor foo() → self::C<self::C::X%>
+ : super core::Object::•() {}
+ static factory •<X extends core::Object? = dynamic>() → self::C<self::C::•::X%>
+ return new self::C::foo<self::C::•::X%>();
+ static factory bar<X extends core::Object? = dynamic>() → self::C<self::C::bar::X%>
+ let <X extends core::Object? = dynamic>() → self::C<X%> #redirecting_factory = self::C::• in let self::C::bar::X% #typeArg0 = null in invalid-expression;
+}
+class D<X extends core::Object? = dynamic> extends core::Object {
+ static final field dynamic _redirecting# = <dynamic>[self::D::bar]/*isLegacy*/;
+ constructor •() → self::D<self::D::X%>
+ : super core::Object::•()
+ ;
+ static factory foo<X extends core::Object? = dynamic>() → self::D<self::D::foo::X%>
+ return new self::D::•<self::D::foo::X%>();
+ static factory bar<X extends core::Object? = dynamic>() → self::D<self::D::bar::X%>
+ let Never #redirecting_factory = self::D::• in let self::D::bar::X% #typeArg0 = null in invalid-expression;
+}
+static method test() → dynamic {
+ self::C::•<(self::C<Never>) → self::C<core::Object?>>();
+ new self::C::foo<(self::C<Never>) → self::C<core::Object?>>();
+ self::C::•<(self::C<Never>) → self::C<core::Object?>>();
+ new self::D::•<(self::D<Never>) → self::D<core::Object?>>();
+ self::D::foo<(self::D<Never>) → self::D<core::Object?>>();
+ new self::D::•<(self::D<Never>) → self::D<core::Object?>>();
+}
+static method main() → dynamic {}
diff --git a/pkg/front_end/testcases/textual_outline.status b/pkg/front_end/testcases/textual_outline.status
index 13ce720..4486b80 100644
--- a/pkg/front_end/testcases/textual_outline.status
+++ b/pkg/front_end/testcases/textual_outline.status
@@ -135,6 +135,8 @@
nonfunction_type_aliases/issue42446: FormatterCrash
nonfunction_type_aliases/issue45051: FormatterCrash
nonfunction_type_aliases/issue45464: FormatterCrash
+nonfunction_type_aliases/issue45519: FormatterCrash
+nonfunction_type_aliases/issue45519_2: FormatterCrash
nonfunction_type_aliases/nullable_supertypes: FormatterCrash
nonfunction_type_aliases/old_version: FormatterCrash
nonfunction_type_aliases/unaliased_bounds_checks_in_constructor_calls: FormatterCrash
diff --git a/pkg/kernel/lib/src/bounds_checks.dart b/pkg/kernel/lib/src/bounds_checks.dart
index 13b791c..a0d9fb8 100644
--- a/pkg/kernel/lib/src/bounds_checks.dart
+++ b/pkg/kernel/lib/src/bounds_checks.dart
@@ -685,6 +685,17 @@
}
return createTypedef(node, newNullability, newTypeArguments);
}
+
+ @override
+ DartType? visitFunctionType(FunctionType node, int variance) {
+ // The variance of the Typedef parameters should be taken into account only
+ // when for the NNBD code.
+ if (node.typedefType != null && isNonNullableByDefault) {
+ return node.typedefType!.accept1(this, variance);
+ } else {
+ return super.visitFunctionType(node, variance);
+ }
+ }
}
int computeVariance(TypeParameter typeParameter, DartType type,
diff --git a/runtime/vm/clustered_snapshot.cc b/runtime/vm/clustered_snapshot.cc
index af19ffa..4a63843 100644
--- a/runtime/vm/clustered_snapshot.cc
+++ b/runtime/vm/clustered_snapshot.cc
@@ -3249,13 +3249,6 @@
// In --use-bare-instruction we reduce the extra indirection via the
// [Function] object by storing the entry point directly into the hashmap.
//
- // Currently our AOT compiler will emit megamorphic calls in certain
- // situations (namely in slow-path code of CheckedSmi* instructions).
- //
- // TODO(compiler-team): Change the CheckedSmi* slow path code to use
- // normal switchable calls instead of megamorphic calls. (This is also a
- // memory balance beause [MegamorphicCache]s are per-selector while
- // [ICData] are per-callsite.)
auto& cache = MegamorphicCache::Handle(d->zone());
for (intptr_t i = start_index_; i < stop_index_; ++i) {
cache ^= refs.At(i);
diff --git a/runtime/vm/compiler/aot/aot_call_specializer.cc b/runtime/vm/compiler/aot/aot_call_specializer.cc
index 49bbe05..215bca5 100644
--- a/runtime/vm/compiler/aot/aot_call_specializer.cc
+++ b/runtime/vm/compiler/aot/aot_call_specializer.cc
@@ -448,7 +448,6 @@
CompileType* left_type = left_value->Type();
CompileType* right_type = right_value->Type();
- const bool is_equality_op = Token::IsEqualityOperator(op_kind);
bool has_nullable_int_args =
left_type->IsNullableInt() && right_type->IsNullableInt();
@@ -458,12 +457,6 @@
}
}
- // NOTE: We cannot use strict comparisons if the receiver has an overridden
- // == operator or if either side can be a double, since 1.0 == 1.
- const bool can_use_strict_compare =
- is_equality_op && has_nullable_int_args &&
- (left_type->IsNullableSmi() || right_type->IsNullableSmi());
-
// We only support binary operations if both operands are nullable integers
// or when we can use a cheap strict comparison operation.
if (!has_nullable_int_args) {
@@ -473,43 +466,32 @@
switch (op_kind) {
case Token::kEQ:
case Token::kNE:
- case Token::kLT:
- case Token::kLTE:
- case Token::kGT:
- case Token::kGTE: {
- const bool can_use_equality_compare =
- is_equality_op && left_type->IsInt() && right_type->IsInt();
-
- // We prefer equality compare, since it doesn't require boxing.
- if (!can_use_equality_compare && can_use_strict_compare) {
+ if (left_type->IsNull() || left_type->IsNullableSmi() ||
+ right_type->IsNull() || right_type->IsNullableSmi()) {
replacement = new (Z) StrictCompareInstr(
instr->source(),
(op_kind == Token::kEQ) ? Token::kEQ_STRICT : Token::kNE_STRICT,
left_value->CopyWithType(Z), right_value->CopyWithType(Z),
/*needs_number_check=*/false, DeoptId::kNone);
- break;
- }
-
- if (can_use_equality_compare) {
+ } else {
+ const bool null_aware =
+ left_type->is_nullable() || right_type->is_nullable();
replacement = new (Z) EqualityCompareInstr(
instr->source(), op_kind, left_value->CopyWithType(Z),
right_value->CopyWithType(Z), kMintCid, DeoptId::kNone,
- Instruction::kNotSpeculative);
- } else if (Token::IsRelationalOperator(op_kind)) {
- left_value = PrepareStaticOpInput(left_value, kMintCid, instr);
- right_value = PrepareStaticOpInput(right_value, kMintCid, instr);
- replacement = new (Z) RelationalOpInstr(
- instr->source(), op_kind, left_value, right_value, kMintCid,
- DeoptId::kNone, Instruction::kNotSpeculative);
- } else {
- // TODO(dartbug.com/30480): Figure out how to handle null in
- // equality comparisons.
- replacement = new (Z)
- CheckedSmiComparisonInstr(op_kind, left_value->CopyWithType(Z),
- right_value->CopyWithType(Z), instr);
+ null_aware, Instruction::kNotSpeculative);
}
break;
- }
+ case Token::kLT:
+ case Token::kLTE:
+ case Token::kGT:
+ case Token::kGTE:
+ left_value = PrepareStaticOpInput(left_value, kMintCid, instr);
+ right_value = PrepareStaticOpInput(right_value, kMintCid, instr);
+ replacement = new (Z) RelationalOpInstr(
+ instr->source(), op_kind, left_value, right_value, kMintCid,
+ DeoptId::kNone, Instruction::kNotSpeculative);
+ break;
case Token::kMOD:
replacement = TryOptimizeMod(instr, op_kind, left_value, right_value);
if (replacement != nullptr) break;
@@ -625,7 +607,8 @@
right_value = PrepareStaticOpInput(right_value, kDoubleCid, instr);
replacement = new (Z) EqualityCompareInstr(
instr->source(), op_kind, left_value, right_value, kDoubleCid,
- DeoptId::kNone, Instruction::kNotSpeculative);
+ DeoptId::kNone, /*null_aware=*/false,
+ Instruction::kNotSpeculative);
break;
}
break;
diff --git a/runtime/vm/compiler/aot/aot_call_specializer.h b/runtime/vm/compiler/aot/aot_call_specializer.h
index fd23ebf..b537ae8 100644
--- a/runtime/vm/compiler/aot/aot_call_specializer.h
+++ b/runtime/vm/compiler/aot/aot_call_specializer.h
@@ -59,12 +59,12 @@
const Function& InterfaceTargetForTableDispatch(InstanceCallBaseInstr* call);
// Try to replace a call with a more specialized instruction working on
- // integers (e.g. BinaryInt64OpInstr, CheckedSmiComparisonInstr,
+ // integers (e.g. BinaryInt64OpInstr, EqualityCompareInstr,
// RelationalOpInstr)
bool TryOptimizeIntegerOperation(TemplateDartCall<0>* call, Token::Kind kind);
// Try to replace a call with a more specialized instruction working on
- // doubles (e.g. BinaryDoubleOpInstr, CheckedSmiComparisonInstr,
+ // doubles (e.g. BinaryDoubleOpInstr, EqualityCompareInstr,
// RelationalOpInstr)
bool TryOptimizeDoubleOperation(TemplateDartCall<0>* call, Token::Kind kind);
diff --git a/runtime/vm/compiler/backend/constant_propagator.cc b/runtime/vm/compiler/backend/constant_propagator.cc
index df2b9df..1ba1c36 100644
--- a/runtime/vm/compiler/backend/constant_propagator.cc
+++ b/runtime/vm/compiler/backend/constant_propagator.cc
@@ -1155,15 +1155,6 @@
SetValue(binary_op, non_constant_);
}
-void ConstantPropagator::VisitCheckedSmiOp(CheckedSmiOpInstr* instr) {
- SetValue(instr, non_constant_);
-}
-
-void ConstantPropagator::VisitCheckedSmiComparison(
- CheckedSmiComparisonInstr* instr) {
- SetValue(instr, non_constant_);
-}
-
void ConstantPropagator::VisitBinarySmiOp(BinarySmiOpInstr* instr) {
VisitBinaryIntegerOp(instr);
}
diff --git a/runtime/vm/compiler/backend/flow_graph_compiler.h b/runtime/vm/compiler/backend/flow_graph_compiler.h
index e05c568..6973f62 100644
--- a/runtime/vm/compiler/backend/flow_graph_compiler.h
+++ b/runtime/vm/compiler/backend/flow_graph_compiler.h
@@ -983,8 +983,6 @@
friend class StoreIndexedInstr; // For AddPcRelativeCallStubTarget().
friend class StoreInstanceFieldInstr; // For AddPcRelativeCallStubTarget().
friend class CheckStackOverflowSlowPath; // For pending_deoptimization_env_.
- friend class CheckedSmiSlowPath; // Same.
- friend class CheckedSmiComparisonSlowPath; // Same.
friend class GraphInstrinsicCodeGenScope; // For optimizing_.
// Architecture specific implementation of simple native moves.
diff --git a/runtime/vm/compiler/backend/il.cc b/runtime/vm/compiler/backend/il.cc
index 5c5db00..0bdc1df 100644
--- a/runtime/vm/compiler/backend/il.cc
+++ b/runtime/vm/compiler/backend/il.cc
@@ -2437,79 +2437,6 @@
return op;
}
-Definition* CheckedSmiOpInstr::Canonicalize(FlowGraph* flow_graph) {
- if ((left()->Type()->ToCid() == kSmiCid) &&
- (right()->Type()->ToCid() == kSmiCid)) {
- Definition* replacement = NULL;
- // Operations that can't deoptimize are specialized here: These include
- // bit-wise operators and comparisons. Other arithmetic operations can
- // overflow or divide by 0 and can't be specialized unless we have extra
- // range information.
- switch (op_kind()) {
- case Token::kBIT_AND:
- FALL_THROUGH;
- case Token::kBIT_OR:
- FALL_THROUGH;
- case Token::kBIT_XOR:
- replacement = new BinarySmiOpInstr(
- op_kind(), new Value(left()->definition()),
- new Value(right()->definition()), DeoptId::kNone);
- FALL_THROUGH;
- default:
- break;
- }
- if (replacement != NULL) {
- flow_graph->InsertBefore(this, replacement, env(), FlowGraph::kValue);
- return replacement;
- }
- }
- return this;
-}
-
-ComparisonInstr* CheckedSmiComparisonInstr::CopyWithNewOperands(Value* left,
- Value* right) {
- UNREACHABLE();
- return NULL;
-}
-
-Definition* CheckedSmiComparisonInstr::Canonicalize(FlowGraph* flow_graph) {
- CompileType* left_type = left()->Type();
- CompileType* right_type = right()->Type();
- intptr_t op_cid = kIllegalCid;
- SpeculativeMode speculative_mode = kGuardInputs;
-
- if ((left_type->ToCid() == kSmiCid) && (right_type->ToCid() == kSmiCid)) {
- op_cid = kSmiCid;
- } else if ( // TODO(dartbug.com/30480): handle nullable types here
- left_type->IsNullableInt() && !left_type->is_nullable() &&
- right_type->IsNullableInt() && !right_type->is_nullable()) {
- op_cid = kMintCid;
- speculative_mode = kNotSpeculative;
- }
-
- if (op_cid != kIllegalCid) {
- Definition* replacement = NULL;
- if (Token::IsRelationalOperator(kind())) {
- replacement = new RelationalOpInstr(
- source(), kind(), left()->CopyWithType(), right()->CopyWithType(),
- op_cid, DeoptId::kNone, speculative_mode);
- } else if (Token::IsEqualityOperator(kind())) {
- replacement = new EqualityCompareInstr(
- source(), kind(), left()->CopyWithType(), right()->CopyWithType(),
- op_cid, DeoptId::kNone, speculative_mode);
- }
- if (replacement != NULL) {
- if (FLAG_trace_strong_mode_types && (op_cid == kMintCid)) {
- THR_Print("[Strong mode] Optimization: replacing %s with %s\n",
- ToCString(), replacement->ToCString());
- }
- flow_graph->InsertBefore(this, replacement, env(), FlowGraph::kValue);
- return replacement;
- }
- }
- return this;
-}
-
Definition* BinaryIntegerOpInstr::Canonicalize(FlowGraph* flow_graph) {
// If both operands are constants evaluate this expression. Might
// occur due to load forwarding after constant propagation pass
@@ -3669,6 +3596,30 @@
return replacement;
}
+Definition* EqualityCompareInstr::Canonicalize(FlowGraph* flow_graph) {
+ if (is_null_aware()) {
+ ASSERT(operation_cid() == kMintCid);
+ // Select more efficient instructions based on operand types.
+ CompileType* left_type = left()->Type();
+ CompileType* right_type = right()->Type();
+ if (left_type->IsNull() || left_type->IsNullableSmi() ||
+ right_type->IsNull() || right_type->IsNullableSmi()) {
+ auto replacement = new StrictCompareInstr(
+ source(),
+ (kind() == Token::kEQ) ? Token::kEQ_STRICT : Token::kNE_STRICT,
+ left()->CopyWithType(), right()->CopyWithType(),
+ /*needs_number_check=*/false, DeoptId::kNone);
+ flow_graph->InsertBefore(this, replacement, env(), FlowGraph::kValue);
+ return replacement;
+ } else {
+ if (!left_type->is_nullable() && !right_type->is_nullable()) {
+ set_null_aware(false);
+ }
+ }
+ }
+ return this;
+}
+
Instruction* CheckClassInstr::Canonicalize(FlowGraph* flow_graph) {
const intptr_t value_cid = value()->Type()->ToCid();
if (value_cid == kDynamicCid) {
@@ -5816,7 +5767,8 @@
ComparisonInstr* EqualityCompareInstr::CopyWithNewOperands(Value* new_left,
Value* new_right) {
return new EqualityCompareInstr(source(), kind(), new_left, new_right,
- operation_cid(), deopt_id());
+ operation_cid(), deopt_id(), is_null_aware(),
+ speculative_mode_);
}
ComparisonInstr* RelationalOpInstr::CopyWithNewOperands(Value* new_left,
diff --git a/runtime/vm/compiler/backend/il.h b/runtime/vm/compiler/backend/il.h
index 7befee0..fc05085 100644
--- a/runtime/vm/compiler/backend/il.h
+++ b/runtime/vm/compiler/backend/il.h
@@ -440,8 +440,6 @@
M(AllocateUninitializedContext, _) \
M(CloneContext, _) \
M(BinarySmiOp, kNoGC) \
- M(CheckedSmiComparison, _) \
- M(CheckedSmiOp, _) \
M(BinaryInt32Op, kNoGC) \
M(UnarySmiOp, kNoGC) \
M(UnaryDoubleOp, kNoGC) \
@@ -4518,8 +4516,10 @@
Value* right,
intptr_t cid,
intptr_t deopt_id,
+ bool null_aware = false,
SpeculativeMode speculative_mode = kGuardInputs)
: TemplateComparison(source, kind, deopt_id),
+ null_aware_(null_aware),
speculative_mode_(speculative_mode) {
ASSERT(Token::IsEqualityOperator(kind));
SetInputAt(0, left);
@@ -4535,8 +4535,12 @@
virtual bool ComputeCanDeoptimize() const { return false; }
+ bool is_null_aware() const { return null_aware_; }
+ void set_null_aware(bool value) { null_aware_ = value; }
+
virtual Representation RequiredInputRepresentation(intptr_t idx) const {
ASSERT((idx == 0) || (idx == 1));
+ if (is_null_aware()) return kTagged;
if (operation_cid() == kDoubleCid) return kUnboxedDouble;
if (operation_cid() == kMintCid) return kUnboxedInt64;
return kTagged;
@@ -4548,12 +4552,16 @@
virtual bool AttributesEqual(Instruction* other) const {
return ComparisonInstr::AttributesEqual(other) &&
+ (null_aware_ == other->AsEqualityCompare()->null_aware_) &&
(speculative_mode_ == other->AsEqualityCompare()->speculative_mode_);
}
+ virtual Definition* Canonicalize(FlowGraph* flow_graph);
+
PRINT_OPERANDS_TO_SUPPORT
private:
+ bool null_aware_;
const SpeculativeMode speculative_mode_;
DISALLOW_COPY_AND_ASSIGN(EqualityCompareInstr);
};
@@ -7607,100 +7615,6 @@
DISALLOW_COPY_AND_ASSIGN(UnaryInt64OpInstr);
};
-class CheckedSmiOpInstr : public TemplateDefinition<2, Throws> {
- public:
- CheckedSmiOpInstr(Token::Kind op_kind,
- Value* left,
- Value* right,
- TemplateDartCall<0>* call)
- : TemplateDefinition(call->deopt_id()), call_(call), op_kind_(op_kind) {
- ASSERT(call->type_args_len() == 0);
- ASSERT(!call->IsInstanceCallBase() ||
- call->AsInstanceCallBase()->CanReceiverBeSmiBasedOnInterfaceTarget(
- Thread::Current()->zone()));
-
- SetInputAt(0, left);
- SetInputAt(1, right);
- }
-
- TemplateDartCall<0>* call() const { return call_; }
- Token::Kind op_kind() const { return op_kind_; }
- Value* left() const { return inputs_[0]; }
- Value* right() const { return inputs_[1]; }
-
- virtual bool ComputeCanDeoptimize() const { return false; }
-
- virtual CompileType ComputeType() const;
- virtual bool RecomputeType();
-
- virtual bool HasUnknownSideEffects() const { return true; }
- virtual bool CanCallDart() const { return true; }
-
- virtual Definition* Canonicalize(FlowGraph* flow_graph);
-
- PRINT_OPERANDS_TO_SUPPORT
-
- DECLARE_INSTRUCTION(CheckedSmiOp)
-
- private:
- TemplateDartCall<0>* call_;
- const Token::Kind op_kind_;
- DISALLOW_COPY_AND_ASSIGN(CheckedSmiOpInstr);
-};
-
-class CheckedSmiComparisonInstr : public TemplateComparison<2, Throws> {
- public:
- CheckedSmiComparisonInstr(Token::Kind op_kind,
- Value* left,
- Value* right,
- TemplateDartCall<0>* call)
- : TemplateComparison(call->source(), op_kind, call->deopt_id()),
- call_(call),
- is_negated_(false) {
- ASSERT(call->type_args_len() == 0);
- ASSERT(!call->IsInstanceCallBase() ||
- call->AsInstanceCallBase()->CanReceiverBeSmiBasedOnInterfaceTarget(
- Thread::Current()->zone()));
-
- SetInputAt(0, left);
- SetInputAt(1, right);
- }
-
- TemplateDartCall<0>* call() const { return call_; }
-
- virtual bool ComputeCanDeoptimize() const { return false; }
-
- virtual CompileType ComputeType() const;
-
- virtual Definition* Canonicalize(FlowGraph* flow_graph);
-
- virtual void NegateComparison() {
- ComparisonInstr::NegateComparison();
- is_negated_ = !is_negated_;
- }
-
- bool is_negated() const { return is_negated_; }
-
- virtual bool HasUnknownSideEffects() const { return true; }
- virtual bool CanCallDart() const { return true; }
-
- PRINT_OPERANDS_TO_SUPPORT
-
- DECLARE_INSTRUCTION(CheckedSmiComparison)
-
- virtual void EmitBranchCode(FlowGraphCompiler* compiler, BranchInstr* branch);
-
- virtual Condition EmitComparisonCode(FlowGraphCompiler* compiler,
- BranchLabels labels);
-
- virtual ComparisonInstr* CopyWithNewOperands(Value* left, Value* right);
-
- private:
- TemplateDartCall<0>* call_;
- bool is_negated_;
- DISALLOW_COPY_AND_ASSIGN(CheckedSmiComparisonInstr);
-};
-
class BinaryIntegerOpInstr : public TemplateDefinition<2, NoThrow, Pure> {
public:
BinaryIntegerOpInstr(Token::Kind op_kind,
diff --git a/runtime/vm/compiler/backend/il_arm.cc b/runtime/vm/compiler/backend/il_arm.cc
index 4f8e3aa..c8b681f 100644
--- a/runtime/vm/compiler/backend/il_arm.cc
+++ b/runtime/vm/compiler/backend/il_arm.cc
@@ -832,7 +832,7 @@
__ Bind(&done);
}
-static Condition TokenKindToSmiCondition(Token::Kind kind) {
+static Condition TokenKindToIntCondition(Token::Kind kind) {
switch (kind) {
case Token::kEQ:
return EQ;
@@ -855,6 +855,16 @@
LocationSummary* EqualityCompareInstr::MakeLocationSummary(Zone* zone,
bool opt) const {
const intptr_t kNumInputs = 2;
+ if (is_null_aware()) {
+ const intptr_t kNumTemps = 1;
+ LocationSummary* locs = new (zone)
+ LocationSummary(zone, kNumInputs, kNumTemps, LocationSummary::kNoCall);
+ locs->set_in(0, Location::RequiresRegister());
+ locs->set_in(1, Location::RequiresRegister());
+ locs->set_temp(0, Location::RequiresRegister());
+ locs->set_out(0, Location::RequiresRegister());
+ return locs;
+ }
if (operation_cid() == kMintCid) {
const intptr_t kNumTemps = 0;
LocationSummary* locs = new (zone)
@@ -961,7 +971,7 @@
Location right = locs->in(1);
ASSERT(!left.IsConstant() || !right.IsConstant());
- Condition true_condition = TokenKindToSmiCondition(kind);
+ Condition true_condition = TokenKindToIntCondition(kind);
if (left.IsConstant()) {
__ CompareObject(right.reg(), left.constant());
@@ -974,26 +984,6 @@
return true_condition;
}
-static Condition TokenKindToMintCondition(Token::Kind kind) {
- switch (kind) {
- case Token::kEQ:
- return EQ;
- case Token::kNE:
- return NE;
- case Token::kLT:
- return LT;
- case Token::kGT:
- return GT;
- case Token::kLTE:
- return LE;
- case Token::kGTE:
- return GE;
- default:
- UNREACHABLE();
- return VS;
- }
-}
-
static Condition EmitUnboxedMintEqualityOp(FlowGraphCompiler* compiler,
LocationSummary* locs,
Token::Kind kind) {
@@ -1009,7 +999,7 @@
__ cmp(left_lo, compiler::Operand(right_lo));
// Compare upper if lower is equal.
__ cmp(left_hi, compiler::Operand(right_hi), EQ);
- return TokenKindToMintCondition(kind);
+ return TokenKindToIntCondition(kind);
}
static Condition EmitUnboxedMintComparisonOp(FlowGraphCompiler* compiler,
@@ -1056,6 +1046,45 @@
return lo_cond;
}
+static Condition EmitNullAwareInt64ComparisonOp(FlowGraphCompiler* compiler,
+ LocationSummary* locs,
+ Token::Kind kind,
+ BranchLabels labels) {
+ ASSERT((kind == Token::kEQ) || (kind == Token::kNE));
+ const Register left = locs->in(0).reg();
+ const Register right = locs->in(1).reg();
+ const Register temp = locs->temp(0).reg();
+ const Condition true_condition = TokenKindToIntCondition(kind);
+ compiler::Label* equal_result =
+ (true_condition == EQ) ? labels.true_label : labels.false_label;
+ compiler::Label* not_equal_result =
+ (true_condition == EQ) ? labels.false_label : labels.true_label;
+
+ // Check if operands have the same value. If they don't, then they could
+ // be equal only if both of them are Mints with the same value.
+ __ cmp(left, compiler::Operand(right));
+ __ b(equal_result, EQ);
+ __ and_(temp, left, compiler::Operand(right));
+ __ BranchIfSmi(temp, not_equal_result);
+ __ CompareClassId(left, kMintCid, temp);
+ __ b(not_equal_result, NE);
+ __ CompareClassId(right, kMintCid, temp);
+ __ b(not_equal_result, NE);
+ __ LoadFieldFromOffset(temp, left, compiler::target::Mint::value_offset());
+ __ LoadFieldFromOffset(TMP, right, compiler::target::Mint::value_offset());
+ __ cmp(temp, compiler::Operand(TMP));
+ __ LoadFieldFromOffset(
+ temp, left,
+ compiler::target::Mint::value_offset() + compiler::target::kWordSize,
+ compiler::kFourBytes, EQ);
+ __ LoadFieldFromOffset(
+ TMP, right,
+ compiler::target::Mint::value_offset() + compiler::target::kWordSize,
+ compiler::kFourBytes, EQ);
+ __ cmp(temp, compiler::Operand(TMP), EQ);
+ return true_condition;
+}
+
static Condition TokenKindToDoubleCondition(Token::Kind kind) {
switch (kind) {
case Token::kEQ:
@@ -1097,6 +1126,10 @@
Condition EqualityCompareInstr::EmitComparisonCode(FlowGraphCompiler* compiler,
BranchLabels labels) {
+ if (is_null_aware()) {
+ ASSERT(operation_cid() == kMintCid);
+ return EmitNullAwareInt64ComparisonOp(compiler, locs(), kind(), labels);
+ }
if (operation_cid() == kSmiCid) {
return EmitSmiComparisonOp(compiler, locs(), kind());
} else if (operation_cid() == kMintCid) {
@@ -3864,334 +3897,6 @@
}
}
-class CheckedSmiSlowPath : public TemplateSlowPathCode<CheckedSmiOpInstr> {
- public:
- CheckedSmiSlowPath(CheckedSmiOpInstr* instruction, intptr_t try_index)
- : TemplateSlowPathCode(instruction), try_index_(try_index) {}
-
- static constexpr intptr_t kNumSlowPathArgs = 2;
-
- virtual void EmitNativeCode(FlowGraphCompiler* compiler) {
- if (compiler::Assembler::EmittingComments()) {
- __ Comment("slow path smi operation");
- }
- __ Bind(entry_label());
- LocationSummary* locs = instruction()->locs();
- Register result = locs->out(0).reg();
- locs->live_registers()->Remove(Location::RegisterLocation(result));
-
- compiler->SaveLiveRegisters(locs);
- if (instruction()->env() != NULL) {
- Environment* env =
- compiler->SlowPathEnvironmentFor(instruction(), kNumSlowPathArgs);
- compiler->pending_deoptimization_env_ = env;
- }
- __ Push(locs->in(0).reg());
- __ Push(locs->in(1).reg());
- const auto& selector = String::Handle(instruction()->call()->Selector());
- const auto& arguments_descriptor =
- Array::Handle(ArgumentsDescriptor::NewBoxed(
- /*type_args_len=*/0, /*num_arguments=*/2));
- compiler->EmitMegamorphicInstanceCall(
- selector, arguments_descriptor, instruction()->call()->deopt_id(),
- instruction()->source(), locs, try_index_, kNumSlowPathArgs);
- __ mov(result, compiler::Operand(R0));
- compiler->RestoreLiveRegisters(locs);
- __ b(exit_label());
- compiler->pending_deoptimization_env_ = NULL;
- }
-
- private:
- intptr_t try_index_;
-};
-
-LocationSummary* CheckedSmiOpInstr::MakeLocationSummary(Zone* zone,
- bool opt) const {
- const intptr_t kNumInputs = 2;
- const intptr_t kNumTemps = 0;
- LocationSummary* summary = new (zone) LocationSummary(
- zone, kNumInputs, kNumTemps, LocationSummary::kCallOnSlowPath);
- summary->set_in(0, Location::RequiresRegister());
- summary->set_in(1, Location::RequiresRegister());
- summary->set_out(0, Location::RequiresRegister());
- return summary;
-}
-
-void CheckedSmiOpInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
- CheckedSmiSlowPath* slow_path =
- new CheckedSmiSlowPath(this, compiler->CurrentTryIndex());
- compiler->AddSlowPathCode(slow_path);
- // Test operands if necessary.
- Register left = locs()->in(0).reg();
- Register right = locs()->in(1).reg();
- Register result = locs()->out(0).reg();
- intptr_t left_cid = this->left()->Type()->ToCid();
- intptr_t right_cid = this->right()->Type()->ToCid();
- bool combined_smi_check = false;
- if (FLAG_use_slow_path) {
- __ b(slow_path->entry_label());
- } else if (this->left()->definition() == this->right()->definition()) {
- __ BranchIfNotSmi(left, slow_path->entry_label());
- } else if (left_cid == kSmiCid) {
- __ BranchIfNotSmi(right, slow_path->entry_label());
- } else if (right_cid == kSmiCid) {
- __ BranchIfNotSmi(left, slow_path->entry_label());
- } else {
- combined_smi_check = true;
- __ orr(result, left, compiler::Operand(right));
- __ BranchIfNotSmi(result, slow_path->entry_label());
- }
- switch (op_kind()) {
- case Token::kADD:
- __ adds(result, left, compiler::Operand(right));
- __ b(slow_path->entry_label(), VS);
- break;
- case Token::kSUB:
- __ subs(result, left, compiler::Operand(right));
- __ b(slow_path->entry_label(), VS);
- break;
- case Token::kMUL:
- __ SmiUntag(IP, left);
- __ smull(result, IP, IP, right);
- // IP: result bits 32..63.
- __ cmp(IP, compiler::Operand(result, ASR, 31));
- __ b(slow_path->entry_label(), NE);
- break;
- case Token::kBIT_OR:
- // Operation may be part of combined smi check.
- if (!combined_smi_check) {
- __ orr(result, left, compiler::Operand(right));
- }
- break;
- case Token::kBIT_AND:
- __ and_(result, left, compiler::Operand(right));
- break;
- case Token::kBIT_XOR:
- __ eor(result, left, compiler::Operand(right));
- break;
- case Token::kSHL:
- ASSERT(result != left);
- ASSERT(result != right);
- __ CompareImmediate(
- right, compiler::target::ToRawSmi(compiler::target::kSmiBits));
- __ b(slow_path->entry_label(), HI);
-
- __ SmiUntag(TMP, right);
- // Check for overflow by shifting left and shifting back arithmetically.
- // If the result is different from the original, there was overflow.
- __ Lsl(result, left, TMP);
- __ cmp(left, compiler::Operand(result, ASR, TMP));
- __ b(slow_path->entry_label(), NE);
- break;
- case Token::kSHR:
- ASSERT(result != left);
- ASSERT(result != right);
- __ CompareImmediate(
- right, compiler::target::ToRawSmi(compiler::target::kSmiBits));
- __ b(slow_path->entry_label(), HI);
-
- __ SmiUntag(result, right);
- __ SmiUntag(TMP, left);
- __ Asr(result, TMP, result);
- __ SmiTag(result);
- break;
- case Token::kUSHR: {
- ASSERT(result != left);
- ASSERT(result != right);
- __ CompareImmediate(right, compiler::target::ToRawSmi(kBitsPerInt64));
- __ b(slow_path->entry_label(), UNSIGNED_GREATER_EQUAL);
-
- compiler::Label done;
- __ SmiUntag(result, right);
- // 64-bit representation of left operand value:
- //
- // ss...sssss s s xxxxxxxxxxxxx
- // | | | | | |
- // 63 32 31 30 kSmiBits-1 0
- //
- // Where 's' is a sign bit.
- //
- // If left operand is negative (sign bit is set), then
- // result will fit into Smi range if and only if
- // the shift amount >= 64 - kSmiBits.
- //
- // If left operand is non-negative, the result always
- // fits into Smi range.
- //
- __ CompareImmediate(result, 64 - compiler::target::kSmiBits);
- // Shift amount >= 64 - kSmiBits > 32, but < 64.
- // Result is guaranteed to fit into Smi range.
- // Low (Smi) part of the left operand is shifted out.
- // High part is filled with sign bits.
- __ sub(result, result, compiler::Operand(32), GE);
- __ Asr(TMP, left, compiler::Operand(31), GE);
- __ Lsr(result, TMP, result, GE);
- __ SmiTag(result, GE);
- __ b(&done, GE);
- // Shift amount < 64 - kSmiBits.
- // If left is negative, then result will not fit into Smi range.
- // Also deopt in case of negative shift amount.
- __ tst(left, compiler::Operand(left));
- __ b(slow_path->entry_label(), MI);
- // At this point left operand is non-negative, so unsigned shift
- // can't overflow.
- __ CompareImmediate(result, compiler::target::kSmiBits);
- // Left operand >= 0, shift amount >= kSmiBits. Result is 0.
- __ LoadImmediate(result, 0, GE);
- // Left operand >= 0, shift amount < kSmiBits < 32.
- __ SmiUntag(TMP, left, LT);
- __ Lsr(result, TMP, result, LT);
- __ SmiTag(result, LT);
- __ Bind(&done);
- break;
- }
- default:
- UNREACHABLE();
- }
- __ Bind(slow_path->exit_label());
-}
-
-class CheckedSmiComparisonSlowPath
- : public TemplateSlowPathCode<CheckedSmiComparisonInstr> {
- public:
- static constexpr intptr_t kNumSlowPathArgs = 2;
-
- CheckedSmiComparisonSlowPath(CheckedSmiComparisonInstr* instruction,
- Environment* env,
- intptr_t try_index,
- BranchLabels labels,
- bool merged)
- : TemplateSlowPathCode(instruction),
- try_index_(try_index),
- labels_(labels),
- merged_(merged),
- env_(env) {
- // The environment must either come from the comparison or the environment
- // was cleared from the comparison (and moved to a branch).
- ASSERT(env == instruction->env() ||
- (merged && instruction->env() == nullptr));
- }
-
- virtual void EmitNativeCode(FlowGraphCompiler* compiler) {
- if (compiler::Assembler::EmittingComments()) {
- __ Comment("slow path smi operation");
- }
- __ Bind(entry_label());
- LocationSummary* locs = instruction()->locs();
- Register result = merged_ ? locs->temp(0).reg() : locs->out(0).reg();
- locs->live_registers()->Remove(Location::RegisterLocation(result));
-
- compiler->SaveLiveRegisters(locs);
- if (env_ != nullptr) {
- compiler->pending_deoptimization_env_ =
- compiler->SlowPathEnvironmentFor(env_, locs, kNumSlowPathArgs);
- }
- __ Push(locs->in(0).reg());
- __ Push(locs->in(1).reg());
- const auto& selector = String::Handle(instruction()->call()->Selector());
- const auto& arguments_descriptor =
- Array::Handle(ArgumentsDescriptor::NewBoxed(
- /*type_args_len=*/0, /*num_arguments=*/2));
- compiler->EmitMegamorphicInstanceCall(
- selector, arguments_descriptor, instruction()->call()->deopt_id(),
- instruction()->source(), locs, try_index_, kNumSlowPathArgs);
- __ mov(result, compiler::Operand(R0));
- compiler->RestoreLiveRegisters(locs);
- compiler->pending_deoptimization_env_ = nullptr;
- if (merged_) {
- __ CompareObject(result, Bool::True());
- __ b(instruction()->is_negated() ? labels_.false_label
- : labels_.true_label,
- EQ);
- __ b(instruction()->is_negated() ? labels_.true_label
- : labels_.false_label);
- } else {
- if (instruction()->is_negated()) {
- // Need to negate the result of slow path call.
- __ CompareObject(result, Bool::True());
- __ LoadObject(result, Bool::True(), NE);
- __ LoadObject(result, Bool::False(), EQ);
- }
- __ b(exit_label());
- }
- }
-
- private:
- intptr_t try_index_;
- BranchLabels labels_;
- bool merged_;
- Environment* env_;
-};
-
-LocationSummary* CheckedSmiComparisonInstr::MakeLocationSummary(
- Zone* zone,
- bool opt) const {
- const intptr_t kNumInputs = 2;
- const intptr_t kNumTemps = 1;
- LocationSummary* summary = new (zone) LocationSummary(
- zone, kNumInputs, kNumTemps, LocationSummary::kCallOnSlowPath);
- summary->set_in(0, Location::RequiresRegister());
- summary->set_in(1, Location::RequiresRegister());
- summary->set_temp(0, Location::RequiresRegister());
- summary->set_out(0, Location::RequiresRegister());
- return summary;
-}
-
-Condition CheckedSmiComparisonInstr::EmitComparisonCode(
- FlowGraphCompiler* compiler,
- BranchLabels labels) {
- return EmitSmiComparisonOp(compiler, locs(), kind());
-}
-
-#define EMIT_SMI_CHECK \
- Register left = locs()->in(0).reg(); \
- Register right = locs()->in(1).reg(); \
- Register temp = locs()->temp(0).reg(); \
- intptr_t left_cid = this->left()->Type()->ToCid(); \
- intptr_t right_cid = this->right()->Type()->ToCid(); \
- if (FLAG_use_slow_path) { \
- __ b(slow_path->entry_label()); \
- } else if (this->left()->definition() == this->right()->definition()) { \
- __ BranchIfNotSmi(left, slow_path->entry_label()); \
- } else if (left_cid == kSmiCid) { \
- __ BranchIfNotSmi(right, slow_path->entry_label()); \
- } else if (right_cid == kSmiCid) { \
- __ BranchIfNotSmi(left, slow_path->entry_label()); \
- } else { \
- __ orr(temp, left, compiler::Operand(right)); \
- __ BranchIfNotSmi(temp, slow_path->entry_label()); \
- }
-
-void CheckedSmiComparisonInstr::EmitBranchCode(FlowGraphCompiler* compiler,
- BranchInstr* branch) {
- BranchLabels labels = compiler->CreateBranchLabels(branch);
- CheckedSmiComparisonSlowPath* slow_path = new CheckedSmiComparisonSlowPath(
- this, branch->env(), compiler->CurrentTryIndex(), labels,
- /* merged = */ true);
- compiler->AddSlowPathCode(slow_path);
- EMIT_SMI_CHECK;
- Condition true_condition = EmitComparisonCode(compiler, labels);
- ASSERT(true_condition != kInvalidCondition);
- EmitBranchOnCondition(compiler, true_condition, labels);
- __ Bind(slow_path->exit_label());
-}
-
-void CheckedSmiComparisonInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
- BranchLabels labels = {NULL, NULL, NULL};
- CheckedSmiComparisonSlowPath* slow_path = new CheckedSmiComparisonSlowPath(
- this, env(), compiler->CurrentTryIndex(), labels,
- /* merged = */ false);
- compiler->AddSlowPathCode(slow_path);
- EMIT_SMI_CHECK;
- Condition true_condition = EmitComparisonCode(compiler, labels);
- ASSERT(true_condition != kInvalidCondition);
- Register result = locs()->out(0).reg();
- __ LoadObject(result, Bool::True(), true_condition);
- __ LoadObject(result, Bool::False(), InvertCondition(true_condition));
- __ Bind(slow_path->exit_label());
-}
-#undef EMIT_SMI_CHECK
-
LocationSummary* BinarySmiOpInstr::MakeLocationSummary(Zone* zone,
bool opt) const {
const intptr_t kNumInputs = 2;
diff --git a/runtime/vm/compiler/backend/il_arm64.cc b/runtime/vm/compiler/backend/il_arm64.cc
index f8a5f78..84bcb80 100644
--- a/runtime/vm/compiler/backend/il_arm64.cc
+++ b/runtime/vm/compiler/backend/il_arm64.cc
@@ -740,7 +740,7 @@
__ Bind(&done);
}
-static Condition TokenKindToSmiCondition(Token::Kind kind) {
+static Condition TokenKindToIntCondition(Token::Kind kind) {
switch (kind) {
case Token::kEQ:
return EQ;
@@ -847,7 +847,7 @@
Location right = locs->in(1);
ASSERT(!left.IsConstant() || !right.IsConstant());
- Condition true_condition = TokenKindToSmiCondition(kind);
+ Condition true_condition = TokenKindToIntCondition(kind);
if (left.IsConstant() || right.IsConstant()) {
// Ensure constant is on the right.
ConstantInstr* constant = nullptr;
@@ -891,7 +891,7 @@
Location right = locs->in(1);
ASSERT(!left.IsConstant() || !right.IsConstant());
- Condition true_condition = TokenKindToSmiCondition(kind);
+ Condition true_condition = TokenKindToIntCondition(kind);
if (left.IsConstant() || right.IsConstant()) {
// Ensure constant is on the right.
ConstantInstr* constant = nullptr;
@@ -925,6 +925,35 @@
return true_condition;
}
+static Condition EmitNullAwareInt64ComparisonOp(FlowGraphCompiler* compiler,
+ LocationSummary* locs,
+ Token::Kind kind,
+ BranchLabels labels) {
+ ASSERT((kind == Token::kEQ) || (kind == Token::kNE));
+ const Register left = locs->in(0).reg();
+ const Register right = locs->in(1).reg();
+ const Condition true_condition = TokenKindToIntCondition(kind);
+ compiler::Label* equal_result =
+ (true_condition == EQ) ? labels.true_label : labels.false_label;
+ compiler::Label* not_equal_result =
+ (true_condition == EQ) ? labels.false_label : labels.true_label;
+
+ // Check if operands have the same value. If they don't, then they could
+ // be equal only if both of them are Mints with the same value.
+ __ CompareObjectRegisters(left, right);
+ __ b(equal_result, EQ);
+ __ and_(TMP, left, compiler::Operand(right), compiler::kObjectBytes);
+ __ BranchIfSmi(TMP, not_equal_result);
+ __ CompareClassId(left, kMintCid);
+ __ b(not_equal_result, NE);
+ __ CompareClassId(right, kMintCid);
+ __ b(not_equal_result, NE);
+ __ LoadFieldFromOffset(TMP, left, Mint::value_offset());
+ __ LoadFieldFromOffset(TMP2, right, Mint::value_offset());
+ __ CompareRegisters(TMP, TMP2);
+ return true_condition;
+}
+
LocationSummary* EqualityCompareInstr::MakeLocationSummary(Zone* zone,
bool opt) const {
const intptr_t kNumInputs = 2;
@@ -941,13 +970,18 @@
const intptr_t kNumTemps = 0;
LocationSummary* locs = new (zone)
LocationSummary(zone, kNumInputs, kNumTemps, LocationSummary::kNoCall);
- locs->set_in(0, LocationRegisterOrConstant(left()));
- // Only one input can be a constant operand. The case of two constant
- // operands should be handled by constant propagation.
- // Only right can be a stack slot.
- locs->set_in(1, locs->in(0).IsConstant()
- ? Location::RequiresRegister()
- : LocationRegisterOrConstant(right()));
+ if (is_null_aware()) {
+ locs->set_in(0, Location::RequiresRegister());
+ locs->set_in(1, Location::RequiresRegister());
+ } else {
+ locs->set_in(0, LocationRegisterOrConstant(left()));
+ // Only one input can be a constant operand. The case of two constant
+ // operands should be handled by constant propagation.
+ // Only right can be a stack slot.
+ locs->set_in(1, locs->in(0).IsConstant()
+ ? Location::RequiresRegister()
+ : LocationRegisterOrConstant(right()));
+ }
locs->set_out(0, Location::RequiresRegister());
return locs;
}
@@ -993,6 +1027,10 @@
Condition EqualityCompareInstr::EmitComparisonCode(FlowGraphCompiler* compiler,
BranchLabels labels) {
+ if (is_null_aware()) {
+ ASSERT(operation_cid() == kMintCid);
+ return EmitNullAwareInt64ComparisonOp(compiler, locs(), kind(), labels);
+ }
if (operation_cid() == kSmiCid) {
return EmitSmiComparisonOp(compiler, locs(), kind(), labels);
} else if (operation_cid() == kMintCid) {
@@ -3429,311 +3467,6 @@
}
}
-class CheckedSmiSlowPath : public TemplateSlowPathCode<CheckedSmiOpInstr> {
- public:
- static constexpr intptr_t kNumSlowPathArgs = 2;
-
- CheckedSmiSlowPath(CheckedSmiOpInstr* instruction, intptr_t try_index)
- : TemplateSlowPathCode(instruction), try_index_(try_index) {}
-
- virtual void EmitNativeCode(FlowGraphCompiler* compiler) {
- if (compiler::Assembler::EmittingComments()) {
- __ Comment("slow path smi operation");
- }
- __ Bind(entry_label());
- LocationSummary* locs = instruction()->locs();
- Register result = locs->out(0).reg();
- locs->live_registers()->Remove(Location::RegisterLocation(result));
-
- compiler->SaveLiveRegisters(locs);
- if (instruction()->env() != NULL) {
- Environment* env =
- compiler->SlowPathEnvironmentFor(instruction(), kNumSlowPathArgs);
- compiler->pending_deoptimization_env_ = env;
- }
- __ PushPair(locs->in(1).reg(), locs->in(0).reg());
- const auto& selector = String::Handle(instruction()->call()->Selector());
- const auto& arguments_descriptor =
- Array::Handle(ArgumentsDescriptor::NewBoxed(
- /*type_args_len=*/0, /*num_arguments=*/2));
- compiler->EmitMegamorphicInstanceCall(
- selector, arguments_descriptor, instruction()->call()->deopt_id(),
- instruction()->source(), locs, try_index_, kNumSlowPathArgs);
- __ mov(result, R0);
- compiler->RestoreLiveRegisters(locs);
- __ b(exit_label());
- compiler->pending_deoptimization_env_ = NULL;
- }
-
- private:
- intptr_t try_index_;
-};
-
-LocationSummary* CheckedSmiOpInstr::MakeLocationSummary(Zone* zone,
- bool opt) const {
- const intptr_t kNumInputs = 2;
- const intptr_t kNumTemps = 0;
- LocationSummary* summary = new (zone) LocationSummary(
- zone, kNumInputs, kNumTemps, LocationSummary::kCallOnSlowPath);
- summary->set_in(0, Location::RequiresRegister());
- summary->set_in(1, Location::RequiresRegister());
- summary->set_out(0, Location::RequiresRegister());
- return summary;
-}
-
-void CheckedSmiOpInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
- CheckedSmiSlowPath* slow_path =
- new CheckedSmiSlowPath(this, compiler->CurrentTryIndex());
- compiler->AddSlowPathCode(slow_path);
- // Test operands if necessary.
- Register left = locs()->in(0).reg();
- Register right = locs()->in(1).reg();
- Register result = locs()->out(0).reg();
- intptr_t left_cid = this->left()->Type()->ToCid();
- intptr_t right_cid = this->right()->Type()->ToCid();
- bool combined_smi_check = false;
- if (FLAG_use_slow_path) {
- __ b(slow_path->entry_label());
- } else if (this->left()->definition() == this->right()->definition()) {
- __ BranchIfNotSmi(left, slow_path->entry_label());
- } else if (left_cid == kSmiCid) {
- __ BranchIfNotSmi(right, slow_path->entry_label());
- } else if (right_cid == kSmiCid) {
- __ BranchIfNotSmi(left, slow_path->entry_label());
- } else {
- combined_smi_check = true;
- __ orr(result, left, compiler::Operand(right));
- __ BranchIfNotSmi(result, slow_path->entry_label());
- }
-
- switch (op_kind()) {
- case Token::kADD:
- __ adds(result, left, compiler::Operand(right), compiler::kObjectBytes);
- __ b(slow_path->entry_label(), VS);
- break;
- case Token::kSUB:
- __ subs(result, left, compiler::Operand(right), compiler::kObjectBytes);
- __ b(slow_path->entry_label(), VS);
- break;
- case Token::kMUL:
- __ SmiUntag(TMP, left);
-#if !defined(DART_COMPRESSED_POINTERS)
- __ mul(result, TMP, right);
- __ smulh(TMP, TMP, right);
- // TMP: result bits 64..127.
-#else
- __ smull(result, TMP, right);
- __ AsrImmediate(TMP, result, 31);
- // TMP: result bits 32..63.
-#endif
- __ cmp(TMP, compiler::Operand(result, ASR, 63));
- __ b(slow_path->entry_label(), NE);
- break;
- case Token::kBIT_OR:
- // Operation may be part of combined smi check.
- if (!combined_smi_check) {
- __ orr(result, left, compiler::Operand(right));
- }
- break;
- case Token::kBIT_AND:
- __ and_(result, left, compiler::Operand(right));
- break;
- case Token::kBIT_XOR:
- __ eor(result, left, compiler::Operand(right));
- break;
- case Token::kSHL:
- ASSERT(result != left);
- ASSERT(result != right);
- __ CompareObject(right, Smi::ZoneHandle(Smi::New(Smi::kBits)));
- __ b(slow_path->entry_label(), CS);
-
- __ SmiUntag(TMP, right);
- __ lslv(result, left, TMP, compiler::kObjectBytes);
- __ asrv(TMP2, result, TMP, compiler::kObjectBytes);
- __ cmp(left, compiler::Operand(TMP2), compiler::kObjectBytes);
- __ b(slow_path->entry_label(), NE); // Overflow.
- break;
- case Token::kSHR:
- ASSERT(result != left);
- ASSERT(result != right);
- __ CompareObject(right, Smi::ZoneHandle(Smi::New(Smi::kBits)));
- __ b(slow_path->entry_label(), CS);
-
- __ SmiUntag(result, right);
- __ SmiUntag(TMP, left);
- __ asrv(result, TMP, result, compiler::kObjectBytes);
- __ SmiTag(result);
- break;
- case Token::kUSHR:
- ASSERT(result != left);
- ASSERT(result != right);
- __ CompareObject(right, Smi::ZoneHandle(Smi::New(kBitsPerInt64)));
- __ b(slow_path->entry_label(), UNSIGNED_GREATER_EQUAL);
-
- __ SmiUntag(result, right);
- __ SmiUntag(TMP, left);
- __ lsrv(result, TMP, result);
- __ SmiTagAndBranchIfOverflow(result, slow_path->entry_label());
- break;
- default:
- UNIMPLEMENTED();
- }
- __ Bind(slow_path->exit_label());
-}
-
-class CheckedSmiComparisonSlowPath
- : public TemplateSlowPathCode<CheckedSmiComparisonInstr> {
- public:
- static constexpr intptr_t kNumSlowPathArgs = 2;
-
- CheckedSmiComparisonSlowPath(CheckedSmiComparisonInstr* instruction,
- Environment* env,
- intptr_t try_index,
- BranchLabels labels,
- bool merged)
- : TemplateSlowPathCode(instruction),
- try_index_(try_index),
- labels_(labels),
- merged_(merged),
- env_(env) {
- // The environment must either come from the comparison or the environment
- // was cleared from the comparison (and moved to a branch).
- ASSERT(env == instruction->env() ||
- (merged && instruction->env() == nullptr));
- }
-
- virtual void EmitNativeCode(FlowGraphCompiler* compiler) {
- if (compiler::Assembler::EmittingComments()) {
- __ Comment("slow path smi operation");
- }
- __ Bind(entry_label());
- LocationSummary* locs = instruction()->locs();
- Register result = merged_ ? locs->temp(0).reg() : locs->out(0).reg();
- locs->live_registers()->Remove(Location::RegisterLocation(result));
-
- compiler->SaveLiveRegisters(locs);
- if (env_ != nullptr) {
- compiler->pending_deoptimization_env_ =
- compiler->SlowPathEnvironmentFor(env_, locs, kNumSlowPathArgs);
- }
- __ PushPair(locs->in(1).reg(), locs->in(0).reg());
- const auto& selector = String::Handle(instruction()->call()->Selector());
- const auto& arguments_descriptor =
- Array::Handle(ArgumentsDescriptor::NewBoxed(
- /*type_args_len=*/0, /*num_arguments=*/2));
- compiler->EmitMegamorphicInstanceCall(
- selector, arguments_descriptor, instruction()->call()->deopt_id(),
- instruction()->source(), locs, try_index_, kNumSlowPathArgs);
- __ mov(result, R0);
- compiler->RestoreLiveRegisters(locs);
- compiler->pending_deoptimization_env_ = nullptr;
- if (merged_) {
- __ CompareObject(result, Bool::True());
- __ b(instruction()->is_negated() ? labels_.false_label
- : labels_.true_label,
- EQ);
- __ b(instruction()->is_negated() ? labels_.true_label
- : labels_.false_label);
- ASSERT(exit_label()->IsUnused());
- } else {
- ASSERT(!instruction()->is_negated());
- __ b(exit_label());
- }
- }
-
- private:
- intptr_t try_index_;
- BranchLabels labels_;
- bool merged_;
- Environment* env_;
-};
-
-LocationSummary* CheckedSmiComparisonInstr::MakeLocationSummary(
- Zone* zone,
- bool opt) const {
- const intptr_t kNumInputs = 2;
- const intptr_t kNumTemps = 1;
- LocationSummary* summary = new (zone) LocationSummary(
- zone, kNumInputs, kNumTemps, LocationSummary::kCallOnSlowPath);
- summary->set_in(0, Location::RequiresRegister());
- summary->set_in(1, Location::RequiresRegister());
- summary->set_temp(0, Location::RequiresRegister());
- summary->set_out(0, Location::RequiresRegister());
- return summary;
-}
-
-Condition CheckedSmiComparisonInstr::EmitComparisonCode(
- FlowGraphCompiler* compiler,
- BranchLabels labels) {
- return EmitSmiComparisonOp(compiler, locs(), kind(), labels);
-}
-
-#define EMIT_SMI_CHECK \
- Register left = locs()->in(0).reg(); \
- Register right = locs()->in(1).reg(); \
- Register temp = locs()->temp(0).reg(); \
- intptr_t left_cid = this->left()->Type()->ToCid(); \
- intptr_t right_cid = this->right()->Type()->ToCid(); \
- if (FLAG_use_slow_path) { \
- __ b(slow_path->entry_label()); \
- } else if (this->left()->definition() == this->right()->definition()) { \
- __ BranchIfNotSmi(left, slow_path->entry_label()); \
- } else if (left_cid == kSmiCid) { \
- __ BranchIfNotSmi(right, slow_path->entry_label()); \
- } else if (right_cid == kSmiCid) { \
- __ BranchIfNotSmi(left, slow_path->entry_label()); \
- } else { \
- __ orr(temp, left, compiler::Operand(right)); \
- __ BranchIfNotSmi(temp, slow_path->entry_label()); \
- }
-
-void CheckedSmiComparisonInstr::EmitBranchCode(FlowGraphCompiler* compiler,
- BranchInstr* branch) {
- BranchLabels labels = compiler->CreateBranchLabels(branch);
- CheckedSmiComparisonSlowPath* slow_path = new CheckedSmiComparisonSlowPath(
- this, branch->env(), compiler->CurrentTryIndex(), labels,
- /* merged = */ true);
- compiler->AddSlowPathCode(slow_path);
- EMIT_SMI_CHECK;
- Condition true_condition = EmitComparisonCode(compiler, labels);
- if (true_condition != kInvalidCondition) {
- EmitBranchOnCondition(compiler, true_condition, labels);
- }
- // No need to bind slow_path->exit_label() as slow path exits through
- // true/false branch labels.
-}
-
-void CheckedSmiComparisonInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
- // Zone-allocate labels to pass them to slow-path which outlives local scope.
- compiler::Label* true_label = new (Z) compiler::Label();
- compiler::Label* false_label = new (Z) compiler::Label();
- compiler::Label done;
- BranchLabels labels = {true_label, false_label, false_label};
- // In case of negated comparison result of a slow path call should be negated.
- // For this purpose, 'merged' slow path is generated: it tests
- // result of a call and jumps directly to true or false label.
- CheckedSmiComparisonSlowPath* slow_path = new CheckedSmiComparisonSlowPath(
- this, env(), compiler->CurrentTryIndex(), labels,
- /* merged = */ is_negated());
- compiler->AddSlowPathCode(slow_path);
- EMIT_SMI_CHECK;
- Condition true_condition = EmitComparisonCode(compiler, labels);
- if (true_condition != kInvalidCondition) {
- EmitBranchOnCondition(compiler, true_condition, labels);
- }
- Register result = locs()->out(0).reg();
- __ Bind(false_label);
- __ LoadObject(result, Bool::False());
- __ b(&done);
- __ Bind(true_label);
- __ LoadObject(result, Bool::True());
- __ Bind(&done);
- // In case of negated comparison slow path exits through true/false labels.
- if (!is_negated()) {
- __ Bind(slow_path->exit_label());
- }
-}
-
LocationSummary* BinarySmiOpInstr::MakeLocationSummary(Zone* zone,
bool opt) const {
const intptr_t kNumInputs = 2;
diff --git a/runtime/vm/compiler/backend/il_ia32.cc b/runtime/vm/compiler/backend/il_ia32.cc
index c7c46fc..8c6fe6d 100644
--- a/runtime/vm/compiler/backend/il_ia32.cc
+++ b/runtime/vm/compiler/backend/il_ia32.cc
@@ -529,7 +529,7 @@
__ Bind(&done);
}
-static Condition TokenKindToSmiCondition(Token::Kind kind) {
+static Condition TokenKindToIntCondition(Token::Kind kind) {
switch (kind) {
case Token::kEQ:
return EQUAL;
@@ -665,7 +665,7 @@
Location right = locs.in(1);
ASSERT(!left.IsConstant() || !right.IsConstant());
- Condition true_condition = TokenKindToSmiCondition(kind);
+ Condition true_condition = TokenKindToIntCondition(kind);
if (left.IsConstant()) {
__ CompareObject(right.reg(), left.constant());
@@ -680,26 +680,6 @@
return true_condition;
}
-static Condition TokenKindToMintCondition(Token::Kind kind) {
- switch (kind) {
- case Token::kEQ:
- return EQUAL;
- case Token::kNE:
- return NOT_EQUAL;
- case Token::kLT:
- return LESS;
- case Token::kGT:
- return GREATER;
- case Token::kLTE:
- return LESS_EQUAL;
- case Token::kGTE:
- return GREATER_EQUAL;
- default:
- UNREACHABLE();
- return OVERFLOW;
- }
-}
-
static Condition EmitUnboxedMintEqualityOp(FlowGraphCompiler* compiler,
const LocationSummary& locs,
Token::Kind kind,
@@ -718,7 +698,7 @@
// Lower is equal, compare upper.
__ cmpl(left2, right2);
__ Bind(&done);
- Condition true_condition = TokenKindToMintCondition(kind);
+ Condition true_condition = TokenKindToIntCondition(kind);
return true_condition;
}
@@ -803,6 +783,10 @@
Condition EqualityCompareInstr::EmitComparisonCode(FlowGraphCompiler* compiler,
BranchLabels labels) {
+ if (is_null_aware()) {
+ // Null-aware EqualityCompare instruction is only used in AOT.
+ UNREACHABLE();
+ }
if (operation_cid() == kSmiCid) {
return EmitSmiComparisonOp(compiler, *locs(), kind(), labels);
} else if (operation_cid() == kMintCid) {
@@ -3143,45 +3127,6 @@
}
}
-LocationSummary* CheckedSmiOpInstr::MakeLocationSummary(Zone* zone,
- bool opt) const {
- // Only for precompiled code, not on ia32 currently.
- UNIMPLEMENTED();
- return NULL;
-}
-
-void CheckedSmiOpInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
- // Only for precompiled code, not on ia32 currently.
- UNIMPLEMENTED();
-}
-
-LocationSummary* CheckedSmiComparisonInstr::MakeLocationSummary(
- Zone* zone,
- bool opt) const {
- // Only for precompiled code, not on ia32 currently.
- UNIMPLEMENTED();
- return NULL;
-}
-
-Condition CheckedSmiComparisonInstr::EmitComparisonCode(
- FlowGraphCompiler* compiler,
- BranchLabels labels) {
- // Only for precompiled code, not on ia32 currently.
- UNIMPLEMENTED();
- return ZERO;
-}
-
-void CheckedSmiComparisonInstr::EmitBranchCode(FlowGraphCompiler* compiler,
- BranchInstr* instr) {
- // Only for precompiled code, not on ia32 currently.
- UNIMPLEMENTED();
-}
-
-void CheckedSmiComparisonInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
- // Only for precompiled code, not on ia32 currently.
- UNIMPLEMENTED();
-}
-
static bool IsSmiValue(const Object& constant, intptr_t value) {
return constant.IsSmi() && (Smi::Cast(constant).Value() == value);
}
diff --git a/runtime/vm/compiler/backend/il_printer.cc b/runtime/vm/compiler/backend/il_printer.cc
index b42d9de..5773401 100644
--- a/runtime/vm/compiler/backend/il_printer.cc
+++ b/runtime/vm/compiler/backend/il_printer.cc
@@ -730,22 +730,6 @@
value()->PrintTo(f);
}
-void CheckedSmiOpInstr::PrintOperandsTo(BaseTextBuffer* f) const {
- f->Printf("%s", Token::Str(op_kind()));
- f->AddString(", ");
- left()->PrintTo(f);
- f->AddString(", ");
- right()->PrintTo(f);
-}
-
-void CheckedSmiComparisonInstr::PrintOperandsTo(BaseTextBuffer* f) const {
- f->Printf("%s", Token::Str(kind()));
- f->AddString(", ");
- left()->PrintTo(f);
- f->AddString(", ");
- right()->PrintTo(f);
-}
-
void BinaryIntegerOpInstr::PrintOperandsTo(BaseTextBuffer* f) const {
f->Printf("%s", Token::Str(op_kind()));
if (is_truncating()) {
diff --git a/runtime/vm/compiler/backend/il_test_helper.cc b/runtime/vm/compiler/backend/il_test_helper.cc
index b860562..a99374c 100644
--- a/runtime/vm/compiler/backend/il_test_helper.cc
+++ b/runtime/vm/compiler/backend/il_test_helper.cc
@@ -202,8 +202,7 @@
// We expect there to be no deoptimizations.
if (mode_ == CompilerPass::kAOT) {
- // TODO(kustermann): Enable this once we get rid of [CheckedSmiSlowPath]s.
- // EXPECT(deopt_info_array.IsNull() || deopt_info_array.Length() == 0);
+ EXPECT(deopt_info_array.IsNull() || deopt_info_array.Length() == 0);
}
}
diff --git a/runtime/vm/compiler/backend/il_x64.cc b/runtime/vm/compiler/backend/il_x64.cc
index 55eaf32..ed081c7 100644
--- a/runtime/vm/compiler/backend/il_x64.cc
+++ b/runtime/vm/compiler/backend/il_x64.cc
@@ -729,13 +729,18 @@
const intptr_t kNumTemps = 0;
LocationSummary* locs = new (zone)
LocationSummary(zone, kNumInputs, kNumTemps, LocationSummary::kNoCall);
- locs->set_in(0, LocationRegisterOrConstant(left()));
- // Only one input can be a constant operand. The case of two constant
- // operands should be handled by constant propagation.
- // Only right can be a stack slot.
- locs->set_in(1, locs->in(0).IsConstant()
- ? Location::RequiresRegister()
- : LocationRegisterOrConstant(right()));
+ if (is_null_aware()) {
+ locs->set_in(0, Location::RequiresRegister());
+ locs->set_in(1, Location::RequiresRegister());
+ } else {
+ locs->set_in(0, LocationRegisterOrConstant(left()));
+ // Only one input can be a constant operand. The case of two constant
+ // operands should be handled by constant propagation.
+ // Only right can be a stack slot.
+ locs->set_in(1, locs->in(0).IsConstant()
+ ? Location::RequiresRegister()
+ : LocationRegisterOrConstant(right()));
+ }
locs->set_out(0, Location::RequiresRegister());
return locs;
}
@@ -885,6 +890,35 @@
return true_condition;
}
+static Condition EmitNullAwareInt64ComparisonOp(FlowGraphCompiler* compiler,
+ const LocationSummary& locs,
+ Token::Kind kind,
+ BranchLabels labels) {
+ ASSERT((kind == Token::kEQ) || (kind == Token::kNE));
+ const Register left = locs.in(0).reg();
+ const Register right = locs.in(1).reg();
+ const Condition true_condition = TokenKindToIntCondition(kind);
+ compiler::Label* equal_result =
+ (true_condition == EQUAL) ? labels.true_label : labels.false_label;
+ compiler::Label* not_equal_result =
+ (true_condition == EQUAL) ? labels.false_label : labels.true_label;
+
+ // Check if operands have the same value. If they don't, then they could
+ // be equal only if both of them are Mints with the same value.
+ __ OBJ(cmp)(left, right);
+ __ j(EQUAL, equal_result);
+ __ OBJ(mov)(TMP, left);
+ __ OBJ(and)(TMP, right);
+ __ BranchIfSmi(TMP, not_equal_result);
+ __ CompareClassId(left, kMintCid);
+ __ j(NOT_EQUAL, not_equal_result);
+ __ CompareClassId(right, kMintCid);
+ __ j(NOT_EQUAL, not_equal_result);
+ __ movq(TMP, compiler::FieldAddress(left, Mint::value_offset()));
+ __ cmpq(TMP, compiler::FieldAddress(right, Mint::value_offset()));
+ return true_condition;
+}
+
static Condition TokenKindToDoubleCondition(Token::Kind kind) {
switch (kind) {
case Token::kEQ:
@@ -923,6 +957,10 @@
Condition EqualityCompareInstr::EmitComparisonCode(FlowGraphCompiler* compiler,
BranchLabels labels) {
+ if (is_null_aware()) {
+ ASSERT(operation_cid() == kMintCid);
+ return EmitNullAwareInt64ComparisonOp(compiler, *locs(), kind(), labels);
+ }
if (operation_cid() == kSmiCid) {
return EmitSmiComparisonOp(compiler, *locs(), kind());
} else if (operation_cid() == kMintCid) {
@@ -3516,342 +3554,6 @@
}
}
-class CheckedSmiSlowPath : public TemplateSlowPathCode<CheckedSmiOpInstr> {
- public:
- CheckedSmiSlowPath(CheckedSmiOpInstr* instruction, intptr_t try_index)
- : TemplateSlowPathCode(instruction), try_index_(try_index) {}
-
- static constexpr intptr_t kNumSlowPathArgs = 2;
-
- virtual void EmitNativeCode(FlowGraphCompiler* compiler) {
- if (compiler::Assembler::EmittingComments()) {
- __ Comment("slow path smi operation");
- }
- __ Bind(entry_label());
- LocationSummary* locs = instruction()->locs();
- Register result = locs->out(0).reg();
- locs->live_registers()->Remove(Location::RegisterLocation(result));
-
- compiler->SaveLiveRegisters(locs);
- if (instruction()->env() != NULL) {
- Environment* env =
- compiler->SlowPathEnvironmentFor(instruction(), kNumSlowPathArgs);
- compiler->pending_deoptimization_env_ = env;
- }
- __ pushq(locs->in(0).reg());
- __ pushq(locs->in(1).reg());
- const auto& selector = String::Handle(instruction()->call()->Selector());
- const auto& arguments_descriptor =
- Array::Handle(ArgumentsDescriptor::NewBoxed(
- /*type_args_len=*/0, /*num_arguments=*/2));
- compiler->EmitMegamorphicInstanceCall(
- selector, arguments_descriptor, instruction()->call()->deopt_id(),
- instruction()->source(), locs, try_index_, kNumSlowPathArgs);
- __ MoveRegister(result, RAX);
- compiler->RestoreLiveRegisters(locs);
- __ jmp(exit_label());
- compiler->pending_deoptimization_env_ = NULL;
- }
-
- private:
- intptr_t try_index_;
-};
-
-LocationSummary* CheckedSmiOpInstr::MakeLocationSummary(Zone* zone,
- bool opt) const {
- bool is_shift = (op_kind() == Token::kSHL) || (op_kind() == Token::kSHR) ||
- (op_kind() == Token::kUSHR);
- const intptr_t kNumInputs = 2;
- const intptr_t kNumTemps = is_shift ? 1 : 0;
- LocationSummary* summary = new (zone) LocationSummary(
- zone, kNumInputs, kNumTemps, LocationSummary::kCallOnSlowPath);
- summary->set_in(0, Location::RequiresRegister());
- summary->set_in(1, Location::RequiresRegister());
- switch (op_kind()) {
- case Token::kADD:
- case Token::kSUB:
- case Token::kMUL:
- case Token::kSHL:
- case Token::kSHR:
- case Token::kUSHR:
- summary->set_out(0, Location::RequiresRegister());
- break;
- case Token::kBIT_OR:
- case Token::kBIT_AND:
- case Token::kBIT_XOR:
- summary->set_out(0, Location::SameAsFirstInput());
- break;
- default:
- UNIMPLEMENTED();
- }
- if (is_shift) {
- summary->set_temp(0, Location::RegisterLocation(RCX));
- }
- return summary;
-}
-
-void CheckedSmiOpInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
- CheckedSmiSlowPath* slow_path =
- new CheckedSmiSlowPath(this, compiler->CurrentTryIndex());
- compiler->AddSlowPathCode(slow_path);
- // Test operands if necessary.
-
- intptr_t left_cid = left()->Type()->ToCid();
- intptr_t right_cid = right()->Type()->ToCid();
- Register left = locs()->in(0).reg();
- Register right = locs()->in(1).reg();
- if (FLAG_use_slow_path) {
- __ jmp(slow_path->entry_label());
- } else if (this->left()->definition() == this->right()->definition()) {
- __ BranchIfNotSmi(left, slow_path->entry_label());
- } else if (left_cid == kSmiCid) {
- __ BranchIfNotSmi(right, slow_path->entry_label());
- } else if (right_cid == kSmiCid) {
- __ BranchIfNotSmi(left, slow_path->entry_label());
- } else {
- __ movq(TMP, left);
- __ orq(TMP, right);
- __ BranchIfNotSmi(TMP, slow_path->entry_label());
- }
- Register result = locs()->out(0).reg();
- switch (op_kind()) {
- case Token::kADD:
- __ movq(result, left);
- __ OBJ(add)(result, right);
- __ j(OVERFLOW, slow_path->entry_label());
- break;
- case Token::kSUB:
- __ movq(result, left);
- __ OBJ(sub)(result, right);
- __ j(OVERFLOW, slow_path->entry_label());
- break;
- case Token::kMUL:
- __ movq(result, left);
- __ SmiUntag(result);
- __ OBJ(imul)(result, right);
- __ j(OVERFLOW, slow_path->entry_label());
- break;
- case Token::kBIT_OR:
- ASSERT(left == result);
- __ orq(result, right);
- break;
- case Token::kBIT_AND:
- ASSERT(left == result);
- __ andq(result, right);
- break;
- case Token::kBIT_XOR:
- ASSERT(left == result);
- __ xorq(result, right);
- break;
- case Token::kSHL:
- ASSERT(result != right);
- ASSERT(locs()->temp(0).reg() == RCX);
- __ CompareObject(right, Smi::ZoneHandle(Smi::New(Smi::kBits)));
- __ j(ABOVE_EQUAL, slow_path->entry_label());
-
- __ movq(RCX, right);
- __ SmiUntag(RCX);
- __ movq(result, left);
- __ shlq(result, RCX);
- __ movq(TMP, result);
- __ OBJ(sar)(TMP, RCX);
- __ OBJ(cmp)(TMP, left);
- __ j(NOT_EQUAL, slow_path->entry_label());
- break;
- case Token::kSHR:
- ASSERT(result != right);
- ASSERT(locs()->temp(0).reg() == RCX);
- __ CompareObject(right, Smi::ZoneHandle(Smi::New(Smi::kBits)));
- __ j(ABOVE_EQUAL, slow_path->entry_label());
-
- __ movq(RCX, right);
- __ SmiUntag(RCX);
- __ movq(result, left);
- __ SmiUntag(result);
- __ OBJ(sar)(result, RCX);
- __ SmiTag(result);
- break;
- case Token::kUSHR: {
- ASSERT(result != right);
- ASSERT(locs()->temp(0).reg() == RCX);
- __ CompareObject(right, Smi::ZoneHandle(Smi::New(kBitsPerInt64)));
- __ j(ABOVE_EQUAL, slow_path->entry_label());
-
- __ movq(RCX, right);
- __ SmiUntag(RCX);
- __ movq(result, left);
- __ SmiUntagAndSignExtend(result);
- __ shrq(result, RCX);
- __ shlq(result, compiler::Immediate(1)); // SmiTag, keep hi bits.
- __ j(OVERFLOW, slow_path->entry_label());
-#if defined(DART_COMPRESSED_POINTERS)
- const Register temp = locs()->temp(0).reg();
- __ movsxd(temp, result);
- __ cmpq(temp, result);
- __ j(NOT_EQUAL, slow_path->entry_label());
-#endif // defined(DART_COMPRESSED_POINTERS)
- break;
- }
- default:
- UNIMPLEMENTED();
- }
- __ Bind(slow_path->exit_label());
-}
-
-class CheckedSmiComparisonSlowPath
- : public TemplateSlowPathCode<CheckedSmiComparisonInstr> {
- public:
- static constexpr intptr_t kNumSlowPathArgs = 2;
-
- CheckedSmiComparisonSlowPath(CheckedSmiComparisonInstr* instruction,
- Environment* env,
- intptr_t try_index,
- BranchLabels labels,
- bool merged)
- : TemplateSlowPathCode(instruction),
- try_index_(try_index),
- labels_(labels),
- merged_(merged),
- env_(env) {
- // The environment must either come from the comparison or the environment
- // was cleared from the comparison (and moved to a branch).
- ASSERT(env == instruction->env() ||
- (merged && instruction->env() == nullptr));
- }
-
- virtual void EmitNativeCode(FlowGraphCompiler* compiler) {
- if (compiler::Assembler::EmittingComments()) {
- __ Comment("slow path smi comparison");
- }
- __ Bind(entry_label());
- LocationSummary* locs = instruction()->locs();
- Register result = merged_ ? locs->temp(0).reg() : locs->out(0).reg();
- locs->live_registers()->Remove(Location::RegisterLocation(result));
-
- compiler->SaveLiveRegisters(locs);
- if (env_ != nullptr) {
- compiler->pending_deoptimization_env_ =
- compiler->SlowPathEnvironmentFor(env_, locs, kNumSlowPathArgs);
- }
- __ pushq(locs->in(0).reg());
- __ pushq(locs->in(1).reg());
-
- const auto& selector = String::Handle(instruction()->call()->Selector());
- const auto& arguments_descriptor =
- Array::Handle(ArgumentsDescriptor::NewBoxed(
- /*type_args_len=*/0, /*num_arguments=*/2));
-
- compiler->EmitMegamorphicInstanceCall(
- selector, arguments_descriptor, instruction()->call()->deopt_id(),
- instruction()->source(), locs, try_index_, kNumSlowPathArgs);
- __ MoveRegister(result, RAX);
- compiler->RestoreLiveRegisters(locs);
- compiler->pending_deoptimization_env_ = nullptr;
- if (merged_) {
- __ CompareObject(result, Bool::True());
- __ j(EQUAL, instruction()->is_negated() ? labels_.false_label
- : labels_.true_label);
- __ jmp(instruction()->is_negated() ? labels_.true_label
- : labels_.false_label);
- ASSERT(exit_label()->IsUnused());
- } else {
- ASSERT(!instruction()->is_negated());
- __ jmp(exit_label());
- }
- }
-
- private:
- intptr_t try_index_;
- BranchLabels labels_;
- bool merged_;
- Environment* env_;
-};
-
-LocationSummary* CheckedSmiComparisonInstr::MakeLocationSummary(
- Zone* zone,
- bool opt) const {
- const intptr_t kNumInputs = 2;
- const intptr_t kNumTemps = 1;
- LocationSummary* summary = new (zone) LocationSummary(
- zone, kNumInputs, kNumTemps, LocationSummary::kCallOnSlowPath);
- summary->set_in(0, Location::RequiresRegister());
- summary->set_in(1, Location::RequiresRegister());
- summary->set_temp(0, Location::RequiresRegister());
- summary->set_out(0, Location::RequiresRegister());
- return summary;
-}
-
-Condition CheckedSmiComparisonInstr::EmitComparisonCode(
- FlowGraphCompiler* compiler,
- BranchLabels labels) {
- return EmitSmiComparisonOp(compiler, *locs(), kind());
-}
-
-#define EMIT_SMI_CHECK \
- intptr_t left_cid = left()->Type()->ToCid(); \
- intptr_t right_cid = right()->Type()->ToCid(); \
- Register left = locs()->in(0).reg(); \
- Register right = locs()->in(1).reg(); \
- if (FLAG_use_slow_path) { \
- __ jmp(slow_path->entry_label()); \
- } else if (this->left()->definition() == this->right()->definition()) { \
- __ BranchIfNotSmi(left, slow_path->entry_label()); \
- } else if (left_cid == kSmiCid) { \
- __ BranchIfNotSmi(right, slow_path->entry_label()); \
- } else if (right_cid == kSmiCid) { \
- __ BranchIfNotSmi(left, slow_path->entry_label()); \
- } else { \
- __ movq(TMP, left); \
- __ orq(TMP, right); \
- __ BranchIfNotSmi(TMP, slow_path->entry_label()); \
- }
-
-void CheckedSmiComparisonInstr::EmitBranchCode(FlowGraphCompiler* compiler,
- BranchInstr* branch) {
- BranchLabels labels = compiler->CreateBranchLabels(branch);
- CheckedSmiComparisonSlowPath* slow_path = new CheckedSmiComparisonSlowPath(
- this, branch->env(), compiler->CurrentTryIndex(), labels,
- /* merged = */ true);
- compiler->AddSlowPathCode(slow_path);
- EMIT_SMI_CHECK;
- Condition true_condition = EmitComparisonCode(compiler, labels);
- ASSERT(true_condition != kInvalidCondition);
- EmitBranchOnCondition(compiler, true_condition, labels);
- // No need to bind slow_path->exit_label() as slow path exits through
- // true/false branch labels.
-}
-
-void CheckedSmiComparisonInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
- // Zone-allocate labels to pass them to slow-path which outlives local scope.
- compiler::Label* true_label = new (Z) compiler::Label();
- compiler::Label* false_label = new (Z) compiler::Label();
- compiler::Label done;
- BranchLabels labels = {true_label, false_label, false_label};
- // In case of negated comparison result of a slow path call should be negated.
- // For this purpose, 'merged' slow path is generated: it tests
- // result of a call and jumps directly to true or false label.
- CheckedSmiComparisonSlowPath* slow_path = new CheckedSmiComparisonSlowPath(
- this, env(), compiler->CurrentTryIndex(), labels,
- /* merged = */ is_negated());
- compiler->AddSlowPathCode(slow_path);
- EMIT_SMI_CHECK;
- Condition true_condition = EmitComparisonCode(compiler, labels);
- ASSERT(true_condition != kInvalidCondition);
- EmitBranchOnCondition(compiler, true_condition, labels,
- compiler::Assembler::kNearJump);
- Register result = locs()->out(0).reg();
- __ Bind(false_label);
- __ LoadObject(result, Bool::False());
- __ jmp(&done, compiler::Assembler::kNearJump);
- __ Bind(true_label);
- __ LoadObject(result, Bool::True());
- __ Bind(&done);
- // In case of negated comparison slow path exits through true/false labels.
- if (!is_negated()) {
- __ Bind(slow_path->exit_label());
- }
-}
-
static bool CanBeImmediate(const Object& constant) {
return constant.IsSmi() &&
compiler::Immediate(static_cast<int64_t>(constant.ptr())).is_int32();
diff --git a/runtime/vm/compiler/backend/type_propagator.cc b/runtime/vm/compiler/backend/type_propagator.cc
index c8e01c4..164e8bb 100644
--- a/runtime/vm/compiler/backend/type_propagator.cc
+++ b/runtime/vm/compiler/backend/type_propagator.cc
@@ -1595,30 +1595,6 @@
return CompileType::Int();
}
-CompileType CheckedSmiOpInstr::ComputeType() const {
- if (left()->Type()->IsNullableInt() && right()->Type()->IsNullableInt()) {
- const AbstractType& abstract_type =
- AbstractType::ZoneHandle(Type::IntType());
- TraceStrongModeType(this, abstract_type);
- return CompileType::FromAbstractType(abstract_type,
- CompileType::kNonNullable);
- } else {
- CompileType* type = call()->Type();
- TraceStrongModeType(this, type);
- return *type;
- }
-}
-
-bool CheckedSmiOpInstr::RecomputeType() {
- return UpdateType(ComputeType());
-}
-
-CompileType CheckedSmiComparisonInstr::ComputeType() const {
- CompileType* type = call()->Type();
- TraceStrongModeType(this, type);
- return *type;
-}
-
CompileType BoxIntegerInstr::ComputeType() const {
return ValueFitsSmi() ? CompileType::FromCid(kSmiCid) : CompileType::Int();
}
diff --git a/runtime/vm/os_thread_fuchsia.cc b/runtime/vm/os_thread_fuchsia.cc
index bf2469a..9108f5d 100644
--- a/runtime/vm/os_thread_fuchsia.cc
+++ b/runtime/vm/os_thread_fuchsia.cc
@@ -133,7 +133,7 @@
return 0;
}
-const ThreadId OSThread::kInvalidThreadId = ZX_KOID_INVALID;
+const ThreadId OSThread::kInvalidThreadId = ZX_HANDLE_INVALID;
const ThreadJoinId OSThread::kInvalidThreadJoinId =
static_cast<ThreadJoinId>(0);
@@ -163,15 +163,7 @@
}
ThreadId OSThread::GetCurrentThreadId() {
- zx_info_handle_basic_t info;
- zx_handle_t thread_handle = thrd_get_zx_handle(thrd_current());
- zx_status_t status =
- zx_object_get_info(thread_handle, ZX_INFO_HANDLE_BASIC, &info,
- sizeof(info), nullptr, nullptr);
- if (status != ZX_OK) {
- FATAL1("Failed to get thread koid: %s\n", zx_status_get_string(status));
- }
- return info.koid;
+ return thrd_get_zx_handle(thrd_current());
}
#ifdef SUPPORT_TIMELINE
diff --git a/runtime/vm/os_thread_fuchsia.h b/runtime/vm/os_thread_fuchsia.h
index da76e8e..e17b4a6 100644
--- a/runtime/vm/os_thread_fuchsia.h
+++ b/runtime/vm/os_thread_fuchsia.h
@@ -18,7 +18,7 @@
namespace dart {
typedef pthread_key_t ThreadLocalKey;
-typedef zx_koid_t ThreadId;
+typedef zx_handle_t ThreadId;
typedef pthread_t ThreadJoinId;
static const ThreadLocalKey kUnsetThreadLocalKey =
diff --git a/sdk/lib/_internal/vm/bin/socket_patch.dart b/sdk/lib/_internal/vm/bin/socket_patch.dart
index 339d9a7..62d56d5 100644
--- a/sdk/lib/_internal/vm/bin/socket_patch.dart
+++ b/sdk/lib/_internal/vm/bin/socket_patch.dart
@@ -491,7 +491,7 @@
// a HttpServer, a WebSocket connection, a process pipe, etc.
Object? owner;
- static Future<List<_InternetAddress>> lookup(String host,
+ static Future<List<InternetAddress>> lookup(String host,
{InternetAddressType type: InternetAddressType.any}) {
return _IOService._dispatch(_IOService.socketLookup, [host, type._value])
.then((response) {
@@ -506,9 +506,9 @@
});
}
- static Stream<List<_InternetAddress>> lookupAsStream(String host,
+ static Stream<List<InternetAddress>> lookupAsStream(String host,
{InternetAddressType type: InternetAddressType.any}) {
- final controller = StreamController<List<_InternetAddress>>();
+ final controller = StreamController<List<InternetAddress>>();
controller.onListen = () {
lookup(host, type: type).then((list) {
controller.add(list);
@@ -588,8 +588,8 @@
/// This avoids making single OS lookup request that internally does both IPv4
/// and IPv6 together, which on iOS sometimes seems to be taking unreasonably
/// long because of slow IPv6 lookup even though IPv4 lookup is fast.
- static Stream<List<_InternetAddress>> staggeredLookup(String host) {
- final controller = StreamController<List<_InternetAddress>>(sync: true);
+ static Stream<List<InternetAddress>> staggeredLookup(String host) {
+ final controller = StreamController<List<InternetAddress>>(sync: true);
controller.onListen = () {
// Completed when there are no further addresses, or when the returned
@@ -684,7 +684,7 @@
// so we run IPv4 and IPv6 name resolution in parallel(IPv6 slightly
// delayed so if IPv4 is successfully looked up, we don't do IPv6 look up
// at all) and grab first successfully resolved name we are able to connect to.
- final Stream<List<_InternetAddress>> stream =
+ final Stream<List<InternetAddress>> stream =
Platform.isIOS || staggeredLookupOverride
? staggeredLookup(hostname)
: lookupAsStream(hostname);
@@ -697,7 +697,7 @@
dynamic host,
int port,
_InternetAddress? source,
- Stream<List<_InternetAddress>> addresses) {
+ Stream<List<InternetAddress>> addresses) {
// Completer for result.
final result = new Completer<_NativeSocket>();
// Error, set if an error occurs.
@@ -713,14 +713,14 @@
Timer? timer;
// Addresses arrived from lookup stream, but haven't been tried to connect
// to yet due to Timer-based throttling.
- final pendingLookedUp = Queue<_InternetAddress>();
+ final pendingLookedUp = Queue<InternetAddress>();
// When deciding how to handle errors we need to know whether more
// addresses potentially are coming from the lookup stream.
bool isLookedUpStreamClosed = false;
- late StreamSubscription<List<_InternetAddress>> addressesSubscription;
+ late StreamSubscription<List<InternetAddress>> addressesSubscription;
- Object? createConnection(_InternetAddress address, _InternetAddress? source,
+ Object? createConnection(InternetAddress address, _InternetAddress? source,
_NativeSocket socket) {
Object? connectionResult;
if (address.type == InternetAddressType.unix) {
@@ -736,19 +736,20 @@
connectionResult is Error ||
connectionResult is OSError);
} else {
+ final address_ = address as _InternetAddress;
if (source == null) {
connectionResult = socket.nativeCreateConnect(
- address._in_addr, port, address._scope_id);
+ address_._in_addr, port, address_._scope_id);
} else {
connectionResult = socket.nativeCreateBindConnect(
- address._in_addr, port, source._in_addr, address._scope_id);
+ address_._in_addr, port, source._in_addr, address_._scope_id);
}
assert(connectionResult == true || connectionResult is OSError);
}
return connectionResult;
}
- createConnectionError(Object? connectionResult, _InternetAddress address,
+ createConnectionError(Object? connectionResult, InternetAddress address,
int port, _NativeSocket socket) {
if (connectionResult is OSError) {
final errorCode = connectionResult.errorCode;
diff --git a/tests/standalone/io/address_lookup_test.dart b/tests/standalone/io/address_lookup_test.dart
new file mode 100644
index 0000000..74b76c5
--- /dev/null
+++ b/tests/standalone/io/address_lookup_test.dart
@@ -0,0 +1,24 @@
+// Copyright (c) 2021, 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.
+//
+// Verifies that one can provide timeout handler for InternetAddress.lookup,
+// which was reported broken https://github.com/dart-lang/sdk/issues/45542.
+
+import "dart:io";
+
+import "package:async_helper/async_helper.dart";
+import "package:expect/expect.dart";
+
+void main() async {
+ asyncStart();
+ final result = <InternetAddress>[];
+ try {
+ result.addAll(await InternetAddress.lookup("some.bad.host.name.7654321")
+ .timeout(const Duration(milliseconds: 1), onTimeout: () => []));
+ } catch (e) {
+ print('managed to fail with $e lookup before timeout');
+ }
+ Expect.isTrue(result.isEmpty);
+ asyncEnd();
+}
diff --git a/tests/standalone_2/io/address_lookup_test.dart b/tests/standalone_2/io/address_lookup_test.dart
new file mode 100644
index 0000000..74b76c5
--- /dev/null
+++ b/tests/standalone_2/io/address_lookup_test.dart
@@ -0,0 +1,24 @@
+// Copyright (c) 2021, 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.
+//
+// Verifies that one can provide timeout handler for InternetAddress.lookup,
+// which was reported broken https://github.com/dart-lang/sdk/issues/45542.
+
+import "dart:io";
+
+import "package:async_helper/async_helper.dart";
+import "package:expect/expect.dart";
+
+void main() async {
+ asyncStart();
+ final result = <InternetAddress>[];
+ try {
+ result.addAll(await InternetAddress.lookup("some.bad.host.name.7654321")
+ .timeout(const Duration(milliseconds: 1), onTimeout: () => []));
+ } catch (e) {
+ print('managed to fail with $e lookup before timeout');
+ }
+ Expect.isTrue(result.isEmpty);
+ asyncEnd();
+}
diff --git a/tools/VERSION b/tools/VERSION
index 7096fe2..079095e 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 13
PATCH 0
-PRERELEASE 187
+PRERELEASE 188
PRERELEASE_PATCH 0
\ No newline at end of file