Skip to content

Move the GetHost trait used in bindgen! into Wasmtime #10746

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

Merged
merged 2 commits into from
May 7, 2025

Conversation

alexcrichton
Copy link
Member

Turns out we don't actually need to generate this GetHost trait, we can instead have it live in one location with extra documentation. There are already extra bounds on the Host associated type at all call-sites so there's no need to additionally have trait bounds in the trait definition, meaning the trait definition is always the same and it can move within Wasmtime.

This shouldn't have any impact on any embedders today, it's just moving things around.

Turns out we don't actually need to generate this `GetHost` trait, we
can instead have it live in one location with extra documentation. There
are already extra bounds on the `Host` associated type at all call-sites
so there's no need to additionally have trait bounds in the trait
definition, meaning the trait definition is always the same and it can
move within Wasmtime.

This shouldn't have any impact on any embedders today, it's just moving
things around.
@alexcrichton alexcrichton requested a review from a team as a code owner May 7, 2025 18:47
@alexcrichton alexcrichton requested review from pchickey and dicej and removed request for a team May 7, 2025 18:47
}}
"
);
// let get_host_bounds = if let CallStyle::Concurrent = self.opts.call_style() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we just remove this code rather than comment it out?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops yes that's my mistake!

@alexcrichton alexcrichton enabled auto-merge May 7, 2025 20:49
@alexcrichton alexcrichton added this pull request to the merge queue May 7, 2025
Merged via the queue into bytecodealliance:main with commit 29d04b1 May 7, 2025
41 checks passed
@alexcrichton alexcrichton deleted the one-get-host branch May 7, 2025 21:24
alexcrichton added a commit to alexcrichton/wasmtime that referenced this pull request May 12, 2025
This commit is a refactoring to the fundamentals of the `bindgen!` macro
and the functions that it generates. Prior to this change the
fundamental entrypoint generated by `bindgen!` was a function
`add_to_linker_get_host` which takes a value of type `G: GetHost`. This
`GetHost` implementation is effectively an alias for a closure whose
return value is able to close over the parameter given lfietime-wise.

The `GetHost` abstraction was added to Wasmtime originally to enable
using any type that implements `Host` traits, not just `&mut U` as was
originally supported. The definition of `GetHost` was _just_ right to
enable a type such as `MyThing<&mut T>` to implement `Host` and a
closure could be provided that could return it. At the time that
`GetHost` was added it was known to be problematic from an
understandability point of view, namely:

* It has a non-obvious definition.
* It's pretty advanced Rust voodoo to understand what it's actually
  doing
* Using `GetHost` required lots of `for<'a> ...` in places which is
  unfamiliar syntax for many.
* `GetHost` values couldn't be type-erased (e.g. put in a trait object)
  as we couldn't figure out the lifetime syntax to do so.

Despite these issues it was the only known solution at hand so we landed
it and kept the previous `add_to_linker` style (`&mut T -> &mut U`) as a
convenience. While this has worked reasonable well (most folks just try
to not look at `GetHost`) it has reached a breaking point in the WASIp3
work.

In the WASIp3 work it's effectively now going to be required that the
`G: GetHost` value is packaged up and actually stored inside of
accessors provided to host functions. This means that `GetHost` values
now need to not only be taken in `add_to_linker` but additionally
provided to the rest of the system through an "accessor". This was made
possible in bytecodealliance#10746 by moving the `GetHost` type into Wasmtime itself (as
opposed to generated code where it lived prior).

While this worked with WASIp3 and it was possible to plumb `G: GetHost`
safely around, this ended up surfacing more issues. Namely all
"concurrent" host functions started getting significantly more
complicated `where` clauses and type signatures. At the end of the day I
felt that we had reached the end of the road to `GetHost` and wanted to
search for alternatives, hence this change.

The fundamental purpose of `GetHost` was to be able to express, in a
generic fashion:

