mirror of
https://github.com/dart-lang/sdk
synced 2024-09-15 23:59:47 +00:00
[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:
parent
a04e69716c
commit
f6f29df4b5
|
@ -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.
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -45,6 +45,8 @@ class Object {
|
|||
/// override [runtimeType].
|
||||
@patch
|
||||
external Type get runtimeType;
|
||||
|
||||
@pragma("wasm:entry-point")
|
||||
_Type get _runtimeType => _getInterfaceTypeRuntimeType(this, _typeArguments);
|
||||
|
||||
@patch
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue