wazero package - github.com/tetratelabs/wazero - Go Packages (original) (raw)
This is an example of how to extend a Go application with an addition function defined in WebAssembly.
Since addWasm was compiled with TinyGo's `wasi` target, we need to configure WASI host imports.
A complete project that does the same as this is available here:https://github.com/tetratelabs/wazero/tree/main/examples/basic
package main
import ( "context" _ "embed" "fmt" "log"
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1")
// addWasm was generated by the following: // // cd examples/basic/testdata; tinygo build -o add.wasm -target=wasi add.go // //go:embed examples/basic/testdata/add.wasm var addWasm []byte
// This is an example of how to extend a Go application with an addition
// function defined in WebAssembly.
//
// Since addWasm was compiled with TinyGo's wasi target, we need to configure
// WASI host imports.
//
// A complete project that does the same as this is available here:
// https://github.com/tetratelabs/wazero/tree/main/examples/basic
func main() {
// Choose the context to use for function calls.
ctx := context.Background()
// Create a new WebAssembly Runtime.
r := wazero.NewRuntime(ctx)
defer r.Close(ctx) // This closes everything this Runtime created.
// Instantiate WASI, which implements host functions needed for TinyGo to
// implement `panic`.
wasi_snapshot_preview1.MustInstantiate(ctx, r)
// Instantiate the guest Wasm into the same runtime. It exports the `add`
// function, implemented in WebAssembly.
mod, err := r.InstantiateWithConfig(ctx, addWasm, wazero.NewModuleConfig().WithStartFunctions("_initialize"))
if err != nil {
log.Panicln(err)
}
// Call the `add` function and print the results to the console.
x, y := uint64(1), uint64(2)
results, err := mod.ExportedFunction("add").Call(ctx, x, y)
if err != nil {
log.Panicln(err)
}
fmt.Printf("%d + %d = %d\n", x, y, results[0])}
Output:
1 + 2 = 3
This is a basic example of using the file system compilation cache via wazero.NewCompilationCacheWithDir. The main goal is to show how it is configured.
package main
import ( "context" _ "embed" "log" "os"
"github.com/tetratelabs/wazero")
// This is a basic example of using the file system compilation cache via wazero.NewCompilationCacheWithDir. // The main goal is to show how it is configured. func main() { // Prepare a cache directory. cacheDir, err := os.MkdirTemp("", "example") if err != nil { log.Panicln(err) } defer os.RemoveAll(cacheDir)
ctx := context.Background()
// Create a runtime config which shares a compilation cache directory.
cache := newCompilationCacheWithDir(cacheDir)
defer cache.Close(ctx)
config := wazero.NewRuntimeConfig().WithCompilationCache(cache)
// Using the same wazero.CompilationCache instance allows the in-memory cache sharing.
newRuntimeCompileClose(ctx, config)
newRuntimeCompileClose(ctx, config)
// Since the above stored compiled functions to disk as well, below won't compile from scratch.
// Instead, compilation result stored in the directory is re-used.
newRuntimeCompileClose(ctx, config.WithCompilationCache(newCompilationCacheWithDir(cacheDir)))
newRuntimeCompileClose(ctx, config.WithCompilationCache(newCompilationCacheWithDir(cacheDir)))}
func newCompilationCacheWithDir(cacheDir string) wazero.CompilationCache { cache, err := wazero.NewCompilationCacheWithDir(cacheDir) if err != nil { log.Panicln(err) } return cache }
// newRuntimeCompileClose creates a new wazero.Runtime, compile a binary, and then delete the runtime. func newRuntimeCompileClose(ctx context.Context, config wazero.RuntimeConfig) { r := wazero.NewRuntimeWithConfig(ctx, config) defer r.Close(ctx) // This closes everything this Runtime created except the file system cache.
_, err := r.CompileModule(ctx, addWasm)
if err != nil {
log.Panicln(err)
}}
This example shows how to configure an embed.FS.
package main
import ( "embed" "io/fs" "log"
"github.com/tetratelabs/wazero")
//go:embed testdata/index.html var testdataIndex embed.FS
var moduleConfig wazero.ModuleConfig
// This example shows how to configure an embed.FS. func main() { // Strip the embedded path testdata/ rooted, err := fs.Sub(testdataIndex, "testdata") if err != nil { log.Panicln(err) }
moduleConfig = wazero.NewModuleConfig().
// Make "index.html" accessible to the guest as "/index.html".
WithFSConfig(wazero.NewFSConfig().WithFSMount(rooted, "/"))}
This is a basic example of retrieving custom sections using RuntimeConfig.WithCustomSections.
package main
import ( "context" _ "embed" "log"
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/api")
// This is a basic example of retrieving custom sections using RuntimeConfig.WithCustomSections. func main() { ctx := context.Background() config := wazero.NewRuntimeConfig().WithCustomSections(true)
r := wazero.NewRuntimeWithConfig(ctx, config)
defer r.Close(ctx)
m, err := r.CompileModule(ctx, addWasm)
if err != nil {
log.Panicln(err)
}
if m.CustomSections() == nil {
log.Panicln("Custom sections should not be nil")
}
mustContain(m.CustomSections(), "producers")
mustContain(m.CustomSections(), "target_features")}
func mustContain(ss []api.CustomSection, name string) { for _, s := range ss { if s.Name() == name { return } } log.Panicf("Could not find section named %s\n", name) }
This section is empty.
This section is empty.
This section is empty.
type CompilationCache interface{ api.Closer }
CompilationCache reduces time spent compiling (Runtime.CompileModule) the same wasm module.
Notes ¶
- This is an interface for decoupling, not third-party implementations. All implementations are in wazero.
- Instances of this can be reused across multiple runtimes, if configured via RuntimeConfig.
- The cache check happens before the compilation, so if multiple Goroutines are trying to compile the same module simultaneously, it is possible that they all compile the module. The design here is that the lock isn't held for the action "Compile" but only for checking and saving the compiled result. Therefore, we strongly recommend that the embedder does the centralized compilation in a single Goroutines (or multiple Goroutines per Wasm binary) to generate cache rather than trying to Compile in parallel for a single module. In other words, we always recommend to produce CompiledModule share it across multiple Goroutines to avoid trying to compile the same module simultaneously.
func NewCompilationCache() CompilationCache
NewCompilationCache returns a new CompilationCache to be passed to RuntimeConfig. This configures only in-memory cache, and doesn't persist to the file system. See wazero.NewCompilationCacheWithDir for detail.
The returned CompilationCache can be used to share the in-memory compilation results across multiple instances of wazero.Runtime.
NewCompilationCacheWithDir is like wazero.NewCompilationCache except the result also writes state into the directory specified by `dirname` parameter.
If the dirname doesn't exist, this creates it or returns an error.
Those running wazero as a CLI or frequently restarting a process using the same wasm should use this feature to reduce time waiting to compile the same module a second time.
The contents written into dirname are wazero-version specific, meaning different versions of wazero will duplicate entries for the same input wasm.
Note: The embedder must safeguard this directory from external changes.
CompiledModule is a WebAssembly module ready to be instantiated (Runtime.InstantiateModule) as an api.Module.
In WebAssembly terminology, this is a decoded, validated, and possibly also compiled module. wazero avoids using the name "Module" for both before and after instantiation as the name conflation has caused confusion. See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#semantic-phases%E2%91%A0
Notes ¶
- This is an interface for decoupling, not third-party implementations. All implementations are in wazero.
- Closing the wazero.Runtime closes any CompiledModule it compiled.
type FSConfig interface {
WithDirMount(dir, guestPath [string](/builtin#string)) [FSConfig](#FSConfig)
WithReadOnlyDirMount(dir, guestPath [string](/builtin#string)) [FSConfig](#FSConfig)
WithFSMount(fs [fs](/io/fs).[FS](/io/fs#FS), guestPath [string](/builtin#string)) [FSConfig](#FSConfig)}
FSConfig configures filesystem paths the embedding host allows the wasm guest to access. Unconfigured paths are not allowed, so functions like `path_open` result in unsupported errors (e.g. syscall.ENOSYS).
Guest Path ¶
`guestPath` is the name of the path the guest should use a filesystem for, or empty for any files.
All `guestPath` paths are normalized, specifically removing any leading or trailing slashes. This means "/", "./" or "." all coerce to empty "".
Multiple `guestPath` values can be configured, but the last longest match wins. For example, if "tmp", then "" were added, a request to open "tmp/foo.txt" use the filesystem associated with "tmp" even though a wider path, "" (all files), was added later.
A `guestPath` of "." coerces to the empty string "" because the current directory is handled by the guest. In other words, the guest resolves ites current directory prior to requesting files.
More notes on `guestPath`
- Working directories are typically tracked in wasm, though possible some relative paths are requested. For example, TinyGo may attempt to resolve a path "../.." in unit tests.
- Zig uses the first path name it sees as the initial working directory of the process.
Scope ¶
Configuration here is module instance scoped. This means you can use the same configuration for multiple calls to Runtime.InstantiateModule. Each module will have a different file descriptor table. Any errors accessing resources allowed here are deferred to instantiation time of each module.
Any host resources present at the time of configuration, but deleted before Runtime.InstantiateModule will trap/panic when the guest wasm initializes or calls functions like `fd_read`.
Windows ¶
While wazero supports Windows as a platform, all known compilers use POSIX conventions at runtime. For example, even when running on Windows, paths used by wasm are separated by forward slash (/), not backslash (\).
Notes ¶
- This is an interface for decoupling, not third-party implementations. All implementations are in wazero.
- FSConfig is immutable. Each WithXXX function returns a new instance including the corresponding change.
- RATIONALE.md includes design background and relationship to WebAssembly System Interfaces (WASI).
func NewFSConfig() FSConfig
NewFSConfig returns a FSConfig that can be used for configuring module instantiation.
HostFunctionBuilder defines a host function (in Go), so that a WebAssembly binary (e.g. %.wasm file) can import and use it.
Here's an example of an addition function:
hostModuleBuilder.NewFunctionBuilder(). WithFunc(func(cxt context.Context, x, y uint32) uint32 { return x + y }). Export("add")
Memory ¶
All host functions act on the importing api.Module, including any memory exported in its binary (%.wasm file). If you are reading or writing memory, it is sand-boxed Wasm memory defined by the guest.
Below, `m` is the importing module, defined in Wasm. `fn` is a host function added via Export. This means that `x` was read from memory defined in Wasm, not arbitrary memory in the process.
fn := func(ctx context.Context, m api.Module, offset uint32) uint32 { x, _ := m.Memory().ReadUint32Le(ctx, offset) return x }
Notes ¶
- This is an interface for decoupling, not third-party implementations. All implementations are in wazero.
HostModuleBuilder is a way to define host functions (in Go), so that a WebAssembly binary (e.g. %.wasm file) can import and use them.
Specifically, this implements the host side of an Application Binary Interface (ABI) like WASI or AssemblyScript.
For example, this defines and instantiates a module named "env" with one function:
ctx := context.Background() r := wazero.NewRuntime(ctx) defer r.Close(ctx) // This closes everything this Runtime created.
hello := func() { println("hello!") } env, _ := r.NewHostModuleBuilder("env"). NewFunctionBuilder().WithFunc(hello).Export("hello"). Instantiate(ctx)
If the same module may be instantiated multiple times, it is more efficient to separate steps. Here's an example:
compiled, _ := r.NewHostModuleBuilder("env"). NewFunctionBuilder().WithFunc(getRandomString).Export("get_random_string"). Compile(ctx)
env1, _ := r.InstantiateModule(ctx, compiled, wazero.NewModuleConfig().WithName("env.1")) env2, _ := r.InstantiateModule(ctx, compiled, wazero.NewModuleConfig().WithName("env.2"))
See HostFunctionBuilder for valid host function signatures and other details.
Notes ¶
- This is an interface for decoupling, not third-party implementations. All implementations are in wazero.
- HostModuleBuilder is mutable: each method returns the same instance for chaining.
- methods do not return errors, to allow chaining. Any validation errors are deferred until Compile.
- Functions are indexed in order of calls to NewFunctionBuilder as insertion ordering is needed by ABI such as Emscripten (invoke_*).
- The semantics of host functions assumes the existence of an "importing module" because, for example, the host function needs access to the memory of the importing module. Therefore, direct use of ExportedFunction is forbidden for host modules. Practically speaking, it is usually meaningless to directly call a host function from Go code as it is already somewhere in Go code.
ModuleConfig configures resources needed by functions that have low-level interactions with the host operating system. Using this, resources such as STDIN can be isolated, so that the same module can be safely instantiated multiple times.
Here's an example:
// Initialize base configuration: config := wazero.NewModuleConfig().WithStdout(buf).WithSysNanotime()
// Assign different configuration on each instantiation mod, _ := r.InstantiateModule(ctx, compiled, config.WithName("rotate").WithArgs("rotate", "angle=90", "dir=cw"))
While wazero supports Windows as a platform, host functions using ModuleConfig follow a UNIX dialect. See RATIONALE.md for design background and relationship to WebAssembly System Interfaces (WASI).
Notes ¶
- This is an interface for decoupling, not third-party implementations. All implementations are in wazero.
- ModuleConfig is immutable. Each WithXXX function returns a new instance including the corresponding change.
func NewModuleConfig() ModuleConfig
NewModuleConfig returns a ModuleConfig that can be used for configuring module instantiation.
type Runtime interface {
Instantiate(ctx [context](/context).[Context](/context#Context), source [][byte](/builtin#byte)) ([api](/github.com/tetratelabs/wazero@v1.10.1/api).[Module](/github.com/tetratelabs/wazero@v1.10.1/api#Module), [error](/builtin#error))
InstantiateWithConfig(ctx [context](/context).[Context](/context#Context), source [][byte](/builtin#byte), config [ModuleConfig](#ModuleConfig)) ([api](/github.com/tetratelabs/wazero@v1.10.1/api).[Module](/github.com/tetratelabs/wazero@v1.10.1/api#Module), [error](/builtin#error))
NewHostModuleBuilder(moduleName [string](/builtin#string)) [HostModuleBuilder](#HostModuleBuilder)
CompileModule(ctx [context](/context).[Context](/context#Context), binary [][byte](/builtin#byte)) ([CompiledModule](#CompiledModule), [error](/builtin#error))
InstantiateModule(ctx [context](/context).[Context](/context#Context), compiled [CompiledModule](#CompiledModule), config [ModuleConfig](#ModuleConfig)) ([api](/github.com/tetratelabs/wazero@v1.10.1/api).[Module](/github.com/tetratelabs/wazero@v1.10.1/api#Module), [error](/builtin#error))
CloseWithExitCode(ctx [context](/context).[Context](/context#Context), exitCode [uint32](/builtin#uint32)) [error](/builtin#error)
Module(moduleName [string](/builtin#string)) [api](/github.com/tetratelabs/wazero@v1.10.1/api).[Module](/github.com/tetratelabs/wazero@v1.10.1/api#Module)
[api](/github.com/tetratelabs/wazero@v1.10.1/api).[Closer](/github.com/tetratelabs/wazero@v1.10.1/api#Closer)}
Runtime allows embedding of WebAssembly modules.
The below is an example of basic initialization:
ctx := context.Background() r := wazero.NewRuntime(ctx) defer r.Close(ctx) // This closes everything this Runtime created.
mod, _ := r.Instantiate(ctx, wasm)
Notes ¶
- This is an interface for decoupling, not third-party implementations. All implementations are in wazero.
- Closing this closes any CompiledModule or Module it instantiated.
NewRuntime returns a runtime with a configuration assigned by NewRuntimeConfig.
NewRuntimeWithConfig returns a runtime with the given configuration.
type RuntimeConfig interface {
WithCoreFeatures([api](/github.com/tetratelabs/wazero@v1.10.1/api).[CoreFeatures](/github.com/tetratelabs/wazero@v1.10.1/api#CoreFeatures)) [RuntimeConfig](#RuntimeConfig)
WithMemoryLimitPages(memoryLimitPages [uint32](/builtin#uint32)) [RuntimeConfig](#RuntimeConfig)
WithMemoryCapacityFromMax(memoryCapacityFromMax [bool](/builtin#bool)) [RuntimeConfig](#RuntimeConfig)
WithDebugInfoEnabled([bool](/builtin#bool)) [RuntimeConfig](#RuntimeConfig)
WithCompilationCache([CompilationCache](#CompilationCache)) [RuntimeConfig](#RuntimeConfig)
WithCustomSections([bool](/builtin#bool)) [RuntimeConfig](#RuntimeConfig)
WithCloseOnContextDone([bool](/builtin#bool)) [RuntimeConfig](#RuntimeConfig)}
RuntimeConfig controls runtime behavior, with the default implementation as NewRuntimeConfig
The example below explicitly limits to Wasm Core 1.0 features as opposed to relying on defaults:
rConfig = wazero.NewRuntimeConfig().WithCoreFeatures(api.CoreFeaturesV1)
Notes ¶
- This is an interface for decoupling, not third-party implementations. All implementations are in wazero.
- RuntimeConfig is immutable. Each WithXXX function returns a new instance including the corresponding change.
func NewRuntimeConfig() RuntimeConfig
NewRuntimeConfig returns a RuntimeConfig using the compiler if it is supported in this environment, or the interpreter otherwise.
func NewRuntimeConfigCompiler() RuntimeConfig
NewRuntimeConfigCompiler compiles WebAssembly modules into runtime.GOARCH-specific assembly for optimal performance.
The default implementation is AOT (Ahead of Time) compilation, applied at Runtime.CompileModule. This allows consistent runtime performance, as well the ability to reduce any first request penalty.
Note: While this is technically AOT, this does not imply any action on your part. wazero automatically performs ahead-of-time compilation as needed when Runtime.CompileModule is invoked.
Warning ¶
- This panics at runtime if the runtime.GOOS or runtime.GOARCH does not support compiler. Use NewRuntimeConfig to safely detect and fallback to NewRuntimeConfigInterpreter if needed.
- If you are using wazero in buildmode=c-archive or c-shared, make sure that you set up the alternate signal stack by using, e.g. `sigaltstack` combined with `SA_ONSTACK` flag on `sigaction` on Linux, before calling any api.Function. This is because the Go runtime does not set up the alternate signal stack for c-archive or c-shared modes, and wazero uses the different stack than the calling Goroutine. Hence, the signal handler might get invoked on the wazero's stack, which may cause a stack overflow.https://github.com/tetratelabs/wazero/blob/2092c0a879f30d49d7b37f333f4547574b8afe0d/internal/integration_test/fuzz/fuzz/tests/sigstack.rs#L19-L36
func NewRuntimeConfigInterpreter() RuntimeConfig
NewRuntimeConfigInterpreter interprets WebAssembly modules instead of compiling them into assembly.