blob: 358796f33c46b28e77b82bee6f461069b2993e84 [file] [log] [blame]
// 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.
// ignore_for_file: non_constant_identifier_names
import 'dart:typed_data';
import 'package:dart2wasm/class_info.dart';
import 'package:dart2wasm/closures.dart';
import 'package:dart2wasm/code_generator.dart';
import 'package:dart2wasm/constants.dart';
import 'package:dart2wasm/dispatch_table.dart';
import 'package:dart2wasm/dynamic_dispatch.dart';
import 'package:dart2wasm/functions.dart';
import 'package:dart2wasm/globals.dart';
import 'package:dart2wasm/param_info.dart';
import 'package:dart2wasm/reference_extensions.dart';
import 'package:dart2wasm/types.dart';
import 'package:kernel/ast.dart';
import 'package:kernel/class_hierarchy.dart'
show ClassHierarchy, ClassHierarchySubtypes, ClosedWorldClassHierarchy;
import 'package:kernel/core_types.dart';
import 'package:kernel/src/printer.dart';
import 'package:kernel/type_environment.dart';
import 'package:vm/metadata/direct_call.dart';
import 'package:wasm_builder/wasm_builder.dart' as w;
/// Options controlling the translation.
class TranslatorOptions {
bool exportAll = false;
bool importSharedMemory = false;
bool inlining = false;
int inliningLimit = 3;
bool nameSection = true;
bool polymorphicSpecialization = false;
bool printKernel = false;
bool printWasm = false;
int? sharedMemoryMaxPages;
bool stringDataSegments = false;
List<int>? watchPoints = null;
}
/// The main entry point for the translation from kernel to Wasm and the hub for
/// all global state in the compiler.
///
/// This class also contains utility methods for types and code generation used
/// throughout the compiler.
class Translator {
// Options for the translation.
final TranslatorOptions options;
// Kernel input and context.
final Component component;
final List<Library> libraries;
final CoreTypes coreTypes;
final TypeEnvironment typeEnvironment;
final ClosedWorldClassHierarchy hierarchy;
late final ClassHierarchySubtypes subtypes;
// Classes and members referenced specifically by the compiler.
late final Class wasmTypesBaseClass;
late final Class wasmArrayBaseClass;
late final Class wasmAnyRefClass;
late final Class wasmExternRefClass;
late final Class wasmFuncRefClass;
late final Class wasmEqRefClass;
late final Class wasmDataRefClass;
late final Class wasmFunctionClass;
late final Class wasmTableClass;
late final Class boxedBoolClass;
late final Class boxedIntClass;
late final Class boxedDoubleClass;
late final Class functionClass;
late final Class listBaseClass;
late final Class fixedLengthListClass;
late final Class growableListClass;
late final Class immutableListClass;
late final Class immutableMapClass;
late final Class immutableSetClass;
late final Class hashFieldBaseClass;
late final Class stringBaseClass;
late final Class oneByteStringClass;
late final Class twoByteStringClass;
late final Class typeClass;
late final Class neverTypeClass;
late final Class dynamicTypeClass;
late final Class voidTypeClass;
late final Class nullTypeClass;
late final Class futureOrTypeClass;
late final Class interfaceTypeClass;
late final Class functionTypeClass;
late final Class genericFunctionTypeClass;
late final Class interfaceTypeParameterTypeClass;
late final Class genericFunctionTypeParameterTypeClass;
late final Class namedParameterClass;
late final Class stackTraceClass;
late final Class ffiCompoundClass;
late final Class ffiPointerClass;
late final Class typedListBaseClass;
late final Class typedListClass;
late final Class typedListViewClass;
late final Class byteDataViewClass;
late final Class unmodifiableByteDataViewClass;
late final Class typeErrorClass;
late final Class typeUniverseClass;
late final Class symbolClass;
late final Class invocationClass;
late final Class noSuchMethodErrorClass;
late final Procedure wasmFunctionCall;
late final Procedure wasmTableCallIndirect;
late final Procedure stackTraceCurrent;
late final Procedure asyncHelper;
late final Procedure awaitHelper;
late final Procedure stringEquals;
late final Procedure stringInterpolate;
late final Procedure throwNullCheckError;
late final Procedure throwThrowNullError;
late final Procedure throwAsCheckError;
late final Procedure throwWasmRefError;
late final Procedure mapFactory;
late final Procedure mapPut;
late final Procedure setFactory;
late final Procedure setAdd;
late final Procedure hashImmutableIndexNullable;
late final Procedure isSubtype;
late final Procedure objectRuntimeType;
late final Procedure typeAsNullable;
late final Procedure objectNoSuchMethod;
late final Procedure invocationGetterFactory;
late final Procedure invocationSetterFactory;
late final Procedure invocationMethodFactory;
late final Procedure invocationGenericMethodFactory;
late final Procedure nullToString;
late final Procedure nullNoSuchMethod;
late final Procedure createNormalizedFutureOrType;
late final Procedure noSuchMethodErrorThrowWithInvocation;
late final Map<Class, w.StorageType> builtinTypes;
late final Map<w.ValueType, Class> boxedClasses;
// Other parts of the global compiler state.
late final ClosureLayouter closureLayouter;
late final ClassInfoCollector classInfoCollector;
late final DispatchTable dispatchTable;
late final Globals globals;
late final Constants constants;
late final Types types;
late final FunctionCollector functions;
late final DynamicDispatcher dynamics;
// Information about the program used and updated by the various phases.
/// [ClassInfo]s of classes in the compilation unit and the [ClassInfo] for
/// the `#Top` struct. Indexed by class ID. Entries added by
/// [ClassInfoCollector].
final List<ClassInfo> classes = [];
/// [ClassInfo]s of classes in the compilation unit. Entries added by
/// [ClassInfoCollector].
final Map<Class, ClassInfo> classInfo = {};
final Map<w.HeapType, ClassInfo> classForHeapType = {};
final Map<Field, int> fieldIndex = {};
final Map<TypeParameter, int> typeParameterIndex = {};
final Map<Reference, ParameterInfo> staticParamInfo = {};
final Map<Field, w.DefinedTable> declaredTables = {};
late Procedure mainFunction;
late final w.Module m;
late final w.DefinedFunction initFunction;
late final w.ValueType voidMarker;
// Lazily create exception tag if used.
late final w.Tag exceptionTag = createExceptionTag();
// Lazily import FFI memory if used.
late final w.Memory ffiMemory = m.importMemory("ffi", "memory",
options.importSharedMemory, 0, options.sharedMemoryMaxPages);
// Caches for when identical source constructs need a common representation.
final Map<w.StorageType, w.ArrayType> arrayTypeCache = {};
final Map<w.BaseFunction, w.DefinedGlobal> functionRefCache = {};
final Map<Procedure, ClosureImplementation> tearOffFunctionCache = {};
ClassInfo get topInfo => classes[0];
ClassInfo get objectInfo => classInfo[coreTypes.objectClass]!;
ClassInfo get stackTraceInfo => classInfo[stackTraceClass]!;
Translator(this.component, this.coreTypes, this.typeEnvironment, this.options)
: libraries = component.libraries,
hierarchy =
ClassHierarchy(component, coreTypes) as ClosedWorldClassHierarchy {
subtypes = hierarchy.computeSubtypesInformation();
closureLayouter = ClosureLayouter(this);
classInfoCollector = ClassInfoCollector(this);
dispatchTable = DispatchTable(this);
functions = FunctionCollector(this);
types = Types(this);
dynamics = DynamicDispatcher(this);
Library lookupLibrary(String importUriString) => component.libraries
.firstWhere((l) => l.importUri == Uri.parse(importUriString));
Class Function(String) makeLookup(String importUriString) {
return (name) => lookupLibrary(importUriString)
.classes
.firstWhere((c) => c.name == name);
}
Class Function(String) lookupCore = makeLookup("dart:core");
Class Function(String) lookupCollection = makeLookup("dart:collection");
Class Function(String) lookupFfi = makeLookup("dart:ffi");
Class Function(String) lookupInternal = makeLookup("dart:_internal");
Class Function(String) lookupTypedData = makeLookup("dart:typed_data");
Class Function(String) lookupWasm = makeLookup("dart:wasm");
wasmTypesBaseClass = lookupWasm("_WasmBase");
wasmArrayBaseClass = lookupWasm("_WasmArray");
wasmAnyRefClass = lookupWasm("WasmAnyRef");
wasmExternRefClass = lookupWasm("WasmExternRef");
wasmFuncRefClass = lookupWasm("WasmFuncRef");
wasmEqRefClass = lookupWasm("WasmEqRef");
wasmDataRefClass = lookupWasm("WasmDataRef");
wasmFunctionClass = lookupWasm("WasmFunction");
wasmTableClass = lookupWasm("WasmTable");
boxedBoolClass = lookupCore("_BoxedBool");
boxedIntClass = lookupCore("_BoxedInt");
boxedDoubleClass = lookupCore("_BoxedDouble");
functionClass = lookupCore("_Function");
fixedLengthListClass = lookupCore("_List");
listBaseClass = lookupCore("_ListBase");
growableListClass = lookupCore("_GrowableList");
immutableListClass = lookupCore("_ImmutableList");
immutableMapClass = lookupCollection("_WasmImmutableLinkedHashMap");
immutableSetClass = lookupCollection("_WasmImmutableLinkedHashSet");
hashFieldBaseClass = lookupCollection("_HashFieldBase");
stringBaseClass = lookupCore("_StringBase");
oneByteStringClass = lookupCore("_OneByteString");
twoByteStringClass = lookupCore("_TwoByteString");
typeClass = lookupCore("_Type");
neverTypeClass = lookupCore("_NeverType");
dynamicTypeClass = lookupCore("_DynamicType");
voidTypeClass = lookupCore("_VoidType");
nullTypeClass = lookupCore("_NullType");
futureOrTypeClass = lookupCore("_FutureOrType");
interfaceTypeClass = lookupCore("_InterfaceType");
functionTypeClass = lookupCore("_FunctionType");
genericFunctionTypeClass = lookupCore("_GenericFunctionType");
interfaceTypeParameterTypeClass = lookupCore("_InterfaceTypeParameterType");
genericFunctionTypeParameterTypeClass =
lookupCore("_GenericFunctionTypeParameterType");
namedParameterClass = lookupCore("_NamedParameter");
stackTraceClass = lookupCore("StackTrace");
typeUniverseClass = lookupCore("_TypeUniverse");
ffiCompoundClass = lookupFfi("_Compound");
ffiPointerClass = lookupFfi("Pointer");
typeErrorClass = lookupCore("_TypeError");
typedListBaseClass = lookupTypedData("_TypedListBase");
typedListClass = lookupTypedData("_TypedList");
typedListViewClass = lookupTypedData("_TypedListView");
byteDataViewClass = lookupTypedData("_ByteDataView");
unmodifiableByteDataViewClass =
lookupTypedData("_UnmodifiableByteDataView");
symbolClass = lookupInternal("Symbol");
wasmFunctionCall =
wasmFunctionClass.procedures.firstWhere((p) => p.name.text == "call");
wasmTableCallIndirect = wasmTableClass.procedures
.firstWhere((p) => p.name.text == "callIndirect");
stackTraceCurrent =
stackTraceClass.procedures.firstWhere((p) => p.name.text == "current");
asyncHelper = lookupLibrary("dart:async")
.procedures
.firstWhere((p) => p.name.text == "_asyncHelper");
awaitHelper = lookupLibrary("dart:async")
.procedures
.firstWhere((p) => p.name.text == "_awaitHelper");
stringEquals =
stringBaseClass.procedures.firstWhere((p) => p.name.text == "==");
stringInterpolate = stringBaseClass.procedures
.firstWhere((p) => p.name.text == "_interpolate");
throwNullCheckError = typeErrorClass.procedures
.firstWhere((p) => p.name.text == "_throwNullCheckError");
throwThrowNullError = typeErrorClass.procedures
.firstWhere((p) => p.name.text == "_throwThrowNullError");
throwAsCheckError = typeErrorClass.procedures
.firstWhere((p) => p.name.text == "_throwAsCheckError");
throwWasmRefError = typeErrorClass.procedures
.firstWhere((p) => p.name.text == "_throwWasmRefError");
mapFactory = lookupCollection("LinkedHashMap").procedures.firstWhere(
(p) => p.kind == ProcedureKind.Factory && p.name.text == "_default");
mapPut = lookupCollection("_CompactLinkedCustomHashMap")
.superclass! // _LinkedHashMapMixin<K, V>
.procedures
.firstWhere((p) => p.name.text == "[]=");
setFactory = lookupCollection("LinkedHashSet").procedures.firstWhere(
(p) => p.kind == ProcedureKind.Factory && p.name.text == "_default");
setAdd = lookupCollection("_CompactLinkedCustomHashSet")
.superclass! // _LinkedHashSetMixin<K, V>
.procedures
.firstWhere((p) => p.name.text == "add");
hashImmutableIndexNullable = lookupCollection("_HashAbstractImmutableBase")
.procedures
.firstWhere((p) => p.name.text == "_indexNullable");
isSubtype = lookupLibrary("dart:core")
.procedures
.firstWhere((p) => p.name.text == "_isSubtype");
objectRuntimeType = lookupCore("Object")
.procedures
.firstWhere((p) => p.name.text == "_runtimeType");
typeAsNullable = lookupCore("_Type")
.procedures
.firstWhere((p) => p.name.text == "asNullable");
objectNoSuchMethod = lookupCore("Object")
.procedures
.firstWhere((p) => p.name.text == "noSuchMethod");
invocationClass = lookupCore('Invocation');
invocationGetterFactory =
invocationClass.procedures.firstWhere((p) => p.name.text == "getter");
invocationSetterFactory =
invocationClass.procedures.firstWhere((p) => p.name.text == "setter");
invocationMethodFactory =
invocationClass.procedures.firstWhere((p) => p.name.text == "method");
invocationGenericMethodFactory = invocationClass.procedures
.firstWhere((p) => p.name.text == "genericMethod");
nullToString = lookupCore("Object")
.procedures
.firstWhere((p) => p.name.text == "_nullToString");
nullNoSuchMethod = lookupCore("Object")
.procedures
.firstWhere((p) => p.name.text == "_nullNoSuchMethod");
createNormalizedFutureOrType = typeUniverseClass.procedures
.firstWhere((p) => p.name.text == "createNormalizedFutureOrType");
builtinTypes = {
coreTypes.boolClass: w.NumType.i32,
coreTypes.intClass: w.NumType.i64,
coreTypes.doubleClass: w.NumType.f64,
wasmAnyRefClass: const w.RefType.any(nullable: false),
wasmExternRefClass: const w.RefType.extern(nullable: false),
wasmFuncRefClass: const w.RefType.func(nullable: false),
wasmEqRefClass: const w.RefType.eq(nullable: false),
wasmDataRefClass: const w.RefType.data(nullable: false),
boxedBoolClass: w.NumType.i32,
boxedIntClass: w.NumType.i64,
boxedDoubleClass: w.NumType.f64,
lookupWasm("WasmI8"): w.PackedType.i8,
lookupWasm("WasmI16"): w.PackedType.i16,
lookupWasm("WasmI32"): w.NumType.i32,
lookupWasm("WasmI64"): w.NumType.i64,
lookupWasm("WasmF32"): w.NumType.f32,
lookupWasm("WasmF64"): w.NumType.f64,
ffiPointerClass: w.NumType.i32,
};
boxedClasses = {
w.NumType.i32: boxedBoolClass,
w.NumType.i64: boxedIntClass,
w.NumType.f64: boxedDoubleClass,
};
noSuchMethodErrorClass = lookupCore("NoSuchMethodError");
noSuchMethodErrorThrowWithInvocation = noSuchMethodErrorClass.procedures
.firstWhere((p) => p.name.text == "_throwWithInvocation");
}
// Finds the `main` method for a given library which is assumed to contain
// `main`, either directly or indirectly.
Procedure _findMainMethod(Library entryLibrary) {
// First check to see if the library itself contains main.
for (final procedure in entryLibrary.procedures) {
if (procedure.name.text == 'main') {
return procedure;
}
}
// In some cases, a main method is defined in another file, and then
// exported. In these cases, we search for the main method in
// [additionalExports].
for (final export in entryLibrary.additionalExports) {
if (export.node is Procedure && export.asProcedure.name.text == 'main') {
return export.asProcedure;
}
}
throw ArgumentError(
'Entry uri ${entryLibrary.fileUri} has no main method.');
}
Uint8List translate() {
m = w.Module(watchPoints: options.watchPoints);
voidMarker = w.RefType.def(w.StructType("void"), nullable: true);
closureLayouter.collect();
classInfoCollector.collect();
functions.collectImportsAndExports();
mainFunction = _findMainMethod(libraries.first);
initFunction =
m.addFunction(m.addFunctionType(const [], const []), "#init");
m.startFunction = initFunction;
globals = Globals(this);
constants = Constants(this);
dispatchTable.build();
m.exportFunction("\$main", generateEntryFunction(mainFunction.reference));
functions.initialize();
while (functions.worklist.isNotEmpty) {
Reference reference = functions.worklist.removeLast();
Member member = reference.asMember;
var function =
functions.getExistingFunction(reference) as w.DefinedFunction;
String canonicalName = "$member";
if (reference.isSetter) {
canonicalName = "$canonicalName=";
} else if (reference.isGetter || reference.isTearOffReference) {
int dot = canonicalName.indexOf('.');
canonicalName = canonicalName.substring(0, dot + 1) +
'=' +
canonicalName.substring(dot + 1);
}
canonicalName = member.enclosingLibrary == libraries.first
? canonicalName
: "${member.enclosingLibrary.importUri} $canonicalName";
String? exportName = functions.exports[reference];
if (options.printKernel || options.printWasm) {
if (exportName != null) {
print("#${function.index}: $canonicalName (exported as $exportName)");
} else {
print("#${function.index}: $canonicalName");
}
print(member.function
?.computeFunctionType(Nullability.nonNullable)
.toStringInternal());
}
if (options.printKernel) {
if (member is Constructor) {
Class cls = member.enclosingClass;
for (Field field in cls.fields) {
if (field.isInstanceMember && field.initializer != null) {
print("${field.name}: ${field.initializer}");
}
}
for (Initializer initializer in member.initializers) {
print(initializer);
}
}
Statement? body = member.function?.body;
if (body != null) {
print(body);
}
if (!options.printWasm) print("");
}
if (options.exportAll && exportName == null) {
m.exportFunction(canonicalName, function);
}
var codeGen = CodeGenerator(this, function, reference);
codeGen.generate();
if (options.printWasm) {
print(function.type);
print(function.body.trace);
}
for (Lambda lambda in codeGen.closures.lambdas.values) {
w.DefinedFunction lambdaFunction =
CodeGenerator(this, lambda.function, reference)
.generateLambda(lambda, codeGen.closures);
_printFunction(lambdaFunction, "$canonicalName (closure)");
}
}
dispatchTable.output();
initFunction.body.end();
for (ConstantInfo info in constants.constantInfo.values) {
w.DefinedFunction? function = info.function;
if (function != null) {
_printFunction(function, info.constant);
} else {
if (options.printWasm) {
print("Global #${info.global.index}: ${info.constant}");
print(info.global.initializer.trace);
}
}
}
_printFunction(initFunction, "init");
return m.encode(emitNameSection: options.nameSection);
}
void _printFunction(w.DefinedFunction function, Object name) {
if (options.printWasm) {
print("#${function.index}: $name");
print(function.body.trace);
}
}
w.DefinedFunction generateEntryFunction(Reference mainReference) {
final ParameterInfo paramInfo = paramInfoFor(mainReference);
assert(paramInfo.typeParamCount == 0);
final w.BaseFunction mainFunction = functions.getFunction(mainReference);
final w.DefinedFunction entry =
m.addFunction(m.addFunctionType(const [], const []), "\$main");
final w.Instructions b = entry.body;
if (paramInfo.positional.isNotEmpty) {
// Supply dummy commandline arguments
constants.instantiateConstant(
entry,
b,
ListConstant(coreTypes.stringNonNullableRawType, const []),
mainFunction.type.inputs[0]);
}
if (paramInfo.positional.length > 1) {
// Supply dummy isolate
constants.instantiateConstant(
entry, b, NullConstant(), mainFunction.type.inputs[1]);
}
for (int i = 2; i < paramInfo.positional.length; i++) {
// Supply default values for positional parameters
constants.instantiateConstant(
entry, b, paramInfo.positional[i]!, mainFunction.type.inputs[i]);
}
for (String name in paramInfo.names) {
// Supply default values for named parameters
constants.instantiateConstant(entry, b, paramInfo.named[name]!,
mainFunction.type.inputs[paramInfo.nameIndex[name]!]);
}
b.call(mainFunction);
convertType(entry, outputOrVoid(mainFunction.type.outputs), voidMarker);
b.end();
return entry;
}
Class classForType(DartType type) {
return type is InterfaceType
? type.classNode
: type is TypeParameterType
? classForType(type.bound)
: coreTypes.objectClass;
}
/// Creates a [Tag] for a void [FunctionType] with two parameters,
/// a [topInfo.nonNullableType] parameter to hold an exception, and a
/// [stackTraceInfo.nonNullableType] to hold a stack trace. This single
/// exception tag is used to throw and catch all Dart exceptions.
w.Tag createExceptionTag() {
w.FunctionType tagType = m.addFunctionType(
[topInfo.nonNullableType, stackTraceInfo.nonNullableType], const []);
w.Tag tag = m.addTag(tagType);
return tag;
}
w.ValueType translateType(DartType type) {
w.StorageType wasmType = translateStorageType(type);
if (wasmType is w.ValueType) return wasmType;
throw "Packed types are only allowed in arrays and fields";
}
bool _hasSuperclass(Class cls, Class superclass) {
while (cls.superclass != null) {
cls = cls.superclass!;
if (cls == superclass) return true;
}
return false;
}
bool isWasmType(Class cls) => _hasSuperclass(cls, wasmTypesBaseClass);
bool isFfiCompound(Class cls) => _hasSuperclass(cls, ffiCompoundClass);
w.StorageType typeForInfo(ClassInfo info, bool nullable,
{bool ensureBoxed = false}) {
Class? cls = info.cls;
if (cls != null) {
w.StorageType? builtin = builtinTypes[cls];
if (builtin != null) {
if (!nullable && (!ensureBoxed || cls == ffiPointerClass)) {
return builtin;
}
if (isWasmType(cls)) {
if (builtin.isPrimitive) throw "Wasm numeric types can't be nullable";
return (builtin as w.RefType).withNullability(nullable);
}
if (cls == ffiPointerClass) throw "FFI types can't be nullable";
Class? boxedClass = boxedClasses[builtin];
if (boxedClass != null) {
info = classInfo[boxedClass]!;
}
} else if (isFfiCompound(cls)) {
if (nullable) throw "FFI types can't be nullable";
return w.NumType.i32;
}
}
return w.RefType.def(info.repr.struct, nullable: nullable);
}
w.StorageType translateStorageType(DartType type) {
if (type is InterfaceType) {
if (type.classNode.superclass == wasmArrayBaseClass) {
DartType elementType = type.typeArguments.single;
return w.RefType.def(arrayTypeForDartType(elementType),
nullable: false);
}
if (type.classNode == wasmFunctionClass) {
DartType functionType = type.typeArguments.single;
if (functionType is! FunctionType) {
throw "The type argument of a WasmFunction must be a function type";
}
if (functionType.typeParameters.isNotEmpty ||
functionType.namedParameters.isNotEmpty ||
functionType.requiredParameterCount !=
functionType.positionalParameters.length) {
throw "A WasmFunction can't have optional/type parameters";
}
List<w.ValueType> inputs = [
for (DartType type in functionType.positionalParameters)
translateType(type)
];
List<w.ValueType> outputs = [
if (functionType.returnType != const VoidType())
translateType(functionType.returnType)
];
w.FunctionType wasmType = m.addFunctionType(inputs, outputs);
return w.RefType.def(wasmType, nullable: type.isPotentiallyNullable);
}
return typeForInfo(
classInfo[type.classNode]!, type.isPotentiallyNullable);
}
if (type is DynamicType || type is VoidType) {
return topInfo.nullableType;
}
// TODO(joshualitt): When we add support to `wasm_builder` for bottom heap
// types, we should return bottom heap type here.
if (type is NullType || type is NeverType) {
return topInfo.nullableType;
}
if (type is TypeParameterType) {
return translateStorageType(type.isPotentiallyNullable
? type.bound.withDeclaredNullability(type.nullability)
: type.bound);
}
if (type is IntersectionType) {
return translateStorageType(type.left);
}
if (type is FutureOrType) {
return topInfo.typeWithNullability(type.isPotentiallyNullable);
}
if (type is FunctionType) {
ClosureRepresentation? representation =
closureLayouter.getClosureRepresentation(
type.typeParameters.length,
type.positionalParameters.length,
type.namedParameters.map((p) => p.name).toList());
return w.RefType.def(
representation != null
? representation.closureStruct
: classInfo[typeClass]!.struct,
nullable: type.isPotentiallyNullable);
}
throw "Unsupported type ${type.runtimeType}";
}
w.ArrayType arrayTypeForDartType(DartType type) {
while (type is TypeParameterType) {
type = type.bound;
}
return wasmArrayType(
translateStorageType(type), type.toText(defaultAstTextStrategy));
}
w.ArrayType wasmArrayType(w.StorageType type, String name) {
return arrayTypeCache.putIfAbsent(type,
() => m.addArrayType("Array<$name>", elementType: w.FieldType(type)));
}
w.DefinedGlobal makeFunctionRef(w.BaseFunction f) {
return functionRefCache.putIfAbsent(f, () {
w.DefinedGlobal global = m.addGlobal(
w.GlobalType(w.RefType.def(f.type, nullable: false), mutable: false));
global.initializer.ref_func(f);
global.initializer.end();
return global;
});
}
ClosureImplementation getTearOffClosure(Procedure member) {
return tearOffFunctionCache.putIfAbsent(member, () {
assert(member.kind == ProcedureKind.Method);
w.BaseFunction target = functions.getFunction(member.reference);
return getClosure(member.function, target, paramInfoFor(member.reference),
"$member tear-off");
});
}
ClosureImplementation getClosure(FunctionNode functionNode,
w.BaseFunction target, ParameterInfo paramInfo, String name) {
// The target function takes an extra initial parameter if it's a function
// expression / local function (which takes a context) or a tear-off of an
// instance method (which takes a receiver).
bool takesContextOrReceiver =
paramInfo.member == null || paramInfo.member!.isInstanceMember;
// Look up the closure representation for the signature.
int typeCount = functionNode.typeParameters.length;
int positionalCount = functionNode.positionalParameters.length;
List<String> names =
functionNode.namedParameters.map((p) => p.name!).toList();
assert(typeCount == paramInfo.typeParamCount);
assert(positionalCount <= paramInfo.positional.length);
assert(names.length <= paramInfo.named.length);
assert(target.type.inputs.length ==
(takesContextOrReceiver ? 1 : 0) +
paramInfo.typeParamCount +
paramInfo.positional.length +
paramInfo.named.length);
ClosureRepresentation representation = closureLayouter
.getClosureRepresentation(typeCount, positionalCount, names)!;
assert(representation.vtableStruct.fields.length ==
representation.vtableBaseIndex +
(1 + positionalCount) +
representation.nameCombinations.length);
List<w.DefinedFunction> functions = [];
bool canBeCalledWith(int posArgCount, List<String> argNames) {
if (posArgCount < functionNode.requiredParameterCount) {
return false;
}
int i = 0, j = 0;
while (i < argNames.length && j < functionNode.namedParameters.length) {
int comp = argNames[i].compareTo(functionNode.namedParameters[j].name!);
if (comp < 0) return false;
if (comp > 0) {
if (functionNode.namedParameters[j++].isRequired) return false;
continue;
}
i++;
j++;
}
if (i < argNames.length) return false;
while (j < functionNode.namedParameters.length) {
if (functionNode.namedParameters[j++].isRequired) return false;
}
return true;
}
w.DefinedFunction makeTrampoline(
w.FunctionType signature, int posArgCount, List<String> argNames) {
w.DefinedFunction function = m.addFunction(signature, name);
w.Instructions b = function.body;
int targetIndex = 0;
if (takesContextOrReceiver) {
w.Local receiver = function.locals[0];
b.local_get(receiver);
convertType(function, receiver.type, target.type.inputs[targetIndex++]);
}
int argIndex = 1;
for (int i = 0; i < typeCount; i++) {
b.local_get(function.locals[argIndex++]);
targetIndex++;
}
for (int i = 0; i < paramInfo.positional.length; i++) {
if (i < posArgCount) {
w.Local arg = function.locals[argIndex++];
b.local_get(arg);
convertType(function, arg.type, target.type.inputs[targetIndex++]);
} else {
constants.instantiateConstant(function, b, paramInfo.positional[i]!,
target.type.inputs[targetIndex++]);
}
}
int argNameIndex = 0;
for (int i = 0; i < paramInfo.names.length; i++) {
String argName = paramInfo.names[i];
if (argNameIndex < argNames.length &&
argNames[argNameIndex] == argName) {
w.Local arg = function.locals[argIndex++];
b.local_get(arg);
convertType(function, arg.type, target.type.inputs[targetIndex++]);
argNameIndex++;
} else {
constants.instantiateConstant(function, b, paramInfo.named[argName]!,
target.type.inputs[targetIndex++]);
}
}
assert(argIndex == signature.inputs.length);
assert(targetIndex == target.type.inputs.length);
assert(argNameIndex == argNames.length);
b.call(target);
convertType(function, outputOrVoid(target.type.outputs),
outputOrVoid(signature.outputs));
b.end();
return function;
}
void fillVtableEntry(
w.Instructions ib, int posArgCount, List<String> argNames) {
int fieldIndex = representation.vtableBaseIndex + functions.length;
assert(fieldIndex ==
representation.fieldIndexForSignature(posArgCount, argNames));
w.FunctionType signature =
(representation.vtableStruct.fields[fieldIndex].type as w.RefType)
.heapType as w.FunctionType;
w.DefinedFunction function = canBeCalledWith(posArgCount, argNames)
? makeTrampoline(signature, posArgCount, argNames)
: globals.getDummyFunction(signature);
functions.add(function);
ib.ref_func(function);
}
w.DefinedGlobal vtable = m.addGlobal(w.GlobalType(
w.RefType.def(representation.vtableStruct, nullable: false),
mutable: false));
w.Instructions ib = vtable.initializer;
if (representation.isGeneric) {
ib.ref_func(representation.instantiationFunction);
}
for (int posArgCount = 0; posArgCount <= positionalCount; posArgCount++) {
fillVtableEntry(ib, posArgCount, const []);
}
for (NameCombination nameCombination in representation.nameCombinations) {
fillVtableEntry(ib, positionalCount, nameCombination.names);
}
ib.struct_new(representation.vtableStruct);
ib.end();
return ClosureImplementation(representation, functions, vtable);
}
w.ValueType outputOrVoid(List<w.ValueType> outputs) {
return outputs.isEmpty ? voidMarker : outputs.single;
}
bool needsConversion(w.ValueType from, w.ValueType to) {
return (from == voidMarker) ^ (to == voidMarker) || !from.isSubtypeOf(to);
}
void convertType(
w.DefinedFunction function, w.ValueType from, w.ValueType to) {
w.Instructions b = function.body;
if (from == voidMarker || to == voidMarker) {
if (from != voidMarker) {
b.drop();
return;
}
if (to != voidMarker) {
assert(to is w.RefType && to.nullable);
// This can happen when a void method has its return type overridden
// to return a value, in which case the selector signature will have a
// non-void return type to encompass all possible return values.
b.ref_null((to as w.RefType).heapType);
return;
}
}
bool fromIsExtern = from.isSubtypeOf(w.RefType.extern(nullable: true));
bool toIsExtern = to.isSubtypeOf(w.RefType.extern(nullable: true));
if (fromIsExtern && !toIsExtern) {
b.extern_internalize();
from = w.RefType.any(nullable: from.nullable);
}
if (!fromIsExtern && toIsExtern) {
to = w.RefType.any(nullable: to.nullable);
}
if (!from.isSubtypeOf(to)) {
if (from is! w.RefType && to is w.RefType) {
// Boxing
ClassInfo info = classInfo[boxedClasses[from]!]!;
assert(info.struct.isSubtypeOf(to.heapType));
w.Local temp = function.addLocal(from);
b.local_set(temp);
b.i32_const(info.classId);
b.local_get(temp);
b.struct_new(info.struct);
} else if (from is w.RefType && to is! w.RefType) {
// Unboxing
ClassInfo info = classInfo[boxedClasses[to]!]!;
if (!from.heapType.isSubtypeOf(info.struct)) {
// Cast to box type
if (!from.heapType.isSubtypeOf(w.HeapType.data)) {
b.ref_as_data();
}
b.ref_cast(info.struct);
}
b.struct_get(info.struct, FieldIndex.boxValue);
} else if (from.withNullability(false).isSubtypeOf(to)) {
// Null check
b.ref_as_non_null();
} else {
// Downcast
if (from.nullable && !to.nullable) {
b.ref_as_non_null();
}
var heapType = (to as w.RefType).heapType;
if (heapType is w.FunctionType) {
b.ref_cast(heapType);
return;
}
w.Label? nullLabel = null;
if (!(from as w.RefType).heapType.isSubtypeOf(w.HeapType.data)) {
if (from.nullable && to.nullable) {
// Nullable cast from above dataref. Since ref.as_data is not
// null-polymorphic, we need to check explicitly for null.
w.Local temp = function.addLocal(from);
b.local_set(temp);
nullLabel = b.block(const [], [to]);
w.Label nonNullLabel =
b.block(const [], [from.withNullability(false)]);
b.local_get(temp);
b.br_on_non_null(nonNullLabel);
b.ref_null(to.heapType);
b.br(nullLabel);
b.end(); // nonNullLabel
}
b.ref_as_data();
}
if (heapType is w.DefType) {
b.ref_cast(heapType);
}
if (nullLabel != null) {
b.end(); // nullLabel
}
}
}
if (!fromIsExtern && toIsExtern) {
b.extern_externalize();
}
}
w.FunctionType signatureFor(Reference target) {
Member member = target.asMember;
if (member.isInstanceMember) {
return dispatchTable.selectorForTarget(target).signature;
} else {
return functions.getFunction(target).type;
}
}
ParameterInfo paramInfoFor(Reference target) {
Member member = target.asMember;
if (member.isInstanceMember) {
return dispatchTable.selectorForTarget(target).paramInfo;
} else {
return staticParamInfo.putIfAbsent(
target, () => ParameterInfo.fromMember(target));
}
}
/// Get the Wasm table declared by [field], or `null` if [field] is not a
/// declaration of a Wasm table.
///
/// This function participates in tree shaking in the sense that if it's
/// never called for a particular table declaration, that table is not added
/// to the output module.
w.DefinedTable? getTable(Field field) {
w.DefinedTable? table = declaredTables[field];
if (table != null) return table;
DartType fieldType = field.type;
if (fieldType is InterfaceType && fieldType.classNode == wasmTableClass) {
w.RefType elementType =
translateType(fieldType.typeArguments.single) as w.RefType;
Expression sizeExp = (field.initializer as ConstructorInvocation)
.arguments
.positional
.single;
if (sizeExp is StaticGet && sizeExp.target is Field) {
sizeExp = (sizeExp.target as Field).initializer!;
}
int size = sizeExp is ConstantExpression
? (sizeExp.constant as IntConstant).value
: (sizeExp as IntLiteral).value;
return declaredTables[field] = m.addTable(elementType, size);
}
return null;
}
Member? singleTarget(TreeNode node) {
DirectCallMetadataRepository metadata =
component.metadata[DirectCallMetadataRepository.repositoryTag]
as DirectCallMetadataRepository;
return metadata.mapping[node]?.target;
}
bool shouldInline(Reference target) {
if (!options.inlining) return false;
Member member = target.asMember;
if (member is Field) return true;
Statement? body = member.function!.body;
return body != null &&
NodeCounter().countNodes(body) <= options.inliningLimit;
}
T? getPragma<T>(Annotatable node, String name, [T? defaultvalue]) {
for (Expression annotation in node.annotations) {
if (annotation is ConstantExpression) {
Constant constant = annotation.constant;
if (constant is InstanceConstant) {
if (constant.classNode == coreTypes.pragmaClass) {
Constant? nameConstant =
constant.fieldValues[coreTypes.pragmaName.fieldReference];
if (nameConstant is StringConstant && nameConstant.value == name) {
Object? value =
constant.fieldValues[coreTypes.pragmaOptions.fieldReference];
if (value is PrimitiveConstant<T>) {
return value.value;
}
return value as T? ?? defaultvalue;
}
}
}
}
}
return null;
}
}
class NodeCounter extends Visitor<void> with VisitorVoidMixin {
int count = 0;
int countNodes(Node node) {
count = 0;
node.accept(this);
return count;
}
@override
void defaultNode(Node node) {
count++;
node.visitChildren(this);
}
}