bytecodealliance / wasmtime

A fast and secure runtime for WebAssembly
https://wasmtime.dev/
Apache License 2.0
15.38k stars 1.3k forks source link

Get instance data in host defined functions with component model #6848

Open bobogei81123 opened 1 year ago

bobogei81123 commented 1 year ago

Feature

Provide instance (Instance struct) or instance data to host defined functions in component model.

Benefit

In #2491, it is suggested to bind the instance data to the closure passed to Func::wrap to store per-instance data. However, this will not work for instances in component model because to bind a function (let host export a function to be called by guests), one need to configure LinkerInstance (e.g., use LinkerInstance::func_wrap or similar methods). Yet, there is only a single linker set up for instantiating all the instances in a store, so there is no way to configure per-instance data.

Providing host function instance and instance data will let host function able to get data tied to the instance that calls the function, or let host perform a nested call to a guest function, etc.

Implementation

I think a possible implementation is to let the function passed to LinkerInstance::func_wrap to be able to take Caller instead of StoreContextMut. The non-component model counterpart Func::wrap takes AsContextMut to achieve this, and I'm not sure why func_wrap takes StoreContextMut instead. Then, we can expose the instance stored in the Caller struct. Of course, this all rely on the assumption that Caller can be retrieve in calls to component host.

alexcrichton commented 1 year ago

You mention that a single linker is used for a store, but that's not quite accurate because you can use any number of linkers to instantiate within a Store. In that sense you can still provide per-function state within the Linker. Otherwise though I'll also note that the question there was in reference to the C API and the Rust API is a bit different. In Rust the closures in Linker are Fn meaning you don't get mutable access to closed-over-state. Instead that sort of information typically goes within the T of a Store<T> which you do get mutable access to.

So, if possible, the recommendation is to store per-instance information into a T in Store<T> and only if necessary store per-instance information within the closures passed to a Linker. Does that work for you?

bobogei81123 commented 1 year ago

I'll also note that the question there was in reference to the C API and the Rust API is a bit different.

Sorry for not stating the question clearly. I'm using the rust API. Otherwise

So, if possible, the recommendation is to store per-instance information into a T in Store and only if necessary store per-instance information within the closures passed to a Linker. Does that work for you?

I'm a little bit confused here because I thought there can be multiple instances inside a single store. My setup is as follows: First I create a global Engine and Store:

fn init() {
  engine = Engine::new(config)?;
  store = Store::new(...);
  linker = Linker::new(&engine);
  MyWasmWorld::add_to_linker(&mut linker, ...)?;
  ...
}

Then, when a plugin (guest program) is loaded, I load the wasm file and instantiate the component

fn load_plugin_from_path(file_path: &str) {
  Component::from_file(engine, file_path)?;
  let (_, instance) = MyWasmWorld::instantiate(&mut store, &component, &linker)?;
  ...
}

My understanding is that load_plugin_from_path can be called multiple time to instantiate multiple plugins in the same store, so I'll need a way to identify which instance is calling the host function.

Or should I use multiple stores for different plugins? But even then, I still need to get the Instance handle to get its exports so I cn call guest functions or inspect its memory, is that correct?

bobogei81123 commented 1 year ago

Oh, I think I get what you are suggesting, I can create one linker each time I instantiate a plugin. Let me try if that works.

alexcrichton commented 1 year ago

Ah yes it's possible to put lots of instances in the same store. The downside of that approach is that you can't incrementally delete instances within the store, it's an all-or-nothing operation (e.g. you can't unload just one plugin, you'd have to unload all of them).

If possible I'd recommend having a Store-per-plugin. With the component abstraction it's not actually necessary to have everything in one store as that's primarily there for core wasm instance linking and shared memory, which the component model doesn't support.

Storing state per-linker would still work, however, if that works for you!

richardpringle commented 1 week ago

@alexcrichton, not sure if this is the best place to ask the question, but is there a clean way to call guest functions from without a host function? (I'm not currently using the component model, if that makes a difference).

I have a bunch of host functions that allocate their results. To do so, they rely on an allocate function exported from the wasm-module. I know I can dynamically get the export and do all the checking, but is there a way to do this at link-time?

It would be nice if there was a way for Linker::instantiate to check for exports in addition to imports in a way that would enable me to make safe static calls back into the module.

Or maybe I just want to expose Instance::get_module_export off of Caller so I don't have to do a lookup by name every time and put the ModuleExport in the Store-data?

Should I be thinking about allocation differently? Is there another way to do this? Please advise if there's a better place to post too.

alexcrichton commented 1 week ago

I'll follow up on https://github.com/bytecodealliance/wasmtime/pull/9525 as this probably isn't the best place to continue this discussion (but no worries!)