diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index b0f028106a..165d8d4f62 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -81,7 +81,7 @@ jobs: - name: Check dynamically-linked libraries (macos) run: | ACTUAL="$(otool -L ${{ matrix.binary_path }}/dfx | awk 'NR > 1{ print $1 }' | grep -v /System/Library/Frameworks | sort | awk -v d=" " '{s=(NR==1?s:s d)$0}END{printf "%s",s}')" - EXPECTED="/usr/lib/libSystem.B.dylib /usr/lib/libiconv.2.dylib /usr/lib/libresolv.9.dylib" + EXPECTED="/usr/lib/libSystem.B.dylib /usr/lib/libc++.1.dylib /usr/lib/libiconv.2.dylib /usr/lib/libresolv.9.dylib" echo "Dynamically-linked libraries:" echo " Actual: $ACTUAL" echo " Expected: $EXPECTED" @@ -93,7 +93,7 @@ jobs: - name: Check dynamically-linked libraries (ubuntu) run: | ACTUAL="$(ldd ${{ matrix.binary_path }}/dfx | awk '{ print $1 }' | sort | awk -v d=" " '{s=(NR==1?s:s d)$0}END{printf "%s",s}')" - EXPECTED="/lib64/ld-linux-x86-64.so.2 libc.so.6 libdl.so.2 libgcc_s.so.1 libm.so.6 libpthread.so.0 linux-vdso.so.1" + EXPECTED="/lib64/ld-linux-x86-64.so.2 libc.so.6 libdl.so.2 libgcc_s.so.1 libm.so.6 libpthread.so.0 libstdc++.so.6 linux-vdso.so.1" echo "Dynamically-linked libraries:" echo " Actual: $ACTUAL" echo " Expected: $EXPECTED" diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c34b97a76..dc62d8ae2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,22 @@ ## DFX +### feat: expose `wasm-opt` optimizer in `ic-wasm` to users + +Add option to specify an "optimize" field for canisters to invoke the `wasm-opt` optimizer through `ic-wasm`. + +This behavior is disabled by default. + +If you want to enable this behavior, you can do so in dfx.json: + + "canisters" : { + "app" : { + "optimize" : "cycles" + } + } + +The options are "cycles", "size", "O4", "O3", "O2", "O1", "O0", "Oz", and "Os". The options starting with "O" are the optimization levels that `wasm-opt` provides. The "cycles" and "size" options are recommended defaults for optimizing for cycle usage and binary size respectively. + ### feat: updates the dfx new starter project for env vars - Updates the starter project for env vars to use the new `dfx build` & `dfx deploy` environment variables diff --git a/Cargo.lock b/Cargo.lock index 03ad4f7751..04fd0f94d3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -154,6 +154,46 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "anstream" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "342258dd14006105c2b75ab1bd7543a03bdf0cfc94383303ac212a04939dff6f" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-wincon", + "concolor-override", + "concolor-query", + "is-terminal", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23ea9e81bd02e310c216d080f6223c179012256e5151c41db88d12c88a1684d2" + +[[package]] +name = "anstyle-parse" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7d1bb534e9efed14f3e5f44e7dd1a4f709384023a4165199a4241e18dff0116" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-wincon" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3127af6145b149f3287bb9a0d10ad9c5692dba8c53ad48285e5bec4063834fa" +dependencies = [ + "anstyle", + "windows-sys 0.45.0", +] + [[package]] name = "anyhow" version = "1.0.70" @@ -552,7 +592,7 @@ dependencies = [ "arbitrary", "binread", "byteorder", - "candid_derive", + "candid_derive 0.5.0", "codespan-reporting", "crc32fast", "data-encoding", @@ -575,6 +615,32 @@ dependencies = [ "thiserror", ] +[[package]] +name = "candid" +version = "0.9.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4163412115fdb2d41d418b2746405c0fa5716be8064d507ec8bacf88027141e6" +dependencies = [ + "anyhow", + "binread", + "byteorder", + "candid_derive 0.6.0", + "codespan-reporting", + "crc32fast", + "data-encoding", + "hex", + "leb128", + "num-bigint 0.4.3", + "num-traits", + "num_enum", + "paste", + "pretty 0.10.0", + "serde", + "serde_bytes", + "sha2 0.10.6", + "thiserror", +] + [[package]] name = "candid_derive" version = "0.5.0" @@ -587,11 +653,26 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "candid_derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34df46ac8b0a44de9b243d9dd21e608838183be20de408c934993ed6a4a8e4c1" +dependencies = [ + "lazy_static", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "cc" version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +dependencies = [ + "jobserver", +] [[package]] name = "cfg-if" @@ -622,8 +703,8 @@ checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" dependencies = [ "atty", "bitflags", - "clap_derive", - "clap_lex", + "clap_derive 3.2.18", + "clap_lex 0.2.4", "indexmap", "once_cell", "strsim", @@ -631,6 +712,31 @@ dependencies = [ "textwrap", ] +[[package]] +name = "clap" +version = "4.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046ae530c528f252094e4a77886ee1374437744b2bff1497aa898bbddbbb29b3" +dependencies = [ + "clap_builder", + "clap_derive 4.2.0", + "once_cell", +] + +[[package]] +name = "clap_builder" +version = "4.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "223163f58c9a40c3b0a43e1c4b50a9ce09f007ea2cb1ec258a687945b4b7929f" +dependencies = [ + "anstream", + "anstyle", + "bitflags", + "clap_lex 0.4.1", + "once_cell", + "strsim", +] + [[package]] name = "clap_derive" version = "3.2.18" @@ -644,6 +750,18 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "clap_derive" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9644cd56d6b87dbe899ef8b053e331c0637664e9e21a33dfcdc36093f5c5c4" +dependencies = [ + "heck 0.4.0", + "proc-macro2", + "quote", + "syn 2.0.11", +] + [[package]] name = "clap_lex" version = "0.2.4" @@ -653,6 +771,12 @@ dependencies = [ "os_str_bytes", ] +[[package]] +name = "clap_lex" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1" + [[package]] name = "cmake" version = "0.1.49" @@ -683,6 +807,21 @@ dependencies = [ "winapi", ] +[[package]] +name = "concolor-override" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a855d4a1978dc52fb0536a04d384c2c0c1aa273597f08b77c8c4d3b2eec6037f" + +[[package]] +name = "concolor-query" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d11d52c3d7ca2e6d0040212be9e4dbbcd78b6447f535b6b561f449427944cf" +dependencies = [ + "windows-sys 0.45.0", +] + [[package]] name = "concurrent-queue" version = "1.2.4" @@ -869,6 +1008,50 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "cxx" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f61f1b6389c3fe1c316bf8a4dccc90a38208354b330925bce1f74a6c4756eb93" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12cee708e8962df2aeb38f594aae5d827c022b6460ac71a7a3e2c3c2aae5a07b" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn 2.0.11", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7944172ae7e4068c533afbb984114a56c46e9ccddda550499caa222902c7f7bb" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.11", +] + [[package]] name = "data-encoding" version = "2.3.2" @@ -916,8 +1099,8 @@ dependencies = [ "base64 0.13.1", "byte-unit", "bytes", - "candid", - "clap", + "candid 0.8.4", + "clap 3.2.23", "console", "crc32fast", "crossbeam", @@ -994,7 +1177,7 @@ dependencies = [ "argon2", "bip32", "byte-unit", - "candid", + "candid 0.8.4", "dialoguer", "directories-next", "flate2", @@ -1780,7 +1963,7 @@ dependencies = [ "base32", "base64 0.13.1", "byteorder", - "candid", + "candid 0.8.4", "futures-util", "hex", "http", @@ -1814,7 +1997,7 @@ version = "0.20.0" dependencies = [ "anyhow", "backoff", - "candid", + "candid 0.8.4", "derivative", "flate2", "futures", @@ -1844,7 +2027,7 @@ version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c98b304a2657bad15bcb547625a018e13cf596676d834cfd93023395a6e2e03a" dependencies = [ - "candid", + "candid 0.8.4", "cfg-if 1.0.0", "ic-cdk-macros", "ic0", @@ -1858,7 +2041,7 @@ version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebf50458685a0fc6b0e414cdba487610aeb199ac94db52d9fd76270565debee7" dependencies = [ - "candid", + "candid 0.8.4", "proc-macro2", "quote", "serde", @@ -1892,7 +2075,7 @@ name = "ic-certified-assets" version = "0.2.5" dependencies = [ "base64 0.13.1", - "candid", + "candid 0.8.4", "hex", "ic-cdk", "ic-cdk-macros", @@ -1947,7 +2130,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f4e57f65dc2b5567f11e0508b3648518097fe9fcd443389eb0057b91a3c5acd" dependencies = [ "base64 0.13.1", - "candid", + "candid 0.8.4", "flate2", "http", "ic-certification 0.23.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1965,7 +2148,7 @@ version = "0.23.2" source = "git+https://github.com/dfinity/agent-rs.git?rev=862ddb2871db426d0e42e816a2f792d224d7bdf5#862ddb2871db426d0e42e816a2f792d224d7bdf5" dependencies = [ "async-trait", - "candid", + "candid 0.8.4", "ic-agent", "leb128", "num-bigint 0.4.3", @@ -1993,15 +2176,18 @@ dependencies = [ [[package]] name = "ic-wasm" -version = "0.2.1" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29aa8bc21f2af958b5d96ee49eaf5c432636724556e1fdf103e01917f04b667f" +checksum = "c2ae0572556cad5b2f7239d6d89aba67f445c48f69610d9f540351cb8929c604" dependencies = [ "anyhow", - "candid", - "clap", + "candid 0.9.0-beta.2", + "clap 4.2.1", + "rustc-demangle", + "tempfile", "thiserror", "walrus", + "wasm-opt", ] [[package]] @@ -2015,8 +2201,8 @@ name = "icx-asset" version = "0.20.0" dependencies = [ "anyhow", - "candid", - "clap", + "candid 0.8.4", + "clap 3.2.23", "delay", "humantime", "ic-agent", @@ -2143,6 +2329,15 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" +[[package]] +name = "jobserver" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" +dependencies = [ + "libc", +] + [[package]] name = "js-sys" version = "0.3.60" @@ -2303,6 +2498,15 @@ dependencies = [ "libc", ] +[[package]] +name = "link-cplusplus" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" +dependencies = [ + "cc", +] + [[package]] name = "linux-raw-sys" version = "0.3.0" @@ -3403,6 +3607,12 @@ dependencies = [ "serde_json", ] +[[package]] +name = "rustc-demangle" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4a36c42d1873f9a77c53bde094f9664d9891bc604a45b4798fd2c389ed12e5b" + [[package]] name = "rustc-hash" version = "1.1.0" @@ -3535,6 +3745,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "scratch" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" + [[package]] name = "sct" version = "0.7.0" @@ -4423,6 +4639,12 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5190c9442dcdaf0ddd50f37420417d219ae5261bbf5db120d0f9bab996c9cba1" +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + [[package]] name = "vcpkg" version = "0.2.15" @@ -4568,6 +4790,46 @@ version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" +[[package]] +name = "wasm-opt" +version = "0.112.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87fef6d0d508f08334e0ab0e6877feb4c0ecb3956bcf2cb950699b22fedf3e9c" +dependencies = [ + "anyhow", + "libc", + "strum", + "strum_macros", + "tempfile", + "thiserror", + "wasm-opt-cxx-sys", + "wasm-opt-sys", +] + +[[package]] +name = "wasm-opt-cxx-sys" +version = "0.112.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc816bbc1596c8f2e8127e137a760c798023ef3d378f2ae51f0f1840e2dfa445" +dependencies = [ + "anyhow", + "cxx", + "cxx-build", + "wasm-opt-sys", +] + +[[package]] +name = "wasm-opt-sys" +version = "0.112.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40199e4f68ef1071b3c6d0bd8026a12b481865d4b9e49c156932ea9a6234dd14" +dependencies = [ + "anyhow", + "cc", + "cxx", + "cxx-build", +] + [[package]] name = "wasm-streams" version = "0.2.3" diff --git a/docs/dfx-json-schema.json b/docs/dfx-json-schema.json index a5a6247925..dc423fc5d9 100644 --- a/docs/dfx-json-schema.json +++ b/docs/dfx-json-schema.json @@ -379,6 +379,19 @@ "$ref": "#/definitions/CanisterMetadataSection" } }, + "optimize": { + "title": "Optimize Canister WASM", + "description": "Invoke wasm level optimizations after building the canister. Optimization level can be set to \"cycles\" to optimize for cycle usage, \"size\" to optimize for binary size, or any of \"O4, O3, O2, O1, O0, Oz, Os\". Disabled by default.", + "default": null, + "anyOf": [ + { + "$ref": "#/definitions/WasmOptLevel" + }, + { + "type": "null" + } + ] + }, "post_install": { "title": "Post-Install Commands", "description": "One or more commands to run post canister installation.", @@ -856,6 +869,22 @@ } } ] + }, + "WasmOptLevel": { + "title": "Wasm Optimization Levels", + "description": "Wasm optimization levels that are passed to `wasm-opt`. \"cycles\" defaults to O3, \"size\" defaults to Oz. O4 through O0 focus on performance (with O0 performing no optimizations), and Oz and Os focus on reducing binary size, where Oz is more aggressive than Os. O3 and Oz empirically give best cycle savings and code size savings respectively.", + "type": "string", + "enum": [ + "cycles", + "size", + "O4", + "O3", + "O2", + "O1", + "O0", + "Oz", + "Os" + ] } } -} +} \ No newline at end of file diff --git a/e2e/assets/error_context/dfx.json b/e2e/assets/error_context/dfx.json index 0c944153f7..2e80192da8 100644 --- a/e2e/assets/error_context/dfx.json +++ b/e2e/assets/error_context/dfx.json @@ -23,6 +23,10 @@ "wasm": "not used", "candid": "not used", "build": "not-the-name-of-an-executable-that-exists oh no certainly not" + }, + "bad_optimization_level": { + "type": "motoko", + "main": "main.mo" } } } \ No newline at end of file diff --git a/e2e/tests-dfx/build.bash b/e2e/tests-dfx/build.bash index 79bef4de4f..c6d02d985c 100644 --- a/e2e/tests-dfx/build.bash +++ b/e2e/tests-dfx/build.bash @@ -119,6 +119,13 @@ teardown() { assert_command dfx build } +@test "build succeeds if enable optimize" { + jq '.canisters.e2e_project_backend.optimize="cycles"' dfx.json | sponge dfx.json + dfx_start + dfx canister create --all + assert_command dfx build +} + @test "build custom canister default no shrink" { install_asset custom_canister install_asset wasm/identity @@ -133,6 +140,20 @@ teardown() { assert_match "Shrink" } +@test "build custom canister default no optimize" { + install_asset custom_canister + install_asset wasm/identity + + dfx_start + dfx canister create --all + assert_command dfx build custom + assert_not_match "Optimize" + + jq '.canisters.custom.optimize="size"' dfx.json | sponge dfx.json + assert_command dfx build custom + assert_match "Optimize" +} + # TODO: Before Tungsten, we need to update this test for code with inter-canister calls. # Currently due to new canister ids, the wasm binary will be different for inter-canister calls. @test "build twice produces the same wasm binary" { diff --git a/e2e/tests-dfx/error_context.bash b/e2e/tests-dfx/error_context.bash index ab64126176..ecf097664e 100644 --- a/e2e/tests-dfx/error_context.bash +++ b/e2e/tests-dfx/error_context.bash @@ -174,3 +174,9 @@ teardown() { # expect to see the underlying cause assert_match "Cannot find command or file" } + +@test "invalid optimization level" { + jq '.canisters.bad_optimization_level.optimize="bad_level"' dfx.json | sponge dfx.json + assert_command_fail dfx_start + assert_match "expected one of " +} \ No newline at end of file diff --git a/src/dfx-core/src/config/model/dfinity.rs b/src/dfx-core/src/config/model/dfinity.rs index fc0b974a4f..16f2a5c885 100644 --- a/src/dfx-core/src/config/model/dfinity.rs +++ b/src/dfx-core/src/config/model/dfinity.rs @@ -75,6 +75,31 @@ pub struct ConfigCanistersCanisterRemote { pub id: BTreeMap, } +/// # Wasm Optimization Levels +/// Wasm optimization levels that are passed to `wasm-opt`. "cycles" defaults to O3, "size" defaults to Oz. +/// O4 through O0 focus on performance (with O0 performing no optimizations), and Oz and Os focus on reducing binary size, where Oz is more aggressive than Os. +/// O3 and Oz empirically give best cycle savings and code size savings respectively. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +pub enum WasmOptLevel { + #[serde(rename = "cycles")] + Cycles, + #[serde(rename = "size")] + Size, + O4, + O3, + O2, + O1, + O0, + Oz, + Os, +} + +impl std::fmt::Display for WasmOptLevel { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + std::fmt::Debug::fmt(self, f) + } +} + #[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "lowercase")] pub enum MetadataVisibility { @@ -195,6 +220,12 @@ pub struct ConfigCanistersCanister { /// Disabled by default for custom canisters. pub shrink: Option, + /// # Optimize Canister WASM + /// Invoke wasm level optimizations after building the canister. Optimization level can be set to "cycles" to optimize for cycle usage, "size" to optimize for binary size, or any of "O4, O3, O2, O1, O0, Oz, Os". + /// Disabled by default. + #[serde(default)] + pub optimize: Option, + /// # Metadata /// Defines metadata sections to set in the canister .wasm #[serde(default)] diff --git a/src/dfx/Cargo.toml b/src/dfx/Cargo.toml index a5d42b7953..a59bed31d8 100644 --- a/src/dfx/Cargo.toml +++ b/src/dfx/Cargo.toml @@ -57,7 +57,7 @@ ic-agent = { workspace = true, features = ["reqwest"] } ic-asset.workspace = true ic-identity-hsm = { workspace = true } ic-utils = { workspace = true } -ic-wasm = "0.2.1" +ic-wasm = "0.3.6" indicatif = "0.16.0" itertools.workspace = true keyring.workspace = true diff --git a/src/dfx/src/lib/builders/custom.rs b/src/dfx/src/lib/builders/custom.rs index f5b581d744..30cc20539a 100644 --- a/src/dfx/src/lib/builders/custom.rs +++ b/src/dfx/src/lib/builders/custom.rs @@ -146,11 +146,20 @@ impl CanisterBuilder for CustomBuilder { } } + let optimize = info.get_optimize(); let shrink = info.get_shrink().unwrap_or(false); // Custom canister may have WASM gzipped - if shrink && is_wasm_format(&wasm)? { - info!(self.logger, "Shrink WASM module size."); - super::shrink_wasm(&wasm)?; + if is_wasm_format(&wasm)? { + if let Some(level) = optimize { + info!( + self.logger, + "Optimize and shrink WASM module at level {}", level + ); + super::optimize_wasm(&wasm, level)?; + } else if shrink { + info!(self.logger, "Shrink WASM module size."); + super::shrink_wasm(&wasm)?; + } } Ok(BuildOutput { diff --git a/src/dfx/src/lib/builders/mod.rs b/src/dfx/src/lib/builders/mod.rs index a2db5f0a3b..ec22fe0318 100644 --- a/src/dfx/src/lib/builders/mod.rs +++ b/src/dfx/src/lib/builders/mod.rs @@ -4,7 +4,7 @@ use crate::lib::environment::Environment; use crate::lib::error::DfxResult; use crate::lib::models::canister::CanisterPool; use crate::util::check_candid_file; -use dfx_core::config::model::dfinity::{Config, Profile}; +use dfx_core::config::model::dfinity::{Config, Profile, WasmOptLevel}; use dfx_core::network::provider::get_network_context; use dfx_core::util; @@ -512,10 +512,27 @@ impl BuildConfig { fn shrink_wasm(wasm_path: impl AsRef) -> DfxResult { let wasm_path = wasm_path.as_ref(); let wasm = std::fs::read(wasm_path).context("Could not read the WASM module.")?; - let shrinked_wasm = - ic_wasm::shrink::shrink(&wasm).context("Could not shrink the WASM module.")?; - std::fs::write(wasm_path, &shrinked_wasm) - .with_context(|| format!("Could not write shrinked WASM to {:?}", wasm_path))?; + let mut module = + ic_wasm::utils::parse_wasm(&wasm, true).context("Could not parse the WASM module.")?; + ic_wasm::shrink::shrink(&mut module); + module + .emit_wasm_file(wasm_path) + .with_context(|| format!("Could not write shrunk WASM to {:?}.", wasm_path))?; + Ok(()) +} + +#[context("Failed to optimize wasm at {}.", &wasm_path.as_ref().display())] +fn optimize_wasm(wasm_path: impl AsRef, level: WasmOptLevel) -> DfxResult { + let wasm_path = wasm_path.as_ref(); + let wasm = std::fs::read(wasm_path).context("Could not read the WASM module.")?; + let mut module = + ic_wasm::utils::parse_wasm(&wasm, true).context("Could not parse the WASM module.")?; + ic_wasm::shrink::shrink_with_wasm_opt(&mut module, &level.to_string()) + .context("Could not optimize the WASM module.")?; + + module + .emit_wasm_file(wasm_path) + .with_context(|| format!("Could not write optimized WASM to {:?}.", wasm_path))?; Ok(()) } diff --git a/src/dfx/src/lib/builders/motoko.rs b/src/dfx/src/lib/builders/motoko.rs index 3e12d51424..a8b411e938 100644 --- a/src/dfx/src/lib/builders/motoko.rs +++ b/src/dfx/src/lib/builders/motoko.rs @@ -166,8 +166,15 @@ impl CanisterBuilder for MotokoBuilder { }; motoko_compile(&self.logger, cache.as_ref(), ¶ms)?; + let optimize = canister_info.get_optimize(); let shrink = canister_info.get_shrink().unwrap_or(true); - if shrink { + if let Some(level) = optimize { + info!( + self.logger, + "Optimize and shrink WASM module at level {}", level + ); + super::optimize_wasm(motoko_info.get_output_wasm_path(), level)?; + } else if shrink { info!(self.logger, "Shrink WASM module size."); super::shrink_wasm(motoko_info.get_output_wasm_path())?; } diff --git a/src/dfx/src/lib/builders/rust.rs b/src/dfx/src/lib/builders/rust.rs index b594662809..64c7a19b02 100644 --- a/src/dfx/src/lib/builders/rust.rs +++ b/src/dfx/src/lib/builders/rust.rs @@ -97,8 +97,15 @@ impl CanisterBuilder for RustBuilder { ); let output = cargo.output().context("Failed to run 'cargo build'. You might need to run `cargo update` (or a similar command like `cargo vendor`) if you have updated `Cargo.toml`, because `dfx build` uses the --locked flag with Cargo.")?; + let optimize = canister_info.get_optimize(); let shrink = canister_info.get_shrink().unwrap_or(true); - if shrink { + if let Some(level) = optimize { + info!( + self.logger, + "Optimize and shrink WASM module at level {}", level + ); + super::optimize_wasm(rust_info.get_output_wasm_path(), level)?; + } else if shrink { info!(self.logger, "Shrink WASM module size."); super::shrink_wasm(rust_info.get_output_wasm_path())?; } diff --git a/src/dfx/src/lib/canister_info.rs b/src/dfx/src/lib/canister_info.rs index fd63f47334..798a6d56c0 100644 --- a/src/dfx/src/lib/canister_info.rs +++ b/src/dfx/src/lib/canister_info.rs @@ -5,6 +5,7 @@ use crate::lib::canister_info::motoko::MotokoCanisterInfo; use crate::lib::error::DfxResult; use dfx_core::config::model::dfinity::{ CanisterDeclarationsConfig, CanisterMetadataSection, CanisterTypeProperties, Config, + WasmOptLevel, }; use dfx_core::network::provider::get_network_context; use dfx_core::util; @@ -52,6 +53,7 @@ pub struct CanisterInfo { post_install: Vec, main: Option, shrink: Option, + optimize: Option, metadata: CanisterMetadataConfig, pull_ready: bool, pull_dependencies: Vec<(String, CanisterId)>, @@ -154,6 +156,7 @@ impl CanisterInfo { post_install, main: canister_config.main.clone(), shrink: canister_config.shrink, + optimize: canister_config.optimize, metadata, pull_ready: canister_config.pull_ready, pull_dependencies, @@ -229,6 +232,15 @@ impl CanisterInfo { self.shrink } + pub fn get_optimize(&self) -> Option { + // Cycles defaults to O3, Size defaults to Oz + self.optimize.map(|level| match level { + WasmOptLevel::Cycles => WasmOptLevel::O3, + WasmOptLevel::Size => WasmOptLevel::Oz, + other => other, + }) + } + pub fn get_build_wasm_path(&self) -> PathBuf { self.output_root.join(&self.name).with_extension("wasm") } diff --git a/src/dfx/src/lib/models/canister.rs b/src/dfx/src/lib/models/canister.rs index ecd6c58817..39bdeddf91 100644 --- a/src/dfx/src/lib/models/canister.rs +++ b/src/dfx/src/lib/models/canister.rs @@ -192,8 +192,10 @@ impl Canister { return Ok(()); } - let mut m = std::fs::read(&wasm_path) + let wasm = std::fs::read(&wasm_path) .with_context(|| format!("Failed to read wasm at {}", wasm_path.display()))?; + let mut m = ic_wasm::utils::parse_wasm(&wasm, true) + .with_context(|| format!("Failed to parse wasm at {}", wasm_path.display()))?; for (name, section) in &metadata_sections { if section.name == CANDID_SERVICE && self.info.is_motoko() { @@ -232,12 +234,12 @@ impl Canister { // if the metadata already exists in the wasm with a different visibility, // then we have to remove it - m = remove_metadata(&m, name)?; + remove_metadata(&mut m, name); - m = add_metadata(&m, visibility, name, data)?; + add_metadata(&mut m, visibility, name, data); } - std::fs::write(&wasm_path, &m) + m.emit_wasm_file(&wasm_path) .with_context(|| format!("Could not write WASM to {:?}", wasm_path)) } }