blob: 9c23c7317db29f62960e27b795e080e201fcd324 [file] [log] [blame] [edit]
// 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:kernel/ast.dart';
import 'package:wasm_builder/wasm_builder.dart' as w;
import 'class_info.dart';
import 'closures.dart';
import 'code_generator.dart' show CallTarget, CodeGenerator, MacroAssembler;
import 'dispatch_table.dart';
import 'reference_extensions.dart';
import 'translator.dart';
/// Stores forwarders for dynamic gets, sets, and invocations. See [Forwarder]
/// for details. Each module will contain its own forwarders for the names
/// invoked from it.
class DynamicForwarders {
final Translator translator;
final w.ModuleBuilder callingModule;
final Map<Name, CallTarget> _getterForwarderOfName = {};
final Map<Name, CallTarget> _setterForwarderOfName = {};
final Map<CallShape, CallTarget> _methodForwarderOfName = {};
DynamicForwarders(this.translator, this.callingModule);
CallTarget getDynamicGetForwarder(Name name) =>
_getterForwarderOfName[name] ??= _DynamicForwarderCallTarget(translator,
_ForwarderKind.Getter, CallShape(name, 0, 0, []), callingModule);
CallTarget getDynamicSetForwarder(Name name) =>
_setterForwarderOfName[name] ??= _DynamicForwarderCallTarget(translator,
_ForwarderKind.Setter, CallShape(name, 0, 1, []), callingModule);
CallTarget getDynamicInvocationForwarder(CallShape shape) {
// Add Wasm function to the map before generating the forwarder code, to
// allow recursive calls in the "call" forwarder.
var forwarder = _methodForwarderOfName[shape];
if (forwarder == null) {
forwarder = _DynamicForwarderCallTarget(
translator, _ForwarderKind.Method, shape, callingModule);
_methodForwarderOfName[shape] = forwarder;
}
return forwarder;
}
}
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;
final CallShape callShape;
final w.ModuleBuilder callingModule;
_DynamicForwarderCallTarget(
this.translator, this._kind, this.callShape, this.callingModule)
: assert(!translator.isDynamicSubmodule ||
(callShape.name.text == 'call' && _kind == _ForwarderKind.Method)),
super(_functionType(translator, _kind, callShape));
static w.FunctionType _functionType(
Translator translator, _ForwarderKind kind, CallShape shape) {
return switch (kind) {
_ForwarderKind.Getter => translator.typesBuilder.defineFunction([
translator.topType,
], [
translator.topType
]),
_ForwarderKind.Setter => translator.typesBuilder.defineFunction([
translator.topType,
translator.topType,
], [
translator.topType
]),
_ForwarderKind.Method => translator.typesBuilder.defineFunction([
translator.topType,
for (int i = 0; i < shape.typeCount; ++i)
translator.translateType(translator.typeType),
for (int i = 0; i < shape.positionalCount; ++i) translator.topType,
for (int i = 0; i < shape.named.length; ++i) translator.topType,
], [
translator.topType
]),
};
}
@override
String get name => 'Dynamic $_kind forwarder for "$callShape"';
@override
bool get supportsInlining => false;
@override
late final w.BaseFunction function = (() {
final function = callingModule.functions.define(signature, name);
final forwarder =
_DynamicForwarderCodeGenerator(translator, _kind, callShape, function);
translator.compilationQueue.add(CompilationTask(function, forwarder));
return function;
})();
}
/// 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 _DynamicForwarderCodeGenerator extends CodeGenerator {
final Translator translator;
final _ForwarderKind _kind;
final CallShape callerShape;
final w.FunctionBuilder function;
_DynamicForwarderCodeGenerator(
this.translator, this._kind, this.callerShape, this.function);
@override
void generate(w.InstructionsBuilder b, List<w.Local> paramLocals,
w.Label? returnLabel) {
assert(returnLabel == null); // no inlining support atm.
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 selectors =
translator.dispatchTable.dynamicGetterSelectors(callerShape.name);
final ranges = selectors
.expand((selector) => selector
.targets(unchecked: false)
.targetRanges
.map((r) => (range: r.range, value: r.target)))
.toList();
ranges.sort((a, b) => a.range.start.compareTo(b.range.start));
final nullableReceiverLocal = function.locals[0];
final outputs = function.type.outputs;
final b = function.body;
// 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);
createGetterInvocationObject(translator, b, callerShape.name);
translator.callReference(
translator.noSuchMethodErrorThrowWithInvocation.reference, b);
b.unreachable();
b.end(); // nullBlock
b.local_set(receiverLocal);
}
b.local_get(receiverLocal);
b.loadClassId(translator, receiverLocal.type);
b.classIdSearch(ranges, outputs, (Reference target) {
final targetMember = target.asMember;
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(
b, receiverLocal.type, targetFunction.type.inputs.first);
translator.callFunction(targetFunction, b);
// Box return value if needed
translator.convertType(
b, targetFunction.type.outputs.single, outputs.single);
}, () {
generateNoSuchMethodCall(translator, b, () => b.local_get(receiverLocal),
() => createGetterInvocationObject(translator, b, callerShape.name));
});
b.return_();
b.end();
}
void _generateSetterCode(Translator translator) {
final selectors =
translator.dispatchTable.dynamicSetterSelectors(callerShape.name);
final ranges = selectors
.expand((selector) => selector
.targets(unchecked: false)
.targetRanges
.map((r) => (range: r.range, value: r.target)))
.toList();
ranges.sort((a, b) => a.range.start.compareTo(b.range.start));
final nullableReceiverLocal = function.locals[0];
final positionalArgLocal = function.locals[1];
final b = function.body;
// 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);
createSetterInvocationObject(
translator, b, callerShape.name, positionalArgLocal);
translator.callReference(
translator.noSuchMethodErrorThrowWithInvocation.reference, b);
b.unreachable();
b.end(); // nullBlock
b.local_set(receiverLocal);
}
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);
}, () {
generateNoSuchMethodCall(
translator,
b,
() => b.local_get(receiverLocal),
() => createSetterInvocationObject(
translator, b, callerShape.name, positionalArgLocal));
b.drop(); // drop noSuchMethod return value
b.local_get(positionalArgLocal);
});
b.return_();
b.end();
}
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);
translator.callReference(
translator.noSuchMethodErrorThrowWithInvocation.reference, b);
b.unreachable();
b.end(); // nullBlock
b.local_set(receiverLocal);
}
final classIdLocal = b.addLocal(w.NumType.i32);
// Continuation of this block calls `noSuchMethod` on the receiver.
final noSuchMethodBlock = b.block();
final methodSelectors =
translator.dispatchTable.dynamicMethodSelectors(callerShape.name);
for (final selector in methodSelectors) {
// Accumulates all class ID ranges that have the same target.
final Map<Reference, List<Range>> targets = {};
for (final (:range, :target)
in selector.targets(unchecked: false).targetRanges) {
targets.putIfAbsent(target, () => []).add(range);
}
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.
if (!callerShape.matchesTarget(targetFunction)) {
continue;
}
b.local_get(receiverLocal);
b.loadClassId(translator, receiverLocal.type);
b.local_set(classIdLocal);
final classIdNoMatch = b.block();
final classIdMatch = b.block();
for (Range classIdRange in classIdRanges) {
if (classIdRange.length == 1) {
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.length);
b.i32_lt_u();
b.br_if(classIdMatch);
}
}
b.br(classIdNoMatch);
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);
}
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);
b.return_();
b.end(); // classIdNoMatch
}
}
final getterValueLocal = b.addLocal(translator.topType);
void handleGetterSelector(SelectorInfo selector) {
for (final (:range, :target)
in selector.targets(unchecked: false).targetRanges) {
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;
}
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 '_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,
b,
closureLocal,
typeArgsLocal,
positionalArgsLocal,
namedArgsLocal,
noSuchMethodBlock);
if (translator.dynamicModuleSupportEnabled) {
generateDynamicClosureCallViaDynamicEntry(
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();
});
} 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) {
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));
b.end();
}
}
enum _ForwarderKind {
Getter,
Setter,
Method;
@override
String toString() {
return switch (this) {
_ForwarderKind.Getter => "get",
_ForwarderKind.Setter => "set",
_ForwarderKind.Method => "method"
};
}
}
/// Generate code that checks shape and type of the closure.
///
/// [closureLocal] should be a local of type `ref #ClosureBase` containing a
/// closure value.
///
/// * [typeArgsLocal] is a `WasmArray<_Type>`
/// * [posArgsLocal] is a `WasmArray<Object?>`
/// * [namedArgsLocal] is a `WasmArray<Object?>` - (symbol, value) pairs
///
/// Will update `typeArgsLocal` with default type arguments (if needed).
///
/// [noSuchMethodBlock] is used as the `br` target when the shape check fails.
void generateDynamicClosureCallShapeAndTypeCheck(
Translator translator,
w.InstructionsBuilder b,
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);
// Read the `_FunctionType` field
final functionTypeLocal =
b.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
// [functionTypeLocal] already on the stack.
b.local_get(typeArgsLocal);
b.local_get(posArgsLocal);
b.local_get(namedArgsLocal);
translator.callReference(translator.checkClosureShape.reference, b);
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);
translator.callReference(translator.checkClosureType.reference, b);
b.drop();
}
// Type check passed \o/
}
void generateDynamicClosureCallViaDynamicEntry(
Translator translator,
w.InstructionsBuilder b,
w.Local closureLocal,
w.Local typeArgsLocal,
w.Local posArgsLocal,
w.Local namedArgsLocal) {
assert(translator.dynamicModuleSupportEnabled ||
translator.closureLayouter.usesFunctionApplyWithNamedArguments);
// 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,
translator.closureLayouter.vtableDynamicClosureCallEntryIndex!);
b.call_ref(translator.dynamicCallVtableEntryFunctionType);
}
void generateDynamicClosureCallViaPositionalArgs(
Translator translator,
w.InstructionsBuilder b,
w.Local closureLocal,
w.Local typeArgsLocal,
w.Local posArgsLocal) {
assert(!translator.dynamicModuleSupportEnabled &&
!translator.closureLayouter.usesFunctionApplyWithNamedArguments);
final maxTypeCount = translator.closureLayouter.maxTypeArgumentCount();
b.emitDenseTableBranch([translator.topType], maxTypeCount, () {
b.local_get(typeArgsLocal);
b.array_len();
}, (typeCount) {
final maxPositionalCount =
translator.closureLayouter.maxPositionalCountFor(typeCount);
b.emitDenseTableBranch([translator.topType], maxPositionalCount, () {
b.local_get(posArgsLocal);
b.array_len();
}, (posCount) {
final representation = translator.closureLayouter
.getClosureRepresentation(typeCount, posCount, []);
if (representation == null) {
// This is a call combination that the closure layouter determined
// cannot occur in the program.
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 < posCount; ++i) {
b.local_get(posArgsLocal);
b.i32_const(i);
b.array_get(translator.nullableObjectArrayType);
}
final vtable = representation.vtableStruct;
final vtableIndex = representation.fieldIndexForSignature(posCount, []);
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));
}, () {
b.unreachable();
});
}, () {
b.unreachable();
});
}
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,
Name memberName,
) {
translator.constants.instantiateConstant(
b,
translator.symbols.getterSymbolFromName(memberName),
translator.classInfo[translator.symbolClass]!.nonNullableType);
translator.callReference(translator.invocationGetterFactory.reference, b);
}
void createSetterInvocationObject(
Translator translator,
w.InstructionsBuilder b,
Name memberName,
w.Local positionalArgLocal,
) {
translator.constants.instantiateConstant(
b,
translator.symbols.setterSymbolFromName(memberName),
translator.classInfo[translator.symbolClass]!.nonNullableType);
b.local_get(positionalArgLocal);
translator.callReference(translator.invocationSetterFactory.reference, b);
}
void generateNoSuchMethodCall(
Translator translator,
w.InstructionsBuilder b,
void Function() pushReceiver,
void Function() pushInvocationObject,
) {
final SelectorInfo noSuchMethodSelector = translator.dispatchTable
.selectorForTarget(translator.objectNoSuchMethod.reference);
translator.functions.recordSelectorUse(noSuchMethodSelector, false);
final signature = noSuchMethodSelector.signature;
final targetRanges =
noSuchMethodSelector.targets(unchecked: false).targetRanges;
final staticDispatchRanges =
noSuchMethodSelector.targets(unchecked: false).staticDispatchRanges;
// NOTE: Keep this in sync with
// `code_generator.dart:AstCodeGenerator._virtualCall`.
final bool directCall =
targetRanges.length == 1 && staticDispatchRanges.length == 1;
final callPolymorphicDispatcher =
!directCall && staticDispatchRanges.isNotEmpty;
final noSuchMethodParamInfo = noSuchMethodSelector.paramInfo;
final noSuchMethodWasmFunctionType = signature;
pushReceiver();
if (callPolymorphicDispatcher) {
b.loadClassId(translator, translator.topTypeNonNullable);
pushReceiver();
}
pushInvocationObject();
final invocationFactory = translator.functions
.getFunction(translator.invocationGenericMethodFactory.reference);
translator.convertType(
b, invocationFactory.type.outputs[0], 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(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(b, namedParameterValue,
noSuchMethodWasmFunctionType.inputs[wasmArgIdx]);
wasmArgIdx += 1;
}
assert(wasmArgIdx == noSuchMethodWasmFunctionType.inputs.length);
if (directCall) {
translator.callReference(targetRanges[0].target, b);
} else if (callPolymorphicDispatcher) {
b.invoke(translator
.getPolymorphicDispatchersForModule(b.moduleBuilder)
.getPolymorphicDispatcher(noSuchMethodSelector,
useUncheckedEntry: false));
} else {
pushReceiver();
translator.callDispatchTable(b, noSuchMethodSelector,
interfaceTarget: translator.objectNoSuchMethod.reference,
useUncheckedEntry: false);
}
}
class ClassIdRange {
final int start;
final int end; // inclusive
ClassIdRange(this.start, this.end);
}