Skip to content

Commit

Permalink
Wazero (#1)
Browse files Browse the repository at this point in the history
wazero: adding initial support
  • Loading branch information
orsinium authored Feb 26, 2024
1 parent 24f5130 commit 05bc406
Show file tree
Hide file tree
Showing 5 changed files with 204 additions and 46 deletions.
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
59 changes: 13 additions & 46 deletions interp/wasman/interp.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
79 changes: 79 additions & 0 deletions interp/wazero/instance.go
Original file line number Diff line number Diff line change
@@ -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")
}
107 changes: 107 additions & 0 deletions interp/wazero/interp.go
Original file line number Diff line number Diff line change
@@ -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
}

0 comments on commit 05bc406

Please sign in to comment.