Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Interaction between WebAssembly.Suspending and WebAssembly.Function? #41

Open
hoodmane opened this issue Jul 31, 2024 · 3 comments
Open

Comments

@hoodmane
Copy link

hoodmane commented Jul 31, 2024

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.

@fgmccabe
Copy link
Collaborator

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.

@hoodmane
Copy link
Author

hoodmane commented Aug 1, 2024

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.

@hoodmane
Copy link
Author

hoodmane commented Aug 1, 2024

I guess v8 is also not implementing the current spec for type reflection as it is written because as I read it:

  1. IsCallable(suspending) is supposed to return false
  2. The spec for the new WebAssembly.Function says

    To construct a new WebAssembly Function from a JavaScript callable object callable and FunctionType signature, perform the following steps:

    1. Assert: IsCallable(callable).

So that assertion should fail and it should throw. The current behavior of the v8 implementation is as follows:

  1. If callable has a [[wrappedFunction]] slot, replace callablewithcallable's [[wrappedFunction]]` slot.
  2. ... original steps here

Explicitly what I would like is for that language to say:

  1. If v has a [[wrappedFunction]] internal slot:
    i. Let func be v's [[wrappedFunction]] slot.
    ii. Assert $IsCallable$(func).
    iii. Create a suspending function from func and functype, and let funcaddr be the result.
    iv. return the result of creating a new Exported Function from funcaddr
  2. ... original steps here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants