Improves instantiation performance by removing ProxyFunc (#942)

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)

```

Signed-off-by: Adrian Cole <adrian@tetrate.io>
This commit is contained in:
Crypt Keeper
2022-12-19 18:18:19 +09:00
committed by GitHub
parent b90e9f394c
commit b1cb9140dd
25 changed files with 620 additions and 1763 deletions

View File

@@ -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}

View File

@@ -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 {

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
`,
},

View File

@@ -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
}
errno = ErrnoSuccess
return
if !mod.Memory().WriteUint64Le(resultTimestamp, uint64(val)) {
return ErrnoFault
}
return ErrnoSuccess
}

View File

@@ -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
`,
},

View File

@@ -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
}

View File

@@ -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
`,
},

View File

@@ -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

View File

@@ -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)
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, stack []uint64) (nread uint32, errno Errno) {
return fdReadOrPread(mod, stack, true)
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)
var fdPrestatGet = newHostFunc(fdPrestatGetName, fdPrestatGetFn, []api.ValueType{i32, i32}, "fd", "result.prestat")
func fdPrestatGetFn(_ context.Context, mod api.Module, stack []uint64) (prestat uint64, errno Errno) {
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)
var fdRead = newHostFunc(
fdReadName, fdReadFn,
[]api.ValueType{i32, i32, i32, i32},
"fd", "iovs", "iovs_len", "result.nread",
)
func fdReadFn(_ context.Context, mod api.Module, stack []uint64) (nread uint32, errno Errno) {
return fdReadOrPread(mod, stack, false)
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)
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, stack []uint64) (uint32, Errno) {
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)
var fdSeek = newHostFunc(
fdSeekName, fdSeekFn,
[]api.ValueType{i32, i64, i32, i32},
"fd", "offset", "whence", "result.newoffset",
)
func fdSeekFn(_ context.Context, mod api.Module, stack []uint64) (int64, Errno) {
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)
var fdWrite = newHostFunc(
fdWriteName, fdWriteFn,
[]api.ValueType{i32, i32, i32, i32},
"fd", "iovs", "iovs_len", "result.nwritten",
)
func fdWriteFn(_ context.Context, mod api.Module, stack []uint64) (uint32, Errno) {
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 {

View File

@@ -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
`,
},

View File

@@ -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)
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) (nevents uint32, errno Errno) {
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

View File

@@ -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
`,
},

View File

@@ -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")

View File

@@ -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,

View File

@@ -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))
}
}

View File

@@ -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)
})
}
}

View File

@@ -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),
},
})
var WasmExit = newSPFunc(wasmExitName, wasmExit)
func wasmExit(ctx context.Context, mod api.Module, stack []uint64) {
code := uint32(stack[0])
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),
},
})
var WasmWrite = newSPFunc(wasmWriteName, wasmWrite)
func wasmWrite(_ context.Context, mod api.Module, stack []uint64) {
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),
},
})
var Nanotime1 = newSPFunc(nanotime1Name, nanotime1)
func nanotime1(_ context.Context, mod api.Module, stack []uint64) {
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),
},
})
var Walltime = newSPFunc(walltimeName, walltime)
func walltime(_ context.Context, mod api.Module, stack []uint64) {
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),
},
})
var GetRandomData = newSPFunc(getRandomDataName, getRandomData)
func getRandomData(_ context.Context, mod api.Module, stack []uint64) {
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)

View File

@@ -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
}

View File

@@ -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:])
}

View File

@@ -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),
},
})
var FinalizeRef = newSPFunc(finalizeRefName, finalizeRef)
func finalizeRef(ctx context.Context, stack []uint64) {
id := uint32(stack[0]) // 32-bits of the ref are the ID
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),
},
})
var StringVal = newSPFunc(stringValName, stringVal)
func stringVal(ctx context.Context, mod api.Module, stack []uint64) {
xAddr, xLen := uint32(stack[0]), uint32(stack[1])
func stringVal(ctx context.Context, mod api.Module, sp []uint64) {
mem := mod.Memory()
x := string(mustRead(mod.Memory(), "x", xAddr, xLen))
// Read (param + result count) * 8 memory starting at SP+8
stack := mustRead(mem, "sp", uint32(sp[0]+8), 24)
stack[0] = storeRef(ctx, x)
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),
},
})
var ValueGet = newSPFunc(valueGetName, valueGet)
func valueGet(ctx context.Context, mod api.Module, stack []uint64) {
vRef := stack[0]
pAddr := uint32(stack[1])
pLen := uint32(stack[2])
func valueGet(ctx context.Context, mod api.Module, sp []uint64) {
mem := mod.Memory()
p := string(mustRead(mod.Memory(), "p", pAddr, pLen))
// 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),
},
})
var ValueSet = newSPFunc(valueSetName, 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]
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),
},
})
var ValueIndex = newSPFunc(valueIndexName, valueIndex)
func valueIndex(ctx context.Context, stack []uint64) {
vRef := stack[0]
i := uint32(stack[1])
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),
},
})
var ValueCall = newSPFunc(valueCallName, 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])
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),
},
})
var ValueNew = newSPFunc(valueNewName, valueNew)
func valueNew(ctx context.Context, mod api.Module, stack []uint64) {
vRef := stack[0]
argsArray := uint32(stack[1])
argsLen := uint32(stack[2])
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),
},
})
var ValueLength = newSPFunc(valueLengthName, valueLength)
func valueLength(ctx context.Context, stack []uint64) {
vRef := stack[0]
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),
},
})
var ValuePrepareString = newSPFunc(valuePrepareStringName, valuePrepareString)
func valuePrepareString(ctx context.Context, stack []uint64) {
vRef := stack[0]
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),
},
})
var ValueLoadString = newSPFunc(valueLoadStringName, valueLoadString)
func valueLoadString(ctx context.Context, mod api.Module, stack []uint64) {
vRef := stack[0]
bAddr := uint32(stack[1])
bLen := uint32(stack[2])
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),
},
})
var CopyBytesToGo = newSPFunc(copyBytesToGoName, 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]
func copyBytesToGo(ctx context.Context, mod api.Module, sp []uint64) {
mem := mod.Memory()
dst := mustRead(mod.Memory(), "dst", dstAddr, dstLen) // nolint
// 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),
},
})
var CopyBytesToJS = newSPFunc(copyBytesToJSName, 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])
func copyBytesToJS(ctx context.Context, mod api.Module, sp []uint64) {
mem := mod.Memory()
src := mustRead(mod.Memory(), "src", srcAddr, srcLen) // nolint
// 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},
}
}

View File

@@ -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 {

View File

@@ -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)
})
}

View File

@@ -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 {