blob: 50e7b0afb3c72948d7854bf279b2f611534bf412 [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.
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/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 lazyConstants = false;
bool localNullability = false;
bool nameSection = true;
bool nominalTypes = true;
bool parameterNullability = true;
bool polymorphicSpecialization = false;
bool printKernel = false;
bool printWasm = false;
bool runtimeTypes = false;
int? sharedMemoryMaxPages;
bool stringDataSegments = false;
List<int>? watchPoints = null;
bool get useRttGlobals => runtimeTypes && !nominalTypes;
}
typedef CodeGenCallback = void Function(w.Instructions);
/// 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 wasmFuncRefClass;
late final Class wasmEqRefClass;
late final Class wasmDataRefClass;
late final Class wasmFunctionClass;
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 typeErrorClass;
late final Class typeUniverseClass;
late final Procedure wasmFunctionCall;
late final Procedure stackTraceCurrent;
late final Procedure stringEquals;
late final Procedure stringInterpolate;
late final Procedure throwNullCheckError;
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 Map<Class, w.StorageType> builtinTypes;
late final Map<w.ValueType, Class> boxedClasses;
// Other parts of the global compiler state.
late final ClassInfoCollector classInfoCollector;
late final DispatchTable dispatchTable;
late final Globals globals;
late final Constants constants;
late final Types types;
late final FunctionCollector functions;
// Information about the program used and updated by the various phases.
final List<ClassInfo> classes = [];
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 = {};
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<int, w.StructType> functionTypeCache = {};
final Map<w.StructType, int> functionTypeParameterCount = {};
final Map<int, w.DefinedGlobal> functionTypeRtt = {};
final Map<w.BaseFunction, w.DefinedGlobal> functionRefCache = {};
final Map<Procedure, w.DefinedFunction> 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();
classInfoCollector = ClassInfoCollector(this);
dispatchTable = DispatchTable(this);
functions = FunctionCollector(this);
types = Types(this);
Class Function(String) makeLookup(String libraryName) {
Library library =
component.libraries.firstWhere((l) => l.name == libraryName);
return (name) => library.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) lookupTypedData = makeLookup("dart.typed_data");
Class Function(String) lookupWasm = makeLookup("dart.wasm");
wasmTypesBaseClass = lookupWasm("_WasmBase");
wasmArrayBaseClass = lookupWasm("_WasmArray");
wasmAnyRefClass = lookupWasm("WasmAnyRef");
wasmFuncRefClass = lookupWasm("WasmFuncRef");
wasmEqRefClass = lookupWasm("WasmEqRef");
wasmDataRefClass = lookupWasm("WasmDataRef");
wasmFunctionClass = lookupWasm("WasmFunction");
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");
wasmFunctionCall =
wasmFunctionClass.procedures.firstWhere((p) => p.name.text == "call");
stackTraceCurrent =
stackTraceClass.procedures.firstWhere((p) => p.name.text == "current");
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");
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 = component.libraries
.firstWhere((l) => l.name == "dart.core")
.procedures
.firstWhere((p) => p.name.text == "_isSubtype");
objectRuntimeType = lookupCore("Object")
.procedures
.firstWhere((p) => p.name.text == "_runtimeType");
builtinTypes = {
coreTypes.boolClass: w.NumType.i32,
coreTypes.intClass: w.NumType.i64,
coreTypes.doubleClass: w.NumType.f64,
wasmAnyRefClass: const w.RefType.any(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,
};
}
Uint8List translate() {
m = w.Module(watchPoints: options.watchPoints);
voidMarker = w.RefType.def(w.StructType("void"), nullable: true);
classInfoCollector.collect();
functions.collectImportsAndExports();
mainFunction =
libraries.first.procedures.firstWhere((p) => p.name.text == "main");
functions.addExport(mainFunction.reference, "main");
initFunction = m.addFunction(functionType(const [], const []), "#init");
m.startFunction = initFunction;
globals = Globals(this);
constants = Constants(this);
dispatchTable.build();
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 (exportName != null) {
m.exportFunction(exportName, function);
} else if (options.exportAll) {
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) {
CodeGenerator(this, lambda.function, reference)
.generateLambda(lambda, codeGen.closures);
_printFunction(lambda.function, "$canonicalName (closure)");
}
}
dispatchTable.output();
constants.finalize();
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);
}
}
}
if (options.lazyConstants) {
_printFunction(constants.oneByteStringFunction, "makeOneByteString");
_printFunction(constants.twoByteStringFunction, "makeTwoByteString");
}
_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);
}
}
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 = functionType(
[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(true);
}
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: !options.parameterNullability || 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 = this.functionType(inputs, outputs);
return w.RefType.def(wasmType, nullable: type.isPotentiallyNullable);
}
return typeForInfo(
classInfo[type.classNode]!, type.isPotentiallyNullable);
}
if (type is DynamicType) {
return topInfo.nullableType;
}
if (type is NullType) {
return topInfo.nullableType;
}
if (type is NeverType) {
return topInfo.nullableType;
}
if (type is VoidType) {
return voidMarker;
}
if (type is TypeParameterType) {
return translateStorageType(type.isPotentiallyNullable
? type.bound.withDeclaredNullability(type.nullability)
: type.bound);
}
if (type is FutureOrType) {
return topInfo.nullableType;
}
if (type is FunctionType) {
if (type.requiredParameterCount != type.positionalParameters.length ||
type.namedParameters.isNotEmpty) {
throw "Function types with optional parameters not supported: $type";
}
return w.RefType.def(closureStructType(type.requiredParameterCount),
nullable:
!options.parameterNullability || 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, () => arrayType("Array<$name>", elementType: w.FieldType(type)));
}
w.StructType closureStructType(int parameterCount) {
return functionTypeCache.putIfAbsent(parameterCount, () {
ClassInfo info = classInfo[functionClass]!;
w.StructType struct = structType("Function$parameterCount",
fields: info.struct.fields, superType: info.struct);
assert(struct.fields.length == FieldIndex.closureFunction);
struct.fields.add(w.FieldType(
w.RefType.def(closureFunctionType(parameterCount), nullable: false),
mutable: false));
if (options.useRttGlobals) {
functionTypeRtt[parameterCount] =
classInfoCollector.makeRtt(struct, info);
}
functionTypeParameterCount[struct] = parameterCount;
return struct;
});
}
w.FunctionType closureFunctionType(int parameterCount) {
return functionType([
w.RefType.data(),
...List<w.ValueType>.filled(parameterCount, topInfo.nullableType)
], [
topInfo.nullableType
]);
}
int parameterCountForFunctionStruct(w.HeapType heapType) {
return functionTypeParameterCount[heapType]!;
}
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;
});
}
w.DefinedFunction getTearOffFunction(Procedure member) {
return tearOffFunctionCache.putIfAbsent(member, () {
assert(member.kind == ProcedureKind.Method);
FunctionNode functionNode = member.function;
int parameterCount = functionNode.requiredParameterCount;
if (functionNode.positionalParameters.length != parameterCount ||
functionNode.namedParameters.isNotEmpty) {
throw "Not supported: Tear-off with optional parameters"
" at ${member.location}";
}
if (functionNode.typeParameters.isNotEmpty) {
throw "Not supported: Tear-off with type parameters"
" at ${member.location}";
}
w.FunctionType memberSignature = signatureFor(member.reference);
w.FunctionType closureSignature = closureFunctionType(parameterCount);
int signatureOffset = member.isInstanceMember ? 1 : 0;
assert(memberSignature.inputs.length == signatureOffset + parameterCount);
assert(closureSignature.inputs.length == 1 + parameterCount);
w.DefinedFunction function =
m.addFunction(closureSignature, "$member (tear-off)");
w.BaseFunction target = functions.getFunction(member.reference);
w.Instructions b = function.body;
for (int i = 0; i < memberSignature.inputs.length; i++) {
w.Local paramLocal = function.locals[(1 - signatureOffset) + i];
b.local_get(paramLocal);
convertType(function, paramLocal.type, memberSignature.inputs[i]);
}
b.call(target);
convertType(function, outputOrVoid(target.type.outputs),
outputOrVoid(closureSignature.outputs));
b.end();
return function;
});
}
w.ValueType typeForLocal(w.ValueType type) {
return options.localNullability ? type : type.withNullability(true);
}
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) {
if (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.heapType);
} else {
// This only happens in invalid but unreachable code produced by the
// TFA dead-code elimination.
b.comment("Non-nullable void conversion");
b.unreachable();
}
return;
}
}
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);
struct_new(b, info);
} 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();
}
ref_cast(b, info);
}
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_as_func();
ref_cast(b, heapType);
return;
}
ClassInfo? info = classForHeapType[heapType];
if (!(from as w.RefType).heapType.isSubtypeOf(w.HeapType.data)) {
b.ref_as_data();
}
ref_cast(
b,
info ??
(heapType.isSubtypeOf(classInfo[functionClass]!.struct)
? parameterCountForFunctionStruct(heapType)
: heapType));
}
}
}
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));
}
}
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;
}
// Wrappers for type creation to abstract over equi-recursive versus nominal
// typing. The given supertype is ignored when nominal types are disabled,
// and a suitable default is inserted when nominal types are enabled.
w.FunctionType functionType(
Iterable<w.ValueType> inputs, Iterable<w.ValueType> outputs,
{w.HeapType? superType}) {
return m.addFunctionType(inputs, outputs,
superType: options.nominalTypes ? superType ?? w.HeapType.func : null);
}
w.StructType structType(String name,
{Iterable<w.FieldType>? fields, w.HeapType? superType}) {
return m.addStructType(name,
fields: fields,
superType: options.nominalTypes ? superType ?? w.HeapType.data : null);
}
w.ArrayType arrayType(String name,
{w.FieldType? elementType, w.HeapType? superType}) {
return m.addArrayType(name,
elementType: elementType,
superType: options.nominalTypes ? superType ?? w.HeapType.data : null);
}
// Wrappers for object allocation and cast instructions to abstract over
// RTT-based and static versions of the instructions.
// The [type] parameter taken by the methods is either a [ClassInfo] (to use
// the RTT for the class), an [int] (to use the RTT for the closure struct
// corresponding to functions with that number of parameters) or a
// [w.DefType] (to use the canonical RTT for the type).
void struct_new(w.Instructions b, Object type) {
if (options.runtimeTypes) {
final struct = _emitRtt(b, type) as w.StructType;
b.struct_new_with_rtt(struct);
} else {
b.struct_new(_targetType(type) as w.StructType);
}
}
void struct_new_default(w.Instructions b, Object type) {
if (options.runtimeTypes) {
final struct = _emitRtt(b, type) as w.StructType;
b.struct_new_default_with_rtt(struct);
} else {
b.struct_new_default(_targetType(type) as w.StructType);
}
}
void array_new(w.Instructions b, w.ArrayType type) {
if (options.runtimeTypes) {
b.rtt_canon(type);
b.array_new_with_rtt(type);
} else {
b.array_new(type);
}
}
void array_new_default(w.Instructions b, w.ArrayType type) {
if (options.runtimeTypes) {
b.rtt_canon(type);
b.array_new_default_with_rtt(type);
} else {
b.array_new_default(type);
}
}
void array_init(w.Instructions b, w.ArrayType type, int length) {
if (options.runtimeTypes) {
b.rtt_canon(type);
b.array_init(type, length);
} else {
b.array_init_static(type, length);
}
}
void array_init_from_data(
w.Instructions b, w.ArrayType type, w.DataSegment data) {
if (options.runtimeTypes) {
b.rtt_canon(type);
b.array_init_from_data(type, data);
} else {
b.array_init_from_data_static(type, data);
}
}
void ref_test(w.Instructions b, Object type) {
if (options.runtimeTypes) {
_emitRtt(b, type);
b.ref_test();
} else {
b.ref_test_static(_targetType(type));
}
}
void ref_cast(w.Instructions b, Object type) {
if (options.runtimeTypes) {
_emitRtt(b, type);
b.ref_cast();
} else {
b.ref_cast_static(_targetType(type));
}
}
void br_on_cast(w.Instructions b, w.Label label, Object type) {
if (options.runtimeTypes) {
_emitRtt(b, type);
b.br_on_cast(label);
} else {
b.br_on_cast_static(label, _targetType(type));
}
}
void br_on_cast_fail(w.Instructions b, w.Label label, Object type) {
if (options.runtimeTypes) {
_emitRtt(b, type);
b.br_on_cast_fail(label);
} else {
b.br_on_cast_static_fail(label, _targetType(type));
}
}
w.DefType _emitRtt(w.Instructions b, Object type) {
if (type is ClassInfo) {
if (options.nominalTypes) {
b.rtt_canon(type.struct);
} else {
b.global_get(type.rtt);
}
return type.struct;
} else if (type is int) {
int parameterCount = type;
w.StructType struct = closureStructType(parameterCount);
if (options.nominalTypes) {
b.rtt_canon(struct);
} else {
w.DefinedGlobal rtt = functionTypeRtt[parameterCount]!;
b.global_get(rtt);
}
return struct;
} else {
b.rtt_canon(type as w.DefType);
return type;
}
}
w.DefType _targetType(Object type) => type is ClassInfo
? type.struct
: type is int
? closureStructType(type)
: type as w.DefType;
}
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);
}
}