[dart2wasm] Enable RTI tests for oddball and interface types.

Change-Id: I4b92d4cf30779b78f3eab1acd18193fdd58ab452
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/249641
Reviewed-by: Aske Simon Christensen <askesc@google.com>
Commit-Queue: Joshua Litt <joshualitt@google.com>
This commit is contained in:
Joshua Litt 2022-07-23 00:56:38 +00:00 committed by Commit Bot
parent a04e69716c
commit f6f29df4b5
9 changed files with 154 additions and 39 deletions

View file

@ -297,7 +297,8 @@ class JsUtilWasmOptimizer extends Transformer {
Expression getObjectOffGlobalThis(Procedure node, List<String> 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.

View file

@ -803,8 +803,14 @@ class ConstantCreator extends ConstantVisitor<ConstantInfo?> {
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 {

View file

@ -593,7 +593,8 @@ class Intrinsifier {
if (className != null) {
List<String> 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;
}
}

View file

@ -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<Class, w.StorageType> builtinTypes;
late final Map<w.ValueType, Class> 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,

View file

@ -63,6 +63,9 @@ class Types {
late final List<List<List<DartType>>> typeRulesSubstitutions =
_buildTypeRulesSubstitutions();
/// A list which maps class ID to the classes [String] name.
late final List<String> 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<Class> 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<Class> subclasses = translator.subtypes
.getSubtypesOf(superclass)
.where((cls) => cls != superclass);
Iterable<InterfaceType> subtypes = subclasses.map(
(Class cls) => cls.getThisType(coreTypes, Nullability.nonNullable));
for (InterfaceType subtype in subtypes) {
@ -144,6 +153,17 @@ class Types {
return typeRulesSubstitutions;
}
List<String> _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<String> 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<StringConstant> 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<Class> 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

View file

@ -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")

View file

@ -45,6 +45,8 @@ class Object {
/// override [runtimeType].
@patch
external Type get runtimeType;
@pragma("wasm:entry-point")
_Type get _runtimeType => _getInterfaceTypeRuntimeType(this, _typeArguments);
@patch

View file

@ -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<List<int>> _getTypeRulesSupers();
external List<List<List<_Type>>> _getTypeRulesSubstitutions();
external List<String> _getTypeNames();
class _Environment {
List<List<_Type>> 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.

View file

@ -196,6 +196,7 @@ class _FutureListener<S, T> {
bool shouldChain(Future<dynamic> value) => value is Future<T> || value is! T;
}
@pragma("wasm:entry-point")
class _Future<T> implements Future<T> {
/// Initial state, waiting for a result. In this state, the
/// [_resultOrListeners] field holds a single-linked list of