From 05bc406ce79cdb63d6155d046a45b2ffe28f8db6 Mon Sep 17 00:00:00 2001 From: Gram Date: Mon, 26 Feb 2024 10:24:18 +0100 Subject: [PATCH] Wazero (#1) wazero: adding initial support --- go.mod | 3 ++ go.sum | 2 + interp/wasman/interp.go | 59 +++++---------------- interp/wazero/instance.go | 79 ++++++++++++++++++++++++++++ interp/wazero/interp.go | 107 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 204 insertions(+), 46 deletions(-) create mode 100644 interp/wazero/instance.go create mode 100644 interp/wazero/interp.go diff --git a/go.mod b/go.mod index 64fb63a..dd2cb5c 100644 --- a/go.mod +++ b/go.mod @@ -2,8 +2,11 @@ module github.com/hybridgroup/mechanoid go 1.22.0 +replace github.com/tetratelabs/wazero => github.com/orsinium-forks/wazero v0.0.0-20240217173836-b12c024bcbe4 + require ( github.com/hybridgroup/wasman v0.0.0-20240223122031-5eaa03843b74 + github.com/tetratelabs/wazero v1.6.0 github.com/urfave/cli/v2 v2.27.1 tinygo.org/x/tinyfs v0.3.1-0.20231212053859-32ae3f6bbad9 ) diff --git a/go.sum b/go.sum index 74ef712..e2faff8 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHH github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/hybridgroup/wasman v0.0.0-20240223122031-5eaa03843b74 h1:kTeT4KPA7SW0lyN0+a39F07Y6/F01pQc6/UWlXlLpd8= github.com/hybridgroup/wasman v0.0.0-20240223122031-5eaa03843b74/go.mod h1:rLavUo4P0xVcDeDnViYEpPQPoACmp1py9UTLPY/R7Lg= +github.com/orsinium-forks/wazero v0.0.0-20240217173836-b12c024bcbe4 h1:MUh9e2izck9aROiwDsDm24UU7kHieYM2911U1t+NASs= +github.com/orsinium-forks/wazero v0.0.0-20240217173836-b12c024bcbe4/go.mod h1:0U0G41+ochRKoPKCJlh0jMg1CHkyfK8kDqiirMmKY8A= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/urfave/cli/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho= diff --git a/interp/wasman/interp.go b/interp/wasman/interp.go index 7808a94..6dc6eab 100644 --- a/interp/wasman/interp.go +++ b/interp/wasman/interp.go @@ -76,63 +76,30 @@ func (i *Interpreter) Halt() error { } // TODO: better implementation using generics? -func (i *Interpreter) DefineFunc(moduleName, funcName string, f interface{}) error { - switch f.(type) { +func (i *Interpreter) DefineFunc(moduleName, funcName string, f any) error { + switch tf := f.(type) { case func(): - if err := wasmaneng.DefineFunc(i.linker, moduleName, funcName, f.(func())); err != nil { - return err - } - return nil + return wasmaneng.DefineFunc(i.linker, moduleName, funcName, tf) case func() int32: - if err := wasmaneng.DefineFunc01(i.linker, moduleName, funcName, f.(func() int32)); err != nil { - return err - } - return nil + return wasmaneng.DefineFunc01(i.linker, moduleName, funcName, tf) case func(int32): - if err := wasmaneng.DefineFunc10(i.linker, moduleName, funcName, f.(func(int32))); err != nil { - return err - } - return nil + return wasmaneng.DefineFunc10(i.linker, moduleName, funcName, tf) case func(int32) int32: - if err := wasmaneng.DefineFunc11(i.linker, moduleName, funcName, f.(func(int32) int32)); err != nil { - return err - } - return nil + return wasmaneng.DefineFunc11(i.linker, moduleName, funcName, tf) case func(int32, int32): - if err := wasmaneng.DefineFunc20(i.linker, moduleName, funcName, f.(func(int32, int32))); err != nil { - return err - } - return nil + return wasmaneng.DefineFunc20(i.linker, moduleName, funcName, tf) case func(int32, int32) int32: - if err := wasmaneng.DefineFunc21(i.linker, moduleName, funcName, f.(func(uint32, uint32) uint32)); err != nil { - return err - } - return nil + return wasmaneng.DefineFunc21(i.linker, moduleName, funcName, tf) case func() uint32: - if err := wasmaneng.DefineFunc01(i.linker, moduleName, funcName, f.(func() uint32)); err != nil { - return err - } - return nil + return wasmaneng.DefineFunc01(i.linker, moduleName, funcName, tf) case func(uint32): - if err := wasmaneng.DefineFunc10(i.linker, moduleName, funcName, f.(func(uint32))); err != nil { - return err - } - return nil + return wasmaneng.DefineFunc10(i.linker, moduleName, funcName, tf) case func(uint32) uint32: - if err := wasmaneng.DefineFunc11(i.linker, moduleName, funcName, f.(func(uint32) uint32)); err != nil { - return err - } - return nil + return wasmaneng.DefineFunc11(i.linker, moduleName, funcName, tf) case func(uint32, uint32): - if err := wasmaneng.DefineFunc20(i.linker, moduleName, funcName, f.(func(uint32, uint32))); err != nil { - return err - } - return nil + return wasmaneng.DefineFunc20(i.linker, moduleName, funcName, tf) case func(uint32, uint32) uint32: - if err := wasmaneng.DefineFunc21(i.linker, moduleName, funcName, f.(func(uint32, uint32) uint32)); err != nil { - return err - } - return nil + return wasmaneng.DefineFunc21(i.linker, moduleName, funcName, tf) default: return engine.ErrInvalidFuncType } diff --git a/interp/wazero/instance.go b/interp/wazero/instance.go new file mode 100644 index 0000000..928e881 --- /dev/null +++ b/interp/wazero/instance.go @@ -0,0 +1,79 @@ +package wazero + +import ( + "context" + "errors" + + "github.com/tetratelabs/wazero/api" +) + +type Instance struct { + module api.Module +} + +func (i *Instance) Call(name string, args ...any) (any, error) { + f := i.module.ExportedFunction(name) + if f == nil { + return nil, errors.New("Function not found") + } + ctx := context.Background() + rawResults, err := f.Call(ctx, encodeArgs(args)...) + if err != nil { + return nil, err + } + results := decodeResults(rawResults, f.Definition().ResultTypes()) + if len(results) == 0 { + return nil, nil + } + if len(results) == 1 { + return results[0], nil + } + return results, nil +} + +func encodeArgs(args []any) []uint64 { + encoded := make([]uint64, len(args)) + for _, arg := range args { + encoded = append(encoded, encodeArg(arg)) + } + return encoded +} + +func encodeArg(arg any) uint64 { + switch val := arg.(type) { + case int32: + return api.EncodeI32(val) + case int64: + return api.EncodeI64(val) + case float32: + return api.EncodeF32(val) + case float64: + return api.EncodeF64(val) + case uint32: + return api.EncodeU32(val) + } + panic("bad arg type") +} + +func decodeResults(results []uint64, vtypes []api.ValueType) []any { + decoded := make([]any, len(results)) + for i, result := range results { + vtype := vtypes[i] + decoded = append(decoded, decodeResult(result, vtype)) + } + return decoded +} + +func decodeResult(result uint64, vtype api.ValueType) any { + switch vtype { + case api.ValueTypeF32: + return api.DecodeF32(result) + case api.ValueTypeF64: + return api.DecodeF64(result) + case api.ValueTypeI32: + return api.DecodeI32(result) + case api.ValueTypeI64: + return api.DecodeI32(result) + } + panic("unreachable") +} diff --git a/interp/wazero/interp.go b/interp/wazero/interp.go new file mode 100644 index 0000000..f35dab7 --- /dev/null +++ b/interp/wazero/interp.go @@ -0,0 +1,107 @@ +package wazero + +import ( + "context" + "errors" + "io" + + "github.com/hybridgroup/mechanoid/engine" + "github.com/tetratelabs/wazero" + "github.com/tetratelabs/wazero/api" +) + +type Interpreter struct { + runtime wazero.Runtime + defs map[string]map[string]any + module api.Module +} + +func (i *Interpreter) Name() string { + return "wazero" +} + +func (i *Interpreter) Init() error { + ctx := context.Background() + conf := wazero.NewRuntimeConfigInterpreter() + conf = conf.WithDebugInfoEnabled(false) + conf = conf.WithMemoryLimitPages(1) + i.runtime = wazero.NewRuntimeWithConfig(ctx, conf) + return nil +} + +func (i *Interpreter) DefineFunc(moduleName, funcName string, f any) error { + if i.defs == nil { + i.defs = make(map[string]map[string]any) + } + if _, exists := i.defs[moduleName]; !exists { + i.defs[moduleName] = make(map[string]any) + } + i.defs[moduleName][funcName] = f + return nil +} + +func (i *Interpreter) Load(code []byte) error { + var err error + ctx := context.Background() + conf := wazero.NewModuleConfig() + conf = conf.WithRandSource(cheapRand{}) + for moduleName, funcs := range i.defs { + b := i.runtime.NewHostModuleBuilder(moduleName) + for funcName, f := range funcs { + b = b.NewFunctionBuilder().WithFunc(f).Export(funcName) + } + compiled, err := b.Compile(ctx) + if err != nil { + return err + } + _, err = i.runtime.InstantiateModule(ctx, compiled, conf) + if err != nil { + return err + } + } + i.module, err = i.runtime.InstantiateWithConfig(ctx, code, conf) + return err +} + +func (i *Interpreter) Run() (engine.Instance, error) { + var err error + ctx := context.Background() + init := i.module.ExportedFunction("_initialize") + if init != nil { + _, err = init.Call(ctx) + if err != nil { + return nil, err + } + } + return &Instance{i.module}, nil +} + +func (i *Interpreter) Halt() error { + ctx := context.Background() + err := i.runtime.Close(ctx) + i.runtime = nil + return err +} + +func (i *Interpreter) MemoryData(ptr, sz uint32) ([]byte, error) { + memory := i.module.ExportedMemory("memory") + if memory == nil { + return nil, errors.New("memory not found") + } + data, inRange := memory.Read(ptr, sz) + if !inRange { + return nil, errors.New("out of range memory access") + } + return data, nil +} + +// A fake RandSource for having fewer memory allocations. +// +// Should not be used with modules that do need an access to random functions. +type cheapRand struct{} + +var _ io.Reader = cheapRand{} + +func (cheapRand) Read(b []byte) (int, error) { + return len(b), nil +}