Conversation
|
I have a few dumb questions
|
|
Apologies for the delay, I was rewriting most of the explainer for a new approach. I'm less convinced now that specifying this in terms of WebIDL is the right approach and so I'm switching back to this being a JS-API. I believe we can get good performance for web API calls by designing value conversions such that JS script cannot intercept them when a web API is imported directly. The new approach attempts this, and it's not as bad as I thought it would be. It's not quite ready for review but is closer now. I have answers to your questions, but they're in terms of the discarded draft so I'm not sure it's relevant anymore. |
|
@eqrion so, something that i've run into as a Rust WebAssembly enjoyer is that even though on paper Rust compiled to webassembly should be faster than javascript, in practice they're about the same speed, and in fact Rust can be slower compared to javascript when it comes to using apis like WebGPU or WebGL2. will this new api fix this? |
|
@ValorZard Performance is going to be very workload dependent. The biggest thing this could provide is to lower the overhead for calling from Rust into a web API. But it's still possible to have bottlenecks in your rust code that make it slower than JS. JS engines are marvelous things and are not an easy target to beat consistently. |
|
I think this is ready for review now. Tagging Luke for review. cc @bvisness @guybedford @vados-cosmonic. Feel free to look at this if you're interested, but no worries if not. |
lukewagner
left a comment
There was a problem hiding this comment.
Great work; exciting to see this all in detail! There's a lot and so I didn't get to read all of it and only skimmed some parts, but here's a first pass of comments/questions.
| instance.exports.greet(); // TypeError | ||
| ``` | ||
|
|
||
| Passing too few arguments is a `TypeError`. Extra arguments are ignored. |
There was a problem hiding this comment.
Would it make sense to allow trailing optional parameters to be omitted?
There was a problem hiding this comment.
I would expect this already because I would expect them to be considered undefined.
| | `list<u8>` | `Uint8Array` | | ||
| | `list<T>`, `list<T, N>`, `tuple<T, U>` | Array | |
There was a problem hiding this comment.
Would we want the other fixed-width integers to also turn into their respective typed arrays? And perhaps also the list<T, N> variants too?
There was a problem hiding this comment.
There are enough flavors of typed array that this would encompass literally all numeric types. Is that what we want?
I could see it being the case that list<N> is always returned to JS as a typed array, but that params of type list<N> would accept plain old JS arrays, using the same behavior as new <Type>Array([ ... ]) or equivalently TypedArray.from().
Still, it's a little funny to have some lists become TypedArrays and others become normal Arrays, but the benefits of using typed arrays for e.g. list<u8> seem so strong that it's probably the right decision.
There was a problem hiding this comment.
Yeah, I think we could have ToJSValue always produce a typed array for scalars, but then allow ToComponentValue to still accept arrays.
There was a problem hiding this comment.
I think I have changed my mind and opened #731 instead.
| import { run } from "./component.wasm"; | ||
|
|
||
| // calls the default export of `https://esm.unpkg.com/slugify@1.6.6` | ||
| run(); |
There was a problem hiding this comment.
Since run() has no params/results, maybe convert it to use a core start function and have the first demo just use <script type="module" src="./component.wasm"> (mentioning that this .html+.wasm is a complete web app without any JS code). And then maybe as a second example add an export with params/results and show what it looks like to import and call from JS like you're doing here. And then to complete the ESM high-level picture, maybe show a component that imports some name without an external-id and use an import-map to map it to a URL, and then mention that this is one way that non-browser APIs like WASI can be polyfilled on the Web.
| 1. Perform `DefinePropertyOrThrow`(|target|, `JSName`(|e|), PropertyDescriptor { [[Value]]: |func|, [[Writable]]: **true**, [[Enumerable]]: **false**, [[Configurable]]: **true** }). | ||
| 1. Return |constructor|. | ||
|
|
||
| A method named `constructor` and a static named `prototype` are rejected because they would unexpectedly change JS class semantics. |
There was a problem hiding this comment.
Along the same lines, I think perhaps arguments, caller, name and length might conflict with predefined class names. I wonder if, rather than throwing a type-error (which might actually be a problem for some of these), these methods are silently omitted but can still be found via their full original plainname on the exports object (just like interfacename exports), and thus these methods/statics/constructors are really just syntactic sugar.
| 1. Let |hostType| be |instance|.[[HostResourceTypes]][|abstractTypeKey|]. | ||
| 1. Assert: |rep| is a host resource value whose [[Type]] is |hostType|. | ||
| 1. Return |rep|.[[JSValue]]. | ||
| - `ToComponentValue(jsValue, own<R> | borrow<R>)`: |
There was a problem hiding this comment.
If the host value is really a Guest resource instance (returned from some other component's export), it seems like ownership needs to be transferred (in the form of emptying the [[Rep]] of the Guest resource instance) so that there is not a double drop.
There was a problem hiding this comment.
Hmm. That would violate the 'no direct linking' direction where host resource imports are treated the same no matter whether it's a true JS class or implemented with a component. I'm not sure I understand the double drop case. The guest resource class instance will be a JS value that has a rep and a finalization entry. When imported as a host resource, it'll just hold that JS value to extend the lifetime so that it doesn't get finalized. If we have a 'Symbol.dispose' on guest resource classes (as suggested above) that trigger drops, that'll cancel the finalization registry and won't lead to a double drop.
There was a problem hiding this comment.
I was more thinking about the other direction: what if JS is given an owned handle, the object-handle-wrapper is passed into a component via own handle, and then JS calls Symbol.dispose on its object-handle-wrapper while the component is still holding its own handle (which it could then try to resource.drop, leading to double-drop, or use, leading to use-after-free -- or maybe both cases trap, but that's weird).
I wonder if, in the same way that the JS-API (might) detect-and-call Symbol.dispose, there's a Symbol.transfer (eventually... in the short term there could be a hard-coded list of object types, kinda like what the branding check does) so that when a JS object is passed into an owned handle, Symbol.transfer is called, if present, with the expectation that the original object gets nerfed. ArrayBuffer detachment (which literally has an ArrayBuffer.transfer()) would be another, non-wasm example.
So then the net result would be: if you have a JS object that wraps an owned handle and pass it into a component, your JS object is mutated to hold a null handle that throws if you do anything with it.
vados-cosmonic
left a comment
There was a problem hiding this comment.
This is a great start, took a pass at the explainer and reference pages and they look good so far, mostly nits/organizational (and emphasizing easy readability of the explainer) and some questions about conversions and WebIDL layout
| ``` | ||
|
|
||
| Passing too few arguments is a `TypeError`. Extra arguments are ignored. | ||
|
|
There was a problem hiding this comment.
I wonder if we should add some explanation around interfaces as exports, since they introduce a kind of nesting here. instance.exports.<iface> versus instance.exports.<fn>
There was a problem hiding this comment.
I think that'd be good as a dedicated section on importing/exporting instances. I'll see about adding that. The explainer is definitely not complete, it's missing some areas like this.
|
|
||
| | Specifier | Resolves to | | ||
| |---|---| | ||
| | `wasm:js/global` | [the global object](#the-global-object) | |
There was a problem hiding this comment.
I do wonder if this should actually be wasm:browser/wasm:dom or something (or maybe split out into multiple packages/interfaces), given that some browser JS globals are not JS globals, and what not, but that's a much wider discussion!
There was a problem hiding this comment.
I think the naming will be bikeshedded a bit, it's not great. The justification for it though is that it's just pulling the globalThis from the JS environment. I don't think we'll want to use wasm:browser or wasm:dom because this JS-API could be implemented on Node or other non-browser environments that have a JS global object.
There was a problem hiding this comment.
I don't think naming it something like wasm:dom would preclude non-browser environments form implementing it, I think actually it would make it clearer that they were bundling support for a DOM-like environment.
I think my main thought here that there is a distinction between the web context and most non-browser-specific objects/operations on globalThis. I think it would be useful to be able to avoiding including DOM globals for a component that did not use them, and include other globals ?
|
Just pushed some fixes to address a bunch of comments. I've not gotten to some of the larger semantic reworks yet though. |
| ``` | ||
|
|
||
| ```js | ||
| const imports = { element: Element }; |
There was a problem hiding this comment.
If type names are converted to PascalCase in JS, then why is this not { Element } (or equivalently { Element: Element })?
| | `list<u8>` | `Uint8Array` | | ||
| | `list<T>`, `list<T, N>`, `tuple<T, U>` | Array | |
There was a problem hiding this comment.
I think I have changed my mind and opened #731 instead.
I'm presenting on Mozilla's prototype implementation which is based off of this, so I wanted to have it publicly accessible somewhere. It needs a couple more iterations before it's ready for detailed review. High-level feedback is welcome though.