import "github.com/guregu/hammertime"
Do you want to use the excellent wasmtime-go Wasm runtime library, but are missing some features like capturing stdout or setting stdin?
This library is a WASI implementation in Go for wasmtime-go. Its goal is to integrate Wasm more deeply with Go but still take advantage of Wasmtime's speedy runtime.
Rough proof of concept targeting wasi_snapshot_preview1
. If this project proves to be useful, I'm thinking a code generation approach targeting preview2 would be the next step.
TL;DR: Alpha!
- βοΈ Note that hammertime does not implement the preview1 capabilities model (yet?).
- β£οΈ It's also not safe to share WASI instances concurrently or across instances (yet?).
- π Lots of
unsafe
. Needs fuzzing or something. - π€ Experimental. Ideas welcome!
- Uses
fs.FS
for the Wasm filesystem. Supportshackpadfs
extensions to add writing, etc. stdin
can be set to anio.Reader
.stdout
andstderr
can be set to aio.Writer
.- More experimental stuff coming soon?
WASI API | Vibe |
---|---|
args_sizes_get | π |
args_get | π |
environ_sizes_get | π |
environ_get | π |
clock_time_get | π§ |
fd_close | π§ |
fd_fdstat_get | π |
fd_fdstat_set_flags | πΆβπ«οΈ |
fd_prestat_get | π |
fd_prestat_dir_name | π |
fd_filestat_get | π§ |
fd_seek | π |
fd_write | π |
fd_read | π |
fd_pread | π§ |
fd_readdir | π |
path_open | π§ |
path_filestat_get | π§ |
path_readlink | π§ |
path_rename | π§ |
path_create_directory | π |
path_remove_directory | π |
path_unlink_file | π |
poll_oneoff | πΆβπ«οΈ |
proc_exit | πΆβπ«οΈ |
Interpretation | |
---|---|
π | Pretty good |
π | Not bad |
π§ | Needs more work/testing/love |
πΆβπ«οΈ | Stub/missing |
See: Godoc
Imagine we have this C program we want to execute as WebAssembly. It's a simple program that receives a newline-separated list of who to greet via standard input, and writes "hello {name}" to standard output.
int main() {
char *line = NULL;
size_t len = 0;
ssize_t read = 0;
while ((read = getline(&line, &len, stdin)) != -1) {
printf("hello %s", line);
}
free(line);
return 0;
}
We can embed and execute it in a Go program like so, capturing the output:
import (
"bytes"
_ "embed"
"log"
"os"
"github.com/bytecodealliance/wasmtime-go/v11"
"github.com/guregu/hammertime"
)
//go:embed hello.wasm
var wasmModule []byte // Protip: stuff your modules into your binary with embed
func main() {
// Standard boilerplate
engine := wasmtime.NewEngine()
store := wasmtime.NewStore(engine)
module, err := wasmtime.NewModule(engine, wasmModule)
if err != nil {
panic(err)
}
linker := wasmtime.NewLinker(engine)
// Prepare our input and output
input := "alice\nbob\n"
stdin := strings.NewReader(input)
stdout := new(bytes.Buffer)
// Set up our custom WASI
wasi := hammertime.NewWASI(
WithArgs([]string{"hello.wasm"}),
WithStdin(stdin), // Stdin can be any io.Reader
WithStdout(stdout), // Capture stdout to a *bytes.Buffer!
WithFS(os.DirFS("testdata")), // Works with Go's fs.FS! (kind of)
)
// Link our WASI
if err := wasi.Link(store, linker); err != nil {
panic(err)
}
// Use wasmtime as normal
instance, err := linker.Instantiate(store, module)
if err != nil {
panic(err)
}
start := instance.GetFunc(store, "_start")
_, err = start.Call(store)
if err != nil {
panic(err)
}
// Grab captured stdout data
output := stdout.String()
fmt.Println(output)
// Prints: hello alice
// hello bob
}
This gives us an easy way to communicate with wasm modules.
Each testdata/*.c file is a little self-contained C program that tests a WASI feature.
To build/run the test files, install WASI SDK, then do something like:
$ export WASI_CC=/path/to/wasi-sdk-XX.0/bin/clang
$ make -j8