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
For 1. export a function from your module which can be used by the host to allocate memory inside the wasm module.
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.
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
.
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.
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.
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
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)
?
Yes that's possible, you'd have to arrange for the Instance
to be in the T
of the Store<T>
Alex Crichton said:
Yes that's possible, you'd have to arrange for the
Instance
to be in theT
of theStore<T>
Thank you, Alex. I am going to pass the Store<T>
and Instance
to 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?
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
and I believe the answer is "no" for preallocating space
but it really depends on specifics, what you're describing is relatively vague
With a Func
you'd want to call .typed::<...>
and then what you can pass in depends on how you type it
e.g. you could make a parameter use &[u8]
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).
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?
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 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 intostore<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
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.
bjorn3 said:
I really mean
callback_pointer
is a function pointerFunction 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))
...
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(()))
}
}
@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.
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?
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:
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: Dec 23 2024 at 13:07 UTC