mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2024-12-04 22:20:53 +01:00
1e7b32490d
This allows for building GoToSocial with [SQLite transpiled to WASM](https://github.com/ncruces/go-sqlite3) and accessed through [Wazero](https://wazero.io/).
280 lines
7.0 KiB
Go
280 lines
7.0 KiB
Go
package wasm
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
"reflect"
|
|
|
|
"github.com/tetratelabs/wazero/api"
|
|
)
|
|
|
|
type paramsKind byte
|
|
|
|
const (
|
|
paramsKindNoContext paramsKind = iota
|
|
paramsKindContext
|
|
paramsKindContextModule
|
|
)
|
|
|
|
// Below are reflection code to get the interface type used to parse functions and set values.
|
|
|
|
var (
|
|
moduleType = reflect.TypeOf((*api.Module)(nil)).Elem()
|
|
goContextType = reflect.TypeOf((*context.Context)(nil)).Elem()
|
|
errorType = reflect.TypeOf((*error)(nil)).Elem()
|
|
)
|
|
|
|
// compile-time check to ensure reflectGoModuleFunction implements
|
|
// api.GoModuleFunction.
|
|
var _ api.GoModuleFunction = (*reflectGoModuleFunction)(nil)
|
|
|
|
type reflectGoModuleFunction struct {
|
|
fn *reflect.Value
|
|
params, results []ValueType
|
|
}
|
|
|
|
// Call implements the same method as documented on api.GoModuleFunction.
|
|
func (f *reflectGoModuleFunction) Call(ctx context.Context, mod api.Module, stack []uint64) {
|
|
callGoFunc(ctx, mod, f.fn, stack)
|
|
}
|
|
|
|
// EqualTo is exposed for testing.
|
|
func (f *reflectGoModuleFunction) EqualTo(that interface{}) bool {
|
|
if f2, ok := that.(*reflectGoModuleFunction); !ok {
|
|
return false
|
|
} else {
|
|
// TODO compare reflect pointers
|
|
return bytes.Equal(f.params, f2.params) && bytes.Equal(f.results, f2.results)
|
|
}
|
|
}
|
|
|
|
// compile-time check to ensure reflectGoFunction implements api.GoFunction.
|
|
var _ api.GoFunction = (*reflectGoFunction)(nil)
|
|
|
|
type reflectGoFunction struct {
|
|
fn *reflect.Value
|
|
pk paramsKind
|
|
params, results []ValueType
|
|
}
|
|
|
|
// EqualTo is exposed for testing.
|
|
func (f *reflectGoFunction) EqualTo(that interface{}) bool {
|
|
if f2, ok := that.(*reflectGoFunction); !ok {
|
|
return false
|
|
} else {
|
|
// TODO compare reflect pointers
|
|
return f.pk == f2.pk &&
|
|
bytes.Equal(f.params, f2.params) && bytes.Equal(f.results, f2.results)
|
|
}
|
|
}
|
|
|
|
// Call implements the same method as documented on api.GoFunction.
|
|
func (f *reflectGoFunction) Call(ctx context.Context, stack []uint64) {
|
|
if f.pk == paramsKindNoContext {
|
|
ctx = nil
|
|
}
|
|
callGoFunc(ctx, nil, f.fn, stack)
|
|
}
|
|
|
|
// callGoFunc executes the reflective function by converting params to Go
|
|
// types. The results of the function call are converted back to api.ValueType.
|
|
func callGoFunc(ctx context.Context, mod api.Module, fn *reflect.Value, stack []uint64) {
|
|
tp := fn.Type()
|
|
|
|
var in []reflect.Value
|
|
pLen := tp.NumIn()
|
|
if pLen != 0 {
|
|
in = make([]reflect.Value, pLen)
|
|
|
|
i := 0
|
|
if ctx != nil {
|
|
in[0] = newContextVal(ctx)
|
|
i++
|
|
}
|
|
if mod != nil {
|
|
in[1] = newModuleVal(mod)
|
|
i++
|
|
}
|
|
|
|
for j := 0; i < pLen; i++ {
|
|
next := tp.In(i)
|
|
val := reflect.New(next).Elem()
|
|
k := next.Kind()
|
|
raw := stack[j]
|
|
j++
|
|
|
|
switch k {
|
|
case reflect.Float32:
|
|
val.SetFloat(float64(math.Float32frombits(uint32(raw))))
|
|
case reflect.Float64:
|
|
val.SetFloat(math.Float64frombits(raw))
|
|
case reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
|
val.SetUint(raw)
|
|
case reflect.Int32, reflect.Int64:
|
|
val.SetInt(int64(raw))
|
|
default:
|
|
panic(fmt.Errorf("BUG: param[%d] has an invalid type: %v", i, k))
|
|
}
|
|
in[i] = val
|
|
}
|
|
}
|
|
|
|
// Execute the host function and push back the call result onto the stack.
|
|
for i, ret := range fn.Call(in) {
|
|
switch ret.Kind() {
|
|
case reflect.Float32:
|
|
stack[i] = uint64(math.Float32bits(float32(ret.Float())))
|
|
case reflect.Float64:
|
|
stack[i] = math.Float64bits(ret.Float())
|
|
case reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
|
stack[i] = ret.Uint()
|
|
case reflect.Int32, reflect.Int64:
|
|
stack[i] = uint64(ret.Int())
|
|
default:
|
|
panic(fmt.Errorf("BUG: result[%d] has an invalid type: %v", i, ret.Kind()))
|
|
}
|
|
}
|
|
}
|
|
|
|
func newContextVal(ctx context.Context) reflect.Value {
|
|
val := reflect.New(goContextType).Elem()
|
|
val.Set(reflect.ValueOf(ctx))
|
|
return val
|
|
}
|
|
|
|
func newModuleVal(m api.Module) reflect.Value {
|
|
val := reflect.New(moduleType).Elem()
|
|
val.Set(reflect.ValueOf(m))
|
|
return val
|
|
}
|
|
|
|
// MustParseGoReflectFuncCode parses Code from the go function or panics.
|
|
//
|
|
// Exposing this simplifies FunctionDefinition of host functions in built-in host
|
|
// modules and tests.
|
|
func MustParseGoReflectFuncCode(fn interface{}) Code {
|
|
_, _, code, err := parseGoReflectFunc(fn)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return code
|
|
}
|
|
|
|
func parseGoReflectFunc(fn interface{}) (params, results []ValueType, code Code, err error) {
|
|
fnV := reflect.ValueOf(fn)
|
|
p := fnV.Type()
|
|
|
|
if fnV.Kind() != reflect.Func {
|
|
err = fmt.Errorf("kind != func: %s", fnV.Kind().String())
|
|
return
|
|
}
|
|
|
|
pk, kindErr := kind(p)
|
|
if kindErr != nil {
|
|
err = kindErr
|
|
return
|
|
}
|
|
|
|
pOffset := 0
|
|
switch pk {
|
|
case paramsKindNoContext:
|
|
case paramsKindContext:
|
|
pOffset = 1
|
|
case paramsKindContextModule:
|
|
pOffset = 2
|
|
}
|
|
|
|
pCount := p.NumIn() - pOffset
|
|
if pCount > 0 {
|
|
params = make([]ValueType, pCount)
|
|
}
|
|
for i := 0; i < len(params); i++ {
|
|
pI := p.In(i + pOffset)
|
|
if t, ok := getTypeOf(pI.Kind()); ok {
|
|
params[i] = t
|
|
continue
|
|
}
|
|
|
|
// Now, we will definitely err, decide which message is best
|
|
var arg0Type reflect.Type
|
|
if hc := pI.Implements(moduleType); hc {
|
|
arg0Type = moduleType
|
|
} else if gc := pI.Implements(goContextType); gc {
|
|
arg0Type = goContextType
|
|
}
|
|
|
|
if arg0Type != nil {
|
|
err = fmt.Errorf("param[%d] is a %s, which may be defined only once as param[0]", i+pOffset, arg0Type)
|
|
} else {
|
|
err = fmt.Errorf("param[%d] is unsupported: %s", i+pOffset, pI.Kind())
|
|
}
|
|
return
|
|
}
|
|
|
|
rCount := p.NumOut()
|
|
if rCount > 0 {
|
|
results = make([]ValueType, rCount)
|
|
}
|
|
for i := 0; i < len(results); i++ {
|
|
rI := p.Out(i)
|
|
if t, ok := getTypeOf(rI.Kind()); ok {
|
|
results[i] = t
|
|
continue
|
|
}
|
|
|
|
// Now, we will definitely err, decide which message is best
|
|
if rI.Implements(errorType) {
|
|
err = fmt.Errorf("result[%d] is an error, which is unsupported", i)
|
|
} else {
|
|
err = fmt.Errorf("result[%d] is unsupported: %s", i, rI.Kind())
|
|
}
|
|
return
|
|
}
|
|
|
|
code = Code{}
|
|
if pk == paramsKindContextModule {
|
|
code.GoFunc = &reflectGoModuleFunction{fn: &fnV, params: params, results: results}
|
|
} else {
|
|
code.GoFunc = &reflectGoFunction{pk: pk, fn: &fnV, params: params, results: results}
|
|
}
|
|
return
|
|
}
|
|
|
|
func kind(p reflect.Type) (paramsKind, error) {
|
|
pCount := p.NumIn()
|
|
if pCount > 0 && p.In(0).Kind() == reflect.Interface {
|
|
p0 := p.In(0)
|
|
if p0.Implements(moduleType) {
|
|
return 0, errors.New("invalid signature: api.Module parameter must be preceded by context.Context")
|
|
} else if p0.Implements(goContextType) {
|
|
if pCount >= 2 && p.In(1).Implements(moduleType) {
|
|
return paramsKindContextModule, nil
|
|
}
|
|
return paramsKindContext, nil
|
|
}
|
|
}
|
|
// Without context param allows portability with reflective runtimes.
|
|
// This allows people to more easily port to wazero.
|
|
return paramsKindNoContext, nil
|
|
}
|
|
|
|
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
|
|
case reflect.Uintptr:
|
|
return ValueTypeExternref, true
|
|
default:
|
|
return 0x00, false
|
|
}
|
|
}
|