├── .github └── workflows │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── cmd └── wasman │ └── main.go ├── config ├── config.go └── wasi.go ├── examples ├── downloader.go ├── hostbytes │ └── main.go ├── hoststring │ └── main.go ├── log │ └── main.go └── numeric │ └── main.go ├── expr ├── expr.go ├── expr_test.go ├── opcode_name.go └── opcodes.go ├── go.mod ├── go.sum ├── instance.go ├── leb128decode ├── leb128.go └── leb128_test.go ├── linker.go ├── linker_test.go ├── module.go ├── segments ├── code.go ├── code_test.go ├── consts.go ├── data.go ├── data_test.go ├── elem.go ├── elem_test.go ├── export.go ├── export_test.go ├── global.go ├── global_test.go ├── import.go └── import_test.go ├── stacks ├── label_stack.go ├── operand_stack.go ├── stack.go └── stacks_test.go ├── tollstation └── toll.go ├── types ├── func.go ├── func_test.go ├── global.go ├── global_test.go ├── limits.go ├── limits_test.go ├── memory.go ├── memory_test.go ├── table.go ├── table_test.go ├── value.go └── value_test.go ├── utils ├── float.go ├── float_test.go ├── page.go └── ptr.go └── wasm ├── func_host.go ├── func_interface.go ├── func_test.go ├── func_webassembly.go ├── global.go ├── instance.go ├── instance_exec.go ├── instance_init.go ├── instance_test.go ├── instr.go ├── instr_arg.go ├── instr_arg_test.go ├── instr_const.go ├── instr_const_test.go ├── instr_control.go ├── instr_control_test.go ├── instr_mem.go ├── instr_mem_test.go ├── instr_num.go ├── instr_num_test.go ├── instr_var.go ├── instr_var_test.go ├── memory.go ├── module.go ├── section.go └── table.go /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: [master] 5 | defaults: 6 | run: 7 | shell: bash 8 | 9 | jobs: 10 | build: 11 | name: Build ngcore 12 | runs-on: ${{ matrix.os }} 13 | strategy: 14 | matrix: 15 | include: 16 | - build: x86_64-linux 17 | os: ubuntu-latest 18 | - build: x86_64-macos 19 | os: macos-latest 20 | - build: x86_64-windows 21 | os: windows-latest 22 | target: x86_64-pc-windows-gnu 23 | - build: aarch64-linux 24 | os: ubuntu-latest 25 | target: aarch64-unknown-linux-gnu 26 | steps: 27 | - uses: actions/checkout@v3 28 | - uses: actions/setup-go@v3 29 | with: 30 | go-version: '^1.18' 31 | 32 | - name: Analysis wasman 33 | run: go vet -v ./... 34 | 35 | - name: Test wasman 36 | run: go test -v ./... 37 | 38 | - name: Test wasman with race 39 | run: go test -race -v ./... 40 | 41 | - name: Download external files 42 | run: go generate ./examples 43 | 44 | - name: Test log example 45 | run: go run ./examples/log 46 | 47 | - name: Test hoststring example 48 | run: go run ./examples/hoststring 49 | 50 | - name: Test hostbytes example 51 | run: go run ./examples/hostbytes 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .vscode 3 | *.wasm 4 | /wasman 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## v0.1.0 2 | 3 | - initial version 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 @mathetake 4 | Copyright (c) 2020 Command M 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WASMan (WebAssembly Manager) 2 | 3 | [![](https://godoc.org/github.com/c0mm4nd/wasman?status.svg)](http://godoc.org/github.com/c0mm4nd/wasman) 4 | [![Go Report Card](https://goreportcard.com/badge/github.com/c0mm4nd/wasman)](https://goreportcard.com/report/github.com/c0mm4nd/wasman) 5 | ![CI](https://github.com/c0mm4nd/wasman/workflows/CI/badge.svg) 6 | 7 | Another wasm interpreter engine for gophers. 8 | 9 | ## Usage 10 | 11 | ### Executable 12 | 13 | Install 14 | 15 | ```bash 16 | go install github.com/c0mm4nd/wasman/cmd/wasman 17 | ``` 18 | 19 | ```bash 20 | $ wasman -h 21 | Usage of ./wasman: 22 | -extern-files string 23 | external modules files 24 | -func string 25 | main func (default "main") 26 | -main string 27 | main module (default "module.wasm") 28 | -max-toll uint 29 | the maximum toll in simple toll station 30 | ``` 31 | 32 | Example: [numeric.wasm](https://github.com/C0MM4ND/minimum-wasm-rs/releases/latest) 33 | 34 | ```bash 35 | $ wasman -main numeric.wasm -func fib 20 # calc the fibonacci number 36 | { 37 | type: i32 38 | result: 6765 39 | toll: 315822 40 | } 41 | ``` 42 | 43 | If we limit the max toll, it will panic when overflow. 44 | 45 | ```bash 46 | $ wasman -main numeric.wasm -max-toll 300000 -func fib 20 47 | panic: toll overflow 48 | 49 | goroutine 1 [running]: 50 | main.main() 51 | /home/ubuntu/Desktop/wasman/cmd/wasman/main.go:85 +0x87d 52 | ``` 53 | 54 | ### Go Embedding 55 | 56 | [![PkgGoDev](https://pkg.go.dev/badge/github.com/c0mm4nd/wasman)](https://pkg.go.dev/github.com/c0mm4nd/wasman) 57 | 58 | #### Example 59 | 60 | *Look for examples?* 61 | 62 | They are in [examples folder](./examples) 63 | 64 | ## TODOs 65 | 66 | - add more complex examples 67 | -------------------------------------------------------------------------------- /cmd/wasman/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "fmt" 7 | "os" 8 | "strconv" 9 | "strings" 10 | 11 | "github.com/c0mm4nd/wasman" 12 | "github.com/c0mm4nd/wasman/config" 13 | "github.com/c0mm4nd/wasman/tollstation" 14 | ) 15 | 16 | var strMainModuleFile = flag.String("main", "module.wasm", "main module") 17 | 18 | var funcName = flag.String("func", "main", "main func") 19 | var maxToll = flag.Uint64("max-toll", 0, "the maximum toll in simple toll station") 20 | 21 | var strExternModules = flag.String("extern-files", "", "external modules files") 22 | 23 | var stdout = os.Stdout // for wasi 24 | 25 | func main() { 26 | flag.Parse() 27 | 28 | externModules := strings.Split(*strExternModules, ",") 29 | f, err := os.Open(*strMainModuleFile) 30 | if err != nil { 31 | panic(err) 32 | } 33 | 34 | mainMod, err := wasman.NewModule(config.ModuleConfig{ 35 | DisableFloatPoint: false, 36 | TollStation: tollstation.NewSimpleTollStation(*maxToll), 37 | }, f) 38 | if err != nil { 39 | panic(err) 40 | } 41 | 42 | externMods := make(map[string]*wasman.Module) 43 | for _, pair := range externModules { 44 | if pair == "" { 45 | continue 46 | } 47 | 48 | li := strings.Split(pair, ":") 49 | if len(li) != 2 { 50 | panic("invalid external module: should input with -extern=:,:") 51 | } 52 | 53 | f, err := os.Open(li[1]) 54 | if err != nil { 55 | panic(err) 56 | } 57 | 58 | mod, err := wasman.NewModule(config.ModuleConfig{}, f) 59 | if err != nil { 60 | panic(err) 61 | } 62 | 63 | externMods[li[0]] = mod 64 | } 65 | 66 | l := wasman.NewLinkerWithModuleMap(config.LinkerConfig{}, externMods) 67 | ins, err := l.Instantiate(mainMod) 68 | if err != nil { 69 | panic(err) 70 | } 71 | 72 | args := make([]uint64, 0) 73 | for _, strArg := range flag.Args() { 74 | if len(strArg) == 0 { 75 | continue 76 | } 77 | 78 | arg, err := strconv.ParseUint(strArg, 10, 64) 79 | if err != nil { 80 | panic(err) 81 | } 82 | 83 | args = append(args, arg) 84 | } 85 | 86 | r, ty, err := ins.CallExportedFunc(*funcName, args...) 87 | if err != nil { 88 | panic(err) 89 | } 90 | 91 | toll := uint64(0) 92 | if ins.ModuleConfig.TollStation != nil { 93 | toll = ins.ModuleConfig.TollStation.GetToll() 94 | } 95 | 96 | result := struct { 97 | Type string `json:"type"` 98 | Result interface{} `json:"result"` 99 | Toll uint64 `json:"toll"` 100 | }{ 101 | "", 102 | nil, 103 | toll, 104 | } 105 | 106 | if r != nil { 107 | result.Type = ty[0].String() 108 | result.Result = r[0] 109 | } 110 | 111 | out, _ := json.MarshalIndent(result, "", " ") 112 | 113 | fmt.Printf(string(out)) 114 | } 115 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/c0mm4nd/wasman/tollstation" 7 | ) 8 | 9 | const ( 10 | // MemoryPageSize is the unit of memory length in WebAssembly, 11 | // and is defined as 2^16 = 65536. 12 | // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#memory-instances%E2%91%A0 13 | DefaultMemoryPageSize = 65536 14 | // MemoryMaxPages is maximum number of pages defined (2^16). 15 | // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#grow-mem 16 | DefaultMemoryMaxPages = 65536 17 | // MemoryPageSizeInBits satisfies the relation: "1 << MemoryPageSizeInBits == MemoryPageSize". 18 | DefaultMemoryPageSizeInBits = 16 19 | ) 20 | 21 | var ( 22 | // ErrShadowing wont appear if LinkerConfig.DisableShadowing is default false 23 | ErrShadowing = errors.New("shadowing is disabled") 24 | ) 25 | 26 | // ModuleConfig is the config applied to the wasman.Module 27 | type ModuleConfig struct { 28 | DisableFloatPoint bool 29 | TollStation tollstation.TollStation 30 | CallDepthLimit *uint64 31 | Recover bool // avoid panic inside vm 32 | } 33 | 34 | // LinkerConfig is the config applied to the wasman.Linker 35 | type LinkerConfig struct { 36 | DisableShadowing bool // false by default 37 | } 38 | -------------------------------------------------------------------------------- /config/wasi.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | // WASIConfig : TODO 4 | type WASIConfig struct { 5 | } 6 | -------------------------------------------------------------------------------- /examples/downloader.go: -------------------------------------------------------------------------------- 1 | //go:generate curl -O -L https://github.com/C0MM4ND/minimum-wasm-rs/releases/download/292d24e4/hoststring.wasm 2 | //go:generate curl -O -L https://github.com/C0MM4ND/minimum-wasm-rs/releases/download/292d24e4/log.wasm 3 | //go:generate curl -O -L https://github.com/C0MM4ND/minimum-wasm-rs/releases/download/292d24e4/numeric.wasm 4 | //go:generate curl -O -L https://github.com/C0MM4ND/minimum-wasm-rs/releases/download/292d24e4/hostbytes.wasm 5 | package main 6 | 7 | func main() {} 8 | -------------------------------------------------------------------------------- /examples/hostbytes/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "C" 4 | import ( 5 | "fmt" 6 | "os" 7 | 8 | "github.com/c0mm4nd/wasman" 9 | "github.com/c0mm4nd/wasman/config" 10 | ) 11 | 12 | // Run me on root folder 13 | // go run ./examples/hostbytes 14 | func main() { 15 | linker1 := wasman.NewLinker(config.LinkerConfig{}) 16 | 17 | message1 := []byte{0xDE, 0xAD, 0x00, 0xBE, 0xEF, 0x00, 0xBA, 0xAD, 0x00, 0xF0, 0x0D} 18 | 19 | err := linker1.DefineAdvancedFunc("env", "get_host_bytes_size", func(ins *wasman.Instance) interface{} { 20 | return func() uint32 { 21 | return uint32(len(message1)) 22 | } 23 | }) 24 | if err != nil { 25 | panic(err) 26 | } 27 | 28 | err = linker1.DefineAdvancedFunc("env", "get_host_bytes", func(ins *wasman.Instance) interface{} { 29 | return func(ptr uint32) { 30 | copy(ins.Memory.Value[ptr:], message1) 31 | } 32 | }) 33 | if err != nil { 34 | panic(err) 35 | } 36 | 37 | message2 := append(message1, message1...) 38 | 39 | err = linker1.DefineAdvancedFunc("env", "get_host_bytes_with_buffer", func(ins *wasman.Instance) interface{} { 40 | return func(index uint32, ptr uint32) uint32 { 41 | if index == 0 { 42 | message2 = append(message1, message1...) // reset the value 43 | } 44 | 45 | length := copy(ins.Memory.Value[ptr:], message2) 46 | message2 = message2[length:] 47 | 48 | return uint32(length) 49 | } 50 | }) 51 | if err != nil { 52 | panic(err) 53 | } 54 | 55 | // cannot call host func in the host func 56 | err = linker1.DefineAdvancedFunc("env", "log_message", func(ins *wasman.Instance) interface{} { 57 | return func(ptr uint32, l uint32) { 58 | // string way 59 | // fmt.Println(C.GoString((*C.char)(unsafe.Pointer(&ins.Memory.Value[ptr])))) // not good for bytes 60 | 61 | // bytes way 62 | msg := ins.Memory.Value[ptr : ptr+l] 63 | fmt.Printf("%x\n", msg) 64 | } 65 | }) 66 | if err != nil { 67 | panic(err) 68 | } 69 | 70 | f, err := os.Open("examples/hostbytes.wasm") 71 | if err != nil { 72 | panic(err) 73 | } 74 | 75 | module, err := wasman.NewModule(config.ModuleConfig{}, f) 76 | if err != nil { 77 | panic(err) 78 | } 79 | ins, err := linker1.Instantiate(module) 80 | if err != nil { 81 | panic(err) 82 | } 83 | 84 | for range make([]byte, 999) { 85 | _, _, err = ins.CallExportedFunc("greet_with_size") 86 | if err != nil { 87 | panic(err) 88 | } 89 | 90 | _, _, err = ins.CallExportedFunc("greet_with_buffer") 91 | if err != nil { 92 | panic(err) 93 | } 94 | } 95 | 96 | fmt.Println("mem size", len(ins.Memory.Value)) 97 | } 98 | -------------------------------------------------------------------------------- /examples/hoststring/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "C" 4 | import ( 5 | "fmt" 6 | "os" 7 | "unsafe" 8 | 9 | "github.com/c0mm4nd/wasman" 10 | "github.com/c0mm4nd/wasman/config" 11 | ) 12 | 13 | // Run me on root folder 14 | // go run ./examples/hoststring 15 | func main() { 16 | linker1 := wasman.NewLinker(config.LinkerConfig{}) 17 | 18 | //err := linker1.DefineMemory("env", "memory", make([]byte, 10)) 19 | 20 | err := linker1.DefineAdvancedFunc("env", "host_string", func(ins *wasman.Instance) interface{} { 21 | return func() uint32 { 22 | message := "WASMan" 23 | 24 | ret, _, err := ins.CallExportedFunc("allocate", uint64(len(message)+1)) 25 | if err != nil { 26 | panic(err) 27 | } 28 | 29 | copy(ins.Memory.Value[ret[0]:], append([]byte(message), byte(0))) // act as a string for rust's CStr::from_ptr(ptr) 30 | 31 | return uint32(ret[0]) 32 | } 33 | }) 34 | if err != nil { 35 | panic(err) 36 | } 37 | 38 | // cannot call host func in the host func 39 | err = linker1.DefineAdvancedFunc("env", "log_message", func(ins *wasman.Instance) interface{} { 40 | return func(ptr uint32, l uint32) { 41 | // string way 42 | fmt.Println(C.GoString((*C.char)(unsafe.Pointer(&ins.Memory.Value[ptr])))) 43 | 44 | // bytes way 45 | msg := ins.Memory.Value[ptr : ptr+l] 46 | fmt.Println(string(msg)) 47 | } 48 | }) 49 | if err != nil { 50 | panic(err) 51 | } 52 | 53 | f, err := os.Open("examples/hoststring.wasm") 54 | if err != nil { 55 | panic(err) 56 | } 57 | 58 | module, err := wasman.NewModule(config.ModuleConfig{}, f) 59 | if err != nil { 60 | panic(err) 61 | } 62 | ins, err := linker1.Instantiate(module) 63 | if err != nil { 64 | panic(err) 65 | } 66 | 67 | for range make([]byte, 999) { 68 | _, _, err = ins.CallExportedFunc("greet") 69 | if err != nil { 70 | panic(err) 71 | } 72 | } 73 | 74 | fmt.Println("mem size", len(ins.Memory.Value)) 75 | } 76 | -------------------------------------------------------------------------------- /examples/log/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "C" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/c0mm4nd/wasman" 9 | "github.com/c0mm4nd/wasman/config" 10 | ) 11 | import "unsafe" 12 | 13 | // Run me on root folder 14 | // go run ./examples/log 15 | func main() { 16 | linker1 := wasman.NewLinker(config.LinkerConfig{}) 17 | 18 | // cannot call host func in the host func 19 | err := linker1.DefineAdvancedFunc("env", "log_message", func(ins *wasman.Instance) interface{} { 20 | return func(ptr uint32, l uint32) { 21 | // need ptr & l 22 | messageByLen := ins.Memory.Value[int(ptr):int(ptr+l)] 23 | fmt.Println(string(messageByLen)) 24 | 25 | // this method just need one ptr 26 | messageByCharVec := C.GoString((*C.char)(unsafe.Pointer(&ins.Memory.Value[ptr]))) 27 | fmt.Println(messageByCharVec) 28 | } 29 | }) 30 | if err != nil { 31 | panic(err) 32 | } 33 | 34 | wasm, err := os.Open("examples/log.wasm") 35 | if err != nil { 36 | panic(err) 37 | } 38 | 39 | module, err := wasman.NewModule(config.ModuleConfig{}, wasm) 40 | if err != nil { 41 | panic(err) 42 | } 43 | ins, err := linker1.Instantiate(module) 44 | if err != nil { 45 | panic(err) 46 | } 47 | 48 | name := "wasman engine" 49 | ret, _, err := ins.CallExportedFunc("allocate", uint64(len(name)+1)) 50 | if err != nil { 51 | panic(err) 52 | } 53 | 54 | ptr := ret[0] 55 | 56 | copy(ins.Memory.Value[ptr:], name) 57 | 58 | for range make([]byte, 100) { 59 | _, _, err = ins.CallExportedFunc("greet", ptr) 60 | if err != nil { 61 | panic(err) 62 | } 63 | } 64 | 65 | fmt.Println("mem size", len(ins.Memory.Value)) 66 | 67 | } 68 | -------------------------------------------------------------------------------- /examples/numeric/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "C" 4 | import ( 5 | "fmt" 6 | "os" 7 | 8 | "github.com/c0mm4nd/wasman" 9 | "github.com/c0mm4nd/wasman/config" 10 | ) 11 | 12 | func FibonacciRecursion(n int) int { 13 | if n <= 1 { 14 | return n 15 | } 16 | return FibonacciRecursion(n-1) + FibonacciRecursion(n-2) 17 | } 18 | 19 | // Run me on root folder 20 | // go run ./examples/hoststring 21 | func main() { 22 | linker1 := wasman.NewLinker(config.LinkerConfig{}) 23 | 24 | f, err := os.Open("examples/numeric.wasm") 25 | if err != nil { 26 | panic(err) 27 | } 28 | 29 | module, err := wasman.NewModule(config.ModuleConfig{}, f) 30 | if err != nil { 31 | panic(err) 32 | } 33 | ins, err := linker1.Instantiate(module) 34 | if err != nil { 35 | panic(err) 36 | } 37 | 38 | for i := 0; i <= 20; i++ { 39 | returns, _, err := ins.CallExportedFunc("fib", uint64(i)) 40 | if err != nil { 41 | panic(err) 42 | } 43 | 44 | fmt.Printf("fib(%d) is %d: got %d \n", i, FibonacciRecursion(i), returns[0]) 45 | 46 | } 47 | 48 | fmt.Println("mem size", len(ins.Memory.Value)) 49 | } 50 | -------------------------------------------------------------------------------- /expr/expr.go: -------------------------------------------------------------------------------- 1 | package expr 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | 7 | "github.com/c0mm4nd/wasman/leb128decode" 8 | "github.com/c0mm4nd/wasman/types" 9 | "github.com/c0mm4nd/wasman/utils" 10 | ) 11 | 12 | // Expression is sequences of instructions terminated by an end marker. 13 | type Expression struct { 14 | OpCode OpCode 15 | Data []byte 16 | } 17 | 18 | // ReadExpression will read an expr.Expression from the io.Reader 19 | func ReadExpression(r *bytes.Reader) (*Expression, error) { 20 | b, err := r.ReadByte() 21 | if err != nil { 22 | return nil, fmt.Errorf("read opcode: %v", err) 23 | } 24 | 25 | remainingBeforeData := int64(r.Len()) 26 | offsetAtData := r.Size() - remainingBeforeData 27 | 28 | op := OpCode(b) 29 | 30 | switch op { 31 | case OpCodeI32Const: 32 | _, _, err = leb128decode.DecodeInt32(r) 33 | case OpCodeI64Const: 34 | _, _, err = leb128decode.DecodeInt64(r) 35 | case OpCodeF32Const: 36 | _, err = utils.ReadFloat32(r) 37 | case OpCodeF64Const: 38 | _, err = utils.ReadFloat64(r) 39 | case OpCodeGlobalGet: 40 | _, _, err = leb128decode.DecodeUint32(r) 41 | default: 42 | return nil, fmt.Errorf("%v for opcodes.OpCode: %#x", types.ErrInvalidTypeByte, b) 43 | } 44 | 45 | if err != nil { 46 | return nil, fmt.Errorf("read value: %v", err) 47 | } 48 | 49 | if b, err = r.ReadByte(); err != nil { 50 | return nil, fmt.Errorf("look for end opcode: %v", err) 51 | } 52 | 53 | if b != byte(OpCodeEnd) { 54 | return nil, fmt.Errorf("constant expression has not terminated") 55 | } 56 | 57 | data := make([]byte, remainingBeforeData-int64(r.Len())-1) 58 | if _, err := r.ReadAt(data, offsetAtData); err != nil { 59 | return nil, fmt.Errorf("error re-buffering Expression Data") 60 | } 61 | 62 | return &Expression{ 63 | OpCode: op, 64 | Data: data, 65 | }, nil 66 | } 67 | -------------------------------------------------------------------------------- /expr/expr_test.go: -------------------------------------------------------------------------------- 1 | package expr_test 2 | 3 | import ( 4 | "bytes" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/c0mm4nd/wasman/expr" 9 | ) 10 | 11 | func TestReadExpr(t *testing.T) { 12 | t.Run("error", func(t *testing.T) { 13 | for _, b := range [][]byte{ 14 | {}, {0xaa}, {0x41, 0x1}, {0x41, 0x01, 0x41}, // all invalid 15 | } { 16 | _, err := expr.ReadExpression(bytes.NewReader(b)) 17 | t.Log(err) 18 | } 19 | }) 20 | 21 | t.Run("ok", func(t *testing.T) { 22 | for _, c := range []struct { 23 | bytes []byte 24 | exp *expr.Expression 25 | }{ 26 | { 27 | bytes: []byte{0x42, 0x01, 0x0b}, 28 | exp: &expr.Expression{OpCode: expr.OpCodeI64Const, Data: []byte{0x01}}, 29 | }, 30 | { 31 | bytes: []byte{0x43, 0x40, 0xe1, 0x47, 0x40, 0x0b}, 32 | exp: &expr.Expression{OpCode: expr.OpCodeF32Const, Data: []byte{0x40, 0xe1, 0x47, 0x40}}, 33 | }, 34 | { 35 | bytes: []byte{0x23, 0x01, 0x0b}, 36 | exp: &expr.Expression{OpCode: expr.OpCodeGlobalGet, Data: []byte{0x01}}, 37 | }, 38 | } { 39 | actual, err := expr.ReadExpression(bytes.NewReader(c.bytes)) 40 | if err != nil { 41 | t.Fail() 42 | } 43 | if !reflect.DeepEqual(c.exp, actual) { 44 | t.Fail() 45 | } 46 | } 47 | }) 48 | } 49 | -------------------------------------------------------------------------------- /expr/opcode_name.go: -------------------------------------------------------------------------------- 1 | package expr 2 | 3 | var names map[OpCode]string // on load 4 | 5 | func GetOpCodeName(op OpCode) string { 6 | if names == nil { 7 | names = map[OpCode]string{ 8 | OpCodeUnreachable: "Unreachable", 9 | OpCodeNop: "Nop", 10 | OpCodeBlock: "Block", 11 | OpCodeLoop: "Loop", 12 | OpCodeIf: "If", 13 | OpCodeElse: "Else", 14 | OpCodeEnd: "End", 15 | OpCodeBr: "Br", 16 | OpCodeBrIf: "BrIf", 17 | OpCodeBrTable: "BrTable", 18 | OpCodeReturn: "Return", 19 | OpCodeCall: "Call", 20 | OpCodeCallIndirect: "CallIndirect", 21 | 22 | // parametric instruction 23 | OpCodeDrop: "Drop", 24 | OpCodeSelect: "Select", 25 | 26 | // variable instruction 27 | OpCodeLocalGet: "LocalGet", 28 | OpCodeLocalSet: "LocalSet", 29 | OpCodeLocalTee: "LocalTee", 30 | OpCodeGlobalGet: "GlobalGet", 31 | OpCodeGlobalSet: "GlobalSet", 32 | 33 | // memory instruction 34 | OpCodeI32Load: "I32Load", 35 | OpCodeI64Load: "I64Load", 36 | OpCodeF32Load: "F32Load", 37 | OpCodeF64Load: "F64Load", 38 | OpCodeI32Load8s: "I32Load8s", 39 | OpCodeI32Load8u: "I32Load8u", 40 | OpCodeI32Load16s: "I32Load16s", 41 | OpCodeI32Load16u: "I32Load16u", 42 | OpCodeI64Load8s: "I64Load8s", 43 | OpCodeI64Load8u: "I64Load8u", 44 | OpCodeI64Load16s: "I64Load16s", 45 | OpCodeI64Load16u: "I64Load16u", 46 | OpCodeI64Load32s: "I64Load32s", 47 | OpCodeI64Load32u: "I64Load32u", 48 | OpCodeI32Store: "I32Store", 49 | OpCodeI64Store: "I64Store", 50 | OpCodeF32Store: "F32Store", 51 | OpCodeF64Store: "F64Store", 52 | OpCodeI32Store8: "I32Store8", 53 | OpCodeI32Store16: "I32Store16", 54 | OpCodeI64Store8: "I64Store8", 55 | OpCodeI64Store16: "I64Store16", 56 | OpCodeI64Store32: "I64Store32", 57 | OpCodeMemorySize: "MemorySize", 58 | OpCodeMemoryGrow: "MemoryGrow", 59 | 60 | // numeric instruction 61 | OpCodeI32Const: "I32Const", 62 | OpCodeI64Const: "I64Const", 63 | OpCodeF32Const: "F32Const", 64 | OpCodeF64Const: "F64Const", 65 | 66 | OpCodeI32Eqz: "I32Eqz", 67 | OpCodeI32Eq: "I32Eq", 68 | OpCodeI32Ne: "I32Ne", 69 | OpCodeI32LtS: "I32LtS", 70 | OpCodeI32LtU: "I32LtU", 71 | OpCodeI32GtS: "I32GtS", 72 | OpCodeI32GtU: "I32GtU", 73 | OpCodeI32LeS: "I32LeS", 74 | OpCodeI32LeU: "I32LeU", 75 | OpCodeI32GeS: "I32GeS", 76 | OpCodeI32GeU: "I32GeU", 77 | 78 | OpCodeI64Eqz: "I64Eqz", 79 | OpCodeI64Eq: "I64Eq", 80 | OpCodeI64Ne: "I64Ne", 81 | OpCodeI64LtS: "I64LtS", 82 | OpCodeI64LtU: "I64LtU", 83 | OpCodeI64GtS: "I64GtS", 84 | OpCodeI64GtU: "I64GtU", 85 | OpCodeI64LeS: "I64LeS", 86 | OpCodeI64LeU: "I64LeU", 87 | OpCodeI64GeS: "I64GeS", 88 | OpCodeI64GeU: "I64GeU", 89 | 90 | OpCodeF32Eq: "F32Eq", 91 | OpCodeF32Ne: "F32Ne", 92 | OpCodeF32Lt: "F32Lt", 93 | OpCodeF32Gt: "F32Gt", 94 | OpCodeF32Le: "F32Le", 95 | OpCodeF32Ge: "F32Ge", 96 | 97 | OpCodeF64Eq: "F64Eq", 98 | OpCodeF64Ne: "F64Ne", 99 | OpCodeF64Lt: "F64Lt", 100 | OpCodeF64Gt: "F64Gt", 101 | OpCodeF64Le: "F64Le", 102 | OpCodeF64Ge: "F64Ge", 103 | 104 | OpCodeI32Clz: "I32Clz", 105 | OpCodeI32Ctz: "I32Ctz", 106 | OpCodeI32PopCnt: "I32PopCnt", 107 | OpCodeI32Add: "I32Add", 108 | OpCodeI32Sub: "I32Sub", 109 | OpCodeI32Mul: "I32Mul", 110 | OpCodeI32DivS: "I32DivS", 111 | OpCodeI32DivU: "I32DivU", 112 | OpCodeI32RemS: "I32RemS", 113 | OpCodeI32RemU: "I32RemU", 114 | OpCodeI32And: "I32And", 115 | OpCodeI32Or: "I32Or", 116 | OpCodeI32Xor: "I32Xor", 117 | OpCodeI32Shl: "I32Shl", 118 | OpCodeI32ShrS: "I32ShrS", 119 | OpCodeI32ShrU: "I32ShrU", 120 | OpCodeI32RotL: "I32RotL", 121 | OpCodeI32RotR: "I32RotR", 122 | 123 | OpCodeI64Clz: "I64Clz", 124 | OpCodeI64Ctz: "I64Ctz", 125 | OpCodeI64PopCnt: "I64PopCnt", 126 | OpCodeI64Add: "I64Add", 127 | OpCodeI64Sub: "I64Sub", 128 | OpCodeI64Mul: "I64Mul", 129 | OpCodeI64DivS: "I64DivS", 130 | OpCodeI64DivU: "I64DivU", 131 | OpCodeI64RemS: "I64RemS", 132 | OpCodeI64RemU: "I64RemU", 133 | OpCodeI64And: "I64And", 134 | OpCodeI64Or: "I64Or", 135 | OpCodeI64Xor: "I64Xor", 136 | OpCodeI64Shl: "I64Shl", 137 | OpCodeI64ShrS: "I64ShrS", 138 | OpCodeI64ShrU: "I64ShrU", 139 | OpCodeI64RotL: "I64RotL", 140 | OpCodeI64RotR: "I64RotR", 141 | 142 | OpCodeF32Abs: "F32Abs", 143 | OpCodeF32Neg: "F32Neg", 144 | OpCodeF32Ceil: "F32Ceil", 145 | OpCodeF32Floor: "F32Floor", 146 | OpCodeF32Trunc: "F32Trunc", 147 | OpCodeF32Nearest: "F32Nearest", 148 | OpCodeF32Sqrt: "F32Sqrt", 149 | OpCodeF32Add: "F32Add", 150 | OpCodeF32Sub: "F32Sub", 151 | OpCodeF32Mul: "F32Mul", 152 | OpCodeF32Div: "F32Div", 153 | OpCodeF32Min: "F32Min", 154 | OpCodeF32Max: "F32Max", 155 | OpCodeF32CopySign: "F32CopySign", 156 | 157 | OpCodeF64Abs: "F64Abs", 158 | OpCodeF64Neg: "F64Neg", 159 | OpCodeF64Ceil: "F64Ceil", 160 | OpCodeF64Floor: "F64Floor", 161 | OpCodeF64Trunc: "F64Trunc", 162 | OpCodeF64Nearest: "F64Nearest", 163 | OpCodeF64Sqrt: "F64Sqrt", 164 | OpCodeF64Add: "F64Add", 165 | OpCodeF64Sub: "F64Sub", 166 | OpCodeF64Mul: "F64Mul", 167 | OpCodeF64Div: "F64Div", 168 | OpCodeF64Min: "F64Min", 169 | OpCodeF64Max: "F64Max", 170 | OpCodeF64CopySign: "F64CopySign", 171 | 172 | OpCodeI32WrapI64: "I32WrapI64", 173 | OpCodeI32TruncF32S: "I32TruncF32S", 174 | OpCodeI32TruncF32U: "I32TruncF32U", 175 | OpCodeI32truncF64S: "I32truncF64S", 176 | OpCodeI32truncF64U: "I32truncF64U", 177 | 178 | OpCodeI64ExtendI32S: "I64ExtendI32S", 179 | OpCodeI64ExtendI32U: "I64ExtendI32U", 180 | OpCodeI64TruncF32S: "I64TruncF32S", 181 | OpCodeI64TruncF32U: "I64TruncF32U", 182 | OpCodeI64TruncF64S: "I64TruncF64S", 183 | OpCodeI64TruncF64U: "I64TruncF64U", 184 | 185 | OpCodeF32ConvertI32S: "F32ConvertI32S", 186 | OpCodeF32ConvertI32U: "F32ConvertI32U", 187 | OpCodeF32ConvertI64S: "F32ConvertI64S", 188 | OpCodeF32ConvertI64U: "F32ConvertI64U", 189 | OpCodeF32DemoteF64: "F32DemoteF64", 190 | 191 | OpCodeF64ConvertI32S: "F64ConvertI32S", 192 | OpCodeF64ConvertI32U: "F64ConvertI32U", 193 | OpCodeF64ConvertI64S: "F64ConvertI64S", 194 | OpCodeF64ConvertI64U: "F64ConvertI64U", 195 | OpCodeF64PromoteF32: "F64PromoteF32", 196 | 197 | OpCodeI32ReinterpretF32: "I32ReinterpretF32", 198 | OpCodeI64ReinterpretF64: "I64ReinterpretF64", 199 | OpCodeF32ReinterpretI32: "F32ReinterpretI32", 200 | OpCodeF64ReinterpretI64: "F64ReinterpretI64", 201 | 202 | OpCodeI32Extend8S: "I32Extend8S", 203 | OpCodeI32Extend16S: "I32Extend16S", 204 | OpCodeI64Extend8S: "I64Extend8S", 205 | OpCodeI64Extend16S: "I64Extend16S", 206 | OpCodeI64Extend32S: "I64Extend32S", 207 | 208 | OpCodeNull: "Null", 209 | OpCodeIsNull: "IsNull", 210 | OpCodeFunc: "Func", 211 | } 212 | 213 | } 214 | 215 | return names[op] 216 | } 217 | -------------------------------------------------------------------------------- /expr/opcodes.go: -------------------------------------------------------------------------------- 1 | package expr 2 | 3 | type OpCode = byte 4 | 5 | const ( 6 | // control instruction 7 | OpCodeUnreachable OpCode = 0x00 8 | OpCodeNop OpCode = 0x01 9 | OpCodeBlock OpCode = 0x02 10 | OpCodeLoop OpCode = 0x03 11 | OpCodeIf OpCode = 0x04 12 | OpCodeElse OpCode = 0x05 13 | OpCodeEnd OpCode = 0x0b 14 | OpCodeBr OpCode = 0x0c 15 | OpCodeBrIf OpCode = 0x0d 16 | OpCodeBrTable OpCode = 0x0e 17 | OpCodeReturn OpCode = 0x0f 18 | OpCodeCall OpCode = 0x10 19 | OpCodeCallIndirect OpCode = 0x11 20 | 21 | // parametric instruction 22 | OpCodeDrop OpCode = 0x1a 23 | OpCodeSelect OpCode = 0x1b 24 | 25 | // variable instruction 26 | OpCodeLocalGet OpCode = 0x20 27 | OpCodeLocalSet OpCode = 0x21 28 | OpCodeLocalTee OpCode = 0x22 29 | OpCodeGlobalGet OpCode = 0x23 30 | OpCodeGlobalSet OpCode = 0x24 31 | 32 | // memory instruction 33 | OpCodeI32Load OpCode = 0x28 34 | OpCodeI64Load OpCode = 0x29 35 | OpCodeF32Load OpCode = 0x2a 36 | OpCodeF64Load OpCode = 0x2b 37 | OpCodeI32Load8s OpCode = 0x2c 38 | OpCodeI32Load8u OpCode = 0x2d 39 | OpCodeI32Load16s OpCode = 0x2e 40 | OpCodeI32Load16u OpCode = 0x2f 41 | OpCodeI64Load8s OpCode = 0x30 42 | OpCodeI64Load8u OpCode = 0x31 43 | OpCodeI64Load16s OpCode = 0x32 44 | OpCodeI64Load16u OpCode = 0x33 45 | OpCodeI64Load32s OpCode = 0x34 46 | OpCodeI64Load32u OpCode = 0x35 47 | OpCodeI32Store OpCode = 0x36 48 | OpCodeI64Store OpCode = 0x37 49 | OpCodeF32Store OpCode = 0x38 50 | OpCodeF64Store OpCode = 0x39 51 | OpCodeI32Store8 OpCode = 0x3a 52 | OpCodeI32Store16 OpCode = 0x3b 53 | OpCodeI64Store8 OpCode = 0x3c 54 | OpCodeI64Store16 OpCode = 0x3d 55 | OpCodeI64Store32 OpCode = 0x3e 56 | OpCodeMemorySize OpCode = 0x3f 57 | OpCodeMemoryGrow OpCode = 0x40 58 | 59 | // numeric instruction 60 | OpCodeI32Const OpCode = 0x41 61 | OpCodeI64Const OpCode = 0x42 62 | OpCodeF32Const OpCode = 0x43 63 | OpCodeF64Const OpCode = 0x44 64 | 65 | OpCodeI32Eqz OpCode = 0x45 66 | OpCodeI32Eq OpCode = 0x46 67 | OpCodeI32Ne OpCode = 0x47 68 | OpCodeI32LtS OpCode = 0x48 69 | OpCodeI32LtU OpCode = 0x49 70 | OpCodeI32GtS OpCode = 0x4a 71 | OpCodeI32GtU OpCode = 0x4b 72 | OpCodeI32LeS OpCode = 0x4c 73 | OpCodeI32LeU OpCode = 0x4d 74 | OpCodeI32GeS OpCode = 0x4e 75 | OpCodeI32GeU OpCode = 0x4f 76 | 77 | OpCodeI64Eqz OpCode = 0x50 78 | OpCodeI64Eq OpCode = 0x51 79 | OpCodeI64Ne OpCode = 0x52 80 | OpCodeI64LtS OpCode = 0x53 81 | OpCodeI64LtU OpCode = 0x54 82 | OpCodeI64GtS OpCode = 0x55 83 | OpCodeI64GtU OpCode = 0x56 84 | OpCodeI64LeS OpCode = 0x57 85 | OpCodeI64LeU OpCode = 0x58 86 | OpCodeI64GeS OpCode = 0x59 87 | OpCodeI64GeU OpCode = 0x5a 88 | 89 | OpCodeF32Eq OpCode = 0x5b 90 | OpCodeF32Ne OpCode = 0x5c 91 | OpCodeF32Lt OpCode = 0x5d 92 | OpCodeF32Gt OpCode = 0x5e 93 | OpCodeF32Le OpCode = 0x5f 94 | OpCodeF32Ge OpCode = 0x60 95 | 96 | OpCodeF64Eq OpCode = 0x61 97 | OpCodeF64Ne OpCode = 0x62 98 | OpCodeF64Lt OpCode = 0x63 99 | OpCodeF64Gt OpCode = 0x64 100 | OpCodeF64Le OpCode = 0x65 101 | OpCodeF64Ge OpCode = 0x66 102 | 103 | OpCodeI32Clz OpCode = 0x67 104 | OpCodeI32Ctz OpCode = 0x68 105 | OpCodeI32PopCnt OpCode = 0x69 106 | OpCodeI32Add OpCode = 0x6a 107 | OpCodeI32Sub OpCode = 0x6b 108 | OpCodeI32Mul OpCode = 0x6c 109 | OpCodeI32DivS OpCode = 0x6d 110 | OpCodeI32DivU OpCode = 0x6e 111 | OpCodeI32RemS OpCode = 0x6f 112 | OpCodeI32RemU OpCode = 0x70 113 | OpCodeI32And OpCode = 0x71 114 | OpCodeI32Or OpCode = 0x72 115 | OpCodeI32Xor OpCode = 0x73 116 | OpCodeI32Shl OpCode = 0x74 117 | OpCodeI32ShrS OpCode = 0x75 118 | OpCodeI32ShrU OpCode = 0x76 119 | OpCodeI32RotL OpCode = 0x77 120 | OpCodeI32RotR OpCode = 0x78 121 | 122 | OpCodeI64Clz OpCode = 0x79 123 | OpCodeI64Ctz OpCode = 0x7a 124 | OpCodeI64PopCnt OpCode = 0x7b 125 | OpCodeI64Add OpCode = 0x7c 126 | OpCodeI64Sub OpCode = 0x7d 127 | OpCodeI64Mul OpCode = 0x7e 128 | OpCodeI64DivS OpCode = 0x7f 129 | OpCodeI64DivU OpCode = 0x80 130 | OpCodeI64RemS OpCode = 0x81 131 | OpCodeI64RemU OpCode = 0x82 132 | OpCodeI64And OpCode = 0x83 133 | OpCodeI64Or OpCode = 0x84 134 | OpCodeI64Xor OpCode = 0x85 135 | OpCodeI64Shl OpCode = 0x86 136 | OpCodeI64ShrS OpCode = 0x87 137 | OpCodeI64ShrU OpCode = 0x88 138 | OpCodeI64RotL OpCode = 0x89 139 | OpCodeI64RotR OpCode = 0x8a 140 | 141 | OpCodeF32Abs OpCode = 0x8b 142 | OpCodeF32Neg OpCode = 0x8c 143 | OpCodeF32Ceil OpCode = 0x8d 144 | OpCodeF32Floor OpCode = 0x8e 145 | OpCodeF32Trunc OpCode = 0x8f 146 | OpCodeF32Nearest OpCode = 0x90 147 | OpCodeF32Sqrt OpCode = 0x91 148 | OpCodeF32Add OpCode = 0x92 149 | OpCodeF32Sub OpCode = 0x93 150 | OpCodeF32Mul OpCode = 0x94 151 | OpCodeF32Div OpCode = 0x95 152 | OpCodeF32Min OpCode = 0x96 153 | OpCodeF32Max OpCode = 0x97 154 | OpCodeF32CopySign OpCode = 0x98 155 | 156 | OpCodeF64Abs OpCode = 0x99 157 | OpCodeF64Neg OpCode = 0x9a 158 | OpCodeF64Ceil OpCode = 0x9b 159 | OpCodeF64Floor OpCode = 0x9c 160 | OpCodeF64Trunc OpCode = 0x9d 161 | OpCodeF64Nearest OpCode = 0x9e 162 | OpCodeF64Sqrt OpCode = 0x9f 163 | OpCodeF64Add OpCode = 0xa0 164 | OpCodeF64Sub OpCode = 0xa1 165 | OpCodeF64Mul OpCode = 0xa2 166 | OpCodeF64Div OpCode = 0xa3 167 | OpCodeF64Min OpCode = 0xa4 168 | OpCodeF64Max OpCode = 0xa5 169 | OpCodeF64CopySign OpCode = 0xa6 170 | 171 | OpCodeI32WrapI64 OpCode = 0xa7 172 | OpCodeI32TruncF32S OpCode = 0xa8 173 | OpCodeI32TruncF32U OpCode = 0xa9 174 | OpCodeI32truncF64S OpCode = 0xaa 175 | OpCodeI32truncF64U OpCode = 0xab 176 | 177 | OpCodeI64ExtendI32S OpCode = 0xac 178 | OpCodeI64ExtendI32U OpCode = 0xad 179 | OpCodeI64TruncF32S OpCode = 0xae 180 | OpCodeI64TruncF32U OpCode = 0xaf 181 | OpCodeI64TruncF64S OpCode = 0xb0 182 | OpCodeI64TruncF64U OpCode = 0xb1 183 | 184 | OpCodeF32ConvertI32S OpCode = 0xb2 185 | OpCodeF32ConvertI32U OpCode = 0xb3 186 | OpCodeF32ConvertI64S OpCode = 0xb4 187 | OpCodeF32ConvertI64U OpCode = 0xb5 188 | OpCodeF32DemoteF64 OpCode = 0xb6 189 | 190 | OpCodeF64ConvertI32S OpCode = 0xb7 191 | OpCodeF64ConvertI32U OpCode = 0xb8 192 | OpCodeF64ConvertI64S OpCode = 0xb9 193 | OpCodeF64ConvertI64U OpCode = 0xba 194 | OpCodeF64PromoteF32 OpCode = 0xbb 195 | 196 | OpCodeI32ReinterpretF32 OpCode = 0xbc 197 | OpCodeI64ReinterpretF64 OpCode = 0xbd 198 | OpCodeF32ReinterpretI32 OpCode = 0xbe 199 | OpCodeF64ReinterpretI64 OpCode = 0xbf 200 | 201 | OpCodeI32Extend8S OpCode = 0xc0 202 | OpCodeI32Extend16S OpCode = 0xc1 203 | OpCodeI64Extend8S OpCode = 0xc2 204 | OpCodeI64Extend16S OpCode = 0xc3 205 | OpCodeI64Extend32S OpCode = 0xc4 206 | 207 | OpCodeNull OpCode = 0xd0 208 | OpCodeIsNull OpCode = 0xd1 209 | OpCodeFunc OpCode = 0xd2 210 | 211 | // TODO: 0xfc 212 | ) 213 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/c0mm4nd/wasman 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c0mm4nd/wasman/87e38ef26abd9c92e957e455aca3369e87d6cbc5/go.sum -------------------------------------------------------------------------------- /instance.go: -------------------------------------------------------------------------------- 1 | package wasman 2 | 3 | import "github.com/c0mm4nd/wasman/wasm" 4 | 5 | // Instance is same to wasm.Instance 6 | type Instance = wasm.Instance 7 | 8 | // NewInstance is a wrapper to the wasm.NewInstance 9 | func NewInstance(module *Module, externModules map[string]*Module) (*Instance, error) { 10 | return wasm.NewInstance(module, externModules) 11 | } 12 | -------------------------------------------------------------------------------- /leb128decode/leb128.go: -------------------------------------------------------------------------------- 1 | package leb128decode 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | ) 8 | 9 | const ( 10 | maxVarintLen32 = 5 11 | maxVarintLen64 = 10 12 | ) 13 | 14 | var ( 15 | // leb128 decoding errors 16 | errOverflow32 = errors.New("overflows a 32-bit integer") 17 | errOverflow33 = errors.New("overflows a 33-bit integer") 18 | errOverflow64 = errors.New("overflows a 64-bit integer") 19 | ) 20 | 21 | // DecodeUint32 will decode a uint32 from io.Reader, returning it as the ret with the bytes length l which it read. 22 | func DecodeUint32(r *bytes.Reader) (ret uint32, bytesRead uint64, err error) { 23 | // Derived from https://github.com/golang/go/blob/aafad20b617ee63d58fcd4f6e0d98fe27760678c/src/encoding/binary/varint.go 24 | // with the modification on the overflow handling tailored for 32-bits. 25 | var s uint32 26 | var b byte 27 | for i := 0; i < maxVarintLen32; i++ { 28 | b, err = r.ReadByte() 29 | if err != nil { 30 | return 0, 0, err 31 | } 32 | if b < 0x80 { 33 | // Unused bits must be all zero. 34 | if i == maxVarintLen32-1 && (b&0xf0) > 0 { 35 | return 0, 0, errOverflow32 36 | } 37 | return ret | uint32(b)< 1 { 58 | return 0, 0, errOverflow64 59 | } 60 | return ret | uint64(b)< 5 { 87 | return 0, 0, errOverflow32 88 | } else if unused := b & 0b00110000; bytesRead == 5 && ret < 0 && unused != 0b00110000 { 89 | return 0, 0, errOverflow32 90 | } else if bytesRead == 5 && ret >= 0 && unused != 0x00 { 91 | return 0, 0, errOverflow32 92 | } 93 | return 94 | } 95 | } 96 | } 97 | 98 | // DecodeInt33AsInt64 will decode a int33 from io.Reader, returning it as the int64 ret with the bytes length l which it read. 99 | func DecodeInt33AsInt64(r *bytes.Reader) (ret int64, bytesRead uint64, err error) { 100 | const ( 101 | int33Mask int64 = 1 << 7 102 | int33Mask2 = ^int33Mask 103 | int33Mask3 = 1 << 6 104 | int33Mask4 = 8589934591 // 2^33-1 105 | int33Mask5 = 1 << 32 106 | int33Mask6 = int33Mask4 + 1 // 2^33 107 | ) 108 | var shift int 109 | var b int64 110 | var rb byte 111 | for shift < 35 { 112 | rb, err = r.ReadByte() 113 | if err != nil { 114 | return 0, 0, fmt.Errorf("readByte failed: %w", err) 115 | } 116 | b = int64(rb) 117 | ret |= (b & int33Mask2) << shift 118 | shift += 7 119 | bytesRead++ 120 | if b&int33Mask == 0 { 121 | break 122 | } 123 | } 124 | 125 | // fixme: can be optimized 126 | if shift < 33 && (b&int33Mask3) == int33Mask3 { 127 | ret |= int33Mask4 << shift 128 | } 129 | ret = ret & int33Mask4 130 | 131 | // if 33rd bit == 1, we translate it as a corresponding signed-33bit minus value 132 | if ret&int33Mask5 > 0 { 133 | ret = ret - int33Mask6 134 | } 135 | // Over flow checks. 136 | // fixme: can be optimized. 137 | if bytesRead > 5 { 138 | return 0, 0, errOverflow33 139 | } else if unused := b & 0b00100000; bytesRead == 5 && ret < 0 && unused != 0b00100000 { 140 | return 0, 0, errOverflow33 141 | } else if bytesRead == 5 && ret >= 0 && unused != 0x00 { 142 | return 0, 0, errOverflow33 143 | } 144 | return ret, bytesRead, nil 145 | } 146 | 147 | // DecodeInt64 will decode a int64 from io.Reader, returning it as the ret with the bytes length l which it read. 148 | func DecodeInt64(r *bytes.Reader) (ret int64, bytesRead uint64, err error) { 149 | const ( 150 | int64Mask3 = 1 << 6 151 | int64Mask4 = ^0 152 | ) 153 | var shift int 154 | var b byte 155 | for { 156 | b, err = r.ReadByte() 157 | if err != nil { 158 | return 0, 0, fmt.Errorf("readByte failed: %w", err) 159 | } 160 | ret |= (int64(b) & 0x7f) << shift 161 | shift += 7 162 | bytesRead++ 163 | if b&0x80 == 0 { 164 | if shift < 64 && (b&int64Mask3) == int64Mask3 { 165 | ret |= int64Mask4 << shift 166 | } 167 | // Over flow checks. 168 | // fixme: can be optimized. 169 | if bytesRead > 10 { 170 | return 0, 0, errOverflow64 171 | } else if unused := b & 0b00111110; bytesRead == 10 && ret < 0 && unused != 0b00111110 { 172 | return 0, 0, errOverflow64 173 | } else if bytesRead == 10 && ret >= 0 && unused != 0x00 { 174 | return 0, 0, errOverflow64 175 | } 176 | return 177 | } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /leb128decode/leb128_test.go: -------------------------------------------------------------------------------- 1 | package leb128decode_test 2 | 3 | import ( 4 | "bytes" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/c0mm4nd/wasman/leb128decode" 9 | ) 10 | 11 | func TestDecodeUint32(t *testing.T) { 12 | for _, c := range []struct { 13 | bytes []byte 14 | exp uint32 15 | }{ 16 | {bytes: []byte{0x04}, exp: 4}, 17 | {bytes: []byte{0x80, 0x7f}, exp: 16256}, 18 | {bytes: []byte{0xe5, 0x8e, 0x26}, exp: 624485}, 19 | {bytes: []byte{0x80, 0x80, 0x80, 0x4f}, exp: 165675008}, 20 | {bytes: []byte{0x89, 0x80, 0x80, 0x80, 0x01}, exp: 268435465}, 21 | } { 22 | actual, l, err := leb128decode.DecodeUint32(bytes.NewReader(c.bytes)) 23 | if err != nil { 24 | t.Fail() 25 | } 26 | if !reflect.DeepEqual(c.exp, actual) { 27 | t.Fail() 28 | } 29 | if uint64(len(c.bytes)) != l { 30 | t.Fail() 31 | } 32 | } 33 | } 34 | 35 | func TestDecodeUint64(t *testing.T) { 36 | for _, c := range []struct { 37 | bytes []byte 38 | exp uint64 39 | }{ 40 | {bytes: []byte{0x04}, exp: 4}, 41 | {bytes: []byte{0x80, 0x7f}, exp: 16256}, 42 | {bytes: []byte{0xe5, 0x8e, 0x26}, exp: 624485}, 43 | {bytes: []byte{0x80, 0x80, 0x80, 0x4f}, exp: 165675008}, 44 | {bytes: []byte{0x89, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01}, exp: 9223372036854775817}, 45 | } { 46 | actual, l, err := leb128decode.DecodeUint64(bytes.NewReader(c.bytes)) 47 | if err != nil { 48 | t.Fail() 49 | } 50 | if !reflect.DeepEqual(c.exp, actual) { 51 | t.Fail() 52 | } 53 | if uint64(len(c.bytes)) != l { 54 | t.Fail() 55 | } 56 | } 57 | } 58 | 59 | func TestDecodeInt32(t *testing.T) { 60 | for _, c := range []struct { 61 | bytes []byte 62 | exp int32 63 | }{ 64 | {bytes: []byte{0x00}, exp: 0}, 65 | {bytes: []byte{0x04}, exp: 4}, 66 | {bytes: []byte{0xFF, 0x00}, exp: 127}, 67 | {bytes: []byte{0x81, 0x01}, exp: 129}, 68 | {bytes: []byte{0x7f}, exp: -1}, 69 | {bytes: []byte{0x81, 0x7f}, exp: -127}, 70 | {bytes: []byte{0xFF, 0x7e}, exp: -129}, 71 | } { 72 | actual, l, err := leb128decode.DecodeInt32(bytes.NewReader(c.bytes)) 73 | if err != nil { 74 | t.Fail() 75 | } 76 | if !reflect.DeepEqual(c.exp, actual) { 77 | t.Fail() 78 | } 79 | if uint64(len(c.bytes)) != l { 80 | t.Fail() 81 | } 82 | } 83 | } 84 | 85 | func TestDecodeInt33AsInt64(t *testing.T) { 86 | for _, c := range []struct { 87 | bytes []byte 88 | exp int64 89 | }{ 90 | {bytes: []byte{0x00}, exp: 0}, 91 | {bytes: []byte{0x04}, exp: 4}, 92 | {bytes: []byte{0x40}, exp: -64}, 93 | {bytes: []byte{0x7f}, exp: -1}, 94 | {bytes: []byte{0x7e}, exp: -2}, 95 | {bytes: []byte{0x7d}, exp: -3}, 96 | {bytes: []byte{0x7c}, exp: -4}, 97 | {bytes: []byte{0xFF, 0x00}, exp: 127}, 98 | {bytes: []byte{0x81, 0x01}, exp: 129}, 99 | {bytes: []byte{0x7f}, exp: -1}, 100 | {bytes: []byte{0x81, 0x7f}, exp: -127}, 101 | {bytes: []byte{0xFF, 0x7e}, exp: -129}, 102 | } { 103 | actual, l, err := leb128decode.DecodeInt33AsInt64(bytes.NewReader(c.bytes)) 104 | if err != nil { 105 | t.Fail() 106 | } 107 | if !reflect.DeepEqual(c.exp, actual) { 108 | t.Fail() 109 | } 110 | if uint64(len(c.bytes)) != l { 111 | t.Fail() 112 | } 113 | } 114 | } 115 | 116 | func TestDecodeInt64(t *testing.T) { 117 | for _, c := range []struct { 118 | bytes []byte 119 | exp int64 120 | }{ 121 | {bytes: []byte{0x00}, exp: 0}, 122 | {bytes: []byte{0x04}, exp: 4}, 123 | {bytes: []byte{0xFF, 0x00}, exp: 127}, 124 | {bytes: []byte{0x81, 0x01}, exp: 129}, 125 | {bytes: []byte{0x7f}, exp: -1}, 126 | {bytes: []byte{0x81, 0x7f}, exp: -127}, 127 | {bytes: []byte{0xFF, 0x7e}, exp: -129}, 128 | {bytes: []byte{0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7f}, 129 | exp: -9223372036854775808}, 130 | } { 131 | actual, l, err := leb128decode.DecodeInt64(bytes.NewReader(c.bytes)) 132 | if err != nil { 133 | t.Fail() 134 | } 135 | if !reflect.DeepEqual(c.exp, actual) { 136 | t.Fail() 137 | } 138 | if uint64(len(c.bytes)) != l { 139 | t.Fail() 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /linker.go: -------------------------------------------------------------------------------- 1 | package wasman 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "reflect" 7 | 8 | "github.com/c0mm4nd/wasman/config" 9 | "github.com/c0mm4nd/wasman/wasm" 10 | 11 | "github.com/c0mm4nd/wasman/segments" 12 | "github.com/c0mm4nd/wasman/types" 13 | ) 14 | 15 | // errors on linking modules 16 | var ( 17 | ErrInvalidSign = errors.New("invalid signature") 18 | ) 19 | 20 | // Linker is a helper to instantiate new modules 21 | type Linker struct { 22 | config.LinkerConfig 23 | 24 | Modules map[string]*Module // the built-in modules which acts as externs when instantiating coming main module 25 | } 26 | 27 | // NewLinker creates a new Linker 28 | func NewLinker(config config.LinkerConfig) *Linker { 29 | return &Linker{ 30 | LinkerConfig: config, 31 | Modules: map[string]*Module{}, 32 | } 33 | } 34 | 35 | // NewLinkerWithModuleMap creates a new Linker with the built-in modules 36 | func NewLinkerWithModuleMap(config config.LinkerConfig, in map[string]*Module) *Linker { 37 | return &Linker{ 38 | LinkerConfig: config, 39 | Modules: in, 40 | } 41 | } 42 | 43 | // Define put the module on its namespace 44 | func (l *Linker) Define(modName string, mod *Module) { 45 | l.Modules[modName] = mod 46 | } 47 | 48 | // AdvancedFunc is a advanced host func comparing to normal go host func 49 | // Dev will be able to handle the pre/post-call process of the func and manipulate 50 | // the Instance's fields like memory 51 | // 52 | // e.g. when we wanna add toll after calling the host func f 53 | // func ExampleFuncGenerator_addToll() { 54 | // var linker = wasman.NewLinker() 55 | // var f = func() {fmt.Println("wasm")} 56 | // 57 | // var af = wasman.AdvancedFunc(func(ins *wasman.Instance) interface{} { 58 | // return func() { 59 | // f() 60 | // ins.AddGas(11) 61 | // } 62 | // }) 63 | // linker.DefineAdvancedFunc("env", "add_gas", af) 64 | // } 65 | // 66 | // e.g. when we wanna manipulate memory 67 | // func ExampleFuncGenerator_addToll() { 68 | // var linker = wasman.NewLinker() 69 | // 70 | // var af = wasman.AdvancedFunc(func(ins *wasman.Instance) interface{} { 71 | // return func(ptr uint32, length uint32) { 72 | // msg := ins.Memory[int(ptr), int(ptr+uint32)] 73 | // fmt.Println(b) 74 | // } 75 | // }) 76 | // 77 | // linker.DefineAdvancedFunc("env", "print_msg", af) 78 | // } 79 | type AdvancedFunc func(ins *Instance) interface{} 80 | 81 | // DefineAdvancedFunc will define a AdvancedFunc on linker 82 | func (l *Linker) DefineAdvancedFunc(modName, funcName string, funcGenerator AdvancedFunc) error { 83 | sig, err := getSignature(reflect.ValueOf(funcGenerator(&Instance{})).Type()) 84 | if err != nil { 85 | return ErrInvalidSign 86 | } 87 | 88 | mod, exists := l.Modules[modName] 89 | if !exists { 90 | mod = &Module{IndexSpace: new(wasm.IndexSpace), ExportSection: map[string]*segments.ExportSegment{}} 91 | l.Modules[modName] = mod 92 | } 93 | 94 | if l.DisableShadowing && mod.ExportSection[funcName] != nil { 95 | return config.ErrShadowing 96 | } 97 | 98 | mod.ExportSection[funcName] = &segments.ExportSegment{ 99 | Name: funcName, 100 | Desc: &segments.ExportDesc{ 101 | Kind: segments.KindFunction, 102 | Index: uint32(len(mod.IndexSpace.Functions)), 103 | }, 104 | } 105 | 106 | mod.IndexSpace.Functions = append(mod.IndexSpace.Functions, &wasm.HostFunc{ 107 | Generator: funcGenerator, 108 | Signature: sig, 109 | }) 110 | 111 | return nil 112 | } 113 | 114 | // DefineFunc puts a simple go style func into Linker's modules. 115 | // This f should be a simply func which doesnt handle ins's fields. 116 | func (l *Linker) DefineFunc(modName, funcName string, f interface{}) error { 117 | fn := func(ins *Instance) interface{} { 118 | return f 119 | } 120 | 121 | sig, err := getSignature(reflect.ValueOf(f).Type()) 122 | if err != nil { 123 | return ErrInvalidSign 124 | } 125 | 126 | mod, exists := l.Modules[modName] 127 | if !exists { 128 | mod = &Module{IndexSpace: new(wasm.IndexSpace), ExportSection: map[string]*segments.ExportSegment{}} 129 | l.Modules[modName] = mod 130 | } 131 | 132 | if l.DisableShadowing && mod.ExportSection[funcName] != nil { 133 | return config.ErrShadowing 134 | } 135 | 136 | mod.ExportSection[funcName] = &segments.ExportSegment{ 137 | Name: funcName, 138 | Desc: &segments.ExportDesc{ 139 | Kind: segments.KindFunction, 140 | Index: uint32(len(mod.IndexSpace.Functions)), 141 | }, 142 | } 143 | 144 | mod.IndexSpace.Functions = append(mod.IndexSpace.Functions, &wasm.HostFunc{ 145 | Generator: fn, 146 | Signature: sig, 147 | }) 148 | 149 | return nil 150 | } 151 | 152 | // DefineGlobal will defined an external global for the main module 153 | func (l *Linker) DefineGlobal(modName, globalName string, global interface{}) error { 154 | ty, err := getTypeOf(reflect.TypeOf(global).Kind()) 155 | if err != nil { 156 | return err 157 | } 158 | 159 | mod, exists := l.Modules[modName] 160 | if !exists { 161 | mod = &Module{IndexSpace: new(wasm.IndexSpace), ExportSection: map[string]*segments.ExportSegment{}} 162 | l.Modules[modName] = mod 163 | } 164 | 165 | if l.DisableShadowing && mod.ExportSection[globalName] != nil { 166 | return config.ErrShadowing 167 | } 168 | 169 | mod.ExportSection[globalName] = &segments.ExportSegment{ 170 | Name: globalName, 171 | Desc: &segments.ExportDesc{ 172 | Kind: segments.KindGlobal, 173 | Index: uint32(len(mod.IndexSpace.Globals)), 174 | }, 175 | } 176 | 177 | mod.IndexSpace.Globals = append(mod.IndexSpace.Globals, &wasm.Global{ 178 | GlobalType: &types.GlobalType{ 179 | ValType: ty, 180 | Mutable: true, 181 | }, 182 | Val: global, 183 | }) 184 | 185 | return nil 186 | } 187 | 188 | // DefineTable will defined an external table for the main module 189 | func (l *Linker) DefineTable(modName, tableName string, table []*uint32) error { 190 | mod, exists := l.Modules[modName] 191 | if !exists { 192 | mod = &Module{IndexSpace: new(wasm.IndexSpace), ExportSection: map[string]*segments.ExportSegment{}} 193 | l.Modules[modName] = mod 194 | } 195 | 196 | if l.DisableShadowing && mod.ExportSection[tableName] != nil { 197 | return config.ErrShadowing 198 | } 199 | 200 | mod.ExportSection[tableName] = &segments.ExportSegment{ 201 | Name: tableName, 202 | Desc: &segments.ExportDesc{ 203 | Kind: segments.KindTable, 204 | Index: uint32(len(mod.IndexSpace.Tables)), 205 | }, 206 | } 207 | 208 | mod.IndexSpace.Tables = append(mod.IndexSpace.Tables, &wasm.Table{ 209 | TableType: *mod.TableSection[0], 210 | Value: table, 211 | }) 212 | 213 | return nil 214 | } 215 | 216 | // DefineMemory will defined an external memory for the main module 217 | func (l *Linker) DefineMemory(modName, memName string, mem []byte) error { 218 | mod, exists := l.Modules[modName] 219 | if !exists { 220 | mod = &Module{IndexSpace: new(wasm.IndexSpace), ExportSection: map[string]*segments.ExportSegment{}} 221 | l.Modules[modName] = mod 222 | } 223 | 224 | if l.DisableShadowing && mod.ExportSection[memName] != nil { 225 | return config.ErrShadowing 226 | } 227 | 228 | mod.ExportSection[memName] = &segments.ExportSegment{ 229 | Name: memName, 230 | Desc: &segments.ExportDesc{ 231 | Kind: segments.KindMem, 232 | Index: uint32(len(mod.IndexSpace.Memories)), 233 | }, 234 | } 235 | 236 | mod.IndexSpace.Memories = append(mod.IndexSpace.Memories, &wasm.Memory{ 237 | MemoryType: *mod.MemorySection[0], 238 | Value: mem, 239 | }) 240 | 241 | return nil 242 | } 243 | 244 | // Instantiate will instantiate a Module into an runnable Instance 245 | func (l *Linker) Instantiate(mainModule *Module) (*Instance, error) { 246 | return NewInstance(mainModule, l.Modules) 247 | } 248 | 249 | func getSignature(p reflect.Type) (*types.FuncType, error) { 250 | var err error 251 | in := make([]types.ValueType, p.NumIn()) 252 | for i := range in { 253 | in[i], err = getTypeOf(p.In(i).Kind()) 254 | if err != nil { 255 | return nil, err 256 | } 257 | } 258 | 259 | out := make([]types.ValueType, p.NumOut()) 260 | for i := range out { 261 | out[i], err = getTypeOf(p.Out(i).Kind()) 262 | if err != nil { 263 | return nil, err 264 | } 265 | } 266 | 267 | return &types.FuncType{InputTypes: in, ReturnTypes: out}, nil 268 | } 269 | 270 | const is64Bit = uint64(^uintptr(0)) == ^uint64(0) 271 | 272 | // getTypeOf converts the go type into wasm val type 273 | func getTypeOf(kind reflect.Kind) (types.ValueType, error) { 274 | if is64Bit { 275 | switch kind { 276 | case reflect.Float64: 277 | return types.ValueTypeF64, nil 278 | case reflect.Float32: 279 | return types.ValueTypeF32, nil 280 | case reflect.Int32, reflect.Uint32: 281 | return types.ValueTypeI32, nil 282 | case reflect.Int64, reflect.Uint64, reflect.Uintptr, reflect.UnsafePointer, reflect.Ptr: 283 | return types.ValueTypeI64, nil 284 | default: 285 | return 0x00, fmt.Errorf("invalid type: %s", kind.String()) 286 | } 287 | } else { 288 | switch kind { 289 | case reflect.Float64: 290 | return types.ValueTypeF64, nil 291 | case reflect.Float32: 292 | return types.ValueTypeF32, nil 293 | case reflect.Int32, reflect.Uint32, reflect.Uintptr, reflect.UnsafePointer, reflect.Ptr: 294 | return types.ValueTypeI32, nil 295 | case reflect.Int64, reflect.Uint64: 296 | return types.ValueTypeI64, nil 297 | default: 298 | return 0x00, fmt.Errorf("invalid type: %s", kind.String()) 299 | } 300 | } 301 | 302 | } 303 | -------------------------------------------------------------------------------- /linker_test.go: -------------------------------------------------------------------------------- 1 | package wasman 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/c0mm4nd/wasman/types" 8 | ) 9 | 10 | // TODO: Add uint ptr tests 11 | 12 | func Test_getTypeOf(t *testing.T) { 13 | for _, c := range []struct { 14 | kind reflect.Kind 15 | exp types.ValueType 16 | }{ 17 | {kind: reflect.Int32, exp: types.ValueTypeI32}, 18 | {kind: reflect.Uint32, exp: types.ValueTypeI32}, 19 | {kind: reflect.Int64, exp: types.ValueTypeI64}, 20 | {kind: reflect.Uint64, exp: types.ValueTypeI64}, 21 | {kind: reflect.Float32, exp: types.ValueTypeF32}, 22 | {kind: reflect.Float64, exp: types.ValueTypeF64}, 23 | } { 24 | actual, err := getTypeOf(c.kind) 25 | if err != nil { 26 | t.Fail() 27 | } 28 | if !reflect.DeepEqual(c.exp, actual) { 29 | t.Fail() 30 | } 31 | } 32 | } 33 | 34 | func Test_getSignature(t *testing.T) { 35 | v := reflect.ValueOf(func(int32, int64, float32, float64) (int32, float64) { return 0, 0 }) 36 | actual, err := getSignature(v.Type()) 37 | if err != nil { 38 | t.Fail() 39 | } 40 | if !reflect.DeepEqual(&types.FuncType{ 41 | InputTypes: []types.ValueType{types.ValueTypeI32, types.ValueTypeI64, types.ValueTypeF32, types.ValueTypeF64}, 42 | ReturnTypes: []types.ValueType{types.ValueTypeI32, types.ValueTypeF64}, 43 | }, actual) { 44 | t.Fail() 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /module.go: -------------------------------------------------------------------------------- 1 | package wasman 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "io/ioutil" 7 | 8 | "github.com/c0mm4nd/wasman/config" 9 | "github.com/c0mm4nd/wasman/wasm" 10 | ) 11 | 12 | // Module is same to wasm.Module 13 | type Module = wasm.Module 14 | 15 | // NewModule is a wrapper to the wasm.NewModule 16 | func NewModule(config config.ModuleConfig, r io.Reader) (*Module, error) { 17 | b, err := ioutil.ReadAll(r) 18 | if err != nil { 19 | return nil, err 20 | } 21 | 22 | return wasm.NewModule(config, bytes.NewReader(b)) 23 | } 24 | -------------------------------------------------------------------------------- /segments/code.go: -------------------------------------------------------------------------------- 1 | package segments 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | 8 | "github.com/c0mm4nd/wasman/expr" 9 | "github.com/c0mm4nd/wasman/leb128decode" 10 | ) 11 | 12 | // CodeSegment is one unit in the wasman.Module's CodeSection 13 | type CodeSegment struct { 14 | NumLocals uint32 15 | Body []byte 16 | } 17 | 18 | // ReadCodeSegment reads one CodeSegment from the io.Reader 19 | func ReadCodeSegment(r *bytes.Reader) (*CodeSegment, error) { 20 | ss, _, err := leb128decode.DecodeUint32(r) 21 | if err != nil { 22 | return nil, fmt.Errorf("get the size of code segment: %w", err) 23 | } 24 | remaining := int64(ss) 25 | 26 | // parse locals 27 | ls, bytesRead, err := leb128decode.DecodeUint32(r) 28 | remaining -= int64(bytesRead) 29 | if err != nil { 30 | return nil, fmt.Errorf("get the size locals: %w", err) 31 | } else if remaining < 0 { 32 | return nil, io.EOF 33 | } 34 | 35 | var numLocals uint32 36 | var n uint32 37 | for i := uint32(0); i < ls; i++ { 38 | n, bytesRead, err = leb128decode.DecodeUint32(r) 39 | remaining -= int64(bytesRead) + 1 // +1 for the subsequent ReadByte 40 | if err != nil { 41 | return nil, fmt.Errorf("read n of locals: %w", err) 42 | } else if remaining < 0 { 43 | return nil, io.EOF 44 | } 45 | numLocals += n 46 | 47 | if _, err := r.ReadByte(); err != nil { 48 | return nil, fmt.Errorf("read type of local") // TODO: save read localType 49 | } 50 | } 51 | 52 | // extract body 53 | body := make([]byte, remaining) 54 | _, err = io.ReadFull(r, body) 55 | if err != nil { 56 | return nil, fmt.Errorf("read body: %w", err) 57 | } 58 | 59 | if body[len(body)-1] != byte(expr.OpCodeEnd) { 60 | return nil, fmt.Errorf("expr not end with opcodes.OpCodeEnd") 61 | } 62 | 63 | return &CodeSegment{ 64 | Body: body[:len(body)-1], 65 | NumLocals: numLocals, 66 | }, nil 67 | } 68 | -------------------------------------------------------------------------------- /segments/code_test.go: -------------------------------------------------------------------------------- 1 | package segments_test 2 | 3 | import ( 4 | "bytes" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/c0mm4nd/wasman/segments" 9 | ) 10 | 11 | func TestReadCodeSegment(t *testing.T) { 12 | buf := []byte{0x9, 0x1, 0x1, 0x1, 0x1, 0x1, 0x12, 0x3, 0x01, 0x0b} 13 | exp := &segments.CodeSegment{ 14 | NumLocals: 0x01, 15 | Body: []byte{0x1, 0x1, 0x12, 0x3, 0x01}, 16 | } 17 | actual, err := segments.ReadCodeSegment(bytes.NewReader(buf)) 18 | if err != nil { 19 | t.Fail() 20 | } 21 | if !reflect.DeepEqual(exp, actual) { 22 | t.Fail() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /segments/consts.go: -------------------------------------------------------------------------------- 1 | package segments 2 | 3 | // Kind means the types of ​the extern values 4 | // https://www.w3.org/TR/wasm-core-1/#external-values%E2%91%A0 5 | // https://www.w3.org/TR/wasm-core-1/#external-typing%E2%91%A0 6 | type Kind = byte 7 | 8 | // available export kinds 9 | const ( 10 | KindFunction Kind = 0x00 11 | KindTable Kind = 0x01 12 | KindMem Kind = 0x02 13 | KindGlobal Kind = 0x03 14 | ) 15 | -------------------------------------------------------------------------------- /segments/data.go: -------------------------------------------------------------------------------- 1 | package segments 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | 8 | "github.com/c0mm4nd/wasman/expr" 9 | "github.com/c0mm4nd/wasman/leb128decode" 10 | ) 11 | 12 | // DataSegment is one unit of the wasman.Module's DataSection, initializing 13 | // a range of memory, at a given offset, with a static vector of bytes 14 | // 15 | // https://www.w3.org/TR/wasm-core-1/#data-segments%E2%91%A0 16 | type DataSegment struct { 17 | MemoryIndex uint32 // supposed to be zero 18 | OffsetExpression *expr.Expression 19 | Init []byte 20 | } 21 | 22 | // ReadDataSegment reads one DataSegment from the io.Reader 23 | func ReadDataSegment(r *bytes.Reader) (*DataSegment, error) { 24 | d, _, err := leb128decode.DecodeUint32(r) 25 | if err != nil { 26 | return nil, fmt.Errorf("read memory index: %w", err) 27 | } 28 | 29 | if d != 0 { 30 | return nil, fmt.Errorf("invalid memory index: %d", d) 31 | } 32 | 33 | expression, err := expr.ReadExpression(r) 34 | if err != nil { 35 | return nil, fmt.Errorf("read offset expression: %w", err) 36 | } 37 | 38 | if expression.OpCode != expr.OpCodeI32Const { 39 | return nil, fmt.Errorf("offset expression must have i32.const opcodes.OpCode but go %#x", expression.OpCode) 40 | } 41 | 42 | vs, _, err := leb128decode.DecodeUint32(r) 43 | if err != nil { 44 | return nil, fmt.Errorf("get the size of vector: %w", err) 45 | } 46 | 47 | b := make([]byte, vs) 48 | if _, err := io.ReadFull(r, b); err != nil { 49 | return nil, fmt.Errorf("read bytes for init: %w", err) 50 | } 51 | 52 | return &DataSegment{ 53 | OffsetExpression: expression, 54 | Init: b, 55 | }, nil 56 | } 57 | -------------------------------------------------------------------------------- /segments/data_test.go: -------------------------------------------------------------------------------- 1 | package segments_test 2 | 3 | import ( 4 | "bytes" 5 | "reflect" 6 | "strconv" 7 | "testing" 8 | 9 | "github.com/c0mm4nd/wasman/expr" 10 | "github.com/c0mm4nd/wasman/segments" 11 | ) 12 | 13 | func TestDataSegment(t *testing.T) { 14 | for i, c := range []struct { 15 | bytes []byte 16 | exp *segments.DataSegment 17 | }{ 18 | { 19 | bytes: []byte{0x0, 0x41, 0x1, 0x0b, 0x02, 0x05, 0x07}, 20 | exp: &segments.DataSegment{ 21 | OffsetExpression: &expr.Expression{ 22 | OpCode: expr.OpCodeI32Const, 23 | Data: []byte{0x01}, 24 | }, 25 | Init: []byte{5, 7}, 26 | }, 27 | }, 28 | { 29 | bytes: []byte{0x0, 0x41, 0x04, 0x0b, 0x01, 0x0a}, 30 | exp: &segments.DataSegment{ 31 | OffsetExpression: &expr.Expression{ 32 | OpCode: expr.OpCodeI32Const, 33 | Data: []byte{0x04}, 34 | }, 35 | Init: []byte{0x0a}, 36 | }, 37 | }, 38 | } { 39 | t.Run(strconv.Itoa(i), func(t *testing.T) { 40 | actual, err := segments.ReadDataSegment(bytes.NewReader(c.bytes)) 41 | if err != nil { 42 | t.Fail() 43 | } 44 | if !reflect.DeepEqual(c.exp, actual) { 45 | t.Fail() 46 | } 47 | }) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /segments/elem.go: -------------------------------------------------------------------------------- 1 | package segments 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | 7 | "github.com/c0mm4nd/wasman/expr" 8 | "github.com/c0mm4nd/wasman/leb128decode" 9 | ) 10 | 11 | // ElemSegment is one unit of the wasm.Module's ElementsSection, initializing 12 | // a subrange of a table, at a given offset, from a static vector of elements. 13 | // 14 | // https://www.w3.org/TR/wasm-core-1/#element-segments%E2%91%A0 15 | type ElemSegment struct { 16 | TableIndex uint32 17 | OffsetExpr *expr.Expression 18 | Init []uint32 19 | } 20 | 21 | // ReadElemSegment reads one ElemSegment from the io.Reader 22 | func ReadElemSegment(r *bytes.Reader) (*ElemSegment, error) { 23 | ti, _, err := leb128decode.DecodeUint32(r) 24 | if err != nil { 25 | return nil, fmt.Errorf("get table index: %w", err) 26 | } 27 | 28 | expression, err := expr.ReadExpression(r) 29 | if err != nil { 30 | return nil, fmt.Errorf("read expr for offset: %w", err) 31 | } 32 | 33 | if expression.OpCode != expr.OpCodeI32Const { 34 | return nil, fmt.Errorf("offset expression must be i32.const but go %#x", expression.OpCode) 35 | } 36 | 37 | vs, _, err := leb128decode.DecodeUint32(r) 38 | if err != nil { 39 | return nil, fmt.Errorf("get size of vector: %w", err) 40 | } 41 | 42 | init := make([]uint32, vs) 43 | for i := range init { 44 | fIDx, _, err := leb128decode.DecodeUint32(r) 45 | if err != nil { 46 | return nil, fmt.Errorf("read function index: %w", err) 47 | } 48 | init[i] = fIDx 49 | } 50 | 51 | return &ElemSegment{ 52 | TableIndex: ti, 53 | OffsetExpr: expression, 54 | Init: init, 55 | }, nil 56 | } 57 | -------------------------------------------------------------------------------- /segments/elem_test.go: -------------------------------------------------------------------------------- 1 | package segments_test 2 | 3 | import ( 4 | "bytes" 5 | "reflect" 6 | "strconv" 7 | "testing" 8 | 9 | "github.com/c0mm4nd/wasman/expr" 10 | "github.com/c0mm4nd/wasman/segments" 11 | ) 12 | 13 | func TestReadElementSegment(t *testing.T) { 14 | for i, c := range []struct { 15 | bytes []byte 16 | exp *segments.ElemSegment 17 | }{ 18 | { 19 | bytes: []byte{0xa, 0x41, 0x1, 0x0b, 0x02, 0x05, 0x07}, 20 | exp: &segments.ElemSegment{ 21 | TableIndex: 10, 22 | OffsetExpr: &expr.Expression{ 23 | OpCode: expr.OpCodeI32Const, 24 | Data: []byte{0x01}, 25 | }, 26 | Init: []uint32{5, 7}, 27 | }, 28 | }, 29 | { 30 | bytes: []byte{0x3, 0x41, 0x04, 0x0b, 0x01, 0x0a}, 31 | exp: &segments.ElemSegment{ 32 | TableIndex: 3, 33 | OffsetExpr: &expr.Expression{ 34 | OpCode: expr.OpCodeI32Const, 35 | Data: []byte{0x04}, 36 | }, 37 | Init: []uint32{10}, 38 | }, 39 | }, 40 | } { 41 | t.Run(strconv.Itoa(i), func(t *testing.T) { 42 | actual, err := segments.ReadElemSegment(bytes.NewReader(c.bytes)) 43 | if err != nil { 44 | t.Fail() 45 | } 46 | if !reflect.DeepEqual(c.exp, actual) { 47 | t.Fail() 48 | } 49 | }) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /segments/export.go: -------------------------------------------------------------------------------- 1 | package segments 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | 8 | "github.com/c0mm4nd/wasman/leb128decode" 9 | "github.com/c0mm4nd/wasman/types" 10 | ) 11 | 12 | // ExportDesc means export descriptions, which describe an export in one wasman.Module 13 | type ExportDesc struct { 14 | Kind byte 15 | Index uint32 16 | } 17 | 18 | // ReadExportDesc reads one ExportDesc from the io.Reader 19 | func ReadExportDesc(r *bytes.Reader) (*ExportDesc, error) { 20 | b := make([]byte, 1) 21 | if _, err := io.ReadFull(r, b); err != nil { 22 | return nil, fmt.Errorf("read value kind: %w", err) 23 | } 24 | 25 | kind := b[0] 26 | if kind >= 0x04 { 27 | return nil, fmt.Errorf("%w: invalid byte for exportdesc: %#x", types.ErrInvalidTypeByte, kind) 28 | } 29 | 30 | id, _, err := leb128decode.DecodeUint32(r) 31 | if err != nil { 32 | return nil, fmt.Errorf("read funcidx: %w", err) 33 | } 34 | 35 | return &ExportDesc{ 36 | Kind: kind, 37 | Index: id, 38 | }, nil 39 | } 40 | 41 | // ExportSegment is one unit of the wasm.Module's ExportSection 42 | type ExportSegment struct { 43 | Name string 44 | Desc *ExportDesc 45 | } 46 | 47 | // ReadExportSegment reads one ExportSegment from the io.Reader 48 | func ReadExportSegment(r *bytes.Reader) (*ExportSegment, error) { 49 | name, err := types.ReadNameValue(r) 50 | if err != nil { 51 | return nil, fmt.Errorf("read name of export module: %w", err) 52 | } 53 | 54 | d, err := ReadExportDesc(r) 55 | if err != nil { 56 | return nil, fmt.Errorf("read export description: %w", err) 57 | } 58 | 59 | return &ExportSegment{Name: name, Desc: d}, nil 60 | } 61 | -------------------------------------------------------------------------------- /segments/export_test.go: -------------------------------------------------------------------------------- 1 | package segments_test 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "reflect" 7 | "strconv" 8 | 9 | "testing" 10 | 11 | "github.com/c0mm4nd/wasman/segments" 12 | "github.com/c0mm4nd/wasman/types" 13 | ) 14 | 15 | func TestReadExportDesc(t *testing.T) { 16 | t.Run("ng", func(t *testing.T) { 17 | buf := []byte{0x04} 18 | _, err := segments.ReadExportDesc(bytes.NewReader(buf)) 19 | if !errors.Is(err, types.ErrInvalidTypeByte) { 20 | t.Log(err) 21 | t.Fail() 22 | } 23 | }) 24 | 25 | for i, c := range []struct { 26 | bytes []byte 27 | exp *segments.ExportDesc 28 | }{ 29 | { 30 | bytes: []byte{0x00, 0x0a}, 31 | exp: &segments.ExportDesc{Kind: 0, Index: 10}, 32 | }, 33 | { 34 | bytes: []byte{0x01, 0x05}, 35 | exp: &segments.ExportDesc{Kind: 1, Index: 5}, 36 | }, 37 | { 38 | bytes: []byte{0x02, 0x01}, 39 | exp: &segments.ExportDesc{Kind: 2, Index: 1}, 40 | }, 41 | { 42 | bytes: []byte{0x03, 0x0b}, 43 | exp: &segments.ExportDesc{Kind: 3, Index: 11}, 44 | }, 45 | } { 46 | t.Run(strconv.Itoa(i), func(t *testing.T) { 47 | actual, err := segments.ReadExportDesc(bytes.NewReader(c.bytes)) 48 | if err != nil { 49 | t.Fail() 50 | } 51 | if !reflect.DeepEqual(c.exp, actual) { 52 | t.Fail() 53 | } 54 | }) 55 | 56 | } 57 | } 58 | 59 | func TestReadExportSegment(t *testing.T) { 60 | exp := &segments.ExportSegment{ 61 | Name: "ABC", 62 | Desc: &segments.ExportDesc{Kind: 0, Index: 10}, 63 | } 64 | 65 | buf := []byte{byte(len(exp.Name))} 66 | buf = append(buf, exp.Name...) 67 | buf = append(buf, 0x00, 0x0a) 68 | 69 | actual, err := segments.ReadExportSegment(bytes.NewReader(buf)) 70 | if err != nil { 71 | t.Fail() 72 | } 73 | if !reflect.DeepEqual(exp, actual) { 74 | t.Fail() 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /segments/global.go: -------------------------------------------------------------------------------- 1 | package segments 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | 7 | "github.com/c0mm4nd/wasman/expr" 8 | "github.com/c0mm4nd/wasman/types" 9 | ) 10 | 11 | // GlobalSegment is one unit of the wasm.Module's GlobalSection 12 | type GlobalSegment struct { 13 | Type *types.GlobalType 14 | Init *expr.Expression 15 | } 16 | 17 | // ReadGlobalSegment reads one GlobalSegment from the io.Reader 18 | func ReadGlobalSegment(r *bytes.Reader) (*GlobalSegment, error) { 19 | gt, err := types.ReadGlobalType(r) 20 | if err != nil { 21 | return nil, fmt.Errorf("read global type: %w", err) 22 | } 23 | 24 | init, err := expr.ReadExpression(r) 25 | if err != nil { 26 | return nil, fmt.Errorf("get init expression: %w", err) 27 | } 28 | 29 | return &GlobalSegment{ 30 | Type: gt, 31 | Init: init, 32 | }, nil 33 | } 34 | -------------------------------------------------------------------------------- /segments/global_test.go: -------------------------------------------------------------------------------- 1 | package segments_test 2 | 3 | import ( 4 | "bytes" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/c0mm4nd/wasman/expr" 9 | "github.com/c0mm4nd/wasman/segments" 10 | "github.com/c0mm4nd/wasman/types" 11 | ) 12 | 13 | func TestReadGlobalSegment(t *testing.T) { 14 | exp := &segments.GlobalSegment{ 15 | Type: &types.GlobalType{ValType: types.ValueTypeI64, Mutable: false}, 16 | Init: &expr.Expression{ 17 | OpCode: expr.OpCodeI64Const, 18 | Data: []byte{0x01}, 19 | }, 20 | } 21 | 22 | buf := []byte{0x7e, 0x00, 0x42, 0x01, 0x0b} 23 | actual, err := segments.ReadGlobalSegment(bytes.NewReader(buf)) 24 | if err != nil { 25 | t.Fail() 26 | } 27 | if !reflect.DeepEqual(exp, actual) { 28 | t.Fail() 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /segments/import.go: -------------------------------------------------------------------------------- 1 | package segments 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | 8 | "github.com/c0mm4nd/wasman/leb128decode" 9 | "github.com/c0mm4nd/wasman/types" 10 | ) 11 | 12 | // ImportDesc means import descriptions, which describe an import in one wasman.Module 13 | type ImportDesc struct { 14 | Kind byte 15 | 16 | TypeIndexPtr *uint32 // => func x 17 | TableTypePtr *types.TableType // => table tt 18 | MemTypePtr *types.MemoryType // => mem mt 19 | GlobalTypePtr *types.GlobalType // => global gt 20 | } 21 | 22 | // ReadImportDesc reads one ImportDesc from the io.Reader 23 | func ReadImportDesc(r *bytes.Reader) (*ImportDesc, error) { 24 | b := make([]byte, 1) 25 | if _, err := io.ReadFull(r, b); err != nil { 26 | return nil, fmt.Errorf("read value kind: %w", err) 27 | } 28 | 29 | switch b[0] { 30 | case KindFunction: 31 | tID, _, err := leb128decode.DecodeUint32(r) 32 | if err != nil { 33 | return nil, fmt.Errorf("read typeindex: %w", err) 34 | } 35 | return &ImportDesc{ 36 | Kind: 0x00, 37 | TypeIndexPtr: &tID, 38 | }, nil 39 | case KindTable: 40 | tt, err := types.ReadTableType(r) 41 | if err != nil { 42 | return nil, fmt.Errorf("read table type: %w", err) 43 | } 44 | return &ImportDesc{ 45 | Kind: 0x01, 46 | TableTypePtr: tt, 47 | }, nil 48 | case KindMem: 49 | mt, err := types.ReadMemoryType(r) 50 | if err != nil { 51 | return nil, fmt.Errorf("read table type: %w", err) 52 | } 53 | return &ImportDesc{ 54 | Kind: 0x02, 55 | MemTypePtr: mt, 56 | }, nil 57 | case KindGlobal: 58 | gt, err := types.ReadGlobalType(r) 59 | if err != nil { 60 | return nil, fmt.Errorf("read global type: %w", err) 61 | } 62 | 63 | return &ImportDesc{ 64 | Kind: 0x03, 65 | GlobalTypePtr: gt, 66 | }, nil 67 | default: 68 | return nil, fmt.Errorf("%w: invalid byte for importdesc: %#x", types.ErrInvalidTypeByte, b[0]) 69 | } 70 | } 71 | 72 | // ImportSegment is one unit of the wasm.Module's ImportSection 73 | type ImportSegment struct { 74 | Module string 75 | Name string 76 | Desc *ImportDesc 77 | } 78 | 79 | // ReadImportSegment reads one ImportSegment from the io.Reader 80 | func ReadImportSegment(r *bytes.Reader) (*ImportSegment, error) { 81 | mn, err := types.ReadNameValue(r) 82 | if err != nil { 83 | return nil, fmt.Errorf("read name of imported module: %w", err) 84 | } 85 | 86 | n, err := types.ReadNameValue(r) 87 | if err != nil { 88 | return nil, fmt.Errorf("read name of imported module component: %w", err) 89 | } 90 | 91 | d, err := ReadImportDesc(r) 92 | if err != nil { 93 | return nil, fmt.Errorf("read import description : %w", err) 94 | } 95 | 96 | return &ImportSegment{Module: mn, Name: n, Desc: d}, nil 97 | } 98 | -------------------------------------------------------------------------------- /segments/import_test.go: -------------------------------------------------------------------------------- 1 | package segments_test 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "reflect" 7 | "strconv" 8 | "testing" 9 | 10 | "github.com/c0mm4nd/wasman/segments" 11 | "github.com/c0mm4nd/wasman/types" 12 | "github.com/c0mm4nd/wasman/utils" 13 | ) 14 | 15 | func TestReadImportDesc(t *testing.T) { 16 | t.Run("ng", func(t *testing.T) { 17 | buf := []byte{0x04} 18 | _, err := segments.ReadImportDesc(bytes.NewReader(buf)) 19 | if !errors.Is(err, types.ErrInvalidTypeByte) { 20 | t.Log(err) 21 | t.Fail() 22 | } 23 | }) 24 | 25 | for i, c := range []struct { 26 | bytes []byte 27 | exp *segments.ImportDesc 28 | }{ 29 | { 30 | bytes: []byte{0x00, 0x0a}, 31 | exp: &segments.ImportDesc{ 32 | Kind: 0, 33 | TypeIndexPtr: utils.Uint32Ptr(10), 34 | }, 35 | }, 36 | { 37 | bytes: []byte{0x01, 0x70, 0x0, 0x0a}, 38 | exp: &segments.ImportDesc{ 39 | Kind: 1, 40 | TableTypePtr: &types.TableType{ 41 | Elem: 0x70, 42 | Limits: &types.Limits{Min: 10}, 43 | }, 44 | }, 45 | }, 46 | { 47 | bytes: []byte{0x02, 0x0, 0x0a}, 48 | exp: &segments.ImportDesc{ 49 | Kind: 2, 50 | MemTypePtr: &types.MemoryType{Min: 10}, 51 | }, 52 | }, 53 | { 54 | bytes: []byte{0x03, 0x7e, 0x01}, 55 | exp: &segments.ImportDesc{ 56 | Kind: 3, 57 | GlobalTypePtr: &types.GlobalType{ValType: types.ValueTypeI64, Mutable: true}, 58 | }, 59 | }, 60 | } { 61 | t.Run(strconv.Itoa(i), func(t *testing.T) { 62 | actual, err := segments.ReadImportDesc(bytes.NewReader(c.bytes)) 63 | if err != nil { 64 | t.Fail() 65 | } 66 | if !reflect.DeepEqual(c.exp, actual) { 67 | t.Fail() 68 | } 69 | }) 70 | 71 | } 72 | } 73 | 74 | func TestReadImportSegment(t *testing.T) { 75 | exp := &segments.ImportSegment{ 76 | Module: "abc", 77 | Name: "ABC", 78 | Desc: &segments.ImportDesc{Kind: 0, TypeIndexPtr: utils.Uint32Ptr(10)}, 79 | } 80 | 81 | buf := []byte{byte(len(exp.Module))} 82 | buf = append(buf, exp.Module...) 83 | buf = append(buf, byte(len(exp.Name))) 84 | buf = append(buf, exp.Name...) 85 | buf = append(buf, 0x00, 0x0a) 86 | 87 | actual, err := segments.ReadImportSegment(bytes.NewReader(buf)) 88 | if err != nil { 89 | t.Fail() 90 | } 91 | if !reflect.DeepEqual(exp, actual) { 92 | t.Fail() 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /stacks/label_stack.go: -------------------------------------------------------------------------------- 1 | package stacks 2 | 3 | const ( 4 | // InitialLabelStackHeight is the initial length of stack 5 | InitialLabelStackHeight = 10 6 | ) 7 | 8 | // Label acts as a signal on the workflow of the control instr 9 | type Label struct { 10 | Arity int 11 | EndPC uint64 12 | ContinuationPC uint64 13 | } 14 | 15 | // NewLabelStack creates a new LabelStack 16 | func NewLabelStack() *Stack[*Label] { 17 | return &Stack[*Label]{ 18 | Values: make([]*Label, InitialLabelStackHeight), 19 | Ptr: -1, 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /stacks/operand_stack.go: -------------------------------------------------------------------------------- 1 | package stacks 2 | 3 | const ( 4 | // InitialOperandStackHeight is the initial length of the OperandStack 5 | InitialOperandStackHeight = 1024 6 | ) 7 | 8 | // NewOperandStack creates a new OperandStack with no limit 9 | func NewOperandStack() *Stack[uint64] { 10 | return &Stack[uint64]{ 11 | Values: make([]uint64, InitialOperandStackHeight), 12 | Ptr: -1, 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /stacks/stack.go: -------------------------------------------------------------------------------- 1 | package stacks 2 | 3 | type Stack[T any] struct { 4 | Values []T 5 | Ptr int 6 | } 7 | 8 | func (s *Stack[T]) Push(val T) { 9 | if s.Ptr+1 == len(s.Values) { 10 | // grow stack 11 | s.Values = append(s.Values, val) 12 | } else { 13 | s.Values[s.Ptr+1] = val 14 | } 15 | 16 | s.Ptr++ 17 | } 18 | 19 | // Pop will return the value on current Ptr, and backspace the Ptr 20 | func (s *Stack[T]) Pop() T { 21 | ret := s.Values[s.Ptr] 22 | s.Ptr-- 23 | return ret 24 | } 25 | 26 | // Drop is same to Pop but no return 27 | func (s *Stack[T]) Drop() { 28 | s.Ptr-- 29 | } 30 | 31 | // Peek will return the value on current Ptr like Pop but Ptr does not get backspace 32 | func (s *Stack[T]) Peek() T { 33 | return s.Values[s.Ptr] 34 | } 35 | 36 | -------------------------------------------------------------------------------- /stacks/stacks_test.go: -------------------------------------------------------------------------------- 1 | package stacks_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/c0mm4nd/wasman/stacks" 8 | ) 9 | 10 | func TestVirtualMachineOperandStack(t *testing.T) { 11 | s := stacks.NewOperandStack() 12 | if stacks.InitialOperandStackHeight != len(s.Values) { 13 | t.Fail() 14 | } 15 | 16 | var exp uint64 = 10 17 | s.Push(exp) 18 | if exp != s.Pop() { 19 | t.Fail() 20 | } 21 | 22 | // verify the length grows 23 | for i := 0; i < stacks.InitialOperandStackHeight+1; i++ { 24 | s.Push(uint64(i)) 25 | } 26 | if len(s.Values) <= stacks.InitialOperandStackHeight { 27 | t.Fail() 28 | } 29 | 30 | // verify the length is not shortened 31 | for i := 0; i < len(s.Values); i++ { 32 | _ = s.Pop() 33 | } 34 | 35 | if len(s.Values) <= stacks.InitialOperandStackHeight { 36 | t.Fail() 37 | } 38 | 39 | // for coverage OperandStack.Drop() 40 | // verify the length is not shortened 41 | for i := 0; i < len(s.Values); i++ { 42 | s.Drop() 43 | } 44 | 45 | if len(s.Values) <= stacks.InitialOperandStackHeight { 46 | t.Fail() 47 | } 48 | } 49 | 50 | func TestVirtualMachineLabelStack(t *testing.T) { 51 | s := stacks.NewLabelStack() 52 | if stacks.InitialLabelStackHeight != len(s.Values) { 53 | t.Fail() 54 | } 55 | 56 | exp := &stacks.Label{Arity: 100} 57 | s.Push(exp) 58 | if !reflect.DeepEqual(exp, s.Pop()) { 59 | t.Fail() 60 | } 61 | 62 | // verify the length grows 63 | for i := 0; i < stacks.InitialLabelStackHeight+1; i++ { 64 | s.Push(&stacks.Label{}) 65 | } 66 | if len(s.Values) <= stacks.InitialLabelStackHeight { 67 | t.Fail() 68 | } 69 | 70 | // verify the length is not shortened 71 | for i := 0; i < len(s.Values); i++ { 72 | _ = s.Pop() 73 | } 74 | 75 | if len(s.Values) <= stacks.InitialLabelStackHeight { 76 | t.Fail() 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /tollstation/toll.go: -------------------------------------------------------------------------------- 1 | package tollstation 2 | 3 | import ( 4 | "errors" 5 | "math" 6 | 7 | "github.com/c0mm4nd/wasman/expr" 8 | ) 9 | 10 | var ( 11 | // ErrTollOverflow occurs when the module's toll is larger than its cap 12 | ErrTollOverflow = errors.New("toll overflow") 13 | ) 14 | 15 | // TollStation is an interface which acts as a toll counter on the cost of one module 16 | type TollStation interface { 17 | GetOpPrice(expr.OpCode) uint64 18 | GetToll() uint64 19 | AddToll(uint64) error 20 | } 21 | 22 | // SimpleTollStation is a simple toll station which charge 1 unit toll per op/instr 23 | type SimpleTollStation struct { 24 | max uint64 25 | total uint64 26 | } 27 | 28 | // NewSimpleTollStation creates a new SimpleTollStation, by default the cap/max of toll is math.MaxUint64 29 | func NewSimpleTollStation(max uint64) *SimpleTollStation { 30 | if max == 0 { 31 | max = math.MaxUint64 32 | } 33 | 34 | totalCost := uint64(0) 35 | 36 | return &SimpleTollStation{ 37 | max: max, 38 | total: totalCost, 39 | } 40 | } 41 | 42 | // GetOpPrice will get the price of one opcode 43 | func (ts *SimpleTollStation) GetOpPrice(_ expr.OpCode) uint64 { 44 | return 1 45 | } 46 | 47 | // GetToll returns the total count in the toll station 48 | func (ts *SimpleTollStation) GetToll() uint64 { 49 | return ts.total 50 | } 51 | 52 | // AddToll adds 1 unit toll per opcode 53 | func (ts *SimpleTollStation) AddToll(toll uint64) error { 54 | if ts.total > ts.max-toll { 55 | return ErrTollOverflow 56 | } 57 | 58 | ts.total += toll 59 | return nil 60 | } 61 | -------------------------------------------------------------------------------- /types/func.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | 8 | "github.com/c0mm4nd/wasman/leb128decode" 9 | ) 10 | 11 | // FuncType classify the signature of functions, mapping a vector of parameters to a vector of results, written as follows. 12 | type FuncType struct { 13 | InputTypes []ValueType 14 | ReturnTypes []ValueType 15 | } 16 | 17 | // ReadFuncType will read a types.ReadFuncType from the io.Reader 18 | func ReadFuncType(r *bytes.Reader) (*FuncType, error) { 19 | b := make([]byte, 1) 20 | if _, err := io.ReadFull(r, b); err != nil { 21 | return nil, fmt.Errorf("read leading byte: %w", err) 22 | } 23 | 24 | if b[0] != 0x60 { 25 | return nil, fmt.Errorf("%w: %#x != 0x60", ErrInvalidTypeByte, b[0]) 26 | } 27 | 28 | s, _, err := leb128decode.DecodeUint32(r) 29 | if err != nil { 30 | return nil, fmt.Errorf("get the size of input value types: %w", err) 31 | } 32 | 33 | ip, err := ReadValueTypes(r, s) 34 | if err != nil { 35 | return nil, fmt.Errorf("read value types of inputs: %w", err) 36 | } 37 | 38 | s, _, err = leb128decode.DecodeUint32(r) 39 | if err != nil { 40 | return nil, fmt.Errorf("get the size of output value types: %w", err) 41 | } 42 | 43 | op, err := ReadValueTypes(r, s) 44 | if err != nil { 45 | return nil, fmt.Errorf("read value types of outputs: %w", err) 46 | } 47 | 48 | return &FuncType{ 49 | InputTypes: ip, 50 | ReturnTypes: op, 51 | }, nil 52 | } 53 | -------------------------------------------------------------------------------- /types/func_test.go: -------------------------------------------------------------------------------- 1 | package types_test 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "reflect" 7 | "strconv" 8 | "testing" 9 | 10 | "github.com/c0mm4nd/wasman/types" 11 | ) 12 | 13 | func TestReadFunctionType(t *testing.T) { 14 | t.Run("ng", func(t *testing.T) { 15 | buf := []byte{0x00} 16 | _, err := types.ReadFuncType(bytes.NewReader(buf)) 17 | if !errors.Is(err, types.ErrInvalidTypeByte) { 18 | t.Fail() 19 | t.Log(err) 20 | } 21 | }) 22 | 23 | for i, c := range []struct { 24 | bytes []byte 25 | exp *types.FuncType 26 | }{ 27 | { 28 | bytes: []byte{0x60, 0x0, 0x0}, 29 | exp: &types.FuncType{ 30 | InputTypes: []types.ValueType{}, 31 | ReturnTypes: []types.ValueType{}, 32 | }, 33 | }, 34 | { 35 | bytes: []byte{0x60, 0x2, 0x7f, 0x7e, 0x0}, 36 | exp: &types.FuncType{ 37 | InputTypes: []types.ValueType{types.ValueTypeI32, types.ValueTypeI64}, 38 | ReturnTypes: []types.ValueType{}, 39 | }, 40 | }, 41 | { 42 | bytes: []byte{0x60, 0x1, 0x7e, 0x2, 0x7f, 0x7e}, 43 | exp: &types.FuncType{ 44 | InputTypes: []types.ValueType{types.ValueTypeI64}, 45 | ReturnTypes: []types.ValueType{types.ValueTypeI32, types.ValueTypeI64}, 46 | }, 47 | }, 48 | { 49 | bytes: []byte{0x60, 0x0, 0x2, 0x7f, 0x7e}, 50 | exp: &types.FuncType{ 51 | InputTypes: []types.ValueType{}, 52 | ReturnTypes: []types.ValueType{types.ValueTypeI32, types.ValueTypeI64}, 53 | }, 54 | }, 55 | } { 56 | t.Run(strconv.Itoa(i), func(t *testing.T) { 57 | actual, err := types.ReadFuncType(bytes.NewReader(c.bytes)) 58 | if err != nil { 59 | t.Fail() 60 | } 61 | if !reflect.DeepEqual(c.exp, actual) { 62 | t.Fail() 63 | } 64 | }) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /types/global.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | ) 7 | 8 | // GlobalType classify global variables, which hold a value and can either be mutable or immutable. 9 | type GlobalType struct { 10 | ValType ValueType 11 | Mutable bool 12 | } 13 | 14 | // ReadGlobalType will read a types.GlobalType from the io.Reader 15 | func ReadGlobalType(r io.Reader) (*GlobalType, error) { 16 | vt, err := ReadValueTypes(r, 1) 17 | if err != nil { 18 | return nil, fmt.Errorf("read value type: %w", err) 19 | } 20 | 21 | ret := &GlobalType{ 22 | ValType: vt[0], 23 | } 24 | 25 | b := make([]byte, 1) 26 | if _, err := io.ReadFull(r, b); err != nil { 27 | return nil, fmt.Errorf("read mutablity: %w", err) 28 | } 29 | 30 | switch mut := b[0]; mut { 31 | case 0x00: 32 | case 0x01: 33 | ret.Mutable = true 34 | default: 35 | return nil, fmt.Errorf("%w for mutability: %#x != 0x00 or 0x01", ErrInvalidTypeByte, mut) 36 | } 37 | return ret, nil 38 | } 39 | -------------------------------------------------------------------------------- /types/global_test.go: -------------------------------------------------------------------------------- 1 | package types_test 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "reflect" 7 | "strconv" 8 | "testing" 9 | 10 | "github.com/c0mm4nd/wasman/types" 11 | ) 12 | 13 | func TestReadGlobalType(t *testing.T) { 14 | t.Run("ng", func(t *testing.T) { 15 | buf := []byte{0x7e, 0x3} 16 | _, err := types.ReadGlobalType(bytes.NewReader(buf)) 17 | if !errors.Is(err, types.ErrInvalidTypeByte) { 18 | t.Log(err) 19 | t.Fail() 20 | } 21 | }) 22 | 23 | for i, c := range []struct { 24 | bytes []byte 25 | exp *types.GlobalType 26 | }{ 27 | {bytes: []byte{0x7e, 0x00}, exp: &types.GlobalType{ValType: types.ValueTypeI64, Mutable: false}}, 28 | {bytes: []byte{0x7e, 0x01}, exp: &types.GlobalType{ValType: types.ValueTypeI64, Mutable: true}}, 29 | } { 30 | t.Run(strconv.Itoa(i), func(t *testing.T) { 31 | actual, err := types.ReadGlobalType(bytes.NewReader(c.bytes)) 32 | if err != nil { 33 | t.Fail() 34 | } 35 | if !reflect.DeepEqual(c.exp, actual) { 36 | t.Fail() 37 | } 38 | }) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /types/limits.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | 8 | "github.com/c0mm4nd/wasman/leb128decode" 9 | ) 10 | 11 | // Limits classify the size range of resizeable storage associated with memory types and table types 12 | // https://www.w3.org/TR/wasm-core-1/#limits%E2%91%A0 13 | type Limits struct { 14 | Min uint32 15 | Max *uint32 // can be nil 16 | } 17 | 18 | // ReadLimits will read a types.Limits from the io.Reader 19 | func ReadLimits(r *bytes.Reader) (*Limits, error) { 20 | b := make([]byte, 1) 21 | _, err := io.ReadFull(r, b) 22 | if err != nil { 23 | return nil, fmt.Errorf("read leading byte: %w", err) 24 | } 25 | 26 | ret := &Limits{} 27 | switch b[0] { 28 | case 0x00: 29 | ret.Min, _, err = leb128decode.DecodeUint32(r) 30 | if err != nil { 31 | return nil, fmt.Errorf("read min of limit: %w", err) 32 | } 33 | case 0x01: 34 | ret.Min, _, err = leb128decode.DecodeUint32(r) 35 | if err != nil { 36 | return nil, fmt.Errorf("read min of limit: %w", err) 37 | } 38 | m, _, err := leb128decode.DecodeUint32(r) 39 | if err != nil { 40 | return nil, fmt.Errorf("read min of limit: %w", err) 41 | } 42 | ret.Max = &m 43 | default: 44 | return nil, fmt.Errorf("%w for limits: %#x != 0x00 or 0x01", ErrInvalidTypeByte, b[0]) 45 | } 46 | return ret, nil 47 | } 48 | -------------------------------------------------------------------------------- /types/limits_test.go: -------------------------------------------------------------------------------- 1 | package types_test 2 | 3 | import ( 4 | "bytes" 5 | "reflect" 6 | "strconv" 7 | "testing" 8 | 9 | "github.com/c0mm4nd/wasman/utils" 10 | 11 | "github.com/c0mm4nd/wasman/types" 12 | ) 13 | 14 | func TestReadLimitsType(t *testing.T) { 15 | for i, c := range []struct { 16 | bytes []byte 17 | exp *types.Limits 18 | }{ 19 | {bytes: []byte{0x00, 0xa}, exp: &types.Limits{Min: 10}}, 20 | {bytes: []byte{0x01, 0xa, 0xa}, exp: &types.Limits{Min: 10, Max: utils.Uint32Ptr(10)}}, 21 | } { 22 | t.Run(strconv.Itoa(i), func(t *testing.T) { 23 | actual, err := types.ReadLimits(bytes.NewReader(c.bytes)) 24 | if err != nil { 25 | t.Fail() 26 | } 27 | if !reflect.DeepEqual(c.exp, actual) { 28 | t.Fail() 29 | } 30 | }) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /types/memory.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import "bytes" 4 | 5 | // MemoryType classify linear memories and their size range. 6 | // https://www.w3.org/TR/wasm-core-1/#memory-types%E2%91%A0 7 | type MemoryType = Limits 8 | 9 | // ReadMemoryType will read a types.MemoryType from the io.Reader 10 | func ReadMemoryType(r *bytes.Reader) (*MemoryType, error) { 11 | return ReadLimits(r) 12 | } 13 | -------------------------------------------------------------------------------- /types/memory_test.go: -------------------------------------------------------------------------------- 1 | package types_test 2 | 3 | import ( 4 | "bytes" 5 | "reflect" 6 | "strconv" 7 | "testing" 8 | 9 | "github.com/c0mm4nd/wasman/utils" 10 | 11 | "github.com/c0mm4nd/wasman/types" 12 | ) 13 | 14 | func TestReadMemoryType(t *testing.T) { 15 | for i, c := range []struct { 16 | bytes []byte 17 | exp *types.MemoryType 18 | }{ 19 | {bytes: []byte{0x00, 0xa}, exp: &types.MemoryType{Min: 10}}, 20 | {bytes: []byte{0x01, 0xa, 0xa}, exp: &types.MemoryType{Min: 10, Max: utils.Uint32Ptr(10)}}, 21 | } { 22 | t.Run(strconv.Itoa(i), func(t *testing.T) { 23 | actual, err := types.ReadMemoryType(bytes.NewReader(c.bytes)) 24 | if err != nil { 25 | t.Fail() 26 | } 27 | if !reflect.DeepEqual(c.exp, actual) { 28 | t.Fail() 29 | } 30 | }) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /types/table.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | ) 8 | 9 | // TableType classify tables over elements of element types within a size range. 10 | // https://www.w3.org/TR/wasm-core-1/#table-types%E2%91%A0 11 | type TableType struct { 12 | Elem byte 13 | Limits *Limits 14 | } 15 | 16 | // ReadTableType will read a types.TableType from the io.Reader 17 | func ReadTableType(r *bytes.Reader) (*TableType, error) { 18 | b := make([]byte, 1) 19 | if _, err := io.ReadFull(r, b); err != nil { 20 | return nil, fmt.Errorf("read leading byte: %w", err) 21 | } 22 | 23 | if b[0] != 0x70 { 24 | return nil, fmt.Errorf("%w: invalid element type %#x != %#x", ErrInvalidTypeByte, b[0], 0x70) 25 | } 26 | 27 | lm, err := ReadLimits(r) 28 | if err != nil { 29 | return nil, fmt.Errorf("read limits: %w", err) 30 | } 31 | 32 | return &TableType{ 33 | Elem: 0x70, 34 | Limits: lm, 35 | }, nil 36 | } 37 | -------------------------------------------------------------------------------- /types/table_test.go: -------------------------------------------------------------------------------- 1 | package types_test 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "reflect" 7 | "strconv" 8 | "testing" 9 | 10 | "github.com/c0mm4nd/wasman/utils" 11 | 12 | "github.com/c0mm4nd/wasman/types" 13 | ) 14 | 15 | func TestReadTableType(t *testing.T) { 16 | t.Run("ng", func(t *testing.T) { 17 | buf := []byte{0x00} 18 | _, err := types.ReadTableType(bytes.NewReader(buf)) 19 | if !errors.Is(err, types.ErrInvalidTypeByte) { 20 | t.Log(err) 21 | t.Fail() 22 | } 23 | }) 24 | 25 | for i, c := range []struct { 26 | bytes []byte 27 | exp *types.TableType 28 | }{ 29 | { 30 | bytes: []byte{0x70, 0x00, 0xa}, 31 | exp: &types.TableType{ 32 | Elem: 0x70, 33 | Limits: &types.Limits{Min: 10}, 34 | }, 35 | }, 36 | { 37 | bytes: []byte{0x70, 0x01, 0x01, 0xa}, 38 | exp: &types.TableType{ 39 | Elem: 0x70, 40 | Limits: &types.Limits{Min: 1, Max: utils.Uint32Ptr(10)}, 41 | }, 42 | }, 43 | } { 44 | c := c 45 | t.Run(strconv.Itoa(i), func(t *testing.T) { 46 | actual, err := types.ReadTableType(bytes.NewReader(c.bytes)) 47 | if err != nil { 48 | t.Fail() 49 | } 50 | if !reflect.DeepEqual(c.exp, actual) { 51 | t.Fail() 52 | } 53 | }) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /types/value.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "io" 8 | 9 | "github.com/c0mm4nd/wasman/leb128decode" 10 | ) 11 | 12 | // ErrInvalidTypeByte means the type byte mismatches the one from wasm binary 13 | var ErrInvalidTypeByte = errors.New("invalid byte") 14 | 15 | // ValueType classifies the individual values that WebAssembly code can compute with and the values that a variable accepts 16 | // https://www.w3.org/TR/wasm-core-1/#value-types%E2%91%A0 17 | type ValueType byte 18 | 19 | const ( 20 | // ValueTypeI32 classify 32 bit integers 21 | ValueTypeI32 ValueType = 0x7f 22 | // ValueTypeI64 classify 64 bit integers 23 | // Integers are not inherently signed or unsigned, the interpretation is determined by individual operations 24 | ValueTypeI64 ValueType = 0x7e 25 | // ValueTypeF32 classify 32 bit floating-point data, known as single 26 | ValueTypeF32 ValueType = 0x7d 27 | // ValueTypeF64 classify 64 bit floating-point data, known as double 28 | ValueTypeF64 ValueType = 0x7c 29 | ) 30 | 31 | // String will convert the types.ValueType into a string 32 | func (v ValueType) String() string { 33 | switch v { 34 | case ValueTypeI32: 35 | return "i32" 36 | case ValueTypeI64: 37 | return "i64" 38 | case ValueTypeF32: 39 | return "f32" 40 | case ValueTypeF64: 41 | return "f64" 42 | default: 43 | return "unknown value type" 44 | } 45 | } 46 | 47 | // ReadValueTypes will read a types.ValueType from the io.Reader 48 | func ReadValueTypes(r io.Reader, num uint32) ([]ValueType, error) { 49 | ret := make([]ValueType, num) 50 | buf := make([]byte, num) 51 | _, err := io.ReadFull(r, buf) 52 | if err != nil { 53 | return nil, err 54 | } 55 | 56 | for i, v := range buf { 57 | switch vt := ValueType(v); vt { 58 | case ValueTypeI32, ValueTypeF32, ValueTypeI64, ValueTypeF64: 59 | ret[i] = vt 60 | default: 61 | return nil, fmt.Errorf("invalid value type: %d", vt) 62 | } 63 | } 64 | return ret, nil 65 | } 66 | 67 | // ReadNameValue will read a name string from the io.Reader 68 | func ReadNameValue(r *bytes.Reader) (string, error) { 69 | vs, _, err := leb128decode.DecodeUint32(r) 70 | if err != nil { 71 | return "", fmt.Errorf("read size of name: %w", err) 72 | } 73 | 74 | buf := make([]byte, vs) 75 | if _, err := io.ReadFull(r, buf); err != nil { 76 | return "", fmt.Errorf("read bytes of name: %w", err) 77 | } 78 | 79 | return string(buf), nil 80 | } 81 | 82 | // HasSameSignature will verify whether the two types.ValueType are same 83 | func HasSameSignature(a []ValueType, b []ValueType) bool { 84 | if len(a) != len(b) { 85 | return false 86 | } 87 | 88 | for i := range a { 89 | if a[i] != b[i] { 90 | return false 91 | } 92 | } 93 | 94 | return true 95 | } 96 | -------------------------------------------------------------------------------- /types/value_test.go: -------------------------------------------------------------------------------- 1 | package types_test 2 | 3 | import ( 4 | "bytes" 5 | "reflect" 6 | "strconv" 7 | "testing" 8 | 9 | "github.com/c0mm4nd/wasman/types" 10 | ) 11 | 12 | func TestReadValueTypes(t *testing.T) { 13 | for i, c := range []struct { 14 | bytes []byte 15 | num uint32 16 | exp []types.ValueType 17 | }{ 18 | { 19 | bytes: []byte{0x7e}, num: 1, exp: []types.ValueType{types.ValueTypeI64}, 20 | }, 21 | { 22 | bytes: []byte{0x7f, 0x7e}, num: 2, exp: []types.ValueType{types.ValueTypeI32, types.ValueTypeI64}, 23 | }, 24 | { 25 | bytes: []byte{0x7f, 0x7e, 0x7d}, num: 2, exp: []types.ValueType{types.ValueTypeI32, types.ValueTypeI64}, 26 | }, 27 | { 28 | bytes: []byte{0x7f, 0x7e, 0x7d, 0x7c}, num: 4, 29 | exp: []types.ValueType{types.ValueTypeI32, types.ValueTypeI64, types.ValueTypeF32, types.ValueTypeF64}, 30 | }, 31 | } { 32 | t.Run(strconv.Itoa(i), func(t *testing.T) { 33 | actual, err := types.ReadValueTypes(bytes.NewReader(c.bytes), c.num) 34 | if err != nil { 35 | t.Fail() 36 | } 37 | if !reflect.DeepEqual(c.exp, actual) { 38 | t.Fail() 39 | } 40 | }) 41 | } 42 | } 43 | 44 | func TestReadNameValue(t *testing.T) { 45 | exp := "abcdefgh你好" 46 | l := len(exp) 47 | buf := []byte{byte(l)} 48 | buf = append(buf, exp...) 49 | actual, err := types.ReadNameValue(bytes.NewReader(buf)) 50 | if err != nil { 51 | t.Fail() 52 | } 53 | if !reflect.DeepEqual(exp, actual) { 54 | t.Fail() 55 | } 56 | } 57 | 58 | func TestHasSameValues(t *testing.T) { 59 | for _, c := range []struct { 60 | a, b []types.ValueType 61 | exp bool 62 | }{ 63 | {a: []types.ValueType{}, exp: true}, 64 | {a: []types.ValueType{}, b: []types.ValueType{}, exp: true}, 65 | {a: []types.ValueType{types.ValueTypeF64}, exp: false}, 66 | {a: []types.ValueType{types.ValueTypeF64}, b: []types.ValueType{types.ValueTypeF64}, exp: true}, 67 | } { 68 | if !reflect.DeepEqual(c.exp, types.HasSameSignature(c.a, c.b)) { 69 | t.Fail() 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /utils/float.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "encoding/binary" 5 | "io" 6 | "math" 7 | ) 8 | 9 | // ReadFloat32 reads a float32 following IEEE 754 10 | func ReadFloat32(r io.Reader) (float32, error) { 11 | buf := make([]byte, 4) 12 | 13 | _, err := io.ReadFull(r, buf) 14 | if err != nil { 15 | return 0, err 16 | } 17 | 18 | raw := binary.LittleEndian.Uint32(buf) 19 | return math.Float32frombits(raw), nil 20 | } 21 | 22 | // ReadFloat64 reads a float64 following IEEE 754 23 | func ReadFloat64(r io.Reader) (float64, error) { 24 | buf := make([]byte, 8) 25 | 26 | _, err := io.ReadFull(r, buf) 27 | if err != nil { 28 | return 0, err 29 | } 30 | 31 | raw := binary.LittleEndian.Uint64(buf) 32 | return math.Float64frombits(raw), nil 33 | } 34 | -------------------------------------------------------------------------------- /utils/float_test.go: -------------------------------------------------------------------------------- 1 | package utils_test 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "math" 7 | "reflect" 8 | "testing" 9 | 10 | "github.com/c0mm4nd/wasman/utils" 11 | ) 12 | 13 | func TestReadFloat32(t *testing.T) { 14 | var exp float32 = 3.1231231231 15 | bs := make([]byte, 4) 16 | binary.LittleEndian.PutUint32(bs, math.Float32bits(exp)) 17 | actual, err := utils.ReadFloat32(bytes.NewReader(bs)) 18 | if err != nil { 19 | t.Fail() 20 | } 21 | if !reflect.DeepEqual(exp, actual) { 22 | t.Fail() 23 | } 24 | } 25 | 26 | func TestReadFloat64(t *testing.T) { 27 | exp := 3.1231231231 28 | bs := make([]byte, 8) 29 | binary.LittleEndian.PutUint64(bs, math.Float64bits(exp)) 30 | 31 | actual, err := utils.ReadFloat64(bytes.NewReader(bs)) 32 | if err != nil { 33 | t.Fail() 34 | } 35 | if !reflect.DeepEqual(exp, actual) { 36 | t.Fail() 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /utils/page.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | func CalcPageSize(contentLen int, pageSize int) int { 4 | n := contentLen / pageSize 5 | if n*pageSize < contentLen { 6 | n++ 7 | } 8 | 9 | return n 10 | } 11 | -------------------------------------------------------------------------------- /utils/ptr.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | func Uint32Ptr(u uint32) *uint32 { 4 | return &u 5 | } 6 | -------------------------------------------------------------------------------- /wasm/func_host.go: -------------------------------------------------------------------------------- 1 | package wasm 2 | 3 | import ( 4 | "math" 5 | "reflect" 6 | 7 | "github.com/c0mm4nd/wasman/types" 8 | ) 9 | 10 | // HostFunc is an implement of wasm.Fn, 11 | // which represents all the functions defined under host(golang) environment 12 | type HostFunc struct { 13 | Signature *types.FuncType // the shape of func (defined by inputs and outputs) 14 | 15 | // Generator is a func defined by other dev which acts as a Generator to the function 16 | // (generate when NewInstance's func initializing 17 | Generator func(ins *Instance) interface{} 18 | 19 | // function is the generated func from Generator, should be set at the time of wasm instance creation 20 | function interface{} 21 | } 22 | 23 | func (f *HostFunc) getType() *types.FuncType { 24 | return f.Signature 25 | } 26 | 27 | func (f *HostFunc) call(ins *Instance) error { 28 | fnVal := reflect.ValueOf(f.function) 29 | ty := fnVal.Type() 30 | in := make([]reflect.Value, ty.NumIn()) 31 | 32 | for i := len(in) - 1; i >= 0; i-- { 33 | val := reflect.New(ty.In(i)).Elem() 34 | raw := ins.OperandStack.Pop() 35 | kind := ty.In(i).Kind() 36 | 37 | switch kind { 38 | case reflect.Float64, reflect.Float32: 39 | val.SetFloat(math.Float64frombits(raw)) 40 | case reflect.Uint32, reflect.Uint64: 41 | val.SetUint(raw) 42 | case reflect.Int32, reflect.Int64: 43 | val.SetInt(int64(raw)) 44 | default: 45 | return ErrFuncInvalidInputType 46 | } 47 | in[i] = val 48 | } 49 | 50 | for _, val := range fnVal.Call(in) { 51 | switch val.Kind() { 52 | case reflect.Float64, reflect.Float32: 53 | ins.OperandStack.Push(math.Float64bits(val.Float())) 54 | case reflect.Uint32, reflect.Uint64: 55 | ins.OperandStack.Push(val.Uint()) 56 | case reflect.Int32, reflect.Int64: 57 | ins.OperandStack.Push(uint64(val.Int())) 58 | default: 59 | return ErrFuncInvalidReturnType 60 | } 61 | } 62 | 63 | return nil 64 | } 65 | -------------------------------------------------------------------------------- /wasm/func_interface.go: -------------------------------------------------------------------------------- 1 | package wasm 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/c0mm4nd/wasman/types" 7 | ) 8 | 9 | // errors of func 10 | var ( 11 | ErrFuncInvalidInputType = errors.New("invalid func input type") 12 | ErrFuncInvalidReturnType = errors.New("invalid func return type") 13 | ) 14 | 15 | // fn is an instance of the func value 16 | type fn interface { 17 | getType() *types.FuncType 18 | call(ins *Instance) error 19 | } 20 | -------------------------------------------------------------------------------- /wasm/func_test.go: -------------------------------------------------------------------------------- 1 | package wasm 2 | 3 | import ( 4 | "math" 5 | "testing" 6 | 7 | "github.com/c0mm4nd/wasman/expr" 8 | "github.com/c0mm4nd/wasman/stacks" 9 | "github.com/c0mm4nd/wasman/types" 10 | ) 11 | 12 | func TestHostFunction_Call(t *testing.T) { 13 | var cnt int64 14 | f := func(in int64) (int32, int64, float32, float64) { 15 | cnt += in 16 | return 1, 2, 3, 4 17 | } 18 | hf := &HostFunc{ 19 | function: f, 20 | Signature: &types.FuncType{ 21 | InputTypes: []types.ValueType{types.ValueTypeI64}, 22 | ReturnTypes: []types.ValueType{types.ValueTypeI32, types.ValueTypeI64, types.ValueTypeF32, types.ValueTypeF64}, 23 | }, 24 | } 25 | 26 | vm := &Instance{OperandStack: stacks.NewOperandStack()} 27 | vm.OperandStack.Push(10) 28 | err := hf.call(vm) 29 | if err != nil { 30 | t.Fail() 31 | } 32 | if vm.OperandStack.Ptr != 3 { 33 | t.Fail() 34 | } 35 | if cnt != 10 { 36 | t.Fail() 37 | } 38 | 39 | // f64 40 | if math.Float64frombits(vm.OperandStack.Pop()) != 4.0 { 41 | t.Fail() 42 | } 43 | if float32(math.Float64frombits(vm.OperandStack.Pop())) != 3.0 { 44 | t.Fail() 45 | } 46 | if vm.OperandStack.Pop() != 2 { 47 | t.Fail() 48 | } 49 | if vm.OperandStack.Pop() != 1 { 50 | t.Fail() 51 | } 52 | } 53 | 54 | func TestNativeFunction_Call(t *testing.T) { 55 | n := &wasmFunc{ 56 | signature: &types.FuncType{}, 57 | body: []byte{ 58 | byte(expr.OpCodeI64Const), 0x05, byte(expr.OpCodeReturn), 59 | }, 60 | } 61 | vm := &Instance{ 62 | Module: new(Module), 63 | OperandStack: stacks.NewOperandStack(), 64 | Active: &Frame{ 65 | PC: 1000, 66 | }, 67 | FrameStack: &stacks.Stack[*Frame]{ 68 | Ptr: -1, 69 | Values: make([]*Frame, stacks.InitialLabelStackHeight), 70 | }, 71 | } 72 | if n.call(vm) != nil { 73 | t.Fail() 74 | } 75 | if vm.OperandStack.Pop() != 0x05 { 76 | t.Fail() 77 | } 78 | if vm.Active.PC != 1000 { 79 | t.Fail() 80 | } 81 | } 82 | 83 | func TestVirtualMachine_execNativeFunction(t *testing.T) { 84 | n := &wasmFunc{ 85 | signature: &types.FuncType{}, 86 | body: []byte{ 87 | byte(expr.OpCodeI64Const), 0x05, 88 | byte(expr.OpCodeI64Const), 0x01, 89 | byte(expr.OpCodeReturn), 90 | }, 91 | } 92 | vm := &Instance{ 93 | Module: new(Module), 94 | OperandStack: stacks.NewOperandStack(), 95 | Active: &Frame{ 96 | Func: n, 97 | }, 98 | } 99 | 100 | if vm.execFunc() != nil { 101 | t.Fail() 102 | } 103 | if vm.Active.PC != 4 { 104 | t.Fail() 105 | } 106 | if vm.OperandStack.Pop() != 0x01 { 107 | t.Fail() 108 | } 109 | if vm.OperandStack.Pop() != 0x05 { 110 | t.Fail() 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /wasm/func_webassembly.go: -------------------------------------------------------------------------------- 1 | package wasm 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/c0mm4nd/wasman/stacks" 7 | "github.com/c0mm4nd/wasman/types" 8 | ) 9 | 10 | type wasmFunc struct { 11 | signature *types.FuncType // the shape of func (defined by inputs and outputs) 12 | NumLocal uint32 // index id in local 13 | body []byte // body 14 | Blocks map[uint64]*funcBlock // instr blocks inside the func 15 | } 16 | 17 | type funcBlock struct { 18 | StartAt uint64 19 | ElseAt uint64 20 | EndAt uint64 21 | 22 | BlockType *types.FuncType 23 | BlockTypeBytes uint64 24 | } 25 | 26 | func (f *wasmFunc) getType() *types.FuncType { 27 | return f.signature 28 | } 29 | 30 | func (f *wasmFunc) call(ins *Instance) (err error) { 31 | al := len(f.signature.InputTypes) 32 | locals := make([]uint64, f.NumLocal+uint32(al)) 33 | for i := 0; i < al; i++ { 34 | locals[al-1-i] = ins.OperandStack.Pop() 35 | } 36 | 37 | prevPtr := ins.FrameStack.Ptr 38 | if ins.Recover { 39 | defer func() { 40 | if v := recover(); v != nil { 41 | ins.FrameStack.Ptr = prevPtr 42 | var ok bool 43 | err, ok = v.(error) 44 | if !ok { 45 | err = fmt.Errorf("runtime error: %v", v) 46 | } 47 | } 48 | }() 49 | } 50 | 51 | prev := ins.Active 52 | frame := &Frame{ 53 | Func: f, 54 | Locals: locals, 55 | LabelStack: stacks.NewLabelStack(), 56 | } 57 | ins.FrameStack.Push(frame) 58 | ins.Active = frame 59 | 60 | err = ins.execFunc() 61 | if err != nil { 62 | return err 63 | } 64 | 65 | ins.Active = prev 66 | 67 | return nil 68 | } 69 | -------------------------------------------------------------------------------- /wasm/global.go: -------------------------------------------------------------------------------- 1 | package wasm 2 | 3 | import "github.com/c0mm4nd/wasman/types" 4 | 5 | // Global is an instance of the global value 6 | type Global struct { 7 | *types.GlobalType 8 | Val interface{} 9 | } 10 | -------------------------------------------------------------------------------- /wasm/instance.go: -------------------------------------------------------------------------------- 1 | package wasm 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | "math" 8 | 9 | "github.com/c0mm4nd/wasman/config" 10 | 11 | "github.com/c0mm4nd/wasman/stacks" 12 | 13 | "github.com/c0mm4nd/wasman/leb128decode" 14 | ) 15 | 16 | // Instance is an instantiated module 17 | type Instance struct { 18 | *Module 19 | 20 | Active *Frame 21 | FrameStack *stacks.Stack[*Frame] 22 | 23 | Functions []fn 24 | Memory *Memory 25 | Globals []uint64 26 | 27 | OperandStack *stacks.Stack[uint64] 28 | } 29 | 30 | // NewInstance will instantiate the module with extern modules 31 | func NewInstance(module *Module, externModules map[string]*Module) (*Instance, error) { 32 | ins := &Instance{ 33 | Module: module, 34 | OperandStack: stacks.NewOperandStack(), 35 | FrameStack: &stacks.Stack[*Frame]{ 36 | Ptr: -1, 37 | Values: make([]*Frame, stacks.InitialLabelStackHeight), 38 | }, 39 | } 40 | 41 | if err := ins.buildIndexSpaces(externModules); err != nil { 42 | return nil, fmt.Errorf("build index space: %w", err) 43 | } 44 | 45 | // initializing memory 46 | ins.Memory = ins.Module.IndexSpace.Memories[0] 47 | if diff := uint64(ins.Module.MemorySection[0].Min)*uint64(config.DefaultMemoryPageSize) - uint64(len(ins.Memory.Value)); diff > 0 { 48 | ins.Memory.Value = append(ins.Memory.Value, make([]byte, diff)...) 49 | } 50 | 51 | // initializing functions 52 | ins.Functions = make([]fn, len(ins.Module.IndexSpace.Functions)) 53 | for i, f := range ins.Module.IndexSpace.Functions { 54 | if wasmFn, ok := f.(*HostFunc); ok { 55 | wasmFn.function = wasmFn.Generator(ins) 56 | ins.Functions[i] = wasmFn 57 | } else { 58 | ins.Functions[i] = f 59 | } 60 | } 61 | 62 | // initialize global 63 | ins.Globals = make([]uint64, len(ins.Module.IndexSpace.Globals)) 64 | for i, raw := range ins.Module.IndexSpace.Globals { 65 | switch v := raw.Val.(type) { 66 | case int32: 67 | ins.Globals[i] = uint64(v) 68 | case int64: 69 | ins.Globals[i] = uint64(v) 70 | case float32: 71 | ins.Globals[i] = uint64(math.Float32bits(v)) 72 | case float64: 73 | ins.Globals[i] = math.Float64bits(v) 74 | } 75 | } 76 | 77 | // exec start functions 78 | for _, id := range ins.Module.StartSection { 79 | if int(id) >= len(ins.Functions) { 80 | return nil, ErrFuncIndexOutOfRange 81 | } 82 | 83 | err := ins.Functions[id].call(ins) 84 | if err != nil { 85 | return nil, err 86 | } 87 | } 88 | 89 | return ins, nil 90 | } 91 | 92 | func (ins *Instance) fetchInt32() (int32, error) { 93 | ret, num, err := leb128decode.DecodeInt32(bytes.NewReader( 94 | ins.Active.Func.body[ins.Active.PC:])) 95 | if err != nil { 96 | return 0, err 97 | } 98 | ins.Active.PC += num - 1 99 | 100 | return ret, nil 101 | } 102 | 103 | func (ins *Instance) fetchUint32() (uint32, error) { 104 | ret, num, err := leb128decode.DecodeUint32(bytes.NewReader( 105 | ins.Active.Func.body[ins.Active.PC:])) 106 | if err != nil { 107 | return 0, err 108 | } 109 | 110 | ins.Active.PC += num - 1 111 | 112 | return ret, nil 113 | } 114 | 115 | func (ins *Instance) fetchInt64() (int64, error) { 116 | ret, num, err := leb128decode.DecodeInt64(bytes.NewReader( 117 | ins.Active.Func.body[ins.Active.PC:])) 118 | if err != nil { 119 | return 0, err 120 | } 121 | 122 | ins.Active.PC += num - 1 123 | 124 | return ret, nil 125 | } 126 | 127 | func (ins *Instance) fetchFloat32() (float32, error) { 128 | v := math.Float32frombits(binary.LittleEndian.Uint32( 129 | ins.Active.Func.body[ins.Active.PC:])) 130 | ins.Active.PC += 3 131 | 132 | return v, nil 133 | } 134 | 135 | func (ins *Instance) fetchFloat64() (float64, error) { 136 | v := math.Float64frombits(binary.LittleEndian.Uint64( 137 | ins.Active.Func.body[ins.Active.PC:])) 138 | ins.Active.PC += 7 139 | 140 | return v, nil 141 | } 142 | -------------------------------------------------------------------------------- /wasm/instance_exec.go: -------------------------------------------------------------------------------- 1 | package wasm 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | 8 | "github.com/c0mm4nd/wasman/expr" 9 | "github.com/c0mm4nd/wasman/leb128decode" 10 | "github.com/c0mm4nd/wasman/segments" 11 | "github.com/c0mm4nd/wasman/types" 12 | "github.com/c0mm4nd/wasman/utils" 13 | ) 14 | 15 | // errors on exec func 16 | var ( 17 | ErrExportedFuncNotFound = errors.New("exported func is not found") 18 | ErrFuncIndexOutOfRange = errors.New("function index out of range") 19 | ErrInvalidArgNum = errors.New("invalid number of arguments") 20 | ) 21 | 22 | func (ins *Instance) execExpr(expression *expr.Expression) (v interface{}, err error) { 23 | r := bytes.NewReader(expression.Data) 24 | switch expression.OpCode { 25 | case expr.OpCodeI32Const: 26 | v, _, err = leb128decode.DecodeInt32(r) 27 | if err != nil { 28 | return nil, fmt.Errorf("read int32: %w", err) 29 | } 30 | case expr.OpCodeI64Const: 31 | v, _, err = leb128decode.DecodeInt64(r) 32 | if err != nil { 33 | return nil, fmt.Errorf("read int64: %w", err) 34 | } 35 | case expr.OpCodeF32Const: 36 | v, err = utils.ReadFloat32(r) 37 | if err != nil { 38 | return nil, fmt.Errorf("read f34: %w", err) 39 | } 40 | case expr.OpCodeF64Const: 41 | v, err = utils.ReadFloat64(r) 42 | if err != nil { 43 | return nil, fmt.Errorf("read f64: %w", err) 44 | } 45 | case expr.OpCodeGlobalGet: 46 | id, _, err := leb128decode.DecodeUint32(r) 47 | if err != nil { 48 | return nil, fmt.Errorf("read index of global: %w", err) 49 | } 50 | if uint32(len(ins.IndexSpace.Globals)) <= id { 51 | return nil, fmt.Errorf("global index out of range") 52 | } 53 | v = ins.IndexSpace.Globals[id].Val 54 | default: 55 | return nil, fmt.Errorf("invalid opt code: %#x", expression.OpCode) 56 | } 57 | return v, nil 58 | } 59 | 60 | func (ins *Instance) execFunc() error { 61 | for ; int(ins.Active.PC) < len(ins.Active.Func.body); ins.Active.PC++ { 62 | opByte := ins.Active.Func.body[ins.Active.PC] 63 | op := expr.OpCode(opByte) 64 | err := instructions[op](ins) 65 | if err != nil { 66 | return err 67 | } 68 | 69 | // Toll 70 | if ins.Module.ModuleConfig.TollStation != nil { 71 | price := ins.TollStation.GetOpPrice(op) 72 | err := ins.TollStation.AddToll(price) 73 | if err != nil { 74 | return err 75 | } 76 | } 77 | 78 | if op == expr.OpCodeReturn { 79 | return nil 80 | } 81 | } 82 | 83 | return nil 84 | } 85 | 86 | // CallExportedFunc will call the func `name` with the args 87 | // TODO: enhance this 88 | func (ins *Instance) CallExportedFunc(name string, args ...uint64) (returns []uint64, returnTypes []types.ValueType, err error) { 89 | exp, ok := ins.Module.ExportSection[name] 90 | if !ok || exp.Desc.Kind != segments.KindFunction { 91 | return nil, nil, ErrExportedFuncNotFound 92 | } 93 | 94 | if int(exp.Desc.Index) >= len(ins.Functions) { 95 | return nil, nil, ErrFuncIndexOutOfRange 96 | } 97 | 98 | f := ins.Functions[exp.Desc.Index] 99 | if len(f.getType().InputTypes) != len(args) { 100 | return nil, nil, ErrInvalidArgNum 101 | } 102 | 103 | for i := range args { 104 | ins.OperandStack.Push(args[i]) 105 | } 106 | 107 | err = f.call(ins) 108 | if err != nil { 109 | return nil, nil, err 110 | } 111 | 112 | ret := make([]uint64, len(f.getType().ReturnTypes)) 113 | for i := range ret { 114 | ret[len(ret)-1-i] = ins.OperandStack.Pop() 115 | } 116 | 117 | return ret, f.getType().ReturnTypes, nil 118 | } 119 | -------------------------------------------------------------------------------- /wasm/instance_init.go: -------------------------------------------------------------------------------- 1 | package wasm 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | 7 | "github.com/c0mm4nd/wasman/config" 8 | "github.com/c0mm4nd/wasman/expr" 9 | "github.com/c0mm4nd/wasman/leb128decode" 10 | "github.com/c0mm4nd/wasman/segments" 11 | "github.com/c0mm4nd/wasman/types" 12 | ) 13 | 14 | // buildIndexSpaces build index spaces of the module with the given external modules 15 | func (ins *Instance) buildIndexSpaces(externModules map[string]*Module) error { 16 | ins.IndexSpace = &IndexSpace{} 17 | 18 | // resolve imports 19 | if err := ins.resolveImports(externModules); err != nil { 20 | return fmt.Errorf("resolve imports: %w", err) 21 | } 22 | 23 | // fill in the gap between the definition and imported ones in index spaces 24 | // note: MVP restricts the size of memory index spaces to 1 25 | if diff := len(ins.TableSection) - len(ins.IndexSpace.Tables); diff > 0 { 26 | for i := 0; i < diff; i++ { 27 | ins.IndexSpace.Tables = append(ins.IndexSpace.Tables, &Table{ 28 | TableType: *ins.TableSection[i+len(ins.IndexSpace.Tables)], 29 | Value: []*uint32{}, 30 | }) 31 | } 32 | } 33 | 34 | // fill in the gap between the definition and imported ones in index spaces 35 | // note: MVP restricts the size of memory index spaces to 1 36 | if diff := len(ins.MemorySection) - len(ins.IndexSpace.Memories); diff > 0 { 37 | for i := 0; i < diff; i++ { 38 | ins.IndexSpace.Memories = append(ins.IndexSpace.Memories, &Memory{ 39 | MemoryType: *ins.MemorySection[i+len(ins.IndexSpace.Memories)], 40 | Value: []byte{}, 41 | }) 42 | } 43 | } 44 | 45 | if err := ins.buildGlobalIndexSpace(); err != nil { 46 | return fmt.Errorf("build global index space: %w", err) 47 | } 48 | if err := ins.buildFunctionIndexSpace(); err != nil { 49 | return fmt.Errorf("build function index space: %w", err) 50 | } 51 | if err := ins.buildTableIndexSpace(); err != nil { 52 | return fmt.Errorf("build table index space: %w", err) 53 | } 54 | if err := ins.buildMemoryIndexSpace(); err != nil { 55 | return fmt.Errorf("build memory index space: %w", err) 56 | } 57 | 58 | return nil 59 | } 60 | 61 | func (ins *Instance) resolveImports(externModules map[string]*Module) error { 62 | for _, is := range ins.ImportSection { 63 | em, ok := externModules[is.Module] 64 | if !ok { 65 | return fmt.Errorf("failed to resolve import of module name %s", is.Module) 66 | } 67 | 68 | es, ok := em.ExportSection[is.Name] 69 | if !ok { 70 | return fmt.Errorf("%s not exported in module %s", is.Name, is.Module) 71 | } 72 | 73 | if is.Desc.Kind != es.Desc.Kind { 74 | return fmt.Errorf("type mismatch on export: got %#x but want %#x", es.Desc.Kind, is.Desc.Kind) 75 | } 76 | switch is.Desc.Kind { 77 | case 0x00: // function 78 | if err := ins.applyFunctionImport(is, em, es); err != nil { 79 | return fmt.Errorf("applyFunctionImport failed: %w", err) 80 | } 81 | case 0x01: // table 82 | if err := ins.applyTableImport(em, es); err != nil { 83 | return fmt.Errorf("applyTableImport failed: %w", err) 84 | } 85 | case 0x02: // memory 86 | if err := ins.applyMemoryImport(em, es); err != nil { 87 | return fmt.Errorf("applyMemoryImport: %w", err) 88 | } 89 | case 0x03: // global 90 | if err := ins.applyGlobalImport(em, es); err != nil { 91 | return fmt.Errorf("applyGlobalImport: %w", err) 92 | } 93 | default: 94 | return fmt.Errorf("invalid kind of import: %#x", is.Desc.Kind) 95 | } 96 | } 97 | return nil 98 | } 99 | 100 | func (ins *Instance) applyFunctionImport(importSeg *segments.ImportSegment, externModule *Module, exportSeg *segments.ExportSegment) error { 101 | if exportSeg.Desc.Index >= uint32(len(externModule.IndexSpace.Functions)) { 102 | return fmt.Errorf("exported index out of range") 103 | } 104 | 105 | if importSeg.Desc.TypeIndexPtr == nil { 106 | return fmt.Errorf("is.Desc.TypeIndexPtr is nill") 107 | } 108 | 109 | iSig := ins.TypeSection[*importSeg.Desc.TypeIndexPtr] 110 | f := externModule.IndexSpace.Functions[exportSeg.Desc.Index] 111 | if !types.HasSameSignature(iSig.ReturnTypes, f.getType().ReturnTypes) { 112 | return fmt.Errorf("return signature mimatch: %#v != %#v", iSig.ReturnTypes, f.getType().ReturnTypes) 113 | } else if !types.HasSameSignature(iSig.InputTypes, f.getType().InputTypes) { 114 | return fmt.Errorf("input signature mimatch: %#v != %#v", iSig.InputTypes, f.getType().InputTypes) 115 | } 116 | ins.IndexSpace.Functions = append(ins.IndexSpace.Functions, f) 117 | return nil 118 | } 119 | 120 | func (ins *Instance) applyTableImport(externModule *Module, exportSeg *segments.ExportSegment) error { 121 | if exportSeg.Desc.Index >= uint32(len(externModule.IndexSpace.Tables)) { 122 | return fmt.Errorf("exported index out of range") 123 | } 124 | 125 | // note: MVP restricts the size of table index spaces to 1 126 | ins.IndexSpace.Tables = append(ins.IndexSpace.Tables, externModule.IndexSpace.Tables[exportSeg.Desc.Index]) 127 | return nil 128 | } 129 | 130 | func (ins *Instance) applyMemoryImport(externModule *Module, exportSegment *segments.ExportSegment) error { 131 | if exportSegment.Desc.Index >= uint32(len(externModule.IndexSpace.Memories)) { 132 | return fmt.Errorf("exported index out of range") 133 | } 134 | 135 | // note: MVP restricts the size of memory index spaces to 1 136 | ins.IndexSpace.Memories = append(ins.IndexSpace.Memories, externModule.IndexSpace.Memories[exportSegment.Desc.Index]) 137 | return nil 138 | } 139 | 140 | func (ins *Instance) applyGlobalImport(externModule *Module, exportSegment *segments.ExportSegment) error { 141 | if exportSegment.Desc.Index >= uint32(len(externModule.IndexSpace.Globals)) { 142 | return fmt.Errorf("exported index out of range") 143 | } 144 | 145 | gb := externModule.IndexSpace.Globals[exportSegment.Desc.Index] 146 | if gb.GlobalType.Mutable { 147 | return fmt.Errorf("cannot import mutable global") 148 | } 149 | 150 | ins.IndexSpace.Globals = append(externModule.IndexSpace.Globals, gb) 151 | return nil 152 | } 153 | 154 | func (ins *Instance) buildGlobalIndexSpace() error { 155 | for _, gs := range ins.GlobalSection { 156 | v, err := ins.execExpr(gs.Init) 157 | if err != nil { 158 | return fmt.Errorf("execution failed: %w", err) 159 | } 160 | ins.IndexSpace.Globals = append(ins.IndexSpace.Globals, &Global{ 161 | GlobalType: gs.Type, 162 | Val: v, 163 | }) 164 | } 165 | return nil 166 | } 167 | 168 | func (ins *Instance) buildFunctionIndexSpace() error { 169 | for codeIndex, typeIndex := range ins.FunctionSection { 170 | if typeIndex >= uint32(len(ins.TypeSection)) { 171 | return fmt.Errorf("function type index out of range") 172 | } else if codeIndex >= len(ins.CodeSection) { 173 | return fmt.Errorf("code index out of range") 174 | } 175 | 176 | f := &wasmFunc{ 177 | signature: ins.TypeSection[typeIndex], 178 | body: ins.CodeSection[codeIndex].Body, 179 | NumLocal: ins.CodeSection[codeIndex].NumLocals, 180 | } 181 | 182 | brs, err := ins.parseBlocks(f.body) 183 | if err != nil { 184 | return fmt.Errorf("parse blocks: %w", err) 185 | } 186 | 187 | f.Blocks = brs 188 | ins.IndexSpace.Functions = append(ins.IndexSpace.Functions, f) 189 | } 190 | 191 | return nil 192 | } 193 | 194 | func (ins *Instance) buildMemoryIndexSpace() error { 195 | for _, d := range ins.Module.DataSection { 196 | // note: MVP restricts the size of memory index spaces to 1 197 | if d.MemoryIndex >= uint32(len(ins.IndexSpace.Memories)) { 198 | return fmt.Errorf("index out of range of index space") 199 | } else if d.MemoryIndex >= uint32(len(ins.MemorySection)) { 200 | return fmt.Errorf("index out of range of memory section") 201 | } 202 | 203 | rawOffset, err := ins.execExpr(d.OffsetExpression) 204 | if err != nil { 205 | return fmt.Errorf("calculate offset: %w", err) 206 | } 207 | 208 | offset, ok := rawOffset.(int32) 209 | if !ok { 210 | return fmt.Errorf("type assertion failed") 211 | } 212 | 213 | size := int(offset) + len(d.Init) 214 | if ins.MemorySection[d.MemoryIndex].Max != nil && uint32(size) > *(ins.MemorySection[d.MemoryIndex].Max)*config.DefaultMemoryPageSize { 215 | return fmt.Errorf("memory size out of limit %d * 64Ki", int(*(ins.MemorySection[d.MemoryIndex].Max))) 216 | } 217 | 218 | memory := ins.IndexSpace.Memories[d.MemoryIndex] 219 | if size > len(memory.Value) { 220 | next := make([]byte, size) 221 | copy(next, memory.Value) 222 | copy(next[offset:], d.Init) 223 | ins.IndexSpace.Memories[d.MemoryIndex].Value = next 224 | } else { 225 | copy(memory.Value[offset:], d.Init) 226 | } 227 | } 228 | return nil 229 | } 230 | 231 | func (ins *Instance) buildTableIndexSpace() error { 232 | for _, elem := range ins.ElementsSection { 233 | // note: MVP restricts the size of memory index spaces to 1 234 | if elem.TableIndex >= uint32(len(ins.IndexSpace.Tables)) { 235 | return fmt.Errorf("index out of range of index space") 236 | } else if elem.TableIndex >= uint32(len(ins.TableSection)) { 237 | // this is just in case since we could assume len(SecTables) == len(indexSpace.Table) 238 | return fmt.Errorf("index out of range of table section") 239 | } 240 | 241 | rawOffset, err := ins.execExpr(elem.OffsetExpr) 242 | if err != nil { 243 | return fmt.Errorf("calculate offset: %w", err) 244 | } 245 | 246 | offset32, ok := rawOffset.(int32) 247 | if !ok { 248 | return fmt.Errorf("type assertion failed") 249 | } 250 | 251 | offset := int(offset32) 252 | size := offset + len(elem.Init) 253 | if ins.TableSection[elem.TableIndex].Limits.Max != nil && 254 | size > int(*(ins.TableSection[elem.TableIndex].Limits.Max)) { 255 | return fmt.Errorf("table size out of limit of %d", int(*(ins.TableSection[elem.TableIndex].Limits.Max))) 256 | } 257 | 258 | table := ins.IndexSpace.Tables[elem.TableIndex] 259 | if size > len(table.Value) { 260 | next := make([]*uint32, size) 261 | copy(next, table.Value) 262 | for i := range elem.Init { 263 | next[i+offset] = &elem.Init[i] 264 | } 265 | ins.IndexSpace.Tables[elem.TableIndex].Value = next 266 | } else { 267 | for i := range elem.Init { 268 | table.Value[i+offset] = &elem.Init[i] 269 | } 270 | } 271 | } 272 | return nil 273 | } 274 | 275 | type blockType = types.FuncType 276 | 277 | func (ins *Instance) readBlockType(r *bytes.Reader) (*blockType, uint64, error) { 278 | raw, l, err := leb128decode.DecodeInt33AsInt64(r) 279 | if err != nil { 280 | return nil, 0, fmt.Errorf("decode int33: %w", err) 281 | } 282 | 283 | var ret *blockType 284 | switch raw { 285 | case -64: // 0x40 in original byte = nil 286 | ret = &blockType{} 287 | case -1: // 0x7f in original byte = i32 288 | ret = &blockType{ReturnTypes: []types.ValueType{types.ValueTypeI32}} 289 | case -2: // 0x7e in original byte = i64 290 | ret = &blockType{ReturnTypes: []types.ValueType{types.ValueTypeI64}} 291 | case -3: // 0x7d in original byte = f32 292 | ret = &blockType{ReturnTypes: []types.ValueType{types.ValueTypeF32}} 293 | case -4: // 0x7c in original byte = f64 294 | ret = &blockType{ReturnTypes: []types.ValueType{types.ValueTypeF64}} 295 | default: 296 | if raw < 0 || (raw >= int64(len(ins.TypeSection))) { 297 | return nil, 0, fmt.Errorf("invalid block type: %d", raw) 298 | } 299 | ret = ins.TypeSection[raw] 300 | } 301 | return ret, l, nil 302 | } 303 | 304 | func (ins *Instance) parseBlocks(body []byte) (map[uint64]*funcBlock, error) { 305 | ret := map[uint64]*funcBlock{} 306 | stack := make([]*funcBlock, 0) 307 | for pc := uint64(0); pc < uint64(len(body)); pc++ { 308 | rawOc := body[pc] 309 | if 0x28 <= rawOc && rawOc <= 0x3e { // memory load,store 310 | pc++ 311 | // align 312 | _, l, err := leb128decode.DecodeUint32(bytes.NewReader(body[pc:])) 313 | if err != nil { 314 | return nil, fmt.Errorf("read memory align: %w", err) 315 | } 316 | pc += l 317 | // offset 318 | _, l, err = leb128decode.DecodeUint32(bytes.NewReader(body[pc:])) 319 | if err != nil { 320 | return nil, fmt.Errorf("read memory offset: %w", err) 321 | } 322 | pc += l - 1 323 | continue 324 | } else if 0x41 <= rawOc && rawOc <= 0x44 { // const instructions 325 | pc++ 326 | switch expr.OpCode(rawOc) { 327 | case expr.OpCodeI32Const: 328 | _, l, err := leb128decode.DecodeInt32(bytes.NewReader(body[pc:])) 329 | if err != nil { 330 | return nil, fmt.Errorf("read immediate: %w", err) 331 | } 332 | pc += l - 1 333 | case expr.OpCodeI64Const: 334 | _, l, err := leb128decode.DecodeInt64(bytes.NewReader(body[pc:])) 335 | if err != nil { 336 | return nil, fmt.Errorf("read immediate: %w", err) 337 | } 338 | pc += l - 1 339 | case expr.OpCodeF32Const: 340 | pc += 3 341 | case expr.OpCodeF64Const: 342 | pc += 7 343 | } 344 | continue 345 | } else if (0x3f <= rawOc && rawOc <= 0x40) || // memory grow,size 346 | (0x20 <= rawOc && rawOc <= 0x24) || // variable instructions 347 | (0x0c <= rawOc && rawOc <= 0x0d) || // br,br_if instructions 348 | (0x10 <= rawOc && rawOc <= 0x11) { // call,call_indirect 349 | pc++ 350 | _, l, err := leb128decode.DecodeUint32(bytes.NewReader(body[pc:])) 351 | if err != nil { 352 | return nil, fmt.Errorf("read immediate: %w", err) 353 | } 354 | pc += l - 1 355 | if rawOc == 0x11 { // if call_indirect 356 | pc++ 357 | } 358 | continue 359 | } else if rawOc == 0x0e { // br_table 360 | pc++ 361 | r := bytes.NewReader(body[pc:]) 362 | nl, num, err := leb128decode.DecodeUint32(r) 363 | if err != nil { 364 | return nil, fmt.Errorf("read immediate: %w", err) 365 | } 366 | 367 | for i := uint32(0); i < nl; i++ { 368 | _, n, err := leb128decode.DecodeUint32(r) 369 | if err != nil { 370 | return nil, fmt.Errorf("read immediate: %w", err) 371 | } 372 | num += n 373 | } 374 | 375 | _, l, err := leb128decode.DecodeUint32(r) 376 | if err != nil { 377 | return nil, fmt.Errorf("read immediate: %w", err) 378 | } 379 | pc += l + num - 1 380 | continue 381 | } 382 | 383 | switch expr.OpCode(rawOc) { 384 | case expr.OpCodeBlock, expr.OpCodeIf, expr.OpCodeLoop: 385 | bt, l, err := ins.readBlockType(bytes.NewReader(body[pc+1:])) 386 | if err != nil { 387 | return nil, fmt.Errorf("read block: %w", err) 388 | } 389 | stack = append(stack, &funcBlock{ 390 | StartAt: pc, 391 | BlockType: bt, 392 | BlockTypeBytes: l, 393 | }) 394 | pc += l 395 | case expr.OpCodeElse: 396 | stack[len(stack)-1].ElseAt = pc 397 | case expr.OpCodeEnd: 398 | bl := stack[len(stack)-1] 399 | stack = stack[:len(stack)-1] 400 | bl.EndAt = pc 401 | ret[bl.StartAt] = bl 402 | } 403 | } 404 | 405 | if len(stack) > 0 { 406 | return nil, fmt.Errorf("ill-nested block exists") 407 | } 408 | 409 | return ret, nil 410 | } 411 | -------------------------------------------------------------------------------- /wasm/instr.go: -------------------------------------------------------------------------------- 1 | package wasm 2 | 3 | import ( 4 | "github.com/c0mm4nd/wasman/expr" 5 | "github.com/c0mm4nd/wasman/stacks" 6 | ) 7 | 8 | // Frame is the context data of one instance 9 | type Frame struct { 10 | PC uint64 11 | Func *wasmFunc 12 | Locals []uint64 13 | LabelStack *stacks.Stack[*stacks.Label] 14 | } 15 | 16 | // instructions are basic wasm instructions 17 | var instructions = [256]func(ins *Instance) error{ 18 | expr.OpCodeUnreachable: unreachable, 19 | expr.OpCodeNop: nop, 20 | expr.OpCodeBlock: block, 21 | expr.OpCodeLoop: loop, 22 | expr.OpCodeIf: ifOp, 23 | expr.OpCodeElse: elseOp, 24 | expr.OpCodeEnd: end, 25 | expr.OpCodeBr: br, 26 | expr.OpCodeBrIf: brIf, 27 | expr.OpCodeBrTable: brTable, 28 | expr.OpCodeReturn: nop, 29 | expr.OpCodeCall: call, 30 | expr.OpCodeCallIndirect: callIndirect, 31 | expr.OpCodeDrop: drop, 32 | expr.OpCodeSelect: selectOp, 33 | expr.OpCodeLocalGet: getLocal, 34 | expr.OpCodeLocalSet: setLocal, 35 | expr.OpCodeLocalTee: teeLocal, 36 | expr.OpCodeGlobalGet: getGlobal, 37 | expr.OpCodeGlobalSet: setGlobal, 38 | expr.OpCodeI32Load: i32Load, 39 | expr.OpCodeI64Load: i64Load, 40 | expr.OpCodeF32Load: f32Load, 41 | expr.OpCodeF64Load: f64Load, 42 | expr.OpCodeI32Load8s: i32Load8s, 43 | expr.OpCodeI32Load8u: i32Load8u, 44 | expr.OpCodeI32Load16s: i32Load16s, 45 | expr.OpCodeI32Load16u: i32Load16u, 46 | expr.OpCodeI64Load8s: i64Load8s, 47 | expr.OpCodeI64Load8u: i64Load8u, 48 | expr.OpCodeI64Load16s: i64Load16s, 49 | expr.OpCodeI64Load16u: i64Load16u, 50 | expr.OpCodeI64Load32s: i64Load32s, 51 | expr.OpCodeI64Load32u: i64Load32u, 52 | expr.OpCodeI32Store: i32Store, 53 | expr.OpCodeI64Store: i64Store, 54 | expr.OpCodeF32Store: f32Store, 55 | expr.OpCodeF64Store: f64Store, 56 | expr.OpCodeI32Store8: i32Store8, 57 | expr.OpCodeI32Store16: i32Store16, 58 | expr.OpCodeI64Store8: i64Store8, 59 | expr.OpCodeI64Store16: i64Store16, 60 | expr.OpCodeI64Store32: i64Store32, 61 | expr.OpCodeMemorySize: memorySize, 62 | expr.OpCodeMemoryGrow: memoryGrow, 63 | expr.OpCodeI32Const: i32Const, 64 | expr.OpCodeI64Const: i64Const, 65 | expr.OpCodeF32Const: f32Const, 66 | expr.OpCodeF64Const: f64Const, 67 | expr.OpCodeI32Eqz: i32eqz, 68 | expr.OpCodeI32Eq: i32eq, 69 | expr.OpCodeI32Ne: i32ne, 70 | expr.OpCodeI32LtS: i32lts, 71 | expr.OpCodeI32LtU: i32ltu, 72 | expr.OpCodeI32GtS: i32gts, 73 | expr.OpCodeI32GtU: i32gtu, 74 | expr.OpCodeI32LeS: i32les, 75 | expr.OpCodeI32LeU: i32leu, 76 | expr.OpCodeI32GeS: i32ges, 77 | expr.OpCodeI32GeU: i32geu, 78 | expr.OpCodeI64Eqz: i64eqz, 79 | expr.OpCodeI64Eq: i64eq, 80 | expr.OpCodeI64Ne: i64ne, 81 | expr.OpCodeI64LtS: i64lts, 82 | expr.OpCodeI64LtU: i64ltu, 83 | expr.OpCodeI64GtS: i64gts, 84 | expr.OpCodeI64GtU: i64gtu, 85 | expr.OpCodeI64LeS: i64les, 86 | expr.OpCodeI64LeU: i64leu, 87 | expr.OpCodeI64GeS: i64ges, 88 | expr.OpCodeI64GeU: i64geu, 89 | expr.OpCodeF32Eq: f32eq, 90 | expr.OpCodeF32Ne: f32ne, 91 | expr.OpCodeF32Lt: f32lt, 92 | expr.OpCodeF32Gt: f32gt, 93 | expr.OpCodeF32Le: f32le, 94 | expr.OpCodeF32Ge: f32ge, 95 | expr.OpCodeF64Eq: f64eq, 96 | expr.OpCodeF64Ne: f64ne, 97 | expr.OpCodeF64Lt: f64lt, 98 | expr.OpCodeF64Gt: f64gt, 99 | expr.OpCodeF64Le: f64le, 100 | expr.OpCodeF64Ge: f64ge, 101 | expr.OpCodeI32Clz: i32clz, 102 | expr.OpCodeI32Ctz: i32ctz, 103 | expr.OpCodeI32PopCnt: i32popcnt, 104 | expr.OpCodeI32Add: i32add, 105 | expr.OpCodeI32Sub: i32sub, 106 | expr.OpCodeI32Mul: i32mul, 107 | expr.OpCodeI32DivS: i32divs, 108 | expr.OpCodeI32DivU: i32divu, 109 | expr.OpCodeI32RemS: i32rems, 110 | expr.OpCodeI32RemU: i32remu, 111 | expr.OpCodeI32And: i32and, 112 | expr.OpCodeI32Or: i32or, 113 | expr.OpCodeI32Xor: i32xor, 114 | expr.OpCodeI32Shl: i32shl, 115 | expr.OpCodeI32ShrS: i32shrs, 116 | expr.OpCodeI32ShrU: i32shru, 117 | expr.OpCodeI32RotL: i32rotl, 118 | expr.OpCodeI32RotR: i32rotr, 119 | expr.OpCodeI64Clz: i64clz, 120 | expr.OpCodeI64Ctz: i64ctz, 121 | expr.OpCodeI64PopCnt: i64popcnt, 122 | expr.OpCodeI64Add: i64add, 123 | expr.OpCodeI64Sub: i64sub, 124 | expr.OpCodeI64Mul: i64mul, 125 | expr.OpCodeI64DivS: i64divs, 126 | expr.OpCodeI64DivU: i64divu, 127 | expr.OpCodeI64RemS: i64rems, 128 | expr.OpCodeI64RemU: i64remu, 129 | expr.OpCodeI64And: i64and, 130 | expr.OpCodeI64Or: i64or, 131 | expr.OpCodeI64Xor: i64xor, 132 | expr.OpCodeI64Shl: i64shl, 133 | expr.OpCodeI64ShrS: i64shrs, 134 | expr.OpCodeI64ShrU: i64shru, 135 | expr.OpCodeI64RotL: i64rotl, 136 | expr.OpCodeI64RotR: i64rotr, 137 | expr.OpCodeF32Abs: f32abs, 138 | expr.OpCodeF32Neg: f32neg, 139 | expr.OpCodeF32Ceil: f32ceil, 140 | expr.OpCodeF32Floor: f32floor, 141 | expr.OpCodeF32Trunc: f32trunc, 142 | expr.OpCodeF32Nearest: f32nearest, 143 | expr.OpCodeF32Sqrt: f32sqrt, 144 | expr.OpCodeF32Add: f32add, 145 | expr.OpCodeF32Sub: f32sub, 146 | expr.OpCodeF32Mul: f32mul, 147 | expr.OpCodeF32Div: f32div, 148 | expr.OpCodeF32Min: f32min, 149 | expr.OpCodeF32Max: f32max, 150 | expr.OpCodeF32CopySign: f32copysign, 151 | expr.OpCodeF64Abs: f64abs, 152 | expr.OpCodeF64Neg: f64neg, 153 | expr.OpCodeF64Ceil: f64ceil, 154 | expr.OpCodeF64Floor: f64floor, 155 | expr.OpCodeF64Trunc: f64trunc, 156 | expr.OpCodeF64Nearest: f64nearest, 157 | expr.OpCodeF64Sqrt: f64sqrt, 158 | expr.OpCodeF64Add: f64add, 159 | expr.OpCodeF64Sub: f64sub, 160 | expr.OpCodeF64Mul: f64mul, 161 | expr.OpCodeF64Div: f64div, 162 | expr.OpCodeF64Min: f64min, 163 | expr.OpCodeF64Max: f64max, 164 | expr.OpCodeF64CopySign: f64copysign, 165 | expr.OpCodeI32WrapI64: i32wrapi64, 166 | expr.OpCodeI32TruncF32S: i32truncf32s, 167 | expr.OpCodeI32TruncF32U: i32truncf32u, 168 | expr.OpCodeI32truncF64S: i32truncf64s, 169 | expr.OpCodeI32truncF64U: i32truncf64u, 170 | expr.OpCodeI64ExtendI32S: i64extendi32s, 171 | expr.OpCodeI64ExtendI32U: i64extendi32u, 172 | expr.OpCodeI64TruncF32S: i64truncf32s, 173 | expr.OpCodeI64TruncF32U: i64truncf32u, 174 | expr.OpCodeI64TruncF64S: i64truncf64s, 175 | expr.OpCodeI64TruncF64U: i64truncf64u, 176 | expr.OpCodeF32ConvertI32S: f32converti32s, 177 | expr.OpCodeF32ConvertI32U: f32converti32u, 178 | expr.OpCodeF32ConvertI64S: f32converti64s, 179 | expr.OpCodeF32ConvertI64U: f32converti64u, 180 | expr.OpCodeF32DemoteF64: f32demotef64, 181 | expr.OpCodeF64ConvertI32S: f64converti32s, 182 | expr.OpCodeF64ConvertI32U: f64converti32u, 183 | expr.OpCodeF64ConvertI64S: f64converti64s, 184 | expr.OpCodeF64ConvertI64U: f64converti64u, 185 | expr.OpCodeF64PromoteF32: f64promotef32, 186 | expr.OpCodeI32ReinterpretF32: nop, 187 | expr.OpCodeI64ReinterpretF64: nop, 188 | expr.OpCodeF32ReinterpretI32: nop, 189 | expr.OpCodeF64ReinterpretI64: nop, 190 | } 191 | -------------------------------------------------------------------------------- /wasm/instr_arg.go: -------------------------------------------------------------------------------- 1 | package wasm 2 | 3 | func drop(ins *Instance) error { 4 | ins.OperandStack.Drop() 5 | 6 | return nil 7 | } 8 | 9 | func selectOp(ins *Instance) error { 10 | c := ins.OperandStack.Pop() 11 | v2 := ins.OperandStack.Pop() 12 | if c == 0 { 13 | _ = ins.OperandStack.Pop() 14 | ins.OperandStack.Push(v2) 15 | } 16 | 17 | return nil 18 | } 19 | -------------------------------------------------------------------------------- /wasm/instr_arg_test.go: -------------------------------------------------------------------------------- 1 | package wasm 2 | -------------------------------------------------------------------------------- /wasm/instr_const.go: -------------------------------------------------------------------------------- 1 | package wasm 2 | 3 | import ( 4 | "math" 5 | ) 6 | 7 | func i32Const(ins *Instance) error { 8 | ins.Active.PC++ 9 | 10 | v, err := ins.fetchInt32() 11 | if err != nil { 12 | return err 13 | } 14 | 15 | ins.OperandStack.Push(uint64(v)) 16 | 17 | return nil 18 | } 19 | 20 | func i64Const(ins *Instance) error { 21 | ins.Active.PC++ 22 | 23 | v, err := ins.fetchInt64() 24 | if err != nil { 25 | return err 26 | } 27 | 28 | ins.OperandStack.Push(uint64(v)) 29 | 30 | return nil 31 | } 32 | 33 | func f32Const(ins *Instance) error { 34 | ins.Active.PC++ 35 | 36 | v, err := ins.fetchFloat32() 37 | if err != nil { 38 | return err 39 | } 40 | 41 | ins.OperandStack.Push(uint64(math.Float32bits(v))) 42 | 43 | return nil 44 | } 45 | 46 | func f64Const(ins *Instance) error { 47 | ins.Active.PC++ 48 | 49 | v, err := ins.fetchFloat64() 50 | if err != nil { 51 | return err 52 | } 53 | 54 | ins.OperandStack.Push(math.Float64bits(v)) 55 | 56 | return nil 57 | } 58 | -------------------------------------------------------------------------------- /wasm/instr_const_test.go: -------------------------------------------------------------------------------- 1 | package wasm 2 | 3 | import ( 4 | "math" 5 | "testing" 6 | 7 | "github.com/c0mm4nd/wasman/expr" 8 | "github.com/c0mm4nd/wasman/stacks" 9 | ) 10 | 11 | func Test_i32Const(t *testing.T) { 12 | ctx := &Frame{ 13 | Func: &wasmFunc{ 14 | body: []byte{byte(expr.OpCodeI32Const), 0x05}, 15 | }, 16 | } 17 | 18 | vm := &Instance{ 19 | Active: ctx, 20 | OperandStack: stacks.NewOperandStack(), 21 | } 22 | err := i32Const(vm) 23 | if err != nil { 24 | t.Fail() 25 | } 26 | if uint32(vm.OperandStack.Pop()) != 0x05 { 27 | t.Fail() 28 | } 29 | if vm.OperandStack.Ptr != -1 { 30 | t.Fail() 31 | } 32 | } 33 | 34 | func Test_i64Const(t *testing.T) { 35 | ctx := &Frame{ 36 | Func: &wasmFunc{ 37 | body: []byte{byte(expr.OpCodeI64Const), 0x05}, 38 | }, 39 | } 40 | 41 | vm := &Instance{ 42 | Active: ctx, 43 | OperandStack: stacks.NewOperandStack(), 44 | } 45 | err := i64Const(vm) 46 | if err != nil { 47 | t.Fail() 48 | } 49 | if vm.OperandStack.Pop() != 0x05 { 50 | t.Fail() 51 | } 52 | if vm.OperandStack.Ptr != -1 { 53 | t.Fail() 54 | } 55 | } 56 | 57 | func Test_f32Const(t *testing.T) { 58 | 59 | ctx := &Frame{ 60 | Func: &wasmFunc{ 61 | body: []byte{byte(expr.OpCodeF32Const), 0x00, 0x00, 0x80, 0x3f}, 62 | }, 63 | } 64 | 65 | vm := &Instance{ 66 | Active: ctx, 67 | OperandStack: stacks.NewOperandStack(), 68 | } 69 | err := f32Const(vm) 70 | if err != nil { 71 | t.Fail() 72 | } 73 | if math.Float32frombits(uint32(vm.OperandStack.Pop())) != 1.0 { 74 | t.Fail() 75 | } 76 | if vm.OperandStack.Ptr != -1 { 77 | t.Fail() 78 | } 79 | } 80 | 81 | func Test_f64Const(t *testing.T) { 82 | ctx := &Frame{ 83 | Func: &wasmFunc{ 84 | body: []byte{byte(expr.OpCodeF64Const), 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3f}, 85 | }, 86 | } 87 | 88 | vm := &Instance{ 89 | Active: ctx, 90 | OperandStack: stacks.NewOperandStack(), 91 | } 92 | err := f64Const(vm) 93 | if err != nil { 94 | t.Fail() 95 | } 96 | if math.Float64frombits(vm.OperandStack.Pop()) != 1.0 { 97 | t.Fail() 98 | } 99 | if vm.OperandStack.Ptr != -1 { 100 | t.Fail() 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /wasm/instr_control.go: -------------------------------------------------------------------------------- 1 | package wasm 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | 7 | "github.com/c0mm4nd/wasman/leb128decode" 8 | "github.com/c0mm4nd/wasman/stacks" 9 | "github.com/c0mm4nd/wasman/types" 10 | ) 11 | 12 | // errors on control instr 13 | var ( 14 | ErrUnreachable = errors.New("unreachable") 15 | ErrBlockNotInitialized = errors.New("block not initialized") 16 | ErrBlockNotFound = errors.New("block not found") 17 | ErrFuncSignMismatch = errors.New("function signature mismatch") 18 | ErrLabelNotFound = errors.New("label not found") 19 | ErrTableIndexOutOfRange = errors.New("table index out of range") 20 | ErrTableInstanceNotInitialized = errors.New("table entry not initialized") 21 | ) 22 | 23 | func unreachable(_ *Instance) error { 24 | return ErrUnreachable 25 | } 26 | 27 | func nop(_ *Instance) error { 28 | return nil 29 | } 30 | 31 | func block(ins *Instance) error { 32 | ctx := ins.Active 33 | block, ok := ctx.Func.Blocks[ctx.PC] 34 | if !ok { 35 | return ErrBlockNotInitialized 36 | } 37 | 38 | ctx.PC += block.BlockTypeBytes 39 | ctx.LabelStack.Push(&stacks.Label{ 40 | Arity: len(block.BlockType.ReturnTypes), 41 | ContinuationPC: block.EndAt, 42 | EndPC: block.EndAt, 43 | }) 44 | 45 | return nil 46 | } 47 | 48 | func loop(ins *Instance) error { 49 | ctx := ins.Active 50 | block, ok := ctx.Func.Blocks[ctx.PC] 51 | if !ok { 52 | return ErrBlockNotFound 53 | } 54 | ctx.PC += block.BlockTypeBytes 55 | ctx.LabelStack.Push(&stacks.Label{ 56 | Arity: len(block.BlockType.ReturnTypes), 57 | ContinuationPC: block.StartAt - 1, 58 | EndPC: block.EndAt, 59 | }) 60 | 61 | return nil 62 | } 63 | 64 | func ifOp(ins *Instance) error { 65 | ctx := ins.Active 66 | block, ok := ctx.Func.Blocks[ins.Active.PC] 67 | if !ok { 68 | return ErrBlockNotInitialized 69 | } 70 | ctx.PC += block.BlockTypeBytes 71 | 72 | if ins.OperandStack.Pop() == 0 && // means false, turn to else codes 73 | block.ElseAt > block.StartAt { 74 | // enter else 75 | ins.Active.PC = block.ElseAt 76 | } 77 | 78 | ctx.LabelStack.Push(&stacks.Label{ 79 | Arity: len(block.BlockType.ReturnTypes), 80 | ContinuationPC: block.EndAt, 81 | EndPC: block.EndAt, 82 | }) 83 | 84 | return nil 85 | } 86 | 87 | func elseOp(ins *Instance) error { 88 | l := ins.Active.LabelStack.Pop() 89 | ins.Active.PC = l.EndPC 90 | 91 | return nil 92 | } 93 | 94 | func end(ins *Instance) error { 95 | if ins.Active.LabelStack.Ptr > -1 { 96 | _ = ins.Active.LabelStack.Pop() 97 | } 98 | 99 | return nil 100 | } 101 | 102 | func br(ins *Instance) error { 103 | ins.Active.PC++ 104 | index, err := ins.fetchUint32() 105 | if err != nil { 106 | return err 107 | } 108 | 109 | return branchAt(ins, index) 110 | } 111 | 112 | func branchAt(ins *Instance, index uint32) error { 113 | var l *stacks.Label 114 | 115 | for i := uint32(0); i < index+1; i++ { 116 | l = ins.Active.LabelStack.Pop() 117 | } 118 | 119 | if l == nil { 120 | return ErrLabelNotFound 121 | } 122 | 123 | ins.Active.PC = l.ContinuationPC 124 | 125 | return nil 126 | } 127 | 128 | func brIf(ins *Instance) error { 129 | ins.Active.PC++ 130 | index, err := ins.fetchUint32() 131 | if err != nil { 132 | return err 133 | } 134 | 135 | c := ins.OperandStack.Pop() 136 | if c != 0 { 137 | return branchAt(ins, index) 138 | } 139 | 140 | return nil 141 | } 142 | 143 | func brTable(ins *Instance) error { 144 | ins.Active.PC++ 145 | r := bytes.NewReader(ins.Active.Func.body[ins.Active.PC:]) 146 | nl, num, err := leb128decode.DecodeUint32(r) 147 | if err != nil { 148 | return err 149 | } 150 | 151 | lis := make([]uint32, nl) 152 | for i := range lis { 153 | li, n, err := leb128decode.DecodeUint32(r) 154 | if err != nil { 155 | return err 156 | } 157 | num += n 158 | lis[i] = li 159 | } 160 | 161 | ln, n, err := leb128decode.DecodeUint32(r) 162 | if err != nil { 163 | return err 164 | } 165 | ins.Active.PC += n + num 166 | 167 | i := ins.OperandStack.Pop() 168 | if uint32(i) < nl { 169 | return branchAt(ins, lis[i]) 170 | } 171 | 172 | return branchAt(ins, ln) 173 | } 174 | 175 | func call(ins *Instance) error { 176 | ins.Active.PC++ 177 | index, err := ins.fetchUint32() 178 | if err != nil { 179 | return err 180 | } 181 | 182 | err = ins.Functions[index].call(ins) 183 | if err != nil { 184 | return err 185 | } 186 | 187 | return nil 188 | } 189 | 190 | func callIndirect(ins *Instance) error { 191 | ins.Active.PC++ 192 | index, err := ins.fetchUint32() 193 | if err != nil { 194 | return err 195 | } 196 | 197 | expType := ins.Module.TypeSection[index] 198 | 199 | tableIndex := ins.OperandStack.Pop() 200 | // note: mvp limits the size of table index space to 1 201 | if tableIndex >= uint64(len(ins.Module.IndexSpace.Tables[0].Value)) { 202 | return ErrTableIndexOutOfRange 203 | } 204 | 205 | te := ins.Module.IndexSpace.Tables[0].Value[tableIndex] 206 | if te == nil { 207 | return ErrTableInstanceNotInitialized 208 | } 209 | 210 | f := ins.Functions[*te] 211 | ft := f.getType() 212 | if !types.HasSameSignature(ft.InputTypes, expType.InputTypes) || 213 | !types.HasSameSignature(ft.ReturnTypes, expType.ReturnTypes) { 214 | return ErrFuncSignMismatch 215 | } 216 | 217 | err = f.call(ins) 218 | if err != nil { 219 | return err 220 | } 221 | 222 | ins.Active.PC++ // skip 0x00 223 | 224 | return nil 225 | } 226 | -------------------------------------------------------------------------------- /wasm/instr_control_test.go: -------------------------------------------------------------------------------- 1 | package wasm 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/c0mm4nd/wasman/utils" 8 | 9 | "github.com/c0mm4nd/wasman/expr" 10 | "github.com/c0mm4nd/wasman/stacks" 11 | "github.com/c0mm4nd/wasman/types" 12 | ) 13 | 14 | func Test_block(t *testing.T) { 15 | ctx := &Frame{ 16 | PC: 1, 17 | Func: &wasmFunc{ 18 | Blocks: map[uint64]*funcBlock{ 19 | 1: { 20 | StartAt: 1, 21 | EndAt: 100, 22 | BlockTypeBytes: 3, 23 | BlockType: &types.FuncType{ReturnTypes: []types.ValueType{types.ValueTypeI32}}, 24 | }, 25 | }, 26 | }, 27 | LabelStack: stacks.NewLabelStack(), 28 | } 29 | if block(&Instance{Active: ctx}) != nil { 30 | t.Fail() 31 | } 32 | if !reflect.DeepEqual(&stacks.Label{ 33 | Arity: 1, 34 | ContinuationPC: 100, 35 | EndPC: 100, 36 | }, ctx.LabelStack.Values[ctx.LabelStack.Ptr]) { 37 | t.Fail() 38 | } 39 | if ctx.PC != 4 { 40 | t.Fail() 41 | } 42 | } 43 | 44 | func Test_loop(t *testing.T) { 45 | ctx := &Frame{ 46 | PC: 1, 47 | Func: &wasmFunc{ 48 | Blocks: map[uint64]*funcBlock{ 49 | 1: { 50 | StartAt: 1, 51 | EndAt: 100, 52 | BlockTypeBytes: 3, 53 | BlockType: &types.FuncType{ReturnTypes: []types.ValueType{types.ValueTypeI32}}, 54 | }, 55 | }, 56 | }, 57 | LabelStack: stacks.NewLabelStack(), 58 | } 59 | if loop(&Instance{Active: ctx}) != nil { 60 | t.Fail() 61 | } 62 | if !reflect.DeepEqual(&stacks.Label{ 63 | Arity: 1, 64 | ContinuationPC: 0, 65 | EndPC: 100, 66 | }, ctx.LabelStack.Values[ctx.LabelStack.Ptr]) { 67 | t.Fail() 68 | } 69 | if ctx.PC != 4 { 70 | t.Fail() 71 | } 72 | } 73 | 74 | func Test_ifOp(t *testing.T) { 75 | t.Run("true", func(t *testing.T) { 76 | ctx := &Frame{ 77 | PC: 1, 78 | Func: &wasmFunc{ 79 | Blocks: map[uint64]*funcBlock{ 80 | 1: { 81 | StartAt: 1, 82 | EndAt: 100, 83 | BlockTypeBytes: 3, 84 | BlockType: &types.FuncType{ReturnTypes: []types.ValueType{types.ValueTypeI32}}, 85 | }, 86 | }, 87 | }, 88 | LabelStack: stacks.NewLabelStack(), 89 | } 90 | vm := &Instance{Active: ctx, OperandStack: stacks.NewOperandStack()} 91 | vm.OperandStack.Push(1) 92 | if ifOp(vm) != nil { 93 | t.Fail() 94 | } 95 | if !reflect.DeepEqual(&stacks.Label{ 96 | Arity: 1, 97 | ContinuationPC: 100, 98 | EndPC: 100, 99 | }, ctx.LabelStack.Values[ctx.LabelStack.Ptr]) { 100 | t.Fail() 101 | } 102 | if ctx.PC != 4 { 103 | t.Fail() 104 | } 105 | }) 106 | t.Run("false", func(t *testing.T) { 107 | ctx := &Frame{ 108 | PC: 1, 109 | Func: &wasmFunc{ 110 | Blocks: map[uint64]*funcBlock{ 111 | 1: { 112 | StartAt: 1, 113 | ElseAt: 50, 114 | EndAt: 100, 115 | BlockTypeBytes: 3, 116 | BlockType: &types.FuncType{ReturnTypes: []types.ValueType{types.ValueTypeI32}}, 117 | }, 118 | }, 119 | }, 120 | LabelStack: stacks.NewLabelStack(), 121 | } 122 | vm := &Instance{Active: ctx, OperandStack: stacks.NewOperandStack()} 123 | vm.OperandStack.Push(0) 124 | if ifOp(vm) != nil { 125 | t.Fail() 126 | } 127 | if !reflect.DeepEqual(&stacks.Label{ 128 | Arity: 1, 129 | ContinuationPC: 100, 130 | EndPC: 100, 131 | }, ctx.LabelStack.Values[ctx.LabelStack.Ptr]) { 132 | t.Fail() 133 | } 134 | if ctx.PC != 50 { 135 | t.Fail() 136 | } 137 | }) 138 | } 139 | 140 | func Test_elseOp(t *testing.T) { 141 | ctx := &Frame{ 142 | LabelStack: stacks.NewLabelStack(), 143 | } 144 | 145 | ctx.LabelStack.Push(&stacks.Label{EndPC: 100000}) 146 | if elseOp(&Instance{Active: ctx}) != nil { 147 | t.Fail() 148 | } 149 | if ctx.PC != 100000 { 150 | t.Fail() 151 | } 152 | } 153 | 154 | func Test_end(t *testing.T) { 155 | ctx := &Frame{LabelStack: stacks.NewLabelStack()} 156 | ctx.LabelStack.Push(&stacks.Label{EndPC: 100000}) 157 | if end(&Instance{Active: ctx}) != nil { 158 | t.Fail() 159 | } 160 | if ctx.LabelStack.Ptr != -1 { 161 | t.Fail() 162 | } 163 | } 164 | 165 | func Test_br(t *testing.T) { 166 | ctx := &Frame{ 167 | LabelStack: stacks.NewLabelStack(), 168 | Func: &wasmFunc{body: []byte{0x00, 0x01}}, 169 | } 170 | vm := &Instance{ 171 | Active: ctx, 172 | OperandStack: stacks.NewOperandStack()} 173 | ctx.LabelStack.Push(&stacks.Label{ContinuationPC: 5}) 174 | ctx.LabelStack.Push(&stacks.Label{}) 175 | if br(vm) != nil { 176 | t.Fail() 177 | } 178 | if ctx.PC != 5 { 179 | t.Fail() 180 | } 181 | } 182 | 183 | func Test_brIf(t *testing.T) { 184 | t.Run("true", func(t *testing.T) { 185 | ctx := &Frame{ 186 | LabelStack: stacks.NewLabelStack(), 187 | Func: &wasmFunc{body: []byte{0x00, 0x01}}, 188 | } 189 | 190 | vm := &Instance{Active: ctx, OperandStack: stacks.NewOperandStack()} 191 | vm.OperandStack.Push(1) 192 | ctx.LabelStack.Push(&stacks.Label{ContinuationPC: 5}) 193 | ctx.LabelStack.Push(&stacks.Label{}) 194 | if brIf(vm) != nil { 195 | t.Fail() 196 | } 197 | if ctx.PC != 5 { 198 | t.Fail() 199 | } 200 | }) 201 | 202 | t.Run("false", func(t *testing.T) { 203 | ctx := &Frame{ 204 | LabelStack: stacks.NewLabelStack(), 205 | Func: &wasmFunc{body: []byte{0x00, 0x01}}, 206 | } 207 | 208 | vm := &Instance{Active: ctx, OperandStack: stacks.NewOperandStack()} 209 | vm.OperandStack.Push(0) 210 | if brIf(vm) != nil { 211 | t.Fail() 212 | } 213 | if ctx.PC != 1 { 214 | t.Fail() 215 | } 216 | }) 217 | } 218 | 219 | func Test_brAt(t *testing.T) { 220 | // fixme: 221 | } 222 | 223 | func Test_brTable(t *testing.T) { 224 | // fixme: 225 | } 226 | 227 | type dummyFunc struct { 228 | cnt int 229 | } 230 | 231 | func (d *dummyFunc) call(_ *Instance) error { 232 | d.cnt++ 233 | return nil 234 | } 235 | 236 | func (d *dummyFunc) getType() *types.FuncType { return &types.FuncType{} } 237 | 238 | func Test_call(t *testing.T) { 239 | df := &dummyFunc{} 240 | ins := &Instance{ 241 | Active: &Frame{ 242 | Func: &wasmFunc{ 243 | body: []byte{byte(expr.OpCodeCall), 0x01}, 244 | }, 245 | }, 246 | Functions: []fn{nil, df}, 247 | } 248 | 249 | if call(ins) != nil { 250 | t.Fail() 251 | } 252 | if df.cnt != 1 { 253 | t.Fail() 254 | } 255 | } 256 | 257 | func Test_callIndirect(t *testing.T) { 258 | df := &dummyFunc{} 259 | ins := &Instance{ 260 | Active: &Frame{ 261 | Func: &wasmFunc{ 262 | body: []byte{byte(expr.OpCodeCall), 0x01, 0x00}, 263 | }, 264 | }, 265 | Functions: []fn{nil, df}, 266 | Module: &Module{ 267 | TypeSection: []*types.FuncType{nil, {}}, 268 | IndexSpace: &IndexSpace{ 269 | Tables: []*Table{ 270 | {Value: []*uint32{nil, utils.Uint32Ptr(1)}}, 271 | }, 272 | }, 273 | }, 274 | OperandStack: stacks.NewOperandStack(), 275 | } 276 | ins.OperandStack.Push(1) 277 | 278 | if callIndirect(ins) != nil { 279 | t.Fail() 280 | } 281 | if df.cnt != 1 { 282 | t.Fail() 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /wasm/instr_mem.go: -------------------------------------------------------------------------------- 1 | package wasm 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | 7 | "github.com/c0mm4nd/wasman/config" 8 | ) 9 | 10 | // ErrPtrOutOfBounds will be throw when the pointer visiting a pos out of the range of memory 11 | var ErrPtrOutOfBounds = errors.New("pointer is out of bounds") 12 | 13 | func memoryBase(ins *Instance) (uint64, error) { 14 | ins.Active.PC++ 15 | _, err := ins.fetchUint32() // ignore align 16 | if err != nil { 17 | return 0, err 18 | } 19 | ins.Active.PC++ 20 | v, err := ins.fetchUint32() 21 | if err != nil { 22 | return 0, err 23 | } 24 | 25 | base := uint64(v) + ins.OperandStack.Pop() 26 | if !(base < uint64(len(ins.Memory.Value))) { 27 | return 0, ErrPtrOutOfBounds 28 | } 29 | 30 | return base, nil 31 | } 32 | 33 | func i32Load(ins *Instance) error { 34 | base, err := memoryBase(ins) 35 | if err != nil { 36 | return err 37 | } 38 | 39 | ins.OperandStack.Push(uint64(binary.LittleEndian.Uint32(ins.Memory.Value[base:]))) 40 | 41 | return nil 42 | } 43 | 44 | func i64Load(ins *Instance) error { 45 | base, err := memoryBase(ins) 46 | if err != nil { 47 | return err 48 | } 49 | 50 | ins.OperandStack.Push(binary.LittleEndian.Uint64(ins.Memory.Value[base:])) 51 | 52 | return nil 53 | } 54 | 55 | func f32Load(ins *Instance) error { 56 | return i32Load(ins) 57 | } 58 | 59 | func f64Load(ins *Instance) error { 60 | return i64Load(ins) 61 | } 62 | 63 | func i32Load8s(ins *Instance) error { 64 | base, err := memoryBase(ins) 65 | if err != nil { 66 | return err 67 | } 68 | 69 | ins.OperandStack.Push(uint64(ins.Memory.Value[base])) 70 | 71 | return nil 72 | } 73 | 74 | func i32Load8u(ins *Instance) error { 75 | return i32Load8s(ins) 76 | } 77 | 78 | func i32Load16s(ins *Instance) error { 79 | base, err := memoryBase(ins) 80 | if err != nil { 81 | return err 82 | } 83 | 84 | ins.OperandStack.Push(uint64(binary.LittleEndian.Uint16(ins.Memory.Value[base:]))) 85 | 86 | return nil 87 | } 88 | 89 | func i32Load16u(ins *Instance) error { 90 | return i32Load16s(ins) 91 | } 92 | 93 | func i64Load8s(ins *Instance) error { 94 | base, err := memoryBase(ins) 95 | if err != nil { 96 | return err 97 | } 98 | 99 | ins.OperandStack.Push(uint64(ins.Memory.Value[base])) 100 | 101 | return nil 102 | } 103 | 104 | func i64Load8u(ins *Instance) error { 105 | return i64Load8s(ins) 106 | } 107 | 108 | func i64Load16s(ins *Instance) error { 109 | base, err := memoryBase(ins) 110 | if err != nil { 111 | return err 112 | } 113 | 114 | ins.OperandStack.Push(uint64(binary.LittleEndian.Uint16(ins.Memory.Value[base:]))) 115 | 116 | return nil 117 | } 118 | 119 | func i64Load16u(ins *Instance) error { 120 | return i64Load16s(ins) 121 | } 122 | 123 | func i64Load32s(ins *Instance) error { 124 | base, err := memoryBase(ins) 125 | if err != nil { 126 | return err 127 | } 128 | 129 | ins.OperandStack.Push(uint64(binary.LittleEndian.Uint32(ins.Memory.Value[base:]))) 130 | 131 | return nil 132 | } 133 | 134 | func i64Load32u(ins *Instance) error { 135 | return i64Load32s(ins) 136 | } 137 | 138 | func i32Store(ins *Instance) error { 139 | val := ins.OperandStack.Pop() 140 | base, err := memoryBase(ins) 141 | if err != nil { 142 | return err 143 | } 144 | 145 | binary.LittleEndian.PutUint32(ins.Memory.Value[base:], uint32(val)) 146 | 147 | return nil 148 | } 149 | 150 | func i64Store(ins *Instance) error { 151 | val := ins.OperandStack.Pop() 152 | base, err := memoryBase(ins) 153 | if err != nil { 154 | return err 155 | } 156 | 157 | binary.LittleEndian.PutUint64(ins.Memory.Value[base:], val) 158 | 159 | return nil 160 | } 161 | 162 | func f32Store(ins *Instance) error { 163 | val := ins.OperandStack.Pop() 164 | base, err := memoryBase(ins) 165 | if err != nil { 166 | return err 167 | } 168 | 169 | binary.LittleEndian.PutUint32(ins.Memory.Value[base:], uint32(val)) 170 | 171 | return nil 172 | } 173 | 174 | func f64Store(ins *Instance) error { 175 | v := ins.OperandStack.Pop() 176 | base, err := memoryBase(ins) 177 | if err != nil { 178 | return err 179 | } 180 | 181 | binary.LittleEndian.PutUint64(ins.Memory.Value[base:], v) 182 | 183 | return nil 184 | } 185 | 186 | func i32Store8(ins *Instance) error { 187 | v := byte(ins.OperandStack.Pop()) 188 | base, err := memoryBase(ins) 189 | if err != nil { 190 | return err 191 | } 192 | 193 | ins.Memory.Value[base] = v 194 | 195 | return nil 196 | } 197 | 198 | func i32Store16(ins *Instance) error { 199 | v := uint16(ins.OperandStack.Pop()) 200 | base, err := memoryBase(ins) 201 | if err != nil { 202 | return err 203 | } 204 | 205 | binary.LittleEndian.PutUint16(ins.Memory.Value[base:], v) 206 | 207 | return nil 208 | } 209 | 210 | func i64Store8(ins *Instance) error { 211 | v := byte(ins.OperandStack.Pop()) 212 | base, err := memoryBase(ins) 213 | if err != nil { 214 | return err 215 | } 216 | 217 | ins.Memory.Value[base] = v 218 | 219 | return nil 220 | } 221 | 222 | func i64Store16(ins *Instance) error { 223 | v := uint16(ins.OperandStack.Pop()) 224 | base, err := memoryBase(ins) 225 | if err != nil { 226 | return err 227 | } 228 | 229 | binary.LittleEndian.PutUint16(ins.Memory.Value[base:], v) 230 | 231 | return nil 232 | } 233 | 234 | func i64Store32(ins *Instance) error { 235 | v := uint32(ins.OperandStack.Pop()) 236 | base, err := memoryBase(ins) 237 | if err != nil { 238 | return err 239 | } 240 | 241 | binary.LittleEndian.PutUint32(ins.Memory.Value[base:], v) 242 | 243 | return nil 244 | } 245 | 246 | func memorySize(ins *Instance) error { 247 | ins.Active.PC++ 248 | ins.OperandStack.Push(uint64(int32(len(ins.Memory.Value) / config.DefaultMemoryPageSize))) 249 | 250 | return nil 251 | } 252 | 253 | func memoryGrow(ins *Instance) error { 254 | ins.Active.PC++ 255 | n := uint32(ins.OperandStack.Pop()) 256 | 257 | if ins.Module.MemorySection[0].Max != nil && 258 | uint64(n+uint32(len(ins.Memory.Value)/config.DefaultMemoryPageSize)) > uint64(*(ins.Module.MemorySection[0].Max)) { 259 | v := int32(-1) 260 | ins.OperandStack.Push(uint64(v)) 261 | 262 | return nil 263 | } 264 | 265 | ins.OperandStack.Push(uint64(len(ins.Memory.Value)) / config.DefaultMemoryPageSize) 266 | ins.Memory.Value = append(ins.Memory.Value, make([]byte, n*config.DefaultMemoryPageSize)...) 267 | 268 | return nil 269 | } 270 | -------------------------------------------------------------------------------- /wasm/instr_mem_test.go: -------------------------------------------------------------------------------- 1 | package wasm 2 | 3 | import ( 4 | "bytes" 5 | "math" 6 | "testing" 7 | 8 | "github.com/c0mm4nd/wasman/utils" 9 | 10 | "github.com/c0mm4nd/wasman/config" 11 | "github.com/c0mm4nd/wasman/expr" 12 | "github.com/c0mm4nd/wasman/stacks" 13 | "github.com/c0mm4nd/wasman/types" 14 | ) 15 | 16 | func Test_i32Load(t *testing.T) { 17 | vm := &Instance{ 18 | Active: &Frame{ 19 | Func: &wasmFunc{ 20 | body: []byte{byte(expr.OpCodeI32Load), 0x00, 0x01}, 21 | }, 22 | }, 23 | Memory: &Memory{ 24 | Value: []byte{0x00, 0x01, 0x00, 0x00, 0x00}, 25 | }, 26 | OperandStack: stacks.NewOperandStack(), 27 | } 28 | 29 | vm.OperandStack.Push(uint64(0)) 30 | if i32Load(vm) != nil { 31 | t.Fail() 32 | } 33 | if vm.OperandStack.Pop() != 1 { 34 | t.Fail() 35 | } 36 | } 37 | 38 | func Test_i64Load(t *testing.T) { 39 | vm := &Instance{ 40 | Active: &Frame{ 41 | Func: &wasmFunc{ 42 | body: []byte{byte(expr.OpCodeI64Load), 0x00, 0x01}, 43 | }, 44 | }, 45 | Memory: &Memory{ 46 | Value: []byte{0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 47 | }, 48 | OperandStack: stacks.NewOperandStack(), 49 | } 50 | 51 | vm.OperandStack.Push(uint64(1)) 52 | if i64Load(vm) != nil { 53 | t.Fail() 54 | } 55 | if vm.OperandStack.Pop() != 1 { 56 | t.Fail() 57 | } 58 | } 59 | 60 | func Test_f32Load(t *testing.T) { 61 | vm := &Instance{ 62 | Active: &Frame{ 63 | Func: &wasmFunc{ 64 | body: []byte{byte(expr.OpCodeF32Load), 0x00, 0x01}, 65 | }, 66 | }, 67 | Memory: &Memory{ 68 | MemoryType: types.MemoryType{}, 69 | Value: []byte{0x00, 0x01, 0x00, 0x00, 0x00}, 70 | }, 71 | OperandStack: stacks.NewOperandStack(), 72 | } 73 | 74 | vm.OperandStack.Push(uint64(0)) 75 | if f32Load(vm) != nil { 76 | t.Fail() 77 | } 78 | if math.Float32frombits(uint32(vm.OperandStack.Pop())) != math.Float32frombits(0x01) { 79 | t.Fail() 80 | } 81 | } 82 | 83 | func Test_f64Load(t *testing.T) { 84 | vm := &Instance{ 85 | Active: &Frame{ 86 | Func: &wasmFunc{ 87 | body: []byte{byte(expr.OpCodeF64Load), 0x00, 0x01}, 88 | }, 89 | }, 90 | Memory: &Memory{ 91 | Value: []byte{0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 92 | }, 93 | OperandStack: stacks.NewOperandStack(), 94 | } 95 | 96 | vm.OperandStack.Push(uint64(1)) 97 | if f64Load(vm) != nil { 98 | t.Fail() 99 | } 100 | if math.Float64frombits(vm.OperandStack.Pop()) != math.Float64frombits(0x01) { 101 | t.Fail() 102 | } 103 | } 104 | 105 | func Test_i32Load8s(t *testing.T) { 106 | vm := &Instance{ 107 | Active: &Frame{ 108 | Func: &wasmFunc{ 109 | body: []byte{byte(expr.OpCodeI32Load), 0x00, 0x01}, 110 | }, 111 | }, 112 | Memory: &Memory{ 113 | Value: []byte{0x00, 0xff}, 114 | }, 115 | OperandStack: stacks.NewOperandStack(), 116 | } 117 | 118 | vm.OperandStack.Push(uint64(0)) 119 | if i32Load8s(vm) != nil { 120 | t.Fail() 121 | } 122 | if int8(vm.OperandStack.Pop()) != int8(-1) { 123 | t.Fail() 124 | } 125 | } 126 | 127 | func Test_i32Load8u(t *testing.T) { 128 | vm := &Instance{ 129 | Active: &Frame{ 130 | Func: &wasmFunc{ 131 | body: []byte{byte(expr.OpCodeI32Load), 0x00, 0x01}, 132 | }, 133 | }, 134 | Memory: &Memory{ 135 | Value: []byte{0x00, 0xff}, 136 | }, 137 | OperandStack: stacks.NewOperandStack(), 138 | } 139 | 140 | vm.OperandStack.Push(uint64(0)) 141 | if i32Load8u(vm) != nil { 142 | t.Fail() 143 | } 144 | if byte(vm.OperandStack.Pop()) != byte(255) { 145 | t.Fail() 146 | } 147 | } 148 | 149 | func Test_i32Load16s(t *testing.T) { 150 | vm := &Instance{ 151 | Active: &Frame{ 152 | Func: &wasmFunc{ 153 | body: []byte{byte(expr.OpCodeI32Load), 0x00, 0x01}, 154 | }, 155 | }, 156 | Memory: &Memory{ 157 | Value: []byte{0x00, 0xff, 0x01}, 158 | }, 159 | OperandStack: stacks.NewOperandStack(), 160 | } 161 | 162 | vm.OperandStack.Push(uint64(0)) 163 | if i32Load16s(vm) != nil { 164 | t.Fail() 165 | } 166 | if int16(vm.OperandStack.Pop()) != int16(0x01ff) { 167 | t.Fail() 168 | } 169 | } 170 | 171 | func Test_i32Load16u(t *testing.T) { 172 | vm := &Instance{ 173 | Active: &Frame{ 174 | Func: &wasmFunc{ 175 | body: []byte{byte(expr.OpCodeI32Load), 0x00, 0x01}, 176 | }, 177 | }, 178 | Memory: &Memory{ 179 | Value: []byte{0x00, 0x00, 0xff}, 180 | }, 181 | OperandStack: stacks.NewOperandStack(), 182 | } 183 | 184 | vm.OperandStack.Push(uint64(0)) 185 | if i32Load16u(vm) != nil { 186 | t.Fail() 187 | } 188 | if uint16(vm.OperandStack.Pop()) != uint16(0xff00) { 189 | t.Fail() 190 | } 191 | } 192 | 193 | func Test_i64Load8s(t *testing.T) { 194 | vm := &Instance{ 195 | Active: &Frame{ 196 | Func: &wasmFunc{ 197 | body: []byte{byte(expr.OpCodeI32Load), 0x00, 0x01}, 198 | }, 199 | }, 200 | Memory: &Memory{ 201 | Value: []byte{0x00, 0xff}, 202 | }, 203 | OperandStack: stacks.NewOperandStack(), 204 | } 205 | 206 | vm.OperandStack.Push(uint64(0)) 207 | if i64Load8s(vm) != nil { 208 | t.Fail() 209 | } 210 | if int8(vm.OperandStack.Pop()) != int8(-1) { 211 | t.Fail() 212 | } 213 | } 214 | 215 | func Test_i64Load8u(t *testing.T) { 216 | vm := &Instance{ 217 | Active: &Frame{ 218 | Func: &wasmFunc{ 219 | body: []byte{byte(expr.OpCodeI32Load), 0x00, 0x01}, 220 | }, 221 | }, 222 | Memory: &Memory{ 223 | Value: []byte{0x00, 0xff}, 224 | }, 225 | OperandStack: stacks.NewOperandStack(), 226 | } 227 | 228 | vm.OperandStack.Push(uint64(0)) 229 | if i64Load8u(vm) != nil { 230 | t.Fail() 231 | } 232 | if byte(vm.OperandStack.Pop()) != byte(255) { 233 | t.Fail() 234 | } 235 | } 236 | 237 | func Test_i64Load16s(t *testing.T) { 238 | vm := &Instance{ 239 | Active: &Frame{ 240 | Func: &wasmFunc{ 241 | body: []byte{byte(expr.OpCodeI32Load), 0x00, 0x01}, 242 | }, 243 | }, 244 | Memory: &Memory{ 245 | Value: []byte{0x00, 0xff, 0x01}, 246 | }, 247 | OperandStack: stacks.NewOperandStack(), 248 | } 249 | 250 | vm.OperandStack.Push(uint64(0)) 251 | if i64Load16s(vm) != nil { 252 | t.Fail() 253 | } 254 | if int16(vm.OperandStack.Pop()) != int16(0x01ff) { 255 | t.Fail() 256 | } 257 | } 258 | 259 | func Test_i64Load16u(t *testing.T) { 260 | vm := &Instance{ 261 | Active: &Frame{ 262 | Func: &wasmFunc{ 263 | body: []byte{byte(expr.OpCodeI32Load), 0x00, 0x01}, 264 | }, 265 | }, 266 | Memory: &Memory{ 267 | Value: []byte{0x00, 0x00, 0xff}, 268 | }, 269 | OperandStack: stacks.NewOperandStack(), 270 | } 271 | 272 | vm.OperandStack.Push(uint64(0)) 273 | if i64Load16u(vm) != nil { 274 | t.Fail() 275 | } 276 | if uint16(vm.OperandStack.Pop()) != uint16(0xff00) { 277 | t.Fail() 278 | } 279 | } 280 | 281 | func Test_i64Load32s(t *testing.T) { 282 | vm := &Instance{ 283 | Active: &Frame{ 284 | Func: &wasmFunc{ 285 | body: []byte{byte(expr.OpCodeI32Load), 0x00, 0x01}, 286 | }, 287 | }, 288 | Memory: &Memory{ 289 | Value: []byte{0x00, 0xff, 0x01, 0x00, 0x01}, 290 | }, 291 | OperandStack: stacks.NewOperandStack(), 292 | } 293 | 294 | vm.OperandStack.Push(uint64(0)) 295 | if i64Load32s(vm) != nil { 296 | t.Fail() 297 | } 298 | if int32(vm.OperandStack.Pop()) != int32(0x010001ff) { 299 | t.Fail() 300 | } 301 | } 302 | 303 | func Test_i64Load32u(t *testing.T) { 304 | vm := &Instance{ 305 | Active: &Frame{ 306 | Func: &wasmFunc{ 307 | body: []byte{byte(expr.OpCodeI32Load), 0x00, 0x01}, 308 | }, 309 | }, 310 | Memory: &Memory{ 311 | Value: []byte{0x00, 0x00, 0xff, 0x00, 0xff}, 312 | }, 313 | OperandStack: stacks.NewOperandStack(), 314 | } 315 | 316 | vm.OperandStack.Push(uint64(0)) 317 | if i64Load32u(vm) != nil { 318 | t.Fail() 319 | } 320 | if uint32(vm.OperandStack.Pop()) != uint32(0xff00ff00) { 321 | t.Fail() 322 | } 323 | } 324 | 325 | func Test_i32Store(t *testing.T) { 326 | vm := &Instance{ 327 | Active: &Frame{ 328 | Func: &wasmFunc{ 329 | body: []byte{byte(expr.OpCodeI32Store), 0x00, 0x01}, 330 | }, 331 | }, 332 | Memory: &Memory{ 333 | Value: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 334 | }, 335 | OperandStack: stacks.NewOperandStack(), 336 | } 337 | 338 | vm.OperandStack.Push(uint64(1)) 339 | vm.OperandStack.Push(uint64(0xffffff11)) 340 | if i32Store(vm) != nil { 341 | t.Fail() 342 | } 343 | if !bytes.Equal(vm.Memory.Value[2:], []byte{0x11, 0xff, 0xff, 0xff}) { 344 | t.Fail() 345 | } 346 | } 347 | 348 | func Test_i64Store(t *testing.T) { 349 | vm := &Instance{ 350 | Active: &Frame{ 351 | Func: &wasmFunc{ 352 | body: []byte{byte(expr.OpCodeI32Store), 0x00, 0x01}, 353 | }, 354 | }, 355 | Memory: &Memory{ 356 | Value: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 357 | }, 358 | OperandStack: stacks.NewOperandStack(), 359 | } 360 | 361 | vm.OperandStack.Push(uint64(1)) 362 | vm.OperandStack.Push(uint64(0xffffff11_22222222)) 363 | if i64Store(vm) != nil { 364 | t.Fail() 365 | } 366 | if !bytes.Equal([]byte{ 367 | 0x22, 0x22, 0x22, 0x22, 368 | 0x11, 0xff, 0xff, 0xff, 369 | }, 370 | vm.Memory.Value[2:], 371 | ) { 372 | t.Fail() 373 | } 374 | } 375 | 376 | func Test_f32Store(t *testing.T) { 377 | vm := &Instance{ 378 | Active: &Frame{ 379 | Func: &wasmFunc{ 380 | body: []byte{byte(expr.OpCodeI32Store), 0x00, 0x01}, 381 | }, 382 | }, 383 | Memory: &Memory{ 384 | Value: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 385 | }, 386 | OperandStack: stacks.NewOperandStack(), 387 | } 388 | 389 | vm.OperandStack.Push(uint64(1)) 390 | vm.OperandStack.Push(uint64(math.Float32bits(math.Float32frombits(0xffff_1111)))) 391 | if f32Store(vm) != nil { 392 | t.Fail() 393 | } 394 | if !bytes.Equal([]byte{0x11, 0x11, 0xff, 0xff}, vm.Memory.Value[2:]) { 395 | t.Fail() 396 | } 397 | } 398 | 399 | func Test_f64Store(t *testing.T) { 400 | vm := &Instance{ 401 | Active: &Frame{ 402 | Func: &wasmFunc{ 403 | body: []byte{byte(expr.OpCodeI32Store), 0x00, 0x01}, 404 | }, 405 | }, 406 | Memory: &Memory{ 407 | Value: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 408 | }, 409 | OperandStack: stacks.NewOperandStack(), 410 | } 411 | 412 | vm.OperandStack.Push(uint64(1)) 413 | vm.OperandStack.Push(math.Float64bits(math.Float64frombits(0xffff_1111_0000_1111))) 414 | if f64Store(vm) != nil { 415 | t.Fail() 416 | } 417 | if !bytes.Equal([]byte{0x11, 0x11, 0x00, 0x00, 0x11, 0x11, 0xff, 0xff}, vm.Memory.Value[2:]) { 418 | t.Fail() 419 | } 420 | } 421 | 422 | func Test_i32store8(t *testing.T) { 423 | vm := &Instance{ 424 | Active: &Frame{ 425 | Func: &wasmFunc{ 426 | body: []byte{byte(expr.OpCodeI32Store), 0x00, 0x01}, 427 | }, 428 | }, 429 | Memory: &Memory{ 430 | Value: []byte{0x00, 0x00, 0x00}, 431 | }, 432 | OperandStack: stacks.NewOperandStack(), 433 | } 434 | 435 | vm.OperandStack.Push(uint64(1)) 436 | vm.OperandStack.Push(uint64(byte(111))) 437 | if i32Store8(vm) != nil { 438 | t.Fail() 439 | } 440 | if vm.Memory.Value[2] != byte(111) { 441 | t.Fail() 442 | } 443 | } 444 | 445 | func Test_i32store16(t *testing.T) { 446 | vm := &Instance{ 447 | Active: &Frame{ 448 | Func: &wasmFunc{ 449 | body: []byte{byte(expr.OpCodeI32Store), 0x00, 0x01}, 450 | }, 451 | }, 452 | Memory: &Memory{ 453 | Value: []byte{0x00, 0x00, 0x00, 0x00}, 454 | }, 455 | OperandStack: stacks.NewOperandStack(), 456 | } 457 | 458 | vm.OperandStack.Push(uint64(1)) 459 | vm.OperandStack.Push(uint64(uint16(0x11ff))) 460 | if i32Store16(vm) != nil { 461 | t.Fail() 462 | } 463 | if !bytes.Equal([]byte{0xff, 0x11}, vm.Memory.Value[2:]) { 464 | t.Fail() 465 | } 466 | } 467 | 468 | func Test_i64store8(t *testing.T) { 469 | vm := &Instance{ 470 | Active: &Frame{ 471 | Func: &wasmFunc{ 472 | body: []byte{byte(expr.OpCodeI32Store), 0x00, 0x01}, 473 | }, 474 | }, 475 | Memory: &Memory{ 476 | Value: []byte{0x00, 0x00, 0x00}, 477 | }, 478 | OperandStack: stacks.NewOperandStack(), 479 | } 480 | 481 | vm.OperandStack.Push(uint64(1)) 482 | vm.OperandStack.Push(uint64(byte(111))) 483 | if i64Store8(vm) != nil { 484 | t.Fail() 485 | } 486 | if vm.Memory.Value[2] != byte(111) { 487 | t.Fail() 488 | } 489 | } 490 | 491 | func Test_i64store16(t *testing.T) { 492 | vm := &Instance{ 493 | Active: &Frame{ 494 | Func: &wasmFunc{ 495 | body: []byte{byte(expr.OpCodeI32Store), 0x00, 0x01}, 496 | }, 497 | }, 498 | Memory: &Memory{ 499 | Value: []byte{0x00, 0x00, 0x00, 0x00}, 500 | }, 501 | OperandStack: stacks.NewOperandStack(), 502 | } 503 | 504 | vm.OperandStack.Push(uint64(1)) 505 | vm.OperandStack.Push(uint64(uint16(0x11ff))) 506 | if i64Store16(vm) != nil { 507 | t.Fail() 508 | } 509 | if !bytes.Equal([]byte{0xff, 0x11}, vm.Memory.Value[2:]) { 510 | t.Fail() 511 | } 512 | } 513 | 514 | func Test_i64store32(t *testing.T) { 515 | vm := &Instance{ 516 | Active: &Frame{ 517 | Func: &wasmFunc{ 518 | body: []byte{byte(expr.OpCodeI32Store), 0x00, 0x01}, 519 | }, 520 | }, 521 | Memory: &Memory{ 522 | Value: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 523 | }, 524 | OperandStack: stacks.NewOperandStack(), 525 | } 526 | 527 | vm.OperandStack.Push(uint64(1)) 528 | vm.OperandStack.Push(uint64(uint32(0x11ff_22ee))) 529 | if i64Store32(vm) != nil { 530 | t.Fail() 531 | } 532 | if !bytes.Equal([]byte{0xee, 0x22, 0xff, 0x11}, vm.Memory.Value[2:]) { 533 | t.Fail() 534 | } 535 | } 536 | 537 | func Test_memorySize(t *testing.T) { 538 | vm := &Instance{ 539 | Active: &Frame{}, 540 | Memory: &Memory{ 541 | Value: make([]byte, config.DefaultMemoryPageSize*2), 542 | }, 543 | OperandStack: stacks.NewOperandStack(), 544 | } 545 | 546 | if memorySize(vm) != nil { 547 | t.Fail() 548 | } 549 | if vm.OperandStack.Pop() != uint64(0x2) { 550 | t.Fail() 551 | } 552 | } 553 | 554 | func Test_memoryGrow(t *testing.T) { 555 | t.Run("ok", func(t *testing.T) { 556 | vm := &Instance{ 557 | Active: &Frame{}, 558 | Memory: &Memory{ 559 | Value: make([]byte, config.DefaultMemoryPageSize*2), 560 | }, 561 | OperandStack: stacks.NewOperandStack(), 562 | Module: &Module{ 563 | MemorySection: []*types.MemoryType{{}}, 564 | }, 565 | } 566 | 567 | vm.OperandStack.Push(5) 568 | if memoryGrow(vm) != nil { 569 | t.Fail() 570 | } 571 | if vm.OperandStack.Pop() != uint64(0x2) { 572 | t.Fail() 573 | } 574 | if len(vm.Memory.Value)/config.DefaultMemoryPageSize != 7 { 575 | t.Fail() 576 | } 577 | }) 578 | 579 | t.Run("oom", func(t *testing.T) { 580 | vm := &Instance{ 581 | Active: &Frame{}, 582 | Memory: &Memory{ 583 | Value: make([]byte, config.DefaultMemoryPageSize*2), 584 | }, 585 | OperandStack: stacks.NewOperandStack(), 586 | Module: &Module{ 587 | MemorySection: []*types.MemoryType{{Max: utils.Uint32Ptr(0)}}, 588 | }, 589 | } 590 | 591 | exp := int32(-1) 592 | vm.OperandStack.Push(5) 593 | err := memoryGrow(vm) 594 | if err != nil { 595 | t.Fail() 596 | } 597 | if vm.OperandStack.Pop() != uint64(exp) { 598 | t.Fail() 599 | } 600 | }) 601 | 602 | } 603 | -------------------------------------------------------------------------------- /wasm/instr_num.go: -------------------------------------------------------------------------------- 1 | package wasm 2 | 3 | import ( 4 | "errors" 5 | "math" 6 | "math/bits" 7 | ) 8 | 9 | // ErrUndefined is a panic error 10 | var ErrUndefined = errors.New("undefined") 11 | 12 | func i32eqz(ins *Instance) error { 13 | if ins.OperandStack.Pop() == 0 { 14 | ins.OperandStack.Push(1) 15 | } else { 16 | ins.OperandStack.Push(0) 17 | } 18 | 19 | return nil 20 | } 21 | 22 | func i32eq(ins *Instance) error { 23 | v1 := ins.OperandStack.Pop() 24 | v2 := ins.OperandStack.Pop() 25 | if v1 == v2 { 26 | ins.OperandStack.Push(1) 27 | } else { 28 | ins.OperandStack.Push(0) 29 | } 30 | 31 | return nil 32 | } 33 | 34 | func i32ne(ins *Instance) error { 35 | v1 := ins.OperandStack.Pop() 36 | v2 := ins.OperandStack.Pop() 37 | if v1 != v2 { 38 | ins.OperandStack.Push(1) 39 | } else { 40 | ins.OperandStack.Push(0) 41 | } 42 | 43 | return nil 44 | } 45 | 46 | func i32lts(ins *Instance) error { 47 | v2 := ins.OperandStack.Pop() 48 | v1 := ins.OperandStack.Pop() 49 | if int32(v1) < int32(v2) { 50 | ins.OperandStack.Push(1) 51 | } else { 52 | ins.OperandStack.Push(0) 53 | } 54 | 55 | return nil 56 | } 57 | 58 | func i32ltu(ins *Instance) error { 59 | v2 := ins.OperandStack.Pop() 60 | v1 := ins.OperandStack.Pop() 61 | if uint32(v1) < uint32(v2) { 62 | 63 | ins.OperandStack.Push(1) 64 | } else { 65 | ins.OperandStack.Push(0) 66 | } 67 | 68 | return nil 69 | } 70 | 71 | func i32gts(ins *Instance) error { 72 | v2 := ins.OperandStack.Pop() 73 | v1 := ins.OperandStack.Pop() 74 | if int32(v1) > int32(v2) { 75 | ins.OperandStack.Push(1) 76 | } else { 77 | ins.OperandStack.Push(0) 78 | } 79 | 80 | return nil 81 | } 82 | 83 | func i32gtu(ins *Instance) error { 84 | v2 := ins.OperandStack.Pop() 85 | v1 := ins.OperandStack.Pop() 86 | if uint32(v1) > uint32(v2) { 87 | ins.OperandStack.Push(1) 88 | } else { 89 | ins.OperandStack.Push(0) 90 | } 91 | 92 | return nil 93 | } 94 | 95 | func i32les(ins *Instance) error { 96 | v2 := ins.OperandStack.Pop() 97 | v1 := ins.OperandStack.Pop() 98 | if int32(v1) <= int32(v2) { 99 | ins.OperandStack.Push(1) 100 | } else { 101 | ins.OperandStack.Push(0) 102 | } 103 | 104 | return nil 105 | } 106 | 107 | func i32leu(ins *Instance) error { 108 | v2 := ins.OperandStack.Pop() 109 | v1 := ins.OperandStack.Pop() 110 | if uint32(v1) <= uint32(v2) { 111 | ins.OperandStack.Push(1) 112 | } else { 113 | ins.OperandStack.Push(0) 114 | } 115 | 116 | return nil 117 | } 118 | 119 | func i32ges(ins *Instance) error { 120 | v2 := ins.OperandStack.Pop() 121 | v1 := ins.OperandStack.Pop() 122 | if int32(v1) >= int32(v2) { 123 | ins.OperandStack.Push(1) 124 | } else { 125 | ins.OperandStack.Push(0) 126 | } 127 | 128 | return nil 129 | } 130 | 131 | func i32geu(ins *Instance) error { 132 | v2 := ins.OperandStack.Pop() 133 | v1 := ins.OperandStack.Pop() 134 | if uint32(v1) >= uint32(v2) { 135 | ins.OperandStack.Push(1) 136 | } else { 137 | ins.OperandStack.Push(0) 138 | } 139 | 140 | return nil 141 | } 142 | 143 | func i64eqz(ins *Instance) error { 144 | if ins.OperandStack.Pop() == 0 { 145 | ins.OperandStack.Push(1) 146 | } else { 147 | ins.OperandStack.Push(0) 148 | } 149 | 150 | return nil 151 | } 152 | 153 | func i64eq(ins *Instance) error { 154 | v1 := ins.OperandStack.Pop() 155 | v2 := ins.OperandStack.Pop() 156 | if v1 == v2 { 157 | ins.OperandStack.Push(1) 158 | } else { 159 | ins.OperandStack.Push(0) 160 | } 161 | 162 | return nil 163 | } 164 | 165 | func i64ne(ins *Instance) error { 166 | v1 := ins.OperandStack.Pop() 167 | v2 := ins.OperandStack.Pop() 168 | if v1 != v2 { 169 | ins.OperandStack.Push(1) 170 | } else { 171 | ins.OperandStack.Push(0) 172 | } 173 | 174 | return nil 175 | } 176 | 177 | func i64lts(ins *Instance) error { 178 | v2 := ins.OperandStack.Pop() 179 | v1 := ins.OperandStack.Pop() 180 | if int64(v1) < int64(v2) { 181 | ins.OperandStack.Push(1) 182 | } else { 183 | ins.OperandStack.Push(0) 184 | } 185 | 186 | return nil 187 | } 188 | 189 | func i64ltu(ins *Instance) error { 190 | v2 := ins.OperandStack.Pop() 191 | v1 := ins.OperandStack.Pop() 192 | if v1 < v2 { 193 | ins.OperandStack.Push(1) 194 | } else { 195 | ins.OperandStack.Push(0) 196 | } 197 | 198 | return nil 199 | } 200 | 201 | func i64gts(ins *Instance) error { 202 | v2 := ins.OperandStack.Pop() 203 | v1 := ins.OperandStack.Pop() 204 | if int64(v1) > int64(v2) { 205 | ins.OperandStack.Push(1) 206 | } else { 207 | ins.OperandStack.Push(0) 208 | } 209 | 210 | return nil 211 | } 212 | 213 | func i64gtu(ins *Instance) error { 214 | v2 := ins.OperandStack.Pop() 215 | v1 := ins.OperandStack.Pop() 216 | if v1 < v2 { 217 | ins.OperandStack.Push(1) 218 | } else { 219 | ins.OperandStack.Push(0) 220 | } 221 | 222 | return nil 223 | } 224 | 225 | func i64les(ins *Instance) error { 226 | v2 := ins.OperandStack.Pop() 227 | v1 := ins.OperandStack.Pop() 228 | if int64(v1) <= int64(v2) { 229 | ins.OperandStack.Push(1) 230 | } else { 231 | ins.OperandStack.Push(0) 232 | } 233 | 234 | return nil 235 | } 236 | 237 | func i64leu(ins *Instance) error { 238 | v2 := ins.OperandStack.Pop() 239 | v1 := ins.OperandStack.Pop() 240 | if v1 <= v2 { 241 | ins.OperandStack.Push(1) 242 | } else { 243 | ins.OperandStack.Push(0) 244 | } 245 | 246 | return nil 247 | } 248 | 249 | func i64ges(ins *Instance) error { 250 | v2 := ins.OperandStack.Pop() 251 | v1 := ins.OperandStack.Pop() 252 | if int64(v1) >= int64(v2) { 253 | ins.OperandStack.Push(1) 254 | } else { 255 | ins.OperandStack.Push(0) 256 | } 257 | 258 | return nil 259 | } 260 | 261 | func i64geu(ins *Instance) error { 262 | v2 := ins.OperandStack.Pop() 263 | v1 := ins.OperandStack.Pop() 264 | if v1 >= v2 { 265 | ins.OperandStack.Push(1) 266 | } else { 267 | ins.OperandStack.Push(0) 268 | } 269 | return nil 270 | } 271 | 272 | func f32eq(ins *Instance) error { 273 | f2 := math.Float32frombits(uint32(ins.OperandStack.Pop())) 274 | f1 := math.Float32frombits(uint32(ins.OperandStack.Pop())) 275 | if f1 == f2 { 276 | ins.OperandStack.Push(1) 277 | } else { 278 | ins.OperandStack.Push(0) 279 | } 280 | 281 | return nil 282 | } 283 | 284 | func f32ne(ins *Instance) error { 285 | f2 := math.Float32frombits(uint32(ins.OperandStack.Pop())) 286 | f1 := math.Float32frombits(uint32(ins.OperandStack.Pop())) 287 | if f1 != f2 { 288 | ins.OperandStack.Push(1) 289 | } else { 290 | ins.OperandStack.Push(0) 291 | } 292 | 293 | return nil 294 | } 295 | 296 | func f32lt(ins *Instance) error { 297 | f2 := math.Float32frombits(uint32(ins.OperandStack.Pop())) 298 | f1 := math.Float32frombits(uint32(ins.OperandStack.Pop())) 299 | if f1 < f2 { 300 | ins.OperandStack.Push(1) 301 | } else { 302 | ins.OperandStack.Push(0) 303 | } 304 | 305 | return nil 306 | } 307 | 308 | func f32gt(ins *Instance) error { 309 | f1 := math.Float32frombits(uint32(ins.OperandStack.Pop())) 310 | f2 := math.Float32frombits(uint32(ins.OperandStack.Pop())) 311 | if f1 > f2 { 312 | ins.OperandStack.Push(1) 313 | } else { 314 | ins.OperandStack.Push(0) 315 | } 316 | 317 | return nil 318 | } 319 | 320 | func f32le(ins *Instance) error { 321 | f2 := math.Float32frombits(uint32(ins.OperandStack.Pop())) 322 | f1 := math.Float32frombits(uint32(ins.OperandStack.Pop())) 323 | if f1 <= f2 { 324 | ins.OperandStack.Push(1) 325 | } else { 326 | ins.OperandStack.Push(0) 327 | } 328 | 329 | return nil 330 | } 331 | 332 | func f32ge(ins *Instance) error { 333 | f2 := math.Float32frombits(uint32(ins.OperandStack.Pop())) 334 | f1 := math.Float32frombits(uint32(ins.OperandStack.Pop())) 335 | if f1 >= f2 { 336 | ins.OperandStack.Push(1) 337 | } else { 338 | ins.OperandStack.Push(0) 339 | } 340 | 341 | return nil 342 | } 343 | 344 | func f64eq(ins *Instance) error { 345 | f2 := math.Float64frombits(ins.OperandStack.Pop()) 346 | f1 := math.Float64frombits(ins.OperandStack.Pop()) 347 | if f1 == f2 { 348 | ins.OperandStack.Push(1) 349 | } else { 350 | ins.OperandStack.Push(0) 351 | } 352 | 353 | return nil 354 | } 355 | 356 | func f64ne(ins *Instance) error { 357 | f2 := math.Float64frombits(ins.OperandStack.Pop()) 358 | f1 := math.Float64frombits(ins.OperandStack.Pop()) 359 | if f1 != f2 { 360 | ins.OperandStack.Push(1) 361 | } else { 362 | ins.OperandStack.Push(0) 363 | } 364 | 365 | return nil 366 | } 367 | 368 | func f64lt(ins *Instance) error { 369 | f2 := math.Float64frombits(ins.OperandStack.Pop()) 370 | f1 := math.Float64frombits(ins.OperandStack.Pop()) 371 | if f1 < f2 { 372 | ins.OperandStack.Push(1) 373 | } else { 374 | ins.OperandStack.Push(0) 375 | } 376 | 377 | return nil 378 | } 379 | 380 | func f64gt(ins *Instance) error { 381 | f2 := math.Float64frombits(ins.OperandStack.Pop()) 382 | f1 := math.Float64frombits(ins.OperandStack.Pop()) 383 | if f1 > f2 { 384 | ins.OperandStack.Push(1) 385 | } else { 386 | ins.OperandStack.Push(0) 387 | } 388 | 389 | return nil 390 | } 391 | 392 | func f64le(ins *Instance) error { 393 | f2 := math.Float64frombits(ins.OperandStack.Pop()) 394 | f1 := math.Float64frombits(ins.OperandStack.Pop()) 395 | if f1 <= f2 { 396 | ins.OperandStack.Push(1) 397 | } else { 398 | ins.OperandStack.Push(0) 399 | } 400 | 401 | return nil 402 | } 403 | 404 | func f64ge(ins *Instance) error { 405 | f2 := math.Float64frombits(ins.OperandStack.Pop()) 406 | f1 := math.Float64frombits(ins.OperandStack.Pop()) 407 | if f1 >= f2 { 408 | ins.OperandStack.Push(1) 409 | } else { 410 | ins.OperandStack.Push(0) 411 | } 412 | 413 | return nil 414 | } 415 | 416 | func i32clz(ins *Instance) error { 417 | ins.OperandStack.Push(uint64(bits.LeadingZeros32(uint32(ins.OperandStack.Pop())))) 418 | 419 | return nil 420 | } 421 | 422 | func i32ctz(ins *Instance) error { 423 | ins.OperandStack.Push(uint64(bits.TrailingZeros32(uint32(ins.OperandStack.Pop())))) 424 | 425 | return nil 426 | } 427 | 428 | func i32popcnt(ins *Instance) error { 429 | ins.OperandStack.Push(uint64(bits.OnesCount32(uint32(ins.OperandStack.Pop())))) 430 | 431 | return nil 432 | } 433 | 434 | func i32add(ins *Instance) error { 435 | ins.OperandStack.Push(uint64(int32(ins.OperandStack.Pop()) + int32(ins.OperandStack.Pop()))) 436 | 437 | return nil 438 | } 439 | 440 | func i32sub(ins *Instance) error { 441 | v2 := ins.OperandStack.Pop() 442 | v1 := ins.OperandStack.Pop() 443 | ins.OperandStack.Push(uint64(int32(v1) - int32(v2))) 444 | 445 | return nil 446 | } 447 | 448 | func i32mul(ins *Instance) error { 449 | ins.OperandStack.Push(uint64(int32(ins.OperandStack.Pop()) * int32(ins.OperandStack.Pop()))) 450 | 451 | return nil 452 | } 453 | 454 | func i32divs(ins *Instance) error { 455 | v2 := int32(ins.OperandStack.Pop()) 456 | v1 := int32(ins.OperandStack.Pop()) 457 | if v2 == 0 || (v1 == math.MinInt32 && v2 == -1) { 458 | return ErrUndefined 459 | } 460 | ins.OperandStack.Push(uint64(v1 / v2)) 461 | 462 | return nil 463 | } 464 | 465 | func i32divu(ins *Instance) error { 466 | v2 := uint32(ins.OperandStack.Pop()) 467 | v1 := uint32(ins.OperandStack.Pop()) 468 | ins.OperandStack.Push(uint64(v1 / v2)) 469 | 470 | return nil 471 | } 472 | 473 | func i32rems(ins *Instance) error { 474 | v2 := int32(ins.OperandStack.Pop()) 475 | v1 := int32(ins.OperandStack.Pop()) 476 | ins.OperandStack.Push(uint64(v1 % v2)) 477 | 478 | return nil 479 | } 480 | 481 | func i32remu(ins *Instance) error { 482 | v2 := uint32(ins.OperandStack.Pop()) 483 | v1 := uint32(ins.OperandStack.Pop()) 484 | ins.OperandStack.Push(uint64(v1 % v2)) 485 | 486 | return nil 487 | } 488 | 489 | func i32and(ins *Instance) error { 490 | v2 := ins.OperandStack.Pop() 491 | v1 := ins.OperandStack.Pop() 492 | ins.OperandStack.Push(uint64(uint32(v1) & uint32(v2))) 493 | 494 | return nil 495 | } 496 | 497 | func i32or(ins *Instance) error { 498 | v2 := ins.OperandStack.Pop() 499 | v1 := ins.OperandStack.Pop() 500 | ins.OperandStack.Push(uint64(uint32(v1) | uint32(v2))) 501 | 502 | return nil 503 | } 504 | 505 | func i32xor(ins *Instance) error { 506 | v2 := ins.OperandStack.Pop() 507 | v1 := ins.OperandStack.Pop() 508 | ins.OperandStack.Push(uint64(uint32(v1) ^ uint32(v2))) 509 | 510 | return nil 511 | } 512 | 513 | func i32shl(ins *Instance) error { 514 | v2 := uint32(ins.OperandStack.Pop()) 515 | v1 := uint32(ins.OperandStack.Pop()) 516 | ins.OperandStack.Push(uint64(v1 << (v2 % 32))) 517 | 518 | return nil 519 | } 520 | 521 | func i32shru(ins *Instance) error { 522 | v2 := uint32(ins.OperandStack.Pop()) 523 | v1 := uint32(ins.OperandStack.Pop()) 524 | ins.OperandStack.Push(uint64(v1 >> (v2 % 32))) 525 | 526 | return nil 527 | } 528 | 529 | func i32shrs(ins *Instance) error { 530 | v2 := uint32(ins.OperandStack.Pop()) 531 | v1 := int32(ins.OperandStack.Pop()) 532 | ins.OperandStack.Push(uint64(v1 >> (v2 % 32))) 533 | 534 | return nil 535 | } 536 | 537 | func i32rotl(ins *Instance) error { 538 | v2 := int(ins.OperandStack.Pop()) 539 | v1 := uint32(ins.OperandStack.Pop()) 540 | ins.OperandStack.Push(uint64(bits.RotateLeft32(v1, v2))) 541 | 542 | return nil 543 | } 544 | 545 | func i32rotr(ins *Instance) error { 546 | v2 := int(ins.OperandStack.Pop()) 547 | v1 := uint32(ins.OperandStack.Pop()) 548 | ins.OperandStack.Push(uint64(bits.RotateLeft32(v1, -v2))) 549 | 550 | return nil 551 | } 552 | 553 | // i64 554 | func i64clz(ins *Instance) error { 555 | ins.OperandStack.Push(uint64(bits.LeadingZeros64(ins.OperandStack.Pop()))) 556 | 557 | return nil 558 | } 559 | 560 | func i64ctz(ins *Instance) error { 561 | ins.OperandStack.Push(uint64(bits.TrailingZeros64(ins.OperandStack.Pop()))) 562 | 563 | return nil 564 | } 565 | 566 | func i64popcnt(ins *Instance) error { 567 | ins.OperandStack.Push(uint64(bits.OnesCount64(ins.OperandStack.Pop()))) 568 | 569 | return nil 570 | } 571 | 572 | func i64add(ins *Instance) error { 573 | ins.OperandStack.Push(ins.OperandStack.Pop() + ins.OperandStack.Pop()) 574 | 575 | return nil 576 | } 577 | 578 | func i64sub(ins *Instance) error { 579 | v2 := int64(ins.OperandStack.Pop()) 580 | v1 := int64(ins.OperandStack.Pop()) 581 | ins.OperandStack.Push(uint64(v1 - v2)) 582 | 583 | return nil 584 | } 585 | 586 | func i64mul(ins *Instance) error { 587 | ins.OperandStack.Push(ins.OperandStack.Pop() * ins.OperandStack.Pop()) 588 | 589 | return nil 590 | } 591 | 592 | func i64divs(ins *Instance) error { 593 | v2 := int64(ins.OperandStack.Pop()) 594 | v1 := int64(ins.OperandStack.Pop()) 595 | if v2 == 0 || (v1 == math.MinInt64 && v2 == -1) { 596 | return ErrUndefined 597 | } 598 | ins.OperandStack.Push(uint64(v1 / v2)) 599 | 600 | return nil 601 | } 602 | 603 | func i64divu(ins *Instance) error { 604 | v2 := ins.OperandStack.Pop() 605 | v1 := ins.OperandStack.Pop() 606 | ins.OperandStack.Push(v1 / v2) 607 | 608 | return nil 609 | } 610 | 611 | func i64rems(ins *Instance) error { 612 | v2 := int64(ins.OperandStack.Pop()) 613 | v1 := int64(ins.OperandStack.Pop()) 614 | ins.OperandStack.Push(uint64(v1 % v2)) 615 | 616 | return nil 617 | } 618 | 619 | func i64remu(ins *Instance) error { 620 | v2 := ins.OperandStack.Pop() 621 | v1 := ins.OperandStack.Pop() 622 | ins.OperandStack.Push(v1 % v2) 623 | 624 | return nil 625 | } 626 | 627 | func i64and(ins *Instance) error { 628 | v2 := int64(ins.OperandStack.Pop()) 629 | v1 := int64(ins.OperandStack.Pop()) 630 | ins.OperandStack.Push(uint64(v1 & v2)) 631 | 632 | return nil 633 | } 634 | 635 | func i64or(ins *Instance) error { 636 | v1 := ins.OperandStack.Pop() 637 | v2 := ins.OperandStack.Pop() 638 | ins.OperandStack.Push(v1 | v2) 639 | 640 | return nil 641 | } 642 | 643 | func i64xor(ins *Instance) error { 644 | v1 := ins.OperandStack.Pop() 645 | v2 := ins.OperandStack.Pop() 646 | ins.OperandStack.Push(v1 ^ v2) 647 | 648 | return nil 649 | } 650 | 651 | func i64shl(ins *Instance) error { 652 | v2 := ins.OperandStack.Pop() 653 | v1 := ins.OperandStack.Pop() 654 | ins.OperandStack.Push(v1 << (v2 % 64)) 655 | 656 | return nil 657 | } 658 | 659 | func i64shru(ins *Instance) error { 660 | v2 := ins.OperandStack.Pop() 661 | v1 := ins.OperandStack.Pop() 662 | ins.OperandStack.Push(v1 >> (v2 % 64)) 663 | 664 | return nil 665 | } 666 | 667 | func i64shrs(ins *Instance) error { 668 | v2 := int64(ins.OperandStack.Pop()) 669 | v1 := int64(ins.OperandStack.Pop()) 670 | ins.OperandStack.Push(uint64(v1 >> (v2 % 64))) 671 | 672 | return nil 673 | } 674 | 675 | func i64rotl(ins *Instance) error { 676 | v2 := int(ins.OperandStack.Pop()) 677 | v1 := ins.OperandStack.Pop() 678 | ins.OperandStack.Push(bits.RotateLeft64(v1, v2)) 679 | 680 | return nil 681 | } 682 | 683 | func i64rotr(ins *Instance) error { 684 | v2 := int(ins.OperandStack.Pop()) 685 | v1 := ins.OperandStack.Pop() 686 | ins.OperandStack.Push(bits.RotateLeft64(v1, -v2)) 687 | 688 | return nil 689 | } 690 | 691 | func f32abs(ins *Instance) error { 692 | const mask uint32 = 1 << 31 693 | v := uint32(ins.OperandStack.Pop()) &^ mask 694 | ins.OperandStack.Push(uint64(v)) 695 | 696 | return nil 697 | } 698 | 699 | func f32neg(ins *Instance) error { 700 | v := -math.Float32frombits(uint32(ins.OperandStack.Pop())) 701 | ins.OperandStack.Push(uint64(math.Float32bits(v))) 702 | 703 | return nil 704 | } 705 | 706 | func f32ceil(ins *Instance) error { 707 | v := math.Float32frombits(uint32(ins.OperandStack.Pop())) 708 | ins.OperandStack.Push(uint64(math.Float32bits(float32(math.Ceil(float64(v)))))) 709 | 710 | return nil 711 | } 712 | 713 | func f32floor(ins *Instance) error { 714 | v := math.Float32frombits(uint32(ins.OperandStack.Pop())) 715 | ins.OperandStack.Push(uint64(math.Float32bits(float32(math.Floor(float64(v)))))) 716 | 717 | return nil 718 | } 719 | 720 | func f32trunc(ins *Instance) error { 721 | v := math.Float32frombits(uint32(ins.OperandStack.Pop())) 722 | ins.OperandStack.Push(uint64(math.Float32bits(float32(math.Trunc(float64(v)))))) 723 | 724 | return nil 725 | } 726 | 727 | func f32nearest(ins *Instance) error { 728 | raw := math.Float32frombits(uint32(ins.OperandStack.Pop())) 729 | v := math.Float64bits(float64(int32(raw + float32(math.Copysign(0.5, float64(raw)))))) 730 | ins.OperandStack.Push(v) 731 | 732 | return nil 733 | } 734 | 735 | func f32sqrt(ins *Instance) error { 736 | v := math.Float32frombits(uint32(ins.OperandStack.Pop())) 737 | ins.OperandStack.Push(uint64(math.Float32bits(float32(math.Sqrt(float64(v)))))) 738 | 739 | return nil 740 | } 741 | 742 | func f32add(ins *Instance) error { 743 | v := math.Float32frombits(uint32(ins.OperandStack.Pop())) + math.Float32frombits(uint32(ins.OperandStack.Pop())) 744 | ins.OperandStack.Push(uint64(math.Float32bits(v))) 745 | 746 | return nil 747 | } 748 | 749 | func f32sub(ins *Instance) error { 750 | v2 := math.Float32frombits(uint32(ins.OperandStack.Pop())) 751 | v1 := math.Float32frombits(uint32(ins.OperandStack.Pop())) 752 | ins.OperandStack.Push(uint64(math.Float32bits(v1 - v2))) 753 | 754 | return nil 755 | } 756 | 757 | func f32mul(ins *Instance) error { 758 | v := math.Float32frombits(uint32(ins.OperandStack.Pop())) * math.Float32frombits(uint32(ins.OperandStack.Pop())) 759 | ins.OperandStack.Push(uint64(math.Float32bits(v))) 760 | 761 | return nil 762 | } 763 | 764 | func f32div(ins *Instance) error { 765 | v2 := math.Float32frombits(uint32(ins.OperandStack.Pop())) 766 | v1 := math.Float32frombits(uint32(ins.OperandStack.Pop())) 767 | ins.OperandStack.Push(uint64(math.Float32bits(v1 / v2))) 768 | 769 | return nil 770 | } 771 | 772 | func f32min(ins *Instance) error { 773 | v2 := math.Float32frombits(uint32(ins.OperandStack.Pop())) 774 | v1 := math.Float32frombits(uint32(ins.OperandStack.Pop())) 775 | ins.OperandStack.Push(uint64(math.Float32bits(float32(math.Min(float64(v1), float64(v2)))))) 776 | 777 | return nil 778 | } 779 | 780 | func f32max(ins *Instance) error { 781 | v2 := math.Float32frombits(uint32(ins.OperandStack.Pop())) 782 | v1 := math.Float32frombits(uint32(ins.OperandStack.Pop())) 783 | ins.OperandStack.Push(uint64(math.Float32bits(float32(math.Min(float64(v1), float64(v2)))))) 784 | 785 | return nil 786 | } 787 | 788 | func f32copysign(ins *Instance) error { 789 | v2 := math.Float32frombits(uint32(ins.OperandStack.Pop())) 790 | v1 := math.Float32frombits(uint32(ins.OperandStack.Pop())) 791 | ins.OperandStack.Push(uint64(math.Float32bits(float32(math.Copysign(float64(v1), float64(v2)))))) 792 | 793 | return nil 794 | } 795 | 796 | func f64abs(ins *Instance) error { 797 | const mask = 1 << 63 798 | v := ins.OperandStack.Pop() &^ mask 799 | ins.OperandStack.Push(v) 800 | 801 | return nil 802 | } 803 | 804 | func f64neg(ins *Instance) error { 805 | v := -math.Float64frombits(ins.OperandStack.Pop()) 806 | ins.OperandStack.Push(math.Float64bits(v)) 807 | 808 | return nil 809 | } 810 | 811 | func f64ceil(ins *Instance) error { 812 | v := math.Float64frombits(ins.OperandStack.Pop()) 813 | ins.OperandStack.Push(math.Float64bits(math.Ceil(v))) 814 | 815 | return nil 816 | } 817 | 818 | func f64floor(ins *Instance) error { 819 | v := math.Float64frombits(ins.OperandStack.Pop()) 820 | ins.OperandStack.Push(math.Float64bits(math.Floor(v))) 821 | 822 | return nil 823 | } 824 | 825 | func f64trunc(ins *Instance) error { 826 | v := math.Float64frombits(ins.OperandStack.Pop()) 827 | ins.OperandStack.Push(math.Float64bits(math.Trunc(v))) 828 | 829 | return nil 830 | } 831 | 832 | func f64nearest(ins *Instance) error { 833 | raw := math.Float64frombits(ins.OperandStack.Pop()) 834 | v := math.Float64bits(float64(int64(raw + math.Copysign(0.5, raw)))) 835 | ins.OperandStack.Push(v) 836 | 837 | return nil 838 | } 839 | 840 | func f64sqrt(ins *Instance) error { 841 | v := math.Float64frombits(ins.OperandStack.Pop()) 842 | ins.OperandStack.Push(math.Float64bits(math.Sqrt(v))) 843 | 844 | return nil 845 | } 846 | 847 | func f64add(ins *Instance) error { 848 | v := math.Float64frombits(ins.OperandStack.Pop()) + math.Float64frombits(ins.OperandStack.Pop()) 849 | ins.OperandStack.Push(math.Float64bits(v)) 850 | 851 | return nil 852 | } 853 | 854 | func f64sub(ins *Instance) error { 855 | v2 := math.Float64frombits(ins.OperandStack.Pop()) 856 | v1 := math.Float64frombits(ins.OperandStack.Pop()) 857 | ins.OperandStack.Push(math.Float64bits(v1 - v2)) 858 | 859 | return nil 860 | } 861 | 862 | func f64mul(ins *Instance) error { 863 | v := math.Float64frombits(ins.OperandStack.Pop()) * math.Float64frombits(ins.OperandStack.Pop()) 864 | ins.OperandStack.Push(math.Float64bits(v)) 865 | 866 | return nil 867 | } 868 | 869 | func f64div(ins *Instance) error { 870 | v2 := math.Float64frombits(ins.OperandStack.Pop()) 871 | v1 := math.Float64frombits(ins.OperandStack.Pop()) 872 | ins.OperandStack.Push(math.Float64bits(v1 / v2)) 873 | 874 | return nil 875 | } 876 | 877 | func f64min(ins *Instance) error { 878 | v2 := math.Float64frombits(ins.OperandStack.Pop()) 879 | v1 := math.Float64frombits(ins.OperandStack.Pop()) 880 | ins.OperandStack.Push(math.Float64bits(math.Min(v1, v2))) 881 | 882 | return nil 883 | } 884 | 885 | func f64max(ins *Instance) error { 886 | v2 := math.Float64frombits(ins.OperandStack.Pop()) 887 | v1 := math.Float64frombits(ins.OperandStack.Pop()) 888 | ins.OperandStack.Push(math.Float64bits(math.Min(v1, v2))) 889 | 890 | return nil 891 | } 892 | 893 | func f64copysign(ins *Instance) error { 894 | v2 := math.Float64frombits(ins.OperandStack.Pop()) 895 | v1 := math.Float64frombits(ins.OperandStack.Pop()) 896 | ins.OperandStack.Push(math.Float64bits(math.Copysign(v1, v2))) 897 | 898 | return nil 899 | } 900 | 901 | func i32wrapi64(ins *Instance) error { 902 | ins.OperandStack.Push(uint64(uint32(ins.OperandStack.Pop()))) 903 | 904 | return nil 905 | } 906 | 907 | func i32truncf32s(ins *Instance) error { 908 | v := math.Float32frombits(uint32(ins.OperandStack.Pop())) 909 | ins.OperandStack.Push(uint64(int32(math.Trunc(float64(v))))) 910 | 911 | return nil 912 | } 913 | 914 | func i32truncf32u(ins *Instance) error { 915 | v := math.Float32frombits(uint32(ins.OperandStack.Pop())) 916 | ins.OperandStack.Push(uint64(uint32(math.Trunc(float64(v))))) 917 | 918 | return nil 919 | } 920 | 921 | func i32truncf64s(ins *Instance) error { 922 | v := math.Float64frombits(ins.OperandStack.Pop()) 923 | ins.OperandStack.Push(uint64(int32(math.Trunc(v)))) 924 | 925 | return nil 926 | } 927 | 928 | func i32truncf64u(ins *Instance) error { 929 | v := math.Float64frombits(ins.OperandStack.Pop()) 930 | ins.OperandStack.Push(uint64(uint32(math.Trunc(v)))) 931 | 932 | return nil 933 | } 934 | 935 | func i64extendi32s(ins *Instance) error { 936 | v := int64(int32(ins.OperandStack.Pop())) 937 | ins.OperandStack.Push(uint64(v)) 938 | 939 | return nil 940 | } 941 | 942 | func i64extendi32u(ins *Instance) error { 943 | v := uint64(uint32(ins.OperandStack.Pop())) 944 | ins.OperandStack.Push(v) 945 | 946 | return nil 947 | } 948 | 949 | func i64truncf32s(ins *Instance) error { 950 | v := math.Trunc(float64(math.Float32frombits(uint32(ins.OperandStack.Pop())))) 951 | ins.OperandStack.Push(uint64(int64(v))) 952 | 953 | return nil 954 | } 955 | 956 | func i64truncf32u(ins *Instance) error { 957 | v := math.Trunc(float64(math.Float32frombits(uint32(ins.OperandStack.Pop())))) 958 | ins.OperandStack.Push(uint64(v)) 959 | 960 | return nil 961 | } 962 | 963 | func i64truncf64s(ins *Instance) error { 964 | v := math.Trunc(math.Float64frombits(ins.OperandStack.Pop())) 965 | ins.OperandStack.Push(uint64(int64(v))) 966 | 967 | return nil 968 | } 969 | 970 | func i64truncf64u(ins *Instance) error { 971 | v := math.Trunc(math.Float64frombits(ins.OperandStack.Pop())) 972 | ins.OperandStack.Push(uint64(v)) 973 | 974 | return nil 975 | } 976 | 977 | func f32converti32s(ins *Instance) error { 978 | v := float32(int32(ins.OperandStack.Pop())) 979 | ins.OperandStack.Push(uint64(math.Float32bits(v))) 980 | 981 | return nil 982 | } 983 | 984 | func f32converti32u(ins *Instance) error { 985 | v := float32(uint32(ins.OperandStack.Pop())) 986 | ins.OperandStack.Push(uint64(math.Float32bits(v))) 987 | 988 | return nil 989 | } 990 | 991 | func f32converti64s(ins *Instance) error { 992 | v := float32(int64(ins.OperandStack.Pop())) 993 | ins.OperandStack.Push(uint64(math.Float32bits(v))) 994 | 995 | return nil 996 | } 997 | 998 | func f32converti64u(ins *Instance) error { 999 | v := float32(ins.OperandStack.Pop()) 1000 | ins.OperandStack.Push(uint64(math.Float32bits(v))) 1001 | 1002 | return nil 1003 | } 1004 | 1005 | func f32demotef64(ins *Instance) error { 1006 | v := float32(math.Float64frombits(ins.OperandStack.Pop())) 1007 | ins.OperandStack.Push(uint64(math.Float32bits(v))) 1008 | 1009 | return nil 1010 | } 1011 | 1012 | func f64converti32s(ins *Instance) error { 1013 | v := float64(int32(ins.OperandStack.Pop())) 1014 | ins.OperandStack.Push(math.Float64bits(v)) 1015 | 1016 | return nil 1017 | } 1018 | 1019 | func f64converti32u(ins *Instance) error { 1020 | v := float64(uint32(ins.OperandStack.Pop())) 1021 | ins.OperandStack.Push(math.Float64bits(v)) 1022 | 1023 | return nil 1024 | } 1025 | 1026 | func f64converti64s(ins *Instance) error { 1027 | v := float64(int64(ins.OperandStack.Pop())) 1028 | ins.OperandStack.Push(math.Float64bits(v)) 1029 | 1030 | return nil 1031 | } 1032 | 1033 | func f64converti64u(ins *Instance) error { 1034 | v := float64(ins.OperandStack.Pop()) 1035 | ins.OperandStack.Push(math.Float64bits(v)) 1036 | 1037 | return nil 1038 | } 1039 | 1040 | func f64promotef32(ins *Instance) error { 1041 | v := float64(math.Float32frombits(uint32(ins.OperandStack.Pop()))) 1042 | ins.OperandStack.Push(math.Float64bits(v)) 1043 | 1044 | return nil 1045 | } 1046 | -------------------------------------------------------------------------------- /wasm/instr_num_test.go: -------------------------------------------------------------------------------- 1 | package wasm 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/c0mm4nd/wasman/stacks" 7 | ) 8 | 9 | type NumTestSet struct { 10 | vm *Instance 11 | } 12 | 13 | func (s *NumTestSet) SetupTest() { 14 | s.vm = &Instance{ 15 | OperandStack: stacks.NewOperandStack(), 16 | } 17 | } 18 | 19 | func (s *NumTestSet) Test_i32eqz(t *testing.T) { 20 | var testTable = []struct { 21 | input int 22 | want uint64 23 | }{ 24 | {input: 0, want: 1}, 25 | {input: 1, want: 0}, 26 | } 27 | for _, tt := range testTable { 28 | s.vm.OperandStack.Push(uint64(tt.input)) 29 | if i32eqz(s.vm) != nil { 30 | t.Fail() 31 | } 32 | if s.vm.OperandStack.Pop() != tt.want { 33 | t.Fail() 34 | } 35 | } 36 | } 37 | 38 | func (s *NumTestSet) Test_i32ne(t *testing.T) { 39 | var testTable = []struct { 40 | input [2]int 41 | want uint64 42 | }{ 43 | {input: [2]int{3, 4}, want: 1}, 44 | {input: [2]int{3, 3}, want: 0}, 45 | } 46 | for _, tt := range testTable { 47 | s.vm.OperandStack.Push(uint64(tt.input[0])) 48 | s.vm.OperandStack.Push(uint64(tt.input[1])) 49 | if i32ne(s.vm) != nil { 50 | t.Fail() 51 | } 52 | if s.vm.OperandStack.Pop() != tt.want { 53 | t.Fail() 54 | } 55 | } 56 | } 57 | 58 | func (s *NumTestSet) Test_i32lts(t *testing.T) { 59 | var testTable = []struct { 60 | input [2]int 61 | want uint64 62 | }{ 63 | {input: [2]int{-4, 1}, want: 1}, 64 | {input: [2]int{4, -1}, want: 0}, 65 | } 66 | for _, tt := range testTable { 67 | s.vm.OperandStack.Push(uint64(tt.input[0])) 68 | s.vm.OperandStack.Push(uint64(tt.input[1])) 69 | if i32lts(s.vm) != nil { 70 | t.Fail() 71 | } 72 | if s.vm.OperandStack.Pop() != tt.want { 73 | t.Fail() 74 | } 75 | } 76 | } 77 | 78 | func (s *NumTestSet) Test_i32ltu(t *testing.T) { 79 | var testTable = []struct { 80 | input [2]int 81 | want uint64 82 | }{ 83 | {input: [2]int{1, 4}, want: 1}, 84 | {input: [2]int{4, 1}, want: 0}, 85 | } 86 | for _, tt := range testTable { 87 | s.vm.OperandStack.Push(uint64(tt.input[0])) 88 | s.vm.OperandStack.Push(uint64(tt.input[1])) 89 | if i32ltu(s.vm) != nil { 90 | t.Fail() 91 | } 92 | if s.vm.OperandStack.Pop() != tt.want { 93 | t.Fail() 94 | } 95 | } 96 | } 97 | 98 | func (s *NumTestSet) Test_i32gts(t *testing.T) { 99 | var testTable = []struct { 100 | input [2]int 101 | want uint64 102 | }{ 103 | {input: [2]int{1, -4}, want: 1}, 104 | {input: [2]int{-4, 1}, want: 0}, 105 | } 106 | for _, tt := range testTable { 107 | s.vm.OperandStack.Push(uint64(tt.input[0])) 108 | s.vm.OperandStack.Push(uint64(tt.input[1])) 109 | if i32gts(s.vm) != nil { 110 | t.Fail() 111 | } 112 | if s.vm.OperandStack.Pop() != tt.want { 113 | t.Fail() 114 | } 115 | } 116 | } 117 | 118 | func TestRunSuite(t *testing.T) { 119 | set := new(NumTestSet) 120 | set.SetupTest() 121 | set.Test_i32eqz(t) 122 | set.Test_i32ne(t) 123 | set.Test_i32lts(t) 124 | set.Test_i32ltu(t) 125 | set.Test_i32gts(t) 126 | } 127 | -------------------------------------------------------------------------------- /wasm/instr_var.go: -------------------------------------------------------------------------------- 1 | package wasm 2 | 3 | func getLocal(ins *Instance) error { 4 | ins.Active.PC++ 5 | id, err := ins.fetchUint32() 6 | if err != nil { 7 | return err 8 | } 9 | 10 | ins.OperandStack.Push(ins.Active.Locals[id]) 11 | 12 | return nil 13 | } 14 | 15 | func setLocal(ins *Instance) error { 16 | ins.Active.PC++ 17 | id, err := ins.fetchUint32() 18 | if err != nil { 19 | return err 20 | } 21 | 22 | v := ins.OperandStack.Pop() 23 | ins.Active.Locals[id] = v 24 | 25 | return nil 26 | } 27 | 28 | func teeLocal(ins *Instance) error { 29 | ins.Active.PC++ 30 | id, err := ins.fetchUint32() 31 | if err != nil { 32 | return err 33 | } 34 | 35 | v := ins.OperandStack.Peek() 36 | ins.Active.Locals[id] = v 37 | 38 | return nil 39 | } 40 | 41 | func getGlobal(ins *Instance) error { 42 | ins.Active.PC++ 43 | id, err := ins.fetchUint32() 44 | if err != nil { 45 | return err 46 | } 47 | 48 | ins.OperandStack.Push(ins.Globals[id]) 49 | 50 | return nil 51 | } 52 | 53 | func setGlobal(ins *Instance) error { 54 | ins.Active.PC++ 55 | id, err := ins.fetchUint32() 56 | if err != nil { 57 | return err 58 | } 59 | 60 | ins.Globals[id] = ins.OperandStack.Pop() 61 | 62 | return nil 63 | } 64 | -------------------------------------------------------------------------------- /wasm/instr_var_test.go: -------------------------------------------------------------------------------- 1 | package wasm 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/c0mm4nd/wasman/expr" 7 | "github.com/c0mm4nd/wasman/stacks" 8 | ) 9 | 10 | func Test_getLocal(t *testing.T) { 11 | exp := uint64(100) 12 | ctx := &Frame{ 13 | Func: &wasmFunc{ 14 | body: []byte{byte(expr.OpCodeLocalGet), 0x05}, 15 | }, 16 | Locals: []uint64{0, 0, 0, 0, 0, exp}, 17 | } 18 | 19 | vm := &Instance{Active: ctx, OperandStack: stacks.NewOperandStack()} 20 | err := getLocal(vm) 21 | if err != nil { 22 | t.Fail() 23 | } 24 | if vm.OperandStack.Pop() != exp { 25 | t.Fail() 26 | } 27 | if vm.OperandStack.Ptr != -1 { 28 | t.Fail() 29 | } 30 | } 31 | 32 | func Test_setLocal(t *testing.T) { 33 | ctx := &Frame{ 34 | Func: &wasmFunc{ 35 | body: []byte{byte(expr.OpCodeLocalSet), 0x05}, 36 | }, 37 | Locals: make([]uint64, 100), 38 | } 39 | 40 | exp := uint64(100) 41 | st := stacks.NewOperandStack() 42 | st.Push(exp) 43 | 44 | vm := &Instance{Active: ctx, OperandStack: st} 45 | err := setLocal(vm) 46 | if err != nil { 47 | t.Fail() 48 | } 49 | if vm.Active.Locals[5] != exp { 50 | t.Fail() 51 | } 52 | if vm.OperandStack.Ptr != -1 { 53 | t.Fail() 54 | } 55 | } 56 | 57 | func Test_teeLocal(t *testing.T) { 58 | ctx := &Frame{ 59 | Func: &wasmFunc{ 60 | body: []byte{byte(expr.OpCodeLocalTee), 0x05}, 61 | }, 62 | Locals: make([]uint64, 100), 63 | } 64 | 65 | exp := uint64(100) 66 | st := stacks.NewOperandStack() 67 | st.Push(exp) 68 | 69 | vm := &Instance{Active: ctx, OperandStack: st} 70 | err := teeLocal(vm) 71 | if err != nil { 72 | t.Fail() 73 | } 74 | if vm.Active.Locals[5] != exp { 75 | t.Fail() 76 | } 77 | if vm.OperandStack.Pop() != exp { 78 | t.Fail() 79 | } 80 | } 81 | 82 | func Test_getGlobal(t *testing.T) { 83 | ctx := &Frame{ 84 | Func: &wasmFunc{ 85 | body: []byte{byte(expr.OpCodeGlobalGet), 0x05}, 86 | }, 87 | } 88 | 89 | exp := uint64(1) 90 | globals := []uint64{0, 0, 0, 0, 0, exp} 91 | 92 | vm := &Instance{ 93 | Active: ctx, 94 | OperandStack: stacks.NewOperandStack(), 95 | Globals: globals, 96 | } 97 | err := getGlobal(vm) 98 | if err != nil { 99 | t.Fail() 100 | } 101 | if vm.OperandStack.Pop() != exp { 102 | t.Fail() 103 | } 104 | if vm.OperandStack.Ptr != -1 { 105 | t.Fail() 106 | } 107 | } 108 | 109 | func Test_setGlobal(t *testing.T) { 110 | ctx := &Frame{ 111 | Func: &wasmFunc{ 112 | body: []byte{byte(expr.OpCodeGlobalSet), 0x05}, 113 | }, 114 | } 115 | 116 | exp := uint64(100) 117 | st := stacks.NewOperandStack() 118 | st.Push(exp) 119 | 120 | vm := &Instance{Active: ctx, OperandStack: st, Globals: []uint64{0, 0, 0, 0, 0, 0}} 121 | err := setGlobal(vm) 122 | if err != nil { 123 | t.Fail() 124 | } 125 | if vm.Globals[5] != exp { 126 | t.Fail() 127 | } 128 | if vm.OperandStack.Ptr != -1 { 129 | t.Fail() 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /wasm/memory.go: -------------------------------------------------------------------------------- 1 | package wasm 2 | 3 | import ( 4 | "github.com/c0mm4nd/wasman/config" 5 | "github.com/c0mm4nd/wasman/types" 6 | ) 7 | 8 | // Memory is an instance of the memory value 9 | type Memory struct { 10 | types.MemoryType 11 | Value []byte 12 | } 13 | 14 | // memoryBytesNumToPages converts the given number of bytes into the number of pages. 15 | func memoryBytesNumToPages(bytesNum uint64) (pages uint32) { 16 | return uint32(bytesNum >> config.DefaultMemoryPageSizeInBits) 17 | } 18 | 19 | // MemoryPagesToBytesNum converts the given pages into the number of bytes contained in these pages. 20 | func MemoryPagesToBytesNum(pages uint32) (bytesNum uint64) { 21 | return uint64(pages) << config.DefaultMemoryPageSizeInBits 22 | } 23 | 24 | // PageSize returns the current memory buffer size in pages. 25 | func (m *Memory) PageSize() uint32 { 26 | return memoryBytesNumToPages(uint64(len(m.Value))) 27 | } 28 | 29 | func (mem *Memory) Grow(newPages uint32) (result uint32) { 30 | currentPages := memoryBytesNumToPages(uint64(len(mem.Value))) 31 | 32 | if mem.Max != nil && 33 | newPages+currentPages > *(mem.Max) { 34 | return 0xffffffff // failed to grow 35 | } 36 | 37 | mem.Value = append(mem.Value, make([]byte, MemoryPagesToBytesNum(newPages))...) 38 | 39 | return currentPages 40 | } 41 | -------------------------------------------------------------------------------- /wasm/module.go: -------------------------------------------------------------------------------- 1 | package wasm 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "io" 8 | 9 | "github.com/c0mm4nd/wasman/config" 10 | 11 | "github.com/c0mm4nd/wasman/segments" 12 | "github.com/c0mm4nd/wasman/types" 13 | ) 14 | 15 | var ( 16 | magic = []byte{0x00, 0x61, 0x73, 0x6D} // aka header 17 | version = []byte{0x01, 0x00, 0x00, 0x00} // version 1, https://www.w3.org/TR/wasm-core-1/ 18 | ) 19 | 20 | // errors on parsing module 21 | var ( 22 | ErrInvalidMagicNumber = errors.New("invalid magic number") 23 | ErrInvalidVersion = errors.New("invalid version header") 24 | ) 25 | 26 | // Module is a standard wasm module implement according to wasm v1, https://www.w3.org/TR/wasm-core-1/#syntax-module%E2%91%A0 27 | type Module struct { 28 | config.ModuleConfig 29 | 30 | // sections 31 | TypeSection []*types.FuncType 32 | ImportSection []*segments.ImportSegment 33 | FunctionSection []uint32 34 | TableSection []*types.TableType 35 | MemorySection []*types.MemoryType 36 | GlobalSection []*segments.GlobalSegment 37 | ExportSection map[string]*segments.ExportSegment 38 | StartSection []uint32 39 | ElementsSection []*segments.ElemSegment 40 | CodeSection []*segments.CodeSegment 41 | DataSection []*segments.DataSegment 42 | 43 | // index spaces 44 | IndexSpace *IndexSpace 45 | } 46 | 47 | // IndexSpace is the indeices to the imports 48 | type IndexSpace struct { 49 | Functions []fn 50 | Globals []*Global 51 | Tables []*Table 52 | Memories []*Memory 53 | } 54 | 55 | // NewModule reads bytes from the io.Reader and read all sections, finally return a wasman.Module entity if no error 56 | func NewModule(config config.ModuleConfig, r *bytes.Reader) (*Module, error) { 57 | // magic number 58 | buf := make([]byte, 4) 59 | if n, err := io.ReadFull(r, buf); err != nil || n != 4 || !bytes.Equal(buf, magic) { 60 | return nil, ErrInvalidMagicNumber 61 | } 62 | 63 | // version 64 | if n, err := io.ReadFull(r, buf); err != nil || n != 4 || !bytes.Equal(buf, version) { 65 | return nil, ErrInvalidVersion 66 | } 67 | 68 | module := &Module{ 69 | ModuleConfig: config, 70 | } 71 | 72 | if err := module.readSections(r); err != nil { 73 | return nil, fmt.Errorf("readSections failed: %w", err) 74 | } 75 | 76 | return module, nil 77 | } 78 | -------------------------------------------------------------------------------- /wasm/section.go: -------------------------------------------------------------------------------- 1 | package wasm 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "io" 8 | 9 | "github.com/c0mm4nd/wasman/leb128decode" 10 | "github.com/c0mm4nd/wasman/segments" 11 | "github.com/c0mm4nd/wasman/types" 12 | ) 13 | 14 | type sectionID byte 15 | 16 | const ( 17 | sectionIDCustom sectionID = 0 18 | sectionIDType sectionID = 1 19 | sectionIDImport sectionID = 2 20 | sectionIDFunction sectionID = 3 21 | sectionIDTable sectionID = 4 22 | sectionIDMemory sectionID = 5 23 | sectionIDGlobal sectionID = 6 24 | sectionIDExport sectionID = 7 25 | sectionIDStart sectionID = 8 26 | sectionIDElement sectionID = 9 27 | sectionIDCode sectionID = 10 28 | sectionIDData sectionID = 11 29 | ) 30 | 31 | func (m *Module) readSections(r *bytes.Reader) error { 32 | for { 33 | if err := m.readSection(r); errors.Is(err, io.EOF) { 34 | return nil 35 | } else if err != nil { 36 | return err 37 | } 38 | } 39 | } 40 | 41 | func (m *Module) readSection(r *bytes.Reader) error { 42 | b := make([]byte, 1) 43 | if _, err := io.ReadFull(r, b); err != nil { 44 | return fmt.Errorf("read section id: %w", err) 45 | } 46 | 47 | ss, _, err := leb128decode.DecodeUint32(r) 48 | if err != nil { 49 | return fmt.Errorf("get size of section for id=%d: %w", sectionID(b[0]), err) 50 | } 51 | 52 | switch sectionID(b[0]) { 53 | case sectionIDCustom: 54 | // Custom section is ignored here: https://www.w3.org/TR/wasm-core-1/#custom-section 55 | bb := make([]byte, ss) 56 | _, err = io.ReadFull(r, bb) 57 | case sectionIDType: 58 | err = m.readSectionTypes(r) 59 | case sectionIDImport: 60 | err = m.readSectionImports(r) 61 | case sectionIDFunction: 62 | err = m.readSectionFunctions(r) 63 | case sectionIDTable: 64 | err = m.readSectionTables(r) 65 | case sectionIDMemory: 66 | err = m.readSectionMemories(r) 67 | case sectionIDGlobal: 68 | err = m.readSectionGlobals(r) 69 | case sectionIDExport: 70 | err = m.readSectionExports(r) 71 | case sectionIDStart: 72 | err = m.readSectionStart(r) 73 | case sectionIDElement: 74 | err = m.readSectionElement(r) 75 | case sectionIDCode: 76 | err = m.readSectionCodes(r) 77 | case sectionIDData: 78 | err = m.readSectionData(r) 79 | default: 80 | err = errors.New("invalid section id") 81 | } 82 | 83 | if err != nil { 84 | return fmt.Errorf("read section for %d: %w", sectionID(b[0]), err) 85 | } 86 | return nil 87 | } 88 | 89 | func (m *Module) readSectionTypes(r *bytes.Reader) error { 90 | vs, _, err := leb128decode.DecodeUint32(r) 91 | if err != nil { 92 | return fmt.Errorf("get size of vector: %w", err) 93 | } 94 | 95 | m.TypeSection = make([]*types.FuncType, vs) 96 | for i := range m.TypeSection { 97 | m.TypeSection[i], err = types.ReadFuncType(r) 98 | if err != nil { 99 | return fmt.Errorf("read %d-th function type: %w", i, err) 100 | } 101 | } 102 | 103 | return nil 104 | } 105 | 106 | func (m *Module) readSectionImports(r *bytes.Reader) error { 107 | vs, _, err := leb128decode.DecodeUint32(r) 108 | if err != nil { 109 | return fmt.Errorf("get size of vector: %w", err) 110 | } 111 | 112 | m.ImportSection = make([]*segments.ImportSegment, vs) 113 | for i := range m.ImportSection { 114 | m.ImportSection[i], err = segments.ReadImportSegment(r) 115 | if err != nil { 116 | return fmt.Errorf("read import: %w", err) 117 | } 118 | } 119 | 120 | return nil 121 | } 122 | 123 | func (m *Module) readSectionFunctions(r *bytes.Reader) error { 124 | vs, _, err := leb128decode.DecodeUint32(r) 125 | if err != nil { 126 | return fmt.Errorf("get size of vector: %w", err) 127 | } 128 | 129 | m.FunctionSection = make([]uint32, vs) 130 | for i := range m.FunctionSection { 131 | m.FunctionSection[i], _, err = leb128decode.DecodeUint32(r) 132 | if err != nil { 133 | return fmt.Errorf("get typeidx: %w", err) 134 | } 135 | } 136 | 137 | return nil 138 | } 139 | 140 | func (m *Module) readSectionTables(r *bytes.Reader) error { 141 | vs, _, err := leb128decode.DecodeUint32(r) 142 | if err != nil { 143 | return fmt.Errorf("get size of vector: %w", err) 144 | } 145 | 146 | m.TableSection = make([]*types.TableType, vs) 147 | for i := range m.TableSection { 148 | m.TableSection[i], err = types.ReadTableType(r) 149 | if err != nil { 150 | return fmt.Errorf("read table type: %w", err) 151 | } 152 | } 153 | 154 | return nil 155 | } 156 | 157 | func (m *Module) readSectionMemories(r *bytes.Reader) error { 158 | vs, _, err := leb128decode.DecodeUint32(r) 159 | if err != nil { 160 | return fmt.Errorf("get size of vector: %w", err) 161 | } 162 | 163 | m.MemorySection = make([]*types.MemoryType, vs) 164 | for i := range m.MemorySection { 165 | m.MemorySection[i], err = types.ReadMemoryType(r) 166 | if err != nil { 167 | return fmt.Errorf("read memory type: %w", err) 168 | } 169 | } 170 | 171 | return nil 172 | } 173 | 174 | func (m *Module) readSectionGlobals(r *bytes.Reader) error { 175 | vs, _, err := leb128decode.DecodeUint32(r) 176 | if err != nil { 177 | return fmt.Errorf("get size of vector: %w", err) 178 | } 179 | 180 | m.GlobalSection = make([]*segments.GlobalSegment, vs) 181 | for i := range m.GlobalSection { 182 | m.GlobalSection[i], err = segments.ReadGlobalSegment(r) 183 | if err != nil { 184 | return fmt.Errorf("read global segment: %w ", err) 185 | } 186 | } 187 | 188 | return nil 189 | } 190 | 191 | func (m *Module) readSectionExports(r *bytes.Reader) error { 192 | vs, _, err := leb128decode.DecodeUint32(r) 193 | if err != nil { 194 | return fmt.Errorf("get size of vector: %w", err) 195 | } 196 | 197 | m.ExportSection = make(map[string]*segments.ExportSegment, vs) 198 | for i := uint32(0); i < vs; i++ { 199 | expDesc, err := segments.ReadExportSegment(r) 200 | if err != nil { 201 | return fmt.Errorf("read export: %w", err) 202 | } 203 | 204 | m.ExportSection[expDesc.Name] = expDesc 205 | } 206 | 207 | return nil 208 | } 209 | 210 | func (m *Module) readSectionStart(r *bytes.Reader) error { 211 | vs, _, err := leb128decode.DecodeUint32(r) 212 | if err != nil { 213 | return fmt.Errorf("get size of vector: %w", err) 214 | } 215 | 216 | m.StartSection = make([]uint32, vs) 217 | for i := range m.StartSection { 218 | m.StartSection[i], _, err = leb128decode.DecodeUint32(r) 219 | if err != nil { 220 | return fmt.Errorf("read function index: %w", err) 221 | } 222 | } 223 | 224 | return nil 225 | } 226 | 227 | func (m *Module) readSectionElement(r *bytes.Reader) error { 228 | vs, _, err := leb128decode.DecodeUint32(r) 229 | if err != nil { 230 | return fmt.Errorf("get size of vector: %w", err) 231 | } 232 | 233 | m.ElementsSection = make([]*segments.ElemSegment, vs) 234 | for i := range m.ElementsSection { 235 | m.ElementsSection[i], err = segments.ReadElemSegment(r) 236 | if err != nil { 237 | return fmt.Errorf("read element: %w", err) 238 | } 239 | } 240 | 241 | return nil 242 | } 243 | 244 | func (m *Module) readSectionCodes(r *bytes.Reader) error { 245 | vs, _, err := leb128decode.DecodeUint32(r) 246 | if err != nil { 247 | return fmt.Errorf("get size of vector: %w", err) 248 | } 249 | 250 | m.CodeSection = make([]*segments.CodeSegment, vs) 251 | for i := range m.CodeSection { 252 | m.CodeSection[i], err = segments.ReadCodeSegment(r) 253 | if err != nil { 254 | return fmt.Errorf("read code segment: %w", err) 255 | } 256 | } 257 | 258 | return nil 259 | } 260 | 261 | func (m *Module) readSectionData(r *bytes.Reader) error { 262 | vs, _, err := leb128decode.DecodeUint32(r) 263 | if err != nil { 264 | return fmt.Errorf("get size of vector: %w", err) 265 | } 266 | 267 | m.DataSection = make([]*segments.DataSegment, vs) 268 | for i := range m.DataSection { 269 | m.DataSection[i], err = segments.ReadDataSegment(r) 270 | if err != nil { 271 | return fmt.Errorf("read data segment: %w", err) 272 | } 273 | } 274 | 275 | return nil 276 | } 277 | -------------------------------------------------------------------------------- /wasm/table.go: -------------------------------------------------------------------------------- 1 | package wasm 2 | 3 | import "github.com/c0mm4nd/wasman/types" 4 | 5 | // Table is an instance of the table value 6 | type Table struct { 7 | types.TableType 8 | Value []*uint32 // vec of addr to func 9 | } 10 | --------------------------------------------------------------------------------