* Give me a closure that takes `&mut T` and returns `D`.
* The `D` type can close over the lifetime in `&mut T`.
* The `D` type must implement `bindgen!`-generated traits.

A realization I had was that we could model this with a generic
associated type in Rust. Rust support for generic associated types is
relatively new and not something I've used much before, but it ended up
being a perfect model for this. The definition of the new `HasData`
trait is deceptively simple:

    trait HasData {
        type Data<'a>;
    }

What this enables us to do though is to generate `add_to_linker`
functions that look like this:

    fn add_to_linker<T, D>(
        linker: &mut Linker<T>,
        getter: fn(&mut T) -> D::Data<'_>,
    ) -> Result<()>
      where
        D: HasData,
        for<'a> D::Data<'a>: Host;

This definition here models `G: GetHost` as a literal function pointer,
and the ability to close over the `&mut T` lifetime with type (not just
`&mut U`) is expressed through the type constructor `type Data<'a>`).
Ideally we could take a generic generic associated type (I'm not even
sure what to call that), but that's not something Rust has today.

Overall this felt like a much simpler way of modeling `GetHost` and its
requirements. This plumbed well throughout the WASIp3 work and the
signatures for concurrent functions felt much more appropriate in terms
of complexity after this change. Taking this change to the limit means
that `GetHost` in its entirety could be purged since all usages of it
could be replaced with `fn(&mut T) -> D::Data<'a>`, a hopefully much
more understandable type.

This change is not all rainbows however, there are some gotchas that
remain:

* One is that all `add_to_linker` generated functions have a `D:
  HasData` type parameter. This type parameter cannot be inferred and
  must always be explicitly specified, and it's not easy to know what to
  supply here without reading documentation. Actually supplying the type
  parameter is quite easy once you know what to do (and what to fill
  in), but it may involve defining a small struct with a custom
  `HasData` implementation which can be non-obvious.

* Another is that the `G: GetHost` value was previously a full Rust
  closure, but now it's transitioning to a function pointer. This is
  done in preparation for WASIp3 work where the function needs to be
  passed around, and doing that behind a generic parameter is more
  effort than it's worth. This means that embedders relying on the true
  closure-like nature here will have to update to using a function
  pointer instead.

* The function pointer is stored in locations that require `'static`,
  and while `fn(T)` might be expected to be `'static` regardless of `T`
  is is, in fact, not. This means that practically `add_to_linker`
  requires `T: 'static`. Relative to just before this change this is a
  possible regression in functionality, but there orthogonal reasons
  beyond just this that we want to start requiring `T: 'static` anyway.
  That means that this isn't actually a regression relative to bytecodealliance#10760, a
  related change.

The first point is partially ameliorated with WASIp3 work insofar that
the `D` type parameter will start serving as a location to specify where
concurrent implementations are found. These concurrent methods don't
take `&mut self` but instead are implemented for `T: HasData` types. In
that sense it's more justified to have this weird type parameter, but in
the meantime without this support it'll feel a bit odd to have this
little type parameter hanging off the side.

This change has been integrated into the WASIp3 prototyping repository
with success. This has additionally been integrated into the Spin
embedding which has one of the more complicated reliances on
`*_get_host` functions known. Given that it's expected that while this
is not necessarily a trivial change to rebase over it should at least be
possible.

Finally the `HasData` trait here has been included with what I'm hoping
is a sufficient amount of documentation to at least give folks a spring
board to understand it. If folks have confusion about this `D` type
parameter my hope is they'll make their way to `HasData` which showcases
various patterns for "librarifying" host implementations of WIT
interfaces. These patterns are all used throughout Wasmtime and WASI
currently in crates and tests and such.
alexcrichton added a commit to alexcrichton/wasmtime that referenced this pull request May 13, 2025
This commit is a refactoring to the fundamentals of the `bindgen!` macro
and the functions that it generates. Prior to this change the
fundamental entrypoint generated by `bindgen!` was a function
`add_to_linker_get_host` which takes a value of type `G: GetHost`. This
`GetHost` implementation is effectively an alias for a closure whose
return value is able to close over the parameter given lfietime-wise.

