Stream: general

Topic: Initialising and invoking Store in Rust wasmer host


view this post on Zulip Przemyslaw Hugh Kaznowski (Jan 25 2024 at 00:45):

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!

view this post on Zulip Przemyslaw Hugh Kaznowski (Jan 25 2024 at 01:33):

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);
}

view this post on Zulip Pat Hickey (Jan 25 2024 at 02:04):

yep, looks like you got it

view this post on Zulip Pat Hickey (Jan 25 2024 at 02:05):

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

view this post on Zulip Pat Hickey (Jan 25 2024 at 02:06):

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)

view this post on Zulip Pat Hickey (Jan 25 2024 at 02:07):

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: Dec 23 2024 at 13:07 UTC