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