Skip to content

Commit 3c5acec

Browse files
authored
Wasm-opt integration (#28)
Add option under `shrink` to optimize using `wasm-opt`
1 parent 67b9915 commit 3c5acec

File tree

12 files changed

+688
-174
lines changed

12 files changed

+688
-174
lines changed

Cargo.lock

Lines changed: 479 additions & 158 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "ic-wasm"
3-
version = "0.3.5"
3+
version = "0.3.6"
44
authors = ["DFINITY Stiftung"]
55
edition = "2021"
66
description = "A library for performing Wasm transformations specific to canisters running on the Internet Computer"
@@ -22,6 +22,8 @@ walrus = "0.19.0"
2222
candid = "0.9.0-beta.2"
2323
rustc-demangle = "0.1"
2424
thiserror = "1.0.35"
25+
wasm-opt = "0.112.0"
26+
tempfile = "3.5.0"
2527

2628
anyhow = { version = "1.0.34", optional = true }
2729
clap = { version = "4.1", features = ["derive", "cargo"], optional = true }

README.md

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ Manage metadata in the Wasm module.
1717
Usage: `ic-wasm <input.wasm> [-o <output.wasm>] metadata [name] [-d <text content> | -f <file content>] [-v <public|private>]`
1818

1919
* List current metadata sections
20-
```
20+
```
2121
$ ic-wasm input.wasm metadata
2222
```
2323

@@ -44,10 +44,32 @@ Usage: `ic-wasm <input.wasm> info`
4444

4545
### Shrink
4646

47-
Remove unused functions and debug info
47+
Remove unused functions and debug info.
4848

4949
Usage: `ic-wasm <input.wasm> -o <output.wasm> shrink`
5050

51+
Optionally invoke wasm optimizations from [`wasm-opt`](https://github.com/WebAssembly/binaryen).
52+
53+
The optimizer exposes different optimization levels to choose from.
54+
55+
Performance levels (optimizes for runtime):
56+
- O4
57+
- O3 (default setting: best for minimizing cycle usage)
58+
- O2
59+
- O1
60+
- O0 (no optimizations)
61+
62+
Code size levels (optimizes for binary size):
63+
- Oz (best for minimizing code size)
64+
- Os
65+
66+
The recommended setting (O3) reduces cycle usage for Motoko programs by ~10% and Rust programs by ~4%. The code size for both languages is reduced by ~16%.
67+
68+
Note: The `icp` metadata sections are preserved through the optimizations.
69+
70+
71+
Usage: `ic-wasm <input.wasm> -o <output.wasm> shrink --optimize <level>`
72+
5173
### Resource
5274

5375
Limit resource usage, mainly used by Motoko Playground
@@ -56,7 +78,7 @@ Usage: `ic-wasm <input.wasm> -o <output.wasm> resource --remove_cycles_transfer
5678

5779
### Instrument (experimental)
5880

59-
Instrument canister method to emit execution trace to stable memory.
81+
Instrument canister method to emit execution trace to stable memory.
6082

6183
Usage: `ic-wasm <input.wasm> -o <output.wasm> instrument --trace-only func1 --trace-only func2`
6284

@@ -81,7 +103,6 @@ Current limitations:
81103
* We cannot measure query calls.
82104
* No concurrent calls
83105

84-
85106
## Library
86107

87108
To use `ic-wasm` as a library, add this to your `Cargo.toml`:

src/bin/main.rs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,11 @@ enum SubCommand {
4848
},
4949
/// List information about the Wasm canister
5050
Info,
51-
/// Remove unused functions and debug info
52-
Shrink,
51+
/// Remove unused functions and debug info. Optionally, optimize the Wasm code
52+
Shrink {
53+
#[clap(short, long, value_parser = ["O0", "O1", "O2", "O3", "O4", "Os", "Oz"])]
54+
optimize: Option<String>,
55+
},
5356
/// Instrument canister method to emit execution trace to stable memory (experimental)
5457
Instrument {
5558
#[clap(short, long)]
@@ -59,14 +62,20 @@ enum SubCommand {
5962

6063
fn main() -> anyhow::Result<()> {
6164
let opts: Opts = Opts::parse();
62-
let keep_name_section = matches!(opts.subcommand, SubCommand::Shrink);
65+
let keep_name_section = matches!(opts.subcommand, SubCommand::Shrink { .. });
6366
let mut m = ic_wasm::utils::parse_wasm_file(opts.input, keep_name_section)?;
6467
match &opts.subcommand {
6568
SubCommand::Info => {
6669
let mut stdout = std::io::stdout();
6770
ic_wasm::info::info(&m, &mut stdout)?;
6871
}
69-
SubCommand::Shrink => ic_wasm::shrink::shrink(&mut m),
72+
SubCommand::Shrink { optimize } => {
73+
use ic_wasm::shrink;
74+
match optimize {
75+
Some(level) => shrink::shrink_with_wasm_opt(&mut m, level)?,
76+
None => shrink::shrink(&mut m),
77+
}
78+
}
7079
SubCommand::Instrument { trace_only } => match trace_only {
7180
None => ic_wasm::instrumentation::instrument(&mut m, &[]),
7281
Some(vec) => ic_wasm::instrumentation::instrument(&mut m, vec),

src/metadata.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use walrus::{IdsToIndices, Module, RawCustomSection};
22

3+
#[derive(Clone, Copy)]
34
pub enum Kind {
45
Public,
56
Private,

src/shrink.rs

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
use walrus::*;
2-
1+
use crate::metadata::*;
32
use crate::utils::*;
3+
use tempfile::NamedTempFile;
4+
use walrus::*;
5+
use wasm_opt::OptimizationOptions;
46

57
pub fn shrink(m: &mut Module) {
68
if is_motoko_canister(m) {
@@ -25,3 +27,63 @@ pub fn shrink(m: &mut Module) {
2527
}
2628
passes::gc::run(m);
2729
}
30+
31+
pub fn shrink_with_wasm_opt(m: &mut Module, level: &str) -> anyhow::Result<()> {
32+
// recursively optimize embedded modules in Motoko actor classes
33+
if is_motoko_canister(m) {
34+
let data = get_motoko_wasm_data_sections(m);
35+
for (id, mut module) in data.into_iter() {
36+
shrink_with_wasm_opt(&mut module, level)?;
37+
let blob = encode_module_as_data_section(module);
38+
m.data.get_mut(id).value = blob;
39+
}
40+
}
41+
42+
// write module to temp file
43+
let temp_file = NamedTempFile::new()?;
44+
m.emit_wasm_file(temp_file.path())?;
45+
46+
// pull out a copy of the custom sections to preserve
47+
let metadata_sections: Vec<(Kind, &str, Vec<u8>)> = m
48+
.customs
49+
.iter()
50+
.filter(|(_, section)| section.name().starts_with("icp:"))
51+
.map(|(_, section)| {
52+
let data = section.data(&IdsToIndices::default()).to_vec();
53+
let full_name = section.name();
54+
match full_name.strip_prefix("public ") {
55+
Some(name) => (Kind::Public, name, data),
56+
None => match full_name.strip_prefix("private ") {
57+
Some(name) => (Kind::Private, name, data),
58+
None => unreachable!(),
59+
},
60+
}
61+
})
62+
.collect();
63+
64+
// read in from temp file and optimize
65+
match level {
66+
"O0" => OptimizationOptions::new_opt_level_0(),
67+
"O1" => OptimizationOptions::new_opt_level_1(),
68+
"O2" => OptimizationOptions::new_opt_level_2(),
69+
"O3" => OptimizationOptions::new_opt_level_3(),
70+
"O4" => OptimizationOptions::new_opt_level_4(),
71+
"Os" => OptimizationOptions::new_optimize_for_size(),
72+
"Oz" => OptimizationOptions::new_optimize_for_size_aggressively(),
73+
_ => unreachable!(),
74+
}
75+
.run(temp_file.path(), temp_file.path())?;
76+
77+
// read optimized wasm back in from temp file
78+
let mut m_opt = parse_wasm_file(temp_file.path().to_path_buf(), false)?;
79+
80+
// re-insert the custom sections
81+
metadata_sections
82+
.into_iter()
83+
.for_each(|(visibility, name, data)| {
84+
add_metadata(&mut m_opt, visibility, name, data);
85+
});
86+
87+
*m = m_opt;
88+
Ok(())
89+
}

tests/deployable.ic-repl.sh

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ function motoko(wasm) {
2121
call S.inc();
2222
call S.get();
2323
assert _ == (43 : nat);
24+
25+
call S.inc();
26+
call S.inc();
27+
call S.get();
28+
assert _ == (45 : nat);
2429
S
2530
};
2631
function rust(wasm) {
@@ -29,6 +34,11 @@ function rust(wasm) {
2934
call S.inc();
3035
call S.read();
3136
assert _ == (43 : nat);
37+
38+
call S.inc();
39+
call S.inc();
40+
call S.read();
41+
assert _ == (45 : nat);
3242
S
3343
};
3444
function wat(wasm) {
@@ -37,6 +47,11 @@ function wat(wasm) {
3747
call S.inc();
3848
call S.get();
3949
assert _ == (43 : int64);
50+
51+
call S.inc();
52+
call S.inc();
53+
call S.get();
54+
assert _ == (45 : int64);
4055
S
4156
};
4257
function classes(wasm) {
@@ -46,6 +61,12 @@ function classes(wasm) {
4661
call S.put(42, "text");
4762
call S.get(42);
4863
assert _ == opt "text";
64+
65+
call S.put(40, "text0");
66+
call S.put(41, "text1");
67+
call S.put(42, "text2");
68+
call S.get(42);
69+
assert _ == opt "text2";
4970
S
5071
};
5172
function classes_limit(wasm) {
@@ -67,26 +88,31 @@ function classes_redirect(wasm) {
6788

6889
let S = motoko(file("ok/motoko-instrument.wasm"));
6990
call S.__get_cycles();
70-
assert _ == (7199 : int64);
91+
assert _ == (9003 : int64);
7192
let S = motoko(file("ok/motoko-gc-instrument.wasm"));
7293
call S.__get_cycles();
73-
assert _ == (177 : int64);
94+
assert _ == (295 : int64);
7495
motoko(file("ok/motoko-shrink.wasm"));
7596
motoko(file("ok/motoko-limit.wasm"));
7697

7798
let S = rust(file("ok/rust-instrument.wasm"));
7899
call S.__get_cycles();
79-
assert _ == (66016 : int64);
100+
assert _ == (136378 : int64);
80101
rust(file("ok/rust-shrink.wasm"));
81102
rust(file("ok/rust-limit.wasm"));
82103

83104
let S = wat(file("ok/wat-instrument.wasm"));
84105
call S.__get_cycles();
85-
assert _ == (121 : int64);
106+
assert _ == (189 : int64);
86107
wat(file("ok/wat-shrink.wasm"));
87108
wat(file("ok/wat-limit.wasm"));
88109

89110
classes(file("ok/classes-shrink.wasm"));
90111
classes_limit(file("ok/classes-limit.wasm"));
91112
classes_redirect(file("ok/classes-redirect.wasm"));
92113
classes(file("ok/classes-nop-redirect.wasm"));
114+
115+
motoko(file("ok/motoko-optimize.wasm"));
116+
rust(file("ok/rust-optimize.wasm"));
117+
wat(file("ok/wat-optimize.wasm"));
118+
classes(file("ok/classes-optimize.wasm"));

tests/ok/classes-optimize.wasm

224 KB
Binary file not shown.

tests/ok/motoko-optimize.wasm

95.5 KB
Binary file not shown.

tests/ok/rust-optimize.wasm

465 KB
Binary file not shown.

0 commit comments

Comments
 (0)