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:
@@ -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}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
`,
|
||||
},
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
`,
|
||||
},
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
`,
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
`,
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
`,
|
||||
},
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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:])
|
||||
}
|
||||
@@ -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},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user