Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
58c6636
initial setup
kentosugama Mar 10, 2023
551bc8c
add wasm-opt crate
kentosugama Apr 6, 2023
9e77b4b
wasm-opt scaffolding
kentosugama Apr 6, 2023
cbc47eb
remove boiler
kentosugama Apr 6, 2023
959a69d
scaffold
kentosugama Apr 6, 2023
cf8e985
extract the custom sectionsg
kentosugama Apr 6, 2023
645fc35
reinsert data sections
kentosugama Apr 6, 2023
6c41502
let user specify optimization level
kentosugama Apr 6, 2023
077559f
use proper temp file
kentosugama Apr 7, 2023
a7af326
recurse for actor classes
kentosugama Apr 7, 2023
c59205e
nicer error messages
kentosugama Apr 7, 2023
ccd1622
add some file tests
kentosugama Apr 7, 2023
65f18ca
add semantic tests
kentosugama Apr 7, 2023
2fae562
test different optimization levels
kentosugama Apr 10, 2023
a8e62ac
make test cases more complex
kentosugama Apr 10, 2023
a07a95a
test preservation of metadata sections
kentosugama Apr 10, 2023
9df6cbc
Add documentation in readme
kentosugama Apr 10, 2023
21dced7
Update README.md
kentosugama Apr 10, 2023
09d0424
move into shrink and remove test cases
kentosugama Apr 10, 2023
ec44977
merge conflict
kentosugama Apr 10, 2023
b40649c
move tests into shrink test
kentosugama Apr 10, 2023
d9229cd
change error handling to not exit
kentosugama Apr 10, 2023
deec640
remove a reparsing of module
kentosugama Apr 11, 2023
82fcb3d
update tests
kentosugama Apr 11, 2023
da85724
Bump version
kentosugama Apr 11, 2023
c163dcf
inline call to list_metadata
kentosugama Apr 11, 2023
1427f59
remove keep_name_section and rename function
kentosugama Apr 11, 2023
dd0b87c
remove redundant get
kentosugama Apr 11, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
637 changes: 479 additions & 158 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "ic-wasm"
version = "0.3.5"
version = "0.3.6"
authors = ["DFINITY Stiftung"]
edition = "2021"
description = "A library for performing Wasm transformations specific to canisters running on the Internet Computer"
Expand All @@ -22,6 +22,8 @@ walrus = "0.19.0"
candid = "0.9.0-beta.2"
rustc-demangle = "0.1"
thiserror = "1.0.35"
wasm-opt = "0.112.0"
tempfile = "3.5.0"

anyhow = { version = "1.0.34", optional = true }
clap = { version = "4.1", features = ["derive", "cargo"], optional = true }
Expand Down
29 changes: 25 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Manage metadata in the Wasm module.
Usage: `ic-wasm <input.wasm> [-o <output.wasm>] metadata [name] [-d <text content> | -f <file content>] [-v <public|private>]`

