Skip to content

Commit 8c07aa5

Browse files
mkustermannCommit Queue
authored and
Commit Queue
committed
[dart2wasm] Handle is/as checks that only require nullability check in backend.
Up to Patchset 4 reverts commit 36fb02d 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 #55516 Change-Id: Ie4a845816ec4eda37a5a2e78cac0aeda0e411abd Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/381482 Reviewed-by: Ömer Ağacan <[email protected]> Commit-Queue: Martin Kustermann <[email protected]>
1 parent a645b6b commit 8c07aa5

File tree

6 files changed

+103
-46
lines changed

6 files changed

+103
-46
lines changed

pkg/dart2wasm/lib/code_generator.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2834,8 +2834,8 @@ abstract class AstCodeGenerator
28342834
? translator.topInfo.nullableType
28352835
: translator.topInfo.nonNullableType;
28362836
wrap(node.operand, boxedOperandType);
2837-
return types.emitAsCheck(
2838-
this, node.type, operandType, boxedOperandType, node.location);
2837+
return types.emitAsCheck(this, node.isCovarianceCheck, node.type,
2838+
operandType, boxedOperandType, node.location);
28392839
}
28402840

28412841
@override
@@ -2952,7 +2952,7 @@ abstract class AstCodeGenerator
29522952
if (translator.options.minify) {
29532953
// We don't need to include the name in the error message, so we can use
29542954
// the optimized `as` checks.
2955-
types.emitAsCheck(this, testedAgainstType,
2955+
types.emitAsCheck(this, false, testedAgainstType,
29562956
translator.coreTypes.objectNullableRawType, argumentType);
29572957
b.drop();
29582958
} else {

pkg/dart2wasm/lib/kernel_nodes.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,8 @@ mixin KernelNodes {
312312
index.getProcedure("dart:core", '_Closure', "_getClosureRuntimeType");
313313
late final Procedure getMasqueradedRuntimeType =
314314
index.getTopLevelProcedure("dart:core", "_getMasqueradedRuntimeType");
315+
late final Procedure isNullabilityCheck =
316+
index.getTopLevelProcedure("dart:core", "_isNullabilityCheck");
315317
late final Procedure isSubtype =
316318
index.getTopLevelProcedure("dart:core", "_isSubtype");
317319
late final Procedure isInterfaceSubtype =

pkg/dart2wasm/lib/transformers.dart

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ import 'package:kernel/clone.dart';
88
import 'package:kernel/core_types.dart';
99
import 'package:kernel/type_algebra.dart';
1010
import 'package:kernel/type_environment.dart';
11-
import 'package:vm/modular/transformations/type_casts_optimizer.dart'
12-
as typeCastsOptimizer show transformAsExpression;
1311

1412
import 'list_factory_specializer.dart';
1513

@@ -727,12 +725,6 @@ class _WasmTransformer extends Transformer {
727725
node.transformChildren(this);
728726
return node.receiver;
729727
}
730-
731-
@override
732-
TreeNode visitAsExpression(AsExpression node) {
733-
node.transformChildren(this);
734-
return typeCastsOptimizer.transformAsExpression(node, typeContext);
735-
}
736728
}
737729

738730
class _AsyncStarFrame {

pkg/dart2wasm/lib/types.dart

Lines changed: 53 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -387,9 +387,11 @@ class Types {
387387
operandTemp = b.addLocal(translator.topInfo.nullableType);
388388
b.local_tee(operandTemp);
389389
}
390+
final checkOnlyNullAssignability =
391+
_requiresOnlyNullAssignabilityCheck(operandType, testedAgainstType);
390392
final (typeToCheck, :checkArguments) =
391393
_canUseTypeCheckHelper(testedAgainstType, operandType);
392-
if (typeToCheck != null) {
394+
if (!checkOnlyNullAssignability && typeToCheck != null) {
393395
if (checkArguments) {
394396
for (final typeArgument in typeToCheck.typeArguments) {
395397
makeType(codeGen, typeArgument);
@@ -403,24 +405,35 @@ class Types {
403405
final typeClassInfo =
404406
translator.classInfo[testedAgainstType.classNode]!;
405407
final typeArguments = testedAgainstType.typeArguments;
406-
b.i32_const(encodedNullability(testedAgainstType));
407-
b.i32_const(typeClassInfo.classId);
408-
if (typeArguments.isEmpty) {
409-
codeGen.call(translator.isInterfaceSubtype0.reference);
410-
} else if (typeArguments.length == 1) {
411-
makeType(codeGen, typeArguments[0]);
412-
codeGen.call(translator.isInterfaceSubtype1.reference);
413-
} else if (typeArguments.length == 2) {
414-
makeType(codeGen, typeArguments[0]);
415-
makeType(codeGen, typeArguments[1]);
416-
codeGen.call(translator.isInterfaceSubtype2.reference);
408+
if (checkOnlyNullAssignability) {
409+
b.i32_const(encodedNullability(testedAgainstType));
410+
codeGen.call(translator.isNullabilityCheck.reference);
417411
} else {
418-
_makeTypeArray(codeGen, typeArguments);
419-
codeGen.call(translator.isInterfaceSubtype.reference);
412+
b.i32_const(encodedNullability(testedAgainstType));
413+
b.i32_const(typeClassInfo.classId);
414+
if (typeArguments.isEmpty) {
415+
codeGen.call(translator.isInterfaceSubtype0.reference);
416+
} else if (typeArguments.length == 1) {
417+
makeType(codeGen, typeArguments[0]);
418+
codeGen.call(translator.isInterfaceSubtype1.reference);
419+
} else if (typeArguments.length == 2) {
420+
makeType(codeGen, typeArguments[0]);
421+
makeType(codeGen, typeArguments[1]);
422+
codeGen.call(translator.isInterfaceSubtype2.reference);
423+
} else {
424+
_makeTypeArray(codeGen, typeArguments);
425+
codeGen.call(translator.isInterfaceSubtype.reference);
426+
}
420427
}
421428
} else {
422429
makeType(codeGen, testedAgainstType);
423-
codeGen.call(translator.isSubtype.reference);
430+
if (checkOnlyNullAssignability) {
431+
b.struct_get(typeClassInfo.struct,
432+
translator.fieldIndex[translator.typeIsDeclaredNullableField]!);
433+
codeGen.call(translator.isNullabilityCheck.reference);
434+
} else {
435+
codeGen.call(translator.isSubtype.reference);
436+
}
424437
}
425438
}
426439
if (translator.options.verifyTypeChecks) {
@@ -438,14 +451,22 @@ class Types {
438451
}
439452
}
440453

441-
w.ValueType emitAsCheck(AstCodeGenerator codeGen, DartType testedAgainstType,
442-
DartType operandType, w.RefType boxedOperandType,
454+
w.ValueType emitAsCheck(
455+
AstCodeGenerator codeGen,
456+
bool isCovarianceCheck,
457+
DartType testedAgainstType,
458+
DartType operandType,
459+
w.RefType boxedOperandType,
443460
[Location? location]) {
444461
final b = codeGen.b;
445462

463+
// Keep casts inserted by the CFE to ensure soundness of covariant types.
464+
final checkOnlyNullAssignability = !isCovarianceCheck &&
465+
_requiresOnlyNullAssignabilityCheck(operandType, testedAgainstType);
466+
446467
final (typeToCheck, :checkArguments) =
447468
_canUseTypeCheckHelper(testedAgainstType, operandType);
448-
if (typeToCheck != null) {
469+
if (!checkOnlyNullAssignability && typeToCheck != null) {
449470
if (checkArguments) {
450471
for (final typeArgument in typeToCheck.typeArguments) {
451472
makeType(codeGen, typeArgument);
@@ -460,6 +481,7 @@ class Types {
460481
b.local_tee(operand);
461482

462483
late List<w.ValueType> outputsToDrop;
484+
b.i32_const(encodedBool(checkOnlyNullAssignability));
463485
if (testedAgainstType is InterfaceType &&
464486
classForType(testedAgainstType) == translator.interfaceTypeClass) {
465487
final typeClassInfo = translator.classInfo[testedAgainstType.classNode]!;
@@ -518,6 +540,17 @@ class Types {
518540
return (null, checkArguments: false);
519541
}
520542

543+
bool _requiresOnlyNullAssignabilityCheck(
544+
DartType operandType, DartType testedAgainstType) {
545+
// We may only need to check that either of these hold:
546+
// * value is non-null
547+
// * value is null and the type is nullable.
548+
return translator.typeEnvironment.isSubtypeOf(
549+
operandType,
550+
testedAgainstType.withDeclaredNullability(Nullability.nonNullable),
551+
type_env.SubtypeCheckMode.withNullabilities);
552+
}
553+
521554
bool _staticTypesEnsureTypeArgumentsMatch(
522555
InterfaceType testedAgainstType, InterfaceType operandType) {
523556
assert(testedAgainstType.typeArguments.isNotEmpty);
@@ -640,6 +673,8 @@ class Types {
640673
int encodedNullability(DartType type) =>
641674
type.declaredNullability == Nullability.nullable ? 1 : 0;
642675

676+
int encodedBool(bool value) => value ? 1 : 0;
677+
643678
class IsCheckerCallTarget extends CallTarget {
644679
final Translator translator;
645680

pkg/front_end/testcases/dart2wasm/inference_update_2/issue52452.dart.strong.transformed.expect

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,13 +128,13 @@ static method testConflictWithNoSuchMethodForwarderIfImplementedInMixin(final se
128128
}
129129
static method testNoConflictWithNoSuchMethodForwarderIfImplementedInMixin1(final self::C c) → void {
130130
if(!(c.{self::C::_f3}{core::int?} == null)) {
131-
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};
131+
final core::int x = c.{self::C::_f3}{core::int?} as{Unchecked} core::int;
132132
self::acceptsInt(x);
133133
}
134134
}
135135
static method testNoConflictWithNoSuchMethodForwarderIfImplementedInMixin2(final self::C c) → void {
136136
if(!(c.{self::C::_f4}{core::int?} == null)) {
137-
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};
137+
final core::int x = c.{self::C::_f4}{core::int?} as{Unchecked} core::int;
138138
self::acceptsInt(x);
139139
}
140140
}

sdk/lib/_internal/wasm/lib/type.dart

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1259,12 +1259,19 @@ bool _isSubtype(Object? o, _Type t) {
12591259
return t._checkInstance(o);
12601260
}
12611261

1262+
@pragma("wasm:entry-point")
1263+
@pragma("wasm:prefer-inline")
1264+
bool _isNullabilityCheck(Object? o, bool isDeclaredNullable) {
1265+
if (o == null) return isDeclaredNullable;
1266+
return true;
1267+
}
1268+
12621269
@pragma("wasm:entry-point")
12631270
@pragma("wasm:prefer-inline")
12641271
bool _isInterfaceSubtype(Object? o, bool isDeclaredNullable, WasmI32 tId,
12651272
WasmArray<_Type> typeArguments) {
1273+
if (o == null) return isDeclaredNullable;
12661274
final t = _InterfaceType(tId, isDeclaredNullable, typeArguments);
1267-
if (o == null) return t.isDeclaredNullable;
12681275
return t._checkInstance(o);
12691276
}
12701277

@@ -1300,44 +1307,65 @@ bool _isTypeSubtype(_Type s, _Type t) {
13001307

13011308
@pragma("wasm:entry-point")
13021309
@pragma("wasm:prefer-inline")
1303-
void _asSubtype(Object? o, _Type t) {
1304-
if (!_isSubtype(o, t)) {
1310+
void _asSubtype(Object? o, bool onlyNullabilityCheck, _Type t) {
1311+
final bool success =
1312+
(onlyNullabilityCheck && _isNullabilityCheck(o, t.isDeclaredNullable)) ||
1313+
_isSubtype(o, t);
1314+
if (!success) {
13051315
_TypeError._throwAsCheckError(o, t);
13061316
}
13071317
}
13081318

13091319
@pragma("wasm:entry-point")
13101320
@pragma("wasm:prefer-inline")
1311-
void _asInterfaceSubtype(Object? o, bool isDeclaredNullable, WasmI32 tId,
1312-
WasmArray<_Type> typeArguments) {
1313-
if (!_isInterfaceSubtype(o, isDeclaredNullable, tId, typeArguments)) {
1321+
void _asInterfaceSubtype(Object? o, bool onlyNullabilityCheck,
1322+
bool isDeclaredNullable, WasmI32 tId, WasmArray<_Type> typeArguments) {
1323+
final bool success =
1324+
(onlyNullabilityCheck && _isNullabilityCheck(o, isDeclaredNullable)) ||
1325+
_isInterfaceSubtype(o, isDeclaredNullable, tId, typeArguments);
1326+
if (!success) {
13141327
_throwInterfaceTypeAsCheckError(o, isDeclaredNullable, tId, typeArguments);
13151328
}
13161329
}
13171330

13181331
@pragma("wasm:entry-point")
13191332
@pragma("wasm:prefer-inline")
1320-
void _asInterfaceSubtype0(Object? o, bool isDeclaredNullable, WasmI32 tId) {
1321-
if (!_isInterfaceSubtype0(o, isDeclaredNullable, tId)) {
1333+
void _asInterfaceSubtype0(Object? o, bool onlyNullabilityCheck,
1334+
bool isDeclaredNullable, WasmI32 tId) {
1335+
final bool success =
1336+
(onlyNullabilityCheck && _isNullabilityCheck(o, isDeclaredNullable)) ||
1337+
_isInterfaceSubtype0(o, isDeclaredNullable, tId);
1338+
if (!success) {
13221339
_throwInterfaceTypeAsCheckError0(o, isDeclaredNullable, tId);
13231340
}
13241341
}
13251342

13261343
@pragma("wasm:entry-point")
13271344
@pragma("wasm:prefer-inline")
1328-
void _asInterfaceSubtype1(
1329-
Object? o, bool isDeclaredNullable, WasmI32 tId, _Type typeArgument0) {
1330-
if (!_isInterfaceSubtype1(o, isDeclaredNullable, tId, typeArgument0)) {
1345+
void _asInterfaceSubtype1(Object? o, bool onlyNullabilityCheck,
1346+
bool isDeclaredNullable, WasmI32 tId, _Type typeArgument0) {
1347+
final bool success =
1348+
(onlyNullabilityCheck && _isNullabilityCheck(o, isDeclaredNullable)) ||
1349+
_isInterfaceSubtype1(o, isDeclaredNullable, tId, typeArgument0);
1350+
if (!success) {
13311351
_throwInterfaceTypeAsCheckError1(o, isDeclaredNullable, tId, typeArgument0);
13321352
}
13331353
}
13341354

13351355
@pragma("wasm:entry-point")
13361356
@pragma("wasm:prefer-inline")
1337-
void _asInterfaceSubtype2(Object? o, bool isDeclaredNullable, WasmI32 tId,
1338-
_Type typeArgument0, _Type typeArgument1) {
1339-
if (!_isInterfaceSubtype2(
1340-
o, isDeclaredNullable, tId, typeArgument0, typeArgument1)) {
1357+
void _asInterfaceSubtype2(
1358+
Object? o,
1359+
bool onlyNullabilityCheck,
1360+
bool isDeclaredNullable,
1361+
WasmI32 tId,
1362+
_Type typeArgument0,
1363+
_Type typeArgument1) {
1364+
final bool success =
1365+
(onlyNullabilityCheck && _isNullabilityCheck(o, isDeclaredNullable)) ||
1366+
_isInterfaceSubtype2(
1367+
o, isDeclaredNullable, tId, typeArgument0, typeArgument1);
1368+
if (!success) {
13411369
_throwInterfaceTypeAsCheckError2(
13421370
o, isDeclaredNullable, tId, typeArgument0, typeArgument1);
13431371
}

0 commit comments

Comments
 (0)