From 30be6a8e2a4927a629ce7f568af398aa1c41e78e Mon Sep 17 00:00:00 2001 From: Crypt Keeper <64215+codefromthecrypt@users.noreply.github.com> Date: Fri, 17 Jun 2022 16:48:35 +0800 Subject: [PATCH] wasi: Implements wasi_snapshot_preview1.poll_oneoff for relative clock events (#629) This implements wasi_snapshot_preview1.poll_oneoff for relative clock events, and in doing so stubs `Nanosleep` which defaults to noop, but can be configured to `time.Sleep`. Signed-off-by: Adrian Cole --- RATIONALE.md | 30 +- config.go | 46 ++- config_test.go | 36 +- internal/platform/time.go | 9 + internal/platform/time_test.go | 10 + internal/sys/sys.go | 26 +- internal/sys/sys_test.go | 29 +- internal/wasm/call_context.go | 2 +- internal/wasm/call_context_test.go | 11 +- internal/wasm/memory.go | 1 + internal/wasm/namespace_test.go | 4 +- site/content/specs.md | 94 ++--- sys/clock.go | 3 + wasi_snapshot_preview1/clock.go | 123 ++++++ wasi_snapshot_preview1/clock_test.go | 278 +++++++++++++ wasi_snapshot_preview1/errno.go | 2 +- wasi_snapshot_preview1/poll.go | 135 +++++++ wasi_snapshot_preview1/poll_test.go | 163 ++++++++ wasi_snapshot_preview1/wasi.go | 417 +++++++------------- wasi_snapshot_preview1/wasi_bench_test.go | 10 +- wasi_snapshot_preview1/wasi_test.go | 460 +++++----------------- 21 files changed, 1171 insertions(+), 718 deletions(-) create mode 100644 wasi_snapshot_preview1/clock.go create mode 100644 wasi_snapshot_preview1/clock_test.go create mode 100644 wasi_snapshot_preview1/poll.go create mode 100644 wasi_snapshot_preview1/poll_test.go diff --git a/RATIONALE.md b/RATIONALE.md index 0fece587..4b869a8a 100644 --- a/RATIONALE.md +++ b/RATIONALE.md @@ -417,7 +417,7 @@ is to satisfy WASI: See https://github.com/WebAssembly/wasi-clocks -## Why default to fake time? +### Why default to fake time? WebAssembly has an implicit design pattern of capabilities based security. By defaulting to a fake time, we reduce the chance of timing attacks, at the cost @@ -425,7 +425,7 @@ of requiring configuration to opt-into real clocks. See https://gruss.cc/files/fantastictimers.pdf for an example attacks. -## Why does fake time increase on reading? +### Why does fake time increase on reading? Both the fake nanotime and walltime increase by 1ms on reading. Particularly in the case of nanotime, this prevents spinning. For example, when Go compiles @@ -433,7 +433,7 @@ the case of nanotime, this prevents spinning. For example, when Go compiles never increases, the gouroutine is mistaken for being busy. This would be worse if a compiler implement sleep using nanotime, yet doesn't check for spinning! -## Why not `time.Clock`? +### Why not `time.Clock`? wazero can't use `time.Clock` as a plugin for clock implementation as it is only substitutable with build flags (`faketime`) and conflates wall and @@ -473,6 +473,30 @@ to a POSIX method. Writing assembly would allow making syscalls without CGO, but comes with the cost that it will require implementations across many combinations of OS and architecture. +## sys.Nanosleep + +All major programming languages have a `sleep` mechanism to block for a +duration. Sleep is typically implemented by a WASI `poll_oneoff` relative clock +subscription. + +For example, the below ends up calling `wasi_snapshot_preview1.poll_oneoff`: + +```zig +const std = @import("std"); +pub fn main() !void { + std.time.sleep(std.time.ns_per_s * 5); +} +``` + +Besides Zig, this is also the case with TinyGo (`-target=wasi`) and Rust +(`--target wasm32-wasi`). This isn't the case with Go (`GOOS=js GOARCH=wasm`), +though. In the latter case, wasm loops on `sys.Nanotime`. + +We decided to expose `sys.Nanosleep` to allow overriding the implementation +used in the common case, even if it isn't used by Go, because this gives an +easy and efficient closure over a common program function. We also documented +`sys.Nanotime` to warn users that some compilers don't optimize sleep. + ## Signed encoding of integer global constant initializers wazero treats integer global constant initializers signed as their interpretation is not known at declaration time. For example, there is no signed integer [value type](https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#value-types%E2%91%A0). diff --git a/config.go b/config.go index 920cccc3..f7fca6a2 100644 --- a/config.go +++ b/config.go @@ -487,8 +487,11 @@ type ModuleConfig interface { // return clock.nanotime() // }, sys.ClockResolution(time.Microsecond.Nanoseconds())) // - // Note: This does not default to time.Since as that violates sandboxing. - // Use WithSysNanotime for a usable implementation. + // Notes: + // * This does not default to time.Since as that violates sandboxing. + // * Some compilers implement sleep by looping on sys.Nanotime (ex. Go). + // * If you set this, you should probably set WithNanosleep also. + // * Use WithSysNanotime for a usable implementation. WithNanotime(sys.Nanotime, sys.ClockResolution) ModuleConfig // WithSysNanotime uses time.Now for sys.Nanotime with a resolution of 1us. @@ -496,6 +499,31 @@ type ModuleConfig interface { // See WithNanotime WithSysNanotime() ModuleConfig + // WithNanosleep configures the how to pause the current goroutine for at + // least the configured nanoseconds. Defaults to return immediately. + // + // Ex. To override with your own sleep function: + // moduleConfig = moduleConfig. + // WithNanosleep(func(ctx context.Context, ns int64) { + // rel := unix.NsecToTimespec(ns) + // remain := unix.Timespec{} + // for { // loop until no more time remaining + // err := unix.ClockNanosleep(unix.CLOCK_MONOTONIC, 0, &rel, &remain) + // --snip-- + // + // Notes: + // * This primarily supports `poll_oneoff` for relative clock events. + // * This does not default to time.Sleep as that violates sandboxing. + // * Some compilers implement sleep by looping on sys.Nanotime (ex. Go). + // * If you set this, you should probably set WithNanotime also. + // * Use WithSysNanosleep for a usable implementation. + WithNanosleep(sys.Nanosleep) ModuleConfig + + // WithSysNanosleep uses time.Sleep for sys.Nanosleep. + // + // See WithNanosleep + WithSysNanosleep() ModuleConfig + // WithRandSource configures a source of random bytes. Defaults to crypto/rand.Reader. // // This reader is most commonly used by the functions like "random_get" in "wasi_snapshot_preview1" or "seed" in @@ -530,6 +558,7 @@ type moduleConfig struct { walltimeResolution sys.ClockResolution nanotime *sys.Nanotime nanotimeResolution sys.ClockResolution + nanosleep *sys.Nanosleep args []string // environ is pair-indexed to retain order similar to os.Environ. environ []string @@ -650,6 +679,18 @@ func (c *moduleConfig) WithSysNanotime() ModuleConfig { return c.WithNanotime(platform.Nanotime, sys.ClockResolution(1)) } +// WithNanosleep implements ModuleConfig.WithNanosleep +func (c *moduleConfig) WithNanosleep(nanosleep sys.Nanosleep) ModuleConfig { + ret := *c // copy + ret.nanosleep = &nanosleep + return &ret +} + +// WithSysNanosleep implements ModuleConfig.WithSysNanosleep +func (c *moduleConfig) WithSysNanosleep() ModuleConfig { + return c.WithNanosleep(platform.Nanosleep) +} + // WithRandSource implements ModuleConfig.WithRandSource func (c *moduleConfig) WithRandSource(source io.Reader) ModuleConfig { ret := c.clone() @@ -698,6 +739,7 @@ func (c *moduleConfig) toSysContext() (sysCtx *internalsys.Context, err error) { c.randSource, c.walltime, c.walltimeResolution, c.nanotime, c.nanotimeResolution, + c.nanosleep, preopens, ) } diff --git a/config_test.go b/config_test.go index bacb5415..1871594e 100644 --- a/config_test.go +++ b/config_test.go @@ -325,6 +325,7 @@ func TestModuleConfig_toSysContext(t *testing.T) { nil, // randSource &wt, 1, // walltime, walltimeResolution &nt, 1, // nanotime, nanotimeResolution + nil, // nanosleep nil, // openedFiles ), }, @@ -341,7 +342,7 @@ func TestModuleConfig_toSysContext(t *testing.T) { nil, // randSource &wt, 1, // walltime, walltimeResolution &nt, 1, // nanotime, nanotimeResolution - + nil, // nanosleep nil, // openedFiles ), }, @@ -358,7 +359,7 @@ func TestModuleConfig_toSysContext(t *testing.T) { nil, // randSource &wt, 1, // walltime, walltimeResolution &nt, 1, // nanotime, nanotimeResolution - + nil, // nanosleep nil, // openedFiles ), }, @@ -375,7 +376,7 @@ func TestModuleConfig_toSysContext(t *testing.T) { nil, // randSource &wt, 1, // walltime, walltimeResolution &nt, 1, // nanotime, nanotimeResolution - + nil, // nanosleep nil, // openedFiles ), }, @@ -392,7 +393,7 @@ func TestModuleConfig_toSysContext(t *testing.T) { nil, // randSource &wt, 1, // walltime, walltimeResolution &nt, 1, // nanotime, nanotimeResolution - + nil, // nanosleep nil, // openedFiles ), }, @@ -409,6 +410,7 @@ func TestModuleConfig_toSysContext(t *testing.T) { nil, // randSource &wt, 1, // walltime, walltimeResolution &nt, 1, // nanotime, nanotimeResolution + nil, // nanosleep nil, // openedFiles ), }, @@ -425,7 +427,7 @@ func TestModuleConfig_toSysContext(t *testing.T) { nil, // randSource &wt, 1, // walltime, walltimeResolution &nt, 1, // nanotime, nanotimeResolution - + nil, // nanosleep nil, // openedFiles ), }, @@ -442,7 +444,7 @@ func TestModuleConfig_toSysContext(t *testing.T) { nil, // randSource &wt, 1, // walltime, walltimeResolution &nt, 1, // nanotime, nanotimeResolution - + nil, // nanosleep nil, // openedFiles ), }, @@ -459,7 +461,7 @@ func TestModuleConfig_toSysContext(t *testing.T) { nil, // randSource &wt, 1, // walltime, walltimeResolution &nt, 1, // nanotime, nanotimeResolution - + nil, // nanosleep nil, // openedFiles ), }, @@ -476,7 +478,7 @@ func TestModuleConfig_toSysContext(t *testing.T) { nil, // randSource &wt, 1, // walltime, walltimeResolution &nt, 1, // nanotime, nanotimeResolution - + nil, // nanosleep map[uint32]*internalsys.FileEntry{ // openedFiles 3: {Path: "/", FS: testFS}, 4: {Path: ".", FS: testFS}, @@ -496,6 +498,7 @@ func TestModuleConfig_toSysContext(t *testing.T) { nil, // randSource &wt, 1, // walltime, walltimeResolution &nt, 1, // nanotime, nanotimeResolution + nil, // nanosleep map[uint32]*internalsys.FileEntry{ // openedFiles 3: {Path: "/", FS: testFS2}, 4: {Path: ".", FS: testFS2}, @@ -515,6 +518,7 @@ func TestModuleConfig_toSysContext(t *testing.T) { nil, // randSource &wt, 1, // walltime, walltimeResolution &nt, 1, // nanotime, nanotimeResolution + nil, // nanosleep map[uint32]*internalsys.FileEntry{ // openedFiles 3: {Path: ".", FS: testFS}, }, @@ -533,6 +537,7 @@ func TestModuleConfig_toSysContext(t *testing.T) { nil, // randSource &wt, 1, // walltime, walltimeResolution &nt, 1, // nanotime, nanotimeResolution + nil, // nanosleep map[uint32]*internalsys.FileEntry{ // openedFiles 3: {Path: "/", FS: testFS}, 4: {Path: ".", FS: testFS2}, @@ -552,6 +557,7 @@ func TestModuleConfig_toSysContext(t *testing.T) { nil, // randSource &wt, 1, // walltime, walltimeResolution &nt, 1, // nanotime, nanotimeResolution + nil, // nanosleep map[uint32]*internalsys.FileEntry{ // openedFiles 3: {Path: ".", FS: testFS}, 4: {Path: "/", FS: testFS2}, @@ -715,6 +721,18 @@ func TestModuleConfig_toSysContext_WithNanotime(t *testing.T) { }) } +// TestModuleConfig_toSysContext_WithNanosleep has to test differently because +// we can't compare function pointers when functions are passed by value. +func TestModuleConfig_toSysContext_WithNanosleep(t *testing.T) { + sysCtx, err := NewModuleConfig(). + WithNanosleep(func(ctx context.Context, ns int64) { + require.Equal(t, testCtx, ctx) + }).(*moduleConfig).toSysContext() + require.NoError(t, err) + // If below pass, the context was correct! + sysCtx.Nanosleep(testCtx, 2) +} + func TestModuleConfig_toSysContext_Errors(t *testing.T) { tests := []struct { name string @@ -820,6 +838,7 @@ func requireSysContext( randSource io.Reader, walltime *sys.Walltime, walltimeResolution sys.ClockResolution, nanotime *sys.Nanotime, nanotimeResolution sys.ClockResolution, + nanosleep *sys.Nanosleep, openedFiles map[uint32]*internalsys.FileEntry, ) *internalsys.Context { sysCtx, err := internalsys.NewContext( @@ -832,6 +851,7 @@ func requireSysContext( randSource, walltime, walltimeResolution, nanotime, nanotimeResolution, + nanosleep, openedFiles, ) require.NoError(t, err) diff --git a/internal/platform/time.go b/internal/platform/time.go index bde551a2..2f5c2a67 100644 --- a/internal/platform/time.go +++ b/internal/platform/time.go @@ -37,6 +37,10 @@ func NewFakeNanotime() *sys.Nanotime { return &nt } +// FakeNanosleep implements sys.Nanosleep by returning without sleeping. +func FakeNanosleep(context.Context, int64) { +} + // Walltime implements sys.Walltime with time.Now. // // Note: This is only notably less efficient than it could be is reading @@ -66,3 +70,8 @@ func nanotimePortable() int64 { func Nanotime(context.Context) int64 { return nanotime() } + +// Nanosleep implements sys.Nanosleep with time.Sleep. +func Nanosleep(_ context.Context, ns int64) { + time.Sleep(time.Duration(ns)) +} diff --git a/internal/platform/time_test.go b/internal/platform/time_test.go index d040e753..3d6ca33c 100644 --- a/internal/platform/time_test.go +++ b/internal/platform/time_test.go @@ -67,3 +67,13 @@ func Test_Nanotime(t *testing.T) { }) } } + +func Test_Nanosleep(t *testing.T) { + ns := int64(50 * time.Millisecond) + start := Nanotime(context.Background()) + Nanosleep(context.Background(), ns) + + duration := Nanotime(context.Background()) - start + max := ns * 2 // max scheduling delay + require.True(t, duration > 0 && duration < max, "Nanosleep(%d) slept for %d", ns, duration) +} diff --git a/internal/sys/sys.go b/internal/sys/sys.go index d06ae4a3..ed0fe7bc 100644 --- a/internal/sys/sys.go +++ b/internal/sys/sys.go @@ -26,6 +26,7 @@ type Context struct { walltimeResolution sys.ClockResolution nanotime *sys.Nanotime nanotimeResolution sys.ClockResolution + nanosleep *sys.Nanosleep randSource io.Reader fs *FSContext @@ -103,8 +104,21 @@ func (c *Context) NanotimeResolution() sys.ClockResolution { return c.nanotimeResolution } +// Nanosleep implements sys.Nanosleep. +func (c *Context) Nanosleep(ctx context.Context, ns int64) { + (*(c.nanosleep))(ctx, ns) +} + // FS returns the file system context. -func (c *Context) FS() *FSContext { +func (c *Context) FS(ctx context.Context) *FSContext { + // Override Context when it is passed via context + if fsValue := ctx.Value(FSKey{}); fsValue != nil { + fsCtx, ok := fsValue.(*FSContext) + if !ok { + panic(fmt.Errorf("unsupported fs key: %v", fsValue)) + } + return fsCtx + } return c.fs } @@ -128,7 +142,7 @@ func (eofReader) Read([]byte) (int, error) { // Note: This isn't a constant because Context.openedFiles is currently mutable even when empty. // TODO: Make it an error to open or close files when no FS was assigned. func DefaultContext() *Context { - if sysCtx, err := NewContext(0, nil, nil, nil, nil, nil, nil, nil, 0, nil, 0, nil); err != nil { + if sysCtx, err := NewContext(0, nil, nil, nil, nil, nil, nil, nil, 0, nil, 0, nil, nil); err != nil { panic(fmt.Errorf("BUG: DefaultContext should never error: %w", err)) } else { return sysCtx @@ -136,6 +150,7 @@ func DefaultContext() *Context { } var _ = DefaultContext() // Force panic on bug. +var ns sys.Nanosleep = platform.FakeNanosleep // NewContext is a factory function which helps avoid needing to know defaults or exporting all fields. // Note: max is exposed for testing. max is only used for env/args validation. @@ -147,6 +162,7 @@ func NewContext( randSource io.Reader, walltime *sys.Walltime, walltimeResolution sys.ClockResolution, nanotime *sys.Nanotime, nanotimeResolution sys.ClockResolution, + nanosleep *sys.Nanosleep, openedFiles map[uint32]*FileEntry, ) (sysCtx *Context, err error) { sysCtx = &Context{args: args, environ: environ} @@ -205,6 +221,12 @@ func NewContext( sysCtx.nanotimeResolution = sys.ClockResolution(time.Nanosecond) } + if nanosleep != nil { + sysCtx.nanosleep = nanosleep + } else { + sysCtx.nanosleep = &ns + } + sysCtx.fs = NewFSContext(openedFiles) return diff --git a/internal/sys/sys_test.go b/internal/sys/sys_test.go index 94df7a8b..3ffa928a 100644 --- a/internal/sys/sys_test.go +++ b/internal/sys/sys_test.go @@ -2,6 +2,7 @@ package sys import ( "bytes" + "context" "crypto/rand" "io" "testing" @@ -23,6 +24,7 @@ func TestDefaultSysContext(t *testing.T) { nil, // randSource nil, 0, // walltime, walltimeResolution nil, 0, // nanotime, nanotimeResolution + nil, // nanosleep nil, // openedFiles ) require.NoError(t, err) @@ -41,8 +43,9 @@ func TestDefaultSysContext(t *testing.T) { require.Equal(t, sys.ClockResolution(1_000), sysCtx.WalltimeResolution()) require.Zero(t, sysCtx.Nanotime(testCtx)) // See above on functions. require.Equal(t, sys.ClockResolution(1), sysCtx.NanotimeResolution()) + require.Equal(t, &ns, sysCtx.nanosleep) require.Equal(t, rand.Reader, sysCtx.RandSource()) - require.Equal(t, NewFSContext(map[uint32]*FileEntry{}), sysCtx.FS()) + require.Equal(t, NewFSContext(map[uint32]*FileEntry{}), sysCtx.FS(testCtx)) } func TestNewContext_Args(t *testing.T) { @@ -93,6 +96,7 @@ func TestNewContext_Args(t *testing.T) { nil, // randSource nil, 0, // walltime, walltimeResolution nil, 0, // nanotime, nanotimeResolution + nil, // nanosleep nil, // openedFiles ) if tc.expectedErr == "" { @@ -154,6 +158,7 @@ func TestNewContext_Environ(t *testing.T) { nil, // randSource nil, 0, // walltime, walltimeResolution nil, 0, // nanotime, nanotimeResolution + nil, // nanosleep nil, // openedFiles ) if tc.expectedErr == "" { @@ -201,6 +206,7 @@ func TestNewContext_Walltime(t *testing.T) { nil, // randSource tc.time, tc.resolution, // walltime, walltimeResolution nil, 0, // nanotime, nanotimeResolution + nil, // nanosleep nil, // openedFiles ) if tc.expectedErr == "" { @@ -248,6 +254,7 @@ func TestNewContext_Nanotime(t *testing.T) { nil, // randSource nil, 0, // nanotime, nanotimeResolution tc.time, tc.resolution, // nanotime, nanotimeResolution + nil, // nanosleep nil, // openedFiles ) if tc.expectedErr == "" { @@ -291,3 +298,23 @@ func Test_clockResolutionInvalid(t *testing.T) { }) } } + +func TestNewContext_Nanosleep(t *testing.T) { + var aNs sys.Nanosleep = func(context.Context, int64) { + } + sysCtx, err := NewContext( + 0, // max + nil, // args + nil, + nil, // stdin + nil, // stdout + nil, // stderr + nil, // randSource + nil, 0, // Nanosleep, NanosleepResolution + nil, 0, // Nanosleep, NanosleepResolution + &aNs, // nanosleep + nil, // openedFiles + ) + require.Nil(t, err) + require.Equal(t, &aNs, sysCtx.nanosleep) +} diff --git a/internal/wasm/call_context.go b/internal/wasm/call_context.go index 0ba82697..a244b8f8 100644 --- a/internal/wasm/call_context.go +++ b/internal/wasm/call_context.go @@ -116,7 +116,7 @@ func (m *CallContext) close(ctx context.Context, exitCode uint32) (c bool, err e return false, nil } if sysCtx := m.Sys; sysCtx != nil { // ex nil if from ModuleBuilder - return true, sysCtx.FS().Close(ctx) + return true, sysCtx.FS(ctx).Close(ctx) } return true, nil } diff --git a/internal/wasm/call_context_test.go b/internal/wasm/call_context_test.go index d5f32d57..a9b977c6 100644 --- a/internal/wasm/call_context_test.go +++ b/internal/wasm/call_context_test.go @@ -144,14 +144,15 @@ func TestCallContext_Close(t *testing.T) { t.Run("calls Context.Close()", func(t *testing.T) { sysCtx := sys.DefaultContext() - sysCtx.FS().OpenFile(&sys.FileEntry{Path: "."}) + fsCtx := sysCtx.FS(testCtx) + + fsCtx.OpenFile(&sys.FileEntry{Path: "."}) m, err := s.Instantiate(context.Background(), ns, &Module{}, t.Name(), sysCtx, nil) require.NoError(t, err) // We use side effects to determine if Close in fact called Context.Close (without repeating sys_test.go). // One side effect of Context.Close is that it clears the openedFiles map. Verify our base case. - fsCtx := sysCtx.FS() _, ok := fsCtx.OpenedFile(3) require.True(t, ok, "sysCtx.openedFiles was empty") @@ -169,7 +170,9 @@ func TestCallContext_Close(t *testing.T) { t.Run("error closing", func(t *testing.T) { // Right now, the only way to err closing the sys context is if a File.Close erred. sysCtx := sys.DefaultContext() - sysCtx.FS().OpenFile(&sys.FileEntry{Path: ".", File: &testFile{errors.New("error closing")}}) + fsCtx := sysCtx.FS(testCtx) + + fsCtx.OpenFile(&sys.FileEntry{Path: ".", File: &testFile{errors.New("error closing")}}) m, err := s.Instantiate(context.Background(), ns, &Module{}, t.Name(), sysCtx, nil) require.NoError(t, err) @@ -177,7 +180,7 @@ func TestCallContext_Close(t *testing.T) { require.EqualError(t, m.Close(testCtx), "error closing") // Verify our intended side-effect - _, ok := sysCtx.FS().OpenedFile(3) + _, ok := fsCtx.OpenedFile(3) require.False(t, ok, "expected no opened files") }) } diff --git a/internal/wasm/memory.go b/internal/wasm/memory.go index 66de50b5..c742986f 100644 --- a/internal/wasm/memory.go +++ b/internal/wasm/memory.go @@ -157,6 +157,7 @@ func (m *MemoryInstance) WriteUint16Le(_ context.Context, offset uint32, v uint1 // WriteUint32Le implements the same method as documented on api.Memory. func (m *MemoryInstance) WriteUint32Le(_ context.Context, offset, v uint32) bool { + // Note: If you use the context.Context param, don't forget to coerce nil to context.Background()! return m.writeUint32Le(offset, v) } diff --git a/internal/wasm/namespace_test.go b/internal/wasm/namespace_test.go index e91fc93a..788addf1 100644 --- a/internal/wasm/namespace_test.go +++ b/internal/wasm/namespace_test.go @@ -163,7 +163,9 @@ func TestNamespace_CloseWithExitCode(t *testing.T) { t.Run("error closing", func(t *testing.T) { sysCtx := sys.DefaultContext() - sysCtx.FS().OpenFile(&sys.FileEntry{Path: ".", File: &testFile{errors.New("error closing")}}) + fsCtx := sysCtx.FS(testCtx) + + fsCtx.OpenFile(&sys.FileEntry{Path: ".", File: &testFile{errors.New("error closing")}}) ns, m1, m2 := newTestNamespace() m1.CallCtx.Sys = sysCtx // This should err, but both should close diff --git a/site/content/specs.md b/site/content/specs.md index 0404775f..23ba11bd 100644 --- a/site/content/specs.md +++ b/site/content/specs.md @@ -76,53 +76,53 @@ your use case (ex which language you are using to compile, a.k.a. target Wasm).
Click to see the full list of supported WASI functions

-| Function | Status | Known Usage | -|:------------------------|:------:|---------------:| -| args_get | ✅ | TinyGo | -| args_sizes_get | ✅ | TinyGo | -| environ_get | ✅ | TinyGo | -| environ_sizes_get | ✅ | TinyGo | -| clock_res_get | ✅ | | -| clock_time_get | ✅ | TinyGo | -| fd_advise | ❌ | | -| fd_allocate | ❌ | | -| fd_close | ✅ | TinyGo | -| fd_datasync | ❌ | | -| fd_fdstat_get | ✅ | TinyGo | -| fd_fdstat_set_flags | ❌ | | -| fd_fdstat_set_rights | ❌ | | -| fd_filestat_get | ❌ | | -| fd_filestat_set_size | ❌ | | -| fd_filestat_set_times | ❌ | | -| fd_pread | ❌ | | -| fd_prestat_get | ✅ | TinyGo | -| fd_prestat_dir_name | ✅ | TinyGo | -| fd_pwrite | ❌ | | -| fd_read | ✅ | TinyGo | -| fd_readdir | ❌ | | -| fd_renumber | ❌ | | -| fd_seek | ✅ | TinyGo | -| fd_sync | ❌ | | -| fd_tell | ❌ | | -| fd_write | ✅ | | -| path_create_directory | ❌ | | -| path_filestat_get | ❌ | | -| path_filestat_set_times | ❌ | | -| path_link | ❌ | | -| path_open | ✅ | TinyGo | -| path_readlink | ❌ | | -| path_remove_directory | ❌ | | -| path_rename | ❌ | | -| path_symlink | ❌ | | -| path_unlink_file | ❌ | | -| poll_oneoff | ✅ | TinyGo | -| proc_exit | ✅ | AssemblyScript | -| proc_raise | ❌ | | -| sched_yield | ❌ | | -| random_get | ✅ | | -| sock_recv | ❌ | | -| sock_send | ❌ | | -| sock_shutdown | ❌ | | +| Function | Status | Known Usage | +|:------------------------|:------:|----------------:| +| args_get | ✅ | TinyGo | +| args_sizes_get | ✅ | TinyGo | +| environ_get | ✅ | TinyGo | +| environ_sizes_get | ✅ | TinyGo | +| clock_res_get | ✅ | | +| clock_time_get | ✅ | TinyGo | +| fd_advise | ❌ | | +| fd_allocate | ❌ | | +| fd_close | ✅ | TinyGo | +| fd_datasync | ❌ | | +| fd_fdstat_get | ✅ | TinyGo | +| fd_fdstat_set_flags | ❌ | | +| fd_fdstat_set_rights | ❌ | | +| fd_filestat_get | ❌ | | +| fd_filestat_set_size | ❌ | | +| fd_filestat_set_times | ❌ | | +| fd_pread | ❌ | | +| fd_prestat_get | ✅ | TinyGo | +| fd_prestat_dir_name | ✅ | TinyGo | +| fd_pwrite | ❌ | | +| fd_read | ✅ | TinyGo | +| fd_readdir | ❌ | | +| fd_renumber | ❌ | | +| fd_seek | ✅ | TinyGo | +| fd_sync | ❌ | | +| fd_tell | ❌ | | +| fd_write | ✅ | | +| path_create_directory | ❌ | | +| path_filestat_get | ❌ | | +| path_filestat_set_times | ❌ | | +| path_link | ❌ | | +| path_open | ✅ | TinyGo | +| path_readlink | ❌ | | +| path_remove_directory | ❌ | | +| path_rename | ❌ | | +| path_symlink | ❌ | | +| path_unlink_file | ❌ | | +| poll_oneoff | ✅ | Rust,TinyGo,Zig | +| proc_exit | ✅ | AssemblyScript | +| proc_raise | ❌ | | +| sched_yield | ❌ | | +| random_get | ✅ | | +| sock_recv | ❌ | | +| sock_send | ❌ | | +| sock_shutdown | ❌ | |

diff --git a/sys/clock.go b/sys/clock.go index e900390d..be60859a 100644 --- a/sys/clock.go +++ b/sys/clock.go @@ -19,3 +19,6 @@ type Walltime func(context.Context) (sec int64, nsec int32) // Note: There are no constraints on the value return except that it // increments. For example, -1 is a valid if the next value is >= 0. type Nanotime func(context.Context) int64 + +// Nanosleep puts the current goroutine to sleep for at least ns nanoseconds. +type Nanosleep func(ctx context.Context, ns int64) diff --git a/wasi_snapshot_preview1/clock.go b/wasi_snapshot_preview1/clock.go new file mode 100644 index 00000000..09f44ec9 --- /dev/null +++ b/wasi_snapshot_preview1/clock.go @@ -0,0 +1,123 @@ +package wasi_snapshot_preview1 + +import ( + "context" + "time" + + "github.com/tetratelabs/wazero/api" +) + +const ( + // functionClockResGet returns the resolution of a clock. + // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-clock_res_getid-clockid---errno-timestamp + functionClockResGet = "clock_res_get" + + // importClockResGet is the WebAssembly 1.0 Text format import of functionClockResGet. + importClockResGet = `(import "wasi_snapshot_preview1" "clock_res_get" + (func $wasi.clock_res_get (param $id i32) (param $result.resolution i32) (result (;errno;) i32)))` + + // functionClockTimeGet returns the time value of a clock. + // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-clock_time_getid-clockid-precision-timestamp---errno-timestamp + functionClockTimeGet = "clock_time_get" + + // importClockTimeGet is the WebAssembly 1.0 Text format import of functionClockTimeGet. + importClockTimeGet = `(import "wasi_snapshot_preview1" "clock_time_get" + (func $wasi.clock_time_get (param $id i32) (param $precision i64) (param $result.timestamp i32) (result (;errno;) i32)))` +) + +// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-clockid-enumu32 +const ( + // clockIDRealtime is the clock ID named "realtime" associated with sys.Walltime + clockIDRealtime = iota + // clockIDMonotonic is the clock ID named "monotonic" with sys.Nanotime + clockIDMonotonic + // clockIDProcessCputime is the unsupported clock ID named "process_cputime_id" + clockIDProcessCputime + // clockIDThreadCputime is the unsupported clock ID named "thread_cputime_id" + clockIDThreadCputime +) + +// ClockResGet is the WASI function named functionClockResGet that returns the resolution of time values returned by ClockTimeGet. +// +// * id - The clock id for which to return the time. +// * resultResolution - the offset to write the resolution to mod.Memory +// * the resolution is an uint64 little-endian encoding. +// +// For example, if the resolution is 100ns, this function writes the below to `mod.Memory`: +// +// uint64le +// +-------------------------------------+ +// | | +// []byte{?, 0x64, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, ?} +// resultResolution --^ +// +// Note: importClockResGet shows this signature in the WebAssembly 1.0 Text Format. +// 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 +func (a *wasi) ClockResGet(ctx context.Context, mod api.Module, id uint32, resultResolution uint32) Errno { + sysCtx := getSysCtx(mod) + + var resolution uint64 // ns + switch id { + case clockIDRealtime: + resolution = uint64(sysCtx.WalltimeResolution()) + case clockIDMonotonic: + resolution = uint64(sysCtx.NanotimeResolution()) + case clockIDProcessCputime, clockIDThreadCputime: + // Similar to many other runtimes, we only support realtime and monotonic clocks. Other types + // are slated to be removed from the next version of WASI. + return ErrnoNotsup + default: + return ErrnoInval + } + if !mod.Memory().WriteUint64Le(ctx, resultResolution, resolution) { + return ErrnoFault + } + return ErrnoSuccess +} + +// ClockTimeGet is the WASI function named functionClockTimeGet that returns the time value of a clock (time.Now). +// +// * id - The clock id for which to return the time. +// * precision - The maximum lag (exclusive) that the returned time value may have, compared to its actual value. +// * resultTimestamp - the offset to write the timestamp to mod.Memory +// * the timestamp is epoch nanoseconds encoded as a uint64 little-endian encoding. +// +// For example, if time.Now returned exactly midnight UTC 2022-01-01 (1640995200000000000), and +// parameters resultTimestamp=1, this function writes the below to `mod.Memory`: +// +// uint64le +// +------------------------------------------+ +// | | +// []byte{?, 0x0, 0x0, 0x1f, 0xa6, 0x70, 0xfc, 0xc5, 0x16, ?} +// resultTimestamp --^ +// +// Note: importClockTimeGet shows this signature in the WebAssembly 1.0 Text Format. +// 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 +func (a *wasi) ClockTimeGet(ctx context.Context, mod api.Module, id uint32, precision uint64, resultTimestamp uint32) Errno { + // TODO: precision is currently ignored. + sysCtx := getSysCtx(mod) + + var val uint64 + switch id { + case clockIDRealtime: + sec, nsec := sysCtx.Walltime(ctx) + val = (uint64(sec) * uint64(time.Second.Nanoseconds())) + uint64(nsec) + case clockIDMonotonic: + val = uint64(sysCtx.Nanotime(ctx)) + case clockIDProcessCputime, clockIDThreadCputime: + // Similar to many other runtimes, we only support realtime and monotonic clocks. Other types + // are slated to be removed from the next version of WASI. + return ErrnoNotsup + default: + return ErrnoInval + } + + if !mod.Memory().WriteUint64Le(ctx, resultTimestamp, val) { + return ErrnoFault + } + return ErrnoSuccess +} diff --git a/wasi_snapshot_preview1/clock_test.go b/wasi_snapshot_preview1/clock_test.go new file mode 100644 index 00000000..f09aac10 --- /dev/null +++ b/wasi_snapshot_preview1/clock_test.go @@ -0,0 +1,278 @@ +package wasi_snapshot_preview1 + +import ( + _ "embed" + "fmt" + "testing" + + "github.com/tetratelabs/wazero/internal/testing/require" + "github.com/tetratelabs/wazero/internal/wasm" +) + +// Test_ClockResGet only tests it is stubbed for GrainLang per #271 +func Test_ClockResGet(t *testing.T) { + mod, fn := instantiateModule(testCtx, t, functionClockResGet, importClockResGet, nil) + defer mod.Close(testCtx) + + resultResolution := uint32(1) // arbitrary offset + + expectedMemoryMicro := []byte{ + '?', // resultResolution is after this + 0xe8, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // little endian-encoded resolution (fixed to 1000). + '?', // stopped after encoding + } + + expectedMemoryNano := []byte{ + '?', // resultResolution is after this + 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // little endian-encoded resolution (fixed to 1000). + '?', // stopped after encoding + } + + tests := []struct { + name string + clockID uint64 + expectedMemory []byte + invocation func(clockID uint64) Errno + }{ + { + name: "wasi.ClockResGet", + clockID: 0, + expectedMemory: expectedMemoryMicro, + invocation: func(clockID uint64) Errno { + return a.ClockResGet(testCtx, mod, uint32(clockID), resultResolution) + }, + }, + { + name: "wasi.ClockResGet", + clockID: 1, + expectedMemory: expectedMemoryNano, + invocation: func(clockID uint64) Errno { + return a.ClockResGet(testCtx, mod, uint32(clockID), resultResolution) + }, + }, + { + name: functionClockResGet, + clockID: 0, + expectedMemory: expectedMemoryMicro, + invocation: func(clockID uint64) Errno { + results, err := fn.Call(testCtx, clockID, uint64(resultResolution)) + require.NoError(t, err) + return Errno(results[0]) // results[0] is the errno + }, + }, + { + name: functionClockResGet, + clockID: 1, + expectedMemory: expectedMemoryNano, + invocation: func(clockID uint64) Errno { + results, err := fn.Call(testCtx, clockID, uint64(resultResolution)) + require.NoError(t, err) + return Errno(results[0]) // results[0] is the errno + }, + }, + } + + for _, tt := range tests { + tc := tt + + t.Run(fmt.Sprintf("%v/clockID=%v", tc.name, tc.clockID), func(t *testing.T) { + maskMemory(t, testCtx, mod, len(tc.expectedMemory)) + + errno := tc.invocation(tc.clockID) + require.Equal(t, ErrnoSuccess, errno, ErrnoName(errno)) + + actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(tc.expectedMemory))) + require.True(t, ok) + require.Equal(t, tc.expectedMemory, actual) + }) + } +} + +func Test_ClockResGet_Unsupported(t *testing.T) { + resultResolution := uint32(1) // arbitrary offset + mod, fn := instantiateModule(testCtx, t, functionClockResGet, importClockResGet, nil) + defer mod.Close(testCtx) + + tests := []struct { + name string + clockID uint64 + expectedErrno Errno + }{ + { + name: "process cputime", + clockID: 2, + expectedErrno: ErrnoNotsup, + }, + { + name: "thread cputime", + clockID: 3, + expectedErrno: ErrnoNotsup, + }, + { + name: "undefined", + clockID: 100, + expectedErrno: ErrnoInval, + }, + } + + for _, tt := range tests { + tc := tt + + t.Run(tc.name, func(t *testing.T) { + results, err := fn.Call(testCtx, tc.clockID, uint64(resultResolution)) + require.NoError(t, err) + errno := Errno(results[0]) // results[0] is the errno + require.Equal(t, tc.expectedErrno, errno, ErrnoName(errno)) + }) + } +} + +func Test_ClockTimeGet(t *testing.T) { + resultTimestamp := uint32(1) // arbitrary offset + + mod, fn := instantiateModule(testCtx, t, functionClockTimeGet, importClockTimeGet, nil) + defer mod.Close(testCtx) + + clocks := []struct { + clock string + id uint32 + expectedMemory []byte + }{ + { + clock: "Realtime", + id: clockIDRealtime, + expectedMemory: []byte{ + '?', // resultTimestamp is after this + 0x0, 0x0, 0x1f, 0xa6, 0x70, 0xfc, 0xc5, 0x16, // little endian-encoded epochNanos + '?', // stopped after encoding + }, + }, + { + clock: "Monotonic", + id: clockIDMonotonic, + expectedMemory: []byte{ + '?', // resultTimestamp is after this + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // fake nanotime starts at zero + '?', // stopped after encoding + }, + }, + } + + for _, c := range clocks { + cc := c + t.Run(cc.clock, func(t *testing.T) { + tests := []struct { + name string + invocation func() Errno + }{ + { + name: "wasi.ClockTimeGet", + invocation: func() Errno { + return a.ClockTimeGet(testCtx, mod, cc.id, 0 /* TODO: precision */, resultTimestamp) + }, + }, + { + name: functionClockTimeGet, + invocation: func() Errno { + results, err := fn.Call(testCtx, uint64(cc.id), 0 /* TODO: precision */, uint64(resultTimestamp)) + require.NoError(t, err) + errno := Errno(results[0]) // results[0] is the errno + return errno + }, + }, + } + + for _, tt := range tests { + tc := tt + t.Run(tc.name, func(t *testing.T) { + // Reset the fake clock + sysCtx, err := newSysContext(nil, nil, nil) + require.NoError(t, err) + mod.(*wasm.CallContext).Sys = sysCtx + + maskMemory(t, testCtx, mod, len(cc.expectedMemory)) + + errno := tc.invocation() + require.Zero(t, errno, ErrnoName(errno)) + + actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(cc.expectedMemory))) + require.True(t, ok) + require.Equal(t, cc.expectedMemory, actual) + }) + } + }) + } +} + +func Test_ClockTimeGet_Unsupported(t *testing.T) { + resultTimestamp := uint32(1) // arbitrary offset + mod, fn := instantiateModule(testCtx, t, functionClockTimeGet, importClockTimeGet, nil) + defer mod.Close(testCtx) + + tests := []struct { + name string + clockID uint64 + expectedErrno Errno + }{ + { + name: "process cputime", + clockID: 2, + expectedErrno: ErrnoNotsup, + }, + { + name: "thread cputime", + clockID: 3, + expectedErrno: ErrnoNotsup, + }, + { + name: "undefined", + clockID: 100, + expectedErrno: ErrnoInval, + }, + } + + for _, tt := range tests { + tc := tt + + t.Run(tc.name, func(t *testing.T) { + results, err := fn.Call(testCtx, tc.clockID, 0 /* TODO: precision */, uint64(resultTimestamp)) + require.NoError(t, err) + errno := Errno(results[0]) // results[0] is the errno + require.Equal(t, tc.expectedErrno, errno, ErrnoName(errno)) + }) + } +} + +func Test_ClockTimeGet_Errors(t *testing.T) { + mod, fn := instantiateModule(testCtx, t, functionClockTimeGet, importClockTimeGet, nil) + defer mod.Close(testCtx) + + memorySize := mod.Memory().Size(testCtx) + + tests := []struct { + name string + resultTimestamp uint32 + argvBufSize uint32 + }{ + { + name: "resultTimestamp out-of-memory", + resultTimestamp: memorySize, + }, + + { + name: "resultTimestamp exceeds the maximum valid address by 1", + resultTimestamp: memorySize - 4 + 1, // 4 is the size of uint32, the type of the count of args + }, + } + + for _, tt := range tests { + tc := tt + + t.Run(tc.name, func(t *testing.T) { + results, err := fn.Call(testCtx, 0 /* TODO: id */, 0 /* TODO: precision */, uint64(tc.resultTimestamp)) + require.NoError(t, err) + errno := Errno(results[0]) // results[0] is the errno + require.Equal(t, ErrnoFault, errno, ErrnoName(errno)) + }) + } +} diff --git a/wasi_snapshot_preview1/errno.go b/wasi_snapshot_preview1/errno.go index c31d4e1f..83473324 100644 --- a/wasi_snapshot_preview1/errno.go +++ b/wasi_snapshot_preview1/errno.go @@ -13,7 +13,7 @@ import ( // // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-errno-enumu16 and // https://linux.die.net/man/3/errno -type Errno = uint32 // alias for parity with wasm.ValueType +type Errno = uint32 // neither uint16 nor an alias for parity with wasm.ValueType // ErrnoName returns the POSIX error code name, except ErrnoSuccess, which is not an error. Ex. Errno2big -> "E2BIG" func ErrnoName(errno Errno) string { diff --git a/wasi_snapshot_preview1/poll.go b/wasi_snapshot_preview1/poll.go new file mode 100644 index 00000000..812f93bc --- /dev/null +++ b/wasi_snapshot_preview1/poll.go @@ -0,0 +1,135 @@ +package wasi_snapshot_preview1 + +import ( + "context" + "encoding/binary" + + "github.com/tetratelabs/wazero/api" +) + +// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-eventtype-enumu8 +const ( + // eventTypeClock is the timeout event named "clock". + eventTypeClock = iota + // eventTypeFdRead is the data available event named "fd_read". + eventTypeFdRead + // eventTypeFdWrite is the capacity available event named "fd_write". + eventTypeFdWrite +) + +// PollOneoff is the WASI function named functionPollOneoff 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 ErrnoInval. +// * resultNevents - count of events. +// +// Result (Errno) +// +// The return value is ErrnoSuccess except the following error conditions: +// * ErrnoInval - If the parameters are invalid +// * ErrnoNotsup - If a parameters is valid, but not yet supported. +// * ErrnoFault - if there is not enough memory to read the subscriptions or write results. +// +// Notes +// +// * Since the `out` pointer nests Errno, the result is always ErrnoSuccess. +// * importPollOneoff shows this signature in the WebAssembly 1.0 Text Format. +// * 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 +func (a *wasi) PollOneoff(ctx context.Context, mod api.Module, in, out, nsubscriptions, resultNevents uint32) Errno { + if nsubscriptions == 0 { + return ErrnoInval + } + + mem := mod.Memory() + + // Ensure capacity prior to the read loop to reduce error handling. + inBuf, ok := mem.Read(ctx, in, nsubscriptions*48) + if !ok { + return ErrnoFault + } + outBuf, ok := mem.Read(ctx, out, nsubscriptions*32) + if !ok { + return ErrnoFault + } + + // Eagerly write the number of events which will equal subscriptions unless + // there's a fault in parsing (not processing). + if !mod.Memory().WriteUint32Le(ctx, resultNevents, nsubscriptions) { + return ErrnoFault + } + + // Loop through all subscriptions and write their output. + for sub := uint32(0); sub < nsubscriptions; sub++ { + inOffset := sub * 48 + outOffset := sub * 32 + + var errno Errno + eventType := inBuf[inOffset+8] // +8 past userdata + switch eventType { + case eventTypeClock: // handle later + // +8 past userdata +8 clock alignment + errno = processClockEvent(ctx, mod, inBuf[inOffset+8+8:]) + case eventTypeFdRead, eventTypeFdWrite: + // +8 past userdata +4 FD alignment + errno = processFDEvent(ctx, mod, eventType, inBuf[inOffset+8+4:]) + default: + return ErrnoInval + } + + // Write the event corresponding to the processed subscription. + // https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-event-struct + copy(outBuf, inBuf[inOffset:inOffset+8]) // userdata + outBuf[outOffset+8] = byte(errno) // uint16, but safe as < 255 + outBuf[outOffset+9] = 0 + binary.LittleEndian.PutUint32(outBuf[outOffset+10:], uint32(eventType)) + // TODO: When FD events are supported, write outOffset+16 + } + return ErrnoSuccess +} + +// processClockEvent supports only relative clock events, as that's what's used +// to implement sleep in various compilers including Rust, Zig and TinyGo. +func processClockEvent(ctx context.Context, mod api.Module, inBuf []byte) Errno { + _ /* ID */ = binary.LittleEndian.Uint32(inBuf[0:8]) // See below + timeout := binary.LittleEndian.Uint64(inBuf[8:16]) // nanos if relative + _ /* precision */ = binary.LittleEndian.Uint64(inBuf[16:24]) // Unused + flags := binary.LittleEndian.Uint16(inBuf[24:32]) + + // subclockflags has only one flag defined: subscription_clock_abstime + switch flags { + case 0: // relative time + case 1: // subscription_clock_abstime + return ErrnoNotsup + default: // subclockflags has only one flag defined. + return ErrnoInval + } + + // https://linux.die.net/man/3/clock_settime says relative timers are + // unaffected. Since this function only supports relative timeout, we can + // skip clock ID validation and use a single sleep function. + + getSysCtx(mod).Nanosleep(ctx, int64(timeout)) + return ErrnoSuccess +} + +// processFDEvent returns a validation error or ErrnoNotsup as file or socket +// subscriptions are not yet supported. +func processFDEvent(ctx context.Context, mod api.Module, eventType byte, inBuf []byte) Errno { + fd := binary.LittleEndian.Uint32(inBuf) + + // Choose the best error, which falls back to unsupported, until we support files. + errno := ErrnoNotsup + if eventType == eventTypeFdRead && fdReader(ctx, mod, fd) == nil { + errno = ErrnoBadf + } else if eventType == eventTypeFdWrite && fdWriter(ctx, mod, fd) == nil { + errno = ErrnoBadf + } + + return errno +} diff --git a/wasi_snapshot_preview1/poll_test.go b/wasi_snapshot_preview1/poll_test.go new file mode 100644 index 00000000..69e3091e --- /dev/null +++ b/wasi_snapshot_preview1/poll_test.go @@ -0,0 +1,163 @@ +package wasi_snapshot_preview1 + +import ( + "testing" + + "github.com/tetratelabs/wazero/internal/testing/require" + "github.com/tetratelabs/wazero/internal/wasm" +) + +func Test_PollOneoff(t *testing.T) { + mod, fn := instantiateModule(testCtx, t, functionPollOneoff, importPollOneoff, nil) + defer mod.Close(testCtx) + + tests := []struct { + name string + mem []byte + expectedMem []byte // at offset out + }{ + { + name: "monotonic relative", + mem: []byte{ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // userdata + eventTypeClock, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // event type and padding + clockIDMonotonic, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // clockID + 0x01, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // timeout (ns) + 0x01, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // precision (ns) + 0x00, 0x00, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // flags (relative) + '?', // stopped after encoding + }, + expectedMem: []byte{ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // userdata + byte(ErrnoSuccess), 0x0, // errno is 16 bit + eventTypeClock, 0x0, 0x0, 0x0, // 4 bytes for type enum + '?', // stopped after encoding + }, + }, + } + + in := uint32(0) // past in + out := uint32(128) // past in + nsubscriptions := uint32(1) + resultNevents := uint32(512) // past out + + requireExpectedMem := func(expectedMem []byte) { + outMem, ok := mod.Memory().Read(testCtx, out, uint32(len(expectedMem))) + require.True(t, ok) + require.Equal(t, expectedMem, outMem) + + nevents, ok := mod.Memory().ReadUint32Le(testCtx, resultNevents) + require.True(t, ok) + require.Equal(t, nsubscriptions, nevents) + } + + for _, tt := range tests { + tc := tt + t.Run(tc.name, func(t *testing.T) { + t.Run("wasi.PollOneoff", func(t *testing.T) { + maskMemory(t, testCtx, mod, 1024) + mod.Memory().Write(testCtx, in, tc.mem) + + errno := a.PollOneoff(testCtx, mod, in, out, nsubscriptions, resultNevents) + require.Equal(t, ErrnoSuccess, errno, ErrnoName(errno)) + requireExpectedMem(tc.expectedMem) + }) + + t.Run(functionPollOneoff, func(t *testing.T) { + maskMemory(t, testCtx, mod, 1024) + mod.Memory().Write(testCtx, in, tc.mem) + + results, err := fn.Call(testCtx, uint64(in), uint64(out), uint64(nsubscriptions), uint64(resultNevents)) + require.NoError(t, err) + errno := Errno(results[0]) // results[0] is the errno + require.Equal(t, ErrnoSuccess, errno, ErrnoName(errno)) + requireExpectedMem(tc.expectedMem) + }) + }) + } +} + +func Test_PollOneoff_Errors(t *testing.T) { + mod, _ := instantiateModule(testCtx, t, functionPollOneoff, importPollOneoff, nil) + defer mod.Close(testCtx) + + tests := []struct { + name string + in, out, nsubscriptions, resultNevents uint32 + mem []byte // at offset in + expectedErrno Errno + expectedMem []byte // at offset out + }{ + { + name: "in out of range", + in: wasm.MemoryPageSize, + nsubscriptions: 1, + out: 128, // past in + resultNevents: 512, //past out + expectedErrno: ErrnoFault, + }, + { + name: "out out of range", + out: wasm.MemoryPageSize, + resultNevents: 512, //past out + nsubscriptions: 1, + expectedErrno: ErrnoFault, + }, + { + name: "resultNevents out of range", + resultNevents: wasm.MemoryPageSize, + nsubscriptions: 1, + expectedErrno: ErrnoFault, + }, + { + name: "nsubscriptions zero", + out: 128, // past in + resultNevents: 512, //past out + expectedErrno: ErrnoInval, + }, + { + name: "unsupported eventTypeFdRead", + nsubscriptions: 1, + mem: []byte{ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // userdata + eventTypeFdRead, 0x0, 0x0, 0x0, + fdStdin, 0x0, 0x0, 0x0, // valid readable FD + '?', // stopped after encoding + }, + expectedErrno: ErrnoSuccess, + out: 128, // past in + resultNevents: 512, //past out + expectedMem: []byte{ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // userdata + byte(ErrnoNotsup), 0x0, // errno is 16 bit + eventTypeFdRead, 0x0, 0x0, 0x0, // 4 bytes for type enum + '?', // stopped after encoding + }, + }, + } + + for _, tt := range tests { + tc := tt + t.Run(tc.name, func(t *testing.T) { + maskMemory(t, testCtx, mod, 1024) + + if tc.mem != nil { + mod.Memory().Write(testCtx, tc.in, tc.mem) + } + + errno := a.PollOneoff(testCtx, mod, tc.in, tc.out, tc.nsubscriptions, tc.resultNevents) + require.Equal(t, tc.expectedErrno, errno, ErrnoName(errno)) + + out, ok := mod.Memory().Read(testCtx, tc.out, uint32(len(tc.expectedMem))) + require.True(t, ok) + require.Equal(t, tc.expectedMem, out) + + // Events should be written on success regardless of nested failure. + if tc.expectedErrno == ErrnoSuccess { + nevents, ok := mod.Memory().ReadUint32Le(testCtx, tc.resultNevents) + require.True(t, ok) + require.Equal(t, uint32(1), nevents) + } + }) + } +} diff --git a/wasi_snapshot_preview1/wasi.go b/wasi_snapshot_preview1/wasi.go index 9355777a..890a2451 100644 --- a/wasi_snapshot_preview1/wasi.go +++ b/wasi_snapshot_preview1/wasi.go @@ -8,7 +8,7 @@ // r := wazero.NewRuntime() // defer r.Close(ctx) // This closes everything this Runtime created. // -// _, _ = wasi_snapshot_preview1.Instantiate(ctx, r) +// _, _ = Instantiate(ctx, r) // mod, _ := r.InstantiateModuleFromBinary(ctx, wasm) // // See https://github.com/WebAssembly/WASI @@ -21,7 +21,6 @@ import ( "io" "io/fs" "path" - "time" "github.com/tetratelabs/wazero" "github.com/tetratelabs/wazero/api" @@ -85,7 +84,7 @@ const ( // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-args_getargv-pointerpointeru8-argv_buf-pointeru8---errno functionArgsGet = "args_get" - // importArgsGet is the WebAssembly 1.0 (20191205) Text format import of functionArgsGet. + // importArgsGet is the WebAssembly 1.0 Text format import of functionArgsGet. importArgsGet = `(import "wasi_snapshot_preview1" "args_get" (func $wasi.args_get (param $argv i32) (param $argv_buf i32) (result (;errno;) i32)))` @@ -93,7 +92,7 @@ const ( // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-args_sizes_get---errno-size-size functionArgsSizesGet = "args_sizes_get" - // importArgsSizesGet is the WebAssembly 1.0 (20191205) Text format import of functionArgsSizesGet. + // importArgsSizesGet is the WebAssembly 1.0 Text format import of functionArgsSizesGet. importArgsSizesGet = `(import "wasi_snapshot_preview1" "args_sizes_get" (func $wasi.args_sizes_get (param $result.argc i32) (param $result.argv_buf_size i32) (result (;errno;) i32)))` @@ -101,7 +100,7 @@ const ( // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-environ_getenviron-pointerpointeru8-environ_buf-pointeru8---errno functionEnvironGet = "environ_get" - // importEnvironGet is the WebAssembly 1.0 (20191205) Text format import of functionEnvironGet. + // importEnvironGet is the WebAssembly 1.0 Text format import of functionEnvironGet. importEnvironGet = `(import "wasi_snapshot_preview1" "environ_get" (func $wasi.environ_get (param $environ i32) (param $environ_buf i32) (result (;errno;) i32)))` @@ -109,31 +108,15 @@ const ( // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-environ_sizes_get---errno-size-size functionEnvironSizesGet = "environ_sizes_get" - // importEnvironSizesGet is the WebAssembly 1.0 (20191205) Text format import of functionEnvironSizesGet. + // importEnvironSizesGet is the WebAssembly 1.0 Text format import of functionEnvironSizesGet. importEnvironSizesGet = `(import "wasi_snapshot_preview1" "environ_sizes_get" (func $wasi.environ_sizes_get (param $result.environc i32) (param $result.environBufSize i32) (result (;errno;) i32)))` - // functionClockResGet returns the resolution of a clock. - // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-clock_res_getid-clockid---errno-timestamp - functionClockResGet = "clock_res_get" - - // importClockResGet is the WebAssembly 1.0 (20191205) Text format import of functionClockResGet. - importClockResGet = `(import "wasi_snapshot_preview1" "clock_res_get" - (func $wasi.clock_res_get (param $id i32) (param $result.resolution i32) (result (;errno;) i32)))` - - // functionClockTimeGet returns the time value of a clock. - // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-clock_time_getid-clockid-precision-timestamp---errno-timestamp - functionClockTimeGet = "clock_time_get" - - // importClockTimeGet is the WebAssembly 1.0 (20191205) Text format import of functionClockTimeGet. - importClockTimeGet = `(import "wasi_snapshot_preview1" "clock_time_get" - (func $wasi.clock_time_get (param $id i32) (param $precision i64) (param $result.timestamp i32) (result (;errno;) i32)))` - // functionFdAdvise provides file advisory information on a file descriptor. // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_advisefd-fd-offset-filesize-len-filesize-advice-advice---errno functionFdAdvise = "fd_advise" - // importFdAdvise is the WebAssembly 1.0 (20191205) Text format import of functionFdAdvise. + // importFdAdvise is the WebAssembly 1.0 Text format import of functionFdAdvise. importFdAdvise = `(import "wasi_snapshot_preview1" "fd_advise" (func $wasi.fd_advise (param $fd i32) (param $offset i64) (param $len i64) (param $result.advice i32) (result (;errno;) i32)))` @@ -141,7 +124,7 @@ const ( // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_allocatefd-fd-offset-filesize-len-filesize---errno functionFdAllocate = "fd_allocate" - // importFdAllocate is the WebAssembly 1.0 (20191205) Text format import of functionFdAllocate. + // importFdAllocate is the WebAssembly 1.0 Text format import of functionFdAllocate. importFdAllocate = `(import "wasi_snapshot_preview1" "fd_allocate" (func $wasi.fd_allocate (param $fd i32) (param $offset i64) (param $len i64) (result (;errno;) i32)))` @@ -149,7 +132,7 @@ const ( // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_close functionFdClose = "fd_close" - // importFdClose is the WebAssembly 1.0 (20191205) Text format import of functionFdClose. + // importFdClose is the WebAssembly 1.0 Text format import of functionFdClose. importFdClose = `(import "wasi_snapshot_preview1" "fd_close" (func $wasi.fd_close (param $fd i32) (result (;errno;) i32)))` @@ -157,7 +140,7 @@ const ( // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_close functionFdDatasync = "fd_datasync" - // importFdDatasync is the WebAssembly 1.0 (20191205) Text format import of functionFdDatasync. + // importFdDatasync is the WebAssembly 1.0 Text format import of functionFdDatasync. importFdDatasync = `(import "wasi_snapshot_preview1" "fd_datasync" (func $wasi.fd_datasync (param $fd i32) (result (;errno;) i32)))` @@ -165,15 +148,15 @@ const ( // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_fdstat_getfd-fd---errno-fdstat functionFdFdstatGet = "fd_fdstat_get" - // importFdFdstatGet is the WebAssembly 1.0 (20191205) Text format import of functionFdFdstatGet. - importFdFdstatGet = `(import "wasi_snapshot_preview1" "fd_fdstat_get" + // importFdFdstatGet is the WebAssembly 1.0 Text format import of functionFdFdstatGet. + _ = /* importFdFdstatGet */ `(import "wasi_snapshot_preview1" "fd_fdstat_get" (func $wasi.fd_fdstat_get (param $fd i32) (param $result.stat i32) (result (;errno;) i32)))` //nolint // functionFdFdstatSetFlags adjusts the flags associated with a file descriptor. // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_fdstat_set_flagsfd-fd-flags-fdflags---errno functionFdFdstatSetFlags = "fd_fdstat_set_flags" - // importFdFdstatSetFlags is the WebAssembly 1.0 (20191205) Text format import of functionFdFdstatSetFlags. + // importFdFdstatSetFlags is the WebAssembly 1.0 Text format import of functionFdFdstatSetFlags. importFdFdstatSetFlags = `(import "wasi_snapshot_preview1" "fd_fdstat_set_flags" (func $wasi.fd_fdstat_set_flags (param $fd i32) (param $flags i32) (result (;errno;) i32)))` @@ -181,7 +164,7 @@ const ( // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_fdstat_set_rightsfd-fd-fs_rights_base-rights-fs_rights_inheriting-rights---errno functionFdFdstatSetRights = "fd_fdstat_set_rights" - // importFdFdstatSetRights is the WebAssembly 1.0 (20191205) Text format import of functionFdFdstatSetRights. + // importFdFdstatSetRights is the WebAssembly 1.0 Text format import of functionFdFdstatSetRights. importFdFdstatSetRights = `(import "wasi_snapshot_preview1" "fd_fdstat_set_rights" (func $wasi.fd_fdstat_set_rights (param $fd i32) (param $fs_rights_base i64) (param $fs_rights_inheriting i64) (result (;errno;) i32)))` @@ -189,7 +172,7 @@ const ( // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_filestat_getfd-fd---errno-filestat functionFdFilestatGet = "fd_filestat_get" - // importFdFilestatGet is the WebAssembly 1.0 (20191205) Text format import of functionFdFilestatGet. + // importFdFilestatGet is the WebAssembly 1.0 Text format import of functionFdFilestatGet. importFdFilestatGet = `(import "wasi_snapshot_preview1" "fd_filestat_get" (func $wasi.fd_filestat_get (param $fd i32) (param $result.buf i32) (result (;errno;) i32)))` @@ -197,7 +180,7 @@ const ( // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_filestat_set_sizefd-fd-size-filesize---errno functionFdFilestatSetSize = "fd_filestat_set_size" - // importFdFilestatSetSize is the WebAssembly 1.0 (20191205) Text format import of functionFdFilestatSetSize. + // importFdFilestatSetSize is the WebAssembly 1.0 Text format import of functionFdFilestatSetSize. importFdFilestatSetSize = `(import "wasi_snapshot_preview1" "fd_filestat_set_size" (func $wasi.fd_filestat_set_size (param $fd i32) (param $size i64) (result (;errno;) i32)))` @@ -205,7 +188,7 @@ const ( // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_filestat_set_timesfd-fd-atim-timestamp-mtim-timestamp-fst_flags-fstflags---errno functionFdFilestatSetTimes = "fd_filestat_set_times" - // importFdFilestatSetTimes is the WebAssembly 1.0 (20191205) Text format import of functionFdFilestatSetTimes. + // importFdFilestatSetTimes is the WebAssembly 1.0 Text format import of functionFdFilestatSetTimes. importFdFilestatSetTimes = `(import "wasi_snapshot_preview1" "fd_filestat_set_times" (func $wasi.fd_filestat_set_times (param $fd i32) (param $atim i64) (param $mtim i64) (param $fst_flags i32) (result (;errno;) i32)))` @@ -213,7 +196,7 @@ const ( // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_preadfd-fd-iovs-iovec_array-offset-filesize---errno-size functionFdPread = "fd_pread" - // importFdPread is the WebAssembly 1.0 (20191205) Text format import of functionFdPread. + // importFdPread is the WebAssembly 1.0 Text format import of functionFdPread. importFdPread = `(import "wasi_snapshot_preview1" "fd_pread" (func $wasi.fd_pread (param $fd i32) (param $iovs i32) (param $iovs_len i32) (param $offset i64) (param $result.nread i32) (result (;errno;) i32)))` @@ -221,7 +204,7 @@ const ( // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_prestat_get functionFdPrestatGet = "fd_prestat_get" - // importFdPrestatGet is the WebAssembly 1.0 (20191205) Text format import of functionFdPrestatGet. + // importFdPrestatGet is the WebAssembly 1.0 Text format import of functionFdPrestatGet. importFdPrestatGet = `(import "wasi_snapshot_preview1" "fd_prestat_get" (func $wasi.fd_prestat_get (param $fd i32) (param $result.prestat i32) (result (;errno;) i32)))` @@ -229,7 +212,7 @@ const ( // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_prestat_dir_name functionFdPrestatDirName = "fd_prestat_dir_name" - // importFdPrestatDirName is the WebAssembly 1.0 (20191205) Text format import of functionFdPrestatDirName. + // importFdPrestatDirName is the WebAssembly 1.0 Text format import of functionFdPrestatDirName. importFdPrestatDirName = `(import "wasi_snapshot_preview1" "fd_prestat_dir_name" (func $wasi.fd_prestat_dir_name (param $fd i32) (param $path i32) (param $path_len i32) (result (;errno;) i32)))` @@ -237,7 +220,7 @@ const ( // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_pwritefd-fd-iovs-ciovec_array-offset-filesize---errno-size functionFdPwrite = "fd_pwrite" - // importFdPwrite is the WebAssembly 1.0 (20191205) Text format import of functionFdPwrite. + // importFdPwrite is the WebAssembly 1.0 Text format import of functionFdPwrite. importFdPwrite = `(import "wasi_snapshot_preview1" "fd_pwrite" (func $wasi.fd_pwrite (param $fd i32) (param $iovs i32) (param $iovs_len i32) (param $offset i64) (param $result.nwritten i32) (result (;errno;) i32)))` @@ -245,7 +228,7 @@ const ( // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_read functionFdRead = "fd_read" - // importFdRead is the WebAssembly 1.0 (20191205) Text format import of functionFdRead. + // importFdRead is the WebAssembly 1.0 Text format import of functionFdRead. importFdRead = `(import "wasi_snapshot_preview1" "fd_read" (func $wasi.fd_read (param $fd i32) (param $iovs i32) (param $iovs_len i32) (param $result.size i32) (result (;errno;) i32)))` @@ -253,7 +236,7 @@ const ( // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_readdirfd-fd-buf-pointeru8-buf_len-size-cookie-dircookie---errno-size functionFdReaddir = "fd_readdir" - // importFdReaddir is the WebAssembly 1.0 (20191205) Text format import of functionFdReaddir. + // importFdReaddir is the WebAssembly 1.0 Text format import of functionFdReaddir. importFdReaddir = `(import "wasi_snapshot_preview1" "fd_readdir" (func $wasi.fd_readdir (param $fd i32) (param $buf i32) (param $buf_len i32) (param $cookie i64) (param $result.bufused i32) (result (;errno;) i32)))` @@ -261,7 +244,7 @@ const ( // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_renumberfd-fd-to-fd---errno functionFdRenumber = "fd_renumber" - // importFdRenumber is the WebAssembly 1.0 (20191205) Text format import of functionFdRenumber. + // importFdRenumber is the WebAssembly 1.0 Text format import of functionFdRenumber. importFdRenumber = `(import "wasi_snapshot_preview1" "fd_renumber" (func $wasi.fd_renumber (param $fd i32) (param $to i32) (result (;errno;) i32)))` @@ -269,7 +252,7 @@ const ( // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_seekfd-fd-offset-filedelta-whence-whence---errno-filesize functionFdSeek = "fd_seek" - // importFdSeek is the WebAssembly 1.0 (20191205) Text format import of functionFdSeek. + // importFdSeek is the WebAssembly 1.0 Text format import of functionFdSeek. importFdSeek = `(import "wasi_snapshot_preview1" "fd_seek" (func $wasi.fd_seek (param $fd i32) (param $offset i64) (param $whence i32) (param $result.newoffset i32) (result (;errno;) i32)))` @@ -277,7 +260,7 @@ const ( // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_syncfd-fd---errno functionFdSync = "fd_sync" - // importFdSync is the WebAssembly 1.0 (20191205) Text format import of functionFdSync. + // importFdSync is the WebAssembly 1.0 Text format import of functionFdSync. importFdSync = `(import "wasi_snapshot_preview1" "fd_sync" (func $wasi.fd_sync (param $fd i32) (result (;errno;) i32)))` @@ -285,7 +268,7 @@ const ( // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_tellfd-fd---errno-filesize functionFdTell = "fd_tell" - // importFdTell is the WebAssembly 1.0 (20191205) Text format import of functionFdTell. + // importFdTell is the WebAssembly 1.0 Text format import of functionFdTell. importFdTell = `(import "wasi_snapshot_preview1" "fd_tell" (func $wasi.fd_tell (param $fd i32) (param $result.offset i32) (result (;errno;) i32)))` @@ -293,7 +276,7 @@ const ( // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_write functionFdWrite = "fd_write" - // importFdWrite is the WebAssembly 1.0 (20191205) Text format import of functionFdWrite. + // importFdWrite is the WebAssembly 1.0 Text format import of functionFdWrite. importFdWrite = `(import "wasi_snapshot_preview1" "fd_write" (func $wasi.fd_write (param $fd i32) (param $iovs i32) (param $iovs_len i32) (param $result.size i32) (result (;errno;) i32)))` @@ -301,7 +284,7 @@ const ( // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_create_directoryfd-fd-path-string---errno functionPathCreateDirectory = "path_create_directory" - // importPathCreateDirectory is the WebAssembly 1.0 (20191205) Text format import of functionPathCreateDirectory. + // importPathCreateDirectory is the WebAssembly 1.0 Text format import of functionPathCreateDirectory. importPathCreateDirectory = `(import "wasi_snapshot_preview1" "path_create_directory" (func $wasi.path_create_directory (param $fd i32) (param $path i32) (param $path_len i32) (result (;errno;) i32)))` @@ -309,7 +292,7 @@ const ( // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_filestat_getfd-fd-flags-lookupflags-path-string---errno-filestat functionPathFilestatGet = "path_filestat_get" - // importPathFilestatGet is the WebAssembly 1.0 (20191205) Text format import of functionPathFilestatGet. + // importPathFilestatGet is the WebAssembly 1.0 Text format import of functionPathFilestatGet. importPathFilestatGet = `(import "wasi_snapshot_preview1" "path_filestat_get" (func $wasi.path_filestat_get (param $fd i32) (param $flags i32) (param $path i32) (param $path_len i32) (param $result.buf i32) (result (;errno;) i32)))` @@ -317,7 +300,7 @@ const ( // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_filestat_set_timesfd-fd-flags-lookupflags-path-string-atim-timestamp-mtim-timestamp-fst_flags-fstflags---errno functionPathFilestatSetTimes = "path_filestat_set_times" - // importPathFilestatSetTimes is the WebAssembly 1.0 (20191205) Text format import of functionPathFilestatSetTimes. + // importPathFilestatSetTimes is the WebAssembly 1.0 Text format import of functionPathFilestatSetTimes. importPathFilestatSetTimes = `(import "wasi_snapshot_preview1" "path_filestat_set_times" (func $wasi.path_filestat_set_times (param $fd i32) (param $flags i32) (param $path i32) (param $path_len i32) (param $atim i64) (param $mtim i64) (param $fst_flags i32) (result (;errno;) i32)))` @@ -325,7 +308,7 @@ const ( // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#path_link functionPathLink = "path_link" - // importPathLink is the WebAssembly 1.0 (20191205) Text format import of functionPathLink. + // importPathLink is the WebAssembly 1.0 Text format import of functionPathLink. importPathLink = `(import "wasi_snapshot_preview1" "path_link" (func $wasi.path_link (param $old_fd i32) (param $old_flags i32) (param $old_path i32) (param $old_path_len i32) (param $new_fd i32) (param $new_path i32) (param $new_path_len i32) (result (;errno;) i32)))` @@ -333,7 +316,7 @@ const ( // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_openfd-fd-dirflags-lookupflags-path-string-oflags-oflags-fs_rights_base-rights-fs_rights_inheriting-rights-fdflags-fdflags---errno-fd functionPathOpen = "path_open" - // importPathOpen is the WebAssembly 1.0 (20191205) Text format import of functionPathOpen. + // importPathOpen is the WebAssembly 1.0 Text format import of functionPathOpen. importPathOpen = `(import "wasi_snapshot_preview1" "path_open" (func $wasi.path_open (param $fd i32) (param $dirflags i32) (param $path i32) (param $path_len i32) (param $oflags i32) (param $fs_rights_base i64) (param $fs_rights_inheriting i64) (param $fdflags i32) (param $result.opened_fd i32) (result (;errno;) i32)))` @@ -341,7 +324,7 @@ const ( // See: https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_readlinkfd-fd-path-string-buf-pointeru8-buf_len-size---errno-size functionPathReadlink = "path_readlink" - // importPathReadlink is the WebAssembly 1.0 (20191205) Text format import of functionPathReadlink. + // importPathReadlink is the WebAssembly 1.0 Text format import of functionPathReadlink. importPathReadlink = `(import "wasi_snapshot_preview1" "path_readlink" (func $wasi.path_readlink (param $fd i32) (param $path i32) (param $path_len i32) (param $buf i32) (param $buf_len i32) (param $result.bufused i32) (result (;errno;) i32)))` @@ -349,7 +332,7 @@ const ( // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_remove_directoryfd-fd-path-string---errno functionPathRemoveDirectory = "path_remove_directory" - // importPathRemoveDirectory is the WebAssembly 1.0 (20191205) Text format import of functionPathRemoveDirectory. + // importPathRemoveDirectory is the WebAssembly 1.0 Text format import of functionPathRemoveDirectory. importPathRemoveDirectory = `(import "wasi_snapshot_preview1" "path_remove_directory" (func $wasi.path_remove_directory (param $fd i32) (param $path i32) (param $path_len i32) (result (;errno;) i32)))` @@ -357,7 +340,7 @@ const ( // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_renamefd-fd-old_path-string-new_fd-fd-new_path-string---errno functionPathRename = "path_rename" - // importPathRename is the WebAssembly 1.0 (20191205) Text format import of functionPathRename. + // importPathRename is the WebAssembly 1.0 Text format import of functionPathRename. importPathRename = `(import "wasi_snapshot_preview1" "path_rename" (func $wasi.path_rename (param $fd i32) (param $old_path i32) (param $old_path_len i32) (param $new_fd i32) (param $new_path i32) (param $new_path_len i32) (result (;errno;) i32)))` @@ -365,7 +348,7 @@ const ( // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#path_symlink functionPathSymlink = "path_symlink" - // importPathSymlink is the WebAssembly 1.0 (20191205) Text format import of functionPathSymlink. + // importPathSymlink is the WebAssembly 1.0 Text format import of functionPathSymlink. importPathSymlink = `(import "wasi_snapshot_preview1" "path_symlink" (func $wasi.path_symlink (param $old_path i32) (param $old_path_len i32) (param $fd i32) (param $new_path i32) (param $new_path_len i32) (result (;errno;) i32)))` @@ -373,15 +356,15 @@ const ( // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_unlink_filefd-fd-path-string---errno functionPathUnlinkFile = "path_unlink_file" - // importPathUnlinkFile is the WebAssembly 1.0 (20191205) Text format import of functionPathUnlinkFile. + // importPathUnlinkFile is the WebAssembly 1.0 Text format import of functionPathUnlinkFile. importPathUnlinkFile = `(import "wasi_snapshot_preview1" "path_unlink_file" (func $wasi.path_unlink_file (param $fd i32) (param $path i32) (param $path_len i32) (result (;errno;) i32)))` - // functionPollOneoff unlinks a file. + // functionPollOneoff concurrently polls for the occurrence of a set of events. // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-poll_oneoffin-constpointersubscription-out-pointerevent-nsubscriptions-size---errno-size functionPollOneoff = "poll_oneoff" - // importPollOneoff is the WebAssembly 1.0 (20191205) Text format import of functionPollOneoff. + // importPollOneoff is the WebAssembly 1.0 Text format import of functionPollOneoff. importPollOneoff = `(import "wasi_snapshot_preview1" "poll_oneoff" (func $wasi.poll_oneoff (param $in i32) (param $out i32) (param $nsubscriptions i32) (param $result.nevents i32) (result (;errno;) i32)))` @@ -389,12 +372,7 @@ const ( // See https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#proc_exit functionProcExit = "proc_exit" - // importProcExit is the WebAssembly 1.0 (20191205) Text format import of functionProcExit. - // - // See importProcExit - // See wasi.ProcExit - // See functionProcExit - // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#proc_exit + // importProcExit is the WebAssembly 1.0 Text format import of functionProcExit. importProcExit = `(import "wasi_snapshot_preview1" "proc_exit" (func $wasi.proc_exit (param $rval i32)))` @@ -402,7 +380,7 @@ const ( // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-proc_raisesig-signal---errno functionProcRaise = "proc_raise" - // importProcRaise is the WebAssembly 1.0 (20191205) Text format import of functionProcRaise. + // importProcRaise is the WebAssembly 1.0 Text format import of functionProcRaise. importProcRaise = `(import "wasi_snapshot_preview1" "proc_raise" (func $wasi.proc_raise (param $sig i32) (result (;errno;) i32)))` @@ -410,7 +388,7 @@ const ( // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-sched_yield---errno functionSchedYield = "sched_yield" - // importSchedYield is the WebAssembly 1.0 (20191205) Text format import of functionSchedYield. + // importSchedYield is the WebAssembly 1.0 Text format import of functionSchedYield. importSchedYield = `(import "wasi_snapshot_preview1" "sched_yield" (func $wasi.sched_yield (result (;errno;) i32)))` @@ -418,7 +396,7 @@ const ( // See: https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-random_getbuf-pointeru8-buf_len-size---errno functionRandomGet = "random_get" - // importRandomGet is the WebAssembly 1.0 (20191205) Text format import of functionRandomGet. + // importRandomGet is the WebAssembly 1.0 Text format import of functionRandomGet. importRandomGet = `(import "wasi_snapshot_preview1" "random_get" (func $wasi.random_get (param $buf i32) (param $buf_len i32) (result (;errno;) i32)))` @@ -426,7 +404,7 @@ const ( // 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 functionSockRecv = "sock_recv" - // importSockRecv is the WebAssembly 1.0 (20191205) Text format import of functionSockRecv. + // importSockRecv is the WebAssembly 1.0 Text format import of functionSockRecv. importSockRecv = `(import "wasi_snapshot_preview1" "sock_recv" (func $wasi.sock_recv (param $fd i32) (param $ri_data i32) (param $ri_data_count i32) (param $ri_flags i32) (param $result.ro_datalen i32) (param $result.ro_flags i32) (result (;errno;) i32)))` @@ -434,7 +412,7 @@ const ( // 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 functionSockSend = "sock_send" - // importSockSend is the WebAssembly 1.0 (20191205) Text format import of functionSockSend. + // importSockSend is the WebAssembly 1.0 Text format import of functionSockSend. importSockSend = `(import "wasi_snapshot_preview1" "sock_send" (func $wasi.sock_send (param $fd i32) (param $si_data i32) (param $si_data_count i32) (param $si_flags i32) (param $result.so_datalen i32) (result (;errno;) i32)))` @@ -442,29 +420,35 @@ const ( // See: https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-sock_shutdownfd-fd-how-sdflags---errno functionSockShutdown = "sock_shutdown" - // importSockShutdown is the WebAssembly 1.0 (20191205) Text format import of functionSockShutdown. + // importSockShutdown is the WebAssembly 1.0 Text format import of functionSockShutdown. importSockShutdown = `(import "wasi_snapshot_preview1" "sock_shutdown" (func $wasi.sock_shutdown (param $fd i32) (param $how i32) (result (;errno;) i32)))` ) +const ( + fdStdin = iota + fdStdout + fdStderr +) + // wasi includes all host functions to export for WASI version "wasi_snapshot_preview1". // // ## Translation notes // ### String -// WebAssembly 1.0 (20191205) has no string type, so any string input parameter expands to two uint32 parameters: offset +// 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 wasi_snapshot_preview1.Errno is always an uint32 parameter. WebAssembly 1.0 (20191205) can have up to one result, -// which is already used by wasi_snapshot_preview1.Errno. This forces other results to be parameters. A result parameter is a memory +// 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. -// wasi_snapshot_preview1.Errno mappings are not defined in WASI, yet, so these mappings are best efforts by maintainers. When in doubt +// 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 // @@ -554,7 +538,7 @@ func wasiFunctions() map[string]interface{} { // offset that begins "a" --+ | // offset that begins "bc" --+ // -// Note: importArgsGet shows this signature in the WebAssembly 1.0 (20191205) Text Format. +// Note: importArgsGet shows this signature in the WebAssembly 1.0 Text Format. // 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 @@ -585,7 +569,7 @@ func (a *wasi) ArgsGet(ctx context.Context, mod api.Module, argv, argvBuf uint32 // resultArgvBufSize --| // len([]byte{'a',0,'b',c',0}) --+ // -// Note: importArgsSizesGet shows this signature in the WebAssembly 1.0 (20191205) Text Format. +// Note: importArgsSizesGet shows this signature in the WebAssembly 1.0 Text Format. // 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 @@ -624,7 +608,7 @@ func (a *wasi) ArgsSizesGet(ctx context.Context, mod api.Module, resultArgc, res // environ offset for "a=b" --+ | // environ offset for "b=cd" --+ // -// Note: importEnvironGet shows this signature in the WebAssembly 1.0 (20191205) Text Format. +// Note: importEnvironGet shows this signature in the WebAssembly 1.0 Text Format. // 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 @@ -656,7 +640,7 @@ func (a *wasi) EnvironGet(ctx context.Context, mod api.Module, environ uint32, e // len([]byte{'a','=','b',0, | // 'b','=','c','d',0}) --+ // -// Note: importEnvironGet shows this signature in the WebAssembly 1.0 (20191205) Text Format. +// Note: importEnvironGet shows this signature in the WebAssembly 1.0 Text Format. // See EnvironGet // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#environ_sizes_get // See https://en.wikipedia.org/wiki/Null-terminated_string @@ -674,87 +658,6 @@ func (a *wasi) EnvironSizesGet(ctx context.Context, mod api.Module, resultEnviro return ErrnoSuccess } -// ClockResGet is the WASI function named functionClockResGet that returns the resolution of time values returned by ClockTimeGet. -// -// * id - The clock id for which to return the time. -// * resultResolution - the offset to write the resolution to mod.Memory -// * the resolution is an uint64 little-endian encoding. -// -// For example, if the resolution is 100ns, this function writes the below to `mod.Memory`: -// -// uint64le -// +-------------------------------------+ -// | | -// []byte{?, 0x64, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, ?} -// resultResolution --^ -// -// Note: importClockResGet shows this signature in the WebAssembly 1.0 (20191205) Text Format. -// 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 -func (a *wasi) ClockResGet(ctx context.Context, mod api.Module, id uint32, resultResolution uint32) Errno { - sysCtx := getSysCtx(mod) - - var resolution uint64 // ns - switch id { - case clockIDRealtime: - resolution = uint64(sysCtx.WalltimeResolution()) - case clockIDMonotonic: - resolution = uint64(sysCtx.NanotimeResolution()) - default: - // Similar to many other runtimes, we only support realtime and monotonic clocks. Other types - // are slated to be removed from the next version of WASI. - return ErrnoNosys - } - if !mod.Memory().WriteUint64Le(ctx, resultResolution, resolution) { - return ErrnoFault - } - return ErrnoSuccess -} - -// ClockTimeGet is the WASI function named functionClockTimeGet that returns the time value of a clock (time.Now). -// -// * id - The clock id for which to return the time. -// * precision - The maximum lag (exclusive) that the returned time value may have, compared to its actual value. -// * resultTimestamp - the offset to write the timestamp to mod.Memory -// * the timestamp is epoch nanoseconds encoded as a uint64 little-endian encoding. -// -// For example, if time.Now returned exactly midnight UTC 2022-01-01 (1640995200000000000), and -// parameters resultTimestamp=1, this function writes the below to `mod.Memory`: -// -// uint64le -// +------------------------------------------+ -// | | -// []byte{?, 0x0, 0x0, 0x1f, 0xa6, 0x70, 0xfc, 0xc5, 0x16, ?} -// resultTimestamp --^ -// -// Note: importClockTimeGet shows this signature in the WebAssembly 1.0 (20191205) Text Format. -// 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 -func (a *wasi) ClockTimeGet(ctx context.Context, mod api.Module, id uint32, precision uint64, resultTimestamp uint32) Errno { - // TODO: precision is currently ignored. - sysCtx := getSysCtx(mod) - - var val uint64 - switch id { - case clockIDRealtime: - sec, nsec := sysCtx.Walltime(ctx) - val = (uint64(sec) * uint64(time.Second.Nanoseconds())) + uint64(nsec) - case clockIDMonotonic: - val = uint64(sysCtx.Nanotime(ctx)) - default: - // Similar to many other runtimes, we only support realtime and monotonic clocks. Other types - // are slated to be removed from the next version of WASI. - return ErrnoNosys - } - - if !mod.Memory().WriteUint64Le(ctx, resultTimestamp, val) { - return ErrnoFault - } - return ErrnoSuccess -} - // FdAdvise is the WASI function named functionFdAdvise and is stubbed for GrainLang per #271 func (a *wasi) FdAdvise(ctx context.Context, mod api.Module, fd uint32, offset, len uint64, resultAdvice uint32) Errno { return ErrnoNosys // stubbed for GrainLang per #271 @@ -769,14 +672,12 @@ func (a *wasi) FdAllocate(ctx context.Context, mod api.Module, fd uint32, offset // // * fd - the file descriptor to close // -// Note: importFdClose shows this signature in the WebAssembly 1.0 (20191205) Text Format. +// Note: importFdClose shows this signature in the WebAssembly 1.0 Text Format. // Note: This is similar to `close` in POSIX. // See https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#fd_close // See https://linux.die.net/man/3/close func (a *wasi) FdClose(ctx context.Context, mod api.Module, fd uint32) Errno { - _, fsc := sysFSCtx(ctx, mod) - - if ok, err := fsc.CloseFile(fd); err != nil { + if ok, err := getSysCtx(mod).FS(ctx).CloseFile(fd); err != nil { return ErrnoIo } else if !ok { return ErrnoBadf @@ -795,9 +696,9 @@ func (a *wasi) FdDatasync(ctx context.Context, mod api.Module, fd uint32) Errno // * fd - the file descriptor to get the fdstat attributes data // * resultFdstat - the offset to write the result fdstat data // -// The wasi_snapshot_preview1.Errno returned is wasi_snapshot_preview1.ErrnoSuccess except the following error conditions: -// * wasi_snapshot_preview1.ErrnoBadf - if `fd` is invalid -// * wasi_snapshot_preview1.ErrnoFault - if `resultFdstat` contains an invalid offset due to the memory constraint +// The Errno returned is ErrnoSuccess except the following error conditions: +// * ErrnoBadf - if `fd` is invalid +// * ErrnoFault - if `resultFdstat` contains an invalid offset due to the memory constraint // // fdstat byte layout is 24-byte size, which as the following elements in order // * fs_filetype 1 byte, to indicate the file type @@ -817,15 +718,13 @@ func (a *wasi) FdDatasync(ctx context.Context, mod api.Module, fd uint32) Errno // | // +-- fs_filetype // -// Note: importFdFdstatGet shows this signature in the WebAssembly 1.0 (20191205) Text Format. +// Note: importFdFdstatGet shows this signature in the WebAssembly 1.0 Text Format. // Note: FdFdstatGet returns similar flags to `fsync(fd, F_GETFL)` in POSIX, as well as additional fields. // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fdstat // See https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#fd_fdstat_get // See https://linux.die.net/man/3/fsync func (a *wasi) FdFdstatGet(ctx context.Context, mod api.Module, fd uint32, resultStat uint32) Errno { - _, fsc := sysFSCtx(ctx, mod) - - if _, ok := fsc.OpenedFile(fd); !ok { + if _, ok := getSysCtx(mod).FS(ctx).OpenedFile(fd); !ok { return ErrnoBadf } return ErrnoSuccess @@ -836,9 +735,9 @@ func (a *wasi) FdFdstatGet(ctx context.Context, mod api.Module, fd uint32, resul // * fd - the file descriptor to get the prestat // * resultPrestat - the offset to write the result prestat data // -// The wasi_snapshot_preview1.Errno returned is wasi_snapshot_preview1.ErrnoSuccess except the following error conditions: -// * wasi_snapshot_preview1.ErrnoBadf - if `fd` is invalid or the `fd` is not a pre-opened directory. -// * wasi_snapshot_preview1.ErrnoFault - if `resultPrestat` is an invalid offset due to the memory constraint +// The Errno returned is ErrnoSuccess except the following error conditions: +// * ErrnoBadf - if `fd` is invalid or the `fd` is not a pre-opened directory. +// * ErrnoFault - if `resultPrestat` is an invalid offset due to the memory constraint // // prestat byte layout is 8 bytes, beginning with an 8-bit tag and 3 pad bytes. The only valid tag is `prestat_dir`, // which is tag zero. This simplifies the byte layout to 4 empty bytes followed by the uint32le encoded path length. @@ -854,14 +753,12 @@ func (a *wasi) FdFdstatGet(ctx context.Context, mod api.Module, fd uint32, resul // tag --+ | // +-- size in bytes of the string "/tmp" // -// Note: importFdPrestatGet shows this signature in the WebAssembly 1.0 (20191205) Text Format. +// Note: importFdPrestatGet shows this signature in the WebAssembly 1.0 Text Format. // See FdPrestatDirName // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#prestat // See https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#fd_prestat_get func (a *wasi) FdPrestatGet(ctx context.Context, mod api.Module, fd uint32, resultPrestat uint32) Errno { - _, fsc := sysFSCtx(ctx, mod) - - entry, ok := fsc.OpenedFile(fd) + entry, ok := getSysCtx(mod).FS(ctx).OpenedFile(fd) if !ok { return ErrnoBadf } @@ -916,10 +813,10 @@ func (a *wasi) FdPread(ctx context.Context, mod api.Module, fd, iovs, iovsCount // * pathLen - the count of bytes to write to `path` // * This should match the uint32le FdPrestatGet writes to offset `resultPrestat`+4 // -// The wasi_snapshot_preview1.Errno returned is wasi_snapshot_preview1.ErrnoSuccess except the following error conditions: -// * wasi_snapshot_preview1.ErrnoBadf - if `fd` is invalid -// * wasi_snapshot_preview1.ErrnoFault - if `path` is an invalid offset due to the memory constraint -// * wasi_snapshot_preview1.ErrnoNametoolong - if `pathLen` is longer than the actual length of the result path +// The Errno returned is ErrnoSuccess except the following error conditions: +// * ErrnoBadf - if `fd` is invalid +// * ErrnoFault - if `path` is an invalid offset due to the memory constraint +// * ErrnoNametoolong - if `pathLen` is longer than the actual length of the result path // // For example, the directory name corresponding with `fd` was "/tmp" and // parameters path=1 pathLen=4 (correct), this function will write the below to `mod.Memory`: @@ -930,13 +827,11 @@ func (a *wasi) FdPread(ctx context.Context, mod api.Module, fd, iovs, iovsCount // []byte{?, '/', 't', 'm', 'p', ?} // path --^ // -// Note: importFdPrestatDirName shows this signature in the WebAssembly 1.0 (20191205) Text Format. +// Note: importFdPrestatDirName shows this signature in the WebAssembly 1.0 Text Format. // See FdPrestatGet // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_prestat_dir_name func (a *wasi) FdPrestatDirName(ctx context.Context, mod api.Module, fd uint32, pathPtr uint32, pathLen uint32) Errno { - _, fsc := sysFSCtx(ctx, mod) - - f, ok := fsc.OpenedFile(fd) + f, ok := getSysCtx(mod).FS(ctx).OpenedFile(fd) if !ok { return ErrnoBadf } @@ -966,10 +861,10 @@ func (a *wasi) FdPwrite(ctx context.Context, mod api.Module, fd, iovs, iovsCount // * iovsCount - the count of memory offset, size pairs to read sequentially starting at iovs. // * resultSize - the offset in `mod.Memory` to write the number of bytes read // -// The wasi_snapshot_preview1.Errno returned is wasi_snapshot_preview1.ErrnoSuccess except the following error conditions: -// * wasi_snapshot_preview1.ErrnoBadf - if `fd` is invalid -// * wasi_snapshot_preview1.ErrnoFault - if `iovs` or `resultSize` contain an invalid offset due to the memory constraint -// * wasi_snapshot_preview1.ErrnoIo - if an IO related error happens during the operation +// The Errno returned is ErrnoSuccess except the following error conditions: +// * ErrnoBadf - if `fd` is invalid +// * ErrnoFault - if `iovs` or `resultSize` contain an invalid offset due to the memory constraint +// * ErrnoIo - if an IO related error happens during the operation // // For example, this function needs to first read `iovs` to determine where to write contents. If // parameters iovs=1 iovsCount=2, this function reads two offset/length pairs from `mod.Memory`: @@ -995,23 +890,16 @@ func (a *wasi) FdPwrite(ctx context.Context, mod api.Module, fd, iovs, iovsCount // iovs[1].offset --+ | // resultSize --+ // -// Note: importFdRead shows this signature in the WebAssembly 1.0 (20191205) Text Format. +// Note: importFdRead shows this signature in the WebAssembly 1.0 Text Format. // Note: This is similar to `readv` in POSIX. // See FdWrite // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_read // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#iovec // See https://linux.die.net/man/3/readv func (a *wasi) FdRead(ctx context.Context, mod api.Module, fd, iovs, iovsCount, resultSize uint32) Errno { - sysCtx, fsCtx := sysFSCtx(ctx, mod) - - var reader io.Reader - - if fd == fdStdin { - reader = sysCtx.Stdin() - } else if f, ok := fsCtx.OpenedFile(fd); !ok || f.File == nil { + reader := fdReader(ctx, mod, fd) + if reader == nil { return ErrnoBadf - } else { - reader = f.File } var nread uint32 @@ -1063,11 +951,11 @@ func (a *wasi) FdRenumber(ctx context.Context, mod api.Module, fd, to uint32) Er // * If io.SeekEnd, new offset == file size of `fd` + `offset`. // * resultNewoffset: the offset in `mod.Memory` to write the new offset to, relative to start of the file // -// The wasi_snapshot_preview1.Errno returned is wasi_snapshot_preview1.ErrnoSuccess except the following error conditions: -// * wasi_snapshot_preview1.ErrnoBadf - if `fd` is invalid -// * wasi_snapshot_preview1.ErrnoFault - if `resultNewoffset` is an invalid offset in `mod.Memory` due to the memory constraint -// * wasi_snapshot_preview1.ErrnoInval - if `whence` is an invalid value -// * wasi_snapshot_preview1.ErrnoIo - if other error happens during the operation of the underying file system +// The Errno returned is ErrnoSuccess except the following error conditions: +// * ErrnoBadf - if `fd` is invalid +// * ErrnoFault - if `resultNewoffset` is an invalid offset in `mod.Memory` due to the memory constraint +// * ErrnoInval - if `whence` is an invalid value +// * ErrnoIo - if other error happens during the operation of the underying file system // // For example, if fd 3 is a file with offset 0, and // parameters fd=3, offset=4, whence=0 (=io.SeekStart), resultNewOffset=1, @@ -1080,16 +968,14 @@ func (a *wasi) FdRenumber(ctx context.Context, mod api.Module, fd, to uint32) Er // resultNewoffset --^ // // See io.Seeker -// Note: importFdSeek shows this signature in the WebAssembly 1.0 (20191205) Text Format. +// Note: importFdSeek shows this signature in the WebAssembly 1.0 Text Format. // Note: This is similar to `lseek` in POSIX. // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_seek // See https://linux.die.net/man/3/lseek func (a *wasi) FdSeek(ctx context.Context, mod api.Module, fd uint32, offset uint64, whence uint32, resultNewoffset uint32) Errno { - _, fsc := sysFSCtx(ctx, mod) - var seeker io.Seeker // Check to see if the file descriptor is available - if f, ok := fsc.OpenedFile(fd); !ok || f.File == nil { + if f, ok := getSysCtx(mod).FS(ctx).OpenedFile(fd); !ok || f.File == nil { return ErrnoBadf // fs.FS doesn't declare io.Seeker, but implementations such as os.File implement it. } else if seeker, ok = f.File.(io.Seeker); !ok { @@ -1129,10 +1015,10 @@ func (a *wasi) FdTell(ctx context.Context, mod api.Module, fd, resultOffset uint // * iovsCount - the count of memory offset, size pairs to read sequentially starting at iovs. // * resultSize - the offset in `mod.Memory` to write the number of bytes written // -// The wasi_snapshot_preview1.Errno returned is wasi_snapshot_preview1.ErrnoSuccess except the following error conditions: -// * wasi_snapshot_preview1.ErrnoBadf - if `fd` is invalid -// * wasi_snapshot_preview1.ErrnoFault - if `iovs` or `resultSize` contain an invalid offset due to the memory constraint -// * wasi_snapshot_preview1.ErrnoIo - if an IO related error happens during the operation +// The Errno returned is ErrnoSuccess except the following error conditions: +// * ErrnoBadf - if `fd` is invalid +// * ErrnoFault - if `iovs` or `resultSize` contain an invalid offset due to the memory constraint +// * ErrnoIo - if an IO related error happens during the operation // // For example, this function needs to first read `iovs` to determine what to write to `fd`. If // parameters iovs=1 iovsCount=2, this function reads two offset/length pairs from `mod.Memory`: @@ -1164,30 +1050,16 @@ func (a *wasi) FdTell(ctx context.Context, mod api.Module, fd, resultOffset uint // []byte{ 0..24, ?, 6, 0, 0, 0', ? } // resultSize --^ // -// Note: importFdWrite shows this signature in the WebAssembly 1.0 (20191205) Text Format. +// Note: importFdWrite shows this signature in the WebAssembly 1.0 Text Format. // Note: This is similar to `writev` in POSIX. // See FdRead // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#ciovec // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_write // See https://linux.die.net/man/3/writev func (a *wasi) FdWrite(ctx context.Context, mod api.Module, fd, iovs, iovsCount, resultSize uint32) Errno { - sysCtx, fsCtx := sysFSCtx(ctx, mod) - - var writer io.Writer - - switch fd { - case fdStdout: - writer = sysCtx.Stdout() - case fdStderr: - writer = sysCtx.Stderr() - default: - // Check to see if the file descriptor is available - if f, ok := fsCtx.OpenedFile(fd); !ok || f.File == nil { - return ErrnoBadf - // fs.FS doesn't declare io.Writer, but implementations such as os.File implement it. - } else if writer, ok = f.File.(io.Writer); !ok { - return ErrnoBadf - } + writer := fdWriter(ctx, mod, fd) + if writer == nil { + return ErrnoBadf } var nwritten uint32 @@ -1217,6 +1089,39 @@ func (a *wasi) FdWrite(ctx context.Context, mod api.Module, fd, iovs, iovsCount, return ErrnoSuccess } +// fdReader returns a valid reader for the given file descriptor or nil if ErrnoBadf. +func fdReader(ctx context.Context, mod api.Module, fd uint32) io.Reader { + sysCtx := getSysCtx(mod) + if fd == fdStdin { + return sysCtx.Stdin() + } else if f, ok := sysCtx.FS(ctx).OpenedFile(fd); !ok || f.File == nil { + return nil + } else { + return f.File + } +} + +// fdWriter returns a valid writer for the given file descriptor or nil if ErrnoBadf. +func fdWriter(ctx context.Context, mod api.Module, fd uint32) io.Writer { + sysCtx := getSysCtx(mod) + switch fd { + case fdStdout: + return sysCtx.Stdout() + case fdStderr: + return sysCtx.Stderr() + default: + // Check to see if the file descriptor is available + if f, ok := sysCtx.FS(ctx).OpenedFile(fd); !ok || f.File == nil { + return nil + // fs.FS doesn't declare io.Writer, but implementations such as os.File implement it. + } else if writer, ok := f.File.(io.Writer); !ok { + return nil + } else { + return writer + } + } +} + // PathCreateDirectory is the WASI function named functionPathCreateDirectory func (a *wasi) PathCreateDirectory(ctx context.Context, mod api.Module, fd, path, pathLen uint32) Errno { return ErrnoNosys // stubbed for GrainLang per #271 @@ -1250,13 +1155,13 @@ func (a *wasi) PathLink(ctx context.Context, mod api.Module, oldFd, oldFlags, ol // * resultOpenedFd - the offset in `mod.Memory` to write the newly created file descriptor to. // * The result FD value is guaranteed to be less than 2**31 // -// The wasi_snapshot_preview1.Errno returned is wasi_snapshot_preview1.ErrnoSuccess except the following error conditions: -// * wasi_snapshot_preview1.ErrnoBadf - if `fd` is invalid -// * wasi_snapshot_preview1.ErrnoFault - if `resultOpenedFd` contains an invalid offset due to the memory constraint -// * wasi_snapshot_preview1.ErrnoNoent - if `path` does not exist. -// * wasi_snapshot_preview1.ErrnoExist - if `path` exists, while `oFlags` requires that it must not. -// * wasi_snapshot_preview1.ErrnoNotdir - if `path` is not a directory, while `oFlags` requires that it must be. -// * wasi_snapshot_preview1.ErrnoIo - if other error happens during the operation of the underying file system. +// The Errno returned is ErrnoSuccess except the following error conditions: +// * ErrnoBadf - if `fd` is invalid +// * ErrnoFault - if `resultOpenedFd` contains an invalid offset due to the memory constraint +// * ErrnoNoent - if `path` does not exist. +// * ErrnoExist - if `path` exists, while `oFlags` requires that it must not. +// * ErrnoNotdir - if `path` is not a directory, while `oFlags` requires that it must be. +// * ErrnoIo - if other error happens during the operation of the underying file system. // // For example, this function needs to first read `path` to determine the file to open. // If parameters `path` = 1, `pathLen` = 6, and the path is "wazero", PathOpen reads the path from `mod.Memory`: @@ -1276,7 +1181,7 @@ func (a *wasi) PathLink(ctx context.Context, mod api.Module, oldFd, oldFlags, ol // []byte{ 0..6, ?, 5, 0, 0, 0, ?} // resultOpenedFd --^ // -// Note: importPathOpen shows this signature in the WebAssembly 1.0 (20191205) Text Format. +// Note: importPathOpen shows this signature in the WebAssembly 1.0 Text Format. // Note: This is similar to `openat` in POSIX. // Note: The returned file descriptor is not guaranteed to be the lowest-numbered file // Note: Rights will never be implemented per https://github.com/WebAssembly/WASI/issues/469#issuecomment-1045251844 @@ -1284,8 +1189,7 @@ func (a *wasi) PathLink(ctx context.Context, mod api.Module, oldFd, oldFlags, ol // See https://linux.die.net/man/3/openat func (a *wasi) PathOpen(ctx context.Context, mod api.Module, fd, dirflags, pathPtr, pathLen, oflags uint32, fsRightsBase, fsRightsInheriting uint64, fdflags, resultOpenedFd uint32) (errno Errno) { - _, fsc := sysFSCtx(ctx, mod) - + fsc := getSysCtx(mod).FS(ctx) dir, ok := fsc.OpenedFile(fd) if !ok || dir.FS == nil { return ErrnoBadf @@ -1339,11 +1243,6 @@ func (a *wasi) PathUnlinkFile(ctx context.Context, mod api.Module, fd, path, pat return ErrnoNosys // stubbed for GrainLang per #271 } -// PollOneoff is the WASI function named functionPollOneoff -func (a *wasi) PollOneoff(ctx context.Context, mod api.Module, in, out, nsubscriptions, resultNevents uint32) Errno { - return ErrnoNosys // stubbed for GrainLang per #271 -} - // ProcExit is the WASI function that terminates the execution of the module with an exit code. // An exit code of 0 indicates successful termination. The meanings of other values are not defined by WASI. // @@ -1351,7 +1250,7 @@ func (a *wasi) PollOneoff(ctx context.Context, mod api.Module, in, out, nsubscri // // In wazero, this calls api.Module CloseWithExitCode. // -// Note: importProcExit shows this signature in the WebAssembly 1.0 (20191205) Text Format. +// Note: importProcExit shows this signature in the WebAssembly 1.0 Text Format. // See https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#proc_exit func (a *wasi) ProcExit(ctx context.Context, mod api.Module, exitCode uint32) { _ = mod.CloseWithExitCode(ctx, exitCode) @@ -1380,7 +1279,7 @@ func (a *wasi) SchedYield(mod api.Module) Errno { // []byte{?, 0x53, 0x8c, 0x7f, 0x96, 0xb1, ?} // buf --^ // -// Note: importRandomGet shows this signature in the WebAssembly 1.0 (20191205) Text Format. +// Note: importRandomGet shows this signature in the WebAssembly 1.0 Text Format. // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-random_getbuf-pointeru8-bufLen-size---errno func (a *wasi) RandomGet(ctx context.Context, mod api.Module, buf uint32, bufLen uint32) (errno Errno) { randSource := getSysCtx(mod).RandSource() @@ -1413,18 +1312,6 @@ func (a *wasi) SockShutdown(ctx context.Context, mod api.Module, fd, how uint32) return ErrnoNosys // stubbed for GrainLang per #271 } -const ( - fdStdin = 0 - fdStdout = 1 - fdStderr = 2 -) - -// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-clockid-enumu32 -const ( - clockIDRealtime = 0 - clockIDMonotonic = 1 -) - func getSysCtx(mod api.Module) *sys.Context { if internal, ok := mod.(*wasm.CallContext); !ok { panic(fmt.Errorf("unsupported wasm.Module implementation: %v", mod)) @@ -1433,22 +1320,6 @@ func getSysCtx(mod api.Module) *sys.Context { } } -func sysFSCtx(ctx context.Context, mod api.Module) (*sys.Context, *sys.FSContext) { - if internal, ok := mod.(*wasm.CallContext); !ok { - panic(fmt.Errorf("unsupported wasm.Module implementation: %v", mod)) - } else { - // Override Context when it is passed via context - if fsValue := ctx.Value(sys.FSKey{}); fsValue != nil { - fsCtx, ok := fsValue.(*sys.FSContext) - if !ok { - panic(fmt.Errorf("unsupported fs key: %v", fsValue)) - } - return internal.Sys, fsCtx - } - return internal.Sys, internal.Sys.FS() - } -} - func openFileEntry(rootFS fs.FS, pathName string) (*sys.FileEntry, Errno) { f, err := rootFS.Open(pathName) if err != nil { diff --git a/wasi_snapshot_preview1/wasi_bench_test.go b/wasi_snapshot_preview1/wasi_bench_test.go index bce80c9a..e4fd409e 100644 --- a/wasi_snapshot_preview1/wasi_bench_test.go +++ b/wasi_snapshot_preview1/wasi_bench_test.go @@ -21,11 +21,11 @@ var testMem = &wasm.MemoryInstance{ }, } -func Test_EnvironGet(t *testing.T) { - sys, err := newSysContext(nil, []string{"a=b", "b=cd"}, nil) +func Test_Benchmark_EnvironGet(t *testing.T) { + sysCtx, err := newSysContext(nil, []string{"a=b", "b=cd"}, nil) require.NoError(t, err) - mod := newModule(make([]byte, 20), sys) + mod := newModule(make([]byte, 20), sysCtx) environGet := (&wasi{}).EnvironGet require.Equal(t, ErrnoSuccess, environGet(testCtx, mod, 11, 1)) @@ -33,7 +33,7 @@ func Test_EnvironGet(t *testing.T) { } func Benchmark_EnvironGet(b *testing.B) { - sys, err := newSysContext(nil, []string{"a=b", "b=cd"}, nil) + sysCtx, err := newSysContext(nil, []string{"a=b", "b=cd"}, nil) if err != nil { b.Fatal(err) } @@ -46,7 +46,7 @@ func Benchmark_EnvironGet(b *testing.B) { 1, 0, 0, 0, // little endian-encoded offset of "a=b" 5, 0, 0, 0, // little endian-encoded offset of "b=cd" 0, - }, sys) + }, sysCtx) environGet := (&wasi{}).EnvironGet b.Run("EnvironGet", func(b *testing.B) { diff --git a/wasi_snapshot_preview1/wasi_test.go b/wasi_snapshot_preview1/wasi_test.go index 9c1c1725..707a1042 100644 --- a/wasi_snapshot_preview1/wasi_test.go +++ b/wasi_snapshot_preview1/wasi_test.go @@ -37,7 +37,7 @@ var testCtx = context.WithValue(context.Background(), struct{}{}, "arbitrary") var a = &wasi{} -func TestSnapshotPreview1_ArgsGet(t *testing.T) { +func Test_ArgsGet(t *testing.T) { sysCtx, err := newSysContext([]string{"a", "bc"}, nil, nil) require.NoError(t, err) @@ -81,7 +81,7 @@ func TestSnapshotPreview1_ArgsGet(t *testing.T) { }) } -func TestSnapshotPreview1_ArgsGet_Errors(t *testing.T) { +func Test_ArgsGet_Errors(t *testing.T) { sysCtx, err := newSysContext([]string{"a", "bc"}, nil, nil) require.NoError(t, err) @@ -131,7 +131,7 @@ func TestSnapshotPreview1_ArgsGet_Errors(t *testing.T) { } } -func TestSnapshotPreview1_ArgsSizesGet(t *testing.T) { +func Test_ArgsSizesGet(t *testing.T) { sysCtx, err := newSysContext([]string{"a", "bc"}, nil, nil) require.NoError(t, err) @@ -174,7 +174,7 @@ func TestSnapshotPreview1_ArgsSizesGet(t *testing.T) { }) } -func TestSnapshotPreview1_ArgsSizesGet_Errors(t *testing.T) { +func Test_ArgsSizesGet_Errors(t *testing.T) { sysCtx, err := newSysContext([]string{"a", "bc"}, nil, nil) require.NoError(t, err) @@ -221,7 +221,7 @@ func TestSnapshotPreview1_ArgsSizesGet_Errors(t *testing.T) { } } -func TestSnapshotPreview1_EnvironGet(t *testing.T) { +func Test_EnvironGet(t *testing.T) { sysCtx, err := newSysContext(nil, []string{"a=b", "b=cd"}, nil) require.NoError(t, err) @@ -266,7 +266,7 @@ func TestSnapshotPreview1_EnvironGet(t *testing.T) { }) } -func TestSnapshotPreview1_EnvironGet_Errors(t *testing.T) { +func Test_EnvironGet_Errors(t *testing.T) { sysCtx, err := newSysContext(nil, []string{"a=bc", "b=cd"}, nil) require.NoError(t, err) @@ -315,7 +315,7 @@ func TestSnapshotPreview1_EnvironGet_Errors(t *testing.T) { } } -func TestSnapshotPreview1_EnvironSizesGet(t *testing.T) { +func Test_EnvironSizesGet(t *testing.T) { sysCtx, err := newSysContext(nil, []string{"a=b", "b=cd"}, nil) require.NoError(t, err) @@ -358,7 +358,7 @@ func TestSnapshotPreview1_EnvironSizesGet(t *testing.T) { }) } -func TestSnapshotPreview1_EnvironSizesGet_Errors(t *testing.T) { +func Test_EnvironSizesGet_Errors(t *testing.T) { sysCtx, err := newSysContext(nil, []string{"a=b", "b=cd"}, nil) require.NoError(t, err) @@ -405,268 +405,8 @@ func TestSnapshotPreview1_EnvironSizesGet_Errors(t *testing.T) { } } -// TestSnapshotPreview1_ClockResGet only tests it is stubbed for GrainLang per #271 -func TestSnapshotPreview1_ClockResGet(t *testing.T) { - mod, fn := instantiateModule(testCtx, t, functionClockResGet, importClockResGet, nil) - defer mod.Close(testCtx) - - resultResolution := uint32(1) // arbitrary offset - - expectedMemoryMicro := []byte{ - '?', // resultResolution is after this - 0xe8, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // little endian-encoded resolution (fixed to 1000). - '?', // stopped after encoding - } - - expectedMemoryNano := []byte{ - '?', // resultResolution is after this - 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // little endian-encoded resolution (fixed to 1000). - '?', // stopped after encoding - } - - tests := []struct { - name string - clockID uint64 - expectedMemory []byte - invocation func(clockID uint64) Errno - }{ - { - name: "wasi.ClockResGet", - clockID: 0, - expectedMemory: expectedMemoryMicro, - invocation: func(clockID uint64) Errno { - return a.ClockResGet(testCtx, mod, uint32(clockID), resultResolution) - }, - }, - { - name: "wasi.ClockResGet", - clockID: 1, - expectedMemory: expectedMemoryNano, - invocation: func(clockID uint64) Errno { - return a.ClockResGet(testCtx, mod, uint32(clockID), resultResolution) - }, - }, - { - name: functionClockResGet, - clockID: 0, - expectedMemory: expectedMemoryMicro, - invocation: func(clockID uint64) Errno { - results, err := fn.Call(testCtx, clockID, uint64(resultResolution)) - require.NoError(t, err) - return Errno(results[0]) // results[0] is the errno - }, - }, - { - name: functionClockResGet, - clockID: 1, - expectedMemory: expectedMemoryNano, - invocation: func(clockID uint64) Errno { - results, err := fn.Call(testCtx, clockID, uint64(resultResolution)) - require.NoError(t, err) - return Errno(results[0]) // results[0] is the errno - }, - }, - } - - for _, tt := range tests { - tc := tt - - t.Run(fmt.Sprintf("%v/clockID=%v", tc.name, tc.clockID), func(t *testing.T) { - maskMemory(t, testCtx, mod, len(tc.expectedMemory)) - - errno := tc.invocation(tc.clockID) - require.Equal(t, ErrnoSuccess, errno, ErrnoName(errno)) - - actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(tc.expectedMemory))) - require.True(t, ok) - require.Equal(t, tc.expectedMemory, actual) - }) - } -} - -func TestSnapshotPreview1_ClockResGet_Unsupported(t *testing.T) { - resultResolution := uint32(1) // arbitrary offset - mod, fn := instantiateModule(testCtx, t, functionClockResGet, importClockResGet, nil) - defer mod.Close(testCtx) - - tests := []struct { - name string - clockID uint64 - }{ - { - name: "process cputime", - clockID: 2, - }, - { - name: "thread cputime", - clockID: 3, - }, - { - name: "undefined", - clockID: 100, - }, - } - - for _, tt := range tests { - tc := tt - - t.Run(tc.name, func(t *testing.T) { - results, err := fn.Call(testCtx, tc.clockID, uint64(resultResolution)) - require.NoError(t, err) - errno := Errno(results[0]) // results[0] is the errno - require.Equal(t, ErrnoNosys, errno, ErrnoName(errno)) - }) - } -} - -func TestSnapshotPreview1_ClockTimeGet(t *testing.T) { - resultTimestamp := uint32(1) // arbitrary offset - - mod, fn := instantiateModule(testCtx, t, functionClockTimeGet, importClockTimeGet, nil) - defer mod.Close(testCtx) - - clocks := []struct { - clock string - id uint32 - expectedMemory []byte - }{ - { - clock: "Realtime", - id: clockIDRealtime, - expectedMemory: []byte{ - '?', // resultTimestamp is after this - 0x0, 0x0, 0x1f, 0xa6, 0x70, 0xfc, 0xc5, 0x16, // little endian-encoded epochNanos - '?', // stopped after encoding - }, - }, - { - clock: "Monotonic", - id: clockIDMonotonic, - expectedMemory: []byte{ - '?', // resultTimestamp is after this - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // fake nanotime starts at zero - '?', // stopped after encoding - }, - }, - } - - for _, c := range clocks { - cc := c - t.Run(cc.clock, func(t *testing.T) { - tests := []struct { - name string - invocation func() Errno - }{ - { - name: "wasi.ClockTimeGet", - invocation: func() Errno { - return a.ClockTimeGet(testCtx, mod, cc.id, 0 /* TODO: precision */, resultTimestamp) - }, - }, - { - name: functionClockTimeGet, - invocation: func() Errno { - results, err := fn.Call(testCtx, uint64(cc.id), 0 /* TODO: precision */, uint64(resultTimestamp)) - require.NoError(t, err) - errno := Errno(results[0]) // results[0] is the errno - return errno - }, - }, - } - - for _, tt := range tests { - tc := tt - t.Run(tc.name, func(t *testing.T) { - // Reset the fake clock - sysCtx, err := newSysContext(nil, nil, nil) - require.NoError(t, err) - mod.(*wasm.CallContext).Sys = sysCtx - - maskMemory(t, testCtx, mod, len(cc.expectedMemory)) - - errno := tc.invocation() - require.Zero(t, errno, ErrnoName(errno)) - - actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(cc.expectedMemory))) - require.True(t, ok) - require.Equal(t, cc.expectedMemory, actual) - }) - } - }) - } -} - -func TestSnapshotPreview1_ClockTimeGet_Unsupported(t *testing.T) { - resultTimestamp := uint32(1) // arbitrary offset - mod, fn := instantiateModule(testCtx, t, functionClockTimeGet, importClockTimeGet, nil) - defer mod.Close(testCtx) - - tests := []struct { - name string - clockID uint64 - }{ - { - name: "process cputime", - clockID: 2, - }, - { - name: "thread cputime", - clockID: 3, - }, - { - name: "undefined", - clockID: 100, - }, - } - - for _, tt := range tests { - tc := tt - - t.Run(tc.name, func(t *testing.T) { - results, err := fn.Call(testCtx, tc.clockID, 0 /* TODO: precision */, uint64(resultTimestamp)) - require.NoError(t, err) - errno := Errno(results[0]) // results[0] is the errno - require.Equal(t, ErrnoNosys, errno, ErrnoName(errno)) - }) - } -} - -func TestSnapshotPreview1_ClockTimeGet_Errors(t *testing.T) { - mod, fn := instantiateModule(testCtx, t, functionClockTimeGet, importClockTimeGet, nil) - defer mod.Close(testCtx) - - memorySize := mod.Memory().Size(testCtx) - - tests := []struct { - name string - resultTimestamp uint32 - argvBufSize uint32 - }{ - { - name: "resultTimestamp out-of-memory", - resultTimestamp: memorySize, - }, - - { - name: "resultTimestamp exceeds the maximum valid address by 1", - resultTimestamp: memorySize - 4 + 1, // 4 is the size of uint32, the type of the count of args - }, - } - - for _, tt := range tests { - tc := tt - - t.Run(tc.name, func(t *testing.T) { - results, err := fn.Call(testCtx, 0 /* TODO: id */, 0 /* TODO: precision */, uint64(tc.resultTimestamp)) - require.NoError(t, err) - errno := Errno(results[0]) // results[0] is the errno - require.Equal(t, ErrnoFault, errno, ErrnoName(errno)) - }) - } -} - -// TestSnapshotPreview1_FdAdvise only tests it is stubbed for GrainLang per #271 -func TestSnapshotPreview1_FdAdvise(t *testing.T) { +// Test_FdAdvise only tests it is stubbed for GrainLang per #271 +func Test_FdAdvise(t *testing.T) { mod, fn := instantiateModule(testCtx, t, functionFdAdvise, importFdAdvise, nil) defer mod.Close(testCtx) @@ -683,8 +423,8 @@ func TestSnapshotPreview1_FdAdvise(t *testing.T) { }) } -// TestSnapshotPreview1_FdAllocate only tests it is stubbed for GrainLang per #271 -func TestSnapshotPreview1_FdAllocate(t *testing.T) { +// Test_FdAllocate only tests it is stubbed for GrainLang per #271 +func Test_FdAllocate(t *testing.T) { mod, fn := instantiateModule(testCtx, t, functionFdAllocate, importFdAllocate, nil) defer mod.Close(testCtx) @@ -701,7 +441,7 @@ func TestSnapshotPreview1_FdAllocate(t *testing.T) { }) } -func TestSnapshotPreview1_FdClose(t *testing.T) { +func Test_FdClose(t *testing.T) { fdToClose := uint32(3) // arbitrary fd fdToKeep := uint32(4) // another arbitrary fd @@ -726,7 +466,7 @@ func TestSnapshotPreview1_FdClose(t *testing.T) { verify := func(mod api.Module) { // Verify fdToClose is closed and removed from the opened FDs. - _, fsc := sysFSCtx(testCtx, mod) + fsc := getSysCtx(mod).FS(testCtx) _, ok := fsc.OpenedFile(fdToClose) require.False(t, ok) @@ -764,8 +504,8 @@ func TestSnapshotPreview1_FdClose(t *testing.T) { }) } -// TestSnapshotPreview1_FdDatasync only tests it is stubbed for GrainLang per #271 -func TestSnapshotPreview1_FdDatasync(t *testing.T) { +// Test_FdDatasync only tests it is stubbed for GrainLang per #271 +func Test_FdDatasync(t *testing.T) { mod, fn := instantiateModule(testCtx, t, functionFdDatasync, importFdDatasync, nil) defer mod.Close(testCtx) @@ -782,14 +522,10 @@ func TestSnapshotPreview1_FdDatasync(t *testing.T) { }) } -// TODO: TestSnapshotPreview1_FdFdstatGet TestSnapshotPreview1_FdFdstatGet_Errors -func TestSnapshotPreview1_FdFdstatGet(t *testing.T) { - t.Skip("TODO") - _ = importFdFdstatGet // stop linter complaint until we implement this -} +// TODO: Test_FdFdstatGet Test_FdFdstatGet_Errors -// TestSnapshotPreview1_FdFdstatSetFlags only tests it is stubbed for GrainLang per #271 -func TestSnapshotPreview1_FdFdstatSetFlags(t *testing.T) { +// Test_FdFdstatSetFlags only tests it is stubbed for GrainLang per #271 +func Test_FdFdstatSetFlags(t *testing.T) { mod, fn := instantiateModule(testCtx, t, functionFdFdstatSetFlags, importFdFdstatSetFlags, nil) defer mod.Close(testCtx) @@ -806,8 +542,8 @@ func TestSnapshotPreview1_FdFdstatSetFlags(t *testing.T) { }) } -// TestSnapshotPreview1_FdFdstatSetRights only tests it is stubbed for GrainLang per #271 -func TestSnapshotPreview1_FdFdstatSetRights(t *testing.T) { +// Test_FdFdstatSetRights only tests it is stubbed for GrainLang per #271 +func Test_FdFdstatSetRights(t *testing.T) { mod, fn := instantiateModule(testCtx, t, functionFdFdstatSetRights, importFdFdstatSetRights, nil) defer mod.Close(testCtx) @@ -824,8 +560,8 @@ func TestSnapshotPreview1_FdFdstatSetRights(t *testing.T) { }) } -// TestSnapshotPreview1_FdFilestatGet only tests it is stubbed for GrainLang per #271 -func TestSnapshotPreview1_FdFilestatGet(t *testing.T) { +// Test_FdFilestatGet only tests it is stubbed for GrainLang per #271 +func Test_FdFilestatGet(t *testing.T) { mod, fn := instantiateModule(testCtx, t, functionFdFilestatGet, importFdFilestatGet, nil) defer mod.Close(testCtx) @@ -842,8 +578,8 @@ func TestSnapshotPreview1_FdFilestatGet(t *testing.T) { }) } -// TestSnapshotPreview1_FdFilestatSetSize only tests it is stubbed for GrainLang per #271 -func TestSnapshotPreview1_FdFilestatSetSize(t *testing.T) { +// Test_FdFilestatSetSize only tests it is stubbed for GrainLang per #271 +func Test_FdFilestatSetSize(t *testing.T) { mod, fn := instantiateModule(testCtx, t, functionFdFilestatSetSize, importFdFilestatSetSize, nil) defer mod.Close(testCtx) @@ -860,8 +596,8 @@ func TestSnapshotPreview1_FdFilestatSetSize(t *testing.T) { }) } -// TestSnapshotPreview1_FdFilestatSetTimes only tests it is stubbed for GrainLang per #271 -func TestSnapshotPreview1_FdFilestatSetTimes(t *testing.T) { +// Test_FdFilestatSetTimes only tests it is stubbed for GrainLang per #271 +func Test_FdFilestatSetTimes(t *testing.T) { mod, fn := instantiateModule(testCtx, t, functionFdFilestatSetTimes, importFdFilestatSetTimes, nil) defer mod.Close(testCtx) @@ -878,8 +614,8 @@ func TestSnapshotPreview1_FdFilestatSetTimes(t *testing.T) { }) } -// TestSnapshotPreview1_FdPread only tests it is stubbed for GrainLang per #271 -func TestSnapshotPreview1_FdPread(t *testing.T) { +// Test_FdPread only tests it is stubbed for GrainLang per #271 +func Test_FdPread(t *testing.T) { mod, fn := instantiateModule(testCtx, t, functionFdPread, importFdPread, nil) defer mod.Close(testCtx) @@ -896,7 +632,7 @@ func TestSnapshotPreview1_FdPread(t *testing.T) { }) } -func TestSnapshotPreview1_FdPrestatGet(t *testing.T) { +func Test_FdPrestatGet(t *testing.T) { fd := uint32(3) // arbitrary fd after 0, 1, and 2, that are stdin/out/err pathName := "/tmp" @@ -941,7 +677,7 @@ func TestSnapshotPreview1_FdPrestatGet(t *testing.T) { }) } -func TestSnapshotPreview1_FdPrestatGet_Errors(t *testing.T) { +func Test_FdPrestatGet_Errors(t *testing.T) { fd := uint32(3) // fd 3 will be opened for the "/tmp" directory after 0, 1, and 2, that are stdin/out/err validAddress := uint32(0) // Arbitrary valid address as arguments to fd_prestat_get. We chose 0 here. @@ -984,7 +720,7 @@ func TestSnapshotPreview1_FdPrestatGet_Errors(t *testing.T) { } } -func TestSnapshotPreview1_FdPrestatDirName(t *testing.T) { +func Test_FdPrestatDirName(t *testing.T) { fd := uint32(3) // arbitrary fd after 0, 1, and 2, that are stdin/out/err sysCtx, err := newSysContext(nil, nil, map[uint32]*internalsys.FileEntry{fd: {Path: "/tmp"}}) @@ -1026,7 +762,7 @@ func TestSnapshotPreview1_FdPrestatDirName(t *testing.T) { }) } -func TestSnapshotPreview1_FdPrestatDirName_Errors(t *testing.T) { +func Test_FdPrestatDirName_Errors(t *testing.T) { fd := uint32(3) // arbitrary fd after 0, 1, and 2, that are stdin/out/err sysCtx, err := newSysContext(nil, nil, map[uint32]*internalsys.FileEntry{fd: {Path: "/tmp"}}) require.NoError(t, err) @@ -1086,8 +822,8 @@ func TestSnapshotPreview1_FdPrestatDirName_Errors(t *testing.T) { } } -// TestSnapshotPreview1_FdPwrite only tests it is stubbed for GrainLang per #271 -func TestSnapshotPreview1_FdPwrite(t *testing.T) { +// Test_FdPwrite only tests it is stubbed for GrainLang per #271 +func Test_FdPwrite(t *testing.T) { mod, fn := instantiateModule(testCtx, t, functionFdPwrite, importFdPwrite, nil) defer mod.Close(testCtx) @@ -1104,7 +840,7 @@ func TestSnapshotPreview1_FdPwrite(t *testing.T) { }) } -func TestSnapshotPreview1_FdRead(t *testing.T) { +func Test_FdRead(t *testing.T) { fd := uint32(3) // arbitrary fd after 0, 1, and 2, that are stdin/out/err iovs := uint32(1) // arbitrary offset initialMemory := []byte{ @@ -1127,7 +863,7 @@ func TestSnapshotPreview1_FdRead(t *testing.T) { '?', ) - // TestSnapshotPreview1_FdRead uses a matrix because setting up test files is complicated and has to be clean each time. + // Test_FdRead uses a matrix because setting up test files is complicated and has to be clean each time. type fdReadFn func(ctx context.Context, mod api.Module, fd, iovs, iovsCount, resultSize uint32) Errno tests := []struct { name string @@ -1173,7 +909,7 @@ func TestSnapshotPreview1_FdRead(t *testing.T) { } } -func TestSnapshotPreview1_FdRead_Errors(t *testing.T) { +func Test_FdRead_Errors(t *testing.T) { validFD := uint32(3) // arbitrary valid fd after 0, 1, and 2, that are stdin/out/err file, testFS := createFile(t, "test_path", []byte{}) // file with empty contents @@ -1265,8 +1001,8 @@ func TestSnapshotPreview1_FdRead_Errors(t *testing.T) { } } -// TestSnapshotPreview1_FdReaddir only tests it is stubbed for GrainLang per #271 -func TestSnapshotPreview1_FdReaddir(t *testing.T) { +// Test_FdReaddir only tests it is stubbed for GrainLang per #271 +func Test_FdReaddir(t *testing.T) { mod, fn := instantiateModule(testCtx, t, functionFdReaddir, importFdReaddir, nil) defer mod.Close(testCtx) @@ -1283,8 +1019,8 @@ func TestSnapshotPreview1_FdReaddir(t *testing.T) { }) } -// TestSnapshotPreview1_FdRenumber only tests it is stubbed for GrainLang per #271 -func TestSnapshotPreview1_FdRenumber(t *testing.T) { +// Test_FdRenumber only tests it is stubbed for GrainLang per #271 +func Test_FdRenumber(t *testing.T) { mod, fn := instantiateModule(testCtx, t, functionFdRenumber, importFdRenumber, nil) defer mod.Close(testCtx) @@ -1301,7 +1037,7 @@ func TestSnapshotPreview1_FdRenumber(t *testing.T) { }) } -func TestSnapshotPreview1_FdSeek(t *testing.T) { +func Test_FdSeek(t *testing.T) { fd := uint32(3) // arbitrary fd after 0, 1, and 2, that are stdin/out/err resultNewoffset := uint32(1) // arbitrary offset in `ctx.Memory` for the new offset value file, testFS := createFile(t, "test_path", []byte("wazero")) // arbitrary non-empty contents @@ -1311,12 +1047,12 @@ func TestSnapshotPreview1_FdSeek(t *testing.T) { }) require.NoError(t, err) - fsCtx := sysCtx.FS() + fsCtx := sysCtx.FS(testCtx) mod, fn := instantiateModule(testCtx, t, functionFdSeek, importFdSeek, sysCtx) defer mod.Close(testCtx) - // TestSnapshotPreview1_FdSeek uses a matrix because setting up test files is complicated and has to be clean each time. + // Test_FdSeek uses a matrix because setting up test files is complicated and has to be clean each time. type fdSeekFn func(ctx context.Context, mod api.Module, fd uint32, offset uint64, whence, resultNewOffset uint32) Errno seekFns := []struct { name string @@ -1410,7 +1146,7 @@ func TestSnapshotPreview1_FdSeek(t *testing.T) { } } -func TestSnapshotPreview1_FdSeek_Errors(t *testing.T) { +func Test_FdSeek_Errors(t *testing.T) { validFD := uint32(3) // arbitrary valid fd after 0, 1, and 2, that are stdin/out/err file, testFS := createFile(t, "test_path", []byte("wazero")) // arbitrary valid file with non-empty contents @@ -1460,8 +1196,8 @@ func TestSnapshotPreview1_FdSeek_Errors(t *testing.T) { } -// TestSnapshotPreview1_FdSync only tests it is stubbed for GrainLang per #271 -func TestSnapshotPreview1_FdSync(t *testing.T) { +// Test_FdSync only tests it is stubbed for GrainLang per #271 +func Test_FdSync(t *testing.T) { mod, fn := instantiateModule(testCtx, t, functionFdSync, importFdSync, nil) defer mod.Close(testCtx) @@ -1478,8 +1214,8 @@ func TestSnapshotPreview1_FdSync(t *testing.T) { }) } -// TestSnapshotPreview1_FdTell only tests it is stubbed for GrainLang per #271 -func TestSnapshotPreview1_FdTell(t *testing.T) { +// Test_FdTell only tests it is stubbed for GrainLang per #271 +func Test_FdTell(t *testing.T) { mod, fn := instantiateModule(testCtx, t, functionFdTell, importFdTell, nil) defer mod.Close(testCtx) @@ -1496,7 +1232,7 @@ func TestSnapshotPreview1_FdTell(t *testing.T) { }) } -func TestSnapshotPreview1_FdWrite(t *testing.T) { +func Test_FdWrite(t *testing.T) { fd := uint32(3) // arbitrary fd after 0, 1, and 2, that are stdin/out/err iovs := uint32(1) // arbitrary offset initialMemory := []byte{ @@ -1519,7 +1255,7 @@ func TestSnapshotPreview1_FdWrite(t *testing.T) { '?', ) - // TestSnapshotPreview1_FdWrite uses a matrix because setting up test files is complicated and has to be clean each time. + // Test_FdWrite uses a matrix because setting up test files is complicated and has to be clean each time. type fdWriteFn func(ctx context.Context, mod api.Module, fd, iovs, iovsCount, resultSize uint32) Errno tests := []struct { name string @@ -1573,7 +1309,7 @@ func TestSnapshotPreview1_FdWrite(t *testing.T) { } } -func TestSnapshotPreview1_FdWrite_Errors(t *testing.T) { +func Test_FdWrite_Errors(t *testing.T) { validFD := uint32(3) // arbitrary valid fd after 0, 1, and 2, that are stdin/out/err tmpDir := t.TempDir() // open before loop to ensure no locking problems. @@ -1651,8 +1387,8 @@ func TestSnapshotPreview1_FdWrite_Errors(t *testing.T) { } } -// TestSnapshotPreview1_PathCreateDirectory only tests it is stubbed for GrainLang per #271 -func TestSnapshotPreview1_PathCreateDirectory(t *testing.T) { +// Test_PathCreateDirectory only tests it is stubbed for GrainLang per #271 +func Test_PathCreateDirectory(t *testing.T) { mod, fn := instantiateModule(testCtx, t, functionPathCreateDirectory, importPathCreateDirectory, nil) defer mod.Close(testCtx) @@ -1669,8 +1405,8 @@ func TestSnapshotPreview1_PathCreateDirectory(t *testing.T) { }) } -// TestSnapshotPreview1_PathFilestatGet only tests it is stubbed for GrainLang per #271 -func TestSnapshotPreview1_PathFilestatGet(t *testing.T) { +// Test_PathFilestatGet only tests it is stubbed for GrainLang per #271 +func Test_PathFilestatGet(t *testing.T) { mod, fn := instantiateModule(testCtx, t, functionPathFilestatGet, importPathFilestatGet, nil) defer mod.Close(testCtx) @@ -1687,8 +1423,8 @@ func TestSnapshotPreview1_PathFilestatGet(t *testing.T) { }) } -// TestSnapshotPreview1_PathFilestatSetTimes only tests it is stubbed for GrainLang per #271 -func TestSnapshotPreview1_PathFilestatSetTimes(t *testing.T) { +// Test_PathFilestatSetTimes only tests it is stubbed for GrainLang per #271 +func Test_PathFilestatSetTimes(t *testing.T) { mod, fn := instantiateModule(testCtx, t, functionPathFilestatSetTimes, importPathFilestatSetTimes, nil) defer mod.Close(testCtx) @@ -1705,8 +1441,8 @@ func TestSnapshotPreview1_PathFilestatSetTimes(t *testing.T) { }) } -// TestSnapshotPreview1_PathLink only tests it is stubbed for GrainLang per #271 -func TestSnapshotPreview1_PathLink(t *testing.T) { +// Test_PathLink only tests it is stubbed for GrainLang per #271 +func Test_PathLink(t *testing.T) { mod, fn := instantiateModule(testCtx, t, functionPathLink, importPathLink, nil) defer mod.Close(testCtx) @@ -1723,7 +1459,7 @@ func TestSnapshotPreview1_PathLink(t *testing.T) { }) } -func TestSnapshotPreview1_PathOpen(t *testing.T) { +func Test_PathOpen(t *testing.T) { type pathOpenArgs struct { fd uint32 dirflags uint32 @@ -1780,7 +1516,7 @@ func TestSnapshotPreview1_PathOpen(t *testing.T) { require.Equal(t, expectedMemory, actual) // verify the file was actually opened - _, fsc := sysFSCtx(ctx, mod) + fsc := getSysCtx(mod).FS(ctx) f, ok := fsc.OpenedFile(expectedFD) require.True(t, ok) require.Equal(t, pathName, f.Path) @@ -1832,7 +1568,7 @@ func TestSnapshotPreview1_PathOpen(t *testing.T) { }) } -func TestSnapshotPreview1_PathOpen_Errors(t *testing.T) { +func Test_PathOpen_Errors(t *testing.T) { validFD := uint32(3) // arbitrary valid fd after 0, 1, and 2, that are stdin/out/err pathName := "wazero" testFS := fstest.MapFS{pathName: &fstest.MapFile{Mode: os.ModeDir}} @@ -1899,8 +1635,8 @@ func TestSnapshotPreview1_PathOpen_Errors(t *testing.T) { } } -// TestSnapshotPreview1_PathReadlink only tests it is stubbed for GrainLang per #271 -func TestSnapshotPreview1_PathReadlink(t *testing.T) { +// Test_PathReadlink only tests it is stubbed for GrainLang per #271 +func Test_PathReadlink(t *testing.T) { mod, fn := instantiateModule(testCtx, t, functionPathReadlink, importPathReadlink, nil) defer mod.Close(testCtx) @@ -1917,8 +1653,8 @@ func TestSnapshotPreview1_PathReadlink(t *testing.T) { }) } -// TestSnapshotPreview1_PathRemoveDirectory only tests it is stubbed for GrainLang per #271 -func TestSnapshotPreview1_PathRemoveDirectory(t *testing.T) { +// Test_PathRemoveDirectory only tests it is stubbed for GrainLang per #271 +func Test_PathRemoveDirectory(t *testing.T) { mod, fn := instantiateModule(testCtx, t, functionPathRemoveDirectory, importPathRemoveDirectory, nil) defer mod.Close(testCtx) @@ -1935,8 +1671,8 @@ func TestSnapshotPreview1_PathRemoveDirectory(t *testing.T) { }) } -// TestSnapshotPreview1_PathRename only tests it is stubbed for GrainLang per #271 -func TestSnapshotPreview1_PathRename(t *testing.T) { +// Test_PathRename only tests it is stubbed for GrainLang per #271 +func Test_PathRename(t *testing.T) { mod, fn := instantiateModule(testCtx, t, functionPathRename, importPathRename, nil) defer mod.Close(testCtx) @@ -1953,8 +1689,8 @@ func TestSnapshotPreview1_PathRename(t *testing.T) { }) } -// TestSnapshotPreview1_PathSymlink only tests it is stubbed for GrainLang per #271 -func TestSnapshotPreview1_PathSymlink(t *testing.T) { +// Test_PathSymlink only tests it is stubbed for GrainLang per #271 +func Test_PathSymlink(t *testing.T) { mod, fn := instantiateModule(testCtx, t, functionPathSymlink, importPathSymlink, nil) defer mod.Close(testCtx) @@ -1971,8 +1707,8 @@ func TestSnapshotPreview1_PathSymlink(t *testing.T) { }) } -// TestSnapshotPreview1_PathUnlinkFile only tests it is stubbed for GrainLang per #271 -func TestSnapshotPreview1_PathUnlinkFile(t *testing.T) { +// Test_PathUnlinkFile only tests it is stubbed for GrainLang per #271 +func Test_PathUnlinkFile(t *testing.T) { mod, fn := instantiateModule(testCtx, t, functionPathUnlinkFile, importPathUnlinkFile, nil) defer mod.Close(testCtx) @@ -1989,25 +1725,7 @@ func TestSnapshotPreview1_PathUnlinkFile(t *testing.T) { }) } -// TestSnapshotPreview1_PollOneoff only tests it is stubbed for GrainLang per #271 -func TestSnapshotPreview1_PollOneoff(t *testing.T) { - mod, fn := instantiateModule(testCtx, t, functionPollOneoff, importPollOneoff, nil) - defer mod.Close(testCtx) - - t.Run("wasi.PollOneoff", func(t *testing.T) { - errno := a.PollOneoff(testCtx, mod, 0, 0, 0, 0) - require.Equal(t, ErrnoNosys, errno, ErrnoName(errno)) - }) - - t.Run(functionPollOneoff, func(t *testing.T) { - results, err := fn.Call(testCtx, 0, 0, 0, 0) - require.NoError(t, err) - errno := Errno(results[0]) // results[0] is the errno - require.Equal(t, ErrnoNosys, errno, ErrnoName(errno)) - }) -} - -func TestSnapshotPreview1_ProcExit(t *testing.T) { +func Test_ProcExit(t *testing.T) { tests := []struct { name string exitCode uint32 @@ -2038,8 +1756,8 @@ func TestSnapshotPreview1_ProcExit(t *testing.T) { } } -// TestSnapshotPreview1_ProcRaise only tests it is stubbed for GrainLang per #271 -func TestSnapshotPreview1_ProcRaise(t *testing.T) { +// Test_ProcRaise only tests it is stubbed for GrainLang per #271 +func Test_ProcRaise(t *testing.T) { mod, fn := instantiateModule(testCtx, t, functionProcRaise, importProcRaise, nil) defer mod.Close(testCtx) @@ -2056,8 +1774,8 @@ func TestSnapshotPreview1_ProcRaise(t *testing.T) { }) } -// TestSnapshotPreview1_SchedYield only tests it is stubbed for GrainLang per #271 -func TestSnapshotPreview1_SchedYield(t *testing.T) { +// Test_SchedYield only tests it is stubbed for GrainLang per #271 +func Test_SchedYield(t *testing.T) { mod, fn := instantiateModule(testCtx, t, functionSchedYield, importSchedYield, nil) defer mod.Close(testCtx) @@ -2074,7 +1792,7 @@ func TestSnapshotPreview1_SchedYield(t *testing.T) { }) } -func TestSnapshotPreview1_RandomGet(t *testing.T) { +func Test_RandomGet(t *testing.T) { expectedMemory := []byte{ '?', // `offset` is after this 0x53, 0x8c, 0x7f, 0x96, 0xb1, // random data from seed value of 42 @@ -2116,7 +1834,7 @@ func TestSnapshotPreview1_RandomGet(t *testing.T) { }) } -func TestSnapshotPreview1_RandomGet_Errors(t *testing.T) { +func Test_RandomGet_Errors(t *testing.T) { validAddress := uint32(0) // arbitrary valid address mod, _ := instantiateModule(testCtx, t, functionRandomGet, importRandomGet, nil) @@ -2152,7 +1870,7 @@ func TestSnapshotPreview1_RandomGet_Errors(t *testing.T) { } } -func TestSnapshotPreview1_RandomGet_SourceError(t *testing.T) { +func Test_RandomGet_SourceError(t *testing.T) { tests := []struct { name string randSource io.Reader @@ -2180,6 +1898,7 @@ func TestSnapshotPreview1_RandomGet_SourceError(t *testing.T) { tc.randSource, nil, 0, nil, 0, + nil, // nanosleep nil, ) require.NoError(t, err) @@ -2193,8 +1912,8 @@ func TestSnapshotPreview1_RandomGet_SourceError(t *testing.T) { } } -// TestSnapshotPreview1_SockRecv only tests it is stubbed for GrainLang per #271 -func TestSnapshotPreview1_SockRecv(t *testing.T) { +// Test_SockRecv only tests it is stubbed for GrainLang per #271 +func Test_SockRecv(t *testing.T) { mod, fn := instantiateModule(testCtx, t, functionSockRecv, importSockRecv, nil) defer mod.Close(testCtx) @@ -2211,8 +1930,8 @@ func TestSnapshotPreview1_SockRecv(t *testing.T) { }) } -// TestSnapshotPreview1_SockSend only tests it is stubbed for GrainLang per #271 -func TestSnapshotPreview1_SockSend(t *testing.T) { +// Test_SockSend only tests it is stubbed for GrainLang per #271 +func Test_SockSend(t *testing.T) { mod, fn := instantiateModule(testCtx, t, functionSockSend, importSockSend, nil) defer mod.Close(testCtx) @@ -2229,8 +1948,8 @@ func TestSnapshotPreview1_SockSend(t *testing.T) { }) } -// TestSnapshotPreview1_SockShutdown only tests it is stubbed for GrainLang per #271 -func TestSnapshotPreview1_SockShutdown(t *testing.T) { +// Test_SockShutdown only tests it is stubbed for GrainLang per #271 +func Test_SockShutdown(t *testing.T) { mod, fn := instantiateModule(testCtx, t, functionSockShutdown, importSockShutdown, nil) defer mod.Close(testCtx) @@ -2300,6 +2019,7 @@ func newSysContext(args, environ []string, openedFiles map[uint32]*internalsys.F deterministicRandomSource(), nil, 0, nil, 0, + nil, // nanosleep openedFiles, ) }