[dart2wasm] Make normal dynamic calls avoid wasm array creations

Currently a dynamic call will call a helper which will create
3 array objects (for type, positional and named arguments).

It will then pass those wasm arrays to type checker methods.
Those will then get values out of the array, type check and call
the target.

We change this now such that in the normal case (**) we avoid
the array creations. Instead we make a dynamic forwarder function
per target and call shape.

We also outline the array creation when creating `Invocation`
objects. So for simple caller shapes - such as `clone()` the
creation of an `Invocation` object is a call without arguments
instead of various array creations.

Issue https://github.com/dart-lang/sdk/issues/62640

(**) If the dynamic call could be call-via-field we still create
those arrays, as we use them for closure type checking.

Change-Id: Ia8f3f3cd95f650bd8706a15ed68a43db4de80a6e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/487020
Reviewed-by: Srujan Gaddam <srujzs@google.com>
This commit is contained in:
Martin Kustermann
2026-03-12 02:17:07 -07:00
parent ea1f4d474e
commit 2e2f8ae7b3
9 changed files with 794 additions and 747 deletions
+261 -164
View File
@@ -4,6 +4,7 @@
import 'dart:collection' show LinkedHashMap;
import 'package:collection/collection.dart';
import 'package:kernel/ast.dart';
import 'package:kernel/type_environment.dart';
import 'package:wasm_builder/wasm_builder.dart' as w;
@@ -12,7 +13,7 @@ import 'async.dart';
import 'class_info.dart';
import 'closures.dart';
import 'dispatch_table.dart';
import 'dynamic_forwarders.dart';
import 'functions.dart' show CallShape, makeDynamicForwarderSignature;
import 'globals.dart';
import 'intrinsics.dart';
import 'param_info.dart';
@@ -3219,10 +3220,6 @@ CodeGenerator? getInlinableMemberCodeGenerator(Translator translator,
return TearOffCodeGenerator(translator, functionType, member);
}
if (reference.isTypeCheckerReference) {
return TypeCheckerCodeGenerator(translator, functionType, member);
}
if (member is Constructor) {
if (reference.isConstructorBodyReference) {
return ConstructorCodeGenerator(translator, functionType, member);
@@ -3433,199 +3430,228 @@ class TearOffCodeGenerator extends AstCodeGenerator {
}
}
class TypeCheckerCodeGenerator extends AstCodeGenerator {
final Member member;
/// Generates code for a dynamic forwarder function.
///
/// Dynamic forwarders are functions that
/// * may have to populate default type arguments
/// * may have to check argument types
/// * may have to unbox arguments
/// * call the normal getter/setter/method target
/// * may have to box the result value
///
/// We generate them for each [CallShape] and the caller guarantees that the
/// [CallShape] is valid for the given target [reference]. The signature for
/// such a forwarder function is determined by [makeDynamicForwarderSignature].
class DynamicForwarderCodeGenerator extends AstCodeGenerator {
final Reference reference;
final CallShape callShape;
TypeCheckerCodeGenerator(
Translator translator, w.FunctionType functionType, this.member)
: super(translator, functionType, member);
DynamicForwarderCodeGenerator(Translator translator,
w.FunctionType functionType, this.reference, this.callShape)
: super(translator, functionType, reference.asMember);
@override
void generateInternal() {
final member = reference.asMember;
// Initialize [Closures] without [Closures.captures]: Similar to
// [TearOffCodeGenerator], type parameters will be loaded from the `this`
// struct.
closures = translator.getClosures(member, findCaptures: false);
if (member is Field ||
(member is Procedure && (member as Procedure).isSetter)) {
_generateFieldSetterTypeCheckerMethod();
if (member is Field) {
if (reference.isImplicitGetter) {
_generateDynamicGetterForwarder();
} else {
assert(reference.isImplicitSetter);
_generateDynamicSetterForwarder();
}
} else {
_generateProcedureTypeCheckerMethod();
member as Procedure;
if (member.isSetter) {
_generateDynamicSetterForwarder();
} else if (member.isGetter) {
_generateDynamicGetterForwarder();
} else {
_generateDynamicMethodForwarder();
}
}
}
/// Generate type checker method for a method.
///
/// This function will be called by an invocation forwarder in a dynamic
/// invocation to type check parameters before calling the actual method.
void _generateProcedureTypeCheckerMethod() {
final receiverLocal = paramLocals[0];
final typeArgsLocal = paramLocals[1];
final positionalArgsLocal = paramLocals[2];
final namedArgsLocal = paramLocals[3];
void _generateDynamicMethodForwarder() {
// The offsets of arguments passed by the caller.
const int argReceiverOffset = 0;
const int argTypesOffset = argReceiverOffset + 1;
final int argPositionalsOffset = argTypesOffset + callShape.typeCount;
final int argNamedOffset = argPositionalsOffset + callShape.positionalCount;
_initializeThis(member.reference);
final targetProcedure = reference.asMember as Procedure;
final targetFunction = targetProcedure.function;
assert(callShape.matchesTarget(targetFunction));
final target = translator.getFunctionEntry(targetProcedure.reference,
uncheckedEntry: false);
final targetSignature = translator.signatureForDirectCall(target);
_initializeThis(reference);
final typeType =
translator.classInfo[translator.typeClass]!.nonNullableType;
final target =
translator.getFunctionEntry(member.reference, uncheckedEntry: false);
final targetParamInfo = translator.paramInfoForDirectCall(target);
final procedure = member as Procedure;
// Bind type parameters
final memberTypeParams = procedure.function.typeParameters;
assert(memberTypeParams.length == targetParamInfo.typeParamCount);
if (memberTypeParams.isNotEmpty) {
// Type argument list is either empty or have the right number of types
// (checked by the forwarder).
b.local_get(typeArgsLocal);
b.array_len();
b.i32_eqz();
b.if_([], List.generate(memberTypeParams.length, (_) => typeType));
// No type arguments passed, initialize with defaults
for (final typeParam in memberTypeParams) {
types.makeType(this, typeParam.defaultType);
}
b.else_();
for (int typeParamIdx = 0;
typeParamIdx < memberTypeParams.length;
typeParamIdx += 1) {
b.local_get(typeArgsLocal);
b.i32_const(typeParamIdx);
b.array_get(translator.typeArrayType);
}
b.end();
// Create locals for type parameters. These will be used by `makeType`
// below when generating types of parameters, for type checks, and when
// pushing the type parameters when calling the actual member.
for (int typeParamIdx = memberTypeParams.length - 1;
typeParamIdx >= 0;
typeParamIdx -= 1) {
final local = addLocal(typeType);
b.local_set(local);
typeLocals[memberTypeParams[typeParamIdx]] = local;
}
}
if (!translator.options.omitImplicitTypeChecks) {
// Check type parameter bounds
for (TypeParameter typeParameter in memberTypeParams) {
if (typeParameter.bound != translator.coreTypes.objectNullableRawType) {
_generateTypeArgumentBoundCheck(typeParameter.name!,
typeLocals[typeParameter]!, typeParameter.bound);
}
}
// Check positional argument types
final List<VariableDeclaration> memberPositionalParams =
procedure.function.positionalParameters;
for (int positionalParamIdx = 0;
positionalParamIdx < memberPositionalParams.length;
positionalParamIdx += 1) {
final param = memberPositionalParams[positionalParamIdx];
b.local_get(positionalArgsLocal);
b.i32_const(positionalParamIdx);
b.array_get(translator.nullableObjectArrayType);
_generateArgumentTypeCheck(param.name!, translator.topType, param.type);
}
// Check named argument types
final memberNamedParams = procedure.function.namedParameters;
/// Maps a named parameter in the member's signature to the parameter's
/// index in the array [namedArgsLocal].
int mapNamedParameterToArrayIndex(String name) {
int? idx;
for (int i = 0; i < targetParamInfo.names.length; i += 1) {
if (targetParamInfo.names[i] == name) {
idx = i;
break;
}
}
return idx!;
}
for (int namedParamIdx = 0;
namedParamIdx < memberNamedParams.length;
namedParamIdx += 1) {
final param = memberNamedParams[namedParamIdx];
b.local_get(namedArgsLocal);
b.i32_const(mapNamedParameterToArrayIndex(param.name!));
b.array_get(translator.nullableObjectArrayType);
_generateArgumentTypeCheck(param.name!, translator.topType, param.type);
}
}
// Argument types are as expected, call the member function
final w.FunctionType memberWasmFunctionType =
translator.signatureForDirectCall(target);
final List<w.ValueType> memberWasmInputs = memberWasmFunctionType.inputs;
// Load the receiver
final receiverLocal = paramLocals[argReceiverOffset];
b.local_get(receiverLocal);
translator.convertType(b, receiverLocal.type, memberWasmInputs[0]);
translator.convertType(b, receiverLocal.type, targetSignature.inputs[0]);
for (final typeParam in memberTypeParams) {
b.local_get(typeLocals[typeParam]!);
// Load type parameters for target.
final targetTypeParams = targetFunction.typeParameters;
assert(targetTypeParams.length == targetParamInfo.typeParamCount);
if (targetTypeParams.isNotEmpty) {
if (callShape.typeCount != 0) {
// Provided by caller.
for (int i = 0; i < targetTypeParams.length; ++i) {
final param = targetTypeParams[i];
final paramValue = paramLocals[argTypesOffset + i];
b.local_get(paramValue);
typeLocals[param] = paramValue;
}
} else {
// Use default-to-bounds.
for (int i = 0; i < targetTypeParams.length; ++i) {
final param = targetTypeParams[i];
types.makeType(this, param.defaultType);
final paramValue = b.addLocal(typeType);
b.local_tee(paramValue);
typeLocals[param] = paramValue;
}
}
}
int memberParamIdx =
1 + targetParamInfo.typeParamCount; // skip receiver and type args
void pushArgument(w.Local listLocal, int listIdx, int wasmInputIdx) {
b.local_get(listLocal);
b.i32_const(listIdx);
b.array_get(translator.nullableObjectArrayType);
translator.convertType(
b, translator.topType, memberWasmInputs[wasmInputIdx]);
// Check type parameter bounds.
if (!translator.options.omitImplicitTypeChecks) {
for (int i = 0; i < targetTypeParams.length; ++i) {
final param = targetTypeParams[i];
if (param.bound != translator.coreTypes.objectNullableRawType) {
final paramValue = typeLocals[param]!;
_generateTypeArgumentBoundCheck(param.name!, paramValue, param.bound);
}
}
}
for (int positionalParamIdx = 0;
positionalParamIdx < targetParamInfo.positional.length;
positionalParamIdx += 1) {
pushArgument(positionalArgsLocal, positionalParamIdx, memberParamIdx);
memberParamIdx += 1;
// Load positional parameters for the target (and check types if needed).
final targetPositionalParams = targetFunction.positionalParameters;
for (int i = 0; i < targetParamInfo.positional.length; i++) {
final targetParamType =
targetSignature.inputs[1 + targetParamInfo.typeParamCount + i];
if (i < callShape.positionalCount) {
// Provided by the caller.
final paramValue = paramLocals[argPositionalsOffset + i];
b.local_get(paramValue);
if (!translator.options.omitImplicitTypeChecks) {
final param = targetPositionalParams[i];
b.local_get(paramValue);
_generateArgumentTypeCheck(
param.name!, translator.topType, param.type);
}
translator.convertType(b, paramValue.type, targetParamType);
} else {
// Default to use if the callee has the `i` parameter.
final defaultFunctionValue = i < targetPositionalParams.length
? (targetPositionalParams[i].initializer as ConstantExpression?)
?.constant
: null;
// Default to use if callee doesn't have the `i` parameter.
final defaultValue = targetParamInfo.positional[i];
// The target wasm function corresponding to an instance method may have
// a selector signature (which is based on all implementations of a
// selector) and therefore may have more parameters than the actual
// target needs (the others are ignored in the callee).
final value = defaultFunctionValue ?? defaultValue!;
translator.constants.instantiateConstant(b, value, targetParamType);
}
}
for (int namedParamIdx = 0;
namedParamIdx < targetParamInfo.names.length;
namedParamIdx += 1) {
pushArgument(namedArgsLocal, namedParamIdx, memberParamIdx);
memberParamIdx += 1;
// Load named arguments (and check types if needed).
final targetNamedParams = targetFunction.namedParameters;
for (int i = 0; i < targetParamInfo.names.length; ++i) {
final targetParamType = targetSignature.inputs[1 +
targetParamInfo.typeParamCount +
targetParamInfo.positional.length +
i];
final name = targetParamInfo.names[i];
final namedParam =
targetNamedParams.firstWhereOrNull((n) => n.name == name);
final callerIndex = callShape.named.indexOf(name);
if (0 <= callerIndex) {
// Provided by the caller.
final paramValue = paramLocals[argNamedOffset + callerIndex];
b.local_get(paramValue);
if (!translator.options.omitImplicitTypeChecks) {
b.local_get(paramValue);
_generateArgumentTypeCheck(
name, translator.topType, namedParam!.type);
}
translator.convertType(b, paramValue.type, targetParamType);
} else {
// Default to use if callee has the `name` parameter.
final defaultFunctionValue =
(namedParam?.initializer as ConstantExpression?)?.constant;
// Default to use if callee doesn't have `name` parameter.
final defaultValue = targetParamInfo.named[name];
// The target wasm function corresponding to an instance method may have
// a selector signature (which is based on all implementations of a
// selector) and therefore may have more parameters than the actual
// target needs (the others are ignored in the callee).
final value = (defaultFunctionValue ?? defaultValue)!;
translator.constants.instantiateConstant(b, value, targetParamType);
}
}
call(target);
translator.convertType(
b,
translator.outputOrVoid(memberWasmFunctionType.outputs),
translator.convertType(b, translator.outputOrVoid(targetSignature.outputs),
translator.topType);
b.return_();
b.end();
}
/// Generate type checker method for a setter.
///
/// This function will be called by a setter forwarder in a dynamic set to
/// type check the setter argument before calling the actual setter.
void _generateFieldSetterTypeCheckerMethod() {
void _generateDynamicGetterForwarder() {
final receiverLocal = paramLocals[0];
_initializeThis(reference);
final member = reference.asMember;
final info = translator.classInfo[member.enclosingClass]!;
if (member is Field) {
int fieldIndex = translator.fieldIndex[member]!;
b.local_get(receiverLocal);
b.struct_get(info.struct, fieldIndex);
translator.convertType(
b, info.struct.fields[fieldIndex].type.unpacked, translator.topType);
} else {
final target =
translator.getFunctionEntry(reference, uncheckedEntry: false);
final getterProcedureWasmType = translator.signatureForDirectCall(target);
final getterWasmOutputs = getterProcedureWasmType.outputs;
assert(getterWasmOutputs.length == 1);
b.local_get(receiverLocal);
call(target);
translator.convertType(b, outputs.single, translator.topType);
}
b.end(); // end function
}
void _generateDynamicSetterForwarder() {
final receiverLocal = paramLocals[0];
final positionalArgLocal = paramLocals[1];
_initializeThis(member.reference);
_initializeThis(reference);
final member_ = member;
final member = reference.asMember;
DartType paramType;
if (member_ is Field) {
paramType = member_.type;
if (member is Field) {
paramType = member.type;
} else {
paramType = (member_ as Procedure).setterType;
paramType = (member as Procedure).setterType;
}
if (!translator.options.omitImplicitTypeChecks) {
@@ -3637,9 +3663,9 @@ class TypeCheckerCodeGenerator extends AstCodeGenerator {
);
}
ClassInfo info = translator.classInfo[member_.enclosingClass]!;
if (member_ is Field) {
int fieldIndex = translator.fieldIndex[member_]!;
ClassInfo info = translator.classInfo[member.enclosingClass]!;
if (member is Field) {
int fieldIndex = translator.fieldIndex[member]!;
b.local_get(receiverLocal);
translator.convertType(b, receiverLocal.type, info.nonNullableType);
b.local_get(positionalArgLocal);
@@ -3647,9 +3673,8 @@ class TypeCheckerCodeGenerator extends AstCodeGenerator {
info.struct.fields[fieldIndex].type.unpacked);
b.struct_set(info.struct, fieldIndex);
} else {
final setterProcedure = member_ as Procedure;
final target = translator.getFunctionEntry(setterProcedure.reference,
uncheckedEntry: false);
final target =
translator.getFunctionEntry(reference, uncheckedEntry: false);
final setterProcedureWasmType = translator.signatureForDirectCall(target);
final setterWasmInputs = setterProcedureWasmType.inputs;
assert(setterWasmInputs.length == 2);
@@ -3665,6 +3690,78 @@ class TypeCheckerCodeGenerator extends AstCodeGenerator {
}
}
/// Creates [Invocation] object based on a [CallShape].
class InvocationCreationStubGenerator implements CodeGenerator {
final Translator translator;
final CallShape callShape;
InvocationCreationStubGenerator(this.translator, this.callShape);
@override
void generate(w.InstructionsBuilder b, List<w.Local> paramLocals,
w.Label? returnLabel) {
assert(returnLabel == null);
int argumentIterator = 0;
final typeArgs =
b.addLocal(w.RefType(translator.typeArrayType, nullable: false));
for (int i = 0; i < callShape.typeCount; ++i) {
b.local_get(paramLocals[argumentIterator++]);
}
b.array_new_fixed(translator.typeArrayType, callShape.typeCount);
b.local_set(typeArgs);
final posArgs = b.addLocal(
w.RefType(translator.nullableObjectArrayType, nullable: false));
for (int i = 0; i < callShape.positionalCount; ++i) {
b.local_get(paramLocals[argumentIterator++]);
}
b.array_new_fixed(
translator.nullableObjectArrayType, callShape.positionalCount);
b.local_set(posArgs);
final namedArgs = b.addLocal(
w.RefType(translator.nullableObjectArrayType, nullable: false));
for (int i = 0; i < callShape.named.length; ++i) {
final name = callShape.named[i];
translator.constants.instantiateConstant(b,
translator.symbols.symbolForNamedParameter(name), translator.topType);
b.local_get(paramLocals[argumentIterator++]);
}
b.array_new_fixed(
translator.nullableObjectArrayType, callShape.named.length * 2);
b.local_set(namedArgs);
createInvocationObject(
translator, b, callShape.name, typeArgs, posArgs, namedArgs);
b.return_();
b.end();
}
}
void createInvocationObject(
Translator translator,
w.InstructionsBuilder b,
Name memberName,
w.Local typeArgsLocal,
w.Local positionalArgsLocal,
w.Local namedArgsLocal) {
translator.constants.instantiateConstant(
b,
translator.symbols.methodSymbolFromName(memberName),
translator.classInfo[translator.symbolClass]!.nonNullableType);
b.local_get(typeArgsLocal);
translator.callReference(translator.typeArgumentsToList.reference, b);
b.local_get(positionalArgsLocal);
translator.callReference(translator.positionalParametersToList.reference, b);
b.local_get(namedArgsLocal);
translator.callReference(translator.namedParametersToMap.reference, b);
translator.callReference(
translator.invocationGenericMethodFactory.reference, b);
}
class InitializerListCodeGenerator extends AstCodeGenerator {
final Constructor member;
+256 -395
View File
@@ -9,6 +9,7 @@ import 'class_info.dart';
import 'closures.dart';
import 'code_generator.dart' show CallTarget, CodeGenerator, MacroAssembler;
import 'dispatch_table.dart';
import 'functions.dart' show CallShape;
import 'reference_extensions.dart';
import 'translator.dart';
@@ -46,61 +47,6 @@ class DynamicForwarders {
}
}
class CallShape {
final Name name;
final int typeCount;
final int positionalCount;
final List<String> named;
CallShape(this.name, this.typeCount, this.positionalCount, this.named);
int get totalArgumentCount => typeCount + positionalCount + named.length;
bool matchesTarget(FunctionNode target) {
if (typeCount != target.typeParameters.length && typeCount != 0) {
return false;
}
if (positionalCount < target.requiredParameterCount ||
positionalCount > target.positionalParameters.length) {
return false;
}
final namedParams = target.namedParameters;
for (final name in namedParams) {
if (name.isRequired && !named.contains(name.name)) {
return false;
}
}
for (final name in named) {
if (!namedParams.any((n) => n.name == name)) {
return false;
}
}
return true;
}
@override
int get hashCode =>
Object.hash(name, typeCount, positionalCount, Object.hashAll(named));
@override
bool operator ==(other) {
if (other is! CallShape) return false;
if (name != other.name) return false;
if (typeCount != other.typeCount) return false;
if (named.length != other.named.length) return false;
for (int i = 0; i < named.length; ++i) {
if (named[i] != other.named[i]) {
return false;
}
}
return true;
}
@override
String toString() =>
'CallShape($name, $typeCount, $positionalCount, ${named.join('-')})';
}
class _DynamicForwarderCallTarget extends CallTarget {
final Translator translator;
final _ForwarderKind _kind;
@@ -308,10 +254,10 @@ class _DynamicForwarderCodeGenerator extends CodeGenerator {
b.local_get(receiverLocal);
b.loadClassId(translator, receiverLocal.type);
b.classIdSearch(ranges, [positionalArgLocal.type], (Reference target) {
final Member targetMember = target.asMember;
b.local_get(receiverLocal);
b.local_get(positionalArgLocal);
translator.callReference(targetMember.typeCheckerReference, b);
translator.callFunction(
translator.functions.getDynamicForwarder(target, callerShape), b);
}, () {
generateNoSuchMethodCall(
translator,
@@ -330,75 +276,20 @@ class _DynamicForwarderCodeGenerator extends CodeGenerator {
void _generateMethodCode(Translator translator) {
final b = function.body;
final nullableReceiverLocal = function.locals[0]; // ref #Top
// Load type parameter as WasmArray<_Type>
final typeArgsLocal = b.addLocal(translator.typeArrayTypeRef);
if (callerShape.typeCount == 0) {
final emptyArray = translator.constants
.makeArrayOf(translator.coreTypes.typeNonNullableRawType, []);
translator.constants
.instantiateConstant(b, emptyArray, translator.typeArrayTypeRef);
} else {
for (int i = 0; i < callerShape.typeCount; ++i) {
b.local_get(function.locals[1 + i]);
}
b.array_new_fixed(translator.typeArrayType, callerShape.typeCount);
}
b.local_set(typeArgsLocal);
// Load positional parameters as WasmArray<Object?>
final positionalArgsLocal =
b.addLocal(translator.nullableObjectArrayTypeRef);
if (callerShape.positionalCount == 0) {
final emptyArray = translator.constants
.makeArrayOf(translator.coreTypes.objectNullableRawType, []);
translator.constants.instantiateConstant(
b, emptyArray, translator.nullableObjectArrayTypeRef);
} else {
for (int i = 0; i < callerShape.positionalCount; ++i) {
b.local_get(function.locals[1 + callerShape.typeCount + i]);
}
b.array_new_fixed(
translator.nullableObjectArrayType, callerShape.positionalCount);
}
b.local_set(positionalArgsLocal);
// Load named parameters as WasmArray<Object?>
final namedArgsLocal = b.addLocal(translator.nullableObjectArrayTypeRef);
if (callerShape.named.isEmpty) {
final emptyArray = translator.constants
.makeArrayOf(translator.coreTypes.objectNullableRawType, []);
translator.constants.instantiateConstant(
b, emptyArray, translator.nullableObjectArrayTypeRef);
} else {
for (int i = 0; i < callerShape.named.length; ++i) {
translator.constants.instantiateConstant(
b,
translator.symbols.symbolForNamedParameter(callerShape.named[i]),
translator.topType);
b.local_get(function.locals[
1 + callerShape.typeCount + callerShape.positionalCount + i]);
}
b.array_new_fixed(
translator.nullableObjectArrayType, callerShape.named.length * 2);
}
b.local_set(namedArgsLocal);
// Check for `null`.
final receiverLocal = b.addLocal(translator.topTypeNonNullable);
{
final nullBlock = b.block([], [translator.topTypeNonNullable]);
b.local_get(nullableReceiverLocal);
b.br_on_non_null(nullBlock);
// Throw `NoSuchMethodError`. Normally this needs to happen via instance
// invocation of `noSuchMethod` (done in [_callNoSuchMethod]), but we don't
// have a `Null` class in dart2wasm so we throw directly.
b.local_get(nullableReceiverLocal);
createInvocationObject(translator, b, callerShape.name, typeArgsLocal,
positionalArgsLocal, namedArgsLocal);
for (int i = 0; i < callerShape.totalArgumentCount; ++i) {
b.local_get(function.locals[1 + i]);
}
translator.callFunction(
translator.functions.getInvocationCreatorStub(callerShape), b);
translator.callReference(
translator.noSuchMethodErrorThrowWithInvocation.reference, b);
b.unreachable();
@@ -416,6 +307,8 @@ class _DynamicForwarderCodeGenerator extends CodeGenerator {
// Continuation of this block calls `noSuchMethod` on the receiver.
final noSuchMethodBlock = b.block();
// Step 1) Look through all possible targets that have the dynamic selector
// as a method.
final methodSelectors =
translator.dispatchTable.dynamicMethodSelectors(callerShape.name);
for (final selector in methodSelectors) {
@@ -429,7 +322,6 @@ class _DynamicForwarderCodeGenerator extends CodeGenerator {
for (final MapEntry(key: target, value: classIdRanges)
in targets.entries) {
final Procedure targetMember = target.asMember as Procedure;
final targetMemberParamInfo = translator.paramInfoForDirectCall(target);
final targetFunction = targetMember.function;
// Filter out targets that cannot match based on mismatched arguments.
@@ -460,281 +352,279 @@ class _DynamicForwarderCodeGenerator extends CodeGenerator {
b.end(); // classIdMatch
b.local_get(receiverLocal);
b.local_get(typeArgsLocal);
if (callerShape.positionalCount ==
targetMemberParamInfo.positional.length) {
b.local_get(positionalArgsLocal);
} else {
final targetPositionals = targetFunction.positionalParameters;
for (int i = 0; i < targetMemberParamInfo.positional.length; ++i) {
if (i < callerShape.positionalCount) {
b.local_get(function.locals[1 + callerShape.typeCount + i]);
continue;
}
final defaultValue = targetMemberParamInfo.positional[i];
// The target (a type checker function) has a signature that is
// created based on the union/merged of all members of the selector.
//
// Some implementations of the selector may have more positionals
// than others, hence the `i < targetPositionals.length`.
final defaultFunctionValue = i < targetPositionals.length
? (targetPositionals[i].initializer as ConstantExpression?)
?.constant
: null;
translator.constants.instantiateConstant(
b, defaultFunctionValue ?? defaultValue!, translator.topType);
}
b.array_new_fixed(translator.nullableObjectArrayType,
targetMemberParamInfo.positional.length);
for (int i = 0; i < callerShape.totalArgumentCount; ++i) {
b.local_get(function.locals[1 + i]);
}
Expression? initializerForNamedParamInMember(String paramName) {
for (int i = 0; i < targetFunction.namedParameters.length; i++) {
if (targetFunction.namedParameters[i].name == paramName) {
return targetFunction.namedParameters[i].initializer;
}
}
return null;
}
if (targetMemberParamInfo.names.isEmpty) {
final emptyArray = translator.constants
.makeArrayOf(translator.coreTypes.objectNullableRawType, []);
translator.constants.instantiateConstant(
b, emptyArray, translator.nullableObjectArrayTypeRef);
} else {
// The type checker forwarder expects all named arguments as an array of
// values (i.e. not array of (symbol, value) pairs).
for (int i = 0; i < targetMemberParamInfo.names.length; ++i) {
final name = targetMemberParamInfo.names[i];
final index = callerShape.named.indexOf(name);
if (index != -1) {
b.local_get(function.locals[1 +
callerShape.typeCount +
callerShape.positionalCount +
index]);
continue;
}
final defaultValue = targetMemberParamInfo.named[name];
final defaultFunctionValue =
(initializerForNamedParamInMember(name) as ConstantExpression?)
?.constant;
assert(defaultValue != null || defaultFunctionValue != null);
translator.constants.instantiateConstant(
b, defaultFunctionValue ?? defaultValue!, translator.topType);
}
b.array_new_fixed(translator.nullableObjectArrayType,
targetMemberParamInfo.named.length);
}
translator.callReference(targetMember.typeCheckerReference, b);
translator.callFunction(
translator.functions.getDynamicForwarder(target, callerShape), b);
b.return_();
b.end(); // classIdNoMatch
}
}
final getterValueLocal = b.addLocal(translator.topType);
void handleGetterSelector(SelectorInfo selector) {
for (final (:range, :target)
in selector.targets(unchecked: false).allTargetRanges) {
final targetMember = target.asMember;
// This loop checks getters and fields. Methods are considered in the
// previous loop, skip them here.
if (targetMember is Procedure && !targetMember.isGetter) {
continue;
// Step 2) The receiver does not have the dynamic selector as a method. Now
// we look through all possible getters with the dynamic selector name,
// invoke the getter and then try to call it (via closure call or `.call()`).
final getterSelectors =
translator.dispatchTable.dynamicGetterSelectors(callerShape.name);
final dynamicMainModuleGetterSelectors = translator
.dynamicMainModuleDispatchTable
?.dynamicGetterSelectors(callerShape.name);
if (getterSelectors.isNotEmpty ||
dynamicMainModuleGetterSelectors != null) {
// Load type parameter as WasmArray<_Type>
final typeArgsLocal = b.addLocal(translator.typeArrayTypeRef);
if (callerShape.typeCount == 0) {
final emptyArray = translator.constants
.makeArrayOf(translator.coreTypes.typeNonNullableRawType, []);
translator.constants
.instantiateConstant(b, emptyArray, translator.typeArrayTypeRef);
} else {
for (int i = 0; i < callerShape.typeCount; ++i) {
b.local_get(function.locals[1 + i]);
}
b.array_new_fixed(translator.typeArrayType, callerShape.typeCount);
}
b.local_set(typeArgsLocal);
for (int classId = range.start; classId <= range.end; ++classId) {
b.local_get(receiverLocal);
b.loadClassId(translator, receiverLocal.type);
b.i32_const(classId);
b.i32_eq();
b.if_();
// Load positional parameters as WasmArray<Object?>
final positionalArgsLocal =
b.addLocal(translator.nullableObjectArrayTypeRef);
if (callerShape.positionalCount == 0) {
final emptyArray = translator.constants
.makeArrayOf(translator.coreTypes.objectNullableRawType, []);
translator.constants.instantiateConstant(
b, emptyArray, translator.nullableObjectArrayTypeRef);
} else {
for (int i = 0; i < callerShape.positionalCount; ++i) {
b.local_get(function.locals[1 + callerShape.typeCount + i]);
}
b.array_new_fixed(
translator.nullableObjectArrayType, callerShape.positionalCount);
}
b.local_set(positionalArgsLocal);
final Reference targetReference;
if (targetMember is Procedure) {
assert(targetMember.isGetter); // methods are skipped above
targetReference = targetMember.reference;
} else if (targetMember is Field) {
targetReference = targetMember.getterReference;
} else {
throw '_generateMethodCode: member is not a procedure or field: $targetMember';
}
final w.BaseFunction targetFunction =
translator.functions.getFunction(targetReference);
// Get field value
b.local_get(receiverLocal);
translator.convertType(
b, receiverLocal.type, targetFunction.type.inputs.first);
translator.callFunction(targetFunction, b);
translator.convertType(
b, targetFunction.type.outputs.single, translator.topType);
b.local_tee(getterValueLocal);
// Throw `NoSuchMethodError` if the value is null
b.br_on_null(noSuchMethodBlock);
// Reuse `receiverLocal`. This also updates the `noSuchMethod` receiver
// below.
b.local_tee(receiverLocal);
// Invoke "call" if the value is not a closure
b.loadClassId(translator, receiverLocal.type);
b.i32_const(
(translator.closureInfo.classId as AbsoluteClassId).value);
b.i32_ne();
b.if_();
// Value is not a closure
final callForwarder = translator
.getDynamicForwardersForModule(b.moduleBuilder)
.getDynamicInvocationForwarder(CallShape(
Name('call'),
callerShape.typeCount,
callerShape.positionalCount,
callerShape.named))
.function;
b.local_get(receiverLocal);
for (int i = 0; i < callerShape.typeCount; ++i) {
b.local_get(function.locals[1 + i]);
}
for (int i = 0; i < callerShape.positionalCount; ++i) {
b.local_get(function.locals[1 + callerShape.typeCount + i]);
}
for (int i = 0; i < callerShape.named.length; ++i) {
b.local_get(function.locals[
1 + callerShape.typeCount + callerShape.positionalCount + i]);
}
translator.callFunction(callForwarder, b);
b.return_();
b.end();
// Cast the closure to `#ClosureBase`
final closureBaseType = w.RefType.def(
translator.closureLayouter.closureBaseStruct,
nullable: false);
final closureLocal = b.addLocal(closureBaseType);
b.local_get(receiverLocal);
b.ref_cast(closureBaseType);
b.local_set(closureLocal);
generateDynamicClosureCallShapeAndTypeCheck(
translator,
// Load named parameters as WasmArray<Object?>
final namedArgsLocal = b.addLocal(translator.nullableObjectArrayTypeRef);
if (callerShape.named.isEmpty) {
final emptyArray = translator.constants
.makeArrayOf(translator.coreTypes.objectNullableRawType, []);
translator.constants.instantiateConstant(
b, emptyArray, translator.nullableObjectArrayTypeRef);
} else {
for (int i = 0; i < callerShape.named.length; ++i) {
translator.constants.instantiateConstant(
b,
closureLocal,
typeArgsLocal,
positionalArgsLocal,
namedArgsLocal,
noSuchMethodBlock);
if (translator.dynamicModuleSupportEnabled) {
generateDynamicClosureCallViaDynamicEntry(
translator.symbols.symbolForNamedParameter(callerShape.named[i]),
translator.topType);
b.local_get(function.locals[
1 + callerShape.typeCount + callerShape.positionalCount + i]);
}
b.array_new_fixed(
translator.nullableObjectArrayType, callerShape.named.length * 2);
}
b.local_set(namedArgsLocal);
final getterValueLocal = b.addLocal(translator.topType);
void handleGetterSelector(SelectorInfo selector) {
for (final (:range, :target)
in selector.targets(unchecked: false).allTargetRanges) {
final targetMember = target.asMember;
// We only care about getters here as methods were already handled in
// the loop in `Step 1` above.
if (targetMember is Procedure && !targetMember.isGetter) {
continue;
}
for (int classId = range.start; classId <= range.end; ++classId) {
b.local_get(receiverLocal);
b.loadClassId(translator, receiverLocal.type);
b.i32_const(classId);
b.i32_eq();
b.if_();
final Reference targetReference;
if (targetMember is Procedure) {
assert(targetMember.isGetter); // methods are skipped above
targetReference = targetMember.reference;
} else if (targetMember is Field) {
targetReference = targetMember.getterReference;
} else {
throw StateError('Expected field getter or procedure getter.');
}
final w.BaseFunction targetFunction =
translator.functions.getFunction(targetReference);
// Get field value
b.local_get(receiverLocal);
translator.convertType(
b, receiverLocal.type, targetFunction.type.inputs.first);
translator.callFunction(targetFunction, b);
translator.convertType(
b, targetFunction.type.outputs.single, translator.topType);
b.local_tee(getterValueLocal);
// Throw `NoSuchMethodError` if the value is null
b.br_on_null(noSuchMethodBlock);
// Reuse `receiverLocal`. This also updates the `noSuchMethod`
// receiver below.
b.local_tee(receiverLocal);
// Invoke "call" if the value is not a closure
b.loadClassId(translator, receiverLocal.type);
b.i32_const(
(translator.closureInfo.classId as AbsoluteClassId).value);
b.i32_ne();
b.if_();
// Value is not a closure
final callForwarder = translator
.getDynamicForwardersForModule(b.moduleBuilder)
.getDynamicInvocationForwarder(CallShape(
Name('call'),
callerShape.typeCount,
callerShape.positionalCount,
callerShape.named))
.function;
b.local_get(receiverLocal);
for (int i = 0; i < callerShape.typeCount; ++i) {
b.local_get(function.locals[1 + i]);
}
for (int i = 0; i < callerShape.positionalCount; ++i) {
b.local_get(function.locals[1 + callerShape.typeCount + i]);
}
for (int i = 0; i < callerShape.named.length; ++i) {
b.local_get(function.locals[
1 + callerShape.typeCount + callerShape.positionalCount + i]);
}
translator.callFunction(callForwarder, b);
b.return_();
b.end();
// Cast the closure to `#ClosureBase`
final closureBaseType = w.RefType.def(
translator.closureLayouter.closureBaseStruct,
nullable: false);
final closureLocal = b.addLocal(closureBaseType);
b.local_get(receiverLocal);
b.ref_cast(closureBaseType);
b.local_set(closureLocal);
generateDynamicClosureCallShapeAndTypeCheck(
translator,
b,
closureLocal,
typeArgsLocal,
positionalArgsLocal,
namedArgsLocal);
} else {
void emitCallForTypeCount(int typeCount) {
final representation = translator.closureLayouter
.getClosureRepresentation(typeCount,
callerShape.positionalCount, callerShape.named);
if (representation == null) {
// This is a call combination that the closure layouter determined
// cannot occur in the program (it means the shape&type checks
// we already performed earlier must have thrown an NSM error
// and we cannot get here).
b.unreachable();
return;
}
b.local_get(closureLocal);
b.struct_get(translator.closureLayouter.closureBaseStruct,
FieldIndex.closureContext);
for (int i = 0; i < typeCount; ++i) {
b.local_get(typeArgsLocal);
b.i32_const(i);
b.array_get(translator.typeArrayType);
}
for (int i = 0; i < callerShape.positionalCount; ++i) {
b.local_get(function.locals[1 + callerShape.typeCount + i]);
}
for (int i = 0; i < callerShape.named.length; ++i) {
b.local_get(function.locals[1 +
callerShape.typeCount +
callerShape.positionalCount +
i]);
}
final vtable = representation.vtableStruct;
final vtableIndex = representation.fieldIndexForSignature(
callerShape.positionalCount, callerShape.named);
b.local_get(closureLocal);
b.struct_get(translator.closureLayouter.closureBaseStruct,
FieldIndex.closureVtable);
b.ref_cast(w.RefType(vtable, nullable: false));
b.struct_get(vtable, vtableIndex);
b.call_ref(vtable.getVtableEntryAt(vtableIndex));
}
// The closure representation algorithm has considered dynamic
// callsites and will have therefore specialized vtable entries
// for valid call shape of dynamic closure calls.
if (callerShape.typeCount == 0) {
// The dynamic callsite has not provided type arguments but the
// target closure may be generic. The shape&type checking we
// already performed may have populated default type arguments (of
// unknown length) for the closure.
//
// So we
final maxTypeCount =
translator.closureLayouter.maxTypeArgumentCount();
b.emitDenseTableBranch([translator.topType], maxTypeCount, () {
b.local_get(typeArgsLocal);
b.array_len();
}, (int typeCount) {
emitCallForTypeCount(typeCount);
}, () {
b.unreachable();
});
namedArgsLocal,
noSuchMethodBlock);
if (translator.dynamicModuleSupportEnabled) {
generateDynamicClosureCallViaDynamicEntry(
translator,
b,
closureLocal,
typeArgsLocal,
positionalArgsLocal,
namedArgsLocal);
} else {
emitCallForTypeCount(callerShape.typeCount);
}
}
b.return_();
void emitCallForTypeCount(int typeCount) {
final representation = translator.closureLayouter
.getClosureRepresentation(typeCount,
callerShape.positionalCount, callerShape.named);
if (representation == null) {
// This is a call combination that the closure layouter
// determined cannot occur in the program (it means the
// shape&type checks we already performed earlier must
// have thrown an NSM error and we cannot get here).
b.unreachable();
return;
}
b.end(); // class ID
b.local_get(closureLocal);
b.struct_get(translator.closureLayouter.closureBaseStruct,
FieldIndex.closureContext);
for (int i = 0; i < typeCount; ++i) {
b.local_get(typeArgsLocal);
b.i32_const(i);
b.array_get(translator.typeArrayType);
}
for (int i = 0; i < callerShape.positionalCount; ++i) {
b.local_get(function.locals[1 + callerShape.typeCount + i]);
}
for (int i = 0; i < callerShape.named.length; ++i) {
b.local_get(function.locals[1 +
callerShape.typeCount +
callerShape.positionalCount +
i]);
}
final vtable = representation.vtableStruct;
final vtableIndex = representation.fieldIndexForSignature(
callerShape.positionalCount, callerShape.named);
b.local_get(closureLocal);
b.struct_get(translator.closureLayouter.closureBaseStruct,
FieldIndex.closureVtable);
b.ref_cast(w.RefType(vtable, nullable: false));
b.struct_get(vtable, vtableIndex);
b.call_ref(vtable.getVtableEntryAt(vtableIndex));
}
// The closure representation algorithm has considered dynamic
// callsites and will have therefore specialized vtable entries
// for valid call shape of dynamic closure calls.
if (callerShape.typeCount == 0) {
// The dynamic callsite has not provided type arguments but the
// target closure may be generic. The shape&type checking we
// already performed may have populated default type arguments
// (of unknown length) for the closure.
//
// So we branch on the number of type parameters to invoke the
// right closure entrypoint.
final maxTypeCount =
translator.closureLayouter.maxTypeArgumentCount();
b.emitDenseTableBranch([translator.topType], maxTypeCount, () {
b.local_get(typeArgsLocal);
b.array_len();
}, (int typeCount) {
emitCallForTypeCount(typeCount);
}, () {
b.unreachable();
});
} else {
emitCallForTypeCount(callerShape.typeCount);
}
}
b.return_();
b.end(); // class ID
}
}
}
}
final getterSelectors =
translator.dispatchTable.dynamicGetterSelectors(callerShape.name);
for (final selector in getterSelectors) {
handleGetterSelector(selector);
}
final dynamicMainModuleGetterSelectors = translator
.dynamicMainModuleDispatchTable
?.dynamicGetterSelectors(callerShape.name);
if (dynamicMainModuleGetterSelectors != null) {
for (final selector in dynamicMainModuleGetterSelectors) {
for (final selector in getterSelectors) {
handleGetterSelector(selector);
}
if (dynamicMainModuleGetterSelectors != null) {
for (final selector in dynamicMainModuleGetterSelectors) {
handleGetterSelector(selector);
}
}
}
b.end(); // noSuchMethodBlock
// Unable to find a matching member, call `noSuchMethod`
generateNoSuchMethodCall(
translator,
b,
() => b.local_get(receiverLocal),
() => createInvocationObject(translator, b, callerShape.name,
typeArgsLocal, positionalArgsLocal, namedArgsLocal));
generateNoSuchMethodCall(translator, b, () => b.local_get(receiverLocal),
() {
for (int i = 0; i < callerShape.totalArgumentCount; ++i) {
b.local_get(function.locals[1 + i]);
}
translator.callFunction(
translator.functions.getInvocationCreatorStub(callerShape), b);
});
b.end();
}
@@ -910,28 +800,6 @@ void generateDynamicClosureCallViaPositionalArgs(
});
}
void createInvocationObject(
Translator translator,
w.InstructionsBuilder b,
Name memberName,
w.Local typeArgsLocal,
w.Local positionalArgsLocal,
w.Local namedArgsLocal) {
translator.constants.instantiateConstant(
b,
translator.symbols.methodSymbolFromName(memberName),
translator.classInfo[translator.symbolClass]!.nonNullableType);
b.local_get(typeArgsLocal);
translator.callReference(translator.typeArgumentsToList.reference, b);
b.local_get(positionalArgsLocal);
translator.callReference(translator.positionalParametersToList.reference, b);
b.local_get(namedArgsLocal);
translator.callReference(translator.namedParametersToMap.reference, b);
translator.callReference(
translator.invocationGenericMethodFactory.reference, b);
}
void createGetterInvocationObject(
Translator translator,
w.InstructionsBuilder b,
@@ -1036,10 +904,3 @@ void generateNoSuchMethodCall(
useUncheckedEntry: false);
}
}
class ClassIdRange {
final int start;
final int end; // inclusive
ClassIdRange(this.start, this.end);
}
+136 -19
View File
@@ -22,6 +22,11 @@ class FunctionCollector {
// Wasm function for each Dart function
final Map<Reference, w.BaseFunction> _functions = {};
// Wasm function for each Dart function + caller shape combination.
final Map<Reference, Map<CallShape, w.BaseFunction>>
_dynamicForwarderFunctions = {};
// Wasm function to create [Invocation] objects based on [CallShape].
final Map<CallShape, w.BaseFunction> _invocationCreatorStubs = {};
// Wasm function for each function expression and local function.
final Map<Lambda, w.BaseFunction> _lambdas = {};
// Selector IDs that are invoked via GDT.
@@ -143,9 +148,7 @@ class FunctionCollector {
: translator.signatureForDirectCall(target);
final function = module.functions.define(ftype, getFunctionName(target))
..isPure = hasPureAnnotation &&
!target.isTypeCheckerReference &&
!target.isCheckedEntryReference;
..isPure = hasPureAnnotation && !target.isCheckedEntryReference;
if (exportName != null) module.exports.export(exportName, function);
// Export the function from the main module if it is callable from
@@ -164,6 +167,34 @@ class FunctionCollector {
});
}
w.BaseFunction getDynamicForwarder(Reference target, CallShape shape) {
return (_dynamicForwarderFunctions[target] ??= {}).putIfAbsent(shape, () {
final module = translator.moduleForReference(target);
final ftype = makeDynamicForwarderSignature(translator, shape);
final name = getDynamicForwarderName(target, shape);
final function = module.functions.define(ftype, name);
final codegen =
DynamicForwarderCodeGenerator(translator, ftype, target, shape);
translator.compilationQueue
.add(AstCompilationTask(function, codegen, target));
return function;
});
}
w.BaseFunction getInvocationCreatorStub(CallShape shape) {
return _invocationCreatorStubs.putIfAbsent(shape, () {
final module = translator.isDynamicSubmodule
? translator.dynamicSubmodule
: translator.mainModule;
final ftype = makeInvocationCreatorSignature(translator, shape);
final name = getInvocationCreatorStubName(shape);
final function = module.functions.define(ftype, name);
final codegen = InvocationCreationStubGenerator(translator, shape);
translator.compilationQueue.add(CompilationTask(function, codegen));
return function;
});
}
w.BaseFunction _importFunctionToDynamicSubmodule(Reference target) {
assert(translator.isDynamicSubmodule);
@@ -217,14 +248,6 @@ class FunctionCollector {
return makeFunctionTypeForBody(translator, member);
}
if (target.isTypeCheckerReference) {
if (member is Field || (member is Procedure && member.isSetter)) {
return translator.dynamicSetForwarderFunctionType;
} else {
return translator.dynamicInvocationForwarderFunctionType;
}
}
if (target.isTearOffReference) {
assert(!translator.dispatchTable
.selectorForTarget(target)
@@ -270,14 +293,6 @@ class FunctionCollector {
memberName = memberName.substring(0, memberName.length - 1);
}
if (target.isTypeCheckerReference) {
if (member is Field || (member is Procedure && member.isSetter)) {
return '$memberName setter type checker';
} else {
return '$memberName invocation type checker';
}
}
if (member is Field) {
if (target.isImplicitSetter) {
return '$memberName= implicit setter';
@@ -299,6 +314,16 @@ class FunctionCollector {
}
}
String getDynamicForwarderName(Reference target, CallShape shape) {
final member = target.asMember;
final memberName = member.toString();
return '$memberName ($shape)';
}
String getInvocationCreatorStubName(CallShape shape) {
return 'Invocation creator ($shape)';
}
void recordSelectorUse(SelectorInfo selector, bool useUncheckedEntry) {
final set =
useUncheckedEntry ? _calledUncheckedSelectors : _calledSelectors;
@@ -609,6 +634,31 @@ w.FunctionType makeFunctionTypeForBody(Translator translator, Member member) {
return translator.typesBuilder.defineFunction(inputs, outputs);
}
w.FunctionType makeDynamicForwarderSignature(
Translator translator, CallShape shape) {
return translator.typesBuilder.defineFunction([
translator.topTypeNonNullable,
for (int i = 0; i < shape.typeCount; ++i)
translator.translateType(translator.types.typeType),
for (int i = 0; i < shape.positionalCount; ++i) translator.topType,
for (int i = 0; i < shape.named.length; ++i) translator.topType,
], [
translator.topType
]);
}
w.FunctionType makeInvocationCreatorSignature(
Translator translator, CallShape shape) {
return translator.typesBuilder.defineFunction([
for (int i = 0; i < shape.typeCount; ++i)
translator.translateType(translator.types.typeType),
for (int i = 0; i < shape.positionalCount; ++i) translator.topType,
for (int i = 0; i < shape.named.length; ++i) translator.topType,
], [
translator.invocationType,
]);
}
w.FunctionType _makeFunctionType(
Translator translator, Reference target, w.ValueType? receiverType,
{bool isImportOrExport = false}) {
@@ -652,3 +702,70 @@ w.FunctionType _makeFunctionType(
return translator.typesBuilder.defineFunction(inputs, outputs);
}
class CallShape {
final Name name;
final int typeCount;
final int positionalCount;
final List<String> named;
CallShape(this.name, this.typeCount, this.positionalCount, this.named);
int get totalArgumentCount => typeCount + positionalCount + named.length;
bool matchesTarget(FunctionNode target) {
if (typeCount != target.typeParameters.length && typeCount != 0) {
return false;
}
if (positionalCount < target.requiredParameterCount ||
positionalCount > target.positionalParameters.length) {
return false;
}
final namedParams = target.namedParameters;
for (final name in namedParams) {
if (name.isRequired && !named.contains(name.name)) {
return false;
}
}
for (final name in named) {
if (!namedParams.any((n) => n.name == name)) {
return false;
}
}
return true;
}
@override
int get hashCode =>
Object.hash(name, typeCount, positionalCount, Object.hashAll(named));
@override
bool operator ==(other) {
if (other is! CallShape) return false;
if (name != other.name) return false;
if (typeCount != other.typeCount) return false;
if (named.length != other.named.length) return false;
for (int i = 0; i < named.length; ++i) {
if (named[i] != other.named[i]) {
return false;
}
}
return true;
}
@override
String toString() {
final sb = StringBuffer();
sb.write('$name');
if (typeCount != 0) {
sb.write(' types:$typeCount');
}
if (positionalCount != 0) {
sb.write(' pos:$positionalCount');
}
if (named.isNotEmpty) {
sb.write(' names:${named.join('-')}');
}
return 'CallShape($sb)';
}
}
+1 -2
View File
@@ -135,8 +135,7 @@ class ModuleOutputData {
}
} else {
node as Procedure;
if (reference.isTypeCheckerReference ||
reference.isCheckedEntryReference ||
if (reference.isCheckedEntryReference ||
reference.isUncheckedEntryReference ||
reference.isBodyReference ||
reference.isTearOffReference) {
+1 -8
View File
@@ -18,8 +18,7 @@ extension GetterSetterReference on Reference {
if (member.setterReference == this) return true;
if (member.isInstanceMember) {
return _isUncheckedEntrySetterReference ||
_isCheckedEntrySetterReference ||
isTypeCheckerReference;
_isCheckedEntrySetterReference;
}
}
return false;
@@ -44,7 +43,6 @@ extension GetterSetterReference on Reference {
// Use Expandos to avoid keeping the procedure alive.
final Expando<Reference> _staticFieldInitializerReference = Expando();
final Expando<Reference> _tearOffReference = Expando();
final Expando<Reference> _typeCheckerReference = Expando();
final Expando<Reference> _checkedEntryReferences = Expando();
final Expando<Reference> _uncheckedEntryReferences = Expando();
final Expando<Reference> _bodyReferences = Expando();
@@ -58,9 +56,6 @@ extension CustomReference on Member {
Reference get tearOffReference =>
_tearOffReference[this] ??= Reference()..node = this;
Reference get typeCheckerReference =>
_typeCheckerReference[this] ??= Reference()..node = this;
Reference get checkedEntryReference {
assert(_memberCanHaveMultipleEntryPoints(this));
return _checkedEntryReferences[this] ??= Reference()..node = this;
@@ -89,8 +84,6 @@ extension IsCustomReference on Reference {
bool get isTearOffReference => _tearOffReference[asMember] == this;
bool get isTypeCheckerReference => _typeCheckerReference[asMember] == this;
bool get isCheckedEntryReference => _checkedEntryReferences[asMember] == this;
bool get _isCheckedEntrySetterReference =>
+10 -12
View File
@@ -38,13 +38,12 @@ class _EntityToIdMapper {
if (reference.isTearOffReference) return 2;
if (reference.isConstructorBodyReference) return 3;
if (reference.isInitializerReference) return 4;
if (reference.isTypeCheckerReference) return 5;
if (reference.isCheckedEntryReference) return 6;
if (reference.isUncheckedEntryReference) return 7;
if (reference.isBodyReference) return 8;
if (reference.isStaticFieldInitializer) return 9;
if (reference.isCheckedEntryReference) return 5;
if (reference.isUncheckedEntryReference) return 6;
if (reference.isBodyReference) return 7;
if (reference.isStaticFieldInitializer) return 8;
assert(reference == reference.asMember.reference);
return 10;
return 9;
}
}
@@ -85,12 +84,11 @@ class _IdToEntityMapper {
if (flag == 2) return (member as Procedure).tearOffReference;
if (flag == 3) return (member as Constructor).constructorBodyReference;
if (flag == 4) return (member as Constructor).initializerReference;
if (flag == 5) return member.typeCheckerReference;
if (flag == 6) return member.checkedEntryReference;
if (flag == 7) return member.uncheckedEntryReference;
if (flag == 8) return member.bodyReference;
if (flag == 9) return (member as Field).staticFieldInitializer;
assert(flag == 10);
if (flag == 5) return member.checkedEntryReference;
if (flag == 6) return member.uncheckedEntryReference;
if (flag == 7) return member.bodyReference;
if (flag == 8) return (member as Field).staticFieldInitializer;
assert(flag == 9);
return member.reference;
}
}
+8 -49
View File
@@ -325,6 +325,11 @@ class Translator with KernelNodes {
// The wasm type used to hold values of `String?`
late final w.RefType stringTypeNullable = stringType.withNullability(true);
// The wasm type used to hold values of `Invocation`
late final w.RefType invocationType = translateType(
InterfaceType(coreTypes.invocationClass, Nullability.nonNullable))
as w.RefType;
final Map<w.ModuleBuilder, PartialInstantiator> _partialInstantiators = {};
PartialInstantiator getPartialInstantiatorForModule(w.ModuleBuilder module) {
return _partialInstantiators[module] ??= PartialInstantiator(this, module);
@@ -415,45 +420,6 @@ class Translator with KernelNodes {
topType,
]);
/// Type of a dynamic invocation forwarder function.
late final w.FunctionType dynamicInvocationForwarderFunctionType =
typesBuilder.defineFunction([
// Receiver
topTypeNonNullable,
// Type arguments
typeArrayTypeRef,
// Positional arguments
nullableObjectArrayTypeRef,
// Named arguments, represented as array of symbol and object pairs
nullableObjectArrayTypeRef,
], [
topType,
]);
/// Type of a dynamic get forwarder function.
late final w.FunctionType dynamicGetForwarderFunctionType =
typesBuilder.defineFunction([
// Receiver
topTypeNonNullable,
], [
topType,
]);
/// Type of a dynamic set forwarder function.
late final w.FunctionType dynamicSetForwarderFunctionType =
typesBuilder.defineFunction([
// Receiver
topTypeNonNullable,
// Positional argument
topType,
], [
topType,
]);
// Module predicates and helpers
final ModuleOutputData _moduleOutputData;
Iterable<w.ModuleBuilder> get modules => _builderToOutput.keys;
@@ -1702,9 +1668,7 @@ class Translator with KernelNodes {
}
w.FunctionType _signatureForModule(Reference target, DispatchTable? table) {
if (table != null &&
!target.isBodyReference &&
!target.isTypeCheckerReference) {
if (table != null && !target.isBodyReference) {
final selector = table.selectorForTarget(target);
if (selector.containsTarget(target) ||
selector.isDynamicSubmoduleOverridable) {
@@ -2327,16 +2291,13 @@ class AstCompilationTask extends CompilationTask {
if (exportName != null) {
header = "$header (exported as $exportName)";
}
if (reference.isTypeCheckerReference) {
header = "$header (type checker)";
}
print(header);
print(function.type);
print(member.function
?.computeFunctionType(Nullability.nonNullable)
.toStringInternal());
}
if (printKernel && !reference.isTypeCheckerReference) {
if (printKernel) {
if (member is Constructor) {
Class cls = member.enclosingClass;
for (Field field in cls.fields) {
@@ -2355,9 +2316,7 @@ class AstCompilationTask extends CompilationTask {
if (!printWasm) print("");
}
final codeGen = getMemberCodeGenerator(translator, function, reference);
codeGen.generate(function.body, function.locals.toList(), null);
_codeGenerator.generate(function.body, function.locals.toList(), null);
if (printWasm) {
print(function.body.trace);
}
+19 -98
View File
@@ -2,8 +2,6 @@
(type $#Top (struct
(field $field0 i32)))
(type $Array<Object?> (array (field (mut (ref null $#Top)))))
(type $Array<WasmI32> (array (field (mut i32))))
(type $Array<_Type> (array (field (mut (ref $_Type)))))
(type $BoxedBool (sub final $#Top (struct
(field $field0 i32)
(field $value (mut i32)))))
@@ -31,24 +29,6 @@
(field $field2 (ref $_Type))
(field $_length (mut i64))
(field $_data (mut (ref $Array<Object?>))))))
(type $_DefaultMap&_HashFieldBase&MapMixin (sub final $_HashFieldBase (struct
(field $field0 i32)
(field $field1 (mut i32))
(field $_index (mut (ref $Array<WasmI32>)))
(field $_hashMask (mut i64))
(field $_data (mut (ref $Array<Object?>)))
(field $_usedData (mut i64))
(field $_deletedKeys (mut i64))
(field $field7 (ref $_Type))
(field $field8 (ref $_Type)))))
(type $_HashFieldBase (sub $Object (struct
(field $field0 i32)
(field $field1 (mut i32))
(field $_index (mut (ref $Array<WasmI32>)))
(field $_hashMask (mut i64))
(field $_data (mut (ref $Array<Object?>)))
(field $_usedData (mut i64))
(field $_deletedKeys (mut i64)))))
(type $_Invocation (sub final $Object (struct
(field $field0 i32)
(field $field1 (mut i32))
@@ -59,32 +39,6 @@
(field $field0 i32)
(field $field1 (mut i32))
(field $isDeclaredNullable i32))))
(global $.a (import "" "a") (ref extern))
(global $.toString (import "" "toString") (ref extern))
(global $"SymbolConstant(#a)" (ref $Symbol)
(i32.const 69)
(i32.const 0)
(global.get $"\"a\"")
(struct.new $Symbol))
(global $"SymbolConstant(#toString)" (ref $Symbol)
(i32.const 69)
(i32.const 0)
(global.get $"\"toString\"")
(struct.new $Symbol))
(global $"WasmArray<Object>[0]" (ref $Array<Object?>)
(array.new_fixed $Array<Object?> 0))
(global $"WasmArray<Type>[0]" (ref $Array<_Type>)
(array.new_fixed $Array<_Type> 0))
(global $"\"a\"" (ref $JSExternWrapper)
(i32.const 106)
(i32.const 0)
(global.get $.a)
(struct.new $JSExternWrapper))
(global $"\"toString\"" (ref $JSExternWrapper)
(i32.const 106)
(i32.const 0)
(global.get $.toString)
(struct.new $JSExternWrapper))
(global $1 (ref $BoxedInt)
(i32.const 65)
(i64.const 1)
@@ -93,95 +47,62 @@
(i32.const 3)
(i32.const 1)
(struct.new $BoxedBool))
(func $Bar.toString invocation type checker (param $this (ref $#Top)) (param $var0 (ref $Array<_Type>)) (param $var1 (ref $Array<Object?>)) (param $var2 (ref $Array<Object?>)) (result (ref null $#Top)) <...>)
(func $"Dynamic method forwarder for \"CallShape(toString, 0, 0, a)\"" (param $var0 (ref null $#Top)) (param $var1 (ref null $#Top)) (result (ref null $#Top))
(local $var2 (ref $Array<_Type>))
(local $var3 (ref $Array<Object?>))
(local $var4 (ref $Array<Object?>))
(local $var5 (ref $#Top))
(local $var6 i32)
(local $var7 (ref null $#Top))
global.get $"WasmArray<Type>[0]"
local.set $var2
global.get $"WasmArray<Object>[0]"
local.set $var3
global.get $"SymbolConstant(#a)"
local.get $var1
array.new_fixed $Array<Object?> 2
local.set $var4
(func $Bar.toString (CallShape(toString names:a)) (param $this (ref $#Top)) (param $var0 (ref null $#Top)) (result (ref null $#Top)) <...>)
(func $"Dynamic method forwarder for \"CallShape(toString names:a)\"" (param $var0 (ref null $#Top)) (param $var1 (ref null $#Top)) (result (ref null $#Top))
(local $var2 (ref $#Top))
(local $var3 i32)
block $label0 (result (ref $#Top))
local.get $var0
br_on_non_null $label0
local.get $var0
global.get $"SymbolConstant(#toString)"
local.get $var2
call $_typeArgumentsToList
local.get $var3
call $_positionalParametersToList
local.get $var4
call $_namedParametersToMap
call $"new Invocation.genericMethod"
local.get $var1
call $"Invocation creator (CallShape(toString names:a))"
call $NoSuchMethodError._throwWithInvocation
unreachable
end $label0
local.set $var5
local.get $var5
local.set $var2
local.get $var2
struct.get $#Top $field0
local.set $var6
local.set $var3
block $label1
block $label2
block $label3
local.get $var6
local.get $var3
i32.const 108
i32.eq
br_if $label3
br $label2
end $label3
local.get $var5
local.get $var2
local.get $var3
local.get $var1
array.new_fixed $Array<Object?> 1
call $"Bar.toString invocation type checker"
call $"Bar.toString (CallShape(toString names:a))"
return
end $label2
block $label4
block $label5
local.get $var6
local.get $var3
i32.const 109
i32.eq
br_if $label5
br $label4
end $label5
local.get $var5
local.get $var2
local.get $var3
local.get $var1
array.new_fixed $Array<Object?> 1
call $"Foo.toString invocation type checker"
call $"Foo.toString (CallShape(toString names:a))"
return
end $label4
end
local.get $var5
global.get $"SymbolConstant(#toString)"
local.get $var2
call $_typeArgumentsToList
local.get $var3
call $_positionalParametersToList
local.get $var4
call $_namedParametersToMap
call $"new Invocation.genericMethod"
local.get $var1
call $"Invocation creator (CallShape(toString names:a))"
call $Object.noSuchMethod
)
(func $Foo.toString invocation type checker (param $this (ref $#Top)) (param $var0 (ref $Array<_Type>)) (param $var1 (ref $Array<Object?>)) (param $var2 (ref $Array<Object?>)) (result (ref null $#Top)) <...>)
(func $new Invocation.genericMethod (param $memberName (ref $Symbol)) (param $typeArguments (ref null $Object)) (param $positionalArguments (ref null $Object)) (param $namedArguments (ref null $Object)) (result (ref $_Invocation)) <...>)
(func $Foo.toString (CallShape(toString names:a)) (param $this (ref $#Top)) (param $var0 (ref null $#Top)) (result (ref null $#Top)) <...>)
(func $Invocation creator (CallShape(toString names:a)) (param $var0 (ref null $#Top)) (result (ref $_Invocation)) <...>)
(func $Bar (result (ref $Object)) <...>)
(func $Foo (result (ref $Object)) <...>)
(func $NoSuchMethodError._throwWithInvocation (param $receiver (ref null $#Top)) (param $invocation (ref $_Invocation)) (result (ref none)) <...>)
(func $Object.noSuchMethod (param $this (ref $#Top)) (param $invocation (ref $_Invocation)) (result (ref null $#Top)) <...>)
(func $_namedParametersToMap (param $namedArguments (ref $Array<Object?>)) (result (ref $_DefaultMap&_HashFieldBase&MapMixin)) <...>)
(func $_positionalParametersToList (param $positional (ref $Array<Object?>)) (result (ref $WasmListBase)) <...>)
(func $_typeArgumentsToList (param $typeArgs (ref $Array<_Type>)) (result (ref $WasmListBase)) <...>)
(func $confuse (param $a (ref null $#Top)) (result (ref null $#Top)) <...>)
(func $main (result (ref null $#Top))
(local $var0 (ref null $#Top))
@@ -191,7 +112,7 @@
global.get $true
local.set $var0
local.get $var0
call $"Dynamic method forwarder for \"CallShape(toString, 0, 0, a)\""
call $"Dynamic method forwarder for \"CallShape(toString names:a)\""
call $print
drop
call $Bar
@@ -199,7 +120,7 @@
global.get $1
local.set $var1
local.get $var1
call $"Dynamic method forwarder for \"CallShape(toString, 0, 0, a)\""
call $"Dynamic method forwarder for \"CallShape(toString names:a)\""
call $print
drop
ref.null none
+102
View File
@@ -0,0 +1,102 @@
// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:expect/expect.dart';
import '' deferred as D;
void main() async {
await D.loadLibrary();
final list = D.getObjects();
final a = list[int.parse('0')];
final b = list[int.parse('1')];
final c = list[int.parse('2')];
final d = list[int.parse('3')];
// Successfull dynamic method/getter/setter call
Expect.equals(52, a.foo(10));
Expect.equals(42, a.fooGetter);
a.fooSetter = 100;
Expect.equals(100, a.fooGetter);
// Successfull dynamic tearoff + dynamic closure call.
final fooTearOff = a.foo;
Expect.equals(110, fooTearOff(10));
// Successful call via field.
Expect.equals(20, b.foo(10));
// Argument type check errors
Expect.throws<TypeError>(() => a.foo(''));
Expect.throws<TypeError>(() => a.fooSetter = '');
// User-defined noSuchMethod handler
Expect.equals(1, c.foo(1));
Expect.equals(2, c.bar(1, 2));
Expect.equals(3, c.baz);
c.buz = 2;
Expect.equals(4, D.cCounter);
// Default noSuchMethod handler
Expect.throwsNoSuchMethodError(() => d.foo(10));
Expect.throwsNoSuchMethodError(() => d.fooGetter);
Expect.throwsNoSuchMethodError(() => d.fooSetter = 10);
final e = list[int.parse('4')];
// Optional positional and type args
Expect.equals("$Object 10 1 2", e.foo(10));
Expect.equals("$Object 10 20 2", e.foo(10, 20));
Expect.equals("$Object 10 20 30", e.foo(10, 20, 30));
Expect.equals("$String hi 1 2", e.foo<String>("hi"));
Expect.equals("$String hi 20 30", e.foo<String>("hi", 20, 30));
// Optional named and type args
Expect.equals("$Object $dynamic 10 null 3", e.bar(10));
Expect.equals("$int $String 10 hi 3", e.bar<int, String>(10, y: "hi"));
Expect.equals(
"$int $String 10 hi 40",
e.bar<int, String>(10, y: "hi", z: 40),
);
Expect.equals("$Object $dynamic 10 null 40", e.bar(10, z: 40));
// NoSuchMethod for wrong shape
Expect.throwsNoSuchMethodError(() => e.foo());
Expect.throwsNoSuchMethodError(() => e.foo(1, 2, 3, 4));
Expect.throwsNoSuchMethodError(() => e.bar(10, unknown: 1));
}
List<dynamic> getObjects() => <dynamic>[
A(),
B((int x) => x * 2),
C(),
Object(),
OptionalArgs(),
];
class A {
int _value = 42;
int foo(int x) => x + _value;
int get fooGetter => _value;
set fooSetter(int x) => _value = x;
}
class B {
final Function foo;
B(this.foo);
}
int cCounter = 0;
class C {
@override
noSuchMethod(Invocation i) => ++cCounter;
}
class OptionalArgs {
String foo<T extends Object>(T x, [int y = 1, int z = 2]) => "$T $x $y $z";
String bar<T extends Object, U>(T x, {U? y, int z = 3}) => "$T $U $x $y $z";
}