The `GetHost` abstraction was added to Wasmtime originally to enable
using any type that implements `Host` traits, not just `&mut U` as was
originally supported. The definition of `GetHost` was _just_ right to
enable a type such as `MyThing<&mut T>` to implement `Host` and a
closure could be provided that could return it. At the time that
`GetHost` was added it was known to be problematic from an
understandability point of view, namely:

* It has a non-obvious definition.
* It's pretty advanced Rust voodoo to understand what it's actually
  doing
* Using `GetHost` required lots of `for<'a> ...` in places which is
  unfamiliar syntax for many.
* `GetHost` values couldn't be type-erased (e.g. put in a trait object)
  as we couldn't figure out the lifetime syntax to do so.

Despite these issues it was the only known solution at hand so we landed
it and kept the previous `add_to_linker` style (`&mut T -> &mut U`) as a
convenience. While this has worked reasonable well (most folks just try
to not look at `GetHost`) it has reached a breaking point in the WASIp3
work.

In the WASIp3 work it's effectively now going to be required that the
`G: GetHost` value is packaged up and actually stored inside of
accessors provided to host functions. This means that `GetHost` values
now need to not only be taken in `add_to_linker` but additionally
provided to the rest of the system through an "accessor". This was made
possible in bytecodealliance#10746 by moving the `GetHost` type into Wasmtime itself (as
opposed to generated code where it lived prior).

While this worked with WASIp3 and it was possible to plumb `G: GetHost`
safely around, this ended up surfacing more issues. Namely all
"concurrent" host functions started getting significantly more
complicated `where` clauses and type signatures. At the end of the day I
felt that we had reached the end of the road to `GetHost` and wanted to
search for alternatives, hence this change.

The fundamental purpose of `GetHost` was to be able to express, in a
generic fashion:

* Give me a closure that takes `&mut T` and returns `D`.
* The `D` type can close over the lifetime in `&mut T`.
* The `D` type must implement `bindgen!`-generated traits.

A realization I had was that we could model this with a generic
associated type in Rust. Rust support for generic associated types is
relatively new and not something I've used much before, but it ended up
being a perfect model for this. The definition of the new `HasData`
trait is deceptively simple:

    trait HasData {
        type Data<'a>;
    }

What this enables us to do though is to generate `add_to_linker`
functions that look like this:

    fn add_to_linker<T, D>(
        linker: &mut Linker<T>,
        getter: fn(&mut T) -> D::Data<'_>,
    ) -> Result<()>
      where
        D: HasData,
        for<'a> D::Data<'a>: Host;

This definition here models `G: GetHost` as a literal function pointer,
and the ability to close over the `&mut T` lifetime with type (not just
`&mut U`) is expressed through the type constructor `type Data<'a>`).
Ideally we could take a generic generic associated type (I'm not even
sure what to call that), but that's not something Rust has today.

Overall this felt like a much simpler way of modeling `GetHost` and its
requirements. This plumbed well throughout the WASIp3 work and the
signatures for concurrent functions felt much more appropriate in terms
of complexity after this change. Taking this change to the limit means
that `GetHost` in its entirety could be purged since all usages of it
could be replaced with `fn(&mut T) -> D::Data<'a>`, a hopefully much
more understandable type.

This change is not all rainbows however, there are some gotchas that
remain:

* One is that all `add_to_linker` generated functions have a `D:
  HasData` type parameter. This type parameter cannot be inferred and
  must always be explicitly specified, and it's not easy to know what to
  supply here without reading documentation. Actually supplying the type
  parameter is quite easy once you know what to do (and what to fill
  in), but it may involve defining a small struct with a custom
  `HasData` implementation which can be non-obvious.

