blob: bdb7c948767c5932f1ed227728c136715d0d6f2b [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 'package:dart2wasm/class_info.dart';
import 'package:dart2wasm/code_generator.dart';
import 'package:dart2wasm/dynamic_forwarders.dart';
import 'package:dart2wasm/translator.dart';
import 'package:kernel/ast.dart';
import 'package:wasm_builder/wasm_builder.dart' as w;
import 'abi.dart' show kWasmAbiEnumIndex;
typedef CodeGenCallback = void Function(w.Instructions);
/// Specialized code generation for external members.
///
/// The code is generated either inlined at the call site, or as the body of the
/// member in [generateMemberIntrinsic].
class Intrinsifier {
final CodeGenerator codeGen;
static const w.ValueType boolType = w.NumType.i32;
static const w.ValueType intType = w.NumType.i64;
static const w.ValueType doubleType = w.NumType.f64;
static final Map<w.ValueType, Map<w.ValueType, Map<String, CodeGenCallback>>>
binaryOperatorMap = {
boolType: {
boolType: {
'|': (b) => b.i32_or(),
'^': (b) => b.i32_xor(),
'&': (b) => b.i32_and(),
}
},
intType: {
intType: {
'+': (b) => b.i64_add(),
'-': (b) => b.i64_sub(),
'*': (b) => b.i64_mul(),
'&': (b) => b.i64_and(),
'|': (b) => b.i64_or(),
'^': (b) => b.i64_xor(),
'<': (b) => b.i64_lt_s(),
'<=': (b) => b.i64_le_s(),
'>': (b) => b.i64_gt_s(),
'>=': (b) => b.i64_ge_s(),
'_div_s': (b) => b.i64_div_s(),
'_shl': (b) => b.i64_shl(),
'_shr_s': (b) => b.i64_shr_s(),
'_shr_u': (b) => b.i64_shr_u(),
'_le_u': (b) => b.i64_le_u(),
'_lt_u': (b) => b.i64_lt_u(),
}
},
doubleType: {
doubleType: {
'+': (b) => b.f64_add(),
'-': (b) => b.f64_sub(),
'*': (b) => b.f64_mul(),
'/': (b) => b.f64_div(),
'<': (b) => b.f64_lt(),
'<=': (b) => b.f64_le(),
'>': (b) => b.f64_gt(),
'>=': (b) => b.f64_ge(),
'_copysign': (b) => b.f64_copysign(),
}
},
};
static final Map<w.ValueType, Map<String, CodeGenCallback>> unaryOperatorMap =
{
intType: {
'unary-': (b) {
b.i64_const(-1);
b.i64_mul();
},
'~': (b) {
b.i64_const(-1);
b.i64_xor();
},
'toDouble': (b) {
b.f64_convert_i64_s();
},
},
doubleType: {
'unary-': (b) {
b.f64_neg();
},
'floorToDouble': (b) {
b.f64_floor();
},
'ceilToDouble': (b) {
b.f64_ceil();
},
'truncateToDouble': (b) {
b.f64_trunc();
},
'_toInt': (b) {
b.i64_trunc_sat_f64_s();
},
},
};
static final Map<String, w.ValueType> unaryResultMap = {
'toDouble': w.NumType.f64,
'floorToDouble': w.NumType.f64,
'ceilToDouble': w.NumType.f64,
'truncateToDouble': w.NumType.f64,
'_toInt': w.NumType.i64,
};
Translator get translator => codeGen.translator;
w.Instructions get b => codeGen.b;
DartType dartTypeOf(Expression exp) => codeGen.dartTypeOf(exp);
w.ValueType typeOfExp(Expression exp) {
return translator.translateType(dartTypeOf(exp));
}
static bool isComparison(String op) =>
op == '<' ||
op == '<=' ||
op == '>' ||
op == '>=' ||
op == '_le_u' ||
op == '_lt_u';
Intrinsifier(this.codeGen);
w.ValueType? generateInstanceGetterIntrinsic(InstanceGet node) {
Expression receiver = node.receiver;
String name = node.name.text;
Member target = node.interfaceTarget;
Class cls = target.enclosingClass!;
// WasmAnyRef.isObject
if (cls == translator.wasmAnyRefClass) {
assert(name == "isObject");
codeGen.wrap(receiver, w.RefType.any(nullable: false));
b.ref_test(translator.topInfo.nonNullableType);
return w.NumType.i32;
}
// WasmArrayRef.length
if (cls == translator.wasmArrayRefClass) {
assert(name == 'length');
codeGen.wrap(receiver, w.RefType.array(nullable: false));
b.array_len();
b.i64_extend_i32_u();
return w.NumType.i64;
}
// WasmTable.size
if (cls == translator.wasmTableClass) {
if (receiver is! StaticGet || receiver.target is! Field) {
throw "Table size not directly on a static field"
" at ${node.location}";
}
w.Table table = translator.getTable(receiver.target as Field)!;
assert(name == "size");
b.table_size(table);
return w.NumType.i32;
}
// int.bitlength
if (cls == translator.coreTypes.intClass && name == 'bitLength') {
w.Local temp = codeGen.function.addLocal(w.NumType.i64);
b.i64_const(64);
codeGen.wrap(receiver, w.NumType.i64);
b.local_tee(temp);
b.local_get(temp);
b.i64_const(63);
b.i64_shr_s();
b.i64_xor();
b.i64_clz();
b.i64_sub();
return w.NumType.i64;
}
// _HashAbstractImmutableBase._indexNullable
if (target == translator.hashImmutableIndexNullable) {
ClassInfo info = translator.classInfo[translator.hashFieldBaseClass]!;
codeGen.wrap(receiver, info.nonNullableType);
b.struct_get(info.struct, FieldIndex.hashBaseIndex);
return info.struct.fields[FieldIndex.hashBaseIndex].type.unpacked;
}
// _Compound._typedDataBase
if (cls == translator.ffiCompoundClass && name == '_typedDataBase') {
// A compound (subclass of Struct or Union) is represented by its i32
// address. The _typedDataBase field contains a Pointer pointing to the
// compound, whose representation is the same.
codeGen.wrap(receiver, w.NumType.i32);
return w.NumType.i32;
}
// Pointer.address
if (cls == translator.ffiPointerClass && name == 'address') {
// A Pointer is represented by its i32 address.
codeGen.wrap(receiver, w.NumType.i32);
b.i64_extend_i32_u();
return w.NumType.i64;
}
return null;
}
w.ValueType? generateInstanceIntrinsic(InstanceInvocation node) {
Expression receiver = node.receiver;
DartType receiverType = dartTypeOf(receiver);
String name = node.name.text;
Procedure target = node.interfaceTarget;
Class cls = target.enclosingClass!;
// _TypedList._(get|set)(Int|Uint|Float)(8|16|32|64)
if (cls == translator.typedListClass) {
Match? match = RegExp("^_(get|set)(Int|Uint|Float)(8|16|32|64)\$")
.matchAsPrefix(name);
if (match != null) {
bool setter = match.group(1) == "set";
bool signed = match.group(2) == "Int";
bool float = match.group(2) == "Float";
int bytes = int.parse(match.group(3)!) ~/ 8;
bool wide = bytes == 8;
ClassInfo typedListInfo =
translator.classInfo[translator.typedListClass]!;
w.RefType arrayType = typedListInfo.struct
.fields[FieldIndex.typedListArray].type.unpacked as w.RefType;
w.ArrayType arrayHeapType = arrayType.heapType as w.ArrayType;
w.ValueType valueType = float ? w.NumType.f64 : w.NumType.i64;
w.ValueType intType = wide ? w.NumType.i64 : w.NumType.i32;
// Prepare array and offset
w.Local array = codeGen.addLocal(arrayType);
w.Local offset = codeGen.addLocal(w.NumType.i32);
codeGen.wrap(receiver, typedListInfo.nonNullableType);
b.struct_get(typedListInfo.struct, FieldIndex.typedListArray);
b.local_set(array);
codeGen.wrap(node.arguments.positional[0], w.NumType.i64);
b.i32_wrap_i64();
b.local_set(offset);
if (setter) {
// Setter
w.Local value = codeGen.addLocal(intType);
codeGen.wrap(node.arguments.positional[1], valueType);
if (wide) {
if (float) {
b.i64_reinterpret_f64();
}
} else {
if (float) {
b.f32_demote_f64();
b.i32_reinterpret_f32();
} else {
b.i32_wrap_i64();
}
}
b.local_set(value);
for (int i = 0; i < bytes; i++) {
b.local_get(array);
b.local_get(offset);
if (i > 0) {
b.i32_const(i);
b.i32_add();
}
b.local_get(value);
if (i > 0) {
if (wide) {
b.i64_const(i * 8);
b.i64_shr_u();
} else {
b.i32_const(i * 8);
b.i32_shr_u();
}
}
if (wide) {
b.i32_wrap_i64();
}
b.array_set(arrayHeapType);
}
return translator.voidMarker;
} else {
// Getter
for (int i = 0; i < bytes; i++) {
b.local_get(array);
b.local_get(offset);
if (i > 0) {
b.i32_const(i);
b.i32_add();
}
if (signed && i == bytes - 1) {
b.array_get_s(arrayHeapType);
} else {
b.array_get_u(arrayHeapType);
}
if (wide) {
if (signed) {
b.i64_extend_i32_s();
} else {
b.i64_extend_i32_u();
}
}
if (i > 0) {
if (wide) {
b.i64_const(i * 8);
b.i64_shl();
b.i64_or();
} else {
b.i32_const(i * 8);
b.i32_shl();
b.i32_or();
}
}
}
if (wide) {
if (float) {
b.f64_reinterpret_i64();
}
} else {
if (float) {
b.f32_reinterpret_i32();
b.f64_promote_f32();
} else {
if (signed) {
b.i64_extend_i32_s();
} else {
b.i64_extend_i32_u();
}
}
}
return valueType;
}
}
}
// WasmAnyRef.toObject
if (cls == translator.wasmAnyRefClass && name == "toObject") {
w.Label succeed = b.block(const [], [translator.topInfo.nonNullableType]);
codeGen.wrap(receiver, const w.RefType.any(nullable: false));
b.br_on_cast(translator.topInfo.nonNullableType, succeed);
codeGen.throwWasmRefError("a Dart object");
b.end(); // succeed
return translator.topInfo.nonNullableType;
}
// WasmIntArray.(readSigned|readUnsigned|write)
// WasmFloatArray.(read|write)
// WasmObjectArray.(read|write)
if (cls.superclass == translator.wasmArrayRefClass) {
DartType elementType =
(receiverType as InterfaceType).typeArguments.single;
w.ArrayType arrayType = translator.arrayTypeForDartType(elementType);
w.StorageType wasmType = arrayType.elementType.type;
bool innerExtend =
wasmType == w.PackedType.i8 || wasmType == w.PackedType.i16;
bool outerExtend =
wasmType.unpacked == w.NumType.i32 || wasmType == w.NumType.f32;
switch (name) {
case 'read':
case 'readSigned':
case 'readUnsigned':
bool unsigned = name == 'readUnsigned';
Expression array = receiver;
Expression index = node.arguments.positional.single;
codeGen.wrap(array, w.RefType.def(arrayType, nullable: false));
codeGen.wrap(index, w.NumType.i64);
b.i32_wrap_i64();
if (innerExtend) {
if (unsigned) {
b.array_get_u(arrayType);
} else {
b.array_get_s(arrayType);
}
} else {
b.array_get(arrayType);
}
if (outerExtend) {
if (wasmType == w.NumType.f32) {
b.f64_promote_f32();
return w.NumType.f64;
} else {
if (unsigned) {
b.i64_extend_i32_u();
} else {
b.i64_extend_i32_s();
}
return w.NumType.i64;
}
}
return wasmType.unpacked;
case 'write':
Expression array = receiver;
Expression index = node.arguments.positional[0];
Expression value = node.arguments.positional[1];
codeGen.wrap(array, w.RefType.def(arrayType, nullable: false));
codeGen.wrap(index, w.NumType.i64);
b.i32_wrap_i64();
codeGen.wrap(value, typeOfExp(value));
if (outerExtend) {
if (wasmType == w.NumType.f32) {
b.f32_demote_f64();
} else {
b.i32_wrap_i64();
}
}
b.array_set(arrayType);
return codeGen.voidMarker;
default:
throw "Unsupported array method: $name";
}
}
// Wasm(I32|I64|F32|F64) conversions
if (cls.superclass?.superclass == translator.wasmTypesBaseClass) {
w.StorageType receiverType = translator.builtinTypes[cls]!;
switch (receiverType) {
case w.NumType.i32:
codeGen.wrap(receiver, w.NumType.i32);
switch (name) {
case "toIntSigned":
b.i64_extend_i32_s();
return w.NumType.i64;
case "toIntUnsigned":
b.i64_extend_i32_u();
return w.NumType.i64;
case "toBool":
b.i32_const(0);
b.i32_ne();
return w.NumType.i32;
default:
throw 'Unknown i32 conversion to $receiverType';
}
case w.NumType.i64:
assert(name == "toInt");
codeGen.wrap(receiver, w.NumType.i64);
return w.NumType.i64;
case w.NumType.f32:
assert(name == "toDouble");
codeGen.wrap(receiver, w.NumType.f32);
b.f64_promote_f32();
return w.NumType.f64;
case w.NumType.f64:
assert(name == "toDouble");
codeGen.wrap(receiver, w.NumType.f64);
return w.NumType.f64;
}
}
// WasmTable.[] and WasmTable.[]=
if (cls == translator.wasmTableClass) {
if (receiver is! StaticGet || receiver.target is! Field) {
throw "Table indexing not directly on a static field"
" at ${node.location}";
}
w.Table table = translator.getTable(receiver.target as Field)!;
codeGen.wrap(node.arguments.positional[0], w.NumType.i32);
if (name == '[]') {
b.table_get(table);
return table.type;
} else {
assert(name == '[]=');
codeGen.wrap(node.arguments.positional[1], table.type);
b.table_set(table);
return codeGen.voidMarker;
}
}
// List.[] on list constants
if (receiver is ConstantExpression &&
receiver.constant is ListConstant &&
name == '[]') {
Expression arg = node.arguments.positional.single;
// If the list is indexed by a constant, or the ABI index, just pick
// the element at that constant index.
int? constIndex = null;
if (arg is IntLiteral) {
constIndex = arg.value;
} else if (arg is ConstantExpression) {
Constant argConst = arg.constant;
if (argConst is IntConstant) {
constIndex = argConst.value;
}
} else if (arg is StaticInvocation) {
if (arg.target.enclosingLibrary.name == "dart.ffi" &&
arg.name.text == "_abi") {
constIndex = kWasmAbiEnumIndex;
}
}
if (constIndex != null) {
ListConstant list = receiver.constant as ListConstant;
Expression element = ConstantExpression(list.entries[constIndex]);
return codeGen.wrap(element, typeOfExp(element));
}
// Access the underlying array directly.
ClassInfo info = translator.classInfo[translator.listBaseClass]!;
Field arrayField = translator.listBaseClass.fields
.firstWhere((f) => f.name.text == '_data');
int arrayFieldIndex = translator.fieldIndex[arrayField]!;
w.ArrayType arrayType =
(info.struct.fields[arrayFieldIndex].type as w.RefType).heapType
as w.ArrayType;
codeGen.wrap(receiver, info.nonNullableType);
b.struct_get(info.struct, arrayFieldIndex);
codeGen.wrap(arg, w.NumType.i64);
b.i32_wrap_i64();
b.array_get(arrayType);
return translator.topInfo.nullableType;
}
if (node.arguments.positional.length == 1) {
// Binary operator
Expression left = node.receiver;
Expression right = node.arguments.positional.single;
DartType argType = dartTypeOf(right);
if (argType is VoidType) return null;
w.ValueType leftType = translator.translateType(receiverType);
w.ValueType rightType = translator.translateType(argType);
var code = binaryOperatorMap[leftType]?[rightType]?[name];
if (code != null) {
w.ValueType outType = isComparison(name) ? w.NumType.i32 : leftType;
codeGen.wrap(left, leftType);
codeGen.wrap(right, rightType);
code(b);
return outType;
}
} else if (node.arguments.positional.isEmpty) {
// Unary operator
Expression operand = node.receiver;
w.ValueType opType = translator.translateType(receiverType);
var code = unaryOperatorMap[opType]?[name];
if (code != null) {
codeGen.wrap(operand, opType);
code(b);
return unaryResultMap[name] ?? opType;
}
}
return null;
}
w.ValueType? generateEqualsIntrinsic(EqualsCall node) {
w.ValueType leftType = typeOfExp(node.left);
w.ValueType rightType = typeOfExp(node.right);
// Compare bool, Pointer or WasmI32.
if (leftType == w.NumType.i32 && rightType == w.NumType.i32) {
codeGen.wrap(node.left, w.NumType.i32);
codeGen.wrap(node.right, w.NumType.i32);
b.i32_eq();
return w.NumType.i32;
}
// Compare int or WasmI64.
if (leftType == w.NumType.i64 && rightType == w.NumType.i64) {
codeGen.wrap(node.left, w.NumType.i64);
codeGen.wrap(node.right, w.NumType.i64);
b.i64_eq();
return w.NumType.i32;
}
// Compare WasmF32.
if (leftType == w.NumType.f32 && rightType == w.NumType.f32) {
codeGen.wrap(node.left, w.NumType.f32);
codeGen.wrap(node.right, w.NumType.f32);
b.f32_eq();
return w.NumType.i32;
}
// Compare double or WasmF64.
if (leftType == doubleType && rightType == doubleType) {
codeGen.wrap(node.left, w.NumType.f64);
codeGen.wrap(node.right, w.NumType.f64);
b.f64_eq();
return w.NumType.i32;
}
return null;
}
w.ValueType? generateStaticGetterIntrinsic(StaticGet node) {
Member target = node.target;
// ClassID getters
String? className = translator.getPragma(target, "wasm:class-id");
if (className != null) {
List<String> libAndClass = className.split("#");
Class cls = translator.libraries
.firstWhere(
(l) => l.name == libAndClass[0] && l.importUri.scheme == 'dart')
.classes
.firstWhere((c) => c.name == libAndClass[1]);
int classId = translator.classInfo[cls]!.classId;
b.i64_const(classId);
return w.NumType.i64;
}
// nullptr
if (target.enclosingLibrary.name == "dart.ffi" &&
target.name.text == "nullptr") {
// A Pointer is represented by its i32 address.
b.i32_const(0);
return w.NumType.i32;
}
if (target.enclosingLibrary.name == "dart.core" &&
target.name.text == "_isIntrinsified") {
// This is part of the VM's [BigInt] implementation. We just return false.
// TODO(joshualitt): Can we find another way to reuse this patch file
// without hardcoding this case?
b.i32_const(0);
return w.NumType.i32;
}
return null;
}
w.ValueType getID(Expression node) {
ClassInfo info = translator.topInfo;
codeGen.wrap(node, info.nonNullableType);
b.struct_get(info.struct, FieldIndex.classId);
b.i64_extend_i32_u();
return w.NumType.i64;
}
w.ValueType changeListClassID(StaticInvocation node, Class newClass) {
ClassInfo receiverInfo = translator.classInfo[translator.listBaseClass]!;
codeGen.wrap(
node.arguments.positional.single, receiverInfo.nonNullableType);
w.Local receiverLocal =
codeGen.function.addLocal(receiverInfo.nonNullableType);
b.local_tee(receiverLocal);
// We ignore the type argument and just update the classID of the
// receiver.
// TODO(joshualitt): If the amount of free space is significant, it
// might be worth doing a copy here.
ClassInfo newInfo = translator.classInfo[newClass]!;
ClassInfo topInfo = translator.topInfo;
b.i32_const(newInfo.classId);
b.struct_set(topInfo.struct, FieldIndex.classId);
b.local_get(receiverLocal);
return newInfo.nonNullableType;
}
w.ValueType? generateStaticIntrinsic(StaticInvocation node) {
String name = node.name.text;
Class? cls = node.target.enclosingClass;
// dart:core static functions
if (node.target.enclosingLibrary == translator.coreTypes.coreLibrary) {
switch (name) {
case "identical":
Expression first = node.arguments.positional[0];
Expression second = node.arguments.positional[1];
DartType boolType = translator.coreTypes.boolNonNullableRawType;
InterfaceType intType = translator.coreTypes.intNonNullableRawType;
DartType doubleType = translator.coreTypes.doubleNonNullableRawType;
List<DartType> types = [dartTypeOf(first), dartTypeOf(second)];
if (types.every((t) => t == intType)) {
codeGen.wrap(first, w.NumType.i64);
codeGen.wrap(second, w.NumType.i64);
b.i64_eq();
return w.NumType.i32;
}
if (types.any((t) =>
t is InterfaceType &&
t != boolType &&
t != doubleType &&
!translator.hierarchy
.isSubtypeOf(intType.classNode, t.classNode))) {
codeGen.wrap(first, w.RefType.eq(nullable: true));
codeGen.wrap(second, w.RefType.eq(nullable: true));
b.ref_eq();
return w.NumType.i32;
}
break;
case "_getHash":
Expression arg = node.arguments.positional[0];
w.ValueType objectType = translator.objectInfo.nonNullableType;
codeGen.wrap(arg, objectType);
b.struct_get(translator.objectInfo.struct, FieldIndex.identityHash);
b.i64_extend_i32_u();
return w.NumType.i64;
case "_setHash":
Expression arg = node.arguments.positional[0];
Expression hash = node.arguments.positional[1];
w.ValueType objectType = translator.objectInfo.nonNullableType;
codeGen.wrap(arg, objectType);
codeGen.wrap(hash, w.NumType.i64);
b.i32_wrap_i64();
b.struct_set(translator.objectInfo.struct, FieldIndex.identityHash);
return codeGen.voidMarker;
case "_getTypeRulesSupers":
return translator.types.makeTypeRulesSupers(b);
case "_getTypeRulesSubstitutions":
return translator.types.makeTypeRulesSubstitutions(b);
case "_getTypeNames":
return translator.types.makeTypeNames(b);
}
}
// dart:_internal static functions
if (node.target.enclosingLibrary.name == "dart._internal") {
switch (name) {
case "unsafeCast":
case "unsafeCastOpaque":
Expression operand = node.arguments.positional.single;
// Just evaluate the operand and let the context convert it to the
// expected type.
return codeGen.wrap(operand, typeOfExp(operand));
case "_nativeEffect":
// Ignore argument
return translator.voidMarker;
case "allocateOneByteString":
ClassInfo info = translator.classInfo[translator.oneByteStringClass]!;
translator.functions.allocateClass(info.classId);
w.ArrayType arrayType =
translator.wasmArrayType(w.PackedType.i8, "WasmI8");
Expression length = node.arguments.positional[0];
b.i32_const(info.classId);
b.i32_const(initialIdentityHash);
codeGen.wrap(length, w.NumType.i64);
b.i32_wrap_i64();
b.array_new_default(arrayType);
b.struct_new(info.struct);
return info.nonNullableType;
case "writeIntoOneByteString":
ClassInfo info = translator.classInfo[translator.oneByteStringClass]!;
w.ArrayType arrayType =
translator.wasmArrayType(w.PackedType.i8, "WasmI8");
Field arrayField = translator.oneByteStringClass.fields
.firstWhere((f) => f.name.text == '_array');
int arrayFieldIndex = translator.fieldIndex[arrayField]!;
Expression string = node.arguments.positional[0];
Expression index = node.arguments.positional[1];
Expression codePoint = node.arguments.positional[2];
codeGen.wrap(string, info.nonNullableType);
b.struct_get(info.struct, arrayFieldIndex);
codeGen.wrap(index, w.NumType.i64);
b.i32_wrap_i64();
codeGen.wrap(codePoint, w.NumType.i64);
b.i32_wrap_i64();
b.array_set(arrayType);
return codeGen.voidMarker;
case "allocateTwoByteString":
ClassInfo info = translator.classInfo[translator.twoByteStringClass]!;
translator.functions.allocateClass(info.classId);
w.ArrayType arrayType =
translator.wasmArrayType(w.PackedType.i16, "WasmI16");
Expression length = node.arguments.positional[0];
b.i32_const(info.classId);
b.i32_const(initialIdentityHash);
codeGen.wrap(length, w.NumType.i64);
b.i32_wrap_i64();
b.array_new_default(arrayType);
b.struct_new(info.struct);
return info.nonNullableType;
case "writeIntoTwoByteString":
ClassInfo info = translator.classInfo[translator.twoByteStringClass]!;
w.ArrayType arrayType =
translator.wasmArrayType(w.PackedType.i16, "WasmI16");
Field arrayField = translator.oneByteStringClass.fields
.firstWhere((f) => f.name.text == '_array');
int arrayFieldIndex = translator.fieldIndex[arrayField]!;
Expression string = node.arguments.positional[0];
Expression index = node.arguments.positional[1];
Expression codePoint = node.arguments.positional[2];
codeGen.wrap(string, info.nonNullableType);
b.struct_get(info.struct, arrayFieldIndex);
codeGen.wrap(index, w.NumType.i64);
b.i32_wrap_i64();
codeGen.wrap(codePoint, w.NumType.i64);
b.i32_wrap_i64();
b.array_set(arrayType);
return codeGen.voidMarker;
case "floatToIntBits":
codeGen.wrap(node.arguments.positional.single, w.NumType.f64);
b.f32_demote_f64();
b.i32_reinterpret_f32();
b.i64_extend_i32_u();
return w.NumType.i64;
case "intBitsToFloat":
codeGen.wrap(node.arguments.positional.single, w.NumType.i64);
b.i32_wrap_i64();
b.f32_reinterpret_i32();
b.f64_promote_f32();
return w.NumType.f64;
case "doubleToIntBits":
codeGen.wrap(node.arguments.positional.single, w.NumType.f64);
b.i64_reinterpret_f64();
return w.NumType.i64;
case "intBitsToDouble":
codeGen.wrap(node.arguments.positional.single, w.NumType.i64);
b.f64_reinterpret_i64();
return w.NumType.f64;
case "getID":
return getID(node.arguments.positional.single);
case "makeListFixedLength":
return changeListClassID(node, translator.fixedLengthListClass);
case "makeFixedListUnmodifiable":
return changeListClassID(node, translator.immutableListClass);
}
}
// dart:ffi static functions
if (node.target.enclosingLibrary.name == "dart.ffi") {
// Pointer.fromAddress
if (name == "fromAddress") {
// A Pointer is represented by its i32 address.
codeGen.wrap(node.arguments.positional.single, w.NumType.i64);
b.i32_wrap_i64();
return w.NumType.i32;
}
// Accesses to Pointer.value, Pointer.value=, Pointer.[], Pointer.[]= and
// the members of structs and unions are desugared by the FFI kernel
// transformations into calls to memory load and store functions.
RegExp loadStoreFunctionNames = RegExp("^_(load|store)"
"((Int|Uint)(8|16|32|64)|(Float|Double)(Unaligned)?|Pointer)\$");
if (loadStoreFunctionNames.hasMatch(name)) {
Expression pointerArg = node.arguments.positional[0];
Expression offsetArg = node.arguments.positional[1];
codeGen.wrap(pointerArg, w.NumType.i32);
int offset;
if (offsetArg is IntLiteral) {
offset = offsetArg.value;
} else if (offsetArg is ConstantExpression &&
offsetArg.constant is IntConstant) {
offset = (offsetArg.constant as IntConstant).value;
} else {
codeGen.wrap(offsetArg, w.NumType.i64);
b.i32_wrap_i64();
b.i32_add();
offset = 0;
}
switch (name) {
case "_loadInt8":
b.i64_load8_s(translator.ffiMemory, offset);
return w.NumType.i64;
case "_loadUint8":
b.i64_load8_u(translator.ffiMemory, offset);
return w.NumType.i64;
case "_loadInt16":
b.i64_load16_s(translator.ffiMemory, offset);
return w.NumType.i64;
case "_loadUint16":
b.i64_load16_u(translator.ffiMemory, offset);
return w.NumType.i64;
case "_loadInt32":
b.i64_load32_s(translator.ffiMemory, offset);
return w.NumType.i64;
case "_loadUint32":
b.i64_load32_u(translator.ffiMemory, offset);
return w.NumType.i64;
case "_loadInt64":
case "_loadUint64":
b.i64_load(translator.ffiMemory, offset);
return w.NumType.i64;
case "_loadFloat":
b.f32_load(translator.ffiMemory, offset);
b.f64_promote_f32();
return w.NumType.f64;
case "_loadFloatUnaligned":
b.f32_load(translator.ffiMemory, offset, 0);
b.f64_promote_f32();
return w.NumType.f64;
case "_loadDouble":
b.f64_load(translator.ffiMemory, offset);
return w.NumType.f64;
case "_loadDoubleUnaligned":
b.f64_load(translator.ffiMemory, offset, 0);
return w.NumType.f64;
case "_loadPointer":
b.i32_load(translator.ffiMemory, offset);
return w.NumType.i32;
case "_storeInt8":
case "_storeUint8":
codeGen.wrap(node.arguments.positional[2], w.NumType.i64);
b.i64_store8(translator.ffiMemory, offset);
return translator.voidMarker;
case "_storeInt16":
case "_storeUint16":
codeGen.wrap(node.arguments.positional[2], w.NumType.i64);
b.i64_store16(translator.ffiMemory, offset);
return translator.voidMarker;
case "_storeInt32":
case "_storeUint32":
codeGen.wrap(node.arguments.positional[2], w.NumType.i64);
b.i64_store32(translator.ffiMemory, offset);
return translator.voidMarker;
case "_storeInt64":
case "_storeUint64":
codeGen.wrap(node.arguments.positional[2], w.NumType.i64);
b.i64_store(translator.ffiMemory, offset);
return translator.voidMarker;
case "_storeFloat":
codeGen.wrap(node.arguments.positional[2], w.NumType.f64);
b.f32_demote_f64();
b.f32_store(translator.ffiMemory, offset);
return translator.voidMarker;
case "_storeFloatUnaligned":
codeGen.wrap(node.arguments.positional[2], w.NumType.f64);
b.f32_demote_f64();
b.f32_store(translator.ffiMemory, offset, 0);
return translator.voidMarker;
case "_storeDouble":
codeGen.wrap(node.arguments.positional[2], w.NumType.f64);
b.f64_store(translator.ffiMemory, offset);
return translator.voidMarker;
case "_storeDoubleUnaligned":
codeGen.wrap(node.arguments.positional[2], w.NumType.f64);
b.f64_store(translator.ffiMemory, offset, 0);
return translator.voidMarker;
case "_storePointer":
codeGen.wrap(node.arguments.positional[2], w.NumType.i32);
b.i32_store(translator.ffiMemory, offset);
return translator.voidMarker;
}
}
}
if (cls != null && translator.isWasmType(cls)) {
// Wasm(Int|Float|Object)Array constructors
if (cls.superclass == translator.wasmArrayRefClass) {
Expression length = node.arguments.positional[0];
w.ArrayType arrayType =
translator.arrayTypeForDartType(node.arguments.types.single);
codeGen.wrap(length, w.NumType.i64);
b.i32_wrap_i64();
b.array_new_default(arrayType);
return w.RefType.def(arrayType, nullable: false);
}
// (WasmFuncRef|WasmFunction).fromRef constructors
if (cls == translator.wasmFunctionClass && name == "fromFuncRef") {
Expression ref = node.arguments.positional[0];
w.RefType resultType = typeOfExp(node) as w.RefType;
w.Label succeed = b.block(const [], [resultType]);
codeGen.wrap(ref, w.RefType.func(nullable: false));
b.br_on_cast(resultType, succeed);
codeGen.throwWasmRefError("a function with the expected signature");
b.end(); // succeed
return resultType;
}
// WasmFunction.fromFunction constructor
if (cls == translator.wasmFunctionClass) {
assert(name == "fromFunction");
Expression f = node.arguments.positional[0];
if (f is! ConstantExpression || f.constant is! StaticTearOffConstant) {
throw "Argument to WasmFunction.fromFunction isn't a static function";
}
StaticTearOffConstant func = f.constant as StaticTearOffConstant;
w.BaseFunction wasmFunction =
translator.functions.getFunction(func.targetReference);
w.Global functionRef = translator.makeFunctionRef(wasmFunction);
b.global_get(functionRef);
return functionRef.type.type;
}
// Wasm(AnyRef|FuncRef|EqRef|StructRef|I32|I64|F32|F64) constructors
Expression value = node.arguments.positional[0];
w.StorageType targetType = translator.builtinTypes[cls]!;
switch (targetType) {
case w.NumType.i32:
switch (name) {
case "fromInt":
codeGen.wrap(value, w.NumType.i64);
b.i32_wrap_i64();
return w.NumType.i32;
case "int8FromInt":
codeGen.wrap(value, w.NumType.i64);
b.i32_wrap_i64();
b.i32_extend8_s();
return w.NumType.i32;
case "uint8FromInt":
codeGen.wrap(value, w.NumType.i64);
b.i32_wrap_i64();
b.i32_const(0xFF);
b.i32_and();
return w.NumType.i32;
case "int16FromInt":
codeGen.wrap(value, w.NumType.i64);
b.i32_wrap_i64();
b.i32_extend16_s();
return w.NumType.i32;
case "uint16FromInt":
codeGen.wrap(value, w.NumType.i64);
b.i32_wrap_i64();
b.i32_const(0xFFFF);
b.i32_and();
return w.NumType.i32;
case "fromBool":
codeGen.wrap(value, w.NumType.i32);
return w.NumType.i32;
default:
throw 'Unhandled WasmI32 factory: $name';
}
case w.NumType.i64:
codeGen.wrap(value, w.NumType.i64);
return w.NumType.i64;
case w.NumType.f32:
codeGen.wrap(value, w.NumType.f64);
b.f32_demote_f64();
return w.NumType.f32;
case w.NumType.f64:
codeGen.wrap(value, w.NumType.f64);
return w.NumType.f64;
default:
w.RefType valueType = targetType as w.RefType;
codeGen.wrap(value, valueType);
return valueType;
}
}
// dart:_wasm static functions
if (node.target.enclosingLibrary.name == "dart._wasm") {
Expression value = node.arguments.positional.single;
switch (name) {
case "_externalizeNonNullable":
codeGen.wrap(value, w.RefType.any(nullable: false));
b.extern_externalize();
return w.RefType.extern(nullable: false);
case "_externalizeNullable":
codeGen.wrap(value, w.RefType.any(nullable: true));
b.extern_externalize();
return w.RefType.extern(nullable: true);
case "_internalizeNonNullable":
codeGen.wrap(value, w.RefType.extern(nullable: false));
b.extern_internalize();
return w.RefType.any(nullable: false);
case "_internalizeNullable":
codeGen.wrap(value, w.RefType.extern(nullable: true));
b.extern_internalize();
return w.RefType.any(nullable: true);
case "_wasmExternRefIsNull":
codeGen.wrap(value, w.RefType.extern(nullable: true));
b.ref_is_null();
return w.NumType.i32;
}
}
return null;
}
w.ValueType? generateConstructorIntrinsic(ConstructorInvocation node) {
String name = node.name.text;
// _Compound.#fromTypedDataBase
if (name == "#fromTypedDataBase") {
// A compound (subclass of Struct or Union) is represented by its i32
// address. The argument to the #fromTypedDataBase constructor is a
// Pointer, whose representation is the same.
codeGen.wrap(node.arguments.positional.single, w.NumType.i32);
return w.NumType.i32;
}
return null;
}
w.ValueType? generateFunctionCallIntrinsic(FunctionInvocation node) {
Expression receiver = node.receiver;
if (receiver is InstanceGet &&
receiver.interfaceTarget == translator.wasmFunctionCall) {
// Receiver is a WasmFunction
assert(receiver.name.text == "call");
w.RefType receiverType =
translator.translateType(dartTypeOf(receiver.receiver)) as w.RefType;
w.Local temp = codeGen.addLocal(receiverType);
codeGen.wrap(receiver.receiver, receiverType);
b.local_set(temp);
w.FunctionType functionType = receiverType.heapType as w.FunctionType;
assert(node.arguments.positional.length == functionType.inputs.length);
for (int i = 0; i < node.arguments.positional.length; i++) {
codeGen.wrap(node.arguments.positional[i], functionType.inputs[i]);
}
b.local_get(temp);
b.call_ref(functionType);
return translator.outputOrVoid(functionType.outputs);
}
if (receiver is InstanceInvocation &&
receiver.interfaceTarget == translator.wasmTableCallIndirect) {
// Receiver is a WasmTable.callIndirect
assert(receiver.name.text == "callIndirect");
Expression tableExp = receiver.receiver;
if (tableExp is! StaticGet || tableExp.target is! Field) {
throw "Table callIndirect not directly on a static field"
" at ${node.location}";
}
w.Table table = translator.getTable(tableExp.target as Field)!;
InterfaceType wasmFunctionType = InterfaceType(
translator.wasmFunctionClass,
Nullability.nonNullable,
[receiver.arguments.types.single]);
w.RefType receiverType =
translator.translateType(wasmFunctionType) as w.RefType;
w.Local tableIndex = codeGen.addLocal(w.NumType.i32);
codeGen.wrap(receiver.arguments.positional.single, w.NumType.i32);
b.local_set(tableIndex);
w.FunctionType functionType = receiverType.heapType as w.FunctionType;
assert(node.arguments.positional.length == functionType.inputs.length);
for (int i = 0; i < node.arguments.positional.length; i++) {
codeGen.wrap(node.arguments.positional[i], functionType.inputs[i]);
}
b.local_get(tableIndex);
b.call_indirect(functionType, table);
return translator.outputOrVoid(functionType.outputs);
}
return null;
}
bool generateMemberIntrinsic(Reference target, w.DefinedFunction function,
List<w.Local> paramLocals, w.Label? returnLabel) {
Member member = target.asMember;
if (member is! Procedure) return false;
String name = member.name.text;
FunctionNode functionNode = member.function;
// Object.==
if (member == translator.coreTypes.objectEquals) {
b.local_get(paramLocals[0]);
b.local_get(paramLocals[1]);
b.ref_eq();
return true;
}
// Object.runtimeType
if (member.enclosingClass == translator.coreTypes.objectClass &&
name == "runtimeType") {
// Simple redirect to `_getMasqueradedRuntimeType`. This is done to keep
// `Object.runtimeType` external. If `Object.runtimeType` is implemented
// in Dart, the TFA will conclude that `null.runtimeType` never returns,
// since it dispatches to `Object.runtimeType`, which uses the receiver
// as non-nullable.
w.Local receiver = paramLocals[0];
b.local_get(receiver);
codeGen.call(translator.getMasqueradedRuntimeType.reference);
return true;
}
// _getActualRuntimeType and _getMasqueradedRuntimeType
if (member.enclosingLibrary == translator.coreTypes.coreLibrary &&
(name == "_getActualRuntimeType" ||
name == "_getMasqueradedRuntimeType")) {
final bool masqueraded = name == "_getMasqueradedRuntimeType";
final w.Local object = paramLocals[0];
final w.Local classId = function.addLocal(w.NumType.i32);
final w.Local resultClassId = function.addLocal(w.NumType.i32);
w.Label interfaceType = b.block();
w.Label notMasqueraded = b.block();
w.Label recordType = b.block();
w.Label functionType = b.block();
w.Label abstractClass = b.block();
// Look up the type category by class ID and switch on it.
b.global_get(translator.types.typeCategoryTable);
b.local_get(object);
b.struct_get(translator.topInfo.struct, FieldIndex.classId);
b.local_tee(classId);
b.array_get_u((translator.types.typeCategoryTable.type.type as w.RefType)
.heapType as w.ArrayType);
b.local_tee(resultClassId);
b.br_table([
abstractClass,
functionType,
recordType,
if (masqueraded) notMasqueraded
], masqueraded ? interfaceType : notMasqueraded);
b.end(); // abstractClass
// We should never see class IDs for abstract types.
b.unreachable();
b.end(); // functionType
w.StructType closureBase = translator.closureLayouter.closureBaseStruct;
b.local_get(object);
b.ref_cast(w.RefType.def(closureBase, nullable: false));
b.struct_get(closureBase, FieldIndex.closureRuntimeType);
b.return_();
b.end(); // recordType
b.local_get(object);
translator.convertType(
function,
object.type,
translator.classInfo[translator.coreTypes.recordClass]!.repr
.nonNullableType);
codeGen.call(translator.recordGetRecordRuntimeType.reference);
b.return_();
b.end(); // notMasqueraded
b.local_get(classId);
b.local_set(resultClassId);
b.end(); // interfaceType
ClassInfo info = translator.classInfo[translator.interfaceTypeClass]!;
b.i32_const(info.classId);
b.i32_const(initialIdentityHash);
// Runtime types are never nullable.
b.i32_const(0);
// Set class ID of interface type.
b.local_get(resultClassId);
b.i64_extend_i32_u();
// Call _typeArguments to get the list of type arguments.
b.local_get(object);
codeGen.call(translator.objectGetTypeArguments.reference);
b.struct_new(info.struct);
b.return_();
}
// identical
if (member == translator.coreTypes.identicalProcedure) {
w.Local first = paramLocals[0];
w.Local second = paramLocals[1];
ClassInfo boolInfo = translator.classInfo[translator.boxedBoolClass]!;
ClassInfo intInfo = translator.classInfo[translator.boxedIntClass]!;
ClassInfo doubleInfo = translator.classInfo[translator.boxedDoubleClass]!;
w.Local cid = function.addLocal(w.NumType.i32);
// If the references are identical, return true.
b.local_get(first);
b.local_get(second);
b.ref_eq();
b.if_();
b.i32_const(1);
b.return_();
b.end();
w.Label fail = b.block();
// If either is `null`, or their class IDs are different, return false.
b.local_get(first);
b.br_on_null(fail);
b.struct_get(translator.topInfo.struct, FieldIndex.classId);
b.local_tee(cid);
b.local_get(second);
b.br_on_null(fail);
b.struct_get(translator.topInfo.struct, FieldIndex.classId);
b.i32_ne();
b.br_if(fail);
// Both bool?
b.local_get(cid);
b.i32_const(boolInfo.classId);
b.i32_eq();
b.if_();
b.local_get(first);
b.ref_cast(boolInfo.nonNullableType);
b.struct_get(boolInfo.struct, FieldIndex.boxValue);
b.local_get(second);
b.ref_cast(boolInfo.nonNullableType);
b.struct_get(boolInfo.struct, FieldIndex.boxValue);
b.i32_eq();
b.return_();
b.end();
// Both int?
b.local_get(cid);
b.i32_const(intInfo.classId);
b.i32_eq();
b.if_();
b.local_get(first);
b.ref_cast(intInfo.nonNullableType);
b.struct_get(intInfo.struct, FieldIndex.boxValue);
b.local_get(second);
b.ref_cast(intInfo.nonNullableType);
b.struct_get(intInfo.struct, FieldIndex.boxValue);
b.i64_eq();
b.return_();
b.end();
// Both double?
b.local_get(cid);
b.i32_const(doubleInfo.classId);
b.i32_eq();
b.if_();
b.local_get(first);
b.ref_cast(doubleInfo.nonNullableType);
b.struct_get(doubleInfo.struct, FieldIndex.boxValue);
b.i64_reinterpret_f64();
b.local_get(second);
b.ref_cast(doubleInfo.nonNullableType);
b.struct_get(doubleInfo.struct, FieldIndex.boxValue);
b.i64_reinterpret_f64();
b.i64_eq();
b.return_();
b.end();
// Not identical
b.end(); // fail
b.i32_const(0);
return true;
}
if (member.enclosingLibrary == translator.coreTypes.coreLibrary &&
name == "identityHashCode") {
final w.Local arg = paramLocals[0];
final w.Local nonNullArg =
function.addLocal(translator.topInfo.nonNullableType);
final List<int> classIds = translator.valueClasses.keys
.map((cls) => translator.classInfo[cls]!.classId)
.toList()
..sort();
// If the argument is `null`, return the hash code of `null`.
final w.Label notNull =
b.block(const [], [translator.topInfo.nonNullableType]);
b.local_get(arg);
b.br_on_non_null(notNull);
b.i64_const(null.hashCode);
b.return_();
b.end(); // notNull
b.local_set(nonNullArg);
// Branch on class ID.
final w.Label defaultLabel = b.block();
final List<w.Label> labels =
List.generate(classIds.length, (_) => b.block());
b.local_get(nonNullArg);
b.struct_get(translator.topInfo.struct, FieldIndex.classId);
int labelIndex = 0;
final List<w.Label> targets = List.generate(classIds.last + 1, (id) {
return id == classIds[labelIndex] ? labels[labelIndex++] : defaultLabel;
});
b.br_table(targets, defaultLabel);
// For value classes, dispatch to their `hashCode` implementation.
for (final int id in classIds.reversed) {
final Class cls = translator.valueClasses[translator.classes[id].cls!]!;
final Procedure hashCodeProcedure =
cls.procedures.firstWhere((p) => p.name.text == "hashCode");
b.end(); // Jump target for class ID
b.local_get(nonNullArg);
codeGen.call(hashCodeProcedure.reference);
b.return_();
}
// For all other classes, dispatch to the `hashCode` implementation in
// `Object`.
b.end(); // defaultLabel
b.local_get(nonNullArg);
codeGen.call(translator.objectHashCode.reference);
return true;
}
// _typeArguments
if (member.name.text == "_typeArguments") {
Class cls = member.enclosingClass!;
ClassInfo classInfo = translator.classInfo[cls]!;
w.Local object = paramLocals[0];
codeGen.makeList(translator.types.typeType, cls.typeParameters.length,
(w.ValueType elementType, int i) {
TypeParameter typeParameter = cls.typeParameters[i];
int typeParameterIndex = translator.typeParameterIndex[typeParameter]!;
b.local_get(object);
b.ref_cast(classInfo.nonNullableType);
b.struct_get(classInfo.struct, typeParameterIndex);
});
return true;
}
// (Int|Uint|Float)(8|16|32|64)(Clamped)?(List|ArrayView) constructors
if (member.isExternal &&
member.enclosingLibrary.name == "dart.typed_data") {
if (member.isFactory) {
String className = member.enclosingClass!.name;
Match? match = RegExp("^(Int|Uint|Float)(8|16|32|64)(Clamped)?List\$")
.matchAsPrefix(className);
if (match != null) {
int elementSize = int.parse(match.group(2)!);
int elementSizeInBytes = elementSize ~/ 8;
int maxNumberOfElements = ((1 << 32) - 1) ~/ elementSizeInBytes;
int shift = elementSizeInBytes.bitLength - 1;
Class cls = member.enclosingLibrary.classes
.firstWhere((c) => c.name == "_$className");
ClassInfo info = translator.classInfo[cls]!;
translator.functions.allocateClass(info.classId);
w.ArrayType arrayType =
translator.wasmArrayType(w.PackedType.i8, "i8");
w.Local length = paramLocals[0];
// Check array size
b.local_get(length);
b.i64_const(maxNumberOfElements);
b.i64_gt_u();
b.if_();
// int value
b.local_get(length);
// int minValue
b.i64_const(0);
// int maxValue
b.i64_const(maxNumberOfElements);
// String? name
b.ref_null(translator.objectInfo.struct);
// String? message
b.ref_null(translator.objectInfo.struct);
b.call(translator.functions.getFunction(
translator.rangeErrorCheckValueInInterval.reference));
b.drop(); // call returns first argument (value)
b.end();
b.i32_const(info.classId);
b.i32_const(initialIdentityHash);
b.local_get(length);
b.i32_wrap_i64();
b.local_get(length);
if (shift > 0) {
b.i64_const(shift);
b.i64_shl();
}
b.i32_wrap_i64();
b.array_new_default(arrayType);
b.struct_new(info.struct);
return true;
}
bool _createView() {
ClassInfo info = translator.classInfo[member.enclosingClass]!;
translator.functions.allocateClass(info.classId);
w.Local buffer = paramLocals[0];
w.Local offsetInBytes = paramLocals[1];
w.Local length = paramLocals[2];
b.i32_const(info.classId);
b.i32_const(initialIdentityHash);
b.local_get(length);
b.i32_wrap_i64();
b.local_get(buffer);
b.local_get(offsetInBytes);
b.i32_wrap_i64();
b.struct_new(info.struct);
return true;
}
match = RegExp(
"^_(Int|Uint|Float)(8|16|32|64|32x4|64x2)(Clamped)?ArrayView\$")
.matchAsPrefix(className);
if (match != null ||
member.enclosingClass == translator.byteDataViewClass) {
return _createView();
}
match = RegExp(
"^_Unmodifiable(Int|Uint|Float)(8|16|32|64|32x4|64x2)(Clamped)?ArrayView\$")
.matchAsPrefix(className);
if (match != null ||
member.enclosingClass == translator.unmodifiableByteDataViewClass) {
return _createView();
}
}
// _TypedListBase.length
// _TypedListView.offsetInBytes
// _TypedListView._typedData
// _ByteDataView.length
// _ByteDataView.offsetInBytes
// _ByteDataView._typedData
if (member.isGetter) {
Class cls = member.enclosingClass!;
ClassInfo info = translator.classInfo[cls]!;
b.local_get(paramLocals[0]);
b.ref_cast(info.nonNullableType);
// TODO(joshualitt): Because we currently merge getters to support
// dynamic calls, the return types of `.length` and `.offsetInBytes` can
// change. Should we decide to stop merging getters, we should remove
// the conversions below.
w.ValueType outputType = function.type.outputs.single;
switch (name) {
case "length":
assert(cls == translator.typedListBaseClass ||
cls == translator.byteDataViewClass);
if (cls == translator.typedListBaseClass) {
b.struct_get(info.struct, FieldIndex.typedListBaseLength);
} else {
b.struct_get(info.struct, FieldIndex.byteDataViewLength);
}
b.i64_extend_i32_u();
translator.convertType(function, intType, outputType);
return true;
case "offsetInBytes":
assert(cls == translator.typedListViewClass ||
cls == translator.byteDataViewClass);
if (cls == translator.typedListViewClass) {
b.struct_get(info.struct, FieldIndex.typedListViewOffsetInBytes);
} else {
b.struct_get(info.struct, FieldIndex.byteDataViewOffsetInBytes);
}
b.i64_extend_i32_u();
translator.convertType(function, intType, outputType);
return true;
case "_typedData":
assert(cls == translator.typedListViewClass ||
cls == translator.byteDataViewClass);
if (cls == translator.typedListViewClass) {
b.struct_get(info.struct, FieldIndex.typedListViewTypedData);
} else {
b.struct_get(info.struct, FieldIndex.byteDataViewTypedData);
}
return true;
}
throw "Unrecognized typed data getter: ${cls.name}.$name";
}
}
// _asyncBridge2
if (member.enclosingLibrary.name == "dart.async" &&
name == "_asyncBridge2") {
w.Local args = paramLocals[0];
w.Local stack = paramLocals[1];
const int stubFieldIndex = 0;
b.local_get(args);
b.local_get(stack);
b.local_get(args);
b.ref_cast(w.RefType.def(translator.functions.asyncStubBaseStruct,
nullable: false));
b.struct_get(translator.functions.asyncStubBaseStruct, stubFieldIndex);
b.call_ref(translator.functions.asyncStubFunctionType);
return true;
}
// int members
if (member.enclosingClass == translator.boxedIntClass &&
member.function.body == null) {
String op = member.name.text;
if (functionNode.requiredParameterCount == 0) {
CodeGenCallback? code = unaryOperatorMap[intType]![op];
if (code != null) {
w.ValueType resultType = unaryResultMap[op] ?? intType;
w.ValueType inputType = function.type.inputs.single;
w.ValueType outputType = function.type.outputs.single;
b.local_get(function.locals[0]);
translator.convertType(function, inputType, intType);
code(b);
translator.convertType(function, resultType, outputType);
return true;
}
} else if (functionNode.requiredParameterCount == 1) {
CodeGenCallback? code = binaryOperatorMap[intType]![intType]![op];
if (code != null) {
w.ValueType leftType = function.type.inputs[0];
w.ValueType rightType = function.type.inputs[1];
w.ValueType outputType = function.type.outputs.single;
if (rightType == intType) {
// int parameter
b.local_get(function.locals[0]);
translator.convertType(function, leftType, intType);
b.local_get(function.locals[1]);
code(b);
if (!isComparison(op)) {
translator.convertType(function, intType, outputType);
}
return true;
}
// num parameter
ClassInfo intInfo = translator.classInfo[translator.boxedIntClass]!;
w.Label intArg = b.block(const [], [intInfo.nonNullableType]);
b.local_get(function.locals[1]);
b.br_on_cast(intInfo.nonNullableType, intArg);
// double argument
b.drop();
b.local_get(function.locals[0]);
translator.convertType(function, leftType, intType);
b.f64_convert_i64_s();
b.local_get(function.locals[1]);
translator.convertType(function, rightType, doubleType);
// Inline double op
CodeGenCallback doubleCode =
binaryOperatorMap[doubleType]![doubleType]![op]!;
doubleCode(b);
if (!isComparison(op)) {
translator.convertType(function, doubleType, outputType);
}
b.return_();
b.end();
// int argument
translator.convertType(function, intInfo.nonNullableType, intType);
w.Local rightTemp = function.addLocal(intType);
b.local_set(rightTemp);
b.local_get(function.locals[0]);
translator.convertType(function, leftType, intType);
b.local_get(rightTemp);
code(b);
if (!isComparison(op)) {
translator.convertType(function, intType, outputType);
}
return true;
}
}
}
// double unary members
if (member.enclosingClass == translator.boxedDoubleClass &&
member.function.body == null) {
String op = member.name.text;
if (functionNode.requiredParameterCount == 0) {
CodeGenCallback? code = unaryOperatorMap[doubleType]![op];
if (code != null) {
w.ValueType resultType = unaryResultMap[op] ?? doubleType;
w.ValueType inputType = function.type.inputs.single;
w.ValueType outputType = function.type.outputs.single;
b.local_get(function.locals[0]);
translator.convertType(function, inputType, doubleType);
code(b);
translator.convertType(function, resultType, outputType);
return true;
}
}
}
if (member.enclosingClass == translator.closureClass && name == "_equals") {
// Function equality works like this:
//
// - Function literals and local functions are only equal if they're the
// same reference.
//
// - Instance tear-offs are equal if they are tear-offs of the same
// method on the same object.
//
// - Tear-offs of static methods and top-level functions are identical
// (and thus equal) when they are tear-offs of the same function. Generic
// instantiations of these are identical when the tear-offs are identical
// and they are instantiated with identical types.
//
// To distinguish a function literal or local function from an instance
// tear-off we check type of the context:
//
// - If context's type is a subtype of the top type for Dart objects then
// the function is a tear-off and we compare the context using the
// `identical` function.
//
// The reason why we use `identical` (instead of `ref.eq`) is to handle
// bool, double, and int receivers in code like `1.toString ==
// 1.toString`, which should evaluate to `true` even if the receivers
// do not point to the same Wasm object.
//
// - Otherwise the function is a function literal or local function.
//
// In pseudo code:
//
// bool _equals(f1, f2) {
// if (identical(f1, f2) return true;
//
// if (<f1 and f2 are instantiations>
// ? f1.context.inner.vtable != f2.context.inner.vtable
// : f1.vtable != f2.vtable) {
// return false;
// }
//
// if (<f1 and f2 are instantiations>) {
// if (typesEqual(f1.context, f2.context)) {
// f1 = f1.context.inner;
// f2 = f2.context.inner;
// if (identical(f1, f2)) return true;
// goto outerClosureContext;
// }
// return false;
// }
//
// outerClosureContext:
// if (f1.context is #Top && f2.context is #Top) {
// return identical(f1.context, f2.context);
// }
//
// return false;
// }
// Check if the arguments are the same
b.local_get(function.locals[0]);
b.local_get(function.locals[1]);
b.ref_eq();
b.if_();
b.i32_const(1); // true
b.return_();
b.end();
// Arguments are different, compare context and vtable references
final w.StructType closureBaseStruct =
translator.closureLayouter.closureBaseStruct;
final w.RefType closureBaseStructRef =
w.RefType.def(closureBaseStruct, nullable: false);
final w.Local fun1 = codeGen.function.addLocal(closureBaseStructRef);
b.local_get(function.locals[0]);
translator.convertType(
function, function.locals[0].type, closureBaseStructRef);
b.local_set(fun1);
final w.Local fun2 = codeGen.function.addLocal(closureBaseStructRef);
b.local_get(function.locals[1]);
translator.convertType(
function, function.locals[1].type, closureBaseStructRef);
b.local_set(fun2);
// Compare vtable references. For instantiation closures compare the
// inner vtables
final instantiationContextBase = w.RefType(
translator.closureLayouter.instantiationContextBaseStruct,
nullable: false);
final vtableRefType = w.RefType.def(
translator.closureLayouter.vtableBaseStruct,
nullable: false);
// Returns vtables of closures that we compare for equality.
final vtablesBlock = b.block([], [vtableRefType, vtableRefType]);
// `br` target when fun1 is not an instantiation
final fun1NotInstantiationBlock =
b.block([], [w.RefType.struct(nullable: false)]);
// `br` target when fun1 is an instantiation, but fun2 is not
final fun1InstantiationFun2NotInstantiationBlock =
b.block([], [w.RefType.struct(nullable: false)]);
b.local_get(fun1);
b.struct_get(closureBaseStruct, FieldIndex.closureContext);
b.br_on_cast_fail(instantiationContextBase, fun1NotInstantiationBlock);
b.struct_get(translator.closureLayouter.instantiationContextBaseStruct,
FieldIndex.instantiationContextInner);
b.struct_get(closureBaseStruct, FieldIndex.closureVtable);
b.local_get(fun2);
b.struct_get(closureBaseStruct, FieldIndex.closureContext);
b.br_on_cast_fail(
instantiationContextBase, fun1InstantiationFun2NotInstantiationBlock);
b.struct_get(translator.closureLayouter.instantiationContextBaseStruct,
FieldIndex.instantiationContextInner);
b.struct_get(closureBaseStruct, FieldIndex.closureVtable);
b.br(vtablesBlock);
b.end(); // fun1InstantiationFun2NotInstantiationBlock
b.i32_const(0); // false
b.return_();
b.end(); // fun1NotInstantiationBlock
b.drop();
b.local_get(fun1);
b.struct_get(closureBaseStruct, FieldIndex.closureVtable);
// To keep the generated code small and simple, instead of checking that
// fun2 is also not an instantiation, we can just return the outer
// (potentially instantiation) vtable here. In the rest of the code
// `ref.eq` will be `false` (as vtable of an instantiation and
// non-instantiation will never be equal) and the function will return
// `false` as expected.
b.local_get(fun2);
b.struct_get(closureBaseStruct, FieldIndex.closureVtable);
b.end(); // vtablesBlock
b.ref_eq();
b.if_(); // fun1.vtable == fun2.vtable
// Check if closures are instantiations. Since they have the same vtable
// it's enough to check just one of them.
final instantiationCheckPassedBlock = b.block();
final notInstantiationBlock =
b.block([], [w.RefType.struct(nullable: false)]);
b.local_get(fun1);
b.struct_get(closureBaseStruct, FieldIndex.closureContext);
b.br_on_cast_fail(instantiationContextBase, notInstantiationBlock);
// Closures are instantiations. Compare inner function vtables to check
// that instantiations are for the same generic function.
void getInstantiationContextInner(w.Local fun) {
b.local_get(fun);
// instantiation.context
b.struct_get(closureBaseStruct, FieldIndex.closureContext);
b.ref_cast(instantiationContextBase);
// instantiation.context.inner
b.struct_get(translator.closureLayouter.instantiationContextBaseStruct,
FieldIndex.instantiationContextInner);
}
// Closures are instantiations of the same function, compare types.
b.local_get(fun1);
b.struct_get(closureBaseStruct, FieldIndex.closureContext);
b.ref_cast(instantiationContextBase);
b.local_get(fun2);
b.struct_get(closureBaseStruct, FieldIndex.closureContext);
b.ref_cast(instantiationContextBase);
getInstantiationContextInner(fun1);
b.struct_get(closureBaseStruct, FieldIndex.closureVtable);
b.ref_cast(w.RefType.def(
translator.closureLayouter.genericVtableBaseStruct,
nullable: false));
b.struct_get(translator.closureLayouter.genericVtableBaseStruct,
FieldIndex.vtableInstantiationTypeComparisonFunction);
b.call_ref(translator
.closureLayouter.instantiationClosureTypeComparisonFunctionType);
b.if_();
getInstantiationContextInner(fun1);
b.local_tee(fun1);
getInstantiationContextInner(fun2);
b.local_tee(fun2);
b.ref_eq();
b.if_();
b.i32_const(1); // true
b.return_();
b.end();
b.br(instantiationCheckPassedBlock);
b.end();
b.i32_const(0); // false
b.return_();
b.i32_const(0); // false
b.return_();
b.end(); // notInstantiationBlock
b.drop();
b.end(); // instantiationCheckPassedBlock
// Compare context references. If context of a function has the top type
// then the function is an instance tear-off. Otherwise it's a closure.
final contextCheckFail = b.block([], [w.RefType.struct(nullable: false)]);
b.local_get(fun1);
b.struct_get(closureBaseStruct, FieldIndex.closureContext);
b.br_on_cast_fail(translator.topInfo.nonNullableType, contextCheckFail);
b.local_get(fun2);
b.struct_get(closureBaseStruct, FieldIndex.closureContext);
b.br_on_cast_fail(translator.topInfo.nonNullableType, contextCheckFail);
// Both contexts are objects, compare for equality with `identical`. This
// handles identical `this` values in instance tear-offs.
b.call(translator.functions
.getFunction(translator.coreTypes.identicalProcedure.reference));
b.return_();
b.end(); // contextCheckFail
b.i32_const(0); // false
b.return_();
b.end(); // fun1.vtable == fun2.vtable
b.i32_const(0); // false
return true;
}
if (member.enclosingClass == translator.coreTypes.functionClass &&
name == "apply") {
assert(function.type.inputs.length == 3);
final closureLocal = function.locals[0]; // ref #ClosureBase
final posArgsNullableLocal = function.locals[1]; // ref null Object,
final namedArgsLocal = function.locals[2]; // ref null Object
final listArgumentType =
translator.classInfo[translator.listBaseClass]!.nonNullableType;
// Create type argument list. It will be initialized as empty and it
// needs to be growable as `_checkClosureShape` updates it with default
// bounds if the function being invokes has type parameters.
final typeArgsLocal = function.addLocal(listArgumentType);
translator.makeList(function, (b) {
translator.constants.instantiateConstant(
function,
b,
TypeLiteralConstant(
InterfaceType(translator.typeClass, Nullability.nonNullable)),
translator.types.nonNullableTypeType);
}, 0, (elementType, elementIndex) {}, isGrowable: true);
b.local_set(typeArgsLocal);
// Create empty list for positional args if the argument is null
final posArgsLocal = function.addLocal(listArgumentType);
b.local_get(posArgsNullableLocal);
b.ref_is_null();
b.if_([], [listArgumentType]);
translator.constants.instantiateConstant(
function,
b,
ListConstant(
InterfaceType(translator.objectInfo.cls!, Nullability.nullable),
[]),
translator.objectInfo.nonNullableType);
b.else_();
// List argument may be a custom list type, convert it to `_ListBase`
// with `_List.of`.
translator.constants.instantiateConstant(
function,
b,
TypeLiteralConstant(DynamicType()),
translator.types.nonNullableTypeType,
);
b.local_get(posArgsNullableLocal);
b.ref_as_non_null();
b.call(translator.functions.getFunction(translator.listOf.reference));
b.end();
b.local_set(posArgsLocal);
// Convert named argument map to list, to be passed to shape and type
// checkers and the dynamic call entry.
final namedArgsListLocal = function.addLocal(listArgumentType);
b.local_get(namedArgsLocal);
b.call(translator.functions
.getFunction(translator.namedParameterMapToList.reference));
b.ref_cast(listArgumentType); // ref Object -> ref _ListBase
b.local_set(namedArgsListLocal);
final noSuchMethodBlock = b.block();
generateDynamicFunctionCall(translator, function, closureLocal,
typeArgsLocal, posArgsLocal, namedArgsListLocal, noSuchMethodBlock);
b.return_();
b.end(); // noSuchMethodBlock
generateNoSuchMethodCall(
translator,
function,
() => b.local_get(closureLocal),
() => createInvocationObject(translator, function, "call",
typeArgsLocal, posArgsLocal, namedArgsListLocal));
return true;
}
if (member.enclosingClass == translator.errorClass && name == "_throw") {
final objectLocal = function.locals[0]; // ref #Top
final stackTraceLocal = function.locals[1]; // ref Object
final notErrorBlock = b.block([], [objectLocal.type]);
final errorClassInfo = translator.classInfo[translator.errorClass]!;
final errorRefType = errorClassInfo.nonNullableType;
final stackTraceFieldIndex =
translator.fieldIndex[translator.errorClassStackTraceField]!;
b.local_get(objectLocal);
b.br_on_cast_fail(errorRefType, notErrorBlock);
// Binaryen can merge struct types, so we need to check class ID in the
// slow path
final errorLocal = function.addLocal(errorRefType);
b.local_tee(errorLocal);
final classIdLocal = function.addLocal(w.NumType.i32);
b.struct_get(translator.topInfo.struct, FieldIndex.classId);
b.local_set(classIdLocal);
final errorBlock = b.block();
bool isErrorClass(Class cls) =>
cls == translator.errorClass ||
(cls.superclass != null && isErrorClass(cls.superclass!));
for (ClassInfo classInfo in translator.classes) {
final Class? cls = classInfo.cls;
if (cls == null || !isErrorClass(cls)) {
continue;
}
b.local_get(classIdLocal);
b.i32_const(classInfo.classId);
b.i32_eq();
b.br_if(errorBlock);
}
b.local_get(errorLocal);
b.br(notErrorBlock);
b.end(); // errorBlock
b.local_get(errorLocal);
b.struct_get(errorClassInfo.struct, stackTraceFieldIndex);
b.ref_is_null();
b.if_();
b.local_get(errorLocal);
b.local_get(stackTraceLocal);
b.struct_set(errorClassInfo.struct, stackTraceFieldIndex);
b.end();
b.local_get(objectLocal);
b.end(); // notErrorBlock
b.local_get(stackTraceLocal);
b.throw_(translator.exceptionTag);
return true;
}
if (member.enclosingClass == translator.wasmExternRefClass &&
name == "nullRef") {
b.ref_null(w.HeapType.noextern);
return true;
}
return false;
}
}