Repository: tetratelabs/wazero
Pull request #942 (2022-12-19 08:39:12)

Improves instantiation performance by removing ProxyFunc
This removes ProxyFunc, which was an internal experiment to make functions that use memory to store parameters or results easier to see. The main issue with the approach was instantiation performance, as it needs to dynamically generate functions. Another approach to visibility can happen later, for example via internal logging hooks.

Notably, this fixed the performance regression after switching WASI to ProxyFunc:
```
name                       old time/op    new time/op    delta
Allocation/Compile-16        39.8ms ± 0%    39.5ms ± 0%   -0.62%  (p=0.016 n=4+5)
Allocation/Instantiate-16    1.74ms ± 4%    0.86ms ± 1%  -50.19%  (p=0.008 n=5+5)
Allocation/Call-16           4.25µs ± 1%    4.21µs ± 2%     ~     (p=0.151 n=5+5)

name                       old alloc/op   new alloc/op   delta
Allocation/Compile-16        20.3MB ± 0%    20.3MB ± 0%     ~     (p=0.841 n=5+5)
Allocation/Instantiate-16    1.04MB ± 0%    0.56MB ± 0%  -45.88%  (p=0.008 n=5+5)
Allocation/Call-16            48.0B ± 0%     48.0B ± 0%     ~     (all equal)

name                       old allocs/op  new allocs/op  delta
Allocation/Compile-16          432k ± 0%      432k ± 0%     ~     (p=0.833 n=5+5)
Allocation/Instantiate-16     16.6k ± 0%      6.7k ± 0%  -59.34%  (p=0.008 n=5+5)
Allocation/Call-16             5.00 ± 0%      5.00 ± 0%     ~     (all equal)
```

Since we also removed it from `GOARCH=wasm GOOS=js`, we experienced a performance benefit there as well:
```
name               old time/op    new time/op    delta
_main/gojs.Run-16    13.7ms ± 1%    12.2ms ± 3%  -10.76%  (p=0.008 n=5+5)

name               old alloc/op   new alloc/op   delta
_main/gojs.Run-16    25.4MB ± 0%    25.0MB ± 0%   -1.66%  (p=0.008 n=5+5)

name               old allocs/op  new allocs/op  delta
_main/gojs.Run-16      166k ± 0%      158k ± 0%   -4.79%  (p=0.016 n=4+5)

```



Patch:
diff --git a/builder.go b/builder.go
index 56fa1f3e..4a5073fa 100644
--- a/builder.go
+++ b/builder.go
@@ -306,11 +306,6 @@ func (b *hostModuleBuilder) ExportHostFunc(fn *wasm.HostFunc) {
 	b.nameToGoFunc[fn.ExportNames[0]] = fn
 }
 
-// ExportProxyFunc implements wasm.ProxyFuncExporter
-func (b *hostModuleBuilder) ExportProxyFunc(fn *wasm.ProxyFunc) {
-	b.nameToGoFunc[fn.Name()] = fn
-}
-
 // NewFunctionBuilder implements HostModuleBuilder.NewFunctionBuilder
 func (b *hostModuleBuilder) NewFunctionBuilder() HostFunctionBuilder {
 	return &hostFunctionBuilder{b: b}
diff --git a/imports/go/gojs.go b/imports/go/gojs.go
index 4d5a7eb8..7b3388e9 100644
--- a/imports/go/gojs.go
+++ b/imports/go/gojs.go
@@ -91,32 +91,31 @@ func Run(ctx context.Context, r wazero.Runtime, compiled wazero.CompiledModule,
 func hostModuleBuilder(r wazero.Runtime) (builder wazero.HostModuleBuilder) {
 	builder = r.NewHostModuleBuilder("go")
 	hfExporter := builder.(wasm.HostFuncExporter)
-	pfExporter := builder.(wasm.ProxyFuncExporter)
 
-	pfExporter.ExportProxyFunc(GetRandomData)
-	pfExporter.ExportProxyFunc(Nanotime1)
-	pfExporter.ExportProxyFunc(WasmExit)
-	pfExporter.ExportProxyFunc(CopyBytesToJS)
-	pfExporter.ExportProxyFunc(ValueCall)
-	pfExporter.ExportProxyFunc(ValueGet)
-	pfExporter.ExportProxyFunc(ValueIndex)
-	pfExporter.ExportProxyFunc(ValueLength)
-	pfExporter.ExportProxyFunc(ValueNew)
-	pfExporter.ExportProxyFunc(ValueSet)
-	pfExporter.ExportProxyFunc(WasmWrite)
+	hfExporter.ExportHostFunc(GetRandomData)
+	hfExporter.ExportHostFunc(Nanotime1)
+	hfExporter.ExportHostFunc(WasmExit)
+	hfExporter.ExportHostFunc(CopyBytesToJS)
+	hfExporter.ExportHostFunc(ValueCall)
+	hfExporter.ExportHostFunc(ValueGet)
+	hfExporter.ExportHostFunc(ValueIndex)
+	hfExporter.ExportHostFunc(ValueLength)
+	hfExporter.ExportHostFunc(ValueNew)
+	hfExporter.ExportHostFunc(ValueSet)
+	hfExporter.ExportHostFunc(WasmWrite)
 	hfExporter.ExportHostFunc(ResetMemoryDataView)
-	pfExporter.ExportProxyFunc(Walltime)
+	hfExporter.ExportHostFunc(Walltime)
 	hfExporter.ExportHostFunc(ScheduleTimeoutEvent)
 	hfExporter.ExportHostFunc(ClearTimeoutEvent)
-	pfExporter.ExportProxyFunc(FinalizeRef)
-	pfExporter.ExportProxyFunc(StringVal)
+	hfExporter.ExportHostFunc(FinalizeRef)
+	hfExporter.ExportHostFunc(StringVal)
 	hfExporter.ExportHostFunc(ValueDelete)
 	hfExporter.ExportHostFunc(ValueSetIndex)
 	hfExporter.ExportHostFunc(ValueInvoke)
-	pfExporter.ExportProxyFunc(ValuePrepareString)
+	hfExporter.ExportHostFunc(ValuePrepareString)
 	hfExporter.ExportHostFunc(ValueInstanceOf)
-	pfExporter.ExportProxyFunc(ValueLoadString)
-	pfExporter.ExportProxyFunc(CopyBytesToGo)
+	hfExporter.ExportHostFunc(ValueLoadString)
+	hfExporter.ExportHostFunc(CopyBytesToGo)
 	hfExporter.ExportHostFunc(Debug)
 	return
 }
diff --git a/imports/wasi_snapshot_preview1/args.go b/imports/wasi_snapshot_preview1/args.go
index 35259f02..3fc77511 100644
--- a/imports/wasi_snapshot_preview1/args.go
+++ b/imports/wasi_snapshot_preview1/args.go
@@ -46,7 +46,7 @@ const (
 // See https://en.wikipedia.org/wiki/Null-terminated_string
 var argsGet = newHostFunc(argsGetName, argsGetFn, []api.ValueType{i32, i32}, "argv", "argv_buf")
 
-func argsGetFn(ctx context.Context, mod api.Module, params []uint64) Errno {
+func argsGetFn(_ context.Context, mod api.Module, params []uint64) Errno {
 	sysCtx := mod.(*wasm.CallContext).Sys
 	argv, argvBuf := uint32(params[0]), uint32(params[1])
 	return writeOffsetsAndNullTerminatedValues(mod.Memory(), sysCtx.Args(), argv, argvBuf, sysCtx.ArgsSize())
@@ -81,17 +81,20 @@ 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 = 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)
+var argsSizesGet = newHostFunc(argsSizesGetName, argsSizesGetFn, []api.ValueType{i32, i32}, "result.argc", "result.argv_len")
 
-func argsSizesGetFn(_ context.Context, mod api.Module, _ []uint64) (argc, argvLen uint32, errno Errno) {
+func argsSizesGetFn(_ context.Context, mod api.Module, params []uint64) Errno {
 	sysCtx := mod.(*wasm.CallContext).Sys
-	argc = uint32(len(sysCtx.Args()))
-	argvLen = sysCtx.ArgsSize()
-	errno = ErrnoSuccess
-	return
+	mem := mod.Memory()
+	resultArgc, resultArgvLen := uint32(params[0]), uint32(params[1])
+
+	// argc and argv_len offsets are not necessarily sequential, so we have to
+	// write them independently.
+	if !mem.WriteUint32Le(resultArgc, uint32(len(sysCtx.Args()))) {
+		return ErrnoFault
+	}
+	if !mem.WriteUint32Le(resultArgvLen, sysCtx.ArgsSize()) {
+		return ErrnoFault
+	}
+	return ErrnoSuccess
 }
diff --git a/imports/wasi_snapshot_preview1/clock.go b/imports/wasi_snapshot_preview1/clock.go
index 126228dd..7e1e4ec0 100644
--- a/imports/wasi_snapshot_preview1/clock.go
+++ b/imports/wasi_snapshot_preview1/clock.go
@@ -51,29 +51,26 @@ 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 = 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)
+var clockResGet = newHostFunc(clockResGetName, clockResGetFn, []api.ValueType{i32, i32}, "id", "result.resolution")
 
-func clockResGetFn(_ context.Context, mod api.Module, stack []uint64) (resolution uint64, errno Errno) {
+func clockResGetFn(_ context.Context, mod api.Module, params []uint64) Errno {
 	sysCtx := mod.(*wasm.CallContext).Sys
-	id := uint32(stack[0])
+	id, resultResolution := uint32(params[0]), uint32(params[1])
 
-	errno = ErrnoSuccess
+	var resolution uint64 // ns
 	switch id {
 	case clockIDRealtime:
 		resolution = uint64(sysCtx.WalltimeResolution())
 	case clockIDMonotonic:
 		resolution = uint64(sysCtx.NanotimeResolution())
 	default:
-		errno = ErrnoInval
+		return ErrnoInval
 	}
-	return
+
+	if !mod.Memory().WriteUint64Le(resultResolution, resolution) {
+		return ErrnoFault
+	}
+	return ErrnoSuccess
 }
 
 // clockTimeGet is the WASI function named clockTimeGetName that returns
@@ -107,30 +104,28 @@ func clockResGetFn(_ context.Context, mod api.Module, stack []uint64) (resolutio
 // 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 = 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)
+var clockTimeGet = newHostFunc(clockTimeGetName, clockTimeGetFn, []api.ValueType{i32, i64, i32}, "id", "precision", "result.timestamp")
 
-func clockTimeGetFn(_ context.Context, mod api.Module, stack []uint64) (timestamp int64, errno Errno) {
+func clockTimeGetFn(_ context.Context, mod api.Module, params []uint64) Errno {
 	sysCtx := mod.(*wasm.CallContext).Sys
-	id := uint32(stack[0])
+	id := uint32(params[0])
 	// TODO: precision is currently ignored.
 	// precision = params[1]
+	resultTimestamp := uint32(params[2])
 
+	var val int64
 	switch id {
 	case clockIDRealtime:
 		sec, nsec := sysCtx.Walltime()
-		timestamp = (sec * time.Second.Nanoseconds()) + int64(nsec)
+		val = (sec * time.Second.Nanoseconds()) + int64(nsec)
 	case clockIDMonotonic:
-		timestamp = sysCtx.Nanotime()
+		val = sysCtx.Nanotime()
 	default:
-		return 0, ErrnoInval
+		return ErrnoInval
+	}
+
+	if !mod.Memory().WriteUint64Le(resultTimestamp, uint64(val)) {
+		return ErrnoFault
 	}
-	errno = ErrnoSuccess
-	return
+	return ErrnoSuccess
 }
diff --git a/imports/wasi_snapshot_preview1/environ.go b/imports/wasi_snapshot_preview1/environ.go
index f428c669..01348d5e 100644
--- a/imports/wasi_snapshot_preview1/environ.go
+++ b/imports/wasi_snapshot_preview1/environ.go
@@ -84,18 +84,20 @@ func environGetFn(_ 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 = 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)
+var environSizesGet = newHostFunc(environSizesGetName, environSizesGetFn, []api.ValueType{i32, i32}, "result.environc", "result.environv_len")
 
-func environSizesGetFn(_ context.Context, mod api.Module, _ []uint64) (environc, environvLen uint32, errno Errno) {
+func environSizesGetFn(_ context.Context, mod api.Module, params []uint64) Errno {
 	sysCtx := mod.(*wasm.CallContext).Sys
+	mem := mod.Memory()
+	resultEnvironc, resultEnvironvLen := uint32(params[0]), uint32(params[1])
 
-	environc = uint32(len(sysCtx.Environ()))
-	environvLen = sysCtx.EnvironSize()
-	errno = ErrnoSuccess
-	return
+	// environc and environv_len offsets are not necessarily sequential, so we
+	// have to write them independently.
+	if !mem.WriteUint32Le(resultEnvironc, uint32(len(sysCtx.Environ()))) {
+		return ErrnoFault
+	}
+	if !mem.WriteUint32Le(resultEnvironvLen, sysCtx.EnvironSize()) {
+		return ErrnoFault
+	}
+	return ErrnoSuccess
 }
diff --git a/imports/wasi_snapshot_preview1/example/README.md b/imports/wasi_snapshot_preview1/example/README.md
index 4acdd751..221e9bb9 100644
--- a/imports/wasi_snapshot_preview1/example/README.md
+++ b/imports/wasi_snapshot_preview1/example/README.md
@@ -16,6 +16,7 @@ to use Wasm built with "tinygo". Here are the included examples:
 * [zig-cc](testdata/zig-cc) - Built via `zig cc cat.c -o cat.wasm --target=wasm32-wasi -O3`
 
 To run the same example with zig-cc:
+
 ```bash
 $ TOOLCHAIN=zig-cc go run cat.go /test.txt
 greet filesystem
@@ -54,7 +55,11 @@ stdin/stdout/stderr and [suggest using wasi-libc instead][5]. This is used in
 the [zig-cc](testdata/zig-cc) example.
 
 [1]: https://github.com/bytecodealliance/cargo-wasi
+
 [2]: https://github.com/rust-lang/rust/issues/73432
+
 [3]: https://github.com/bytecodealliance/cargo-wasi
+
 [4]: https://github.com/WebAssembly/binaryen
+
 [5]: https://github.com/emscripten-core/emscripten/issues/17167#issuecomment-1150252755
diff --git a/imports/wasi_snapshot_preview1/fs.go b/imports/wasi_snapshot_preview1/fs.go
index 804b8945..e9d5857b 100644
--- a/imports/wasi_snapshot_preview1/fs.go
+++ b/imports/wasi_snapshot_preview1/fs.go
@@ -100,7 +100,7 @@ func fdCloseFn(_ context.Context, mod api.Module, params []uint64) Errno {
 // the data of a file to disk.
 //
 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_datasyncfd-fd---errno
-var fdDatasync = stubFunction(fdDatasyncName, []wasm.ValueType{i32}, "fd")
+var fdDatasync = stubFunction(fdDatasyncName, []api.ValueType{i32}, "fd")
 
 // fdFdstatGet is the WASI function named fdFdstatGetName which returns the
 // attributes of a file descriptor.
@@ -362,17 +362,14 @@ 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 = 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(_ context.Context, mod api.Module, stack []uint64) (nread uint32, errno Errno) {
-	return fdReadOrPread(mod, stack, true)
+var fdPread = newHostFunc(
+	fdPreadName, fdPreadFn,
+	[]api.ValueType{i32, i32, i32, i64, i32},
+	"fd", "iovs", "iovs_len", "offset", "result.nread",
+)
+
+func fdPreadFn(_ context.Context, mod api.Module, params []uint64) Errno {
+	return fdReadOrPread(mod, params, true)
 }
 
 // fdPrestatGet is the WASI function named fdPrestatGetName which returns
@@ -406,34 +403,29 @@ func fdPreadFn(_ context.Context, mod api.Module, stack []uint64) (nread uint32,
 //
 // See fdPrestatDirName and
 // https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#prestat
-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(_ context.Context, mod api.Module, stack []uint64) (prestat uint64, errno Errno) {
+var fdPrestatGet = newHostFunc(fdPrestatGetName, fdPrestatGetFn, []api.ValueType{i32, i32}, "fd", "result.prestat")
+
+func fdPrestatGetFn(_ context.Context, mod api.Module, params []uint64) Errno {
 	fsc := mod.(*wasm.CallContext).Sys.FS()
-	fd := uint32(stack[0])
+	fd, resultPrestat := uint32(params[0]), uint32(params[1])
 
 	// Currently, we only pre-open the root file descriptor.
 	if fd != internalsys.FdRoot {
-		return 0, ErrnoBadf
+		return ErrnoBadf
 	}
 
 	entry, ok := fsc.OpenedFile(fd)
 	if !ok {
-		return 0, ErrnoBadf
+		return ErrnoBadf
 	}
 
 	// Upper 32-bits are zero because...
 	// * Zero-value 8-bit tag, and 3-byte zero-value padding
-	prestat = uint64(len(entry.Name) << 32)
-	errno = ErrnoSuccess
-	return
+	prestat := uint64(len(entry.Name) << 32)
+	if !mod.Memory().WriteUint64Le(resultPrestat, prestat) {
+		return ErrnoFault
+	}
+	return ErrnoSuccess
 }
 
 // fdPrestatDirName is the WASI function named fdPrestatDirNameName which
@@ -556,44 +548,45 @@ var fdPwrite = stubFunction(
 //
 // See fdWrite
 // and https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_read
-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(_ context.Context, mod api.Module, stack []uint64) (nread uint32, errno Errno) {
-	return fdReadOrPread(mod, stack, false)
+var fdRead = newHostFunc(
+	fdReadName, fdReadFn,
+	[]api.ValueType{i32, i32, i32, i32},
+	"fd", "iovs", "iovs_len", "result.nread",
+)
+
+func fdReadFn(_ context.Context, mod api.Module, params []uint64) Errno {
+	return fdReadOrPread(mod, params, false)
 }
 
-func fdReadOrPread(mod api.Module, stack []uint64, isPread bool) (uint32, Errno) {
+func fdReadOrPread(mod api.Module, params []uint64, isPread bool) Errno {
 	mem := mod.Memory()
 	fsc := mod.(*wasm.CallContext).Sys.FS()
 
-	fd := uint32(stack[0])
-	iovs := uint32(stack[1])
-	iovsCount := uint32(stack[2])
+	fd := uint32(params[0])
+	iovs := uint32(params[1])
+	iovsCount := uint32(params[2])
 
 	var offset int64
+	var resultNread uint32
 	if isPread {
-		offset = int64(stack[3])
+		offset = int64(params[3])
+		resultNread = uint32(params[4])
+	} else {
+		resultNread = uint32(params[3])
 	}
 
 	r := fsc.FdReader(fd)
 	if r == nil {
-		return 0, ErrnoBadf
+		return ErrnoBadf
 	}
 
 	if isPread {
 		if s, ok := r.(io.Seeker); ok {
 			if _, err := s.Seek(offset, io.SeekStart); err != nil {
-				return 0, ErrnoFault
+				return ErrnoFault
 			}
 		} else {
-			return 0, ErrnoInval
+			return ErrnoInval
 		}
 	}
 
@@ -601,7 +594,7 @@ func fdReadOrPread(mod api.Module, stack []uint64, isPread bool) (uint32, Errno)
 	iovsStop := iovsCount << 3 // iovsCount * 8
 	iovsBuf, ok := mem.Read(iovs, iovsStop)
 	if !ok {
-		return 0, ErrnoFault
+		return ErrnoFault
 	}
 
 	for iovsPos := uint32(0); iovsPos < iovsStop; iovsPos += 8 {
@@ -610,7 +603,7 @@ func fdReadOrPread(mod api.Module, stack []uint64, isPread bool) (uint32, Errno)
 
 		b, ok := mem.Read(offset, l)
 		if !ok {
-			return 0, ErrnoFault
+			return ErrnoFault
 		}
 
 		n, err := r.Read(b)
@@ -618,12 +611,16 @@ func fdReadOrPread(mod api.Module, stack []uint64, isPread bool) (uint32, Errno)
 
 		shouldContinue, errno := fdRead_shouldContinueRead(uint32(n), l, err)
 		if errno != ErrnoSuccess {
-			return 0, errno
+			return errno
 		} else if !shouldContinue {
 			break
 		}
 	}
-	return nread, ErrnoSuccess
+	if !mem.WriteUint32Le(resultNread, nread) {
+		return ErrnoFault
+	} else {
+		return ErrnoSuccess
+	}
 }
 
 // fdRead_shouldContinueRead decides whether to continue reading the next iovec
@@ -647,42 +644,40 @@ 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 = 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(_ context.Context, mod api.Module, stack []uint64) (uint32, Errno) {
+var fdReaddir = newHostFunc(
+	fdReaddirName, fdReaddirFn,
+	[]wasm.ValueType{i32, i32, i32, i64, i32},
+	"fd", "buf", "buf_len", "cookie", "result.bufused",
+)
+
+func fdReaddirFn(_ context.Context, mod api.Module, params []uint64) Errno {
 	mem := mod.Memory()
 	fsc := mod.(*wasm.CallContext).Sys.FS()
 
-	fd := uint32(stack[0])
-	buf := uint32(stack[1])
-	bufLen := uint32(stack[2])
+	fd := uint32(params[0])
+	buf := uint32(params[1])
+	bufLen := uint32(params[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(stack[3])
+	cookie := int64(params[3])
+	resultBufused := uint32(params[4])
 
 	// The bufLen must be enough to write a dirent. Otherwise, the caller can't
 	// read what the next cookie is.
 	if bufLen < direntSize {
-		return 0, ErrnoInval
+		return ErrnoInval
 	}
 
 	// Validate the FD is a directory
 	rd, dir, errno := openedDir(fsc, fd)
 	if errno != ErrnoSuccess {
-		return 0, errno
+		return errno
 	}
 
 	// expect a cookie only if we are continuing a read.
 	if cookie == 0 && dir.CountRead > 0 {
-		return 0, ErrnoInval // cookie is minimally one.
+		return ErrnoInval // cookie is minimally one.
 	}
 
 	// First, determine the maximum directory entries that can be encoded as
@@ -702,14 +697,14 @@ func fdReaddirFn(_ context.Context, mod api.Module, stack []uint64) (uint32, Err
 	// we cannot seek to a previous directory position. Collect these entries.
 	entries, errno := lastDirEntries(dir, cookie)
 	if errno != ErrnoSuccess {
-		return 0, errno
+		return 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 0, ErrnoIo
+				return ErrnoIo
 			}
 			dir.CountRead += uint64(len(l))
 			entries = append(entries, l...)
@@ -733,13 +728,16 @@ func fdReaddirFn(_ context.Context, mod api.Module, stack []uint64) (uint32, Err
 
 		dirents, ok := mem.Read(buf, bufused)
 		if !ok {
-			return 0, ErrnoFault
+			return ErrnoFault
 		}
 
 		writeDirents(entries, direntCount, writeTruncatedEntry, dirents, d_next)
 	}
 
-	return bufused, ErrnoSuccess
+	if !mem.WriteUint32Le(resultBufused, bufused) {
+		return ErrnoFault
+	}
+	return ErrnoSuccess
 }
 
 const largestDirent = int64(math.MaxUint32 - direntSize)
@@ -958,51 +956,52 @@ 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 = 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(_ context.Context, mod api.Module, stack []uint64) (int64, Errno) {
+var fdSeek = newHostFunc(
+	fdSeekName, fdSeekFn,
+	[]api.ValueType{i32, i64, i32, i32},
+	"fd", "offset", "whence", "result.newoffset",
+)
+
+func fdSeekFn(_ context.Context, mod api.Module, params []uint64) Errno {
 	fsc := mod.(*wasm.CallContext).Sys.FS()
-	fd := uint32(stack[0])
-	offset := stack[1]
-	whence := uint32(stack[2])
+	fd := uint32(params[0])
+	offset := params[1]
+	whence := uint32(params[2])
+	resultNewoffset := uint32(params[3])
 
 	if fd == internalsys.FdRoot {
-		return 0, ErrnoBadf // cannot seek a directory
+		return ErrnoBadf // cannot seek a directory
 	}
 
 	var seeker io.Seeker
 	// Check to see if the file descriptor is available
 	if f, ok := fsc.OpenedFile(fd); !ok {
-		return 0, ErrnoBadf
+		return ErrnoBadf
 		// fs.FS doesn't declare io.Seeker, but implementations such as os.File implement it.
 	} else if seeker, ok = f.File.(io.Seeker); !ok {
-		return 0, ErrnoBadf
+		return ErrnoBadf
 	}
 
 	if whence > io.SeekEnd /* exceeds the largest valid whence */ {
-		return 0, ErrnoInval
+		return ErrnoInval
 	}
 
 	newOffset, err := seeker.Seek(int64(offset), int(whence))
 	if err != nil {
-		return 0, ErrnoIo
+		return ErrnoIo
 	}
 
-	return newOffset, ErrnoSuccess
+	if !mod.Memory().WriteUint64Le(resultNewoffset, uint64(newOffset)) {
+		return ErrnoFault
+	}
+	return ErrnoSuccess
 }
 
 // fdSync is the WASI function named fdSyncName which synchronizes the data
 // and metadata of a file to disk.
 //
 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_syncfd-fd---errno
-var fdSync = stubFunction(fdSyncName, []wasm.ValueType{i32}, "fd")
+var fdSync = stubFunction(fdSyncName, []api.ValueType{i32}, "fd")
 
 // fdTell is the WASI function named fdTellName which returns the current
 // offset of a file descriptor.
@@ -1068,26 +1067,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 = 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(_ context.Context, mod api.Module, stack []uint64) (uint32, Errno) {
+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 {
 	mem := mod.Memory()
 	fsc := mod.(*wasm.CallContext).Sys.FS()
 
-	fd := uint32(stack[0])
-	iovs := uint32(stack[1])
-	iovsCount := uint32(stack[2])
+	fd := uint32(params[0])
+	iovs := uint32(params[1])
+	iovsCount := uint32(params[2])
+	resultNwritten := uint32(params[3])
 
 	writer := fsc.FdWriter(fd)
 	if writer == nil {
-		return 0, ErrnoBadf
+		return ErrnoBadf
 	}
 
 	var err error
@@ -1095,7 +1092,7 @@ func fdWriteFn(_ context.Context, mod api.Module, stack []uint64) (uint32, Errno
 	iovsStop := iovsCount << 3 // iovsCount * 8
 	iovsBuf, ok := mem.Read(iovs, iovsStop)
 	if !ok {
-		return 0, ErrnoFault
+		return ErrnoFault
 	}
 
 	for iovsPos := uint32(0); iovsPos < iovsStop; iovsPos += 8 {
@@ -1108,16 +1105,20 @@ func fdWriteFn(_ context.Context, mod api.Module, stack []uint64) (uint32, Errno
 		} else {
 			b, ok := mem.Read(offset, l)
 			if !ok {
-				return 0, ErrnoFault
+				return ErrnoFault
 			}
 			n, err = writer.Write(b)
 			if err != nil {
-				return 0, ErrnoIo
+				return ErrnoIo
 			}
 		}
 		nwritten += uint32(n)
 	}
-	return nwritten, ErrnoSuccess
+
+	if !mod.Memory().WriteUint32Le(resultNwritten, nwritten) {
+		return ErrnoFault
+	}
+	return ErrnoSuccess
 }
 
 // pathCreateDirectory is the WASI function named pathCreateDirectoryName
@@ -1287,14 +1288,11 @@ 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 = 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)
+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",
+)
 
 // wasiOflags are open flags used by pathOpen
 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-oflags-flagsu16
@@ -1311,7 +1309,7 @@ const (
 	wasiOflagsTrunc // nolint
 )
 
-func pathOpenFn(_ context.Context, mod api.Module, params []uint64) (uint32, Errno) {
+func pathOpenFn(_ context.Context, mod api.Module, params []uint64) Errno {
 	fsc := mod.(*wasm.CallContext).Sys.FS()
 
 	dirfd := uint32(params[0])
@@ -1333,6 +1331,7 @@ func pathOpenFn(_ context.Context, mod api.Module, params []uint64) (uint32, Err
 
 	// TODO: only notable fdflag for opening is wasiFdflagsAppend
 	_ /* fdflags */ = wasiFdflags(uint32(params[7]))
+	resultOpenedFd := uint32(params[8])
 
 	// Note: We don't handle AT_FDCWD, as that's resolved in the compiler.
 	// There's no working directory function in WASI, so CWD cannot be handled
@@ -1340,12 +1339,12 @@ func pathOpenFn(_ context.Context, mod api.Module, params []uint64) (uint32, Err
 	//
 	// See https://github.com/WebAssembly/wasi-libc/blob/659ff414560721b1660a19685110e484a081c3d4/libc-bottom-half/sources/at_fdcwd.c#L24-L26
 	if _, ok := fsc.OpenedFile(dirfd); !ok {
-		return 0, ErrnoBadf
+		return ErrnoBadf
 	}
 
 	b, ok := mod.Memory().Read(path, pathLen)
 	if !ok {
-		return 0, ErrnoFault
+		return ErrnoFault
 	}
 
 	// TODO: path is not precise here, as it should be a path relative to the
@@ -1354,17 +1353,23 @@ func pathOpenFn(_ context.Context, mod api.Module, params []uint64) (uint32, Err
 	// path="bar", this should open "/tmp/foo/bar" not "/bar".
 	//
 	// See https://linux.die.net/man/2/openat
-	newFD, errnoResult := openFile(fsc, string(b))
-	if errnoResult != ErrnoSuccess {
-		return 0, errnoResult
+	newFD, errno := openFile(fsc, string(b))
+	if errno != ErrnoSuccess {
+		return errno
 	}
 
 	// Check any flags that require the file to evaluate.
 	if oflags&wasiOflagsDirectory != 0 {
-		return newFD, failIfNotDirectory(fsc, newFD)
+		if errno = failIfNotDirectory(fsc, newFD); errno != ErrnoSuccess {
+			return errno
+		}
 	}
 
-	return newFD, ErrnoSuccess
+	if !mod.Memory().WriteUint32Le(resultOpenedFd, newFD) {
+		_ = fsc.CloseFile(newFD)
+		return ErrnoFault
+	}
+	return ErrnoSuccess
 }
 
 func failIfNotDirectory(fsc *internalsys.FSContext, fd uint32) Errno {
diff --git a/imports/wasi_snapshot_preview1/poll.go b/imports/wasi_snapshot_preview1/poll.go
index 09d523ed..edb74670 100644
--- a/imports/wasi_snapshot_preview1/poll.go
+++ b/imports/wasi_snapshot_preview1/poll.go
@@ -45,22 +45,20 @@ 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 = 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) {
+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 {
 	in := uint32(params[0])
 	out := uint32(params[1])
 	nsubscriptions := uint32(params[2])
+	resultNevents := uint32(params[3])
 
 	if nsubscriptions == 0 {
-		return 0, ErrnoInval
+		return ErrnoInval
 	}
 
 	mem := mod.Memory()
@@ -68,17 +66,23 @@ func pollOneoffFn(ctx context.Context, mod api.Module, params []uint64) (nevents
 	// Ensure capacity prior to the read loop to reduce error handling.
 	inBuf, ok := mem.Read(in, nsubscriptions*48)
 	if !ok {
-		return 0, ErrnoFault
+		return ErrnoFault
 	}
 	outBuf, ok := mem.Read(out, nsubscriptions*32)
 	if !ok {
-		return 0, ErrnoFault
+		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(resultNevents, nsubscriptions) {
+		return ErrnoFault
 	}
 
 	// Loop through all subscriptions and write their output.
-	for ; nevents < nsubscriptions; nevents++ {
-		inOffset := nevents * 48
-		outOffset := nevents * 32
+	for i := uint32(0); i < nsubscriptions; i++ {
+		inOffset := i * 48
+		outOffset := i * 32
 
 		eventType := inBuf[inOffset+8] // +8 past userdata
 		var errno Errno                // errno for this specific event
@@ -90,7 +94,7 @@ func pollOneoffFn(ctx context.Context, mod api.Module, params []uint64) (nevents
 			// +8 past userdata +4 FD alignment
 			errno = processFDEvent(mod, eventType, inBuf[inOffset+8+4:])
 		default:
-			return 0, ErrnoInval
+			return ErrnoInval
 		}
 
 		// Write the event corresponding to the processed subscription.
@@ -101,7 +105,7 @@ func pollOneoffFn(ctx context.Context, mod api.Module, params []uint64) (nevents
 		le.PutUint32(outBuf[outOffset+10:], uint32(eventType))
 		// TODO: When FD events are supported, write outOffset+16
 	}
-	return nevents, ErrnoSuccess
+	return ErrnoSuccess
 }
 
 // processClockEvent supports only relative name events, as that's what's used
diff --git a/imports/wasi_snapshot_preview1/proc.go b/imports/wasi_snapshot_preview1/proc.go
index 597e1910..d67b0248 100644
--- a/imports/wasi_snapshot_preview1/proc.go
+++ b/imports/wasi_snapshot_preview1/proc.go
@@ -48,4 +48,4 @@ func procExitFn(ctx context.Context, mod api.Module, params []uint64) {
 // procRaise is stubbed and will never be supported, as it was removed.
 //
 // See https://github.com/WebAssembly/WASI/pull/136
-var procRaise = stubFunction(procRaiseName, []wasm.ValueType{i32}, "sig")
+var procRaise = stubFunction(procRaiseName, []api.ValueType{i32}, "sig")
diff --git a/imports/wasi_snapshot_preview1/wasi.go b/imports/wasi_snapshot_preview1/wasi.go
index 85f617d1..314068cb 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.(wasm.ProxyFuncExporter).ExportProxyFunc(argsSizesGet)
+	exporter.ExportHostFunc(argsSizesGet)
 	exporter.ExportHostFunc(environGet)
-	exporter.(wasm.ProxyFuncExporter).ExportProxyFunc(environSizesGet)
-	exporter.(wasm.ProxyFuncExporter).ExportProxyFunc(clockResGet)
-	exporter.(wasm.ProxyFuncExporter).ExportProxyFunc(clockTimeGet)
+	exporter.ExportHostFunc(environSizesGet)
+	exporter.ExportHostFunc(clockResGet)
+	exporter.ExportHostFunc(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.(wasm.ProxyFuncExporter).ExportProxyFunc(fdPread)
-	exporter.(wasm.ProxyFuncExporter).ExportProxyFunc(fdPrestatGet)
+	exporter.ExportHostFunc(fdPread)
+	exporter.ExportHostFunc(fdPrestatGet)
 	exporter.ExportHostFunc(fdPrestatDirName)
 	exporter.ExportHostFunc(fdPwrite)
-	exporter.(wasm.ProxyFuncExporter).ExportProxyFunc(fdRead)
-	exporter.(wasm.ProxyFuncExporter).ExportProxyFunc(fdReaddir)
+	exporter.ExportHostFunc(fdRead)
+	exporter.ExportHostFunc(fdReaddir)
 	exporter.ExportHostFunc(fdRenumber)
-	exporter.(wasm.ProxyFuncExporter).ExportProxyFunc(fdSeek)
+	exporter.ExportHostFunc(fdSeek)
 	exporter.ExportHostFunc(fdSync)
 	exporter.ExportHostFunc(fdTell)
-	exporter.(wasm.ProxyFuncExporter).ExportProxyFunc(fdWrite)
+	exporter.ExportHostFunc(fdWrite)
 	exporter.ExportHostFunc(pathCreateDirectory)
 	exporter.ExportHostFunc(pathFilestatGet)
 	exporter.ExportHostFunc(pathFilestatSetTimes)
 	exporter.ExportHostFunc(pathLink)
-	exporter.(wasm.ProxyFuncExporter).ExportProxyFunc(pathOpen)
+	exporter.ExportHostFunc(pathOpen)
 	exporter.ExportHostFunc(pathReadlink)
 	exporter.ExportHostFunc(pathRemoveDirectory)
 	exporter.ExportHostFunc(pathRename)
 	exporter.ExportHostFunc(pathSymlink)
 	exporter.ExportHostFunc(pathUnlinkFile)
-	exporter.(wasm.ProxyFuncExporter).ExportProxyFunc(pollOneoff)
+	exporter.ExportHostFunc(pollOneoff)
 	exporter.ExportHostFunc(procExit)
 	exporter.ExportHostFunc(procRaise)
 	exporter.ExportHostFunc(schedYield)
@@ -258,7 +258,12 @@ func writeOffsetsAndNullTerminatedValues(mem api.Memory, values [][]byte, offset
 	return ErrnoSuccess
 }
 
-func newHostFunc(name string, goFunc wasiFunc, paramTypes []api.ValueType, paramNames ...string) *wasm.HostFunc {
+func newHostFunc(
+	name string,
+	goFunc wasiFunc,
+	paramTypes []api.ValueType,
+	paramNames ...string,
+) *wasm.HostFunc {
 	return &wasm.HostFunc{
 		ExportNames: []string{name},
 		Name:        name,
@@ -287,7 +292,7 @@ func stubFunction(name string, paramTypes []wasm.ValueType, paramNames ...string
 		ExportNames: []string{name},
 		ParamTypes:  paramTypes,
 		ParamNames:  paramNames,
-		ResultTypes: []wasm.ValueType{i32},
+		ResultTypes: []api.ValueType{i32},
 		ResultNames: []string{"errno"},
 		Code: &wasm.Code{
 			IsHostFunction: true,
diff --git a/imports/wasi_snapshot_preview1/wrapper.go b/imports/wasi_snapshot_preview1/wrapper.go
deleted file mode 100644
index 9ef429e9..00000000
--- a/imports/wasi_snapshot_preview1/wrapper.go
+++ /dev/null
@@ -1,239 +0,0 @@
-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))
-	}
-}
diff --git a/internal/gojs/runtime.go b/internal/gojs/runtime.go
index ed494513..ed2d3961 100644
--- a/internal/gojs/runtime.go
+++ b/internal/gojs/runtime.go
@@ -5,13 +5,10 @@ import (
 	"fmt"
 
 	"github.com/tetratelabs/wazero/api"
-	"github.com/tetratelabs/wazero/internal/gojs/spfunc"
 	"github.com/tetratelabs/wazero/internal/wasm"
 )
 
 const (
-	i32, i64 = api.ValueTypeI32, api.ValueTypeI64
-
 	wasmExitName             = "runtime.wasmExit"
 	wasmWriteName            = "runtime.wasmWrite"
 	resetMemoryDataViewName  = "runtime.resetMemoryDataView"
@@ -25,19 +22,13 @@ const (
 // WasmExit implements runtime.wasmExit which supports runtime.exit.
 //
 // See https://github.com/golang/go/blob/go1.19/src/runtime/sys_wasm.go#L28
-var WasmExit = spfunc.MustCallFromSP(false, &wasm.HostFunc{
-	ExportNames: []string{wasmExitName},
-	Name:        wasmExitName,
-	ParamTypes:  []api.ValueType{i32},
-	ParamNames:  []string{"code"},
-	Code: &wasm.Code{
-		IsHostFunction: true,
-		GoFunc:         api.GoModuleFunc(wasmExit),
-	},
-})
-
-func wasmExit(ctx context.Context, mod api.Module, stack []uint64) {
-	code := uint32(stack[0])
+var WasmExit = newSPFunc(wasmExitName, wasmExit)
+
+func wasmExit(ctx context.Context, mod api.Module, sp []uint64) {
+	mem := mod.Memory()
+
+	// Read the code from offset SP+8
+	code := mustReadUint32Le(mem, "code", uint32(sp[0]+8))
 
 	getState(ctx).clear()
 	_ = mod.CloseWithExitCode(ctx, code) // TODO: should ours be signed bit (like -1 == 255)?
@@ -47,21 +38,18 @@ func wasmExit(ctx context.Context, mod api.Module, stack []uint64) {
 // runtime.writeErr. This implements `println`.
 //
 // See https://github.com/golang/go/blob/go1.19/src/runtime/os_js.go#L29
-var WasmWrite = spfunc.MustCallFromSP(false, &wasm.HostFunc{
-	ExportNames: []string{wasmWriteName},
-	Name:        wasmWriteName,
-	ParamTypes:  []api.ValueType{i32, i32, i32},
-	ParamNames:  []string{"fd", "p", "n"},
-	Code: &wasm.Code{
-		IsHostFunction: true,
-		GoFunc:         api.GoModuleFunc(wasmWrite),
-	},
-})
-
-func wasmWrite(_ context.Context, mod api.Module, stack []uint64) {
+var WasmWrite = newSPFunc(wasmWriteName, wasmWrite)
+
+func wasmWrite(_ context.Context, mod api.Module, sp []uint64) {
 	fsc := mod.(*wasm.CallContext).Sys.FS()
+	mem := mod.Memory()
 
-	fd, p, n := uint32(stack[0]), uint32(stack[1]), uint32(stack[2])
+	// Read (param + result count) * 8 memory starting at SP+8
+	stack := mustRead(mem, "sp", uint32(sp[0]+8), 24)
+
+	fd := le.Uint32(stack)
+	p := le.Uint32(stack[8:])
+	n := le.Uint32(stack[16:])
 
 	writer := fsc.FdWriter(fd)
 	if writer == nil {
@@ -89,39 +77,30 @@ var ResetMemoryDataView = &wasm.HostFunc{
 // Nanotime1 implements runtime.nanotime which supports time.Since.
 //
 // See https://github.com/golang/go/blob/go1.19/src/runtime/sys_wasm.s#L184
-var Nanotime1 = spfunc.MustCallFromSP(false, &wasm.HostFunc{
-	ExportNames: []string{nanotime1Name},
-	Name:        nanotime1Name,
-	ResultTypes: []api.ValueType{i64},
-	Code: &wasm.Code{
-		IsHostFunction: true,
-		GoFunc:         api.GoModuleFunc(nanotime1),
-	},
-})
-
-func nanotime1(_ context.Context, mod api.Module, stack []uint64) {
+var Nanotime1 = newSPFunc(nanotime1Name, nanotime1)
+
+func nanotime1(_ context.Context, mod api.Module, sp []uint64) {
 	time := mod.(*wasm.CallContext).Sys.Nanotime()
-	stack[0] = api.EncodeI64(time)
+	mem := mod.Memory()
+
+	// Write the result to offset SP+8
+	mustWriteUint64Le(mem, "time", uint32(sp[0]+8), api.EncodeI64(time))
 }
 
 // Walltime implements runtime.walltime which supports time.Now.
 //
 // See https://github.com/golang/go/blob/go1.19/src/runtime/sys_wasm.s#L188
-var Walltime = spfunc.MustCallFromSP(false, &wasm.HostFunc{
-	ExportNames: []string{walltimeName},
-	Name:        walltimeName,
-	ResultTypes: []api.ValueType{i64, i32},
-	ResultNames: []string{"sec", "nsec"},
-	Code: &wasm.Code{
-		IsHostFunction: true,
-		GoFunc:         api.GoModuleFunc(walltime),
-	},
-})
-
-func walltime(_ context.Context, mod api.Module, stack []uint64) {
+var Walltime = newSPFunc(walltimeName, walltime)
+
+func walltime(_ context.Context, mod api.Module, sp []uint64) {
 	sec, nsec := mod.(*wasm.CallContext).Sys.Walltime()
-	stack[0] = api.EncodeI64(sec)
-	stack[1] = api.EncodeI32(nsec)
+	mem := mod.Memory()
+
+	// Write results starting at SP+8
+	results := mustRead(mem, "sp", uint32(sp[0]+8), 16)
+
+	le.PutUint64(results, uint64(sec))
+	le.PutUint32(results[8:], uint32(nsec))
 }
 
 // ScheduleTimeoutEvent implements runtime.scheduleTimeoutEvent which supports
@@ -147,20 +126,17 @@ var ClearTimeoutEvent = stubFunction(clearTimeoutEventName)
 // for runtime.fastrand.
 //
 // See https://github.com/golang/go/blob/go1.19/src/runtime/sys_wasm.s#L200
-var GetRandomData = spfunc.MustCallFromSP(false, &wasm.HostFunc{
-	ExportNames: []string{getRandomDataName},
-	Name:        getRandomDataName,
-	ParamTypes:  []api.ValueType{i32, i32},
-	ParamNames:  []string{"buf", "bufLen"},
-	Code: &wasm.Code{
-		IsHostFunction: true,
-		GoFunc:         api.GoModuleFunc(getRandomData),
-	},
-})
-
-func getRandomData(_ context.Context, mod api.Module, stack []uint64) {
+var GetRandomData = newSPFunc(getRandomDataName, getRandomData)
+
+func getRandomData(_ context.Context, mod api.Module, sp []uint64) {
 	randSource := mod.(*wasm.CallContext).Sys.RandSource()
-	buf, bufLen := uint32(stack[0]), uint32(stack[1])
+	mem := mod.Memory()
+
+	// Read (param + result count) * 8 memory starting at SP+8
+	stack := mustRead(mem, "sp", uint32(sp[0]+8), 16)
+
+	buf := le.Uint32(stack)
+	bufLen := le.Uint32(stack[8:])
 
 	r := mustRead(mod.Memory(), "r", buf, bufLen)
 
diff --git a/internal/gojs/spfunc/spfunc.go b/internal/gojs/spfunc/spfunc.go
deleted file mode 100644
index 666d210f..00000000
--- a/internal/gojs/spfunc/spfunc.go
+++ /dev/null
@@ -1,189 +0,0 @@
-package spfunc
-
-import (
-	"errors"
-	"fmt"
-
-	"github.com/tetratelabs/wazero/api"
-	"github.com/tetratelabs/wazero/internal/leb128"
-	"github.com/tetratelabs/wazero/internal/wasm"
-)
-
-const debugMode = false
-
-func MustCallFromSP(expectSP bool, proxied *wasm.HostFunc) *wasm.ProxyFunc {
-	if ret, err := callFromSP(expectSP, proxied); err != nil {
-		panic(err)
-	} else {
-		return ret
-	}
-}
-
-// callFromSP generates code to call a function with the provided signature.
-// The returned function has a single api.ValueTypeI32 parameter of SP. Each
-// parameter is read at 8 byte offsets after that, and each result is written
-// at 8 byte offsets after the parameters.
-//
-// # Parameters
-//
-//   - expectSP: true if a constructor or method invocation. The last result is
-//     an updated SP value (i32), which affects the result memory offsets.
-func callFromSP(expectSP bool, proxied *wasm.HostFunc) (*wasm.ProxyFunc, error) {
-	params := proxied.ParamTypes
-	results := proxied.ResultTypes
-	if (8+len(params)+len(results))*8 > 255 {
-		return nil, errors.New("TODO: memory offset larger than one byte")
-	}
-
-	if debugMode {
-		fmt.Printf("(func $%s.proxy (param $%s %s)", proxied.Name, "sp", wasm.ValueTypeName(wasm.ValueTypeI32))
-	}
-
-	var localTypes []api.ValueType
-
-	resultSpOffset := 8 + len(params)*8
-	resultSPIndex := byte(0)
-	if len(results) > 0 {
-		if debugMode {
-			fmt.Printf(" (local %s %s)", wasm.ValueTypeName(wasm.ValueTypeI32), wasm.ValueTypeName(wasm.ValueTypeI64))
-		}
-		localTypes = append(localTypes, api.ValueTypeI32, api.ValueTypeI64)
-		if expectSP {
-			if debugMode {
-				fmt.Printf(" (local %s)", wasm.ValueTypeName(wasm.ValueTypeI32))
-			}
-			resultSPIndex = 3
-			resultSpOffset += 8
-			localTypes = append(localTypes, api.ValueTypeI32)
-		}
-	}
-
-	// Load all parameters onto the stack.
-	var code []byte
-	for i, t := range params {
-		if debugMode {
-			fmt.Printf("\n;; param[%d]=%s\n", i, wasm.ValueTypeName(t))
-		}
-
-		// First, add the memory offset to load onto the stack.
-		offset := 8 + int(i*8)
-		code = compileAddOffsetToSP(code, 0, offset)
-
-		// Next, load stack parameter $i from memory at that offset.
-		switch t {
-		case api.ValueTypeI32:
-			if debugMode {
-				fmt.Println(wasm.OpcodeI32LoadName)
-			}
-			code = append(code, wasm.OpcodeI32Load, 0x2, 0x0) // alignment=2 (natural alignment) staticOffset=0
-		case api.ValueTypeI64:
-			if debugMode {
-				fmt.Println(wasm.OpcodeI64LoadName)
-			}
-			code = append(code, wasm.OpcodeI64Load, 0x3, 0x0) // alignment=3 (natural alignment) staticOffset=0
-		default:
-			panic(errors.New("TODO: param " + api.ValueTypeName(t)))
-		}
-	}
-
-	// Now that all parameters are on the stack, call the function
-	callFuncPos := len(code) + 1
-	if debugMode {
-		fmt.Printf("\n%s 0\n", wasm.OpcodeCallName)
-	}
-
-	// Call index zero is a placeholder as it is replaced later.
-	code = append(code, wasm.OpcodeCall, 0)
-
-	// The stack may now have results. Iterate backwards.
-	i := len(results) - 1
-	if expectSP {
-		if debugMode {
-			fmt.Printf("%s %d ;; refresh SP\n", wasm.OpcodeLocalSetName, resultSPIndex)
-		}
-		code = append(code, wasm.OpcodeLocalSet, resultSPIndex)
-		i--
-	}
-	for ; i >= 0; i-- {
-		// pop current result from stack
-		t := results[i]
-		if debugMode {
-			fmt.Printf("\n;; result[%d]=%s\n", i, wasm.ValueTypeName(t))
-		}
-
-		var typeIndex byte
-		switch t {
-		case api.ValueTypeI32:
-			typeIndex = 1
-		case api.ValueTypeI64:
-			typeIndex = 2
-		default:
-			panic(errors.New("TODO: result " + api.ValueTypeName(t)))
-		}
-
-		if debugMode {
-			fmt.Printf("%s %d ;; next result\n", wasm.OpcodeLocalSetName, typeIndex)
-		}
-		code = append(code, wasm.OpcodeLocalSet, typeIndex)
-
-		offset := resultSpOffset + i*8
-		code = compileAddOffsetToSP(code, resultSPIndex, offset)
-
-		if debugMode {
-			fmt.Printf("%s %d ;; store next result\n", wasm.OpcodeLocalGetName, typeIndex)
-		}
-		code = append(code, wasm.OpcodeLocalGet, typeIndex)
-
-		switch t {
-		case api.ValueTypeI32:
-			if debugMode {
-				fmt.Println(wasm.OpcodeI32StoreName)
-			}
-			code = append(code, wasm.OpcodeI32Store, 0x2, 0x0) // alignment=2 (natural alignment) staticOffset=0
-		case api.ValueTypeI64:
-			if debugMode {
-				fmt.Println(wasm.OpcodeI64StoreName)
-			}
-			code = append(code, wasm.OpcodeI64Store, 0x3, 0x0) // alignment=3 (natural alignment) staticOffset=0
-		default:
-			panic(errors.New("TODO: result " + api.ValueTypeName(t)))
-		}
-
-	}
-	if debugMode {
-		fmt.Println("\n)")
-	}
-	code = append(code, wasm.OpcodeEnd)
-	return &wasm.ProxyFunc{
-		Proxy: &wasm.HostFunc{
-			ExportNames: proxied.ExportNames,
-			Name:        proxied.Name + ".proxy",
-			ParamTypes:  []api.ValueType{api.ValueTypeI32},
-			ParamNames:  []string{"sp"},
-			Code:        &wasm.Code{IsHostFunction: true, LocalTypes: localTypes, Body: code},
-		},
-		Proxied: &wasm.HostFunc{
-			Name:        proxied.Name,
-			ParamTypes:  proxied.ParamTypes,
-			ParamNames:  proxied.ParamNames,
-			ResultTypes: proxied.ResultTypes,
-			ResultNames: proxied.ResultNames,
-			Code:        proxied.Code,
-		},
-		CallBodyPos: callFuncPos,
-	}, nil
-}
-
-func compileAddOffsetToSP(code []byte, spLocalIndex byte, offset int) []byte {
-	if debugMode {
-		fmt.Printf("%s %d ;; SP\n", wasm.OpcodeLocalGetName, spLocalIndex)
-		fmt.Printf("%s %d ;; offset\n", wasm.OpcodeI32ConstName, offset)
-		fmt.Printf("%s\n", wasm.OpcodeI32AddName)
-	}
-	code = append(code, wasm.OpcodeLocalGet, spLocalIndex)
-	// See /RATIONALE.md we can't tell the signed interpretation of a constant, so default to signed.
-	code = append(code, wasm.OpcodeI32Const)
-	code = append(code, leb128.EncodeInt32(int32(offset))...)
-	code = append(code, wasm.OpcodeI32Add)
-	return code
-}
diff --git a/internal/gojs/syscall.go b/internal/gojs/syscall.go
index b6b05b2f..2aaa4c7f 100644
--- a/internal/gojs/syscall.go
+++ b/internal/gojs/syscall.go
@@ -2,6 +2,7 @@ package gojs
 
 import (
 	"context"
+	"encoding/binary"
 	"errors"
 	"fmt"
 	"io/fs"
@@ -9,7 +10,6 @@ import (
 	"syscall"
 
 	"github.com/tetratelabs/wazero/api"
-	"github.com/tetratelabs/wazero/internal/gojs/spfunc"
 	"github.com/tetratelabs/wazero/internal/wasm"
 	"github.com/tetratelabs/wazero/sys"
 )
@@ -33,23 +33,19 @@ const (
 	copyBytesToJSName      = "syscall/js.copyBytesToJS"
 )
 
+var le = binary.LittleEndian
+
 // FinalizeRef implements js.finalizeRef, which is used as a
 // runtime.SetFinalizer on the given reference.
 //
 // See https://github.com/golang/go/blob/go1.19/src/syscall/js/js.go#L61
-var FinalizeRef = spfunc.MustCallFromSP(false, &wasm.HostFunc{
-	ExportNames: []string{finalizeRefName},
-	Name:        finalizeRefName,
-	ParamTypes:  []api.ValueType{i32},
-	ParamNames:  []string{"r"},
-	Code: &wasm.Code{
-		IsHostFunction: true,
-		GoFunc:         api.GoFunc(finalizeRef),
-	},
-})
-
-func finalizeRef(ctx context.Context, stack []uint64) {
-	id := uint32(stack[0]) // 32-bits of the ref are the ID
+var FinalizeRef = newSPFunc(finalizeRefName, finalizeRef)
+
+func finalizeRef(ctx context.Context, mod api.Module, sp []uint64) {
+	mem := mod.Memory()
+
+	ref := mustReadUint64Le(mem, "ref", uint32(sp[0]+8))
+	id := uint32(ref) // 32-bits of the ref are the ID
 
 	getState(ctx).values.decrement(id)
 }
@@ -59,25 +55,23 @@ func finalizeRef(ctx context.Context, stack []uint64) {
 //
 // See https://github.com/golang/go/blob/go1.19/src/syscall/js/js.go#L212
 // and https://github.com/golang/go/blob/go1.19/misc/wasm/wasm_exec.js#L305-L308
-var StringVal = spfunc.MustCallFromSP(false, &wasm.HostFunc{
-	ExportNames: []string{stringValName},
-	Name:        stringValName,
-	ParamTypes:  []api.ValueType{i32, i32},
-	ParamNames:  []string{"xAddr", "xLen"},
-	ResultTypes: []api.ValueType{i64},
-	ResultNames: []string{"ref"},
-	Code: &wasm.Code{
-		IsHostFunction: true,
-		GoFunc:         api.GoModuleFunc(stringVal),
-	},
-})
-
-func stringVal(ctx context.Context, mod api.Module, stack []uint64) {
-	xAddr, xLen := uint32(stack[0]), uint32(stack[1])
-
-	x := string(mustRead(mod.Memory(), "x", xAddr, xLen))
-
-	stack[0] = storeRef(ctx, x)
+var StringVal = newSPFunc(stringValName, stringVal)
+
+func stringVal(ctx context.Context, mod api.Module, sp []uint64) {
+	mem := mod.Memory()
+
+	// Read (param + result count) * 8 memory starting at SP+8
+	stack := mustRead(mem, "sp", uint32(sp[0]+8), 24)
+
+	xAddr := le.Uint32(stack)
+	xLen := le.Uint32(stack[8:])
+
+	x := string(mustRead(mem, "x", xAddr, xLen))
+
+	ref := storeRef(ctx, x)
+
+	// Write the results to memory at positions after the parameters.
+	le.PutUint64(stack[16:], ref)
 }
 
 // ValueGet implements js.valueGet, which is used to load a js.Value property
@@ -86,25 +80,19 @@ func stringVal(ctx context.Context, mod api.Module, stack []uint64) {
 //
 // See https://github.com/golang/go/blob/go1.19/src/syscall/js/js.go#L295
 // and https://github.com/golang/go/blob/go1.19/misc/wasm/wasm_exec.js#L311-L316
-var ValueGet = spfunc.MustCallFromSP(false, &wasm.HostFunc{
-	ExportNames: []string{valueGetName},
-	Name:        valueGetName,
-	ParamTypes:  []api.ValueType{i64, i32, i32},
-	ParamNames:  []string{"v", "pAddr", "pLen"},
-	ResultTypes: []api.ValueType{i64},
-	ResultNames: []string{"ref"},
-	Code: &wasm.Code{
-		IsHostFunction: true,
-		GoFunc:         api.GoModuleFunc(valueGet),
-	},
-})
-
-func valueGet(ctx context.Context, mod api.Module, stack []uint64) {
-	vRef := stack[0]
-	pAddr := uint32(stack[1])
-	pLen := uint32(stack[2])
-
-	p := string(mustRead(mod.Memory(), "p", pAddr, pLen))
+var ValueGet = newSPFunc(valueGetName, valueGet)
+
+func valueGet(ctx context.Context, mod api.Module, sp []uint64) {
+	mem := mod.Memory()
+
+	// Read (param + result count) * 8 memory starting at SP+8
+	stack := mustRead(mem, "sp", uint32(sp[0]+8), 32)
+
+	vRef := le.Uint64(stack)
+	pAddr := le.Uint32(stack[8:])
+	pLen := le.Uint32(stack[16:])
+
+	p := string(mustRead(mem, "p", pAddr, pLen))
 	v := loadValue(ctx, ref(vRef))
 
 	var result interface{}
@@ -123,7 +111,10 @@ func valueGet(ctx context.Context, mod api.Module, stack []uint64) {
 		panic(fmt.Errorf("TODO: valueGet(v=%v, p=%s)", v, p))
 	}
 
-	stack[0] = storeRef(ctx, result)
+	ref := storeRef(ctx, result)
+
+	// Write the results to memory at positions after the parameters.
+	le.PutUint64(stack[24:], ref)
 }
 
 // ValueSet implements js.valueSet, which is used to store a js.Value property
@@ -132,25 +123,21 @@ func valueGet(ctx context.Context, mod api.Module, stack []uint64) {
 //
 // See https://github.com/golang/go/blob/go1.19/src/syscall/js/js.go#L309
 // and https://github.com/golang/go/blob/go1.19/misc/wasm/wasm_exec.js#L318-L322
-var ValueSet = spfunc.MustCallFromSP(false, &wasm.HostFunc{
-	ExportNames: []string{valueSetName},
-	Name:        valueSetName,
-	ParamTypes:  []api.ValueType{i64, i32, i32, i64},
-	ParamNames:  []string{"v", "pAddr", "pLen", "x"},
-	Code: &wasm.Code{
-		IsHostFunction: true,
-		GoFunc:         api.GoModuleFunc(valueSet),
-	},
-})
-
-func valueSet(ctx context.Context, mod api.Module, stack []uint64) {
-	vRef := stack[0]
-	pAddr := uint32(stack[1])
-	pLen := uint32(stack[2])
-	xRef := stack[3]
+var ValueSet = newSPFunc(valueSetName, valueSet)
+
+func valueSet(ctx context.Context, mod api.Module, sp []uint64) {
+	mem := mod.Memory()
+
+	// Read (param + result count) * 8 memory starting at SP+8
+	stack := mustRead(mem, "sp", uint32(sp[0]+8), 32)
+
+	vRef := le.Uint64(stack)
+	pAddr := le.Uint32(stack[8:])
+	pLen := le.Uint32(stack[16:])
+	xRef := le.Uint64(stack[24:])
 
 	v := loadValue(ctx, ref(vRef))
-	p := string(mustRead(mod.Memory(), "p", pAddr, pLen))
+	p := string(mustRead(mem, "p", pAddr, pLen))
 	x := loadValue(ctx, ref(xRef))
 	if v == getState(ctx) {
 		switch p {
@@ -184,27 +171,24 @@ var ValueDelete = stubFunction(valueDeleteName)
 //
 // See https://github.com/golang/go/blob/go1.19/src/syscall/js/js.go#L334
 // and https://github.com/golang/go/blob/go1.19/misc/wasm/wasm_exec.js#L331-L334
-var ValueIndex = spfunc.MustCallFromSP(false, &wasm.HostFunc{
-	ExportNames: []string{valueIndexName},
-	Name:        valueIndexName,
-	ParamTypes:  []api.ValueType{i64, i32},
-	ParamNames:  []string{"v", "i"},
-	ResultTypes: []api.ValueType{i64},
-	ResultNames: []string{"ref"},
-	Code: &wasm.Code{
-		IsHostFunction: true,
-		GoFunc:         api.GoFunc(valueIndex),
-	},
-})
-
-func valueIndex(ctx context.Context, stack []uint64) {
-	vRef := stack[0]
-	i := uint32(stack[1])
+var ValueIndex = newSPFunc(valueIndexName, valueIndex)
+
+func valueIndex(ctx context.Context, mod api.Module, sp []uint64) {
+	mem := mod.Memory()
+
+	// Read (param + result count) * 8 memory starting at SP+8
+	stack := mustRead(mem, "sp", uint32(sp[0]+8), 24)
+
+	vRef := le.Uint64(stack)
+	i := le.Uint32(stack[8:])
 
 	v := loadValue(ctx, ref(vRef))
 	result := v.(*objectArray).slice[i]
 
-	stack[0] = storeRef(ctx, result)
+	ref := storeRef(ctx, result)
+
+	// Write the results to memory at positions after the parameters.
+	le.PutUint64(stack[16:], ref)
 }
 
 // ValueSetIndex is stubbed as it is only used for js.ValueOf when the input is
@@ -218,33 +202,27 @@ var ValueSetIndex = stubFunction(valueSetIndexName)
 //
 // See https://github.com/golang/go/blob/go1.19/src/syscall/js/js.go#L394
 // and https://github.com/golang/go/blob/go1.19/misc/wasm/wasm_exec.js#L343-L358
-var ValueCall = spfunc.MustCallFromSP(true, &wasm.HostFunc{
-	ExportNames: []string{valueCallName},
-	Name:        valueCallName,
-	ParamTypes:  []api.ValueType{i64, i32, i32, i32, i32},
-	ParamNames:  []string{"v", "mAddr", "mLen", "argsArray", "argsLen"},
-	ResultTypes: []api.ValueType{i64, i32, i32},
-	ResultNames: []string{"ref", "ok", "sp"},
-	Code: &wasm.Code{
-		IsHostFunction: true,
-		GoFunc:         api.GoModuleFunc(valueCall),
-	},
-})
-
-func valueCall(ctx context.Context, mod api.Module, stack []uint64) {
-	vRef := stack[0]
-	mAddr := uint32(stack[1])
-	mLen := uint32(stack[2])
-	argsArray := uint32(stack[3])
-	argsLen := uint32(stack[4])
+var ValueCall = newSPFunc(valueCallName, valueCall)
+
+func valueCall(ctx context.Context, mod api.Module, sp []uint64) {
+	mem := mod.Memory()
+
+	// Read param count * 8 memory starting at SP+8
+	params := mustRead(mem, "sp", uint32(sp[0]+8), 40)
+
+	vRef := le.Uint64(params)
+	mAddr := le.Uint32(params[8:])
+	mLen := le.Uint32(params[16:])
+	argsArray := le.Uint32(params[24:])
+	argsLen := le.Uint32(params[32:])
 
 	this := ref(vRef)
 	v := loadValue(ctx, this)
-	m := string(mustRead(mod.Memory(), "m", mAddr, mLen))
+	m := string(mustRead(mem, "m", mAddr, mLen))
 	args := loadArgs(ctx, mod, argsArray, argsLen)
 
 	var xRef uint64
-	var ok, sp uint32
+	var ok uint32
 	if c, isCall := v.(jsCall); !isCall {
 		panic(fmt.Errorf("TODO: valueCall(v=%v, m=%v, args=%v)", v, m, args))
 	} else if result, err := c.call(ctx, mod, this, m, args...); err != nil {
@@ -255,8 +233,12 @@ func valueCall(ctx context.Context, mod api.Module, stack []uint64) {
 		ok = 1
 	}
 
-	sp = refreshSP(mod)
-	stack[0], stack[1], stack[2] = xRef, uint64(ok), uint64(sp)
+	// On refresh, start to write results 16 bytes after the last parameter.
+	results := mustRead(mem, "sp", refreshSP(mod)+56, 16)
+
+	// Write the results back to the stack
+	le.PutUint64(results, xRef)
+	le.PutUint32(results[8:], ok)
 }
 
 // ValueInvoke is stubbed as it isn't used in Go's main source tree.
@@ -269,30 +251,24 @@ var ValueInvoke = stubFunction(valueInvokeName)
 //
 // See https://github.com/golang/go/blob/go1.19/src/syscall/js/js.go#L432
 // and https://github.com/golang/go/blob/go1.19/misc/wasm/wasm_exec.js#L380-L391
-var ValueNew = spfunc.MustCallFromSP(true, &wasm.HostFunc{
-	ExportNames: []string{valueNewName},
-	Name:        valueNewName,
-	ParamTypes:  []api.ValueType{i64, i32, i32},
-	ParamNames:  []string{"v", "argsArray", "argsLen"},
-	ResultTypes: []api.ValueType{i64, i32, i32},
-	ResultNames: []string{"ref", "ok", "sp"},
-	Code: &wasm.Code{
-		IsHostFunction: true,
-		GoFunc:         api.GoModuleFunc(valueNew),
-	},
-})
-
-func valueNew(ctx context.Context, mod api.Module, stack []uint64) {
-	vRef := stack[0]
-	argsArray := uint32(stack[1])
-	argsLen := uint32(stack[2])
+var ValueNew = newSPFunc(valueNewName, valueNew)
+
+func valueNew(ctx context.Context, mod api.Module, sp []uint64) {
+	mem := mod.Memory()
+
+	// Read param count * 8 memory starting at SP+8
+	params := mustRead(mem, "sp", uint32(sp[0]+8), 24)
+
+	vRef := le.Uint64(params)
+	argsArray := le.Uint32(params[8:])
+	argsLen := le.Uint32(params[16:])
 
 	args := loadArgs(ctx, mod, argsArray, argsLen)
 	ref := ref(vRef)
 	v := loadValue(ctx, ref)
 
 	var xRef uint64
-	var ok, sp uint32
+	var ok uint32
 	switch ref {
 	case refArrayConstructor:
 		result := &objectArray{}
@@ -328,8 +304,12 @@ func valueNew(ctx context.Context, mod api.Module, stack []uint64) {
 		panic(fmt.Errorf("TODO: valueNew(v=%v, args=%v)", v, args))
 	}
 
-	sp = refreshSP(mod)
-	stack[0], stack[1], stack[2] = xRef, uint64(ok), uint64(sp)
+	// On refresh, start to write results 16 bytes after the last parameter.
+	results := mustRead(mem, "sp", refreshSP(mod)+40, 16)
+
+	// Write the results back to the stack
+	le.PutUint64(results, xRef)
+	le.PutUint32(results[8:], ok)
 }
 
 // ValueLength implements js.valueLength, which is used to load the length
@@ -337,25 +317,21 @@ func valueNew(ctx context.Context, mod api.Module, stack []uint64) {
 //
 // See https://github.com/golang/go/blob/go1.19/src/syscall/js/js.go#L372
 // and https://github.com/golang/go/blob/go1.19/misc/wasm/wasm_exec.js#L396-L397
-var ValueLength = spfunc.MustCallFromSP(false, &wasm.HostFunc{
-	ExportNames: []string{valueLengthName},
-	Name:        valueLengthName,
-	ParamTypes:  []api.ValueType{i64},
-	ParamNames:  []string{"v"},
-	ResultTypes: []api.ValueType{i32},
-	Code: &wasm.Code{
-		IsHostFunction: true,
-		GoFunc:         api.GoFunc(valueLength),
-	},
-})
-
-func valueLength(ctx context.Context, stack []uint64) {
-	vRef := stack[0]
+var ValueLength = newSPFunc(valueLengthName, valueLength)
+
+func valueLength(ctx context.Context, mod api.Module, sp []uint64) {
+	mem := mod.Memory()
+
+	// Read (param + result count) * 8 memory starting at SP+8
+	stack := mustRead(mem, "sp", uint32(sp[0]+8), 16)
+
+	vRef := le.Uint64(stack)
 
 	v := loadValue(ctx, ref(vRef))
 	l := uint32(len(v.(*objectArray).slice))
 
-	stack[0] = uint64(l)
+	// Write the results to memory at positions after the parameters.
+	le.PutUint32(stack[8:], l)
 }
 
 // ValuePrepareString implements js.valuePrepareString, which is used to load
@@ -365,21 +341,15 @@ func valueLength(ctx context.Context, stack []uint64) {
 //
 // See https://github.com/golang/go/blob/go1.19/src/syscall/js/js.go#L531
 // and https://github.com/golang/go/blob/go1.19/misc/wasm/wasm_exec.js#L402-L405
-var ValuePrepareString = spfunc.MustCallFromSP(false, &wasm.HostFunc{
-	ExportNames: []string{valuePrepareStringName},
-	Name:        valuePrepareStringName,
-	ParamTypes:  []api.ValueType{i64},
-	ParamNames:  []string{"v"},
-	ResultTypes: []api.ValueType{i64, i32},
-	ResultNames: []string{"ref", "len"},
-	Code: &wasm.Code{
-		IsHostFunction: true,
-		GoFunc:         api.GoFunc(valuePrepareString),
-	},
-})
-
-func valuePrepareString(ctx context.Context, stack []uint64) {
-	vRef := stack[0]
+var ValuePrepareString = newSPFunc(valuePrepareStringName, valuePrepareString)
+
+func valuePrepareString(ctx context.Context, mod api.Module, sp []uint64) {
+	mem := mod.Memory()
+
+	// Read (param + result count) * 8 memory starting at SP+8
+	stack := mustRead(mem, "sp", uint32(sp[0]+8), 24)
+
+	vRef := le.Uint64(stack)
 
 	v := loadValue(ctx, ref(vRef))
 	s := valueString(v)
@@ -387,7 +357,9 @@ func valuePrepareString(ctx context.Context, stack []uint64) {
 	sRef := storeRef(ctx, s)
 	sLen := uint32(len(s))
 
-	stack[0], stack[1] = sRef, uint64(sLen)
+	// Write the results to memory at positions after the parameters.
+	le.PutUint64(stack[8:], sRef)
+	le.PutUint32(stack[16:], sLen)
 }
 
 // ValueLoadString implements js.valueLoadString, which is used copy a string
@@ -396,25 +368,21 @@ func valuePrepareString(ctx context.Context, stack []uint64) {
 // See https://github.com/golang/go/blob/go1.19/src/syscall/js/js.go#L533
 //
 //	https://github.com/golang/go/blob/go1.19/misc/wasm/wasm_exec.js#L410-L412
-var ValueLoadString = spfunc.MustCallFromSP(false, &wasm.HostFunc{
-	ExportNames: []string{valueLoadStringName},
-	Name:        valueLoadStringName,
-	ParamTypes:  []api.ValueType{i64, i32, i32},
-	ParamNames:  []string{"v", "bAddr", "bLen"},
-	Code: &wasm.Code{
-		IsHostFunction: true,
-		GoFunc:         api.GoModuleFunc(valueLoadString),
-	},
-})
-
-func valueLoadString(ctx context.Context, mod api.Module, stack []uint64) {
-	vRef := stack[0]
-	bAddr := uint32(stack[1])
-	bLen := uint32(stack[2])
+var ValueLoadString = newSPFunc(valueLoadStringName, valueLoadString)
+
+func valueLoadString(ctx context.Context, mod api.Module, sp []uint64) {
+	mem := mod.Memory()
+
+	// Read (param + result count) * 8 memory starting at SP+8
+	stack := mustRead(mem, "sp", uint32(sp[0]+8), 24)
+
+	vRef := le.Uint64(stack)
+	bAddr := le.Uint32(stack[8:])
+	bLen := le.Uint32(stack[16:])
 
 	v := loadValue(ctx, ref(vRef))
 	s := valueString(v)
-	b := mustRead(mod.Memory(), "b", bAddr, bLen)
+	b := mustRead(mem, "b", bAddr, bLen)
 	copy(b, s)
 }
 
@@ -433,26 +401,20 @@ var ValueInstanceOf = stubFunction(valueInstanceOfName)
 //
 // See https://github.com/golang/go/blob/go1.19/src/syscall/js/js.go#L569
 // and https://github.com/golang/go/blob/go1.19/misc/wasm/wasm_exec.js#L424-L433
-var CopyBytesToGo = spfunc.MustCallFromSP(false, &wasm.HostFunc{
-	ExportNames: []string{copyBytesToGoName},
-	Name:        copyBytesToGoName,
-	ParamTypes:  []api.ValueType{i32, i32, i32, i64},
-	ParamNames:  []string{"dstAddr", "dstLen", "_", "src"},
-	ResultTypes: []api.ValueType{i32, i32},
-	ResultNames: []string{"n", "ok"},
-	Code: &wasm.Code{
-		IsHostFunction: true,
-		GoFunc:         api.GoModuleFunc(copyBytesToGo),
-	},
-})
-
-func copyBytesToGo(ctx context.Context, mod api.Module, stack []uint64) {
-	dstAddr := uint32(stack[0])
-	dstLen := uint32(stack[1])
-	_ /* unknown */ = uint32(stack[2])
-	srcRef := stack[3]
-
-	dst := mustRead(mod.Memory(), "dst", dstAddr, dstLen) // nolint
+var CopyBytesToGo = newSPFunc(copyBytesToGoName, copyBytesToGo)
+
+func copyBytesToGo(ctx context.Context, mod api.Module, sp []uint64) {
+	mem := mod.Memory()
+
+	// Read (param + result count) * 8 memory starting at SP+8
+	stack := mustRead(mem, "sp", uint32(sp[0]+8), 48)
+
+	dstAddr := le.Uint32(stack)
+	dstLen := le.Uint32(stack[8:])
+	/* unknown := le.Uint32(stack[16:]) */
+	srcRef := le.Uint64(stack[24:])
+
+	dst := mustRead(mem, "dst", dstAddr, dstLen)
 	v := loadValue(ctx, ref(srcRef))
 
 	var n, ok uint32
@@ -461,7 +423,9 @@ func copyBytesToGo(ctx context.Context, mod api.Module, stack []uint64) {
 		ok = 1
 	}
 
-	stack[0], stack[1] = uint64(n), uint64(ok)
+	// Write the results to memory at positions after the parameters.
+	le.PutUint32(stack[32:], n)
+	le.PutUint32(stack[40:], ok)
 }
 
 // CopyBytesToJS copies linear memory to a JavaScript managed byte array.
@@ -474,26 +438,20 @@ func copyBytesToGo(ctx context.Context, mod api.Module, stack []uint64) {
 //
 // See https://github.com/golang/go/blob/go1.19/src/syscall/js/js.go#L583
 // and https://github.com/golang/go/blob/go1.19/misc/wasm/wasm_exec.js#L438-L448
-var CopyBytesToJS = spfunc.MustCallFromSP(false, &wasm.HostFunc{
-	ExportNames: []string{copyBytesToJSName},
-	Name:        copyBytesToJSName,
-	ParamTypes:  []api.ValueType{i64, i32, i32, i32},
-	ParamNames:  []string{"dst", "srcAddr", "srcLen", "_"},
-	ResultTypes: []api.ValueType{i32, i32},
-	ResultNames: []string{"n", "ok"},
-	Code: &wasm.Code{
-		IsHostFunction: true,
-		GoFunc:         api.GoModuleFunc(copyBytesToJS),
-	},
-})
-
-func copyBytesToJS(ctx context.Context, mod api.Module, stack []uint64) {
-	dstRef := stack[0]
-	srcAddr := uint32(stack[1])
-	srcLen := uint32(stack[2])
-	_ /* unknown */ = uint32(stack[3])
-
-	src := mustRead(mod.Memory(), "src", srcAddr, srcLen) // nolint
+var CopyBytesToJS = newSPFunc(copyBytesToJSName, copyBytesToJS)
+
+func copyBytesToJS(ctx context.Context, mod api.Module, sp []uint64) {
+	mem := mod.Memory()
+
+	// Read (param + result count) * 8 memory starting at SP+8
+	stack := mustRead(mem, "sp", uint32(sp[0]+8), 48)
+
+	dstRef := le.Uint64(stack)
+	srcAddr := le.Uint32(stack[8:])
+	srcLen := le.Uint32(stack[16:])
+	/* unknown := le.Uint32(stack[24:]) */
+
+	src := mustRead(mem, "src", srcAddr, srcLen) // nolint
 	v := loadValue(ctx, ref(dstRef))
 
 	var n, ok uint32
@@ -504,7 +462,9 @@ func copyBytesToJS(ctx context.Context, mod api.Module, stack []uint64) {
 		ok = 1
 	}
 
-	stack[0], stack[1] = uint64(n), uint64(ok)
+	// Write the results to memory at positions after the parameters.
+	le.PutUint32(stack[32:], n)
+	le.PutUint32(stack[40:], ok)
 }
 
 // refreshSP refreshes the stack pointer, which is needed prior to storeValue
@@ -603,3 +563,13 @@ func (f funcWrapper) invoke(ctx context.Context, mod api.Module, args ...interfa
 
 	return e.result, nil
 }
+
+func newSPFunc(name string, goFunc api.GoModuleFunc) *wasm.HostFunc {
+	return &wasm.HostFunc{
+		ExportNames: []string{name},
+		Name:        name,
+		ParamTypes:  []api.ValueType{api.ValueTypeI32},
+		ParamNames:  []string{"sp"},
+		Code:        &wasm.Code{IsHostFunction: true, GoFunc: goFunc},
+	}
+}
diff --git a/internal/gojs/util.go b/internal/gojs/util.go
index 28723503..42f3f977 100644
--- a/internal/gojs/util.go
+++ b/internal/gojs/util.go
@@ -33,6 +33,16 @@ func mustRead(mem api.Memory, fieldName string, offset, byteCount uint32) []byte
 	return buf
 }
 
+// mustReadUint32Le is like api.Memory except that it panics if the offset
+// is out of range.
+func mustReadUint32Le(mem api.Memory, fieldName string, offset uint32) uint32 {
+	result, ok := mem.ReadUint32Le(offset)
+	if !ok {
+		panic(fmt.Errorf("out of memory reading %s", fieldName))
+	}
+	return result
+}
+
 // mustReadUint64Le is like api.Memory except that it panics if the offset
 // is out of range.
 func mustReadUint64Le(mem api.Memory, fieldName string, offset uint32) uint64 {
diff --git a/internal/leb128/leb128.go b/internal/leb128/leb128.go
index 0e35585d..52eccfe4 100644
--- a/internal/leb128/leb128.go
+++ b/internal/leb128/leb128.go
@@ -93,37 +93,27 @@ func EncodeUint64(value uint64) (buf []byte) {
 	}
 }
 
-type nextByte interface {
-	next(i int) (byte, error)
-}
-
-type byteSliceNext []byte
-
-func (n byteSliceNext) next(i int) (byte, error) {
-	if i >= len(n) {
-		return 0, io.EOF
-	}
-	return n[i], nil
-}
-
-type byteReaderNext struct{ io.ByteReader }
-
-func (n byteReaderNext) next(_ int) (byte, error) { return n.ReadByte() }
+type nextByte func(i int) (byte, error)
 
 func DecodeUint32(r io.ByteReader) (ret uint32, bytesRead uint64, err error) {
-	return decodeUint32(byteReaderNext{r})
+	return decodeUint32(func(_ int) (byte, error) { return r.ReadByte() })
 }
 
 func LoadUint32(buf []byte) (ret uint32, bytesRead uint64, err error) {
-	return decodeUint32(byteSliceNext(buf))
+	return decodeUint32(func(i int) (byte, error) {
+		if i >= len(buf) {
+			return 0, io.EOF
+		}
+		return buf[i], nil
+	})
 }
 
-func decodeUint32(buf nextByte) (ret uint32, bytesRead uint64, err error) {
+func decodeUint32(next nextByte) (ret uint32, bytesRead uint64, err error) {
 	// Derived from https://github.com/golang/go/blob/aafad20b617ee63d58fcd4f6e0d98fe27760678c/src/encoding/binary/varint.go
 	// with the modification on the overflow handling tailored for 32-bits.
 	var s uint32
 	for i := 0; i < maxVarintLen32; i++ {
-		b, err := buf.next(i)
+		b, err := next(i)
 		if err != nil {
 			return 0, 0, err
 		}
@@ -167,18 +157,23 @@ func LoadUint64(buf []byte) (ret uint64, bytesRead uint64, err error) {
 }
 
 func DecodeInt32(r io.ByteReader) (ret int32, bytesRead uint64, err error) {
-	return decodeInt32(byteReaderNext{r})
+	return decodeInt32(func(_ int) (byte, error) { return r.ReadByte() })
 }
 
 func LoadInt32(buf []byte) (ret int32, bytesRead uint64, err error) {
-	return decodeInt32(byteSliceNext(buf))
+	return decodeInt32(func(i int) (byte, error) {
+		if i >= len(buf) {
+			return 0, io.EOF
+		}
+		return buf[i], nil
+	})
 }
 
-func decodeInt32(buf nextByte) (ret int32, bytesRead uint64, err error) {
+func decodeInt32(next nextByte) (ret int32, bytesRead uint64, err error) {
 	var shift int
 	var b byte
 	for {
-		b, err = buf.next(int(bytesRead))
+		b, err = next(int(bytesRead))
 		if err != nil {
 			return 0, 0, fmt.Errorf("readByte failed: %w", err)
 		}
@@ -248,18 +243,23 @@ func DecodeInt33AsInt64(r io.ByteReader) (ret int64, bytesRead uint64, err error
 }
 
 func DecodeInt64(r io.ByteReader) (ret int64, bytesRead uint64, err error) {
-	return decodeInt64(byteReaderNext{r})
+	return decodeInt64(func(_ int) (byte, error) { return r.ReadByte() })
 }
 
 func LoadInt64(buf []byte) (ret int64, bytesRead uint64, err error) {
-	return decodeInt64(byteSliceNext(buf))
+	return decodeInt64(func(i int) (byte, error) {
+		if i >= len(buf) {
+			return 0, io.EOF
+		}
+		return buf[i], nil
+	})
 }
 
-func decodeInt64(buf nextByte) (ret int64, bytesRead uint64, err error) {
+func decodeInt64(next nextByte) (ret int64, bytesRead uint64, err error) {
 	var shift int
 	var b byte
 	for {
-		b, err = buf.next(int(bytesRead))
+		b, err = next(int(bytesRead))
 		if err != nil {
 			return 0, 0, fmt.Errorf("readByte failed: %w", err)
 		}
diff --git a/internal/wasm/host.go b/internal/wasm/host.go
index c610d715..64cd2cf8 100644
--- a/internal/wasm/host.go
+++ b/internal/wasm/host.go
@@ -1,7 +1,6 @@
 package wasm
 
 import (
-	"errors"
 	"fmt"
 	"sort"
 
@@ -9,28 +8,6 @@ import (
 	"github.com/tetratelabs/wazero/internal/wasmdebug"
 )
 
-type ProxyFuncExporter interface {
-	ExportProxyFunc(*ProxyFunc)
-}
-
-// ProxyFunc is a function defined both in wasm and go. This is used to
-// optimize the Go signature or obviate calls based on what can be done
-// mechanically in wasm.
-type ProxyFunc struct {
-	// Proxy must be a wasm func
-	Proxy *HostFunc
-	// Proxied should be a go func.
-	Proxied *HostFunc
-
-	// CallBodyPos is the position in Code.Body of the caller to replace the
-	// real funcIdx of the proxied.
-	CallBodyPos int
-}
-
-func (p *ProxyFunc) Name() string {
-	return p.Proxied.Name
-}
-
 type HostFuncExporter interface {
 	ExportHostFunc(*HostFunc)
 }
@@ -133,14 +110,6 @@ func NewHostModule(
 	return
 }
 
-// maxProxiedFuncIdx is the maximum index where leb128 encoding matches the bit
-// of an unsigned literal byte. Using this simplifies host function index
-// substitution.
-//
-// Note: this is 127, not 255 because when the MSB is set, leb128 encoding
-// doesn't match the literal byte.
-const maxProxiedFuncIdx = 127
-
 func addFuncs(
 	m *Module,
 	nameToGoFunc map[string]interface{},
@@ -166,29 +135,6 @@ func addFuncs(
 		if hf, ok := v.(*HostFunc); ok {
 			nameToFunc[hf.Name] = hf
 			funcNames = append(funcNames, hf.Name)
-		} else if pf, ok := v.(*ProxyFunc); ok {
-			// First, add the proxied function which also gives us the real
-			// position in the function index namespace, We will need this
-			// later. We've kept code simpler by limiting the max index to
-			// what is encodable in a single byte. This is ok as we don't have
-			// any current use cases for hundreds of proxy functions.
-			proxiedIdx := len(funcNames)
-			if proxiedIdx > maxProxiedFuncIdx {
-				return errors.New("TODO: proxied funcidx larger than one byte")
-			}
-			nameToFunc[pf.Proxied.Name] = pf.Proxied
-			funcNames = append(funcNames, pf.Proxied.Name)
-
-			// Now that we have the real index of the proxied function,
-			// substitute that for the zero placeholder in the proxy's code
-			// body. This placeholder is at index CallBodyPos in the slice.
-			proxyBody := make([]byte, len(pf.Proxy.Code.Body))
-			copy(proxyBody, pf.Proxy.Code.Body)
-			proxyBody[pf.CallBodyPos] = byte(proxiedIdx)
-			proxy := pf.Proxy.WithWasm(proxyBody)
-
-			nameToFunc[proxy.Name] = proxy
-			funcNames = append(funcNames, proxy.Name)
 		} else { // reflection
 			params, results, code, ftErr := parseGoReflectFunc(v)
 			if ftErr != nil {


Test patch:
diff --git a/imports/go/example/stars_test.go b/imports/go/example/stars_test.go
index 8499e62e..38231df3 100644
--- a/imports/go/example/stars_test.go
+++ b/imports/go/example/stars_test.go
@@ -81,6 +81,7 @@ func Benchmark_main(b *testing.B) {
 	cfg := wazero.NewModuleConfig()
 
 	b.Run("gojs.Run", func(b *testing.B) {
+		b.ReportAllocs()
 		for i := 0; i < b.N; i++ {
 			err = gojs.Run(ctx, r, compiled, cfg)
 			if exitErr, ok := err.(*sys.ExitError); ok && exitErr.ExitCode() != 0 {
diff --git a/imports/wasi_snapshot_preview1/args_test.go b/imports/wasi_snapshot_preview1/args_test.go
index a5e46ae0..2eb52789 100644
--- a/imports/wasi_snapshot_preview1/args_test.go
+++ b/imports/wasi_snapshot_preview1/args_test.go
@@ -130,10 +130,8 @@ func Test_argsSizesGet(t *testing.T) {
 	requireErrno(t, ErrnoSuccess, mod, argsSizesGetName, uint64(resultArgc), uint64(resultArgvLen))
 	require.Equal(t, `
 --> 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
+	==> wasi_snapshot_preview1.args_sizes_get(result.argc=16,result.argv_len=21)
+	<== ESUCCESS
 <-- 0
 `, "\n"+log.String())
 
@@ -160,8 +158,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
 `,
 		},
@@ -171,8 +169,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
 `,
 		},
@@ -182,8 +180,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
 `,
 		},
@@ -193,8 +191,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 ee4d9ae0..8a54559d 100644
--- a/imports/wasi_snapshot_preview1/clock_test.go
+++ b/imports/wasi_snapshot_preview1/clock_test.go
@@ -36,10 +36,8 @@ func Test_clockResGet(t *testing.T) {
 			expectedMemory: expectedMemoryMicro,
 			expectedLog: `
 --> 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
+	==> wasi_snapshot_preview1.clock_res_get(id=0,result.resolution=16)
+	<== ESUCCESS
 <-- 0
 `,
 		},
@@ -49,10 +47,8 @@ func Test_clockResGet(t *testing.T) {
 			expectedMemory: expectedMemoryNano,
 			expectedLog: `
 --> 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
+	==> wasi_snapshot_preview1.clock_res_get(id=1,result.resolution=16)
+	<== ESUCCESS
 <-- 0
 `,
 		},
@@ -93,10 +89,8 @@ func Test_clockResGet_Unsupported(t *testing.T) {
 			expectedErrno: ErrnoInval,
 			expectedLog: `
 --> 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
+	==> wasi_snapshot_preview1.clock_res_get(id=2,result.resolution=16)
+	<== EINVAL
 <-- 28
 `,
 		},
@@ -106,10 +100,8 @@ func Test_clockResGet_Unsupported(t *testing.T) {
 			expectedErrno: ErrnoInval,
 			expectedLog: `
 --> 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
+	==> wasi_snapshot_preview1.clock_res_get(id=3,result.resolution=16)
+	<== EINVAL
 <-- 28
 `,
 		},
@@ -119,10 +111,8 @@ func Test_clockResGet_Unsupported(t *testing.T) {
 			expectedErrno: ErrnoInval,
 			expectedLog: `
 --> 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
+	==> wasi_snapshot_preview1.clock_res_get(id=100,result.resolution=16)
+	<== EINVAL
 <-- 28
 `,
 		},
@@ -161,10 +151,8 @@ func Test_clockTimeGet(t *testing.T) {
 			},
 			expectedLog: `
 --> 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
+	==> wasi_snapshot_preview1.clock_time_get(id=0,precision=0,result.timestamp=16)
+	<== ESUCCESS
 <-- 0
 `,
 		},
@@ -178,10 +166,8 @@ func Test_clockTimeGet(t *testing.T) {
 			},
 			expectedLog: `
 --> 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
+	==> wasi_snapshot_preview1.clock_time_get(id=1,precision=0,result.timestamp=16)
+	<== ESUCCESS
 <-- 0
 `,
 		},
@@ -221,10 +207,8 @@ func Test_clockTimeGet_Unsupported(t *testing.T) {
 			expectedErrno: ErrnoInval,
 			expectedLog: `
 --> 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
+	==> wasi_snapshot_preview1.clock_time_get(id=2,precision=0,result.timestamp=16)
+	<== EINVAL
 <-- 28
 `,
 		},
@@ -234,10 +218,8 @@ func Test_clockTimeGet_Unsupported(t *testing.T) {
 			expectedErrno: ErrnoInval,
 			expectedLog: `
 --> 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
+	==> wasi_snapshot_preview1.clock_time_get(id=3,precision=0,result.timestamp=16)
+	<== EINVAL
 <-- 28
 `,
 		},
@@ -247,10 +229,8 @@ func Test_clockTimeGet_Unsupported(t *testing.T) {
 			expectedErrno: ErrnoInval,
 			expectedLog: `
 --> 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
+	==> wasi_snapshot_preview1.clock_time_get(id=100,precision=0,result.timestamp=16)
+	<== EINVAL
 <-- 28
 `,
 		},
@@ -285,8 +265,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
 `,
 		},
@@ -295,8 +275,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 41ef7b1c..e3c17bf9 100644
--- a/imports/wasi_snapshot_preview1/environ_test.go
+++ b/imports/wasi_snapshot_preview1/environ_test.go
@@ -134,10 +134,8 @@ func Test_environSizesGet(t *testing.T) {
 	requireErrno(t, ErrnoSuccess, mod, environSizesGetName, uint64(resultEnvironc), uint64(resultEnvironvLen))
 	require.Equal(t, `
 --> 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
+	==> wasi_snapshot_preview1.environ_sizes_get(result.environc=16,result.environv_len=21)
+	<== ESUCCESS
 <-- 0
 `, "\n"+log.String())
 
@@ -165,8 +163,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
 `,
 		},
@@ -176,8 +174,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
 `,
 		},
@@ -187,8 +185,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
 `,
 		},
@@ -198,8 +196,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 7c15bb5d..67fe33aa 100644
--- a/imports/wasi_snapshot_preview1/fs_test.go
+++ b/imports/wasi_snapshot_preview1/fs_test.go
@@ -537,10 +537,8 @@ 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)
-		==> wasi_snapshot_preview1.fdPread(fd=4,iovs=1,iovs_len=2,offset=0)
-		<== (nread=6,ESUCCESS)
-	<-- ESUCCESS
+	==> wasi_snapshot_preview1.fd_pread(fd=4,iovs=1,iovs_len=2,offset=0,result.nread=26)
+	<== ESUCCESS
 <-- 0
 `,
 		},
@@ -556,10 +554,8 @@ 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)
-		==> wasi_snapshot_preview1.fdPread(fd=4,iovs=1,iovs_len=2,offset=2)
-		<== (nread=4,ESUCCESS)
-	<-- ESUCCESS
+	==> wasi_snapshot_preview1.fd_pread(fd=4,iovs=1,iovs_len=2,offset=2,result.nread=26)
+	<== ESUCCESS
 <-- 0
 `,
 		},
