WebAssembly / js-promise-integration

JavaScript Promise Integration
Other
66 stars 17 forks source link

Feature detecting JSPI / what is the point of WebAssembly.Suspender? #21

Closed hoodmane closed 5 months ago

hoodmane commented 1 year ago

@brendandahl added the JSPI feature detection to wasm-feature-detect which looks like:

"Suspender" in WebAssembly

https://github.com/GoogleChromeLabs/wasm-feature-detect/blob/main/src/detectors/jspi/index.js

Currently this does seem to work. It is even possible to construct an instance of WebAssembly.Suspender. This happens four times in the v8 test suite, but in no case is the Suspender object used for anything after it is constructed. For instance in TestStackSwitchSuspendArgs the suspender was originally used but became dead code when the API was changed: https://github.com/v8/v8/blob/6bc1b613e94016010df71ecbb89c04ce267df3b3/test/mjsunit/wasm/stack-switching.js#L303 https://chromium.googlesource.com/v8/v8/+/921135c7059a0e2c2df2e6ddad7ae5f52311f589%5E%21/test/mjsunit/wasm/stack-switching.js

A few questions:

  1. Does the proposal currently mention WebAssembly.Suspender in any way?
  2. Is there any purpose to the suspenders constructed with WebAssembly.Suspender? Seems most likely to me that they are completely useless.
  3. Is there any point to being able to use WebAssembly.Suspender for instanceof checks? This also seems of very dubious value to me.
  4. If the only remaining purpose of WebAssembly.Suspender is to allow feature detection of the JSPI without having to attempt to instantiate a webassembly module, would this not be better served by a boolean e.g., WebAssembly.supportsJSPI?

@thibaudmichaud

fgmccabe commented 1 year ago
  1. Personally, I would not endorse the feature detection use of Suspender.
  2. In the proposal the Suspender object does not normally figure in JS code. It can, however, leak; which is why it is given a first class definition.
  3. The internals of Suspenders are deliberately opaque at this time.
  4. The primary function of Suspenders is enable two 'functionalities': it allows us to give a correct semantics especially in the case of combining wasm modules, and it acts as a future anchor for core stack switching. (I.e., where wasm applications can make use of coroutining internally.)
dschuff commented 1 year ago
  1. Personally, I would not endorse the feature detection use of Suspender.

Why not? Do you have another suggestion? JSPI is trickier to feature-detect than most other extensions because there aren't any new core constructs such as instructions that we can run through the validator.

fgmccabe commented 1 year ago

Well, you will get an exception if you try to create a suspending WebAssembly.function and JSPI is not enabled.

ajklein commented 1 year ago

See also #2 for more discussion about Suspender.

brendandahl commented 1 year ago

Francis, Above you mentioned the Supender will be used for core stack switching as well, so checking for the Suspender may feature detect both. Will that also be a problem for checking for an exception when using a function with promising/suspending or will core stack switching use something else at the boundary?

hoodmane commented 1 year ago

Personally, I would not endorse the feature detection use of Suspender.

I think the alternative is to use the fact that promising: "first" transforms the type of a function from ["externref", ...rest] => result to [...rest] => [externref] to feature detect. So first we make a wasm module that exports a function that takes an externref argument and returns nothing and does nothing:

(module (func (export "o") (param externref)))

Then we can feature detect with:


async function supportsJSPI() {
  try {
    const m = new Uint8Array([
      0, 97, 115, 109, 1, 0, 0, 0, 1, 5, 1, 96, 1, 111, 0, 3, 2, 1, 0, 7, 5, 1,
      1, 111, 0, 0, 10, 4, 1, 2, 0, 11,
    ]);
    const { instance } = await WebAssembly.instantiate(m);
    new WebAssembly.Function(
      {
        parameters: [],
        results: ["externref"],
      },
      instance.exports.o,
      { promising: "first" }
    );
    return true;
  } catch (e) {
    return false;
  }
}

In case JSPI is not supported, WebAssembly.Function throws the error:

The signature of Argument 1 (a WebAssembly function) does not match the signature specified in Argument 0
fgmccabe commented 1 year ago

We have received a fair amount of 'feedback' about the Suspender object. At first blush, it definitely appears to be superfluous. You may also remember that the original version of JSPI hid the suspender from the wasm code. The true value of Suspender objects today comes from the more unusual scenarios: combining modules in sandwich configurations. In addition to the intrinsic utility of Suspenders for JSPI applications, there is also the issue of core stack switching; specifically how core ss will integrate with the world of JS and/or hosts. This represents a 'design issue' that is not fully resolved at this time; but there is some hope that Suspender objects will be important in that scenario. This is obviously not part of JSPI itself.

Incidentally, simply implementing JSPI/core stack switching in terms of the latter fails on the shores of exception handling and traps.

The upshot is that, while Suspenders are currently part of JSPI, I would prefer to de-emphasize their role and use as much as possible. For example, currently, the only way that a JS program would ever see a Suspender is if it were leaked from the wasm module.

hoodmane commented 1 year ago

To be clear, I am not questioning the value of the suspender object itself, I think the point of it is clear. I was questioning specifically the purpose of the WebAssembly.Suspender global. It seems to me that running delete WebAssembly.Suspender causes no loss of functionality of any sort, so I was concerned that "Suspender" in WebAssembly could be broken by the removal of WebAssembly.Suspender.

brendandahl commented 1 year ago

If we delete WebAssembly.Suspender what will the type of the object be when it's passed out from wasm to js?

hoodmane commented 1 year ago

Well it could have a type which is just not directly accessible from the JavaScript global scope. In that case you would have to acquire a suspender, move it into JS, and then get it via someSuspender.constructor.

My goal is to have an easy way of feature detecting stack switching, preferably easier than the code I indicated above. "Suspender" in WebAssembly is a great way to do this feature detection from my perspective. My concern is that the text of the proposal does not say anything about the JS global WebAssembly.Suspender so it seems to me that some other JS engine could implement the proposal without "Suspender" in WebAssembly being true. If this is going to be the way that https://github.com/GoogleChromeLabs/wasm-feature-detect feature detects the JSPI I think it would be preferable for there to be language in the proposal that implies that this method will return true on a conforming implementation.

fgmccabe commented 1 year ago

To be clear, if we delete Suspender, there will be nothing to leak out to JS.

fgmccabe commented 5 months ago

Closing, since it has been decided that the Suspender object is no longer visible to programmers.