Skip to content
This repository has been archived by the owner on Dec 22, 2021. It is now read-only.

JS API for SIMD: WA.Global<v128>, lane accessors, coercions at calls #502

Open
lars-t-hansen opened this issue Apr 8, 2021 · 6 comments

Comments

@lars-t-hansen
Copy link
Contributor

(Yeah, I know we're done but I figured it was more natural to file the issue here than in relaxed-simd. Looking for feedback.)

Generally JS does not have a representation for v128 so things become a little awkward; JS can't easily talk about v128 values. But JS can talk about WA.Global objects of type v128, and it can talk about the individual lanes in such an object if accessors were provided. There are a couple of things we could do to make life easier for the JS programmer who interacts with wasm SIMD code:

  • provide lane getter and setter methods on WA.Global when the type is v128, we probably should have done this already
  • allow WA.Global objects to be passed to exported wasm functions for parameters of type v128 and also make such exported functions return new WA.Global objects when a return type is v128. In effect, WA.Global becomes the JS representation of v128.

(This is based on some work @eqrion is doing for our testing infrastructure, where we rely on a variant of this system, see https://bugzilla.mozilla.org/show_bug.cgi?id=1703105.)

@tlively
Copy link
Member

tlively commented Apr 8, 2021

I'm no expert on the design of the JS API, but this all sounds reasonable to me 👍 Would the path forward for this be as a small new proposal or do you imagine we could slip it into this SIMD proposal before official standardization?

@ngzhian
Copy link
Member

ngzhian commented Apr 8, 2021

Sounds useful to be able to talk about v128 from JS. Does having lane accessors on WA.Global mean that we define new methods on interface Global?

@dtig
Copy link
Member

dtig commented Apr 9, 2021

Given that JS not being able to talk to V128 values was by design, i.e. we expect that most JS developers won't need to interact with SIMD code, would this change be geared towards making testing infrastructure simpler, or do you see other uses for exposing this to JS?

To be clear, I have no objection to the approach proposed if we decide this is the way to go - but trying to make sure that we expect this to be useful for cases other than test code.

For the path forward, I would advocate for this to be a part of the proposal because (1) Very low likelihood of breaking anything because. (2) This doesn't introduce any spec changes, and only touches the JS API. I think this is allowed as a part of Phase 4 under cosmetic changes to the proposal.

@lars-t-hansen
Copy link
Contributor Author

lars-t-hansen commented Apr 9, 2021

@ngzhian, indeed I think the easiest thing solution be new methods on the Global interface, though it could be phrased in terms of attributes. Not intending this as more than a suggestion, but if g is a v128 WA.Global then the method API might be:

g.getLane('i32x4',2)    // interpret as i32x4 and return lane 2
g.setLane('i32x4',2,-1) // ditto, set lane 2 to -1

and an attribute API might be:

g.i32x4                   // interpret as i32x4 and return array of [lane0, lane1, lane2, lane3]
g.i32x4 = [0, -1, 0, -1]  // set all four i32x4 lanes
g.i32x4 = [,,-1]          // set lane 2 to -1, trailing unchanged lanes can be omitted

with the additional constraint that these methods/getters/setters all throw if the type is not v128.

@dtig, regarding use cases: testing will be simpler, for sure. For real uses, programs using JS glue code in a wasm application may be simpler since that glue code may be able to avoid being tied to the wasm heap (the alternative to putting SIMD values into boxes is to expose the linear heap and pass addresses around). I'm actually not sure how strong these non-testing use cases are and we may need more time to evaluate that. I would expect the accessors to be fairly uncontroversial and something we could slip into the current spec. The more general idea about using WA.Global as a value box with coercion at the call boundary could be something we take through the CG as a separate proposal if we can come up with compelling use cases.

Edit: added the following:

Right now, JS can create a v128 global but only with the implicit default value (zero). If we add attributes that can be assigned from arrays, we should probably also allow the constructor to take an array for the value, with the same interpretation.

@eqrion
Copy link
Contributor

eqrion commented Apr 9, 2021

I'll provide a little bit more background here. The referenced bug is for running the SIMD spec tests in our JS-shell. To do this, we're converting the .wast tests to equivalent JS. This runs into issues with limitations in the JS-API (not just for SIMD, although SIMD is the hardest).

The major issues are:

  • Cannot output every kind of f32/f64 NaN constant as a JS constant.
  • JS coercion is lossy w.r.t. NaN values.
  • Cannot output every f32 constant as a lossless JS constant (i.e. JS constants are doubles).
  • Cannot create/examine/pass/receive v128 values.

There are multiple options for solving these issues. The one we're moving forward with is to add test-only extensions to the JS-API to workaround these issues.

The basic idea is to re-use WebAssembly.Global as a lossless container for tagged values.

This is the set of extensions we're using:

  • wasmGlobalFromArrayBuffer - creates a global initialized from exact bytes. Only works on 'POD' value types.
  • wasmGlobalExtractLane - extracts a lane from a v128 global and returns it as a new global of appropriate type (i.e. i32, i64, f32, f64).
  • wasmGlobalsEqual - compares if two global values are strictly equal.
  • wasmLosslessInvoke - invokes a wasm function with parameters accepting globals and receives all results back as globals.

Anything less than these operations wasn't sufficient for our purposes.

If we wanted to move some of these operations into the standard, I'd recommend the following:

  1. Introduce a standalone WA.Value interface for tagged values.
    a. Same constructor as WA.Global minus 'mutable' attribute
    b. Allow value.get accessor, but not value.set
  2. Have WA.Global inherit WA.Value.
  3. Allow constructing WA.Value from bytes (when POD)
  4. Allow converting WA.Value to bytes (when POD)
  5. Add WA.invoke method which invokes exported WA function with rules that
    a. Parameters may be WA.Value or coercable JS value
    b. Results will be new WA.Value's

The rationale behind WA.Value is to seperate out the 'tagged-value' aspect of WA.Global into it's own concept that isn't tied to WA.Global specific issues, like mutability.

The rationale behind WA.invoke is to opt-in to the new coercion behavior, especially w.r.t. results which will always be WA.Value's.

Now should this all be done? I don't know if there's a use-case for this besides ours (strict wasm engine testing). If there's not, then this might be more work than it's worth?

@rossberg
Copy link
Member

rossberg commented Apr 12, 2021

Hm, it doesn't strike me as great API design to conflate the interface for globals with SIMD-specific stuff. If we want to provide SIMD accessors in JS, then let's introduce a WA.SIMD class.

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

No branches or pull requests

6 participants