Skip to content

Commit

Permalink
internal/wasmtools, all: refinements by @ydnar
Browse files Browse the repository at this point in the history
.github/workflows: remove some (now) unneeded steps

.gitignore: condense internal/wasmtools/.gitignore

internal/wasmtools: use single quotes, add Rust edition

Makefile: update order of wasm-tools.wasm.\* targets)

internal/wasmtools: use gzipped wasm-tools.wasm.gz with sync.Once

internal/wasmtools: rebuild wasm-tools.wasm.gz

CHANGELOG: wordsmith the wasm-tools and Wazero update

internal/wasmtools, wit: remove optional name arg

Removing this did not seem to affect tests.

internal/wasmtools: leave timeout to the caller

Makefile: reorder gzip targets

internal/wasmtools: oops

Makefile: next try

wit: remove check for wasm-tools in PATH

internal/wasmtools, wit: swap key and value types for fsMap

Not all fs.FS are hashable, but all strings are, so use map[string]fs.FS instead.

wit/bindgen: use internal/wasmtools to run wasm-tools in WebAssembly

internal/wasmtools: use wazero.CompilationCache

This speeds up wit/bindgen tests 10x

internal/wasmtools: fix TinyGo impl

internal/wasmtools: remove Runner interface

This wasn’t used anywhere, so removing.

internal/wasmtools: change Run() to accept optional stdout and stderr

internal/wasmtools: move args to varadic trailing arg in Run

wit: (*testing.common).Errorf does not support error-wrapping directive %w

internal/wasmtools, wit/bindgen: additional cleanups for TinyGo and WASI

.github/workflows/test: TinyGo needs wasm-tools for -target=wasip2

.github/dependabot: add internal/wasmtools for Cargo updates

cmd/wit-bindgen-go, internal/{module,witcli}: extract module helper into new package

internal/wasmtools: try to use disk-based compilation cache in TMPDIR

internal/cmd/wasm-tools: add Wazero-based wasm-tools executable

This removes the requirement for wasmtime in the Makefile

Makefile: use Wazero-based internal/cmd/wasm-tools executable instead of Wasmtime

.github/workflows/test: ignore all Markdown files

internal/wasmtools: rebuild wasm-tools.wasm.gz

internal/{cmd/wasm-tools,wasmtools}, wit: default mapping of ./ and / directories

.github/workflows: install wasm wrapped in Go version of wasm-tools

all: revert 26c1e72; Wazero directory handling tricky to manage

Wazero flattens ./ into '', which is treated as /, overriding the / mapping.

Makefile: just use wasm-tools

It’s faster, and works with arbitrary paths.
  • Loading branch information
ydnar committed Dec 20, 2024
1 parent 6e9a74c commit a411917
Show file tree
Hide file tree
Showing 22 changed files with 275 additions and 214 deletions.
6 changes: 6 additions & 0 deletions .github/dependabot.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,9 @@ updates:
schedule:
interval: weekly
open-pull-requests-limit: 10

- package-ecosystem: cargo
directory: "/internal/wasmtools"
schedule:
interval: weekly
open-pull-requests-limit: 10
3 changes: 0 additions & 3 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,6 @@ jobs:
with:
go-version-file: go.mod

- name: Set up wams-tools.wasm
run: make internal/wasmtools/wasm-tools.wasm

- name: Run Go tests
run: go test ${{ env.go-modules }}

Expand Down
26 changes: 3 additions & 23 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ on:
paths-ignore:
- '.prettier*'
- '.vscode/**'
- '*.md'
- '**/*.md'
- 'docs/**'
- 'LICENSE'

Expand All @@ -34,13 +34,8 @@ jobs:
with:
go-version-file: go.mod

- name: Set up wams-tools.wasm
run: make internal/wasmtools/wasm-tools.wasm

- name: Vet Go code
run: |
make build
go vet ${{ env.go-modules }} ./tests/...
run: go vet ${{ env.go-modules }} ./tests/...

# Test with Go
test-go:
Expand All @@ -61,14 +56,6 @@ jobs:
with:
go-version: ${{ matrix.go-version }}

- name: Set up wasm-tools
uses: bytecodealliance/actions/wasm-tools/setup@v1
with:
version: ${{ env.wasm-tools-version }}

- name: Set up wams-tools.wasm
run: make internal/wasmtools/wasm-tools.wasm

- name: Run Go tests
run: go test -v ${{ env.go-modules }}

Expand Down Expand Up @@ -108,11 +95,6 @@ jobs:
with:
tinygo-version: ${{ matrix.tinygo-version }}

- name: Set up wasm-tools
uses: bytecodealliance/actions/wasm-tools/setup@v1
with:
version: ${{ env.wasm-tools-version }}

- name: Test with TinyGo
run: tinygo test -v ${{ env.go-modules }}

Expand Down Expand Up @@ -144,14 +126,12 @@ jobs:
with:
tinygo-version: ${{ matrix.tinygo-version }}