@@ -605,10 +601,8 @@ func Test_fdPread_Errors(t *testing.T) {
 			expectedErrno: ErrnoBadf,
 			expectedLog: `
 --> 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
+	==> wasi_snapshot_preview1.fd_pread(fd=42,iovs=65532,iovs_len=65532,offset=0,result.nread=65532)
+	<== EBADF
 <-- 8
 `,
 		},
@@ -619,8 +613,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
 `,
 		},
@@ -632,8 +626,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
 `,
 		},
@@ -648,10 +642,8 @@ 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)
-		==> wasi_snapshot_preview1.fdPread(fd=4,iovs=65532,iovs_len=65532,offset=0)
-		<== (nread=0,EFAULT)
-	<-- EFAULT
+	==> wasi_snapshot_preview1.fd_pread(fd=4,iovs=65532,iovs_len=65532,offset=0,result.nread=65531)
+	<== EFAULT
 <-- 21
 `,
 		},
@@ -667,10 +659,8 @@ 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)
-		==> wasi_snapshot_preview1.fdPread(fd=4,iovs=65528,iovs_len=65528,offset=0)
-		<== (nread=0,EFAULT)
-	<-- EFAULT
+	==> wasi_snapshot_preview1.fd_pread(fd=4,iovs=65528,iovs_len=65528,offset=0,result.nread=65527)
+	<== EFAULT
 <-- 21
 `,
 		},
