gojs: extracts parameter names into a pseudo name section (#963)

`GOARCH=wasm GOOS=js` defines parameter names in go source, and they are
indirectly related to the wasm parameter "sp". This creates a pseudo
name section so that we can access the parameter names. The alternative
would be adding a hack to normal FunctionDefinition, only used for gojs.

Signed-off-by: Adrian Cole <adrian@tetrate.io>
This commit is contained in:
Crypt Keeper
2022-12-27 14:09:20 +08:00
committed by GitHub
parent 1ad900d179
commit 5751bd758c
8 changed files with 345 additions and 158 deletions

View File

@@ -59,7 +59,10 @@ func WriteArgsAndEnviron(mod api.Module) (argc, argv uint32, err error) {
err = errors.New("total length of command line and environment variables exceeds limit")
}
buf := util.MustRead(mem, "argvPtrs", argv, stop)
buf, ok := mem.Read(argv, stop)
if !ok {
panic("out of memory reading argvPtrs")
}
pos := uint32(0)
for _, ptr := range argvPtrs {
le.PutUint64(buf[pos:], uint64(ptr))

View File

@@ -0,0 +1,186 @@
// Package custom is similar to the WebAssembly Custom Sections. These are
// needed because `GOARCH=wasm GOOS=js` functions aren't defined naturally
// in WebAssembly. For example, every function has a single parameter "sp",
// which implicitly maps to stack parameters in this package.
package custom
const (
// NamePadding is a marker for a parameter which has no purpose, except
// padding. It should not be logged.
NamePadding = "padding"
)
type Names struct {
// Name is the WebAssembly function name.
Name string
// ParamNames are the parameters read in 8-byte strides from the stack
// pointer (SP). This may be nil or include NamePadding.
ParamNames []string
// ResultNames are the results written in 8-byte strides from the stack
// pointer (SP), after ParamNames.
ResultNames []string
}
const NameDebug = "debug"
const (
NameRuntimeWasmExit = "runtime.wasmExit"
NameRuntimeWasmWrite = "runtime.wasmWrite"
NameRuntimeResetMemoryDataView = "runtime.resetMemoryDataView"
NameRuntimeNanotime1 = "runtime.nanotime1"
NameRuntimeWalltime = "runtime.walltime"
NameRuntimeScheduleTimeoutEvent = "runtime.scheduleTimeoutEvent" // TODO: trigger usage
NameRuntimeClearTimeoutEvent = "runtime.clearTimeoutEvent" // TODO: trigger usage
NameRuntimeGetRandomData = "runtime.getRandomData"
)
const (
NameSyscallFinalizeRef = "syscall/js.finalizeRef"
NameSyscallStringVal = "syscall/js.stringVal"
NameSyscallValueGet = "syscall/js.valueGet"
NameSyscallValueSet = "syscall/js.valueSet"
NameSyscallValueDelete = "syscall/js.valueDelete" // stubbed
NameSyscallValueIndex = "syscall/js.valueIndex"
NameSyscallValueSetIndex = "syscall/js.valueSetIndex" // stubbed
NameSyscallValueCall = "syscall/js.valueCall"
NameSyscallValueInvoke = "syscall/js.valueInvoke" // stubbed
NameSyscallValueNew = "syscall/js.valueNew"
NameSyscallValueLength = "syscall/js.valueLength"
NameSyscallValuePrepareString = "syscall/js.valuePrepareString"
NameSyscallValueLoadString = "syscall/js.valueLoadString"
NameSyscallValueInstanceOf = "syscall/js.valueInstanceOf" // stubbed
NameSyscallCopyBytesToGo = "syscall/js.copyBytesToGo"
NameSyscallCopyBytesToJS = "syscall/js.copyBytesToJS"
)
var NameSection = map[string]*Names{
NameDebug: {
Name: NameDebug,
ParamNames: []string{},
ResultNames: []string{},
},
NameRuntimeWasmExit: {
Name: NameRuntimeWasmExit,
ParamNames: []string{"code"},
ResultNames: []string{},
},
NameRuntimeWasmWrite: {
Name: NameRuntimeWasmWrite,
ParamNames: []string{"fd", "p", "p_len"},
ResultNames: []string{},
},
NameRuntimeResetMemoryDataView: {
Name: NameRuntimeResetMemoryDataView,
ParamNames: []string{},
ResultNames: []string{},
},
NameRuntimeNanotime1: {
Name: NameRuntimeNanotime1,
ParamNames: []string{},
ResultNames: []string{"nsec"},
},
NameRuntimeWalltime: {
Name: NameRuntimeWalltime,
ParamNames: []string{},
ResultNames: []string{"sec", "nsec"},
},
NameRuntimeScheduleTimeoutEvent: {
Name: NameRuntimeScheduleTimeoutEvent,
ParamNames: []string{"ms"},
ResultNames: []string{"id"},
},
NameRuntimeClearTimeoutEvent: {
Name: NameRuntimeClearTimeoutEvent,
ParamNames: []string{"id"},
ResultNames: []string{},
},
NameRuntimeGetRandomData: {
Name: NameRuntimeGetRandomData,
ParamNames: []string{"r", "r_len"},
ResultNames: []string{},
},
NameSyscallFinalizeRef: {
Name: NameSyscallFinalizeRef,
ParamNames: []string{"r"},
ResultNames: []string{},
},
NameSyscallStringVal: {
Name: NameSyscallStringVal,
ParamNames: []string{"x", "x_len"},
ResultNames: []string{"r"},
},
NameSyscallValueGet: {
Name: NameSyscallValueGet,
ParamNames: []string{"v", "p", "p_len"},
ResultNames: []string{"r"},
},
NameSyscallValueSet: {
Name: NameSyscallValueSet,
ParamNames: []string{"v", "p", "p_len", "x"},
ResultNames: []string{},
},
NameSyscallValueDelete: {
Name: NameSyscallValueDelete,
ParamNames: []string{"v", "p", "p_len"},
ResultNames: []string{},
},
NameSyscallValueIndex: {
Name: NameSyscallValueIndex,
ParamNames: []string{"v", "i"},
ResultNames: []string{"r"},
},
NameSyscallValueSetIndex: {
Name: NameSyscallValueSetIndex,
ParamNames: []string{"v", "i", "x"},
ResultNames: []string{},
},
NameSyscallValueCall: {
Name: NameSyscallValueCall,
ParamNames: []string{"v", "m", "m_len", "args", "args_len", NamePadding},
ResultNames: []string{"res", "ok"},
},
NameSyscallValueInvoke: {
Name: NameSyscallValueInvoke,
ParamNames: []string{"v", "args", "args_len", NamePadding},
ResultNames: []string{"res", "ok"},
},
NameSyscallValueNew: {
Name: NameSyscallValueNew,
ParamNames: []string{"v", "args", "args_len", NamePadding},
ResultNames: []string{"res", "ok"},
},
NameSyscallValueLength: {
Name: NameSyscallValueLength,
ParamNames: []string{"v"},
ResultNames: []string{"len"},
},
NameSyscallValuePrepareString: {
Name: NameSyscallValuePrepareString,
ParamNames: []string{"v"},
ResultNames: []string{"str", "length"},
},
NameSyscallValueLoadString: {
Name: NameSyscallValueLoadString,
ParamNames: []string{"v", "b", "b_len"},
ResultNames: []string{},
},
NameSyscallValueInstanceOf: {
Name: NameSyscallValueInstanceOf,
ParamNames: []string{"v", "t"},
ResultNames: []string{"ok"},
},
NameSyscallCopyBytesToGo: {
Name: NameSyscallCopyBytesToGo,
ParamNames: []string{"dst", "dst_len", NamePadding, "src"},
ResultNames: []string{"n", "ok"},
},
NameSyscallCopyBytesToJS: {
Name: NameSyscallCopyBytesToJS,
ParamNames: []string{"dst", "src", "src_len", NamePadding},
ResultNames: []string{"n", "ok"},
},
}

View File

@@ -5,9 +5,9 @@ package goarch
import (
"context"
"encoding/binary"
"fmt"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/gojs/custom"
"github.com/tetratelabs/wazero/internal/gojs/util"
"github.com/tetratelabs/wazero/internal/wasm"
)
@@ -37,8 +37,10 @@ func NoopFunction(name string) *wasm.HostFunc {
var le = binary.LittleEndian
type Stack interface {
// Name is the function name being invoked.
Name() string
Param(i int) uint64
ParamName(i int) string
// ParamBytes reads a byte slice, given its memory offset and length (stack
// positions i, i+1)
@@ -56,8 +58,6 @@ type Stack interface {
// can trigger a Go event handler.
Refresh(api.Module)
ResultName(i int) string
SetResult(i int, v uint64)
SetResultBool(i int, v bool)
@@ -69,17 +69,24 @@ type Stack interface {
SetResultUint32(i int, v uint32)
}
func NewStack(mem api.Memory, sp uint32, paramNames, resultNames []string) Stack {
s := &stack{paramNames: paramNames, resultNames: resultNames}
func NewStack(name string, mem api.Memory, sp uint32) Stack {
names := custom.NameSection[name]
s := &stack{name: name, paramCount: len(names.ParamNames), resultCount: len(names.ResultNames)}
s.refresh(mem, sp)
return s
}
type stack struct {
paramNames, resultNames []string
name string
paramCount, resultCount int
buf []byte
}
// Name implements Stack.Name
func (s *stack) Name() string {
return s.name
}
// Param implements Stack.Param
func (s *stack) Param(i int) (res uint64) {
pos := i << 3
@@ -87,16 +94,11 @@ func (s *stack) Param(i int) (res uint64) {
return
}
// ParamName implements Stack.ParamName
func (s *stack) ParamName(i int) string {
return s.paramNames[i]
}
// ParamBytes implements Stack.ParamBytes
func (s *stack) ParamBytes(mem api.Memory, i int) (res []byte) {
offset := s.ParamUint32(i)
byteCount := s.ParamUint32(i + 1)
return mustRead(mem, s.paramNames[i], offset, byteCount)
return util.MustRead(mem, s.name, i, offset, byteCount)
}
// ParamString implements Stack.ParamString
@@ -115,21 +117,20 @@ func (s *stack) Refresh(mod api.Module) {
}
func (s *stack) refresh(mem api.Memory, sp uint32) {
count := uint32(len(s.paramNames) + len(s.resultNames))
s.buf = mustRead(mem, "sp", sp+8, count<<3)
count := uint32(s.paramCount + s.resultCount)
buf, ok := mem.Read(sp+8, count<<3)
if !ok {
panic("out of memory reading stack")
}
s.buf = buf
}
// SetResult implements Stack.SetResult
func (s *stack) SetResult(i int, v uint64) {
pos := (len(s.paramNames) + i) << 3
pos := (s.paramCount + i) << 3
le.PutUint64(s.buf[pos:], v)
}
// ResultName implements Stack.ResultName
func (s *stack) ResultName(i int) string {
return s.resultNames[i]
}
// SetResultBool implements Stack.SetResultBool
func (s *stack) SetResultBool(i int, v bool) {
if v {
@@ -164,29 +165,19 @@ func getSP(mod api.Module) uint32 {
return uint32(mod.(*wasm.CallContext).GlobalVal(0))
}
// mustRead is like api.Memory except that it panics if the offset and
// byteCount are out of range.
func mustRead(mem api.Memory, fieldName string, offset, byteCount uint32) []byte {
buf, ok := mem.Read(offset, byteCount)
if !ok {
panic(fmt.Errorf("out of memory reading %s", fieldName))
}
return buf
}
func NewFunc(name string, goFunc Func, paramNames, resultNames []string) *wasm.HostFunc {
return util.NewFunc(name, (&stackFunc{f: goFunc, paramNames: paramNames, resultNames: resultNames}).Call)
func NewFunc(name string, goFunc Func) *wasm.HostFunc {
return util.NewFunc(name, (&stackFunc{name: name, f: goFunc}).Call)
}
type Func func(context.Context, api.Module, Stack)
type stackFunc struct {
f Func
paramNames, resultNames []string
name string
f Func
}
// Call implements the same method as defined on api.GoModuleFunction.
func (f *stackFunc) Call(ctx context.Context, mod api.Module, wasmStack []uint64) {
s := NewStack(mod.Memory(), uint32(wasmStack[0]), f.paramNames, f.resultNames)
s := NewStack(f.name, mod.Memory(), uint32(wasmStack[0]))
f.f(ctx, mod, s)
}

View File

@@ -95,16 +95,16 @@ type stack struct {
s goarch.Stack
}
// Name implements the same method as documented on goarch.Stack
func (s *stack) Name() string {
return s.s.Name()
}
// Param implements the same method as documented on goarch.Stack
func (s *stack) Param(i int) uint64 {
return s.s.Param(i)
}
// ParamName implements the same method as documented on goarch.Stack
func (s *stack) ParamName(i int) string {
return s.s.ParamName(i)
}
// ParamBytes implements the same method as documented on goarch.Stack
func (s *stack) ParamBytes(mem api.Memory, i int) []byte {
return s.s.ParamBytes(mem, i)
@@ -123,7 +123,7 @@ func (s *stack) ParamRefs(mem api.Memory, i int) []Ref {
result := make([]Ref, 0, size)
buf := util.MustRead(mem, s.s.ParamName(i), offset, byteCount)
buf := util.MustRead(mem, s.Name(), i, offset, byteCount)
for pos := uint32(0); pos < byteCount; pos += 8 {
ref := Ref(le.Uint64(buf[pos:]))
result = append(result, ref)
@@ -155,7 +155,7 @@ func (s *stack) ParamVals(ctx context.Context, mem api.Memory, i int, loader Val
result := make([]interface{}, 0, size)
buf := util.MustRead(mem, s.s.ParamName(i), offset, byteCount)
buf := util.MustRead(mem, s.Name(), i, offset, byteCount)
for pos := uint32(0); pos < byteCount; pos += 8 {
ref := Ref(le.Uint64(buf[pos:]))
result = append(result, loader(ctx, ref))
@@ -168,11 +168,6 @@ func (s *stack) Refresh(mod api.Module) {
s.s.Refresh(mod)
}
// ResultName implements the same method as documented on goarch.Stack
func (s *stack) ResultName(i int) string {
return s.s.ResultName(i)
}
// SetResult implements the same method as documented on goarch.Stack
func (s *stack) SetResult(i int, v uint64) {
s.s.SetResult(i, v)
@@ -203,19 +198,19 @@ func (s *stack) SetResultUint32(i int, v uint32) {
s.s.SetResultUint32(i, v)
}
func NewFunc(name string, goFunc Func, paramNames, resultNames []string) *wasm.HostFunc {
return util.NewFunc(name, (&stackFunc{f: goFunc, paramNames: paramNames, resultNames: resultNames}).Call)
func NewFunc(name string, goFunc Func) *wasm.HostFunc {
return util.NewFunc(name, (&stackFunc{name: name, f: goFunc}).Call)
}
type Func func(context.Context, api.Module, Stack)
type stackFunc struct {
f Func
paramNames, resultNames []string
name string
f Func
}
// Call implements the same method as defined on api.GoModuleFunction.
func (f *stackFunc) Call(ctx context.Context, mod api.Module, wasmStack []uint64) {
s := &stack{goarch.NewStack(mod.Memory(), uint32(wasmStack[0]), f.paramNames, f.resultNames)}
s := &stack{goarch.NewStack(f.name, mod.Memory(), uint32(wasmStack[0]))}
f.f(ctx, mod, s)
}

View File

@@ -5,33 +5,20 @@ import (
"fmt"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/gojs/custom"
"github.com/tetratelabs/wazero/internal/gojs/goarch"
"github.com/tetratelabs/wazero/internal/wasm"
)
const (
wasmExitName = "runtime.wasmExit"
wasmWriteName = "runtime.wasmWrite"
resetMemoryDataViewName = "runtime.resetMemoryDataView"
nanotime1Name = "runtime.nanotime1"
walltimeName = "runtime.walltime"
scheduleTimeoutEventName = "runtime.scheduleTimeoutEvent" // TODO: trigger usage
clearTimeoutEventName = "runtime.clearTimeoutEvent" // TODO: trigger usage
getRandomDataName = "runtime.getRandomData"
)
// Debug has unknown use, so stubbed.
//
// See https://github.com/golang/go/blob/go1.19/src/cmd/link/internal/wasm/asm.go#L133-L138
var Debug = goarch.StubFunction("debug")
var Debug = goarch.StubFunction(custom.NameDebug)
// 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 = goarch.NewFunc(wasmExitName, wasmExit,
[]string{"code"},
[]string{},
)
var WasmExit = goarch.NewFunc(custom.NameRuntimeWasmExit, wasmExit)
func wasmExit(ctx context.Context, mod api.Module, stack goarch.Stack) {
code := stack.ParamUint32(0)
@@ -44,10 +31,7 @@ func wasmExit(ctx context.Context, mod api.Module, stack goarch.Stack) {
// runtime.writeErr. This implements `println`.
//
// See https://github.com/golang/go/blob/go1.19/src/runtime/os_js.go#L29
var WasmWrite = goarch.NewFunc(wasmWriteName, wasmWrite,
[]string{"fd", "p", "p_len"},
[]string{},
)
var WasmWrite = goarch.NewFunc(custom.NameRuntimeWasmWrite, wasmWrite)
func wasmWrite(_ context.Context, mod api.Module, stack goarch.Stack) {
fd := stack.ParamUint32(0)
@@ -70,15 +54,12 @@ func wasmWrite(_ context.Context, mod api.Module, stack goarch.Stack) {
// See https://github.com/golang/go/blob/go1.19/src/runtime/mem_js.go#L82
//
// TODO: Compiler-based memory.grow callbacks are ignored until we have a generic solution #601
var ResetMemoryDataView = goarch.NoopFunction(resetMemoryDataViewName)
var ResetMemoryDataView = goarch.NoopFunction(custom.NameRuntimeResetMemoryDataView)
// 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 = goarch.NewFunc(nanotime1Name, nanotime1,
[]string{},
[]string{"nsec"},
)
var Nanotime1 = goarch.NewFunc(custom.NameRuntimeNanotime1, nanotime1)
func nanotime1(_ context.Context, mod api.Module, stack goarch.Stack) {
nsec := mod.(*wasm.CallContext).Sys.Nanotime()
@@ -89,10 +70,7 @@ func nanotime1(_ context.Context, mod api.Module, stack goarch.Stack) {
// 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 = goarch.NewFunc(walltimeName, walltime,
[]string{},
[]string{"sec", "nsec"},
)
var Walltime = goarch.NewFunc(custom.NameRuntimeWalltime, walltime)
func walltime(_ context.Context, mod api.Module, stack goarch.Stack) {
sec, nsec := mod.(*wasm.CallContext).Sys.Walltime()
@@ -108,7 +86,7 @@ func walltime(_ context.Context, mod api.Module, stack goarch.Stack) {
// goroutine and invokes code compiled into wasm "resume".
//
// See https://github.com/golang/go/blob/go1.19/src/runtime/sys_wasm.s#L192
var ScheduleTimeoutEvent = goarch.StubFunction(scheduleTimeoutEventName)
var ScheduleTimeoutEvent = goarch.StubFunction(custom.NameRuntimeScheduleTimeoutEvent)
// ^^ stubbed because signal handling is not implemented in GOOS=js
@@ -116,7 +94,7 @@ var ScheduleTimeoutEvent = goarch.StubFunction(scheduleTimeoutEventName)
// runtime.notetsleepg used by runtime.signal_recv.
//
// See https://github.com/golang/go/blob/go1.19/src/runtime/sys_wasm.s#L196
var ClearTimeoutEvent = goarch.StubFunction(clearTimeoutEventName)
var ClearTimeoutEvent = goarch.StubFunction(custom.NameRuntimeClearTimeoutEvent)
// ^^ stubbed because signal handling is not implemented in GOOS=js
@@ -124,10 +102,7 @@ var ClearTimeoutEvent = goarch.StubFunction(clearTimeoutEventName)
// for runtime.fastrand.
//
// See https://github.com/golang/go/blob/go1.19/src/runtime/sys_wasm.s#L200
var GetRandomData = goarch.NewFunc(getRandomDataName, getRandomData,
[]string{"r", "r_len"},
[]string{},
)
var GetRandomData = goarch.NewFunc(custom.NameRuntimeGetRandomData, getRandomData)
func getRandomData(_ context.Context, mod api.Module, stack goarch.Stack) {
r := stack.ParamBytes(mod.Memory(), 0 /*, 1 */)

View File

@@ -9,36 +9,18 @@ import (
"syscall"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/gojs/custom"
"github.com/tetratelabs/wazero/internal/gojs/goarch"
"github.com/tetratelabs/wazero/internal/gojs/goos"
"github.com/tetratelabs/wazero/internal/wasm"
"github.com/tetratelabs/wazero/sys"
)
const (
finalizeRefName = "syscall/js.finalizeRef"
stringValName = "syscall/js.stringVal"
valueGetName = "syscall/js.valueGet"
valueSetName = "syscall/js.valueSet"
valueDeleteName = "syscall/js.valueDelete" // stubbed
valueIndexName = "syscall/js.valueIndex"
valueSetIndexName = "syscall/js.valueSetIndex" // stubbed
valueCallName = "syscall/js.valueCall"
valueInvokeName = "syscall/js.valueInvoke" // stubbed
valueNewName = "syscall/js.valueNew"
valueLengthName = "syscall/js.valueLength"
valuePrepareStringName = "syscall/js.valuePrepareString"
valueLoadStringName = "syscall/js.valueLoadString"
valueInstanceOfName = "syscall/js.valueInstanceOf" // stubbed
copyBytesToGoName = "syscall/js.copyBytesToGo"
copyBytesToJSName = "syscall/js.copyBytesToJS"
)
// 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 = goos.NewFunc(finalizeRefName, finalizeRef, []string{"r"}, nil)
var FinalizeRef = goos.NewFunc(custom.NameSyscallFinalizeRef, finalizeRef)
func finalizeRef(ctx context.Context, _ api.Module, stack goos.Stack) {
r := stack.ParamRef(0)
@@ -53,7 +35,7 @@ func finalizeRef(ctx context.Context, _ api.Module, stack goos.Stack) {
//
// 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 = goos.NewFunc(stringValName, stringVal, []string{"x", "x_len"}, []string{"r"})
var StringVal = goos.NewFunc(custom.NameSyscallStringVal, stringVal)
func stringVal(ctx context.Context, mod api.Module, stack goos.Stack) {
x := stack.ParamString(mod.Memory(), 0)
@@ -69,10 +51,7 @@ func stringVal(ctx context.Context, mod api.Module, stack goos.Stack) {
//
// 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 = goos.NewFunc(valueGetName, valueGet,
[]string{"v", "p", "p_len"},
[]string{"r"},
)
var ValueGet = goos.NewFunc(custom.NameSyscallValueGet, valueGet)
func valueGet(ctx context.Context, mod api.Module, stack goos.Stack) {
v := stack.ParamVal(ctx, 0, loadValue)
@@ -104,10 +83,7 @@ func valueGet(ctx context.Context, mod api.Module, stack goos.Stack) {
//
// 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 = goos.NewFunc(valueSetName, valueSet,
[]string{"v", "p", "p_len", "x"},
[]string{},
)
var ValueSet = goos.NewFunc(custom.NameSyscallValueSet, valueSet)
func valueSet(ctx context.Context, mod api.Module, stack goos.Stack) {
v := stack.ParamVal(ctx, 0, loadValue)
@@ -138,7 +114,7 @@ func valueSet(ctx context.Context, mod api.Module, stack goos.Stack) {
// ValueDelete is stubbed as it isn't used in Go's main source tree.
//
// See https://github.com/golang/go/blob/go1.19/src/syscall/js/js.go#L321
var ValueDelete = goarch.StubFunction(valueDeleteName)
var ValueDelete = goarch.StubFunction(custom.NameSyscallValueDelete)
// ValueIndex implements js.valueIndex, which is used to load a js.Value property
// by index, e.g. `v.Index(0)`. Notably, this is used by js.handleEvent to read
@@ -146,10 +122,7 @@ var ValueDelete = goarch.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 = goos.NewFunc(valueIndexName, valueIndex,
[]string{"v", "i"},
[]string{"r"},
)
var ValueIndex = goos.NewFunc(custom.NameSyscallValueIndex, valueIndex)
func valueIndex(ctx context.Context, _ api.Module, stack goos.Stack) {
v := stack.ParamVal(ctx, 0, loadValue)
@@ -165,17 +138,14 @@ func valueIndex(ctx context.Context, _ api.Module, stack goos.Stack) {
// []interface{}, which doesn't appear to occur in Go's source tree.
//
// See https://github.com/golang/go/blob/go1.19/src/syscall/js/js.go#L348
var ValueSetIndex = goarch.StubFunction(valueSetIndexName)
var ValueSetIndex = goarch.StubFunction(custom.NameSyscallValueSetIndex)
// ValueCall implements js.valueCall, which is used to call a js.Value function
// by name, e.g. `document.Call("createElement", "div")`.
//
// 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 = goos.NewFunc(valueCallName, valueCall,
[]string{"v", "m", "m_len", "args", "args_len", "padding"},
[]string{"res", "ok"},
)
var ValueCall = goos.NewFunc(custom.NameSyscallValueCall, valueCall)
func valueCall(ctx context.Context, mod api.Module, stack goos.Stack) {
mem := mod.Memory()
@@ -207,17 +177,14 @@ func valueCall(ctx context.Context, mod api.Module, stack goos.Stack) {
// ValueInvoke is stubbed as it isn't used in Go's main source tree.
//
// See https://github.com/golang/go/blob/go1.19/src/syscall/js/js.go#L413
var ValueInvoke = goarch.StubFunction(valueInvokeName)
var ValueInvoke = goarch.StubFunction(custom.NameSyscallValueInvoke)
// ValueNew implements js.valueNew, which is used to call a js.Value, e.g.
// `array.New(2)`.
//
// 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 = goos.NewFunc(valueNewName, valueNew,
[]string{"v", "args", "args_len", "padding"},
[]string{"res", "ok"},
)
var ValueNew = goos.NewFunc(custom.NameSyscallValueNew, valueNew)
func valueNew(ctx context.Context, mod api.Module, stack goos.Stack) {
mem := mod.Memory()
@@ -271,10 +238,7 @@ func valueNew(ctx context.Context, mod api.Module, stack goos.Stack) {
//
// 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 = goos.NewFunc(valueLengthName, valueLength,
[]string{"v"},
[]string{"len"},
)
var ValueLength = goos.NewFunc(custom.NameSyscallValueLength, valueLength)
func valueLength(ctx context.Context, _ api.Module, stack goos.Stack) {
v := stack.ParamVal(ctx, 0, loadValue)
@@ -291,10 +255,7 @@ func valueLength(ctx context.Context, _ api.Module, stack goos.Stack) {
//
// 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 = goos.NewFunc(valuePrepareStringName, valuePrepareString,
[]string{"v"},
[]string{"str", "length"},
)
var ValuePrepareString = goos.NewFunc(custom.NameSyscallValuePrepareString, valuePrepareString)
func valuePrepareString(ctx context.Context, _ api.Module, stack goos.Stack) {
v := stack.ParamVal(ctx, 0, loadValue)
@@ -314,10 +275,7 @@ func valuePrepareString(ctx context.Context, _ api.Module, stack goos.Stack) {
// 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 = goos.NewFunc(valueLoadStringName, valueLoadString,
[]string{"v", "b", "b_len"},
[]string{},
)
var ValueLoadString = goos.NewFunc(custom.NameSyscallValueLoadString, valueLoadString)
func valueLoadString(ctx context.Context, mod api.Module, stack goos.Stack) {
v := stack.ParamVal(ctx, 0, loadValue)
@@ -330,7 +288,7 @@ func valueLoadString(ctx context.Context, mod api.Module, stack goos.Stack) {
// ValueInstanceOf is stubbed as it isn't used in Go's main source tree.
//
// See https://github.com/golang/go/blob/go1.19/src/syscall/js/js.go#L543
var ValueInstanceOf = goarch.StubFunction(valueInstanceOfName)
var ValueInstanceOf = goarch.StubFunction(custom.NameSyscallValueInstanceOf)
// CopyBytesToGo copies a JavaScript managed byte array to linear memory.
// For example, this is used to read an HTTP response body.
@@ -342,10 +300,7 @@ var ValueInstanceOf = goarch.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 = goos.NewFunc(copyBytesToGoName, copyBytesToGo,
[]string{"dst", "dst_len", "padding", "src"},
[]string{"n", "ok"},
)
var CopyBytesToGo = goos.NewFunc(custom.NameSyscallCopyBytesToGo, copyBytesToGo)
func copyBytesToGo(ctx context.Context, mod api.Module, stack goos.Stack) {
dst := stack.ParamBytes(mod.Memory(), 0 /*, 1 */)
@@ -373,10 +328,7 @@ func copyBytesToGo(ctx context.Context, mod api.Module, stack goos.Stack) {
//
// 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 = goos.NewFunc(copyBytesToJSName, copyBytesToJS,
[]string{"dst", "src", "src_len", "padding"},
[]string{"n", "ok"},
)
var CopyBytesToJS = goos.NewFunc(custom.NameSyscallCopyBytesToJS, copyBytesToJS)
func copyBytesToJS(ctx context.Context, mod api.Module, stack goos.Stack) {
dst := stack.ParamVal(ctx, 0, loadValue)

View File

@@ -4,6 +4,7 @@ import (
"fmt"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/gojs/custom"
"github.com/tetratelabs/wazero/internal/wasm"
)
@@ -17,12 +18,21 @@ func MustWrite(mem api.Memory, fieldName string, offset uint32, val []byte) {
// MustRead is like api.Memory except that it panics if the offset and
// byteCount are out of range.
func MustRead(mem api.Memory, fieldName string, offset, byteCount uint32) []byte {
func MustRead(mem api.Memory, funcName string, paramIdx int, offset, byteCount uint32) []byte {
buf, ok := mem.Read(offset, byteCount)
if !ok {
panic(fmt.Errorf("out of memory reading %s", fieldName))
if ok {
return buf
}
return buf
var paramName string
if names, ok := custom.NameSection[funcName]; ok {
if paramIdx < len(names.ParamNames) {
paramName = names.ParamNames[paramIdx]
}
}
if paramName == "" {
paramName = fmt.Sprintf("%s param[%d]", funcName, paramIdx)
}
panic(fmt.Errorf("out of memory reading %s", paramName))
}
func NewFunc(name string, goFunc api.GoModuleFunc) *wasm.HostFunc {

View File

@@ -0,0 +1,75 @@
package util
import (
"testing"
"github.com/tetratelabs/wazero/internal/gojs/custom"
"github.com/tetratelabs/wazero/internal/testing/require"
"github.com/tetratelabs/wazero/internal/wasm"
)
func TestMustRead(t *testing.T) {
mem := &wasm.MemoryInstance{Buffer: []byte{1, 2, 3, 4, 5, 6, 7, 8}, Min: 1}
tests := []struct {
name string
funcName string
paramIdx int
offset, byteCount uint32
expected []byte
expectedPanic string
}{
{
name: "read nothing",
},
{
name: "read all",
offset: 0,
byteCount: 8,
expected: []byte{1, 2, 3, 4, 5, 6, 7, 8},
},
{
name: "read some",
offset: 4,
byteCount: 2,
expected: []byte{5, 6},
},
{
name: "read too many",
funcName: custom.NameSyscallCopyBytesToGo,
offset: 4,
byteCount: 5,
expectedPanic: "out of memory reading dst",
},
{
name: "read too many - function not in names",
funcName: "not_in_names",
offset: 4,
byteCount: 5,
expectedPanic: "out of memory reading not_in_names param[0]",
},
{
name: "read too many - in names, but no params",
funcName: custom.NameDebug,
offset: 4,
byteCount: 5,
expectedPanic: "out of memory reading debug param[0]",
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
if tc.expectedPanic != "" {
err := require.CapturePanic(func() {
MustRead(mem, tc.funcName, tc.paramIdx, tc.offset, tc.byteCount)
})
require.EqualError(t, err, tc.expectedPanic)
} else {
buf := MustRead(mem, tc.funcName, tc.paramIdx, tc.offset, tc.byteCount)
require.Equal(t, tc.expected, buf)
}
})
}
}