Reputation: 1638
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)))
...
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))`
By returning a raw pointer to a tuple instead of a tuple directly, I get a return
clause on the function's signature.
@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.
# 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/
[package]
name = "wat_mvp"
version = "0.1.0"
edition = "2024"
[workspace]
members = ["./plugin"]
[dependencies]
wasmtime = "30.0.2"
[package]
name = "plugin"
version = "0.1.0"
edition = "2024"
[lib]
crate-type = ["cdylib"]
[dependencies]
#![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...
}
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