J-Cake
J-Cake

Reputation: 1638

Why does the resulting WAT code not seem to align with the function which produced it?

When compiling the below Rust code against the wasm32-unknown-unknown target (without dependencies like WASM-Bindgen), the following WAT results.

mod wasm {
    unsafe extern "C" {
        #[link_name = "systemCall"]
        #[unsafe(no_mangle)]
        pub(super) fn syscall(payload: u32, len: u32) -> (u32, u32);
    }
}
(module $example_plugin.wasm
  
  ...

  (type $t5 (func (param i32 i32 i32)))
  (import "env" "systemCall" (func $systemCall (type $t5)))

  ...

The resulting WAT is obviously heavily simplified. Unfortunately, the Rust signature does not match what I expect the WAT signature to look like and I'm hoping for insight as to why and what I should do to get the expected WAT.

(module $example_plugin.wasm
  
  ...

  (type $t5 (func (param i32 i32) (result i32 i32)))
  (import "env" "systemCall" (func $systemCall (type $t5)))

  ...

Why is this the expected function signature?

I'm attempting to load WASM modules through wasmtime. I'm hoping to implement an IPC mechanism by having the WASM serialise a message of a known format and passing it back to the engine.

My function for handling this from the WASM side is supposed to receive a string by its raw parts. (Ptr and Len) and having the host read the bytes from its memory and assemble the string again and the inverse for the response.

The host-side function is defined using the Func::wrap method

linker.func_wrap("env", "systemCall", move |mut caller: Caller<Api>, ptr: u32, len: u32| -> (u32, u32) {
  ...
})

When loading the plugin which calls the syscall method, wasmtime yields the error

Caused by:
    types incompatible: expected type `(func (param i32 i32 i32))`, found type `(func (param i32 i32) (result i32 i32))`

Insights

  1. By returning a raw pointer to a tuple instead of a tuple directly, I get a return clause on the function's signature.

  2. @Finomnis pointed out that the compiler throws a warning that tuples aren't FFI-safe. Having corrected this using a #[repr(C)] struct, the same function signature can be observed.

MVP

Here you can find an MPV

Running the example

# 1. Compile the plugin
cd plugin
cargo build --target wasm32-unknown-unknown

# 2. Run the engine
cd ..
cargo run --target x86_64-unkown-linux-gnu

Please assume the :: in the file names are /

Cargo.toml

[package]
name = "wat_mvp"
version = "0.1.0"
edition = "2024"

[workspace]
members = ["./plugin"]

[dependencies]
wasmtime = "30.0.2"

plugin/Cargo.toml

[package]
name = "plugin"
version = "0.1.0"
edition = "2024"

[lib]
crate-type = ["cdylib"]

[dependencies]

plugin/src/lib.rs

#![no_std]
extern crate alloc;

use alloc::{borrow::ToOwned, string::String};

mod wasm {
    #[repr(C)]
    pub(super) struct StringResponse {
        pub ptr: u32,
        pub len: u32
    }

    unsafe extern "C" {
        #[link_name = "systemCall"]
        pub(super) fn syscall(payload: u32, len: u32) -> *const StringResponse;

        #[link_name = "signal"]
        pub(super) fn signal();
    }
}

#[unsafe(export_name = "onLoad")]
pub fn main() {
    let mut request = "Test".to_owned();
    let response = unsafe {
        let wasm::StringResponse { ptr, len } = *wasm::syscall(request.as_mut_ptr() as u32, request.len() as u32);
        String::from_raw_parts(ptr as *mut u8, len as usize, len as usize)
    };

    // Do something with response. Am in no_std, so can't print...
}

src/main.rs

use std::path::PathBuf;

use wasmtime::{Caller, CodeBuilder, Engine, Linker, Store};

fn main() {
    let engine = Engine::default();
    let mut linker = Linker::new(&engine);

    linker.func_wrap("env", "systemCall", move |caller: Caller<()>, ptr: u32, len: u32| -> u32 {
        todo!(); // This function is actually quite complex because you have to place the contents of the string into the memory of the wasm module. Assume it works
    })
    .expect("Could not inject systemCall function");

    let mut store = Store::new(&engine, ());

    let module = CodeBuilder::new(&engine)
        .wasm_binary_file(&PathBuf::from("./plugin/target/wasm32-unknown-unknown/debug/plugin.wasm"))
        .expect("couln't load wasm")
        .compile_module()
        .expect("couln't compile module");

    let instance = linker.instantiate(&mut store, &module)
        .expect("Failed to instantiate");

    instance.clone().get_typed_func::<(), ()>(&mut store, "onLoad")
        .expect("Failed to localte entryPoint")
        .call(&mut store, ())
        .expect("Failed to call load");
}

Upvotes: 2

Views: 46

Answers (0)

Related Questions