[dart2wasm] Use [WasmArray]s for type arguments in dynamic calls

This avoids allocating lists in dynamic calls for type
arguments.

So far the closure shape checking functionality also
- as a side-effect of shape checking - added default type arguments
to a growable list. We now cleanly separate the logic to use
default type arguments vs shape checking.

=> Doing so allows removing the remaining usages of [List] in the RTT.
=> Probably also results in smaller & faster dynamic calls.

The list allocation is now pushed to the slow path where we
create [Invocation] object that is then used for NoSuchMethod
handling.

Change-Id: I4823cda0aa9b5f1f137813bc5848c365665da5fd
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/344822
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
This commit is contained in:
Martin Kustermann 2024-01-05 15:40:40 +00:00 committed by Commit Queue
parent 3343b7a841
commit cbe0e6443d
9 changed files with 75 additions and 88 deletions

View file

@ -570,24 +570,13 @@ class ClosureLayouter extends RecursiveVisitor {
b.struct_get(
instantiationContextStruct, FieldIndex.instantiationContextInner);
// Push types, as list
translator.makeList(
function,
(b) {
translator.constants.instantiateConstant(
function,
b,
TypeLiteralConstant(
InterfaceType(translator.typeClass, Nullability.nonNullable)),
translator.types.nonNullableTypeType);
},
typeCount,
// Push types
translator.makeArray(function, translator.typeArrayType, typeCount,
(elementType, elementIdx) {
b.local_get(instantiationContextLocal);
b.struct_get(instantiationContextStruct,
FieldIndex.instantiationContextTypeArgumentsBase + elementIdx);
},
isGrowable: true);
b.local_get(instantiationContextLocal);
b.struct_get(instantiationContextStruct,
FieldIndex.instantiationContextTypeArgumentsBase + elementIdx);
});
b.local_get(posArgsListLocal);
b.local_get(namedArgsListLocal);

View file

@ -1896,15 +1896,12 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
function.addLocal(translator.topInfo.nullableType);
b.local_set(nullableReceiverLocal);
// Evaluate type arguments. Type argument list is growable as we may want
// to add default bounds when the callee has type parameters but no type
// arguments are passed.
makeList(InterfaceType(translator.typeClass, Nullability.nonNullable),
typeArguments.length, (elementType, elementIdx) {
translator.types.makeType(this, typeArguments[elementIdx]);
}, isGrowable: true);
// Evaluate type arguments.
final typeArgsLocal = function.addLocal(
translator.classInfo[translator.growableListClass]!.nonNullableType);
makeArray(translator.typeArrayType, typeArguments.length,
(elementType, elementIdx) {
translator.types.makeType(this, typeArguments[elementIdx]);
}));
b.local_set(typeArgsLocal);
// Evaluate positional arguments
@ -3399,7 +3396,7 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
// Type argument list is either empty or have the right number of types
// (checked by the forwarder).
b.local_get(typeArgsLocal);
translator.getListLength(b);
b.array_len();
b.i32_eqz();
b.if_([], List.generate(memberTypeParams.length, (_) => typeType));
// No type arguments passed, initialize with defaults
@ -3411,9 +3408,8 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
typeParamIdx < memberTypeParams.length;
typeParamIdx += 1) {
b.local_get(typeArgsLocal);
translator.indexList(b, (b) => b.i32_const(typeParamIdx));
translator.convertType(
function, translator.topInfo.nullableType, typeType);
b.i32_const(typeParamIdx);
b.array_get(translator.typeArrayType);
}
b.end();

View file

