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

Isorecursive types vs. JavaScript-created functions #292

Closed
jakobkummerow opened this issue Apr 26, 2022 · 6 comments
Closed

Isorecursive types vs. JavaScript-created functions #292

jakobkummerow opened this issue Apr 26, 2022 · 6 comments

Comments

@jakobkummerow
Copy link
Contributor

This issue concerns the integration of the GC proposal and the Type Reflection proposal. (I suppose the discussion could happen in either's repository.)

With pre-GC Wasm, it's possible to export a function that takes another funcref as a parameter, typed as anyref or funcref or ref $sig for a specific signature $sig. With "Type Reflection", matching function references can be constructed using new WebAssembly.Function(signature, js_callable), where signature is a JavaScript object representing a description of the desired signature, e.g. {parameters: ["i32", "f64"], results: ["anyref"]}. Since the signature is stored (internally) along with the funcref, the funcref can pass any required type checks (e.g. by call_indirect or table.set) and be called according to this signature. Engines can use this signature to compile an appropriate "wrapper" to make the underlying JS function callable from Wasm (which generally requires conversion of parameters/results, and depending on engine design choices also adaptation of the calling convention).

With WasmGC and the isorecursive hybrid type system we are now pursuing, recursion groups matter: both for signatures themselves, and even more so for module-defined struct/array types that may be referenced from signatures. There is currently no way to specify rec-groups in new WebAssembly.Function, which significantly limits what can be done from JavaScript: specifically, it's impossible to refer to module-defined types.

For the specific subset of cases where signatures only use generic types for all parameters and results, we can resolve this by treating each WebAssembly.Function call as creating an implicit one-element recgroup, which would be consistent with handling of legacy, non-recgroup-using type sections of modules. We may want to officially write down somewhere that that's the expected behavior. A (minor?) concern is that malicious or pathological JavaScript code could overwhelm the type canonicalization system/cache with an unbounded number of dynamically-generated types, and keeping track of when these can be cleaned up might be rather involved (i.e. have both an implementation complexity and a runtime performance cost).

For full expressiveness, we'll need more. There are several options, including at least:

(1) Type exports. If there was a way to refer to types defined by a module, we could make it possible to use WebAssembly.Function to create functions whose signatures match that module's types. That could conceivably be either be the entire signature (example in strawman notation: new WebAssembly.Function(my_module.exports.signature1, js_callable)), or it could be individual parameters and results (example: new WebAssembly.Function({parameters: [WebAssembly.optref(my_module.exports.my_type)], results: []}, js_callable)).

(2) Fully featured ability to construct types in JavaScript, in other words: extend the Type Reflection proposal by a facility to create entire rec-groups of types, and rely on implicit isorecursive canonicalization to have these externally-created types be identified with the module's own types. Note that this might get rather unwieldy for large rec-groups, especially when large modules choose to use fully nominal types by having a single huge rec-group. (We are expecting to see modules with tens of thousands of types, maybe more.)

(3) We could consider being more permissive and defining some kind of "on demand" system for adaptively "morphing" JS-provided functions to whatever signature they need in a given situation. That would be at odds with Wasm's generally strict approach to typing though, and I'm worried about performance costs (calls and/or typechecks could become significantly more expensive, this cost might fluctuate quite wildly depending on caching and overall app behavior), and I'm also worried about implementation complexity.

Any other ideas or concerns?

@rossberg
Copy link
Member

The JS API generally wants the ability to accurately express all types expressible within Wasm. This includes recursive types, but also other GC types. So (2) is needed. Type exports may be an additional convenience in some cases, but are not a general replacement nor necessarily better – with only those, you'd need to construct and compile auxiliary Wasm modules to get types in certain scenarios, which is no less unwieldy, and exactly the kludge that the type reflection API is supposed to make unnecessary.

@askeksa-google
Copy link

askeksa-google commented Apr 27, 2022

Type exports seem the most convenient way to specify this. If a module expects to be handed a function of a particular type, it will have that type already defined and will know to export it.

@rossberg
Copy link
Member

@askeksa-google, keep in mind that we don't have access to exports until we have instantiated a module. So you'd need a second module to instantiate and import the type from first if it's needed on the JS side to construct and supply a function import.

@askeksa-google
Copy link

For function imports we don't have this problem, do we? Since those already have a precise type specified in the module, the appropriate wrapper can be created directly from the supplied JS function at import time.

@rossberg
Copy link
Member

True, but of course, there might be reasons to construct/wrap the function explicitly. And this won't apply to use cases like putting functions in tables or passing function references.

@tlively
Copy link
Member

tlively commented Nov 15, 2022

Closing this in favor of more recent discussion in #338.

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

4 participants