mirror of
https://github.com/dart-lang/sdk
synced 2024-09-15 21:20:36 +00:00
5c06035575
The compiler would perform O(n) operation (where `n` is number of classes that implement a selector) every time a GDT call is made. Change-Id: I428a69faa0f5d83e11b3948fd7e009f49c1acba1 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/348400 Reviewed-by: Slava Egorov <vegorov@google.com> Commit-Queue: Martin Kustermann <kustermann@google.com>
873 lines
29 KiB
Dart
873 lines
29 KiB
Dart
// Copyright (c) 2022, 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:dart2wasm/class_info.dart';
|
|
import 'package:dart2wasm/dispatch_table.dart';
|
|
import 'package:dart2wasm/reference_extensions.dart';
|
|
import 'package:dart2wasm/translator.dart';
|
|
|
|
import 'package:kernel/ast.dart';
|
|
|
|
import 'package:wasm_builder/wasm_builder.dart' as w;
|
|
|
|
/// Stores forwarders for dynamic gets, sets, and invocations. See [Forwarder]
|
|
/// for details.
|
|
class DynamicForwarders {
|
|
final Translator translator;
|
|
|
|
final Map<String, Forwarder> _getterForwarderOfName = {};
|
|
final Map<String, Forwarder> _setterForwarderOfName = {};
|
|
final Map<String, Forwarder> _methodForwarderOfName = {};
|
|
|
|
DynamicForwarders(this.translator);
|
|
|
|
Forwarder getDynamicGetForwarder(String memberName) =>
|
|
_getterForwarderOfName[memberName] ??=
|
|
Forwarder(translator, _ForwarderKind.Getter, memberName)
|
|
.._generateCode(translator);
|
|
|
|
Forwarder getDynamicSetForwarder(String memberName) =>
|
|
_setterForwarderOfName[memberName] ??=
|
|
Forwarder(translator, _ForwarderKind.Setter, memberName)
|
|
.._generateCode(translator);
|
|
|
|
Forwarder getDynamicInvocationForwarder(String memberName) {
|
|
// Add Wasm function to the map before generating the forwarder code, to
|
|
// allow recursive calls in the "call" forwarder.
|
|
var forwarder = _methodForwarderOfName[memberName];
|
|
if (forwarder == null) {
|
|
forwarder = Forwarder(translator, _ForwarderKind.Method, memberName);
|
|
_methodForwarderOfName[memberName] = forwarder;
|
|
forwarder._generateCode(translator);
|
|
}
|
|
return forwarder;
|
|
}
|
|
}
|
|
|
|
/// A function that "forwards" a dynamic get, set, or invocation to the right
|
|
/// type checking member.
|
|
///
|
|
/// A forwarder function takes 4 arguments:
|
|
///
|
|
/// - The receiver of the dynamic get, set, or invocation
|
|
/// - A Dart list for type arguments (empty in gets and sets)
|
|
/// - A Dart list of positional arguments (empty in gets)
|
|
/// - A Dart list of named arguments (empty in gets and sets)
|
|
///
|
|
/// It compares the receiver class ID with the IDs of classes with a matching
|
|
/// member name ([memberName]). When it finds a match, it compares the passed
|
|
/// arguments with expected parameters, adjusts parameter lists with default
|
|
/// values, and calls the matching member's type checker method, which type
|
|
/// checks the passed arguments before calling the actual member.
|
|
///
|
|
/// A forwarder calls `noSuchMethod` on the receiver when a matching member is
|
|
/// not found, or the passed arguments do not match the expected parameters of
|
|
/// the member.
|
|
class Forwarder {
|
|
final _ForwarderKind kind;
|
|
|
|
final String memberName;
|
|
|
|
final w.FunctionBuilder function;
|
|
|
|
Forwarder(Translator translator, this.kind, this.memberName)
|
|
: function = translator.m.functions.define(
|
|
kind.functionType(translator), "$kind forwarder for '$memberName'");
|
|
|
|
void _generateCode(Translator translator) {
|
|
switch (kind) {
|
|
case _ForwarderKind.Getter:
|
|
_generateGetterCode(translator);
|
|
break;
|
|
|
|
case _ForwarderKind.Setter:
|
|
_generateSetterCode(translator);
|
|
break;
|
|
|
|
case _ForwarderKind.Method:
|
|
_generateMethodCode(translator);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void _generateGetterCode(Translator translator) {
|
|
final b = function.body;
|
|
|
|
final receiverLocal = function.locals[0];
|
|
|
|
final selectors =
|
|
translator.dispatchTable.dynamicGetterSelectors(memberName);
|
|
for (final selector in selectors) {
|
|
for (int classID in selector.classIds) {
|
|
final Reference target = selector.targets[classID]!;
|
|
final targetMember = target.asMember;
|
|
if (targetMember.isAbstract) {
|
|
continue;
|
|
}
|
|
final targetClass = targetMember.enclosingClass!;
|
|
final targetClassInfo = translator.classInfo[targetClass]!;
|
|
|
|
b.local_get(receiverLocal);
|
|
b.struct_get(translator.topInfo.struct, FieldIndex.classId);
|
|
b.i32_const(classID);
|
|
b.i32_eq();
|
|
b.if_();
|
|
|
|
final w.ValueType receiverType = targetClassInfo.nonNullableType;
|
|
final Reference targetReference;
|
|
if (targetMember is Procedure) {
|
|
targetReference = targetMember.isGetter
|
|
? targetMember.reference
|
|
: targetMember.tearOffReference;
|
|
} else if (targetMember is Field) {
|
|
targetReference = targetMember.getterReference;
|
|
} else {
|
|
throw '_generateGetterCode: member is not a procedure or field: $targetMember';
|
|
}
|
|
|
|
final w.BaseFunction targetFunction =
|
|
translator.functions.getFunction(targetReference);
|
|
b.local_get(receiverLocal);
|
|
translator.convertType(function, receiverLocal.type, receiverType);
|
|
b.call(targetFunction);
|
|
// Box return value if needed
|
|
translator.convertType(function, targetFunction.type.outputs.single,
|
|
kind.functionType(translator).outputs.single);
|
|
b.return_();
|
|
|
|
b.end();
|
|
}
|
|
}
|
|
|
|
generateNoSuchMethodCall(
|
|
translator,
|
|
function,
|
|
() => b.local_get(receiverLocal),
|
|
() => createGetterInvocationObject(translator, function, memberName));
|
|
|
|
b.end();
|
|
}
|
|
|
|
void _generateSetterCode(Translator translator) {
|
|
final b = function.body;
|
|
|
|
final receiverLocal = function.locals[0];
|
|
final positionalArgLocal = function.locals[1];
|
|
|
|
final selectors =
|
|
translator.dispatchTable.dynamicSetterSelectors(memberName);
|
|
for (final selector in selectors) {
|
|
for (int classID in selector.classIds) {
|
|
final Reference target = selector.targets[classID]!;
|
|
final Member targetMember = target.asMember;
|
|
if (targetMember.isAbstract) {
|
|
continue;
|
|
}
|
|
|
|
b.local_get(receiverLocal);
|
|
b.struct_get(translator.topInfo.struct, FieldIndex.classId);
|
|
b.i32_const(classID);
|
|
b.i32_eq();
|
|
b.if_();
|
|
|
|
b.local_get(receiverLocal);
|
|
b.local_get(positionalArgLocal);
|
|
b.call(translator.functions
|
|
.getFunction(targetMember.typeCheckerReference));
|
|
b.return_();
|
|
|
|
b.end();
|
|
}
|
|
}
|
|
|
|
generateNoSuchMethodCall(
|
|
translator,
|
|
function,
|
|
() => b.local_get(receiverLocal),
|
|
() => createSetterInvocationObject(
|
|
translator, function, memberName, positionalArgLocal));
|
|
|
|
b.drop(); // drop noSuchMethod return value
|
|
b.local_get(positionalArgLocal);
|
|
|
|
b.end();
|
|
}
|
|
|
|
void _generateMethodCode(Translator translator) {
|
|
final b = function.body;
|
|
|
|
final receiverLocal = function.locals[0]; // ref #Top
|
|
final typeArgsLocal = function.locals[1]; // ref WasmArray
|
|
final positionalArgsLocal = function.locals[2]; // ref WasmArray
|
|
final namedArgsLocal = function.locals[3]; // ref WasmArray
|
|
|
|
final classIdLocal = function.addLocal(w.NumType.i32);
|
|
|
|
// Continuation of this block calls `noSuchMethod` on the receiver.
|
|
final noSuchMethodBlock = b.block();
|
|
|
|
final numArgsLocal = function.addLocal(w.NumType.i32);
|
|
|
|
final methodSelectors =
|
|
translator.dispatchTable.dynamicMethodSelectors(memberName);
|
|
for (final selector in methodSelectors) {
|
|
// Map methods to classes that inherit them, to avoid generating
|
|
// duplicate blocks when a method is inherited by multiple classes.
|
|
final Map<Reference, List<int>> targets = {};
|
|
for (final classTarget in selector.targets.entries) {
|
|
targets.putIfAbsent(classTarget.value, () => []).add(classTarget.key);
|
|
}
|
|
|
|
for (final targetClasses in targets.entries) {
|
|
final Reference target = targetClasses.key;
|
|
final Procedure targetMember = target.asMember as Procedure;
|
|
final List<int> classIds = targetClasses.value;
|
|
if (targetMember.isAbstract) {
|
|
continue;
|
|
}
|
|
|
|
final targetMemberParamInfo = translator.paramInfoFor(target);
|
|
|
|
b.local_get(receiverLocal);
|
|
b.struct_get(translator.topInfo.struct, FieldIndex.classId);
|
|
b.local_set(classIdLocal);
|
|
|
|
final classIdNoMatch = b.block();
|
|
final classIdMatch = b.block();
|
|
|
|
classIds.sort();
|
|
final List<ClassIdRange> classIdRanges = [];
|
|
int i = 0;
|
|
while (i < classIds.length) {
|
|
final start = classIds[i];
|
|
while ((i < classIds.length - 1) &&
|
|
(classIds[i] + 1 == classIds[i + 1])) {
|
|
i += 1;
|
|
}
|
|
classIdRanges.add(ClassIdRange(start, classIds[i]));
|
|
i += 1;
|
|
}
|
|
|
|
for (ClassIdRange classIdRange in classIdRanges) {
|
|
if (classIdRange.start == classIdRange.end) {
|
|
b.local_get(classIdLocal);
|
|
b.i32_const(classIdRange.start);
|
|
b.i32_eq();
|
|
b.br_if(classIdMatch);
|
|
} else {
|
|
b.local_get(classIdLocal);
|
|
b.i32_const(classIdRange.start);
|
|
b.i32_sub();
|
|
b.i32_const(classIdRange.end - classIdRange.start);
|
|
b.i32_le_u();
|
|
b.br_if(classIdMatch);
|
|
}
|
|
}
|
|
|
|
b.br(classIdNoMatch);
|
|
b.end(); // classIdMatch
|
|
|
|
// Check number of type arguments. It needs to be 0 or match the
|
|
// member's type parameters.
|
|
if (targetMemberParamInfo.typeParamCount == 0) {
|
|
// typeArgs.length == 0
|
|
b.local_get(typeArgsLocal);
|
|
b.array_len();
|
|
b.i32_eqz();
|
|
} else {
|
|
// typeArgs.length == 0 || typeArgs.length == typeParams.length
|
|
b.local_get(typeArgsLocal);
|
|
b.array_len();
|
|
b.local_tee(numArgsLocal);
|
|
b.i32_eqz();
|
|
b.local_get(numArgsLocal);
|
|
b.i32_const(targetMemberParamInfo.typeParamCount);
|
|
b.i32_eq();
|
|
b.i32_or();
|
|
}
|
|
b.i32_eqz();
|
|
b.br_if(noSuchMethodBlock);
|
|
|
|
// Check number of positional parameters and add missing optional
|
|
// arguments
|
|
final nRequired =
|
|
targetMemberParamInfo.positional.where((arg) => arg == null).length;
|
|
final nTotal = targetMemberParamInfo.positional.length;
|
|
|
|
// positionalArgs.length >= nRequired &&
|
|
// positionalArgs.length <= nTotal
|
|
b.local_get(positionalArgsLocal);
|
|
b.array_len();
|
|
b.local_tee(numArgsLocal);
|
|
b.i32_const(nRequired);
|
|
b.i32_ge_u();
|
|
b.local_get(numArgsLocal);
|
|
b.i32_const(nTotal);
|
|
b.i32_le_u();
|
|
b.i32_and();
|
|
b.i32_eqz();
|
|
b.br_if(noSuchMethodBlock);
|
|
|
|
// Add default values of optional positional parameters if needed
|
|
w.Local? adjustedPositionalArgsLocal;
|
|
if (nRequired != nTotal) {
|
|
adjustedPositionalArgsLocal =
|
|
function.addLocal(translator.nullableObjectArrayTypeRef);
|
|
b.i32_const(nTotal);
|
|
b.array_new_default(translator.nullableObjectArrayType);
|
|
b.local_set(adjustedPositionalArgsLocal);
|
|
|
|
// Copy passed arguments
|
|
final argIdxLocal = function.addLocal(w.NumType.i32);
|
|
b.i32_const(0);
|
|
b.local_set(argIdxLocal);
|
|
|
|
final loopBlock = b.loop();
|
|
b.local_get(argIdxLocal);
|
|
b.local_get(numArgsLocal);
|
|
b.i32_lt_u();
|
|
b.if_();
|
|
b.local_get(adjustedPositionalArgsLocal);
|
|
b.local_get(argIdxLocal);
|
|
b.local_get(positionalArgsLocal);
|
|
b.local_get(argIdxLocal);
|
|
b.array_get(translator.nullableObjectArrayType);
|
|
b.array_set(translator.nullableObjectArrayType);
|
|
b.local_get(argIdxLocal);
|
|
b.i32_const(1);
|
|
b.i32_add();
|
|
b.local_set(argIdxLocal);
|
|
b.br(loopBlock);
|
|
b.end(); // end if
|
|
b.end(); // end loop
|
|
|
|
// Add optional parameters
|
|
for (var optionalParamIdx = nRequired;
|
|
optionalParamIdx < nTotal;
|
|
optionalParamIdx += 1) {
|
|
b.local_get(argIdxLocal);
|
|
b.i32_const(optionalParamIdx);
|
|
b.i32_le_u();
|
|
b.if_();
|
|
|
|
final param = targetMemberParamInfo.positional[optionalParamIdx]!;
|
|
|
|
b.local_get(adjustedPositionalArgsLocal);
|
|
b.i32_const(optionalParamIdx);
|
|
translator.constants.instantiateConstant(
|
|
function, b, param, translator.topInfo.nullableType);
|
|
b.array_set(translator.nullableObjectArrayType);
|
|
b.end();
|
|
}
|
|
}
|
|
|
|
// Check named arguments and adjust the named argument list. Named
|
|
// parameters in the member should be in the list or have a default
|
|
// value.
|
|
w.Local? adjustedNamedArgsLocal;
|
|
if (targetMemberParamInfo.named.isEmpty) {
|
|
// namedArgs.length == 0
|
|
b.local_get(namedArgsLocal);
|
|
b.array_len();
|
|
b.i32_eqz();
|
|
b.i32_eqz();
|
|
b.br_if(noSuchMethodBlock);
|
|
} else {
|
|
adjustedNamedArgsLocal =
|
|
function.addLocal(translator.nullableObjectArrayTypeRef);
|
|
b.i32_const(targetMemberParamInfo.named.length);
|
|
b.array_new_default(translator.nullableObjectArrayType);
|
|
b.local_set(adjustedNamedArgsLocal);
|
|
|
|
final namedParameterIdxLocal = function.addLocal(
|
|
translator.classInfo[translator.boxedIntClass]!.nullableType);
|
|
|
|
final remainingNamedArgsLocal = numArgsLocal;
|
|
b.local_get(namedArgsLocal);
|
|
b.array_len();
|
|
b.i32_const(1);
|
|
b.i32_shr_u();
|
|
b.local_set(remainingNamedArgsLocal);
|
|
|
|
final targetMemberFunction = targetMember.function;
|
|
|
|
Expression? initializerForNamedParamInMember(String paramName) {
|
|
for (int i = 0;
|
|
i < targetMemberFunction.namedParameters.length;
|
|
i += 1) {
|
|
if (targetMemberFunction.namedParameters[i].name == paramName) {
|
|
return targetMemberFunction.namedParameters[i].initializer;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
for (int nameIdx = 0;
|
|
nameIdx < targetMemberParamInfo.names.length;
|
|
++nameIdx) {
|
|
final String name = targetMemberParamInfo.names[nameIdx];
|
|
final Constant? paramInfoDefaultValue =
|
|
targetMemberParamInfo.named[name];
|
|
final Expression? functionNodeDefaultValue =
|
|
initializerForNamedParamInMember(name);
|
|
|
|
b.local_get(namedArgsLocal);
|
|
translator.constants.instantiateConstant(
|
|
function,
|
|
b,
|
|
SymbolConstant(name, null),
|
|
translator.classInfo[translator.symbolClass]!.nonNullableType);
|
|
|
|
b.call(translator.functions
|
|
.getFunction(translator.getNamedParameterIndex.reference));
|
|
b.local_tee(namedParameterIdxLocal);
|
|
|
|
b.ref_is_null();
|
|
b.i32_eqz();
|
|
b.if_();
|
|
b.local_get(remainingNamedArgsLocal);
|
|
b.i32_const(1);
|
|
b.i32_sub();
|
|
b.local_set(remainingNamedArgsLocal);
|
|
b.end();
|
|
|
|
b.local_get(namedParameterIdxLocal);
|
|
b.ref_is_null();
|
|
if (functionNodeDefaultValue == null &&
|
|
paramInfoDefaultValue == null) {
|
|
// Required parameter missing
|
|
b.br_if(noSuchMethodBlock);
|
|
|
|
// Copy provided named parameter.
|
|
|
|
b.local_get(adjustedNamedArgsLocal);
|
|
b.i32_const(nameIdx);
|
|
|
|
b.local_get(namedArgsLocal);
|
|
b.local_get(namedParameterIdxLocal);
|
|
translator.convertType(
|
|
function, namedParameterIdxLocal.type, w.NumType.i64);
|
|
b.i32_wrap_i64();
|
|
b.array_get(translator.nullableObjectArrayType);
|
|
|
|
b.array_set(translator.nullableObjectArrayType);
|
|
} else {
|
|
// Optional, either has a default in the member or not used by
|
|
// the member
|
|
b.if_();
|
|
|
|
b.local_get(adjustedNamedArgsLocal);
|
|
b.i32_const(nameIdx);
|
|
|
|
if (functionNodeDefaultValue != null) {
|
|
// Used by the member, has a default value
|
|
translator.constants.instantiateConstant(
|
|
function,
|
|
b,
|
|
(functionNodeDefaultValue as ConstantExpression).constant,
|
|
translator.topInfo.nullableType);
|
|
} else {
|
|
// Not used by the member
|
|
translator.constants.instantiateConstant(
|
|
function,
|
|
b,
|
|
paramInfoDefaultValue!,
|
|
translator.topInfo.nullableType,
|
|
);
|
|
}
|
|
b.array_set(translator.nullableObjectArrayType);
|
|
|
|
b.else_();
|
|
|
|
b.local_get(adjustedNamedArgsLocal);
|
|
b.i32_const(nameIdx);
|
|
b.local_get(namedArgsLocal);
|
|
b.local_get(namedParameterIdxLocal);
|
|
translator.convertType(
|
|
function, namedParameterIdxLocal.type, w.NumType.i64);
|
|
b.i32_wrap_i64();
|
|
b.array_get(translator.nullableObjectArrayType);
|
|
b.array_set(translator.nullableObjectArrayType);
|
|
|
|
b.end();
|
|
}
|
|
}
|
|
|
|
// Check that all named arguments are used. If not, it means that the
|
|
// call site has extra names that the member doesn't have.
|
|
b.local_get(remainingNamedArgsLocal);
|
|
b.i32_eqz();
|
|
b.i32_eqz();
|
|
b.br_if(noSuchMethodBlock);
|
|
}
|
|
|
|
b.local_get(receiverLocal);
|
|
b.local_get(typeArgsLocal);
|
|
b.local_get(adjustedPositionalArgsLocal ?? positionalArgsLocal);
|
|
b.local_get(adjustedNamedArgsLocal ?? namedArgsLocal);
|
|
final wasmFunction =
|
|
translator.functions.getFunction(targetMember.typeCheckerReference);
|
|
b.call(wasmFunction);
|
|
b.return_();
|
|
b.end(); // classIdNoMatch
|
|
}
|
|
}
|
|
|
|
final getterSelectors =
|
|
translator.dispatchTable.dynamicGetterSelectors(memberName);
|
|
final getterValueLocal = function.addLocal(translator.topInfo.nullableType);
|
|
for (final selector in getterSelectors) {
|
|
for (int classID in selector.classIds) {
|
|
final Reference target = selector.targets[classID]!;
|
|
final targetMember = target.asMember;
|
|
if (targetMember.isAbstract) {
|
|
continue;
|
|
}
|
|
// This loop checks getters and fields. Methods are considered in the
|
|
// previous loop, skip them here.
|
|
if (targetMember is Procedure && !targetMember.isGetter) {
|
|
continue;
|
|
}
|
|
final targetClass = targetMember.enclosingClass!;
|
|
final targetClassInfo = translator.classInfo[targetClass]!;
|
|
|
|
b.local_get(receiverLocal);
|
|
b.struct_get(translator.topInfo.struct, FieldIndex.classId);
|
|
b.i32_const(classID);
|
|
b.i32_eq();
|
|
b.if_();
|
|
|
|
final w.ValueType receiverType = targetClassInfo.nonNullableType;
|
|
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(function, receiverLocal.type, receiverType);
|
|
b.call(targetFunction);
|
|
translator.convertType(function, targetFunction.type.outputs.single,
|
|
translator.topInfo.nullableType);
|
|
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.struct_get(translator.topInfo.struct, FieldIndex.classId);
|
|
b.i32_const(translator.closureInfo.classId);
|
|
b.i32_ne();
|
|
b.if_();
|
|
// Value is not a closure
|
|
final callForwarder = translator.dynamicForwarders
|
|
.getDynamicInvocationForwarder("call")
|
|
.function;
|
|
b.local_get(receiverLocal);
|
|
b.local_get(typeArgsLocal);
|
|
b.local_get(positionalArgsLocal);
|
|
b.local_get(namedArgsLocal);
|
|
b.call(callForwarder);
|
|
b.return_();
|
|
b.end();
|
|
|
|
// Cast the closure to `#ClosureBase`
|
|
final closureBaseType = w.RefType.def(
|
|
translator.closureLayouter.closureBaseStruct,
|
|
nullable: false);
|
|
final closureLocal = function.addLocal(closureBaseType);
|
|
b.local_get(receiverLocal);
|
|
b.ref_cast(closureBaseType);
|
|
b.local_set(closureLocal);
|
|
|
|
generateDynamicFunctionCall(
|
|
translator,
|
|
function,
|
|
closureLocal,
|
|
typeArgsLocal,
|
|
positionalArgsLocal,
|
|
namedArgsLocal,
|
|
noSuchMethodBlock);
|
|
b.return_();
|
|
|
|
b.end(); // class ID
|
|
}
|
|
}
|
|
|
|
b.end(); // noSuchMethodBlock
|
|
|
|
// Unable to find a matching member, call `noSuchMethod`
|
|
generateNoSuchMethodCall(
|
|
translator,
|
|
function,
|
|
() => b.local_get(receiverLocal),
|
|
() => createInvocationObject(translator, function, memberName,
|
|
typeArgsLocal, positionalArgsLocal, namedArgsLocal));
|
|
|
|
b.end();
|
|
}
|
|
}
|
|
|
|
enum _ForwarderKind {
|
|
Getter,
|
|
Setter,
|
|
Method;
|
|
|
|
String toString() {
|
|
switch (this) {
|
|
case _ForwarderKind.Getter:
|
|
return "get";
|
|
case _ForwarderKind.Setter:
|
|
return "set";
|
|
case _ForwarderKind.Method:
|
|
return "method";
|
|
}
|
|
}
|
|
|
|
w.FunctionType functionType(Translator translator) {
|
|
switch (this) {
|
|
case _ForwarderKind.Getter:
|
|
return translator.dynamicGetForwarderFunctionType;
|
|
case _ForwarderKind.Setter:
|
|
return translator.dynamicSetForwarderFunctionType;
|
|
case _ForwarderKind.Method:
|
|
return translator.dynamicInvocationForwarderFunctionType;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Generate code that checks shape and type of the closure and generate a call
|
|
/// to its dynamic call vtable entry.
|
|
///
|
|
/// [closureLocal] should be a local of type `ref #ClosureBase` containing a
|
|
/// closure value.
|
|
///
|
|
/// [typeArgsLocal], [posArgsLocal], [namedArgsLocal] are the locals for type,
|
|
/// positional, and named arguments, respectively. Types of these locals must
|
|
/// be `ref _ListBase`.
|
|
///
|
|
/// [noSuchMethodBlock] is used as the `br` target when the shape check fails.
|
|
void generateDynamicFunctionCall(
|
|
Translator translator,
|
|
w.FunctionBuilder function,
|
|
w.Local closureLocal,
|
|
w.Local typeArgsLocal,
|
|
w.Local posArgsLocal,
|
|
w.Local namedArgsLocal,
|
|
w.Label noSuchMethodBlock,
|
|
) {
|
|
assert(typeArgsLocal.type == translator.typeArrayTypeRef);
|
|
assert(posArgsLocal.type == translator.nullableObjectArrayTypeRef);
|
|
assert(namedArgsLocal.type == translator.nullableObjectArrayTypeRef);
|
|
|
|
final b = function.body;
|
|
|
|
// Read the `_FunctionType` field
|
|
final functionTypeLocal =
|
|
function.addLocal(translator.closureLayouter.functionTypeType);
|
|
b.local_get(closureLocal);
|
|
b.struct_get(translator.closureLayouter.closureBaseStruct,
|
|
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);
|
|
b.local_get(namedArgsLocal);
|
|
b.call(
|
|
translator.functions.getFunction(translator.checkClosureShape.reference));
|
|
|
|
b.i32_eqz();
|
|
b.br_if(noSuchMethodBlock);
|
|
|
|
// Shape check passed, check types
|
|
if (!translator.options.omitImplicitTypeChecks) {
|
|
b.local_get(functionTypeLocal);
|
|
b.local_get(typeArgsLocal);
|
|
b.local_get(posArgsLocal);
|
|
b.local_get(namedArgsLocal);
|
|
b.call(translator.functions
|
|
.getFunction(translator.checkClosureType.reference));
|
|
b.drop();
|
|
}
|
|
|
|
// Type check passed, call vtable entry
|
|
b.local_get(closureLocal);
|
|
b.local_get(typeArgsLocal);
|
|
b.local_get(posArgsLocal);
|
|
b.local_get(namedArgsLocal);
|
|
|
|
// Get vtable
|
|
b.local_get(closureLocal);
|
|
b.struct_get(
|
|
translator.closureLayouter.closureBaseStruct, FieldIndex.closureVtable);
|
|
|
|
// Get entry function
|
|
b.struct_get(translator.closureLayouter.vtableBaseStruct, 0);
|
|
|
|
b.call_ref(translator.dynamicCallVtableEntryFunctionType);
|
|
}
|
|
|
|
void createInvocationObject(
|
|
Translator translator,
|
|
w.FunctionBuilder function,
|
|
String memberName,
|
|
w.Local typeArgsLocal,
|
|
w.Local positionalArgsLocal,
|
|
w.Local namedArgsLocal) {
|
|
final b = function.body;
|
|
|
|
translator.constants.instantiateConstant(
|
|
function,
|
|
b,
|
|
SymbolConstant(memberName, null),
|
|
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));
|
|
b.local_get(namedArgsLocal);
|
|
b.call(translator.functions
|
|
.getFunction(translator.namedParametersToMap.reference));
|
|
b.call(translator.functions
|
|
.getFunction(translator.invocationGenericMethodFactory.reference));
|
|
}
|
|
|
|
void createGetterInvocationObject(
|
|
Translator translator,
|
|
w.FunctionBuilder function,
|
|
String memberName,
|
|
) {
|
|
final b = function.body;
|
|
|
|
translator.constants.instantiateConstant(
|
|
function,
|
|
b,
|
|
SymbolConstant(memberName, null),
|
|
translator.classInfo[translator.symbolClass]!.nonNullableType);
|
|
|
|
b.call(translator.functions
|
|
.getFunction(translator.invocationGetterFactory.reference));
|
|
}
|
|
|
|
void createSetterInvocationObject(
|
|
Translator translator,
|
|
w.FunctionBuilder function,
|
|
String memberName,
|
|
w.Local positionalArgLocal,
|
|
) {
|
|
final b = function.body;
|
|
|
|
memberName = '$memberName=';
|
|
|
|
translator.constants.instantiateConstant(
|
|
function,
|
|
b,
|
|
SymbolConstant(memberName, null),
|
|
translator.classInfo[translator.symbolClass]!.nonNullableType);
|
|
|
|
b.local_get(positionalArgLocal);
|
|
b.call(translator.functions
|
|
.getFunction(translator.invocationSetterFactory.reference));
|
|
}
|
|
|
|
void generateNoSuchMethodCall(
|
|
Translator translator,
|
|
w.FunctionBuilder function,
|
|
void Function() pushReceiver,
|
|
void Function() pushInvocationObject,
|
|
) {
|
|
final b = function.body;
|
|
|
|
final SelectorInfo noSuchMethodSelector = translator.dispatchTable
|
|
.selectorForTarget(translator.objectNoSuchMethod.reference);
|
|
translator.functions.recordSelectorUse(noSuchMethodSelector);
|
|
|
|
final noSuchMethodParamInfo = noSuchMethodSelector.paramInfo;
|
|
final noSuchMethodWasmFunctionType = noSuchMethodSelector.signature;
|
|
|
|
pushReceiver();
|
|
pushInvocationObject();
|
|
|
|
final invocationFactory = translator.functions
|
|
.getFunction(translator.invocationGenericMethodFactory.reference);
|
|
translator.convertType(function, invocationFactory.type.outputs[0],
|
|
noSuchMethodSelector.signature.inputs[1]);
|
|
|
|
// `noSuchMethod` can have extra parameters as long as they are optional.
|
|
// Push any optional positional parameters.
|
|
int wasmArgIdx = 2;
|
|
for (int positionalArgIdx = 1;
|
|
positionalArgIdx < noSuchMethodParamInfo.positional.length;
|
|
positionalArgIdx += 1) {
|
|
final positionalParameterValue =
|
|
noSuchMethodParamInfo.positional[positionalArgIdx]!;
|
|
translator.constants.instantiateConstant(
|
|
function,
|
|
b,
|
|
positionalParameterValue,
|
|
noSuchMethodWasmFunctionType.inputs[wasmArgIdx]);
|
|
wasmArgIdx += 1;
|
|
}
|
|
|
|
// Push any optional named parameters
|
|
for (String namedParameterName in noSuchMethodParamInfo.names) {
|
|
final namedParameterValue =
|
|
noSuchMethodParamInfo.named[namedParameterName]!;
|
|
translator.constants.instantiateConstant(function, b, namedParameterValue,
|
|
noSuchMethodWasmFunctionType.inputs[wasmArgIdx]);
|
|
wasmArgIdx += 1;
|
|
}
|
|
|
|
assert(wasmArgIdx == noSuchMethodWasmFunctionType.inputs.length);
|
|
|
|
// Get class id for virtual call
|
|
pushReceiver();
|
|
b.struct_get(translator.topInfo.struct, FieldIndex.classId);
|
|
|
|
// Virtual call to noSuchMethod
|
|
int selectorOffset = noSuchMethodSelector.offset!;
|
|
if (selectorOffset != 0) {
|
|
b.i32_const(selectorOffset);
|
|
b.i32_add();
|
|
}
|
|
|
|
b.call_indirect(noSuchMethodWasmFunctionType);
|
|
}
|
|
|
|
class ClassIdRange {
|
|
final int start;
|
|
final int end; // inclusive
|
|
|
|
ClassIdRange(this.start, this.end);
|
|
}
|