@ -58,10 +58,6 @@ class Constants {
w.ModuleBuilder get m => translator.m;
/// Makes a type list [ListConstant].
ListConstant makeTypeList(Iterable<DartType> types) => ListConstant(
translator.typeType, types.map((t) => TypeLiteralConstant(t)).toList());
/// Makes a `WasmArray<_Type>` [InstanceConstant].
InstanceConstant makeTypeArray(Iterable<DartType> types) => makeArrayOf(
translator.typeType, types.map((t) => TypeLiteralConstant(t)).toList());
@ -647,7 +643,7 @@ class ConstantCreator extends ConstantVisitor<ConstantInfo?>
final namedArgsListLocal = function.locals[3];
b.local_get(closureLocal);
final ListConstant typeArgs = constants.makeTypeList(constant.types);
final InstanceConstant typeArgs = constants.makeTypeArray(constant.types);
constants.instantiateConstant(
function, b, typeArgs, typeArgsListLocal.type);
b.local_get(posArgsListLocal);

View file

@ -200,7 +200,7 @@ class Forwarder {
final b = function.body;
final receiverLocal = function.locals[0]; // ref #Top
final typeArgsLocal = function.locals[1]; // ref _ListBase
final typeArgsLocal = function.locals[1]; // ref WasmArray
final positionalArgsLocal = function.locals[2]; // ref WasmArray
final namedArgsLocal = function.locals[3]; // ref WasmArray
@ -277,12 +277,12 @@ class Forwarder {
if (targetMemberParamInfo.typeParamCount == 0) {
// typeArgs.length == 0
b.local_get(typeArgsLocal);
translator.getListLength(b);
b.array_len();
b.i32_eqz();
} else {
// typeArgs.length == 0 || typeArgs.length == typeParams.length
b.local_get(typeArgsLocal);
translator.getListLength(b);
b.array_len();
b.local_tee(numArgsLocal);
b.i32_eqz();
b.local_get(numArgsLocal);
@ -673,9 +673,7 @@ void generateDynamicFunctionCall(
w.Local namedArgsLocal,
w.Label noSuchMethodBlock,
) {
final listArgumentType =
translator.classInfo[translator.listBaseClass]!.nonNullableType;
assert(typeArgsLocal.type == listArgumentType);
assert(typeArgsLocal.type == translator.typeArrayTypeRef);
assert(posArgsLocal.type == translator.nullableObjectArrayTypeRef);
assert(namedArgsLocal.type == translator.nullableObjectArrayTypeRef);
@ -689,6 +687,20 @@ void generateDynamicFunctionCall(
FieldIndex.closureRuntimeType);
b.local_tee(functionTypeLocal);
// If no type arguments were supplied but the closure has type parameters, use
// the default values.
b.local_get(typeArgsLocal);
b.array_len();
b.i32_eqz();
b.if_();
b.local_get(functionTypeLocal);
b.struct_get(
translator.classInfo[translator.functionTypeClass]!.struct,
translator
.fieldIndex[translator.functionTypeTypeParameterDefaultsField]!);
b.local_set(typeArgsLocal);
b.end();
// Check closure shape
b.local_get(typeArgsLocal);
b.local_get(posArgsLocal);
@ -743,6 +755,8 @@ void createInvocationObject(
translator.classInfo[translator.symbolClass]!.nonNullableType);
b.local_get(typeArgsLocal);
b.call(translator.functions
.getFunction(translator.typeArgumentsToList.reference));
b.local_get(positionalArgsLocal);
b.call(translator.functions
.getFunction(translator.positionalParametersToList.reference));

View file

@ -1710,21 +1710,9 @@ class Intrinsifier {
final posArgsNullableLocal = function.locals[1]; // ref null Object,
final namedArgsLocal = function.locals[2]; // ref null Object
final listArgumentType =
translator.classInfo[translator.listBaseClass]!.nonNullableType;
// Create type argument list. It will be initialized as empty and it
// needs to be growable as `_checkClosureShape` updates it with default
// bounds if the function being invokes has type parameters.
final typeArgsLocal = function.addLocal(listArgumentType);
translator.makeList(function, (b) {
translator.constants.instantiateConstant(
function,
b,
TypeLiteralConstant(
InterfaceType(translator.typeClass, Nullability.nonNullable)),
translator.types.nonNullableTypeType);
}, 0, (elementType, elementIndex) {}, isGrowable: true);
// Create empty type arguments array.
final typeArgsLocal = function.addLocal(translator.makeArray(function,
translator.typeArrayType, 0, (elementType, elementIndex) {}));
b.local_set(typeArgsLocal);
// Create empty list for positional args if the argument is null

View file

@ -71,6 +71,8 @@ mixin KernelNodes {
index.getClass("dart:core", "_AbstractFunctionType");
late final Class functionTypeClass =
index.getClass("dart:core", "_FunctionType");
late final Field functionTypeTypeParameterDefaultsField =
index.getField("dart:core", "_FunctionType", "typeParameterDefaults");
late final Class functionTypeParameterTypeClass =
index.getClass("dart:core", "_FunctionTypeParameterType");
late final Class futureOrTypeClass =
@ -275,6 +277,8 @@ mixin KernelNodes {
// dart:core dynamic invocation helper procedures
late final Procedure getNamedParameterIndex =
index.getTopLevelProcedure("dart:core", "_getNamedParameterIndex");
late final Procedure typeArgumentsToList =
index.getTopLevelProcedure("dart:core", "_typeArgumentsToList");
late final Procedure positionalParametersToList =
index.getTopLevelProcedure("dart:core", "_positionalParametersToList");
late final Procedure namedParametersToMap =

View file

@ -130,6 +130,8 @@ class Translator with KernelNodes {
.heapType as w.ArrayType;
late final w.ArrayType nullableObjectArrayType =
arrayTypeForDartType(coreTypes.objectRawType(Nullability.nullable));
late final w.RefType typeArrayTypeRef =
w.RefType.def(typeArrayType, nullable: false);
late final w.RefType nullableObjectArrayTypeRef =
w.RefType.def(nullableObjectArrayType, nullable: false);
@ -185,7 +187,7 @@ class Translator with KernelNodes {
w.RefType.def(closureLayouter.closureBaseStruct, nullable: false),
// Type arguments
classInfo[listBaseClass]!.nonNullableType,
typeArrayTypeRef,
// Positional arguments
nullableObjectArrayTypeRef,
@ -203,7 +205,7 @@ class Translator with KernelNodes {
topInfo.nonNullableType,
// Type arguments
classInfo[listBaseClass]!.nonNullableType,
typeArrayTypeRef,
// Positional arguments
nullableObjectArrayTypeRef,
@ -1202,7 +1204,8 @@ class _ClosureDynamicEntryGenerator implements _FunctionGenerator {
// Push type arguments
for (int typeIdx = 0; typeIdx < typeCount; typeIdx += 1) {
b.local_get(typeArgsListLocal);
translator.indexList(b, (b) => b.i32_const(typeIdx));
b.i32_const(typeIdx);
b.array_get(translator.typeArrayType);
translator.convertType(
function, translator.topInfo.nullableType, targetInputs[inputIdx]);
inputIdx += 1;

View file

@ -18,6 +18,17 @@ int? _getNamedParameterIndex(
return null;
}
/// Converts type arguments passed to a dynamic forwarder to a
/// list that can be passed to `Invocation` constructors.
@pragma("wasm:entry-point")
List<_Type?> _typeArgumentsToList(WasmArray<_Type> typeArgs) {
final result = <_Type>[];
for (int i = 0; i < typeArgs.length; ++i) {
result.add(typeArgs[i]);
}
return result;
}
/// Converts a positional parameter list passed to a dynamic forwarder to a
/// list that can be passed to `Invocation` constructors.
@pragma("wasm:entry-point")

View file

@ -57,19 +57,6 @@ extension on WasmArray<String> {
bool get isNotEmpty => length != 0;
}
// TODO: Remove any occurence of `List`s in this file.
extension on List<_Type> {
@pragma("wasm:prefer-inline")
WasmArray<_Type> toWasmArray() {
if (isEmpty) return const WasmArray<_Type>.literal(<_Type>[]);
final result = WasmArray<_Type>.filled(length, this[0]);
for (int i = 1; i < length; ++i) {
result[i] = this[i];
}
return result;
}
}
// TODO(joshualitt): We can cache the result of [_FutureOrType.asFuture].
abstract class _Type implements Type {
final bool isDeclaredNullable;
@ -393,6 +380,7 @@ class _FunctionType extends _Type {
// representations that don't have this overhead in the common case.
final int typeParameterOffset;
final WasmArray<_Type> typeParameterBounds;
@pragma("wasm:entry-point")
final WasmArray<_Type> typeParameterDefaults;
final _Type returnType;
final WasmArray<_Type> positionalParameters;
@ -1165,16 +1153,12 @@ class _TypeCheckVerificationError extends Error {
///
/// [namedArguments] is a list of `Symbol` and `Object?` pairs.
@pragma("wasm:entry-point")
bool _checkClosureShape(_FunctionType functionType, List<_Type> typeArguments,
WasmArray<Object?> positionalArguments, WasmArray<dynamic> namedArguments) {
// Check type args, add default types to the type list if its empty
if (typeArguments.isEmpty) {
final defaults = functionType.typeParameterDefaults;
for (int i = 0; i < defaults.length; ++i) {
typeArguments.add(defaults[i]);
}
} else if (typeArguments.length !=
functionType.typeParameterDefaults.length) {
bool _checkClosureShape(
_FunctionType functionType,
WasmArray<_Type> typeArguments,
WasmArray<Object?> positionalArguments,
WasmArray<dynamic> namedArguments) {
if (typeArguments.length != functionType.typeParameterDefaults.length) {
return false;
}
@ -1236,16 +1220,18 @@ bool _checkClosureShape(_FunctionType functionType, List<_Type> typeArguments,
///
/// [namedArguments] is a list of `Symbol` and `Object?` pairs.
@pragma("wasm:entry-point")
void _checkClosureType(_FunctionType functionType, List<_Type> typeArguments,
WasmArray<Object?> positionalArguments, WasmArray<dynamic> namedArguments) {
void _checkClosureType(
_FunctionType functionType,
WasmArray<_Type> typeArguments,
WasmArray<Object?> positionalArguments,
WasmArray<dynamic> namedArguments) {
assert(functionType.typeParameterBounds.length == typeArguments.length);
if (!typeArguments.isEmpty) {
final typesAsArray = typeArguments.toWasmArray();
for (int i = 0; i < typesAsArray.length; i += 1) {
final typeArgument = typesAsArray[i];
for (int i = 0; i < typeArguments.length; i += 1) {
final typeArgument = typeArguments[i];
final paramBound = _TypeUniverse.substituteTypeArgument(
functionType.typeParameterBounds[i], typesAsArray, functionType);
functionType.typeParameterBounds[i], typeArguments, functionType);
if (!_typeUniverse.isSubtype(typeArgument, null, paramBound, null)) {
final stackTrace = StackTrace.current;
final typeError = _TypeError.fromMessageAndStackTrace(
@ -1257,7 +1243,7 @@ void _checkClosureType(_FunctionType functionType, List<_Type> typeArguments,
}
functionType = _TypeUniverse.substituteFunctionTypeArgument(
functionType, typesAsArray);
functionType, typeArguments);
}
// Check positional arguments