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

Type reflection and GC types #338

Open
takikawa opened this issue Nov 8, 2022 · 8 comments
Open

Type reflection and GC types #338

takikawa opened this issue Nov 8, 2022 · 8 comments
Labels
Post-MVP Ideas for Post-MVP extensions

Comments

@takikawa
Copy link
Contributor

takikawa commented Nov 8, 2022

Currently the JS API is proposed to be a "no-frills" approach (#279). One question that hasn't been resolved yet is if and how type reflection should interact with this proposal.

There has been some discussion already about how type reflection could be handled. For example, by extending the current AST to expand to recursive types and other type definitions.

Such an extension should probably be built on what's proposed for typed function references (https://github.com/WebAssembly/function-references/blob/main/proposals/function-references/Overview.md#js-api), which looks like this:

A ValueType can be described by an object of the form {ref: ConsType, ...}
    type ValueType = ... | {ref: ConsType, nullable: bool}

A ConsType can be described by a suitable union type
    type ConsType = "func" | "extern" | DefType

The DefType non-terminal hasn't been defined yet for function references either, but for functions, arrays, and struct there could be straightforward ways to define additional AST descriptions for those type definitions (e.g., { deftype: DefTypeKind, ...} and then type-specific fields)

However, for recursive types, something more complicated is needed. To describe a reference ValueType that points to a recursive DefType, it's necessary to encode the recursion group. You could use the notion of a projection from the spec and return a DefType like this:

{ deftype: "projection", group: RecursionGroup, index: int }

One downside I see for this approach, though, is that the RecursionGroup here could be quite big if you eagerly represent it as an AST like other types. IIRC there was some discussion that some producers may output modules where all types are in a single big recursion group.

The JS API would also need to do something for sub types. Potentially the whole inheritance chain needs to be represented in the AST as well, and you may encounter recursive types along the inheritance chain too.

Instead of using an AST-like approach, I wonder if it's better to create an API like WebAssembly.Type that would provide an interface over type definitions. The DefType specified above would be an object encoding this API rather than an AST object.

Something like this is already mentioned as a possibility for the type imports proposal, which allows type definitions to be exported (even concrete ones) and thus needs a new kind of interface to match other kinds of exports. With an interface like this, the details of recursion groups could be queried on demand as method calls instead of properties. This new interface could also account for type canonicalization (like was suggested in Andreas' comment mentioned earlier).

Anyone have other thoughts on how this should work? Would adding a WebAssembly.Type be too large a change for a no-frills JS API? Should this be deferred and type reflection just throw on typed references?

@tlively
Copy link
Member

tlively commented Nov 8, 2022

This sounds like a good direction, but I'm not familiar enough with JS APIs to have a solid picture of the details. What would the WebAssembly.Type API look like?

@ajklein
Copy link

ajklein commented Nov 9, 2022

Thanks for digging into this topic, I think it's definitely useful to start thinking about what this might look like in future.

That said, I don't think we should include this in the MVP, just to keep the set of deliverables for the initial proposal minimal. We've managed to get away without type reflection on the existing core spec for years, so it seems unlikely that the GC MVP would suffer greatly from not having such reflection abilities included on day 1.

@rossberg
Copy link
Member

Agreed with @ajklein. I think for the no-frills approach the appropriate cause of action for type reflection would be not to add anything complex yet. We should even drop the additions suggested in the funcref proposal. I think all we'd want to add is the "anyref" value type.

@takikawa
Copy link
Contributor Author

This sounds like a good direction, but I'm not familiar enough with JS APIs to have a solid picture of the details. What would the WebAssembly.Type API look like?

I didn't have a concrete idea for this in mind, but potentially there could be a WebAssembly.Type interface that has at least a kind() method and then perhaps some sub-interfaces for WebAssembly.ArrayType, WebAssembly.RecursiveType, etc. that could provide the detailed information.

That said, I don't think we should include this in the MVP, just to keep the set of deliverables for the initial proposal minimal.

Yes, I'm fine with this approach for now as well. I thought it could make sense to add type reflection if it were straightforward to add an AST-based reflection like have in the base type reflection proposal, but that approach seems limiting for the reasons I wrote earlier.

In that case, would we want type reflection to return a vague type (like collapsing all of these to "anyref") or throw on complex types? (e.g., if you query a global's type and the global uses GC types)

@tlively
Copy link
Member

tlively commented Nov 10, 2022

Is there a forward compatibility story if we approximate to "anyref" now and want to introduce reflection on more refined types in the future? I guess we could add an additional API to access the more complex details of "anyref".

I also think it would be strange to allow reflecting on "anyref" without also allowing anyref values to be passed to functions, so this decision seems intertwined with the discussion in #279.

@rossberg
Copy link
Member

Yes, we definitely should not "weaken" non-expressible types. If something isn't any/func/externref, you cannot reflect it, i.e., the respective .type functions would throw for now. Everything else would not be forward-compatible.

But that is independent from supporting anyref, e.g., it also applies to funcref.

@manoskouk
Copy link
Contributor

One thing we should specify is how types defined in the JS API interact with type canonicalization.
The obvious approach is to canonicalize them as singleton recursive groups (aka types without a recursive group) without supertypes. E.g. if a module defines a recursive group as {externref -> externref, i32 -> i32} and an import of type 0, it should not be possible to pass a WebAssembly.Function of type externref -> externref as that import.

@rossberg
Copy link
Member

rossberg commented Dec 9, 2022

@manoskouk, yes, those are different types, that's a direct consequence of the iso-recursive semantics. As long as the JS API has no syntax for recursive types, it can only create the equivalent of Wasm 2.0 type definitions (which we already reinterpret to be a shorthand for a singleton recursion).

IOW, this follows from our chosen Wasm semantics and the fact that the type reflection API really only defines JS syntax for expressing Wasm type ASTs, it doesn't introduce its own type semantics.

@tlively tlively added Post-MVP Ideas for Post-MVP extensions and removed requires discussion labels Mar 3, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Post-MVP Ideas for Post-MVP extensions
Projects
None yet
Development

No branches or pull requests

5 participants