* Another is that the `G: GetHost` value was previously a full Rust
  closure, but now it's transitioning to a function pointer. This is
  done in preparation for WASIp3 work where the function needs to be
  passed around, and doing that behind a generic parameter is more
  effort than it's worth. This means that embedders relying on the true
  closure-like nature here will have to update to using a function
  pointer instead.

* The function pointer is stored in locations that require `'static`,
  and while `fn(T)` might be expected to be `'static` regardless of `T`
  is is, in fact, not. This means that practically `add_to_linker`
  requires `T: 'static`. Relative to just before this change this is a
  possible regression in functionality, but there orthogonal reasons
  beyond just this that we want to start requiring `T: 'static` anyway.
  That means that this isn't actually a regression relative to bytecodealliance#10760, a
  related change.

The first point is partially ameliorated with WASIp3 work insofar that
the `D` type parameter will start serving as a location to specify where
concurrent implementations are found. These concurrent methods don't
take `&mut self` but instead are implemented for `T: HasData` types. In
that sense it's more justified to have this weird type parameter, but in
the meantime without this support it'll feel a bit odd to have this
little type parameter hanging off the side.

This change has been integrated into the WASIp3 prototyping repository
with success. This has additionally been integrated into the Spin
embedding which has one of the more complicated reliances on
`*_get_host` functions known. Given that it's expected that while this
is not necessarily a trivial change to rebase over it should at least be
possible.

Finally the `HasData` trait here has been included with what I'm hoping
is a sufficient amount of documentation to at least give folks a spring
board to understand it. If folks have confusion about this `D` type
parameter my hope is they'll make their way to `HasData` which showcases
various patterns for "librarifying" host implementations of WIT
interfaces. These patterns are all used throughout Wasmtime and WASI
currently in crates and tests and such.
github-merge-queue bot pushed a commit that referenced this pull request May 13, 2025
* Replace `GetHost` with a function pointer, add `HasData`

This commit is a refactoring to the fundamentals of the `bindgen!` macro
and the functions that it generates. Prior to this change the
fundamental entrypoint generated by `bindgen!` was a function
`add_to_linker_get_host` which takes a value of type `G: GetHost`. This
`GetHost` implementation is effectively an alias for a closure whose
return value is able to close over the parameter given lfietime-wise.

The `GetHost` abstraction was added to Wasmtime originally to enable
using any type that implements `Host` traits, not just `&mut U` as was
originally supported. The definition of `GetHost` was _just_ right to
enable a type such as `MyThing<&mut T>` to implement `Host` and a
closure could be provided that could return it. At the time that
`GetHost` was added it was known to be problematic from an
understandability point of view, namely:

* It has a non-obvious definition.
* It's pretty advanced Rust voodoo to understand what it's actually
  doing
* Using `GetHost` required lots of `for<'a> ...` in places which is
  unfamiliar syntax for many.
* `GetHost` values couldn't be type-erased (e.g. put in a trait object)
  as we couldn't figure out the lifetime syntax to do so.

Despite these issues it was the only known solution at hand so we landed
it and kept the previous `add_to_linker` style (`&mut T -> &mut U`) as a
convenience. While this has worked reasonable well (most folks just try
to not look at `GetHost`) it has reached a breaking point in the WASIp3
work.

In the WASIp3 work it's effectively now going to be required that the
`G: GetHost` value is packaged up and actually stored inside of
accessors provided to host functions. This means that `GetHost` values
now need to not only be taken in `add_to_linker` but additionally
provided to the rest of the system through an "accessor". This was made
possible in #10746 by moving the `GetHost` type into Wasmtime itself (as
opposed to generated code where it lived prior).

While this worked with WASIp3 and it was possible to plumb `G: GetHost`
safely around, this ended up surfacing more issues. Namely all
"concurrent" host functions started getting significantly more
complicated `where` clauses and type signatures. At the end of the day I
felt that we had reached the end of the road to `GetHost` and wanted to
search for alternatives, hence this change.

