├── .editorconfig ├── .github └── workflows │ ├── benchmark.yml │ ├── example.yml │ ├── integration-test.yml │ ├── witc-release.yml │ └── witc.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── Setup.hs ├── app └── Main.hs ├── bench ├── Cargo.toml ├── README.md ├── instance_export.wit ├── instance_export │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── instance_import │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── runtime │ ├── Cargo.toml │ └── src │ │ └── lib.rs └── runtime_export.wit ├── bindings └── rust │ ├── .gitignore │ ├── invoke-witc │ ├── Cargo.toml │ ├── README.md │ └── src │ │ └── lib.rs │ └── witc-abi │ ├── Cargo.toml │ ├── README.md │ └── src │ ├── instance.rs │ ├── lib.rs │ └── runtime.rs ├── example ├── WitTest │ ├── Cargo.toml │ ├── host │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ ├── lib │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ ├── recursive-types.wit │ ├── test.wit │ └── types.wit ├── keyvalue-demo │ ├── Cargo.toml │ ├── README.md │ ├── instance-client │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ ├── keyvalue.wit │ └── runtime-store │ │ ├── Cargo.toml │ │ └── src │ │ └── main.rs ├── logging-demo │ ├── Cargo.toml │ ├── README.md │ ├── instance-logging │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ ├── instance-service │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ ├── logging.wit │ └── runtime │ │ ├── Cargo.toml │ │ └── src │ │ └── main.rs ├── traffic-lights │ ├── Cargo.toml │ ├── README.md │ ├── instance-lights │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ ├── runtime-toggle │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ └── traffic-lights.wit └── wasmedge_opencvmini.wit ├── install.ps1 ├── install.sh ├── note ├── README.md └── drop │ ├── c_abi.md │ ├── component-three-fn-impl.md │ └── wasm_abi.md ├── package.yaml ├── src └── Wit │ ├── Ast.hs │ ├── Check.hs │ ├── Config.hs │ ├── Gen.hs │ ├── Gen │ ├── Export.hs │ ├── Import.hs │ ├── Normalization.hs │ ├── Plugin.hs │ └── Type.hs │ ├── Parser.hs │ └── TypeValue.hs ├── stack.yaml ├── stack.yaml.lock ├── test-project ├── Cargo.toml ├── Makefile ├── export1.wit ├── export2.wit ├── import_wasm │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ └── lib.rs └── src │ └── main.rs ├── test ├── Spec.hs ├── Wit │ ├── CheckSpec.hs │ └── ParserSpec.hs └── data │ ├── bad-import.wit │ ├── bad-import2.wit │ ├── bad-types.wit │ ├── bad-types2.wit │ ├── cycle-import-a.wit │ ├── cycle-import-b.wit │ ├── func-using-missing-type.wit │ └── good.wit └── witc.cabal /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is the top-most EditorConfig file 2 | root = true 3 | 4 | # All Files 5 | [*] 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | 9 | [*.hs] 10 | indent_style = space 11 | indent_size = 2 12 | 13 | [*.rs] 14 | indent_style = space 15 | indent_size = 4 16 | -------------------------------------------------------------------------------- /.github/workflows/benchmark.yml: -------------------------------------------------------------------------------- 1 | name: Rust Benchmark 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | branches: 8 | - main 9 | 10 | permissions: 11 | contents: write 12 | deployments: write 13 | 14 | jobs: 15 | benchmark: 16 | name: Run Rust benchmark 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v2 20 | - uses: dtolnay/rust-toolchain@stable 21 | with: 22 | toolchain: nightly 23 | target: wasm32-wasi 24 | - name: Install WasmEdge 25 | run: | 26 | curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | bash -s -- -v 0.13.3 27 | - name: Install witc 28 | run: | 29 | stack install 30 | - name: Run benchmark 31 | run: | 32 | source $HOME/.wasmedge/env 33 | cd bench && cargo build --release 34 | cargo bench -p runtime | tee output.txt 35 | - name: Store benchmark result 36 | uses: benchmark-action/github-action-benchmark@v1 37 | with: 38 | name: Rust Benchmark 39 | tool: "cargo" 40 | output-file-path: bench/output.txt 41 | github-token: ${{ secrets.GITHUB_TOKEN }} 42 | auto-push: true 43 | # Show alert with commit comment on detecting possible performance regression 44 | alert-threshold: "200%" 45 | comment-on-alert: true 46 | fail-on-alert: false 47 | alert-comment-cc-users: "@dannypsnl,@dm4" 48 | -------------------------------------------------------------------------------- /.github/workflows/example.yml: -------------------------------------------------------------------------------- 1 | name: Run examples 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - "**.hs" 9 | - "**.cabal" 10 | - "**.yaml" 11 | - "**.yml" 12 | - "**.toml" 13 | - "**.rs" 14 | - "**.wit" 15 | pull_request: 16 | branches: 17 | - main 18 | paths: 19 | - "**.hs" 20 | - "**.cabal" 21 | - "**.yaml" 22 | - "**.yml" 23 | - "**.toml" 24 | - "**.rs" 25 | - "**.wit" 26 | 27 | jobs: 28 | cargo_run: 29 | runs-on: ubuntu-latest 30 | steps: 31 | - uses: actions/checkout@v3 32 | - uses: haskell/actions/setup@v2 33 | - uses: dtolnay/rust-toolchain@stable 34 | with: 35 | toolchain: nightly 36 | target: wasm32-wasi 37 | - name: Install WasmEdge 38 | run: | 39 | curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | bash -s -- -v 0.13.3 40 | - name: Install witc 41 | run: | 42 | stack install 43 | - name: Run all examples 44 | run: | 45 | source $HOME/.wasmedge/env 46 | (cd example/logging-demo && cargo run --release) 47 | (cd example/keyvalue-demo && cargo run --release) 48 | (cd example/WitTest && cargo run --release) 49 | (cd example/traffic-lights && cargo run --release) 50 | -------------------------------------------------------------------------------- /.github/workflows/integration-test.yml: -------------------------------------------------------------------------------- 1 | name: Integration Test 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | branches: 8 | - main 9 | 10 | jobs: 11 | benchmark: 12 | name: Run Rust test 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v2 16 | - uses: dtolnay/rust-toolchain@stable 17 | with: 18 | toolchain: stable 19 | target: wasm32-wasi 20 | - name: Install WasmEdge 21 | run: | 22 | curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | bash -s -- -v 0.13.3 23 | - name: Install witc 24 | run: | 25 | stack install 26 | - name: Run tess 27 | run: | 28 | source $HOME/.wasmedge/env 29 | cd test-project && make test 30 | -------------------------------------------------------------------------------- /.github/workflows/witc-release.yml: -------------------------------------------------------------------------------- 1 | name: Release witc artifacts 2 | 3 | on: 4 | # Trigger the workflow on the new 'v*' tag created 5 | push: 6 | tags: 7 | - "v*" 8 | workflow_dispatch: 9 | 10 | permissions: 11 | contents: write 12 | 13 | jobs: 14 | create_release: 15 | name: Create Github Release 16 | strategy: 17 | matrix: 18 | os: [ubuntu, windows, macos] 19 | env: 20 | SUFFIX: "${{ github.ref_name }}-${{ matrix.os }}" 21 | runs-on: ${{ matrix.os }}-latest 22 | steps: 23 | - uses: actions/checkout@v3 24 | - uses: haskell/actions/setup@v2 25 | with: 26 | ghc-version: "9.2.8" 27 | - run: stack install --local-bin-path ./ 28 | - if: matrix.os == 'windows' 29 | run: | 30 | mv witc.exe witc-$env:SUFFIX.exe 31 | echo "artifactPath=witc-$env:SUFFIX.exe" >> $env:GITHUB_ENV 32 | - if: matrix.os != 'windows' 33 | run: | 34 | mv witc witc-$SUFFIX 35 | echo "artifactPath=witc-$SUFFIX" >> $GITHUB_ENV 36 | 37 | - name: Upload binary 38 | uses: actions/upload-artifact@v3 39 | with: 40 | name: witc binary 41 | path: ${{ env.artifactPath }} 42 | 43 | - name: Get current date 44 | id: date 45 | run: echo "DATE=$(date +%Y-%m-%d-%H:%M)" >> $GITHUB_ENV 46 | 47 | - name: Release 48 | uses: softprops/action-gh-release@v1 49 | with: 50 | files: ${{ env.artifactPath }} 51 | -------------------------------------------------------------------------------- /.github/workflows/witc.yml: -------------------------------------------------------------------------------- 1 | name: Build and Test witc 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - "**.hs" 9 | - "**.cabal" 10 | - "**.yaml" 11 | - "**.yml" 12 | pull_request: 13 | branches: 14 | - main 15 | paths: 16 | - "**.hs" 17 | - "**.cabal" 18 | - "**.yaml" 19 | - "**.yml" 20 | 21 | jobs: 22 | test: 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v3 26 | - uses: haskell/actions/setup@v2 27 | - run: stack test 28 | build: 29 | runs-on: ubuntu-latest 30 | steps: 31 | - uses: actions/checkout@v3 32 | - uses: haskell/actions/setup@v2 33 | - run: stack build 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .stack-work/ 2 | *~ 3 | dist-*/ 4 | target/ 5 | Cargo.lock 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog for `witc` 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to the 7 | [Haskell Package Versioning Policy](https://pvp.haskell.org/). 8 | 9 | ## Unreleased 10 | 11 | - syntax: allow omit return type 12 | - rust: export generated type 13 | 14 | ## 0.4 15 | 16 | - plugin generation: 17 | - generate type definition into Plugin 18 | - allow local name ref type 19 | - check circular imports 20 | - codegen rust FFI for plugin 21 | 22 | ## 0.3.1 23 | 24 | - any error should lead process error 25 | 26 | ## 0.3 27 | 28 | - upgrade to wasmedge 0.12.1 (sdk 0.8.1) 29 | - correct dependencies lookup 30 | 31 | introduce working directory concept 32 | 33 | 1. for file checking, the locaiton directory of file is the working directory 34 | 2. for directory checking, the directory is the working directory 35 | 36 | ## 0.2.1 37 | 38 | - export multiple component in runtime 39 | 40 | ## 0.2 41 | 42 | - performance improvement: memory queue of call 43 | - validation: check directory 44 | - CLI: add `--help` and subcommand 45 | - validation: check imports existed, e.g. `use {a, b, c} from m` will ensure `m` does have type definition `a`, `b`, and `c` 46 | 47 | ## 0.1 48 | 49 | - wasm interface types supporting 50 | - `func` 51 | - `record` 52 | - `variant` 53 | - `enum` 54 | - `resource` 55 | - backend: code generation 56 | - rust 57 | - runtime import/export 58 | - instance import/export 59 | - check command 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 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. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # witc 2 | 3 | > **Warning** 4 | > This is an early-stage project 5 | 6 | A compiler generates code for `*.wit` files, this project purpose to make `*.wit` supportment for [wasmedge](https://github.com/WasmEdge/WasmEdge). 7 | 8 | - [benchmark tracking](https://second-state.github.io/witc/dev/bench/index.html) 9 | 10 | ### Overview 11 | 12 | To understand this project, I will show you what's `*.wit` stand for first. The story starts by passing a string as an argument to function in the wasm instance, if you do so, you will find out that wasm has no type called `string`. You will figure out that you only need to encode the string as a pair of `i32`, which means `(i32, i32)` and one for address, one for string length. However, the address valid out of an instance will not be valid in that instance. Then you found runtime(e.g. wasmedge, wasmtime) can operate the memory of instances, write data to somewhere in the instance, and use that address, problem solved! 13 | 14 | Quickly, your program grows, and now you manage tons of mappings. 15 | 16 | ```rust 17 | fn foo(s: String, s2: String) -> String 18 | // <-> 19 | fn foo(s_addr: i32, s_size: i32, s2_addr: i32, s2_size: i32) -> (i32, i32) 20 | ``` 21 | 22 | The thing is a bit out of control, not to say compound types like **structure**, **enum**, and **list**. In this sense, **wit** stands for one source code that is reusable for multi-target, and multi-direction and **witc** does code generation and manages ABI and memory operations. Thus, you can import/export types or functions from instance or runtime. 23 | 24 | ### Installation 25 | 26 | `witc` has pre-built binary can be installed via our bash installation script. 27 | 28 | ```sh 29 | curl -sSf https://raw.githubusercontent.com/second-state/witc/main/install.sh | bash 30 | ``` 31 | 32 | #### Windows 33 | 34 | `witc` has a powershell installation script, but it's not full automatic. 35 | 36 | ```powershell 37 | wget https://raw.githubusercontent.com/second-state/witc/main/install.ps1 38 | ./install.ps1 39 | ``` 40 | 41 | Once you run the installation script, it should download the latest executable to **current path** you run the script, so you have to add this path into `$env:PATH` or move the executable to somewhere in `$env:PATH`. 42 | 43 | #### Build from Haskell source 44 | 45 | If you want the latest executable, you need to build from source. 46 | 47 | ```sh 48 | stack install 49 | # install to `~/.local/bin` 50 | ``` 51 | 52 | ### Usage 53 | 54 | `*.wit` files are sharing interface and common types between different component in the wasm ecosystem, a runtime is a component, a running wasm module is a component. In our context, wasmedge is the runtime, and wasm module instance is the instance. Under **witc**, you have three directions 55 | 56 | 1. instance invokes a runtime function 57 | 2. instance invokes an instance function 58 | 3. runtime invokes an instance function 59 | 60 | Without **witc** you can still invoke these function, but have to convert typing by yourself and figuring out complicated layout. Now, development is easy and lovely, all callsite and implementation using the native type of that language. 61 | 62 | #### Rust examples 63 | 64 | > **Note** Don't forget to install rust supplyment when you are trying to run example out of box 65 | 66 | To illustrate how to use witc, we have the following examples, and you can use the following command to run them 67 | 68 | ```sh 69 | cargo run --release 70 | ``` 71 | 72 | - [demo: keyvalue](./example/keyvalue-demo/): instance invokes runtime 73 | - [demo: logging](./example/logging-demo/): instance invokes instance 74 | - [demo: traffic lights](./example/traffic-lights/): runtime invokes instance 75 | 76 | #### Generate Plugin header 77 | 78 | This is new experiment we are doing, this ability will relief wasmedge plugin users from manually management for the standard API, there will have `*.wit` definitions for these plugins. All you need to is using witc to manage it, here is an example: 79 | 80 | ```sh 81 | $ witc plugin ./example/wasmedge_opencvmini.wit 82 | 83 | #[link(wasm_import_module = "wasmedge_opencvmini")] 84 | extern "C" { 85 | #[link_name = "wasmedge_opencvmini_imdecode"] 86 | pub fn imdecode(buf_ptr: *const u8, buf_len: usize) -> u32; 87 | #[link_name = "wasmedge_opencvmini_imshow"] 88 | pub fn imshow(window_name_ptr: *const u8, window_name_len: usize, mat_key: u32) -> (); 89 | #[link_name = "wasmedge_opencvmini_waitkey"] 90 | pub fn waitkey(delay: u32) -> (); 91 | } 92 | ``` 93 | 94 | #### CLI 95 | 96 | You can also use following commands to let compiler output to stdout 97 | 98 | - instance 99 | 100 | - import `witc instance import xxx.wit`, or `witc instance import xxx.wit yyy` if you want to have different import name(usually happened when you import code from another instance) 101 | - export `witc instance export xxx.wit` 102 | 103 | - runtime 104 | 105 | - export `witc runtime export xxx.wit` 106 | 107 | ### Development 108 | 109 | To get the proper Haskell configuration, we recommend you install the following combination with [`ghcup`](https://www.haskell.org/ghcup/). 110 | 111 | ```shell 112 | ghcup install ghc 9.2.7 113 | ghcup install hls 1.10.0.0 114 | ``` 115 | 116 | ### Why witc? 117 | 118 | You might wonder why you need `witc` since `wit-bindgen` already exists. Although `wit-bindgen` is good, it is currently in active development, and `witc` will explore different approach that increases the diversity of wit related toolchain. Additionally, the Component Model and Canonical ABI change frequently with large updates, in this sense `witc` will serve as a middle project to wait for `wit-bindgen` to become stable. We will contribute to `wit-bindgen` at that point, for these reasons, we will support a small number of features in `witc` that only ensuring that the basic demo works. 119 | -------------------------------------------------------------------------------- /Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /app/Main.hs: -------------------------------------------------------------------------------- 1 | {- 2 | cli design 3 | 4 | witc instance import xxx.wit 5 | witc runtime export xxx.wit 6 | witc check xxx.wit 7 | witc check -- check all wit files in current directory 8 | -} 9 | module Main (main) where 10 | 11 | import Control.Monad 12 | import Control.Monad.Except 13 | import Control.Monad.Reader 14 | import Data.List (isSuffixOf) 15 | import Options.Applicative 16 | import Prettyprinter 17 | import Prettyprinter.Render.Terminal 18 | import System.Directory 19 | import System.Exit 20 | import System.FilePath 21 | import Wit.Check 22 | import Wit.Config 23 | import Wit.Gen 24 | 25 | main :: IO () 26 | main = do 27 | join $ 28 | execParser 29 | ( info 30 | (helper <*> versionOption <*> programOptions) 31 | ( fullDesc 32 | <> progDesc "compiler for wit" 33 | <> header 34 | "witc - compiler for wit, a language for describing wasm interface types" 35 | ) 36 | ) 37 | where 38 | versionOption :: Parser (a -> a) 39 | versionOption = infoOption "0.4" (long "version" <> help "Show version") 40 | programOptions :: Parser (IO ()) 41 | programOptions = 42 | subparser 43 | ( command 44 | "plugin" 45 | ( info 46 | (codegenPluginCmd <$> strArgument (metavar "FILE" <> help "Wit file")) 47 | (progDesc "Generate plugin import code for wasm module") 48 | ) 49 | <> command 50 | "check" 51 | ( info 52 | (checkCmd <$> optional (strArgument (metavar "FILE" <> help "Name of the thing to create"))) 53 | (progDesc "Validate wit file") 54 | ) 55 | <> command 56 | "instance" 57 | ( info 58 | ( subparser 59 | ( command 60 | "import" 61 | ( info 62 | ( codegenCmd (Instance Import) 63 | <$> strArgument (metavar "FILE" <> help "Wit file") 64 | <*> strArgument (value "wasmedge" <> help "Name of import") 65 | ) 66 | (progDesc "Generate import code for instance (wasm)") 67 | ) 68 | <> command 69 | "export" 70 | ( info 71 | ( codegenCmd (Instance Export) 72 | <$> strArgument (metavar "FILE" <> help "Wit file") 73 | <*> strArgument (value "wasmedge" <> help "Name of export") 74 | ) 75 | (progDesc "Generate export code for instance (wasm)") 76 | ) 77 | ) 78 | ) 79 | (progDesc "Generate code for instance (wasm)") 80 | ) 81 | <> command 82 | "runtime" 83 | ( info 84 | ( subparser 85 | ( command 86 | "import" 87 | ( info 88 | ( codegenCmd (Runtime Import) 89 | <$> strArgument (metavar "FILE" <> help "Wit file") 90 | <*> strArgument (value "wasmedge" <> help "Name of import") 91 | ) 92 | (progDesc "Generate import code for runtime (WasmEdge)") 93 | ) 94 | <> command 95 | "export" 96 | ( info 97 | ( codegenCmd (Runtime Export) 98 | <$> strArgument (metavar "FILE" <> help "Wit file") 99 | <*> strArgument (value "wasmedge" <> help "Name of export") 100 | ) 101 | (progDesc "Generate export code for runtime (WasmEdge)") 102 | ) 103 | ) 104 | ) 105 | (progDesc "Generate code for runtime (WasmEdge)") 106 | ) 107 | ) 108 | 109 | checkCmd :: Maybe FilePath -> IO () 110 | checkCmd (Just file) = do 111 | dirExists <- doesDirectoryExist file 112 | if dirExists 113 | then checkDir file 114 | else do 115 | fileExists <- doesFileExist file 116 | if fileExists 117 | then checkFileWithDoneHint (takeDirectory file) (takeFileName file) 118 | else putStrLn "no file or directory" 119 | checkCmd Nothing = do 120 | dir <- getCurrentDirectory 121 | checkDir dir 122 | 123 | checkDir :: FilePath -> IO () 124 | checkDir dir = do 125 | witFileList <- filter (".wit" `isSuffixOf`) <$> listDirectory dir 126 | forM_ witFileList $ 127 | \f -> checkFileWithDoneHint dir f 128 | 129 | checkFileWithDoneHint :: FilePath -> FilePath -> IO () 130 | checkFileWithDoneHint dir file = do 131 | result <- runExceptT (checkFile dir file) 132 | case result of 133 | Left e -> printCheckError e 134 | Right _ -> putDoc $ pretty file <+> annotate (color Green) (pretty "OK") <+> line 135 | 136 | printCheckError :: CheckError -> IO () 137 | printCheckError e = do 138 | putDoc $ annotate (color Red) $ pretty e 139 | return () 140 | 141 | codegenPluginCmd :: FilePath -> IO () 142 | codegenPluginCmd file = do 143 | let pluginName = takeBaseName file 144 | codegenCmd (Plugin pluginName) file pluginName 145 | 146 | codegenCmd :: Mode -> FilePath -> String -> IO () 147 | codegenCmd mode file importName = do 148 | (targetMod, checked) <- runExit $ checkFile (takeDirectory file) (takeFileName file) 149 | let doc = runReader (prettyFile Config {language = Rust, codegenMode = mode} importName targetMod) checked 150 | putDoc doc 151 | 152 | runExit :: ExceptT CheckError IO a -> IO a 153 | runExit act = do 154 | result <- runExceptT act 155 | case result of 156 | Left e -> putDoc (annotate (color Red) $ pretty e) *> exitFailure 157 | Right a -> pure a 158 | -------------------------------------------------------------------------------- /bench/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["runtime", "instance_import", "instance_export"] 3 | -------------------------------------------------------------------------------- /bench/README.md: -------------------------------------------------------------------------------- 1 | # benchmark 2 | 3 | First, you must build every package in this repository 4 | 5 | ```shell 6 | cargo build --release 7 | ``` 8 | 9 | Then you are able to do benchmarking 10 | 11 | ```shell 12 | cargo bench -p runtime 13 | ``` 14 | -------------------------------------------------------------------------------- /bench/instance_export.wit: -------------------------------------------------------------------------------- 1 | record c2 { 2 | name: string, 3 | age: u32 4 | } 5 | 6 | base2: func(c: c2) -> c2 7 | -------------------------------------------------------------------------------- /bench/instance_export/Cargo.toml: -------------------------------------------------------------------------------- 1 | cargo-features = [ "per-package-target" ] 2 | 3 | [package] 4 | name = "instance_export" 5 | version = "0.1.0" 6 | edition = "2021" 7 | default-target = "wasm32-wasi" 8 | 9 | [lib] 10 | crate-type = [ "cdylib" ] 11 | 12 | [dependencies] 13 | invoke-witc = { path = "../../bindings/rust/invoke-witc" } 14 | witc-abi = { path = "../../bindings/rust/witc-abi" } 15 | serde = { version = "1.0", features = ["derive"] } 16 | serde_json = "*" 17 | -------------------------------------------------------------------------------- /bench/instance_export/src/lib.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | invoke_witc::wit_instance!(export("instance_export.wit")); 3 | 4 | fn base2(c: c2) -> c2 { 5 | c2 { 6 | name: c.name, 7 | age: c.age + 1, 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /bench/instance_import/Cargo.toml: -------------------------------------------------------------------------------- 1 | cargo-features = [ "per-package-target" ] 2 | 3 | [package] 4 | name = "instance_import" 5 | version = "0.1.0" 6 | edition = "2021" 7 | default-target = "wasm32-wasi" 8 | 9 | [lib] 10 | crate-type = [ "cdylib" ] 11 | 12 | [dependencies] 13 | invoke-witc = { path = "../../bindings/rust/invoke-witc" } 14 | witc-abi = { path = "../../bindings/rust/witc-abi" } 15 | serde = { version = "1.0", features = ["derive"] } 16 | serde_json = "*" 17 | -------------------------------------------------------------------------------- /bench/instance_import/src/lib.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | invoke_witc::wit_instance!(import("runtime_export.wit")); 3 | invoke_witc::wit_instance!(import(instance_export = "instance_export.wit")); 4 | 5 | #[link(wasm_import_module = "host")] 6 | extern "C" { 7 | fn host_fib(n: u64) -> u64; 8 | } 9 | 10 | #[no_mangle] 11 | pub unsafe extern "C" fn call_base() -> u32 { 12 | let arg = c { 13 | name: "test".to_string(), 14 | age: 1, 15 | }; 16 | let r = base(arg); 17 | return r.age; 18 | } 19 | 20 | #[no_mangle] 21 | pub unsafe extern "C" fn call_base2() -> u32 { 22 | let arg = c2 { 23 | name: "test".to_string(), 24 | age: 1, 25 | }; 26 | let r = base2(arg); 27 | return r.age; 28 | } 29 | 30 | #[no_mangle] 31 | pub unsafe extern "C" fn call_fib() -> u64 { 32 | return fib(10); 33 | } 34 | 35 | #[no_mangle] 36 | pub unsafe extern "C" fn call_host_fib() -> u64 { 37 | return host_fib(10); 38 | } 39 | -------------------------------------------------------------------------------- /bench/runtime/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "runtime" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | [lib] 8 | 9 | [dependencies] 10 | wasmedge-sdk = "0.8.1" 11 | invoke-witc = { path = "../../bindings/rust/invoke-witc" } 12 | witc-abi = { path = "../../bindings/rust/witc-abi" } 13 | serde = { version = "1.0", features = ["derive"] } 14 | serde_json = "*" 15 | -------------------------------------------------------------------------------- /bench/runtime/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | extern crate test; 3 | use serde::{Deserialize, Serialize}; 4 | use wasmedge_sdk::{error::HostFuncError, host_function, Caller, WasmValue}; 5 | invoke_witc::wit_runtime!(export("runtime_export.wit")); 6 | invoke_witc::wit_runtime!(import(instance_export = "instance_export.wit")); 7 | 8 | fn base(c1: c) -> c { 9 | c { 10 | name: c1.name, 11 | age: c1.age + 1, 12 | } 13 | } 14 | 15 | fn fib(n: u64) -> u64 { 16 | if n <= 1 { 17 | return n; 18 | } 19 | return fib(n - 1) + fib(n - 2); 20 | } 21 | 22 | #[host_function] 23 | fn host_fib(_: Caller, values: Vec) -> Result, HostFuncError> { 24 | let v = values[0].to_i64(); 25 | let r = fib(v as u64); 26 | Ok(vec![WasmValue::from_i64(r as i64)]) 27 | } 28 | 29 | #[cfg(test)] 30 | mod tests { 31 | use super::*; 32 | use test::Bencher; 33 | use wasmedge_sdk::{ 34 | config::{CommonConfigOptions, ConfigBuilder, HostRegistrationConfigOptions}, 35 | ImportObjectBuilder, Vm, VmBuilder, 36 | }; 37 | 38 | fn test_vm() -> Vm { 39 | let config = ConfigBuilder::new(CommonConfigOptions::default()) 40 | .with_host_registration_config(HostRegistrationConfigOptions::default().wasi(true)) 41 | .build() 42 | .unwrap(); 43 | 44 | VmBuilder::new() 45 | .with_config(config) 46 | .build() 47 | .unwrap() 48 | .register_import_module(witc_abi::runtime::component_model_wit_object().unwrap()) 49 | .unwrap() 50 | .register_module_from_file( 51 | "instance_export", 52 | "../target/wasm32-wasi/release/instance_export.wasm", 53 | ) 54 | .unwrap() 55 | .register_import_module(wasmedge::wit_import_object().unwrap()) 56 | .unwrap() 57 | .register_import_module( 58 | ImportObjectBuilder::new() 59 | .with_func::("host_fib", host_fib) 60 | .unwrap() 61 | .build("host") 62 | .unwrap(), 63 | ) 64 | .unwrap() 65 | .register_module_from_file( 66 | "instance_import", 67 | "../target/wasm32-wasi/release/instance_import.wasm", 68 | ) 69 | .unwrap() 70 | } 71 | 72 | #[bench] 73 | fn base_native(b: &mut Bencher) { 74 | b.iter(|| { 75 | let n = test::black_box(1000); 76 | 77 | base(c { 78 | name: "test".to_string(), 79 | age: n, 80 | }); 81 | }); 82 | } 83 | 84 | #[bench] 85 | fn base_instance_invokes_runtime(b: &mut Bencher) { 86 | let vm = test_vm(); 87 | 88 | b.iter(|| { 89 | vm.run_func(Some("instance_import"), "call_base", None) 90 | .unwrap(); 91 | }); 92 | } 93 | 94 | #[bench] 95 | fn base_instance_invokes_instance(b: &mut Bencher) { 96 | let vm = test_vm(); 97 | 98 | b.iter(|| { 99 | vm.run_func(Some("instance_import"), "call_base2", None) 100 | .unwrap(); 101 | }); 102 | } 103 | 104 | #[bench] 105 | fn base_runtime_invokes_instance(b: &mut Bencher) { 106 | let vm = test_vm(); 107 | 108 | b.iter(|| { 109 | base2( 110 | &vm, 111 | c2 { 112 | name: "test".to_string(), 113 | age: 1, 114 | }, 115 | ); 116 | }); 117 | } 118 | 119 | #[bench] 120 | fn fib_native(b: &mut Bencher) { 121 | b.iter(|| { 122 | let n = test::black_box(10); 123 | 124 | fib(n); 125 | }); 126 | } 127 | 128 | #[bench] 129 | fn fib_instance_invokes_runtime(b: &mut Bencher) { 130 | let vm = test_vm(); 131 | 132 | b.iter(|| { 133 | vm.run_func(Some("instance_import"), "call_fib", None) 134 | .unwrap(); 135 | }); 136 | } 137 | 138 | #[bench] 139 | fn fib_instance_invokes_host_function(b: &mut Bencher) { 140 | let vm = test_vm(); 141 | 142 | b.iter(|| { 143 | vm.run_func(Some("instance_import"), "call_host_fib", None) 144 | .unwrap(); 145 | }); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /bench/runtime_export.wit: -------------------------------------------------------------------------------- 1 | record c { 2 | name: string, 3 | age: u32 4 | } 5 | 6 | base: func(c: c) -> c 7 | fib: func(n: u64) -> u64 8 | -------------------------------------------------------------------------------- /bindings/rust/.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | -------------------------------------------------------------------------------- /bindings/rust/invoke-witc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "invoke-witc" 3 | version = "0.4.0" 4 | edition = "2021" 5 | description = "rust macros invokes witc" 6 | license = "Apache-2.0" 7 | 8 | [lib] 9 | proc-macro = true 10 | 11 | [dependencies] 12 | syn = "1" 13 | -------------------------------------------------------------------------------- /bindings/rust/invoke-witc/README.md: -------------------------------------------------------------------------------- 1 | # invoke-witc 2 | 3 | Add the crate to the dependencies. 4 | 5 | ``` 6 | invoke-witc = "0.2" 7 | ``` 8 | 9 | The usage of this crate is using the following macros to generate polyfill programs. e.g. 10 | 11 | ```rust 12 | invoke_witc::wit_instance!(import("./xxx.wit")); 13 | invoke_witc::wit_runtime!(import("./xxx.wit")); 14 | invoke_witc::wit_runtime!(export("./xxx.wit")); 15 | ``` 16 | 17 | Let's assume there has a file `xxx.wit` contains 18 | 19 | ```wit 20 | num : func(a: u32) -> u32 21 | ``` 22 | 23 | and discuss some special cases 24 | 25 | ## import: instance vs runtime 26 | 27 | In instance, if we write 28 | 29 | ```rust 30 | invoke_witc::wit_instance!(import("./xxx.wit")); 31 | ``` 32 | 33 | then we can just use `num(10)` to get a `u32`. But in runtime, due to the limitation, we will have to write 34 | 35 | ```rust 36 | let u = num(&vm, 10); 37 | ``` 38 | 39 | where `vm : Vm`. 40 | 41 | ## export in runtime: with & without name 42 | 43 | Let's say we are exporting component `xxx` in runtime, we will need to provide definition of `num`, like the following. 44 | 45 | ```rust 46 | invoke_witc::wit_runtime!(export("./xxx.wit")); 47 | 48 | // An example implementation 49 | fn num(a: u32) -> u32 { a + 1 } 50 | ``` 51 | 52 | Then we have to register import object to `vm : Vm`, therefore, we will write 53 | 54 | ```rust 55 | vm.register_import_module(xxx::wit_import_object()?)? 56 | ``` 57 | 58 | to ensure later wasm instances can use this implementation. A notable thing is that you can define export name: 59 | 60 | ``` 61 | invoke_witc::wit_runtime!(export(a = "./xxx.wit")); 62 | invoke_witc::wit_runtime!(export(b = "./yyy.wit")); 63 | ``` 64 | 65 | with export name, the `wit_import_object` will be wrapped under different module, and hence, we will have: 66 | 67 | ```rust 68 | vm.register_import_module(a::wit_import_object()?)? 69 | .register_import_module(b::wit_import_object()?)? 70 | ``` 71 | -------------------------------------------------------------------------------- /bindings/rust/invoke-witc/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use std::process::{Command, Output}; 3 | use syn::{parse_macro_input, Lit, Meta, MetaList, NestedMeta}; 4 | 5 | fn lit_as_str(lit: &Lit) -> String { 6 | match lit { 7 | Lit::Str(s) => s.value(), 8 | _ => unreachable!(), 9 | } 10 | } 11 | fn name_value_meta(meta: &Meta) -> (String, String) { 12 | match meta { 13 | Meta::NameValue(nv) => ( 14 | nv.path.get_ident().unwrap().to_string(), 15 | lit_as_str(&nv.lit), 16 | ), 17 | _ => unreachable!(), 18 | } 19 | } 20 | 21 | fn check_version() { 22 | let ver_output = Command::new("witc").args(["--version"]).output().unwrap(); 23 | let ver = String::from_utf8(ver_output.stdout).unwrap(); 24 | if ver != "0.4\n" { 25 | panic!("witc version mismatch: expected 0.4, got {}", ver); 26 | } 27 | } 28 | 29 | #[proc_macro] 30 | pub fn wit_instance(input: TokenStream) -> TokenStream { 31 | check_version(); 32 | let MetaList { path, nested, .. } = parse_macro_input!(input); 33 | let mode = path.get_ident().unwrap().to_string(); 34 | let r: Output = match &nested[0] { 35 | NestedMeta::Lit(lit) => { 36 | let wit_file = lit_as_str(lit); 37 | Command::new("witc") 38 | .args(["instance", &mode, &wit_file]) 39 | .output() 40 | .unwrap() 41 | } 42 | NestedMeta::Meta(meta) => { 43 | let (in_out_name, wit_file) = name_value_meta(meta); 44 | Command::new("witc") 45 | .args(["instance", &mode, &wit_file, &in_out_name]) 46 | .output() 47 | .unwrap() 48 | } 49 | }; 50 | String::from_utf8_lossy(&r.stdout).parse().unwrap() 51 | } 52 | 53 | #[proc_macro] 54 | pub fn wit_runtime(input: TokenStream) -> TokenStream { 55 | check_version(); 56 | let MetaList { path, nested, .. } = parse_macro_input!(input); 57 | let mode = path.get_ident().unwrap().to_string(); 58 | let r: Output = match &nested[0] { 59 | NestedMeta::Lit(lit) => { 60 | let wit_file = lit_as_str(lit); 61 | Command::new("witc") 62 | .args(["runtime", &mode, &wit_file]) 63 | .output() 64 | .unwrap() 65 | } 66 | NestedMeta::Meta(meta) => { 67 | let (in_out_name, wit_file) = name_value_meta(meta); 68 | Command::new("witc") 69 | .args(["runtime", &mode, &wit_file, &in_out_name]) 70 | .output() 71 | .unwrap() 72 | } 73 | }; 74 | String::from_utf8_lossy(&r.stdout).parse().unwrap() 75 | } 76 | -------------------------------------------------------------------------------- /bindings/rust/witc-abi/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "witc-abi" 3 | version = "0.4.0" 4 | edition = "2021" 5 | description = "rust supplment for witc" 6 | license = "Apache-2.0" 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [target.'cfg(not(target_arch = "wasm32"))'.dependencies] 11 | wasmedge-sdk = "0.8.1" 12 | once_cell = "1.17.1" 13 | -------------------------------------------------------------------------------- /bindings/rust/witc-abi/README.md: -------------------------------------------------------------------------------- 1 | # witc_abi 2 | 3 | This repository is working with `invoke_witc`, you will not need to use this crate directly, just add it into dependencies. 4 | 5 | ```toml 6 | witc_abi = "0.2" 7 | ``` 8 | 9 | ## Runtime 10 | 11 | You will need to register common helper for your `vm : Vm` if you want to use witc 12 | 13 | ```rust 14 | vm.register_import_module(witc_abi::runtime::component_model_wit_object()?)? 15 | ``` 16 | -------------------------------------------------------------------------------- /bindings/rust/witc-abi/src/instance.rs: -------------------------------------------------------------------------------- 1 | #[repr(C)] 2 | pub struct ReadBuf { 3 | offset: usize, 4 | len: usize, 5 | } 6 | 7 | impl ToString for ReadBuf { 8 | fn to_string(self: &Self) -> String { 9 | unsafe { String::from_raw_parts(self.offset as *mut u8, self.len, self.len) } 10 | } 11 | } 12 | 13 | #[link(wasm_import_module = "wasmedge.component.model")] 14 | extern "C" { 15 | pub fn require_queue() -> i32; 16 | pub fn write(id: i32, offset: usize, len: usize); 17 | pub fn read(id: i32) -> ReadBuf; 18 | } 19 | -------------------------------------------------------------------------------- /bindings/rust/witc-abi/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(target_arch = "wasm32")] 2 | pub mod instance; 3 | 4 | #[cfg(not(target_arch = "wasm32"))] 5 | pub mod runtime; 6 | -------------------------------------------------------------------------------- /bindings/rust/witc-abi/src/runtime.rs: -------------------------------------------------------------------------------- 1 | use once_cell::sync::Lazy; 2 | use std::{ 3 | collections::{HashMap, VecDeque}, 4 | sync::atomic::{AtomicI32, Ordering}, 5 | }; 6 | use wasmedge_sdk::{ 7 | error::HostFuncError, host_function, Caller, ImportObject, ImportObjectBuilder, WasmEdgeResult, 8 | WasmValue, 9 | }; 10 | 11 | pub struct GlobalState { 12 | counter: AtomicI32, 13 | queue_pool: HashMap>, 14 | } 15 | 16 | impl GlobalState { 17 | fn new() -> Self { 18 | Self { 19 | counter: AtomicI32::new(0), 20 | queue_pool: HashMap::new(), 21 | } 22 | } 23 | 24 | // This allocation algorithm relys on HashMap will limit the bucket size to a fixed number, 25 | // and the calls will not grow too fast (run out of i32 to use). 26 | // It still might have problem, if two limits above are broke. 27 | pub fn new_queue(&mut self) -> i32 { 28 | let id = self.counter.fetch_add(1, Ordering::SeqCst); 29 | self.queue_pool.insert(id, VecDeque::new()); 30 | id 31 | } 32 | 33 | pub fn put_buffer(&mut self, queue_id: i32, buf: String) { 34 | self.queue_pool.get_mut(&queue_id).unwrap().push_back(buf); 35 | } 36 | 37 | pub fn read_buffer(&mut self, queue_id: i32) -> String { 38 | self.queue_pool 39 | .get_mut(&queue_id) 40 | .unwrap() 41 | .pop_front() 42 | .unwrap() 43 | } 44 | } 45 | 46 | pub static mut STATE: Lazy = Lazy::new(|| GlobalState::new()); 47 | 48 | #[host_function] 49 | fn require_queue(_caller: Caller, _input: Vec) -> Result, HostFuncError> { 50 | unsafe { 51 | let id = STATE.new_queue(); 52 | Ok(vec![WasmValue::from_i32(id)]) 53 | } 54 | } 55 | 56 | #[host_function] 57 | fn put_buffer(caller: Caller, input: Vec) -> Result, HostFuncError> { 58 | let id = input[0].to_i32(); 59 | let offset = input[1].to_i32() as u32; 60 | let len = input[2].to_i32() as u32; 61 | 62 | let data_buffer = caller.memory(0).unwrap().read_string(offset, len).unwrap(); 63 | 64 | unsafe { 65 | STATE.put_buffer(id, data_buffer); 66 | } 67 | 68 | Ok(vec![]) 69 | } 70 | 71 | #[host_function] 72 | fn read_buffer(caller: Caller, input: Vec) -> Result, HostFuncError> { 73 | let read_buf_struct_ptr = input[0].to_i32() as u32; 74 | let queue_id = input[1].to_i32(); 75 | 76 | let data_buffer = unsafe { &STATE.read_buffer(queue_id) }; 77 | // capacity will use underlying vector's capacity 78 | // potential problem is it might be bigger than exact (data) needs 79 | let data_size = data_buffer.capacity() as u32; 80 | // The idea here is putting data from backward of memory, and hopes it would not overlap with program usage 81 | let mut mem = caller.memory(0).unwrap(); 82 | let offset = (mem.size() as u32) - data_size; 83 | mem.write(data_buffer, offset).unwrap(); 84 | 85 | let instance_ptr = offset as u32; 86 | let mut struct_content = instance_ptr.to_le_bytes().to_vec(); 87 | // This assuming that the struct `ReadBuf` in instance will have linear layout 88 | // 89 | // #[repr(C)] 90 | // pub struct ReadBuf { 91 | // pub offset: usize, 92 | // pub len: usize, 93 | // } 94 | struct_content.extend((data_buffer.len() as u32).to_le_bytes()); 95 | mem.write(struct_content, read_buf_struct_ptr).unwrap(); 96 | 97 | Ok(vec![]) 98 | } 99 | 100 | pub fn component_model_wit_object() -> WasmEdgeResult { 101 | ImportObjectBuilder::new() 102 | .with_func::<(), i32>("require_queue", require_queue)? 103 | .with_func::<(i32, i32, i32), ()>("write", put_buffer)? 104 | .with_func::<(i32, i32), ()>("read", read_buffer)? 105 | .build("wasmedge.component.model") 106 | } 107 | -------------------------------------------------------------------------------- /example/WitTest/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["host", "lib"] 3 | -------------------------------------------------------------------------------- /example/WitTest/host/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "host" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | wasmedge-sdk = "0.8.1" 10 | anyhow = "*" 11 | serde = { version = "1.0", features = ["derive"] } 12 | serde_json = "*" 13 | 14 | invoke-witc = { path = "../../../bindings/rust/invoke-witc" } 15 | witc-abi = { path = "../../../bindings/rust/witc-abi" } 16 | -------------------------------------------------------------------------------- /example/WitTest/host/src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Error; 2 | use serde::{Deserialize, Serialize}; 3 | use wasmedge_sdk::{ 4 | config::{CommonConfigOptions, ConfigBuilder, HostRegistrationConfigOptions}, 5 | VmBuilder, 6 | }; 7 | invoke_witc::wit_runtime!(export("./test.wit")); 8 | 9 | fn set_name(p: person, name: String) -> person { 10 | println!("wasmedge: Person: {:?}", p); 11 | person { name, age: p.age } 12 | } 13 | 14 | fn exchange_enum(c: color) -> u32 { 15 | println!("wasmedge: color: {:?}", c); 16 | 0 17 | } 18 | 19 | fn maybe_test(v: Option) -> u32 { 20 | println!("wasmedge: Option: {:?}", v); 21 | 0 22 | } 23 | 24 | fn send_result(r: Result) -> u32 { 25 | println!("wasmedge: Result: {:?}", r); 26 | 0 27 | } 28 | 29 | fn send_result2(r: Result) -> u32 { 30 | println!("wasmedge: Result: {:?}", r); 31 | 0 32 | } 33 | 34 | fn exchange_list(v: Vec) -> u32 { 35 | println!("wasmedge: Vec: {:?}", v); 36 | 0 37 | } 38 | 39 | fn exchange_list_string(v: Vec) -> u32 { 40 | println!("wasmedge: Vec: {:?}", v); 41 | 0 42 | } 43 | 44 | fn pass_nat(n: nat) -> u32 { 45 | println!("{:?}", n); 46 | 0 47 | } 48 | 49 | fn main() -> Result<(), Error> { 50 | let config = ConfigBuilder::new(CommonConfigOptions::default()) 51 | .with_host_registration_config(HostRegistrationConfigOptions::default().wasi(true)) 52 | .build()?; 53 | 54 | let vm = VmBuilder::new() 55 | .with_config(config) 56 | .build()? 57 | .register_import_module(witc_abi::runtime::component_model_wit_object()?)? 58 | .register_import_module(wasmedge::wit_import_object()?)? 59 | .register_module_from_file("lib", "target/wasm32-wasi/release/lib.wasm")?; 60 | 61 | let result = vm.run_func(Some("lib"), "start", None)?; 62 | assert!(result[0].to_i32() == 0); 63 | 64 | Ok(()) 65 | } 66 | -------------------------------------------------------------------------------- /example/WitTest/lib/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 = "WitTest" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /example/WitTest/lib/Cargo.toml: -------------------------------------------------------------------------------- 1 | cargo-features = [ "per-package-target" ] 2 | 3 | [package] 4 | name = "lib" 5 | version = "0.1.0" 6 | edition = "2021" 7 | default-target = "wasm32-wasi" 8 | 9 | [lib] 10 | crate-type = [ "cdylib" ] 11 | 12 | [dependencies] 13 | serde = { version = "1.0", features = ["derive"] } 14 | serde_json = "*" 15 | 16 | invoke-witc = { path = "../../../bindings/rust/invoke-witc" } 17 | witc-abi = { path = "../../../bindings/rust/witc-abi" } 18 | -------------------------------------------------------------------------------- /example/WitTest/lib/src/lib.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | invoke_witc::wit_instance!(import(wasmedge = "./test.wit")); 3 | 4 | #[no_mangle] 5 | pub unsafe extern "C" fn start() -> u32 { 6 | // string & struct (wit record) 7 | let p1 = person { 8 | name: "Carlo".into(), 9 | age: 30, 10 | }; 11 | let p2 = set_name(p1, "Phillips".into()); 12 | let _p3 = set_name(p2, "August".into()); 13 | // enum 0-product (wit enum) 14 | let _i = exchange_enum(color::green); 15 | // Option (wit option) 16 | let _r = maybe_test(Some(5)); 17 | let _r = maybe_test(None); 18 | // Result (wit expected) 19 | let _r = send_result(Ok("this is fine".into())); 20 | let _r = send_result(Err("test111".into())); 21 | let _r = send_result2(Ok(1)); 22 | let _r = send_result2(Err(1)); 23 | let _r = send_result2(Err(2)); 24 | let _r = send_result2(Err(3)); 25 | // Vec (wit list) 26 | let mut v = Vec::with_capacity(3); 27 | v.push(1); 28 | v.push(2); 29 | let _l = exchange_list(v); 30 | let _l = exchange_list_string(vec!["test".into(), "abc".into()]); 31 | // enum (wit variant) 32 | let _ = pass_nat(nat::zero); 33 | let _ = pass_nat(nat::suc(Box::new(nat::suc(Box::new(nat::suc(Box::new( 34 | nat::zero, 35 | ))))))); 36 | 37 | return 0; 38 | } 39 | -------------------------------------------------------------------------------- /example/WitTest/recursive-types.wit: -------------------------------------------------------------------------------- 1 | variant nat { 2 | zero, 3 | suc(nat) 4 | } 5 | -------------------------------------------------------------------------------- /example/WitTest/test.wit: -------------------------------------------------------------------------------- 1 | use { person, color } from types 2 | use * from recursive-types 3 | 4 | set-name: func(p : person, name : string) -> person 5 | 6 | exchange-enum: func(c : color) -> u32 7 | 8 | maybe-test: func(r : option) -> u32 9 | 10 | send-result: func(r : expected) -> u32 11 | send-result2: func(r : expected) -> u32 12 | 13 | exchange-list: func(l : list) -> u32 14 | exchange-list-string: func(l : list) -> u32 15 | 16 | pass-nat: func(n : nat) -> u32; 17 | -------------------------------------------------------------------------------- /example/WitTest/types.wit: -------------------------------------------------------------------------------- 1 | record person { 2 | name : string, 3 | age : u32 4 | } 5 | 6 | enum color { red, green, blue } 7 | -------------------------------------------------------------------------------- /example/keyvalue-demo/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["runtime-store", "instance-client"] 3 | -------------------------------------------------------------------------------- /example/keyvalue-demo/README.md: -------------------------------------------------------------------------------- 1 | # keyvalue 2 | 3 | This demo shows how to implement resource. With a `keyvalue.wit` as the following 4 | 5 | ```wit 6 | /// a keyvalue interface 7 | resource keyvalue { 8 | /// open a key-value store 9 | static open: func(name: string) -> expected 10 | 11 | /// get the payload for a given key 12 | get: func(key: string) -> expected, keyvalue-error> 13 | 14 | /// set the payload for a given key 15 | set: func(key: string, value: list) -> expected 16 | 17 | /// list the keys in the store 18 | keys: func() -> expected, keyvalue-error> 19 | 20 | /// delete the payload for a given key 21 | delete: func(key:string) -> expected 22 | } 23 | 24 | /// common keyvalue errors 25 | variant keyvalue-error { 26 | key-not-found(string), 27 | invalid-key(string), 28 | invalid-value(string), 29 | connection-error(string), 30 | authentication-error(string), 31 | timeout-error(string), 32 | io-error(string), 33 | unexpected-error(string) 34 | } 35 | ``` 36 | 37 | The type like `keyvalue` call **resource**, it will only live in the one side, caller will not have a real access to internal of it. Those function in resource can be separated by is static or not. 38 | 39 | 1. static one is related but no signature changes 40 | 2. non-static will get `handle : keyvalue` binding as first parameter 41 | 42 | Let's see some mangling 43 | 44 | 1. `keyvalue_open : func(name: string) -> expected` 45 | 2. `set: func(handle: keyvalue, key: string, value: list) -> expected` 46 | 47 | Now, you have all idea about the transformation 48 | 49 | ### instance (callsite) 50 | 51 | ```rust 52 | #![feature(wasm_abi)] 53 | use serde::{Deserialize, Serialize}; 54 | invoke_witc::wit_instance!(import("./keyvalue.wit")); 55 | 56 | struct Store { 57 | handle: keyvalue, 58 | } 59 | 60 | impl Store { 61 | fn open(name: String) -> Self { 62 | Self { 63 | handle: open_keyvalue(name).unwrap(), 64 | } 65 | } 66 | 67 | fn set(&self, key: String, value: Vec) { 68 | keyvalue_set(self.handle, key, value).unwrap(); 69 | } 70 | 71 | fn get(&self, key: String) -> Vec { 72 | keyvalue_get(self.handle, key).unwrap() 73 | } 74 | 75 | fn keys(&self) -> Vec { 76 | keyvalue_keys(self.handle).unwrap() 77 | } 78 | 79 | fn delete(&self, key: String) { 80 | keyvalue_delete(self.handle, key).unwrap() 81 | } 82 | } 83 | ``` 84 | 85 | This is resource concept's correspondning in rust. 86 | 87 | ### runtime (provides implementations) 88 | 89 | ```rust 90 | use witc_abi::runtime::*; 91 | invoke_witc::wit_runtime!(export("./keyvalue.wit")); 92 | 93 | static mut STORES: Vec = Vec::new(); 94 | 95 | struct Store { 96 | name: String, 97 | map: std::collections::HashMap>, 98 | } 99 | impl Store { 100 | fn new(name: String) -> Self { 101 | Self { 102 | name, 103 | map: std::collections::HashMap::new(), 104 | } 105 | } 106 | } 107 | 108 | fn open_keyvalue(name: String) -> Result { 109 | println!("new store `{}`", name); 110 | unsafe { 111 | STORES.push(Store::new(name)); 112 | Ok((STORES.len() - 1) as u32) 113 | } 114 | } 115 | fn keyvalue_set(handle: keyvalue, key: String, value: Vec) -> Result<(), keyvalue_error> { 116 | let store = unsafe { &mut STORES[handle as usize] }; 117 | println!("insert `{}` to store `{}`", key, store.name); 118 | store.map.insert(key, value); 119 | Ok(()) 120 | } 121 | fn keyvalue_get(handle: keyvalue, key: String) -> Result, keyvalue_error> { 122 | let store = unsafe { &mut STORES[handle as usize] }; 123 | println!("get `{}` from store `{}`", key, store.name); 124 | store 125 | .map 126 | .get(key.as_str()) 127 | .map(|v| v.to_vec()) 128 | .ok_or(keyvalue_error::key_not_found(key)) 129 | } 130 | 131 | fn keyvalue_keys(handle: keyvalue) -> Result, keyvalue_error> { 132 | let store = unsafe { &mut STORES[handle as usize] }; 133 | let keys = store.map.clone().into_keys().collect(); 134 | println!("store `{}` keys: {:?}", store.name, keys); 135 | Ok(keys) 136 | } 137 | 138 | fn keyvalue_delete(handle: keyvalue, key: String) -> Result<(), keyvalue_error> { 139 | let store = unsafe { &mut STORES[handle as usize] }; 140 | store.map.remove(&key); 141 | println!("remove `{}` from store `{}`", key, store.name); 142 | Ok(()) 143 | } 144 | ``` 145 | 146 | -------------------------------------------------------------------------------- /example/keyvalue-demo/instance-client/Cargo.toml: -------------------------------------------------------------------------------- 1 | cargo-features = [ "per-package-target" ] 2 | 3 | [package] 4 | name = "instance-service" 5 | version = "0.1.0" 6 | edition = "2021" 7 | default-target = "wasm32-wasi" 8 | 9 | [lib] 10 | crate-type = [ "cdylib" ] 11 | 12 | [dependencies] 13 | serde = { version = "1.0", features = ["derive"] } 14 | serde_json = "*" 15 | 16 | invoke-witc = { path = "../../../bindings/rust/invoke-witc" } 17 | witc-abi = { path = "../../../bindings/rust/witc-abi" } 18 | -------------------------------------------------------------------------------- /example/keyvalue-demo/instance-client/src/lib.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | invoke_witc::wit_instance!(import("./keyvalue.wit")); 3 | 4 | struct Store { 5 | handle: keyvalue, 6 | } 7 | 8 | impl Store { 9 | fn open(name: String) -> Self { 10 | Self { 11 | handle: keyvalue_open(name).unwrap(), 12 | } 13 | } 14 | 15 | fn set(&self, key: String, value: Vec) { 16 | keyvalue_set(self.handle, key, value).unwrap(); 17 | } 18 | 19 | fn get(&self, key: String) -> Vec { 20 | keyvalue_get(self.handle, key).unwrap() 21 | } 22 | 23 | fn keys(&self) -> Vec { 24 | keyvalue_keys(self.handle).unwrap() 25 | } 26 | 27 | fn delete(&self, key: String) { 28 | keyvalue_delete(self.handle, key).unwrap() 29 | } 30 | } 31 | 32 | #[no_mangle] 33 | pub unsafe extern "C" fn start() -> u32 { 34 | let store = Store::open("store A".to_string()); 35 | 36 | store.set("key1".to_string(), vec![1, 2, 3]); 37 | let value = store.get("key1".to_string()); 38 | assert_eq!(value, vec![1, 2, 3]); 39 | 40 | store.set("key2".to_string(), vec![4, 5, 6]); 41 | store.set("key3".to_string(), vec![7, 8, 9]); 42 | 43 | store.delete("key2".to_string()); 44 | 45 | return store.keys().len() as u32; 46 | } 47 | -------------------------------------------------------------------------------- /example/keyvalue-demo/keyvalue.wit: -------------------------------------------------------------------------------- 1 | /// a keyvalue interface 2 | resource keyvalue { 3 | /// open a key-value store 4 | static open: func(name: string) -> expected 5 | 6 | /// get the payload for a given key 7 | get: func(key: string) -> expected, keyvalue-error> 8 | 9 | /// set the payload for a given key 10 | set: func(key: string, value: list) -> expected 11 | 12 | /// list the keys in the store 13 | keys: func() -> expected, keyvalue-error> 14 | 15 | /// delete the payload for a given key 16 | delete: func(key:string) -> expected 17 | } 18 | 19 | /// common keyvalue errors 20 | variant keyvalue-error { 21 | key-not-found(string), 22 | invalid-key(string), 23 | invalid-value(string), 24 | connection-error(string), 25 | authentication-error(string), 26 | timeout-error(string), 27 | io-error(string), 28 | unexpected-error(string) 29 | } 30 | -------------------------------------------------------------------------------- /example/keyvalue-demo/runtime-store/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "runtime-logging" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | wasmedge-sdk = "0.8.1" 10 | anyhow = "*" 11 | serde = { version = "1.0", features = ["derive"] } 12 | serde_json = "*" 13 | 14 | invoke-witc = { path = "../../../bindings/rust/invoke-witc" } 15 | witc-abi = { path = "../../../bindings/rust/witc-abi" } 16 | -------------------------------------------------------------------------------- /example/keyvalue-demo/runtime-store/src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Error; 2 | use serde::{Deserialize, Serialize}; 3 | use wasmedge_sdk::{ 4 | config::{CommonConfigOptions, ConfigBuilder, HostRegistrationConfigOptions}, 5 | VmBuilder, 6 | }; 7 | invoke_witc::wit_runtime!(export("./keyvalue.wit")); 8 | 9 | static mut STORES: Vec = Vec::new(); 10 | 11 | struct Store { 12 | name: String, 13 | map: std::collections::HashMap>, 14 | } 15 | impl Store { 16 | fn new(name: String) -> Self { 17 | Self { 18 | name, 19 | map: std::collections::HashMap::new(), 20 | } 21 | } 22 | } 23 | 24 | fn keyvalue_open(name: String) -> Result { 25 | println!("new store `{}`", name); 26 | unsafe { 27 | STORES.push(Store::new(name)); 28 | Ok((STORES.len() - 1) as u32) 29 | } 30 | } 31 | fn keyvalue_set(handle: keyvalue, key: String, value: Vec) -> Result<(), keyvalue_error> { 32 | let store = unsafe { &mut STORES[handle as usize] }; 33 | println!("insert `{}` to store `{}`", key, store.name); 34 | store.map.insert(key, value); 35 | Ok(()) 36 | } 37 | fn keyvalue_get(handle: keyvalue, key: String) -> Result, keyvalue_error> { 38 | let store = unsafe { &mut STORES[handle as usize] }; 39 | println!("get `{}` from store `{}`", key, store.name); 40 | store 41 | .map 42 | .get(key.as_str()) 43 | .map(|v| v.to_vec()) 44 | .ok_or(keyvalue_error::key_not_found(key)) 45 | } 46 | fn keyvalue_keys(handle: keyvalue) -> Result, keyvalue_error> { 47 | let store = unsafe { &mut STORES[handle as usize] }; 48 | let keys = store.map.clone().into_keys().collect(); 49 | println!("store `{}` keys: {:?}", store.name, keys); 50 | Ok(keys) 51 | } 52 | fn keyvalue_delete(handle: keyvalue, key: String) -> Result<(), keyvalue_error> { 53 | let store = unsafe { &mut STORES[handle as usize] }; 54 | store.map.remove(&key); 55 | println!("remove `{}` from store `{}`", key, store.name); 56 | Ok(()) 57 | } 58 | 59 | fn main() -> Result<(), Error> { 60 | let config = ConfigBuilder::new(CommonConfigOptions::default()) 61 | .with_host_registration_config(HostRegistrationConfigOptions::default().wasi(true)) 62 | .build()?; 63 | 64 | let vm = VmBuilder::new() 65 | .with_config(config) 66 | .build()? 67 | .register_import_module(witc_abi::runtime::component_model_wit_object()?)? 68 | .register_import_module(wasmedge::wit_import_object()?)? 69 | .register_module_from_file( 70 | "instance-service", 71 | "target/wasm32-wasi/release/instance_service.wasm", 72 | )?; 73 | 74 | let result = vm.run_func(Some("instance-service"), "start", None)?; 75 | assert!(result[0].to_i32() == 2); 76 | 77 | Ok(()) 78 | } 79 | -------------------------------------------------------------------------------- /example/logging-demo/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "runtime", 4 | "instance-logging", 5 | "instance-service" 6 | ] 7 | -------------------------------------------------------------------------------- /example/logging-demo/README.md: -------------------------------------------------------------------------------- 1 | # logging 2 | 3 | This demo shows across instance call, the following is our wit file: 4 | 5 | ```wit 6 | record pack { 7 | message : string, 8 | level: u32 9 | } 10 | 11 | log: func(p : pack) -> u32 12 | ``` 13 | 14 | ### instance (provides `log` implementation) 15 | 16 | ```rust 17 | use serde::{Deserialize, Serialize}; 18 | invoke_witc::wit_instance!(export("./logging.wit")); 19 | 20 | fn log(p: pack) -> u32 { 21 | let s = format!("{} {}", p.level, p.message); 22 | unsafe { 23 | runtime_println(s.as_ptr(), s.len()); 24 | } 25 | p.level 26 | } 27 | ``` 28 | 29 | ### instance (mock another service that use logging) 30 | 31 | ```rust 32 | use serde::{Deserialize, Serialize}; 33 | invoke_witc::wit_instance!(import(instance_logging = "logging.wit")); 34 | 35 | #[no_mangle] 36 | pub unsafe extern "C" fn start() -> u32 { 37 | let _ = log(pack { 38 | message: "cannot connect to 196.128.10.3".to_string(), 39 | level: 1, 40 | }); 41 | return 0; 42 | } 43 | ``` 44 | 45 | ### runtime (glue instances) 46 | 47 | ```rust 48 | let vm = Vm::new(Some(config))? 49 | .register_module_from_file( 50 | "instance_logging", 51 | "target/wasm32-wasi/release/instance_logging.wasm", 52 | )? 53 | .register_module_from_file( 54 | "instance-service", 55 | "target/wasm32-wasi/release/instance_service.wasm", 56 | )?; 57 | 58 | let result = vm.run_func(Some("instance-service"), "start", None)?; 59 | assert!(result[0].to_i32() == 0); 60 | ``` 61 | -------------------------------------------------------------------------------- /example/logging-demo/instance-logging/Cargo.toml: -------------------------------------------------------------------------------- 1 | cargo-features = [ "per-package-target" ] 2 | 3 | [package] 4 | name = "instance-logging" 5 | version = "0.1.0" 6 | edition = "2021" 7 | default-target = "wasm32-wasi" 8 | 9 | [lib] 10 | crate-type = [ "cdylib" ] 11 | 12 | [dependencies] 13 | serde = { version = "1.0", features = ["derive"] } 14 | serde_json = "*" 15 | 16 | invoke-witc = { path = "../../../bindings/rust/invoke-witc" } 17 | witc-abi = { path = "../../../bindings/rust/witc-abi" } 18 | -------------------------------------------------------------------------------- /example/logging-demo/instance-logging/src/lib.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | invoke_witc::wit_instance!(export("./logging.wit")); 3 | 4 | #[link(wasm_import_module = "runtime")] 5 | extern "C" { 6 | fn runtime_println(str_ptr: *const u8, str_len: usize) -> (); 7 | } 8 | 9 | fn log(p: pack) -> u32 { 10 | let s = format!("{} {}", p.level, p.message); 11 | unsafe { 12 | runtime_println(s.as_ptr(), s.len()); 13 | } 14 | p.level 15 | } 16 | -------------------------------------------------------------------------------- /example/logging-demo/instance-service/Cargo.toml: -------------------------------------------------------------------------------- 1 | cargo-features = [ "per-package-target" ] 2 | 3 | [package] 4 | name = "instance-service" 5 | version = "0.1.0" 6 | edition = "2021" 7 | default-target = "wasm32-wasi" 8 | 9 | [lib] 10 | crate-type = [ "cdylib" ] 11 | 12 | [dependencies] 13 | serde = { version = "1.0", features = ["derive"] } 14 | serde_json = "*" 15 | 16 | invoke-witc = { path = "../../../bindings/rust/invoke-witc" } 17 | witc-abi = { path = "../../../bindings/rust/witc-abi" } 18 | -------------------------------------------------------------------------------- /example/logging-demo/instance-service/src/lib.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | invoke_witc::wit_instance!(import(instance_logging = "logging.wit")); 3 | 4 | #[no_mangle] 5 | pub unsafe extern "C" fn start() -> u32 { 6 | let _ = log(pack { 7 | message: "cannot connect to 196.128.10.3".to_string(), 8 | level: 1, 9 | }); 10 | return 0; 11 | } 12 | -------------------------------------------------------------------------------- /example/logging-demo/logging.wit: -------------------------------------------------------------------------------- 1 | record pack { 2 | message : string, 3 | level: u32 4 | } 5 | 6 | log: func(p : pack) -> u32 7 | -------------------------------------------------------------------------------- /example/logging-demo/runtime/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "runtime" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | wasmedge-sdk = "0.8.1" 10 | anyhow = "*" 11 | serde = { version = "1.0", features = ["derive"] } 12 | serde_json = "*" 13 | 14 | invoke-witc = { path = "../../../bindings/rust/invoke-witc" } 15 | witc-abi = { path = "../../../bindings/rust/witc-abi" } 16 | -------------------------------------------------------------------------------- /example/logging-demo/runtime/src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Error; 2 | use wasmedge_sdk::{ 3 | config::{CommonConfigOptions, ConfigBuilder, HostRegistrationConfigOptions}, 4 | error::HostFuncError, 5 | host_function, Caller, ImportObjectBuilder, VmBuilder, WasmValue, 6 | }; 7 | 8 | fn load_string(caller: &Caller, addr: u32, size: u32) -> String { 9 | let mem = caller.memory(0).unwrap(); 10 | let data = mem.read(addr, size).expect("fail to get string"); 11 | String::from_utf8_lossy(&data).to_string() 12 | } 13 | #[host_function] 14 | fn runtime_println(caller: Caller, input: Vec) -> Result, HostFuncError> { 15 | let s = load_string(&caller, input[0].to_i32() as u32, input[1].to_i32() as u32); 16 | println!("{}", s); 17 | Ok(vec![]) 18 | } 19 | 20 | fn main() -> Result<(), Error> { 21 | let config = ConfigBuilder::new(CommonConfigOptions::default()) 22 | .with_host_registration_config(HostRegistrationConfigOptions::default().wasi(true)) 23 | .build()?; 24 | 25 | let vm = VmBuilder::new() 26 | .with_config(config) 27 | .build()? 28 | .register_import_module(witc_abi::runtime::component_model_wit_object()?)? 29 | .register_import_module( 30 | ImportObjectBuilder::new() 31 | .with_func::<(i32, i32), ()>("runtime_println", runtime_println)? 32 | .build("runtime")?, 33 | )? 34 | .register_module_from_file( 35 | "instance_logging", 36 | "target/wasm32-wasi/release/instance_logging.wasm", 37 | )? 38 | .register_module_from_file( 39 | "instance-service", 40 | "target/wasm32-wasi/release/instance_service.wasm", 41 | )?; 42 | 43 | let result = vm.run_func(Some("instance-service"), "start", None)?; 44 | assert!(result[0].to_i32() == 0); 45 | 46 | Ok(()) 47 | } 48 | -------------------------------------------------------------------------------- /example/traffic-lights/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["runtime-toggle", "instance-lights"] 3 | -------------------------------------------------------------------------------- /example/traffic-lights/README.md: -------------------------------------------------------------------------------- 1 | # runtime import 2 | 3 | The example shows how runtime invokes instance's function as local one. First, we have wit file: 4 | 5 | ```wit 6 | enum light { 7 | green, 8 | yellow, 9 | red 10 | } 11 | 12 | toggle : func(status : light) -> light 13 | ``` 14 | 15 | ### instance (wasm module) 16 | 17 | In the wasm module, we write down the following code 18 | 19 | ```rust 20 | fn toggle(status: light) -> light { 21 | use light::*; 22 | match status { 23 | green => yellow, 24 | yellow => red, 25 | red => green, 26 | } 27 | } 28 | ``` 29 | 30 | and write down below at the top of the file 31 | 32 | ```rust 33 | invoke_witc::wit_instance!(export("traffic-lights.wit")); 34 | ``` 35 | 36 | to export definitions. 37 | 38 | ### runtime (wasmedge) 39 | 40 | In runtime side, since `vm` can be created dynamically, thus, the generated wrapper has a parameter `&Vm`. Below is how to use it 41 | 42 | ```rust 43 | let start = light::green; 44 | let r = toggle(&vm, start); 45 | println!("{:?}", r) 46 | ``` 47 | 48 | Just like in wasm module, you have to write the below code to import another component correctly 49 | 50 | ```rust 51 | invoke_witc::wit_runtime!(import(instance = "traffic-lights.wit")); 52 | ``` 53 | -------------------------------------------------------------------------------- /example/traffic-lights/instance-lights/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 = "WitTest" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /example/traffic-lights/instance-lights/Cargo.toml: -------------------------------------------------------------------------------- 1 | cargo-features = [ "per-package-target" ] 2 | 3 | [package] 4 | name = "instance-lights" 5 | version = "0.1.0" 6 | edition = "2021" 7 | default-target = "wasm32-wasi" 8 | 9 | [lib] 10 | crate-type = [ "cdylib" ] 11 | 12 | [dependencies] 13 | serde = { version = "1.0", features = ["derive"] } 14 | serde_json = "*" 15 | 16 | invoke-witc = { path = "../../../bindings/rust/invoke-witc" } 17 | witc-abi = { path = "../../../bindings/rust/witc-abi" } 18 | -------------------------------------------------------------------------------- /example/traffic-lights/instance-lights/src/lib.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | invoke_witc::wit_instance!(export("traffic-lights.wit")); 3 | 4 | fn toggle(status: light) -> light { 5 | use light::*; 6 | match status { 7 | green => yellow, 8 | yellow => red, 9 | red => green, 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /example/traffic-lights/runtime-toggle/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "runtime-toggle" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | wasmedge-sdk = "0.8.1" 10 | anyhow = "*" 11 | serde = { version = "1.0", features = ["derive"] } 12 | serde_json = "*" 13 | 14 | invoke-witc = { path = "../../../bindings/rust/invoke-witc" } 15 | witc-abi = { path = "../../../bindings/rust/witc-abi" } 16 | -------------------------------------------------------------------------------- /example/traffic-lights/runtime-toggle/src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Error; 2 | use serde::{Deserialize, Serialize}; 3 | use wasmedge_sdk::{ 4 | config::{CommonConfigOptions, ConfigBuilder, HostRegistrationConfigOptions}, 5 | VmBuilder, 6 | }; 7 | invoke_witc::wit_runtime!(import(lights = "traffic-lights.wit")); 8 | 9 | fn main() -> Result<(), Error> { 10 | let config = ConfigBuilder::new(CommonConfigOptions::default()) 11 | .with_host_registration_config(HostRegistrationConfigOptions::default().wasi(true)) 12 | .build()?; 13 | 14 | let vm = VmBuilder::new() 15 | .with_config(config) 16 | .build()? 17 | .register_import_module(witc_abi::runtime::component_model_wit_object()?)? 18 | .register_module_from_file("lights", "target/wasm32-wasi/release/instance_lights.wasm")?; 19 | 20 | let start = light::green; 21 | let r = toggle(&vm, start); 22 | println!("{:?}", r); 23 | let r = toggle(&vm, r); 24 | println!("{:?}", r); 25 | let r = toggle(&vm, r); 26 | println!("{:?}", r); 27 | let r = toggle(&vm, r); 28 | println!("{:?}", r); 29 | 30 | Ok(()) 31 | } 32 | -------------------------------------------------------------------------------- /example/traffic-lights/traffic-lights.wit: -------------------------------------------------------------------------------- 1 | enum light { 2 | green, 3 | yellow, 4 | red 5 | } 6 | 7 | toggle : func(status : light) -> light 8 | -------------------------------------------------------------------------------- /example/wasmedge_opencvmini.wit: -------------------------------------------------------------------------------- 1 | imdecode: func(buf: list) -> u32 2 | imshow: func(window-name: string, mat-key: u32) 3 | waitkey: func(delay: u32) 4 | -------------------------------------------------------------------------------- /install.ps1: -------------------------------------------------------------------------------- 1 | $VERSION = "0.4" 2 | 3 | wget "https://github.com/second-state/witc/releases/download/v${VERSION}/witc-v${VERSION}-windows.exe" 4 | 5 | mv "witc-v${VERSION}-windows.exe" witc.exe 6 | chmod +x witc.exe 7 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | VERSION="0.4" 3 | 4 | case $(uname) in 5 | 'Linux') 6 | download_url="https://github.com/second-state/witc/releases/download/v${VERSION}/witc-v${VERSION}-ubuntu" 7 | filename="witc-v${VERSION}-ubuntu" 8 | ;; 9 | 'Darwin') 10 | download_url="https://github.com/second-state/witc/releases/download/v${VERSION}/witc-v${VERSION}-macos" 11 | filename="witc-v${VERSION}-macos" 12 | ;; 13 | *) 14 | echo "Unsupported platform" 15 | exit 1 16 | ;; 17 | esac 18 | 19 | wget ${download_url} 20 | mv ${filename} witc 21 | chmod +x witc 22 | mv witc /usr/local/bin/witc 23 | -------------------------------------------------------------------------------- /note/README.md: -------------------------------------------------------------------------------- 1 | # Note 2 | 3 | ## Current implementation: runtime management queue 4 | 5 | This implementation assuming a queue pool in runtime (provided by witc_abi), the existed three functions are 6 | 7 | 1. require a new queue 8 | 2. putting data into queue 9 | 3. getting data from queue 10 | 11 | In runtime, we have direct access to the global state object. In instance, we will use the provided host functions. Whatever you're, a function call will be the following steps: 12 | 13 | 1. requires a queue, get an ID 14 | 2. write encoded arguments to the queue by the ID 15 | 3. invokes converted interface function, provides ID to it 16 | 4. read all returns and decode them back from the queue by the ID 17 | 18 | Therefore, each callee (implementor) must do 19 | 20 | 1. read all parameters back from queue by ID and decode them 21 | 2. invokes real function with decoded data 22 | 3. write encoded returns back to queue by ID 23 | 24 | ## Survey 25 | 26 | Before we use current solution, we have tried. 27 | 28 | - [implementation](./drop/component-three-fn-impl.md): each component provides allocate/read/write 29 | - [C ABI](./drop/c_abi.md): `Option`, `Result`, `Vec`, and `String` don't have a stable layout across different rust versions, however. Therefore, we define our types with a stable layout and convert between them. 30 | - [wasm ABI](./drop/wasm_abi.md): rust memory layout. 31 | -------------------------------------------------------------------------------- /note/drop/c_abi.md: -------------------------------------------------------------------------------- 1 | # C ABI 2 | 3 | For more stable representation for data structures in rust, 4 | we could use `repr(C)` and `std:ffi:CString` to make the representation more consistent. Check PR [stable-api](https://github.com/second-state/witc/pull/22) for more details. 5 | 6 | ### Rust `CString` 7 | 8 | `CString` will become a 2-tuple `(i32, i32)` that for 9 | 10 | 1. address 11 | 2. length 12 | 13 | ### `string` 14 | 15 | The `string` type in wit will become `WitString` and could convert between Rust `String`. 16 | 17 | ```rust 18 | #[repr(C)] 19 | pub struct WitString { 20 | addr: *mut u8, 21 | cap: usize, 22 | len: usize, 23 | } 24 | ``` 25 | 26 | ### `option` 27 | 28 | The `option` type in wit will become `WitOption` and could convert between Rust `Option`. 29 | 30 | ```rust 31 | #[repr(C, u32)] 32 | pub enum WitOption { 33 | None, 34 | Some(T), 35 | } 36 | ``` 37 | 38 | ### `expected` 39 | 40 | The `expected` type in wit will become `WitResult` and could convert between Rust `Result`. 41 | 42 | ```rust 43 | #[repr(C, u32)] 44 | pub enum WitResult { 45 | Ok(T), 46 | Err(E), 47 | } 48 | ``` 49 | 50 | ### `list` 51 | 52 | The `list` type in wit will become `WitVec` and could convert between Rust `Vec`. 53 | 54 | ```rust 55 | #[repr(C)] 56 | pub struct WitVec { 57 | ptr: usize, 58 | cap: usize, 59 | len: usize, 60 | phantom: PhantomData, 61 | } 62 | ``` 63 | 64 | ### trait `Runtime` 65 | 66 | We create a trait `Runtime` which contains the size information and build logic for ABI objects. 67 | In addition to the above types, `witc` also generate implement code to other types like `record` and `enum`, which have stable layout. 68 | 69 | Check [witc-abi](../bindings/rust/witc-abi/) for more information. 70 | -------------------------------------------------------------------------------- /note/drop/component-three-fn-impl.md: -------------------------------------------------------------------------------- 1 | # implementation: each component provides allocate/read/write interface 2 | 3 | This implementation assuming each component provides three functions (below is conceptual haskell code) 4 | 5 | ```haskell 6 | allocate :: U32 -> IO U32 7 | allocate size = makeHandle size 8 | write :: U32 -> Byte -> IO () 9 | write handle byte = do 10 | bv <- getHandle handle 11 | push bv byte 12 | read :: U32 -> U32 -> IO Byte 13 | read handle offset = do 14 | bv <- getHandle handle 15 | pure bv 16 | ``` 17 | 18 | and several helpers to fill the gap for runtime and instance 19 | 20 | - `getHandle :: U32 -> IO [Byte]` is an assumed function, which pull put handle from a global mutable pool (such as `IORef` in haskell). 21 | - `putHandle :: [Byte] -> IO U32` inserted a bytes into the pool, and returns a new unique `U32` value as handle. 22 | - `push :: [Byte] -> Byte -> IO ()` modify the bytes directly. 23 | 24 | ### Caller 25 | 26 | Every caller encode it's arguments to bytes and invokes `allocate` and `write` to put argument to callee. For example, `let c = foo(a, b)` can have generated `foo :: A -> B -> IO C` 27 | 28 | ```haskell 29 | foo :: A -> B -> IO C 30 | foo a b = do 31 | bv_a <- toJson a -- encode object to bytes first 32 | han_a <- allocate (length bv_a) -- allocate enough size bytes in the other component, and get a handle 33 | for (write han_a) bv_a -- write all char to remote via handle 34 | 35 | bv_b <- toJson b -- again, this follows previous argument 36 | han_b <- allocate (length bv_b) 37 | for (write han_b) bv_b 38 | 39 | (han_c, size_c) <- extern_foo(han_a, han_b) -- finally, invokes the mangled function (or wrapper), get handle of return value 40 | bv_c <- map (read han_c) [0..size_c-1] -- make bytes that big enough, and read data back via handle of return value 41 | fromJson bv_c 42 | ``` 43 | 44 | As you can see, the `foo` will be generated to wrap function that defined in another component automatically. 45 | 46 | ### Callee 47 | 48 | In callee side, who implements the function, will get a generated wrapper (wrapper has mangled name). 49 | 50 | ```haskell 51 | extern_foo :: U32 -> U32 -> IO (U32, U32) 52 | extern_foo han_a han_b = do 53 | bv_a <- getHandle han_a -- since caller already allocate & write bytes into handle pool, we can expect there has data 54 | bv_b <- getHandle han_b 55 | c <- apply foo (map fromJson [bv_a, bv_b]) -- forwarding decoded object to implementation, notice that, `foo` must be implemented in this component 56 | bv_c <- toJson c -- encode the return value as return bytes 57 | han_c <- putHandle bv_c -- put return bytes into pool, so that it can be represented by a handle 58 | pure (han_c, length bv_c) -- returns the (handle, bytes-length) pair 59 | ``` 60 | 61 | With these, one can understand and modify this repository without fear. 62 | 63 | ## Misc 64 | 65 | 1. using `#![feature(wasm_abi)]`, since rust has no stable binary format for whatever type 66 | 2. using `#[link(wasm_import_module = "mod")]` 67 | -------------------------------------------------------------------------------- /note/drop/wasm_abi.md: -------------------------------------------------------------------------------- 1 | # Wasm ABI 2 | 3 | This development note records the conversion from **Rust** type to wasm representation, it's not for most people not goes to implement `wit` format compiler, but should be valuable internally. 4 | 5 | ### Rust `String`, wit `string` 6 | 7 | `String` will become a 3-tuple `(i32, i32, i32)` that for 8 | 9 | 1. address 10 | 2. capability 11 | 3. length 12 | 13 | ### Rust `struct A`, wit `record A` 14 | 15 | A structure will be unfold till only wasm type left, for example 16 | 17 | ```rust 18 | struct person { 19 | name: String, 20 | age: u32 21 | } 22 | ``` 23 | 24 | turns to 25 | 26 | ```rust 27 | (i32, i32, i32, i32) 28 | ``` 29 | 30 | > If you don't quite get the idea, reconsider the encoding of `String`, and how tree layout of structure to be linear tuple. 31 | 32 | ### Rust `enum`, wit `enum` 33 | 34 | Tricky thing is **wit** also has `variant` type seems just like **Rust** `enum` for sum-of-product, but here is only about the trivial case: sum of tag. In this sense, **Rust** `enum` is the **wit** `enum`, and the encoding is an integer. 35 | 36 | Hence, if we said there has a definition 37 | 38 | ```wit 39 | enum color { 40 | red, 41 | green, 42 | blue 43 | } 44 | ``` 45 | 46 | After witc compile it to **Rust** then wasm abi takes over, there would have a mapping 47 | 48 | - `color::red` => `0` 49 | - `color::green` => `1` 50 | - `color::blue` => `2` 51 | 52 | ### Rust `Option`, wit `option` 53 | 54 | `option` is the first type function, it takes one type and returns one type, witc compiles it to **Rust** `Option` type. The definition in **Rust** should be: 55 | 56 | ```rust 57 | enum Option { 58 | None, 59 | Some(T), 60 | } 61 | ``` 62 | 63 | As expected, the tagging is `i32` when to wasm code, so the mapping as the following: 64 | 65 | - `None` => `0` 66 | - `Some` => `1` 67 | 68 | and then the second encoding is depending on `T`. Thus, `option` end up be `(i32, i32)` 69 | 70 | ### Rust `Result`, wit `expected` 71 | 72 | First wit `expected` turns to `Result` in **Rust**, then following above idea by finding definition of `Result` 73 | 74 | ```rust 75 | enum Result { 76 | Ok(T), 77 | Err(E), 78 | } 79 | ``` 80 | 81 | As guess that simple type like `expected`, the converted wasm type is `(i32, i32)`, and the first for tagging the second for value. 82 | 83 | However, when thing came to `Err` the encoding is much more confusing now, the result of `Err` first `i32` is not always `1` but can also be any non-zero value. What's going on here? 84 | 85 | Back to concrete example, the `expected` has conversion `(i32, i32, i32)`. First, the first `i32` still is `0` when it's `Ok`, but it would be an address when it's `Err`. In fact, for a construction `Err("abc")`, the tuple would be `(addr, 3, 3)`. Of course, the `String` encoding here. 86 | 87 | Thus, the current inspection shows that the `Result` type basically is just `0`, and is `1` when `E` is a single `i32` representable type. When `E` is not `i32` representable, it will in the heap of **Rust** instance. 88 | 89 | ### Rust `Vec`, wit `list` 90 | 91 | First, a `Vec` will be came a 3-tuple `(i32, i32, i32)`, for 92 | 93 | 1. address 94 | 2. capability 95 | 3. length 96 | 97 | Just like `String`. Then you can get a linear memory chunk for `Vec` to get all data, now you need to decode it out to target type. For example, `vec!["test".to_string(), "abc".to_string()]` will have such chunk as 98 | 99 | ```rust 100 | [48, 13, 16, 0, 4, 0, 0, 0, 4, 0, 0, 0, 64, 13, 16, 0, 3, 0, 0, 0, 3, 0, 0, 0] 101 | ``` 102 | 103 | It's easy to figure out what's that 104 | 105 | - `"test"` 106 | 1. addr: 48, 13, 16, 0 107 | 2. cap: 4, 0, 0, 0 108 | 3. len: 4, 0, 0, 0 109 | - `"abc"` 110 | 1. addr: 64, 13, 16, 0 111 | 2. cap: 3, 0, 0, 0 112 | 3. len: 3, 0, 0, 0 113 | 114 | That's all. 115 | -------------------------------------------------------------------------------- /package.yaml: -------------------------------------------------------------------------------- 1 | name: witc 2 | version: 0.4.0.0 3 | github: "second-state/witc" 4 | license: Apache-2.0 5 | author: "secondstate" 6 | maintainer: 7 | - "dannypsnl@secondstate.io" 8 | - "dm4@users.noreply.github.com" 9 | copyright: "2023 secondstate" 10 | 11 | extra-source-files: 12 | - README.md 13 | - CHANGELOG.md 14 | 15 | # Metadata used when publishing your package 16 | # synopsis: Short description of your package 17 | # category: Web 18 | 19 | # To avoid duplicated efforts in documentation and dealing with the 20 | # complications of embedding Haddock markup inside cabal files, it is 21 | # common to point users to the README.md file. 22 | description: Please see the README on GitHub at 23 | 24 | dependencies: 25 | - base >= 4.16 && < 5 26 | - mtl >= 2.2 27 | - containers >= 0.6 28 | - transformers >= 0.5 29 | - primitive >= 0.7 30 | - directory >= 1.3 31 | - megaparsec >= 9.2 32 | - text >= 1.2 33 | - prettyprinter >= 1.7 34 | - prettyprinter-ansi-terminal >= 1.1.3 35 | - template-haskell >= 2.18 36 | - optparse-applicative >= 0.16 37 | - filepath >= 1.4 38 | - algebraic-graphs >= 0.6 39 | 40 | ghc-options: 41 | - -Wall 42 | - -Wcompat 43 | - -Widentities 44 | - -Wincomplete-record-updates 45 | - -Wincomplete-uni-patterns 46 | - -Wmissing-export-lists 47 | - -Wmissing-home-modules 48 | - -Wpartial-fields 49 | - -Wredundant-constraints 50 | 51 | default-extensions: 52 | - FlexibleContexts 53 | - ImportQualifiedPost 54 | - DuplicateRecordFields 55 | - OverloadedRecordDot 56 | - LambdaCase 57 | - ViewPatterns 58 | 59 | library: 60 | source-dirs: src 61 | 62 | executables: 63 | witc: 64 | main: Main.hs 65 | source-dirs: app 66 | ghc-options: 67 | - -threaded 68 | - -rtsopts 69 | - -with-rtsopts=-N 70 | dependencies: 71 | - witc 72 | 73 | tests: 74 | witc-test: 75 | main: Spec.hs 76 | source-dirs: test 77 | ghc-options: 78 | - -threaded 79 | - -rtsopts 80 | - -with-rtsopts=-N 81 | dependencies: 82 | - witc 83 | - hspec 84 | - hspec-megaparsec 85 | -------------------------------------------------------------------------------- /src/Wit/Ast.hs: -------------------------------------------------------------------------------- 1 | module Wit.Ast 2 | ( WitFile (..), 3 | Use (..), 4 | Definition (..), 5 | Function (..), 6 | Attr (..), 7 | Type (..), 8 | dependencies, 9 | ) 10 | where 11 | 12 | import Text.Megaparsec (SourcePos) 13 | 14 | data WitFile = WitFile 15 | { use_list :: [Use], 16 | definition_list :: [Definition] 17 | } 18 | deriving (Show, Eq) 19 | 20 | data Use 21 | = SrcPosUse SourcePos Use 22 | | -- use { a, b, c } from mod 23 | Use [(SourcePos, String)] String 24 | | -- use * from mod 25 | UseAll String 26 | deriving (Show, Eq) 27 | 28 | dependencies :: [Use] -> [FilePath] 29 | dependencies [] = [] 30 | dependencies (SrcPosUse _ use : xs) = dependencies (use : xs) 31 | dependencies (UseAll path : xs) = path : dependencies xs 32 | dependencies (Use _ path : xs) = path : dependencies xs 33 | 34 | data Definition 35 | = SrcPos SourcePos Definition 36 | | Resource String [(Attr, Function)] 37 | | Func Function 38 | | -- record event { specversion: string, ty: string } 39 | Record String [(String, Type)] 40 | | -- type payload = list 41 | TypeAlias String Type 42 | | Variant String [(String, [Type])] 43 | | Enum String [String] 44 | deriving (Show, Eq) 45 | 46 | data Function = Function String [(String, Type)] Type 47 | deriving (Show, Eq) 48 | 49 | data Attr 50 | = Static 51 | | Member 52 | deriving (Show, Eq) 53 | 54 | data Type 55 | = SrcPosType SourcePos Type 56 | | PrimString 57 | | PrimUnit 58 | | PrimU8 59 | | PrimU16 60 | | PrimU32 61 | | PrimU64 62 | | PrimI8 63 | | PrimI16 64 | | PrimI32 65 | | PrimI64 66 | | PrimChar 67 | | PrimF32 68 | | PrimF64 69 | | Optional Type 70 | | ListTy Type 71 | | ExpectedTy Type Type 72 | | TupleTy [Type] 73 | | -- If we parsed something unknown, it probably is a user defined type 74 | -- and hence, we will use checker to reject undefined type errors 75 | Defined String 76 | deriving (Show) 77 | 78 | instance Eq Type where 79 | (SrcPosType _ a) == (SrcPosType _ b) = a == b 80 | PrimString == PrimString = True 81 | PrimUnit == PrimUnit = True 82 | PrimU8 == PrimU8 = True 83 | PrimU16 == PrimU16 = True 84 | PrimU32 == PrimU32 = True 85 | PrimU64 == PrimU64 = True 86 | PrimI8 == PrimI8 = True 87 | PrimI16 == PrimI16 = True 88 | PrimI32 == PrimI32 = True 89 | PrimI64 == PrimI64 = True 90 | PrimChar == PrimChar = True 91 | PrimF32 == PrimF32 = True 92 | PrimF64 == PrimF64 = True 93 | Optional a == Optional b = a == b 94 | ListTy a == ListTy b = a == b 95 | ExpectedTy a1 b1 == ExpectedTy a2 b2 = a1 == a2 && b1 == b2 96 | TupleTy as == TupleTy bs = as == bs 97 | Defined a == Defined b = a == b 98 | _ == _ = False 99 | -------------------------------------------------------------------------------- /src/Wit/Check.hs: -------------------------------------------------------------------------------- 1 | module Wit.Check 2 | ( CheckError (..), 3 | checkFile, 4 | CheckResult (..), 5 | TyEnv, 6 | Context, 7 | ) 8 | where 9 | 10 | import Algebra.Graph.AdjacencyMap (AdjacencyMap, connect, empty, overlay, overlays, vertex) 11 | import Algebra.Graph.ToGraph (ToGraph (topSort)) 12 | import Control.Monad 13 | import Control.Monad.Except 14 | import Control.Monad.Reader 15 | import Control.Monad.State 16 | import Data.Map.Lazy qualified as M 17 | import Prettyprinter 18 | import System.Directory 19 | import System.FilePath (normalise, ()) 20 | import Text.Megaparsec (SourcePos, errorBundlePretty, parse, sourcePosPretty) 21 | import Wit.Ast 22 | import Wit.Parser (ParserError, pWitFile) 23 | import Wit.TypeValue (TypeSig (..), TypeVal (..)) 24 | 25 | checkFile :: 26 | (MonadIO m, MonadError CheckError m) => 27 | FilePath -> 28 | FilePath -> 29 | m 30 | (FilePath, M.Map FilePath CheckResult) 31 | checkFile dirpath filepath = do 32 | (toCheckList, parsed) <- runReaderT (trackFile filepath) dirpath 33 | checked <- 34 | foldM 35 | ( \checked file -> do 36 | let ast = parsed M.! file 37 | c <- runReaderT (evalStateT (check checked ast) emptyCheckState) dirpath 38 | return $ M.insert file c checked 39 | ) 40 | M.empty 41 | toCheckList 42 | return (filepath, checked) 43 | 44 | data CheckError 45 | = PErr ParserError 46 | | CheckError String (Maybe SourcePos) 47 | | Bundle [CheckError] 48 | 49 | instance Pretty CheckError where 50 | pretty e = go e <> line 51 | where 52 | go :: CheckError -> Doc ann 53 | go (PErr parseErr) = pretty $ errorBundlePretty parseErr 54 | go (CheckError msg (Just pos)) = pretty (sourcePosPretty pos) <> colon <+> pretty msg 55 | go (CheckError msg Nothing) = pretty msg 56 | go (Bundle es) = vsep (map go es) 57 | 58 | type Name = String 59 | 60 | type TyEnv = M.Map Name TypeVal 61 | 62 | type Context = M.Map Name TypeSig 63 | 64 | data CheckState = CheckState 65 | { errors :: [CheckError], 66 | -- maps type name to its type value 67 | -- this is created for type definition 68 | typeEnvironment :: TyEnv, 69 | -- maps func or resource to its signature 70 | -- foo : func (x1 : A1, x2 : A2, ...) -> R 71 | context :: Context 72 | } 73 | 74 | updateEnvironment :: (MonadState CheckState m) => Name -> TypeVal -> m () 75 | updateEnvironment name ty = do 76 | checkState <- get 77 | put $ checkState {typeEnvironment = M.insert name ty checkState.typeEnvironment} 78 | 79 | updateContext :: (MonadState CheckState m) => Name -> TypeSig -> m () 80 | updateContext name ty = do 81 | checkState <- get 82 | put $ checkState {context = M.insert name ty checkState.context} 83 | 84 | data CheckResult = CheckResult 85 | { tyEnv :: TyEnv, 86 | ctx :: Context 87 | } 88 | 89 | emptyCheckState :: CheckState 90 | emptyCheckState = CheckState [] M.empty M.empty 91 | 92 | collect :: (MonadState CheckState m, MonadError CheckError m) => m () -> m () 93 | collect ma = ma `catchError` (\e -> modify (\s -> s {errors = e : s.errors})) 94 | 95 | bundle :: (MonadState CheckState m, MonadError CheckError m) => m () 96 | bundle = do 97 | s <- get 98 | case s.errors of 99 | [] -> return () 100 | _ -> do 101 | put (s {errors = []}) 102 | throwError $ Bundle $ reverse s.errors 103 | 104 | report :: (MonadError CheckError m) => String -> m a 105 | report msg = throwError $ CheckError msg Nothing 106 | 107 | addPos :: (MonadError CheckError m) => SourcePos -> m a -> m a 108 | addPos pos = withError updatePos 109 | where 110 | updatePos :: CheckError -> CheckError 111 | updatePos (CheckError msg Nothing) = CheckError msg (Just pos) 112 | updatePos (Bundle es) = Bundle $ map updatePos es 113 | updatePos e = e 114 | 115 | evaluateType :: (MonadState CheckState m, MonadError CheckError m) => Type -> m TypeVal 116 | evaluateType (SrcPosType pos ty) = addPos pos $ evaluateType ty 117 | evaluateType PrimString = return TyString 118 | evaluateType PrimUnit = return TyUnit 119 | evaluateType PrimU8 = return TyU8 120 | evaluateType PrimU16 = return TyU16 121 | evaluateType PrimU32 = return TyU32 122 | evaluateType PrimU64 = return TyU64 123 | evaluateType PrimI8 = return TyI8 124 | evaluateType PrimI16 = return TyI16 125 | evaluateType PrimI32 = return TyI32 126 | evaluateType PrimI64 = return TyI64 127 | evaluateType PrimChar = return TyChar 128 | evaluateType PrimF32 = return TyF32 129 | evaluateType PrimF64 = return TyF64 130 | evaluateType (Optional ty) = TyOptional <$> evaluateType ty 131 | evaluateType (ListTy ty) = TyList <$> evaluateType ty 132 | evaluateType (ExpectedTy ty1 ty2) = TyExpected <$> evaluateType ty1 <*> evaluateType ty2 133 | evaluateType (TupleTy tys) = TyTuple <$> mapM evaluateType tys 134 | evaluateType (Defined name) = do 135 | checkState <- get 136 | case M.lookup name checkState.typeEnvironment of 137 | Just (TyRef n) -> return (TyRef n) 138 | Just (TyExternRef m n) -> return (TyExternRef m n) 139 | -- stuck other type value in resolving here, since expanded a ref is not wanted here 140 | -- we will, however, expand a ref in codegen 141 | Just _ -> return (TyRef name) 142 | Nothing -> report $ "Type `" <> name <> "` not found" 143 | 144 | trackFile :: 145 | (MonadIO m, MonadError CheckError m, MonadReader FilePath m) => 146 | FilePath -> 147 | m ([FilePath], M.Map FilePath WitFile) 148 | trackFile filepath = do 149 | (depGraph, parsed) <- runStateT (go filepath) M.empty 150 | case topSort (vertex filepath `overlay` depGraph) of 151 | Left c -> throwError $ CheckError (filepath <> ": cyclic dependency\n " <> show c) Nothing 152 | Right todoList -> return (reverse todoList, parsed) 153 | where 154 | go :: 155 | (MonadIO m, MonadState (M.Map FilePath WitFile) m, MonadError CheckError m, MonadReader FilePath m) => 156 | FilePath -> 157 | m (AdjacencyMap FilePath) 158 | go path = do 159 | workingDir <- ask 160 | existed <- liftIO $ doesFileExist $ workingDir path 161 | if existed 162 | then do 163 | wit_ast <- parseFile path 164 | modify (M.insert path wit_ast) 165 | let deps = map (<> ".wit") $ dependencies (use_list wit_ast) 166 | let g = map (connect (vertex path) . vertex) deps 167 | gs <- forM deps $ \dep -> do 168 | visited <- get 169 | if M.member dep visited 170 | then return empty 171 | else go dep 172 | return $ overlays $ gs ++ g 173 | else report $ "no file named `" <> normalise workingDir path <> "`" 174 | 175 | parseFile :: (MonadIO m, MonadError CheckError m, MonadReader FilePath m) => FilePath -> m WitFile 176 | parseFile filepath = do 177 | workingDir <- ask 178 | content <- liftIO $ readFile $ workingDir filepath 179 | case parse pWitFile filepath content of 180 | Left e -> throwError $ PErr e 181 | Right ast -> return ast 182 | 183 | check :: 184 | (MonadIO m, MonadError CheckError m, MonadState CheckState m) => 185 | M.Map FilePath CheckResult -> 186 | WitFile -> 187 | m CheckResult 188 | check checked wit_file = do 189 | forM_ wit_file.use_list (checkAndImportUse checked) 190 | bundle 191 | forM_ wit_file.definition_list (collect . defineType) 192 | bundle 193 | forM_ (definition_list wit_file) (collect . defineTerm) 194 | bundle 195 | checkState <- get 196 | return 197 | CheckResult 198 | { tyEnv = checkState.typeEnvironment, 199 | ctx = checkState.context 200 | } 201 | where 202 | checkAndImportUse :: (MonadError CheckError m, MonadState CheckState m) => M.Map FilePath CheckResult -> Use -> m () 203 | checkAndImportUse mod_env (SrcPosUse pos u) = addPos pos $ checkAndImportUse mod_env u 204 | checkAndImportUse mod_env (Use imports mod_name) = do 205 | forM_ imports $ \(pos, name) -> do 206 | let m = mod_env M.! (mod_name <> ".wit") 207 | addPos pos $ ensureRequire mod_name m.tyEnv name 208 | updateEnvironment name (TyExternRef (mod_name <> ".wit") name) 209 | checkAndImportUse mod_env (UseAll mod_name) = do 210 | let m = mod_env M.! (mod_name <> ".wit") 211 | forM_ (M.toList m.tyEnv) $ \(name, _) -> do 212 | updateEnvironment name (TyExternRef (mod_name <> ".wit") name) 213 | 214 | -- ensure required types are defined in the imported module 215 | ensureRequire :: (MonadError CheckError m) => String -> TyEnv -> String -> m () 216 | ensureRequire mod_name env require_name = 217 | when 218 | (require_name `M.notMember` env) 219 | (report ("no type `" ++ require_name ++ "` in module `" ++ mod_name ++ "`")) 220 | 221 | defineType :: (MonadError CheckError m, MonadState CheckState m) => Definition -> m () 222 | defineType (SrcPos _ def) = defineType def 223 | defineType (Enum name cs) = updateEnvironment name $ TyEnum cs 224 | defineType (Record name fields) = do 225 | updateEnvironment name (TyRef name) 226 | fs <- 227 | forM 228 | fields 229 | ( \(field_name, ty) -> do 230 | ty' <- evaluateType ty 231 | return (field_name, ty') 232 | ) 233 | updateEnvironment name (TyRecord fs) 234 | 235 | -- as a sum of product, it's ok to be defined recursively 236 | defineType (Variant name cases) = do 237 | updateEnvironment name (TyRef name) 238 | cs <- forM cases $ 239 | \(case_name, tys) -> do 240 | tys' <- mapM evaluateType tys 241 | return (case_name, TyTuple tys') 242 | updateEnvironment name (TySum cs) 243 | -- Notice that, type alias though can have recursive definition, but it should be invalid 244 | -- Since we will have no idea how to deal with `type A = A` 245 | -- 246 | -- Due to linear check, we also avoid circular definition like `type A = B; type B = A` 247 | -- The first definition will failed 248 | -- 249 | -- Of course, we can have more complicated definition `type A = C A` might be valid in some languages 250 | -- but that is also a thing we would like to avoid 251 | defineType (TypeAlias name ty) = do 252 | tyv <- evaluateType ty 253 | updateEnvironment name tyv 254 | -- resource is not only a term definer, but also a type definer 255 | -- it will have a handle i32 as type value 256 | defineType (Resource name _) = updateEnvironment name TyU32 257 | defineType (Func _) = return () 258 | 259 | defineTerm :: (MonadError CheckError m, MonadState CheckState m) => Definition -> m () 260 | defineTerm (SrcPos pos def) = addPos pos $ defineTerm def 261 | defineTerm (Func f) = defineFn f 262 | defineTerm (Resource resource_name func_list) = 263 | forM_ func_list (\(attr, fn) -> defineFn (transform fn attr)) 264 | where 265 | transform :: Function -> Attr -> Function 266 | transform (Function name binders retTyp) = \case 267 | -- e.g. 268 | -- `static open: func(name: string) -> expected` 269 | -- ~> out of resource 270 | -- `keyvalue_open: func(name: string) -> expected` 271 | Static -> Function (resource_name <> "_" <> name) binders retTyp 272 | -- e.g. 273 | -- `get: func(key: string) -> expected, keyvalue-error> ` 274 | -- ~> out of resource 275 | -- `keyvalue_get: func(handle: keyvalue, key: string) -> expected, keyvalue-error> ` 276 | Member -> Function (resource_name <> "_" <> name) (("handle", Defined resource_name) : binders) retTyp 277 | defineTerm _ = return () 278 | 279 | defineFn :: (MonadError CheckError m, MonadState CheckState m) => Function -> m () 280 | defineFn (Function name binders result_ty) = do 281 | binders' <- forM binders (\(p, pTy) -> do pTy' <- evaluateType pTy; return (p, pTy')) 282 | resultTy <- evaluateType result_ty 283 | updateContext name (TyArrow binders' resultTy) 284 | 285 | -- WARNING: port from mtl, once newer mtl is applied, we can remove this 286 | withError :: MonadError e m => (e -> e) -> m a -> m a 287 | withError f action = tryError action >>= either (throwError . f) pure 288 | 289 | -- WARNING: port from mtl, once newer mtl is applied, we can remove this 290 | tryError :: MonadError e m => m a -> m (Either e a) 291 | tryError act = (Right <$> act) `catchError` (pure . Left) 292 | -------------------------------------------------------------------------------- /src/Wit/Config.hs: -------------------------------------------------------------------------------- 1 | module Wit.Config 2 | ( Config (..), 3 | Direction (..), 4 | Mode (..), 5 | SupportedLanguage (..), 6 | ) 7 | where 8 | 9 | data SupportedLanguage 10 | = Rust 11 | 12 | data Direction 13 | = Import 14 | | Export 15 | 16 | type PluginName = String 17 | 18 | data Mode 19 | = Instance Direction 20 | | Runtime Direction 21 | | Plugin PluginName 22 | 23 | data Config = Config 24 | { language :: SupportedLanguage, 25 | codegenMode :: Mode 26 | } 27 | -------------------------------------------------------------------------------- /src/Wit/Gen.hs: -------------------------------------------------------------------------------- 1 | module Wit.Gen 2 | ( prettyFile, 3 | ) 4 | where 5 | 6 | import Control.Monad.Reader 7 | import Data.Map.Lazy qualified as M 8 | import Prettyprinter 9 | import Wit.Check 10 | import Wit.Config 11 | import Wit.Gen.Export 12 | import Wit.Gen.Import 13 | import Wit.Gen.Plugin 14 | import Wit.Gen.Type 15 | 16 | genContext :: 17 | MonadReader (M.Map FilePath CheckResult) m => 18 | M.Map String t -> 19 | (String -> t -> m (Doc a)) -> 20 | m (Doc a) 21 | genContext m f = do 22 | let m' = M.toList m 23 | t <- forM m' (uncurry f) 24 | return $ foldl (\acc x -> acc <> line <> x) mempty t 25 | 26 | prettyFile :: MonadReader (M.Map FilePath CheckResult) m => Config -> String -> FilePath -> m (Doc a) 27 | prettyFile config inOutName targetMod = do 28 | checked <- ask 29 | let CheckResult {tyEnv = ty_env, ctx = context} = checked M.! targetMod 30 | prettyTyDefs <- genContext ty_env genTypeDefRust 31 | case config.codegenMode of 32 | Instance Import -> do 33 | r <- genContext context prettyDefExtern 34 | r2 <- genContext context prettyDefWrap 35 | return $ 36 | prettyTyDefs 37 | <> line' 38 | <> ( ( pretty "#[link(wasm_import_module = " 39 | <> dquotes (pretty inOutName) 40 | <> pretty ")]" 41 | ) 42 | <> line' 43 | <> pretty "extern \"C\"" 44 | <+> braces 45 | ( line 46 | <> indent 4 r 47 | <> line 48 | ) 49 | <> r2 50 | ) 51 | Instance Export -> do 52 | r <- genContext context toUnsafeExtern 53 | return $ 54 | prettyTyDefs 55 | <> line' 56 | <> r 57 | Runtime Import -> do 58 | r <- genContext context (toVmWrapper inOutName) 59 | return $ 60 | prettyTyDefs 61 | <> line' 62 | <> r 63 | Runtime Export -> do 64 | r <- genContext context toHostFunction 65 | return $ 66 | prettyTyDefs 67 | <> line' 68 | <> vsep 69 | [ pretty $ "mod " ++ inOutName, 70 | braces 71 | ( witObject inOutName (M.keys context) 72 | <> line' 73 | <> pretty "use wasmedge_sdk::Caller;" 74 | <> line' 75 | <> pretty "use super::*;" 76 | <> line' 77 | <> r 78 | ) 79 | ] 80 | Plugin pluginName -> do 81 | r <- genContext context (convertFuncRust pluginName) 82 | return $ 83 | vsep 84 | [ prettyTyDefs, 85 | pretty "#[link(wasm_import_module =" 86 | <+> dquotes (pretty pluginName) 87 | <> pretty 88 | ")]", 89 | pretty 90 | "extern \"C\"" 91 | <+> braces (indent 4 r) 92 | ] 93 | -------------------------------------------------------------------------------- /src/Wit/Gen/Export.hs: -------------------------------------------------------------------------------- 1 | module Wit.Gen.Export 2 | ( witObject, 3 | toHostFunction, 4 | toUnsafeExtern, 5 | ) 6 | where 7 | 8 | import Control.Monad.Reader 9 | import Data.Map.Lazy qualified as M 10 | import Prettyprinter 11 | import Wit.Check 12 | import Wit.Gen.Normalization 13 | import Wit.Gen.Type 14 | import Wit.TypeValue 15 | 16 | -- instance 17 | toUnsafeExtern :: MonadReader (M.Map FilePath CheckResult) m => String -> TypeSig -> m (Doc a) 18 | toUnsafeExtern name (TyArrow param_list _result_ty) = do 19 | ps <- mapM getParameter param_list 20 | return $ 21 | vsep 22 | [ pretty "#[no_mangle]", 23 | pretty "pub unsafe extern \"C\"", 24 | hsep 25 | [ pretty "fn", 26 | pretty $ externalConvention name, 27 | parens $ pretty "id: i32" 28 | ], 29 | braces 30 | ( indent 31 | 4 32 | ( vsep $ 33 | ps 34 | ++ [ pretty "let r =" 35 | <+> pretty (normalizeIdentifier name) 36 | <+> tupled (map (\(x, _) -> pretty x) param_list) 37 | <+> pretty ";", 38 | pretty "let result_str = serde_json::to_string(&r).unwrap();", 39 | pretty "witc_abi::instance::write(id, result_str.as_ptr() as usize, result_str.len());" 40 | ] 41 | ) 42 | ) 43 | ] 44 | where 45 | getParameter :: MonadReader (M.Map FilePath CheckResult) m => (String, TypeVal) -> m (Doc a) 46 | getParameter (x, ty) = do 47 | ty' <- genTypeRust ty 48 | return $ 49 | hsep 50 | [ pretty "let", 51 | pretty x, 52 | pretty ":", 53 | ty', 54 | pretty "=", 55 | hcat 56 | [ pretty "serde_json::from_str(witc_abi::instance::read(id).to_string().as_str()).unwrap();" 57 | ] 58 | ] 59 | 60 | -- runtime 61 | toHostFunction :: MonadReader (M.Map FilePath CheckResult) m => String -> TypeSig -> m (Doc a) 62 | toHostFunction name (TyArrow param_list _result_ty) = do 63 | ps <- mapM letParam param_list 64 | return $ 65 | pretty "#[wasmedge_sdk::host_function]" 66 | <+> line 67 | <+> hsep (map pretty ["fn", externalConvention name]) 68 | <+> parens (pretty "caller: wasmedge_sdk::Caller, input: Vec") 69 | <+> pretty "->" 70 | <+> pretty "Result, wasmedge_sdk::error::HostFuncError>" 71 | <+> braces 72 | ( line 73 | <> indent 74 | 4 75 | ( pretty "let id = input[0].to_i32();" 76 | <> line 77 | <> vsep ps 78 | <> vsep 79 | [ pretty "let r =" 80 | <+> pretty (normalizeIdentifier name) 81 | <+> tupled (map (\(x, _) -> pretty x) param_list) 82 | <+> pretty ";", 83 | pretty "let result_str = serde_json::to_string(&r).unwrap();", 84 | pretty "unsafe { witc_abi::runtime::STATE.put_buffer(id, result_str) }", 85 | pretty "Ok(vec![])" 86 | ] 87 | <> line 88 | ) 89 | ) 90 | where 91 | letParam :: 92 | MonadReader (M.Map FilePath CheckResult) m => 93 | (String, TypeVal) -> 94 | m (Doc a) 95 | letParam (x, ty) = do 96 | ty' <- genTypeRust ty 97 | return $ 98 | hsep 99 | [ pretty "let", 100 | pretty x, 101 | pretty ":", 102 | ty', 103 | pretty "=", 104 | hcat 105 | [pretty "serde_json::from_str(unsafe { witc_abi::runtime::STATE.read_buffer(id).as_str() }).unwrap();"] 106 | ] 107 | 108 | -- runtime wasm import object 109 | witObject :: String -> [String] -> Doc a 110 | witObject exportName defs = 111 | pretty "pub fn wit_import_object() -> wasmedge_sdk::WasmEdgeResult" 112 | <+> braces 113 | ( pretty "Ok" 114 | <+> parens 115 | ( pretty "wasmedge_sdk::ImportObjectBuilder::new()" 116 | <+> vsep (map withFunc defs) 117 | <+> pretty 118 | ( ".build(\"" 119 | ++ exportName 120 | ++ "\")?" 121 | ) 122 | ) 123 | ) 124 | where 125 | withFunc :: String -> Doc a 126 | withFunc (pretty . externalConvention -> name) = 127 | -- i32: every convention function should just get the id of the queue 128 | -- (): returns nothing (real returns will be sent by queue) 129 | pretty ".with_func::" 130 | <+> tupled [dquotes name, name] 131 | <+> pretty "?" 132 | -------------------------------------------------------------------------------- /src/Wit/Gen/Import.hs: -------------------------------------------------------------------------------- 1 | module Wit.Gen.Import 2 | ( prettyDefWrap, 3 | prettyDefExtern, 4 | toVmWrapper, 5 | ) 6 | where 7 | 8 | import Control.Monad.Reader 9 | import Data.Map.Lazy qualified as M 10 | import Prettyprinter 11 | import Wit.Check 12 | import Wit.Gen.Normalization 13 | import Wit.Gen.Type 14 | import Wit.TypeValue 15 | 16 | -- runtime 17 | toVmWrapper :: MonadReader (M.Map FilePath CheckResult) m => String -> String -> TypeSig -> m (Doc a) 18 | toVmWrapper importName name (TyArrow param_list result_ty) = do 19 | params <- 20 | mapM 21 | ( \(p, ty) -> do 22 | ty' <- genTypeRust ty 23 | return $ pretty p <+> pretty ":" <+> ty' 24 | ) 25 | param_list 26 | result_ty' <- genTypeRust result_ty 27 | sendArgs <- mapM sendArgument param_list 28 | return $ 29 | hsep 30 | [ pretty "fn", 31 | pretty $ normalizeIdentifier name, 32 | tupled $ pretty "vm: &wasmedge_sdk::Vm" : params, 33 | pretty "->", 34 | result_ty' 35 | ] 36 | <+> braces 37 | ( indent 38 | 4 39 | ( vsep 40 | ( [pretty "let id = unsafe { witc_abi::runtime::STATE.new_queue() }; "] 41 | ++ sendArgs 42 | ++ [ hsep 43 | [ pretty "vm.run_func(Some(", 44 | dquotes (pretty importName), 45 | pretty "), ", 46 | dquotes (pretty $ externalConvention name), 47 | pretty ", vec![wasmedge_sdk::WasmValue::from_i32(id)]).unwrap();" 48 | ], 49 | pretty "serde_json::from_str(unsafe { witc_abi::runtime::STATE.read_buffer(id).as_str() }).unwrap()" 50 | ] 51 | ) 52 | ) 53 | ) 54 | where 55 | sendArgument :: MonadReader (M.Map FilePath CheckResult) m => (String, TypeVal) -> m (Doc a) 56 | sendArgument (param_name, _) = 57 | return $ 58 | pretty $ 59 | "unsafe { witc_abi::runtime::STATE.put_buffer(id, serde_json::to_string(&" 60 | ++ param_name 61 | ++ ").unwrap()); }" 62 | 63 | -- instance 64 | prettyDefWrap :: 65 | MonadReader (M.Map FilePath CheckResult) m => 66 | String -> 67 | TypeSig -> 68 | m (Doc a) 69 | prettyDefWrap name (TyArrow param_list result_ty) = do 70 | rty <- genTypeRust result_ty 71 | ps <- mapM prettyBinder param_list 72 | sendArgs <- mapM sendArgument param_list 73 | return $ 74 | pretty "fn" 75 | <+> pretty (normalizeIdentifier name) 76 | <> parens (hsep $ punctuate comma ps) 77 | <+> pretty "->" 78 | <+> rty 79 | <+> braces 80 | ( pretty "unsafe" 81 | <> braces 82 | ( line 83 | <> indent 84 | 4 85 | ( -- require queue 86 | pretty 87 | "let id = witc_abi::instance::require_queue();" 88 | <> line' 89 | <> hsep sendArgs 90 | <> line' 91 | <> pretty (externalConvention name ++ "(id);") 92 | <> line' 93 | <> pretty "let mut returns: Vec = vec![];" 94 | <> line' 95 | <> pretty "serde_json::from_str(witc_abi::instance::read(id).to_string().as_str()).unwrap()" 96 | ) 97 | <> line 98 | ) 99 | ) 100 | where 101 | sendArgument :: MonadReader (M.Map FilePath CheckResult) m => (String, TypeVal) -> m (Doc a) 102 | sendArgument (param_name, _) = 103 | return $ 104 | pretty 105 | ("let r = serde_json::to_string(&" ++ param_name ++ ").unwrap();") 106 | <> line' 107 | <> pretty "witc_abi::instance::write(id, r.as_ptr() as usize, r.len());" 108 | 109 | prettyBinder :: 110 | MonadReader (M.Map FilePath CheckResult) m => 111 | (String, TypeVal) -> 112 | m (Doc a) 113 | prettyBinder (field_name, ty) = do 114 | ty' <- genTypeRust ty 115 | return $ pretty field_name <> pretty ":" <+> ty' 116 | 117 | prettyDefExtern :: 118 | MonadReader (M.Map FilePath CheckResult) m => 119 | String -> 120 | TypeSig -> 121 | m (Doc a) 122 | prettyDefExtern (externalConvention -> name) (TyArrow {}) = do 123 | return $ pretty "fn" <+> pretty name <> pretty "(id: i32);" 124 | -------------------------------------------------------------------------------- /src/Wit/Gen/Normalization.hs: -------------------------------------------------------------------------------- 1 | module Wit.Gen.Normalization 2 | ( normalizeIdentifier, 3 | externalConvention, 4 | ) 5 | where 6 | 7 | externalConvention :: String -> String 8 | externalConvention s = "extern_" ++ normalizeIdentifier s 9 | 10 | normalizeIdentifier :: String -> String 11 | normalizeIdentifier = map f 12 | where 13 | f '-' = '_' 14 | f c = c 15 | -------------------------------------------------------------------------------- /src/Wit/Gen/Plugin.hs: -------------------------------------------------------------------------------- 1 | module Wit.Gen.Plugin 2 | ( convertFuncRust, 3 | ) 4 | where 5 | 6 | import Control.Monad.Reader 7 | import Data.Map.Lazy qualified as M 8 | import Prettyprinter 9 | import Wit.Check 10 | import Wit.Gen.Normalization 11 | import Wit.TypeValue 12 | 13 | convertFuncRust :: MonadReader (M.Map FilePath CheckResult) m => String -> String -> TypeSig -> m (Doc a) 14 | convertFuncRust pluginName (normalizeIdentifier -> name) (TyArrow param_list result_ty) = 15 | return $ 16 | pretty "#[link_name =" 17 | <+> dquotes (pretty $ pluginName ++ "_" ++ name) 18 | <> pretty "]" 19 | <+> line' 20 | <> pretty 21 | "pub" 22 | <+> pretty "fn" 23 | <+> pretty name 24 | <> parens (hsep (punctuate comma (map convertParamRust param_list))) 25 | <+> pretty "->" 26 | <+> convertTyRust result_ty 27 | <> semi 28 | where 29 | convertPtrTypeRust paramName = 30 | pretty (paramName ++ "_ptr") 31 | <> colon 32 | <+> pretty "*const u8" 33 | <> comma 34 | <+> pretty (paramName ++ "_len") 35 | <> colon 36 | <+> pretty "usize" 37 | 38 | convertParamRust (normalizeIdentifier -> paramName, ty) = 39 | case ty of 40 | -- string & list can be converted to a pointer and a size 41 | TyString -> convertPtrTypeRust paramName 42 | TyList _ -> convertPtrTypeRust paramName 43 | _ -> pretty paramName <> colon <+> convertTyRust ty 44 | 45 | convertTyRust :: TypeVal -> Doc a 46 | convertTyRust TyUnit = pretty "()" 47 | convertTyRust TyU8 = pretty "u8" 48 | convertTyRust TyU16 = pretty "u16" 49 | convertTyRust TyU32 = pretty "u32" 50 | convertTyRust TyU64 = pretty "u64" 51 | convertTyRust TyI8 = pretty "i8" 52 | convertTyRust TyI16 = pretty "i16" 53 | convertTyRust TyI32 = pretty "i32" 54 | convertTyRust TyI64 = pretty "i64" 55 | convertTyRust TyChar = pretty "char" 56 | convertTyRust TyF32 = pretty "f32" 57 | convertTyRust TyF64 = pretty "f64" 58 | convertTyRust (TyRef (normalizeIdentifier -> name)) = pretty name 59 | convertTyRust _ = error "unsupported type occurs when generating plugin" 60 | -------------------------------------------------------------------------------- /src/Wit/Gen/Type.hs: -------------------------------------------------------------------------------- 1 | {- Type & its definition should be the same for any direction, hence, it should be independent -} 2 | module Wit.Gen.Type 3 | ( genTypeDefRust, 4 | genTypeRust, 5 | ) 6 | where 7 | 8 | import Control.Monad.Reader 9 | import Data.Map.Lazy qualified as M 10 | import Prettyprinter 11 | import Wit.Check 12 | import Wit.Gen.Normalization 13 | import Wit.TypeValue 14 | 15 | genTypeDefRust :: 16 | MonadReader (M.Map FilePath CheckResult) m => 17 | String -> 18 | TypeVal -> 19 | m (Doc a) 20 | genTypeDefRust (normalizeIdentifier -> name) = \case 21 | TyRecord fields -> do 22 | fields' <- 23 | forM 24 | fields 25 | ( \(n, ty) -> do 26 | b <- genTypeRust ty 27 | return $ hsep [pretty n, pretty ":", b] 28 | ) 29 | return $ 30 | pretty "#[derive(Serialize, Deserialize, Debug)]" 31 | <> line 32 | <> pretty "pub struct" 33 | <+> pretty name 34 | <+> braces 35 | ( line 36 | <> indent 37 | 4 38 | ( vsep $ 39 | punctuate 40 | comma 41 | fields' 42 | ) 43 | <> line 44 | ) 45 | TySum cases -> do 46 | cases' <- forM cases genCase 47 | return $ 48 | pretty "#[derive(Serialize, Deserialize, Debug)]" 49 | <> line 50 | <> pretty "pub enum" 51 | <+> pretty name 52 | <+> braces (line <> indent 4 (vsep $ punctuate comma cases') <> line) 53 | where 54 | genCase :: MonadReader (M.Map FilePath CheckResult) m => (String, TypeVal) -> m (Doc a) 55 | genCase (normalizeIdentifier -> n, ty) = do 56 | b <- boxType ty 57 | return $ pretty n <> b 58 | boxType :: MonadReader (M.Map FilePath CheckResult) m => TypeVal -> m (Doc a) 59 | boxType (TyOptional ty) = do 60 | b <- boxType ty 61 | return $ pretty "Option" <> angles b 62 | boxType (TyList ty) = do 63 | b <- boxType ty 64 | return $ pretty "Vec" <> angles b 65 | boxType (TyExpected a b) = do 66 | a' <- boxType a 67 | b' <- boxType b 68 | return $ pretty "Result" <> angles (a' <> pretty "," <> b') 69 | boxType (TyTuple []) = return mempty 70 | boxType (TyTuple ty_list) = do 71 | tys <- forM ty_list boxType 72 | return $ parens (hsep $ punctuate comma tys) 73 | boxType (TyRef (normalizeIdentifier -> n)) = 74 | if n == name 75 | then return $ pretty "Box" <> angles (pretty n) 76 | else return $ pretty n 77 | boxType ty = genTypeRust ty 78 | TyEnum cases -> 79 | return $ 80 | pretty "#[derive(Serialize, Deserialize, Debug)]" 81 | <> line 82 | <> pretty "pub enum" 83 | <+> pretty name 84 | <+> braces 85 | ( line 86 | <> indent 4 (vsep $ punctuate comma (map (pretty . normalizeIdentifier) cases)) 87 | <> line 88 | ) 89 | TyExternRef mod_file ty_name -> do 90 | checked <- ask 91 | genTypeDefRust ty_name $ (checked M.! mod_file).tyEnv M.! ty_name 92 | ty -> do 93 | b <- genTypeRust ty 94 | return $ pretty "pub type" <+> pretty name <+> pretty "=" <+> b <> pretty ";" 95 | 96 | genTypeRust :: MonadReader (M.Map FilePath CheckResult) m => TypeVal -> m (Doc a) 97 | genTypeRust = \case 98 | TyString -> return $ pretty "String" 99 | TyUnit -> return $ pretty "()" 100 | TyU8 -> return $ pretty "u8" 101 | TyU16 -> return $ pretty "u16" 102 | TyU32 -> return $ pretty "u32" 103 | TyU64 -> return $ pretty "u64" 104 | TyI8 -> return $ pretty "i8" 105 | TyI16 -> return $ pretty "i16" 106 | TyI32 -> return $ pretty "i32" 107 | TyI64 -> return $ pretty "i64" 108 | TyChar -> return $ pretty "char" 109 | TyF32 -> return $ pretty "f32" 110 | TyF64 -> return $ pretty "f64" 111 | TyOptional ty -> do 112 | b <- genTypeRust ty 113 | return $ pretty "Option" <> angles b 114 | TyList ty -> do 115 | b <- genTypeRust ty 116 | return $ pretty "Vec" <> angles b 117 | TyExpected a b -> do 118 | a' <- genTypeRust a 119 | b' <- genTypeRust b 120 | return $ pretty "Result" <> angles (a' <> pretty "," <> b') 121 | TyTuple [] -> return mempty 122 | TyTuple ty_list -> do 123 | tys <- forM ty_list genTypeRust 124 | return $ parens (hsep $ punctuate comma tys) 125 | TyRef (normalizeIdentifier -> name) -> return $ pretty name 126 | TyExternRef _ (normalizeIdentifier -> ty_name) -> return $ pretty ty_name 127 | _ -> error "crash type" 128 | -------------------------------------------------------------------------------- /src/Wit/Parser.hs: -------------------------------------------------------------------------------- 1 | module Wit.Parser 2 | ( -- parser error for this module 3 | ParserError, 4 | -- file level parser 5 | pWitFile, 6 | -- Use statement 7 | pUse, 8 | -- Definition 9 | pDefinition, 10 | -- define object 11 | pResource, 12 | pFunc, 13 | -- define type 14 | pRecord, 15 | pTypeAlias, 16 | pVariant, 17 | pEnum, 18 | ) 19 | where 20 | 21 | import Control.Monad 22 | import Data.Char 23 | import Data.Functor 24 | import Data.Maybe 25 | import Data.Void 26 | import Text.Megaparsec hiding (State) 27 | import Text.Megaparsec.Char 28 | import Text.Megaparsec.Char.Lexer qualified as L 29 | import Wit.Ast 30 | 31 | type Parser = Parsec Void String 32 | 33 | type ParserError = ParseErrorBundle String Void 34 | 35 | pWitFile :: Parser WitFile 36 | pWitFile = do 37 | lexeme space 38 | us <- many $ lexeme (withPos SrcPosUse pUse) 39 | ds <- many $ lexeme (withPos SrcPos pDefinition) 40 | return WitFile {use_list = us, definition_list = ds} 41 | 42 | pUse :: Parser Use 43 | pUse = do 44 | keyword "use" 45 | all_syntax <- optional $ symbol "*" 46 | case all_syntax of 47 | Just _ -> do 48 | keyword "from" 49 | UseAll <$> identifier 50 | Nothing -> do 51 | id_list <- braces $ sepEndBy sourceId (symbol ",") 52 | keyword "from" 53 | Use id_list <$> identifier 54 | where 55 | sourceId = do 56 | pos <- getSourcePos 57 | name <- identifier 58 | return (pos, name) 59 | 60 | pDefinition :: Parser Definition 61 | pDefinition = 62 | pRecord 63 | <|> pTypeAlias 64 | <|> pVariant 65 | <|> pEnum 66 | <|> pResource 67 | <|> pFunc 68 | 69 | -- object definition 70 | pResource, pFunc :: Parser Definition 71 | pResource = do 72 | keyword "resource" 73 | Resource <$> identifier <*> braces (many resourceFunc) 74 | where 75 | resourceFunc = do 76 | attr <- option Member (keyword "static" $> Static) 77 | f <- pFunction 78 | return (attr, f) 79 | pFunc = Func <$> pFunction 80 | 81 | pFunction :: Parser Function 82 | pFunction = do 83 | fn_name <- identifier 84 | symbol ":" 85 | keyword "func" 86 | params <- parens (sepEndBy pParam (symbol ",")) 87 | result_ty <- optional pResultType 88 | let f = Function fn_name params 89 | return 90 | ( case result_ty of 91 | Nothing -> f PrimUnit 92 | Just ret_ty -> f ret_ty 93 | ) 94 | where 95 | pParam :: Parser (String, Type) 96 | pParam = (,) <$> (identifier <* symbol ":") <*> pType 97 | pResultType :: Parser Type 98 | pResultType = symbol "->" *> pType 99 | 100 | -- type definition 101 | pRecord, pTypeAlias, pVariant, pEnum :: Parser Definition 102 | pRecord = do 103 | keyword "record" 104 | record_name <- identifier 105 | field_list <- braces $ sepEndBy pRecordField (symbol ",") 106 | return $ Record record_name field_list 107 | where 108 | pRecordField :: Parser (String, Type) 109 | pRecordField = (,) <$> (identifier <* symbol ":") <*> pType 110 | pTypeAlias = do 111 | keyword "type" 112 | name <- identifier 113 | symbol "=" 114 | TypeAlias name <$> pType 115 | pVariant = do 116 | keyword "variant" 117 | name <- identifier 118 | case_list <- braces $ sepEndBy pVariantCase (symbol ",") 119 | return $ Variant name case_list 120 | where 121 | -- tag-name "(" type ("," type)* ")" 122 | pVariantCase :: Parser (String, [Type]) 123 | pVariantCase = do 124 | tag_name <- identifier 125 | type_list <- optional $ parens $ sepBy pType (symbol ",") 126 | return (tag_name, fromMaybe [] type_list) 127 | pEnum = do 128 | keyword "enum" 129 | name <- identifier 130 | case_list <- braces $ sepEndBy identifier (symbol ",") 131 | return $ Enum name case_list 132 | 133 | pType :: Parser Type 134 | pType = 135 | withPos 136 | SrcPosType 137 | ( choice 138 | [expectedTy, tupleTy, listTy, optionalTy, primitiveTy] 139 | "" 140 | ) 141 | where 142 | expectedTy, tupleTy, listTy, optionalTy, primitiveTy :: Parser Type 143 | expectedTy = do 144 | keyword "expected" 145 | (a, b) <- angles $ (,) <$> pType <*> (symbol "," *> pType) 146 | return $ ExpectedTy a b 147 | tupleTy = do 148 | keyword "tuple" 149 | TupleTy <$> angles (sepBy1 pType (symbol ",")) 150 | listTy = do 151 | keyword "list" 152 | ListTy <$> angles pType 153 | optionalTy = do 154 | keyword "option" 155 | Optional <$> angles pType 156 | primitiveTy = do 157 | name <- identifier 158 | case name of 159 | "unit" -> return PrimUnit 160 | "string" -> return PrimString 161 | "u8" -> return PrimU8 162 | "u16" -> return PrimU16 163 | "u32" -> return PrimU32 164 | "u64" -> return PrimU64 165 | "s8" -> return PrimI8 166 | "s16" -> return PrimI16 167 | "s32" -> return PrimI32 168 | "s64" -> return PrimI64 169 | "char" -> return PrimChar 170 | "f32" -> return PrimF32 171 | "f64" -> return PrimF64 172 | name' -> return $ Defined name' 173 | 174 | ------------ 175 | -- helper -- 176 | ------------ 177 | withPos :: (SourcePos -> a -> a) -> Parser a -> Parser a 178 | withPos c p = c <$> getSourcePos <*> p 179 | 180 | ------------ 181 | -- tokens -- 182 | ------------ 183 | lineComment, blockComment :: Parser () 184 | lineComment = L.skipLineComment "//" 185 | blockComment = L.skipBlockComment "/*" "*/" 186 | 187 | whitespace :: Parser () 188 | whitespace = L.space space1 lineComment blockComment 189 | 190 | lexeme :: Parser a -> Parser a 191 | lexeme = L.lexeme whitespace 192 | 193 | symbol :: String -> Parser () 194 | symbol s = L.symbol whitespace s $> () 195 | 196 | wrap :: String -> String -> (Parser a -> Parser a) 197 | wrap l r = between (symbol l) (symbol r) 198 | 199 | parens, braces, angles :: Parser a -> Parser a 200 | parens = wrap "(" ")" 201 | braces = wrap "{" "}" 202 | angles = wrap "<" ">" 203 | 204 | keyword :: String -> Parser () 205 | keyword kw = do 206 | x <- string kw ("keyword: `" ++ kw ++ "`") 207 | guard (isKeyword x) 208 | (takeWhile1P Nothing isAlphaNum *> empty) <|> whitespace 209 | 210 | identifier :: Parser String 211 | identifier = do 212 | x <- takeWhile1P Nothing isValidChar 213 | guard (not (isKeyword x)) 214 | x <$ whitespace 215 | where 216 | isValidChar :: Char -> Bool 217 | isValidChar '-' = True 218 | isValidChar c = isAlphaNum c 219 | 220 | isKeyword :: String -> Bool 221 | isKeyword "use" = True 222 | isKeyword "from" = True 223 | isKeyword "resource" = True 224 | isKeyword "static" = True 225 | isKeyword "func" = True 226 | isKeyword "record" = True 227 | isKeyword "type" = True 228 | isKeyword "variant" = True 229 | isKeyword "enum" = True 230 | isKeyword "expected" = True 231 | isKeyword "tuple" = True 232 | isKeyword "list" = True 233 | isKeyword "option" = True 234 | isKeyword _ = False 235 | -------------------------------------------------------------------------------- /src/Wit/TypeValue.hs: -------------------------------------------------------------------------------- 1 | module Wit.TypeValue 2 | ( TypeVal (..), 3 | TypeSig (..), 4 | ) 5 | where 6 | 7 | data TypeVal 8 | = TyString 9 | | TyUnit 10 | | TyU8 11 | | TyU16 12 | | TyU32 13 | | TyU64 14 | | TyI8 15 | | TyI16 16 | | TyI32 17 | | TyI64 18 | | TyChar 19 | | TyF32 20 | | TyF64 21 | | TyOptional TypeVal 22 | | TyList TypeVal 23 | | TyExpected TypeVal TypeVal 24 | | -- nameful product type 25 | TyRecord [(String, TypeVal)] 26 | | -- product type: A × B × C 27 | TyTuple [TypeVal] 28 | | -- sum type: A + B + C 29 | -- we record the name of the sum type to handle recursion in it 30 | TyEnum [String] 31 | | TySum [(String, TypeVal)] 32 | | -- checker should ensure reference is linked to defined type 33 | -- 34 | -- we have two modes 35 | -- 1. flatten type: wasmedge plugin codegen 36 | -- 2. recursive type: witc component codegen 37 | -- 38 | -- flatten type disallowed recursive, and hence we cannot resolve type reference directly, so we need this 39 | TyRef String 40 | | -- This is double level reference, it refers to module and the type name 41 | TyExternRef String String 42 | 43 | data TypeSig 44 | = -- function type: func (x : A, y : B) -> C 45 | -- but we expect parameters are well named 46 | TyArrow [(String, TypeVal)] TypeVal 47 | -------------------------------------------------------------------------------- /stack.yaml: -------------------------------------------------------------------------------- 1 | resolver: 2 | url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/20/20.yaml 3 | 4 | packages: 5 | - . 6 | -------------------------------------------------------------------------------- /stack.yaml.lock: -------------------------------------------------------------------------------- 1 | # This file was autogenerated by Stack. 2 | # You should not edit this file by hand. 3 | # For more information, please see the documentation at: 4 | # https://docs.haskellstack.org/en/stable/lock_files 5 | 6 | packages: [] 7 | snapshots: 8 | - completed: 9 | sha256: 126fa33ceb11f5e85ceb4e86d434756bd9a8439e2e5776d306a15fbc63b01e89 10 | size: 650041 11 | url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/20/20.yaml 12 | original: 13 | url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/20/20.yaml 14 | -------------------------------------------------------------------------------- /test-project/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "test-project" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | wasmedge-sdk = "0.8.1" 8 | serde = { version = "1.0", features = ["derive"] } 9 | serde_json = "*" 10 | 11 | invoke-witc = { path = "../bindings/rust/invoke-witc" } 12 | witc-abi = { path = "../bindings/rust/witc-abi" } 13 | -------------------------------------------------------------------------------- /test-project/Makefile: -------------------------------------------------------------------------------- 1 | test: build-wasm 2 | cargo test 3 | .PHONY: test 4 | 5 | build-wasm: 6 | cargo build --manifest-path import_wasm/Cargo.toml --target wasm32-wasi -r 7 | .PHONY: build-wasm 8 | -------------------------------------------------------------------------------- /test-project/export1.wit: -------------------------------------------------------------------------------- 1 | identity-one: func(a: u32) -> u32 2 | -------------------------------------------------------------------------------- /test-project/export2.wit: -------------------------------------------------------------------------------- 1 | identity-two: func(a: u32) -> u32 2 | -------------------------------------------------------------------------------- /test-project/import_wasm/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 = "anyhow" 7 | version = "1.0.71" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" 10 | 11 | [[package]] 12 | name = "autocfg" 13 | version = "1.1.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 16 | 17 | [[package]] 18 | name = "bindgen" 19 | version = "0.61.0" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "8a022e58a142a46fea340d68012b9201c094e93ec3d033a944a24f8fd4a4f09a" 22 | dependencies = [ 23 | "bitflags", 24 | "cexpr", 25 | "clang-sys", 26 | "lazy_static", 27 | "lazycell", 28 | "peeking_take_while", 29 | "proc-macro2", 30 | "quote", 31 | "regex", 32 | "rustc-hash", 33 | "shlex", 34 | "syn 1.0.109", 35 | ] 36 | 37 | [[package]] 38 | name = "bitflags" 39 | version = "1.3.2" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 42 | 43 | [[package]] 44 | name = "cc" 45 | version = "1.0.79" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" 48 | 49 | [[package]] 50 | name = "cexpr" 51 | version = "0.6.0" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" 54 | dependencies = [ 55 | "nom", 56 | ] 57 | 58 | [[package]] 59 | name = "cfg-if" 60 | version = "1.0.0" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 63 | 64 | [[package]] 65 | name = "clang-sys" 66 | version = "1.6.1" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f" 69 | dependencies = [ 70 | "glob", 71 | "libc", 72 | "libloading", 73 | ] 74 | 75 | [[package]] 76 | name = "cmake" 77 | version = "0.1.50" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130" 80 | dependencies = [ 81 | "cc", 82 | ] 83 | 84 | [[package]] 85 | name = "getrandom" 86 | version = "0.2.9" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" 89 | dependencies = [ 90 | "cfg-if", 91 | "libc", 92 | "wasi", 93 | ] 94 | 95 | [[package]] 96 | name = "glob" 97 | version = "0.3.1" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" 100 | 101 | [[package]] 102 | name = "import_wasm" 103 | version = "0.1.0" 104 | dependencies = [ 105 | "invoke-witc", 106 | "serde", 107 | "serde_json", 108 | "witc-abi", 109 | ] 110 | 111 | [[package]] 112 | name = "invoke-witc" 113 | version = "0.2.1" 114 | dependencies = [ 115 | "syn 1.0.109", 116 | ] 117 | 118 | [[package]] 119 | name = "itoa" 120 | version = "1.0.6" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" 123 | 124 | [[package]] 125 | name = "lazy_static" 126 | version = "1.4.0" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 129 | 130 | [[package]] 131 | name = "lazycell" 132 | version = "1.3.0" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" 135 | 136 | [[package]] 137 | name = "leb128" 138 | version = "0.2.5" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" 141 | 142 | [[package]] 143 | name = "libc" 144 | version = "0.2.142" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317" 147 | 148 | [[package]] 149 | name = "libloading" 150 | version = "0.7.4" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" 153 | dependencies = [ 154 | "cfg-if", 155 | "winapi", 156 | ] 157 | 158 | [[package]] 159 | name = "lock_api" 160 | version = "0.4.9" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" 163 | dependencies = [ 164 | "autocfg", 165 | "scopeguard", 166 | ] 167 | 168 | [[package]] 169 | name = "memchr" 170 | version = "2.5.0" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 173 | 174 | [[package]] 175 | name = "minimal-lexical" 176 | version = "0.2.1" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 179 | 180 | [[package]] 181 | name = "nom" 182 | version = "7.1.3" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 185 | dependencies = [ 186 | "memchr", 187 | "minimal-lexical", 188 | ] 189 | 190 | [[package]] 191 | name = "num-derive" 192 | version = "0.3.3" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" 195 | dependencies = [ 196 | "proc-macro2", 197 | "quote", 198 | "syn 1.0.109", 199 | ] 200 | 201 | [[package]] 202 | name = "num-traits" 203 | version = "0.2.15" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" 206 | dependencies = [ 207 | "autocfg", 208 | ] 209 | 210 | [[package]] 211 | name = "once_cell" 212 | version = "1.17.1" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" 215 | 216 | [[package]] 217 | name = "parking_lot" 218 | version = "0.12.1" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 221 | dependencies = [ 222 | "lock_api", 223 | "parking_lot_core", 224 | ] 225 | 226 | [[package]] 227 | name = "parking_lot_core" 228 | version = "0.9.7" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" 231 | dependencies = [ 232 | "cfg-if", 233 | "libc", 234 | "redox_syscall", 235 | "smallvec", 236 | "windows-sys", 237 | ] 238 | 239 | [[package]] 240 | name = "paste" 241 | version = "1.0.12" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" 244 | 245 | [[package]] 246 | name = "peeking_take_while" 247 | version = "0.1.2" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" 250 | 251 | [[package]] 252 | name = "ppv-lite86" 253 | version = "0.2.17" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 256 | 257 | [[package]] 258 | name = "proc-macro2" 259 | version = "1.0.56" 260 | source = "registry+https://github.com/rust-lang/crates.io-index" 261 | checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" 262 | dependencies = [ 263 | "unicode-ident", 264 | ] 265 | 266 | [[package]] 267 | name = "quote" 268 | version = "1.0.26" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" 271 | dependencies = [ 272 | "proc-macro2", 273 | ] 274 | 275 | [[package]] 276 | name = "rand" 277 | version = "0.8.5" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 280 | dependencies = [ 281 | "libc", 282 | "rand_chacha", 283 | "rand_core", 284 | ] 285 | 286 | [[package]] 287 | name = "rand_chacha" 288 | version = "0.3.1" 289 | source = "registry+https://github.com/rust-lang/crates.io-index" 290 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 291 | dependencies = [ 292 | "ppv-lite86", 293 | "rand_core", 294 | ] 295 | 296 | [[package]] 297 | name = "rand_core" 298 | version = "0.6.4" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 301 | dependencies = [ 302 | "getrandom", 303 | ] 304 | 305 | [[package]] 306 | name = "redox_syscall" 307 | version = "0.2.16" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 310 | dependencies = [ 311 | "bitflags", 312 | ] 313 | 314 | [[package]] 315 | name = "regex" 316 | version = "1.8.1" 317 | source = "registry+https://github.com/rust-lang/crates.io-index" 318 | checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" 319 | dependencies = [ 320 | "regex-syntax", 321 | ] 322 | 323 | [[package]] 324 | name = "regex-syntax" 325 | version = "0.7.1" 326 | source = "registry+https://github.com/rust-lang/crates.io-index" 327 | checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" 328 | 329 | [[package]] 330 | name = "rustc-hash" 331 | version = "1.1.0" 332 | source = "registry+https://github.com/rust-lang/crates.io-index" 333 | checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 334 | 335 | [[package]] 336 | name = "ryu" 337 | version = "1.0.13" 338 | source = "registry+https://github.com/rust-lang/crates.io-index" 339 | checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" 340 | 341 | [[package]] 342 | name = "scopeguard" 343 | version = "1.1.0" 344 | source = "registry+https://github.com/rust-lang/crates.io-index" 345 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 346 | 347 | [[package]] 348 | name = "serde" 349 | version = "1.0.160" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c" 352 | dependencies = [ 353 | "serde_derive", 354 | ] 355 | 356 | [[package]] 357 | name = "serde_derive" 358 | version = "1.0.160" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df" 361 | dependencies = [ 362 | "proc-macro2", 363 | "quote", 364 | "syn 2.0.15", 365 | ] 366 | 367 | [[package]] 368 | name = "serde_json" 369 | version = "1.0.96" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" 372 | dependencies = [ 373 | "itoa", 374 | "ryu", 375 | "serde", 376 | ] 377 | 378 | [[package]] 379 | name = "shlex" 380 | version = "1.1.0" 381 | source = "registry+https://github.com/rust-lang/crates.io-index" 382 | checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" 383 | 384 | [[package]] 385 | name = "smallvec" 386 | version = "1.10.0" 387 | source = "registry+https://github.com/rust-lang/crates.io-index" 388 | checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" 389 | 390 | [[package]] 391 | name = "syn" 392 | version = "1.0.109" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 395 | dependencies = [ 396 | "proc-macro2", 397 | "quote", 398 | "unicode-ident", 399 | ] 400 | 401 | [[package]] 402 | name = "syn" 403 | version = "2.0.15" 404 | source = "registry+https://github.com/rust-lang/crates.io-index" 405 | checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" 406 | dependencies = [ 407 | "proc-macro2", 408 | "quote", 409 | "unicode-ident", 410 | ] 411 | 412 | [[package]] 413 | name = "thiserror" 414 | version = "1.0.40" 415 | source = "registry+https://github.com/rust-lang/crates.io-index" 416 | checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" 417 | dependencies = [ 418 | "thiserror-impl", 419 | ] 420 | 421 | [[package]] 422 | name = "thiserror-impl" 423 | version = "1.0.40" 424 | source = "registry+https://github.com/rust-lang/crates.io-index" 425 | checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" 426 | dependencies = [ 427 | "proc-macro2", 428 | "quote", 429 | "syn 2.0.15", 430 | ] 431 | 432 | [[package]] 433 | name = "unicode-ident" 434 | version = "1.0.8" 435 | source = "registry+https://github.com/rust-lang/crates.io-index" 436 | checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" 437 | 438 | [[package]] 439 | name = "unicode-width" 440 | version = "0.1.10" 441 | source = "registry+https://github.com/rust-lang/crates.io-index" 442 | checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" 443 | 444 | [[package]] 445 | name = "wasi" 446 | version = "0.11.0+wasi-snapshot-preview1" 447 | source = "registry+https://github.com/rust-lang/crates.io-index" 448 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 449 | 450 | [[package]] 451 | name = "wasm-encoder" 452 | version = "0.26.0" 453 | source = "registry+https://github.com/rust-lang/crates.io-index" 454 | checksum = "d05d0b6fcd0aeb98adf16e7975331b3c17222aa815148f5b976370ce589d80ef" 455 | dependencies = [ 456 | "leb128", 457 | ] 458 | 459 | [[package]] 460 | name = "wasmedge-macro" 461 | version = "0.3.0" 462 | source = "registry+https://github.com/rust-lang/crates.io-index" 463 | checksum = "6f159a9a7d3d2301de2fc9cb88ad3459af9e95cbd5a0f57437efccc2b572a027" 464 | dependencies = [ 465 | "proc-macro2", 466 | "quote", 467 | "syn 1.0.109", 468 | ] 469 | 470 | [[package]] 471 | name = "wasmedge-sdk" 472 | version = "0.8.1" 473 | source = "registry+https://github.com/rust-lang/crates.io-index" 474 | checksum = "4e1a816fe1c063bde3061fd651247571aea3146f0b86858da2ea240c65180a4d" 475 | dependencies = [ 476 | "anyhow", 477 | "num-derive", 478 | "num-traits", 479 | "thiserror", 480 | "wasmedge-macro", 481 | "wasmedge-sys", 482 | "wasmedge-types", 483 | "wat", 484 | ] 485 | 486 | [[package]] 487 | name = "wasmedge-sys" 488 | version = "0.13.1" 489 | source = "registry+https://github.com/rust-lang/crates.io-index" 490 | checksum = "f96d8d8ad1e95ef156fdc676b9cffcd88a5522b27c31aa28f2bb9e423bec95ea" 491 | dependencies = [ 492 | "bindgen", 493 | "cmake", 494 | "lazy_static", 495 | "libc", 496 | "parking_lot", 497 | "paste", 498 | "rand", 499 | "thiserror", 500 | "wasmedge-macro", 501 | "wasmedge-types", 502 | "wat", 503 | ] 504 | 505 | [[package]] 506 | name = "wasmedge-types" 507 | version = "0.4.1" 508 | source = "registry+https://github.com/rust-lang/crates.io-index" 509 | checksum = "aab1f57fd27746277379ab012475fa77679f91cc3bec1fa7b09b1628c5fb0339" 510 | dependencies = [ 511 | "thiserror", 512 | "wat", 513 | ] 514 | 515 | [[package]] 516 | name = "wast" 517 | version = "57.0.0" 518 | source = "registry+https://github.com/rust-lang/crates.io-index" 519 | checksum = "6eb0f5ed17ac4421193c7477da05892c2edafd67f9639e3c11a82086416662dc" 520 | dependencies = [ 521 | "leb128", 522 | "memchr", 523 | "unicode-width", 524 | "wasm-encoder", 525 | ] 526 | 527 | [[package]] 528 | name = "wat" 529 | version = "1.0.63" 530 | source = "registry+https://github.com/rust-lang/crates.io-index" 531 | checksum = "ab9ab0d87337c3be2bb6fc5cd331c4ba9fd6bcb4ee85048a0dd59ed9ecf92e53" 532 | dependencies = [ 533 | "wast", 534 | ] 535 | 536 | [[package]] 537 | name = "winapi" 538 | version = "0.3.9" 539 | source = "registry+https://github.com/rust-lang/crates.io-index" 540 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 541 | dependencies = [ 542 | "winapi-i686-pc-windows-gnu", 543 | "winapi-x86_64-pc-windows-gnu", 544 | ] 545 | 546 | [[package]] 547 | name = "winapi-i686-pc-windows-gnu" 548 | version = "0.4.0" 549 | source = "registry+https://github.com/rust-lang/crates.io-index" 550 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 551 | 552 | [[package]] 553 | name = "winapi-x86_64-pc-windows-gnu" 554 | version = "0.4.0" 555 | source = "registry+https://github.com/rust-lang/crates.io-index" 556 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 557 | 558 | [[package]] 559 | name = "windows-sys" 560 | version = "0.45.0" 561 | source = "registry+https://github.com/rust-lang/crates.io-index" 562 | checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" 563 | dependencies = [ 564 | "windows-targets", 565 | ] 566 | 567 | [[package]] 568 | name = "windows-targets" 569 | version = "0.42.2" 570 | source = "registry+https://github.com/rust-lang/crates.io-index" 571 | checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" 572 | dependencies = [ 573 | "windows_aarch64_gnullvm", 574 | "windows_aarch64_msvc", 575 | "windows_i686_gnu", 576 | "windows_i686_msvc", 577 | "windows_x86_64_gnu", 578 | "windows_x86_64_gnullvm", 579 | "windows_x86_64_msvc", 580 | ] 581 | 582 | [[package]] 583 | name = "windows_aarch64_gnullvm" 584 | version = "0.42.2" 585 | source = "registry+https://github.com/rust-lang/crates.io-index" 586 | checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" 587 | 588 | [[package]] 589 | name = "windows_aarch64_msvc" 590 | version = "0.42.2" 591 | source = "registry+https://github.com/rust-lang/crates.io-index" 592 | checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" 593 | 594 | [[package]] 595 | name = "windows_i686_gnu" 596 | version = "0.42.2" 597 | source = "registry+https://github.com/rust-lang/crates.io-index" 598 | checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" 599 | 600 | [[package]] 601 | name = "windows_i686_msvc" 602 | version = "0.42.2" 603 | source = "registry+https://github.com/rust-lang/crates.io-index" 604 | checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" 605 | 606 | [[package]] 607 | name = "windows_x86_64_gnu" 608 | version = "0.42.2" 609 | source = "registry+https://github.com/rust-lang/crates.io-index" 610 | checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" 611 | 612 | [[package]] 613 | name = "windows_x86_64_gnullvm" 614 | version = "0.42.2" 615 | source = "registry+https://github.com/rust-lang/crates.io-index" 616 | checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" 617 | 618 | [[package]] 619 | name = "windows_x86_64_msvc" 620 | version = "0.42.2" 621 | source = "registry+https://github.com/rust-lang/crates.io-index" 622 | checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" 623 | 624 | [[package]] 625 | name = "witc-abi" 626 | version = "0.2.1" 627 | dependencies = [ 628 | "once_cell", 629 | "wasmedge-sdk", 630 | ] 631 | -------------------------------------------------------------------------------- /test-project/import_wasm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "import_wasm" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = [ "cdylib" ] 8 | 9 | [dependencies] 10 | serde = { version = "1.0", features = ["derive"] } 11 | serde_json = "*" 12 | 13 | invoke-witc = { path = "../../bindings/rust/invoke-witc" } 14 | witc-abi = { path = "../../bindings/rust/witc-abi" } 15 | -------------------------------------------------------------------------------- /test-project/import_wasm/src/lib.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | invoke_witc::wit_instance!(import(export1 = "../export1.wit")); 3 | invoke_witc::wit_instance!(import(export2 = "../export2.wit")); 4 | 5 | #[no_mangle] 6 | pub unsafe extern "C" fn run(a: u32) -> u32 { 7 | return identity_two(identity_one(a)); 8 | } 9 | -------------------------------------------------------------------------------- /test-project/src/main.rs: -------------------------------------------------------------------------------- 1 | use wasmedge_sdk::{ 2 | config::{CommonConfigOptions, ConfigBuilder, HostRegistrationConfigOptions}, 3 | Vm, VmBuilder, WasmEdgeResult, 4 | }; 5 | invoke_witc::wit_runtime!(export(export1 = "export1.wit")); 6 | invoke_witc::wit_runtime!(export(export2 = "export2.wit")); 7 | 8 | fn identity_one(a: u32) -> u32 { 9 | a 10 | } 11 | fn identity_two(a: u32) -> u32 { 12 | a 13 | } 14 | 15 | fn main() { 16 | let vm = build_vm().expect("vm failed"); 17 | 18 | let result = vm 19 | .run_func( 20 | Some("import_wasm"), 21 | "run", 22 | vec![wasmedge_sdk::WasmValue::from_i32(1)], 23 | ) 24 | .expect("test"); 25 | 26 | println!("{}", result[0].to_i32()); 27 | } 28 | 29 | fn build_vm() -> WasmEdgeResult { 30 | let config = ConfigBuilder::new(CommonConfigOptions::default()) 31 | .with_host_registration_config(HostRegistrationConfigOptions::default().wasi(true)) 32 | .build()?; 33 | 34 | VmBuilder::new() 35 | .with_config(config) 36 | .build()? 37 | .register_import_module(witc_abi::runtime::component_model_wit_object()?)? 38 | .register_import_module(export1::wit_import_object()?)? 39 | .register_import_module(export2::wit_import_object()?)? 40 | .register_module_from_file( 41 | "import_wasm", 42 | "import_wasm/target/wasm32-wasi/release/import_wasm.wasm", 43 | ) 44 | } 45 | 46 | #[test] 47 | fn export1() { 48 | let vm = build_vm().unwrap(); 49 | 50 | let result = vm 51 | .run_func( 52 | Some("import_wasm"), 53 | "run", 54 | vec![wasmedge_sdk::WasmValue::from_i32(1)], 55 | ) 56 | .unwrap(); 57 | 58 | assert!(result[0].to_i32() == 1); 59 | } 60 | -------------------------------------------------------------------------------- /test/Spec.hs: -------------------------------------------------------------------------------- 1 | {-# OPTIONS_GHC -F -pgmF hspec-discover #-} 2 | -------------------------------------------------------------------------------- /test/Wit/CheckSpec.hs: -------------------------------------------------------------------------------- 1 | module Wit.CheckSpec (spec) where 2 | 3 | import Control.Monad.Except 4 | import System.FilePath 5 | import Test.Hspec 6 | import Wit.Check 7 | 8 | specFile :: FilePath -> IO () 9 | specFile file = do 10 | r <- runExceptT $ checkFile (takeDirectory file) (takeFileName file) 11 | case r of 12 | Left _ -> return () 13 | Right _ -> expectationFailure "checker should find some errors" 14 | 15 | spec :: Spec 16 | spec = describe "check wit" $ do 17 | context "check definition" $ do 18 | it "`bad-types` should report undefined type" $ do 19 | specFile "test/data/bad-types.wit" 20 | it "`bad-types2` should report undefined type" $ do 21 | specFile "test/data/bad-types2.wit" 22 | it "`func-using-missing-type` should report undefined type" $ do 23 | specFile "test/data/func-using-missing-type.wit" 24 | context "check dependencies" $ do 25 | it "should report no such file" $ do 26 | specFile "test/data/bad-import.wit" 27 | it "should report no such definition in the dependency" $ do 28 | specFile "test/data/bad-import2.wit" 29 | context "cyclic import" $ do 30 | it "should report cyclic import" $ do 31 | specFile "test/data/test/data/cycle-import-a.wit" 32 | -------------------------------------------------------------------------------- /test/Wit/ParserSpec.hs: -------------------------------------------------------------------------------- 1 | module Wit.ParserSpec (spec) where 2 | 3 | import Test.Hspec 4 | import Test.Hspec.Megaparsec 5 | import Text.Megaparsec 6 | import Wit.Parser 7 | 8 | spec :: Spec 9 | spec = describe "parse wit" $ do 10 | context "use statement" $ do 11 | it "use {a, b, c} from mod" $ do 12 | parse pUse "" `shouldSucceedOn` "use { a, b, c } from mod" 13 | it "use * from mod" $ do 14 | parse pUse "" `shouldSucceedOn` "use * from mod" 15 | context "definitions" $ do 16 | it "resource" $ do 17 | let input = 18 | unlines 19 | [ "resource configs {", 20 | " // Obtain an app config store, identifiable through a resource descriptor", 21 | " static open: func(name: string) -> expected", 22 | " // Get an app configuration given a config store, and an identifiable key", 23 | " get: func(key: string) -> expected", 24 | " // Set an app configuration given a config store, an identifiable key, and its' value", 25 | " set: func(key: string, value: payload) -> expected", 26 | "}" 27 | ] 28 | parse pDefinition "" `shouldSucceedOn` input 29 | it "function" $ do 30 | parse pDefinition "" `shouldSucceedOn` "handle-http: func(req: request) -> expected" 31 | context "type definitions" $ do 32 | it "type definition: enum" $ do 33 | parse pDefinition "" 34 | `shouldSucceedOn` unlines 35 | [ "enum method {", 36 | " get,", 37 | " post,", 38 | " put,", 39 | " delete,", 40 | " patch,", 41 | " head,", 42 | " options,", 43 | "}" 44 | ] 45 | -- type definition check 46 | it "type defintion: record" $ do 47 | parse pDefinition "" `shouldSucceedOn` "record person { name: string, email: option }" 48 | parse pDefinition "" `shouldSucceedOn` "record person { name: string, email: option, }" 49 | it "type defintion: variant" $ do 50 | parse pDefinition "" 51 | `shouldSucceedOn` unlines 52 | [ "variant error {", 53 | " error-with-description(string)", 54 | "}" 55 | ] 56 | it "type defintion: type alias" $ do 57 | parse pDefinition "" `shouldSucceedOn` "type payload = list" 58 | -- record 59 | it "record: oneline" $ do 60 | let input = "record person { name: string, email: option }" 61 | parse pRecord "" `shouldSucceedOn` input 62 | it "record: cross-line" $ do 63 | let input = 64 | unlines 65 | [ "record person {", 66 | " name: string,", 67 | " email: option,", 68 | " data: option,", 69 | "}" 70 | ] 71 | parse pRecord "" `shouldSucceedOn` input 72 | it "record: no fields" $ do 73 | let input = "record person {}" 74 | parse pRecord "" `shouldSucceedOn` input 75 | -- type alias 76 | it "type alias: payload is list" $ do 77 | let input = "type payload = list" 78 | parse pTypeAlias "" `shouldSucceedOn` input 79 | it "type alias: map is a pair list" $ do 80 | let input = "type map = list>" 81 | parse pTypeAlias "" `shouldSucceedOn` input 82 | -- variant 83 | it "variant: error" $ do 84 | let input = 85 | unlines 86 | [ "variant error {", 87 | " error-with-description(string)", 88 | "}" 89 | ] 90 | parse pVariant "" `shouldSucceedOn` input 91 | it "enum: basic" $ do 92 | let input = 93 | unlines 94 | [ "enum method {", 95 | " get,", 96 | " post,", 97 | " put,", 98 | " delete,", 99 | " patch,", 100 | " head,", 101 | " options,", 102 | "}" 103 | ] 104 | parse pEnum "" `shouldSucceedOn` input 105 | -------------------------------------------------------------------------------- /test/data/bad-import.wit: -------------------------------------------------------------------------------- 1 | use * from nothisfile 2 | -------------------------------------------------------------------------------- /test/data/bad-import2.wit: -------------------------------------------------------------------------------- 1 | use { abc, def } from good 2 | -------------------------------------------------------------------------------- /test/data/bad-types.wit: -------------------------------------------------------------------------------- 1 | type a = b 2 | type c = d 3 | -------------------------------------------------------------------------------- /test/data/bad-types2.wit: -------------------------------------------------------------------------------- 1 | variant nat { 2 | zero, 3 | suc(nat2) 4 | } 5 | -------------------------------------------------------------------------------- /test/data/cycle-import-a.wit: -------------------------------------------------------------------------------- 1 | use * from cycle-import-b 2 | -------------------------------------------------------------------------------- /test/data/cycle-import-b.wit: -------------------------------------------------------------------------------- 1 | use * from cycle-import-a 2 | -------------------------------------------------------------------------------- /test/data/func-using-missing-type.wit: -------------------------------------------------------------------------------- 1 | foo: func(a : A) -> B 2 | -------------------------------------------------------------------------------- /test/data/good.wit: -------------------------------------------------------------------------------- 1 | type int32 = s32 2 | -------------------------------------------------------------------------------- /witc.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: 1.12 2 | 3 | -- This file has been generated from package.yaml by hpack version 0.35.1. 4 | -- 5 | -- see: https://github.com/sol/hpack 6 | 7 | name: witc 8 | version: 0.4.0.0 9 | description: Please see the README on GitHub at 10 | homepage: https://github.com/second-state/witc#readme 11 | bug-reports: https://github.com/second-state/witc/issues 12 | author: secondstate 13 | maintainer: dannypsnl@secondstate.io, 14 | dm4@users.noreply.github.com 15 | copyright: 2023 secondstate 16 | license: Apache-2.0 17 | license-file: LICENSE 18 | build-type: Simple 19 | extra-source-files: 20 | README.md 21 | CHANGELOG.md 22 | 23 | source-repository head 24 | type: git 25 | location: https://github.com/second-state/witc 26 | 27 | library 28 | exposed-modules: 29 | Wit.Ast 30 | Wit.Check 31 | Wit.Config 32 | Wit.Gen 33 | Wit.Gen.Export 34 | Wit.Gen.Import 35 | Wit.Gen.Normalization 36 | Wit.Gen.Plugin 37 | Wit.Gen.Type 38 | Wit.Parser 39 | Wit.TypeValue 40 | other-modules: 41 | Paths_witc 42 | hs-source-dirs: 43 | src 44 | default-extensions: 45 | FlexibleContexts 46 | ImportQualifiedPost 47 | DuplicateRecordFields 48 | OverloadedRecordDot 49 | LambdaCase 50 | ViewPatterns 51 | ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints 52 | build-depends: 53 | algebraic-graphs >=0.6 54 | , base >=4.16 && <5 55 | , containers >=0.6 56 | , directory >=1.3 57 | , filepath >=1.4 58 | , megaparsec >=9.2 59 | , mtl >=2.2 60 | , optparse-applicative >=0.16 61 | , prettyprinter >=1.7 62 | , prettyprinter-ansi-terminal >=1.1.3 63 | , primitive >=0.7 64 | , template-haskell >=2.18 65 | , text >=1.2 66 | , transformers >=0.5 67 | default-language: Haskell2010 68 | 69 | executable witc 70 | main-is: Main.hs 71 | other-modules: 72 | Paths_witc 73 | hs-source-dirs: 74 | app 75 | default-extensions: 76 | FlexibleContexts 77 | ImportQualifiedPost 78 | DuplicateRecordFields 79 | OverloadedRecordDot 80 | LambdaCase 81 | ViewPatterns 82 | ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N 83 | build-depends: 84 | algebraic-graphs >=0.6 85 | , base >=4.16 && <5 86 | , containers >=0.6 87 | , directory >=1.3 88 | , filepath >=1.4 89 | , megaparsec >=9.2 90 | , mtl >=2.2 91 | , optparse-applicative >=0.16 92 | , prettyprinter >=1.7 93 | , prettyprinter-ansi-terminal >=1.1.3 94 | , primitive >=0.7 95 | , template-haskell >=2.18 96 | , text >=1.2 97 | , transformers >=0.5 98 | , witc 99 | default-language: Haskell2010 100 | 101 | test-suite witc-test 102 | type: exitcode-stdio-1.0 103 | main-is: Spec.hs 104 | other-modules: 105 | Wit.CheckSpec 106 | Wit.ParserSpec 107 | Paths_witc 108 | hs-source-dirs: 109 | test 110 | default-extensions: 111 | FlexibleContexts 112 | ImportQualifiedPost 113 | DuplicateRecordFields 114 | OverloadedRecordDot 115 | LambdaCase 116 | ViewPatterns 117 | ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N 118 | build-depends: 119 | algebraic-graphs >=0.6 120 | , base >=4.16 && <5 121 | , containers >=0.6 122 | , directory >=1.3 123 | , filepath >=1.4 124 | , hspec 125 | , hspec-megaparsec 126 | , megaparsec >=9.2 127 | , mtl >=2.2 128 | , optparse-applicative >=0.16 129 | , prettyprinter >=1.7 130 | , prettyprinter-ansi-terminal >=1.1.3 131 | , primitive >=0.7 132 | , template-haskell >=2.18 133 | , text >=1.2 134 | , transformers >=0.5 135 | , witc 136 | default-language: Haskell2010 137 | --------------------------------------------------------------------------------