diff --git a/pkg/dart2wasm/dart2wasm.md b/pkg/dart2wasm/dart2wasm.md index 0f9d65f9e7c..dd96ed20459 100644 --- a/pkg/dart2wasm/dart2wasm.md +++ b/pkg/dart2wasm/dart2wasm.md @@ -60,5 +60,5 @@ inst.exports.baz(2); In the signatures of imported and exported functions, use the following types: - For numbers, use `double`. -- For Dart objects, use the corresponding Dart type. The fields of the underlying representation can be accessed on the JS side as `.$field0`, `.$field1` etc., but there is currently no defined way of finding the field index of a particular Dart field, so this mechanism is mainly useful for special objects with known layout. -- For JS objects, use the `WasmAnyRef` type (or `WasmAnyRef?` as applicable) from the `dart:wasm` package. These can be passed around and stored as opaque values on the Dart side. +- For JS objects, use the `WasmExternRef?` type from the `dart:wasm` package, which translates to the Wasm `externref` type. These can be passed around and stored as opaque values on the Dart side. +- For Dart objects, use the corresponding Dart type. This will be emitted as `externref` and automatically converted to and from the Dart type at the boundary. diff --git a/pkg/dart2wasm/lib/code_generator.dart b/pkg/dart2wasm/lib/code_generator.dart index d96f90c1469..9a282f58245 100644 --- a/pkg/dart2wasm/lib/code_generator.dart +++ b/pkg/dart2wasm/lib/code_generator.dart @@ -333,6 +333,22 @@ class CodeGenerator extends ExpressionVisitor1 typeLocals[typeParameters[i]] = paramLocals[typeParameterOffset + i]; } + // For all parameters whose Wasm type has been forced to `externref` due to + // this function being an export, internalize and cast the parameter to the + // canonical representation type for its Dart type. + locals.forEach((parameter, local) { + DartType parameterType = parameter.type; + if (local.type == w.RefType.extern(nullable: true) && + !(parameterType is InterfaceType && + parameterType.classNode == translator.wasmExternRefClass)) { + w.Local newLocal = addLocal(translateType(parameterType)); + b.local_get(local); + translator.convertType(function, local.type, newLocal.type); + b.local_set(newLocal); + locals[parameter] = newLocal; + } + }); + closures.findCaptures(member); if (hasThis) { @@ -1408,7 +1424,7 @@ class CodeGenerator extends ExpressionVisitor1 @override w.ValueType visitEqualsNull(EqualsNull node, w.ValueType expectedType) { - wrap(node.expression, const w.RefType.top(nullable: true)); + wrap(node.expression, const w.RefType.any(nullable: true)); b.ref_is_null(); return w.NumType.i32; } diff --git a/pkg/dart2wasm/lib/functions.dart b/pkg/dart2wasm/lib/functions.dart index 10bdcd588d3..dfe61897432 100644 --- a/pkg/dart2wasm/lib/functions.dart +++ b/pkg/dart2wasm/lib/functions.dart @@ -219,12 +219,14 @@ class FunctionCollector extends MemberVisitor1 { List typeParameters = List.filled(typeParamCount, translator.classInfo[translator.typeClass]!.nonNullableType); - // The JS embedder will not accept Wasm struct types as parameter or return - // types for functions called from JS. We need to use eqref instead. + // The only reference types allowed as parameters and returns on imported + // or exported functions for JS interop are `externref` and `funcref`. w.ValueType adjustExternalType(w.ValueType type) { - if (isImportOrExport && - type.isSubtypeOf(translator.topInfo.nullableType)) { - return w.RefType.eq(nullable: type.nullable); + if (isImportOrExport && type is w.RefType) { + if (type.heapType.isSubtypeOf(w.HeapType.func)) { + return w.RefType.func(nullable: true); + } + return w.RefType.extern(nullable: true); } return type; } diff --git a/pkg/dart2wasm/lib/translator.dart b/pkg/dart2wasm/lib/translator.dart index a03e359cb84..d1b1ef82806 100644 --- a/pkg/dart2wasm/lib/translator.dart +++ b/pkg/dart2wasm/lib/translator.dart @@ -739,8 +739,8 @@ class Translator { } if (to != voidMarker) { if (to is w.RefType && to.nullable) { - // This can happen when a void method has its return type overridden to - // return a value, in which case the selector signature will have a + // This can happen when a void method has its return type overridden + // to return a value, in which case the selector signature will have a // non-void return type to encompass all possible return values. b.ref_null(to.heapType); } else { @@ -752,6 +752,17 @@ class Translator { return; } } + + bool fromIsExtern = from.isSubtypeOf(w.RefType.extern(nullable: true)); + bool toIsExtern = to.isSubtypeOf(w.RefType.extern(nullable: true)); + if (fromIsExtern && !toIsExtern) { + b.extern_internalize(); + from = w.RefType.any(nullable: from.nullable); + } + if (!fromIsExtern && toIsExtern) { + to = w.RefType.any(nullable: to.nullable); + } + if (!from.isSubtypeOf(to)) { if (from is! w.RefType && to is w.RefType) { // Boxing @@ -812,6 +823,10 @@ class Translator { } } } + + if (!fromIsExtern && toIsExtern) { + b.extern_externalize(); + } } w.FunctionType signatureFor(Reference target) { diff --git a/sdk/lib/_internal/wasm/lib/async_patch.dart b/sdk/lib/_internal/wasm/lib/async_patch.dart index e9ed97eaf70..b9e3dfb6a2a 100644 --- a/sdk/lib/_internal/wasm/lib/async_patch.dart +++ b/sdk/lib/_internal/wasm/lib/async_patch.dart @@ -53,20 +53,18 @@ import 'dart:_internal' show patch, scheduleCallback, unsafeCastOpaque; import 'dart:wasm'; @pragma("wasm:entry-point") -Future _asyncHelper(WasmEqRef args) { +Future _asyncHelper(WasmDataRef args) { Completer completer = Completer(); _callAsyncBridge(args, completer); return completer.future; } @pragma("wasm:import", "dart2wasm.callAsyncBridge") -external void _callAsyncBridge(WasmEqRef args, Completer completer); +external void _callAsyncBridge(WasmDataRef args, Completer completer); @pragma("wasm:export", "\$asyncBridge") WasmAnyRef? _asyncBridge( - WasmExternRef? stack, WasmExternRef? argsRef, WasmExternRef? completerRef) { - WasmDataRef args = unsafeCastOpaque(argsRef!.internalize()); - Completer completer = unsafeCastOpaque(completerRef!.internalize()); + WasmExternRef? stack, WasmDataRef args, Completer completer) { try { Object? result = _asyncBridge2(args, stack); completer.complete(result); @@ -87,9 +85,7 @@ class _FutureError { @pragma("wasm:entry-point") Object? _awaitHelper(Object? operand, WasmExternRef? stack) { if (operand is! Future) return operand; - WasmExternRef futureRef = WasmAnyRef.fromObject(operand).externalize(); - Object? result = - unsafeCastOpaque(_futurePromise(stack, futureRef).internalize()); + Object? result = _futurePromise(stack, operand); if (result is _FutureError) { // TODO(askesc): Combine stack traces throw result.exception; @@ -98,11 +94,10 @@ Object? _awaitHelper(Object? operand, WasmExternRef? stack) { } @pragma("wasm:import", "dart2wasm.futurePromise") -external WasmExternRef? _futurePromise( - WasmExternRef? stack, WasmExternRef? future); +external Object? _futurePromise(WasmExternRef? stack, Future future); @pragma("wasm:export", "\$awaitCallback") -void _awaitCallback(Future future, WasmAnyRef resolve) { +void _awaitCallback(Future future, WasmExternRef? resolve) { future.then((value) { _callResolve(resolve, value); }, onError: (exception, stackTrace) { @@ -111,4 +106,4 @@ void _awaitCallback(Future future, WasmAnyRef resolve) { } @pragma("wasm:import", "dart2wasm.callResolve") -external void _callResolve(WasmAnyRef resolve, Object? result); +external void _callResolve(WasmExternRef? resolve, Object? result); diff --git a/tests/web/wasm/wasm_types_test.dart b/tests/web/wasm/wasm_types_test.dart index 0ce54a34b58..1f1da18b621 100644 --- a/tests/web/wasm/wasm_types_test.dart +++ b/tests/web/wasm/wasm_types_test.dart @@ -7,14 +7,14 @@ import 'dart:wasm'; import 'package:expect/expect.dart'; @pragma("wasm:import", "Object.create") -external WasmAnyRef createObject(WasmAnyRef? prototype); +external WasmExternRef? createObject(WasmExternRef? prototype); @pragma("wasm:import", "Array.of") -external WasmAnyRef singularArray(WasmAnyRef element); +external WasmExternRef? singularArray(WasmExternRef? element); @pragma("wasm:import", "Reflect.apply") -external WasmAnyRef apply( - WasmFuncRef target, WasmAnyRef thisArgument, WasmAnyRef argumentsList); +external WasmExternRef? apply(WasmFuncRef? target, WasmExternRef? thisArgument, + WasmExternRef? argumentsList); WasmAnyRef? anyRef; WasmEqRef? eqRef; @@ -32,7 +32,7 @@ test() { Object dartObject1 = "1"; Object dartObject2 = true; Object dartObject3 = Object(); - WasmAnyRef jsObject1 = createObject(null); + WasmAnyRef jsObject1 = createObject(null)!.internalize(); // A JS object is not a Dart object. Expect.isFalse(jsObject1.isObject); @@ -77,7 +77,7 @@ test() { var dartObjectRef = WasmEqRef.fromObject("Dart object"); var ff = WasmFunction.fromFunction(fun); ff.call(dartObjectRef); - apply(ff, createObject(null), singularArray(dartObjectRef)); + apply(ff, createObject(null), singularArray(dartObjectRef.externalize())); // Cast a typed function reference to a `funcref` and back. WasmFuncRef funcref = WasmFuncRef.fromWasmFunction(ff); @@ -86,7 +86,7 @@ test() { // Create a typed function reference from an import and call it. var createObjectFun = WasmFunction.fromFunction(createObject); - WasmAnyRef jsObject3 = createObjectFun.call(null); + WasmAnyRef jsObject3 = createObjectFun.call(null).internalize()!; Expect.isFalse(jsObject3.isObject); Expect.equals(3, funCount);