@@ -687,10 +677,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=65526)
-	--> 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
+	==> wasi_snapshot_preview1.fd_pread(fd=4,iovs=65527,iovs_len=65527,offset=0,result.nread=65526)
+	<== EFAULT
 <-- 21
 `,
 		},
@@ -708,8 +696,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
 `,
 		},
@@ -751,10 +739,8 @@ func Test_fdPrestatGet(t *testing.T) {
 	requireErrno(t, ErrnoSuccess, mod, fdPrestatGetName, uint64(fd), uint64(resultPrestat))
 	require.Equal(t, `
 --> proxy.fd_prestat_get(fd=3,result.prestat=1)
-	--> wasi_snapshot_preview1.fd_prestat_get(fd=3,result.prestat=1)
-		==> wasi_snapshot_preview1.fdPrestatGet(fd=3)
-		<== (prestat=4294967296,ESUCCESS)
-	<-- ESUCCESS
+	==> wasi_snapshot_preview1.fd_prestat_get(fd=3,result.prestat=1)
+	<== ESUCCESS
 <-- 0
 `, "\n"+log.String())
 
@@ -783,10 +769,8 @@ 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)
-		==> wasi_snapshot_preview1.fdPrestatGet(fd=42)
-		<== (prestat=0,EBADF)
-	<-- EBADF
+	==> wasi_snapshot_preview1.fd_prestat_get(fd=42,result.prestat=0)
+	<== EBADF
 <-- 8
 `,
 		},
@@ -797,8 +781,8 @@ func Test_fdPrestatGet_Errors(t *testing.T) {
 			expectedErrno: ErrnoFault,
 			expectedLog: `
 --> proxy.fd_prestat_get(fd=3,result.prestat=65536)
