├── .github ├── dependabot.yml └── workflows │ ├── go-test.yml │ └── golangci-lint.yml ├── .gitignore ├── LICENSE.txt ├── Makefile ├── README.md ├── engine.go ├── engines ├── tests │ ├── engine.go │ └── pool.go ├── wasmer │ ├── engine_test.go │ ├── example_test.go │ ├── go.mod │ ├── go.sum │ ├── wasmer.go │ └── wasmer_test.go ├── wasmtime │ ├── engine_test.go │ ├── example_test.go │ ├── go.mod │ ├── go.sum │ ├── wasmtime.go │ └── wasmtime_test.go └── wazero │ ├── engine_test.go │ ├── go.mod │ ├── go.sum │ ├── wazero.go │ └── wazero_test.go ├── example ├── go.mod ├── go.sum └── main.go ├── go.mod ├── go.sum ├── go.work ├── go.work.sum ├── hello ├── Makefile ├── go.mod ├── go.sum └── main.go ├── pool.go └── testdata ├── as ├── .gitignore ├── Makefile ├── assembly │ ├── index.ts │ └── tsconfig.json ├── package-lock.json └── package.json ├── go ├── Makefile ├── go.mod ├── go.sum └── main.go └── rust ├── Cargo.lock ├── Cargo.toml ├── Makefile └── src └── main.rs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Please see the documentation for all configuration options: 2 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 3 | 4 | version: 2 5 | updates: 6 | - package-ecosystem: "gomod" # See documentation for possible values 7 | directory: "/" # Location of package manifests 8 | schedule: 9 | interval: "weekly" 10 | -------------------------------------------------------------------------------- /.github/workflows/go-test.yml: -------------------------------------------------------------------------------- 1 | name: go tests 2 | 3 | on: 4 | push: 5 | tags: 6 | - v* 7 | branches: 8 | - master 9 | - main 10 | pull_request: 11 | 12 | jobs: 13 | 14 | build: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v4 18 | 19 | - name: Set up Go 20 | uses: actions/setup-go@v5 21 | with: 22 | go-version: '1.23.5' 23 | - uses: acifani/setup-tinygo@v2 24 | with: 25 | tinygo-version: '0.35.0' 26 | 27 | - name: Set up Rust 28 | uses: actions-rs/toolchain@v1 29 | with: 30 | toolchain: stable 31 | target: wasm32-unknown-unknown 32 | 33 | - name: Build Wasm 34 | run: make build-wasm 35 | 36 | - name: Test 37 | run: make tests 38 | -------------------------------------------------------------------------------- /.github/workflows/golangci-lint.yml: -------------------------------------------------------------------------------- 1 | name: golangci-lint 2 | on: 3 | push: 4 | tags: 5 | - v* 6 | branches: 7 | - master 8 | - main 9 | pull_request: 10 | jobs: 11 | generate-matrix: 12 | name: Generate matrix 13 | runs-on: ubuntu-latest 14 | outputs: 15 | matrix: ${{ steps.set-matrix.outputs.matrix }} 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v2 19 | - name: Set matrix 20 | id: set-matrix 21 | run: | 22 | MODULES=$(find . -type f -name "go.mod" | sed -e 's/\/go.mod//g' | jq -R -s -c 'split("\n")[:-1]') 23 | echo "matrix=${MODULES}" >> $GITHUB_OUTPUT 24 | golangci: 25 | name: golangci 26 | needs: generate-matrix 27 | runs-on: ubuntu-latest 28 | strategy: 29 | matrix: 30 | modules: ${{fromJson(needs.generate-matrix.outputs.matrix)}} 31 | steps: 32 | - uses: actions/setup-go@v3 33 | with: 34 | go-version: '1.23' 35 | - uses: actions/checkout@v3 36 | - name: golangci-lint 37 | uses: golangci/golangci-lint-action@v3 38 | with: 39 | # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version 40 | version: latest 41 | 42 | # Optional: working directory, useful for monorepos 43 | working-directory: ${{ matrix.modules }} 44 | 45 | # Optional: golangci-lint command line arguments. 46 | # args: --issues-exit-code=0 47 | 48 | # Optional: show only new issues if it's a pull request. The default value is `false`. 49 | # only-new-issues: true 50 | 51 | # Optional: if set to true then the all caching functionality will be complete disabled, 52 | # takes precedence over all other caching options. 53 | # skip-cache: true 54 | 55 | # Optional: if set to true then the action don't cache or restore ~/go/pkg. 56 | # skip-pkg-cache: true 57 | 58 | # Optional: if set to true then the action don't cache or restore ~/.cache/go-build. 59 | # skip-build-cache: true 60 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.dll 4 | *.so 5 | *.dylib 6 | example/example 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Coverage 15 | .coverage/ 16 | 17 | # No Swapfiles 18 | *.swp 19 | 20 | # Rust build Directory 21 | target/ 22 | 23 | # npm Modules 24 | node_modules 25 | 26 | # Vendor 27 | vendor 28 | 29 | # Mac stuff 30 | .DS_Store 31 | 32 | # Generated wasm files 33 | *.wasm 34 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile to build and execute tests 2 | 3 | .PHONY: tests 4 | tests: 5 | @echo "Executing Go tests" 6 | mkdir -p .coverage 7 | go test -v -covermode=count -coverprofile=.coverage/coverage.out \ 8 | ./engines/wasmer/... \ 9 | ./engines/wasmtime/... \ 10 | ./engines/wazero/... \ 11 | ./... 12 | go tool cover -html=.coverage/coverage.out -o .coverage/coverage.html 13 | 14 | .PHONY: build-wasm 15 | build-wasm: build-as build-example build-go build-rust 16 | 17 | .PHONY: build-example 18 | build-example: 19 | $(MAKE) -C hello build 20 | 21 | .PHONY: build-as 22 | build-as: 23 | $(MAKE) -C testdata/as build 24 | 25 | .PHONY: build-go 26 | build-go: 27 | $(MAKE) -C testdata/go build 28 | 29 | .PHONY: build-rust 30 | build-rust: 31 | $(MAKE) -C testdata/rust build 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # waPC Host for Go 2 | 3 | [![Gitter](https://badges.gitter.im/wapc/community.svg)](https://gitter.im/wapc/community) 4 | [![PkgGoDev](https://pkg.go.dev/badge/github.com/wapc/wapc-go)](https://pkg.go.dev/github.com/wapc/wapc-go) 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/wapc/wapc-go)](https://goreportcard.com/report/github.com/wapc/wapc-go) 6 | [![go tests](https://github.com/wapc/wapc-go/actions/workflows/go-test.yml/badge.svg)](https://github.com/wapc/wapc-go/actions/workflows/go-test.yml) 7 | 8 | This is the Golang implementation of the **waPC** standard for WebAssembly host runtimes. It allows any WebAssembly module to be loaded as a guest and receive requests for invocation as well as to make its own function requests of the host. 9 | 10 | ## Example 11 | 12 | The following is a simple example of synchronous, bidirectional procedure calls between a WebAssembly host runtime and the guest module. 13 | 14 | ```go 15 | package main 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | "os" 21 | "strings" 22 | 23 | "github.com/wapc/wapc-go" 24 | "github.com/wapc/wapc-go/engines/wazero" 25 | ) 26 | 27 | func main() { 28 | if len(os.Args) < 2 { 29 | fmt.Println("usage: hello ") 30 | return 31 | } 32 | name := os.Args[1] 33 | ctx := context.Background() 34 | guest, err := os.ReadFile("hello/hello.wasm") 35 | if err != nil { 36 | panic(err) 37 | } 38 | 39 | engine := wazero.Engine() 40 | 41 | module, err := engine.New(ctx, host, guest, &wapc.ModuleConfig{ 42 | Logger: wapc.PrintlnLogger, 43 | Stdout: os.Stdout, 44 | Stderr: os.Stderr, 45 | }) 46 | if err != nil { 47 | panic(err) 48 | } 49 | defer module.Close(ctx) 50 | 51 | instance, err := module.Instantiate(ctx) 52 | if err != nil { 53 | panic(err) 54 | } 55 | defer instance.Close(ctx) 56 | 57 | result, err := instance.Invoke(ctx, "hello", []byte(name)) 58 | if err != nil { 59 | panic(err) 60 | } 61 | 62 | fmt.Println(string(result)) 63 | } 64 | 65 | func host(ctx context.Context, binding, namespace, operation string, payload []byte) ([]byte, error) { 66 | // Route the payload to any custom functionality accordingly. 67 | // You can even route to other waPC modules!!! 68 | switch namespace { 69 | case "example": 70 | switch operation { 71 | case "capitalize": 72 | name := string(payload) 73 | name = strings.Title(name) 74 | return []byte(name), nil 75 | } 76 | } 77 | return []byte("default"), nil 78 | } 79 | ``` 80 | 81 | To see this in action, enter the following in your shell: 82 | 83 | ``` 84 | $ go run example/main.go waPC! 85 | hello called 86 | Hello, WaPC! 87 | ``` 88 | 89 | Alternatively you can use a `Pool` to manage a pool of instances. 90 | 91 | ```go 92 | pool, err := wapc.NewPool(ctx, module, 10, func(instance wapc.Instance) error { 93 | // Do something to initialize this instance before use. 94 | return nil 95 | }) 96 | if err != nil { 97 | panic(err) 98 | } 99 | defer pool.Close(ctx) 100 | 101 | for i := 0; i < 100; i++ { 102 | instance, err := pool.Get(10 * time.Millisecond) 103 | if err != nil { 104 | panic(err) 105 | } 106 | 107 | result, err := instance.Invoke(ctx, "hello", []byte("waPC")) 108 | if err != nil { 109 | panic(err) 110 | } 111 | 112 | fmt.Println(string(result)) 113 | 114 | err = pool.Return(instance) 115 | if err != nil { 116 | panic(err) 117 | } 118 | } 119 | ``` 120 | 121 | While the above example uses wazero, wapc-go is decoupled (via [`wapc.Engine`](#engines)) and can be used with different runtimes. 122 | 123 | ## Engines 124 | 125 | Here are the supported `wapc.Engine` implementations, in alphabetical order: 126 | 127 | | Name | Usage | Build Tag | Package | 128 | |:-----------:|:-------------------:|-----------|:-----------------------------------------------------------------------------------------------------:| 129 | | wasmer-go | `wasmer.Engine()` | wasmer | [github.com/wasmerio/wasmer-go](https://pkg.go.dev/github.com/wasmerio/wasmer-go) | 130 | | wasmtime-go | `wasmtime.Engine()` | wasmtime | [github.com/bytecodealliance/wasmtime-go](https://pkg.go.dev/github.com/bytecodealliance/wasmtime-go) | 131 | | wazero | `wazero.Engine()` | N/A | [github.com/tetratelabs/wazero](https://wazero.io) | 132 | 133 | 134 | For example, to switch the engine to wasmer, change [example/main.go](example/main.go) like below: 135 | ```diff 136 | --- a/example/main.go 137 | +++ b/example/main.go 138 | @@ -7,7 +7,7 @@ import ( 139 | "strings" 140 | 141 | "github.com/wapc/wapc-go" 142 | - "github.com/wapc/wapc-go/engines/wazero" 143 | + "github.com/wapc/wapc-go/engines/wasmer" 144 | ) 145 | 146 | func main() { 147 | @@ -22,7 +22,7 @@ func main() { 148 | panic(err) 149 | } 150 | 151 | - engine := wazero.Engine() 152 | + engine := wasmer.Engine() 153 | 154 | module, err := engine.New(ctx, code, hostCall) 155 | if err != nil { 156 | ``` 157 | 158 | Then, run with its build tag: 159 | ```bash 160 | $ go run --tags wasmer example/main.go waPC! 161 | hello called 162 | Hello, WaPC! 163 | ``` 164 | 165 | ### Differences with [wapc-rs](https://github.com/wapc/wapc-rs) (Rust) 166 | 167 | Besides engine choices, there differences between this library and the Rust implementation: 168 | * Separate compilation (`New`) and instantiation (`Instantiate`) steps. This is to incur the cost of compilation once in a multi-instance scenario. 169 | * `Pool` for creating a pool of instances for a given Module. 170 | -------------------------------------------------------------------------------- /engine.go: -------------------------------------------------------------------------------- 1 | package wapc 2 | 3 | import ( 4 | "context" 5 | "io" 6 | ) 7 | 8 | type ( 9 | // Logger is the waPC logger for `__console_log` function calls. 10 | Logger func(msg string) 11 | 12 | // HostCallHandler is a function to invoke to handle when a guest is performing a host call. 13 | HostCallHandler func(ctx context.Context, binding, namespace, operation string, payload []byte) ([]byte, error) 14 | 15 | Engine interface { 16 | // Name of the engine. Ex. "wazero" 17 | Name() string 18 | 19 | // New compiles a new WebAssembly module representing the guest, and 20 | // configures the host functions it uses. 21 | // - host: implements host module functions called by the guest 22 | // - guest: the guest WebAssembly binary (%.wasm) to compile 23 | // - config: configures the host and guest. 24 | New(ctx context.Context, host HostCallHandler, guest []byte, config *ModuleConfig) (Module, error) 25 | } 26 | 27 | // ModuleConfig includes parameters to Engine.New. 28 | // 29 | // Note: Implementations should copy fields they use instead of storing 30 | // a reference to this type. 31 | ModuleConfig struct { 32 | // Logger is the logger waPC uses for `__console_log` calls 33 | Logger Logger 34 | // Stdout is the writer WASI uses for `fd_write` to file descriptor 1. 35 | Stdout io.Writer 36 | // Stderr is the writer WASI uses for `fd_write` to file descriptor 2. 37 | Stderr io.Writer 38 | } 39 | 40 | // Module is a WebAssembly Module. 41 | Module interface { 42 | // Instantiate creates a single instance of the module with its own memory. 43 | Instantiate(context.Context) (Instance, error) 44 | 45 | // Close releases resources from this module, returning the first error encountered. 46 | // Note: This should be called before after calling Instance.Close on any instances of this module. 47 | Close(context.Context) error 48 | } 49 | 50 | // Instance is an instantiated Module 51 | Instance interface { 52 | // MemorySize is the size in bytes of the memory available to this Instance. 53 | MemorySize() uint32 54 | 55 | // Invoke calls `operation` with `payload` on the module and returns a byte slice payload. 56 | Invoke(ctx context.Context, operation string, payload []byte) ([]byte, error) 57 | 58 | // Close releases resources from this instance, returning the first error encountered. 59 | // Note: This should be called before calling Module.Close. 60 | Close(context.Context) error 61 | } 62 | ) 63 | 64 | // compile-time check to ensure NoOpHostCallHandler implements HostCallHandler. 65 | var _ HostCallHandler = NoOpHostCallHandler 66 | 67 | // NoOpHostCallHandler is a noop host call handler to use if your host does not 68 | // need to support host calls. 69 | func NoOpHostCallHandler(context.Context, string, string, string, []byte) ([]byte, error) { 70 | return []byte{}, nil 71 | } 72 | 73 | // compile-time check to ensure PrintlnLogger implements Logger. 74 | var _ Logger = PrintlnLogger 75 | 76 | // PrintlnLogger will print the supplied message to standard error. 77 | // A newline is appended to the end of the message. 78 | func PrintlnLogger(message string) { 79 | println(message) 80 | } 81 | -------------------------------------------------------------------------------- /engines/tests/engine.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "path/filepath" 7 | "testing" 8 | "time" 9 | 10 | "github.com/wapc/wapc-go" 11 | ) 12 | 13 | var ctx = context.Background() 14 | 15 | const TESTDATA_DIR = "../../testdata" 16 | 17 | func TestGuest(t *testing.T, engine wapc.Engine) { 18 | lang := map[string]string{ 19 | "assemblyscript": "as/hello.wasm", 20 | "go": "go/hello.wasm", 21 | "rust": "rust/hello.wasm", 22 | } 23 | t.Run(engine.Name(), func(t *testing.T) { 24 | for l, p := range lang { 25 | t.Run("Module testing with "+l+" Guest", func(t *testing.T) { 26 | // Read .wasm file 27 | guest, err := os.ReadFile(filepath.Join(TESTDATA_DIR, p)) 28 | if err != nil { 29 | t.Errorf("Unable to open test file - %s", err) 30 | } 31 | 32 | // Use these later 33 | callbackCh := make(chan struct{}, 2) 34 | host := func(context.Context, string, string, string, []byte) ([]byte, error) { 35 | callbackCh <- struct{}{} 36 | return []byte(""), nil 37 | } 38 | 39 | payload := []byte("Testing") 40 | 41 | // Create new module with a callback function 42 | m, err := engine.New(ctx, host, guest, &wapc.ModuleConfig{ 43 | Logger: wapc.PrintlnLogger, 44 | Stdout: os.Stdout, 45 | Stderr: os.Stderr, 46 | }) 47 | if err != nil { 48 | t.Errorf("Error creating module - %s", err) 49 | } 50 | defer m.Close(ctx) 51 | 52 | // Instantiate Module 53 | i, err := m.Instantiate(ctx) 54 | if err != nil { 55 | t.Errorf("Error instantiating module - %s", err) 56 | } 57 | defer i.Close(ctx) 58 | 59 | t.Run("Call Successful Function", func(t *testing.T) { 60 | // Call echo function 61 | r, err := i.Invoke(ctx, "echo", payload) 62 | if err != nil { 63 | t.Errorf("Unexpected error when calling wasm module - %s", err) 64 | } 65 | 66 | // Verify payload is returned 67 | if len(r) != len(payload) { 68 | t.Errorf("Unexpected response message, got %s, expected %s", r, payload) 69 | } 70 | 71 | // Verify if callback is called 72 | select { 73 | case <-time.After(5 * time.Second): 74 | t.Errorf("Timeout waiting for callback execution") 75 | case <-callbackCh: 76 | return 77 | } 78 | }) 79 | 80 | t.Run("Call Failing Function", func(t *testing.T) { 81 | // Call nope function 82 | _, err := i.Invoke(ctx, "nope", payload) 83 | if err == nil { 84 | t.Errorf("Expected error when calling failing function, got nil") 85 | } 86 | }) 87 | 88 | t.Run("Call Unregistered Function", func(t *testing.T) { 89 | _, err := i.Invoke(ctx, "404", payload) 90 | if err == nil { 91 | t.Errorf("Expected error when calling unregistered function, got nil") 92 | } 93 | }) 94 | 95 | }) 96 | } 97 | }) 98 | } 99 | 100 | func TestModuleBadBytes(t *testing.T, engine wapc.Engine) { 101 | t.Run(engine.Name(), func(t *testing.T) { 102 | host := wapc.NoOpHostCallHandler 103 | guest := []byte("Do not do this at home kids") 104 | _, err := engine.New(ctx, host, guest, &wapc.ModuleConfig{}) 105 | if err == nil { 106 | t.Errorf("Expected error when creating module with invalid wasm, got nil") 107 | } 108 | }) 109 | } 110 | 111 | func TestModule(t *testing.T, engine wapc.Engine) { 112 | t.Run(engine.Name(), func(t *testing.T) { 113 | host := wapc.NoOpHostCallHandler 114 | // Read .wasm file 115 | guest, err := os.ReadFile(filepath.Join(TESTDATA_DIR, "as/hello.wasm")) 116 | if err != nil { 117 | t.Errorf("Unable to open test file - %s", err) 118 | } 119 | 120 | // Use these later 121 | payload := []byte("Testing") 122 | 123 | // Create new module with a NoOpCallback function 124 | m, err := engine.New(ctx, host, guest, &wapc.ModuleConfig{ 125 | Logger: wapc.PrintlnLogger, 126 | Stdout: os.Stdout, 127 | Stderr: os.Stderr, 128 | }) 129 | if err != nil { 130 | t.Errorf("Error creating module - %s", err) 131 | } 132 | defer m.Close(ctx) 133 | 134 | // Instantiate Module 135 | i, err := m.Instantiate(ctx) 136 | if err != nil { 137 | t.Errorf("Error instantiating module - %s", err) 138 | } 139 | defer i.Close(ctx) 140 | 141 | t.Run("Check MemorySize", func(t *testing.T) { 142 | // Verify implementations didn't mistake size in bytes for page count. 143 | expectedMemorySize := uint32(65536) // 1 page 144 | if i.MemorySize() != expectedMemorySize { 145 | t.Errorf("Unexpected memory size, got %d, expected %d", i.MemorySize(), expectedMemorySize) 146 | } 147 | }) 148 | 149 | t.Run("Call Function", func(t *testing.T) { 150 | // Call echo function 151 | r, err := i.Invoke(ctx, "echo", payload) 152 | if err != nil { 153 | t.Errorf("Unexpected error when calling wasm module - %s", err) 154 | } 155 | 156 | // Verify payload is returned 157 | if len(r) != len(payload) { 158 | t.Errorf("Unexpected response message, got %s, expected %s", r, payload) 159 | } 160 | }) 161 | 162 | i.Close(ctx) 163 | 164 | t.Run("Call Function with Closed Instance", func(t *testing.T) { 165 | // Call echo function 166 | _, err := i.Invoke(ctx, "echo", payload) 167 | if err == nil { 168 | t.Errorf("Expected error when calling wasm module with closed instance") 169 | } 170 | }) 171 | }) 172 | } 173 | -------------------------------------------------------------------------------- /engines/tests/pool.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "path/filepath" 7 | "testing" 8 | "time" 9 | 10 | "github.com/wapc/wapc-go" 11 | ) 12 | 13 | func TestGuestsWithPool(t *testing.T, engine wapc.Engine) { 14 | lang := map[string]string{ 15 | "assemblyscript": "as/hello.wasm", 16 | "go": "go/hello.wasm", 17 | "rust": "rust/hello.wasm", 18 | } 19 | 20 | t.Run(engine.Name(), func(t *testing.T) { 21 | for l, p := range lang { 22 | t.Run("Module testing with "+l+" Guest", func(t *testing.T) { 23 | // Read .wasm file 24 | guest, err := os.ReadFile(filepath.Join(TESTDATA_DIR, p)) 25 | if err != nil { 26 | t.Errorf("Unable to open test file - %s", err) 27 | } 28 | 29 | // Use these later 30 | callbackCh := make(chan struct{}, 2) 31 | host := func(context.Context, string, string, string, []byte) ([]byte, error) { 32 | callbackCh <- struct{}{} 33 | return []byte(""), nil 34 | } 35 | payload := []byte("Testing") 36 | 37 | // Create new module with a callback function 38 | m, err := engine.New(ctx, host, guest, &wapc.ModuleConfig{}) 39 | if err != nil { 40 | t.Errorf("Error creating module - %s", err) 41 | } 42 | defer m.Close(ctx) 43 | 44 | p, err := wapc.NewPool(ctx, m, 10) 45 | if err != nil { 46 | t.Errorf("Error creating module pool - %s", err) 47 | } 48 | defer p.Close(ctx) 49 | 50 | i, err := p.Get(10 * time.Second) 51 | if err != nil { 52 | t.Errorf("Error unable to fetch instance from pool - %s", err) 53 | } 54 | 55 | t.Run("Call Successful Function", func(t *testing.T) { 56 | // Call echo function 57 | r, err := i.Invoke(ctx, "echo", payload) 58 | if err != nil { 59 | t.Errorf("Unexpected error when calling wasm module - %s", err) 60 | } 61 | 62 | // Verify payload is returned 63 | if len(r) != len(payload) { 64 | t.Errorf("Unexpected response message, got %s, expected %s", r, payload) 65 | } 66 | 67 | // Verify if callback is called 68 | select { 69 | case <-time.After(5 * time.Second): 70 | t.Errorf("Timeout waiting for callback execution") 71 | case <-callbackCh: 72 | return 73 | } 74 | }) 75 | 76 | t.Run("Call Failing Function", func(t *testing.T) { 77 | // Call nope function 78 | _, err := i.Invoke(ctx, "nope", payload) 79 | if err == nil { 80 | t.Errorf("Expected error when calling failing function, got nil") 81 | } 82 | }) 83 | 84 | t.Run("Call Unregistered Function", func(t *testing.T) { 85 | _, err := i.Invoke(ctx, "404", payload) 86 | if err == nil { 87 | t.Errorf("Expected error when calling unregistered function, got nil") 88 | } 89 | }) 90 | 91 | err = p.Return(i) 92 | if err != nil { 93 | t.Errorf("Unexpected error when returning instance to pool - %s", err) 94 | } 95 | }) 96 | } 97 | }) 98 | } 99 | -------------------------------------------------------------------------------- /engines/wasmer/engine_test.go: -------------------------------------------------------------------------------- 1 | package wasmer_test 2 | 3 | // import ( 4 | // "testing" 5 | 6 | // "github.com/wapc/wapc-go/engines/tests" 7 | // "github.com/wapc/wapc-go/engines/wasmer" 8 | // ) 9 | 10 | // func TestGuest(t *testing.T) { 11 | // tests.TestGuest(t, wasmer.Engine()) 12 | // } 13 | 14 | // func TestModuleBadBytes(t *testing.T) { 15 | // tests.TestModuleBadBytes(t, wasmer.Engine()) 16 | // } 17 | 18 | // func TestModule(t *testing.T) { 19 | // tests.TestModule(t, wasmer.Engine()) 20 | // } 21 | 22 | // func TestGuestsWithPool(t *testing.T) { 23 | // tests.TestGuestsWithPool(t, wasmer.Engine()) 24 | // } 25 | -------------------------------------------------------------------------------- /engines/wasmer/example_test.go: -------------------------------------------------------------------------------- 1 | package wasmer 2 | 3 | // import ( 4 | // "context" 5 | // "log" 6 | 7 | // "github.com/wasmerio/wasmer-go/wasmer" 8 | 9 | // "github.com/wapc/wapc-go" 10 | // ) 11 | 12 | // // This shows how to customize the underlying wasmer engine used by waPC. 13 | // func Example_custom() { 14 | // // Set up the context used to instantiate the engine. 15 | // ctx := context.Background() 16 | 17 | // // Configure waPC to use a specific wasmer feature. 18 | // e := EngineWithRuntime(func() (*wasmer.Engine, error) { 19 | // return wasmer.NewEngineWithConfig(wasmer.NewConfig().UseDylibEngine()), nil 20 | // }) 21 | 22 | // // Instantiate a module normally. 23 | // m, err := e.New(ctx, wapc.NoOpHostCallHandler, guest, mc) 24 | // if err != nil { 25 | // log.Panicf("Error creating module - %v\n", err) 26 | // } 27 | // defer m.Close(ctx) 28 | 29 | // // Output: 30 | // } 31 | -------------------------------------------------------------------------------- /engines/wasmer/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/wapc/wapc-go/engines/wasmer 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.23.4 6 | 7 | require ( 8 | github.com/wapc/wapc-go v0.0.0 9 | github.com/wasmerio/wasmer-go v1.0.4 10 | ) 11 | 12 | require ( 13 | github.com/Workiva/go-datastructures v1.1.5 // indirect 14 | github.com/davecgh/go-spew v1.1.1 // indirect 15 | gopkg.in/yaml.v3 v3.0.1 // indirect 16 | ) 17 | 18 | replace github.com/wapc/wapc-go => ../.. 19 | -------------------------------------------------------------------------------- /engines/wasmer/go.sum: -------------------------------------------------------------------------------- 1 | github.com/Workiva/go-datastructures v1.1.5 h1:5YfhQ4ry7bZc2Mc7R0YZyYwpf5c6t1cEFvdAhd6Mkf4= 2 | github.com/Workiva/go-datastructures v1.1.5/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A= 3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 5 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= 7 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 8 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 9 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 10 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 11 | github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= 12 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 13 | github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg= 14 | github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q= 15 | github.com/wasmerio/wasmer-go v1.0.4 h1:MnqHoOGfiQ8MMq2RF6wyCeebKOe84G88h5yv+vmxJgs= 16 | github.com/wasmerio/wasmer-go v1.0.4/go.mod h1:0gzVdSfg6pysA6QVp6iVRPTagC6Wq9pOE8J86WKb2Fk= 17 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 18 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 19 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 20 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 21 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 22 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 23 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 24 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 25 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 26 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 27 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 28 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 29 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 30 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 31 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 32 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 33 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 34 | golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 35 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 36 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 37 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 38 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 39 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 40 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 41 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 42 | -------------------------------------------------------------------------------- /engines/wasmer/wasmer.go: -------------------------------------------------------------------------------- 1 | //go:build (amd64 || arm64) && !windows && cgo && !wasmtime 2 | 3 | package wasmer 4 | 5 | import ( 6 | "context" 7 | "encoding/binary" 8 | "errors" 9 | "fmt" 10 | "io" 11 | "sync/atomic" 12 | "time" 13 | 14 | "github.com/wasmerio/wasmer-go/wasmer" 15 | 16 | "github.com/wapc/wapc-go" 17 | ) 18 | 19 | type ( 20 | engine struct { 21 | newRuntime NewRuntime 22 | } 23 | 24 | // Module represents a compile waPC module. 25 | Module struct { 26 | hostCallHandler wapc.HostCallHandler 27 | 28 | engine *wasmer.Engine 29 | store *wasmer.Store 30 | module *wasmer.Module 31 | 32 | logger wapc.Logger 33 | stdout, stderr io.Writer 34 | 35 | // closed is atomically updated to ensure Close is only invoked once. 36 | closed uint32 37 | } 38 | 39 | // Instance is a single instantiation of a module with its own memory. 40 | Instance struct { 41 | m *Module 42 | guestCall func(...interface{}) (interface{}, error) 43 | 44 | inst *wasmer.Instance 45 | mem *wasmer.Memory 46 | 47 | context *invokeContext 48 | 49 | // waPC functions 50 | guestRequest *wasmer.Function 51 | guestResponse *wasmer.Function 52 | guestError *wasmer.Function 53 | hostCall *wasmer.Function 54 | hostResponseLen *wasmer.Function 55 | hostResponse *wasmer.Function 56 | hostErrorLen *wasmer.Function 57 | hostError *wasmer.Function 58 | consoleLog *wasmer.Function 59 | 60 | // AssemblyScript functions 61 | abort *wasmer.Function 62 | 63 | // WASI functions 64 | fdWrite *wasmer.Function 65 | fdClose *wasmer.Function 66 | fdFdstatGet *wasmer.Function 67 | fdPrestatGet *wasmer.Function 68 | fdPrestatDirName *wasmer.Function 69 | fdRead *wasmer.Function 70 | fdSeek *wasmer.Function 71 | pathOpen *wasmer.Function 72 | procExit *wasmer.Function 73 | argsSizesGet *wasmer.Function 74 | argsGet *wasmer.Function 75 | clockTimeGet *wasmer.Function 76 | environSizesGet *wasmer.Function 77 | environGet *wasmer.Function 78 | 79 | // closed is atomically updated to ensure Close is only invoked once. 80 | closed uint32 81 | } 82 | 83 | invokeContext struct { 84 | ctx context.Context 85 | operation string 86 | 87 | guestReq []byte 88 | guestResp []byte 89 | guestErr string 90 | 91 | hostResp []byte 92 | hostErr error 93 | } 94 | ) 95 | 96 | // Ensure the engine conforms to the waPC interface. 97 | var _ = (wapc.Module)((*Module)(nil)) 98 | var _ = (wapc.Instance)((*Instance)(nil)) 99 | 100 | var engineInstance = engine{newRuntime: DefaultRuntime} 101 | 102 | // Engine returns a new wapc.Engine which uses the DefaultRuntime. 103 | func Engine() wapc.Engine { 104 | return &engineInstance 105 | } 106 | 107 | // NewRuntime returns a new wazero runtime which is called when the New method 108 | // on wapc.Engine is called. The result is closed upon wapc.Module Close. 109 | type NewRuntime func() (*wasmer.Engine, error) 110 | 111 | // EngineWithRuntime allows you to customize or return an alternative to 112 | // DefaultRuntime, 113 | func EngineWithRuntime(newRuntime NewRuntime) wapc.Engine { 114 | return &engine{newRuntime: newRuntime} 115 | } 116 | 117 | // DefaultRuntime implements NewRuntime by returning a wasmer Engine 118 | func DefaultRuntime() (*wasmer.Engine, error) { 119 | return wasmer.NewEngine(), nil 120 | } 121 | 122 | func (e *engine) Name() string { 123 | return "wasmer" 124 | } 125 | 126 | // New implements the same method as documented on wapc.Engine. 127 | func (e *engine) New(_ context.Context, host wapc.HostCallHandler, guest []byte, config *wapc.ModuleConfig) (mod wapc.Module, err error) { 128 | r, err := e.newRuntime() 129 | if err != nil { 130 | return nil, err 131 | } 132 | store := wasmer.NewStore(r) 133 | 134 | module, err := wasmer.NewModule(store, guest) 135 | if err != nil { 136 | return nil, err 137 | } 138 | 139 | return &Module{ 140 | engine: r, 141 | store: store, 142 | module: module, 143 | logger: config.Logger, 144 | stdout: config.Stdout, 145 | stderr: config.Stderr, 146 | hostCallHandler: host, 147 | }, nil 148 | } 149 | 150 | // Unwrap allows access to wasmer-specific features. 151 | func (m *Module) Unwrap() *wasmer.Module { 152 | return m.module 153 | } 154 | 155 | // UnwrapStore allows access to wasmer-specific features. 156 | func (m *Module) UnwrapStore() *wasmer.Store { 157 | return m.store 158 | } 159 | 160 | // Instantiate creates a single instance of the module with its own memory. 161 | func (m *Module) Instantiate(ctx context.Context) (wapc.Instance, error) { 162 | if closed := atomic.LoadUint32(&m.closed); closed != 0 { 163 | return nil, errors.New("cannot Instantiate when a module is closed") 164 | } 165 | // Note: There's still a race below, even if the above check is still useful. 166 | 167 | instance := Instance{ 168 | m: m, 169 | } 170 | importObject := wasmer.NewImportObject() 171 | importObject.Register("env", instance.envRuntime()) 172 | importObject.Register("wapc", instance.wapcRuntime()) 173 | wasiRuntime := instance.wasiRuntime() 174 | importObject.Register("wasi_unstable", wasiRuntime) 175 | importObject.Register("wasi_snapshot_preview1", wasiRuntime) 176 | importObject.Register("wasi", wasiRuntime) 177 | 178 | inst, err := wasmer.NewInstance(m.module, importObject) 179 | if err != nil { 180 | return nil, err 181 | } 182 | instance.inst = inst 183 | 184 | instance.mem, err = inst.Exports.GetMemory("memory") 185 | if err != nil { 186 | return nil, err 187 | } 188 | 189 | instance.guestCall, err = inst.Exports.GetFunction("__guest_call") 190 | if err != nil { 191 | return nil, errors.New("could not find exported function '__guest_call'") 192 | } 193 | 194 | // Initialize the instance of it exposes a `_start` function. 195 | initFunctions := []string{"_start", "wapc_init"} 196 | for _, initFunction := range initFunctions { 197 | if initFn, err := inst.Exports.GetFunction(initFunction); err == nil { 198 | context := invokeContext{ 199 | ctx: ctx, 200 | } 201 | instance.context = &context 202 | 203 | if _, err := initFn(); err != nil { 204 | return nil, fmt.Errorf("could not initialize instance: %w", err) 205 | } 206 | } 207 | } 208 | 209 | return &instance, nil 210 | } 211 | 212 | // Unwrap allows access to wasmer-specific features. 213 | func (i *Instance) Unwrap() *wasmer.Instance { 214 | return i.inst 215 | } 216 | 217 | // Unwrap allows access to wasmer-specific features. 218 | func (i *Instance) UnwrapStore() *wasmer.Store { 219 | return i.m.store 220 | } 221 | 222 | func (i *Instance) envRuntime() map[string]wasmer.IntoExtern { 223 | // abort is for assemblyscript 224 | i.abort = wasmer.NewFunction( 225 | i.m.store, 226 | wasmer.NewFunctionType( 227 | wasmer.NewValueTypes(wasmer.I32, wasmer.I32, wasmer.I32, wasmer.I32), 228 | wasmer.NewValueTypes()), 229 | func(args []wasmer.Value) ([]wasmer.Value, error) { 230 | return []wasmer.Value{}, nil 231 | }, 232 | ) 233 | return map[string]wasmer.IntoExtern{ 234 | "abort": i.abort, 235 | } 236 | } 237 | 238 | func (i *Instance) wapcRuntime() map[string]wasmer.IntoExtern { 239 | i.guestRequest = wasmer.NewFunction( 240 | i.m.store, 241 | wasmer.NewFunctionType(wasmer.NewValueTypes(wasmer.I32, wasmer.I32), wasmer.NewValueTypes()), 242 | func(args []wasmer.Value) ([]wasmer.Value, error) { 243 | operationPtr := args[0].I32() 244 | payloadPtr := args[1].I32() 245 | data := i.mem.Data() 246 | copy(data[operationPtr:], i.context.operation) 247 | copy(data[payloadPtr:], i.context.guestReq) 248 | return []wasmer.Value{}, nil 249 | }, 250 | ) 251 | i.guestResponse = wasmer.NewFunction( 252 | i.m.store, 253 | wasmer.NewFunctionType(wasmer.NewValueTypes(wasmer.I32, wasmer.I32), wasmer.NewValueTypes()), 254 | func(args []wasmer.Value) ([]wasmer.Value, error) { 255 | ptr := args[0].I32() 256 | len := args[1].I32() 257 | data := i.mem.Data() 258 | buf := make([]byte, len) 259 | copy(buf, data[ptr:ptr+len]) 260 | i.context.guestResp = buf 261 | return []wasmer.Value{}, nil 262 | }, 263 | ) 264 | i.guestError = wasmer.NewFunction( 265 | i.m.store, 266 | wasmer.NewFunctionType(wasmer.NewValueTypes(wasmer.I32, wasmer.I32), wasmer.NewValueTypes()), 267 | func(args []wasmer.Value) ([]wasmer.Value, error) { 268 | ptr := args[0].I32() 269 | len := args[1].I32() 270 | data := i.mem.Data() 271 | cp := make([]byte, len) 272 | copy(cp, data[ptr:ptr+len]) 273 | i.context.guestErr = string(cp) 274 | return []wasmer.Value{}, nil 275 | }, 276 | ) 277 | i.hostCall = wasmer.NewFunction( 278 | i.m.store, 279 | wasmer.NewFunctionType(wasmer.NewValueTypes(wasmer.I32, wasmer.I32, wasmer.I32, wasmer.I32, wasmer.I32, wasmer.I32, wasmer.I32, wasmer.I32), wasmer.NewValueTypes(wasmer.I32)), 280 | func(args []wasmer.Value) ([]wasmer.Value, error) { 281 | bindingPtr := args[0].I32() 282 | bindingLen := args[1].I32() 283 | namespacePtr := args[2].I32() 284 | namespaceLen := args[3].I32() 285 | operationPtr := args[4].I32() 286 | operationLen := args[5].I32() 287 | payloadPtr := args[6].I32() 288 | payloadLen := args[7].I32() 289 | 290 | if i.m.hostCallHandler == nil { 291 | return []wasmer.Value{wasmer.NewI32(0)}, nil 292 | } 293 | 294 | data := i.mem.Data() 295 | binding := string(data[bindingPtr : bindingPtr+bindingLen]) 296 | namespace := string(data[namespacePtr : namespacePtr+namespaceLen]) 297 | operation := string(data[operationPtr : operationPtr+operationLen]) 298 | payload := make([]byte, payloadLen) 299 | copy(payload, data[payloadPtr:payloadPtr+payloadLen]) 300 | 301 | i.context.hostResp, i.context.hostErr = i.m.hostCallHandler(i.context.ctx, binding, namespace, operation, payload) 302 | if i.context.hostErr != nil { 303 | return []wasmer.Value{wasmer.NewI32(0)}, nil 304 | } 305 | 306 | return []wasmer.Value{wasmer.NewI32(1)}, nil 307 | }, 308 | ) 309 | i.hostResponseLen = wasmer.NewFunction( 310 | i.m.store, 311 | wasmer.NewFunctionType(wasmer.NewValueTypes(), wasmer.NewValueTypes(wasmer.I32)), 312 | func(args []wasmer.Value) ([]wasmer.Value, error) { 313 | return []wasmer.Value{wasmer.NewI32(int32(len(i.context.hostResp)))}, nil 314 | }, 315 | ) 316 | i.hostResponse = wasmer.NewFunction( 317 | i.m.store, 318 | wasmer.NewFunctionType(wasmer.NewValueTypes(wasmer.I32), wasmer.NewValueTypes()), 319 | func(args []wasmer.Value) ([]wasmer.Value, error) { 320 | if i.context.hostResp != nil { 321 | ptr := args[0].I32() 322 | data := i.mem.Data() 323 | copy(data[ptr:], i.context.hostResp) 324 | } 325 | return []wasmer.Value{}, nil 326 | }, 327 | ) 328 | i.hostErrorLen = wasmer.NewFunction( 329 | i.m.store, 330 | wasmer.NewFunctionType(wasmer.NewValueTypes(), wasmer.NewValueTypes(wasmer.I32)), 331 | func(args []wasmer.Value) ([]wasmer.Value, error) { 332 | if i.context.hostErr == nil { 333 | return []wasmer.Value{wasmer.NewI32(0)}, nil 334 | } 335 | errStr := i.context.hostErr.Error() 336 | return []wasmer.Value{wasmer.NewI32(int32(len(errStr)))}, nil 337 | }, 338 | ) 339 | i.hostError = wasmer.NewFunction( 340 | i.m.store, 341 | wasmer.NewFunctionType(wasmer.NewValueTypes(wasmer.I32), wasmer.NewValueTypes()), 342 | func(args []wasmer.Value) ([]wasmer.Value, error) { 343 | if i.context.hostErr == nil { 344 | return []wasmer.Value{}, nil 345 | } 346 | 347 | ptr := args[0].I32() 348 | errStr := i.context.hostErr.Error() 349 | data := i.mem.Data() 350 | copy(data[ptr:], errStr) 351 | return []wasmer.Value{}, nil 352 | }, 353 | ) 354 | i.consoleLog = wasmer.NewFunction( 355 | i.m.store, 356 | wasmer.NewFunctionType(wasmer.NewValueTypes(wasmer.I32, wasmer.I32), wasmer.NewValueTypes()), 357 | func(args []wasmer.Value) ([]wasmer.Value, error) { 358 | if i.m.logger != nil { 359 | data := i.mem.Data() 360 | ptr := args[0].I32() 361 | len := args[1].I32() 362 | msg := string(data[ptr : ptr+len]) 363 | i.m.logger(msg) 364 | } 365 | return []wasmer.Value{}, nil 366 | }, 367 | ) 368 | return map[string]wasmer.IntoExtern{ 369 | "__guest_request": i.guestRequest, 370 | "__guest_response": i.guestResponse, 371 | "__guest_error": i.guestError, 372 | "__host_call": i.hostCall, 373 | "__host_response_len": i.hostResponseLen, 374 | "__host_response": i.hostResponse, 375 | "__host_error_len": i.hostErrorLen, 376 | "__host_error": i.hostError, 377 | "__console_log": i.consoleLog, 378 | } 379 | } 380 | 381 | func (i *Instance) wasiRuntime() map[string]wasmer.IntoExtern { 382 | i.fdWrite = wasmer.NewFunction( 383 | i.m.store, 384 | wasmer.NewFunctionType(wasmer.NewValueTypes(wasmer.I32, wasmer.I32, wasmer.I32, wasmer.I32), wasmer.NewValueTypes(wasmer.I32)), 385 | func(args []wasmer.Value) ([]wasmer.Value, error) { 386 | fileDescriptor := args[0].I32() 387 | iovsPtr := args[1].I32() 388 | iovsLen := args[2].I32() 389 | writtenPtr := args[3].I32() 390 | 391 | // Only writing to stdio is supported 392 | var writer io.Writer 393 | switch fileDescriptor { 394 | case 1: 395 | writer = i.m.stdout 396 | case 2: 397 | writer = i.m.stderr 398 | } 399 | 400 | if writer == nil { 401 | return []wasmer.Value{wasmer.NewI32(0)}, nil 402 | } 403 | 404 | if i.m.stdout == nil { 405 | return []wasmer.Value{wasmer.NewI32(0)}, nil 406 | } 407 | data := i.mem.Data() 408 | iov := data[iovsPtr:] 409 | bytesWritten := uint32(0) 410 | 411 | for iovsLen > 0 { 412 | iovsLen-- 413 | base := binary.LittleEndian.Uint32(iov) 414 | length := binary.LittleEndian.Uint32(iov[4:]) 415 | stringBytes := data[base : base+length] 416 | _, _ = writer.Write(stringBytes) 417 | iov = iov[8:] 418 | bytesWritten += length 419 | } 420 | 421 | binary.LittleEndian.PutUint32(data[writtenPtr:], bytesWritten) 422 | 423 | return []wasmer.Value{wasmer.NewI32(int32(bytesWritten))}, nil 424 | }, 425 | ) 426 | 427 | i.fdClose = wasmer.NewFunction( 428 | i.m.store, 429 | wasmer.NewFunctionType(wasmer.NewValueTypes(wasmer.I32), wasmer.NewValueTypes(wasmer.I32)), 430 | func(args []wasmer.Value) ([]wasmer.Value, error) { 431 | // Not implemented. 432 | return []wasmer.Value{wasmer.NewI32(8)}, nil 433 | }, 434 | ) 435 | 436 | i.fdPrestatGet = wasmer.NewFunction( 437 | i.m.store, 438 | wasmer.NewFunctionType(wasmer.NewValueTypes(wasmer.I32, wasmer.I32), wasmer.NewValueTypes(wasmer.I32)), 439 | func(args []wasmer.Value) ([]wasmer.Value, error) { 440 | // Not implemented. 441 | return []wasmer.Value{wasmer.NewI32(8)}, nil 442 | }, 443 | ) 444 | 445 | i.fdPrestatDirName = wasmer.NewFunction( 446 | i.m.store, 447 | wasmer.NewFunctionType(wasmer.NewValueTypes(wasmer.I32, wasmer.I32, wasmer.I32), wasmer.NewValueTypes(wasmer.I32)), 448 | func(args []wasmer.Value) ([]wasmer.Value, error) { 449 | // Not implemented. 450 | return []wasmer.Value{wasmer.NewI32(8)}, nil 451 | }, 452 | ) 453 | 454 | i.fdRead = wasmer.NewFunction( 455 | i.m.store, 456 | wasmer.NewFunctionType(wasmer.NewValueTypes(wasmer.I32, wasmer.I32, wasmer.I32, wasmer.I32), wasmer.NewValueTypes(wasmer.I32)), 457 | func(args []wasmer.Value) ([]wasmer.Value, error) { 458 | // Not implemented. 459 | return []wasmer.Value{wasmer.NewI32(8)}, nil 460 | }, 461 | ) 462 | 463 | i.fdSeek = wasmer.NewFunction( 464 | i.m.store, 465 | wasmer.NewFunctionType(wasmer.NewValueTypes(wasmer.I32, wasmer.I64, wasmer.I32, wasmer.I32), wasmer.NewValueTypes(wasmer.I32)), 466 | func(args []wasmer.Value) ([]wasmer.Value, error) { 467 | // Not implemented. 468 | return []wasmer.Value{wasmer.NewI32(8)}, nil 469 | }, 470 | ) 471 | 472 | i.pathOpen = wasmer.NewFunction( 473 | i.m.store, 474 | wasmer.NewFunctionType(wasmer.NewValueTypes(wasmer.I32, wasmer.I32, wasmer.I32, wasmer.I32, wasmer.I32, 475 | wasmer.I64, wasmer.I64, wasmer.I32, wasmer.I32), wasmer.NewValueTypes(wasmer.I32)), 476 | func(args []wasmer.Value) ([]wasmer.Value, error) { 477 | // Not implemented. 478 | return []wasmer.Value{wasmer.NewI32(28)}, nil 479 | }, 480 | ) 481 | 482 | i.procExit = wasmer.NewFunction( 483 | i.m.store, 484 | wasmer.NewFunctionType(wasmer.NewValueTypes(wasmer.I32), wasmer.NewValueTypes()), 485 | func(args []wasmer.Value) ([]wasmer.Value, error) { 486 | // Not implemented. 487 | return []wasmer.Value{wasmer.NewI32(0)}, nil 488 | }, 489 | ) 490 | 491 | i.fdFdstatGet = wasmer.NewFunction( 492 | i.m.store, 493 | wasmer.NewFunctionType(wasmer.NewValueTypes(wasmer.I32, wasmer.I32), wasmer.NewValueTypes(wasmer.I32)), 494 | func(args []wasmer.Value) ([]wasmer.Value, error) { 495 | // Not implemented. 496 | return []wasmer.Value{wasmer.NewI32(8)}, nil 497 | }, 498 | ) 499 | 500 | i.argsSizesGet = wasmer.NewFunction( 501 | i.m.store, 502 | wasmer.NewFunctionType(wasmer.NewValueTypes(wasmer.I32, wasmer.I32), wasmer.NewValueTypes(wasmer.I32)), 503 | func(args []wasmer.Value) ([]wasmer.Value, error) { 504 | // Not implemented. 505 | argc := args[0].I32() 506 | argvBufSize := args[1].I32() 507 | data := i.mem.Data() 508 | 509 | binary.LittleEndian.PutUint32(data[argc:], 0) 510 | binary.LittleEndian.PutUint32(data[argvBufSize:], 0) 511 | 512 | return []wasmer.Value{wasmer.NewI32(0)}, nil 513 | }, 514 | ) 515 | 516 | i.argsGet = wasmer.NewFunction( 517 | i.m.store, 518 | wasmer.NewFunctionType(wasmer.NewValueTypes(wasmer.I32, wasmer.I32), wasmer.NewValueTypes(wasmer.I32)), 519 | func(args []wasmer.Value) ([]wasmer.Value, error) { 520 | // Not implemented. 521 | return []wasmer.Value{wasmer.NewI32(0)}, nil 522 | }, 523 | ) 524 | 525 | i.environSizesGet = wasmer.NewFunction( 526 | i.m.store, 527 | wasmer.NewFunctionType(wasmer.NewValueTypes(wasmer.I32, wasmer.I32), wasmer.NewValueTypes(wasmer.I32)), 528 | func(args []wasmer.Value) ([]wasmer.Value, error) { 529 | // Not implemented. 530 | return []wasmer.Value{wasmer.NewI32(0)}, nil 531 | }, 532 | ) 533 | 534 | i.environGet = wasmer.NewFunction( 535 | i.m.store, 536 | wasmer.NewFunctionType(wasmer.NewValueTypes(wasmer.I32, wasmer.I32), wasmer.NewValueTypes(wasmer.I32)), 537 | func(args []wasmer.Value) ([]wasmer.Value, error) { 538 | // Not implemented. 539 | return []wasmer.Value{wasmer.NewI32(0)}, nil 540 | }, 541 | ) 542 | 543 | i.clockTimeGet = wasmer.NewFunction( 544 | i.m.store, 545 | wasmer.NewFunctionType(wasmer.NewValueTypes(wasmer.I32, wasmer.I64, wasmer.I32), wasmer.NewValueTypes(wasmer.I32)), 546 | func(args []wasmer.Value) ([]wasmer.Value, error) { 547 | //(ctx *wasm.HostFunctionCallContext, id uint32, precision uint64, timestampPtr uint32) (err Errno) { 548 | data := i.mem.Data() 549 | timestampPtr := args[2].I32() 550 | nanos := uint64(time.Now().UnixNano()) 551 | binary.LittleEndian.PutUint64(data[timestampPtr:], nanos) 552 | return []wasmer.Value{wasmer.NewI32(0)}, nil 553 | }, 554 | ) 555 | 556 | return map[string]wasmer.IntoExtern{ 557 | "fd_write": i.fdWrite, 558 | "fd_close": i.fdClose, 559 | "fd_fdstat_get": i.fdFdstatGet, 560 | "fd_prestat_get": i.fdPrestatGet, 561 | "fd_prestat_dir_name": i.fdPrestatDirName, 562 | "fd_read": i.fdRead, 563 | "fd_seek": i.fdSeek, 564 | "path_open": i.pathOpen, 565 | "proc_exit": i.procExit, 566 | "args_sizes_get": i.argsSizesGet, 567 | "args_get": i.argsGet, 568 | "environ_sizes_get": i.environSizesGet, 569 | "environ_get": i.environGet, 570 | "clock_time_get": i.clockTimeGet, 571 | } 572 | } 573 | 574 | // MemorySize returns the memory length of the underlying instance. 575 | func (i *Instance) MemorySize() uint32 { 576 | return uint32(i.mem.DataSize()) 577 | } 578 | 579 | // Invoke calls `operation` with `payload` on the module and returns a byte slice payload. 580 | func (i *Instance) Invoke(ctx context.Context, operation string, payload []byte) ([]byte, error) { 581 | if closed := atomic.LoadUint32(&i.closed); closed != 0 { 582 | return nil, fmt.Errorf("error invoking guest with closed instance") 583 | } 584 | // Note: There's still a race below, even if the above check is still useful. 585 | 586 | context := invokeContext{ 587 | ctx: ctx, 588 | operation: operation, 589 | guestReq: payload, 590 | } 591 | i.context = &context 592 | 593 | successValue, err := i.guestCall(len(operation), len(payload)) 594 | if err != nil { 595 | return nil, fmt.Errorf("error invoking guest: %w", err) 596 | } 597 | if context.guestErr != "" { 598 | return nil, errors.New(context.guestErr) 599 | } 600 | 601 | successI32, _ := successValue.(int32) 602 | success := successI32 == 1 603 | 604 | if success { 605 | return context.guestResp, nil 606 | } 607 | 608 | return nil, fmt.Errorf("call to %q was unsuccessful", operation) 609 | } 610 | 611 | // Close closes the single instance. This should be called before calling `Close` on the Module itself. 612 | func (i *Instance) Close(context.Context) error { 613 | if !atomic.CompareAndSwapUint32(&i.closed, 0, 1) { 614 | return nil 615 | } 616 | 617 | // Explicitly release references on wasmer types, so they can be GC'ed. 618 | i.mem = nil 619 | i.context = nil 620 | i.guestRequest = nil 621 | i.guestResponse = nil 622 | i.guestError = nil 623 | i.hostCall = nil 624 | i.hostResponseLen = nil 625 | i.hostResponse = nil 626 | i.hostErrorLen = nil 627 | i.hostError = nil 628 | i.consoleLog = nil 629 | i.abort = nil 630 | i.fdWrite = nil 631 | if inst := i.inst; inst != nil { 632 | inst.Close() 633 | } 634 | return nil 635 | } 636 | 637 | // Close closes the module. This should be called after calling `Close` on any instances that were created. 638 | func (m *Module) Close(context.Context) error { 639 | if !atomic.CompareAndSwapUint32(&m.closed, 0, 1) { 640 | return nil 641 | } 642 | 643 | // Explicitly release references on wasmer types so they can be GC'ed. 644 | if mod := m.module; mod != nil { 645 | mod.Close() 646 | m.module = nil 647 | } 648 | if store := m.store; store != nil { 649 | store.Close() 650 | m.store = nil 651 | } 652 | m.engine = nil 653 | return nil 654 | } 655 | -------------------------------------------------------------------------------- /engines/wasmer/wasmer_test.go: -------------------------------------------------------------------------------- 1 | package wasmer 2 | 3 | // import ( 4 | // "context" 5 | // "errors" 6 | // "log" 7 | // "os" 8 | // "testing" 9 | 10 | // "github.com/wasmerio/wasmer-go/wasmer" 11 | 12 | // "github.com/wapc/wapc-go" 13 | // ) 14 | 15 | // // testCtx is an arbitrary, non-default context. Non-nil also prevents linter errors. 16 | // var testCtx = context.WithValue(context.Background(), struct{}{}, "arbitrary") 17 | 18 | // var guest []byte 19 | // var mc = &wapc.ModuleConfig{ 20 | // Logger: wapc.PrintlnLogger, 21 | // Stdout: os.Stdout, 22 | // Stderr: os.Stderr, 23 | // } 24 | 25 | // // TestMain ensures we can read the example wasm prior to running unit tests. 26 | // func TestMain(m *testing.M) { 27 | // var err error 28 | // guest, err = os.ReadFile("../../testdata/go/hello.wasm") 29 | // if err != nil { 30 | // log.Panicln(err) 31 | // } 32 | // os.Exit(m.Run()) 33 | // } 34 | 35 | // func TestEngine_WithEngine(t *testing.T) { 36 | // t.Run("ok", func(t *testing.T) { 37 | // expected := wasmer.NewEngine() 38 | 39 | // e := EngineWithRuntime(func() (*wasmer.Engine, error) { 40 | // return expected, nil 41 | // }) 42 | 43 | // m, err := e.New(testCtx, wapc.NoOpHostCallHandler, guest, mc) 44 | // if err != nil { 45 | // t.Errorf("Error creating module - %v", err) 46 | // } 47 | // defer m.Close(testCtx) 48 | 49 | // if have := m.(*Module).engine; have != expected { 50 | // t.Errorf("Unexpected engine, have %v, expected %v", have, expected) 51 | // } 52 | // }) 53 | 54 | // t.Run("nil not ok", func(t *testing.T) { 55 | // expectedErr := "function set by WithEngine returned nil" 56 | // e := EngineWithRuntime(func() (*wasmer.Engine, error) { 57 | // return nil, errors.New(expectedErr) 58 | // }) 59 | 60 | // if _, err := e.New(testCtx, wapc.NoOpHostCallHandler, guest, mc); err.Error() != expectedErr { 61 | // t.Errorf("Unexpected error, have %v, expected %v", err, expectedErr) 62 | // } 63 | // }) 64 | // } 65 | 66 | // func TestModule_Unwrap(t *testing.T) { 67 | // m, err := EngineWithRuntime(DefaultRuntime).New(testCtx, wapc.NoOpHostCallHandler, guest, mc) 68 | // if err != nil { 69 | // t.Errorf("Error creating module - %v", err) 70 | // } 71 | // defer m.Close(testCtx) 72 | 73 | // mod := m.(*Module) 74 | // expected := mod.module 75 | // if have := mod.Unwrap(); have != expected { 76 | // t.Errorf("Unexpected module, have %v, expected %v", have, expected) 77 | // } 78 | // } 79 | 80 | // func TestInstance_Unwrap(t *testing.T) { 81 | // m, err := EngineWithRuntime(DefaultRuntime).New(testCtx, wapc.NoOpHostCallHandler, guest, mc) 82 | // if err != nil { 83 | // t.Errorf("Error creating module - %v", err) 84 | // } 85 | // defer m.Close(testCtx) 86 | 87 | // i, err := m.Instantiate(testCtx) 88 | // if err != nil { 89 | // t.Errorf("Error creating instance - %v", err) 90 | // } 91 | // defer m.Close(testCtx) 92 | 93 | // inst := i.(*Instance) 94 | // expected := inst.inst 95 | // if have := inst.Unwrap(); have != expected { 96 | // t.Errorf("Unexpected instance, have %v, expected %v", have, expected) 97 | // } 98 | // } 99 | -------------------------------------------------------------------------------- /engines/wasmtime/engine_test.go: -------------------------------------------------------------------------------- 1 | package wasmtime_test 2 | 3 | // import ( 4 | // "testing" 5 | 6 | // "github.com/wapc/wapc-go/engines/tests" 7 | // "github.com/wapc/wapc-go/engines/wasmtime" 8 | // ) 9 | 10 | // func TestGuest(t *testing.T) { 11 | // tests.TestGuest(t, wasmtime.Engine()) 12 | // } 13 | 14 | // func TestModuleBadBytes(t *testing.T) { 15 | // tests.TestModuleBadBytes(t, wasmtime.Engine()) 16 | // } 17 | 18 | // func TestModule(t *testing.T) { 19 | // tests.TestModule(t, wasmtime.Engine()) 20 | // } 21 | 22 | // func TestGuestsWithPool(t *testing.T) { 23 | // tests.TestGuestsWithPool(t, wasmtime.Engine()) 24 | // } 25 | -------------------------------------------------------------------------------- /engines/wasmtime/example_test.go: -------------------------------------------------------------------------------- 1 | //go:build amd64 2 | // +build amd64 3 | 4 | package wasmtime 5 | 6 | // import ( 7 | // "context" 8 | // "log" 9 | 10 | // "github.com/bytecodealliance/wasmtime-go" 11 | 12 | // "github.com/wapc/wapc-go" 13 | // ) 14 | 15 | // // This shows how to customize the underlying wasmer engine used by waPC. 16 | // func Example_custom() { 17 | // // Set up the context used to instantiate the engine. 18 | // ctx := context.Background() 19 | // cfg := wasmtime.NewConfig() 20 | // cfg.SetWasmMemory64(true) 21 | // e := EngineWithRuntime(func() (*wasmtime.Engine, error) { 22 | // return wasmtime.NewEngineWithConfig(cfg), nil 23 | // }) 24 | // // Configure waPC to use a specific wasmer feature. 25 | 26 | // // Instantiate a module normally. 27 | // m, err := e.New(ctx, wapc.NoOpHostCallHandler, guest, mc) 28 | // if err != nil { 29 | // log.Panicf("Error creating module - %v\n", err) 30 | // } 31 | // defer m.Close(ctx) 32 | 33 | // // Output: 34 | // } 35 | -------------------------------------------------------------------------------- /engines/wasmtime/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/wapc/wapc-go/engines/wasmtime 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.23.4 6 | 7 | require ( 8 | github.com/bytecodealliance/wasmtime-go v1.0.0 9 | github.com/wapc/wapc-go v0.0.0 10 | ) 11 | 12 | require github.com/Workiva/go-datastructures v1.1.5 // indirect 13 | 14 | replace github.com/wapc/wapc-go => ../.. 15 | -------------------------------------------------------------------------------- /engines/wasmtime/go.sum: -------------------------------------------------------------------------------- 1 | github.com/Workiva/go-datastructures v1.1.5 h1:5YfhQ4ry7bZc2Mc7R0YZyYwpf5c6t1cEFvdAhd6Mkf4= 2 | github.com/Workiva/go-datastructures v1.1.5/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A= 3 | github.com/bytecodealliance/wasmtime-go v1.0.0 h1:9u9gqaUiaJeN5IoD1L7egD8atOnTGyJcNp8BhkL9cUU= 4 | github.com/bytecodealliance/wasmtime-go v1.0.0/go.mod h1:jjlqQbWUfVSbehpErw3UoWFndBXRRMvfikYH6KsCwOg= 5 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 7 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= 9 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 10 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 11 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 12 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 13 | github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= 14 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 15 | github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg= 16 | github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q= 17 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 18 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 19 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 20 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 21 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 22 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 23 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 24 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 25 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 26 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 27 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 28 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 29 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 30 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 31 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 32 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 33 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 34 | golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 35 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 36 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 37 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 38 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 39 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 40 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 41 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 42 | -------------------------------------------------------------------------------- /engines/wasmtime/wasmtime.go: -------------------------------------------------------------------------------- 1 | //go:build (((amd64 || arm64) && !windows) || (amd64 && windows)) && cgo && !wasmer 2 | 3 | package wasmtime 4 | 5 | import ( 6 | "context" 7 | "errors" 8 | "fmt" 9 | "sync/atomic" 10 | 11 | "github.com/bytecodealliance/wasmtime-go" 12 | 13 | "github.com/wapc/wapc-go" 14 | ) 15 | 16 | type ( 17 | engine struct { 18 | newRuntime NewRuntime 19 | } 20 | 21 | // Module represents a compile waPC module. 22 | Module struct { 23 | hostCallHandler wapc.HostCallHandler 24 | 25 | engine *wasmtime.Engine 26 | store *wasmtime.Store 27 | module *wasmtime.Module 28 | 29 | logger wapc.Logger 30 | 31 | // closed is atomically updated to ensure Close is only invoked once. 32 | closed uint32 33 | } 34 | 35 | // Instance is a single instantiation of a module with its own memory. 36 | Instance struct { 37 | m *Module 38 | guestCall *wasmtime.Func 39 | 40 | inst *wasmtime.Instance 41 | mem *wasmtime.Memory 42 | 43 | context *invokeContext 44 | 45 | // waPC functions 46 | guestRequest *wasmtime.Func 47 | guestResponse *wasmtime.Func 48 | guestError *wasmtime.Func 49 | hostCall *wasmtime.Func 50 | hostResponseLen *wasmtime.Func 51 | hostResponse *wasmtime.Func 52 | hostErrorLen *wasmtime.Func 53 | hostError *wasmtime.Func 54 | consoleLog *wasmtime.Func 55 | 56 | // AssemblyScript functions 57 | abort *wasmtime.Func 58 | 59 | // closed is atomically updated to ensure Close is only invoked once. 60 | closed uint32 61 | } 62 | 63 | invokeContext struct { 64 | ctx context.Context 65 | operation string 66 | 67 | guestReq []byte 68 | guestResp []byte 69 | guestErr string 70 | 71 | hostResp []byte 72 | hostErr error 73 | } 74 | ) 75 | 76 | // Ensure the engine conforms to the waPC interface. 77 | var _ = (wapc.Module)((*Module)(nil)) 78 | var _ = (wapc.Instance)((*Instance)(nil)) 79 | 80 | var engineInstance = engine{newRuntime: DefaultRuntime} 81 | 82 | // Engine returns a new wapc.Engine which uses the DefaultRuntime. 83 | func Engine() wapc.Engine { 84 | return &engineInstance 85 | } 86 | 87 | // NewRuntime returns a new wazero runtime which is called when the New method 88 | // on wapc.Engine is called. The result is closed upon wapc.Module Close. 89 | type NewRuntime func() (*wasmtime.Engine, error) 90 | 91 | // EngineWithRuntime allows you to customize or return an alternative to 92 | // DefaultRuntime, 93 | func EngineWithRuntime(newRuntime NewRuntime) wapc.Engine { 94 | return &engine{newRuntime: newRuntime} 95 | } 96 | 97 | // DefaultRuntime implements NewRuntime by returning a wasmtime Engine 98 | func DefaultRuntime() (*wasmtime.Engine, error) { 99 | return wasmtime.NewEngine(), nil 100 | } 101 | 102 | func (e *engine) Name() string { 103 | return "wasmtime" 104 | } 105 | 106 | // New implements the same method as documented on wapc.Engine. 107 | func (e *engine) New(_ context.Context, host wapc.HostCallHandler, guest []byte, config *wapc.ModuleConfig) (mod wapc.Module, err error) { 108 | r, err := e.newRuntime() 109 | if err != nil { 110 | return nil, err 111 | } 112 | store := wasmtime.NewStore(r) 113 | wasiConfig := wasmtime.NewWasiConfig() 114 | // Note: wasmtime does not support writer-based stdout/stderr 115 | store.SetWasi(wasiConfig) 116 | 117 | module, err := wasmtime.NewModule(r, guest) 118 | if err != nil { 119 | return nil, err 120 | } 121 | 122 | return &Module{ 123 | engine: r, 124 | store: store, 125 | module: module, 126 | logger: config.Logger, 127 | hostCallHandler: host, 128 | }, nil 129 | } 130 | 131 | // Instantiate creates a single instance of the module with its own memory. 132 | func (m *Module) Instantiate(ctx context.Context) (wapc.Instance, error) { 133 | if closed := atomic.LoadUint32(&m.closed); closed != 0 { 134 | return nil, errors.New("cannot Instantiate when a module is closed") 135 | } 136 | // Note: There's still a race below, even if the above check is still useful. 137 | 138 | instance := Instance{ 139 | m: m, 140 | } 141 | 142 | linker := wasmtime.NewLinker(m.engine) 143 | if err := linker.DefineWasi(); err != nil { 144 | return nil, err 145 | } 146 | 147 | for name, fn := range instance.envRuntime() { 148 | if err := linker.Define("env", name, fn); err != nil { 149 | return nil, fmt.Errorf("Cannot define function env.%s: %w", name, err) 150 | } 151 | } 152 | 153 | for name, fn := range instance.wapcRuntime() { 154 | if err := linker.Define("wapc", name, fn); err != nil { 155 | return nil, fmt.Errorf("Cannot define function wapc.%s: %w", name, err) 156 | } 157 | } 158 | 159 | inst, err := linker.Instantiate(m.store, m.module) 160 | if err != nil { 161 | return nil, err 162 | } 163 | instance.inst = inst 164 | 165 | instance.mem = inst.GetExport(m.store, "memory").Memory() 166 | if err != nil { 167 | return nil, err 168 | } 169 | 170 | instance.guestCall = inst.GetFunc(m.store, "__guest_call") 171 | if instance.guestCall == nil { 172 | return nil, errors.New("could not find exported function '__guest_call'") 173 | } 174 | 175 | // Initialize the instance of it exposes a `_start` function. 176 | initFunctions := []string{"_start", "wapc_init"} 177 | for _, initFunction := range initFunctions { 178 | if initFn := inst.GetFunc(m.store, initFunction); initFn != nil { 179 | context := invokeContext{ 180 | ctx: ctx, 181 | } 182 | instance.context = &context 183 | 184 | if _, err := initFn.Call(m.store); err != nil { 185 | return nil, fmt.Errorf("could not initialize instance: %w", err) 186 | } 187 | } 188 | } 189 | 190 | return &instance, nil 191 | } 192 | 193 | func (i *Instance) envRuntime() map[string]*wasmtime.Func { 194 | // abort is for assemblyscript 195 | params := []*wasmtime.ValType{ 196 | wasmtime.NewValType(wasmtime.KindI32), 197 | wasmtime.NewValType(wasmtime.KindI32), 198 | wasmtime.NewValType(wasmtime.KindI32), 199 | wasmtime.NewValType(wasmtime.KindI32), 200 | } 201 | var results []*wasmtime.ValType 202 | 203 | i.abort = wasmtime.NewFunc( 204 | i.m.store, 205 | wasmtime.NewFuncType(params, results), 206 | func(caller *wasmtime.Caller, params []wasmtime.Val) ([]wasmtime.Val, *wasmtime.Trap) { 207 | return []wasmtime.Val{}, nil 208 | }, 209 | ) 210 | 211 | return map[string]*wasmtime.Func{ 212 | "abort": i.abort, 213 | } 214 | } 215 | 216 | func (i *Instance) wapcRuntime() map[string]*wasmtime.Func { 217 | i.guestRequest = wasmtime.NewFunc( 218 | i.m.store, 219 | wasmtime.NewFuncType( 220 | []*wasmtime.ValType{ 221 | wasmtime.NewValType(wasmtime.KindI32), 222 | wasmtime.NewValType(wasmtime.KindI32), 223 | }, 224 | []*wasmtime.ValType{}, 225 | ), 226 | func(c *wasmtime.Caller, args []wasmtime.Val) ([]wasmtime.Val, *wasmtime.Trap) { 227 | operationPtr := args[0].I32() 228 | payloadPtr := args[1].I32() 229 | data := i.mem.UnsafeData(i.m.store) 230 | copy(data[operationPtr:], i.context.operation) 231 | copy(data[payloadPtr:], i.context.guestReq) 232 | return []wasmtime.Val{}, nil 233 | }, 234 | ) 235 | 236 | i.guestResponse = wasmtime.NewFunc( 237 | i.m.store, 238 | wasmtime.NewFuncType( 239 | []*wasmtime.ValType{ 240 | wasmtime.NewValType(wasmtime.KindI32), 241 | wasmtime.NewValType(wasmtime.KindI32), 242 | }, 243 | []*wasmtime.ValType{}, 244 | ), 245 | func(c *wasmtime.Caller, args []wasmtime.Val) ([]wasmtime.Val, *wasmtime.Trap) { 246 | ptr := args[0].I32() 247 | len := args[1].I32() 248 | data := i.mem.UnsafeData(i.m.store) 249 | buf := make([]byte, len) 250 | copy(buf, data[ptr:ptr+len]) 251 | i.context.guestResp = buf 252 | return []wasmtime.Val{}, nil 253 | }, 254 | ) 255 | 256 | i.guestError = wasmtime.NewFunc( 257 | i.m.store, 258 | wasmtime.NewFuncType( 259 | []*wasmtime.ValType{ 260 | wasmtime.NewValType(wasmtime.KindI32), 261 | wasmtime.NewValType(wasmtime.KindI32), 262 | }, 263 | []*wasmtime.ValType{}, 264 | ), 265 | func(c *wasmtime.Caller, args []wasmtime.Val) ([]wasmtime.Val, *wasmtime.Trap) { 266 | ptr := args[0].I32() 267 | len := args[1].I32() 268 | data := i.mem.UnsafeData(i.m.store) 269 | cp := make([]byte, len) 270 | copy(cp, data[ptr:ptr+len]) 271 | i.context.guestErr = string(cp) 272 | return []wasmtime.Val{}, nil 273 | }, 274 | ) 275 | 276 | i.hostCall = wasmtime.NewFunc( 277 | i.m.store, 278 | 279 | wasmtime.NewFuncType( 280 | []*wasmtime.ValType{ 281 | wasmtime.NewValType(wasmtime.KindI32), 282 | wasmtime.NewValType(wasmtime.KindI32), 283 | wasmtime.NewValType(wasmtime.KindI32), 284 | wasmtime.NewValType(wasmtime.KindI32), 285 | wasmtime.NewValType(wasmtime.KindI32), 286 | wasmtime.NewValType(wasmtime.KindI32), 287 | wasmtime.NewValType(wasmtime.KindI32), 288 | wasmtime.NewValType(wasmtime.KindI32), 289 | }, 290 | []*wasmtime.ValType{ 291 | wasmtime.NewValType(wasmtime.KindI32), 292 | }, 293 | ), 294 | func(c *wasmtime.Caller, args []wasmtime.Val) ([]wasmtime.Val, *wasmtime.Trap) { 295 | bindingPtr := args[0].I32() 296 | bindingLen := args[1].I32() 297 | namespacePtr := args[2].I32() 298 | namespaceLen := args[3].I32() 299 | operationPtr := args[4].I32() 300 | operationLen := args[5].I32() 301 | payloadPtr := args[6].I32() 302 | payloadLen := args[7].I32() 303 | 304 | if i.m.hostCallHandler == nil { 305 | return []wasmtime.Val{wasmtime.ValI32(0)}, nil 306 | } 307 | 308 | data := i.mem.UnsafeData(i.m.store) 309 | binding := string(data[bindingPtr : bindingPtr+bindingLen]) 310 | namespace := string(data[namespacePtr : namespacePtr+namespaceLen]) 311 | operation := string(data[operationPtr : operationPtr+operationLen]) 312 | payload := make([]byte, payloadLen) 313 | copy(payload, data[payloadPtr:payloadPtr+payloadLen]) 314 | 315 | i.context.hostResp, i.context.hostErr = i.m.hostCallHandler(i.context.ctx, binding, namespace, operation, payload) 316 | if i.context.hostErr != nil { 317 | return []wasmtime.Val{wasmtime.ValI32(0)}, nil 318 | } 319 | 320 | return []wasmtime.Val{wasmtime.ValI32(1)}, nil 321 | }, 322 | ) 323 | 324 | i.hostResponseLen = wasmtime.NewFunc( 325 | i.m.store, 326 | wasmtime.NewFuncType( 327 | []*wasmtime.ValType{}, 328 | []*wasmtime.ValType{ 329 | wasmtime.NewValType(wasmtime.KindI32), 330 | }, 331 | ), 332 | func(c *wasmtime.Caller, args []wasmtime.Val) ([]wasmtime.Val, *wasmtime.Trap) { 333 | return []wasmtime.Val{wasmtime.ValI32(int32(len(i.context.hostResp)))}, nil 334 | }, 335 | ) 336 | 337 | i.hostResponse = wasmtime.NewFunc( 338 | i.m.store, 339 | wasmtime.NewFuncType( 340 | []*wasmtime.ValType{ 341 | wasmtime.NewValType(wasmtime.KindI32), 342 | }, 343 | []*wasmtime.ValType{}, 344 | ), 345 | func(c *wasmtime.Caller, args []wasmtime.Val) ([]wasmtime.Val, *wasmtime.Trap) { 346 | if i.context.hostResp != nil { 347 | ptr := args[0].I32() 348 | data := i.mem.UnsafeData(i.m.store) 349 | copy(data[ptr:], i.context.hostResp) 350 | } 351 | return []wasmtime.Val{}, nil 352 | }, 353 | ) 354 | 355 | i.hostErrorLen = wasmtime.NewFunc( 356 | i.m.store, 357 | wasmtime.NewFuncType( 358 | []*wasmtime.ValType{}, 359 | []*wasmtime.ValType{ 360 | wasmtime.NewValType(wasmtime.KindI32), 361 | }, 362 | ), 363 | func(c *wasmtime.Caller, args []wasmtime.Val) ([]wasmtime.Val, *wasmtime.Trap) { 364 | if i.context.hostErr == nil { 365 | return []wasmtime.Val{wasmtime.ValI32(0)}, nil 366 | } 367 | errStr := i.context.hostErr.Error() 368 | return []wasmtime.Val{wasmtime.ValI32(int32(len(errStr)))}, nil 369 | }, 370 | ) 371 | 372 | i.hostError = wasmtime.NewFunc( 373 | i.m.store, 374 | wasmtime.NewFuncType( 375 | []*wasmtime.ValType{ 376 | wasmtime.NewValType(wasmtime.KindI32), 377 | }, 378 | []*wasmtime.ValType{}, 379 | ), 380 | func(c *wasmtime.Caller, args []wasmtime.Val) ([]wasmtime.Val, *wasmtime.Trap) { 381 | if i.context.hostErr == nil { 382 | return []wasmtime.Val{}, nil 383 | } 384 | 385 | ptr := args[0].I32() 386 | errStr := i.context.hostErr.Error() 387 | data := i.mem.UnsafeData(i.m.store) 388 | copy(data[ptr:], errStr) 389 | return []wasmtime.Val{}, nil 390 | }, 391 | ) 392 | 393 | i.consoleLog = wasmtime.NewFunc( 394 | i.m.store, 395 | wasmtime.NewFuncType( 396 | []*wasmtime.ValType{ 397 | wasmtime.NewValType(wasmtime.KindI32), 398 | wasmtime.NewValType(wasmtime.KindI32), 399 | }, 400 | []*wasmtime.ValType{}, 401 | ), 402 | func(c *wasmtime.Caller, args []wasmtime.Val) ([]wasmtime.Val, *wasmtime.Trap) { 403 | if i.m.logger != nil { 404 | data := i.mem.UnsafeData(i.m.store) 405 | ptr := args[0].I32() 406 | len := args[1].I32() 407 | msg := string(data[ptr : ptr+len]) 408 | i.m.logger(msg) 409 | } 410 | return []wasmtime.Val{}, nil 411 | }, 412 | ) 413 | 414 | return map[string]*wasmtime.Func{ 415 | "__guest_request": i.guestRequest, 416 | "__guest_response": i.guestResponse, 417 | "__guest_error": i.guestError, 418 | "__host_call": i.hostCall, 419 | "__host_response_len": i.hostResponseLen, 420 | "__host_response": i.hostResponse, 421 | "__host_error_len": i.hostErrorLen, 422 | "__host_error": i.hostError, 423 | "__console_log": i.consoleLog, 424 | } 425 | } 426 | 427 | // MemorySize returns the memory length of the underlying instance. 428 | func (i *Instance) MemorySize() uint32 { 429 | return uint32(i.mem.DataSize(i.m.store)) 430 | } 431 | 432 | // Invoke calls `operation` with `payload` on the module and returns a byte slice payload. 433 | func (i *Instance) Invoke(ctx context.Context, operation string, payload []byte) ([]byte, error) { 434 | if closed := atomic.LoadUint32(&i.closed); closed != 0 { 435 | return nil, fmt.Errorf("error invoking guest with closed instance") 436 | } 437 | // Note: There's still a race below, even if the above check is still useful. 438 | 439 | context := invokeContext{ 440 | ctx: ctx, 441 | operation: operation, 442 | guestReq: payload, 443 | } 444 | i.context = &context 445 | 446 | successValue, err := i.guestCall.Call(i.m.store, len(operation), len(payload)) 447 | if err != nil { 448 | return nil, fmt.Errorf("error invoking guest: %w", err) 449 | } 450 | if context.guestErr != "" { 451 | return nil, errors.New(context.guestErr) 452 | } 453 | 454 | successI32, _ := successValue.(int32) 455 | success := successI32 == 1 456 | 457 | if success { 458 | return context.guestResp, nil 459 | } 460 | 461 | return nil, fmt.Errorf("call to %q was unsuccessful", operation) 462 | } 463 | 464 | // Unwrap allows access to wasmtime-specific features. 465 | func (i *Instance) Unwrap() *wasmtime.Instance { 466 | return i.inst 467 | } 468 | 469 | // UnwrapStore allows access to wasmtime-specific features. 470 | func (i *Instance) UnwrapStore() *wasmtime.Store { 471 | return i.m.store 472 | } 473 | 474 | // Close closes the single instance. This should be called before calling `Close` on the Module itself. 475 | func (i *Instance) Close(context.Context) error { 476 | if !atomic.CompareAndSwapUint32(&i.closed, 0, 1) { 477 | return nil 478 | } 479 | 480 | // Explicitly release references on wasmtime types, so they can be GC'ed. 481 | i.inst = nil 482 | i.mem = nil 483 | i.context = nil 484 | i.guestRequest = nil 485 | i.guestResponse = nil 486 | i.guestError = nil 487 | i.hostCall = nil 488 | i.hostResponseLen = nil 489 | i.hostResponse = nil 490 | i.hostErrorLen = nil 491 | i.hostError = nil 492 | i.consoleLog = nil 493 | i.abort = nil 494 | return nil // wasmtime only closes via finalizer 495 | } 496 | 497 | // Unwrap allows access to wasmtime-specific features. 498 | func (m *Module) Unwrap() *wasmtime.Module { 499 | return m.module 500 | } 501 | 502 | // UnwrapStore allows access to wasmtime-specific features. 503 | func (m *Module) UnwrapStore() *wasmtime.Store { 504 | return m.store 505 | } 506 | 507 | // Close closes the module. This should be called after calling `Close` on any instances that were created. 508 | func (m *Module) Close(context.Context) error { 509 | if !atomic.CompareAndSwapUint32(&m.closed, 0, 1) { 510 | return nil 511 | } 512 | 513 | // Explicitly release references on wasmtime types, so they can be GC'ed. 514 | m.module = nil 515 | if store := m.store; store != nil { 516 | store.GC() 517 | m.store = nil 518 | } 519 | m.engine = nil 520 | return nil // wasmtime only closes via finalizer 521 | } 522 | -------------------------------------------------------------------------------- /engines/wasmtime/wasmtime_test.go: -------------------------------------------------------------------------------- 1 | package wasmtime 2 | 3 | // import ( 4 | // "context" 5 | // "errors" 6 | // "log" 7 | // "os" 8 | // "testing" 9 | 10 | // "github.com/bytecodealliance/wasmtime-go" 11 | 12 | // "github.com/wapc/wapc-go" 13 | // ) 14 | 15 | // type testKey struct{} 16 | 17 | // // testCtx is an arbitrary, non-default context. Non-nil also prevents linter errors. 18 | // var testCtx = context.WithValue(context.Background(), testKey{}, "arbitrary") 19 | 20 | // var guest []byte 21 | // var mc = &wapc.ModuleConfig{ 22 | // Logger: wapc.PrintlnLogger, 23 | // Stdout: os.Stdout, 24 | // Stderr: os.Stderr, 25 | // } 26 | 27 | // // TestMain ensures we can read the example wasm prior to running unit tests. 28 | // func TestMain(m *testing.M) { 29 | // var err error 30 | // guest, err = os.ReadFile("../../testdata/go/hello.wasm") 31 | // if err != nil { 32 | // log.Panicln(err) 33 | // } 34 | // os.Exit(m.Run()) 35 | // } 36 | 37 | // func TestEngine_WithEngine(t *testing.T) { 38 | // t.Run("ok", func(t *testing.T) { 39 | // expected := wasmtime.NewEngine() 40 | 41 | // e := EngineWithRuntime(func() (*wasmtime.Engine, error) { 42 | // return expected, nil 43 | // }) 44 | // m, err := e.New(testCtx, wapc.NoOpHostCallHandler, guest, mc) 45 | // if err != nil { 46 | // t.Errorf("Error creating module - %v", err) 47 | // } 48 | // defer m.Close(testCtx) 49 | 50 | // if have := m.(*Module).engine; have != expected { 51 | // t.Errorf("Unexpected engine, have %v, expected %v", have, expected) 52 | // } 53 | // }) 54 | // t.Run("nil not ok", func(t *testing.T) { 55 | // expectedErr := "function set by WithEngine returned nil" 56 | // e := EngineWithRuntime(func() (*wasmtime.Engine, error) { 57 | // return nil, errors.New(expectedErr) 58 | // }) 59 | 60 | // if _, err := e.New(testCtx, wapc.NoOpHostCallHandler, guest, mc); err.Error() != expectedErr { 61 | // t.Errorf("Unexpected error, have %v, expected %v", err, expectedErr) 62 | // } 63 | // }) 64 | // } 65 | 66 | // func TestModule_Unwrap(t *testing.T) { 67 | // m, err := EngineWithRuntime(DefaultRuntime).New(testCtx, wapc.NoOpHostCallHandler, guest, mc) 68 | // if err != nil { 69 | // t.Errorf("Error creating module - %v", err) 70 | // } 71 | // defer m.Close(testCtx) 72 | 73 | // mod := m.(*Module) 74 | // expected := mod.module 75 | // if have := mod.Unwrap(); have != expected { 76 | // t.Errorf("Unexpected module, have %v, expected %v", have, expected) 77 | // } 78 | // } 79 | 80 | // func TestInstance_Unwrap(t *testing.T) { 81 | // m, err := EngineWithRuntime(DefaultRuntime).New(testCtx, wapc.NoOpHostCallHandler, guest, mc) 82 | // if err != nil { 83 | // t.Errorf("Error creating module - %v", err) 84 | // } 85 | // defer m.Close(testCtx) 86 | 87 | // i, err := m.Instantiate(testCtx) 88 | // if err != nil { 89 | // t.Errorf("Error creating instance - %v", err) 90 | // } 91 | // defer m.Close(testCtx) 92 | 93 | // inst := i.(*Instance) 94 | // expected := inst.inst 95 | // if have := inst.Unwrap(); have != expected { 96 | // t.Errorf("Unexpected instance, have %v, expected %v", have, expected) 97 | // } 98 | // } 99 | -------------------------------------------------------------------------------- /engines/wazero/engine_test.go: -------------------------------------------------------------------------------- 1 | package wazero_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/wapc/wapc-go/engines/tests" 7 | "github.com/wapc/wapc-go/engines/wazero" 8 | ) 9 | 10 | func TestGuest(t *testing.T) { 11 | tests.TestGuest(t, wazero.Engine()) 12 | } 13 | 14 | func TestModuleBadBytes(t *testing.T) { 15 | tests.TestModuleBadBytes(t, wazero.Engine()) 16 | } 17 | 18 | func TestModule(t *testing.T) { 19 | tests.TestModule(t, wazero.Engine()) 20 | } 21 | 22 | func TestGuestsWithPool(t *testing.T) { 23 | tests.TestGuestsWithPool(t, wazero.Engine()) 24 | } 25 | -------------------------------------------------------------------------------- /engines/wazero/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/wapc/wapc-go/engines/wazero 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.23.4 6 | 7 | require ( 8 | github.com/tetratelabs/wazero v1.9.0 9 | github.com/wapc/wapc-go v0.0.0 10 | ) 11 | 12 | require ( 13 | github.com/Workiva/go-datastructures v1.1.5 // indirect 14 | github.com/davecgh/go-spew v1.1.1 // indirect 15 | gopkg.in/yaml.v3 v3.0.1 // indirect 16 | ) 17 | 18 | replace github.com/wapc/wapc-go => ../.. 19 | -------------------------------------------------------------------------------- /engines/wazero/go.sum: -------------------------------------------------------------------------------- 1 | github.com/Workiva/go-datastructures v1.1.5 h1:5YfhQ4ry7bZc2Mc7R0YZyYwpf5c6t1cEFvdAhd6Mkf4= 2 | github.com/Workiva/go-datastructures v1.1.5/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A= 3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 5 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= 7 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 8 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 9 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 10 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 11 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 12 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 13 | github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I= 14 | github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM= 15 | github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg= 16 | github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q= 17 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 18 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 19 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 20 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 21 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 22 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 23 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 24 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 25 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 26 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 27 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 28 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 29 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 30 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 31 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 32 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 33 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 34 | golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 35 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 36 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 37 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 38 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 39 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 40 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 41 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 42 | -------------------------------------------------------------------------------- /engines/wazero/wazero.go: -------------------------------------------------------------------------------- 1 | package wazero 2 | 3 | import ( 4 | "context" 5 | "crypto/rand" 6 | "errors" 7 | "fmt" 8 | "sync/atomic" 9 | 10 | "github.com/tetratelabs/wazero" 11 | "github.com/tetratelabs/wazero/api" 12 | "github.com/tetratelabs/wazero/imports/assemblyscript" 13 | "github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1" 14 | "github.com/tetratelabs/wazero/sys" 15 | 16 | "github.com/wapc/wapc-go" 17 | ) 18 | 19 | const i32 = api.ValueTypeI32 20 | 21 | // functionStart is the name of the nullary function a module exports if it is a WASI Command Module. 22 | // 23 | // See https://github.com/WebAssembly/WASI/blob/snapshot-01/design/application-abi.md#current-unstable-abi 24 | const functionStart = "_start" 25 | 26 | // functionInitialize is the name of the function to initialize the runtime. 27 | const functionInitialize = "_initialize" 28 | 29 | // functionInit is the name of the nullary function that initializes waPC. 30 | const functionWapcInit = "wapc_init" 31 | 32 | // functionGuestCall is a callback required to be exported. Below is its signature in WebAssembly 1.0 (MVP) Text Format: 33 | // 34 | // (func $__guest_call (param $operation_len i32) (param $payload_len i32) (result (;errno;) i32)) 35 | const functionGuestCall = "__guest_call" 36 | 37 | type ( 38 | engine struct{ newRuntime NewRuntime } 39 | 40 | // Module represents a compiled waPC module. 41 | Module struct { 42 | // wapcHostCallHandler is the value of wapcHost.callHandler 43 | wapcHostCallHandler wapc.HostCallHandler 44 | 45 | runtime wazero.Runtime 46 | compiled wazero.CompiledModule 47 | 48 | instanceCounter uint64 49 | 50 | config wazero.ModuleConfig 51 | 52 | // closed is atomically updated to ensure Close is only invoked once. 53 | closed uint32 54 | } 55 | 56 | Instance struct { 57 | name string 58 | m api.Module 59 | guestCall api.Function 60 | 61 | // closed is atomically updated to ensure Close is only invoked once. 62 | closed uint32 63 | } 64 | 65 | invokeContext struct { 66 | operation string 67 | 68 | guestReq []byte 69 | guestResp []byte 70 | guestErr string 71 | 72 | hostResp []byte 73 | hostErr error 74 | } 75 | ) 76 | 77 | // Ensure the engine conforms to the waPC interface. 78 | var _ = (wapc.Module)((*Module)(nil)) 79 | var _ = (wapc.Instance)((*Instance)(nil)) 80 | 81 | var engineInstance = engine{newRuntime: DefaultRuntime} 82 | 83 | // Engine returns a new wapc.Engine which uses the DefaultRuntime. 84 | func Engine() wapc.Engine { 85 | return &engineInstance 86 | } 87 | 88 | // NewRuntime returns a new wazero runtime which is called when the New method 89 | // on wapc.Engine is called. The result is closed upon wapc.Module Close. 90 | type NewRuntime func(context.Context) (wazero.Runtime, error) 91 | 92 | // EngineWithRuntime allows you to customize or return an alternative to 93 | // DefaultRuntime, 94 | func EngineWithRuntime(newRuntime NewRuntime) wapc.Engine { 95 | return &engine{newRuntime: newRuntime} 96 | } 97 | 98 | func (e *engine) Name() string { 99 | return "wazero" 100 | } 101 | 102 | // DefaultRuntime implements NewRuntime by returning a wazero runtime with WASI 103 | // and AssemblyScript host functions instantiated. 104 | func DefaultRuntime(ctx context.Context) (wazero.Runtime, error) { 105 | r := wazero.NewRuntime(ctx) 106 | 107 | if _, err := wasi_snapshot_preview1.Instantiate(ctx, r); err != nil { 108 | _ = r.Close(ctx) 109 | return nil, err 110 | } 111 | 112 | // This disables the abort message as no other engines write it. 113 | envBuilder := r.NewHostModuleBuilder("env") 114 | assemblyscript.NewFunctionExporter().WithAbortMessageDisabled().ExportFunctions(envBuilder) 115 | if _, err := envBuilder.Instantiate(ctx); err != nil { 116 | _ = r.Close(ctx) 117 | return nil, err 118 | } 119 | return r, nil 120 | } 121 | 122 | // New implements the same method as documented on wapc.Engine. 123 | func (e *engine) New(ctx context.Context, host wapc.HostCallHandler, guest []byte, config *wapc.ModuleConfig) (mod wapc.Module, err error) { 124 | r, err := e.newRuntime(ctx) 125 | if err != nil { 126 | return nil, err 127 | } 128 | 129 | m := &Module{runtime: r, wapcHostCallHandler: host} 130 | 131 | m.config = wazero.NewModuleConfig(). 132 | WithStartFunctions() // Manually call _start and _init 133 | 134 | if config.Stdout != nil { 135 | m.config = m.config.WithStdout(config.Stdout) 136 | } 137 | if config.Stderr != nil { 138 | m.config = m.config.WithStderr(config.Stderr) 139 | } 140 | m.config = m.config.WithRandSource(rand.Reader).WithSysNanosleep().WithSysNanotime().WithSysWalltime() 141 | 142 | mod = m 143 | 144 | if _, err = instantiateWapcHost(ctx, r, m.wapcHostCallHandler, config.Logger); err != nil { 145 | _ = r.Close(ctx) 146 | return 147 | } 148 | 149 | if m.compiled, err = r.CompileModule(ctx, guest); err != nil { 150 | _ = r.Close(ctx) 151 | return 152 | } 153 | return 154 | } 155 | 156 | // UnwrapRuntime allows access to wazero-specific runtime features. 157 | func (m *Module) UnwrapRuntime() *wazero.Runtime { 158 | return &m.runtime 159 | } 160 | 161 | // WithConfig allows you to override or replace wazero.ModuleConfig used to instantiate each guest. 162 | // This can be used to configure clocks or filesystem access. 163 | // 164 | // The default (function input) conflgures WASI and waPC init functions as well as stdout and stderr. 165 | func (m *Module) WithConfig(callback func(wazero.ModuleConfig) wazero.ModuleConfig) { 166 | m.config = callback(m.config) 167 | } 168 | 169 | // wapcHost implements all required waPC host function exports. 170 | // 171 | // See https://wapc.io/docs/spec/#required-host-exports 172 | type wapcHost struct { 173 | // callHandler implements hostCall, which returns false (0) when nil. 174 | callHandler wapc.HostCallHandler 175 | 176 | // logger is used to implement consoleLog. 177 | logger wapc.Logger 178 | } 179 | 180 | // instantiateWapcHost instantiates a wapcHost and returns it and its corresponding module, or an error. 181 | // - r: used to instantiate the waPC host module 182 | // - callHandler: used to implement hostCall 183 | // - logger: used to implement consoleLog 184 | func instantiateWapcHost(ctx context.Context, r wazero.Runtime, callHandler wapc.HostCallHandler, logger wapc.Logger) (api.Module, error) { 185 | h := &wapcHost{callHandler: callHandler, logger: logger} 186 | // Export host functions (in the order defined in https://wapc.io/docs/spec/#required-host-exports) 187 | // Note: These are defined manually (without reflection) for higher performance as waPC is a foundational library. 188 | return r.NewHostModuleBuilder("wapc"). 189 | NewFunctionBuilder(). 190 | WithGoModuleFunction(api.GoModuleFunc(h.hostCall), []api.ValueType{i32, i32, i32, i32, i32, i32, i32, i32}, []api.ValueType{i32}). 191 | WithParameterNames("bind_ptr", "bind_len", "ns_ptr", "ns_len", "cmd_ptr", "cmd_len", "payload_ptr", "payload_len"). 192 | Export("__host_call"). 193 | NewFunctionBuilder(). 194 | WithGoModuleFunction(api.GoModuleFunc(h.consoleLog), []api.ValueType{i32, i32}, []api.ValueType{}). 195 | WithParameterNames("ptr", "len"). 196 | Export("__console_log"). 197 | NewFunctionBuilder(). 198 | WithGoModuleFunction(api.GoModuleFunc(h.guestRequest), []api.ValueType{i32, i32}, []api.ValueType{}). 199 | WithParameterNames("op_ptr", "ptr"). 200 | Export("__guest_request"). 201 | NewFunctionBuilder(). 202 | WithGoModuleFunction(api.GoModuleFunc(h.hostResponse), []api.ValueType{i32}, []api.ValueType{}). 203 | WithParameterNames("ptr"). 204 | Export("__host_response"). 205 | NewFunctionBuilder(). 206 | WithGoFunction(api.GoFunc(h.hostResponseLen), []api.ValueType{}, []api.ValueType{i32}). 207 | Export("__host_response_len"). 208 | NewFunctionBuilder(). 209 | WithGoModuleFunction(api.GoModuleFunc(h.guestResponse), []api.ValueType{i32, i32}, []api.ValueType{}). 210 | WithParameterNames("ptr", "len"). 211 | Export("__guest_response"). 212 | NewFunctionBuilder(). 213 | WithGoModuleFunction(api.GoModuleFunc(h.guestError), []api.ValueType{i32, i32}, []api.ValueType{}). 214 | WithParameterNames("ptr", "len"). 215 | Export("__guest_error"). 216 | NewFunctionBuilder(). 217 | WithGoModuleFunction(api.GoModuleFunc(h.hostError), []api.ValueType{i32}, []api.ValueType{}). 218 | WithParameterNames("ptr"). 219 | Export("__host_error"). 220 | NewFunctionBuilder(). 221 | WithGoFunction(api.GoFunc(h.hostErrorLen), []api.ValueType{}, []api.ValueType{i32}). 222 | Export("__host_error_len"). 223 | Instantiate(ctx) 224 | } 225 | 226 | // hostCall is the WebAssembly function export "__host_call", which initiates a host using the callHandler using 227 | // parameters read from linear memory (wasm.Memory). 228 | func (w *wapcHost) hostCall(ctx context.Context, m api.Module, stack []uint64) { 229 | bindPtr := uint32(stack[0]) 230 | bindLen := uint32(stack[1]) 231 | nsPtr := uint32(stack[2]) 232 | nsLen := uint32(stack[3]) 233 | cmdPtr := uint32(stack[4]) 234 | cmdLen := uint32(stack[5]) 235 | payloadPtr := uint32(stack[6]) 236 | payloadLen := uint32(stack[7]) 237 | 238 | ic := fromInvokeContext(ctx) 239 | if ic == nil || w.callHandler == nil { 240 | stack[0] = 0 // false: neither an invocation context, nor a callHandler 241 | return 242 | } 243 | 244 | mem := m.Memory() 245 | binding := requireReadString(mem, "binding", bindPtr, bindLen) 246 | namespace := requireReadString(mem, "namespace", nsPtr, nsLen) 247 | operation := requireReadString(mem, "operation", cmdPtr, cmdLen) 248 | payload := requireRead(mem, "payload", payloadPtr, payloadLen) 249 | 250 | if ic.hostResp, ic.hostErr = w.callHandler(ctx, binding, namespace, operation, payload); ic.hostErr != nil { 251 | stack[0] = 0 // false: error (assumed to be logged already?) 252 | } else { 253 | stack[0] = 1 // true 254 | } 255 | } 256 | 257 | // consoleLog is the WebAssembly function export "__console_log", which logs the message stored by the guest at the 258 | // given offset (ptr) and length (len) in linear memory (wasm.Memory). 259 | func (w *wapcHost) consoleLog(_ context.Context, m api.Module, params []uint64) { 260 | ptr := uint32(params[0]) 261 | len := uint32(params[1]) 262 | 263 | if log := w.logger; log != nil { 264 | msg := requireReadString(m.Memory(), "msg", ptr, len) 265 | w.logger(msg) 266 | } 267 | } 268 | 269 | // guestRequest is the WebAssembly function export "__guest_request", which writes the invokeContext.operation and 270 | // invokeContext.guestReq to the given offsets (opPtr, ptr) in linear memory (wasm.Memory). 271 | func (w *wapcHost) guestRequest(ctx context.Context, m api.Module, params []uint64) { 272 | opPtr := uint32(params[0]) 273 | ptr := uint32(params[1]) 274 | 275 | ic := fromInvokeContext(ctx) 276 | if ic == nil { 277 | return // no invoke context 278 | } 279 | 280 | mem := m.Memory() 281 | if operation := ic.operation; operation != "" { 282 | mem.Write(opPtr, []byte(operation)) 283 | } 284 | if guestReq := ic.guestReq; guestReq != nil { 285 | mem.Write(ptr, guestReq) 286 | } 287 | } 288 | 289 | // hostResponse is the WebAssembly function export "__host_response", which writes the invokeContext.hostResp to the 290 | // given offset (ptr) in linear memory (wasm.Memory). 291 | func (w *wapcHost) hostResponse(ctx context.Context, m api.Module, params []uint64) { 292 | ptr := uint32(params[0]) 293 | 294 | if ic := fromInvokeContext(ctx); ic == nil { 295 | return // no invoke context 296 | } else if hostResp := ic.hostResp; hostResp != nil { 297 | m.Memory().Write(ptr, hostResp) 298 | } 299 | } 300 | 301 | // hostResponse is the WebAssembly function export "__host_response_len", which returns the length of the current host 302 | // response from invokeContext.hostResp. 303 | func (w *wapcHost) hostResponseLen(ctx context.Context, results []uint64) { 304 | var hostResponseLen uint32 305 | if ic := fromInvokeContext(ctx); ic == nil { 306 | results[0] = 0 // no invoke context 307 | } else if hostResp := ic.hostResp; hostResp != nil { 308 | hostResponseLen = uint32(len(hostResp)) 309 | results[0] = uint64(hostResponseLen) 310 | } else { 311 | results[0] = 0 // no host response 312 | } 313 | } 314 | 315 | // guestResponse is the WebAssembly function export "__guest_response", which reads invokeContext.guestResp from the 316 | // given offset (ptr) and length (len) in linear memory (wasm.Memory). 317 | func (w *wapcHost) guestResponse(ctx context.Context, m api.Module, params []uint64) { 318 | ptr := uint32(params[0]) 319 | len := uint32(params[1]) 320 | 321 | if ic := fromInvokeContext(ctx); ic == nil { 322 | return // no invoke context 323 | } else { 324 | ic.guestResp = requireRead(m.Memory(), "guestResp", ptr, len) 325 | } 326 | } 327 | 328 | // guestError is the WebAssembly function export "__guest_error", which reads invokeContext.guestErr from the given 329 | // offset (ptr) and length (len) in linear memory (wasm.Memory). 330 | func (w *wapcHost) guestError(ctx context.Context, m api.Module, params []uint64) { 331 | ptr := uint32(params[0]) 332 | len := uint32(params[1]) 333 | 334 | if ic := fromInvokeContext(ctx); ic == nil { 335 | return // no invoke context 336 | } else { 337 | ic.guestErr = requireReadString(m.Memory(), "guestErr", ptr, len) 338 | } 339 | } 340 | 341 | // hostError is the WebAssembly function export "__host_error", which writes the invokeContext.hostErr to the given 342 | // offset (ptr) in linear memory (wasm.Memory). 343 | func (w *wapcHost) hostError(ctx context.Context, m api.Module, params []uint64) { 344 | ptr := uint32(params[0]) 345 | 346 | if ic := fromInvokeContext(ctx); ic == nil { 347 | return // no invoke context 348 | } else if hostErr := ic.hostErr; hostErr != nil { 349 | m.Memory().Write(ptr, []byte(hostErr.Error())) 350 | } 351 | } 352 | 353 | // hostError is the WebAssembly function export "__host_error_len", which returns the length of the current host error 354 | // from invokeContext.hostErr. 355 | func (w *wapcHost) hostErrorLen(ctx context.Context, results []uint64) { 356 | var hostErrorLen uint32 357 | if ic := fromInvokeContext(ctx); ic == nil { 358 | results[0] = 0 // no invoke context 359 | } else if hostErr := ic.hostErr; hostErr != nil { 360 | hostErrorLen = uint32(len(hostErr.Error())) 361 | results[0] = uint64(hostErrorLen) 362 | } else { 363 | results[0] = 0 // no host error 364 | } 365 | } 366 | 367 | // Instantiate implements the same method as documented on wapc.Module. 368 | func (m *Module) Instantiate(ctx context.Context) (wapc.Instance, error) { 369 | if closed := atomic.LoadUint32(&m.closed); closed != 0 { 370 | return nil, errors.New("cannot Instantiate when a module is closed") 371 | } 372 | // Note: There's still a race below, even if the above check is still useful. 373 | 374 | moduleName := fmt.Sprintf("%d", atomic.AddUint64(&m.instanceCounter, 1)) 375 | 376 | module, err := m.runtime.InstantiateModule(ctx, m.compiled, m.config.WithName(moduleName)) 377 | if err != nil { 378 | return nil, err 379 | } 380 | 381 | // Call any WASI or waPC start functions on instantiate. 382 | funcs := []string{functionStart, functionInitialize, functionWapcInit} 383 | for _, f := range funcs { 384 | exportedFunc := module.ExportedFunction(f) 385 | if exportedFunc != nil { 386 | ic := invokeContext{operation: f, guestReq: nil} 387 | ictx := newInvokeContext(ctx, &ic) 388 | if _, err := exportedFunc.Call(ictx); err != nil { 389 | if exitErr, ok := err.(*sys.ExitError); ok { 390 | if exitErr.ExitCode() != 0 { 391 | module.Close(ctx) 392 | 393 | return nil, fmt.Errorf("module closed with exit_code(%d)", exitErr.ExitCode()) 394 | } 395 | } else { 396 | module.Close(ctx) 397 | 398 | return nil, fmt.Errorf("error calling %s: %v", f, err) 399 | } 400 | } 401 | } 402 | } 403 | 404 | instance := Instance{name: moduleName, m: module} 405 | 406 | if instance.guestCall = module.ExportedFunction(functionGuestCall); instance.guestCall == nil { 407 | _ = module.Close(ctx) 408 | return nil, fmt.Errorf("module %s didn't export function %s", moduleName, functionGuestCall) 409 | } 410 | 411 | return &instance, nil 412 | } 413 | 414 | // MemorySize implements the same method as documented on wapc.Instance. 415 | func (i *Instance) MemorySize() uint32 { 416 | return i.m.Memory().Size() 417 | } 418 | 419 | type invokeContextKey struct{} 420 | 421 | func newInvokeContext(ctx context.Context, ic *invokeContext) context.Context { 422 | return context.WithValue(ctx, invokeContextKey{}, ic) 423 | } 424 | 425 | // fromInvokeContext returns invokeContext value or nil if there was none. 426 | // 427 | // Note: This is never nil if called by Instance.Invoke 428 | // TODO: It may be better to panic on nil or error as if this is nil, it is a bug in waPC, as no other path calls this. 429 | func fromInvokeContext(ctx context.Context) *invokeContext { 430 | ic, _ := ctx.Value(invokeContextKey{}).(*invokeContext) 431 | return ic 432 | } 433 | 434 | // UnwrapModule allows access to wazero-specific api.Module. 435 | func (i *Instance) UnwrapModule() api.Module { 436 | return i.m 437 | } 438 | 439 | // Invoke implements the same method as documented on wapc.Instance. 440 | func (i *Instance) Invoke(ctx context.Context, operation string, payload []byte) ([]byte, error) { 441 | if closed := atomic.LoadUint32(&i.closed); closed != 0 { 442 | return nil, fmt.Errorf("error invoking guest with closed instance") 443 | } 444 | // Note: There's still a race below, even if the above check is still useful. 445 | 446 | ic := invokeContext{operation: operation, guestReq: payload} 447 | ctx = newInvokeContext(ctx, &ic) 448 | 449 | results, err := i.guestCall.Call(ctx, uint64(len(operation)), uint64(len(payload))) 450 | if err != nil { 451 | return nil, fmt.Errorf("error invoking guest: %w", err) 452 | } 453 | if ic.guestErr != "" { // guestErr is not nil if the guest called "__guest_error". 454 | return nil, errors.New(ic.guestErr) 455 | } 456 | 457 | result := results[0] 458 | success := result == 1 459 | 460 | if success { // guestResp is not nil if the guest called "__guest_response". 461 | return ic.guestResp, nil 462 | } 463 | 464 | return nil, fmt.Errorf("call to %q was unsuccessful", operation) 465 | } 466 | 467 | // Close implements the same method as documented on wapc.Instance. 468 | func (i *Instance) Close(ctx context.Context) error { 469 | if !atomic.CompareAndSwapUint32(&i.closed, 0, 1) { 470 | return nil 471 | } 472 | return i.m.Close(ctx) 473 | } 474 | 475 | // Close implements the same method as documented on wapc.Module. 476 | func (m *Module) Close(ctx context.Context) (err error) { 477 | if !atomic.CompareAndSwapUint32(&m.closed, 0, 1) { 478 | return 479 | } 480 | err = m.runtime.Close(ctx) // closes everything 481 | m.runtime = nil 482 | return 483 | } 484 | 485 | // requireReadString is a convenience function that casts requireRead 486 | func requireReadString(mem api.Memory, fieldName string, offset, byteCount uint32) string { 487 | return string(requireRead(mem, fieldName, offset, byteCount)) 488 | } 489 | 490 | // requireRead is like api.Memory except that it panics if the offset and byteCount are out of range. 491 | func requireRead(mem api.Memory, fieldName string, offset, byteCount uint32) []byte { 492 | buf, ok := mem.Read(offset, byteCount) 493 | if !ok { 494 | panic(fmt.Errorf("out of memory reading %s", fieldName)) 495 | } 496 | return buf 497 | } 498 | -------------------------------------------------------------------------------- /engines/wazero/wazero_test.go: -------------------------------------------------------------------------------- 1 | package wazero 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "log" 7 | "os" 8 | "testing" 9 | 10 | "github.com/tetratelabs/wazero" 11 | "github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1" 12 | 13 | "github.com/wapc/wapc-go" 14 | ) 15 | 16 | type testKey struct{} 17 | 18 | // testCtx is an arbitrary, non-default context. Non-nil also prevents linter errors. 19 | var testCtx = context.WithValue(context.Background(), testKey{}, "arbitrary") 20 | 21 | var guest []byte 22 | var mc = &wapc.ModuleConfig{ 23 | Logger: wapc.PrintlnLogger, 24 | Stdout: os.Stdout, 25 | Stderr: os.Stderr, 26 | } 27 | 28 | // TestMain ensures we can read the example wasm prior to running unit tests. 29 | func TestMain(m *testing.M) { 30 | var err error 31 | guest, err = os.ReadFile("../../testdata/go/hello.wasm") 32 | if err != nil { 33 | log.Panicln(err) 34 | } 35 | os.Exit(m.Run()) 36 | } 37 | 38 | // TestModule_UnwrapRuntime ensures the Unwrap returns the correct Runtime interface 39 | func TestModule_UnwrapRuntime(t *testing.T) { 40 | m, err := EngineWithRuntime(DefaultRuntime).New(testCtx, wapc.NoOpHostCallHandler, guest, mc) 41 | if err != nil { 42 | t.Errorf("Error creating module - %v", err) 43 | } 44 | defer m.Close(testCtx) 45 | 46 | mod := m.(*Module) 47 | expected := &mod.runtime 48 | if have := mod.UnwrapRuntime(); have != expected { 49 | t.Errorf("Unexpected module, have %v, expected %v", have, expected) 50 | } 51 | } 52 | 53 | // TestModule_WithConfig ensures the module config can be extended 54 | func TestModule_WithConfig(t *testing.T) { 55 | m, err := EngineWithRuntime(DefaultRuntime).New(testCtx, wapc.NoOpHostCallHandler, guest, mc) 56 | if err != nil { 57 | t.Errorf("Error creating module - %v", err) 58 | } 59 | defer m.Close(testCtx) 60 | 61 | var mock = &mockModuleConfig{} 62 | m.(*Module).config = mock 63 | m.(*Module).WithConfig(func(config wazero.ModuleConfig) wazero.ModuleConfig { 64 | return config.WithSysWalltime() 65 | }) 66 | if !mock.calledWithSysWalltime { 67 | t.Errorf(`Expected call to WithSysWalltime`) 68 | } 69 | } 70 | 71 | type mockModuleConfig struct { 72 | wazero.ModuleConfig 73 | calledWithSysWalltime bool 74 | } 75 | 76 | func (m *mockModuleConfig) WithSysWalltime() wazero.ModuleConfig { 77 | m.calledWithSysWalltime = true 78 | return m 79 | } 80 | 81 | // TestInstance_UnwrapModule ensures the Unwrap returns the correct api.Module interface 82 | func TestInstance_UnwrapModule(t *testing.T) { 83 | m, err := EngineWithRuntime(DefaultRuntime).New(testCtx, wapc.NoOpHostCallHandler, guest, mc) 84 | if err != nil { 85 | t.Errorf("Error creating module - %v", err) 86 | } 87 | defer m.Close(testCtx) 88 | 89 | mod, err := m.Instantiate(testCtx) 90 | if err != nil { 91 | t.Errorf("Error instantiating module - %v", err) 92 | } 93 | inst := mod.(*Instance) 94 | expected := inst.m 95 | if have := inst.UnwrapModule(); have != expected { 96 | t.Errorf("Unexpected module, have %v, expected %v", have, expected) 97 | } 98 | } 99 | 100 | func TestEngineWithRuntime(t *testing.T) { 101 | t.Run("instantiates custom runtime", func(t *testing.T) { 102 | r := wazero.NewRuntime(testCtx) 103 | defer r.Close(testCtx) 104 | 105 | if _, err := wasi_snapshot_preview1.Instantiate(testCtx, r); err != nil { 106 | _ = r.Close(testCtx) 107 | t.Errorf("Error creating module - %v", err) 108 | } 109 | 110 | // TinyGo doesn't need the AssemblyScript host functions which are 111 | // instantiated by default. 112 | e := EngineWithRuntime(func(ctx context.Context) (wazero.Runtime, error) { 113 | return r, nil 114 | }) 115 | 116 | m, err := e.New(testCtx, wapc.NoOpHostCallHandler, guest, mc) 117 | if err != nil { 118 | t.Errorf("Error creating module - %v", err) 119 | } 120 | 121 | if have := m.(*Module).runtime; have != r { 122 | t.Errorf("Unexpected runtime, got %v, expected %v", have, r) 123 | } 124 | 125 | // We expect this to close the runtime returned by NewRuntime 126 | m.Close(testCtx) 127 | // Ensure the runtime is now closed by invoking a related method 128 | if mod := r.Module(wasi_snapshot_preview1.ModuleName); mod != nil { 129 | t.Errorf("Expected Module.Close to close wazero Runtime") 130 | } 131 | }) 132 | 133 | t.Run("error instantiating runtime", func(t *testing.T) { 134 | expectedErr := errors.New("broken") 135 | 136 | e := EngineWithRuntime(func(context.Context) (wazero.Runtime, error) { 137 | return nil, expectedErr 138 | }) 139 | 140 | if _, err := e.New(testCtx, wapc.NoOpHostCallHandler, guest, mc); err != expectedErr { 141 | t.Errorf("Unexpected error, got %v, expected %v", err, expectedErr) 142 | } 143 | }) 144 | } 145 | -------------------------------------------------------------------------------- /example/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/wapc/wapc-go/example 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.23.4 6 | 7 | require ( 8 | github.com/wapc/wapc-go v0.0.0 9 | github.com/wapc/wapc-go/engines/wazero v0.0.0 10 | ) 11 | 12 | require ( 13 | github.com/Workiva/go-datastructures v1.1.5 // indirect 14 | github.com/tetratelabs/wazero v1.8.2 // indirect 15 | ) 16 | 17 | replace ( 18 | github.com/wapc/wapc-go => ../ 19 | github.com/wapc/wapc-go/engines/wazero => ../engines/wazero 20 | ) 21 | -------------------------------------------------------------------------------- /example/go.sum: -------------------------------------------------------------------------------- 1 | github.com/Workiva/go-datastructures v1.1.5 h1:5YfhQ4ry7bZc2Mc7R0YZyYwpf5c6t1cEFvdAhd6Mkf4= 2 | github.com/Workiva/go-datastructures v1.1.5/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A= 3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 5 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= 7 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 8 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 9 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 10 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 11 | github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= 12 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 13 | github.com/tetratelabs/wazero v1.8.2 h1:yIgLR/b2bN31bjxwXHD8a3d+BogigR952csSDdLYEv4= 14 | github.com/tetratelabs/wazero v1.8.2/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs= 15 | github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg= 16 | github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q= 17 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 18 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 19 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 20 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 21 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 22 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 23 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 24 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 25 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 26 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 27 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 28 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 29 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 30 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 31 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 32 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 33 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 34 | golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 35 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 36 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 37 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 38 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 39 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 40 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 41 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 42 | -------------------------------------------------------------------------------- /example/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "strings" 8 | 9 | "github.com/wapc/wapc-go" 10 | "github.com/wapc/wapc-go/engines/wazero" 11 | ) 12 | 13 | func main() { 14 | if len(os.Args) < 2 { 15 | fmt.Println("usage: hello ") 16 | return 17 | } 18 | name := os.Args[1] 19 | ctx := context.Background() 20 | guest, err := os.ReadFile("hello/hello.wasm") 21 | if err != nil { 22 | panic(err) 23 | } 24 | 25 | engine := wazero.Engine() 26 | 27 | module, err := engine.New(ctx, host, guest, &wapc.ModuleConfig{ 28 | Logger: wapc.PrintlnLogger, 29 | Stdout: os.Stdout, 30 | Stderr: os.Stderr, 31 | }) 32 | if err != nil { 33 | panic(err) 34 | } 35 | defer module.Close(ctx) 36 | 37 | instance, err := module.Instantiate(ctx) 38 | if err != nil { 39 | panic(err) 40 | } 41 | defer instance.Close(ctx) 42 | 43 | result, err := instance.Invoke(ctx, "hello", []byte(name)) 44 | if err != nil { 45 | panic(err) 46 | } 47 | 48 | fmt.Println(string(result)) 49 | } 50 | 51 | func host(_ context.Context, binding, namespace, operation string, payload []byte) ([]byte, error) { 52 | // Route the payload to any custom functionality accordingly. 53 | // You can even route to other waPC modules!!! 54 | switch namespace { 55 | case "example": 56 | switch operation { 57 | case "capitalize": 58 | name := string(payload) 59 | name = strings.Title(name) // nolint 60 | return []byte(name), nil 61 | } 62 | } 63 | return []byte("default"), nil 64 | } 65 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/wapc/wapc-go 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.23.4 6 | 7 | require github.com/Workiva/go-datastructures v1.1.5 8 | 9 | require github.com/stretchr/testify v1.10.0 // indirect 10 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/Workiva/go-datastructures v1.1.5 h1:5YfhQ4ry7bZc2Mc7R0YZyYwpf5c6t1cEFvdAhd6Mkf4= 2 | github.com/Workiva/go-datastructures v1.1.5/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A= 3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 5 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= 7 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 8 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 9 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 10 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 11 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 12 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 13 | github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg= 14 | github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q= 15 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 16 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 17 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 18 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 19 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 20 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 21 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 22 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 23 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 24 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 25 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 26 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 27 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 28 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 29 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 30 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 31 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 32 | golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 33 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 34 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 35 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 36 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 37 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 38 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 39 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 40 | -------------------------------------------------------------------------------- /go.work: -------------------------------------------------------------------------------- 1 | go 1.23.0 2 | 3 | use ( 4 | . 5 | ./engines/wasmer 6 | ./engines/wasmtime 7 | ./engines/wazero 8 | ./example 9 | ./hello 10 | ./testdata/go 11 | ) 12 | -------------------------------------------------------------------------------- /go.work.sum: -------------------------------------------------------------------------------- 1 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 2 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 3 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 4 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 5 | golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= 6 | gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 7 | -------------------------------------------------------------------------------- /hello/Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | @echo "----------" 3 | @echo "Building Go wasm Guest" 4 | @echo "----------" 5 | tinygo build -o hello.wasm -scheduler=none --no-debug -target=wasip1 -buildmode=c-shared main.go 6 | -------------------------------------------------------------------------------- /hello/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/wapc/wapc-go/hello 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.23.4 6 | 7 | require github.com/wapc/wapc-guest-tinygo v0.3.3 8 | -------------------------------------------------------------------------------- /hello/go.sum: -------------------------------------------------------------------------------- 1 | github.com/wapc/wapc-guest-tinygo v0.3.3 h1:jLebiwjVSHLGnS+BRabQ6+XOV7oihVWAc05Hf1SbeR0= 2 | github.com/wapc/wapc-guest-tinygo v0.3.3/go.mod h1:mzM3CnsdSYktfPkaBdZ8v88ZlfUDEy5Jh5XBOV3fYcw= 3 | -------------------------------------------------------------------------------- /hello/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | wapc "github.com/wapc/wapc-guest-tinygo" 7 | ) 8 | 9 | //go:wasmexport wapc_init 10 | func Initialize() { 11 | // Register echo and fail functions 12 | wapc.RegisterFunctions(wapc.Functions{ 13 | "hello": Hello, 14 | }) 15 | } 16 | 17 | // Hello will callback the host and return the payload 18 | func Hello(payload []byte) ([]byte, error) { 19 | fmt.Println("hello called") 20 | // Make a host call to capitalize the name. 21 | nameBytes, err := wapc.HostCall("", "example", "capitalize", payload) 22 | if err != nil { 23 | return nil, err 24 | } 25 | // Format the message. 26 | msg := "Hello, " + string(nameBytes) 27 | return []byte(msg), nil 28 | } 29 | -------------------------------------------------------------------------------- /pool.go: -------------------------------------------------------------------------------- 1 | package wapc 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "time" 8 | 9 | "github.com/Workiva/go-datastructures/queue" 10 | ) 11 | 12 | type ( 13 | // Pool is a wrapper around a ringbuffer of WASM modules 14 | Pool struct { 15 | rb *queue.RingBuffer 16 | module Module 17 | instances []Instance 18 | } 19 | 20 | InstanceInitialize func(instance Instance) error 21 | ) 22 | 23 | // NewPool takes in compiled WASM module and a size and returns a pool 24 | // containing `size` instances of that module. 25 | func NewPool(ctx context.Context, module Module, size uint64, initializer ...InstanceInitialize) (*Pool, error) { 26 | var initialize InstanceInitialize 27 | if len(initializer) > 0 { 28 | initialize = initializer[0] 29 | } 30 | rb := queue.NewRingBuffer(size) 31 | instances := make([]Instance, size) 32 | for i := uint64(0); i < size; i++ { 33 | inst, err := module.Instantiate(ctx) 34 | if err != nil { 35 | return nil, err 36 | } 37 | 38 | if initialize != nil { 39 | if err = initialize(inst); err != nil { 40 | return nil, fmt.Errorf("could not initialize instance: %w", err) 41 | } 42 | } 43 | 44 | ok, err := rb.Offer(inst) 45 | if err != nil { 46 | return nil, err 47 | } 48 | if !ok { 49 | return nil, fmt.Errorf("could not add module %d to module pool of size %d", i, size) 50 | } 51 | 52 | instances[i] = inst 53 | } 54 | 55 | return &Pool{ 56 | rb: rb, 57 | module: module, 58 | instances: instances, 59 | }, nil 60 | } 61 | 62 | // Get returns a module from the pool if it can be retrieved 63 | // within the passed timeout window, if not it returns an error 64 | func (p *Pool) Get(timeout time.Duration) (Instance, error) { 65 | instanceIface, err := p.rb.Poll(timeout) 66 | if err != nil { 67 | return nil, fmt.Errorf("get from pool timed out: %w", err) 68 | } 69 | 70 | inst, ok := instanceIface.(Instance) 71 | if !ok { 72 | return nil, errors.New("item retrieved from pool is not an instance") 73 | } 74 | 75 | return inst, nil 76 | } 77 | 78 | // Return takes a module and adds it to the pool 79 | // This should only be called using a module 80 | func (p *Pool) Return(inst Instance) error { 81 | ok, err := p.rb.Offer(inst) 82 | if err != nil { 83 | return err 84 | } 85 | 86 | if !ok { 87 | return errors.New("cannot return instance to full pool") 88 | } 89 | 90 | return nil 91 | } 92 | 93 | // Close closes down all the instances contained by the pool. 94 | func (p *Pool) Close(ctx context.Context) { 95 | p.rb.Dispose() 96 | 97 | for _, inst := range p.instances { 98 | inst.Close(ctx) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /testdata/as/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | -------------------------------------------------------------------------------- /testdata/as/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build 2 | build: 3 | @echo "----------" 4 | @echo "Building AssemblyScript wasm Guest" 5 | @echo "----------" 6 | npm install 7 | npm run build 8 | -------------------------------------------------------------------------------- /testdata/as/assembly/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | register, 3 | handleCall, 4 | hostCall, 5 | handleAbort, 6 | Result, 7 | } from "@wapc/as-guest"; 8 | 9 | // Register Successful Function 10 | register("echo", function(payload: ArrayBuffer): Result { 11 | // Callback with Payload 12 | hostCall("wapc", "testing", "echo", payload) 13 | return Result.ok(payload); 14 | }) 15 | 16 | // Register Error Function 17 | register("nope", function(payload: ArrayBuffer): Result { 18 | throw Result.error(new Error("No payload")); 19 | }) 20 | 21 | // waPC boilerplate code 22 | export function __guest_call(operation_size: usize, payload_size: usize): bool { 23 | return handleCall(operation_size, payload_size); 24 | } 25 | 26 | function abort(message: string | null, fileName: string | null, lineNumber: u32, columnNumber: u32): void { 27 | handleAbort(message, fileName, lineNumber, columnNumber); 28 | } 29 | -------------------------------------------------------------------------------- /testdata/as/assembly/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "assemblyscript/std/assembly.json", 3 | "include": [ 4 | "./**/*.ts" 5 | ] 6 | } -------------------------------------------------------------------------------- /testdata/as/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello_wapc", 3 | "version": "1.0.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "hello_wapc", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@wapc/as-guest": "^v0.3.1" 13 | }, 14 | "devDependencies": { 15 | "assemblyscript": "^0.27.31" 16 | } 17 | }, 18 | "node_modules/@wapc/as-guest": { 19 | "version": "0.3.1", 20 | "resolved": "https://registry.npmjs.org/@wapc/as-guest/-/as-guest-0.3.1.tgz", 21 | "integrity": "sha512-RftwLaQTB9GPX5Nu/sKoPcTP4sxmUYYqdMLRhi9xBj14SK7rP3EP2wvIbnSga4xBBdyXvd8v5Tsq7bfAj5BhSA==" 22 | }, 23 | "node_modules/assemblyscript": { 24 | "version": "0.27.31", 25 | "resolved": "https://registry.npmjs.org/assemblyscript/-/assemblyscript-0.27.31.tgz", 26 | "integrity": "sha512-Ra8kiGhgJQGZcBxjtMcyVRxOEJZX64kd+XGpjWzjcjgxWJVv+CAQO0aDBk4GQVhjYbOkATarC83mHjAVGtwPBQ==", 27 | "dev": true, 28 | "license": "Apache-2.0", 29 | "dependencies": { 30 | "binaryen": "116.0.0-nightly.20240114", 31 | "long": "^5.2.1" 32 | }, 33 | "bin": { 34 | "asc": "bin/asc.js", 35 | "asinit": "bin/asinit.js" 36 | }, 37 | "engines": { 38 | "node": ">=16", 39 | "npm": ">=7" 40 | }, 41 | "funding": { 42 | "type": "opencollective", 43 | "url": "https://opencollective.com/assemblyscript" 44 | } 45 | }, 46 | "node_modules/binaryen": { 47 | "version": "116.0.0-nightly.20240114", 48 | "resolved": "https://registry.npmjs.org/binaryen/-/binaryen-116.0.0-nightly.20240114.tgz", 49 | "integrity": "sha512-0GZrojJnuhoe+hiwji7QFaL3tBlJoA+KFUN7ouYSDGZLSo9CKM8swQX8n/UcbR0d1VuZKU+nhogNzv423JEu5A==", 50 | "dev": true, 51 | "license": "Apache-2.0", 52 | "bin": { 53 | "wasm-opt": "bin/wasm-opt", 54 | "wasm2js": "bin/wasm2js" 55 | } 56 | }, 57 | "node_modules/long": { 58 | "version": "5.2.4", 59 | "resolved": "https://registry.npmjs.org/long/-/long-5.2.4.tgz", 60 | "integrity": "sha512-qtzLbJE8hq7VabR3mISmVGtoXP8KGc2Z/AT8OuqlYD7JTR3oqrgwdjnk07wpj1twXxYmgDXgoKVWUG/fReSzHg==", 61 | "dev": true, 62 | "license": "Apache-2.0" 63 | } 64 | }, 65 | "dependencies": { 66 | "@wapc/as-guest": { 67 | "version": "0.3.1", 68 | "resolved": "https://registry.npmjs.org/@wapc/as-guest/-/as-guest-0.3.1.tgz", 69 | "integrity": "sha512-RftwLaQTB9GPX5Nu/sKoPcTP4sxmUYYqdMLRhi9xBj14SK7rP3EP2wvIbnSga4xBBdyXvd8v5Tsq7bfAj5BhSA==" 70 | }, 71 | "assemblyscript": { 72 | "version": "0.27.31", 73 | "resolved": "https://registry.npmjs.org/assemblyscript/-/assemblyscript-0.27.31.tgz", 74 | "integrity": "sha512-Ra8kiGhgJQGZcBxjtMcyVRxOEJZX64kd+XGpjWzjcjgxWJVv+CAQO0aDBk4GQVhjYbOkATarC83mHjAVGtwPBQ==", 75 | "dev": true, 76 | "requires": { 77 | "binaryen": "116.0.0-nightly.20240114", 78 | "long": "^5.2.1" 79 | } 80 | }, 81 | "binaryen": { 82 | "version": "116.0.0-nightly.20240114", 83 | "resolved": "https://registry.npmjs.org/binaryen/-/binaryen-116.0.0-nightly.20240114.tgz", 84 | "integrity": "sha512-0GZrojJnuhoe+hiwji7QFaL3tBlJoA+KFUN7ouYSDGZLSo9CKM8swQX8n/UcbR0d1VuZKU+nhogNzv423JEu5A==", 85 | "dev": true 86 | }, 87 | "long": { 88 | "version": "5.2.4", 89 | "resolved": "https://registry.npmjs.org/long/-/long-5.2.4.tgz", 90 | "integrity": "sha512-qtzLbJE8hq7VabR3mISmVGtoXP8KGc2Z/AT8OuqlYD7JTR3oqrgwdjnk07wpj1twXxYmgDXgoKVWUG/fReSzHg==", 91 | "dev": true 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /testdata/as/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello_wapc", 3 | "version": "1.0.0", 4 | "description": "AssemblyScript waPC example", 5 | "scripts": { 6 | "build": "asc assembly/index.ts -o hello.wasm --use abort=assembly/index/abort --optimize" 7 | }, 8 | "author": "", 9 | "license": "ISC", 10 | "dependencies": { 11 | "@wapc/as-guest": "^v0.3.1" 12 | }, 13 | "devDependencies": { 14 | "assemblyscript": "^0.27.31" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /testdata/go/Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | @echo "----------" 3 | @echo "Building Go wasm Guest" 4 | @echo "----------" 5 | tinygo build -o hello.wasm -scheduler=none --no-debug -target=wasip1 -buildmode=c-shared main.go 6 | -------------------------------------------------------------------------------- /testdata/go/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/wapc/wapc-go/testdata/go 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.23.4 6 | 7 | require github.com/wapc/wapc-guest-tinygo v0.3.3 8 | -------------------------------------------------------------------------------- /testdata/go/go.sum: -------------------------------------------------------------------------------- 1 | github.com/wapc/wapc-guest-tinygo v0.3.3 h1:jLebiwjVSHLGnS+BRabQ6+XOV7oihVWAc05Hf1SbeR0= 2 | github.com/wapc/wapc-guest-tinygo v0.3.3/go.mod h1:mzM3CnsdSYktfPkaBdZ8v88ZlfUDEy5Jh5XBOV3fYcw= 3 | -------------------------------------------------------------------------------- /testdata/go/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | wapc "github.com/wapc/wapc-guest-tinygo" 7 | ) 8 | 9 | //go:wasmexport wapc_init 10 | func Initialize() { 11 | // Register echo and fail functions 12 | wapc.RegisterFunctions(wapc.Functions{ 13 | "echo": Echo, 14 | "nope": Fail, 15 | }) 16 | } 17 | 18 | // Echo will callback the host and return the payload 19 | func Echo(payload []byte) ([]byte, error) { 20 | // Callback with Payload 21 | _, err := wapc.HostCall("wapc", "testing", "echo", payload) 22 | if err != nil { 23 | return []byte(""), err 24 | } 25 | return payload, nil 26 | } 27 | 28 | // Fail will return an error when called 29 | func Fail(payload []byte) ([]byte, error) { 30 | return []byte(""), fmt.Errorf("Planned Failure") 31 | } 32 | -------------------------------------------------------------------------------- /testdata/rust/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "autocfg" 7 | version = "1.1.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 10 | 11 | [[package]] 12 | name = "bitflags" 13 | version = "2.6.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 16 | 17 | [[package]] 18 | name = "cfg-if" 19 | version = "1.0.0" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 22 | 23 | [[package]] 24 | name = "libc" 25 | version = "0.2.126" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" 28 | 29 | [[package]] 30 | name = "lock_api" 31 | version = "0.4.7" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" 34 | dependencies = [ 35 | "autocfg", 36 | "scopeguard", 37 | ] 38 | 39 | [[package]] 40 | name = "once_cell" 41 | version = "1.13.0" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" 44 | 45 | [[package]] 46 | name = "parking_lot" 47 | version = "0.12.3" 48 | source = "registry+https://github.com/rust-lang/crates.io-index" 49 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 50 | dependencies = [ 51 | "lock_api", 52 | "parking_lot_core", 53 | ] 54 | 55 | [[package]] 56 | name = "parking_lot_core" 57 | version = "0.9.10" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 60 | dependencies = [ 61 | "cfg-if", 62 | "libc", 63 | "redox_syscall", 64 | "smallvec", 65 | "windows-targets", 66 | ] 67 | 68 | [[package]] 69 | name = "redox_syscall" 70 | version = "0.5.3" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" 73 | dependencies = [ 74 | "bitflags", 75 | ] 76 | 77 | [[package]] 78 | name = "rust" 79 | version = "0.1.0" 80 | dependencies = [ 81 | "wapc-guest", 82 | ] 83 | 84 | [[package]] 85 | name = "scopeguard" 86 | version = "1.1.0" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 89 | 90 | [[package]] 91 | name = "smallvec" 92 | version = "1.9.0" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" 95 | 96 | [[package]] 97 | name = "wapc-guest" 98 | version = "1.1.0" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "57b7619cdb37b84600569a46fe2e9ded4b5a68acc394efbe40092fb63ff2808c" 101 | dependencies = [ 102 | "once_cell", 103 | "parking_lot", 104 | ] 105 | 106 | [[package]] 107 | name = "windows-targets" 108 | version = "0.52.6" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 111 | dependencies = [ 112 | "windows_aarch64_gnullvm", 113 | "windows_aarch64_msvc", 114 | "windows_i686_gnu", 115 | "windows_i686_gnullvm", 116 | "windows_i686_msvc", 117 | "windows_x86_64_gnu", 118 | "windows_x86_64_gnullvm", 119 | "windows_x86_64_msvc", 120 | ] 121 | 122 | [[package]] 123 | name = "windows_aarch64_gnullvm" 124 | version = "0.52.6" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 127 | 128 | [[package]] 129 | name = "windows_aarch64_msvc" 130 | version = "0.52.6" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 133 | 134 | [[package]] 135 | name = "windows_i686_gnu" 136 | version = "0.52.6" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 139 | 140 | [[package]] 141 | name = "windows_i686_gnullvm" 142 | version = "0.52.6" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 145 | 146 | [[package]] 147 | name = "windows_i686_msvc" 148 | version = "0.52.6" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 151 | 152 | [[package]] 153 | name = "windows_x86_64_gnu" 154 | version = "0.52.6" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 157 | 158 | [[package]] 159 | name = "windows_x86_64_gnullvm" 160 | version = "0.52.6" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 163 | 164 | [[package]] 165 | name = "windows_x86_64_msvc" 166 | version = "0.52.6" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 169 | -------------------------------------------------------------------------------- /testdata/rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust" 3 | version = "0.1.0" 4 | authors = ["Benjamin Cane "] 5 | edition = "2021" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | wapc-guest = "1.1.0" 11 | -------------------------------------------------------------------------------- /testdata/rust/Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | @echo "----------" 3 | @echo "Building Rust wasm Guest" 4 | @echo "----------" 5 | cargo build --target wasm32-unknown-unknown --release 6 | cp target/wasm32-unknown-unknown/release/rust.wasm ./hello.wasm 7 | -------------------------------------------------------------------------------- /testdata/rust/src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate wapc_guest as guest; 2 | use guest::prelude::*; 3 | 4 | fn main() {} 5 | 6 | #[no_mangle] 7 | pub extern "C" fn wapc_init() { 8 | // Register echo and nope functions 9 | register_function("echo", hello); 10 | register_function("nope", fail); 11 | } 12 | 13 | // hello will callback the host and return the payload 14 | fn hello(msg: &[u8]) -> CallResult { 15 | let _res = host_call("wapc", "testing", "echo", &msg.to_vec()); 16 | Ok(msg.to_vec()) 17 | } 18 | 19 | // fail will return an error result 20 | fn fail(_msg: &[u8]) -> CallResult { 21 | Err("Planned Failure".into()) 22 | } 23 | --------------------------------------------------------------------------------