├── .gitattributes ├── .gitignore ├── tests ├── hello.wasm ├── no_fmt.wasm ├── no_alloc.wasm ├── snip_me.wasm ├── no_panicking.wasm ├── README.md ├── hello.rs └── tests.rs ├── .travis.yml ├── .github └── dependabot.yml ├── Cargo.toml ├── README.tpl ├── CONTRIBUTING.md ├── README.md └── src ├── bin └── wasm-snip.rs └── lib.rs /.gitattributes: -------------------------------------------------------------------------------- 1 | README.md -diff -merge 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /tests/*.actual 2 | /target/ 3 | **/*.rs.bk 4 | Cargo.lock 5 | -------------------------------------------------------------------------------- /tests/hello.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rustwasm/wasm-snip/HEAD/tests/hello.wasm -------------------------------------------------------------------------------- /tests/no_fmt.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rustwasm/wasm-snip/HEAD/tests/no_fmt.wasm -------------------------------------------------------------------------------- /tests/no_alloc.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rustwasm/wasm-snip/HEAD/tests/no_alloc.wasm -------------------------------------------------------------------------------- /tests/snip_me.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rustwasm/wasm-snip/HEAD/tests/snip_me.wasm -------------------------------------------------------------------------------- /tests/no_panicking.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rustwasm/wasm-snip/HEAD/tests/no_panicking.wasm -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | To regenerate the wasm file: 2 | 3 | ``` 4 | $ rustc +nightly --target wasm32-unknown-unknown -O -g ./hello.rs 5 | ``` 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | 3 | cache: cargo 4 | 5 | before_script: 6 | - cargo install -f cargo-readme 7 | 8 | rust: 9 | - stable 10 | - beta 11 | - nightly 12 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "08:00" 8 | open-pull-requests-limit: 10 9 | ignore: 10 | - dependency-name: walrus 11 | versions: 12 | - 0.18.0 13 | -------------------------------------------------------------------------------- /tests/hello.rs: -------------------------------------------------------------------------------- 1 | #![cfg(target_arch = "wasm32")] 2 | 3 | #[no_mangle] 4 | pub fn fluxions(x: usize) -> usize { 5 | unsafe { 6 | imported(x); 7 | imported2(x); 8 | imported3(x); 9 | imported4(x) 10 | } 11 | } 12 | 13 | #[no_mangle] 14 | pub fn quicksilver(x: usize) { 15 | if x > 100 { 16 | snip_me(); 17 | } 18 | } 19 | 20 | extern "C" { 21 | fn imported(x: usize) -> usize; 22 | fn imported2(x: usize) -> usize; 23 | fn imported3(x: usize) -> usize; 24 | fn imported4(x: usize) -> usize; 25 | } 26 | 27 | #[inline(never)] 28 | fn snip_me() { 29 | println!("this is gonna get snipped"); 30 | } 31 | 32 | pub fn main() {} 33 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Nick Fitzgerald ", "The Rust and WebAssembly Working Group"] 3 | categories = ["wasm"] 4 | description = "Replace a wasm function with an `unreachable`." 5 | edition = "2018" 6 | keywords = [ 7 | "symbol", 8 | "webassembly", 9 | "snip", 10 | "size", 11 | "profiling", 12 | ] 13 | license = "Apache-2.0/MIT" 14 | name = "wasm-snip" 15 | readme = "./README.md" 16 | repository = "https://github.com/rustwasm/wasm-snip" 17 | version = "0.4.0" 18 | 19 | [[bin]] 20 | doc = false 21 | name = "wasm-snip" 22 | path = "src/bin/wasm-snip.rs" 23 | required-features = ["exe"] 24 | 25 | [dependencies] 26 | failure = "0.1.5" 27 | walrus = { version = "0.12.0", features = ["parallel"] } 28 | regex = "1.3.1" 29 | rayon = "1.2.0" 30 | 31 | [dependencies.clap] 32 | optional = true 33 | version = "2.33.0" 34 | 35 | [features] 36 | default = ["exe"] 37 | exe = ["clap"] 38 | 39 | [dev-dependencies] 40 | assert_cmd = "1.0.0" 41 | -------------------------------------------------------------------------------- /README.tpl: -------------------------------------------------------------------------------- 1 |
2 | 3 |

