juntyr opened issue #8915:
I have a resource that is implemented on the host via the
bindgen
macro. In some of its methods, it needs to call back into the guest.When implementing the resource methods for the state struct on the host via the
Host...
trait, I'm given a mutable reference to the state struct (which holds the resource table) and the resource handle (similar to https://docs.rs/wasmtime/latest/wasmtime/component/bindgen_examples/_4_imported_resources/example/imported_resources/logging/trait.HostLogger.html). To call a method in the guest, I need access to the guest resourceGuest...
, and a mutable reference to the store.How do I get access to the store inside a host-side resource method and how could I instantiate the state struct to know about the guest resource?
Thanks for your help!
juntyr edited issue #8915:
I have a resource that is implemented on the host via the
bindgen
macro. In some of its methods, it needs to call back into the guest.When implementing the resource methods for the state struct on the host via the
Host...
trait, I'm given a mutable reference to the state struct (which holds the resource table) and the resource handle (similar to https://docs.rs/wasmtime/latest/wasmtime/component/bindgen_examples/_4_imported_resources/example/imported_resources/logging/trait.HostLogger.html). To call a method in the guest, I need access to the guest resourceGuest...
, and a mutable reference to the store (similar to https://docs.rs/wasmtime/latest/wasmtime/component/bindgen_examples/_6_exported_resources/exports/example/exported_resources/logging/struct.GuestLogger.html).How do I get access to the store inside a host-side resource method and how could I instantiate the state struct to know about the guest resource?
Thanks for your help!
alexcrichton commented on issue #8915:
Currently this isn't possible with
bindgen!
-generated bindings. You'll have to useLinker
APIs directly and thread around theStoreContextMut<T>
that you're given to the point where the recursive call is needed.
juntyr commented on issue #8915:
I briefly tried the threading approach by only minimally modifying the generated trait methods, which was only seems possible with lots of handwavy unsafe code. It looks like I might unfortunately need to do a hybrid approach where I copy-paste the generated linking code but scrap the traits in favour of handwritten inline code to be able to do the shrinking and extension of the store data to and from the host state without unsafe code.
Perhaps at some point this could be supported by the bindings generator, e.g. by passing the state and a function that can temporarily extract the host resource trait being implemented / by passing &mut self as is but also proving a function that temporarily re-extends &mut self back into the store context (this would probably require unsafe in the implementation but would still be a safe interface to provide).
The other aspect I’m still unclear on is how to give the host resource access to the guest resource, since the former is defined before instantiating when setting up the linker, and the latter is only available once an instance has been created. Do you have any hints for that?
alexcrichton commented on issue #8915:
One option I've considered in the past is that this could be done on a per-method basis. For example most trait methods could take
&mut self
but they could be allow-listed to takestore: StoreContextMut<T>
instead. I'm not entirely sure how theT
would be specified though but that might be part of the macro configuration. Basically though putting this in the macro configuration as another option I think is reasonable.The other aspect I’m still unclear on is how to give the host resource access to the guest resource, since the former is defined before instantiating when setting up the linker, and the latter is only available once an instance has been created. Do you have any hints for that?
Do you have an example to take a look at? I can't quite wrap my head around what the problem is from this description but an example would be helpful
juntyr commented on issue #8915:
One option I've considered in the past is that this could be done on a per-method basis. For example most trait methods could take
&mut self
but they could be allow-listed to takestore: StoreContextMut<T>
instead. I'm not entirely sure how theT
would be specified though but that might be part of the macro configuration. Basically though putting this in the macro configuration as another option I think is reasonable.That sounds like a good idea! While manually updating all bindings code was definitely some work, it was interesting to do, though I would of course prefer being able to automate this in the future.
The other aspect I’m still unclear on is how to give the host resource access to the guest resource, since the former is defined before instantiating when setting up the linker, and the latter is only available once an instance has been created. Do you have any hints for that?
Do you have an example to take a look at? I can't quite wrap my head around what the problem is from this description but an example would be helpful
Yes, I moved forward as far as I could and can now show my remaining problem.
This is from my slow-burning attempt to make a crate to support cross-component serde by encoding the serializer + deserializer protocol using wit. In this latest attempt to see how far I can go, I have focused only on the
Serialize
on the guest andSerializer
on the host part, for which the wit file is here: https://github.com/juntyr/serde-wit/blob/wasmtime-ser/wit/serde-ser.witLet's take the example of
serialize_some
, which is added to the linker here: https://github.com/juntyr/serde-wit/blob/542a831d91e271a455ea3be6b383b62e15472256/src/ser/wasmtime_host/provider/bindings.rs#L524-L558. After some setup, we need to wrap the to-serialize resource we got from the guest insideSerializableSerialize
so thatserde
can serialize it, which happens by calling theserialize
method on the guest here: https://github.com/juntyr/serde-wit/blob/542a831d91e271a455ea3be6b383b62e15472256/src/ser/wasmtime_host/provider/bindings.rs#L543-L544.The question is where to get the
guest
from. At the moment, theSerializableSerialize
just provides it via atodo!()
here: https://github.com/juntyr/serde-wit/blob/542a831d91e271a455ea3be6b383b62e15472256/src/ser/wasmtime_host/provider.rs#L892.So, to sum up, we're inside a hostside resource method and need to call a guestside resource method. The hostside method is defined when setting up the linker, but as far as I can tell the guestside method is only retrieved when we instantiate the components after linking (at least that's what the bindings code seems to do). Is there a way in the hostside callback to dynamically get the guestside method, or can we somehow feed the guestside method back into the hostside code?
Thanks for your help!
juntyr commented on issue #8915:
It seems like the core module linker provides host functions access to a caller struct (https://docs.rs/wasmtime/latest/wasmtime/struct.Caller.html), which allows looking up exports, here: https://docs.rs/wasmtime/latest/wasmtime/struct.Linker.html#method.func_new
But the component linker only provides the mutable store context here: https://docs.rs/wasmtime/latest/wasmtime/component/struct.LinkerInstance.html#method.func_wrap
If this functionality is currently not possible, perhaps the component linker could also give host functions access to a caller struct?
alexcrichton commented on issue #8915:
Thanks for detailing a bit more, I can definitely see how this is tricky. From a WIT perspective what you're describing is not possible to do because imports cannot refer to types in the exports. Much of the design of the component model and embedding API and such reflects this and will sort of echo this sentiment.
If you have enough ahead-of-time knowledge what you'll be required to do is to load the exports of the component and put them in the
T
ofStore<T>
ahead of time. They can later be accessed during the host import call.As for
Caller
, that's something that we'd ideally remove from the core API. It's a "hack" to make things like WASI easier to use and generally make it more convenient to work with, but it's not present on the web for example and isn't available in many other engines. The definition ofCaller
sort of makes sense for core wasm but it's much more nebulous with components because it's not clear if the calling component is a deeply nested component in a component graph or the outermost component. That all basically makes it where even if we wanted to giveCaller
to components (which I don't think we do) it would be difficult to do so.
juntyr commented on issue #8915:
Thanks for detailing a bit more, I can definitely see how this is tricky. From a WIT perspective what you're describing is not possible to do because imports cannot refer to types in the exports. Much of the design of the component model and embedding API and such reflects this and will sort of echo this sentiment.
I came across this design restriction several months ago already. At the moment, my hacky solution is to manually lower and lift some resources into integer handles to break this cycle. This does make the protocol work, but I see how the design also informs what APIs can then be written.
If you have enough ahead-of-time knowledge what you'll be required to do is to load the exports of the component and put them in the
T
ofStore<T>
ahead of time. They can later be accessed during the host import call.That sounds like a reasonable approach to me. As the interface has both an export and an import part, which bindgen exposes through initialise methods anyways, I think I could adapt this API to stash the guest exports into the host state in the store.
While I think this would work if only one component is initialised, do you have an idea for what would could be done when multiple components are initialised? When the host export is called from a component, how could I find out which instance called it, so that I could look up the corresponding guest export stashed inside the host store state?
As for
Caller
, that's something that we'd ideally remove from the core API. It's a "hack" to make things like WASI easier to use and generally make it more convenient to work with, but it's not present on the web for example and isn't available in many other engines. The definition ofCaller
sort of makes sense for core wasm but it's much more nebulous with components because it's not clear if the calling component is a deeply nested component in a component graph or the outermost component. That all basically makes it where even if we wanted to giveCaller
to components (which I don't think we do) it would be difficult to do so.Thank you so much for giving me this context!
alexcrichton commented on issue #8915:
While I think this would work if only one component is initialised, do you have an idea for what would could be done when multiple components are initialised? When the host export is called from a component, how could I find out which instance called it, so that I could look up the corresponding guest export stashed inside the host store state?
I think the best approximation is on the embedder side you store within
T
ofStore<T>
which component was the last to start the call. That way when an import is called you can read that and see which component started the call originally. Would that work?
Last updated: Dec 23 2024 at 13:07 UTC