[dart2wasm] Add Wasm function references and more Wasm ref conversions

Having first-class Wasm function references makes it possible to call
JS function objects directly from Dart, and to call some Dart functions
(static functions with no optional parameters and no type parameters)
from JS as function objects.

Change-Id: I1c788338d418c8857493ec76560d74fdd17d5dd2
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/241001
Reviewed-by: Joshua Litt <joshualitt@google.com>
Reviewed-by: Jackson Gardner <jacksongardner@google.com>
Commit-Queue: Aske Simon Christensen <askesc@google.com>
This commit is contained in:
Aske Simon Christensen
2022-04-19 09:40:17 +00:00
committed by Commit Bot
parent bf2c56afbf
commit fec466e3fe
7 changed files with 357 additions and 55 deletions
+31 -1
View File
@@ -388,6 +388,17 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
});
}
/// Helper function to throw a Wasm ref downcast error.
void throwWasmRefError(String expected) {
wrap(
StringLiteral(expected),
translator
.translateType(translator.coreTypes.stringNonNullableRawType));
_call(translator.stackTraceCurrent.reference);
_call(translator.throwWasmRefError.reference);
b.unreachable();
}
/// Generates code for an expression plus conversion code to convert the
/// result to the expected type if needed. All expression code generation goes
/// through this method.
@@ -1660,9 +1671,28 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
@override
w.ValueType visitFunctionInvocation(
FunctionInvocation node, w.ValueType expectedType) {
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 = addLocal(receiverType);
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++) {
wrap(node.arguments.positional[i], functionType.inputs[i]);
}
b.local_get(temp);
b.call_ref();
return translator.outputOrVoid(functionType.outputs);
}
int parameterCount = node.functionType?.requiredParameterCount ??
node.arguments.positional.length;
return _functionCall(parameterCount, node.receiver, node.arguments);
return _functionCall(parameterCount, receiver, node.arguments);
}
w.ValueType _functionCall(
+106 -41
View File
@@ -118,28 +118,45 @@ class Intrinsifier {
Intrinsifier(this.codeGen);
w.ValueType? generateInstanceGetterIntrinsic(InstanceGet node) {
DartType receiverType = dartTypeOf(node.receiver);
Expression receiver = node.receiver;
DartType receiverType = dartTypeOf(receiver);
String name = node.name.text;
Member target = node.interfaceTarget;
Class cls = target.enclosingClass!;
// WasmAnyRef.isObject
if (cls == translator.wasmAnyRefClass) {
assert(name == "isObject");
w.Label succeed = b.block(const [], const [w.NumType.i32]);
w.Label fail = b.block(const [], const [w.RefType.any(nullable: false)]);
codeGen.wrap(receiver, w.RefType.any(nullable: false));
b.br_on_non_data(fail);
translator.ref_test(b, translator.topInfo);
b.br(succeed);
b.end(); // fail
b.drop();
b.i32_const(0);
b.end(); // succeed
return w.NumType.i32;
}
// _WasmArray.length
if (node.interfaceTarget.enclosingClass == translator.wasmArrayBaseClass) {
if (cls == translator.wasmArrayBaseClass) {
assert(name == 'length');
DartType elementType =
(receiverType as InterfaceType).typeArguments.single;
w.ArrayType arrayType = translator.arrayTypeForDartType(elementType);
Expression array = node.receiver;
codeGen.wrap(array, w.RefType.def(arrayType, nullable: true));
codeGen.wrap(receiver, w.RefType.def(arrayType, nullable: true));
b.array_len(arrayType);
b.i64_extend_i32_u();
return w.NumType.i64;
}
// int.bitlength
if (node.interfaceTarget.enclosingClass == translator.coreTypes.intClass &&
name == 'bitLength') {
if (cls == translator.coreTypes.intClass && name == 'bitLength') {
w.Local temp = codeGen.function.addLocal(w.NumType.i64);
b.i64_const(64);
codeGen.wrap(node.receiver, w.NumType.i64);
codeGen.wrap(receiver, w.NumType.i64);
b.local_tee(temp);
b.local_get(temp);
b.i64_const(63);
@@ -151,28 +168,26 @@ class Intrinsifier {
}
// _HashAbstractImmutableBase._indexNullable
if (node.interfaceTarget == translator.immutableMapIndexNullable) {
if (target == translator.immutableMapIndexNullable) {
ClassInfo info = translator.classInfo[translator.hashFieldBaseClass]!;
codeGen.wrap(node.receiver, info.nullableType);
codeGen.wrap(receiver, info.nullableType);
b.struct_get(info.struct, FieldIndex.hashBaseIndex);
return info.struct.fields[FieldIndex.hashBaseIndex].type.unpacked;
}
// _Compound._typedDataBase
if (node.interfaceTarget.enclosingClass == translator.ffiCompoundClass &&
name == '_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(node.receiver, w.NumType.i32);
codeGen.wrap(receiver, w.NumType.i32);
return w.NumType.i32;
}
// Pointer.address
if (node.interfaceTarget.enclosingClass == translator.ffiPointerClass &&
name == 'address') {
if (cls == translator.ffiPointerClass && name == 'address') {
// A Pointer is represented by its i32 address.
codeGen.wrap(node.receiver, w.NumType.i32);
codeGen.wrap(receiver, w.NumType.i32);
b.i64_extend_i32_u();
return w.NumType.i64;
}
@@ -185,17 +200,17 @@ class Intrinsifier {
DartType receiverType = dartTypeOf(receiver);
String name = node.name.text;
Procedure target = node.interfaceTarget;
Class cls = target.enclosingClass!;
// _TypedListBase._setRange
if (target.enclosingClass == translator.typedListBaseClass &&
name == "_setRange") {
if (cls == translator.typedListBaseClass && name == "_setRange") {
// Always fall back to alternative implementation.
b.i32_const(0);
return w.NumType.i32;
}
// _TypedList._(get|set)(Int|Uint|Float)(8|16|32|64)
if (node.interfaceTarget.enclosingClass == translator.typedListClass) {
if (cls == translator.typedListClass) {
Match? match = RegExp("^_(get|set)(Int|Uint|Float)(8|16|32|64)\$")
.matchAsPrefix(name);
if (match != null) {
@@ -319,11 +334,24 @@ class Intrinsifier {
}
}
// WasmAnyRef.toObject
if (cls == translator.wasmAnyRefClass) {
assert(name == "toObject");
w.Label succeed = b.block(const [], [translator.topInfo.nonNullableType]);
w.Label fail = b.block(const [], const [w.RefType.any(nullable: false)]);
codeGen.wrap(receiver, w.RefType.any(nullable: false));
b.br_on_non_data(fail);
translator.br_on_cast(b, succeed, translator.topInfo);
b.end(); // fail
codeGen.throwWasmRefError("a Dart object");
b.end(); // succeed
return translator.topInfo.nonNullableType;
}
// WasmIntArray.(readSigned|readUnsigned|write)
// WasmFloatArray.(read|write)
// WasmObjectArray.(read|write)
if (node.interfaceTarget.enclosingClass?.superclass ==
translator.wasmArrayBaseClass) {
if (cls.superclass == translator.wasmArrayBaseClass) {
DartType elementType =
(receiverType as InterfaceType).typeArguments.single;
w.ArrayType arrayType = translator.arrayTypeForDartType(elementType);
@@ -388,10 +416,8 @@ class Intrinsifier {
}
// Wasm(I32|I64|F32|F64) conversions
if (node.interfaceTarget.enclosingClass?.superclass?.superclass ==
translator.wasmTypesBaseClass) {
w.StorageType receiverType =
translator.builtinTypes[node.interfaceTarget.enclosingClass]!;
if (cls.superclass?.superclass == translator.wasmTypesBaseClass) {
w.StorageType receiverType = translator.builtinTypes[cls]!;
switch (receiverType) {
case w.NumType.i32:
assert(name == "toIntSigned" || name == "toIntUnsigned");
@@ -557,6 +583,7 @@ class Intrinsifier {
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) {
@@ -710,7 +737,7 @@ class Intrinsifier {
b.f64_reinterpret_i64();
return w.NumType.f64;
case "getID":
assert(node.target.enclosingClass?.name == "ClassID");
assert(cls?.name == "ClassID");
ClassInfo info = translator.topInfo;
codeGen.wrap(node.arguments.positional.single, info.nullableType);
b.struct_get(info.struct, FieldIndex.classId);
@@ -836,24 +863,58 @@ class Intrinsifier {
}
}
// Wasm(Int|Float|Object)Array constructors
if (node.target.enclosingClass?.superclass ==
translator.wasmArrayBaseClass) {
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();
translator.array_new_default(b, arrayType);
return w.RefType.def(arrayType, nullable: false);
}
if (cls != null && translator.isWasmType(cls)) {
// Wasm(Int|Float|Object)Array constructors
if (cls.superclass == translator.wasmArrayBaseClass) {
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();
translator.array_new_default(b, arrayType);
return w.RefType.def(arrayType, nullable: false);
}
// Wasm(I32|I64|F32|F64) constructors
if (node.target.enclosingClass?.superclass?.superclass ==
translator.wasmTypesBaseClass) {
// (WasmFuncRef|WasmFunction).fromRef constructors
if ((cls == translator.wasmFuncRefClass ||
cls == translator.wasmFunctionClass) &&
name == "fromRef") {
Expression ref = node.arguments.positional[0];
w.RefType resultType = typeOfExp(node) as w.RefType;
w.Label succeed = b.block(const [], [resultType]);
w.Label fail =
b.block(const [], const [w.RefType.any(nullable: false)]);
codeGen.wrap(ref, w.RefType.any(nullable: false));
b.br_on_non_func(fail);
if (cls == translator.wasmFunctionClass) {
assert(resultType.heapType is w.FunctionType);
translator.br_on_cast_fail(b, fail, resultType.heapType);
}
b.br(succeed);
b.end(); // fail
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|DataRef|I32|I64|F32|F64) constructors
Expression value = node.arguments.positional[0];
w.StorageType targetType =
translator.builtinTypes[node.target.enclosingClass]!;
w.StorageType targetType = translator.builtinTypes[cls]!;
switch (targetType) {
case w.NumType.i32:
codeGen.wrap(value, w.NumType.i64);
@@ -869,6 +930,10 @@ class Intrinsifier {
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;
}
}
+47 -9
View File
@@ -67,8 +67,10 @@ class Translator {
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;
@@ -91,11 +93,13 @@ class Translator {
late final Class typedListViewClass;
late final Class byteDataViewClass;
late final Class typeErrorClass;
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 immutableMapIndexNullable;
@@ -130,7 +134,7 @@ class Translator {
final Map<int, w.StructType> functionTypeCache = {};
final Map<w.StructType, int> functionTypeParameterCount = {};
final Map<int, w.DefinedGlobal> functionTypeRtt = {};
final Map<w.DefinedFunction, w.DefinedGlobal> functionRefCache = {};
final Map<w.BaseFunction, w.DefinedGlobal> functionRefCache = {};
final Map<Procedure, w.DefinedFunction> tearOffFunctionCache = {};
ClassInfo get topInfo => classes[0];
@@ -161,8 +165,10 @@ class Translator {
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");
@@ -185,6 +191,8 @@ class Translator {
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 =
@@ -195,6 +203,8 @@ class Translator {
.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")
@@ -208,9 +218,10 @@ class Translator {
coreTypes.boolClass: w.NumType.i32,
coreTypes.intClass: w.NumType.i64,
coreTypes.doubleClass: w.NumType.f64,
wasmAnyRefClass: w.RefType.any(nullable: false),
wasmEqRefClass: w.RefType.eq(nullable: false),
wasmDataRefClass: w.RefType.data(nullable: false),
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,
@@ -421,6 +432,28 @@ class Translator {
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);
}
@@ -498,7 +531,7 @@ class Translator {
return functionTypeParameterCount[heapType]!;
}
w.DefinedGlobal makeFunctionRef(w.DefinedFunction f) {
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));
@@ -605,11 +638,16 @@ class Translator {
b.ref_as_non_null();
} else {
// Downcast
var heapType = (to as w.RefType).heapType;
ClassInfo? info = classForHeapType[heapType];
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();
}
@@ -711,7 +749,7 @@ class Translator {
// 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.DataType] (to use the canonical RTT for the type).
// [w.DefType] (to use the canonical RTT for the type).
void struct_new(w.Instructions b, Object type) {
if (options.runtimeTypes) {
@@ -823,7 +861,7 @@ class Translator {
}
return struct;
} else {
b.rtt_canon(type as w.DataType);
b.rtt_canon(type as w.DefType);
return type;
}
}
+2 -1
View File
@@ -1234,7 +1234,8 @@ class Instructions with SerializerMixin {
/// Emit a `br_on_cast_static_fail` instruction.
void br_on_cast_static_fail(Label label, DefType targetType) {
assert(_verifyBranchTypes(label, 1, [_topOfStack]));
assert(_verifyCast((inputs) => [RefType.def(targetType, nullable: false)],
assert(_verifyCastStatic(
(inputs) => [RefType.def(targetType, nullable: false)],
trace: ['br_on_cast_static_fail', label, targetType]));
writeBytes(const [0xFB, 0x47]);
_writeLabel(label);
@@ -65,4 +65,11 @@ class _TypeError extends _Error implements TypeError {
stackTrace);
return _throwObjectWithStackTrace(typeError, stackTrace);
}
@pragma("wasm:entry-point")
static Never _throwWasmRefError(String expected, StackTrace stackTrace) {
final typeError = _TypeError.fromMessageAndStackTrace(
"The Wasm reference is not $expected", stackTrace);
return _throwObjectWithStackTrace(typeError, stackTrace);
}
}
+54 -3
View File
@@ -23,17 +23,49 @@ abstract class _WasmFloat extends _WasmBase {}
/// The Wasm `anyref` type.
@pragma("wasm:entry-point")
class WasmAnyRef extends _WasmBase {}
class WasmAnyRef extends _WasmBase {
/// Upcast Dart object to `anyref`.
external factory WasmAnyRef.fromObject(Object o);
/// Whether this reference is a Dart object.
external bool get isObject;
/// Downcast `anyref` to a Dart object.
///
/// Will throw if the reference is not a Dart object.
external Object toObject();
}
/// The Wasm `funcref` type.
@pragma("wasm:entry-point")
class WasmFuncRef extends WasmAnyRef {
/// Upcast typed function reference to `funcref`
external factory WasmFuncRef.fromWasmFunction(WasmFunction<Function> fun);
/// Downcast `anyref` to `funcref`.
///
/// Will throw if the reference is not a `funcref`.
external factory WasmFuncRef.fromRef(WasmAnyRef ref);
}
/// The Wasm `eqref` type.
@pragma("wasm:entry-point")
class WasmEqRef extends WasmAnyRef {}
class WasmEqRef extends WasmAnyRef {
/// Upcast Dart object to `eqref`.
external factory WasmEqRef.fromObject(Object o);
}
/// The Wasm `dataref` type.
@pragma("wasm:entry-point")
class WasmDataRef extends WasmEqRef {}
class WasmDataRef extends WasmEqRef {
/// Upcast Dart object to `dataref`.
external factory WasmDataRef.fromObject(Object o);
}
abstract class _WasmArray extends WasmDataRef {
/// Dummy factory to silence error about missing superclass constructor.
external factory _WasmArray._dummy();
external int get length;
}
@@ -102,6 +134,25 @@ class WasmObjectArray<T extends Object?> extends _WasmArray {
external void write(int index, T value);
}
/// Wasm typed function reference.
@pragma("wasm:entry-point")
class WasmFunction<F extends Function> extends WasmFuncRef {
/// Create a typed function reference referring to the given function.
///
/// The argument must directly name a static function with no optional
/// parameters and no type parameters.
external factory WasmFunction.fromFunction(F f);
/// Downcast `anyref` to a typed function reference.
///
/// Will throw if the reference is not a function with the expected signature.
external factory WasmFunction.fromRef(WasmAnyRef ref);
/// Call the function referred to by this typed function reference.
@pragma("wasm:entry-point")
external F get call;
}
extension IntToWasmInt on int {
WasmI32 toWasmI32() => WasmI32.fromInt(this);
WasmI64 toWasmI64() => WasmI64.fromInt(this);
+110
View File
@@ -0,0 +1,110 @@
// 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:wasm';
import 'package:expect/expect.dart';
@pragma("wasm:import", "Object.create")
external WasmAnyRef createObject(WasmAnyRef? prototype);
@pragma("wasm:import", "Array.of")
external WasmAnyRef singularArray(WasmAnyRef element);
@pragma("wasm:import", "Reflect.apply")
external WasmAnyRef apply(
WasmAnyRef target, WasmAnyRef thisArgument, WasmAnyRef argumentsList);
WasmAnyRef? anyRef;
WasmEqRef? eqRef;
WasmDataRef? dataRef;
int funCount = 0;
void fun(WasmEqRef arg) {
funCount++;
Expect.equals("Dart object", arg.toObject());
}
test() {
// Some test objects
Object dartObject1 = "1";
Object dartObject2 = true;
Object dartObject3 = Object();
WasmAnyRef jsObject1 = createObject(null);
// A JS object is not a Dart object.
Expect.isFalse(jsObject1.isObject);
// A Wasm ref can be null and can be checked for null.
WasmAnyRef? jsObject2 = null;
Expect.isTrue(jsObject2 == null);
// Upcast Dart objects to Wasm refs and put them in fields.
anyRef = WasmAnyRef.fromObject(dartObject1);
eqRef = WasmEqRef.fromObject(dartObject2);
dataRef = WasmDataRef.fromObject(dartObject3);
// Dart objects are Dart objects.
Expect.isTrue(anyRef!.isObject);
Expect.isTrue(eqRef!.isObject);
Expect.isTrue(dataRef!.isObject);
// Casting back yields the original objects.
Expect.identical(dartObject1, anyRef!.toObject());
Expect.identical(dartObject2, eqRef!.toObject());
Expect.identical(dartObject3, dataRef!.toObject());
// Casting a JS object to a Dart object throws.
Object o;
Expect.throws(() {
o = jsObject1.toObject();
}, (_) => true);
// Integer and float conversions
Expect.equals(1, 1.toWasmI32().toIntSigned());
Expect.equals(-2, (-2).toWasmI32().toIntSigned());
Expect.equals(3, 3.toWasmI32().toIntUnsigned());
Expect.notEquals(-4, (-4).toWasmI32().toIntUnsigned());
Expect.equals(5, 5.toWasmI64().toInt());
Expect.equals(6.0, 6.0.toWasmF32().toDouble());
Expect.notEquals(7.1, 7.1.toWasmF32().toDouble());
Expect.equals(8.0, 8.0.toWasmF64().toDouble());
// Create a typed function reference for a Dart function and call it, both
// directly and from JS.
var dartObjectRef = WasmEqRef.fromObject("Dart object");
var ff = WasmFunction.fromFunction(fun);
ff.call(dartObjectRef);
apply(ff, createObject(null), singularArray(dartObjectRef));
Expect.isFalse(ff.isObject);
// Cast a typed function reference to a `funcref` and back.
WasmFuncRef funcref = WasmFuncRef.fromWasmFunction(ff);
var ff2 = WasmFunction<void Function(WasmEqRef)>.fromRef(funcref);
ff2.call(dartObjectRef);
Expect.isFalse(ff2.isObject);
// Casting a non-function JS object to a typed function reference throws.
Expect.throws(() {
WasmFunction<double Function(double)>.fromRef(jsObject1);
}, (_) => true);
// Create a typed function reference from an import and call it.
var createObjectFun = WasmFunction.fromFunction(createObject);
WasmAnyRef jsObject3 = createObjectFun.call(null);
Expect.isFalse(jsObject3.isObject);
Expect.equals(3, funCount);
}
main() {
try {
test();
} catch (e, s) {
print(e);
print(s);
rethrow;
}
}