Heya!
I am trying to pass pointers to buffers from my wasm plugin to my native host that is running wasmer. I have the following in the host
fn main() {
// Import the plugin
let wasm_plugin_path = std::env::args().nth(1).expect(format!(
"No wasm plugin path provided. Expected a directory containing {EXECUTABLE_WASM} and {LIBRARY_WASM}"
).as_str());
let base = Path::new(&wasm_plugin_path);
let wasm_library =
std::fs::read(base.join(Path::new(LIBRARY_WASM))).expect("Failed to read wasm library");
let wasm_plugin =
std::fs::read(base.join(Path::new(EXECUTABLE_WASM))).expect("Failed to read wasm plugin");
// Configure the engine and plugin
let config = wasmtime::Config::new();
let engine = wasmtime::Engine::new(&config).expect("Failed to create engine using config");
// Declare shared data between host and wasm module, including available host functions
let mut linker = wasmtime::Linker::new(&engine);
linker
.func_wrap(WASM_HOST_MODULE, "display_result", display_result)
.expect("Failed to wrap host function");
let mut store = wasmtime::Store::new(&engine, [0u32; 1024 * 1024 * 10]);
// Configure the module
let lib_module = wasmtime::Module::new(&engine, &wasm_library)
.expect("Failed to compile provided wasm library");
let exec_module = wasmtime::Module::new(&engine, &wasm_plugin)
.expect("Failed to compile provided wasm plugin");
let instance = linker
.module(&mut store, WASM_LIB_MODULE, &lib_module)
.expect("Failed to instantiate library")
.instantiate(&mut store, &exec_module)
.expect("Failed to instantiate module");
// We now call the plugin function, that will add the 2 numbers, and invoke the host function to display the result
let plugin_function = instance
.get_typed_func::<(i32, i32), i32>(&mut store, "compute_results")
.expect("Failed to find plugin function");
let (left, right) = (8, 4);
println!("Calling plugin function with {} and {}", left, right);
plugin_function
.call(&mut store, (left, right))
.expect("Failed to call plugin function");
}
/// A host function that can be called from the Wasm plugin
fn display_result(mut caller: wasmtime::Caller<'_, &[u32]>, buffer_ptr: u32, len: u32) {
// We avoid pointer arithmetic as it is unsafe, compared to the size-checked slice from Rust
let start: &mut [u32] = caller.data_mut();
let bytes_u32 = &start[buffer_ptr as usize..buffer_ptr as usize + len];
let bytes_u8 = unsafe { std::mem::transmute::<&[u32], &[u8]>(bytes_u32) };
let string = std::str::from_utf8(bytes_u8).expect("Failed to convert bytes to string");
println!("{}", string);
}
The issue is around the display_result
function. Ideally, this would accept a wasm-pointer to the buffer in the store, and the size of the string. However, instead I get the following
error[E0277]: the trait bound `for<'a, 'b> fn(Caller<'a, &'b [u32]>, u32, usize) {display_result}: IntoFunc<_, _, _>` is not satisfied
--> wasm-runtime/src/main.rs:29:56
|
29 | .func_wrap(WASM_HOST_MODULE, "display_result", display_result)
| --------- ^^^^^^^^^^^^^^ the trait `IntoFunc<_, _, _>` is not implemented for fn item `for<'a, 'b> fn(Caller<'a, &'b [u32]>, u32, usize) {display_result}`
| |
| required by a bound introduced by this call
|
note: required by a bound in `Linker::<T>::func_wrap`
--> /Users/hugh/.cargo/registry/src/index.crates.io-6f17d22bba15001f/wasmtime-16.0.0/src/linker.rs:552:20
|
548 | pub fn func_wrap<Params, Args>(
| --------- required by a bound in this associated function
...
552 | func: impl IntoFunc<T, Params, Args>,
| ^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `Linker::<T>::func_wrap`
Clearly the type of the function is coming from the store generic type - the owned array I am assigning it. Am I initialising the Store correctly? Am I accessing the store correctly? Should my signature change? Any advice would be appreciated, thank you!
I have actually been able to solve this myself thankfully :) The Store type is just a carrier for context. It isn't a backing type of the exposed memory, but is available to host functions in case they need to handle state.
The way I solved the display result is as follows
fn display_result(mut caller: wasmtime::Caller<'_, ()>, buffer_ptr: i32, len: i32) {
let mem = caller
.get_export("memory")
.expect("Failed to get memory export");
let mem = match mem {
Extern::Func(f) => panic!("Expected memory, found function"),
Extern::Global(g) => panic!("Expected memory, found global"),
Extern::Table(t) => panic!("Expected memory, found table"),
Extern::Memory(m) => m,
Extern::SharedMemory(sm) => panic!("Expected memory, found shared memory"),
};
let mut buffer: Vec<u8> = vec![0; len as usize];
mem.read(&caller, buffer_ptr as usize, buffer.as_mut_slice())
.expect("Failed to read memory");
// We avoid pointer arithmetic as it is unsafe, compared to the size-checked slice from Rust
let string = std::str::from_utf8(buffer.as_slice()).expect("Failed to convert bytes to string");
println!("{}", string);
}
yep, looks like you got it
just as a suggestion: if you are able to define your api using the witx
language, the wiggle
crate will abstract away some of these implementation details of reading out of memory
but better yet than using witx is to use the component model and wit language, with wasmtime's component model support (wasmtime::component::bindgen! instead of the wiggle one, basically), and that helps a bunch with guest bindings in many languages (see the wit-bindgen project)
with the component model you'll never have to worry about pointers and linear memory when you are writing host code, and in guest code it is abstracted over as well (except for bindings generated for C, where pointers are just life)
Last updated: Nov 22 2024 at 16:03 UTC