-	--> wasi_snapshot_preview1.fd_prestat_get(fd=3,result.prestat=65536)
-	<-- EFAULT
+	==> wasi_snapshot_preview1.fd_prestat_get(fd=3,result.prestat=65536)
+	<== EFAULT
 <-- 21
 `,
 		},
@@ -971,10 +955,8 @@ 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)
-		==> wasi_snapshot_preview1.fdRead(fd=4,iovs=1,iovs_len=2)
-		<== (nread=6,ESUCCESS)
-	<-- ESUCCESS
+	==> wasi_snapshot_preview1.fd_read(fd=4,iovs=1,iovs_len=2,result.nread=26)
+	<== ESUCCESS
 <-- 0
 `, "\n"+log.String())
 
@@ -1001,10 +983,8 @@ func Test_fdRead_Errors(t *testing.T) {
 			expectedErrno: ErrnoBadf,
 			expectedLog: `
 --> 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
+	==> wasi_snapshot_preview1.fd_read(fd=42,iovs=65532,iovs_len=65532,result.nread=65532)
+	<== EBADF
 <-- 8
 `,
 		},
@@ -1016,8 +996,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
 `,
 		},
@@ -1032,10 +1012,8 @@ 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)
-		==> wasi_snapshot_preview1.fdRead(fd=4,iovs=65532,iovs_len=65532)
-		<== (nread=0,EFAULT)
-	<-- EFAULT
+	==> wasi_snapshot_preview1.fd_read(fd=4,iovs=65532,iovs_len=65532,result.nread=65531)
+	<== EFAULT
 <-- 21
 `,
 		},
