blob: bcc96f5a1b8c04a9dc3ab799efea37ac9f18eda6 [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';
import 'dispatch_table.dart';
import 'reference_extensions.dart';
import 'translator.dart';
/// 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 function expression and local function.
final Map<Lambda, w.BaseFunction> _lambdas = {};
// Names of exported functions
final Map<Reference, String> _exports = {};
// Selector IDs that are invoked via GDT.
final Set<int> _calledSelectors = {};
// 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 = {};
FunctionCollector(this.translator);
void _collectImportsAndExports() {
for (Library library in translator.libraries) {
library.procedures.forEach(_importOrExport);
library.fields.forEach(_importOrExport);
for (Class cls in library.classes) {
cls.procedures.forEach(_importOrExport);
}
}
}
void _importOrExport(Member member) {
String? importName =
translator.getPragma(member, "wasm:import", member.name.text);
if (importName != null) {
int dot = importName.indexOf('.');
if (dot != -1) {
assert(!member.isInstanceMember);
String module = importName.substring(0, dot);
String name = importName.substring(dot + 1);
if (member is Procedure) {
w.FunctionType ftype = _makeFunctionType(
translator, member.reference, null,
isImportOrExport: true);
_functions[member.reference] = translator
.moduleForReference(member.reference)
.functions
.import(module, name, ftype, "$importName (import)");
}
}
}
String? exportName =
translator.getPragma(member, "wasm:export", member.name.text);
if (exportName != null) {
if (member is Procedure) {
_makeFunctionType(translator, member.reference, null,
isImportOrExport: true);
}
_exports[member.reference] = exportName;
}
}
/// If the member with the reference [target] is exported, get the export
/// name.
String? getExportName(Reference target) => _exports[target];
void initialize() {
_collectImportsAndExports();
// Add exports to the module and add exported functions to the
// compilationQueue.
for (var export in _exports.entries) {
Reference target = export.key;
Member node = target.asMember;
if (node is Procedure) {
assert(!node.isInstanceMember);
assert(!node.isGetter);
w.FunctionType ftype =
_makeFunctionType(translator, target, null, isImportOrExport: true);
final module = translator.moduleForReference(target);
w.FunctionBuilder function = module.functions.define(ftype, "$node");
_functions[target] = function;
module.exports.export(export.value, function);
translator.compilationQueue.add(AstCompilationTask(function,
getMemberCodeGenerator(translator, function, target), target));
} else if (node is Field) {
final module = translator.moduleForReference(target);
w.Table? table = translator.getTable(module, node);
if (table != null) {
module.exports.export(export.value, table);
}
}
}
// 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 module = translator.moduleForReference(target);
final function = module.functions.define(
translator.signatureForDirectCall(target), getFunctionName(target));
translator.compilationQueue.add(AstCompilationTask(function,
getMemberCodeGenerator(translator, function, target), target));
return function;
});
}
w.BaseFunction getLambdaFunction(
Lambda lambda, Member enclosingMember, Closures enclosingMemberClosures) {
return _lambdas.putIfAbsent(lambda, () {
translator.compilationQueue.add(CompilationTask(
lambda.function,
getLambdaCodeGenerator(
translator, lambda, enclosingMember, enclosingMemberClosures)));
return lambda.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 _getFunctionType(Reference target) {
final Member member = target.asMember;
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)
.targetSet
.contains(target));
return translator.signatureForDirectCall(target);
}
return member.accept1(_FunctionTypeGenerator(translator), target);
}
String getFunctionName(Reference target) {
if (target.isTearOffReference) {
return "${target.asMember} tear-off";
}
final Member member = target.asMember;
String memberName = member.toString();
if (memberName.endsWith('.')) {
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) {
return '$memberName initializer';
}
if (target.isInitializerReference) {
return 'new $memberName (initializer)';
} else if (target.isConstructorBodyReference) {
return 'new $memberName (constructor body)';
} else if (member is Procedure && member.isFactory) {
return 'new $memberName';
} else {
return memberName;
}
}
void recordSelectorUse(SelectorInfo selector) {
if (_calledSelectors.add(selector.id)) {
for (final (:range, :target) in selector.targetRanges) {
for (int classId = range.start; classId <= range.end; ++classId) {
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 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);
}
}
}
/// 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;
_FunctionTypeGenerator(this.translator);
@override
w.FunctionType visitField(Field node, Reference target) {
if (!node.isInstanceMember) {
// Static field initializer function or implicit getter/setter.
return _makeFunctionType(translator, target, null);
}
assert(!translator.dispatchTable
.selectorForTarget(target)
.targetSet
.contains(target));
final receiverType = target.asMember.enclosingClass!
.getThisType(translator.coreTypes, Nullability.nonNullable);
return _makeFunctionType(
translator, target, translator.translateType(receiverType));
}
@override
w.FunctionType visitProcedure(Procedure node, Reference target) {
assert(!node.isAbstract);
if (!node.isInstanceMember) {
return _makeFunctionType(translator, target, null);
}
assert(!translator.dispatchTable
.selectorForTarget(target)
.targetSet
.contains(target));
final receiverType = target.asMember.enclosingClass!
.getThisType(translator.coreTypes, Nullability.nonNullable);
return _makeFunctionType(
translator, target, translator.translateType(receiverType));
}
@override
w.FunctionType visitConstructor(Constructor node, Reference target) {
// Get this constructor's argument types
List<w.ValueType> arguments = _getInputTypes(
translator, target, null, false, translator.translateType);
// 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, arguments);
}
if (target.isConstructorBodyReference) {
return _getConstructorBodyType(node, arguments);
}
return _getConstructorAllocatorType(node, arguments);
}
w.FunctionType _getConstructorAllocatorType(
Constructor node, List<w.ValueType> arguments) {
return translator.typesBuilder.defineFunction(arguments,
[translator.classInfo[node.enclosingClass]!.nonNullableType.unpacked]);
}
w.FunctionType _getInitializerType(
Constructor node, Reference target, List<w.ValueType> arguments) {
final ClassInfo info = translator.classInfo[node.enclosingClass]!;
assert(translator.constructorClosures.containsKey(node.reference));
Closures closures = translator.constructorClosures[node.reference]!;
List<w.ValueType> superOrRedirectedInitializerArgs = [];
for (Initializer initializer in node.initializers) {
if (initializer is SuperInitializer) {
Supertype? supersupertype = initializer.target.enclosingClass.supertype;
if (supersupertype != null) {
ClassInfo superInfo = info.superInfo!;
w.FunctionType superInitializer = translator
.signatureForDirectCall(initializer.target.initializerReference);
final int numSuperclassFields = superInfo.getClassFieldTypes().length;
final int numSuperContextAndConstructorArgs =
superInitializer.outputs.length - numSuperclassFields;
// get types of super initializer outputs, ignoring the superclass
// fields
superOrRedirectedInitializerArgs = superInitializer.outputs
.sublist(0, numSuperContextAndConstructorArgs);
}
} else if (initializer is RedirectingInitializer) {
Supertype? supersupertype = initializer.target.enclosingClass.supertype;
if (supersupertype != null) {
w.FunctionType redirectedInitializer = translator
.signatureForDirectCall(initializer.target.initializerReference);
final int numClassFields = info.getClassFieldTypes().length;
final int numRedirectedContextAndConstructorArgs =
redirectedInitializer.outputs.length - numClassFields;
// get types of redirecting initializer outputs, ignoring the class
// fields
superOrRedirectedInitializerArgs = redirectedInitializer.outputs
.sublist(0, numRedirectedContextAndConstructorArgs);
}
}
}
// Get this classes's field types
final List<w.ValueType> fieldTypes = info.getClassFieldTypes();
// Add nullable context reference for when the constructor has a non-empty
// context
Context? context = closures.contexts[node];
w.ValueType? contextRef;
if (context != null) {
assert(!context.isEmpty);
contextRef = w.RefType.struct(nullable: true);
}
final List<w.ValueType> outputs = superOrRedirectedInitializerArgs +
arguments.reversed.toList() +
(contextRef != null ? [contextRef] : []) +
fieldTypes;
return translator.typesBuilder.defineFunction(arguments, outputs);
}
w.FunctionType _getConstructorBodyType(
Constructor node, List<w.ValueType> arguments) {
assert(translator.constructorClosures.containsKey(node.reference));
Closures closures = translator.constructorClosures[node.reference]!;
Context? context = closures.contexts[node];
List<w.ValueType> inputs = [
translator.classInfo[node.enclosingClass]!.nonNullableType.unpacked
];
if (context != null) {
assert(!context.isEmpty);
// Nullable context reference for when the constructor has a non-empty
// context
w.ValueType contextRef = w.RefType.struct(nullable: true);
inputs.add(contextRef);
}
inputs += arguments;
for (Initializer initializer in node.initializers) {
if (initializer is SuperInitializer ||
initializer is RedirectingInitializer) {
Constructor target = initializer is SuperInitializer
? initializer.target
: (initializer as RedirectingInitializer).target;
Supertype? supersupertype = target.enclosingClass.supertype;
if (supersupertype != null) {
w.FunctionType superOrRedirectedConstructorBodyType = translator
.signatureForDirectCall(target.constructorBodyReference);
// drop receiver param
inputs += superOrRedirectedConstructorBodyType.inputs.sublist(1);
}
}
}
return translator.typesBuilder.defineFunction(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 {
FunctionNode function = member.function!;
typeParamCount = (member is Constructor
? member.enclosingClass.typeParameters
: 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;
}
w.FunctionType _makeFunctionType(
Translator translator, Reference target, w.ValueType? receiverType,
{bool isImportOrExport = false}) {
Member member = target.asMember;
if (member is Field && !member.isInstanceMember) {
final isGetter = target.isImplicitGetter;
final isSetter = target.isImplicitSetter;
if (isGetter || isSetter) {
final fieldType = translator.translateTypeOfField(member);
if (isGetter) {
return translator.typesBuilder.defineFunction(const [], [fieldType]);
}
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);
final List<w.ValueType> inputs = _getInputTypes(
translator, target, receiverType, isImportOrExport, translateType);
// Setters don't have an output with the exception of static implicit setters.
final bool emptyOutputList =
(member is Field && member.setterReference == target) ||
(member is Procedure && member.isSetter);
bool isVoidType(DartType t) =>
(isImportOrExport && t is VoidType) ||
(t is InterfaceType && t.classNode == translator.wasmVoidClass);
final List<w.ValueType> outputs;
if (emptyOutputList) {
outputs = const [];
} else {
final DartType returnType = translator.typeOfReturnValue(member);
outputs = !isVoidType(returnType) ? [translateType(returnType)] : const [];
}
return translator.typesBuilder.defineFunction(inputs, outputs);
}