Repository: tetratelabs/wazero
Pull request #890 (2022-12-06 07:22:01)

wasi: proxy functions to that natural results are visible
Right now, developing and troubleshooting wasi host functions are difficult as they are required to be WebAssembly 1.0 compatible. Specifically, they can only return a single result, and that's always used for errno. Other results are passed as out-parameters (memory offsets to write results), and their values are not very useful in practice.

This change proxies functions so that we can see their results while still keeping compatibility with wasm signatures. It uses the same approach we use in gojs (`GOOS=js GOARCH=wasm go build...`). What happens is we proxy in wasm, so that the go function implementing the ABI function has natural multi-value returned.

Specifically, we get logging like this, which is better as you can see the values returned:
```diff
-       ==> wasi_snapshot_preview1.args_sizes_get(result.argc=1,result.argv_len=6)
-       <== ESUCCESS
+       --> wasi_snapshot_preview1.args_sizes_get(result.argc=1,result.argv_len=6)
+               ==> wasi_snapshot_preview1.args_sizes_get.go()
+               <== (argc=2,argv_len=5,ESUCCESS)
+       <-- ESUCCESS
```

codefromthecrypt: will leave this draft as I refactor as many WASI functions as I can. In the process I imagine a lot of the code will generalize as opposed to re-defining N helpers manually.
codefromthecrypt: next step: convert the function that proxies the function to be generic. ex claim N result parameters
codefromthecrypt: made progress with a number of file-related calls. once I back port all the calls whose out params can fit in 8 bytes (i64), I'll go back and clean up. Particularly, I'll try to clean up logging so that results that are flags are formatted when in wasi mode, as well any obvious cruft cleanup opportunities in the main code. need another day.
mathetake: how's the perf diff look like before vs this?
codefromthecrypt: bench stat in progress!

```bash
$ git checkout main;go test -run='^$' -bench=. -count=5 > old.txt; git checkout wrap-wasi; go test -run='^$' -bench=. -count=5 >new.txt; benchstat old.txt new.txt
```
codefromthecrypt: perf looks same across 5 runs. verified with bench stat because ad-hoc looked the same

```
name                               old time/op    new time/op    delta
_ArgsEnviron/args_get-16              119ns ± 2%     119ns ± 2%    ~     (p=1.000 n=5+5)
_ArgsEnviron/args_sizes_get-16        106ns ±16%      97ns ± 2%    ~     (p=0.056 n=5+5)
_ArgsEnviron/environ_get-16           123ns ± 4%     118ns ± 2%  -4.21%  (p=0.016 n=5+5)
_ArgsEnviron/environ_sizes_get-16    98.7ns ± 3%    96.7ns ± 2%    ~     (p=0.310 n=5+5)

name                               old alloc/op   new alloc/op   delta
_ArgsEnviron/args_get-16              24.0B ± 0%     24.0B ± 0%    ~     (all equal)
_ArgsEnviron/args_sizes_get-16        24.0B ± 0%     24.0B ± 0%    ~     (all equal)
_ArgsEnviron/environ_get-16           24.0B ± 0%     24.0B ± 0%    ~     (all equal)
_ArgsEnviron/environ_sizes_get-16     24.0B ± 0%     24.0B ± 0%    ~     (all equal)

name                               old allocs/op  new allocs/op  delta
_ArgsEnviron/args_get-16               2.00 ± 0%      2.00 ± 0%    ~     (all equal)
_ArgsEnviron/args_sizes_get-16         2.00 ± 0%      2.00 ± 0%    ~     (all equal)
_ArgsEnviron/environ_get-16            2.00 ± 0%      2.00 ± 0%    ~     (all equal)
_ArgsEnviron/environ_sizes_get-16      2.00 ± 0%      2.00 ± 0%    ~     (all equal)
```

