Stream: wasmtime

Topic: Invoke a callback function that's defined in wasm component?


view this post on Zulip Bryton (Jun 26 2024 at 09:43):

Hello folks,

I want to let my wasmtime host side implementations to invoke a function that's defined in a wasm component.
I am going to use Instance::get_func(store, name) method to get a Func instance of that callback function, and then invoke it by Func::call(store, params, results).

But, I am confused by: 1). how can I allocate a memory inside the store and then pass it to callback function for later use? I saw some code snippets that get memory from caller in wasm module not in wasm component.

2). Can I use u32 instead of string to pass the callback function to the wasmtime host side? I am using wasmtime 22.0.0, Instance::get_func(store, name) only takes string typed name parameter, I also noticed that @Alex Crichton recent patches allow Instance::get_func() to also take a func_index parameter, but it seems that it still need to have the string name first and then convert to func_index. The drawback of passing by string is we cannot know what's the name of a callback function unless ask wasm component developer to follow a specific naming rule. I think that a callback function pointer inside a wasm component can be represent by a u32 and then passed down to wasmtime engine. Is there any chance to convert this u32 callback function pointer to a Func instance?

Thanks,
Chengdong

view this post on Zulip bjorn3 (Jun 26 2024 at 11:38):

For 1. export a function from your module which can be used by the host to allocate memory inside the wasm module.

view this post on Zulip bjorn3 (Jun 26 2024 at 11:41):

For 2. If you are having a function pointer inside your wasm module which you want to pass to the host, this will be a u32 which indexes into a table. You can export the table from the wasm module to allow the host to access it and look up the index into the table to get a Func you can call.

view this post on Zulip Bryton (Jun 26 2024 at 14:42):

Thanks a lots @bjorn3

For 1, I am not using wasm module, I am using wasm component, I believe that it is much hard to touch memory allocator inside wasm component. Moreover, anyone can be the wasm component developer, there is a rule to force developer to export a memory allocation function.

Can I use memory::new() to allocate a memory area inside store and then pass the handle to callback function?

For 2. Again, there is no strong rule force wasm component developer to export the callback function, for example, define a export inside the WIT file.

Like this issue: https://github.com/bytecodealliance/wasmtime/issues/2646, the third parameter of startTimer() can be any callback function pointer. There is no explicit export.

Hello! I'm currently embedding the Wasmtime in the C/C++ and got an issue with the callbacks. What I'm trying to achieve is to call a function, exported by the WASM script, from within the host sys...

view this post on Zulip bjorn3 (Jun 26 2024 at 14:46):

For 1. If you have a component, you aren't supposed to directly allocate inside the component. Instead why you pass a type like string or list<i8> as argument it will be automatically allocated for you if the component needs it to be memory allocated.

view this post on Zulip bjorn3 (Jun 26 2024 at 14:48):

For 2. Can you make the component return a funcref to the host? I don't know if components are allowed to do that though.

view this post on Zulip Alex Crichton (Jun 26 2024 at 14:49):

At this time, no, you can't pass a funcref or a table index out of a component and have the host call it, there's no way to pass a callback to the host. That's modeled instead with resources

view this post on Zulip Bryton (Jun 26 2024 at 15:00):

Alex Crichton said:

At this time, no, you can't pass a funcref or a table index out of a component and have the host call it, there's no way to pass a callback to the host. That's modeled instead with resources

If I define a foo function inside WIT file, that function takes a callback_name string parameter. like this:
foo: func(callback_name: string)

Can I use this callback_name to get a Func by passing it to Instance::get_func(&mut store, callback_name)?

view this post on Zulip Alex Crichton (Jun 26 2024 at 15:09):

Yes that's possible, you'd have to arrange for the Instance to be in the T of the Store<T>

view this post on Zulip Bryton (Jun 26 2024 at 15:17):

Alex Crichton said:

Yes that's possible, you'd have to arrange for the Instance to be in the T of the Store<T>

Thank you, Alex. I am going to pass the Store<T> and Instanceto the host side Context struct, I know it’s ugly, but it should work.

The next two questions as I mentioned before, can I pass a u32 callback function “pointer” to Instance::get_func(&mut store, callback_pointer) instead of string name. I believe wasm component developer is more willing to pass a function pointer instead of a function name.

And let say the callback function definition takes a [u8, size] type parameter, Is there any method to pre-allocate a memory area inside the store<T> by something like memory::new(&mut store …) and save the data to that memory area, and finally pass it as a parameter to the callback function?

view this post on Zulip Alex Crichton (Jun 26 2024 at 15:18):

oh that callback_pointer isn't a pointer, it's a host-defined index the wasm guest has no way of creating or naming, so it's not related to function pointers

view this post on Zulip Alex Crichton (Jun 26 2024 at 15:18):

and I believe the answer is "no" for preallocating space

view this post on Zulip Alex Crichton (Jun 26 2024 at 15:18):

but it really depends on specifics, what you're describing is relatively vague

view this post on Zulip Alex Crichton (Jun 26 2024 at 15:19):

With a Func you'd want to call .typed::<...> and then what you can pass in depends on how you type it

view this post on Zulip Alex Crichton (Jun 26 2024 at 15:19):

e.g. you could make a parameter use &[u8]

view this post on Zulip Bryton (Jun 26 2024 at 15:32):

Alex Crichton said:

oh that callback_pointer isn't a pointer, it's a host-defined index the wasm guest has no way of creating or naming, so it's not related to function pointers

