[feature] support processing of (many) more media types (#3090)

* initial work replacing our media decoding / encoding pipeline with ffprobe + ffmpeg

* specify the video codec to use when generating static image from emoji

* update go-storage library (fixes incompatibility after updating go-iotools)

* maintain image aspect ratio when generating a thumbnail for it

* update readme to show go-ffmpreg

* fix a bunch of media tests, move filesize checking to callers of media manager for more flexibility

* remove extra debug from error message

* fix up incorrect function signatures

* update PutFile to just use regular file copy, as changes are file is on separate partition

* fix remaining tests, remove some unneeded tests now we're working with ffmpeg/ffprobe

* update more tests, add more code comments

* add utilities to generate processed emoji / media outputs

* fix remaining tests

* add test for opus media file, add license header to utility cmds

* limit the number of concurrently available ffmpeg / ffprobe instances

* reduce number of instances

* further reduce number of instances

* fix envparsing test with configuration variables

* update docs and configuration with new media-{local,remote}-max-size variables
This commit is contained in:
kim
2024-07-12 09:39:47 +00:00
committed by GitHub
parent 5bc567196b
commit cde2fb6244
376 changed files with 8026 additions and 54091 deletions

View File

@ -0,0 +1,97 @@
package wasi_snapshot_preview1
import (
"context"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/experimental/sys"
"github.com/tetratelabs/wazero/internal/wasip1"
"github.com/tetratelabs/wazero/internal/wasm"
)
// argsGet is the WASI function named ArgsGetName that reads command-line
// argument data.
//
// # Parameters
//
// - argv: offset to begin writing argument offsets in uint32 little-endian
// encoding to api.Memory
// - argsSizesGet result argc * 4 bytes are written to this offset
// - argvBuf: offset to write the null terminated arguments to api.Memory
// - argsSizesGet result argv_len bytes are written to this offset
//
// Result (Errno)
//
// The return value is ErrnoSuccess except the following error conditions:
// - sys.EFAULT: there is not enough memory to write results
//
// For example, if argsSizesGet wrote argc=2 and argvLen=5 for arguments:
// "a" and "bc" parameters argv=7 and argvBuf=1, this function writes the below
// to api.Memory:
//
// argvLen uint32le uint32le
// +----------------+ +--------+ +--------+
// | | | | | |
// []byte{?, 'a', 0, 'b', 'c', 0, ?, 1, 0, 0, 0, 3, 0, 0, 0, ?}
// argvBuf --^ ^ ^
// argv --| |
// offset that begins "a" --+ |
// offset that begins "bc" --+
//
// See argsSizesGet
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#args_get
// See https://en.wikipedia.org/wiki/Null-terminated_string
var argsGet = newHostFunc(wasip1.ArgsGetName, argsGetFn, []api.ValueType{i32, i32}, "argv", "argv_buf")
func argsGetFn(_ context.Context, mod api.Module, params []uint64) sys.Errno {
sysCtx := mod.(*wasm.ModuleInstance).Sys
argv, argvBuf := uint32(params[0]), uint32(params[1])
return writeOffsetsAndNullTerminatedValues(mod.Memory(), sysCtx.Args(), argv, argvBuf, sysCtx.ArgsSize())
}
// argsSizesGet is the WASI function named ArgsSizesGetName that reads
// command-line argument sizes.
//
// # Parameters
//
// - resultArgc: offset to write the argument count to api.Memory
// - resultArgvLen: offset to write the null-terminated argument length to
// api.Memory
//
// Result (Errno)
//
// The return value is ErrnoSuccess except the following error conditions:
// - sys.EFAULT: there is not enough memory to write results
//
// For example, if args are "a", "bc" and parameters resultArgc=1 and
// resultArgvLen=6, this function writes the below to api.Memory:
//
// uint32le uint32le
// +--------+ +--------+
// | | | |
// []byte{?, 2, 0, 0, 0, ?, 5, 0, 0, 0, ?}
// resultArgc --^ ^
// 2 args --+ |
// resultArgvLen --|
// len([]byte{'a',0,'b',c',0}) --+
//
// See argsGet
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#args_sizes_get
// See https://en.wikipedia.org/wiki/Null-terminated_string
var argsSizesGet = newHostFunc(wasip1.ArgsSizesGetName, argsSizesGetFn, []api.ValueType{i32, i32}, "result.argc", "result.argv_len")
func argsSizesGetFn(_ context.Context, mod api.Module, params []uint64) sys.Errno {
sysCtx := mod.(*wasm.ModuleInstance).Sys
mem := mod.Memory()
resultArgc, resultArgvLen := uint32(params[0]), uint32(params[1])
// argc and argv_len offsets are not necessarily sequential, so we have to
// write them independently.
if !mem.WriteUint32Le(resultArgc, uint32(len(sysCtx.Args()))) {
return sys.EFAULT
}
if !mem.WriteUint32Le(resultArgvLen, sysCtx.ArgsSize()) {
return sys.EFAULT
}
return 0
}

View File

@ -0,0 +1,116 @@
package wasi_snapshot_preview1
import (
"context"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/experimental/sys"
"github.com/tetratelabs/wazero/internal/wasip1"
"github.com/tetratelabs/wazero/internal/wasm"
)
// clockResGet is the WASI function named ClockResGetName that returns the
// resolution of time values returned by clockTimeGet.
//
// # Parameters
//
// - id: clock ID to use
// - resultResolution: offset to write the resolution to api.Memory
// - the resolution is an uint64 little-endian encoding
//
// Result (Errno)
//
// The return value is 0 except the following error conditions:
// - sys.ENOTSUP: the clock ID is not supported.
// - sys.EINVAL: the clock ID is invalid.
// - sys.EFAULT: there is not enough memory to write results
//
// For example, if the resolution is 100ns, this function writes the below to
// api.Memory:
//
// uint64le
// +-------------------------------------+
// | |
// []byte{?, 0x64, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, ?}
// resultResolution --^
//
// Note: This is similar to `clock_getres` in POSIX.
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-clock_res_getid-clockid---errno-timestamp
// See https://linux.die.net/man/3/clock_getres
var clockResGet = newHostFunc(wasip1.ClockResGetName, clockResGetFn, []api.ValueType{i32, i32}, "id", "result.resolution")
func clockResGetFn(_ context.Context, mod api.Module, params []uint64) sys.Errno {
sysCtx := mod.(*wasm.ModuleInstance).Sys
id, resultResolution := uint32(params[0]), uint32(params[1])
var resolution uint64 // ns
switch id {
case wasip1.ClockIDRealtime:
resolution = uint64(sysCtx.WalltimeResolution())
case wasip1.ClockIDMonotonic:
resolution = uint64(sysCtx.NanotimeResolution())
default:
return sys.EINVAL
}
if !mod.Memory().WriteUint64Le(resultResolution, resolution) {
return sys.EFAULT
}
return 0
}
// clockTimeGet is the WASI function named ClockTimeGetName that returns
// the time value of a name (time.Now).
//
// # Parameters
//
// - id: clock ID to use
// - precision: maximum lag (exclusive) that the returned time value may have,
// compared to its actual value
// - resultTimestamp: offset to write the timestamp to api.Memory
// - the timestamp is epoch nanos encoded as a little-endian uint64
//
// Result (Errno)
//
// The return value is 0 except the following error conditions:
// - sys.ENOTSUP: the clock ID is not supported.
// - sys.EINVAL: the clock ID is invalid.
// - sys.EFAULT: there is not enough memory to write results
//
// For example, if time.Now returned exactly midnight UTC 2022-01-01
// (1640995200000000000), and parameters resultTimestamp=1, this function
// writes the below to api.Memory:
//
// uint64le
// +------------------------------------------+
// | |
// []byte{?, 0x0, 0x0, 0x1f, 0xa6, 0x70, 0xfc, 0xc5, 0x16, ?}
// resultTimestamp --^
//
// Note: This is similar to `clock_gettime` in POSIX.
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-clock_time_getid-clockid-precision-timestamp---errno-timestamp
// See https://linux.die.net/man/3/clock_gettime
var clockTimeGet = newHostFunc(wasip1.ClockTimeGetName, clockTimeGetFn, []api.ValueType{i32, i64, i32}, "id", "precision", "result.timestamp")
func clockTimeGetFn(_ context.Context, mod api.Module, params []uint64) sys.Errno {
sysCtx := mod.(*wasm.ModuleInstance).Sys
id := uint32(params[0])
// TODO: precision is currently ignored.
// precision = params[1]
resultTimestamp := uint32(params[2])
var val int64
switch id {
case wasip1.ClockIDRealtime:
val = sysCtx.WalltimeNanos()
case wasip1.ClockIDMonotonic:
val = sysCtx.Nanotime()
default:
return sys.EINVAL
}
if !mod.Memory().WriteUint64Le(resultTimestamp, uint64(val)) {
return sys.EFAULT
}
return 0
}

View File

@ -0,0 +1,100 @@
package wasi_snapshot_preview1
import (
"context"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/experimental/sys"
"github.com/tetratelabs/wazero/internal/wasip1"
"github.com/tetratelabs/wazero/internal/wasm"
)
// environGet is the WASI function named EnvironGetName that reads
// environment variables.
//
// # Parameters
//
// - environ: offset to begin writing environment offsets in uint32
// little-endian encoding to api.Memory
// - environSizesGet result environc * 4 bytes are written to this offset
// - environBuf: offset to write the null-terminated variables to api.Memory
// - the format is like os.Environ: null-terminated "key=val" entries
// - environSizesGet result environLen bytes are written to this offset
//
// Result (Errno)
//
// The return value is 0 except the following error conditions:
// - sys.EFAULT: there is not enough memory to write results
//
// For example, if environSizesGet wrote environc=2 and environLen=9 for
// environment variables: "a=b", "b=cd" and parameters environ=11 and
// environBuf=1, this function writes the below to api.Memory:
//
// environLen uint32le uint32le
// +------------------------------------+ +--------+ +--------+
// | | | | | |
// []byte{?, 'a', '=', 'b', 0, 'b', '=', 'c', 'd', 0, ?, 1, 0, 0, 0, 5, 0, 0, 0, ?}
// environBuf --^ ^ ^
// environ offset for "a=b" --+ |
// environ offset for "b=cd" --+
//
// See environSizesGet
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#environ_get
// See https://en.wikipedia.org/wiki/Null-terminated_string
var environGet = newHostFunc(wasip1.EnvironGetName, environGetFn, []api.ValueType{i32, i32}, "environ", "environ_buf")
func environGetFn(_ context.Context, mod api.Module, params []uint64) sys.Errno {
sysCtx := mod.(*wasm.ModuleInstance).Sys
environ, environBuf := uint32(params[0]), uint32(params[1])
return writeOffsetsAndNullTerminatedValues(mod.Memory(), sysCtx.Environ(), environ, environBuf, sysCtx.EnvironSize())
}
// environSizesGet is the WASI function named EnvironSizesGetName that
// reads environment variable sizes.
//
// # Parameters
//
// - resultEnvironc: offset to write the count of environment variables to
// api.Memory
// - resultEnvironvLen: offset to write the null-terminated environment
// variable length to api.Memory
//
// Result (Errno)
//
// The return value is 0 except the following error conditions:
// - sys.EFAULT: there is not enough memory to write results
//
// For example, if environ are "a=b","b=cd" and parameters resultEnvironc=1 and
// resultEnvironvLen=6, this function writes the below to api.Memory:
//
// uint32le uint32le
// +--------+ +--------+
// | | | |
// []byte{?, 2, 0, 0, 0, ?, 9, 0, 0, 0, ?}
// resultEnvironc --^ ^
// 2 variables --+ |
// resultEnvironvLen --|
// len([]byte{'a','=','b',0, |
// 'b','=','c','d',0}) --+
//
// See environGet
// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#environ_sizes_get
// and https://en.wikipedia.org/wiki/Null-terminated_string
var environSizesGet = newHostFunc(wasip1.EnvironSizesGetName, environSizesGetFn, []api.ValueType{i32, i32}, "result.environc", "result.environv_len")
func environSizesGetFn(_ context.Context, mod api.Module, params []uint64) sys.Errno {
sysCtx := mod.(*wasm.ModuleInstance).Sys
mem := mod.Memory()
resultEnvironc, resultEnvironvLen := uint32(params[0]), uint32(params[1])
// environc and environv_len offsets are not necessarily sequential, so we
// have to write them independently.
if !mem.WriteUint32Le(resultEnvironc, uint32(len(sysCtx.Environ()))) {
return sys.EFAULT
}
if !mem.WriteUint32Le(resultEnvironvLen, sysCtx.EnvironSize()) {
return sys.EFAULT
}
return 0
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,239 @@
package wasi_snapshot_preview1
import (
"context"
"time"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/experimental/sys"
"github.com/tetratelabs/wazero/internal/fsapi"
internalsys "github.com/tetratelabs/wazero/internal/sys"
"github.com/tetratelabs/wazero/internal/wasip1"
"github.com/tetratelabs/wazero/internal/wasm"
)
// pollOneoff is the WASI function named PollOneoffName that concurrently
// polls for the occurrence of a set of events.
//
// # Parameters
//
// - in: pointer to the subscriptions (48 bytes each)
// - out: pointer to the resulting events (32 bytes each)
// - nsubscriptions: count of subscriptions, zero returns sys.EINVAL.
// - resultNevents: count of events.
//
// Result (Errno)
//
// The return value is 0 except the following error conditions:
// - sys.EINVAL: the parameters are invalid
// - sys.ENOTSUP: a parameters is valid, but not yet supported.
// - sys.EFAULT: there is not enough memory to read the subscriptions or
// write results.
//
// # Notes
//
// - Since the `out` pointer nests Errno, the result is always 0.
// - This is similar to `poll` in POSIX.
//
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#poll_oneoff
// See https://linux.die.net/man/3/poll
var pollOneoff = newHostFunc(
wasip1.PollOneoffName, pollOneoffFn,
[]api.ValueType{i32, i32, i32, i32},
"in", "out", "nsubscriptions", "result.nevents",
)
type event struct {
eventType byte
userData []byte
errno wasip1.Errno
}
func pollOneoffFn(_ context.Context, mod api.Module, params []uint64) sys.Errno {
in := uint32(params[0])
out := uint32(params[1])
nsubscriptions := uint32(params[2])
resultNevents := uint32(params[3])
if nsubscriptions == 0 {
return sys.EINVAL
}
mem := mod.Memory()
// Ensure capacity prior to the read loop to reduce error handling.
inBuf, ok := mem.Read(in, nsubscriptions*48)
if !ok {
return sys.EFAULT
}
outBuf, ok := mem.Read(out, nsubscriptions*32)
// zero-out all buffer before writing
for i := range outBuf {
outBuf[i] = 0
}
if !ok {
return sys.EFAULT
}
// Eagerly write the number of events which will equal subscriptions unless
// there's a fault in parsing (not processing).
if !mod.Memory().WriteUint32Le(resultNevents, nsubscriptions) {
return sys.EFAULT
}
// Loop through all subscriptions and write their output.
// Extract FS context, used in the body of the for loop for FS access.
fsc := mod.(*wasm.ModuleInstance).Sys.FS()
// Slice of events that are processed out of the loop (blocking stdin subscribers).
var blockingStdinSubs []*event
// The timeout is initialized at max Duration, the loop will find the minimum.
var timeout time.Duration = 1<<63 - 1
// Count of all the subscriptions that have been already written back to outBuf.
// nevents*32 returns at all times the offset where the next event should be written:
// this way we ensure that there are no gaps between records.
nevents := uint32(0)
// Layout is subscription_u: Union
// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#subscription_u
for i := uint32(0); i < nsubscriptions; i++ {
inOffset := i * 48
outOffset := nevents * 32
eventType := inBuf[inOffset+8] // +8 past userdata
// +8 past userdata +8 contents_offset
argBuf := inBuf[inOffset+8+8:]
userData := inBuf[inOffset : inOffset+8]
evt := &event{
eventType: eventType,
userData: userData,
errno: wasip1.ErrnoSuccess,
}
switch eventType {
case wasip1.EventTypeClock: // handle later
newTimeout, err := processClockEvent(argBuf)
if err != 0 {
return err
}
// Min timeout.
if newTimeout < timeout {
timeout = newTimeout
}
// Ack the clock event to the outBuf.
writeEvent(outBuf[outOffset:], evt)
nevents++
case wasip1.EventTypeFdRead:
fd := int32(le.Uint32(argBuf))
if fd < 0 {
return sys.EBADF
}
if file, ok := fsc.LookupFile(fd); !ok {
evt.errno = wasip1.ErrnoBadf
writeEvent(outBuf[outOffset:], evt)
nevents++
} else if fd != internalsys.FdStdin && file.File.IsNonblock() {
writeEvent(outBuf[outOffset:], evt)
nevents++
} else {
// if the fd is Stdin, and it is in blocking mode,
// do not ack yet, append to a slice for delayed evaluation.
blockingStdinSubs = append(blockingStdinSubs, evt)
}
case wasip1.EventTypeFdWrite:
fd := int32(le.Uint32(argBuf))
if fd < 0 {
return sys.EBADF
}
if _, ok := fsc.LookupFile(fd); ok {
evt.errno = wasip1.ErrnoNotsup
} else {
evt.errno = wasip1.ErrnoBadf
}
nevents++
writeEvent(outBuf[outOffset:], evt)
default:
return sys.EINVAL
}
}
sysCtx := mod.(*wasm.ModuleInstance).Sys
if nevents == nsubscriptions {
// We already wrote back all the results. We already wrote this number
// earlier to offset `resultNevents`.
// We only need to observe the timeout (nonzero if there are clock subscriptions)
// and return.
if timeout > 0 {
sysCtx.Nanosleep(int64(timeout))
}
return 0
}
// If there are blocking stdin subscribers, check for data with given timeout.
stdin, ok := fsc.LookupFile(internalsys.FdStdin)
if !ok {
return sys.EBADF
}
// Wait for the timeout to expire, or for some data to become available on Stdin.
if stdinReady, errno := stdin.File.Poll(fsapi.POLLIN, int32(timeout.Milliseconds())); errno != 0 {
return errno
} else if stdinReady {
// stdin has data ready to for reading, write back all the events
for i := range blockingStdinSubs {
evt := blockingStdinSubs[i]
evt.errno = 0
writeEvent(outBuf[nevents*32:], evt)
nevents++
}
}
if nevents != nsubscriptions {
if !mod.Memory().WriteUint32Le(resultNevents, nevents) {
return sys.EFAULT
}
}
return 0
}
// processClockEvent supports only relative name events, as that's what's used
// to implement sleep in various compilers including Rust, Zig and TinyGo.
func processClockEvent(inBuf []byte) (time.Duration, sys.Errno) {
_ /* ID */ = le.Uint32(inBuf[0:8]) // See below
timeout := le.Uint64(inBuf[8:16]) // nanos if relative
_ /* precision */ = le.Uint64(inBuf[16:24]) // Unused
flags := le.Uint16(inBuf[24:32])
var err sys.Errno
// subclockflags has only one flag defined: subscription_clock_abstime
switch flags {
case 0: // relative time
case 1: // subscription_clock_abstime
err = sys.ENOTSUP
default: // subclockflags has only one flag defined.
err = sys.EINVAL
}
if err != 0 {
return 0, err
} else {
// https://linux.die.net/man/3/clock_settime says relative timers are
// unaffected. Since this function only supports relative timeout, we can
// skip name ID validation and use a single sleep function.
return time.Duration(timeout), 0
}
}
// writeEvent writes the event corresponding to the processed subscription.
// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-event-struct
func writeEvent(outBuf []byte, evt *event) {
copy(outBuf, evt.userData) // userdata
outBuf[8] = byte(evt.errno) // uint16, but safe as < 255
outBuf[9] = 0
le.PutUint32(outBuf[10:], uint32(evt.eventType))
// TODO: When FD events are supported, write outOffset+16
}

View File

@ -0,0 +1,44 @@
package wasi_snapshot_preview1
import (
"context"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/wasip1"
"github.com/tetratelabs/wazero/internal/wasm"
"github.com/tetratelabs/wazero/sys"
)
// procExit is the WASI function named ProcExitName that terminates the
// execution of the module with an exit code. The only successful exit code is
// zero.
//
// # Parameters
//
// - exitCode: exit code.
//
// See https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#proc_exit
var procExit = &wasm.HostFunc{
ExportName: wasip1.ProcExitName,
Name: wasip1.ProcExitName,
ParamTypes: []api.ValueType{i32},
ParamNames: []string{"rval"},
Code: wasm.Code{GoFunc: api.GoModuleFunc(procExitFn)},
}
func procExitFn(ctx context.Context, mod api.Module, params []uint64) {
exitCode := uint32(params[0])
// Ensure other callers see the exit code.
_ = mod.CloseWithExitCode(ctx, exitCode)
// Prevent any code from executing after this function. For example, LLVM
// inserts unreachable instructions after calls to exit.
// See: https://github.com/emscripten-core/emscripten/issues/12322
panic(sys.NewExitError(exitCode))
}
// procRaise is stubbed and will never be supported, as it was removed.
//
// See https://github.com/WebAssembly/WASI/pull/136
var procRaise = stubFunction(wasip1.ProcRaiseName, []api.ValueType{i32}, "sig")

View File

@ -0,0 +1,55 @@
package wasi_snapshot_preview1
import (
"context"
"io"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/experimental/sys"
"github.com/tetratelabs/wazero/internal/wasip1"
"github.com/tetratelabs/wazero/internal/wasm"
)
// randomGet is the WASI function named RandomGetName which writes random
// data to a buffer.
//
// # Parameters
//
// - buf: api.Memory offset to write random values
// - bufLen: size of random data in bytes
//
// Result (Errno)
//
// The return value is ErrnoSuccess except the following error conditions:
// - sys.EFAULT: `buf` or `bufLen` point to an offset out of memory
// - sys.EIO: a file system error
//
// For example, if underlying random source was seeded like
// `rand.NewSource(42)`, we expect api.Memory to contain:
//
// bufLen (5)
// +--------------------------+
// | |
// []byte{?, 0x53, 0x8c, 0x7f, 0x96, 0xb1, ?}
// buf --^
//
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-random_getbuf-pointeru8-bufLen-size---errno
var randomGet = newHostFunc(wasip1.RandomGetName, randomGetFn, []api.ValueType{i32, i32}, "buf", "buf_len")
func randomGetFn(_ context.Context, mod api.Module, params []uint64) sys.Errno {
sysCtx := mod.(*wasm.ModuleInstance).Sys
randSource := sysCtx.RandSource()
buf, bufLen := uint32(params[0]), uint32(params[1])
randomBytes, ok := mod.Memory().Read(buf, bufLen)
if !ok { // out-of-range
return sys.EFAULT
}
// We can ignore the returned n as it only != byteCount on error
if _, err := io.ReadAtLeast(randSource, randomBytes, int(bufLen)); err != nil {
return sys.EIO
}
return 0
}

View File

@ -0,0 +1,22 @@
package wasi_snapshot_preview1
import (
"context"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/experimental/sys"
"github.com/tetratelabs/wazero/internal/wasip1"
"github.com/tetratelabs/wazero/internal/wasm"
)
// schedYield is the WASI function named SchedYieldName which temporarily
// yields execution of the calling thread.
//
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-sched_yield---errno
var schedYield = newHostFunc(wasip1.SchedYieldName, schedYieldFn, nil)
func schedYieldFn(_ context.Context, mod api.Module, _ []uint64) sys.Errno {
sysCtx := mod.(*wasm.ModuleInstance).Sys
sysCtx.Osyield()
return 0
}

View File

@ -0,0 +1,188 @@
package wasi_snapshot_preview1
import (
"context"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/experimental/sys"
socketapi "github.com/tetratelabs/wazero/internal/sock"
"github.com/tetratelabs/wazero/internal/sysfs"
"github.com/tetratelabs/wazero/internal/wasip1"
"github.com/tetratelabs/wazero/internal/wasm"
)
// sockAccept is the WASI function named SockAcceptName which accepts a new
// incoming connection.
//
// See: https://github.com/WebAssembly/WASI/blob/0ba0c5e2e37625ca5a6d3e4255a998dfaa3efc52/phases/snapshot/docs.md#sock_accept
// and https://github.com/WebAssembly/WASI/pull/458
var sockAccept = newHostFunc(
wasip1.SockAcceptName,
sockAcceptFn,
[]wasm.ValueType{i32, i32, i32},
"fd", "flags", "result.fd",
)
func sockAcceptFn(_ context.Context, mod api.Module, params []uint64) (errno sys.Errno) {
mem := mod.Memory()
fsc := mod.(*wasm.ModuleInstance).Sys.FS()
fd := int32(params[0])
flags := uint32(params[1])
resultFd := uint32(params[2])
nonblock := flags&uint32(wasip1.FD_NONBLOCK) != 0
var connFD int32
if connFD, errno = fsc.SockAccept(fd, nonblock); errno == 0 {
mem.WriteUint32Le(resultFd, uint32(connFD))
}
return
}
// sockRecv is the WASI function named SockRecvName which receives a
// message from a socket.
//
// See: https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-sock_recvfd-fd-ri_data-iovec_array-ri_flags-riflags---errno-size-roflags
var sockRecv = newHostFunc(
wasip1.SockRecvName,
sockRecvFn,
[]wasm.ValueType{i32, i32, i32, i32, i32, i32},
"fd", "ri_data", "ri_data_len", "ri_flags", "result.ro_datalen", "result.ro_flags",
)
func sockRecvFn(_ context.Context, mod api.Module, params []uint64) sys.Errno {
mem := mod.Memory()
fsc := mod.(*wasm.ModuleInstance).Sys.FS()
fd := int32(params[0])
riData := uint32(params[1])
riDataCount := uint32(params[2])
riFlags := uint8(params[3])
resultRoDatalen := uint32(params[4])
resultRoFlags := uint32(params[5])
var conn socketapi.TCPConn
if e, ok := fsc.LookupFile(fd); !ok {
return sys.EBADF // Not open
} else if conn, ok = e.File.(socketapi.TCPConn); !ok {
return sys.EBADF // Not a conn
}
if riFlags & ^(wasip1.RI_RECV_PEEK|wasip1.RI_RECV_WAITALL) != 0 {
return sys.ENOTSUP
}
if riFlags&wasip1.RI_RECV_PEEK != 0 {
// Each record in riData is of the form:
// type iovec struct { buf *uint8; bufLen uint32 }
// This means that the first `uint32` is a `buf *uint8`.
firstIovecBufAddr, ok := mem.ReadUint32Le(riData)
if !ok {
return sys.EINVAL
}
// Read bufLen
firstIovecBufLen, ok := mem.ReadUint32Le(riData + 4)
if !ok {
return sys.EINVAL
}
firstIovecBuf, ok := mem.Read(firstIovecBufAddr, firstIovecBufLen)
if !ok {
return sys.EINVAL
}
n, err := conn.Recvfrom(firstIovecBuf, sysfs.MSG_PEEK)
if err != 0 {
return err
}
mem.WriteUint32Le(resultRoDatalen, uint32(n))
mem.WriteUint16Le(resultRoFlags, 0)
return 0
}
// If riFlags&wasip1.RECV_WAITALL != 0 then we should
// do a blocking operation until all data has been retrieved;
// otherwise we are able to return earlier.
// For simplicity, we currently wait all regardless the flag.
bufSize, errno := readv(mem, riData, riDataCount, conn.Read)
if errno != 0 {
return errno
}
mem.WriteUint32Le(resultRoDatalen, bufSize)
mem.WriteUint16Le(resultRoFlags, 0)
return 0
}
// sockSend is the WASI function named SockSendName which sends a message
// on a socket.
//
// See: https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-sock_sendfd-fd-si_data-ciovec_array-si_flags-siflags---errno-size
var sockSend = newHostFunc(
wasip1.SockSendName,
sockSendFn,
[]wasm.ValueType{i32, i32, i32, i32, i32},
"fd", "si_data", "si_data_len", "si_flags", "result.so_datalen",
)
func sockSendFn(_ context.Context, mod api.Module, params []uint64) sys.Errno {
mem := mod.Memory()
fsc := mod.(*wasm.ModuleInstance).Sys.FS()
fd := int32(params[0])
siData := uint32(params[1])
siDataCount := uint32(params[2])
siFlags := uint32(params[3])
resultSoDatalen := uint32(params[4])
if siFlags != 0 {
return sys.ENOTSUP
}
var conn socketapi.TCPConn
if e, ok := fsc.LookupFile(fd); !ok {
return sys.EBADF // Not open
} else if conn, ok = e.File.(socketapi.TCPConn); !ok {
return sys.EBADF // Not a conn
}
bufSize, errno := writev(mem, siData, siDataCount, conn.Write)
if errno != 0 {
return errno
}
mem.WriteUint32Le(resultSoDatalen, bufSize)
return 0
}
// sockShutdown is the WASI function named SockShutdownName which shuts
// down socket send and receive channels.
//
// See: https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-sock_shutdownfd-fd-how-sdflags---errno
var sockShutdown = newHostFunc(wasip1.SockShutdownName, sockShutdownFn, []wasm.ValueType{i32, i32}, "fd", "how")
func sockShutdownFn(_ context.Context, mod api.Module, params []uint64) sys.Errno {
fsc := mod.(*wasm.ModuleInstance).Sys.FS()
fd := int32(params[0])
how := uint8(params[1])
var conn socketapi.TCPConn
if e, ok := fsc.LookupFile(fd); !ok {
return sys.EBADF // Not open
} else if conn, ok = e.File.(socketapi.TCPConn); !ok {
return sys.EBADF // Not a conn
}
sysHow := 0
switch how {
case wasip1.SD_RD | wasip1.SD_WR:
sysHow = socketapi.SHUT_RD | socketapi.SHUT_WR
case wasip1.SD_RD:
sysHow = socketapi.SHUT_RD
case wasip1.SD_WR:
sysHow = socketapi.SHUT_WR
default:
return sys.EINVAL
}
// TODO: Map this instead of relying on syscall symbols.
return conn.Shutdown(sysHow)
}

View File

@ -0,0 +1,314 @@
// 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 []api.ValueType,
paramNames ...string,
) *wasm.HostFunc {
return &wasm.HostFunc{
ExportName: name,
Name: name,
ParamTypes: paramTypes,
ParamNames: paramNames,
ResultTypes: []api.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: []api.ValueType{i32},
ResultNames: []string{"errno"},
Code: wasm.Code{
GoFunc: api.GoModuleFunc(func(_ context.Context, _ api.Module, stack []uint64) { stack[0] = uint64(wasip1.ErrnoNosys) }),
},
}
}

View File

@ -0,0 +1,6 @@
package wasip1
const (
ArgsGetName = "args_get"
ArgsSizesGetName = "args_sizes_get"
)

View File

@ -0,0 +1,16 @@
package wasip1
const (
ClockResGetName = "clock_res_get"
ClockTimeGetName = "clock_time_get"
)
// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-clockid-enumu32
const (
// ClockIDRealtime is the name ID named "realtime" like sys.Walltime
ClockIDRealtime = iota
// ClockIDMonotonic is the name ID named "monotonic" like sys.Nanotime
ClockIDMonotonic
// Note: clockIDProcessCputime and clockIDThreadCputime were removed by
// WASI maintainers: https://github.com/WebAssembly/wasi-libc/pull/294
)

View File

@ -0,0 +1,6 @@
package wasip1
const (
EnvironGetName = "environ_get"
EnvironSizesGetName = "environ_sizes_get"
)

View File

@ -0,0 +1,314 @@
package wasip1
import (
"fmt"
"github.com/tetratelabs/wazero/experimental/sys"
)
// Errno is neither uint16 nor an alias for parity with wasm.ValueType.
type Errno = uint32
// ErrnoName returns the POSIX error code name, except ErrnoSuccess, which is
// not an error. e.g. Errno2big -> "E2BIG"
func ErrnoName(errno uint32) string {
if int(errno) < len(errnoToString) {
return errnoToString[errno]
}
return fmt.Sprintf("errno(%d)", errno)
}
// Note: Below prefers POSIX symbol names over WASI ones, even if the docs are from WASI.
// See https://linux.die.net/man/3/errno
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#variants-1
const (
// ErrnoSuccess No error occurred. System call completed successfully.
ErrnoSuccess Errno = iota
// Errno2big Argument list too long.
Errno2big
// ErrnoAcces Permission denied.
ErrnoAcces
// ErrnoAddrinuse Address in use.
ErrnoAddrinuse
// ErrnoAddrnotavail Address not available.
ErrnoAddrnotavail
// ErrnoAfnosupport Address family not supported.
ErrnoAfnosupport
// ErrnoAgain Resource unavailable, or operation would block.
ErrnoAgain
// ErrnoAlready Connection already in progress.
ErrnoAlready
// ErrnoBadf Bad file descriptor.
ErrnoBadf
// ErrnoBadmsg Bad message.
ErrnoBadmsg
// ErrnoBusy Device or resource busy.
ErrnoBusy
// ErrnoCanceled Operation canceled.
ErrnoCanceled
// ErrnoChild No child processes.
ErrnoChild
// ErrnoConnaborted Connection aborted.
ErrnoConnaborted
// ErrnoConnrefused Connection refused.
ErrnoConnrefused
// ErrnoConnreset Connection reset.
ErrnoConnreset
// ErrnoDeadlk Resource deadlock would occur.
ErrnoDeadlk
// ErrnoDestaddrreq Destination address required.
ErrnoDestaddrreq
// ErrnoDom Mathematics argument out of domain of function.
ErrnoDom
// ErrnoDquot Reserved.
ErrnoDquot
// ErrnoExist File exists.
ErrnoExist
// ErrnoFault Bad address.
ErrnoFault
// ErrnoFbig File too large.
ErrnoFbig
// ErrnoHostunreach Host is unreachable.
ErrnoHostunreach
// ErrnoIdrm Identifier removed.
ErrnoIdrm
// ErrnoIlseq Illegal byte sequence.
ErrnoIlseq
// ErrnoInprogress Operation in progress.
ErrnoInprogress
// ErrnoIntr Interrupted function.
ErrnoIntr
// ErrnoInval Invalid argument.
ErrnoInval
// ErrnoIo I/O error.
ErrnoIo
// ErrnoIsconn Socket is connected.
ErrnoIsconn
// ErrnoIsdir Is a directory.
ErrnoIsdir
// ErrnoLoop Too many levels of symbolic links.
ErrnoLoop
// ErrnoMfile File descriptor value too large.
ErrnoMfile
// ErrnoMlink Too many links.
ErrnoMlink
// ErrnoMsgsize Message too large.
ErrnoMsgsize
// ErrnoMultihop Reserved.
ErrnoMultihop
// ErrnoNametoolong Filename too long.
ErrnoNametoolong
// ErrnoNetdown Network is down.
ErrnoNetdown
// ErrnoNetreset Connection aborted by network.
ErrnoNetreset
// ErrnoNetunreach Network unreachable.
ErrnoNetunreach
// ErrnoNfile Too many files open in system.
ErrnoNfile
// ErrnoNobufs No buffer space available.
ErrnoNobufs
// ErrnoNodev No such device.
ErrnoNodev
// ErrnoNoent No such file or directory.
ErrnoNoent
// ErrnoNoexec Executable file format error.
ErrnoNoexec
// ErrnoNolck No locks available.
ErrnoNolck
// ErrnoNolink Reserved.
ErrnoNolink
// ErrnoNomem Not enough space.
ErrnoNomem
// ErrnoNomsg No message of the desired type.
ErrnoNomsg
// ErrnoNoprotoopt No message of the desired type.
ErrnoNoprotoopt
// ErrnoNospc No space left on device.
ErrnoNospc
// ErrnoNosys function not supported.
ErrnoNosys
// ErrnoNotconn The socket is not connected.
ErrnoNotconn
// ErrnoNotdir Not a directory or a symbolic link to a directory.
ErrnoNotdir
// ErrnoNotempty Directory not empty.
ErrnoNotempty
// ErrnoNotrecoverable State not recoverable.
ErrnoNotrecoverable
// ErrnoNotsock Not a socket.
ErrnoNotsock
// ErrnoNotsup Not supported, or operation not supported on socket.
ErrnoNotsup
// ErrnoNotty Inappropriate I/O control operation.
ErrnoNotty
// ErrnoNxio No such device or address.
ErrnoNxio
// ErrnoOverflow Value too large to be stored in data type.
ErrnoOverflow
// ErrnoOwnerdead Previous owner died.
ErrnoOwnerdead
// ErrnoPerm Operation not permitted.
ErrnoPerm
// ErrnoPipe Broken pipe.
ErrnoPipe
// ErrnoProto Protocol error.
ErrnoProto
// ErrnoProtonosupport Protocol error.
ErrnoProtonosupport
// ErrnoPrototype Protocol wrong type for socket.
ErrnoPrototype
// ErrnoRange Result too large.
ErrnoRange
// ErrnoRofs Read-only file system.
ErrnoRofs
// ErrnoSpipe Invalid seek.
ErrnoSpipe
// ErrnoSrch No such process.
ErrnoSrch
// ErrnoStale Reserved.
ErrnoStale
// ErrnoTimedout Connection timed out.
ErrnoTimedout
// ErrnoTxtbsy Text file busy.
ErrnoTxtbsy
// ErrnoXdev Cross-device link.
ErrnoXdev
// Note: ErrnoNotcapable was removed by WASI maintainers.
// See https://github.com/WebAssembly/wasi-libc/pull/294
)
var errnoToString = [...]string{
"ESUCCESS",
"E2BIG",
"EACCES",
"EADDRINUSE",
"EADDRNOTAVAIL",
"EAFNOSUPPORT",
"EAGAIN",
"EALREADY",
"EBADF",
"EBADMSG",
"EBUSY",
"ECANCELED",
"ECHILD",
"ECONNABORTED",
"ECONNREFUSED",
"ECONNRESET",
"EDEADLK",
"EDESTADDRREQ",
"EDOM",
"EDQUOT",
"EEXIST",
"EFAULT",
"EFBIG",
"EHOSTUNREACH",
"EIDRM",
"EILSEQ",
"EINPROGRESS",
"EINTR",
"EINVAL",
"EIO",
"EISCONN",
"EISDIR",
"ELOOP",
"EMFILE",
"EMLINK",
"EMSGSIZE",
"EMULTIHOP",
"ENAMETOOLONG",
"ENETDOWN",
"ENETRESET",
"ENETUNREACH",
"ENFILE",
"ENOBUFS",
"ENODEV",
"ENOENT",
"ENOEXEC",
"ENOLCK",
"ENOLINK",
"ENOMEM",
"ENOMSG",
"ENOPROTOOPT",
"ENOSPC",
"ENOSYS",
"ENOTCONN",
"ENOTDIR",
"ENOTEMPTY",
"ENOTRECOVERABLE",
"ENOTSOCK",
"ENOTSUP",
"ENOTTY",
"ENXIO",
"EOVERFLOW",
"EOWNERDEAD",
"EPERM",
"EPIPE",
"EPROTO",
"EPROTONOSUPPORT",
"EPROTOTYPE",
"ERANGE",
"EROFS",
"ESPIPE",
"ESRCH",
"ESTALE",
"ETIMEDOUT",
"ETXTBSY",
"EXDEV",
"ENOTCAPABLE",
}
// ToErrno coerces the error to a WASI Errno.
//
// Note: Coercion isn't centralized in sys.FSContext because ABI use different
// error codes. For example, wasi-filesystem doesn't map to these
// Errno.
func ToErrno(errno sys.Errno) Errno {
switch errno {
case 0:
return ErrnoSuccess
case sys.EACCES:
return ErrnoAcces
case sys.EAGAIN:
return ErrnoAgain
case sys.EBADF:
return ErrnoBadf
case sys.EEXIST:
return ErrnoExist
case sys.EFAULT:
return ErrnoFault
case sys.EINTR:
return ErrnoIntr
case sys.EINVAL:
return ErrnoInval
case sys.EIO:
return ErrnoIo
case sys.EISDIR:
return ErrnoIsdir
case sys.ELOOP:
return ErrnoLoop
case sys.ENAMETOOLONG:
return ErrnoNametoolong
case sys.ENOENT:
return ErrnoNoent
case sys.ENOSYS:
return ErrnoNosys
case sys.ENOTDIR:
return ErrnoNotdir
case sys.ERANGE:
return ErrnoRange
case sys.ENOTEMPTY:
return ErrnoNotempty
case sys.ENOTSOCK:
return ErrnoNotsock
case sys.ENOTSUP:
return ErrnoNotsup
case sys.EPERM:
return ErrnoPerm
case sys.EROFS:
return ErrnoRofs
default:
return ErrnoIo
}
}

View File

@ -0,0 +1,164 @@
package wasip1
import (
"fmt"
)
const (
FdAdviseName = "fd_advise"
FdAllocateName = "fd_allocate"
FdCloseName = "fd_close"
FdDatasyncName = "fd_datasync"
FdFdstatGetName = "fd_fdstat_get"
FdFdstatSetFlagsName = "fd_fdstat_set_flags"
FdFdstatSetRightsName = "fd_fdstat_set_rights"
FdFilestatGetName = "fd_filestat_get"
FdFilestatSetSizeName = "fd_filestat_set_size"
FdFilestatSetTimesName = "fd_filestat_set_times"
FdPreadName = "fd_pread"
FdPrestatGetName = "fd_prestat_get"
FdPrestatDirNameName = "fd_prestat_dir_name"
FdPwriteName = "fd_pwrite"
FdReadName = "fd_read"
FdReaddirName = "fd_readdir"
FdRenumberName = "fd_renumber"
FdSeekName = "fd_seek"
FdSyncName = "fd_sync"
FdTellName = "fd_tell"
FdWriteName = "fd_write"
PathCreateDirectoryName = "path_create_directory"
PathFilestatGetName = "path_filestat_get"
PathFilestatSetTimesName = "path_filestat_set_times"
PathLinkName = "path_link"
PathOpenName = "path_open"
PathReadlinkName = "path_readlink"
PathRemoveDirectoryName = "path_remove_directory"
PathRenameName = "path_rename"
PathSymlinkName = "path_symlink"
PathUnlinkFileName = "path_unlink_file"
)
// oflags are open flags used by path_open
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-oflags-flagsu16
const (
// O_CREAT creates a file if it does not exist.
O_CREAT uint16 = 1 << iota //nolint
// O_DIRECTORY fails if not a directory.
O_DIRECTORY
// O_EXCL fails if file already exists.
O_EXCL //nolint
// O_TRUNC truncates the file to size 0.
O_TRUNC //nolint
)
func OflagsString(oflags int) string {
return flagsString(oflagNames[:], oflags)
}
var oflagNames = [...]string{
"CREAT",
"DIRECTORY",
"EXCL",
"TRUNC",
}
// file descriptor flags
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fdflags
const (
FD_APPEND uint16 = 1 << iota //nolint
FD_DSYNC
FD_NONBLOCK
FD_RSYNC
FD_SYNC
)
func FdFlagsString(fdflags int) string {
return flagsString(fdflagNames[:], fdflags)
}
var fdflagNames = [...]string{
"APPEND",
"DSYNC",
"NONBLOCK",
"RSYNC",
"SYNC",
}
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#lookupflags
const (
// LOOKUP_SYMLINK_FOLLOW expands a path if it resolves into a symbolic
// link.
LOOKUP_SYMLINK_FOLLOW uint16 = 1 << iota //nolint
)
var lookupflagNames = [...]string{
"SYMLINK_FOLLOW",
}
func LookupflagsString(lookupflags int) string {
return flagsString(lookupflagNames[:], lookupflags)
}
// DirentSize is the size of the dirent struct, which should be followed by the
// length of a file name.
const DirentSize = uint32(24)
const (
FILETYPE_UNKNOWN uint8 = iota
FILETYPE_BLOCK_DEVICE
FILETYPE_CHARACTER_DEVICE
FILETYPE_DIRECTORY
FILETYPE_REGULAR_FILE
FILETYPE_SOCKET_DGRAM
FILETYPE_SOCKET_STREAM
FILETYPE_SYMBOLIC_LINK
)
// FiletypeName returns string name of the file type.
func FiletypeName(filetype uint8) string {
if int(filetype) < len(filetypeToString) {
return filetypeToString[filetype]
}
return fmt.Sprintf("filetype(%d)", filetype)
}
var filetypeToString = [...]string{
"UNKNOWN",
"BLOCK_DEVICE",
"CHARACTER_DEVICE",
"DIRECTORY",
"REGULAR_FILE",
"SOCKET_DGRAM",
"SOCKET_STREAM",
"SYMBOLIC_LINK",
}
// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fstflags
const (
FstflagsAtim uint16 = 1 << iota
FstflagsAtimNow
FstflagsMtim
FstflagsMtimNow
)
var fstflagNames = [...]string{
"ATIM",
"ATIM_NOW",
"MTIM",
"MTIM_NOW",
}
func FstflagsString(fdflags int) string {
return flagsString(fstflagNames[:], fdflags)
}
// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-advice-enumu8
const (
FdAdviceNormal byte = iota
FdAdviceSequential
FdAdviceRandom
FdAdviceWillNeed
FdAdviceDontNeed
FdAdviceNoReuse
)

View File

@ -0,0 +1,15 @@
package wasip1
// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-eventtype-enumu8
const (
// EventTypeClock is the timeout event named "name".
EventTypeClock = iota
// EventTypeFdRead is the data available event named "fd_read".
EventTypeFdRead
// EventTypeFdWrite is the capacity available event named "fd_write".
EventTypeFdWrite
)
const (
PollOneoffName = "poll_oneoff"
)

View File

@ -0,0 +1,6 @@
package wasip1
const (
ProcExitName = "proc_exit"
ProcRaiseName = "proc_raise"
)

View File

@ -0,0 +1,3 @@
package wasip1
const RandomGetName = "random_get"

View File

@ -0,0 +1,148 @@
package wasip1
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-rights-flagsu64
const (
// RIGHT_FD_DATASYNC is the right to invoke fd_datasync. If RIGHT_PATH_OPEN
// is set, includes the right to invoke path_open with FD_DSYNC.
RIGHT_FD_DATASYNC uint32 = 1 << iota //nolint
// RIGHT_FD_READ is he right to invoke fd_read and sock_recv. If
// RIGHT_FD_SYNC is set, includes the right to invoke fd_pread.
RIGHT_FD_READ
// RIGHT_FD_SEEK is the right to invoke fd_seek. This flag implies
// RIGHT_FD_TELL.
RIGHT_FD_SEEK
// RIGHT_FDSTAT_SET_FLAGS is the right to invoke fd_fdstat_set_flags.
RIGHT_FDSTAT_SET_FLAGS
// RIGHT_FD_SYNC The right to invoke fd_sync. If path_open is set, includes
// the right to invoke path_open with FD_RSYNC and FD_DSYNC.
RIGHT_FD_SYNC
// RIGHT_FD_TELL is the right to invoke fd_seek in such a way that the file
// offset remains unaltered (i.e., whence::cur with offset zero), or to
// invoke fd_tell.
RIGHT_FD_TELL
// RIGHT_FD_WRITE is the right to invoke fd_write and sock_send. If
// RIGHT_FD_SEEK is set, includes the right to invoke fd_pwrite.
RIGHT_FD_WRITE
// RIGHT_FD_ADVISE is the right to invoke fd_advise.
RIGHT_FD_ADVISE
// RIGHT_FD_ALLOCATE is the right to invoke fd_allocate.
RIGHT_FD_ALLOCATE
// RIGHT_PATH_CREATE_DIRECTORY is the right to invoke
// path_create_directory.
RIGHT_PATH_CREATE_DIRECTORY
// RIGHT_PATH_CREATE_FILE when RIGHT_PATH_OPEN is set, the right to invoke
// path_open with O_CREAT.
RIGHT_PATH_CREATE_FILE
// RIGHT_PATH_LINK_SOURCE is the right to invoke path_link with the file
// descriptor as the source directory.
RIGHT_PATH_LINK_SOURCE
// RIGHT_PATH_LINK_TARGET is the right to invoke path_link with the file
// descriptor as the target directory.
RIGHT_PATH_LINK_TARGET
// RIGHT_PATH_OPEN is the right to invoke path_open.
RIGHT_PATH_OPEN
// RIGHT_FD_READDIR is the right to invoke fd_readdir.
RIGHT_FD_READDIR
// RIGHT_PATH_READLINK is the right to invoke path_readlink.
RIGHT_PATH_READLINK
// RIGHT_PATH_RENAME_SOURCE is the right to invoke path_rename with the
// file descriptor as the source directory.
RIGHT_PATH_RENAME_SOURCE
// RIGHT_PATH_RENAME_TARGET is the right to invoke path_rename with the
// file descriptor as the target directory.
RIGHT_PATH_RENAME_TARGET
// RIGHT_PATH_FILESTAT_GET is the right to invoke path_filestat_get.
RIGHT_PATH_FILESTAT_GET
// RIGHT_PATH_FILESTAT_SET_SIZE is the right to change a file's size (there
// is no path_filestat_set_size). If RIGHT_PATH_OPEN is set, includes the
// right to invoke path_open with O_TRUNC.
RIGHT_PATH_FILESTAT_SET_SIZE
// RIGHT_PATH_FILESTAT_SET_TIMES is the right to invoke
// path_filestat_set_times.
RIGHT_PATH_FILESTAT_SET_TIMES
// RIGHT_FD_FILESTAT_GET is the right to invoke fd_filestat_get.
RIGHT_FD_FILESTAT_GET
// RIGHT_FD_FILESTAT_SET_SIZE is the right to invoke fd_filestat_set_size.
RIGHT_FD_FILESTAT_SET_SIZE
// RIGHT_FD_FILESTAT_SET_TIMES is the right to invoke
// fd_filestat_set_times.
RIGHT_FD_FILESTAT_SET_TIMES
// RIGHT_PATH_SYMLINK is the right to invoke path_symlink.
RIGHT_PATH_SYMLINK
// RIGHT_PATH_REMOVE_DIRECTORY is the right to invoke
// path_remove_directory.
RIGHT_PATH_REMOVE_DIRECTORY
// RIGHT_PATH_UNLINK_FILE is the right to invoke path_unlink_file.
RIGHT_PATH_UNLINK_FILE
// RIGHT_POLL_FD_READWRITE when RIGHT_FD_READ is set, includes the right to
// invoke poll_oneoff to subscribe to eventtype::fd_read. If RIGHT_FD_WRITE
// is set, includes the right to invoke poll_oneoff to subscribe to
// eventtype::fd_write.
RIGHT_POLL_FD_READWRITE
// RIGHT_SOCK_SHUTDOWN is the right to invoke sock_shutdown.
RIGHT_SOCK_SHUTDOWN
)
func RightsString(rights int) string {
return flagsString(rightNames[:], rights)
}
var rightNames = [...]string{
"FD_DATASYNC",
"FD_READ",
"FD_SEEK",
"FDSTAT_SET_FLAGS",
"FD_SYNC",
"FD_TELL",
"FD_WRITE",
"FD_ADVISE",
"FD_ALLOCATE",
"PATH_CREATE_DIRECTORY",
"PATH_CREATE_FILE",
"PATH_LINK_SOURCE",
"PATH_LINK_TARGET",
"PATH_OPEN",
"FD_READDIR",
"PATH_READLINK",
"PATH_RENAME_SOURCE",
"PATH_RENAME_TARGET",
"PATH_FILESTAT_GET",
"PATH_FILESTAT_SET_SIZE",
"PATH_FILESTAT_SET_TIMES",
"FD_FILESTAT_GET",
"FD_FILESTAT_SET_SIZE",
"FD_FILESTAT_SET_TIMES",
"PATH_SYMLINK",
"PATH_REMOVE_DIRECTORY",
"PATH_UNLINK_FILE",
"POLL_FD_READWRITE",
"SOCK_SHUTDOWN",
}

View File

@ -0,0 +1,3 @@
package wasip1
const SchedYieldName = "sched_yield"

View File

@ -0,0 +1,71 @@
package wasip1
import "strconv"
const (
SockAcceptName = "sock_accept"
SockRecvName = "sock_recv"
SockSendName = "sock_send"
SockShutdownName = "sock_shutdown"
)
// SD Flags indicate which channels on a socket to shut down.
// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-sdflags-flagsu8
const (
// SD_RD disables further receive operations.
SD_RD uint8 = 1 << iota //nolint
// SD_WR disables further send operations.
SD_WR
)
func SdFlagsString(sdflags int) string {
return flagsString(sdflagNames[:], sdflags)
}
var sdflagNames = [...]string{
"RD",
"WR",
}
// SI Flags are flags provided to sock_send. As there are currently no flags defined, it must be set to zero.
// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-siflags-u16
func SiFlagsString(siflags int) string {
if siflags == 0 {
return ""
}
return strconv.Itoa(siflags)
}
// RI Flags are flags provided to sock_recv.
// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-riflags-flagsu16
const (
// RI_RECV_PEEK returns the message without removing it from the socket's receive queue
RI_RECV_PEEK uint8 = 1 << iota //nolint
// RI_RECV_WAITALL on byte-stream sockets, block until the full amount of data can be returned.
RI_RECV_WAITALL
)
func RiFlagsString(riflags int) string {
return flagsString(riflagNames[:], riflags)
}
var riflagNames = [...]string{
"RECV_PEEK",
"RECV_WAITALL",
}
// RO Flags are flags returned by sock_recv.
// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-roflags-flagsu16
const (
// RO_RECV_DATA_TRUNCATED is returned by sock_recv when message data has been truncated.
RO_RECV_DATA_TRUNCATED uint8 = 1 << iota //nolint
)
func RoFlagsString(roflags int) string {
return flagsString(roflagNames[:], roflags)
}
var roflagNames = [...]string{
"RECV_DATA_TRUNCATED",
}

View File

@ -0,0 +1,26 @@
// Package wasip1 is a helper to remove package cycles re-using constants.
package wasip1
import (
"strings"
)
// InternalModuleName is not named ModuleName, to avoid a clash on dot imports.
const InternalModuleName = "wasi_snapshot_preview1"
func flagsString(names []string, f int) string {
var builder strings.Builder
first := true
for i, sf := range names {
target := 1 << i
if target&f != 0 {
if !first {
builder.WriteByte('|')
} else {
first = false
}
builder.WriteString(sf)
}
}
return builder.String()
}