├── crates ├── tests │ ├── src │ │ ├── check.rs │ │ └── lib.rs │ ├── tests │ │ ├── round_trip │ │ │ ├── nop.wat │ │ │ ├── if-no-else.wat │ │ │ ├── br-label.wat │ │ │ ├── br-to-func.wat │ │ │ ├── name-section.wat │ │ │ ├── refer-forward.wat │ │ │ ├── memory-grow.wat │ │ │ ├── local-tee.wat │ │ │ ├── start.wat │ │ │ ├── globals.wat │ │ │ ├── import-memory.wat │ │ │ ├── import-global.wat │ │ │ ├── gc_keep_used_memories.wat │ │ │ ├── import-gc.wat │ │ │ ├── import-table.wat │ │ │ ├── gc_keep_used_globals.wat │ │ │ ├── import-func.wat │ │ │ ├── gc_keep_used_tables.wat │ │ │ ├── global-init.wat │ │ │ ├── drop.wat │ │ │ ├── br-table-2.wat │ │ │ ├── unreachable_4.wat │ │ │ ├── return.wat │ │ │ ├── loop.wat │ │ │ ├── const.wat │ │ │ ├── elem-segments-1.wat │ │ │ ├── memory-init.wast │ │ │ ├── memory-size.wat │ │ │ ├── return_2.wat │ │ │ ├── unreachable.wat │ │ │ ├── fuzz-0.wat │ │ │ ├── gc_keep_used_imports.wat │ │ │ ├── multi-0.wat │ │ │ ├── unreachable_2.wat │ │ │ ├── type-deduplicate.wat │ │ │ ├── anyref1.wat │ │ │ ├── elem-segments-2.wat │ │ │ ├── elem-segments-3.wat │ │ │ ├── stuff-after-loop.wat │ │ │ ├── params.wat │ │ │ ├── table-init.wast │ │ │ ├── used-local-in-local.wat │ │ │ ├── gc_unused_tables.wat │ │ │ ├── anyref2.wat │ │ │ ├── call.wat │ │ │ ├── gc_unused_memories.wat │ │ │ ├── gc_unused_types.wat │ │ │ ├── used-local-set.wat │ │ │ ├── gc_unused_global.wat │ │ │ ├── gc_unused_imports.wat │ │ │ ├── local-get.wat │ │ │ ├── stuff-after-loop-2.wat │ │ │ ├── gc_unused_funcs.wat │ │ │ ├── return_call.wat │ │ │ ├── mem.wat │ │ │ ├── inc.wat │ │ │ ├── return_call_indirect.wat │ │ │ ├── keep-elem-segments.wat │ │ │ ├── loop2.wat │ │ │ ├── call_2.wat │ │ │ ├── entry-block-type.wat │ │ │ ├── select.wat │ │ │ ├── multi-2.wat │ │ │ ├── gc_unused_block.wat │ │ │ ├── call-two-params.wat │ │ │ ├── multi-1.wat │ │ │ ├── unreachable_3.wat │ │ │ ├── if_else.wat │ │ │ ├── gc_keep_used_funcs.wat │ │ │ ├── anyref3.wat │ │ │ ├── gc_unused_funcs_2.wat │ │ │ ├── not-tree-like.wat │ │ │ ├── count-to-ten.wat │ │ │ ├── block.wat │ │ │ ├── if_else_2.wat │ │ │ ├── multi-3.wat │ │ │ ├── br_table.wat │ │ │ ├── gc_transitive_global_refs.wat │ │ │ ├── bulk-memory.wat │ │ │ ├── gc_transitive_global_refs_extended.wat │ │ │ ├── gc_exception_handling_with_globals.wat │ │ │ ├── fac.wat │ │ │ ├── try_table_roundtrip.wat │ │ │ ├── many_funcs.wat │ │ │ ├── atomic.wat │ │ │ └── fac-multi-value.wat │ │ ├── valid │ │ │ ├── drop.wat │ │ │ ├── loop.wat │ │ │ ├── const.wat │ │ │ ├── call.wat │ │ │ ├── stuff-after-loop.wat │ │ │ ├── min-fac.wat │ │ │ ├── stuff-after-loop-2.wat │ │ │ ├── block.wat │ │ │ ├── inc.wat │ │ │ ├── if_else.wat │ │ │ ├── select.wat │ │ │ ├── count-to-ten.wat │ │ │ ├── fac.wat │ │ │ └── fac-multi-value.wat │ │ ├── invalid │ │ │ ├── multi-value-1.wat │ │ │ ├── multi-value-0.wat │ │ │ ├── multi-value-3.wat │ │ │ ├── multi-value-2.wat │ │ │ └── if-with-no-else.wat │ │ ├── function_imports │ │ │ └── pets_function_imports.wat │ │ ├── valid.rs │ │ ├── invalid.rs │ │ ├── function_imports.rs │ │ ├── round_trip.rs │ │ ├── ir │ │ │ └── bulk-memory.wat │ │ ├── custom_sections.rs │ │ ├── spec-tests.rs │ │ └── const_expr_mutation.rs │ ├── Cargo.toml │ └── build.rs ├── macro │ ├── LICENSE-MIT │ ├── LICENSE-APACHE │ └── Cargo.toml ├── tests-utils │ ├── Cargo.toml │ └── src │ │ └── lib.rs └── fuzz-utils │ ├── Cargo.toml │ └── before-after.sh ├── fuzz ├── .gitignore ├── README.md ├── fuzz_targets │ ├── watgen.rs │ ├── wasm-opt-ttf.rs │ └── raw.rs └── Cargo.toml ├── benches ├── fixtures │ └── dodrio-todomvc.wasm └── benches.rs ├── src ├── passes │ ├── mod.rs │ └── gc.rs ├── error.rs ├── lib.rs ├── module │ ├── locals.rs │ ├── debug │ │ ├── units.rs │ │ └── mod.rs │ ├── producers.rs │ ├── tags.rs │ ├── globals.rs │ ├── types.rs │ ├── tables.rs │ ├── memories.rs │ └── functions │ │ └── local_function │ │ └── context.rs ├── map.rs ├── arena_set.rs ├── parse.rs ├── emit.rs ├── tombstone_arena.rs └── ty.rs ├── .gitmodules ├── .gitignore ├── examples ├── parse.rs ├── round-trip.rs └── build-wasm-from-scratch.rs ├── publish.sh ├── CONTRIBUTING.md ├── LICENSE-MIT ├── Cargo.toml ├── README.md ├── .github └── workflows │ └── main.yml └── CHANGELOG.md /crates/tests/src/check.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /crates/macro/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../../LICENSE-MIT -------------------------------------------------------------------------------- /crates/macro/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../../LICENSE-APACHE -------------------------------------------------------------------------------- /fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | target 3 | corpus 4 | artifacts 5 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/nop.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (func 3 | nop)) 4 | 5 | ;; CHECK: (module 6 | -------------------------------------------------------------------------------- /benches/fixtures/dodrio-todomvc.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasm-bindgen/walrus/HEAD/benches/fixtures/dodrio-todomvc.wasm -------------------------------------------------------------------------------- /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/round_trip/if-no-else.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (func 3 | i32.const 0 4 | if 5 | end)) 6 | 7 | ;; CHECK: (module 8 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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-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/name-section.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (func $wat) 3 | (export "another" (func $wat))) 4 | 5 | ;; CHECK: (func $wat 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "walrus-tests/tests/spec-tests"] 2 | path = crates/tests/tests/spec-tests 3 | url = https://github.com/WebAssembly/testsuite 4 | -------------------------------------------------------------------------------- /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/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/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/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/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/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/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 | -------------------------------------------------------------------------------- /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/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/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/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/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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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/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-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/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/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/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/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-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-utils/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "walrus-tests-utils" 3 | version = "0.1.0" 4 | authors = ["Nick Fitzgerald "] 5 | edition = "2021" 6 | publish = false 7 | 8 | [dependencies] 9 | tempfile = "3.1.0" 10 | anyhow = "1.0" 11 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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/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/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/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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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/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/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/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 | ;) -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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/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/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/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/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/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/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_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/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/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/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/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/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/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/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/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/fuzz-utils/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Nick Fitzgerald "] 3 | edition = "2021" 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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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 | ;) -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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/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/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/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/macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Nick Fitzgerald "] 3 | edition = "2021" 4 | name = "walrus-macro" 5 | version = "0.24.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/tests/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "walrus-tests" 3 | version = "0.1.0" 4 | authors = ["Nick Fitzgerald "] 5 | edition = "2021" 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/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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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.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/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/gc_transitive_global_refs.wat: -------------------------------------------------------------------------------- 1 | ;; Keep transitively referenced globals through const expressions. 2 | ;; Global A is used by a function, Global A references Global B in its initializer. 3 | ;; Both should be kept by GC. 4 | 5 | (module 6 | ;; Global B - referenced by Global A's initializer 7 | (global $globalB i32 (i32.const 42)) 8 | 9 | ;; Global A - uses Global B in its initializer (simple const expr) 10 | (global $globalA i32 (global.get $globalB)) 11 | 12 | ;; Unused global that should be removed 13 | (global $unused i32 (i32.const 999)) 14 | 15 | ;; Function that uses Global A 16 | (func $f (result i32) 17 | global.get $globalA) 18 | 19 | (export "f" (func $f))) 20 | 21 | (; CHECK-ALL: 22 | (module 23 | (type (;0;) (func (result i32))) 24 | (func $f (;0;) (type 0) (result i32) 25 | global.get $globalA 26 | ) 27 | (global $globalB (;0;) i32 i32.const 42) 28 | (global $globalA (;1;) i32 global.get $globalB) 29 | (export "f" (func $f)) 30 | ;) 31 | -------------------------------------------------------------------------------- /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/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 | ;) -------------------------------------------------------------------------------- /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, ConstOp}; 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Nick Fitzgerald "] 3 | edition = "2021" 4 | name = "walrus" 5 | version = "0.24.4" 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.24.0' } 32 | wasm-encoder = "0.240.0" 33 | wasmparser = "0.240.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 | -------------------------------------------------------------------------------- /crates/tests/tests/round_trip/gc_transitive_global_refs_extended.wat: -------------------------------------------------------------------------------- 1 | ;; Keep transitively referenced globals through extended const expressions. 2 | ;; Global A uses Global B and Global C in an extended const expression (with arithmetic). 3 | ;; All used globals should be kept by GC. 4 | 5 | (module 6 | ;; Base globals used in extended const expression 7 | (global $globalB i32 (i32.const 10)) 8 | (global $globalC i32 (i32.const 32)) 9 | 10 | ;; Global A - uses extended const expr: globalB + globalC 11 | (global $globalA i32 12 | (i32.add 13 | (global.get $globalB) 14 | (global.get $globalC))) 15 | 16 | ;; Unused global that should be removed 17 | (global $unused i32 (i32.const 999)) 18 | 19 | ;; Function that uses Global A 20 | (func $f (result i32) 21 | global.get $globalA) 22 | 23 | (export "f" (func $f))) 24 | 25 | (; CHECK-ALL: 26 | (module 27 | (type (;0;) (func (result i32))) 28 | (func $f (;0;) (type 0) (result i32) 29 | global.get $globalA 30 | ) 31 | (global $globalB (;0;) i32 i32.const 10) 32 | (global $globalC (;1;) i32 i32.const 32) 33 | (global $globalA (;2;) i32 global.get $globalB global.get $globalC i32.add) 34 | (export "f" (func $f)) 35 | ;) 36 | -------------------------------------------------------------------------------- /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/tests/tests/round_trip/gc_exception_handling_with_globals.wat: -------------------------------------------------------------------------------- 1 | ;; Keep tags and globals used in exception handling. 2 | ;; Function uses try_table with a tag, and the catch block uses a global. 3 | 4 | (module 5 | ;; Tag for exceptions 6 | (tag $myTag (param i32)) 7 | 8 | ;; Global used in catch handler 9 | (global $errorValue (mut i32) (i32.const 99)) 10 | 11 | ;; Unused global that should be removed 12 | (global $unused i32 (i32.const 888)) 13 | 14 | ;; Function that uses try_table with the tag and global 15 | (func $f (result i32) 16 | (block $catch (result i32) 17 | (try_table (result i32) (catch $myTag $catch) 18 | (i32.const 42) 19 | ) 20 | ) 21 | drop 22 | global.get $errorValue) 23 | 24 | (export "f" (func $f))) 25 | 26 | (; CHECK-ALL: 27 | (module 28 | (type (;0;) (func (result i32))) 29 | (type (;1;) (func (param i32))) 30 | (func $f (;0;) (type 0) (result i32) 31 | block (result i32) ;; label = @1 32 | try_table (result i32) (catch $myTag 0 (;@1;)) ;; label = @2 33 | i32.const 42 34 | end 35 | end 36 | drop 37 | global.get $errorValue 38 | ) 39 | (tag $myTag (;0;) (type 1) (param i32)) 40 | (global $errorValue (;0;) (mut i32) i32.const 99) 41 | (export "f" (func $f)) 42 | ;) 43 | -------------------------------------------------------------------------------- /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/try_table_roundtrip.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (tag $e-i32-i32 (param i32 i32)) 3 | 4 | (func $throw-1-2 5 | i32.const 1 6 | i32.const 2 7 | throw $e-i32-i32 8 | ) 9 | 10 | (func (export "test-throw-1-2") 11 | (block $h (result i32 i32) 12 | (try_table (catch $e-i32-i32 $h) 13 | (call $throw-1-2) 14 | ) 15 | (return) 16 | ) 17 | (if (i32.ne (i32.const 2)) (then (unreachable))) 18 | (if (i32.ne (i32.const 1)) (then (unreachable))) 19 | ) 20 | ) 21 | 22 | (; CHECK-ALL: 23 | (module 24 | (type (;0;) (func)) 25 | (type (;1;) (func (result i32 i32))) 26 | (type (;2;) (func (param i32 i32))) 27 | (func (;0;) (type 0) 28 | block (type 1) (result i32 i32) ;; label = @1 29 | try_table (catch $e-i32-i32 0 (;@1;)) ;; label = @2 30 | call $throw-1-2 31 | end 32 | return 33 | end 34 | i32.const 2 35 | i32.ne 36 | if ;; label = @1 37 | unreachable 38 | else 39 | end 40 | i32.const 1 41 | i32.ne 42 | if ;; label = @1 43 | unreachable 44 | else 45 | end 46 | ) 47 | (func $throw-1-2 (;1;) (type 0) 48 | i32.const 1 49 | i32.const 2 50 | throw $e-i32-i32 51 | ) 52 | (tag $e-i32-i32 (;0;) (type 2) (param i32 i32)) 53 | (export "test-throw-1-2" (func 0)) 54 | ;) 55 | -------------------------------------------------------------------------------- /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/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/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 | ;) -------------------------------------------------------------------------------- /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/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/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 | ImportKind::Tag(t) => used.tags.contains(t), 23 | }; 24 | if !used { 25 | unused_imports.push(import.id()); 26 | } 27 | } 28 | for id in unused_imports { 29 | m.imports.delete(id); 30 | } 31 | 32 | for id in unused(&used.tables, m.tables.iter().map(|t| t.id())) { 33 | m.tables.delete(id); 34 | } 35 | for id in unused(&used.globals, m.globals.iter().map(|t| t.id())) { 36 | m.globals.delete(id); 37 | } 38 | for id in unused(&used.memories, m.memories.iter().map(|t| t.id())) { 39 | m.memories.delete(id); 40 | } 41 | for id in unused(&used.tags, m.tags.iter().map(|t| t.id())) { 42 | m.tags.delete(id); 43 | } 44 | for id in unused(&used.data, m.data.iter().map(|t| t.id())) { 45 | m.data.delete(id); 46 | } 47 | for id in unused(&used.elements, m.elements.iter().map(|t| t.id())) { 48 | m.elements.delete(id); 49 | } 50 | for id in unused(&used.types, m.types.iter().map(|t| t.id())) { 51 | m.types.delete(id); 52 | } 53 | for id in unused(&used.funcs, m.funcs.iter().map(|t| t.id())) { 54 | m.funcs.delete(id); 55 | } 56 | } 57 | 58 | fn unused(used: &IdHashSet, all: impl Iterator>) -> Vec> { 59 | let mut unused = Vec::new(); 60 | for id in all { 61 | if !used.contains(&id) { 62 | unused.push(id); 63 | } 64 | } 65 | unused 66 | } 67 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /src/parse.rs: -------------------------------------------------------------------------------- 1 | use crate::map::IdHashMap; 2 | use crate::{DataId, ElementId, Function, FunctionId, GlobalId, Result}; 3 | use crate::{LocalId, MemoryId, TableId, TagId, 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 | tags: Vec, 29 | locals: IdHashMap>, 30 | } 31 | 32 | macro_rules! define_push_get { 33 | ( $push:ident, $get:ident, $id_ty:ty, $member:ident ) => { 34 | impl IndicesToIds { 35 | /// Pushes a new local ID to map it to the next index internally 36 | pub(crate) fn $push(&mut self, id: $id_ty) -> u32 { 37 | self.$member.push(id); 38 | (self.$member.len() - 1) as u32 39 | } 40 | 41 | /// Gets the ID for a particular index. 42 | /// 43 | /// If the index did not exist in the original Wasm binary, an `Err` 44 | /// is returned. 45 | pub fn $get(&self, index: u32) -> Result<$id_ty> { 46 | match self.$member.get(index as usize) { 47 | Some(x) => Ok(*x), 48 | None => bail!( 49 | "index `{}` is out of bounds for {}", 50 | index, 51 | stringify!($member) 52 | ), 53 | } 54 | } 55 | } 56 | }; 57 | } 58 | 59 | define_push_get!(push_table, get_table, TableId, tables); 60 | define_push_get!(push_type, get_type, TypeId, types); 61 | define_push_get!(push_func, get_func, FunctionId, funcs); 62 | define_push_get!(push_global, get_global, GlobalId, globals); 63 | define_push_get!(push_memory, get_memory, MemoryId, memories); 64 | define_push_get!(push_element, get_element, ElementId, elements); 65 | define_push_get!(push_data, get_data, DataId, data); 66 | define_push_get!(push_tag, get_tag, TagId, tags); 67 | 68 | impl IndicesToIds { 69 | /// Pushes a new local ID to map it to the next index internally 70 | pub(crate) fn push_local(&mut self, function: FunctionId, id: LocalId) -> u32 { 71 | let list = self.locals.entry(function).or_default(); 72 | list.push(id); 73 | (list.len() as u32) - 1 74 | } 75 | 76 | /// Gets the ID for a particular index 77 | pub fn get_local(&self, function: FunctionId, index: u32) -> Result { 78 | let locals = match self.locals.get(&function) { 79 | Some(x) => x, 80 | None => bail!( 81 | "function index `{}` is out of bounds for local", 82 | function.index() 83 | ), 84 | }; 85 | match locals.get(index as usize) { 86 | Some(x) => Ok(*x), 87 | None => bail!( 88 | "index `{}` in function `{}` is out of bounds for local", 89 | index, 90 | function.index(), 91 | ), 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/module/producers.rs: -------------------------------------------------------------------------------- 1 | //! Handling of the wasm `producers` section 2 | //! 3 | //! Specified upstream at 4 | //! 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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::{Tag, TagId, 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 Emit for &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 | tags: IdHashMap, 48 | pub(crate) locals: IdHashMap>, 49 | } 50 | 51 | macro_rules! define_get_index { 52 | ( $( 53 | $get_name:ident, $id_ty:ty, $member:ident; 54 | )* ) => { 55 | impl IdsToIndices { 56 | $( 57 | /// Get the index for the given identifier. 58 | #[inline] 59 | pub fn $get_name(&self, id: $id_ty) -> u32 { 60 | self.$member.get(&id).cloned().unwrap_or_else(|| panic!( 61 | "{}: Should never try and get the index for an identifier that has not already had \ 62 | its index set. This means that either we are attempting to get the index of \ 63 | an unused identifier, or that we are emitting sections in the wrong order. \n\n\ 64 | id = {:?}", 65 | stringify!($get_name), 66 | id, 67 | )) 68 | } 69 | )* 70 | } 71 | }; 72 | } 73 | 74 | macro_rules! define_get_push_index { 75 | ( $( 76 | $get_name:ident, $push_name:ident, $id_ty:ty, $member:ident; 77 | )* ) => { 78 | define_get_index!( $( $get_name, $id_ty, $member; )* ); 79 | impl IdsToIndices { 80 | $( 81 | /// Adds the given identifier to this set, assigning it the next 82 | /// available index. 83 | #[inline] 84 | pub(crate) fn $push_name(&mut self, id: $id_ty) { 85 | let idx = self.$member.len() as u32; 86 | log::trace!(concat!(stringify!($push_name),": assigning index {} to {:?}"), idx, id); 87 | self.$member.insert(id, idx); 88 | } 89 | )* 90 | } 91 | }; 92 | } 93 | 94 | define_get_push_index! { 95 | get_table_index, push_table, TableId, tables; 96 | get_type_index, push_type, TypeId, types; 97 | get_func_index, push_func, FunctionId, funcs; 98 | get_global_index, push_global, GlobalId, globals; 99 | get_memory_index, push_memory, MemoryId, memories; 100 | get_element_index, push_element, ElementId, elements; 101 | get_tag_index, push_tag, TagId, tags; 102 | } 103 | define_get_index! { 104 | get_data_index, DataId, data; 105 | } 106 | 107 | impl IdsToIndices { 108 | /// Sets the data index to the specified value 109 | pub(crate) fn set_data_index(&mut self, id: DataId, idx: u32) { 110 | self.data.insert(id, idx); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /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/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 | // Tests that require the "gc" feature. 10 | "tests_spec_tests_array_fill_wast" 11 | | "tests_spec_tests_br_on_cast_wast" 12 | | "tests_spec_tests_br_on_cast_fail_wast" 13 | | "tests_spec_tests_i31_wast" 14 | | "tests_spec_tests_array_new_elem_wast" 15 | | "tests_spec_tests_array_new_data_wast" 16 | | "tests_spec_tests_array_init_elem_wast" 17 | | "tests_spec_tests_array_copy_wast" 18 | | "tests_spec_tests_array_wast" 19 | | "tests_spec_tests_data_wast" 20 | | "tests_spec_tests_extern_wast" 21 | | "tests_spec_tests_array_init_data_wast" 22 | | "tests_spec_tests_ref_cast_wast" 23 | | "tests_spec_tests_ref_null_wast" 24 | | "tests_spec_tests_ref_test_wast" 25 | | "tests_spec_tests_ref_eq_wast" 26 | | "tests_spec_tests_struct_wast" 27 | | "tests_spec_tests_type_canon_wast" 28 | | "tests_spec_tests_type_subtyping_wast" 29 | // Tests that require typed references (ref $typeidx) from function-references proposal 30 | | "tests_spec_tests_br_on_non_null_wast" 31 | | "tests_spec_tests_br_on_null_wast" 32 | | "tests_spec_tests_br_table_wast" 33 | | "tests_spec_tests_call_ref_wast" 34 | | "tests_spec_tests_elem_wast" 35 | | "tests_spec_tests_instance_wast" 36 | | "tests_spec_tests_linking_wast" 37 | | "tests_spec_tests_local_init_wast" 38 | | "tests_spec_tests_ref_as_non_null_wast" 39 | | "tests_spec_tests_ref_is_null_wast" 40 | | "tests_spec_tests_ref_wast" 41 | | "tests_spec_tests_return_call_ref_wast" 42 | | "tests_spec_tests_table_sub_wast" 43 | | "tests_spec_tests_table_wast" 44 | | "tests_spec_tests_type_equivalence_wast" 45 | | "tests_spec_tests_type_rec_wast" 46 | | "tests_spec_tests_unreached_valid_wast" 47 | // Tests that use legacy syntax that is not supported by wasm-tools. 48 | | "tests_spec_tests_legacy_rethrow_wast" 49 | | "tests_spec_tests_legacy_throw_wast" 50 | | "tests_spec_tests_legacy_try_catch_wast" 51 | | "tests_spec_tests_legacy_try_delegate_wast" 52 | // Tests that require GC proposal features not yet supported. 53 | | "tests_spec_tests_tag_wast" // Uses recursive types (rec) 54 | | "tests_spec_tests_try_table_wast" // Uses typed refs like (ref (module 0)) 55 | => true, 56 | 57 | _ => false, 58 | } 59 | } 60 | 61 | fn for_each_wat_file(dir: P, mut f: F) 62 | where 63 | P: AsRef, 64 | F: FnMut(&Path), 65 | { 66 | println!("cargo:rerun-if-changed={}", dir.as_ref().display()); 67 | for entry in WalkDir::new(dir) { 68 | let entry = entry.unwrap(); 69 | if entry.path().extension() == Some(OsStr::new("wat")) 70 | || entry.path().extension() == Some(OsStr::new("wast")) 71 | { 72 | println!("cargo:rerun-if-changed={}", entry.path().display()); 73 | f(entry.path()); 74 | } 75 | } 76 | } 77 | 78 | fn path_to_ident(p: &Path) -> String { 79 | p.display() 80 | .to_string() 81 | .chars() 82 | .map(|c| match c { 83 | 'a'..='z' | 'A'..='Z' | '0'..='9' | '_' => c, 84 | _ => '_', 85 | }) 86 | .collect() 87 | } 88 | 89 | fn generate_tests(name: &str) { 90 | let mut tests = String::new(); 91 | 92 | for_each_wat_file(Path::new("tests").join(name), |path| { 93 | let test_name = path_to_ident(path); 94 | let ignore_test = if is_known_failing(&test_name) { 95 | "#[ignore]" 96 | } else { 97 | "" 98 | }; 99 | tests.push_str(&format!( 100 | "#[test] {} fn {}() {{ walrus_tests_utils::handle(run({:?}.as_ref())); }}\n", 101 | ignore_test, 102 | test_name, 103 | path.display(), 104 | )); 105 | }); 106 | 107 | let out_dir = env::var("OUT_DIR").unwrap(); 108 | fs::write(Path::new(&out_dir).join(name).with_extension("rs"), &tests) 109 | .expect("should write generated valid.rs file OK"); 110 | } 111 | 112 | fn main() { 113 | println!("cargo:rerun-if-changed=build.rs"); 114 | println!("cargo:rerun-if-env-changed=WALRUS_TESTS_DOT"); 115 | 116 | generate_tests("valid"); 117 | generate_tests("round_trip"); 118 | generate_tests("spec-tests"); 119 | generate_tests("function_imports"); 120 | generate_tests("invalid"); 121 | } 122 | -------------------------------------------------------------------------------- /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/tags.rs: -------------------------------------------------------------------------------- 1 | //! Tags for exception handling 2 | 3 | use crate::emit::{Emit, EmitContext}; 4 | use crate::module::imports::ImportId; 5 | use crate::parse::IndicesToIds; 6 | use crate::tombstone_arena::{Id, Tombstone, TombstoneArena}; 7 | use crate::TypeId; 8 | use anyhow::Result; 9 | 10 | /// The id of a tag. 11 | pub type TagId = Id; 12 | 13 | /// A tag in a WebAssembly module, used for exception handling. 14 | #[derive(Debug)] 15 | pub struct Tag { 16 | /// The id of this tag. 17 | pub id: TagId, 18 | /// The type signature of this tag (function type). 19 | pub ty: TypeId, 20 | /// The kind of tag (imported or local). 21 | pub kind: TagKind, 22 | /// The name of this tag, used for debugging purposes in the `name` 23 | /// custom section. 24 | pub name: Option, 25 | } 26 | 27 | /// The kind of tag. 28 | #[derive(Debug)] 29 | pub enum TagKind { 30 | /// An imported tag. 31 | Import(ImportId), 32 | /// A locally defined tag. 33 | Local, 34 | } 35 | 36 | impl Tag { 37 | /// Create a new local tag with the given type. 38 | pub fn new(id: TagId, ty: TypeId) -> Tag { 39 | Tag { 40 | id, 41 | ty, 42 | kind: TagKind::Local, 43 | name: None, 44 | } 45 | } 46 | 47 | /// Get the id of this tag. 48 | pub fn id(&self) -> TagId { 49 | self.id 50 | } 51 | 52 | /// Get the type of this tag. 53 | pub fn ty(&self) -> TypeId { 54 | self.ty 55 | } 56 | } 57 | 58 | impl Tombstone for Tag { 59 | fn on_delete(&mut self) { 60 | // No resources to clean up 61 | } 62 | } 63 | 64 | impl Emit for ModuleTags { 65 | fn emit(&self, cx: &mut EmitContext) { 66 | log::debug!("emit tag section"); 67 | 68 | let tags: Vec<_> = self 69 | .iter() 70 | .filter(|t| matches!(t.kind, TagKind::Local)) 71 | .collect(); 72 | 73 | if tags.is_empty() { 74 | return; 75 | } 76 | 77 | let mut tag_section = wasm_encoder::TagSection::new(); 78 | 79 | for tag in tags { 80 | cx.indices.push_tag(tag.id); 81 | let ty_idx = cx.indices.get_type_index(tag.ty); 82 | tag_section.tag(wasm_encoder::TagType { 83 | kind: wasm_encoder::TagKind::Exception, 84 | func_type_idx: ty_idx, 85 | }); 86 | } 87 | 88 | cx.wasm_module.section(&tag_section); 89 | } 90 | } 91 | 92 | /// All tags in a WebAssembly module. 93 | #[derive(Debug, Default)] 94 | pub struct ModuleTags { 95 | /// Arena for tags. 96 | arena: TombstoneArena, 97 | } 98 | 99 | impl ModuleTags { 100 | /// Create a new empty tag arena. 101 | pub fn new() -> ModuleTags { 102 | ModuleTags::default() 103 | } 104 | 105 | /// Add a new tag to this module. 106 | pub fn add(&mut self, ty: TypeId) -> TagId { 107 | let id = self.arena.next_id(); 108 | let tag = Tag::new(id, ty); 109 | self.arena.alloc(tag) 110 | } 111 | 112 | /// Add an imported tag to this module. 113 | pub fn add_import(&mut self, ty: TypeId, import: ImportId) -> TagId { 114 | let id = self.arena.next_id(); 115 | let tag = Tag { 116 | id, 117 | ty, 118 | kind: TagKind::Import(import), 119 | name: None, 120 | }; 121 | self.arena.alloc(tag) 122 | } 123 | 124 | /// Get a tag by id. 125 | pub fn get(&self, id: TagId) -> &Tag { 126 | &self.arena[id] 127 | } 128 | 129 | /// Get a mutable reference to a tag by id. 130 | pub fn get_mut(&mut self, id: TagId) -> &mut Tag { 131 | &mut self.arena[id] 132 | } 133 | 134 | /// Get an iterator over all tags. 135 | pub fn iter(&self) -> impl Iterator { 136 | self.arena.iter().map(|(_, tag)| tag) 137 | } 138 | 139 | /// Get an iterator over all tags with their IDs. 140 | pub fn iter_mut(&mut self) -> impl Iterator { 141 | self.arena.iter_mut().map(|(_, tag)| tag) 142 | } 143 | 144 | /// Delete a tag from this module. 145 | pub fn delete(&mut self, id: TagId) { 146 | self.arena.delete(id); 147 | } 148 | } 149 | 150 | impl Module { 151 | /// Parse the tag section of a wasm module. 152 | pub(crate) fn parse_tags( 153 | &mut self, 154 | section: wasmparser::TagSectionReader, 155 | ids: &mut IndicesToIds, 156 | ) -> Result<()> { 157 | log::debug!("parse tag section"); 158 | for tag in section { 159 | let tag = tag?; 160 | // Currently Exception is the only TagKind variant 161 | let wasmparser::TagKind::Exception = tag.kind; 162 | let ty = ids.get_type(tag.func_type_idx)?; 163 | let tag_id = self.tags.add(ty); 164 | ids.push_tag(tag_id); 165 | } 166 | Ok(()) 167 | } 168 | } 169 | 170 | use crate::Module; 171 | -------------------------------------------------------------------------------- /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 init_expr = ConstExpr::eval(&g.init_expr, ids)?; 127 | let id = self.globals.add_local( 128 | ValType::parse(&g.ty.content_type)?, 129 | g.ty.mutable, 130 | g.ty.shared, 131 | init_expr, 132 | ); 133 | ids.push_global(id); 134 | } 135 | Ok(()) 136 | } 137 | } 138 | 139 | impl Emit for ModuleGlobals { 140 | fn emit(&self, cx: &mut EmitContext) { 141 | log::debug!("emit global section"); 142 | let mut wasm_global_section = wasm_encoder::GlobalSection::new(); 143 | 144 | fn get_local(global: &Global) -> Option<(&Global, &ConstExpr)> { 145 | match &global.kind { 146 | GlobalKind::Import(_) => None, 147 | GlobalKind::Local(local) => Some((global, local)), 148 | } 149 | } 150 | 151 | // All imported globals emitted earlier during the import section, so 152 | // filter those out. 153 | let globals = self.iter().filter_map(get_local).count(); 154 | if globals == 0 { 155 | return; 156 | } 157 | 158 | for (global, local) in self.iter().filter_map(get_local) { 159 | cx.indices.push_global(global.id()); 160 | 161 | wasm_global_section.global( 162 | wasm_encoder::GlobalType { 163 | val_type: global.ty.to_wasmencoder_type(), 164 | mutable: global.mutable, 165 | shared: global.shared, 166 | }, 167 | &local.to_wasmencoder_type(cx), 168 | ); 169 | } 170 | 171 | cx.wasm_module.section(&wasm_global_section); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /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.ty().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/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<'_, T> { 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<'_, T> 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/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 | RefType::Exnref => wasm_encoder::RefType::EXNREF, 195 | }, 196 | shared: false, 197 | }); 198 | } 199 | 200 | cx.wasm_module.section(&wasm_table_section); 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /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/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 | /// A nullable reference to an exception (exnref from exception handling proposal) 154 | Exnref, 155 | } 156 | 157 | impl TryFrom for RefType { 158 | type Error = anyhow::Error; 159 | 160 | fn try_from(ref_type: wasmparser::RefType) -> Result { 161 | match ref_type { 162 | wasmparser::RefType::FUNCREF => Ok(RefType::Funcref), 163 | wasmparser::RefType::EXTERNREF => Ok(RefType::Externref), 164 | wasmparser::RefType::EXNREF => Ok(RefType::Exnref), 165 | _ => bail!("unsupported ref type {:?}", ref_type), 166 | } 167 | } 168 | } 169 | 170 | impl ValType { 171 | pub(crate) fn from_wasmparser_type(ty: wasmparser::ValType) -> Result> { 172 | let v = vec![ValType::parse(&ty)?]; 173 | Ok(v.into_boxed_slice()) 174 | } 175 | 176 | #[allow(clippy::wrong_self_convention)] 177 | pub(crate) fn to_wasmencoder_type(&self) -> wasm_encoder::ValType { 178 | match self { 179 | ValType::I32 => wasm_encoder::ValType::I32, 180 | ValType::I64 => wasm_encoder::ValType::I64, 181 | ValType::F32 => wasm_encoder::ValType::F32, 182 | ValType::F64 => wasm_encoder::ValType::F64, 183 | ValType::V128 => wasm_encoder::ValType::V128, 184 | ValType::Ref(ref_type) => match ref_type { 185 | RefType::Externref => wasm_encoder::ValType::Ref(wasm_encoder::RefType::EXTERNREF), 186 | RefType::Funcref => wasm_encoder::ValType::Ref(wasm_encoder::RefType::FUNCREF), 187 | RefType::Exnref => wasm_encoder::ValType::Ref(wasm_encoder::RefType::EXNREF), 188 | }, 189 | } 190 | } 191 | 192 | pub(crate) fn parse(input: &wasmparser::ValType) -> Result { 193 | match input { 194 | wasmparser::ValType::I32 => Ok(ValType::I32), 195 | wasmparser::ValType::I64 => Ok(ValType::I64), 196 | wasmparser::ValType::F32 => Ok(ValType::F32), 197 | wasmparser::ValType::F64 => Ok(ValType::F64), 198 | wasmparser::ValType::V128 => Ok(ValType::V128), 199 | wasmparser::ValType::Ref(ref_type) => match *ref_type { 200 | wasmparser::RefType::EXTERNREF => Ok(ValType::Ref(RefType::Externref)), 201 | wasmparser::RefType::FUNCREF => Ok(ValType::Ref(RefType::Funcref)), 202 | wasmparser::RefType::EXNREF => Ok(ValType::Ref(RefType::Exnref)), 203 | _ => bail!("unsupported ref type {:?}", ref_type), 204 | }, 205 | } 206 | } 207 | } 208 | 209 | impl fmt::Display for ValType { 210 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 211 | write!( 212 | f, 213 | "{}", 214 | match self { 215 | ValType::I32 => "i32", 216 | ValType::I64 => "i64", 217 | ValType::F32 => "f32", 218 | ValType::F64 => "f64", 219 | ValType::V128 => "v128", 220 | ValType::Ref(RefType::Externref) => "externref", 221 | ValType::Ref(RefType::Funcref) => "funcref", 222 | ValType::Ref(RefType::Exnref) => "exnref", 223 | } 224 | ) 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /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 | pub fn add_legacy_catch(&mut self, catch: crate::ir::LegacyCatch) -> Result<()> { 160 | // Find the most recent Try instruction in the parent control block 161 | let frame = self.control(1)?; // Parent block, not the try block itself 162 | let block = frame.block; 163 | let seq = self.func.block_mut(block); 164 | 165 | // The Try instruction should be the last instruction in the parent block 166 | if let Some((Instr::Try(ref mut try_instr), _)) = seq.instrs.last_mut() { 167 | try_instr.catches.push(catch); 168 | return Ok(()); 169 | } 170 | 171 | anyhow::bail!("No Try instruction found to add catch clause to"); 172 | } 173 | } 174 | 175 | fn impl_push_control( 176 | types: &ModuleTypes, 177 | kind: BlockKind, 178 | func: &mut LocalFunction, 179 | controls: &mut ControlStack, 180 | start_types: Box<[ValType]>, 181 | end_types: Box<[ValType]>, 182 | ) -> Result { 183 | let ty = InstrSeqType::existing(types, &start_types, &end_types).ok_or_else(|| { 184 | anyhow::anyhow!( 185 | "attempted to push a control frame for an instruction \ 186 | sequence with a type that does not exist" 187 | ) 188 | .context(format!("type: {:?} -> {:?}", &start_types, &end_types)) 189 | })?; 190 | 191 | Ok(impl_push_control_with_ty( 192 | types, 193 | kind, 194 | func, 195 | controls, 196 | ty, 197 | start_types, 198 | end_types, 199 | )) 200 | } 201 | 202 | fn impl_push_control_with_ty( 203 | types: &ModuleTypes, 204 | kind: BlockKind, 205 | func: &mut LocalFunction, 206 | controls: &mut ControlStack, 207 | ty: InstrSeqType, 208 | start_types: Box<[ValType]>, 209 | end_types: Box<[ValType]>, 210 | ) -> InstrSeqId { 211 | if let InstrSeqType::MultiValue(ty) = ty { 212 | debug_assert_eq!(types.params(ty), &start_types[..]); 213 | debug_assert_eq!(types.results(ty), &end_types[..]); 214 | } 215 | 216 | let block = func.add_block(|id| InstrSeq::new(id, ty)); 217 | 218 | controls.push(ControlFrame { 219 | start_types, 220 | end_types, 221 | unreachable: false, 222 | block, 223 | kind, 224 | }); 225 | 226 | block 227 | } 228 | 229 | fn impl_pop_control(controls: &mut ControlStack) -> Result { 230 | controls 231 | .last() 232 | .ok_or(ErrorKind::InvalidWasm) 233 | .context("attempted to pop a frame from an empty control stack")?; 234 | let frame = controls.pop().unwrap(); 235 | Ok(frame) 236 | } 237 | -------------------------------------------------------------------------------- /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/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("custom-descriptors") => return Ok(()), 29 | Some("custom-page-sizes") => return Ok(()), 30 | Some("exception-handling") => &[], 31 | Some("extended-const") => &[], 32 | Some("function-references") => &[], 33 | Some("gc") => return Ok(()), 34 | Some("relaxed-simd") => &[], 35 | Some("tail-call") => &[], 36 | Some("threads") => return Ok(()), 37 | Some("wide-arithmetic") => return Ok(()), 38 | Some(other) => bail!("unknown wasm proposal: {}", other), 39 | }; 40 | 41 | let tempdir = TempDir::new()?; 42 | let json = tempdir.path().join("foo.json"); 43 | // Using `wasm-tools json-from-wast` instead of wabt's `wast2json` 44 | // because the latter is slow to support new proposals. 45 | let output = Command::new("wasm-tools") 46 | .arg("json-from-wast") 47 | .arg("--pretty") 48 | .arg(wast) 49 | .arg("--output") 50 | .arg(&json) 51 | .arg("--wasm-dir") 52 | .arg(tempdir.path()) 53 | .output()?; 54 | if !output.status.success() { 55 | let stderr = String::from_utf8_lossy(&output.stderr); 56 | bail!("failed to run `wasm-tools json-from-wast`\nstderr: {stderr}"); 57 | } 58 | 59 | let contents = fs::read_to_string(&json).context("failed to read file")?; 60 | let test: Test = serde_json::from_str(&contents).context("failed to parse file")?; 61 | let mut files = Vec::new(); 62 | 63 | let mut config = walrus::ModuleConfig::new(); 64 | if proposal.is_none() { 65 | // For non-proposals tests, we only enable the stable features. 66 | // For proposals tests, we enable all supported features. 67 | config.only_stable_features(true); 68 | } 69 | 70 | let wabt_ok = run_spectest_interp(tempdir.path(), extra_args).is_ok(); 71 | 72 | let mut should_not_parse = vec![]; 73 | let mut non_deterministic = vec![]; 74 | for command in test.commands { 75 | let filename = match command.get("filename") { 76 | Some(name) => name.as_str().unwrap().to_string(), 77 | None => continue, 78 | }; 79 | // walrus only process .wasm binary files 80 | if filename.ends_with(".wat") { 81 | continue; 82 | } 83 | let line = command["line"].as_u64().unwrap(); 84 | let path = tempdir.path().join(filename); 85 | match command["type"].as_str().unwrap() { 86 | "assert_invalid" | "assert_malformed" => { 87 | if proposal.is_some() 88 | && ["zero byte expected", "multiple memories"] 89 | .contains(&command["text"].as_str().unwrap()) 90 | { 91 | // The multi-memory proposal is enabled for all proprosals tests 92 | // but some proposals tests still expect them to fail. 93 | continue; 94 | } 95 | 96 | let wasm = fs::read(&path)?; 97 | if config.parse(&wasm).is_ok() { 98 | should_not_parse.push(line); 99 | } 100 | } 101 | cmd => { 102 | // The bytes read from the original spec test case 103 | let bytes0 = fs::read(&path)?; 104 | // The module parsed from bytes0 105 | let mut wasm1 = config.parse(&bytes0).with_context(|| { 106 | format!( 107 | "error parsing wasm ({}, line {})", 108 | path.to_string_lossy(), 109 | line 110 | ) 111 | })?; 112 | // The bytes emitted from wasm1 113 | let bytes1 = wasm1.emit_wasm(); 114 | fs::write(&path, &bytes1)?; 115 | // The module parsed from bytes1 116 | let mut wasm2 = config.parse(&bytes1).with_context(|| { 117 | format!( 118 | "error re-parsing wasm ({}, line {})", 119 | path.to_string_lossy(), 120 | line 121 | ) 122 | })?; 123 | // The bytes emitted from wasm2 124 | let bytes2 = wasm2.emit_wasm(); 125 | 126 | if bytes1 != bytes2 { 127 | non_deterministic.push(line); 128 | } 129 | files.push((cmd.to_string(), path.to_path_buf())); 130 | continue; 131 | } 132 | } 133 | } 134 | 135 | let mut message = String::new(); 136 | if !should_not_parse.is_empty() { 137 | message.push_str(&format!( 138 | "wasm parsed when it shouldn't at line: {:?}", 139 | should_not_parse 140 | )); 141 | } 142 | if !non_deterministic.is_empty() { 143 | message.push_str(&format!( 144 | "wasm isn't deterministic at line: {:?}", 145 | non_deterministic 146 | )); 147 | } 148 | if !message.is_empty() { 149 | panic!("{}", message); 150 | } 151 | 152 | // If wabt didn't succeed before we ran walrus there's no hope of it passing 153 | // after we run walrus. 154 | if !wabt_ok { 155 | return Ok(()); 156 | } 157 | 158 | // First up run the spec-tests as-is after we round-tripped through walrus. 159 | // This should for sure work correctly 160 | run_spectest_interp(tempdir.path(), extra_args)?; 161 | 162 | // Next run the same spec tests with semantics-preserving passes implemented 163 | // in walrus. Everything should continue to pass. 164 | for (cmd, file) in files.iter() { 165 | let wasm = fs::read(file)?; 166 | let mut module = config.parse(&wasm)?; 167 | 168 | // Tests which assert that they're not linkable tend to not work with 169 | // the gc pass because it removes things which would cause a module to 170 | // become unlinkable. This doesn't matter too much in the real world 171 | // (hopefully), so just don't gc assert_unlinkable modules. The same 172 | // applies to assert_uninstantiable modules due to removal of unused 173 | // elements and tables. 174 | if !matches!(cmd.as_str(), "assert_unlinkable" | "assert_uninstantiable") { 175 | walrus::passes::gc::run(&mut module); 176 | } 177 | 178 | let wasm = module.emit_wasm(); 179 | fs::write(file, wasm)?; 180 | } 181 | 182 | run_spectest_interp(tempdir.path(), extra_args)?; 183 | 184 | Ok(()) 185 | } 186 | 187 | fn run_spectest_interp(cwd: &Path, extra_args: &[&str]) -> Result<(), anyhow::Error> { 188 | let output = Command::new("spectest-interp") 189 | .current_dir(cwd) 190 | .arg("foo.json") 191 | .args(extra_args) 192 | .output() 193 | .context("executing `spectest-interp`")?; 194 | 195 | // If the interpreter exits with success it may still have failed some 196 | // tests. Check the output for `X/Y tests passed.` and make sure `X` equals 197 | // `Y`. 198 | if output.status.success() { 199 | let stdout = String::from_utf8_lossy(&output.stdout); 200 | if let Some(line) = stdout.lines().find(|l| l.ends_with("tests passed.")) { 201 | let part = line.split_whitespace().next().unwrap(); 202 | let mut parts = part.split("/"); 203 | let a = parts.next().unwrap().parse::(); 204 | let b = parts.next().unwrap().parse::(); 205 | if a == b { 206 | return Ok(()); 207 | } 208 | } 209 | } 210 | println!("status: {}", output.status); 211 | println!("stdout:\n{}", String::from_utf8_lossy(&output.stdout)); 212 | println!("stderr:\n{}", String::from_utf8_lossy(&output.stderr)); 213 | bail!("failed"); 214 | } 215 | 216 | include!(concat!(env!("OUT_DIR"), "/spec-tests.rs")); 217 | -------------------------------------------------------------------------------- /crates/tests/tests/const_expr_mutation.rs: -------------------------------------------------------------------------------- 1 | //! Tests for const expression mutation API 2 | 3 | use walrus::{ConstExpr, ConstOp, Module, ModuleConfig, RefType, ValType}; 4 | 5 | #[test] 6 | fn create_global_with_extended_const_expr_i32_add() { 7 | let mut config = ModuleConfig::new(); 8 | config.generate_producers_section(false); 9 | let mut module = Module::with_config(config.clone()); 10 | 11 | let init = ConstExpr::Extended(vec![ 12 | ConstOp::I32Const(5), 13 | ConstOp::I32Const(3), 14 | ConstOp::I32Add, 15 | ]); 16 | 17 | let global_id = module.globals.add_local(ValType::I32, false, false, init); 18 | module.exports.add("g", global_id); 19 | 20 | let wasm = module.emit_wasm(); 21 | 22 | let mut module2 = config.parse(&wasm).unwrap(); 23 | let wasm2 = module2.emit_wasm(); 24 | 25 | assert_eq!(wasm, wasm2, "Round-trip should be deterministic"); 26 | } 27 | 28 | #[test] 29 | fn create_global_with_extended_const_expr_i32_sub() { 30 | let mut config = ModuleConfig::new(); 31 | config.generate_producers_section(false); 32 | let mut module = Module::with_config(config.clone()); 33 | 34 | let init = ConstExpr::Extended(vec![ 35 | ConstOp::I32Const(15), 36 | ConstOp::I32Const(7), 37 | ConstOp::I32Sub, 38 | ]); 39 | 40 | let global_id = module.globals.add_local(ValType::I32, false, false, init); 41 | module.exports.add("g", global_id); 42 | 43 | let wasm = module.emit_wasm(); 44 | let mut module2 = config.parse(&wasm).unwrap(); 45 | let wasm2 = module2.emit_wasm(); 46 | 47 | assert_eq!(wasm, wasm2); 48 | } 49 | 50 | #[test] 51 | fn create_global_with_extended_const_expr_i32_mul() { 52 | let mut config = ModuleConfig::new(); 53 | config.generate_producers_section(false); 54 | let mut module = Module::with_config(config.clone()); 55 | 56 | let init = ConstExpr::Extended(vec![ 57 | ConstOp::I32Const(4), 58 | ConstOp::I32Const(6), 59 | ConstOp::I32Mul, 60 | ]); 61 | 62 | let global_id = module.globals.add_local(ValType::I32, false, false, init); 63 | module.exports.add("g", global_id); 64 | 65 | let wasm = module.emit_wasm(); 66 | let mut module2 = config.parse(&wasm).unwrap(); 67 | let wasm2 = module2.emit_wasm(); 68 | 69 | assert_eq!(wasm, wasm2); 70 | } 71 | 72 | #[test] 73 | fn create_global_with_extended_const_expr_i64_add() { 74 | let mut config = ModuleConfig::new(); 75 | config.generate_producers_section(false); 76 | let mut module = Module::with_config(config.clone()); 77 | 78 | let init = ConstExpr::Extended(vec![ 79 | ConstOp::I64Const(100), 80 | ConstOp::I64Const(50), 81 | ConstOp::I64Add, 82 | ]); 83 | 84 | let global_id = module.globals.add_local(ValType::I64, false, false, init); 85 | module.exports.add("g", global_id); 86 | 87 | let wasm = module.emit_wasm(); 88 | let mut module2 = config.parse(&wasm).unwrap(); 89 | let wasm2 = module2.emit_wasm(); 90 | 91 | assert_eq!(wasm, wasm2); 92 | } 93 | 94 | #[test] 95 | fn create_global_with_extended_const_expr_i64_sub() { 96 | let mut config = ModuleConfig::new(); 97 | config.generate_producers_section(false); 98 | let mut module = Module::with_config(config.clone()); 99 | 100 | let init = ConstExpr::Extended(vec![ 101 | ConstOp::I64Const(200), 102 | ConstOp::I64Const(75), 103 | ConstOp::I64Sub, 104 | ]); 105 | 106 | let global_id = module.globals.add_local(ValType::I64, false, false, init); 107 | module.exports.add("g", global_id); 108 | 109 | let wasm = module.emit_wasm(); 110 | let mut module2 = config.parse(&wasm).unwrap(); 111 | let wasm2 = module2.emit_wasm(); 112 | 113 | assert_eq!(wasm, wasm2); 114 | } 115 | 116 | #[test] 117 | fn create_global_with_extended_const_expr_i64_mul() { 118 | let mut config = ModuleConfig::new(); 119 | config.generate_producers_section(false); 120 | let mut module = Module::with_config(config.clone()); 121 | 122 | let init = ConstExpr::Extended(vec![ 123 | ConstOp::I64Const(8), 124 | ConstOp::I64Const(9), 125 | ConstOp::I64Mul, 126 | ]); 127 | 128 | let global_id = module.globals.add_local(ValType::I64, false, false, init); 129 | module.exports.add("g", global_id); 130 | 131 | let wasm = module.emit_wasm(); 132 | let mut module2 = config.parse(&wasm).unwrap(); 133 | let wasm2 = module2.emit_wasm(); 134 | 135 | assert_eq!(wasm, wasm2); 136 | } 137 | 138 | #[test] 139 | fn create_global_with_extended_const_expr_complex() { 140 | let mut config = ModuleConfig::new(); 141 | config.generate_producers_section(false); 142 | let mut module = Module::with_config(config.clone()); 143 | 144 | let init = ConstExpr::Extended(vec![ 145 | ConstOp::I32Const(10), 146 | ConstOp::I32Const(5), 147 | ConstOp::I32Add, 148 | ConstOp::I32Const(2), 149 | ConstOp::I32Mul, 150 | ]); 151 | 152 | let global_id = module.globals.add_local(ValType::I32, false, false, init); 153 | module.exports.add("g", global_id); 154 | 155 | let wasm = module.emit_wasm(); 156 | let mut module2 = config.parse(&wasm).unwrap(); 157 | let wasm2 = module2.emit_wasm(); 158 | 159 | assert_eq!(wasm, wasm2); 160 | } 161 | 162 | #[test] 163 | fn create_global_with_extended_const_expr_global_get() { 164 | let mut config = ModuleConfig::new(); 165 | config.generate_producers_section(false); 166 | let mut module = Module::with_config(config.clone()); 167 | 168 | let base_global = module.globals.add_local( 169 | ValType::I32, 170 | false, 171 | false, 172 | ConstExpr::Value(walrus::ir::Value::I32(42)), 173 | ); 174 | 175 | let init = ConstExpr::Extended(vec![ 176 | ConstOp::GlobalGet(base_global), 177 | ConstOp::I32Const(8), 178 | ConstOp::I32Add, 179 | ]); 180 | 181 | let derived_global = module.globals.add_local(ValType::I32, false, false, init); 182 | module.exports.add("base", base_global); 183 | module.exports.add("derived", derived_global); 184 | 185 | let wasm = module.emit_wasm(); 186 | let mut module2 = config.parse(&wasm).unwrap(); 187 | let wasm2 = module2.emit_wasm(); 188 | 189 | assert_eq!(wasm, wasm2); 190 | } 191 | 192 | #[test] 193 | fn create_global_with_extended_const_expr_f32() { 194 | let mut config = ModuleConfig::new(); 195 | config.generate_producers_section(false); 196 | let mut module = Module::with_config(config.clone()); 197 | 198 | let init = ConstExpr::Extended(vec![ConstOp::F32Const(3.14)]); 199 | 200 | let global_id = module.globals.add_local(ValType::F32, false, false, init); 201 | module.exports.add("g", global_id); 202 | 203 | let wasm = module.emit_wasm(); 204 | let mut module2 = config.parse(&wasm).unwrap(); 205 | let wasm2 = module2.emit_wasm(); 206 | 207 | assert_eq!(wasm, wasm2); 208 | } 209 | 210 | #[test] 211 | fn create_global_with_extended_const_expr_f64() { 212 | let mut config = ModuleConfig::new(); 213 | config.generate_producers_section(false); 214 | let mut module = Module::with_config(config.clone()); 215 | 216 | let init = ConstExpr::Extended(vec![ConstOp::F64Const(2.71828)]); 217 | 218 | let global_id = module.globals.add_local(ValType::F64, false, false, init); 219 | module.exports.add("g", global_id); 220 | 221 | let wasm = module.emit_wasm(); 222 | let mut module2 = config.parse(&wasm).unwrap(); 223 | let wasm2 = module2.emit_wasm(); 224 | 225 | assert_eq!(wasm, wasm2); 226 | } 227 | 228 | #[test] 229 | fn create_global_with_extended_const_expr_v128() { 230 | let mut config = ModuleConfig::new(); 231 | config.generate_producers_section(false); 232 | let mut module = Module::with_config(config.clone()); 233 | 234 | let init = ConstExpr::Extended(vec![ConstOp::V128Const(0x0102030405060708090a0b0c0d0e0f10)]); 235 | 236 | let global_id = module.globals.add_local(ValType::V128, false, false, init); 237 | module.exports.add("g", global_id); 238 | 239 | let wasm = module.emit_wasm(); 240 | let mut module2 = config.parse(&wasm).unwrap(); 241 | let wasm2 = module2.emit_wasm(); 242 | 243 | assert_eq!(wasm, wasm2); 244 | } 245 | 246 | #[test] 247 | fn create_global_with_extended_const_expr_ref_null() { 248 | let mut config = ModuleConfig::new(); 249 | config.generate_producers_section(false); 250 | let mut module = Module::with_config(config.clone()); 251 | 252 | let init = ConstExpr::Extended(vec![ConstOp::RefNull(RefType::Funcref)]); 253 | 254 | let global_id = module 255 | .globals 256 | .add_local(ValType::Ref(RefType::Funcref), false, false, init); 257 | module.exports.add("g", global_id); 258 | 259 | let wasm = module.emit_wasm(); 260 | let mut module2 = config.parse(&wasm).unwrap(); 261 | let wasm2 = module2.emit_wasm(); 262 | 263 | assert_eq!(wasm, wasm2); 264 | } 265 | 266 | #[test] 267 | fn create_global_with_extended_const_expr_ref_func() { 268 | let mut config = ModuleConfig::new(); 269 | config.generate_producers_section(false); 270 | let mut module = Module::with_config(config.clone()); 271 | 272 | let builder = walrus::FunctionBuilder::new(&mut module.types, &[], &[]); 273 | let func_id = builder.finish(vec![], &mut module.funcs); 274 | 275 | let init = ConstExpr::Extended(vec![ConstOp::RefFunc(func_id)]); 276 | 277 | let global_id = module 278 | .globals 279 | .add_local(ValType::Ref(RefType::Funcref), false, false, init); 280 | module.exports.add("f", func_id); 281 | module.exports.add("g", global_id); 282 | 283 | let wasm = module.emit_wasm(); 284 | let mut module2 = config.parse(&wasm).unwrap(); 285 | let wasm2 = module2.emit_wasm(); 286 | 287 | assert_eq!(wasm, wasm2); 288 | } 289 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------