The fundamental purpose of `GetHost` was to be able to express, in a
generic fashion:

* Give me a closure that takes `&mut T` and returns `D`.
* The `D` type can close over the lifetime in `&mut T`.
* The `D` type must implement `bindgen!`-generated traits.

A realization I had was that we could model this with a generic
associated type in Rust. Rust support for generic associated types is
relatively new and not something I've used much before, but it ended up
being a perfect model for this. The definition of the new `HasData`
trait is deceptively simple:

    trait HasData {
        type Data<'a>;
    }

What this enables us to do though is to generate `add_to_linker`
functions that look like this:

    fn add_to_linker<T, D>(
        linker: &mut Linker<T>,
        getter: fn(&mut T) -> D::Data<'_>,
    ) -> Result<()>
      where
        D: HasData,
        for<'a> D::Data<'a>: Host;

This definition here models `G: GetHost` as a literal function pointer,
and the ability to close over the `&mut T` lifetime with type (not just
`&mut U`) is expressed through the type constructor `type Data<'a>`).
Ideally we could take a generic generic associated type (I'm not even
sure what to call that), but that's not something Rust has today.

Overall this felt like a much simpler way of modeling `GetHost` and its
requirements. This plumbed well throughout the WASIp3 work and the
signatures for concurrent functions felt much more appropriate in terms
of complexity after this change. Taking this change to the limit means
that `GetHost` in its entirety could be purged since all usages of it
could be replaced with `fn(&mut T) -> D::Data<'a>`, a hopefully much
more understandable type.

This change is not all rainbows however, there are some gotchas that
remain:

* One is that all `add_to_linker` generated functions have a `D:
  HasData` type parameter. This type parameter cannot be inferred and
  must always be explicitly specified, and it's not easy to know what to
  supply here without reading documentation. Actually supplying the type
  parameter is quite easy once you know what to do (and what to fill
  in), but it may involve defining a small struct with a custom
  `HasData` implementation which can be non-obvious.

* Another is that the `G: GetHost` value was previously a full Rust
  closure, but now it's transitioning to a function pointer. This is
  done in preparation for WASIp3 work where the function needs to be
  passed around, and doing that behind a generic parameter is more
  effort than it's worth. This means that embedders relying on the true
  closure-like nature here will have to update to using a function
  pointer instead.

* The function pointer is stored in locations that require `'static`,
  and while `fn(T)` might be expected to be `'static` regardless of `T`
  is is, in fact, not. This means that practically `add_to_linker`
  requires `T: 'static`. Relative to just before this change this is a
  possible regression in functionality, but there orthogonal reasons
  beyond just this that we want to start requiring `T: 'static` anyway.
  That means that this isn't actually a regression relative to #10760, a
  related change.

The first point is partially ameliorated with WASIp3 work insofar that
the `D` type parameter will start serving as a location to specify where
concurrent implementations are found. These concurrent methods don't
take `&mut self` but instead are implemented for `T: HasData` types. In
that sense it's more justified to have this weird type parameter, but in
the meantime without this support it'll feel a bit odd to have this
little type parameter hanging off the side.

This change has been integrated into the WASIp3 prototyping repository
with success. This has additionally been integrated into the Spin
embedding which has one of the more complicated reliances on
`*_get_host` functions known. Given that it's expected that while this
is not necessarily a trivial change to rebase over it should at least be
possible.

Finally the `HasData` trait here has been included with what I'm hoping
is a sufficient amount of documentation to at least give folks a spring
board to understand it. If folks have confusion about this `D` type
parameter my hope is they'll make their way to `HasData` which showcases
various patterns for "librarifying" host implementations of WIT
interfaces. These patterns are all used throughout Wasmtime and WASI
currently in crates and tests and such.

* Update expanded test expectations
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

Successfully merging this pull request may close these issues.

2 participants