Wasm-bindgen: Can the size of the generated wasm be reduced?

Created on 23 Mar 2018  路  21Comments  路  Source: rustwasm/wasm-bindgen

Currently wasm binary for even this simple function

#![feature(proc_macro)]

extern crate wasm_bindgen;

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn run(x: i32, y: i32) -> i32 {
    x + y
}

is 230kb, after applying wasm-gc.

file-size

Most helpful comment

You could reduce the file size much more. Add the following to Cargo.toml and do cargo build --release.

[profile.release]
lto = true

All 21 comments

Thanks for the report! Are you also compiling with LTO and optimizations?

Actually no, I thought --release had LTO enabled by default. And LTO brought it down to 10 kb. Should I close this issue?

Ok, great!

Note that a bunch of that 10kb comes from the static descriptors that wasm-bindgen emits. Those will eventually go away but if you run the wasm-bindgen CLI and then run wasm-opt from the binaryen toolkit over the output wasm file it should be much smaller.

Eventually though none of that should be necessary and a normal LTO build should be quite small! (after running wasm-bindgen)

@alexcrichton would you consider extending the "Basic Usage" example to run wasm-gc, wasm-opt, and/or whatever wasm-bindgen flags (LTO? --release?) are required to achieve the minimum file size?

Here are steps that worked for me:

  1. Build with --release

    cargo build --release --target wasm32-unknown-unknown
    
  2. Run wasm-bindgen

    wasm-bindgen target/wasm32-unknown-unknown/release/js_hello_world.wasm --out-dir .
    
  3. Run wasm-gc

    wasm-gc js_hello_world_bg.wasm js_hello_world_bg_gc.wasm
    
  4. Run wasm-opt

    wasm-opt js_hello_world_bg_gc.wasm -Os -o js_hello_world_bg_gc_opt.wasm
    

Then, update js_hello_world.js to import js_hello_world_bg_gc_opt.

$ wc -c <js_hello_world_bg.wasm
  237298
$ wc -c <js_hello_world_bg_gc.wasm
  227057
$ wc -c <js_hello_world_bg_gc_opt.wasm
  167750

You could reduce the file size much more. Add the following to Cargo.toml and do cargo build --release.

[profile.release]
lto = true

@markandrus thanks for the report!

This is definitely a general problem of "we need documentation about generating the smallest binaries". I've opened an issue at https://github.com/rust-lang-nursery/rust-wasm/issues/109 for documenting this in the working group's book

Thanks for the tips, y'all!

With lto = true (and the same sequence of transformations), I get

$ wc -c <js_hello_world_gc_opt.wasm
   43513

If I add opt-size = 'z', it gets even smaller:

$ wc -c <js_hello_world_gc_opt.wasm
   36128

Using wee_alloc and std,

$ wc -c <js_hello_world_gc_opt.wasm
   32294

I dont think no_std is an option鈥攁t least for the "Basic Usage" example, since it uses format鈥攕o I'm not sure it can go smaller?

@markandrus the "hello world" example uses string formatting and allocation which are two major contributors to code size. Projects focused on code size tend to avoid at least string formatting and often allocations, so the "hello world" isn't necessarily representative of what a hello-world would look like for a size-concerned application

@alexcrichton I tried #![no_std] but with no luck. It seems wasm_bindgen includes std automatically:

#![no_std] // it seems the compiler just ignores the annotation
#![feature(proc_macro, wasm_custom_section, wasm_import_module)]
#![feature(global_allocator)]
#![feature(alloc, core_intrinsics, lang_items)]

extern crate wasm_bindgen;

extern crate alloc;
extern crate wee_alloc;

use wasm_bindgen::prelude::*;

#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;

/* I have to comment the following lines out to compile the code */
// #[lang = "panic_fmt"]
// extern "C" fn panic_fmt(_args: ::core::fmt::Arguments, _file: &'static str, _line: u32) -> ! {
//     use core::intrinsics;
//     unsafe {
//         intrinsics::abort();
//     }
// }

#[wasm_bindgen]
extern "C" {
    fn alert(s: &str);
}

#[wasm_bindgen]
pub fn greet(name: &str) {
    alert("hello");
}

@michael8090 I think you're likely running up against "string allocation and panicking is large in rust right now", which while hopefully reduced on the next successful nightly isn't necessarily directly related to wasm-bindgen.

I'm trying to compile wasm-bindgen with no_std too, and I got this error:

error[E0433]: failed to resolve. Did you mean `nom::lib::std`?
 --> src/wasm.rs:6:1
  |
6 | #[wasm_bindgen(module = "./parser_definitions")]
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Did you mean `nom::lib::std`?

rustc tries to find another libstd somewhere in another crate (here, nom).

Is it possible to compile wasm-bindgen with no_std?

Ah currently the macro can only be used in crates that have std at the root, but I'd love to fix that! Nothing inherently requires std here (but passing things like String does). @Hywan want to open a dedicated issue for that?

Sure, done :-).

I'm having trouble getting a minimal-size setup. My intent was to try to make a small demo module that adds two numbers and build up from there but it's not easy for me to understand the combination of incantations for lto, opt-size, wasm-opt, no_std (I think), no allocator, no formatting (wasm-snip?) that I need. Would it be possible to get an example added to the repo showing the smallest known possible wasm-bindgen output that can be used as a starting point for size-conscious libraries?

@sophiebits sure yeah! I just added a new example which you can also play around with online.

The key things for "simple code" (aka adding numbers) is to enable LTO and to compile with optimizations. That's set by wasm-bindgen's workspace currently.

For a bit larger of an example I've seen the dom example is also quite small in the compile wasm size, which you can also explore online as well.

If you're wondering about a particular piece of code though and how to get it smaller just let me know! I'd love to help out and see what we can improve in the libraries/tooling along the way.

Just tried and the add example works great. When building dom with --release, I get 234,042 bytes and after wasm-opt -Os 170,992 bytes. Just wanted to check if that's the size you'd expect?

Hm odd! When I compile the dom example with --release I get a 1.5k wasm file coming out of wasm-bindgen and 632 bytes after wasm-opt.

@sophiebits are you sure you're compiling with LTO though? When I turn off LTO and opt-level = 's' the output of wasm-bindgen jumps to 217k and the wasm-opt optimized size is 161k. If not though, have some code I could poke around and try to reproduce with?

Ahh. I was testing in the repo directly (having edited your build.sh) but I neglected to update "debug" to "release" in the path ../../target/wasm32-unknown-unknown/release/dom.wasm. I get what you get now!

Heh I've done that more than once before as well :)

Great to see the dramatic reduction of the add example to 600 bytes! What is still in the module? In comparison, IIRC the add example in AssemblyScript is only 80 bytes, so maybe there is still room for improvement?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

fitzgen picture fitzgen  路  3Comments

MarcAntoine-Arnaud picture MarcAntoine-Arnaud  路  3Comments

kyren picture kyren  路  3Comments

fitzgen picture fitzgen  路  4Comments

NateLing picture NateLing  路  3Comments