In wasmtime 22.0.0, get_func() only support string function name parameter, I saw you recent patches changed the definition accept a func_index, do you mean my callback_pointer is a func_index?

I really mean callback_pointer is a function pointer, I saw some examples that use a u32 callback function pointer to index some kind of table struct to get a Func instance in wasm module (not wasm component). For example: https://github.com/eunomia-bpf/wasm-bpf/blob/35409ea40073f4af3fea29a938f76b763a19476c/runtime/wasm-bpf-rs/src/utils.rs#L120

I am thinking if it is possible to do the same thing here(wasm component).

WebAssembly library, toolchain and runtime for eBPF programs - eunomia-bpf/wasm-bpf

view this post on Zulip Bryton (Jun 26 2024 at 15:38):

Alex Crichton said:

With a Func you'd want to call .typed::<...> and then what you can pass in depends on how you type it

I believe that if a memory area is inside store<T>, then functions inside a wasm component can directly use it. Otherwise it needs some additional operations to pass it to the store<T>, lifting?

Do you mean as I knew the type of callback function, like you said call .typed::<…> I don’t need to care about put the the data into store<T>, wasmtime will do it for me?

view this post on Zulip bjorn3 (Jun 26 2024 at 16:07):

I really mean callback_pointer is a function pointer

Function pointers in wasm are generally implemented as indexes into a table of functions that can be called using a function pointer. For the host to call such a function pointer, it has to look up the function in the table. It can only do this if the table is exported. I'm not sure if wasm components allow exporting a table. You could have a function exported by your wasm component which accepts a function pointer and function arguments as arguments and calls the function pointer. Then the host could call this function.

view this post on Zulip Alex Crichton (Jun 26 2024 at 17:09):

I am thinking if it is possible to do the same thing here(wasm component).

The short answer is no, you can't do that with components. Tables cannot be exported from components and function pointers aren't a type in the component type system. In the component model these sorts of interactions are modeled as resources which require more static information.

Do you mean as I knew the type of callback function, like you said call .typed::<…> I don’t need to care about put the the data into store<T>, wasmtime will do it for me?

Sorry what I mean is that I'd recommend getting something compiling and asking questions further from there. It's tough to answer specifiic API questions which aren't based on a concrete piece of code otherwise. What you're describing can and cannot work, it really highly depends on specifics and can be subtle. I can do my best to lead you through the specifics here but it's very easy to be misinterpreted because I'm providing context I fear that won't be fully understood because I can't talk about specifics. If there's a piece of code though I can provide specific advice which should be easier to follow

view this post on Zulip Bryton (Jun 27 2024 at 01:40):

Many thanks to both of you @bjorn3 and @Alex Crichton . As Alex suggested, I'd like to do some experiments first, then we can be more focus on specific problems if I find some.

view this post on Zulip Bryton (Jun 27 2024 at 08:03):

bjorn3 said:

I really mean callback_pointer is a function pointer

Function pointers in wasm are generally implemented as indexes into a table of functions that can be called using a function pointer. For the host to call such a function pointer, it has to look up the function in the table. It can only do this if the table is exported. I'm not sure if wasm components allow exporting a table. You could have a function exported by your wasm component which accepts a function pointer and function arguments as arguments and calls the function pointer. Then the host could call this function.

I find that add println!("cargo:rustc-link-arg=--export-table"); in build.rs can let cargo component build to export a table named "__indirect_function_table".

The correspending output in .wasm file looks like this:

    (table (;0;) 328 328 funcref)
    (memory (;0;) 17)
    (global $__stack_pointer (;0;) (mut i32) i32.const 1048576)
    (export "memory" (memory 0))
    (export "_start" (func $_start))
    (export "__main_void" (func $__main_void))
    (export "__indirect_function_table" (table 0))
...

view this post on Zulip Bryton (Jun 28 2024 at 03:59):

        let modules = instance.exports(store).root().modules();
        for (name, module) in modules {
            println!("module name: {}", name);
            match module.get_export("__indirect_function_table") {
                Some(extern_type_table) => {
                    match extern_type_table.table() {
                        Some(table_type) => {
                            let table: Table = table_type.wasmtime_table();
                            let func = table.get(store, callback_index).unwrap()
                                         .as_func()
                                         .unwrap()
                                         .unwrap();
                            let func = func.typed::<i32, &[u8]>(store)
                            .unwrap()
                            .call(123, buffer);

                        }
                        None => return Ok(Err(()))
                    }
                }
                None => return Ok(Err(()))
            }
        }

view this post on Zulip Bryton (Jun 28 2024 at 04:06):

@Alex Crichton the above code shows the idea of how to get a typed function by index instead of by a string name.

It should work if the TableType::wasmtime_table() is not a crate private funciton.

view this post on Zulip Bryton (Jun 28 2024 at 04:08):

I believe that you must have some thoughts that don't export the Table to users. Could you give me the reason why it is designed like this?

view this post on Zulip Bryton (Jun 28 2024 at 04:10):

From my understanding, I think it is quite normal to setup a callback function by pointer/index(wasm table) instead of by a function name. :rolling_on_the_floor_laughing:

view this post on Zulip Alex Crichton (Jul 08 2024 at 14:30):

Sorry but I think you're really far afield of what's supported here. I may not be able to help much more other than saying that the above example fundamentally won't work, you can't get access to a Table from a component. There's various foundational reasons for this which stem from other architectural decisions. Doing this won't be as simple as "just make this function public" or something like that.


Last updated: Oct 23 2024 at 20:03 UTC