├── .github └── workflows │ └── main.yml ├── .gitignore ├── .gitmodules ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── benches ├── benches.rs └── fixtures │ └── dodrio-todomvc.wasm ├── crates ├── fuzz-utils │ ├── Cargo.toml │ ├── before-after.sh │ └── src │ │ └── lib.rs ├── macro │ ├── Cargo.toml │ ├── LICENSE-APACHE │ ├── LICENSE-MIT │ └── src │ │ └── lib.rs ├── tests-utils │ ├── Cargo.toml │ └── src │ │ └── lib.rs └── tests │ ├── Cargo.toml │ ├── build.rs │ ├── src │ ├── check.rs │ └── lib.rs │ └── tests │ ├── custom_sections.rs │ ├── function_imports.rs │ ├── function_imports │ └── pets_function_imports.wat │ ├── invalid.rs │ ├── invalid │ ├── if-with-no-else.wat │ ├── multi-value-0.wat │ ├── multi-value-1.wat │ ├── multi-value-2.wat │ └── multi-value-3.wat │ ├── ir │ └── bulk-memory.wat │ ├── round_trip.rs │ ├── round_trip │ ├── anyref1.wat │ ├── anyref2.wat │ ├── anyref3.wat │ ├── atomic.wat │ ├── block.wat │ ├── br-label.wat │ ├── br-table-2.wat │ ├── br-to-func.wat │ ├── br_table.wat │ ├── bulk-memory.wat │ ├── call-two-params.wat │ ├── call.wat │ ├── call_2.wat │ ├── const.wat │ ├── count-to-ten.wat │ ├── drop.wat │ ├── elem-segments-1.wat │ ├── elem-segments-2.wat │ ├── elem-segments-3.wat │ ├── entry-block-type.wat │ ├── fac-multi-value.wat │ ├── fac.wat │ ├── fuzz-0.wat │ ├── gc_keep_used_funcs.wat │ ├── gc_keep_used_globals.wat │ ├── gc_keep_used_imports.wat │ ├── gc_keep_used_memories.wat │ ├── gc_keep_used_tables.wat │ ├── gc_unused_block.wat │ ├── gc_unused_funcs.wat │ ├── gc_unused_funcs_2.wat │ ├── gc_unused_global.wat │ ├── gc_unused_imports.wat │ ├── gc_unused_memories.wat │ ├── gc_unused_tables.wat │ ├── gc_unused_types.wat │ ├── global-init.wat │ ├── globals.wat │ ├── if-no-else.wat │ ├── if_else.wat │ ├── if_else_2.wat │ ├── import-func.wat │ ├── import-gc.wat │ ├── import-global.wat │ ├── import-memory.wat │ ├── import-table.wat │ ├── inc.wat │ ├── keep-elem-segments.wat │ ├── local-get.wat │ ├── local-tee.wat │ ├── loop.wat │ ├── loop2.wat │ ├── many_funcs.wat │ ├── mem.wat │ ├── memory-grow.wat │ ├── memory-init.wast │ ├── memory-size.wat │ ├── multi-0.wat │ ├── multi-1.wat │ ├── multi-2.wat │ ├── multi-3.wat │ ├── name-section.wat │ ├── nop.wat │ ├── not-tree-like.wat │ ├── params.wat │ ├── refer-forward.wat │ ├── return.wat │ ├── return_2.wat │ ├── return_call.wat │ ├── return_call_indirect.wat │ ├── select.wat │ ├── simd.wat │ ├── start.wat │ ├── stuff-after-loop-2.wat │ ├── stuff-after-loop.wat │ ├── table-init.wast │ ├── type-deduplicate.wat │ ├── unreachable.wat │ ├── unreachable_2.wat │ ├── unreachable_3.wat │ ├── unreachable_4.wat │ ├── used-local-in-local.wat │ └── used-local-set.wat │ ├── spec-tests.rs │ ├── valid.rs │ └── valid │ ├── block.wat │ ├── call.wat │ ├── const.wat │ ├── count-to-ten.wat │ ├── drop.wat │ ├── fac-multi-value.wat │ ├── fac.wat │ ├── if_else.wat │ ├── inc.wat │ ├── loop.wat │ ├── min-fac.wat │ ├── select.wat │ ├── stuff-after-loop-2.wat │ └── stuff-after-loop.wat ├── examples ├── build-wasm-from-scratch.rs ├── parse.rs └── round-trip.rs ├── fuzz ├── .gitignore ├── Cargo.toml ├── README.md └── fuzz_targets │ ├── raw.rs │ ├── wasm-opt-ttf.rs │ └── watgen.rs ├── publish.sh └── src ├── arena_set.rs ├── const_expr.rs ├── dot.rs ├── emit.rs ├── error.rs ├── function_builder.rs ├── ir ├── mod.rs └── traversals.rs ├── lib.rs ├── map.rs ├── module ├── config.rs ├── custom.rs ├── data.rs ├── debug │ ├── dwarf.rs │ ├── expression.rs │ ├── mod.rs │ └── units.rs ├── elements.rs ├── exports.rs ├── functions │ ├── local_function │ │ ├── context.rs │ │ ├── emit.rs │ │ └── mod.rs │ └── mod.rs ├── globals.rs ├── imports.rs ├── locals.rs ├── memories.rs ├── mod.rs ├── producers.rs ├── tables.rs └── types.rs ├── parse.rs ├── passes ├── gc.rs ├── mod.rs └── used.rs ├── tombstone_arena.rs └── ty.rs /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | test: 6 | name: Test 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | rust: [stable, beta, nightly] 11 | steps: 12 | - uses: actions/checkout@v4 13 | with: 14 | submodules: true 15 | - name: Install Rust 16 | run: rustup update ${{ matrix.rust }} && rustup default ${{ matrix.rust }} 17 | - uses: cargo-bins/cargo-binstall@main 18 | - name: Install wasm-tools (need json-from-wast subcommand) 19 | run: cargo binstall wasm-tools -y 20 | - name: Install wabt 21 | run: | 22 | set -e 23 | curl -L https://github.com/WebAssembly/wabt/releases/download/1.0.35/wabt-1.0.35-ubuntu-20.04.tar.gz | tar xzf - 24 | echo "`pwd`/wabt-1.0.35/bin" > $GITHUB_PATH 25 | - name: Install binaryen 26 | run: | 27 | set -e 28 | curl -L https://github.com/WebAssembly/binaryen/releases/download/version_117/binaryen-version_117-x86_64-linux.tar.gz | tar xzf - 29 | echo "`pwd`/binaryen-version_117/bin" > $GITHUB_PATH 30 | - run: cargo build --all 31 | - run: cargo test --all 32 | - run: cargo check --benches 33 | - run: cargo test --features parallel 34 | - run: cargo test --features parallel --manifest-path crates/tests/Cargo.toml 35 | 36 | fuzz_crate: 37 | name: Fuzz Crate 38 | runs-on: ubuntu-latest 39 | steps: 40 | - uses: actions/checkout@v4 41 | - name: Install Rust 42 | run: rustup update stable && rustup default stable 43 | - name: Install wabt 44 | run: | 45 | set -e 46 | curl -L https://github.com/WebAssembly/wabt/releases/download/1.0.35/wabt-1.0.35-ubuntu-20.04.tar.gz | tar xzf - 47 | echo "`pwd`/wabt-1.0.35/bin" > $GITHUB_PATH 48 | - name: Install binaryen 49 | run: | 50 | set -e 51 | curl -L https://github.com/WebAssembly/binaryen/releases/download/version_117/binaryen-version_117-x86_64-linux.tar.gz | tar xzf - 52 | echo "`pwd`/binaryen-version_117/bin" > $GITHUB_PATH 53 | - name: Run fuzzer 54 | run: cargo test -p walrus-fuzz-utils > fuzz.log || (tail -n 1000 fuzz.log && exit 1) 55 | env: 56 | # 300 seconds = 5 minutes. 57 | WALRUS_FUZZ_TIMEOUT: 300 58 | 59 | fuzz: 60 | name: Fuzz 61 | runs-on: ubuntu-latest 62 | strategy: 63 | matrix: 64 | test: [watgen, wasm-opt-ttf, raw] 65 | steps: 66 | - uses: actions/checkout@v4 67 | - name: Install Rust 68 | run: rustup update nightly && rustup default nightly 69 | - run: cargo install cargo-fuzz 70 | - name: Install wabt 71 | run: | 72 | set -e 73 | curl -L https://github.com/WebAssembly/wabt/releases/download/1.0.35/wabt-1.0.35-ubuntu-20.04.tar.gz | tar xzf - 74 | echo "`pwd`/wabt-1.0.35/bin" > $GITHUB_PATH 75 | - name: Install binaryen 76 | run: | 77 | set -e 78 | curl -L https://github.com/WebAssembly/binaryen/releases/download/version_117/binaryen-version_117-x86_64-linux.tar.gz | tar xzf - 79 | echo "`pwd`/binaryen-version_117/bin" > $GITHUB_PATH 80 | - name: Run fuzzer 81 | run: | 82 | cargo fuzz run ${{ matrix.test }} -- -max_total_time=300 -rss_limit_mb=4096 > fuzz.log 2>&1 || (tail -n 1000 fuzz.log && exit 1) 83 | 84 | rustfmt: 85 | name: Rustfmt 86 | runs-on: ubuntu-latest 87 | steps: 88 | - uses: actions/checkout@v4 89 | - name: Install Rust 90 | run: rustup update stable && rustup default stable && rustup component add rustfmt 91 | - run: cargo fmt -- --check 92 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | 5 | crates/tests/tests/**/*.wasm 6 | crates/tests/tests/**/*.dot 7 | crates/tests/tests/**/*.out 8 | crates/tests/tests/**/*.out.wasm 9 | crates/tests/tests/**/*.out.wat 10 | 11 | .vscode 12 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "walrus-tests/tests/spec-tests"] 2 | path = crates/tests/tests/spec-tests 3 | url = https://github.com/WebAssembly/testsuite 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # `walrus` Change Log 2 | 3 | -------------------------------------------------------------------------------- 4 | 5 | ## Unreleased 6 | 7 | Released YYYY-MM-DD. 8 | 9 | ### Added 10 | 11 | * TODO (or remove section if none) 12 | 13 | ### Changed 14 | 15 | * TODO (or remove section if none) 16 | 17 | ### Deprecated 18 | 19 | * TODO (or remove section if none) 20 | 21 | ### Removed 22 | 23 | * TODO (or remove section if none) 24 | 25 | ### Fixed 26 | 27 | * TODO (or remove section if none) 28 | 29 | ### Security 30 | 31 | * TODO (or remove section if none) 32 | 33 | -------------------------------------------------------------------------------- 34 | 35 | ## 0.15.0 36 | 37 | Released 2020-02-03. 38 | 39 | ### Added 40 | 41 | * Added support for typed `select` instructions. 42 | 43 | -------------------------------------------------------------------------------- 44 | 45 | ## 0.14.0 46 | 47 | -------------------------------------------------------------------------------- 48 | 49 | ## 0.13.0 50 | 51 | -------------------------------------------------------------------------------- 52 | 53 | ## 0.12.0 54 | 55 | Released 2019-09-10. 56 | 57 | ### Added 58 | 59 | * Added support for multi-value Wasm! 60 | 61 | * Added `ModuleExports::get_exported_{func, table, memory, global}` helper 62 | functions to get an export by the id of the thing that it is exporting (if 63 | any). 64 | 65 | * Added fuzz testing with libFuzzer and `cargo fuzz`. 66 | 67 | ### Changed 68 | 69 | * No longer using the "derive" feature from `failure`, which should result in 70 | slimmer dependency graphs and faster builds. 71 | 72 | * `Module::emit_wasm` is no longer fallible. It never actually did ever return 73 | an `Err` and now the type signature reflects that. 74 | 75 | -------------------------------------------------------------------------------- 76 | 77 | ## 0.11.0 78 | 79 | Released 2019-08-13. 80 | 81 | ### Added 82 | 83 | * `walrus::Module::write_graphviz_dot`: You can now render a whole Wasm module 84 | as a GraphViz Dot file (previously you could only do one function at a time) 85 | and it will also show the relationships between all the various Wasm 86 | structures (previously it only showed the relationships between instructions). 87 | 88 | ### Changed 89 | 90 | * The intermediate representation for instructions (`walrus::ir::*`) has been 91 | overhauled. `Expr` has been renamed to `Instr`, and an operator no longer 92 | points to its operands as nested children in the AST. Instead of representing 93 | every instruction as a node in an AST, now there is a tree of instruction 94 | sequences. An instruction sequence is a vector of `Instr`s that relate to each 95 | other implicitly via their effect on the stack. A nested `block ... end`, 96 | `loop ... end`, or `if ... else ... end` form new nested instruction 97 | sequences. 98 | 99 | * The `Visitor` and `VisitorMut` traits and traversing the IR has also been 100 | overhauled: 101 | 102 | * Visitors are no longer recursive, and should never recursively call 103 | `self.visit_foo()` from inside `self.visit_bar()`. Not in the default 104 | provided trait methods and not in any user-written overrides of those 105 | trait methods. 106 | 107 | * There are now *traversal functions* which take a visitor, a 108 | `LocalFunction`, and a start `InstrSeqId`, and then perform some kind of 109 | traversal over the function's IR from the given start sequence. These 110 | traversal functions are *not* recursive, and are implemented with explicit 111 | work lists and while loops. This avoids blowing the stack on deeply nested 112 | Wasm inputs. Although we can still OOM, we leave fixing that to future 113 | PRs. Right now there are only two traversals, because that is all we've 114 | needed so far: an in-order DFS for immutable visitors (needs to be 115 | in-order so we can encode instructions in the right order) and a pre-order 116 | DFS for mutable visitors (pre-order is the easiest traversal to implement 117 | iteratively). We can add more traversals as we need them. 118 | 119 | ### Removed 120 | 121 | * The `Dot` trait has been made internal. Use the new 122 | `walrus::Module::write_graphviz_dot` method instead. 123 | 124 | * The `Visit` trait is no longer exported in the public API, and has been made 125 | internal. Use the new traversal functions instead (`walrus::ir::dfs_in_order` 126 | and `walrus::ir::dfs_pre_order_mut`). 127 | 128 | -------------------------------------------------------------------------------- 129 | 130 | ## 0.10.0 131 | 132 | -------------------------------------------------------------------------------- 133 | 134 | ## 0.9.0 135 | 136 | -------------------------------------------------------------------------------- 137 | 138 | ## 0.8.0 139 | 140 | Released 2019-06-05. 141 | 142 | ### Added 143 | 144 | * Added `ModuleExports::iter_mut` for iterating over exclusive references to a 145 | module's exports. 146 | 147 | * Added a `ModuleConfig::on_parse` hook, which has access to a map from indices 148 | in the original Wasm binary to the newly assigned walrus IDs. This is a good 149 | time to parse custom sections that reference functions or types or whatever by 150 | index. 151 | 152 | * The `TableKind` enum now has various `unwrap_*` helpers to get a particular 153 | variant's inner data or else panic. 154 | 155 | * Added `ModuleFunctions::by_name` to get a function ID by function name. 156 | 157 | ### Changed 158 | 159 | * The `CustomSection::data` trait method now has a new parameter: a map from 160 | walrus IDs to their indices in the new wasm binary we are emitting. This is 161 | useful for custom sections that reference functions or types or whatever by 162 | index. 163 | 164 | -------------------------------------------------------------------------------- 165 | 166 | ## 0.7.0 167 | 168 | Released 2019-05-17. 169 | 170 | ### Added 171 | 172 | * Added the `walrus::ModuleCustomSections` API for working with arbitrary custom 173 | sections, including and especially custom sections that `walrus` itself has no 174 | special knowledge of. This is exposed as the `customs` field of a 175 | `walrus::Module`. 176 | 177 | * Added the `Module::with_config` constructor method to create a default, empty 178 | module that uses the given configuration. 179 | 180 | ### Removed 181 | 182 | * The `walrus::Module::custom` vector of raw custom modules has been removed and 183 | is superceded by the new `walrus::ModuleCustomSections` API. If you were using 184 | this and the old `CustomSection` type, switch to use the `RawCustomSection` 185 | type with `ModuleCustomSections`. 186 | 187 | -------------------------------------------------------------------------------- 188 | 189 | ## 0.6.0 190 | 191 | Released 2019-05-02. 192 | 193 | ### Added 194 | 195 | * `ModuleConfig::parse_file` and `Module::parse_file_with_config` helper 196 | functions to easily parse a Wasm file from disk with a given configuration. 197 | 198 | ### Changed 199 | 200 | * `ModuleConfig::parse` takes `&self` instead of `&mut self` now. This was just 201 | an oversight / copy-past error before. 202 | 203 | -------------------------------------------------------------------------------- 204 | 205 | ## 0.5.0 206 | 207 | -------------------------------------------------------------------------------- 208 | 209 | ## 0.4.0 210 | 211 | -------------------------------------------------------------------------------- 212 | 213 | ## 0.3.0 214 | 215 | Released 2019-02-19. 216 | 217 | ### Added 218 | 219 | * Added support for the [reference 220 | types](https://github.com/WebAssembly/reference-types/blob/master/proposals/reference-types/Overview.md) 221 | wasm proposal. [#50](https://github.com/rustwasm/walrus/pull/50) 222 | * Can finish a `FunctionBuilder` with the relevant `&mut` parts of a module, 223 | rather than a whole `&mut Module`. This is useful when some parts of the 224 | module are mutably borrowed 225 | elsewhere. [#56](https://github.com/rustwasm/walrus/pull/56) 226 | * Can get a `FunctionBuilder` from an existing `LocalFunction` so you can build 227 | new expressions for the 228 | function. [#54](https://github.com/rustwasm/walrus/pull/54) 229 | * Added the ability to delete functions, imports, exports, etc. Usually it is 230 | easier to just let the builtin GCing emit only the necessary bits of the wasm 231 | binary, but manually deleting will remove the item from iterators over the 232 | module's parts. If you delete a thing, you are responsible for ensuring that 233 | nothing else is referencing it (eg there are no remaining calls to a function 234 | that you are deleting, etc). [#58](https://github.com/rustwasm/walrus/pull/58) 235 | * Added an `id` getter for 236 | `Import`s. [#59](https://github.com/rustwasm/walrus/pull/59) 237 | * Added a mutable iterator for tables in a 238 | module. [#59](https://github.com/rustwasm/walrus/pull/59) 239 | * Added a convenience function for getting the main function table for a 240 | module. [#57](https://github.com/rustwasm/walrus/pull/57) 241 | 242 | ### Changed 243 | 244 | * The `WithSideEffects` expression variant can have arbitrary stack-neutral side 245 | effects before its value now, in addition to after its 246 | value. [#55](https://github.com/rustwasm/walrus/pull/55) 247 | 248 | -------------------------------------------------------------------------------- 249 | 250 | ## 0.2.1 251 | 252 | Released 2019-02-14. 253 | 254 | ### Added 255 | 256 | * Added configuration options for controlling emission of the producers section 257 | 258 | -------------------------------------------------------------------------------- 259 | 260 | ## 0.2.0 261 | 262 | Released 2019-02-14. 263 | 264 | ### Added 265 | 266 | * Added configuration options for controlling emission of the DWARF and name 267 | custom sections. 268 | 269 | ### Changed 270 | 271 | * Changed the synthetic naming option from "generate_names" to 272 | "generate_synthetic_names_for_anonymous_items" to more accurately reflect what 273 | it does. 274 | 275 | -------------------------------------------------------------------------------- 276 | 277 | ## 0.1.0 278 | 279 | Released 2019-02-12. 280 | 281 | ### Added 282 | 283 | * Initial release! 284 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to `walrus` 2 | 3 | ## Build 4 | 5 | To build `walrus`, run: 6 | 7 | ``` 8 | cargo build 9 | ``` 10 | 11 | ## Test 12 | 13 | The tests rely on [WABT][] being installed on your system's `$PATH`, so make 14 | sure you have that first. 15 | 16 | Then run: 17 | 18 | ``` 19 | cargo test --all 20 | ``` 21 | 22 | [WABT]: https://github.com/WebAssembly/wabt 23 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Nick Fitzgerald "] 3 | edition = "2018" 4 | name = "walrus" 5 | version = "0.23.3" 6 | license = "MIT/Apache-2.0" 7 | readme = "README.md" 8 | categories = ["wasm"] 9 | repository = "https://github.com/rustwasm/walrus" 10 | homepage = "https://github.com/rustwasm/walrus" 11 | documentation = "https://docs.rs/walrus" 12 | description = """ 13 | A library for performing WebAssembly transformations 14 | """ 15 | 16 | [lib] 17 | path = "src/lib.rs" 18 | bench = false 19 | 20 | [[bench]] 21 | name = "benches" 22 | path = "benches/benches.rs" 23 | harness = false 24 | 25 | [dependencies] 26 | anyhow = "1.0" 27 | id-arena = "2.2.1" 28 | leb128 = "0.2.4" 29 | log = "0.4.8" 30 | rayon = { version = "1.1.0", optional = true } 31 | walrus-macro = { path = './crates/macro', version = '=0.22.0' } 32 | wasm-encoder = "0.214.0" 33 | wasmparser = "0.214.0" 34 | gimli = "0.26.0" 35 | 36 | [features] 37 | parallel = ['rayon', 'id-arena/rayon'] 38 | 39 | [dev-dependencies] 40 | env_logger = "0.11.0" 41 | criterion = "0.5.0" 42 | 43 | [workspace] 44 | members = [ 45 | "./crates/fuzz-utils", 46 | "./crates/macro", 47 | "./crates/tests", 48 | "./crates/tests-utils", 49 | ] 50 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Nick Fitzgerald 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |

Walrus 🌊🐘

4 | 5 | Walrus is a WebAssembly transformation library 6 | 7 |

8 | Crates.io version 9 | Download 10 | docs.rs docs 11 |

12 | 13 |

14 | API Docs 15 | | 16 | Contributing 17 | | 18 | Chat 19 |

