├── .codecov.yml ├── .gitmodules ├── .travis.yml ├── LICENSE ├── README.md ├── ci └── run-tests.go ├── cmd ├── wasm-dump │ ├── hex.go │ ├── main.go │ ├── main_test.go │ └── testdata │ │ ├── add-ex-main.wasm.txt │ │ ├── add-ex.wasm.txt │ │ └── basic.wasm.txt └── wasm-run │ ├── main.go │ ├── main_test.go │ └── testdata │ └── basic.wasm.txt ├── disasm ├── asm.go ├── asm_test.go ├── disasm.go ├── disasm_test.go └── log.go ├── doc.go ├── exec ├── call.go ├── call_test.go ├── const.go ├── control.go ├── conv.go ├── exec_test.go ├── func.go ├── func_table.go ├── internal │ └── compile │ │ ├── allocator.go │ │ ├── allocator_test.go │ │ ├── backend.go │ │ ├── backend_amd64.go │ │ ├── backend_amd64_test.go │ │ ├── compile.go │ │ ├── lower_amd64.go │ │ ├── native.go │ │ ├── native_386.go │ │ ├── native_amd64.go │ │ ├── native_exec.go │ │ ├── native_exec_386.go │ │ ├── native_exec_amd64.go │ │ ├── native_exec_amd64.s │ │ ├── optimizer_amd64.go │ │ ├── scanner.go │ │ └── scanner_amd64.go ├── memory.go ├── native_compile.go ├── native_compile_nogae_amd64.go ├── native_compile_test.go ├── num.go ├── num_test.go ├── parametric.go ├── reinterp.go ├── testdata │ ├── add-ex-main.wasm │ ├── add-ex-main.wast │ ├── add-ex.wasm │ ├── add-ex.wast │ ├── basic.wasm │ ├── binary.wasm │ ├── br.wasm │ ├── brif-loop.wasm │ ├── brif.wasm │ ├── brtable.wasm │ ├── bug-49.wasm │ ├── bug-49.wat │ ├── call-zero-args.wasm │ ├── call.wasm │ ├── callindirect.wasm │ ├── cast.wasm │ ├── compare.wasm │ ├── convert.wasm │ ├── expr-block.wasm │ ├── expr-br.wasm │ ├── expr-brif.wasm │ ├── expr-if.wasm │ ├── if.wasm │ ├── ifelse-stack-bug.wasm │ ├── load.wasm │ ├── loop.wasm │ ├── modules.json │ ├── nested-if.wasm │ ├── return-void.wasm │ ├── return.wasm │ ├── rust-basic.rs │ ├── rust-basic.wasm │ ├── select.wasm │ ├── spec │ │ ├── address.wasm │ │ ├── address.wast │ │ ├── block.wasm │ │ ├── block.wast │ │ ├── br.wasm │ │ ├── br.wast │ │ ├── br_if.wasm │ │ ├── br_if.wast │ │ ├── br_table.wasm │ │ ├── br_table.wast │ │ ├── break-drop.wasm │ │ ├── break-drop.wast │ │ ├── call_indirect.wasm │ │ ├── call_indirect.wast │ │ ├── endianness.wasm │ │ ├── endianness.wast │ │ ├── fac.wasm │ │ ├── fac.wast │ │ ├── forward.wasm │ │ ├── forward.wast │ │ ├── get_local.wasm │ │ ├── get_local.wast │ │ ├── globals.wasm │ │ ├── globals.wast │ │ ├── i32.wasm │ │ ├── if.wasm │ │ ├── if.wast │ │ ├── loop.wasm │ │ ├── loop.wast │ │ ├── memory_redundancy.wasm │ │ ├── memory_redundancy.wast │ │ ├── modules.json │ │ ├── names.wasm │ │ ├── names.wast │ │ ├── nop.wasm │ │ ├── nop.wast │ │ ├── resizing.wasm │ │ ├── resizing.wast │ │ ├── return.wasm │ │ ├── return.wast │ │ ├── select.wasm │ │ ├── select.wast │ │ ├── switch.wasm │ │ ├── switch.wast │ │ ├── tee_local.wasm │ │ ├── tee_local.wast │ │ ├── traps_int_div.wasm │ │ ├── traps_int_div.wast │ │ ├── traps_int_rem.wasm │ │ ├── traps_int_rem.wast │ │ ├── traps_mem.wasm │ │ ├── traps_mem.wast │ │ ├── unreachable.wasm │ │ ├── unwind.wasm │ │ └── unwind.wast │ ├── start.wasm │ ├── store.wasm │ └── unary.wasm ├── var.go ├── vm.go ├── vm_debug_stack.go ├── vm_example_test.go ├── vm_stack.go └── vm_test.go ├── go.mod ├── go.sum ├── internal └── stack │ └── stack.go ├── tests ├── run_testcase.sh └── spec_test_runner.go ├── validate ├── error.go ├── log.go ├── operand.go ├── validate.go ├── validate_test.go └── vm.go ├── wagon_test.go ├── wasm ├── doc.go ├── encode.go ├── encode_test.go ├── imports.go ├── index.go ├── init_expr.go ├── internal │ └── readpos │ │ ├── readpos.go │ │ └── readpos_test.go ├── leb128 │ ├── read.go │ ├── read_test.go │ ├── write.go │ └── write_test.go ├── log.go ├── module.go ├── module_test.go ├── operators │ ├── call.go │ ├── comp.go │ ├── const.go │ ├── control.go │ ├── conv.go │ ├── conv_test.go │ ├── memory.go │ ├── num.go │ ├── op.go │ ├── op_test.go │ ├── parametric.go │ ├── reinterp.go │ ├── var.go │ └── wagon_internal.go ├── read.go ├── section.go ├── section_test.go ├── testdata │ ├── custom_funcs_locals.wasm │ ├── custom_section.wasm │ ├── empty.wasm │ ├── empty.wast │ ├── empty.wat │ ├── f64.wasm │ ├── f64.wast │ ├── f64.wat │ ├── globals.wasm │ ├── globals.wast │ ├── hello-world-tinygo.wasm │ ├── i64.wasm │ ├── i64.wast │ ├── i64.wat │ ├── int_exprs.wasm │ ├── int_exprs.wast │ ├── int_exprs.wat │ ├── nofuncs.wasm │ ├── nofuncs.wast │ └── spec │ │ ├── sigtest.wasm │ │ └── sigtest.wat └── types.go └── wast ├── doc.go ├── scanner.go ├── scanner_test.go ├── token.go ├── write.go └── write_test.go /.codecov.yml: -------------------------------------------------------------------------------- 1 | 2 | 3 | coverage: 4 | status: 5 | patch: 6 | default: 7 | enabled: no 8 | project: 9 | default: 10 | enabled: no 11 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "tests/spectestcase"] 2 | path = tests/spectestcase 3 | url = https://github.com/ontio/testsuite 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go_import_path: github.com/go-interpreter/wagon 3 | os: 4 | - linux 5 | 6 | env: 7 | - TAGS="-tags \"travis debugstack\"" 8 | 9 | cache: 10 | directories: 11 | - $HOME/.cache/go-build 12 | - $HOME/gopath/pkg/mod 13 | 14 | matrix: 15 | fast_finish: true 16 | allow_failures: 17 | - go: master 18 | include: 19 | - go: 1.14.x 20 | env: 21 | - COVERAGE="-cover -race" 22 | - go: 1.13.x 23 | env: 24 | - COVERAGE="" 25 | - go: master 26 | env: 27 | - COVERAGE="-race" 28 | - GO111MODULE="on" 29 | 30 | sudo: false 31 | 32 | script: 33 | - go get -d -t -v ./... 34 | - GOARCH=386 go install -v $TAGS ./... 35 | - GOARCH=amd64 go install -v $TAGS ./... 36 | - go run ./ci/run-tests.go $COVERAGE 37 | - cd tests && bash ./run_testcase.sh 38 | 39 | after_success: 40 | - bash <(curl -s https://codecov.io/bash) 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright ©2017 The go-interpreter Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | * Redistributions of source code must retain the above copyright 6 | notice, this list of conditions and the following disclaimer. 7 | * Redistributions in binary form must reproduce the above copyright 8 | notice, this list of conditions and the following disclaimer in the 9 | documentation and/or other materials provided with the distribution. 10 | * Neither the name of the go-interpreter project nor the names of its authors and 11 | contributors may be used to endorse or promote products derived from this 12 | software without specific prior written permission. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | wagon 2 | ===== 3 | 4 | [![Build Status](https://travis-ci.org/go-interpreter/wagon.svg?branch=master)](https://travis-ci.org/go-interpreter/wagon) 5 | [![codecov](https://codecov.io/gh/go-interpreter/wagon/branch/master/graph/badge.svg)](https://codecov.io/gh/go-interpreter/wagon) 6 | [![GoDoc](https://godoc.org/github.com/go-interpreter/wagon?status.svg)](https://godoc.org/github.com/go-interpreter/wagon) 7 | 8 | `wagon` is a [WebAssembly](http://webassembly.org)-based interpreter in [Go](https://golang.org), for [Go](https://golang.org). 9 | 10 | **As of 2020/05/11 Wagon is in read-only mode, and looking for a maintainer. 11 | You may want to look at https://github.com/mathetake/gasm instead.** 12 | 13 | --- 14 | 15 | ## Purpose 16 | 17 | `wagon` aims to provide tools (executables+libraries) to: 18 | 19 | - decode `wasm` binary files 20 | - load and execute `wasm` modules' bytecode. 21 | 22 | `wagon` doesn't concern itself with the production of the `wasm` binary files; 23 | these files should be produced with another tool (such as [wabt](https://github.com/WebAssembly/wabt) or [binaryen](https://github.com/WebAssembly/binaryen).) 24 | `wagon` *may* provide a utility to produce `wasm` files from `wast` or `wat` files (and vice versa.) 25 | 26 | The primary goal of `wagon` is to provide the building blocks to be able to build an interpreter for Go code, that could be embedded in Jupyter or any Go program. 27 | 28 | 29 | ## Contributing 30 | 31 | See the [CONTRIBUTING](https://github.com/go-interpreter/license/blob/master/CONTRIBUTE.md) guide for pointers on how to contribute to `go-interpreter` and `wagon`. 32 | -------------------------------------------------------------------------------- /ci/run-tests.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build ignore 6 | 7 | package main 8 | 9 | import ( 10 | "bufio" 11 | "bytes" 12 | "flag" 13 | "io/ioutil" 14 | "log" 15 | "os" 16 | "os/exec" 17 | "strings" 18 | ) 19 | 20 | func main() { 21 | log.SetPrefix("ci: ") 22 | log.SetFlags(0) 23 | 24 | var ( 25 | race = flag.Bool("race", false, "enable race detector") 26 | cover = flag.Bool("cover", false, "enable code coverage") 27 | tags = flag.String("tags", "", "build tags") 28 | ) 29 | 30 | flag.Parse() 31 | 32 | out := new(bytes.Buffer) 33 | cmd := exec.Command("go", "list", "./...") 34 | cmd.Stdout = out 35 | cmd.Stderr = os.Stderr 36 | cmd.Stdin = os.Stdin 37 | 38 | err := cmd.Run() 39 | if err != nil { 40 | log.Fatal(err) 41 | } 42 | 43 | f, err := os.Create("coverage.txt") 44 | if err != nil { 45 | log.Fatal(err) 46 | } 47 | defer f.Close() 48 | 49 | args := []string{"test", "-v"} 50 | 51 | if *cover { 52 | args = append(args, "-coverprofile=profile.out", "-covermode=atomic") 53 | } 54 | if *tags != "" { 55 | args = append(args, "-tags="+*tags) 56 | } 57 | if *race { 58 | args = append(args, "-race") 59 | } 60 | args = append(args, "") 61 | 62 | scan := bufio.NewScanner(out) 63 | for scan.Scan() { 64 | pkg := scan.Text() 65 | if strings.Contains(pkg, "vendor") { 66 | continue 67 | } 68 | args[len(args)-1] = pkg 69 | cmd := exec.Command("go", args...) 70 | cmd.Stdin = os.Stdin 71 | cmd.Stdout = os.Stdout 72 | cmd.Stderr = os.Stderr 73 | err := cmd.Run() 74 | if err != nil { 75 | log.Fatal(err) 76 | } 77 | if *cover { 78 | profile, err := ioutil.ReadFile("profile.out") 79 | if err != nil { 80 | log.Fatal(err) 81 | } 82 | _, err = f.Write(profile) 83 | if err != nil { 84 | log.Fatal(err) 85 | } 86 | os.Remove("profile.out") 87 | } 88 | } 89 | 90 | err = f.Close() 91 | if err != nil { 92 | log.Fatal(err) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /cmd/wasm-dump/hex.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "bytes" 9 | "encoding/hex" 10 | "io" 11 | ) 12 | 13 | // hexDump is like hex.Dump but with an optional offset. 14 | func hexDump(data []byte, offset uint) string { 15 | buf := new(bytes.Buffer) 16 | d := &dumper{w: buf, n: offset} 17 | d.Write(data) 18 | d.Close() 19 | return buf.String() 20 | } 21 | 22 | type dumper struct { 23 | w io.Writer 24 | rightChars [18]byte 25 | buf [14]byte 26 | used int // number of bytes in the current line 27 | n uint // number of bytes, total 28 | } 29 | 30 | func toChar(b byte) byte { 31 | if b < 32 || b > 126 { 32 | return '.' 33 | } 34 | return b 35 | } 36 | 37 | func (h *dumper) Write(data []byte) (n int, err error) { 38 | // Output lines look like: 39 | // 00000010 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d |./0123456789:;<=| 40 | // ^ offset ^ extra space ^ ASCII of line. 41 | for i := range data { 42 | if h.used == 0 { 43 | // At the beginning of a line we print the current 44 | // offset in hex. 45 | h.buf[0] = byte(h.n >> 24) 46 | h.buf[1] = byte(h.n >> 16) 47 | h.buf[2] = byte(h.n >> 8) 48 | h.buf[3] = byte(h.n) 49 | hex.Encode(h.buf[4:], h.buf[:4]) 50 | h.buf[12] = ' ' 51 | h.buf[13] = ' ' 52 | _, err = h.w.Write(h.buf[4:]) 53 | if err != nil { 54 | return 55 | } 56 | } 57 | hex.Encode(h.buf[:], data[i:i+1]) 58 | h.buf[2] = ' ' 59 | l := 3 60 | if h.used == 7 { 61 | // There's an additional space after the 8th byte. 62 | h.buf[3] = ' ' 63 | l = 4 64 | } else if h.used == 15 { 65 | // At the end of the line there's an extra space and 66 | // the bar for the right column. 67 | h.buf[3] = ' ' 68 | h.buf[4] = '|' 69 | l = 5 70 | } 71 | _, err = h.w.Write(h.buf[:l]) 72 | if err != nil { 73 | return 74 | } 75 | n++ 76 | h.rightChars[h.used] = toChar(data[i]) 77 | h.used++ 78 | h.n++ 79 | if h.used == 16 { 80 | h.rightChars[16] = '|' 81 | h.rightChars[17] = '\n' 82 | _, err = h.w.Write(h.rightChars[:]) 83 | if err != nil { 84 | return 85 | } 86 | h.used = 0 87 | } 88 | } 89 | return 90 | } 91 | 92 | func (h *dumper) Close() (err error) { 93 | // See the comments in Write() for the details of this format. 94 | if h.used == 0 { 95 | return 96 | } 97 | h.buf[0] = ' ' 98 | h.buf[1] = ' ' 99 | h.buf[2] = ' ' 100 | h.buf[3] = ' ' 101 | h.buf[4] = '|' 102 | nBytes := h.used 103 | for h.used < 16 { 104 | l := 3 105 | if h.used == 7 { 106 | l = 4 107 | } else if h.used == 15 { 108 | l = 5 109 | } 110 | _, err = h.w.Write(h.buf[:l]) 111 | if err != nil { 112 | return 113 | } 114 | h.used++ 115 | } 116 | h.rightChars[nBytes] = '|' 117 | h.rightChars[nBytes+1] = '\n' 118 | _, err = h.w.Write(h.rightChars[:nBytes+2]) 119 | return 120 | } 121 | -------------------------------------------------------------------------------- /cmd/wasm-dump/main_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "bytes" 9 | "flag" 10 | "io/ioutil" 11 | "testing" 12 | ) 13 | 14 | func TestProcess(t *testing.T) { 15 | opts := []string{"-h", "-x", "-s", "-d"} 16 | err := flag.CommandLine.Parse(opts) 17 | if err != nil { 18 | t.Fatal(err) 19 | } 20 | 21 | for _, tc := range []struct { 22 | name string 23 | want string 24 | }{ 25 | { 26 | name: "../../exec/testdata/basic.wasm", 27 | want: "testdata/basic.wasm.txt", 28 | }, 29 | { 30 | name: "../../exec/testdata/add-ex.wasm", 31 | want: "testdata/add-ex.wasm.txt", 32 | }, 33 | { 34 | name: "../../exec/testdata/add-ex-main.wasm", 35 | want: "testdata/add-ex-main.wasm.txt", 36 | }, 37 | } { 38 | t.Run(tc.name, func(t *testing.T) { 39 | out := new(bytes.Buffer) 40 | process(out, tc.name) 41 | 42 | want, err := ioutil.ReadFile(tc.want) 43 | if err != nil { 44 | t.Fatal(err) 45 | } 46 | 47 | if got, want := out.Bytes(), want; !bytes.Equal(got, want) { 48 | t.Fatalf("invalid output.\ngot:\n%s\nwant:\n%s\n", string(got), string(want)) 49 | } 50 | }) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /cmd/wasm-dump/testdata/add-ex-main.wasm.txt: -------------------------------------------------------------------------------- 1 | ../../exec/testdata/add-ex-main.wasm: module version: 0x1 2 | 3 | sections: 4 | 5 | type start=0x0000000a end=0x0000001e (size=0x00000014) count: 4 6 | import start=0x00000020 end=0x00000037 (size=0x00000017) count: 2 7 | function start=0x00000039 end=0x0000003d (size=0x00000004) count: 3 8 | code start=0x0000003f end=0x0000005f (size=0x00000020) count: 3 9 | ../../exec/testdata/add-ex-main.wasm: module version: 0x1 10 | 11 | contents of section type: 12 | 0000000a 04 60 02 7f 7f 01 7f 60 01 7f 00 60 00 01 7f 60 |.`.....`...`...`| 13 | 0000001a 02 7f 7f 00 |....| 14 | 15 | contents of section import: 16 | 00000020 02 03 61 64 64 04 69 61 64 64 00 00 02 67 6f 05 |..add.iadd...go.| 17 | 00000030 70 72 69 6e 74 00 01 |print..| 18 | 19 | contents of section function: 20 | 00000039 03 02 00 03 |....| 21 | 22 | contents of section code: 23 | 0000003f 03 09 00 41 02 41 28 10 00 0f 0b 09 00 20 00 20 |...A.A(...... . | 24 | 0000004f 01 10 00 0f 0b 0a 00 20 00 20 01 10 00 10 01 0b |....... . ......| 25 | 26 | ../../exec/testdata/add-ex-main.wasm: module version: 0x1 27 | 28 | code disassembly: 29 | 30 | func[0]: [i32]> 31 | 000000: 41 02 00 00 00 | i32.const 2 32 | 000006: 41 28 00 00 00 | i32.const 40 33 | 00000c: 10 00 00 00 00 | call 0 34 | 000012: 0f | return 35 | 000014: 0b | end 36 | 37 | func[1]: [i32]> 38 | 000000: 20 00 00 00 00 | get_local 0 39 | 000006: 20 01 00 00 00 | get_local 1 40 | 00000c: 10 00 00 00 00 | call 0 41 | 000012: 0f | return 42 | 000014: 0b | end 43 | 44 | func[2]: []> 45 | 000000: 20 00 00 00 00 | get_local 0 46 | 000006: 20 01 00 00 00 | get_local 1 47 | 00000c: 10 00 00 00 00 | call 0 48 | 000012: 10 01 00 00 00 | call 1 49 | 000018: 0b | end 50 | ../../exec/testdata/add-ex-main.wasm: module version: 0x1 51 | 52 | section details: 53 | 54 | type: 55 | - type[0] [i32]> 56 | - type[1] []> 57 | - type[2] [i32]> 58 | - type[3] []> 59 | import: 60 | - function[0] sig=0 <- add.iadd 61 | - function[1] sig=1 <- go.print 62 | function: 63 | - func[0] sig=2 64 | - func[1] sig=0 65 | - func[2] sig=3 66 | -------------------------------------------------------------------------------- /cmd/wasm-dump/testdata/add-ex.wasm.txt: -------------------------------------------------------------------------------- 1 | ../../exec/testdata/add-ex.wasm: module version: 0x1 2 | 3 | sections: 4 | 5 | type start=0x0000000a end=0x00000011 (size=0x00000007) count: 1 6 | function start=0x00000013 end=0x00000015 (size=0x00000002) count: 1 7 | export start=0x00000017 end=0x0000001f (size=0x00000008) count: 1 8 | code start=0x00000021 end=0x0000002a (size=0x00000009) count: 1 9 | ../../exec/testdata/add-ex.wasm: module version: 0x1 10 | 11 | contents of section type: 12 | 0000000a 01 60 02 7f 7f 01 7f |.`.....| 13 | 14 | contents of section function: 15 | 00000013 01 00 |..| 16 | 17 | contents of section export: 18 | 00000017 01 04 69 61 64 64 00 00 |..iadd..| 19 | 20 | contents of section code: 21 | 00000021 01 07 00 20 00 20 01 6a 0b |... . .j.| 22 | 23 | ../../exec/testdata/add-ex.wasm: module version: 0x1 24 | 25 | code disassembly: 26 | 27 | func[0]: [i32]> 28 | 000000: 20 00 00 00 00 | get_local 0 29 | 000006: 20 01 00 00 00 | get_local 1 30 | 00000c: 6a | i32.add 31 | 00000e: 0b | end 32 | ../../exec/testdata/add-ex.wasm: module version: 0x1 33 | 34 | section details: 35 | 36 | type: 37 | - type[0] [i32]> 38 | function: 39 | - func[0] sig=0 40 | export: 41 | - function[0] -> "iadd" 42 | -------------------------------------------------------------------------------- /cmd/wasm-dump/testdata/basic.wasm.txt: -------------------------------------------------------------------------------- 1 | ../../exec/testdata/basic.wasm: module version: 0x1 2 | 3 | sections: 4 | 5 | type start=0x0000000a end=0x0000000f (size=0x00000005) count: 1 6 | function start=0x00000011 end=0x00000013 (size=0x00000002) count: 1 7 | export start=0x00000015 end=0x0000001d (size=0x00000008) count: 1 8 | code start=0x0000001f end=0x00000026 (size=0x00000007) count: 1 9 | ../../exec/testdata/basic.wasm: module version: 0x1 10 | 11 | contents of section type: 12 | 0000000a 01 60 00 01 7f |.`...| 13 | 14 | contents of section function: 15 | 00000011 01 00 |..| 16 | 17 | contents of section export: 18 | 00000015 01 04 6d 61 69 6e 00 00 |..main..| 19 | 20 | contents of section code: 21 | 0000001f 01 05 00 41 2a 0f 0b |...A*..| 22 | 23 | ../../exec/testdata/basic.wasm: module version: 0x1 24 | 25 | code disassembly: 26 | 27 | func[0]: [i32]> 28 | 000000: 41 2a 00 00 00 | i32.const 42 29 | 000006: 0f | return 30 | 000008: 0b | end 31 | ../../exec/testdata/basic.wasm: module version: 0x1 32 | 33 | section details: 34 | 35 | type: 36 | - type[0] [i32]> 37 | function: 38 | - func[0] sig=0 39 | export: 40 | - function[0] -> "main" 41 | -------------------------------------------------------------------------------- /cmd/wasm-run/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "flag" 9 | "fmt" 10 | "io" 11 | "log" 12 | "os" 13 | 14 | "github.com/go-interpreter/wagon/exec" 15 | "github.com/go-interpreter/wagon/validate" 16 | "github.com/go-interpreter/wagon/wasm" 17 | ) 18 | 19 | func main() { 20 | log.SetPrefix("wasm-run: ") 21 | log.SetFlags(0) 22 | 23 | verbose := flag.Bool("v", false, "enable/disable verbose mode") 24 | verify := flag.Bool("verify-module", false, "run module verification") 25 | 26 | flag.Parse() 27 | 28 | if flag.NArg() < 1 { 29 | flag.Usage() 30 | os.Exit(1) 31 | } 32 | 33 | wasm.SetDebugMode(*verbose) 34 | 35 | run(os.Stdout, flag.Arg(0), *verify) 36 | } 37 | 38 | func run(w io.Writer, fname string, verify bool) { 39 | f, err := os.Open(fname) 40 | if err != nil { 41 | log.Fatal(err) 42 | } 43 | defer f.Close() 44 | 45 | m, err := wasm.ReadModule(f, importer) 46 | if err != nil { 47 | log.Fatalf("could not read module: %v", err) 48 | } 49 | 50 | if verify { 51 | err = validate.VerifyModule(m) 52 | if err != nil { 53 | log.Fatalf("could not verify module: %v", err) 54 | } 55 | } 56 | 57 | if m.Export == nil { 58 | log.Fatalf("module has no export section") 59 | } 60 | 61 | vm, err := exec.NewVM(m) 62 | if err != nil { 63 | log.Fatalf("could not create VM: %v", err) 64 | } 65 | 66 | for name, e := range m.Export.Entries { 67 | i := int64(e.Index) 68 | fidx := m.Function.Types[int(i)] 69 | ftype := m.Types.Entries[int(fidx)] 70 | switch len(ftype.ReturnTypes) { 71 | case 1: 72 | fmt.Fprintf(w, "%s() %s => ", name, ftype.ReturnTypes[0]) 73 | case 0: 74 | fmt.Fprintf(w, "%s() => ", name) 75 | default: 76 | log.Printf("running exported functions with more than one return value is not supported") 77 | continue 78 | } 79 | if len(ftype.ParamTypes) > 0 { 80 | log.Printf("running exported functions with input parameters is not supported") 81 | continue 82 | } 83 | o, err := vm.ExecCode(i) 84 | if err != nil { 85 | fmt.Fprintf(w, "\n") 86 | log.Printf("err=%v", err) 87 | continue 88 | } 89 | if len(ftype.ReturnTypes) == 0 { 90 | fmt.Fprintf(w, "\n") 91 | continue 92 | } 93 | fmt.Fprintf(w, "%[1]v (%[1]T)\n", o) 94 | } 95 | } 96 | 97 | func importer(name string) (*wasm.Module, error) { 98 | f, err := os.Open(name + ".wasm") 99 | if err != nil { 100 | return nil, err 101 | } 102 | defer f.Close() 103 | m, err := wasm.ReadModule(f, nil) 104 | if err != nil { 105 | return nil, err 106 | } 107 | err = validate.VerifyModule(m) 108 | if err != nil { 109 | return nil, err 110 | } 111 | return m, nil 112 | } 113 | -------------------------------------------------------------------------------- /cmd/wasm-run/main_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "bytes" 9 | "io/ioutil" 10 | "testing" 11 | ) 12 | 13 | func TestRun(t *testing.T) { 14 | for _, tc := range []struct { 15 | name string 16 | verify bool 17 | want string 18 | }{ 19 | { 20 | name: "../../exec/testdata/basic.wasm", 21 | want: "testdata/basic.wasm.txt", 22 | }, 23 | { 24 | name: "../../exec/testdata/basic.wasm", 25 | verify: true, 26 | want: "testdata/basic.wasm.txt", 27 | }, 28 | } { 29 | t.Run(tc.name, func(t *testing.T) { 30 | out := new(bytes.Buffer) 31 | run(out, tc.name, tc.verify) 32 | 33 | want, err := ioutil.ReadFile(tc.want) 34 | if err != nil { 35 | t.Fatal(err) 36 | } 37 | 38 | if got, want := string(out.Bytes()), string(want); got != want { 39 | t.Fatalf("invalid output.\ngot:\n%s\nwant:\n%s\n", got, want) 40 | } 41 | }) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /cmd/wasm-run/testdata/basic.wasm.txt: -------------------------------------------------------------------------------- 1 | main() i32 => 42 (uint32) 2 | -------------------------------------------------------------------------------- /disasm/asm.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package disasm 6 | 7 | import ( 8 | "bytes" 9 | "encoding/binary" 10 | "math" 11 | 12 | "github.com/go-interpreter/wagon/wasm" 13 | "github.com/go-interpreter/wagon/wasm/leb128" 14 | ops "github.com/go-interpreter/wagon/wasm/operators" 15 | ) 16 | 17 | // Assemble encodes a set of instructions into binary representation. 18 | func Assemble(instr []Instr) ([]byte, error) { 19 | body := new(bytes.Buffer) 20 | for _, ins := range instr { 21 | body.WriteByte(ins.Op.Code) 22 | switch op := ins.Op.Code; op { 23 | case ops.Block, ops.Loop, ops.If: 24 | body.WriteByte(byte(ins.Immediates[0].(wasm.BlockType))) 25 | case ops.Br, ops.BrIf: 26 | leb128.WriteVarUint32(body, ins.Immediates[0].(uint32)) 27 | case ops.BrTable: 28 | cnt := ins.Immediates[0].(uint32) 29 | leb128.WriteVarUint32(body, cnt) 30 | for i := uint32(0); i < cnt; i++ { 31 | leb128.WriteVarUint32(body, ins.Immediates[i+1].(uint32)) 32 | } 33 | leb128.WriteVarUint32(body, ins.Immediates[1+cnt].(uint32)) 34 | case ops.Call, ops.CallIndirect: 35 | leb128.WriteVarUint32(body, ins.Immediates[0].(uint32)) 36 | if op == ops.CallIndirect { 37 | leb128.WriteVarUint32(body, ins.Immediates[1].(uint32)) 38 | } 39 | case ops.GetLocal, ops.SetLocal, ops.TeeLocal, ops.GetGlobal, ops.SetGlobal: 40 | leb128.WriteVarUint32(body, ins.Immediates[0].(uint32)) 41 | case ops.I32Const: 42 | leb128.WriteVarint64(body, int64(ins.Immediates[0].(int32))) 43 | case ops.I64Const: 44 | leb128.WriteVarint64(body, ins.Immediates[0].(int64)) 45 | case ops.F32Const: 46 | f := ins.Immediates[0].(float32) 47 | var b [4]byte 48 | binary.LittleEndian.PutUint32(b[:], math.Float32bits(f)) 49 | body.Write(b[:]) 50 | case ops.F64Const: 51 | f := ins.Immediates[0].(float64) 52 | var b [8]byte 53 | binary.LittleEndian.PutUint64(b[:], math.Float64bits(f)) 54 | body.Write(b[:]) 55 | case ops.I32Load, ops.I64Load, ops.F32Load, ops.F64Load, ops.I32Load8s, ops.I32Load8u, ops.I32Load16s, ops.I32Load16u, ops.I64Load8s, ops.I64Load8u, ops.I64Load16s, ops.I64Load16u, ops.I64Load32s, ops.I64Load32u, ops.I32Store, ops.I64Store, ops.F32Store, ops.F64Store, ops.I32Store8, ops.I32Store16, ops.I64Store8, ops.I64Store16, ops.I64Store32: 56 | leb128.WriteVarUint32(body, ins.Immediates[0].(uint32)) 57 | leb128.WriteVarUint32(body, ins.Immediates[1].(uint32)) 58 | case ops.CurrentMemory, ops.GrowMemory: 59 | leb128.WriteVarUint32(body, uint32(ins.Immediates[0].(uint8))) 60 | } 61 | } 62 | return body.Bytes(), nil 63 | } 64 | -------------------------------------------------------------------------------- /disasm/asm_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package disasm_test 6 | 7 | import ( 8 | "bytes" 9 | "io/ioutil" 10 | "path/filepath" 11 | "testing" 12 | 13 | "github.com/go-interpreter/wagon/disasm" 14 | "github.com/go-interpreter/wagon/wasm" 15 | ) 16 | 17 | var testPaths = []string{ 18 | "../wasm/testdata", 19 | "../exec/testdata", 20 | "../exec/testdata/spec", 21 | } 22 | 23 | func TestAssemble(t *testing.T) { 24 | for _, dir := range testPaths { 25 | fnames, err := filepath.Glob(filepath.Join(dir, "*.wasm")) 26 | if err != nil { 27 | t.Fatal(err) 28 | } 29 | for _, fname := range fnames { 30 | name := fname 31 | t.Run(filepath.Base(name), func(t *testing.T) { 32 | raw, err := ioutil.ReadFile(name) 33 | if err != nil { 34 | t.Fatal(err) 35 | } 36 | 37 | r := bytes.NewReader(raw) 38 | m, err := wasm.DecodeModule(r) 39 | if err != nil { 40 | t.Fatalf("error reading module %v", err) 41 | } 42 | if m.Code == nil { 43 | t.SkipNow() 44 | } 45 | for _, f := range m.Code.Bodies { 46 | d, err := disasm.Disassemble(f.Code) 47 | if err != nil { 48 | t.Fatalf("disassemble failed: %v", err) 49 | } 50 | code, err := disasm.Assemble(d) 51 | if err != nil { 52 | t.Fatalf("assemble failed: %v", err) 53 | } 54 | if !bytes.Equal(f.Code, code) { 55 | t.Fatal("code is different") 56 | } 57 | } 58 | }) 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /disasm/disasm_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package disasm_test 6 | 7 | import ( 8 | "bytes" 9 | "io/ioutil" 10 | "path/filepath" 11 | "testing" 12 | 13 | "github.com/go-interpreter/wagon/disasm" 14 | "github.com/go-interpreter/wagon/wasm" 15 | ) 16 | 17 | func TestDisassemble(t *testing.T) { 18 | for _, dir := range testPaths { 19 | fnames, err := filepath.Glob(filepath.Join(dir, "*.wasm")) 20 | if err != nil { 21 | t.Fatal(err) 22 | } 23 | for _, fname := range fnames { 24 | name := fname 25 | t.Run(filepath.Base(name), func(t *testing.T) { 26 | raw, err := ioutil.ReadFile(name) 27 | if err != nil { 28 | t.Fatal(err) 29 | } 30 | 31 | r := bytes.NewReader(raw) 32 | m, err := wasm.ReadModule(r, nil) 33 | if err != nil { 34 | t.Fatalf("error reading module %v", err) 35 | } 36 | for _, f := range m.FunctionIndexSpace { 37 | _, err := disasm.NewDisassembly(f, m) 38 | if err != nil { 39 | t.Fatalf("disassemble failed: %v", err) 40 | } 41 | } 42 | }) 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /disasm/log.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package disasm 6 | 7 | import ( 8 | "io/ioutil" 9 | "log" 10 | "os" 11 | ) 12 | 13 | var ( 14 | logger *log.Logger 15 | logging bool 16 | ) 17 | 18 | func SetDebugMode(l bool) { 19 | w := ioutil.Discard 20 | logging = l 21 | 22 | if l { 23 | w = os.Stderr 24 | } 25 | 26 | logger = log.New(w, "", log.Lshortfile) 27 | logger.SetFlags(log.Lshortfile) 28 | 29 | } 30 | 31 | func init() { 32 | SetDebugMode(false) 33 | } 34 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package wagon is a WebAssembly-based interpreter in Go, for Go. 6 | package wagon 7 | -------------------------------------------------------------------------------- /exec/call.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package exec 6 | 7 | import ( 8 | "errors" 9 | 10 | "github.com/go-interpreter/wagon/wasm" 11 | ) 12 | 13 | var ( 14 | // ErrSignatureMismatch is the error value used while trapping the VM when 15 | // a signature mismatch between the table entry and the type entry is found 16 | // in a call_indirect operation. 17 | ErrSignatureMismatch = errors.New("exec: signature mismatch in call_indirect") 18 | // ErrUndefinedElementIndex is the error value used while trapping the VM when 19 | // an invalid index to the module's table space is used as an operand to 20 | // call_indirect 21 | ErrUndefinedElementIndex = errors.New("exec: undefined element index") 22 | ) 23 | 24 | func (vm *VM) call() { 25 | index := vm.fetchUint32() 26 | 27 | vm.funcs[index].call(vm, int64(index)) 28 | } 29 | 30 | func (vm *VM) callIndirect() { 31 | index := vm.fetchUint32() 32 | fnExpect := vm.module.Types.Entries[index] 33 | _ = vm.fetchUint32() // reserved (https://github.com/WebAssembly/design/blob/27ac254c854994103c24834a994be16f74f54186/BinaryEncoding.md#call-operators-described-here) 34 | tableIndex := vm.popUint32() 35 | if int(tableIndex) >= len(vm.module.TableIndexSpace[0]) { 36 | panic(ErrUndefinedElementIndex) 37 | } 38 | tableEntry := vm.module.TableIndexSpace[0][tableIndex] 39 | if !tableEntry.Initialized { 40 | panic(wasm.UninitializedTableEntryError(tableIndex)) 41 | } 42 | elemIndex := tableEntry.Index 43 | fnActual := vm.module.FunctionIndexSpace[elemIndex] 44 | 45 | if len(fnExpect.ParamTypes) != len(fnActual.Sig.ParamTypes) { 46 | panic(ErrSignatureMismatch) 47 | } 48 | if len(fnExpect.ReturnTypes) != len(fnActual.Sig.ReturnTypes) { 49 | panic(ErrSignatureMismatch) 50 | } 51 | 52 | for i := range fnExpect.ParamTypes { 53 | if fnExpect.ParamTypes[i] != fnActual.Sig.ParamTypes[i] { 54 | panic(ErrSignatureMismatch) 55 | } 56 | } 57 | 58 | for i := range fnExpect.ReturnTypes { 59 | if fnExpect.ReturnTypes[i] != fnActual.Sig.ReturnTypes[i] { 60 | panic(ErrSignatureMismatch) 61 | } 62 | } 63 | 64 | vm.funcs[elemIndex].call(vm, int64(elemIndex)) 65 | } 66 | -------------------------------------------------------------------------------- /exec/const.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package exec 6 | 7 | func (vm *VM) i32Const() { 8 | vm.pushUint32(vm.fetchUint32()) 9 | } 10 | 11 | func (vm *VM) i64Const() { 12 | vm.pushUint64(vm.fetchUint64()) 13 | } 14 | 15 | func (vm *VM) f32Const() { 16 | vm.pushFloat32(vm.fetchFloat32()) 17 | } 18 | 19 | func (vm *VM) f64Const() { 20 | vm.pushFloat64(vm.fetchFloat64()) 21 | } 22 | -------------------------------------------------------------------------------- /exec/control.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package exec 6 | 7 | import "errors" 8 | 9 | // ErrUnreachable is the error value used while trapping the VM when 10 | // an unreachable operator is reached during execution. 11 | var ErrUnreachable = errors.New("exec: reached unreachable") 12 | 13 | func (vm *VM) unreachable() { 14 | panic(ErrUnreachable) 15 | } 16 | 17 | func (vm *VM) nop() {} 18 | -------------------------------------------------------------------------------- /exec/conv.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package exec 6 | 7 | import ( 8 | "math" 9 | ) 10 | 11 | func (vm *VM) i32Wrapi64() { 12 | vm.pushUint32(uint32(vm.popUint64())) 13 | } 14 | 15 | func (vm *VM) i32TruncSF32() { 16 | vm.pushInt32(int32(math.Trunc(float64(vm.popFloat32())))) 17 | } 18 | 19 | func (vm *VM) i32TruncUF32() { 20 | vm.pushUint32(uint32(math.Trunc(float64(vm.popFloat32())))) 21 | } 22 | 23 | func (vm *VM) i32TruncSF64() { 24 | vm.pushInt32(int32(math.Trunc(vm.popFloat64()))) 25 | } 26 | 27 | func (vm *VM) i32TruncUF64() { 28 | vm.pushUint32(uint32(math.Trunc(vm.popFloat64()))) 29 | } 30 | 31 | func (vm *VM) i64ExtendSI32() { 32 | vm.pushInt64(int64(vm.popInt32())) 33 | } 34 | 35 | func (vm *VM) i64ExtendUI32() { 36 | vm.pushUint64(uint64(vm.popUint32())) 37 | } 38 | 39 | func (vm *VM) i64TruncSF32() { 40 | vm.pushInt64(int64(math.Trunc(float64(vm.popFloat32())))) 41 | } 42 | 43 | func (vm *VM) i64TruncUF32() { 44 | vm.pushUint64(uint64(math.Trunc(float64(vm.popFloat32())))) 45 | } 46 | 47 | func (vm *VM) i64TruncSF64() { 48 | vm.pushInt64(int64(math.Trunc(vm.popFloat64()))) 49 | } 50 | 51 | func (vm *VM) i64TruncUF64() { 52 | vm.pushUint64(uint64(math.Trunc(vm.popFloat64()))) 53 | } 54 | 55 | func (vm *VM) f32ConvertSI32() { 56 | vm.pushFloat32(float32(vm.popInt32())) 57 | } 58 | 59 | func (vm *VM) f32ConvertUI32() { 60 | vm.pushFloat32(float32(vm.popUint32())) 61 | } 62 | 63 | func (vm *VM) f32ConvertSI64() { 64 | vm.pushFloat32(float32(vm.popInt64())) 65 | } 66 | 67 | func (vm *VM) f32ConvertUI64() { 68 | vm.pushFloat32(float32(vm.popUint64())) 69 | } 70 | 71 | func (vm *VM) f32DemoteF64() { 72 | vm.pushFloat32(float32(vm.popFloat64())) 73 | } 74 | 75 | func (vm *VM) f64ConvertSI32() { 76 | vm.pushFloat64(float64(vm.popInt32())) 77 | } 78 | 79 | func (vm *VM) f64ConvertUI32() { 80 | vm.pushFloat64(float64(vm.popUint32())) 81 | } 82 | 83 | func (vm *VM) f64ConvertSI64() { 84 | vm.pushFloat64(float64(vm.popInt64())) 85 | } 86 | 87 | func (vm *VM) f64ConvertUI64() { 88 | vm.pushFloat64(float64(vm.popUint64())) 89 | } 90 | 91 | func (vm *VM) f64PromoteF32() { 92 | vm.pushFloat64(float64(vm.popFloat32())) 93 | } 94 | -------------------------------------------------------------------------------- /exec/func.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package exec 6 | 7 | import ( 8 | "fmt" 9 | "math" 10 | "reflect" 11 | 12 | "github.com/go-interpreter/wagon/exec/internal/compile" 13 | ) 14 | 15 | type function interface { 16 | call(vm *VM, index int64) 17 | } 18 | 19 | type compiledFunction struct { 20 | code []byte 21 | codeMeta *compile.BytecodeMetadata 22 | branchTables []*compile.BranchTable 23 | maxDepth int // maximum stack depth reached while executing the function body 24 | totalLocalVars int // number of local variables used by the function 25 | args int // number of arguments the function accepts 26 | returns bool // whether the function returns a value 27 | 28 | asm []asmBlock 29 | } 30 | 31 | type asmBlock struct { 32 | // Compiled unit in native machine code. 33 | nativeUnit compile.NativeCodeUnit 34 | // where in the instruction stream to resume after native execution. 35 | resumePC uint 36 | } 37 | 38 | type goFunction struct { 39 | val reflect.Value 40 | typ reflect.Type 41 | } 42 | 43 | func (fn goFunction) call(vm *VM, index int64) { 44 | // numIn = # of call inputs + vm, as the function expects 45 | // an additional *VM argument 46 | numIn := fn.typ.NumIn() 47 | args := make([]reflect.Value, numIn) 48 | proc := NewProcess(vm) 49 | 50 | // Pass proc as an argument. Check that the function indeed 51 | // expects a *Process argument. 52 | if reflect.ValueOf(proc).Kind() != fn.typ.In(0).Kind() { 53 | panic(fmt.Sprintf("exec: the first argument of a host function was %s, expected %s", fn.typ.In(0).Kind(), reflect.ValueOf(vm).Kind())) 54 | } 55 | args[0] = reflect.ValueOf(proc) 56 | 57 | for i := numIn - 1; i >= 1; i-- { 58 | val := reflect.New(fn.typ.In(i)).Elem() 59 | raw := vm.popUint64() 60 | kind := fn.typ.In(i).Kind() 61 | 62 | switch kind { 63 | case reflect.Float64, reflect.Float32: 64 | val.SetFloat(math.Float64frombits(raw)) 65 | case reflect.Uint32, reflect.Uint64: 66 | val.SetUint(raw) 67 | case reflect.Int32, reflect.Int64: 68 | val.SetInt(int64(raw)) 69 | default: 70 | panic(fmt.Sprintf("exec: args %d invalid kind=%v", i, kind)) 71 | } 72 | 73 | args[i] = val 74 | } 75 | 76 | rtrns := fn.val.Call(args) 77 | for i, out := range rtrns { 78 | kind := out.Kind() 79 | switch kind { 80 | case reflect.Float64, reflect.Float32: 81 | vm.pushFloat64(out.Float()) 82 | case reflect.Uint32, reflect.Uint64: 83 | vm.pushUint64(out.Uint()) 84 | case reflect.Int32, reflect.Int64: 85 | vm.pushInt64(out.Int()) 86 | default: 87 | panic(fmt.Sprintf("exec: return value %d invalid kind=%v", i, kind)) 88 | } 89 | } 90 | } 91 | 92 | func (compiled compiledFunction) call(vm *VM, index int64) { 93 | // Make space on the stack for all intermediate values and 94 | // a possible return value. 95 | newStack := make([]uint64, 0, compiled.maxDepth+1) 96 | locals := make([]uint64, compiled.totalLocalVars) 97 | 98 | for i := compiled.args - 1; i >= 0; i-- { 99 | locals[i] = vm.popUint64() 100 | } 101 | 102 | //save execution context 103 | prevCtxt := vm.ctx 104 | 105 | vm.ctx = context{ 106 | stack: newStack, 107 | locals: locals, 108 | code: compiled.code, 109 | asm: compiled.asm, 110 | pc: 0, 111 | curFunc: index, 112 | } 113 | 114 | rtrn := vm.execCode(compiled) 115 | 116 | //restore execution context 117 | vm.ctx = prevCtxt 118 | 119 | if compiled.returns { 120 | vm.pushUint64(rtrn) 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /exec/internal/compile/allocator.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build !appengine 6 | 7 | package compile 8 | 9 | import ( 10 | "unsafe" 11 | 12 | mmap "github.com/edsrzf/mmap-go" 13 | ) 14 | 15 | const ( 16 | minAllocSize = 1024 * 8 // 8kb executable pages. 17 | // alignment - instruction caching works better on aligned boundaries. 18 | allocationAlignment = 128 - 1 19 | ) 20 | 21 | type mmapBlock struct { 22 | mem mmap.MMap 23 | consumed uint32 24 | remaining uint32 25 | } 26 | 27 | // MMapAllocator copies instructions into executable memory. 28 | type MMapAllocator struct { 29 | last *mmapBlock 30 | blocks []*mmapBlock 31 | } 32 | 33 | // Close frees all pages allocated by the allocator. 34 | func (a *MMapAllocator) Close() error { 35 | for _, block := range a.blocks { 36 | if err := block.mem.Unmap(); err != nil { 37 | return err 38 | } 39 | } 40 | return nil 41 | } 42 | 43 | // AllocateExec allocates a block of executable memory with the given code contained. 44 | func (a *MMapAllocator) AllocateExec(asm []byte) (NativeCodeUnit, error) { 45 | consumed := uint32(len(asm)+allocationAlignment) & ^uint32(allocationAlignment) 46 | if a.last != nil && a.last.remaining > consumed { 47 | copy(a.last.mem[a.last.consumed:], asm) 48 | out := asmBlock{ 49 | mem: unsafe.Pointer(&a.last.mem[a.last.consumed]), 50 | } 51 | a.last.remaining -= consumed 52 | a.last.consumed += consumed 53 | return &out, nil 54 | } 55 | 56 | alloc := minAllocSize 57 | if int(consumed) > alloc { // not big enough? make minAlloc + aligned len 58 | alloc += int(consumed) 59 | } 60 | m, err := mmap.MapRegion(nil, alloc, mmap.EXEC|mmap.RDWR, mmap.ANON, int64(0)) 61 | if err != nil { 62 | return nil, err 63 | } 64 | a.last = &mmapBlock{ 65 | mem: m, 66 | consumed: consumed, 67 | remaining: uint32(alloc) - consumed, 68 | } 69 | a.blocks = append(a.blocks, a.last) 70 | copy(m, asm) 71 | 72 | out := asmBlock{ 73 | mem: unsafe.Pointer(&m[0]), 74 | } 75 | return &out, nil 76 | } 77 | -------------------------------------------------------------------------------- /exec/internal/compile/allocator_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build !appengine 6 | 7 | package compile 8 | 9 | import ( 10 | "testing" 11 | "unsafe" 12 | ) 13 | 14 | func TestMMapAllocator(t *testing.T) { 15 | a := &MMapAllocator{} 16 | defer a.Close() 17 | 18 | if _, err := a.AllocateExec([]byte{1, 2, 3, 4}); err != nil { 19 | t.Fatal(err) 20 | } 21 | if d := **(**[4]byte)(unsafe.Pointer(&a.last.mem)); d != [4]byte{1, 2, 3, 4} { 22 | t.Errorf("shortAlloc = %d, want [4]byte{1,2,3,4}", d) 23 | } 24 | if want := uint32(128); a.last.consumed != want { 25 | t.Errorf("a.last.consumed = %d, want %d", a.last.consumed, want) 26 | } 27 | if want := uint32(minAllocSize - allocationAlignment - 1); a.last.remaining != want { 28 | t.Errorf("a.last.remaining = %d, want %d", a.last.remaining, want) 29 | } 30 | 31 | if _, err := a.AllocateExec([]byte{4, 3, 2, 1}); err != nil { 32 | t.Fatal(err) 33 | } 34 | if want := uint32(256); a.last.consumed != want { 35 | t.Errorf("a.last.consumed = %d, want %d", a.last.consumed, want) 36 | } 37 | if want := uint32(minAllocSize - allocationAlignment*2 - 2); a.last.remaining != want { 38 | t.Errorf("a.last.remaining = %d, want %d", a.last.remaining, want) 39 | } 40 | 41 | // Test allocation of massive slice - should be 32k more & new block. 42 | b := make([]byte, 36*1024) 43 | b[1] = 5 44 | if _, err := a.AllocateExec(b); err != nil { 45 | t.Fatal(err) 46 | } 47 | if d := **(**[2]byte)(unsafe.Pointer(&a.last.mem)); d != [2]byte{0, 5} { 48 | t.Errorf("bigAlloc = %d, want [2]byte{0, 5}", d) 49 | } 50 | if want := uint32(36 * 1024); a.last.consumed != want { 51 | t.Errorf("a.last.consumed = %d, want %d", a.last.consumed, want) 52 | } 53 | if want := uint32(minAllocSize); a.last.remaining != want { 54 | t.Errorf("a.last.remaining = %d, want %d", a.last.remaining, want) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /exec/internal/compile/backend.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package compile 6 | 7 | type dirtyState uint8 8 | 9 | const ( 10 | stateScratch dirtyState = iota // We don't care about the value. 11 | stateStackLen // Stores the stack len (dirty). 12 | stateStackFirstElem // Caches a pointer to the stack array. 13 | stateLocalFirstElem // Caches a pointer to the locals array. 14 | stateGlobalSliceHeader // Caches a pointer to the globals slice header. 15 | ) 16 | -------------------------------------------------------------------------------- /exec/internal/compile/native.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package compile 6 | 7 | // NativeCodeUnit represents compiled native code. 8 | type NativeCodeUnit interface { 9 | Invoke(stack, locals, globals *[]uint64, mem *[]byte) JITExitSignal 10 | } 11 | 12 | // CompletionStatus describes the final status of a native execution. 13 | type CompletionStatus uint64 14 | 15 | // Valid completion statuses. 16 | const ( 17 | CompletionOK CompletionStatus = iota 18 | CompletionBadBounds 19 | CompletionUnreachable 20 | CompletionFatalInternalError 21 | CompletionDivideZero 22 | ) 23 | -------------------------------------------------------------------------------- /exec/internal/compile/native_386.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package compile 6 | 7 | import ( 8 | "bytes" 9 | "os" 10 | "os/exec" 11 | ) 12 | 13 | func debugPrintAsm(asm []byte) { 14 | cmd := exec.Command("ndisasm", "-b32", "-") 15 | cmd.Stdin = bytes.NewReader(asm) 16 | cmd.Stdout = os.Stdout 17 | cmd.Run() 18 | } 19 | 20 | func makeExitIndex(idx int) CompletionStatus { 21 | return CompletionStatus((int64(idx) << 8) & exitIndexMask) 22 | } 23 | 24 | // FIXME: adapt these constants and JITExitSignal for 32b architectures. 25 | 26 | const ( 27 | statusMask = 15 28 | exitIndexMask = 0x00000000ffffff00 29 | unknownIndex = 0xffffff 30 | ) 31 | 32 | // JITExitSignal is the value returned from the execution of a native section. 33 | // The bits of this packed 64bit value is encoded as follows: 34 | // [00:04] Completion Status 35 | // [04:08] Reserved 36 | // [08:32] Index of the WASM instruction where the exit occurred. 37 | // [32:64] Status-specific 32bit value. 38 | type JITExitSignal uint64 39 | 40 | // CompletionStatus decodes and returns the completion status of the exit. 41 | func (s JITExitSignal) CompletionStatus() CompletionStatus { 42 | return CompletionStatus(s & statusMask) 43 | } 44 | 45 | // Index returns the index to the instruction where the exit happened. 46 | // 0xffffff is returned if the exit was due to normal completion. 47 | func (s JITExitSignal) Index() int64 { 48 | return (int64(s) & exitIndexMask) >> 8 49 | } 50 | -------------------------------------------------------------------------------- /exec/internal/compile/native_amd64.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package compile 6 | 7 | import ( 8 | "bytes" 9 | "os" 10 | "os/exec" 11 | ) 12 | 13 | func debugPrintAsm(asm []byte) { 14 | cmd := exec.Command("ndisasm", "-b64", "-") 15 | cmd.Stdin = bytes.NewReader(asm) 16 | cmd.Stdout = os.Stdout 17 | cmd.Run() 18 | } 19 | 20 | func makeExitIndex(idx int) CompletionStatus { 21 | return CompletionStatus((int64(idx) << 8) & exitIndexMask) 22 | } 23 | 24 | const ( 25 | statusMask = 15 26 | exitIndexMask = 0x00000000ffffff00 27 | unknownIndex = 0xffffff 28 | ) 29 | 30 | // JITExitSignal is the value returned from the execution of a native section. 31 | // The bits of this packed 64bit value is encoded as follows: 32 | // [00:04] Completion Status 33 | // [04:08] Reserved 34 | // [08:32] Index of the WASM instruction where the exit occurred. 35 | // [32:64] Status-specific 32bit value. 36 | type JITExitSignal uint64 37 | 38 | // CompletionStatus decodes and returns the completion status of the exit. 39 | func (s JITExitSignal) CompletionStatus() CompletionStatus { 40 | return CompletionStatus(s & statusMask) 41 | } 42 | 43 | // Index returns the index to the instruction where the exit happened. 44 | // 0xffffff is returned if the exit was due to normal completion. 45 | func (s JITExitSignal) Index() int { 46 | return (int(s) & exitIndexMask) >> 8 47 | } 48 | -------------------------------------------------------------------------------- /exec/internal/compile/native_exec.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build !appengine 6 | 7 | package compile 8 | 9 | import "unsafe" 10 | 11 | type asmBlock struct { 12 | mem unsafe.Pointer 13 | } 14 | 15 | func (b *asmBlock) Invoke(stack, locals, globals *[]uint64, mem *[]byte) JITExitSignal { 16 | return JITExitSignal(jitcall(unsafe.Pointer(&b.mem), stack, locals, globals, mem)) 17 | } 18 | -------------------------------------------------------------------------------- /exec/internal/compile/native_exec_386.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build !appengine 6 | 7 | package compile 8 | 9 | import "unsafe" 10 | 11 | func jitcall(asm unsafe.Pointer, stack, locals, globals *[]uint64, mem *[]byte) uint64 { 12 | panic("not implemented") 13 | } 14 | -------------------------------------------------------------------------------- /exec/internal/compile/native_exec_amd64.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build !appengine 6 | 7 | package compile 8 | 9 | import "unsafe" 10 | 11 | func jitcall(asm unsafe.Pointer, stack, locals, globals *[]uint64, mem *[]byte) uint64 12 | -------------------------------------------------------------------------------- /exec/internal/compile/native_exec_amd64.s: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build amd64 !appengine 6 | 7 | #include "funcdata.h" 8 | #include "textflag.h" 9 | 10 | // jitcall(*asm, *stackSlice, *localSlice, *globalSlice, *memSlice) uint64 11 | TEXT ·jitcall(SB),NOSPLIT|NOFRAME,$0-48 12 | GO_ARGS 13 | MOVQ asm+0(FP), AX // Load the address of the assembly section. 14 | MOVQ stack+8(FP), R10 // Load the address of the stack. 15 | MOVQ locals+16(FP), R11 // Load the address of the locals. 16 | MOVQ mem+32(FP), SI // Load the address of main memory. 17 | MOVQ 0(AX), AX // Deference pointer to native code. 18 | JMP AX // Jump to native code. 19 | -------------------------------------------------------------------------------- /exec/internal/compile/optimizer_amd64.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package compile 6 | 7 | import ( 8 | "math" 9 | 10 | asm "github.com/twitchyliquid64/golang-asm" 11 | "github.com/twitchyliquid64/golang-asm/obj" 12 | "github.com/twitchyliquid64/golang-asm/obj/x86" 13 | ) 14 | 15 | func peepholeOptimizeAMD64(builder *asm.Builder) error { 16 | inst := builder.Root() 17 | for ; inst.Link != nil; inst = inst.Link { 18 | // Replace mov , 0 with xor. 19 | if inst.As == x86.AMOVQ && inst.To.Type == obj.TYPE_REG && 20 | inst.From.Type == obj.TYPE_CONST && inst.From.Offset == 0 { 21 | inst.As = x86.AXORL 22 | inst.From = inst.To 23 | } 24 | 25 | // If we are loading a constant to a register and its less than 32bit, 26 | // use the 32bit version (its shorter). 27 | if n := inst.From.Offset; inst.As == x86.AMOVQ && inst.From.Type == obj.TYPE_CONST && (n > 0 && n < math.MaxInt32) { 28 | inst.As = x86.AMOVL 29 | } 30 | 31 | // If its an add by an immediate 1, convert to increment. 32 | if inst.As == x86.AADDQ && inst.From.Type == obj.TYPE_CONST && inst.From.Offset == 1 { 33 | inst.As = x86.AINCQ 34 | inst.From = obj.Addr{} 35 | } 36 | } 37 | 38 | return nil 39 | } 40 | -------------------------------------------------------------------------------- /exec/internal/compile/scanner.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package compile 6 | 7 | type scanner struct { 8 | supportedOpcodes map[byte]bool 9 | } 10 | 11 | // InstructionMetadata describes a bytecode instruction. 12 | type InstructionMetadata struct { 13 | Op byte 14 | // Start represents the byte offset of this instruction 15 | // in the function's instruction stream. 16 | Start int 17 | // Size is the number of bytes in the instruction stream 18 | // needed to represent this instruction. 19 | Size int 20 | } 21 | 22 | // CompilationCandidate describes a range of bytecode that can 23 | // be translated to native code. 24 | type CompilationCandidate struct { 25 | Start uint // Bytecode index of the first opcode. 26 | End uint // Bytecode index of the last byte in the instruction. 27 | StartInstruction int // InstructionMeta index of the first instruction. 28 | EndInstruction int // InstructionMeta index of the last instruction. 29 | Metrics Metrics // Metrics about the instructions between first & last index. 30 | } 31 | 32 | func (s *CompilationCandidate) reset() { 33 | s.Start = 0 34 | s.End = 0 35 | s.StartInstruction = 0 36 | s.EndInstruction = 1 37 | s.Metrics = Metrics{} 38 | } 39 | 40 | // Bounds returns the beginning & end index in the bytecode which 41 | // this candidate would replace. The end index is not inclusive. 42 | func (s *CompilationCandidate) Bounds() (uint, uint) { 43 | return s.Start, s.End 44 | } 45 | 46 | // Metrics describes the heuristics of an instruction sequence. 47 | type Metrics struct { 48 | MemoryReads, MemoryWrites uint 49 | StackReads, StackWrites uint 50 | 51 | AllOps int 52 | IntegerOps int 53 | FloatOps int 54 | } 55 | -------------------------------------------------------------------------------- /exec/internal/compile/scanner_amd64.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package compile 6 | 7 | import ( 8 | ops "github.com/go-interpreter/wagon/wasm/operators" 9 | ) 10 | 11 | // ScanFunc scans the given function information, emitting selections of 12 | // bytecode which could be compiled into function code. 13 | func (s *scanner) ScanFunc(bytecode []byte, meta *BytecodeMetadata) ([]CompilationCandidate, error) { 14 | var finishedCandidates []CompilationCandidate 15 | inProgress := CompilationCandidate{} 16 | 17 | for i, inst := range meta.Instructions { 18 | // Except for the first instruction, we cant emit a native section 19 | // where other parts of code try and call into us halfway. Maybe we 20 | // can support that in the future. 21 | _, hasInboundTarget := meta.InboundTargets[int64(inst.Start)] 22 | isInsideBranchTarget := hasInboundTarget && inst.Start > 0 && inProgress.Metrics.AllOps > 0 23 | 24 | if !s.supportedOpcodes[inst.Op] || isInsideBranchTarget { 25 | // See if the candidate can be emitted. 26 | if inProgress.Metrics.AllOps > 2 { 27 | finishedCandidates = append(finishedCandidates, inProgress) 28 | } 29 | inProgress.reset() 30 | continue 31 | } 32 | 33 | // Still a supported run. 34 | 35 | if inProgress.Metrics.AllOps == 0 { 36 | // First instruction of the candidate - setup structure. 37 | inProgress.Start = uint(inst.Start) 38 | inProgress.StartInstruction = i 39 | } 40 | inProgress.EndInstruction = i + 1 41 | inProgress.End = uint(inst.Start) + uint(inst.Size) 42 | 43 | // TODO: Add to this table as backends support more opcodes. 44 | switch inst.Op { 45 | case ops.I64Load, ops.I32Load, ops.F64Load, ops.F32Load: 46 | fakeBE := &AMD64Backend{} 47 | memSize, _ := fakeBE.paramsForMemoryOp(inst.Op) 48 | inProgress.Metrics.MemoryReads += memSize 49 | inProgress.Metrics.StackWrites++ 50 | case ops.I64Store, ops.I32Store, ops.F64Store, ops.F32Store: 51 | fakeBE := &AMD64Backend{} 52 | memSize, _ := fakeBE.paramsForMemoryOp(inst.Op) 53 | inProgress.Metrics.MemoryWrites += memSize 54 | inProgress.Metrics.StackReads += 2 55 | case ops.I64Const, ops.I32Const, ops.GetLocal, ops.GetGlobal: 56 | inProgress.Metrics.IntegerOps++ 57 | inProgress.Metrics.StackWrites++ 58 | case ops.F64Const, ops.F32Const: 59 | inProgress.Metrics.FloatOps++ 60 | inProgress.Metrics.StackWrites++ 61 | case ops.SetLocal, ops.SetGlobal: 62 | inProgress.Metrics.IntegerOps++ 63 | inProgress.Metrics.StackReads++ 64 | case ops.I64Eqz: 65 | inProgress.Metrics.IntegerOps++ 66 | inProgress.Metrics.StackReads++ 67 | inProgress.Metrics.StackWrites++ 68 | 69 | case ops.I64Eq, ops.I64Ne, ops.I64LtU, ops.I64GtU, ops.I64LeU, ops.I64GeU, 70 | ops.I64Shl, ops.I64ShrU, ops.I64ShrS, 71 | ops.I64DivU, ops.I32DivU, ops.I64RemU, ops.I32RemU, ops.I64DivS, ops.I32DivS, ops.I64RemS, ops.I32RemS, 72 | ops.I64Add, ops.I32Add, ops.I64Sub, ops.I32Sub, ops.I64Mul, ops.I32Mul, 73 | ops.I64And, ops.I32And, ops.I64Or, ops.I32Or, ops.I64Xor, ops.I32Xor: 74 | inProgress.Metrics.IntegerOps++ 75 | inProgress.Metrics.StackReads += 2 76 | inProgress.Metrics.StackWrites++ 77 | 78 | case ops.F64Add, ops.F32Add, ops.F64Sub, ops.F32Sub, ops.F64Div, ops.F32Div, ops.F64Mul, ops.F32Mul, 79 | ops.F64Min, ops.F32Min, ops.F64Max, ops.F32Max, 80 | ops.F64Eq, ops.F64Ne, ops.F64Lt, ops.F64Gt, ops.F64Le, ops.F64Ge, 81 | ops.F32Eq, ops.F32Ne, ops.F32Lt, ops.F32Gt, ops.F32Le, ops.F32Ge: 82 | inProgress.Metrics.FloatOps++ 83 | inProgress.Metrics.StackReads += 2 84 | inProgress.Metrics.StackWrites++ 85 | 86 | case ops.F64ConvertUI64, ops.F64ConvertSI64, ops.F32ConvertUI64, ops.F32ConvertSI64, 87 | ops.F64ConvertUI32, ops.F64ConvertSI32, ops.F32ConvertUI32, ops.F32ConvertSI32: 88 | inProgress.Metrics.FloatOps++ 89 | inProgress.Metrics.StackReads++ 90 | inProgress.Metrics.StackWrites++ 91 | 92 | case ops.Drop: 93 | inProgress.Metrics.StackReads++ 94 | case ops.Select: 95 | inProgress.Metrics.StackReads += 3 96 | inProgress.Metrics.StackWrites++ 97 | 98 | case ops.F64ReinterpretI64, ops.F32ReinterpretI32, ops.I64ReinterpretF64, ops.I32ReinterpretF32: 99 | inProgress.Metrics.FloatOps++ 100 | inProgress.Metrics.IntegerOps++ 101 | } 102 | inProgress.Metrics.AllOps++ 103 | } 104 | 105 | // End of instructions - emit the inProgress candidate if 106 | // its at least 3 instructions. 107 | if inProgress.Metrics.AllOps > 2 { 108 | finishedCandidates = append(finishedCandidates, inProgress) 109 | } 110 | return finishedCandidates, nil 111 | } 112 | -------------------------------------------------------------------------------- /exec/native_compile_nogae_amd64.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build !appengine 6 | 7 | package exec 8 | 9 | import ( 10 | "encoding/binary" 11 | 12 | "github.com/go-interpreter/wagon/exec/internal/compile" 13 | ) 14 | 15 | func init() { 16 | supportedNativeArchs = append(supportedNativeArchs, nativeArch{ 17 | Arch: "amd64", 18 | OS: "linux", 19 | make: makeAMD64NativeBackend, 20 | }, nativeArch{ 21 | Arch: "amd64", 22 | OS: "windows", 23 | make: makeAMD64NativeBackend, 24 | }, nativeArch{ 25 | Arch: "amd64", 26 | OS: "darwin", 27 | make: makeAMD64NativeBackend, 28 | }) 29 | } 30 | 31 | func makeAMD64NativeBackend(endianness binary.ByteOrder) *nativeCompiler { 32 | be := &compile.AMD64Backend{EmitBoundsChecks: debugStackDepth} 33 | return &nativeCompiler{ 34 | Builder: be, 35 | Scanner: be.Scanner(), 36 | allocator: &compile.MMapAllocator{}, 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /exec/num_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package exec 6 | 7 | import ( 8 | "fmt" 9 | "testing" 10 | 11 | "github.com/go-interpreter/wagon/wasm/operators" 12 | ) 13 | 14 | func TestF32BinOps(t *testing.T) { 15 | for _, tc := range []struct { 16 | opcode byte 17 | z1 float32 18 | z2 float32 19 | want float32 20 | }{ 21 | {operators.F32Sub, 3.0, 2.0, 1.0}, 22 | {operators.F32Copysign, 3.0, 2.0, 3.0}, 23 | {operators.F32Copysign, 3.0, -2.0, -3.0}, 24 | } { 25 | name, err := operators.New(tc.opcode) 26 | if err != nil { 27 | t.Fatalf("could not lookup operator 0x%x: %+v", tc.opcode, name) 28 | } 29 | t.Run(fmt.Sprintf("%v(%v,%v)", name, tc.z1, tc.z2), func(t *testing.T) { 30 | vm := new(VM) 31 | vm.newFuncTable() 32 | vm.pushFloat32(tc.z1) 33 | vm.pushFloat32(tc.z2) 34 | vm.funcTable[tc.opcode]() 35 | if got, want := vm.popFloat32(), tc.want; got != want { 36 | t.Fatalf("got=%v, want=%v", got, want) 37 | } 38 | }) 39 | } 40 | } 41 | 42 | func TestF64BinOps(t *testing.T) { 43 | for _, tc := range []struct { 44 | opcode byte 45 | z1 float64 46 | z2 float64 47 | want float64 48 | }{ 49 | {operators.F64Sub, 3.0, 2.0, 1.0}, 50 | {operators.F64Copysign, 3.0, 2.0, 3.0}, 51 | {operators.F64Copysign, 3.0, -2.0, -3.0}, 52 | } { 53 | name, err := operators.New(tc.opcode) 54 | if err != nil { 55 | t.Fatalf("could not lookup operator 0x%x: %+v", tc.opcode, name) 56 | } 57 | t.Run(fmt.Sprintf("%v(%v,%v)", name, tc.z1, tc.z2), func(t *testing.T) { 58 | vm := new(VM) 59 | vm.newFuncTable() 60 | vm.pushFloat64(tc.z1) 61 | vm.pushFloat64(tc.z2) 62 | vm.funcTable[tc.opcode]() 63 | if got, want := vm.popFloat64(), tc.want; got != want { 64 | t.Fatalf("got=%v, want=%v", got, want) 65 | } 66 | }) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /exec/parametric.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package exec 6 | 7 | func (vm *VM) drop() { 8 | vm.ctx.stack = vm.ctx.stack[:len(vm.ctx.stack)-1] 9 | } 10 | 11 | func (vm *VM) selectOp() { 12 | c := vm.popUint32() 13 | val2 := vm.popUint64() 14 | val1 := vm.popUint64() 15 | 16 | if c != 0 { 17 | vm.pushUint64(val1) 18 | } else { 19 | vm.pushUint64(val2) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /exec/reinterp.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package exec 6 | 7 | import ( 8 | "math" 9 | ) 10 | 11 | // these operations are essentially no-ops. 12 | // TODO(vibhavp): Add optimisations to package compiles that 13 | // removes them from the original bytecode. 14 | 15 | func (vm *VM) i32ReinterpretF32() { 16 | vm.pushUint32(math.Float32bits(vm.popFloat32())) 17 | } 18 | 19 | func (vm *VM) i64ReinterpretF64() { 20 | vm.pushUint64(math.Float64bits(vm.popFloat64())) 21 | } 22 | 23 | func (vm *VM) f32ReinterpretI32() { 24 | vm.pushFloat32(math.Float32frombits(vm.popUint32())) 25 | } 26 | 27 | func (vm *VM) f64ReinterpretI64() { 28 | vm.pushFloat64(math.Float64frombits(vm.popUint64())) 29 | } 30 | -------------------------------------------------------------------------------- /exec/testdata/add-ex-main.wasm: -------------------------------------------------------------------------------- 1 | asm````addiaddgoprint 2 |  AA(  3 |  -------------------------------------------------------------------------------- /exec/testdata/add-ex-main.wast: -------------------------------------------------------------------------------- 1 | ;; a module that imports two other modules, and defines 3 functions 2 | ;; using exported functions from these two imported modules. 3 | ;; 4 | ;; see https://github.com/WebAssembly/spec/tree/master/interpreter/#s-expression-syntax 5 | ;; for a reference about the syntax. 6 | (module 7 | (import "add" "iadd" (func $iadd (param i32 i32) (result i32))) 8 | (import "go" "print" (func $print (param i32))) 9 | 10 | (func $fct1 (result i32) 11 | (return (call $iadd (i32.const 2) (i32.const 40))) 12 | ) 13 | (func $fct2 (param i32 i32) (result i32) 14 | (return (call $iadd (get_local 0) (get_local 1))) 15 | ) 16 | (func $fct3 (param i32 i32) 17 | (call $print (call $iadd (get_local 0) (get_local 1))) 18 | ) 19 | ) 20 | -------------------------------------------------------------------------------- /exec/testdata/add-ex.wasm: -------------------------------------------------------------------------------- 1 | asm`iadd 2 |  j -------------------------------------------------------------------------------- /exec/testdata/add-ex.wast: -------------------------------------------------------------------------------- 1 | ;; a simple module with one exported function "iadd". 2 | ;; 3 | ;; see https://github.com/WebAssembly/spec/tree/master/interpreter/#s-expression-syntax 4 | ;; for a reference about the syntax. 5 | (module 6 | (type (;0;) (func (param i32 i32) (result i32))) 7 | (func (;0;) (type 0) (param i32 i32) (result i32) 8 | get_local 0 9 | get_local 1 10 | i32.add) 11 | (export "iadd" (func 0))) 12 | -------------------------------------------------------------------------------- /exec/testdata/basic.wasm: -------------------------------------------------------------------------------- 1 | asm`main 2 | A* -------------------------------------------------------------------------------- /exec/testdata/binary.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-interpreter/wagon/d9964a5b71752113f6c43d26d03028d0224f9ff8/exec/testdata/binary.wasm -------------------------------------------------------------------------------- /exec/testdata/br.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-interpreter/wagon/d9964a5b71752113f6c43d26d03028d0224f9ff8/exec/testdata/br.wasm -------------------------------------------------------------------------------- /exec/testdata/brif-loop.wasm: -------------------------------------------------------------------------------- 1 | asm 2 | ``test1test2 3 | (@ Aj!  H  A A 4 |  -------------------------------------------------------------------------------- /exec/testdata/brif.wasm: -------------------------------------------------------------------------------- 1 | asm 2 | ``test1test2 3 | @ A A A A -------------------------------------------------------------------------------- /exec/testdata/brtable.wasm: -------------------------------------------------------------------------------- 1 | asm 2 | ``!test0test1test2test3 3 | =@@@@  A A A A A A A -------------------------------------------------------------------------------- /exec/testdata/bug-49.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-interpreter/wagon/d9964a5b71752113f6c43d26d03028d0224f9ff8/exec/testdata/bug-49.wasm -------------------------------------------------------------------------------- /exec/testdata/call-zero-args.wasm: -------------------------------------------------------------------------------- 1 | asm ``h 2 | A*  j A -------------------------------------------------------------------------------- /exec/testdata/call.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-interpreter/wagon/d9964a5b71752113f6c43d26d03028d0224f9ff8/exec/testdata/call.wasm -------------------------------------------------------------------------------- /exec/testdata/callindirect.wasm: -------------------------------------------------------------------------------- 1 | asm```` pM test_zerotest_onetest_addtest_sub trap_oob 2 | trap_sig_mismatch 3 | A  4 | i A A    j  k   A A 5 | A 6 | AA 7 | A 8 | AA 9 | A 10 | AA 11 | A 12 | AA -------------------------------------------------------------------------------- /exec/testdata/cast.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-interpreter/wagon/d9964a5b71752113f6c43d26d03028d0224f9ff8/exec/testdata/cast.wasm -------------------------------------------------------------------------------- /exec/testdata/compare.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-interpreter/wagon/d9964a5b71752113f6c43d26d03028d0224f9ff8/exec/testdata/compare.wasm -------------------------------------------------------------------------------- /exec/testdata/convert.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-interpreter/wagon/d9964a5b71752113f6c43d26d03028d0224f9ff8/exec/testdata/convert.wasm -------------------------------------------------------------------------------- /exec/testdata/expr-block.wasm: -------------------------------------------------------------------------------- 1 | asm`test 2 |  3 | A 4 | A -------------------------------------------------------------------------------- /exec/testdata/expr-br.wasm: -------------------------------------------------------------------------------- 1 | asm 2 | ``test1test2 3 | # AF@A  A A A -------------------------------------------------------------------------------- /exec/testdata/expr-brif.wasm: -------------------------------------------------------------------------------- 1 | asm 2 | ``test1test2 3 | A* A k A A -------------------------------------------------------------------------------- /exec/testdata/expr-if.wasm: -------------------------------------------------------------------------------- 1 | asm 2 | ``test1test2 3 |  AFAA A A -------------------------------------------------------------------------------- /exec/testdata/if.wasm: -------------------------------------------------------------------------------- 1 | asm` if1if2 2 | L#A!A@ Aj! A@ Aj!  &A@A!A! A@A!A! j -------------------------------------------------------------------------------- /exec/testdata/ifelse-stack-bug.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-interpreter/wagon/d9964a5b71752113f6c43d26d03028d0224f9ff8/exec/testdata/ifelse-stack-bug.wasm -------------------------------------------------------------------------------- /exec/testdata/load.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-interpreter/wagon/d9964a5b71752113f6c43d26d03028d0224f9ff8/exec/testdata/load.wasm -------------------------------------------------------------------------------- /exec/testdata/loop.wasm: -------------------------------------------------------------------------------- 1 | asm`loop 2 | #!@  j! Aj! AH@   -------------------------------------------------------------------------------- /exec/testdata/nested-if.wasm: -------------------------------------------------------------------------------- 1 | asm`f 2 | @A@AA@  A -------------------------------------------------------------------------------- /exec/testdata/return-void.wasm: -------------------------------------------------------------------------------- 1 | asm ```#test1check1test2check2 2 | 2 AF@ AA6 A A( A A( -------------------------------------------------------------------------------- /exec/testdata/return.wasm: -------------------------------------------------------------------------------- 1 | asm 2 | ``test1test2test3 3 | 2 AF@A AF@A A A A A -------------------------------------------------------------------------------- /exec/testdata/rust-basic.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // To compile: 6 | // rustc --target wasm32-unknown-unknown -O --crate-type=cdylib rust-basic.rs -o rust-basic.wasm 7 | 8 | #[no_mangle] 9 | pub extern "C" fn x2_plus_y2_minus_13(x: u64, y: u64) -> u64 { 10 | let x2 = x * x; 11 | let y2 = y * y; 12 | ((x2 + y2 - 13) & 0xff) + 1 13 | } 14 | 15 | 16 | #[no_mangle] 17 | pub extern "C" fn loopedArithmeticI64Benchmark(n: u64, input: u64) -> u64 { 18 | let mut out = input + 2; 19 | for x in 0..n { 20 | let y = (input * x / 3) * 2; 21 | out += (((input + 13) * input) & 0x66ff) - x; 22 | out += y & 0x9; 23 | out += (x * 4) / 3 + y << 3; 24 | out += (x * 5) / 2 + y << 1; 25 | out += (x * 6) / 6 + y << 11; 26 | out += (x * 4) / 3 + y << 3; 27 | out += (x * 5) / 2 + y << 1; 28 | out += (x * 6) / 6 + y << 11; 29 | out += (x * 4) / 3 + y << 3; 30 | out += (x * 5) / 2 + y << 1; 31 | out += (x * 6) / 6 + y << 11; 32 | if x > 5 { 33 | out -= y * 2 - 1; 34 | } 35 | } 36 | out 37 | } 38 | 39 | #[no_mangle] 40 | pub extern "C" fn loopedArithmeticF64Benchmark(n: u64, input: f64) -> f64 { 41 | let mut out = input + 2f64; 42 | for x in 0..n { 43 | let y = (input * x as f64 / 3.121) * 2.003; 44 | out += ((input + 13.0) * input) - x as f64; 45 | out -= ((input + 11.0) * input) - x as f64; 46 | out += ((input + 14.11) * input) - x as f64; 47 | out -= ((input + 10.11) * input) - x as f64; 48 | out += ((input + 15.22) * input) - x as f64; 49 | out -= ((input + 9.222) * input) - x as f64; 50 | out += y * input; 51 | out *= 1.999; 52 | if x > 5 && y > 0.0 { 53 | out -= y * 2.0; 54 | } 55 | } 56 | out 57 | } 58 | 59 | #[no_mangle] 60 | pub extern "C" fn loopedArithmeticF32Benchmark(n: u64, input: f32) -> f32 { 61 | let mut out = input + 2f32; 62 | for x in 0..n { 63 | let y = (input * x as f32 / 3.121) * 2.003; 64 | out += ((input + 13.0) * input) - x as f32; 65 | out -= ((input + 11.0) * input) - x as f32; 66 | out += ((input + 14.11) * input) - x as f32; 67 | out -= ((input + 10.11) * input) - x as f32; 68 | out += ((input + 15.22) * input) - x as f32; 69 | out -= ((input + 9.222) * input) - x as f32; 70 | out += y * input; 71 | out *= 1.999; 72 | if x > 5 && y > 0.0 { 73 | out -= y * 2.0; 74 | } 75 | } 76 | out 77 | } 78 | -------------------------------------------------------------------------------- /exec/testdata/rust-basic.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-interpreter/wagon/d9964a5b71752113f6c43d26d03028d0224f9ff8/exec/testdata/rust-basic.wasm -------------------------------------------------------------------------------- /exec/testdata/select.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-interpreter/wagon/d9964a5b71752113f6c43d26d03028d0224f9ff8/exec/testdata/select.wasm -------------------------------------------------------------------------------- /exec/testdata/spec/address.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-interpreter/wagon/d9964a5b71752113f6c43d26d03028d0224f9ff8/exec/testdata/spec/address.wasm -------------------------------------------------------------------------------- /exec/testdata/spec/address.wast: -------------------------------------------------------------------------------- 1 | (module 2 | (memory 1) 3 | (data (i32.const 0) "abcdefghijklmnopqrstuvwxyz") 4 | 5 | (func (export "good1") (param $i i32) (result i32) 6 | (i32.load8_u offset=0 (get_local $i)) ;; 97 'a' 7 | ) 8 | (func (export "good2") (param $i i32) (result i32) 9 | (i32.load8_u offset=1 (get_local $i)) ;; 98 'b' 10 | ) 11 | (func (export "good3") (param $i i32) (result i32) 12 | (i32.load8_u offset=2 (get_local $i)) ;; 99 'c' 13 | ) 14 | (func (export "good4") (param $i i32) (result i32) 15 | (i32.load8_u offset=25 (get_local $i)) ;; 122 'z' 16 | ) 17 | 18 | (func (export "good5") (param $i i32) (result i32) 19 | (i32.load16_u offset=0 (get_local $i)) ;; 25185 'ab' 20 | ) 21 | (func (export "good6") (param $i i32) (result i32) 22 | (i32.load16_u align=1 (get_local $i)) ;; 25185 'ab' 23 | ) 24 | (func (export "good7") (param $i i32) (result i32) 25 | (i32.load16_u offset=1 align=1 (get_local $i)) ;; 25442 'bc' 26 | ) 27 | (func (export "good8") (param $i i32) (result i32) 28 | (i32.load16_u offset=2 (get_local $i)) ;; 25699 'cd' 29 | ) 30 | (func (export "good9") (param $i i32) (result i32) 31 | (i32.load16_u offset=25 align=1 (get_local $i)) ;; 122 'z\0' 32 | ) 33 | 34 | (func (export "good10") (param $i i32) (result i32) 35 | (i32.load offset=0 (get_local $i)) ;; 1684234849 'abcd' 36 | ) 37 | (func (export "good11") (param $i i32) (result i32) 38 | (i32.load offset=1 align=1 (get_local $i)) ;; 1701077858 'bcde' 39 | ) 40 | (func (export "good12") (param $i i32) (result i32) 41 | (i32.load offset=2 align=2 (get_local $i)) ;; 1717920867 'cdef' 42 | ) 43 | (func (export "good13") (param $i i32) (result i32) 44 | (i32.load offset=25 align=1 (get_local $i)) ;; 122 'z\0\0\0' 45 | ) 46 | 47 | (func (export "bad") (param $i i32) 48 | (drop (i32.load offset=4294967295 (get_local $i))) 49 | ) 50 | ) -------------------------------------------------------------------------------- /exec/testdata/spec/block.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-interpreter/wagon/d9964a5b71752113f6c43d26d03028d0224f9ff8/exec/testdata/spec/block.wasm -------------------------------------------------------------------------------- /exec/testdata/spec/block.wast: -------------------------------------------------------------------------------- 1 | (module 2 | ;; Auxiliary definition 3 | (func $dummy) 4 | 5 | (func (export "empty") 6 | (block) 7 | (block $l) 8 | ) 9 | 10 | (func (export "singular") (result i32) 11 | (block (nop)) 12 | (block (result i32) (i32.const 7)) 13 | ) 14 | 15 | (func (export "multi") (result i32) 16 | (block (call $dummy) (call $dummy) (call $dummy) (call $dummy)) 17 | (block (result i32) (call $dummy) (call $dummy) (call $dummy) (i32.const 8)) 18 | ) 19 | 20 | (func (export "nested") (result i32) 21 | (block (result i32) 22 | (block (call $dummy) (block) (nop)) 23 | (block (result i32) (call $dummy) (i32.const 9)) 24 | ) 25 | ) 26 | 27 | (func (export "deep") (result i32) 28 | (block (result i32) (block (result i32) 29 | (block (result i32) (block (result i32) 30 | (block (result i32) (block (result i32) 31 | (block (result i32) (block (result i32) 32 | (block (result i32) (block (result i32) 33 | (block (result i32) (block (result i32) 34 | (block (result i32) (block (result i32) 35 | (block (result i32) (block (result i32) 36 | (block (result i32) (block (result i32) 37 | (block (result i32) (block (result i32) 38 | (block (result i32) (block (result i32) 39 | (block (result i32) (block (result i32) 40 | (block (result i32) (block (result i32) 41 | (block (result i32) (block (result i32) 42 | (block (result i32) (block (result i32) 43 | (block (result i32) (block (result i32) 44 | (block (result i32) (block (result i32) 45 | (block (result i32) (block (result i32) 46 | (block (result i32) (block (result i32) 47 | (call $dummy) (i32.const 150) 48 | )) 49 | )) 50 | )) 51 | )) 52 | )) 53 | )) 54 | )) 55 | )) 56 | )) 57 | )) 58 | )) 59 | )) 60 | )) 61 | )) 62 | )) 63 | )) 64 | )) 65 | )) 66 | )) 67 | ) 68 | 69 | (func (export "as-unary-operand") (result i32) 70 | (i32.ctz (block (result i32) (call $dummy) (i32.const 13))) 71 | ) 72 | (func (export "as-binary-operand") (result i32) 73 | (i32.mul 74 | (block (result i32) (call $dummy) (i32.const 3)) 75 | (block (result i32) (call $dummy) (i32.const 4)) 76 | ) 77 | ) 78 | (func (export "as-test-operand") (result i32) 79 | (i32.eqz (block (result i32) (call $dummy) (i32.const 13))) 80 | ) 81 | (func (export "as-compare-operand") (result i32) 82 | (f32.gt 83 | (block (result f32) (call $dummy) (f32.const 3)) 84 | (block (result f32) (call $dummy) (f32.const 3)) 85 | ) 86 | ) 87 | 88 | (func (export "break-bare") (result i32) 89 | (block (br 0) (unreachable)) 90 | (block (br_if 0 (i32.const 1)) (unreachable)) 91 | (block (br_table 0 (i32.const 0)) (unreachable)) 92 | (block (br_table 0 0 0 (i32.const 1)) (unreachable)) 93 | (i32.const 19) 94 | ) 95 | (func (export "break-value") (result i32) 96 | (block (result i32) (br 0 (i32.const 18)) (i32.const 19)) 97 | ) 98 | (func (export "break-repeated") (result i32) 99 | (block (result i32) 100 | (br 0 (i32.const 18)) 101 | (br 0 (i32.const 19)) 102 | (drop (br_if 0 (i32.const 20) (i32.const 0))) 103 | (drop (br_if 0 (i32.const 20) (i32.const 1))) 104 | (br 0 (i32.const 21)) 105 | (br_table 0 (i32.const 22) (i32.const 4)) 106 | (br_table 0 0 0 (i32.const 23) (i32.const 1)) 107 | (i32.const 21) 108 | ) 109 | ) 110 | (func (export "break-inner") (result i32) 111 | (local i32) 112 | (set_local 0 (i32.const 0)) 113 | (set_local 0 (i32.add (get_local 0) (block (result i32) (block (result i32) (br 1 (i32.const 0x1)))))) 114 | (set_local 0 (i32.add (get_local 0) (block (result i32) (block (br 0)) (i32.const 0x2)))) 115 | (set_local 0 116 | (i32.add (get_local 0) (block (result i32) (i32.ctz (br 0 (i32.const 0x4))))) 117 | ) 118 | (set_local 0 119 | (i32.add (get_local 0) (block (result i32) (i32.ctz (block (result i32) (br 1 (i32.const 0x8)))))) 120 | ) 121 | (get_local 0) 122 | ) 123 | 124 | (func (export "effects") (result i32) 125 | (local i32) 126 | (block 127 | (set_local 0 (i32.const 1)) 128 | (set_local 0 (i32.mul (get_local 0) (i32.const 3))) 129 | (set_local 0 (i32.sub (get_local 0) (i32.const 5))) 130 | (set_local 0 (i32.mul (get_local 0) (i32.const 7))) 131 | (br 0) 132 | (set_local 0 (i32.mul (get_local 0) (i32.const 100))) 133 | ) 134 | (i32.eq (get_local 0) (i32.const -14)) 135 | ) 136 | ) -------------------------------------------------------------------------------- /exec/testdata/spec/br.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-interpreter/wagon/d9964a5b71752113f6c43d26d03028d0224f9ff8/exec/testdata/spec/br.wasm -------------------------------------------------------------------------------- /exec/testdata/spec/br_if.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-interpreter/wagon/d9964a5b71752113f6c43d26d03028d0224f9ff8/exec/testdata/spec/br_if.wasm -------------------------------------------------------------------------------- /exec/testdata/spec/br_if.wast: -------------------------------------------------------------------------------- 1 | (module 2 | (func $dummy) 3 | 4 | (func (export "as-block-first") (param i32) (result i32) 5 | (block (br_if 0 (get_local 0)) (return (i32.const 2))) (i32.const 3) 6 | ) 7 | (func (export "as-block-mid") (param i32) (result i32) 8 | (block (call $dummy) (br_if 0 (get_local 0)) (return (i32.const 2))) 9 | (i32.const 3) 10 | ) 11 | (func (export "as-block-last") (param i32) 12 | (block (call $dummy) (call $dummy) (br_if 0 (get_local 0))) 13 | ) 14 | (func (export "as-block-first-value") (param i32) (result i32) 15 | (block (result i32) 16 | (drop (br_if 0 (i32.const 10) (get_local 0))) (return (i32.const 11)) 17 | ) 18 | ) 19 | (func (export "as-block-mid-value") (param i32) (result i32) 20 | (block (result i32) 21 | (call $dummy) 22 | (drop (br_if 0 (i32.const 20) (get_local 0))) 23 | (return (i32.const 21)) 24 | ) 25 | ) 26 | (func (export "as-block-last-value") (param i32) (result i32) 27 | (block (result i32) 28 | (call $dummy) (call $dummy) (br_if 0 (i32.const 11) (get_local 0)) 29 | ) 30 | ) 31 | 32 | (func (export "as-loop-first") (param i32) (result i32) 33 | (block (loop (br_if 1 (get_local 0)) (return (i32.const 2)))) (i32.const 3) 34 | ) 35 | (func (export "as-loop-mid") (param i32) (result i32) 36 | (block (loop (call $dummy) (br_if 1 (get_local 0)) (return (i32.const 2)))) 37 | (i32.const 4) 38 | ) 39 | (func (export "as-loop-last") (param i32) 40 | (loop (call $dummy) (br_if 1 (get_local 0))) 41 | ) 42 | 43 | (func (export "as-if-then") (param i32 i32) 44 | (block 45 | (if (get_local 0) (then (br_if 1 (get_local 1))) (else (call $dummy))) 46 | ) 47 | ) 48 | (func (export "as-if-else") (param i32 i32) 49 | (block 50 | (if (get_local 0) (then (call $dummy)) (else (br_if 1 (get_local 1)))) 51 | ) 52 | ) 53 | 54 | (func (export "nested-block-value") (param i32) (result i32) 55 | (i32.add 56 | (i32.const 1) 57 | (block (result i32) 58 | (drop (i32.const 2)) 59 | (i32.add 60 | (i32.const 4) 61 | (block (result i32) 62 | (drop (br_if 1 (i32.const 8) (get_local 0))) 63 | (i32.const 16) 64 | ) 65 | ) 66 | ) 67 | ) 68 | ) 69 | 70 | (func (export "nested-br-value") (param i32) (result i32) 71 | (i32.add 72 | (i32.const 1) 73 | (block (result i32) 74 | (drop (i32.const 2)) 75 | (br 0 76 | (block (result i32) 77 | (drop (br_if 1 (i32.const 8) (get_local 0))) (i32.const 4) 78 | ) 79 | ) 80 | (i32.const 16) 81 | ) 82 | ) 83 | ) 84 | 85 | (func (export "nested-br_if-value") (param i32) (result i32) 86 | (i32.add 87 | (i32.const 1) 88 | (block (result i32) 89 | (drop (i32.const 2)) 90 | (drop (br_if 0 91 | (block (result i32) 92 | (drop (br_if 1 (i32.const 8) (get_local 0))) (i32.const 4) 93 | ) 94 | (i32.const 1) 95 | )) 96 | (i32.const 16) 97 | ) 98 | ) 99 | ) 100 | 101 | (func (export "nested-br_if-value-cond") (param i32) (result i32) 102 | (i32.add 103 | (i32.const 1) 104 | (block (result i32) 105 | (drop (i32.const 2)) 106 | (drop (br_if 0 107 | (i32.const 4) 108 | (block (result i32) 109 | (drop (br_if 1 (i32.const 8) (get_local 0))) (i32.const 1) 110 | ) 111 | )) 112 | (i32.const 16) 113 | ) 114 | ) 115 | ) 116 | 117 | (func (export "nested-br_table-value") (param i32) (result i32) 118 | (i32.add 119 | (i32.const 1) 120 | (block (result i32) 121 | (drop (i32.const 2)) 122 | (br_table 0 123 | (block (result i32) 124 | (drop (br_if 1 (i32.const 8) (get_local 0))) (i32.const 4) 125 | ) 126 | (i32.const 1) 127 | ) 128 | (i32.const 16) 129 | ) 130 | ) 131 | ) 132 | 133 | (func (export "nested-br_table-value-index") (param i32) (result i32) 134 | (i32.add 135 | (i32.const 1) 136 | (block (result i32) 137 | (drop (i32.const 2)) 138 | (br_table 0 139 | (i32.const 4) 140 | (block (result i32) 141 | (drop (br_if 1 (i32.const 8) (get_local 0))) (i32.const 1) 142 | ) 143 | ) 144 | (i32.const 16) 145 | ) 146 | ) 147 | ) 148 | ) -------------------------------------------------------------------------------- /exec/testdata/spec/br_table.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-interpreter/wagon/d9964a5b71752113f6c43d26d03028d0224f9ff8/exec/testdata/spec/br_table.wasm -------------------------------------------------------------------------------- /exec/testdata/spec/break-drop.wasm: -------------------------------------------------------------------------------- 1 | asm`brbr_ifbr_table 2 | @ @A 3 | @A -------------------------------------------------------------------------------- /exec/testdata/spec/break-drop.wast: -------------------------------------------------------------------------------- 1 | (module 2 | (func (export "br") (block (br 0))) 3 | (func (export "br_if") (block (br_if 0 (i32.const 1)))) 4 | (func (export "br_table") (block (br_table 0 (i32.const 0)))) 5 | ) -------------------------------------------------------------------------------- /exec/testdata/spec/call_indirect.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-interpreter/wagon/d9964a5b71752113f6c43d26d03028d0224f9ff8/exec/testdata/spec/call_indirect.wasm -------------------------------------------------------------------------------- /exec/testdata/spec/endianness.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-interpreter/wagon/d9964a5b71752113f6c43d26d03028d0224f9ff8/exec/testdata/spec/endianness.wasm -------------------------------------------------------------------------------- /exec/testdata/spec/endianness.wast: -------------------------------------------------------------------------------- 1 | (module 2 | (memory 1) 3 | 4 | ;; Stores an i16 value in little-endian-format 5 | (func $i16_store_little (param $address i32) (param $value i32) 6 | (i32.store8 (get_local $address) (get_local $value)) 7 | (i32.store8 (i32.add (get_local $address) (i32.const 1)) (i32.shr_u (get_local $value) (i32.const 8))) 8 | ) 9 | 10 | ;; Stores an i32 value in little-endian format 11 | (func $i32_store_little (param $address i32) (param $value i32) 12 | (call $i16_store_little (get_local $address) (get_local $value)) 13 | (call $i16_store_little (i32.add (get_local $address) (i32.const 2)) (i32.shr_u (get_local $value) (i32.const 16))) 14 | ) 15 | 16 | ;; Stores an i64 value in little-endian format 17 | (func $i64_store_little (param $address i32) (param $value i64) 18 | (call $i32_store_little (get_local $address) (i32.wrap/i64 (get_local $value))) 19 | (call $i32_store_little (i32.add (get_local $address) (i32.const 4)) (i32.wrap/i64 (i64.shr_u (get_local $value) (i64.const 32)))) 20 | ) 21 | 22 | ;; Loads an i16 value in little-endian format 23 | (func $i16_load_little (param $address i32) (result i32) 24 | (i32.or 25 | (i32.load8_u (get_local $address)) 26 | (i32.shl (i32.load8_u (i32.add (get_local $address) (i32.const 1))) (i32.const 8)) 27 | ) 28 | ) 29 | 30 | ;; Loads an i32 value in little-endian format 31 | (func $i32_load_little (param $address i32) (result i32) 32 | (i32.or 33 | (call $i16_load_little (get_local $address)) 34 | (i32.shl (call $i16_load_little (i32.add (get_local $address) (i32.const 2))) (i32.const 16)) 35 | ) 36 | ) 37 | 38 | ;; Loads an i64 value in little-endian format 39 | (func $i64_load_little (param $address i32) (result i64) 40 | (i64.or 41 | (i64.extend_u/i32 (call $i32_load_little (get_local $address))) 42 | (i64.shl (i64.extend_u/i32 (call $i32_load_little (i32.add (get_local $address) (i32.const 4)))) (i64.const 32)) 43 | ) 44 | ) 45 | 46 | (func (export "i32_load16_s") (param $value i32) (result i32) 47 | (call $i16_store_little (i32.const 0) (get_local $value)) 48 | (i32.load16_s (i32.const 0)) 49 | ) 50 | 51 | (func (export "i32_load16_u") (param $value i32) (result i32) 52 | (call $i16_store_little (i32.const 0) (get_local $value)) 53 | (i32.load16_u (i32.const 0)) 54 | ) 55 | 56 | (func (export "i32_load") (param $value i32) (result i32) 57 | (call $i32_store_little (i32.const 0) (get_local $value)) 58 | (i32.load (i32.const 0)) 59 | ) 60 | 61 | (func (export "i64_load16_s") (param $value i64) (result i64) 62 | (call $i16_store_little (i32.const 0) (i32.wrap/i64 (get_local $value))) 63 | (i64.load16_s (i32.const 0)) 64 | ) 65 | 66 | (func (export "i64_load16_u") (param $value i64) (result i64) 67 | (call $i16_store_little (i32.const 0) (i32.wrap/i64 (get_local $value))) 68 | (i64.load16_u (i32.const 0)) 69 | ) 70 | 71 | (func (export "i64_load32_s") (param $value i64) (result i64) 72 | (call $i32_store_little (i32.const 0) (i32.wrap/i64 (get_local $value))) 73 | (i64.load32_s (i32.const 0)) 74 | ) 75 | 76 | (func (export "i64_load32_u") (param $value i64) (result i64) 77 | (call $i32_store_little (i32.const 0) (i32.wrap/i64 (get_local $value))) 78 | (i64.load32_u (i32.const 0)) 79 | ) 80 | 81 | (func (export "i64_load") (param $value i64) (result i64) 82 | (call $i64_store_little (i32.const 0) (get_local $value)) 83 | (i64.load (i32.const 0)) 84 | ) 85 | 86 | (func (export "f32_load") (param $value f32) (result f32) 87 | (call $i32_store_little (i32.const 0) (i32.reinterpret/f32 (get_local $value))) 88 | (f32.load (i32.const 0)) 89 | ) 90 | 91 | (func (export "f64_load") (param $value f64) (result f64) 92 | (call $i64_store_little (i32.const 0) (i64.reinterpret/f64 (get_local $value))) 93 | (f64.load (i32.const 0)) 94 | ) 95 | 96 | 97 | (func (export "i32_store16") (param $value i32) (result i32) 98 | (i32.store16 (i32.const 0) (get_local $value)) 99 | (call $i16_load_little (i32.const 0)) 100 | ) 101 | 102 | (func (export "i32_store") (param $value i32) (result i32) 103 | (i32.store (i32.const 0) (get_local $value)) 104 | (call $i32_load_little (i32.const 0)) 105 | ) 106 | 107 | (func (export "i64_store16") (param $value i64) (result i64) 108 | (i64.store16 (i32.const 0) (get_local $value)) 109 | (i64.extend_u/i32 (call $i16_load_little (i32.const 0))) 110 | ) 111 | 112 | (func (export "i64_store32") (param $value i64) (result i64) 113 | (i64.store32 (i32.const 0) (get_local $value)) 114 | (i64.extend_u/i32 (call $i32_load_little (i32.const 0))) 115 | ) 116 | 117 | (func (export "i64_store") (param $value i64) (result i64) 118 | (i64.store (i32.const 0) (get_local $value)) 119 | (call $i64_load_little (i32.const 0)) 120 | ) 121 | 122 | (func (export "f32_store") (param $value f32) (result f32) 123 | (f32.store (i32.const 0) (get_local $value)) 124 | (f32.reinterpret/i32 (call $i32_load_little (i32.const 0))) 125 | ) 126 | 127 | (func (export "f64_store") (param $value f64) (result f64) 128 | (f64.store (i32.const 0) (get_local $value)) 129 | (f64.reinterpret/i64 (call $i64_load_little (i32.const 0))) 130 | ) 131 | ) -------------------------------------------------------------------------------- /exec/testdata/spec/fac.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-interpreter/wagon/d9964a5b71752113f6c43d26d03028d0224f9ff8/exec/testdata/spec/fac.wasm -------------------------------------------------------------------------------- /exec/testdata/spec/fac.wast: -------------------------------------------------------------------------------- 1 | (module 2 | ;; Recursive factorial 3 | (func (export "fac-rec") (param i64) (result i64) 4 | (if (result i64) (i64.eq (get_local 0) (i64.const 0)) 5 | (then (i64.const 1)) 6 | (else 7 | (i64.mul (get_local 0) (call 0 (i64.sub (get_local 0) (i64.const 1)))) 8 | ) 9 | ) 10 | ) 11 | 12 | ;; Recursive factorial named 13 | (func $fac-rec-named (export "fac-rec-named") (param $n i64) (result i64) 14 | (if (result i64) (i64.eq (get_local $n) (i64.const 0)) 15 | (then (i64.const 1)) 16 | (else 17 | (i64.mul 18 | (get_local $n) 19 | (call $fac-rec-named (i64.sub (get_local $n) (i64.const 1))) 20 | ) 21 | ) 22 | ) 23 | ) 24 | 25 | ;; Iterative factorial 26 | (func (export "fac-iter") (param i64) (result i64) 27 | (local i64 i64) 28 | (set_local 1 (get_local 0)) 29 | (set_local 2 (i64.const 1)) 30 | (block 31 | (loop 32 | (if 33 | (i64.eq (get_local 1) (i64.const 0)) 34 | (then (br 2)) 35 | (else 36 | (set_local 2 (i64.mul (get_local 1) (get_local 2))) 37 | (set_local 1 (i64.sub (get_local 1) (i64.const 1))) 38 | ) 39 | ) 40 | (br 0) 41 | ) 42 | ) 43 | (get_local 2) 44 | ) 45 | 46 | ;; Iterative factorial named 47 | (func (export "fac-iter-named") (param $n i64) (result i64) 48 | (local $i i64) 49 | (local $res i64) 50 | (set_local $i (get_local $n)) 51 | (set_local $res (i64.const 1)) 52 | (block $done 53 | (loop $loop 54 | (if 55 | (i64.eq (get_local $i) (i64.const 0)) 56 | (then (br $done)) 57 | (else 58 | (set_local $res (i64.mul (get_local $i) (get_local $res))) 59 | (set_local $i (i64.sub (get_local $i) (i64.const 1))) 60 | ) 61 | ) 62 | (br $loop) 63 | ) 64 | ) 65 | (get_local $res) 66 | ) 67 | 68 | ;; Optimized factorial. 69 | (func (export "fac-opt") (param i64) (result i64) 70 | (local i64) 71 | (set_local 1 (i64.const 1)) 72 | (block 73 | (br_if 0 (i64.lt_s (get_local 0) (i64.const 2))) 74 | (loop 75 | (set_local 1 (i64.mul (get_local 1) (get_local 0))) 76 | (set_local 0 (i64.add (get_local 0) (i64.const -1))) 77 | (br_if 0 (i64.gt_s (get_local 0) (i64.const 1))) 78 | ) 79 | ) 80 | (get_local 1) 81 | ) 82 | ) -------------------------------------------------------------------------------- /exec/testdata/spec/forward.wasm: -------------------------------------------------------------------------------- 1 | asm`evenodd 2 | + AFA Ak  AFA Ak -------------------------------------------------------------------------------- /exec/testdata/spec/forward.wast: -------------------------------------------------------------------------------- 1 | (module 2 | (func $even (export "even") (param $n i32) (result i32) 3 | (if (result i32) (i32.eq (get_local $n) (i32.const 0)) 4 | (then (i32.const 1)) 5 | (else (call $odd (i32.sub (get_local $n) (i32.const 1)))) 6 | ) 7 | ) 8 | 9 | (func $odd (export "odd") (param $n i32) (result i32) 10 | (if (result i32) (i32.eq (get_local $n) (i32.const 0)) 11 | (then (i32.const 0)) 12 | (else (call $even (i32.sub (get_local $n) (i32.const 1)))) 13 | ) 14 | ) 15 | ) -------------------------------------------------------------------------------- /exec/testdata/spec/get_local.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-interpreter/wagon/d9964a5b71752113f6c43d26d03028d0224f9ff8/exec/testdata/spec/get_local.wasm -------------------------------------------------------------------------------- /exec/testdata/spec/get_local.wast: -------------------------------------------------------------------------------- 1 | ;; Test `get_local` operator 2 | 3 | (module 4 | ;; Typing 5 | 6 | (func (export "type-local-i32") (result i32) (local i32) (get_local 0)) 7 | (func (export "type-local-i64") (result i64) (local i64) (get_local 0)) 8 | (func (export "type-local-f32") (result f32) (local f32) (get_local 0)) 9 | (func (export "type-local-f64") (result f64) (local f64) (get_local 0)) 10 | 11 | (func (export "type-param-i32") (param i32) (result i32) (get_local 0)) 12 | (func (export "type-param-i64") (param i64) (result i64) (get_local 0)) 13 | (func (export "type-param-f32") (param f32) (result f32) (get_local 0)) 14 | (func (export "type-param-f64") (param f64) (result f64) (get_local 0)) 15 | 16 | (func (export "type-mixed") (param i64 f32 f64 i32 i32) 17 | (local f32 i64 i64 f64) 18 | (drop (i64.eqz (get_local 0))) 19 | (drop (f32.neg (get_local 1))) 20 | (drop (f64.neg (get_local 2))) 21 | (drop (i32.eqz (get_local 3))) 22 | (drop (i32.eqz (get_local 4))) 23 | (drop (f32.neg (get_local 5))) 24 | (drop (i64.eqz (get_local 6))) 25 | (drop (i64.eqz (get_local 7))) 26 | (drop (f64.neg (get_local 8))) 27 | ) 28 | 29 | ;; Reading 30 | 31 | (func (export "read") (param i64 f32 f64 i32 i32) (result f64) 32 | (local f32 i64 i64 f64) 33 | (set_local 5 (f32.const 5.5)) 34 | (set_local 6 (i64.const 6)) 35 | (set_local 8 (f64.const 8)) 36 | (f64.add 37 | (f64.convert_u/i64 (get_local 0)) 38 | (f64.add 39 | (f64.promote/f32 (get_local 1)) 40 | (f64.add 41 | (get_local 2) 42 | (f64.add 43 | (f64.convert_u/i32 (get_local 3)) 44 | (f64.add 45 | (f64.convert_s/i32 (get_local 4)) 46 | (f64.add 47 | (f64.promote/f32 (get_local 5)) 48 | (f64.add 49 | (f64.convert_u/i64 (get_local 6)) 50 | (f64.add 51 | (f64.convert_u/i64 (get_local 7)) 52 | (get_local 8) 53 | ) 54 | ) 55 | ) 56 | ) 57 | ) 58 | ) 59 | ) 60 | ) 61 | ) 62 | ) -------------------------------------------------------------------------------- /exec/testdata/spec/globals.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-interpreter/wagon/d9964a5b71752113f6c43d26d03028d0224f9ff8/exec/testdata/spec/globals.wasm -------------------------------------------------------------------------------- /exec/testdata/spec/globals.wast: -------------------------------------------------------------------------------- 1 | ;; Test globals 2 | 3 | (module 4 | (global $a i32 (i32.const -2)) 5 | (global (;1;) f32 (f32.const -3)) 6 | (global (;2;) f64 (f64.const -4)) 7 | (global $b i64 (i64.const -5)) 8 | 9 | (global $x (mut i32) (i32.const -12)) 10 | (global (;5;) (mut f32) (f32.const -13)) 11 | (global (;6;) (mut f64) (f64.const -14)) 12 | (global $y (mut i64) (i64.const -15)) 13 | 14 | (func (export "get-a") (result i32) (get_global $a)) 15 | (func (export "get-b") (result i64) (get_global $b)) 16 | (func (export "get-x") (result i32) (get_global $x)) 17 | (func (export "get-y") (result i64) (get_global $y)) 18 | (func (export "set-x") (param i32) (set_global $x (get_local 0))) 19 | (func (export "set-y") (param i64) (set_global $y (get_local 0))) 20 | 21 | (func (export "get-1") (result f32) (get_global 1)) 22 | (func (export "get-2") (result f64) (get_global 2)) 23 | (func (export "get-5") (result f32) (get_global 5)) 24 | (func (export "get-6") (result f64) (get_global 6)) 25 | (func (export "set-5") (param f32) (set_global 5 (get_local 0))) 26 | (func (export "set-6") (param f64) (set_global 6 (get_local 0))) 27 | ) -------------------------------------------------------------------------------- /exec/testdata/spec/i32.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-interpreter/wagon/d9964a5b71752113f6c43d26d03028d0224f9ff8/exec/testdata/spec/i32.wasm -------------------------------------------------------------------------------- /exec/testdata/spec/if.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-interpreter/wagon/d9964a5b71752113f6c43d26d03028d0224f9ff8/exec/testdata/spec/if.wasm -------------------------------------------------------------------------------- /exec/testdata/spec/if.wast: -------------------------------------------------------------------------------- 1 | ;; Test `if` operator 2 | 3 | (module 4 | ;; Auxiliary definition 5 | (func $dummy) 6 | 7 | (func (export "empty") (param i32) 8 | (if (get_local 0) (then)) 9 | (if (get_local 0) (then) (else)) 10 | (if $l (get_local 0) (then)) 11 | (if $l (get_local 0) (then) (else)) 12 | ) 13 | 14 | (func (export "singular") (param i32) (result i32) 15 | (if (get_local 0) (then (nop))) 16 | (if (get_local 0) (then (nop)) (else (nop))) 17 | (if (result i32) (get_local 0) (then (i32.const 7)) (else (i32.const 8))) 18 | ) 19 | 20 | (func (export "multi") (param i32) (result i32) 21 | (if (get_local 0) (then (call $dummy) (call $dummy) (call $dummy))) 22 | (if (get_local 0) (then) (else (call $dummy) (call $dummy) (call $dummy))) 23 | (if (result i32) (get_local 0) 24 | (then (call $dummy) (call $dummy) (i32.const 8)) 25 | (else (call $dummy) (call $dummy) (i32.const 9)) 26 | ) 27 | ) 28 | 29 | (func (export "nested") (param i32 i32) (result i32) 30 | (if (result i32) (get_local 0) 31 | (then 32 | (if (get_local 1) (then (call $dummy) (block) (nop))) 33 | (if (get_local 1) (then) (else (call $dummy) (block) (nop))) 34 | (if (result i32) (get_local 1) 35 | (then (call $dummy) (i32.const 9)) 36 | (else (call $dummy) (i32.const 10)) 37 | ) 38 | ) 39 | (else 40 | (if (get_local 1) (then (call $dummy) (block) (nop))) 41 | (if (get_local 1) (then) (else (call $dummy) (block) (nop))) 42 | (if (result i32) (get_local 1) 43 | (then (call $dummy) (i32.const 10)) 44 | (else (call $dummy) (i32.const 11)) 45 | ) 46 | ) 47 | ) 48 | ) 49 | 50 | (func (export "as-unary-operand") (param i32) (result i32) 51 | (i32.ctz 52 | (if (result i32) (get_local 0) 53 | (then (call $dummy) (i32.const 13)) 54 | (else (call $dummy) (i32.const -13)) 55 | ) 56 | ) 57 | ) 58 | (func (export "as-binary-operand") (param i32 i32) (result i32) 59 | (i32.mul 60 | (if (result i32) (get_local 0) 61 | (then (call $dummy) (i32.const 3)) 62 | (else (call $dummy) (i32.const -3)) 63 | ) 64 | (if (result i32) (get_local 1) 65 | (then (call $dummy) (i32.const 4)) 66 | (else (call $dummy) (i32.const -5)) 67 | ) 68 | ) 69 | ) 70 | (func (export "as-test-operand") (param i32) (result i32) 71 | (i32.eqz 72 | (if (result i32) (get_local 0) 73 | (then (call $dummy) (i32.const 13)) 74 | (else (call $dummy) (i32.const 0)) 75 | ) 76 | ) 77 | ) 78 | (func (export "as-compare-operand") (param i32 i32) (result i32) 79 | (f32.gt 80 | (if (result f32) (get_local 0) 81 | (then (call $dummy) (f32.const 3)) 82 | (else (call $dummy) (f32.const -3)) 83 | ) 84 | (if (result f32) (get_local 1) 85 | (then (call $dummy) (f32.const 4)) 86 | (else (call $dummy) (f32.const -4)) 87 | ) 88 | ) 89 | ) 90 | 91 | (func (export "break-bare") (result i32) 92 | (if (i32.const 1) (then (br 0) (unreachable))) 93 | (if (i32.const 1) (then (br 0) (unreachable)) (else (unreachable))) 94 | (if (i32.const 0) (then (unreachable)) (else (br 0) (unreachable))) 95 | (if (i32.const 1) (then (br_if 0 (i32.const 1)) (unreachable))) 96 | (if (i32.const 1) (then (br_if 0 (i32.const 1)) (unreachable)) (else (unreachable))) 97 | (if (i32.const 0) (then (unreachable)) (else (br_if 0 (i32.const 1)) (unreachable))) 98 | (if (i32.const 1) (then (br_table 0 (i32.const 0)) (unreachable))) 99 | (if (i32.const 1) (then (br_table 0 (i32.const 0)) (unreachable)) (else (unreachable))) 100 | (if (i32.const 0) (then (unreachable)) (else (br_table 0 (i32.const 0)) (unreachable))) 101 | (i32.const 19) 102 | ) 103 | 104 | (func (export "break-value") (param i32) (result i32) 105 | (if (result i32) (get_local 0) 106 | (then (br 0 (i32.const 18)) (i32.const 19)) 107 | (else (br 0 (i32.const 21)) (i32.const 20)) 108 | ) 109 | ) 110 | 111 | (func (export "effects") (param i32) (result i32) 112 | (local i32) 113 | (if 114 | (block (result i32) (set_local 1 (i32.const 1)) (get_local 0)) 115 | (then 116 | (set_local 1 (i32.mul (get_local 1) (i32.const 3))) 117 | (set_local 1 (i32.sub (get_local 1) (i32.const 5))) 118 | (set_local 1 (i32.mul (get_local 1) (i32.const 7))) 119 | (br 0) 120 | (set_local 1 (i32.mul (get_local 1) (i32.const 100))) 121 | ) 122 | (else 123 | (set_local 1 (i32.mul (get_local 1) (i32.const 5))) 124 | (set_local 1 (i32.sub (get_local 1) (i32.const 7))) 125 | (set_local 1 (i32.mul (get_local 1) (i32.const 3))) 126 | (br 0) 127 | (set_local 1 (i32.mul (get_local 1) (i32.const 1000))) 128 | ) 129 | ) 130 | (get_local 1) 131 | ) 132 | ) -------------------------------------------------------------------------------- /exec/testdata/spec/loop.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-interpreter/wagon/d9964a5b71752113f6c43d26d03028d0224f9ff8/exec/testdata/spec/loop.wasm -------------------------------------------------------------------------------- /exec/testdata/spec/memory_redundancy.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-interpreter/wagon/d9964a5b71752113f6c43d26d03028d0224f9ff8/exec/testdata/spec/memory_redundancy.wasm -------------------------------------------------------------------------------- /exec/testdata/spec/memory_redundancy.wast: -------------------------------------------------------------------------------- 1 | (module 2 | (memory 1 1) 3 | 4 | (func (export "zero_everything") 5 | (i32.store (i32.const 0) (i32.const 0)) 6 | (i32.store (i32.const 4) (i32.const 0)) 7 | (i32.store (i32.const 8) (i32.const 0)) 8 | (i32.store (i32.const 12) (i32.const 0)) 9 | ) 10 | 11 | (func (export "test_store_to_load") (result i32) 12 | (i32.store (i32.const 8) (i32.const 0)) 13 | (f32.store (i32.const 5) (f32.const -0.0)) 14 | (i32.load (i32.const 8)) 15 | ) 16 | 17 | (func (export "test_redundant_load") (result i32) 18 | (local $t i32) 19 | (local $s i32) 20 | (set_local $t (i32.load (i32.const 8))) 21 | (i32.store (i32.const 5) (i32.const 0x80000000)) 22 | (set_local $s (i32.load (i32.const 8))) 23 | (i32.add (get_local $t) (get_local $s)) 24 | ) 25 | 26 | (func (export "test_dead_store") (result f32) 27 | (local $t f32) 28 | (i32.store (i32.const 8) (i32.const 0x23232323)) 29 | (set_local $t (f32.load (i32.const 11))) 30 | (i32.store (i32.const 8) (i32.const 0)) 31 | (get_local $t) 32 | ) 33 | 34 | ;; A function named "malloc" which implementations nonetheless shouldn't 35 | ;; assume behaves like C malloc. 36 | (func $malloc (export "malloc") 37 | (param $size i32) 38 | (result i32) 39 | (i32.const 16) 40 | ) 41 | 42 | ;; Call malloc twice, but unlike C malloc, we don't get non-aliasing pointers. 43 | (func (export "malloc_aliasing") 44 | (result i32) 45 | (local $x i32) 46 | (local $y i32) 47 | (set_local $x (call $malloc (i32.const 4))) 48 | (set_local $y (call $malloc (i32.const 4))) 49 | (i32.store (get_local $x) (i32.const 42)) 50 | (i32.store (get_local $y) (i32.const 43)) 51 | (i32.load (get_local $x)) 52 | ) 53 | ) -------------------------------------------------------------------------------- /exec/testdata/spec/names.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-interpreter/wagon/d9964a5b71752113f6c43d26d03028d0224f9ff8/exec/testdata/spec/names.wasm -------------------------------------------------------------------------------- /exec/testdata/spec/nop.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-interpreter/wagon/d9964a5b71752113f6c43d26d03028d0224f9ff8/exec/testdata/spec/nop.wasm -------------------------------------------------------------------------------- /exec/testdata/spec/resizing.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-interpreter/wagon/d9964a5b71752113f6c43d26d03028d0224f9ff8/exec/testdata/spec/resizing.wasm -------------------------------------------------------------------------------- /exec/testdata/spec/resizing.wast: -------------------------------------------------------------------------------- 1 | (module 2 | (memory 0) 3 | 4 | (func (export "load_at_zero") (result i32) (i32.load (i32.const 0))) 5 | (func (export "store_at_zero") (i32.store (i32.const 0) (i32.const 2))) 6 | 7 | (func (export "load_at_page_size") (result i32) (i32.load (i32.const 0x10000))) 8 | (func (export "store_at_page_size") (i32.store (i32.const 0x10000) (i32.const 3))) 9 | 10 | (func (export "grow") (param $sz i32) (result i32) (grow_memory (get_local $sz))) 11 | (func (export "size") (result i32) (current_memory)) 12 | ) -------------------------------------------------------------------------------- /exec/testdata/spec/return.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-interpreter/wagon/d9964a5b71752113f6c43d26d03028d0224f9ff8/exec/testdata/spec/return.wasm -------------------------------------------------------------------------------- /exec/testdata/spec/select.wasm: -------------------------------------------------------------------------------- 1 | asm%``~~~`}}}`|||``h 2 | select_i32 3 | select_i64 4 | select_f32 5 | select_f64 select_trap_l select_trap_rselect_unreached 6 | T         A  A  AAACA -------------------------------------------------------------------------------- /exec/testdata/spec/select.wast: -------------------------------------------------------------------------------- 1 | (module 2 | (func (export "select_i32") (param $lhs i32) (param $rhs i32) (param $cond i32) (result i32) 3 | (select (get_local $lhs) (get_local $rhs) (get_local $cond))) 4 | 5 | (func (export "select_i64") (param $lhs i64) (param $rhs i64) (param $cond i32) (result i64) 6 | (select (get_local $lhs) (get_local $rhs) (get_local $cond))) 7 | 8 | (func (export "select_f32") (param $lhs f32) (param $rhs f32) (param $cond i32) (result f32) 9 | (select (get_local $lhs) (get_local $rhs) (get_local $cond))) 10 | 11 | (func (export "select_f64") (param $lhs f64) (param $rhs f64) (param $cond i32) (result f64) 12 | (select (get_local $lhs) (get_local $rhs) (get_local $cond))) 13 | 14 | ;; Check that both sides of the select are evaluated 15 | (func (export "select_trap_l") (param $cond i32) (result i32) 16 | (select (unreachable) (i32.const 0) (get_local $cond)) 17 | ) 18 | (func (export "select_trap_r") (param $cond i32) (result i32) 19 | (select (i32.const 0) (unreachable) (get_local $cond)) 20 | ) 21 | 22 | (func (export "select_unreached") 23 | (unreachable) (select) 24 | (unreachable) (i32.const 0) (select) 25 | (unreachable) (i32.const 0) (i32.const 0) (select) 26 | (unreachable) (f32.const 0) (i32.const 0) (select) 27 | (unreachable) 28 | ) 29 | ) -------------------------------------------------------------------------------- /exec/testdata/spec/switch.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-interpreter/wagon/d9964a5b71752113f6c43d26d03028d0224f9ff8/exec/testdata/spec/switch.wasm -------------------------------------------------------------------------------- /exec/testdata/spec/switch.wast: -------------------------------------------------------------------------------- 1 | (module 2 | ;; Statement switch 3 | (func (export "stmt") (param $i i32) (result i32) 4 | (local $j i32) 5 | (set_local $j (i32.const 100)) 6 | (block $switch 7 | (block $7 8 | (block $default 9 | (block $6 10 | (block $5 11 | (block $4 12 | (block $3 13 | (block $2 14 | (block $1 15 | (block $0 16 | (br_table $0 $1 $2 $3 $4 $5 $6 $7 $default 17 | (get_local $i) 18 | ) 19 | ) ;; 0 20 | (return (get_local $i)) 21 | ) ;; 1 22 | (nop) 23 | ;; fallthrough 24 | ) ;; 2 25 | ;; fallthrough 26 | ) ;; 3 27 | (set_local $j (i32.sub (i32.const 0) (get_local $i))) 28 | (br $switch) 29 | ) ;; 4 30 | (br $switch) 31 | ) ;; 5 32 | (set_local $j (i32.const 101)) 33 | (br $switch) 34 | ) ;; 6 35 | (set_local $j (i32.const 101)) 36 | ;; fallthrough 37 | ) ;; default 38 | (set_local $j (i32.const 102)) 39 | ) ;; 7 40 | ;; fallthrough 41 | ) 42 | (return (get_local $j)) 43 | ) 44 | 45 | ;; Expression switch 46 | (func (export "expr") (param $i i64) (result i64) 47 | (local $j i64) 48 | (set_local $j (i64.const 100)) 49 | (return 50 | (block $switch (result i64) 51 | (block $7 52 | (block $default 53 | (block $4 54 | (block $5 55 | (block $6 56 | (block $3 57 | (block $2 58 | (block $1 59 | (block $0 60 | (br_table $0 $1 $2 $3 $4 $5 $6 $7 $default 61 | (i32.wrap/i64 (get_local $i)) 62 | ) 63 | ) ;; 0 64 | (return (get_local $i)) 65 | ) ;; 1 66 | (nop) 67 | ;; fallthrough 68 | ) ;; 2 69 | ;; fallthrough 70 | ) ;; 3 71 | (br $switch (i64.sub (i64.const 0) (get_local $i))) 72 | ) ;; 6 73 | (set_local $j (i64.const 101)) 74 | ;; fallthrough 75 | ) ;; 4 76 | ;; fallthrough 77 | ) ;; 5 78 | ;; fallthrough 79 | ) ;; default 80 | (br $switch (get_local $j)) 81 | ) ;; 7 82 | (i64.const -5) 83 | ) 84 | ) 85 | ) 86 | 87 | ;; Argument switch 88 | (func (export "arg") (param $i i32) (result i32) 89 | (return 90 | (block $2 (result i32) 91 | (i32.add (i32.const 10) 92 | (block $1 (result i32) 93 | (i32.add (i32.const 100) 94 | (block $0 (result i32) 95 | (i32.add (i32.const 1000) 96 | (block $default (result i32) 97 | (br_table $0 $1 $2 $default 98 | (i32.mul (i32.const 2) (get_local $i)) 99 | (i32.and (i32.const 3) (get_local $i)) 100 | ) 101 | ) 102 | ) 103 | ) 104 | ) 105 | ) 106 | ) 107 | ) 108 | ) 109 | ) 110 | 111 | ;; Corner cases 112 | (func (export "corner") (result i32) 113 | (block 114 | (br_table 0 (i32.const 0)) 115 | ) 116 | (i32.const 1) 117 | ) 118 | ) 119 | -------------------------------------------------------------------------------- /exec/testdata/spec/tee_local.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-interpreter/wagon/d9964a5b71752113f6c43d26d03028d0224f9ff8/exec/testdata/spec/tee_local.wasm -------------------------------------------------------------------------------- /exec/testdata/spec/tee_local.wast: -------------------------------------------------------------------------------- 1 | (module 2 | ;; Typing 3 | 4 | (func (export "type-local-i32") (result i32) (local i32) (tee_local 0 (i32.const 0))) 5 | (func (export "type-local-i64") (result i64) (local i64) (tee_local 0 (i64.const 0))) 6 | (func (export "type-local-f32") (result f32) (local f32) (tee_local 0 (f32.const 0))) 7 | (func (export "type-local-f64") (result f64) (local f64) (tee_local 0 (f64.const 0))) 8 | 9 | (func (export "type-param-i32") (param i32) (result i32) (tee_local 0 (i32.const 10))) 10 | (func (export "type-param-i64") (param i64) (result i64) (tee_local 0 (i64.const 11))) 11 | (func (export "type-param-f32") (param f32) (result f32) (tee_local 0 (f32.const 11.1))) 12 | (func (export "type-param-f64") (param f64) (result f64) (tee_local 0 (f64.const 12.2))) 13 | 14 | (func (export "type-mixed") (param i64 f32 f64 i32 i32) (local f32 i64 i64 f64) 15 | (drop (i64.eqz (tee_local 0 (i64.const 0)))) 16 | (drop (f32.neg (tee_local 1 (f32.const 0)))) 17 | (drop (f64.neg (tee_local 2 (f64.const 0)))) 18 | (drop (i32.eqz (tee_local 3 (i32.const 0)))) 19 | (drop (i32.eqz (tee_local 4 (i32.const 0)))) 20 | (drop (f32.neg (tee_local 5 (f32.const 0)))) 21 | (drop (i64.eqz (tee_local 6 (i64.const 0)))) 22 | (drop (i64.eqz (tee_local 7 (i64.const 0)))) 23 | (drop (f64.neg (tee_local 8 (f64.const 0)))) 24 | ) 25 | 26 | ;; Writing 27 | 28 | (func (export "write") (param i64 f32 f64 i32 i32) (result i64) (local f32 i64 i64 f64) 29 | (drop (tee_local 1 (f32.const -0.3))) 30 | (drop (tee_local 3 (i32.const 40))) 31 | (drop (tee_local 4 (i32.const -7))) 32 | (drop (tee_local 5 (f32.const 5.5))) 33 | (drop (tee_local 6 (i64.const 6))) 34 | (drop (tee_local 8 (f64.const 8))) 35 | (i64.trunc_s/f64 36 | (f64.add 37 | (f64.convert_u/i64 (get_local 0)) 38 | (f64.add 39 | (f64.promote/f32 (get_local 1)) 40 | (f64.add 41 | (get_local 2) 42 | (f64.add 43 | (f64.convert_u/i32 (get_local 3)) 44 | (f64.add 45 | (f64.convert_s/i32 (get_local 4)) 46 | (f64.add 47 | (f64.promote/f32 (get_local 5)) 48 | (f64.add 49 | (f64.convert_u/i64 (get_local 6)) 50 | (f64.add 51 | (f64.convert_u/i64 (get_local 7)) 52 | (get_local 8) 53 | ) 54 | ) 55 | ) 56 | ) 57 | ) 58 | ) 59 | ) 60 | ) 61 | ) 62 | ) 63 | 64 | ;; Result 65 | 66 | (func (export "result") (param i64 f32 f64 i32 i32) (result f64) 67 | (local f32 i64 i64 f64) 68 | (f64.add 69 | (f64.convert_u/i64 (tee_local 0 (i64.const 1))) 70 | (f64.add 71 | (f64.promote/f32 (tee_local 1 (f32.const 2))) 72 | (f64.add 73 | (tee_local 2 (f64.const 3.3)) 74 | (f64.add 75 | (f64.convert_u/i32 (tee_local 3 (i32.const 4))) 76 | (f64.add 77 | (f64.convert_s/i32 (tee_local 4 (i32.const 5))) 78 | (f64.add 79 | (f64.promote/f32 (tee_local 5 (f32.const 5.5))) 80 | (f64.add 81 | (f64.convert_u/i64 (tee_local 6 (i64.const 6))) 82 | (f64.add 83 | (f64.convert_u/i64 (tee_local 7 (i64.const 0))) 84 | (tee_local 8 (f64.const 8)) 85 | ) 86 | ) 87 | ) 88 | ) 89 | ) 90 | ) 91 | ) 92 | ) 93 | ) 94 | ) -------------------------------------------------------------------------------- /exec/testdata/spec/traps_int_div.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-interpreter/wagon/d9964a5b71752113f6c43d26d03028d0224f9ff8/exec/testdata/spec/traps_int_div.wasm -------------------------------------------------------------------------------- /exec/testdata/spec/traps_int_div.wast: -------------------------------------------------------------------------------- 1 | (module 2 | (func (export "no_dce.i32.div_s") (param $x i32) (param $y i32) 3 | (drop (i32.div_s (get_local $x) (get_local $y)))) 4 | (func (export "no_dce.i32.div_u") (param $x i32) (param $y i32) 5 | (drop (i32.div_u (get_local $x) (get_local $y)))) 6 | (func (export "no_dce.i64.div_s") (param $x i64) (param $y i64) 7 | (drop (i64.div_s (get_local $x) (get_local $y)))) 8 | (func (export "no_dce.i64.div_u") (param $x i64) (param $y i64) 9 | (drop (i64.div_u (get_local $x) (get_local $y)))) 10 | ) 11 | -------------------------------------------------------------------------------- /exec/testdata/spec/traps_int_rem.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-interpreter/wagon/d9964a5b71752113f6c43d26d03028d0224f9ff8/exec/testdata/spec/traps_int_rem.wasm -------------------------------------------------------------------------------- /exec/testdata/spec/traps_int_rem.wast: -------------------------------------------------------------------------------- 1 | (module 2 | (func (export "no_dce.i32.rem_s") (param $x i32) (param $y i32) 3 | (drop (i32.rem_s (get_local $x) (get_local $y)))) 4 | (func (export "no_dce.i32.rem_u") (param $x i32) (param $y i32) 5 | (drop (i32.rem_u (get_local $x) (get_local $y)))) 6 | (func (export "no_dce.i64.rem_s") (param $x i64) (param $y i64) 7 | (drop (i64.rem_s (get_local $x) (get_local $y)))) 8 | (func (export "no_dce.i64.rem_u") (param $x i64) (param $y i64) 9 | (drop (i64.rem_u (get_local $x) (get_local $y)))) 10 | ) -------------------------------------------------------------------------------- /exec/testdata/spec/traps_mem.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-interpreter/wagon/d9964a5b71752113f6c43d26d03028d0224f9ff8/exec/testdata/spec/traps_mem.wasm -------------------------------------------------------------------------------- /exec/testdata/spec/traps_mem.wast: -------------------------------------------------------------------------------- 1 | (module 2 | (memory 1) 3 | 4 | (func (export "no_dce.i32.load") (param $i i32) (drop (i32.load (get_local $i)))) 5 | (func (export "no_dce.i32.load16_s") (param $i i32) (drop (i32.load16_s (get_local $i)))) 6 | (func (export "no_dce.i32.load16_u") (param $i i32) (drop (i32.load16_u (get_local $i)))) 7 | (func (export "no_dce.i32.load8_s") (param $i i32) (drop (i32.load8_s (get_local $i)))) 8 | (func (export "no_dce.i32.load8_u") (param $i i32) (drop (i32.load8_u (get_local $i)))) 9 | (func (export "no_dce.i64.load") (param $i i32) (drop (i64.load (get_local $i)))) 10 | (func (export "no_dce.i64.load32_s") (param $i i32) (drop (i64.load32_s (get_local $i)))) 11 | (func (export "no_dce.i64.load32_u") (param $i i32) (drop (i64.load32_u (get_local $i)))) 12 | (func (export "no_dce.i64.load16_s") (param $i i32) (drop (i64.load16_s (get_local $i)))) 13 | (func (export "no_dce.i64.load16_u") (param $i i32) (drop (i64.load16_u (get_local $i)))) 14 | (func (export "no_dce.i64.load8_s") (param $i i32) (drop (i64.load8_s (get_local $i)))) 15 | (func (export "no_dce.i64.load8_u") (param $i i32) (drop (i64.load8_u (get_local $i)))) 16 | (func (export "no_dce.f32.load") (param $i i32) (drop (f32.load (get_local $i)))) 17 | (func (export "no_dce.f64.load") (param $i i32) (drop (f64.load (get_local $i)))) 18 | ) -------------------------------------------------------------------------------- /exec/testdata/spec/unreachable.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-interpreter/wagon/d9964a5b71752113f6c43d26d03028d0224f9ff8/exec/testdata/spec/unreachable.wasm -------------------------------------------------------------------------------- /exec/testdata/spec/unwind.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-interpreter/wagon/d9964a5b71752113f6c43d26d03028d0224f9ff8/exec/testdata/spec/unwind.wasm -------------------------------------------------------------------------------- /exec/testdata/start.wasm: -------------------------------------------------------------------------------- 1 | asm``get 2 |  AA*6 A( -------------------------------------------------------------------------------- /exec/testdata/store.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-interpreter/wagon/d9964a5b71752113f6c43d26d03028d0224f9ff8/exec/testdata/store.wasm -------------------------------------------------------------------------------- /exec/testdata/unary.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-interpreter/wagon/d9964a5b71752113f6c43d26d03028d0224f9ff8/exec/testdata/unary.wasm -------------------------------------------------------------------------------- /exec/var.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package exec 6 | 7 | func (vm *VM) getLocal() { 8 | index := vm.fetchUint32() 9 | vm.pushUint64(vm.ctx.locals[int(index)]) 10 | } 11 | 12 | func (vm *VM) setLocal() { 13 | index := vm.fetchUint32() 14 | vm.ctx.locals[int(index)] = vm.popUint64() 15 | } 16 | 17 | func (vm *VM) teeLocal() { 18 | index := vm.fetchUint32() 19 | val := vm.ctx.stack[len(vm.ctx.stack)-1] 20 | vm.ctx.locals[int(index)] = val 21 | } 22 | 23 | func (vm *VM) getGlobal() { 24 | index := vm.fetchUint32() 25 | vm.pushUint64(vm.globals[int(index)]) 26 | } 27 | 28 | func (vm *VM) setGlobal() { 29 | index := vm.fetchUint32() 30 | vm.globals[int(index)] = vm.popUint64() 31 | } 32 | -------------------------------------------------------------------------------- /exec/vm_debug_stack.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build debugstack 6 | 7 | package exec 8 | 9 | // debugStackDepth enables runtime checks of the stack depth. If 10 | // the stack every would exceed or underflow its expected bounds, 11 | // a panic is thrown. 12 | const debugStackDepth = true 13 | -------------------------------------------------------------------------------- /exec/vm_example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package exec_test 6 | 7 | import ( 8 | "bytes" 9 | "fmt" 10 | "io/ioutil" 11 | "log" 12 | "reflect" 13 | 14 | "github.com/go-interpreter/wagon/exec" 15 | "github.com/go-interpreter/wagon/wasm" 16 | ) 17 | 18 | func ExampleVM_add() { 19 | raw, err := compileWast2Wasm("testdata/add-ex-main.wast") 20 | if err != nil { 21 | log.Fatalf("could not compile wast file: %v", err) 22 | } 23 | 24 | m, err := wasm.ReadModule(bytes.NewReader(raw), func(name string) (*wasm.Module, error) { 25 | // ReadModule takes as a second argument an optional "importer" function 26 | // that is supposed to locate and import other modules when some module is 27 | // requested (by name.) 28 | // Theoretically, a general "importer" function not unlike the Python's 'import' 29 | // mechanism (that tries to locate and import modules from a $PYTHONPATH) 30 | // could be devised. 31 | switch name { 32 | case "add": 33 | raw, err := compileWast2Wasm("testdata/add-ex.wast") 34 | if err != nil { 35 | return nil, fmt.Errorf("could not compile wast file hosting %q: %v", name, err) 36 | } 37 | 38 | add, err := wasm.ReadModule(bytes.NewReader(raw), nil) 39 | if err != nil { 40 | return nil, fmt.Errorf("could not read wasm %q module: %v", name, err) 41 | } 42 | return add, nil 43 | case "go": 44 | // create a whole new module, called "go", from scratch. 45 | // this module will contain one exported function "print", 46 | // implemented itself in pure Go. 47 | print := func(proc *exec.Process, v int32) { 48 | fmt.Printf("result = %v\n", v) 49 | } 50 | 51 | m := wasm.NewModule() 52 | m.Types = &wasm.SectionTypes{ 53 | Entries: []wasm.FunctionSig{ 54 | { 55 | Form: 0, // value for the 'func' type constructor 56 | ParamTypes: []wasm.ValueType{wasm.ValueTypeI32}, 57 | }, 58 | }, 59 | } 60 | m.FunctionIndexSpace = []wasm.Function{ 61 | { 62 | Sig: &m.Types.Entries[0], 63 | Host: reflect.ValueOf(print), 64 | Body: &wasm.FunctionBody{}, // create a dummy wasm body (the actual value will be taken from Host.) 65 | }, 66 | } 67 | m.Export = &wasm.SectionExports{ 68 | Entries: map[string]wasm.ExportEntry{ 69 | "print": { 70 | FieldStr: "print", 71 | Kind: wasm.ExternalFunction, 72 | Index: 0, 73 | }, 74 | }, 75 | } 76 | 77 | return m, nil 78 | } 79 | return nil, fmt.Errorf("module %q unknown", name) 80 | }) 81 | if err != nil { 82 | log.Fatalf("could not read module: %v", err) 83 | } 84 | 85 | vm, err := exec.NewVM(m) 86 | if err != nil { 87 | log.Fatalf("could not create wagon vm: %v", err) 88 | } 89 | 90 | const fct1 = 2 // index of function fct1 91 | out, err := vm.ExecCode(fct1) 92 | if err != nil { 93 | log.Fatalf("could not execute fct1(): %v", err) 94 | } 95 | fmt.Printf("fct1() -> %v\n", out) 96 | 97 | const fct2 = 3 // index of function fct2 98 | out, err = vm.ExecCode(fct2, 40, 6) 99 | if err != nil { 100 | log.Fatalf("could not execute fct2(40, 6): %v", err) 101 | } 102 | fmt.Printf("fct2() -> %v\n", out) 103 | 104 | const fct3 = 4 // index of function fct3 105 | out, err = vm.ExecCode(fct3, 42, 42) 106 | if err != nil { 107 | log.Fatalf("could not execute fct3(42, 42): %v", err) 108 | } 109 | fmt.Printf("fct3() -> %v\n", out) 110 | 111 | // Output: 112 | // fct1() -> 42 113 | // fct2() -> 46 114 | // result = 84 115 | // fct3() -> 116 | } 117 | 118 | // compileWast2Wasm fakes a compilation pass from WAST to WASM. 119 | // 120 | // When wagon gets a WAST parser, this function will be running an actual compilation. 121 | // See: https://github.com/go-interpreter/wagon/issues/34 122 | func compileWast2Wasm(fname string) ([]byte, error) { 123 | switch fname { 124 | case "testdata/add-ex.wast": 125 | // obtained by running: 126 | // $> wat2wasm -v -o add-ex.wasm add-ex.wast 127 | return ioutil.ReadFile("testdata/add-ex.wasm") 128 | case "testdata/add-ex-main.wast": 129 | // obtained by running: 130 | // $> wat2wasm -v -o add-ex-main.wasm add-ex-main.wast 131 | return ioutil.ReadFile("testdata/add-ex-main.wasm") 132 | } 133 | return nil, fmt.Errorf("unknown wast test file %q", fname) 134 | } 135 | -------------------------------------------------------------------------------- /exec/vm_stack.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build !debugstack 6 | 7 | package exec 8 | 9 | // debugStackDepth enables runtime checks of the stack depth. If 10 | // the stack every would exceed or underflow its expected bounds, 11 | // a panic is thrown. 12 | const debugStackDepth = false 13 | -------------------------------------------------------------------------------- /exec/vm_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package exec 6 | 7 | import ( 8 | "testing" 9 | ) 10 | 11 | var ( 12 | smallMemoryVM = &VM{memory: []byte{1, 2, 3}} 13 | emptyMemoryVM = &VM{memory: []byte{}} 14 | smallMemoryProcess = &Process{vm: smallMemoryVM} 15 | emptyMemoryProcess = &Process{vm: emptyMemoryVM} 16 | tooBigABuffer = []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0} 17 | ) 18 | 19 | func TestNormalWrite(t *testing.T) { 20 | vm := &VM{memory: make([]byte, 300)} 21 | proc := &Process{vm: vm} 22 | n, err := proc.WriteAt(tooBigABuffer, 0) 23 | if err != nil { 24 | t.Fatalf("Found an error when writing: %v", err) 25 | } 26 | if n != len(tooBigABuffer) { 27 | t.Fatalf("Number of written bytes was %d, should have been %d", n, len(tooBigABuffer)) 28 | } 29 | } 30 | 31 | func TestWriteBoundary(t *testing.T) { 32 | n, err := smallMemoryProcess.WriteAt(tooBigABuffer, 0) 33 | if err == nil { 34 | t.Fatal("Should have reported an error and didn't") 35 | } 36 | if n != len(smallMemoryVM.memory) { 37 | t.Fatalf("Number of written bytes was %d, should have been 0", n) 38 | } 39 | } 40 | 41 | func TestReadBoundary(t *testing.T) { 42 | buf := make([]byte, 300) 43 | n, err := smallMemoryProcess.ReadAt(buf, 0) 44 | if err == nil { 45 | t.Fatal("Should have reported an error and didn't") 46 | } 47 | if n != len(smallMemoryVM.memory) { 48 | t.Fatalf("Number of written bytes was %d, should have been 0", n) 49 | } 50 | } 51 | 52 | func TestReadEmpty(t *testing.T) { 53 | buf := make([]byte, 300) 54 | n, err := emptyMemoryProcess.ReadAt(buf, 0) 55 | if err == nil { 56 | t.Fatal("Should have reported an error and didn't") 57 | } 58 | if n != 0 { 59 | t.Fatalf("Number of written bytes was %d, should have been 0", n) 60 | } 61 | } 62 | 63 | func TestReadOffset(t *testing.T) { 64 | buf0 := make([]byte, 2) 65 | n0, err := smallMemoryProcess.ReadAt(buf0, 0) 66 | if err != nil { 67 | t.Fatalf("Error reading 1-byte buffer: %v", err) 68 | } 69 | if n0 != 2 { 70 | t.Fatalf("Read %d bytes, expected 2", n0) 71 | } 72 | 73 | buf1 := make([]byte, 1) 74 | n1, err := smallMemoryProcess.ReadAt(buf1, 1) 75 | if err != nil { 76 | t.Fatalf("Error reading 1-byte buffer: %v", err) 77 | } 78 | if n1 != 1 { 79 | t.Fatalf("Read %d bytes, expected 1.", n0) 80 | } 81 | 82 | if buf0[1] != buf1[0] { 83 | t.Fatal("Read two different bytes from what should be the same location") 84 | } 85 | } 86 | 87 | func TestWriteEmpty(t *testing.T) { 88 | n, err := emptyMemoryProcess.WriteAt(tooBigABuffer, 0) 89 | if err == nil { 90 | t.Fatal("Should have reported an error and didn't") 91 | } 92 | if n != 0 { 93 | t.Fatalf("Number of written bytes was %d, should have been 0", n) 94 | } 95 | } 96 | 97 | func TestWriteOffset(t *testing.T) { 98 | vm := &VM{memory: make([]byte, 300)} 99 | proc := &Process{vm: vm} 100 | 101 | n, err := proc.WriteAt(tooBigABuffer, 2) 102 | if err != nil { 103 | t.Fatalf("error writing to buffer: %v", err) 104 | } 105 | if n != len(tooBigABuffer) { 106 | t.Fatalf("Number of written bytes was %d, should have been %d", n, len(tooBigABuffer)) 107 | } 108 | 109 | if vm.memory[0] != 0 || vm.memory[1] != 0 || vm.memory[2] != tooBigABuffer[0] { 110 | t.Fatal("Writing at offset didn't work") 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/go-interpreter/wagon 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/edsrzf/mmap-go v1.0.0 7 | github.com/twitchyliquid64/golang-asm v0.0.0-20190126203739-365674df15fc 8 | golang.org/x/sys v0.0.0-20190306220234-b354f8bf4d9e // indirect 9 | ) 10 | 11 | // users in some countries cannot access golang.org. 12 | replace golang.org/x/sys => github.com/golang/sys v0.0.0-20190306220234-b354f8bf4d9e 13 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= 2 | github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= 3 | github.com/golang/sys v0.0.0-20190306220234-b354f8bf4d9e h1:Yv9Nf3qPhnbsludlsbTa1z6lOpYbJxHKoh5lXyqfP3I= 4 | github.com/golang/sys v0.0.0-20190306220234-b354f8bf4d9e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 5 | github.com/twitchyliquid64/golang-asm v0.0.0-20190126203739-365674df15fc h1:RTUQlKzoZZVG3umWNzOYeFecQLIh+dbxXvJp1zPQJTI= 6 | github.com/twitchyliquid64/golang-asm v0.0.0-20190126203739-365674df15fc/go.mod h1:NoCfSFWosfqMqmmD7hApkirIK9ozpHjxRnRxs1l413A= 7 | -------------------------------------------------------------------------------- /internal/stack/stack.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package stack implements a growable uint64 stack 6 | package stack 7 | 8 | type Stack struct { 9 | slice []uint64 10 | } 11 | 12 | func (s *Stack) Push(b uint64) { 13 | s.slice = append(s.slice, b) 14 | } 15 | 16 | func (s *Stack) Pop() uint64 { 17 | v := s.Top() 18 | s.slice = s.slice[:len(s.slice)-1] 19 | return v 20 | } 21 | 22 | func (s *Stack) SetTop(v uint64) { 23 | s.slice[len(s.slice)-1] = v 24 | } 25 | 26 | func (s *Stack) Top() uint64 { 27 | return s.slice[len(s.slice)-1] 28 | } 29 | 30 | func (s *Stack) Get(i int) uint64 { 31 | return s.slice[i] 32 | } 33 | 34 | func (s *Stack) Set(i int, v uint64) { 35 | s.slice[i] = v 36 | } 37 | 38 | func (s *Stack) Len() int { 39 | return len(s.slice) 40 | } 41 | -------------------------------------------------------------------------------- /tests/run_testcase.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -ex 4 | rm -rf temp 5 | mkdir temp 6 | cd temp 7 | 8 | if ! which wast2json ; then 9 | wget https://github.com/WebAssembly/wabt/releases/download/1.0.13/wabt-1.0.13-linux.tar.gz 10 | tar -xzvf wabt-1.0.13-linux.tar.gz 11 | WAST2JSON=wabt-1.0.13/wast2json 12 | else 13 | WAST2JSON=wast2json 14 | fi 15 | 16 | 17 | go build $TAGS -o spec_test ../spec_test_runner.go 18 | 19 | for file in ../spectestcase/*.wast ; do 20 | ${WAST2JSON} ${file} 21 | done 22 | 23 | for json in *.json ; do 24 | ./spec_test ${json} 25 | done 26 | -------------------------------------------------------------------------------- /tests/spec_test_runner.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package main 5 | 6 | import ( 7 | "bytes" 8 | "encoding/json" 9 | "fmt" 10 | "io/ioutil" 11 | "log" 12 | "os" 13 | "path" 14 | "path/filepath" 15 | 16 | "github.com/go-interpreter/wagon/exec" 17 | "github.com/go-interpreter/wagon/wasm" 18 | ) 19 | 20 | // this file is based on github.com/perlin-network/life/spec/test_runner/runner.go 21 | 22 | type Config struct { 23 | SourceFilename string `json:"source_filename"` 24 | Commands []Command `json:"commands"` 25 | } 26 | 27 | type Command struct { 28 | Type string `json:"type"` 29 | Line int `json:"line"` 30 | Filename string `json:"filename"` 31 | Name string `json:"name"` 32 | Action CmdAction `json:"action"` 33 | Text string `json:"text"` 34 | ModuleType string `json:"module_type"` 35 | Expected []ValueInfo `json:"expected"` 36 | } 37 | 38 | type CmdAction struct { 39 | Type string `json:"type"` 40 | Module string `json:"module"` 41 | Field string `json:"field"` 42 | Args []ValueInfo `json:"args"` 43 | Expected []ValueInfo `json:"expected"` 44 | } 45 | 46 | type ValueInfo struct { 47 | Type string `json:"type"` 48 | Value string `json:"value"` 49 | } 50 | 51 | func LoadConfigFromFile(filename string) *Config { 52 | raw, err := ioutil.ReadFile(filename) 53 | if err != nil { 54 | panic(err) 55 | } 56 | var cfg Config 57 | err = json.Unmarshal(raw, &cfg) 58 | if err != nil { 59 | panic(err) 60 | } 61 | return &cfg 62 | } 63 | 64 | func (c *Config) Run(cfgPath string) { 65 | var vm *exec.VM 66 | namedVMs := make(map[string]*exec.VM) 67 | 68 | dir, _ := filepath.Split(cfgPath) 69 | 70 | for _, cmd := range c.Commands { 71 | switch cmd.Type { 72 | case "module": 73 | input, err := ioutil.ReadFile(path.Join(dir, cmd.Filename)) 74 | if err != nil { 75 | panic(err) 76 | } 77 | m, err := wasm.ReadModule(bytes.NewBuffer(input), nil) 78 | if err != nil { 79 | log.Fatalf("could not read module: %v", err) 80 | } 81 | 82 | vm, err = exec.NewVM(m) 83 | if err != nil { 84 | panic(fmt.Errorf("l%d: %s, could not create VM: %v", cmd.Line, cfgPath, err)) 85 | } 86 | vm.RecoverPanic = true 87 | if cmd.Name != "" { 88 | namedVMs[cmd.Name] = vm 89 | } 90 | case "assert_return", "action": 91 | localVM := vm 92 | if cmd.Action.Module != "" { 93 | if target, ok := namedVMs[cmd.Action.Module]; ok { 94 | localVM = target 95 | } else { 96 | panic("named module not found") 97 | } 98 | } 99 | if localVM == nil { 100 | panic("module not found") 101 | } 102 | 103 | switch cmd.Action.Type { 104 | case "invoke": 105 | entryID, ok := localVM.GetExportEntry(cmd.Action.Field) 106 | if !ok { 107 | panic("export not found (func)") 108 | } 109 | args := make([]uint64, 0) 110 | for _, arg := range cmd.Action.Args { 111 | var val uint64 112 | fmt.Sscanf(arg.Value, "%d", &val) 113 | args = append(args, val) 114 | } 115 | ret, err := localVM.ExecCode(int64(entryID.Index), args...) 116 | if err != nil { 117 | panic(err) 118 | } 119 | if len(cmd.Expected) != 0 { 120 | var _exp uint64 121 | fmt.Sscanf(cmd.Expected[0].Value, "%d", &_exp) 122 | exp := int64(_exp) 123 | var result int64 124 | if cmd.Expected[0].Type == "i32" || cmd.Expected[0].Type == "f32" { 125 | result = int64(ret.(uint32)) 126 | exp = int64(uint32(exp)) 127 | } else { 128 | result = int64(ret.(uint64)) 129 | } 130 | if result != exp { 131 | panic(fmt.Errorf("l%d: %s, ret mismatch: got %d, expected %d", cmd.Line, cfgPath, result, exp)) 132 | } 133 | } 134 | case "get": 135 | val, ok := localVM.GetGlobal(cmd.Action.Field) 136 | if !ok { 137 | panic("export not found (global)") 138 | } 139 | var exp uint64 140 | fmt.Sscanf(cmd.Expected[0].Value, "%d", &exp) 141 | if cmd.Expected[0].Type == "i32" || cmd.Expected[0].Type == "f32" { 142 | val = uint64(uint32(val)) 143 | exp = uint64(uint32(exp)) 144 | } 145 | if val != exp { 146 | panic(fmt.Errorf("val mismatch: got %d, expected %d\n", val, exp)) 147 | } 148 | default: 149 | panic(cmd.Action.Type) 150 | } 151 | case "assert_trap": 152 | localVM := vm 153 | if cmd.Action.Module != "" { 154 | if target, ok := namedVMs[cmd.Action.Module]; ok { 155 | localVM = target 156 | } else { 157 | panic("named module not found") 158 | } 159 | } 160 | if localVM == nil { 161 | panic("module not found") 162 | } 163 | switch cmd.Action.Type { 164 | case "invoke": 165 | entryID, ok := localVM.GetExportEntry(cmd.Action.Field) 166 | if !ok { 167 | panic("export not found (func)") 168 | } 169 | args := make([]uint64, 0) 170 | for _, arg := range cmd.Action.Args { 171 | var val uint64 172 | fmt.Sscanf(arg.Value, "%d", &val) 173 | args = append(args, val) 174 | } 175 | _, err := localVM.ExecCode(int64(entryID.Index), args...) 176 | if err == nil { 177 | panic(fmt.Errorf("L%d: %s, expect a trap\n", cmd.Line, cfgPath)) 178 | } 179 | default: 180 | panic(cmd.Action.Type) 181 | } 182 | 183 | case "assert_malformed", "assert_invalid", "assert_exhaustion", "assert_unlinkable", 184 | "assert_return_canonical_nan", "assert_return_arithmetic_nan": 185 | fmt.Printf("skipping %s\n", cmd.Type) 186 | default: 187 | panic(cmd.Type) 188 | } 189 | fmt.Printf("PASS L%d: %s\n", cmd.Line, cfgPath) 190 | } 191 | } 192 | 193 | func main() { 194 | cfg := LoadConfigFromFile(os.Args[1]) 195 | cfg.Run(os.Args[1]) 196 | } 197 | -------------------------------------------------------------------------------- /validate/error.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package validate 6 | 7 | import ( 8 | "errors" 9 | "fmt" 10 | 11 | "github.com/go-interpreter/wagon/wasm" 12 | ops "github.com/go-interpreter/wagon/wasm/operators" 13 | ) 14 | 15 | // Error wraps validation errors with information about where the error 16 | // was encountered. 17 | type Error struct { 18 | Offset int // Byte offset in the bytecode vector where the error occurs. 19 | Function int // Index into the function index space for the offending function. 20 | Err error 21 | } 22 | 23 | func (e Error) Error() string { 24 | return fmt.Sprintf("error while validating function %d at offset %d: %v", e.Function, e.Offset, e.Err) 25 | } 26 | 27 | // ErrStackUnderflow is returned if an instruction consumes a value, but there 28 | // are no values on the stack. 29 | var ErrStackUnderflow = errors.New("validate: stack underflow") 30 | 31 | // InvalidImmediateError is returned if the immediate value provided 32 | // is invalid for the given instruction. 33 | type InvalidImmediateError struct { 34 | ImmType string 35 | OpName string 36 | } 37 | 38 | func (e InvalidImmediateError) Error() string { 39 | return fmt.Sprintf("invalid immediate for op %s (should be %s)", e.OpName, e.ImmType) 40 | } 41 | 42 | // UnmatchedOpError is returned if a block does not have a corresponding 43 | // end instruction, or if an else instruction is encountered outside of 44 | // an if block. 45 | type UnmatchedOpError byte 46 | 47 | func (e UnmatchedOpError) Error() string { 48 | n1, _ := ops.New(byte(e)) 49 | return fmt.Sprintf("encountered unmatched %s", n1.Name) 50 | } 51 | 52 | // InvalidTypeError is returned if a branch is encountered which points to 53 | // a block that does not exist. 54 | type InvalidLabelError uint32 55 | 56 | func (e InvalidLabelError) Error() string { 57 | return fmt.Sprintf("invalid nesting depth %d", uint32(e)) 58 | } 59 | 60 | // UnmatchedIfValueErr is returned if an if block returns a value, but 61 | // no else block is present. 62 | type UnmatchedIfValueErr wasm.ValueType 63 | 64 | func (e UnmatchedIfValueErr) Error() string { 65 | return fmt.Sprintf("if block returns value of type %v but no else present", wasm.ValueType(e)) 66 | } 67 | 68 | // InvalidTableIndexError is returned if a table is referenced with an 69 | // out-of-bounds index. 70 | type InvalidTableIndexError struct { 71 | table string 72 | index uint32 73 | } 74 | 75 | func (e InvalidTableIndexError) Error() string { 76 | return fmt.Sprintf("invalid index %d for %s", e.index, e.table) 77 | } 78 | 79 | // InvalidLocalIndexError is returned if a local variable index is referenced 80 | // which does not exist. 81 | type InvalidLocalIndexError uint32 82 | 83 | func (e InvalidLocalIndexError) Error() string { 84 | return fmt.Sprintf("invalid index for local variable %d", uint32(e)) 85 | } 86 | 87 | // InvalidTypeError is returned if there is a mismatch between the type(s) 88 | // an operator or function accepts, and the value provided. 89 | type InvalidTypeError struct { 90 | Wanted wasm.ValueType 91 | Got wasm.ValueType 92 | } 93 | 94 | func valueTypeStr(v wasm.ValueType) string { 95 | switch v { 96 | case noReturn: 97 | return "void" 98 | case unknownType: 99 | return "anytype" 100 | default: 101 | return v.String() 102 | } 103 | } 104 | 105 | func (e InvalidTypeError) Error() string { 106 | return fmt.Sprintf("invalid type, got: %v, wanted: %v", valueTypeStr(e.Got), valueTypeStr(e.Wanted)) 107 | } 108 | 109 | // NoSectionError is returned if a section does not exist. 110 | type NoSectionError wasm.SectionID 111 | 112 | func (e NoSectionError) Error() string { 113 | return fmt.Sprintf("reference to non existant section (id %d) in module", wasm.SectionID(e)) 114 | } 115 | 116 | // UnbalancedStackErr is returned if there are too many items on the stack 117 | // than is valid for the current block or function. 118 | type UnbalancedStackErr wasm.ValueType 119 | 120 | func (e UnbalancedStackErr) Error() string { 121 | return fmt.Sprintf("unbalanced stack (top of stack is %s)", valueTypeStr(wasm.ValueType(e))) 122 | } 123 | -------------------------------------------------------------------------------- /validate/log.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package validate 6 | 7 | import ( 8 | "io/ioutil" 9 | "log" 10 | "os" 11 | ) 12 | 13 | var PrintDebugInfo = false 14 | 15 | var logger *log.Logger 16 | 17 | func init() { 18 | w := ioutil.Discard 19 | 20 | if PrintDebugInfo { 21 | w = os.Stderr 22 | } 23 | 24 | logger = log.New(w, "", log.Lshortfile) 25 | log.SetFlags(log.Lshortfile) 26 | } 27 | -------------------------------------------------------------------------------- /validate/operand.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package validate 6 | 7 | import ( 8 | "github.com/go-interpreter/wagon/wasm" 9 | ) 10 | 11 | const ( 12 | noReturn = wasm.ValueType(wasm.BlockTypeEmpty) 13 | unknownType = wasm.ValueType(0) 14 | ) 15 | 16 | type operand struct { 17 | Type wasm.ValueType 18 | } 19 | 20 | // Equal returns true if the operand and given type are equivalent 21 | // for typechecking purposes. 22 | func (p operand) Equal(t wasm.ValueType) bool { 23 | if p.Type == unknownType || t == unknownType { 24 | return true 25 | } 26 | return p.Type == t 27 | } 28 | -------------------------------------------------------------------------------- /wagon_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package wagon 6 | 7 | import ( 8 | "bufio" 9 | "bytes" 10 | "os/exec" 11 | "testing" 12 | ) 13 | 14 | func TestGovet(t *testing.T) { 15 | buf := new(bytes.Buffer) 16 | cmd := exec.Command("go", "list", "./...") 17 | cmd.Stdout = buf 18 | cmd.Stderr = buf 19 | err := cmd.Run() 20 | if err != nil { 21 | t.Fatalf("error getting package list: %v\n%s", err, string(buf.Bytes())) 22 | } 23 | var pkgs []string 24 | s := bufio.NewScanner(buf) 25 | for s.Scan() { 26 | pkgs = append(pkgs, s.Text()) 27 | } 28 | if err = s.Err(); err != nil { 29 | t.Fatalf("error parsing package list: %v", err) 30 | } 31 | 32 | cmd = exec.Command("go", append([]string{"vet"}, pkgs...)...) 33 | buf = new(bytes.Buffer) 34 | cmd.Stdout = buf 35 | cmd.Stderr = buf 36 | err = cmd.Run() 37 | if err != nil { 38 | t.Fatalf("error running %s:\n%s\n%v", "go vet", string(buf.Bytes()), err) 39 | } 40 | } 41 | 42 | func TestGofmt(t *testing.T) { 43 | exe, err := exec.LookPath("goimports") 44 | if err != nil { 45 | switch e := err.(type) { 46 | case *exec.Error: 47 | if e.Err == exec.ErrNotFound { 48 | exe, err = exec.LookPath("gofmt") 49 | } 50 | } 51 | } 52 | if err != nil { 53 | t.Fatal(err) 54 | } 55 | 56 | cmd := exec.Command(exe, "-d", ".") 57 | buf := new(bytes.Buffer) 58 | cmd.Stdout = buf 59 | cmd.Stderr = buf 60 | 61 | err = cmd.Run() 62 | if err != nil { 63 | t.Fatalf("error running %s:\n%s\n%v", exe, string(buf.Bytes()), err) 64 | } 65 | 66 | if len(buf.Bytes()) != 0 { 67 | t.Errorf("some files were not gofmt'ed:\n%s\n", string(buf.Bytes())) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /wasm/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package wasm provides functions for reading and parsing WebAssembly modules. 6 | package wasm 7 | -------------------------------------------------------------------------------- /wasm/encode.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package wasm 6 | 7 | import ( 8 | "bytes" 9 | "encoding/binary" 10 | "io" 11 | 12 | "github.com/go-interpreter/wagon/wasm/leb128" 13 | ) 14 | 15 | const currentVersion = 0x01 16 | 17 | // EncodeModule writes a provided module to w using WASM binary encoding. 18 | func EncodeModule(w io.Writer, m *Module) error { 19 | if err := writeU32(w, Magic); err != nil { 20 | return err 21 | } 22 | if err := writeU32(w, currentVersion); err != nil { 23 | return err 24 | } 25 | sections := m.Sections 26 | buf := new(bytes.Buffer) 27 | for _, s := range sections { 28 | if _, err := leb128.WriteVarUint32(w, uint32(s.SectionID())); err != nil { 29 | return err 30 | } 31 | buf.Reset() 32 | if err := s.WritePayload(buf); err != nil { 33 | return err 34 | } 35 | if _, err := leb128.WriteVarUint32(w, uint32(buf.Len())); err != nil { 36 | return err 37 | } 38 | if _, err := buf.WriteTo(w); err != nil { 39 | return err 40 | } 41 | } 42 | return nil 43 | } 44 | 45 | func writeStringUint(w io.Writer, s string) error { 46 | return writeBytesUint(w, []byte(s)) 47 | } 48 | 49 | func writeBytesUint(w io.Writer, p []byte) error { 50 | _, err := leb128.WriteVarUint32(w, uint32(len(p))) 51 | if err != nil { 52 | return err 53 | } 54 | _, err = w.Write(p) 55 | return err 56 | } 57 | 58 | func writeU32(w io.Writer, n uint32) error { 59 | var buf [4]byte 60 | binary.LittleEndian.PutUint32(buf[:], n) 61 | _, err := w.Write(buf[:]) 62 | return err 63 | } 64 | 65 | func writeU64(w io.Writer, n uint64) error { 66 | var buf [8]byte 67 | binary.LittleEndian.PutUint64(buf[:], n) 68 | _, err := w.Write(buf[:]) 69 | return err 70 | } 71 | -------------------------------------------------------------------------------- /wasm/encode_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package wasm_test 6 | 7 | import ( 8 | "bytes" 9 | "io/ioutil" 10 | "os" 11 | "path" 12 | "path/filepath" 13 | "testing" 14 | 15 | "github.com/go-interpreter/wagon/wasm" 16 | ) 17 | 18 | var skipModuleFilenames = map[string]bool{ 19 | // Contains debug information section of type Custom. Custom types 20 | // can be inserted in any order in the sequence, hence the encodings 21 | // mismatch is valid. 22 | "rust-basic.wasm": true, 23 | } 24 | 25 | func TestEncode(t *testing.T) { 26 | for _, dir := range testPaths { 27 | fnames, err := filepath.Glob(filepath.Join(dir, "*.wasm")) 28 | if err != nil { 29 | t.Fatal(err) 30 | } 31 | for _, fname := range fnames { 32 | if skipModuleFilenames[path.Base(fname)] { 33 | continue 34 | } 35 | 36 | name := fname 37 | t.Run(filepath.Base(name), func(t *testing.T) { 38 | raw, err := ioutil.ReadFile(name) 39 | if err != nil { 40 | t.Fatal(err) 41 | } 42 | 43 | r := bytes.NewReader(raw) 44 | m, err := wasm.DecodeModule(r) 45 | if err != nil { 46 | t.Fatalf("error reading module %v", err) 47 | } 48 | buf := new(bytes.Buffer) 49 | err = wasm.EncodeModule(buf, m) 50 | if err != nil { 51 | t.Fatalf("error writing module %v", err) 52 | } 53 | if !bytes.Equal(buf.Bytes(), raw) { 54 | ioutil.WriteFile(name+"_got", buf.Bytes(), 0644) 55 | t.Fatal("modules are different") 56 | } else { 57 | os.Remove(name + "_got") 58 | } 59 | }) 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /wasm/init_expr.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package wasm 6 | 7 | import ( 8 | "bytes" 9 | "errors" 10 | "fmt" 11 | "io" 12 | "math" 13 | 14 | "github.com/go-interpreter/wagon/wasm/leb128" 15 | ) 16 | 17 | const ( 18 | i32Const byte = 0x41 19 | i64Const byte = 0x42 20 | f32Const byte = 0x43 21 | f64Const byte = 0x44 22 | getGlobal byte = 0x23 23 | end byte = 0x0b 24 | ) 25 | 26 | var ErrEmptyInitExpr = errors.New("wasm: Initializer expression produces no value") 27 | 28 | type InvalidInitExprOpError byte 29 | 30 | func (e InvalidInitExprOpError) Error() string { 31 | return fmt.Sprintf("wasm: Invalid opcode in initializer expression: %#x", byte(e)) 32 | } 33 | 34 | type InvalidGlobalIndexError uint32 35 | 36 | func (e InvalidGlobalIndexError) Error() string { 37 | return fmt.Sprintf("wasm: Invalid index to global index space: %#x", uint32(e)) 38 | } 39 | 40 | func readInitExpr(r io.Reader) ([]byte, error) { 41 | b := make([]byte, 1) 42 | buf := new(bytes.Buffer) 43 | r = io.TeeReader(r, buf) 44 | 45 | outer: 46 | for { 47 | _, err := io.ReadFull(r, b) 48 | if err != nil { 49 | return nil, err 50 | } 51 | switch b[0] { 52 | case i32Const: 53 | _, err := leb128.ReadVarint32(r) 54 | if err != nil { 55 | return nil, err 56 | } 57 | case i64Const: 58 | _, err := leb128.ReadVarint64(r) 59 | if err != nil { 60 | return nil, err 61 | } 62 | case f32Const: 63 | if _, err := readU32(r); err != nil { 64 | return nil, err 65 | } 66 | case f64Const: 67 | if _, err := readU64(r); err != nil { 68 | return nil, err 69 | } 70 | case getGlobal: 71 | _, err := leb128.ReadVarUint32(r) 72 | if err != nil { 73 | return nil, err 74 | } 75 | case end: 76 | break outer 77 | default: 78 | return nil, InvalidInitExprOpError(b[0]) 79 | } 80 | } 81 | 82 | if buf.Len() == 0 { 83 | return nil, ErrEmptyInitExpr 84 | } 85 | 86 | return buf.Bytes(), nil 87 | } 88 | 89 | // ExecInitExpr executes an initializer expression and returns an interface{} value 90 | // which can either be int32, int64, float32 or float64. 91 | // It returns an error if the expression is invalid, and nil when the expression 92 | // yields no value. 93 | func (m *Module) ExecInitExpr(expr []byte) (interface{}, error) { 94 | var stack []uint64 95 | var lastVal ValueType 96 | r := bytes.NewReader(expr) 97 | 98 | if r.Len() == 0 { 99 | return nil, ErrEmptyInitExpr 100 | } 101 | 102 | for { 103 | b, err := r.ReadByte() 104 | if err == io.EOF { 105 | break 106 | } else if err != nil { 107 | return nil, err 108 | } 109 | switch b { 110 | case i32Const: 111 | i, err := leb128.ReadVarint32(r) 112 | if err != nil { 113 | return nil, err 114 | } 115 | stack = append(stack, uint64(i)) 116 | lastVal = ValueTypeI32 117 | case i64Const: 118 | i, err := leb128.ReadVarint64(r) 119 | if err != nil { 120 | return nil, err 121 | } 122 | stack = append(stack, uint64(i)) 123 | lastVal = ValueTypeI64 124 | case f32Const: 125 | i, err := readU32(r) 126 | if err != nil { 127 | return nil, err 128 | } 129 | stack = append(stack, uint64(i)) 130 | lastVal = ValueTypeF32 131 | case f64Const: 132 | i, err := readU64(r) 133 | if err != nil { 134 | return nil, err 135 | } 136 | stack = append(stack, i) 137 | lastVal = ValueTypeF64 138 | case getGlobal: 139 | index, err := leb128.ReadVarUint32(r) 140 | if err != nil { 141 | return nil, err 142 | } 143 | globalVar := m.GetGlobal(int(index)) 144 | if globalVar == nil { 145 | return nil, InvalidGlobalIndexError(index) 146 | } 147 | lastVal = globalVar.Type.Type 148 | case end: 149 | break 150 | default: 151 | return nil, InvalidInitExprOpError(b) 152 | } 153 | } 154 | 155 | if len(stack) == 0 { 156 | return nil, nil 157 | } 158 | 159 | v := stack[len(stack)-1] 160 | switch lastVal { 161 | case ValueTypeI32: 162 | return int32(v), nil 163 | case ValueTypeI64: 164 | return int64(v), nil 165 | case ValueTypeF32: 166 | return math.Float32frombits(uint32(v)), nil 167 | case ValueTypeF64: 168 | return math.Float64frombits(uint64(v)), nil 169 | default: 170 | panic(fmt.Sprintf("Invalid value type produced by initializer expression: %d", int8(lastVal))) 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /wasm/internal/readpos/readpos.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package readpos 6 | 7 | import ( 8 | "io" 9 | ) 10 | 11 | // ReadPos implements io.Reader and stores the current number of bytes read from 12 | // the reader 13 | type ReadPos struct { 14 | R io.Reader 15 | CurPos int64 16 | } 17 | 18 | // Read implements the io.Reader interface 19 | func (r *ReadPos) Read(p []byte) (int, error) { 20 | n, err := r.R.Read(p) 21 | r.CurPos += int64(n) 22 | return n, err 23 | } 24 | 25 | // ReadByte implements the io.ByteReader interface 26 | func (r *ReadPos) ReadByte() (byte, error) { 27 | p := make([]byte, 1) 28 | n, err := r.R.Read(p) 29 | r.CurPos += int64(n) 30 | return p[0], err 31 | } 32 | -------------------------------------------------------------------------------- /wasm/internal/readpos/readpos_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package readpos_test 6 | 7 | import ( 8 | "bytes" 9 | "io" 10 | "testing" 11 | 12 | "github.com/go-interpreter/wagon/wasm/internal/readpos" 13 | ) 14 | 15 | func TestRead(t *testing.T) { 16 | data := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} 17 | for i, test := range []struct { 18 | r io.Reader 19 | data []byte 20 | want int 21 | err error 22 | }{ 23 | { 24 | r: bytes.NewReader(data), 25 | data: nil, 26 | want: 0, 27 | err: nil, 28 | }, 29 | { 30 | r: bytes.NewReader(nil), 31 | data: nil, 32 | want: 0, 33 | err: io.EOF, 34 | }, 35 | { 36 | r: bytes.NewReader(nil), 37 | data: make([]byte, 2), 38 | want: 0, 39 | err: io.EOF, 40 | }, 41 | { 42 | r: bytes.NewReader(data), 43 | data: data, 44 | want: len(data), 45 | err: nil, 46 | }, 47 | { 48 | r: bytes.NewReader(data[:1]), 49 | data: make([]byte, 2), 50 | want: 1, 51 | err: nil, 52 | }, 53 | } { 54 | r := readpos.ReadPos{R: test.r} 55 | n, err := r.Read(test.data) 56 | switch { 57 | case err != test.err: 58 | t.Errorf("test-#%d: got err=%v. want=%v", i, err, test.err) 59 | continue 60 | case n != test.want: 61 | t.Errorf("test-#%d: got n=%v. want=%v", i, n, test.want) 62 | case int(r.CurPos) != test.want: 63 | t.Errorf("test-#%d: got pos=%v. want=%v", i, r.CurPos, test.want) 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /wasm/leb128/read.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package leb128 provides functions for reading integer values encoded in the 6 | // Little Endian Base 128 (LEB128) format: https://en.wikipedia.org/wiki/LEB128 7 | package leb128 8 | 9 | import ( 10 | "errors" 11 | "io" 12 | ) 13 | 14 | // readVarUint reads an unsigned integer of size n defined in https://webassembly.github.io/spec/core/binary/values.html#binary-int 15 | // readVarUint panics if n>64. 16 | func readVarUint(r io.Reader, n uint) (uint64, error) { 17 | if n > 64 { 18 | panic(errors.New("leb128: n must <= 64")) 19 | } 20 | p := make([]byte, 1) 21 | var res uint64 22 | var shift uint 23 | for { 24 | _, err := io.ReadFull(r, p) 25 | if err != nil { 26 | return 0, err 27 | } 28 | b := uint64(p[0]) 29 | switch { 30 | // note: can not use b < 1<= 1<<7 && n > 7: 35 | res += (1 << shift) * (b - 1<<7) 36 | shift += 7 37 | n -= 7 38 | default: 39 | return 0, errors.New("leb128: invalid uint") 40 | } 41 | } 42 | } 43 | 44 | // readVarint reads a signed integer of size n, defined in https://webassembly.github.io/spec/core/binary/values.html#binary-int 45 | // readVarint panics if n>64. 46 | func readVarint(r io.Reader, n uint) (int64, error) { 47 | if n > 64 { 48 | panic(errors.New("leb128: n must <= 64")) 49 | } 50 | p := make([]byte, 1) 51 | var res int64 52 | var shift uint 53 | for { 54 | _, err := io.ReadFull(r, p) 55 | if err != nil { 56 | return 0, err 57 | } 58 | b := int64(p[0]) 59 | switch { 60 | case b < 1<<6 && uint64(b) < uint64(1<<(n-1)): 61 | res += (1 << shift) * b 62 | return res, nil 63 | case b >= 1<<6 && b < 1<<7 && uint64(b)+1<<(n-1) >= 1<<7: 64 | res += (1 << shift) * (b - 1<<7) 65 | return res, nil 66 | case b >= 1<<7 && n > 7: 67 | res += (1 << shift) * (b - 1<<7) 68 | shift += 7 69 | n -= 7 70 | default: 71 | return 0, errors.New("leb128: invalid int") 72 | } 73 | } 74 | } 75 | 76 | // ReadVarUint32 reads a LEB128 encoded unsigned 32-bit integer from r, and 77 | // returns the integer value, and the error (if any). 78 | func ReadVarUint32(r io.Reader) (uint32, error) { 79 | n, err := readVarUint(r, 32) 80 | if err != nil { 81 | return 0, err 82 | } 83 | return uint32(n), nil 84 | } 85 | 86 | // ReadVarint32 reads a LEB128 encoded signed 32-bit integer from r, and 87 | // returns the integer value, and the error (if any). 88 | func ReadVarint32(r io.Reader) (int32, error) { 89 | n, err := readVarint(r, 32) 90 | if err != nil { 91 | return 0, err 92 | } 93 | 94 | return int32(n), nil 95 | } 96 | 97 | // ReadVarint64 reads a LEB128 encoded signed 64-bit integer from r, and 98 | // returns the integer value, and the error (if any). 99 | func ReadVarint64(r io.Reader) (int64, error) { 100 | return readVarint(r, 64) 101 | } 102 | 103 | // ReadVarUint64 reads a LEB128 encoded unsigned 64-bit integer from r, and 104 | // returns the integer value, and the error (if any). 105 | func ReadVarUint64(r io.Reader) (uint64, error) { 106 | return readVarUint(r, 64) 107 | } 108 | -------------------------------------------------------------------------------- /wasm/leb128/write.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package leb128 6 | 7 | import "io" 8 | 9 | // Copied from cmd/internal/dwarf/dwarf.go 10 | 11 | // AppendUleb128 appends v to b using unsigned LEB128 encoding. 12 | func AppendUleb128(b []byte, v uint64) []byte { 13 | for { 14 | c := uint8(v & 0x7f) 15 | v >>= 7 16 | if v != 0 { 17 | c |= 0x80 18 | } 19 | b = append(b, c) 20 | if c&0x80 == 0 { 21 | break 22 | } 23 | } 24 | return b 25 | } 26 | 27 | // AppendSleb128 appends v to b using signed LEB128 encoding. 28 | func AppendSleb128(b []byte, v int64) []byte { 29 | for { 30 | c := uint8(v & 0x7f) 31 | s := uint8(v & 0x40) 32 | v >>= 7 33 | if (v != -1 || s == 0) && (v != 0 || s != 0) { 34 | c |= 0x80 35 | } 36 | b = append(b, c) 37 | if c&0x80 == 0 { 38 | break 39 | } 40 | } 41 | return b 42 | } 43 | 44 | // WriteVarUint32 writes a LEB128 encoded unsigned 32-bit integer to w. 45 | // It returns the integer value, the size of the encoded value (in bytes), and 46 | // the error (if any). 47 | func WriteVarUint32(w io.Writer, cur uint32) (int, error) { 48 | var buf []byte 49 | buf = AppendUleb128(buf, uint64(cur)) 50 | return w.Write(buf) 51 | } 52 | 53 | // WriteVarint64 writes a LEB128 encoded signed 64-bit integer to w, and 54 | // returns the integer value, the size of the encoded value, and the error 55 | // (if any) 56 | func WriteVarint64(w io.Writer, cur int64) (int, error) { 57 | var buf []byte 58 | buf = AppendSleb128(buf, cur) 59 | return w.Write(buf) 60 | } 61 | -------------------------------------------------------------------------------- /wasm/leb128/write_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package leb128 6 | 7 | import ( 8 | "bytes" 9 | "fmt" 10 | "testing" 11 | ) 12 | 13 | func TestWriteVarUint32(t *testing.T) { 14 | for _, c := range casesUint { 15 | t.Run(fmt.Sprint(c.v), func(t *testing.T) { 16 | buf := new(bytes.Buffer) 17 | _, err := WriteVarUint32(buf, c.v) 18 | if err != nil { 19 | t.Fatal(err) 20 | } 21 | if !bytes.Equal(buf.Bytes(), c.b) { 22 | t.Fatalf("unexpected output: %x", buf.Bytes()) 23 | } 24 | }) 25 | } 26 | } 27 | 28 | func TestWriteVarint64(t *testing.T) { 29 | for _, c := range casesInt { 30 | t.Run(fmt.Sprint(c.v), func(t *testing.T) { 31 | buf := new(bytes.Buffer) 32 | _, err := WriteVarint64(buf, c.v) 33 | if err != nil { 34 | t.Fatal(err) 35 | } 36 | if !bytes.Equal(buf.Bytes(), c.b) { 37 | t.Fatalf("unexpected output: %x", buf.Bytes()) 38 | } 39 | }) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /wasm/log.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package wasm 6 | 7 | import ( 8 | "io/ioutil" 9 | "log" 10 | "os" 11 | ) 12 | 13 | var logger *log.Logger 14 | 15 | func init() { 16 | SetDebugMode(false) 17 | } 18 | 19 | func SetDebugMode(dbg bool) { 20 | w := ioutil.Discard 21 | if dbg { 22 | w = os.Stderr 23 | } 24 | logger = log.New(w, "", log.Lshortfile) 25 | } 26 | -------------------------------------------------------------------------------- /wasm/module.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package wasm 6 | 7 | import ( 8 | "errors" 9 | "fmt" 10 | "io" 11 | "reflect" 12 | 13 | "github.com/go-interpreter/wagon/wasm/internal/readpos" 14 | ) 15 | 16 | var ErrInvalidMagic = errors.New("wasm: Invalid magic number") 17 | 18 | const ( 19 | Magic uint32 = 0x6d736100 20 | Version uint32 = 0x1 21 | ) 22 | 23 | // Function represents an entry in the function index space of a module. 24 | type Function struct { 25 | Sig *FunctionSig 26 | Body *FunctionBody 27 | Host reflect.Value 28 | Name string 29 | } 30 | 31 | // IsHost indicates whether this function is a host function as defined in: 32 | // https://webassembly.github.io/spec/core/exec/modules.html#host-functions 33 | func (fct *Function) IsHost() bool { 34 | return fct.Host != reflect.Value{} 35 | } 36 | 37 | // Module represents a parsed WebAssembly module: 38 | // http://webassembly.org/docs/modules/ 39 | type Module struct { 40 | Version uint32 41 | Sections []Section 42 | 43 | Types *SectionTypes 44 | Import *SectionImports 45 | Function *SectionFunctions 46 | Table *SectionTables 47 | Memory *SectionMemories 48 | Global *SectionGlobals 49 | Export *SectionExports 50 | Start *SectionStartFunction 51 | Elements *SectionElements 52 | Code *SectionCode 53 | Data *SectionData 54 | Customs []*SectionCustom 55 | 56 | // The function index space of the module 57 | FunctionIndexSpace []Function 58 | GlobalIndexSpace []GlobalEntry 59 | 60 | // function indices into the global function space 61 | // the limit of each table is its capacity (cap) 62 | TableIndexSpace [][]TableEntry 63 | LinearMemoryIndexSpace [][]byte 64 | 65 | imports struct { 66 | Funcs []uint32 67 | Globals int 68 | Tables int 69 | Memories int 70 | } 71 | } 72 | 73 | // TableEntry represents a table index and tracks its initialized state. 74 | type TableEntry struct { 75 | Index uint32 76 | Initialized bool 77 | } 78 | 79 | // Custom returns a custom section with a specific name, if it exists. 80 | func (m *Module) Custom(name string) *SectionCustom { 81 | for _, s := range m.Customs { 82 | if s.Name == name { 83 | return s 84 | } 85 | } 86 | return nil 87 | } 88 | 89 | // NewModule creates a new empty module 90 | func NewModule() *Module { 91 | return &Module{ 92 | Types: &SectionTypes{}, 93 | Import: &SectionImports{}, 94 | Table: &SectionTables{}, 95 | Memory: &SectionMemories{}, 96 | Global: &SectionGlobals{}, 97 | Export: &SectionExports{}, 98 | Start: &SectionStartFunction{}, 99 | Elements: &SectionElements{}, 100 | Data: &SectionData{}, 101 | } 102 | } 103 | 104 | // ResolveFunc is a function that takes a module name and 105 | // returns a valid resolved module. 106 | type ResolveFunc func(name string) (*Module, error) 107 | 108 | // DecodeModule is the same as ReadModule, but it only decodes the module without 109 | // initializing the index space or resolving imports. 110 | func DecodeModule(r io.Reader) (*Module, error) { 111 | reader := &readpos.ReadPos{ 112 | R: r, 113 | CurPos: 0, 114 | } 115 | m := &Module{} 116 | magic, err := readU32(reader) 117 | if err != nil { 118 | return nil, err 119 | } 120 | if magic != Magic { 121 | return nil, ErrInvalidMagic 122 | } 123 | if m.Version, err = readU32(reader); err != nil { 124 | return nil, err 125 | } 126 | if m.Version != Version { 127 | return nil, fmt.Errorf("wasm: unknown binary version: %d", m.Version) 128 | } 129 | 130 | err = newSectionsReader(m).readSections(reader) 131 | if err != nil { 132 | return nil, err 133 | } 134 | 135 | return m, nil 136 | } 137 | 138 | // ReadModule reads a module from the reader r. resolvePath must take a string 139 | // and a return a reader to the module pointed to by the string. 140 | func ReadModule(r io.Reader, resolvePath ResolveFunc) (*Module, error) { 141 | m, err := DecodeModule(r) 142 | if err != nil { 143 | return nil, err 144 | } 145 | 146 | m.LinearMemoryIndexSpace = make([][]byte, 1) 147 | if m.Table != nil { 148 | m.TableIndexSpace = make([][]TableEntry, int(len(m.Table.Entries))) 149 | } 150 | 151 | if m.Import != nil && resolvePath != nil { 152 | if m.Code == nil { 153 | m.Code = &SectionCode{} 154 | } 155 | 156 | err := m.resolveImports(resolvePath) 157 | if err != nil { 158 | return nil, err 159 | } 160 | } 161 | 162 | for _, fn := range []func() error{ 163 | m.populateGlobals, 164 | m.populateFunctions, 165 | m.populateTables, 166 | m.populateLinearMemory, 167 | } { 168 | if err := fn(); err != nil { 169 | return nil, err 170 | } 171 | } 172 | 173 | logger.Printf("There are %d entries in the function index space.", len(m.FunctionIndexSpace)) 174 | return m, nil 175 | } 176 | -------------------------------------------------------------------------------- /wasm/operators/call.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package operators 6 | 7 | var ( 8 | Call = newPolymorphicOp(0x10, "call") 9 | CallIndirect = newPolymorphicOp(0x11, "call_indirect") 10 | ) 11 | -------------------------------------------------------------------------------- /wasm/operators/comp.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package operators 6 | 7 | import ( 8 | "github.com/go-interpreter/wagon/wasm" 9 | ) 10 | 11 | var ( 12 | I32Eqz = newOp(0x45, "i32.eqz", []wasm.ValueType{wasm.ValueTypeI32}, wasm.ValueTypeI32) 13 | I32Eq = newOp(0x46, "i32.eq", []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32}, wasm.ValueTypeI32) 14 | I32Ne = newOp(0x47, "i32.ne", []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32}, wasm.ValueTypeI32) 15 | I32LtS = newOp(0x48, "i32.lt_s", []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32}, wasm.ValueTypeI32) 16 | I32LtU = newOp(0x49, "i32.lt_u", []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32}, wasm.ValueTypeI32) 17 | I32GtS = newOp(0x4a, "i32.gt_s", []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32}, wasm.ValueTypeI32) 18 | I32GtU = newOp(0x4b, "i32.gt_u", []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32}, wasm.ValueTypeI32) 19 | I32LeS = newOp(0x4c, "i32.le_s", []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32}, wasm.ValueTypeI32) 20 | I32LeU = newOp(0x4d, "i32.le_u", []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32}, wasm.ValueTypeI32) 21 | I32GeS = newOp(0x4e, "i32.ge_s", []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32}, wasm.ValueTypeI32) 22 | I32GeU = newOp(0x4f, "i32.ge_u", []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32}, wasm.ValueTypeI32) 23 | I64Eqz = newOp(0x50, "i64.eqz", []wasm.ValueType{wasm.ValueTypeI64}, wasm.ValueTypeI32) 24 | I64Eq = newOp(0x51, "i64.eq", []wasm.ValueType{wasm.ValueTypeI64, wasm.ValueTypeI64}, wasm.ValueTypeI32) 25 | I64Ne = newOp(0x52, "i64.ne", []wasm.ValueType{wasm.ValueTypeI64, wasm.ValueTypeI64}, wasm.ValueTypeI32) 26 | I64LtS = newOp(0x53, "i64.lt_s", []wasm.ValueType{wasm.ValueTypeI64, wasm.ValueTypeI64}, wasm.ValueTypeI32) 27 | I64LtU = newOp(0x54, "i64.lt_u", []wasm.ValueType{wasm.ValueTypeI64, wasm.ValueTypeI64}, wasm.ValueTypeI32) 28 | I64GtS = newOp(0x55, "i64.gt_s", []wasm.ValueType{wasm.ValueTypeI64, wasm.ValueTypeI64}, wasm.ValueTypeI32) 29 | I64GtU = newOp(0x56, "i64.gt_u", []wasm.ValueType{wasm.ValueTypeI64, wasm.ValueTypeI64}, wasm.ValueTypeI32) 30 | I64LeS = newOp(0x57, "i64.le_s", []wasm.ValueType{wasm.ValueTypeI64, wasm.ValueTypeI64}, wasm.ValueTypeI32) 31 | I64LeU = newOp(0x58, "i64.le_u", []wasm.ValueType{wasm.ValueTypeI64, wasm.ValueTypeI64}, wasm.ValueTypeI32) 32 | I64GeS = newOp(0x59, "i64.ge_s", []wasm.ValueType{wasm.ValueTypeI64, wasm.ValueTypeI64}, wasm.ValueTypeI32) 33 | I64GeU = newOp(0x5a, "i64.ge_u", []wasm.ValueType{wasm.ValueTypeI64, wasm.ValueTypeI64}, wasm.ValueTypeI32) 34 | F32Eq = newOp(0x5b, "f32.eq", []wasm.ValueType{wasm.ValueTypeF32, wasm.ValueTypeF32}, wasm.ValueTypeI32) 35 | F32Ne = newOp(0x5c, "f32.ne", []wasm.ValueType{wasm.ValueTypeF32, wasm.ValueTypeF32}, wasm.ValueTypeI32) 36 | F32Lt = newOp(0x5d, "f32.lt", []wasm.ValueType{wasm.ValueTypeF32, wasm.ValueTypeF32}, wasm.ValueTypeI32) 37 | F32Gt = newOp(0x5e, "f32.gt", []wasm.ValueType{wasm.ValueTypeF32, wasm.ValueTypeF32}, wasm.ValueTypeI32) 38 | F32Le = newOp(0x5f, "f32.le", []wasm.ValueType{wasm.ValueTypeF32, wasm.ValueTypeF32}, wasm.ValueTypeI32) 39 | F32Ge = newOp(0x60, "f32.ge", []wasm.ValueType{wasm.ValueTypeF32, wasm.ValueTypeF32}, wasm.ValueTypeI32) 40 | F64Eq = newOp(0x61, "f64.eq", []wasm.ValueType{wasm.ValueTypeF64, wasm.ValueTypeF64}, wasm.ValueTypeI32) 41 | F64Ne = newOp(0x62, "f64.ne", []wasm.ValueType{wasm.ValueTypeF64, wasm.ValueTypeF64}, wasm.ValueTypeI32) 42 | F64Lt = newOp(0x63, "f64.lt", []wasm.ValueType{wasm.ValueTypeF64, wasm.ValueTypeF64}, wasm.ValueTypeI32) 43 | F64Gt = newOp(0x64, "f64.gt", []wasm.ValueType{wasm.ValueTypeF64, wasm.ValueTypeF64}, wasm.ValueTypeI32) 44 | F64Le = newOp(0x65, "f64.le", []wasm.ValueType{wasm.ValueTypeF64, wasm.ValueTypeF64}, wasm.ValueTypeI32) 45 | F64Ge = newOp(0x66, "f64.ge", []wasm.ValueType{wasm.ValueTypeF64, wasm.ValueTypeF64}, wasm.ValueTypeI32) 46 | ) 47 | -------------------------------------------------------------------------------- /wasm/operators/const.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package operators 6 | 7 | import ( 8 | "github.com/go-interpreter/wagon/wasm" 9 | ) 10 | 11 | var ( 12 | I32Const = newOp(0x41, "i32.const", nil, wasm.ValueTypeI32) 13 | I64Const = newOp(0x42, "i64.const", nil, wasm.ValueTypeI64) 14 | F32Const = newOp(0x43, "f32.const", nil, wasm.ValueTypeF32) 15 | F64Const = newOp(0x44, "f64.const", nil, wasm.ValueTypeF64) 16 | ) 17 | -------------------------------------------------------------------------------- /wasm/operators/control.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package operators 6 | 7 | import ( 8 | "github.com/go-interpreter/wagon/wasm" 9 | ) 10 | 11 | var ( 12 | Unreachable = newOp(0x00, "unreachable", nil, noReturn) 13 | Nop = newOp(0x01, "nop", nil, noReturn) 14 | Block = newOp(0x02, "block", nil, noReturn) 15 | Loop = newOp(0x03, "loop", nil, noReturn) 16 | If = newOp(0x04, "if", []wasm.ValueType{wasm.ValueTypeI32}, noReturn) 17 | Else = newOp(0x05, "else", nil, noReturn) 18 | End = newOp(0x0b, "end", nil, noReturn) 19 | Br = newPolymorphicOp(0x0c, "br") 20 | BrIf = newOp(0x0d, "br_if", []wasm.ValueType{wasm.ValueTypeI32}, noReturn) 21 | BrTable = newPolymorphicOp(0x0e, "br_table") 22 | Return = newPolymorphicOp(0x0f, "return") 23 | ) 24 | -------------------------------------------------------------------------------- /wasm/operators/conv.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package operators 6 | 7 | import ( 8 | "regexp" 9 | 10 | "github.com/go-interpreter/wagon/wasm" 11 | ) 12 | 13 | var reCvrtOp = regexp.MustCompile(`(.+)\.(?:[a-z]|\_)+\/(.+)`) 14 | 15 | func valType(s string) wasm.ValueType { 16 | switch s { 17 | case "i32": 18 | return wasm.ValueTypeI32 19 | case "i64": 20 | return wasm.ValueTypeI64 21 | case "f32": 22 | return wasm.ValueTypeF32 23 | case "f64": 24 | return wasm.ValueTypeF64 25 | default: 26 | panic("Invalid value type string: " + s) 27 | } 28 | } 29 | 30 | func newConversionOp(code byte, name string) byte { 31 | matches := reCvrtOp.FindStringSubmatch(name) 32 | if len(matches) == 0 { 33 | panic(name + " is not a conversion operator") 34 | } 35 | 36 | returns := valType(matches[1]) 37 | param := valType(matches[2]) 38 | 39 | return newOp(code, name, []wasm.ValueType{param}, returns) 40 | } 41 | 42 | var ( 43 | I32WrapI64 = newConversionOp(0xa7, "i32.wrap/i64") 44 | I32TruncSF32 = newConversionOp(0xa8, "i32.trunc_s/f32") 45 | I32TruncUF32 = newConversionOp(0xa9, "i32.trunc_u/f32") 46 | I32TruncSF64 = newConversionOp(0xaa, "i32.trunc_s/f64") 47 | I32TruncUF64 = newConversionOp(0xab, "i32.trunc_u/f64") 48 | I64ExtendSI32 = newConversionOp(0xac, "i64.extend_s/i32") 49 | I64ExtendUI32 = newConversionOp(0xad, "i64.extend_u/i32") 50 | I64TruncSF32 = newConversionOp(0xae, "i64.trunc_s/f32") 51 | I64TruncUF32 = newConversionOp(0xaf, "i64.trunc_u/f32") 52 | I64TruncSF64 = newConversionOp(0xb0, "i64.trunc_s/f64") 53 | I64TruncUF64 = newConversionOp(0xb1, "i64.trunc_u/f64") 54 | F32ConvertSI32 = newConversionOp(0xb2, "f32.convert_s/i32") 55 | F32ConvertUI32 = newConversionOp(0xb3, "f32.convert_u/i32") 56 | F32ConvertSI64 = newConversionOp(0xb4, "f32.convert_s/i64") 57 | F32ConvertUI64 = newConversionOp(0xb5, "f32.convert_u/i64") 58 | F32DemoteF64 = newConversionOp(0xb6, "f32.demote/f64") 59 | F64ConvertSI32 = newConversionOp(0xb7, "f64.convert_s/i32") 60 | F64ConvertUI32 = newConversionOp(0xb8, "f64.convert_u/i32") 61 | F64ConvertSI64 = newConversionOp(0xb9, "f64.convert_s/i64") 62 | F64ConvertUI64 = newConversionOp(0xba, "f64.convert_u/i64") 63 | F64PromoteF32 = newConversionOp(0xbb, "f64.promote/f32") 64 | ) 65 | -------------------------------------------------------------------------------- /wasm/operators/conv_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package operators 6 | 7 | import ( 8 | "reflect" 9 | "testing" 10 | 11 | "github.com/go-interpreter/wagon/wasm" 12 | ) 13 | 14 | func TestNewConversionOp(t *testing.T) { 15 | origOps := ops 16 | defer func() { 17 | ops = origOps 18 | }() 19 | 20 | ops = [256]Op{} 21 | testCases := []struct { 22 | name string 23 | args []wasm.ValueType 24 | returns wasm.ValueType 25 | }{ 26 | {"i32.wrap/i64", []wasm.ValueType{wasm.ValueTypeI64}, wasm.ValueTypeI32}, 27 | {"i32.trunc_s/f32", []wasm.ValueType{wasm.ValueTypeF32}, wasm.ValueTypeI32}, 28 | } 29 | 30 | for i, testCase := range testCases { 31 | op, err := New(newConversionOp(byte(i), testCase.name)) 32 | if err != nil { 33 | t.Fatalf("%s: unexpected error from New: %v", testCase.name, err) 34 | } 35 | 36 | if !reflect.DeepEqual(op.Args, testCase.args) { 37 | t.Fatalf("%s: unexpected param types: got=%v, want=%v", testCase.name, op.Args, testCase.args) 38 | } 39 | 40 | if op.Returns != testCase.returns { 41 | t.Fatalf("%s: unexpected return type: got=%v, want=%v", testCase.name, op.Returns, testCase.returns) 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /wasm/operators/memory.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package operators 6 | 7 | import ( 8 | "github.com/go-interpreter/wagon/wasm" 9 | ) 10 | 11 | var ( 12 | I32Load = newOp(0x28, "i32.load", []wasm.ValueType{wasm.ValueTypeI32}, wasm.ValueTypeI32) 13 | I64Load = newOp(0x29, "i64.load", []wasm.ValueType{wasm.ValueTypeI32}, wasm.ValueTypeI64) 14 | F32Load = newOp(0x2a, "f32.load", []wasm.ValueType{wasm.ValueTypeI32}, wasm.ValueTypeF32) 15 | F64Load = newOp(0x2b, "f64.load", []wasm.ValueType{wasm.ValueTypeI32}, wasm.ValueTypeF64) 16 | I32Load8s = newOp(0x2c, "i32.load8_s", []wasm.ValueType{wasm.ValueTypeI32}, wasm.ValueTypeI32) 17 | I32Load8u = newOp(0x2d, "i32.load8_u", []wasm.ValueType{wasm.ValueTypeI32}, wasm.ValueTypeI32) 18 | I32Load16s = newOp(0x2e, "i32.load16_s", []wasm.ValueType{wasm.ValueTypeI32}, wasm.ValueTypeI32) 19 | I32Load16u = newOp(0x2f, "i32.load16_u", []wasm.ValueType{wasm.ValueTypeI32}, wasm.ValueTypeI32) 20 | I64Load8s = newOp(0x30, "i64.load8_s", []wasm.ValueType{wasm.ValueTypeI32}, wasm.ValueTypeI64) 21 | I64Load8u = newOp(0x31, "i64.load8_u", []wasm.ValueType{wasm.ValueTypeI32}, wasm.ValueTypeI64) 22 | I64Load16s = newOp(0x32, "i64.load16_s", []wasm.ValueType{wasm.ValueTypeI32}, wasm.ValueTypeI64) 23 | I64Load16u = newOp(0x33, "i64.load16_u", []wasm.ValueType{wasm.ValueTypeI32}, wasm.ValueTypeI64) 24 | I64Load32s = newOp(0x34, "i64.load32_s", []wasm.ValueType{wasm.ValueTypeI32}, wasm.ValueTypeI64) 25 | I64Load32u = newOp(0x35, "i64.load32_u", []wasm.ValueType{wasm.ValueTypeI32}, wasm.ValueTypeI64) 26 | 27 | I32Store = newOp(0x36, "i32.store", []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32}, noReturn) 28 | I64Store = newOp(0x37, "i64.store", []wasm.ValueType{wasm.ValueTypeI64, wasm.ValueTypeI32}, noReturn) 29 | F32Store = newOp(0x38, "f32.store", []wasm.ValueType{wasm.ValueTypeF32, wasm.ValueTypeI32}, noReturn) 30 | F64Store = newOp(0x39, "f64.store", []wasm.ValueType{wasm.ValueTypeF64, wasm.ValueTypeI32}, noReturn) 31 | I32Store8 = newOp(0x3a, "i32.store8", []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32}, noReturn) 32 | I32Store16 = newOp(0x3b, "i32.store16", []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32}, noReturn) 33 | I64Store8 = newOp(0x3c, "i64.store8", []wasm.ValueType{wasm.ValueTypeI64, wasm.ValueTypeI32}, noReturn) 34 | I64Store16 = newOp(0x3d, "i64.store16", []wasm.ValueType{wasm.ValueTypeI64, wasm.ValueTypeI32}, noReturn) 35 | I64Store32 = newOp(0x3e, "i64.store32", []wasm.ValueType{wasm.ValueTypeI64, wasm.ValueTypeI32}, noReturn) 36 | 37 | // TODO: rename operations accordingly 38 | 39 | CurrentMemory = newOp(0x3f, "memory.size", nil, wasm.ValueTypeI32) 40 | GrowMemory = newOp(0x40, "memory.grow", []wasm.ValueType{wasm.ValueTypeI32}, wasm.ValueTypeI32) 41 | ) 42 | -------------------------------------------------------------------------------- /wasm/operators/op.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package operators provides all operators used by WebAssembly bytecode, 6 | // together with their parameter and return type(s). 7 | package operators 8 | 9 | import ( 10 | "fmt" 11 | 12 | "github.com/go-interpreter/wagon/wasm" 13 | ) 14 | 15 | var ( 16 | ops [256]Op // an array of Op values mapped by wasm opcodes, used by New(). 17 | noReturn = wasm.ValueType(wasm.BlockTypeEmpty) 18 | ) 19 | 20 | // Op describes a WASM operator. 21 | type Op struct { 22 | Code byte // The single-byte opcode 23 | Name string // The name of the operator 24 | 25 | // Whether this operator is polymorphic. 26 | // A polymorphic operator has a variable arity. call, call_indirect, and 27 | // drop are examples of polymorphic operators. 28 | Polymorphic bool 29 | Args []wasm.ValueType // an array of value types used by the operator as arguments, is nil for polymorphic operators 30 | Returns wasm.ValueType // the value returned (pushed) by the operator, is 0 for polymorphic operators 31 | } 32 | 33 | func (o Op) IsValid() bool { 34 | return o.Name != "" 35 | } 36 | 37 | func newOp(code byte, name string, args []wasm.ValueType, returns wasm.ValueType) byte { 38 | if ops[code].IsValid() { 39 | panic(fmt.Errorf("Opcode %#x is already assigned to %s", code, ops[code].Name)) 40 | } 41 | 42 | op := Op{ 43 | Code: code, 44 | Name: name, 45 | Polymorphic: false, 46 | Args: args, 47 | Returns: returns, 48 | } 49 | ops[code] = op 50 | return code 51 | } 52 | 53 | func newPolymorphicOp(code byte, name string) byte { 54 | if ops[code].IsValid() { 55 | panic(fmt.Errorf("Opcode %#x is already assigned to %s", code, ops[code].Name)) 56 | } 57 | 58 | op := Op{ 59 | Code: code, 60 | Name: name, 61 | Polymorphic: true, 62 | } 63 | ops[code] = op 64 | return code 65 | } 66 | 67 | type InvalidOpcodeError byte 68 | 69 | func (e InvalidOpcodeError) Error() string { 70 | return fmt.Sprintf("Invalid opcode: %#x", byte(e)) 71 | } 72 | 73 | // New returns the Op object for a valid given opcode. 74 | // If code is invalid, an ErrInvalidOpcode is returned. 75 | func New(code byte) (Op, error) { 76 | var op Op 77 | 78 | if int(code) >= len(ops) || internalOpcodes[code] { 79 | return op, InvalidOpcodeError(code) 80 | } 81 | 82 | op = ops[code] 83 | if !op.IsValid() { 84 | return op, InvalidOpcodeError(code) 85 | } 86 | return op, nil 87 | } 88 | -------------------------------------------------------------------------------- /wasm/operators/op_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package operators 6 | 7 | import ( 8 | "testing" 9 | ) 10 | 11 | func TestNew(t *testing.T) { 12 | op1, err := New(Unreachable) 13 | if err != nil { 14 | t.Fatalf("unexpected error from New: %v", err) 15 | } 16 | if op1.Name != "unreachable" { 17 | t.Fatalf("0x00: unexpected Op name. got=%s, want=unrechable", op1.Name) 18 | } 19 | if !op1.IsValid() { 20 | t.Fatalf("0x00: operator %v is invalid (should be valid)", op1) 21 | } 22 | 23 | op2, err := New(0xff) 24 | if err == nil { 25 | t.Fatalf("0xff: expected error while getting Op value") 26 | } 27 | if op2.IsValid() { 28 | t.Fatalf("0xff: operator %v is valid (should be invalid)", op2) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /wasm/operators/parametric.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package operators 6 | 7 | var ( 8 | Drop = newPolymorphicOp(0x1a, "drop") 9 | Select = newPolymorphicOp(0x1b, "select") 10 | ) 11 | -------------------------------------------------------------------------------- /wasm/operators/reinterp.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package operators 6 | 7 | import ( 8 | "github.com/go-interpreter/wagon/wasm" 9 | ) 10 | 11 | var ( 12 | I32ReinterpretF32 = newOp(0xbc, "i32.reinterpret/f32", []wasm.ValueType{wasm.ValueTypeF32}, wasm.ValueTypeI32) 13 | I64ReinterpretF64 = newOp(0xbd, "i64.reinterpret/f64", []wasm.ValueType{wasm.ValueTypeF64}, wasm.ValueTypeI64) 14 | F32ReinterpretI32 = newOp(0xbe, "f32.reinterpret/i32", []wasm.ValueType{wasm.ValueTypeI32}, wasm.ValueTypeF32) 15 | F64ReinterpretI64 = newOp(0xbf, "f64.reinterpret/i64", []wasm.ValueType{wasm.ValueTypeI64}, wasm.ValueTypeF64) 16 | ) 17 | -------------------------------------------------------------------------------- /wasm/operators/var.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package operators 6 | 7 | var ( 8 | GetLocal = newPolymorphicOp(0x20, "get_local") 9 | SetLocal = newPolymorphicOp(0x21, "set_local") 10 | TeeLocal = newPolymorphicOp(0x22, "tee_local") 11 | GetGlobal = newPolymorphicOp(0x23, "get_global") 12 | SetGlobal = newPolymorphicOp(0x24, "set_global") 13 | ) 14 | -------------------------------------------------------------------------------- /wasm/operators/wagon_internal.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package operators 6 | 7 | import "github.com/go-interpreter/wagon/wasm" 8 | 9 | // These opcodes implement optimizations in wagon execution, and are invalid 10 | // opcodes for any uses other than internal use. Expect them to change at any 11 | // time. 12 | // If these opcodes are ever used in future wasm instructions, feel free to 13 | // reassign them to other free opcodes. 14 | var ( 15 | internalOpcodes = map[byte]bool{ 16 | WagonNativeExec: true, 17 | } 18 | 19 | WagonNativeExec = newOp(0xfe, "wagon.nativeExec", []wasm.ValueType{wasm.ValueTypeI64}, noReturn) 20 | ) 21 | -------------------------------------------------------------------------------- /wasm/read.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package wasm 6 | 7 | import ( 8 | "bytes" 9 | "encoding/binary" 10 | "errors" 11 | "io" 12 | "unicode/utf8" 13 | 14 | "github.com/go-interpreter/wagon/wasm/leb128" 15 | ) 16 | 17 | // to avoid memory attack 18 | const maxInitialCap = 10 * 1024 19 | 20 | func getInitialCap(count uint32) uint32 { 21 | if count > maxInitialCap { 22 | return maxInitialCap 23 | } 24 | return count 25 | } 26 | 27 | func readBytes(r io.Reader, n uint32) ([]byte, error) { 28 | if n == 0 { 29 | return nil, nil 30 | } 31 | limited := io.LimitReader(r, int64(n)) 32 | buf := &bytes.Buffer{} 33 | num, _ := buf.ReadFrom(limited) 34 | if num == int64(n) { 35 | return buf.Bytes(), nil 36 | } 37 | return nil, io.ErrUnexpectedEOF 38 | } 39 | 40 | func writeByte(w io.Writer, b byte) error { 41 | _, err := w.Write([]byte{b}) 42 | return err 43 | } 44 | 45 | func ReadByte(r io.Reader) (byte, error) { 46 | p := make([]byte, 1) 47 | _, err := io.ReadFull(r, p) 48 | return p[0], err 49 | } 50 | 51 | func readBytesUint(r io.Reader) ([]byte, error) { 52 | n, err := leb128.ReadVarUint32(r) 53 | if err != nil { 54 | return nil, err 55 | } 56 | return readBytes(r, n) 57 | } 58 | 59 | func readUTF8String(r io.Reader, n uint32) (string, error) { 60 | bytes, err := readBytes(r, n) 61 | if err != nil { 62 | return "", err 63 | } 64 | if !utf8.Valid(bytes) { 65 | return "", errors.New("wasm: invalid utf-8 string") 66 | } 67 | return string(bytes), nil 68 | } 69 | 70 | func readUTF8StringUint(r io.Reader) (string, error) { 71 | n, err := leb128.ReadVarUint32(r) 72 | if err != nil { 73 | return "", err 74 | } 75 | return readUTF8String(r, n) 76 | } 77 | 78 | func readU32(r io.Reader) (uint32, error) { 79 | var buf [4]byte 80 | _, err := io.ReadFull(r, buf[:]) 81 | if err != nil { 82 | return 0, err 83 | } 84 | return binary.LittleEndian.Uint32(buf[:]), nil 85 | } 86 | 87 | func readU64(r io.Reader) (uint64, error) { 88 | var buf [8]byte 89 | _, err := io.ReadFull(r, buf[:]) 90 | if err != nil { 91 | return 0, err 92 | } 93 | return binary.LittleEndian.Uint64(buf[:]), nil 94 | } 95 | -------------------------------------------------------------------------------- /wasm/section_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package wasm_test 6 | 7 | import ( 8 | "bytes" 9 | "io/ioutil" 10 | "path/filepath" 11 | "testing" 12 | 13 | "github.com/go-interpreter/wagon/wasm" 14 | ) 15 | 16 | func TestSectionCustom(t *testing.T) { 17 | fname := "testdata/custom_funcs_locals.wasm" 18 | 19 | t.Run(filepath.Base(fname), func(t *testing.T) { 20 | raw, err := ioutil.ReadFile(fname) 21 | if err != nil { 22 | t.Fatal(err) 23 | } 24 | 25 | r := bytes.NewReader(raw) 26 | m, err := wasm.DecodeModule(r) 27 | if err != nil { 28 | t.Fatalf("error reading module %v", err) 29 | } 30 | 31 | nameCustom := m.Custom("name") 32 | if nameCustom == nil { 33 | t.Fatal("can not find name custom section") 34 | } 35 | 36 | var nSec wasm.NameSection 37 | err = nSec.UnmarshalWASM(bytes.NewReader(nameCustom.Data)) 38 | if err != nil { 39 | t.Fatalf("error name Section Unmarshal %v", err) 40 | } 41 | 42 | // check FunctionNames MarshalWASM func 43 | if len(nSec.Types[wasm.NameFunction]) == 0 { 44 | t.Fatalf("%s doesn't have custom FunctionNames section", fname) 45 | } 46 | 47 | sub, err := nSec.Decode(wasm.NameFunction) 48 | if err != nil { 49 | t.Fatalf("error NameSection Decode NameFunction %v", err) 50 | } 51 | 52 | funcNames, ok := sub.(*wasm.FunctionNames) 53 | if !ok { 54 | t.Fatal("error NameSubsection type") 55 | } 56 | 57 | buf := new(bytes.Buffer) 58 | err = funcNames.MarshalWASM(buf) 59 | if err != nil { 60 | t.Fatalf("error FunctionNames Marshal %v", err) 61 | } 62 | if !bytes.Equal(buf.Bytes(), nSec.Types[wasm.NameFunction]) { 63 | t.Fatal("error Marshal and Unmarshal FunctionNames") 64 | } 65 | 66 | // check LocalNames MarshalWASM func 67 | if len(nSec.Types[wasm.NameLocal]) == 0 { 68 | t.Fatalf("%s doesn't have custom LocalNames section", fname) 69 | } 70 | 71 | sub, err = nSec.Decode(wasm.NameLocal) 72 | if err != nil { 73 | t.Fatalf("error NameSection Decode NameFunction %v", err) 74 | } 75 | 76 | localNames, ok := sub.(*wasm.LocalNames) 77 | if !ok { 78 | t.Fatal("error NameSubsection type") 79 | } 80 | 81 | buf = new(bytes.Buffer) 82 | err = localNames.MarshalWASM(buf) 83 | if err != nil { 84 | t.Fatalf("error LocalNames Marshal %v", err) 85 | } 86 | if !bytes.Equal(buf.Bytes(), nSec.Types[wasm.NameLocal]) { 87 | t.Fatal("error Marshal and Unmarshal LocalNames") 88 | } 89 | 90 | }) 91 | } 92 | -------------------------------------------------------------------------------- /wasm/testdata/custom_funcs_locals.wasm: -------------------------------------------------------------------------------- 1 | asm`env print_numpmemorymain 2 |   linking'name print_nummain local_a -------------------------------------------------------------------------------- /wasm/testdata/custom_section.wasm: -------------------------------------------------------------------------------- 1 | asm``imports imported_func exported_func 2 | A* name$i -------------------------------------------------------------------------------- /wasm/testdata/empty.wasm: -------------------------------------------------------------------------------- 1 | asm -------------------------------------------------------------------------------- /wasm/testdata/empty.wast: -------------------------------------------------------------------------------- 1 | (module 2 | ) 3 | -------------------------------------------------------------------------------- /wasm/testdata/empty.wat: -------------------------------------------------------------------------------- 1 | (module) 2 | -------------------------------------------------------------------------------- /wasm/testdata/f64.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-interpreter/wagon/d9964a5b71752113f6c43d26d03028d0224f9ff8/wasm/testdata/f64.wasm -------------------------------------------------------------------------------- /wasm/testdata/f64.wast: -------------------------------------------------------------------------------- 1 | (module 2 | (func (export "add") (param $x f64) (param $y f64) (result f64) (f64.add (get_local $x) (get_local $y))) 3 | (func (export "sub") (param $x f64) (param $y f64) (result f64) (f64.sub (get_local $x) (get_local $y))) 4 | (func (export "mul") (param $x f64) (param $y f64) (result f64) (f64.mul (get_local $x) (get_local $y))) 5 | (func (export "div") (param $x f64) (param $y f64) (result f64) (f64.div (get_local $x) (get_local $y))) 6 | (func (export "sqrt") (param $x f64) (result f64) (f64.sqrt (get_local $x))) 7 | (func (export "min") (param $x f64) (param $y f64) (result f64) (f64.min (get_local $x) (get_local $y))) 8 | (func (export "max") (param $x f64) (param $y f64) (result f64) (f64.max (get_local $x) (get_local $y))) 9 | (func (export "ceil") (param $x f64) (result f64) (f64.ceil (get_local $x))) 10 | (func (export "floor") (param $x f64) (result f64) (f64.floor (get_local $x))) 11 | (func (export "trunc") (param $x f64) (result f64) (f64.trunc (get_local $x))) 12 | (func (export "nearest") (param $x f64) (result f64) (f64.nearest (get_local $x))) 13 | ) 14 | -------------------------------------------------------------------------------- /wasm/testdata/f64.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func (param f64 f64) (result f64))) 3 | (type (;1;) (func (param f64) (result f64))) 4 | (func (;0;) (type 0) (param f64 f64) (result f64) 5 | get_local 0 6 | get_local 1 7 | f64.add) 8 | (func (;1;) (type 0) (param f64 f64) (result f64) 9 | get_local 0 10 | get_local 1 11 | f64.sub) 12 | (func (;2;) (type 0) (param f64 f64) (result f64) 13 | get_local 0 14 | get_local 1 15 | f64.mul) 16 | (func (;3;) (type 0) (param f64 f64) (result f64) 17 | get_local 0 18 | get_local 1 19 | f64.div) 20 | (func (;4;) (type 1) (param f64) (result f64) 21 | get_local 0 22 | f64.sqrt) 23 | (func (;5;) (type 0) (param f64 f64) (result f64) 24 | get_local 0 25 | get_local 1 26 | f64.min) 27 | (func (;6;) (type 0) (param f64 f64) (result f64) 28 | get_local 0 29 | get_local 1 30 | f64.max) 31 | (func (;7;) (type 1) (param f64) (result f64) 32 | get_local 0 33 | f64.ceil) 34 | (func (;8;) (type 1) (param f64) (result f64) 35 | get_local 0 36 | f64.floor) 37 | (func (;9;) (type 1) (param f64) (result f64) 38 | get_local 0 39 | f64.trunc) 40 | (func (;10;) (type 1) (param f64) (result f64) 41 | get_local 0 42 | f64.nearest) 43 | (export "add" (func 0)) 44 | (export "sub" (func 1)) 45 | (export "mul" (func 2)) 46 | (export "div" (func 3)) 47 | (export "sqrt" (func 4)) 48 | (export "min" (func 5)) 49 | (export "max" (func 6)) 50 | (export "ceil" (func 7)) 51 | (export "floor" (func 8)) 52 | (export "trunc" (func 9)) 53 | (export "nearest" (func 10))) 54 | -------------------------------------------------------------------------------- /wasm/testdata/globals.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-interpreter/wagon/d9964a5b71752113f6c43d26d03028d0224f9ff8/wasm/testdata/globals.wasm -------------------------------------------------------------------------------- /wasm/testdata/globals.wast: -------------------------------------------------------------------------------- 1 | (module 2 | (global $a i32 (i32.const -2)) 3 | (global (;1;) f32 (f32.const -3)) 4 | (global (;2;) f64 (f64.const -4)) 5 | (global $b i64 (i64.const -5)) 6 | 7 | (global $x (mut i32) (i32.const -12)) 8 | (global (;5;) (mut f32) (f32.const -13)) 9 | (global (;6;) (mut f64) (f64.const -14)) 10 | (global $y (mut i64) (i64.const -15)) 11 | 12 | (func (export "get-a") (result i32) (get_global $a)) 13 | (func (export "get-b") (result i64) (get_global $b)) 14 | (func (export "get-x") (result i32) (get_global $x)) 15 | (func (export "get-y") (result i64) (get_global $y)) 16 | (func (export "set-x") (param i32) (set_global $x (get_local 0))) 17 | (func (export "set-y") (param i64) (set_global $y (get_local 0))) 18 | 19 | (func (export "get-1") (result f32) (get_global 1)) 20 | (func (export "get-2") (result f64) (get_global 2)) 21 | (func (export "get-5") (result f32) (get_global 5)) 22 | (func (export "get-6") (result f64) (get_global 6)) 23 | (func (export "set-5") (param f32) (set_global 5 (get_local 0))) 24 | (func (export "set-6") (param f64) (set_global 6 (get_local 0))) 25 | ) 26 | 27 | -------------------------------------------------------------------------------- /wasm/testdata/hello-world-tinygo.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-interpreter/wagon/d9964a5b71752113f6c43d26d03028d0224f9ff8/wasm/testdata/hello-world-tinygo.wasm -------------------------------------------------------------------------------- /wasm/testdata/i64.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-interpreter/wagon/d9964a5b71752113f6c43d26d03028d0224f9ff8/wasm/testdata/i64.wasm -------------------------------------------------------------------------------- /wasm/testdata/i64.wast: -------------------------------------------------------------------------------- 1 | (module 2 | (func (export "add") (param $x i64) (param $y i64) (result i64) (i64.add (get_local $x) (get_local $y))) 3 | (func (export "sub") (param $x i64) (param $y i64) (result i64) (i64.sub (get_local $x) (get_local $y))) 4 | (func (export "mul") (param $x i64) (param $y i64) (result i64) (i64.mul (get_local $x) (get_local $y))) 5 | (func (export "div_s") (param $x i64) (param $y i64) (result i64) (i64.div_s (get_local $x) (get_local $y))) 6 | (func (export "div_u") (param $x i64) (param $y i64) (result i64) (i64.div_u (get_local $x) (get_local $y))) 7 | (func (export "rem_s") (param $x i64) (param $y i64) (result i64) (i64.rem_s (get_local $x) (get_local $y))) 8 | (func (export "rem_u") (param $x i64) (param $y i64) (result i64) (i64.rem_u (get_local $x) (get_local $y))) 9 | (func (export "and") (param $x i64) (param $y i64) (result i64) (i64.and (get_local $x) (get_local $y))) 10 | (func (export "or") (param $x i64) (param $y i64) (result i64) (i64.or (get_local $x) (get_local $y))) 11 | (func (export "xor") (param $x i64) (param $y i64) (result i64) (i64.xor (get_local $x) (get_local $y))) 12 | (func (export "shl") (param $x i64) (param $y i64) (result i64) (i64.shl (get_local $x) (get_local $y))) 13 | (func (export "shr_s") (param $x i64) (param $y i64) (result i64) (i64.shr_s (get_local $x) (get_local $y))) 14 | (func (export "shr_u") (param $x i64) (param $y i64) (result i64) (i64.shr_u (get_local $x) (get_local $y))) 15 | (func (export "rotl") (param $x i64) (param $y i64) (result i64) (i64.rotl (get_local $x) (get_local $y))) 16 | (func (export "rotr") (param $x i64) (param $y i64) (result i64) (i64.rotr (get_local $x) (get_local $y))) 17 | (func (export "clz") (param $x i64) (result i64) (i64.clz (get_local $x))) 18 | (func (export "ctz") (param $x i64) (result i64) (i64.ctz (get_local $x))) 19 | (func (export "popcnt") (param $x i64) (result i64) (i64.popcnt (get_local $x))) 20 | (func (export "eqz") (param $x i64) (result i32) (i64.eqz (get_local $x))) 21 | (func (export "eq") (param $x i64) (param $y i64) (result i32) (i64.eq (get_local $x) (get_local $y))) 22 | (func (export "ne") (param $x i64) (param $y i64) (result i32) (i64.ne (get_local $x) (get_local $y))) 23 | (func (export "lt_s") (param $x i64) (param $y i64) (result i32) (i64.lt_s (get_local $x) (get_local $y))) 24 | (func (export "lt_u") (param $x i64) (param $y i64) (result i32) (i64.lt_u (get_local $x) (get_local $y))) 25 | (func (export "le_s") (param $x i64) (param $y i64) (result i32) (i64.le_s (get_local $x) (get_local $y))) 26 | (func (export "le_u") (param $x i64) (param $y i64) (result i32) (i64.le_u (get_local $x) (get_local $y))) 27 | (func (export "gt_s") (param $x i64) (param $y i64) (result i32) (i64.gt_s (get_local $x) (get_local $y))) 28 | (func (export "gt_u") (param $x i64) (param $y i64) (result i32) (i64.gt_u (get_local $x) (get_local $y))) 29 | (func (export "ge_s") (param $x i64) (param $y i64) (result i32) (i64.ge_s (get_local $x) (get_local $y))) 30 | (func (export "ge_u") (param $x i64) (param $y i64) (result i32) (i64.ge_u (get_local $x) (get_local $y))) 31 | ) 32 | -------------------------------------------------------------------------------- /wasm/testdata/i64.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func (param i64 i64) (result i64))) 3 | (type (;1;) (func (param i64) (result i64))) 4 | (type (;2;) (func (param i64) (result i32))) 5 | (type (;3;) (func (param i64 i64) (result i32))) 6 | (func (;0;) (type 0) (param i64 i64) (result i64) 7 | get_local 0 8 | get_local 1 9 | i64.add) 10 | (func (;1;) (type 0) (param i64 i64) (result i64) 11 | get_local 0 12 | get_local 1 13 | i64.sub) 14 | (func (;2;) (type 0) (param i64 i64) (result i64) 15 | get_local 0 16 | get_local 1 17 | i64.mul) 18 | (func (;3;) (type 0) (param i64 i64) (result i64) 19 | get_local 0 20 | get_local 1 21 | i64.div_s) 22 | (func (;4;) (type 0) (param i64 i64) (result i64) 23 | get_local 0 24 | get_local 1 25 | i64.div_u) 26 | (func (;5;) (type 0) (param i64 i64) (result i64) 27 | get_local 0 28 | get_local 1 29 | i64.rem_s) 30 | (func (;6;) (type 0) (param i64 i64) (result i64) 31 | get_local 0 32 | get_local 1 33 | i64.rem_u) 34 | (func (;7;) (type 0) (param i64 i64) (result i64) 35 | get_local 0 36 | get_local 1 37 | i64.and) 38 | (func (;8;) (type 0) (param i64 i64) (result i64) 39 | get_local 0 40 | get_local 1 41 | i64.or) 42 | (func (;9;) (type 0) (param i64 i64) (result i64) 43 | get_local 0 44 | get_local 1 45 | i64.xor) 46 | (func (;10;) (type 0) (param i64 i64) (result i64) 47 | get_local 0 48 | get_local 1 49 | i64.shl) 50 | (func (;11;) (type 0) (param i64 i64) (result i64) 51 | get_local 0 52 | get_local 1 53 | i64.shr_s) 54 | (func (;12;) (type 0) (param i64 i64) (result i64) 55 | get_local 0 56 | get_local 1 57 | i64.shr_u) 58 | (func (;13;) (type 0) (param i64 i64) (result i64) 59 | get_local 0 60 | get_local 1 61 | i64.rotl) 62 | (func (;14;) (type 0) (param i64 i64) (result i64) 63 | get_local 0 64 | get_local 1 65 | i64.rotr) 66 | (func (;15;) (type 1) (param i64) (result i64) 67 | get_local 0 68 | i64.clz) 69 | (func (;16;) (type 1) (param i64) (result i64) 70 | get_local 0 71 | i64.ctz) 72 | (func (;17;) (type 1) (param i64) (result i64) 73 | get_local 0 74 | i64.popcnt) 75 | (func (;18;) (type 2) (param i64) (result i32) 76 | get_local 0 77 | i64.eqz) 78 | (func (;19;) (type 3) (param i64 i64) (result i32) 79 | get_local 0 80 | get_local 1 81 | i64.eq) 82 | (func (;20;) (type 3) (param i64 i64) (result i32) 83 | get_local 0 84 | get_local 1 85 | i64.ne) 86 | (func (;21;) (type 3) (param i64 i64) (result i32) 87 | get_local 0 88 | get_local 1 89 | i64.lt_s) 90 | (func (;22;) (type 3) (param i64 i64) (result i32) 91 | get_local 0 92 | get_local 1 93 | i64.lt_u) 94 | (func (;23;) (type 3) (param i64 i64) (result i32) 95 | get_local 0 96 | get_local 1 97 | i64.le_s) 98 | (func (;24;) (type 3) (param i64 i64) (result i32) 99 | get_local 0 100 | get_local 1 101 | i64.le_u) 102 | (func (;25;) (type 3) (param i64 i64) (result i32) 103 | get_local 0 104 | get_local 1 105 | i64.gt_s) 106 | (func (;26;) (type 3) (param i64 i64) (result i32) 107 | get_local 0 108 | get_local 1 109 | i64.gt_u) 110 | (func (;27;) (type 3) (param i64 i64) (result i32) 111 | get_local 0 112 | get_local 1 113 | i64.ge_s) 114 | (func (;28;) (type 3) (param i64 i64) (result i32) 115 | get_local 0 116 | get_local 1 117 | i64.ge_u) 118 | (export "add" (func 0)) 119 | (export "sub" (func 1)) 120 | (export "mul" (func 2)) 121 | (export "div_s" (func 3)) 122 | (export "div_u" (func 4)) 123 | (export "rem_s" (func 5)) 124 | (export "rem_u" (func 6)) 125 | (export "and" (func 7)) 126 | (export "or" (func 8)) 127 | (export "xor" (func 9)) 128 | (export "shl" (func 10)) 129 | (export "shr_s" (func 11)) 130 | (export "shr_u" (func 12)) 131 | (export "rotl" (func 13)) 132 | (export "rotr" (func 14)) 133 | (export "clz" (func 15)) 134 | (export "ctz" (func 16)) 135 | (export "popcnt" (func 17)) 136 | (export "eqz" (func 18)) 137 | (export "eq" (func 19)) 138 | (export "ne" (func 20)) 139 | (export "lt_s" (func 21)) 140 | (export "lt_u" (func 22)) 141 | (export "le_s" (func 23)) 142 | (export "le_u" (func 24)) 143 | (export "gt_s" (func 25)) 144 | (export "gt_u" (func 26)) 145 | (export "ge_s" (func 27)) 146 | (export "ge_u" (func 28))) 147 | -------------------------------------------------------------------------------- /wasm/testdata/int_exprs.wasm: -------------------------------------------------------------------------------- 1 | asm ``~~mi32.no_fold_cmp_s_offseti32.no_fold_cmp_u_offseti64.no_fold_cmp_s_offseti64.no_fold_cmp_u_offset 2 | 9 Aj AjH Aj AjI B| B|S B| B|T -------------------------------------------------------------------------------- /wasm/testdata/int_exprs.wast: -------------------------------------------------------------------------------- 1 | (module 2 | (func (export "i32.no_fold_cmp_s_offset") (param $x i32) (param $y i32) (result i32) 3 | (i32.lt_s (i32.add (get_local $x) (i32.const 1)) (i32.add (get_local $y) (i32.const 1)))) 4 | (func (export "i32.no_fold_cmp_u_offset") (param $x i32) (param $y i32) (result i32) 5 | (i32.lt_u (i32.add (get_local $x) (i32.const 1)) (i32.add (get_local $y) (i32.const 1)))) 6 | 7 | (func (export "i64.no_fold_cmp_s_offset") (param $x i64) (param $y i64) (result i32) 8 | (i64.lt_s (i64.add (get_local $x) (i64.const 1)) (i64.add (get_local $y) (i64.const 1)))) 9 | (func (export "i64.no_fold_cmp_u_offset") (param $x i64) (param $y i64) (result i32) 10 | (i64.lt_u (i64.add (get_local $x) (i64.const 1)) (i64.add (get_local $y) (i64.const 1)))) 11 | ) 12 | -------------------------------------------------------------------------------- /wasm/testdata/int_exprs.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func (param i32 i32) (result i32))) 3 | (type (;1;) (func (param i64 i64) (result i32))) 4 | (func (;0;) (type 0) (param i32 i32) (result i32) 5 | get_local 0 6 | i32.const 1 7 | i32.add 8 | get_local 1 9 | i32.const 1 10 | i32.add 11 | i32.lt_s) 12 | (func (;1;) (type 0) (param i32 i32) (result i32) 13 | get_local 0 14 | i32.const 1 15 | i32.add 16 | get_local 1 17 | i32.const 1 18 | i32.add 19 | i32.lt_u) 20 | (func (;2;) (type 1) (param i64 i64) (result i32) 21 | get_local 0 22 | i64.const 1 23 | i64.add 24 | get_local 1 25 | i64.const 1 26 | i64.add 27 | i64.lt_s) 28 | (func (;3;) (type 1) (param i64 i64) (result i32) 29 | get_local 0 30 | i64.const 1 31 | i64.add 32 | get_local 1 33 | i64.const 1 34 | i64.add 35 | i64.lt_u) 36 | (export "i32.no_fold_cmp_s_offset" (func 0)) 37 | (export "i32.no_fold_cmp_u_offset" (func 1)) 38 | (export "i64.no_fold_cmp_s_offset" (func 2)) 39 | (export "i64.no_fold_cmp_u_offset" (func 3))) 40 | -------------------------------------------------------------------------------- /wasm/testdata/nofuncs.wasm: -------------------------------------------------------------------------------- 1 | asm`ethereumfinish 2 | memory -------------------------------------------------------------------------------- /wasm/testdata/nofuncs.wast: -------------------------------------------------------------------------------- 1 | (module 2 | (import "ethereum" "finish" (func $finish (param i32 i32))) 3 | (memory 1) 4 | (export "memory" (memory 0)) 5 | ;; no "main" export 6 | ) 7 | -------------------------------------------------------------------------------- /wasm/testdata/spec/sigtest.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-interpreter/wagon/d9964a5b71752113f6c43d26d03028d0224f9ff8/wasm/testdata/spec/sigtest.wasm -------------------------------------------------------------------------------- /wasm/testdata/spec/sigtest.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func (param i32) (result i32))) 3 | (type (;1;) (func (param i32 i32) (result i32))) 4 | (type (;2;) (func (result i32))) 5 | (type (;3;) (func (param i64)(result i64))) 6 | (import "env" "ontologyblockchain" (func (;0;) (type 3))) 7 | (import "env" "test" (global (;0;) i64)) 8 | (func (;1;) (type 1) (param i32 i32) (result i32) 9 | local.get 0 10 | local.get 1 11 | i32.add 12 | ) 13 | (func (;2;) (type 0) (param i32) (result i32) 14 | local.get 0 15 | ) 16 | (func (;3;) (type 2) 17 | i32.const 100 18 | i32.const 200 19 | call 1 20 | ) 21 | (export "invoke" (func 3)) 22 | (global (;1;) (mut i32) (i32.const 100)) 23 | ) 24 | -------------------------------------------------------------------------------- /wast/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package wast provides functions to handle wast files. 6 | // 7 | // See https://webassembly.github.io/spec/core/text/ 8 | package wast 9 | -------------------------------------------------------------------------------- /wast/scanner_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package wast 6 | 7 | import ( 8 | "fmt" 9 | "testing" 10 | ) 11 | 12 | func TestScanner(t *testing.T) { 13 | for _, fname := range []string{ 14 | // "../exec/testdata/spec/address.wast", // FIXME 15 | "../exec/testdata/spec/block.wast", 16 | "../exec/testdata/spec/break-drop.wast", 17 | "../exec/testdata/spec/br_if.wast", 18 | // "../exec/testdata/spec/br_table.wast", // FIXME 19 | // "../exec/testdata/spec/br.wast", // FIXME 20 | "../exec/testdata/spec/call_indirect.wast", 21 | // "../exec/testdata/spec/endianness.wast", // FIXME 22 | "../exec/testdata/spec/fac.wast", 23 | "../exec/testdata/spec/forward.wast", 24 | "../exec/testdata/spec/get_local.wast", 25 | "../exec/testdata/spec/globals.wast", 26 | "../exec/testdata/spec/if.wast", 27 | // "../exec/testdata/spec/loop.wast", // FIXME 28 | "../exec/testdata/spec/memory_redundancy.wast", 29 | "../exec/testdata/spec/names.wast", 30 | "../exec/testdata/spec/nop.wast", 31 | "../exec/testdata/spec/resizing.wast", 32 | // "../exec/testdata/spec/return.wast", // FIXME 33 | "../exec/testdata/spec/select.wast", 34 | "../exec/testdata/spec/switch.wast", 35 | "../exec/testdata/spec/tee_local.wast", 36 | "../exec/testdata/spec/traps_int_div.wast", 37 | "../exec/testdata/spec/traps_int_rem.wast", 38 | // "../exec/testdata/spec/traps_mem.wast", // FIXME 39 | "../exec/testdata/spec/unwind.wast", 40 | } { 41 | t.Run(fname, func(t *testing.T) { 42 | 43 | s := NewScanner(fname) 44 | if len(s.Errors) > 0 { 45 | fmt.Println(s.Errors[0]) 46 | return 47 | } 48 | 49 | var tok *Token 50 | tok = s.Next() 51 | for tok.Kind != EOF { 52 | fmt.Printf("%d:%d %s\n", tok.Line, tok.Column, tok.String()) 53 | tok = s.Next() 54 | } 55 | fmt.Printf("%d:%d %s\n", tok.Line, tok.Column, tok.String()) 56 | 57 | for _, err := range s.Errors { 58 | fmt.Print(err) 59 | } 60 | 61 | if len(s.Errors) > 0 { 62 | t.Errorf("wast: failed with %d errors", len(s.Errors)) 63 | } 64 | }) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /wast/write_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The go-interpreter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package wast_test 6 | 7 | import ( 8 | "bytes" 9 | "io/ioutil" 10 | "os" 11 | "path/filepath" 12 | "strings" 13 | "testing" 14 | 15 | "github.com/go-interpreter/wagon/wasm" 16 | "github.com/go-interpreter/wagon/wast" 17 | ) 18 | 19 | var testPaths = []string{ 20 | "../wasm/testdata", 21 | "../exec/testdata", 22 | "../exec/testdata/spec", 23 | } 24 | 25 | func TestAssemble(t *testing.T) { 26 | for _, dir := range testPaths { 27 | fnames, err := filepath.Glob(filepath.Join(dir, "*.wasm")) 28 | if err != nil { 29 | t.Fatal(err) 30 | } 31 | for _, fname := range fnames { 32 | name := fname 33 | tname := strings.TrimSuffix(name, ".wasm") + ".wat" 34 | if _, err := os.Stat(tname); err != nil { 35 | continue 36 | } 37 | t.Run(filepath.Base(name), func(t *testing.T) { 38 | raw, err := ioutil.ReadFile(name) 39 | if err != nil { 40 | t.Fatal(err) 41 | } 42 | exp, err := ioutil.ReadFile(tname) 43 | if err != nil { 44 | t.Fatal(err) 45 | } 46 | 47 | r := bytes.NewReader(raw) 48 | m, err := wasm.DecodeModule(r) 49 | if err != nil { 50 | t.Fatalf("error reading module %v", err) 51 | } 52 | buf := new(bytes.Buffer) 53 | err = wast.WriteTo(buf, m) 54 | if err != nil { 55 | t.Fatal(err) 56 | } 57 | if !bytes.Equal(exp, buf.Bytes()) { 58 | ioutil.WriteFile(tname+"_got", buf.Bytes(), 0644) 59 | t.Fatalf("output is different") 60 | } else { 61 | os.Remove(tname + "_got") 62 | } 63 | }) 64 | } 65 | } 66 | } 67 | --------------------------------------------------------------------------------