@@ -1051,10 +1029,8 @@ 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)
-		==> wasi_snapshot_preview1.fdRead(fd=4,iovs=65528,iovs_len=65528)
-		<== (nread=0,EFAULT)
-	<-- EFAULT
+	==> wasi_snapshot_preview1.fd_read(fd=4,iovs=65528,iovs_len=65528,result.nread=65527)
+	<== EFAULT
 <-- 21
 `,
 		},
@@ -1071,10 +1047,8 @@ 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)
-		==> wasi_snapshot_preview1.fdRead(fd=4,iovs=65527,iovs_len=65527)
-		<== (nread=0,EFAULT)
-	<-- EFAULT
+	==> wasi_snapshot_preview1.fd_read(fd=4,iovs=65527,iovs_len=65527,result.nread=65526)
+	<== EFAULT
 <-- 21
 `,
 		},
@@ -1092,8 +1066,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
 `,
 		},
@@ -1549,10 +1523,8 @@ 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)
-		==> wasi_snapshot_preview1.fdReaddir(fd=4,buf=65536,buf_len=1000,cookie=0)
-		<== (bufused=0,EFAULT)
-	<-- EFAULT
+	==> wasi_snapshot_preview1.fd_readdir(fd=4,buf=65536,buf_len=1000,cookie=0,result.bufused=0)
+	<== EFAULT
 <-- 21
 `,
 		},
