[dart2wasm] Handle is/as checks that only require nullability check in backend.
Up to Patchset 4 reverts commit 36fb02decad14ae79e6e410fa46340cd9502cada
Remaining Patchsets implement specialized support in is/as check
implementation to handle cases where only a null check is required. The
most common case in iterators:
class <...>Iterator<T> {
T? _current;
T get current => _current as T;
}
This ensures we're never emitting the general subtype checking code as
this only requres checking whether the object is non-`null` or whether
the destination type is declared nullable.
The asymmetry between `is` and `as` checks comes due to the fact that
the `as` checks have to (for the slow case) materialize a type object
for a nice error message.
Issue https://github.com/dart-lang/sdk/issues/55516
Change-Id: Ie4a845816ec4eda37a5a2e78cac0aeda0e411abd
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/381482
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
diff --git a/pkg/dart2wasm/lib/code_generator.dart b/pkg/dart2wasm/lib/code_generator.dart
index 94d3fb2..e1d17b9 100644
--- a/pkg/dart2wasm/lib/code_generator.dart
+++ b/pkg/dart2wasm/lib/code_generator.dart
@@ -2834,8 +2834,8 @@
? translator.topInfo.nullableType
: translator.topInfo.nonNullableType;
wrap(node.operand, boxedOperandType);
- return types.emitAsCheck(
- this, node.type, operandType, boxedOperandType, node.location);
+ return types.emitAsCheck(this, node.isCovarianceCheck, node.type,
+ operandType, boxedOperandType, node.location);
}
@override
@@ -2952,7 +2952,7 @@
if (translator.options.minify) {
// We don't need to include the name in the error message, so we can use
// the optimized `as` checks.
- types.emitAsCheck(this, testedAgainstType,
+ types.emitAsCheck(this, false, testedAgainstType,
translator.coreTypes.objectNullableRawType, argumentType);
b.drop();
} else {
diff --git a/pkg/dart2wasm/lib/kernel_nodes.dart b/pkg/dart2wasm/lib/kernel_nodes.dart
index 91d52bb..31178f4 100644
--- a/pkg/dart2wasm/lib/kernel_nodes.dart
+++ b/pkg/dart2wasm/lib/kernel_nodes.dart
@@ -312,6 +312,8 @@
index.getProcedure("dart:core", '_Closure', "_getClosureRuntimeType");
late final Procedure getMasqueradedRuntimeType =
index.getTopLevelProcedure("dart:core", "_getMasqueradedRuntimeType");
+ late final Procedure isNullabilityCheck =
+ index.getTopLevelProcedure("dart:core", "_isNullabilityCheck");
late final Procedure isSubtype =
index.getTopLevelProcedure("dart:core", "_isSubtype");
late final Procedure isInterfaceSubtype =
diff --git a/pkg/dart2wasm/lib/transformers.dart b/pkg/dart2wasm/lib/transformers.dart
index 9956bc5..ef049a0 100644
--- a/pkg/dart2wasm/lib/transformers.dart
+++ b/pkg/dart2wasm/lib/transformers.dart
@@ -8,8 +8,6 @@
import 'package:kernel/core_types.dart';
import 'package:kernel/type_algebra.dart';
import 'package:kernel/type_environment.dart';
-import 'package:vm/modular/transformations/type_casts_optimizer.dart'
- as typeCastsOptimizer show transformAsExpression;
import 'list_factory_specializer.dart';
@@ -727,12 +725,6 @@
node.transformChildren(this);
return node.receiver;
}
-
- @override
- TreeNode visitAsExpression(AsExpression node) {
- node.transformChildren(this);
- return typeCastsOptimizer.transformAsExpression(node, typeContext);
- }
}
class _AsyncStarFrame {
diff --git a/pkg/dart2wasm/lib/types.dart b/pkg/dart2wasm/lib/types.dart
index 0a88619..93fe46c 100644
--- a/pkg/dart2wasm/lib/types.dart
+++ b/pkg/dart2wasm/lib/types.dart
@@ -387,9 +387,11 @@
operandTemp = b.addLocal(translator.topInfo.nullableType);
b.local_tee(operandTemp);
}
+ final checkOnlyNullAssignability =
+ _requiresOnlyNullAssignabilityCheck(operandType, testedAgainstType);
final (typeToCheck, :checkArguments) =
_canUseTypeCheckHelper(testedAgainstType, operandType);
- if (typeToCheck != null) {
+ if (!checkOnlyNullAssignability && typeToCheck != null) {
if (checkArguments) {
for (final typeArgument in typeToCheck.typeArguments) {
makeType(codeGen, typeArgument);
@@ -403,24 +405,35 @@
final typeClassInfo =
translator.classInfo[testedAgainstType.classNode]!;
final typeArguments = testedAgainstType.typeArguments;
- b.i32_const(encodedNullability(testedAgainstType));
- b.i32_const(typeClassInfo.classId);
- if (typeArguments.isEmpty) {
- codeGen.call(translator.isInterfaceSubtype0.reference);
- } else if (typeArguments.length == 1) {
- makeType(codeGen, typeArguments[0]);
- codeGen.call(translator.isInterfaceSubtype1.reference);
- } else if (typeArguments.length == 2) {
- makeType(codeGen, typeArguments[0]);
- makeType(codeGen, typeArguments[1]);
- codeGen.call(translator.isInterfaceSubtype2.reference);
+ if (checkOnlyNullAssignability) {
+ b.i32_const(encodedNullability(testedAgainstType));
+ codeGen.call(translator.isNullabilityCheck.reference);
} else {
- _makeTypeArray(codeGen, typeArguments);
- codeGen.call(translator.isInterfaceSubtype.reference);
+ b.i32_const(encodedNullability(testedAgainstType));
+ b.i32_const(typeClassInfo.classId);
+ if (typeArguments.isEmpty) {
+ codeGen.call(translator.isInterfaceSubtype0.reference);
+ } else if (typeArguments.length == 1) {
+ makeType(codeGen, typeArguments[0]);
+ codeGen.call(translator.isInterfaceSubtype1.reference);
+ } else if (typeArguments.length == 2) {
+ makeType(codeGen, typeArguments[0]);
+ makeType(codeGen, typeArguments[1]);
+ codeGen.call(translator.isInterfaceSubtype2.reference);
+ } else {
+ _makeTypeArray(codeGen, typeArguments);
+ codeGen.call(translator.isInterfaceSubtype.reference);
+ }
}
} else {
makeType(codeGen, testedAgainstType);
- codeGen.call(translator.isSubtype.reference);
+ if (checkOnlyNullAssignability) {
+ b.struct_get(typeClassInfo.struct,
+ translator.fieldIndex[translator.typeIsDeclaredNullableField]!);
+ codeGen.call(translator.isNullabilityCheck.reference);
+ } else {
+ codeGen.call(translator.isSubtype.reference);
+ }
}
}
if (translator.options.verifyTypeChecks) {
@@ -438,14 +451,22 @@
}
}
- w.ValueType emitAsCheck(AstCodeGenerator codeGen, DartType testedAgainstType,
- DartType operandType, w.RefType boxedOperandType,
+ w.ValueType emitAsCheck(
+ AstCodeGenerator codeGen,
+ bool isCovarianceCheck,
+ DartType testedAgainstType,
+ DartType operandType,
+ w.RefType boxedOperandType,
[Location? location]) {
final b = codeGen.b;
+ // Keep casts inserted by the CFE to ensure soundness of covariant types.
+ final checkOnlyNullAssignability = !isCovarianceCheck &&
+ _requiresOnlyNullAssignabilityCheck(operandType, testedAgainstType);
+
final (typeToCheck, :checkArguments) =
_canUseTypeCheckHelper(testedAgainstType, operandType);
- if (typeToCheck != null) {
+ if (!checkOnlyNullAssignability && typeToCheck != null) {
if (checkArguments) {
for (final typeArgument in typeToCheck.typeArguments) {
makeType(codeGen, typeArgument);
@@ -460,6 +481,7 @@
b.local_tee(operand);
late List<w.ValueType> outputsToDrop;
+ b.i32_const(encodedBool(checkOnlyNullAssignability));
if (testedAgainstType is InterfaceType &&
classForType(testedAgainstType) == translator.interfaceTypeClass) {
final typeClassInfo = translator.classInfo[testedAgainstType.classNode]!;
@@ -518,6 +540,17 @@
return (null, checkArguments: false);
}
+ bool _requiresOnlyNullAssignabilityCheck(
+ DartType operandType, DartType testedAgainstType) {
+ // We may only need to check that either of these hold:
+ // * value is non-null
+ // * value is null and the type is nullable.
+ return translator.typeEnvironment.isSubtypeOf(
+ operandType,
+ testedAgainstType.withDeclaredNullability(Nullability.nonNullable),
+ type_env.SubtypeCheckMode.withNullabilities);
+ }
+
bool _staticTypesEnsureTypeArgumentsMatch(
InterfaceType testedAgainstType, InterfaceType operandType) {
assert(testedAgainstType.typeArguments.isNotEmpty);
@@ -640,6 +673,8 @@
int encodedNullability(DartType type) =>
type.declaredNullability == Nullability.nullable ? 1 : 0;
+int encodedBool(bool value) => value ? 1 : 0;
+
class IsCheckerCallTarget extends CallTarget {
final Translator translator;
diff --git a/pkg/front_end/testcases/dart2wasm/inference_update_2/issue52452.dart.strong.transformed.expect b/pkg/front_end/testcases/dart2wasm/inference_update_2/issue52452.dart.strong.transformed.expect
index 52ea82d..9324da8 100644
--- a/pkg/front_end/testcases/dart2wasm/inference_update_2/issue52452.dart.strong.transformed.expect
+++ b/pkg/front_end/testcases/dart2wasm/inference_update_2/issue52452.dart.strong.transformed.expect
@@ -128,13 +128,13 @@
}
static method testNoConflictWithNoSuchMethodForwarderIfImplementedInMixin1(final self::C c) → void {
if(!(c.{self::C::_f3}{core::int?} == null)) {
- final core::int x = let core::int? #t3 = c.{self::C::_f3}{core::int?} in #t3 == null ?{core::int} #t3 as{Unchecked} core::int : #t3{core::int};
+ final core::int x = c.{self::C::_f3}{core::int?} as{Unchecked} core::int;
self::acceptsInt(x);
}
}
static method testNoConflictWithNoSuchMethodForwarderIfImplementedInMixin2(final self::C c) → void {
if(!(c.{self::C::_f4}{core::int?} == null)) {
- final core::int x = let core::int? #t4 = c.{self::C::_f4}{core::int?} in #t4 == null ?{core::int} #t4 as{Unchecked} core::int : #t4{core::int};
+ final core::int x = c.{self::C::_f4}{core::int?} as{Unchecked} core::int;
self::acceptsInt(x);
}
}
diff --git a/sdk/lib/_internal/wasm/lib/type.dart b/sdk/lib/_internal/wasm/lib/type.dart
index 2bd1bba..838c9eb 100644
--- a/sdk/lib/_internal/wasm/lib/type.dart
+++ b/sdk/lib/_internal/wasm/lib/type.dart
@@ -1261,10 +1261,17 @@
@pragma("wasm:entry-point")
@pragma("wasm:prefer-inline")
+bool _isNullabilityCheck(Object? o, bool isDeclaredNullable) {
+ if (o == null) return isDeclaredNullable;
+ return true;
+}
+
+@pragma("wasm:entry-point")
+@pragma("wasm:prefer-inline")
bool _isInterfaceSubtype(Object? o, bool isDeclaredNullable, WasmI32 tId,
WasmArray<_Type> typeArguments) {
+ if (o == null) return isDeclaredNullable;
final t = _InterfaceType(tId, isDeclaredNullable, typeArguments);
- if (o == null) return t.isDeclaredNullable;
return t._checkInstance(o);
}
@@ -1300,44 +1307,65 @@
@pragma("wasm:entry-point")
@pragma("wasm:prefer-inline")
-void _asSubtype(Object? o, _Type t) {
- if (!_isSubtype(o, t)) {
+void _asSubtype(Object? o, bool onlyNullabilityCheck, _Type t) {
+ final bool success =
+ (onlyNullabilityCheck && _isNullabilityCheck(o, t.isDeclaredNullable)) ||
+ _isSubtype(o, t);
+ if (!success) {
_TypeError._throwAsCheckError(o, t);
}
}
@pragma("wasm:entry-point")
@pragma("wasm:prefer-inline")
-void _asInterfaceSubtype(Object? o, bool isDeclaredNullable, WasmI32 tId,
- WasmArray<_Type> typeArguments) {
- if (!_isInterfaceSubtype(o, isDeclaredNullable, tId, typeArguments)) {
+void _asInterfaceSubtype(Object? o, bool onlyNullabilityCheck,
+ bool isDeclaredNullable, WasmI32 tId, WasmArray<_Type> typeArguments) {
+ final bool success =
+ (onlyNullabilityCheck && _isNullabilityCheck(o, isDeclaredNullable)) ||
+ _isInterfaceSubtype(o, isDeclaredNullable, tId, typeArguments);
+ if (!success) {
_throwInterfaceTypeAsCheckError(o, isDeclaredNullable, tId, typeArguments);
}
}
@pragma("wasm:entry-point")
@pragma("wasm:prefer-inline")
-void _asInterfaceSubtype0(Object? o, bool isDeclaredNullable, WasmI32 tId) {
- if (!_isInterfaceSubtype0(o, isDeclaredNullable, tId)) {
+void _asInterfaceSubtype0(Object? o, bool onlyNullabilityCheck,
+ bool isDeclaredNullable, WasmI32 tId) {
+ final bool success =
+ (onlyNullabilityCheck && _isNullabilityCheck(o, isDeclaredNullable)) ||
+ _isInterfaceSubtype0(o, isDeclaredNullable, tId);
+ if (!success) {
_throwInterfaceTypeAsCheckError0(o, isDeclaredNullable, tId);
}
}
@pragma("wasm:entry-point")
@pragma("wasm:prefer-inline")
-void _asInterfaceSubtype1(
- Object? o, bool isDeclaredNullable, WasmI32 tId, _Type typeArgument0) {
- if (!_isInterfaceSubtype1(o, isDeclaredNullable, tId, typeArgument0)) {
+void _asInterfaceSubtype1(Object? o, bool onlyNullabilityCheck,
+ bool isDeclaredNullable, WasmI32 tId, _Type typeArgument0) {
+ final bool success =
+ (onlyNullabilityCheck && _isNullabilityCheck(o, isDeclaredNullable)) ||
+ _isInterfaceSubtype1(o, isDeclaredNullable, tId, typeArgument0);
+ if (!success) {
_throwInterfaceTypeAsCheckError1(o, isDeclaredNullable, tId, typeArgument0);
}
}
@pragma("wasm:entry-point")
@pragma("wasm:prefer-inline")
-void _asInterfaceSubtype2(Object? o, bool isDeclaredNullable, WasmI32 tId,
- _Type typeArgument0, _Type typeArgument1) {
- if (!_isInterfaceSubtype2(
- o, isDeclaredNullable, tId, typeArgument0, typeArgument1)) {
+void _asInterfaceSubtype2(
+ Object? o,
+ bool onlyNullabilityCheck,
+ bool isDeclaredNullable,
+ WasmI32 tId,
+ _Type typeArgument0,
+ _Type typeArgument1) {
+ final bool success =
+ (onlyNullabilityCheck && _isNullabilityCheck(o, isDeclaredNullable)) ||
+ _isInterfaceSubtype2(
+ o, isDeclaredNullable, tId, typeArgument0, typeArgument1);
+ if (!success) {
_throwInterfaceTypeAsCheckError2(
o, isDeclaredNullable, tId, typeArgument0, typeArgument1);
}