Skip to content

This is an example showing how to export and import functions between a Rust application and Rust WebAssembly.

License

Notifications You must be signed in to change notification settings

rich-murphey/wasm-hostcall-example

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

89 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Contents

WebAssembly passing Structs to Host functions

This demo shows WebAssembly (Wasm) calling host application functions, where both Wasm and application are in Rust. In particular, Wasm is passing references to objects that have either static or dynamic size.

Wasmtime is an embedded WebAssembly virtual machine runtime. The Rust application uses Wasmtime to load and run a Rust WebAssembly module. This demo shows the WebAssembly module calling functions in the host application.

Wasmtime is new and evolving. Features to import and export functions between WebAssembly and host will almost certainly be enhanced. Here are some production uses:

  • Fastly provides a http_guest API. Customers use this to deploy Rust web apps on edge compute.
  • Cloudflare provides a Wasm Wireshark firewall API, Wirefilter, to run customer Wasm firewall at the edge. Cloudflare is expanding this to Web Application Firewall.

This demo is intended to show how to work within certain interim limitations on argument types.

One limitation is, WebAssembly (Wasm) is 32-bit while the application is 64-bit. Wasm pointers are a 32-bit offset in Virtual Machine (VM) memory. To obtain a 64-bit address on the host side, Wasm pointers must be indexed into VM memory's byte array. Fat pointers such as &[u8] or &str are handled transparently on the WebAssembly side; however, on the host side, they are received as two separate arguments, the 32-bit offset and size.

An additional limitation is pointers to structs. Passing a pointer to a struct (e.g. &struct) requires additional code in both WebAssembly and the host application. This demo shows examples for two kinds of structs:

  • Structs that have the Serialize trait. We serialize it and pass the offset and length of the serialized copy instead. Fields can be String, Vec and other dynamic sized types.
  • Structs that have the Copy trait — a fixed size and no pointer fields. We pass the the offset and size of the struct itself.

There are certain trade-offs.

  • Serialization verifies the struct's field types.
  • Directly passing 'Copy' structs does not, and is faster.

In both examples here, the size of the struct is verified.

Prerequisites

To build this demo, first install rust, then add features:

rustup target add wasm32-wasi
cargo install wasm-pack

Building

After the above, clone this project:

git clone https://github.com/rich-murphey/wasm-hostcall-example.git
cd wasm-hostcall-example

Then build the WebAssembly module:

wasm-pack build wasm

Then build and run the application:

cargo run

Code Samples

Rust WebAssembly imports these functions from the host application to demonstrate passing various argument types:

fn log_int(s: i32)   // passes an integer
fn log_str(s: &str)  // passes pointer and size, zero-copy.
fn log_ab(ab: &AB)   // passes pointer and size of a serialized copy
fn log_cd(cd: &CD)   // passes pointer and size of a struct, zero-copy.

#[derive(Debug, Serialize, Deserialize)]
pub struct AB {
    pub a: u32,
    pub b: String,
}

#[derive(Debug, Copy, Clone)]
pub struct CD {
    pub c: i32,
    pub d: ArrayString::<[u8; CD_N]>,
}

The WebAssembly (Wasm) function hello() in wasm/src/lib.rs calls the above functions.

pub fn hello() -> Result<i32,JsValue> {
    log_int(1234);
    log_str("Hello World!");
    log_ab(&AB{a: 1234, b: "abcd".to_string()});
    log_cd(&CD::from(1234, "hello world"));
    Ok(4567)
}

The WebAssembly side of the API is defined in wasm/src/imports.rs. Note that log_int() and log_str() do not need any additional conversion on the WebAssembly side.

The host (application) side of the API is defined in src/exports.rs:

// Given a rust &str at an offset and size in caller's Wasm memory, log it to stdout.
fn log_str(caller: Caller<'_>, offset: i32, size: i32) -> Result<(), Trap> {
    let mem :Memory = mem_from(&caller)?;                 // caller's VM memory
    let slice :&[u8] = slice_from(&mem, offset, size)?; // string's byte slice
    let string :&str = std::str::from_utf8(slice)         // convert to utf-8
        .or_else(|_|Err(Trap::new("invalid utf-8")))?;
    println!("str: {}", string);                          // print the string
    Ok(())
}

See exports.rs and imports.rs for the corresponding code for the other functions in the API.

Acknowledgments

Suggestions and comments are welcome. Please feel free to open an issue if you can suggest improvements, or find parts that are unclear.

About

This is an example showing how to export and import functions between a Rust application and Rust WebAssembly.

Topics

Resources

License

Stars

Watchers

Forks