├── internal └── wasmtest │ ├── memory.go │ ├── call.go │ ├── types.go │ └── module.go ├── go.mod ├── go.sum ├── testdata └── answer.wat ├── Makefile ├── .gitignore ├── wasm ├── wasm.go └── memory.go ├── options.go ├── LICENSE ├── module_test.go ├── decorator.go ├── function_test.go ├── types ├── format.go ├── types_test.go └── types.go ├── module.go ├── README.md └── function.go /internal/wasmtest/memory.go: -------------------------------------------------------------------------------- 1 | package wasmtest 2 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/stealthrocket/wazergo 2 | 3 | go 1.20 4 | 5 | require github.com/tetratelabs/wazero v1.1.0 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/tetratelabs/wazero v1.1.0 h1:EByoAhC+QcYpwSZJSs/aV0uokxPwBgKxfiokSUwAknQ= 2 | github.com/tetratelabs/wazero v1.1.0/go.mod h1:wYx2gNRg8/WihJfSDxA1TIL8H+GkfLYm+bIfbblu9VQ= 3 | -------------------------------------------------------------------------------- /testdata/answer.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func (result i32))) 3 | (import "test" "answer" (func $__imported_answer (type 0))) 4 | (func $answer (type 0) (result i32) 5 | call $__imported_answer) 6 | (export "answer" (func $answer))) 7 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean test testdata 2 | 3 | testdata.wat = $(wildcard testdata/*.wat) 4 | testdata.wasm = $(testdata.wat:.wat=.wasm) 5 | 6 | test: testdata 7 | go test -v ./... 8 | 9 | testdata: $(testdata.wasm) 10 | 11 | clean: 12 | rm -f $(testdata.wasm) 13 | 14 | %.wasm: %.wat 15 | wat2wasm -o $@ $< 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | *~ 8 | 9 | # Test binary, built with `go test -c` 10 | *.test 11 | *.wasm 12 | 13 | # Output of the go coverage tool, specifically when used with LiteIDE 14 | *.out 15 | 16 | # Dependency directories (remove the comment below to include it) 17 | # vendor/ 18 | -------------------------------------------------------------------------------- /internal/wasmtest/call.go: -------------------------------------------------------------------------------- 1 | package wasmtest 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/stealthrocket/wazergo" 7 | "github.com/stealthrocket/wazergo/types" 8 | "github.com/tetratelabs/wazero/api" 9 | ) 10 | 11 | func Call[R types.Param[R], T any](fn wazergo.Function[T], ctx context.Context, module api.Module, this T, args ...types.Result) (ret R) { 12 | malloc = 0 13 | 14 | stack := make([]uint64, max(fn.NumParams(), fn.NumResults())) 15 | memory := module.Memory() 16 | offset := 0 17 | 18 | for _, arg := range args { 19 | arg.StoreValue(memory, stack[offset:]) 20 | offset += len(arg.ValueTypes()) 21 | } 22 | 23 | fn.Func(this, ctx, module, stack) 24 | return ret.LoadValue(memory, stack) 25 | } 26 | 27 | func max(a, b int) int { 28 | if a > b { 29 | return a 30 | } 31 | return b 32 | } 33 | -------------------------------------------------------------------------------- /wasm/wasm.go: -------------------------------------------------------------------------------- 1 | // Package wasm provides the generic components used to build wazero plugins. 2 | package wasm 3 | 4 | import ( 5 | "fmt" 6 | 7 | "github.com/tetratelabs/wazero/api" 8 | ) 9 | 10 | // SEGFAULT is an error type used as value in panics triggered by reading 11 | // outside of the addressable memory of a program. 12 | type SEGFAULT struct{ Offset, Length uint32 } 13 | 14 | func (err SEGFAULT) Error() string { 15 | return fmt.Sprintf("segmentation fault: @%08x/%d", err.Offset, err.Length) 16 | } 17 | 18 | // Read returns a byte slice from a module memory. The function calls Read on 19 | // the given memory and panics if offset/length are beyond the range of memory. 20 | func Read(memory api.Memory, offset, length uint32) []byte { 21 | b, ok := memory.Read(offset, length) 22 | if !ok { 23 | panic(SEGFAULT{offset, length}) 24 | } 25 | return b 26 | } 27 | -------------------------------------------------------------------------------- /options.go: -------------------------------------------------------------------------------- 1 | package wazergo 2 | 3 | // Option is a generic interface used to represent options that apply 4 | // configuration to a value. 5 | type Option[T any] interface { 6 | // Configure is called to apply the configuration option to the value passed 7 | // as argument. 8 | Configure(T) 9 | } 10 | 11 | // OptionFunc is a constructor which creates an option from a function. 12 | // This function is useful to leverage type inference and not have to repeat 13 | // the type T in the type parameter. 14 | func OptionFunc[T any](opt func(T)) Option[T] { return option[T](opt) } 15 | 16 | type option[T any] func(T) 17 | 18 | func (option option[T]) Configure(value T) { option(value) } 19 | 20 | // Configure applies the list of options to the value passed as first argument. 21 | func Configure[T any](value T, options ...Option[T]) { 22 | for _, opt := range options { 23 | opt.Configure(value) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Stealth Rocket 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /internal/wasmtest/types.go: -------------------------------------------------------------------------------- 1 | package wasmtest 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/stealthrocket/wazergo/types" 7 | "github.com/stealthrocket/wazergo/wasm" 8 | "github.com/tetratelabs/wazero/api" 9 | ) 10 | 11 | var malloc uint32 12 | 13 | func brk(memory api.Memory, offset, length uint32) ([]byte, uint32) { 14 | b := wasm.Read(memory, offset, length) 15 | malloc = offset + length 16 | return b, offset 17 | } 18 | 19 | func sbrk(memory api.Memory, size uint32) ([]byte, uint32) { 20 | return brk(memory, malloc, size) 21 | } 22 | 23 | // Bytes is an extension of the types.Bytes type which adds the ability to treat 24 | // those values as results, so they can be passed as argument to Call* functions 25 | // in tests. The content of the byte slice is copied to the WebAssembly module 26 | // memory, starting at address zero in each invocation of Call* functions. This 27 | // would be really unsafe to do in a production applicaiton, which is why the 28 | // feature is only made available to unit tests. 29 | type Bytes types.Bytes 30 | 31 | func (arg Bytes) FormatValue(w io.Writer, memory api.Memory, stack []uint64) { 32 | types.Bytes(arg).FormatValue(w, memory, stack) 33 | } 34 | 35 | func (arg Bytes) LoadValue(memory api.Memory, stack []uint64) Bytes { 36 | return Bytes(types.Bytes(arg).LoadValue(memory, stack)) 37 | } 38 | 39 | func (arg Bytes) StoreValue(memory api.Memory, stack []uint64) { 40 | b, offset := sbrk(memory, uint32(len(arg))) 41 | copy(b, arg) 42 | stack[0] = api.EncodeU32(offset) 43 | stack[1] = api.EncodeU32(uint32(len(arg))) 44 | } 45 | 46 | func (arg Bytes) ValueTypes() []api.ValueType { 47 | return types.Bytes(arg).ValueTypes() 48 | } 49 | -------------------------------------------------------------------------------- /internal/wasmtest/module.go: -------------------------------------------------------------------------------- 1 | package wasmtest 2 | 3 | import ( 4 | "github.com/stealthrocket/wazergo" 5 | "github.com/tetratelabs/wazero/api" 6 | ) 7 | 8 | // Module is an implementation of wazero's api.Module interface intended to be 9 | // used as a stub in tests. The main feature is the ability to define a memory 10 | // that the module will be exposing as its exported "memory". 11 | type Module struct { 12 | api.Module // TODO: implement more features of the interface 13 | name string 14 | memory moduleMemory 15 | } 16 | 17 | // ModuleOption represents configuration options for the Module type. 18 | type ModuleOption = wazergo.Option[*Module] 19 | 20 | // Memory sets the memory of a Module instance. 21 | func Memory(memory api.Memory) ModuleOption { 22 | return wazergo.OptionFunc(func(module *Module) { module.memory.Memory = memory }) 23 | } 24 | 25 | // NewModule constructs a Module instance with the given name and configuration 26 | // options. 27 | func NewModule(name string, opts ...ModuleOption) *Module { 28 | module := &Module{name: name} 29 | module.memory.module = module 30 | wazergo.Configure(module, opts...) 31 | return module 32 | } 33 | 34 | func (mod *Module) Name() string { return mod.name } 35 | 36 | func (mod *Module) Memory() api.Memory { return &mod.memory } 37 | 38 | func (mod *Module) ExportedMemory(name string) api.Memory { 39 | switch name { 40 | case "memory": 41 | return &mod.memory 42 | default: 43 | return nil 44 | } 45 | } 46 | 47 | type moduleMemory struct { 48 | module *Module 49 | api.Memory 50 | } 51 | 52 | func (mem *moduleMemory) Definition() api.MemoryDefinition { 53 | return &moduleMemoryDefinition{mem.module, mem.Memory.Definition()} 54 | } 55 | 56 | type moduleMemoryDefinition struct { 57 | module *Module 58 | api.MemoryDefinition 59 | } 60 | 61 | func (def *moduleMemoryDefinition) ModuleName() string { 62 | return def.module.name 63 | } 64 | 65 | func (def *moduleMemoryDefinition) ExportNames() []string { 66 | return []string{"memory"} 67 | } 68 | -------------------------------------------------------------------------------- /module_test.go: -------------------------------------------------------------------------------- 1 | package wazergo_test 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "testing" 7 | 8 | "github.com/stealthrocket/wazergo" 9 | . "github.com/stealthrocket/wazergo/types" 10 | "github.com/tetratelabs/wazero" 11 | "github.com/tetratelabs/wazero/api" 12 | ) 13 | 14 | var hostModule wazergo.HostModule[*hostInstance] = hostFunctions{ 15 | "answer": wazergo.F0((*hostInstance).Answer), 16 | } 17 | 18 | type hostFunctions wazergo.Functions[*hostInstance] 19 | 20 | func (m hostFunctions) Name() string { 21 | return "test" 22 | } 23 | 24 | func (m hostFunctions) Functions() wazergo.Functions[*hostInstance] { 25 | return (wazergo.Functions[*hostInstance](m)) 26 | } 27 | 28 | func (m hostFunctions) Instantiate(ctx context.Context, opts ...wazergo.Option[*hostInstance]) (*hostInstance, error) { 29 | ins := new(hostInstance) 30 | wazergo.Configure(ins, opts...) 31 | return ins, nil 32 | } 33 | 34 | type hostInstance struct { 35 | answer int 36 | } 37 | 38 | func (m *hostInstance) Close(ctx context.Context) error { 39 | return nil 40 | } 41 | 42 | func (m *hostInstance) Answer(ctx context.Context) Int32 { 43 | return Int32(m.answer) 44 | } 45 | 46 | func answer(a int) wazergo.Option[*hostInstance] { 47 | return wazergo.OptionFunc(func(m *hostInstance) { m.answer = a }) 48 | } 49 | 50 | func TestMultipleHostModuleInstances(t *testing.T) { 51 | ctx := context.Background() 52 | 53 | runtime := wazero.NewRuntime(ctx) 54 | defer runtime.Close(ctx) 55 | 56 | // three copies, all share the same host module name but different state 57 | instance0 := wazergo.MustInstantiate(ctx, runtime, hostModule, answer(0)) 58 | instance1 := wazergo.MustInstantiate(ctx, runtime, hostModule, answer(21)) 59 | instance2 := wazergo.MustInstantiate(ctx, runtime, hostModule, answer(42)) 60 | 61 | defer instance0.Close(ctx) 62 | defer instance1.Close(ctx) 63 | defer instance2.Close(ctx) 64 | 65 | guest, err := loadModule(ctx, runtime, "testdata/answer.wasm") 66 | if err != nil { 67 | t.Fatal(err) 68 | } 69 | defer guest.Close(ctx) 70 | 71 | answer := guest.ExportedFunction("answer") 72 | r0, _ := answer.Call(wazergo.WithModuleInstance(ctx, instance0)) 73 | r1, _ := answer.Call(wazergo.WithModuleInstance(ctx, instance1)) 74 | r2, _ := answer.Call(wazergo.WithModuleInstance(ctx, instance2)) 75 | 76 | for i, test := range [...]struct{ want, got int }{ 77 | {want: 0, got: int(r0[0])}, 78 | {want: 21, got: int(r1[0])}, 79 | {want: 42, got: int(r2[0])}, 80 | } { 81 | if test.want != test.got { 82 | t.Errorf("result %d is wrong: want=%d got=%d", i, test.want, test.got) 83 | } 84 | } 85 | } 86 | 87 | func loadModule(ctx context.Context, runtime wazero.Runtime, filePath string) (api.Module, error) { 88 | b, err := os.ReadFile(filePath) 89 | if err != nil { 90 | return nil, err 91 | } 92 | return runtime.Instantiate(ctx, b) 93 | } 94 | -------------------------------------------------------------------------------- /decorator.go: -------------------------------------------------------------------------------- 1 | package wazergo 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | "log" 8 | "strings" 9 | 10 | . "github.com/stealthrocket/wazergo/types" 11 | "github.com/tetratelabs/wazero/api" 12 | ) 13 | 14 | // Decorator is an interface type which applies a transformation to a function. 15 | type Decorator[T Module] interface { 16 | Decorate(module string, fn Function[T]) Function[T] 17 | } 18 | 19 | // DecoratorFunc is a helper used to create decorators from functions using type 20 | // inference to keep the syntax simple. 21 | func DecoratorFunc[T Module](d func(string, Function[T]) Function[T]) Decorator[T] { 22 | return decoratorFunc[T](d) 23 | } 24 | 25 | type decoratorFunc[T Module] func(string, Function[T]) Function[T] 26 | 27 | func (d decoratorFunc[T]) Decorate(module string, fn Function[T]) Function[T] { return d(module, fn) } 28 | 29 | // Log constructs a function decorator which adds logging to function calls. 30 | func Log[T Module](logger *log.Logger) Decorator[T] { 31 | return DecoratorFunc(func(module string, fn Function[T]) Function[T] { 32 | if logger == nil { 33 | return fn 34 | } 35 | n := fn.NumParams() 36 | return fn.WithFunc(func(this T, ctx context.Context, module api.Module, stack []uint64) { 37 | params := make([]uint64, n) 38 | copy(params, stack) 39 | 40 | panicked := true 41 | defer func() { 42 | memory := module.Memory() 43 | buffer := new(strings.Builder) 44 | defer logger.Printf("%s", buffer) 45 | 46 | fmt.Fprintf(buffer, "%s::%s(", module, fn.Name) 47 | formatValues(buffer, memory, params, fn.Params) 48 | fmt.Fprintf(buffer, ")") 49 | 50 | if panicked { 51 | fmt.Fprintf(buffer, " PANIC!") 52 | } else { 53 | fmt.Fprintf(buffer, " → ") 54 | formatValues(buffer, memory, stack, fn.Results) 55 | } 56 | }() 57 | 58 | fn.Func(this, ctx, module, stack) 59 | panicked = false 60 | }) 61 | }) 62 | } 63 | 64 | func formatValues(w io.Writer, memory api.Memory, stack []uint64, values []Value) { 65 | for i, v := range values { 66 | if i > 0 { 67 | fmt.Fprintf(w, ", ") 68 | } 69 | v.FormatValue(w, memory, stack) 70 | stack = stack[len(v.ValueTypes()):] 71 | } 72 | } 73 | 74 | // Decorate returns a version of the given host module where the decorators were 75 | // applied to all its functions. 76 | func Decorate[T Module](mod HostModule[T], decorators ...Decorator[T]) HostModule[T] { 77 | functions := mod.Functions() 78 | decorated := &decoratedHostModule[T]{ 79 | hostModule: mod, 80 | functions: make(Functions[T], len(functions)), 81 | } 82 | moduleName := mod.Name() 83 | for name, function := range functions { 84 | for _, decorator := range decorators { 85 | function = decorator.Decorate(moduleName, function) 86 | } 87 | decorated.functions[name] = function 88 | } 89 | return decorated 90 | } 91 | 92 | type decoratedHostModule[T Module] struct { 93 | hostModule HostModule[T] 94 | functions Functions[T] 95 | } 96 | 97 | func (m *decoratedHostModule[T]) Name() string { 98 | return m.hostModule.Name() 99 | } 100 | 101 | func (m *decoratedHostModule[T]) Functions() Functions[T] { 102 | return m.functions 103 | } 104 | 105 | func (m *decoratedHostModule[T]) Instantiate(ctx context.Context, options ...Option[T]) (T, error) { 106 | return m.hostModule.Instantiate(ctx, options...) 107 | } 108 | -------------------------------------------------------------------------------- /function_test.go: -------------------------------------------------------------------------------- 1 | package wazergo_test 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "reflect" 7 | "strconv" 8 | "testing" 9 | 10 | . "github.com/stealthrocket/wazergo" 11 | "github.com/stealthrocket/wazergo/internal/wasmtest" 12 | . "github.com/stealthrocket/wazergo/types" 13 | "github.com/stealthrocket/wazergo/wasm" 14 | "github.com/tetratelabs/wazero/api" 15 | ) 16 | 17 | type value[T any] ParamResult[T] 18 | 19 | type instance struct{} 20 | 21 | func (*instance) Close(context.Context) error { return nil } 22 | 23 | type plugin struct{} 24 | 25 | func (plugin) Name() string { return "test" } 26 | func (plugin) Functions() Functions[*instance] { return nil } 27 | func (plugin) Instantiate(...Option[*instance]) *instance { return nil } 28 | 29 | func TestFunc0(t *testing.T) { 30 | oops := errors.New("oops") 31 | testFunc0(t, 1, func(*instance, context.Context) Int32 { return 1 }) 32 | testFunc0(t, 2, func(*instance, context.Context) Int64 { return 2 }) 33 | testFunc0(t, 3, func(*instance, context.Context) Uint32 { return 3 }) 34 | testFunc0(t, 4, func(*instance, context.Context) Uint64 { return 4 }) 35 | testFunc0(t, 0.1, func(*instance, context.Context) Float32 { return 0.1 }) 36 | testFunc0(t, 0.5, func(*instance, context.Context) Float64 { return 0.5 }) 37 | testFunc0(t, OK, func(*instance, context.Context) Error { return OK }) 38 | testFunc0(t, Fail(^Errno(0)), func(*instance, context.Context) Error { return Fail(oops) }) 39 | } 40 | 41 | func TestFunc1(t *testing.T) { 42 | testFunc1(t, 42, 42, func(this *instance, ctx context.Context, v Int32) Int32 { 43 | return v 44 | }) 45 | testFunc1(t, Res(Int32(42)), wasmtest.Bytes("42"), 46 | func(this *instance, ctx context.Context, v wasmtest.Bytes) Optional[Int32] { 47 | i, err := strconv.Atoi(string(v)) 48 | return Opt(Int32(i), err) 49 | }, 50 | ) 51 | } 52 | 53 | func TestFunc2(t *testing.T) { 54 | testFunc2(t, Res(Int32(41)), wasmtest.Bytes("42"), wasmtest.Bytes("-1"), 55 | func(this *instance, ctx context.Context, v1, v2 wasmtest.Bytes) Optional[Int32] { 56 | i1, _ := strconv.Atoi(string(v1)) 57 | i2, _ := strconv.Atoi(string(v2)) 58 | return Res(Int32(i1 + i2)) 59 | }, 60 | ) 61 | } 62 | 63 | func testFunc(t *testing.T, opts []Option[*instance], test func(*instance, context.Context, api.Module)) { 64 | t.Helper() 65 | memory := wasm.NewFixedSizeMemory(wasm.PageSize) 66 | module := wasmtest.NewModule("test", wasmtest.Memory(memory)) 67 | test(new(instance), context.Background(), module) 68 | } 69 | 70 | func testFunc0[R value[R]](t *testing.T, want R, f func(*instance, context.Context) R, opts ...Option[*instance]) { 71 | t.Helper() 72 | testFunc(t, opts, func(this *instance, ctx context.Context, module api.Module) { 73 | t.Helper() 74 | assertEqual(t, want, wasmtest.Call[R](F0(f), ctx, module, this)) 75 | }) 76 | } 77 | 78 | func testFunc1[R value[R], T value[T]](t *testing.T, want R, arg T, f func(*instance, context.Context, T) R, opts ...Option[*instance]) { 79 | t.Helper() 80 | testFunc(t, opts, func(this *instance, ctx context.Context, module api.Module) { 81 | t.Helper() 82 | assertEqual(t, want, wasmtest.Call[R](F1(f), ctx, module, this, arg)) 83 | }) 84 | } 85 | 86 | func testFunc2[R value[R], T1 value[T1], T2 value[T2]](t *testing.T, want R, arg1 T1, arg2 T2, f func(*instance, context.Context, T1, T2) R, opts ...Option[*instance]) { 87 | t.Helper() 88 | testFunc(t, opts, func(this *instance, ctx context.Context, module api.Module) { 89 | t.Helper() 90 | assertEqual(t, want, wasmtest.Call[R](F2(f), ctx, module, this, arg1, arg2)) 91 | }) 92 | } 93 | 94 | func assertEqual(t *testing.T, want, got any) { 95 | t.Helper() 96 | if !reflect.DeepEqual(want, got) { 97 | t.Errorf("result mismatch: want=%+v got=%+v", want, got) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /types/format.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "reflect" 7 | ) 8 | 9 | // Formatter is an interface used to customize the behavior of Format. 10 | type Formatter interface { 11 | Format(io.Writer) 12 | } 13 | 14 | // Format is a helper function which can be used in the implementation of the 15 | // FormatValue and FormatObject methods of Value and Object[T]. 16 | // 17 | // The code below represents a common usage pattern: 18 | // 19 | // func (v T) FormatObject(w io.Writer, memory api.Memory, object []byte) { 20 | // types.Format(w, v.LoadObject(memory, object)) 21 | // } 22 | // 23 | // If T is a struct type, the output is wrapped in "{...}" and the struct fields 24 | // are iterated and printed as comma-separated "name:value" pairs. The name may 25 | // be customized by defining a "name" struct field tag such as: 26 | // 27 | // type T struct { 28 | // Field int32 `name:"field"` 29 | // } 30 | // 31 | // If any of the values impelement the Formatter or fmt.Stringer interfaces, 32 | // formatting is delegated to those methods. 33 | // 34 | // The implementation of Format has to use reflection, so it may not be best 35 | // suited to use in contexts where performance is critical, in which cases the 36 | // program is better off providing a custom implementation of the method. 37 | func Format(w io.Writer, v any) { format(w, reflect.ValueOf(v)) } 38 | 39 | var ( 40 | formatterInterface = reflect.TypeOf((*Formatter)(nil)).Elem() 41 | stringerInterface = reflect.TypeOf((*fmt.Stringer)(nil)).Elem() 42 | ) 43 | 44 | func format(w io.Writer, v reflect.Value) { 45 | // TODO: to improve performance we could generate the formatters once and 46 | // keep track of them in a cache (e.g. similar to what encoding/json does). 47 | t := v.Type() 48 | if t.Implements(formatterInterface) { 49 | v.Interface().(Formatter).Format(w) 50 | return 51 | } 52 | if t.Implements(stringerInterface) { 53 | io.WriteString(w, v.Interface().(fmt.Stringer).String()) 54 | return 55 | } 56 | switch t.Kind() { 57 | case reflect.Bool: 58 | formatBool(w, v.Bool()) 59 | case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: 60 | formatInt(w, v.Int()) 61 | case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint, reflect.Uintptr: 62 | formatUint(w, v.Uint()) 63 | case reflect.Float32, reflect.Float64: 64 | formatFloat(w, v.Float()) 65 | case reflect.String: 66 | formatString(w, v.String()) 67 | case reflect.Array: 68 | formatArray(w, v) 69 | case reflect.Slice: 70 | if v.Type().Elem().Kind() == reflect.Uint8 { 71 | formatBytes(w, v.Bytes()) 72 | } else { 73 | formatArray(w, v) 74 | } 75 | case reflect.Struct: 76 | formatStruct(w, v) 77 | case reflect.Pointer: 78 | formatPointer(w, v) 79 | default: 80 | formatUnsupported(w, v) 81 | } 82 | } 83 | 84 | func formatBool(w io.Writer, v bool) { 85 | fmt.Fprintf(w, "%t", v) 86 | } 87 | 88 | func formatInt(w io.Writer, v int64) { 89 | fmt.Fprintf(w, "%d", v) 90 | } 91 | 92 | func formatUint(w io.Writer, v uint64) { 93 | fmt.Fprintf(w, "%d", v) 94 | } 95 | 96 | func formatFloat(w io.Writer, v float64) { 97 | fmt.Fprintf(w, "%g", v) 98 | } 99 | 100 | func formatString(w io.Writer, v string) { 101 | fmt.Fprintf(w, "%q", v) 102 | } 103 | 104 | func formatBytes(w io.Writer, v []byte) { 105 | Bytes(v).Format(w) 106 | } 107 | 108 | func formatArray(w io.Writer, v reflect.Value) { 109 | io.WriteString(w, "[") 110 | for i, n := 0, v.Len(); i < n; i++ { 111 | if i != 0 { 112 | io.WriteString(w, ",") 113 | } 114 | format(w, v.Index(i)) 115 | } 116 | io.WriteString(w, "]") 117 | } 118 | 119 | func formatStruct(w io.Writer, v reflect.Value) { 120 | io.WriteString(w, "{") 121 | t := v.Type() 122 | for i, f := range reflect.VisibleFields(t) { 123 | if i != 0 { 124 | io.WriteString(w, ",") 125 | } 126 | name := f.Tag.Get("name") 127 | if name == "" { 128 | name = f.Name 129 | } 130 | io.WriteString(w, name) 131 | io.WriteString(w, ":") 132 | format(w, v.FieldByIndex(f.Index)) 133 | } 134 | io.WriteString(w, "}") 135 | } 136 | 137 | func formatPointer(w io.Writer, v reflect.Value) { 138 | if v.IsNil() { 139 | io.WriteString(w, "") 140 | } else { 141 | format(w, v.Elem()) 142 | } 143 | } 144 | 145 | func formatUnsupported(w io.Writer, v reflect.Value) { 146 | fmt.Fprintf(w, "<%s>", v.Type().Name()) 147 | } 148 | -------------------------------------------------------------------------------- /wasm/memory.go: -------------------------------------------------------------------------------- 1 | package wasm 2 | 3 | import ( 4 | "encoding/binary" 5 | "math" 6 | 7 | "github.com/tetratelabs/wazero/api" 8 | ) 9 | 10 | // PageSize is the size of memory pages in WebAssembly programs (64 KiB). 11 | const PageSize = 64 * 1024 12 | 13 | func ceil(size uint32) uint32 { 14 | size += PageSize - 1 15 | size /= PageSize 16 | size *= PageSize 17 | return size 18 | } 19 | 20 | type memoryDefinition struct{ *Memory } 21 | 22 | func (def memoryDefinition) ModuleName() string { return "" } 23 | 24 | func (def memoryDefinition) Index() uint32 { return 0 } 25 | 26 | func (def memoryDefinition) Import() (moduleName, name string, isImport bool) { return } 27 | 28 | func (def memoryDefinition) ExportNames() []string { return nil } 29 | 30 | func (def memoryDefinition) Min() uint32 { return 0 } 31 | 32 | func (def memoryDefinition) Max() (uint32, bool) { return ceil(uint32(len(def.memory))), true } 33 | 34 | // Memory is an implementation of the api.Memory interface of wazero backed by 35 | // a Go byte slice. The memory has a fixed size and cannot grow nor shrink. 36 | // 37 | // This type is mostly useful in tests to construct memory areas where output 38 | // parameters can be stored. 39 | type Memory struct { 40 | memory []byte 41 | api.Memory 42 | } 43 | 44 | // NewFixedSizeMemory constructs a Memory instance of size bytes aligned on the 45 | // WebAssembly page size. 46 | func NewFixedSizeMemory(size uint32) *Memory { 47 | return &Memory{ 48 | memory: make([]byte, ceil(size)), 49 | } 50 | } 51 | 52 | func (mem *Memory) Definition() api.MemoryDefinition { return memoryDefinition{Memory: mem} } 53 | 54 | func (mem *Memory) Size() uint32 { return uint32(len(mem.memory)) } 55 | 56 | func (mem *Memory) Grow(uint32) (uint32, bool) { return ceil(uint32(len(mem.memory))), false } 57 | 58 | func (mem *Memory) ReadByte(offset uint32) (byte, bool) { 59 | if mem.isOutOfRange(offset, 1) { 60 | return 0, false 61 | } 62 | return mem.memory[offset], true 63 | } 64 | 65 | func (mem *Memory) ReadUint16Le(offset uint32) (uint16, bool) { 66 | if mem.isOutOfRange(offset, 2) { 67 | return 0, false 68 | } 69 | return binary.LittleEndian.Uint16(mem.memory[offset:]), true 70 | } 71 | 72 | func (mem *Memory) ReadUint32Le(offset uint32) (uint32, bool) { 73 | if mem.isOutOfRange(offset, 4) { 74 | return 0, false 75 | } 76 | return binary.LittleEndian.Uint32(mem.memory[offset:]), true 77 | } 78 | 79 | func (mem *Memory) ReadUint64Le(offset uint32) (uint64, bool) { 80 | if mem.isOutOfRange(offset, 8) { 81 | return 0, false 82 | } 83 | return binary.LittleEndian.Uint64(mem.memory[offset:]), true 84 | } 85 | 86 | func (mem *Memory) ReadFloat32Le(offset uint32) (float32, bool) { 87 | v, ok := mem.ReadUint32Le(offset) 88 | return math.Float32frombits(v), ok 89 | } 90 | 91 | func (mem *Memory) ReadFloat64Le(offset uint32) (float64, bool) { 92 | v, ok := mem.ReadUint64Le(offset) 93 | return math.Float64frombits(v), ok 94 | } 95 | 96 | func (mem *Memory) Read(offset, length uint32) ([]byte, bool) { 97 | if mem.isOutOfRange(offset, length) { 98 | return nil, false 99 | } 100 | return mem.memory[offset : offset+length : offset+length], true 101 | } 102 | 103 | func (mem *Memory) WriteByte(offset uint32, value byte) bool { 104 | if mem.isOutOfRange(offset, 1) { 105 | return false 106 | } 107 | mem.memory[offset] = value 108 | return true 109 | } 110 | 111 | func (mem *Memory) WriteUint16Le(offset uint32, value uint16) bool { 112 | if mem.isOutOfRange(offset, 2) { 113 | return false 114 | } 115 | binary.LittleEndian.PutUint16(mem.memory[offset:], value) 116 | return true 117 | } 118 | 119 | func (mem *Memory) WriteUint32Le(offset uint32, value uint32) bool { 120 | if mem.isOutOfRange(offset, 4) { 121 | return false 122 | } 123 | binary.LittleEndian.PutUint32(mem.memory[offset:], value) 124 | return true 125 | } 126 | 127 | func (mem *Memory) WriteUint64Le(offset uint32, value uint64) bool { 128 | if mem.isOutOfRange(offset, 4) { 129 | return false 130 | } 131 | binary.LittleEndian.PutUint64(mem.memory[offset:], value) 132 | return true 133 | } 134 | 135 | func (mem *Memory) WriteFloat32Le(offset uint32, value float32) bool { 136 | return mem.WriteUint32Le(offset, math.Float32bits(value)) 137 | } 138 | 139 | func (mem *Memory) WriteFloat64Le(offset uint32, value float64) bool { 140 | return mem.WriteUint64Le(offset, math.Float64bits(value)) 141 | } 142 | 143 | func (mem *Memory) Write(offset uint32, value []byte) bool { 144 | if mem.isOutOfRange(offset, uint32(len(value))) { 145 | return false 146 | } 147 | copy(mem.memory[offset:], value) 148 | return true 149 | } 150 | 151 | func (mem *Memory) WriteString(offset uint32, value string) bool { 152 | if mem.isOutOfRange(offset, uint32(len(value))) { 153 | return false 154 | } 155 | copy(mem.memory[offset:], value) 156 | return true 157 | } 158 | 159 | func (mem *Memory) isOutOfRange(offset, length uint32) bool { 160 | size := mem.Size() 161 | return offset >= size || length > size || offset > (size-length) 162 | } 163 | -------------------------------------------------------------------------------- /types/types_test.go: -------------------------------------------------------------------------------- 1 | package types_test 2 | 3 | import ( 4 | "io" 5 | "reflect" 6 | "strings" 7 | "testing" 8 | "unsafe" 9 | 10 | . "github.com/stealthrocket/wazergo/types" 11 | "github.com/tetratelabs/wazero/api" 12 | ) 13 | 14 | func TestLoadAndStoreValue(t *testing.T) { 15 | testLoadAndStoreValue(t, None{}) 16 | testLoadAndStoreValue(t, OK) 17 | 18 | testLoadAndStoreValue(t, Bool(false)) 19 | testLoadAndStoreValue(t, Bool(true)) 20 | 21 | testLoadAndStoreValue(t, Int8(-1)) 22 | testLoadAndStoreValue(t, Int16(-2)) 23 | testLoadAndStoreValue(t, Int32(-3)) 24 | testLoadAndStoreValue(t, Int64(-4)) 25 | 26 | testLoadAndStoreValue(t, Uint8(1)) 27 | testLoadAndStoreValue(t, Uint16(2)) 28 | testLoadAndStoreValue(t, Uint32(3)) 29 | testLoadAndStoreValue(t, Uint64(4)) 30 | 31 | testLoadAndStoreValue(t, Float32(0.1)) 32 | testLoadAndStoreValue(t, Float64(0.5)) 33 | 34 | testLoadAndStoreValue(t, Duration(0)) 35 | testLoadAndStoreValue(t, Duration(1e9)) 36 | } 37 | 38 | func testLoadAndStoreValue[T ParamResult[T]](t *testing.T, value T) { 39 | var loaded T 40 | var stack = make([]uint64, len(value.ValueTypes())) 41 | 42 | value.StoreValue(nil, stack) 43 | loaded = loaded.LoadValue(nil, stack) 44 | 45 | if !reflect.DeepEqual(value, loaded) { 46 | t.Errorf("values mismatch: want=%#v got=%#v", value, loaded) 47 | } 48 | 49 | for i := range stack { 50 | stack[i] = 0 51 | } 52 | 53 | var optionalValue Optional[T] 54 | var optionalLoaded Optional[T] 55 | 56 | stack = make([]uint64, len(optionalValue.ValueTypes())) 57 | optionalValue = Res(value) 58 | optionalValue.StoreValue(nil, stack) 59 | optionalLoaded = optionalLoaded.LoadValue(nil, stack) 60 | 61 | if !reflect.DeepEqual(optionalValue, optionalLoaded) { 62 | t.Errorf("optional values mismatch: want=%#v got=%#v", optionalValue, optionalLoaded) 63 | } 64 | } 65 | 66 | type Vec3d struct { 67 | X float32 `name:"x"` 68 | Y float32 `name:"y"` 69 | Z float32 `name:"z"` 70 | } 71 | 72 | func (v Vec3d) FormatObject(w io.Writer, m api.Memory, object []byte) { 73 | Format(w, v.LoadObject(m, object)) 74 | } 75 | 76 | func (v Vec3d) LoadObject(_ api.Memory, object []byte) Vec3d { 77 | return UnsafeLoadObject[Vec3d](object) 78 | } 79 | 80 | func (v Vec3d) StoreObject(_ api.Memory, object []byte) { 81 | UnsafeStoreObject[Vec3d](object, v) 82 | } 83 | 84 | func (v Vec3d) ObjectSize() int { 85 | return 12 86 | } 87 | 88 | func TestLoadAndStoreObject(t *testing.T) { 89 | testLoadAndStoreObject(t, None{}) 90 | 91 | testLoadAndStoreValue(t, Bool(false)) 92 | testLoadAndStoreValue(t, Bool(true)) 93 | 94 | testLoadAndStoreObject(t, Int8(-1)) 95 | testLoadAndStoreObject(t, Int16(-2)) 96 | testLoadAndStoreObject(t, Int32(-3)) 97 | testLoadAndStoreObject(t, Int64(-4)) 98 | 99 | testLoadAndStoreObject(t, Uint8(1)) 100 | testLoadAndStoreObject(t, Uint16(2)) 101 | testLoadAndStoreObject(t, Uint32(3)) 102 | testLoadAndStoreObject(t, Uint64(4)) 103 | 104 | testLoadAndStoreObject(t, Float32(0.1)) 105 | testLoadAndStoreObject(t, Float64(0.5)) 106 | 107 | testLoadAndStoreObject(t, Duration(0)) 108 | testLoadAndStoreObject(t, Duration(1e9)) 109 | 110 | testLoadAndStoreObject(t, Vec3d{1, 2, 3}) 111 | } 112 | 113 | func testLoadAndStoreObject[T Object[T]](t *testing.T, value T) { 114 | var loaded T 115 | var object = make([]byte, value.ObjectSize()) 116 | 117 | value.StoreObject(nil, object) 118 | loaded = loaded.LoadObject(nil, object) 119 | 120 | if !reflect.DeepEqual(value, loaded) { 121 | t.Errorf("objects mismatch: want=%#v got=%#v", value, loaded) 122 | } 123 | } 124 | 125 | type structType[T any] struct { 126 | value T 127 | } 128 | 129 | func (t structType[T]) FormatObject(w io.Writer, memory api.Memory, object []byte) { 130 | Format(w, t.LoadObject(memory, object).value) 131 | } 132 | 133 | func (t structType[T]) LoadObject(memory api.Memory, object []byte) structType[T] { 134 | return UnsafeLoadObject[structType[T]](object) 135 | } 136 | 137 | func (t structType[T]) StoreObject(memory api.Memory, object []byte) { 138 | UnsafeStoreObject(object, t) 139 | } 140 | 141 | func (t structType[T]) ObjectSize() int { 142 | return int(unsafe.Sizeof(t)) 143 | } 144 | 145 | func st[T any](v T) structType[T] { 146 | return structType[T]{value: v} 147 | } 148 | 149 | func TestFormatObject(t *testing.T) { 150 | testFormatObject(t, None{}, `(none)`) 151 | 152 | testFormatObject(t, Bool(false), `false`) 153 | testFormatObject(t, Bool(true), `true`) 154 | 155 | testFormatObject(t, Int8(-1), `-1`) 156 | testFormatObject(t, Int16(-2), `-2`) 157 | testFormatObject(t, Int32(-3), `-3`) 158 | testFormatObject(t, Int64(-4), `-4`) 159 | 160 | testFormatObject(t, Uint8(1), `1`) 161 | testFormatObject(t, Uint16(2), `2`) 162 | testFormatObject(t, Uint32(3), `3`) 163 | testFormatObject(t, Uint64(4), `4`) 164 | 165 | testFormatObject(t, Float32(0.1), `0.1`) 166 | testFormatObject(t, Float64(0.5), `0.5`) 167 | 168 | testFormatObject(t, Duration(0), `0s`) 169 | testFormatObject(t, Duration(1e9), `1s`) 170 | 171 | testFormatObject(t, Vec3d{1, 2, 3}, `{x:1,y:2,z:3}`) 172 | 173 | testFormatObject(t, st(struct{}{}), `{}`) 174 | testFormatObject(t, st(struct{ F bool }{false}), `{F:false}`) 175 | testFormatObject(t, st(struct{ F bool }{true}), `{F:true}`) 176 | testFormatObject(t, st(struct{ F int32 }{-1}), `{F:-1}`) 177 | testFormatObject(t, st(struct{ F uint64 }{42}), `{F:42}`) 178 | testFormatObject(t, st(struct{ F float64 }{0.5}), `{F:0.5}`) 179 | testFormatObject(t, st(struct{ F string }{"hello world"}), `{F:"hello world"}`) 180 | testFormatObject(t, st(struct{ F []byte }{[]byte("hello world")}), `{F:"hello world"}`) 181 | testFormatObject(t, st(struct{ F [3]int32 }{[3]int32{1, 2, 3}}), `{F:[1,2,3]}`) 182 | } 183 | 184 | func testFormatObject[T Object[T]](t *testing.T, value T, format string) { 185 | buffer := new(strings.Builder) 186 | object := make([]byte, value.ObjectSize()) 187 | 188 | value.StoreObject(nil, object) 189 | value.FormatObject(buffer, nil, object) 190 | 191 | if s := buffer.String(); s != format { 192 | t.Errorf("object format mismatch: want=%q got=%q", format, s) 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /module.go: -------------------------------------------------------------------------------- 1 | package wazergo 2 | 3 | import ( 4 | "context" 5 | 6 | . "github.com/stealthrocket/wazergo/types" 7 | "github.com/tetratelabs/wazero" 8 | "github.com/tetratelabs/wazero/api" 9 | ) 10 | 11 | // Module is a type constraint used to validate that all module instances 12 | // created from wazero host modules abide to the same set of requirements. 13 | type Module interface{ api.Closer } 14 | 15 | // HostModule is an interface representing type-safe wazero host modules. 16 | // The interface is parametrized on the module type that it instantiates. 17 | // 18 | // HostModule instances are expected to be immutable and therfore safe to use 19 | // concurrently from multiple goroutines. 20 | type HostModule[T Module] interface { 21 | // Returns the name of the host module (e.g. "wasi_snapshot_preview1"). 22 | Name() string 23 | // Returns the collection of functions exported by the host module. 24 | // The method may return the same value across multiple calls to this 25 | // method, the program is expected to treat it as a read-only value. 26 | Functions() Functions[T] 27 | // Creates a new instance of the host module type, using the list of options 28 | // passed as arguments to configure it. This method is intended to be called 29 | // automatically when instantiating a module via an instantiation context. 30 | Instantiate(ctx context.Context, options ...Option[T]) (T, error) 31 | } 32 | 33 | // Build builds the host module p in the wazero runtime r, returning the 34 | // instance of HostModuleBuilder that was created. This is a low level function 35 | // which is only exposed for certain advanced use cases where a program might 36 | // not be able to leverage Compile/Instantiate, most application should not need 37 | // to use this function. 38 | func Build[T Module](runtime wazero.Runtime, mod HostModule[T]) wazero.HostModuleBuilder { 39 | moduleName := mod.Name() 40 | builder := runtime.NewHostModuleBuilder(moduleName) 41 | 42 | for export, fn := range mod.Functions() { 43 | if fn.Name == "" { 44 | fn.Name = export 45 | } 46 | 47 | paramTypes := appendValueTypes(make([]api.ValueType, 0, fn.NumParams()), fn.Params) 48 | resultTypes := appendValueTypes(make([]api.ValueType, 0, fn.NumResults()), fn.Results) 49 | 50 | builder.NewFunctionBuilder(). 51 | WithGoModuleFunction(bind(fn.Func), paramTypes, resultTypes). 52 | WithName(fn.Name). 53 | Export(export) 54 | } 55 | 56 | return builder 57 | } 58 | 59 | func appendValueTypes(buffer []api.ValueType, values []Value) []api.ValueType { 60 | for _, v := range values { 61 | buffer = append(buffer, v.ValueTypes()...) 62 | } 63 | return buffer 64 | } 65 | 66 | func bind[T Module](f func(T, context.Context, api.Module, []uint64)) api.GoModuleFunction { 67 | return contextualizedGoModuleFunction[T](f) 68 | } 69 | 70 | type contextualizedGoModuleFunction[T Module] func(T, context.Context, api.Module, []uint64) 71 | 72 | func (f contextualizedGoModuleFunction[T]) Call(ctx context.Context, module api.Module, stack []uint64) { 73 | this := ctx.Value((*ModuleInstance[T])(nil)).(T) 74 | f(this, ctx, module, stack) 75 | } 76 | 77 | // CompiledModule represents a compiled version of a wazero host module. 78 | type CompiledModule[T Module] struct { 79 | HostModule HostModule[T] 80 | wazero.CompiledModule 81 | // The compiled module captures the runtime that it was compiled for since 82 | // instantiation of the host module must happen in the same runtime. 83 | // This prevents application from having to pass the runtime again when 84 | // instantiating the module, which is redundant and sometimes error prone 85 | // (e.g. the wrong runtime could be used during instantiation). 86 | runtime wazero.Runtime 87 | } 88 | 89 | // Compile compiles a wazero host module within the given context. 90 | func Compile[T Module](ctx context.Context, runtime wazero.Runtime, mod HostModule[T]) (*CompiledModule[T], error) { 91 | compiledModule, err := Build(runtime, mod).Compile(ctx) 92 | if err != nil { 93 | return nil, err 94 | } 95 | return &CompiledModule[T]{mod, compiledModule, runtime}, nil 96 | } 97 | 98 | // MustCompile is like Compile but it panics if there is an error. 99 | func MustCompile[T Module](ctx context.Context, runtime wazero.Runtime, mod HostModule[T]) *CompiledModule[T] { 100 | compiledModule, err := Compile(ctx, runtime, mod) 101 | if err != nil { 102 | panic(err) 103 | } 104 | return compiledModule 105 | } 106 | 107 | // Instantiate creates an instance of the compiled module for in the given runtime 108 | // 109 | // Instantiate may be called multiple times to create multiple copies of the host 110 | // module state. This is useful to allow the program to create scopes where the 111 | // state of the host module needs to bind uniquely to a subset of the guest 112 | // modules instantiated in the runtime. 113 | func (c *CompiledModule[T]) Instantiate(ctx context.Context, options ...Option[T]) (*ModuleInstance[T], error) { 114 | // TODO: relying on the name here may be inaccurate, we are making the 115 | // assumption that the program did not register a different module under 116 | // the same name. 117 | moduleName := c.HostModule.Name() 118 | module := c.runtime.Module(moduleName) 119 | if module == nil { 120 | config := wazero.NewModuleConfig().WithStartFunctions() 121 | m, err := c.runtime.InstantiateModule(ctx, c.CompiledModule, config) 122 | if err != nil { 123 | return nil, err 124 | } 125 | module = m 126 | } 127 | instance, err := c.HostModule.Instantiate(ctx, options...) 128 | if err != nil { 129 | return nil, err 130 | } 131 | return &ModuleInstance[T]{module, moduleName, instance}, nil 132 | } 133 | 134 | // ModuleInstance represents a module instance created from a compiled host module. 135 | type ModuleInstance[T Module] struct { 136 | api.Module 137 | moduleName string 138 | instance T 139 | } 140 | 141 | func (m *ModuleInstance[T]) String() string { 142 | return "module[" + m.moduleName + "]" 143 | } 144 | 145 | func (m *ModuleInstance[T]) Name() string { 146 | return m.moduleName 147 | } 148 | 149 | func (m *ModuleInstance[T]) ExportedFunction(name string) api.Function { 150 | if f := m.Module.ExportedFunction(name); f != nil { 151 | return &moduleInstanceFunction[T]{f, m} 152 | } 153 | return nil 154 | } 155 | 156 | func (m *ModuleInstance[T]) Close(ctx context.Context) error { 157 | return m.instance.Close(ctx) 158 | } 159 | 160 | func (m *ModuleInstance[T]) CloseWithExitCode(ctx context.Context, _ uint32) error { 161 | return m.Close(ctx) 162 | } 163 | 164 | type moduleInstanceFunction[T Module] struct { 165 | api.Function 166 | instance *ModuleInstance[T] 167 | } 168 | 169 | func (f *moduleInstanceFunction[T]) Call(ctx context.Context, params ...uint64) ([]uint64, error) { 170 | return f.Function.Call(WithModuleInstance(ctx, f.instance), params...) 171 | } 172 | 173 | func (f *moduleInstanceFunction[T]) CallWithStack(ctx context.Context, stack []uint64) error { 174 | return f.Function.CallWithStack(WithModuleInstance(ctx, f.instance), stack) 175 | } 176 | 177 | // Instantiate compiles and instantiates a host module. 178 | func Instantiate[T Module](ctx context.Context, runtime wazero.Runtime, mod HostModule[T], options ...Option[T]) (*ModuleInstance[T], error) { 179 | c, err := Compile[T](ctx, runtime, mod) 180 | if err != nil { 181 | return nil, err 182 | } 183 | return c.Instantiate(ctx, options...) 184 | } 185 | 186 | // MustInstantiate is like Instantiate but it panics if an error is encountered. 187 | func MustInstantiate[T Module](ctx context.Context, runtime wazero.Runtime, mod HostModule[T], options ...Option[T]) *ModuleInstance[T] { 188 | instance, err := Instantiate(ctx, runtime, mod, options...) 189 | if err != nil { 190 | panic(err) 191 | } 192 | return instance 193 | } 194 | 195 | // WithModuleInstance returns a Go context inheriting from ctx and containing 196 | // the state needed for module instantiated from wazero host module to properly 197 | // bind their methods to their receiver (e.g. the module instance). 198 | // 199 | // Use this function when calling methods of an instantiated WebAssenbly module 200 | // which may invoke exported functions of a wazero host module, for example: 201 | // 202 | // // The program first creates the modules instances for the host modules. 203 | // instance1 := wazergo.MustInstantiate(ctx, runtime, firstHostModule) 204 | // instance2 := wazergo.MustInstantiate(ctx, runtime, otherHostModule) 205 | // 206 | // ... 207 | // 208 | // // In this example the parent is the background context, but it might be any 209 | // // other Go context relevant to the application. 210 | // ctx := context.Background() 211 | // ctx = wazergo.WithModuleInstance(ctx, instance1) 212 | // ctx = wazergo.WithModuleInstance(ctx, instance2) 213 | // 214 | // start := module.ExportedFunction("_start") 215 | // r, err := start.Call(ctx) 216 | // if err != nil { 217 | // ... 218 | // } 219 | func WithModuleInstance[T Module](ctx context.Context, ins *ModuleInstance[T]) context.Context { 220 | return context.WithValue(ctx, (*ModuleInstance[T])(nil), ins.instance) 221 | } 222 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wazergo 2 | 3 | This package is a library of generic types intended to help create WebAssembly 4 | host modules for [wazero](https://github.com/tetratelabs/wazero). 5 | 6 | ## Motivation 7 | 8 | WebAssembly imports provide powerful features to express dependencies between 9 | modules. A module can invoke functions of another module by declaring imports 10 | which are mapped to exports of another module. Programs using wazero can create 11 | such modules entirely in Go to provide extensions built into the host: those are 12 | called *host modules*. 13 | 14 | When defining host modules, the Go program declares the list of exported 15 | functions using one of these two APIs of the 16 | [`wazero.HostFunctionBuilder`](https://pkg.go.dev/github.com/tetratelabs/wazero#HostFunctionBuilder): 17 | 18 | ```go 19 | // WithGoModuleFunction is an advanced feature for those who need higher 20 | // performance than WithFunc at the cost of more complexity. 21 | // 22 | // Here's an example addition function that loads operands from memory: 23 | // 24 | // builder.WithGoModuleFunction(api.GoModuleFunc(func(ctx context.Context, mod api.Module, params []uint64) []uint64 { 25 | // mem := m.Memory() 26 | // offset := uint32(params[0]) 27 | // 28 | // x, _ := mem.ReadUint32Le(ctx, offset) 29 | // y, _ := mem.ReadUint32Le(ctx, offset + 4) // 32 bits == 4 bytes! 30 | // sum := x + y 31 | // 32 | // return []uint64{sum} 33 | // }, []api.ValueType{api.ValueTypeI32, api.ValueTypeI32}, []api.ValueType{api.ValueTypeI32}) 34 | // 35 | // As you can see above, defining in this way implies knowledge of which 36 | // WebAssembly api.ValueType is appropriate for each parameter and result. 37 | // 38 | // ... 39 | // 40 | WithGoModuleFunction(fn api.GoModuleFunction, params, results []api.ValueType) HostFunctionBuilder 41 | ``` 42 | 43 | ```go 44 | // WithFunc uses reflect.Value to map a go `func` to a WebAssembly 45 | // compatible Signature. An input that isn't a `func` will fail to 46 | // instantiate. 47 | // 48 | // Here's an example of an addition function: 49 | // 50 | // builder.WithFunc(func(cxt context.Context, x, y uint32) uint32 { 51 | // return x + y 52 | // }) 53 | // 54 | // ... 55 | // 56 | WithFunc(interface{}) HostFunctionBuilder 57 | ``` 58 | 59 | The first is a low level API which offers the highest performance but also comes 60 | with usability challenges. The user needs to properly map the stack state to 61 | function parameters and return values, as well as declare the correspondingg 62 | function signature, _manually_ doing the mapping between Go and WebAssembly 63 | types. 64 | 65 | The second is a higher level API that most developers should probably prefer to 66 | use. However, it comes with limitations, both in terms of performance due to the 67 | use of reflection, but also usability since the parameters can only be primitive 68 | integer or floating point types: 69 | 70 | ```go 71 | // Except for the context.Context and optional api.Module, all parameters 72 | // or result types must map to WebAssembly numeric value types. This means 73 | // uint32, int32, uint64, int64, float32 or float64. 74 | ``` 75 | 76 | At [Stealth Rocket](https://github.com/stealthrocket), we leverage wazero as 77 | a core WebAssembly runtime, that we extend with host modules to enhance the 78 | capabilities of the WebAssembly programs. We wanted to improve the ergonomy of 79 | maintaining our host modules, while maintaining the performance overhead to a 80 | minimum. We wanted to test the hypothesis that Go generics could be used to 81 | achieve these goals, and this repository is the outcome of that experiment. 82 | 83 | ## Usage 84 | 85 | This package is intended to be used as a library to create host modules for 86 | wazero. The code is separated in two packages: the top level [`wazergo`][wazergo] 87 | package contains the type and functions used to build host modules, including 88 | the declaration of functions they export. The [`types`][types] subpackage 89 | contains the declaration of generic types representing integers, floats, 90 | pointers, arrays, etc... 91 | 92 | Programs using the [`types`][types] package often import its symbols directly 93 | into their package name namespace(s), which helps declare the host module 94 | functions. For example: 95 | 96 | ```go 97 | import ( 98 | . "github.com/stealthrocket/wazergo/types" 99 | ) 100 | 101 | ... 102 | 103 | // Answer returns a Int32 declared in the types package. 104 | func (m *Module) Answer(ctx context.Context) Int32 { 105 | return 42 106 | } 107 | ``` 108 | 109 | ### Building Host Modules 110 | 111 | To construct a host module, the program must declare a type satisfying the 112 | [`Module`][Module] interface, and construct a [`HostModule[T]`][HostModule] 113 | of that type, along with the list of its exported functions. The following model 114 | is often useful: 115 | 116 | ```go 117 | package my_host_module 118 | 119 | import ( 120 | "github.com/stealthrocket/wazergo" 121 | ) 122 | 123 | // Declare the host module from a set of exported functions. 124 | var HostModule wazergo.HostModule[*Module] = functions{ 125 | ... 126 | } 127 | 128 | // The `functions` type impements `HostModule[*Module]`, providing the 129 | // module name, map of exported functions, and the ability to create instances 130 | // of the module type. 131 | type functions wazergo.Functions[*Module] 132 | 133 | func (f functions) Name() string { 134 | return "my_host_module" 135 | } 136 | 137 | func (f functions) Functions() wazergo.Functions[*Module] { 138 | return (wazergo.Functions[*Module])(f) 139 | } 140 | 141 | func (f functions) Instantiate(ctx context.Context, opts ...Option) (*Module, error) { 142 | mod := &Module{ 143 | ... 144 | } 145 | wazergo.Configure(mod, opts...) 146 | return mod, nil 147 | } 148 | 149 | type Option = wazergo.Option[*Module] 150 | 151 | // Module will be the Go type we use to maintain the state of our module 152 | // instances. 153 | type Module struct { 154 | ... 155 | } 156 | 157 | func (Module) Close(context.Context) error { 158 | return nil 159 | } 160 | 161 | ``` 162 | 163 | There are a few concepts of the library that we are getting exposed to in this 164 | example: 165 | 166 | - [`HostModule[T]`][HostModule] is an interface parametrized on the type 167 | of our module instances. This interface is the bridge between the library and 168 | the wazero APIs. 169 | 170 | - [`Functions[T]`][Functions] is a map type parametrized on the module 171 | type, it associates the exported function names to the method of the module 172 | type that will be invoked when WebAssembly programs invoke them as imported 173 | symbols. 174 | 175 | - [`Optional[T]`][Optional] is an interface type parameterized on the 176 | module type and representing the configuration options available on the 177 | module. It is common for the package to declare options using function 178 | constructors, for example: 179 | 180 | ```go 181 | func CustomValue(value int) Option { 182 | return wazergo.OptionFunc(func(m *Module) { ... }) 183 | } 184 | ``` 185 | 186 | These types are helpers to glue the Go type where the host module is implemented 187 | (`Module` in our example) to the generic abstractions provided by the library to 188 | drive configuration and instantiation of the modules in wazero. 189 | 190 | ### Declaring Host Functions 191 | 192 | The declaration of host functions is done by constructing a map of exported 193 | names to methods of the module type, and is where the `types` subpackage can be 194 | employed to define parameters and return values. 195 | 196 | ```go 197 | package my_host_module 198 | 199 | import ( 200 | . "github.com/stealthrocket/wazergo" 201 | . "github.com/stealthrocket/wazergo/types" 202 | ) 203 | 204 | var HostModule HostModule[*Module] = functions{ 205 | "answer": F0((*Module).Answer), 206 | "double": F1((*Module).Double), 207 | } 208 | 209 | ... 210 | 211 | func (m *Module) Answer(ctx context.Context) Int32 { 212 | return 42 213 | } 214 | 215 | func (m *Module) Double(ctx context.Context, f Float32) Float32 { 216 | return f + f 217 | } 218 | ``` 219 | 220 | - Exported methods of a host module must always start with a 221 | [`context.Context`][context] parameter. 222 | 223 | - The parameters and return values must satisfy [`Param[T]`][Param] and 224 | [`Result`][Result] interfaces. The [`types`][types] subpackage contains types 225 | that do, but the application can construct its own for more advanced use 226 | cases (e.g. struct types). 227 | 228 | - When constructing the [`Functions[T]`][Functions] map, the program 229 | must use one of the [`F*`][F] generics constructors to create a 230 | [`Function[T]`][Function] value from methods of the module. 231 | The program must use a function constructor matching the number of parameter 232 | to the method (e.g. [`F2`][F2] if there are two parameters, not including the 233 | context). The function constructors handle the conversion of Go function 234 | signatures to WebAssembly function types using information about their generic 235 | type parameters. 236 | 237 | - Methods of the module must have a single return value. For the common case of 238 | having to return either a value or an error (in which case the WebAssembly 239 | function has two results), the generic [`Optional[T]`][Optional] 240 | type can be used, or the application may declare its own result types. 241 | 242 | ### Composite Parameter Types 243 | 244 | [`Array[T]`][Array] type is base generic type used to represent 245 | contiguous sequences of fixed-length primitive values such as integers and 246 | floats. Array values map to a pair of `i32` values for the memory offset and 247 | number of elements in the array. For example, the [`Bytes`][Bytes] type 248 | (equivalent to a Go `[]byte`) is expressed as `Array[byte]`. 249 | 250 | [`Param[T]`][Param] and [`Result`][Result] are the interfaces used 251 | as type constraints in generic type paramaeters 252 | 253 | To express sequences of non-primitive types, the generic [`List[T]`][List] 254 | type can represent lists of types implementing the [`Object[T]`][Object] 255 | interface. [`Object[T]`][Object] is used by types that can be loaded from, 256 | or stored to the module memory. 257 | 258 | ### Memory Safety 259 | 260 | Memory safety is guaranteed both by the use of wazero's `Memory` type, and 261 | triggering a panic with a value of type [`SEGFAULT`][SEGFAULT] if the program 262 | attempts to access a memory address outside of its own linear memory. 263 | 264 | The panic effectively interrupts the program flow at the call site of the host 265 | function, and is turned into an error by wazero so the host application can 266 | safely handle the module termination. 267 | 268 | ### Type Safety 269 | 270 | Type safety is guaranteed by the package at multiple levels. 271 | 272 | Due to the use of generics, the compiler is able to verify that the host module 273 | constructed by the program is semantically correct. For example, the compiler 274 | will refuse to create a host function where one of the return value is a type 275 | which does not implement the [`Result`][Result] interface. 276 | 277 | Runtime validation is then added by wazero when mapping module imports to ensure 278 | that the low level WebAssembly signatures of the imports match with those of the 279 | host module. 280 | 281 | ### Host Module Instantiation 282 | 283 | Calls to the host functions of a module require injecting the context in which 284 | the host module was instantiated into the context in which the exported functions 285 | of a module instante that depend on it are called (e.g. binding of the method 286 | receiver to the calls to carry state across invocations). 287 | 288 | This is done by injecting the host module instance into the context used when 289 | calling into a WebAssembly module. 290 | 291 | ```go 292 | runtime := wazero.NewRuntime(ctxS) 293 | defer runtime.Close(ctx) 294 | 295 | instance := wazergo.MustInstantiate(ctx, runtime, my_host_module.HostModule) 296 | 297 | ... 298 | 299 | // When invoking exported functions of a module; this may also be done 300 | // automatically via calls to wazero.Runtime.InstantiateModule which 301 | // invoke the start function(s). 302 | ctx = wazergo.WithModuleInstance(ctx, instance) 303 | 304 | start := module.ExportedFunction("_start") 305 | r, err := start.Call(ctx) 306 | if err != nil { 307 | ... 308 | } 309 | ``` 310 | 311 | The `_start` function may be called automatically when instantiating a wazero 312 | module, in which case the host module instance must be injected in the calling 313 | context. 314 | 315 | ```go 316 | ctx = wazergo.WithModuleInstance(ctx, instance) 317 | 318 | _, err := runtime.InstantiateModule(ctx, compiledModule, moduleConfig) 319 | ``` 320 | 321 | Alternatively, the guest module instantiation may disabling calling `_start` 322 | automatically, so binding of the host module to the calling context can be 323 | deferred. 324 | 325 | ```go 326 | _, err := runtime.InstantiateModule(ctx, compiledModule, 327 | // disable calling _start during module instantiation 328 | moduleConfig.WithStartFunctions(), 329 | ) 330 | ``` 331 | 332 | ## Contributing 333 | 334 | No software is ever complete, and while there will be porbably be additions and 335 | fixes brought to the library, it is usable in its current state, and while we 336 | aim to maintain backward compatibility, breaking changes might be introduced if 337 | necessary to improve usability as we learn more from using the library. 338 | 339 | Pull requests are welcome! Anything that is not a simple fix would probably 340 | benefit from being discussed in an issue first. 341 | 342 | Remember to be respectful and open minded! 343 | 344 | [context]: https://pkg.go.dev/context#Context 345 | [F]: https://pkg.go.dev/github.com/stealthrocket/wazergo#F0 346 | [F2]: https://pkg.go.dev/github.com/stealthrocket/wazergo#F2 347 | [Function]: https://pkg.go.dev/github.com/stealthrocket/wazergo#Function 348 | [Functions]: https://pkg.go.dev/github.com/stealthrocket/wazergo#Functions 349 | [HostModule]: https://pkg.go.dev/github.com/stealthrocket/wazergo#HostModule 350 | [Module]: https://pkg.go.dev/github.com/stealthrocket/wazergo#Module 351 | [Optional]: https://pkg.go.dev/github.com/stealthrocket/wazergo/types#Optional 352 | [Array]: https://pkg.go.dev/github.com/stealthrocket/wazergo/types#Array 353 | [List]: https://pkg.go.dev/github.com/stealthrocket/wazergo/types#List 354 | [Bytes]: https://pkg.go.dev/github.com/stealthrocket/wazergo/types#Bytes 355 | [Object]: https://pkg.go.dev/github.com/stealthrocket/wazergo/types#Object 356 | [Param]: https://pkg.go.dev/github.com/stealthrocket/wazergo/types#Param 357 | [Result]: https://pkg.go.dev/github.com/stealthrocket/wazergo/types#Param 358 | [types]: https://pkg.go.dev/github.com/stealthrocket/wazergo/types 359 | [wazergo]: https://pkg.go.dev/github.com/stealthrocket/wazergo 360 | [SEGFAULT]: https://pkg.go.dev/github.com/stealthrocket/wazergo#SEGFAULT 361 | -------------------------------------------------------------------------------- /function.go: -------------------------------------------------------------------------------- 1 | package wazergo 2 | 3 | import ( 4 | "context" 5 | 6 | . "github.com/stealthrocket/wazergo/types" 7 | "github.com/tetratelabs/wazero/api" 8 | ) 9 | 10 | // Functions is a map type representing the collection of functions exported 11 | // by a plugin. The map keys are the names of that each function gets exported 12 | // as. The function value is the description of the wazero host function to 13 | // be added when building a plugin. The type parameter T is used to ensure 14 | // consistency between the plugin definition and the functions that compose it. 15 | type Functions[T any] map[string]Function[T] 16 | 17 | // Function represents a single function exported by a plugin. Programs may 18 | // configure the fields individually but it is often preferrable to use one of 19 | // the Func* constructors instead to let the Go compiler ensure type and memory 20 | // safety when generating the code to bridge between WebAssembly and Go. 21 | type Function[T any] struct { 22 | Name string 23 | Params []Value 24 | Results []Value 25 | Func func(T, context.Context, api.Module, []uint64) 26 | } 27 | 28 | // NumParams is the number of parameters this function reads from the stack. 29 | // 30 | // Note that this is not necessarily the same as len(f.Params), since Params 31 | // holds higher level values that may correspond to more than one stack param. 32 | func (f *Function[T]) NumParams() int { 33 | return countStackValues(f.Params) 34 | } 35 | 36 | // NumResults is the number of return values this function writes to the stack. 37 | // 38 | // Note that this is not necessarily the same as len(f.Results), since Results 39 | // holds higher level values that may correspond to more than one stack result. 40 | func (f *Function[T]) NumResults() int { 41 | return countStackValues(f.Results) 42 | } 43 | 44 | // WithFunc returns a copy of the Function with the internal Func field 45 | // replaced. 46 | func (f Function[T]) WithFunc(fn func(T, context.Context, api.Module, []uint64)) Function[T] { 47 | f.Func = fn 48 | return f 49 | } 50 | 51 | func countStackValues(values []Value) (count int) { 52 | for _, v := range values { 53 | count += len(v.ValueTypes()) 54 | } 55 | return 56 | } 57 | 58 | // F0 is the Function constructor for functions accepting no parameters. 59 | func F0[T any, R Result](fn func(T, context.Context) R) Function[T] { 60 | var ret R 61 | return Function[T]{ 62 | Results: []Value{ret}, 63 | Func: func(this T, ctx context.Context, module api.Module, stack []uint64) { 64 | fn(this, ctx).StoreValue(module.Memory(), stack) 65 | }, 66 | } 67 | } 68 | 69 | // F1 is the Function constructor for functions accepting one parameter. 70 | func F1[T any, P Param[P], R Result](fn func(T, context.Context, P) R) Function[T] { 71 | var ret R 72 | var arg P 73 | return Function[T]{ 74 | Params: []Value{arg}, 75 | Results: []Value{ret}, 76 | Func: func(this T, ctx context.Context, module api.Module, stack []uint64) { 77 | var arg P 78 | var memory = module.Memory() 79 | fn(this, ctx, arg.LoadValue(memory, stack)).StoreValue(memory, stack) 80 | }, 81 | } 82 | } 83 | 84 | // F2 is the Function constructor for functions accepting two parameters. 85 | func F2[ 86 | T any, 87 | P1 Param[P1], 88 | P2 Param[P2], 89 | R Result, 90 | ](fn func(T, context.Context, P1, P2) R) Function[T] { 91 | var ret R 92 | var arg1 P1 93 | var arg2 P2 94 | params1 := arg1.ValueTypes() 95 | params2 := arg2.ValueTypes() 96 | a := len(params1) 97 | b := len(params2) + a 98 | return Function[T]{ 99 | Params: []Value{arg1, arg2}, 100 | Results: []Value{ret}, 101 | Func: func(this T, ctx context.Context, module api.Module, stack []uint64) { 102 | var arg1 P1 103 | var arg2 P2 104 | var memory = module.Memory() 105 | fn(this, ctx, 106 | arg1.LoadValue(memory, stack[0:a:a]), 107 | arg2.LoadValue(memory, stack[a:b:b]), 108 | ).StoreValue(memory, stack) 109 | }, 110 | } 111 | } 112 | 113 | // F3 is the Function constructor for functions accepting three parameters. 114 | func F3[ 115 | T any, 116 | P1 Param[P1], 117 | P2 Param[P2], 118 | P3 Param[P3], 119 | R Result, 120 | ](fn func(T, context.Context, P1, P2, P3) R) Function[T] { 121 | var ret R 122 | var arg1 P1 123 | var arg2 P2 124 | var arg3 P3 125 | params1 := arg1.ValueTypes() 126 | params2 := arg2.ValueTypes() 127 | params3 := arg3.ValueTypes() 128 | a := len(params1) 129 | b := len(params2) + a 130 | c := len(params3) + b 131 | return Function[T]{ 132 | Params: []Value{arg1, arg2, arg3}, 133 | Results: []Value{ret}, 134 | Func: func(this T, ctx context.Context, module api.Module, stack []uint64) { 135 | var arg1 P1 136 | var arg2 P2 137 | var arg3 P3 138 | var memory = module.Memory() 139 | fn(this, ctx, 140 | arg1.LoadValue(memory, stack[0:a:a]), 141 | arg2.LoadValue(memory, stack[a:b:b]), 142 | arg3.LoadValue(memory, stack[b:c:c]), 143 | ).StoreValue(memory, stack) 144 | }, 145 | } 146 | } 147 | 148 | // F4 is the Function constructor for functions accepting four parameters. 149 | func F4[ 150 | T any, 151 | P1 Param[P1], 152 | P2 Param[P2], 153 | P3 Param[P3], 154 | P4 Param[P4], 155 | R Result, 156 | ](fn func(T, context.Context, P1, P2, P3, P4) R) Function[T] { 157 | var ret R 158 | var arg1 P1 159 | var arg2 P2 160 | var arg3 P3 161 | var arg4 P4 162 | params1 := arg1.ValueTypes() 163 | params2 := arg2.ValueTypes() 164 | params3 := arg3.ValueTypes() 165 | params4 := arg4.ValueTypes() 166 | a := len(params1) 167 | b := len(params2) + a 168 | c := len(params3) + b 169 | d := len(params4) + c 170 | return Function[T]{ 171 | Params: []Value{arg1, arg2, arg3, arg4}, 172 | Results: []Value{ret}, 173 | Func: func(this T, ctx context.Context, module api.Module, stack []uint64) { 174 | var arg1 P1 175 | var arg2 P2 176 | var arg3 P3 177 | var arg4 P4 178 | var memory = module.Memory() 179 | fn(this, ctx, 180 | arg1.LoadValue(memory, stack[0:a:a]), 181 | arg2.LoadValue(memory, stack[a:b:b]), 182 | arg3.LoadValue(memory, stack[b:c:c]), 183 | arg4.LoadValue(memory, stack[c:d:d]), 184 | ).StoreValue(memory, stack) 185 | }, 186 | } 187 | } 188 | 189 | // F5 is the Function constructor for functions accepting five parameters. 190 | func F5[ 191 | T any, 192 | P1 Param[P1], 193 | P2 Param[P2], 194 | P3 Param[P3], 195 | P4 Param[P4], 196 | P5 Param[P5], 197 | R Result, 198 | ](fn func(T, context.Context, P1, P2, P3, P4, P5) R) Function[T] { 199 | var ret R 200 | var arg1 P1 201 | var arg2 P2 202 | var arg3 P3 203 | var arg4 P4 204 | var arg5 P5 205 | params1 := arg1.ValueTypes() 206 | params2 := arg2.ValueTypes() 207 | params3 := arg3.ValueTypes() 208 | params4 := arg4.ValueTypes() 209 | params5 := arg5.ValueTypes() 210 | a := len(params1) 211 | b := len(params2) + a 212 | c := len(params3) + b 213 | d := len(params4) + c 214 | e := len(params5) + d 215 | return Function[T]{ 216 | Params: []Value{arg1, arg2, arg3, arg4, arg5}, 217 | Results: []Value{ret}, 218 | Func: func(this T, ctx context.Context, module api.Module, stack []uint64) { 219 | var arg1 P1 220 | var arg2 P2 221 | var arg3 P3 222 | var arg4 P4 223 | var arg5 P5 224 | var memory = module.Memory() 225 | fn(this, ctx, 226 | arg1.LoadValue(memory, stack[0:a:a]), 227 | arg2.LoadValue(memory, stack[a:b:b]), 228 | arg3.LoadValue(memory, stack[b:c:c]), 229 | arg4.LoadValue(memory, stack[c:d:d]), 230 | arg5.LoadValue(memory, stack[d:e:e]), 231 | ).StoreValue(memory, stack) 232 | }, 233 | } 234 | } 235 | 236 | // F6 is the Function constructor for functions accepting six parameters. 237 | func F6[ 238 | T any, 239 | P1 Param[P1], 240 | P2 Param[P2], 241 | P3 Param[P3], 242 | P4 Param[P4], 243 | P5 Param[P5], 244 | P6 Param[P6], 245 | R Result, 246 | ](fn func(T, context.Context, P1, P2, P3, P4, P5, P6) R) Function[T] { 247 | var ret R 248 | var arg1 P1 249 | var arg2 P2 250 | var arg3 P3 251 | var arg4 P4 252 | var arg5 P5 253 | var arg6 P6 254 | params1 := arg1.ValueTypes() 255 | params2 := arg2.ValueTypes() 256 | params3 := arg3.ValueTypes() 257 | params4 := arg4.ValueTypes() 258 | params5 := arg5.ValueTypes() 259 | params6 := arg6.ValueTypes() 260 | a := len(params1) 261 | b := len(params2) + a 262 | c := len(params3) + b 263 | d := len(params4) + c 264 | e := len(params5) + d 265 | f := len(params6) + e 266 | return Function[T]{ 267 | Params: []Value{arg1, arg2, arg3, arg4, arg5, arg6}, 268 | Results: []Value{ret}, 269 | Func: func(this T, ctx context.Context, module api.Module, stack []uint64) { 270 | var arg1 P1 271 | var arg2 P2 272 | var arg3 P3 273 | var arg4 P4 274 | var arg5 P5 275 | var arg6 P6 276 | var memory = module.Memory() 277 | fn(this, ctx, 278 | arg1.LoadValue(memory, stack[0:a:a]), 279 | arg2.LoadValue(memory, stack[a:b:b]), 280 | arg3.LoadValue(memory, stack[b:c:c]), 281 | arg4.LoadValue(memory, stack[c:d:d]), 282 | arg5.LoadValue(memory, stack[d:e:e]), 283 | arg6.LoadValue(memory, stack[e:f:f]), 284 | ).StoreValue(memory, stack) 285 | }, 286 | } 287 | } 288 | 289 | // F7 is the Function constructor for functions accepting seven parameters. 290 | func F7[ 291 | T any, 292 | P1 Param[P1], 293 | P2 Param[P2], 294 | P3 Param[P3], 295 | P4 Param[P4], 296 | P5 Param[P5], 297 | P6 Param[P6], 298 | P7 Param[P7], 299 | R Result, 300 | ](fn func(T, context.Context, P1, P2, P3, P4, P5, P6, P7) R) Function[T] { 301 | var ret R 302 | var arg1 P1 303 | var arg2 P2 304 | var arg3 P3 305 | var arg4 P4 306 | var arg5 P5 307 | var arg6 P6 308 | var arg7 P7 309 | params1 := arg1.ValueTypes() 310 | params2 := arg2.ValueTypes() 311 | params3 := arg3.ValueTypes() 312 | params4 := arg4.ValueTypes() 313 | params5 := arg5.ValueTypes() 314 | params6 := arg6.ValueTypes() 315 | params7 := arg7.ValueTypes() 316 | a := len(params1) 317 | b := len(params2) + a 318 | c := len(params3) + b 319 | d := len(params4) + c 320 | e := len(params5) + d 321 | f := len(params6) + e 322 | g := len(params7) + f 323 | return Function[T]{ 324 | Params: []Value{arg1, arg2, arg3, arg4, arg5, arg6, arg7}, 325 | Results: []Value{ret}, 326 | Func: func(this T, ctx context.Context, module api.Module, stack []uint64) { 327 | var arg1 P1 328 | var arg2 P2 329 | var arg3 P3 330 | var arg4 P4 331 | var arg5 P5 332 | var arg6 P6 333 | var arg7 P7 334 | var memory = module.Memory() 335 | fn(this, ctx, 336 | arg1.LoadValue(memory, stack[0:a:a]), 337 | arg2.LoadValue(memory, stack[a:b:b]), 338 | arg3.LoadValue(memory, stack[b:c:c]), 339 | arg4.LoadValue(memory, stack[c:d:d]), 340 | arg5.LoadValue(memory, stack[d:e:e]), 341 | arg6.LoadValue(memory, stack[e:f:f]), 342 | arg7.LoadValue(memory, stack[f:g:g]), 343 | ).StoreValue(memory, stack) 344 | }, 345 | } 346 | } 347 | 348 | // F8 is the Function constructor for functions accepting eight parameters. 349 | func F8[ 350 | T any, 351 | P1 Param[P1], 352 | P2 Param[P2], 353 | P3 Param[P3], 354 | P4 Param[P4], 355 | P5 Param[P5], 356 | P6 Param[P6], 357 | P7 Param[P7], 358 | P8 Param[P8], 359 | R Result, 360 | ](fn func(T, context.Context, P1, P2, P3, P4, P5, P6, P7, P8) R) Function[T] { 361 | var ret R 362 | var arg1 P1 363 | var arg2 P2 364 | var arg3 P3 365 | var arg4 P4 366 | var arg5 P5 367 | var arg6 P6 368 | var arg7 P7 369 | var arg8 P8 370 | params1 := arg1.ValueTypes() 371 | params2 := arg2.ValueTypes() 372 | params3 := arg3.ValueTypes() 373 | params4 := arg4.ValueTypes() 374 | params5 := arg5.ValueTypes() 375 | params6 := arg6.ValueTypes() 376 | params7 := arg7.ValueTypes() 377 | params8 := arg8.ValueTypes() 378 | a := len(params1) 379 | b := len(params2) + a 380 | c := len(params3) + b 381 | d := len(params4) + c 382 | e := len(params5) + d 383 | f := len(params6) + e 384 | g := len(params7) + f 385 | h := len(params8) + g 386 | return Function[T]{ 387 | Params: []Value{arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8}, 388 | Results: []Value{ret}, 389 | Func: func(this T, ctx context.Context, module api.Module, stack []uint64) { 390 | var arg1 P1 391 | var arg2 P2 392 | var arg3 P3 393 | var arg4 P4 394 | var arg5 P5 395 | var arg6 P6 396 | var arg7 P7 397 | var arg8 P8 398 | var memory = module.Memory() 399 | fn(this, ctx, 400 | arg1.LoadValue(memory, stack[0:a:a]), 401 | arg2.LoadValue(memory, stack[a:b:b]), 402 | arg3.LoadValue(memory, stack[b:c:c]), 403 | arg4.LoadValue(memory, stack[c:d:d]), 404 | arg5.LoadValue(memory, stack[d:e:e]), 405 | arg6.LoadValue(memory, stack[e:f:f]), 406 | arg7.LoadValue(memory, stack[f:g:g]), 407 | arg8.LoadValue(memory, stack[g:h:h]), 408 | ).StoreValue(memory, stack) 409 | }, 410 | } 411 | } 412 | 413 | // F9 is the Function constructor for functions accepting nine parameters. 414 | func F9[ 415 | T any, 416 | P1 Param[P1], 417 | P2 Param[P2], 418 | P3 Param[P3], 419 | P4 Param[P4], 420 | P5 Param[P5], 421 | P6 Param[P6], 422 | P7 Param[P7], 423 | P8 Param[P8], 424 | P9 Param[P9], 425 | R Result, 426 | ](fn func(T, context.Context, P1, P2, P3, P4, P5, P6, P7, P8, P9) R) Function[T] { 427 | var ret R 428 | var arg1 P1 429 | var arg2 P2 430 | var arg3 P3 431 | var arg4 P4 432 | var arg5 P5 433 | var arg6 P6 434 | var arg7 P7 435 | var arg8 P8 436 | var arg9 P9 437 | params1 := arg1.ValueTypes() 438 | params2 := arg2.ValueTypes() 439 | params3 := arg3.ValueTypes() 440 | params4 := arg4.ValueTypes() 441 | params5 := arg5.ValueTypes() 442 | params6 := arg6.ValueTypes() 443 | params7 := arg7.ValueTypes() 444 | params8 := arg8.ValueTypes() 445 | params9 := arg9.ValueTypes() 446 | a := len(params1) 447 | b := len(params2) + a 448 | c := len(params3) + b 449 | d := len(params4) + c 450 | e := len(params5) + d 451 | f := len(params6) + e 452 | g := len(params7) + f 453 | h := len(params8) + g 454 | i := len(params9) + h 455 | return Function[T]{ 456 | Params: []Value{arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9}, 457 | Results: []Value{ret}, 458 | Func: func(this T, ctx context.Context, module api.Module, stack []uint64) { 459 | var arg1 P1 460 | var arg2 P2 461 | var arg3 P3 462 | var arg4 P4 463 | var arg5 P5 464 | var arg6 P6 465 | var arg7 P7 466 | var arg8 P8 467 | var arg9 P9 468 | var memory = module.Memory() 469 | fn(this, ctx, 470 | arg1.LoadValue(memory, stack[0:a:a]), 471 | arg2.LoadValue(memory, stack[a:b:b]), 472 | arg3.LoadValue(memory, stack[b:c:c]), 473 | arg4.LoadValue(memory, stack[c:d:d]), 474 | arg5.LoadValue(memory, stack[d:e:e]), 475 | arg6.LoadValue(memory, stack[e:f:f]), 476 | arg7.LoadValue(memory, stack[f:g:g]), 477 | arg8.LoadValue(memory, stack[g:h:h]), 478 | arg9.LoadValue(memory, stack[h:i:i]), 479 | ).StoreValue(memory, stack) 480 | }, 481 | } 482 | } 483 | 484 | // F10 is the Function constructor for functions accepting ten parameters. 485 | func F10[ 486 | T any, 487 | P1 Param[P1], 488 | P2 Param[P2], 489 | P3 Param[P3], 490 | P4 Param[P4], 491 | P5 Param[P5], 492 | P6 Param[P6], 493 | P7 Param[P7], 494 | P8 Param[P8], 495 | P9 Param[P9], 496 | P10 Param[P10], 497 | R Result, 498 | ](fn func(T, context.Context, P1, P2, P3, P4, P5, P6, P7, P8, P9, P10) R) Function[T] { 499 | var ret R 500 | var arg1 P1 501 | var arg2 P2 502 | var arg3 P3 503 | var arg4 P4 504 | var arg5 P5 505 | var arg6 P6 506 | var arg7 P7 507 | var arg8 P8 508 | var arg9 P9 509 | var arg10 P10 510 | params1 := arg1.ValueTypes() 511 | params2 := arg2.ValueTypes() 512 | params3 := arg3.ValueTypes() 513 | params4 := arg4.ValueTypes() 514 | params5 := arg5.ValueTypes() 515 | params6 := arg6.ValueTypes() 516 | params7 := arg7.ValueTypes() 517 | params8 := arg8.ValueTypes() 518 | params9 := arg9.ValueTypes() 519 | params10 := arg10.ValueTypes() 520 | a := len(params1) 521 | b := len(params2) + a 522 | c := len(params3) + b 523 | d := len(params4) + c 524 | e := len(params5) + d 525 | f := len(params6) + e 526 | g := len(params7) + f 527 | h := len(params8) + g 528 | i := len(params9) + h 529 | j := len(params10) + i 530 | return Function[T]{ 531 | Params: []Value{arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10}, 532 | Results: []Value{ret}, 533 | Func: func(this T, ctx context.Context, module api.Module, stack []uint64) { 534 | var arg1 P1 535 | var arg2 P2 536 | var arg3 P3 537 | var arg4 P4 538 | var arg5 P5 539 | var arg6 P6 540 | var arg7 P7 541 | var arg8 P8 542 | var arg9 P9 543 | var arg10 P10 544 | var memory = module.Memory() 545 | fn(this, ctx, 546 | arg1.LoadValue(memory, stack[0:a:a]), 547 | arg2.LoadValue(memory, stack[a:b:b]), 548 | arg3.LoadValue(memory, stack[b:c:c]), 549 | arg4.LoadValue(memory, stack[c:d:d]), 550 | arg5.LoadValue(memory, stack[d:e:e]), 551 | arg6.LoadValue(memory, stack[e:f:f]), 552 | arg7.LoadValue(memory, stack[f:g:g]), 553 | arg8.LoadValue(memory, stack[g:h:h]), 554 | arg9.LoadValue(memory, stack[h:i:i]), 555 | arg10.LoadValue(memory, stack[i:j:j]), 556 | ).StoreValue(memory, stack) 557 | }, 558 | } 559 | } 560 | 561 | // F11 is the Function constructor for functions accepting eleven parameters. 562 | func F11[ 563 | T any, 564 | P1 Param[P1], 565 | P2 Param[P2], 566 | P3 Param[P3], 567 | P4 Param[P4], 568 | P5 Param[P5], 569 | P6 Param[P6], 570 | P7 Param[P7], 571 | P8 Param[P8], 572 | P9 Param[P9], 573 | P10 Param[P10], 574 | P11 Param[P11], 575 | R Result, 576 | ](fn func(T, context.Context, P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11) R) Function[T] { 577 | var ret R 578 | var arg1 P1 579 | var arg2 P2 580 | var arg3 P3 581 | var arg4 P4 582 | var arg5 P5 583 | var arg6 P6 584 | var arg7 P7 585 | var arg8 P8 586 | var arg9 P9 587 | var arg10 P10 588 | var arg11 P11 589 | params1 := arg1.ValueTypes() 590 | params2 := arg2.ValueTypes() 591 | params3 := arg3.ValueTypes() 592 | params4 := arg4.ValueTypes() 593 | params5 := arg5.ValueTypes() 594 | params6 := arg6.ValueTypes() 595 | params7 := arg7.ValueTypes() 596 | params8 := arg8.ValueTypes() 597 | params9 := arg9.ValueTypes() 598 | params10 := arg10.ValueTypes() 599 | params11 := arg11.ValueTypes() 600 | a := len(params1) 601 | b := len(params2) + a 602 | c := len(params3) + b 603 | d := len(params4) + c 604 | e := len(params5) + d 605 | f := len(params6) + e 606 | g := len(params7) + f 607 | h := len(params8) + g 608 | i := len(params9) + h 609 | j := len(params10) + i 610 | k := len(params11) + j 611 | return Function[T]{ 612 | Params: []Value{arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11}, 613 | Results: []Value{ret}, 614 | Func: func(this T, ctx context.Context, module api.Module, stack []uint64) { 615 | var arg1 P1 616 | var arg2 P2 617 | var arg3 P3 618 | var arg4 P4 619 | var arg5 P5 620 | var arg6 P6 621 | var arg7 P7 622 | var arg8 P8 623 | var arg9 P9 624 | var arg10 P10 625 | var arg11 P11 626 | var memory = module.Memory() 627 | fn(this, ctx, 628 | arg1.LoadValue(memory, stack[0:a:a]), 629 | arg2.LoadValue(memory, stack[a:b:b]), 630 | arg3.LoadValue(memory, stack[b:c:c]), 631 | arg4.LoadValue(memory, stack[c:d:d]), 632 | arg5.LoadValue(memory, stack[d:e:e]), 633 | arg6.LoadValue(memory, stack[e:f:f]), 634 | arg7.LoadValue(memory, stack[f:g:g]), 635 | arg8.LoadValue(memory, stack[g:h:h]), 636 | arg9.LoadValue(memory, stack[h:i:i]), 637 | arg10.LoadValue(memory, stack[i:j:j]), 638 | arg11.LoadValue(memory, stack[j:k:k]), 639 | ).StoreValue(memory, stack) 640 | }, 641 | } 642 | } 643 | 644 | // F12 is the Function constructor for functions accepting twelve parameters. 645 | func F12[ 646 | T any, 647 | P1 Param[P1], 648 | P2 Param[P2], 649 | P3 Param[P3], 650 | P4 Param[P4], 651 | P5 Param[P5], 652 | P6 Param[P6], 653 | P7 Param[P7], 654 | P8 Param[P8], 655 | P9 Param[P9], 656 | P10 Param[P10], 657 | P11 Param[P11], 658 | P12 Param[P12], 659 | R Result, 660 | ](fn func(T, context.Context, P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12) R) Function[T] { 661 | var ret R 662 | var arg1 P1 663 | var arg2 P2 664 | var arg3 P3 665 | var arg4 P4 666 | var arg5 P5 667 | var arg6 P6 668 | var arg7 P7 669 | var arg8 P8 670 | var arg9 P9 671 | var arg10 P10 672 | var arg11 P11 673 | var arg12 P12 674 | params1 := arg1.ValueTypes() 675 | params2 := arg2.ValueTypes() 676 | params3 := arg3.ValueTypes() 677 | params4 := arg4.ValueTypes() 678 | params5 := arg5.ValueTypes() 679 | params6 := arg6.ValueTypes() 680 | params7 := arg7.ValueTypes() 681 | params8 := arg8.ValueTypes() 682 | params9 := arg9.ValueTypes() 683 | params10 := arg10.ValueTypes() 684 | params11 := arg11.ValueTypes() 685 | params12 := arg12.ValueTypes() 686 | a := len(params1) 687 | b := len(params2) + a 688 | c := len(params3) + b 689 | d := len(params4) + c 690 | e := len(params5) + d 691 | f := len(params6) + e 692 | g := len(params7) + f 693 | h := len(params8) + g 694 | i := len(params9) + h 695 | j := len(params10) + i 696 | k := len(params11) + j 697 | l := len(params12) + k 698 | return Function[T]{ 699 | Params: []Value{arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12}, 700 | Results: []Value{ret}, 701 | Func: func(this T, ctx context.Context, module api.Module, stack []uint64) { 702 | var arg1 P1 703 | var arg2 P2 704 | var arg3 P3 705 | var arg4 P4 706 | var arg5 P5 707 | var arg6 P6 708 | var arg7 P7 709 | var arg8 P8 710 | var arg9 P9 711 | var arg10 P10 712 | var arg11 P11 713 | var arg12 P12 714 | var memory = module.Memory() 715 | fn(this, ctx, 716 | arg1.LoadValue(memory, stack[0:a:a]), 717 | arg2.LoadValue(memory, stack[a:b:b]), 718 | arg3.LoadValue(memory, stack[b:c:c]), 719 | arg4.LoadValue(memory, stack[c:d:d]), 720 | arg5.LoadValue(memory, stack[d:e:e]), 721 | arg6.LoadValue(memory, stack[e:f:f]), 722 | arg7.LoadValue(memory, stack[f:g:g]), 723 | arg8.LoadValue(memory, stack[g:h:h]), 724 | arg9.LoadValue(memory, stack[h:i:i]), 725 | arg10.LoadValue(memory, stack[i:j:j]), 726 | arg11.LoadValue(memory, stack[j:k:k]), 727 | arg12.LoadValue(memory, stack[k:l:l]), 728 | ).StoreValue(memory, stack) 729 | }, 730 | } 731 | } 732 | -------------------------------------------------------------------------------- /types/types.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "math" 9 | "strconv" 10 | "syscall" 11 | "time" 12 | "unsafe" 13 | 14 | "github.com/stealthrocket/wazergo/wasm" 15 | "github.com/tetratelabs/wazero/api" 16 | ) 17 | 18 | // Value represents a field in a function signature, which may be a parameter 19 | // or a result. 20 | type Value interface { 21 | // Writes a human-readable representation of the field to w, using the 22 | // memory and stack of a program to locate the field value. 23 | FormatValue(w io.Writer, memory api.Memory, stack []uint64) 24 | // Returns the sequence of primitive types that the result value is 25 | // composed of. Values of primitive types will have a single type, 26 | // but more complex types will have more (e.g. a buffer may hold two 27 | // 32 bits integers for the pointer and length). The number of types 28 | // indicates the number of words that are consumed from the stack when 29 | // loading the parameter. 30 | ValueTypes() []api.ValueType 31 | } 32 | 33 | // Param is an interface representing parameters of WebAssembly functions which 34 | // are read form the stack. 35 | // 36 | // The interface is mainly used as a constraint for generic type parameters. 37 | type Param[T any] interface { 38 | Value 39 | // Loads and returns the parameter value from the stack and optionally 40 | // reading its content from memory. 41 | LoadValue(memory api.Memory, stack []uint64) T 42 | } 43 | 44 | // Result is an interface reprenting results of WebAssembly functions which 45 | // are written to the stack. 46 | // 47 | // The interface is mainly used as a constraint for generic type parameters. 48 | type Result interface { 49 | Value 50 | // Stores the result value onto the stack and optionally writing its 51 | // content to memory. 52 | StoreValue(memory api.Memory, stack []uint64) 53 | } 54 | 55 | // ParamResult is an interface implemented by types which can be used as both a 56 | // parameter and a result. 57 | type ParamResult[T any] interface { 58 | Param[T] 59 | Result 60 | } 61 | 62 | // Object is an interface which represents values that can be loaded or stored 63 | // in memory. 64 | // 65 | // The interface is mainly used as a constraint for generic type parameters. 66 | type Object[T any] interface { 67 | // Writes a human-readable representation of the object to w. 68 | FormatObject(w io.Writer, memory api.Memory, object []byte) 69 | // Loads and returns the object from the given byte slice. If the object 70 | // contains pointers it migh read them from the module memory passed as 71 | // first argument. 72 | LoadObject(memory api.Memory, object []byte) T 73 | // Stores the object to the given tye slice. If the object contains pointers 74 | // it might write them to the module memory passed as first argument (doing 75 | // so without writing to arbitrary location is difficult so this use case is 76 | // rather rare). 77 | StoreObject(memory api.Memory, object []byte) 78 | // Returns the size of the the object in bytes. The byte slices passed to 79 | // LoadObject and StoreObjects are guaranteed to be at least of the length 80 | // returned by this method. 81 | ObjectSize() int 82 | } 83 | 84 | // UnsafeLoadObject is a helper which may be used to implement the LoadObject 85 | // method for object types which do not contain any inner pointers. 86 | // 87 | // func (v T) LoadObject(_ api.Memory, object []byte) T { 88 | // return types.UnsafeLoadObject[T](object) 89 | // } 90 | func UnsafeLoadObject[T Object[T]](mem []byte) T { 91 | return *unsafeCastObject[T](mem) 92 | } 93 | 94 | // UnsafeStoreObject is a helper which may be used to implement the StoreObject 95 | // method for object types which do not contain any inner pointers. 96 | // 97 | // func (v T) StoreObject(_ api.Memory, object []byte) { 98 | // types.UnsafeStoreObject(object, v) 99 | // } 100 | func UnsafeStoreObject[T Object[T]](mem []byte, obj T) { 101 | *unsafeCastObject[T](mem) = obj 102 | } 103 | 104 | func unsafeCastObject[T Object[T]](mem []byte) *T { 105 | return (*T)(unsafe.Pointer(unsafe.SliceData(mem))) 106 | } 107 | 108 | func objectSize[T Object[T]]() int { 109 | var typ T 110 | return typ.ObjectSize() 111 | } 112 | 113 | type Int8 int8 114 | 115 | func (arg Int8) Format(w io.Writer) { 116 | fmt.Fprintf(w, "%d", arg) 117 | } 118 | 119 | func (arg Int8) FormatValue(w io.Writer, memory api.Memory, stack []uint64) { 120 | arg.LoadValue(memory, stack).Format(w) 121 | } 122 | 123 | func (arg Int8) FormatObject(w io.Writer, memory api.Memory, object []byte) { 124 | arg.LoadObject(memory, object).Format(w) 125 | } 126 | 127 | func (arg Int8) LoadValue(memory api.Memory, stack []uint64) Int8 { 128 | return Int8(api.DecodeI32(stack[0])) 129 | } 130 | 131 | func (arg Int8) LoadObject(memory api.Memory, object []byte) Int8 { 132 | return Int8(object[0]) 133 | } 134 | 135 | func (arg Int8) StoreValue(memory api.Memory, stack []uint64) { 136 | stack[0] = api.EncodeI32(int32(arg)) 137 | } 138 | 139 | func (arg Int8) StoreObject(memory api.Memory, object []byte) { 140 | object[0] = byte(arg) 141 | } 142 | 143 | func (arg Int8) ValueTypes() []api.ValueType { 144 | return []api.ValueType{api.ValueTypeI32} 145 | } 146 | 147 | func (arg Int8) ObjectSize() int { 148 | return 1 149 | } 150 | 151 | var ( 152 | _ Object[Int8] = Int8(0) 153 | _ Param[Int8] = Int8(0) 154 | _ Result = Int8(0) 155 | _ Formatter = Int8(0) 156 | ) 157 | 158 | type Int16 int16 159 | 160 | func (arg Int16) Format(w io.Writer) { 161 | fmt.Fprintf(w, "%d", arg) 162 | } 163 | 164 | func (arg Int16) FormatValue(w io.Writer, memory api.Memory, stack []uint64) { 165 | arg.LoadValue(memory, stack).Format(w) 166 | } 167 | 168 | func (arg Int16) FormatObject(w io.Writer, memory api.Memory, object []byte) { 169 | arg.LoadObject(memory, object).Format(w) 170 | } 171 | 172 | func (arg Int16) LoadValue(memory api.Memory, stack []uint64) Int16 { 173 | return Int16(api.DecodeI32(stack[0])) 174 | } 175 | 176 | func (arg Int16) LoadObject(memory api.Memory, object []byte) Int16 { 177 | return Int16(binary.LittleEndian.Uint16(object)) 178 | } 179 | 180 | func (arg Int16) StoreValue(memory api.Memory, stack []uint64) { 181 | stack[0] = api.EncodeI32(int32(arg)) 182 | } 183 | 184 | func (arg Int16) StoreObject(memory api.Memory, object []byte) { 185 | binary.LittleEndian.PutUint16(object, uint16(arg)) 186 | } 187 | 188 | func (arg Int16) ValueTypes() []api.ValueType { 189 | return []api.ValueType{api.ValueTypeI32} 190 | } 191 | 192 | func (arg Int16) ObjectSize() int { 193 | return 2 194 | } 195 | 196 | var ( 197 | _ Object[Int16] = Int16(0) 198 | _ Param[Int16] = Int16(0) 199 | _ Result = Int16(0) 200 | _ Formatter = Int16(0) 201 | ) 202 | 203 | type Int32 int32 204 | 205 | func (arg Int32) Format(w io.Writer) { 206 | fmt.Fprintf(w, "%d", arg) 207 | } 208 | 209 | func (arg Int32) FormatValue(w io.Writer, memory api.Memory, stack []uint64) { 210 | arg.LoadValue(memory, stack).Format(w) 211 | } 212 | 213 | func (arg Int32) FormatObject(w io.Writer, memory api.Memory, object []byte) { 214 | arg.LoadObject(memory, object).Format(w) 215 | } 216 | 217 | func (arg Int32) LoadValue(memory api.Memory, stack []uint64) Int32 { 218 | return Int32(api.DecodeI32(stack[0])) 219 | } 220 | 221 | func (arg Int32) LoadObject(memory api.Memory, object []byte) Int32 { 222 | return Int32(binary.LittleEndian.Uint32(object)) 223 | } 224 | 225 | func (arg Int32) StoreValue(memory api.Memory, stack []uint64) { 226 | stack[0] = api.EncodeI32(int32(arg)) 227 | } 228 | 229 | func (arg Int32) StoreObject(memory api.Memory, object []byte) { 230 | binary.LittleEndian.PutUint32(object, uint32(arg)) 231 | } 232 | 233 | func (arg Int32) ValueTypes() []api.ValueType { 234 | return []api.ValueType{api.ValueTypeI32} 235 | } 236 | 237 | func (arg Int32) ObjectSize() int { 238 | return 4 239 | } 240 | 241 | var ( 242 | _ Object[Int32] = Int32(0) 243 | _ Param[Int32] = Int32(0) 244 | _ Result = Int32(0) 245 | _ Formatter = Int32(0) 246 | ) 247 | 248 | type Int64 int64 249 | 250 | func (arg Int64) Format(w io.Writer) { 251 | fmt.Fprintf(w, "%d", arg) 252 | } 253 | 254 | func (arg Int64) FormatValue(w io.Writer, memory api.Memory, stack []uint64) { 255 | arg.LoadValue(memory, stack).Format(w) 256 | } 257 | 258 | func (arg Int64) FormatObject(w io.Writer, memory api.Memory, object []byte) { 259 | arg.LoadObject(memory, object).Format(w) 260 | } 261 | 262 | func (arg Int64) LoadValue(memory api.Memory, stack []uint64) Int64 { 263 | return Int64(stack[0]) 264 | } 265 | 266 | func (arg Int64) LoadObject(memory api.Memory, object []byte) Int64 { 267 | return Int64(binary.LittleEndian.Uint64(object)) 268 | } 269 | 270 | func (arg Int64) StoreValue(memory api.Memory, stack []uint64) { 271 | stack[0] = uint64(arg) 272 | } 273 | 274 | func (arg Int64) StoreObject(memory api.Memory, object []byte) { 275 | binary.LittleEndian.PutUint64(object, uint64(arg)) 276 | } 277 | 278 | func (arg Int64) ValueTypes() []api.ValueType { 279 | return []api.ValueType{api.ValueTypeI64} 280 | } 281 | 282 | func (arg Int64) ObjectSize() int { 283 | return 8 284 | } 285 | 286 | var ( 287 | _ Object[Int64] = Int64(0) 288 | _ Param[Int64] = Int64(0) 289 | _ Result = Int64(0) 290 | _ Formatter = Int64(0) 291 | ) 292 | 293 | type Bool bool 294 | 295 | func (arg Bool) Format(w io.Writer) { 296 | fmt.Fprintf(w, "%t", arg) 297 | } 298 | 299 | func (arg Bool) FormatValue(w io.Writer, memory api.Memory, stack []uint64) { 300 | arg.LoadValue(memory, stack).Format(w) 301 | } 302 | 303 | func (arg Bool) FormatObject(w io.Writer, memory api.Memory, object []byte) { 304 | arg.LoadObject(memory, object).Format(w) 305 | } 306 | 307 | func (arg Bool) LoadValue(memory api.Memory, stack []uint64) Bool { 308 | return Bool(api.DecodeU32(stack[0]) != 0) 309 | } 310 | 311 | func (arg Bool) LoadObject(memory api.Memory, object []byte) Bool { 312 | return Bool(object[0] != 0) 313 | } 314 | 315 | func (arg Bool) StoreValue(memory api.Memory, stack []uint64) { 316 | stack[0] = api.EncodeU32(uint32(arg.byte())) 317 | } 318 | 319 | func (arg Bool) StoreObject(memory api.Memory, object []byte) { 320 | object[0] = arg.byte() 321 | } 322 | 323 | func (arg Bool) ValueTypes() []api.ValueType { 324 | return []api.ValueType{api.ValueTypeI32} 325 | } 326 | 327 | func (arg Bool) ObjectSize() int { 328 | return 1 329 | } 330 | 331 | func (arg Bool) byte() byte { 332 | if arg { 333 | return 1 334 | } 335 | return 0 336 | } 337 | 338 | var ( 339 | _ Object[Bool] = Bool(false) 340 | _ Param[Bool] = Bool(false) 341 | _ Result = Bool(false) 342 | _ Formatter = Bool(false) 343 | ) 344 | 345 | type Uint8 uint8 346 | 347 | func (arg Uint8) Format(w io.Writer) { 348 | fmt.Fprintf(w, "%d", arg) 349 | } 350 | 351 | func (arg Uint8) FormatValue(w io.Writer, memory api.Memory, stack []uint64) { 352 | arg.LoadValue(memory, stack).Format(w) 353 | } 354 | 355 | func (arg Uint8) FormatObject(w io.Writer, memory api.Memory, object []byte) { 356 | arg.LoadObject(memory, object).Format(w) 357 | } 358 | 359 | func (arg Uint8) LoadValue(memory api.Memory, stack []uint64) Uint8 { 360 | return Uint8(api.DecodeU32(stack[0])) 361 | } 362 | 363 | func (arg Uint8) LoadObject(memory api.Memory, object []byte) Uint8 { 364 | return Uint8(object[0]) 365 | } 366 | 367 | func (arg Uint8) StoreValue(memory api.Memory, stack []uint64) { 368 | stack[0] = api.EncodeU32(uint32(arg)) 369 | } 370 | 371 | func (arg Uint8) StoreObject(memory api.Memory, object []byte) { 372 | object[0] = byte(arg) 373 | } 374 | 375 | func (arg Uint8) ValueTypes() []api.ValueType { 376 | return []api.ValueType{api.ValueTypeI32} 377 | } 378 | 379 | func (arg Uint8) ObjectSize() int { 380 | return 1 381 | } 382 | 383 | var ( 384 | _ Object[Uint8] = Uint8(0) 385 | _ Param[Uint8] = Uint8(0) 386 | _ Result = Uint8(0) 387 | _ Formatter = Uint8(0) 388 | ) 389 | 390 | type Uint16 uint16 391 | 392 | func (arg Uint16) Format(w io.Writer) { 393 | fmt.Fprintf(w, "%d", arg) 394 | } 395 | 396 | func (arg Uint16) FormatValue(w io.Writer, memory api.Memory, stack []uint64) { 397 | arg.LoadValue(memory, stack).Format(w) 398 | } 399 | 400 | func (arg Uint16) FormatObject(w io.Writer, memory api.Memory, object []byte) { 401 | arg.LoadObject(memory, object).Format(w) 402 | } 403 | 404 | func (arg Uint16) LoadValue(memory api.Memory, stack []uint64) Uint16 { 405 | return Uint16(api.DecodeU32(stack[0])) 406 | } 407 | 408 | func (arg Uint16) LoadObject(memory api.Memory, object []byte) Uint16 { 409 | return Uint16(binary.LittleEndian.Uint16(object)) 410 | } 411 | 412 | func (arg Uint16) StoreValue(memory api.Memory, stack []uint64) { 413 | stack[0] = api.EncodeU32(uint32(arg)) 414 | } 415 | 416 | func (arg Uint16) StoreObject(memory api.Memory, object []byte) { 417 | binary.LittleEndian.PutUint16(object, uint16(arg)) 418 | } 419 | 420 | func (arg Uint16) ValueTypes() []api.ValueType { 421 | return []api.ValueType{api.ValueTypeI32} 422 | } 423 | 424 | func (arg Uint16) ObjectSize() int { 425 | return 2 426 | } 427 | 428 | var ( 429 | _ Object[Uint16] = Uint16(0) 430 | _ Param[Uint16] = Uint16(0) 431 | _ Result = Uint16(0) 432 | _ Formatter = Uint16(0) 433 | ) 434 | 435 | type Uint32 uint32 436 | 437 | func (arg Uint32) Format(w io.Writer) { 438 | fmt.Fprintf(w, "%d", arg) 439 | } 440 | 441 | func (arg Uint32) FormatValue(w io.Writer, memory api.Memory, stack []uint64) { 442 | arg.LoadValue(memory, stack).Format(w) 443 | } 444 | 445 | func (arg Uint32) FormatObject(w io.Writer, memory api.Memory, object []byte) { 446 | arg.LoadObject(memory, object).Format(w) 447 | } 448 | 449 | func (arg Uint32) LoadValue(memory api.Memory, stack []uint64) Uint32 { 450 | return Uint32(api.DecodeU32(stack[0])) 451 | } 452 | 453 | func (arg Uint32) LoadObject(memory api.Memory, object []byte) Uint32 { 454 | return Uint32(binary.LittleEndian.Uint32(object)) 455 | } 456 | 457 | func (arg Uint32) StoreValue(memory api.Memory, stack []uint64) { 458 | stack[0] = api.EncodeU32(uint32(arg)) 459 | } 460 | 461 | func (arg Uint32) StoreObject(memory api.Memory, object []byte) { 462 | binary.LittleEndian.PutUint32(object, uint32(arg)) 463 | } 464 | 465 | func (arg Uint32) ValueTypes() []api.ValueType { 466 | return []api.ValueType{api.ValueTypeI32} 467 | } 468 | 469 | func (arg Uint32) ObjectSize() int { 470 | return 4 471 | } 472 | 473 | var ( 474 | _ Object[Uint32] = Uint32(0) 475 | _ Param[Uint32] = Uint32(0) 476 | _ Result = Uint32(0) 477 | _ Formatter = Uint32(0) 478 | ) 479 | 480 | type Uint64 uint64 481 | 482 | func (arg Uint64) Format(w io.Writer) { 483 | fmt.Fprintf(w, "%d", arg) 484 | } 485 | 486 | func (arg Uint64) FormatValue(w io.Writer, memory api.Memory, stack []uint64) { 487 | arg.LoadValue(memory, stack).Format(w) 488 | } 489 | 490 | func (arg Uint64) FormatObject(w io.Writer, memory api.Memory, object []byte) { 491 | arg.LoadObject(memory, object).Format(w) 492 | } 493 | 494 | func (arg Uint64) LoadValue(memory api.Memory, stack []uint64) Uint64 { 495 | return Uint64(stack[0]) 496 | } 497 | 498 | func (arg Uint64) LoadObject(memory api.Memory, object []byte) Uint64 { 499 | return Uint64(binary.LittleEndian.Uint64(object)) 500 | } 501 | 502 | func (arg Uint64) StoreValue(memory api.Memory, stack []uint64) { 503 | stack[0] = uint64(arg) 504 | } 505 | 506 | func (arg Uint64) StoreObject(memory api.Memory, object []byte) { 507 | binary.LittleEndian.PutUint64(object, uint64(arg)) 508 | } 509 | 510 | func (arg Uint64) ValueTypes() []api.ValueType { 511 | return []api.ValueType{api.ValueTypeI64} 512 | } 513 | 514 | func (arg Uint64) ObjectSize() int { 515 | return 8 516 | } 517 | 518 | var ( 519 | _ Object[Uint64] = Uint64(0) 520 | _ Param[Uint64] = Uint64(0) 521 | _ Result = Uint64(0) 522 | _ Formatter = Uint64(0) 523 | ) 524 | 525 | type Float32 float32 526 | 527 | func (arg Float32) Format(w io.Writer) { 528 | fmt.Fprintf(w, "%g", arg) 529 | } 530 | 531 | func (arg Float32) FormatValue(w io.Writer, memory api.Memory, stack []uint64) { 532 | arg.LoadValue(memory, stack).Format(w) 533 | } 534 | 535 | func (arg Float32) FormatObject(w io.Writer, memory api.Memory, object []byte) { 536 | arg.LoadObject(memory, object).Format(w) 537 | } 538 | 539 | func (arg Float32) LoadValue(memory api.Memory, stack []uint64) Float32 { 540 | return Float32(api.DecodeF32(stack[0])) 541 | } 542 | 543 | func (arg Float32) LoadObject(memory api.Memory, object []byte) Float32 { 544 | return Float32(math.Float32frombits(binary.LittleEndian.Uint32(object))) 545 | } 546 | 547 | func (arg Float32) StoreValue(memory api.Memory, stack []uint64) { 548 | stack[0] = api.EncodeF32(float32(arg)) 549 | } 550 | 551 | func (arg Float32) StoreObject(memory api.Memory, object []byte) { 552 | binary.LittleEndian.PutUint32(object, math.Float32bits(float32(arg))) 553 | } 554 | 555 | func (arg Float32) ValueTypes() []api.ValueType { 556 | return []api.ValueType{api.ValueTypeF32} 557 | } 558 | 559 | func (arg Float32) ObjectSize() int { 560 | return 4 561 | } 562 | 563 | var ( 564 | _ Object[Float32] = Float32(0) 565 | _ Param[Float32] = Float32(0) 566 | _ Result = Float32(0) 567 | _ Formatter = Float32(0) 568 | ) 569 | 570 | type Float64 float64 571 | 572 | func (arg Float64) Format(w io.Writer) { 573 | fmt.Fprintf(w, "%g", arg) 574 | } 575 | 576 | func (arg Float64) FormatValue(w io.Writer, memory api.Memory, stack []uint64) { 577 | arg.LoadValue(memory, stack).Format(w) 578 | } 579 | 580 | func (arg Float64) FormatObject(w io.Writer, memory api.Memory, object []byte) { 581 | arg.LoadObject(memory, object).Format(w) 582 | } 583 | 584 | func (arg Float64) LoadValue(memory api.Memory, stack []uint64) Float64 { 585 | return Float64(api.DecodeF64(stack[0])) 586 | } 587 | 588 | func (arg Float64) LoadObject(memory api.Memory, object []byte) Float64 { 589 | return Float64(math.Float64frombits(binary.LittleEndian.Uint64(object))) 590 | } 591 | 592 | func (arg Float64) StoreValue(memory api.Memory, stack []uint64) { 593 | stack[0] = api.EncodeF64(float64(arg)) 594 | } 595 | 596 | func (arg Float64) StoreObject(memory api.Memory, object []byte) { 597 | binary.LittleEndian.PutUint64(object, math.Float64bits(float64(arg))) 598 | } 599 | 600 | func (arg Float64) ValueTypes() []api.ValueType { 601 | return []api.ValueType{api.ValueTypeF64} 602 | } 603 | 604 | func (arg Float64) ObjectSize() int { 605 | return 8 606 | } 607 | 608 | var ( 609 | _ Param[Float64] = Float64(0) 610 | _ Result = Float64(0) 611 | _ Formatter = Float64(0) 612 | ) 613 | 614 | type Duration time.Duration 615 | 616 | func (arg Duration) Format(w io.Writer) { 617 | fmt.Fprintf(w, "%s", time.Duration(arg)) 618 | } 619 | 620 | func (arg Duration) FormatValue(w io.Writer, memory api.Memory, stack []uint64) { 621 | arg.LoadValue(memory, stack).Format(w) 622 | } 623 | 624 | func (arg Duration) FormatObject(w io.Writer, memory api.Memory, object []byte) { 625 | arg.LoadObject(memory, object).Format(w) 626 | } 627 | 628 | func (arg Duration) LoadValue(memory api.Memory, stack []uint64) Duration { 629 | return Duration(stack[0]) 630 | } 631 | 632 | func (arg Duration) LoadObject(memory api.Memory, object []byte) Duration { 633 | return Duration(binary.LittleEndian.Uint64(object)) 634 | } 635 | 636 | func (arg Duration) StoreValue(memory api.Memory, stack []uint64) { 637 | stack[0] = uint64(arg) 638 | } 639 | 640 | func (arg Duration) StoreObject(memory api.Memory, object []byte) { 641 | binary.LittleEndian.PutUint64(object, uint64(arg)) 642 | } 643 | 644 | func (arg Duration) ValueTypes() []api.ValueType { 645 | return []api.ValueType{api.ValueTypeI64} 646 | } 647 | 648 | func (arg Duration) ObjectSize() int { 649 | return 8 650 | } 651 | 652 | var ( 653 | _ Object[Duration] = Duration(0) 654 | _ Param[Duration] = Duration(0) 655 | _ Result = Duration(0) 656 | _ Formatter = Duration(0) 657 | ) 658 | 659 | type primitive interface { 660 | ~int8 | ~int16 | ~int32 | ~int64 | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr | ~float32 | ~float64 661 | } 662 | 663 | // Array is a type representing a sequence of contiguous items in memory. Array 664 | // values are composed of a pair of pointer and number of items. The item size 665 | // is determined by the size of the type T. 666 | // 667 | // Arrays only satisfy the Param interface, they cannot be used as return value 668 | // of functions. 669 | // 670 | // At this time, arrays may only be composed of primitive Go types, but this 671 | // restriction may be relaxed in a future version. Use List to laod sequences of 672 | // complex types. 673 | type Array[T primitive] []T 674 | 675 | func (arg Array[T]) Format(w io.Writer) { 676 | fmt.Fprintf(w, "[") 677 | for i, v := range arg { 678 | if i > 0 { 679 | fmt.Fprintf(w, ", ") 680 | } 681 | fmt.Fprintf(w, "%v", v) 682 | } 683 | fmt.Fprintf(w, "]") 684 | } 685 | 686 | func (arg Array[T]) FormatObject(w io.Writer, memory api.Memory, object []byte) { 687 | arg.LoadObject(memory, object).Format(w) 688 | } 689 | 690 | func (arg Array[T]) FormatValue(w io.Writer, memory api.Memory, stack []uint64) { 691 | arg.LoadValue(memory, stack).Format(w) 692 | } 693 | 694 | func (arg Array[T]) LoadObject(memory api.Memory, object []byte) Array[T] { 695 | offset := binary.LittleEndian.Uint32(object[:4]) 696 | length := binary.LittleEndian.Uint32(object[4:]) 697 | return arg.load(memory, offset, length) 698 | } 699 | 700 | func (arg Array[T]) LoadValue(memory api.Memory, stack []uint64) Array[T] { 701 | offset := api.DecodeU32(stack[0]) 702 | length := api.DecodeU32(stack[1]) 703 | return arg.load(memory, offset, length) 704 | } 705 | 706 | func (arg Array[T]) load(memory api.Memory, offset, length uint32) Array[T] { 707 | size := unsafe.Sizeof(T(0)) 708 | data := wasm.Read(memory, offset, length*uint32(size)) 709 | return unsafe.Slice(*(**T)(unsafe.Pointer(&data)), length) 710 | } 711 | 712 | func (arg Array[T]) StoreObject(memory api.Memory, object []byte) { 713 | // TODO: move this to a compile time check by separating the load/store 714 | // capabilities of the Object interface. 715 | panic("NOT IMPLEMENTED") 716 | } 717 | 718 | func (arg Array[T]) ObjectSize() int { 719 | return 8 720 | } 721 | 722 | func (arg Array[T]) ValueTypes() []api.ValueType { 723 | return []api.ValueType{api.ValueTypeI32, api.ValueTypeI32} 724 | } 725 | 726 | var ( 727 | _ Param[Array[byte]] = Array[byte](nil) 728 | _ Formatter = Array[byte](nil) 729 | ) 730 | 731 | // Bytes is a type alias for arrays of bytes, which is a common use case 732 | // (e.g. I/O functions working on a byte buffer). 733 | type Bytes Array[byte] 734 | 735 | func (arg Bytes) Format(w io.Writer) { 736 | if len(arg) <= 32 { 737 | fmt.Fprintf(w, "%q", arg) 738 | return 739 | } 740 | b := arg[:20:20] 741 | b = append(b, "... ("...) 742 | b = strconv.AppendUint(b, uint64(len(arg)), 10) 743 | b = append(b, " bytes)"...) 744 | fmt.Fprintf(w, "%q", b) 745 | } 746 | 747 | func (arg Bytes) FormatObject(w io.Writer, memory api.Memory, object []byte) { 748 | arg.LoadObject(memory, object).Format(w) 749 | } 750 | 751 | func (arg Bytes) FormatValue(w io.Writer, memory api.Memory, stack []uint64) { 752 | arg.LoadValue(memory, stack).Format(w) 753 | } 754 | 755 | func (arg Bytes) LoadObject(memory api.Memory, object []byte) Bytes { 756 | return Bytes(arg.array().LoadObject(memory, object)) 757 | } 758 | 759 | func (arg Bytes) LoadValue(memory api.Memory, stack []uint64) Bytes { 760 | return Bytes(arg.array().LoadValue(memory, stack)) 761 | } 762 | 763 | func (arg Bytes) StoreObject(memory api.Memory, object []byte) { 764 | arg.array().StoreObject(memory, object) 765 | } 766 | 767 | func (arg Bytes) ObjectSize() int { 768 | return arg.array().ObjectSize() 769 | } 770 | 771 | func (arg Bytes) ValueTypes() []api.ValueType { 772 | return arg.array().ValueTypes() 773 | } 774 | 775 | func (arg Bytes) array() Array[byte] { 776 | return (Array[byte])(arg) 777 | } 778 | 779 | var ( 780 | _ Param[Bytes] = Bytes(nil) 781 | _ Formatter = Bytes(nil) 782 | ) 783 | 784 | // String is similar to Bytes but holds the value as a Go string which is not 785 | // sharing memory with the WebAssembly program memory anymore. 786 | type String string 787 | 788 | func (arg String) Format(w io.Writer) { 789 | fmt.Fprintf(w, "%q", arg) 790 | } 791 | 792 | func (arg String) FormatValue(w io.Writer, memory api.Memory, stack []uint64) { 793 | arg.LoadValue(memory, stack).Format(w) 794 | } 795 | 796 | func (arg String) LoadValue(memory api.Memory, stack []uint64) String { 797 | offset := api.DecodeU32(stack[0]) 798 | length := api.DecodeU32(stack[1]) 799 | return String(wasm.Read(memory, offset, length)) 800 | } 801 | 802 | func (arg String) ValueTypes() []api.ValueType { 803 | return []api.ValueType{api.ValueTypeI32, api.ValueTypeI32} 804 | } 805 | 806 | var ( 807 | _ Param[String] = String("") 808 | _ Formatter = String("") 809 | ) 810 | 811 | // Pointer is a parameter type used to represent a pointer to an object held in 812 | // program memory. 813 | type Pointer[T Object[T]] struct { 814 | memory api.Memory 815 | offset uint32 816 | } 817 | 818 | // New constructs a pointer to an object of type T backed by Go memory. 819 | // 820 | // This function is mostly useful to construct pointers to pass to module 821 | // methods in tests, its usage in actual production code should be rare. 822 | func New[T Object[T]]() Pointer[T] { 823 | return Ptr[T](wasm.NewFixedSizeMemory(uint32(objectSize[T]())), 0) 824 | } 825 | 826 | // Ptr constructs a pointer of objects T backed by a memory area at a specified 827 | // offset. 828 | // 829 | // This function is mostly useful to construct pointers to pass to module 830 | // methods in tests, its usage in actual production code should be rare. 831 | func Ptr[T Object[T]](memory api.Memory, offset uint32) Pointer[T] { 832 | return Pointer[T]{memory, offset} 833 | } 834 | 835 | func (arg Pointer[T]) FormatValue(w io.Writer, memory api.Memory, stack []uint64) { 836 | arg = arg.LoadValue(memory, stack) 837 | fmt.Fprintf(w, "&") 838 | arg.Load().FormatObject(w, memory, arg.Object()) 839 | } 840 | 841 | func (arg Pointer[T]) LoadValue(memory api.Memory, stack []uint64) Pointer[T] { 842 | return Pointer[T]{memory, api.DecodeU32(stack[0])} 843 | } 844 | 845 | func (arg Pointer[T]) ValueTypes() []api.ValueType { 846 | return []api.ValueType{api.ValueTypeI32} 847 | } 848 | 849 | func (arg Pointer[T]) FormatObject(w io.Writer, memory api.Memory, object []byte) { 850 | // TODO: recursively follow pointer 851 | fmt.Fprintf(w, "Pointer(%#x)", arg.offset) 852 | } 853 | 854 | func (arg Pointer[T]) LoadObject(memory api.Memory, object []byte) Pointer[T] { 855 | offset := uint32(binary.LittleEndian.Uint32(object)) 856 | return Pointer[T]{memory, offset} 857 | } 858 | 859 | func (arg Pointer[T]) StoreObject(memory api.Memory, object []byte) { 860 | binary.LittleEndian.PutUint32(object, arg.offset) 861 | } 862 | 863 | func (arg Pointer[T]) ObjectSize() int { 864 | return 4 865 | } 866 | 867 | func (arg Pointer[T]) Memory() api.Memory { 868 | return arg.memory 869 | } 870 | 871 | func (arg Pointer[T]) Offset() uint32 { 872 | return arg.offset 873 | } 874 | 875 | func (arg Pointer[T]) Object() []byte { 876 | return wasm.Read(arg.memory, arg.offset, uint32(objectSize[T]())) 877 | } 878 | 879 | func (arg Pointer[T]) Load() (value T) { 880 | return value.LoadObject(arg.memory, arg.Object()) 881 | } 882 | 883 | func (arg Pointer[T]) Store(value T) { 884 | value.StoreObject(arg.memory, arg.Object()) 885 | } 886 | 887 | func (arg Pointer[T]) Index(index int) Pointer[T] { 888 | return Pointer[T]{arg.memory, arg.offset + uint32(index*objectSize[T]())} 889 | } 890 | 891 | func (arg Pointer[T]) Append(buffer []T, count int) []T { 892 | for i := 0; i < count; i++ { 893 | buffer = append(buffer, arg.Index(i).Load()) 894 | } 895 | return buffer 896 | } 897 | 898 | func (arg Pointer[T]) Slice(count int) []T { 899 | return arg.Append(make([]T, 0, count), count) 900 | } 901 | 902 | func (arg Pointer[T]) UnsafeSlice(count int) []T { 903 | var typ T 904 | size := typ.ObjectSize() 905 | if count == 0 || size == 0 { 906 | return nil 907 | } 908 | data := wasm.Read(arg.memory, arg.offset, uint32(count*size)) 909 | return unsafe.Slice((*T)(unsafe.Pointer(&data[0])), count) 910 | } 911 | 912 | var ( 913 | _ Param[Pointer[None]] = Pointer[None]{} 914 | ) 915 | 916 | // List represents a sequence of objects held in module memory. 917 | type List[T Object[T]] struct { 918 | ptr Pointer[T] 919 | len uint32 920 | } 921 | 922 | // MakeList constructs a list from a pointer to an object of type T 923 | // and a length. 924 | func MakeList[T Object[T]](ptr Pointer[T], length int) List[T] { 925 | return List[T]{ptr, uint32(length)} 926 | } 927 | 928 | func (arg List[T]) FormatValue(w io.Writer, memory api.Memory, stack []uint64) { 929 | fmt.Fprintf(w, "[") 930 | arg = arg.LoadValue(memory, stack) 931 | for i := 0; i < arg.Len(); i++ { 932 | if i > 0 { 933 | fmt.Fprintf(w, ",") 934 | } 935 | p := arg.ptr.Index(i) 936 | v := p.Load() 937 | v.FormatObject(w, memory, p.Object()) 938 | } 939 | fmt.Fprintf(w, "]") 940 | } 941 | 942 | func (arg List[T]) LoadValue(memory api.Memory, stack []uint64) List[T] { 943 | return List[T]{ 944 | ptr: arg.ptr.LoadValue(memory, stack), 945 | len: api.DecodeU32(stack[1]), 946 | } 947 | } 948 | 949 | func (arg List[T]) ValueTypes() []api.ValueType { 950 | return []api.ValueType{api.ValueTypeI32, api.ValueTypeI32} 951 | } 952 | 953 | func (arg List[T]) Len() int { 954 | return int(arg.len) 955 | } 956 | 957 | func (arg List[T]) Index(index int) Pointer[T] { 958 | if index < 0 || index >= arg.Len() { 959 | panic(fmt.Errorf("%T: index out of bounds (%d/%d)", arg, index, arg.Len())) 960 | } 961 | return arg.ptr.Index(index) 962 | } 963 | 964 | func (arg List[T]) Range(fn func(int, T) bool) { 965 | for i := 0; i < arg.Len(); i++ { 966 | if !fn(i, arg.ptr.Index(i).Load()) { 967 | break 968 | } 969 | } 970 | } 971 | 972 | func (arg List[T]) Append(buffer []T) []T { 973 | return arg.ptr.Append(buffer, arg.Len()) 974 | } 975 | 976 | func (arg List[T]) Slice() []T { 977 | return arg.ptr.Slice(arg.Len()) 978 | } 979 | 980 | func (arg List[T]) UnsafeSlice() []T { 981 | return arg.ptr.UnsafeSlice(arg.Len()) 982 | } 983 | 984 | var ( 985 | _ Param[List[None]] = List[None]{} 986 | ) 987 | 988 | // Optional represents a function result which may be missing due to the program 989 | // encountering an error. The type contains either a value of type T or an error. 990 | type Optional[T ParamResult[T]] struct { 991 | res T 992 | err error 993 | } 994 | 995 | // Value returns the underlying value of the optional. The method panics if opt 996 | // contained an error. 997 | func (opt Optional[T]) Result() T { 998 | if opt.err != nil { 999 | panic(opt.err) 1000 | } 1001 | return opt.res 1002 | } 1003 | 1004 | // Error returns the error embedded in opt, or nil if opt contains a value. 1005 | func (opt Optional[T]) Error() error { 1006 | return opt.err 1007 | } 1008 | 1009 | func (opt Optional[T]) FormatValue(w io.Writer, memory api.Memory, stack []uint64) { 1010 | if opt.err != nil { 1011 | fmt.Fprintf(w, "ERROR: %v", opt.err) 1012 | } else { 1013 | opt.res.FormatValue(w, memory, stack) 1014 | } 1015 | } 1016 | 1017 | func (opt Optional[T]) LoadValue(memory api.Memory, stack []uint64) Optional[T] { 1018 | n := len(opt.res.ValueTypes()) 1019 | opt.res = opt.res.LoadValue(memory, stack[:n:n]) 1020 | opt.err = makeErrno(api.DecodeI32(stack[n])) 1021 | return opt 1022 | } 1023 | 1024 | func (opt Optional[T]) StoreValue(memory api.Memory, stack []uint64) { 1025 | if n := len(opt.res.ValueTypes()); opt.err != nil { 1026 | for i := range stack[:n] { 1027 | stack[i] = 0 1028 | } 1029 | stack[n] = api.EncodeI32(int32(AsErrno(opt.err))) 1030 | } else { 1031 | opt.res.StoreValue(memory, stack[:n:n]) 1032 | stack[n] = 0 1033 | } 1034 | } 1035 | 1036 | func (opt Optional[T]) ValueTypes() []api.ValueType { 1037 | return append(opt.res.ValueTypes(), api.ValueTypeI32) 1038 | } 1039 | 1040 | // Opt constructs an optional from a pair of a result and error. 1041 | func Opt[T ParamResult[T]](res T, err error) Optional[T] { 1042 | return Optional[T]{res: res, err: err} 1043 | } 1044 | 1045 | // Res constructs an optional from a value. 1046 | func Res[T ParamResult[T]](res T) Optional[T] { 1047 | return Optional[T]{res: res} 1048 | } 1049 | 1050 | // Err constructs an optional from an error. The function panics if the error is 1051 | // nil since a nil error indicates that the optional should contain a value. 1052 | func Err[T ParamResult[T]](err error) Optional[T] { 1053 | if err == nil { 1054 | panic("cannot create an optional error value from a nil error") 1055 | } 1056 | return Optional[T]{err: err} 1057 | } 1058 | 1059 | var ( 1060 | _ Param[Optional[None]] = Optional[None]{} 1061 | _ Result = Optional[None]{} 1062 | ) 1063 | 1064 | // None is a special type of size zero bytes. 1065 | type None struct{} 1066 | 1067 | func formatNone(w io.Writer) { io.WriteString(w, "(none)") } 1068 | 1069 | func (None) Format(w io.Writer) { formatNone(w) } 1070 | 1071 | func (None) FormatValue(w io.Writer, _ api.Memory, _ []uint64) { formatNone(w) } 1072 | 1073 | func (None) FormatObject(w io.Writer, _ api.Memory, _ []byte) { formatNone(w) } 1074 | 1075 | func (None) LoadValue(api.Memory, []uint64) (none None) { return } 1076 | 1077 | func (None) LoadObject(api.Memory, []byte) (none None) { return } 1078 | 1079 | func (None) StoreValue(api.Memory, []uint64) {} 1080 | 1081 | func (None) StoreObject(api.Memory, []byte) {} 1082 | 1083 | func (None) ValueTypes() []api.ValueType { return nil } 1084 | 1085 | func (None) ObjectSize() int { return 0 } 1086 | 1087 | var ( 1088 | _ Object[None] = None{} 1089 | _ Param[None] = None{} 1090 | _ Result = None{} 1091 | _ Formatter = None{} 1092 | ) 1093 | 1094 | // Error is a special optional type which either contains an error or no values. 1095 | // It is useful to represent return values of functions that may error but do 1096 | // not return any other values. 1097 | type Error = Optional[None] 1098 | 1099 | // OK is a special value indicating that a function which returns nothing has 1100 | // not errored either. 1101 | var OK Optional[None] 1102 | 1103 | // Fail constructs an Error value from the given Go error. The function panics 1104 | // if err is nil. 1105 | func Fail(err error) Error { return Err[None](err) } 1106 | 1107 | // Errno is an error type representing error codes that are often returned by 1108 | // WebAssembly module functions. 1109 | // 1110 | // This type is employed when converting Go errors embedded into optional values 1111 | // to WebAssembly error codes. Go errors are converted to Errno values by 1112 | // unwrapping and inspecting the values to determine their error code. If a Go 1113 | // error has an Errno method, it is called to be converted to an Errno value. 1114 | // 1115 | // The special value 0 always indicates that there were no errors, similarly to 1116 | // how a Go program would treat a nil error. 1117 | type Errno int32 1118 | 1119 | // Errno returns err as an int32 value. 1120 | func (err Errno) Errno() int32 { 1121 | return int32(err) 1122 | } 1123 | 1124 | // Error returns a human readable representation of err. 1125 | func (err Errno) Error() string { 1126 | if i := int(err); i >= 0 && i < len(ErrorStrings) { 1127 | if s := ErrorStrings[i]; s != "" { 1128 | return s 1129 | } 1130 | } 1131 | return fmt.Sprintf("errno(%d)", err) 1132 | } 1133 | 1134 | func (err Errno) FormatValue(w io.Writer, memory api.Memory, stack []uint64) { 1135 | io.WriteString(w, err.LoadValue(memory, stack).Error()) 1136 | } 1137 | 1138 | func (err Errno) LoadValue(memory api.Memory, stack []uint64) Errno { 1139 | return Errno(api.DecodeI32(stack[0])) 1140 | } 1141 | 1142 | func (err Errno) StoreValue(memory api.Memory, stack []uint64) { 1143 | stack[0] = api.EncodeI32(int32(err)) 1144 | } 1145 | 1146 | func (err Errno) ValueTypes() []api.ValueType { 1147 | return []api.ValueType{api.ValueTypeI32} 1148 | } 1149 | 1150 | var ( 1151 | _ Param[Errno] = Errno(0) 1152 | _ Result = Errno(0) 1153 | 1154 | // ErrorStrings is a global used in the formatting of Errno values. 1155 | // 1156 | // The table is empty by default, but the program may assign a table indexed 1157 | // by error code to customize the error messages. 1158 | // 1159 | // There is no synchronization so it is recommended to assign this global 1160 | // during program initialization (e.g. in an init function). 1161 | ErrorStrings []string 1162 | ) 1163 | 1164 | func makeErrno(errno int32) error { 1165 | if errno == 0 { 1166 | return nil 1167 | } 1168 | return Errno(errno) 1169 | } 1170 | 1171 | func AsErrno(err error) Errno { 1172 | if err == nil { 1173 | return 0 1174 | } 1175 | for { 1176 | switch e := errors.Unwrap(err).(type) { 1177 | case nil: 1178 | return -1 // unknown, just don't return 0 1179 | case interface{ Errno() int32 }: 1180 | return Errno(e.Errno()) 1181 | case syscall.Errno: 1182 | return Errno(int32(e)) 1183 | default: 1184 | err = e 1185 | } 1186 | } 1187 | } 1188 | --------------------------------------------------------------------------------