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

Wasm GC and JS Interop #107

Closed
tebbi opened this issue Jul 29, 2020 · 8 comments
Closed

Wasm GC and JS Interop #107

tebbi opened this issue Jul 29, 2020 · 8 comments

Comments

@tebbi
Copy link

tebbi commented Jul 29, 2020

This is a new proposal for how Wasm GC objects could be accessed from JS.

Slides: https://docs.google.com/presentation/d/1BqRlDrQIYdkRHHtoZ7F8a2tpFwYoumCCbqQ4SpULajI/
Document: https://docs.google.com/document/d/1_f-w9r81VCwfCKcFt7ZiWqxYc35ERgbTcgzusip-8oQ/

@skuzmich
Copy link

I really like how this proposal allows exposing structs and arrays to JS without wrappers, yet you can fully customize its JS appearance by adding custom methods and hiding private fields. This is great for languages that compile to JS and already have a JS "ABI". Languages that yet to design this ABI, would have to make less trade-offs between efficiency and ease of use.

And it is definitely nice if we don't have to duplicate identity hash codes in VM and user-space. There were some issues #50 and #106 opened regarding that.

@Benjozork
Copy link

Benjozork commented Aug 3, 2020

I might be misunderstanding this, but could someone explain how this differs / interacts with the interface types proposal (which already has some prototypal implementations) ? At a first glance it seems to have some amount of overlap.

@aardappel
Copy link

I do not understand the reasoning why this shouldn't be specified in JS, or in a custom section as data.

"code executed once in JS is slow", well, that is fine, if it is indeed executed just once? Do we expect startup time to be (measurably) different when such declarations are present?

There is a cost to new instructions, with all tools and engines ideally being aware of them, validate them etc. This seems like it can be solved in less expensive ways. Also, it would be good for Wasm engines that do not run in a JS context to not need to be aware of this data.

I thought there was originally a plan to access Wasm GC from JS using something like the Typed Objects proposal?

@jakobkummerow
Copy link
Contributor

could someone explain how this differs / interacts with the interface types proposal

The primary focus of Interface Types is passing around data by copying it (and allowing engines, in some cases, to optimize away some of the copying). A particular (though not the only) use case is modules (or host systems) that don't know or trust each other.
This proposal's focus is sharing objects, with very little overhead (no wrapping, no copying). A particular (though not the only) use case is applications that are half JavaScript and half Wasm, where both parts know and trust each other (e.g. because they're made by the same developer, maybe along a UI + backend split, or somesuch).
I think those are mostly orthogonal use cases.

"code executed once in JS is slow", well, that is fine, if it is indeed executed just once? Do we expect startup time to be (measurably) different when such declarations are present?

The "executed once" comment refers to code that describes how to expose given Wasm object types to JS or other embedding languages, not to actual interactions on such objects; so yes, that would only need to be executed once. For a toy example that only sets up two types, I wouldn't expect a measurable performance impact; for large real apps however, I would -- we know that many web apps struggle with startup speed just because they're so big, and we know that a big contributor to slow startup is large amounts of run-once JS code.

I do not understand the reasoning why this shouldn't be specified in JS, or in a custom section as data. [...] This seems like it can be solved in less expensive ways.

Our (@tebbi's and mine) best estimate was that this was the least expensive way that enables the desired properties; we're certainly open to alternatives, e.g. a (custom or standardized) section in the module, or extra stuff in the existing types section, if folks think that's easier/better than new instructions. While the proposed instructions are certainly new, they do fit in well with established (at least in the sense of the GC proposal) Wasm patterns (like rtt.sub in global initializers). Personally I would be quite happy with an entirely-declarative section instead of instructions; of course the details would have to be worked out (the concept of associating this information with RTTs is very elegant, and RTTs are defined via instructions; so maybe the section approach would mean that the metadata is associated with static types, and RTTs inherit it from there; although it's not immediately clear how that would interact with the structural typing of static types).

it would be good for Wasm engines that do not run in a JS context to not need to be aware of this data.

A Wasm engine that decides not to support embedding dynamic languages can implement these instructions as no-ops. Whether the data is in the module or not is up to the creator of the module, and is the ~same regardless of whether it's an instruction sequence, or a section, or something else. Obviously, a custom section is easiest to ignore for engines that don't care about it; however we would certainly want the contents and behavior of this section to be standardized, as we're not talking about some optional tool-specific debugging metadata here, but rather an integral part of how some applications would work, and they should certainly work that way in all engines that decide to support this use case.

I thought there was originally a plan to access Wasm GC from JS using something like the Typed Objects proposal?

Yes, this is effectively a counter-proposal to the draft at https://github.com/WebAssembly/gc/blob/master/proposals/gc/MVP-JS.md. We believe that putting the "what does this object look like when exposed to the embedder?" information into the Wasm module provides several significant benefits (see the doc linked in the top post).

One way to think about this from a very high level is that it's a new variant of exporting things.
Old: when a Wasm module creator decides "I want this function to be callable from the outside", they put it into the exports section.
New: when a Wasm module creator decides "I want to expose this object to the outside, and it should appear to have a .foo property there that maps to its n-th field / a given function / etc", they annotate it accordingly. (These annotations could happen via RTT-enriching instructions as proposed, or via some section, or otherwise.)

@bvibber
Copy link

bvibber commented Aug 4, 2020

A big difference from the MVP-JS proposal is that the setup code is no longer JS-specific, but could be also used by host embeddings providing a sufficiently JS-like object model. This might be useful for modules meant to be used standalone in multiple different environments.

While at first it seems very JS specific because of the object model, I feel that it would actually be better for alternate host languages than having to change compilers to emit setup code for every possible host.

@bvibber
Copy link

bvibber commented Aug 6, 2020

About private fields and internal state -- I'm not so sure this proposal handles that as well as would be needed for secure isolation of module internals: there seems to be no way to create a field that cannot be read except by the creating module.

Thus I suspect we would still need something like JS's WeakMap to implement a safe, unforgeable mapping from shareable GC wrapper objects to local pointers into linear memory.

@jakobkummerow
Copy link
Contributor

there seems to be no way to create a field that cannot be read except by the creating module

I don't think I understand this concern? The idea is precisely that you can specify which fields are and aren't readable by the outside world. E.g. you could define a Wasm struct with three fields:

(struct $t (field i32 mut) (field i64 mut) (field f64 mut))

and then define an RTT for it that exposes some of them (for readability, this notation assumes instructions are passing values on the stack implicitly):

(rtt.canon $t)
// optionally: (rtt.sub) sequence here to control Wasm-side castability
(rtt.extern_readable_field 0 "rw_field")
(rtt.extern_readable_field 0 "rw_field")
(rtt.extern_readable_field 1 "ro_field")
(global.set $rtt_global)

and then when you create an object with (struct.new_with_rtt $t (i32.const 123) (i64.const 234) (f64.const 345) (global.get $rtt_global)), and hand out that object to the embedder's code (i.e. JavaScript in the browser; other languages elsewhere), then for the embedder the i32 field will be readable and writable as obj.rw_field, the i64 field will be readonly as obj.ro_field (while it is mutable in the Wasm module), and the f64 field will be hidden.

Is that what you meant?

@tlively
Copy link
Member

tlively commented Nov 1, 2022

We have consensus on a "no-frills" approach to JS interop for the MVP (#279), so closing this. PRs adding post-MVP ideas to the post-MVP doc would be welcome, though.

@tlively tlively closed this as completed Nov 1, 2022
rossberg added a commit that referenced this issue Nov 8, 2023
Fix formatting in the spec binary section for table initializers
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

7 participants