WebAssembly Execution Engine
Pyroxide includes a high-performance, sandboxed WebAssembly (WASM) execution engine powered by wasmtime. This engine allows you to run safe, compiled, low-latency code in background workers without having to rebuild or redeploy Pyroxide itself.
It provides a completely dynamic scripting alternative that runs at native execution speeds while remaining fully isolated from the host operating system.
Architecture & Memory Protocol
Since WebAssembly runs in a strict sandbox, the guest module does not share memory addresses directly with Pyroxide. To pass data back and forth, Pyroxide implements a lightweight Host-Guest Memory Protocol:
- Host Allocation: The host calls the guest’s exported
alloc(size)function to allocate a buffer ofsizebytes inside the WASM linear memory. - Payload Transfer: The host writes the input payload bytes (String or Bytes) directly into the guest memory at the returned offset pointer.
- Execution: The host calls the target function (e.g.
run(ptr, len)) returning a packedu64containing the output pointer and length:out_ptr= high 32 bitsout_len= low 32 bits
- Result Retrieval: The host reads the resulting bytes from the guest memory using the unpacked offset and length, then reconstructs the Python return type.
- Host Deallocation: The host calls the guest’s exported
dealloc(ptr, size)function on both the input and output buffers to prevent memory leaks in the guest runtime.
Writing a WASM Guest Module (Rust)
Here is a template for compiling a Rust module to wasm32-unknown-unknown that processes input text:
#![allow(unused)]
#![no_std]
#![no_main]
fn main() {
use core::panic::PanicInfo;
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {}
}
// 64KB static buffer to simplify memory management
static mut BUFFER: [u8; 65536] = [0; 65536];
#[no_mangle]
pub extern "C" fn alloc(_size: u32) -> u32 {
unsafe { BUFFER.as_mut_ptr() as u32 }
}
#[no_mangle]
pub extern "C" fn dealloc(_ptr: u32, _size: u32) {
// No-op for static buffer, or implement dynamic heap dealloc
}
#[no_mangle]
pub unsafe extern "C" fn run(ptr: u32, len: u32) -> u64 {
let slice = core::slice::from_raw_parts_mut(ptr as *mut u8, len as usize);
for c in slice.iter_mut() {
match *c {
b'a'..=b'm' | b'A'..=b'M' => *c += 13,
b'n'..=b'z' | b'N'..=b'Z' => *c -= 13,
_ => {}
}
}
// Return packed pointer (high 32 bits) and length (low 32 bits)
((ptr as u64) << 32) | (len as u64)
}
}
Compile the file using rustc directly:
rustc --target wasm32-unknown-unknown -O --crate-type=cdylib module.rs -o module.wasm
Python Usage
1. Registering the Module
Load the compiled .wasm bytecode in Python and register it in Pyroxide’s global module registry:
from pyroxide import register_wasm
with open("module.wasm", "rb") as f:
wasm_bytes = f.read()
# Register under a unique name
register_wasm("my_module", wasm_bytes)
2. Submitting WASM Tasks
Decorate standard functions using @wasm_task to offload work:
from pyroxide import wasm_task
@wasm_task("my_module", "run")
def rot13_cipher(payload: str) -> str:
"""This function acts as a type stub. Execution is redirected to the WASM runner."""
pass
# Run in background asynchronously (GIL-free)
handle = rot13_cipher("Hello World!")
print("Status:", handle.status)
# Await output
result = handle.result()
print("Decrypted:", result) # "Uryyb Jbeyq!"
Benefits of the WASM Engine
- Safety & Isolation: Code runs within the
wasmtimesandbox. A crash or panic in guest code cannot crash the host Python runtime or the Pyroxide broker. - Dynamic Updates: Register new modules and trigger task updates at runtime without restarting worker threads or redeploying code.
- GIL-Free Speed: Native execution runs concurrently across the worker pool without ever locking Python’s GIL.