๐จ hammertime 
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.
Status
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!
Features
- Uses
fs.FS for the Wasm filesystem. Supports hackpadfs extensions to add writing, etc.
stdin can be set to an io.Reader.
stdout and stderr can be set to a io.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 |
๐ถโ๐ซ๏ธ |
Legend
|
Interpretation |
| ๐ |
Pretty good |
| ๐ |
Not bad |
| ๐ง |
Needs more work/testing/love |
| ๐ถโ๐ซ๏ธ |
Stub/missing |
Usage
See: Godoc
Quick Start
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.
Testing
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