mirror of
https://github.com/wazero/wazero
synced 2026-06-21 14:12:37 +00:00
315 lines
11 KiB
Go
315 lines
11 KiB
Go
// Package wasi_snapshot_preview1 contains Go-defined functions to access
|
|
// system calls, such as opening a file, similar to Go's x/sys package. These
|
|
// are accessible from WebAssembly-defined functions via importing ModuleName.
|
|
// All WASI functions return a single Errno result: ErrnoSuccess on success.
|
|
//
|
|
// e.g. Call Instantiate before instantiating any wasm binary that imports
|
|
// "wasi_snapshot_preview1", Otherwise, it will error due to missing imports.
|
|
//
|
|
// ctx := context.Background()
|
|
// r := wazero.NewRuntime(ctx)
|
|
// defer r.Close(ctx) // This closes everything this Runtime created.
|
|
//
|
|
// wasi_snapshot_preview1.MustInstantiate(ctx, r)
|
|
// mod, _ := r.Instantiate(ctx, wasm)
|
|
//
|
|
// See https://github.com/WebAssembly/WASI
|
|
package wasi_snapshot_preview1
|
|
|
|
import (
|
|
"context"
|
|
"encoding/binary"
|
|
|
|
"github.com/tetratelabs/wazero"
|
|
"github.com/tetratelabs/wazero/api"
|
|
"github.com/tetratelabs/wazero/experimental/sys"
|
|
"github.com/tetratelabs/wazero/internal/wasip1"
|
|
"github.com/tetratelabs/wazero/internal/wasm"
|
|
)
|
|
|
|
// ModuleName is the module name WASI functions are exported into.
|
|
//
|
|
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md
|
|
const ModuleName = wasip1.InternalModuleName
|
|
|
|
const i32, i64 = wasm.ValueTypeI32, wasm.ValueTypeI64
|
|
|
|
var le = binary.LittleEndian
|
|
|
|
// MustInstantiate calls Instantiate or panics on error.
|
|
//
|
|
// This is a simpler function for those who know the module ModuleName is not
|
|
// already instantiated, and don't need to unload it.
|
|
func MustInstantiate(ctx context.Context, r wazero.Runtime) {
|
|
if _, err := Instantiate(ctx, r); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
// Instantiate instantiates the ModuleName module into the runtime.
|
|
//
|
|
// # Notes
|
|
//
|
|
// - Failure cases are documented on wazero.Runtime InstantiateModule.
|
|
// - Closing the wazero.Runtime has the same effect as closing the result.
|
|
func Instantiate(ctx context.Context, r wazero.Runtime) (api.Closer, error) {
|
|
return NewBuilder(r).Instantiate(ctx)
|
|
}
|
|
|
|
// Builder configures the ModuleName module for later use via Compile or Instantiate.
|
|
//
|
|
// # Notes
|
|
//
|
|
// - This is an interface for decoupling, not third-party implementations.
|
|
// All implementations are in wazero.
|
|
type Builder interface {
|
|
// Compile compiles the ModuleName module. Call this before Instantiate.
|
|
//
|
|
// Note: This has the same effect as the same function on wazero.HostModuleBuilder.
|
|
Compile(context.Context) (wazero.CompiledModule, error)
|
|
|
|
// Instantiate instantiates the ModuleName module and returns a function to close it.
|
|
//
|
|
// Note: This has the same effect as the same function on wazero.HostModuleBuilder.
|
|
Instantiate(context.Context) (api.Closer, error)
|
|
}
|
|
|
|
// NewBuilder returns a new Builder.
|
|
func NewBuilder(r wazero.Runtime) Builder {
|
|
return &builder{r}
|
|
}
|
|
|
|
type builder struct{ r wazero.Runtime }
|
|
|
|
// hostModuleBuilder returns a new wazero.HostModuleBuilder for ModuleName
|
|
func (b *builder) hostModuleBuilder() wazero.HostModuleBuilder {
|
|
ret := b.r.NewHostModuleBuilder(ModuleName)
|
|
exportFunctions(ret)
|
|
return ret
|
|
}
|
|
|
|
// Compile implements Builder.Compile
|
|
func (b *builder) Compile(ctx context.Context) (wazero.CompiledModule, error) {
|
|
return b.hostModuleBuilder().Compile(ctx)
|
|
}
|
|
|
|
// Instantiate implements Builder.Instantiate
|
|
func (b *builder) Instantiate(ctx context.Context) (api.Closer, error) {
|
|
return b.hostModuleBuilder().Instantiate(ctx)
|
|
}
|
|
|
|
// FunctionExporter exports functions into a wazero.HostModuleBuilder.
|
|
//
|
|
// # Notes
|
|
//
|
|
// - This is an interface for decoupling, not third-party implementations.
|
|
// All implementations are in wazero.
|
|
type FunctionExporter interface {
|
|
ExportFunctions(wazero.HostModuleBuilder)
|
|
}
|
|
|
|
// NewFunctionExporter returns a new FunctionExporter. This is used for the
|
|
// following two use cases:
|
|
// - Overriding a builtin function with an alternate implementation.
|
|
// - Exporting functions to the module "wasi_unstable" for legacy code.
|
|
//
|
|
// # Example of overriding default behavior
|
|
//
|
|
// // Export the default WASI functions.
|
|
// wasiBuilder := r.NewHostModuleBuilder(ModuleName)
|
|
// wasi_snapshot_preview1.NewFunctionExporter().ExportFunctions(wasiBuilder)
|
|
//
|
|
// // Subsequent calls to NewFunctionBuilder override built-in exports.
|
|
// wasiBuilder.NewFunctionBuilder().
|
|
// WithFunc(func(ctx context.Context, mod api.Module, exitCode uint32) {
|
|
// // your custom logic
|
|
// }).Export("proc_exit")
|
|
//
|
|
// # Example of using the old module name for WASI
|
|
//
|
|
// // Instantiate the current WASI functions under the wasi_unstable
|
|
// // instead of wasi_snapshot_preview1.
|
|
// wasiBuilder := r.NewHostModuleBuilder("wasi_unstable")
|
|
// wasi_snapshot_preview1.NewFunctionExporter().ExportFunctions(wasiBuilder)
|
|
// _, err := wasiBuilder.Instantiate(testCtx, r)
|
|
func NewFunctionExporter() FunctionExporter {
|
|
return &functionExporter{}
|
|
}
|
|
|
|
type functionExporter struct{}
|
|
|
|
// ExportFunctions implements FunctionExporter.ExportFunctions
|
|
func (functionExporter) ExportFunctions(builder wazero.HostModuleBuilder) {
|
|
exportFunctions(builder)
|
|
}
|
|
|
|
// ## Translation notes
|
|
// ### String
|
|
// WebAssembly 1.0 has no string type, so any string input parameter expands to two uint32 parameters: offset
|
|
// and length.
|
|
//
|
|
// ### iovec_array
|
|
// `iovec_array` is encoded as two uin32le values (i32): offset and count.
|
|
//
|
|
// ### Result
|
|
// Each result besides Errno is always an uint32 parameter. WebAssembly 1.0 can have up to one result,
|
|
// which is already used by Errno. This forces other results to be parameters. A result parameter is a memory
|
|
// offset to write the result to. As memory offsets are uint32, each parameter representing a result is uint32.
|
|
//
|
|
// ### Errno
|
|
// The WASI specification is sometimes ambiguous resulting in some runtimes interpreting the same function ways.
|
|
// Errno mappings are not defined in WASI, yet, so these mappings are best efforts by maintainers. When in doubt
|
|
// about portability, first look at /RATIONALE.md and if needed an issue on
|
|
// https://github.com/WebAssembly/WASI/issues
|
|
//
|
|
// ## Memory
|
|
// In WebAssembly 1.0 (20191205), there may be up to one Memory per store, which means api.Memory is always the
|
|
// wasm.Store Memories index zero: `store.Memories[0].Buffer`
|
|
//
|
|
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md
|
|
// See https://github.com/WebAssembly/WASI/issues/215
|
|
// See https://wwa.w3.org/TR/2019/REC-wasm-core-1-20191205/#memory-instances%E2%91%A0.
|
|
|
|
// exportFunctions adds all go functions that implement wasi.
|
|
// These should be exported in the module named ModuleName.
|
|
func exportFunctions(builder wazero.HostModuleBuilder) {
|
|
exporter := builder.(wasm.HostFuncExporter)
|
|
|
|
// Note: these are ordered per spec for consistency even if the resulting
|
|
// map can't guarantee that.
|
|
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#functions
|
|
exporter.ExportHostFunc(argsGet)
|
|
exporter.ExportHostFunc(argsSizesGet)
|
|
exporter.ExportHostFunc(environGet)
|
|
exporter.ExportHostFunc(environSizesGet)
|
|
exporter.ExportHostFunc(clockResGet)
|
|
exporter.ExportHostFunc(clockTimeGet)
|
|
exporter.ExportHostFunc(fdAdvise)
|
|
exporter.ExportHostFunc(fdAllocate)
|
|
exporter.ExportHostFunc(fdClose)
|
|
exporter.ExportHostFunc(fdDatasync)
|
|
exporter.ExportHostFunc(fdFdstatGet)
|
|
exporter.ExportHostFunc(fdFdstatSetFlags)
|
|
exporter.ExportHostFunc(fdFdstatSetRights)
|
|
exporter.ExportHostFunc(fdFilestatGet)
|
|
exporter.ExportHostFunc(fdFilestatSetSize)
|
|
exporter.ExportHostFunc(fdFilestatSetTimes)
|
|
exporter.ExportHostFunc(fdPread)
|
|
exporter.ExportHostFunc(fdPrestatGet)
|
|
exporter.ExportHostFunc(fdPrestatDirName)
|
|
exporter.ExportHostFunc(fdPwrite)
|
|
exporter.ExportHostFunc(fdRead)
|
|
exporter.ExportHostFunc(fdReaddir)
|
|
exporter.ExportHostFunc(fdRenumber)
|
|
exporter.ExportHostFunc(fdSeek)
|
|
exporter.ExportHostFunc(fdSync)
|
|
exporter.ExportHostFunc(fdTell)
|
|
exporter.ExportHostFunc(fdWrite)
|
|
exporter.ExportHostFunc(pathCreateDirectory)
|
|
exporter.ExportHostFunc(pathFilestatGet)
|
|
exporter.ExportHostFunc(pathFilestatSetTimes)
|
|
exporter.ExportHostFunc(pathLink)
|
|
exporter.ExportHostFunc(pathOpen)
|
|
exporter.ExportHostFunc(pathReadlink)
|
|
exporter.ExportHostFunc(pathRemoveDirectory)
|
|
exporter.ExportHostFunc(pathRename)
|
|
exporter.ExportHostFunc(pathSymlink)
|
|
exporter.ExportHostFunc(pathUnlinkFile)
|
|
exporter.ExportHostFunc(pollOneoff)
|
|
exporter.ExportHostFunc(procExit)
|
|
exporter.ExportHostFunc(procRaise)
|
|
exporter.ExportHostFunc(schedYield)
|
|
exporter.ExportHostFunc(randomGet)
|
|
exporter.ExportHostFunc(sockAccept)
|
|
exporter.ExportHostFunc(sockRecv)
|
|
exporter.ExportHostFunc(sockSend)
|
|
exporter.ExportHostFunc(sockShutdown)
|
|
}
|
|
|
|
// writeOffsetsAndNullTerminatedValues is used to write NUL-terminated values
|
|
// for args or environ, given a pre-defined bytesLen (which includes NUL
|
|
// terminators).
|
|
func writeOffsetsAndNullTerminatedValues(mem api.Memory, values [][]byte, offsets, bytes, bytesLen uint32) sys.Errno {
|
|
// The caller may not place bytes directly after offsets, so we have to
|
|
// read them independently.
|
|
valuesLen := len(values)
|
|
offsetsLen := uint32(valuesLen * 4) // uint32Le
|
|
offsetsBuf, ok := mem.Read(offsets, offsetsLen)
|
|
if !ok {
|
|
return sys.EFAULT
|
|
}
|
|
bytesBuf, ok := mem.Read(bytes, bytesLen)
|
|
if !ok {
|
|
return sys.EFAULT
|
|
}
|
|
|
|
// Loop through the values, first writing the location of its data to
|
|
// offsetsBuf[oI], then its NUL-terminated data at bytesBuf[bI]
|
|
var oI, bI uint32
|
|
for _, value := range values {
|
|
// Go can't guarantee inlining as there's not //go:inline directive.
|
|
// This inlines uint32 little-endian encoding instead.
|
|
bytesOffset := bytes + bI
|
|
offsetsBuf[oI] = byte(bytesOffset)
|
|
offsetsBuf[oI+1] = byte(bytesOffset >> 8)
|
|
offsetsBuf[oI+2] = byte(bytesOffset >> 16)
|
|
offsetsBuf[oI+3] = byte(bytesOffset >> 24)
|
|
oI += 4 // size of uint32 we just wrote
|
|
|
|
// Write the next value to memory with a NUL terminator
|
|
copy(bytesBuf[bI:], value)
|
|
bI += uint32(len(value))
|
|
bytesBuf[bI] = 0 // NUL terminator
|
|
bI++
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
func newHostFunc(
|
|
name string,
|
|
goFunc wasiFunc,
|
|
paramTypes []wasm.ValueType,
|
|
paramNames ...string,
|
|
) *wasm.HostFunc {
|
|
return &wasm.HostFunc{
|
|
ExportName: name,
|
|
Name: name,
|
|
ParamTypes: paramTypes,
|
|
ParamNames: paramNames,
|
|
ResultTypes: []wasm.ValueType{i32},
|
|
ResultNames: []string{"errno"},
|
|
Code: wasm.Code{GoFunc: goFunc},
|
|
}
|
|
}
|
|
|
|
// wasiFunc special cases that all WASI functions return a single Errno
|
|
// result. The returned value will be written back to the stack at index zero.
|
|
type wasiFunc func(ctx context.Context, mod api.Module, params []uint64) sys.Errno
|
|
|
|
// Call implements the same method as documented on api.GoModuleFunction.
|
|
func (f wasiFunc) Call(ctx context.Context, mod api.Module, stack []uint64) {
|
|
// Write the result back onto the stack
|
|
errno := f(ctx, mod, stack)
|
|
if errno != 0 {
|
|
stack[0] = uint64(wasip1.ToErrno(errno))
|
|
} else { // special case ass ErrnoSuccess is zero
|
|
stack[0] = 0
|
|
}
|
|
}
|
|
|
|
// stubFunction stubs for GrainLang per #271.
|
|
func stubFunction(name string, paramTypes []wasm.ValueType, paramNames ...string) *wasm.HostFunc {
|
|
return &wasm.HostFunc{
|
|
ExportName: name,
|
|
Name: name,
|
|
ParamTypes: paramTypes,
|
|
ParamNames: paramNames,
|
|
ResultTypes: []wasm.ValueType{i32},
|
|
ResultNames: []string{"errno"},
|
|
Code: wasm.Code{
|
|
GoFunc: api.GoModuleFunc(func(_ context.Context, _ api.Module, stack []uint64) { stack[0] = uint64(wasip1.ErrnoNosys) }),
|
|
},
|
|
}
|
|
}
|