rygo6 opened issue #3562:
This is kind of a wasmTime issue. But probably more so I am just new to WasmTime and Rust trying to figure this out.
I have a scenario where a linked imported function needs to look up an exported table and call a function from it.
This is basically what I have hacked together thus far:
pub struct EmWasmNode { wasmStore: Store<WasiCtx>, wasmTable: Table, } impl EmWasmNode { pub fn new(filePath: &str) -> Result<Self> { let engine = Engine::default(); let module = Module::from_file(&engine, filePath)?; let mut linker = Linker::new(&engine); wasmtime_wasi::add_to_linker(&mut linker, |s| s)?; let wasi = WasiCtxBuilder::new() .inherit_stdio() .inherit_args()? .build(); let mut store = Store::new(&engine, wasi); linker.func_wrap("env", "emscripten_set_main_loop", |p0: i32, p1: i32, p2: i32| { /***** How would I access wasmTable and wasmStore from here to execute more methods??? *****/ //let browserIterationFuncOption:Option<wasmtime::Val> = Self::wasmTable.get(&mut Self::wasmStore, p0 as u32); // browserIterationFuncOption.unwrap().unwrap_funcref().call(&store, ()); })?; let instance = linker.instantiate(&mut store, &module)?; let table = instance .get_export(&mut store, "__indirect_function_table") .and_then(extern_table) .unwrap(); let start = instance.get_typed_func::<(), (), _>(&mut store, "_start")?; start.call(&mut store, ())?; Ok(EmWasmNode { wasmStore: store, wasmTable: table, }) }
I've tried a couple different routes to accomplish this, but neither have succeeded. This seems like an exceedingly simple thing that has been taking me hours of failure. Is there something obvious I am missing about how WasmTime or Rust is supposed to work here?
alexcrichton commented on issue #3562:
Thanks for the report, and I'd recommend browsing some of the examples in this repository to get an idea about some of the idioms for Rust and imported functions.
One of the first things you'll want to do is to use a
Caller<'_, T>
as the first argument to youremscripten_set_main_loop
closure. That will allow you to call the.get_export
function or otherwise access closed-over values.For getting the
store
and thetable
into the closure you can either use.get_export
or you can use something similar toRc<RefCell<...>>
to start out atNone
and then set toSome
after the instance has been instantiated and the exports found on the instance.
rygo6 commented on issue #3562:
So it looks like the called can't export the table right now? Says its disallowed?
pub fn get_export(&mut self, name: &str) -> Option<Extern> { // All instances created have a `host_state` with a pointer pointing // back to themselves. If this caller doesn't have that `host_state` // then it probably means it was a host-created object like `Func::new` // which doesn't have any exports we want to return anyway. match self .caller .host_state() .downcast_ref::<Instance>()? .get_export(&mut self.store, name)? { Extern::Func(f) => Some(Extern::Func(f)), Extern::Memory(f) => Some(Extern::Memory(f)), // Intentionally ignore other Extern items here since this API is // supposed to be a temporary stop-gap until interface types. _ => None, } }
Also I tried use a point, RC or ARC and trapping that in the enclosure. But it wouldn't let me because the enclosure has static lifetime and so not variable from the method or impl can be captured in the closure. I considered making a 'static' datastore for tables outside of the impl and passing that in, but that just seems so contrived?
rygo6 edited a comment on issue #3562:
So it looks like the called can't export the table right now? Says its disallowed?
pub fn get_export(&mut self, name: &str) -> Option<Extern> { // All instances created have a `host_state` with a pointer pointing // back to themselves. If this caller doesn't have that `host_state` // then it probably means it was a host-created object like `Func::new` // which doesn't have any exports we want to return anyway. match self .caller .host_state() .downcast_ref::<Instance>()? .get_export(&mut self.store, name)? { Extern::Func(f) => Some(Extern::Func(f)), Extern::Memory(f) => Some(Extern::Memory(f)), // Intentionally ignore other Extern items here since this API is // supposed to be a temporary stop-gap until interface types. _ => None, } }
Also I tried use a pointer, RC or ARC and trapping that in the enclosure. But it wouldn't let me because the enclosure has static lifetime and so not variable from the method or impl can be captured in the closure. I considered making a 'static' datastore for tables outside of the impl and passing that in, but that just seems so contrived?
rygo6 edited a comment on issue #3562:
So it looks like the caller can't export the table right now? Says its disallowed?
pub fn get_export(&mut self, name: &str) -> Option<Extern> { // All instances created have a `host_state` with a pointer pointing // back to themselves. If this caller doesn't have that `host_state` // then it probably means it was a host-created object like `Func::new` // which doesn't have any exports we want to return anyway. match self .caller .host_state() .downcast_ref::<Instance>()? .get_export(&mut self.store, name)? { Extern::Func(f) => Some(Extern::Func(f)), Extern::Memory(f) => Some(Extern::Memory(f)), // Intentionally ignore other Extern items here since this API is // supposed to be a temporary stop-gap until interface types. _ => None, } }
Also I tried use a pointer, RC or ARC and trapping that in the enclosure. But it wouldn't let me because the enclosure has static lifetime and so not variable from the method or impl can be captured in the closure. I considered making a 'static' datastore for tables outside of the impl and passing that in, but that just seems so contrived?
rygo6 edited a comment on issue #3562:
So it looks like the caller can't export the table right now? Says its disallowed?
pub fn get_export(&mut self, name: &str) -> Option<Extern> { // All instances created have a `host_state` with a pointer pointing // back to themselves. If this caller doesn't have that `host_state` // then it probably means it was a host-created object like `Func::new` // which doesn't have any exports we want to return anyway. match self .caller .host_state() .downcast_ref::<Instance>()? .get_export(&mut self.store, name)? { Extern::Func(f) => Some(Extern::Func(f)), Extern::Memory(f) => Some(Extern::Memory(f)), // Intentionally ignore other Extern items here since this API is // supposed to be a temporary stop-gap until interface types. _ => None, } }
Also I tried use a pointer, RC or ARC and capturing that in the enclosure. But it wouldn't let me because the enclosure has static lifetime and so not variable from the method or impl can be captured in the closure. I considered making a 'static' datastore for tables outside of the impl and passing that in, but that just seems so contrived?
rygo6 edited a comment on issue #3562:
So it looks like the caller can't export the table right now? Says its disallowed?
pub fn get_export(&mut self, name: &str) -> Option<Extern> { // All instances created have a `host_state` with a pointer pointing // back to themselves. If this caller doesn't have that `host_state` // then it probably means it was a host-created object like `Func::new` // which doesn't have any exports we want to return anyway. match self .caller .host_state() .downcast_ref::<Instance>()? .get_export(&mut self.store, name)? { Extern::Func(f) => Some(Extern::Func(f)), Extern::Memory(f) => Some(Extern::Memory(f)), // Intentionally ignore other Extern items here since this API is // supposed to be a temporary stop-gap until interface types. _ => None, } }
Also I tried use a pointer, RC or ARC and capturing that in the enclosure. But it wouldn't let me because the enclosure has static lifetime and so no variable from the method or impl can be captured in the closure. I considered making a 'static' global datastore for tables outside of the impl and passing that in, but that just seems so contrived?
alexcrichton commented on issue #3562:
Ah yes that's a restriction we can likely lift (and probably lift for all other types as well) given the direction that interface types is going in.
Otherwise the recommended way to store data for closures is not actually to close over them but rather use the
T
inStore<T>
. That way within theT
you could have something liketable: Option<wasmtime::Table>
which is filled in after it's instantiated/created. Then the closure can takeCaller<'_, T>
as the first parameter and access thetable
field, asserting it'sSome
or otherwise returning a trap.
xpepermint commented on issue #3562:
@alexcrichton so how would you then call a host function from another host function?
alexcrichton commented on issue #3562:
Hm sorry I'm not sure I understand, @xpepermint can you clarify your question or do you have some example code to poke around?
xpepermint commented on issue #3562:
@alexcrichton sorry about that. The title is kinda related to my question and I hope this won't shadow @rygo6's original post. Here's the code:
let mut config = Config::new(); let engine = Engine::new(&config).unwrap(); let module_ecno = Module::from_file(&engine, "....wasm").unwrap(); let mut linker = Linker::new(&engine); linker.func_wrap1_async("host", "fn1", |mut caller, ptr: i32| Box::new(async move { let state = caller.data(); // OK let mem = caller.get_export("memory").unwrap().into_memory().unwrap(); // I think OK let instance = caller.get_export("???").unwrap(); // how // **** how do I get the instance **** // **** CALL fn2 in the same context **** Ok(100) })).unwrap(); linker.func_wrap1_async("host", "fn2", |mut caller, ptr: i32| Box::new(async move { Ok(100) })).unwrap(); wasmtime_wasi::add_to_linker(&mut linker, |s| &mut s).unwrap(); ...
So, how do you call a host-defined function from a host-defined function? If what I'm trying to do is not the right way of using it, please explain how should host function be communicating with the rest of the (outside context) program.
alexcrichton commented on issue #3562:
Can you refactor it such that
fn2
is defined as a Rust-native function and call that directly?Otherwise unless you close over the
wasmtime::Func
value in theStore<T>
or similar you can't access it directly.
xpepermint commented on issue #3562:
@alexcrichton got it. Thanks.
xpepermint commented on issue #3562:
@alexcrichton I've been thinking, you can't even read the memory inside the host function because you need the
store
?
alexcrichton commented on issue #3562:
Yes the memory is owned by the
Store
, so you have to have access to theStore
to read the memory.
xpepermint commented on issue #3562:
@alexcrichton I found the solution for the
store
which now allows me to manipulate thememory
.let mut ctx = caller.as_context_mut(); ... mem.read(&mut ctx, ptr as usize, &mut output).unwrap();
xpepermint edited a comment on issue #3562:
@alexcrichton I found a solution for the
store
which now allows me to manipulate thememory
.let mut ctx = caller.as_context_mut(); ... mem.read(&mut ctx, ptr as usize, &mut output).unwrap();
rygo6 commented on issue #3562:
Ah yes that's a restriction we can likely lift (and probably lift for all other types as well) given the direction that interface types is going in.
Otherwise the recommended way to store data for closures is not actually to close over them but rather use the
T
inStore<T>
. That way within theT
you could have something liketable: Option<wasmtime::Table>
which is filled in after it's instantiated/created. Then the closure can takeCaller<'_, T>
as the first parameter and access thetable
field, asserting it'sSome
or otherwise returning a trap.Thanks for your help, although still have an issue. I am making a Store<WasiCtx> because I need WASI support. Is it still possible to add more data to that somehow? Or do I need to actually go down and edit the Store to hold more data types?
alexcrichton commented on issue #3562:
A
Store<T>
can only hold one arbitrary typeT
but you can haveT = (U, V)
orT = MyCustomStruct
which holds many fields. Embeddings like wasi don't require that theT
is a specific type, only that you can acquire the wasi-specific type fromT
Last updated: Dec 23 2024 at 12:05 UTC