blob: 9a91ce06ac202de49e7987e5752f2f1d66723f4e [file] [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:kernel/names.dart';
import 'package:wasm_builder/wasm_builder.dart' as w;
import 'closures.dart';
import 'code_generator.dart';
import 'dispatch_table.dart';
import 'dynamic_dispatch_table.dart';
import 'namer.dart';
import 'reference_extensions.dart';
import 'translator.dart';
import 'util.dart' as util;
/// This class is responsible for collecting import and export annotations.
/// It also creates Wasm functions for Dart members and manages the compilation
/// queue used to achieve tree shaking.
class FunctionCollector {
final Translator translator;
// 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.
final Set<int> _calledSelectors = {};
final Set<int> _calledUncheckedSelectors = {};
final Set<CallShape> _calledDynamicSelectors = {};
// Class IDs for classes that are allocated somewhere in the program
final Set<int> _allocatedClasses = {};
// For each class ID, which functions should be added to the compilation queue
// if an allocation of that class is encountered
final Map<int, List<Reference>> _pendingAllocation = {};
final Map<int, List<(CallShape, Reference)>> _pendingAllocationDynamic = {};
FunctionCollector(this.translator);
InteropMemberNamer get interopNamer => translator.interopMemberNamer;
void _collectImportsAndExports() {
for (Library library in translator.libraries) {
library.procedures.forEach(_handleExports);
for (Class cls in library.classes) {
cls.procedures.forEach(_handleExports);
}
}
}
void _handleExports(Procedure member) {
// Register the names of any members that are exported from the program.
final isStrongExport = interopNamer.registerExternalExportName(member);
if (isStrongExport) {
// Ensure any strong exports are enqueued for compilation.
getFunction(member.reference);
}
}
/// If the member with the reference [target] is exported, get the export
/// name.
String? getExportName(Reference target) =>
interopNamer.getExportName(target.asMember);
void initialize() {
_collectImportsAndExports();
// Value classes are always implicitly allocated.
recordClassAllocation(
translator.classInfo[translator.boxedBoolClass]!.classId,
);
recordClassAllocation(
translator.classInfo[translator.boxedIntClass]!.classId,
);
recordClassAllocation(
translator.classInfo[translator.boxedDoubleClass]!.classId,
);
}
w.BaseFunction? getExistingFunction(Reference target) {
return _functions[target];
}
w.BaseFunction getFunction(Reference target) {
return _functions.putIfAbsent(target, () {
final member = target.asMember;
final hasPureAnnotation = util.hasWasmPureFunctionPragma(
translator.coreTypes,
member,
);
// If this function is a `@pragma('wasm:import', '<module>.<name>')` we
// import the function and return it.
if (member.reference == target && member.annotations.isNotEmpty) {
final importName = interopNamer.getImportName(member);
if (importName != null) {
final ftype = _makeFunctionType(
translator,
member.reference,
null,
isImportOrExport: true,
synthesizeNullReturnValue: false,
synthesizeNoReturn: false,
);
return _functions[member.reference] =
translator
.moduleForReference(member.reference)
.functions
.import(
importName.moduleName,
importName.itemName,
ftype,
"$importName (import)",
)
..isPure = hasPureAnnotation;
}
}
final module = translator.moduleForReference(target);
// If this function is exported via
// * `@pragma('wasm:export', '<name>')` or
// * `@pragma('wasm:weak-export', '<name>')`
// we export it under the given `<name>`
String? exportName;
if (member.reference == target) {
exportName = interopNamer.getExportName(member);
assert(exportName == null || member is Procedure && member.isStatic);
}
final w.FunctionType ftype = exportName != null
? _makeFunctionType(
translator,
target,
null,
isImportOrExport: true,
synthesizeNullReturnValue: false,
synthesizeNoReturn: false,
)
: translator.signatureForDirectCall(target);
final function = module.functions.define(ftype, getFunctionName(target))
..isPure = hasPureAnnotation && !target.isCheckedEntryReference;
if (exportName != null) {
// Add weak exports to the module as we now know they're used. Strong
// exports have already been added.
module.exports.export(exportName, function);
}
translator.compilationQueue.add(
AstCompilationTask(
function,
getMemberCodeGenerator(translator, function, target),
target,
),
);
return function;
});
}
bool hasDynamicSelectorCall(CallShape shape) =>
_calledDynamicSelectors.contains(shape);
w.BaseFunction? getExistingDynamicForwarder(
Reference target,
CallShape shape,
) {
return _dynamicForwarderFunctions[target]?[shape];
}
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(MethodCallShape shape) {
return _invocationCreatorStubs.putIfAbsent(shape, () {
final module = 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 getLambdaFunction(Lambda lambda) {
return _lambdas.putIfAbsent(lambda, () {
final module = translator.moduleForReference(
lambda.enclosingMember.reference,
);
final function = module.functions.define(
getLambdaFunctionType(lambda),
getLambdaFunctionName(lambda),
);
translator.compilationQueue.add(
CompilationTask(function, getLambdaCodeGenerator(translator, lambda)),
);
return function;
});
}
w.FunctionType getFunctionType(Reference target) {
// We first try to get the function type by seeing if we already
// compiled the [target] function.
//
// We do that because [target] may refer to a imported/exported function
// which get their function type translated differently (it would be
// incorrect to use [_getFunctionType]).
final existingFunction = getExistingFunction(target);
if (existingFunction != null) return existingFunction.type;
return _getFunctionType(target);
}
w.FunctionType getLambdaFunctionType(Lambda lambda) {
final node = lambda.functionNode;
final inputs = <w.ValueType>[
closureContextFieldType,
...List.filled(
node.typeParameters.length,
translator.types.nonNullableTypeType,
),
for (final param in node.positionalParameters)
translator.translateType(param.type),
for (final param in node.namedParameters)
translator.translateType(param.type),
];
final outputs = [translator.translateType(node.returnType)];
return translator.typesBuilder.defineFunction(inputs, outputs);
}
w.FunctionType _getFunctionType(Reference target) {
final Member member = target.asMember;
final synthesizeNullReturnValue = this.synthesizeNullReturnValue(target);
final synthesizeNoReturn = this.synthesizeNoReturn(target);
if (target.isBodyReference) {
// This is the function body that is always called directly (never via
// dispatch table) and with checked arguments. That means we can make a
// precise function type signature based on that member's argument types.
return makeFunctionTypeForBody(
translator,
member,
synthesizeNullReturnValue,
synthesizeNoReturn,
);
}
return member.accept1(
_FunctionTypeGenerator(
translator,
synthesizeNullReturnValue,
synthesizeNoReturn,
),
target,
);
}
bool synthesizeNullReturnValue(Reference target) {
final member = target.asMember;
if (target.isSetter) return true;
if (member.name == indexSetName) return true;
final returnType = translator.typeOfReturnValue(member);
final wasmType = translator.translateReturnType(returnType);
if (wasmType case w.RefType(heapType: w.HeapType.none, nullable: true)) {
return true;
}
return false;
}
bool synthesizeNoReturn(Reference target) {
final member = target.asMember;
if (member is! Procedure) return false;
final returnType = translator.typeOfReturnValue(member);
final wasmType = translator.translateReturnType(returnType);
if (wasmType case w.RefType(heapType: w.HeapType.none, nullable: false)) {
return true;
}
return false;
}
String getFunctionName(Reference target) {
final Member member = target.asMember;
String memberName = member.toString();
if (target.isTearOffReference) {
return "$memberName tear-off";
}
if (target.isCheckedEntryReference) {
return "$memberName (checked entry)";
}
if (target.isUncheckedEntryReference) {
return "$memberName (unchecked entry)";
}
final noInline = translator.getPragma<bool>(
member,
"wasm:never-inline",
true,
);
// We add "<noInline>" to the function name. When we invoke `wasm-opt` we
// then pass the `--no-inline=*<noInline>*` flag, which will prevent
// binaryen from inlining those functions.
//
// => Effectively we make `@pragma('wasm:never-inline')` work for binaryen
// as well.
final inlinePostfix = noInline == true ? ' <noInline>' : '';
if (target.isBodyReference) {
return "$memberName (body)$inlinePostfix";
}
if (memberName.endsWith('.')) {
memberName = memberName.substring(0, memberName.length - 1);
}
if (member is Field) {
if (target.isImplicitSetter) {
return '$memberName= implicit setter';
}
if (target.isStaticFieldInitializer) {
return '$memberName field initializer';
}
return '$memberName implicit getter';
}
if (target.isInitializerReference) {
return 'new $memberName (initializer)';
} else if (target.isConstructorBodyReference) {
return 'new $memberName (constructor body)$inlinePostfix';
} else if (member is Procedure && member.isFactory) {
return 'new $memberName';
} else {
return '$memberName$inlinePostfix';
}
}
String getLambdaFunctionName(Lambda lambda) {
final location = lambda.functionNode.location;
final member = lambda.enclosingMember;
final lambdaNode = lambda.functionNode.parent;
if (lambdaNode is FunctionDeclaration) {
final functionNodeName = lambdaNode.variable.name;
return "$member closure $functionNodeName at $location";
}
assert(lambdaNode is FunctionExpression);
return "$member closure at $location";
}
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;
if (set.add(selector.id)) {
for (final (:range, :target)
in selector.targets(unchecked: useUncheckedEntry).allTargetRanges) {
for (int classId = range.start; classId <= range.end; ++classId) {
recordClassTargetUse(classId, target);
}
}
}
}
void recordClassTargetUse(int classId, Reference target) {
if (_allocatedClasses.contains(classId)) {
// Class declaring or inheriting member is allocated somewhere.
getFunction(target);
} else {
// Remember the member in case an allocation is encountered later.
_pendingAllocation.putIfAbsent(classId, () => []).add(target);
}
}
void recordDynamicSelectorUse(DynamicSelector selector) {
if (_calledDynamicSelectors.add(selector.shape)) {
selector.targets.forEach((classId, target) {
recordClassDynamicTargetUse(classId, selector.shape, target);
});
}
}
void recordClassDynamicTargetUse(
int classId,
CallShape shape,
Reference target,
) {
if (_allocatedClasses.contains(classId)) {
getDynamicForwarder(target, shape);
} else {
_pendingAllocationDynamic.putIfAbsent(classId, () => []).add((
shape,
target,
));
}
}
void recordClassAllocation(int classId) {
if (_allocatedClasses.add(classId)) {
// Schedule all members that were pending allocation of this class.
for (Reference target in _pendingAllocation[classId] ?? const []) {
getFunction(target);
}
for (final (shape, target)
in _pendingAllocationDynamic[classId] ??
const <(CallShape, Reference)>[]) {
getDynamicForwarder(target, shape);
}
}
}
/// Returns an iterable of translated procedures.
Iterable<Procedure> get translatedProcedures =>
_functions.keys.map((k) => k.node).whereType<Procedure>();
}
class _FunctionTypeGenerator extends MemberVisitor1<w.FunctionType, Reference> {
final Translator translator;
final bool synthesizeNullReturnValue;
final bool synthesizeNoReturn;
_FunctionTypeGenerator(
this.translator,
this.synthesizeNullReturnValue,
this.synthesizeNoReturn,
);
@override
w.FunctionType visitField(Field node, Reference target) {
if (!node.isInstanceMember) {
// Static field initializer function or implicit getter/setter.
return _makeFunctionType(
translator,
target,
null,
synthesizeNullReturnValue: synthesizeNullReturnValue,
synthesizeNoReturn: synthesizeNoReturn,
);
}
assert(
!translator.dispatchTable
.selectorForTarget(target)
.containsTarget(target) &&
!translator.dispatchTable
.selectorForTarget(target)
.containsTarget(target),
);
final receiverType = target.asMember.enclosingClass!.getThisType(
translator.coreTypes,
Nullability.nonNullable,
);
return _makeFunctionType(
translator,
target,
translator.translateType(receiverType),
synthesizeNullReturnValue: synthesizeNullReturnValue,
synthesizeNoReturn: synthesizeNoReturn,
);
}
@override
w.FunctionType visitProcedure(Procedure node, Reference target) {
assert(!node.isAbstract);
if (!node.isInstanceMember) {
return _makeFunctionType(
translator,
target,
null,
synthesizeNullReturnValue: synthesizeNullReturnValue,
synthesizeNoReturn: synthesizeNoReturn,
);
}
assert(
!translator.dispatchTable
.selectorForTarget(target)
.containsTarget(target),
);
final receiverType = translator.translateType(
target.asMember.enclosingClass!.getThisType(
translator.coreTypes,
Nullability.nonNullable,
),
);
if (target.isTearOffReference) {
return makeTearOffFunctionType(translator, node.function, receiverType);
}
return _makeFunctionType(
translator,
target,
receiverType,
synthesizeNullReturnValue: synthesizeNullReturnValue,
synthesizeNoReturn: synthesizeNoReturn,
);
}
@override
w.FunctionType visitConstructor(Constructor node, Reference target) {
// We need the contexts of the constructor before generating the initializer
// and constructor body functions, as these functions will return/take a
// context argument if context must be shared between them. Generate the
// contexts the first time we visit a constructor.
translator.constructorClosures[node.reference] ??= translator.getClosures(
node,
);
if (target.isInitializerReference) {
return _getInitializerType(node, target);
}
if (target.isConstructorBodyReference) {
return _getConstructorBodyType(node);
}
return _getConstructorAllocatorType(node);
}
w.FunctionType _getConstructorAllocatorType(Constructor node) {
final constructorInfo = translator.getConstructorInfo(node);
List<w.ValueType> inputs = _getConstructorInputTypes(
translator,
node,
node.enclosingClass.typeParameters,
constructorInfo.allParameters,
translator.translateType,
);
return translator.typesBuilder.defineFunction(inputs, [
translator.classInfo[node.enclosingClass]!.nonNullableType.unpacked,
]);
}
w.FunctionType _getInitializerType(Constructor node, Reference target) {
final info = translator.classInfo[node.enclosingClass]!;
assert(translator.constructorClosures.containsKey(node.reference));
final constructorInfo = translator.getConstructorInfo(node);
final inputs = _getConstructorInputTypes(
translator,
node,
constructorInfo.initializerTypeParameters,
constructorInfo.initializerParameters,
translator.translateType,
);
final outputs = <w.ValueType>[];
final closures = translator.constructorClosures[node.reference]!;
// Redirecting constructors don't have a real body and don't need the
// context in the body.
final isRedirectInitializer =
node.initializers.lastOrNull is RedirectingInitializer;
if (!isRedirectInitializer) {
if (closures.contexts[node] case var context?) {
assert(!context.isEmpty);
outputs.add(const w.RefType.struct(nullable: true));
}
}
outputs.addAll(
_getConstructorInputTypes(
translator,
node,
const [],
constructorInfo.bodyParameters,
translator.translateType,
),
);
for (final initializer in node.initializers) {
if (initializer is SuperInitializer ||
initializer is RedirectingInitializer) {
final target = initializer is SuperInitializer
? initializer.target
: (initializer as RedirectingInitializer).target;
if (target.enclosingClass.supertype != null) {
final targetInfo = translator.classInfo[target.enclosingClass]!;
final targetOutputs = translator
.signatureForDirectCall(target.initializerReference)
.outputs;
outputs.addAll(
targetOutputs.sublist(
0,
targetOutputs.length - targetInfo.getClassFieldTypes().length,
),
);
break;
}
}
}
outputs.addAll(info.getClassFieldTypes());
return translator.typesBuilder.defineFunction(inputs, outputs);
}
w.FunctionType _getConstructorBodyType(Constructor node) {
assert(translator.constructorClosures.containsKey(node.reference));
final inputs = <w.ValueType>[
translator.classInfo[node.enclosingClass]!.nonNullableType.unpacked,
];
final closures = translator.constructorClosures[node.reference]!;
// Redirecting constructors don't have a real body and don't need the
// context in the body.
final isRedirectInitializer =
node.initializers.lastOrNull is RedirectingInitializer;
if (!isRedirectInitializer) {
if (closures.contexts[node] case var context?) {
assert(!context.isEmpty);
inputs.add(w.RefType.struct(nullable: true));
}
}
final constructorInfo = translator.getConstructorInfo(node);
inputs.addAll(
_getConstructorInputTypes(
translator,
node,
const [],
constructorInfo.bodyParameters,
translator.translateType,
),
);
for (final initializer in node.initializers) {
if (initializer is SuperInitializer ||
initializer is RedirectingInitializer) {
final target = initializer is SuperInitializer
? initializer.target
: (initializer as RedirectingInitializer).target;
if (target.enclosingClass.supertype != null) {
final targetBodyType = translator.signatureForDirectCall(
target.constructorBodyReference,
);
// drop receiver param
inputs.addAll(targetBodyType.inputs.sublist(1));
}
}
}
return translator.typesBuilder.defineFunction(inputs, []);
}
}
List<w.ValueType> _getConstructorInputTypes(
Translator translator,
Constructor member,
List<TypeParameter> typeParameters,
List<Variable> parameters,
w.ValueType Function(DartType) translateType,
) {
final List<w.ValueType> inputs = [];
final List<w.ValueType> wasmTypeParameters = List.filled(
typeParameters.length,
translateType(InterfaceType(translator.typeClass, Nullability.nonNullable)),
);
inputs.addAll(wasmTypeParameters);
final List<DartType> params = parameters.map((p) {
final function = p.parent as FunctionNode;
final positionalIndex = function.positionalParameters.indexOf(p);
final isRequired = positionalIndex != -1
? positionalIndex < function.requiredParameterCount
: p.isRequired;
return translator.typeOfParameterVariable(p, isRequired);
}).toList();
inputs.addAll(params.map(translateType));
return inputs;
}
List<w.ValueType> _getInputTypes(
Translator translator,
Reference target,
w.ValueType? receiverType,
bool isImportOrExport,
w.ValueType Function(DartType) translateType,
) {
Member member = target.asMember;
int typeParamCount = 0;
Iterable<DartType> params;
if (member is Field) {
params = [if (target.isImplicitSetter) member.setterType];
} else {
assert(member is Procedure);
FunctionNode function = member.function!;
typeParamCount = function.typeParameters.length;
List<String> names = [for (var p in function.namedParameters) p.name!]
..sort();
final typeForParam = translator.typeOfParameterVariable;
Map<String, DartType> nameTypes = {
for (var p in function.namedParameters)
p.name!: typeForParam(p, p.isRequired),
};
final positionals = function.positionalParameters;
params = [
for (int i = 0; i < positionals.length; ++i)
typeForParam(positionals[i], i < function.requiredParameterCount),
for (String name in names) nameTypes[name]!,
];
}
final List<w.ValueType> typeParameters = List.filled(
typeParamCount,
translateType(InterfaceType(translator.typeClass, Nullability.nonNullable)),
);
final List<w.ValueType> inputs = [];
if (receiverType != null) {
assert(!isImportOrExport);
inputs.add(receiverType);
}
inputs.addAll(typeParameters);
inputs.addAll(params.map(translateType));
return inputs;
}
// Functions that get checked & unchecked variants will run the actual body by
// calling a body function. This builds the signature of such body functions.
//
// Implicit setters also support checked/unchecked entries, but those will not
// call a shared body but have such body (which is trivial) in the checked &
// unchecked functions directly.
w.FunctionType makeFunctionTypeForBody(
Translator translator,
Member member,
bool synthesizeNullReturnValue,
bool synthesizeNoReturn,
) {
assert(member.isInstanceMember);
assert(member is Procedure);
final function = member.function!;
final receiverType = member.enclosingClass!.getThisType(
translator.coreTypes,
Nullability.nonNullable,
);
final inputs = <w.ValueType>[
translator.translateType(receiverType),
for (final _ in function.typeParameters)
translator.translateType(translator.types.typeType),
for (final p in function.positionalParameters)
translator.translateType(translator.typeOfCheckedParameterVariable(p)),
for (final p in function.namedParameters)
translator.translateType(translator.typeOfCheckedParameterVariable(p)),
];
final hasNoReturnValue = synthesizeNullReturnValue || synthesizeNoReturn;
final outputs = [
if (!hasNoReturnValue)
translator.translateReturnType(translator.typeOfReturnValue(member)),
];
return translator.typesBuilder.defineFunction(inputs, outputs);
}
w.FunctionType makeDynamicDispatcherSignature(
Translator translator,
CallShape shape,
) => _makeDynamicSignature(translator, shape, true);
w.FunctionType makeDynamicForwarderSignature(
Translator translator,
CallShape shape,
) => _makeDynamicSignature(translator, shape, false);
w.FunctionType _makeDynamicSignature(
Translator translator,
CallShape shape,
bool nullableReceiver,
) {
switch (shape) {
case GetterCallShape():
return translator.typesBuilder.defineFunction(
[nullableReceiver ? translator.topType : translator.topTypeNonNullable],
[translator.topType],
);
case SetterCallShape():
return translator.typesBuilder.defineFunction([
nullableReceiver ? translator.topType : translator.topTypeNonNullable,
translator.topType,
], []);
case MethodCallShape():
return translator.typesBuilder.defineFunction([
nullableReceiver ? translator.topType : 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,
], shape.isIndexSet ? [] : [translator.topType]);
}
}
w.FunctionType makeTearOffFunctionType(
Translator translator,
FunctionNode function,
w.ValueType? receiverType,
) {
return translator.typesBuilder.defineFunction(
[?receiverType],
[
translator.translateType(
function.computeFunctionType(Nullability.nonNullable),
),
],
);
}
w.FunctionType makeInvocationCreatorSignature(
Translator translator,
MethodCallShape 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, {
required bool synthesizeNullReturnValue,
required bool synthesizeNoReturn,
bool isImportOrExport = false,
}) {
Member member = target.asMember;
if (member is Field && !member.isInstanceMember) {
final fieldType = translator.translateTypeOfField(member);
if (target.isImplicitGetter) {
return translator.typesBuilder.defineFunction(
const [],
synthesizeNullReturnValue ? [] : [fieldType],
);
}
if (target.isStaticFieldInitializer) {
return translator.typesBuilder.defineFunction(const [], [fieldType]);
}
assert(target.isImplicitSetter);
return translator.typesBuilder.defineFunction([fieldType], const []);
}
// Translate types differently for imports and exports.
w.ValueType translateType(DartType type) => isImportOrExport
? translator.translateExternalType(type)
: translator.translateType(type);
w.ValueType translateReturnType(DartType type) => isImportOrExport
? translator.translateExternalType(type)
: translator.translateReturnType(type);
final List<w.ValueType> inputs = _getInputTypes(
translator,
target,
receiverType,
isImportOrExport,
translateType,
);
bool isVoidType(DartType t) =>
(isImportOrExport && t is VoidType) ||
(t is InterfaceType && t.classNode == translator.wasmVoidClass);
final List<w.ValueType> outputs;
final hasNoReturnValue = synthesizeNullReturnValue || synthesizeNoReturn;
if (hasNoReturnValue) {
outputs = const [];
} else {
final DartType returnType = translator.typeOfReturnValue(member);
outputs = !isVoidType(returnType)
? [translateReturnType(returnType)]
: const [];
}
return translator.typesBuilder.defineFunction(inputs, outputs);
}
sealed class CallShape {
final Name name;
CallShape(this.name);
bool get isGetter;
bool get isSetter;
bool get isMethod;
}
final class MethodCallShape extends CallShape {
final int typeCount;
final int positionalCount;
final List<String> named;
MethodCallShape(super.name, this.typeCount, this.positionalCount, this.named);
bool get isIndexSet => name == indexSetName;
@override
bool get isGetter => false;
@override
bool get isSetter => false;
@override
bool get isMethod => true;
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;
}
MethodCallShape copyWithName(Name newName) =>
MethodCallShape(newName, typeCount, positionalCount, named);
@override
int get hashCode =>
Object.hash(name, typeCount, positionalCount, Object.hashAll(named));
@override
bool operator ==(other) {
if (other is! MethodCallShape) return false;
if (name != other.name) return false;
if (typeCount != other.typeCount) return false;
if (positionalCount != other.positionalCount) 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 'MethodCallShape($sb)';
}
}
final class GetterCallShape extends CallShape {
GetterCallShape(super.name);
@override
bool get isGetter => true;
@override
bool get isSetter => false;
@override
bool get isMethod => false;
@override
int get hashCode => name.hashCode;
@override
bool operator ==(other) {
if (other is! GetterCallShape) return false;
if (name != other.name) return false;
return true;
}
@override
String toString() {
return 'GetterCallShape($name)';
}
}
final class SetterCallShape extends CallShape {
SetterCallShape(super.name);
@override
bool get isGetter => false;
@override
bool get isSetter => true;
@override
bool get isMethod => false;
@override
int get hashCode => name.hashCode;
@override
bool operator ==(other) {
if (other is! SetterCallShape) return false;
if (name != other.name) return false;
return true;
}
@override
String toString() {
return 'SetterCallShape($name)';
}
}