๐จ 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