20 | 21 | Built with 🦀🕸 by The Rust and WebAssembly Working Group 22 |
23 | 24 | ## About 25 | 26 | The `walrus` crate is a Rust library for performing WebAssembly transformations 27 | in a robust and ergonomic fashion. The crate is still in its early days but is 28 | currently used to power the [`wasm-bindgen`] CLI tool and its own internal 29 | transformations. 30 | 31 | [`wasm-bindgen`]: https://github.com/rustwasm/wasm-bindgen 32 | 33 | Using `walrus` will, in the long term, also allow transforming WebAssembly while 34 | preserving DWARF debug information to ensure that debugging the final module is 35 | just as nice as debugging the intermediate module. 36 | 37 | Stay tuned for more information in the future! 38 | 39 | ## Examples 40 | 41 | * Check out `examples/build-wasm-from-scratch.rs` for a quick intro to building 42 | a Wasm module from scratch with `walrus`. 43 | * Check out the [`wasm-snip`](https://github.com/rustwasm/wasm-snip) project for 44 | a relatively simple and self-contained but still Real World example of using 45 | `walrus`. 46 | 47 | ## License 48 | 49 | This project is licensed under either of 50 | 51 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or 52 | http://www.apache.org/licenses/LICENSE-2.0) 53 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or 54 | http://opensource.org/licenses/MIT) 55 | 56 | at your option. 57 | 58 | ## Contribution 59 | 60 | Unless you explicitly state otherwise, any contribution intentionally submitted 61 | for inclusion in this project by you, as defined in the Apache-2.0 license, 62 | shall be dual licensed as above, without any additional terms or conditions. 63 | -------------------------------------------------------------------------------- /benches/benches.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, criterion_main, Criterion}; 2 | use walrus::Module; 3 | 4 | fn criterion_benchmark(c: &mut Criterion) { 5 | let mut group = c.benchmark_group("round-trip-with-gc"); 6 | group.bench_function("dodrio-todomvc.wasm", |b| { 7 | let input_wasm = include_bytes!("./fixtures/dodrio-todomvc.wasm"); 8 | b.iter(|| { 9 | let input_wasm = black_box(input_wasm); 10 | let mut module = Module::from_buffer(input_wasm).unwrap(); 11 | walrus::passes::gc::run(&mut module); 12 | let output_wasm = module.emit_wasm(); 13 | black_box(output_wasm); 14 | }); 15 | }); 16 | } 17 | 18 | criterion_group!(benches, criterion_benchmark); 19 | criterion_main!(benches); 20 | -------------------------------------------------------------------------------- /benches/fixtures/dodrio-todomvc.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rustwasm/walrus/cd8fde06d44ba8bc86588e8f21f8080f8fbeb251/benches/fixtures/dodrio-todomvc.wasm -------------------------------------------------------------------------------- /crates/fuzz-utils/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Nick Fitzgerald "] 3 | edition = "2018" 4 | name = "walrus-fuzz-utils" 5 | version = "0.1.0" 6 | publish = false 7 | 8 | [dependencies] 9 | anyhow = "1.0" 10 | env_logger = "0.11.3" 11 | rand = { version = "0.7.0", features = ['small_rng'] } 12 | tempfile = "3.1.0" 13 | wasmparser = "0.212.0" 14 | wat = "1.0" 15 | 16 | [dependencies.walrus] 17 | path = "../.." 18 | 19 | [dependencies.walrus-tests-utils] 20 | path = "../tests-utils" 21 | 22 | [dev-dependencies] 23 | bufrng = "1.0.1" 24 | -------------------------------------------------------------------------------- /crates/fuzz-utils/before-after.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This script can be used with `creduce` to reduce the size of a WAT test case 4 | # that has different output before vs after being round tripped through 5 | # Walrus. I've successfully used it to reduce ~5KiB test cases down to ~100 6 | # bytes in only a couple minutes! 7 | # 8 | # Usage: 9 | # 10 | # creduce walrus/crates/fuzz/before-after.sh path/to/fuzz.wat 11 | 12 | set -eux 13 | 14 | INPUT="fuzz.wat" 15 | TEST_FILE="fuzz.wasm" 16 | OUT_FILE="$TEST_FILE.walrus.wasm" 17 | ORIG_OUTPUT_FILE="$TEST_FILE.output.txt" 18 | NEW_OUTPUT_FILE="$OUT_FILE.output.txt" 19 | 20 | function interp { 21 | wasm-interp \ 22 | --run-all-exports \ 23 | --dummy-import-func \ 24 | --enable-threads \ 25 | --enable-bulk-memory \ 26 | --enable-reference-types \ 27 | --enable-simd \ 28 | "$1" 29 | } 30 | 31 | wat2wasm "$INPUT" -o "$TEST_FILE" --enable-simd --enable-bulk-memory 32 | interp "$TEST_FILE" > "$ORIG_OUTPUT_FILE" 33 | 34 | cargo run \ 35 | --manifest-path "$(dirname "$0")/../../Cargo.toml" \ 36 | --example round-trip \ 37 | "$TEST_FILE" \ 38 | "$OUT_FILE" 39 | 40 | interp "$OUT_FILE" > "$NEW_OUTPUT_FILE" 41 | 42 | ! diff -U3 "$ORIG_OUTPUT_FILE" "$NEW_OUTPUT_FILE" 43 | -------------------------------------------------------------------------------- /crates/macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Nick Fitzgerald "] 3 | edition = "2018" 4 | name = "walrus-macro" 5 | version = "0.22.0" 6 | license = "MIT/Apache-2.0" 7 | categories = ["wasm"] 8 | repository = "https://github.com/rustwasm/walrus/tree/crates/macro" 9 | homepage = "https://github.com/rustwasm/walrus" 10 | documentation = "https://docs.rs/walrus-macro" 11 | description = """ 12 | Internal macros used by the `walrus` crate, not for public consumption. 13 | """ 14 | 15 | [dependencies] 16 | heck = "0.5.0" 17 | proc-macro2 = "1.0.86" 18 | quote = "1.0.37" 19 | syn = { version = "2.0.77", features = ['extra-traits'] } 20 | 21 | [lib] 22 | proc-macro = true 23 | -------------------------------------------------------------------------------- /crates/macro/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../../LICENSE-APACHE -------------------------------------------------------------------------------- /crates/macro/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../../LICENSE-MIT -------------------------------------------------------------------------------- /crates/tests-utils/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "walrus-tests-utils" 3 | version = "0.1.0" 4 | authors = ["Nick Fitzgerald "] 5 | edition = "2018" 6 | publish = false 7 | 8 | [dependencies] 9 | tempfile = "3.1.0" 10 | anyhow = "1.0" 11 | -------------------------------------------------------------------------------- /crates/tests-utils/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{bail, Context}; 2 | use std::ffi::OsStr; 3 | use std::fs; 4 | use std::path::Path; 5 | use std::process::{Command, Stdio}; 6 | use std::sync::Once; 7 | 8 | pub type Result = std::result::Result; 9 | 10 | fn require_tool(tool: &str, repo: &str) { 11 | let diagnostic = format!("Could not spawn {}; do you have {} installed?", tool, repo); 12 | let status = Command::new(tool) 13 | .arg("--help") 14 | .stdout(Stdio::null()) 15 | .stderr(Stdio::null()) 16 | .status() 17 | .expect(&diagnostic); 18 | assert!(status.success(), "{}", diagnostic) 19 | } 20 | 21 | fn require_wasm_interp() { 22 | require_tool("wasm-interp", "https://github.com/WebAssembly/wabt"); 23 | } 24 | 25 | /// Run `wasm-interp` on the given wat file. 26 | pub fn wasm_interp(path: &Path) -> Result { 27 | static CHECK: Once = Once::new(); 28 | CHECK.call_once(require_wasm_interp); 29 | 30 | let mut cmd = Command::new("wasm-interp"); 31 | cmd.arg(path); 32 | cmd.arg("--run-all-exports"); 33 | // This requires a build of WABT at least as new as `41adcbfb` to get 34 | // `wasm-interp`'s `--dummy-import-func`. 35 | cmd.arg("--dummy-import-func"); 36 | cmd.arg("--enable-all"); 37 | println!("running: {:?}", cmd); 38 | let output = cmd.output().context("could notrun wasm-interp")?; 39 | if !output.status.success() { 40 | bail!( 41 | "wasm-interp exited with status {:?}\n\nstderr = '''\n{}\n'''", 42 | output.status, 43 | String::from_utf8_lossy(&output.stderr) 44 | ); 45 | } 46 | 47 | Ok(String::from_utf8_lossy(&output.stdout).into_owned()) 48 | } 49 | 50 | fn require_wasm_opt() { 51 | require_tool("wasm-opt", "https://github.com/WebAssembly/binaryen"); 52 | } 53 | 54 | /// Run `wasm-opt` on the given input file with optional extra arguments, and 55 | /// return the resulting wasm binary as an in-memory buffer. 56 | pub fn wasm_opt(input: &Path, args: A) -> Result> 57 | where 58 | A: IntoIterator, 59 | S: AsRef, 60 | { 61 | static CHECK: Once = Once::new(); 62 | CHECK.call_once(require_wasm_opt); 63 | 64 | let tmp = tempfile::NamedTempFile::new().unwrap(); 65 | 66 | let mut cmd = Command::new("wasm-opt"); 67 | cmd.arg(input); 68 | cmd.arg("-o"); 69 | cmd.arg(tmp.path()); 70 | cmd.args([ 71 | "--enable-threads", 72 | "--enable-bulk-memory", 73 | // "--enable-reference-types", 74 | "--enable-simd", 75 | ]); 76 | cmd.args(args); 77 | println!("running: {:?}", cmd); 78 | let output = cmd.output().context("could not run wasm-opt")?; 79 | if !output.status.success() { 80 | bail!( 81 | "wasm-opt exited with status {:?}\n\nstderr = '''\n{}\n'''", 82 | output.status, 83 | String::from_utf8_lossy(&output.stderr) 84 | ); 85 | } 86 | 87 | let buf = fs::read(tmp.path())?; 88 | Ok(buf) 89 | } 90 | 91 | pub fn handle(result: T) { 92 | result.handle(); 93 | } 94 | 95 | pub trait TestResult { 96 | fn handle(self); 97 | } 98 | 99 | impl TestResult for () { 100 | fn handle(self) {} 101 | } 102 | 103 | impl TestResult for Result<()> { 104 | fn handle(self) { 105 | match self { 106 | Ok(()) => {} 107 | Err(e) => panic!("got an error: {:?}", e), 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /crates/tests/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "walrus-tests" 3 | version = "0.1.0" 4 | authors = ["Nick Fitzgerald "] 5 | edition = "2018" 6 | publish = false 7 | 8 | [build-dependencies] 9 | walkdir = "2.2.9" 10 | 11 | [dev-dependencies] 12 | anyhow = "1.0" 13 | env_logger = "0.8.1" 14 | serde = { version = "1.0.99", features = ['derive'] } 15 | serde_json = { version = "1.0.40", features = ['preserve_order'] } 16 | tempfile = "3.1.0" 17 | walrus = { path = "../.." } 18 | walrus-tests-utils = { path = "../tests-utils" } 19 | wasmprinter = "=0.2.78" 20 | wat = "1.0.85" 21 | 22 | [features] 23 | parallel = ['walrus/parallel'] 24 | 25 | [lib] 26 | doctest = false 27 | test = false 28 | -------------------------------------------------------------------------------- /crates/tests/build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::ffi::OsStr; 3 | use std::fs; 4 | use std::path::Path; 5 | use walkdir::WalkDir; 6 | 7 | fn is_known_failing(name: &str) -> bool { 8 | match name { 9 | // enabling multi-memory means that these tests fail, but the failure is 10 | // benign. 11 | "tests_spec_tests_proposals_bulk_memory_operations_binary_wast" 12 | | "tests_spec_tests_proposals_reference_types_binary_wast" => true, 13 | 14 | _ => false, 15 | } 16 | } 17 | 18 | fn for_each_wat_file(dir: P, mut f: F) 19 | where 20 | P: AsRef, 21 | F: FnMut(&Path), 22 | { 23 | println!("cargo:rerun-if-changed={}", dir.as_ref().display()); 24 | for entry in WalkDir::new(dir) { 25 | let entry = entry.unwrap(); 26 | if entry.path().extension() == Some(OsStr::new("wat")) 27 | || entry.path().extension() == Some(OsStr::new("wast")) 28 | { 29 | println!("cargo:rerun-if-changed={}", entry.path().display()); 30 | f(entry.path()); 31 | } 32 | } 33 | } 34 | 35 | fn path_to_ident(p: &Path) -> String { 36 | p.display() 37 | .to_string() 38 | .chars() 39 | .map(|c| match c { 40 | 'a'..='z' | 'A'..='Z' | '0'..='9' | '_' => c, 41 | _ => '_', 42 | }) 43 | .collect() 44 | } 45 | 46 | fn generate_tests(name: &str) { 47 | let mut tests = String::new(); 48 | 49 | for_each_wat_file(Path::new("tests").join(name), |path| { 50 | let test_name = path_to_ident(path); 51 | let ignore_test = if is_known_failing(&test_name) { 52 | "#[ignore]" 53 | } else { 54 | "" 55 | }; 56 | tests.push_str(&format!( 57 | "#[test] {} fn {}() {{ walrus_tests_utils::handle(run({:?}.as_ref())); }}\n", 58 | ignore_test, 59 | test_name, 60 | path.display(), 61 | )); 62 | }); 63 | 64 | let out_dir = env::var("OUT_DIR").unwrap(); 65 | fs::write(Path::new(&out_dir).join(name).with_extension("rs"), &tests) 66 | .expect("should write generated valid.rs file OK"); 67 | } 68 | 69 | fn main() { 70 | println!("cargo:rerun-if-changed=build.rs"); 71 | println!("cargo:rerun-if-env-changed=WALRUS_TESTS_DOT"); 72 | 73 | generate_tests("valid"); 74 | generate_tests("round_trip"); 75 | generate_tests("spec-tests"); 76 | generate_tests("function_imports"); 77 | generate_tests("invalid"); 78 | } 79 | -------------------------------------------------------------------------------- /crates/tests/src/check.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rustwasm/walrus/cd8fde06d44ba8bc86588e8f21f8080f8fbeb251/crates/tests/src/check.rs -------------------------------------------------------------------------------- /crates/tests/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A simple `FileCheck` clone from LLVM for our purposes 2 | //! 3 | //! This will parse a file looking for directives to match against some expected 4 | //! output. Typically we'll be running tooling like `wasm2wat` and then 5 | //! executing `CHECK` matches against the output of `wasm2wat`. 6 | //! 7 | //! Currently there's two possible types of checks: 8 | //! 9 | //! * `;; CHECK:` - start of a block of text to check for in the output. This can 10 | //! be optionally followed with a number of `;; NEXT:` lines which must show 11 | //! up in the output after the original line: 12 | //! 13 | //! ```wat 14 | //! (module) 15 | //! 16 | //! ;; CHECK: (module) 17 | //! ``` 18 | //! 19 | //! or ... 20 | //! 21 | //! ```wat 22 | //! (module 23 | //! (func (export "a"))) 24 | //! 25 | //! ;; CHECK: (func (;0;)) 26 | //! ``` 27 | //! 28 | //! or ... 29 | //! 30 | //! ```wat 31 | //! (module 32 | //! (func (export "a"))) 33 | //! 34 | //! ;; CHECK: (func (;0;)) 35 | //! ;; NEXT: (export "a" (func 0)) 36 | //! ``` 37 | //! 38 | //! * `(; CHECK-ALL:` can be used (and terminated at the end with `;)` to match 39 | //! the output exhaustively. This is typically used in conjunction with 40 | //! `WALRUS_BLESS` below to automatically update the output of tests. 41 | //! 42 | //! The above two directives are typically written in-line with tests to have 43 | //! everything in one nice location. 44 | //! 45 | //! It is an error to have a test file with no directives in it at all. 46 | //! 47 | //! ## Automatically updating tests 48 | //! 49 | //! If the `CHECK-ALL` directive is used, or if no directive is used in a file, 50 | //! then the expected output can be automatically updated. By running tests with 51 | //! `WALRUS_BLESS` in the environment: 52 | //! 53 | //! ```bash 54 | //! WALRUS_BLESS=1 cargo test --all 55 | //! ``` 56 | //! 57 | //! the expected output of each test will be updated with the actual output of 58 | //! the tool at hand. If a `CHECK-ALL` directive is present we'll update the 59 | //! text after it. If a no test directive is present one will be added to the 60 | //! end of the file. 61 | //! 62 | //! Note that this is somewhat experimental, so it's recommended to make liberal 63 | //! use of git to prevent destructive edits. 64 | 65 | use std::env; 66 | use std::fs; 67 | use std::path::{Path, PathBuf}; 68 | 69 | pub enum FileCheck { 70 | Exhaustive(Vec, PathBuf), 71 | Patterns(Vec>), 72 | None(PathBuf), 73 | } 74 | 75 | impl FileCheck { 76 | pub fn from_file(path: &Path) -> FileCheck { 77 | let contents = fs::read_to_string(path).expect("should read file to string OK"); 78 | let mut patterns = vec![]; 79 | let mut iter = contents.lines().map(str::trim); 80 | while let Some(line) = iter.next() { 81 | if line.starts_with("(; CHECK-ALL:") { 82 | if !patterns.is_empty() { 83 | panic!("CHECK cannot be used with CHECK-ALL"); 84 | } 85 | let mut pattern = Vec::new(); 86 | for line in iter.by_ref() { 87 | if line == ";)" { 88 | break; 89 | } 90 | pattern.push(line.to_string()); 91 | } 92 | if iter.next().is_some() { 93 | panic!("CHECK-ALL must be at the end of the file"); 94 | } 95 | return FileCheck::Exhaustive(pattern, path.to_path_buf()); 96 | } 97 | 98 | if let Some(p) = line.strip_prefix(";; CHECK:") { 99 | patterns.push(vec![p.to_string()]); 100 | } 101 | if let Some(n) = line.strip_prefix(";; NEXT:") { 102 | let p = patterns 103 | .last_mut() 104 | .expect("NEXT should never come before CHECK"); 105 | p.push(n.to_string()); 106 | } 107 | } 108 | if patterns.is_empty() { 109 | FileCheck::None(path.to_path_buf()) 110 | } else { 111 | FileCheck::Patterns(patterns) 112 | } 113 | } 114 | 115 | pub fn check(&self, output: &str) { 116 | let output_lines = output.lines().collect::>(); 117 | let bless = env::var("WALRUS_BLESS").is_ok(); 118 | match self { 119 | FileCheck::Patterns(patterns) => { 120 | 'outer: for pattern in patterns { 121 | let first_line = &pattern[0]; 122 | 123 | let mut start = 0; 124 | 125 | 'inner: while let Some(pos) = output_lines[start..] 126 | .iter() 127 | .position(|l| matches(l, first_line)) 128 | { 129 | start = pos + 1; 130 | if output_lines[pos..].len() + 1 < pattern.len() { 131 | break; 132 | } 133 | for (out_line, pat_line) in 134 | output_lines[pos + 1..].iter().zip(&pattern[1..]) 135 | { 136 | if !matches(out_line, pat_line) { 137 | continue 'inner; 138 | } 139 | } 140 | 141 | continue 'outer; 142 | } 143 | self.missing_pattern(pattern, output); 144 | } 145 | } 146 | FileCheck::Exhaustive(_, path) | FileCheck::None(path) if bless => { 147 | update_output(path, output) 148 | } 149 | FileCheck::Exhaustive(pattern, _) => { 150 | for (out_line, pat_line) in output_lines.iter().zip(pattern) { 151 | if !matches(out_line, pat_line) { 152 | self.missing_pattern(pattern, output) 153 | } 154 | } 155 | } 156 | FileCheck::None(_) => { 157 | println!(); 158 | println!("no test assertions were found in this file, but"); 159 | println!("you can rerun tests with `WALRUS_BLESS=1` to"); 160 | println!("automatically add assertions to this file"); 161 | println!(); 162 | panic!("no tests to run") 163 | } 164 | } 165 | } 166 | 167 | fn missing_pattern(&self, pattern: &[String], output: &str) -> ! { 168 | let pattern = pattern 169 | .iter() 170 | .enumerate() 171 | .map(|(i, l)| format!(" {}: {}", if i == 0 { "CHECK" } else { "NEXT" }, l,)) 172 | .collect::>() 173 | .join("\n"); 174 | 175 | let output = output 176 | .lines() 177 | .map(|l| format!(" {}", l.trim_end())) 178 | .collect::>() 179 | .join("\n"); 180 | 181 | panic!( 182 | "\ 183 | CHECK failed!\n\n\ 184 | Did not find pattern\n\n\ 185 | {}\n\n\ 186 | in output\n\n\ 187 | {}\n\n", 188 | pattern, output 189 | ); 190 | } 191 | } 192 | 193 | fn matches(mut actual: &str, expected: &str) -> bool { 194 | actual = actual.trim(); 195 | // skip a leading comment 196 | if actual.starts_with("(;") { 197 | actual = actual[actual.find(";)").unwrap() + 2..].trim(); 198 | } 199 | actual.starts_with(expected.trim()) 200 | } 201 | 202 | fn update_output(path: &Path, output: &str) { 203 | let contents = fs::read_to_string(path).unwrap(); 204 | let start = contents.find("(; CHECK-ALL:").unwrap_or(contents.len()); 205 | 206 | let mut new_output = String::new(); 207 | for line in output.lines() { 208 | if !line.is_empty() { 209 | new_output.push_str(" "); 210 | new_output.push_str(line.trim_end()); 211 | } 212 | new_output.push('\n'); 213 | } 214 | let new = format!( 215 | "{}\n\n(; CHECK-ALL:\n{}\n;)\n", 216 | contents[..start].trim(), 217 | new_output.trim_end() 218 | ); 219 | fs::write(path, new).unwrap(); 220 | } 221 | -------------------------------------------------------------------------------- /crates/tests/tests/custom_sections.rs: -------------------------------------------------------------------------------- 1 | //! Tests for working with custom sections that `walrus` doesn't know about. 2 | 3 | use std::borrow::Cow; 4 | use walrus::{CodeTransform, CustomSection, IdsToIndices, Module, ModuleConfig, ValType}; 5 | 6 | #[derive(Clone, Debug, Default, PartialEq, Eq)] 7 | struct HelloCustomSection(String); 8 | 9 | impl HelloCustomSection { 10 | fn parse(data: &[u8]) -> Option { 11 | let data = std::str::from_utf8(data).ok()?; 12 | if !data.starts_with("Hello, ") || !data.ends_with("!") { 13 | return None; 14 | } 15 | let who = data["Hello, ".len()..data.len() - 1].to_string(); 16 | Some(HelloCustomSection(who)) 17 | } 18 | } 19 | 20 | impl CustomSection for HelloCustomSection { 21 | fn name(&self) -> &str { 22 | "hello" 23 | } 24 | 25 | fn data(&self, _: &IdsToIndices) -> Cow<[u8]> { 26 | let data = format!("Hello, {}!", self.0); 27 | data.into_bytes().into() 28 | } 29 | } 30 | 31 | #[test] 32 | fn round_trip_unkown_custom_sections() { 33 | let mut config = ModuleConfig::new(); 34 | config.generate_producers_section(false); 35 | 36 | let indices = IdsToIndices::default(); 37 | 38 | let mut module = Module::with_config(config.clone()); 39 | 40 | let world = HelloCustomSection("World".into()); 41 | let world_id = module.customs.add(world.clone()); 42 | assert_eq!(module.customs.get(world_id).unwrap(), &world); 43 | 44 | assert_eq!( 45 | module 46 | .customs 47 | .iter() 48 | .map(|(id, s)| (id, s.data(&indices))) 49 | .collect::>(), 50 | [(world_id.into(), world.data(&indices))] 51 | ); 52 | 53 | let wasm = module.emit_wasm(); 54 | let mut module = config.parse(&wasm).unwrap(); 55 | 56 | let world_round_tripped = module.customs.remove_raw("hello").unwrap(); 57 | assert_eq!(world_round_tripped.data(&indices), world.data(&indices)); 58 | 59 | let new_world = HelloCustomSection::parse(&world.data(&indices)).unwrap(); 60 | assert_eq!(new_world.data(&indices), world.data(&indices)); 61 | module.customs.add(new_world); 62 | 63 | let new_wasm = module.emit_wasm(); 64 | assert_eq!(wasm, new_wasm); 65 | } 66 | 67 | // Insert a `(drop (i32.const 0))` at the start of the function and assert that 68 | // all instructions are pushed down by the size of a `(drop (i32.const 0))`, 69 | // which is 3. 70 | #[test] 71 | fn smoke_test_code_transform() { 72 | use std::sync::atomic::{AtomicUsize, Ordering}; 73 | 74 | static APPLIED_CODE_TRANSFORM: AtomicUsize = AtomicUsize::new(0); 75 | 76 | #[derive(Debug)] 77 | struct CheckCodeTransform; 78 | impl CustomSection for CheckCodeTransform { 79 | fn name(&self) -> &str { 80 | "check-code-transform" 81 | } 82 | 83 | fn data(&self, _: &IdsToIndices) -> Cow<[u8]> { 84 | vec![].into() 85 | } 86 | 87 | fn apply_code_transform(&mut self, transform: &CodeTransform) { 88 | APPLIED_CODE_TRANSFORM.store(1, Ordering::SeqCst); 89 | assert!(!transform.instruction_map.is_empty()); 90 | for (input_offset, output_offset) in transform.instruction_map.iter() { 91 | assert_eq!(input_offset.data() as usize + 3, *output_offset); 92 | } 93 | } 94 | } 95 | 96 | let mut config = ModuleConfig::new(); 97 | config.generate_producers_section(false); 98 | 99 | let wasm = { 100 | let mut module = Module::with_config(config.clone()); 101 | 102 | let mut builder = walrus::FunctionBuilder::new(&mut module.types, &[], &[ValType::I32]); 103 | builder.func_body().i32_const(1337); 104 | let locals = vec![]; 105 | let f_id = builder.finish(locals, &mut module.funcs); 106 | 107 | module.exports.add("f", f_id); 108 | 109 | module.emit_wasm() 110 | }; 111 | 112 | config.preserve_code_transform(true); 113 | 114 | let mut module = config.parse(&wasm).unwrap(); 115 | module.customs.add(CheckCodeTransform); 116 | 117 | for (_id, f) in module.funcs.iter_local_mut() { 118 | let builder = f.builder_mut(); 119 | builder.func_body().const_at(0, walrus::ir::Value::I32(0)); 120 | builder.func_body().drop_at(1); 121 | } 122 | 123 | // Emit the new, transformed wasm. This should trigger the 124 | // `apply_code_transform` method to be called. 125 | let _wasm = module.emit_wasm(); 126 | 127 | assert_eq!(APPLIED_CODE_TRANSFORM.load(Ordering::SeqCst), 1); 128 | } 129 | -------------------------------------------------------------------------------- /crates/tests/tests/function_imports.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | 3 | fn run(wat_path: &Path) -> Result<(), anyhow::Error> { 4 | static INIT_LOGS: std::sync::Once = std::sync::Once::new(); 5 | INIT_LOGS.call_once(|| { 6 | env_logger::init(); 7 | }); 8 | 9 | let wasm = wat::parse_file(wat_path)?; 10 | let module = walrus::Module::from_buffer(&wasm)?; 11 | 12 | assert!(module.imports.find("doggo", "husky").is_some()); 13 | assert!(module.imports.find("doggo", "shepherd").is_some()); 14 | assert!(module.imports.find("doggo", "siamese").is_none()); 15 | assert!(module.imports.find("snek", "cobra").is_none()); 16 | assert!(module.imports.find("cat", "siamese").is_some()); 17 | 18 | Ok(()) 19 | } 20 | 21 | include!(concat!(env!("OUT_DIR"), "/function_imports.rs")); 22 | -------------------------------------------------------------------------------- /crates/tests/tests/function_imports/pets_function_imports.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (func)) 3 | (import "doggo" "husky" (func (type 0))) 4 | (import "doggo" "shepherd" (func (type 0))) 5 | (import "cat" "siamese" (func (type 0))) 6 | (export "b" (func 0)) 7 | ) -------------------------------------------------------------------------------- /crates/tests/tests/invalid.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | use std::sync::Once; 3 | 4 | fn run(wat: &Path) -> Result<(), anyhow::Error> { 5 | static INIT_LOGS: Once = Once::new(); 6 | INIT_LOGS.call_once(|| { 7 | env_logger::init(); 8 | }); 9 | 10 | let wasm = wat::parse_file(wat)?; 11 | 12 | // NB: reading the module will do the validation. 13 | match walrus::Module::from_buffer(&wasm) { 14 | Err(e) => { 15 | eprintln!("Got error, as expected: {:?}", e); 16 | } 17 | Ok(_) => anyhow::bail!("expected {} to be invalid, but it was valid", wat.display()), 18 | } 19 | 20 | Ok(()) 21 | } 22 | 23 | include!(concat!(env!("OUT_DIR"), "/invalid.rs")); 24 | -------------------------------------------------------------------------------- /crates/tests/tests/invalid/if-with-no-else.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (func (export "f") (param i32) (result i32) 3 | (local.get 0) 4 | (if (result i32) 5 | (then (i32.const 1)) 6 | ;; Note: since the `if` block is supposed to produce a result, we need an 7 | ;; `else` here to produce that result if the condition is false. 8 | ;; 9 | ;; (else (i32.const 2)) 10 | ) 11 | ) 12 | ) 13 | -------------------------------------------------------------------------------- /crates/tests/tests/invalid/multi-value-0.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (func (export "i64.dup") (param i64) (result i64 i64) 3 | ;; With this commented out, there's only a single i64 on the stack. 4 | ;; get_local 0 5 | (local.get 0))) 6 | -------------------------------------------------------------------------------- /crates/tests/tests/invalid/multi-value-1.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (func (export "i64.dup") (param i64) (result i64 i64) 3 | ;; Too many i64s on the stack. 4 | (local.get 0) 5 | (local.get 0) 6 | (local.get 0))) 7 | -------------------------------------------------------------------------------- /crates/tests/tests/invalid/multi-value-2.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (func (export "multiBlock") (param i64 i64) (result i64 i64 i64) 3 | ;; With this commented out, not enough i64s on the stack for the block. 4 | ;; (local.get 1) 5 | (local.get 0) 6 | (block (param i64 i64) (result i64 i64 i64) 7 | (i64.const 1234)))) 8 | -------------------------------------------------------------------------------- /crates/tests/tests/invalid/multi-value-3.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (func (export "multiBlock") (param i64 i64) (result i64 i64 i64) 3 | ;; Too many i64s on the stack for the block. 4 | (local.get 0) 5 | (local.get 1) 6 | (local.get 0) 7 | (block (param i64 i64) (result i64 i64 i64) 8 | (i64.const 1234)))) 9 | -------------------------------------------------------------------------------- /crates/tests/tests/ir/bulk-memory.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (memory 1) 3 | 4 | (func 5 | (memory.init 0 6 | (i32.const 1) 7 | (i32.const 2) 8 | (i32.const 3)) 9 | (data.drop 2) 10 | 11 | (memory.copy 12 | (i32.const 1) 13 | (i32.const 2) 14 | (i32.const 3)) 15 | 16 | (memory.fill 17 | (i32.const 1) 18 | (i32.const 2) 19 | (i32.const 3)) 20 | ) 21 | 22 | (data "A") 23 | (data (i32.const 0) "b") 24 | (data "C") 25 | ) 26 | 27 | ;; CHECK: (func 28 | ;; NEXT: (block 29 | ;; NEXT: (memory.init 0 0 30 | ;; NEXT: (const 1) 31 | ;; NEXT: (const 2) 32 | ;; NEXT: (const 3) 33 | ;; NEXT: ) 34 | ;; NEXT: (data.drop 2) 35 | ;; NEXT: (memory.copy 0 0 36 | ;; NEXT: (const 1) 37 | ;; NEXT: (const 2) 38 | ;; NEXT: (const 3) 39 | ;; NEXT: ) 40 | ;; NEXT: (memory.fill 0 41 | ;; NEXT: (const 1) 42 | ;; NEXT: (const 2) 43 | ;; NEXT: (const 3) 44 | ;; NEXT: ) 45 | ;; NEXT: ) 46 | ;; NEXT: ) 47 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::path::Path; 3 | 4 | fn run(wat_path: &Path) -> Result<(), anyhow::Error> { 5 | static INIT_LOGS: std::sync::Once = std::sync::Once::new(); 6 | INIT_LOGS.call_once(|| { 7 | env_logger::init(); 8 | }); 9 | 10 | let wasm = wat::parse_file(wat_path)?; 11 | let mut module = walrus::Module::from_buffer(&wasm)?; 12 | 13 | if env::var("WALRUS_TESTS_DOT").is_ok() { 14 | module.write_graphviz_dot(wat_path.with_extension("dot"))?; 15 | } 16 | 17 | let out_wasm_file = wat_path.with_extension("out.wasm"); 18 | walrus::passes::gc::run(&mut module); 19 | module.emit_wasm_file(&out_wasm_file)?; 20 | 21 | let out_wat = wasmprinter::print_file(&out_wasm_file)?; 22 | let checker = walrus_tests::FileCheck::from_file(wat_path); 23 | checker.check(&out_wat); 24 | Ok(()) 25 | } 26 | 27 | include!(concat!(env!("OUT_DIR"), "/round_trip.rs")); 28 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/anyref1.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (import "x" "y" (func (param externref))) 3 | (func (export "a") (param externref) 4 | local.get 0 5 | call 0)) 6 | 7 | (; CHECK-ALL: 8 | (module 9 | (type (;0;) (func (param externref))) 10 | (import "x" "y" (func (;0;) (type 0))) 11 | (func (;1;) (type 0) (param externref) 12 | local.get 0 13 | call 0 14 | ) 15 | (export "a" (func 1)) 16 | ;) -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/anyref2.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (table 1 externref) 3 | (func (export "a") (param i32) (result externref) 4 | local.get 0 5 | table.get 0)) 6 | 7 | (; CHECK-ALL: 8 | (module 9 | (type (;0;) (func (param i32) (result externref))) 10 | (func (;0;) (type 0) (param i32) (result externref) 11 | local.get 0 12 | table.get 0 13 | ) 14 | (table (;0;) 1 externref) 15 | (export "a" (func 0)) 16 | ;) -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/anyref3.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (func)) 3 | (table 1 funcref) 4 | (table 1 externref) 5 | (func (export "a") (param i32) (result externref) 6 | local.get 0 7 | call_indirect (type 0) 8 | local.get 0 9 | table.get 1)) 10 | 11 | (; CHECK-ALL: 12 | (module 13 | (type (;0;) (func)) 14 | (type (;1;) (func (param i32) (result externref))) 15 | (func (;0;) (type 1) (param i32) (result externref) 16 | local.get 0 17 | call_indirect (type 0) 18 | local.get 0 19 | table.get 1 20 | ) 21 | (table (;0;) 1 funcref) 22 | (table (;1;) 1 externref) 23 | (export "a" (func 0)) 24 | ;) -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/atomic.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (memory 1 1 shared) 3 | 4 | (func (export "atomics") 5 | (i32.atomic.rmw.cmpxchg 6 | (i32.const 0) 7 | (i32.const 1) 8 | (i32.const 2)) 9 | 10 | (i32.atomic.rmw.add 11 | (i32.const 0) 12 | (i32.const 1)) 13 | 14 | (memory.atomic.notify 15 | (i32.const 0) 16 | (i32.const 1)) 17 | 18 | (memory.atomic.wait32 19 | (i32.const 0) 20 | (i32.const 1) 21 | (i64.const 2) 22 | ) 23 | 24 | (memory.atomic.wait64 25 | (i32.const 0) 26 | (i64.const 1) 27 | (i64.const 2) 28 | ) 29 | 30 | i32.add 31 | i32.add 32 | i32.add 33 | i32.add 34 | drop 35 | ) 36 | ) 37 | 38 | (; CHECK-ALL: 39 | (module 40 | (type (;0;) (func)) 41 | (func (;0;) (type 0) 42 | i32.const 0 43 | i32.const 1 44 | i32.const 2 45 | i32.atomic.rmw.cmpxchg 46 | i32.const 0 47 | i32.const 1 48 | i32.atomic.rmw.add 49 | i32.const 0 50 | i32.const 1 51 | memory.atomic.notify 52 | i32.const 0 53 | i32.const 1 54 | i64.const 2 55 | memory.atomic.wait32 56 | i32.const 0 57 | i64.const 1 58 | i64.const 2 59 | memory.atomic.wait64 60 | i32.add 61 | i32.add 62 | i32.add 63 | i32.add 64 | drop 65 | ) 66 | (memory (;0;) 1 1 shared) 67 | (export "atomics" (func 0)) 68 | ;) -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/block.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func (result i32))) 3 | (func $f (type 0) (local i32) 4 | i32.const 0 5 | drop 6 | block 7 | i32.const 1 8 | drop 9 | end 10 | i32.const 2) 11 | 12 | (func (export "foo") (result i32) 13 | block (result i32) 14 | i32.const 0 15 | end) 16 | (export "f" (func $f))) 17 | 18 | (; CHECK-ALL: 19 | (module 20 | (type (;0;) (func (result i32))) 21 | (func $f (;0;) (type 0) (result i32) 22 | i32.const 0 23 | drop 24 | block ;; label = @1 25 | i32.const 1 26 | drop 27 | end 28 | i32.const 2 29 | ) 30 | (func (;1;) (type 0) (result i32) 31 | block (result i32) ;; label = @1 32 | i32.const 0 33 | end 34 | ) 35 | (export "foo" (func 1)) 36 | (export "f" (func $f)) 37 | ;) -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/br-label.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (func 3 | loop 4 | br 1 5 | end 6 | )) 7 | 8 | ;; CHECK: (module 9 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/br-table-2.wat: -------------------------------------------------------------------------------- 1 | 2 | (module 3 | (func (export "break-br_table-nested-num") (param i32) (result i32) 4 | (i32.add 5 | (block (result i32) 6 | (br_table 0 1 0 (i32.const 50) (local.get 0)) (i32.const 51) 7 | ) 8 | (i32.const 2) 9 | ) 10 | ) 11 | ) 12 | 13 | ;; CHECK: br_table 0 14 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/br-to-func.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (func (result i32) 3 | i32.const 0 4 | br 0)) 5 | 6 | ;; CHECK: (module 7 | 8 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/br_table.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func (param i32) (result i32))) 3 | (func $f (type 0) 4 | block 5 | block 6 | block 7 | local.get 0 8 | br_table 0 1 2 9 | end 10 | i32.const 300 11 | return 12 | end 13 | i32.const 200 14 | return 15 | end 16 | i32.const 100) 17 | (export "f" (func $f))) 18 | 19 | (; CHECK-ALL: 20 | (module 21 | (type (;0;) (func (param i32) (result i32))) 22 | (func $f (;0;) (type 0) (param i32) (result i32) 23 | block ;; label = @1 24 | block ;; label = @2 25 | block ;; label = @3 26 | local.get 0 27 | br_table 0 (;@3;) 1 (;@2;) 2 (;@1;) 28 | end 29 | i32.const 300 30 | return 31 | end 32 | i32.const 200 33 | return 34 | end 35 | i32.const 100 36 | ) 37 | (export "f" (func $f)) 38 | ;) -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/bulk-memory.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (memory 1) 3 | 4 | (func (export "a") 5 | (memory.init 0 6 | (i32.const 1) 7 | (i32.const 2) 8 | (i32.const 3)) 9 | (data.drop 2) 10 | 11 | (memory.copy 12 | (i32.const 1) 13 | (i32.const 2) 14 | (i32.const 3)) 15 | 16 | (memory.fill 17 | (i32.const 1) 18 | (i32.const 2) 19 | (i32.const 3)) 20 | ) 21 | 22 | (data "A") 23 | (data (i32.const 0) "b") 24 | (data "C") 25 | ) 26 | 27 | (; CHECK-ALL: 28 | (module 29 | (type (;0;) (func)) 30 | (func (;0;) (type 0) 31 | i32.const 1 32 | i32.const 2 33 | i32.const 3 34 | memory.init 0 35 | data.drop 2 36 | i32.const 1 37 | i32.const 2 38 | i32.const 3 39 | memory.copy 40 | i32.const 1 41 | i32.const 2 42 | i32.const 3 43 | memory.fill 44 | ) 45 | (memory (;0;) 1) 46 | (export "a" (func 0)) 47 | (data (;0;) "A") 48 | (data (;1;) (i32.const 0) "b") 49 | (data (;2;) "C") 50 | ;) -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/call-two-params.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (import "spectest" "print_i32_f32" (func $print_i32_f32 (param i32 f32))) 3 | 4 | (func (export "print32") 5 | (call $print_i32_f32 6 | (i32.const 1) 7 | (f32.const 42) 8 | ) 9 | ) 10 | ) 11 | 12 | (; CHECK-ALL: 13 | (module 14 | (type (;0;) (func)) 15 | (type (;1;) (func (param i32 f32))) 16 | (import "spectest" "print_i32_f32" (func $print_i32_f32 (;0;) (type 1))) 17 | (func (;1;) (type 0) 18 | i32.const 1 19 | f32.const 0x1.5p+5 (;=42;) 20 | call $print_i32_f32 21 | ) 22 | (export "print32" (func 1)) 23 | ;) -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/call.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func (result i32))) 3 | (import "env" "f" (func (;0;) (type 0))) 4 | (func $g (type 0) (result i32) 5 | (call 0)) 6 | (export "g" (func $g))) 7 | 8 | (; CHECK-ALL: 9 | (module 10 | (type (;0;) (func (result i32))) 11 | (import "env" "f" (func (;0;) (type 0))) 12 | (func $g (;1;) (type 0) (result i32) 13 | call 0 14 | ) 15 | (export "g" (func $g)) 16 | ;) -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/call_2.wat: -------------------------------------------------------------------------------- 1 | ;; Calls with parameters. 2 | 3 | (module 4 | (type (;0;) (func (param i32) (result i32))) 5 | (import "env" "f" (func (type 0))) 6 | (func $g (type 0) (param i32) (result i32) 7 | (local.get 0) 8 | (call 0)) 9 | (export "g" (func $g))) 10 | 11 | (; CHECK-ALL: 12 | (module 13 | (type (;0;) (func (param i32) (result i32))) 14 | (import "env" "f" (func (;0;) (type 0))) 15 | (func $g (;1;) (type 0) (param i32) (result i32) 16 | local.get 0 17 | call 0 18 | ) 19 | (export "g" (func $g)) 20 | ;) -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/const.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func (result i32))) 3 | (func $f (type 0) (result i32) 4 | i32.const 42) 5 | (export "f" (func $f))) 6 | 7 | (; CHECK-ALL: 8 | (module 9 | (type (;0;) (func (result i32))) 10 | (func $f (;0;) (type 0) (result i32) 11 | i32.const 42 12 | ) 13 | (export "f" (func $f)) 14 | ;) -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/count-to-ten.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func (result i32))) 3 | (func $f (type 0) (local i32) 4 | (local.set 0 (i32.const 9)) 5 | loop 6 | (br_if 0 (i32.eqz (local.get 0))) 7 | (local.set 0 (i32.add (local.get 0) (i32.const 1))) 8 | end 9 | i32.const 10) 10 | (export "count_to_ten" (func $f))) 11 | 12 | (; CHECK-ALL: 13 | (module 14 | (type (;0;) (func (result i32))) 15 | (func $f (;0;) (type 0) (result i32) 16 | (local i32) 17 | i32.const 9 18 | local.set 0 19 | loop ;; label = @1 20 | local.get 0 21 | i32.eqz 22 | br_if 0 (;@1;) 23 | local.get 0 24 | i32.const 1 25 | i32.add 26 | local.set 0 27 | end 28 | i32.const 10 29 | ) 30 | (export "count_to_ten" (func $f)) 31 | ;) -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/drop.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func)) 3 | (func $f (type 0) 4 | (drop (i32.const 42))) 5 | (export "f" (func $f))) 6 | 7 | (; CHECK-ALL: 8 | (module 9 | (type (;0;) (func)) 10 | (func $f (;0;) (type 0) 11 | i32.const 42 12 | drop 13 | ) 14 | (export "f" (func $f)) 15 | ;) -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/elem-segments-1.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (table 1 funcref) 3 | (func) 4 | (elem (i32.const 1) 0) 5 | (export "foo" (table 0)) 6 | ) 7 | 8 | (; CHECK-ALL: 9 | (module 10 | (type (;0;) (func)) 11 | (func (;0;) (type 0)) 12 | (table (;0;) 1 funcref) 13 | (export "foo" (table 0)) 14 | (elem (;0;) (i32.const 1) func 0) 15 | ;) -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/elem-segments-2.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (table 1 funcref) 3 | (func) 4 | (elem (i32.const 1) 0) 5 | (elem (i32.const 2) 0) 6 | (export "foo" (table 0)) 7 | ) 8 | 9 | (; CHECK-ALL: 10 | (module 11 | (type (;0;) (func)) 12 | (func (;0;) (type 0)) 13 | (table (;0;) 1 funcref) 14 | (export "foo" (table 0)) 15 | (elem (;0;) (i32.const 1) func 0) 16 | (elem (;1;) (i32.const 2) func 0) 17 | ;) -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/elem-segments-3.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (table 1 funcref) 3 | (func) 4 | (elem (i32.const 1) 0) 5 | (elem (i32.const 3) 0) 6 | (export "foo" (table 0)) 7 | ) 8 | 9 | (; CHECK-ALL: 10 | (module 11 | (type (;0;) (func)) 12 | (func (;0;) (type 0)) 13 | (table (;0;) 1 funcref) 14 | (export "foo" (table 0)) 15 | (elem (;0;) (i32.const 1) func 0) 16 | (elem (;1;) (i32.const 3) func 0) 17 | ;) -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/entry-block-type.wat: -------------------------------------------------------------------------------- 1 | ;; The function entry's `InstrSeq` implicitly has type `[] -> [i64 i64]` but 2 | ;; that type should *NOT* appear in the emitted Type section. 3 | 4 | (module 5 | (func (export "multiLoop") (param i64 i64) (result i64 i64) 6 | (local.get 1) 7 | (local.get 0))) 8 | 9 | (; CHECK-ALL: 10 | (module 11 | (type (;0;) (func (param i64 i64) (result i64 i64))) 12 | (func (;0;) (type 0) (param i64 i64) (result i64 i64) 13 | local.get 1 14 | local.get 0 15 | ) 16 | (export "multiLoop" (func 0)) 17 | ;) -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/fac-multi-value.wat: -------------------------------------------------------------------------------- 1 | (module 2 | ;; Iterative factorial without locals. 3 | (func $pick0 (param i64) (result i64 i64) 4 | (local.get 0) (local.get 0) 5 | ) 6 | (func $pick1 (param i64 i64) (result i64 i64 i64) 7 | (local.get 0) (local.get 1) (local.get 0) 8 | ) 9 | (func (export "fac-ssa") (param i64) (result i64) 10 | (i64.const 1) (local.get 0) 11 | (loop $l (param i64 i64) (result i64) 12 | (call $pick1) (call $pick1) (i64.mul) 13 | (call $pick1) (i64.const 1) (i64.sub) 14 | (call $pick0) (i64.const 0) (i64.gt_u) 15 | (br_if $l) 16 | (drop) (return) 17 | ) 18 | ) 19 | ) 20 | 21 | (; CHECK-ALL: 22 | (module 23 | (type (;0;) (func (param i64) (result i64))) 24 | (type (;1;) (func (param i64) (result i64 i64))) 25 | (type (;2;) (func (param i64 i64) (result i64))) 26 | (type (;3;) (func (param i64 i64) (result i64 i64 i64))) 27 | (func (;0;) (type 0) (param i64) (result i64) 28 | i64.const 1 29 | local.get 0 30 | loop (type 2) (param i64 i64) (result i64) ;; label = @1 31 | call $pick1 32 | call $pick1 33 | i64.mul 34 | call $pick1 35 | i64.const 1 36 | i64.sub 37 | call $pick0 38 | i64.const 0 39 | i64.gt_u 40 | br_if 0 (;@1;) 41 | drop 42 | return 43 | end 44 | ) 45 | (func $pick1 (;1;) (type 3) (param i64 i64) (result i64 i64 i64) 46 | local.get 0 47 | local.get 1 48 | local.get 0 49 | ) 50 | (func $pick0 (;2;) (type 1) (param i64) (result i64 i64) 51 | local.get 0 52 | local.get 0 53 | ) 54 | (export "fac-ssa" (func 0)) 55 | ;) -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/fac.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func (param i32) (result i32))) 3 | (func $fac (type 0) (local i32) 4 | block 5 | local.get 0 6 | local.set 1 7 | loop 8 | ;; if local 0 == 0, break 9 | local.get 0 10 | i32.eqz 11 | br_if 1 12 | 13 | ;; local 1 = local 0 * local 1 14 | local.get 1 15 | local.get 0 16 | i32.mul 17 | local.set 1 18 | 19 | ;; local 0 = local 0 - 1 20 | local.get 0 21 | i32.const 1 22 | i32.sub 23 | local.set 0 24 | end 25 | end 26 | 27 | ;; return the accumulated value 28 | local.get 1) 29 | (export "fac" (func $fac))) 30 | 31 | (; CHECK-ALL: 32 | (module 33 | (type (;0;) (func (param i32) (result i32))) 34 | (func $fac (;0;) (type 0) (param i32) (result i32) 35 | (local i32) 36 | block ;; label = @1 37 | local.get 0 38 | local.set 1 39 | loop ;; label = @2 40 | local.get 0 41 | i32.eqz 42 | br_if 1 (;@1;) 43 | local.get 1 44 | local.get 0 45 | i32.mul 46 | local.set 1 47 | local.get 0 48 | i32.const 1 49 | i32.sub 50 | local.set 0 51 | end 52 | end 53 | local.get 1 54 | ) 55 | (export "fac" (func $fac)) 56 | ;) -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/fuzz-0.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (memory 1) 3 | (data (i32.const 0)) 4 | (export "" (func $b)) 5 | (func $b 6 | data.drop 0)) 7 | 8 | (; CHECK-ALL: 9 | (module 10 | (type (;0;) (func)) 11 | (func $b (;0;) (type 0) 12 | data.drop 0 13 | ) 14 | (memory (;0;) 1) 15 | (export "" (func $b)) 16 | (data (;0;) (i32.const 0) "") 17 | ;) -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/gc_keep_used_funcs.wat: -------------------------------------------------------------------------------- 1 | ;; Do not gc non-exported, but transitively called functions. 2 | 3 | (module 4 | (type (;0;) (func (result i32))) 5 | (func $f (type 0) (result i32) 6 | (i32.add (i32.const 42) (i32.const 1))) 7 | (func $g (type 0) (result i32) 8 | (call $f)) 9 | (export "g" (func $g))) 10 | 11 | (; CHECK-ALL: 12 | (module 13 | (type (;0;) (func (result i32))) 14 | (func $f (;0;) (type 0) (result i32) 15 | i32.const 42 16 | i32.const 1 17 | i32.add 18 | ) 19 | (func $g (;1;) (type 0) (result i32) 20 | call $f 21 | ) 22 | (export "g" (func $g)) 23 | ;) -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/gc_keep_used_globals.wat: -------------------------------------------------------------------------------- 1 | ;; Do not gc used globals. 2 | 3 | (module 4 | (global $used i32 (i32.const 666)) 5 | (export "g" (global $used))) 6 | 7 | (; CHECK-ALL: 8 | (module 9 | (global $used (;0;) i32 i32.const 666) 10 | (export "g" (global $used)) 11 | ;) -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/gc_keep_used_imports.wat: -------------------------------------------------------------------------------- 1 | ;; Keep used imports. 2 | 3 | (module 4 | (type (;0;) (func (result i32))) 5 | (import "env" "used" (func $used (type 0))) 6 | (export "used" (func $used))) 7 | 8 | (; CHECK-ALL: 9 | (module 10 | (type (;0;) (func (result i32))) 11 | (import "env" "used" (func $used (;0;) (type 0))) 12 | (export "used" (func $used)) 13 | ;) -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/gc_keep_used_memories.wat: -------------------------------------------------------------------------------- 1 | ;; Do not gc a used memory. 2 | 3 | (module 4 | (memory $m 2) 5 | (export "m" (memory $m))) 6 | 7 | (; CHECK-ALL: 8 | (module 9 | (memory $m (;0;) 2) 10 | (export "m" (memory $m)) 11 | ;) -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/gc_keep_used_tables.wat: -------------------------------------------------------------------------------- 1 | ;; Can remove an unused table. 2 | 3 | (module 4 | (type (;0;) (func (result i32))) 5 | (table 1 1 funcref) 6 | (export "t" (table 0))) 7 | 8 | (; CHECK-ALL: 9 | (module 10 | (table (;0;) 1 1 funcref) 11 | (export "t" (table 0)) 12 | ;) -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/gc_unused_block.wat: -------------------------------------------------------------------------------- 1 | ;; Can remove unused blocks. 2 | 3 | (module 4 | (type (;0;) (func (result i32))) 5 | (func $f (type 0) (result i32) 6 | (block 7 | br 0 8 | (loop 9 | ;; This loop block is unreachable, and should get GC'd. 10 | nop)) 11 | i32.const 42) 12 | (export "f" (func $f))) 13 | 14 | (; CHECK-ALL: 15 | (module 16 | (type (;0;) (func (result i32))) 17 | (func $f (;0;) (type 0) (result i32) 18 | block ;; label = @1 19 | br 0 (;@1;) 20 | end 21 | i32.const 42 22 | ) 23 | (export "f" (func $f)) 24 | ;) -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/gc_unused_funcs.wat: -------------------------------------------------------------------------------- 1 | ;; Can remove an unused function. 2 | 3 | (module 4 | (type (;0;) (func (result i32))) 5 | (func $unused (type 0) (result i32) 6 | i32.const 1337) 7 | (func $f (type 0) (result i32) 8 | i32.const 42) 9 | (export "f" (func $f))) 10 | 11 | (; CHECK-ALL: 12 | (module 13 | (type (;0;) (func (result i32))) 14 | (func $f (;0;) (type 0) (result i32) 15 | i32.const 42 16 | ) 17 | (export "f" (func $f)) 18 | ;) 19 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/gc_unused_funcs_2.wat: -------------------------------------------------------------------------------- 1 | ;; Can remove two unused functions that call each other. 2 | 3 | (module 4 | (type (;0;) (func (result i32))) 5 | 6 | ;; Two functions that are mutually recursive. 7 | (func $a (type 0) (result i32) 8 | call $b) 9 | (func $b (type 0) (result i32) 10 | call $a) 11 | 12 | ;; An unrelated function that we export. 13 | (func $f (type 0) (result i32) 14 | i32.const 42) 15 | (export "f" (func $f))) 16 | 17 | (; CHECK-ALL: 18 | (module 19 | (type (;0;) (func (result i32))) 20 | (func $f (;0;) (type 0) (result i32) 21 | i32.const 42 22 | ) 23 | (export "f" (func $f)) 24 | ;) -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/gc_unused_global.wat: -------------------------------------------------------------------------------- 1 | ;; Can remove an unused global. 2 | 3 | (module 4 | (type (;0;) (func (result i32))) 5 | (global $unused (mut i32) (i32.const 666)) 6 | (func $f (type 0) (result i32) 7 | i32.const 42) 8 | (export "f" (func $f))) 9 | 10 | (; CHECK-ALL: 11 | (module 12 | (type (;0;) (func (result i32))) 13 | (func $f (;0;) (type 0) (result i32) 14 | i32.const 42 15 | ) 16 | (export "f" (func $f)) 17 | ;) 18 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/gc_unused_imports.wat: -------------------------------------------------------------------------------- 1 | ;; Can remove an unused import. 2 | 3 | (module 4 | (type (;0;) (func (result i32))) 5 | (import "env" "unused" (func $unused (type 0))) 6 | (func $f (type 0) (result i32) 7 | i32.const 42) 8 | (export "f" (func $f))) 9 | 10 | (; CHECK-ALL: 11 | (module 12 | (type (;0;) (func (result i32))) 13 | (func $f (;0;) (type 0) (result i32) 14 | i32.const 42 15 | ) 16 | (export "f" (func $f)) 17 | ;) 18 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/gc_unused_memories.wat: -------------------------------------------------------------------------------- 1 | ;; Can remove an unused memory. 2 | 3 | (module 4 | (type (;0;) (func (result i32))) 5 | (memory $unused 2) 6 | (func $f (type 0) (result i32) 7 | i32.const 42) 8 | (export "f" (func $f))) 9 | 10 | (; CHECK-ALL: 11 | (module 12 | (type (;0;) (func (result i32))) 13 | (func $f (;0;) (type 0) (result i32) 14 | i32.const 42 15 | ) 16 | (export "f" (func $f)) 17 | ;) 18 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/gc_unused_tables.wat: -------------------------------------------------------------------------------- 1 | ;; Can remove an unused table. 2 | 3 | (module 4 | (type (;0;) (func (result i32))) 5 | (table 1 1 funcref) 6 | (func $f (type 0) (result i32) 7 | i32.const 42) 8 | (export "f" (func $f))) 9 | 10 | (; CHECK-ALL: 11 | (module 12 | (type (;0;) (func (result i32))) 13 | (func $f (;0;) (type 0) (result i32) 14 | i32.const 42 15 | ) 16 | (export "f" (func $f)) 17 | ;) 18 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/gc_unused_types.wat: -------------------------------------------------------------------------------- 1 | ;; Can remove an unused type. 2 | 3 | (module 4 | (type (;0;) (func (result i32))) 5 | (type (;1;) (func (param i32))) 6 | (func $f (type 0) (result i32) 7 | i32.const 42) 8 | (export "f" (func $f))) 9 | 10 | (; CHECK-ALL: 11 | (module 12 | (type (;0;) (func (result i32))) 13 | (func $f (;0;) (type 0) (result i32) 14 | i32.const 42 15 | ) 16 | (export "f" (func $f)) 17 | ;) 18 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/global-init.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (import "x" "y" (global i32)) 3 | (global i32 (global.get 0)) 4 | (export "x" (global 1))) 5 | 6 | (; CHECK-ALL: 7 | (module 8 | (import "x" "y" (global (;0;) i32)) 9 | (global (;1;) i32 global.get 0) 10 | (export "x" (global 1)) 11 | ;) 12 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/globals.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (global (mut i32) (i32.const 0)) 3 | (export "a" (global 0))) 4 | 5 | (; CHECK-ALL: 6 | (module 7 | (global (;0;) (mut i32) i32.const 0) 8 | (export "a" (global 0)) 9 | ;) 10 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/if-no-else.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (func 3 | i32.const 0 4 | if 5 | end)) 6 | 7 | ;; CHECK: (module 8 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/if_else.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func (param i32) (result i32))) 3 | (func $if_else (type 0) 4 | local.get 0 5 | if (result i32) 6 | i32.const 1 7 | else 8 | i32.const 2 9 | end) 10 | (export "if_else" (func $if_else))) 11 | 12 | (; CHECK-ALL: 13 | (module 14 | (type (;0;) (func (param i32) (result i32))) 15 | (func $if_else (;0;) (type 0) (param i32) (result i32) 16 | local.get 0 17 | if (result i32) ;; label = @1 18 | i32.const 1 19 | else 20 | i32.const 2 21 | end 22 | ) 23 | (export "if_else" (func $if_else)) 24 | ;) 25 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/if_else_2.wat: -------------------------------------------------------------------------------- 1 | ;; Test multiple expressions in if/else arms. 2 | 3 | (module 4 | (type (;0;) (func (param i32) (result i32))) 5 | (func $if_else (type 0) 6 | local.get 0 7 | if (result i32) 8 | (local.set 0 (i32.const 2)) 9 | i32.const 1 10 | else 11 | (local.set 0 (i32.const 1)) 12 | i32.const 2 13 | end) 14 | (export "if_else" (func $if_else))) 15 | 16 | (; CHECK-ALL: 17 | (module 18 | (type (;0;) (func (param i32) (result i32))) 19 | (func $if_else (;0;) (type 0) (param i32) (result i32) 20 | local.get 0 21 | if (result i32) ;; label = @1 22 | i32.const 2 23 | local.set 0 24 | i32.const 1 25 | else 26 | i32.const 1 27 | local.set 0 28 | i32.const 2 29 | end 30 | ) 31 | (export "if_else" (func $if_else)) 32 | ;) 33 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/import-func.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (func)) 3 | (import "" "" (func (type 0))) 4 | (export "b" (func 0)) 5 | ) 6 | 7 | (; CHECK-ALL: 8 | (module 9 | (type (;0;) (func)) 10 | (import "" "" (func (;0;) (type 0))) 11 | (export "b" (func 0)) 12 | ;) 13 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/import-gc.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (func)) 3 | (import "" "a" (func (type 0))) 4 | (import "" "b" (table 1 funcref)) 5 | (import "" "c" (global i32)) 6 | (import "" "d" (memory 1)) 7 | ) 8 | 9 | ;; CHECK: (module 10 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/import-global.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (import "" "" (global i32)) 3 | (export "b" (global 0)) 4 | ) 5 | 6 | (; CHECK-ALL: 7 | (module 8 | (import "" "" (global (;0;) i32)) 9 | (export "b" (global 0)) 10 | ;) 11 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/import-memory.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (import "" "" (memory 1)) 3 | (export "b" (memory 0)) 4 | ) 5 | 6 | (; CHECK-ALL: 7 | (module 8 | (import "" "" (memory (;0;) 1)) 9 | (export "b" (memory 0)) 10 | ;) 11 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/import-table.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (import "" "" (table 1 funcref)) 3 | (export "b" (table 0)) 4 | ) 5 | 6 | (; CHECK-ALL: 7 | (module 8 | (import "" "" (table (;0;) 1 funcref)) 9 | (export "b" (table 0)) 10 | ;) 11 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/inc.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func (param i32) (result i32))) 3 | (func $inc (type 0) (param i32) (result i32) 4 | (i32.add 5 | (local.get 0) 6 | (i32.const 1))) 7 | (export "inc" (func $inc))) 8 | 9 | (; CHECK-ALL: 10 | (module 11 | (type (;0;) (func (param i32) (result i32))) 12 | (func $inc (;0;) (type 0) (param i32) (result i32) 13 | local.get 0 14 | i32.const 1 15 | i32.add 16 | ) 17 | (export "inc" (func $inc)) 18 | ;) 19 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/keep-elem-segments.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (func)) 3 | 4 | (table funcref (elem 1)) 5 | 6 | (func 7 | i32.const 0 8 | call_indirect 9 | ) 10 | 11 | (func) 12 | 13 | (export "foo" (func 0)) 14 | ) 15 | 16 | (; CHECK-ALL: 17 | (module 18 | (type (;0;) (func)) 19 | (func (;0;) (type 0) 20 | i32.const 0 21 | call_indirect (type 0) 22 | ) 23 | (func (;1;) (type 0)) 24 | (table (;0;) 1 1 funcref) 25 | (export "foo" (func 0)) 26 | (elem (;0;) (i32.const 0) func 1) 27 | ;) 28 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/local-get.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (func (export "as-br-value") (param i32) (result i32) 3 | (block (result i32) (br 0 (local.get 0))) 4 | ) 5 | ) 6 | 7 | (; CHECK-ALL: 8 | (module 9 | (type (;0;) (func (param i32) (result i32))) 10 | (func (;0;) (type 0) (param i32) (result i32) 11 | block (result i32) ;; label = @1 12 | local.get 0 13 | br 0 (;@1;) 14 | end 15 | ) 16 | (export "as-br-value" (func 0)) 17 | ;) 18 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/local-tee.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (func (export "x") 3 | (local i32) 4 | i32.const 0 5 | local.tee 0 6 | local.set 0)) 7 | 8 | ;; CHECK: local.tee 0 9 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/loop.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func)) 3 | (func $f (type 0) 4 | loop 5 | end) 6 | (export "inf_loop" (func $f))) 7 | 8 | (; CHECK-ALL: 9 | (module 10 | (type (;0;) (func)) 11 | (func $f (;0;) (type 0) 12 | loop ;; label = @1 13 | end 14 | ) 15 | (export "inf_loop" (func $f)) 16 | ;) 17 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/loop2.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (func $dummy) 3 | (func (export "as-loop-last") (param i32) 4 | (loop 5 | (call $dummy) 6 | (br_if 1 (local.get 0))) 7 | )) 8 | 9 | (; CHECK-ALL: 10 | (module 11 | (type (;0;) (func)) 12 | (type (;1;) (func (param i32))) 13 | (func (;0;) (type 1) (param i32) 14 | loop ;; label = @1 15 | call $dummy 16 | local.get 0 17 | br_if 1 (;@0;) 18 | end 19 | ) 20 | (func $dummy (;1;) (type 0)) 21 | (export "as-loop-last" (func 0)) 22 | ;) 23 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/many_funcs.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func (result i32))) 3 | (func $a (type 0) 4 | i32.const 0) 5 | (func $b (type 0) 6 | (i32.add (i32.const 1) (i32.const 2))) 7 | (func $c (type 0) 8 | (i32.add 9 | (i32.const 3) 10 | (i32.add (i32.const 4) (i32.const 5)))) 11 | (func $d (type 0) 12 | (i32.add 13 | (i32.add (i32.const 0) (i32.const 1)) 14 | (i32.add (i32.const 2) (i32.const 3)))) 15 | (export "a" (func $a)) 16 | (export "b" (func $b)) 17 | (export "c" (func $c)) 18 | (export "d" (func $d))) 19 | 20 | ;; Note that these functions get properly sorted from largest to smallest. 21 | 22 | (; CHECK-ALL: 23 | (module 24 | (type (;0;) (func (result i32))) 25 | (func $d (;0;) (type 0) (result i32) 26 | i32.const 0 27 | i32.const 1 28 | i32.add 29 | i32.const 2 30 | i32.const 3 31 | i32.add 32 | i32.add 33 | ) 34 | (func $c (;1;) (type 0) (result i32) 35 | i32.const 3 36 | i32.const 4 37 | i32.const 5 38 | i32.add 39 | i32.add 40 | ) 41 | (func $b (;2;) (type 0) (result i32) 42 | i32.const 1 43 | i32.const 2 44 | i32.add 45 | ) 46 | (func $a (;3;) (type 0) (result i32) 47 | i32.const 0 48 | ) 49 | (export "a" (func $a)) 50 | (export "b" (func $b)) 51 | (export "c" (func $c)) 52 | (export "d" (func $d)) 53 | ;) 54 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/mem.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (memory 0) 3 | (func (export "f") 4 | i32.const 1 5 | i32.load 6 | drop 7 | 8 | i32.const 2 9 | f32.const 3 10 | f32.store)) 11 | 12 | (; CHECK-ALL: 13 | (module 14 | (type (;0;) (func)) 15 | (func (;0;) (type 0) 16 | i32.const 1 17 | i32.load 18 | drop 19 | i32.const 2 20 | f32.const 0x1.8p+1 (;=3;) 21 | f32.store 22 | ) 23 | (memory (;0;) 0) 24 | (export "f" (func 0)) 25 | ;) 26 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/memory-grow.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (memory 0) 3 | (func (export "a") 4 | i32.const 0 5 | memory.grow 6 | drop)) 7 | 8 | ;; CHECK: memory.grow 9 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/memory-init.wast: -------------------------------------------------------------------------------- 1 | (module 2 | (import "x" "y" (global i32)) 3 | (memory 1) 4 | (func) 5 | (data (global.get 0) "") 6 | (export "x" (memory 0))) 7 | 8 | (; CHECK-ALL: 9 | (module 10 | (import "x" "y" (global (;0;) i32)) 11 | (memory (;0;) 1) 12 | (export "x" (memory 0)) 13 | (data (;0;) (global.get 0) "") 14 | ;) 15 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/memory-size.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (memory 0) 3 | (func (;0;) (result i32) 4 | memory.size ) 5 | (export "get" (func 0))) 6 | 7 | (; CHECK-ALL: 8 | (module 9 | (type (;0;) (func (result i32))) 10 | (func (;0;) (type 0) (result i32) 11 | memory.size 12 | ) 13 | (memory (;0;) 0) 14 | (export "get" (func 0)) 15 | ;) 16 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/multi-0.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (func (export "i64.dup") (param i64) (result i64 i64) 3 | (local.get 0) (local.get 0))) 4 | 5 | (; CHECK-ALL: 6 | (module 7 | (type (;0;) (func (param i64) (result i64 i64))) 8 | (func (;0;) (type 0) (param i64) (result i64 i64) 9 | local.get 0 10 | local.get 0 11 | ) 12 | (export "i64.dup" (func 0)) 13 | ;) 14 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/multi-1.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (func (export "multiBlock") (param i64 i64) (result i64 i64 i64) 3 | (local.get 1) 4 | (local.get 0) 5 | (block (param i64 i64) (result i64 i64 i64) 6 | (i64.const 1234)))) 7 | 8 | (; CHECK-ALL: 9 | (module 10 | (type (;0;) (func (param i64 i64) (result i64 i64 i64))) 11 | (func (;0;) (type 0) (param i64 i64) (result i64 i64 i64) 12 | local.get 1 13 | local.get 0 14 | block (type 0) (param i64 i64) (result i64 i64 i64) ;; label = @1 15 | i64.const 1234 16 | end 17 | ) 18 | (export "multiBlock" (func 0)) 19 | ;) 20 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/multi-2.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (func (export "multiLoop") (param i64 i64) (result i64 i64) 3 | (local.get 1) 4 | (local.get 0) 5 | (loop (param i64 i64) (result i64 i64) 6 | return))) 7 | 8 | (; CHECK-ALL: 9 | (module 10 | (type (;0;) (func (param i64 i64) (result i64 i64))) 11 | (func (;0;) (type 0) (param i64 i64) (result i64 i64) 12 | local.get 1 13 | local.get 0 14 | loop (type 0) (param i64 i64) (result i64 i64) ;; label = @1 15 | return 16 | end 17 | ) 18 | (export "multiLoop" (func 0)) 19 | ;) 20 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/multi-3.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (func (export "multiLoop") (param i32 i64 i64) (result i64 i64) 3 | (local.get 2) 4 | (local.get 1) 5 | (local.get 0) 6 | (if (param i64 i64) (result i64 i64) 7 | (then return) 8 | (else 9 | (drop) 10 | (drop) 11 | (i64.const 0) 12 | (i64.const 0))))) 13 | 14 | (; CHECK-ALL: 15 | (module 16 | (type (;0;) (func (param i32 i64 i64) (result i64 i64))) 17 | (type (;1;) (func (param i64 i64) (result i64 i64))) 18 | (func (;0;) (type 0) (param i32 i64 i64) (result i64 i64) 19 | local.get 2 20 | local.get 1 21 | local.get 0 22 | if (type 1) (param i64 i64) (result i64 i64) ;; label = @1 23 | return 24 | else 25 | drop 26 | drop 27 | i64.const 0 28 | i64.const 0 29 | end 30 | ) 31 | (export "multiLoop" (func 0)) 32 | ;) 33 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/name-section.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (func $wat) 3 | (export "another" (func $wat))) 4 | 5 | ;; CHECK: (func $wat 6 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/nop.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (func 3 | nop)) 4 | 5 | ;; CHECK: (module 6 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/not-tree-like.wat: -------------------------------------------------------------------------------- 1 | ;; Non-tree-like, stack-y expressions. 2 | 3 | (module 4 | (import "env" "blackbox" (func $blackbox (param i32) (result i32))) 5 | (func (export "$f") (result i32) 6 | (call $blackbox (i32.const 1)) 7 | (drop (call $blackbox (i32.const 2))) 8 | (call $blackbox (i32.const 3)) 9 | i32.add 10 | )) 11 | 12 | (; CHECK-ALL: 13 | (module 14 | (type (;0;) (func (result i32))) 15 | (type (;1;) (func (param i32) (result i32))) 16 | (import "env" "blackbox" (func $blackbox (;0;) (type 1))) 17 | (func (;1;) (type 0) (result i32) 18 | i32.const 1 19 | call $blackbox 20 | i32.const 2 21 | call $blackbox 22 | drop 23 | i32.const 3 24 | call $blackbox 25 | i32.add 26 | ) 27 | (export "$f" (func 1)) 28 | ;) 29 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/params.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (func (param i32 i32) (result i32) 3 | local.get 1 4 | local.get 0 5 | i32.add) 6 | (export "foo" (func 0))) 7 | 8 | (; CHECK-ALL: 9 | (module 10 | (type (;0;) (func (param i32 i32) (result i32))) 11 | (func (;0;) (type 0) (param i32 i32) (result i32) 12 | local.get 1 13 | local.get 0 14 | i32.add 15 | ) 16 | (export "foo" (func 0)) 17 | ;) 18 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/refer-forward.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (func (export "b") call $a) 3 | (func $a) 4 | ) 5 | 6 | ;; CHECK: call $a 7 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/return.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (func $f (result i32) 3 | (return (i32.const 1))) 4 | (export "f" (func $f))) 5 | 6 | (; CHECK-ALL: 7 | (module 8 | (type (;0;) (func (result i32))) 9 | (func $f (;0;) (type 0) (result i32) 10 | i32.const 1 11 | return 12 | ) 13 | (export "f" (func $f)) 14 | ;) 15 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/return_2.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (func $f (result i32) 3 | (return (i32.const 1)) 4 | i32.const 2) 5 | (export "f" (func $f))) 6 | 7 | (; CHECK-ALL: 8 | (module 9 | (type (;0;) (func (result i32))) 10 | (func $f (;0;) (type 0) (result i32) 11 | i32.const 1 12 | return 13 | ) 14 | (export "f" (func $f)) 15 | ;) 16 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/return_call.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (func $g (result i32) 3 | i32.const 42 4 | return) 5 | (func $f (result i32) 6 | return_call $g) 7 | (export "f" (func $f))) 8 | 9 | (; CHECK-ALL: 10 | (module 11 | (type (;0;) (func (result i32))) 12 | (func $g (;0;) (type 0) (result i32) 13 | i32.const 42 14 | return 15 | ) 16 | (func $f (;1;) (type 0) (result i32) 17 | return_call $g 18 | ) 19 | (export "f" (func $f)) 20 | ;) 21 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/return_call_indirect.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (func (result i32))) 3 | (table 1 funcref) 4 | (func (export "a") (param i32) (result i32) 5 | local.get 0 6 | return_call_indirect (type 0))) 7 | 8 | (; CHECK-ALL: 9 | (module 10 | (type (;0;) (func (result i32))) 11 | (type (;1;) (func (param i32) (result i32))) 12 | (func (;0;) (type 1) (param i32) (result i32) 13 | local.get 0 14 | return_call_indirect (type 0) 15 | ) 16 | (table (;0;) 1 funcref) 17 | (export "a" (func 0)) 18 | ;) 19 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/select.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func (param i32) (result i32))) 3 | (func $do_select (type 0) (param i32) (result i32) 4 | i32.const 2 5 | i32.const 1 6 | local.get 0 7 | select) 8 | (export "do_select" (func $do_select))) 9 | 10 | (; CHECK-ALL: 11 | (module 12 | (type (;0;) (func (param i32) (result i32))) 13 | (func $do_select (;0;) (type 0) (param i32) (result i32) 14 | i32.const 2 15 | i32.const 1 16 | local.get 0 17 | select 18 | ) 19 | (export "do_select" (func $do_select)) 20 | ;) 21 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/start.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (start 0) 3 | (func)) 4 | 5 | (; CHECK-ALL: 6 | (module 7 | (type (;0;) (func)) 8 | (func (;0;) (type 0)) 9 | (start 0) 10 | ;) 11 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/stuff-after-loop-2.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func (result i32))) 3 | (func $f (type 0) 4 | loop 5 | br 0 6 | end 7 | i32.const 1) 8 | (export "f" (func $f))) 9 | 10 | (; CHECK-ALL: 11 | (module 12 | (type (;0;) (func (result i32))) 13 | (func $f (;0;) (type 0) (result i32) 14 | loop ;; label = @1 15 | br 0 (;@1;) 16 | end 17 | i32.const 1 18 | ) 19 | (export "f" (func $f)) 20 | ;) 21 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/stuff-after-loop.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func (result i32))) 3 | (func $f (type 0) 4 | loop 5 | end 6 | i32.const 1) 7 | (export "f" (func $f))) 8 | 9 | (; CHECK-ALL: 10 | (module 11 | (type (;0;) (func (result i32))) 12 | (func $f (;0;) (type 0) (result i32) 13 | loop ;; label = @1 14 | end 15 | i32.const 1 16 | ) 17 | (export "f" (func $f)) 18 | ;) 19 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/table-init.wast: -------------------------------------------------------------------------------- 1 | (module 2 | (import "x" "y" (global i32)) 3 | (table 1 funcref) 4 | (func) 5 | (elem (global.get 0) 0) 6 | (export "x" (table 0))) 7 | 8 | (; CHECK-ALL: 9 | (module 10 | (type (;0;) (func)) 11 | (import "x" "y" (global (;0;) i32)) 12 | (func (;0;) (type 0)) 13 | (table (;0;) 1 funcref) 14 | (export "x" (table 0)) 15 | (elem (;0;) (global.get 0) func 0) 16 | ;) 17 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/type-deduplicate.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func)) 3 | (type (;1;) (func)) 4 | (func $f (type 0)) 5 | (func (;1;) (type 1)) 6 | 7 | (export "a" (func $f)) 8 | (export "b" (func 1)) 9 | ) 10 | 11 | (; CHECK-ALL: 12 | (module 13 | (type (;0;) (func)) 14 | (func $f (;0;) (type 0)) 15 | (func (;1;) (type 0)) 16 | (export "a" (func $f)) 17 | (export "b" (func 1)) 18 | ;) 19 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/unreachable.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func (result i32))) 3 | (func $f (type 0) (result i32) 4 | unreachable) 5 | (export "f" (func $f))) 6 | 7 | (; CHECK-ALL: 8 | (module 9 | (type (;0;) (func (result i32))) 10 | (func $f (;0;) (type 0) (result i32) 11 | unreachable 12 | ) 13 | (export "f" (func $f)) 14 | ;) 15 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/unreachable_2.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func (result i32))) 3 | (func $f (type 0) (result i32) 4 | unreachable 5 | i32.const 42) 6 | (export "f" (func $f))) 7 | 8 | (; CHECK-ALL: 9 | (module 10 | (type (;0;) (func (result i32))) 11 | (func $f (;0;) (type 0) (result i32) 12 | unreachable 13 | ) 14 | (export "f" (func $f)) 15 | ;) 16 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/unreachable_3.wat: -------------------------------------------------------------------------------- 1 | ;; Instructions after an unreachable should not be emitted, but unreachable 2 | ;; instructions outside the block should be. 3 | 4 | (module 5 | (type (;0;) (func (result i32))) 6 | (func $f (type 0) (result i32) 7 | block 8 | unreachable 9 | end 10 | i32.const 42) 11 | (export "f" (func $f))) 12 | 13 | (; CHECK-ALL: 14 | (module 15 | (type (;0;) (func (result i32))) 16 | (func $f (;0;) (type 0) (result i32) 17 | block ;; label = @1 18 | unreachable 19 | end 20 | i32.const 42 21 | ) 22 | (export "f" (func $f)) 23 | ;) 24 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/unreachable_4.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (func $f (result i32) 3 | unreachable 4 | i32.add) 5 | (export "f" (func $f))) 6 | 7 | (; CHECK-ALL: 8 | (module 9 | (type (;0;) (func (result i32))) 10 | (func $f (;0;) (type 0) (result i32) 11 | unreachable 12 | ) 13 | (export "f" (func $f)) 14 | ;) 15 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/used-local-in-local.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (global i32 (i32.const 0)) 3 | (func $foo 4 | (local i32 i32) 5 | local.get 0 6 | local.set 1) 7 | (export "foo" (func $foo)) 8 | ) 9 | 10 | (; CHECK-ALL: 11 | (module 12 | (type (;0;) (func)) 13 | (func $foo (;0;) (type 0) 14 | (local i32 i32) 15 | local.get 0 16 | local.set 1 17 | ) 18 | (export "foo" (func $foo)) 19 | ;) 20 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/used-local-set.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (global i32 (i32.const 0)) 3 | (func $foo (local i32) 4 | global.get 0 5 | local.set 0) 6 | (export "foo" (func $foo)) 7 | ) 8 | 9 | (; CHECK-ALL: 10 | (module 11 | (type (;0;) (func)) 12 | (func $foo (;0;) (type 0) 13 | (local i32) 14 | global.get 0 15 | local.set 0 16 | ) 17 | (global (;0;) i32 i32.const 0) 18 | (export "foo" (func $foo)) 19 | ;) 20 | -------------------------------------------------------------------------------- /crates/tests/tests/spec-tests.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{bail, Context}; 2 | use std::fs; 3 | use std::path::Path; 4 | use std::process::Command; 5 | use tempfile::TempDir; 6 | 7 | #[derive(serde::Deserialize, serde::Serialize)] 8 | struct Test { 9 | source_filename: String, 10 | commands: Vec, 11 | } 12 | 13 | fn run(wast: &Path) -> Result<(), anyhow::Error> { 14 | static INIT_LOGS: std::sync::Once = std::sync::Once::new(); 15 | INIT_LOGS.call_once(|| { 16 | env_logger::init(); 17 | }); 18 | 19 | let proposal = wast 20 | .iter() 21 | .skip_while(|part| *part != "proposals") 22 | .nth(1) 23 | .map(|s| s.to_str().unwrap()); 24 | 25 | let extra_args: &[&str] = match proposal { 26 | None => &[], 27 | Some("annotations") => return Ok(()), 28 | Some("exception-handling") => return Ok(()), 29 | Some("extended-const") => return Ok(()), 30 | Some("function-references") => return Ok(()), 31 | Some("gc") => return Ok(()), 32 | Some("memory64") => &["--enable-memory64"], 33 | Some("multi-memory") => &["--enable-multi-memory"], 34 | Some("relaxed-simd") => return Ok(()), 35 | Some("tail-call") => return Ok(()), 36 | Some("threads") => return Ok(()), 37 | Some(other) => bail!("unknown wasm proposal: {}", other), 38 | }; 39 | 40 | let tempdir = TempDir::new()?; 41 | let json = tempdir.path().join("foo.json"); 42 | // Using `wasm-tools json-from-wast` instead of wabt's `wast2json` 43 | // because the latter is slow to support new proposals. 44 | let output = Command::new("wasm-tools") 45 | .arg("json-from-wast") 46 | .arg("--pretty") 47 | .arg(wast) 48 | .arg("--output") 49 | .arg(&json) 50 | .arg("--wasm-dir") 51 | .arg(tempdir.path()) 52 | .output()?; 53 | if !output.status.success() { 54 | let stderr = String::from_utf8_lossy(&output.stderr); 55 | bail!("failed to run `wasm-tools json-from-wast`\nstderr: {stderr}"); 56 | } 57 | 58 | let contents = fs::read_to_string(&json).context("failed to read file")?; 59 | let test: Test = serde_json::from_str(&contents).context("failed to parse file")?; 60 | let mut files = Vec::new(); 61 | 62 | let mut config = walrus::ModuleConfig::new(); 63 | if proposal.is_none() { 64 | // For non-proposals tests, we only enable the stable features. 65 | // For proposals tests, we enable all supported features. 66 | config.only_stable_features(true); 67 | } 68 | 69 | let wabt_ok = run_spectest_interp(tempdir.path(), extra_args).is_ok(); 70 | 71 | let mut should_not_parse = vec![]; 72 | let mut non_deterministic = vec![]; 73 | for command in test.commands { 74 | let filename = match command.get("filename") { 75 | Some(name) => name.as_str().unwrap().to_string(), 76 | None => continue, 77 | }; 78 | // walrus only process .wasm binary files 79 | if filename.ends_with(".wat") { 80 | continue; 81 | } 82 | let line = command["line"].as_u64().unwrap(); 83 | let path = tempdir.path().join(filename); 84 | match command["type"].as_str().unwrap() { 85 | "assert_invalid" | "assert_malformed" => { 86 | if proposal.is_some() 87 | && ["zero byte expected", "multiple memories"] 88 | .contains(&command["text"].as_str().unwrap()) 89 | { 90 | // The multi-memory proposal is enabled for all proprosals tests 91 | // but some proposals tests still expect them to fail. 92 | continue; 93 | } 94 | 95 | let wasm = fs::read(&path)?; 96 | if config.parse(&wasm).is_ok() { 97 | should_not_parse.push(line); 98 | } 99 | } 100 | cmd => { 101 | // The bytes read from the original spec test case 102 | let bytes0 = fs::read(&path)?; 103 | // The module parsed from bytes0 104 | let mut wasm1 = config 105 | .parse(&bytes0) 106 | .with_context(|| format!("error parsing wasm (line {})", line))?; 107 | // The bytes emitted from wasm1 108 | let bytes1 = wasm1.emit_wasm(); 109 | fs::write(&path, &bytes1)?; 110 | // The module parsed from bytes1 111 | let mut wasm2 = config 112 | .parse(&bytes1) 113 | .with_context(|| format!("error re-parsing wasm (line {})", line))?; 114 | // The bytes emitted from wasm2 115 | let bytes2 = wasm2.emit_wasm(); 116 | 117 | if bytes1 != bytes2 { 118 | non_deterministic.push(line); 119 | } 120 | files.push((cmd.to_string(), path.to_path_buf())); 121 | continue; 122 | } 123 | } 124 | } 125 | 126 | let mut message = String::new(); 127 | if !should_not_parse.is_empty() { 128 | message.push_str(&format!( 129 | "wasm parsed when it shouldn't at line: {:?}", 130 | should_not_parse 131 | )); 132 | } 133 | if !non_deterministic.is_empty() { 134 | message.push_str(&format!( 135 | "wasm isn't deterministic at line: {:?}", 136 | non_deterministic 137 | )); 138 | } 139 | if !message.is_empty() { 140 | panic!("{}", message); 141 | } 142 | 143 | // If wabt didn't succeed before we ran walrus there's no hope of it passing 144 | // after we run walrus. 145 | if !wabt_ok { 146 | return Ok(()); 147 | } 148 | 149 | // First up run the spec-tests as-is after we round-tripped through walrus. 150 | // This should for sure work correctly 151 | run_spectest_interp(tempdir.path(), extra_args)?; 152 | 153 | // Next run the same spec tests with semantics-preserving passes implemented 154 | // in walrus. Everything should continue to pass. 155 | for (cmd, file) in files.iter() { 156 | let wasm = fs::read(file)?; 157 | let mut module = config.parse(&wasm)?; 158 | 159 | // Tests which assert that they're not linkable tend to not work with 160 | // the gc pass because it removes things which would cause a module to 161 | // become unlinkable. This doesn't matter too much in the real world 162 | // (hopefully), so just don't gc assert_unlinkable modules. The same 163 | // applies to assert_uninstantiable modules due to removal of unused 164 | // elements and tables. 165 | if !matches!(cmd.as_str(), "assert_unlinkable" | "assert_uninstantiable") { 166 | walrus::passes::gc::run(&mut module); 167 | } 168 | 169 | let wasm = module.emit_wasm(); 170 | fs::write(file, wasm)?; 171 | } 172 | 173 | run_spectest_interp(tempdir.path(), extra_args)?; 174 | 175 | Ok(()) 176 | } 177 | 178 | fn run_spectest_interp(cwd: &Path, extra_args: &[&str]) -> Result<(), anyhow::Error> { 179 | let output = Command::new("spectest-interp") 180 | .current_dir(cwd) 181 | .arg("foo.json") 182 | .args(extra_args) 183 | .output() 184 | .context("executing `spectest-interp`")?; 185 | 186 | // If the interpreter exits with success it may still have failed some 187 | // tests. Check the output for `X/Y tests passed.` and make sure `X` equals 188 | // `Y`. 189 | if output.status.success() { 190 | let stdout = String::from_utf8_lossy(&output.stdout); 191 | if let Some(line) = stdout.lines().find(|l| l.ends_with("tests passed.")) { 192 | let part = line.split_whitespace().next().unwrap(); 193 | let mut parts = part.split("/"); 194 | let a = parts.next().unwrap().parse::(); 195 | let b = parts.next().unwrap().parse::(); 196 | if a == b { 197 | return Ok(()); 198 | } 199 | } 200 | } 201 | println!("status: {}", output.status); 202 | println!("stdout:\n{}", String::from_utf8_lossy(&output.stdout)); 203 | println!("stderr:\n{}", String::from_utf8_lossy(&output.stderr)); 204 | bail!("failed"); 205 | } 206 | 207 | include!(concat!(env!("OUT_DIR"), "/spec-tests.rs")); 208 | -------------------------------------------------------------------------------- /crates/tests/tests/valid.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::path::Path; 3 | use std::sync::Once; 4 | 5 | fn run(wat: &Path) -> Result<(), anyhow::Error> { 6 | static INIT_LOGS: Once = Once::new(); 7 | INIT_LOGS.call_once(|| { 8 | env_logger::init(); 9 | }); 10 | 11 | let wasm = wat::parse_file(wat)?; 12 | 13 | // NB: reading the module will do the validation. 14 | let module = walrus::Module::from_buffer(&wasm)?; 15 | 16 | if env::var("WALRUS_TESTS_DOT").is_ok() { 17 | module.write_graphviz_dot(wat.with_extension("dot"))?; 18 | } 19 | 20 | Ok(()) 21 | } 22 | 23 | include!(concat!(env!("OUT_DIR"), "/valid.rs")); 24 | -------------------------------------------------------------------------------- /crates/tests/tests/valid/block.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func (result i32))) 3 | (func $inc (type 0) (local i32) 4 | i32.const 0 5 | drop 6 | block 7 | i32.const 1 8 | drop 9 | end 10 | i32.const 2)) 11 | -------------------------------------------------------------------------------- /crates/tests/tests/valid/call.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func (result i32))) 3 | (import "env" "f" (func $f (type 0))) 4 | (func $g (type 0) (result i32) 5 | (call $f)) 6 | (export "g" (func $g))) 7 | -------------------------------------------------------------------------------- /crates/tests/tests/valid/const.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func (result i32))) 3 | (func $inc (type 0) (result i32) 4 | i32.const 42) 5 | (table (;0;) 1 1 funcref) 6 | (memory (;0;) 16)) 7 | -------------------------------------------------------------------------------- /crates/tests/tests/valid/count-to-ten.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func (result i32))) 3 | (func (;0;) (type 0) (local i32) 4 | (local.set 0 (i32.const 9)) 5 | loop 6 | (br_if 0 (i32.eqz (local.get 0))) 7 | 8 | (local.set 0 (i32.add (local.get 0) (i32.const 1))) 9 | end 10 | i32.const 10) 11 | (export "count_to_ten" (func 0))) 12 | -------------------------------------------------------------------------------- /crates/tests/tests/valid/drop.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func)) 3 | (func (type 0) 4 | i32.const 42 5 | drop)) 6 | -------------------------------------------------------------------------------- /crates/tests/tests/valid/fac-multi-value.wat: -------------------------------------------------------------------------------- 1 | ;; Taken from `spec-tests/proposals/multi-value/fac.wast`. 2 | 3 | (module 4 | ;; Iterative factorial without locals. 5 | (func $pick0 (param i64) (result i64 i64) 6 | (local.get 0) (local.get 0) 7 | ) 8 | (func $pick1 (param i64 i64) (result i64 i64 i64) 9 | (local.get 0) (local.get 1) (local.get 0) 10 | ) 11 | (func (export "fac-ssa") (param i64) (result i64) 12 | (i64.const 1) (local.get 0) 13 | (loop $l (param i64 i64) (result i64) 14 | (call $pick1) (call $pick1) (i64.mul) 15 | (call $pick1) (i64.const 1) (i64.sub) 16 | (call $pick0) (i64.const 0) (i64.gt_u) 17 | (br_if $l) 18 | (drop) (return) 19 | ) 20 | ) 21 | ) 22 | -------------------------------------------------------------------------------- /crates/tests/tests/valid/fac.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func (param i32) (result i32))) 3 | (func (;0;) (type 0) (local i32) 4 | block 5 | local.get 0 6 | local.set 1 7 | loop 8 | ;; if local 0 == 0, break 9 | local.get 0 10 | i32.eqz 11 | br_if 1 12 | 13 | ;; local 1 = local 0 * local 1 14 | local.get 1 15 | local.get 0 16 | i32.mul 17 | local.set 1 18 | 19 | ;; local 0 = local 0 - 1 20 | local.get 0 21 | i32.const 1 22 | i32.sub 23 | local.set 0 24 | end 25 | end 26 | 27 | ;; return the accumulated value 28 | local.get 1) 29 | (export "fac" (func 0))) 30 | -------------------------------------------------------------------------------- /crates/tests/tests/valid/if_else.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func (param i32) (result i32))) 3 | (func (;0;) (type 0) 4 | local.get 0 5 | if (result i32) 6 | i32.const 1 7 | else 8 | i32.const 2 9 | end) 10 | (export "if_else" (func 0))) 11 | -------------------------------------------------------------------------------- /crates/tests/tests/valid/inc.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func (param i32) (result i32))) 3 | (func $inc (type 0) (param i32) (result i32) 4 | local.get 0 5 | i32.const 1 6 | i32.add) 7 | (table (;0;) 1 1 funcref) 8 | (memory (;0;) 16)) 9 | -------------------------------------------------------------------------------- /crates/tests/tests/valid/loop.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func)) 3 | (func (;0;) (type 0) 4 | loop 5 | end) 6 | (export "inf_loop" (func 0))) 7 | -------------------------------------------------------------------------------- /crates/tests/tests/valid/min-fac.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func (param i32) (result i32))) 3 | (func (;0;) (type 0) (local i32) 4 | block 5 | br 0 6 | end 7 | i32.const 3) 8 | (export "fac" (func 0))) 9 | -------------------------------------------------------------------------------- /crates/tests/tests/valid/select.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func (param i32) (result i32))) 3 | (func $do_select (type 0) (param i32) (result i32) 4 | i32.const 2 5 | i32.const 1 6 | local.get 0 7 | select) 8 | (memory (;0;) 16) 9 | (export "do_select" (func $do_select))) 10 | -------------------------------------------------------------------------------- /crates/tests/tests/valid/stuff-after-loop-2.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func (result i32))) 3 | (func (;0;) (type 0) 4 | loop 5 | br 0 6 | end 7 | i32.const 1) 8 | (export "inf_loop_with_return" (func 0))) 9 | -------------------------------------------------------------------------------- /crates/tests/tests/valid/stuff-after-loop.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func (result i32))) 3 | (func (;0;) (type 0) 4 | loop 5 | end 6 | i32.const 1) 7 | (export "inf_loop_with_return" (func 0))) 8 | -------------------------------------------------------------------------------- /examples/build-wasm-from-scratch.rs: -------------------------------------------------------------------------------- 1 | //! This example constructs a Wasm module from scratch with Walrus. 2 | //! 3 | //! The module we are building implements and exports the `factorial` function, 4 | //! and imports a `env.log` function to observe incremental results. 5 | //! 6 | //! You can run the built Wasm module using Node.js (for example) like this: 7 | //! 8 | //! ```js 9 | //! const fs = require("fs"); 10 | //! 11 | //! async function main() { 12 | //! const bytes = fs.readFileSync("target/out.wasm"); 13 | //! const env = { log: val => console.log(`logged ${val}`), }; 14 | //! const { instance } = await WebAssembly.instantiate( 15 | //! bytes, 16 | //! { 17 | //! env: { 18 | //! log(val) { 19 | //! console.log(`log saw ${val}`); 20 | //! } 21 | //! } 22 | //! } 23 | //! ); 24 | //! const result = instance.exports.factorial(5); 25 | //! console.log(`factorial(5) = ${result}`); 26 | //! } 27 | //! 28 | //! main(); 29 | //! ``` 30 | 31 | use walrus::ir::*; 32 | use walrus::{FunctionBuilder, Module, ModuleConfig, ValType}; 33 | 34 | fn main() -> walrus::Result<()> { 35 | // Construct a new Walrus module. 36 | let config = ModuleConfig::new(); 37 | let mut module = Module::with_config(config); 38 | 39 | // Import the `log` function. 40 | let log_type = module.types.add(&[ValType::I32], &[]); 41 | let (log, _) = module.add_import_func("env", "log", log_type); 42 | 43 | // Building this factorial implementation: 44 | // https://github.com/WebAssembly/testsuite/blob/7816043/fac.wast#L46-L66 45 | let mut factorial = FunctionBuilder::new(&mut module.types, &[ValType::I32], &[ValType::I32]); 46 | 47 | // Create our parameter and our two locals. 48 | let n = module.locals.add(ValType::I32); 49 | let i = module.locals.add(ValType::I32); 50 | let res = module.locals.add(ValType::I32); 51 | 52 | factorial 53 | // Enter the function's body. 54 | .func_body() 55 | // (local.set $i (local.get $n)) 56 | .local_get(n) 57 | .local_set(i) 58 | // (local.set $res (i32.const 1)) 59 | .i32_const(1) 60 | .local_set(res) 61 | .block(None, |done| { 62 | let done_id = done.id(); 63 | done.loop_(None, |loop_| { 64 | let loop_id = loop_.id(); 65 | loop_ 66 | // (call $log (local.get $res)) 67 | .local_get(res) 68 | .call(log) 69 | // (i32.eq (local.get $i) (i32.const 0)) 70 | .local_get(i) 71 | .i32_const(0) 72 | .binop(BinaryOp::I32Eq) 73 | .if_else( 74 | None, 75 | |then| { 76 | // (then (br $done)) 77 | then.br(done_id); 78 | }, 79 | |else_| { 80 | else_ 81 | // (local.set $res (i32.mul (local.get $i) (local.get $res))) 82 | .local_get(i) 83 | .local_get(res) 84 | .binop(BinaryOp::I32Mul) 85 | .local_set(res) 86 | // (local.set $i (i32.sub (local.get $i) (i32.const 1)))) 87 | .local_get(i) 88 | .i32_const(1) 89 | .binop(BinaryOp::I32Sub) 90 | .local_set(i); 91 | }, 92 | ) 93 | .br(loop_id); 94 | }); 95 | }) 96 | .local_get(res); 97 | 98 | let factorial = factorial.finish(vec![n], &mut module.funcs); 99 | 100 | // Export the `factorial` function. 101 | module.exports.add("factorial", factorial); 102 | 103 | // Emit the `.wasm` binary to the `target/out.wasm` file. 104 | module.emit_wasm_file("target/out.wasm") 105 | } 106 | -------------------------------------------------------------------------------- /examples/parse.rs: -------------------------------------------------------------------------------- 1 | // A small example which is primarily used to help benchmark parsing in walrus 2 | // right now. 3 | 4 | fn main() { 5 | env_logger::init(); 6 | let a = std::env::args().nth(1).unwrap(); 7 | walrus::Module::from_file(a).unwrap(); 8 | } 9 | -------------------------------------------------------------------------------- /examples/round-trip.rs: -------------------------------------------------------------------------------- 1 | //! A small example which is primarily used to help benchmark walrus right now. 2 | 3 | fn main() -> anyhow::Result<()> { 4 | env_logger::init(); 5 | let a = std::env::args() 6 | .nth(1) 7 | .ok_or_else(|| anyhow::anyhow!("must provide the input wasm file as the first argument"))?; 8 | let mut m = walrus::Module::from_file(&a)?; 9 | let wasm = m.emit_wasm(); 10 | if let Some(destination) = std::env::args().nth(2) { 11 | std::fs::write(destination, wasm)?; 12 | } 13 | Ok(()) 14 | } 15 | -------------------------------------------------------------------------------- /fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | target 3 | corpus 4 | artifacts 5 | -------------------------------------------------------------------------------- /fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "walrus-fuzz" 3 | version = "0.0.1" 4 | authors = ["Automatically generated"] 5 | publish = false 6 | edition = "2018" 7 | 8 | [package.metadata] 9 | cargo-fuzz = true 10 | 11 | [dependencies] 12 | bufrng = "1.0.1" 13 | 14 | [dependencies.walrus] 15 | path = ".." 16 | 17 | [dependencies.walrus-fuzz-utils] 18 | path = "../crates/fuzz-utils" 19 | 20 | [dependencies.libfuzzer-sys] 21 | git = "https://github.com/rust-fuzz/libfuzzer-sys.git" 22 | 23 | # Prevent this from interfering with workspaces. 24 | [workspace] 25 | members = ["."] 26 | 27 | [[bin]] 28 | name = "watgen" 29 | path = "fuzz_targets/watgen.rs" 30 | 31 | [[bin]] 32 | name = "wasm-opt-ttf" 33 | path = "fuzz_targets/wasm-opt-ttf.rs" 34 | 35 | [[bin]] 36 | name = "raw" 37 | path = "fuzz_targets/raw.rs" 38 | -------------------------------------------------------------------------------- /fuzz/README.md: -------------------------------------------------------------------------------- 1 | # Fuzzing Walrus with `cargo fuzz`! 2 | 3 | ## Prerequisites 4 | 5 | ``` 6 | cargo install cargo-fuzz 7 | ``` 8 | 9 | ## Fuzzing 10 | 11 | ``` 12 | cargo fuzz run watgen 13 | cargo fuzz run wasm-opt-ttf 14 | cargo fuzz run raw 15 | ``` 16 | 17 | ## Learn More 18 | 19 | [Learn more about `cargo fuzz` from the `rust-fuzz` 20 | book.](https://rust-fuzz.github.io/book/cargo-fuzz.html) 21 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/raw.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | #[macro_use] 4 | extern crate libfuzzer_sys; 5 | 6 | fuzz_target!(|data: &[u8]| { 7 | let mut module = match walrus::Module::from_buffer(data) { 8 | Ok(m) => m, 9 | Err(_) => return, 10 | }; 11 | let serialized = module.emit_wasm(); 12 | let mut module = 13 | walrus::Module::from_buffer(&serialized).expect("we should only emit valid Wasm data"); 14 | let reserialized = module.emit_wasm(); 15 | assert_eq!( 16 | serialized, reserialized, 17 | "emitting wasm should be deterministic" 18 | ); 19 | }); 20 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/wasm-opt-ttf.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | #[macro_use] 4 | extern crate libfuzzer_sys; 5 | 6 | use bufrng::BufRng; 7 | use walrus_fuzz_utils::{Config, WasmOptTtf}; 8 | 9 | fuzz_target!(|data: &[u8]| { 10 | let data = if data.is_empty() { &[0] } else { data }; 11 | let fuel = data.len(); 12 | let rng = BufRng::new(data); 13 | let mut config = Config::::new(rng).set_fuel(fuel); 14 | if let Err(e) = config.run_one() { 15 | walrus_fuzz_utils::print_err(&e); 16 | panic!("Found an error! {}", e); 17 | } 18 | }); 19 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/watgen.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | #[macro_use] 4 | extern crate libfuzzer_sys; 5 | 6 | use bufrng::BufRng; 7 | use walrus_fuzz_utils::{Config, WatGen}; 8 | 9 | fuzz_target!(|data: &[u8]| { 10 | let data = if data.is_empty() { &[0] } else { data }; 11 | let fuel = data.len(); 12 | let rng = BufRng::new(data); 13 | let mut config = Config::, BufRng>::new(rng).set_fuel(fuel); 14 | if let Err(e) = config.run_one() { 15 | walrus_fuzz_utils::print_err(&e); 16 | panic!("Found an error! {}", e); 17 | } 18 | }); 19 | -------------------------------------------------------------------------------- /publish.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Helper script for publishing all the Walrus crates. 4 | # 5 | # Usage: 6 | # 7 | # ./publish.sh 8 | 9 | set -eux 10 | 11 | cd "$(dirname "$0")/crates/macro" 12 | cargo publish 13 | 14 | # Let crates.io's index notice that we published the macro. 15 | sleep 10 16 | 17 | cd ../.. 18 | cargo publish 19 | -------------------------------------------------------------------------------- /src/arena_set.rs: -------------------------------------------------------------------------------- 1 | use crate::tombstone_arena::{Tombstone, TombstoneArena}; 2 | use id_arena::Id; 3 | use std::collections::HashMap; 4 | use std::hash::Hash; 5 | use std::ops; 6 | 7 | /// A set of unique `T`s that are backed by an arena. 8 | #[derive(Debug)] 9 | pub struct ArenaSet { 10 | arena: TombstoneArena, 11 | already_in_arena: HashMap>, 12 | } 13 | 14 | impl ArenaSet { 15 | /// Construct a new set. 16 | pub fn new() -> ArenaSet { 17 | ArenaSet { 18 | arena: TombstoneArena::default(), 19 | already_in_arena: HashMap::new(), 20 | } 21 | } 22 | 23 | /// Insert a value into the arena and get its id. 24 | pub fn insert(&mut self, val: T) -> Id { 25 | if let Some(id) = self.already_in_arena.get(&val) { 26 | return *id; 27 | } 28 | 29 | let id = self.arena.alloc(val.clone()); 30 | self.already_in_arena.insert(val, id); 31 | id 32 | } 33 | 34 | /// Get the id that will be used for the next unique item added to this set. 35 | pub fn next_id(&self) -> Id { 36 | self.arena.next_id() 37 | } 38 | 39 | /// Remove an item from this set 40 | pub fn remove(&mut self, id: Id) 41 | where 42 | T: Tombstone, 43 | { 44 | self.already_in_arena.remove(&self.arena[id]); 45 | self.arena.delete(id); 46 | } 47 | 48 | /// Iterate over the items in this arena and their ids. 49 | pub fn iter(&self) -> impl Iterator, &T)> { 50 | self.arena.iter() 51 | } 52 | } 53 | 54 | impl ops::Index> for ArenaSet { 55 | type Output = T; 56 | 57 | #[inline] 58 | fn index(&self, id: Id) -> &T { 59 | &self.arena[id] 60 | } 61 | } 62 | 63 | impl ops::IndexMut> for ArenaSet { 64 | #[inline] 65 | fn index_mut(&mut self, id: Id) -> &mut T { 66 | &mut self.arena[id] 67 | } 68 | } 69 | 70 | impl Default for ArenaSet { 71 | fn default() -> ArenaSet { 72 | ArenaSet::new() 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/const_expr.rs: -------------------------------------------------------------------------------- 1 | //! Handling wasm constant values 2 | 3 | use crate::emit::EmitContext; 4 | use crate::ir::Value; 5 | use crate::parse::IndicesToIds; 6 | use crate::RefType; 7 | use crate::{FunctionId, GlobalId, Result}; 8 | use anyhow::bail; 9 | 10 | /// A constant which is produced in WebAssembly, typically used in global 11 | /// initializers or element/data offsets. 12 | #[derive(Debug, Copy, Clone)] 13 | pub enum ConstExpr { 14 | /// An immediate constant value 15 | Value(Value), 16 | /// A constant value referenced by the global specified 17 | Global(GlobalId), 18 | /// A null reference 19 | RefNull(RefType), 20 | /// A function initializer 21 | RefFunc(FunctionId), 22 | } 23 | 24 | impl ConstExpr { 25 | pub(crate) fn eval(init: &wasmparser::ConstExpr, ids: &IndicesToIds) -> Result { 26 | use wasmparser::Operator::*; 27 | let mut reader = init.get_operators_reader(); 28 | let val = match reader.read()? { 29 | I32Const { value } => ConstExpr::Value(Value::I32(value)), 30 | I64Const { value } => ConstExpr::Value(Value::I64(value)), 31 | F32Const { value } => ConstExpr::Value(Value::F32(f32::from_bits(value.bits()))), 32 | F64Const { value } => ConstExpr::Value(Value::F64(f64::from_bits(value.bits()))), 33 | V128Const { value } => ConstExpr::Value(Value::V128(v128_to_u128(&value))), 34 | GlobalGet { global_index } => ConstExpr::Global(ids.get_global(global_index)?), 35 | RefNull { hty } => { 36 | let val_type = match hty { 37 | wasmparser::HeapType::Abstract { shared: _, ty } => match ty { 38 | wasmparser::AbstractHeapType::Func => RefType::Funcref, 39 | wasmparser::AbstractHeapType::Extern => RefType::Externref, 40 | other => bail!( 41 | "unsupported abstract heap type in constant expression: {other:?}" 42 | ), 43 | }, 44 | wasmparser::HeapType::Concrete(_) => { 45 | bail!("unsupported concrete heap type in constant expression") 46 | } 47 | }; 48 | ConstExpr::RefNull(val_type) 49 | } 50 | RefFunc { function_index } => ConstExpr::RefFunc(ids.get_func(function_index)?), 51 | _ => bail!("invalid constant expression"), 52 | }; 53 | match reader.read()? { 54 | End => {} 55 | _ => bail!("invalid constant expression"), 56 | } 57 | reader.ensure_end()?; 58 | Ok(val) 59 | } 60 | 61 | pub(crate) fn to_wasmencoder_type(&self, cx: &EmitContext) -> wasm_encoder::ConstExpr { 62 | match self { 63 | ConstExpr::Value(v) => match v { 64 | Value::I32(v) => wasm_encoder::ConstExpr::i32_const(*v), 65 | Value::I64(v) => wasm_encoder::ConstExpr::i64_const(*v), 66 | Value::F32(v) => wasm_encoder::ConstExpr::f32_const(*v), 67 | Value::F64(v) => wasm_encoder::ConstExpr::f64_const(*v), 68 | Value::V128(v) => wasm_encoder::ConstExpr::v128_const(*v as i128), 69 | }, 70 | ConstExpr::Global(g) => { 71 | wasm_encoder::ConstExpr::global_get(cx.indices.get_global_index(*g)) 72 | } 73 | ConstExpr::RefNull(ty) => wasm_encoder::ConstExpr::ref_null(match ty { 74 | RefType::Externref => wasm_encoder::HeapType::Abstract { 75 | shared: false, 76 | ty: wasm_encoder::AbstractHeapType::Extern, 77 | }, 78 | RefType::Funcref => wasm_encoder::HeapType::Abstract { 79 | shared: false, 80 | ty: wasm_encoder::AbstractHeapType::Func, 81 | }, 82 | }), 83 | ConstExpr::RefFunc(f) => { 84 | wasm_encoder::ConstExpr::ref_func(cx.indices.get_func_index(*f)) 85 | } 86 | } 87 | } 88 | } 89 | 90 | pub(crate) fn v128_to_u128(value: &wasmparser::V128) -> u128 { 91 | let n = value.bytes(); 92 | ((n[0] as u128) << 0) 93 | | ((n[1] as u128) << 8) 94 | | ((n[2] as u128) << 16) 95 | | ((n[3] as u128) << 24) 96 | | ((n[4] as u128) << 32) 97 | | ((n[5] as u128) << 40) 98 | | ((n[6] as u128) << 48) 99 | | ((n[7] as u128) << 56) 100 | | ((n[8] as u128) << 64) 101 | | ((n[9] as u128) << 72) 102 | | ((n[10] as u128) << 80) 103 | | ((n[11] as u128) << 88) 104 | | ((n[12] as u128) << 96) 105 | | ((n[13] as u128) << 104) 106 | | ((n[14] as u128) << 112) 107 | | ((n[15] as u128) << 120) 108 | } 109 | -------------------------------------------------------------------------------- /src/emit.rs: -------------------------------------------------------------------------------- 1 | //! Traits and code for emitting high-level structures as low-level, raw wasm 2 | //! structures. E.g. translating from globally unique identifiers down to the 3 | //! raw wasm structure's index spaces. 4 | 5 | use crate::ir::Local; 6 | use crate::map::{IdHashMap, IdHashSet}; 7 | use crate::{CodeTransform, Global, GlobalId, Memory, MemoryId, Module, Table, TableId}; 8 | use crate::{Data, DataId, Element, ElementId, Function, FunctionId}; 9 | use crate::{Type, TypeId}; 10 | 11 | pub struct EmitContext<'a> { 12 | pub module: &'a Module, 13 | pub indices: &'a mut IdsToIndices, 14 | pub wasm_module: wasm_encoder::Module, 15 | pub locals: IdHashMap>, 16 | pub code_transform: CodeTransform, 17 | } 18 | 19 | /// Anything that can be lowered to raw wasm structures. 20 | pub trait Emit { 21 | /// Emit `self` into the given context. 22 | fn emit(&self, cx: &mut EmitContext); 23 | } 24 | 25 | impl<'a, T: ?Sized + Emit> Emit for &'a T { 26 | fn emit(&self, cx: &mut EmitContext) { 27 | T::emit(self, cx) 28 | } 29 | } 30 | 31 | /// Maps our high-level identifiers to the raw indices they end up emitted at. 32 | /// 33 | /// As we lower to raw wasm structures, we cement various constructs' locations 34 | /// in their respective index spaces. For example, a type with some id `A` ends 35 | /// up being the `i^th` type emitted in the raw wasm type section. When a 36 | /// function references that type, it needs to reference it by its `i` index 37 | /// since the identifier `A` doesn't exist at the raw wasm level. 38 | #[derive(Debug, Default)] 39 | pub struct IdsToIndices { 40 | tables: IdHashMap, 41 | types: IdHashMap, 42 | funcs: IdHashMap, 43 | globals: IdHashMap, 44 | memories: IdHashMap, 45 | elements: IdHashMap, 46 | data: IdHashMap, 47 | pub(crate) locals: IdHashMap>, 48 | } 49 | 50 | macro_rules! define_get_index { 51 | ( $( 52 | $get_name:ident, $id_ty:ty, $member:ident; 53 | )* ) => { 54 | impl IdsToIndices { 55 | $( 56 | /// Get the index for the given identifier. 57 | #[inline] 58 | pub fn $get_name(&self, id: $id_ty) -> u32 { 59 | self.$member.get(&id).cloned().unwrap_or_else(|| panic!( 60 | "{}: Should never try and get the index for an identifier that has not already had \ 61 | its index set. This means that either we are attempting to get the index of \ 62 | an unused identifier, or that we are emitting sections in the wrong order. \n\n\ 63 | id = {:?}", 64 | stringify!($get_name), 65 | id, 66 | )) 67 | } 68 | )* 69 | } 70 | }; 71 | } 72 | 73 | macro_rules! define_get_push_index { 74 | ( $( 75 | $get_name:ident, $push_name:ident, $id_ty:ty, $member:ident; 76 | )* ) => { 77 | define_get_index!( $( $get_name, $id_ty, $member; )* ); 78 | impl IdsToIndices { 79 | $( 80 | /// Adds the given identifier to this set, assigning it the next 81 | /// available index. 82 | #[inline] 83 | pub(crate) fn $push_name(&mut self, id: $id_ty) { 84 | let idx = self.$member.len() as u32; 85 | log::trace!(concat!(stringify!($push_name),": assigning index {} to {:?}"), idx, id); 86 | self.$member.insert(id, idx); 87 | } 88 | )* 89 | } 90 | }; 91 | } 92 | 93 | define_get_push_index! { 94 | get_table_index, push_table, TableId, tables; 95 | get_type_index, push_type, TypeId, types; 96 | get_func_index, push_func, FunctionId, funcs; 97 | get_global_index, push_global, GlobalId, globals; 98 | get_memory_index, push_memory, MemoryId, memories; 99 | get_element_index, push_element, ElementId, elements; 100 | } 101 | define_get_index! { 102 | get_data_index, DataId, data; 103 | } 104 | 105 | impl IdsToIndices { 106 | /// Sets the data index to the specified value 107 | pub(crate) fn set_data_index(&mut self, id: DataId, idx: u32) { 108 | self.data.insert(id, idx); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | //! Error types and utilities. 2 | 3 | use std::fmt; 4 | 5 | /// Either `Ok(T)` or `Err(failure::Error)`. 6 | pub use anyhow::Result; 7 | 8 | /// A leaf wasm error type. 9 | /// 10 | /// Just an enum with no further information. Extra diagnostics are attached via 11 | /// failure's `context` method. 12 | #[derive(Copy, Clone, Eq, PartialEq, Debug)] 13 | pub enum ErrorKind { 14 | /// Given invalid input wasm. 15 | InvalidWasm, 16 | } 17 | 18 | impl fmt::Display for ErrorKind { 19 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 20 | match self { 21 | ErrorKind::InvalidWasm => "The input WebAssembly is invalid".fmt(f), 22 | } 23 | } 24 | } 25 | 26 | impl std::error::Error for ErrorKind {} 27 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! The `walrus` WebAssembly transformations library. 2 | 3 | #![deny(missing_debug_implementations)] 4 | #![deny(missing_docs)] 5 | 6 | #[cfg(feature = "parallel")] 7 | macro_rules! maybe_parallel { 8 | ($e:ident.($serial:ident | $parallel:ident)) => { 9 | $e.$parallel() 10 | }; 11 | } 12 | 13 | #[cfg(not(feature = "parallel"))] 14 | macro_rules! maybe_parallel { 15 | ($e:ident.($serial:ident | $parallel:ident)) => { 16 | $e.$serial() 17 | }; 18 | } 19 | 20 | mod arena_set; 21 | mod const_expr; 22 | pub mod dot; 23 | mod emit; 24 | mod error; 25 | mod function_builder; 26 | pub mod ir; 27 | mod map; 28 | mod module; 29 | mod parse; 30 | pub mod passes; 31 | mod tombstone_arena; 32 | mod ty; 33 | 34 | pub use crate::const_expr::ConstExpr; 35 | pub use crate::emit::IdsToIndices; 36 | pub use crate::error::{ErrorKind, Result}; 37 | pub use crate::function_builder::{FunctionBuilder, InstrSeqBuilder}; 38 | pub use crate::ir::{Local, LocalId}; 39 | pub use crate::module::*; 40 | pub use crate::parse::IndicesToIds; 41 | pub use crate::ty::{RefType, Type, TypeId, ValType}; 42 | -------------------------------------------------------------------------------- /src/map.rs: -------------------------------------------------------------------------------- 1 | //! Walrus-specific hash maps and hash sets which typically hash much more 2 | //! quickly than libstd's hash map which isn't optimized for speed. 3 | 4 | use id_arena::Id; 5 | use std::collections::{HashMap, HashSet}; 6 | use std::hash::{BuildHasher, Hasher}; 7 | 8 | /// Type parameter for the hasher of a `HashMap` or `HashSet` 9 | #[derive(Default, Copy, Clone, Debug)] 10 | pub struct BuildIdHasher; 11 | 12 | /// Hasher constructed by `BuildIdHasher`, only ever used to hash an `Id` 13 | #[derive(Default, Copy, Clone, Debug)] 14 | pub struct IdHasher { 15 | hash: u64, 16 | } 17 | 18 | pub type IdHashMap = HashMap, V, BuildIdHasher>; 19 | pub type IdHashSet = HashSet, BuildIdHasher>; 20 | 21 | impl BuildHasher for BuildIdHasher { 22 | type Hasher = IdHasher; 23 | 24 | fn build_hasher(&self) -> IdHasher { 25 | IdHasher { hash: 0 } 26 | } 27 | } 28 | 29 | // This is the "speed" of this hasher. We're only ever going to hash `Id`, 30 | // and `Id` is composed of two parts: one part arena ID and one part index 31 | // in the arena. The arena ID is a 32-bit integer and the index is a `usize`, so 32 | // we're only going to handle those two types here. 33 | // 34 | // The goal is to produce a 64-bit result, and 32 of those comes from the arena 35 | // ID. We can assume that none of the arena indexes are larger than 32-bit 36 | // because wasm can't encode more than 2^32 items anyway, so we can basically 37 | // just shift each item into the lower 32-bits of the hash. Ideally this'll end 38 | // up producing a very optimized version of hashing! 39 | // 40 | // To explore this a bit, see https://godbolt.org/z/QxkXgF 41 | impl Hasher for IdHasher { 42 | fn write_u32(&mut self, amt: u32) { 43 | self.hash <<= 32; 44 | self.hash |= amt as u64; 45 | } 46 | 47 | fn write_usize(&mut self, amt: usize) { 48 | self.hash <<= 32; 49 | self.hash |= amt as u64; 50 | } 51 | 52 | fn write(&mut self, _other: &[u8]) { 53 | panic!("hashing an `Id` should only be usize/u32") 54 | } 55 | 56 | fn finish(&self) -> u64 { 57 | self.hash 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/module/config.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Result; 2 | use crate::ir::InstrLocId; 3 | use crate::module::Module; 4 | use crate::parse::IndicesToIds; 5 | use std::fmt; 6 | use std::path::Path; 7 | use wasmparser::WasmFeatures; 8 | 9 | /// Configuration for a `Module` which currently affects parsing. 10 | #[derive(Default)] 11 | pub struct ModuleConfig { 12 | pub(crate) generate_dwarf: bool, 13 | pub(crate) generate_synthetic_names_for_anonymous_items: bool, 14 | pub(crate) only_stable_features: bool, 15 | pub(crate) skip_strict_validate: bool, 16 | pub(crate) skip_producers_section: bool, 17 | pub(crate) skip_name_section: bool, 18 | pub(crate) preserve_code_transform: bool, 19 | pub(crate) on_parse: 20 | Option Result<()> + Sync + Send + 'static>>, 21 | pub(crate) on_instr_loc: Option InstrLocId + Sync + Send + 'static>>, 22 | } 23 | 24 | impl Clone for ModuleConfig { 25 | fn clone(&self) -> ModuleConfig { 26 | ModuleConfig { 27 | // These are all cloned... 28 | generate_dwarf: self.generate_dwarf, 29 | generate_synthetic_names_for_anonymous_items: self 30 | .generate_synthetic_names_for_anonymous_items, 31 | only_stable_features: self.only_stable_features, 32 | skip_strict_validate: self.skip_strict_validate, 33 | skip_producers_section: self.skip_producers_section, 34 | skip_name_section: self.skip_name_section, 35 | preserve_code_transform: self.preserve_code_transform, 36 | 37 | // ... and this is left empty. 38 | on_parse: None, 39 | on_instr_loc: None, 40 | } 41 | } 42 | } 43 | 44 | impl fmt::Debug for ModuleConfig { 45 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 46 | // Destructure `self` so that we get compilation errors if we forget to 47 | // add new fields to the debug here. 48 | let ModuleConfig { 49 | ref generate_dwarf, 50 | ref generate_synthetic_names_for_anonymous_items, 51 | ref only_stable_features, 52 | ref skip_strict_validate, 53 | ref skip_producers_section, 54 | ref skip_name_section, 55 | ref preserve_code_transform, 56 | ref on_parse, 57 | ref on_instr_loc, 58 | } = self; 59 | 60 | f.debug_struct("ModuleConfig") 61 | .field("generate_dwarf", generate_dwarf) 62 | .field( 63 | "generate_synthetic_names_for_anonymous_items", 64 | generate_synthetic_names_for_anonymous_items, 65 | ) 66 | .field("only_stable_features", only_stable_features) 67 | .field("skip_strict_validate", skip_strict_validate) 68 | .field("skip_producers_section", skip_producers_section) 69 | .field("skip_name_section", skip_name_section) 70 | .field("preserve_code_transform", preserve_code_transform) 71 | .field("on_parse", &on_parse.as_ref().map(|_| "..")) 72 | .field("on_instr_loc", &on_instr_loc.as_ref().map(|_| "..")) 73 | .finish() 74 | } 75 | } 76 | 77 | impl ModuleConfig { 78 | /// Creates a fresh new configuration with default settings. 79 | pub fn new() -> ModuleConfig { 80 | ModuleConfig::default() 81 | } 82 | 83 | /// Sets a flag to whether DWARF debug sections are generated for this 84 | /// module. 85 | /// 86 | /// By default this flag is `false`. Note that any emitted DWARF is 87 | /// currently wildly incorrect and buggy, and is also larger than the wasm 88 | /// itself! 89 | pub fn generate_dwarf(&mut self, generate: bool) -> &mut ModuleConfig { 90 | self.generate_dwarf = generate; 91 | // generate_dwarf implies preserve_code_transform 92 | self.preserve_code_transform = generate || self.preserve_code_transform; 93 | self 94 | } 95 | 96 | /// Sets a flag to whether the custom "name" section is generated for this 97 | /// module. 98 | /// 99 | /// The "name" section contains symbol names for the module, functions, 100 | /// locals, types, memories, tables, data, elements and globals. 101 | /// When enabled, stack traces will use these names, instead of 102 | /// `wasm-function[123]`. 103 | /// 104 | /// By default this flag is `true`. 105 | pub fn generate_name_section(&mut self, generate: bool) -> &mut ModuleConfig { 106 | self.skip_name_section = !generate; 107 | self 108 | } 109 | 110 | /// Sets a flag to whether synthetic debugging names are generated for 111 | /// anonymous locals/functions/etc when parsing and running passes for this 112 | /// module. 113 | /// 114 | /// By default this flag is `false`, and it will generate quite a few names 115 | /// if enabled! 116 | pub fn generate_synthetic_names_for_anonymous_items( 117 | &mut self, 118 | generate: bool, 119 | ) -> &mut ModuleConfig { 120 | self.generate_synthetic_names_for_anonymous_items = generate; 121 | self 122 | } 123 | 124 | /// Indicates whether the module, after parsing, performs strict validation 125 | /// of the wasm module to adhere with the current version of the wasm 126 | /// specification. 127 | /// 128 | /// This can be expensive for some modules and strictly isn't required to 129 | /// create a `Module` from a wasm file. This includes checks such as "atomic 130 | /// instructions require a shared memory". 131 | /// 132 | /// By default this flag is `true` 133 | pub fn strict_validate(&mut self, strict: bool) -> &mut ModuleConfig { 134 | self.skip_strict_validate = !strict; 135 | self 136 | } 137 | 138 | /// Indicates whether the module will have the "producers" custom section 139 | /// which preserves the original producers and also includes `walrus`. 140 | /// 141 | /// This is generally used for telemetry in browsers, but for otherwise tiny 142 | /// wasm binaries can add some size to the binary. 143 | /// 144 | /// By default this flag is `true` 145 | pub fn generate_producers_section(&mut self, generate: bool) -> &mut ModuleConfig { 146 | self.skip_producers_section = !generate; 147 | self 148 | } 149 | 150 | /// Indicates whether this module is allowed to use only stable WebAssembly 151 | /// features or not. 152 | /// 153 | /// This is currently used to disable some validity checks required by the 154 | /// WebAssembly specification. It's not religiously adhered to throughout 155 | /// the codebase, even if set to `true` some unstable features may still be 156 | /// allowed. 157 | /// 158 | /// By default this flag is `false`. 159 | pub fn only_stable_features(&mut self, only: bool) -> &mut ModuleConfig { 160 | self.only_stable_features = only; 161 | self 162 | } 163 | 164 | /// Returns a `wasmparser::WasmFeatures` based on the enabled proposals 165 | /// which should be used for `wasmparser::Parser`` and `wasmparser::Validator`. 166 | pub(crate) fn get_wasmparser_wasm_features(&self) -> WasmFeatures { 167 | // Start from empty so that we explicitly control what is enabled. 168 | let mut features = WasmFeatures::empty(); 169 | // This is not a proposal. 170 | features.insert(WasmFeatures::FLOATS); 171 | // Always enable [finished proposals](https://github.com/WebAssembly/proposals/blob/main/finished-proposals.md). 172 | features.insert(WasmFeatures::MUTABLE_GLOBAL); 173 | features.insert(WasmFeatures::SATURATING_FLOAT_TO_INT); 174 | features.insert(WasmFeatures::SIGN_EXTENSION); 175 | features.insert(WasmFeatures::MULTI_VALUE); 176 | features.insert(WasmFeatures::REFERENCE_TYPES); 177 | features.insert(WasmFeatures::BULK_MEMORY); 178 | features.insert(WasmFeatures::SIMD); 179 | features.insert(WasmFeatures::RELAXED_SIMD); 180 | features.insert(WasmFeatures::TAIL_CALL); 181 | // Enable supported active proposals. 182 | if !self.only_stable_features { 183 | // # Fully supported proposals. 184 | features.insert(WasmFeatures::MULTI_MEMORY); 185 | features.insert(WasmFeatures::MEMORY64); 186 | // # Partially supported proposals. 187 | // ## threads 188 | // spec-tests/proposals/threads still fail 189 | // round_trip tests already require this feature, so we can't disable it by default. 190 | features.insert(WasmFeatures::THREADS); 191 | } 192 | features 193 | } 194 | 195 | /// Provide a function that is invoked after successfully parsing a module, 196 | /// and gets access to data structures that only exist at parse time, such 197 | /// as the map from indices in the original Wasm to the new walrus IDs. 198 | /// 199 | /// This is a good place to parse custom sections that reference things by 200 | /// index. 201 | /// 202 | /// This will never be invoked for modules that are created from scratch, 203 | /// and are not parsed from an existing Wasm binary. 204 | /// 205 | /// Note that only one `on_parse` function may be registered and subsequent 206 | /// registrations will override the old ones. 207 | /// 208 | /// Note that cloning a `ModuleConfig` will result in a config that does not 209 | /// have an `on_parse` function, even if the original did. 210 | pub fn on_parse(&mut self, f: F) -> &mut ModuleConfig 211 | where 212 | F: Fn(&mut Module, &IndicesToIds) -> Result<()> + Send + Sync + 'static, 213 | { 214 | self.on_parse = Some(Box::new(f) as _); 215 | self 216 | } 217 | 218 | /// Provide a function that is invoked on source location ID step. 219 | /// 220 | /// Note that cloning a `ModuleConfig` will result in a config that does not 221 | /// have an `on_instr_loc` function, even if the original did. 222 | pub fn on_instr_loc(&mut self, f: F) -> &mut ModuleConfig 223 | where 224 | F: Fn(&usize) -> InstrLocId + Send + Sync + 'static, 225 | { 226 | self.on_instr_loc = Some(Box::new(f) as _); 227 | self 228 | } 229 | 230 | /// Sets a flag to whether code transform is preverved during parsing. 231 | /// 232 | /// By default this flag is `false`. 233 | pub fn preserve_code_transform(&mut self, preserve: bool) -> &mut ModuleConfig { 234 | self.preserve_code_transform = preserve; 235 | self 236 | } 237 | 238 | /// Parses an in-memory WebAssembly file into a `Module` using this 239 | /// configuration. 240 | pub fn parse(&self, wasm: &[u8]) -> Result { 241 | Module::parse(wasm, self) 242 | } 243 | 244 | /// Parses a WebAssembly file into a `Module` using this configuration. 245 | pub fn parse_file

