diff --git a/pkg/_js_interop_checks/lib/src/transformations/js_util_wasm_optimizer.dart b/pkg/_js_interop_checks/lib/src/transformations/js_util_wasm_optimizer.dart index 89e3b5a92b2..e8cc373d1e3 100644 --- a/pkg/_js_interop_checks/lib/src/transformations/js_util_wasm_optimizer.dart +++ b/pkg/_js_interop_checks/lib/src/transformations/js_util_wasm_optimizer.dart @@ -297,7 +297,8 @@ class JsUtilWasmOptimizer extends Transformer { Expression getObjectOffGlobalThis(Procedure node, List selectors) { Expression currentTarget = _globalThis; for (String selector in selectors) { - currentTarget = _getProperty(node, currentTarget, selector); + currentTarget = _getProperty(node, currentTarget, selector, + typeArgument: _nonNullableObjectType); } return currentTarget; } @@ -315,8 +316,9 @@ class JsUtilWasmOptimizer extends Transformer { type: _nonNullableObjectType); body.add(object); for (VariableDeclaration variable in node.function.namedParameters) { - body.add(ExpressionStatement( - _setProperty(node, VariableGet(object), variable.name!, variable))); + body.add(ExpressionStatement(_setProperty( + node, VariableGet(object), variable.name!, variable, + typeArgument: variable.type))); } body.add(ReturnStatement(VariableGet(object))); return Block(body); @@ -349,12 +351,12 @@ class JsUtilWasmOptimizer extends Transformer { /// /// The new [Expression] is equivalent to: /// `js_util.getProperty([object], [getterName])`. - Expression _getProperty( - Procedure node, Expression object, String getterName) => + Expression _getProperty(Procedure node, Expression object, String getterName, + {DartType? typeArgument}) => StaticInvocation( _getPropertyTarget, Arguments([object, StringLiteral(getterName)], - types: [node.function.returnType])) + types: [typeArgument ?? node.function.returnType])) ..fileOffset = node.fileOffset; /// Returns a new function body for the given [node] external getter. @@ -377,11 +379,11 @@ class JsUtilWasmOptimizer extends Transformer { /// The new [Expression] is equivalent to: /// `js_util.setProperty([object], [setterName], [value])`. Expression _setProperty(Procedure node, Expression object, String setterName, - VariableDeclaration value) => + VariableDeclaration value, {DartType? typeArgument}) => StaticInvocation( _setPropertyTarget, Arguments([object, StringLiteral(setterName), VariableGet(value)], - types: [node.function.returnType])) + types: [typeArgument ?? node.function.returnType])) ..fileOffset = node.fileOffset; /// Returns a new function body for the given [node] external setter. diff --git a/pkg/dart2wasm/lib/constants.dart b/pkg/dart2wasm/lib/constants.dart index e62bf7f0ecf..2d2a59aaebf 100644 --- a/pkg/dart2wasm/lib/constants.dart +++ b/pkg/dart2wasm/lib/constants.dart @@ -803,8 +803,14 @@ class ConstantCreator extends ConstantVisitor { return createConstant(constant, info.nonNullableType, (function, b) { b.i32_const(info.classId); b.i32_const(initialIdentityHash); - types.encodeNullability(b, type); - b.i32_const(environmentIndex); + + // A type parameter's type nullability is undetermined when it's + // syntactically not declared nullable and the bound of the type + // parameter is nullable. Because we are encoding the declared + // nullability, we only declare a type parameter to be nullable if it is + // explicitly declared to be nullabe. + b.i32_const(type.declaredNullability == Nullability.nullable ? 1 : 0); + b.i64_const(environmentIndex); translator.struct_new(b, info); }); } else { diff --git a/pkg/dart2wasm/lib/intrinsics.dart b/pkg/dart2wasm/lib/intrinsics.dart index 68f9aeba509..9e5effc649b 100644 --- a/pkg/dart2wasm/lib/intrinsics.dart +++ b/pkg/dart2wasm/lib/intrinsics.dart @@ -593,7 +593,8 @@ class Intrinsifier { if (className != null) { List libAndClass = className.split("#"); Class cls = translator.libraries - .firstWhere((l) => l.name == libAndClass[0]) + .firstWhere( + (l) => l.name == libAndClass[0] && l.importUri.scheme == 'dart') .classes .firstWhere((c) => c.name == libAndClass[1]); int classId = translator.classInfo[cls]!.classId; @@ -682,6 +683,8 @@ class Intrinsifier { return translator.types.makeTypeRulesSupers(b); case "_getTypeRulesSubstitutions": return translator.types.makeTypeRulesSubstitutions(b); + case "_getTypeNames": + return translator.types.makeTypeNames(b); case "_getInterfaceTypeRuntimeType": Expression object = node.arguments.positional[0]; Expression typeArguments = node.arguments.positional[1]; @@ -793,6 +796,26 @@ class Intrinsifier { return w.NumType.f64; case "getID": return getID(node.arguments.positional.single); + case "makeListFixedLength": + ClassInfo receiverInfo = + translator.classInfo[translator.listBaseClass]!; + codeGen.wrap( + node.arguments.positional.single, receiverInfo.nonNullableType); + w.Local receiverLocal = + codeGen.function.addLocal(receiverInfo.nullableType); + b.local_tee(receiverLocal); + // We ignore the type argument and just update the classID of the + // receiver. + // TODO(joshualitt): If the amount of free space is significant, it + // might be worth doing a copy here. + ClassInfo topInfo = translator.topInfo; + ClassInfo fixedLengthListInfo = + translator.classInfo[translator.fixedLengthListClass]!; + b.i32_const(fixedLengthListInfo.classId); + b.struct_set(topInfo.struct, FieldIndex.classId); + b.local_get(receiverLocal); + b.ref_as_non_null(); + return fixedLengthListInfo.nonNullableType; } } diff --git a/pkg/dart2wasm/lib/translator.dart b/pkg/dart2wasm/lib/translator.dart index 8e6c7d90d6d..8cd848364c6 100644 --- a/pkg/dart2wasm/lib/translator.dart +++ b/pkg/dart2wasm/lib/translator.dart @@ -127,6 +127,7 @@ class Translator { late final Procedure hashImmutableIndexNullable; late final Procedure isSubtype; late final Procedure objectRuntimeType; + late final Procedure typeAsNullable; late final Map builtinTypes; late final Map boxedClasses; @@ -272,6 +273,9 @@ class Translator { objectRuntimeType = lookupCore("Object") .procedures .firstWhere((p) => p.name.text == "_runtimeType"); + typeAsNullable = lookupCore("_Type") + .procedures + .firstWhere((p) => p.name.text == "asNullable"); builtinTypes = { coreTypes.boolClass: w.NumType.i32, coreTypes.intClass: w.NumType.i64, diff --git a/pkg/dart2wasm/lib/types.dart b/pkg/dart2wasm/lib/types.dart index 50e42834627..1011b26a4da 100644 --- a/pkg/dart2wasm/lib/types.dart +++ b/pkg/dart2wasm/lib/types.dart @@ -63,6 +63,9 @@ class Types { late final List>> typeRulesSubstitutions = _buildTypeRulesSubstitutions(); + /// A list which maps class ID to the classes [String] name. + late final List typeNames = _buildTypeNames(); + Types(this.translator); w.ValueType classAndFieldToType(Class cls, int fieldIndex) => @@ -100,8 +103,14 @@ class Types { if (superclassInfo.cls == null || superclassInfo.cls == coreTypes.objectClass) continue; Class superclass = superclassInfo.cls!; - Iterable subclasses = - _getConcreteSubtypes(superclass).where((cls) => cls != superclass); + + // TODO(joshualitt): This includes abstract types that can't be + // instantiated, but might be needed for subtype checks. The majority of + // abstract classes are probably unnecessary though. We should filter + // these cases to reduce the size of the type rules. + Iterable subclasses = translator.subtypes + .getSubtypesOf(superclass) + .where((cls) => cls != superclass); Iterable subtypes = subclasses.map( (Class cls) => cls.getThisType(coreTypes, Nullability.nonNullable)); for (InterfaceType subtype in subtypes) { @@ -144,6 +153,17 @@ class Types { return typeRulesSubstitutions; } + List _buildTypeNames() { + // This logic assumes `translator.classes` returns the classes indexed by + // class ID. If we ever change that logic, we will need to change this code. + List typeNames = []; + for (ClassInfo classInfo in translator.classes) { + String className = classInfo.cls?.name ?? ''; + typeNames.add(className); + } + return typeNames; + } + /// Builds a map of subclasses to the transitive set of superclasses they /// implement. /// TODO(joshualitt): This implementation is just temporary. Eventually we @@ -205,6 +225,25 @@ class Types { return expectedType; } + /// Returns a list of string type names for pretty printing types. + w.ValueType makeTypeNames(w.Instructions b) { + w.ValueType expectedType = + translator.classInfo[translator.immutableListClass]!.nonNullableType; + DartType stringType = InterfaceType( + translator.stringBaseClass, + Nullability.nonNullable, + [translator.coreTypes.stringNonNullableRawType]); + List listStringConstant = []; + for (String name in typeNames) { + listStringConstant.add(StringConstant(name)); + } + DartType listStringType = InterfaceType( + translator.immutableListClass, Nullability.nonNullable, [stringType]); + translator.constants.instantiateConstant(null, b, + ListConstant(listStringType, listStringConstant), expectedType); + return expectedType; + } + bool isGenericFunction(FunctionType type) => type.typeParameters.isNotEmpty; bool isGenericFunctionTypeParameter(TypeParameterType type) => @@ -358,7 +397,11 @@ class Types { type is FutureOrType || type is FunctionType); if (type is TypeParameterType) { - return codeGen.instantiateTypeParameter(type.parameter); + codeGen.instantiateTypeParameter(type.parameter); + if (type.declaredNullability == Nullability.nullable) { + codeGen.call(translator.typeAsNullable.reference); + } + return nonNullableTypeType; } ClassInfo info = translator.classInfo[classForType(type)]!; translator.functions.allocateClass(info.classId); @@ -390,16 +433,20 @@ class Types { void emitTypeTest(CodeGenerator codeGen, DartType type, DartType operandType, TreeNode node) { w.Instructions b = codeGen.b; - if (type is! InterfaceType) { - // TODO(joshualitt): We can enable this after fixing `.runtimeType`. - // makeType(codeGen, type); - // codeGen.call(translator.isSubtype.reference); - print("Not implemented: Type test with non-interface type $type" + if (type is FunctionType) { + // TODO(joshualitt): We can enable type tests for [FunctionType] after + // enabling `.runtimeType` for [FunctionType]. + print("Not implemented: Type test with function type $type" " at ${node.location}"); b.drop(); b.i32_const(1); return; } + if (type is! InterfaceType) { + makeType(codeGen, type); + codeGen.call(translator.isSubtype.reference); + return; + } bool isPotentiallyNullable = operandType.isPotentiallyNullable; w.Label? resultLabel; if (isPotentiallyNullable) { @@ -412,6 +459,15 @@ class Types { b.local_get(operand); b.br_on_null(nullLabel); } + void _endPotentiallyNullableBlock() { + if (isPotentiallyNullable) { + b.br(resultLabel!); + b.end(); // nullLabel + encodeNullability(b, type); + b.end(); // resultLabel + } + } + if (type.typeArguments.any((t) => t is! DynamicType)) { // If the tested-against type as an instance of the static operand type // has the same type arguments as the static operand type, it is not @@ -421,8 +477,10 @@ class Types { .getTypeAsInstanceOf(type, cls, codeGen.member.enclosingLibrary) ?.withDeclaredNullability(operandType.declaredNullability); if (base != operandType) { - print("Not implemented: Type test with type arguments" - " at ${node.location}"); + makeType(codeGen, type); + codeGen.call(translator.isSubtype.reference); + _endPotentiallyNullableBlock(); + return; } } List concrete = _getConcreteSubtypes(type.classNode).toList(); @@ -454,12 +512,7 @@ class Types { b.i32_const(0); b.end(); // done } - if (isPotentiallyNullable) { - b.br(resultLabel!); - b.end(); // nullLabel - encodeNullability(b, type); - b.end(); // resultLabel - } + _endPotentiallyNullableBlock(); } /// Returns true if a given type is nullable, and false otherwise. This diff --git a/sdk/lib/_internal/wasm/lib/class_id.dart b/sdk/lib/_internal/wasm/lib/class_id.dart index 08f20064e81..312ec3e4b5a 100644 --- a/sdk/lib/_internal/wasm/lib/class_id.dart +++ b/sdk/lib/_internal/wasm/lib/class_id.dart @@ -16,10 +16,12 @@ class ClassID { external static int get cidUint8ArrayView; @pragma("wasm:class-id", "dart.core#Object") external static int get cidObject; - @pragma("wasm:class-id", "dart.async#Future") - external static int get cidFuture; + @pragma("wasm:class-id", "dart.async#_Future") + external static int get cid_Future; @pragma("wasm:class-id", "dart.core#Function") external static int get cidFunction; + @pragma("wasm:class-id", "dart.core#_Function") + external static int get cid_Function; // Class IDs for RTI Types. @pragma("wasm:class-id", "dart.core#_NeverType") diff --git a/sdk/lib/_internal/wasm/lib/object_patch.dart b/sdk/lib/_internal/wasm/lib/object_patch.dart index e61e06479c2..8ba627e69fa 100644 --- a/sdk/lib/_internal/wasm/lib/object_patch.dart +++ b/sdk/lib/_internal/wasm/lib/object_patch.dart @@ -45,6 +45,8 @@ class Object { /// override [runtimeType]. @patch external Type get runtimeType; + + @pragma("wasm:entry-point") _Type get _runtimeType => _getInterfaceTypeRuntimeType(this, _typeArguments); @patch diff --git a/sdk/lib/_internal/wasm/lib/type.dart b/sdk/lib/_internal/wasm/lib/type.dart index 4a1ea765377..8df02e3910d 100644 --- a/sdk/lib/_internal/wasm/lib/type.dart +++ b/sdk/lib/_internal/wasm/lib/type.dart @@ -48,6 +48,7 @@ abstract class _Type implements Type { @pragma("wasm:entry-point") class _NeverType extends _Type { + @pragma("wasm:entry-point") const _NeverType() : super(false); @override @@ -63,6 +64,7 @@ class _NeverType extends _Type { @pragma("wasm:entry-point") class _DynamicType extends _Type { + @pragma("wasm:entry-point") const _DynamicType() : super(true); @override @@ -77,6 +79,7 @@ class _DynamicType extends _Type { @pragma("wasm:entry-point") class _VoidType extends _Type { + @pragma("wasm:entry-point") const _VoidType() : super(true); @override @@ -91,6 +94,7 @@ class _VoidType extends _Type { @pragma("wasm:entry-point") class _NullType extends _Type { + @pragma("wasm:entry-point") const _NullType() : super(true); @override @@ -110,6 +114,7 @@ class _NullType extends _Type { class _InterfaceTypeParameterType extends _Type { final int environmentIndex; + @pragma("wasm:entry-point") const _InterfaceTypeParameterType(super.isNullable, this.environmentIndex); @override @@ -121,7 +126,7 @@ class _InterfaceTypeParameterType extends _Type { throw 'Type parameter should have been substituted already.'; @override - String toString() => '$environmentIndex'; + String toString() => 'T$environmentIndex'; } @pragma("wasm:entry-point") @@ -140,7 +145,7 @@ class _GenericFunctionTypeParameterType extends _Type { throw 'Type parameter should have been substituted already.'; @override - String toString() => '$environmentIndex'; + String toString() => 'G$environmentIndex'; } @pragma("wasm:entry-point") @@ -151,7 +156,7 @@ class _FutureOrType extends _Type { const _FutureOrType(bool isNullable, this.typeArgument) : super(isNullable); _InterfaceType get asFuture => - _InterfaceType(ClassID.cidFuture, isNullable, [typeArgument]); + _InterfaceType(ClassID.cid_Future, isNullable, [typeArgument]); @override _Type get _asNonNullable { @@ -231,8 +236,7 @@ class _InterfaceType extends _Type { @override String toString() { StringBuffer s = StringBuffer(); - s.write("Interface"); - s.write(classId); + s.write(_getTypeNames()[classId]); if (typeArguments.isNotEmpty) { s.write("<"); for (int i = 0; i < typeArguments.length; i++) { @@ -364,8 +368,8 @@ class _FunctionType extends _Type { } // TODO(joshualitt): Implement. This should probably extend _FunctionType. -@pragma("wasm:entry-point") class _GenericFunctionType extends _Type { + @pragma("wasm:entry-point") const _GenericFunctionType(bool isNullable) : super(isNullable); @override @@ -380,6 +384,7 @@ class _GenericFunctionType extends _Type { external List> _getTypeRulesSupers(); external List>> _getTypeRulesSubstitutions(); +external List _getTypeNames(); class _Environment { List> scopes = []; @@ -450,7 +455,8 @@ class _TypeUniverse { } bool isFunctionType(_Type t) => - isSpecificInterfaceType(t, ClassID.cidFunction); + isSpecificInterfaceType(t, ClassID.cidFunction) || + isSpecificInterfaceType(t, ClassID.cid_Function); bool areTypeArgumentsSubtypes(List<_Type> sArgs, _Environment? sEnv, List<_Type> tArgs, _Environment? tEnv) { @@ -484,13 +490,22 @@ class _TypeUniverse { List<_Type> substitutions = typeRulesSubstitutions[sId][sSuperIndexOfT]; + // If we have empty type arguments then create a list of dynamic type + // arguments. + List<_Type> sTypeArguments = s.typeArguments; + if (substitutions.isNotEmpty && sTypeArguments.isEmpty) { + sTypeArguments = List<_Type>.generate( + substitutions.length, (int index) => const _DynamicType(), + growable: false); + } + // If [sEnv] is null, then create a new environment. Otherwise, we are doing // a recursive type check, so extend the existing environment with [s]'s // type arguments. if (sEnv == null) { - sEnv = _Environment.from(s.typeArguments); + sEnv = _Environment.from(sTypeArguments); } else { - sEnv.push(s.typeArguments); + sEnv.push(sTypeArguments); } bool result = areTypeArgumentsSubtypes(substitutions, sEnv, t.typeArguments, tEnv); @@ -562,6 +577,7 @@ class _TypeUniverse { } while (sIndex < sNamedLength) { if (sNamed[sIndex].isRequired) return false; + sIndex++; } return true; } @@ -606,7 +622,7 @@ class _TypeUniverse { if (!isSubtype(sFutureOr.typeArgument, sEnv, t, tEnv)) { return false; } - return _isSubtype(sFutureOr.asFuture, t); + return isSubtype(sFutureOr.asFuture, sEnv, t, tEnv); } // Left Nullable: @@ -643,6 +659,12 @@ class _TypeUniverse { return true; } + // TODO(joshualitt): This is not correct, but it is necessary until we fully + // implement RTI for FunctionTypes. + if (isFunctionType(s) && (t.isFunction || t.isGenericFunction)) { + return true; + } + // Positional Function Types + Named Function Types: if (s.isGenericFunction && t.isGenericFunction) { // TODO(joshualitt): Implement case. diff --git a/sdk/lib/async/future_impl.dart b/sdk/lib/async/future_impl.dart index 44b390658fa..f8fb75052df 100644 --- a/sdk/lib/async/future_impl.dart +++ b/sdk/lib/async/future_impl.dart @@ -196,6 +196,7 @@ class _FutureListener { bool shouldChain(Future value) => value is Future || value is! T; } +@pragma("wasm:entry-point") class _Future implements Future { /// Initial state, waiting for a result. In this state, the /// [_resultOrListeners] field holds a single-linked list of