@@ -1564,10 +1536,8 @@ func Test_fdReaddir_Errors(t *testing.T) {
 			expectedErrno: ErrnoBadf,
 			expectedLog: `
 --> proxy.fd_readdir(fd=42,buf=0,buf_len=24,cookie=0,result.bufused=1000)
-	--> wasi_snapshot_preview1.fd_readdir(fd=42,buf=0,buf_len=24,cookie=0,result.bufused=1000)
-		==> wasi_snapshot_preview1.fdReaddir(fd=42,buf=0,buf_len=24,cookie=0)
-		<== (bufused=0,EBADF)
-	<-- EBADF
+	==> wasi_snapshot_preview1.fd_readdir(fd=42,buf=0,buf_len=24,cookie=0,result.bufused=1000)
+	<== EBADF
 <-- 8
 `,
 		},
@@ -1579,10 +1549,8 @@ func Test_fdReaddir_Errors(t *testing.T) {
 			expectedErrno: ErrnoBadf,
 			expectedLog: `
 --> proxy.fd_readdir(fd=5,buf=0,buf_len=24,cookie=0,result.bufused=1000)
-	--> wasi_snapshot_preview1.fd_readdir(fd=5,buf=0,buf_len=24,cookie=0,result.bufused=1000)
-		==> wasi_snapshot_preview1.fdReaddir(fd=5,buf=0,buf_len=24,cookie=0)
-		<== (bufused=0,EBADF)
-	<-- EBADF
+	==> wasi_snapshot_preview1.fd_readdir(fd=5,buf=0,buf_len=24,cookie=0,result.bufused=1000)
+	<== EBADF
 <-- 8
 `,
 		},
@@ -1594,10 +1562,8 @@ 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)
-		==> wasi_snapshot_preview1.fdReaddir(fd=4,buf=65536,buf_len=1000,cookie=0)
-		<== (bufused=0,EFAULT)
-	<-- EFAULT
+	==> wasi_snapshot_preview1.fd_readdir(fd=4,buf=65536,buf_len=1000,cookie=0,result.bufused=0)
+	<== EFAULT
 <-- 21
 `,
 		},
@@ -1609,10 +1575,8 @@ 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)
-		==> wasi_snapshot_preview1.fdReaddir(fd=4,buf=65535,buf_len=1000,cookie=0)
-		<== (bufused=0,EFAULT)
-	<-- EFAULT
+	==> wasi_snapshot_preview1.fd_readdir(fd=4,buf=65535,buf_len=1000,cookie=0,result.bufused=0)
+	<== EFAULT
 <-- 21
 `,
 		},
@@ -1624,10 +1588,8 @@ func Test_fdReaddir_Errors(t *testing.T) {
 			expectedErrno: ErrnoInval,
 			expectedLog: `
 --> proxy.fd_readdir(fd=4,buf=0,buf_len=1,cookie=0,result.bufused=1000)
-	--> wasi_snapshot_preview1.fd_readdir(fd=4,buf=0,buf_len=1,cookie=0,result.bufused=1000)
-		==> wasi_snapshot_preview1.fdReaddir(fd=4,buf=0,buf_len=1,cookie=0)
-		<== (bufused=0,EINVAL)
-	<-- EINVAL
+	==> wasi_snapshot_preview1.fd_readdir(fd=4,buf=0,buf_len=1,cookie=0,result.bufused=1000)
+	<== EINVAL
 <-- 28
 `,
 		},
@@ -1640,10 +1602,8 @@ 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)
-		==> wasi_snapshot_preview1.fdReaddir(fd=4,buf=0,buf_len=1000,cookie=1)
-		<== (bufused=0,EINVAL)
-	<-- EINVAL
+	==> wasi_snapshot_preview1.fd_readdir(fd=4,buf=0,buf_len=1000,cookie=1,result.bufused=2000)
+	<== EINVAL
 <-- 28
 `,
 		},
@@ -1656,10 +1616,8 @@ 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)
-		==> wasi_snapshot_preview1.fdReaddir(fd=4,buf=0,buf_len=1000,cookie=1)
-		<== (bufused=0,EINVAL)
-	<-- EINVAL
+	==> wasi_snapshot_preview1.fd_readdir(fd=4,buf=0,buf_len=1000,cookie=1,result.bufused=2000)
+	<== EINVAL
 <-- 28
 `,
 		},
@@ -1673,10 +1631,8 @@ 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)
-		==> wasi_snapshot_preview1.fdReaddir(fd=4,buf=0,buf_len=1000,cookie=-1)
-		<== (bufused=0,EINVAL)
-	<-- EINVAL
+	==> wasi_snapshot_preview1.fd_readdir(fd=4,buf=0,buf_len=1000,cookie=-1,result.bufused=2000)
+	<== EINVAL
 <-- 28
 `,
 		},
@@ -1964,10 +1920,8 @@ 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)
-		==> wasi_snapshot_preview1.fdSeek(fd=4,offset=4,whence=0)
-		<== (newoffset=4,ESUCCESS)
-	<-- ESUCCESS
+	==> wasi_snapshot_preview1.fd_seek(fd=4,offset=4,whence=0,result.newoffset=1)
+	<== ESUCCESS
 <-- 0
 `,
 		},
@@ -1983,10 +1937,8 @@ 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)
-		==> wasi_snapshot_preview1.fdSeek(fd=4,offset=1,whence=1)
-		<== (newoffset=2,ESUCCESS)
-	<-- ESUCCESS
+	==> wasi_snapshot_preview1.fd_seek(fd=4,offset=1,whence=1,result.newoffset=1)
+	<== ESUCCESS
 <-- 0
 `,
 		},
@@ -2002,10 +1954,8 @@ 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)
-		==> wasi_snapshot_preview1.fdSeek(fd=4,offset=-1,whence=2)
-		<== (newoffset=5,ESUCCESS)
-	<-- ESUCCESS
+	==> wasi_snapshot_preview1.fd_seek(fd=4,offset=-1,whence=2,result.newoffset=1)
+	<== ESUCCESS
 <-- 0
 `,
 		},
@@ -2063,10 +2013,8 @@ 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)
-		==> wasi_snapshot_preview1.fdSeek(fd=42,offset=0,whence=0)
-		<== (newoffset=0,EBADF)
-	<-- EBADF
+	==> wasi_snapshot_preview1.fd_seek(fd=42,offset=0,whence=0,result.newoffset=0)
+	<== EBADF
 <-- 8
 `,
 		},
@@ -2077,10 +2025,8 @@ 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)
-		==> wasi_snapshot_preview1.fdSeek(fd=4,offset=0,whence=3)
-		<== (newoffset=0,EINVAL)
-	<-- EINVAL
+	==> wasi_snapshot_preview1.fd_seek(fd=4,offset=0,whence=3,result.newoffset=0)
+	<== EINVAL
 <-- 28
 `,
 		},
@@ -2091,8 +2037,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
 `,
 		},
@@ -2165,10 +2111,8 @@ 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)
-		==> wasi_snapshot_preview1.fdWrite(fd=4,iovs=1,iovs_len=2)
-		<== (nwritten=6,ESUCCESS)
-	<-- ESUCCESS
+	==> wasi_snapshot_preview1.fd_write(fd=4,iovs=1,iovs_len=2,result.nwritten=26)
+	<== ESUCCESS
 <-- 0
 `, "\n"+log.String())
 
@@ -2220,10 +2164,8 @@ 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)
-		==> wasi_snapshot_preview1.fdWrite(fd=1,iovs=1,iovs_len=2)
-		<== (nwritten=6,ESUCCESS)
-	<-- ESUCCESS
+	==> wasi_snapshot_preview1.fd_write(fd=1,iovs=1,iovs_len=2,result.nwritten=26)
+	<== ESUCCESS
 <-- 0
 `, "\n"+log.String())
 
@@ -2254,10 +2196,8 @@ 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)
-		==> wasi_snapshot_preview1.fdWrite(fd=42,iovs=0,iovs_len=1)
-		<== (nwritten=0,EBADF)
-	<-- EBADF
+	==> wasi_snapshot_preview1.fd_write(fd=42,iovs=0,iovs_len=1,result.nwritten=0)
+	<== EBADF
 <-- 8
 `,
 		},
@@ -2268,10 +2208,8 @@ func Test_fdWrite_Errors(t *testing.T) {
 			expectedErrno: ErrnoFault,
 			expectedLog: `
 --> 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
+	==> wasi_snapshot_preview1.fd_write(fd=4,iovs=65534,iovs_len=1,result.nwritten=0)
+	<== EFAULT
 <-- 21
 `,
 		},
@@ -2282,10 +2220,8 @@ func Test_fdWrite_Errors(t *testing.T) {
 			expectedErrno: ErrnoFault,
 			expectedLog: `
 --> 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
+	==> wasi_snapshot_preview1.fd_write(fd=4,iovs=65532,iovs_len=1,result.nwritten=0)
+	<== EFAULT
 <-- 21
 `,
 		},
@@ -2296,10 +2232,8 @@ func Test_fdWrite_Errors(t *testing.T) {
 			expectedErrno: ErrnoFault,
 			expectedLog: `
 --> 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
+	==> wasi_snapshot_preview1.fd_write(fd=4,iovs=65531,iovs_len=1,result.nwritten=0)
+	<== EFAULT
 <-- 21
 `,
 		},
@@ -2310,10 +2244,8 @@ func Test_fdWrite_Errors(t *testing.T) {
 			expectedErrno: ErrnoFault,
 			expectedLog: `
 --> 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
+	==> wasi_snapshot_preview1.fd_write(fd=4,iovs=65527,iovs_len=1,result.nwritten=0)
+	<== EFAULT
 <-- 21
 `,
 		},
@@ -2324,8 +2256,8 @@ func Test_fdWrite_Errors(t *testing.T) {
 			expectedErrno:  ErrnoFault,
 			expectedLog: `
 --> 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
+	==> wasi_snapshot_preview1.fd_write(fd=4,iovs=0,iovs_len=1,result.nwritten=65536)
+	<== EFAULT
 <-- 21
 `,
 		},
@@ -2640,10 +2572,8 @@ 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)
-		==> 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
+	==> 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
 <-- 0
 `, "\n"+log.String())
 
@@ -2684,10 +2614,8 @@ 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)
-		==> 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
+	==> 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
 <-- 8
 `,
 		},
@@ -2699,10 +2627,8 @@ 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)
-		==> 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
+	==> 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
 <-- 21
 `,
 		},
@@ -2715,10 +2641,8 @@ 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)
-		==> 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
+	==> 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
 <-- 44
 `,
 		},
@@ -2730,10 +2654,8 @@ 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)
-		==> 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
+	==> 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
 <-- 21
 `,
 		},
@@ -2746,10 +2668,8 @@ 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)
-		==> 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
+	==> 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
 <-- 44
 `,
 		},
@@ -2763,8 +2683,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
 `,
 		},
@@ -2778,10 +2698,8 @@ func Test_pathOpen_Errors(t *testing.T) {
 			expectedErrno: ErrnoNotdir,
 			expectedLog: `
 --> proxy.path_open(fd=3,dirflags=0,path=0,path_len=6,oflags=3,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=3,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=3,fs_rights_base=0,fs_rights_inheriting=0,fdflags=0)
-		<== (opened_fd=4,ENOTDIR)
-	<-- ENOTDIR
+	==> wasi_snapshot_preview1.path_open(fd=3,dirflags=0,path=0,path_len=6,oflags=3,fs_rights_base=0,fs_rights_inheriting=0,fdflags=0,result.opened_fd=0)
+	<== ENOTDIR
 <-- 54
 `,
 		},
diff --git a/imports/wasi_snapshot_preview1/poll_test.go b/imports/wasi_snapshot_preview1/poll_test.go
index 78cfb616..4efb89c0 100644
--- a/imports/wasi_snapshot_preview1/poll_test.go
+++ b/imports/wasi_snapshot_preview1/poll_test.go
@@ -42,10 +42,8 @@ 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)
-		==> wasi_snapshot_preview1.pollOneoff(in=0,out=128,nsubscriptions=1)
-		<== (nevents=1,ESUCCESS)
-	<-- ESUCCESS
+	==> wasi_snapshot_preview1.poll_oneoff(in=0,out=128,nsubscriptions=1,result.nevents=512)
+	<== ESUCCESS
 <-- 0
 `, "\n"+log.String())
 
@@ -79,10 +77,8 @@ 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)
-		==> wasi_snapshot_preview1.pollOneoff(in=65536,out=128,nsubscriptions=1)
-		<== (nevents=0,EFAULT)
-	<-- EFAULT
+	==> wasi_snapshot_preview1.poll_oneoff(in=65536,out=128,nsubscriptions=1,result.nevents=512)
+	<== EFAULT
 <-- 21
 `,
 		},
@@ -94,10 +90,8 @@ 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)
-		==> wasi_snapshot_preview1.pollOneoff(in=0,out=65536,nsubscriptions=1)
-		<== (nevents=0,EFAULT)
-	<-- EFAULT
+	==> wasi_snapshot_preview1.poll_oneoff(in=0,out=65536,nsubscriptions=1,result.nevents=512)
+	<== EFAULT
 <-- 21
 `,
 		},
@@ -108,8 +102,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
 `,
 		},
@@ -120,10 +114,8 @@ 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)
-		==> wasi_snapshot_preview1.pollOneoff(in=0,out=128,nsubscriptions=0)
-		<== (nevents=0,EINVAL)
-	<-- EINVAL
+	==> wasi_snapshot_preview1.poll_oneoff(in=0,out=128,nsubscriptions=0,result.nevents=512)
+	<== EINVAL
 <-- 28
 `,
 		},
