174 lines
4.6 KiB
Go
174 lines
4.6 KiB
Go
// 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"
|
|
|
|
"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"
|
|
)
|
|
|
|
// 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}},
|
|
}
|
|
}
|
|
|
|
var le = binary.LittleEndian
|
|
|
|
type Stack interface {
|
|
// Name is the function name being invoked.
|
|
Name() string
|
|
|
|
Param(i int) uint64
|
|
|
|
// 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)
|
|
|
|
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(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 {
|
|
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
|
|
res = le.Uint64(s.buf[pos:])
|
|
return
|
|
}
|
|
|
|
// 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 util.MustRead(mem, s.name, i, offset, byteCount)
|
|
}
|
|
|
|
// ParamString implements Stack.ParamString
|
|
func (s *stack) ParamString(mem api.Memory, i int) string {
|
|
return string(s.ParamBytes(mem, i)) // safe copy of guest memory
|
|
}
|
|
|
|
// 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(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 := (s.paramCount + i) << 3
|
|
le.PutUint64(s.buf[pos:], v)
|
|
}
|
|
|
|
// 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))
|
|
}
|
|
|
|
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 {
|
|
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(f.name, mod.Memory(), uint32(wasmStack[0]))
|
|
f.f(ctx, mod, s)
|
|
}
|