diff --git a/pkg/dart2wasm/lib/code_generator.dart b/pkg/dart2wasm/lib/code_generator.dart index f086339148e..d12efbd1ba7 100644 --- a/pkg/dart2wasm/lib/code_generator.dart +++ b/pkg/dart2wasm/lib/code_generator.dart @@ -388,6 +388,17 @@ class CodeGenerator extends ExpressionVisitor1 }); } + /// 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 @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( diff --git a/pkg/dart2wasm/lib/intrinsics.dart b/pkg/dart2wasm/lib/intrinsics.dart index 0fa93293ace..7fb0cf5f898 100644 --- a/pkg/dart2wasm/lib/intrinsics.dart +++ b/pkg/dart2wasm/lib/intrinsics.dart @@ -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; } } diff --git a/pkg/dart2wasm/lib/translator.dart b/pkg/dart2wasm/lib/translator.dart index 1eacf817087..2a61d3c1831 100644 --- a/pkg/dart2wasm/lib/translator.dart +++ b/pkg/dart2wasm/lib/translator.dart @@ -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 functionTypeCache = {}; final Map functionTypeParameterCount = {}; final Map functionTypeRtt = {}; - final Map functionRefCache = {}; + final Map functionRefCache = {}; final Map 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 inputs = [ + for (DartType type in functionType.positionalParameters) + translateType(type) + ]; + List 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; } } diff --git a/pkg/wasm_builder/lib/src/instructions.dart b/pkg/wasm_builder/lib/src/instructions.dart index 10df25a0082..6c04e05717c 100644 --- a/pkg/wasm_builder/lib/src/instructions.dart +++ b/pkg/wasm_builder/lib/src/instructions.dart @@ -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); diff --git a/sdk/lib/_internal/wasm/lib/errors_patch.dart b/sdk/lib/_internal/wasm/lib/errors_patch.dart index d560ac0ddfe..666f279ad4f 100644 --- a/sdk/lib/_internal/wasm/lib/errors_patch.dart +++ b/sdk/lib/_internal/wasm/lib/errors_patch.dart @@ -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); + } } diff --git a/sdk/lib/wasm/wasm_types.dart b/sdk/lib/wasm/wasm_types.dart index 288deaa92a9..ea09211c4f9 100644 --- a/sdk/lib/wasm/wasm_types.dart +++ b/sdk/lib/wasm/wasm_types.dart @@ -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 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 extends _WasmArray { external void write(int index, T value); } +/// Wasm typed function reference. +@pragma("wasm:entry-point") +class WasmFunction 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); diff --git a/tests/web/wasm/wasm_types_test.dart b/tests/web/wasm/wasm_types_test.dart new file mode 100644 index 00000000000..553860588ff --- /dev/null +++ b/tests/web/wasm/wasm_types_test.dart @@ -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.fromRef(funcref); + ff2.call(dartObjectRef); + Expect.isFalse(ff2.isObject); + + // Casting a non-function JS object to a typed function reference throws. + Expect.throws(() { + WasmFunction.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; + } +}