@@ -147,10 +139,8 @@ 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)
-		==> wasi_snapshot_preview1.pollOneoff(in=0,out=128,nsubscriptions=1)
-		<== (nevents=1,ESUCCESS)
-	<-- ESUCCESS
+	==> wasi_snapshot_preview1.poll_oneoff(in=0,out=128,nsubscriptions=1,result.nevents=512)
+	<== ESUCCESS
 <-- 0
 `,
 		},
diff --git a/imports/wasi_snapshot_preview1/wrapper_test.go b/imports/wasi_snapshot_preview1/wrapper_test.go
deleted file mode 100644
index f605701a..00000000
--- a/imports/wasi_snapshot_preview1/wrapper_test.go
+++ /dev/null
@@ -1,255 +0,0 @@
-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)
-		})
-	}
-}
diff --git a/internal/gojs/spfunc/spfunc_test.go b/internal/gojs/spfunc/spfunc_test.go
deleted file mode 100644
index e98043d9..00000000
--- a/internal/gojs/spfunc/spfunc_test.go
+++ /dev/null
@@ -1,263 +0,0 @@
-package spfunc
-
-import (
-	"context"
-	_ "embed"
-	"testing"
-
-	"github.com/tetratelabs/wazero"
-	"github.com/tetratelabs/wazero/api"
-	"github.com/tetratelabs/wazero/internal/testing/require"
-	"github.com/tetratelabs/wazero/internal/wasm"
-	"github.com/tetratelabs/wazero/internal/wasm/binary"
-)
-
-const i32, i64 = api.ValueTypeI32, api.ValueTypeI64
-
-// testCtx is an arbitrary, non-default context. Non-nil also prevents linter errors.
-var testCtx = context.WithValue(context.Background(), struct{}{}, "arbitrary")
-
-func Test_callFromSP(t *testing.T) {
-	tests := []struct {
-		name                string
-		expectSP            bool
-		inputMem, outputMem []byte
-		proxied             *wasm.HostFunc
-		expected            *wasm.ProxyFunc
-		expectedErr         string
-	}{
-		{
-			name: "i32_v",
-			proxied: &wasm.HostFunc{
-				ExportNames: []string{"ex"},
-				Name:        "n",
-				ParamTypes:  []wasm.ValueType{wasm.ValueTypeI32},
-				ParamNames:  []string{"x"},
-				Code:        &wasm.Code{IsHostFunction: true, Body: []byte{wasm.OpcodeEnd}},
-			},
-			expected: &wasm.ProxyFunc{
-				Proxy: &wasm.HostFunc{
-					ExportNames: []string{"ex"},
-					Name:        "n.proxy",
-					ParamTypes:  []wasm.ValueType{wasm.ValueTypeI32},
-					ParamNames:  []string{"sp"},
-					Code: &wasm.Code{
-						IsHostFunction: true,
-						LocalTypes:     nil, // because there are no results
-						Body: []byte{
-							wasm.OpcodeLocalGet, 0, wasm.OpcodeI32Const, 8, wasm.OpcodeI32Add,
-							wasm.OpcodeI32Load, 0x2, 0x0,
-							wasm.OpcodeCall, 0,
-							wasm.OpcodeEnd,
-						},
-					},
-				},
-				Proxied: &wasm.HostFunc{
-					Name:       "n",
-					ParamTypes: []wasm.ValueType{wasm.ValueTypeI32},
-					ParamNames: []string{"x"},
-					Code:       &wasm.Code{IsHostFunction: true, Body: []byte{wasm.OpcodeEnd}},
-				},
-				CallBodyPos: 9,
-			},
-		},
-		{
-			name: "i32_i32",
-			proxied: &wasm.HostFunc{
-				ExportNames: []string{"ex"},
-				Name:        "n",
-				ParamTypes:  []wasm.ValueType{wasm.ValueTypeI32},
-				ParamNames:  []string{"x"},
-				ResultTypes: []wasm.ValueType{wasm.ValueTypeI32},
-				ResultNames: []string{"y"},
-				Code:        &wasm.Code{IsHostFunction: true, Body: []byte{wasm.OpcodeI32Const, 1, wasm.OpcodeEnd}},
-			},
-			expected: &wasm.ProxyFunc{
-				Proxy: &wasm.HostFunc{
-					ExportNames: []string{"ex"},
-					Name:        "n.proxy",
-					ParamTypes:  []wasm.ValueType{wasm.ValueTypeI32},
-					ParamNames:  []string{"sp"},
-					Code: &wasm.Code{
-						IsHostFunction: true,
-						LocalTypes:     []api.ValueType{api.ValueTypeI32, api.ValueTypeI64},
-						Body: []byte{
-							wasm.OpcodeLocalGet, 0, wasm.OpcodeI32Const, 8, wasm.OpcodeI32Add,
-							wasm.OpcodeI32Load, 0x2, 0x0,
-							wasm.OpcodeCall, 0,
-							wasm.OpcodeLocalSet, 1,
-							wasm.OpcodeLocalGet, 0, wasm.OpcodeI32Const, 16, wasm.OpcodeI32Add,
-							wasm.OpcodeLocalGet, 1,
-							wasm.OpcodeI32Store, 0x2, 0x0,
-							wasm.OpcodeEnd,
-						},
-					},
-				},
-				Proxied: &wasm.HostFunc{
-					Name:        "n",
-					ParamTypes:  []wasm.ValueType{wasm.ValueTypeI32},
-					ParamNames:  []string{"x"},
-					ResultTypes: []wasm.ValueType{wasm.ValueTypeI32},
-					ResultNames: []string{"y"},
-					Code:        &wasm.Code{IsHostFunction: true, Body: []byte{wasm.OpcodeI32Const, 1, wasm.OpcodeEnd}},
-				},
-				CallBodyPos: 9,
-			},
-		},
-		{
-			name: "i32i32_i64",
-			proxied: &wasm.HostFunc{
-				ExportNames: []string{"ex"},
-				Name:        "n",
-				ParamTypes:  []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32},
-				ParamNames:  []string{"x", "y"},
-				ResultTypes: []wasm.ValueType{wasm.ValueTypeI64},
-				ResultNames: []string{"z"},
-				Code:        &wasm.Code{IsHostFunction: true, Body: []byte{wasm.OpcodeI64Const, 1, wasm.OpcodeEnd}},
-			},
-			expected: &wasm.ProxyFunc{
-				Proxy: &wasm.HostFunc{
-					ExportNames: []string{"ex"},
-					Name:        "n.proxy",
-					ParamTypes:  []wasm.ValueType{wasm.ValueTypeI32},
-					ParamNames:  []string{"sp"},
-					Code: &wasm.Code{
-						IsHostFunction: true,
-						LocalTypes:     []api.ValueType{api.ValueTypeI32, api.ValueTypeI64},
-						Body: []byte{
-							wasm.OpcodeLocalGet, 0, wasm.OpcodeI32Const, 8, wasm.OpcodeI32Add,
-							wasm.OpcodeI32Load, 0x2, 0x0,
-							wasm.OpcodeLocalGet, 0, wasm.OpcodeI32Const, 16, wasm.OpcodeI32Add,
-							wasm.OpcodeI32Load, 0x2, 0x0,
-							wasm.OpcodeCall, 0,
-							wasm.OpcodeLocalSet, 2,
-							wasm.OpcodeLocalGet, 0, wasm.OpcodeI32Const, 24, wasm.OpcodeI32Add,
-							wasm.OpcodeLocalGet, 2,
-							wasm.OpcodeI64Store, 0x3, 0x0,
-							wasm.OpcodeEnd,
-						},
-					},
-				},
-				Proxied: &wasm.HostFunc{
-					Name:        "n",
-					ParamTypes:  []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32},
-					ParamNames:  []string{"x", "y"},
-					ResultTypes: []wasm.ValueType{wasm.ValueTypeI64},
-					ResultNames: []string{"z"},
-					Code:        &wasm.Code{IsHostFunction: true, Body: []byte{wasm.OpcodeI64Const, 1, wasm.OpcodeEnd}},
-				},
-				CallBodyPos: 17,
-			},
-		},
-	}
-
-	for _, tt := range tests {
-		tc := tt
-
-		t.Run(tc.name, func(t *testing.T) {
-			proxy, err := callFromSP(tc.expectSP, tc.proxied)
-			if tc.expectedErr != "" {
-				require.EqualError(t, err, tc.expectedErr)
-			} else {
-				require.Equal(t, tc.expected, proxy)
-			}
-		})
-	}
-}
-
-var spMem = []byte{
-	0, 0, 0, 0, 0, 0, 0, 0,
-	1, 0, 0, 0, 0, 0, 0, 0,
-	2, 0, 0, 0, 0, 0, 0, 0,
-	3, 0, 0, 0, 0, 0, 0, 0,
-	4, 0, 0, 0, 0, 0, 0, 0,
-	5, 0, 0, 0, 0, 0, 0, 0,
-	6, 0, 0, 0, 0, 0, 0, 0,
-	7, 0, 0, 0, 0, 0, 0, 0,
-	8, 0, 0, 0, 0, 0, 0, 0,
-	9, 0, 0, 0, 0, 0, 0, 0,
-	10, 0, 0, 0, 0, 0, 0, 0,
-}
-
-func i64i32i32i32i32_i64i32_withSP(_ context.Context, stack []uint64) {
-	vRef := stack[0]
-	mAddr := uint32(stack[1])
-	mLen := uint32(stack[2])
-	argsArray := uint32(stack[3])
-	argsLen := uint32(stack[4])
-
-	if vRef != 1 {
-		panic("vRef")
-	}
-	if mAddr != 2 {
-		panic("mAddr")
-	}
-	if mLen != 3 {
-		panic("mLen")
-	}
-	if argsArray != 4 {
-		panic("argsArray")
-	}
-	if argsLen != 5 {
-		panic("argsLen")
-	}
-
-	// set results
-	stack[0] = 10
-	stack[1] = 20
-	stack[2] = 8
-}
-
-func TestMustCallFromSP(t *testing.T) {
-	r := wazero.NewRuntimeWithConfig(testCtx, wazero.NewRuntimeConfigInterpreter())
-	defer r.Close(testCtx)
-
-	funcName := "i64i32i32i32i32_i64i32_withSP"
-	builder := r.NewHostModuleBuilder("go")
-	builder.(wasm.ProxyFuncExporter).ExportProxyFunc(MustCallFromSP(true, &wasm.HostFunc{
-		ExportNames: []string{funcName},
-		Name:        funcName,
-		ParamTypes:  []api.ValueType{i64, i32, i32, i32, i32},
-		ParamNames:  []string{"v", "mAddr", "mLen", "argsArray", "argsLen"},
-		ResultTypes: []api.ValueType{i64, i32, i32},
-		Code: &wasm.Code{
-			IsHostFunction: true,
-			GoFunc:         api.GoFunc(i64i32i32i32i32_i64i32_withSP),
-		},
-	}))
-	im, err := builder.Instantiate(testCtx, r)
-	require.NoError(t, err)
-
-	callDef := im.ExportedFunction(funcName).Definition()
-
-	bin := binary.EncodeModule(&wasm.Module{
-		TypeSection: []*wasm.FunctionType{{
-			Params:  callDef.ParamTypes(),
-			Results: callDef.ResultTypes(),
-		}},
-		ImportSection:   []*wasm.Import{{Module: "go", Name: funcName}},
-		MemorySection:   &wasm.Memory{Min: 1, Cap: 1, Max: 1},
-		FunctionSection: []wasm.Index{0},
-		CodeSection: []*wasm.Code{
-			{Body: []byte{wasm.OpcodeLocalGet, 0, wasm.OpcodeCall, 0, wasm.OpcodeEnd}},
-		},
-		ExportSection: []*wasm.Export{{Name: funcName, Index: 1}},
-	})
-	require.NoError(t, err)
-
-	mod, err := r.InstantiateModuleFromBinary(testCtx, bin)
-	require.NoError(t, err)
-
-	memView, ok := mod.Memory().Read(0, uint32(len(spMem)))
-	require.True(t, ok)
-	copy(memView, spMem)
-
-	_, err = mod.ExportedFunction(funcName).Call(testCtx, 0) // SP
-	require.NoError(t, err)
-	require.Equal(t, []byte{
-		7, 0, 0, 0, 0, 0, 0, 0, // 7 left alone as SP was refreshed to 8
-		10, 0, 0, 0, 0, 0, 0, 0,
-		20, 0, 0, 0, 0, 0, 0, 0,
-		10, 0, 0, 0, 0, 0, 0, 0,
-	}, memView[56:])
-}
diff --git a/internal/integration_test/vs/bench.go b/internal/integration_test/vs/bench.go
index 0f2e725d..8f056eba 100644
--- a/internal/integration_test/vs/bench.go
+++ b/internal/integration_test/vs/bench.go
@@ -74,15 +74,18 @@ func runCallBenchmark(rt Runtime, rtCfg *RuntimeConfig, call func(Module, int) e
 func benchmark(b *testing.B, runtime func() Runtime, rtCfg *RuntimeConfig, call func(Module, int) error) {
 	rt := runtime()
 	b.Run("Compile", func(b *testing.B) {
+		b.ReportAllocs()
 		benchmarkCompile(b, rt, rtCfg)
 	})
 	b.Run("Instantiate", func(b *testing.B) {
+		b.ReportAllocs()
 		benchmarkInstantiate(b, rt, rtCfg)
 	})
 
 	// Don't burn CPU when this is already going to be called in runTestBenchmark_Call_CompilerFastest
 	if ensureCompilerFastest != "true" || rt.Name() == compilerRuntime {
 		b.Run("Call", func(b *testing.B) {
+			b.ReportAllocs()
 			benchmarkCall(b, rt, rtCfg, call)
 		})
 	}
diff --git a/internal/leb128/leb128_alloc_test.go b/internal/leb128/leb128_alloc_test.go
new file mode 100644
index 00000000..ccc9d0eb
--- /dev/null
+++ b/internal/leb128/leb128_alloc_test.go
@@ -0,0 +1,119 @@
+package leb128
+
+import (
+	"bytes"
+	"testing"
+
+	"github.com/tetratelabs/wazero/internal/testing/require"
+)
+
+// TestLeb128NoAlloc ensures no allocation required in the leb128 package.
+func TestLeb128NoAlloc(t *testing.T) {
+	t.Run("LoadUint32", func(t *testing.T) {
+		result := testing.Benchmark(BenchmarkLoadUint32)
+		require.Zero(t, result.AllocsPerOp())
+	})
+	t.Run("LoadUint64", func(t *testing.T) {
+		result := testing.Benchmark(BenchmarkLoadUint64)
+		require.Zero(t, result.AllocsPerOp())
+	})
+	t.Run("LoadInt32", func(t *testing.T) {
+		result := testing.Benchmark(BenchmarkLoadInt32)
+		require.Zero(t, result.AllocsPerOp())
+	})
+	t.Run("LoadInt64", func(t *testing.T) {
+		result := testing.Benchmark(BenchmarkLoadInt64)
+		require.Zero(t, result.AllocsPerOp())
+	})
+	t.Run("DecodeUint32", func(t *testing.T) {
+		result := testing.Benchmark(BenchmarkDecodeUint32)
+		require.Zero(t, result.AllocsPerOp())
+	})
+	t.Run("DecodeInt32", func(t *testing.T) {
+		result := testing.Benchmark(BenchmarkDecodeInt32)
+		require.Zero(t, result.AllocsPerOp())
+	})
+	t.Run("DecodeInt64", func(t *testing.T) {
+		result := testing.Benchmark(BenchmarkDecodeInt64)
+		require.Zero(t, result.AllocsPerOp())
+	})
+}
+
+func BenchmarkLoadUint32(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		_, _, err := LoadUint32([]byte{0x80, 0x80, 0x80, 0x4f})
+		if err != nil {
+			b.Fatal(err)
+		}
+	}
+}
+
+func BenchmarkLoadUint64(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		_, _, err := LoadUint64([]byte{0x80, 0x80, 0x80, 0x4f})
+		if err != nil {
+			b.Fatal(err)
+		}
+	}
+}
+
+func BenchmarkLoadInt32(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		_, _, err := LoadInt32([]byte{0x80, 0x80, 0x80, 0x4f})
+		if err != nil {
+			b.Fatal(err)
+		}
+	}
+}
+
+func BenchmarkLoadInt64(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		_, _, err := LoadInt64([]byte{0x80, 0x80, 0x80, 0x4f})
+		if err != nil {
+			b.Fatal(err)
+		}
+	}
+}
+
+func BenchmarkDecodeUint32(b *testing.B) {
+	b.ReportAllocs()
+	data := []byte{0x80, 0x80, 0x80, 0x4f}
+	r := bytes.NewReader(data)
+	for i := 0; i < b.N; i++ {
+		_, _, err := DecodeUint32(r)
+		if err != nil {
+			b.Fatal(err)
+		}
+		r.Reset(data)
+	}
+}
+
+func BenchmarkDecodeInt32(b *testing.B) {
+	b.ReportAllocs()
+	data := []byte{0x80, 0x80, 0x80, 0x4f}
+	r := bytes.NewReader(data)
+	for i := 0; i < b.N; i++ {
+		_, _, err := DecodeInt32(r)
+		if err != nil {
+			b.Fatal(err)
+		}
+		r.Reset(data)
+	}
+}
+
+func BenchmarkDecodeInt64(b *testing.B) {
+	b.ReportAllocs()
+	data := []byte{0x80, 0x80, 0x80, 0x4f}
+	r := bytes.NewReader(data)
+	for i := 0; i < b.N; i++ {
+		_, _, err := DecodeInt64(r)
+		if err != nil {
+			b.Fatal(err)
+		}
+		r.Reset(data)
+	}
+}