Skip to content
This repository was archived by the owner on Apr 25, 2025. It is now read-only.

No room to specify JS/embedder-specific prototype #211

Closed
RossTate opened this issue May 6, 2021 · 10 comments
Closed

No room to specify JS/embedder-specific prototype #211

RossTate opened this issue May 6, 2021 · 10 comments

Comments

@RossTate
Copy link
Contributor

RossTate commented May 6, 2021

It would be good for GC objects be "immutable prototype exotic objects" in JS, i.e. the prototype is fixed. That enables a bunch of JS-side optimization (regardless of whether the prototype's contents are specified on the wasm side or the JS side) and builds in a (from what I understand) increasingly common defensive programming pattern (Object.preventExtensions was added in ES5 for this). However, there seems to be no way for a wasm program to indicate which struct allocations are related, i.e. should have the same prototype. How is this being done or supposed to be done, or is the expectation that wasm objects will have mutable prototypes that need to be set by JS-side code each time they cross into JS?

@rossberg
Copy link
Member

rossberg commented May 7, 2021

In JS engines the prototype is stored as part of the hidden class/map/shape of an object. That corresponds rather directly to an RTT from the proposal, and they are in fact implemented as such e.g. in V8. So the canonical way to customise the prototype would be that the JS API provides a way to create custom RTTs and the Wasm module imports them.

Potentially, the prototypes of Wasm-created objects could be fixed-from-inception, or the API could provide a way to mark them as such. But that would be rather alien to JS. No such mechanism exists elsewhere in JS for user objects (e.g., for ES6 classes), so the Wasm API seems like the wrong place to invent it. We may wish that instance prototypes were immutable by default, but JS is what it is, and engines have learnt to cope. You might not be aware that they already have extensive mechanisms to dynamically optimise for the absence of prototype mutation, and those apply to JS and Wasm-created objects alike.

@tschneidereit
Copy link
Member

It would be good for GC objects be "immutable prototype exotic objects" in JS, i.e. the prototype is fixed.

I haven't followed the GC proposal all that closely in quite some time, including the JS reflection, but when I worked on the Typed Objects companion proposal a few years back we settled on exactly this.

@jakobkummerow
Copy link
Contributor

I think this question (generalized to "what about the prototype?") is one aspect of the bigger question "what should the JS API for WasmGC look like?", which I consider a very open question. (We have MVP-JS.md, but IIUC everyone agrees that that's far from final, and we don't know what a closer-to-final version of that might look like.)

I think on a very high level, there are two possible paths we could take:
(1) In JavaScript, the __proto__ of a WasmGC struct/array is always null. That would avoid the need to specify prototypes, but would imply certain limitations to what JS code can do with Wasm objects, which might be considered prohibitive.
(2) There is some way for applications to specify a custom prototype (either in the Wasm module, or via the JS code that deals with module instantiation and such). This would, at the very least, address the concern that there is currently "no room to specify such a prototype". (Of course there are still lots of open questions about the details of such a design.)

Regarding our strategy:

  • The answers we'll find for settling the pure-Wasm open questions (types etc.) will likely influence the requirements for the JS API (especially around prototypes), so while it might make sense to keep the latter in mind when dealing with the former, it also makes sense to take care of the former first.
  • As usual, the design is ideally informed by actual customer needs; we're hoping that the ongoing prototyping can shed some light on things in this regard (e.g. one possible observation could be that no highly-expressive API is required, because toolchains that emit Wasm modules can also emit accompanying JS glue code; or the opposite could be the case: that certain features are absolutely needed for building real-world apps consisting of collaborating JS and WasmGC components -- we'll see). Such concrete experience should also help us assess whether concepts like frozen prototypes are convenient or cumbersome; though further, specific experimentation may well be required on that front.

@RossTate
Copy link
Contributor Author

RossTate commented May 7, 2021

Thanks for all the responses above!

@jakobkummerow What are you doing currently? (I understand that it's temporary; I'm just curious what it is at the moment and how the teams currently targeting the prototype are utilizing it for JS interop.)

@jakobkummerow
Copy link
Contributor

jakobkummerow commented May 7, 2021