Patch:
diff --git a/imports/wasi_snapshot_preview1/args.go b/imports/wasi_snapshot_preview1/args.go
index 5fb040d8..15cc9139 100644
--- a/imports/wasi_snapshot_preview1/args.go
+++ b/imports/wasi_snapshot_preview1/args.go
@@ -81,20 +81,17 @@ func argsGetFn(ctx context.Context, mod api.Module, params []uint64) Errno {
 // See argsGet
 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#args_sizes_get
 // See https://en.wikipedia.org/wiki/Null-terminated_string
-var argsSizesGet = newHostFunc(argsSizesGetName, argsSizesGetFn, []api.ValueType{i32, i32}, "result.argc", "result.argv_len")
+var argsSizesGet = proxyResultParams(&wasm.HostFunc{
+	Name:        "argsSizesGet",
+	ResultTypes: []api.ValueType{i32, i32, i32},
+	ResultNames: []string{"argc", "argv_len", "errno"},
+	Code:        &wasm.Code{IsHostFunction: true, GoFunc: u32u32ResultParam(argsSizesGetFn)},
+}, argsSizesGetName)
 
-func argsSizesGetFn(ctx context.Context, mod api.Module, params []uint64) Errno {
+func argsSizesGetFn(_ context.Context, mod api.Module, _ []uint64) (argc, argvLen uint32, errno Errno) {
 	sysCtx := mod.(*wasm.CallContext).Sys
-	mem := mod.Memory()
-	resultArgc, resultArgvLen := uint32(params[0]), uint32(params[1])
-
-	// argc and argv_len offsets are not necessarily sequential, so we have to
-	// write them independently.
-	if !mem.WriteUint32Le(ctx, resultArgc, uint32(len(sysCtx.Args()))) {
-		return ErrnoFault
-	}
-	if !mem.WriteUint32Le(ctx, resultArgvLen, sysCtx.ArgsSize()) {
-		return ErrnoFault
-	}
-	return ErrnoSuccess
+	argc = uint32(len(sysCtx.Args()))
+	argvLen = sysCtx.ArgsSize()
+	errno = ErrnoSuccess
+	return
 }
diff --git a/imports/wasi_snapshot_preview1/clock.go b/imports/wasi_snapshot_preview1/clock.go
index f6e46d0d..13c60d22 100644
--- a/imports/wasi_snapshot_preview1/clock.go
+++ b/imports/wasi_snapshot_preview1/clock.go
@@ -51,26 +51,29 @@ const (
 // Note: This is similar to `clock_getres` in POSIX.
 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-clock_res_getid-clockid---errno-timestamp
 // See https://linux.die.net/man/3/clock_getres
-var clockResGet = newHostFunc(clockResGetName, clockResGetFn, []api.ValueType{i32, i32}, "id", "result.resolution")
+var clockResGet = proxyResultParams(&wasm.HostFunc{
+	Name:        "clockResGet",
+	ParamTypes:  []api.ValueType{i32},
+	ParamNames:  []string{"id"},
+	ResultTypes: []api.ValueType{i64, i32},
+	ResultNames: []string{"resolution", "errno"},
+	Code:        &wasm.Code{IsHostFunction: true, GoFunc: u64ResultParam(clockResGetFn)},
+}, clockResGetName)
 
-func clockResGetFn(ctx context.Context, mod api.Module, params []uint64) Errno {
+func clockResGetFn(_ context.Context, mod api.Module, stack []uint64) (resolution uint64, errno Errno) {
 	sysCtx := mod.(*wasm.CallContext).Sys
-	id, resultResolution := uint32(params[0]), uint32(params[1])
+	id := uint32(stack[0])
 
-	var resolution uint64 // ns
+	errno = ErrnoSuccess
 	switch id {
 	case clockIDRealtime:
 		resolution = uint64(sysCtx.WalltimeResolution())
 	case clockIDMonotonic:
 		resolution = uint64(sysCtx.NanotimeResolution())
 	default:
-		return ErrnoInval
+		errno = ErrnoInval
 	}
-
-	if !mod.Memory().WriteUint64Le(ctx, resultResolution, resolution) {
-		return ErrnoFault
-	}
-	return ErrnoSuccess
+	return
 }
 
 // clockTimeGet is the WASI function named clockTimeGetName that returns
@@ -104,28 +107,30 @@ func clockResGetFn(ctx context.Context, mod api.Module, params []uint64) Errno {
 // Note: This is similar to `clock_gettime` in POSIX.
 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-clock_time_getid-clockid-precision-timestamp---errno-timestamp
 // See https://linux.die.net/man/3/clock_gettime
-var clockTimeGet = newHostFunc(clockTimeGetName, clockTimeGetFn, []api.ValueType{i32, i64, i32}, "id", "precision", "result.timestamp")
+var clockTimeGet = proxyResultParams(&wasm.HostFunc{
+	Name:        "clockTimeGet",
+	ParamTypes:  []api.ValueType{i32, i64},
+	ParamNames:  []string{"id", "precision"},
+	ResultTypes: []api.ValueType{i64, i32},
+	ResultNames: []string{"timestamp", "errno"},
+	Code:        &wasm.Code{IsHostFunction: true, GoFunc: i64ResultParam(clockTimeGetFn)},
+}, clockTimeGetName)
 
-func clockTimeGetFn(ctx context.Context, mod api.Module, params []uint64) Errno {
+func clockTimeGetFn(ctx context.Context, mod api.Module, stack []uint64) (timestamp int64, errno Errno) {
 	sysCtx := mod.(*wasm.CallContext).Sys
-	id := uint32(params[0])
+	id := uint32(stack[0])
 	// TODO: precision is currently ignored.
 	// precision = params[1]
-	resultTimestamp := uint32(params[2])
 
-	var val uint64
 	switch id {
 	case clockIDRealtime:
 		sec, nsec := sysCtx.Walltime(ctx)
-		val = (uint64(sec) * uint64(time.Second.Nanoseconds())) + uint64(nsec)
+		timestamp = (sec * time.Second.Nanoseconds()) + int64(nsec)
 	case clockIDMonotonic:
-		val = uint64(sysCtx.Nanotime(ctx))
+		timestamp = sysCtx.Nanotime(ctx)
 	default:
-		return ErrnoInval
-	}
-
-	if !mod.Memory().WriteUint64Le(ctx, resultTimestamp, val) {
-		return ErrnoFault
+		return 0, ErrnoInval
 	}
-	return ErrnoSuccess
+	errno = ErrnoSuccess
+	return
 }
diff --git a/imports/wasi_snapshot_preview1/environ.go b/imports/wasi_snapshot_preview1/environ.go
index 9169f8f9..c8e20eb0 100644
--- a/imports/wasi_snapshot_preview1/environ.go
+++ b/imports/wasi_snapshot_preview1/environ.go
@@ -84,20 +84,18 @@ func environGetFn(ctx context.Context, mod api.Module, params []uint64) Errno {
 // See environGet
 // https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#environ_sizes_get
 // and https://en.wikipedia.org/wiki/Null-terminated_string
-var environSizesGet = newHostFunc(environSizesGetName, environSizesGetFn, []api.ValueType{i32, i32}, "result.environc", "result.environv_len")
+var environSizesGet = proxyResultParams(&wasm.HostFunc{
+	Name:        "environSizesGet",
+	ResultTypes: []api.ValueType{i32, i32, i32},
+	ResultNames: []string{"environc", "environv_len", "errno"},
+	Code:        &wasm.Code{IsHostFunction: true, GoFunc: u32u32ResultParam(environSizesGetFn)},
+}, environSizesGetName)
 
-func environSizesGetFn(ctx context.Context, mod api.Module, params []uint64) Errno {
+func environSizesGetFn(_ context.Context, mod api.Module, _ []uint64) (environc, environvLen uint32, errno Errno) {
 	sysCtx := mod.(*wasm.CallContext).Sys
-	mem := mod.Memory()
-	resultEnvironc, resultEnvironvLen := uint32(params[0]), uint32(params[1])
 
-	// environc and environv_len offsets are not necessarily sequential, so we
-	// have to write them independently.
-	if !mem.WriteUint32Le(ctx, resultEnvironc, uint32(len(sysCtx.Environ()))) {
-		return ErrnoFault
-	}
-	if !mem.WriteUint32Le(ctx, resultEnvironvLen, sysCtx.EnvironSize()) {
-		return ErrnoFault
-	}
-	return ErrnoSuccess
+	environc = uint32(len(sysCtx.Environ()))
+	environvLen = sysCtx.EnvironSize()
+	errno = ErrnoSuccess
+	return
 }
diff --git a/imports/wasi_snapshot_preview1/fs.go b/imports/wasi_snapshot_preview1/fs.go
index 16d07669..c9f20540 100644
--- a/imports/wasi_snapshot_preview1/fs.go
+++ b/imports/wasi_snapshot_preview1/fs.go
@@ -56,7 +56,7 @@ const (
 var fdAdvise = stubFunction(
 	fdAdviseName,
 	[]wasm.ValueType{i32, i64, i64, i32},
-	"fd", "offset", "len", "result.advice",
+	"fd", "offset", "len", "advice",
 )
 
 // fdAllocate is the WASI function named fdAllocateName which forces the
@@ -141,6 +141,8 @@ var fdDatasync = stubFunction(fdDatasyncName, []wasm.ValueType{i32}, "fd")
 // and https://linux.die.net/man/3/fsync
 var fdFdstatGet = newHostFunc(fdFdstatGetName, fdFdstatGetFn, []api.ValueType{i32, i32}, "fd", "result.stat")
 
+// fdFdstatGetFn cannot currently use proxyResultParams because fdstat is larger
+// than api.ValueTypeI64 (i64 == 8 bytes, but fdstat is 24).
 func fdFdstatGetFn(ctx context.Context, mod api.Module, params []uint64) Errno {
 	fd, resultFdstat := uint32(params[0]), uint32(params[1])
 
@@ -302,6 +304,8 @@ const (
 	wasiFiletypeSymbolicLink
 )
 
+// fdFilestatGetFn cannot currently use proxyResultParams because filestat is
+// larger than api.ValueTypeI64 (i64 == 8 bytes, but filestat is 64).
 func fdFilestatGetFn(ctx context.Context, mod api.Module, params []uint64) Errno {
 	return fdFilestatGetFunc(ctx, mod, uint32(params[0]), uint32(params[1]))
 }
@@ -407,14 +411,17 @@ var fdFilestatSetTimes = stubFunction(
 // Except for handling offset, this implementation is identical to fdRead.
 //
 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_preadfd-fd-iovs-iovec_array-offset-filesize---errno-size
-var fdPread = newHostFunc(
-	fdPreadName, fdPreadFn,
-	[]api.ValueType{i32, i32, i32, i64, i32},
-	"fd", "iovs", "iovs_len", "offset", "result.nread",
-)
-
-func fdPreadFn(ctx context.Context, mod api.Module, params []uint64) Errno {
-	return fdReadOrPread(ctx, mod, params, true)
+var fdPread = proxyResultParams(&wasm.HostFunc{
+	Name:        "fdPread",
+	ParamTypes:  []api.ValueType{i32, i32, i32, i64},
+	ParamNames:  []string{"fd", "iovs", "iovs_len", "offset"},
+	ResultTypes: []api.ValueType{i32, i32},
+	ResultNames: []string{"nread", "errno"},
+	Code:        &wasm.Code{IsHostFunction: true, GoFunc: u32ResultParam(fdPreadFn)},
+}, fdPreadName)
+
+func fdPreadFn(ctx context.Context, mod api.Module, stack []uint64) (nread uint32, errno Errno) {
+	return fdReadOrPread(ctx, mod, stack, true)
 }
 
 // fdPrestatGet is the WASI function named fdPrestatGetName which returns
@@ -448,27 +455,29 @@ func fdPreadFn(ctx context.Context, mod api.Module, params []uint64) Errno {
 //
 // See fdPrestatDirName and
 // https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#prestat
-var fdPrestatGet = newHostFunc(fdPrestatGetName, fdPrestatGetFn, []api.ValueType{i32, i32}, "fd", "result.prestat")
-
-func fdPrestatGetFn(ctx context.Context, mod api.Module, params []uint64) Errno {
+var fdPrestatGet = proxyResultParams(&wasm.HostFunc{
+	Name:        "fdPrestatGet",
+	ParamTypes:  []api.ValueType{i32},
+	ParamNames:  []string{"fd"},
+	ResultTypes: []api.ValueType{i64, i32},
+	ResultNames: []string{"prestat", "errno"},
+	Code:        &wasm.Code{IsHostFunction: true, GoFunc: u64ResultParam(fdPrestatGetFn)},
+}, fdPrestatGetName)
+
+func fdPrestatGetFn(ctx context.Context, mod api.Module, stack []uint64) (prestat uint64, errno Errno) {
 	sysCtx := mod.(*wasm.CallContext).Sys
-	fd, resultPrestat := uint32(params[0]), uint32(params[1])
+	fd := uint32(stack[0])
 
 	entry, ok := sysCtx.FS(ctx).OpenedFile(ctx, fd)
 	if !ok {
-		return ErrnoBadf
-	}
-
-	// Zero-value 8-bit tag, and 3-byte zero-value paddings, which is uint32le(0) in short.
-	if !mod.Memory().WriteUint32Le(ctx, resultPrestat, uint32(0)) {
-		return ErrnoFault
+		return 0, ErrnoBadf
 	}
 
-	// Write the length of the directory name at offset 4.
-	if !mod.Memory().WriteUint32Le(ctx, resultPrestat+4, uint32(len(entry.Path))) {
-		return ErrnoFault
-	}
-	return ErrnoSuccess
+	// Upper 32-bits are zero because...
+	// * Zero-value 8-bit tag, and 3-byte zero-value padding
+	prestat = uint64(len(entry.Path) << 32)
+	errno = ErrnoSuccess
+	return
 }
 
 // fdPrestatDirName is the WASI function named fdPrestatDirNameName which
@@ -588,44 +597,43 @@ var fdPwrite = stubFunction(
 //
 // See fdWrite
 // and https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_read
-var fdRead = newHostFunc(
-	fdReadName, fdReadFn,
-	[]api.ValueType{i32, i32, i32, i32},
-	"fd", "iovs", "iovs_len", "result.nread",
-)
-
-func fdReadFn(ctx context.Context, mod api.Module, params []uint64) Errno {
-	return fdReadOrPread(ctx, mod, params, false)
+var fdRead = proxyResultParams(&wasm.HostFunc{
+	Name:        "fdRead",
+	ParamTypes:  []api.ValueType{i32, i32, i32},
+	ParamNames:  []string{"fd", "iovs", "iovs_len"},
+	ResultTypes: []api.ValueType{i32, i32},
+	ResultNames: []string{"nread", "errno"},
+	Code:        &wasm.Code{IsHostFunction: true, GoFunc: u32ResultParam(fdReadFn)},
+}, fdReadName)
+
+func fdReadFn(ctx context.Context, mod api.Module, stack []uint64) (nread uint32, errno Errno) {
+	return fdReadOrPread(ctx, mod, stack, false)
 }
 
-func fdReadOrPread(ctx context.Context, mod api.Module, params []uint64, isPread bool) Errno {
+func fdReadOrPread(ctx context.Context, mod api.Module, stack []uint64, isPread bool) (uint32, Errno) {
 	sysCtx := mod.(*wasm.CallContext).Sys
 	mem := mod.Memory()
-	fd := uint32(params[0])
-	iovs := uint32(params[1])
-	iovsCount := uint32(params[2])
+	fd := uint32(stack[0])
+	iovs := uint32(stack[1])
+	iovsCount := uint32(stack[2])
 
 	var offset int64
-	var resultNread uint32
 	if isPread {
-		offset = int64(params[3])
-		resultNread = uint32(params[4])
-	} else {
-		resultNread = uint32(params[3])
+		offset = int64(stack[3])
 	}
 
 	r := internalsys.FdReader(ctx, sysCtx, fd)
 	if r == nil {
-		return ErrnoBadf
+		return 0, ErrnoBadf
 	}
 
 	if isPread {
 		if s, ok := r.(io.Seeker); ok {
 			if _, err := s.Seek(offset, io.SeekStart); err != nil {
-				return ErrnoFault
+				return 0, ErrnoFault
 			}
 		} else {
-			return ErrnoInval
+			return 0, ErrnoInval
 		}
 	}
 
@@ -634,15 +642,15 @@ func fdReadOrPread(ctx context.Context, mod api.Module, params []uint64, isPread
 		iov := iovs + i*8
 		offset, ok := mem.ReadUint32Le(ctx, iov)
 		if !ok {
-			return ErrnoFault
+			return 0, ErrnoFault
 		}
 		l, ok := mem.ReadUint32Le(ctx, iov+4)
 		if !ok {
-			return ErrnoFault
+			return 0, ErrnoFault
 		}
 		b, ok := mem.Read(ctx, offset, l)
 		if !ok {
-			return ErrnoFault
+			return 0, ErrnoFault
 		}
 
 		n, err := r.Read(b)
@@ -650,16 +658,12 @@ func fdReadOrPread(ctx context.Context, mod api.Module, params []uint64, isPread
 
 		shouldContinue, errno := fdRead_shouldContinueRead(uint32(n), l, err)
 		if errno != ErrnoSuccess {
-			return errno
+			return 0, errno
 		} else if !shouldContinue {
 			break
 		}
 	}
-	if !mem.WriteUint32Le(ctx, resultNread, nread) {
-		return ErrnoFault
-	} else {
-		return ErrnoSuccess
-	}
+	return nread, ErrnoSuccess
 }
 
 // fdRead_shouldContinueRead decides whether to continue reading the next iovec
@@ -683,31 +687,33 @@ func fdRead_shouldContinueRead(n, l uint32, err error) (bool, Errno) {
 // entries from a directory.
 //
 // 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
-var fdReaddir = newHostFunc(
-	fdReaddirName, fdReaddirFn,
-	[]wasm.ValueType{i32, i32, i32, i64, i32},
-	"fd", "buf", "buf_len", "cookie", "result.bufused",
-)
-
-func fdReaddirFn(ctx context.Context, mod api.Module, params []uint64) Errno {
-	fd := uint32(params[0])
-	buf := uint32(params[1])
-	bufLen := uint32(params[2])
+var fdReaddir = proxyResultParams(&wasm.HostFunc{
+	Name:        "fdReaddir",
+	ParamTypes:  []api.ValueType{i32, i32, i32, i64},
+	ParamNames:  []string{"fd", "buf", "buf_len", "cookie"},
+	ResultTypes: []api.ValueType{i32, i32},
+	ResultNames: []string{"bufused", "errno"},
+	Code:        &wasm.Code{IsHostFunction: true, GoFunc: u32ResultParam(fdReaddirFn)},
+}, fdReaddirName)
+
+func fdReaddirFn(ctx context.Context, mod api.Module, stack []uint64) (uint32, Errno) {
+	fd := uint32(stack[0])
+	buf := uint32(stack[1])
+	bufLen := uint32(stack[2])
 	// We control the value of the cookie, and it should never be negative.
 	// However, we coerce it to signed to ensure the caller doesn't manipulate
 	// it in such a way that becomes negative.
-	cookie := int64(params[3])
-	resultBufused := uint32(params[4])
+	cookie := int64(stack[3])
 
 	// Validate the FD is a directory
 	rd, dir, errno := openedDir(ctx, mod, fd)
 	if errno != ErrnoSuccess {
-		return errno
+		return 0, errno
 	}
 
 	// expect a cookie only if we are continuing a read.
 	if cookie == 0 && dir.CountRead > 0 {
-		return ErrnoInval // invalid as a cookie is minimally one.
+		return 0, ErrnoInval // invalid as a cookie is minimally one.
 	}
 
 	// First, determine the maximum directory entries that can be encoded as
@@ -727,14 +733,14 @@ func fdReaddirFn(ctx context.Context, mod api.Module, params []uint64) Errno {
 	// we cannot seek to a previous directory position. Collect these entries.
 	entries, errno := lastDirEntries(dir, cookie)
 	if errno != ErrnoSuccess {
-		return errno
+		return 0, errno
 	}
 
 	// Check if we have maxDirEntries, and read more from the FS as needed.
 	if entryCount := len(entries); entryCount < maxDirEntries {
 		if l, err := rd.ReadDir(maxDirEntries - entryCount); err != io.EOF {
 			if err != nil {
-				return ErrnoIo
+				return 0, ErrnoIo
 			}
 			dir.CountRead += uint64(len(l))
 			entries = append(entries, l...)
@@ -760,16 +766,13 @@ func fdReaddirFn(ctx context.Context, mod api.Module, params []uint64) Errno {
 
 		dirents, ok := mem.Read(ctx, buf, bufused)
 		if !ok {
-			return ErrnoFault
+			return 0, ErrnoFault
 		}
 
 		writeDirents(entries, direntCount, writeTruncatedEntry, dirents, d_next)
 	}
 
-	if !mem.WriteUint32Le(ctx, resultBufused, bufused) {
-		return ErrnoFault
-	}
-	return ErrnoSuccess
+	return bufused, ErrnoSuccess
 }
 
 const largestDirent = int64(math.MaxUint32 - direntSize)
@@ -983,40 +986,40 @@ var fdRenumber = stubFunction(fdRenumberName, []wasm.ValueType{i32, i32}, "fd",
 //
 // See io.Seeker
 // and https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_seek
-var fdSeek = newHostFunc(
-	fdSeekName, fdSeekFn,
-	[]api.ValueType{i32, i64, i32, i32},
-	"fd", "offset", "whence", "result.newoffset",
-)
-
-func fdSeekFn(ctx context.Context, mod api.Module, params []uint64) Errno {
+var fdSeek = proxyResultParams(&wasm.HostFunc{
+	Name:        "fdSeek",
+	ParamTypes:  []api.ValueType{i32, i64, i32},
+	ParamNames:  []string{"fd", "offset", "whence"},
+	ResultTypes: []api.ValueType{i64, i32},
+	ResultNames: []string{"newoffset", "errno"},
+	Code:        &wasm.Code{IsHostFunction: true, GoFunc: i64ResultParam(fdSeekFn)},
+}, fdSeekName)
+
+func fdSeekFn(ctx context.Context, mod api.Module, stack []uint64) (int64, Errno) {
 	sysCtx := mod.(*wasm.CallContext).Sys
-	fd := uint32(params[0])
-	offset := params[1]
-	whence := uint32(params[2])
-	resultNewoffset := uint32(params[3])
+	fd := uint32(stack[0])
+	offset := stack[1]
+	whence := uint32(stack[2])
 
 	var seeker io.Seeker
 	// Check to see if the file descriptor is available
 	if f, ok := sysCtx.FS(ctx).OpenedFile(ctx, fd); !ok || f.File == nil {
-		return ErrnoBadf
+		return 0, 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 {
-		return ErrnoBadf
+		return 0, ErrnoBadf
 	}
 
 	if whence > io.SeekEnd /* exceeds the largest valid whence */ {
-		return ErrnoInval
+		return 0, ErrnoInval
 	}
 
 	newOffset, err := seeker.Seek(int64(offset), int(whence))
 	if err != nil {
-		return ErrnoIo
+		return 0, ErrnoIo
 	}
-	if !mod.Memory().WriteUint64Le(ctx, resultNewoffset, uint64(newOffset)) {
-		return ErrnoFault
-	}
-	return ErrnoSuccess
+
+	return newOffset, ErrnoSuccess
 }
 
 // fdSync is the WASI function named fdSyncName which synchronizes the data
@@ -1089,22 +1092,24 @@ var fdTell = stubFunction(fdTellName, []wasm.ValueType{i32, i32}, "fd", "result.
 // See fdRead
 // https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#ciovec
 // and https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_write
-var fdWrite = newHostFunc(
-	fdWriteName, fdWriteFn,
-	[]api.ValueType{i32, i32, i32, i32},
-	"fd", "iovs", "iovs_len", "result.nwritten",
-)
-
-func fdWriteFn(ctx context.Context, mod api.Module, params []uint64) Errno {
-	fd := uint32(params[0])
-	iovs := uint32(params[1])
-	iovsCount := uint32(params[2])
-	resultNwritten := uint32(params[3])
+var fdWrite = proxyResultParams(&wasm.HostFunc{
+	Name:        "fdWrite",
+	ParamTypes:  []api.ValueType{i32, i32, i32},
+	ParamNames:  []string{"fd", "iovs", "iovs_len"},
+	ResultTypes: []api.ValueType{i32, i32},
+	ResultNames: []string{"nwritten", "errno"},
+	Code:        &wasm.Code{IsHostFunction: true, GoFunc: u32ResultParam(fdWriteFn)},
+}, fdWriteName)
+
+func fdWriteFn(ctx context.Context, mod api.Module, stack []uint64) (uint32, Errno) {
+	fd := uint32(stack[0])
+	iovs := uint32(stack[1])
+	iovsCount := uint32(stack[2])
 
 	sysCtx := mod.(*wasm.CallContext).Sys
 	writer := internalsys.FdWriter(ctx, sysCtx, fd)
 	if writer == nil {
-		return ErrnoBadf
+		return 0, ErrnoBadf
 	}
 
 	var err error
@@ -1113,13 +1118,13 @@ func fdWriteFn(ctx context.Context, mod api.Module, params []uint64) Errno {
 		iov := iovs + i*8
 		offset, ok := mod.Memory().ReadUint32Le(ctx, iov)
 		if !ok {
-			return ErrnoFault
+			return 0, ErrnoFault
 		}
 		// Note: emscripten has been known to write zero length iovec. However,
 		// it is not common in other compilers, so we don't optimize for it.
 		l, ok := mod.Memory().ReadUint32Le(ctx, iov+4)
 		if !ok {
-			return ErrnoFault
+			return 0, ErrnoFault
 		}
 
 		var n int
@@ -1128,19 +1133,16 @@ func fdWriteFn(ctx context.Context, mod api.Module, params []uint64) Errno {
 		} else {
 			b, ok := mod.Memory().Read(ctx, offset, l)
 			if !ok {
-				return ErrnoFault
+				return 0, ErrnoFault
 			}
 			n, err = writer.Write(b)
 			if err != nil {
-				return ErrnoIo
+				return 0, ErrnoIo
 			}
 		}
 		nwritten += uint32(n)
 	}
-	if !mod.Memory().WriteUint32Le(ctx, resultNwritten, nwritten) {
-		return ErrnoFault
-	}
-	return ErrnoSuccess
+	return nwritten, ErrnoSuccess
 }
 
 // pathCreateDirectory is the WASI function named pathCreateDirectoryName
@@ -1187,6 +1189,8 @@ var pathFilestatGet = newHostFunc(
 	"fd", "flags", "path", "path_len", "result.buf",
 )
 
+// pathFilestatGetFn cannot currently use proxyResultParams because filestat is
+// larger than api.ValueTypeI64 (i64 == 8 bytes, but filestat is 64).
 func pathFilestatGetFn(ctx context.Context, mod api.Module, params []uint64) Errno {
 	sysCtx := mod.(*wasm.CallContext).Sys
 	fsc := sysCtx.FS(ctx)
@@ -1301,13 +1305,16 @@ var pathLink = stubFunction(
 //   - The returned file descriptor is not guaranteed to be the lowest-number
 //
 // See https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#path_open
-var pathOpen = newHostFunc(
-	pathOpenName, pathOpenFn,
-	[]api.ValueType{i32, i32, i32, i32, i32, i64, i64, i32, i32},
-	"fd", "dirflags", "path", "path_len", "oflags", "fs_rights_base", "fs_rights_inheriting", "fdflags", "result.opened_fd",
-)
-
-func pathOpenFn(ctx context.Context, mod api.Module, params []uint64) Errno {
+var pathOpen = proxyResultParams(&wasm.HostFunc{
+	Name:        "pathOpen",
+	ParamTypes:  []api.ValueType{i32, i32, i32, i32, i32, i64, i64, i32},
+	ParamNames:  []string{"fd", "dirflags", "path", "path_len", "oflags", "fs_rights_base", "fs_rights_inheriting", "fdflags"},
+	ResultTypes: []api.ValueType{i32, i32},
+	ResultNames: []string{"opened_fd", "errno"},
+	Code:        &wasm.Code{IsHostFunction: true, GoFunc: u32ResultParam(pathOpenFn)},
+}, pathOpenName)
+
+func pathOpenFn(ctx context.Context, mod api.Module, params []uint64) (uint32, Errno) {
 	sysCtx := mod.(*wasm.CallContext).Sys
 	fsc := sysCtx.FS(ctx)
 
@@ -1319,27 +1326,22 @@ func pathOpenFn(ctx context.Context, mod api.Module, params []uint64) Errno {
 	// rights aren't used
 	_, _ = params[5], params[6]
 	_ /* fdflags */ = uint32(params[7])
-	resultOpenedFd := uint32(params[8])
 
 	if _, ok := fsc.OpenedFile(ctx, fd); !ok {
-		return ErrnoBadf
+		return 0, ErrnoBadf
 	}
 
 	b, ok := mod.Memory().Read(ctx, path, pathLen)
 	if !ok {
-		return ErrnoFault
+		return 0, ErrnoFault
 	}
 
 	newFD, errnoResult := openFile(ctx, fsc, string(b))
 	if errnoResult != ErrnoSuccess {
-		return errnoResult
+		return 0, errnoResult
 	}
 
-	if !mod.Memory().WriteUint32Le(ctx, resultOpenedFd, newFD) {
-		_ = fsc.CloseFile(ctx, newFD)
-		return ErrnoFault
-	}
-	return ErrnoSuccess
+	return newFD, ErrnoSuccess
 }
 
 // pathReadlink is the WASI function named pathReadlinkName that reads the
diff --git a/imports/wasi_snapshot_preview1/poll.go b/imports/wasi_snapshot_preview1/poll.go
index 5055f498..35f2bcb0 100644
--- a/imports/wasi_snapshot_preview1/poll.go
+++ b/imports/wasi_snapshot_preview1/poll.go
@@ -46,20 +46,22 @@ const (
 //
 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#poll_oneoff
 // See https://linux.die.net/man/3/poll
-var pollOneoff = newHostFunc(
-	pollOneoffName, pollOneoffFn,
-	[]api.ValueType{i32, i32, i32, i32},
-	"in", "out", "nsubscriptions", "result.nevents",
-)
-
-func pollOneoffFn(ctx context.Context, mod api.Module, params []uint64) Errno {
+var pollOneoff = proxyResultParams(&wasm.HostFunc{
+	Name:        "pollOneoff",
+	ParamTypes:  []api.ValueType{i32, i32, i32},
+	ParamNames:  []string{"in", "out", "nsubscriptions"},
+	ResultTypes: []api.ValueType{i32, i32},
+	ResultNames: []string{"nevents", "errno"},
+	Code:        &wasm.Code{IsHostFunction: true, GoFunc: u32ResultParam(pollOneoffFn)},
+}, pollOneoffName)
+
+func pollOneoffFn(ctx context.Context, mod api.Module, params []uint64) (nevents uint32, errno Errno) {
 	in := uint32(params[0])
 	out := uint32(params[1])
 	nsubscriptions := uint32(params[2])
-	resultNevents := uint32(params[3])
 
 	if nsubscriptions == 0 {
-		return ErrnoInval
+		return 0, ErrnoInval
 	}
 
 	mem := mod.Memory()
@@ -67,26 +69,20 @@ func pollOneoffFn(ctx context.Context, mod api.Module, params []uint64) Errno {
 	// Ensure capacity prior to the read loop to reduce error handling.
 	inBuf, ok := mem.Read(ctx, in, nsubscriptions*48)
 	if !ok {
-		return ErrnoFault
+		return 0, 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
+		return 0, ErrnoFault
 	}
 
 	// Loop through all subscriptions and write their output.
-	for sub := uint32(0); sub < nsubscriptions; sub++ {
-		inOffset := sub * 48
-		outOffset := sub * 32
+	for ; nevents < nsubscriptions; nevents++ {
+		inOffset := nevents * 48
+		outOffset := nevents * 32
 
-		var errno Errno
 		eventType := inBuf[inOffset+8] // +8 past userdata
+		var errno Errno                // errno for this specific event
 		switch eventType {
 		case eventTypeClock: // handle later
 			// +8 past userdata +8 name alignment
@@ -95,7 +91,7 @@ func pollOneoffFn(ctx context.Context, mod api.Module, params []uint64) Errno {
 			// +8 past userdata +4 FD alignment
 			errno = processFDEvent(ctx, mod, eventType, inBuf[inOffset+8+4:])
 		default:
-			return ErrnoInval
+			return 0, ErrnoInval
 		}
 
 		// Write the event corresponding to the processed subscription.
@@ -106,7 +102,7 @@ func pollOneoffFn(ctx context.Context, mod api.Module, params []uint64) Errno {
 		le.PutUint32(outBuf[outOffset+10:], uint32(eventType))
 		// TODO: When FD events are supported, write outOffset+16
 	}
-	return ErrnoSuccess
+	return nevents, ErrnoSuccess
 }
 
 // processClockEvent supports only relative name events, as that's what's used
diff --git a/imports/wasi_snapshot_preview1/wasi.go b/imports/wasi_snapshot_preview1/wasi.go
index adb26b16..848b6f81 100644
--- a/imports/wasi_snapshot_preview1/wasi.go
+++ b/imports/wasi_snapshot_preview1/wasi.go
@@ -171,11 +171,11 @@ func exportFunctions(builder wazero.HostModuleBuilder) {
 	// map can't guarantee that.
 	// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#functions
 	exporter.ExportHostFunc(argsGet)
-	exporter.ExportHostFunc(argsSizesGet)
+	exporter.(wasm.ProxyFuncExporter).ExportProxyFunc(argsSizesGet)
 	exporter.ExportHostFunc(environGet)
-	exporter.ExportHostFunc(environSizesGet)
-	exporter.ExportHostFunc(clockResGet)
-	exporter.ExportHostFunc(clockTimeGet)
+	exporter.(wasm.ProxyFuncExporter).ExportProxyFunc(environSizesGet)
+	exporter.(wasm.ProxyFuncExporter).ExportProxyFunc(clockResGet)
+	exporter.(wasm.ProxyFuncExporter).ExportProxyFunc(clockTimeGet)
 	exporter.ExportHostFunc(fdAdvise)
 	exporter.ExportHostFunc(fdAllocate)
 	exporter.ExportHostFunc(fdClose)
@@ -186,28 +186,28 @@ func exportFunctions(builder wazero.HostModuleBuilder) {
 	exporter.ExportHostFunc(fdFilestatGet)
 	exporter.ExportHostFunc(fdFilestatSetSize)
 	exporter.ExportHostFunc(fdFilestatSetTimes)
-	exporter.ExportHostFunc(fdPread)
-	exporter.ExportHostFunc(fdPrestatGet)
+	exporter.(wasm.ProxyFuncExporter).ExportProxyFunc(fdPread)
+	exporter.(wasm.ProxyFuncExporter).ExportProxyFunc(fdPrestatGet)
 	exporter.ExportHostFunc(fdPrestatDirName)
 	exporter.ExportHostFunc(fdPwrite)
-	exporter.ExportHostFunc(fdRead)
-	exporter.ExportHostFunc(fdReaddir)
+	exporter.(wasm.ProxyFuncExporter).ExportProxyFunc(fdRead)
+	exporter.(wasm.ProxyFuncExporter).ExportProxyFunc(fdReaddir)
 	exporter.ExportHostFunc(fdRenumber)
-	exporter.ExportHostFunc(fdSeek)
+	exporter.(wasm.ProxyFuncExporter).ExportProxyFunc(fdSeek)
 	exporter.ExportHostFunc(fdSync)
 	exporter.ExportHostFunc(fdTell)
-	exporter.ExportHostFunc(fdWrite)
+	exporter.(wasm.ProxyFuncExporter).ExportProxyFunc(fdWrite)
 	exporter.ExportHostFunc(pathCreateDirectory)
 	exporter.ExportHostFunc(pathFilestatGet)
 	exporter.ExportHostFunc(pathFilestatSetTimes)
 	exporter.ExportHostFunc(pathLink)
-	exporter.ExportHostFunc(pathOpen)
+	exporter.(wasm.ProxyFuncExporter).ExportProxyFunc(pathOpen)
 	exporter.ExportHostFunc(pathReadlink)
 	exporter.ExportHostFunc(pathRemoveDirectory)
 	exporter.ExportHostFunc(pathRename)
 	exporter.ExportHostFunc(pathSymlink)
 	exporter.ExportHostFunc(pathUnlinkFile)
-	exporter.ExportHostFunc(pollOneoff)
+	exporter.(wasm.ProxyFuncExporter).ExportProxyFunc(pollOneoff)
 	exporter.ExportHostFunc(procExit)
 	exporter.ExportHostFunc(procRaise)
 	exporter.ExportHostFunc(schedYield)
diff --git a/imports/wasi_snapshot_preview1/wrapper.go b/imports/wasi_snapshot_preview1/wrapper.go
new file mode 100644
index 00000000..9ef429e9
--- /dev/null
+++ b/imports/wasi_snapshot_preview1/wrapper.go
@@ -0,0 +1,239 @@
+package wasi_snapshot_preview1
+
+import (
+	"context"
+	"fmt"
+
+	"github.com/tetratelabs/wazero/api"
+	"github.com/tetratelabs/wazero/internal/leb128"
+	"github.com/tetratelabs/wazero/internal/wasm"
+)
+
+type u32ResultParam func(ctx context.Context, mod api.Module, stack []uint64) (uint32, Errno)
+
+// Call implements the same method as documented on api.GoModuleFunc.
+func (f u32ResultParam) Call(ctx context.Context, mod api.Module, stack []uint64) {
+	r1, errno := f(ctx, mod, stack)
+	stack[0] = uint64(r1)
+	stack[1] = uint64(errno)
+}
+
+type i64ResultParam func(ctx context.Context, mod api.Module, stack []uint64) (int64, Errno)
+
+// Call implements the same method as documented on api.GoModuleFunc.
+func (f i64ResultParam) Call(ctx context.Context, mod api.Module, stack []uint64) {
+	r1, errno := f(ctx, mod, stack)
+	stack[0] = uint64(r1)
+	stack[1] = uint64(errno)
+}
+
+type u64ResultParam func(ctx context.Context, mod api.Module, stack []uint64) (uint64, Errno)
+
+// Call implements the same method as documented on api.GoModuleFunc.
+func (f u64ResultParam) Call(ctx context.Context, mod api.Module, stack []uint64) {
+	r1, errno := f(ctx, mod, stack)
+	stack[0] = r1
+	stack[1] = uint64(errno)
+}
+
+type u32u32ResultParam func(ctx context.Context, mod api.Module, stack []uint64) (r1, r2 uint32, errno Errno)
+
+// Call implements the same method as documented on api.GoModuleFunc.
+func (f u32u32ResultParam) Call(ctx context.Context, mod api.Module, stack []uint64) {
+	r1, r2, errno := f(ctx, mod, stack)
+	stack[0] = uint64(r1)
+	stack[1] = uint64(r2)
+	stack[2] = uint64(errno)
+}
+
+// memBytes adds an i32 value of memory[0]'s size in bytes to the stack
+var memBytes = append(append([]byte{wasm.OpcodeMemorySize, 0, wasm.OpcodeI32Const}, leb128.EncodeInt32(int32(wasm.MemoryPageSize))...), wasm.OpcodeI32Mul)
+
+const (
+	nilType = 0x40
+	success = byte(ErrnoSuccess)
+	fault   = byte(ErrnoFault)
+	// fakeFuncIdx is a placeholder as it is replaced later.
+	fakeFuncIdx = byte(0)
+)
+
+// proxyResultParams defines a function in wasm that has out parameters for each
+// result in the goFunc that isn't Errno. An out parameter is an i32 of the
+// memory offset to write the result length.
+//
+// # Example
+//
+// For example, if goFunc has this signature:
+//
+//	(func $argsSizesGet
+//	  (result (; argc ;) i32)
+//	  (result (; argv_len ;) i32)
+//	  (result (; errno ;) i32) ...)
+//
+// This would write the following signature:
+//
+//	(func (export "args_sizes_get")
+//	  (param $result.argc_len i32)
+//	  (param $result.argv_len i32)
+//	  (result (; errno ;) i32) ...)
+//
+// "args_sizes_get" would verify the memory offsets of the "result.*"
+// parameters, then call "argsSizesGet". The first two results would be written
+// to the corresponding "result.*" parameters if the errno is ErrnoSuccess. In
+// any case the errno is propagated to the caller of "args_sizes_get".
+//
+// # Why does this exist
+//
+// Proxying allows less code in the go-defined function, and also allows the
+// natural results to be visible to logging or otherwise interceptors. This
+// helps in troubleshooting, particularly as some WASI results are inputs to
+// subsequent functions, and when invalid can cause errors difficult to
+// diagnose. For example, the interceptor could see an api.ValueTypeI32 result
+// representing bufused and write it to the log. Without this, the same
+// interceptor would need access to wasm memory to read the value, and it would
+// be generally impossible get insight without a debugger.
+func proxyResultParams(goFunc *wasm.HostFunc, exportName string) *wasm.ProxyFunc {
+	// result params are those except errno
+	outLen := len(goFunc.ResultTypes) - 1
+	outTypes := goFunc.ResultTypes[:outLen]
+
+	// Conventionally, we name the out parameters result.X
+	outParamNames := make([]string, outLen)
+	for i := 0; i < outLen; i++ {
+		outParamNames[i] = "result." + goFunc.ResultNames[i]
+	}
+
+	// outParams are i32, even if the corresponding outParamLengths are not,
+	// because memory offsets are i32.
+	outParamTypes := make([]api.ValueType, outLen)
+	for i := 0; i < outLen; i++ {
+		outParamTypes[i] = i32
+	}
+	outParamIdx := len(goFunc.ParamTypes)
+
+	proxy := &wasm.HostFunc{
+		ExportNames: []string{exportName},
+		Name:        exportName,
+		ParamTypes:  append(goFunc.ParamTypes, outParamTypes...),
+		ParamNames:  append(goFunc.ParamNames, outParamNames...),
+		ResultTypes: []api.ValueType{i32},
+		ResultNames: []string{"errno"},
+		Code:        &wasm.Code{IsHostFunction: true, LocalTypes: []api.ValueType{i32}},
+	}
+
+	// Determine the index of a temporary local used for i32 manipulation. This
+	// will be directly after the parameters.
+	localValIdxI32 := byte(len(proxy.ParamTypes))
+
+	// Only allocate an i64 local if an out type is i64
+	localI64Temp := localValIdxI32 + 1
+	for _, t := range outTypes {
+		if t == i64 {
+			proxy.Code.LocalTypes = append(proxy.Code.LocalTypes, i64)
+			break
+		}
+	}
+
+	// Get the current memory size in bytes, to validate parameters.
+	body := memBytes                                         // stack: [memSizeBytes]
+	body = append(body, wasm.OpcodeLocalTee, localValIdxI32) // stack: [memSizeBytes]
+
+	// Write code to verify out parameters are in range to write values.
+	// Notably, this validates prior to calling the host function.
+	body = append(body, compileMustValidMemOffset(outParamIdx, outTypes[0])...)
+	for i := 1; i < outLen; i++ {
+		body = append(body, wasm.OpcodeLocalGet, localValIdxI32) // stack: [memSizeBytes]
+		body = append(body, compileMustValidMemOffset(outParamIdx+i, outTypes[i])...)
+	}
+
+	// Now, it is safe to call the go function
+	for i := range goFunc.ParamTypes {
+		body = append(body, wasm.OpcodeLocalGet, byte(i)) // stack: [p1, p2...]
+	}
+	body = append(body, wasm.OpcodeCall, fakeFuncIdx) //     stack: [r1, r2... errno]
+	// Capture the position in the wasm ProxyFunc rewrites with a real index.
+	fakeFuncIdxPos := len(body) - 1
+
+	// On failure, return the errno
+	body = append(body, compileMustErrnoSuccess(localValIdxI32)...) // stack: [r1, r2...]
+
+	// On success, results to write to memory are in backwards order.
+	for i := outLen - 1; i >= 0; i-- {
+		outType := outTypes[i]
+		localValIdx := localValIdxI32
+		switch outType {
+		case i32:
+		case i64:
+			localValIdx = localI64Temp
+		default:
+			panic(fmt.Errorf("TODO: unsupported outType: %v", outType))
+		}
+		localOffsetIdx := byte(outParamIdx + i)
+		body = append(body, compileStore(localOffsetIdx, localValIdx, outType)...)
+	}
+
+	// Finally, add the success error code to the stack.
+	body = append(body,
+		wasm.OpcodeI32Const, success, // stack: [success]
+		wasm.OpcodeEnd,
+	)
+
+	// Assign the wasm generated as the proxy's body
+	proxy.Code.Body = body
+	return &wasm.ProxyFunc{Proxy: proxy, Proxied: goFunc, CallBodyPos: fakeFuncIdxPos}
+}
+
+// compileMustErrnoSuccess returns the stack top value if it isn't
+// ErrnoSuccess.
+func compileMustErrnoSuccess(localI32Temp byte) []byte {
+	// copy the errno to a local, so we can return it later if needed
+	return []byte{
+		wasm.OpcodeLocalTee, localI32Temp, // stack: [errno]
+		wasm.OpcodeI32Const, success, //      stack: [errno, success]
+
+		// If the result wasn't success, return errno.
+		wasm.OpcodeI32Ne, wasm.OpcodeIf, nilType, // stack: []
+		wasm.OpcodeLocalGet, localI32Temp, //        stack: [errno]
+		wasm.OpcodeReturn, wasm.OpcodeEnd, //        stack: []
+	}
+}
+
+// compileMustValidMemOffset returns ErrnoFault if params[paramIdx]+4
+// exceeds available memory (without growing). The stack top value must be
+// memories[0] size in bytes.
+func compileMustValidMemOffset(paramIdx int, outType api.ValueType) []byte {
+	byteLength := 4
+	switch outType {
+	case i32:
+	case i64:
+		byteLength = 8
+	default:
+		panic(fmt.Errorf("TODO: unsupported outType: %v", outType))
+	}
+	return []byte{
+		wasm.OpcodeI32Const, byte(byteLength), wasm.OpcodeI32Sub, // stack: [memBytes-byteLength]
+		wasm.OpcodeLocalGet, byte(paramIdx), //       stack: [memBytes-byteLength, $0]
+		wasm.OpcodeI32LtU, wasm.OpcodeIf, nilType, // stack: []
+		wasm.OpcodeI32Const, fault, //                stack: [efault]
+		wasm.OpcodeReturn, wasm.OpcodeEnd, //         stack: []
+	}
+}
+
+// compileStore stores stack top stack value to the memory offset in
+// locals[localIdx]. This can't exceed available memory due to previous
+// validation in compileMustValidMemOffset.
+func compileStore(localOffsetIdx, localValIdx byte, outType api.ValueType) []byte {
+	body := []byte{
+		wasm.OpcodeLocalSet, localValIdx, //    stack: []
+		wasm.OpcodeLocalGet, localOffsetIdx, // stack: [offset]
+		wasm.OpcodeLocalGet, localValIdx, //    stack: [offset, v]
+	}
+	switch outType {
+	case i32:
+		return append(body, wasm.OpcodeI32Store, 0x2, 0x0) // stack: []
+	case i64:
+		return append(body, wasm.OpcodeI64Store, 0x3, 0x0) // stack: []
+	default:
+		panic(fmt.Errorf("TODO: unsupported outType: %v", outType))
+	}
+}


Test patch:
diff --git a/imports/wasi_snapshot_preview1/args_test.go b/imports/wasi_snapshot_preview1/args_test.go
index 087d61f6..8b136e57 100644
--- a/imports/wasi_snapshot_preview1/args_test.go
+++ b/imports/wasi_snapshot_preview1/args_test.go
@@ -11,29 +11,29 @@ func Test_argsGet(t *testing.T) {
 	mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithArgs("a", "bc"))
 	defer r.Close(testCtx)
 
-	argv := uint32(7)    // arbitrary offset
-	argvBuf := uint32(1) // arbitrary offset
+	argvBuf := uint32(16) // arbitrary offset
+	argv := uint32(22)    // arbitrary offset
 	expectedMemory := []byte{
 		'?',                 // argvBuf is after this
 		'a', 0, 'b', 'c', 0, // null terminated "a", "bc"
-		'?',        // argv is after this
-		1, 0, 0, 0, // little endian-encoded offset of "a"
-		3, 0, 0, 0, // little endian-encoded offset of "bc"
+		'?',         // argv is after this
+		16, 0, 0, 0, // little endian-encoded offset of "a"
+		18, 0, 0, 0, // little endian-encoded offset of "bc"
 		'?', // stopped after encoding
 	}
 
-	maskMemory(t, testCtx, mod, len(expectedMemory))
+	maskMemory(t, testCtx, mod, len(expectedMemory)+int(argvBuf))
 
 	// Invoke argsGet and check the memory side effects.
 	requireErrno(t, ErrnoSuccess, mod, argsGetName, uint64(argv), uint64(argvBuf))
 	require.Equal(t, `
---> proxy.args_get(argv=7,argv_buf=1)
-	==> wasi_snapshot_preview1.args_get(argv=7,argv_buf=1)
+--> proxy.args_get(argv=22,argv_buf=16)
+	==> wasi_snapshot_preview1.args_get(argv=22,argv_buf=16)
 	<== ESUCCESS
 <-- 0
 `, "\n"+log.String())
 
-	actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(expectedMemory)))
+	actual, ok := mod.Memory().Read(testCtx, argvBuf-1, uint32(len(expectedMemory)))
 	require.True(t, ok)
 	require.Equal(t, expectedMemory, actual)
 }
@@ -43,7 +43,7 @@ func Test_argsGet_Errors(t *testing.T) {
 	defer r.Close(testCtx)
 
 	memorySize := mod.Memory().Size(testCtx)
-	validAddress := uint32(0) // arbitrary valid address as arguments to args_get. We chose 0 here.
+	validAddress := uint32(0) // arbitrary
 
 	tests := []struct {
 		name          string
@@ -114,8 +114,8 @@ func Test_argsSizesGet(t *testing.T) {
 	mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithArgs("a", "bc"))
 	defer r.Close(testCtx)
 
-	resultArgc := uint32(1)    // arbitrary offset
-	resultArgvLen := uint32(6) // arbitrary offset
+	resultArgc := uint32(16)    // arbitrary offset
+	resultArgvLen := uint32(21) // arbitrary offset
 	expectedMemory := []byte{
 		'?',                // resultArgc is after this
 		0x2, 0x0, 0x0, 0x0, // little endian-encoded arg count
@@ -124,18 +124,20 @@ func Test_argsSizesGet(t *testing.T) {
 		'?', // stopped after encoding
 	}
 
-	maskMemory(t, testCtx, mod, len(expectedMemory))
+	maskMemory(t, testCtx, mod, int(resultArgc)+len(expectedMemory))
 
 	// Invoke argsSizesGet and check the memory side effects.
 	requireErrno(t, ErrnoSuccess, mod, argsSizesGetName, uint64(resultArgc), uint64(resultArgvLen))
 	require.Equal(t, `
---> proxy.args_sizes_get(result.argc=1,result.argv_len=6)
-	==> wasi_snapshot_preview1.args_sizes_get(result.argc=1,result.argv_len=6)
-	<== ESUCCESS
+--> proxy.args_sizes_get(result.argc=16,result.argv_len=21)
+	--> wasi_snapshot_preview1.args_sizes_get(result.argc=16,result.argv_len=21)
+		==> wasi_snapshot_preview1.argsSizesGet()
+		<== (argc=2,argv_len=5,ESUCCESS)
+	<-- ESUCCESS
 <-- 0
 `, "\n"+log.String())
 
-	actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(expectedMemory)))
+	actual, ok := mod.Memory().Read(testCtx, resultArgc-1, uint32(len(expectedMemory)))
 	require.True(t, ok)
 	require.Equal(t, expectedMemory, actual)
 }
@@ -158,8 +160,8 @@ func Test_argsSizesGet_Errors(t *testing.T) {
 			argvLen: validAddress,
 			expectedLog: `
 --> proxy.args_sizes_get(result.argc=65536,result.argv_len=0)
-	==> wasi_snapshot_preview1.args_sizes_get(result.argc=65536,result.argv_len=0)
-	<== EFAULT
+	--> wasi_snapshot_preview1.args_sizes_get(result.argc=65536,result.argv_len=0)
+	<-- EFAULT
 <-- 21
 `,
 		},
@@ -169,8 +171,8 @@ func Test_argsSizesGet_Errors(t *testing.T) {
 			argvLen: memorySize,
 			expectedLog: `
 --> proxy.args_sizes_get(result.argc=0,result.argv_len=65536)
-	==> wasi_snapshot_preview1.args_sizes_get(result.argc=0,result.argv_len=65536)
-	<== EFAULT
+	--> wasi_snapshot_preview1.args_sizes_get(result.argc=0,result.argv_len=65536)
+	<-- EFAULT
 <-- 21
 `,
 		},
@@ -180,8 +182,8 @@ func Test_argsSizesGet_Errors(t *testing.T) {
 			argvLen: validAddress,
 			expectedLog: `
 --> proxy.args_sizes_get(result.argc=65533,result.argv_len=0)
-	==> wasi_snapshot_preview1.args_sizes_get(result.argc=65533,result.argv_len=0)
-	<== EFAULT
+	--> wasi_snapshot_preview1.args_sizes_get(result.argc=65533,result.argv_len=0)
+	<-- EFAULT
 <-- 21
 `,
 		},
@@ -191,8 +193,8 @@ func Test_argsSizesGet_Errors(t *testing.T) {
 			argvLen: memorySize - 4 + 1, // 4 is count of bytes to encode uint32le
 			expectedLog: `
 --> proxy.args_sizes_get(result.argc=0,result.argv_len=65533)
-	==> wasi_snapshot_preview1.args_sizes_get(result.argc=0,result.argv_len=65533)
-	<== EFAULT
+	--> wasi_snapshot_preview1.args_sizes_get(result.argc=0,result.argv_len=65533)
+	<-- EFAULT
 <-- 21
 `,
 		},
diff --git a/imports/wasi_snapshot_preview1/clock_test.go b/imports/wasi_snapshot_preview1/clock_test.go
index 1cee43d1..76af78f8 100644
--- a/imports/wasi_snapshot_preview1/clock_test.go
+++ b/imports/wasi_snapshot_preview1/clock_test.go
@@ -35,9 +35,11 @@ func Test_clockResGet(t *testing.T) {
 			clockID:        clockIDRealtime,
 			expectedMemory: expectedMemoryMicro,
 			expectedLog: `
---> proxy.clock_res_get(id=0,result.resolution=1)
-	==> wasi_snapshot_preview1.clock_res_get(id=0,result.resolution=1)
-	<== ESUCCESS
+--> proxy.clock_res_get(id=0,result.resolution=16)
+	--> wasi_snapshot_preview1.clock_res_get(id=0,result.resolution=16)
+		==> wasi_snapshot_preview1.clockResGet(id=0)
+		<== (resolution=1000,ESUCCESS)
+	<-- ESUCCESS
 <-- 0
 `,
 		},
@@ -46,9 +48,11 @@ func Test_clockResGet(t *testing.T) {
 			clockID:        clockIDMonotonic,
 			expectedMemory: expectedMemoryNano,
 			expectedLog: `
---> proxy.clock_res_get(id=1,result.resolution=1)
-	==> wasi_snapshot_preview1.clock_res_get(id=1,result.resolution=1)
-	<== ESUCCESS
+--> proxy.clock_res_get(id=1,result.resolution=16)
+	--> wasi_snapshot_preview1.clock_res_get(id=1,result.resolution=16)
+		==> wasi_snapshot_preview1.clockResGet(id=1)
+		<== (resolution=1,ESUCCESS)
+	<-- ESUCCESS
 <-- 0
 `,
 		},
@@ -60,13 +64,13 @@ func Test_clockResGet(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			defer log.Reset()
 
-			maskMemory(t, testCtx, mod, len(tc.expectedMemory))
+			resultResolution := 16 // arbitrary offset
+			maskMemory(t, testCtx, mod, resultResolution+len(tc.expectedMemory))
 
-			resultResolution := uint32(1) // arbitrary offset
 			requireErrno(t, ErrnoSuccess, mod, clockResGetName, uint64(tc.clockID), uint64(resultResolution))
 			require.Equal(t, tc.expectedLog, "\n"+log.String())
 
-			actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(tc.expectedMemory)))
+			actual, ok := mod.Memory().Read(testCtx, uint32(resultResolution-1), uint32(len(tc.expectedMemory)))
 			require.True(t, ok)
 			require.Equal(t, tc.expectedMemory, actual)
 		})
@@ -88,9 +92,11 @@ func Test_clockResGet_Unsupported(t *testing.T) {
 			clockID:       2,
 			expectedErrno: ErrnoInval,
 			expectedLog: `
---> proxy.clock_res_get(id=2,result.resolution=1)
-	==> wasi_snapshot_preview1.clock_res_get(id=2,result.resolution=1)
-	<== EINVAL
+--> proxy.clock_res_get(id=2,result.resolution=16)
+	--> wasi_snapshot_preview1.clock_res_get(id=2,result.resolution=16)
+		==> wasi_snapshot_preview1.clockResGet(id=2)
+		<== (resolution=0,EINVAL)
+	<-- EINVAL
 <-- 28
 `,
 		},
@@ -99,9 +105,11 @@ func Test_clockResGet_Unsupported(t *testing.T) {
 			clockID:       3,
 			expectedErrno: ErrnoInval,
 			expectedLog: `
---> proxy.clock_res_get(id=3,result.resolution=1)
-	==> wasi_snapshot_preview1.clock_res_get(id=3,result.resolution=1)
-	<== EINVAL
+--> proxy.clock_res_get(id=3,result.resolution=16)
+	--> wasi_snapshot_preview1.clock_res_get(id=3,result.resolution=16)
+		==> wasi_snapshot_preview1.clockResGet(id=3)
+		<== (resolution=0,EINVAL)
+	<-- EINVAL
 <-- 28
 `,
 		},
@@ -110,9 +118,11 @@ func Test_clockResGet_Unsupported(t *testing.T) {
 			clockID:       100,
 			expectedErrno: ErrnoInval,
 			expectedLog: `
---> proxy.clock_res_get(id=100,result.resolution=1)
-	==> wasi_snapshot_preview1.clock_res_get(id=100,result.resolution=1)
-	<== EINVAL
+--> proxy.clock_res_get(id=100,result.resolution=16)
+	--> wasi_snapshot_preview1.clock_res_get(id=100,result.resolution=16)
+		==> wasi_snapshot_preview1.clockResGet(id=100)
+		<== (resolution=0,EINVAL)
+	<-- EINVAL
 <-- 28
 `,
 		},
@@ -124,7 +134,7 @@ func Test_clockResGet_Unsupported(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			defer log.Reset()
 
-			resultResolution := uint32(1) // arbitrary offset
+			resultResolution := 16 // arbitrary offset
 			requireErrno(t, tc.expectedErrno, mod, clockResGetName, uint64(tc.clockID), uint64(resultResolution))
 			require.Equal(t, tc.expectedLog, "\n"+log.String())
 		})
@@ -150,9 +160,11 @@ func Test_clockTimeGet(t *testing.T) {
 				'?', // stopped after encoding
 			},
 			expectedLog: `
---> proxy.clock_time_get(id=0,precision=0,result.timestamp=1)
-	==> wasi_snapshot_preview1.clock_time_get(id=0,precision=0,result.timestamp=1)
-	<== ESUCCESS
+--> proxy.clock_time_get(id=0,precision=0,result.timestamp=16)
+	--> wasi_snapshot_preview1.clock_time_get(id=0,precision=0,result.timestamp=16)
+		==> wasi_snapshot_preview1.clockTimeGet(id=0,precision=0)
+		<== (timestamp=1640995200000000000,ESUCCESS)
+	<-- ESUCCESS
 <-- 0
 `,
 		},
@@ -165,9 +177,11 @@ func Test_clockTimeGet(t *testing.T) {
 				'?', // stopped after encoding
 			},
 			expectedLog: `
---> proxy.clock_time_get(id=1,precision=0,result.timestamp=1)
-	==> wasi_snapshot_preview1.clock_time_get(id=1,precision=0,result.timestamp=1)
-	<== ESUCCESS
+--> proxy.clock_time_get(id=1,precision=0,result.timestamp=16)
+	--> wasi_snapshot_preview1.clock_time_get(id=1,precision=0,result.timestamp=16)
+		==> wasi_snapshot_preview1.clockTimeGet(id=1,precision=0)
+		<== (timestamp=0,ESUCCESS)
+	<-- ESUCCESS
 <-- 0
 `,
 		},
@@ -178,13 +192,13 @@ func Test_clockTimeGet(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			defer log.Reset()
 
-			maskMemory(t, testCtx, mod, len(tc.expectedMemory))
+			resultTimestamp := 16 // arbitrary offset
+			maskMemory(t, testCtx, mod, resultTimestamp+len(tc.expectedMemory))
 
-			resultTimestamp := uint32(1) // arbitrary offset
 			requireErrno(t, ErrnoSuccess, mod, clockTimeGetName, uint64(tc.clockID), 0 /* TODO: precision */, uint64(resultTimestamp))
 			require.Equal(t, tc.expectedLog, "\n"+log.String())
 
-			actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(tc.expectedMemory)))
+			actual, ok := mod.Memory().Read(testCtx, uint32(resultTimestamp-1), uint32(len(tc.expectedMemory)))
 			require.True(t, ok)
 			require.Equal(t, tc.expectedMemory, actual)
 		})
@@ -206,9 +220,11 @@ func Test_clockTimeGet_Unsupported(t *testing.T) {
 			clockID:       2,
 			expectedErrno: ErrnoInval,
 			expectedLog: `
---> proxy.clock_time_get(id=2,precision=0,result.timestamp=1)
-	==> wasi_snapshot_preview1.clock_time_get(id=2,precision=0,result.timestamp=1)
-	<== EINVAL
+--> proxy.clock_time_get(id=2,precision=0,result.timestamp=16)
+	--> wasi_snapshot_preview1.clock_time_get(id=2,precision=0,result.timestamp=16)
+		==> wasi_snapshot_preview1.clockTimeGet(id=2,precision=0)
+		<== (timestamp=0,EINVAL)
+	<-- EINVAL
 <-- 28
 `,
 		},
@@ -217,9 +233,11 @@ func Test_clockTimeGet_Unsupported(t *testing.T) {
 			clockID:       3,
 			expectedErrno: ErrnoInval,
 			expectedLog: `
---> proxy.clock_time_get(id=3,precision=0,result.timestamp=1)
-	==> wasi_snapshot_preview1.clock_time_get(id=3,precision=0,result.timestamp=1)
-	<== EINVAL
+--> proxy.clock_time_get(id=3,precision=0,result.timestamp=16)
+	--> wasi_snapshot_preview1.clock_time_get(id=3,precision=0,result.timestamp=16)
+		==> wasi_snapshot_preview1.clockTimeGet(id=3,precision=0)
+		<== (timestamp=0,EINVAL)
+	<-- EINVAL
 <-- 28
 `,
 		},
@@ -228,9 +246,11 @@ func Test_clockTimeGet_Unsupported(t *testing.T) {
 			clockID:       100,
 			expectedErrno: ErrnoInval,
 			expectedLog: `
---> proxy.clock_time_get(id=100,precision=0,result.timestamp=1)
-	==> wasi_snapshot_preview1.clock_time_get(id=100,precision=0,result.timestamp=1)
-	<== EINVAL
+--> proxy.clock_time_get(id=100,precision=0,result.timestamp=16)
+	--> wasi_snapshot_preview1.clock_time_get(id=100,precision=0,result.timestamp=16)
+		==> wasi_snapshot_preview1.clockTimeGet(id=100,precision=0)
+		<== (timestamp=0,EINVAL)
+	<-- EINVAL
 <-- 28
 `,
 		},
@@ -242,8 +262,7 @@ func Test_clockTimeGet_Unsupported(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			defer log.Reset()
 
-			resultTimestamp := uint32(1) // arbitrary offset
-
+			resultTimestamp := 16 // arbitrary offset
 			requireErrno(t, tc.expectedErrno, mod, clockTimeGetName, uint64(tc.clockID), uint64(0) /* TODO: precision */, uint64(resultTimestamp))
 			require.Equal(t, tc.expectedLog, "\n"+log.String())
 		})
@@ -266,8 +285,8 @@ func Test_clockTimeGet_Errors(t *testing.T) {
 			resultTimestamp: memorySize,
 			expectedLog: `
 --> proxy.clock_time_get(id=0,precision=0,result.timestamp=65536)
-	==> wasi_snapshot_preview1.clock_time_get(id=0,precision=0,result.timestamp=65536)
-	<== EFAULT
+	--> wasi_snapshot_preview1.clock_time_get(id=0,precision=0,result.timestamp=65536)
+	<-- EFAULT
 <-- 21
 `,
 		},
@@ -276,8 +295,8 @@ func Test_clockTimeGet_Errors(t *testing.T) {
 			resultTimestamp: memorySize - 4 + 1, // 4 is the size of uint32, the type of the count of args
 			expectedLog: `
 --> proxy.clock_time_get(id=0,precision=0,result.timestamp=65533)
-	==> wasi_snapshot_preview1.clock_time_get(id=0,precision=0,result.timestamp=65533)
-	<== EFAULT
+	--> wasi_snapshot_preview1.clock_time_get(id=0,precision=0,result.timestamp=65533)
+	<-- EFAULT
 <-- 21
 `,
 		},
diff --git a/imports/wasi_snapshot_preview1/environ_test.go b/imports/wasi_snapshot_preview1/environ_test.go
index 5d2bf088..da943cd4 100644
--- a/imports/wasi_snapshot_preview1/environ_test.go
+++ b/imports/wasi_snapshot_preview1/environ_test.go
@@ -12,30 +12,30 @@ func Test_environGet(t *testing.T) {
 		WithEnv("a", "b").WithEnv("b", "cd"))
 	defer r.Close(testCtx)
 
-	resultEnviron := uint32(11)   // arbitrary offset
-	resultEnvironBuf := uint32(1) // arbitrary offset
+	resultEnvironBuf := uint32(16) // arbitrary offset
+	resultEnviron := uint32(26)    // arbitrary offset
 	expectedMemory := []byte{
 		'?',              // environBuf is after this
 		'a', '=', 'b', 0, // null terminated "a=b",
 		'b', '=', 'c', 'd', 0, // null terminated "b=cd"
-		'?',        // environ is after this
-		1, 0, 0, 0, // little endian-encoded offset of "a=b"
-		5, 0, 0, 0, // little endian-encoded offset of "b=cd"
+		'?',         // environ is after this
+		16, 0, 0, 0, // little endian-encoded offset of "a=b"
+		20, 0, 0, 0, // little endian-encoded offset of "b=cd"
 		'?', // stopped after encoding
 	}
 
-	maskMemory(t, testCtx, mod, len(expectedMemory))
+	maskMemory(t, testCtx, mod, len(expectedMemory)+int(resultEnvironBuf))
 
 	// Invoke environGet and check the memory side effects.
 	requireErrno(t, ErrnoSuccess, mod, environGetName, uint64(resultEnviron), uint64(resultEnvironBuf))
 	require.Equal(t, `
---> proxy.environ_get(environ=11,environ_buf=1)
-	==> wasi_snapshot_preview1.environ_get(environ=11,environ_buf=1)
+--> proxy.environ_get(environ=26,environ_buf=16)
+	==> wasi_snapshot_preview1.environ_get(environ=26,environ_buf=16)
 	<== ESUCCESS
 <-- 0
 `, "\n"+log.String())
 
-	actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(expectedMemory)))
+	actual, ok := mod.Memory().Read(testCtx, resultEnvironBuf-1, uint32(len(expectedMemory)))
 	require.True(t, ok)
 	require.Equal(t, expectedMemory, actual)
 }
@@ -118,8 +118,8 @@ func Test_environSizesGet(t *testing.T) {
 		WithEnv("a", "b").WithEnv("b", "cd"))
 	defer r.Close(testCtx)
 
-	resultEnvironc := uint32(1)    // arbitrary offset
-	resultEnvironvLen := uint32(6) // arbitrary offset
+	resultEnvironc := uint32(16)    // arbitrary offset
+	resultEnvironvLen := uint32(21) // arbitrary offset
 	expectedMemory := []byte{
 		'?',                // resultEnvironc is after this
 		0x2, 0x0, 0x0, 0x0, // little endian-encoded environment variable count
@@ -128,18 +128,20 @@ func Test_environSizesGet(t *testing.T) {
 		'?', // stopped after encoding
 	}
 
-	maskMemory(t, testCtx, mod, len(expectedMemory))
+	maskMemory(t, testCtx, mod, len(expectedMemory)+int(resultEnvironc))
 
 	// Invoke environSizesGet and check the memory side effects.
 	requireErrno(t, ErrnoSuccess, mod, environSizesGetName, uint64(resultEnvironc), uint64(resultEnvironvLen))
 	require.Equal(t, `
---> proxy.environ_sizes_get(result.environc=1,result.environv_len=6)
-	==> wasi_snapshot_preview1.environ_sizes_get(result.environc=1,result.environv_len=6)
-	<== ESUCCESS
+--> proxy.environ_sizes_get(result.environc=16,result.environv_len=21)
+	--> wasi_snapshot_preview1.environ_sizes_get(result.environc=16,result.environv_len=21)
+		==> wasi_snapshot_preview1.environSizesGet()
+		<== (environc=2,environv_len=9,ESUCCESS)
+	<-- ESUCCESS
 <-- 0
 `, "\n"+log.String())
 
-	actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(expectedMemory)))
+	actual, ok := mod.Memory().Read(testCtx, resultEnvironc-1, uint32(len(expectedMemory)))
 	require.True(t, ok)
 	require.Equal(t, expectedMemory, actual)
 }
@@ -150,7 +152,7 @@ func Test_environSizesGet_Errors(t *testing.T) {
 	defer r.Close(testCtx)
 
 	memorySize := mod.Memory().Size(testCtx)
-	validAddress := uint32(0) // arbitrary valid address as arguments to environ_sizes_get. We chose 0 here.
+	validAddress := uint32(0) // arbitrary
 
 	tests := []struct {
 		name                 string
@@ -163,8 +165,8 @@ func Test_environSizesGet_Errors(t *testing.T) {
 			environLen: validAddress,
 			expectedLog: `
 --> proxy.environ_sizes_get(result.environc=65536,result.environv_len=0)
-	==> wasi_snapshot_preview1.environ_sizes_get(result.environc=65536,result.environv_len=0)
-	<== EFAULT
+	--> wasi_snapshot_preview1.environ_sizes_get(result.environc=65536,result.environv_len=0)
+	<-- EFAULT
 <-- 21
 `,
 		},
@@ -174,8 +176,8 @@ func Test_environSizesGet_Errors(t *testing.T) {
 			environLen: memorySize,
 			expectedLog: `
 --> proxy.environ_sizes_get(result.environc=0,result.environv_len=65536)
-	==> wasi_snapshot_preview1.environ_sizes_get(result.environc=0,result.environv_len=65536)
-	<== EFAULT
+	--> wasi_snapshot_preview1.environ_sizes_get(result.environc=0,result.environv_len=65536)
+	<-- EFAULT
 <-- 21
 `,
 		},
@@ -185,8 +187,8 @@ func Test_environSizesGet_Errors(t *testing.T) {
 			environLen: validAddress,
 			expectedLog: `
 --> proxy.environ_sizes_get(result.environc=65533,result.environv_len=0)
-	==> wasi_snapshot_preview1.environ_sizes_get(result.environc=65533,result.environv_len=0)
-	<== EFAULT
+	--> wasi_snapshot_preview1.environ_sizes_get(result.environc=65533,result.environv_len=0)
+	<-- EFAULT
 <-- 21
 `,
 		},
@@ -196,8 +198,8 @@ func Test_environSizesGet_Errors(t *testing.T) {
 			environLen: memorySize - 4 + 1, // 4 is count of bytes to encode uint32le
 			expectedLog: `
 --> proxy.environ_sizes_get(result.environc=0,result.environv_len=65533)
-	==> wasi_snapshot_preview1.environ_sizes_get(result.environc=0,result.environv_len=65533)
-	<== EFAULT
+	--> wasi_snapshot_preview1.environ_sizes_get(result.environc=0,result.environv_len=65533)
+	<-- EFAULT
 <-- 21
 `,
 		},
diff --git a/imports/wasi_snapshot_preview1/fs_test.go b/imports/wasi_snapshot_preview1/fs_test.go
index 897260e2..74afa788 100644
--- a/imports/wasi_snapshot_preview1/fs_test.go
+++ b/imports/wasi_snapshot_preview1/fs_test.go
@@ -14,6 +14,7 @@ import (
 
 	"github.com/tetratelabs/wazero"
 	"github.com/tetratelabs/wazero/api"
+	"github.com/tetratelabs/wazero/internal/leb128"
 	internalsys "github.com/tetratelabs/wazero/internal/sys"
 	"github.com/tetratelabs/wazero/internal/testing/require"
 	"github.com/tetratelabs/wazero/internal/wasm"
@@ -23,8 +24,8 @@ import (
 func Test_fdAdvise(t *testing.T) {
 	log := requireErrnoNosys(t, fdAdviseName, 0, 0, 0, 0)
 	require.Equal(t, `
---> proxy.fd_advise(fd=0,offset=0,len=0,result.advice=0)
-	--> wasi_snapshot_preview1.fd_advise(fd=0,offset=0,len=0,result.advice=0)
+--> proxy.fd_advise(fd=0,offset=0,len=0,advice=0)
+	--> wasi_snapshot_preview1.fd_advise(fd=0,offset=0,len=0,advice=0)
 	<-- ENOSYS
 <-- 52
 `, log)
@@ -529,8 +530,10 @@ func Test_fdPread(t *testing.T) {
 			),
 			expectedLog: `
 --> proxy.fd_pread(fd=4,iovs=1,iovs_len=2,offset=0,result.nread=26)
-	==> wasi_snapshot_preview1.fd_pread(fd=4,iovs=1,iovs_len=2,offset=0,result.nread=26)
-	<== ESUCCESS
+	--> wasi_snapshot_preview1.fd_pread(fd=4,iovs=1,iovs_len=2,offset=0,result.nread=26)
+		==> wasi_snapshot_preview1.fdPread(fd=4,iovs=1,iovs_len=2,offset=0)
+		<== (nread=6,ESUCCESS)
+	<-- ESUCCESS
 <-- 0
 `,
 		},
@@ -546,8 +549,10 @@ func Test_fdPread(t *testing.T) {
 			),
 			expectedLog: `
 --> proxy.fd_pread(fd=4,iovs=1,iovs_len=2,offset=2,result.nread=26)
-	==> wasi_snapshot_preview1.fd_pread(fd=4,iovs=1,iovs_len=2,offset=2,result.nread=26)
-	<== ESUCCESS
+	--> wasi_snapshot_preview1.fd_pread(fd=4,iovs=1,iovs_len=2,offset=2,result.nread=26)
+		==> wasi_snapshot_preview1.fdPread(fd=4,iovs=1,iovs_len=2,offset=2)
+		<== (nread=4,ESUCCESS)
+	<-- ESUCCESS
 <-- 0
 `,
 		},
@@ -588,12 +593,15 @@ func Test_fdPread_Errors(t *testing.T) {
 	}{
 		{
 			name:          "invalid fd",
-			fd:            42, // arbitrary invalid fd
+			fd:            42,                         // arbitrary invalid fd
+			memory:        []byte{'?', '?', '?', '?'}, // pass result.nread validation
 			expectedErrno: ErrnoBadf,
 			expectedLog: `
---> proxy.fd_pread(fd=42,iovs=65536,iovs_len=65536,offset=0,result.nread=65536)
-	==> wasi_snapshot_preview1.fd_pread(fd=42,iovs=65536,iovs_len=65536,offset=0,result.nread=65536)
-	<== EBADF
+--> proxy.fd_pread(fd=42,iovs=65532,iovs_len=65532,offset=0,result.nread=65532)
+	--> wasi_snapshot_preview1.fd_pread(fd=42,iovs=65532,iovs_len=65532,offset=0,result.nread=65532)
+		==> wasi_snapshot_preview1.fdPread(fd=42,iovs=65532,iovs_len=65532,offset=0)
+		<== (nread=0,EBADF)
+	<-- EBADF
 <-- 8
 `,
 		},
@@ -604,8 +612,8 @@ func Test_fdPread_Errors(t *testing.T) {
 			expectedErrno: ErrnoFault,
 			expectedLog: `
 --> proxy.fd_pread(fd=4,iovs=65536,iovs_len=65536,offset=7,result.nread=65536)
-	==> wasi_snapshot_preview1.fd_pread(fd=4,iovs=65536,iovs_len=65536,offset=7,result.nread=65536)
-	<== EFAULT
+	--> wasi_snapshot_preview1.fd_pread(fd=4,iovs=65536,iovs_len=65536,offset=7,result.nread=65536)
+	<-- EFAULT
 <-- 21
 `,
 		},
@@ -617,8 +625,8 @@ func Test_fdPread_Errors(t *testing.T) {
 			expectedErrno: ErrnoFault,
 			expectedLog: `
 --> proxy.fd_pread(fd=4,iovs=65536,iovs_len=65535,offset=0,result.nread=65535)
-	==> wasi_snapshot_preview1.fd_pread(fd=4,iovs=65536,iovs_len=65535,offset=0,result.nread=65535)
-	<== EFAULT
+	--> wasi_snapshot_preview1.fd_pread(fd=4,iovs=65536,iovs_len=65535,offset=0,result.nread=65535)
+	<-- EFAULT
 <-- 21
 `,
 		},
@@ -633,8 +641,10 @@ func Test_fdPread_Errors(t *testing.T) {
 			expectedErrno: ErrnoFault,
 			expectedLog: `
 --> proxy.fd_pread(fd=4,iovs=65532,iovs_len=65532,offset=0,result.nread=65531)
-	==> wasi_snapshot_preview1.fd_pread(fd=4,iovs=65532,iovs_len=65532,offset=0,result.nread=65531)
-	<== EFAULT
+	--> wasi_snapshot_preview1.fd_pread(fd=4,iovs=65532,iovs_len=65532,offset=0,result.nread=65531)
+		==> wasi_snapshot_preview1.fdPread(fd=4,iovs=65532,iovs_len=65532,offset=0)
+		<== (nread=0,EFAULT)
+	<-- EFAULT
 <-- 21
 `,
 		},
@@ -650,8 +660,10 @@ func Test_fdPread_Errors(t *testing.T) {
 			expectedErrno: ErrnoFault,
 			expectedLog: `
 --> proxy.fd_pread(fd=4,iovs=65528,iovs_len=65528,offset=0,result.nread=65527)
-	==> wasi_snapshot_preview1.fd_pread(fd=4,iovs=65528,iovs_len=65528,offset=0,result.nread=65527)
-	<== EFAULT
+	--> wasi_snapshot_preview1.fd_pread(fd=4,iovs=65528,iovs_len=65528,offset=0,result.nread=65527)
+		==> wasi_snapshot_preview1.fdPread(fd=4,iovs=65528,iovs_len=65528,offset=0)
+		<== (nread=0,EFAULT)
+	<-- EFAULT
 <-- 21
 `,
 		},
@@ -668,8 +680,10 @@ func Test_fdPread_Errors(t *testing.T) {
 			expectedErrno: ErrnoFault,
 			expectedLog: `
 --> proxy.fd_pread(fd=4,iovs=65527,iovs_len=65527,offset=0,result.nread=65526)
-	==> wasi_snapshot_preview1.fd_pread(fd=4,iovs=65527,iovs_len=65527,offset=0,result.nread=65526)
-	<== EFAULT
+	--> wasi_snapshot_preview1.fd_pread(fd=4,iovs=65527,iovs_len=65527,offset=0,result.nread=65526)
+		==> wasi_snapshot_preview1.fdPread(fd=4,iovs=65527,iovs_len=65527,offset=0)
+		<== (nread=0,EFAULT)
+	<-- EFAULT
 <-- 21
 `,
 		},
@@ -687,8 +701,8 @@ func Test_fdPread_Errors(t *testing.T) {
 			expectedErrno: ErrnoFault,
 			expectedLog: `
 --> proxy.fd_pread(fd=4,iovs=65527,iovs_len=65527,offset=0,result.nread=65536)
-	==> wasi_snapshot_preview1.fd_pread(fd=4,iovs=65527,iovs_len=65527,offset=0,result.nread=65536)
-	<== EFAULT
+	--> wasi_snapshot_preview1.fd_pread(fd=4,iovs=65527,iovs_len=65527,offset=0,result.nread=65536)
+	<-- EFAULT
 <-- 21
 `,
 		},
@@ -730,8 +744,10 @@ func Test_fdPrestatGet(t *testing.T) {
 	requireErrno(t, ErrnoSuccess, mod, fdPrestatGetName, uint64(fd), uint64(resultPrestat))
 	require.Equal(t, `
 --> proxy.fd_prestat_get(fd=4,result.prestat=1)
-	==> wasi_snapshot_preview1.fd_prestat_get(fd=4,result.prestat=1)
-	<== ESUCCESS
+	--> wasi_snapshot_preview1.fd_prestat_get(fd=4,result.prestat=1)
+		==> wasi_snapshot_preview1.fdPrestatGet(fd=4)
+		<== (prestat=17179869184,ESUCCESS)
+	<-- ESUCCESS
 <-- 0
 `, "\n"+log.String())
 
@@ -760,8 +776,10 @@ func Test_fdPrestatGet_Errors(t *testing.T) {
 			expectedErrno: ErrnoBadf,
 			expectedLog: `
 --> proxy.fd_prestat_get(fd=42,result.prestat=0)
-	==> wasi_snapshot_preview1.fd_prestat_get(fd=42,result.prestat=0)
-	<== EBADF
+	--> wasi_snapshot_preview1.fd_prestat_get(fd=42,result.prestat=0)
+		==> wasi_snapshot_preview1.fdPrestatGet(fd=42)
+		<== (prestat=0,EBADF)
+	<-- EBADF
 <-- 8
 `,
 		},
@@ -772,8 +790,8 @@ func Test_fdPrestatGet_Errors(t *testing.T) {
 			expectedErrno: ErrnoFault,
 			expectedLog: `
 --> proxy.fd_prestat_get(fd=4,result.prestat=65536)
-	==> wasi_snapshot_preview1.fd_prestat_get(fd=4,result.prestat=65536)
-	<== EFAULT
+	--> wasi_snapshot_preview1.fd_prestat_get(fd=4,result.prestat=65536)
+	<-- EFAULT
 <-- 21
 `,
 		},
@@ -948,8 +966,10 @@ func Test_fdRead(t *testing.T) {
 	requireErrno(t, ErrnoSuccess, mod, fdReadName, uint64(fd), uint64(iovs), uint64(iovsCount), uint64(resultNread))
 	require.Equal(t, `
 --> proxy.fd_read(fd=4,iovs=1,iovs_len=2,result.nread=26)
-	==> wasi_snapshot_preview1.fd_read(fd=4,iovs=1,iovs_len=2,result.nread=26)
-	<== ESUCCESS
+	--> wasi_snapshot_preview1.fd_read(fd=4,iovs=1,iovs_len=2,result.nread=26)
+		==> wasi_snapshot_preview1.fdRead(fd=4,iovs=1,iovs_len=2)
+		<== (nread=6,ESUCCESS)
+	<-- ESUCCESS
 <-- 0
 `, "\n"+log.String())
 
@@ -971,12 +991,15 @@ func Test_fdRead_Errors(t *testing.T) {
 	}{
 		{
 			name:          "invalid fd",
-			fd:            42, // arbitrary invalid fd
+			fd:            42,                         // arbitrary invalid fd
+			memory:        []byte{'?', '?', '?', '?'}, // pass result.nread validation
 			expectedErrno: ErrnoBadf,
 			expectedLog: `
---> proxy.fd_read(fd=42,iovs=65536,iovs_len=65536,result.nread=65536)
-	==> wasi_snapshot_preview1.fd_read(fd=42,iovs=65536,iovs_len=65536,result.nread=65536)
-	<== EBADF
+--> proxy.fd_read(fd=42,iovs=65532,iovs_len=65532,result.nread=65532)
+	--> wasi_snapshot_preview1.fd_read(fd=42,iovs=65532,iovs_len=65532,result.nread=65532)
+		==> wasi_snapshot_preview1.fdRead(fd=42,iovs=65532,iovs_len=65532)
+		<== (nread=0,EBADF)
+	<-- EBADF
 <-- 8
 `,
 		},
@@ -988,8 +1011,8 @@ func Test_fdRead_Errors(t *testing.T) {
 			expectedErrno: ErrnoFault,
 			expectedLog: `
 --> proxy.fd_read(fd=4,iovs=65536,iovs_len=65535,result.nread=65535)
-	==> wasi_snapshot_preview1.fd_read(fd=4,iovs=65536,iovs_len=65535,result.nread=65535)
-	<== EFAULT
+	--> wasi_snapshot_preview1.fd_read(fd=4,iovs=65536,iovs_len=65535,result.nread=65535)
+	<-- EFAULT
 <-- 21
 `,
 		},
@@ -1004,8 +1027,10 @@ func Test_fdRead_Errors(t *testing.T) {
 			expectedErrno: ErrnoFault,
 			expectedLog: `
 --> proxy.fd_read(fd=4,iovs=65532,iovs_len=65532,result.nread=65531)
-	==> wasi_snapshot_preview1.fd_read(fd=4,iovs=65532,iovs_len=65532,result.nread=65531)
-	<== EFAULT
+	--> wasi_snapshot_preview1.fd_read(fd=4,iovs=65532,iovs_len=65532,result.nread=65531)
+		==> wasi_snapshot_preview1.fdRead(fd=4,iovs=65532,iovs_len=65532)
+		<== (nread=0,EFAULT)
+	<-- EFAULT
 <-- 21
 `,
 		},
@@ -1021,8 +1046,10 @@ func Test_fdRead_Errors(t *testing.T) {
 			expectedErrno: ErrnoFault,
 			expectedLog: `
 --> proxy.fd_read(fd=4,iovs=65528,iovs_len=65528,result.nread=65527)
-	==> wasi_snapshot_preview1.fd_read(fd=4,iovs=65528,iovs_len=65528,result.nread=65527)
-	<== EFAULT
+	--> wasi_snapshot_preview1.fd_read(fd=4,iovs=65528,iovs_len=65528,result.nread=65527)
+		==> wasi_snapshot_preview1.fdRead(fd=4,iovs=65528,iovs_len=65528)
+		<== (nread=0,EFAULT)
+	<-- EFAULT
 <-- 21
 `,
 		},
@@ -1039,8 +1066,10 @@ func Test_fdRead_Errors(t *testing.T) {
 			expectedErrno: ErrnoFault,
 			expectedLog: `
 --> proxy.fd_read(fd=4,iovs=65527,iovs_len=65527,result.nread=65526)
-	==> wasi_snapshot_preview1.fd_read(fd=4,iovs=65527,iovs_len=65527,result.nread=65526)
-	<== EFAULT
+	--> wasi_snapshot_preview1.fd_read(fd=4,iovs=65527,iovs_len=65527,result.nread=65526)
+		==> wasi_snapshot_preview1.fdRead(fd=4,iovs=65527,iovs_len=65527)
+		<== (nread=0,EFAULT)
+	<-- EFAULT
 <-- 21
 `,
 		},
@@ -1058,8 +1087,8 @@ func Test_fdRead_Errors(t *testing.T) {
 			expectedErrno: ErrnoFault,
 			expectedLog: `
 --> proxy.fd_read(fd=4,iovs=65527,iovs_len=65527,result.nread=65536)
-	==> wasi_snapshot_preview1.fd_read(fd=4,iovs=65527,iovs_len=65527,result.nread=65536)
-	<== EFAULT
+	--> wasi_snapshot_preview1.fd_read(fd=4,iovs=65527,iovs_len=65527,result.nread=65536)
+	<-- EFAULT
 <-- 21
 `,
 		},
@@ -1532,8 +1561,10 @@ func Test_fdReaddir_Errors(t *testing.T) {
 			expectedErrno: ErrnoFault,
 			expectedLog: `
 --> proxy.fd_readdir(fd=4,buf=65536,buf_len=1000,cookie=0,result.bufused=0)
-	==> wasi_snapshot_preview1.fd_readdir(fd=4,buf=65536,buf_len=1000,cookie=0,result.bufused=0)
-	<== EFAULT
+	--> wasi_snapshot_preview1.fd_readdir(fd=4,buf=65536,buf_len=1000,cookie=0,result.bufused=0)
+		==> wasi_snapshot_preview1.fdReaddir(fd=4,buf=65536,buf_len=1000,cookie=0)
+		<== (bufused=0,EFAULT)
+	<-- EFAULT
 <-- 21
 `,
 		},
@@ -1543,8 +1574,10 @@ func Test_fdReaddir_Errors(t *testing.T) {
 			expectedErrno: ErrnoBadf,
 			expectedLog: `
 --> proxy.fd_readdir(fd=42,buf=0,buf_len=0,cookie=0,result.bufused=0)
-	==> wasi_snapshot_preview1.fd_readdir(fd=42,buf=0,buf_len=0,cookie=0,result.bufused=0)
-	<== EBADF
+	--> wasi_snapshot_preview1.fd_readdir(fd=42,buf=0,buf_len=0,cookie=0,result.bufused=0)
+		==> wasi_snapshot_preview1.fdReaddir(fd=42,buf=0,buf_len=0,cookie=0)
+		<== (bufused=0,EBADF)
+	<-- EBADF
 <-- 8
 `,
 		},
@@ -1554,8 +1587,10 @@ func Test_fdReaddir_Errors(t *testing.T) {
 			expectedErrno: ErrnoNotdir,
 			expectedLog: `
 --> proxy.fd_readdir(fd=5,buf=0,buf_len=0,cookie=0,result.bufused=0)
-	==> wasi_snapshot_preview1.fd_readdir(fd=5,buf=0,buf_len=0,cookie=0,result.bufused=0)
-	<== ENOTDIR
+	--> wasi_snapshot_preview1.fd_readdir(fd=5,buf=0,buf_len=0,cookie=0,result.bufused=0)
+		==> wasi_snapshot_preview1.fdReaddir(fd=5,buf=0,buf_len=0,cookie=0)
+		<== (bufused=0,ENOTDIR)
+	<-- ENOTDIR
 <-- 54
 `,
 		},
@@ -1567,8 +1602,10 @@ func Test_fdReaddir_Errors(t *testing.T) {
 			expectedErrno: ErrnoFault,
 			expectedLog: `
 --> proxy.fd_readdir(fd=4,buf=65536,buf_len=1000,cookie=0,result.bufused=0)
-	==> wasi_snapshot_preview1.fd_readdir(fd=4,buf=65536,buf_len=1000,cookie=0,result.bufused=0)
-	<== EFAULT
+	--> wasi_snapshot_preview1.fd_readdir(fd=4,buf=65536,buf_len=1000,cookie=0,result.bufused=0)
+		==> wasi_snapshot_preview1.fdReaddir(fd=4,buf=65536,buf_len=1000,cookie=0)
+		<== (bufused=0,EFAULT)
+	<-- EFAULT
 <-- 21
 `,
 		},
@@ -1580,8 +1617,10 @@ func Test_fdReaddir_Errors(t *testing.T) {
 			expectedErrno: ErrnoFault,
 			expectedLog: `
 --> proxy.fd_readdir(fd=4,buf=65535,buf_len=1000,cookie=0,result.bufused=0)
-	==> wasi_snapshot_preview1.fd_readdir(fd=4,buf=65535,buf_len=1000,cookie=0,result.bufused=0)
-	<== EFAULT
+	--> wasi_snapshot_preview1.fd_readdir(fd=4,buf=65535,buf_len=1000,cookie=0,result.bufused=0)
+		==> wasi_snapshot_preview1.fdReaddir(fd=4,buf=65535,buf_len=1000,cookie=0)
+		<== (bufused=0,EFAULT)
+	<-- EFAULT
 <-- 21
 `,
 		},
@@ -1593,8 +1632,8 @@ func Test_fdReaddir_Errors(t *testing.T) {
 			expectedErrno: ErrnoFault,
 			expectedLog: `
 --> proxy.fd_readdir(fd=4,buf=0,buf_len=1,cookie=0,result.bufused=65536)
-	==> wasi_snapshot_preview1.fd_readdir(fd=4,buf=0,buf_len=1,cookie=0,result.bufused=65536)
-	<== EFAULT
+	--> wasi_snapshot_preview1.fd_readdir(fd=4,buf=0,buf_len=1,cookie=0,result.bufused=65536)
+	<-- EFAULT
 <-- 21
 `,
 		},
@@ -1607,8 +1646,10 @@ func Test_fdReaddir_Errors(t *testing.T) {
 			expectedErrno: ErrnoInval,
 			expectedLog: `
 --> proxy.fd_readdir(fd=4,buf=0,buf_len=1000,cookie=1,result.bufused=2000)
-	==> wasi_snapshot_preview1.fd_readdir(fd=4,buf=0,buf_len=1000,cookie=1,result.bufused=2000)
-	<== EINVAL
+	--> wasi_snapshot_preview1.fd_readdir(fd=4,buf=0,buf_len=1000,cookie=1,result.bufused=2000)
+		==> wasi_snapshot_preview1.fdReaddir(fd=4,buf=0,buf_len=1000,cookie=1)
+		<== (bufused=0,EINVAL)
+	<-- EINVAL
 <-- 28
 `,
 		},
@@ -1622,8 +1663,10 @@ func Test_fdReaddir_Errors(t *testing.T) {
 			expectedErrno: ErrnoInval,
 			expectedLog: `
 --> proxy.fd_readdir(fd=4,buf=0,buf_len=1000,cookie=-1,result.bufused=2000)
-	==> wasi_snapshot_preview1.fd_readdir(fd=4,buf=0,buf_len=1000,cookie=-1,result.bufused=2000)
-	<== EINVAL
+	--> wasi_snapshot_preview1.fd_readdir(fd=4,buf=0,buf_len=1000,cookie=-1,result.bufused=2000)
+		==> wasi_snapshot_preview1.fdReaddir(fd=4,buf=0,buf_len=1000,cookie=-1)
+		<== (bufused=0,EINVAL)
+	<-- EINVAL
 <-- 28
 `,
 		},
@@ -1911,8 +1954,10 @@ func Test_fdSeek(t *testing.T) {
 			},
 			expectedLog: `
 --> proxy.fd_seek(fd=4,offset=4,whence=0,result.newoffset=1)
-	==> wasi_snapshot_preview1.fd_seek(fd=4,offset=4,whence=0,result.newoffset=1)
-	<== ESUCCESS
+	--> wasi_snapshot_preview1.fd_seek(fd=4,offset=4,whence=0,result.newoffset=1)
+		==> wasi_snapshot_preview1.fdSeek(fd=4,offset=4,whence=0)
+		<== (newoffset=4,ESUCCESS)
+	<-- ESUCCESS
 <-- 0
 `,
 		},
@@ -1928,8 +1973,10 @@ func Test_fdSeek(t *testing.T) {
 			},
 			expectedLog: `
 --> proxy.fd_seek(fd=4,offset=1,whence=1,result.newoffset=1)
-	==> wasi_snapshot_preview1.fd_seek(fd=4,offset=1,whence=1,result.newoffset=1)
-	<== ESUCCESS
+	--> wasi_snapshot_preview1.fd_seek(fd=4,offset=1,whence=1,result.newoffset=1)
+		==> wasi_snapshot_preview1.fdSeek(fd=4,offset=1,whence=1)
+		<== (newoffset=2,ESUCCESS)
+	<-- ESUCCESS
 <-- 0
 `,
 		},
@@ -1945,8 +1992,10 @@ func Test_fdSeek(t *testing.T) {
 			},
 			expectedLog: `
 --> proxy.fd_seek(fd=4,offset=-1,whence=2,result.newoffset=1)
-	==> wasi_snapshot_preview1.fd_seek(fd=4,offset=-1,whence=2,result.newoffset=1)
-	<== ESUCCESS
+	--> wasi_snapshot_preview1.fd_seek(fd=4,offset=-1,whence=2,result.newoffset=1)
+		==> wasi_snapshot_preview1.fdSeek(fd=4,offset=-1,whence=2)
+		<== (newoffset=5,ESUCCESS)
+	<-- ESUCCESS
 <-- 0
 `,
 		},
@@ -2004,8 +2053,10 @@ func Test_fdSeek_Errors(t *testing.T) {
 			expectedErrno: ErrnoBadf,
 			expectedLog: `
 --> proxy.fd_seek(fd=42,offset=0,whence=0,result.newoffset=0)
-	==> wasi_snapshot_preview1.fd_seek(fd=42,offset=0,whence=0,result.newoffset=0)
-	<== EBADF
+	--> wasi_snapshot_preview1.fd_seek(fd=42,offset=0,whence=0,result.newoffset=0)
+		==> wasi_snapshot_preview1.fdSeek(fd=42,offset=0,whence=0)
+		<== (newoffset=0,EBADF)
+	<-- EBADF
 <-- 8
 `,
 		},
@@ -2016,8 +2067,10 @@ func Test_fdSeek_Errors(t *testing.T) {
 			expectedErrno: ErrnoInval,
 			expectedLog: `
 --> proxy.fd_seek(fd=4,offset=0,whence=3,result.newoffset=0)
-	==> wasi_snapshot_preview1.fd_seek(fd=4,offset=0,whence=3,result.newoffset=0)
-	<== EINVAL
+	--> wasi_snapshot_preview1.fd_seek(fd=4,offset=0,whence=3,result.newoffset=0)
+		==> wasi_snapshot_preview1.fdSeek(fd=4,offset=0,whence=3)
+		<== (newoffset=0,EINVAL)
+	<-- EINVAL
 <-- 28
 `,
 		},
@@ -2028,8 +2081,8 @@ func Test_fdSeek_Errors(t *testing.T) {
 			expectedErrno:   ErrnoFault,
 			expectedLog: `
 --> proxy.fd_seek(fd=4,offset=0,whence=0,result.newoffset=65536)
-	==> wasi_snapshot_preview1.fd_seek(fd=4,offset=0,whence=0,result.newoffset=65536)
-	<== EFAULT
+	--> wasi_snapshot_preview1.fd_seek(fd=4,offset=0,whence=0,result.newoffset=65536)
+	<-- EFAULT
 <-- 21
 `,
 		},
@@ -2102,8 +2155,10 @@ func Test_fdWrite(t *testing.T) {
 	requireErrno(t, ErrnoSuccess, mod, fdWriteName, uint64(fd), uint64(iovs), uint64(iovsCount), uint64(resultNwritten))
 	require.Equal(t, `
 --> proxy.fd_write(fd=4,iovs=1,iovs_len=2,result.nwritten=26)
-	==> wasi_snapshot_preview1.fd_write(fd=4,iovs=1,iovs_len=2,result.nwritten=26)
-	<== ESUCCESS
+	--> wasi_snapshot_preview1.fd_write(fd=4,iovs=1,iovs_len=2,result.nwritten=26)
+		==> wasi_snapshot_preview1.fdWrite(fd=4,iovs=1,iovs_len=2)
+		<== (nwritten=6,ESUCCESS)
+	<-- ESUCCESS
 <-- 0
 `, "\n"+log.String())
 
@@ -2155,8 +2210,10 @@ func Test_fdWrite_discard(t *testing.T) {
 	requireErrno(t, ErrnoSuccess, mod, fdWriteName, uint64(fd), uint64(iovs), uint64(iovsCount), uint64(resultNwritten))
 	require.Equal(t, `
 --> proxy.fd_write(fd=1,iovs=1,iovs_len=2,result.nwritten=26)
-	==> wasi_snapshot_preview1.fd_write(fd=1,iovs=1,iovs_len=2,result.nwritten=26)
-	<== ESUCCESS
+	--> wasi_snapshot_preview1.fd_write(fd=1,iovs=1,iovs_len=2,result.nwritten=26)
+		==> wasi_snapshot_preview1.fdWrite(fd=1,iovs=1,iovs_len=2)
+		<== (nwritten=6,ESUCCESS)
+	<-- ESUCCESS
 <-- 0
 `, "\n"+log.String())
 
@@ -2172,19 +2229,14 @@ func Test_fdWrite_Errors(t *testing.T) {
 	defer r.Close(testCtx)
 
 	// Setup valid test memory
-	iovs, iovsCount := uint32(0), uint32(1)
-	memory := []byte{
-		8, 0, 0, 0, // = iovs[0].offset (where the data "hi" begins)
-		2, 0, 0, 0, // = iovs[0].length (how many bytes are in "hi")
-		'h', 'i', // iovs[0].length bytes
-	}
+	iovsCount := uint32(1)
+	memSize := mod.Memory().Size(testCtx)
 
 	tests := []struct {
-		name               string
-		fd, resultNwritten uint32
-		memory             []byte
-		expectedErrno      Errno
-		expectedLog        string
+		name                     string
+		fd, iovs, resultNwritten uint32
+		expectedErrno            Errno
+		expectedLog              string
 	}{
 		{
 			name:          "invalid fd",
@@ -2192,69 +2244,78 @@ func Test_fdWrite_Errors(t *testing.T) {
 			expectedErrno: ErrnoBadf,
 			expectedLog: `
 --> proxy.fd_write(fd=42,iovs=0,iovs_len=1,result.nwritten=0)
-	==> wasi_snapshot_preview1.fd_write(fd=42,iovs=0,iovs_len=1,result.nwritten=0)
-	<== EBADF
+	--> wasi_snapshot_preview1.fd_write(fd=42,iovs=0,iovs_len=1,result.nwritten=0)
+		==> wasi_snapshot_preview1.fdWrite(fd=42,iovs=0,iovs_len=1)
+		<== (nwritten=0,EBADF)
+	<-- EBADF
 <-- 8
 `,
 		},
 		{
 			name:          "out-of-memory reading iovs[0].offset",
 			fd:            fd,
-			memory:        []byte{},
+			iovs:          memSize - 2,
 			expectedErrno: ErrnoFault,
 			expectedLog: `
---> proxy.fd_write(fd=4,iovs=0,iovs_len=1,result.nwritten=0)
-	==> wasi_snapshot_preview1.fd_write(fd=4,iovs=0,iovs_len=1,result.nwritten=0)
-	<== EFAULT
+--> proxy.fd_write(fd=4,iovs=65534,iovs_len=1,result.nwritten=0)
+	--> wasi_snapshot_preview1.fd_write(fd=4,iovs=65534,iovs_len=1,result.nwritten=0)
+		==> wasi_snapshot_preview1.fdWrite(fd=4,iovs=65534,iovs_len=1)
+		<== (nwritten=0,EFAULT)
+	<-- EFAULT
 <-- 21
 `,
 		},
 		{
 			name:          "out-of-memory reading iovs[0].length",
 			fd:            fd,
-			memory:        memory[0:4], // iovs[0].offset was 4 bytes and iovs[0].length next, but not enough mod.Memory()!
+			iovs:          memSize - 4, // iovs[0].offset was 4 bytes and iovs[0].length next, but not enough mod.Memory()!
 			expectedErrno: ErrnoFault,
 			expectedLog: `
---> proxy.fd_write(fd=4,iovs=0,iovs_len=1,result.nwritten=0)
-	==> wasi_snapshot_preview1.fd_write(fd=4,iovs=0,iovs_len=1,result.nwritten=0)
-	<== EFAULT
+--> proxy.fd_write(fd=4,iovs=65532,iovs_len=1,result.nwritten=0)
+	--> wasi_snapshot_preview1.fd_write(fd=4,iovs=65532,iovs_len=1,result.nwritten=0)
+		==> wasi_snapshot_preview1.fdWrite(fd=4,iovs=65532,iovs_len=1)
+		<== (nwritten=0,EFAULT)
+	<-- EFAULT
 <-- 21
 `,
 		},
 		{
 			name:          "iovs[0].offset is outside memory",
 			fd:            fd,
-			memory:        memory[0:8], // iovs[0].offset (where to read "hi") is outside memory.
+			iovs:          memSize - 5, // iovs[0].offset (where to read "hi") is outside memory.
 			expectedErrno: ErrnoFault,
 			expectedLog: `
---> proxy.fd_write(fd=4,iovs=0,iovs_len=1,result.nwritten=0)
-	==> wasi_snapshot_preview1.fd_write(fd=4,iovs=0,iovs_len=1,result.nwritten=0)
-	<== EFAULT
+--> proxy.fd_write(fd=4,iovs=65531,iovs_len=1,result.nwritten=0)
+	--> wasi_snapshot_preview1.fd_write(fd=4,iovs=65531,iovs_len=1,result.nwritten=0)
+		==> wasi_snapshot_preview1.fdWrite(fd=4,iovs=65531,iovs_len=1)
+		<== (nwritten=0,EFAULT)
+	<-- EFAULT
 <-- 21
 `,
 		},
 		{
 			name:          "length to read exceeds memory by 1",
 			fd:            fd,
-			memory:        memory[0:9], // iovs[0].offset (where to read "hi") is in memory, but truncated.
+			iovs:          memSize - 9, // iovs[0].offset (where to read "hi") is in memory, but truncated.
 			expectedErrno: ErrnoFault,
 			expectedLog: `
---> proxy.fd_write(fd=4,iovs=0,iovs_len=1,result.nwritten=0)
-	==> wasi_snapshot_preview1.fd_write(fd=4,iovs=0,iovs_len=1,result.nwritten=0)
-	<== EFAULT
+--> proxy.fd_write(fd=4,iovs=65527,iovs_len=1,result.nwritten=0)
+	--> wasi_snapshot_preview1.fd_write(fd=4,iovs=65527,iovs_len=1,result.nwritten=0)
+		==> wasi_snapshot_preview1.fdWrite(fd=4,iovs=65527,iovs_len=1)
+		<== (nwritten=0,EFAULT)
+	<-- EFAULT
 <-- 21
 `,
 		},
 		{
 			name:           "resultNwritten offset is outside memory",
 			fd:             fd,
-			memory:         memory,
-			resultNwritten: uint32(len(memory)), // read was ok, but there wasn't enough memory to write the result.
+			resultNwritten: memSize, // read was ok, but there wasn't enough memory to write the result.
 			expectedErrno:  ErrnoFault,
 			expectedLog: `
---> proxy.fd_write(fd=4,iovs=0,iovs_len=1,result.nwritten=10)
-	==> wasi_snapshot_preview1.fd_write(fd=4,iovs=0,iovs_len=1,result.nwritten=10)
-	<== EFAULT
+--> proxy.fd_write(fd=4,iovs=0,iovs_len=1,result.nwritten=65536)
+	--> wasi_snapshot_preview1.fd_write(fd=4,iovs=0,iovs_len=1,result.nwritten=65536)
+	<-- EFAULT
 <-- 21
 `,
 		},
@@ -2265,9 +2326,14 @@ func Test_fdWrite_Errors(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			defer log.Reset()
 
-			mod.Memory().(*wasm.MemoryInstance).Buffer = tc.memory
+			mod.Memory().Write(testCtx, tc.iovs, append(
+				leb128.EncodeUint32(tc.iovs+8), // = iovs[0].offset (where the data "hi" begins)
+				// = iovs[0].length (how many bytes are in "hi")
+				2, 0, 0, 0,
+				'h', 'i', // iovs[0].length bytes
+			))
 
-			requireErrno(t, tc.expectedErrno, mod, fdWriteName, uint64(tc.fd), uint64(iovs), uint64(iovsCount),
+			requireErrno(t, tc.expectedErrno, mod, fdWriteName, uint64(tc.fd), uint64(tc.iovs), uint64(iovsCount),
 				uint64(tc.resultNwritten))
 			require.Equal(t, tc.expectedLog, "\n"+log.String())
 		})
@@ -2563,8 +2629,10 @@ func Test_pathOpen(t *testing.T) {
 		uint64(pathLen), uint64(oflags), fsRightsBase, fsRightsInheriting, uint64(fdflags), uint64(resultOpenedFd))
 	require.Equal(t, `
 --> proxy.path_open(fd=3,dirflags=0,path=1,path_len=6,oflags=0,fs_rights_base=1,fs_rights_inheriting=2,fdflags=0,result.opened_fd=8)
-	==> wasi_snapshot_preview1.path_open(fd=3,dirflags=0,path=1,path_len=6,oflags=0,fs_rights_base=1,fs_rights_inheriting=2,fdflags=0,result.opened_fd=8)
-	<== ESUCCESS
+	--> wasi_snapshot_preview1.path_open(fd=3,dirflags=0,path=1,path_len=6,oflags=0,fs_rights_base=1,fs_rights_inheriting=2,fdflags=0,result.opened_fd=8)
+		==> wasi_snapshot_preview1.pathOpen(fd=3,dirflags=0,path=1,path_len=6,oflags=0,fs_rights_base=1,fs_rights_inheriting=2,fdflags=0)
+		<== (opened_fd=4,ESUCCESS)
+	<-- ESUCCESS
 <-- 0
 `, "\n"+log.String())
 
@@ -2601,8 +2669,10 @@ func Test_pathOpen_Errors(t *testing.T) {
 			expectedErrno: ErrnoBadf,
 			expectedLog: `
 --> proxy.path_open(fd=42,dirflags=0,path=0,path_len=0,oflags=0,fs_rights_base=0,fs_rights_inheriting=0,fdflags=0,result.opened_fd=0)
-	==> wasi_snapshot_preview1.path_open(fd=42,dirflags=0,path=0,path_len=0,oflags=0,fs_rights_base=0,fs_rights_inheriting=0,fdflags=0,result.opened_fd=0)
-	<== EBADF
+	--> wasi_snapshot_preview1.path_open(fd=42,dirflags=0,path=0,path_len=0,oflags=0,fs_rights_base=0,fs_rights_inheriting=0,fdflags=0,result.opened_fd=0)
+		==> wasi_snapshot_preview1.pathOpen(fd=42,dirflags=0,path=0,path_len=0,oflags=0,fs_rights_base=0,fs_rights_inheriting=0,fdflags=0)
+		<== (opened_fd=0,EBADF)
+	<-- EBADF
 <-- 8
 `,
 		},
@@ -2614,8 +2684,10 @@ func Test_pathOpen_Errors(t *testing.T) {
 			expectedErrno: ErrnoFault,
 			expectedLog: `
 --> proxy.path_open(fd=3,dirflags=0,path=65536,path_len=6,oflags=0,fs_rights_base=0,fs_rights_inheriting=0,fdflags=0,result.opened_fd=0)
-	==> wasi_snapshot_preview1.path_open(fd=3,dirflags=0,path=65536,path_len=6,oflags=0,fs_rights_base=0,fs_rights_inheriting=0,fdflags=0,result.opened_fd=0)
-	<== EFAULT
+	--> wasi_snapshot_preview1.path_open(fd=3,dirflags=0,path=65536,path_len=6,oflags=0,fs_rights_base=0,fs_rights_inheriting=0,fdflags=0,result.opened_fd=0)
+		==> wasi_snapshot_preview1.pathOpen(fd=3,dirflags=0,path=65536,path_len=6,oflags=0,fs_rights_base=0,fs_rights_inheriting=0,fdflags=0)
+		<== (opened_fd=0,EFAULT)
+	<-- EFAULT
 <-- 21
 `,
 		},
@@ -2628,8 +2700,10 @@ func Test_pathOpen_Errors(t *testing.T) {
 			expectedErrno: ErrnoNoent,
 			expectedLog: `
 --> proxy.path_open(fd=3,dirflags=0,path=0,path_len=6,oflags=0,fs_rights_base=0,fs_rights_inheriting=0,fdflags=0,result.opened_fd=0)
-	==> wasi_snapshot_preview1.path_open(fd=3,dirflags=0,path=0,path_len=6,oflags=0,fs_rights_base=0,fs_rights_inheriting=0,fdflags=0,result.opened_fd=0)
-	<== ENOENT
+	--> wasi_snapshot_preview1.path_open(fd=3,dirflags=0,path=0,path_len=6,oflags=0,fs_rights_base=0,fs_rights_inheriting=0,fdflags=0,result.opened_fd=0)
+		==> wasi_snapshot_preview1.pathOpen(fd=3,dirflags=0,path=0,path_len=6,oflags=0,fs_rights_base=0,fs_rights_inheriting=0,fdflags=0)
+		<== (opened_fd=0,ENOENT)
+	<-- ENOENT
 <-- 44
 `,
 		},
@@ -2641,8 +2715,10 @@ func Test_pathOpen_Errors(t *testing.T) {
 			expectedErrno: ErrnoFault,
 			expectedLog: `
 --> proxy.path_open(fd=3,dirflags=0,path=0,path_len=65537,oflags=0,fs_rights_base=0,fs_rights_inheriting=0,fdflags=0,result.opened_fd=0)
-	==> wasi_snapshot_preview1.path_open(fd=3,dirflags=0,path=0,path_len=65537,oflags=0,fs_rights_base=0,fs_rights_inheriting=0,fdflags=0,result.opened_fd=0)
-	<== EFAULT
+	--> wasi_snapshot_preview1.path_open(fd=3,dirflags=0,path=0,path_len=65537,oflags=0,fs_rights_base=0,fs_rights_inheriting=0,fdflags=0,result.opened_fd=0)
+		==> wasi_snapshot_preview1.pathOpen(fd=3,dirflags=0,path=0,path_len=65537,oflags=0,fs_rights_base=0,fs_rights_inheriting=0,fdflags=0)
+		<== (opened_fd=0,EFAULT)
+	<-- EFAULT
 <-- 21
 `,
 		},
@@ -2655,8 +2731,10 @@ func Test_pathOpen_Errors(t *testing.T) {
 			expectedErrno: ErrnoNoent,
 			expectedLog: `
 --> proxy.path_open(fd=3,dirflags=0,path=0,path_len=5,oflags=0,fs_rights_base=0,fs_rights_inheriting=0,fdflags=0,result.opened_fd=0)
-	==> wasi_snapshot_preview1.path_open(fd=3,dirflags=0,path=0,path_len=5,oflags=0,fs_rights_base=0,fs_rights_inheriting=0,fdflags=0,result.opened_fd=0)
-	<== ENOENT
+	--> wasi_snapshot_preview1.path_open(fd=3,dirflags=0,path=0,path_len=5,oflags=0,fs_rights_base=0,fs_rights_inheriting=0,fdflags=0,result.opened_fd=0)
+		==> wasi_snapshot_preview1.pathOpen(fd=3,dirflags=0,path=0,path_len=5,oflags=0,fs_rights_base=0,fs_rights_inheriting=0,fdflags=0)
+		<== (opened_fd=0,ENOENT)
+	<-- ENOENT
 <-- 44
 `,
 		},
@@ -2670,8 +2748,8 @@ func Test_pathOpen_Errors(t *testing.T) {
 			expectedErrno:  ErrnoFault,
 			expectedLog: `
 --> proxy.path_open(fd=3,dirflags=0,path=0,path_len=6,oflags=0,fs_rights_base=0,fs_rights_inheriting=0,fdflags=0,result.opened_fd=65536)
-	==> wasi_snapshot_preview1.path_open(fd=3,dirflags=0,path=0,path_len=6,oflags=0,fs_rights_base=0,fs_rights_inheriting=0,fdflags=0,result.opened_fd=65536)
-	<== EFAULT
+	--> wasi_snapshot_preview1.path_open(fd=3,dirflags=0,path=0,path_len=6,oflags=0,fs_rights_base=0,fs_rights_inheriting=0,fdflags=0,result.opened_fd=65536)
+	<-- EFAULT
 <-- 21
 `,
 		},
diff --git a/imports/wasi_snapshot_preview1/poll_test.go b/imports/wasi_snapshot_preview1/poll_test.go
index b85f0503..18e2cad9 100644
--- a/imports/wasi_snapshot_preview1/poll_test.go
+++ b/imports/wasi_snapshot_preview1/poll_test.go
@@ -42,8 +42,10 @@ func Test_pollOneoff(t *testing.T) {
 		uint64(resultNevents))
 	require.Equal(t, `
 --> proxy.poll_oneoff(in=0,out=128,nsubscriptions=1,result.nevents=512)
-	==> wasi_snapshot_preview1.poll_oneoff(in=0,out=128,nsubscriptions=1,result.nevents=512)
-	<== ESUCCESS
+	--> wasi_snapshot_preview1.poll_oneoff(in=0,out=128,nsubscriptions=1,result.nevents=512)
+		==> wasi_snapshot_preview1.pollOneoff(in=0,out=128,nsubscriptions=1)
+		<== (nevents=1,ESUCCESS)
+	<-- ESUCCESS
 <-- 0
 `, "\n"+log.String())
 
@@ -77,8 +79,10 @@ func Test_pollOneoff_Errors(t *testing.T) {
 			expectedErrno:  ErrnoFault,
 			expectedLog: `
 --> proxy.poll_oneoff(in=65536,out=128,nsubscriptions=1,result.nevents=512)
-	==> wasi_snapshot_preview1.poll_oneoff(in=65536,out=128,nsubscriptions=1,result.nevents=512)
-	<== EFAULT
+	--> wasi_snapshot_preview1.poll_oneoff(in=65536,out=128,nsubscriptions=1,result.nevents=512)
+		==> wasi_snapshot_preview1.pollOneoff(in=65536,out=128,nsubscriptions=1)
+		<== (nevents=0,EFAULT)
+	<-- EFAULT
 <-- 21
 `,
 		},
@@ -90,8 +94,10 @@ func Test_pollOneoff_Errors(t *testing.T) {
 			expectedErrno:  ErrnoFault,
 			expectedLog: `
 --> proxy.poll_oneoff(in=0,out=65536,nsubscriptions=1,result.nevents=512)
-	==> wasi_snapshot_preview1.poll_oneoff(in=0,out=65536,nsubscriptions=1,result.nevents=512)
-	<== EFAULT
+	--> wasi_snapshot_preview1.poll_oneoff(in=0,out=65536,nsubscriptions=1,result.nevents=512)
+		==> wasi_snapshot_preview1.pollOneoff(in=0,out=65536,nsubscriptions=1)
+		<== (nevents=0,EFAULT)
+	<-- EFAULT
 <-- 21
 `,
 		},
@@ -102,8 +108,8 @@ func Test_pollOneoff_Errors(t *testing.T) {
 			expectedErrno:  ErrnoFault,
 			expectedLog: `
 --> proxy.poll_oneoff(in=0,out=0,nsubscriptions=1,result.nevents=65536)
-	==> wasi_snapshot_preview1.poll_oneoff(in=0,out=0,nsubscriptions=1,result.nevents=65536)
-	<== EFAULT
+	--> wasi_snapshot_preview1.poll_oneoff(in=0,out=0,nsubscriptions=1,result.nevents=65536)
+	<-- EFAULT
 <-- 21
 `,
 		},
@@ -114,8 +120,10 @@ func Test_pollOneoff_Errors(t *testing.T) {
 			expectedErrno: ErrnoInval,
 			expectedLog: `
 --> proxy.poll_oneoff(in=0,out=128,nsubscriptions=0,result.nevents=512)
-	==> wasi_snapshot_preview1.poll_oneoff(in=0,out=128,nsubscriptions=0,result.nevents=512)
-	<== EINVAL
+	--> wasi_snapshot_preview1.poll_oneoff(in=0,out=128,nsubscriptions=0,result.nevents=512)
+		==> wasi_snapshot_preview1.pollOneoff(in=0,out=128,nsubscriptions=0)
+		<== (nevents=0,EINVAL)
+	<-- EINVAL
 <-- 28
 `,
 		},
@@ -139,8 +147,10 @@ func Test_pollOneoff_Errors(t *testing.T) {
 			},
 			expectedLog: `
 --> proxy.poll_oneoff(in=0,out=128,nsubscriptions=1,result.nevents=512)
-	==> wasi_snapshot_preview1.poll_oneoff(in=0,out=128,nsubscriptions=1,result.nevents=512)
-	<== ESUCCESS
+	--> wasi_snapshot_preview1.poll_oneoff(in=0,out=128,nsubscriptions=1,result.nevents=512)
+		==> wasi_snapshot_preview1.pollOneoff(in=0,out=128,nsubscriptions=1)
+		<== (nevents=1,ESUCCESS)
+	<-- ESUCCESS
 <-- 0
 `,
 		},
diff --git a/imports/wasi_snapshot_preview1/wrapper_test.go b/imports/wasi_snapshot_preview1/wrapper_test.go
new file mode 100644
index 00000000..f605701a
--- /dev/null
+++ b/imports/wasi_snapshot_preview1/wrapper_test.go
@@ -0,0 +1,255 @@
+package wasi_snapshot_preview1
+
+import (
+	_ "embed"
+	"testing"
+
+	"github.com/tetratelabs/wazero/internal/testing/require"
+	wasm "github.com/tetratelabs/wazero/internal/wasm"
+)
+
+func Test_proxyResultParams(t *testing.T) {
+	tests := []struct {
+		name       string
+		goFunc     *wasm.HostFunc
+		exportName string
+		expected   *wasm.ProxyFunc
+	}{
+		{
+			name: "v_i32i32",
+			goFunc: &wasm.HostFunc{
+				ResultTypes: []wasm.ValueType{i32, i32},
+				ResultNames: []string{"r1", "errno"},
+				Code: &wasm.Code{IsHostFunction: true, Body: []byte{
+					wasm.OpcodeI32Const, 1,
+					wasm.OpcodeI32Const, byte(ErrnoSuccess),
+					wasm.OpcodeEnd,
+				}},
+			},
+			exportName: "export",
+			expected: &wasm.ProxyFunc{
+				Proxy: &wasm.HostFunc{
+					ExportNames: []string{"export"},
+					Name:        "export",
+					ParamTypes:  []wasm.ValueType{i32},
+					ParamNames:  []string{"result.r1"},
+					ResultTypes: []wasm.ValueType{i32},
+					ResultNames: []string{"errno"},
+					Code: &wasm.Code{
+						IsHostFunction: true,
+						LocalTypes:     []wasm.ValueType{i32},
+						Body: []byte{
+							// store memSizeBytes in a temp local and leave it on the stack.
+							wasm.OpcodeMemorySize, 0, // size of memory[0]
+							wasm.OpcodeI32Const, 0x80, 0x80, 0x04, // leb128 MemoryPageSize
+							wasm.OpcodeI32Mul,
+							wasm.OpcodeLocalTee, 1,
+
+							// EFAULT if memSizeBytes-4 < result.r1; unsigned
+							wasm.OpcodeI32Const, 4, wasm.OpcodeI32Sub,
+							wasm.OpcodeLocalGet, 0,
+							wasm.OpcodeI32LtU, wasm.OpcodeIf, nilType,
+							wasm.OpcodeI32Const, fault,
+							wasm.OpcodeReturn, wasm.OpcodeEnd,
+
+							// call the proxied goFunc without parameters
+							wasm.OpcodeCall, fakeFuncIdx,
+
+							// return the errno if not ESUCCESS
+							wasm.OpcodeLocalTee, 1, wasm.OpcodeI32Const, success,
+							wasm.OpcodeI32Ne, wasm.OpcodeIf, nilType,
+							wasm.OpcodeLocalGet, 1, // errno
+							wasm.OpcodeReturn, wasm.OpcodeEnd,
+
+							// store r1 at the memory offset in result.r1
+							wasm.OpcodeLocalSet, 1,
+							wasm.OpcodeLocalGet, 0, // result.r1
+							wasm.OpcodeLocalGet, 1, // r1
+							wasm.OpcodeI32Store, 0x2, 0x0,
+
+							// return ESUCCESS
+							wasm.OpcodeI32Const, success, wasm.OpcodeEnd,
+						},
+					},
+				},
+				Proxied: &wasm.HostFunc{
+					ResultTypes: []wasm.ValueType{i32, i32},
+					ResultNames: []string{"r1", "errno"},
+					Code: &wasm.Code{IsHostFunction: true, Body: []byte{
+						wasm.OpcodeI32Const, 1,
+						wasm.OpcodeI32Const, byte(ErrnoSuccess),
+						wasm.OpcodeEnd,
+					}},
+				},
+				CallBodyPos: 22,
+			},
+		},
+		{
+			name: "v_i32i64i32",
+			goFunc: &wasm.HostFunc{
+				ResultTypes: []wasm.ValueType{i32, i64, i32},
+				ResultNames: []string{"r1", "r2", "errno"},
+				Code: &wasm.Code{IsHostFunction: true, Body: []byte{
+					wasm.OpcodeI32Const, 1,
+					wasm.OpcodeI64Const, 2,
+					wasm.OpcodeI32Const, byte(ErrnoSuccess),
+					wasm.OpcodeEnd,
+				}},
+			},
+			exportName: "export",
+			expected: &wasm.ProxyFunc{
+				Proxy: &wasm.HostFunc{
+					ExportNames: []string{"export"},
+					Name:        "export",
+					ParamTypes:  []wasm.ValueType{i32, i32},
+					ParamNames:  []string{"result.r1", "result.r2"},
+					ResultTypes: []wasm.ValueType{i32},
+					ResultNames: []string{"errno"},
+					Code: &wasm.Code{
+						IsHostFunction: true,
+						LocalTypes:     []wasm.ValueType{i32, i64},
+						Body: []byte{
+							// store memSizeBytes in a temp local and leave it on the stack.
+							wasm.OpcodeMemorySize, 0, // size of memory[0]
+							wasm.OpcodeI32Const, 0x80, 0x80, 0x04, // leb128 MemoryPageSize
+							wasm.OpcodeI32Mul,
+							wasm.OpcodeLocalTee, 2,
+
+							// EFAULT if memSizeBytes-4 < result.r1; unsigned
+							wasm.OpcodeI32Const, 4, wasm.OpcodeI32Sub,
+							wasm.OpcodeLocalGet, 0,
+							wasm.OpcodeI32LtU, wasm.OpcodeIf, nilType,
+							wasm.OpcodeI32Const, fault,
+							wasm.OpcodeReturn, wasm.OpcodeEnd,
+
+							// EFAULT if memSizeBytes-8 < result.r2; unsigned
+							wasm.OpcodeLocalGet, 2,
+							wasm.OpcodeI32Const, 8, wasm.OpcodeI32Sub,
+							wasm.OpcodeLocalGet, 1,
+							wasm.OpcodeI32LtU, wasm.OpcodeIf, nilType,
+							wasm.OpcodeI32Const, fault,
+							wasm.OpcodeReturn, wasm.OpcodeEnd,
+
+							// call the proxied goFunc without parameters
+							wasm.OpcodeCall, fakeFuncIdx,
+
+							// return the errno if not ESUCCESS
+							wasm.OpcodeLocalTee, 2, wasm.OpcodeI32Const, success,
+							wasm.OpcodeI32Ne, wasm.OpcodeIf, nilType,
+							wasm.OpcodeLocalGet, 2, // errno
+							wasm.OpcodeReturn, wasm.OpcodeEnd,
+
+							// store r2 at the memory offset in result.r2
+							wasm.OpcodeLocalSet, 3,
+							wasm.OpcodeLocalGet, 1, // result.r2
+							wasm.OpcodeLocalGet, 3, // r2
+							wasm.OpcodeI64Store, 0x3, 0x0,
+
+							// store r1 at the memory offset in result.r1
+							wasm.OpcodeLocalSet, 2,
+							wasm.OpcodeLocalGet, 0, // result.r1
+							wasm.OpcodeLocalGet, 2, // r1
+							wasm.OpcodeI32Store, 0x2, 0x0,
+
+							// return ESUCCESS
+							wasm.OpcodeI32Const, success, wasm.OpcodeEnd,
+						},
+					},
+				},
+				Proxied: &wasm.HostFunc{
+					ResultTypes: []wasm.ValueType{i32, i64, i32},
+					ResultNames: []string{"r1", "r2", "errno"},
+					Code: &wasm.Code{IsHostFunction: true, Body: []byte{
+						wasm.OpcodeI32Const, 1,
+						wasm.OpcodeI64Const, 2,
+						wasm.OpcodeI32Const, byte(ErrnoSuccess),
+						wasm.OpcodeEnd,
+					}},
+				},
+				CallBodyPos: 36,
+			},
+		},
+		{
+			name: "i32_i32i32",
+			goFunc: &wasm.HostFunc{
+				ParamTypes:  []wasm.ValueType{i32},
+				ParamNames:  []string{"p1"},
+				ResultTypes: []wasm.ValueType{i32, i32},
+				ResultNames: []string{"r1", "errno"},
+				Code: &wasm.Code{IsHostFunction: true, Body: []byte{
+					wasm.OpcodeI32Const, 1,
+					wasm.OpcodeI32Const, byte(ErrnoSuccess),
+					wasm.OpcodeEnd,
+				}},
+			},
+			exportName: "export",
+			expected: &wasm.ProxyFunc{
+				Proxy: &wasm.HostFunc{
+					ExportNames: []string{"export"},
+					Name:        "export",
+					ParamTypes:  []wasm.ValueType{i32, i32},
+					ParamNames:  []string{"p1", "result.r1"},
+					ResultTypes: []wasm.ValueType{i32},
+					ResultNames: []string{"errno"},
+					Code: &wasm.Code{
+						IsHostFunction: true,
+						LocalTypes:     []wasm.ValueType{i32},
+						Body: []byte{
+							// store memSizeBytes in a temp local and leave it on the stack.
+							wasm.OpcodeMemorySize, 0, // size of memory[0]
+							wasm.OpcodeI32Const, 0x80, 0x80, 0x04, // leb128 MemoryPageSize
+							wasm.OpcodeI32Mul,
+							wasm.OpcodeLocalTee, 2,
+
+							// EFAULT if memSizeBytes-4 < result.r1; unsigned
+							wasm.OpcodeI32Const, 4, wasm.OpcodeI32Sub,
+							wasm.OpcodeLocalGet, 1,
+							wasm.OpcodeI32LtU, wasm.OpcodeIf, nilType,
+							wasm.OpcodeI32Const, fault,
+							wasm.OpcodeReturn, wasm.OpcodeEnd,
+
+							// call the proxied goFunc with parameters
+							wasm.OpcodeLocalGet, 0, wasm.OpcodeCall, fakeFuncIdx,
+
+							// return the errno if not ESUCCESS
+							wasm.OpcodeLocalTee, 2, wasm.OpcodeI32Const, success,
+							wasm.OpcodeI32Ne, wasm.OpcodeIf, nilType,
+							wasm.OpcodeLocalGet, 2, // errno
+							wasm.OpcodeReturn, wasm.OpcodeEnd,
+
+							// store r1 at the memory offset in result.r1
+							wasm.OpcodeLocalSet, 2,
+							wasm.OpcodeLocalGet, 1, // result.r1
+							wasm.OpcodeLocalGet, 2, // r1
+							wasm.OpcodeI32Store, 0x2, 0x0,
+
+							// return ESUCCESS
+							wasm.OpcodeI32Const, success, wasm.OpcodeEnd,
+						},
+					},
+				},
+				Proxied: &wasm.HostFunc{
+					ParamTypes:  []wasm.ValueType{i32},
+					ParamNames:  []string{"p1"},
+					ResultTypes: []wasm.ValueType{i32, i32},
+					ResultNames: []string{"r1", "errno"},
+					Code: &wasm.Code{IsHostFunction: true, Body: []byte{
+						wasm.OpcodeI32Const, 1,
+						wasm.OpcodeI32Const, byte(ErrnoSuccess),
+						wasm.OpcodeEnd,
+					}},
+				},
+				CallBodyPos: 24,
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		tc := tt
+
+		t.Run(tc.name, func(t *testing.T) {
+			proxy := proxyResultParams(tc.goFunc, tc.exportName)
+			require.Equal(t, tc.expected, proxy)
+		})
+	}
+}