# TinyGo needs wasm-tools for -target=wasip2
- name: Set up wasm-tools
uses: bytecodealliance/actions/wasm-tools/setup@v1
with:
version: ${{ env.wasm-tools-version }}

- name: Set up wams-tools.wasm
run: make internal/wasmtools/wasm-tools.wasm

- name: Set up Wasmtime
uses: bytecodealliance/actions/wasmtime/setup@v1
with:
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.DS_Store
/generated
/internal/wasmtools/target
/internal/wasmtools/wasm-tools.wasm
go.work.sum
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),

### Added

- `internal/wasmtools` now loads compiled `wasm-tools` Wasm module from the `wasm-tools` crate and executes it using wazero. This allows `wit-bindgen-go` to run on any platform without needing to install `wasm-tools` natively.
- [`wasm-tools`](https://crates.io/crates/wasm-tools) is now vendored as a WebAssembly module, executed using [Wazero](https://wazero.io/). This allows package `wit` and `wit-bindgen-go` to run on any supported platform without needing to separately install `wasm-tools`.

### Changed

Expand Down
21 changes: 11 additions & 10 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ json: $(wit_files)

.PHONY: $(wit_files)
$(wit_files): internal/wasmtools/wasm-tools.wasm
wasmtime --dir . internal/wasmtools/wasm-tools.wasm component wit -j --all-features $@ > $@.json
wasm-tools component wit -j --all-features $@ > $@.json

# golden recompiles the .golden.wit test files.
.PHONY: golden
Expand All @@ -21,25 +21,26 @@ generated: clean json
.PHONY: clean
clean:
rm -rf ./generated/*
rm -f internal/wasmtools/wasm-tools.wasm
rm -f internal/wasmtools/wasm-tools.wasm.gz

# tests/generated writes generated Go code to the tests directory
.PHONY: tests/generated
tests/generated: json
go generate ./tests

# build builds the cmd/wit-bindgen-go binary
.PHONY: build
build: internal/wasmtools/wasm-tools.wasm
go build -o wit-bindgen-go ./cmd/wit-bindgen-go
# wasm-tools builds the internal/wasmtools/wasm-tools.wasm.gz artifact
.PHONY: wasm-tools
wasm-tools: internal/wasmtools/target/wasm32-wasip1/release/wasm-tools.wasm
gzip -c $< > internal/wasmtools/wasm-tools.wasm.gz

internal/wasmtools/wasm-tools.wasm: internal/wasmtools/wasm-tools.wasm.gz
gzip -dc $< > $@

internal/wasmtools/wasm-tools.wasm.gz:
internal/wasmtools/target/wasm32-wasip1/release/wasm-tools.wasm: internal/wasmtools/Cargo.*
cd internal/wasmtools && \
cargo build --target wasm32-wasip1 --release -p wasm-tools
gzip -c internal/wasmtools/target/wasm32-wasip1/release/wasm-tools.wasm > $@

# internal/wasmtools/wasm-tools.wasm decompresses wasm-tools.wasm.gz for other make targets
internal/wasmtools/wasm-tools.wasm: internal/wasmtools/wasm-tools.wasm.gz
gzip -dc internal/wasmtools/wasm-tools.wasm.gz > $@

# test runs Go and TinyGo tests
GOTESTARGS :=
Expand Down
4 changes: 2 additions & 2 deletions cmd/wit-bindgen-go/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (

"go.bytecodealliance.org/cmd/wit-bindgen-go/cmd/generate"
"go.bytecodealliance.org/cmd/wit-bindgen-go/cmd/wit"
"go.bytecodealliance.org/internal/witcli"
"go.bytecodealliance.org/internal/module"
)

func main() {
Expand Down Expand Up @@ -64,7 +64,7 @@ var version = &cli.Command{
Name: "version",
Usage: "print the version",
Action: func(ctx context.Context, cmd *cli.Command) error {
fmt.Fprintf(cmd.Writer, "%s version %s\n", cmd.Root().Name, witcli.Version())
fmt.Fprintf(cmd.Writer, "%s version %s\n", cmd.Root().Name, module.Version())
return nil
},
}
37 changes: 37 additions & 0 deletions internal/cmd/wasm-tools/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package main

import (
"context"
"fmt"
"io/fs"
"os"

"go.bytecodealliance.org/internal/wasmtools"
)

func main() {
ctx := context.Background()
wasmTools, err := wasmtools.New(ctx)
if err != nil {
exit(err)
}

fsMap := map[string]fs.FS{
"./": os.DirFS("./"),
}

var args []string
if len(os.Args) != 0 {
args = os.Args[1:]
}

err = wasmTools.Run(ctx, os.Stdin, os.Stdout, os.Stderr, fsMap, args...)
if err != nil {
exit(err)
}
}

func exit(err error) {
fmt.Fprintf(os.Stderr, "wasm-tools: %v\n", err)
os.Exit(1)
}
22 changes: 18 additions & 4 deletions internal/witcli/version.go → internal/module/module.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,32 @@
package witcli
package module

import (
"runtime/debug"
"sync"
)

// Version returns the version string of this module.
// Path returns the path of the main module.
func Path() string {
build := buildInfo()
if build == nil {
return "(none)"
}
return build.Main.Path
}

// Version returns the version string of the main module.
func Version() string {
return versionString()
}

var buildInfo = sync.OnceValue(func() *debug.BuildInfo {
build, _ := debug.ReadBuildInfo()
return build
})

var versionString = sync.OnceValue(func() string {
build, ok := debug.ReadBuildInfo()
if !ok {
build := buildInfo()
if build == nil {
return "(none)"
}
version := build.Main.Version
Expand Down
2 changes: 0 additions & 2 deletions internal/wasmtools/.gitignore

This file was deleted.

11 changes: 6 additions & 5 deletions internal/wasmtools/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
[package]
name = "wasm-tools-go"
name = 'wasm-tools-go'
edition = '2021'

[dependencies]
wasm-tools = { version = "1.221.0", default-features = false, features = [
"component",
wasm-tools = { version = '1.221.0', default-features = false, features = [
'component',
] }

[profile.release]
lto = true # compile with link-time optimization
codegen-units = 1 # compile with a single codegen unit
opt-level = "z" # optimize for size
panic = "abort" # panic=abort will remove the panic code
opt-level = 'z' # optimize for size
panic = 'abort' # panic=abort will remove the panic code
strip = true # strip the debug information
14 changes: 14 additions & 0 deletions internal/wasmtools/instance.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package wasmtools

import (
"context"
"io"
"io/fs"
)

type instance interface {
Close(ctx context.Context) error
Run(ctx context.Context, stdin io.Reader, stdout, stderr io.Writer, fsMap map[string]fs.FS, args ...string) error
}

var _ instance = &Instance{}
118 changes: 118 additions & 0 deletions internal/wasmtools/instance_other.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
//go:build !tinygo

package wasmtools

import (
"bytes"
"compress/gzip"
"context"
"crypto/rand"
_ "embed"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
"strings"
"sync"

"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
"go.bytecodealliance.org/internal/module"
)

//go:embed wasm-tools.wasm.gz
var compressed []byte

var decompress = sync.OnceValues(func() ([]byte, error) {
r, err := gzip.NewReader(bytes.NewReader(compressed))
if err != nil {
return nil, err
}
defer r.Close()
var buf bytes.Buffer
_, err = buf.ReadFrom(r)
return buf.Bytes(), err
})

var compilationCache = sync.OnceValue(func() wazero.CompilationCache {
// First try on-disk cache, so subsequent invocations can share cache
tmp := os.TempDir()
if tmp != "" {
rep := strings.NewReplacer(" ", "", "(", "", ")", "")
dir := filepath.Join(tmp, rep.Replace(module.Path()+"@"+module.Version()))
c, err := wazero.NewCompilationCacheWithDir(dir)
if err == nil {
return c
}
}

// Fall back to in-memory cache
return wazero.NewCompilationCache()
})

// Instance is a compiled wazero instance.
type Instance struct {
runtime wazero.Runtime
module wazero.CompiledModule
}

// New creates a new wazero instance.
func New(ctx context.Context) (*Instance, error) {
c := wazero.NewRuntimeConfig().
WithCloseOnContextDone(true).
WithCompilationCache(compilationCache())

r := wazero.NewRuntimeWithConfig(ctx, c)
if _, err := wasi_snapshot_preview1.Instantiate(ctx, r); err != nil {
return nil, fmt.Errorf("error instantiating WASI: %w", err)
}

wasmTools, err := decompress()
if err != nil {
return nil, err
}

module, err := r.CompileModule(ctx, wasmTools)
if err != nil {
return nil, fmt.Errorf("error compiling wasm module: %w", err)
}
return &Instance{runtime: r, module: module}, nil
}

// Close closes the wazero runtime resource.
func (w *Instance) Close(ctx context.Context) error {
return w.runtime.Close(ctx)
}

// Run runs the wasm module with the context, arguments,
// and optional stdin, stdout, stderr, and filesystem map.
// Supply a context with a timeout or other cancellation mechanism to control execution time.
// Returns an error if instantiation fails.
func (w *Instance) Run(ctx context.Context, stdin io.Reader, stdout, stderr io.Writer, fsMap map[string]fs.FS, args ...string) error {
config := wazero.NewModuleConfig().
WithRandSource(rand.Reader).
WithSysNanosleep().
WithSysNanotime().
WithSysWalltime().
WithArgs(append([]string{"wasm-tools.wasm"}, args...)...)

if stdin != nil {
config = config.WithStdin(stdin)
}
if stdout != nil {
config = config.WithStdout(stdout)
}
if stderr != nil {
config = config.WithStderr(stderr)
}

fsConfig := wazero.NewFSConfig()
for guestPath, guestFS := range fsMap {
fsConfig = fsConfig.WithFSMount(guestFS, guestPath)
}
config = config.WithFSConfig(fsConfig)

_, err := w.runtime.InstantiateModule(ctx, w.module, config)
return err
}
Loading

0 comments on commit a411917

Please sign in to comment.