gojs: refactors GOOS and GOARCH specific code into their own packages (#959)
This refactors GOOS and GOARCH specific code into their own packages. This allows logging interceptors to be built without cyclic package dependencies. Signed-off-by: Adrian Cole <adrian@tetrate.io>
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
package gojs
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/internal/gojs/util"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
|
||||
@@ -14,6 +16,8 @@ const (
|
||||
wasmMinDataAddr = endOfPageZero + maxArgsAndEnviron // ld.wasmMinDataAddr
|
||||
)
|
||||
|
||||
var le = binary.LittleEndian
|
||||
|
||||
// WriteArgsAndEnviron writes arguments and environment variables to memory, so
|
||||
// they can be read by main, Go compiles as the function export "run".
|
||||
func WriteArgsAndEnviron(mod api.Module) (argc, argv uint32, err error) {
|
||||
@@ -28,14 +32,16 @@ func WriteArgsAndEnviron(mod api.Module) (argc, argv uint32, err error) {
|
||||
strPtr := func(val []byte, field string, i int) (ptr uint32) {
|
||||
// TODO: return err and format "%s[%d], field, i"
|
||||
ptr = offset
|
||||
mustWrite(mem, field, offset, append(val, 0))
|
||||
util.MustWrite(mem, field, offset, append(val, 0))
|
||||
offset += uint32(len(val) + 1)
|
||||
if pad := offset % 8; pad != 0 {
|
||||
offset += 8 - pad
|
||||
}
|
||||
return
|
||||
}
|
||||
argvPtrs := make([]uint32, 0, len(args)+1+len(environ)+1)
|
||||
|
||||
argvPtrLen := len(args) + 1 + len(environ) + 1
|
||||
argvPtrs := make([]uint32, 0, argvPtrLen)
|
||||
for i, arg := range args {
|
||||
argvPtrs = append(argvPtrs, strPtr(arg, "args", i))
|
||||
}
|
||||
@@ -47,14 +53,18 @@ func WriteArgsAndEnviron(mod api.Module) (argc, argv uint32, err error) {
|
||||
argvPtrs = append(argvPtrs, 0)
|
||||
|
||||
argv = offset
|
||||
for _, ptr := range argvPtrs {
|
||||
// TODO: return err and format "argvPtrs[%d], i"
|
||||
mustWriteUint64Le(mem, "argvPtrs[i]", offset, uint64(ptr))
|
||||
offset += 8
|
||||
}
|
||||
|
||||
if offset >= wasmMinDataAddr {
|
||||
stop := uint32(argvPtrLen << 3) // argvPtrLen * 8
|
||||
if offset+stop >= wasmMinDataAddr {
|
||||
err = errors.New("total length of command line and environment variables exceeds limit")
|
||||
}
|
||||
|
||||
buf := util.MustRead(mem, "argvPtrs", argv, stop)
|
||||
pos := uint32(0)
|
||||
for _, ptr := range argvPtrs {
|
||||
le.PutUint64(buf[pos:], uint64(ptr))
|
||||
pos += 8
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -2,21 +2,15 @@ package gojs
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/tetratelabs/wazero/internal/gojs/goos"
|
||||
)
|
||||
|
||||
const (
|
||||
// predefined
|
||||
|
||||
idValueNaN uint32 = iota
|
||||
idValueZero
|
||||
idValueNull
|
||||
idValueTrue
|
||||
idValueFalse
|
||||
idValueGlobal
|
||||
idJsGo
|
||||
|
||||
// The below are derived from analyzing `*_js.go` source.
|
||||
idObjectConstructor
|
||||
idObjectConstructor uint32 = goos.NextID + iota
|
||||
idArrayConstructor
|
||||
idJsProcess
|
||||
idJsfs
|
||||
@@ -31,25 +25,17 @@ const (
|
||||
)
|
||||
|
||||
const (
|
||||
refValueUndefined = ref(0)
|
||||
refValueNaN = (nanHead|ref(typeFlagNone))<<32 | ref(idValueNaN)
|
||||
refValueZero = (nanHead|ref(typeFlagNone))<<32 | ref(idValueZero)
|
||||
refValueNull = (nanHead|ref(typeFlagNone))<<32 | ref(idValueNull)
|
||||
refValueTrue = (nanHead|ref(typeFlagNone))<<32 | ref(idValueTrue)
|
||||
refValueFalse = (nanHead|ref(typeFlagNone))<<32 | ref(idValueFalse)
|
||||
refValueGlobal = (nanHead|ref(typeFlagObject))<<32 | ref(idValueGlobal)
|
||||
refJsGo = (nanHead|ref(typeFlagObject))<<32 | ref(idJsGo)
|
||||
refObjectConstructor = (nanHead|ref(typeFlagFunction))<<32 | ref(idObjectConstructor)
|
||||
refArrayConstructor = (nanHead|ref(typeFlagFunction))<<32 | ref(idArrayConstructor)
|
||||
refJsProcess = (nanHead|ref(typeFlagObject))<<32 | ref(idJsProcess)
|
||||
refJsfs = (nanHead|ref(typeFlagObject))<<32 | ref(idJsfs)
|
||||
refJsfsConstants = (nanHead|ref(typeFlagObject))<<32 | ref(idJsfsConstants)
|
||||
refUint8ArrayConstructor = (nanHead|ref(typeFlagFunction))<<32 | ref(idUint8ArrayConstructor)
|
||||
refJsCrypto = (nanHead|ref(typeFlagFunction))<<32 | ref(idJsCrypto)
|
||||
refJsDateConstructor = (nanHead|ref(typeFlagFunction))<<32 | ref(idJsDateConstructor)
|
||||
refJsDate = (nanHead|ref(typeFlagObject))<<32 | ref(idJsDate)
|
||||
refHttpFetch = (nanHead|ref(typeFlagFunction))<<32 | ref(idHttpFetch)
|
||||
refHttpHeadersConstructor = (nanHead|ref(typeFlagFunction))<<32 | ref(idHttpHeaders)
|
||||
refObjectConstructor = (goos.NanHead|goos.Ref(goos.TypeFlagFunction))<<32 | goos.Ref(idObjectConstructor)
|
||||
refArrayConstructor = (goos.NanHead|goos.Ref(goos.TypeFlagFunction))<<32 | goos.Ref(idArrayConstructor)
|
||||
refJsProcess = (goos.NanHead|goos.Ref(goos.TypeFlagObject))<<32 | goos.Ref(idJsProcess)
|
||||
refJsfs = (goos.NanHead|goos.Ref(goos.TypeFlagObject))<<32 | goos.Ref(idJsfs)
|
||||
refJsfsConstants = (goos.NanHead|goos.Ref(goos.TypeFlagObject))<<32 | goos.Ref(idJsfsConstants)
|
||||
refUint8ArrayConstructor = (goos.NanHead|goos.Ref(goos.TypeFlagFunction))<<32 | goos.Ref(idUint8ArrayConstructor)
|
||||
refJsCrypto = (goos.NanHead|goos.Ref(goos.TypeFlagFunction))<<32 | goos.Ref(idJsCrypto)
|
||||
refJsDateConstructor = (goos.NanHead|goos.Ref(goos.TypeFlagFunction))<<32 | goos.Ref(idJsDateConstructor)
|
||||
refJsDate = (goos.NanHead|goos.Ref(goos.TypeFlagObject))<<32 | goos.Ref(idJsDate)
|
||||
refHttpFetch = (goos.NanHead|goos.Ref(goos.TypeFlagFunction))<<32 | goos.Ref(idHttpFetch)
|
||||
refHttpHeadersConstructor = (goos.NanHead|goos.Ref(goos.TypeFlagFunction))<<32 | goos.Ref(idHttpHeaders)
|
||||
)
|
||||
|
||||
// newJsGlobal = js.Global() // js.go init
|
||||
@@ -58,7 +44,7 @@ func newJsGlobal(rt http.RoundTripper) *jsVal {
|
||||
if rt != nil {
|
||||
fetchProperty = refHttpFetch
|
||||
}
|
||||
return newJsVal(refValueGlobal, "global").
|
||||
return newJsVal(goos.RefValueGlobal, "global").
|
||||
addProperties(map[string]interface{}{
|
||||
"Object": objectConstructor,
|
||||
"Array": arrayConstructor,
|
||||
@@ -91,8 +77,8 @@ var (
|
||||
// jsProcess = js.Global().Get("process") // fs_js.go init
|
||||
jsProcess = newJsVal(refJsProcess, "process").
|
||||
addProperties(map[string]interface{}{
|
||||
"pid": float64(1), // Get("pid").Int() in syscall_js.go for syscall.Getpid
|
||||
"ppid": refValueZero, // Get("ppid").Int() in syscall_js.go for syscall.Getppid
|
||||
"pid": float64(1), // Get("pid").Int() in syscall_js.go for syscall.Getpid
|
||||
"ppid": goos.RefValueZero, // Get("ppid").Int() in syscall_js.go for syscall.Getppid
|
||||
}).
|
||||
addFunction("cwd", &cwd{}). // syscall.Cwd in fs_js.go
|
||||
addFunction("chdir", &chdir{}). // syscall.Chdir in fs_js.go
|
||||
|
||||
@@ -26,8 +26,7 @@ func compileAndRun(ctx context.Context, arg string, config wazero.ModuleConfig)
|
||||
|
||||
ns := rt.NewNamespace(ctx)
|
||||
builder := rt.NewHostModuleBuilder("go")
|
||||
gojs.NewFunctionExporter().
|
||||
ExportFunctions(builder)
|
||||
gojs.NewFunctionExporter().ExportFunctions(builder)
|
||||
if _, err = builder.Instantiate(ctx, ns); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ var jsCrypto = newJsVal(refJsCrypto, "crypto").
|
||||
type getRandomValues struct{}
|
||||
|
||||
// invoke implements jsFn.invoke
|
||||
func (*getRandomValues) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
|
||||
func (*getRandomValues) invoke(_ context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
|
||||
randSource := mod.(*wasm.CallContext).Sys.RandSource()
|
||||
|
||||
r := args[0].(*byteArray)
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"syscall"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/internal/gojs/goos"
|
||||
internalsys "github.com/tetratelabs/wazero/internal/sys"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
@@ -252,7 +253,7 @@ func (*jsfsWrite) invoke(ctx context.Context, mod api.Module, args ...interface{
|
||||
n, err := syscallWrite(mod, fd, fOffset, buf.slice[offset:offset+byteCount])
|
||||
return callback.invoke(ctx, mod, refJsfs, err, n) // note: error first
|
||||
}
|
||||
return callback.invoke(ctx, mod, refJsfs, nil, refValueZero)
|
||||
return callback.invoke(ctx, mod, refJsfs, nil, goos.RefValueZero)
|
||||
}
|
||||
|
||||
// syscallWrite is like syscall.Write
|
||||
@@ -313,15 +314,15 @@ func syscallReaddir(_ context.Context, mod api.Module, name string) (*objectArra
|
||||
type returnZero struct{}
|
||||
|
||||
// invoke implements jsFn.invoke
|
||||
func (*returnZero) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
|
||||
return refValueZero, nil
|
||||
func (*returnZero) invoke(context.Context, api.Module, ...interface{}) (interface{}, error) {
|
||||
return goos.RefValueZero, nil
|
||||
}
|
||||
|
||||
type returnSliceOfZero struct{}
|
||||
|
||||
// invoke implements jsFn.invoke
|
||||
func (*returnSliceOfZero) invoke(context.Context, api.Module, ...interface{}) (interface{}, error) {
|
||||
return &objectArray{slice: []interface{}{refValueZero}}, nil
|
||||
return &objectArray{slice: []interface{}{goos.RefValueZero}}, nil
|
||||
}
|
||||
|
||||
type returnArg0 struct{}
|
||||
@@ -418,7 +419,7 @@ func (s *jsSt) get(_ context.Context, propertyKey string) interface{} {
|
||||
}
|
||||
|
||||
// call implements jsCall.call
|
||||
func (s *jsSt) call(ctx context.Context, mod api.Module, this ref, method string, args ...interface{}) (interface{}, error) {
|
||||
func (s *jsSt) call(_ context.Context, _ api.Module, _ goos.Ref, method string, _ ...interface{}) (interface{}, error) {
|
||||
if method == "isDirectory" {
|
||||
return s.isDir, nil
|
||||
}
|
||||
|
||||
192
internal/gojs/goarch/wasm.go
Normal file
192
internal/gojs/goarch/wasm.go
Normal file
@@ -0,0 +1,192 @@
|
||||
// Package goarch isolates code from runtime.GOARCH=wasm in a way that avoids
|
||||
// cyclic dependencies when re-used from other packages.
|
||||
package goarch
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/internal/gojs/util"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
|
||||
// StubFunction stubs functions not used in Go's main source tree.
|
||||
// This traps (unreachable opcode) to ensure the function is never called.
|
||||
func StubFunction(name string) *wasm.HostFunc {
|
||||
return &wasm.HostFunc{
|
||||
ExportNames: []string{name},
|
||||
Name: name,
|
||||
ParamTypes: []wasm.ValueType{wasm.ValueTypeI32},
|
||||
ParamNames: []string{"sp"},
|
||||
Code: &wasm.Code{IsHostFunction: true, Body: []byte{wasm.OpcodeUnreachable, wasm.OpcodeEnd}},
|
||||
}
|
||||
}
|
||||
|
||||
func NoopFunction(name string) *wasm.HostFunc {
|
||||
return &wasm.HostFunc{
|
||||
ExportNames: []string{name},
|
||||
Name: name,
|
||||
ParamTypes: []wasm.ValueType{wasm.ValueTypeI32},
|
||||
ParamNames: []string{"sp"},
|
||||
Code: &wasm.Code{IsHostFunction: true, Body: []byte{wasm.OpcodeEnd}},
|
||||
}
|
||||
}
|
||||
|
||||
var le = binary.LittleEndian
|
||||
|
||||
type Stack interface {
|
||||
Param(i int) uint64
|
||||
ParamName(i int) string
|
||||
|
||||
// ParamBytes reads a byte slice, given its memory offset and length (stack
|
||||
// positions i, i+1)
|
||||
ParamBytes(mem api.Memory, i int) []byte
|
||||
|
||||
// ParamString reads a string, given its memory offset and length (stack
|
||||
// positions i, i+1)
|
||||
ParamString(mem api.Memory, i int) string
|
||||
|
||||
ParamUint32(i int) uint32
|
||||
|
||||
// Refresh the stack from the current stack pointer (SP).
|
||||
//
|
||||
// Note: This is needed prior to storing a value when in an operation that
|
||||
// can trigger a Go event handler.
|
||||
Refresh(api.Module)
|
||||
|
||||
ResultName(i int) string
|
||||
|
||||
SetResult(i int, v uint64)
|
||||
|
||||
SetResultBool(i int, v bool)
|
||||
|
||||
SetResultI32(i int, v int32)
|
||||
|
||||
SetResultI64(i int, v int64)
|
||||
|
||||
SetResultUint32(i int, v uint32)
|
||||
}
|
||||
|
||||
func NewStack(mem api.Memory, sp uint32, paramNames, resultNames []string) Stack {
|
||||
s := &stack{paramNames: paramNames, resultNames: resultNames}
|
||||
s.refresh(mem, sp)
|
||||
return s
|
||||
}
|
||||
|
||||
type stack struct {
|
||||
paramNames, resultNames []string
|
||||
buf []byte
|
||||
}
|
||||
|
||||
// Param implements Stack.Param
|
||||
func (s *stack) Param(i int) (res uint64) {
|
||||
pos := i << 3
|
||||
res = le.Uint64(s.buf[pos:])
|
||||
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)
|
||||
}
|
||||
|
||||
// ParamString implements Stack.ParamString
|
||||
func (s *stack) ParamString(mem api.Memory, i int) string {
|
||||
return string(s.ParamBytes(mem, i))
|
||||
}
|
||||
|
||||
// ParamUint32 implements Stack.ParamUint32
|
||||
func (s *stack) ParamUint32(i int) uint32 {
|
||||
return uint32(s.Param(i))
|
||||
}
|
||||
|
||||
// Refresh implements Stack.Refresh
|
||||
func (s *stack) Refresh(mod api.Module) {
|
||||
s.refresh(mod.Memory(), getSP(mod))
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// SetResult implements Stack.SetResult
|
||||
func (s *stack) SetResult(i int, v uint64) {
|
||||
pos := (len(s.paramNames) + 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 {
|
||||
s.SetResultUint32(i, 1)
|
||||
} else {
|
||||
s.SetResultUint32(i, 0)
|
||||
}
|
||||
}
|
||||
|
||||
// SetResultI32 implements Stack.SetResultI32
|
||||
func (s *stack) SetResultI32(i int, v int32) {
|
||||
s.SetResult(i, uint64(v))
|
||||
}
|
||||
|
||||
// SetResultI64 implements Stack.SetResultI64
|
||||
func (s *stack) SetResultI64(i int, v int64) {
|
||||
s.SetResult(i, uint64(v))
|
||||
}
|
||||
|
||||
// SetResultUint32 implements Stack.SetResultUint32
|
||||
func (s *stack) SetResultUint32(i int, v uint32) {
|
||||
s.SetResult(i, uint64(v))
|
||||
}
|
||||
|
||||
// getSP gets the stack pointer, which is needed prior to storing a value when
|
||||
// in an operation that can trigger a Go event handler.
|
||||
//
|
||||
// See https://github.com/golang/go/blob/go1.19/misc/wasm/wasm_exec.js#L210-L213
|
||||
func getSP(mod api.Module) uint32 {
|
||||
// Cheat by reading global[0] directly instead of through a function proxy.
|
||||
// https://github.com/golang/go/blob/go1.19/src/runtime/rt0_js_wasm.s#L87-L90
|
||||
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)
|
||||
}
|
||||
|
||||
type Func func(context.Context, api.Module, Stack)
|
||||
|
||||
type stackFunc struct {
|
||||
f Func
|
||||
paramNames, resultNames []string
|
||||
}
|
||||
|
||||
// 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)
|
||||
f.f(ctx, mod, s)
|
||||
}
|
||||
221
internal/gojs/goos/js.go
Normal file
221
internal/gojs/goos/js.go
Normal file
@@ -0,0 +1,221 @@
|
||||
// Package goos isolates code from runtime.GOOS=js in a way that avoids cyclic
|
||||
// dependencies when re-used from other packages.
|
||||
package goos
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/internal/gojs/goarch"
|
||||
"github.com/tetratelabs/wazero/internal/gojs/util"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
|
||||
// Ref is used to identify a JavaScript value, since the value itself cannot
|
||||
// be passed to WebAssembly.
|
||||
//
|
||||
// The JavaScript value "undefined" is represented by the value 0.
|
||||
//
|
||||
// A JavaScript number (64-bit float, except 0 and NaN) is represented by its
|
||||
// IEEE 754 binary representation.
|
||||
//
|
||||
// All other values are represented as an IEEE 754 binary representation of NaN
|
||||
// with bits 0-31 used as an ID and bits 32-34 used to differentiate between
|
||||
// string, symbol, function and object.
|
||||
type Ref uint64
|
||||
|
||||
const (
|
||||
IdValueNaN uint32 = iota
|
||||
IdValueZero
|
||||
IdValueNull
|
||||
IdValueTrue
|
||||
IdValueFalse
|
||||
IdValueGlobal
|
||||
IdJsGo
|
||||
NextID
|
||||
)
|
||||
|
||||
const (
|
||||
RefValueUndefined = Ref(0)
|
||||
RefValueNaN = (NanHead|Ref(TypeFlagNone))<<32 | Ref(IdValueNaN)
|
||||
RefValueZero = (NanHead|Ref(TypeFlagNone))<<32 | Ref(IdValueZero)
|
||||
RefValueNull = (NanHead|Ref(TypeFlagNone))<<32 | Ref(IdValueNull)
|
||||
RefValueTrue = (NanHead|Ref(TypeFlagNone))<<32 | Ref(IdValueTrue)
|
||||
RefValueFalse = (NanHead|Ref(TypeFlagNone))<<32 | Ref(IdValueFalse)
|
||||
RefValueGlobal = (NanHead|Ref(TypeFlagObject))<<32 | Ref(IdValueGlobal)
|
||||
RefJsGo = (NanHead|Ref(TypeFlagObject))<<32 | Ref(IdJsGo)
|
||||
)
|
||||
|
||||
type TypeFlag byte
|
||||
|
||||
// the type flags need to be in sync with gojs.js
|
||||
const (
|
||||
TypeFlagNone TypeFlag = iota
|
||||
TypeFlagObject
|
||||
TypeFlagString
|
||||
TypeFlagSymbol // nolint
|
||||
TypeFlagFunction
|
||||
)
|
||||
|
||||
func ValueRef(id uint32, typeFlag TypeFlag) Ref {
|
||||
return (NanHead|Ref(typeFlag))<<32 | Ref(id)
|
||||
}
|
||||
|
||||
var le = binary.LittleEndian
|
||||
|
||||
// NanHead are the upper 32 bits of a Ref which are set if the value is not encoded as an IEEE 754 number (see above).
|
||||
const NanHead = 0x7FF80000
|
||||
|
||||
func (ref Ref) ParseFloat() (v float64, ok bool) {
|
||||
if (ref>>32)&NanHead != NanHead {
|
||||
v = api.DecodeF64(uint64(ref))
|
||||
ok = true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type ValLoader func(context.Context, Ref) interface{}
|
||||
|
||||
type Stack interface {
|
||||
goarch.Stack
|
||||
|
||||
ParamRef(i int) Ref
|
||||
|
||||
ParamRefs(mem api.Memory, i int) []Ref
|
||||
|
||||
ParamVal(ctx context.Context, i int, loader ValLoader) interface{}
|
||||
|
||||
ParamVals(ctx context.Context, mem api.Memory, i int, loader ValLoader) []interface{}
|
||||
|
||||
SetResultRef(i int, v Ref)
|
||||
}
|
||||
|
||||
type stack struct {
|
||||
s goarch.Stack
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// ParamRef implements Stack.ParamRef
|
||||
func (s *stack) ParamRef(i int) Ref {
|
||||
return Ref(s.s.Param(i))
|
||||
}
|
||||
|
||||
// ParamRefs implements Stack.ParamRefs
|
||||
func (s *stack) ParamRefs(mem api.Memory, i int) []Ref {
|
||||
offset := s.s.ParamUint32(i)
|
||||
size := s.s.ParamUint32(i + 1)
|
||||
byteCount := size << 3 // size * 8
|
||||
|
||||
result := make([]Ref, 0, size)
|
||||
|
||||
buf := util.MustRead(mem, s.s.ParamName(i), offset, byteCount)
|
||||
for pos := uint32(0); pos < byteCount; pos += 8 {
|
||||
ref := Ref(le.Uint64(buf[pos:]))
|
||||
result = append(result, ref)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// ParamString implements the same method as documented on goarch.Stack
|
||||
func (s *stack) ParamString(mem api.Memory, i int) string {
|
||||
return s.s.ParamString(mem, i)
|
||||
}
|
||||
|
||||
// ParamUint32 implements the same method as documented on goarch.Stack
|
||||
func (s *stack) ParamUint32(i int) uint32 {
|
||||
return s.s.ParamUint32(i)
|
||||
}
|
||||
|
||||
// ParamVal implements Stack.ParamVal
|
||||
func (s *stack) ParamVal(ctx context.Context, i int, loader ValLoader) interface{} {
|
||||
ref := s.ParamRef(i)
|
||||
return loader(ctx, ref)
|
||||
}
|
||||
|
||||
// ParamVals implements Stack.ParamVals
|
||||
func (s *stack) ParamVals(ctx context.Context, mem api.Memory, i int, loader ValLoader) []interface{} {
|
||||
offset := s.s.ParamUint32(i)
|
||||
size := s.s.ParamUint32(i + 1)
|
||||
byteCount := size << 3 // size * 8
|
||||
|
||||
result := make([]interface{}, 0, size)
|
||||
|
||||
buf := util.MustRead(mem, s.s.ParamName(i), offset, byteCount)
|
||||
for pos := uint32(0); pos < byteCount; pos += 8 {
|
||||
ref := Ref(le.Uint64(buf[pos:]))
|
||||
result = append(result, loader(ctx, ref))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Refresh implements the same method as documented on goarch.Stack
|
||||
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)
|
||||
}
|
||||
|
||||
// SetResultBool implements the same method as documented on goarch.Stack
|
||||
func (s *stack) SetResultBool(i int, v bool) {
|
||||
s.s.SetResultBool(i, v)
|
||||
}
|
||||
|
||||
// SetResultI32 implements the same method as documented on goarch.Stack
|
||||
func (s *stack) SetResultI32(i int, v int32) {
|
||||
s.s.SetResultI32(i, v)
|
||||
}
|
||||
|
||||
// SetResultI64 implements the same method as documented on goarch.Stack
|
||||
func (s *stack) SetResultI64(i int, v int64) {
|
||||
s.s.SetResultI64(i, v)
|
||||
}
|
||||
|
||||
// SetResultRef implements Stack.SetResultRef
|
||||
func (s *stack) SetResultRef(i int, v Ref) {
|
||||
s.s.SetResult(i, uint64(v))
|
||||
}
|
||||
|
||||
// SetResultUint32 implements the same method as documented on goarch.Stack
|
||||
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)
|
||||
}
|
||||
|
||||
type Func func(context.Context, api.Module, Stack)
|
||||
|
||||
type stackFunc struct {
|
||||
f Func
|
||||
paramNames, resultNames []string
|
||||
}
|
||||
|
||||
// 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)}
|
||||
f.f(ctx, mod, s)
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"sort"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/internal/gojs/goos"
|
||||
)
|
||||
|
||||
// headersConstructor = Get("Headers").New() // http.Roundtrip && "fetch"
|
||||
@@ -57,7 +58,7 @@ type fetchPromise struct {
|
||||
}
|
||||
|
||||
// call implements jsCall.call
|
||||
func (p *fetchPromise) call(ctx context.Context, mod api.Module, this ref, method string, args ...interface{}) (interface{}, error) {
|
||||
func (p *fetchPromise) call(ctx context.Context, mod api.Module, this goos.Ref, method string, args ...interface{}) (interface{}, error) {
|
||||
if method == "then" {
|
||||
if res, err := p.rt.RoundTrip(p.req); err != nil {
|
||||
failure := args[1].(funcWrapper)
|
||||
@@ -76,7 +77,7 @@ type fetchResult struct {
|
||||
}
|
||||
|
||||
// get implements jsGet.get
|
||||
func (s *fetchResult) get(ctx context.Context, propertyKey string) interface{} {
|
||||
func (s *fetchResult) get(_ context.Context, propertyKey string) interface{} {
|
||||
switch propertyKey {
|
||||
case "headers":
|
||||
names := make([]string, 0, len(s.res.Header))
|
||||
@@ -97,7 +98,7 @@ func (s *fetchResult) get(ctx context.Context, propertyKey string) interface{} {
|
||||
}
|
||||
|
||||
// call implements jsCall.call
|
||||
func (s *fetchResult) call(ctx context.Context, _ api.Module, this ref, method string, _ ...interface{}) (interface{}, error) {
|
||||
func (s *fetchResult) call(_ context.Context, _ api.Module, _ goos.Ref, method string, _ ...interface{}) (interface{}, error) {
|
||||
switch method {
|
||||
case "arrayBuffer":
|
||||
v := &arrayPromise{reader: s.res.Body}
|
||||
@@ -127,7 +128,7 @@ func (h *headers) get(_ context.Context, propertyKey string) interface{} {
|
||||
}
|
||||
|
||||
// call implements jsCall.call
|
||||
func (h *headers) call(_ context.Context, _ api.Module, this ref, method string, args ...interface{}) (interface{}, error) {
|
||||
func (h *headers) call(_ context.Context, _ api.Module, _ goos.Ref, method string, args ...interface{}) (interface{}, error) {
|
||||
switch method {
|
||||
case "entries":
|
||||
// Sort names for consistent iteration
|
||||
@@ -150,7 +151,7 @@ type arrayPromise struct {
|
||||
}
|
||||
|
||||
// call implements jsCall.call
|
||||
func (p *arrayPromise) call(ctx context.Context, mod api.Module, this ref, method string, args ...interface{}) (interface{}, error) {
|
||||
func (p *arrayPromise) call(ctx context.Context, mod api.Module, this goos.Ref, method string, args ...interface{}) (interface{}, error) {
|
||||
switch method {
|
||||
case "then":
|
||||
defer p.reader.Close()
|
||||
|
||||
@@ -5,19 +5,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
)
|
||||
|
||||
// ref is used to identify a JavaScript value, since the value itself can not be passed to WebAssembly.
|
||||
//
|
||||
// The JavaScript value "undefined" is represented by the value 0.
|
||||
// A JavaScript number (64-bit float, except 0 and NaN) is represented by its IEEE 754 binary representation.
|
||||
// All other values are represented as an IEEE 754 binary representation of NaN with bits 0-31 used as
|
||||
// an ID and bits 32-34 used to differentiate between string, symbol, function and object.
|
||||
type ref uint64
|
||||
|
||||
const (
|
||||
parameterSp = "sp"
|
||||
functionDebug = "debug"
|
||||
"github.com/tetratelabs/wazero/internal/gojs/goos"
|
||||
)
|
||||
|
||||
// jsFn is a jsCall.call function, configured via jsVal.addFunction.
|
||||
@@ -31,36 +19,18 @@ type jsGet interface {
|
||||
|
||||
// jsCall allows calling a method/function by name.
|
||||
type jsCall interface {
|
||||
call(ctx context.Context, mod api.Module, this ref, method string, args ...interface{}) (interface{}, error)
|
||||
call(ctx context.Context, mod api.Module, this goos.Ref, method string, args ...interface{}) (interface{}, error)
|
||||
}
|
||||
|
||||
// nanHead are the upper 32 bits of a ref which are set if the value is not encoded as an IEEE 754 number (see above).
|
||||
const nanHead = 0x7FF80000
|
||||
|
||||
type typeFlag byte
|
||||
|
||||
const (
|
||||
// the type flags need to be in sync with gojs.js
|
||||
typeFlagNone typeFlag = iota
|
||||
typeFlagObject
|
||||
typeFlagString
|
||||
typeFlagSymbol // nolint
|
||||
typeFlagFunction
|
||||
)
|
||||
|
||||
func valueRef(id uint32, typeFlag typeFlag) ref {
|
||||
return (nanHead|ref(typeFlag))<<32 | ref(id)
|
||||
}
|
||||
|
||||
func newJsVal(ref ref, name string) *jsVal {
|
||||
func newJsVal(ref goos.Ref, name string) *jsVal {
|
||||
return &jsVal{ref: ref, name: name, properties: map[string]interface{}{}, functions: map[string]jsFn{}}
|
||||
}
|
||||
|
||||
// jsVal corresponds to a generic js.Value in go, when `GOOS=js`.
|
||||
type jsVal struct {
|
||||
// ref when is the constant reference used for built-in values, such as
|
||||
// ref is the constant reference used for built-in values, such as
|
||||
// objectConstructor.
|
||||
ref
|
||||
ref goos.Ref
|
||||
name string
|
||||
properties map[string]interface{}
|
||||
functions map[string]jsFn
|
||||
@@ -91,7 +61,7 @@ func (v *jsVal) get(_ context.Context, propertyKey string) interface{} {
|
||||
}
|
||||
|
||||
// call implements jsCall.call
|
||||
func (v *jsVal) call(ctx context.Context, mod api.Module, this ref, method string, args ...interface{}) (interface{}, error) {
|
||||
func (v *jsVal) call(ctx context.Context, mod api.Module, this goos.Ref, method string, args ...interface{}) (interface{}, error) {
|
||||
if v, ok := v.functions[method]; ok {
|
||||
return v.invoke(ctx, mod, args...)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/internal/gojs/goarch"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
|
||||
@@ -19,16 +20,21 @@ const (
|
||||
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")
|
||||
|
||||
// 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 = newSPFunc(wasmExitName, wasmExit)
|
||||
var WasmExit = goarch.NewFunc(wasmExitName, wasmExit,
|
||||
[]string{"code"},
|
||||
[]string{},
|
||||
)
|
||||
|
||||
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))
|
||||
func wasmExit(ctx context.Context, mod api.Module, stack goarch.Stack) {
|
||||
code := stack.ParamUint32(0)
|
||||
|
||||
getState(ctx).clear()
|
||||
_ = mod.CloseWithExitCode(ctx, code) // TODO: should ours be signed bit (like -1 == 255)?
|
||||
@@ -38,25 +44,22 @@ func wasmExit(ctx context.Context, mod api.Module, sp []uint64) {
|
||||
// runtime.writeErr. This implements `println`.
|
||||
//
|
||||
// See https://github.com/golang/go/blob/go1.19/src/runtime/os_js.go#L29
|
||||
var WasmWrite = newSPFunc(wasmWriteName, wasmWrite)
|
||||
var WasmWrite = goarch.NewFunc(wasmWriteName, wasmWrite,
|
||||
[]string{"fd", "p", "p_len"},
|
||||
[]string{},
|
||||
)
|
||||
|
||||
func wasmWrite(_ context.Context, mod api.Module, stack goarch.Stack) {
|
||||
fd := stack.ParamUint32(0)
|
||||
p := stack.ParamBytes(mod.Memory(), 1 /*, 2 */)
|
||||
|
||||
func wasmWrite(_ context.Context, mod api.Module, sp []uint64) {
|
||||
fsc := mod.(*wasm.CallContext).Sys.FS()
|
||||
mem := mod.Memory()
|
||||
|
||||
// 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 {
|
||||
panic(fmt.Errorf("unexpected fd %d", fd))
|
||||
}
|
||||
|
||||
if _, err := writer.Write(mustRead(mod.Memory(), "p", p, n)); err != nil {
|
||||
if _, err := writer.Write(p); err != nil {
|
||||
panic(fmt.Errorf("error writing p: %w", err))
|
||||
}
|
||||
}
|
||||
@@ -65,42 +68,37 @@ func wasmWrite(_ context.Context, mod api.Module, sp []uint64) {
|
||||
// cached view of memory should be reset.
|
||||
//
|
||||
// See https://github.com/golang/go/blob/go1.19/src/runtime/mem_js.go#L82
|
||||
var ResetMemoryDataView = &wasm.HostFunc{
|
||||
ExportNames: []string{resetMemoryDataViewName},
|
||||
Name: resetMemoryDataViewName,
|
||||
ParamTypes: []wasm.ValueType{wasm.ValueTypeI32},
|
||||
ParamNames: []string{parameterSp},
|
||||
// TODO: Compiler-based memory.grow callbacks are ignored until we have a generic solution #601
|
||||
Code: &wasm.Code{IsHostFunction: true, Body: []byte{wasm.OpcodeEnd}},
|
||||
}
|
||||
//
|
||||
// TODO: Compiler-based memory.grow callbacks are ignored until we have a generic solution #601
|
||||
var ResetMemoryDataView = goarch.NoopFunction(resetMemoryDataViewName)
|
||||
|
||||
// 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 = newSPFunc(nanotime1Name, nanotime1)
|
||||
var Nanotime1 = goarch.NewFunc(nanotime1Name, nanotime1,
|
||||
[]string{},
|
||||
[]string{"nsec"},
|
||||
)
|
||||
|
||||
func nanotime1(_ context.Context, mod api.Module, sp []uint64) {
|
||||
time := mod.(*wasm.CallContext).Sys.Nanotime()
|
||||
mem := mod.Memory()
|
||||
func nanotime1(_ context.Context, mod api.Module, stack goarch.Stack) {
|
||||
nsec := mod.(*wasm.CallContext).Sys.Nanotime()
|
||||
|
||||
// Write the result to offset SP+8
|
||||
mustWriteUint64Le(mem, "time", uint32(sp[0]+8), api.EncodeI64(time))
|
||||
stack.SetResultI64(0, nsec)
|
||||
}
|
||||
|
||||
// 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 = newSPFunc(walltimeName, walltime)
|
||||
var Walltime = goarch.NewFunc(walltimeName, walltime,
|
||||
[]string{},
|
||||
[]string{"sec", "nsec"},
|
||||
)
|
||||
|
||||
func walltime(_ context.Context, mod api.Module, sp []uint64) {
|
||||
func walltime(_ context.Context, mod api.Module, stack goarch.Stack) {
|
||||
sec, nsec := mod.(*wasm.CallContext).Sys.Walltime()
|
||||
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))
|
||||
stack.SetResultI64(0, sec)
|
||||
stack.SetResultI32(1, nsec)
|
||||
}
|
||||
|
||||
// ScheduleTimeoutEvent implements runtime.scheduleTimeoutEvent which supports
|
||||
@@ -110,7 +108,7 @@ func walltime(_ context.Context, mod api.Module, sp []uint64) {
|
||||
// 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 = stubFunction(scheduleTimeoutEventName)
|
||||
var ScheduleTimeoutEvent = goarch.StubFunction(scheduleTimeoutEventName)
|
||||
|
||||
// ^^ stubbed because signal handling is not implemented in GOOS=js
|
||||
|
||||
@@ -118,7 +116,7 @@ var ScheduleTimeoutEvent = 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 = stubFunction(clearTimeoutEventName)
|
||||
var ClearTimeoutEvent = goarch.StubFunction(clearTimeoutEventName)
|
||||
|
||||
// ^^ stubbed because signal handling is not implemented in GOOS=js
|
||||
|
||||
@@ -126,23 +124,20 @@ var ClearTimeoutEvent = stubFunction(clearTimeoutEventName)
|
||||
// for runtime.fastrand.
|
||||
//
|
||||
// See https://github.com/golang/go/blob/go1.19/src/runtime/sys_wasm.s#L200
|
||||
var GetRandomData = newSPFunc(getRandomDataName, getRandomData)
|
||||
var GetRandomData = goarch.NewFunc(getRandomDataName, getRandomData,
|
||||
[]string{"r", "r_len"},
|
||||
[]string{},
|
||||
)
|
||||
|
||||
func getRandomData(_ context.Context, mod api.Module, stack goarch.Stack) {
|
||||
r := stack.ParamBytes(mod.Memory(), 0 /*, 1 */)
|
||||
|
||||
func getRandomData(_ context.Context, mod api.Module, sp []uint64) {
|
||||
randSource := mod.(*wasm.CallContext).Sys.RandSource()
|
||||
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)
|
||||
|
||||
bufLen := len(r)
|
||||
if n, err := randSource.Read(r); err != nil {
|
||||
panic(fmt.Errorf("RandSource.Read(r /* len=%d */) failed: %w", bufLen, err))
|
||||
} else if uint32(n) != bufLen {
|
||||
} else if n != bufLen {
|
||||
panic(fmt.Errorf("RandSource.Read(r /* len=%d */) read %d bytes", bufLen, n))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"math"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/internal/gojs/goos"
|
||||
)
|
||||
|
||||
func WithState(ctx context.Context) context.Context {
|
||||
@@ -27,7 +28,7 @@ func getState(ctx context.Context) *state {
|
||||
type event struct {
|
||||
// id is the funcWrapper.id
|
||||
id uint32
|
||||
this ref
|
||||
this goos.Ref
|
||||
args *objectArray
|
||||
result interface{}
|
||||
}
|
||||
@@ -54,23 +55,23 @@ var (
|
||||
// value written by storeValue.
|
||||
//
|
||||
// See https://github.com/golang/go/blob/go1.19/misc/wasm/wasm_exec.js#L122-L133
|
||||
func loadValue(ctx context.Context, ref ref) interface{} { // nolint
|
||||
func loadValue(ctx context.Context, ref goos.Ref) interface{} { // nolint
|
||||
switch ref {
|
||||
case 0:
|
||||
return undefined
|
||||
case refValueNaN:
|
||||
case goos.RefValueNaN:
|
||||
return NaN
|
||||
case refValueZero:
|
||||
case goos.RefValueZero:
|
||||
return float64(0)
|
||||
case refValueNull:
|
||||
case goos.RefValueNull:
|
||||
return nil
|
||||
case refValueTrue:
|
||||
case goos.RefValueTrue:
|
||||
return true
|
||||
case refValueFalse:
|
||||
case goos.RefValueFalse:
|
||||
return false
|
||||
case refValueGlobal:
|
||||
case goos.RefValueGlobal:
|
||||
return getState(ctx).valueGlobal
|
||||
case refJsGo:
|
||||
case goos.RefJsGo:
|
||||
return getState(ctx)
|
||||
case refObjectConstructor:
|
||||
return objectConstructor
|
||||
@@ -93,74 +94,63 @@ func loadValue(ctx context.Context, ref ref) interface{} { // nolint
|
||||
case refHttpHeadersConstructor:
|
||||
return headersConstructor
|
||||
default:
|
||||
if (ref>>32)&nanHead != nanHead { // numbers are passed through as a ref
|
||||
return api.DecodeF64(uint64(ref))
|
||||
if f, ok := ref.ParseFloat(); ok { // numbers are passed through as a Ref
|
||||
return f
|
||||
}
|
||||
return getState(ctx).values.get(uint32(ref))
|
||||
}
|
||||
}
|
||||
|
||||
// loadArgs returns a slice of `len` values at the memory offset `addr`. The
|
||||
// returned slice is temporary, not stored in state.values.
|
||||
//
|
||||
// See https://github.com/golang/go/blob/go1.19/misc/wasm/wasm_exec.js#L191-L199
|
||||
func loadArgs(ctx context.Context, mod api.Module, sliceAddr, sliceLen uint32) []interface{} { // nolint
|
||||
result := make([]interface{}, 0, sliceLen)
|
||||
for i := uint32(0); i < sliceLen; i++ { // nolint
|
||||
iRef := mustReadUint64Le(mod.Memory(), "iRef", sliceAddr+i*8)
|
||||
result = append(result, loadValue(ctx, ref(iRef)))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// storeRef stores a value prior to returning to wasm from a host function.
|
||||
// This returns 8 bytes to represent either the value or a reference to it.
|
||||
// Any side effects besides memory must be cleaned up on wasmExit.
|
||||
//
|
||||
// See https://github.com/golang/go/blob/go1.19/misc/wasm/wasm_exec.js#L135-L183
|
||||
func storeRef(ctx context.Context, v interface{}) uint64 { // nolint
|
||||
func storeRef(ctx context.Context, v interface{}) goos.Ref { // nolint
|
||||
// allow-list because we control all implementations
|
||||
if v == undefined {
|
||||
return uint64(refValueUndefined)
|
||||
return goos.RefValueUndefined
|
||||
} else if v == nil {
|
||||
return uint64(refValueNull)
|
||||
} else if r, ok := v.(ref); ok {
|
||||
return uint64(r)
|
||||
return goos.RefValueNull
|
||||
} else if r, ok := v.(goos.Ref); ok {
|
||||
return r
|
||||
} else if b, ok := v.(bool); ok {
|
||||
if b {
|
||||
return uint64(refValueTrue)
|
||||
return goos.RefValueTrue
|
||||
} else {
|
||||
return uint64(refValueFalse)
|
||||
return goos.RefValueFalse
|
||||
}
|
||||
} else if c, ok := v.(*jsVal); ok {
|
||||
return uint64(c.ref) // already stored
|
||||
return c.ref // already stored
|
||||
} else if _, ok := v.(*event); ok {
|
||||
id := getState(ctx).values.increment(v)
|
||||
return uint64(valueRef(id, typeFlagFunction))
|
||||
return goos.ValueRef(id, goos.TypeFlagFunction)
|
||||
} else if _, ok := v.(funcWrapper); ok {
|
||||
id := getState(ctx).values.increment(v)
|
||||
return uint64(valueRef(id, typeFlagFunction))
|
||||
return goos.ValueRef(id, goos.TypeFlagFunction)
|
||||
} else if _, ok := v.(jsFn); ok {
|
||||
id := getState(ctx).values.increment(v)
|
||||
return uint64(valueRef(id, typeFlagFunction))
|
||||
return goos.ValueRef(id, goos.TypeFlagFunction)
|
||||
} else if _, ok := v.(string); ok {
|
||||
id := getState(ctx).values.increment(v)
|
||||
return uint64(valueRef(id, typeFlagString))
|
||||
return goos.ValueRef(id, goos.TypeFlagString)
|
||||
} else if ui, ok := v.(uint32); ok {
|
||||
if ui == 0 {
|
||||
return uint64(refValueZero)
|
||||
return goos.RefValueZero
|
||||
}
|
||||
return api.EncodeF64(float64(ui)) // numbers are encoded as float and passed through as a ref
|
||||
// numbers are encoded as float and passed through as a Ref
|
||||
return goos.Ref(api.EncodeF64(float64(ui)))
|
||||
} else if u, ok := v.(uint64); ok {
|
||||
return u // float is already encoded as a uint64, doesn't need to be stored.
|
||||
// float is already encoded as a uint64, doesn't need to be stored.
|
||||
return goos.Ref(u)
|
||||
} else if f64, ok := v.(float64); ok {
|
||||
if f64 == 0 {
|
||||
return uint64(refValueZero)
|
||||
return goos.RefValueZero
|
||||
}
|
||||
return api.EncodeF64(f64)
|
||||
return goos.Ref(api.EncodeF64(f64))
|
||||
}
|
||||
id := getState(ctx).values.increment(v)
|
||||
return uint64(valueRef(id, typeFlagObject))
|
||||
return goos.ValueRef(id, goos.TypeFlagObject)
|
||||
}
|
||||
|
||||
type values struct {
|
||||
@@ -231,7 +221,7 @@ func (s *state) get(_ context.Context, propertyKey string) interface{} {
|
||||
}
|
||||
|
||||
// call implements jsCall.call
|
||||
func (s *state) call(_ context.Context, _ api.Module, this ref, method string, args ...interface{}) (interface{}, error) {
|
||||
func (s *state) call(_ context.Context, _ api.Module, _ goos.Ref, method string, args ...interface{}) (interface{}, error) {
|
||||
switch method {
|
||||
case "_makeFuncWrapper":
|
||||
return funcWrapper(args[0].(float64)), nil
|
||||
@@ -250,7 +240,7 @@ func (s *state) clear() {
|
||||
}
|
||||
|
||||
func toInt64(arg interface{}) int64 {
|
||||
if arg == refValueZero || arg == undefined {
|
||||
if arg == goos.RefValueZero || arg == undefined {
|
||||
return 0
|
||||
} else if u, ok := arg.(int64); ok {
|
||||
return u
|
||||
@@ -259,7 +249,7 @@ func toInt64(arg interface{}) int64 {
|
||||
}
|
||||
|
||||
func toUint32(arg interface{}) uint32 {
|
||||
if arg == refValueZero || arg == undefined {
|
||||
if arg == goos.RefValueZero || arg == undefined {
|
||||
return 0
|
||||
} else if u, ok := arg.(uint32); ok {
|
||||
return u
|
||||
|
||||
@@ -2,7 +2,6 @@ package gojs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
@@ -10,6 +9,8 @@ import (
|
||||
"syscall"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"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"
|
||||
)
|
||||
@@ -33,19 +34,16 @@ 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 = newSPFunc(finalizeRefName, finalizeRef)
|
||||
var FinalizeRef = goos.NewFunc(finalizeRefName, finalizeRef, []string{"r"}, nil)
|
||||
|
||||
func finalizeRef(ctx context.Context, mod api.Module, sp []uint64) {
|
||||
mem := mod.Memory()
|
||||
func finalizeRef(ctx context.Context, _ api.Module, stack goos.Stack) {
|
||||
r := stack.ParamRef(0)
|
||||
|
||||
ref := mustReadUint64Le(mem, "ref", uint32(sp[0]+8))
|
||||
id := uint32(ref) // 32-bits of the ref are the ID
|
||||
id := uint32(r) // 32-bits of the ref are the ID
|
||||
|
||||
getState(ctx).values.decrement(id)
|
||||
}
|
||||
@@ -55,23 +53,14 @@ func finalizeRef(ctx context.Context, mod api.Module, sp []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 = newSPFunc(stringValName, stringVal)
|
||||
var StringVal = goos.NewFunc(stringValName, stringVal, []string{"x", "x_len"}, []string{"r"})
|
||||
|
||||
func stringVal(ctx context.Context, mod api.Module, sp []uint64) {
|
||||
mem := mod.Memory()
|
||||
func stringVal(ctx context.Context, mod api.Module, stack goos.Stack) {
|
||||
x := stack.ParamString(mod.Memory(), 0)
|
||||
|
||||
// Read (param + result count) * 8 memory starting at SP+8
|
||||
stack := mustRead(mem, "sp", uint32(sp[0]+8), 24)
|
||||
r := 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)
|
||||
stack.SetResultRef(0, r)
|
||||
}
|
||||
|
||||
// ValueGet implements js.valueGet, which is used to load a js.Value property
|
||||
@@ -80,20 +69,14 @@ func stringVal(ctx context.Context, mod api.Module, sp []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 = newSPFunc(valueGetName, valueGet)
|
||||
var ValueGet = goos.NewFunc(valueGetName, valueGet,
|
||||
[]string{"v", "p", "p_len"},
|
||||
[]string{"r"},
|
||||
)
|
||||
|
||||
func valueGet(ctx context.Context, mod api.Module, sp []uint64) {
|
||||
mem := mod.Memory()
|
||||
|
||||
// Read (param + result count) * 8 memory starting at SP+8
|
||||
stack := mustRead(mem, "sp", uint32(sp[0]+8), 32)
|
||||
|
||||
vRef := le.Uint64(stack)
|
||||
pAddr := le.Uint32(stack[8:])
|
||||
pLen := le.Uint32(stack[16:])
|
||||
|
||||
p := string(mustRead(mem, "p", pAddr, pLen))
|
||||
v := loadValue(ctx, ref(vRef))
|
||||
func valueGet(ctx context.Context, mod api.Module, stack goos.Stack) {
|
||||
v := stack.ParamVal(ctx, 0, loadValue)
|
||||
p := stack.ParamString(mod.Memory(), 1 /*, 2 */)
|
||||
|
||||
var result interface{}
|
||||
if g, ok := v.(jsGet); ok {
|
||||
@@ -111,10 +94,8 @@ func valueGet(ctx context.Context, mod api.Module, sp []uint64) {
|
||||
panic(fmt.Errorf("TODO: valueGet(v=%v, p=%s)", v, p))
|
||||
}
|
||||
|
||||
ref := storeRef(ctx, result)
|
||||
|
||||
// Write the results to memory at positions after the parameters.
|
||||
le.PutUint64(stack[24:], ref)
|
||||
r := storeRef(ctx, result)
|
||||
stack.SetResultRef(0, r)
|
||||
}
|
||||
|
||||
// ValueSet implements js.valueSet, which is used to store a js.Value property
|
||||
@@ -123,23 +104,17 @@ func valueGet(ctx context.Context, mod api.Module, sp []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 = newSPFunc(valueSetName, valueSet)
|
||||
var ValueSet = goos.NewFunc(valueSetName, valueSet,
|
||||
[]string{"v", "p", "p_len", "x"},
|
||||
[]string{},
|
||||
)
|
||||
|
||||
func valueSet(ctx context.Context, mod api.Module, sp []uint64) {
|
||||
mem := mod.Memory()
|
||||
func valueSet(ctx context.Context, mod api.Module, stack goos.Stack) {
|
||||
v := stack.ParamVal(ctx, 0, loadValue)
|
||||
p := stack.ParamString(mod.Memory(), 1 /*, 2 */)
|
||||
x := stack.ParamVal(ctx, 3, loadValue)
|
||||
|
||||
// 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(mem, "p", pAddr, pLen))
|
||||
x := loadValue(ctx, ref(xRef))
|
||||
if v == getState(ctx) {
|
||||
if p := p; v == getState(ctx) {
|
||||
switch p {
|
||||
case "_pendingEvent":
|
||||
if x == nil { // syscall_js.handleEvent
|
||||
@@ -163,7 +138,7 @@ func valueSet(ctx context.Context, mod api.Module, sp []uint64) {
|
||||
// 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 = stubFunction(valueDeleteName)
|
||||
var ValueDelete = goarch.StubFunction(valueDeleteName)
|
||||
|
||||
// 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
|
||||
@@ -171,145 +146,124 @@ 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 = newSPFunc(valueIndexName, valueIndex)
|
||||
var ValueIndex = goos.NewFunc(valueIndexName, valueIndex,
|
||||
[]string{"v", "i"},
|
||||
[]string{"r"},
|
||||
)
|
||||
|
||||
func valueIndex(ctx context.Context, mod api.Module, sp []uint64) {
|
||||
mem := mod.Memory()
|
||||
func valueIndex(ctx context.Context, _ api.Module, stack goos.Stack) {
|
||||
v := stack.ParamVal(ctx, 0, loadValue)
|
||||
i := stack.ParamUint32(1)
|
||||
|
||||
// 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]
|
||||
|
||||
ref := storeRef(ctx, result)
|
||||
|
||||
// Write the results to memory at positions after the parameters.
|
||||
le.PutUint64(stack[16:], ref)
|
||||
r := storeRef(ctx, result)
|
||||
stack.SetResultRef(0, r)
|
||||
}
|
||||
|
||||
// ValueSetIndex is stubbed as it is only used for js.ValueOf when the input is
|
||||
// []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 = stubFunction(valueSetIndexName)
|
||||
var ValueSetIndex = goarch.StubFunction(valueSetIndexName)
|
||||
|
||||
// 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 = newSPFunc(valueCallName, valueCall)
|
||||
var ValueCall = goos.NewFunc(valueCallName, valueCall,
|
||||
[]string{"v", "m", "m_len", "args", "args_len", "padding"},
|
||||
[]string{"res", "ok"},
|
||||
)
|
||||
|
||||
func valueCall(ctx context.Context, mod api.Module, sp []uint64) {
|
||||
func valueCall(ctx context.Context, mod api.Module, stack goos.Stack) {
|
||||
mem := mod.Memory()
|
||||
vRef := stack.ParamRef(0)
|
||||
m := stack.ParamString(mem, 1 /*, 2 */)
|
||||
args := stack.ParamVals(ctx, mem, 3 /*, 4 */, loadValue)
|
||||
// 5 = padding
|
||||
|
||||
// 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(mem, "m", mAddr, mLen))
|
||||
args := loadArgs(ctx, mod, argsArray, argsLen)
|
||||
|
||||
var xRef uint64
|
||||
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 {
|
||||
xRef = storeRef(ctx, err)
|
||||
ok = 0
|
||||
} else {
|
||||
xRef = storeRef(ctx, result)
|
||||
ok = 1
|
||||
v := loadValue(ctx, vRef)
|
||||
c, isCall := v.(jsCall)
|
||||
if !isCall {
|
||||
panic(fmt.Errorf("TODO: valueCall(v=%v, m=%s, args=%v)", v, m, args))
|
||||
}
|
||||
|
||||
// On refresh, start to write results 16 bytes after the last parameter.
|
||||
results := mustRead(mem, "sp", refreshSP(mod)+56, 16)
|
||||
var res goos.Ref
|
||||
var ok bool
|
||||
if result, err := c.call(ctx, mod, vRef, m, args...); err != nil {
|
||||
res = storeRef(ctx, err)
|
||||
} else {
|
||||
res = storeRef(ctx, result)
|
||||
ok = true
|
||||
}
|
||||
|
||||
// Write the results back to the stack
|
||||
le.PutUint64(results, xRef)
|
||||
le.PutUint32(results[8:], ok)
|
||||
stack.Refresh(mod)
|
||||
stack.SetResultRef(0, res)
|
||||
stack.SetResultBool(1, ok)
|
||||
}
|
||||
|
||||
// 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 = stubFunction(valueInvokeName)
|
||||
var ValueInvoke = goarch.StubFunction(valueInvokeName)
|
||||
|
||||
// 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 = newSPFunc(valueNewName, valueNew)
|
||||
var ValueNew = goos.NewFunc(valueNewName, valueNew,
|
||||
[]string{"v", "args", "args_len", "padding"},
|
||||
[]string{"res", "ok"},
|
||||
)
|
||||
|
||||
func valueNew(ctx context.Context, mod api.Module, sp []uint64) {
|
||||
func valueNew(ctx context.Context, mod api.Module, stack goos.Stack) {
|
||||
mem := mod.Memory()
|
||||
vRef := stack.ParamRef(0)
|
||||
args := stack.ParamVals(ctx, mem, 1 /*, 2 */, loadValue)
|
||||
// 3 = padding
|
||||
|
||||
// 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 uint32
|
||||
switch ref {
|
||||
var res goos.Ref
|
||||
var ok bool
|
||||
switch vRef {
|
||||
case refArrayConstructor:
|
||||
result := &objectArray{}
|
||||
xRef = storeRef(ctx, result)
|
||||
ok = 1
|
||||
res = storeRef(ctx, result)
|
||||
ok = true
|
||||
case refUint8ArrayConstructor:
|
||||
var result *byteArray
|
||||
if n, ok := args[0].(float64); ok {
|
||||
var result interface{}
|
||||
a := args[0]
|
||||
if n, ok := a.(float64); ok {
|
||||
result = &byteArray{make([]byte, uint32(n))}
|
||||
} else if n, ok := args[0].(uint32); ok {
|
||||
result = &byteArray{make([]byte, n)}
|
||||
} else if b, ok := args[0].(*byteArray); ok {
|
||||
// In case of below, in HTTP, return the same ref
|
||||
} else if _, ok := a.(*byteArray); ok {
|
||||
// In case of wrapping, increment the counter of the same ref.
|
||||
// uint8arrayWrapper := uint8Array.New(args[0])
|
||||
result = b
|
||||
result = stack.ParamRefs(mem, 1)[0]
|
||||
} else {
|
||||
panic(fmt.Errorf("TODO: valueNew(v=%v, args=%v)", v, args))
|
||||
panic(fmt.Errorf("TODO: valueNew(v=%v, args=%v)", vRef, args))
|
||||
}
|
||||
xRef = storeRef(ctx, result)
|
||||
ok = 1
|
||||
res = storeRef(ctx, result)
|
||||
ok = true
|
||||
case refObjectConstructor:
|
||||
result := &object{properties: map[string]interface{}{}}
|
||||
xRef = storeRef(ctx, result)
|
||||
ok = 1
|
||||
res = storeRef(ctx, result)
|
||||
ok = true
|
||||
case refHttpHeadersConstructor:
|
||||
result := &headers{headers: http.Header{}}
|
||||
xRef = storeRef(ctx, result)
|
||||
ok = 1
|
||||
res = storeRef(ctx, result)
|
||||
ok = true
|
||||
case refJsDateConstructor:
|
||||
xRef = uint64(refJsDate)
|
||||
ok = 1
|
||||
res = refJsDate
|
||||
ok = true
|
||||
default:
|
||||
panic(fmt.Errorf("TODO: valueNew(v=%v, args=%v)", v, args))
|
||||
panic(fmt.Errorf("TODO: valueNew(v=%v, args=%v)", vRef, args))
|
||||
}
|
||||
|
||||
// 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)
|
||||
stack.Refresh(mod)
|
||||
stack.SetResultRef(0, res)
|
||||
stack.SetResultBool(1, ok)
|
||||
}
|
||||
|
||||
// ValueLength implements js.valueLength, which is used to load the length
|
||||
@@ -317,21 +271,17 @@ func valueNew(ctx context.Context, mod api.Module, sp []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 = newSPFunc(valueLengthName, valueLength)
|
||||
var ValueLength = goos.NewFunc(valueLengthName, valueLength,
|
||||
[]string{"v"},
|
||||
[]string{"len"},
|
||||
)
|
||||
|
||||
func valueLength(ctx context.Context, mod api.Module, sp []uint64) {
|
||||
mem := mod.Memory()
|
||||
func valueLength(ctx context.Context, _ api.Module, stack goos.Stack) {
|
||||
v := stack.ParamVal(ctx, 0, loadValue)
|
||||
|
||||
// Read (param + result count) * 8 memory starting at SP+8
|
||||
stack := mustRead(mem, "sp", uint32(sp[0]+8), 16)
|
||||
len := len(v.(*objectArray).slice)
|
||||
|
||||
vRef := le.Uint64(stack)
|
||||
|
||||
v := loadValue(ctx, ref(vRef))
|
||||
l := uint32(len(v.(*objectArray).slice))
|
||||
|
||||
// Write the results to memory at positions after the parameters.
|
||||
le.PutUint32(stack[8:], l)
|
||||
stack.SetResultUint32(0, uint32(len))
|
||||
}
|
||||
|
||||
// ValuePrepareString implements js.valuePrepareString, which is used to load
|
||||
@@ -341,25 +291,21 @@ func valueLength(ctx context.Context, mod api.Module, sp []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 = newSPFunc(valuePrepareStringName, valuePrepareString)
|
||||
var ValuePrepareString = goos.NewFunc(valuePrepareStringName, valuePrepareString,
|
||||
[]string{"v"},
|
||||
[]string{"str", "length"},
|
||||
)
|
||||
|
||||
func valuePrepareString(ctx context.Context, mod api.Module, sp []uint64) {
|
||||
mem := mod.Memory()
|
||||
func valuePrepareString(ctx context.Context, _ api.Module, stack goos.Stack) {
|
||||
v := stack.ParamVal(ctx, 0, loadValue)
|
||||
|
||||
// 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)
|
||||
|
||||
sRef := storeRef(ctx, s)
|
||||
sLen := uint32(len(s))
|
||||
|
||||
// Write the results to memory at positions after the parameters.
|
||||
le.PutUint64(stack[8:], sRef)
|
||||
le.PutUint32(stack[16:], sLen)
|
||||
stack.SetResultRef(0, sRef)
|
||||
stack.SetResultUint32(1, sLen)
|
||||
}
|
||||
|
||||
// ValueLoadString implements js.valueLoadString, which is used copy a string
|
||||
@@ -368,28 +314,23 @@ func valuePrepareString(ctx context.Context, mod api.Module, sp []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 = newSPFunc(valueLoadStringName, valueLoadString)
|
||||
var ValueLoadString = goos.NewFunc(valueLoadStringName, valueLoadString,
|
||||
[]string{"v", "b", "b_len"},
|
||||
[]string{},
|
||||
)
|
||||
|
||||
func valueLoadString(ctx context.Context, mod api.Module, sp []uint64) {
|
||||
mem := mod.Memory()
|
||||
func valueLoadString(ctx context.Context, mod api.Module, stack goos.Stack) {
|
||||
v := stack.ParamVal(ctx, 0, loadValue)
|
||||
b := stack.ParamBytes(mod.Memory(), 1 /*, 2 */)
|
||||
|
||||
// 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(mem, "b", bAddr, bLen)
|
||||
copy(b, s)
|
||||
}
|
||||
|
||||
// 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 = stubFunction(valueInstanceOfName)
|
||||
var ValueInstanceOf = goarch.StubFunction(valueInstanceOfName)
|
||||
|
||||
// CopyBytesToGo copies a JavaScript managed byte array to linear memory.
|
||||
// For example, this is used to read an HTTP response body.
|
||||
@@ -401,31 +342,25 @@ 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 = newSPFunc(copyBytesToGoName, copyBytesToGo)
|
||||
var CopyBytesToGo = goos.NewFunc(copyBytesToGoName, copyBytesToGo,
|
||||
[]string{"dst", "dst_len", "padding", "src"},
|
||||
[]string{"n", "ok"},
|
||||
)
|
||||
|
||||
func copyBytesToGo(ctx context.Context, mod api.Module, sp []uint64) {
|
||||
mem := mod.Memory()
|
||||
func copyBytesToGo(ctx context.Context, mod api.Module, stack goos.Stack) {
|
||||
dst := stack.ParamBytes(mod.Memory(), 0 /*, 1 */)
|
||||
// padding = 2
|
||||
src := stack.ParamVal(ctx, 3, loadValue)
|
||||
|
||||
// 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
|
||||
if src, isBuf := v.(*byteArray); isBuf {
|
||||
var n uint32
|
||||
var ok bool
|
||||
if src, isBuf := src.(*byteArray); isBuf {
|
||||
n = uint32(copy(dst, src.slice))
|
||||
ok = 1
|
||||
ok = true
|
||||
}
|
||||
|
||||
// Write the results to memory at positions after the parameters.
|
||||
le.PutUint32(stack[32:], n)
|
||||
le.PutUint32(stack[40:], ok)
|
||||
stack.SetResultUint32(0, n)
|
||||
stack.SetResultBool(1, ok)
|
||||
}
|
||||
|
||||
// CopyBytesToJS copies linear memory to a JavaScript managed byte array.
|
||||
@@ -438,43 +373,27 @@ func copyBytesToGo(ctx context.Context, mod api.Module, sp []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 = newSPFunc(copyBytesToJSName, copyBytesToJS)
|
||||
var CopyBytesToJS = goos.NewFunc(copyBytesToJSName, copyBytesToJS,
|
||||
[]string{"dst", "src", "src_len", "padding"},
|
||||
[]string{"n", "ok"},
|
||||
)
|
||||
|
||||
func copyBytesToJS(ctx context.Context, mod api.Module, sp []uint64) {
|
||||
mem := mod.Memory()
|
||||
func copyBytesToJS(ctx context.Context, mod api.Module, stack goos.Stack) {
|
||||
dst := stack.ParamVal(ctx, 0, loadValue)
|
||||
src := stack.ParamBytes(mod.Memory(), 1 /*, 2 */)
|
||||
// padding = 3
|
||||
|
||||
// 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
|
||||
if dst, isBuf := v.(*byteArray); isBuf {
|
||||
var n uint32
|
||||
var ok bool
|
||||
if dst, isBuf := dst.(*byteArray); isBuf {
|
||||
if dst != nil { // empty is possible on EOF
|
||||
n = uint32(copy(dst.slice, src))
|
||||
}
|
||||
ok = 1
|
||||
ok = true
|
||||
}
|
||||
|
||||
// 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
|
||||
// when in an operation that can trigger a Go event handler.
|
||||
//
|
||||
// See https://github.com/golang/go/blob/go1.19/misc/wasm/wasm_exec.js#L210-L213
|
||||
func refreshSP(mod api.Module) uint32 {
|
||||
// Cheat by reading global[0] directly instead of through a function proxy.
|
||||
// https://github.com/golang/go/blob/go1.19/src/runtime/rt0_js_wasm.s#L87-L90
|
||||
return uint32(mod.(*wasm.CallContext).GlobalVal(0))
|
||||
stack.SetResultUint32(0, n)
|
||||
stack.SetResultBool(1, ok)
|
||||
}
|
||||
|
||||
// syscallErr is a (GOARCH=wasm) error, which must match a key in mapJSError.
|
||||
@@ -536,7 +455,7 @@ type funcWrapper uint32
|
||||
|
||||
// jsFn implements jsFn.invoke
|
||||
func (f funcWrapper) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
|
||||
e := &event{id: uint32(f), this: args[0].(ref)}
|
||||
e := &event{id: uint32(f), this: args[0].(goos.Ref)}
|
||||
|
||||
if len(args) > 1 { // Ensure arguments are hashable.
|
||||
e.args = &objectArray{args[1:]}
|
||||
@@ -563,13 +482,3 @@ 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},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,6 @@ var (
|
||||
type getTimezoneOffset struct{}
|
||||
|
||||
// invoke implements jsFn.invoke
|
||||
func (*getTimezoneOffset) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
|
||||
func (*getTimezoneOffset) invoke(context.Context, api.Module, ...interface{}) (interface{}, error) {
|
||||
return uint32(0), nil // UTC
|
||||
}
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
package gojs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
|
||||
// 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 = stubFunction(functionDebug)
|
||||
|
||||
// stubFunction stubs functions not used in Go's main source tree.
|
||||
func stubFunction(name string) *wasm.HostFunc {
|
||||
return &wasm.HostFunc{
|
||||
ExportNames: []string{name},
|
||||
Name: name,
|
||||
ParamTypes: []wasm.ValueType{wasm.ValueTypeI32},
|
||||
ParamNames: []string{parameterSp},
|
||||
Code: &wasm.Code{IsHostFunction: true, Body: []byte{wasm.OpcodeUnreachable, wasm.OpcodeEnd}},
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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 {
|
||||
result, ok := mem.ReadUint64Le(offset)
|
||||
if !ok {
|
||||
panic(fmt.Errorf("out of memory reading %s", fieldName))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// mustWrite is like api.Memory except that it panics if the offset
|
||||
// is out of range.
|
||||
func mustWrite(mem api.Memory, fieldName string, offset uint32, val []byte) {
|
||||
if ok := mem.Write(offset, val); !ok {
|
||||
panic(fmt.Errorf("out of memory writing %s", fieldName))
|
||||
}
|
||||
}
|
||||
|
||||
// mustWriteUint64Le is like api.Memory except that it panics if the offset
|
||||
// is out of range.
|
||||
func mustWriteUint64Le(mem api.Memory, fieldName string, offset uint32, val uint64) {
|
||||
if ok := mem.WriteUint64Le(offset, val); !ok {
|
||||
panic(fmt.Errorf("out of memory writing %s", fieldName))
|
||||
}
|
||||
}
|
||||
36
internal/gojs/util/util.go
Normal file
36
internal/gojs/util/util.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
|
||||
// MustWrite is like api.Memory except that it panics if the offset
|
||||
// is out of range.
|
||||
func MustWrite(mem api.Memory, fieldName string, offset uint32, val []byte) {
|
||||
if ok := mem.Write(offset, val); !ok {
|
||||
panic(fmt.Errorf("out of memory writing %s", fieldName))
|
||||
}
|
||||
}
|
||||
|
||||
// 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 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},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user