├── .gitignore ├── LICENSE ├── README.md ├── _examples ├── basic │ ├── README.md │ ├── Taskfile.yml │ ├── go.mod │ ├── go.sum │ ├── guest │ │ └── guest.go │ └── host.go ├── strings │ ├── README.md │ ├── Taskfile.yml │ ├── go.mod │ ├── go.sum │ ├── guest │ │ └── guest.go │ └── host.go └── wasm-target.json ├── errors.go ├── go.mod ├── go.sum ├── host_funcs.go ├── host_funcs_test.go ├── store.go ├── types_int.go ├── types_mem.go ├── types_mem_test.go ├── types_misc.go ├── types_test.go ├── types_uint.go └── wazero.go /.gitignore: -------------------------------------------------------------------------------- 1 | /_examples/*/*.wasm 2 | .task/ 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | 2024 Gram 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 13 | in all 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wypes 2 | 3 | Go library to define type-safe host functions in [wazero](https://github.com/tetratelabs/wazero) and other [WebAssembly](https://webassembly.org/) runtimes. 4 | 5 | Features: 6 | 7 | * 🛡 Type safe 8 | * 🐎 Fast 9 | * 🔨 Works with any WebAssmebly runtime, like [wazero](https://github.com/tetratelabs/wazero) or [wasman](https://github.com/c0mm4nd/wasman) 10 | * 🧠 Handles for you memory operations 11 | * 👉 Manages external references 12 | * 🧼 Simple and clean API 13 | * 🐜 Can be compiled with TinyGo 14 | * 😎 No reflect, no unsafe, only generics and dedication. 15 | 16 | ## 📦 Installation 17 | 18 | ```bash 19 | go get github.com/orsinium-labs/wypes 20 | ``` 21 | 22 | ## 🔧 Usage 23 | 24 | Define a function using provided types: 25 | 26 | ```go 27 | func addI32(a wypes.Int32, b wypes.Int32) wypes.Int32 { 28 | return a + b 29 | } 30 | ``` 31 | 32 | Define a mapping of module and function names to function definitions: 33 | 34 | ```go 35 | modules := wypes.Modules{ 36 | "env": { 37 | "add_i32": wypes.H2(addI32), 38 | }, 39 | } 40 | ``` 41 | 42 | Link the modules to the runtime. We provide a convenience method to do this for wazero: 43 | 44 | ```go 45 | err := modules.DefineWazero(r, nil) 46 | ``` 47 | 48 | That's it! Now the wasm module can call the `env.add_i32` function. 49 | 50 | ## 🛹 Tricks 51 | 52 | The library provides lots of useful types that you can use in your functions. Make sure to [check the docs](https://pkg.go.dev/github.com/orsinium-labs/wypes). A few highlights: 53 | 54 | 1. [Context](https://pkg.go.dev/github.com/orsinium-labs/wypes#Context) provides access to the context.Context passed into the guest function call in wazero. 55 | 1. [Store](https://pkg.go.dev/github.com/orsinium-labs/wypes#Store) provides access to all the state: memory, stack, references. 56 | 1. [Duration](https://pkg.go.dev/github.com/orsinium-labs/wypes#Duration) and [Time](https://pkg.go.dev/github.com/orsinium-labs/wypes#Time) to pass time.Duration and time.Time (as UNIX timestamp). 57 | 1. [HostRef](https://pkg.go.dev/github.com/orsinium-labs/wypes#HostRef) can hold a reference to the [Refs](https://pkg.go.dev/github.com/orsinium-labs/wypes#Refs) store of host objects. 58 | 1. [Void](https://pkg.go.dev/github.com/orsinium-labs/wypes#Void) is used as the return type for functions that return no value. 59 | 60 | See [documentation](https://pkg.go.dev/github.com/orsinium-labs/wypes) for more. 61 | -------------------------------------------------------------------------------- /_examples/basic/README.md: -------------------------------------------------------------------------------- 1 | # basic 2 | 3 | A basic example of using wypes. It demonstrates how to: 4 | 5 | 1. Integrate wypes with wazero 6 | 1. Define and call host functions 7 | 8 | Running using [task](https://taskfile.dev/installation/): 9 | 10 | ```bash 11 | task run 12 | ``` 13 | 14 | Expected output: 15 | 16 | ```text 17 | Result: (4 + 5) * 2 = 18 18 | ``` 19 | -------------------------------------------------------------------------------- /_examples/basic/Taskfile.yml: -------------------------------------------------------------------------------- 1 | # https://taskfile.dev 2 | version: '3' 3 | 4 | tasks: 5 | build-guest: 6 | cmds: 7 | - tinygo build -target ../wasm-target.json ./guest 8 | - wasm-strip guest.wasm 9 | - ls -lh guest.wasm 10 | 11 | run: 12 | cmds: 13 | - task: build-guest 14 | - go run . 15 | -------------------------------------------------------------------------------- /_examples/basic/go.mod: -------------------------------------------------------------------------------- 1 | module wazero 2 | 3 | go 1.21.3 4 | 5 | replace github.com/orsinium-labs/wypes => ../../ 6 | 7 | require ( 8 | github.com/orsinium-labs/wypes v0.1.0 9 | github.com/tetratelabs/wazero v1.6.0 10 | ) 11 | -------------------------------------------------------------------------------- /_examples/basic/go.sum: -------------------------------------------------------------------------------- 1 | github.com/orsinium-labs/tinytest v1.0.0 h1:YiGm/whlGm3mn/ynx9CCFuvEa3Q6yEGrzrKXEqJOkdc= 2 | github.com/orsinium-labs/tinytest v1.0.0/go.mod h1:GwcYBp0aKi6zujzBXFpCnqw6RSLSp9JSedDyu/V1DF4= 3 | github.com/tetratelabs/wazero v1.6.0 h1:z0H1iikCdP8t+q341xqepY4EWvHEw8Es7tlqiVzlP3g= 4 | github.com/tetratelabs/wazero v1.6.0/go.mod h1:0U0G41+ochRKoPKCJlh0jMg1CHkyfK8kDqiirMmKY8A= 5 | -------------------------------------------------------------------------------- /_examples/basic/guest/guest.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | //go:export add_i32 4 | func addI32(int32, int32) int32 5 | 6 | //go:export run 7 | func Run() int32 { 8 | return addI32(4, 5) * 2 9 | } 10 | -------------------------------------------------------------------------------- /_examples/basic/host.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | _ "embed" 6 | "errors" 7 | "fmt" 8 | "log" 9 | 10 | "github.com/orsinium-labs/wypes" 11 | "github.com/tetratelabs/wazero" 12 | ) 13 | 14 | //go:embed guest.wasm 15 | var source []byte 16 | 17 | func addI32(a wypes.Int32, b wypes.Int32) wypes.Int32 { 18 | return a + b 19 | } 20 | 21 | func main() { 22 | err := run() 23 | if err != nil { 24 | log.Fatalf("error: %v", err) 25 | } 26 | } 27 | 28 | func run() error { 29 | ctx := context.Background() 30 | r := wazero.NewRuntime(ctx) 31 | modules := wypes.Modules{ 32 | "env": { 33 | "add_i32": wypes.H2(addI32), 34 | }, 35 | } 36 | err := modules.DefineWazero(r, nil) 37 | if err != nil { 38 | return fmt.Errorf("define host functions: %v", err) 39 | } 40 | m, err := r.Instantiate(ctx, source) 41 | if err != nil { 42 | return fmt.Errorf("instantiate module: %v", err) 43 | } 44 | entry := m.ExportedFunction("run") 45 | if entry == nil { 46 | return errors.New("guest function run is not defined or not exported") 47 | } 48 | res, err := entry.Call(ctx) 49 | if err != nil { 50 | return fmt.Errorf("call exported function: %v", err) 51 | } 52 | fmt.Printf("Result: (4 + 5) * 2 = %v", res[0]) 53 | return nil 54 | } 55 | -------------------------------------------------------------------------------- /_examples/strings/README.md: -------------------------------------------------------------------------------- 1 | # basic 2 | 3 | A basic example of using wypes. It demonstrates how to: 4 | 5 | 1. Integrate wypes with wazero 6 | 1. Define and call host functions 7 | 8 | Running using [task](https://taskfile.dev/installation/): 9 | 10 | ```bash 11 | task run 12 | ``` 13 | 14 | Expected output: 15 | 16 | ```text 17 | Result: (4 + 5) * 2 = 18 18 | ``` 19 | -------------------------------------------------------------------------------- /_examples/strings/Taskfile.yml: -------------------------------------------------------------------------------- 1 | # https://taskfile.dev 2 | version: '3' 3 | 4 | tasks: 5 | build-guest: 6 | cmds: 7 | - tinygo build -target ../wasm-target.json ./guest 8 | - wasm-strip guest.wasm 9 | - ls -lh guest.wasm 10 | 11 | run: 12 | cmds: 13 | - task: build-guest 14 | - go run . 15 | -------------------------------------------------------------------------------- /_examples/strings/go.mod: -------------------------------------------------------------------------------- 1 | module wazero 2 | 3 | go 1.21.3 4 | 5 | replace github.com/orsinium-labs/wypes => ../../ 6 | 7 | require ( 8 | github.com/orsinium-labs/wypes v0.1.0 9 | github.com/tetratelabs/wazero v1.6.0 10 | ) 11 | -------------------------------------------------------------------------------- /_examples/strings/go.sum: -------------------------------------------------------------------------------- 1 | github.com/orsinium-labs/tinytest v1.0.0 h1:YiGm/whlGm3mn/ynx9CCFuvEa3Q6yEGrzrKXEqJOkdc= 2 | github.com/orsinium-labs/tinytest v1.0.0/go.mod h1:GwcYBp0aKi6zujzBXFpCnqw6RSLSp9JSedDyu/V1DF4= 3 | github.com/tetratelabs/wazero v1.6.0 h1:z0H1iikCdP8t+q341xqepY4EWvHEw8Es7tlqiVzlP3g= 4 | github.com/tetratelabs/wazero v1.6.0/go.mod h1:0U0G41+ochRKoPKCJlh0jMg1CHkyfK8kDqiirMmKY8A= 5 | -------------------------------------------------------------------------------- /_examples/strings/guest/guest.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | //go:export print 4 | func hostPrint(string) 5 | 6 | //go:export greet 7 | func greet() { 8 | hostPrint("Hello, Joe") 9 | } 10 | -------------------------------------------------------------------------------- /_examples/strings/host.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | _ "embed" 6 | "errors" 7 | "fmt" 8 | "log" 9 | 10 | "github.com/orsinium-labs/wypes" 11 | "github.com/tetratelabs/wazero" 12 | ) 13 | 14 | //go:embed guest.wasm 15 | var source []byte 16 | 17 | func hostPrint(s wypes.String) wypes.Void { 18 | fmt.Println(s.Unwrap()) 19 | return wypes.Void{} 20 | } 21 | 22 | func main() { 23 | err := run() 24 | if err != nil { 25 | log.Fatalf("error: %v", err) 26 | } 27 | } 28 | 29 | func run() error { 30 | ctx := context.Background() 31 | r := wazero.NewRuntime(ctx) 32 | modules := wypes.Modules{ 33 | "env": { 34 | "print": wypes.H1(hostPrint), 35 | }, 36 | } 37 | err := modules.DefineWazero(r, nil) 38 | if err != nil { 39 | return fmt.Errorf("define host functions: %v", err) 40 | } 41 | m, err := r.Instantiate(ctx, source) 42 | if err != nil { 43 | return fmt.Errorf("instantiate module: %v", err) 44 | } 45 | entry := m.ExportedFunction("greet") 46 | if entry == nil { 47 | return errors.New("guest function greet is not defined or not exported") 48 | } 49 | _, err = entry.Call(ctx) 50 | if err != nil { 51 | return fmt.Errorf("call exported function: %v", err) 52 | } 53 | return nil 54 | } 55 | -------------------------------------------------------------------------------- /_examples/wasm-target.json: -------------------------------------------------------------------------------- 1 | { 2 | "llvm-target": "wasm32-unknown-unknown", 3 | "cpu": "generic", 4 | "features": "+mutable-globals,+nontrapping-fptoint,+sign-ext,+bulk-memory", 5 | "build-tags": ["tinygo.wasm", "wasm_unknown"], 6 | "goos": "linux", 7 | "goarch": "arm", 8 | "linker": "wasm-ld", 9 | "rtlib": "compiler-rt", 10 | "scheduler": "none", 11 | "cflags": ["-mno-bulk-memory", "-mnontrapping-fptoint", "-msign-ext"], 12 | "ldflags": [ 13 | "--allow-undefined", 14 | "--no-demangle", 15 | "--no-entry", 16 | "--initial-memory=65536", 17 | "--max-memory=65536", 18 | "--stack-first", 19 | "-zstack-size=14752" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /errors.go: -------------------------------------------------------------------------------- 1 | package wypes 2 | 3 | import "errors" 4 | 5 | var ( 6 | ErrRefNotFound = errors.New("HostRef with the given ID is not found in Refs") 7 | ErrMemRead = errors.New("Memory.Read is out of bounds") 8 | ErrMemWrite = errors.New("Memory.Write is out of bounds") 9 | ErrRefCast = errors.New("Reference returned by Refs.Get is not of the type expected by HostRef") 10 | ) 11 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/orsinium-labs/wypes 2 | 3 | go 1.21.3 4 | 5 | require ( 6 | github.com/orsinium-labs/tinytest v1.0.0 7 | github.com/tetratelabs/wazero v1.6.0 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/orsinium-labs/tinytest v1.0.0 h1:YiGm/whlGm3mn/ynx9CCFuvEa3Q6yEGrzrKXEqJOkdc= 2 | github.com/orsinium-labs/tinytest v1.0.0/go.mod h1:GwcYBp0aKi6zujzBXFpCnqw6RSLSp9JSedDyu/V1DF4= 3 | github.com/tetratelabs/wazero v1.6.0 h1:z0H1iikCdP8t+q341xqepY4EWvHEw8Es7tlqiVzlP3g= 4 | github.com/tetratelabs/wazero v1.6.0/go.mod h1:0U0G41+ochRKoPKCJlh0jMg1CHkyfK8kDqiirMmKY8A= 5 | -------------------------------------------------------------------------------- /host_funcs.go: -------------------------------------------------------------------------------- 1 | package wypes 2 | 3 | // HostFunc is a wrapped host-defined function. 4 | // 5 | // It is constructed with functions from [H0] to [H8] where the number is 6 | // how many arguments it accepts. If you need more, use [Pair]. 7 | // 8 | // There is always exactly one result. If you need to return nothing, use [Void]. 9 | // If you want to return 2 or more values, use [Pair], but make sure that the guest 10 | // and the runtime support multi-value returns. 11 | type HostFunc struct { 12 | Params []Value 13 | Results []Value 14 | Call func(*Store) 15 | } 16 | 17 | func (f *HostFunc) NumParams() int { 18 | return countStackValues(f.Params) 19 | } 20 | 21 | func (f *HostFunc) NumResults() int { 22 | return countStackValues(f.Results) 23 | } 24 | 25 | func (f *HostFunc) ParamValueTypes() []ValueType { 26 | return mergeValueTypes(f.Params) 27 | } 28 | 29 | func (f *HostFunc) ResultValueTypes() []ValueType { 30 | return mergeValueTypes(f.Results) 31 | } 32 | 33 | func countStackValues(values []Value) int { 34 | count := 0 35 | for _, v := range values { 36 | count += len(v.ValueTypes()) 37 | } 38 | return count 39 | } 40 | 41 | func mergeValueTypes(values []Value) []ValueType { 42 | res := make([]ValueType, 0, len(values)) 43 | for _, v := range values { 44 | res = append(res, v.ValueTypes()...) 45 | } 46 | return res 47 | } 48 | 49 | // H0 defines a [HostFunc] that accepts no arguments. 50 | func H0[Z Lower]( 51 | fn func() Z, 52 | ) HostFunc { 53 | var z Z 54 | return HostFunc{ 55 | Params: []Value{}, 56 | Results: []Value{z}, 57 | Call: func(s *Store) { 58 | fn().Lower(s) 59 | }, 60 | } 61 | } 62 | 63 | // H1 defines a [HostFunc] that accepts 1 high-level argument. 64 | func H1[A Lift[A], Z Lower]( 65 | fn func(A) Z, 66 | ) HostFunc { 67 | var a A 68 | var z Z 69 | return HostFunc{ 70 | Params: []Value{a}, 71 | Results: []Value{z}, 72 | Call: func(s *Store) { 73 | a := a.Lift(s) 74 | fn(a).Lower(s) 75 | }, 76 | } 77 | } 78 | 79 | // H2 defines a [HostFunc] that accepts 2 high-level arguments. 80 | func H2[A Lift[A], B Lift[B], Z Lower]( 81 | fn func(A, B) Z, 82 | ) HostFunc { 83 | var a A 84 | var b B 85 | var z Z 86 | return HostFunc{ 87 | Params: []Value{a, b}, 88 | Results: []Value{z}, 89 | Call: func(s *Store) { 90 | b := b.Lift(s) 91 | a := a.Lift(s) 92 | fn(a, b).Lower(s) 93 | }, 94 | } 95 | } 96 | 97 | // H3 defines a [HostFunc] that accepts 3 high-level arguments. 98 | func H3[A Lift[A], B Lift[B], C Lift[C], Z Lower]( 99 | fn func(A, B, C) Z, 100 | ) HostFunc { 101 | var a A 102 | var b B 103 | var c C 104 | var z Z 105 | return HostFunc{ 106 | Params: []Value{a, b, c}, 107 | Results: []Value{z}, 108 | Call: func(s *Store) { 109 | c := c.Lift(s) 110 | b := b.Lift(s) 111 | a := a.Lift(s) 112 | fn(a, b, c).Lower(s) 113 | }, 114 | } 115 | } 116 | 117 | // H4 defines a [HostFunc] that accepts 4 high-level arguments. 118 | func H4[A Lift[A], B Lift[B], C Lift[C], D Lift[D], Z Lower]( 119 | fn func(A, B, C, D) Z, 120 | ) HostFunc { 121 | var a A 122 | var b B 123 | var c C 124 | var d D 125 | var z Z 126 | return HostFunc{ 127 | Params: []Value{a, b, c, d}, 128 | Results: []Value{z}, 129 | Call: func(s *Store) { 130 | d := d.Lift(s) 131 | c := c.Lift(s) 132 | b := b.Lift(s) 133 | a := a.Lift(s) 134 | fn(a, b, c, d).Lower(s) 135 | }, 136 | } 137 | } 138 | 139 | // H5 defines a [HostFunc] that accepts 5 high-level arguments. 140 | func H5[A Lift[A], B Lift[B], C Lift[C], D Lift[D], E Lift[E], Z Lower]( 141 | fn func(A, B, C, D, E) Z, 142 | ) HostFunc { 143 | var a A 144 | var b B 145 | var c C 146 | var d D 147 | var e E 148 | var z Z 149 | return HostFunc{ 150 | Params: []Value{a, b, c, d, e}, 151 | Results: []Value{z}, 152 | Call: func(s *Store) { 153 | e := e.Lift(s) 154 | d := d.Lift(s) 155 | c := c.Lift(s) 156 | b := b.Lift(s) 157 | a := a.Lift(s) 158 | fn(a, b, c, d, e).Lower(s) 159 | }, 160 | } 161 | } 162 | 163 | // H6 defines a [HostFunc] that accepts 6 high-level arguments. 164 | func H6[A Lift[A], B Lift[B], C Lift[C], D Lift[D], E Lift[E], F Lift[F], Z Lower]( 165 | fn func(A, B, C, D, E, F) Z, 166 | ) HostFunc { 167 | var a A 168 | var b B 169 | var c C 170 | var d D 171 | var e E 172 | var f F 173 | var z Z 174 | return HostFunc{ 175 | Params: []Value{a, b, c, d, e, f}, 176 | Results: []Value{z}, 177 | Call: func(s *Store) { 178 | f := f.Lift(s) 179 | e := e.Lift(s) 180 | d := d.Lift(s) 181 | c := c.Lift(s) 182 | b := b.Lift(s) 183 | a := a.Lift(s) 184 | fn(a, b, c, d, e, f).Lower(s) 185 | }, 186 | } 187 | } 188 | 189 | // H7 defines a [HostFunc] that accepts 7 high-level arguments. 190 | func H7[A Lift[A], B Lift[B], C Lift[C], D Lift[D], E Lift[E], F Lift[F], G Lift[G], Z Lower]( 191 | fn func(A, B, C, D, E, F, G) Z, 192 | ) HostFunc { 193 | var a A 194 | var b B 195 | var c C 196 | var d D 197 | var e E 198 | var f F 199 | var g G 200 | var z Z 201 | return HostFunc{ 202 | Params: []Value{a, b, c, d, e, f, g}, 203 | Results: []Value{z}, 204 | Call: func(s *Store) { 205 | g := g.Lift(s) 206 | f := f.Lift(s) 207 | e := e.Lift(s) 208 | d := d.Lift(s) 209 | c := c.Lift(s) 210 | b := b.Lift(s) 211 | a := a.Lift(s) 212 | fn(a, b, c, d, e, f, g).Lower(s) 213 | }, 214 | } 215 | } 216 | 217 | // H8 defines a [HostFunc] that accepts 8 high-level arguments. 218 | func H8[A Lift[A], B Lift[B], C Lift[C], D Lift[D], E Lift[E], F Lift[F], G Lift[G], H Lift[H], Z Lower]( 219 | fn func(A, B, C, D, E, F, G, H) Z, 220 | ) HostFunc { 221 | var a A 222 | var b B 223 | var c C 224 | var d D 225 | var e E 226 | var f F 227 | var g G 228 | var h H 229 | var z Z 230 | return HostFunc{ 231 | Params: []Value{a, b, c, d, e, f, g, h}, 232 | Results: []Value{z}, 233 | Call: func(s *Store) { 234 | h := h.Lift(s) 235 | g := g.Lift(s) 236 | f := f.Lift(s) 237 | e := e.Lift(s) 238 | d := d.Lift(s) 239 | c := c.Lift(s) 240 | b := b.Lift(s) 241 | a := a.Lift(s) 242 | fn(a, b, c, d, e, f, g, h).Lower(s) 243 | }, 244 | } 245 | } 246 | 247 | // H9 defines a [HostFunc] that accepts 9 high-level arguments. 248 | func H9[A Lift[A], B Lift[B], C Lift[C], D Lift[D], E Lift[E], F Lift[F], G Lift[G], H Lift[H], I Lift[I], Z Lower]( 249 | fn func(A, B, C, D, E, F, G, H, I) Z, 250 | ) HostFunc { 251 | var a A 252 | var b B 253 | var c C 254 | var d D 255 | var e E 256 | var f F 257 | var g G 258 | var h H 259 | var i I 260 | var z Z 261 | return HostFunc{ 262 | Params: []Value{a, b, c, d, e, f, g, h, i}, 263 | Results: []Value{z}, 264 | Call: func(s *Store) { 265 | i := i.Lift(s) 266 | h := h.Lift(s) 267 | g := g.Lift(s) 268 | f := f.Lift(s) 269 | e := e.Lift(s) 270 | d := d.Lift(s) 271 | c := c.Lift(s) 272 | b := b.Lift(s) 273 | a := a.Lift(s) 274 | fn(a, b, c, d, e, f, g, h, i).Lower(s) 275 | }, 276 | } 277 | } 278 | 279 | // H10 defines a [HostFunc] that accepts 10 high-level arguments. 280 | func H10[A Lift[A], B Lift[B], C Lift[C], D Lift[D], E Lift[E], F Lift[F], G Lift[G], H Lift[H], I Lift[I], J Lift[J], Z Lower]( 281 | fn func(A, B, C, D, E, F, G, H, I, J) Z, 282 | ) HostFunc { 283 | var a A 284 | var b B 285 | var c C 286 | var d D 287 | var e E 288 | var f F 289 | var g G 290 | var h H 291 | var i I 292 | var j J 293 | var z Z 294 | return HostFunc{ 295 | Params: []Value{a, b, c, d, e, f, g, h, i, j}, 296 | Results: []Value{z}, 297 | Call: func(s *Store) { 298 | j := j.Lift(s) 299 | i := i.Lift(s) 300 | h := h.Lift(s) 301 | g := g.Lift(s) 302 | f := f.Lift(s) 303 | e := e.Lift(s) 304 | d := d.Lift(s) 305 | c := c.Lift(s) 306 | b := b.Lift(s) 307 | a := a.Lift(s) 308 | fn(a, b, c, d, e, f, g, h, i, j).Lower(s) 309 | }, 310 | } 311 | } 312 | 313 | // H11 defines a [HostFunc] that accepts 11 high-level arguments. 314 | func H11[A Lift[A], B Lift[B], C Lift[C], D Lift[D], E Lift[E], F Lift[F], G Lift[G], H Lift[H], I Lift[I], J Lift[J], K Lift[K], Z Lower]( 315 | fn func(A, B, C, D, E, F, G, H, I, J, K) Z, 316 | ) HostFunc { 317 | var a A 318 | var b B 319 | var c C 320 | var d D 321 | var e E 322 | var f F 323 | var g G 324 | var h H 325 | var i I 326 | var j J 327 | var k K 328 | var z Z 329 | return HostFunc{ 330 | Params: []Value{a, b, c, d, e, f, g, h, i, j, k}, 331 | Results: []Value{z}, 332 | Call: func(s *Store) { 333 | k := k.Lift(s) 334 | j := j.Lift(s) 335 | i := i.Lift(s) 336 | h := h.Lift(s) 337 | g := g.Lift(s) 338 | f := f.Lift(s) 339 | e := e.Lift(s) 340 | d := d.Lift(s) 341 | c := c.Lift(s) 342 | b := b.Lift(s) 343 | a := a.Lift(s) 344 | fn(a, b, c, d, e, f, g, h, i, j, k).Lower(s) 345 | }, 346 | } 347 | } 348 | 349 | // H12 defines a [HostFunc] that accepts 12 high-level arguments. 350 | func H12[A Lift[A], B Lift[B], C Lift[C], D Lift[D], E Lift[E], F Lift[F], G Lift[G], H Lift[H], I Lift[I], J Lift[J], K Lift[K], L Lift[L], Z Lower]( 351 | fn func(A, B, C, D, E, F, G, H, I, J, K, L) Z, 352 | ) HostFunc { 353 | var a A 354 | var b B 355 | var c C 356 | var d D 357 | var e E 358 | var f F 359 | var g G 360 | var h H 361 | var i I 362 | var j J 363 | var k K 364 | var l L 365 | var z Z 366 | return HostFunc{ 367 | Params: []Value{a, b, c, d, e, f, g, h, i, j, k, l}, 368 | Results: []Value{z}, 369 | Call: func(s *Store) { 370 | l := l.Lift(s) 371 | k := k.Lift(s) 372 | j := j.Lift(s) 373 | i := i.Lift(s) 374 | h := h.Lift(s) 375 | g := g.Lift(s) 376 | f := f.Lift(s) 377 | e := e.Lift(s) 378 | d := d.Lift(s) 379 | c := c.Lift(s) 380 | b := b.Lift(s) 381 | a := a.Lift(s) 382 | fn(a, b, c, d, e, f, g, h, i, j, k, l).Lower(s) 383 | }, 384 | } 385 | } 386 | 387 | // H13 defines a [HostFunc] that accepts 13 high-level arguments. 388 | func H13[A Lift[A], B Lift[B], C Lift[C], D Lift[D], E Lift[E], F Lift[F], G Lift[G], H Lift[H], I Lift[I], J Lift[J], K Lift[K], L Lift[L], M Lift[M], Z Lower]( 389 | fn func(A, B, C, D, E, F, G, H, I, J, K, L, M) Z, 390 | ) HostFunc { 391 | var a A 392 | var b B 393 | var c C 394 | var d D 395 | var e E 396 | var f F 397 | var g G 398 | var h H 399 | var i I 400 | var j J 401 | var k K 402 | var l L 403 | var m M 404 | var z Z 405 | return HostFunc{ 406 | Params: []Value{a, b, c, d, e, f, g, h, i, j, k, l, m}, 407 | Results: []Value{z}, 408 | Call: func(s *Store) { 409 | m := m.Lift(s) 410 | l := l.Lift(s) 411 | k := k.Lift(s) 412 | j := j.Lift(s) 413 | i := i.Lift(s) 414 | h := h.Lift(s) 415 | g := g.Lift(s) 416 | f := f.Lift(s) 417 | e := e.Lift(s) 418 | d := d.Lift(s) 419 | c := c.Lift(s) 420 | b := b.Lift(s) 421 | a := a.Lift(s) 422 | fn(a, b, c, d, e, f, g, h, i, j, k, l, m).Lower(s) 423 | }, 424 | } 425 | } 426 | 427 | // H14 defines a [HostFunc] that accepts 14 high-level arguments. 428 | func H14[A Lift[A], B Lift[B], C Lift[C], D Lift[D], E Lift[E], F Lift[F], G Lift[G], H Lift[H], I Lift[I], J Lift[J], K Lift[K], L Lift[L], M Lift[M], N Lift[N], Z Lower]( 429 | fn func(A, B, C, D, E, F, G, H, I, J, K, L, M, N) Z, 430 | ) HostFunc { 431 | var a A 432 | var b B 433 | var c C 434 | var d D 435 | var e E 436 | var f F 437 | var g G 438 | var h H 439 | var i I 440 | var j J 441 | var k K 442 | var l L 443 | var m M 444 | var n N 445 | var z Z 446 | return HostFunc{ 447 | Params: []Value{a, b, c, d, e, f, g, h, i, j, k, l, m, n}, 448 | Results: []Value{z}, 449 | Call: func(s *Store) { 450 | n := n.Lift(s) 451 | m := m.Lift(s) 452 | l := l.Lift(s) 453 | k := k.Lift(s) 454 | j := j.Lift(s) 455 | i := i.Lift(s) 456 | h := h.Lift(s) 457 | g := g.Lift(s) 458 | f := f.Lift(s) 459 | e := e.Lift(s) 460 | d := d.Lift(s) 461 | c := c.Lift(s) 462 | b := b.Lift(s) 463 | a := a.Lift(s) 464 | fn(a, b, c, d, e, f, g, h, i, j, k, l, m, n).Lower(s) 465 | }, 466 | } 467 | } 468 | 469 | // H15 defines a [HostFunc] that accepts 15 high-level arguments. 470 | func H15[A Lift[A], B Lift[B], C Lift[C], D Lift[D], E Lift[E], F Lift[F], G Lift[G], H Lift[H], I Lift[I], J Lift[J], K Lift[K], L Lift[L], M Lift[M], N Lift[N], O Lift[O], Z Lower]( 471 | fn func(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O) Z, 472 | ) HostFunc { 473 | var a A 474 | var b B 475 | var c C 476 | var d D 477 | var e E 478 | var f F 479 | var g G 480 | var h H 481 | var i I 482 | var j J 483 | var k K 484 | var l L 485 | var m M 486 | var n N 487 | var o O 488 | var z Z 489 | return HostFunc{ 490 | Params: []Value{a, b, c, d, e, f, g, h, i, j, k, l, m, n, o}, 491 | Results: []Value{z}, 492 | Call: func(s *Store) { 493 | o := o.Lift(s) 494 | n := n.Lift(s) 495 | m := m.Lift(s) 496 | l := l.Lift(s) 497 | k := k.Lift(s) 498 | j := j.Lift(s) 499 | i := i.Lift(s) 500 | h := h.Lift(s) 501 | g := g.Lift(s) 502 | f := f.Lift(s) 503 | e := e.Lift(s) 504 | d := d.Lift(s) 505 | c := c.Lift(s) 506 | b := b.Lift(s) 507 | a := a.Lift(s) 508 | fn(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o).Lower(s) 509 | }, 510 | } 511 | } 512 | 513 | // H16 defines a [HostFunc] that accepts 16 high-level arguments. 514 | func H16[A Lift[A], B Lift[B], C Lift[C], D Lift[D], E Lift[E], F Lift[F], G Lift[G], H Lift[H], I Lift[I], J Lift[J], K Lift[K], L Lift[L], M Lift[M], N Lift[N], O Lift[O], P Lift[P], Z Lower]( 515 | fn func(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P) Z, 516 | ) HostFunc { 517 | var a A 518 | var b B 519 | var c C 520 | var d D 521 | var e E 522 | var f F 523 | var g G 524 | var h H 525 | var i I 526 | var j J 527 | var k K 528 | var l L 529 | var m M 530 | var n N 531 | var o O 532 | var p P 533 | var z Z 534 | return HostFunc{ 535 | Params: []Value{a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p}, 536 | Results: []Value{z}, 537 | Call: func(s *Store) { 538 | p := p.Lift(s) 539 | o := o.Lift(s) 540 | n := n.Lift(s) 541 | m := m.Lift(s) 542 | l := l.Lift(s) 543 | k := k.Lift(s) 544 | j := j.Lift(s) 545 | i := i.Lift(s) 546 | h := h.Lift(s) 547 | g := g.Lift(s) 548 | f := f.Lift(s) 549 | e := e.Lift(s) 550 | d := d.Lift(s) 551 | c := c.Lift(s) 552 | b := b.Lift(s) 553 | a := a.Lift(s) 554 | fn(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p).Lower(s) 555 | }, 556 | } 557 | } 558 | 559 | // H17 defines a [HostFunc] that accepts 17 high-level arguments. 560 | func H17[A Lift[A], B Lift[B], C Lift[C], D Lift[D], E Lift[E], F Lift[F], G Lift[G], H Lift[H], I Lift[I], J Lift[J], K Lift[K], L Lift[L], M Lift[M], N Lift[N], O Lift[O], P Lift[P], Q Lift[Q], Z Lower]( 561 | fn func(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q) Z, 562 | ) HostFunc { 563 | var a A 564 | var b B 565 | var c C 566 | var d D 567 | var e E 568 | var f F 569 | var g G 570 | var h H 571 | var i I 572 | var j J 573 | var k K 574 | var l L 575 | var m M 576 | var n N 577 | var o O 578 | var p P 579 | var q Q 580 | var z Z 581 | return HostFunc{ 582 | Params: []Value{a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q}, 583 | Results: []Value{z}, 584 | Call: func(s *Store) { 585 | q := q.Lift(s) 586 | p := p.Lift(s) 587 | o := o.Lift(s) 588 | n := n.Lift(s) 589 | m := m.Lift(s) 590 | l := l.Lift(s) 591 | k := k.Lift(s) 592 | j := j.Lift(s) 593 | i := i.Lift(s) 594 | h := h.Lift(s) 595 | g := g.Lift(s) 596 | f := f.Lift(s) 597 | e := e.Lift(s) 598 | d := d.Lift(s) 599 | c := c.Lift(s) 600 | b := b.Lift(s) 601 | a := a.Lift(s) 602 | fn(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q).Lower(s) 603 | }, 604 | } 605 | } 606 | 607 | // H18 defines a [HostFunc] that accepts 18 high-level arguments. 608 | func H18[A Lift[A], B Lift[B], C Lift[C], D Lift[D], E Lift[E], F Lift[F], G Lift[G], H Lift[H], I Lift[I], J Lift[J], K Lift[K], L Lift[L], M Lift[M], N Lift[N], O Lift[O], P Lift[P], Q Lift[Q], R Lift[R], Z Lower]( 609 | fn func(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R) Z, 610 | ) HostFunc { 611 | var a A 612 | var b B 613 | var c C 614 | var d D 615 | var e E 616 | var f F 617 | var g G 618 | var h H 619 | var i I 620 | var j J 621 | var k K 622 | var l L 623 | var m M 624 | var n N 625 | var o O 626 | var p P 627 | var q Q 628 | var r R 629 | var z Z 630 | return HostFunc{ 631 | Params: []Value{a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r}, 632 | Results: []Value{z}, 633 | Call: func(s *Store) { 634 | r := r.Lift(s) 635 | q := q.Lift(s) 636 | p := p.Lift(s) 637 | o := o.Lift(s) 638 | n := n.Lift(s) 639 | m := m.Lift(s) 640 | l := l.Lift(s) 641 | k := k.Lift(s) 642 | j := j.Lift(s) 643 | i := i.Lift(s) 644 | h := h.Lift(s) 645 | g := g.Lift(s) 646 | f := f.Lift(s) 647 | e := e.Lift(s) 648 | d := d.Lift(s) 649 | c := c.Lift(s) 650 | b := b.Lift(s) 651 | a := a.Lift(s) 652 | fn(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r).Lower(s) 653 | }, 654 | } 655 | } 656 | -------------------------------------------------------------------------------- /host_funcs_test.go: -------------------------------------------------------------------------------- 1 | package wypes_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/orsinium-labs/tinytest/is" 7 | "github.com/orsinium-labs/wypes" 8 | ) 9 | 10 | func TestH0(t *testing.T) { 11 | c := is.NewRelaxed(t) 12 | stack := wypes.NewSliceStack(4) 13 | store := wypes.Store{Stack: stack} 14 | f := wypes.H0(func() wypes.Int { return 13 }) 15 | f.Call(&store) 16 | is.Equal(c, stack.Pop(), 13) 17 | } 18 | 19 | func TestH1(t *testing.T) { 20 | c := is.NewRelaxed(t) 21 | stack := wypes.NewSliceStack(4) 22 | store := wypes.Store{Stack: stack} 23 | stack.Push(12) 24 | f := wypes.H1(func(x wypes.Int) wypes.Int { 25 | return x * 2 26 | }) 27 | f.Call(&store) 28 | is.Equal(c, stack.Pop(), 24) 29 | } 30 | 31 | func TestH2(t *testing.T) { 32 | c := is.NewRelaxed(t) 33 | stack := wypes.NewSliceStack(4) 34 | store := wypes.Store{Stack: stack} 35 | stack.Push(18) 36 | stack.Push(12) 37 | f := wypes.H2(func(a, b wypes.Int) wypes.Int { 38 | return a - b 39 | }) 40 | f.Call(&store) 41 | is.Equal(c, stack.Pop(), 6) 42 | } 43 | -------------------------------------------------------------------------------- /store.go: -------------------------------------------------------------------------------- 1 | package wypes 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | type Raw = uint64 8 | type Addr = uint32 9 | type ValueType = byte 10 | 11 | const ( 12 | // ValueTypeI32 is a 32-bit integer. 13 | ValueTypeI32 ValueType = 0x7f 14 | 15 | // ValueTypeI64 is a 64-bit integer. 16 | ValueTypeI64 ValueType = 0x7e 17 | 18 | // ValueTypeF32 is a 32-bit floating point number. 19 | ValueTypeF32 ValueType = 0x7d 20 | 21 | // ValueTypeF64 is a 64-bit floating point number. 22 | ValueTypeF64 ValueType = 0x7c 23 | 24 | // ValueTypeExternref is an externref type. 25 | // 26 | // Not supported by many guests including TinyGo, so we don't use it. 27 | // https://github.com/tinygo-org/tinygo/issues/2702 28 | ValueTypeExternref ValueType = 0x6f 29 | ) 30 | 31 | // Store provides access for host-defined functions to the runtime data. 32 | // 33 | // Store itself implements [Lift] and so can be used as a host-defined function argument. 34 | type Store struct { 35 | // Stack is where [Lift] takes the values from and [Lower] puts values to. 36 | Stack Stack 37 | 38 | // Memory is used by [Lift] and [Lower] of memory-based types, 39 | // like [Bytes] and [String]. 40 | Memory Memory 41 | 42 | // Refs is used by [HostRef] to pass through the gues module references 43 | // to complex objects in the host environment that cannot be lowered into wasm. 44 | Refs Refs 45 | 46 | // Context can be retrieved by the [Context] type. 47 | Context context.Context 48 | 49 | // Error holds the latest error that happened during [Lift] or [Lower]. 50 | Error error 51 | } 52 | 53 | // ValueTypes implements [Value] interface. 54 | func (*Store) ValueTypes() []ValueType { 55 | return []ValueType{} 56 | } 57 | 58 | // Lift implements [Lift] interface. 59 | func (*Store) Lift(s *Store) *Store { 60 | return s 61 | } 62 | 63 | // Memory provides access to the linear memory of the wasm runtime. 64 | // 65 | // The interface is compatible with wazero memory. 66 | type Memory interface { 67 | // Read is used to [Lift] values of memory-backed types, like [Bytes] and [String]. 68 | Read(offset Addr, count uint32) ([]byte, bool) 69 | 70 | // Read is used to [Lower] values of memory-backed types, like [Bytes] and [String]. 71 | Write(offset Addr, v []byte) bool 72 | } 73 | 74 | // Wraps a slice of bytes to be used as [Memory]. 75 | type SliceMemory []byte 76 | 77 | // Create new memory instance that internally stores data in a slice. 78 | func NewSliceMemory(size int) *SliceMemory { 79 | s := make(SliceMemory, size) 80 | return &s 81 | } 82 | 83 | // Read implements the [Memory] interface. 84 | func (m *SliceMemory) Read(offset Addr, count uint32) ([]byte, bool) { 85 | if !m.hasSize(offset, uint64(count)) { 86 | return nil, false 87 | } 88 | return (*m)[offset : offset+count : offset+count], true 89 | } 90 | 91 | // Write implements the [Memory] interface. 92 | func (m *SliceMemory) Write(offset Addr, v []byte) bool { 93 | if !m.hasSize(offset, uint64(len(v))) { 94 | return false 95 | } 96 | copy((*m)[offset:], v) 97 | return true 98 | } 99 | 100 | // hasSize returns true if Len is sufficient for byteCount at the given offset. 101 | func (m *SliceMemory) hasSize(offset uint32, byteCount uint64) bool { 102 | return uint64(offset)+byteCount <= uint64(len(*m)) // uint64 prevents overflow on add 103 | } 104 | 105 | func (s *SliceMemory) Len() int { 106 | return len(*s) 107 | } 108 | 109 | // Refs holds references to Go values that you want to reference from wasm using [HostRef]. 110 | type Refs interface { 111 | Get(idx uint32, def any) (any, bool) 112 | Set(idx uint32, val any) 113 | Put(val any) uint32 114 | Drop(idx uint32) 115 | } 116 | 117 | // MapRefs is a simple [Refs] implementation powered by a map. 118 | // 119 | // Must be constructed with [NewMapRefs]. 120 | type MapRefs struct { 121 | Raw map[uint32]any 122 | idx uint32 123 | } 124 | 125 | func NewMapRefs() MapRefs { 126 | return MapRefs{Raw: make(map[uint32]any)} 127 | } 128 | 129 | func (r MapRefs) Get(idx uint32, def any) (any, bool) { 130 | val, found := r.Raw[idx] 131 | if !found { 132 | return def, false 133 | } 134 | return val, true 135 | } 136 | 137 | func (r MapRefs) Set(idx uint32, val any) { 138 | r.Raw[idx] = val 139 | } 140 | 141 | func (r MapRefs) Put(val any) uint32 { 142 | r.idx += 1 143 | 144 | // skip already used cells 145 | _, used := r.Raw[r.idx] 146 | for used { 147 | r.idx += 1 148 | _, used = r.Raw[r.idx] 149 | } 150 | 151 | r.Raw[r.idx] = val 152 | return r.idx 153 | } 154 | 155 | func (r MapRefs) Drop(idx uint32) { 156 | delete(r.Raw, idx) 157 | } 158 | 159 | type Stack interface { 160 | Push(Raw) 161 | Pop() Raw 162 | } 163 | 164 | // SliceStack adapts a slice of raw values into a [Stack]. 165 | type SliceStack []uint64 166 | 167 | func NewSliceStack(cap int) *SliceStack { 168 | s := make(SliceStack, 0, cap) 169 | return &s 170 | } 171 | 172 | func (s *SliceStack) Push(v uint64) { 173 | *s = append(*s, v) 174 | } 175 | 176 | func (s *SliceStack) Pop() uint64 { 177 | idx := len(*s) - 1 178 | v := (*s)[idx] 179 | *s = (*s)[:idx] 180 | return v 181 | } 182 | 183 | func (s *SliceStack) Len() int { 184 | return len(*s) 185 | } 186 | 187 | // Value is an interface implemented by all the types in wypes. 188 | type Value interface { 189 | ValueTypes() []ValueType 190 | } 191 | 192 | // Lift reads values from [Store] into a native Go value. 193 | type Lift[T any] interface { 194 | Value 195 | Lift(*Store) T 196 | } 197 | 198 | // Lower writes a native Go value into the [Store]. 199 | type Lower interface { 200 | Value 201 | Lower(*Store) 202 | } 203 | 204 | // LiftLower is a type that implements both [Lift] and [Lower]. 205 | type LiftLower[T any] interface { 206 | Lift[T] 207 | Lower 208 | } 209 | 210 | // MemoryLift reads values from [Store.Memory] into a native Go value. 211 | type MemoryLift[T any] interface { 212 | MemoryLift(*Store, Addr) (T, uint32) 213 | } 214 | 215 | // MemoryLower writes a native Go value into the [Store.Memory]. 216 | type MemoryLower[T any] interface { 217 | MemoryLower(*Store, Addr) uint32 218 | } 219 | 220 | // MemoryLiftLower is a type that implements both [MemoryLift] and [MemoryLower]. 221 | type MemoryLiftLower[T any] interface { 222 | MemoryLift[T] 223 | MemoryLower[T] 224 | } 225 | 226 | // Modules is a collection of host-defined modules. 227 | // 228 | // It maps module names to the module definitions. 229 | type Modules map[string]Module 230 | 231 | // Module is a collection of host-defined functions in a module with the same name. 232 | // 233 | // It maps function names to function definitions. 234 | type Module map[string]HostFunc 235 | -------------------------------------------------------------------------------- /types_int.go: -------------------------------------------------------------------------------- 1 | package wypes 2 | 3 | import "encoding/binary" 4 | 5 | // Int8 wraps [int8], a signed 8-bit integer. 6 | type Int8 int8 7 | 8 | const int8Size = 1 9 | 10 | // Unwrap returns the wrapped value. 11 | func (v Int8) Unwrap() int8 { 12 | return int8(v) 13 | } 14 | 15 | // ValueTypes implements [Value] interface. 16 | func (Int8) ValueTypes() []ValueType { 17 | return []ValueType{ValueTypeI32} 18 | } 19 | 20 | // Lift implements [Lift] interface. 21 | func (Int8) Lift(s *Store) Int8 { 22 | return Int8(s.Stack.Pop()) 23 | } 24 | 25 | // Lower implements [Lower] interface. 26 | func (v Int8) Lower(s *Store) { 27 | s.Stack.Push(Raw(v)) 28 | } 29 | 30 | // MemoryLift implements [MemoryLift] interface. 31 | func (Int8) MemoryLift(s *Store, offset uint32) (Int8, uint32) { 32 | raw, ok := s.Memory.Read(offset, int8Size) 33 | if !ok { 34 | s.Error = ErrMemRead 35 | return Int8(0), 0 36 | } 37 | 38 | return Int8(raw[0]), int8Size 39 | } 40 | 41 | // MemoryLower implements [MemoryLower] interface. 42 | func (v Int8) MemoryLower(s *Store, offset uint32) (length uint32) { 43 | ok := s.Memory.Write(offset, []byte{byte(v)}) 44 | if !ok { 45 | s.Error = ErrMemWrite 46 | return 0 47 | } 48 | 49 | return int8Size 50 | } 51 | 52 | // Int16 wraps [int16], a signed 16-bit integer. 53 | type Int16 int16 54 | 55 | const int16Size = 2 56 | 57 | // Unwrap returns the wrapped value. 58 | func (v Int16) Unwrap() int16 { 59 | return int16(v) 60 | } 61 | 62 | // ValueTypes implements [Value] interface. 63 | func (Int16) ValueTypes() []ValueType { 64 | return []ValueType{ValueTypeI32} 65 | } 66 | 67 | // Lift implements [Lift] interface. 68 | func (Int16) Lift(s *Store) Int16 { 69 | return Int16(s.Stack.Pop()) 70 | } 71 | 72 | // Lower implements [Lower] interface. 73 | func (v Int16) Lower(s *Store) { 74 | s.Stack.Push(Raw(v)) 75 | } 76 | 77 | // MemoryLift implements [MemoryLifter] interface. 78 | func (Int16) MemoryLift(s *Store, offset uint32) (Int16, uint32) { 79 | raw, ok := s.Memory.Read(offset, int16Size) 80 | if !ok { 81 | s.Error = ErrMemRead 82 | return Int16(0), 0 83 | } 84 | 85 | return Int16(binary.LittleEndian.Uint16(raw)), int16Size 86 | } 87 | 88 | // MemoryLower implements [MemoryLower] interface. 89 | func (v Int16) MemoryLower(s *Store, offset uint32) (length uint32) { 90 | data := make([]byte, int16Size) 91 | binary.LittleEndian.PutUint16(data, uint16(v)) 92 | ok := s.Memory.Write(offset, data) 93 | if !ok { 94 | s.Error = ErrMemWrite 95 | return 0 96 | } 97 | 98 | return int16Size 99 | } 100 | 101 | // Int32 wraps [int32], a signed 32-bit integer. 102 | type Int32 int32 103 | 104 | const int32Size = 4 105 | 106 | // Unwrap returns the wrapped value. 107 | func (v Int32) Unwrap() int32 { 108 | return int32(v) 109 | } 110 | 111 | // ValueTypes implements [Value] interface. 112 | func (Int32) ValueTypes() []ValueType { 113 | return []ValueType{ValueTypeI32} 114 | } 115 | 116 | // Lift implements [Lift] interface. 117 | func (Int32) Lift(s *Store) Int32 { 118 | return Int32(s.Stack.Pop()) 119 | } 120 | 121 | // Lower implements [Lower] interface. 122 | func (v Int32) Lower(s *Store) { 123 | s.Stack.Push(Raw(v)) 124 | } 125 | 126 | // MemoryLift implements [MemoryLifter] interface. 127 | func (Int32) MemoryLift(s *Store, offset uint32) (Int32, uint32) { 128 | raw, ok := s.Memory.Read(offset, int32Size) 129 | if !ok { 130 | s.Error = ErrMemRead 131 | return Int32(0), 0 132 | } 133 | 134 | return Int32(binary.LittleEndian.Uint32(raw)), int32Size 135 | } 136 | 137 | // MemoryLower implements [MemoryLower] interface. 138 | func (v Int32) MemoryLower(s *Store, offset uint32) (length uint32) { 139 | data := make([]byte, int32Size) 140 | binary.LittleEndian.PutUint32(data, uint32(v)) 141 | ok := s.Memory.Write(offset, data) 142 | if !ok { 143 | s.Error = ErrMemWrite 144 | return 0 145 | } 146 | 147 | return int32Size 148 | } 149 | 150 | // Int64 wraps [int64], a signed 64-bit integer. 151 | type Int64 int64 152 | 153 | const int64Size = 8 154 | 155 | // Unwrap returns the wrapped value. 156 | func (v Int64) Unwrap() int64 { 157 | return int64(v) 158 | } 159 | 160 | // ValueTypes implements [Value] interface. 161 | func (Int64) ValueTypes() []ValueType { 162 | return []ValueType{ValueTypeI64} 163 | } 164 | 165 | // Lift implements [Lift] interface. 166 | func (Int64) Lift(s *Store) Int64 { 167 | return Int64(s.Stack.Pop()) 168 | } 169 | 170 | // Lower implements [Lower] interface. 171 | func (v Int64) Lower(s *Store) { 172 | s.Stack.Push(Raw(v)) 173 | } 174 | 175 | // MemoryLift implements [MemoryLifter] interface. 176 | func (Int64) MemoryLift(s *Store, offset uint32) (Int64, uint32) { 177 | raw, ok := s.Memory.Read(offset, int64Size) 178 | if !ok { 179 | s.Error = ErrMemRead 180 | return Int64(0), 0 181 | } 182 | 183 | return Int64(binary.LittleEndian.Uint64(raw)), int64Size 184 | } 185 | 186 | // MemoryLower implements [MemoryLower] interface. 187 | func (v Int64) MemoryLower(s *Store, offset uint32) (length uint32) { 188 | data := make([]byte, int64Size) 189 | binary.LittleEndian.PutUint64(data, uint64(v)) 190 | ok := s.Memory.Write(offset, data) 191 | if !ok { 192 | s.Error = ErrMemWrite 193 | return 0 194 | } 195 | 196 | return int64Size 197 | } 198 | 199 | // Int wraps [int], a signed 32-bit integer. 200 | type Int int 201 | 202 | // Unwrap returns the wrapped value. 203 | func (v Int) Unwrap() int { 204 | return int(v) 205 | } 206 | 207 | // ValueTypes implements [Value] interface. 208 | func (Int) ValueTypes() []ValueType { 209 | return []ValueType{ValueTypeI64} 210 | } 211 | 212 | // Lift implements [Lift] interface. 213 | func (Int) Lift(s *Store) Int { 214 | return Int(s.Stack.Pop()) 215 | } 216 | 217 | // Lower implements [Lower] interface. 218 | func (v Int) Lower(s *Store) { 219 | s.Stack.Push(Raw(v)) 220 | } 221 | 222 | // MemoryLift implements [MemoryLifter] interface. 223 | func (Int) MemoryLift(s *Store, offset uint32) (Int, uint32) { 224 | raw, ok := s.Memory.Read(offset, int64Size) 225 | if !ok { 226 | s.Error = ErrMemRead 227 | return Int(0), 0 228 | } 229 | 230 | return Int(binary.LittleEndian.Uint64(raw)), int64Size 231 | } 232 | 233 | // MemoryLower implements [MemoryLower] interface. 234 | func (v Int) MemoryLower(s *Store, offset uint32) (length uint32) { 235 | data := make([]byte, int64Size) 236 | binary.LittleEndian.PutUint64(data, uint64(v)) 237 | ok := s.Memory.Write(offset, data) 238 | if !ok { 239 | s.Error = ErrMemWrite 240 | return 0 241 | } 242 | 243 | return int64Size 244 | } 245 | -------------------------------------------------------------------------------- /types_mem.go: -------------------------------------------------------------------------------- 1 | package wypes 2 | 3 | import ( 4 | "encoding/binary" 5 | ) 6 | 7 | // Bytes wraps a slice of bytes. 8 | // 9 | // The bytes are passed through the linear memory. 10 | // Since the memory is controlled and allocated by the guest module, 11 | // you have to provide the Offset to be able to [Lower] the value into the memory. 12 | // The offset should be obtained from the guest module, either as an explicit 13 | // function argument or by calling its allocator. 14 | type Bytes struct { 15 | Offset uint32 16 | Raw []byte 17 | } 18 | 19 | // Unwrap returns the wrapped value. 20 | func (v Bytes) Unwrap() []byte { 21 | return v.Raw 22 | } 23 | 24 | // ValueTypes implements [Value] interface. 25 | func (v Bytes) ValueTypes() []ValueType { 26 | return []ValueType{ValueTypeI32, ValueTypeI32} 27 | } 28 | 29 | // Lift implements [Lift] interface. 30 | func (Bytes) Lift(s *Store) Bytes { 31 | size := uint32(s.Stack.Pop()) 32 | offset := uint32(s.Stack.Pop()) 33 | raw, ok := s.Memory.Read(offset, size) 34 | if !ok { 35 | s.Error = ErrMemRead 36 | } 37 | return Bytes{Offset: offset, Raw: raw} 38 | } 39 | 40 | // Lower implements [Lower] interface. 41 | func (v Bytes) Lower(s *Store) { 42 | ok := s.Memory.Write(v.Offset, v.Raw) 43 | if !ok { 44 | s.Error = ErrMemWrite 45 | } 46 | size := len(v.Raw) 47 | s.Stack.Push(Raw(v.Offset)) 48 | s.Stack.Push(Raw(size)) 49 | } 50 | 51 | // MemoryLift implements [MemoryLift] interface. 52 | func (Bytes) MemoryLift(s *Store, offset uint32) (Bytes, uint32) { 53 | sp, ok := s.Memory.Read(offset, 8) 54 | if !ok { 55 | s.Error = ErrMemRead 56 | return Bytes{}, 0 57 | } 58 | ptr := binary.LittleEndian.Uint32(sp[0:]) 59 | sz := binary.LittleEndian.Uint32(sp[4:]) 60 | 61 | raw, ok := s.Memory.Read(ptr, sz) 62 | if !ok { 63 | s.Error = ErrMemRead 64 | return Bytes{}, 0 65 | } 66 | return Bytes{Offset: offset, Raw: raw}, sz 67 | } 68 | 69 | // MemoryLower implements [MemoryLower] interface. 70 | func (v Bytes) MemoryLower(s *Store, offset uint32) (length uint32) { 71 | ptrdata := make([]byte, 8) 72 | binary.LittleEndian.PutUint32(ptrdata[0:], offset+8) 73 | binary.LittleEndian.PutUint32(ptrdata[4:], uint32(len(v.Raw))) 74 | 75 | ok := s.Memory.Write(offset, ptrdata) 76 | if !ok { 77 | s.Error = ErrMemWrite 78 | } 79 | ok = s.Memory.Write(offset+8, v.Raw) 80 | if !ok { 81 | s.Error = ErrMemWrite 82 | } 83 | return uint32(len(v.Raw)) 84 | } 85 | 86 | // String wraps [string]. 87 | // 88 | // The string is passed through the linear memory. 89 | // Since the memory is controlled and allocated by the guest module, 90 | // you have to provide the Offset to be able to [Lower] the value into the memory. 91 | // The offset should be obtained from the guest module, either as an explicit 92 | // function argument or by calling its allocator. 93 | type String struct { 94 | Offset uint32 95 | Raw string 96 | } 97 | 98 | // Unwrap returns the wrapped value. 99 | func (v String) Unwrap() string { 100 | return v.Raw 101 | } 102 | 103 | // ValueTypes implements [Value] interface. 104 | func (v String) ValueTypes() []ValueType { 105 | return []ValueType{ValueTypeI32, ValueTypeI32} 106 | } 107 | 108 | // Lift implements [Lift] interface. 109 | func (String) Lift(s *Store) String { 110 | size := uint32(s.Stack.Pop()) 111 | offset := uint32(s.Stack.Pop()) 112 | raw, ok := s.Memory.Read(offset, size) 113 | if !ok { 114 | s.Error = ErrMemRead 115 | } 116 | return String{Offset: offset, Raw: string(raw)} 117 | } 118 | 119 | // Lower implements [Lower] interface. 120 | func (v String) Lower(s *Store) { 121 | ok := s.Memory.Write(v.Offset, []byte(v.Raw)) 122 | if !ok { 123 | s.Error = ErrMemWrite 124 | } 125 | size := len(v.Raw) 126 | s.Stack.Push(Raw(v.Offset)) 127 | s.Stack.Push(Raw(size)) 128 | } 129 | 130 | // MemoryLift implements [MemoryLift] interface. 131 | func (String) MemoryLift(s *Store, offset uint32) (String, uint32) { 132 | sp, ok := s.Memory.Read(offset, 8) 133 | if !ok { 134 | s.Error = ErrMemRead 135 | return String{}, 0 136 | } 137 | ptr := binary.LittleEndian.Uint32(sp[0:]) 138 | sz := binary.LittleEndian.Uint32(sp[4:]) 139 | 140 | raw, ok := s.Memory.Read(ptr, sz) 141 | if !ok { 142 | s.Error = ErrMemRead 143 | return String{}, 0 144 | } 145 | return String{Offset: offset, Raw: string(raw)}, sz 146 | } 147 | 148 | // MemoryLower implements [MemoryLower] interface. 149 | func (v String) MemoryLower(s *Store, offset uint32) (length uint32) { 150 | ptrdata := make([]byte, 8) 151 | binary.LittleEndian.PutUint32(ptrdata[0:], offset+8) 152 | binary.LittleEndian.PutUint32(ptrdata[4:], uint32(len(v.Raw))) 153 | 154 | ok := s.Memory.Write(offset, ptrdata) 155 | if !ok { 156 | s.Error = ErrMemWrite 157 | } 158 | ok = s.Memory.Write(offset+8, []byte(v.Raw)) 159 | if !ok { 160 | s.Error = ErrMemWrite 161 | } 162 | return uint32(len(v.Raw)) 163 | } 164 | 165 | // ReturnedList wraps a Go slice of any type that supports the [MemoryLiftLower] interface so it can be returned as a List. 166 | // This is the implementation required for the host side of component model functions that return a *[cm.List] type. 167 | // See https://github.com/bytecodealliance/wasm-tools-go/blob/main/cm/list.go 168 | type ReturnedList[T MemoryLiftLower[T]] struct { 169 | Offset uint32 170 | DataPtr uint32 171 | Raw []T 172 | } 173 | 174 | // Unwrap returns the wrapped value. 175 | func (v ReturnedList[T]) Unwrap() []T { 176 | return v.Raw 177 | } 178 | 179 | // ValueTypes implements [Value] interface. 180 | func (v ReturnedList[T]) ValueTypes() []ValueType { 181 | return []ValueType{ValueTypeI32} 182 | } 183 | 184 | // Lift implements [Lift] interface. 185 | func (ReturnedList[T]) Lift(s *Store) ReturnedList[T] { 186 | offset := uint32(s.Stack.Pop()) 187 | buf, ok := s.Memory.Read(offset, 8) 188 | if !ok { 189 | s.Error = ErrMemRead 190 | return ReturnedList[T]{} 191 | } 192 | 193 | ptr := binary.LittleEndian.Uint32(buf[0:]) 194 | sz := binary.LittleEndian.Uint32(buf[4:]) 195 | 196 | // empty list, probably a return value to be filled in later. 197 | if ptr == 0 || sz == 0 { 198 | return ReturnedList[T]{Offset: offset} 199 | } 200 | 201 | data := make([]T, sz) 202 | p := ptr 203 | var length uint32 204 | for i := uint32(0); i < sz; i++ { 205 | data[i], length = T.MemoryLift(data[0], s, p) 206 | p += length 207 | } 208 | 209 | return ReturnedList[T]{Offset: offset, DataPtr: ptr, Raw: data} 210 | } 211 | 212 | // Lower implements [Lower] interface. 213 | // See https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md#flattening 214 | // To use this need to have pre-allocated linear memory into which to write the actual data. 215 | func (v ReturnedList[T]) Lower(s *Store) { 216 | if v.DataPtr == 0 { 217 | s.Error = ErrMemWrite 218 | return 219 | } 220 | 221 | size := len(v.Raw) 222 | 223 | ptr := v.DataPtr 224 | for i := uint32(0); i < uint32(size); i++ { 225 | length := v.Raw[i].MemoryLower(s, ptr) 226 | ptr += length 227 | } 228 | 229 | ptrdata := make([]byte, 8) 230 | binary.LittleEndian.PutUint32(ptrdata[0:], v.DataPtr) 231 | binary.LittleEndian.PutUint32(ptrdata[4:], uint32(len(v.Raw))) 232 | s.Memory.Write(v.Offset, ptrdata) 233 | } 234 | 235 | // List wraps a Go slice of any type that implements the [MemoryLiftLower] interface. 236 | // This is the implementation required for the host side of component model functions that pass [cm.List] parameters. 237 | type List[T MemoryLiftLower[T]] struct { 238 | Offset uint32 239 | Raw []T 240 | } 241 | 242 | // Unwrap returns the wrapped value. 243 | func (v List[T]) Unwrap() []T { 244 | return v.Raw 245 | } 246 | 247 | // ValueTypes implements [Value] interface. 248 | func (v List[T]) ValueTypes() []ValueType { 249 | return []ValueType{ValueTypeI32, ValueTypeI32} 250 | } 251 | 252 | // Lift implements [Lift] interface. 253 | func (List[T]) Lift(s *Store) List[T] { 254 | size := uint32(s.Stack.Pop()) 255 | offset := uint32(s.Stack.Pop()) 256 | // empty list 257 | if size == 0 { 258 | return List[T]{Offset: offset} 259 | } 260 | data := make([]T, size) 261 | ptr := offset 262 | var length uint32 263 | for i := uint32(0); i < size; i++ { 264 | data[i], length = T.MemoryLift(data[0], s, ptr) 265 | ptr += length 266 | } 267 | return List[T]{Offset: offset, Raw: data} 268 | } 269 | 270 | // Lower implements [Lower] interface. 271 | // See https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md#flattening 272 | // In theory we should re-allocate enough linear memory into which to write the actual data. 273 | func (v List[T]) Lower(s *Store) { 274 | size := len(v.Raw) 275 | ptr := v.Offset 276 | for i := uint32(0); i < uint32(size); i++ { 277 | length := v.Raw[i].MemoryLower(s, ptr) 278 | ptr += length 279 | } 280 | s.Stack.Push(Raw(v.Offset)) 281 | s.Stack.Push(Raw(size)) 282 | } 283 | 284 | // MemoryLift implements [MemoryLift] interface. 285 | func (List[T]) MemoryLift(s *Store, offset uint32) (List[T], uint32) { 286 | sp, ok := s.Memory.Read(offset, 8) 287 | if !ok { 288 | s.Error = ErrMemRead 289 | return List[T]{}, 0 290 | } 291 | ptr := binary.LittleEndian.Uint32(sp[0:]) 292 | sz := binary.LittleEndian.Uint32(sp[4:]) 293 | 294 | data := make([]T, sz) 295 | var v T 296 | var length uint32 297 | for i := uint32(0); i < uint32(sz); i++ { 298 | data[i], length = v.MemoryLift(s, ptr) 299 | ptr += length 300 | } 301 | 302 | return List[T]{Offset: offset, Raw: data}, sz 303 | } 304 | 305 | // MemoryLower implements [MemoryLower] interface. 306 | func (v List[T]) MemoryLower(s *Store, offset uint32) (length uint32) { 307 | sz := len(v.Raw) 308 | ptr := offset + 8 309 | for i := uint32(0); i < uint32(sz); i++ { 310 | length := v.Raw[i].MemoryLower(s, ptr) 311 | ptr += length 312 | } 313 | 314 | ptrdata := make([]byte, 8) 315 | binary.LittleEndian.PutUint32(ptrdata[0:], offset+8) 316 | binary.LittleEndian.PutUint32(ptrdata[4:], uint32(len(v.Raw))) 317 | 318 | ok := s.Memory.Write(offset, ptrdata) 319 | if !ok { 320 | s.Error = ErrMemWrite 321 | } 322 | 323 | return uint32(sz) 324 | } 325 | 326 | // ListStrings wraps a Go slice of strings. 327 | // This is the implementation required for the host side of component model functions that pass [cm.List] of strings 328 | // as parameters. 329 | // See https://github.com/bytecodealliance/wasm-tools-go/blob/main/cm/list.go 330 | type ListStrings struct { 331 | Offset uint32 332 | Raw []string 333 | } 334 | 335 | // Unwrap returns the wrapped value. 336 | func (v ListStrings) Unwrap() []string { 337 | return v.Raw 338 | } 339 | 340 | // ValueTypes implements [Value] interface. 341 | func (v ListStrings) ValueTypes() []ValueType { 342 | return []ValueType{ValueTypeI32, ValueTypeI32} 343 | } 344 | 345 | // Lift implements [Lift] interface. 346 | func (ListStrings) Lift(s *Store) ListStrings { 347 | size := uint32(s.Stack.Pop()) 348 | offset := uint32(s.Stack.Pop()) 349 | 350 | // empty list 351 | if size == 0 { 352 | return ListStrings{Offset: offset} 353 | } 354 | 355 | data := make([]string, size) 356 | 357 | for i := uint32(0); i < size; i++ { 358 | buf, ok := s.Memory.Read(offset+i*8, 8) 359 | if !ok { 360 | s.Error = ErrMemRead 361 | return ListStrings{Offset: offset, Raw: data} 362 | } 363 | 364 | ptr := binary.LittleEndian.Uint32(buf[0:]) 365 | sz := binary.LittleEndian.Uint32(buf[4:]) 366 | 367 | raw, ok := s.Memory.Read(ptr, sz) 368 | if !ok { 369 | s.Error = ErrMemRead 370 | return ListStrings{Offset: offset, Raw: data} 371 | } 372 | 373 | data[i] = string(raw) 374 | } 375 | 376 | return ListStrings{Offset: offset, Raw: data} 377 | } 378 | 379 | // Lower implements [Lower] interface. 380 | // See https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md#flattening 381 | // In theory we should re-allocate enough linear memory into which to write the actual data. 382 | func (v ListStrings) Lower(s *Store) { 383 | size := uint32(len(v.Raw)) 384 | plen := size * 8 385 | 386 | // write pointers 387 | for i := uint32(0); i < size; i++ { 388 | ptrdata := make([]byte, 8) 389 | binary.LittleEndian.PutUint32(ptrdata[0:], v.Offset+i*8+plen) 390 | binary.LittleEndian.PutUint32(ptrdata[4:], uint32(len(v.Raw[i]))) 391 | 392 | ok := s.Memory.Write(v.Offset+i*8, ptrdata) 393 | if !ok { 394 | s.Error = ErrMemRead 395 | return 396 | } 397 | } 398 | 399 | // write the actual strings 400 | for i, str := range v.Raw { 401 | ptr := v.Offset + plen + uint32(i)*8 402 | 403 | ok := s.Memory.Write(ptr, []byte(str)) 404 | if !ok { 405 | s.Error = ErrMemRead 406 | return 407 | } 408 | } 409 | 410 | s.Stack.Push(Raw(v.Offset)) 411 | s.Stack.Push(Raw(size)) 412 | } 413 | 414 | // Result is the implementation required for the host side of component model functions that return a *[cm.Result] type. 415 | // See https://github.com/bytecodealliance/wasm-tools-go/blob/main/cm/result.go 416 | type Result[Shape MemoryLiftLower[Shape], OK MemoryLiftLower[OK], Err MemoryLiftLower[Err]] struct { 417 | Offset uint32 418 | DataPtr uint32 419 | OK OK 420 | Error Err 421 | IsError bool 422 | } 423 | 424 | // Unwrap returns the wrapped value. 425 | func (v Result[Shape, OK, Err]) Unwrap() any { 426 | if v.IsError { 427 | return v.Error 428 | } 429 | 430 | return v.OK 431 | } 432 | 433 | // ValueTypes implements [Value] interface. 434 | func (v Result[Shape, OK, Err]) ValueTypes() []ValueType { 435 | return []ValueType{ValueTypeI32} 436 | } 437 | 438 | // Lift implements [Lift] interface. Lifting a result is not supported. 439 | func (Result[Shape, OK, Err]) Lift(s *Store) Result[Shape, OK, Err] { 440 | offset := uint32(s.Stack.Pop()) 441 | 442 | var B UInt32 443 | isError, sz := B.MemoryLift(s, offset) 444 | 445 | if isError > 0 { 446 | var E Err 447 | err, _ := E.MemoryLift(s, offset+sz) 448 | return Result[Shape, OK, Err]{ 449 | IsError: true, 450 | Error: err, 451 | Offset: offset, 452 | } 453 | } 454 | 455 | var T OK 456 | val, _ := T.MemoryLift(s, offset+sz) 457 | return Result[Shape, OK, Err]{ 458 | IsError: false, 459 | OK: val, 460 | Offset: offset, 461 | } 462 | } 463 | 464 | // Lower implements [Lower] interface. 465 | // See https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md#flattening 466 | // To use this need to have pre-allocated linear memory into which to write the actual data. 467 | func (v Result[Shape, OK, Err]) Lower(s *Store) { 468 | if v.DataPtr == 0 { 469 | s.Error = ErrMemWrite 470 | return 471 | } 472 | 473 | var isError UInt32 474 | if v.IsError { 475 | isError = 1 476 | } 477 | sz := isError.MemoryLower(s, v.Offset) 478 | 479 | switch v.IsError { 480 | case true: 481 | v.Error.MemoryLower(s, v.Offset+sz) 482 | case false: 483 | v.OK.MemoryLower(s, v.Offset+sz) 484 | } 485 | } 486 | 487 | // TODO: fixed-width array 488 | // TODO: CString 489 | -------------------------------------------------------------------------------- /types_mem_test.go: -------------------------------------------------------------------------------- 1 | package wypes_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/orsinium-labs/tinytest/is" 7 | "github.com/orsinium-labs/wypes" 8 | ) 9 | 10 | func TestBytes(t *testing.T) { 11 | c := is.NewRelaxed(t) 12 | stack := wypes.NewSliceStack(4) 13 | store := wypes.Store{Stack: stack, Memory: wypes.NewSliceMemory(1024)} 14 | 15 | data := []byte("Hello, World!") 16 | wypes.Bytes{Raw: data}.Lower(&store) 17 | 18 | result := wypes.Bytes{}.Lift(&store) 19 | is.SliceEqual(c, result.Unwrap(), data) 20 | } 21 | 22 | func TestReturnedListEmpty(t *testing.T) { 23 | c := is.NewRelaxed(t) 24 | stack := wypes.NewSliceStack(4) 25 | store := wypes.Store{Stack: stack, Memory: wypes.NewSliceMemory(1024)} 26 | 27 | wypes.ReturnedList[wypes.UInt32]{Offset: 64}.Lower(&store) 28 | 29 | store.Stack.Push(64) 30 | list := wypes.ReturnedList[wypes.UInt32]{}.Lift(&store) 31 | 32 | is.SliceEqual(c, list.Unwrap(), []wypes.UInt32{}) 33 | } 34 | 35 | func TestReturnedListUint32(t *testing.T) { 36 | c := is.NewRelaxed(t) 37 | stack := wypes.NewSliceStack(4) 38 | store := wypes.Store{Stack: stack, Memory: wypes.NewSliceMemory(1024)} 39 | 40 | data := []wypes.UInt32{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} 41 | wypes.ReturnedList[wypes.UInt32]{Offset: 64, Raw: data, DataPtr: 128}.Lower(&store) 42 | 43 | store.Stack.Push(64) 44 | list := wypes.ReturnedList[wypes.UInt32]{}.Lift(&store) 45 | 46 | is.SliceEqual(c, list.Unwrap(), data) 47 | } 48 | 49 | func TestReturnedListUint16(t *testing.T) { 50 | c := is.NewRelaxed(t) 51 | stack := wypes.NewSliceStack(4) 52 | store := wypes.Store{Stack: stack, Memory: wypes.NewSliceMemory(1024)} 53 | 54 | data := []wypes.UInt16{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} 55 | wypes.ReturnedList[wypes.UInt16]{Offset: 96, Raw: data, DataPtr: 128}.Lower(&store) 56 | 57 | store.Stack.Push(96) 58 | list := wypes.ReturnedList[wypes.UInt16]{}.Lift(&store) 59 | 60 | is.SliceEqual(c, list.Unwrap(), data) 61 | } 62 | 63 | func TestReturnedListInt16(t *testing.T) { 64 | c := is.NewRelaxed(t) 65 | stack := wypes.NewSliceStack(4) 66 | store := wypes.Store{Stack: stack, Memory: wypes.NewSliceMemory(1024)} 67 | 68 | data := []wypes.Int16{1, -2, 3, 4, -5, 6, -7, 8, 9, 10} 69 | wypes.ReturnedList[wypes.Int16]{Offset: 64, Raw: data, DataPtr: 128}.Lower(&store) 70 | 71 | store.Stack.Push(64) 72 | list := wypes.ReturnedList[wypes.Int16]{}.Lift(&store) 73 | 74 | is.SliceEqual(c, list.Unwrap(), data) 75 | } 76 | 77 | func TestReturnedListInt32(t *testing.T) { 78 | c := is.NewRelaxed(t) 79 | stack := wypes.NewSliceStack(4) 80 | store := wypes.Store{Stack: stack, Memory: wypes.NewSliceMemory(1024)} 81 | 82 | data := []wypes.Int32{1, -2, 3, 4, -5, 6, -7, 8, 9, 10} 83 | wypes.ReturnedList[wypes.Int32]{Offset: 64, Raw: data, DataPtr: 128}.Lower(&store) 84 | 85 | store.Stack.Push(64) 86 | list := wypes.ReturnedList[wypes.Int32]{}.Lift(&store) 87 | 88 | is.SliceEqual(c, list.Unwrap(), data) 89 | } 90 | 91 | func TestReturnedListFloat32(t *testing.T) { 92 | c := is.NewRelaxed(t) 93 | stack := wypes.NewSliceStack(4) 94 | store := wypes.Store{Stack: stack, Memory: wypes.NewSliceMemory(1024)} 95 | 96 | data := []wypes.Float32{1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9, 10.1} 97 | wypes.ReturnedList[wypes.Float32]{Raw: data, DataPtr: 128}.Lower(&store) 98 | 99 | store.Stack.Push(0) 100 | list := wypes.ReturnedList[wypes.Float32]{}.Lift(&store) 101 | 102 | is.SliceEqual(c, list.Unwrap(), data) 103 | } 104 | 105 | func TestListEmpty(t *testing.T) { 106 | c := is.NewRelaxed(t) 107 | stack := wypes.NewSliceStack(4) 108 | store := wypes.Store{Stack: stack, Memory: wypes.NewSliceMemory(1024)} 109 | 110 | wypes.List[wypes.UInt32]{Offset: 64}.Lower(&store) 111 | 112 | store.Stack.Push(64) 113 | store.Stack.Push(0) 114 | list := wypes.List[wypes.UInt32]{}.Lift(&store) 115 | 116 | is.SliceEqual(c, list.Unwrap(), []wypes.UInt32{}) 117 | } 118 | 119 | func TestListUint16(t *testing.T) { 120 | c := is.NewRelaxed(t) 121 | stack := wypes.NewSliceStack(4) 122 | store := wypes.Store{Stack: stack, Memory: wypes.NewSliceMemory(1024)} 123 | 124 | data := []wypes.UInt16{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} 125 | wypes.List[wypes.UInt16]{Offset: 96, Raw: data}.Lower(&store) 126 | 127 | store.Stack.Push(96) 128 | store.Stack.Push(10) 129 | list := wypes.List[wypes.UInt16]{}.Lift(&store) 130 | 131 | is.SliceEqual(c, list.Unwrap(), data) 132 | } 133 | 134 | func TestListUint32(t *testing.T) { 135 | c := is.NewRelaxed(t) 136 | stack := wypes.NewSliceStack(4) 137 | store := wypes.Store{Stack: stack, Memory: wypes.NewSliceMemory(1024)} 138 | 139 | data := []wypes.UInt32{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} 140 | wypes.List[wypes.UInt32]{Offset: 64, Raw: data}.Lower(&store) 141 | 142 | store.Stack.Push(64) 143 | store.Stack.Push(10) 144 | list := wypes.List[wypes.UInt32]{}.Lift(&store) 145 | 146 | is.SliceEqual(c, list.Unwrap(), data) 147 | } 148 | 149 | func TestListInt16(t *testing.T) { 150 | c := is.NewRelaxed(t) 151 | stack := wypes.NewSliceStack(4) 152 | store := wypes.Store{Stack: stack, Memory: wypes.NewSliceMemory(1024)} 153 | 154 | data := []wypes.Int16{1, -2, 3, -4, -5, 6, -7, 8, -9, 10} 155 | wypes.List[wypes.Int16]{Offset: 96, Raw: data}.Lower(&store) 156 | 157 | store.Stack.Push(96) 158 | store.Stack.Push(10) 159 | list := wypes.List[wypes.Int16]{}.Lift(&store) 160 | 161 | is.SliceEqual(c, list.Unwrap(), data) 162 | } 163 | 164 | func TestListInt32(t *testing.T) { 165 | c := is.NewRelaxed(t) 166 | stack := wypes.NewSliceStack(4) 167 | store := wypes.Store{Stack: stack, Memory: wypes.NewSliceMemory(1024)} 168 | 169 | data := []wypes.Int32{1, -2, 3, -4, -5, 6, -7, 8, -9, 10} 170 | wypes.List[wypes.Int32]{Offset: 64, Raw: data}.Lower(&store) 171 | 172 | store.Stack.Push(64) 173 | store.Stack.Push(10) 174 | list := wypes.List[wypes.Int32]{}.Lift(&store) 175 | 176 | is.SliceEqual(c, list.Unwrap(), data) 177 | } 178 | 179 | func TestListFloat32(t *testing.T) { 180 | c := is.NewRelaxed(t) 181 | stack := wypes.NewSliceStack(4) 182 | store := wypes.Store{Stack: stack, Memory: wypes.NewSliceMemory(1024)} 183 | 184 | data := []wypes.Float32{1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9, 10.1} 185 | wypes.List[wypes.Float32]{Raw: data}.Lower(&store) 186 | 187 | store.Stack.Push(0) 188 | store.Stack.Push(10) 189 | list := wypes.List[wypes.Float32]{}.Lift(&store) 190 | 191 | is.SliceEqual(c, list.Unwrap(), data) 192 | } 193 | 194 | func TestListBool(t *testing.T) { 195 | c := is.NewRelaxed(t) 196 | stack := wypes.NewSliceStack(4) 197 | store := wypes.Store{Stack: stack, Memory: wypes.NewSliceMemory(1024)} 198 | 199 | data := []wypes.Bool{true, false, false, true, true, false, true, false, true, false} 200 | wypes.List[wypes.Bool]{Offset: 64, Raw: data}.Lower(&store) 201 | 202 | store.Stack.Push(64) 203 | store.Stack.Push(10) 204 | list := wypes.List[wypes.Bool]{}.Lift(&store) 205 | 206 | is.SliceEqual(c, list.Unwrap(), data) 207 | } 208 | 209 | func TestListEmptyStrings(t *testing.T) { 210 | c := is.NewRelaxed(t) 211 | stack := wypes.NewSliceStack(4) 212 | store := wypes.Store{Stack: stack, Memory: wypes.NewSliceMemory(1024)} 213 | 214 | data := []string{} 215 | wypes.ListStrings{Offset: 64, Raw: data}.Lower(&store) 216 | 217 | store.Stack.Push(64) 218 | store.Stack.Push(uint64(len(data))) 219 | list := wypes.ListStrings{}.Lift(&store) 220 | 221 | is.SliceEqual(c, list.Unwrap(), data) 222 | } 223 | 224 | func TestListStrings(t *testing.T) { 225 | c := is.NewRelaxed(t) 226 | stack := wypes.NewSliceStack(4) 227 | store := wypes.Store{Stack: stack, Memory: wypes.NewSliceMemory(1024)} 228 | 229 | data := []string{"Hello", "World", "!"} 230 | wypes.ListStrings{Offset: 64, Raw: data}.Lower(&store) 231 | 232 | store.Stack.Push(64) 233 | store.Stack.Push(uint64(len(data))) 234 | list := wypes.ListStrings{}.Lift(&store) 235 | 236 | is.SliceEqual(c, list.Unwrap(), data) 237 | } 238 | 239 | func TestResultOKUInt32(t *testing.T) { 240 | c := is.NewRelaxed(t) 241 | stack := wypes.NewSliceStack(4) 242 | store := wypes.Store{Stack: stack, Memory: wypes.NewSliceMemory(1024)} 243 | save := wypes.Result[wypes.UInt32, wypes.UInt32, wypes.UInt32]{ 244 | IsError: false, 245 | OK: 1, 246 | Offset: 64, 247 | DataPtr: 128, 248 | } 249 | 250 | save.Lower(&store) 251 | store.Stack.Push(64) 252 | result := wypes.Result[wypes.UInt32, wypes.UInt32, wypes.UInt32]{}.Lift(&store) 253 | 254 | is.Equal(c, result.IsError, false) 255 | is.Equal(c, result.OK, wypes.UInt32(1)) 256 | } 257 | 258 | func TestResultErrUInt32(t *testing.T) { 259 | c := is.NewRelaxed(t) 260 | stack := wypes.NewSliceStack(4) 261 | store := wypes.Store{Stack: stack, Memory: wypes.NewSliceMemory(1024)} 262 | save := wypes.Result[wypes.UInt32, wypes.UInt32, wypes.UInt32]{ 263 | IsError: true, 264 | Error: 1, 265 | Offset: 64, 266 | DataPtr: 128, 267 | } 268 | 269 | save.Lower(&store) 270 | store.Stack.Push(64) 271 | result := wypes.Result[wypes.UInt32, wypes.UInt32, wypes.UInt32]{}.Lift(&store) 272 | 273 | is.Equal(c, result.IsError, true) 274 | is.Equal(c, result.Error, wypes.UInt32(1)) 275 | } 276 | 277 | func TestResultOKStringUInt32(t *testing.T) { 278 | c := is.NewRelaxed(t) 279 | stack := wypes.NewSliceStack(4) 280 | store := wypes.Store{Stack: stack, Memory: wypes.NewSliceMemory(1024)} 281 | save := wypes.Result[wypes.String, wypes.String, wypes.UInt32]{ 282 | IsError: false, 283 | OK: wypes.String{Raw: "awesome"}, 284 | Error: wypes.UInt32(0), 285 | Offset: 64, 286 | DataPtr: 128, 287 | } 288 | 289 | save.Lower(&store) 290 | store.Stack.Push(64) 291 | result := wypes.Result[wypes.String, wypes.String, wypes.UInt32]{}.Lift(&store) 292 | 293 | is.Equal(c, result.IsError, false) 294 | is.Equal(c, result.OK.Unwrap(), "awesome") 295 | } 296 | 297 | func TestResultErrStringUInt32(t *testing.T) { 298 | c := is.NewRelaxed(t) 299 | stack := wypes.NewSliceStack(4) 300 | store := wypes.Store{Stack: stack, Memory: wypes.NewSliceMemory(1024)} 301 | save := wypes.Result[wypes.String, wypes.String, wypes.UInt32]{ 302 | IsError: true, 303 | OK: wypes.String{Raw: "awesome"}, 304 | Error: wypes.UInt32(100), 305 | Offset: 64, 306 | DataPtr: 128, 307 | } 308 | 309 | save.Lower(&store) 310 | store.Stack.Push(64) 311 | result := wypes.Result[wypes.String, wypes.String, wypes.UInt32]{}.Lift(&store) 312 | 313 | is.Equal(c, result.IsError, true) 314 | is.Equal(c, result.Error.Unwrap(), 100) 315 | } 316 | 317 | func TestResultOKListUInt32(t *testing.T) { 318 | c := is.NewRelaxed(t) 319 | stack := wypes.NewSliceStack(4) 320 | store := wypes.Store{Stack: stack, Memory: wypes.NewSliceMemory(1024)} 321 | save := wypes.Result[wypes.List[wypes.UInt32], wypes.List[wypes.UInt32], wypes.UInt32]{ 322 | IsError: false, 323 | OK: wypes.List[wypes.UInt32]{Raw: []wypes.UInt32{1, 2, 3, 4, 5}}, 324 | Error: wypes.UInt32(0), 325 | Offset: 64, 326 | DataPtr: 128, 327 | } 328 | 329 | save.Lower(&store) 330 | store.Stack.Push(64) 331 | result := wypes.Result[wypes.List[wypes.UInt32], wypes.List[wypes.UInt32], wypes.UInt32]{}.Lift(&store) 332 | 333 | is.Equal(c, result.IsError, false) 334 | is.SliceEqual(c, result.OK.Raw, []wypes.UInt32{1, 2, 3, 4, 5}) 335 | } 336 | 337 | func TestResultErrListUInt32(t *testing.T) { 338 | c := is.NewRelaxed(t) 339 | stack := wypes.NewSliceStack(4) 340 | store := wypes.Store{Stack: stack, Memory: wypes.NewSliceMemory(1024)} 341 | save := wypes.Result[wypes.List[wypes.UInt32], wypes.List[wypes.UInt32], wypes.String]{ 342 | IsError: true, 343 | Error: wypes.String{Raw: "error"}, 344 | Offset: 64, 345 | DataPtr: 128, 346 | } 347 | 348 | save.Lower(&store) 349 | store.Stack.Push(64) 350 | result := wypes.Result[wypes.List[wypes.UInt32], wypes.List[wypes.UInt32], wypes.String]{}.Lift(&store) 351 | 352 | is.Equal(c, result.IsError, true) 353 | is.Equal(c, result.Error.Unwrap(), "error") 354 | } 355 | 356 | func TestResultOKBytes(t *testing.T) { 357 | c := is.NewRelaxed(t) 358 | stack := wypes.NewSliceStack(4) 359 | store := wypes.Store{Stack: stack, Memory: wypes.NewSliceMemory(1024)} 360 | save := wypes.Result[wypes.Bytes, wypes.Bytes, wypes.UInt32]{ 361 | IsError: false, 362 | OK: wypes.Bytes{Raw: []byte{1, 2, 3, 4, 5}}, 363 | Error: wypes.UInt32(0), 364 | Offset: 64, 365 | DataPtr: 128, 366 | } 367 | 368 | save.Lower(&store) 369 | store.Stack.Push(64) 370 | result := wypes.Result[wypes.Bytes, wypes.Bytes, wypes.UInt32]{}.Lift(&store) 371 | 372 | is.Equal(c, result.IsError, false) 373 | is.SliceEqual(c, result.OK.Raw, []byte{1, 2, 3, 4, 5}) 374 | } 375 | 376 | func TestResultOKHostRef(t *testing.T) { 377 | type thing struct { 378 | ID wypes.Int32 379 | } 380 | 381 | c := is.NewRelaxed(t) 382 | stack := wypes.NewSliceStack(4) 383 | store := wypes.Store{ 384 | Stack: stack, 385 | Memory: wypes.NewSliceMemory(1024), 386 | Refs: wypes.NewMapRefs(), 387 | } 388 | ref := thing{ID: 5} 389 | save := wypes.Result[wypes.HostRef[*thing], wypes.HostRef[*thing], wypes.UInt32]{ 390 | IsError: false, 391 | OK: wypes.HostRef[*thing]{Raw: &ref}, 392 | Error: wypes.UInt32(0), 393 | Offset: 64, 394 | DataPtr: 128, 395 | } 396 | 397 | save.Lower(&store) 398 | store.Stack.Push(64) 399 | result := wypes.Result[wypes.HostRef[*thing], wypes.HostRef[*thing], wypes.UInt32]{}.Lift(&store) 400 | 401 | is.Equal(c, result.IsError, false) 402 | is.Equal(c, result.OK.Raw, &ref) 403 | } 404 | -------------------------------------------------------------------------------- /types_misc.go: -------------------------------------------------------------------------------- 1 | package wypes 2 | 3 | import ( 4 | "context" 5 | "encoding/binary" 6 | "math" 7 | "time" 8 | ) 9 | 10 | // Bool wraps [bool]. 11 | type Bool bool 12 | 13 | const BoolSize = 1 14 | 15 | // Unwrap returns the wrapped value. 16 | func (v Bool) Unwrap() bool { 17 | return bool(v) 18 | } 19 | 20 | // ValueTypes implements [Value] interface. 21 | func (Bool) ValueTypes() []ValueType { 22 | return []ValueType{ValueTypeI32} 23 | } 24 | 25 | // Lift implements [Lift] interface. 26 | func (Bool) Lift(s *Store) Bool { 27 | return s.Stack.Pop() != 0 28 | } 29 | 30 | // Lower implements [Lower] interface. 31 | func (v Bool) Lower(s *Store) { 32 | res := 0 33 | if v { 34 | res = 1 35 | } 36 | s.Stack.Push(Raw(res)) 37 | } 38 | 39 | // MemoryLift implements [MemoryLift] interface. 40 | func (Bool) MemoryLift(s *Store, offset uint32) (Bool, uint32) { 41 | raw, ok := s.Memory.Read(offset, BoolSize) 42 | if !ok { 43 | s.Error = ErrMemRead 44 | return Bool(false), 0 45 | } 46 | 47 | return Bool(raw[0] > 0), BoolSize 48 | } 49 | 50 | // MemoryLower implements [MemoryLower] interface. 51 | func (v Bool) MemoryLower(s *Store, offset uint32) (length uint32) { 52 | res := byte(0) 53 | if v { 54 | res = 1 55 | } 56 | 57 | ok := s.Memory.Write(offset, []byte{res}) 58 | if !ok { 59 | s.Error = ErrMemRead 60 | return 0 61 | } 62 | 63 | return BoolSize 64 | } 65 | 66 | // Float32 wraps [float32]. 67 | type Float32 float32 68 | 69 | const Float32Size = 4 70 | 71 | // Unwrap returns the wrapped value. 72 | func (v Float32) Unwrap() float32 { 73 | return float32(v) 74 | } 75 | 76 | // ValueTypes implements [Value] interface. 77 | func (Float32) ValueTypes() []ValueType { 78 | return []ValueType{ValueTypeF32} 79 | } 80 | 81 | // Lift implements [Lift] interface. 82 | func (Float32) Lift(s *Store) Float32 { 83 | f := math.Float32frombits(uint32(s.Stack.Pop())) 84 | return Float32(f) 85 | } 86 | 87 | // Lower implements [Lower] interface. 88 | func (v Float32) Lower(s *Store) { 89 | r := math.Float32bits(float32(v)) 90 | s.Stack.Push(Raw(r)) 91 | } 92 | 93 | // MemoryLift implements [MemoryLift] interface. 94 | func (Float32) MemoryLift(s *Store, offset uint32) (Float32, uint32) { 95 | raw, ok := s.Memory.Read(offset, Float32Size) 96 | if !ok { 97 | s.Error = ErrMemRead 98 | return Float32(0), 0 99 | } 100 | 101 | return Float32(math.Float32frombits(binary.LittleEndian.Uint32(raw))), Float32Size 102 | } 103 | 104 | // MemoryLower implements [MemoryLower] interface. 105 | func (v Float32) MemoryLower(s *Store, offset uint32) (length uint32) { 106 | data := make([]byte, Float32Size) 107 | binary.LittleEndian.PutUint32(data, math.Float32bits(float32(v))) 108 | ok := s.Memory.Write(offset, data) 109 | if !ok { 110 | s.Error = ErrMemRead 111 | return 0 112 | } 113 | 114 | return Float32Size 115 | } 116 | 117 | // Float64 wraps [float64]. 118 | type Float64 float64 119 | 120 | const Float64Size = 8 121 | 122 | // Unwrap returns the wrapped value. 123 | func (v Float64) Unwrap() float64 { 124 | return float64(v) 125 | } 126 | 127 | // ValueTypes implements [Value] interface. 128 | func (Float64) ValueTypes() []ValueType { 129 | return []ValueType{ValueTypeF64} 130 | } 131 | 132 | // Lift implements [Lift] interface. 133 | func (Float64) Lift(s *Store) Float64 { 134 | f := math.Float64frombits(s.Stack.Pop()) 135 | return Float64(f) 136 | } 137 | 138 | // Lower implements [Lower] interface. 139 | func (v Float64) Lower(s *Store) { 140 | res := math.Float64bits(float64(v)) 141 | s.Stack.Push(Raw(res)) 142 | } 143 | 144 | // MemoryLift implements [MemoryLift] interface. 145 | func (Float64) MemoryLift(s *Store, offset uint32) (Float64, uint32) { 146 | raw, ok := s.Memory.Read(offset, Float64Size) 147 | if !ok { 148 | s.Error = ErrMemRead 149 | return Float64(0), 0 150 | } 151 | 152 | return Float64(math.Float32frombits(binary.LittleEndian.Uint32(raw))), Float64Size 153 | } 154 | 155 | // MemoryLower implements [MemoryLower] interface. 156 | func (v Float64) MemoryLower(s *Store, offset uint32) (length uint32) { 157 | data := make([]byte, Float64Size) 158 | binary.LittleEndian.PutUint64(data, math.Float64bits(float64(v))) 159 | ok := s.Memory.Write(offset, data) 160 | if !ok { 161 | s.Error = ErrMemRead 162 | return 0 163 | } 164 | 165 | return Float64Size 166 | } 167 | 168 | // Complex64 wraps [complex64]. 169 | type Complex64 complex64 170 | 171 | // Unwrap returns the wrapped value. 172 | func (v Complex64) Unwrap() complex64 { 173 | return complex64(v) 174 | } 175 | 176 | // ValueTypes implements [Value] interface. 177 | func (Complex64) ValueTypes() []ValueType { 178 | return []ValueType{ValueTypeF32, ValueTypeF32} 179 | } 180 | 181 | // Lift implements [Lift] interface. 182 | func (Complex64) Lift(s *Store) Complex64 { 183 | c := complex( 184 | math.Float32frombits(uint32(s.Stack.Pop())), 185 | math.Float32frombits(uint32(s.Stack.Pop())), 186 | ) 187 | return Complex64(c) 188 | } 189 | 190 | // Lower implements [Lower] interface. 191 | func (v Complex64) Lower(s *Store) { 192 | vReal := math.Float32bits(real(v)) 193 | vImag := math.Float32bits(imag(v)) 194 | s.Stack.Push(Raw(vReal)) 195 | s.Stack.Push(Raw(vImag)) 196 | } 197 | 198 | // Complex128 wraps [complex128]. 199 | type Complex128 complex128 200 | 201 | // Unwrap returns the wrapped value. 202 | func (v Complex128) Unwrap() complex128 { 203 | return complex128(v) 204 | } 205 | 206 | // ValueTypes implements [Value] interface. 207 | func (Complex128) ValueTypes() []ValueType { 208 | return []ValueType{ValueTypeF64, ValueTypeF64} 209 | } 210 | 211 | // Lift implements [Lift] interface. 212 | func (Complex128) Lift(s *Store) Complex128 { 213 | c := complex( 214 | math.Float64frombits(uint64(s.Stack.Pop())), 215 | math.Float64frombits(uint64(s.Stack.Pop())), 216 | ) 217 | return Complex128(c) 218 | } 219 | 220 | // Lower implements [Lower] interface. 221 | func (v Complex128) Lower(s *Store) { 222 | vReal := math.Float64bits(real(v)) 223 | vImag := math.Float64bits(imag(v)) 224 | s.Stack.Push(Raw(vReal)) 225 | s.Stack.Push(Raw(vImag)) 226 | } 227 | 228 | // Duration wraps [time.Duration]. 229 | type Duration time.Duration 230 | 231 | // Unwrap returns the wrapped value. 232 | func (v Duration) Unwrap() time.Duration { 233 | return time.Duration(v) 234 | } 235 | 236 | // ValueTypes implements [Value] interface. 237 | func (Duration) ValueTypes() []ValueType { 238 | return []ValueType{ValueTypeI64} 239 | } 240 | 241 | // Lift implements [Lift] interface. 242 | func (Duration) Lift(s *Store) Duration { 243 | return Duration(s.Stack.Pop()) 244 | } 245 | 246 | // Lower implements [Lower] interface. 247 | func (v Duration) Lower(s *Store) { 248 | s.Stack.Push(Raw(v)) 249 | } 250 | 251 | // Time wraps [time.Time]. 252 | type Time time.Time 253 | 254 | // Unwrap returns the wrapped value. 255 | func (v Time) Unwrap() time.Time { 256 | return time.Time(v) 257 | } 258 | 259 | // ValueTypes implements [Value] interface. 260 | func (Time) ValueTypes() []ValueType { 261 | return []ValueType{ValueTypeI64} 262 | } 263 | 264 | // Lift implements [Lift] interface. 265 | func (Time) Lift(s *Store) Time { 266 | return Time(time.Unix(int64(s.Stack.Pop()), 0)) 267 | } 268 | 269 | // Lower implements [Lower] interface. 270 | func (v Time) Lower(s *Store) { 271 | s.Stack.Push(Raw(time.Time(v).Unix())) 272 | } 273 | 274 | // Context wraps [context.Context]. 275 | type Context struct{ ctx context.Context } 276 | 277 | // Unwrap returns the wrapped value. 278 | func (v Context) Unwrap() context.Context { 279 | return v.ctx 280 | } 281 | 282 | // ValueTypes implements [Value] interface. 283 | func (Context) ValueTypes() []ValueType { 284 | return []ValueType{} 285 | } 286 | 287 | // Lift implements [Lift] interface. 288 | func (Context) Lift(s Store) Context { 289 | return Context{ctx: s.Context} 290 | } 291 | 292 | // Void is a return type of a function that returns nothing. 293 | type Void struct{} 294 | 295 | // ValueTypes implements [Value] interface. 296 | func (Void) ValueTypes() []ValueType { 297 | return []ValueType{} 298 | } 299 | 300 | // Lower implements [Lower] interface. 301 | func (Void) Lower(s *Store) {} 302 | 303 | // Pair wraps two values of arbitrary types. 304 | // 305 | // You can combine multiple pairs to pass more than 2 values at once. 306 | // All values are passed through the stack, not memory. 307 | type Pair[L LiftLower[L], R LiftLower[R]] struct { 308 | Left L 309 | Right R 310 | } 311 | 312 | // ValueTypes implements [Value] interface. 313 | func (v Pair[L, R]) ValueTypes() []ValueType { 314 | types := make([]ValueType, 0, 2) 315 | types = append(types, v.Left.ValueTypes()...) 316 | types = append(types, v.Right.ValueTypes()...) 317 | return types 318 | } 319 | 320 | // Lift implements [Lift] interface. 321 | func (Pair[L, R]) Lift(s *Store) Pair[L, R] { 322 | var left L 323 | var right R 324 | return Pair[L, R]{ 325 | Left: left.Lift(s), 326 | Right: right.Lift(s), 327 | } 328 | } 329 | 330 | // Lower implements [Lower] interface. 331 | func (v Pair[L, R]) Lower(s *Store) { 332 | v.Left.Lower(s) 333 | v.Right.Lower(s) 334 | } 335 | 336 | // HostRef is a reference to a Go object stored on the host side in [Refs]. 337 | // 338 | // References created this way are never collected by GC because there is no way 339 | // to know if the wasm module still needs it. So it is important to explicitly clean 340 | // references by calling [HostRef.Drop]. 341 | // 342 | // A common usage pattern is to create a reference in one host-defined function, 343 | // return it into the wasm module, and then clean it up in another host-defined function 344 | // caled from wasm when the guest doesn't need the value anymore. 345 | // In this scenario, the latter function accepts HostRef as an argument and calls its 346 | // [HostRef.Drop] method. After that, the reference is removed from [Refs] in the [Store] 347 | // and will be eventually collected by GC. 348 | type HostRef[T any] struct { 349 | Raw T 350 | index uint32 351 | refs Refs 352 | } 353 | 354 | // Unwrap returns the wrapped value. 355 | func (v HostRef[T]) Unwrap() T { 356 | return v.Raw 357 | } 358 | 359 | // Drop remove the reference from [Refs] in [Store]. 360 | // 361 | // Can be called only on lifted references 362 | // (passed as an argument into a host-defined function). 363 | func (v HostRef[T]) Drop() { 364 | if v.refs != nil { 365 | v.refs.Drop(v.index) 366 | } 367 | } 368 | 369 | // ValueTypes implements [Value] interface. 370 | func (HostRef[T]) ValueTypes() []ValueType { 371 | return []ValueType{ValueTypeI32} 372 | } 373 | 374 | // Lift implements [Lift] interface. 375 | func (HostRef[T]) Lift(s *Store) HostRef[T] { 376 | index := uint32(s.Stack.Pop()) 377 | var def T 378 | raw, found := s.Refs.Get(index, def) 379 | if !found { 380 | s.Error = ErrRefNotFound 381 | } 382 | cast, ok := raw.(T) 383 | if found && !ok { 384 | s.Error = ErrRefCast 385 | cast = def 386 | } 387 | return HostRef[T]{ 388 | Raw: cast, 389 | index: index, 390 | refs: s.Refs, 391 | } 392 | } 393 | 394 | // Lower implements [Lower] interface. 395 | func (v HostRef[T]) Lower(s *Store) { 396 | var index uint32 397 | if v.index == 0 { 398 | index = s.Refs.Put(v.Raw) 399 | } else { 400 | index = v.index 401 | s.Refs.Set(v.index, v.Raw) 402 | } 403 | s.Stack.Push(Raw(index)) 404 | } 405 | 406 | // MemoryLift implements [MemoryLifter] interface. 407 | func (HostRef[T]) MemoryLift(s *Store, offset uint32) (HostRef[T], uint32) { 408 | i, ok := s.Memory.Read(offset, uInt32Size) 409 | var def T 410 | if !ok { 411 | s.Error = ErrMemRead 412 | return HostRef[T]{}, 0 413 | } 414 | index := binary.LittleEndian.Uint32(i) 415 | raw, found := s.Refs.Get(index, def) 416 | if !found { 417 | s.Error = ErrRefNotFound 418 | } 419 | cast, ok := raw.(T) 420 | if found && !ok { 421 | s.Error = ErrRefCast 422 | cast = def 423 | } 424 | return HostRef[T]{ 425 | Raw: cast, 426 | index: index, 427 | refs: s.Refs, 428 | }, uInt32Size 429 | } 430 | 431 | // MemoryLower implements [MemoryLower] interface. 432 | func (v HostRef[T]) MemoryLower(s *Store, offset uint32) (length uint32) { 433 | var index uint32 434 | if v.index == 0 { 435 | index = s.Refs.Put(v.Raw) 436 | } else { 437 | index = v.index 438 | s.Refs.Set(v.index, v.Raw) 439 | } 440 | 441 | data := make([]byte, uInt32Size) 442 | binary.LittleEndian.PutUint32(data, uint32(index)) 443 | ok := s.Memory.Write(offset, data) 444 | if !ok { 445 | s.Error = ErrMemWrite 446 | return 0 447 | } 448 | 449 | return uInt32Size 450 | } 451 | -------------------------------------------------------------------------------- /types_test.go: -------------------------------------------------------------------------------- 1 | package wypes_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/orsinium-labs/tinytest/is" 7 | "github.com/orsinium-labs/wypes" 8 | ) 9 | 10 | func testRoundtrip[T wypes.LiftLower[T]](t *testing.T) { 11 | c := is.NewRelaxed(t) 12 | stack := wypes.NewSliceStack(4) 13 | store := wypes.Store{Stack: stack} 14 | 15 | // push the value to be checked on the stack 16 | stack.Push(123) 17 | is.Equal(c, stack.Len(), 1) 18 | 19 | // lift, stack should be empty 20 | var i T 21 | i = i.Lift(&store) 22 | is.Equal(c, stack.Len(), 0) 23 | 24 | // lower, it should put the value on the stack 25 | i.Lower(&store) 26 | is.Equal(c, stack.Len(), 1) 27 | 28 | // pop from the stack and check the value 29 | is.Equal(c, stack.Pop(), 123) 30 | is.Equal(c, stack.Len(), 0) 31 | } 32 | 33 | // Test that lifting and then lowering a value doesn't change the value. 34 | func TestRoundtrip(t *testing.T) { 35 | t.Run("Int8", testRoundtrip[wypes.Int8]) 36 | t.Run("Int16", testRoundtrip[wypes.Int16]) 37 | t.Run("Int32", testRoundtrip[wypes.Int32]) 38 | t.Run("Int64", testRoundtrip[wypes.Int64]) 39 | t.Run("Int", testRoundtrip[wypes.Int]) 40 | 41 | t.Run("UInt8", testRoundtrip[wypes.UInt8]) 42 | t.Run("UInt16", testRoundtrip[wypes.UInt16]) 43 | t.Run("UInt32", testRoundtrip[wypes.UInt32]) 44 | t.Run("UInt64", testRoundtrip[wypes.UInt64]) 45 | t.Run("UInt", testRoundtrip[wypes.UInt]) 46 | t.Run("Byte", testRoundtrip[wypes.Byte]) 47 | t.Run("Rune", testRoundtrip[wypes.Rune]) 48 | 49 | t.Run("Float32", testRoundtrip[wypes.Float32]) 50 | t.Run("Float64", testRoundtrip[wypes.Float64]) 51 | t.Run("Duration", testRoundtrip[wypes.Duration]) 52 | t.Run("Time", testRoundtrip[wypes.Time]) 53 | } 54 | 55 | func testRoundtripPair[T wypes.LiftLower[T]](t *testing.T) { 56 | c := is.NewRelaxed(t) 57 | stack := wypes.NewSliceStack(4) 58 | store := wypes.Store{Stack: stack} 59 | 60 | // push the values to be checked on the stack 61 | stack.Push(123) 62 | stack.Push(79) 63 | is.Equal(c, stack.Len(), 2) 64 | 65 | // lift, stack should be empty 66 | var i T 67 | i = i.Lift(&store) 68 | is.Equal(c, stack.Len(), 0) 69 | 70 | // lower, it should put the values on the stack 71 | i.Lower(&store) 72 | is.Equal(c, stack.Len(), 2) 73 | 74 | // pop from the stack and check the value 75 | is.Equal(c, stack.Pop(), 123) 76 | is.Equal(c, stack.Pop(), 79) 77 | is.Equal(c, stack.Len(), 0) 78 | } 79 | 80 | func TestRoundtripPair(t *testing.T) { 81 | t.Run("Complex64", testRoundtripPair[wypes.Complex64]) 82 | t.Run("Complex128", testRoundtripPair[wypes.Complex128]) 83 | t.Run("Pair", testRoundtripPair[wypes.Pair[wypes.Int16, wypes.Int32]]) 84 | } 85 | 86 | // A static check that all primitive types can be implicitly cast from literals. 87 | func TestAssignLiteral(t *testing.T) { 88 | var _ wypes.Int8 = 123 89 | var _ wypes.Int16 = 12377 90 | var _ wypes.Int32 = 1237777777 91 | var _ wypes.Int64 = 1237777777777777777 92 | var _ wypes.Int = 1237777777 93 | 94 | var _ wypes.UInt8 = 123 95 | var _ wypes.UInt16 = 12377 96 | var _ wypes.UInt32 = 1237777777 97 | var _ wypes.UInt64 = 1237777777777777777 98 | var _ wypes.UInt = 1237777777 99 | 100 | var _ wypes.Float32 = 1.5 101 | var _ wypes.Float64 = 1.5 102 | var _ wypes.Complex64 = 3.4 + 1.5i 103 | var _ wypes.Complex128 = 3.4 + 1.5i 104 | var _ wypes.Bool = true 105 | } 106 | 107 | func TestString_Lift(t *testing.T) { 108 | c := is.NewRelaxed(t) 109 | stack := wypes.NewSliceStack(4) 110 | memory := wypes.NewSliceMemory(40) 111 | ok := memory.Write(3, []byte("hello!")) 112 | is.True(c, ok) 113 | store := wypes.Store{Stack: stack, Memory: memory} 114 | stack.Push(3) // offset 115 | stack.Push(6) // len 116 | var typ wypes.String 117 | val := typ.Lift(&store) 118 | is.Equal(c, val.Unwrap(), "hello!") 119 | } 120 | 121 | func TestString_Lower(t *testing.T) { 122 | c := is.NewRelaxed(t) 123 | stack := wypes.NewSliceStack(4) 124 | memory := wypes.NewSliceMemory(40) 125 | store := wypes.Store{Stack: stack, Memory: memory} 126 | 127 | val1 := wypes.String{ 128 | Offset: 3, 129 | Raw: "hello!", 130 | } 131 | val1.Lower(&store) 132 | val2 := val1.Lift(&store) 133 | is.Equal(c, val2.Unwrap(), "hello!") 134 | } 135 | 136 | func TestBytes_Lift(t *testing.T) { 137 | c := is.NewRelaxed(t) 138 | stack := wypes.NewSliceStack(4) 139 | memory := wypes.NewSliceMemory(40) 140 | ok := memory.Write(3, []byte("hello!")) 141 | is.True(c, ok) 142 | store := wypes.Store{Stack: stack, Memory: memory} 143 | stack.Push(3) // offset 144 | stack.Push(6) // len 145 | var typ wypes.Bytes 146 | val := typ.Lift(&store) 147 | is.SliceEqual(c, val.Unwrap(), []byte("hello!")) 148 | } 149 | 150 | func TestBytes_Lower(t *testing.T) { 151 | c := is.NewRelaxed(t) 152 | store := wypes.Store{ 153 | Stack: wypes.NewSliceStack(4), 154 | Memory: wypes.NewSliceMemory(40), 155 | } 156 | 157 | val1 := wypes.Bytes{ 158 | Offset: 3, 159 | Raw: []byte("hello!"), 160 | } 161 | val1.Lower(&store) 162 | val2 := val1.Lift(&store) 163 | is.SliceEqual(c, val2.Unwrap(), []byte("hello!")) 164 | } 165 | 166 | type user struct { 167 | name string 168 | } 169 | 170 | func TestHostRef_Lower(t *testing.T) { 171 | c := is.NewRelaxed(t) 172 | stack := wypes.NewSliceStack(4) 173 | store := wypes.Store{ 174 | Stack: stack, 175 | Refs: wypes.NewMapRefs(), 176 | } 177 | val1 := wypes.HostRef[user]{Raw: user{"aragorn"}} 178 | val2 := wypes.HostRef[user]{Raw: user{"gandalf"}} 179 | val1.Lower(&store) 180 | val2.Lower(&store) 181 | val3 := val2.Lift(&store) 182 | is.Equal(c, val3.Unwrap(), user{"gandalf"}) 183 | } 184 | 185 | func TestHostRef_Drop(t *testing.T) { 186 | c := is.NewRelaxed(t) 187 | refs := wypes.NewMapRefs() 188 | store := wypes.Store{ 189 | Stack: wypes.NewSliceStack(4), 190 | Refs: refs, 191 | } 192 | val1 := wypes.HostRef[user]{Raw: user{"aragorn"}} 193 | val1.Lower(&store) 194 | val2 := val1.Lift(&store) 195 | is.Equal(c, val2.Unwrap(), user{"aragorn"}) 196 | is.Equal(c, len(refs.Raw), 1) 197 | val2.Drop() 198 | is.Equal(c, len(refs.Raw), 0) 199 | } 200 | -------------------------------------------------------------------------------- /types_uint.go: -------------------------------------------------------------------------------- 1 | package wypes 2 | 3 | import "encoding/binary" 4 | 5 | // UInt8 wraps uint8, 8-bit unsigned integer. 6 | type UInt8 uint8 7 | 8 | const uInt8Size = 1 9 | 10 | // Unwrap returns the wrapped value. 11 | func (v UInt8) Unwrap() uint8 { 12 | return uint8(v) 13 | } 14 | 15 | // ValueTypes implements [Value] interface. 16 | func (UInt8) ValueTypes() []ValueType { 17 | return []ValueType{ValueTypeI32} 18 | } 19 | 20 | // Lift implements [Lift] interface. 21 | func (UInt8) Lift(s *Store) UInt8 { 22 | return UInt8(s.Stack.Pop()) 23 | } 24 | 25 | // Lower implements [Lower] interface. 26 | func (v UInt8) Lower(s *Store) { 27 | s.Stack.Push(Raw(v)) 28 | } 29 | 30 | // MemoryLift implements [MemoryLift] interface. 31 | func (UInt8) MemoryLift(s *Store, offset uint32) (UInt8, uint32) { 32 | raw, ok := s.Memory.Read(offset, uInt8Size) 33 | if !ok { 34 | s.Error = ErrMemRead 35 | return UInt8(0), 0 36 | } 37 | 38 | return UInt8(raw[0]), uInt8Size 39 | } 40 | 41 | // MemoryLower implements [MemoryLower] interface. 42 | func (v UInt8) MemoryLower(s *Store, offset uint32) (length uint32) { 43 | ok := s.Memory.Write(offset, []byte{byte(v)}) 44 | if !ok { 45 | s.Error = ErrMemWrite 46 | return 0 47 | } 48 | 49 | return uInt8Size 50 | } 51 | 52 | // UInt16 wraps uint16, 16-bit unsigned integer. 53 | type UInt16 uint16 54 | 55 | const uInt16Size = 2 56 | 57 | // Unwrap returns the wrapped value. 58 | func (v UInt16) Unwrap() uint16 { 59 | return uint16(v) 60 | } 61 | 62 | // ValueTypes implements [Value] interface. 63 | func (UInt16) ValueTypes() []ValueType { 64 | return []ValueType{ValueTypeI32} 65 | } 66 | 67 | // Lift implements [Lift] interface. 68 | func (UInt16) Lift(s *Store) UInt16 { 69 | return UInt16(s.Stack.Pop()) 70 | } 71 | 72 | // Lower implements [Lower] interface. 73 | func (v UInt16) Lower(s *Store) { 74 | s.Stack.Push(Raw(v)) 75 | } 76 | 77 | // MemoryLift implements [MemoryLift] interface. 78 | func (UInt16) MemoryLift(s *Store, offset uint32) (UInt16, uint32) { 79 | raw, ok := s.Memory.Read(offset, uInt16Size) 80 | if !ok { 81 | s.Error = ErrMemRead 82 | return UInt16(0), 0 83 | } 84 | 85 | return UInt16(binary.LittleEndian.Uint16(raw)), uInt16Size 86 | } 87 | 88 | // MemoryLower implements [MemoryLower] interface. 89 | func (v UInt16) MemoryLower(s *Store, offset uint32) (length uint32) { 90 | data := make([]byte, uInt16Size) 91 | binary.LittleEndian.PutUint16(data, uint16(v)) 92 | ok := s.Memory.Write(offset, data) 93 | if !ok { 94 | s.Error = ErrMemWrite 95 | return 0 96 | } 97 | 98 | return uInt16Size 99 | } 100 | 101 | // UInt32 wraps uint32, 32-bit unsigned integer. 102 | type UInt32 uint32 103 | 104 | const uInt32Size = 4 105 | 106 | // Unwrap returns the wrapped value. 107 | func (v UInt32) Unwrap() uint32 { 108 | return uint32(v) 109 | } 110 | 111 | // ValueTypes implements [Value] interface. 112 | func (UInt32) ValueTypes() []ValueType { 113 | return []ValueType{ValueTypeI32} 114 | } 115 | 116 | // Lift implements [Lift] interface. 117 | func (UInt32) Lift(s *Store) UInt32 { 118 | return UInt32(s.Stack.Pop()) 119 | } 120 | 121 | // Lower implements [Lower] interface. 122 | func (v UInt32) Lower(s *Store) { 123 | s.Stack.Push(Raw(v)) 124 | } 125 | 126 | // MemoryLift implements [MemoryLift] interface. 127 | func (UInt32) MemoryLift(s *Store, offset uint32) (UInt32, uint32) { 128 | raw, ok := s.Memory.Read(offset, uInt32Size) 129 | if !ok { 130 | s.Error = ErrMemRead 131 | return UInt32(0), 0 132 | } 133 | 134 | return UInt32(binary.LittleEndian.Uint32(raw)), uInt32Size 135 | } 136 | 137 | // MemoryLower implements [MemoryLower] interface. 138 | func (v UInt32) MemoryLower(s *Store, offset uint32) (length uint32) { 139 | data := make([]byte, uInt32Size) 140 | binary.LittleEndian.PutUint32(data, uint32(v)) 141 | ok := s.Memory.Write(offset, data) 142 | if !ok { 143 | s.Error = ErrMemWrite 144 | return 0 145 | } 146 | 147 | return uInt32Size 148 | } 149 | 150 | // UInt64 wraps uint64, 64-bit unsigned integer. 151 | type UInt64 uint64 152 | 153 | const uInt64Size = 8 154 | 155 | // Unwrap returns the wrapped value. 156 | func (v UInt64) Unwrap() uint64 { 157 | return uint64(v) 158 | } 159 | 160 | // ValueTypes implements [Value] interface. 161 | func (UInt64) ValueTypes() []ValueType { 162 | return []ValueType{ValueTypeI64} 163 | } 164 | 165 | // Lift implements [Lift] interface. 166 | func (UInt64) Lift(s *Store) UInt64 { 167 | return UInt64(s.Stack.Pop()) 168 | } 169 | 170 | // Lower implements [Lower] interface. 171 | func (v UInt64) Lower(s *Store) { 172 | s.Stack.Push(Raw(v)) 173 | } 174 | 175 | // MemoryLift implements [MemoryLift] interface. 176 | func (UInt64) MemoryLift(s *Store, offset uint32) (UInt64, uint32) { 177 | raw, ok := s.Memory.Read(offset, uInt64Size) 178 | if !ok { 179 | s.Error = ErrMemRead 180 | return UInt64(0), 0 181 | } 182 | 183 | return UInt64(binary.LittleEndian.Uint64(raw)), uInt64Size 184 | } 185 | 186 | // MemoryLower implements [MemoryLower] interface. 187 | func (v UInt64) MemoryLower(s *Store, offset uint32) (length uint32) { 188 | data := make([]byte, uInt64Size) 189 | binary.LittleEndian.PutUint64(data, uint64(v)) 190 | ok := s.Memory.Write(offset, data) 191 | if !ok { 192 | s.Error = ErrMemWrite 193 | return 0 194 | } 195 | 196 | return uInt64Size 197 | } 198 | 199 | // UInt wraps uint, 32-bit unsigned integer. 200 | type UInt uint 201 | 202 | const uIntSize = 8 203 | 204 | // Unwrap returns the wrapped value. 205 | func (v UInt) Unwrap() uint { 206 | return uint(v) 207 | } 208 | 209 | // ValueTypes implements [Value] interface. 210 | func (UInt) ValueTypes() []ValueType { 211 | return []ValueType{ValueTypeI64} 212 | } 213 | 214 | // Lift implements [Lift] interface. 215 | func (UInt) Lift(s *Store) UInt { 216 | return UInt(s.Stack.Pop()) 217 | } 218 | 219 | // Lower implements [Lower] interface. 220 | func (v UInt) Lower(s *Store) { 221 | s.Stack.Push(Raw(v)) 222 | } 223 | 224 | // MemoryLift implements [Reader] interface. 225 | func (UInt) MemoryLift(s *Store, offset uint32) (UInt, uint32) { 226 | raw, ok := s.Memory.Read(offset, uIntSize) 227 | if !ok { 228 | s.Error = ErrMemRead 229 | return UInt(0), 0 230 | } 231 | 232 | return UInt(binary.LittleEndian.Uint64(raw)), uIntSize 233 | } 234 | 235 | // MemoryLower implements [MemoryLower] interface. 236 | func (v UInt) MemoryLower(s *Store, offset uint32) (length uint32) { 237 | data := make([]byte, uIntSize) 238 | binary.LittleEndian.PutUint64(data, uint64(v)) 239 | ok := s.Memory.Write(offset, data) 240 | if !ok { 241 | s.Error = ErrMemWrite 242 | return 0 243 | } 244 | 245 | return uIntSize 246 | } 247 | 248 | // UIntPtr wraps uintptr, pointer-sized unsigned integer. 249 | type UIntPtr uintptr 250 | 251 | const uIntPtrSize = 8 252 | 253 | // Unwrap returns the wrapped value. 254 | func (v UIntPtr) Unwrap() uintptr { 255 | return uintptr(v) 256 | } 257 | 258 | // ValueTypes implements [Value] interface. 259 | func (UIntPtr) ValueTypes() []ValueType { 260 | return []ValueType{ValueTypeI64} 261 | } 262 | 263 | // Lift implements [Lift] interface. 264 | func (UIntPtr) Lift(s *Store) UIntPtr { 265 | return UIntPtr(s.Stack.Pop()) 266 | } 267 | 268 | // Lower implements [Lower] interface. 269 | func (v UIntPtr) Lower(s *Store) { 270 | s.Stack.Push(Raw(v)) 271 | } 272 | 273 | // MemoryLift implements [MemoryLift] interface. 274 | func (UIntPtr) MemoryLift(s *Store, offset uint32) (UIntPtr, uint32) { 275 | raw, ok := s.Memory.Read(offset, uIntPtrSize) 276 | if !ok { 277 | s.Error = ErrMemRead 278 | return UIntPtr(0), 0 279 | } 280 | 281 | return UIntPtr(binary.LittleEndian.Uint64(raw)), uIntPtrSize 282 | } 283 | 284 | // MemoryLower implements [MemoryLower] interface. 285 | func (v UIntPtr) MemoryLower(s *Store, offset uint32) (length uint32) { 286 | data := make([]byte, uIntPtrSize) 287 | binary.LittleEndian.PutUint64(data, uint64(v)) 288 | ok := s.Memory.Write(offset, data) 289 | if !ok { 290 | s.Error = ErrMemWrite 291 | return 0 292 | } 293 | 294 | return uIntPtrSize 295 | } 296 | 297 | // Rune is an alias for [UInt32]. 298 | type Rune = UInt32 299 | 300 | // Byte is an alias for [UInt8]. 301 | type Byte = UInt8 302 | -------------------------------------------------------------------------------- /wazero.go: -------------------------------------------------------------------------------- 1 | //go:build !nowazero 2 | // +build !nowazero 3 | 4 | package wypes 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/tetratelabs/wazero" 10 | "github.com/tetratelabs/wazero/api" 11 | ) 12 | 13 | // DefineWazero registers all the host modules in the given wazero runtime. 14 | func (ms Modules) DefineWazero(runtime wazero.Runtime, refs Refs) error { 15 | if refs == nil { 16 | refs = NewMapRefs() 17 | } 18 | for modName, funcs := range ms { 19 | err := funcs.DefineWazero(runtime, modName, refs) 20 | if err != nil { 21 | return err 22 | } 23 | } 24 | return nil 25 | } 26 | 27 | // DefineWazero registers the host module in the given wazero runtime. 28 | func (m Module) DefineWazero(runtime wazero.Runtime, modName string, refs Refs) error { 29 | var err error 30 | mb := runtime.NewHostModuleBuilder(modName) 31 | for funcName, funcDef := range m { 32 | fb := mb.NewFunctionBuilder() 33 | fb = fb.WithGoModuleFunction( 34 | wazeroAdaptHostFunc(funcDef, refs), 35 | funcDef.ParamValueTypes(), 36 | funcDef.ResultValueTypes(), 37 | ) 38 | mb = fb.Export(funcName) 39 | } 40 | _, err = mb.Instantiate(context.Background()) 41 | return err 42 | } 43 | 44 | func wazeroAdaptHostFunc(hf HostFunc, refs Refs) api.GoModuleFunction { 45 | return api.GoModuleFunc(func(ctx context.Context, mod api.Module, stack []uint64) { 46 | adaptedStack := SliceStack(stack) 47 | store := Store{ 48 | Memory: mod.Memory(), 49 | Stack: &adaptedStack, 50 | Refs: refs, 51 | Context: ctx, 52 | } 53 | hf.Call(&store) 54 | }) 55 | } 56 | --------------------------------------------------------------------------------