-
Notifications
You must be signed in to change notification settings - Fork 72
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
How should casts/checks for structs passed into Wasm work in the JS API? #203
Comments
Thanks for writing this up, @takikawa! I've had the same concern for a while. The JS API's design philosophy (beyond this proposal) has been that coercions can be automatically generated from type signatures. But the research in this area indicates that such an approach tends to cause coercions to be either inefficient (originally, the GC proposal expected coercions to perform structural equi-recursive type casts) or prohibitively lossy (as your example illustrates). My advice would be to find a way to move coercions into application space. The problem is that coercion systems tend to be specific to the two systems being coerced between, in this case wasm and JS, which is why I've been exploring ways to equip wasm modules with embedder-specific linking. I've been working out a high-level strategy for a JS-specific coercion system that bridges the ideas in @tebbi's #132 with the ideas in the @tschneidereit's newer Typed Objects proposal and addresses the issues raised above, but it utilizes a nominal type system (which also makes it avoid another issue in the current JS API wherein equivalent type signatures have different semantics) and so currently is not a viable option. |
I think the high-level comment is: yes, there are unsolved issues/questions with the JS API design, and what the JS API will end up looking like is still very much an open question. One thing that seems pretty certain (to me at least) that the current state of the "MVP-JS"document will not be the final state of things. Regarding the "where does Regarding the Looking forward to hearing more about Ross' explorations! |
@RossTate Thanks, you make some good points regarding automatic coercions being potentially inefficient (I have some experience with this being a problem in the gradual typing world) or inexpressive. But this seems like it's partly an artifact of the type system that the coercions have to enforce, right? Hypothetically if we considered nominal types, wouldn't that have different tradeoffs even with automatic coercion? Also I look forward to hearing more about embedder-specific linking & how that connects to coercions as well. :) @jakobkummerow Thanks for bringing up the |
Oh, this is Asumu @takikawa! Great to have ya 😄 For context for everyone else, Asumu is lead author on the paper that brought broad awareness to the performance problems in "sound" gradual typing. The relevance to the JS API is that sound gradual typing (more generally speaking) is all about mixing languages safely (more specifically where the languages differ only in being statically versus dynamically typed), which is generally done through some means of coercions at the boundary points between the languages. His paper identified major problems in the performance overhead caused by these coercions, and many research teams (including my own) have been pursuing a wide variety of ways to address those overheads. I believe there are many lessons for language interop to take away from these works. One is that there is a lot of choice in how to design these coercions, and those choices have substantial consequences and tradeoffs (w.r.t. performance, functionality, guarantees, and so on). This is why I was suggesting pushing as much coercion into the WebAssembly module (or embedder-specific portion thereof) rather than attempting to derive coercions automatically. @jakobkummerow's suggestion is also essentially doing this by using just The above is largely about bringing JS references into wasm, but that is only half of the picture. The other half is putting wasm references out into JS. It's one thing to put them out as black boxes, but ideally they can be made to be accessible in "natural" ways from JS, e.g. field accesses and method invocations and such. In the current JS API, the expectation is that such "decoration" is performed on the JS side. But there are problems with that, such as the issues identified in the slides in #107, plus the issue that these decorations would all be going through coercions. So decorating it inside (an embedder-specific portion of) wasm, along the lines of @tebbi's #132 (sorry, I linked the wrong related issue before) could be a big performance improvement. There are lots of loose ends to tie, but that's something I've been working through (though there are a number of points that would greatly benefit from more insights from y'all). |
Closing this in favor of the more up-to-date discussion of casts on the JS-Wasm boundary: #279 (comment). |
I'd like to ask a design question about how checks for structs that are passed into Wasm functions by JS code should work. Here is a concrete example with a Wasm module and some JS code that interacts with it to motivate the discussion:
The key thing here is that a struct originates in Wasm (via
makePt
), goes to JS, and then is supplied back to Wasm (viaaddXY
). At the point that it goes back to Wasm, a check/cast is needed to ensure it's really a(ref $pt)
as promised.An obvious/naive approach to this might be to extend
ToWebAssemblyValue
in the JS API with a new caseToWebAssemblyValue(v, (ref $t))
that would evaluate the cast(ref.cast v (rtt.canon $t))
. This will work for the exact example presented above, but unfortunately it is easy to construct situations in which the cast is more conservative than the type(ref $pt)
due to inheritance chains in the RTT.For example, suppose that there's more code in the original Wasm module that constructs points in a different way, using a different set of RTTs. And let's suppose that we still want to call
addXY
on these other kinds of points:Then the cast will fail on points created by
makePt2()
even though they are still inhabitants of(ref $pt)
. Because of this cast behavior, JS cannot executeaddXY(makePt2())
.In the current MVP JS API document, I didn't see a mention of how this specific situation might work out in terms of the casts. On the other hand, a while back there was an alternative proposal by the V8 team that involved extending the RTT.
In that design, it is assumed that structs are not automatically castable and you need to specify a concrete RTT to use for a cast if you want to access a field in JS. If I understand correctly, this only works for fields and not for function arguments in general as in the examples above (for
addXY
you could make it a method using that API, but you may have struct arguments that are not the method receiver in general).This kind of JS/Wasm interaction with function calls and structs seems like a case that would be very desirable for the JS API and GC proposal design to support. Are there any changes we could make for RTTs or the type system that would enable this with low friction? Or will we need to use a design (like the V8 proposal) in which casts are somehow explicitly specified for struct fields and struct-typed function arguments? (either via the RTT, or perhaps some other mechanism like a custom section)
The text was updated successfully, but these errors were encountered: