[][src]Crate wapc

wapc

The wapc crate provides a high-level WebAssembly host runtime that conforms to an RPC mechanism called waPC. waPC is designed to be a fixed, lightweight standard allowing both sides of the guest/host boundary to make method calls containing arbitrary binary payloads. Neither side of the contract is ever required to perform explicit allocation, ensuring maximum portability for wasm targets that might behave differently in the presence of garbage collectors and memory relocation, compaction, etc.

The interface may at first appear more "chatty" than other protocols, but the cleanliness, ease of use, simplified developer experience, and purpose-fit aim toward stateless WebAssembly modules is worth the few extra nanoseconds of latency.

To use wapc, first you'll need a waPC-compliant WebAssembly module (referred to as the guest) to load and execute. You can find a number of these samples available in the GitHub repository, and anything compiled with the wascc actor SDK can also be invoked via waPC as it is 100% waPC compliant.

Next, you will need to chose a runtime engine. waPC describes the function call flow required for wasm-RPC, but it does not dictate how the low-level WebAssembly function calls are made. This allows you to select whatever engine best suits your needs, whether it's a JIT-based engine or an interpreter-based one. Simply instantiate anything that implements the WebAssemblyEngineProvider trait and pass it to the WapcHost constructor and the WapcHost will facilitate all RPCs.

To make function calls, ensure that you provided a suitable host callback function (or closure) when you created your WapcHost. Then invoke the call function to initiate the RPC flow.

Example

This example is not tested
extern crate wapc;
use wapc::prelude::*;
use wasmtime_provider::WasmtimeEnginerProvider; // Choose your own engine provider

pub fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let module_bytes = load_file();
    let engine = WasmtimeEngineProvider::new(&bytes, None);
    let host = WapcHost::new(
                Box::new(engine),
                |id: u64, bd: &str, ns: &str, op: &str, payload: &[u8]| {
                  println!("Guest {} invoked '{}->{}:{}' with payload of {} bytes",
                            id, bd, ns, op, payload.len());
                  Ok(vec![])
                })?;

    let res = host.call("wapc:sample!Hello", b"this is a test")?;
    assert_eq!(res, b"hello world!");

    Ok(())
}

Notes

waPC is reactive. Guest modules cannot initiate host calls without first handling a call initiated by the host. It is up to the runtime engine provider (e.g. wasmtime or wasm3) to invoke the required start functions (if present) during initialization. Guest modules can synchronously make as many host calls as they like, but keep in mind that if a host call takes too long or fails, it'll cause the initiating guest call to also fail.

In summary, keep host callbacks fast and and free of panic-friendly unwrap()s, and do not spawn new threads within a host callback unless you must (and can synchronously return a value) because waPC assumes a single-threaded execution environment. Also note that for safety the host callback function intentionally has no references to the WebAssembly module bytes or the running instance. If you need an external reference in the callback, you can capture it in a closure.

RPC Exchange Flow

The following is a detailed outline of which functions are invoked and in which order to support a waPC exchange flow, which is always triggered by a consumer invoking the call function. Invoking and handling these low-level functions is the responsibility of the engine provider, while orchestrating the high-level control flow is the job of the WapcHost.

  1. Host invokes __guest_call on the WebAssembly module (via the engine provider)
  2. Guest calls the __guest_request function to instruct the host to write the request parameters to linear memory
  3. Guest uses the op_len and msg_len parameters long with the pointer values it generated in step 2 to retrieve the operation (UTF-8 string) and payload (opaque byte array)
  4. Guest performs work
  5. (Optional) Guest invokes __host_call on host with pointers and lengths indicating the binding, namespace, operation, and payload.
  6. (Optional) Guest can use __host_response and host_response_len functions to obtain and interpret results
  7. (Optional) Guest can use __host_error_len and __host_error to obtain the host error if indicated (__host_call returns 0)
    1. Steps 5-7 can repeat with as many different host calls as the guest needs
  8. Guest will call guest_error to indicate if an error occurred during processing
  9. Guest will call guest_response to store the opaque response payload
  10. Guest will return 0 (error) or 1 (success) at the end of __guest_call

Required Host Exports

List of functions that must be exported by the host (imported by the guest)

ModuleFunctionParametersDescription
wapc__host_callbr_ptr: i32
bd_len: i32
ns_ptr: i32
ns_len: i32
op_ptr: i32
op_len: i32
ptr: i32
len: i32
-> i32
Invoked to initiate a host call
wapc__console_logptr: i32, len: i32Allows guest to log to stdout
wapc__guest_requestop_ptr: i32
ptr: i32
Writes the guest request payload and operation name to linear memory at the designated locations
wapc__host_responseptr: i32Instructs host to write the host response payload to the given location in linear memory
wapc__host_response_len-> i32Obtains the length of the current host response
wapc__guest_responseptr: i32
len: i32
Tells the host the size and location of the current guest response payload
wapc__guest_errorptr: i32
len: i32
Tells the host the size and location of the current guest error payload
wapc__host_errorptr: i32Instructs the host to write the host error payload to the given location
wapc__host_error_len-> i32Queries the host for the length of the current host error (0 if none)

Required Guest Exports

List of functions that must be exported by the guest (invoked by the host)

FunctionParametersDescription
__guest_callop_len: i32
msg_len: i32
Invoked by the host to start an RPC exchange with the guest module

Modules

errors

Library-specific error types and utility functions

Structs

Invocation

Represents a waPC invocation, which is a combination of an operation string and the corresponding binary payload

ModuleState

Module state is essentially a 'handle' that is passed to a runtime engine to allow it to read and write relevant data as different low-level functions are executed during a waPC conversation

WapcFunctions

A list of the function names that are part of each waPC conversation

WapcHost

A WebAssembly host runtime for waPC-compliant modules

WasiParams

Parameters defining the options for enabling WASI on a module (if applicable)

Constants

HOST_NAMESPACE

The host module name / namespace that guest modules must use for imports

Traits

ModuleHost

The module host (waPC) must provide an implementation of this trait to the engine provider to enable waPC function calls.

WebAssemblyEngineProvider

An engine provider is any code that encapsulates low-level WebAssembly interactions such as reading from and writing to linear memory, executing functions, and mapping imports in a way that conforms to the waPC conversation protocol.

Type Definitions

Result

A result type for errors that occur within the wapc library