Files
wazero/internal/wasm/gofunc.go
Crypt Keeper 119144ece8 Stops proliferation of MVP jargon (#295)
MVP was a term used by WebAssembly insiders when ramping up to the 1.0
spec. While these folks still use that term it is confusing and
unnecessary way to qualify a W3C version. Here are some of the problems:

* MVP does not match a W3C published URL
* MVP does not match a git tag on the spec repo
* MVP was a work in progress, so there are text that say "not in MVP"
  which ended up in 1.0 (as MVP became more than it was).
* MVP is jargon to people who don't know that stands for Minimum Viable Product.

This stops this practice and instead uses the W3C 1.0 Draft version
instead: 20191205

Signed-off-by: Adrian Cole <adrian@tetrate.io>
2022-02-25 09:34:05 +08:00

161 lines
4.6 KiB
Go

package internalwasm
import (
"context"
"fmt"
"reflect"
publicwasm "github.com/tetratelabs/wazero/wasm"
)
// FunctionKind identifies the type of function that can be called.
type FunctionKind byte
const (
// FunctionKindWasm is not a Go function: it is implemented in Wasm.
FunctionKindWasm FunctionKind = iota
// FunctionKindGoNoContext is a function implemented in Go, with a signature matching FunctionType.
FunctionKindGoNoContext
// FunctionKindGoContext is a function implemented in Go, with a signature matching FunctionType, except arg zero is
// a context.Context.
FunctionKindGoContext
// FunctionKindGoModuleContext is a function implemented in Go, with a signature matching FunctionType, except arg zero is
// a ModuleContext.
FunctionKindGoModuleContext
)
// GoFunc binds a WebAssembly 1.0 (20191205) Type Use to a Go func signature.
type GoFunc struct {
wasmFunctionName string
// functionKind is never FunctionKindWasm
functionKind FunctionKind
functionType *FunctionType
goFunc *reflect.Value
}
func NewGoFunc(wasmFunctionName string, goFunc interface{}) (hf *GoFunc, err error) {
hf = &GoFunc{wasmFunctionName: wasmFunctionName}
fn := reflect.ValueOf(goFunc)
hf.goFunc = &fn
hf.functionKind, hf.functionType, _, err = GetFunctionType(hf.wasmFunctionName, hf.goFunc, false)
return
}
// Below are reflection code to get the interface type used to parse functions and set values.
var moduleContextType = reflect.TypeOf((*publicwasm.ModuleContext)(nil)).Elem()
var goContextType = reflect.TypeOf((*context.Context)(nil)).Elem()
var errorType = reflect.TypeOf((*error)(nil)).Elem()
// GetHostFunctionCallContextValue returns a reflect.Value for a context param[0], or nil if there isn't one.
func GetHostFunctionCallContextValue(fk FunctionKind, ctx *ModuleContext) *reflect.Value {
switch fk {
case FunctionKindGoNoContext: // no special param zero
case FunctionKindGoContext:
val := reflect.New(goContextType).Elem()
val.Set(reflect.ValueOf(ctx.Context()))
return &val
case FunctionKindGoModuleContext:
val := reflect.New(moduleContextType).Elem()
val.Set(reflect.ValueOf(ctx))
return &val
}
return nil
}
// GetFunctionType returns the function type corresponding to the function signature or errs if invalid.
func GetFunctionType(name string, fn *reflect.Value, allowErrorResult bool) (fk FunctionKind, ft *FunctionType, hasErrorResult bool, err error) {
if fn.Kind() != reflect.Func {
err = fmt.Errorf("%s is a %s, but should be a Func", name, fn.Kind().String())
return
}
p := fn.Type()
pOffset := 0
pCount := p.NumIn()
fk = FunctionKindGoNoContext
if pCount > 0 && p.In(0).Kind() == reflect.Interface {
p0 := p.In(0)
if p0.Implements(moduleContextType) {
fk = FunctionKindGoModuleContext
pOffset = 1
pCount--
} else if p0.Implements(goContextType) {
fk = FunctionKindGoContext
pOffset = 1
pCount--
}
}
rCount := p.NumOut()
if (allowErrorResult && rCount > 2) || (!allowErrorResult && rCount > 1) {
err = fmt.Errorf("%s has more than one result", name)
return
}
if allowErrorResult && rCount > 0 {
maybeErrIdx := rCount - 1
if p.Out(maybeErrIdx).Implements(errorType) {
hasErrorResult = true
rCount--
}
}
ft = &FunctionType{Params: make([]ValueType, pCount), Results: make([]ValueType, rCount)}
for i := 0; i < len(ft.Params); i++ {
pI := p.In(i + pOffset)
if t, ok := getTypeOf(pI.Kind()); ok {
ft.Params[i] = t
continue
}
// Now, we will definitely err, decide which message is best
var arg0Type reflect.Type
if hc := pI.Implements(moduleContextType); hc {
arg0Type = moduleContextType
} else if gc := pI.Implements(goContextType); gc {
arg0Type = goContextType
}
if arg0Type != nil {
err = fmt.Errorf("%s param[%d] is a %s, which may be defined only once as param[0]", name, i+pOffset, arg0Type)
} else {
err = fmt.Errorf("%s param[%d] is unsupported: %s", name, i+pOffset, pI.Kind())
}
return
}
if rCount == 0 {
return
}
result := p.Out(0)
if t, ok := getTypeOf(result.Kind()); ok {
ft.Results[0] = t
return
}
if result.Implements(errorType) {
err = fmt.Errorf("%s result[0] is an error, which is unsupported", name)
} else {
err = fmt.Errorf("%s result[0] is unsupported: %s", name, result.Kind())
}
return
}
func getTypeOf(kind reflect.Kind) (ValueType, bool) {
switch kind {
case reflect.Float64:
return ValueTypeF64, true
case reflect.Float32:
return ValueTypeF32, true
case reflect.Int32, reflect.Uint32:
return ValueTypeI32, true
case reflect.Int64, reflect.Uint64:
return ValueTypeI64, true
default:
return 0x00, false
}
}