hammertime

package module
v0.0.0-...-f75486c Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 11, 2023 License: BSD-2-Clause Imports: 17 Imported by: 0

README

🔨 hammertime GoDoc

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

Documentation

Overview

Example
wasm, err := os.ReadFile(filepath.Join("testdata", "args.wasm"))
if err != nil {
	panic(err)
}

engine := wasmtime.NewEngine()
store := wasmtime.NewStore(engine)
module, err := wasmtime.NewModule(engine, wasm)
if err != nil {
	panic(err)
}
linker := wasmtime.NewLinker(engine)

stdout := new(bytes.Buffer)
wasi := NewWASI(
	WithArgs([]string{"args.wasm", "hello", "world"}),
	WithStdout(stdout),
)
wasi.Link(store, linker)

instance, err := linker.Instantiate(store, module)
if err != nil {
	panic(err)
}

start := instance.GetFunc(store, "_start")
start.Call(store)

// captured stdout:
fmt.Println(stdout.String())
Output:
3
0: args.wasm
1: hello
2: world

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Clock

type Clock interface {
	// Now reports the current time.
	Now() time.Time
}

Clock can be used to customize the current time.

var SystemClock Clock = systemClock{}

SystemClock uses the host OS time.

type FixedClock

type FixedClock time.Time

FixedClock always reports the same time (itself).

func (FixedClock) Now

func (fc FixedClock) Now() time.Time

type Option

type Option func(*WASI)

func WithArgs

func WithArgs(args []string) Option

WithArgs sets the command line arguments to args.

func WithClock

func WithClock(clock Clock) Option

WithClock sets the clock. TODO: clock types.

func WithDebug

func WithDebug(debug bool) Option

WithDebug enables spammy debug logs.

func WithEnv

func WithEnv(env map[string]string) Option

WithEnv sets the environment to env, a map of names to values.

func WithFS

func WithFS(fsys fs.FS) Option

WithFS uses the given filesystem.

func WithStderr

func WithStderr(w io.Writer) Option

WithStdout sets standard error to w.

func WithStdin

func WithStdin(r io.Reader) Option

WithStdin sets standard input to r.

func WithStdout

func WithStdout(w io.Writer) Option

WithStdout sets standard output to w.

type WASI

type WASI struct {
	// contains filtered or unexported fields
}

WASI is a WASI environment.

func NewWASI

func NewWASI(opts ...Option) *WASI

NewWASI creates a new WASI environment. Currently they may not be shared between instances.

func (wasi *WASI) Link(store wasmtime.Storelike, linker *wasmtime.Linker) error

Link defines all (supported) WASI functions on the given linker.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL