experimental

package
v1.8.0 Latest Latest
Warning

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

Go to latest
Published: Aug 14, 2024 License: Apache-2.0 Imports: 3 Imported by: 28

Documentation

Overview

Package experimental includes features we aren't yet sure about. These are enabled with context.Context keys.

Note: All features here may be changed or deleted at any time, so use with caution!

Example (CloseNotifier)

This shows how to implement a custom cleanup task on close.

package main

import (
	"context"

	"github.com/tetratelabs/wazero/experimental"
)

var ctx context.Context

// This shows how to implement a custom cleanup task on close.
func main() {
	closeCh := make(chan struct{})
	ctx = experimental.WithCloseNotifier(
		context.Background(),
		experimental.CloseNotifyFunc(func(context.Context, uint32) { close(closeCh) }),
	)

	// ... create module, do some work. Sometime later in another goroutine:

	select {
	case <-closeCh:
		// do some cleanup
	default:
		// do some more work with the module
	}

}
Output:

Example (CustomListenerFactory)

This shows how to make a listener that counts go function calls.

package main

import (
	"context"
	"fmt"
	"log"
	"sort"

	_ "embed"
	"github.com/tetratelabs/wazero"
	"github.com/tetratelabs/wazero/api"
	"github.com/tetratelabs/wazero/experimental"
	"github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
)

// listenerWasm was generated by the following:
//
//	cd logging/testdata; wat2wasm --debug-names listener.wat
//
//go:embed logging/testdata/listener.wasm
var listenerWasm []byte

// uniqGoFuncs implements both FunctionListenerFactory and FunctionListener
type uniqGoFuncs map[string]struct{}

// callees returns the go functions called.
func (u uniqGoFuncs) callees() []string {
	ret := make([]string, 0, len(u))
	for k := range u {
		ret = append(ret, k)
	}

	sort.Strings(ret)
	return ret
}

// NewFunctionListener implements FunctionListenerFactory.NewFunctionListener
func (u uniqGoFuncs) NewFunctionListener(def api.FunctionDefinition) experimental.FunctionListener {
	if def.GoFunction() == nil {
		return nil
	}
	return u
}

// Before implements FunctionListener.Before
func (u uniqGoFuncs) Before(ctx context.Context, _ api.Module, def api.FunctionDefinition, _ []uint64, _ experimental.StackIterator) {
	u[def.DebugName()] = struct{}{}
}

// After implements FunctionListener.After
func (u uniqGoFuncs) After(context.Context, api.Module, api.FunctionDefinition, []uint64) {}

// Abort implements FunctionListener.Abort
func (u uniqGoFuncs) Abort(context.Context, api.Module, api.FunctionDefinition, error) {}

func main() {
	u := uniqGoFuncs{}

	// Set context to one that has an experimental listener
	ctx := experimental.WithFunctionListenerFactory(context.Background(), u)

	r := wazero.NewRuntime(ctx)
	defer r.Close(ctx) // This closes everything this Runtime created.

	wasi_snapshot_preview1.MustInstantiate(ctx, r)

	mod, err := r.Instantiate(ctx, listenerWasm)
	if err != nil {
		log.Panicln(err)
	}

	for i := 0; i < 5; i++ {
		if _, err = mod.ExportedFunction("rand").Call(ctx, 4); err != nil {
			log.Panicln(err)
		}
	}

	// A Go function was called multiple times, but we should only see it once.
	for _, f := range u.callees() {
		fmt.Println(f)
	}

}
Output:

wasi_snapshot_preview1.fd_write
wasi_snapshot_preview1.random_get
Example (EnableSnapshotterKey)
package main

import (
	"context"
	_ "embed"
	"fmt"
	"log"

	"github.com/tetratelabs/wazero"
	"github.com/tetratelabs/wazero/api"
	"github.com/tetratelabs/wazero/experimental"
)

// snapshotWasm was generated by the following:
//
//	cd testdata; wat2wasm snapshot.wat
//
//go:embed testdata/snapshot.wasm
var snapshotWasm []byte

type snapshotsKey struct{}

func main() {
	ctx := context.Background()

	rt := wazero.NewRuntime(ctx)
	defer rt.Close(ctx) // This closes everything this Runtime created.

	// Enable experimental snapshotting functionality by setting it to context. We use this
	// context when invoking functions, indicating to wazero to enable it.
	ctx = experimental.WithSnapshotter(ctx)

	// Also place a mutable holder of snapshots to be referenced during restore.
	var snapshots []experimental.Snapshot
	ctx = context.WithValue(ctx, snapshotsKey{}, &snapshots)

	// Register host functions using snapshot and restore. Generally snapshot is saved
	// into a mutable location in context to be referenced during restore.
	_, err := rt.NewHostModuleBuilder("example").
		NewFunctionBuilder().
		WithFunc(func(ctx context.Context, mod api.Module, snapshotPtr uint32) int32 {
			// Because we enabled snapshots with WithSnapshotter, this is non-nil.
			snapshot := experimental.GetSnapshotter(ctx).Snapshot()

			// Get our mutable snapshots holder to be able to add to it. Our example only calls snapshot
			// and restore once but real programs will often call them at multiple layers within a call
			// stack with various e.g., try/catch statements.
			snapshots := ctx.Value(snapshotsKey{}).(*[]experimental.Snapshot)
			idx := len(*snapshots)
			*snapshots = append(*snapshots, snapshot)

			// Write a value to be passed back to restore. This is meant to be opaque to the guest
			// and used to re-reference the snapshot.
			ok := mod.Memory().WriteUint32Le(snapshotPtr, uint32(idx))
			if !ok {
				log.Panicln("failed to write snapshot index")
			}

			return 0
		}).
		Export("snapshot").
		NewFunctionBuilder().
		WithFunc(func(ctx context.Context, mod api.Module, snapshotPtr uint32) {
			// Read the value written by snapshot to re-reference the snapshot.
			idx, ok := mod.Memory().ReadUint32Le(snapshotPtr)
			if !ok {
				log.Panicln("failed to read snapshot index")
			}

			// Get the snapshot
			snapshots := ctx.Value(snapshotsKey{}).(*[]experimental.Snapshot)
			snapshot := (*snapshots)[idx]

			// Restore! The invocation of this function will end as soon as we invoke
			// Restore, so we also pass in our return value. The guest function run
			// will finish with this return value.
			snapshot.Restore([]uint64{5})
		}).
		Export("restore").
		Instantiate(ctx)
	if err != nil {
		log.Panicln(err)
	}

	mod, err := rt.Instantiate(ctx, snapshotWasm) // Instantiate the actual code
	if err != nil {
		log.Panicln(err)
	}

	// Call the guest entrypoint.
	res, err := mod.ExportedFunction("run").Call(ctx)
	if err != nil {
		log.Panicln(err)
	}
	// We restored and returned the restore value, so it's our result. If restore
	// was instead a no-op, we would have returned 10 from normal code flow.
	fmt.Println(res[0])
}
Output:

5
Example (ImportResolver)
package main

import (
	"bytes"
	"context"
	_ "embed"
	"fmt"
	"log"

	"github.com/tetratelabs/wazero"
	"github.com/tetratelabs/wazero/api"
	"github.com/tetratelabs/wazero/experimental"
	"github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
)

var (
	// These wasm files were generated by the following:
	// cd testdata
	// wat2wasm --debug-names inoutdispatcher.wat
	// wat2wasm --debug-names inoutdispatcherclient.wat

	//go:embed testdata/inoutdispatcher.wasm
	inoutdispatcherWasm []byte
	//go:embed testdata/inoutdispatcherclient.wasm
	inoutdispatcherclientWasm []byte
)

func main() {
	ctx := context.Background()

	r := wazero.NewRuntime(ctx)
	defer r.Close(ctx)

	// The client imports the inoutdispatcher module that reads from stdin and writes to stdout.
	// This means that we need multiple instances of the inoutdispatcher module to have different stdin/stdout.
	// This example demonstrates a way to do that.
	type mod struct {
		in  bytes.Buffer
		out bytes.Buffer

		client api.Module
	}

	wasi_snapshot_preview1.MustInstantiate(ctx, r)

	idm, err := r.CompileModule(ctx, inoutdispatcherWasm)
	if err != nil {
		log.Panicln(err)
	}
	idcm, err := r.CompileModule(ctx, inoutdispatcherclientWasm)
	if err != nil {
		log.Panicln(err)
	}

	const numInstances = 3
	mods := make([]*mod, numInstances)
	for i := range mods {
		mods[i] = &mod{}
		m := mods[i]

		const inoutDispatcherModuleName = "inoutdispatcher"

		dispatcherInstance, err := r.InstantiateModule(ctx, idm,
			wazero.NewModuleConfig().
				WithStdin(&m.in).
				WithStdout(&m.out).
				WithName("")) // Makes it an anonymous module.
		if err != nil {
			log.Panicln(err)
		}

		ctx = experimental.WithImportResolver(ctx, func(name string) api.Module {
			if name == inoutDispatcherModuleName {
				return dispatcherInstance
			}
			return nil
		})

		m.client, err = r.InstantiateModule(ctx, idcm, wazero.NewModuleConfig().WithName(fmt.Sprintf("m%d", i)))
		if err != nil {
			log.Panicln(err)
		}

	}

	for i, m := range mods {
		m.in.WriteString(fmt.Sprintf("Module instance #%d", i))
		_, err := m.client.ExportedFunction("dispatch").Call(ctx)
		if err != nil {
			log.Panicln(err)
		}
	}

	for i, m := range mods {
		fmt.Printf("out%d: %s\n", i, m.out.String())
	}

}
Output:

out0: Module instance #0
out1: Module instance #1
out2: Module instance #2
Example (StackIterator)
package main

import (
	"fmt"

	_ "embed"
	"github.com/tetratelabs/wazero/api"
	"github.com/tetratelabs/wazero/experimental"
	"github.com/tetratelabs/wazero/internal/wasm"
)

func main() {
	it := &fakeStackIterator{}

	for it.Next() {
		fn := it.Function()
		pc := it.ProgramCounter()
		fmt.Println("function:", fn.Definition().DebugName())
		fmt.Println("\tprogram counter:", pc)
		fmt.Println("\tsource offset:", fn.SourceOffsetForPC(pc))
	}

}

type fakeStackIterator struct {
	iteration    int
	def          api.FunctionDefinition
	args         []uint64
	pc           uint64
	sourceOffset uint64
}

func (s *fakeStackIterator) Next() bool {
	switch s.iteration {
	case 0:
		s.def = &mockFunctionDefinition{debugName: "fn0"}
		s.args = []uint64{1, 2, 3}
		s.pc = 5890831
		s.sourceOffset = 1234
	case 1:
		s.def = &mockFunctionDefinition{debugName: "fn1"}
		s.args = []uint64{}
		s.pc = 5899822
		s.sourceOffset = 7286
	case 2:
		s.def = &mockFunctionDefinition{debugName: "fn2"}
		s.args = []uint64{4}
		s.pc = 6820312
		s.sourceOffset = 935891
	case 3:
		return false
	}
	s.iteration++
	return true
}

func (s *fakeStackIterator) Function() experimental.InternalFunction {
	return internalFunction{
		definition:   s.def,
		sourceOffset: s.sourceOffset,
	}
}

func (s *fakeStackIterator) ProgramCounter() experimental.ProgramCounter {
	return experimental.ProgramCounter(s.pc)
}

type internalFunction struct {
	definition   api.FunctionDefinition
	sourceOffset uint64
}

func (f internalFunction) Definition() api.FunctionDefinition {
	return f.definition
}

func (f internalFunction) SourceOffsetForPC(pc experimental.ProgramCounter) uint64 {
	return f.sourceOffset
}

type mockFunctionDefinition struct {
	debugName string
	*wasm.FunctionDefinition
}

func (f *mockFunctionDefinition) DebugName() string {
	return f.debugName
}

func (f *mockFunctionDefinition) ParamTypes() []wasm.ValueType {
	return []wasm.ValueType{}
}

func (f *mockFunctionDefinition) ResultTypes() []wasm.ValueType {
	return []wasm.ValueType{}
}
Output:

function: fn0
	program counter: 5890831
	source offset: 1234
function: fn1
	program counter: 5899822
	source offset: 7286
function: fn2
	program counter: 6820312
	source offset: 935891

Index

Examples

Constants

View Source
const CoreFeaturesThreads = api.CoreFeatureSIMD << 1

CoreFeaturesThreads enables threads instructions ("threads").

Notes

  • The instruction list is too long to enumerate in godoc. See https://github.com/WebAssembly/threads/blob/main/proposals/threads/Overview.md
  • Atomic operations are guest-only until api.Memory or otherwise expose them to host functions.
  • On systems without mmap available, the memory will pre-allocate to the maximum size. Many binaries will use a theroetical maximum like 4GB, so if using such a binary on a system without mmap, consider editing the binary to reduce the max size setting of memory.

Variables

This section is empty.

Functions

func BenchmarkFunctionListener added in v1.2.0

func BenchmarkFunctionListener(n int, module api.Module, stack []StackFrame, listener FunctionListener)

BenchmarkFunctionListener implements a benchmark for function listeners.

The benchmark calls Before and After methods repeatedly using the provided module an stack frames to invoke the methods.

The stack frame is a representation of the call stack that the Before method will be invoked with. The top of the stack is stored at index zero. The stack must contain at least one frame or the benchmark will fail.

func WithCloseNotifier added in v1.3.0

func WithCloseNotifier(ctx context.Context, notifier CloseNotifier) context.Context

WithCloseNotifier registers the given CloseNotifier into the given context.Context.

func WithFunctionListenerFactory added in v1.7.1

func WithFunctionListenerFactory(ctx context.Context, factory FunctionListenerFactory) context.Context

WithFunctionListenerFactory registers a FunctionListenerFactory with the context.

func WithImportResolver added in v1.8.0

func WithImportResolver(ctx context.Context, resolver ImportResolver) context.Context

WithImportResolver returns a new context with the given ImportResolver.

func WithMemoryAllocator added in v1.7.1

func WithMemoryAllocator(ctx context.Context, allocator MemoryAllocator) context.Context

WithMemoryAllocator registers the given MemoryAllocator into the given context.Context.

func WithSnapshotter added in v1.7.1

func WithSnapshotter(ctx context.Context) context.Context

WithSnapshotter enables snapshots. Passing the returned context to a exported function invocation enables snapshots, and allows host functions to retrieve the Snapshotter using GetSnapshotter.

Types

type CloseNotifier added in v1.3.0

type CloseNotifier interface {
	// CloseNotify is a notification that occurs *before* an api.Module is
	// closed. `exitCode` is zero on success or in the case there was no exit
	// code.
	//
	// Notes:
	//   - This does not return an error because the module will be closed
	//     unconditionally.
	//   - Do not panic from this function as it doing so could cause resource
	//     leaks.
	//   - While this is only called once per module, if configured for
	//     multiple modules, it will be called for each, e.g. on runtime close.
	CloseNotify(ctx context.Context, exitCode uint32)
}

CloseNotifier is a notification hook, invoked when a module is closed.

Note: This is experimental progress towards #1197, and likely to change. Do not expose this in shared libraries as it can cause version locks.

type CloseNotifyFunc added in v1.3.0

type CloseNotifyFunc func(ctx context.Context, exitCode uint32)

CloseNotifyFunc is a convenience for defining inlining a CloseNotifier.

func (CloseNotifyFunc) CloseNotify added in v1.3.0

func (f CloseNotifyFunc) CloseNotify(ctx context.Context, exitCode uint32)

CloseNotify implements CloseNotifier.CloseNotify.

type FunctionListener

type FunctionListener interface {
	// Before is invoked before a function is called.
	//
	// There is always one corresponding call to After or Abort for each call to
	// Before. This guarantee allows the listener to maintain an internal stack
	// to perform correlations between the entry and exit of functions.
	//
	// # Params
	//
	//   - ctx: the context of the caller function which must be the same
	//	   instance or parent of the result.
	//   - mod: the calling module.
	//   - def: the function definition.
	//   - params:  api.ValueType encoded parameters.
	//   - stackIterator: iterator on the call stack. At least one entry is
	//     guaranteed (the called function), whose Args() will be equal to
	//     params. The iterator will be reused between calls to Before.
	//
	// Note: api.Memory is meant for inspection, not modification.
	// mod can be cast to InternalModule to read non-exported globals.
	Before(ctx context.Context, mod api.Module, def api.FunctionDefinition, params []uint64, stackIterator StackIterator)

	// After is invoked after a function is called.
	//
	// # Params
	//
	//   - ctx: the context of the caller function.
	//   - mod: the calling module.
	//   - def: the function definition.
	//   - results: api.ValueType encoded results.
	//
	// # Notes
	//
	//   - api.Memory is meant for inspection, not modification.
	//   - This is not called when a host function panics, or a guest function traps.
	//      See Abort for more details.
	After(ctx context.Context, mod api.Module, def api.FunctionDefinition, results []uint64)

	// Abort is invoked when a function does not return due to a trap or panic.
	//
	// # Params
	//
	//   - ctx: the context of the caller function.
	//   - mod: the calling module.
	//   - def: the function definition.
	//   - err: the error value representing the reason why the function aborted.
	//
	// # Notes
	//
	//   - api.Memory is meant for inspection, not modification.
	Abort(ctx context.Context, mod api.Module, def api.FunctionDefinition, err error)
}

FunctionListener can be registered for any function via FunctionListenerFactory to be notified when the function is called.

type FunctionListenerFactory

type FunctionListenerFactory interface {
	// NewFunctionListener returns a FunctionListener for a defined function.
	// If nil is returned, no listener will be notified.
	NewFunctionListener(api.FunctionDefinition) FunctionListener
}

FunctionListenerFactory returns FunctionListeners to be notified when a function is called.

func MultiFunctionListenerFactory added in v1.2.0

func MultiFunctionListenerFactory(factories ...FunctionListenerFactory) FunctionListenerFactory

MultiFunctionListenerFactory constructs a FunctionListenerFactory which combines the listeners created by each of the factories passed as arguments.

This function is useful when multiple listeners need to be hooked to a module because the propagation mechanism based on installing a listener factory in the context.Context used when instantiating modules allows for a single listener to be installed.

The stack iterator passed to the Before method is reset so that each listener can iterate the call stack independently without impacting the ability of other listeners to do so.

type FunctionListenerFactoryFunc added in v1.2.0

type FunctionListenerFactoryFunc func(api.FunctionDefinition) FunctionListener

FunctionListenerFactoryFunc is a function type implementing the FunctionListenerFactory interface, making it possible to use regular functions and methods as factory of function listeners.

func (FunctionListenerFactoryFunc) NewFunctionListener added in v1.2.0

NewFunctionListener satisfies the FunctionListenerFactory interface, calls f.

type FunctionListenerFunc added in v1.2.0

type FunctionListenerFunc func(context.Context, api.Module, api.FunctionDefinition, []uint64, StackIterator)

FunctionListenerFunc is a function type implementing the FunctionListener interface, making it possible to use regular functions and methods as listeners of function invocation.

The FunctionListener interface declares two methods (Before and After), but this type invokes its value only when Before is called. It is best suites for cases where the host does not need to perform correlation between the start and end of the function call.

func (FunctionListenerFunc) Abort added in v1.2.0

Abort is declared to satisfy the FunctionListener interface, but it does nothing.

func (FunctionListenerFunc) After added in v1.2.0

After is declared to satisfy the FunctionListener interface, but it does nothing.

func (FunctionListenerFunc) Before added in v1.2.0

func (f FunctionListenerFunc) Before(ctx context.Context, mod api.Module, def api.FunctionDefinition, params []uint64, stackIterator StackIterator)

Before satisfies the FunctionListener interface, calls f.

type ImportResolver added in v1.8.0

type ImportResolver func(name string) api.Module

ImportResolver is an experimental func type that, if set, will be used as the first step in resolving imports. See issue 2294. If the import name is not found, it should return nil.

type InternalFunction added in v1.2.0

type InternalFunction interface {
	// Definition provides introspection into the function's names and
	// signature.
	Definition() api.FunctionDefinition

	// SourceOffsetForPC resolves a program counter into its corresponding
	// offset in the Code section of the module this function belongs to.
	// The source offset is meant to help map the function calls to their
	// location in the original source files. Returns 0 if the offset cannot
	// be calculated.
	SourceOffsetForPC(pc ProgramCounter) uint64
}

InternalFunction exposes some information about a function instance.

type InternalModule added in v1.1.0

type InternalModule interface {
	api.Module

	// NumGlobal returns the count of all globals in the module.
	NumGlobal() int

	// Global provides a read-only view for a given global index.
	//
	// The methods panics if i is out of bounds.
	Global(i int) api.Global
}

InternalModule is an api.Module that exposes additional information.

type LinearMemory added in v1.7.1

type LinearMemory interface {
	// Reallocates the linear memory to size bytes in length.
	//
	// Notes:
	//   - To back a shared memory, Reallocate can't change the address of the
	//     backing []byte (only its length/capacity may change).
	Reallocate(size uint64) []byte
	// Free the backing memory buffer.
	Free()
}

LinearMemory is an expandable []byte that backs a Wasm linear memory.

type MemoryAllocator added in v1.7.1

type MemoryAllocator interface {
	// Allocate should create a new LinearMemory with the given specification:
	// cap is the suggested initial capacity for the backing []byte,
	// and max the maximum length that will ever be requested.
	//
	// Notes:
	//   - To back a shared memory, the address of the backing []byte cannot
	//     change. This is checked at runtime. Implementations should document
	//     if the returned LinearMemory meets this requirement.
	Allocate(cap, max uint64) LinearMemory
}

MemoryAllocator is a memory allocation hook, invoked to create a LinearMemory.

type MemoryAllocatorFunc added in v1.7.1

type MemoryAllocatorFunc func(cap, max uint64) LinearMemory

MemoryAllocatorFunc is a convenience for defining inlining a MemoryAllocator.

func (MemoryAllocatorFunc) Allocate added in v1.7.1

func (f MemoryAllocatorFunc) Allocate(cap, max uint64) LinearMemory

Allocate implements MemoryAllocator.Allocate.

type ProgramCounter added in v1.2.0

type ProgramCounter uint64

ProgramCounter is an opaque value representing a specific execution point in a module. It is meant to be used with Function.SourceOffsetForPC and StackIterator.

type Snapshot added in v1.7.0

type Snapshot interface {
	// Restore sets the Wasm execution state to the capture. Because a host function
	// calling this is resetting the pointer to the executation stack, the host function
	// will not be able to return values in the normal way. ret is a slice of values the
	// host function intends to return from the restored function.
	Restore(ret []uint64)
}

Snapshot holds the execution state at the time of a Snapshotter.Snapshot call.

type Snapshotter added in v1.7.0

type Snapshotter interface {
	// Snapshot captures the current execution state.
	Snapshot() Snapshot
}

Snapshotter allows host functions to snapshot the WebAssembly execution environment.

func GetSnapshotter added in v1.7.1

func GetSnapshotter(ctx context.Context) Snapshotter

GetSnapshotter gets the Snapshotter from a host function. It is only present if WithSnapshotter was called with the function invocation context.

type StackFrame added in v1.2.0

type StackFrame struct {
	Function     api.Function
	Params       []uint64
	Results      []uint64
	PC           uint64
	SourceOffset uint64
}

StackFrame represents a frame on the call stack.

type StackIterator added in v1.0.2

type StackIterator interface {
	// Next moves the iterator to the next function in the stack. Returns
	// false if it reached the bottom of the stack.
	Next() bool
	// Function describes the function called by the current frame.
	Function() InternalFunction
	// ProgramCounter returns the program counter associated with the
	// function call.
	ProgramCounter() ProgramCounter
}

StackIterator allows iterating on each function of the call stack, starting from the top. At least one call to Next() is required to start the iteration.

Note: The iterator provides a view of the call stack at the time of iteration. As a result, parameter values may be different than the ones their function was called with.

func NewStackIterator added in v1.2.0

func NewStackIterator(stack ...StackFrame) StackIterator

NewStackIterator constructs a stack iterator from a list of stack frames. The top most frame is the last one.

Directories

Path Synopsis
Package sysfs includes a low-level filesystem interface and utilities needed for WebAssembly host functions (ABI) such as WASI.
Package sysfs includes a low-level filesystem interface and utilities needed for WebAssembly host functions (ABI) such as WASI.

Jump to

Keyboard shortcuts

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