These are some quick notes documenting a Saturday morning's experiment. All the steps were executed on Linux.
TL;DR: What?
Compiling a static binary that runs a compiled Ahead-of-Time (AOT) WASM module.
Ideally it'd have been WASM bytecode compiled to native code, but I couldn't find a way that didn't rely on the WASM runtime, invoking it from the command line or embedded in a C program.
Why?
I've been thinking and learning this week about WebAssembly and its potential since I read "WASM will replace containers".
I decided initially to use Wasmer and ended filing a question on their repository because their own native binary build command doesn't work as expected.
Finally, I landed on Bytecode Alliance's WebAssembly Micro Runtime (WAMR) wamrc
compiler.
How?
The code
(module
;; Import the required WASI functions
(import "wasi_snapshot_preview1" "fd_write" (func $fd_write (param i32 i32 i32 i32) (result i32)))
;; Define a buffer to store the message
(memory (export "memory") 1)
(data (i32.const 8) "Hello, World!\n")
;; Define the _start function
(func $main (export "_start")
;; Setup the iovec array
(i32.store (i32.const 0) (i32.const 8)) ;; pointer to the message
(i32.store (i32.const 4) (i32.const 14)) ;; length of the message
;; Call fd_write
(call $fd_write
(i32.const 1) ;; file_descriptor - 1 for stdout
(i32.const 0) ;; *iovs - pointer to the iovec array
(i32.const 1) ;; iovs_len - number of iovec entries
(i32.const 20) ;; nwritten - where to store the number of bytes written
)
drop ;; Discard the result
)
)
This is WebAssembly Text. Learn more here.
The tools
Everything had to be compiled from scratch.
wasm-opt
isn't really required, so I'll skip it. I learnt about it while looking at these benchmarks, and I want these notes to keep track of the interesting tidbits I discovered.
The process
First, let's generate WASM bytecode from our WebAssembly Text code:
wat2wasm ./hello_world.wat -o ./hello_world.wasm
Next, compile it:
wamrc --format=aot -o ./hello_world.aot ./hello_world.wasm
For the last step, we need some C glue to embed the WAMR runtime:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "wasm_export.h" // Now points to WAMR's core/iwasm/include/
#include "hello_world.h"
// Define a static pool buffer with proper alignment
#define POOL_SIZE (256 * 1024) // 256 KB pool
static uint8_t global_pool_buf[POOL_SIZE] __attribute__((aligned(8))); // 8-byte alignment
int main() {
RuntimeInitArgs init_args;
memset(&init_args, 0, sizeof(RuntimeInitArgs));
// Configure memory pool
init_args.mem_alloc_type = Alloc_With_Pool; // Use pool allocator
init_args.mem_alloc_option.pool.heap_buf = global_pool_buf;
init_args.mem_alloc_option.pool.heap_size = sizeof(global_pool_buf);
// Initialize the WAMR runtime
if (!wasm_runtime_full_init(&init_args)) {
printf("Runtime initialization failed.\n");
return -1;
}
// Load the AOT module from the embedded blob
char error_buf[128];
wasm_module_t module = wasm_runtime_load(hello_world_aot, hello_world_aot_len, error_buf, sizeof(error_buf));
if (!module) {
printf("Load failed: %s\n", error_buf);
return -1;
}
// Instantiate the module
wasm_module_inst_t inst = wasm_runtime_instantiate(module, 65536, 65536, error_buf, sizeof(error_buf));
if (!inst) {
printf("Failed to instantiate: %s\n", error_buf);
return -1;
}
// Call a function named "main" exported from the WASM module
wasm_function_inst_t func = wasm_runtime_lookup_function(inst, "_start");
if (!func) {
printf("Function '_start' not found.\n");
return -1;
}
wasm_exec_env_t env = wasm_runtime_create_exec_env(inst, 65536);
if (!env) {
printf("Failed to create environment.\n");
return -1;
}
// Execute the function
wasm_runtime_call_wasm(env, func, 0, NULL);
// Cleanup
wasm_runtime_deinstantiate(inst);
wasm_runtime_unload(module);
wasm_runtime_destroy();
return 0;
}
The sharp reader may be asking "Where are the wasm_export.h
and hello_world.h
includes? The former is in wasm-micro-runtime/core/iwasm/include
, and the later needs to be generated:
xxd -i hello_world.aot > hello_world.h
And we can compile and run it:
gcc -static $HOME/Code/hello-wasm/main.c $HOME/Code/wasm-micro-runtime/product-mini/platforms/linux/build/libiwasm.a -lm -I $HOME/Code/wasm-micro-runtime/core/iwasm/include -I $HOME/Code/hello-wasm -o $HOME/Code/hello-wasm/hello_world
$HOME/Code/hello-wasm/hello_world
Hello, World!
Note: it's not a perfect statically-linked binary. It depends on getaddrinfo
, but this is better left out of scope right now.
Image by Duncan Cumming.