Stream: git-wasmtime

Topic: wasmtime / issue #3562 Call a function in an exported tab...


view this post on Zulip Wasmtime GitHub notifications bot (Nov 26 2021 at 12:39):

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?

view this post on Zulip Wasmtime GitHub notifications bot (Nov 29 2021 at 15:18):

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 your emscripten_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 the table into the closure you can either use .get_export or you can use something similar to Rc<RefCell<...>> to start out at None and then set to Some after the instance has been instantiated and the exports found on the instance.

view this post on Zulip Wasmtime GitHub notifications bot (Dec 07 2021 at 04:31):

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?

view this post on Zulip Wasmtime GitHub notifications bot (Dec 07 2021 at 04:31):

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?

view this post on Zulip Wasmtime GitHub notifications bot (Dec 07 2021 at 04:32):

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?

view this post on Zulip Wasmtime GitHub notifications bot (Dec 07 2021 at 04:32):

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?

view this post on Zulip Wasmtime GitHub notifications bot (Dec 07 2021 at 04:32):

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?

view this post on Zulip Wasmtime GitHub notifications bot (Dec 13 2021 at 15:18):

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 in Store<T>. That way within the T you could have something like table: Option<wasmtime::Table> which is filled in after it's instantiated/created. Then the closure can take Caller<'_, T> as the first parameter and access the table field, asserting it's Some or otherwise returning a trap.

view this post on Zulip Wasmtime GitHub notifications bot (Jan 03 2022 at 15:37):

xpepermint commented on issue #3562:

@alexcrichton so how would you then call a host function from another host function?

view this post on Zulip Wasmtime GitHub notifications bot (Jan 04 2022 at 15:06):

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?

view this post on Zulip Wasmtime GitHub notifications bot (Jan 04 2022 at 15:25):

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.

view this post on Zulip Wasmtime GitHub notifications bot (Jan 04 2022 at 17:39):

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 the Store<T> or similar you can't access it directly.

view this post on Zulip Wasmtime GitHub notifications bot (Jan 04 2022 at 17:45):

xpepermint commented on issue #3562:

@alexcrichton got it. Thanks.

view this post on Zulip Wasmtime GitHub notifications bot (Jan 06 2022 at 16:50):

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?

view this post on Zulip Wasmtime GitHub notifications bot (Jan 06 2022 at 19:14):

alexcrichton commented on issue #3562:

Yes the memory is owned by the Store, so you have to have access to the Store to read the memory.

view this post on Zulip Wasmtime GitHub notifications bot (Jan 07 2022 at 01:22):

xpepermint commented on issue #3562:

@alexcrichton I found the solution for the store which now allows me to manipulate the memory.

let mut ctx = caller.as_context_mut();
...
mem.read(&mut ctx, ptr as usize, &mut output).unwrap();

view this post on Zulip Wasmtime GitHub notifications bot (Jan 07 2022 at 15:16):

xpepermint edited a comment on issue #3562:

@alexcrichton I found a solution for the store which now allows me to manipulate the memory.

let mut ctx = caller.as_context_mut();
...
mem.read(&mut ctx, ptr as usize, &mut output).unwrap();

view this post on Zulip Wasmtime GitHub notifications bot (Jan 14 2022 at 06:21):

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 in Store<T>. That way within the T you could have something like table: Option<wasmtime::Table> which is filled in after it's instantiated/created. Then the closure can take Caller<'_, T> as the first parameter and access the table field, asserting it's Some 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?

view this post on Zulip Wasmtime GitHub notifications bot (Jan 18 2022 at 15:21):

alexcrichton commented on issue #3562:

A Store<T> can only hold one arbitrary type T but you can have T = (U, V) or T = MyCustomStruct which holds many fields. Embeddings like wasi don't require that the T is a specific type, only that you can acquire the wasi-specific type from T


Last updated: Nov 22 2024 at 16:03 UTC