(&self, path: P) -> Result 246 | where 247 | P: AsRef, 248 | { 249 | Module::from_file_with_config(path, self) 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /src/module/debug/mod.rs: -------------------------------------------------------------------------------- 1 | mod dwarf; 2 | mod expression; 3 | mod units; 4 | 5 | use crate::emit::{Emit, EmitContext}; 6 | use crate::{CustomSection, Module, RawCustomSection}; 7 | use gimli::*; 8 | 9 | use self::dwarf::{AddressSearchPreference, ConvertContext, DEAD_CODE}; 10 | use self::expression::{CodeAddressConverter, CodeAddressGenerator}; 11 | use self::units::DebuggingInformationCursor; 12 | 13 | /// The DWARF debug section in input WebAssembly binary. 14 | #[derive(Debug, Default)] 15 | pub struct ModuleDebugData { 16 | /// DWARF debug data 17 | pub dwarf: read::Dwarf>, 18 | } 19 | 20 | impl Module { 21 | pub(crate) fn parse_debug_sections( 22 | &mut self, 23 | mut debug_sections: Vec, 24 | ) -> Result<()> { 25 | let load_section = |id: gimli::SectionId| -> Result> { 26 | Ok( 27 | match debug_sections 28 | .iter_mut() 29 | .find(|section| section.name() == id.name()) 30 | { 31 | Some(section) => std::mem::take(&mut section.data), 32 | None => Vec::new(), 33 | }, 34 | ) 35 | }; 36 | 37 | self.debug.dwarf = read::Dwarf::load(load_section)?; 38 | 39 | Ok(()) 40 | } 41 | } 42 | 43 | impl Emit for ModuleDebugData { 44 | fn emit(&self, cx: &mut EmitContext) { 45 | let address_generator = CodeAddressGenerator::new(&cx.module.funcs); 46 | let address_converter = CodeAddressConverter::new(&cx.code_transform); 47 | 48 | let convert_address = |address, search_preference| -> Option { 49 | let address = address as usize; 50 | let code = address_generator.find_address(address, search_preference); 51 | let address = address_converter.find_address(code); 52 | 53 | address 54 | .map(|x| (x - cx.code_transform.code_section_start) as u64) 55 | .map(write::Address::Constant) 56 | }; 57 | 58 | let from_dwarf = cx 59 | .module 60 | .debug 61 | .dwarf 62 | .borrow(|sections| EndianSlice::new(sections.as_ref(), LittleEndian)); 63 | 64 | let mut dwarf = write::Dwarf::from(&from_dwarf, &|address| { 65 | if address == 0 || address == DEAD_CODE { 66 | Some(write::Address::Constant(address)) 67 | } else { 68 | convert_address(address, AddressSearchPreference::InclusiveFunctionEnd) 69 | .or(Some(write::Address::Constant(DEAD_CODE))) 70 | } 71 | }) 72 | .expect("cannot convert to writable dwarf"); 73 | 74 | let units = { 75 | let mut from_unit_headers = from_dwarf.units(); 76 | let mut units = Vec::new(); 77 | 78 | while let Some(from_unit) = from_unit_headers.next().expect("") { 79 | let index = units.len(); 80 | units.push((from_unit, dwarf.units.id(index))); 81 | } 82 | 83 | units 84 | }; 85 | 86 | let mut convert_context = ConvertContext::new( 87 | &from_dwarf.debug_str, 88 | &from_dwarf.debug_line_str, 89 | &mut dwarf.strings, 90 | &mut dwarf.line_strings, 91 | &convert_address, 92 | ); 93 | 94 | for (from_id, id) in units { 95 | let from_unit: Unit, usize> = 96 | from_dwarf.unit(from_id).expect("readable unit"); 97 | let unit = dwarf.units.get_mut(id); 98 | 99 | // perform high pc transformation of DWARF .debug_info 100 | { 101 | let mut from_entries = from_unit.entries(); 102 | let mut entries = DebuggingInformationCursor::new(unit); 103 | 104 | convert_context.convert_high_pc(&mut from_entries, &mut entries); 105 | } 106 | 107 | // perform line program transformation 108 | if let Some(program) = convert_context.convert_unit_line_program(from_unit) { 109 | unit.line_program = program; 110 | } 111 | } 112 | 113 | let mut sections = write::Sections::new(write::EndianVec::new(gimli::LittleEndian)); 114 | dwarf.write(&mut sections).expect("write failed"); 115 | sections 116 | .for_each( 117 | |id: SectionId, data: &write::EndianVec| -> Result<()> { 118 | if !data.slice().is_empty() { 119 | cx.wasm_module.section(&wasm_encoder::CustomSection { 120 | name: id.name().into(), 121 | data: data.slice().into(), 122 | }); 123 | } 124 | Ok(()) 125 | }, 126 | ) 127 | .expect("never"); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/module/debug/units.rs: -------------------------------------------------------------------------------- 1 | use gimli::write::{DebuggingInformationEntry, Unit, UnitEntryId}; 2 | 3 | pub(crate) struct DebuggingInformationCursor<'a> { 4 | entry_id_stack: Vec, 5 | 6 | unit: &'a mut Unit, 7 | 8 | called_next_dfs: bool, 9 | } 10 | 11 | impl<'a> DebuggingInformationCursor<'a> { 12 | pub fn new(unit: &'a mut Unit) -> Self { 13 | Self { 14 | unit, 15 | entry_id_stack: Vec::new(), 16 | called_next_dfs: false, 17 | } 18 | } 19 | 20 | pub fn current(&mut self) -> Option<&mut DebuggingInformationEntry> { 21 | if !self.entry_id_stack.is_empty() { 22 | Some(self.unit.get_mut(*self.entry_id_stack.last().unwrap())) 23 | } else { 24 | None 25 | } 26 | } 27 | 28 | pub fn next_dfs(&mut self) -> Option<&mut DebuggingInformationEntry> { 29 | if !self.called_next_dfs { 30 | let root = self.unit.root(); 31 | self.entry_id_stack.push(root); 32 | self.called_next_dfs = true; 33 | return self.current(); 34 | } 35 | 36 | if self.entry_id_stack.is_empty() { 37 | return None; 38 | } 39 | 40 | let last_element_id = self.entry_id_stack.pop().unwrap(); 41 | let last_element = self.unit.get_mut(last_element_id); 42 | 43 | self.entry_id_stack.append( 44 | &mut last_element 45 | .children() 46 | .map(UnitEntryId::clone) 47 | .rev() 48 | .collect(), 49 | ); 50 | 51 | self.current() 52 | } 53 | } 54 | 55 | #[cfg(test)] 56 | mod tests { 57 | use super::*; 58 | use gimli::constants; 59 | use gimli::write::*; 60 | use gimli::{Encoding, Format}; 61 | 62 | #[test] 63 | fn test_create_instance() { 64 | let mut unit1 = Unit::new( 65 | Encoding { 66 | version: 4, 67 | address_size: 8, 68 | format: Format::Dwarf32, 69 | }, 70 | LineProgram::none(), 71 | ); 72 | 73 | let root_id = unit1.root(); 74 | let child1_id = unit1.add(root_id, constants::DW_TAG_subprogram); 75 | let child2_id = unit1.add(child1_id, constants::DW_TAG_lexical_block); 76 | 77 | let mut cursor = DebuggingInformationCursor::new(&mut unit1); 78 | 79 | assert!(cursor.current().is_none()); 80 | assert_eq!(cursor.next_dfs().unwrap().id(), root_id); 81 | assert_eq!(cursor.next_dfs().unwrap().id(), child1_id); 82 | assert_eq!(cursor.next_dfs().unwrap().id(), child2_id); 83 | assert!(cursor.next_dfs().is_none()); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/module/functions/local_function/context.rs: -------------------------------------------------------------------------------- 1 | //! Context needed when validating instructions and constructing our `Instr` IR. 2 | 3 | use crate::error::{ErrorKind, Result}; 4 | use crate::ir::{BlockKind, Instr, InstrLocId, InstrSeq, InstrSeqId, InstrSeqType}; 5 | use crate::module::functions::{FunctionId, LocalFunction}; 6 | use crate::module::Module; 7 | use crate::parse::IndicesToIds; 8 | use crate::ty::ValType; 9 | use crate::{ModuleTypes, TypeId}; 10 | use anyhow::Context; 11 | 12 | #[derive(Debug)] 13 | pub(crate) struct ControlFrame { 14 | /// The parameter types of the block (checked before entering the block). 15 | pub start_types: Box<[ValType]>, 16 | 17 | /// The result type of the block (used to check its result). 18 | pub end_types: Box<[ValType]>, 19 | 20 | /// If `true`, then this frame is unreachable. This is used to handle 21 | /// stack-polymorphic typing after unconditional branches. 22 | pub unreachable: bool, 23 | 24 | /// The id of this control frame's block. 25 | pub block: InstrSeqId, 26 | 27 | /// This control frame's kind of block, eg loop vs block vs if/else. 28 | pub kind: BlockKind, 29 | } 30 | 31 | /// The control frame stack. 32 | pub(crate) type ControlStack = Vec; 33 | 34 | #[derive(Debug)] 35 | pub(crate) struct ValidationContext<'a> { 36 | /// The module that we're adding a function for. 37 | pub module: &'a Module, 38 | 39 | /// Mapping of indexes back to ids. 40 | pub indices: &'a IndicesToIds, 41 | 42 | /// The arena id of `func`. 43 | pub func_id: FunctionId, 44 | 45 | /// The function being validated/constructed. 46 | pub func: &'a mut LocalFunction, 47 | 48 | /// The control frames stack. 49 | pub controls: &'a mut ControlStack, 50 | 51 | /// If we're currently parsing an if/else instruction, where we're at 52 | pub if_else: Vec, 53 | } 54 | 55 | #[derive(Debug)] 56 | pub struct IfElseState { 57 | pub start: InstrLocId, 58 | pub consequent: InstrSeqId, 59 | pub alternative: Option, 60 | } 61 | 62 | impl<'a> ValidationContext<'a> { 63 | /// Create a new function context. 64 | pub fn new( 65 | module: &'a Module, 66 | indices: &'a IndicesToIds, 67 | func_id: FunctionId, 68 | func: &'a mut LocalFunction, 69 | controls: &'a mut ControlStack, 70 | ) -> ValidationContext<'a> { 71 | ValidationContext { 72 | module, 73 | indices, 74 | func_id, 75 | func, 76 | controls, 77 | if_else: Vec::new(), 78 | } 79 | } 80 | 81 | pub fn push_control( 82 | &mut self, 83 | kind: BlockKind, 84 | start_types: Box<[ValType]>, 85 | end_types: Box<[ValType]>, 86 | ) -> Result { 87 | impl_push_control( 88 | &self.module.types, 89 | kind, 90 | self.func, 91 | self.controls, 92 | start_types, 93 | end_types, 94 | ) 95 | } 96 | 97 | pub fn push_control_with_ty(&mut self, kind: BlockKind, ty: TypeId) -> InstrSeqId { 98 | let (start_types, end_types) = self.module.types.params_results(ty); 99 | let start_types: Box<[_]> = start_types.into(); 100 | let end_types: Box<[_]> = end_types.into(); 101 | impl_push_control_with_ty( 102 | &self.module.types, 103 | kind, 104 | self.func, 105 | self.controls, 106 | ty.into(), 107 | start_types, 108 | end_types, 109 | ) 110 | } 111 | 112 | pub fn pop_control(&mut self) -> Result<(ControlFrame, InstrSeqId)> { 113 | let frame = impl_pop_control(self.controls)?; 114 | let block = frame.block; 115 | Ok((frame, block)) 116 | } 117 | 118 | pub fn unreachable(&mut self) { 119 | let frame = self.controls.last_mut().unwrap(); 120 | frame.unreachable = true; 121 | } 122 | 123 | pub fn control(&self, n: usize) -> Result<&ControlFrame> { 124 | if n >= self.controls.len() { 125 | anyhow::bail!("jump to nonexistent control block"); 126 | } 127 | let idx = self.controls.len() - n - 1; 128 | Ok(&self.controls[idx]) 129 | } 130 | 131 | pub fn alloc_instr_in_block( 132 | &mut self, 133 | block: InstrSeqId, 134 | instr: impl Into, 135 | loc: InstrLocId, 136 | ) { 137 | self.func.block_mut(block).instrs.push((instr.into(), loc)); 138 | } 139 | 140 | pub fn alloc_instr_in_control( 141 | &mut self, 142 | control: usize, 143 | instr: impl Into, 144 | loc: InstrLocId, 145 | ) -> Result<()> { 146 | let frame = self.control(control)?; 147 | if frame.unreachable { 148 | return Ok(()); 149 | } 150 | let block = frame.block; 151 | self.alloc_instr_in_block(block, instr, loc); 152 | Ok(()) 153 | } 154 | 155 | pub fn alloc_instr(&mut self, instr: impl Into, loc: InstrLocId) { 156 | self.alloc_instr_in_control(0, instr, loc).unwrap(); 157 | } 158 | } 159 | 160 | fn impl_push_control( 161 | types: &ModuleTypes, 162 | kind: BlockKind, 163 | func: &mut LocalFunction, 164 | controls: &mut ControlStack, 165 | start_types: Box<[ValType]>, 166 | end_types: Box<[ValType]>, 167 | ) -> Result { 168 | let ty = InstrSeqType::existing(types, &start_types, &end_types).ok_or_else(|| { 169 | anyhow::anyhow!( 170 | "attempted to push a control frame for an instruction \ 171 | sequence with a type that does not exist" 172 | ) 173 | .context(format!("type: {:?} -> {:?}", &start_types, &end_types)) 174 | })?; 175 | 176 | Ok(impl_push_control_with_ty( 177 | types, 178 | kind, 179 | func, 180 | controls, 181 | ty, 182 | start_types, 183 | end_types, 184 | )) 185 | } 186 | 187 | fn impl_push_control_with_ty( 188 | types: &ModuleTypes, 189 | kind: BlockKind, 190 | func: &mut LocalFunction, 191 | controls: &mut ControlStack, 192 | ty: InstrSeqType, 193 | start_types: Box<[ValType]>, 194 | end_types: Box<[ValType]>, 195 | ) -> InstrSeqId { 196 | if let InstrSeqType::MultiValue(ty) = ty { 197 | debug_assert_eq!(types.params(ty), &start_types[..]); 198 | debug_assert_eq!(types.results(ty), &end_types[..]); 199 | } 200 | 201 | let block = func.add_block(|id| InstrSeq::new(id, ty)); 202 | 203 | controls.push(ControlFrame { 204 | start_types, 205 | end_types, 206 | unreachable: false, 207 | block, 208 | kind, 209 | }); 210 | 211 | block 212 | } 213 | 214 | fn impl_pop_control(controls: &mut ControlStack) -> Result { 215 | controls 216 | .last() 217 | .ok_or(ErrorKind::InvalidWasm) 218 | .context("attempted to pop a frame from an empty control stack")?; 219 | let frame = controls.pop().unwrap(); 220 | Ok(frame) 221 | } 222 | -------------------------------------------------------------------------------- /src/module/globals.rs: -------------------------------------------------------------------------------- 1 | //! Globals within a wasm module. 2 | use crate::emit::{Emit, EmitContext}; 3 | use crate::parse::IndicesToIds; 4 | use crate::tombstone_arena::{Id, Tombstone, TombstoneArena}; 5 | use crate::{ConstExpr, ImportId, Module, Result, ValType}; 6 | 7 | /// The id of a global. 8 | pub type GlobalId = Id; 9 | 10 | /// A wasm global. 11 | #[derive(Debug)] 12 | pub struct Global { 13 | // NB: Not public so that it can't get out of sync with the arena this is 14 | // contained within. 15 | id: GlobalId, 16 | /// This global's type. 17 | pub ty: ValType, 18 | /// Whether this global is mutable or not. 19 | pub mutable: bool, 20 | /// Whether this global is shared or not. 21 | pub shared: bool, 22 | /// The kind of global this is 23 | pub kind: GlobalKind, 24 | /// The name of this data, used for debugging purposes in the `name` 25 | /// custom section. 26 | pub name: Option, 27 | } 28 | 29 | impl Tombstone for Global {} 30 | 31 | /// The different kinds of globals a wasm module can have 32 | #[derive(Debug)] 33 | pub enum GlobalKind { 34 | /// An imported global without a known initializer 35 | Import(ImportId), 36 | /// A locally declare global with the specified identifier 37 | Local(ConstExpr), 38 | } 39 | 40 | impl Global { 41 | /// Get this global's id. 42 | pub fn id(&self) -> GlobalId { 43 | self.id 44 | } 45 | } 46 | 47 | /// The set of globals in each function in this module. 48 | #[derive(Debug, Default)] 49 | pub struct ModuleGlobals { 50 | /// The arena where the globals are stored. 51 | arena: TombstoneArena, 52 | } 53 | 54 | impl ModuleGlobals { 55 | /// Adds a new imported global to this list. 56 | pub fn add_import( 57 | &mut self, 58 | ty: ValType, 59 | mutable: bool, 60 | shared: bool, 61 | import_id: ImportId, 62 | ) -> GlobalId { 63 | self.arena.alloc_with_id(|id| Global { 64 | id, 65 | ty, 66 | mutable, 67 | shared, 68 | kind: GlobalKind::Import(import_id), 69 | name: None, 70 | }) 71 | } 72 | 73 | /// Construct a new global, that does not originate from any of the input 74 | /// wasm globals. 75 | pub fn add_local( 76 | &mut self, 77 | ty: ValType, 78 | mutable: bool, 79 | shared: bool, 80 | init: ConstExpr, 81 | ) -> GlobalId { 82 | self.arena.alloc_with_id(|id| Global { 83 | id, 84 | ty, 85 | mutable, 86 | shared, 87 | kind: GlobalKind::Local(init), 88 | name: None, 89 | }) 90 | } 91 | 92 | /// Gets a reference to a global given its id 93 | pub fn get(&self, id: GlobalId) -> &Global { 94 | &self.arena[id] 95 | } 96 | 97 | /// Gets a reference to a global given its id 98 | pub fn get_mut(&mut self, id: GlobalId) -> &mut Global { 99 | &mut self.arena[id] 100 | } 101 | 102 | /// Removes a global from this module. 103 | /// 104 | /// It is up to you to ensure that any potential references to the deleted 105 | /// global are also removed, eg `get_global` expressions. 106 | pub fn delete(&mut self, id: GlobalId) { 107 | self.arena.delete(id); 108 | } 109 | 110 | /// Get a shared reference to this module's globals. 111 | pub fn iter(&self) -> impl Iterator { 112 | self.arena.iter().map(|(_, f)| f) 113 | } 114 | } 115 | 116 | impl Module { 117 | /// Construct a new, empty set of globals for a module. 118 | pub(crate) fn parse_globals( 119 | &mut self, 120 | section: wasmparser::GlobalSectionReader, 121 | ids: &mut IndicesToIds, 122 | ) -> Result<()> { 123 | log::debug!("parse global section"); 124 | for g in section { 125 | let g = g?; 126 | let id = self.globals.add_local( 127 | ValType::parse(&g.ty.content_type)?, 128 | g.ty.mutable, 129 | g.ty.shared, 130 | ConstExpr::eval(&g.init_expr, ids)?, 131 | ); 132 | ids.push_global(id); 133 | } 134 | Ok(()) 135 | } 136 | } 137 | 138 | impl Emit for ModuleGlobals { 139 | fn emit(&self, cx: &mut EmitContext) { 140 | log::debug!("emit global section"); 141 | let mut wasm_global_section = wasm_encoder::GlobalSection::new(); 142 | 143 | fn get_local(global: &Global) -> Option<(&Global, &ConstExpr)> { 144 | match &global.kind { 145 | GlobalKind::Import(_) => None, 146 | GlobalKind::Local(local) => Some((global, local)), 147 | } 148 | } 149 | 150 | // All imported globals emitted earlier during the import section, so 151 | // filter those out. 152 | let globals = self.iter().filter_map(get_local).count(); 153 | if globals == 0 { 154 | return; 155 | } 156 | 157 | for (global, local) in self.iter().filter_map(get_local) { 158 | cx.indices.push_global(global.id()); 159 | 160 | wasm_global_section.global( 161 | wasm_encoder::GlobalType { 162 | val_type: global.ty.to_wasmencoder_type(), 163 | mutable: global.mutable, 164 | shared: global.shared, 165 | }, 166 | &local.to_wasmencoder_type(cx), 167 | ); 168 | } 169 | 170 | cx.wasm_module.section(&wasm_global_section); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/module/locals.rs: -------------------------------------------------------------------------------- 1 | //! All the locals used by functions in a wasm module. 2 | 3 | use crate::ir::{Local, LocalId}; 4 | use crate::ty::ValType; 5 | use id_arena::Arena; 6 | 7 | /// The set of locals in each function in this module. 8 | #[derive(Debug, Default)] 9 | pub struct ModuleLocals { 10 | arena: Arena, 11 | } 12 | 13 | impl ModuleLocals { 14 | /// Construct a new local, that does not originate from any of the input 15 | /// wasm locals. 16 | pub fn add(&mut self, ty: ValType) -> LocalId { 17 | let id = self.arena.next_id(); 18 | let id2 = self.arena.alloc(Local::new(id, ty)); 19 | debug_assert_eq!(id, id2); 20 | id 21 | } 22 | 23 | /// Gets a reference to a local given its id 24 | pub fn get(&self, id: LocalId) -> &Local { 25 | &self.arena[id] 26 | } 27 | 28 | /// Gets a reference to a local given its id 29 | pub fn get_mut(&mut self, id: LocalId) -> &mut Local { 30 | &mut self.arena[id] 31 | } 32 | 33 | /// Get a shared reference to this module's locals. 34 | pub fn iter(&self) -> impl Iterator { 35 | self.arena.iter().map(|(_, f)| f) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/module/memories.rs: -------------------------------------------------------------------------------- 1 | //! Memories used in a wasm module. 2 | 3 | use crate::emit::{Emit, EmitContext}; 4 | use crate::map::IdHashSet; 5 | use crate::parse::IndicesToIds; 6 | use crate::tombstone_arena::{Id, Tombstone, TombstoneArena}; 7 | use crate::{Data, ImportId, Module, Result}; 8 | 9 | /// The id of a memory. 10 | pub type MemoryId = Id; 11 | 12 | /// A memory in the wasm. 13 | #[derive(Debug)] 14 | pub struct Memory { 15 | id: MemoryId, 16 | /// Whether or not this is a “shared” memory 17 | /// 18 | /// This is part of the threads proposal. 19 | pub shared: bool, 20 | /// Whether or not this is a 64-bit memory. 21 | /// 22 | /// This is part of the memory64 proposal. 23 | pub memory64: bool, 24 | /// Initial size of this memory, in wasm pages. 25 | pub initial: u64, 26 | /// Optional maximum size of this memory, in wasm pages. 27 | pub maximum: Option, 28 | ///The log base 2 of the memory’s custom page size. 29 | /// 30 | /// Memory pages are, by default, 64KiB large (i.e. 216 or 65536). 31 | /// 32 | /// The custom-page-sizes proposal allows changing it to other values. 33 | pub page_size_log2: Option, 34 | /// Whether or not this memory is imported, and if so from where. 35 | pub import: Option, 36 | /// Active data segments that will be used to initialize this memory. 37 | pub data_segments: IdHashSet, 38 | /// The name of this memory, used for debugging purposes in the `name` 39 | /// custom section. 40 | pub name: Option, 41 | } 42 | 43 | impl Tombstone for Memory { 44 | fn on_delete(&mut self) { 45 | self.data_segments = Default::default(); 46 | } 47 | } 48 | 49 | impl Memory { 50 | /// Return the id of this memory 51 | pub fn id(&self) -> MemoryId { 52 | self.id 53 | } 54 | } 55 | 56 | /// The set of memories in this module. 57 | #[derive(Debug, Default)] 58 | pub struct ModuleMemories { 59 | arena: TombstoneArena, 60 | } 61 | 62 | impl ModuleMemories { 63 | /// Add an imported memory 64 | pub fn add_import( 65 | &mut self, 66 | shared: bool, 67 | memory64: bool, 68 | initial: u64, 69 | maximum: Option, 70 | page_size_log2: Option, 71 | import: ImportId, 72 | ) -> MemoryId { 73 | let id = self.arena.next_id(); 74 | let id2 = self.arena.alloc(Memory { 75 | id, 76 | shared, 77 | memory64, 78 | initial, 79 | maximum, 80 | page_size_log2, 81 | import: Some(import), 82 | data_segments: Default::default(), 83 | name: None, 84 | }); 85 | debug_assert_eq!(id, id2); 86 | id 87 | } 88 | 89 | /// Construct a new memory, that does not originate from any of the input 90 | /// wasm memories. 91 | pub fn add_local( 92 | &mut self, 93 | shared: bool, 94 | memory64: bool, 95 | initial: u64, 96 | maximum: Option, 97 | page_size_log2: Option, 98 | ) -> MemoryId { 99 | let id = self.arena.next_id(); 100 | let id2 = self.arena.alloc(Memory { 101 | id, 102 | shared, 103 | memory64, 104 | initial, 105 | maximum, 106 | page_size_log2, 107 | import: None, 108 | data_segments: Default::default(), 109 | name: None, 110 | }); 111 | debug_assert_eq!(id, id2); 112 | id 113 | } 114 | 115 | /// Gets a reference to a memory given its id 116 | pub fn get(&self, id: MemoryId) -> &Memory { 117 | &self.arena[id] 118 | } 119 | 120 | /// Gets a reference to a memory given its id 121 | pub fn get_mut(&mut self, id: MemoryId) -> &mut Memory { 122 | &mut self.arena[id] 123 | } 124 | 125 | /// Removes a memory from this module. 126 | /// 127 | /// It is up to you to ensure that any potential references to the deleted 128 | /// memory are also removed, eg `mem.load` expressions and exports. 129 | pub fn delete(&mut self, id: MemoryId) { 130 | self.arena.delete(id); 131 | } 132 | 133 | /// Get a shared reference to this module's memories. 134 | pub fn iter(&self) -> impl Iterator { 135 | self.arena.iter().map(|(_, f)| f) 136 | } 137 | 138 | /// Get a mutable reference to this module's memories. 139 | pub fn iter_mut(&mut self) -> impl Iterator { 140 | self.arena.iter_mut().map(|(_, f)| f) 141 | } 142 | 143 | /// Get the number of memories in this module 144 | pub fn len(&self) -> usize { 145 | self.arena.len() 146 | } 147 | 148 | /// Checks if there are no memories in this module 149 | pub fn is_empty(&self) -> bool { 150 | self.arena.len() == 0 151 | } 152 | } 153 | 154 | impl Module { 155 | /// Construct a new, empty set of memories for a module. 156 | pub(crate) fn parse_memories( 157 | &mut self, 158 | section: wasmparser::MemorySectionReader, 159 | ids: &mut IndicesToIds, 160 | ) -> Result<()> { 161 | log::debug!("parse memory section"); 162 | for m in section { 163 | let m = m?; 164 | let id = self.memories.add_local( 165 | m.shared, 166 | m.memory64, 167 | m.initial, 168 | m.maximum, 169 | m.page_size_log2, 170 | ); 171 | ids.push_memory(id); 172 | } 173 | Ok(()) 174 | } 175 | } 176 | 177 | impl Emit for ModuleMemories { 178 | fn emit(&self, cx: &mut EmitContext) { 179 | log::debug!("emit memory section"); 180 | 181 | let mut wasm_memory_section = wasm_encoder::MemorySection::new(); 182 | 183 | // imported memories are emitted earlier 184 | let memories = self.iter().filter(|m| m.import.is_none()).count(); 185 | if memories == 0 { 186 | return; 187 | } 188 | 189 | for memory in self.iter().filter(|m| m.import.is_none()) { 190 | cx.indices.push_memory(memory.id()); 191 | 192 | wasm_memory_section.memory(wasm_encoder::MemoryType { 193 | minimum: memory.initial, 194 | maximum: memory.maximum, 195 | memory64: memory.memory64, 196 | shared: memory.shared, 197 | page_size_log2: memory.page_size_log2, 198 | }); 199 | } 200 | 201 | cx.wasm_module.section(&wasm_memory_section); 202 | } 203 | } 204 | 205 | #[cfg(test)] 206 | mod tests { 207 | use crate::Module; 208 | 209 | #[test] 210 | fn memories_len() { 211 | let mut module = Module::default(); 212 | assert_eq!(module.memories.len(), 0); 213 | 214 | module.memories.add_local(false, false, 0, Some(1024), None); 215 | assert_eq!(module.memories.len(), 1); 216 | 217 | module 218 | .memories 219 | .add_local(true, true, 1024, Some(2048), Some(16)); 220 | assert_eq!(module.memories.len(), 2); 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /src/module/producers.rs: -------------------------------------------------------------------------------- 1 | //! Handling of the wasm `producers` section 2 | //! 3 | //! Specified upstream at 4 | //! https://github.com/WebAssembly/tool-conventions/blob/master/ProducersSection.md 5 | 6 | use crate::emit::{Emit, EmitContext}; 7 | use crate::error::Result; 8 | use crate::module::Module; 9 | 10 | /// Representation of the wasm custom section `producers` 11 | #[derive(Debug, Default)] 12 | pub struct ModuleProducers { 13 | fields: Vec, 14 | } 15 | 16 | #[derive(Debug)] 17 | struct Field { 18 | name: String, 19 | values: Vec, 20 | } 21 | 22 | #[derive(Debug)] 23 | struct Value { 24 | name: String, 25 | version: String, 26 | } 27 | 28 | impl ModuleProducers { 29 | /// Adds a new `language` (versioned) to the producers section 30 | pub fn add_language(&mut self, language: &str, version: &str) { 31 | self.field("language", language, version); 32 | } 33 | 34 | /// Adds a new `processed-by` (versioned) to the producers section 35 | pub fn add_processed_by(&mut self, tool: &str, version: &str) { 36 | self.field("processed-by", tool, version); 37 | } 38 | 39 | /// Adds a new `sdk` (versioned) to the producers section 40 | pub fn add_sdk(&mut self, sdk: &str, version: &str) { 41 | self.field("sdk", sdk, version); 42 | } 43 | 44 | fn field(&mut self, field_name: &str, name: &str, version: &str) { 45 | let new_value = Value { 46 | name: name.to_string(), 47 | version: version.to_string(), 48 | }; 49 | for field in self.fields.iter_mut() { 50 | if field.name != field_name { 51 | continue; 52 | } 53 | 54 | for value in field.values.iter_mut() { 55 | if value.name == name { 56 | *value = new_value; 57 | return; 58 | } 59 | } 60 | field.values.push(new_value); 61 | return; 62 | } 63 | self.fields.push(Field { 64 | name: field_name.to_string(), 65 | values: vec![new_value], 66 | }) 67 | } 68 | 69 | /// Clear the producers section of all keys/values 70 | pub fn clear(&mut self) { 71 | self.fields.truncate(0); 72 | } 73 | } 74 | 75 | impl Module { 76 | /// Parse a producers section from the custom section payload specified. 77 | pub(crate) fn parse_producers_section( 78 | &mut self, 79 | data: wasmparser::ProducersSectionReader, 80 | ) -> Result<()> { 81 | log::debug!("parse producers section"); 82 | 83 | for field in data { 84 | let field = field?; 85 | let mut values = Vec::new(); 86 | for value in field.values { 87 | let value = value?; 88 | values.push(Value { 89 | name: value.name.to_string(), 90 | version: value.version.to_string(), 91 | }); 92 | } 93 | let name = field.name.to_string(); 94 | self.producers.fields.push(Field { name, values }); 95 | } 96 | 97 | Ok(()) 98 | } 99 | } 100 | 101 | impl Emit for ModuleProducers { 102 | fn emit(&self, cx: &mut EmitContext) { 103 | log::debug!("emit producers section"); 104 | if self.fields.is_empty() { 105 | return; 106 | } 107 | let mut wasm_producers_section = wasm_encoder::ProducersSection::new(); 108 | for field in &self.fields { 109 | let mut producers_field = wasm_encoder::ProducersField::new(); 110 | for value in &field.values { 111 | producers_field.value(&value.name, &value.version); 112 | } 113 | wasm_producers_section.field(&field.name, &producers_field); 114 | } 115 | cx.wasm_module.section(&wasm_producers_section); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/module/tables.rs: -------------------------------------------------------------------------------- 1 | //! Tables within a wasm module. 2 | 3 | use std::convert::TryInto; 4 | 5 | use crate::emit::{Emit, EmitContext}; 6 | use crate::map::IdHashSet; 7 | use crate::parse::IndicesToIds; 8 | use crate::tombstone_arena::{Id, Tombstone, TombstoneArena}; 9 | use crate::{Element, ImportId, Module, RefType, Result}; 10 | use anyhow::bail; 11 | 12 | /// The id of a table. 13 | pub type TableId = Id; 14 | 15 | /// A table in the wasm. 16 | #[derive(Debug)] 17 | pub struct Table { 18 | id: TableId, 19 | /// Whether or not this is a 64-bit table. 20 | pub table64: bool, 21 | /// The initial size of this table 22 | pub initial: u64, 23 | /// The maximum size of this table 24 | pub maximum: Option, 25 | /// The type of the elements in this table 26 | pub element_ty: RefType, 27 | /// Whether or not this table is imported, and if so what imports it. 28 | pub import: Option, 29 | /// Active data segments that will be used to initialize this memory. 30 | pub elem_segments: IdHashSet, 31 | /// The name of this table, used for debugging purposes in the `name` 32 | /// custom section. 33 | pub name: Option, 34 | } 35 | 36 | impl Tombstone for Table {} 37 | 38 | impl Table { 39 | /// Get this table's id. 40 | pub fn id(&self) -> TableId { 41 | self.id 42 | } 43 | } 44 | 45 | /// The set of tables in this module. 46 | #[derive(Debug, Default)] 47 | pub struct ModuleTables { 48 | /// The arena containing this module's tables. 49 | arena: TombstoneArena
, 50 | } 51 | 52 | impl ModuleTables { 53 | /// Adds a new imported table to this list of tables 54 | pub fn add_import( 55 | &mut self, 56 | table64: bool, 57 | initial: u64, 58 | maximum: Option, 59 | element_ty: RefType, 60 | import: ImportId, 61 | ) -> TableId { 62 | let id = self.arena.next_id(); 63 | self.arena.alloc(Table { 64 | id, 65 | table64, 66 | initial, 67 | maximum, 68 | element_ty, 69 | import: Some(import), 70 | elem_segments: Default::default(), 71 | name: None, 72 | }) 73 | } 74 | 75 | /// Construct a new table, that does not originate from any of the input 76 | /// wasm tables. 77 | pub fn add_local( 78 | &mut self, 79 | table64: bool, 80 | initial: u64, 81 | maximum: Option, 82 | element_ty: RefType, 83 | ) -> TableId { 84 | let id = self.arena.next_id(); 85 | let id2 = self.arena.alloc(Table { 86 | id, 87 | table64, 88 | initial, 89 | maximum, 90 | element_ty, 91 | import: None, 92 | elem_segments: Default::default(), 93 | name: None, 94 | }); 95 | debug_assert_eq!(id, id2); 96 | id 97 | } 98 | 99 | /// Returns the actual table associated with an ID 100 | pub fn get(&self, table: TableId) -> &Table { 101 | &self.arena[table] 102 | } 103 | 104 | /// Returns the actual table associated with an ID 105 | pub fn get_mut(&mut self, table: TableId) -> &mut Table { 106 | &mut self.arena[table] 107 | } 108 | 109 | /// Removes a table from this module. 110 | /// 111 | /// It is up to you to ensure that any potential references to the deleted 112 | /// table are also removed, eg `call_indirect` expressions and exports, etc. 113 | pub fn delete(&mut self, id: TableId) { 114 | self.arena.delete(id); 115 | } 116 | 117 | /// Iterates over all tables in this section. 118 | pub fn iter(&self) -> impl Iterator { 119 | self.arena.iter().map(|p| p.1) 120 | } 121 | 122 | /// Finds a unique function table in a module. 123 | /// 124 | /// Modules produced by compilers like LLVM typically have one function 125 | /// table for indirect function calls. This function will look for a single 126 | /// function table inside this module, and return that if found. If no 127 | /// function tables are present `None` will be returned 128 | /// 129 | /// # Errors 130 | /// 131 | /// Returns an error if there are two function tables in this module 132 | pub fn main_function_table(&self) -> Result> { 133 | let mut tables = self.iter().filter(|t| t.element_ty == RefType::Funcref); 134 | let id = match tables.next() { 135 | Some(t) => t.id(), 136 | None => return Ok(None), 137 | }; 138 | if tables.next().is_some() { 139 | bail!("module contains more than one function table"); 140 | } 141 | Ok(Some(id)) 142 | } 143 | 144 | /// Iterates over all tables in this section. 145 | pub fn iter_mut(&mut self) -> impl Iterator { 146 | self.arena.iter_mut().map(|p| p.1) 147 | } 148 | } 149 | 150 | impl Module { 151 | /// Construct a new, empty set of tables for a module. 152 | pub(crate) fn parse_tables( 153 | &mut self, 154 | section: wasmparser::TableSectionReader, 155 | ids: &mut IndicesToIds, 156 | ) -> Result<()> { 157 | log::debug!("parse table section"); 158 | for t in section { 159 | let t = t?; 160 | let id = self.tables.add_local( 161 | t.ty.table64, 162 | t.ty.initial, 163 | t.ty.maximum, 164 | t.ty.element_type.try_into()?, 165 | ); 166 | ids.push_table(id); 167 | } 168 | Ok(()) 169 | } 170 | } 171 | 172 | impl Emit for ModuleTables { 173 | fn emit(&self, cx: &mut EmitContext) { 174 | log::debug!("emit table section"); 175 | 176 | let mut wasm_table_section = wasm_encoder::TableSection::new(); 177 | 178 | // Skip imported tables because those are emitted in the import section. 179 | let tables = self.iter().filter(|t| t.import.is_none()).count(); 180 | if tables == 0 { 181 | return; 182 | } 183 | 184 | for table in self.iter().filter(|t| t.import.is_none()) { 185 | cx.indices.push_table(table.id()); 186 | 187 | wasm_table_section.table(wasm_encoder::TableType { 188 | table64: table.table64, 189 | minimum: table.initial, 190 | maximum: table.maximum, 191 | element_type: match table.element_ty { 192 | RefType::Externref => wasm_encoder::RefType::EXTERNREF, 193 | RefType::Funcref => wasm_encoder::RefType::FUNCREF, 194 | }, 195 | shared: false, 196 | }); 197 | } 198 | 199 | cx.wasm_module.section(&wasm_table_section); 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /src/module/types.rs: -------------------------------------------------------------------------------- 1 | //! Types in a wasm module. 2 | 3 | use crate::arena_set::ArenaSet; 4 | use crate::emit::{Emit, EmitContext}; 5 | use crate::error::Result; 6 | use crate::module::Module; 7 | use crate::parse::IndicesToIds; 8 | use crate::ty::{Type, TypeId, ValType}; 9 | 10 | /// The set of de-duplicated types within a module. 11 | #[derive(Debug, Default)] 12 | pub struct ModuleTypes { 13 | arena: ArenaSet, 14 | } 15 | 16 | impl ModuleTypes { 17 | /// Get a type associated with an ID 18 | pub fn get(&self, id: TypeId) -> &Type { 19 | &self.arena[id] 20 | } 21 | 22 | /// Get a type associated with an ID 23 | pub fn get_mut(&mut self, id: TypeId) -> &mut Type { 24 | &mut self.arena[id] 25 | } 26 | 27 | /// Get the parameters and results for the given type. 28 | pub fn params_results(&self, id: TypeId) -> (&[ValType], &[ValType]) { 29 | let ty = self.get(id); 30 | (ty.params(), ty.results()) 31 | } 32 | 33 | /// Get the parameters for the given type. 34 | pub fn params(&self, id: TypeId) -> &[ValType] { 35 | self.get(id).params() 36 | } 37 | 38 | /// Get the results for the given type. 39 | pub fn results(&self, id: TypeId) -> &[ValType] { 40 | self.get(id).results() 41 | } 42 | 43 | /// Get a type ID by its name. 44 | /// 45 | /// This is currently only intended for in-memory modifications, and by 46 | /// default will always return `None` for a newly parsed module. A 47 | /// hypothetical future WAT text format to `walrus::Module` parser could 48 | /// preserve type names from the WAT. 49 | pub fn by_name(&self, name: &str) -> Option { 50 | self.arena.iter().find_map(|(id, ty)| { 51 | if ty.name.as_deref() == Some(name) { 52 | Some(id) 53 | } else { 54 | None 55 | } 56 | }) 57 | } 58 | 59 | /// Get a shared reference to this module's types. 60 | pub fn iter(&self) -> impl Iterator { 61 | self.arena.iter().map(|(_, f)| f) 62 | } 63 | 64 | /// Removes a type from this module. 65 | /// 66 | /// It is up to you to ensure that any potential references to the deleted 67 | /// type are also removed, eg `call_indirect` expressions, function types, 68 | /// etc. 69 | pub fn delete(&mut self, ty: TypeId) { 70 | self.arena.remove(ty); 71 | } 72 | 73 | /// Add a new type to this module, and return its `Id` 74 | pub fn add(&mut self, params: &[ValType], results: &[ValType]) -> TypeId { 75 | let id = self.arena.next_id(); 76 | self.arena.insert(Type::new( 77 | id, 78 | params.to_vec().into_boxed_slice(), 79 | results.to_vec().into_boxed_slice(), 80 | )) 81 | } 82 | 83 | pub(crate) fn add_entry_ty(&mut self, results: &[ValType]) -> TypeId { 84 | let id = self.arena.next_id(); 85 | self.arena.insert(Type::for_function_entry( 86 | id, 87 | results.to_vec().into_boxed_slice(), 88 | )) 89 | } 90 | 91 | /// Find the existing type for the given parameters and results. 92 | pub fn find(&self, params: &[ValType], results: &[ValType]) -> Option { 93 | self.arena.iter().find_map(|(id, ty)| { 94 | if !ty.is_for_function_entry() && ty.params() == params && ty.results() == results { 95 | Some(id) 96 | } else { 97 | None 98 | } 99 | }) 100 | } 101 | 102 | pub(crate) fn find_for_function_entry(&self, results: &[ValType]) -> Option { 103 | self.arena.iter().find_map(|(id, ty)| { 104 | if ty.is_for_function_entry() && ty.params().is_empty() && ty.results() == results { 105 | Some(id) 106 | } else { 107 | None 108 | } 109 | }) 110 | } 111 | } 112 | 113 | impl Module { 114 | /// Construct the set of types within a module. 115 | pub(crate) fn parse_types( 116 | &mut self, 117 | section: wasmparser::TypeSectionReader, 118 | ids: &mut IndicesToIds, 119 | ) -> Result<()> { 120 | log::debug!("parsing type section"); 121 | for ty in section.into_iter_err_on_gc_types() { 122 | let fun_ty = ty?; 123 | let id = self.types.arena.next_id(); 124 | let params = fun_ty 125 | .params() 126 | .iter() 127 | .map(ValType::parse) 128 | .collect::>>()? 129 | .into_boxed_slice(); 130 | let results = fun_ty 131 | .results() 132 | .iter() 133 | .map(ValType::parse) 134 | .collect::>>()? 135 | .into_boxed_slice(); 136 | let id = self.types.arena.insert(Type::new(id, params, results)); 137 | ids.push_type(id); 138 | } 139 | 140 | Ok(()) 141 | } 142 | } 143 | 144 | impl Emit for ModuleTypes { 145 | fn emit(&self, cx: &mut EmitContext) { 146 | log::debug!("emitting type section"); 147 | 148 | let mut wasm_type_section = wasm_encoder::TypeSection::new(); 149 | 150 | let mut tys = self 151 | .arena 152 | .iter() 153 | .filter(|(_, ty)| !ty.is_for_function_entry()) 154 | .collect::>(); 155 | 156 | if tys.is_empty() { 157 | return; 158 | } 159 | 160 | // Sort for deterministic ordering. 161 | tys.sort_by_key(|&(_, ty)| ty); 162 | 163 | for (id, ty) in tys { 164 | cx.indices.push_type(id); 165 | wasm_type_section.function( 166 | ty.params().iter().map(ValType::to_wasmencoder_type), 167 | ty.results().iter().map(ValType::to_wasmencoder_type), 168 | ); 169 | } 170 | 171 | cx.wasm_module.section(&wasm_type_section); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/parse.rs: -------------------------------------------------------------------------------- 1 | use crate::map::IdHashMap; 2 | use crate::{DataId, ElementId, Function, FunctionId, GlobalId, Result}; 3 | use crate::{LocalId, MemoryId, TableId, TypeId}; 4 | use anyhow::bail; 5 | 6 | /// Maps from old indices in the original Wasm binary to `walrus` IDs. 7 | /// 8 | /// This is intended to be used with `walrus::Module`s that were parsed from 9 | /// some existing Wasm binary. `walrus::Module`s that are built up from scratch, 10 | /// and not originally parsed from an existing Wasm binary, will have an empty 11 | /// `IndicesToIds`. 12 | /// 13 | /// For example, this allows you to get the `walrus::FunctionId` of some Wasm 14 | /// function when you have its old index in the original Wasm module. 15 | /// 16 | /// Any newly built or added things (functions, tables, types, etc) are not 17 | /// associated with an old index (since they were not present in the original 18 | /// Wasm binary). 19 | #[derive(Debug, Default)] 20 | pub struct IndicesToIds { 21 | tables: Vec, 22 | types: Vec, 23 | funcs: Vec, 24 | globals: Vec, 25 | memories: Vec, 26 | elements: Vec, 27 | data: Vec, 28 | locals: IdHashMap>, 29 | } 30 | 31 | macro_rules! define_push_get { 32 | ( $push:ident, $get:ident, $id_ty:ty, $member:ident ) => { 33 | impl IndicesToIds { 34 | /// Pushes a new local ID to map it to the next index internally 35 | pub(crate) fn $push(&mut self, id: $id_ty) -> u32 { 36 | self.$member.push(id); 37 | (self.$member.len() - 1) as u32 38 | } 39 | 40 | /// Gets the ID for a particular index. 41 | /// 42 | /// If the index did not exist in the original Wasm binary, an `Err` 43 | /// is returned. 44 | pub fn $get(&self, index: u32) -> Result<$id_ty> { 45 | match self.$member.get(index as usize) { 46 | Some(x) => Ok(*x), 47 | None => bail!( 48 | "index `{}` is out of bounds for {}", 49 | index, 50 | stringify!($member) 51 | ), 52 | } 53 | } 54 | } 55 | }; 56 | } 57 | 58 | define_push_get!(push_table, get_table, TableId, tables); 59 | define_push_get!(push_type, get_type, TypeId, types); 60 | define_push_get!(push_func, get_func, FunctionId, funcs); 61 | define_push_get!(push_global, get_global, GlobalId, globals); 62 | define_push_get!(push_memory, get_memory, MemoryId, memories); 63 | define_push_get!(push_element, get_element, ElementId, elements); 64 | define_push_get!(push_data, get_data, DataId, data); 65 | 66 | impl IndicesToIds { 67 | /// Pushes a new local ID to map it to the next index internally 68 | pub(crate) fn push_local(&mut self, function: FunctionId, id: LocalId) -> u32 { 69 | let list = self.locals.entry(function).or_default(); 70 | list.push(id); 71 | (list.len() as u32) - 1 72 | } 73 | 74 | /// Gets the ID for a particular index 75 | pub fn get_local(&self, function: FunctionId, index: u32) -> Result { 76 | let locals = match self.locals.get(&function) { 77 | Some(x) => x, 78 | None => bail!( 79 | "function index `{}` is out of bounds for local", 80 | function.index() 81 | ), 82 | }; 83 | match locals.get(index as usize) { 84 | Some(x) => Ok(*x), 85 | None => bail!( 86 | "index `{}` in function `{}` is out of bounds for local", 87 | index, 88 | function.index(), 89 | ), 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/passes/gc.rs: -------------------------------------------------------------------------------- 1 | //! Removes any non-referenced items from a module 2 | //! 3 | //! This commit will remove functions, data, etc, that are not referenced 4 | //! internally and can be safely removed. 5 | 6 | use crate::map::IdHashSet; 7 | use crate::passes::used::Used; 8 | use crate::{ImportKind, Module}; 9 | use id_arena::Id; 10 | 11 | /// Run GC passes over the module specified. 12 | pub fn run(m: &mut Module) { 13 | let used = Used::new(m); 14 | 15 | let mut unused_imports = Vec::new(); 16 | for import in m.imports.iter() { 17 | let used = match &import.kind { 18 | ImportKind::Function(f) => used.funcs.contains(f), 19 | ImportKind::Table(t) => used.tables.contains(t), 20 | ImportKind::Global(g) => used.globals.contains(g), 21 | ImportKind::Memory(m) => used.memories.contains(m), 22 | }; 23 | if !used { 24 | unused_imports.push(import.id()); 25 | } 26 | } 27 | for id in unused_imports { 28 | m.imports.delete(id); 29 | } 30 | 31 | for id in unused(&used.tables, m.tables.iter().map(|t| t.id())) { 32 | m.tables.delete(id); 33 | } 34 | for id in unused(&used.globals, m.globals.iter().map(|t| t.id())) { 35 | m.globals.delete(id); 36 | } 37 | for id in unused(&used.memories, m.memories.iter().map(|t| t.id())) { 38 | m.memories.delete(id); 39 | } 40 | for id in unused(&used.data, m.data.iter().map(|t| t.id())) { 41 | m.data.delete(id); 42 | } 43 | for id in unused(&used.elements, m.elements.iter().map(|t| t.id())) { 44 | m.elements.delete(id); 45 | } 46 | for id in unused(&used.types, m.types.iter().map(|t| t.id())) { 47 | m.types.delete(id); 48 | } 49 | for id in unused(&used.funcs, m.funcs.iter().map(|t| t.id())) { 50 | m.funcs.delete(id); 51 | } 52 | } 53 | 54 | fn unused(used: &IdHashSet, all: impl Iterator>) -> Vec> { 55 | let mut unused = Vec::new(); 56 | for id in all { 57 | if !used.contains(&id) { 58 | unused.push(id); 59 | } 60 | } 61 | unused 62 | } 63 | -------------------------------------------------------------------------------- /src/passes/mod.rs: -------------------------------------------------------------------------------- 1 | //! Passes over whole modules or individual functions. 2 | 3 | pub mod gc; 4 | mod used; 5 | pub use self::used::Roots; 6 | -------------------------------------------------------------------------------- /src/tombstone_arena.rs: -------------------------------------------------------------------------------- 1 | use crate::map::IdHashSet; 2 | use id_arena::Arena as InnerArena; 3 | use std::ops::{Index, IndexMut}; 4 | 5 | #[cfg(feature = "parallel")] 6 | use rayon::iter::plumbing::UnindexedConsumer; 7 | #[cfg(feature = "parallel")] 8 | use rayon::prelude::*; 9 | 10 | pub use id_arena::Id; 11 | 12 | /// A wrapper around an `id_arena::Arena` that adds a tombstone set for deleting 13 | /// items. 14 | #[derive(Debug)] 15 | pub struct TombstoneArena { 16 | inner: InnerArena, 17 | dead: IdHashSet, 18 | } 19 | 20 | // Note: can't derive because that would require `T: Default`. 21 | impl Default for TombstoneArena { 22 | fn default() -> TombstoneArena { 23 | TombstoneArena { 24 | inner: Default::default(), 25 | dead: Default::default(), 26 | } 27 | } 28 | } 29 | 30 | /// Like `Drop` but for after an item is marked deleted from a `TombstoneArena`. 31 | /// 32 | /// Note that this is *not* setting the item to a tombstone (eg turning an 33 | /// `Option` into a `None`). Instead, this is an opportunity to clean up heap 34 | /// allocations or whatever other resource. 35 | /// 36 | /// Note that the item must still be in a valid state after `on_delete`, since 37 | /// its `Drop` implementation may still be called in the future if it has one. 38 | pub trait Tombstone { 39 | /// "Drop" this item. 40 | fn on_delete(&mut self) { 41 | // Do nothing by default. 42 | } 43 | } 44 | 45 | impl TombstoneArena 46 | where 47 | T: Tombstone, 48 | { 49 | /// Delete the item with the given id from the arena. 50 | pub fn delete(&mut self, id: Id) { 51 | assert!(self.contains(id)); 52 | self.dead.insert(id); 53 | self.inner[id].on_delete(); 54 | } 55 | } 56 | 57 | impl TombstoneArena { 58 | pub fn alloc(&mut self, val: T) -> Id { 59 | self.inner.alloc(val) 60 | } 61 | 62 | pub fn alloc_with_id(&mut self, f: F) -> Id 63 | where 64 | F: FnOnce(Id) -> T, 65 | { 66 | let id = self.next_id(); 67 | self.alloc(f(id)) 68 | } 69 | 70 | pub fn get(&self, id: Id) -> Option<&T> { 71 | if self.dead.contains(&id) { 72 | None 73 | } else { 74 | self.inner.get(id) 75 | } 76 | } 77 | 78 | pub fn get_mut(&mut self, id: Id) -> Option<&mut T> { 79 | if self.dead.contains(&id) { 80 | None 81 | } else { 82 | self.inner.get_mut(id) 83 | } 84 | } 85 | 86 | pub fn next_id(&self) -> Id { 87 | self.inner.next_id() 88 | } 89 | 90 | pub fn len(&self) -> usize { 91 | self.inner.len() - self.dead.len() 92 | } 93 | 94 | pub fn contains(&self, id: Id) -> bool { 95 | self.inner.get(id).is_some() && !self.dead.contains(&id) 96 | } 97 | 98 | pub fn iter(&self) -> impl Iterator, &T)> { 99 | self.inner 100 | .iter() 101 | .filter(move |&(id, _)| !self.dead.contains(&id)) 102 | } 103 | 104 | pub fn iter_mut(&mut self) -> IterMut { 105 | IterMut { 106 | dead: &self.dead, 107 | inner: self.inner.iter_mut(), 108 | } 109 | } 110 | 111 | #[cfg(feature = "parallel")] 112 | pub fn par_iter(&self) -> impl ParallelIterator, &T)> 113 | where 114 | T: Sync, 115 | { 116 | self.inner 117 | .par_iter() 118 | .filter(move |&(id, _)| !self.dead.contains(&id)) 119 | } 120 | 121 | #[cfg(feature = "parallel")] 122 | pub fn par_iter_mut(&mut self) -> ParIterMut 123 | where 124 | T: Send + Sync, 125 | { 126 | ParIterMut { 127 | dead: &self.dead, 128 | inner: self.inner.par_iter_mut(), 129 | } 130 | } 131 | } 132 | 133 | impl Index> for TombstoneArena { 134 | type Output = T; 135 | 136 | fn index(&self, id: Id) -> &T { 137 | assert!(!self.dead.contains(&id)); 138 | &self.inner[id] 139 | } 140 | } 141 | 142 | impl IndexMut> for TombstoneArena { 143 | fn index_mut(&mut self, id: Id) -> &mut T { 144 | assert!(!self.dead.contains(&id)); 145 | &mut self.inner[id] 146 | } 147 | } 148 | 149 | #[derive(Debug)] 150 | pub struct IterMut<'a, T: 'a> { 151 | dead: &'a IdHashSet, 152 | inner: id_arena::IterMut<'a, T, id_arena::DefaultArenaBehavior>, 153 | } 154 | 155 | impl<'a, T: 'a> Iterator for IterMut<'a, T> { 156 | type Item = (Id, &'a mut T); 157 | 158 | fn next(&mut self) -> Option { 159 | loop { 160 | match self.inner.next() { 161 | Some((id, _)) if self.dead.contains(&id) => continue, 162 | x => return x, 163 | } 164 | } 165 | } 166 | } 167 | 168 | #[derive(Debug)] 169 | #[cfg(feature = "parallel")] 170 | pub struct ParIterMut<'a, T: 'a + Send + Sync> { 171 | dead: &'a IdHashSet, 172 | inner: id_arena::ParIterMut<'a, T, id_arena::DefaultArenaBehavior>, 173 | } 174 | 175 | #[cfg(feature = "parallel")] 176 | impl<'a, T> ParallelIterator for ParIterMut<'a, T> 177 | where 178 | T: Send + Sync, 179 | { 180 | type Item = (Id, &'a mut T); 181 | 182 | fn drive_unindexed(self, consumer: C) -> C::Result 183 | where 184 | C: UnindexedConsumer, 185 | { 186 | let dead = self.dead; 187 | self.inner 188 | .filter(move |&(id, _)| !dead.contains(&id)) 189 | .drive_unindexed(consumer) 190 | } 191 | } 192 | 193 | #[cfg(test)] 194 | mod tests { 195 | use super::*; 196 | use std::rc::Rc; 197 | 198 | struct Doggo { 199 | good_boi: Option>, 200 | } 201 | 202 | // oh god this is the saddest impl I have ever written for sure T.T 203 | impl Tombstone for Doggo { 204 | fn on_delete(&mut self) { 205 | self.good_boi = None; 206 | } 207 | } 208 | 209 | #[test] 210 | fn can_delete() { 211 | let mut a = TombstoneArena::::default(); 212 | 213 | let rc = Rc::new(()); 214 | assert_eq!(Rc::strong_count(&rc), 1); 215 | 216 | let id = a.alloc(Doggo { 217 | good_boi: Some(rc.clone()), 218 | }); 219 | 220 | assert_eq!(Rc::strong_count(&rc), 2); 221 | assert!(a.contains(id)); 222 | 223 | a.delete(id); 224 | 225 | assert_eq!( 226 | Rc::strong_count(&rc), 227 | 1, 228 | "the on_delete should have been called" 229 | ); 230 | assert!( 231 | !a.contains(id), 232 | "and the arena no longer contains the doggo :(" 233 | ); 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /src/ty.rs: -------------------------------------------------------------------------------- 1 | //! WebAssembly function and value types. 2 | 3 | use crate::error::Result; 4 | use crate::tombstone_arena::Tombstone; 5 | use anyhow::bail; 6 | use id_arena::Id; 7 | use std::cmp::Ordering; 8 | use std::convert::TryFrom; 9 | use std::fmt; 10 | use std::hash; 11 | 12 | /// An identifier for types. 13 | pub type TypeId = Id; 14 | 15 | /// A function type. 16 | #[derive(Debug, Clone)] 17 | pub struct Type { 18 | id: TypeId, 19 | params: Box<[ValType]>, 20 | results: Box<[ValType]>, 21 | 22 | // Whether or not this type is for a multi-value function entry block, and 23 | // therefore is for internal use only and shouldn't be emitted when we 24 | // serialize the Type section. 25 | is_for_function_entry: bool, 26 | 27 | /// An optional name for debugging. 28 | /// 29 | /// This is not really used by anything currently, but a theoretical WAT to 30 | /// walrus parser could keep track of the original name in the WAT. 31 | pub name: Option, 32 | } 33 | 34 | impl PartialEq for Type { 35 | #[inline] 36 | fn eq(&self, rhs: &Type) -> bool { 37 | // NB: do not compare id or name. 38 | self.params == rhs.params 39 | && self.results == rhs.results 40 | && self.is_for_function_entry == rhs.is_for_function_entry 41 | } 42 | } 43 | 44 | impl Eq for Type {} 45 | 46 | impl PartialOrd for Type { 47 | fn partial_cmp(&self, rhs: &Type) -> Option { 48 | Some(self.cmp(rhs)) 49 | } 50 | } 51 | 52 | impl Ord for Type { 53 | fn cmp(&self, rhs: &Type) -> Ordering { 54 | self.params() 55 | .cmp(rhs.params()) 56 | .then_with(|| self.results().cmp(rhs.results())) 57 | } 58 | } 59 | 60 | impl hash::Hash for Type { 61 | #[inline] 62 | fn hash(&self, h: &mut H) { 63 | // Do not hash id or name. 64 | self.params.hash(h); 65 | self.results.hash(h); 66 | self.is_for_function_entry.hash(h); 67 | } 68 | } 69 | 70 | impl Tombstone for Type { 71 | fn on_delete(&mut self) { 72 | self.params = Box::new([]); 73 | self.results = Box::new([]); 74 | } 75 | } 76 | 77 | impl Type { 78 | /// Construct a new function type. 79 | #[inline] 80 | pub(crate) fn new(id: TypeId, params: Box<[ValType]>, results: Box<[ValType]>) -> Type { 81 | Type { 82 | id, 83 | params, 84 | results, 85 | is_for_function_entry: false, 86 | name: None, 87 | } 88 | } 89 | 90 | /// Construct a new type for function entry blocks. 91 | #[inline] 92 | pub(crate) fn for_function_entry(id: TypeId, results: Box<[ValType]>) -> Type { 93 | let params = vec![].into(); 94 | Type { 95 | id, 96 | params, 97 | results, 98 | is_for_function_entry: true, 99 | name: None, 100 | } 101 | } 102 | 103 | /// Get the id of this type. 104 | #[inline] 105 | pub fn id(&self) -> TypeId { 106 | self.id 107 | } 108 | 109 | /// Get the parameters to this function type. 110 | #[inline] 111 | pub fn params(&self) -> &[ValType] { 112 | &self.params 113 | } 114 | 115 | /// Get the results of this function type. 116 | #[inline] 117 | pub fn results(&self) -> &[ValType] { 118 | &self.results 119 | } 120 | 121 | pub(crate) fn is_for_function_entry(&self) -> bool { 122 | self.is_for_function_entry 123 | } 124 | } 125 | 126 | /// A value type. 127 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] 128 | pub enum ValType { 129 | /// 32-bit integer. 130 | I32, 131 | /// 64-bit integer. 132 | I64, 133 | /// 32-bit float. 134 | F32, 135 | /// 64-bit float. 136 | F64, 137 | /// 128-bit vector. 138 | V128, 139 | /// Reference. 140 | Ref(RefType), 141 | } 142 | 143 | /// A reference type. 144 | /// 145 | /// The "function references" and "gc" proposals will add more reference types. 146 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] 147 | #[non_exhaustive] 148 | pub enum RefType { 149 | /// A nullable reference to an untyped function 150 | Funcref, 151 | /// A nullable reference to an extern object 152 | Externref, 153 | } 154 | 155 | impl TryFrom for RefType { 156 | type Error = anyhow::Error; 157 | 158 | fn try_from(ref_type: wasmparser::RefType) -> Result { 159 | match ref_type { 160 | wasmparser::RefType::FUNCREF => Ok(RefType::Funcref), 161 | wasmparser::RefType::EXTERNREF => Ok(RefType::Externref), 162 | _ => bail!("unsupported ref type {:?}", ref_type), 163 | } 164 | } 165 | } 166 | 167 | impl ValType { 168 | pub(crate) fn from_wasmparser_type(ty: wasmparser::ValType) -> Result> { 169 | let v = vec![ValType::parse(&ty)?]; 170 | Ok(v.into_boxed_slice()) 171 | } 172 | 173 | pub(crate) fn to_wasmencoder_type(&self) -> wasm_encoder::ValType { 174 | match self { 175 | ValType::I32 => wasm_encoder::ValType::I32, 176 | ValType::I64 => wasm_encoder::ValType::I64, 177 | ValType::F32 => wasm_encoder::ValType::F32, 178 | ValType::F64 => wasm_encoder::ValType::F64, 179 | ValType::V128 => wasm_encoder::ValType::V128, 180 | ValType::Ref(ref_type) => match ref_type { 181 | RefType::Externref => wasm_encoder::ValType::Ref(wasm_encoder::RefType::EXTERNREF), 182 | RefType::Funcref => wasm_encoder::ValType::Ref(wasm_encoder::RefType::FUNCREF), 183 | }, 184 | } 185 | } 186 | 187 | pub(crate) fn parse(input: &wasmparser::ValType) -> Result { 188 | match input { 189 | wasmparser::ValType::I32 => Ok(ValType::I32), 190 | wasmparser::ValType::I64 => Ok(ValType::I64), 191 | wasmparser::ValType::F32 => Ok(ValType::F32), 192 | wasmparser::ValType::F64 => Ok(ValType::F64), 193 | wasmparser::ValType::V128 => Ok(ValType::V128), 194 | wasmparser::ValType::Ref(ref_type) => match *ref_type { 195 | wasmparser::RefType::EXTERNREF => Ok(ValType::Ref(RefType::Externref)), 196 | wasmparser::RefType::FUNCREF => Ok(ValType::Ref(RefType::Funcref)), 197 | _ => bail!("unsupported ref type {:?}", ref_type), 198 | }, 199 | } 200 | } 201 | } 202 | 203 | impl fmt::Display for ValType { 204 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 205 | write!( 206 | f, 207 | "{}", 208 | match self { 209 | ValType::I32 => "i32", 210 | ValType::I64 => "i64", 211 | ValType::F32 => "f32", 212 | ValType::F64 => "f64", 213 | ValType::V128 => "v128", 214 | ValType::Ref(RefType::Externref) => "externref", 215 | ValType::Ref(RefType::Funcref) => "funcref", 216 | } 217 | ) 218 | } 219 | } 220 | --------------------------------------------------------------------------------