[dart2wasm] Coerce types on imports/exports to externref or funcref

Change-Id: Id7fe0775e14b7ed16c925819ffc9c41dea863e4d
Cq-Include-Trybots: luci.dart.try:dart2wasm-linux-x64-d8-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/258001
Auto-Submit: Aske Simon Christensen <askesc@google.com>
Reviewed-by: Joshua Litt <joshualitt@google.com>
Commit-Queue: Joshua Litt <joshualitt@google.com>
This commit is contained in:
Aske Simon Christensen 2022-09-09 18:10:34 +00:00 committed by Commit Bot
parent 7055da30a7
commit 9af959c167
6 changed files with 57 additions and 29 deletions

View file

@ -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.

View file

@ -333,6 +333,22 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
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<w.ValueType, w.ValueType>
@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;
}

View file

@ -219,12 +219,14 @@ class FunctionCollector extends MemberVisitor1<w.FunctionType, Reference> {
List<w.ValueType> 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;
}

View file

@ -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) {

View file

@ -53,20 +53,18 @@ import 'dart:_internal' show patch, scheduleCallback, unsafeCastOpaque;
import 'dart:wasm';
@pragma("wasm:entry-point")
Future<T> _asyncHelper<T>(WasmEqRef args) {
Future<T> _asyncHelper<T>(WasmDataRef args) {
Completer<T> completer = Completer();
_callAsyncBridge(args, completer);
return completer.future;
}
@pragma("wasm:import", "dart2wasm.callAsyncBridge")
external void _callAsyncBridge(WasmEqRef args, Completer<Object?> completer);
external void _callAsyncBridge(WasmDataRef args, Completer<Object?> completer);
@pragma("wasm:export", "\$asyncBridge")
WasmAnyRef? _asyncBridge(
WasmExternRef? stack, WasmExternRef? argsRef, WasmExternRef? completerRef) {
WasmDataRef args = unsafeCastOpaque(argsRef!.internalize());
Completer<Object?> completer = unsafeCastOpaque(completerRef!.internalize());
WasmExternRef? stack, WasmDataRef args, Completer<Object?> 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<Object?> future);
@pragma("wasm:export", "\$awaitCallback")
void _awaitCallback(Future<Object?> future, WasmAnyRef resolve) {
void _awaitCallback(Future<Object?> future, WasmExternRef? resolve) {
future.then((value) {
_callResolve(resolve, value);
}, onError: (exception, stackTrace) {
@ -111,4 +106,4 @@ void _awaitCallback(Future<Object?> future, WasmAnyRef resolve) {
}
@pragma("wasm:import", "dart2wasm.callResolve")
external void _callResolve(WasmAnyRef resolve, Object? result);
external void _callResolve(WasmExternRef? resolve, Object? result);

View file

@ -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);