Interaction between `WebAssembly.Suspending` and `WebAssembly.Function`?
When trying to put a suspending function into a WebAssembly.Table, I found that in the current v8 implementation, WebAssembly.Function removes the suspending. I made a v8 bug report here with a full reproduction.
const suspendingF = new WebAssembly.Suspending(x => x);
const wasmFuncF = new WebAssembly.Function({parameters: ["externref"], results: ["externref"]}, suspendingF);
const wasmTable = new WebAssembly.Table({element: "funcref", initial: 1});
wasmTable.set(0, wasmFuncF);
async function asyncFunc() {
return 7;
}
function jslog(val) {
console.log("jslog", val);
}
And then have a wasm module that does:
(func
(call $asyncFunc)
(i32.const 0)
(call_indirect $table (type $indirect_call))
(call $jslog)
)
Instead of logging 7, it logs Promise {<pending>}.
If I compose them the other way
const wasmFuncF = new WebAssembly.Function({parameters: ["externref"], results: ["externref"]}, x => x);
const suspendingF = new WebAssembly.Suspending(wasmFuncF);
const wasmTable = new WebAssembly.Table({element: "funcref", initial: 1});
wasmTable.set(0, suspendingF);
It raises TypeError: WebAssembly.Table.set(): Argument 1 is invalid for table: function-typed object must be null (if nullable) or a Wasm function object.
Does the spec have anything to say about the expected interaction of WebAssembly.Function and WebAssembly.Table? I should think at least new WebAssembly.Function(sig, new WebAssembly.Suspending(func)) should be required to work in a compliant implementation.
new WebAssembly.Suspending(func) does NOT return a function value! As specified, it represents a wrapped function which is only usable in the context of an import during module instantiation.
Note that WebAssembly.Function is part of a different proposal that is still in phase 3.
So according to the current spec, if I want to use a suspending WebAssembly function with call_indirect I should reexport it rather than using WebAssembly.Function? Does it seem reasonable to you to specify somewhere that when both type reflection and JSPI are implemented, they should interact in this way? Perhaps the most appropriate spot for this is in the type reflection spec, where it could say for instance that the behavior of new WebAssembly.Function is identical to:
function wasmFunctionPolyfill({ parameters, results }, f) {
const builder = new WasmModuleBuilder();
const functionIndex = builder.addImport("env", "f", { parameters, results });
builder.addExport("exportedFunction", functionIndex);
const buffer = builder.toBuffer();
const module = new WebAssembly.Module(buffer);
const instance = new WebAssembly.Instance(module, {"env": { f } });
return instance.exports.exportedFunction;
}
With language to that effect in the reflection proposal, compliant implementers of both would be forced to make them interact the way that makes sense.
I guess v8 is also not implementing the current spec for type reflection as it is written because as I read it:
IsCallable(suspending)is supposed to returnfalse- The spec for the
new WebAssembly.FunctionsaysTo construct a new WebAssembly Function from a JavaScript callable object callable and FunctionType signature, perform the following steps:
- Assert: IsCallable(callable).
So that assertion should fail and it should throw. The current behavior of the v8 implementation is as follows:
- If
callablehas a[[wrappedFunction]] slot, replacecallablewithcallable's[[wrappedFunction]]` slot. - ... original steps here
Explicitly what I would like is for that language to say:
- If
vhas a[[wrappedFunction]]internal slot: i. Letfuncbev's[[wrappedFunction]]slot. ii. Assert $IsCallable$(func). iii. Create a suspending function fromfuncandfunctype, and letfuncaddrbe the result. iv. return the result of creating a new Exported Function fromfuncaddr - ... original steps here