├── .github ├── CODEOWNERS ├── CONTRIBUTING.md └── workflows │ ├── release.yml │ ├── rust.yml │ └── wasm.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── NOTICE ├── README.md ├── rust-toolchain.toml ├── src ├── bin │ └── main.rs ├── info.rs ├── instrumentation.rs ├── lib.rs ├── limit_resource.rs ├── metadata.rs ├── optimize.rs ├── shrink.rs └── utils.rs └── tests ├── README.md ├── classes.wasm ├── deployable.ic-repl.sh ├── evm.mo ├── evm.wasm ├── motoko-region.wasm ├── motoko.wasm ├── ok ├── classes-limit.wasm ├── classes-nop-redirect.wasm ├── classes-optimize-names.wasm ├── classes-optimize.wasm ├── classes-redirect.wasm ├── classes-shrink.wasm ├── evm-redirect.wasm ├── motoko-gc-instrument.wasm ├── motoko-instrument.wasm ├── motoko-limit.wasm ├── motoko-region-instrument.wasm ├── motoko-shrink.wasm ├── rust-instrument.wasm ├── rust-limit.wasm ├── rust-region-instrument.wasm ├── rust-shrink.wasm ├── wat-instrument.wasm ├── wat-limit.wasm └── wat-shrink.wasm ├── rust-region.wasm ├── rust.wasm ├── tests.rs ├── wat.wasm └── wat.wasm.gz /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @dfinity/dx 2 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thank you for your interest in contributing to the Rust crates for the Internet Computer. 4 | By participating in this project, you agree to abide by our [Code of Conduct](./CODE_OF_CONDUCT.md). 5 | 6 | As a member of the community, you are invited and encouraged to contribute by submitting issues, offering suggestions for improvements, adding review comments to existing pull requests, or creating new pull requests to fix issues. 7 | 8 | All contributions to DFINITY documentation and the developer community are respected and appreciated. 9 | Your participation is an important factor in the success of the Internet Computer. 10 | 11 | ## Contents of this repository 12 | 13 | This repository contains source code for the canister interface description language—often referred to as Candid or IDL. Candid provides a common language for specifying the signature of a canister service and interacting with canisters running on the 14 | Internet Computer. 15 | 16 | ## Before you contribute 17 | 18 | Before contributing, please take a few minutes to review these contributor guidelines. 19 | The contributor guidelines are intended to make the contribution process easy and effective for everyone involved in addressing your issue, assessing changes, and finalizing your pull requests. 20 | 21 | Before contributing, consider the following: 22 | 23 | - If you want to report an issue, click **Issues**. 24 | 25 | - If you have more general questions related to Candid and its use, post a message to the [community forum](https://forum.dfinity.org/) or submit a [support request](mailto://support@dfinity.org). 26 | 27 | - If you are reporting a bug, provide as much information about the problem as possible. 28 | 29 | - If you want to contribute directly to this repository, typical fixes might include any of the following: 30 | 31 | - Fixes to resolve bugs or documentation errors 32 | - Code improvements 33 | - Feature requests 34 | 35 | Note that any contribution to this repository must be submitted in the form of a **pull request**. 36 | 37 | - If you are creating a pull request, be sure that the pull request only implements one fix or suggestion. 38 | 39 | If you are new to working with GitHub repositories and creating pull requests, consider exploring [First Contributions](https://github.com/firstcontributions/first-contributions) or [How to Contribute to an Open Source Project on GitHub](https://egghead.io/courses/how-to-contribute-to-an-open-source-project-on-github). 40 | 41 | # How to make a contribution 42 | 43 | Depending on the type of contribution you want to make, you might follow different workflows. 44 | 45 | This section describes the most common workflow scenarios: 46 | 47 | - Reporting an issue 48 | - Submitting a pull request 49 | 50 | ### Reporting an issue 51 | 52 | To open a new issue: 53 | 54 | 1. Click **Issues**. 55 | 56 | 1. Click **New Issue**. 57 | 58 | 1. Click **Open a blank issue**. 59 | 60 | 1. Type a title and description, then click **Submit new issue**. 61 | 62 | Be as clear and descriptive as possible. 63 | 64 | For any problem, describe it in detail, including details about the crate, the version of the code you are using, the results you expected, and how the actual results differed from your expectations. 65 | 66 | ### Submitting a pull request 67 | 68 | If you want to submit a pull request to fix an issue or add a feature, here's a summary of what you need to do: 69 | 70 | 1. Make sure you have a GitHub account, an internet connection, and access to a terminal shell or GitHub Desktop application for running commands. 71 | 72 | 1. Navigate to the DFINITY public repository in a web browser. 73 | 74 | 1. Click **Fork** to create a copy the repository associated with the issue you want to address under your GitHub account or organization name. 75 | 76 | 1. Clone the repository to your local machine. 77 | 78 | 1. Create a new branch for your fix by running a command similar to the following: 79 | 80 | ```bash 81 | git checkout -b my-branch-name-here 82 | ``` 83 | 84 | 1. Open the file you want to fix in a text editor and make the appropriate changes for the issue you are trying to address. 85 | 86 | 1. Add the file contents of the changed files to the index `git` uses to manage the state of the project by running a command similar to the following: 87 | 88 | ```bash 89 | git add path-to-changed-file 90 | ``` 91 | 1. Commit your changes to store the contents you added to the index along with a descriptive message by running a command similar to the following: 92 | 93 | ```bash 94 | git commit -m "Description of the fix being committed." 95 | ``` 96 | 97 | 1. Push the changes to the remote repository by running a command similar to the following: 98 | 99 | ```bash 100 | git push origin my-branch-name-here 101 | ``` 102 | 103 | 1. Create a new pull request for the branch you pushed to the upstream GitHub repository. 104 | 105 | Provide a title that includes a short description of the changes made. 106 | 107 | 1. Wait for the pull request to be reviewed. 108 | 109 | 1. Make changes to the pull request, if requested. 110 | 111 | 1. Celebrate your success after your pull request is merged! 112 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | tags: 5 | - '*' 6 | jobs: 7 | build: 8 | name: Build for ${{ matrix.name }} 9 | runs-on: ${{ matrix.os }} 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | include: 14 | - os: ubuntu-20.04 15 | name: linux64 16 | artifact_name: target/release/ic-wasm 17 | asset_name: ic-wasm-linux64 18 | - os: ubuntu-20.04 19 | name: linux-musl 20 | artifact_name: target/release/ic-wasm 21 | asset_name: ic-wasm-linux-musl 22 | - os: macos-13 23 | name: macos 24 | artifact_name: target/release/ic-wasm 25 | asset_name: ic-wasm-macos 26 | steps: 27 | - uses: actions/checkout@v4 28 | 29 | # Regular build for non-MUSL targets 30 | - name: Build (non-MUSL) 31 | if: matrix.name != 'linux-musl' 32 | run: cargo build --release --locked 33 | 34 | # Native MUSL build using Alpine Docker 35 | - name: Build (MUSL) 36 | if: matrix.name == 'linux-musl' 37 | run: | 38 | docker run --rm -v $(pwd):/src -w /src rust:alpine sh -c ' 39 | # Install build dependencies 40 | apk add --no-cache musl-dev binutils g++ 41 | 42 | # Build the project 43 | cargo build --release --locked 44 | ' 45 | 46 | - name: 'Upload assets' 47 | uses: actions/upload-artifact@v4 48 | with: 49 | name: ${{ matrix.asset_name }} 50 | path: ${{ matrix.artifact_name }} 51 | retention-days: 3 52 | 53 | test: 54 | needs: build 55 | name: Test for ${{ matrix.os }} 56 | runs-on: ${{ matrix.os }} 57 | strategy: 58 | fail-fast: false 59 | matrix: 60 | include: 61 | - os: ubuntu-22.04 62 | asset_name: ic-wasm-linux64 63 | - os: ubuntu-20.04 64 | asset_name: ic-wasm-linux64 65 | - os: ubuntu-22.04 66 | asset_name: ic-wasm-linux-musl 67 | - os: ubuntu-20.04 68 | asset_name: ic-wasm-linux-musl 69 | - os: macos-13 70 | asset_name: ic-wasm-macos 71 | - os: macos-14 72 | asset_name: ic-wasm-macos 73 | steps: 74 | - name: Get executable 75 | id: download 76 | uses: actions/download-artifact@v4 77 | with: 78 | name: ${{ matrix.asset_name }} 79 | 80 | - name: Test MUSL binary in Alpine 81 | if: matrix.asset_name == 'ic-wasm-linux-musl' 82 | run: | 83 | docker run --rm -v $(pwd):/test alpine:latest sh -c ' 84 | chmod +x /test/ic-wasm 85 | /test/ic-wasm --version 86 | ' 87 | 88 | - name: Test non-MUSL binary 89 | if: matrix.asset_name != 'ic-wasm-linux-musl' 90 | run: | 91 | chmod +x ic-wasm 92 | ./ic-wasm --version 93 | 94 | publish: 95 | needs: test 96 | name: Publish ${{ matrix.asset_name }} 97 | strategy: 98 | fail-fast: false 99 | matrix: 100 | include: 101 | - asset_name: ic-wasm-linux64 102 | binstall_name: ic-wasm-x86_64-unknown-linux-gnu.tar.gz 103 | - asset_name: ic-wasm-linux-musl 104 | binstall_name: ic-wasm-x86_64-unknown-linux-musl.tar.gz 105 | - asset_name: ic-wasm-macos 106 | binstall_name: ic-wasm-x86_64-apple-darwin.tar.gz 107 | runs-on: ubuntu-latest 108 | steps: 109 | - name: Get executable 110 | uses: actions/download-artifact@v4 111 | with: 112 | name: ${{ matrix.asset_name }} 113 | 114 | - name: Upload binaries to release 115 | uses: svenstaro/upload-release-action@v2 116 | with: 117 | repo_token: ${{ secrets.GITHUB_TOKEN }} 118 | file: ic-wasm 119 | asset_name: ${{ matrix.asset_name }} 120 | tag: ${{ github.ref }} 121 | 122 | - name: Bundle for binstall 123 | run: | 124 | chmod +x ic-wasm 125 | tar -cvzf ${{ matrix.binstall_name }} ic-wasm 126 | 127 | - name: Upload binstall binaries to release 128 | uses: svenstaro/upload-release-action@v2 129 | with: 130 | repo_token: ${{ secrets.GITHUB_TOKEN }} 131 | file: ${{ matrix.binstall_name }} 132 | asset_name: ${{ matrix.binstall_name }} 133 | tag: ${{ github.ref }} -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | 8 | jobs: 9 | rust: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - name: Cache cargo build 14 | uses: actions/cache@v4 15 | with: 16 | path: | 17 | ~/.cargo/registry 18 | ~/.cargo/git 19 | target 20 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 21 | - name: Build 22 | run: cargo build 23 | - name: Run tests 24 | run: cargo test -- --test-threads=1 25 | - name: fmt 26 | run: cargo fmt -v -- --check 27 | - name: lint 28 | run: cargo clippy --tests -- -D clippy::all 29 | - name: doc 30 | run: cargo doc 31 | -------------------------------------------------------------------------------- /.github/workflows/wasm.yml: -------------------------------------------------------------------------------- 1 | name: Wasm 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | env: 12 | DFX_VERSION: 0.23.0 13 | IC_REPL_VERSION: 0.7.5 14 | steps: 15 | - uses: actions/checkout@v4 16 | - name: Install dfx 17 | uses: dfinity/setup-dfx@main 18 | with: 19 | dfx-version: "${{ env.DFX_VERSION }}" 20 | - name: Install dependencies 21 | run: | 22 | wget https://github.com/chenyan2002/ic-repl/releases/download/$IC_REPL_VERSION/ic-repl-linux64 23 | cp ./ic-repl-linux64 /usr/local/bin/ic-repl 24 | chmod a+x /usr/local/bin/ic-repl 25 | rustup target add wasm32-unknown-unknown 26 | - name: Check compilation without default features for wasm32-unknown-unknown target (needed for motoko-playground) 27 | run: | 28 | cargo build --no-default-features --target wasm32-unknown-unknown 29 | - name: Start replica 30 | run: | 31 | echo "{}" > dfx.json 32 | dfx cache install 33 | dfx start --background 34 | - name: Test 35 | run: | 36 | ic-repl tests/deployable.ic-repl.sh -v 37 | - name: stop dfx 38 | run: | 39 | echo "dfx stop" 40 | dfx stop 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | /.vscode/ 4 | /.idea/ 5 | 6 | # will have compiled files and executables 7 | /target/ 8 | 9 | /tests/out.wasm 10 | 11 | # These are backup files generated by rustfmt 12 | **/*.rs.bk 13 | 14 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [unreleased] 8 | 9 | ## [0.9.5] - 2025-01-28 10 | 11 | * Fix compilation without default features. 12 | 13 | ## [0.9.4] - 2025-01-27 14 | 15 | * Allow `sign_with_schnorr` in `limit_resource`. 16 | 17 | ## [0.9.3] - 2025-01-10 18 | 19 | * Validate the manipulated module before emitting it and give a warning if that fails. 20 | 21 | ## [0.9.2] - 2025-01-09 22 | 23 | * Fix: limit_resource works with wasm64. 24 | 25 | ## [0.9.1] - 2024-11-18 26 | 27 | * Add redirect for evm canister. 28 | 29 | ## [0.9.0] - 2024-10-01 30 | 31 | * (breaking) Use 64bit API for stable memory in profiling and bump walrus 32 | 33 | ## [0.8.6] - 2024-09-24 34 | 35 | * Add data section check when limiting Wasm heap memory. 36 | 37 | ## [0.8.5] - 2024-09-05 38 | 39 | * Fix http_request redirect. 40 | 41 | ## [0.8.4] - 2024-09-05 42 | 43 | * Add `keep_name_section` option to the `metadata` subcommand. 44 | 45 | ## [0.8.3] - 2024-08-27 46 | 47 | * Fix memory id in limit_resource. 48 | 49 | ## [0.8.2] - 2024-08-27 50 | 51 | * Add support for limiting Wasm heap memory. 52 | 53 | ## [0.8.1] - 2024-08-20 54 | 55 | * Redirect canister snapshot calls in `limit_resource` module. 56 | 57 | ## [0.8.0] - 2024-07-09 58 | 59 | * Upgrade dependency walrus. 60 | * This enables ic-wasm to process memory64 Wasm modules. 61 | 62 | ## [0.7.3] - 2024-06-27 63 | 64 | * Enable WebAssembly SIMD in `optimize` subcommand. 65 | 66 | ## [0.7.2] - 2024-04-06 67 | 68 | * Bump dependency for libflate 69 | 70 | ## [0.7.1] - 2024-03-20 71 | 72 | * `utils::parse_wasm` and `utils::parse_wasm_file` can take both gzipped and original Wasm inputs. 73 | 74 | ## [0.3.0 -- 0.7.0] 75 | 76 | - Profiling 77 | + Support profiling stable memory 78 | + `__get_profiling` supports streaming data download 79 | + Trace only a subset of functions 80 | + Add `__toggle_entry` function 81 | + Use the new cost model for metering 82 | - Add optimize command to use wasm-opt 83 | - Added support for JSON output to `ic-wasm info`. 84 | 85 | ## [0.2.0] - 2022-09-21 86 | 87 | ### Changed 88 | - Decoupled library API with walrus (#19) 89 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "adler32" 7 | version = "1.2.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" 10 | 11 | [[package]] 12 | name = "ahash" 13 | version = "0.8.11" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" 16 | dependencies = [ 17 | "cfg-if", 18 | "once_cell", 19 | "version_check", 20 | "zerocopy", 21 | ] 22 | 23 | [[package]] 24 | name = "allocator-api2" 25 | version = "0.2.20" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9" 28 | 29 | [[package]] 30 | name = "anstream" 31 | version = "0.6.18" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" 34 | dependencies = [ 35 | "anstyle", 36 | "anstyle-parse", 37 | "anstyle-query", 38 | "anstyle-wincon", 39 | "colorchoice", 40 | "is_terminal_polyfill", 41 | "utf8parse", 42 | ] 43 | 44 | [[package]] 45 | name = "anstyle" 46 | version = "1.0.10" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 49 | 50 | [[package]] 51 | name = "anstyle-parse" 52 | version = "0.2.6" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 55 | dependencies = [ 56 | "utf8parse", 57 | ] 58 | 59 | [[package]] 60 | name = "anstyle-query" 61 | version = "1.1.2" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 64 | dependencies = [ 65 | "windows-sys 0.59.0", 66 | ] 67 | 68 | [[package]] 69 | name = "anstyle-wincon" 70 | version = "3.0.6" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" 73 | dependencies = [ 74 | "anstyle", 75 | "windows-sys 0.59.0", 76 | ] 77 | 78 | [[package]] 79 | name = "anyhow" 80 | version = "1.0.93" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" 83 | 84 | [[package]] 85 | name = "arrayvec" 86 | version = "0.5.2" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" 89 | 90 | [[package]] 91 | name = "assert_cmd" 92 | version = "2.0.16" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "dc1835b7f27878de8525dc71410b5a31cdcc5f230aed5ba5df968e09c201b23d" 95 | dependencies = [ 96 | "anstyle", 97 | "bstr", 98 | "doc-comment", 99 | "libc", 100 | "predicates", 101 | "predicates-core", 102 | "predicates-tree", 103 | "wait-timeout", 104 | ] 105 | 106 | [[package]] 107 | name = "autocfg" 108 | version = "1.4.0" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 111 | 112 | [[package]] 113 | name = "binread" 114 | version = "2.2.0" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "16598dfc8e6578e9b597d9910ba2e73618385dc9f4b1d43dd92c349d6be6418f" 117 | dependencies = [ 118 | "binread_derive", 119 | "lazy_static", 120 | "rustversion", 121 | ] 122 | 123 | [[package]] 124 | name = "binread_derive" 125 | version = "2.1.0" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | checksum = "1d9672209df1714ee804b1f4d4f68c8eb2a90b1f7a07acf472f88ce198ef1fed" 128 | dependencies = [ 129 | "either", 130 | "proc-macro2", 131 | "quote", 132 | "syn 1.0.109", 133 | ] 134 | 135 | [[package]] 136 | name = "bitflags" 137 | version = "2.6.0" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 140 | 141 | [[package]] 142 | name = "block-buffer" 143 | version = "0.10.4" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 146 | dependencies = [ 147 | "generic-array", 148 | ] 149 | 150 | [[package]] 151 | name = "bstr" 152 | version = "1.11.0" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "1a68f1f47cdf0ec8ee4b941b2eee2a80cb796db73118c0dd09ac63fbe405be22" 155 | dependencies = [ 156 | "memchr", 157 | "regex-automata", 158 | "serde", 159 | ] 160 | 161 | [[package]] 162 | name = "byteorder" 163 | version = "1.5.0" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 166 | 167 | [[package]] 168 | name = "candid" 169 | version = "0.10.10" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "6c30ee7f886f296b6422c0ff017e89dd4f831521dfdcc76f3f71aae1ce817222" 172 | dependencies = [ 173 | "anyhow", 174 | "binread", 175 | "byteorder", 176 | "candid_derive", 177 | "hex", 178 | "ic_principal", 179 | "leb128", 180 | "num-bigint", 181 | "num-traits", 182 | "paste", 183 | "pretty", 184 | "serde", 185 | "serde_bytes", 186 | "stacker", 187 | "thiserror", 188 | ] 189 | 190 | [[package]] 191 | name = "candid_derive" 192 | version = "0.6.6" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "3de398570c386726e7a59d9887b68763c481477f9a043fb998a2e09d428df1a9" 195 | dependencies = [ 196 | "lazy_static", 197 | "proc-macro2", 198 | "quote", 199 | "syn 2.0.87", 200 | ] 201 | 202 | [[package]] 203 | name = "cc" 204 | version = "1.2.1" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" 207 | dependencies = [ 208 | "jobserver", 209 | "libc", 210 | "shlex", 211 | ] 212 | 213 | [[package]] 214 | name = "cfg-if" 215 | version = "1.0.0" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 218 | 219 | [[package]] 220 | name = "clap" 221 | version = "4.5.21" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" 224 | dependencies = [ 225 | "clap_builder", 226 | "clap_derive", 227 | ] 228 | 229 | [[package]] 230 | name = "clap_builder" 231 | version = "4.5.21" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" 234 | dependencies = [ 235 | "anstream", 236 | "anstyle", 237 | "clap_lex", 238 | "strsim", 239 | ] 240 | 241 | [[package]] 242 | name = "clap_derive" 243 | version = "4.5.18" 244 | source = "registry+https://github.com/rust-lang/crates.io-index" 245 | checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" 246 | dependencies = [ 247 | "heck 0.5.0", 248 | "proc-macro2", 249 | "quote", 250 | "syn 2.0.87", 251 | ] 252 | 253 | [[package]] 254 | name = "clap_lex" 255 | version = "0.7.3" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" 258 | 259 | [[package]] 260 | name = "codespan-reporting" 261 | version = "0.11.1" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" 264 | dependencies = [ 265 | "termcolor", 266 | "unicode-width", 267 | ] 268 | 269 | [[package]] 270 | name = "colorchoice" 271 | version = "1.0.3" 272 | source = "registry+https://github.com/rust-lang/crates.io-index" 273 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 274 | 275 | [[package]] 276 | name = "core2" 277 | version = "0.4.0" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" 280 | dependencies = [ 281 | "memchr", 282 | ] 283 | 284 | [[package]] 285 | name = "cpufeatures" 286 | version = "0.2.15" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "0ca741a962e1b0bff6d724a1a0958b686406e853bb14061f218562e1896f95e6" 289 | dependencies = [ 290 | "libc", 291 | ] 292 | 293 | [[package]] 294 | name = "crc32fast" 295 | version = "1.4.2" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" 298 | dependencies = [ 299 | "cfg-if", 300 | ] 301 | 302 | [[package]] 303 | name = "crypto-common" 304 | version = "0.1.6" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 307 | dependencies = [ 308 | "generic-array", 309 | "typenum", 310 | ] 311 | 312 | [[package]] 313 | name = "cxx" 314 | version = "1.0.130" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | checksum = "23c042a0ba58aaff55299632834d1ea53ceff73d62373f62c9ae60890ad1b942" 317 | dependencies = [ 318 | "cc", 319 | "cxxbridge-flags", 320 | "cxxbridge-macro", 321 | "link-cplusplus", 322 | ] 323 | 324 | [[package]] 325 | name = "cxx-build" 326 | version = "1.0.130" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | checksum = "45dc1c88d0fdac57518a9b1f6c4f4fb2aca8f3c30c0d03d7d8518b47ca0bcea6" 329 | dependencies = [ 330 | "cc", 331 | "codespan-reporting", 332 | "proc-macro2", 333 | "quote", 334 | "scratch", 335 | "syn 2.0.87", 336 | ] 337 | 338 | [[package]] 339 | name = "cxxbridge-flags" 340 | version = "1.0.130" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "aa7ed7d30b289e2592cc55bc2ccd89803a63c913e008e6eb59f06cddf45bb52f" 343 | 344 | [[package]] 345 | name = "cxxbridge-macro" 346 | version = "1.0.130" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "0b8c465d22de46b851c04630a5fc749a26005b263632ed2e0d9cc81518ead78d" 349 | dependencies = [ 350 | "proc-macro2", 351 | "quote", 352 | "rustversion", 353 | "syn 2.0.87", 354 | ] 355 | 356 | [[package]] 357 | name = "dary_heap" 358 | version = "0.3.7" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | checksum = "04d2cd9c18b9f454ed67da600630b021a8a80bf33f8c95896ab33aaf1c26b728" 361 | 362 | [[package]] 363 | name = "data-encoding" 364 | version = "2.6.0" 365 | source = "registry+https://github.com/rust-lang/crates.io-index" 366 | checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" 367 | 368 | [[package]] 369 | name = "difflib" 370 | version = "0.4.0" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" 373 | 374 | [[package]] 375 | name = "digest" 376 | version = "0.10.7" 377 | source = "registry+https://github.com/rust-lang/crates.io-index" 378 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 379 | dependencies = [ 380 | "block-buffer", 381 | "crypto-common", 382 | ] 383 | 384 | [[package]] 385 | name = "doc-comment" 386 | version = "0.3.3" 387 | source = "registry+https://github.com/rust-lang/crates.io-index" 388 | checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" 389 | 390 | [[package]] 391 | name = "either" 392 | version = "1.13.0" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" 395 | 396 | [[package]] 397 | name = "equivalent" 398 | version = "1.0.1" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 401 | 402 | [[package]] 403 | name = "errno" 404 | version = "0.3.9" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" 407 | dependencies = [ 408 | "libc", 409 | "windows-sys 0.52.0", 410 | ] 411 | 412 | [[package]] 413 | name = "fallible-iterator" 414 | version = "0.2.0" 415 | source = "registry+https://github.com/rust-lang/crates.io-index" 416 | checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" 417 | 418 | [[package]] 419 | name = "fastrand" 420 | version = "2.2.0" 421 | source = "registry+https://github.com/rust-lang/crates.io-index" 422 | checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" 423 | 424 | [[package]] 425 | name = "foldhash" 426 | version = "0.1.4" 427 | source = "registry+https://github.com/rust-lang/crates.io-index" 428 | checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" 429 | 430 | [[package]] 431 | name = "generic-array" 432 | version = "0.14.7" 433 | source = "registry+https://github.com/rust-lang/crates.io-index" 434 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 435 | dependencies = [ 436 | "typenum", 437 | "version_check", 438 | ] 439 | 440 | [[package]] 441 | name = "gimli" 442 | version = "0.26.2" 443 | source = "registry+https://github.com/rust-lang/crates.io-index" 444 | checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" 445 | dependencies = [ 446 | "fallible-iterator", 447 | "indexmap 1.9.3", 448 | "stable_deref_trait", 449 | ] 450 | 451 | [[package]] 452 | name = "hashbrown" 453 | version = "0.12.3" 454 | source = "registry+https://github.com/rust-lang/crates.io-index" 455 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 456 | 457 | [[package]] 458 | name = "hashbrown" 459 | version = "0.14.5" 460 | source = "registry+https://github.com/rust-lang/crates.io-index" 461 | checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 462 | dependencies = [ 463 | "ahash", 464 | "allocator-api2", 465 | "serde", 466 | ] 467 | 468 | [[package]] 469 | name = "hashbrown" 470 | version = "0.15.2" 471 | source = "registry+https://github.com/rust-lang/crates.io-index" 472 | checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" 473 | dependencies = [ 474 | "foldhash", 475 | "serde", 476 | ] 477 | 478 | [[package]] 479 | name = "heck" 480 | version = "0.4.1" 481 | source = "registry+https://github.com/rust-lang/crates.io-index" 482 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 483 | 484 | [[package]] 485 | name = "heck" 486 | version = "0.5.0" 487 | source = "registry+https://github.com/rust-lang/crates.io-index" 488 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 489 | 490 | [[package]] 491 | name = "hex" 492 | version = "0.4.3" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 495 | 496 | [[package]] 497 | name = "ic-wasm" 498 | version = "0.9.5" 499 | dependencies = [ 500 | "anyhow", 501 | "assert_cmd", 502 | "candid", 503 | "clap", 504 | "libflate", 505 | "rustc-demangle", 506 | "serde", 507 | "serde_json", 508 | "tempfile", 509 | "thiserror", 510 | "walrus", 511 | "wasm-opt", 512 | "wasmparser 0.223.0", 513 | ] 514 | 515 | [[package]] 516 | name = "ic_principal" 517 | version = "0.1.1" 518 | source = "registry+https://github.com/rust-lang/crates.io-index" 519 | checksum = "1762deb6f7c8d8c2bdee4b6c5a47b60195b74e9b5280faa5ba29692f8e17429c" 520 | dependencies = [ 521 | "crc32fast", 522 | "data-encoding", 523 | "serde", 524 | "sha2", 525 | "thiserror", 526 | ] 527 | 528 | [[package]] 529 | name = "id-arena" 530 | version = "2.2.1" 531 | source = "registry+https://github.com/rust-lang/crates.io-index" 532 | checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" 533 | 534 | [[package]] 535 | name = "indexmap" 536 | version = "1.9.3" 537 | source = "registry+https://github.com/rust-lang/crates.io-index" 538 | checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" 539 | dependencies = [ 540 | "autocfg", 541 | "hashbrown 0.12.3", 542 | ] 543 | 544 | [[package]] 545 | name = "indexmap" 546 | version = "2.7.0" 547 | source = "registry+https://github.com/rust-lang/crates.io-index" 548 | checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" 549 | dependencies = [ 550 | "equivalent", 551 | "hashbrown 0.15.2", 552 | "serde", 553 | ] 554 | 555 | [[package]] 556 | name = "is_terminal_polyfill" 557 | version = "1.70.1" 558 | source = "registry+https://github.com/rust-lang/crates.io-index" 559 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 560 | 561 | [[package]] 562 | name = "itoa" 563 | version = "1.0.11" 564 | source = "registry+https://github.com/rust-lang/crates.io-index" 565 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 566 | 567 | [[package]] 568 | name = "jobserver" 569 | version = "0.1.32" 570 | source = "registry+https://github.com/rust-lang/crates.io-index" 571 | checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" 572 | dependencies = [ 573 | "libc", 574 | ] 575 | 576 | [[package]] 577 | name = "lazy_static" 578 | version = "1.5.0" 579 | source = "registry+https://github.com/rust-lang/crates.io-index" 580 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 581 | 582 | [[package]] 583 | name = "leb128" 584 | version = "0.2.5" 585 | source = "registry+https://github.com/rust-lang/crates.io-index" 586 | checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" 587 | 588 | [[package]] 589 | name = "libc" 590 | version = "0.2.162" 591 | source = "registry+https://github.com/rust-lang/crates.io-index" 592 | checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398" 593 | 594 | [[package]] 595 | name = "libflate" 596 | version = "2.1.0" 597 | source = "registry+https://github.com/rust-lang/crates.io-index" 598 | checksum = "45d9dfdc14ea4ef0900c1cddbc8dcd553fbaacd8a4a282cf4018ae9dd04fb21e" 599 | dependencies = [ 600 | "adler32", 601 | "core2", 602 | "crc32fast", 603 | "dary_heap", 604 | "libflate_lz77", 605 | ] 606 | 607 | [[package]] 608 | name = "libflate_lz77" 609 | version = "2.1.0" 610 | source = "registry+https://github.com/rust-lang/crates.io-index" 611 | checksum = "e6e0d73b369f386f1c44abd9c570d5318f55ccde816ff4b562fa452e5182863d" 612 | dependencies = [ 613 | "core2", 614 | "hashbrown 0.14.5", 615 | "rle-decode-fast", 616 | ] 617 | 618 | [[package]] 619 | name = "link-cplusplus" 620 | version = "1.0.9" 621 | source = "registry+https://github.com/rust-lang/crates.io-index" 622 | checksum = "9d240c6f7e1ba3a28b0249f774e6a9dd0175054b52dfbb61b16eb8505c3785c9" 623 | dependencies = [ 624 | "cc", 625 | ] 626 | 627 | [[package]] 628 | name = "linux-raw-sys" 629 | version = "0.4.14" 630 | source = "registry+https://github.com/rust-lang/crates.io-index" 631 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" 632 | 633 | [[package]] 634 | name = "log" 635 | version = "0.4.22" 636 | source = "registry+https://github.com/rust-lang/crates.io-index" 637 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 638 | 639 | [[package]] 640 | name = "memchr" 641 | version = "2.7.4" 642 | source = "registry+https://github.com/rust-lang/crates.io-index" 643 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 644 | 645 | [[package]] 646 | name = "num-bigint" 647 | version = "0.4.6" 648 | source = "registry+https://github.com/rust-lang/crates.io-index" 649 | checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" 650 | dependencies = [ 651 | "num-integer", 652 | "num-traits", 653 | "serde", 654 | ] 655 | 656 | [[package]] 657 | name = "num-integer" 658 | version = "0.1.46" 659 | source = "registry+https://github.com/rust-lang/crates.io-index" 660 | checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" 661 | dependencies = [ 662 | "num-traits", 663 | ] 664 | 665 | [[package]] 666 | name = "num-traits" 667 | version = "0.2.19" 668 | source = "registry+https://github.com/rust-lang/crates.io-index" 669 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 670 | dependencies = [ 671 | "autocfg", 672 | ] 673 | 674 | [[package]] 675 | name = "once_cell" 676 | version = "1.20.2" 677 | source = "registry+https://github.com/rust-lang/crates.io-index" 678 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" 679 | 680 | [[package]] 681 | name = "paste" 682 | version = "1.0.15" 683 | source = "registry+https://github.com/rust-lang/crates.io-index" 684 | checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" 685 | 686 | [[package]] 687 | name = "predicates" 688 | version = "3.1.2" 689 | source = "registry+https://github.com/rust-lang/crates.io-index" 690 | checksum = "7e9086cc7640c29a356d1a29fd134380bee9d8f79a17410aa76e7ad295f42c97" 691 | dependencies = [ 692 | "anstyle", 693 | "difflib", 694 | "predicates-core", 695 | ] 696 | 697 | [[package]] 698 | name = "predicates-core" 699 | version = "1.0.8" 700 | source = "registry+https://github.com/rust-lang/crates.io-index" 701 | checksum = "ae8177bee8e75d6846599c6b9ff679ed51e882816914eec639944d7c9aa11931" 702 | 703 | [[package]] 704 | name = "predicates-tree" 705 | version = "1.0.11" 706 | source = "registry+https://github.com/rust-lang/crates.io-index" 707 | checksum = "41b740d195ed3166cd147c8047ec98db0e22ec019eb8eeb76d343b795304fb13" 708 | dependencies = [ 709 | "predicates-core", 710 | "termtree", 711 | ] 712 | 713 | [[package]] 714 | name = "pretty" 715 | version = "0.12.3" 716 | source = "registry+https://github.com/rust-lang/crates.io-index" 717 | checksum = "b55c4d17d994b637e2f4daf6e5dc5d660d209d5642377d675d7a1c3ab69fa579" 718 | dependencies = [ 719 | "arrayvec", 720 | "typed-arena", 721 | "unicode-width", 722 | ] 723 | 724 | [[package]] 725 | name = "proc-macro2" 726 | version = "1.0.89" 727 | source = "registry+https://github.com/rust-lang/crates.io-index" 728 | checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" 729 | dependencies = [ 730 | "unicode-ident", 731 | ] 732 | 733 | [[package]] 734 | name = "psm" 735 | version = "0.1.24" 736 | source = "registry+https://github.com/rust-lang/crates.io-index" 737 | checksum = "200b9ff220857e53e184257720a14553b2f4aa02577d2ed9842d45d4b9654810" 738 | dependencies = [ 739 | "cc", 740 | ] 741 | 742 | [[package]] 743 | name = "quote" 744 | version = "1.0.37" 745 | source = "registry+https://github.com/rust-lang/crates.io-index" 746 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 747 | dependencies = [ 748 | "proc-macro2", 749 | ] 750 | 751 | [[package]] 752 | name = "regex-automata" 753 | version = "0.4.9" 754 | source = "registry+https://github.com/rust-lang/crates.io-index" 755 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 756 | 757 | [[package]] 758 | name = "rle-decode-fast" 759 | version = "1.0.3" 760 | source = "registry+https://github.com/rust-lang/crates.io-index" 761 | checksum = "3582f63211428f83597b51b2ddb88e2a91a9d52d12831f9d08f5e624e8977422" 762 | 763 | [[package]] 764 | name = "rustc-demangle" 765 | version = "0.1.24" 766 | source = "registry+https://github.com/rust-lang/crates.io-index" 767 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 768 | 769 | [[package]] 770 | name = "rustix" 771 | version = "0.38.40" 772 | source = "registry+https://github.com/rust-lang/crates.io-index" 773 | checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0" 774 | dependencies = [ 775 | "bitflags", 776 | "errno", 777 | "libc", 778 | "linux-raw-sys", 779 | "windows-sys 0.52.0", 780 | ] 781 | 782 | [[package]] 783 | name = "rustversion" 784 | version = "1.0.18" 785 | source = "registry+https://github.com/rust-lang/crates.io-index" 786 | checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" 787 | 788 | [[package]] 789 | name = "ryu" 790 | version = "1.0.18" 791 | source = "registry+https://github.com/rust-lang/crates.io-index" 792 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 793 | 794 | [[package]] 795 | name = "scratch" 796 | version = "1.0.7" 797 | source = "registry+https://github.com/rust-lang/crates.io-index" 798 | checksum = "a3cf7c11c38cb994f3d40e8a8cde3bbd1f72a435e4c49e85d6553d8312306152" 799 | 800 | [[package]] 801 | name = "semver" 802 | version = "1.0.23" 803 | source = "registry+https://github.com/rust-lang/crates.io-index" 804 | checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" 805 | 806 | [[package]] 807 | name = "serde" 808 | version = "1.0.215" 809 | source = "registry+https://github.com/rust-lang/crates.io-index" 810 | checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" 811 | dependencies = [ 812 | "serde_derive", 813 | ] 814 | 815 | [[package]] 816 | name = "serde_bytes" 817 | version = "0.11.15" 818 | source = "registry+https://github.com/rust-lang/crates.io-index" 819 | checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a" 820 | dependencies = [ 821 | "serde", 822 | ] 823 | 824 | [[package]] 825 | name = "serde_derive" 826 | version = "1.0.215" 827 | source = "registry+https://github.com/rust-lang/crates.io-index" 828 | checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" 829 | dependencies = [ 830 | "proc-macro2", 831 | "quote", 832 | "syn 2.0.87", 833 | ] 834 | 835 | [[package]] 836 | name = "serde_json" 837 | version = "1.0.132" 838 | source = "registry+https://github.com/rust-lang/crates.io-index" 839 | checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" 840 | dependencies = [ 841 | "itoa", 842 | "memchr", 843 | "ryu", 844 | "serde", 845 | ] 846 | 847 | [[package]] 848 | name = "sha2" 849 | version = "0.10.8" 850 | source = "registry+https://github.com/rust-lang/crates.io-index" 851 | checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" 852 | dependencies = [ 853 | "cfg-if", 854 | "cpufeatures", 855 | "digest", 856 | ] 857 | 858 | [[package]] 859 | name = "shlex" 860 | version = "1.3.0" 861 | source = "registry+https://github.com/rust-lang/crates.io-index" 862 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 863 | 864 | [[package]] 865 | name = "stable_deref_trait" 866 | version = "1.2.0" 867 | source = "registry+https://github.com/rust-lang/crates.io-index" 868 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 869 | 870 | [[package]] 871 | name = "stacker" 872 | version = "0.1.17" 873 | source = "registry+https://github.com/rust-lang/crates.io-index" 874 | checksum = "799c883d55abdb5e98af1a7b3f23b9b6de8ecada0ecac058672d7635eb48ca7b" 875 | dependencies = [ 876 | "cc", 877 | "cfg-if", 878 | "libc", 879 | "psm", 880 | "windows-sys 0.59.0", 881 | ] 882 | 883 | [[package]] 884 | name = "strsim" 885 | version = "0.11.1" 886 | source = "registry+https://github.com/rust-lang/crates.io-index" 887 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 888 | 889 | [[package]] 890 | name = "strum" 891 | version = "0.24.1" 892 | source = "registry+https://github.com/rust-lang/crates.io-index" 893 | checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" 894 | 895 | [[package]] 896 | name = "strum_macros" 897 | version = "0.24.3" 898 | source = "registry+https://github.com/rust-lang/crates.io-index" 899 | checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" 900 | dependencies = [ 901 | "heck 0.4.1", 902 | "proc-macro2", 903 | "quote", 904 | "rustversion", 905 | "syn 1.0.109", 906 | ] 907 | 908 | [[package]] 909 | name = "syn" 910 | version = "1.0.109" 911 | source = "registry+https://github.com/rust-lang/crates.io-index" 912 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 913 | dependencies = [ 914 | "proc-macro2", 915 | "quote", 916 | "unicode-ident", 917 | ] 918 | 919 | [[package]] 920 | name = "syn" 921 | version = "2.0.87" 922 | source = "registry+https://github.com/rust-lang/crates.io-index" 923 | checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" 924 | dependencies = [ 925 | "proc-macro2", 926 | "quote", 927 | "unicode-ident", 928 | ] 929 | 930 | [[package]] 931 | name = "tempfile" 932 | version = "3.14.0" 933 | source = "registry+https://github.com/rust-lang/crates.io-index" 934 | checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" 935 | dependencies = [ 936 | "cfg-if", 937 | "fastrand", 938 | "once_cell", 939 | "rustix", 940 | "windows-sys 0.59.0", 941 | ] 942 | 943 | [[package]] 944 | name = "termcolor" 945 | version = "1.4.1" 946 | source = "registry+https://github.com/rust-lang/crates.io-index" 947 | checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" 948 | dependencies = [ 949 | "winapi-util", 950 | ] 951 | 952 | [[package]] 953 | name = "termtree" 954 | version = "0.4.1" 955 | source = "registry+https://github.com/rust-lang/crates.io-index" 956 | checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" 957 | 958 | [[package]] 959 | name = "thiserror" 960 | version = "1.0.69" 961 | source = "registry+https://github.com/rust-lang/crates.io-index" 962 | checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 963 | dependencies = [ 964 | "thiserror-impl", 965 | ] 966 | 967 | [[package]] 968 | name = "thiserror-impl" 969 | version = "1.0.69" 970 | source = "registry+https://github.com/rust-lang/crates.io-index" 971 | checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 972 | dependencies = [ 973 | "proc-macro2", 974 | "quote", 975 | "syn 2.0.87", 976 | ] 977 | 978 | [[package]] 979 | name = "typed-arena" 980 | version = "2.0.2" 981 | source = "registry+https://github.com/rust-lang/crates.io-index" 982 | checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" 983 | 984 | [[package]] 985 | name = "typenum" 986 | version = "1.17.0" 987 | source = "registry+https://github.com/rust-lang/crates.io-index" 988 | checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" 989 | 990 | [[package]] 991 | name = "unicode-ident" 992 | version = "1.0.13" 993 | source = "registry+https://github.com/rust-lang/crates.io-index" 994 | checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" 995 | 996 | [[package]] 997 | name = "unicode-width" 998 | version = "0.1.14" 999 | source = "registry+https://github.com/rust-lang/crates.io-index" 1000 | checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" 1001 | 1002 | [[package]] 1003 | name = "utf8parse" 1004 | version = "0.2.2" 1005 | source = "registry+https://github.com/rust-lang/crates.io-index" 1006 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 1007 | 1008 | [[package]] 1009 | name = "version_check" 1010 | version = "0.9.5" 1011 | source = "registry+https://github.com/rust-lang/crates.io-index" 1012 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 1013 | 1014 | [[package]] 1015 | name = "wait-timeout" 1016 | version = "0.2.0" 1017 | source = "registry+https://github.com/rust-lang/crates.io-index" 1018 | checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" 1019 | dependencies = [ 1020 | "libc", 1021 | ] 1022 | 1023 | [[package]] 1024 | name = "walrus" 1025 | version = "0.22.0" 1026 | source = "registry+https://github.com/rust-lang/crates.io-index" 1027 | checksum = "d68aa3c7b80be75c8458fc087453e5a31a226cfffede2e9b932393b2ea1c624a" 1028 | dependencies = [ 1029 | "anyhow", 1030 | "gimli", 1031 | "id-arena", 1032 | "leb128", 1033 | "log", 1034 | "walrus-macro", 1035 | "wasm-encoder", 1036 | "wasmparser 0.212.0", 1037 | ] 1038 | 1039 | [[package]] 1040 | name = "walrus-macro" 1041 | version = "0.22.0" 1042 | source = "registry+https://github.com/rust-lang/crates.io-index" 1043 | checksum = "439ad39ff894c43c9649fa724cdde9a6fc50b855d517ef071a93e5df82fe51d3" 1044 | dependencies = [ 1045 | "heck 0.5.0", 1046 | "proc-macro2", 1047 | "quote", 1048 | "syn 2.0.87", 1049 | ] 1050 | 1051 | [[package]] 1052 | name = "wasm-encoder" 1053 | version = "0.212.0" 1054 | source = "registry+https://github.com/rust-lang/crates.io-index" 1055 | checksum = "501940df4418b8929eb6d52f1aade1fdd15a5b86c92453cb696e3c906bd3fc33" 1056 | dependencies = [ 1057 | "leb128", 1058 | ] 1059 | 1060 | [[package]] 1061 | name = "wasm-opt" 1062 | version = "0.116.1" 1063 | source = "registry+https://github.com/rust-lang/crates.io-index" 1064 | checksum = "2fd87a4c135535ffed86123b6fb0f0a5a0bc89e50416c942c5f0662c645f679c" 1065 | dependencies = [ 1066 | "anyhow", 1067 | "libc", 1068 | "strum", 1069 | "strum_macros", 1070 | "tempfile", 1071 | "thiserror", 1072 | "wasm-opt-cxx-sys", 1073 | "wasm-opt-sys", 1074 | ] 1075 | 1076 | [[package]] 1077 | name = "wasm-opt-cxx-sys" 1078 | version = "0.116.0" 1079 | source = "registry+https://github.com/rust-lang/crates.io-index" 1080 | checksum = "8c57b28207aa724318fcec6575fe74803c23f6f266fce10cbc9f3f116762f12e" 1081 | dependencies = [ 1082 | "anyhow", 1083 | "cxx", 1084 | "cxx-build", 1085 | "wasm-opt-sys", 1086 | ] 1087 | 1088 | [[package]] 1089 | name = "wasm-opt-sys" 1090 | version = "0.116.0" 1091 | source = "registry+https://github.com/rust-lang/crates.io-index" 1092 | checksum = "8a1cce564dc768dacbdb718fc29df2dba80bd21cb47d8f77ae7e3d95ceb98cbe" 1093 | dependencies = [ 1094 | "anyhow", 1095 | "cc", 1096 | "cxx", 1097 | "cxx-build", 1098 | ] 1099 | 1100 | [[package]] 1101 | name = "wasmparser" 1102 | version = "0.212.0" 1103 | source = "registry+https://github.com/rust-lang/crates.io-index" 1104 | checksum = "8d28bc49ba1e5c5b61ffa7a2eace10820443c4b7d1c0b144109261d14570fdf8" 1105 | dependencies = [ 1106 | "ahash", 1107 | "bitflags", 1108 | "hashbrown 0.14.5", 1109 | "indexmap 2.7.0", 1110 | "semver", 1111 | "serde", 1112 | ] 1113 | 1114 | [[package]] 1115 | name = "wasmparser" 1116 | version = "0.223.0" 1117 | source = "registry+https://github.com/rust-lang/crates.io-index" 1118 | checksum = "d5a99faceb1a5a84dd6084ec4bfa4b2ab153b5793b43fd8f58b89232634afc35" 1119 | dependencies = [ 1120 | "bitflags", 1121 | "hashbrown 0.15.2", 1122 | "indexmap 2.7.0", 1123 | "semver", 1124 | "serde", 1125 | ] 1126 | 1127 | [[package]] 1128 | name = "winapi-util" 1129 | version = "0.1.9" 1130 | source = "registry+https://github.com/rust-lang/crates.io-index" 1131 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 1132 | dependencies = [ 1133 | "windows-sys 0.59.0", 1134 | ] 1135 | 1136 | [[package]] 1137 | name = "windows-sys" 1138 | version = "0.52.0" 1139 | source = "registry+https://github.com/rust-lang/crates.io-index" 1140 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1141 | dependencies = [ 1142 | "windows-targets", 1143 | ] 1144 | 1145 | [[package]] 1146 | name = "windows-sys" 1147 | version = "0.59.0" 1148 | source = "registry+https://github.com/rust-lang/crates.io-index" 1149 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 1150 | dependencies = [ 1151 | "windows-targets", 1152 | ] 1153 | 1154 | [[package]] 1155 | name = "windows-targets" 1156 | version = "0.52.6" 1157 | source = "registry+https://github.com/rust-lang/crates.io-index" 1158 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 1159 | dependencies = [ 1160 | "windows_aarch64_gnullvm", 1161 | "windows_aarch64_msvc", 1162 | "windows_i686_gnu", 1163 | "windows_i686_gnullvm", 1164 | "windows_i686_msvc", 1165 | "windows_x86_64_gnu", 1166 | "windows_x86_64_gnullvm", 1167 | "windows_x86_64_msvc", 1168 | ] 1169 | 1170 | [[package]] 1171 | name = "windows_aarch64_gnullvm" 1172 | version = "0.52.6" 1173 | source = "registry+https://github.com/rust-lang/crates.io-index" 1174 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 1175 | 1176 | [[package]] 1177 | name = "windows_aarch64_msvc" 1178 | version = "0.52.6" 1179 | source = "registry+https://github.com/rust-lang/crates.io-index" 1180 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 1181 | 1182 | [[package]] 1183 | name = "windows_i686_gnu" 1184 | version = "0.52.6" 1185 | source = "registry+https://github.com/rust-lang/crates.io-index" 1186 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 1187 | 1188 | [[package]] 1189 | name = "windows_i686_gnullvm" 1190 | version = "0.52.6" 1191 | source = "registry+https://github.com/rust-lang/crates.io-index" 1192 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 1193 | 1194 | [[package]] 1195 | name = "windows_i686_msvc" 1196 | version = "0.52.6" 1197 | source = "registry+https://github.com/rust-lang/crates.io-index" 1198 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 1199 | 1200 | [[package]] 1201 | name = "windows_x86_64_gnu" 1202 | version = "0.52.6" 1203 | source = "registry+https://github.com/rust-lang/crates.io-index" 1204 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 1205 | 1206 | [[package]] 1207 | name = "windows_x86_64_gnullvm" 1208 | version = "0.52.6" 1209 | source = "registry+https://github.com/rust-lang/crates.io-index" 1210 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 1211 | 1212 | [[package]] 1213 | name = "windows_x86_64_msvc" 1214 | version = "0.52.6" 1215 | source = "registry+https://github.com/rust-lang/crates.io-index" 1216 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 1217 | 1218 | [[package]] 1219 | name = "zerocopy" 1220 | version = "0.7.35" 1221 | source = "registry+https://github.com/rust-lang/crates.io-index" 1222 | checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" 1223 | dependencies = [ 1224 | "zerocopy-derive", 1225 | ] 1226 | 1227 | [[package]] 1228 | name = "zerocopy-derive" 1229 | version = "0.7.35" 1230 | source = "registry+https://github.com/rust-lang/crates.io-index" 1231 | checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" 1232 | dependencies = [ 1233 | "proc-macro2", 1234 | "quote", 1235 | "syn 2.0.87", 1236 | ] 1237 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ic-wasm" 3 | version = "0.9.5" 4 | authors = ["DFINITY Stiftung"] 5 | edition = "2021" 6 | description = "A library for performing Wasm transformations specific to canisters running on the Internet Computer" 7 | license = "Apache-2.0" 8 | readme = "README.md" 9 | documentation = "https://docs.rs/ic-wasm" 10 | repository = "https://github.com/dfinity/ic-wasm" 11 | categories = ["wasm"] 12 | keywords = ["internet-computer", "canister", "dfinity"] 13 | include = ["src", "Cargo.toml", "LICENSE", "README.md"] 14 | 15 | [[bin]] 16 | name = "ic-wasm" 17 | path = "src/bin/main.rs" 18 | required-features = ["exe"] 19 | 20 | [dependencies] 21 | # Major version bump of walrus should result in a major version bump of ic-wasm. 22 | # Because we expose walrus types in ic-wasm public API. 23 | walrus = "0.22.0" 24 | candid = "0.10" 25 | rustc-demangle = "0.1" 26 | thiserror = "1.0.35" 27 | libflate = "2.0" 28 | wasmparser = "0.223.0" 29 | 30 | wasm-opt = { version = "0.116.0", optional = true } 31 | tempfile = { version = "3.5.0", optional = true } 32 | anyhow = { version = "1.0.34", optional = true } 33 | clap = { version = "4.1", features = ["derive", "cargo"], optional = true } 34 | serde = { version = "1.0", optional = true } 35 | serde_json = { version = "1.0", optional = true } 36 | 37 | [features] 38 | default = ["exe", "wasm-opt"] 39 | exe = ["anyhow", "clap", "serde"] 40 | wasm-opt = ["dep:wasm-opt", "tempfile"] 41 | serde = ["dep:serde", "dep:serde_json"] 42 | 43 | [dev-dependencies] 44 | assert_cmd = "2" 45 | 46 | [package.metadata.binstall] 47 | pkg-url = "{ repo }/releases/download/{ version }/{ name }-{ target }{ archive-suffix }" 48 | pkg-fmt = "tgz" # archive-suffix = .tar.gz 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, and 10 | distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by the 13 | copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other 16 | entities that control, are controlled by, or are under common control with 17 | that entity. For the purposes of this definition, "control" means (i) the 18 | power, direct or indirect, to cause the direction or management of such 19 | entity, whether by contract or otherwise, or (ii) ownership of fifty percent 20 | (50%) or more of the outstanding shares, or (iii) beneficial ownership of 21 | such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity exercising 24 | permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation source, and 28 | configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical transformation 31 | or translation of a Source form, including but not limited to compiled 32 | object code, generated documentation, and conversions to other media types. 33 | 34 | "Work" shall mean the work of authorship, whether in Source or Object form, 35 | made available under the License, as indicated by a copyright notice that is 36 | included in or attached to the work (an example is provided in the Appendix 37 | below). 38 | 39 | "Derivative Works" shall mean any work, whether in Source or Object form, 40 | that is based on (or derived from) the Work and for which the editorial 41 | revisions, annotations, elaborations, or other modifications represent, as a 42 | whole, an original work of authorship. For the purposes of this License, 43 | Derivative Works shall not include works that remain separable from, or 44 | merely link (or bind by name) to the interfaces of, the Work and Derivative 45 | Works thereof. 46 | 47 | "Contribution" shall mean any work of authorship, including the original 48 | version of the Work and any modifications or additions to that Work or 49 | Derivative Works thereof, that is intentionally submitted to Licensor for 50 | inclusion in the Work by the copyright owner or by an individual or Legal 51 | Entity authorized to submit on behalf of the copyright owner. For the 52 | purposes of this definition, "submitted" means any form of electronic, 53 | verbal, or written communication sent to the Licensor or its 54 | representatives, including but not limited to communication on electronic 55 | mailing lists, source code control systems, and issue tracking systems that 56 | are managed by, or on behalf of, the Licensor for the purpose of discussing 57 | and improving the Work, but excluding communication that is conspicuously 58 | marked or otherwise designated in writing by the copyright owner as "Not a 59 | Contribution." 60 | 61 | "Contributor" shall mean Licensor and any individual or Legal Entity on 62 | behalf of whom a Contribution has been received by Licensor and subsequently 63 | incorporated within the Work. 64 | 65 | 2. Grant of Copyright License. Subject to the terms and conditions of this 66 | License, each Contributor hereby grants to You a perpetual, worldwide, 67 | non-exclusive, no-charge, royalty-free, irrevocable copyright license to 68 | reproduce, prepare Derivative Works of, publicly display, publicly perform, 69 | sublicense, and distribute the Work and such Derivative Works in Source or 70 | Object form. 71 | 72 | 3. Grant of Patent License. Subject to the terms and conditions of this 73 | License, each Contributor hereby grants to You a perpetual, worldwide, 74 | non-exclusive, no-charge, royalty-free, irrevocable (except as stated in 75 | this section) patent license to make, have made, use, offer to sell, sell, 76 | import, and otherwise transfer the Work, where such license applies only to 77 | those patent claims licensable by such Contributor that are necessarily 78 | infringed by their Contribution(s) alone or by combination of their 79 | Contribution(s) with the Work to which such Contribution(s) was submitted. 80 | If You institute patent litigation against any entity (including a 81 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a 82 | Contribution incorporated within the Work constitutes direct or contributory 83 | patent infringement, then any patent licenses granted to You under this 84 | License for that Work shall terminate as of the date such litigation is 85 | filed. 86 | 87 | 4. Redistribution. You may reproduce and distribute copies of the Work or 88 | Derivative Works thereof in any medium, with or without modifications, and 89 | in Source or Object form, provided that You meet the following conditions: 90 | 91 | a. You must give any other recipients of the Work or Derivative Works a 92 | copy of this License; and 93 | 94 | b. You must cause any modified files to carry prominent notices stating 95 | that You changed the files; and 96 | 97 | c. You must retain, in the Source form of any Derivative Works that You 98 | distribute, all copyright, patent, trademark, and attribution notices 99 | from the Source form of the Work, excluding those notices that do not 100 | pertain to any part of the Derivative Works; and 101 | 102 | d. If the Work includes a "NOTICE" text file as part of its distribution, 103 | then any Derivative Works that You distribute must include a readable 104 | copy of the attribution notices contained within such NOTICE file, 105 | excluding those notices that do not pertain to any part of the Derivative 106 | Works, in at least one of the following places: within a NOTICE text file 107 | distributed as part of the Derivative Works; within the Source form or 108 | documentation, if provided along with the Derivative Works; or, within a 109 | display generated by the Derivative Works, if and wherever such 110 | third-party notices normally appear. The contents of the NOTICE file are 111 | for informational purposes only and do not modify the License. You may 112 | add Your own attribution notices within Derivative Works that You 113 | distribute, alongside or as an addendum to the NOTICE text from the Work, 114 | provided that such additional attribution notices cannot be construed as 115 | modifying the License. 116 | 117 | You may add Your own copyright statement to Your modifications and may 118 | provide additional or different license terms and conditions for use, 119 | reproduction, or distribution of Your modifications, or for any such 120 | Derivative Works as a whole, provided Your use, reproduction, and 121 | distribution of the Work otherwise complies with the conditions stated in 122 | this License. 123 | 124 | 5. Submission of Contributions. Unless You explicitly state otherwise, any 125 | Contribution intentionally submitted for inclusion in the Work by You to the 126 | Licensor shall be under the terms and conditions of this License, without 127 | any additional terms or conditions. Notwithstanding the above, nothing 128 | herein shall supersede or modify the terms of any separate license agreement 129 | you may have executed with Licensor regarding such Contributions. 130 | 131 | 6. Trademarks. This License does not grant permission to use the trade names, 132 | trademarks, service marks, or product names of the Licensor, except as 133 | required for reasonable and customary use in describing the origin of the 134 | Work and reproducing the content of the NOTICE file. 135 | 136 | 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in 137 | writing, Licensor provides the Work (and each Contributor provides its 138 | Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 139 | KIND, either express or implied, including, without limitation, any 140 | warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or 141 | FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining 142 | the appropriateness of using or redistributing the Work and assume any risks 143 | associated with Your exercise of permissions under this License. 144 | 145 | 8. Limitation of Liability. In no event and under no legal theory, whether in 146 | tort (including negligence), contract, or otherwise, unless required by 147 | applicable law (such as deliberate and grossly negligent acts) or agreed to 148 | in writing, shall any Contributor be liable to You for damages, including 149 | any direct, indirect, special, incidental, or consequential damages of any 150 | character arising as a result of this License or out of the use or inability 151 | to use the Work (including but not limited to damages for loss of goodwill, 152 | work stoppage, computer failure or malfunction, or any and all other 153 | commercial damages or losses), even if such Contributor has been advised of 154 | the possibility of such damages. 155 | 156 | 9. Accepting Warranty or Additional Liability. While redistributing the Work or 157 | Derivative Works thereof, You may choose to offer, and charge a fee for, 158 | acceptance of support, warranty, indemnity, or other liability obligations 159 | and/or rights consistent with this License. However, in accepting such 160 | obligations, You may act only on Your own behalf and on Your sole 161 | responsibility, not on behalf of any other Contributor, and only if You 162 | agree to indemnify, defend, and hold each Contributor harmless for any 163 | liability incurred by, or claims asserted against, such Contributor by 164 | reason of your accepting any such warranty or additional liability. 165 | 166 | END OF TERMS AND CONDITIONS 167 | 168 | LLVM EXCEPTIONS TO THE APACHE 2.0 LICENSE 169 | 170 | As an exception, if, as a result of your compiling your source code, portions 171 | of this Software are embedded into an Object form of such source code, you may 172 | redistribute such embedded portions in such Object form without complying with 173 | the conditions of Sections 4(a), 4(b) and 4(d) of the License. 174 | 175 | In addition, if you combine or link compiled forms of this Software with 176 | software that is licensed under the GPLv2 ("Combined Software") and if a court 177 | of competent jurisdiction determines that the patent provision (Section 3), the 178 | indemnity provision (Section 9) or other Section of the License conflicts with 179 | the conditions of the GPLv2, you may retroactively and prospectively choose to 180 | deem waived or otherwise exclude such Section(s) of the License, but only in 181 | their entirety and only with respect to the Combined Software. 182 | 183 | END OF LLVM EXCEPTIONS 184 | 185 | APPENDIX: How to apply the Apache License to your work. 186 | 187 | To apply the Apache License to your work, attach the following boilerplate 188 | notice, with the fields enclosed by brackets "[]" replaced with your own 189 | identifying information. (Don't include the brackets!) The text should be 190 | enclosed in the appropriate comment syntax for the file format. We also 191 | recommend that a file or class name and description of purpose be included on 192 | the same "printed page" as the copyright notice for easier identification 193 | within third-party archives. 194 | 195 | Copyright [yyyy] [name of copyright owner] 196 | 197 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use 198 | this file except in compliance with the License. You may obtain a copy of the 199 | License at 200 | 201 | http://www.apache.org/licenses/LICENSE-2.0 202 | 203 | Unless required by applicable law or agreed to in writing, software distributed 204 | under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 205 | CONDITIONS OF ANY KIND, either express or implied. See the License for the 206 | specific language governing permissions and limitations under the License. 207 | 208 | END OF APPENDIX 209 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright 2020 DFINITY Stiftung 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use 4 | this file except in compliance with the License. You may obtain a copy of the 5 | License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software distributed 10 | under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 11 | CONDITIONS OF ANY KIND, either express or implied. See the License for the 12 | specific language governing permissions and limitations under the License. 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `ic-wasm` 2 | 3 | A library for transforming Wasm canisters running on the Internet Computer 4 | 5 | ## Executable 6 | 7 | To install the `ic-wasm` executable, run 8 | 9 | ``` 10 | $ cargo install ic-wasm 11 | ``` 12 | 13 | ### Metadata 14 | 15 | Manage metadata in the Wasm module. 16 | 17 | Usage: `ic-wasm [-o ] metadata [name] [-d | -f ] [-v ]` 18 | 19 | * List current metadata sections 20 | ``` 21 | $ ic-wasm input.wasm metadata 22 | ``` 23 | 24 | * List a specific metadata content 25 | ``` 26 | $ ic-wasm input.wasm metadata candid:service 27 | ``` 28 | 29 | * Add/overwrite a private metadata section 30 | 31 | **Note**: the hashes of private metadata sections are readable by anyone. If a section contains low-entropy data, the attacker could brute-force the contents. 32 | ``` 33 | $ ic-wasm input.wasm -o output.wasm metadata new_section -d "hello, world" 34 | ``` 35 | 36 | * Add/overwrite a public metadata section from file 37 | ``` 38 | $ ic-wasm input.wasm -o output.wasm metadata candid:service -f service.did -v public 39 | ``` 40 | 41 | ### Info 42 | 43 | Print information about the Wasm canister 44 | 45 | Usage: `ic-wasm info` 46 | 47 | ### Shrink 48 | 49 | Remove unused functions and debug info. 50 | 51 | Note: The `icp` metadata sections are preserved through the shrink. 52 | 53 | Usage: `ic-wasm -o shrink` 54 | 55 | ### Optimize 56 | 57 | Invoke wasm optimizations from [`wasm-opt`](https://github.com/WebAssembly/binaryen). 58 | 59 | The optimizer exposes different optimization levels to choose from. 60 | 61 | Performance levels (optimizes for runtime): 62 | - O4 63 | - O3 (default setting: best for minimizing cycle usage) 64 | - O2 65 | - O1 66 | - O0 (no optimizations) 67 | 68 | Code size levels (optimizes for binary size): 69 | - Oz (best for minimizing code size) 70 | - Os 71 | 72 | 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%. 73 | 74 | Note: The `icp` metadata sections are preserved through the optimizations. 75 | 76 | Usage: `ic-wasm -o optimize ` 77 | 78 | There are two further flags exposed from `wasm-opt`: 79 | - `--inline-functions-with-loops` 80 | - `--always-inline-max-function-size ` 81 | 82 | These were exposed to aggressively inline functions, which are common in Motoko programs. With the new cost model, there is a large performance gain from inlining functions with loops, but also a large blowup in binary size. Due to the binary size increase, we may not be able to apply this inlining for actor classes inside a Wasm module. 83 | 84 | E.g. 85 | `ic-wasm -o optimize O3 --inline-functions-with-loops --always-inline-max-function-size 100` 86 | 87 | ### Resource 88 | 89 | Limit resource usage, mainly used by Motoko Playground 90 | 91 | Usage: `ic-wasm -o resource --remove_cycles_transfer --limit_stable_memory_page 1024` 92 | 93 | ### Instrument (experimental) 94 | 95 | Instrument canister method to emit execution trace to stable memory. 96 | 97 | Usage: `ic-wasm -o instrument --trace-only func1 --trace-only func2 --start-page 16 --page-limit 30` 98 | 99 | Instrumented canister has the following additional endpoints: 100 | 101 | * `__get_cycles: () -> (int64) query`. Get the current cycle counter. 102 | * `__get_profiling: (idx:int32) -> (vec { record { int32; int64 }}, opt int32) query`. Get the execution trace log, starting with `idx` 0. If the log is larger than 2M, it returns the first 2M of trace, and the next `idx` for the next 2M chunk. 103 | * `__toggle_tracing: () -> ()`. Disable/enable logging the execution trace. 104 | * `__toggle_entry: () -> ()`. Disable/enable clearing exection trace for each update call. 105 | * `icp:public name` metadata. Used to map func_id from execution trace to function name. 106 | 107 | When `--trace-only` flag is provided, the counter and trace logging will only happen during the execution of that function, instead of tracing the whole update call. Note that the function itself has to be non-recursive. 108 | 109 | #### Working with upgrades and stable memory 110 | 111 | By default, execution trace is stored in the first few pages (up to 32 pages) of stable memory. Without any user side support, we cannot profile upgrade or code which accesses stable memory. If the canister can pre-allocate a fixed region of stable memory at `canister_init`, we can then pass this address to `ic-wasm` via the `--start-page` flag, so that the trace is written to this pre-allocated space without corrupting the rest of the stable memory access. 112 | 113 | Another optional flag `--page-limit` specifies the number of pre-allocated pages in stable memory. By default, it's set to 4096 pages (256MB). We only store trace up to `page-limit` pages, the remaining trace is dropped. 114 | 115 | The recommended way of pre-allocating stable memory is via the `Region` library in Motoko, and `ic-stable-structures` in Rust. But developers are free to use any other libraries or even the raw stable memory system API to pre-allocate space, as long as the developer can guarantee that the pre-allocated space is not touched by the rest of the code. 116 | 117 | The following is the code sample for pre-allocating stable memory in Motoko (with `--start-page 16`), 118 | 119 | ```motoko 120 | import Region "mo:base/Region"; 121 | actor { 122 | stable let profiling = do { 123 | let r = Region.new(); 124 | ignore Region.grow(r, 4096); // Increase the page number if you need larger log space 125 | r; 126 | }; 127 | ... 128 | } 129 | ``` 130 | 131 | and in Rust (with `--start-page 1`) 132 | 133 | ```rust 134 | use ic_stable_structures::{ 135 | memory_manager::{MemoryId, MemoryManager}, 136 | writer::Writer, 137 | DefaultMemoryImpl, Memory, 138 | }; 139 | thread_local! { 140 | static MEMORY_MANAGER: RefCell> = 141 | RefCell::new(MemoryManager::init(DefaultMemoryImpl::default())); 142 | } 143 | const PROFILING: MemoryId = MemoryId::new(0); 144 | const UPGRADES: MemoryId = MemoryId::new(1); 145 | 146 | #[ic_cdk::init] 147 | fn init() { 148 | let memory = MEMORY_MANAGER.with(|m| m.borrow().get(PROFILING)); 149 | memory.grow(4096); // Increase the page number if you need larger log space 150 | ... 151 | } 152 | #[ic_cdk::pre_upgrade] 153 | fn pre_upgrade() { 154 | let mut memory = MEMORY_MANAGER.with(|m| m.borrow().get(UPGRADES)); 155 | ... 156 | } 157 | #[ic_cdk::post_upgrade] 158 | fn post_upgrade() { 159 | let memory = MEMORY_MANAGER.with(|m| m.borrow().get(UPGRADES)); 160 | ... 161 | } 162 | ``` 163 | 164 | #### Current limitations 165 | 166 | * Without pre-allocating stable memory from user code, we cannot profile upgrade or code that accesses stable memory. You can profile traces larger than 256M, if you pre-allocate large pages of stable memory and specify the `page-limit` flag. Larger traces can be fetched in a streamming fashion via `__get_profiling(idx)`. 167 | * Since the pre-allocation happens in `canister_init`, we cannot profile `canister_init`. 168 | * If heartbeat is present, it's hard to measure any other method calls. It's also hard to measure a specific heartbeat event. 169 | * We cannot measure query calls. 170 | * No concurrent calls. 171 | 172 | ## Library 173 | 174 | To use `ic-wasm` as a library, add this to your `Cargo.toml`: 175 | 176 | ```toml 177 | [dependencies.ic-wasm] 178 | default-features = false 179 | ``` 180 | 181 | ## Contribution 182 | 183 | See our [CONTRIBUTING](.github/CONTRIBUTING.md) to get started. 184 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "stable" 3 | components = ["rustfmt", "clippy"] 4 | -------------------------------------------------------------------------------- /src/bin/main.rs: -------------------------------------------------------------------------------- 1 | use clap::{crate_authors, crate_version, Parser}; 2 | use ic_wasm::utils::make_validator_with_features; 3 | use std::path::PathBuf; 4 | 5 | #[derive(Parser)] 6 | #[clap( 7 | version = crate_version!(), 8 | author = crate_authors!(), 9 | )] 10 | struct Opts { 11 | /// Input Wasm file. 12 | input: PathBuf, 13 | 14 | /// Write the transformed Wasm file if provided. 15 | #[clap(short, long)] 16 | output: Option, 17 | 18 | #[clap(subcommand)] 19 | subcommand: SubCommand, 20 | } 21 | 22 | #[derive(Parser)] 23 | enum SubCommand { 24 | /// Manage metadata in the Wasm module 25 | Metadata { 26 | /// Name of metadata. If not provided, list the current metadata sections. 27 | name: Option, 28 | /// Content of metadata as a string 29 | #[clap(short, long, requires("name"))] 30 | data: Option, 31 | /// Content of metadata from a file 32 | #[clap(short, long, requires("name"), conflicts_with("data"))] 33 | file: Option, 34 | /// Visibility of metadata 35 | #[clap(short, long, value_parser = ["public", "private"], default_value = "private")] 36 | visibility: String, 37 | /// Preserve the `name` section in the generated Wasm. This is needed to 38 | /// display the names of functions, locals, etc. in backtraces or 39 | /// debuggers. 40 | #[clap(short, long)] 41 | keep_name_section: bool, 42 | }, 43 | /// Limit resource usage 44 | Resource { 45 | /// Remove cycles_add system API call 46 | #[clap(short, long)] 47 | remove_cycles_transfer: bool, 48 | /// Allocate at most specified amount of memory pages for Wasm heap memory 49 | #[clap(short('m'), long)] 50 | limit_heap_memory_page: Option, 51 | /// Allocate at most specified amount of memory pages for stable memory 52 | #[clap(short, long)] 53 | limit_stable_memory_page: Option, 54 | /// Redirects controller system API calls to specified motoko backend canister ID 55 | #[clap(short, long)] 56 | playground_backend_redirect: Option, 57 | }, 58 | /// List information about the Wasm canister 59 | Info { 60 | /// Format the output as JSON 61 | #[clap(short, long)] 62 | json: bool, 63 | }, 64 | /// Remove unused functions and debug info 65 | Shrink { 66 | /// Preserve the `name` section in the generated Wasm. This is needed to 67 | /// display the names of functions, locals, etc. in backtraces or 68 | /// debuggers. 69 | #[clap(short, long)] 70 | keep_name_section: bool, 71 | }, 72 | /// Optimize the Wasm module using wasm-opt 73 | #[cfg(feature = "wasm-opt")] 74 | Optimize { 75 | #[clap()] 76 | level: ic_wasm::optimize::OptLevel, 77 | #[clap(long("inline-functions-with-loops"))] 78 | inline_functions_with_loops: bool, 79 | #[clap(long("always-inline-max-function-size"))] 80 | always_inline_max_function_size: Option, 81 | /// Preserve the `name` section in the generated Wasm. This is needed to 82 | /// display the names of functions, locals, etc. in backtraces or 83 | /// debuggers. 84 | #[clap(short, long)] 85 | keep_name_section: bool, 86 | }, 87 | /// Instrument canister method to emit execution trace to stable memory (experimental) 88 | Instrument { 89 | /// Trace only the specified list of functions. The function cannot be recursive 90 | #[clap(short, long)] 91 | trace_only: Option>, 92 | /// If the canister preallocates a stable memory region, specify the starting page. Required if you want to profile upgrades, or the canister uses stable memory 93 | #[clap(short, long)] 94 | start_page: Option, 95 | /// The number of pages of the preallocated stable memory 96 | #[clap(short, long, requires("start_page"))] 97 | page_limit: Option, 98 | }, 99 | } 100 | 101 | fn main() -> anyhow::Result<()> { 102 | let opts: Opts = Opts::parse(); 103 | let keep_name_section = match opts.subcommand { 104 | SubCommand::Shrink { keep_name_section } => keep_name_section, 105 | #[cfg(feature = "wasm-opt")] 106 | SubCommand::Optimize { 107 | keep_name_section, .. 108 | } => keep_name_section, 109 | SubCommand::Metadata { 110 | keep_name_section, .. 111 | } => keep_name_section, 112 | _ => false, 113 | }; 114 | let mut m = ic_wasm::utils::parse_wasm_file(opts.input, keep_name_section)?; 115 | match &opts.subcommand { 116 | SubCommand::Info { json } => { 117 | let wasm_info = ic_wasm::info::WasmInfo::from(&m); 118 | if *json { 119 | let json = serde_json::to_string_pretty(&wasm_info) 120 | .expect("Failed to express the Wasm information as JSON."); 121 | println!("{}", json); 122 | } else { 123 | print!("{wasm_info}"); 124 | } 125 | } 126 | SubCommand::Shrink { .. } => ic_wasm::shrink::shrink(&mut m), 127 | #[cfg(feature = "wasm-opt")] 128 | SubCommand::Optimize { 129 | level, 130 | inline_functions_with_loops, 131 | always_inline_max_function_size, 132 | .. 133 | } => ic_wasm::optimize::optimize( 134 | &mut m, 135 | level, 136 | *inline_functions_with_loops, 137 | always_inline_max_function_size, 138 | keep_name_section, 139 | )?, 140 | SubCommand::Instrument { 141 | trace_only, 142 | start_page, 143 | page_limit, 144 | } => { 145 | use ic_wasm::instrumentation::{instrument, Config}; 146 | let config = Config { 147 | trace_only_funcs: trace_only.clone().unwrap_or(vec![]), 148 | start_address: start_page.map(|page| i64::from(page) * 65536), 149 | page_limit: *page_limit, 150 | }; 151 | instrument(&mut m, config).map_err(|e| anyhow::anyhow!("{e}"))?; 152 | } 153 | SubCommand::Resource { 154 | remove_cycles_transfer, 155 | limit_heap_memory_page, 156 | limit_stable_memory_page, 157 | playground_backend_redirect, 158 | } => { 159 | use ic_wasm::limit_resource::{limit_resource, Config}; 160 | let config = Config { 161 | remove_cycles_add: *remove_cycles_transfer, 162 | limit_heap_memory_page: *limit_heap_memory_page, 163 | limit_stable_memory_page: *limit_stable_memory_page, 164 | playground_canister_id: *playground_backend_redirect, 165 | }; 166 | limit_resource(&mut m, &config) 167 | } 168 | SubCommand::Metadata { 169 | name, 170 | data, 171 | file, 172 | visibility, 173 | keep_name_section: _, 174 | } => { 175 | use ic_wasm::metadata::*; 176 | if let Some(name) = name { 177 | let visibility = match visibility.as_str() { 178 | "public" => Kind::Public, 179 | "private" => Kind::Private, 180 | _ => unreachable!(), 181 | }; 182 | let data = match (data, file) { 183 | (Some(data), None) => data.as_bytes().to_vec(), 184 | (None, Some(path)) => std::fs::read(path)?, 185 | (None, None) => { 186 | let data = get_metadata(&m, name); 187 | if let Some(data) = data { 188 | println!("{}", String::from_utf8_lossy(&data)); 189 | } else { 190 | println!("Cannot find metadata {name}"); 191 | } 192 | return Ok(()); 193 | } 194 | (_, _) => unreachable!(), 195 | }; 196 | add_metadata(&mut m, visibility, name, data) 197 | } else { 198 | let names = list_metadata(&m); 199 | for name in names.iter() { 200 | println!("{name}"); 201 | } 202 | return Ok(()); 203 | } 204 | } 205 | }; 206 | // validate new module 207 | let module_bytes = m.emit_wasm(); 208 | let mut validator = make_validator_with_features(); 209 | if let Err(e) = validator.validate_all(&module_bytes) { 210 | println!("WARNING: The output of ic-wasm failed to validate. Please report this via github issue or on https://forum.dfinity.org/"); 211 | eprintln!("{e}"); 212 | } 213 | if let Some(output) = opts.output { 214 | std::fs::write(output, module_bytes).expect("failed to write wasm module"); 215 | } 216 | Ok(()) 217 | } 218 | -------------------------------------------------------------------------------- /src/info.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::io::Write; 3 | use walrus::{ExportItem, Module}; 4 | 5 | #[cfg(feature = "serde")] 6 | use serde::{Deserialize, Serialize}; 7 | 8 | use crate::{utils::*, Error}; 9 | 10 | /// External information about a Wasm, such as API methods. 11 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 12 | pub struct WasmInfo { 13 | language: LanguageSpecificInfo, 14 | number_of_types: usize, 15 | number_of_globals: usize, 16 | number_of_data_sections: usize, 17 | size_of_data_sections: usize, 18 | number_of_functions: usize, 19 | number_of_callbacks: usize, 20 | start_function: Option, 21 | exported_methods: Vec, 22 | imported_ic0_system_api: Vec, 23 | custom_sections: Vec, 24 | } 25 | 26 | /// External information that is specific to one language 27 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 28 | pub enum LanguageSpecificInfo { 29 | Motoko { 30 | embedded_wasm: Vec<(String, WasmInfo)>, 31 | }, 32 | Unknown, 33 | } 34 | 35 | /// Information about an exported method. 36 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 37 | pub struct ExportedMethodInfo { 38 | name: String, 39 | internal_name: String, 40 | } 41 | 42 | /// Statistics about a custom section. 43 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 44 | pub struct CustomSectionInfo { 45 | name: String, 46 | size: usize, 47 | } 48 | 49 | impl From<&Module> for WasmInfo { 50 | fn from(m: &Module) -> WasmInfo { 51 | let (number_of_data_sections, size_of_data_sections) = m 52 | .data 53 | .iter() 54 | .fold((0, 0), |(count, size), d| (count + 1, size + d.value.len())); 55 | 56 | WasmInfo { 57 | language: LanguageSpecificInfo::from(m), 58 | number_of_types: m.types.iter().count(), 59 | number_of_globals: m.globals.iter().count(), 60 | number_of_data_sections, 61 | size_of_data_sections, 62 | number_of_functions: m.funcs.iter().count(), 63 | number_of_callbacks: m.elements.iter().count(), 64 | start_function: m.start.map(|id| get_func_name(m, id)), 65 | exported_methods: m 66 | .exports 67 | .iter() 68 | .filter_map(|e| match e.item { 69 | ExportItem::Function(id) => Some(ExportedMethodInfo { 70 | name: e.name.clone(), 71 | internal_name: get_func_name(m, id), 72 | }), 73 | _ => None, 74 | }) 75 | .collect(), 76 | imported_ic0_system_api: m 77 | .imports 78 | .iter() 79 | .filter(|i| i.module == "ic0") 80 | .map(|i| i.name.clone()) 81 | .collect(), 82 | custom_sections: m 83 | .customs 84 | .iter() 85 | .map(|(_, s)| CustomSectionInfo { 86 | name: s.name().to_string(), 87 | size: s.data(&Default::default()).len(), 88 | }) 89 | .collect(), 90 | } 91 | } 92 | } 93 | 94 | impl From<&Module> for LanguageSpecificInfo { 95 | fn from(m: &Module) -> LanguageSpecificInfo { 96 | if is_motoko_canister(m) { 97 | let mut embedded_wasm = Vec::new(); 98 | for (data_id, embedded_module) in get_motoko_wasm_data_sections(m) { 99 | embedded_wasm.push((format!("{:?}", data_id), WasmInfo::from(&embedded_module))); 100 | } 101 | return LanguageSpecificInfo::Motoko { embedded_wasm }; 102 | } 103 | LanguageSpecificInfo::Unknown 104 | } 105 | } 106 | 107 | impl fmt::Display for WasmInfo { 108 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 109 | write!(f, "{}", self.language)?; 110 | writeln!(f, "Number of types: {}", self.number_of_types)?; 111 | writeln!(f, "Number of globals: {}", self.number_of_globals)?; 112 | writeln!(f)?; 113 | writeln!( 114 | f, 115 | "Number of data sections: {}", 116 | self.number_of_data_sections 117 | )?; 118 | writeln!( 119 | f, 120 | "Size of data sections: {} bytes", 121 | self.size_of_data_sections 122 | )?; 123 | writeln!(f)?; 124 | writeln!(f, "Number of functions: {}", self.number_of_functions)?; 125 | writeln!(f, "Number of callbacks: {}", self.number_of_callbacks)?; 126 | writeln!(f, "Start function: {:?}", self.start_function)?; 127 | let exports: Vec<_> = self 128 | .exported_methods 129 | .iter() 130 | .map( 131 | |ExportedMethodInfo { 132 | name, 133 | internal_name, 134 | }| { 135 | if name == internal_name { 136 | internal_name.clone() 137 | } else { 138 | format!("{name} ({internal_name})") 139 | } 140 | }, 141 | ) 142 | .collect(); 143 | writeln!(f, "Exported methods: {exports:#?}")?; 144 | writeln!(f)?; 145 | writeln!( 146 | f, 147 | "Imported IC0 System API: {:#?}", 148 | self.imported_ic0_system_api 149 | )?; 150 | writeln!(f)?; 151 | let customs: Vec<_> = self 152 | .custom_sections 153 | .iter() 154 | .map(|section_info| format!("{} ({} bytes)", section_info.name, section_info.size)) 155 | .collect(); 156 | writeln!(f, "Custom sections with size: {customs:#?}")?; 157 | Ok(()) 158 | } 159 | } 160 | 161 | impl fmt::Display for LanguageSpecificInfo { 162 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 163 | match self { 164 | LanguageSpecificInfo::Motoko { embedded_wasm } => { 165 | writeln!(f, "This is a Motoko canister")?; 166 | for (_, wasm_info) in embedded_wasm { 167 | writeln!(f, "--- Start decoding an embedded Wasm ---")?; 168 | write!(f, "{}", wasm_info)?; 169 | writeln!(f, "--- End of decoding ---")?; 170 | } 171 | writeln!(f) 172 | } 173 | LanguageSpecificInfo::Unknown => Ok(()), 174 | } 175 | } 176 | } 177 | 178 | /// Print general summary of the Wasm module 179 | pub fn info(m: &Module, output: &mut dyn Write) -> Result<(), Error> { 180 | write!(output, "{}", WasmInfo::from(m))?; 181 | Ok(()) 182 | } 183 | -------------------------------------------------------------------------------- /src/instrumentation.rs: -------------------------------------------------------------------------------- 1 | use walrus::ir::*; 2 | use walrus::*; 3 | 4 | use crate::utils::*; 5 | use std::collections::HashSet; 6 | 7 | const METADATA_SIZE: i32 = 24; 8 | const DEFAULT_PAGE_LIMIT: i32 = 16 * 256; // 256M 9 | const LOG_ITEM_SIZE: i32 = 12; 10 | const MAX_ITEMS_PER_QUERY: i32 = 174758; // (2M - 40) / LOG_ITEM_SIZE; 11 | 12 | struct InjectionPoint { 13 | position: usize, 14 | cost: i64, 15 | kind: InjectionKind, 16 | } 17 | impl InjectionPoint { 18 | fn new() -> Self { 19 | InjectionPoint { 20 | position: 0, 21 | cost: 0, 22 | kind: InjectionKind::Static, 23 | } 24 | } 25 | } 26 | 27 | struct Variables { 28 | total_counter: GlobalId, 29 | log_size: GlobalId, 30 | page_size: GlobalId, 31 | is_init: GlobalId, 32 | is_entry: GlobalId, 33 | dynamic_counter_func: FunctionId, 34 | dynamic_counter64_func: FunctionId, 35 | } 36 | 37 | pub struct Config { 38 | pub trace_only_funcs: Vec, 39 | pub start_address: Option, 40 | pub page_limit: Option, 41 | } 42 | impl Config { 43 | pub fn is_preallocated(&self) -> bool { 44 | self.start_address.is_some() 45 | } 46 | pub fn log_start_address(&self) -> i64 { 47 | self.start_address.unwrap_or(0) + METADATA_SIZE as i64 48 | } 49 | pub fn metadata_start_address(&self) -> i64 { 50 | self.start_address.unwrap_or(0) 51 | } 52 | pub fn page_limit(&self) -> i64 { 53 | i64::from( 54 | self.page_limit 55 | .map(|x| x - 1) 56 | .unwrap_or(DEFAULT_PAGE_LIMIT - 1), 57 | ) // minus 1 because of metadata 58 | } 59 | } 60 | 61 | /// When trace_only_funcs is not empty, counting and tracing is only enabled for those listed functions per update call. 62 | /// TODO: doesn't handle recursive entry functions. Need to create a wrapper for the recursive entry function. 63 | pub fn instrument(m: &mut Module, config: Config) -> Result<(), String> { 64 | let mut trace_only_ids = HashSet::new(); 65 | for name in config.trace_only_funcs.iter() { 66 | let id = match m.funcs.by_name(name) { 67 | Some(id) => id, 68 | None => return Err(format!("func \"{name}\" not found")), 69 | }; 70 | trace_only_ids.insert(id); 71 | } 72 | let is_partial_tracing = !trace_only_ids.is_empty(); 73 | let func_cost = FunctionCost::new(m); 74 | let total_counter = 75 | m.globals 76 | .add_local(ValType::I64, true, false, ConstExpr::Value(Value::I64(0))); 77 | let log_size = m 78 | .globals 79 | .add_local(ValType::I32, true, false, ConstExpr::Value(Value::I32(0))); 80 | let page_size = m 81 | .globals 82 | .add_local(ValType::I32, true, false, ConstExpr::Value(Value::I32(0))); 83 | let is_init = m 84 | .globals 85 | .add_local(ValType::I32, true, false, ConstExpr::Value(Value::I32(1))); 86 | let is_entry = m 87 | .globals 88 | .add_local(ValType::I32, true, false, ConstExpr::Value(Value::I32(0))); 89 | let opt_init = if is_partial_tracing { 90 | Some(is_init) 91 | } else { 92 | None 93 | }; 94 | let dynamic_counter_func = make_dynamic_counter(m, total_counter, &opt_init); 95 | let dynamic_counter64_func = make_dynamic_counter64(m, total_counter, &opt_init); 96 | let vars = Variables { 97 | total_counter, 98 | log_size, 99 | is_init, 100 | is_entry, 101 | dynamic_counter_func, 102 | dynamic_counter64_func, 103 | page_size, 104 | }; 105 | 106 | for (id, func) in m.funcs.iter_local_mut() { 107 | if id != dynamic_counter_func && id != dynamic_counter64_func { 108 | inject_metering( 109 | func, 110 | func.entry_block(), 111 | &vars, 112 | &func_cost, 113 | is_partial_tracing, 114 | ); 115 | } 116 | } 117 | let writer = make_stable_writer(m, &vars, &config); 118 | let printer = make_printer(m, &vars, writer); 119 | for (id, func) in m.funcs.iter_local_mut() { 120 | if id != printer 121 | && id != writer 122 | && id != dynamic_counter_func 123 | && id != dynamic_counter64_func 124 | { 125 | let is_partial_tracing = trace_only_ids.contains(&id); 126 | inject_profiling_prints(&m.types, printer, id, func, is_partial_tracing, &vars); 127 | } 128 | } 129 | if !is_partial_tracing { 130 | //inject_start(m, vars.is_init); 131 | inject_init(m, vars.is_init); 132 | } 133 | // Persist globals 134 | inject_pre_upgrade(m, &vars, &config); 135 | inject_post_upgrade(m, &vars, &config); 136 | 137 | inject_canister_methods(m, &vars); 138 | let leb = make_leb128_encoder(m); 139 | make_stable_getter(m, &vars, leb, &config); 140 | make_getter(m, &vars); 141 | make_toggle_func(m, "__toggle_tracing", vars.is_init); 142 | make_toggle_func(m, "__toggle_entry", vars.is_entry); 143 | let name = make_name_section(m); 144 | m.customs.add(name); 145 | Ok(()) 146 | } 147 | 148 | fn inject_metering( 149 | func: &mut LocalFunction, 150 | start: InstrSeqId, 151 | vars: &Variables, 152 | func_cost: &FunctionCost, 153 | is_partial_tracing: bool, 154 | ) { 155 | use InjectionKind::*; 156 | let mut stack = vec![start]; 157 | while let Some(seq_id) = stack.pop() { 158 | let seq = func.block(seq_id); 159 | // Finding injection points 160 | let mut injection_points = vec![]; 161 | let mut curr = InjectionPoint::new(); 162 | // each function has at least a unit cost 163 | if seq_id == start { 164 | curr.cost += 1; 165 | } 166 | for (pos, (instr, _)) in seq.instrs.iter().enumerate() { 167 | curr.position = pos; 168 | match instr { 169 | Instr::Block(Block { seq }) | Instr::Loop(Loop { seq }) => { 170 | match func.block(*seq).ty { 171 | InstrSeqType::Simple(Some(_)) => curr.cost += instr_cost(instr), 172 | InstrSeqType::Simple(None) => (), 173 | InstrSeqType::MultiValue(_) => unreachable!("Multivalue not supported"), 174 | } 175 | stack.push(*seq); 176 | injection_points.push(curr); 177 | curr = InjectionPoint::new(); 178 | } 179 | Instr::IfElse(IfElse { 180 | consequent, 181 | alternative, 182 | }) => { 183 | curr.cost += instr_cost(instr); 184 | stack.push(*consequent); 185 | stack.push(*alternative); 186 | injection_points.push(curr); 187 | curr = InjectionPoint::new(); 188 | } 189 | Instr::Br(_) | Instr::BrIf(_) | Instr::BrTable(_) => { 190 | // br always points to a block, so we don't need to push the br block to stack for traversal 191 | curr.cost += instr_cost(instr); 192 | injection_points.push(curr); 193 | curr = InjectionPoint::new(); 194 | } 195 | Instr::Return(_) | Instr::Unreachable(_) => { 196 | curr.cost += instr_cost(instr); 197 | injection_points.push(curr); 198 | curr = InjectionPoint::new(); 199 | } 200 | Instr::Call(Call { func }) => { 201 | curr.cost += instr_cost(instr); 202 | match func_cost.get_cost(*func) { 203 | Some((cost, InjectionKind::Static)) => curr.cost += cost, 204 | Some((cost, kind @ InjectionKind::Dynamic)) 205 | | Some((cost, kind @ InjectionKind::Dynamic64)) => { 206 | curr.cost += cost; 207 | let dynamic = InjectionPoint { 208 | position: pos, 209 | cost: 0, 210 | kind, 211 | }; 212 | injection_points.push(dynamic); 213 | } 214 | None => {} 215 | } 216 | } 217 | Instr::MemoryFill(_) 218 | | Instr::MemoryCopy(_) 219 | | Instr::MemoryInit(_) 220 | | Instr::TableCopy(_) 221 | | Instr::TableInit(_) => { 222 | curr.cost += instr_cost(instr); 223 | let dynamic = InjectionPoint { 224 | position: pos, 225 | cost: 0, 226 | kind: InjectionKind::Dynamic, 227 | }; 228 | injection_points.push(dynamic); 229 | } 230 | _ => { 231 | curr.cost += instr_cost(instr); 232 | } 233 | } 234 | } 235 | injection_points.push(curr); 236 | // Reconstruct instructions 237 | let injection_points = injection_points 238 | .iter() 239 | .filter(|point| point.cost > 0 || point.kind != Static); 240 | let mut builder = func.builder_mut().instr_seq(seq_id); 241 | let original = builder.instrs_mut(); 242 | let mut instrs = vec![]; 243 | let mut last_injection_position = 0; 244 | for point in injection_points { 245 | instrs.extend_from_slice(&original[last_injection_position..point.position]); 246 | // injection happens one instruction before the injection_points, so the cost contains 247 | // the control flow instruction. 248 | match point.kind { 249 | Static => { 250 | #[rustfmt::skip] 251 | instrs.extend_from_slice(&[ 252 | (GlobalGet { global: vars.total_counter }.into(), Default::default()), 253 | (Const { value: Value::I64(point.cost) }.into(), Default::default()), 254 | ]); 255 | if is_partial_tracing { 256 | #[rustfmt::skip] 257 | instrs.extend_from_slice(&[ 258 | (GlobalGet { global: vars.is_init }.into(), Default::default()), 259 | (Const { value: Value::I32(1) }.into(), Default::default()), 260 | (Binop { op: BinaryOp::I32Xor }.into(), Default::default()), 261 | (Unop { op: UnaryOp::I64ExtendUI32 }.into(), Default::default()), 262 | (Binop { op: BinaryOp::I64Mul }.into(), Default::default()), 263 | ]); 264 | } 265 | #[rustfmt::skip] 266 | instrs.extend_from_slice(&[ 267 | (Binop { op: BinaryOp::I64Add }.into(), Default::default()), 268 | (GlobalSet { global: vars.total_counter }.into(), Default::default()), 269 | ]); 270 | } 271 | Dynamic => { 272 | // Assume top of the stack is the i32 size parameter 273 | #[rustfmt::skip] 274 | instrs.push((Call { func: vars.dynamic_counter_func }.into(), Default::default())); 275 | } 276 | Dynamic64 => { 277 | #[rustfmt::skip] 278 | instrs.push((Call { func: vars.dynamic_counter64_func }.into(), Default::default())); 279 | } 280 | }; 281 | last_injection_position = point.position; 282 | } 283 | instrs.extend_from_slice(&original[last_injection_position..]); 284 | *original = instrs; 285 | } 286 | } 287 | 288 | fn inject_profiling_prints( 289 | types: &ModuleTypes, 290 | printer: FunctionId, 291 | id: FunctionId, 292 | func: &mut LocalFunction, 293 | is_partial_tracing: bool, 294 | vars: &Variables, 295 | ) { 296 | // Put the original function body inside a block, so that if the code 297 | // use br_if/br_table to exit the function, we can still output the exit signal. 298 | let start_id = func.entry_block(); 299 | let original_block = func.block_mut(start_id); 300 | let start_instrs = original_block.instrs.split_off(0); 301 | let start_ty = match original_block.ty { 302 | InstrSeqType::MultiValue(id) => { 303 | let valtypes = types.results(id); 304 | InstrSeqType::Simple(match valtypes.len() { 305 | 0 => None, 306 | 1 => Some(valtypes[0]), 307 | _ => unreachable!("Multivalue return not supported"), 308 | }) 309 | } 310 | // top-level block is using the function signature 311 | InstrSeqType::Simple(_) => unreachable!(), 312 | }; 313 | let mut inner_start = func.builder_mut().dangling_instr_seq(start_ty); 314 | *(inner_start.instrs_mut()) = start_instrs; 315 | let inner_start_id = inner_start.id(); 316 | let mut start_builder = func.builder_mut().func_body(); 317 | if is_partial_tracing { 318 | start_builder.i32_const(0).global_set(vars.is_init); 319 | } 320 | start_builder 321 | .i32_const(id.index() as i32) 322 | .call(printer) 323 | .instr(Block { 324 | seq: inner_start_id, 325 | }) 326 | // TOOD fix when id == 0 327 | .i32_const(-(id.index() as i32)) 328 | .call(printer); 329 | // TODO this only works for non-recursive entry function 330 | if is_partial_tracing { 331 | start_builder.i32_const(1).global_set(vars.is_init); 332 | } 333 | let mut stack = vec![inner_start_id]; 334 | while let Some(seq_id) = stack.pop() { 335 | let mut builder = func.builder_mut().instr_seq(seq_id); 336 | let original = builder.instrs_mut(); 337 | let mut instrs = vec![]; 338 | for (instr, loc) in original.iter() { 339 | match instr { 340 | Instr::Block(Block { seq }) | Instr::Loop(Loop { seq }) => { 341 | stack.push(*seq); 342 | instrs.push((instr.clone(), *loc)); 343 | } 344 | Instr::IfElse(IfElse { 345 | consequent, 346 | alternative, 347 | }) => { 348 | stack.push(*alternative); 349 | stack.push(*consequent); 350 | instrs.push((instr.clone(), *loc)); 351 | } 352 | Instr::Return(_) => { 353 | instrs.push(( 354 | Instr::Br(Br { 355 | block: inner_start_id, 356 | }), 357 | *loc, 358 | )); 359 | } 360 | // redirect br,br_if,br_table to inner seq id 361 | Instr::Br(Br { block }) if *block == start_id => { 362 | instrs.push(( 363 | Instr::Br(Br { 364 | block: inner_start_id, 365 | }), 366 | *loc, 367 | )); 368 | } 369 | Instr::BrIf(BrIf { block }) if *block == start_id => { 370 | instrs.push(( 371 | Instr::BrIf(BrIf { 372 | block: inner_start_id, 373 | }), 374 | *loc, 375 | )); 376 | } 377 | Instr::BrTable(BrTable { blocks, default }) => { 378 | let mut blocks = blocks.clone(); 379 | for i in 0..blocks.len() { 380 | if let Some(id) = blocks.get_mut(i) { 381 | if *id == start_id { 382 | *id = inner_start_id 383 | }; 384 | } 385 | } 386 | let default = if *default == start_id { 387 | inner_start_id 388 | } else { 389 | *default 390 | }; 391 | instrs.push((Instr::BrTable(BrTable { blocks, default }), *loc)); 392 | } 393 | _ => instrs.push((instr.clone(), *loc)), 394 | } 395 | } 396 | *original = instrs; 397 | } 398 | } 399 | 400 | fn make_dynamic_counter( 401 | m: &mut Module, 402 | total_counter: GlobalId, 403 | opt_init: &Option, 404 | ) -> FunctionId { 405 | let mut builder = FunctionBuilder::new(&mut m.types, &[ValType::I32], &[ValType::I32]); 406 | let size = m.locals.add(ValType::I32); 407 | let mut seq = builder.func_body(); 408 | seq.local_get(size); 409 | if let Some(is_init) = opt_init { 410 | seq.global_get(*is_init) 411 | .i32_const(1) 412 | .binop(BinaryOp::I32Xor) 413 | .binop(BinaryOp::I32Mul); 414 | } 415 | seq.unop(UnaryOp::I64ExtendUI32) 416 | .global_get(total_counter) 417 | .binop(BinaryOp::I64Add) 418 | .global_set(total_counter) 419 | .local_get(size); 420 | builder.finish(vec![size], &mut m.funcs) 421 | } 422 | fn make_dynamic_counter64( 423 | m: &mut Module, 424 | total_counter: GlobalId, 425 | opt_init: &Option, 426 | ) -> FunctionId { 427 | let mut builder = FunctionBuilder::new(&mut m.types, &[ValType::I64], &[ValType::I64]); 428 | let size = m.locals.add(ValType::I64); 429 | let mut seq = builder.func_body(); 430 | seq.local_get(size); 431 | if let Some(is_init) = opt_init { 432 | seq.global_get(*is_init) 433 | .i32_const(1) 434 | .binop(BinaryOp::I32Xor) 435 | .unop(UnaryOp::I64ExtendUI32) 436 | .binop(BinaryOp::I64Mul); 437 | } 438 | seq.global_get(total_counter) 439 | .binop(BinaryOp::I64Add) 440 | .global_set(total_counter) 441 | .local_get(size); 442 | builder.finish(vec![size], &mut m.funcs) 443 | } 444 | fn make_stable_writer(m: &mut Module, vars: &Variables, config: &Config) -> FunctionId { 445 | let writer = get_ic_func_id(m, "stable64_write"); 446 | let grow = get_ic_func_id(m, "stable64_grow"); 447 | let mut builder = FunctionBuilder::new( 448 | &mut m.types, 449 | &[ValType::I64, ValType::I64, ValType::I64], 450 | &[], 451 | ); 452 | let start_address = config.log_start_address(); 453 | let size_limit = config.page_limit() * 65536; 454 | let is_preallocated = config.is_preallocated(); 455 | let offset = m.locals.add(ValType::I64); 456 | let src = m.locals.add(ValType::I64); 457 | let size = m.locals.add(ValType::I64); 458 | builder 459 | .func_body() 460 | .local_get(offset) 461 | .local_get(size) 462 | .binop(BinaryOp::I64Add); 463 | if is_preallocated { 464 | builder.func_body().i64_const(size_limit); 465 | } else { 466 | builder 467 | .func_body() 468 | .global_get(vars.page_size) 469 | .i32_const(65536) 470 | .binop(BinaryOp::I32Mul) 471 | .i32_const(METADATA_SIZE) 472 | .binop(BinaryOp::I32Sub) 473 | // SI because it can be negative 474 | .unop(UnaryOp::I64ExtendSI32); 475 | } 476 | builder 477 | .func_body() 478 | .binop(BinaryOp::I64GtS) 479 | .if_else( 480 | None, 481 | |then| { 482 | if is_preallocated { 483 | then.return_(); 484 | } else { 485 | // This assumes user code doesn't use stable memory 486 | then.global_get(vars.page_size) 487 | .i32_const(DEFAULT_PAGE_LIMIT) 488 | .binop(BinaryOp::I32GtS) // trace > default_page_limit 489 | .if_else( 490 | None, 491 | |then| { 492 | then.return_(); 493 | }, 494 | |else_| { 495 | else_ 496 | .i64_const(1) 497 | .call(grow) 498 | .drop() 499 | .global_get(vars.page_size) 500 | .i32_const(1) 501 | .binop(BinaryOp::I32Add) 502 | .global_set(vars.page_size); 503 | }, 504 | ); 505 | } 506 | }, 507 | |_| {}, 508 | ) 509 | .i64_const(start_address) 510 | .local_get(offset) 511 | .binop(BinaryOp::I64Add) 512 | .local_get(src) 513 | .local_get(size) 514 | .call(writer) 515 | .global_get(vars.log_size) 516 | .i32_const(1) 517 | .binop(BinaryOp::I32Add) 518 | .global_set(vars.log_size); 519 | builder.finish(vec![offset, src, size], &mut m.funcs) 520 | } 521 | 522 | fn make_printer(m: &mut Module, vars: &Variables, writer: FunctionId) -> FunctionId { 523 | let memory = get_memory_id(m); 524 | let mut builder = FunctionBuilder::new(&mut m.types, &[ValType::I32], &[]); 525 | let func_id = m.locals.add(ValType::I32); 526 | let a = m.locals.add(ValType::I32); 527 | let b = m.locals.add(ValType::I64); 528 | builder.func_body().global_get(vars.is_init).if_else( 529 | None, 530 | |then| { 531 | then.return_(); 532 | }, 533 | |else_| { 534 | #[rustfmt::skip] 535 | else_ 536 | // backup memory 537 | .i32_const(0) 538 | .load(memory, LoadKind::I32 { atomic: false }, MemArg { offset: 0, align: 4}) 539 | .local_set(a) 540 | .i32_const(4) 541 | .load(memory, LoadKind::I64 { atomic: false }, MemArg { offset: 0, align: 8}) 542 | .local_set(b) 543 | // print 544 | .i32_const(0) 545 | .local_get(func_id) 546 | .store(memory, StoreKind::I32 { atomic: false }, MemArg { offset: 0, align: 4 }) 547 | .i32_const(4) 548 | .global_get(vars.total_counter) 549 | .store(memory, StoreKind::I64 { atomic: false }, MemArg { offset: 0, align: 8 }) 550 | .global_get(vars.log_size) 551 | .unop(UnaryOp::I64ExtendUI32) 552 | .i64_const(LOG_ITEM_SIZE as i64) 553 | .binop(BinaryOp::I64Mul) 554 | .i64_const(0) 555 | .i64_const(LOG_ITEM_SIZE as i64) 556 | .call(writer) 557 | // restore memory 558 | .i32_const(0) 559 | .local_get(a) 560 | .store(memory, StoreKind::I32 { atomic: false }, MemArg { offset: 0, align: 4 }) 561 | .i32_const(4) 562 | .local_get(b) 563 | .store(memory, StoreKind::I64 { atomic: false }, MemArg { offset: 0, align: 8 }); 564 | }, 565 | ); 566 | builder.finish(vec![func_id], &mut m.funcs) 567 | } 568 | /* 569 | // We can use this function once we have a system memroy for logs. 570 | // Otherwise, we cannot call stable_write in canister_init 571 | fn inject_start(m: &mut Module, is_init: GlobalId) { 572 | if let Some(id) = m.start { 573 | let mut builder = get_builder(m, id); 574 | #[rustfmt::skip] 575 | builder 576 | .instr(Const { value: Value::I32(0) }) 577 | .instr(GlobalSet { global: is_init }); 578 | } 579 | } 580 | */ 581 | fn inject_canister_methods(m: &mut Module, vars: &Variables) { 582 | let methods: Vec<_> = m 583 | .exports 584 | .iter() 585 | .filter_map(|e| match e.item { 586 | ExportItem::Function(id) 587 | if e.name != "canister_update __motoko_async_helper" 588 | && (e.name.starts_with("canister_update") 589 | || e.name.starts_with("canister_query") 590 | || e.name.starts_with("canister_composite_query") 591 | || e.name.starts_with("canister_heartbeat") 592 | // don't clear logs for timer and post_upgrade, as they are trigger by other signals 593 | //|| e.name == "canister_global_timer" 594 | //|| e.name == "canister_post_upgrade" 595 | || e.name == "canister_pre_upgrade") => 596 | { 597 | Some(id) 598 | } 599 | _ => None, 600 | }) 601 | .collect(); 602 | for id in methods.iter() { 603 | let mut builder = get_builder(m, *id); 604 | #[rustfmt::skip] 605 | inject_top( 606 | &mut builder, 607 | vec![ 608 | // log_size = is_entry ? log_size : 0 609 | GlobalGet { global: vars.is_entry }.into(), 610 | GlobalGet { global: vars.log_size }.into(), 611 | Binop { op: BinaryOp::I32Mul }.into(), 612 | GlobalSet { global: vars.log_size }.into(), 613 | ], 614 | ); 615 | } 616 | } 617 | fn inject_init(m: &mut Module, is_init: GlobalId) { 618 | let mut builder = get_or_create_export_func(m, "canister_init"); 619 | // canister_init in Motoko use stable_size to decide if there is stable memory to deserialize. 620 | // Region initialization in Motoko is also done here. 621 | // We can only enable profiling at the end of init, otherwise stable.grow breaks this check. 622 | builder.i32_const(0).global_set(is_init); 623 | } 624 | fn inject_pre_upgrade(m: &mut Module, vars: &Variables, config: &Config) { 625 | let writer = get_ic_func_id(m, "stable64_write"); 626 | let memory = get_memory_id(m); 627 | let a = m.locals.add(ValType::I64); 628 | let b = m.locals.add(ValType::I64); 629 | let c = m.locals.add(ValType::I64); 630 | let mut builder = get_or_create_export_func(m, "canister_pre_upgrade"); 631 | #[rustfmt::skip] 632 | builder 633 | // backup memory. This is not strictly needed, since it's at the end of pre-upgrade. 634 | .i32_const(0) 635 | .load(memory, LoadKind::I64 { atomic: false }, MemArg { offset: 0, align: 8}) 636 | .local_set(a) 637 | .i32_const(8) 638 | .load(memory, LoadKind::I64 { atomic: false }, MemArg { offset: 0, align: 8}) 639 | .local_set(b) 640 | .i32_const(16) 641 | .load(memory, LoadKind::I64 { atomic: false }, MemArg { offset: 0, align: 8}) 642 | .local_set(c) 643 | // persist globals 644 | .i32_const(0) 645 | .global_get(vars.total_counter) 646 | .store(memory, StoreKind::I64 { atomic: false }, MemArg { offset: 0, align: 8 }) 647 | .i32_const(8) 648 | .global_get(vars.log_size) 649 | .store(memory, StoreKind::I32 { atomic: false }, MemArg { offset: 0, align: 4 }) 650 | .i32_const(12) 651 | .global_get(vars.page_size) 652 | .store(memory, StoreKind::I32 { atomic: false }, MemArg { offset: 0, align: 4 }) 653 | .i32_const(16) 654 | .global_get(vars.is_init) 655 | .store(memory, StoreKind::I32 { atomic: false }, MemArg { offset: 0, align: 4 }) 656 | .i32_const(20) 657 | .global_get(vars.is_entry) 658 | .store(memory, StoreKind::I32 { atomic: false }, MemArg { offset: 0, align: 4 }) 659 | .i64_const(config.metadata_start_address()) 660 | .i64_const(0) 661 | .i64_const(METADATA_SIZE as i64) 662 | .call(writer) 663 | // restore memory 664 | .i32_const(0) 665 | .local_get(a) 666 | .store(memory, StoreKind::I64 { atomic: false }, MemArg { offset: 0, align: 8 }) 667 | .i32_const(8) 668 | .local_get(b) 669 | .store(memory, StoreKind::I64 { atomic: false }, MemArg { offset: 0, align: 8 }) 670 | .i32_const(16) 671 | .local_get(c) 672 | .store(memory, StoreKind::I64 { atomic: false }, MemArg { offset: 0, align: 8 }); 673 | } 674 | fn inject_post_upgrade(m: &mut Module, vars: &Variables, config: &Config) { 675 | let reader = get_ic_func_id(m, "stable64_read"); 676 | let memory = get_memory_id(m); 677 | let a = m.locals.add(ValType::I64); 678 | let b = m.locals.add(ValType::I64); 679 | let c = m.locals.add(ValType::I64); 680 | let mut builder = get_or_create_export_func(m, "canister_post_upgrade"); 681 | #[rustfmt::skip] 682 | inject_top(&mut builder, vec![ 683 | // backup 684 | Const { value: Value::I32(0) }.into(), 685 | Load { memory, kind: LoadKind::I64 { atomic: false }, arg: MemArg { offset: 0, align: 8 } }.into(), 686 | LocalSet { local: a }.into(), 687 | Const { value: Value::I32(8) }.into(), 688 | Load { memory, kind: LoadKind::I64 { atomic: false }, arg: MemArg { offset: 0, align: 8 } }.into(), 689 | LocalSet { local: b }.into(), 690 | Const { value: Value::I32(16) }.into(), 691 | Load { memory, kind: LoadKind::I64 { atomic: false }, arg: MemArg { offset: 0, align: 8 } }.into(), 692 | LocalSet { local: c }.into(), 693 | // load from stable memory 694 | Const { value: Value::I64(0) }.into(), 695 | Const { value: Value::I64(config.metadata_start_address()) }.into(), 696 | Const { value: Value::I64(METADATA_SIZE as i64) }.into(), 697 | Call { func: reader }.into(), 698 | Const { value: Value::I32(0) }.into(), 699 | Load { memory, kind: LoadKind::I64 { atomic: false }, arg: MemArg { offset: 0, align: 8 } }.into(), 700 | GlobalSet { global: vars.total_counter }.into(), 701 | Const { value: Value::I32(8) }.into(), 702 | Load { memory, kind: LoadKind::I32 { atomic: false }, arg: MemArg { offset: 0, align: 4 } }.into(), 703 | GlobalSet { global: vars.log_size }.into(), 704 | Const { value: Value::I32(12) }.into(), 705 | Load { memory, kind: LoadKind::I32 { atomic: false }, arg: MemArg { offset: 0, align: 4 } }.into(), 706 | GlobalSet { global: vars.page_size }.into(), 707 | Const { value: Value::I32(16) }.into(), 708 | Load { memory, kind: LoadKind::I32 { atomic: false }, arg: MemArg { offset: 0, align: 4 } }.into(), 709 | GlobalSet { global: vars.is_init }.into(), 710 | Const { value: Value::I32(20) }.into(), 711 | Load { memory, kind: LoadKind::I32 { atomic: false }, arg: MemArg { offset: 0, align: 4 } }.into(), 712 | GlobalSet { global: vars.is_entry }.into(), 713 | // restore 714 | Const { value: Value::I32(0) }.into(), 715 | LocalGet { local: a }.into(), 716 | Store { memory, kind: StoreKind::I64 { atomic: false }, arg: MemArg { offset: 0, align: 8 } }.into(), 717 | Const { value: Value::I32(8) }.into(), 718 | LocalGet { local: b }.into(), 719 | Store { memory, kind: StoreKind::I64 { atomic: false }, arg: MemArg { offset: 0, align: 8 } }.into(), 720 | Const { value: Value::I32(16) }.into(), 721 | LocalGet { local: c }.into(), 722 | Store { memory, kind: StoreKind::I64 { atomic: false }, arg: MemArg { offset: 0, align: 8 } }.into(), 723 | ]); 724 | } 725 | 726 | fn make_stable_getter(m: &mut Module, vars: &Variables, leb: FunctionId, config: &Config) { 727 | let memory = get_memory_id(m); 728 | let arg_size = get_ic_func_id(m, "msg_arg_data_size"); 729 | let arg_copy = get_ic_func_id(m, "msg_arg_data_copy"); 730 | let reply_data = get_ic_func_id(m, "msg_reply_data_append"); 731 | let reply = get_ic_func_id(m, "msg_reply"); 732 | let trap = get_ic_func_id(m, "trap"); 733 | let reader = get_ic_func_id(m, "stable64_read"); 734 | let idx = m.locals.add(ValType::I32); 735 | let len = m.locals.add(ValType::I32); 736 | let next_idx = m.locals.add(ValType::I32); 737 | let mut builder = FunctionBuilder::new(&mut m.types, &[], &[]); 738 | builder.name("__get_profiling".to_string()); 739 | #[rustfmt::skip] 740 | builder.func_body() 741 | // allocate 2M of heap memory, it's a query call, the system will give back the memory. 742 | .memory_size(memory) 743 | .i32_const(32) 744 | .binop(BinaryOp::I32LtU) 745 | .if_else( 746 | None, 747 | |then| { 748 | then 749 | .i32_const(32) 750 | .memory_grow(memory) 751 | .drop(); 752 | }, 753 | |_| {} 754 | ) 755 | // parse input idx 756 | .call(arg_size) 757 | .i32_const(11) 758 | .binop(BinaryOp::I32Ne) 759 | .if_else( 760 | None, 761 | |then| { 762 | then.i32_const(0) 763 | .i32_const(0) 764 | .call(trap); 765 | }, 766 | |_| {}, 767 | ) 768 | .i32_const(0) 769 | .i32_const(7) 770 | .i32_const(4) 771 | .call(arg_copy) 772 | .i32_const(0) 773 | .load(memory, LoadKind::I32 { atomic: false }, MemArg { offset: 0, align: 4}) 774 | .local_set(idx) 775 | // write header (vec { record { int32; int64 } }, opt int32) 776 | .i32_const(0) 777 | .i64_const(0x6c016d034c444944) // "DIDL036d016c" 778 | .store(memory, StoreKind::I64 { atomic: false }, MemArg { offset: 0, align: 8 }) 779 | .i32_const(8) 780 | .i64_const(0x02756e7401750002) // "02007501746e7502" 781 | .store(memory, StoreKind::I64 { atomic: false }, MemArg { offset: 0, align: 8 }) 782 | .i32_const(16) 783 | .i32_const(0x0200) // "0002" 784 | .store(memory, StoreKind::I32 { atomic: false }, MemArg { offset: 0, align: 4}) 785 | .i32_const(0) 786 | .i32_const(18) 787 | .call(reply_data) 788 | // if log_size - idx > MAX_ITEMS_PER_QUERY 789 | .global_get(vars.log_size) 790 | .local_get(idx) 791 | .binop(BinaryOp::I32Sub) 792 | .local_tee(len) 793 | .i32_const(MAX_ITEMS_PER_QUERY) 794 | .binop(BinaryOp::I32GtU) 795 | .if_else( 796 | None, 797 | |then| { 798 | then.i32_const(MAX_ITEMS_PER_QUERY) 799 | .local_set(len) 800 | .local_get(idx) 801 | .i32_const(MAX_ITEMS_PER_QUERY) 802 | .binop(BinaryOp::I32Add) 803 | .local_set(next_idx); 804 | }, 805 | |else_| { 806 | else_.i32_const(0) 807 | .local_set(next_idx); 808 | }, 809 | ) 810 | .local_get(len) 811 | .call(leb) 812 | .i32_const(0) 813 | .i32_const(5) 814 | .call(reply_data) 815 | // read stable logs 816 | .i64_const(0) 817 | .i64_const(config.log_start_address()) 818 | .local_get(idx) 819 | .unop(UnaryOp::I64ExtendUI32) 820 | .i64_const(LOG_ITEM_SIZE as i64) 821 | .binop(BinaryOp::I64Mul) 822 | .binop(BinaryOp::I64Add) 823 | .local_get(len) 824 | .unop(UnaryOp::I64ExtendUI32) 825 | .i64_const(LOG_ITEM_SIZE as i64) 826 | .binop(BinaryOp::I64Mul) 827 | .call(reader) 828 | .i32_const(0) 829 | .local_get(len) 830 | .i32_const(LOG_ITEM_SIZE) 831 | .binop(BinaryOp::I32Mul) 832 | .call(reply_data) 833 | // opt next idx 834 | .local_get(next_idx) 835 | .unop(UnaryOp::I32Eqz) 836 | .if_else( 837 | None, 838 | |then| { 839 | then.i32_const(0) 840 | .i32_const(0) 841 | .store(memory, StoreKind::I32 { atomic: false }, MemArg { offset: 0, align: 4}) 842 | .i32_const(0) 843 | .i32_const(1) 844 | .call(reply_data); 845 | }, 846 | |else_| { 847 | else_.i32_const(0) 848 | .i32_const(1) 849 | .store(memory, StoreKind::I32 { atomic: false }, MemArg { offset: 0, align: 1}) 850 | .i32_const(1) 851 | .local_get(next_idx) 852 | .store(memory, StoreKind::I32 { atomic: false }, MemArg { offset: 0, align: 4}) 853 | .i32_const(0) 854 | .i32_const(5) 855 | .call(reply_data); 856 | }, 857 | ) 858 | .call(reply); 859 | let getter = builder.finish(vec![], &mut m.funcs); 860 | m.exports.add("canister_query __get_profiling", getter); 861 | } 862 | // Generate i32 to 5-byte LEB128 encoding at memory address 0..5 863 | fn make_leb128_encoder(m: &mut Module) -> FunctionId { 864 | let memory = get_memory_id(m); 865 | let mut builder = FunctionBuilder::new(&mut m.types, &[ValType::I32], &[]); 866 | let value = m.locals.add(ValType::I32); 867 | let mut instrs = builder.func_body(); 868 | for i in 0..5 { 869 | instrs 870 | .i32_const(i) 871 | .local_get(value) 872 | .i32_const(0x7f) 873 | .binop(BinaryOp::I32And); 874 | if i < 4 { 875 | instrs.i32_const(0x80).binop(BinaryOp::I32Or); 876 | } 877 | #[rustfmt::skip] 878 | instrs 879 | .store(memory, StoreKind::I32_8 { atomic: false }, MemArg { offset: 0, align: 1 }) 880 | .local_get(value) 881 | .i32_const(7) 882 | .binop(BinaryOp::I32ShrU) 883 | .local_set(value); 884 | } 885 | builder.finish(vec![value], &mut m.funcs) 886 | } 887 | fn make_name_section(m: &Module) -> RawCustomSection { 888 | use candid::Encode; 889 | let name: Vec<_> = m 890 | .funcs 891 | .iter() 892 | .filter_map(|f| { 893 | if matches!(f.kind, FunctionKind::Local(_)) { 894 | use rustc_demangle::demangle; 895 | let name = f.name.as_ref()?; 896 | let demangled = format!("{:#}", demangle(name)); 897 | Some((f.id().index() as u16, demangled)) 898 | } else { 899 | None 900 | } 901 | }) 902 | .collect(); 903 | let data = Encode!(&name).unwrap(); 904 | RawCustomSection { 905 | name: "icp:public name".to_string(), 906 | data, 907 | } 908 | } 909 | 910 | fn make_getter(m: &mut Module, vars: &Variables) { 911 | let memory = get_memory_id(m); 912 | let reply_data = get_ic_func_id(m, "msg_reply_data_append"); 913 | let reply = get_ic_func_id(m, "msg_reply"); 914 | let mut getter = FunctionBuilder::new(&mut m.types, &[], &[]); 915 | getter.name("__get_cycles".to_string()); 916 | #[rustfmt::skip] 917 | getter 918 | .func_body() 919 | // It's a query call, so we can arbitrarily change the memory without restoring them afterwards. 920 | .i32_const(0) 921 | .i64_const(0x007401004c444944) // "DIDL000174xx" in little endian 922 | .store(memory, StoreKind::I64 { atomic: false }, MemArg { offset: 0, align: 8 }) 923 | .i32_const(7) 924 | .global_get(vars.total_counter) 925 | .store(memory, StoreKind::I64 { atomic: false }, MemArg { offset: 0, align: 8 }) 926 | .i32_const(0) 927 | .i32_const(15) 928 | .call(reply_data) 929 | .call(reply); 930 | let getter = getter.finish(vec![], &mut m.funcs); 931 | m.exports.add("canister_query __get_cycles", getter); 932 | } 933 | fn make_toggle_func(m: &mut Module, name: &str, var: GlobalId) { 934 | let memory = get_memory_id(m); 935 | let reply_data = get_ic_func_id(m, "msg_reply_data_append"); 936 | let reply = get_ic_func_id(m, "msg_reply"); 937 | let tmp = m.locals.add(ValType::I64); 938 | let mut builder = FunctionBuilder::new(&mut m.types, &[], &[]); 939 | builder.name(name.to_string()); 940 | #[rustfmt::skip] 941 | builder 942 | .func_body() 943 | .global_get(var) 944 | .i32_const(1) 945 | .binop(BinaryOp::I32Xor) 946 | .global_set(var) 947 | .i32_const(0) 948 | .load(memory, LoadKind::I64 { atomic: false }, MemArg { offset: 0, align: 8 }) 949 | .local_set(tmp) 950 | .i32_const(0) 951 | .i64_const(0x4c444944) // "DIDL0000xxxx" 952 | .store(memory, StoreKind::I64 { atomic: false }, MemArg { offset: 0, align: 8 }) 953 | .i32_const(0) 954 | .i32_const(6) 955 | .call(reply_data) 956 | .i32_const(0) 957 | .local_get(tmp) 958 | .store(memory, StoreKind::I64 { atomic: false }, MemArg { offset: 0, align: 8 }) 959 | .call(reply); 960 | let id = builder.finish(vec![], &mut m.funcs); 961 | m.exports.add(&format!("canister_update {name}"), id); 962 | } 963 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod info; 2 | pub mod instrumentation; 3 | pub mod limit_resource; 4 | pub mod metadata; 5 | #[cfg(feature = "wasm-opt")] 6 | pub mod optimize; 7 | pub mod shrink; 8 | pub mod utils; 9 | 10 | #[derive(Debug, thiserror::Error)] 11 | pub enum Error { 12 | #[error("Failed on IO.")] 13 | IO(#[from] std::io::Error), 14 | 15 | #[error("Could not parse the data as WASM module. {0}")] 16 | WasmParse(String), 17 | 18 | #[error("{0}")] 19 | MetadataNotFound(String), 20 | } 21 | -------------------------------------------------------------------------------- /src/limit_resource.rs: -------------------------------------------------------------------------------- 1 | use candid::Principal; 2 | use std::collections::{HashMap, HashSet}; 3 | use walrus::ir::*; 4 | use walrus::*; 5 | 6 | pub struct Config { 7 | pub remove_cycles_add: bool, 8 | pub limit_stable_memory_page: Option, 9 | pub limit_heap_memory_page: Option, 10 | pub playground_canister_id: Option, 11 | } 12 | 13 | struct Replacer(HashMap); 14 | impl VisitorMut for Replacer { 15 | fn visit_instr_mut(&mut self, instr: &mut Instr, _: &mut InstrLocId) { 16 | if let Instr::Call(walrus::ir::Call { func }) = instr { 17 | if let Some(new_id) = self.0.get(func) { 18 | *instr = Call { func: *new_id }.into(); 19 | } 20 | } 21 | } 22 | } 23 | impl Replacer { 24 | fn new() -> Self { 25 | Self(HashMap::new()) 26 | } 27 | fn add(&mut self, old: FunctionId, new: FunctionId) { 28 | self.0.insert(old, new); 29 | } 30 | } 31 | 32 | pub fn limit_resource(m: &mut Module, config: &Config) { 33 | let wasm64 = match m.memories.len() { 34 | 0 => false, // Wasm module declares no memory is treated as wasm32 35 | 1 => m.memories.get(m.get_memory_id().unwrap()).memory64, 36 | _ => panic!("The Canister Wasm module should have at most one memory"), 37 | }; 38 | 39 | if let Some(limit) = config.limit_heap_memory_page { 40 | limit_heap_memory(m, limit); 41 | } 42 | 43 | let mut replacer = Replacer::new(); 44 | 45 | if config.remove_cycles_add { 46 | make_cycles_add(m, &mut replacer, wasm64); 47 | make_cycles_add128(m, &mut replacer); 48 | make_cycles_burn128(m, &mut replacer, wasm64); 49 | } 50 | 51 | if let Some(limit) = config.limit_stable_memory_page { 52 | make_stable_grow(m, &mut replacer, wasm64, limit as i32); 53 | make_stable64_grow(m, &mut replacer, limit as i64); 54 | } 55 | 56 | if let Some(redirect_id) = config.playground_canister_id { 57 | make_redirect_call_new(m, &mut replacer, wasm64, redirect_id); 58 | } 59 | 60 | let new_ids = replacer.0.values().cloned().collect::>(); 61 | m.funcs.iter_local_mut().for_each(|(id, func)| { 62 | if new_ids.contains(&id) { 63 | return; 64 | } 65 | dfs_pre_order_mut(&mut replacer, func, func.entry_block()); 66 | }); 67 | } 68 | 69 | fn limit_heap_memory(m: &mut Module, limit: u32) { 70 | if let Ok(memory_id) = m.get_memory_id() { 71 | let memory = m.memories.get_mut(memory_id); 72 | let limit = limit as u64; 73 | if memory.initial > limit { 74 | // If memory.initial is greater than the provided limit, it is 75 | // possible there is an active data segment with an offset in the 76 | // range [limit, memory.initial]. 77 | // 78 | // In that case, we don't restrict the heap memory limit as it could 79 | // have undefined behaviour. 80 | 81 | if m.data 82 | .iter() 83 | .filter_map(|data| { 84 | match data.kind { 85 | DataKind::Passive => None, 86 | DataKind::Active { 87 | memory: data_memory_id, 88 | offset, 89 | } => { 90 | if data_memory_id == memory_id { 91 | match offset { 92 | ConstExpr::Value(Value::I32(offset)) => Some(offset as u64), 93 | ConstExpr::Value(Value::I64(offset)) => Some(offset as u64), 94 | _ => { 95 | // It wouldn't pass IC wasm validation 96 | None 97 | } 98 | } 99 | } else { 100 | None 101 | } 102 | } 103 | } 104 | }) 105 | .all(|offset| offset < limit * 65536) 106 | { 107 | memory.initial = limit; 108 | } else { 109 | panic!("Unable to restrict Wasm heap memory to {} pages", limit); 110 | } 111 | } 112 | memory.maximum = Some(limit); 113 | } 114 | } 115 | 116 | fn make_cycles_add(m: &mut Module, replacer: &mut Replacer, wasm64: bool) { 117 | if let Some(old_cycles_add) = get_ic_func_id(m, "call_cycles_add") { 118 | if wasm64 { 119 | panic!("Wasm64 module should not call `call_cycles_add`"); 120 | } 121 | let mut builder = FunctionBuilder::new(&mut m.types, &[ValType::I64], &[]); 122 | let amount = m.locals.add(ValType::I64); 123 | builder.func_body().local_get(amount).drop(); 124 | let new_cycles_add = builder.finish(vec![amount], &mut m.funcs); 125 | replacer.add(old_cycles_add, new_cycles_add); 126 | } 127 | } 128 | 129 | fn make_cycles_add128(m: &mut Module, replacer: &mut Replacer) { 130 | if let Some(old_cycles_add128) = get_ic_func_id(m, "call_cycles_add128") { 131 | let mut builder = FunctionBuilder::new(&mut m.types, &[ValType::I64, ValType::I64], &[]); 132 | let high = m.locals.add(ValType::I64); 133 | let low = m.locals.add(ValType::I64); 134 | builder 135 | .func_body() 136 | .local_get(high) 137 | .local_get(low) 138 | .drop() 139 | .drop(); 140 | let new_cycles_add128 = builder.finish(vec![high, low], &mut m.funcs); 141 | replacer.add(old_cycles_add128, new_cycles_add128); 142 | } 143 | } 144 | 145 | fn make_cycles_burn128(m: &mut Module, replacer: &mut Replacer, wasm64: bool) { 146 | if let Some(older_cycles_burn128) = get_ic_func_id(m, "call_cycles_burn128") { 147 | let dst_type = match wasm64 { 148 | true => ValType::I64, 149 | false => ValType::I32, 150 | }; 151 | let mut builder = 152 | FunctionBuilder::new(&mut m.types, &[ValType::I64, ValType::I64, dst_type], &[]); 153 | let high = m.locals.add(ValType::I64); 154 | let low = m.locals.add(ValType::I64); 155 | let dst = m.locals.add(dst_type); 156 | builder 157 | .func_body() 158 | .local_get(high) 159 | .local_get(low) 160 | .local_get(dst) 161 | .drop() 162 | .drop() 163 | .drop(); 164 | let new_cycles_burn128 = builder.finish(vec![high, low, dst], &mut m.funcs); 165 | replacer.add(older_cycles_burn128, new_cycles_burn128); 166 | } 167 | } 168 | 169 | fn make_stable_grow(m: &mut Module, replacer: &mut Replacer, wasm64: bool, limit: i32) { 170 | if let Some(old_stable_grow) = get_ic_func_id(m, "stable_grow") { 171 | if wasm64 { 172 | panic!("Wasm64 module should not call `stable_grow`"); 173 | } 174 | // stable_size is added to import if it wasn't imported 175 | let stable_size = get_ic_func_id(m, "stable_size").unwrap(); 176 | let mut builder = FunctionBuilder::new(&mut m.types, &[ValType::I32], &[ValType::I32]); 177 | let requested = m.locals.add(ValType::I32); 178 | builder 179 | .func_body() 180 | .call(stable_size) 181 | .local_get(requested) 182 | .binop(BinaryOp::I32Add) 183 | .i32_const(limit) 184 | .binop(BinaryOp::I32GtU) 185 | .if_else( 186 | Some(ValType::I32), 187 | |then| { 188 | then.i32_const(-1); 189 | }, 190 | |else_| { 191 | else_.local_get(requested).call(old_stable_grow); 192 | }, 193 | ); 194 | let new_stable_grow = builder.finish(vec![requested], &mut m.funcs); 195 | replacer.add(old_stable_grow, new_stable_grow); 196 | } 197 | } 198 | 199 | fn make_stable64_grow(m: &mut Module, replacer: &mut Replacer, limit: i64) { 200 | if let Some(old_stable64_grow) = get_ic_func_id(m, "stable64_grow") { 201 | // stable64_size is added to import if it wasn't imported 202 | let stable64_size = get_ic_func_id(m, "stable64_size").unwrap(); 203 | let mut builder = FunctionBuilder::new(&mut m.types, &[ValType::I64], &[ValType::I64]); 204 | let requested = m.locals.add(ValType::I64); 205 | builder 206 | .func_body() 207 | .call(stable64_size) 208 | .local_get(requested) 209 | .binop(BinaryOp::I64Add) 210 | .i64_const(limit) 211 | .binop(BinaryOp::I64GtU) 212 | .if_else( 213 | Some(ValType::I64), 214 | |then| { 215 | then.i64_const(-1); 216 | }, 217 | |else_| { 218 | else_.local_get(requested).call(old_stable64_grow); 219 | }, 220 | ); 221 | let new_stable64_grow = builder.finish(vec![requested], &mut m.funcs); 222 | replacer.add(old_stable64_grow, new_stable64_grow); 223 | } 224 | } 225 | 226 | #[allow(clippy::too_many_arguments)] 227 | fn check_list( 228 | memory: MemoryId, 229 | checks: &mut InstrSeqBuilder, 230 | no_redirect: LocalId, 231 | size: LocalId, 232 | src: LocalId, 233 | is_rename: Option, 234 | list: &Vec<&[u8]>, 235 | wasm64: bool, 236 | ) { 237 | let checks_id = checks.id(); 238 | for bytes in list { 239 | checks.block(None, |list_check| { 240 | let list_check_id = list_check.id(); 241 | // Check the length 242 | list_check.local_get(size); 243 | match wasm64 { 244 | true => { 245 | list_check 246 | .i64_const(bytes.len() as i64) 247 | .binop(BinaryOp::I64Ne); 248 | } 249 | false => { 250 | list_check 251 | .i32_const(bytes.len() as i32) 252 | .binop(BinaryOp::I32Ne); 253 | } 254 | } 255 | list_check.br_if(list_check_id); 256 | // Load bytes at src onto the stack 257 | for i in 0..bytes.len() { 258 | list_check.local_get(src).load( 259 | memory, 260 | match wasm64 { 261 | true => LoadKind::I64_8 { 262 | kind: ExtendedLoad::ZeroExtend, 263 | }, 264 | false => LoadKind::I32_8 { 265 | kind: ExtendedLoad::ZeroExtend, 266 | }, 267 | }, 268 | MemArg { 269 | offset: i as u32, 270 | align: 1, 271 | }, 272 | ); 273 | } 274 | for byte in bytes.iter().rev() { 275 | match wasm64 { 276 | true => { 277 | list_check.i64_const(*byte as i64).binop(BinaryOp::I64Ne); 278 | } 279 | false => { 280 | list_check.i32_const(*byte as i32).binop(BinaryOp::I32Ne); 281 | } 282 | } 283 | list_check.br_if(list_check_id); 284 | } 285 | // names were equal, so skip all remaining checks and redirect 286 | if let Some(is_rename) = is_rename { 287 | if bytes == b"http_request" { 288 | list_check.i32_const(1).local_set(is_rename); 289 | } else { 290 | list_check.i32_const(0).local_set(is_rename); 291 | } 292 | } 293 | list_check.i32_const(0).local_set(no_redirect).br(checks_id); 294 | }); 295 | } 296 | // None matched 297 | checks.i32_const(1).local_set(no_redirect); 298 | } 299 | 300 | fn make_redirect_call_new( 301 | m: &mut Module, 302 | replacer: &mut Replacer, 303 | wasm64: bool, 304 | redirect_id: Principal, 305 | ) { 306 | if let Some(old_call_new) = get_ic_func_id(m, "call_new") { 307 | let pointer_type = match wasm64 { 308 | true => ValType::I64, 309 | false => ValType::I32, 310 | }; 311 | let redirect_id = redirect_id.as_slice(); 312 | // Specify the same args as `call_new` so that WASM will correctly check mismatching args 313 | let callee_src = m.locals.add(pointer_type); 314 | let callee_size = m.locals.add(pointer_type); 315 | let name_src = m.locals.add(pointer_type); 316 | let name_size = m.locals.add(pointer_type); 317 | let arg5 = m.locals.add(pointer_type); 318 | let arg6 = m.locals.add(pointer_type); 319 | let arg7 = m.locals.add(pointer_type); 320 | let arg8 = m.locals.add(pointer_type); 321 | 322 | let memory = m 323 | .get_memory_id() 324 | .expect("Canister Wasm module should have only one memory"); 325 | 326 | // Scratch variables 327 | let no_redirect = m.locals.add(ValType::I32); 328 | let is_rename = m.locals.add(ValType::I32); 329 | let mut memory_backup = Vec::new(); 330 | for _ in 0..redirect_id.len() { 331 | memory_backup.push(m.locals.add(pointer_type)); 332 | } 333 | let redirect_canisters = [ 334 | Principal::from_slice(&[]), 335 | Principal::from_text("7hfb6-caaaa-aaaar-qadga-cai").unwrap(), 336 | ]; 337 | 338 | // All functions that require controller permissions or cycles. 339 | // For simplicity, We mingle all canister methods in a single list. 340 | // Method names shouldn't overlap. 341 | let controller_function_names = [ 342 | "create_canister", 343 | "update_settings", 344 | "install_code", 345 | "uninstall_code", 346 | "canister_status", 347 | "stop_canister", 348 | "start_canister", 349 | "delete_canister", 350 | "list_canister_snapshots", 351 | "take_canister_snapshot", 352 | "load_canister_snapshot", 353 | "delete_canister_snapshot", 354 | // These functions doesn't require controller permissions, but needs cycles 355 | "sign_with_ecdsa", 356 | "sign_with_schnorr", 357 | "http_request", // Will be renamed to "_ttp_request", because the name conflicts with the http serving endpoint. 358 | "_ttp_request", // need to redirect renamed function as well, because the second time we see this function, it's already renamed in memory 359 | // methods from evm canister 360 | "eth_call", 361 | "eth_feeHistory", 362 | "eth_getBlockByNumber", 363 | "eth_getLogs", 364 | "eth_getTransactionCount", 365 | "eth_getTransactionReceipt", 366 | "eth_sendRawTransaction", 367 | "request", 368 | ]; 369 | 370 | let mut builder = FunctionBuilder::new( 371 | &mut m.types, 372 | &[ 373 | pointer_type, 374 | pointer_type, 375 | pointer_type, 376 | pointer_type, 377 | pointer_type, 378 | pointer_type, 379 | pointer_type, 380 | pointer_type, 381 | ], 382 | &[], 383 | ); 384 | 385 | builder 386 | .func_body() 387 | .block(None, |checks| { 388 | let checks_id = checks.id(); 389 | // Check if callee address is from redirect_canisters 390 | checks 391 | .block(None, |id_check| { 392 | check_list( 393 | memory, 394 | id_check, 395 | no_redirect, 396 | callee_size, 397 | callee_src, 398 | None, 399 | &redirect_canisters 400 | .iter() 401 | .map(|p| p.as_slice()) 402 | .collect::>(), 403 | wasm64, 404 | ); 405 | }) 406 | .local_get(no_redirect) 407 | .br_if(checks_id); 408 | // Callee address matches, check method name is in the list 409 | check_list( 410 | memory, 411 | checks, 412 | no_redirect, 413 | name_size, 414 | name_src, 415 | Some(is_rename), 416 | &controller_function_names 417 | .iter() 418 | .map(|s| s.as_bytes()) 419 | .collect::>(), 420 | wasm64, 421 | ); 422 | }) 423 | .local_get(no_redirect) 424 | .if_else( 425 | None, 426 | |block| { 427 | // Put all the args back on stack and call call_new without redirecting 428 | block 429 | .local_get(callee_src) 430 | .local_get(callee_size) 431 | .local_get(name_src) 432 | .local_get(name_size) 433 | .local_get(arg5) 434 | .local_get(arg6) 435 | .local_get(arg7) 436 | .local_get(arg8) 437 | .call(old_call_new); 438 | }, 439 | |block| { 440 | // Save current memory starting from address 0 into local variables 441 | for (address, backup_var) in memory_backup.iter().enumerate() { 442 | match wasm64 { 443 | true => { 444 | block 445 | .i64_const(address as i64) 446 | .load( 447 | memory, 448 | LoadKind::I64_8 { 449 | kind: ExtendedLoad::ZeroExtend, 450 | }, 451 | MemArg { 452 | offset: 0, 453 | align: 1, 454 | }, 455 | ) 456 | .local_set(*backup_var); 457 | } 458 | false => { 459 | block 460 | .i32_const(address as i32) 461 | .load( 462 | memory, 463 | LoadKind::I32_8 { 464 | kind: ExtendedLoad::ZeroExtend, 465 | }, 466 | MemArg { 467 | offset: 0, 468 | align: 1, 469 | }, 470 | ) 471 | .local_set(*backup_var); 472 | } 473 | } 474 | } 475 | 476 | // Write the canister id into memory at address 0 477 | for (address, byte) in redirect_id.iter().enumerate() { 478 | match wasm64 { 479 | true => { 480 | block 481 | .i64_const(address as i64) 482 | .i64_const(*byte as i64) 483 | .store( 484 | memory, 485 | StoreKind::I64_8 { atomic: false }, 486 | MemArg { 487 | offset: 0, 488 | align: 1, 489 | }, 490 | ); 491 | } 492 | false => { 493 | block 494 | .i32_const(address as i32) 495 | .i32_const(*byte as i32) 496 | .store( 497 | memory, 498 | StoreKind::I32_8 { atomic: false }, 499 | MemArg { 500 | offset: 0, 501 | align: 1, 502 | }, 503 | ); 504 | } 505 | } 506 | } 507 | block.local_get(is_rename).if_else( 508 | None, 509 | |then| match wasm64 { 510 | true => { 511 | then.local_get(name_src).i64_const('_' as i64).store( 512 | memory, 513 | StoreKind::I64_8 { atomic: false }, 514 | MemArg { 515 | offset: 0, 516 | align: 1, 517 | }, 518 | ); 519 | } 520 | false => { 521 | then.local_get(name_src).i32_const('_' as i32).store( 522 | memory, 523 | StoreKind::I32_8 { atomic: false }, 524 | MemArg { 525 | offset: 0, 526 | align: 1, 527 | }, 528 | ); 529 | } 530 | }, 531 | |_| {}, 532 | ); 533 | match wasm64 { 534 | true => { 535 | block.i64_const(0).i64_const(redirect_id.len() as i64); 536 | } 537 | false => { 538 | block.i32_const(0).i32_const(redirect_id.len() as i32); 539 | } 540 | } 541 | 542 | block 543 | .local_get(name_src) 544 | .local_get(name_size) 545 | .local_get(arg5) 546 | .local_get(arg6) 547 | .local_get(arg7) 548 | .local_get(arg8) 549 | .call(old_call_new); 550 | 551 | // Restore old memory 552 | for (address, byte) in memory_backup.iter().enumerate() { 553 | match wasm64 { 554 | true => { 555 | block.i64_const(address as i64).local_get(*byte).store( 556 | memory, 557 | StoreKind::I64_8 { atomic: false }, 558 | MemArg { 559 | offset: 0, 560 | align: 1, 561 | }, 562 | ); 563 | } 564 | false => { 565 | block.i32_const(address as i32).local_get(*byte).store( 566 | memory, 567 | StoreKind::I32_8 { atomic: false }, 568 | MemArg { 569 | offset: 0, 570 | align: 1, 571 | }, 572 | ); 573 | } 574 | } 575 | } 576 | }, 577 | ); 578 | let new_call_new = builder.finish( 579 | vec![ 580 | callee_src, 581 | callee_size, 582 | name_src, 583 | name_size, 584 | arg5, 585 | arg6, 586 | arg7, 587 | arg8, 588 | ], 589 | &mut m.funcs, 590 | ); 591 | replacer.add(old_call_new, new_call_new); 592 | } 593 | } 594 | 595 | /// Get the FuncionId of a system API in ic0 import. 596 | /// 597 | /// If stable_size or stable64_size is not imported, add them to the module. 598 | fn get_ic_func_id(m: &mut Module, method: &str) -> Option { 599 | match m.imports.find("ic0", method) { 600 | Some(id) => match m.imports.get(id).kind { 601 | ImportKind::Function(func_id) => Some(func_id), 602 | _ => unreachable!(), 603 | }, 604 | None => { 605 | let ty = match method { 606 | "stable_size" => Some(m.types.add(&[], &[ValType::I32])), 607 | "stable64_size" => Some(m.types.add(&[], &[ValType::I64])), 608 | _ => None, 609 | }; 610 | match ty { 611 | Some(ty) => { 612 | let func_id = m.add_import_func("ic0", method, ty).0; 613 | Some(func_id) 614 | } 615 | None => None, 616 | } 617 | } 618 | } 619 | } 620 | -------------------------------------------------------------------------------- /src/metadata.rs: -------------------------------------------------------------------------------- 1 | use walrus::{IdsToIndices, Module, RawCustomSection}; 2 | 3 | #[derive(Clone, Copy)] 4 | pub enum Kind { 5 | Public, 6 | Private, 7 | } 8 | 9 | /// Add or overwrite a metadata section 10 | pub fn add_metadata(m: &mut Module, visibility: Kind, name: &str, data: Vec) { 11 | let name = match visibility { 12 | Kind::Public => "icp:public ".to_owned(), 13 | Kind::Private => "icp:private ".to_owned(), 14 | } + name; 15 | drop(m.customs.remove_raw(&name)); 16 | let custom_section = RawCustomSection { name, data }; 17 | m.customs.add(custom_section); 18 | } 19 | 20 | /// Remove a metadata section 21 | pub fn remove_metadata(m: &mut Module, name: &str) { 22 | let public = "icp:public ".to_owned() + name; 23 | let private = "icp:private ".to_owned() + name; 24 | m.customs.remove_raw(&public); 25 | m.customs.remove_raw(&private); 26 | } 27 | 28 | /// List current metadata sections 29 | pub fn list_metadata(m: &Module) -> Vec<&str> { 30 | m.customs 31 | .iter() 32 | .map(|section| section.1.name()) 33 | .filter(|name| name.starts_with("icp:")) 34 | .collect() 35 | } 36 | 37 | /// Get the content of metadata 38 | pub fn get_metadata<'a>(m: &'a Module, name: &'a str) -> Option> { 39 | let public = "icp:public ".to_owned() + name; 40 | let private = "icp:private ".to_owned() + name; 41 | m.customs 42 | .iter() 43 | .find(|(_, section)| section.name() == public || section.name() == private) 44 | .map(|(_, section)| section.data(&IdsToIndices::default())) 45 | } 46 | -------------------------------------------------------------------------------- /src/optimize.rs: -------------------------------------------------------------------------------- 1 | use crate::metadata::*; 2 | use crate::utils::*; 3 | use clap::ValueEnum; 4 | use walrus::*; 5 | 6 | #[derive(Clone, Copy, PartialEq, Eq, ValueEnum)] 7 | pub enum OptLevel { 8 | #[clap(name = "O0")] 9 | O0, 10 | #[clap(name = "O1")] 11 | O1, 12 | #[clap(name = "O2")] 13 | O2, 14 | #[clap(name = "O3")] 15 | O3, 16 | #[clap(name = "O4")] 17 | O4, 18 | #[clap(name = "Os")] 19 | Os, 20 | #[clap(name = "Oz")] 21 | Oz, 22 | } 23 | 24 | pub fn optimize( 25 | m: &mut Module, 26 | level: &OptLevel, 27 | inline_functions_with_loops: bool, 28 | always_inline_max_function_size: &Option, 29 | keep_name_section: bool, 30 | ) -> anyhow::Result<()> { 31 | use tempfile::NamedTempFile; 32 | use wasm_opt::OptimizationOptions; 33 | // recursively optimize embedded modules in Motoko actor classes 34 | if is_motoko_canister(m) { 35 | let data = get_motoko_wasm_data_sections(m); 36 | for (id, mut module) in data.into_iter() { 37 | let old_size = module.emit_wasm().len(); 38 | optimize( 39 | &mut module, 40 | level, 41 | inline_functions_with_loops, 42 | always_inline_max_function_size, 43 | keep_name_section, 44 | )?; 45 | let new_size = module.emit_wasm().len(); 46 | // Guard against embedded actor class overriding the parent module 47 | if new_size <= old_size { 48 | let blob = encode_module_as_data_section(module); 49 | m.data.get_mut(id).value = blob; 50 | } else { 51 | eprintln!("Warning: embedded actor class module was not optimized because the optimized module is larger than the original module"); 52 | } 53 | } 54 | } 55 | 56 | // write module to temp file 57 | let temp_file = NamedTempFile::new()?; 58 | m.emit_wasm_file(temp_file.path())?; 59 | 60 | // pull out a copy of the custom sections to preserve 61 | let metadata_sections: Vec<(Kind, &str, Vec)> = m 62 | .customs 63 | .iter() 64 | .filter(|(_, section)| section.name().starts_with("icp:")) 65 | .map(|(_, section)| { 66 | let data = section.data(&IdsToIndices::default()).to_vec(); 67 | let full_name = section.name(); 68 | match full_name.strip_prefix("public ") { 69 | Some(name) => (Kind::Public, name, data), 70 | None => match full_name.strip_prefix("private ") { 71 | Some(name) => (Kind::Private, name, data), 72 | None => unreachable!(), 73 | }, 74 | } 75 | }) 76 | .collect(); 77 | 78 | // read in from temp file and optimize 79 | let mut optimizations = match level { 80 | OptLevel::O0 => OptimizationOptions::new_opt_level_0(), 81 | OptLevel::O1 => OptimizationOptions::new_opt_level_1(), 82 | OptLevel::O2 => OptimizationOptions::new_opt_level_2(), 83 | OptLevel::O3 => OptimizationOptions::new_opt_level_3(), 84 | OptLevel::O4 => OptimizationOptions::new_opt_level_4(), 85 | OptLevel::Os => OptimizationOptions::new_optimize_for_size(), 86 | OptLevel::Oz => OptimizationOptions::new_optimize_for_size_aggressively(), 87 | }; 88 | optimizations.debug_info(keep_name_section); 89 | optimizations.allow_functions_with_loops(inline_functions_with_loops); 90 | if let Some(max_size) = always_inline_max_function_size { 91 | optimizations.always_inline_max_size(*max_size); 92 | } 93 | 94 | for feature in IC_ENABLED_WASM_FEATURES { 95 | optimizations.enable_feature(feature); 96 | } 97 | optimizations.run(temp_file.path(), temp_file.path())?; 98 | 99 | // read optimized wasm back in from temp file 100 | let mut m_opt = parse_wasm_file(temp_file.path().to_path_buf(), keep_name_section)?; 101 | 102 | // re-insert the custom sections 103 | metadata_sections 104 | .into_iter() 105 | .for_each(|(visibility, name, data)| { 106 | add_metadata(&mut m_opt, visibility, name, data); 107 | }); 108 | 109 | *m = m_opt; 110 | Ok(()) 111 | } 112 | -------------------------------------------------------------------------------- /src/shrink.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::*; 2 | use walrus::*; 3 | 4 | pub fn shrink(m: &mut Module) { 5 | if is_motoko_canister(m) { 6 | let ids = get_motoko_wasm_data_sections(m); 7 | for (id, mut module) in ids.into_iter() { 8 | shrink(&mut module); 9 | let blob = encode_module_as_data_section(module); 10 | let original_len = m.data.get(id).value.len(); 11 | if blob.len() < original_len { 12 | m.data.get_mut(id).value = blob; 13 | } 14 | } 15 | } 16 | let to_remove: Vec<_> = m 17 | .customs 18 | .iter() 19 | .filter(|(_, section)| !section.name().starts_with("icp:")) 20 | .map(|(id, _)| id) 21 | .collect(); 22 | for s in to_remove { 23 | m.customs.delete(s); 24 | } 25 | passes::gc::run(m); 26 | } 27 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use crate::Error; 2 | use libflate::gzip; 3 | use std::borrow::Cow; 4 | use std::collections::HashMap; 5 | use std::io::{self, Read}; 6 | use walrus::*; 7 | #[cfg(feature = "wasm-opt")] 8 | use wasm_opt::Feature; 9 | use wasmparser::{Validator, WasmFeaturesInflated}; 10 | 11 | pub const WASM_MAGIC_BYTES: &[u8] = &[0, 97, 115, 109]; 12 | 13 | pub const GZIPPED_WASM_MAGIC_BYTES: &[u8] = &[31, 139, 8]; 14 | 15 | // The feature set should be align with IC `wasmtime` validation config: 16 | // https://github.com/dfinity/ic/blob/6a6470d705a0f36fb94743b12892280409f85688/rs/embedders/src/wasm_utils/validation.rs#L1385 17 | // Since we use both wasm_opt::Feature and wasmparse::WasmFeature, we have to define the config/features for both. 18 | #[cfg(feature = "wasm-opt")] 19 | pub const IC_ENABLED_WASM_FEATURES: [Feature; 7] = [ 20 | Feature::MutableGlobals, 21 | Feature::TruncSat, 22 | Feature::Simd, 23 | Feature::BulkMemory, 24 | Feature::SignExt, 25 | Feature::ReferenceTypes, 26 | Feature::Memory64, 27 | ]; 28 | 29 | pub const IC_ENABLED_WASM_FEATURES_INFLATED: WasmFeaturesInflated = WasmFeaturesInflated { 30 | mutable_global: true, 31 | saturating_float_to_int: true, 32 | sign_extension: true, 33 | reference_types: true, 34 | multi_value: true, 35 | bulk_memory: true, 36 | simd: true, 37 | relaxed_simd: false, 38 | threads: true, 39 | shared_everything_threads: true, 40 | tail_call: false, 41 | floats: true, 42 | multi_memory: true, 43 | exceptions: true, 44 | memory64: true, 45 | extended_const: true, 46 | component_model: true, 47 | function_references: false, 48 | memory_control: true, 49 | gc: false, 50 | custom_page_sizes: true, 51 | component_model_values: true, 52 | component_model_nested_names: true, 53 | component_model_more_flags: true, 54 | component_model_multiple_returns: true, 55 | legacy_exceptions: true, 56 | gc_types: true, 57 | stack_switching: true, 58 | wide_arithmetic: false, 59 | component_model_async: true, 60 | }; 61 | 62 | pub fn make_validator_with_features() -> Validator { 63 | Validator::new_with_features(IC_ENABLED_WASM_FEATURES_INFLATED.into()) 64 | } 65 | 66 | fn wasm_parser_config(keep_name_section: bool) -> ModuleConfig { 67 | let mut config = walrus::ModuleConfig::new(); 68 | config.generate_name_section(keep_name_section); 69 | config.generate_producers_section(false); 70 | config 71 | } 72 | 73 | fn decompress(bytes: &[u8]) -> Result, std::io::Error> { 74 | let mut decoder = gzip::Decoder::new(bytes)?; 75 | let mut decoded_data = Vec::new(); 76 | decoder.read_to_end(&mut decoded_data)?; 77 | Ok(decoded_data) 78 | } 79 | 80 | pub fn parse_wasm(bytes: &[u8], keep_name_section: bool) -> Result { 81 | let wasm = if bytes.starts_with(WASM_MAGIC_BYTES) { 82 | Ok(Cow::Borrowed(bytes)) 83 | } else if bytes.starts_with(GZIPPED_WASM_MAGIC_BYTES) { 84 | decompress(bytes).map(Cow::Owned) 85 | } else { 86 | Err(io::Error::new( 87 | io::ErrorKind::InvalidInput, 88 | "Input must be either gzipped or uncompressed WASM.", 89 | )) 90 | } 91 | .map_err(Error::IO)?; 92 | let config = wasm_parser_config(keep_name_section); 93 | config 94 | .parse(&wasm) 95 | .map_err(|e| Error::WasmParse(e.to_string())) 96 | } 97 | 98 | pub fn parse_wasm_file(file: std::path::PathBuf, keep_name_section: bool) -> Result { 99 | let bytes = std::fs::read(file).map_err(Error::IO)?; 100 | parse_wasm(&bytes[..], keep_name_section) 101 | } 102 | 103 | #[derive(Clone, Copy, PartialEq, Eq)] 104 | pub(crate) enum InjectionKind { 105 | Static, 106 | Dynamic, 107 | Dynamic64, 108 | } 109 | 110 | pub(crate) struct FunctionCost(HashMap); 111 | impl FunctionCost { 112 | pub fn new(m: &Module) -> Self { 113 | let mut res = HashMap::new(); 114 | for (method, func) in m.imports.iter().filter_map(|i| { 115 | if let ImportKind::Function(func) = i.kind { 116 | if i.module == "ic0" { 117 | Some((i.name.as_str(), func)) 118 | } else { 119 | None 120 | } 121 | } else { 122 | None 123 | } 124 | }) { 125 | use InjectionKind::*; 126 | // System API cost taken from https://github.com/dfinity/ic/blob/master/rs/embedders/src/wasmtime_embedder/system_api_complexity.rs 127 | let cost = match method { 128 | "accept_message" => (500, Static), 129 | "call_cycles_add" | "call_cycles_add128" => (500, Static), 130 | "call_data_append" => (500, Dynamic), 131 | "call_new" => (1500, Static), 132 | "call_on_cleanup" => (500, Static), 133 | "call_perform" => (5000, Static), 134 | "canister_cycle_balance" | "canister_cycle_balance128" => (500, Static), 135 | "canister_self_copy" => (500, Dynamic), 136 | "canister_self_size" => (500, Static), 137 | "canister_status" | "canister_version" => (500, Static), 138 | "certified_data_set" => (500, Dynamic), 139 | "data_certificate_copy" => (500, Dynamic), 140 | "data_certificate_present" | "data_certificate_size" => (500, Static), 141 | "debug_print" => (100, Dynamic), 142 | "global_timer_set" => (500, Static), 143 | "is_controller" => (1000, Dynamic), 144 | "msg_arg_data_copy" => (500, Dynamic), 145 | "msg_arg_data_size" => (500, Static), 146 | "msg_caller_copy" => (500, Dynamic), 147 | "msg_caller_size" => (500, Static), 148 | "msg_cycles_accept" | "msg_cycles_accept128" => (500, Static), 149 | "msg_cycles_available" | "msg_cycles_available128" => (500, Static), 150 | "msg_cycles_refunded" | "msg_cycles_refunded128" => (500, Static), 151 | "cycles_burn128" => (100, Static), 152 | "msg_method_name_copy" => (500, Dynamic), 153 | "msg_method_name_size" => (500, Static), 154 | "msg_reject_code" | "msg_reject_msg_size" => (500, Static), 155 | "msg_reject_msg_copy" => (500, Dynamic), 156 | "msg_reject" => (500, Dynamic), 157 | "msg_reply_data_append" => (500, Dynamic), 158 | "msg_reply" => (500, Static), 159 | "performance_counter" => (200, Static), 160 | "stable_grow" | "stable64_grow" => (100, Static), 161 | "stable_size" | "stable64_size" => (20, Static), 162 | "stable_read" => (20, Dynamic), 163 | "stable_write" => (20, Dynamic), 164 | "stable64_read" => (20, Dynamic64), 165 | "stable64_write" => (20, Dynamic64), 166 | "trap" => (500, Dynamic), 167 | "time" => (500, Static), 168 | _ => (20, Static), 169 | }; 170 | res.insert(func, cost); 171 | } 172 | Self(res) 173 | } 174 | pub fn get_cost(&self, id: FunctionId) -> Option<(i64, InjectionKind)> { 175 | self.0.get(&id).copied() 176 | } 177 | } 178 | pub(crate) fn instr_cost(i: &ir::Instr) -> i64 { 179 | use ir::*; 180 | use BinaryOp::*; 181 | use UnaryOp::*; 182 | // Cost taken from https://github.com/dfinity/ic/blob/master/rs/embedders/src/wasm_utils/instrumentation.rs 183 | match i { 184 | Instr::Block(..) | Instr::Loop(..) => 0, 185 | Instr::Const(..) | Instr::Load(..) | Instr::Store(..) => 1, 186 | Instr::GlobalGet(..) | Instr::GlobalSet(..) => 2, 187 | Instr::TableGet(..) | Instr::TableSet(..) => 5, 188 | Instr::TableGrow(..) | Instr::MemoryGrow(..) => 300, 189 | Instr::MemorySize(..) => 20, 190 | Instr::TableSize(..) => 100, 191 | Instr::MemoryFill(..) | Instr::MemoryCopy(..) | Instr::MemoryInit(..) => 100, 192 | Instr::TableFill(..) | Instr::TableCopy(..) | Instr::TableInit(..) => 100, 193 | Instr::DataDrop(..) | Instr::ElemDrop(..) => 300, 194 | Instr::Call(..) => 5, 195 | Instr::CallIndirect(..) => 10, // missing ReturnCall/Indirect 196 | Instr::IfElse(..) | Instr::Br(..) | Instr::BrIf(..) | Instr::BrTable(..) => 2, 197 | Instr::RefIsNull(..) => 5, 198 | Instr::RefFunc(..) => 130, 199 | Instr::Unop(Unop { op }) => match op { 200 | F32Ceil | F32Floor | F32Trunc | F32Nearest | F32Sqrt => 20, 201 | F64Ceil | F64Floor | F64Trunc | F64Nearest | F64Sqrt => 20, 202 | F32Abs | F32Neg | F64Abs | F64Neg => 2, 203 | F32ConvertSI32 | F64ConvertSI64 | F32ConvertSI64 | F64ConvertSI32 => 3, 204 | F64ConvertUI32 | F32ConvertUI64 | F32ConvertUI32 | F64ConvertUI64 => 16, 205 | I64TruncSF32 | I64TruncUF32 | I64TruncSF64 | I64TruncUF64 => 20, 206 | I32TruncSF32 | I32TruncUF32 | I32TruncSF64 | I32TruncUF64 => 20, // missing TruncSat? 207 | _ => 1, 208 | }, 209 | Instr::Binop(Binop { op }) => match op { 210 | I32DivS | I32DivU | I32RemS | I32RemU => 10, 211 | I64DivS | I64DivU | I64RemS | I64RemU => 10, 212 | F32Add | F32Sub | F32Mul | F32Div | F32Min | F32Max => 20, 213 | F64Add | F64Sub | F64Mul | F64Div | F64Min | F64Max => 20, 214 | F32Copysign | F64Copysign => 2, 215 | F32Eq | F32Ne | F32Lt | F32Gt | F32Le | F32Ge => 3, 216 | F64Eq | F64Ne | F64Lt | F64Gt | F64Le | F64Ge => 3, 217 | _ => 1, 218 | }, 219 | _ => 1, 220 | } 221 | } 222 | 223 | pub(crate) fn get_ic_func_id(m: &mut Module, method: &str) -> FunctionId { 224 | match m.imports.find("ic0", method) { 225 | Some(id) => match m.imports.get(id).kind { 226 | ImportKind::Function(func_id) => func_id, 227 | _ => unreachable!(), 228 | }, 229 | None => { 230 | let ty = match method { 231 | "stable_write" => m 232 | .types 233 | .add(&[ValType::I32, ValType::I32, ValType::I32], &[]), 234 | "stable64_write" => m 235 | .types 236 | .add(&[ValType::I64, ValType::I64, ValType::I64], &[]), 237 | "stable_read" => m 238 | .types 239 | .add(&[ValType::I32, ValType::I32, ValType::I32], &[]), 240 | "stable64_read" => m 241 | .types 242 | .add(&[ValType::I64, ValType::I64, ValType::I64], &[]), 243 | "stable_grow" => m.types.add(&[ValType::I32], &[ValType::I32]), 244 | "stable64_grow" => m.types.add(&[ValType::I64], &[ValType::I64]), 245 | "stable_size" => m.types.add(&[], &[ValType::I32]), 246 | "stable64_size" => m.types.add(&[], &[ValType::I64]), 247 | "call_cycles_add" => m.types.add(&[ValType::I64], &[]), 248 | "call_cycles_add128" => m.types.add(&[ValType::I64, ValType::I64], &[]), 249 | "cycles_burn128" => m 250 | .types 251 | .add(&[ValType::I64, ValType::I64, ValType::I32], &[]), 252 | "call_new" => m.types.add( 253 | &[ 254 | ValType::I32, 255 | ValType::I32, 256 | ValType::I32, 257 | ValType::I32, 258 | ValType::I32, 259 | ValType::I32, 260 | ValType::I32, 261 | ValType::I32, 262 | ], 263 | &[], 264 | ), 265 | "debug_print" => m.types.add(&[ValType::I32, ValType::I32], &[]), 266 | "trap" => m.types.add(&[ValType::I32, ValType::I32], &[]), 267 | "msg_arg_data_size" => m.types.add(&[], &[ValType::I32]), 268 | "msg_arg_data_copy" => m 269 | .types 270 | .add(&[ValType::I32, ValType::I32, ValType::I32], &[]), 271 | "msg_reply_data_append" => m.types.add(&[ValType::I32, ValType::I32], &[]), 272 | "msg_reply" => m.types.add(&[], &[]), 273 | _ => unreachable!(), 274 | }; 275 | m.add_import_func("ic0", method, ty).0 276 | } 277 | } 278 | } 279 | 280 | pub(crate) fn get_memory_id(m: &Module) -> MemoryId { 281 | m.memories 282 | .iter() 283 | .next() 284 | .expect("only single memory is supported") 285 | .id() 286 | } 287 | 288 | pub(crate) fn get_export_func_id(m: &Module, method: &str) -> Option { 289 | let e = m.exports.iter().find(|e| e.name == method)?; 290 | if let ExportItem::Function(id) = e.item { 291 | Some(id) 292 | } else { 293 | None 294 | } 295 | } 296 | pub(crate) fn get_or_create_export_func<'a>( 297 | m: &'a mut Module, 298 | method: &'a str, 299 | ) -> InstrSeqBuilder<'a> { 300 | let id = match get_export_func_id(m, method) { 301 | Some(id) => id, 302 | None => { 303 | let builder = FunctionBuilder::new(&mut m.types, &[], &[]); 304 | let id = builder.finish(vec![], &mut m.funcs); 305 | m.exports.add(method, id); 306 | id 307 | } 308 | }; 309 | get_builder(m, id) 310 | } 311 | 312 | pub(crate) fn get_builder(m: &mut Module, id: FunctionId) -> InstrSeqBuilder<'_> { 313 | if let FunctionKind::Local(func) = &mut m.funcs.get_mut(id).kind { 314 | let id = func.entry_block(); 315 | func.builder_mut().instr_seq(id) 316 | } else { 317 | unreachable!() 318 | } 319 | } 320 | 321 | pub(crate) fn inject_top(builder: &mut InstrSeqBuilder<'_>, instrs: Vec) { 322 | for instr in instrs.into_iter().rev() { 323 | builder.instr_at(0, instr); 324 | } 325 | } 326 | 327 | pub(crate) fn get_func_name(m: &Module, id: FunctionId) -> String { 328 | m.funcs 329 | .get(id) 330 | .name 331 | .as_ref() 332 | .unwrap_or(&format!("func_{}", id.index())) 333 | .to_string() 334 | } 335 | 336 | pub(crate) fn is_motoko_canister(m: &Module) -> bool { 337 | m.customs.iter().any(|(_, s)| { 338 | s.name() == "icp:private motoko:compiler" || s.name() == "icp:public motoko:compiler" 339 | }) || m 340 | .exports 341 | .iter() 342 | .any(|e| e.name == "canister_update __motoko_async_helper") 343 | } 344 | 345 | pub(crate) fn is_motoko_wasm_data_section(blob: &[u8]) -> Option<&[u8]> { 346 | let len = blob.len() as u32; 347 | if len > 100 348 | && blob[0..4] == [0x11, 0x00, 0x00, 0x00] // tag for blob 349 | && blob[8..12] == [0x00, 0x61, 0x73, 0x6d] 350 | // Wasm magic number 351 | { 352 | let decoded_len = u32::from_le_bytes(blob[4..8].try_into().unwrap()); 353 | if decoded_len + 8 == len { 354 | return Some(&blob[8..]); 355 | } 356 | } 357 | None 358 | } 359 | 360 | pub(crate) fn get_motoko_wasm_data_sections(m: &Module) -> Vec<(DataId, Module)> { 361 | m.data 362 | .iter() 363 | .filter_map(|d| { 364 | let blob = is_motoko_wasm_data_section(&d.value)?; 365 | let mut config = ModuleConfig::new(); 366 | config.generate_name_section(false); 367 | config.generate_producers_section(false); 368 | let m = config.parse(blob).ok()?; 369 | Some((d.id(), m)) 370 | }) 371 | .collect() 372 | } 373 | 374 | pub(crate) fn encode_module_as_data_section(mut m: Module) -> Vec { 375 | let blob = m.emit_wasm(); 376 | let blob_len = blob.len(); 377 | let mut res = Vec::with_capacity(blob_len + 8); 378 | res.extend_from_slice(&[0x11, 0x00, 0x00, 0x00]); 379 | let encoded_len = (blob_len as u32).to_le_bytes(); 380 | res.extend_from_slice(&encoded_len); 381 | res.extend_from_slice(&blob); 382 | res 383 | } 384 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | To regenerate the Wasm file: 2 | 3 | * `wat.wasm` is generated from the [dfinity/examples/wasm/counter](https://github.com/dfinity/examples/tree/master/wasm/counter) 4 | * `motoko.wasm` is generated from [dfinity/examples/motoko/counter](https://github.com/dfinity/examples/tree/master/motoko/counter) 5 | * `motoko-region.wasm` is generated by adding the following in the Motoko counter code: 6 | ```motoko 7 | import Region "mo:base/Region"; 8 | actor { 9 | stable let profiling = do { 10 | let r = Region.new(); 11 | ignore Region.grow(r, 32); 12 | r; 13 | }; 14 | ... 15 | } 16 | ``` 17 | * `rust.wasm` is generated from [dfinity/examples/rust/counter](https://github.com/dfinity/examples/tree/master/rust/counter), and add the following pre/post-upgrade hooks: 18 | ```rust 19 | use ic_stable_structures::{ 20 | memory_manager::{MemoryId, MemoryManager}, 21 | writer::Writer, 22 | DefaultMemoryImpl, Memory, 23 | }; 24 | thread_local! { 25 | static MEMORY_MANAGER: RefCell> = 26 | RefCell::new(MemoryManager::init(DefaultMemoryImpl::default())); 27 | } 28 | 29 | const UPGRADES: MemoryId = MemoryId::new(0); 30 | 31 | #[ic_cdk::pre_upgrade] 32 | fn pre_upgrade() { 33 | let bytes = COUNTER.with(|counter| Encode!(counter).unwrap()); 34 | let len = bytes.len() as u32; 35 | let mut memory = MEMORY_MANAGER.with(|m| m.borrow().get(UPGRADES)); 36 | let mut writer = Writer::new(&mut memory, 0); 37 | writer.write(&len.to_le_bytes()).unwrap(); 38 | writer.write(&bytes).unwrap(); 39 | } 40 | #[ic_cdk::post_upgrade] 41 | fn post_upgrade() { 42 | let memory = MEMORY_MANAGER.with(|m| m.borrow().get(UPGRADES)); 43 | let mut len_bytes = [0; 4]; 44 | memory.read(0, &mut len_bytes); 45 | let len = u32::from_le_bytes(len_bytes) as usize; 46 | let mut bytes = vec![0; len]; 47 | memory.read(4, &mut bytes); 48 | let value = Decode!(&bytes, Nat).unwrap(); 49 | COUNTER.with(|cell| *cell.borrow_mut() = value); 50 | } 51 | ``` 52 | * `rust-region.wasm` is generated by adding the following in the Rust counter code: 53 | ```rust 54 | const PROFILING: MemoryId = MemoryId::new(100); 55 | #[ic_cdk::init] 56 | fn init() { 57 | let memory = MEMORY_MANAGER.with(|m| m.borrow().get(PROFILING)); 58 | memory.grow(32); 59 | ... 60 | } 61 | ``` 62 | * `classes.wasm` is generated from [dfinity/examples/motoko/classes](https://github.com/dfinity/examples/blob/master/motoko/classes/src/map/Map.mo) 63 | -------------------------------------------------------------------------------- /tests/classes.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfinity/ic-wasm/4c52e75c12bb730e795d8a4c2862987f4a9524a3/tests/classes.wasm -------------------------------------------------------------------------------- /tests/deployable.ic-repl.sh: -------------------------------------------------------------------------------- 1 | #!ic-repl 2 | // Check if the transformed Wasm module can be deployed 3 | 4 | function install(wasm) { 5 | let id = call ic.provisional_create_canister_with_cycles(record { settings = null; amount = null }); 6 | let S = id.canister_id; 7 | call ic.install_code( 8 | record { 9 | arg = encode (); 10 | wasm_module = wasm; 11 | mode = variant { install }; 12 | canister_id = S; 13 | } 14 | ); 15 | S 16 | }; 17 | function upgrade(S, wasm) { 18 | call ic.install_code( 19 | record { 20 | arg = encode (); 21 | wasm_module = wasm; 22 | mode = variant { upgrade }; 23 | canister_id = S; 24 | } 25 | ); 26 | }; 27 | 28 | function counter(wasm) { 29 | let S = install(wasm); 30 | call S.set(42); 31 | call S.inc(); 32 | call S.get(); 33 | assert _ == (43 : nat); 34 | 35 | call S.inc(); 36 | call S.inc(); 37 | call S.get(); 38 | assert _ == (45 : nat); 39 | S 40 | }; 41 | function wat(wasm) { 42 | let S = install(wasm); 43 | call S.set((42 : int64)); 44 | call S.inc(); 45 | call S.get(); 46 | assert _ == (43 : int64); 47 | 48 | call S.inc(); 49 | call S.inc(); 50 | call S.get(); 51 | assert _ == (45 : int64); 52 | S 53 | }; 54 | function classes(wasm) { 55 | let S = install(wasm); 56 | call S.get(42); 57 | assert _ == (null : opt empty); 58 | call S.put(42, "text"); 59 | call S.get(42); 60 | assert _ == opt "text"; 61 | 62 | call S.put(40, "text0"); 63 | call S.put(41, "text1"); 64 | call S.put(42, "text2"); 65 | call S.get(42); 66 | assert _ == opt "text2"; 67 | read_state("canister", S, "metadata/candid:args"); 68 | assert _ == "()"; 69 | read_state("canister", S, "metadata/motoko:compiler"); 70 | assert _ == blob "0.6.26"; 71 | S 72 | }; 73 | function classes_limit(wasm) { 74 | let S = install(wasm); 75 | call S.get(42); 76 | assert _ == (null : opt empty); 77 | fail call S.put(42, "text"); 78 | assert _ ~= "0 cycles were received"; 79 | S 80 | }; 81 | function classes_redirect(wasm) { 82 | let S = install(wasm); 83 | call S.get(42); 84 | assert _ == (null : opt empty); 85 | fail call S.put(42, "text"); 86 | assert _ ~= "zz73r-nyaaa-aabbb-aaaca-cai not found"; 87 | S 88 | }; 89 | function check_profiling(S, cycles, len) { 90 | call S.__get_cycles(); 91 | assert _ == (cycles : int64); 92 | call S.__get_profiling((0:nat32)); 93 | assert _[0].size() == (len : nat); 94 | assert _[1] == (null : opt empty); 95 | call S.__get_profiling((1:nat32)); 96 | assert _[0].size() == (sub(len,1) : nat); 97 | null 98 | }; 99 | function evm_redirect(wasm) { 100 | let S = install(wasm); 101 | fail call S.request(); 102 | assert _ ~= "zz73r-nyaaa-aabbb-aaaca-cai not found"; 103 | fail call S.requestCost(); 104 | assert _ ~= "7hfb6-caaaa-aaaar-qadga-cai not found"; 105 | fail call S.non_evm_request(); 106 | assert _ ~= "cpmcr-yeaaa-aaaaa-qaala-cai not found"; 107 | S 108 | }; 109 | 110 | evm_redirect(file("ok/evm-redirect.wasm")); 111 | 112 | let S = counter(file("ok/motoko-instrument.wasm")); 113 | check_profiling(S, 21571, 78); 114 | let S = counter(file("ok/motoko-gc-instrument.wasm")); 115 | check_profiling(S, 595, 4); 116 | let wasm = file("ok/motoko-region-instrument.wasm"); 117 | let S = counter(wasm); 118 | check_profiling(S, 478652, 78); 119 | upgrade(S, wasm); 120 | call S.get(); 121 | assert _ == (45 : nat); 122 | check_profiling(S, 494682, 460); 123 | counter(file("ok/motoko-shrink.wasm")); 124 | counter(file("ok/motoko-limit.wasm")); 125 | 126 | let S = counter(file("ok/rust-instrument.wasm")); 127 | check_profiling(S, 75279, 576); 128 | let wasm = file("ok/rust-region-instrument.wasm"); 129 | let S = counter(wasm); 130 | check_profiling(S, 152042, 574); 131 | upgrade(S, wasm); 132 | call S.get(); 133 | assert _ == (45 : nat); 134 | check_profiling(S, 1023168, 2344); 135 | counter(file("ok/rust-shrink.wasm")); 136 | counter(file("ok/rust-limit.wasm")); 137 | 138 | let S = wat(file("ok/wat-instrument.wasm")); 139 | check_profiling(S, 5656, 2); 140 | wat(file("ok/wat-shrink.wasm")); 141 | wat(file("ok/wat-limit.wasm")); 142 | 143 | classes(file("ok/classes-shrink.wasm")); 144 | classes(file("ok/classes-optimize.wasm")); 145 | classes(file("ok/classes-optimize-names.wasm")); 146 | classes_limit(file("ok/classes-limit.wasm")); 147 | classes_redirect(file("ok/classes-redirect.wasm")); 148 | classes(file("ok/classes-nop-redirect.wasm")); 149 | -------------------------------------------------------------------------------- /tests/evm.mo: -------------------------------------------------------------------------------- 1 | actor { 2 | let evm : actor { request: shared () -> async (); requestCost: shared () -> async () } = actor "7hfb6-caaaa-aaaar-qadga-cai"; 3 | let non_evm : actor { request: shared () -> async (); requestCost: shared () -> async () } = actor "cpmcr-yeaaa-aaaaa-qaala-cai"; 4 | public func requestCost() : async () { 5 | await evm.requestCost(); 6 | }; 7 | public func request() : async () { 8 | await evm.request(); 9 | }; 10 | public func non_evm_request() : async () { 11 | await non_evm.request(); 12 | }; 13 | } 14 | 15 | -------------------------------------------------------------------------------- /tests/evm.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfinity/ic-wasm/4c52e75c12bb730e795d8a4c2862987f4a9524a3/tests/evm.wasm -------------------------------------------------------------------------------- /tests/motoko-region.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfinity/ic-wasm/4c52e75c12bb730e795d8a4c2862987f4a9524a3/tests/motoko-region.wasm -------------------------------------------------------------------------------- /tests/motoko.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfinity/ic-wasm/4c52e75c12bb730e795d8a4c2862987f4a9524a3/tests/motoko.wasm -------------------------------------------------------------------------------- /tests/ok/classes-limit.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfinity/ic-wasm/4c52e75c12bb730e795d8a4c2862987f4a9524a3/tests/ok/classes-limit.wasm -------------------------------------------------------------------------------- /tests/ok/classes-nop-redirect.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfinity/ic-wasm/4c52e75c12bb730e795d8a4c2862987f4a9524a3/tests/ok/classes-nop-redirect.wasm -------------------------------------------------------------------------------- /tests/ok/classes-optimize-names.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfinity/ic-wasm/4c52e75c12bb730e795d8a4c2862987f4a9524a3/tests/ok/classes-optimize-names.wasm -------------------------------------------------------------------------------- /tests/ok/classes-optimize.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfinity/ic-wasm/4c52e75c12bb730e795d8a4c2862987f4a9524a3/tests/ok/classes-optimize.wasm -------------------------------------------------------------------------------- /tests/ok/classes-redirect.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfinity/ic-wasm/4c52e75c12bb730e795d8a4c2862987f4a9524a3/tests/ok/classes-redirect.wasm -------------------------------------------------------------------------------- /tests/ok/classes-shrink.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfinity/ic-wasm/4c52e75c12bb730e795d8a4c2862987f4a9524a3/tests/ok/classes-shrink.wasm -------------------------------------------------------------------------------- /tests/ok/evm-redirect.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfinity/ic-wasm/4c52e75c12bb730e795d8a4c2862987f4a9524a3/tests/ok/evm-redirect.wasm -------------------------------------------------------------------------------- /tests/ok/motoko-gc-instrument.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfinity/ic-wasm/4c52e75c12bb730e795d8a4c2862987f4a9524a3/tests/ok/motoko-gc-instrument.wasm -------------------------------------------------------------------------------- /tests/ok/motoko-instrument.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfinity/ic-wasm/4c52e75c12bb730e795d8a4c2862987f4a9524a3/tests/ok/motoko-instrument.wasm -------------------------------------------------------------------------------- /tests/ok/motoko-limit.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfinity/ic-wasm/4c52e75c12bb730e795d8a4c2862987f4a9524a3/tests/ok/motoko-limit.wasm -------------------------------------------------------------------------------- /tests/ok/motoko-region-instrument.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfinity/ic-wasm/4c52e75c12bb730e795d8a4c2862987f4a9524a3/tests/ok/motoko-region-instrument.wasm -------------------------------------------------------------------------------- /tests/ok/motoko-shrink.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfinity/ic-wasm/4c52e75c12bb730e795d8a4c2862987f4a9524a3/tests/ok/motoko-shrink.wasm -------------------------------------------------------------------------------- /tests/ok/rust-instrument.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfinity/ic-wasm/4c52e75c12bb730e795d8a4c2862987f4a9524a3/tests/ok/rust-instrument.wasm -------------------------------------------------------------------------------- /tests/ok/rust-limit.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfinity/ic-wasm/4c52e75c12bb730e795d8a4c2862987f4a9524a3/tests/ok/rust-limit.wasm -------------------------------------------------------------------------------- /tests/ok/rust-region-instrument.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfinity/ic-wasm/4c52e75c12bb730e795d8a4c2862987f4a9524a3/tests/ok/rust-region-instrument.wasm -------------------------------------------------------------------------------- /tests/ok/rust-shrink.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfinity/ic-wasm/4c52e75c12bb730e795d8a4c2862987f4a9524a3/tests/ok/rust-shrink.wasm -------------------------------------------------------------------------------- /tests/ok/wat-instrument.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfinity/ic-wasm/4c52e75c12bb730e795d8a4c2862987f4a9524a3/tests/ok/wat-instrument.wasm -------------------------------------------------------------------------------- /tests/ok/wat-limit.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfinity/ic-wasm/4c52e75c12bb730e795d8a4c2862987f4a9524a3/tests/ok/wat-limit.wasm -------------------------------------------------------------------------------- /tests/ok/wat-shrink.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfinity/ic-wasm/4c52e75c12bb730e795d8a4c2862987f4a9524a3/tests/ok/wat-shrink.wasm -------------------------------------------------------------------------------- /tests/rust-region.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfinity/ic-wasm/4c52e75c12bb730e795d8a4c2862987f4a9524a3/tests/rust-region.wasm -------------------------------------------------------------------------------- /tests/rust.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfinity/ic-wasm/4c52e75c12bb730e795d8a4c2862987f4a9524a3/tests/rust.wasm -------------------------------------------------------------------------------- /tests/tests.rs: -------------------------------------------------------------------------------- 1 | use assert_cmd::Command; 2 | 3 | use std::fs; 4 | use std::path::Path; 5 | 6 | fn wasm_input(wasm: &str, output: bool) -> Command { 7 | let mut cmd = Command::cargo_bin("ic-wasm").unwrap(); 8 | let path = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests"); 9 | cmd.arg(path.join(wasm)); 10 | if output { 11 | cmd.arg("-o").arg(path.join("out.wasm")); 12 | } 13 | cmd 14 | } 15 | 16 | fn assert_wasm(expected: &str) { 17 | let path = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests"); 18 | let expected = path.join("ok").join(expected); 19 | let out = path.join("out.wasm"); 20 | let ok = fs::read(&expected).unwrap(); 21 | let actual = fs::read(&out).unwrap(); 22 | if ok != actual { 23 | use std::env; 24 | use std::io::Write; 25 | if env::var("REGENERATE_GOLDENFILES").is_ok() { 26 | let mut f = fs::File::create(&expected).unwrap(); 27 | f.write_all(&actual).unwrap(); 28 | } else { 29 | panic!( 30 | "ic_wasm did not result in expected wasm file: {} != {}. Run \"REGENERATE_GOLDENFILES=1 cargo test\" to update the wasm files", 31 | expected.display(), 32 | out.display() 33 | ); 34 | } 35 | } 36 | } 37 | 38 | fn assert_functions_are_named() { 39 | let path = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests"); 40 | let out = path.join("out.wasm"); 41 | 42 | let module = walrus::Module::from_file(out).unwrap(); 43 | let name_count = module.funcs.iter().filter(|f| f.name.is_some()).count(); 44 | let total = module.funcs.iter().count(); 45 | // Walrus doesn't give direct access to the name section, but as a proxy 46 | // just check that moste functions have names. 47 | assert!( 48 | name_count > total / 2, 49 | "Module has {total} functions but only {name_count} have names." 50 | ) 51 | } 52 | 53 | #[test] 54 | fn instrumentation() { 55 | wasm_input("motoko.wasm", true) 56 | .arg("instrument") 57 | .assert() 58 | .success(); 59 | assert_wasm("motoko-instrument.wasm"); 60 | wasm_input("motoko.wasm", true) 61 | .arg("instrument") 62 | .arg("-t") 63 | .arg("schedule_copying_gc") 64 | .assert() 65 | .success(); 66 | assert_wasm("motoko-gc-instrument.wasm"); 67 | wasm_input("motoko-region.wasm", true) 68 | .arg("instrument") 69 | .arg("-s") 70 | .arg("16") 71 | .assert() 72 | .success(); 73 | assert_wasm("motoko-region-instrument.wasm"); 74 | wasm_input("wat.wasm", true) 75 | .arg("instrument") 76 | .assert() 77 | .success(); 78 | assert_wasm("wat-instrument.wasm"); 79 | wasm_input("wat.wasm.gz", true) 80 | .arg("instrument") 81 | .assert() 82 | .success(); 83 | assert_wasm("wat-instrument.wasm"); 84 | wasm_input("rust.wasm", true) 85 | .arg("instrument") 86 | .assert() 87 | .success(); 88 | assert_wasm("rust-instrument.wasm"); 89 | wasm_input("rust-region.wasm", true) 90 | .arg("instrument") 91 | .arg("-s") 92 | .arg("1") 93 | .assert() 94 | .success(); 95 | assert_wasm("rust-region-instrument.wasm"); 96 | } 97 | 98 | #[test] 99 | fn shrink() { 100 | wasm_input("motoko.wasm", true) 101 | .arg("shrink") 102 | .assert() 103 | .success(); 104 | assert_wasm("motoko-shrink.wasm"); 105 | wasm_input("wat.wasm", true) 106 | .arg("shrink") 107 | .assert() 108 | .success(); 109 | assert_wasm("wat-shrink.wasm"); 110 | wasm_input("wat.wasm.gz", true) 111 | .arg("shrink") 112 | .assert() 113 | .success(); 114 | assert_wasm("wat-shrink.wasm"); 115 | wasm_input("rust.wasm", true) 116 | .arg("shrink") 117 | .assert() 118 | .success(); 119 | assert_wasm("rust-shrink.wasm"); 120 | wasm_input("classes.wasm", true) 121 | .arg("shrink") 122 | .assert() 123 | .success(); 124 | assert_wasm("classes-shrink.wasm"); 125 | } 126 | #[test] 127 | fn optimize() { 128 | let expected_metadata = r#"icp:public candid:service 129 | icp:private candid:args 130 | icp:private motoko:stable-types 131 | icp:private motoko:compiler 132 | "#; 133 | 134 | wasm_input("classes.wasm", true) 135 | .arg("optimize") 136 | .arg("O3") 137 | .arg("--inline-functions-with-loops") 138 | .arg("--always-inline-max-function-size") 139 | .arg("100") 140 | .assert() 141 | .success(); 142 | assert_wasm("classes-optimize.wasm"); 143 | wasm_input("ok/classes-optimize.wasm", false) 144 | .arg("metadata") 145 | .assert() 146 | .stdout(expected_metadata) 147 | .success(); 148 | wasm_input("classes.wasm", true) 149 | .arg("optimize") 150 | .arg("O3") 151 | .arg("--keep-name-section") 152 | .assert() 153 | .success(); 154 | assert_wasm("classes-optimize-names.wasm"); 155 | } 156 | 157 | #[test] 158 | fn resource() { 159 | wasm_input("motoko.wasm", true) 160 | .arg("resource") 161 | .arg("--remove-cycles-transfer") 162 | .arg("--limit-stable-memory-page") 163 | .arg("32") 164 | .assert() 165 | .success(); 166 | assert_wasm("motoko-limit.wasm"); 167 | wasm_input("wat.wasm", true) 168 | .arg("resource") 169 | .arg("--remove-cycles-transfer") 170 | .arg("--limit-stable-memory-page") 171 | .arg("32") 172 | .assert() 173 | .success(); 174 | assert_wasm("wat-limit.wasm"); 175 | wasm_input("wat.wasm.gz", true) 176 | .arg("resource") 177 | .arg("--remove-cycles-transfer") 178 | .arg("--limit-stable-memory-page") 179 | .arg("32") 180 | .assert() 181 | .success(); 182 | assert_wasm("wat-limit.wasm"); 183 | wasm_input("rust.wasm", true) 184 | .arg("resource") 185 | .arg("--remove-cycles-transfer") 186 | .arg("--limit-stable-memory-page") 187 | .arg("32") 188 | .assert() 189 | .success(); 190 | assert_wasm("rust-limit.wasm"); 191 | wasm_input("classes.wasm", true) 192 | .arg("resource") 193 | .arg("--remove-cycles-transfer") 194 | .arg("--limit-stable-memory-page") 195 | .arg("32") 196 | .assert() 197 | .success(); 198 | assert_wasm("classes-limit.wasm"); 199 | let test_canister_id = "zz73r-nyaaa-aabbb-aaaca-cai"; 200 | let management_canister_id = "aaaaa-aa"; 201 | wasm_input("classes.wasm", true) 202 | .arg("resource") 203 | .arg("--playground-backend-redirect") 204 | .arg(test_canister_id) 205 | .assert() 206 | .success(); 207 | assert_wasm("classes-redirect.wasm"); 208 | wasm_input("classes.wasm", true) 209 | .arg("resource") 210 | .arg("--playground-backend-redirect") 211 | .arg(management_canister_id) 212 | .assert() 213 | .success(); 214 | assert_wasm("classes-nop-redirect.wasm"); 215 | wasm_input("evm.wasm", true) 216 | .arg("resource") 217 | .arg("--playground-backend-redirect") 218 | .arg(test_canister_id) 219 | .assert() 220 | .success(); 221 | assert_wasm("evm-redirect.wasm"); 222 | } 223 | 224 | #[test] 225 | fn info() { 226 | let expected = r#"Number of types: 6 227 | Number of globals: 1 228 | 229 | Number of data sections: 3 230 | Size of data sections: 35 bytes 231 | 232 | Number of functions: 9 233 | Number of callbacks: 0 234 | Start function: None 235 | Exported methods: [ 236 | "canister_query get (func_5)", 237 | "canister_update inc (func_6)", 238 | "canister_update set (func_7)", 239 | ] 240 | 241 | Imported IC0 System API: [ 242 | "msg_reply", 243 | "msg_reply_data_append", 244 | "msg_arg_data_size", 245 | "msg_arg_data_copy", 246 | "trap", 247 | ] 248 | 249 | Custom sections with size: [] 250 | "#; 251 | wasm_input("wat.wasm", false) 252 | .arg("info") 253 | .assert() 254 | .stdout(expected) 255 | .success(); 256 | wasm_input("wat.wasm.gz", false) 257 | .arg("info") 258 | .assert() 259 | .stdout(expected) 260 | .success(); 261 | } 262 | 263 | #[test] 264 | fn json_info() { 265 | let expected = r#"{ 266 | "language": "Unknown", 267 | "number_of_types": 6, 268 | "number_of_globals": 1, 269 | "number_of_data_sections": 3, 270 | "size_of_data_sections": 35, 271 | "number_of_functions": 9, 272 | "number_of_callbacks": 0, 273 | "start_function": null, 274 | "exported_methods": [ 275 | { 276 | "name": "canister_query get", 277 | "internal_name": "func_5" 278 | }, 279 | { 280 | "name": "canister_update inc", 281 | "internal_name": "func_6" 282 | }, 283 | { 284 | "name": "canister_update set", 285 | "internal_name": "func_7" 286 | } 287 | ], 288 | "imported_ic0_system_api": [ 289 | "msg_reply", 290 | "msg_reply_data_append", 291 | "msg_arg_data_size", 292 | "msg_arg_data_copy", 293 | "trap" 294 | ], 295 | "custom_sections": [] 296 | } 297 | "#; 298 | wasm_input("wat.wasm", false) 299 | .arg("info") 300 | .arg("--json") 301 | .assert() 302 | .stdout(expected) 303 | .success(); 304 | wasm_input("wat.wasm.gz", false) 305 | .arg("info") 306 | .arg("--json") 307 | .assert() 308 | .stdout(expected) 309 | .success(); 310 | } 311 | 312 | #[test] 313 | fn metadata() { 314 | // List metadata 315 | wasm_input("motoko.wasm", false) 316 | .arg("metadata") 317 | .assert() 318 | .stdout( 319 | r#"icp:public candid:service 320 | icp:private motoko:stable-types 321 | icp:private motoko:compiler 322 | icp:public candid:args 323 | "#, 324 | ) 325 | .success(); 326 | // Get motoko:compiler content 327 | wasm_input("motoko.wasm", false) 328 | .arg("metadata") 329 | .arg("motoko:compiler") 330 | .assert() 331 | .stdout("0.10.0\n") 332 | .success(); 333 | // Get a non-existed metadata 334 | wasm_input("motoko.wasm", false) 335 | .arg("metadata") 336 | .arg("whatever") 337 | .assert() 338 | .stdout("Cannot find metadata whatever\n") 339 | .success(); 340 | // Overwrite motoko:compiler 341 | wasm_input("motoko.wasm", true) 342 | .arg("metadata") 343 | .arg("motoko:compiler") 344 | .arg("-d") 345 | .arg("hello") 346 | .assert() 347 | .success(); 348 | wasm_input("out.wasm", false) 349 | .arg("metadata") 350 | .arg("motoko:compiler") 351 | .assert() 352 | .stdout("hello\n") 353 | .success(); 354 | // Add a new metadata 355 | wasm_input("motoko.wasm", true) 356 | .arg("metadata") 357 | .arg("whatever") 358 | .arg("-d") 359 | .arg("what?") 360 | .arg("-v") 361 | .arg("public") 362 | .assert() 363 | .success(); 364 | wasm_input("out.wasm", false) 365 | .arg("metadata") 366 | .assert() 367 | .stdout( 368 | r#"icp:public candid:service 369 | icp:private motoko:stable-types 370 | icp:private motoko:compiler 371 | icp:public candid:args 372 | icp:public whatever 373 | "#, 374 | ) 375 | .success(); 376 | } 377 | 378 | #[test] 379 | fn metadata_keep_name_section() { 380 | for file in [ 381 | "motoko.wasm", 382 | "classes.wasm", 383 | "motoko-region.wasm", 384 | "rust.wasm", 385 | ] { 386 | wasm_input(file, true) 387 | .arg("metadata") 388 | .arg("foo") 389 | .arg("-d") 390 | .arg("hello") 391 | .arg("--keep-name-section") 392 | .assert() 393 | .success(); 394 | assert_functions_are_named(); 395 | } 396 | } 397 | -------------------------------------------------------------------------------- /tests/wat.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfinity/ic-wasm/4c52e75c12bb730e795d8a4c2862987f4a9524a3/tests/wat.wasm -------------------------------------------------------------------------------- /tests/wat.wasm.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfinity/ic-wasm/4c52e75c12bb730e795d8a4c2862987f4a9524a3/tests/wat.wasm.gz --------------------------------------------------------------------------------