* List current metadata sections
```
```
$ ic-wasm input.wasm metadata
```

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

### Shrink

Remove unused functions and debug info
Remove unused functions and debug info.

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

Optionally invoke wasm optimizations from [`wasm-opt`](https://github.com/WebAssembly/binaryen).

The optimizer exposes different optimization levels to choose from.

Performance levels (optimizes for runtime):
- O4
- O3 (default setting: best for minimizing cycle usage)
- O2
- O1
- O0 (no optimizations)

Code size levels (optimizes for binary size):
- Oz (best for minimizing code size)
- Os

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%.

Note: The `icp` metadata sections are preserved through the optimizations.


Usage: `ic-wasm <input.wasm> -o <output.wasm> shrink --optimize <level>`

### Resource

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

### Instrument (experimental)

Instrument canister method to emit execution trace to stable memory.
Instrument canister method to emit execution trace to stable memory.

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

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


## Library

To use `ic-wasm` as a library, add this to your `Cargo.toml`:
Expand Down
17 changes: 13 additions & 4 deletions src/bin/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,11 @@ enum SubCommand {
},
/// List information about the Wasm canister
Info,
/// Remove unused functions and debug info
Shrink,
/// Remove unused functions and debug info. Optionally, optimize the Wasm code
Shrink {
#[clap(short, long, value_parser = ["O0", "O1", "O2", "O3", "O4", "Os", "Oz"])]
optimize: Option<String>,
},
/// Instrument canister method to emit execution trace to stable memory (experimental)
Instrument {
#[clap(short, long)]
Expand All @@ -59,14 +62,20 @@ enum SubCommand {

fn main() -> anyhow::Result<()> {
let opts: Opts = Opts::parse();
let keep_name_section = matches!(opts.subcommand, SubCommand::Shrink);
let keep_name_section = matches!(opts.subcommand, SubCommand::Shrink { .. });
let mut m = ic_wasm::utils::parse_wasm_file(opts.input, keep_name_section)?;
match &opts.subcommand {
SubCommand::Info => {
let mut stdout = std::io::stdout();
ic_wasm::info::info(&m, &mut stdout)?;
}
SubCommand::Shrink => ic_wasm::shrink::shrink(&mut m),
SubCommand::Shrink { optimize } => {
use ic_wasm::shrink;
match optimize {
Some(level) => shrink::shrink_with_wasm_opt(&mut m, level)?,
None => shrink::shrink(&mut m),
}
}
SubCommand::Instrument { trace_only } => match trace_only {
None => ic_wasm::instrumentation::instrument(&mut m, &[]),
Some(vec) => ic_wasm::instrumentation::instrument(&mut m, vec),
Expand Down
1 change: 1 addition & 0 deletions src/metadata.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use walrus::{IdsToIndices, Module, RawCustomSection};

#[derive(Clone, Copy)]
pub enum Kind {
Public,
Private,
Expand Down
66 changes: 64 additions & 2 deletions src/shrink.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use walrus::*;

use crate::metadata::*;
use crate::utils::*;
use tempfile::NamedTempFile;
use walrus::*;
use wasm_opt::OptimizationOptions;

pub fn shrink(m: &mut Module) {
if is_motoko_canister(m) {
Expand All @@ -25,3 +27,63 @@ pub fn shrink(m: &mut Module) {
}
passes::gc::run(m);
}

pub fn shrink_with_wasm_opt(m: &mut Module, level: &str) -> anyhow::Result<()> {
// recursively optimize embedded modules in Motoko actor classes
if is_motoko_canister(m) {
let data = get_motoko_wasm_data_sections(m);
for (id, mut module) in data.into_iter() {
shrink_with_wasm_opt(&mut module, level)?;
let blob = encode_module_as_data_section(module);
m.data.get_mut(id).value = blob;
}
}

// write module to temp file
let temp_file = NamedTempFile::new()?;
m.emit_wasm_file(temp_file.path())?;

// pull out a copy of the custom sections to preserve
let metadata_sections: Vec<(Kind, &str, Vec<u8>)> = m
.customs
.iter()
.filter(|(_, section)| section.name().starts_with("icp:"))
.map(|(_, section)| {
let data = section.data(&IdsToIndices::default()).to_vec();
let full_name = section.name();
match full_name.strip_prefix("public ") {
Some(name) => (Kind::Public, name, data),
None => match full_name.strip_prefix("private ") {
Some(name) => (Kind::Private, name, data),
None => unreachable!(),
},
}
})
.collect();

// read in from temp file and optimize
match level {
"O0" => OptimizationOptions::new_opt_level_0(),
"O1" => OptimizationOptions::new_opt_level_1(),
"O2" => OptimizationOptions::new_opt_level_2(),
"O3" => OptimizationOptions::new_opt_level_3(),
"O4" => OptimizationOptions::new_opt_level_4(),
"Os" => OptimizationOptions::new_optimize_for_size(),
"Oz" => OptimizationOptions::new_optimize_for_size_aggressively(),
_ => unreachable!(),
}
.run(temp_file.path(), temp_file.path())?;

// read optimized wasm back in from temp file
let mut m_opt = parse_wasm_file(temp_file.path().to_path_buf(), false)?;

// re-insert the custom sections
metadata_sections
.into_iter()
.for_each(|(visibility, name, data)| {
add_metadata(&mut m_opt, visibility, name, data);
});

*m = m_opt;
Ok(())
}
34 changes: 30 additions & 4 deletions tests/deployable.ic-repl.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ function motoko(wasm) {
call S.inc();
call S.get();
assert _ == (43 : nat);

call S.inc();
call S.inc();
call S.get();
assert _ == (45 : nat);
S
};
function rust(wasm) {
Expand All @@ -29,6 +34,11 @@ function rust(wasm) {
call S.inc();
call S.read();
assert _ == (43 : nat);

call S.inc();
call S.inc();
call S.read();
assert _ == (45 : nat);
S
};
function wat(wasm) {
Expand All @@ -37,6 +47,11 @@ function wat(wasm) {
call S.inc();
call S.get();
assert _ == (43 : int64);

call S.inc();
call S.inc();
call S.get();
assert _ == (45 : int64);
S
};
function classes(wasm) {
Expand All @@ -46,6 +61,12 @@ function classes(wasm) {
call S.put(42, "text");
call S.get(42);
assert _ == opt "text";

call S.put(40, "text0");
call S.put(41, "text1");
call S.put(42, "text2");
call S.get(42);
assert _ == opt "text2";
S
};
function classes_limit(wasm) {
Expand All @@ -67,26 +88,31 @@ function classes_redirect(wasm) {

let S = motoko(file("ok/motoko-instrument.wasm"));
call S.__get_cycles();
assert _ == (7199 : int64);
assert _ == (9003 : int64);
let S = motoko(file("ok/motoko-gc-instrument.wasm"));
call S.__get_cycles();
assert _ == (177 : int64);
assert _ == (295 : int64);
motoko(file("ok/motoko-shrink.wasm"));
motoko(file("ok/motoko-limit.wasm"));

let S = rust(file("ok/rust-instrument.wasm"));
call S.__get_cycles();
assert _ == (66016 : int64);
assert _ == (136378 : int64);
rust(file("ok/rust-shrink.wasm"));
rust(file("ok/rust-limit.wasm"));

let S = wat(file("ok/wat-instrument.wasm"));
call S.__get_cycles();
assert _ == (121 : int64);
assert _ == (189 : int64);
wat(file("ok/wat-shrink.wasm"));
wat(file("ok/wat-limit.wasm"));

classes(file("ok/classes-shrink.wasm"));
classes_limit(file("ok/classes-limit.wasm"));
classes_redirect(file("ok/classes-redirect.wasm"));
classes(file("ok/classes-nop-redirect.wasm"));

motoko(file("ok/motoko-optimize.wasm"));
rust(file("ok/rust-optimize.wasm"));
wat(file("ok/wat-optimize.wasm"));
classes(file("ok/classes-optimize.wasm"));
Binary file added tests/ok/classes-optimize.wasm
Binary file not shown.
Binary file added tests/ok/motoko-optimize.wasm
Binary file not shown.
Binary file added tests/ok/rust-optimize.wasm
Binary file not shown.
Binary file added tests/ok/wat-optimize.wasm
Binary file not shown.
74 changes: 73 additions & 1 deletion tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ fn instrumentation() {

#[test]
fn shrink() {
let expected_metadata = r#"icp:public candid:service
icp:private candid:args
icp:private motoko:stable-types
icp:private motoko:compiler
"#;

wasm_input("motoko.wasm", true)
.arg("shrink")
.assert()
Expand All @@ -74,7 +80,73 @@ fn shrink() {
.arg("shrink")
.assert()
.success();
assert_wasm("classes-shrink.wasm")
assert_wasm("classes-shrink.wasm");

wasm_input("motoko.wasm", true)
.arg("shrink")
.arg("--optimize")
.arg("O3")
.assert()
.success();
assert_wasm("motoko-optimize.wasm");
wasm_input("ok/motoko-optimize.wasm", false)
.arg("metadata")
.assert()
.stdout(expected_metadata)
.success();
wasm_input("ok/motoko-optimize.wasm", false)
.arg("metadata")
.arg("motoko:compiler")
.assert()
.stdout("0.6.25\n")
.success();
wasm_input("ok/motoko-optimize.wasm", false)
.arg("metadata")
.arg("candid:args")
.assert()
.stdout("()\n")
.success();

wasm_input("rust.wasm", true)
.arg("shrink")
.arg("--optimize")
.arg("O3")
.assert()
.success();
assert_wasm("rust-optimize.wasm");

wasm_input("classes.wasm", true)
.arg("shrink")
.arg("--optimize")
.arg("O3")
.assert()
.success();
assert_wasm("classes-optimize.wasm");
wasm_input("ok/classes-optimize.wasm", false)
.arg("metadata")
.assert()
.stdout(expected_metadata)
.success();
wasm_input("ok/classes-optimize.wasm", false)
.arg("metadata")
.arg("motoko:compiler")
.assert()
.stdout("0.6.26\n")
.success();
wasm_input("ok/classes-optimize.wasm", false)
.arg("metadata")
.arg("candid:args")
.assert()
.stdout("()\n")
.success();

wasm_input("wat.wasm", true)
.arg("shrink")
.arg("--optimize")
.arg("O3")
.assert()
.success();
assert_wasm("wat-optimize.wasm");
}

#[test]
Expand Down