wasm-snip

4 | 5 | wasm-snip replaces a Wasm function's body with an unreachable instruction. 6 | 7 |

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

13 | 14 |

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

21 | 22 | Built with 🦀🕸 by The Rust and WebAssembly Working Group 23 |
24 | 25 | ## About 26 | 27 | {{readme}} 28 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to `wasm-snip` 2 | 3 | Hi! We'd love to have your contributions! If you want help or mentorship, reach 4 | out to us in a GitHub issue, or ping `fitzgen` 5 | in [#rust on irc.mozilla.org](irc://irc.mozilla.org#rust) and introduce 6 | yourself. 7 | 8 | 9 | 10 | 11 | 12 | - [Code of Conduct](#code-of-conduct) 13 | - [Building](#building) 14 | - [Testing](#testing) 15 | - [Automatic code formatting](#automatic-code-formatting) 16 | 17 | 18 | 19 | ## Code of Conduct 20 | 21 | We abide by the [Rust Code of Conduct][coc] and ask that you do as well. 22 | 23 | [coc]: https://www.rust-lang.org/en-US/conduct.html 24 | 25 | ## Building 26 | 27 | ``` 28 | $ cargo build 29 | ``` 30 | 31 | ## Testing 32 | 33 | The tests require `cargo-readme` to be installed: 34 | 35 | ``` 36 | $ cargo install cargo-readme 37 | ``` 38 | 39 | To run all the tests: 40 | 41 | ``` 42 | $ cargo test 43 | ``` 44 | 45 | ## Automatic code formatting 46 | 47 | We use [`rustfmt`](https://github.com/rust-lang-nursery/rustfmt) to enforce a 48 | consistent code style across the whole code base. 49 | 50 | You can install the latest version of `rustfmt` with this command: 51 | 52 | ``` 53 | $ rustup update nightly 54 | $ cargo install -f rustfmt-nightly 55 | ``` 56 | 57 | Ensure that `~/.cargo/bin` is on your path. 58 | 59 | Once that is taken care of, you can (re)format all code by running this command: 60 | 61 | ``` 62 | $ cargo fmt 63 | ``` 64 | -------------------------------------------------------------------------------- /tests/tests.rs: -------------------------------------------------------------------------------- 1 | use assert_cmd::prelude::*; 2 | use std::fs; 3 | use std::path::Path; 4 | use std::process::Command; 5 | 6 | #[test] 7 | fn cargo_readme_up_to_date() { 8 | println!("Checking that `cargo readme > README.md` is up to date..."); 9 | 10 | let expected = Command::new("cargo") 11 | .arg("readme") 12 | .current_dir(env!("CARGO_MANIFEST_DIR")) 13 | .output() 14 | .expect("should run `cargo readme` OK") 15 | .stdout; 16 | let expected = String::from_utf8(expected).unwrap(); 17 | 18 | let actual = fs::read_to_string(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md")) 19 | .expect("should open README.md file"); 20 | 21 | if actual != expected { 22 | panic!("Run `cargo readme > README.md` to update README.md"); 23 | } 24 | } 25 | 26 | fn assert_snip>(cmd: &mut Command, expected_path: P) { 27 | let expected_path = Path::new(env!("CARGO_MANIFEST_DIR")) 28 | .join("tests") 29 | .join(expected_path); 30 | 31 | let actual_path = expected_path.with_extension("wasm.actual"); 32 | 33 | cmd.arg("--skip-producers-section") 34 | .arg("-o") 35 | .arg(&actual_path) 36 | .assert() 37 | .success(); 38 | 39 | let expected = fs::read(&expected_path).expect("should open expected wasm file"); 40 | let actual = fs::read(&actual_path).expect("should open snipped.wasm file"); 41 | 42 | if actual != expected { 43 | panic!( 44 | "snipping did not result in expected wasm file: {} != {}", 45 | expected_path.display(), 46 | actual_path.display(), 47 | ); 48 | } 49 | } 50 | 51 | fn wasm_snip() -> Command { 52 | let mut cmd = Command::cargo_bin("wasm-snip").unwrap(); 53 | cmd.arg( 54 | Path::new(env!("CARGO_MANIFEST_DIR")) 55 | .join("tests") 56 | .join("hello.wasm"), 57 | ); 58 | cmd 59 | } 60 | 61 | #[test] 62 | fn snip_me() { 63 | assert_snip( 64 | wasm_snip().arg("_ZN5hello7snip_me17hf15dbd799e7ad6aaE"), 65 | "snip_me.wasm", 66 | ); 67 | } 68 | 69 | #[test] 70 | fn patterns() { 71 | assert_snip(wasm_snip().arg("-p").arg(".*alloc.*"), "no_alloc.wasm"); 72 | } 73 | 74 | #[test] 75 | fn snip_rust_fmt_code() { 76 | assert_snip(wasm_snip().arg("--snip-rust-fmt-code"), "no_fmt.wasm"); 77 | } 78 | 79 | #[test] 80 | fn snip_rust_panicking_code() { 81 | assert_snip( 82 | wasm_snip().arg("--snip-rust-panicking-code"), 83 | "no_panicking.wasm", 84 | ); 85 | } 86 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |

wasm-snip

4 | 5 | wasm-snip replaces a Wasm function's body with an unreachable instruction. 6 | 7 |

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

13 | 14 |

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

21 | 22 | Built with 🦀🕸 by The Rust and WebAssembly Working Group 23 |
24 | 25 | ## About 26 | 27 | 28 | `wasm-snip` replaces a WebAssembly function's body with an `unreachable`. 29 | 30 | Maybe you know that some function will never be called at runtime, but the 31 | compiler can't prove that at compile time? Snip it! All the functions it 32 | transitively called — which weren't called by anything else and therefore 33 | could also never be called at runtime — will get removed too. 34 | 35 | Very helpful when shrinking the size of WebAssembly binaries! 36 | 37 | This functionality relies on the "name" section being present in the `.wasm` 38 | file, so build with debug symbols: 39 | 40 | ```toml 41 | [profile.release] 42 | debug = true 43 | ``` 44 | 45 | * [Executable](#executable) 46 | * [Library](#library) 47 | * [License](#license) 48 | * [Contributing](#contributing) 49 | 50 | ### Executable 51 | 52 | To install the `wasm-snip` executable, run 53 | 54 | ``` 55 | $ cargo install wasm-snip 56 | ``` 57 | 58 | You can use `wasm-snip` to remove the `annoying_space_waster` 59 | function from `input.wasm` and put the new binary in `output.wasm` like this: 60 | 61 | ``` 62 | $ wasm-snip input.wasm -o output.wasm annoying_space_waster 63 | ``` 64 | 65 | For information on using the `wasm-snip` executable, run 66 | 67 | ``` 68 | $ wasm-snip --help 69 | ``` 70 | 71 | And you'll get the most up-to-date help text, like: 72 | 73 | ``` 74 | Replace a wasm function with an `unreachable`. 75 | 76 | USAGE: 77 | wasm-snip [FLAGS] [OPTIONS] [--] [function]... 78 | 79 | FLAGS: 80 | -h, --help Prints help information 81 | --snip-rust-fmt-code Snip Rust's `std::fmt` and `core::fmt` code. 82 | --snip-rust-panicking-code Snip Rust's `std::panicking` and `core::panicking` code. 83 | -V, --version Prints version information 84 | 85 | OPTIONS: 86 | -o, --output The path to write the output wasm file to. Defaults to stdout. 87 | -p, --pattern ... Snip any function that matches the given regular expression. 88 | 89 | ARGS: 90 | The input wasm file containing the function(s) to snip. 91 | ... The specific function(s) to snip. These must match exactly. Use the -p flag for fuzzy matching. 92 | ``` 93 | 94 | ### Library 95 | 96 | To use `wasm-snip` as a library, add this to your `Cargo.toml`: 97 | 98 | ```toml 99 | [dependencies.wasm-snip] 100 | # Do not build the executable. 101 | default-features = false 102 | ``` 103 | 104 | See [docs.rs/wasm-snip][docs] for API documentation. 105 | 106 | [docs]: https://docs.rs/wasm-snip 107 | 108 | ### License 109 | 110 | Licensed under either of 111 | 112 | * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) 113 | 114 | * [MIT license](http://opensource.org/licenses/MIT) 115 | 116 | at your option. 117 | 118 | ### Contributing 119 | 120 | See 121 | [CONTRIBUTING.md](https://github.com/rustwasm/wasm-snip/blob/master/CONTRIBUTING.md) 122 | for hacking. 123 | 124 | Unless you explicitly state otherwise, any contribution intentionally submitted 125 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 126 | dual licensed as above, without any additional terms or conditions. 127 | 128 | -------------------------------------------------------------------------------- /src/bin/wasm-snip.rs: -------------------------------------------------------------------------------- 1 | use failure::ResultExt; 2 | use std::fs; 3 | use std::io::{self, Write}; 4 | use std::process; 5 | 6 | fn main() { 7 | if let Err(e) = try_main() { 8 | eprintln!("error: {}", e); 9 | for c in e.iter_chain().skip(1) { 10 | eprintln!(" caused by {}", c); 11 | } 12 | eprintln!("{}", e.backtrace()); 13 | process::exit(1) 14 | } 15 | } 16 | 17 | fn try_main() -> Result<(), failure::Error> { 18 | let matches = parse_args(); 19 | 20 | let mut opts = wasm_snip::Options::default(); 21 | 22 | opts.functions = matches 23 | .values_of("function") 24 | .map(|fs| fs.map(|f| f.to_string()).collect()) 25 | .unwrap_or(vec![]); 26 | 27 | opts.patterns = matches 28 | .values_of("pattern") 29 | .map(|ps| ps.map(|p| p.to_string()).collect()) 30 | .unwrap_or(vec![]); 31 | 32 | if matches.is_present("snip_rust_fmt_code") { 33 | opts.snip_rust_fmt_code = true; 34 | } 35 | 36 | if matches.is_present("snip_rust_panicking_code") { 37 | opts.snip_rust_panicking_code = true; 38 | } 39 | 40 | if matches.is_present("skip_producers_section") { 41 | opts.skip_producers_section = true; 42 | } 43 | 44 | let config = walrus_config_from_options(&opts); 45 | let path = matches.value_of("input").unwrap(); 46 | let buf = fs::read(&path).with_context(|_| format!("failed to read file {}", path))?; 47 | let mut module = config.parse(&buf)?; 48 | 49 | wasm_snip::snip(&mut module, opts).context("failed to snip functions from wasm module")?; 50 | 51 | if let Some(output) = matches.value_of("output") { 52 | module 53 | .emit_wasm_file(output) 54 | .with_context(|_| format!("failed to emit snipped wasm to {}", output))?; 55 | } else { 56 | let wasm = module.emit_wasm(); 57 | let stdout = io::stdout(); 58 | let mut stdout = stdout.lock(); 59 | stdout 60 | .write_all(&wasm) 61 | .context("failed to write wasm to stdout")?; 62 | } 63 | 64 | Ok(()) 65 | } 66 | 67 | fn walrus_config_from_options(options: &wasm_snip::Options) -> walrus::ModuleConfig { 68 | let mut config = walrus::ModuleConfig::new(); 69 | config.generate_producers_section(!options.skip_producers_section); 70 | config 71 | } 72 | 73 | fn parse_args() -> clap::ArgMatches<'static> { 74 | clap::App::new(env!("CARGO_PKG_NAME")) 75 | .version(env!("CARGO_PKG_VERSION")) 76 | .author(env!("CARGO_PKG_AUTHORS")) 77 | .about(env!("CARGO_PKG_DESCRIPTION")) 78 | .long_about( 79 | " 80 | `wasm-snip` replaces a WebAssembly function's body with an `unreachable`. 81 | 82 | Maybe you know that some function will never be called at runtime, but the 83 | compiler can't prove that at compile time? Snip it! Then run `wasm-gc`[0] again 84 | and all the functions it transitively called (which could also never be called 85 | at runtime) will get removed too. 86 | 87 | Very helpful when shrinking the size of WebAssembly binaries! 88 | 89 | [0]: https://github.com/alexcrichton/wasm-gc 90 | ", 91 | ) 92 | .arg( 93 | clap::Arg::with_name("output") 94 | .short("o") 95 | .long("output") 96 | .takes_value(true) 97 | .help("The path to write the output wasm file to. Defaults to stdout."), 98 | ) 99 | .arg( 100 | clap::Arg::with_name("input") 101 | .required(true) 102 | .help("The input wasm file containing the function(s) to snip."), 103 | ) 104 | .arg(clap::Arg::with_name("function").multiple(true).help( 105 | "The specific function(s) to snip. These must match \ 106 | exactly. Use the -p flag for fuzzy matching.", 107 | )) 108 | .arg( 109 | clap::Arg::with_name("pattern") 110 | .required(false) 111 | .multiple(true) 112 | .short("p") 113 | .long("pattern") 114 | .takes_value(true) 115 | .help("Snip any function that matches the given regular expression."), 116 | ) 117 | .arg( 118 | clap::Arg::with_name("snip_rust_fmt_code") 119 | .required(false) 120 | .long("snip-rust-fmt-code") 121 | .help("Snip Rust's `std::fmt` and `core::fmt` code."), 122 | ) 123 | .arg( 124 | clap::Arg::with_name("snip_rust_panicking_code") 125 | .required(false) 126 | .long("snip-rust-panicking-code") 127 | .help("Snip Rust's `std::panicking` and `core::panicking` code."), 128 | ) 129 | .arg( 130 | clap::Arg::with_name("skip_producers_section") 131 | .required(false) 132 | .long("skip-producers-section") 133 | .help("Do not emit the 'producers' custom section."), 134 | ) 135 | .get_matches() 136 | } 137 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | 3 | `wasm-snip` replaces a WebAssembly function's body with an `unreachable`. 4 | 5 | Maybe you know that some function will never be called at runtime, but the 6 | compiler can't prove that at compile time? Snip it! All the functions it 7 | transitively called — which weren't called by anything else and therefore 8 | could also never be called at runtime — will get removed too. 9 | 10 | Very helpful when shrinking the size of WebAssembly binaries! 11 | 12 | This functionality relies on the "name" section being present in the `.wasm` 13 | file, so build with debug symbols: 14 | 15 | ```toml 16 | [profile.release] 17 | debug = true 18 | ``` 19 | 20 | * [Executable](#executable) 21 | * [Library](#library) 22 | * [License](#license) 23 | * [Contributing](#contributing) 24 | 25 | ## Executable 26 | 27 | To install the `wasm-snip` executable, run 28 | 29 | ```text 30 | $ cargo install wasm-snip 31 | ``` 32 | 33 | You can use `wasm-snip` to remove the `annoying_space_waster` 34 | function from `input.wasm` and put the new binary in `output.wasm` like this: 35 | 36 | ```text 37 | $ wasm-snip input.wasm -o output.wasm annoying_space_waster 38 | ``` 39 | 40 | For information on using the `wasm-snip` executable, run 41 | 42 | ```text 43 | $ wasm-snip --help 44 | ``` 45 | 46 | And you'll get the most up-to-date help text, like: 47 | 48 | ```text 49 | Replace a wasm function with an `unreachable`. 50 | 51 | USAGE: 52 | wasm-snip [FLAGS] [OPTIONS] [--] [function]... 53 | 54 | FLAGS: 55 | -h, --help Prints help information 56 | --snip-rust-fmt-code Snip Rust's `std::fmt` and `core::fmt` code. 57 | --snip-rust-panicking-code Snip Rust's `std::panicking` and `core::panicking` code. 58 | -V, --version Prints version information 59 | 60 | OPTIONS: 61 | -o, --output The path to write the output wasm file to. Defaults to stdout. 62 | -p, --pattern ... Snip any function that matches the given regular expression. 63 | 64 | ARGS: 65 | The input wasm file containing the function(s) to snip. 66 | ... The specific function(s) to snip. These must match exactly. Use the -p flag for fuzzy matching. 67 | ``` 68 | 69 | ## Library 70 | 71 | To use `wasm-snip` as a library, add this to your `Cargo.toml`: 72 | 73 | ```toml 74 | [dependencies.wasm-snip] 75 | # Do not build the executable. 76 | default-features = false 77 | ``` 78 | 79 | See [docs.rs/wasm-snip][docs] for API documentation. 80 | 81 | [docs]: https://docs.rs/wasm-snip 82 | 83 | ## License 84 | 85 | Licensed under either of 86 | 87 | * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) 88 | 89 | * [MIT license](http://opensource.org/licenses/MIT) 90 | 91 | at your option. 92 | 93 | ## Contributing 94 | 95 | See 96 | [CONTRIBUTING.md](https://github.com/rustwasm/wasm-snip/blob/master/CONTRIBUTING.md) 97 | for hacking. 98 | 99 | Unless you explicitly state otherwise, any contribution intentionally submitted 100 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 101 | dual licensed as above, without any additional terms or conditions. 102 | 103 | */ 104 | 105 | #![deny(missing_docs)] 106 | #![deny(missing_debug_implementations)] 107 | 108 | use failure::ResultExt; 109 | use rayon::prelude::*; 110 | use std::collections::HashMap; 111 | use std::collections::HashSet; 112 | use std::path; 113 | use walrus::ir::VisitorMut; 114 | 115 | /// Input configuration. 116 | #[derive(Clone, Debug)] 117 | pub enum Input { 118 | /// The input `.wasm` file that should have its function snipped. 119 | File(path::PathBuf), 120 | /// The input WebAssembly blob that should have its function snipped. 121 | Buffer(Vec), 122 | // TODO: Support Walrus module directly. 123 | // Module(walrus::Module), 124 | } 125 | 126 | impl Default for Input { 127 | fn default() -> Self { 128 | Input::File(path::PathBuf::default()) 129 | } 130 | } 131 | 132 | /// Options for controlling which functions in what `.wasm` file should be 133 | /// snipped. 134 | #[derive(Clone, Debug, Default)] 135 | pub struct Options { 136 | /// The functions that should be snipped from the `.wasm` file. 137 | pub functions: Vec, 138 | 139 | /// The regex patterns whose matches should be snipped from the `.wasm` 140 | /// file. 141 | pub patterns: Vec, 142 | 143 | /// Should Rust `std::fmt` and `core::fmt` functions be snipped? 144 | pub snip_rust_fmt_code: bool, 145 | 146 | /// Should Rust `std::panicking` and `core::panicking` functions be snipped? 147 | pub snip_rust_panicking_code: bool, 148 | 149 | /// Should we skip generating [the "producers" custom 150 | /// section](https://github.com/WebAssembly/tool-conventions/blob/master/ProducersSection.md)? 151 | pub skip_producers_section: bool, 152 | } 153 | 154 | /// Snip the functions from the input file described by the options. 155 | pub fn snip(module: &mut walrus::Module, options: Options) -> Result<(), failure::Error> { 156 | if !options.skip_producers_section { 157 | module 158 | .producers 159 | .add_processed_by("wasm-snip", env!("CARGO_PKG_VERSION")); 160 | } 161 | 162 | let names: HashSet = options.functions.iter().cloned().collect(); 163 | let re_set = build_regex_set(options).context("failed to compile regex")?; 164 | let to_snip = find_functions_to_snip(&module, &names, &re_set); 165 | 166 | replace_calls_with_unreachable(module, &to_snip); 167 | unexport_snipped_functions(module, &to_snip); 168 | unimport_snipped_functions(module, &to_snip); 169 | snip_table_elements(module, &to_snip); 170 | delete_functions_to_snip(module, &to_snip); 171 | walrus::passes::gc::run(module); 172 | 173 | Ok(()) 174 | } 175 | 176 | fn build_regex_set(mut options: Options) -> Result { 177 | // Snip the Rust `fmt` code, if requested. 178 | if options.snip_rust_fmt_code { 179 | // Mangled symbols. 180 | options.patterns.push(".*4core3fmt.*".into()); 181 | options.patterns.push(".*3std3fmt.*".into()); 182 | 183 | // Mangled in impl. 184 | options.patterns.push(r#".*core\.\.fmt\.\..*"#.into()); 185 | options.patterns.push(r#".*std\.\.fmt\.\..*"#.into()); 186 | 187 | // Demangled symbols. 188 | options.patterns.push(".*core::fmt::.*".into()); 189 | options.patterns.push(".*std::fmt::.*".into()); 190 | } 191 | 192 | // Snip the Rust `panicking` code, if requested. 193 | if options.snip_rust_panicking_code { 194 | // Mangled symbols. 195 | options.patterns.push(".*4core9panicking.*".into()); 196 | options.patterns.push(".*3std9panicking.*".into()); 197 | 198 | // Mangled in impl. 199 | options.patterns.push(r#".*core\.\.panicking\.\..*"#.into()); 200 | options.patterns.push(r#".*std\.\.panicking\.\..*"#.into()); 201 | 202 | // Demangled symbols. 203 | options.patterns.push(".*core::panicking::.*".into()); 204 | options.patterns.push(".*std::panicking::.*".into()); 205 | } 206 | 207 | Ok(regex::RegexSet::new(options.patterns)?) 208 | } 209 | 210 | fn find_functions_to_snip( 211 | module: &walrus::Module, 212 | names: &HashSet, 213 | re_set: ®ex::RegexSet, 214 | ) -> HashSet { 215 | module 216 | .funcs 217 | .par_iter() 218 | .filter_map(|f| { 219 | f.name.as_ref().and_then(|name| { 220 | if names.contains(name) || re_set.is_match(name) { 221 | Some(f.id()) 222 | } else { 223 | None 224 | } 225 | }) 226 | }) 227 | .collect() 228 | } 229 | 230 | fn delete_functions_to_snip(module: &mut walrus::Module, to_snip: &HashSet) { 231 | for f in to_snip.iter().cloned() { 232 | module.funcs.delete(f); 233 | } 234 | } 235 | 236 | fn replace_calls_with_unreachable( 237 | module: &mut walrus::Module, 238 | to_snip: &HashSet, 239 | ) { 240 | struct Replacer<'a> { 241 | to_snip: &'a HashSet, 242 | } 243 | 244 | impl Replacer<'_> { 245 | fn should_snip_call(&self, instr: &walrus::ir::Instr) -> bool { 246 | if let walrus::ir::Instr::Call(walrus::ir::Call { func }) = instr { 247 | if self.to_snip.contains(func) { 248 | return true; 249 | } 250 | } 251 | false 252 | } 253 | } 254 | 255 | impl VisitorMut for Replacer<'_> { 256 | fn visit_instr_mut(&mut self, instr: &mut walrus::ir::Instr) { 257 | if self.should_snip_call(instr) { 258 | *instr = walrus::ir::Unreachable {}.into(); 259 | } 260 | } 261 | } 262 | 263 | module.funcs.par_iter_local_mut().for_each(|(id, func)| { 264 | // Don't bother transforming functions that we are snipping. 265 | if to_snip.contains(&id) { 266 | return; 267 | } 268 | 269 | let entry = func.entry_block(); 270 | walrus::ir::dfs_pre_order_mut(&mut Replacer { to_snip }, func, entry); 271 | }); 272 | } 273 | 274 | fn unexport_snipped_functions(module: &mut walrus::Module, to_snip: &HashSet) { 275 | let exports_to_snip: HashSet = module 276 | .exports 277 | .iter() 278 | .filter_map(|e| match e.item { 279 | walrus::ExportItem::Function(f) if to_snip.contains(&f) => Some(e.id()), 280 | _ => None, 281 | }) 282 | .collect(); 283 | 284 | for e in exports_to_snip { 285 | module.exports.delete(e); 286 | } 287 | } 288 | 289 | fn unimport_snipped_functions(module: &mut walrus::Module, to_snip: &HashSet) { 290 | let imports_to_snip: HashSet = module 291 | .imports 292 | .iter() 293 | .filter_map(|i| match i.kind { 294 | walrus::ImportKind::Function(f) if to_snip.contains(&f) => Some(i.id()), 295 | _ => None, 296 | }) 297 | .collect(); 298 | 299 | for i in imports_to_snip { 300 | module.imports.delete(i); 301 | } 302 | } 303 | 304 | fn snip_table_elements(module: &mut walrus::Module, to_snip: &HashSet) { 305 | let mut unreachable_funcs: HashMap = Default::default(); 306 | 307 | let make_unreachable_func = |ty: walrus::TypeId, 308 | types: &mut walrus::ModuleTypes, 309 | locals: &mut walrus::ModuleLocals, 310 | funcs: &mut walrus::ModuleFunctions| 311 | -> walrus::FunctionId { 312 | let ty = types.get(ty); 313 | let params = ty.params().to_vec(); 314 | let locals: Vec<_> = params.iter().map(|ty| locals.add(*ty)).collect(); 315 | let results = ty.results().to_vec(); 316 | let mut builder = walrus::FunctionBuilder::new(types, ¶ms, &results); 317 | builder.func_body().unreachable(); 318 | builder.finish(locals, funcs) 319 | }; 320 | 321 | for t in module.tables.iter_mut() { 322 | if let walrus::TableKind::Function(ref mut ft) = t.kind { 323 | let types = &mut module.types; 324 | let locals = &mut module.locals; 325 | let funcs = &mut module.funcs; 326 | 327 | ft.elements 328 | .iter_mut() 329 | .flat_map(|el| el) 330 | .filter(|f| to_snip.contains(f)) 331 | .for_each(|el| { 332 | let ty = funcs.get(*el).ty(); 333 | *el = *unreachable_funcs 334 | .entry(ty) 335 | .or_insert_with(|| make_unreachable_func(ty, types, locals, funcs)); 336 | }); 337 | 338 | ft.relative_elements 339 | .iter_mut() 340 | .flat_map(|(_, elems)| elems.iter_mut().filter(|f| to_snip.contains(f))) 341 | .for_each(|el| { 342 | let ty = funcs.get(*el).ty(); 343 | *el = *unreachable_funcs 344 | .entry(ty) 345 | .or_insert_with(|| make_unreachable_func(ty, types, locals, funcs)); 346 | }); 347 | } 348 | } 349 | } 350 | --------------------------------------------------------------------------------