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:
@@ -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))
|
||||
|
||||
186
internal/gojs/custom/names.go
Normal file
186
internal/gojs/custom/names.go
Normal 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"},
|
||||
},
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 */)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
75
internal/gojs/util/util_test.go
Normal file
75
internal/gojs/util/util_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user