wasmerio / wasmer

🚀 The leading Wasm Runtime supporting WASIX and WASI
https://wasmer.io
MIT License
18.87k stars 807 forks source link

How can I access shared memory within an imported function? #3986

Open john01dav opened 1 year ago

john01dav commented 1 year ago

In order to do most interesting things it is necessary to have an ABI that allows imported functions to read shared memory. For example, if the WASM code wants to pass a string to the host code it can give a pointer and length, which the host code can then read within the body of the imported function. But, when I am trying to write this, I am running into an issue. In order to get a MemoryView I need a & reference to the store, but I need to give a &mut reference to the store in Function::call or TypedFunction::call, which precludes having this & reference. How can I implement such an ABI?

ptitSeb commented 1 year ago

Look at this example examples/imports_function_env_global.rs that does an import with store and all, and combine with examples/memory.rs. We don't have an example that does exactly what you want yet, we might work on adding one later.

thedavidmeister commented 1 year ago

@ptitSeb i don't think imports_function_env_global has an example of including the store in the env available to the functions when they run?

thedavidmeister commented 1 year ago

ah, i figured out that you can get store and data from the FunctionEnvMut natively

sdbondi commented 1 week ago

I'm sorry if this is a bad place for this question. I would like to ask if this is the correct approach for reading memory from a host function. This code works but it seems weird to pass the memory into the FunctionEnv. Please excuse the unwraps and other bad code as this is just a quick proof of concept.

    fn read_to_vec(view: &MemoryView, ptr: WasmPtr<u8>, len: u32) -> Vec<u8> {
        let mut vec = Vec::with_capacity(len as usize);
        for i in 0..len {
            let a = ptr.add_offset(i).unwrap();
            let b = a.read(&view).unwrap();
            vec.push(b);
        }

        vec
    }

    // Wasm passes in a pointer + len to some data
    fn host_fn(mut env: FunctionEnvMut<State>,ptr: WasmPtr<u8>, len: u32) -> u8 {
        let (s, mut store) = env.data_and_store_mut();
        let view = s.memory.as_ref().unwrap().view(&mut store);
        let v = read_to_vec(&view, ptr, len);
        // Adding 100 just to see that the call works
        v[0] + 100
    }

    struct State {
        memory: Option<Memory>
    }

  // Snip ... creating engine, store, loading wasm module from bytes

    let env = FunctionEnv::new(&mut store, State {  memory: None });

    // Create an empty import object.
    let import_object = imports! {
        "env" => {
            "host_fn" => Function::new_typed_with_env(&mut store, &env, host_fn)
        }
    };

    let instance = Instance::new(&mut store, &module, &import_object)?;

    let test_call: TypedFunction<(), ()> =
        instance.exports.get_typed_function(&mut store, "test_call")?;

    // Set memory before calling test_call
    let memory = instance.exports.get_memory("memory")?;
    env.as_mut(&mut store).memory = Some(memory.clone());

    let result = test_call.call(&mut store, ()).unwrap();
   assert_eq!(result, 223); // 123 + 100

The WASM compiled from rust

#[no_mangle]
pub unsafe extern "C" fn test_call() -> u8 {
    let v2 = vec![123u8,2,3,4];
    let result = host_fn(v2.as_ptr(), v2.len() as u32);
   result
}

extern "C" {
    pub fn host_fn(ptr: *const u8, len: u32) -> u8;
}

EDIT: in the process of writing a SO question I found this which suggest the same :) https://stackoverflow.com/questions/75753403/wasmer-host-functions-accessing-memory