Custom WASM Modules
Extend Equana with high-performance WebAssembly functions
WebAssembly (WASM) modules let you extend Equana with custom high-performance functions written in languages like C, C++, or Rust. Upload a compiled WASM binary along with a JSON manifest describing your functions, and call them directly from Equana.
Use cases: Implement performance-critical algorithms, wrap existing C/C++ libraries, or add specialized functions not available in the core library.
How It Works
- Write your function in C, C++, or Rust
- Compile to WebAssembly (.wasm file)
- Create a manifest file (.wasm.json) describing your functions
- Upload both files to your workspace
- Call your functions from Equana
# Workspace file structure:
#
# my_workspace/
# scripts/
# notebook.json
# functions/
# myfunction.m
# mymodule.wasm # Compiled WASM binary
# mymodule.wasm.json # Manifest file
#
# The .wasm and .wasm.json files must have matching names.
# Place them in your workspace root or any subdirectory.Manifest Format
The manifest is a JSON file that describes your WASM module and its exported functions. It must have the same base name as your .wasm file with a .wasm.json extension.
Module Fields
| Field | Type | Description | Required |
|---|---|---|---|
version | number | Schema version (always 1) | Yes |
name | string | Display name for the module | Yes |
description | string | Module description | No |
wasmFile | string | WASM filename (defaults to {manifest}.wasm) | No |
memory | object | Memory configuration { initial, maximum } | No |
functions | array | Array of function definitions | Yes |
Function Fields
| Field | Type | Description |
|---|---|---|
name | string | Equana function name (what users call) |
wasmExport | string | Actual export name in WASM binary |
signature | string | Display signature (e.g., "y = func(x)") |
params | array | Parameter definitions with name and type |
returns | object | Return type and optional shape |
# mymodule.wasm.json - Manifest file structure
#
# {
# "version": 1,
# "name": "my_module",
# "description": "My custom math operations",
# "functions": [
# {
# "name": "scale_array",
# "wasmExport": "scale_f64",
# "signature": "y = scale_array(x, factor)",
# "params": [
# { "name": "x", "type": "f64[]" },
# { "name": "factor", "type": "f64" }
# ],
# "returns": { "type": "f64[]" }
# }
# ]
# }Type Marshalling
Equana automatically converts between Equana values and WASM types. Here's how types are mapped:
| Equana Type | WASM Type | Notes |
|---|---|---|
| number (scalar) | f64 | 64-bit float |
| number (scalar) | i32 | 32-bit integer (truncated) |
| NDArray | f64[] | Float array (pointer + length) |
| NDArray | i32[] | Integer array (pointer + length) |
| boolean | i32 | true=1, false=0 |
| (no return) | void | Function returns null |
Array handling: Arrays are flattened to 1D and passed as a pointer plus length. Use the
shapefield in returns to specify how the output should be reshaped (e.g.,["input.rows", "input.cols"]).
Scalar Functions
For functions that take and return scalar values, use f64 or i32 types:
# Manifest for a scalar function (add two numbers)
#
# {
# "version": 1,
# "name": "math_utils",
# "functions": [
# {
# "name": "fast_add",
# "wasmExport": "add_i32",
# "signature": "c = fast_add(a, b)",
# "params": [
# { "name": "a", "type": "i32" },
# { "name": "b", "type": "i32" }
# ],
# "returns": { "type": "i32" }
# }
# ]
# }Array Functions
For array operations, use f64[] or i32[] types. Specify the output shape to preserve dimensions:
# Manifest for array operations that preserve shape
#
# {
# "version": 1,
# "name": "array_ops",
# "functions": [
# {
# "name": "array_square",
# "wasmExport": "square_elements",
# "signature": "y = array_square(x)",
# "params": [
# { "name": "x", "type": "f64[]" }
# ],
# "returns": {
# "type": "f64[]",
# "shape": ["input.rows", "input.cols"]
# }
# }
# ]
# }
#
# The shape field preserves the input dimensions:
# - "input.rows" uses the first input's row count
# - "input.cols" uses the first input's column countShape References
The shape field supports these references:
"input.rows"— Row count of first array input"input.cols"— Column count of first array inputnumber— Fixed dimension (e.g.,[1, 10])
Writing WASM Code
You can write WASM modules in any language that compiles to WebAssembly. Here are examples in C and Rust:
C Example
# Example C source (mymodule.c):
#
# #include <stdint.h>
#
# __attribute__((export_name("add_arrays")))
# void add_arrays(
# double* a, int32_t len_a,
# double* b, int32_t len_b,
# double* out
# ) {
# for (int i = 0; i < len_a; i++) {
# out[i] = a[i] + b[i];
# }
# }
#
# Compile with: clang --target=wasm32 -O2 -nostdlib \
# -Wl,--no-entry -Wl,--export-all -o mymodule.wasm mymodule.cRust Example
# Example Rust source (lib.rs):
#
# #[no_mangle]
# pub extern "C" fn scale_f64(
# ptr: *const f64,
# len: usize,
# factor: f64,
# out_ptr: *mut f64,
# ) {
# unsafe {
# for i in 0..len {
# *out_ptr.add(i) = *ptr.add(i) * factor;
# }
# }
# }
#
# Compile with: cargo build --target wasm32-unknown-unknown --releaseMemory Configuration
For modules that process large arrays, you can configure memory limits:
# Memory configuration in manifest:
#
# {
# "version": 1,
# "name": "large_arrays",
# "memory": {
# "initial": 256, # Initial pages (256 * 64KB = 16MB)
# "maximum": 1024 # Max pages (1024 * 64KB = 64MB)
# },
# "functions": [...]
# }
#
# Default: 256 initial pages (16MB), 1024 max (64MB)
# Each page is 64KB. Increase for large array operations.Complete Example
Here's a complete workflow for creating a custom dot product function:
# Complete Example: Custom dot product
#
# 1. Write C code (dot_product.c):
# double dot_product(double* a, int len_a, double* b, int len_b) {
# double sum = 0.0;
# for (int i = 0; i < len_a; i++) sum += a[i] * b[i];
# return sum;
# }
#
# 2. Create manifest (dot_product.wasm.json):
# {
# "version": 1,
# "name": "dot_product",
# "functions": [{
# "name": "fast_dot",
# "wasmExport": "dot_product",
# "signature": "d = fast_dot(a, b)",
# "params": [
# { "name": "a", "type": "f64[]" },
# { "name": "b", "type": "f64[]" }
# ],
# "returns": { "type": "f64" }
# }]
# }
#
# 3. Upload both files to your workspace
#
# 4. Use in Equana:
# a = [1, 2, 3, 4, 5];
# b = [2, 2, 2, 2, 2];
# result = fast_dot(a, b) # Returns 30Tips & Best Practices
- Use
f64for compatibility (all Equana numbers are doubles) - Export a
mallocandfreefunction for better memory management - Keep function names descriptive but short (they become Equana function names)
- Test your WASM module locally before uploading
- For debugging, add a simple test function that returns a known value
Security note: WASM runs in a sandboxed environment and cannot access the filesystem, network, or other system resources. It can only operate on data passed to it through function parameters.