The current behavior of our prototype is that you can pass WasmGC structs/arrays to JS, where they will show up as opaque (empty-looking, as if it were new Object()) objects. As a mental model, it's pretty much the mirror image of passing JS objects through Wasm as externref. Under the hood, a fresh wrapper object is created every time, which has a hidden property pointing at the real Wasm object. The reason is that this was quick to implement, unlocks key basic functionality (namely, being able to hold references), and its behavior should be forward-compatible to whatever we'll end up spec'ing later. If an application wants to read/write properties of such objects, it can (inefficiently) do so by exporting Wasm functions that do that (again, mirroring how a Wasm module would read properties from a JS object that it's holding an externref reference to).

We have also (slowly...) started exploring a prototype implementation of the basics of true interaction, where it will be possible to read/write struct/array fields as my_wasm_struct.$field0 and my_array[0] respectively. This isn't ready yet, and we don't have a timeline as it's been considered fairly low priority so far. Under the hood, it will no longer use wrapper objects, so passing objects back and forth will be very efficient. The idea behind this approach is to postpone anything that would require spec work, including prototypes (so I guess we'll use __proto__ === null for now; I haven't really thought about it much). The superficial details (e.g., "should it really be .$field0?") are easy to change; the big effort is adding support for handling Wasm objects anywhere in the engine where JS objects are being dealt with, and we wanted to get started on that. As I said in my last post, we'll be curious to see whether this functionality might be enough, or what specific additional bits might be desirable.

Edit to add: the teams targeting the prototype have said that for now they're mostly happy with using numeric Wasm types on the boundary. They're also interested in strings, but that's a whole separate discussion.

@RossTate
Copy link
Contributor Author

RossTate commented May 7, 2021

Thanks for the update! I'm glad to hear y'all are looking into the wrapper-free approach, given that the research on efficient interop suggests wrappers can be quite problematic for performance.

Regarding the edit, it sounds like the teams aren't looking into JS interop (yet) then.

@rossberg
Copy link
Member

@jakobkummerow:

I think on a very high level, there are two possible paths we could take:

Actually, there are two separate questions: what prototypes Wasm objects start out with, and to what extent they are mutable. A null prototype could still be mutable.

@takikawa
Copy link
Contributor

At Igalia we're concerned about the cost at the Wasm/JS boundary to constructing new wrapper objects (whether in the engine or in the toolchain), and so we'd like to investigate ways that Wasm GC structs can be directly represented to JavaScript without additional allocations. If this object will be used directly, the question in this issue of fixed vs mutable prototype seems quite relevant.

From Ross:

It would be good for GC objects be "immutable prototype exotic objects" in JS, i.e. the prototype is fixed.

From Till:

I haven't followed the GC proposal all that closely in quite some time, including the JS reflection, but when I worked on the Typed Objects companion proposal a few years back we settled on exactly this.

Speaking of Typed Objects, @syg, @littledan and others are in the early stages of developing a JS proposal on fixed-shape objects that would address some of the same goals (i.e., have a fixed prototype) while also enabling concurrent uses of such objects. However, it's not clear whether fields in such fixed-shape objects should have dynamically-enforced types, or just be untyped. Either way, the thought was that such fixed-shape objects would have immutable prototypes from the time they are created.

There's a hope that such fixed-shape objects would correspond, at some level, to the way that Wasm GC structs are exposed at the JS API level, including the fixed prototype property (e.g., via the "way for applications to specify a custom prototype" mentioned earlier in the thread). Wasm GC structs may be more expressive (e.g., dynamically enforcing field types), but it would be great to align in other ways where possible, to reduce implementation burden for engines implementing the Wasm/JS API, and to provide a uniform mental model for JS developers who may use both fixed-shape objects and the JS representation of Wasm GC objects.

@RossTate
Copy link
Contributor Author

Ah, good point on immutability of the prototype being useful for parallelism/concurrency! In prior discussions, similar concerns have been raised about supporting multithread-safe runtimes of Java and the like in wasm, wherein it's often important (for memory safety) that various aspects of v-tables and other runtime structures are immutable after initialization has completed (the "build and freeze" pattern).

@tlively
Copy link
Member

tlively commented Nov 1, 2022

These concerns are resolved by the "no-frills" JS interop proposal (#279) we've settled on for the MVP.

@tlively tlively closed this as completed Nov 1, 2022
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