2

Currently, I am working on a project which uses the wasmtime (a wasm runtime implemented by rust) to load and initiate some wasm modules.

And I would like a wasm module to access the global val. So I write some code:

extern "C" {
    static offset: i32;
}

#[no_mangle]
pub extern "C" fn hello() -> i32 {
    unsafe { offset }
}

And I use the command to compile it to wasm:

cargo build --target wasm32-unknown-unknown

Next, I write another rust program to load and initiate the wasm module:

use wasmtime::*;

struct MyState {
    count: i32,
}

fn main() -> Result<()> {
    let engine = Engine::default();
    let mut store = Store::new(&engine, MyState { count: 0 });

    // the path is corrected.
    let module = Module::from_file(&engine, "./src/linkerglobal.wasm")?; 
    let mut linker = Linker::new(&engine);

    // create a global value
    let ty = GlobalType::new(ValType::I32, Mutability::Var);
    let my_global = Global::new(&mut store, ty, Val::I32(0x1234))?;
    linker.define(&mut store, "host", "offset", my_global)?;


    let instance = linker.instantiate(&mut store, &module)?;


    let hello = instance.get_typed_func::<(), i32>(&mut store, "hello")?;
    let res = hello.call(&mut store, ())?;
    println!("res: {}", res); // it should be 0x1235

    Ok(())
}

I think the output should be: res: 4660, however, the output is res: 0.

Can you help me? Thanks a lot

1
  • from my knowledge, global is belong to module itself, runtime does not have privilege to do the modification, please use table for outside interaction with wasm module
    – Jesse
    Commented Jun 26 at 2:19

1 Answer 1

1

Rust does not support WebAssembly import globals. This is because their address cannot be taken, but the address of every Rust static must be able to be taken. Rust and/or WebAssembly will need to be updated in order for that to work, as said here and here. It may be possible to do by some proc-macro and postprocessing of the generated wasm file (like wasm-bindgen does), but I don't know a tool that does that.

What you can do, however, is defining and exporting the static by Rust then mutating it in the host by the engine. The static has to be static mut or inside an UnsafeCell for that to not be UB, but it can be nicely encapsulated:

use std::cell::UnsafeCell;
use std::ops::Deref;

#[repr(transparent)]
pub struct ExternalStatic<T> {
    value: UnsafeCell<T>,
}

impl<T> ExternalStatic<T> {
    pub const fn new(value: T) -> Self {
        Self {
            value: UnsafeCell::new(value),
        }
    }
}

// SAFETY: See `Deref`.
unsafe impl<T> Sync for ExternalStatic<T> {}

impl<T> Deref for ExternalStatic<T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        // SAFETY: We never provide any mutable reference to this value.
        // It may be changed externally by the executor, but the executor
        // can cause all sorts of UB inside the Rust code, and this isn't
        // really something of our responsibility. The executor should be
        // cautious and only change it correctly.
        unsafe { &*self.value.get() }
    }
}

Then:

#[no_mangle]
pub static offset: ExternalStatic<i32> = ExternalStatic::new(0);

#[no_mangle]
pub extern "C" fn hello() -> i32 {
    *offset
}

Then set it in the host like the following:

use wasmtime::*;

fn main() -> Result<()> {
    let engine = Engine::default();
    let mut store = Store::new(&engine, ());

    // the path is corrected.
    let module = Module::from_file(&engine, "./src/linkerglobal.wasm")?;
    let linker = Linker::new(&engine);

    let instance = linker.instantiate(&mut store, &module)?;
    let memory = instance.get_memory(&mut store, "memory").unwrap();

    // Set `offset` to 0x1234.
    let offset_global = instance
        .get_export(&mut store, "offset")
        .unwrap()
        .into_global()
        .unwrap();
    let offset_addr = offset_global.get(&mut store).unwrap_i32();
    memory
        .write(&mut store, offset_addr as usize, &0x1234i32.to_le_bytes())
        .unwrap();

    let hello = instance.get_typed_func::<(), i32>(&mut store, "hello")?;
    let res = hello.call(&mut store, ())?;
    println!("res: 0x{:X}", res); // it should be 0x1234

    Ok(())
}

Not the answer you're looking for? Browse other questions tagged or ask your own question.