├── .editorconfig ├── .github ├── CODEOWNERS ├── dependabot.yml └── workflows │ └── check.yml ├── .gitignore ├── BENCHMARKS.md ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── benches ├── execution.rs ├── fixtures │ ├── wasm │ │ ├── contract_terminate.wasm │ │ ├── contract_transfer.wasm │ │ ├── coremark_minimal.wasm │ │ ├── dns.wasm │ │ ├── erc1155.wasm │ │ ├── erc20.wasm │ │ ├── erc721.wasm │ │ ├── many_blocks.wasm │ │ ├── multisig.wasm │ │ ├── proxy.wasm │ │ ├── rand_extension.wasm │ │ ├── trait_erc20.wasm │ │ └── wasm_kernel.wasm │ └── wat │ │ ├── count_until.wat │ │ ├── factorial.wat │ │ ├── fibonacci.wat │ │ ├── global_bump.wat │ │ ├── memory-vec-add.wat │ │ └── recursive_ok.wat └── instrumentation.rs ├── justfile ├── rustfmt.toml ├── src ├── export_globals.rs ├── gas_metering │ ├── backend.rs │ ├── mod.rs │ └── validation.rs ├── lib.rs └── stack_limiter │ ├── max_height.rs │ ├── mod.rs │ └── thunk.rs └── tests ├── diff.rs ├── expectations ├── gas │ ├── branch_host_fn.wat │ ├── branch_mut_global.wat │ ├── call_host_fn.wat │ ├── call_mut_global.wat │ ├── ifs_host_fn.wat │ ├── ifs_mut_global.wat │ ├── simple_host_fn.wat │ ├── simple_mut_global.wat │ ├── start_host_fn.wat │ └── start_mut_global.wat └── stack-height │ ├── empty_functions.wat │ ├── global.wat │ ├── imports.wat │ ├── many_locals.wat │ ├── simple.wat │ ├── start.wat │ └── table.wat ├── fixtures ├── gas │ ├── branch.wat │ ├── call.wat │ ├── ifs.wat │ ├── simple.wat │ └── start.wat └── stack-height │ ├── empty_functions.wat │ ├── global.wat │ ├── imports.wat │ ├── many_locals.wat │ ├── simple.wat │ ├── start.wat │ └── table.wat └── overhead.rs /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | [*] 3 | indent_style=tab 4 | indent_size=tab 5 | tab_width=4 6 | end_of_line=lf 7 | charset=utf-8 8 | trim_trailing_whitespace=true 9 | max_line_length=100 10 | insert_final_newline=true 11 | 12 | [*.md] 13 | max_line_length=80 14 | indent_style=space 15 | indent_size=2 16 | 17 | [*.yml] 18 | indent_style=space 19 | indent_size=2 20 | tab_width=8 21 | end_of_line=lf 22 | 23 | [*.sh] 24 | indent_style=space 25 | indent_size=2 26 | tab_width=8 27 | end_of_line=lf 28 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # For details about syntax, see: 2 | # https://help.github.com/en/articles/about-code-owners 3 | 4 | * @athei @pepyakin 5 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | labels: [] 6 | schedule: 7 | interval: "daily" 8 | - package-ecosystem: github-actions 9 | directory: '/' 10 | schedule: 11 | interval: daily 12 | -------------------------------------------------------------------------------- /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | name: Check 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | rustfmt: 14 | runs-on: "ubuntu_20_64_core" 15 | steps: 16 | - name: Install Rust toolchain 17 | uses: actions-rs/toolchain@v1 18 | with: 19 | profile: minimal 20 | toolchain: nightly 21 | components: rustfmt 22 | 23 | - uses: actions/checkout@v4 24 | 25 | - name: Fmt 26 | uses: actions-rs/cargo@v1 27 | with: 28 | toolchain: nightly 29 | command: fmt 30 | args: --all -- --check 31 | 32 | clippy: 33 | runs-on: "ubuntu_20_64_core" 34 | steps: 35 | - name: Install Rust toolchain 36 | uses: actions-rs/toolchain@v1 37 | with: 38 | profile: minimal 39 | toolchain: stable 40 | components: clippy 41 | default: true 42 | 43 | - uses: actions/checkout@v4 44 | 45 | - name: Clippy 46 | uses: actions-rs/clippy-check@v1 47 | with: 48 | token: ${{ secrets.GITHUB_TOKEN }} 49 | args: --all-targets --all-features -- -D warnings 50 | 51 | build: 52 | runs-on: "ubuntu_20_64_core" 53 | steps: 54 | - name: Install Rust toolchain 55 | uses: actions-rs/toolchain@v1 56 | with: 57 | profile: minimal 58 | target: wasm32-unknown-unknown 59 | toolchain: stable 60 | default: true 61 | 62 | - uses: actions/checkout@v4 63 | 64 | - name: Cargo build 65 | uses: actions-rs/cargo@v1 66 | with: 67 | command: build 68 | 69 | - name: Cargo build (std) 70 | uses: actions-rs/cargo@v1 71 | with: 72 | command: build 73 | args: --all-features 74 | 75 | - name: Cargo build (no_std) 76 | uses: actions-rs/cargo@v1 77 | with: 78 | command: build 79 | args: --no-default-features 80 | 81 | - name: Cargo build (wasm) 82 | uses: actions-rs/cargo@v1 83 | with: 84 | command: build 85 | args: --no-default-features --target wasm32-unknown-unknown 86 | 87 | test: 88 | strategy: 89 | matrix: 90 | os: ["ubuntu_20_64_core", "macos-latest", "windows-latest"] 91 | runs-on: ${{ matrix.os }} 92 | steps: 93 | - name: Install Rust toolchain 94 | uses: actions-rs/toolchain@v1 95 | with: 96 | profile: minimal 97 | toolchain: stable 98 | default: true 99 | 100 | - name: Set git to use LF 101 | run: | 102 | git config --global core.autocrlf false 103 | git config --global core.eol lf 104 | 105 | - uses: actions/checkout@v4 106 | 107 | - name: Cargo test 108 | uses: actions-rs/cargo@v1 109 | with: 110 | command: test 111 | args: --all-features 112 | 113 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /Cargo.lock 2 | target 3 | .cargo 4 | .DS_Store 5 | .idea 6 | .vscode 7 | *~ -------------------------------------------------------------------------------- /BENCHMARKS.md: -------------------------------------------------------------------------------- 1 | # Benchmarks 2 | 3 | ## Table of Contents 4 | 5 | - [Benchmark Results](#benchmark-results) 6 | - [coremark, instrumented](#coremark,-instrumented) 7 | - [recursive_ok, instrumented](#recursive_ok,-instrumented) 8 | - [fibonacci_recursive, instrumented](#fibonacci_recursive,-instrumented) 9 | - [factorial_recursive, instrumented](#factorial_recursive,-instrumented) 10 | - [count_until, instrumented](#count_until,-instrumented) 11 | - [memory_vec_add, instrumented](#memory_vec_add,-instrumented) 12 | - [wasm_kernel::tiny_keccak, instrumented](#wasm_kernel::tiny_keccak,-instrumented) 13 | - [global_bump, instrumented](#global_bump,-instrumented) 14 | 15 | ## Instrumented Modules sizes 16 | 17 | | fixture | original size | gas metered/host fn | gas metered/mut global | size diff | 18 | |------------------------------|------------------|---------------------|------------------------|-----------| 19 | | recursive_ok.wat | 0 kb | 0 kb (137%) | 0 kb (177%) | +29% | 20 | | count_until.wat | 0 kb | 0 kb (125%) | 0 kb (153%) | +21% | 21 | | global_bump.wat | 0 kb | 0 kb (123%) | 0 kb (145%) | +18% | 22 | | memory-vec-add.wat | 0 kb | 0 kb (116%) | 0 kb (134%) | +15% | 23 | | factorial.wat | 0 kb | 0 kb (125%) | 0 kb (145%) | +15% | 24 | | fibonacci.wat | 0 kb | 0 kb (121%) | 0 kb (134%) | +10% | 25 | | contract_terminate.wasm | 1 kb | 1 kb (110%) | 1 kb (112%) | +2% | 26 | | coremark_minimal.wasm | 7 kb | 8 kb (114%) | 8 kb (115%) | +0% | 27 | | trait_erc20.wasm | 10 kb | 11 kb (108%) | 11 kb (108%) | +0% | 28 | | rand_extension.wasm | 4 kb | 5 kb (109%) | 5 kb (109%) | +0% | 29 | | multisig.wasm | 27 kb | 30 kb (110%) | 30 kb (110%) | +0% | 30 | | wasm_kernel.wasm | 779 kb | 787 kb (100%) | 795 kb (101%) | +0% | 31 | | many_blocks.wasm | 1023 kb | 2389 kb (233%) | 2389 kb (233%) | +0% | 32 | | contract_transfer.wasm | 7 kb | 8 kb (113%) | 8 kb (113%) | +0% | 33 | | erc1155.wasm | 26 kb | 29 kb (111%) | 29 kb (111%) | +0% | 34 | | erc20.wasm | 9 kb | 10 kb (108%) | 10 kb (109%) | +0% | 35 | | dns.wasm | 10 kb | 11 kb (108%) | 11 kb (108%) | +0% | 36 | | proxy.wasm | 3 kb | 4 kb (108%) | 4 kb (109%) | +0% | 37 | | erc721.wasm | 13 kb | 14 kb (108%) | 14 kb (108%) | +0% | 38 | 39 | ## Benchmark Results 40 | 41 | ### coremark, instrumented 42 | 43 | | | `with host_function::Injector` | `with mutable_global::Injector` | 44 | |:-------|:----------------------------------------|:----------------------------------------- | 45 | | | `20.81 s` (✅ **1.00x**) | `20.20 s` (✅ **1.03x faster**) | 46 | 47 | ### recursive_ok, instrumented 48 | 49 | | | `with host_function::Injector` | `with mutable_global::Injector` | 50 | |:-------|:----------------------------------------|:----------------------------------------- | 51 | | | `367.11 us` (✅ **1.00x**) | `585.39 us` (❌ *1.59x slower*) | 52 | 53 | ### fibonacci_recursive, instrumented 54 | 55 | | | `with host_function::Injector` | `with mutable_global::Injector` | 56 | |:-------|:----------------------------------------|:----------------------------------------- | 57 | | | `9.15 us` (✅ **1.00x**) | `13.56 us` (❌ *1.48x slower*) | 58 | 59 | ### factorial_recursive, instrumented 60 | 61 | | | `with host_function::Injector` | `with mutable_global::Injector` | 62 | |:-------|:----------------------------------------|:----------------------------------------- | 63 | | | `1.50 us` (✅ **1.00x**) | `1.98 us` (❌ *1.32x slower*) | 64 | 65 | ### count_until, instrumented 66 | 67 | | | `with host_function::Injector` | `with mutable_global::Injector` | 68 | |:-------|:----------------------------------------|:----------------------------------------- | 69 | | | `5.03 ms` (✅ **1.00x**) | `8.13 ms` (❌ *1.62x slower*) | 70 | 71 | ### memory_vec_add, instrumented 72 | 73 | | | `with host_function::Injector` | `with mutable_global::Injector` | 74 | |:-------|:----------------------------------------|:----------------------------------------- | 75 | | | `6.21 ms` (✅ **1.00x**) | `8.45 ms` (❌ *1.36x slower*) | 76 | 77 | ### wasm_kernel::tiny_keccak, instrumented 78 | 79 | | | `with host_function::Injector` | `with mutable_global::Injector` | 80 | |:-------|:----------------------------------------|:----------------------------------------- | 81 | | | `925.22 us` (✅ **1.00x**) | `1.08 ms` (❌ *1.17x slower*) | 82 | 83 | ### global_bump, instrumented 84 | 85 | | | `with host_function::Injector` | `with mutable_global::Injector` | 86 | |:-------|:----------------------------------------|:----------------------------------------- | 87 | | | `3.79 ms` (✅ **1.00x**) | `7.03 ms` (❌ *1.86x slower*) | 88 | 89 | --- 90 | Made with [criterion-table](https://github.com/nu11ptr/criterion-table) 91 | 92 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | The semantic versioning guarantees cover the interface to the substrate runtime which 9 | includes this pallet as a dependency. This module will also add storage migrations whenever 10 | changes require it. Stability with regard to offchain tooling is explicitly excluded from 11 | this guarantee: For example adding a new field to an in-storage data structure will require 12 | changes to frontends to properly display it. However, those changes will still be regarded 13 | as a minor version bump. 14 | 15 | The interface provided to smart contracts will adhere to semver with one exception: Even 16 | major version bumps will be backwards compatible with regard to already deployed contracts. 17 | In other words: Upgrading this pallet will not break pre-existing contracts. 18 | 19 | ## [Unreleased] 20 | 21 | ### New 22 | 23 | - Add new gas metering method: mutable global + local gas function 24 | [#34](https://github.com/paritytech/wasm-instrument/pull/34) 25 | - Account for locals initialization costs 26 | [#38](https://github.com/paritytech/wasm-instrument/pull/38) 27 | 28 | ## [v0.3.0] 29 | 30 | ### Changed 31 | 32 | - Use 64bit arithmetic for per-block gas counter 33 | [#30](https://github.com/paritytech/wasm-instrument/pull/30) 34 | 35 | ## [v0.2.0] 2022-06-06 36 | 37 | ### Changed 38 | 39 | - Adjust debug information (if already parsed) when injecting gas metering 40 | [#16](https://github.com/paritytech/wasm-instrument/pull/16) 41 | 42 | ## [v0.1.1] 2022-01-18 43 | 44 | ### Fixed 45 | 46 | - Stack metering disregarded the activiation frame. 47 | [#2](https://github.com/paritytech/wasm-instrument/pull/2) 48 | 49 | ## [v0.1.0] 2022-01-11 50 | 51 | ### Changed 52 | 53 | - Created from [pwasm-utils](https://github.com/paritytech/wasm-utils) by removing unused code and cleaning up docs. 54 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wasm-instrument" 3 | version = "0.4.0" 4 | edition = "2021" 5 | rust-version = "1.56.1" 6 | authors = ["Parity Technologies "] 7 | license = "MIT OR Apache-2.0" 8 | description = "Instrument and transform wasm modules." 9 | keywords = ["wasm", "webassembly", "blockchain", "gas-metering", "parity"] 10 | categories = ["wasm", "no-std"] 11 | repository = "https://github.com/paritytech/wasm-instrument" 12 | include = ["src/**/*", "LICENSE-*", "README.md"] 13 | 14 | [[bench]] 15 | name = "instrumentation" 16 | harness = false 17 | path = "benches/instrumentation.rs" 18 | 19 | [[bench]] 20 | name = "execution" 21 | harness = false 22 | path = "benches/execution.rs" 23 | 24 | [profile.bench] 25 | lto = "fat" 26 | codegen-units = 1 27 | 28 | [dependencies] 29 | parity-wasm = { version = "0.45", default-features = false } 30 | 31 | [dev-dependencies] 32 | binaryen = "0.12" 33 | criterion = "0.5" 34 | diff = "0.1" 35 | pretty_assertions = "1" 36 | rand = "0.8" 37 | wat = "1" 38 | wasmparser = "0.206" 39 | wasmprinter = "0.200" 40 | wasmi = "0.31" 41 | 42 | [features] 43 | default = ["std"] 44 | std = ["parity-wasm/std"] 45 | sign_ext = ["parity-wasm/sign_ext"] 46 | 47 | [lib] 48 | bench = false 49 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 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, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising 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 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wasm-instrument 2 | 3 | A Rust library containing a collection of wasm module instrumentations and transformations 4 | mainly useful for wasm based block chains and smart contracts. 5 | 6 | ## Provided functionality 7 | 8 | This is a non exhaustive list of provided functionality. Please check out the [documentation](https://docs.rs/wasm-instrument/latest/wasm_instrument/) for details. 9 | 10 | ### Gas Metering 11 | 12 | Add gas metering to your platform by injecting the necessary code directly into the wasm module. This allows having a uniform gas metering implementation across different execution engines (interpreters, JIT compilers). 13 | 14 | ### Stack Height Limiter 15 | 16 | Neither the wasm standard nor any sufficiently complex execution engine specifies how many items on the wasm stack are supported before the execution aborts or malfunctions. Even the same execution engine on different operating systems or host architectures could support a different number of stack items and be well within its rights. 17 | 18 | This is the kind of indeterminism that can lead to consensus failures when used in a blockchain context. 19 | 20 | To address this issue we can inject some code that meters the stack height at runtime and aborts the execution when it reaches a predefined limit. Choosing this limit suffciently small so that it is smaller than what any reasonably parameterized execution engine would support solves the issue: All execution engines would reach the injected limit before hitting any implementation specific limitation. 21 | 22 | ## License 23 | 24 | `wasm-instrument` is distributed under the terms of both the MIT license and the 25 | Apache License (Version 2.0), at your choice. 26 | 27 | See LICENSE-APACHE, and LICENSE-MIT for details. 28 | 29 | ### Contribution 30 | 31 | Unless you explicitly state otherwise, any contribution intentionally submitted 32 | for inclusion in `wasm-instrument` by you, as defined in the Apache-2.0 license, shall be 33 | dual licensed as above, without any additional terms or conditions. 34 | -------------------------------------------------------------------------------- /benches/execution.rs: -------------------------------------------------------------------------------- 1 | use criterion::{ 2 | criterion_group, criterion_main, measurement::Measurement, Bencher, BenchmarkGroup, Criterion, 3 | }; 4 | use std::{ 5 | fs::read, 6 | path::PathBuf, 7 | time::{Duration, SystemTime, UNIX_EPOCH}, 8 | }; 9 | use wasm_instrument::{ 10 | gas_metering::{self, host_function, mutable_global, ConstantCostRules}, 11 | parity_wasm::{deserialize_buffer, elements::Module, serialize}, 12 | }; 13 | use wasmi::{ 14 | core::{Pages, TrapCode, F32}, 15 | Caller, Config, Engine, Instance, Linker, Memory, StackLimits, Store, TypedFunc, Value, 16 | }; 17 | 18 | /// Describes a gas metering strategy we want to benchmark. 19 | /// 20 | /// Most strategies just need a subset of these functions. Hence we added default 21 | /// implementations for all of them. 22 | trait MeteringStrategy { 23 | /// The wasmi config we should be using for this strategy. 24 | fn config() -> Config { 25 | Config::default() 26 | } 27 | 28 | /// The strategy may or may not want to instrument the module. 29 | fn instrument_module(module: Module) -> Module { 30 | module 31 | } 32 | 33 | /// The strategy might need to define additional host functions. 34 | fn define_host_funcs(_linker: &mut Linker) {} 35 | 36 | /// The strategy might need to do some initialization of the wasm instance. 37 | fn init_instance(_module: &mut BenchInstance) {} 38 | } 39 | 40 | /// Don't do any metering at all. This is helpful as a baseline. 41 | struct NoMetering; 42 | 43 | /// Use wasmi's builtin fuel metering. 44 | struct WasmiMetering; 45 | 46 | /// Instrument the module using [`host_function::Injector`]. 47 | struct HostFunctionMetering; 48 | 49 | /// Instrument the module using [`mutable_global::Injector`]. 50 | struct MutableGlobalMetering; 51 | 52 | impl MeteringStrategy for NoMetering {} 53 | 54 | impl MeteringStrategy for WasmiMetering { 55 | fn config() -> Config { 56 | let mut config = Config::default(); 57 | config.consume_fuel(true); 58 | config 59 | } 60 | 61 | fn init_instance(module: &mut BenchInstance) { 62 | module.store.add_fuel(u64::MAX).unwrap(); 63 | } 64 | } 65 | 66 | impl MeteringStrategy for HostFunctionMetering { 67 | fn instrument_module(module: Module) -> Module { 68 | let backend = host_function::Injector::new("env", "gas"); 69 | gas_metering::inject(module, backend, &ConstantCostRules::default()).unwrap() 70 | } 71 | 72 | fn define_host_funcs(linker: &mut Linker) { 73 | // the instrumentation relies on the existing of this function 74 | linker 75 | .func_wrap("env", "gas", |mut caller: Caller<'_, u64>, amount_consumed: u64| { 76 | let gas_remaining = caller.data_mut(); 77 | *gas_remaining = 78 | gas_remaining.checked_sub(amount_consumed).ok_or(TrapCode::OutOfFuel)?; 79 | Ok(()) 80 | }) 81 | .unwrap(); 82 | } 83 | } 84 | 85 | impl MeteringStrategy for MutableGlobalMetering { 86 | fn instrument_module(module: Module) -> Module { 87 | let backend = mutable_global::Injector::new("gas_left"); 88 | gas_metering::inject(module, backend, &ConstantCostRules::default()).unwrap() 89 | } 90 | 91 | fn init_instance(module: &mut BenchInstance) { 92 | // the instrumentation relies on the host to initialize the global with the gas limit 93 | // we just init to the maximum so it will never run out 94 | module 95 | .instance 96 | .get_global(&mut module.store, "gas_left") 97 | .unwrap() 98 | .set(&mut module.store, Value::I64(-1i64)) // the same as u64::MAX 99 | .unwrap(); 100 | } 101 | } 102 | 103 | /// A wasm instance ready to be benchmarked. 104 | struct BenchInstance { 105 | store: Store, 106 | instance: Instance, 107 | } 108 | 109 | impl BenchInstance { 110 | /// Create a new instance for the supplied metering strategy. 111 | /// 112 | /// `wasm`: The raw wasm module for the benchmark. 113 | /// `define_host_func`: In here the caller can define additional host function. 114 | fn new(wasm: &[u8], define_host_funcs: &H) -> Self 115 | where 116 | S: MeteringStrategy, 117 | H: Fn(&mut Linker), 118 | { 119 | let module = deserialize_buffer(wasm).unwrap(); 120 | let instrumented_module = S::instrument_module(module); 121 | let input = serialize(instrumented_module).unwrap(); 122 | let mut config = S::config(); 123 | config.set_stack_limits(StackLimits::new(1024, 1024 * 1024, 64 * 1024).unwrap()); 124 | let engine = Engine::new(&config); 125 | let module = wasmi::Module::new(&engine, &mut &input[..]).unwrap(); 126 | let mut linker = Linker::new(&engine); 127 | S::define_host_funcs(&mut linker); 128 | define_host_funcs(&mut linker); 129 | // init host state with maximum gas_left (only used by host_function instrumentation) 130 | let mut store = Store::new(&engine, u64::MAX); 131 | let instance = linker.instantiate(&mut store, &module).unwrap().start(&mut store).unwrap(); 132 | let mut bench_module = Self { store, instance }; 133 | S::init_instance(&mut bench_module); 134 | bench_module 135 | } 136 | } 137 | 138 | /// Runs a benchmark for every strategy. 139 | /// 140 | /// We require the closures to implement `Fn` as they are executed for every strategy and we 141 | /// don't want them to change in between. 142 | /// 143 | /// `group`: The benchmark group within the benchmarks will be executed. 144 | /// `wasm`: The raw wasm module for the benchmark. 145 | /// `define_host_func`: In here the caller can define additional host function. 146 | /// `f`: In here the user should perform the benchmark. Will be executed for every strategy. 147 | fn for_strategies(group: &mut BenchmarkGroup, wasm: &[u8], define_host_funcs: H, f: F) 148 | where 149 | M: Measurement, 150 | H: Fn(&mut Linker), 151 | F: Fn(&mut Bencher, &mut BenchInstance), 152 | { 153 | let mut module = BenchInstance::new::(wasm, &define_host_funcs); 154 | group.bench_function("no_metering", |bench| f(bench, &mut module)); 155 | 156 | let mut module = BenchInstance::new::(wasm, &define_host_funcs); 157 | group.bench_function("wasmi_builtin", |bench| f(bench, &mut module)); 158 | 159 | let mut module = BenchInstance::new::(wasm, &define_host_funcs); 160 | group.bench_function("host_function", |bench| f(bench, &mut module)); 161 | 162 | let mut module = BenchInstance::new::(wasm, &define_host_funcs); 163 | group.bench_function("mutable_global", |bench| f(bench, &mut module)); 164 | } 165 | 166 | /// Converts the `.wat` encoded `bytes` into `.wasm` encoded bytes. 167 | fn wat2wasm(bytes: &[u8]) -> Vec { 168 | wat::parse_bytes(bytes).unwrap().into_owned() 169 | } 170 | 171 | fn fixture_dir() -> PathBuf { 172 | let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); 173 | path.push("benches"); 174 | path.push("fixtures"); 175 | path.push("wasm"); 176 | path 177 | } 178 | 179 | fn gas_metered_coremark(c: &mut Criterion) { 180 | let mut group = c.benchmark_group("coremark"); 181 | // Benchmark host_function::Injector 182 | let wasm_filename = "coremark_minimal.wasm"; 183 | let bytes = read(fixture_dir().join(wasm_filename)).unwrap(); 184 | let define_host_funcs = |linker: &mut Linker| { 185 | linker 186 | .func_wrap("env", "clock_ms", || { 187 | SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() as u64 188 | }) 189 | .unwrap(); 190 | }; 191 | for_strategies(&mut group, &bytes, define_host_funcs, |bench, module| { 192 | bench.iter(|| { 193 | let run = module.instance.get_typed_func::<(), F32>(&mut module.store, "run").unwrap(); 194 | // Call the wasm! 195 | run.call(&mut module.store, ()).unwrap(); 196 | }) 197 | }); 198 | } 199 | 200 | fn gas_metered_recursive_ok(c: &mut Criterion) { 201 | let mut group = c.benchmark_group("recursive_ok"); 202 | const RECURSIVE_DEPTH: i32 = 8000; 203 | let wasm_bytes = wat2wasm(include_bytes!("fixtures/wat/recursive_ok.wat")); 204 | for_strategies( 205 | &mut group, 206 | &wasm_bytes, 207 | |_| {}, 208 | |bench, module| { 209 | let bench_call = 210 | module.instance.get_typed_func::(&module.store, "call").unwrap(); 211 | bench.iter(|| { 212 | let result = bench_call.call(&mut module.store, RECURSIVE_DEPTH).unwrap(); 213 | assert_eq!(result, 0); 214 | }) 215 | }, 216 | ); 217 | } 218 | 219 | fn gas_metered_fibonacci_recursive(c: &mut Criterion) { 220 | let mut group = c.benchmark_group("fibonacci_recursive"); 221 | const FIBONACCI_REC_N: i64 = 10; 222 | let wasm_bytes = wat2wasm(include_bytes!("fixtures/wat/fibonacci.wat")); 223 | for_strategies( 224 | &mut group, 225 | &wasm_bytes, 226 | |_| {}, 227 | |bench, module| { 228 | let bench_call = module 229 | .instance 230 | .get_typed_func::(&module.store, "fib_recursive") 231 | .unwrap(); 232 | bench.iter(|| { 233 | bench_call.call(&mut module.store, FIBONACCI_REC_N).unwrap(); 234 | }); 235 | }, 236 | ); 237 | } 238 | 239 | fn gas_metered_fac_recursive(c: &mut Criterion) { 240 | let mut group = c.benchmark_group("factorial_recursive"); 241 | let wasm_bytes = wat2wasm(include_bytes!("fixtures/wat/factorial.wat")); 242 | for_strategies( 243 | &mut group, 244 | &wasm_bytes, 245 | |_| {}, 246 | |bench, module| { 247 | let fac = module 248 | .instance 249 | .get_typed_func::(&module.store, "recursive_factorial") 250 | .unwrap(); 251 | bench.iter(|| { 252 | let result = fac.call(&mut module.store, 25).unwrap(); 253 | assert_eq!(result, 7034535277573963776); 254 | }) 255 | }, 256 | ); 257 | } 258 | 259 | fn gas_metered_count_until(c: &mut Criterion) { 260 | const COUNT_UNTIL: i32 = 100_000; 261 | let mut group = c.benchmark_group("count_until"); 262 | let wasm_bytes = wat2wasm(include_bytes!("fixtures/wat/count_until.wat")); 263 | for_strategies( 264 | &mut group, 265 | &wasm_bytes, 266 | |_| {}, 267 | |bench, module| { 268 | let count_until = module 269 | .instance 270 | .get_typed_func::(&module.store, "count_until") 271 | .unwrap(); 272 | bench.iter(|| { 273 | let result = count_until.call(&mut module.store, COUNT_UNTIL).unwrap(); 274 | assert_eq!(result, COUNT_UNTIL); 275 | }) 276 | }, 277 | ); 278 | } 279 | 280 | fn gas_metered_vec_add(c: &mut Criterion) { 281 | fn test_for( 282 | b: &mut Bencher, 283 | vec_add: TypedFunc<(i32, i32, i32, i32), ()>, 284 | mut store: &mut Store, 285 | mem: Memory, 286 | len: usize, 287 | vec_a: A, 288 | vec_b: B, 289 | ) where 290 | A: IntoIterator, 291 | B: IntoIterator, 292 | { 293 | use core::mem::size_of; 294 | 295 | let ptr_result = 10; 296 | let len_result = len * size_of::(); 297 | let ptr_a = ptr_result + len_result; 298 | let len_a = len * size_of::(); 299 | let ptr_b = ptr_a + len_a; 300 | 301 | // Reset `result` buffer to zeros: 302 | mem.data_mut(&mut store)[ptr_result..ptr_result + (len * size_of::())].fill(0); 303 | // Initialize `a` buffer: 304 | for (n, a) in vec_a.into_iter().take(len).enumerate() { 305 | mem.write(&mut store, ptr_a + (n * size_of::()), &a.to_le_bytes()).unwrap(); 306 | } 307 | // Initialize `b` buffer: 308 | for (n, b) in vec_b.into_iter().take(len).enumerate() { 309 | mem.write(&mut store, ptr_b + (n * size_of::()), &b.to_le_bytes()).unwrap(); 310 | } 311 | 312 | // Prepare parameters and all Wasm `vec_add`: 313 | let params = (ptr_result as i32, ptr_a as i32, ptr_b as i32, len as i32); 314 | b.iter(|| { 315 | vec_add.call(&mut store, params).unwrap(); 316 | }); 317 | 318 | // Validate the result buffer: 319 | for n in 0..len { 320 | let mut buffer4 = [0x00; 4]; 321 | let mut buffer8 = [0x00; 8]; 322 | let a = { 323 | mem.read(&store, ptr_a + (n * size_of::()), &mut buffer4).unwrap(); 324 | i32::from_le_bytes(buffer4) 325 | }; 326 | let b = { 327 | mem.read(&store, ptr_b + (n * size_of::()), &mut buffer4).unwrap(); 328 | i32::from_le_bytes(buffer4) 329 | }; 330 | let actual_result = { 331 | mem.read(&store, ptr_result + (n * size_of::()), &mut buffer8).unwrap(); 332 | i64::from_le_bytes(buffer8) 333 | }; 334 | let expected_result = (a as i64) + (b as i64); 335 | assert_eq!( 336 | expected_result, actual_result, 337 | "given a = {a} and b = {b}, results diverge at index {n}" 338 | ); 339 | } 340 | } 341 | 342 | let mut group = c.benchmark_group("memory_vec_add"); 343 | let wasm_bytes = wat2wasm(include_bytes!("fixtures/wat/memory-vec-add.wat")); 344 | const LEN: usize = 100_000; 345 | 346 | for_strategies( 347 | &mut group, 348 | &wasm_bytes, 349 | |_| {}, 350 | |bench, module| { 351 | let vec_add = module 352 | .instance 353 | .get_typed_func::<(i32, i32, i32, i32), ()>(&module.store, "vec_add") 354 | .unwrap(); 355 | let mem = module.instance.get_memory(&module.store, "mem").unwrap(); 356 | mem.grow(&mut module.store, Pages::new(25).unwrap()).unwrap(); 357 | test_for( 358 | bench, 359 | vec_add, 360 | &mut module.store, 361 | mem, 362 | LEN, 363 | (0..LEN).map(|i| (i * i) as i32), 364 | (0..LEN).map(|i| (i * 10) as i32), 365 | ) 366 | }, 367 | ); 368 | } 369 | 370 | fn gas_metered_tiny_keccak(c: &mut Criterion) { 371 | let mut group = c.benchmark_group("wasm_kernel::tiny_keccak"); 372 | let wasm_filename = "wasm_kernel.wasm"; 373 | let wasm_bytes = read(fixture_dir().join(wasm_filename)).unwrap(); 374 | for_strategies( 375 | &mut group, 376 | &wasm_bytes, 377 | |_| {}, 378 | |bench, module| { 379 | let prepare = module 380 | .instance 381 | .get_typed_func::<(), i32>(&module.store, "prepare_tiny_keccak") 382 | .unwrap(); 383 | let keccak = module 384 | .instance 385 | .get_typed_func::(&module.store, "bench_tiny_keccak") 386 | .unwrap(); 387 | let test_data_ptr = prepare.call(&mut module.store, ()).unwrap(); 388 | bench.iter(|| { 389 | keccak.call(&mut module.store, test_data_ptr).unwrap(); 390 | }) 391 | }, 392 | ); 393 | } 394 | 395 | fn gas_metered_global_bump(c: &mut Criterion) { 396 | const BUMP_AMOUNT: i32 = 100_000; 397 | let mut group = c.benchmark_group("global_bump"); 398 | let wasm_bytes = wat2wasm(include_bytes!("fixtures/wat/global_bump.wat")); 399 | for_strategies( 400 | &mut group, 401 | &wasm_bytes, 402 | |_| {}, 403 | |bench, module| { 404 | let bump = module.instance.get_typed_func::(&module.store, "bump").unwrap(); 405 | bench.iter(|| { 406 | let result = bump.call(&mut module.store, BUMP_AMOUNT).unwrap(); 407 | assert_eq!(result, BUMP_AMOUNT); 408 | }) 409 | }, 410 | ); 411 | } 412 | 413 | criterion_group!( 414 | name = coremark; 415 | config = Criterion::default() 416 | .sample_size(10) 417 | .measurement_time(Duration::from_millis(275000)) 418 | .warm_up_time(Duration::from_millis(1000)); 419 | targets = 420 | gas_metered_coremark, 421 | ); 422 | criterion_group!( 423 | name = wasmi_fixtures; 424 | config = Criterion::default() 425 | .sample_size(10) 426 | .measurement_time(Duration::from_millis(250000)) 427 | .warm_up_time(Duration::from_millis(1000)); 428 | targets = 429 | gas_metered_recursive_ok, 430 | gas_metered_fibonacci_recursive, 431 | gas_metered_fac_recursive, 432 | gas_metered_count_until, 433 | gas_metered_vec_add, 434 | gas_metered_tiny_keccak, 435 | gas_metered_global_bump, 436 | ); 437 | criterion_main!(coremark, wasmi_fixtures); 438 | -------------------------------------------------------------------------------- /benches/fixtures/wasm/contract_terminate.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/wasm-instrument/0b222d76fde9cd928534374419a8555f9214830c/benches/fixtures/wasm/contract_terminate.wasm -------------------------------------------------------------------------------- /benches/fixtures/wasm/contract_transfer.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/wasm-instrument/0b222d76fde9cd928534374419a8555f9214830c/benches/fixtures/wasm/contract_transfer.wasm -------------------------------------------------------------------------------- /benches/fixtures/wasm/coremark_minimal.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/wasm-instrument/0b222d76fde9cd928534374419a8555f9214830c/benches/fixtures/wasm/coremark_minimal.wasm -------------------------------------------------------------------------------- /benches/fixtures/wasm/dns.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/wasm-instrument/0b222d76fde9cd928534374419a8555f9214830c/benches/fixtures/wasm/dns.wasm -------------------------------------------------------------------------------- /benches/fixtures/wasm/erc1155.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/wasm-instrument/0b222d76fde9cd928534374419a8555f9214830c/benches/fixtures/wasm/erc1155.wasm -------------------------------------------------------------------------------- /benches/fixtures/wasm/erc20.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/wasm-instrument/0b222d76fde9cd928534374419a8555f9214830c/benches/fixtures/wasm/erc20.wasm -------------------------------------------------------------------------------- /benches/fixtures/wasm/erc721.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/wasm-instrument/0b222d76fde9cd928534374419a8555f9214830c/benches/fixtures/wasm/erc721.wasm -------------------------------------------------------------------------------- /benches/fixtures/wasm/many_blocks.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/wasm-instrument/0b222d76fde9cd928534374419a8555f9214830c/benches/fixtures/wasm/many_blocks.wasm -------------------------------------------------------------------------------- /benches/fixtures/wasm/multisig.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/wasm-instrument/0b222d76fde9cd928534374419a8555f9214830c/benches/fixtures/wasm/multisig.wasm -------------------------------------------------------------------------------- /benches/fixtures/wasm/proxy.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/wasm-instrument/0b222d76fde9cd928534374419a8555f9214830c/benches/fixtures/wasm/proxy.wasm -------------------------------------------------------------------------------- /benches/fixtures/wasm/rand_extension.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/wasm-instrument/0b222d76fde9cd928534374419a8555f9214830c/benches/fixtures/wasm/rand_extension.wasm -------------------------------------------------------------------------------- /benches/fixtures/wasm/trait_erc20.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/wasm-instrument/0b222d76fde9cd928534374419a8555f9214830c/benches/fixtures/wasm/trait_erc20.wasm -------------------------------------------------------------------------------- /benches/fixtures/wasm/wasm_kernel.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/wasm-instrument/0b222d76fde9cd928534374419a8555f9214830c/benches/fixtures/wasm/wasm_kernel.wasm -------------------------------------------------------------------------------- /benches/fixtures/wat/count_until.wat: -------------------------------------------------------------------------------- 1 | ;; Exports a function `count_until` that takes an input `n`. 2 | ;; The exported function counts an integer `n` times and then returns `n`. 3 | (module 4 | (func $count_until (export "count_until") (param $limit i32) (result i32) 5 | (local $counter i32) 6 | (block 7 | (loop 8 | (br_if 9 | 1 10 | (i32.eq 11 | (local.tee $counter 12 | (i32.add 13 | (local.get $counter) 14 | (i32.const 1) 15 | ) 16 | ) 17 | (local.get $limit) 18 | ) 19 | ) 20 | (br 0) 21 | ) 22 | ) 23 | (return (local.get $counter)) 24 | ) 25 | ) 26 | -------------------------------------------------------------------------------- /benches/fixtures/wat/factorial.wat: -------------------------------------------------------------------------------- 1 | (module 2 | ;; Iterative factorial function, does not use recursion. 3 | (func (export "iterative_factorial") (param i64) (result i64) 4 | (local i64) 5 | (local.set 1 (i64.const 1)) 6 | (block 7 | (br_if 0 (i64.lt_s (local.get 0) (i64.const 2))) 8 | (loop 9 | (local.set 1 (i64.mul (local.get 1) (local.get 0))) 10 | (local.set 0 (i64.add (local.get 0) (i64.const -1))) 11 | (br_if 0 (i64.gt_s (local.get 0) (i64.const 1))) 12 | ) 13 | ) 14 | (local.get 1) 15 | ) 16 | 17 | ;; Recursive trivial factorial function. 18 | (func $rec_fac (export "recursive_factorial") (param i64) (result i64) 19 | (if (result i64) 20 | (i64.eq (local.get 0) (i64.const 0)) 21 | (then (i64.const 1)) 22 | (else 23 | (i64.mul 24 | (local.get 0) 25 | (call $rec_fac 26 | (i64.sub 27 | (local.get 0) 28 | (i64.const 1) 29 | ) 30 | ) 31 | ) 32 | ) 33 | ) 34 | ) 35 | ) 36 | -------------------------------------------------------------------------------- /benches/fixtures/wat/fibonacci.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (func $fib_recursive (export "fib_recursive") (param $N i64) (result i64) 3 | (if 4 | (i64.le_s (local.get $N) (i64.const 1)) 5 | (then (return (local.get $N))) 6 | ) 7 | (return 8 | (i64.add 9 | (call $fib_recursive 10 | (i64.sub (local.get $N) (i64.const 1)) 11 | ) 12 | (call $fib_recursive 13 | (i64.sub (local.get $N) (i64.const 2)) 14 | ) 15 | ) 16 | ) 17 | ) 18 | 19 | (func $fib_iterative (export "fib_iterative") (param $N i64) (result i64) 20 | (local $n1 i64) 21 | (local $n2 i64) 22 | (local $tmp i64) 23 | (local $i i64) 24 | ;; return $N for N <= 1 25 | (if 26 | (i64.le_s (local.get $N) (i64.const 1)) 27 | (then (return (local.get $N))) 28 | ) 29 | (local.set $n1 (i64.const 1)) 30 | (local.set $n2 (i64.const 1)) 31 | (local.set $i (i64.const 2)) 32 | ;;since we normally return n2, handle n=1 case specially 33 | (loop $again 34 | (if 35 | (i64.lt_s (local.get $i) (local.get $N)) 36 | (then 37 | (local.set $tmp (i64.add (local.get $n1) (local.get $n2))) 38 | (local.set $n1 (local.get $n2)) 39 | (local.set $n2 (local.get $tmp)) 40 | (local.set $i (i64.add (local.get $i) (i64.const 1))) 41 | (br $again) 42 | ) 43 | ) 44 | ) 45 | (local.get $n2) 46 | ) 47 | ) 48 | -------------------------------------------------------------------------------- /benches/fixtures/wat/global_bump.wat: -------------------------------------------------------------------------------- 1 | ;; Exports a function `bump` that takes an input `n`. 2 | ;; The exported function bumps a global variable `n` times and then returns it. 3 | (module 4 | (global $g (mut i32) (i32.const 0)) 5 | (func $bump (export "bump") (param $n i32) (result i32) 6 | (global.set $g (i32.const 0)) 7 | (block $break 8 | (loop $continue 9 | (br_if ;; if $g == $n then break 10 | $break 11 | (i32.eq 12 | (global.get $g) 13 | (local.get $n) 14 | ) 15 | ) 16 | (global.set $g ;; $g += 1 17 | (i32.add 18 | (global.get $g) 19 | (i32.const 1) 20 | ) 21 | ) 22 | (br $continue) 23 | ) 24 | ) 25 | (return (global.get $g)) 26 | ) 27 | ) 28 | -------------------------------------------------------------------------------- /benches/fixtures/wat/memory-vec-add.wat: -------------------------------------------------------------------------------- 1 | ;; Exports a function `vec_add` that computes the addition of 2 vectors 2 | ;; of length `len` starting at `ptr_a` and `ptr_b` and stores the result 3 | ;; into a buffer of the same length starting at `ptr_result`. 4 | (module 5 | (memory (export "mem") 1) 6 | (func (export "vec_add") 7 | (param $ptr_result i32) 8 | (param $ptr_a i32) 9 | (param $ptr_b i32) 10 | (param $len i32) 11 | (local $n i32) 12 | (block $exit 13 | (loop $loop 14 | (br_if ;; exit loop if $n == $len 15 | $exit 16 | (i32.eq 17 | (local.get $n) 18 | (local.get $len) 19 | ) 20 | ) 21 | (i64.store offset=0 ;; ptr_result[n] = ptr_a[n] + ptr_b[n] 22 | (i32.add 23 | (local.get $ptr_result) 24 | (i32.mul 25 | (local.get $n) 26 | (i32.const 8) 27 | ) 28 | ) 29 | (i64.add 30 | (i64.load32_s offset=0 ;; load ptr_a[n] 31 | (i32.add 32 | (local.get $ptr_a) 33 | (i32.mul 34 | (local.get $n) 35 | (i32.const 4) 36 | ) 37 | ) 38 | ) 39 | (i64.load32_s offset=0 ;; load ptr_b[n] 40 | (i32.add 41 | (local.get $ptr_b) 42 | (i32.mul 43 | (local.get $n) 44 | (i32.const 4) 45 | ) 46 | ) 47 | ) 48 | ) 49 | ) 50 | (local.set $n ;; increment n 51 | (i32.add (local.get $n) (i32.const 1)) 52 | ) 53 | (br $loop) ;; continue loop 54 | ) 55 | ) 56 | (return) 57 | ) 58 | ) 59 | -------------------------------------------------------------------------------- /benches/fixtures/wat/recursive_ok.wat: -------------------------------------------------------------------------------- 1 | ;; Exports a function `call` that takes an input `n`. 2 | ;; The exported function calls itself `n` times. 3 | (module 4 | (func $call (export "call") (param $n i32) (result i32) 5 | (if (result i32) 6 | (local.get $n) 7 | (then 8 | (return 9 | (call $call 10 | (i32.sub 11 | (local.get $n) 12 | (i32.const 1) 13 | ) 14 | ) 15 | ) 16 | ) 17 | (else 18 | (return (local.get $n)) 19 | ) 20 | ) 21 | ) 22 | ) 23 | -------------------------------------------------------------------------------- /benches/instrumentation.rs: -------------------------------------------------------------------------------- 1 | use criterion::{ 2 | criterion_group, criterion_main, measurement::Measurement, BenchmarkGroup, Criterion, 3 | Throughput, 4 | }; 5 | use std::{ 6 | fs::{read, read_dir}, 7 | path::PathBuf, 8 | }; 9 | use wasm_instrument::{ 10 | gas_metering::{self, host_function, ConstantCostRules}, 11 | inject_stack_limiter, 12 | parity_wasm::{deserialize_buffer, elements::Module}, 13 | }; 14 | 15 | fn fixture_dir() -> PathBuf { 16 | let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); 17 | path.push("benches"); 18 | path.push("fixtures"); 19 | path.push("wasm"); 20 | path 21 | } 22 | 23 | fn for_fixtures(group: &mut BenchmarkGroup, f: F) 24 | where 25 | F: Fn(Module), 26 | M: Measurement, 27 | { 28 | for entry in read_dir(fixture_dir()).unwrap() { 29 | let entry = entry.unwrap(); 30 | let bytes = read(entry.path()).unwrap(); 31 | group.throughput(Throughput::Bytes(bytes.len().try_into().unwrap())); 32 | group.bench_with_input(entry.file_name().to_str().unwrap(), &bytes, |bench, input| { 33 | bench.iter(|| f(deserialize_buffer(input).unwrap())) 34 | }); 35 | } 36 | } 37 | 38 | fn gas_metering(c: &mut Criterion) { 39 | let mut group = c.benchmark_group("Gas Metering"); 40 | for_fixtures(&mut group, |module| { 41 | gas_metering::inject( 42 | module, 43 | host_function::Injector::new("env", "gas"), 44 | &ConstantCostRules::default(), 45 | ) 46 | .unwrap(); 47 | }); 48 | } 49 | 50 | fn stack_height_limiter(c: &mut Criterion) { 51 | let mut group = c.benchmark_group("Stack Height Limiter"); 52 | for_fixtures(&mut group, |module| { 53 | inject_stack_limiter(module, 128).unwrap(); 54 | }); 55 | } 56 | 57 | criterion_group!(benches, gas_metering, stack_height_limiter); 58 | criterion_main!(benches); 59 | -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | _default: 2 | just --list 3 | 4 | # Run rustfmt and ensure the code meets the expectation of the checks in the CI 5 | format: 6 | cargo +nightly fmt --all 7 | 8 | # Run basic checks similar to what the CI does to ensure your code is fine 9 | check: 10 | cargo +nightly fmt --all -- --check 11 | cargo +stable clippy --all-targets --all-features -- -D warnings 12 | 13 | # Run the tests 14 | test: 15 | cargo test --all-features 16 | 17 | # So you are ready? This runs format, check and test 18 | ready: format check test 19 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | # Basic 2 | hard_tabs = true 3 | max_width = 100 4 | use_small_heuristics = "Max" 5 | # Imports 6 | imports_granularity = "Crate" 7 | reorder_imports = true 8 | # Consistency 9 | newline_style = "Unix" 10 | # Format comments 11 | comment_width = 100 12 | wrap_comments = true 13 | # Misc 14 | chain_width = 80 15 | spaces_around_ranges = false 16 | binop_separator = "Back" 17 | reorder_impl_items = false 18 | match_arm_leading_pipes = "Preserve" 19 | match_arm_blocks = false 20 | match_block_trailing_comma = true 21 | trailing_comma = "Vertical" 22 | trailing_semicolon = false 23 | use_field_init_shorthand = true 24 | -------------------------------------------------------------------------------- /src/export_globals.rs: -------------------------------------------------------------------------------- 1 | use alloc::{format, vec::Vec}; 2 | use parity_wasm::elements; 3 | 4 | /// Export all declared mutable globals as `prefix_index`. 5 | /// 6 | /// This will export all internal mutable globals under the name of 7 | /// concat(`prefix`, `"_"`, `i`) where i is the index inside the range of 8 | /// [0..total number of internal mutable globals]. 9 | pub fn export_mutable_globals(module: &mut elements::Module, prefix: &str) { 10 | let exports = global_section(module) 11 | .map(|section| { 12 | section 13 | .entries() 14 | .iter() 15 | .enumerate() 16 | .filter_map( 17 | |(index, global)| { 18 | if global.global_type().is_mutable() { 19 | Some(index) 20 | } else { 21 | None 22 | } 23 | }, 24 | ) 25 | .collect::>() 26 | }) 27 | .unwrap_or_default(); 28 | 29 | if module.export_section().is_none() { 30 | module 31 | .sections_mut() 32 | .push(elements::Section::Export(elements::ExportSection::default())); 33 | } 34 | 35 | for (symbol_index, export) in exports.into_iter().enumerate() { 36 | let new_entry = elements::ExportEntry::new( 37 | format!("{}_{}", prefix, symbol_index), 38 | elements::Internal::Global( 39 | (module.import_count(elements::ImportCountType::Global) + export) as _, 40 | ), 41 | ); 42 | export_section(module) 43 | .expect("added above if does not exists") 44 | .entries_mut() 45 | .push(new_entry); 46 | } 47 | } 48 | 49 | fn export_section(module: &mut elements::Module) -> Option<&mut elements::ExportSection> { 50 | for section in module.sections_mut() { 51 | if let elements::Section::Export(sect) = section { 52 | return Some(sect) 53 | } 54 | } 55 | None 56 | } 57 | 58 | fn global_section(module: &mut elements::Module) -> Option<&mut elements::GlobalSection> { 59 | for section in module.sections_mut() { 60 | if let elements::Section::Global(sect) = section { 61 | return Some(sect) 62 | } 63 | } 64 | None 65 | } 66 | 67 | #[cfg(test)] 68 | mod tests { 69 | 70 | use super::export_mutable_globals; 71 | use parity_wasm::elements; 72 | 73 | fn parse_wat(source: &str) -> elements::Module { 74 | let module_bytes = wat::parse_str(source).unwrap(); 75 | wasmparser::validate(&module_bytes).unwrap(); 76 | elements::deserialize_buffer(module_bytes.as_ref()).expect("failed to parse module") 77 | } 78 | 79 | macro_rules! test_export_global { 80 | (name = $name:ident; input = $input:expr; expected = $expected:expr) => { 81 | #[test] 82 | fn $name() { 83 | let mut input_module = parse_wat($input); 84 | let expected_module = parse_wat($expected); 85 | 86 | export_mutable_globals(&mut input_module, "exported_internal_global"); 87 | 88 | let actual_bytes = elements::serialize(input_module) 89 | .expect("injected module must have a function body"); 90 | 91 | let expected_bytes = elements::serialize(expected_module) 92 | .expect("injected module must have a function body"); 93 | 94 | let actual_wat = wasmprinter::print_bytes(actual_bytes).unwrap(); 95 | let expected_wat = wasmprinter::print_bytes(expected_bytes).unwrap(); 96 | 97 | if actual_wat != expected_wat { 98 | for diff in diff::lines(&expected_wat, &actual_wat) { 99 | match diff { 100 | diff::Result::Left(l) => println!("-{}", l), 101 | diff::Result::Both(l, _) => println!(" {}", l), 102 | diff::Result::Right(r) => println!("+{}", r), 103 | } 104 | } 105 | panic!() 106 | } 107 | } 108 | }; 109 | } 110 | 111 | test_export_global! { 112 | name = simple; 113 | input = r#" 114 | (module 115 | (global (;0;) (mut i32) (i32.const 1)) 116 | (global (;1;) (mut i32) (i32.const 0))) 117 | "#; 118 | expected = r#" 119 | (module 120 | (global (;0;) (mut i32) (i32.const 1)) 121 | (global (;1;) (mut i32) (i32.const 0)) 122 | (export "exported_internal_global_0" (global 0)) 123 | (export "exported_internal_global_1" (global 1))) 124 | "# 125 | } 126 | 127 | test_export_global! { 128 | name = with_import; 129 | input = r#" 130 | (module 131 | (import "env" "global" (global $global i64)) 132 | (global (;0;) (mut i32) (i32.const 1)) 133 | (global (;1;) (mut i32) (i32.const 0))) 134 | "#; 135 | expected = r#" 136 | (module 137 | (import "env" "global" (global $global i64)) 138 | (global (;0;) (mut i32) (i32.const 1)) 139 | (global (;1;) (mut i32) (i32.const 0)) 140 | (export "exported_internal_global_0" (global 1)) 141 | (export "exported_internal_global_1" (global 2))) 142 | "# 143 | } 144 | 145 | test_export_global! { 146 | name = with_import_and_some_are_immutable; 147 | input = r#" 148 | (module 149 | (import "env" "global" (global $global i64)) 150 | (global (;0;) i32 (i32.const 1)) 151 | (global (;1;) (mut i32) (i32.const 0))) 152 | "#; 153 | expected = r#" 154 | (module 155 | (import "env" "global" (global $global i64)) 156 | (global (;0;) i32 (i32.const 1)) 157 | (global (;1;) (mut i32) (i32.const 0)) 158 | (export "exported_internal_global_0" (global 2))) 159 | "# 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/gas_metering/backend.rs: -------------------------------------------------------------------------------- 1 | //! Provides backends for the gas metering instrumentation 2 | use parity_wasm::elements; 3 | 4 | /// Implementation details of the specific method of the gas metering. 5 | #[derive(Clone)] 6 | pub enum GasMeter { 7 | /// Gas metering with an external function. 8 | External { 9 | /// Name of the module to import the gas function from. 10 | module: &'static str, 11 | /// Name of the external gas function to be imported. 12 | function: &'static str, 13 | }, 14 | /// Gas metering with a local function and a mutable global. 15 | Internal { 16 | /// Name of the mutable global to be exported. 17 | global: &'static str, 18 | /// Body of the local gas counting function to be injected. 19 | func_instructions: elements::Instructions, 20 | /// Cost of the gas function execution. 21 | cost: u64, 22 | }, 23 | } 24 | 25 | use super::Rules; 26 | /// Under the hood part of the gas metering mechanics. 27 | pub trait Backend { 28 | /// Provides the gas metering implementation details. 29 | fn gas_meter(self, module: &elements::Module, rules: &R) -> GasMeter; 30 | } 31 | 32 | /// Gas metering with an external host function. 33 | pub mod host_function { 34 | use super::{Backend, GasMeter, Rules}; 35 | use parity_wasm::elements::Module; 36 | /// Injects invocations of the gas charging host function into each metering block. 37 | pub struct Injector { 38 | /// The name of the module to import the gas function from. 39 | module: &'static str, 40 | /// The name of the gas function to import. 41 | name: &'static str, 42 | } 43 | 44 | impl Injector { 45 | pub fn new(module: &'static str, name: &'static str) -> Self { 46 | Self { module, name } 47 | } 48 | } 49 | 50 | impl Backend for Injector { 51 | fn gas_meter(self, _module: &Module, _rules: &R) -> GasMeter { 52 | GasMeter::External { module: self.module, function: self.name } 53 | } 54 | } 55 | } 56 | 57 | /// Gas metering with a mutable global. 58 | /// 59 | /// # Note 60 | /// 61 | /// Not for all execution engines this method gives performance wins compared to using an [external 62 | /// host function](host_function). See benchmarks and size overhead tests for examples of how to 63 | /// make measurements needed to decide which gas metering method is better for your particular case. 64 | /// 65 | /// # Warning 66 | /// 67 | /// It is not recommended to apply [stack limiter](crate::inject_stack_limiter) instrumentation to a 68 | /// module instrumented with this type of gas metering. This could lead to a massive module size 69 | /// bloat. This is a known issue to be fixed in upcoming versions. 70 | pub mod mutable_global { 71 | use super::{Backend, GasMeter, Rules}; 72 | use alloc::vec; 73 | use parity_wasm::elements::{self, Instruction, Module}; 74 | /// Injects a mutable global variable and a local function to the module to track 75 | /// current gas left. 76 | /// 77 | /// The function is called in every metering block. In case of falling out of gas, the global is 78 | /// set to the sentinel value `U64::MAX` and `unreachable` instruction is called. The execution 79 | /// engine should take care of getting the current global value and setting it back in order to 80 | /// sync the gas left value during an execution. 81 | pub struct Injector { 82 | /// The export name of the gas tracking global. 83 | pub global_name: &'static str, 84 | } 85 | 86 | impl Injector { 87 | pub fn new(global_name: &'static str) -> Self { 88 | Self { global_name } 89 | } 90 | } 91 | 92 | impl Backend for Injector { 93 | fn gas_meter(self, module: &Module, rules: &R) -> GasMeter { 94 | let gas_global_idx = module.globals_space() as u32; 95 | 96 | let func_instructions = vec![ 97 | Instruction::GetGlobal(gas_global_idx), 98 | Instruction::GetLocal(0), 99 | Instruction::I64GeU, 100 | Instruction::If(elements::BlockType::NoResult), 101 | Instruction::GetGlobal(gas_global_idx), 102 | Instruction::GetLocal(0), 103 | Instruction::I64Sub, 104 | Instruction::SetGlobal(gas_global_idx), 105 | Instruction::Else, 106 | // sentinel val u64::MAX 107 | Instruction::I64Const(-1i64), // non-charged instruction 108 | Instruction::SetGlobal(gas_global_idx), // non-charged instruction 109 | Instruction::Unreachable, // non-charged instruction 110 | Instruction::End, 111 | Instruction::End, 112 | ]; 113 | 114 | // calculate gas used for the gas charging func execution itself 115 | let mut gas_fn_cost = func_instructions.iter().fold(0, |cost: u64, instruction| { 116 | cost.saturating_add(rules.instruction_cost(instruction).unwrap_or(u32::MAX).into()) 117 | }); 118 | // don't charge for the instructions used to fail when out of gas 119 | let fail_cost = [ 120 | Instruction::I64Const(-1i64), // non-charged instruction 121 | Instruction::SetGlobal(gas_global_idx), // non-charged instruction 122 | Instruction::Unreachable, // non-charged instruction 123 | ] 124 | .iter() 125 | .fold(0, |cost: u64, instruction| { 126 | cost.saturating_add(rules.instruction_cost(instruction).unwrap_or(u32::MAX).into()) 127 | }); 128 | 129 | // the fail costs are a subset of the overall costs and hence this never underflows 130 | gas_fn_cost -= fail_cost; 131 | 132 | GasMeter::Internal { 133 | global: self.global_name, 134 | func_instructions: elements::Instructions::new(func_instructions), 135 | cost: gas_fn_cost, 136 | } 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/gas_metering/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module is used to instrument a Wasm module with the gas metering code. 2 | //! 3 | //! The primary public interface is the [`inject`] function which transforms a given 4 | //! module into one that charges gas for code to be executed. See function documentation for usage 5 | //! and details. 6 | 7 | mod backend; 8 | 9 | pub use backend::{host_function, mutable_global, Backend, GasMeter}; 10 | 11 | #[cfg(test)] 12 | mod validation; 13 | 14 | use alloc::{vec, vec::Vec}; 15 | use core::{cmp::min, mem, num::NonZeroU32}; 16 | use parity_wasm::{ 17 | builder, 18 | elements::{self, IndexMap, Instruction, ValueType}, 19 | }; 20 | 21 | /// An interface that describes instruction costs. 22 | pub trait Rules { 23 | /// Returns the cost for the passed `instruction`. 24 | /// 25 | /// Returning `None` makes the gas instrumention end with an error. This is meant 26 | /// as a way to have a partial rule set where any instruction that is not specifed 27 | /// is considered as forbidden. 28 | fn instruction_cost(&self, instruction: &Instruction) -> Option; 29 | 30 | /// Returns the costs for growing the memory using the `memory.grow` instruction. 31 | /// 32 | /// Please note that these costs are in addition to the costs specified by `instruction_cost` 33 | /// for the `memory.grow` instruction. Those are meant as dynamic costs which take the 34 | /// amount of pages that the memory is grown by into consideration. This is not possible 35 | /// using `instruction_cost` because those costs depend on the stack and must be injected as 36 | /// code into the function calling `memory.grow`. Therefore returning anything but 37 | /// [`MemoryGrowCost::Free`] introduces some overhead to the `memory.grow` instruction. 38 | fn memory_grow_cost(&self) -> MemoryGrowCost; 39 | 40 | /// A surcharge cost to calling a function that is added per local of that function. 41 | fn call_per_local_cost(&self) -> u32; 42 | } 43 | 44 | /// Dynamic costs for memory growth. 45 | #[derive(Debug, PartialEq, Eq, Copy, Clone)] 46 | pub enum MemoryGrowCost { 47 | /// Skip per page charge. 48 | /// 49 | /// # Note 50 | /// 51 | /// This makes sense when the amount of pages that a module is allowed to use is limited 52 | /// to a rather small number by static validation. In that case it is viable to 53 | /// benchmark the costs of `memory.grow` as the worst case (growing to to the maximum 54 | /// number of pages). 55 | Free, 56 | /// Charge the specified amount for each page that the memory is grown by. 57 | Linear(NonZeroU32), 58 | } 59 | 60 | impl MemoryGrowCost { 61 | /// True iff memory growths code needs to be injected. 62 | fn enabled(&self) -> bool { 63 | match self { 64 | Self::Free => false, 65 | Self::Linear(_) => true, 66 | } 67 | } 68 | } 69 | 70 | /// A type that implements [`Rules`] so that every instruction costs the same. 71 | /// 72 | /// This is a simplification that is mostly useful for development and testing. 73 | /// 74 | /// # Note 75 | /// 76 | /// In a production environment it usually makes no sense to assign every instruction 77 | /// the same cost. A proper implemention of [`Rules`] should be provided that is probably 78 | /// created by benchmarking. 79 | pub struct ConstantCostRules { 80 | instruction_cost: u32, 81 | memory_grow_cost: u32, 82 | call_per_local_cost: u32, 83 | } 84 | 85 | impl ConstantCostRules { 86 | /// Create a new [`ConstantCostRules`]. 87 | /// 88 | /// Uses `instruction_cost` for every instruction and `memory_grow_cost` to dynamically 89 | /// meter the memory growth instruction. 90 | pub fn new(instruction_cost: u32, memory_grow_cost: u32, call_per_local_cost: u32) -> Self { 91 | Self { instruction_cost, memory_grow_cost, call_per_local_cost } 92 | } 93 | } 94 | 95 | impl Default for ConstantCostRules { 96 | /// Uses instruction cost of `1` and disables memory growth instrumentation. 97 | fn default() -> Self { 98 | Self { instruction_cost: 1, memory_grow_cost: 0, call_per_local_cost: 1 } 99 | } 100 | } 101 | 102 | impl Rules for ConstantCostRules { 103 | fn instruction_cost(&self, _: &Instruction) -> Option { 104 | Some(self.instruction_cost) 105 | } 106 | 107 | fn memory_grow_cost(&self) -> MemoryGrowCost { 108 | NonZeroU32::new(self.memory_grow_cost).map_or(MemoryGrowCost::Free, MemoryGrowCost::Linear) 109 | } 110 | 111 | fn call_per_local_cost(&self) -> u32 { 112 | self.call_per_local_cost 113 | } 114 | } 115 | 116 | /// Transforms a given module into one that tracks the gas charged during its execution. 117 | /// 118 | /// The output module uses the `gas` function to track the gas spent. The function could be either 119 | /// an imported or a local one modifying a mutable global. The argument is the amount of gas 120 | /// required to continue execution. The execution engine is meant to keep track of the total amount 121 | /// of gas used and trap or otherwise halt execution of the runtime if the gas usage exceeds some 122 | /// allowed limit. 123 | /// 124 | /// The body of each function of the original module is divided into metered blocks, and the calls 125 | /// to charge gas are inserted at the beginning of every such block of code. A metered block is 126 | /// defined so that, unless there is a trap, either all of the instructions are executed or none 127 | /// are. These are similar to basic blocks in a control flow graph, except that in some cases 128 | /// multiple basic blocks can be merged into a single metered block. This is the case if any path 129 | /// through the control flow graph containing one basic block also contains another. 130 | /// 131 | /// Charging gas at the beginning of each metered block ensures that 1) all instructions 132 | /// executed are already paid for, 2) instructions that will not be executed are not charged for 133 | /// unless execution traps, and 3) the number of calls to `gas` is minimized. The corollary is 134 | /// that modules instrumented with this metering code may charge gas for instructions not 135 | /// executed in the event of a trap. 136 | /// 137 | /// Additionally, each `memory.grow` instruction found in the module is instrumented to first 138 | /// make a call to charge gas for the additional pages requested. This cannot be done as part of 139 | /// the block level gas charges as the gas cost is not static and depends on the stack argument 140 | /// to `memory.grow`. 141 | /// 142 | /// The above transformations are performed for every function body defined in the module. This 143 | /// function also rewrites all function indices references by code, table elements, etc., since 144 | /// the addition of an imported functions changes the indices of module-defined functions. If 145 | /// the module has a `NameSection`, added by calling `parse_names`, the indices will also be 146 | /// updated. 147 | /// 148 | /// Syncronizing the amount of gas charged with the execution engine can be done in two ways. The 149 | /// first way is by calling the imported `gas` host function, see [`host_function`] for details. The 150 | /// second way is by using a local `gas` function together with a mutable global, see 151 | /// [`mutable_global`] for details. 152 | /// 153 | /// This routine runs in time linear in the size of the input module. 154 | /// 155 | /// The function fails if the module contains any operation forbidden by gas rule set, returning 156 | /// the original module as an `Err`. 157 | pub fn inject( 158 | module: elements::Module, 159 | backend: B, 160 | rules: &R, 161 | ) -> Result { 162 | // Prepare module and return the gas function 163 | let gas_meter = backend.gas_meter(&module, rules); 164 | 165 | let import_count = module.import_count(elements::ImportCountType::Function) as u32; 166 | let functions_space = module.functions_space() as u32; 167 | let gas_global_idx = module.globals_space() as u32; 168 | 169 | let mut mbuilder = builder::from_module(module.clone()); 170 | 171 | // Calculate the indexes and gas function cost, 172 | // for external gas function the cost is counted on the host side 173 | let (gas_func_idx, total_func, gas_fn_cost) = match gas_meter { 174 | GasMeter::External { module: gas_module, function } => { 175 | // Inject the import of the gas function 176 | let import_sig = mbuilder 177 | .push_signature(builder::signature().with_param(ValueType::I64).build_sig()); 178 | mbuilder.push_import( 179 | builder::import() 180 | .module(gas_module) 181 | .field(function) 182 | .external() 183 | .func(import_sig) 184 | .build(), 185 | ); 186 | 187 | (import_count, functions_space + 1, 0) 188 | }, 189 | GasMeter::Internal { global, ref func_instructions, cost } => { 190 | // Inject the gas counting global 191 | mbuilder.push_global( 192 | builder::global() 193 | .with_type(ValueType::I64) 194 | .mutable() 195 | .init_expr(Instruction::I64Const(0)) 196 | .build(), 197 | ); 198 | // Inject the export entry for the gas counting global 199 | let ebuilder = builder::ExportBuilder::new(); 200 | let global_export = ebuilder 201 | .field(global) 202 | .with_internal(elements::Internal::Global(gas_global_idx)) 203 | .build(); 204 | mbuilder.push_export(global_export); 205 | 206 | let func_idx = functions_space; 207 | 208 | // Build local gas function 209 | let gas_func_sig = 210 | builder::SignatureBuilder::new().with_param(ValueType::I64).build_sig(); 211 | 212 | let function = builder::FunctionBuilder::new() 213 | .with_signature(gas_func_sig) 214 | .body() 215 | .with_instructions(func_instructions.clone()) 216 | .build() 217 | .build(); 218 | 219 | // Inject local gas function 220 | mbuilder.push_function(function); 221 | 222 | (func_idx, func_idx + 1, cost) 223 | }, 224 | }; 225 | 226 | // We need the built the module for making injections to its blocks 227 | let mut resulting_module = mbuilder.build(); 228 | 229 | let mut need_grow_counter = false; 230 | let mut result = Ok(()); 231 | // Iterate over module sections and perform needed transformations. 232 | // Indexes are needed to be fixed up in `GasMeter::External` case, as it adds an imported 233 | // function, which goes to the beginning of the module's functions space. 234 | 'outer: for section in resulting_module.sections_mut() { 235 | match section { 236 | elements::Section::Code(code_section) => { 237 | let injection_targets = match gas_meter { 238 | GasMeter::External { .. } => code_section.bodies_mut().as_mut_slice(), 239 | // Don't inject counters to the local gas function, which is the last one as 240 | // it's just added. Cost for its execution is added statically before each 241 | // invocation (see `inject_counter()`). 242 | GasMeter::Internal { .. } => { 243 | let len = code_section.bodies().len(); 244 | &mut code_section.bodies_mut()[..len - 1] 245 | }, 246 | }; 247 | 248 | for func_body in injection_targets { 249 | // Increment calling addresses if needed 250 | if let GasMeter::External { .. } = gas_meter { 251 | for instruction in func_body.code_mut().elements_mut().iter_mut() { 252 | if let Instruction::Call(call_index) = instruction { 253 | if *call_index >= gas_func_idx { 254 | *call_index += 1 255 | } 256 | } 257 | } 258 | } 259 | result = func_body 260 | .locals() 261 | .iter() 262 | .try_fold(0u32, |count, val_type| count.checked_add(val_type.count())) 263 | .ok_or(()) 264 | .and_then(|locals_count| { 265 | inject_counter( 266 | func_body.code_mut(), 267 | gas_fn_cost, 268 | locals_count, 269 | rules, 270 | gas_func_idx, 271 | ) 272 | }); 273 | if result.is_err() { 274 | break 'outer 275 | } 276 | if rules.memory_grow_cost().enabled() && 277 | inject_grow_counter(func_body.code_mut(), total_func) > 0 278 | { 279 | need_grow_counter = true; 280 | } 281 | } 282 | }, 283 | elements::Section::Export(export_section) => 284 | if let GasMeter::External { module: _, function: _ } = gas_meter { 285 | for export in export_section.entries_mut() { 286 | if let elements::Internal::Function(func_index) = export.internal_mut() { 287 | if *func_index >= gas_func_idx { 288 | *func_index += 1 289 | } 290 | } 291 | } 292 | }, 293 | elements::Section::Element(elements_section) => { 294 | // Note that we do not need to check the element type referenced because in the 295 | // WebAssembly 1.0 spec, the only allowed element type is funcref. 296 | if let GasMeter::External { .. } = gas_meter { 297 | for segment in elements_section.entries_mut() { 298 | // update all indirect call addresses initial values 299 | for func_index in segment.members_mut() { 300 | if *func_index >= gas_func_idx { 301 | *func_index += 1 302 | } 303 | } 304 | } 305 | } 306 | }, 307 | elements::Section::Start(start_idx) => 308 | if let GasMeter::External { .. } = gas_meter { 309 | if *start_idx >= gas_func_idx { 310 | *start_idx += 1 311 | } 312 | }, 313 | elements::Section::Name(s) => 314 | if let GasMeter::External { .. } = gas_meter { 315 | for functions in s.functions_mut() { 316 | *functions.names_mut() = 317 | IndexMap::from_iter(functions.names().iter().map(|(mut idx, name)| { 318 | if idx >= gas_func_idx { 319 | idx += 1; 320 | } 321 | 322 | (idx, name.clone()) 323 | })); 324 | } 325 | }, 326 | _ => {}, 327 | } 328 | } 329 | 330 | result.map_err(|_| module)?; 331 | 332 | if need_grow_counter { 333 | Ok(add_grow_counter(resulting_module, rules, gas_func_idx)) 334 | } else { 335 | Ok(resulting_module) 336 | } 337 | } 338 | 339 | /// A control flow block is opened with the `block`, `loop`, and `if` instructions and is closed 340 | /// with `end`. Each block implicitly defines a new label. The control blocks form a stack during 341 | /// program execution. 342 | /// 343 | /// An example of block: 344 | /// 345 | /// ```wasm 346 | /// loop 347 | /// i32.const 1 348 | /// local.get 0 349 | /// i32.sub 350 | /// local.tee 0 351 | /// br_if 0 352 | /// end 353 | /// ``` 354 | /// 355 | /// The start of the block is `i32.const 1`. 356 | #[derive(Debug)] 357 | struct ControlBlock { 358 | /// The lowest control stack index corresponding to a forward jump targeted by a br, br_if, or 359 | /// br_table instruction within this control block. The index must refer to a control block 360 | /// that is not a loop, meaning it is a forward jump. Given the way Wasm control flow is 361 | /// structured, the lowest index on the stack represents the furthest forward branch target. 362 | /// 363 | /// This value will always be at most the index of the block itself, even if there is no 364 | /// explicit br instruction targeting this control block. This does not affect how the value is 365 | /// used in the metering algorithm. 366 | lowest_forward_br_target: usize, 367 | 368 | /// The active metering block that new instructions contribute a gas cost towards. 369 | active_metered_block: MeteredBlock, 370 | 371 | /// Whether the control block is a loop. Loops have the distinguishing feature that branches to 372 | /// them jump to the beginning of the block, not the end as with the other control blocks. 373 | is_loop: bool, 374 | } 375 | 376 | /// A block of code that metering instructions will be inserted at the beginning of. Metered blocks 377 | /// are constructed with the property that, in the absence of any traps, either all instructions in 378 | /// the block are executed or none are. 379 | #[derive(Debug)] 380 | struct MeteredBlock { 381 | /// Index of the first instruction (aka `Opcode`) in the block. 382 | start_pos: usize, 383 | /// Sum of costs of all instructions until end of the block. 384 | cost: u64, 385 | } 386 | 387 | /// Counter is used to manage state during the gas metering algorithm implemented by 388 | /// `inject_counter`. 389 | struct Counter { 390 | /// A stack of control blocks. This stack grows when new control blocks are opened with 391 | /// `block`, `loop`, and `if` and shrinks when control blocks are closed with `end`. The first 392 | /// block on the stack corresponds to the function body, not to any labelled block. Therefore 393 | /// the actual Wasm label index associated with each control block is 1 less than its position 394 | /// in this stack. 395 | stack: Vec, 396 | 397 | /// A list of metered blocks that have been finalized, meaning they will no longer change. 398 | finalized_blocks: Vec, 399 | } 400 | 401 | impl Counter { 402 | fn new() -> Counter { 403 | Counter { stack: Vec::new(), finalized_blocks: Vec::new() } 404 | } 405 | 406 | /// Open a new control block. The cursor is the position of the first instruction in the block. 407 | fn begin_control_block(&mut self, cursor: usize, is_loop: bool) { 408 | let index = self.stack.len(); 409 | self.stack.push(ControlBlock { 410 | lowest_forward_br_target: index, 411 | active_metered_block: MeteredBlock { start_pos: cursor, cost: 0 }, 412 | is_loop, 413 | }) 414 | } 415 | 416 | /// Close the last control block. The cursor is the position of the final (pseudo-)instruction 417 | /// in the block. 418 | fn finalize_control_block(&mut self, cursor: usize) -> Result<(), ()> { 419 | // This either finalizes the active metered block or merges its cost into the active 420 | // metered block in the previous control block on the stack. 421 | self.finalize_metered_block(cursor)?; 422 | 423 | // Pop the control block stack. 424 | let closing_control_block = self.stack.pop().ok_or(())?; 425 | let closing_control_index = self.stack.len(); 426 | 427 | if self.stack.is_empty() { 428 | return Ok(()) 429 | } 430 | 431 | // Update the lowest_forward_br_target for the control block now on top of the stack. 432 | { 433 | let control_block = self.stack.last_mut().ok_or(())?; 434 | control_block.lowest_forward_br_target = min( 435 | control_block.lowest_forward_br_target, 436 | closing_control_block.lowest_forward_br_target, 437 | ); 438 | } 439 | 440 | // If there may have been a branch to a lower index, then also finalize the active metered 441 | // block for the previous control block. Otherwise, finalize it and begin a new one. 442 | let may_br_out = closing_control_block.lowest_forward_br_target < closing_control_index; 443 | if may_br_out { 444 | self.finalize_metered_block(cursor)?; 445 | } 446 | 447 | Ok(()) 448 | } 449 | 450 | /// Finalize the current active metered block. 451 | /// 452 | /// Finalized blocks have final cost which will not change later. 453 | fn finalize_metered_block(&mut self, cursor: usize) -> Result<(), ()> { 454 | let closing_metered_block = { 455 | let control_block = self.stack.last_mut().ok_or(())?; 456 | mem::replace( 457 | &mut control_block.active_metered_block, 458 | MeteredBlock { start_pos: cursor + 1, cost: 0 }, 459 | ) 460 | }; 461 | 462 | // If the block was opened with a `block`, then its start position will be set to that of 463 | // the active metered block in the control block one higher on the stack. This is because 464 | // any instructions between a `block` and the first branch are part of the same basic block 465 | // as the preceding instruction. In this case, instead of finalizing the block, merge its 466 | // cost into the other active metered block to avoid injecting unnecessary instructions. 467 | let last_index = self.stack.len() - 1; 468 | if last_index > 0 { 469 | let prev_control_block = self 470 | .stack 471 | .get_mut(last_index - 1) 472 | .expect("last_index is greater than 0; last_index is stack size - 1; qed"); 473 | let prev_metered_block = &mut prev_control_block.active_metered_block; 474 | if closing_metered_block.start_pos == prev_metered_block.start_pos { 475 | prev_metered_block.cost = 476 | prev_metered_block.cost.checked_add(closing_metered_block.cost).ok_or(())?; 477 | return Ok(()) 478 | } 479 | } 480 | 481 | if closing_metered_block.cost > 0 { 482 | self.finalized_blocks.push(closing_metered_block); 483 | } 484 | Ok(()) 485 | } 486 | 487 | /// Handle a branch instruction in the program. The cursor is the index of the branch 488 | /// instruction in the program. The indices are the stack positions of the target control 489 | /// blocks. Recall that the index is 0 for a `return` and relatively indexed from the top of 490 | /// the stack by the label of `br`, `br_if`, and `br_table` instructions. 491 | fn branch(&mut self, cursor: usize, indices: &[usize]) -> Result<(), ()> { 492 | self.finalize_metered_block(cursor)?; 493 | 494 | // Update the lowest_forward_br_target of the current control block. 495 | for &index in indices { 496 | let target_is_loop = { 497 | let target_block = self.stack.get(index).ok_or(())?; 498 | target_block.is_loop 499 | }; 500 | if target_is_loop { 501 | continue 502 | } 503 | 504 | let control_block = self.stack.last_mut().ok_or(())?; 505 | control_block.lowest_forward_br_target = 506 | min(control_block.lowest_forward_br_target, index); 507 | } 508 | 509 | Ok(()) 510 | } 511 | 512 | /// Returns the stack index of the active control block. Returns None if stack is empty. 513 | fn active_control_block_index(&self) -> Option { 514 | self.stack.len().checked_sub(1) 515 | } 516 | 517 | /// Get a reference to the currently active metered block. 518 | fn active_metered_block(&mut self) -> Result<&mut MeteredBlock, ()> { 519 | let top_block = self.stack.last_mut().ok_or(())?; 520 | Ok(&mut top_block.active_metered_block) 521 | } 522 | 523 | /// Increment the cost of the current block by the specified value. 524 | fn increment(&mut self, val: u32) -> Result<(), ()> { 525 | let top_block = self.active_metered_block()?; 526 | top_block.cost = top_block.cost.checked_add(val.into()).ok_or(())?; 527 | Ok(()) 528 | } 529 | } 530 | 531 | fn inject_grow_counter(instructions: &mut elements::Instructions, grow_counter_func: u32) -> usize { 532 | use parity_wasm::elements::Instruction::*; 533 | let mut counter = 0; 534 | for instruction in instructions.elements_mut() { 535 | if let GrowMemory(_) = *instruction { 536 | *instruction = Call(grow_counter_func); 537 | counter += 1; 538 | } 539 | } 540 | counter 541 | } 542 | 543 | fn add_grow_counter( 544 | module: elements::Module, 545 | rules: &R, 546 | gas_func: u32, 547 | ) -> elements::Module { 548 | use parity_wasm::elements::Instruction::*; 549 | 550 | let cost = match rules.memory_grow_cost() { 551 | MemoryGrowCost::Free => return module, 552 | MemoryGrowCost::Linear(val) => val.get(), 553 | }; 554 | 555 | let mut b = builder::from_module(module); 556 | b.push_function( 557 | builder::function() 558 | .signature() 559 | .with_param(ValueType::I32) 560 | .with_result(ValueType::I32) 561 | .build() 562 | .body() 563 | .with_instructions(elements::Instructions::new(vec![ 564 | GetLocal(0), 565 | GetLocal(0), 566 | I64ExtendUI32, 567 | I64Const(i64::from(cost)), 568 | I64Mul, 569 | // todo: there should be strong guarantee that it does not return anything on 570 | // stack? 571 | Call(gas_func), 572 | GrowMemory(0), 573 | End, 574 | ])) 575 | .build() 576 | .build(), 577 | ); 578 | 579 | b.build() 580 | } 581 | 582 | fn determine_metered_blocks( 583 | instructions: &elements::Instructions, 584 | rules: &R, 585 | locals_count: u32, 586 | ) -> Result, ()> { 587 | use parity_wasm::elements::Instruction::*; 588 | 589 | let mut counter = Counter::new(); 590 | 591 | // Begin an implicit function (i.e. `func...end`) block. 592 | counter.begin_control_block(0, false); 593 | // Add locals initialization cost to the function block. 594 | let locals_init_cost = rules.call_per_local_cost().checked_mul(locals_count).ok_or(())?; 595 | counter.increment(locals_init_cost)?; 596 | 597 | for cursor in 0..instructions.elements().len() { 598 | let instruction = &instructions.elements()[cursor]; 599 | let instruction_cost = rules.instruction_cost(instruction).ok_or(())?; 600 | match instruction { 601 | Block(_) => { 602 | counter.increment(instruction_cost)?; 603 | 604 | // Begin new block. The cost of the following opcodes until `end` or `else` will 605 | // be included into this block. The start position is set to that of the previous 606 | // active metered block to signal that they should be merged in order to reduce 607 | // unnecessary metering instructions. 608 | let top_block_start_pos = counter.active_metered_block()?.start_pos; 609 | counter.begin_control_block(top_block_start_pos, false); 610 | }, 611 | If(_) => { 612 | counter.increment(instruction_cost)?; 613 | counter.begin_control_block(cursor + 1, false); 614 | }, 615 | Loop(_) => { 616 | counter.increment(instruction_cost)?; 617 | counter.begin_control_block(cursor + 1, true); 618 | }, 619 | End => { 620 | counter.finalize_control_block(cursor)?; 621 | }, 622 | Else => { 623 | counter.finalize_metered_block(cursor)?; 624 | }, 625 | Br(label) | BrIf(label) => { 626 | counter.increment(instruction_cost)?; 627 | 628 | // Label is a relative index into the control stack. 629 | let active_index = counter.active_control_block_index().ok_or(())?; 630 | let target_index = active_index.checked_sub(*label as usize).ok_or(())?; 631 | counter.branch(cursor, &[target_index])?; 632 | }, 633 | BrTable(br_table_data) => { 634 | counter.increment(instruction_cost)?; 635 | 636 | let active_index = counter.active_control_block_index().ok_or(())?; 637 | let target_indices = [br_table_data.default] 638 | .iter() 639 | .chain(br_table_data.table.iter()) 640 | .map(|label| active_index.checked_sub(*label as usize)) 641 | .collect::>>() 642 | .ok_or(())?; 643 | counter.branch(cursor, &target_indices)?; 644 | }, 645 | Return => { 646 | counter.increment(instruction_cost)?; 647 | counter.branch(cursor, &[0])?; 648 | }, 649 | _ => { 650 | // An ordinal non control flow instruction increments the cost of the current block. 651 | counter.increment(instruction_cost)?; 652 | }, 653 | } 654 | } 655 | 656 | counter.finalized_blocks.sort_unstable_by_key(|block| block.start_pos); 657 | Ok(counter.finalized_blocks) 658 | } 659 | 660 | fn inject_counter( 661 | instructions: &mut elements::Instructions, 662 | gas_function_cost: u64, 663 | locals_count: u32, 664 | rules: &R, 665 | gas_func: u32, 666 | ) -> Result<(), ()> { 667 | let blocks = determine_metered_blocks(instructions, rules, locals_count)?; 668 | insert_metering_calls(instructions, gas_function_cost, blocks, gas_func) 669 | } 670 | 671 | // Then insert metering calls into a sequence of instructions given the block locations and costs. 672 | fn insert_metering_calls( 673 | instructions: &mut elements::Instructions, 674 | gas_function_cost: u64, 675 | blocks: Vec, 676 | gas_func: u32, 677 | ) -> Result<(), ()> { 678 | use parity_wasm::elements::Instruction::*; 679 | 680 | // To do this in linear time, construct a new vector of instructions, copying over old 681 | // instructions one by one and injecting new ones as required. 682 | let new_instrs_len = instructions.elements().len() + 2 * blocks.len(); 683 | let original_instrs = 684 | mem::replace(instructions.elements_mut(), Vec::with_capacity(new_instrs_len)); 685 | let new_instrs = instructions.elements_mut(); 686 | 687 | let mut block_iter = blocks.into_iter().peekable(); 688 | for (original_pos, instr) in original_instrs.into_iter().enumerate() { 689 | // If there the next block starts at this position, inject metering instructions. 690 | let used_block = if let Some(block) = block_iter.peek() { 691 | if block.start_pos == original_pos { 692 | new_instrs 693 | .push(I64Const((block.cost.checked_add(gas_function_cost).ok_or(())?) as i64)); 694 | new_instrs.push(Call(gas_func)); 695 | true 696 | } else { 697 | false 698 | } 699 | } else { 700 | false 701 | }; 702 | 703 | if used_block { 704 | block_iter.next(); 705 | } 706 | 707 | // Copy over the original instruction. 708 | new_instrs.push(instr); 709 | } 710 | 711 | if block_iter.next().is_some() { 712 | return Err(()) 713 | } 714 | 715 | Ok(()) 716 | } 717 | 718 | #[cfg(test)] 719 | mod tests { 720 | use super::*; 721 | use parity_wasm::{builder, elements, elements::Instruction::*, serialize}; 722 | use pretty_assertions::assert_eq; 723 | 724 | fn get_function_body( 725 | module: &elements::Module, 726 | index: usize, 727 | ) -> Option<&[elements::Instruction]> { 728 | module 729 | .code_section() 730 | .and_then(|code_section| code_section.bodies().get(index)) 731 | .map(|func_body| func_body.code().elements()) 732 | } 733 | 734 | #[test] 735 | fn simple_grow_host_fn() { 736 | let module = parse_wat( 737 | r#"(module 738 | (func (result i32) 739 | global.get 0 740 | memory.grow) 741 | (global i32 (i32.const 42)) 742 | (memory 0 1) 743 | )"#, 744 | ); 745 | let backend = host_function::Injector::new("env", "gas"); 746 | let injected_module = 747 | super::inject(module, backend, &ConstantCostRules::new(1, 10_000, 1)).unwrap(); 748 | 749 | assert_eq!( 750 | get_function_body(&injected_module, 0).unwrap(), 751 | &vec![I64Const(2), Call(0), GetGlobal(0), Call(2), End][..] 752 | ); 753 | assert_eq!( 754 | get_function_body(&injected_module, 1).unwrap(), 755 | &vec![ 756 | GetLocal(0), 757 | GetLocal(0), 758 | I64ExtendUI32, 759 | I64Const(10000), 760 | I64Mul, 761 | Call(0), 762 | GrowMemory(0), 763 | End, 764 | ][..] 765 | ); 766 | 767 | let binary = serialize(injected_module).expect("serialization failed"); 768 | wasmparser::validate(&binary).unwrap(); 769 | } 770 | 771 | #[test] 772 | fn simple_grow_mut_global() { 773 | let module = parse_wat( 774 | r#"(module 775 | (func (result i32) 776 | global.get 0 777 | memory.grow) 778 | (global i32 (i32.const 42)) 779 | (memory 0 1) 780 | )"#, 781 | ); 782 | let backend = mutable_global::Injector::new("gas_left"); 783 | let injected_module = 784 | super::inject(module, backend, &ConstantCostRules::new(1, 10_000, 1)).unwrap(); 785 | 786 | assert_eq!( 787 | get_function_body(&injected_module, 0).unwrap(), 788 | &vec![I64Const(13), Call(1), GetGlobal(0), Call(2), End][..] 789 | ); 790 | assert_eq!( 791 | get_function_body(&injected_module, 1).unwrap(), 792 | &vec![ 793 | Instruction::GetGlobal(1), 794 | Instruction::GetLocal(0), 795 | Instruction::I64GeU, 796 | Instruction::If(elements::BlockType::NoResult), 797 | Instruction::GetGlobal(1), 798 | Instruction::GetLocal(0), 799 | Instruction::I64Sub, 800 | Instruction::SetGlobal(1), 801 | Instruction::Else, 802 | // sentinel val u64::MAX 803 | Instruction::I64Const(-1i64), // non-charged instruction 804 | Instruction::SetGlobal(1), // non-charged instruction 805 | Instruction::Unreachable, // non-charged instruction 806 | Instruction::End, 807 | Instruction::End, 808 | ][..] 809 | ); 810 | assert_eq!( 811 | get_function_body(&injected_module, 2).unwrap(), 812 | &vec![ 813 | GetLocal(0), 814 | GetLocal(0), 815 | I64ExtendUI32, 816 | I64Const(10000), 817 | I64Mul, 818 | Call(1), 819 | GrowMemory(0), 820 | End, 821 | ][..] 822 | ); 823 | 824 | let binary = serialize(injected_module).expect("serialization failed"); 825 | wasmparser::validate(&binary).unwrap(); 826 | } 827 | 828 | #[test] 829 | fn grow_no_gas_no_track_host_fn() { 830 | let module = parse_wat( 831 | r"(module 832 | (func (result i32) 833 | global.get 0 834 | memory.grow) 835 | (global i32 (i32.const 42)) 836 | (memory 0 1) 837 | )", 838 | ); 839 | let backend = host_function::Injector::new("env", "gas"); 840 | let injected_module = 841 | super::inject(module, backend, &ConstantCostRules::default()).unwrap(); 842 | 843 | assert_eq!( 844 | get_function_body(&injected_module, 0).unwrap(), 845 | &vec![I64Const(2), Call(0), GetGlobal(0), GrowMemory(0), End][..] 846 | ); 847 | 848 | assert_eq!(injected_module.functions_space(), 2); 849 | 850 | let binary = serialize(injected_module).expect("serialization failed"); 851 | wasmparser::validate(&binary).unwrap(); 852 | } 853 | 854 | #[test] 855 | fn grow_no_gas_no_track_mut_global() { 856 | let module = parse_wat( 857 | r"(module 858 | (func (result i32) 859 | global.get 0 860 | memory.grow) 861 | (global i32 (i32.const 42)) 862 | (memory 0 1) 863 | )", 864 | ); 865 | let backend = mutable_global::Injector::new("gas_left"); 866 | let injected_module = 867 | super::inject(module, backend, &ConstantCostRules::default()).unwrap(); 868 | 869 | assert_eq!( 870 | get_function_body(&injected_module, 0).unwrap(), 871 | &vec![I64Const(13), Call(1), GetGlobal(0), GrowMemory(0), End][..] 872 | ); 873 | 874 | assert_eq!(injected_module.functions_space(), 2); 875 | 876 | let binary = serialize(injected_module).expect("serialization failed"); 877 | wasmparser::validate(&binary).unwrap(); 878 | } 879 | 880 | #[test] 881 | fn call_index_host_fn() { 882 | let module = builder::module() 883 | .global() 884 | .value_type() 885 | .i32() 886 | .build() 887 | .function() 888 | .signature() 889 | .param() 890 | .i32() 891 | .build() 892 | .body() 893 | .build() 894 | .build() 895 | .function() 896 | .signature() 897 | .param() 898 | .i32() 899 | .build() 900 | .body() 901 | .with_instructions(elements::Instructions::new(vec![ 902 | Call(0), 903 | If(elements::BlockType::NoResult), 904 | Call(0), 905 | Call(0), 906 | Call(0), 907 | Else, 908 | Call(0), 909 | Call(0), 910 | End, 911 | Call(0), 912 | End, 913 | ])) 914 | .build() 915 | .build() 916 | .build(); 917 | 918 | let backend = host_function::Injector::new("env", "gas"); 919 | let injected_module = 920 | super::inject(module, backend, &ConstantCostRules::default()).unwrap(); 921 | 922 | assert_eq!( 923 | get_function_body(&injected_module, 1).unwrap(), 924 | &vec![ 925 | I64Const(3), 926 | Call(0), 927 | Call(1), 928 | If(elements::BlockType::NoResult), 929 | I64Const(3), 930 | Call(0), 931 | Call(1), 932 | Call(1), 933 | Call(1), 934 | Else, 935 | I64Const(2), 936 | Call(0), 937 | Call(1), 938 | Call(1), 939 | End, 940 | Call(1), 941 | End 942 | ][..] 943 | ); 944 | } 945 | 946 | #[test] 947 | fn call_index_mut_global() { 948 | let module = builder::module() 949 | .global() 950 | .value_type() 951 | .i32() 952 | .build() 953 | .function() 954 | .signature() 955 | .param() 956 | .i32() 957 | .build() 958 | .body() 959 | .build() 960 | .build() 961 | .function() 962 | .signature() 963 | .param() 964 | .i32() 965 | .build() 966 | .body() 967 | .with_instructions(elements::Instructions::new(vec![ 968 | Call(0), 969 | If(elements::BlockType::NoResult), 970 | Call(0), 971 | Call(0), 972 | Call(0), 973 | Else, 974 | Call(0), 975 | Call(0), 976 | End, 977 | Call(0), 978 | End, 979 | ])) 980 | .build() 981 | .build() 982 | .build(); 983 | 984 | let backend = mutable_global::Injector::new("gas_left"); 985 | let injected_module = 986 | super::inject(module, backend, &ConstantCostRules::default()).unwrap(); 987 | 988 | assert_eq!( 989 | get_function_body(&injected_module, 1).unwrap(), 990 | &vec![ 991 | I64Const(14), 992 | Call(2), 993 | Call(0), 994 | If(elements::BlockType::NoResult), 995 | I64Const(14), 996 | Call(2), 997 | Call(0), 998 | Call(0), 999 | Call(0), 1000 | Else, 1001 | I64Const(13), 1002 | Call(2), 1003 | Call(0), 1004 | Call(0), 1005 | End, 1006 | Call(0), 1007 | End 1008 | ][..] 1009 | ); 1010 | } 1011 | 1012 | fn parse_wat(source: &str) -> elements::Module { 1013 | let module_bytes = wat::parse_str(source).unwrap(); 1014 | elements::deserialize_buffer(module_bytes.as_ref()).unwrap() 1015 | } 1016 | 1017 | macro_rules! test_gas_counter_injection { 1018 | (names = ($name1:ident, $name2:ident); input = $input:expr; expected = $expected:expr) => { 1019 | #[test] 1020 | fn $name1() { 1021 | let input_module = parse_wat($input); 1022 | let expected_module = parse_wat($expected); 1023 | let injected_module = super::inject( 1024 | input_module, 1025 | host_function::Injector::new("env", "gas"), 1026 | &ConstantCostRules::default(), 1027 | ) 1028 | .expect("inject_gas_counter call failed"); 1029 | 1030 | let actual_func_body = get_function_body(&injected_module, 0) 1031 | .expect("injected module must have a function body"); 1032 | let expected_func_body = get_function_body(&expected_module, 0) 1033 | .expect("post-module must have a function body"); 1034 | 1035 | assert_eq!(actual_func_body, expected_func_body); 1036 | } 1037 | 1038 | #[test] 1039 | fn $name2() { 1040 | let input_module = parse_wat($input); 1041 | let draft_module = parse_wat($expected); 1042 | let gas_fun_cost = match mutable_global::Injector::new("gas_left") 1043 | .gas_meter(&input_module, &ConstantCostRules::default()) 1044 | { 1045 | GasMeter::Internal { cost, .. } => cost as i64, 1046 | _ => 0i64, 1047 | }; 1048 | 1049 | let injected_module = super::inject( 1050 | input_module, 1051 | mutable_global::Injector::new("gas_left"), 1052 | &ConstantCostRules::default(), 1053 | ) 1054 | .expect("inject_gas_counter call failed"); 1055 | 1056 | let actual_func_body = get_function_body(&injected_module, 0) 1057 | .expect("injected module must have a function body"); 1058 | let mut expected_func_body = get_function_body(&draft_module, 0) 1059 | .expect("post-module must have a function body") 1060 | .to_vec(); 1061 | 1062 | // modify expected instructions set for gas_metering::mutable_global 1063 | let mut iter = expected_func_body.iter_mut(); 1064 | while let Some(ins) = iter.next() { 1065 | if let I64Const(cost) = ins { 1066 | if let Some(ins_next) = iter.next() { 1067 | if let Call(0) = ins_next { 1068 | *cost += gas_fun_cost; 1069 | *ins_next = Call(1); 1070 | } 1071 | } 1072 | } 1073 | } 1074 | 1075 | assert_eq!(actual_func_body, &expected_func_body); 1076 | } 1077 | }; 1078 | } 1079 | 1080 | test_gas_counter_injection! { 1081 | names = (simple_host_fn, simple_mut_global); 1082 | input = r#" 1083 | (module 1084 | (func (result i32) 1085 | (global.get 0))) 1086 | "#; 1087 | expected = r#" 1088 | (module 1089 | (func (result i32) 1090 | (call 0 (i64.const 1)) 1091 | (global.get 0))) 1092 | "# 1093 | } 1094 | 1095 | test_gas_counter_injection! { 1096 | names = (nested_host_fn, nested_mut_global); 1097 | input = r#" 1098 | (module 1099 | (func (result i32) 1100 | (global.get 0) 1101 | (block 1102 | (global.get 0) 1103 | (global.get 0) 1104 | (global.get 0)) 1105 | (global.get 0))) 1106 | "#; 1107 | expected = r#" 1108 | (module 1109 | (func (result i32) 1110 | (call 0 (i64.const 6)) 1111 | (global.get 0) 1112 | (block 1113 | (global.get 0) 1114 | (global.get 0) 1115 | (global.get 0)) 1116 | (global.get 0))) 1117 | "# 1118 | } 1119 | 1120 | test_gas_counter_injection! { 1121 | names = (ifelse_host_fn, ifelse_mut_global); 1122 | input = r#" 1123 | (module 1124 | (func (result i32) 1125 | (global.get 0) 1126 | (if 1127 | (then 1128 | (global.get 0) 1129 | (global.get 0) 1130 | (global.get 0)) 1131 | (else 1132 | (global.get 0) 1133 | (global.get 0))) 1134 | (global.get 0))) 1135 | "#; 1136 | expected = r#" 1137 | (module 1138 | (func (result i32) 1139 | (call 0 (i64.const 3)) 1140 | (global.get 0) 1141 | (if 1142 | (then 1143 | (call 0 (i64.const 3)) 1144 | (global.get 0) 1145 | (global.get 0) 1146 | (global.get 0)) 1147 | (else 1148 | (call 0 (i64.const 2)) 1149 | (global.get 0) 1150 | (global.get 0))) 1151 | (global.get 0))) 1152 | "# 1153 | } 1154 | 1155 | test_gas_counter_injection! { 1156 | names = (branch_innermost_host_fn, branch_innermost_mut_global); 1157 | input = r#" 1158 | (module 1159 | (func (result i32) 1160 | (global.get 0) 1161 | (block 1162 | (global.get 0) 1163 | (drop) 1164 | (br 0) 1165 | (global.get 0) 1166 | (drop)) 1167 | (global.get 0))) 1168 | "#; 1169 | expected = r#" 1170 | (module 1171 | (func (result i32) 1172 | (call 0 (i64.const 6)) 1173 | (global.get 0) 1174 | (block 1175 | (global.get 0) 1176 | (drop) 1177 | (br 0) 1178 | (call 0 (i64.const 2)) 1179 | (global.get 0) 1180 | (drop)) 1181 | (global.get 0))) 1182 | "# 1183 | } 1184 | 1185 | test_gas_counter_injection! { 1186 | names = (branch_outer_block_host_fn, branch_outer_block_mut_global); 1187 | input = r#" 1188 | (module 1189 | (func (result i32) 1190 | (global.get 0) 1191 | (block 1192 | (global.get 0) 1193 | (if 1194 | (then 1195 | (global.get 0) 1196 | (global.get 0) 1197 | (drop) 1198 | (br_if 1))) 1199 | (global.get 0) 1200 | (drop)) 1201 | (global.get 0))) 1202 | "#; 1203 | expected = r#" 1204 | (module 1205 | (func (result i32) 1206 | (call 0 (i64.const 5)) 1207 | (global.get 0) 1208 | (block 1209 | (global.get 0) 1210 | (if 1211 | (then 1212 | (call 0 (i64.const 4)) 1213 | (global.get 0) 1214 | (global.get 0) 1215 | (drop) 1216 | (br_if 1))) 1217 | (call 0 (i64.const 2)) 1218 | (global.get 0) 1219 | (drop)) 1220 | (global.get 0))) 1221 | "# 1222 | } 1223 | 1224 | test_gas_counter_injection! { 1225 | names = (branch_outer_loop_host_fn, branch_outer_loop_mut_global); 1226 | input = r#" 1227 | (module 1228 | (func (result i32) 1229 | (global.get 0) 1230 | (loop 1231 | (global.get 0) 1232 | (if 1233 | (then 1234 | (global.get 0) 1235 | (br_if 0)) 1236 | (else 1237 | (global.get 0) 1238 | (global.get 0) 1239 | (drop) 1240 | (br_if 1))) 1241 | (global.get 0) 1242 | (drop)) 1243 | (global.get 0))) 1244 | "#; 1245 | expected = r#" 1246 | (module 1247 | (func (result i32) 1248 | (call 0 (i64.const 3)) 1249 | (global.get 0) 1250 | (loop 1251 | (call 0 (i64.const 4)) 1252 | (global.get 0) 1253 | (if 1254 | (then 1255 | (call 0 (i64.const 2)) 1256 | (global.get 0) 1257 | (br_if 0)) 1258 | (else 1259 | (call 0 (i64.const 4)) 1260 | (global.get 0) 1261 | (global.get 0) 1262 | (drop) 1263 | (br_if 1))) 1264 | (global.get 0) 1265 | (drop)) 1266 | (global.get 0))) 1267 | "# 1268 | } 1269 | 1270 | test_gas_counter_injection! { 1271 | names = (return_from_func_host_fn, return_from_func_mut_global); 1272 | input = r#" 1273 | (module 1274 | (func (result i32) 1275 | (global.get 0) 1276 | (if 1277 | (then 1278 | (return))) 1279 | (global.get 0))) 1280 | "#; 1281 | expected = r#" 1282 | (module 1283 | (func (result i32) 1284 | (call 0 (i64.const 2)) 1285 | (global.get 0) 1286 | (if 1287 | (then 1288 | (call 0 (i64.const 1)) 1289 | (return))) 1290 | (call 0 (i64.const 1)) 1291 | (global.get 0))) 1292 | "# 1293 | } 1294 | 1295 | test_gas_counter_injection! { 1296 | names = (branch_from_if_not_else_host_fn, branch_from_if_not_else_mut_global); 1297 | input = r#" 1298 | (module 1299 | (func (result i32) 1300 | (global.get 0) 1301 | (block 1302 | (global.get 0) 1303 | (if 1304 | (then (br 1)) 1305 | (else (br 0))) 1306 | (global.get 0) 1307 | (drop)) 1308 | (global.get 0))) 1309 | "#; 1310 | expected = r#" 1311 | (module 1312 | (func (result i32) 1313 | (call 0 (i64.const 5)) 1314 | (global.get 0) 1315 | (block 1316 | (global.get 0) 1317 | (if 1318 | (then 1319 | (call 0 (i64.const 1)) 1320 | (br 1)) 1321 | (else 1322 | (call 0 (i64.const 1)) 1323 | (br 0))) 1324 | (call 0 (i64.const 2)) 1325 | (global.get 0) 1326 | (drop)) 1327 | (global.get 0))) 1328 | "# 1329 | } 1330 | 1331 | test_gas_counter_injection! { 1332 | names = (empty_loop_host_fn, empty_loop_mut_global); 1333 | input = r#" 1334 | (module 1335 | (func 1336 | (loop 1337 | (br 0) 1338 | ) 1339 | unreachable 1340 | ) 1341 | ) 1342 | "#; 1343 | expected = r#" 1344 | (module 1345 | (func 1346 | (call 0 (i64.const 2)) 1347 | (loop 1348 | (call 0 (i64.const 1)) 1349 | (br 0) 1350 | ) 1351 | unreachable 1352 | ) 1353 | ) 1354 | "# 1355 | } 1356 | } 1357 | -------------------------------------------------------------------------------- /src/gas_metering/validation.rs: -------------------------------------------------------------------------------- 1 | //! This module is used to validate the correctness of the gas metering algorithm. 2 | //! 3 | //! Since the gas metering algorithm is complex, this checks correctness by fuzzing. The testing 4 | //! strategy is to generate random, valid Wasm modules using Binaryen's translate-to-fuzz 5 | //! functionality, then ensure for all functions defined, in all execution paths though the 6 | //! function body that do not trap that the amount of gas charged by the proposed metering 7 | //! instructions is correct. This is done by constructing a control flow graph and exhaustively 8 | //! searching through all paths, which may take exponential time in the size of the function body in 9 | //! the worst case. 10 | 11 | use super::{ConstantCostRules, MeteredBlock, Rules}; 12 | use parity_wasm::elements::{FuncBody, Instruction}; 13 | use std::collections::BTreeMap as Map; 14 | 15 | /// An ID for a node in a ControlFlowGraph. 16 | type NodeId = usize; 17 | 18 | /// A node in a control flow graph is commonly known as a basic block. This is a sequence of 19 | /// operations that are always executed sequentially. 20 | #[derive(Debug, Default)] 21 | struct ControlFlowNode { 22 | /// The index of the first instruction in the basic block. This is only used for debugging. 23 | first_instr_pos: Option, 24 | 25 | /// The actual gas cost of executing all instructions in the basic block. 26 | actual_cost: u64, 27 | 28 | /// The amount of gas charged by the injected metering instructions within this basic block. 29 | charged_cost: u64, 30 | 31 | /// Whether there are any other nodes in the graph that loop back to this one. Every cycle in 32 | /// the control flow graph contains at least one node with this flag set. 33 | is_loop_target: bool, 34 | 35 | /// Edges in the "forward" direction of the graph. The graph of nodes and their forward edges 36 | /// forms a directed acyclic graph (DAG). 37 | forward_edges: Vec, 38 | 39 | /// Edges in the "backwards" direction. These edges form cycles in the graph. 40 | loopback_edges: Vec, 41 | } 42 | 43 | /// A control flow graph where nodes are basic blocks and edges represent possible transitions 44 | /// between them in execution flow. The graph has two types of edges, forward and loop-back edges. 45 | /// The subgraph with only the forward edges forms a directed acyclic graph (DAG); including the 46 | /// loop-back edges introduces cycles. 47 | #[derive(Debug)] 48 | pub struct ControlFlowGraph { 49 | nodes: Vec, 50 | } 51 | 52 | impl ControlFlowGraph { 53 | fn new() -> Self { 54 | ControlFlowGraph { nodes: Vec::new() } 55 | } 56 | 57 | fn get_node(&self, node_id: NodeId) -> &ControlFlowNode { 58 | self.nodes.get(node_id).unwrap() 59 | } 60 | 61 | fn get_node_mut(&mut self, node_id: NodeId) -> &mut ControlFlowNode { 62 | self.nodes.get_mut(node_id).unwrap() 63 | } 64 | 65 | fn add_node(&mut self) -> NodeId { 66 | self.nodes.push(ControlFlowNode::default()); 67 | self.nodes.len() - 1 68 | } 69 | 70 | fn increment_actual_cost(&mut self, node_id: NodeId, cost: u32) { 71 | self.get_node_mut(node_id).actual_cost += u64::from(cost); 72 | } 73 | 74 | fn increment_charged_cost(&mut self, node_id: NodeId, cost: u64) { 75 | self.get_node_mut(node_id).charged_cost += cost; 76 | } 77 | 78 | fn set_first_instr_pos(&mut self, node_id: NodeId, first_instr_pos: usize) { 79 | self.get_node_mut(node_id).first_instr_pos = Some(first_instr_pos) 80 | } 81 | 82 | fn new_edge(&mut self, from_id: NodeId, target_frame: &ControlFrame) { 83 | if target_frame.is_loop { 84 | self.new_loopback_edge(from_id, target_frame.entry_node); 85 | } else { 86 | self.new_forward_edge(from_id, target_frame.exit_node); 87 | } 88 | } 89 | 90 | fn new_forward_edge(&mut self, from_id: NodeId, to_id: NodeId) { 91 | self.get_node_mut(from_id).forward_edges.push(to_id) 92 | } 93 | 94 | fn new_loopback_edge(&mut self, from_id: NodeId, to_id: NodeId) { 95 | self.get_node_mut(from_id).loopback_edges.push(to_id); 96 | self.get_node_mut(to_id).is_loop_target = true; 97 | } 98 | } 99 | 100 | /// A control frame is opened upon entry into a function and by the `block`, `if`, and `loop` 101 | /// instructions and is closed by `end` instructions. 102 | struct ControlFrame { 103 | is_loop: bool, 104 | entry_node: NodeId, 105 | exit_node: NodeId, 106 | active_node: NodeId, 107 | } 108 | 109 | impl ControlFrame { 110 | fn new(entry_node_id: NodeId, exit_node_id: NodeId, is_loop: bool) -> Self { 111 | ControlFrame { 112 | is_loop, 113 | entry_node: entry_node_id, 114 | exit_node: exit_node_id, 115 | active_node: entry_node_id, 116 | } 117 | } 118 | } 119 | 120 | /// Construct a control flow graph from a function body and the metered blocks computed for it. 121 | /// 122 | /// This assumes that the function body has been validated already, otherwise this may panic. 123 | fn build_control_flow_graph( 124 | body: &FuncBody, 125 | rules: &impl Rules, 126 | blocks: &[MeteredBlock], 127 | ) -> Result { 128 | let mut graph = ControlFlowGraph::new(); 129 | 130 | let entry_node_id = graph.add_node(); 131 | let terminal_node_id = graph.add_node(); 132 | 133 | graph.set_first_instr_pos(entry_node_id, 0); 134 | 135 | let mut stack = vec![ControlFrame::new(entry_node_id, terminal_node_id, false)]; 136 | let mut metered_blocks_iter = blocks.iter().peekable(); 137 | 138 | let locals_count = body 139 | .locals() 140 | .iter() 141 | .try_fold(0u32, |count, val_type| count.checked_add(val_type.count())) 142 | .ok_or(())?; 143 | let locals_init_cost = rules.call_per_local_cost().checked_mul(locals_count).ok_or(())?; 144 | 145 | for (cursor, instruction) in body.code().elements().iter().enumerate() { 146 | let active_node_id = stack 147 | .last() 148 | .expect("module is valid by pre-condition; control stack must not be empty; qed") 149 | .active_node; 150 | 151 | // Increment the charged cost if there are metering instructions to be inserted here. 152 | let apply_block = 153 | metered_blocks_iter.peek().map_or(false, |block| block.start_pos == cursor); 154 | if apply_block { 155 | let next_metered_block = 156 | metered_blocks_iter.next().expect("peek returned an item; qed"); 157 | graph.increment_charged_cost(active_node_id, next_metered_block.cost); 158 | } 159 | 160 | // Add locals initialization cost to the function block. 161 | if cursor == 0 { 162 | graph.increment_actual_cost(active_node_id, locals_init_cost); 163 | } 164 | let instruction_cost = rules.instruction_cost(instruction).ok_or(())?; 165 | match instruction { 166 | Instruction::Block(_) => { 167 | graph.increment_actual_cost(active_node_id, instruction_cost); 168 | 169 | let exit_node_id = graph.add_node(); 170 | stack.push(ControlFrame::new(active_node_id, exit_node_id, false)); 171 | }, 172 | Instruction::If(_) => { 173 | graph.increment_actual_cost(active_node_id, instruction_cost); 174 | 175 | let then_node_id = graph.add_node(); 176 | let exit_node_id = graph.add_node(); 177 | 178 | stack.push(ControlFrame::new(then_node_id, exit_node_id, false)); 179 | graph.new_forward_edge(active_node_id, then_node_id); 180 | graph.set_first_instr_pos(then_node_id, cursor + 1); 181 | }, 182 | Instruction::Loop(_) => { 183 | graph.increment_actual_cost(active_node_id, instruction_cost); 184 | 185 | let loop_node_id = graph.add_node(); 186 | let exit_node_id = graph.add_node(); 187 | 188 | stack.push(ControlFrame::new(loop_node_id, exit_node_id, true)); 189 | graph.new_forward_edge(active_node_id, loop_node_id); 190 | graph.set_first_instr_pos(loop_node_id, cursor + 1); 191 | }, 192 | Instruction::Else => { 193 | let active_frame_idx = stack.len() - 1; 194 | let prev_frame_idx = stack.len() - 2; 195 | 196 | let else_node_id = graph.add_node(); 197 | stack[active_frame_idx].active_node = else_node_id; 198 | 199 | let prev_node_id = stack[prev_frame_idx].active_node; 200 | graph.new_forward_edge(prev_node_id, else_node_id); 201 | graph.set_first_instr_pos(else_node_id, cursor + 1); 202 | }, 203 | Instruction::End => { 204 | let closing_frame = stack.pop() 205 | .expect("module is valid by pre-condition; ends correspond to control stack frames; qed"); 206 | 207 | graph.new_forward_edge(active_node_id, closing_frame.exit_node); 208 | graph.set_first_instr_pos(closing_frame.exit_node, cursor + 1); 209 | 210 | if let Some(active_frame) = stack.last_mut() { 211 | active_frame.active_node = closing_frame.exit_node; 212 | } 213 | }, 214 | Instruction::Br(label) => { 215 | graph.increment_actual_cost(active_node_id, instruction_cost); 216 | 217 | let active_frame_idx = stack.len() - 1; 218 | let target_frame_idx = active_frame_idx - (*label as usize); 219 | graph.new_edge(active_node_id, &stack[target_frame_idx]); 220 | 221 | // Next instruction is unreachable, but carry on anyway. 222 | let new_node_id = graph.add_node(); 223 | stack[active_frame_idx].active_node = new_node_id; 224 | graph.set_first_instr_pos(new_node_id, cursor + 1); 225 | }, 226 | Instruction::BrIf(label) => { 227 | graph.increment_actual_cost(active_node_id, instruction_cost); 228 | 229 | let active_frame_idx = stack.len() - 1; 230 | let target_frame_idx = active_frame_idx - (*label as usize); 231 | graph.new_edge(active_node_id, &stack[target_frame_idx]); 232 | 233 | let new_node_id = graph.add_node(); 234 | stack[active_frame_idx].active_node = new_node_id; 235 | graph.new_forward_edge(active_node_id, new_node_id); 236 | graph.set_first_instr_pos(new_node_id, cursor + 1); 237 | }, 238 | Instruction::BrTable(br_table_data) => { 239 | graph.increment_actual_cost(active_node_id, instruction_cost); 240 | 241 | let active_frame_idx = stack.len() - 1; 242 | for &label in [br_table_data.default].iter().chain(br_table_data.table.iter()) { 243 | let target_frame_idx = active_frame_idx - (label as usize); 244 | graph.new_edge(active_node_id, &stack[target_frame_idx]); 245 | } 246 | 247 | let new_node_id = graph.add_node(); 248 | stack[active_frame_idx].active_node = new_node_id; 249 | graph.set_first_instr_pos(new_node_id, cursor + 1); 250 | }, 251 | Instruction::Return => { 252 | graph.increment_actual_cost(active_node_id, instruction_cost); 253 | 254 | graph.new_forward_edge(active_node_id, terminal_node_id); 255 | 256 | let active_frame_idx = stack.len() - 1; 257 | let new_node_id = graph.add_node(); 258 | stack[active_frame_idx].active_node = new_node_id; 259 | graph.set_first_instr_pos(new_node_id, cursor + 1); 260 | }, 261 | _ => graph.increment_actual_cost(active_node_id, instruction_cost), 262 | } 263 | } 264 | 265 | assert!(stack.is_empty()); 266 | 267 | Ok(graph) 268 | } 269 | 270 | /// Exhaustively search through all paths in the control flow graph, starting from the first node 271 | /// and ensure that 1) all paths with only forward edges ending with the terminal node have an 272 | /// equal total actual gas cost and total charged gas cost, and 2) all cycles beginning with a loop 273 | /// entry point and ending with a node with a loop-back edge to the entry point have equal actual 274 | /// and charged gas costs. If this returns true, then the metered blocks used to construct the 275 | /// control flow graph are correct with respect to the function body. 276 | /// 277 | /// In the worst case, this runs in time exponential in the size of the graph. 278 | fn validate_graph_gas_costs(graph: &ControlFlowGraph) -> bool { 279 | fn visit( 280 | graph: &ControlFlowGraph, 281 | node_id: NodeId, 282 | mut total_actual: u64, 283 | mut total_charged: u64, 284 | loop_costs: &mut Map, 285 | ) -> bool { 286 | let node = graph.get_node(node_id); 287 | 288 | total_actual += node.actual_cost; 289 | total_charged += node.charged_cost; 290 | 291 | if node.is_loop_target { 292 | loop_costs.insert(node_id, (node.actual_cost, node.charged_cost)); 293 | } 294 | 295 | if node.forward_edges.is_empty() && total_actual != total_charged { 296 | return false 297 | } 298 | 299 | for loop_node_id in node.loopback_edges.iter() { 300 | let (loop_actual, loop_charged) = loop_costs 301 | .get_mut(loop_node_id) 302 | .expect("cannot arrive at loopback edge without visiting loop entry node"); 303 | if loop_actual != loop_charged { 304 | return false 305 | } 306 | } 307 | 308 | for next_node_id in node.forward_edges.iter() { 309 | if !visit(graph, *next_node_id, total_actual, total_charged, loop_costs) { 310 | return false 311 | } 312 | } 313 | 314 | if node.is_loop_target { 315 | loop_costs.remove(&node_id); 316 | } 317 | 318 | true 319 | } 320 | 321 | // Recursively explore all paths through the execution graph starting from the entry node. 322 | visit(graph, 0, 0, 0, &mut Map::new()) 323 | } 324 | 325 | /// Validate that the metered blocks are correct with respect to the function body by exhaustively 326 | /// searching all paths through the control flow graph. 327 | /// 328 | /// This assumes that the function body has been validated already, otherwise this may panic. 329 | fn validate_metering_injections( 330 | body: &FuncBody, 331 | rules: &impl Rules, 332 | blocks: &[MeteredBlock], 333 | ) -> Result { 334 | let graph = build_control_flow_graph(body, rules, blocks)?; 335 | Ok(validate_graph_gas_costs(&graph)) 336 | } 337 | 338 | mod tests { 339 | use super::{super::determine_metered_blocks, *}; 340 | 341 | use binaryen::tools::translate_to_fuzz_mvp; 342 | use parity_wasm::elements; 343 | use rand::{thread_rng, RngCore}; 344 | 345 | #[test] 346 | fn test_build_control_flow_graph() { 347 | for _ in 0..20 { 348 | let mut rand_input = [0u8; 2048]; 349 | thread_rng().fill_bytes(&mut rand_input); 350 | 351 | let module_bytes = translate_to_fuzz_mvp(&rand_input).write(); 352 | let module: elements::Module = elements::deserialize_buffer(&module_bytes) 353 | .expect("failed to parse Wasm blob generated by translate_to_fuzz"); 354 | 355 | for func_body in module.code_section().iter().flat_map(|section| section.bodies()) { 356 | let rules = ConstantCostRules::default(); 357 | let locals_count = func_body.locals().iter().map(|val_type| val_type.count()).sum(); 358 | 359 | let metered_blocks = 360 | determine_metered_blocks(func_body.code(), &rules, locals_count).unwrap(); 361 | let success = 362 | validate_metering_injections(func_body, &rules, &metered_blocks).unwrap(); 363 | assert!(success); 364 | } 365 | } 366 | } 367 | } 368 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(feature = "std"), no_std)] 2 | 3 | extern crate alloc; 4 | 5 | mod export_globals; 6 | pub mod gas_metering; 7 | mod stack_limiter; 8 | 9 | pub use export_globals::export_mutable_globals; 10 | pub use parity_wasm; 11 | pub use stack_limiter::inject as inject_stack_limiter; 12 | -------------------------------------------------------------------------------- /src/stack_limiter/max_height.rs: -------------------------------------------------------------------------------- 1 | use super::resolve_func_type; 2 | use alloc::vec::Vec; 3 | use parity_wasm::elements::{self, BlockType, Type}; 4 | 5 | #[cfg(feature = "sign_ext")] 6 | use parity_wasm::elements::SignExtInstruction; 7 | 8 | // The cost in stack items that should be charged per call of a function. This is 9 | // is a static cost that is added to each function call. This makes sense because even 10 | // if a function does not use any parameters or locals some stack space on the host 11 | // machine might be consumed to hold some context. 12 | const ACTIVATION_FRAME_COST: u32 = 2; 13 | 14 | /// Control stack frame. 15 | #[derive(Debug)] 16 | struct Frame { 17 | /// Stack becomes polymorphic only after an instruction that 18 | /// never passes control further was executed. 19 | is_polymorphic: bool, 20 | 21 | /// Count of values which will be pushed after the exit 22 | /// from the current block. 23 | end_arity: u32, 24 | 25 | /// Count of values which should be poped upon a branch to 26 | /// this frame. 27 | /// 28 | /// This might be diffirent from `end_arity` since branch 29 | /// to the loop header can't take any values. 30 | branch_arity: u32, 31 | 32 | /// Stack height before entering in the block. 33 | start_height: u32, 34 | } 35 | 36 | /// This is a compound stack that abstracts tracking height of the value stack 37 | /// and manipulation of the control stack. 38 | struct Stack { 39 | height: u32, 40 | control_stack: Vec, 41 | } 42 | 43 | impl Stack { 44 | fn new() -> Stack { 45 | Stack { height: ACTIVATION_FRAME_COST, control_stack: Vec::new() } 46 | } 47 | 48 | /// Returns current height of the value stack. 49 | fn height(&self) -> u32 { 50 | self.height 51 | } 52 | 53 | /// Returns a reference to a frame by specified depth relative to the top of 54 | /// control stack. 55 | fn frame(&self, rel_depth: u32) -> Result<&Frame, &'static str> { 56 | let control_stack_height: usize = self.control_stack.len(); 57 | let last_idx = control_stack_height.checked_sub(1).ok_or("control stack is empty")?; 58 | let idx = last_idx.checked_sub(rel_depth as usize).ok_or("control stack out-of-bounds")?; 59 | Ok(&self.control_stack[idx]) 60 | } 61 | 62 | /// Mark successive instructions as unreachable. 63 | /// 64 | /// This effectively makes stack polymorphic. 65 | fn mark_unreachable(&mut self) -> Result<(), &'static str> { 66 | let top_frame = self.control_stack.last_mut().ok_or("stack must be non-empty")?; 67 | top_frame.is_polymorphic = true; 68 | Ok(()) 69 | } 70 | 71 | /// Push control frame into the control stack. 72 | fn push_frame(&mut self, frame: Frame) { 73 | self.control_stack.push(frame); 74 | } 75 | 76 | /// Pop control frame from the control stack. 77 | /// 78 | /// Returns `Err` if the control stack is empty. 79 | fn pop_frame(&mut self) -> Result { 80 | self.control_stack.pop().ok_or("stack must be non-empty") 81 | } 82 | 83 | /// Truncate the height of value stack to the specified height. 84 | fn trunc(&mut self, new_height: u32) { 85 | self.height = new_height; 86 | } 87 | 88 | /// Push specified number of values into the value stack. 89 | /// 90 | /// Returns `Err` if the height overflow usize value. 91 | fn push_values(&mut self, value_count: u32) -> Result<(), &'static str> { 92 | self.height = self.height.checked_add(value_count).ok_or("stack overflow")?; 93 | Ok(()) 94 | } 95 | 96 | /// Pop specified number of values from the value stack. 97 | /// 98 | /// Returns `Err` if the stack happen to be negative value after 99 | /// values popped. 100 | fn pop_values(&mut self, value_count: u32) -> Result<(), &'static str> { 101 | if value_count == 0 { 102 | return Ok(()) 103 | } 104 | { 105 | let top_frame = self.frame(0)?; 106 | if self.height == top_frame.start_height { 107 | // It is an error to pop more values than was pushed in the current frame 108 | // (ie pop values pushed in the parent frame), unless the frame became 109 | // polymorphic. 110 | return if top_frame.is_polymorphic { 111 | Ok(()) 112 | } else { 113 | return Err("trying to pop more values than pushed") 114 | } 115 | } 116 | } 117 | 118 | self.height = self.height.checked_sub(value_count).ok_or("stack underflow")?; 119 | 120 | Ok(()) 121 | } 122 | } 123 | 124 | /// This function expects the function to be validated. 125 | pub fn compute(func_idx: u32, module: &elements::Module) -> Result { 126 | use parity_wasm::elements::Instruction::*; 127 | 128 | let func_section = module.function_section().ok_or("No function section")?; 129 | let code_section = module.code_section().ok_or("No code section")?; 130 | let type_section = module.type_section().ok_or("No type section")?; 131 | 132 | // Get a signature and a body of the specified function. 133 | let func_sig_idx = func_section 134 | .entries() 135 | .get(func_idx as usize) 136 | .ok_or("Function is not found in func section")? 137 | .type_ref(); 138 | let Type::Function(func_signature) = type_section 139 | .types() 140 | .get(func_sig_idx as usize) 141 | .ok_or("Function is not found in func section")?; 142 | let body = code_section 143 | .bodies() 144 | .get(func_idx as usize) 145 | .ok_or("Function body for the index isn't found")?; 146 | let instructions = body.code(); 147 | 148 | let mut stack = Stack::new(); 149 | let mut max_height: u32 = 0; 150 | let mut pc = 0; 151 | 152 | // Add implicit frame for the function. Breaks to this frame and execution of 153 | // the last end should deal with this frame. 154 | let func_arity = func_signature.results().len() as u32; 155 | stack.push_frame(Frame { 156 | is_polymorphic: false, 157 | end_arity: func_arity, 158 | branch_arity: func_arity, 159 | start_height: 0, 160 | }); 161 | 162 | loop { 163 | if pc >= instructions.elements().len() { 164 | break 165 | } 166 | 167 | // If current value stack is higher than maximal height observed so far, 168 | // save the new height. 169 | // However, we don't increase maximal value in unreachable code. 170 | if stack.height() > max_height && !stack.frame(0)?.is_polymorphic { 171 | max_height = stack.height(); 172 | } 173 | 174 | let opcode = &instructions.elements()[pc]; 175 | 176 | match opcode { 177 | Nop => {}, 178 | Block(ty) | Loop(ty) | If(ty) => { 179 | let end_arity = u32::from(*ty != BlockType::NoResult); 180 | let branch_arity = if let Loop(_) = *opcode { 0 } else { end_arity }; 181 | if let If(_) = *opcode { 182 | stack.pop_values(1)?; 183 | } 184 | let height = stack.height(); 185 | stack.push_frame(Frame { 186 | is_polymorphic: false, 187 | end_arity, 188 | branch_arity, 189 | start_height: height, 190 | }); 191 | }, 192 | Else => { 193 | // The frame at the top should be pushed by `If`. So we leave 194 | // it as is. 195 | }, 196 | End => { 197 | let frame = stack.pop_frame()?; 198 | stack.trunc(frame.start_height); 199 | stack.push_values(frame.end_arity)?; 200 | }, 201 | Unreachable => { 202 | stack.mark_unreachable()?; 203 | }, 204 | Br(target) => { 205 | // Pop values for the destination block result. 206 | let target_arity = stack.frame(*target)?.branch_arity; 207 | stack.pop_values(target_arity)?; 208 | 209 | // This instruction unconditionally transfers control to the specified block, 210 | // thus all instruction until the end of the current block is deemed unreachable 211 | stack.mark_unreachable()?; 212 | }, 213 | BrIf(target) => { 214 | // Pop values for the destination block result. 215 | let target_arity = stack.frame(*target)?.branch_arity; 216 | stack.pop_values(target_arity)?; 217 | 218 | // Pop condition value. 219 | stack.pop_values(1)?; 220 | 221 | // Push values back. 222 | stack.push_values(target_arity)?; 223 | }, 224 | BrTable(br_table_data) => { 225 | let arity_of_default = stack.frame(br_table_data.default)?.branch_arity; 226 | 227 | // Check that all jump targets have an equal arities. 228 | for target in &*br_table_data.table { 229 | let arity = stack.frame(*target)?.branch_arity; 230 | if arity != arity_of_default { 231 | return Err("Arity of all jump-targets must be equal") 232 | } 233 | } 234 | 235 | // Because all jump targets have an equal arities, we can just take arity of 236 | // the default branch. 237 | stack.pop_values(arity_of_default)?; 238 | 239 | // This instruction doesn't let control flow to go further, since the control flow 240 | // should take either one of branches depending on the value or the default branch. 241 | stack.mark_unreachable()?; 242 | }, 243 | Return => { 244 | // Pop return values of the function. Mark successive instructions as unreachable 245 | // since this instruction doesn't let control flow to go further. 246 | stack.pop_values(func_arity)?; 247 | stack.mark_unreachable()?; 248 | }, 249 | Call(idx) => { 250 | let ty = resolve_func_type(*idx, module)?; 251 | 252 | // Pop values for arguments of the function. 253 | stack.pop_values(ty.params().len() as u32)?; 254 | 255 | // Push result of the function execution to the stack. 256 | let callee_arity = ty.results().len() as u32; 257 | stack.push_values(callee_arity)?; 258 | }, 259 | CallIndirect(x, _) => { 260 | let Type::Function(ty) = 261 | type_section.types().get(*x as usize).ok_or("Type not found")?; 262 | 263 | // Pop the offset into the function table. 264 | stack.pop_values(1)?; 265 | 266 | // Pop values for arguments of the function. 267 | stack.pop_values(ty.params().len() as u32)?; 268 | 269 | // Push result of the function execution to the stack. 270 | let callee_arity = ty.results().len() as u32; 271 | stack.push_values(callee_arity)?; 272 | }, 273 | Drop => { 274 | stack.pop_values(1)?; 275 | }, 276 | Select => { 277 | // Pop two values and one condition. 278 | stack.pop_values(2)?; 279 | stack.pop_values(1)?; 280 | 281 | // Push the selected value. 282 | stack.push_values(1)?; 283 | }, 284 | GetLocal(_) => { 285 | stack.push_values(1)?; 286 | }, 287 | SetLocal(_) => { 288 | stack.pop_values(1)?; 289 | }, 290 | TeeLocal(_) => { 291 | // This instruction pops and pushes the value, so 292 | // effectively it doesn't modify the stack height. 293 | stack.pop_values(1)?; 294 | stack.push_values(1)?; 295 | }, 296 | GetGlobal(_) => { 297 | stack.push_values(1)?; 298 | }, 299 | SetGlobal(_) => { 300 | stack.pop_values(1)?; 301 | }, 302 | I32Load(_, _) | 303 | I64Load(_, _) | 304 | F32Load(_, _) | 305 | F64Load(_, _) | 306 | I32Load8S(_, _) | 307 | I32Load8U(_, _) | 308 | I32Load16S(_, _) | 309 | I32Load16U(_, _) | 310 | I64Load8S(_, _) | 311 | I64Load8U(_, _) | 312 | I64Load16S(_, _) | 313 | I64Load16U(_, _) | 314 | I64Load32S(_, _) | 315 | I64Load32U(_, _) => { 316 | // These instructions pop the address and pushes the result, 317 | // which effictively don't modify the stack height. 318 | stack.pop_values(1)?; 319 | stack.push_values(1)?; 320 | }, 321 | 322 | I32Store(_, _) | 323 | I64Store(_, _) | 324 | F32Store(_, _) | 325 | F64Store(_, _) | 326 | I32Store8(_, _) | 327 | I32Store16(_, _) | 328 | I64Store8(_, _) | 329 | I64Store16(_, _) | 330 | I64Store32(_, _) => { 331 | // These instructions pop the address and the value. 332 | stack.pop_values(2)?; 333 | }, 334 | 335 | CurrentMemory(_) => { 336 | // Pushes current memory size 337 | stack.push_values(1)?; 338 | }, 339 | GrowMemory(_) => { 340 | // Grow memory takes the value of pages to grow and pushes 341 | stack.pop_values(1)?; 342 | stack.push_values(1)?; 343 | }, 344 | 345 | I32Const(_) | I64Const(_) | F32Const(_) | F64Const(_) => { 346 | // These instructions just push the single literal value onto the stack. 347 | stack.push_values(1)?; 348 | }, 349 | 350 | I32Eqz | I64Eqz => { 351 | // These instructions pop the value and compare it against zero, and pushes 352 | // the result of the comparison. 353 | stack.pop_values(1)?; 354 | stack.push_values(1)?; 355 | }, 356 | 357 | I32Eq | I32Ne | I32LtS | I32LtU | I32GtS | I32GtU | I32LeS | I32LeU | I32GeS | 358 | I32GeU | I64Eq | I64Ne | I64LtS | I64LtU | I64GtS | I64GtU | I64LeS | I64LeU | 359 | I64GeS | I64GeU | F32Eq | F32Ne | F32Lt | F32Gt | F32Le | F32Ge | F64Eq | F64Ne | 360 | F64Lt | F64Gt | F64Le | F64Ge => { 361 | // Comparison operations take two operands and produce one result. 362 | stack.pop_values(2)?; 363 | stack.push_values(1)?; 364 | }, 365 | 366 | I32Clz | I32Ctz | I32Popcnt | I64Clz | I64Ctz | I64Popcnt | F32Abs | F32Neg | 367 | F32Ceil | F32Floor | F32Trunc | F32Nearest | F32Sqrt | F64Abs | F64Neg | F64Ceil | 368 | F64Floor | F64Trunc | F64Nearest | F64Sqrt => { 369 | // Unary operators take one operand and produce one result. 370 | stack.pop_values(1)?; 371 | stack.push_values(1)?; 372 | }, 373 | 374 | I32Add | I32Sub | I32Mul | I32DivS | I32DivU | I32RemS | I32RemU | I32And | I32Or | 375 | I32Xor | I32Shl | I32ShrS | I32ShrU | I32Rotl | I32Rotr | I64Add | I64Sub | 376 | I64Mul | I64DivS | I64DivU | I64RemS | I64RemU | I64And | I64Or | I64Xor | I64Shl | 377 | I64ShrS | I64ShrU | I64Rotl | I64Rotr | F32Add | F32Sub | F32Mul | F32Div | 378 | F32Min | F32Max | F32Copysign | F64Add | F64Sub | F64Mul | F64Div | F64Min | 379 | F64Max | F64Copysign => { 380 | // Binary operators take two operands and produce one result. 381 | stack.pop_values(2)?; 382 | stack.push_values(1)?; 383 | }, 384 | 385 | I32WrapI64 | I32TruncSF32 | I32TruncUF32 | I32TruncSF64 | I32TruncUF64 | 386 | I64ExtendSI32 | I64ExtendUI32 | I64TruncSF32 | I64TruncUF32 | I64TruncSF64 | 387 | I64TruncUF64 | F32ConvertSI32 | F32ConvertUI32 | F32ConvertSI64 | F32ConvertUI64 | 388 | F32DemoteF64 | F64ConvertSI32 | F64ConvertUI32 | F64ConvertSI64 | F64ConvertUI64 | 389 | F64PromoteF32 | I32ReinterpretF32 | I64ReinterpretF64 | F32ReinterpretI32 | 390 | F64ReinterpretI64 => { 391 | // Conversion operators take one value and produce one result. 392 | stack.pop_values(1)?; 393 | stack.push_values(1)?; 394 | }, 395 | 396 | #[cfg(feature = "sign_ext")] 397 | SignExt(SignExtInstruction::I32Extend8S) | 398 | SignExt(SignExtInstruction::I32Extend16S) | 399 | SignExt(SignExtInstruction::I64Extend8S) | 400 | SignExt(SignExtInstruction::I64Extend16S) | 401 | SignExt(SignExtInstruction::I64Extend32S) => { 402 | stack.pop_values(1)?; 403 | stack.push_values(1)?; 404 | }, 405 | } 406 | pc += 1; 407 | } 408 | 409 | Ok(max_height) 410 | } 411 | 412 | #[cfg(test)] 413 | mod tests { 414 | use super::*; 415 | use parity_wasm::elements; 416 | 417 | fn parse_wat(source: &str) -> elements::Module { 418 | elements::deserialize_buffer(&wat::parse_str(source).expect("Failed to wat2wasm")) 419 | .expect("Failed to deserialize the module") 420 | } 421 | 422 | #[test] 423 | fn simple_test() { 424 | let module = parse_wat( 425 | r#" 426 | (module 427 | (func 428 | i32.const 1 429 | i32.const 2 430 | i32.const 3 431 | drop 432 | drop 433 | drop 434 | ) 435 | ) 436 | "#, 437 | ); 438 | 439 | let height = compute(0, &module).unwrap(); 440 | assert_eq!(height, 3 + ACTIVATION_FRAME_COST); 441 | } 442 | 443 | #[test] 444 | fn implicit_and_explicit_return() { 445 | let module = parse_wat( 446 | r#" 447 | (module 448 | (func (result i32) 449 | i32.const 0 450 | return 451 | ) 452 | ) 453 | "#, 454 | ); 455 | 456 | let height = compute(0, &module).unwrap(); 457 | assert_eq!(height, 1 + ACTIVATION_FRAME_COST); 458 | } 459 | 460 | #[test] 461 | fn dont_count_in_unreachable() { 462 | let module = parse_wat( 463 | r#" 464 | (module 465 | (memory 0) 466 | (func (result i32) 467 | unreachable 468 | memory.grow 469 | ) 470 | ) 471 | "#, 472 | ); 473 | 474 | let height = compute(0, &module).unwrap(); 475 | assert_eq!(height, ACTIVATION_FRAME_COST); 476 | } 477 | 478 | #[test] 479 | fn yet_another_test() { 480 | let module = parse_wat( 481 | r#" 482 | (module 483 | (memory 0) 484 | (func 485 | ;; Push two values and then pop them. 486 | ;; This will make max depth to be equal to 2. 487 | i32.const 0 488 | i32.const 1 489 | drop 490 | drop 491 | 492 | ;; Code after `unreachable` shouldn't have an effect 493 | ;; on the max depth. 494 | unreachable 495 | i32.const 0 496 | i32.const 1 497 | i32.const 2 498 | ) 499 | ) 500 | "#, 501 | ); 502 | 503 | let height = compute(0, &module).unwrap(); 504 | assert_eq!(height, 2 + ACTIVATION_FRAME_COST); 505 | } 506 | 507 | #[test] 508 | fn call_indirect() { 509 | let module = parse_wat( 510 | r#" 511 | (module 512 | (table $ptr 1 1 funcref) 513 | (elem $ptr (i32.const 0) func 1) 514 | (func $main 515 | (call_indirect (i32.const 0)) 516 | (call_indirect (i32.const 0)) 517 | (call_indirect (i32.const 0)) 518 | ) 519 | (func $callee 520 | i64.const 42 521 | drop 522 | ) 523 | ) 524 | "#, 525 | ); 526 | 527 | let height = compute(0, &module).unwrap(); 528 | assert_eq!(height, 1 + ACTIVATION_FRAME_COST); 529 | } 530 | 531 | #[test] 532 | fn breaks() { 533 | let module = parse_wat( 534 | r#" 535 | (module 536 | (func $main 537 | block (result i32) 538 | block (result i32) 539 | i32.const 99 540 | br 1 541 | end 542 | end 543 | drop 544 | ) 545 | ) 546 | "#, 547 | ); 548 | 549 | let height = compute(0, &module).unwrap(); 550 | assert_eq!(height, 1 + ACTIVATION_FRAME_COST); 551 | } 552 | 553 | #[test] 554 | fn if_else_works() { 555 | let module = parse_wat( 556 | r#" 557 | (module 558 | (func $main 559 | i32.const 7 560 | i32.const 1 561 | if (result i32) 562 | i32.const 42 563 | else 564 | i32.const 99 565 | end 566 | i32.const 97 567 | drop 568 | drop 569 | drop 570 | ) 571 | ) 572 | "#, 573 | ); 574 | 575 | let height = compute(0, &module).unwrap(); 576 | assert_eq!(height, 3 + ACTIVATION_FRAME_COST); 577 | } 578 | } 579 | -------------------------------------------------------------------------------- /src/stack_limiter/mod.rs: -------------------------------------------------------------------------------- 1 | //! Contains the code for the stack height limiter instrumentation. 2 | 3 | use alloc::{vec, vec::Vec}; 4 | use core::mem; 5 | use parity_wasm::{ 6 | builder, 7 | elements::{self, Instruction, Instructions, Type}, 8 | }; 9 | 10 | /// Macro to generate preamble and postamble. 11 | macro_rules! instrument_call { 12 | ($callee_idx: expr, $callee_stack_cost: expr, $stack_height_global_idx: expr, $stack_limit: expr) => {{ 13 | use $crate::parity_wasm::elements::Instruction::*; 14 | [ 15 | // stack_height += stack_cost(F) 16 | GetGlobal($stack_height_global_idx), 17 | I32Const($callee_stack_cost), 18 | I32Add, 19 | SetGlobal($stack_height_global_idx), 20 | // if stack_counter > LIMIT: unreachable 21 | GetGlobal($stack_height_global_idx), 22 | I32Const($stack_limit as i32), 23 | I32GtU, 24 | If(elements::BlockType::NoResult), 25 | Unreachable, 26 | End, 27 | // Original call 28 | Call($callee_idx), 29 | // stack_height -= stack_cost(F) 30 | GetGlobal($stack_height_global_idx), 31 | I32Const($callee_stack_cost), 32 | I32Sub, 33 | SetGlobal($stack_height_global_idx), 34 | ] 35 | }}; 36 | } 37 | 38 | mod max_height; 39 | mod thunk; 40 | 41 | pub struct Context { 42 | stack_height_global_idx: u32, 43 | func_stack_costs: Vec, 44 | stack_limit: u32, 45 | } 46 | 47 | impl Context { 48 | /// Returns index in a global index space of a stack_height global variable. 49 | fn stack_height_global_idx(&self) -> u32 { 50 | self.stack_height_global_idx 51 | } 52 | 53 | /// Returns `stack_cost` for `func_idx`. 54 | fn stack_cost(&self, func_idx: u32) -> Option { 55 | self.func_stack_costs.get(func_idx as usize).cloned() 56 | } 57 | 58 | /// Returns stack limit specified by the rules. 59 | fn stack_limit(&self) -> u32 { 60 | self.stack_limit 61 | } 62 | } 63 | 64 | /// Inject the instumentation that makes stack overflows deterministic, by introducing 65 | /// an upper bound of the stack size. 66 | /// 67 | /// This pass introduces a global mutable variable to track stack height, 68 | /// and instruments all calls with preamble and postamble. 69 | /// 70 | /// Stack height is increased prior the call. Otherwise, the check would 71 | /// be made after the stack frame is allocated. 72 | /// 73 | /// The preamble is inserted before the call. It increments 74 | /// the global stack height variable with statically determined "stack cost" 75 | /// of the callee. If after the increment the stack height exceeds 76 | /// the limit (specified by the `rules`) then execution traps. 77 | /// Otherwise, the call is executed. 78 | /// 79 | /// The postamble is inserted after the call. The purpose of the postamble is to decrease 80 | /// the stack height by the "stack cost" of the callee function. 81 | /// 82 | /// Note, that we can't instrument all possible ways to return from the function. The simplest 83 | /// example would be a trap issued by the host function. 84 | /// That means stack height global won't be equal to zero upon the next execution after such trap. 85 | /// 86 | /// # Thunks 87 | /// 88 | /// Because stack height is increased prior the call few problems arises: 89 | /// 90 | /// - Stack height isn't increased upon an entry to the first function, i.e. exported function. 91 | /// - Start function is executed externally (similar to exported functions). 92 | /// - It is statically unknown what function will be invoked in an indirect call. 93 | /// 94 | /// The solution for this problems is to generate a intermediate functions, called 'thunks', which 95 | /// will increase before and decrease the stack height after the call to original function, and 96 | /// then make exported function and table entries, start section to point to a corresponding thunks. 97 | /// 98 | /// # Stack cost 99 | /// 100 | /// Stack cost of the function is calculated as a sum of it's locals 101 | /// and the maximal height of the value stack. 102 | /// 103 | /// All values are treated equally, as they have the same size. 104 | /// 105 | /// The rationale is that this makes it possible to use the following very naive wasm executor: 106 | /// 107 | /// - values are implemented by a union, so each value takes a size equal to the size of the largest 108 | /// possible value type this union can hold. (In MVP it is 8 bytes) 109 | /// - each value from the value stack is placed on the native stack. 110 | /// - each local variable and function argument is placed on the native stack. 111 | /// - arguments pushed by the caller are copied into callee stack rather than shared between the 112 | /// frames. 113 | /// - upon entry into the function entire stack frame is allocated. 114 | pub fn inject( 115 | mut module: elements::Module, 116 | stack_limit: u32, 117 | ) -> Result { 118 | let mut ctx = Context { 119 | stack_height_global_idx: generate_stack_height_global(&mut module), 120 | func_stack_costs: compute_stack_costs(&module)?, 121 | stack_limit, 122 | }; 123 | 124 | instrument_functions(&mut ctx, &mut module)?; 125 | let module = thunk::generate_thunks(&mut ctx, module)?; 126 | 127 | Ok(module) 128 | } 129 | 130 | /// Generate a new global that will be used for tracking current stack height. 131 | fn generate_stack_height_global(module: &mut elements::Module) -> u32 { 132 | let global_entry = builder::global() 133 | .value_type() 134 | .i32() 135 | .mutable() 136 | .init_expr(Instruction::I32Const(0)) 137 | .build(); 138 | 139 | // Try to find an existing global section. 140 | for section in module.sections_mut() { 141 | if let elements::Section::Global(gs) = section { 142 | gs.entries_mut().push(global_entry); 143 | return (gs.entries().len() as u32) - 1 144 | } 145 | } 146 | 147 | // Existing section not found, create one! 148 | module 149 | .sections_mut() 150 | .push(elements::Section::Global(elements::GlobalSection::with_entries(vec![global_entry]))); 151 | 0 152 | } 153 | 154 | /// Calculate stack costs for all functions. 155 | /// 156 | /// Returns a vector with a stack cost for each function, including imports. 157 | fn compute_stack_costs(module: &elements::Module) -> Result, &'static str> { 158 | let func_imports = module.import_count(elements::ImportCountType::Function); 159 | 160 | // TODO: optimize! 161 | (0..module.functions_space()) 162 | .map(|func_idx| { 163 | if func_idx < func_imports { 164 | // We can't calculate stack_cost of the import functions. 165 | Ok(0) 166 | } else { 167 | compute_stack_cost(func_idx as u32, module) 168 | } 169 | }) 170 | .collect() 171 | } 172 | 173 | /// Stack cost of the given *defined* function is the sum of it's locals count (that is, 174 | /// number of arguments plus number of local variables) and the maximal stack 175 | /// height. 176 | fn compute_stack_cost(func_idx: u32, module: &elements::Module) -> Result { 177 | // To calculate the cost of a function we need to convert index from 178 | // function index space to defined function spaces. 179 | let func_imports = module.import_count(elements::ImportCountType::Function) as u32; 180 | let defined_func_idx = func_idx 181 | .checked_sub(func_imports) 182 | .ok_or("This should be a index of a defined function")?; 183 | 184 | let code_section = 185 | module.code_section().ok_or("Due to validation code section should exists")?; 186 | let body = &code_section 187 | .bodies() 188 | .get(defined_func_idx as usize) 189 | .ok_or("Function body is out of bounds")?; 190 | 191 | let mut locals_count: u32 = 0; 192 | for local_group in body.locals() { 193 | locals_count = 194 | locals_count.checked_add(local_group.count()).ok_or("Overflow in local count")?; 195 | } 196 | 197 | let max_stack_height = max_height::compute(defined_func_idx, module)?; 198 | 199 | locals_count 200 | .checked_add(max_stack_height) 201 | .ok_or("Overflow in adding locals_count and max_stack_height") 202 | } 203 | 204 | fn instrument_functions( 205 | ctx: &mut Context, 206 | module: &mut elements::Module, 207 | ) -> Result<(), &'static str> { 208 | for section in module.sections_mut() { 209 | if let elements::Section::Code(code_section) = section { 210 | for func_body in code_section.bodies_mut() { 211 | let opcodes = func_body.code_mut(); 212 | instrument_function(ctx, opcodes)?; 213 | } 214 | } 215 | } 216 | Ok(()) 217 | } 218 | 219 | /// This function searches `call` instructions and wrap each call 220 | /// with preamble and postamble. 221 | /// 222 | /// Before: 223 | /// 224 | /// ```text 225 | /// local.get 0 226 | /// local.get 1 227 | /// call 228 228 | /// drop 229 | /// ``` 230 | /// 231 | /// After: 232 | /// 233 | /// ```text 234 | /// local.get 0 235 | /// local.get 1 236 | /// 237 | /// < ... preamble ... > 238 | /// 239 | /// call 228 240 | /// 241 | /// < .. postamble ... > 242 | /// 243 | /// drop 244 | /// ``` 245 | fn instrument_function(ctx: &mut Context, func: &mut Instructions) -> Result<(), &'static str> { 246 | use Instruction::*; 247 | 248 | struct InstrumentCall { 249 | offset: usize, 250 | callee: u32, 251 | cost: u32, 252 | } 253 | 254 | let calls: Vec<_> = func 255 | .elements() 256 | .iter() 257 | .enumerate() 258 | .filter_map(|(offset, instruction)| { 259 | if let Call(callee) = instruction { 260 | ctx.stack_cost(*callee).and_then(|cost| { 261 | if cost > 0 { 262 | Some(InstrumentCall { callee: *callee, offset, cost }) 263 | } else { 264 | None 265 | } 266 | }) 267 | } else { 268 | None 269 | } 270 | }) 271 | .collect(); 272 | 273 | // The `instrumented_call!` contains the call itself. This is why we need to subtract one. 274 | let len = func.elements().len() + calls.len() * (instrument_call!(0, 0, 0, 0).len() - 1); 275 | let original_instrs = mem::replace(func.elements_mut(), Vec::with_capacity(len)); 276 | let new_instrs = func.elements_mut(); 277 | 278 | let mut calls = calls.into_iter().peekable(); 279 | for (original_pos, instr) in original_instrs.into_iter().enumerate() { 280 | // whether there is some call instruction at this position that needs to be instrumented 281 | let did_instrument = if let Some(call) = calls.peek() { 282 | if call.offset == original_pos { 283 | let new_seq = instrument_call!( 284 | call.callee, 285 | call.cost as i32, 286 | ctx.stack_height_global_idx(), 287 | ctx.stack_limit() 288 | ); 289 | new_instrs.extend_from_slice(&new_seq); 290 | true 291 | } else { 292 | false 293 | } 294 | } else { 295 | false 296 | }; 297 | 298 | if did_instrument { 299 | calls.next(); 300 | } else { 301 | new_instrs.push(instr); 302 | } 303 | } 304 | 305 | if calls.next().is_some() { 306 | return Err("Not all calls were used") 307 | } 308 | 309 | Ok(()) 310 | } 311 | 312 | fn resolve_func_type( 313 | func_idx: u32, 314 | module: &elements::Module, 315 | ) -> Result<&elements::FunctionType, &'static str> { 316 | let types = module.type_section().map(|ts| ts.types()).unwrap_or(&[]); 317 | let functions = module.function_section().map(|fs| fs.entries()).unwrap_or(&[]); 318 | 319 | let func_imports = module.import_count(elements::ImportCountType::Function); 320 | let sig_idx = if func_idx < func_imports as u32 { 321 | module 322 | .import_section() 323 | .expect("function import count is not zero; import section must exists; qed") 324 | .entries() 325 | .iter() 326 | .filter_map(|entry| match entry.external() { 327 | elements::External::Function(idx) => Some(*idx), 328 | _ => None, 329 | }) 330 | .nth(func_idx as usize) 331 | .expect( 332 | "func_idx is less than function imports count; 333 | nth function import must be `Some`; 334 | qed", 335 | ) 336 | } else { 337 | functions 338 | .get(func_idx as usize - func_imports) 339 | .ok_or("Function at the specified index is not defined")? 340 | .type_ref() 341 | }; 342 | let Type::Function(ty) = types 343 | .get(sig_idx as usize) 344 | .ok_or("The signature as specified by a function isn't defined")?; 345 | Ok(ty) 346 | } 347 | 348 | #[cfg(test)] 349 | mod tests { 350 | use super::*; 351 | use parity_wasm::elements; 352 | 353 | fn parse_wat(source: &str) -> elements::Module { 354 | elements::deserialize_buffer(&wat::parse_str(source).expect("Failed to wat2wasm")) 355 | .expect("Failed to deserialize the module") 356 | } 357 | 358 | fn validate_module(module: elements::Module) { 359 | let binary = elements::serialize(module).expect("Failed to serialize"); 360 | wasmparser::validate(&binary).expect("Invalid module"); 361 | } 362 | 363 | #[test] 364 | fn test_with_params_and_result() { 365 | let module = parse_wat( 366 | r#" 367 | (module 368 | (func (export "i32.add") (param i32 i32) (result i32) 369 | local.get 0 370 | local.get 1 371 | i32.add 372 | ) 373 | ) 374 | "#, 375 | ); 376 | 377 | let module = inject(module, 1024).expect("Failed to inject stack counter"); 378 | validate_module(module); 379 | } 380 | } 381 | -------------------------------------------------------------------------------- /src/stack_limiter/thunk.rs: -------------------------------------------------------------------------------- 1 | use alloc::{collections::BTreeMap as Map, vec::Vec}; 2 | use parity_wasm::{ 3 | builder, 4 | elements::{self, FunctionType, Internal}, 5 | }; 6 | 7 | use super::{resolve_func_type, Context}; 8 | 9 | struct Thunk { 10 | signature: FunctionType, 11 | // Index in function space of this thunk. 12 | idx: Option, 13 | callee_stack_cost: u32, 14 | } 15 | 16 | pub fn generate_thunks( 17 | ctx: &mut Context, 18 | module: elements::Module, 19 | ) -> Result { 20 | // First, we need to collect all function indices that should be replaced by thunks 21 | let mut replacement_map: Map = { 22 | let exports = module.export_section().map(|es| es.entries()).unwrap_or(&[]); 23 | let elem_segments = module.elements_section().map(|es| es.entries()).unwrap_or(&[]); 24 | let start_func_idx = module.start_section(); 25 | 26 | let exported_func_indices = exports.iter().filter_map(|entry| match entry.internal() { 27 | Internal::Function(function_idx) => Some(*function_idx), 28 | _ => None, 29 | }); 30 | let table_func_indices = 31 | elem_segments.iter().flat_map(|segment| segment.members()).cloned(); 32 | 33 | // Replacement map is at least export section size. 34 | let mut replacement_map: Map = Map::new(); 35 | 36 | for func_idx in exported_func_indices 37 | .chain(table_func_indices) 38 | .chain(start_func_idx.into_iter()) 39 | { 40 | let callee_stack_cost = ctx.stack_cost(func_idx).ok_or("function index isn't found")?; 41 | 42 | // Don't generate a thunk if stack_cost of a callee is zero. 43 | if callee_stack_cost != 0 { 44 | replacement_map.insert( 45 | func_idx, 46 | Thunk { 47 | signature: resolve_func_type(func_idx, &module)?.clone(), 48 | idx: None, 49 | callee_stack_cost, 50 | }, 51 | ); 52 | } 53 | } 54 | 55 | replacement_map 56 | }; 57 | 58 | // Then, we generate a thunk for each original function. 59 | 60 | // Save current func_idx 61 | let mut next_func_idx = module.functions_space() as u32; 62 | 63 | let mut mbuilder = builder::from_module(module); 64 | for (func_idx, thunk) in replacement_map.iter_mut() { 65 | let instrumented_call = instrument_call!( 66 | *func_idx, 67 | thunk.callee_stack_cost as i32, 68 | ctx.stack_height_global_idx(), 69 | ctx.stack_limit() 70 | ); 71 | // Thunk body consist of: 72 | // - argument pushing 73 | // - instrumented call 74 | // - end 75 | let mut thunk_body: Vec = 76 | Vec::with_capacity(thunk.signature.params().len() + instrumented_call.len() + 1); 77 | 78 | for (arg_idx, _) in thunk.signature.params().iter().enumerate() { 79 | thunk_body.push(elements::Instruction::GetLocal(arg_idx as u32)); 80 | } 81 | thunk_body.extend_from_slice(&instrumented_call); 82 | thunk_body.push(elements::Instruction::End); 83 | 84 | // TODO: Don't generate a signature, but find an existing one. 85 | 86 | mbuilder = mbuilder 87 | .function() 88 | // Signature of the thunk should match the original function signature. 89 | .signature() 90 | .with_params(thunk.signature.params().to_vec()) 91 | .with_results(thunk.signature.results().to_vec()) 92 | .build() 93 | .body() 94 | .with_instructions(elements::Instructions::new(thunk_body)) 95 | .build() 96 | .build(); 97 | 98 | thunk.idx = Some(next_func_idx); 99 | next_func_idx += 1; 100 | } 101 | let mut module = mbuilder.build(); 102 | 103 | // And finally, fixup thunks in export and table sections. 104 | 105 | // Fixup original function index to a index of a thunk generated earlier. 106 | let fixup = |function_idx: &mut u32| { 107 | // Check whether this function is in replacement_map, since 108 | // we can skip thunk generation (e.g. if stack_cost of function is 0). 109 | if let Some(thunk) = replacement_map.get(function_idx) { 110 | *function_idx = 111 | thunk.idx.expect("At this point an index must be assigned to each thunk"); 112 | } 113 | }; 114 | 115 | for section in module.sections_mut() { 116 | match section { 117 | elements::Section::Export(export_section) => 118 | for entry in export_section.entries_mut() { 119 | if let Internal::Function(function_idx) = entry.internal_mut() { 120 | fixup(function_idx) 121 | } 122 | }, 123 | elements::Section::Element(elem_section) => 124 | for segment in elem_section.entries_mut() { 125 | for function_idx in segment.members_mut() { 126 | fixup(function_idx) 127 | } 128 | }, 129 | elements::Section::Start(start_idx) => fixup(start_idx), 130 | _ => {}, 131 | } 132 | } 133 | 134 | Ok(module) 135 | } 136 | -------------------------------------------------------------------------------- /tests/diff.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fs, 3 | io::{self, Read, Write}, 4 | path::{Path, PathBuf}, 5 | }; 6 | use wasm_instrument::{self as instrument, gas_metering, parity_wasm::elements}; 7 | use wasmparser::validate; 8 | 9 | fn slurp>(path: P) -> io::Result> { 10 | let mut f = fs::File::open(path)?; 11 | let mut buf = vec![]; 12 | f.read_to_end(&mut buf)?; 13 | Ok(buf) 14 | } 15 | 16 | fn dump>(path: P, buf: &[u8]) -> io::Result<()> { 17 | let mut f = fs::File::create(path)?; 18 | f.write_all(buf)?; 19 | Ok(()) 20 | } 21 | 22 | fn run_diff_test Vec>( 23 | test_dir: &str, 24 | in_name: &str, 25 | out_name: &str, 26 | test: F, 27 | ) { 28 | let mut fixture_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); 29 | fixture_path.push("tests"); 30 | fixture_path.push("fixtures"); 31 | fixture_path.push(test_dir); 32 | fixture_path.push(in_name); 33 | 34 | let mut expected_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); 35 | expected_path.push("tests"); 36 | expected_path.push("expectations"); 37 | expected_path.push(test_dir); 38 | expected_path.push(out_name); 39 | 40 | let fixture_wasm = wat::parse_file(&fixture_path).expect("Failed to read fixture"); 41 | validate(&fixture_wasm).expect("Fixture is invalid"); 42 | 43 | let expected_wat = slurp(&expected_path).unwrap_or_default(); 44 | let expected_wat = std::str::from_utf8(&expected_wat).expect("Failed to decode expected wat"); 45 | 46 | let actual_wasm = test(fixture_wasm.as_ref()); 47 | validate(&actual_wasm).expect("Result module is invalid"); 48 | 49 | let actual_wat = 50 | wasmprinter::print_bytes(&actual_wasm).expect("Failed to convert result wasm to wat"); 51 | 52 | if actual_wat != expected_wat { 53 | println!("difference!"); 54 | println!("--- {}", expected_path.display()); 55 | println!("+++ {} test {}", test_dir, out_name); 56 | for diff in diff::lines(expected_wat, &actual_wat) { 57 | match diff { 58 | diff::Result::Left(l) => println!("-{}", l), 59 | diff::Result::Both(l, _) => println!(" {}", l), 60 | diff::Result::Right(r) => println!("+{}", r), 61 | } 62 | } 63 | 64 | if std::env::var_os("BLESS").is_some() { 65 | dump(&expected_path, actual_wat.as_bytes()).expect("Failed to write to expected"); 66 | } else { 67 | panic!(); 68 | } 69 | } 70 | } 71 | 72 | mod stack_height { 73 | use super::*; 74 | 75 | macro_rules! def_stack_height_test { 76 | ( $name:ident ) => { 77 | #[test] 78 | fn $name() { 79 | run_diff_test( 80 | "stack-height", 81 | concat!(stringify!($name), ".wat"), 82 | concat!(stringify!($name), ".wat"), 83 | |input| { 84 | let module = 85 | elements::deserialize_buffer(input).expect("Failed to deserialize"); 86 | let instrumented = instrument::inject_stack_limiter(module, 1024) 87 | .expect("Failed to instrument with stack counter"); 88 | elements::serialize(instrumented).expect("Failed to serialize") 89 | }, 90 | ); 91 | } 92 | }; 93 | } 94 | 95 | def_stack_height_test!(simple); 96 | def_stack_height_test!(start); 97 | def_stack_height_test!(table); 98 | def_stack_height_test!(global); 99 | def_stack_height_test!(imports); 100 | def_stack_height_test!(many_locals); 101 | def_stack_height_test!(empty_functions); 102 | } 103 | 104 | mod gas { 105 | use super::*; 106 | 107 | macro_rules! def_gas_test { 108 | ( ($input:ident, $name1:ident, $name2:ident) ) => { 109 | #[test] 110 | fn $name1() { 111 | run_diff_test( 112 | "gas", 113 | concat!(stringify!($input), ".wat"), 114 | concat!(stringify!($name1), ".wat"), 115 | |input| { 116 | let rules = gas_metering::ConstantCostRules::default(); 117 | 118 | let module: elements::Module = 119 | elements::deserialize_buffer(input).expect("Failed to deserialize"); 120 | let module = module.parse_names().expect("Failed to parse names"); 121 | let backend = gas_metering::host_function::Injector::new("env", "gas"); 122 | 123 | let instrumented = gas_metering::inject(module, backend, &rules) 124 | .expect("Failed to instrument with gas metering"); 125 | elements::serialize(instrumented).expect("Failed to serialize") 126 | }, 127 | ); 128 | } 129 | 130 | #[test] 131 | fn $name2() { 132 | run_diff_test( 133 | "gas", 134 | concat!(stringify!($input), ".wat"), 135 | concat!(stringify!($name2), ".wat"), 136 | |input| { 137 | let rules = gas_metering::ConstantCostRules::default(); 138 | 139 | let module: elements::Module = 140 | elements::deserialize_buffer(input).expect("Failed to deserialize"); 141 | let module = module.parse_names().expect("Failed to parse names"); 142 | let backend = gas_metering::mutable_global::Injector::new("gas_left"); 143 | let instrumented = gas_metering::inject(module, backend, &rules) 144 | .expect("Failed to instrument with gas metering"); 145 | elements::serialize(instrumented).expect("Failed to serialize") 146 | }, 147 | ); 148 | } 149 | }; 150 | } 151 | 152 | def_gas_test!((ifs, ifs_host_fn, ifs_mut_global)); 153 | def_gas_test!((simple, simple_host_fn, simple_mut_global)); 154 | def_gas_test!((start, start_host_fn, start_mut_global)); 155 | def_gas_test!((call, call_host_fn, call_mut_global)); 156 | def_gas_test!((branch, branch_host_fn, branch_mut_global)); 157 | } 158 | -------------------------------------------------------------------------------- /tests/expectations/gas/branch_host_fn.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func (result i32))) 3 | (type (;1;) (func (param i64))) 4 | (import "env" "gas" (func (;0;) (type 1))) 5 | (func $fibonacci_with_break (;1;) (type 0) (result i32) 6 | (local i32 i32) 7 | i64.const 15 8 | call 0 9 | block ;; label = @1 10 | i32.const 0 11 | local.set 0 12 | i32.const 1 13 | local.set 1 14 | local.get 0 15 | local.get 1 16 | local.tee 0 17 | i32.add 18 | local.set 1 19 | i32.const 1 20 | br_if 0 (;@1;) 21 | i64.const 5 22 | call 0 23 | local.get 0 24 | local.get 1 25 | local.tee 0 26 | i32.add 27 | local.set 1 28 | end 29 | local.get 1 30 | ) 31 | ) -------------------------------------------------------------------------------- /tests/expectations/gas/branch_mut_global.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func (result i32))) 3 | (type (;1;) (func (param i64))) 4 | (func $fibonacci_with_break (;0;) (type 0) (result i32) 5 | (local $x i32) (local $y i32) 6 | i64.const 26 7 | call 1 8 | block ;; label = @1 9 | i32.const 0 10 | local.set $x 11 | i32.const 1 12 | local.set $y 13 | local.get $x 14 | local.get $y 15 | local.tee $x 16 | i32.add 17 | local.set $y 18 | i32.const 1 19 | br_if 0 (;@1;) 20 | i64.const 16 21 | call 1 22 | local.get $x 23 | local.get $y 24 | local.tee $x 25 | i32.add 26 | local.set $y 27 | end 28 | local.get $y 29 | ) 30 | (func (;1;) (type 1) (param i64) 31 | global.get 0 32 | local.get 0 33 | i64.ge_u 34 | if ;; label = @1 35 | global.get 0 36 | local.get 0 37 | i64.sub 38 | global.set 0 39 | else 40 | i64.const -1 41 | global.set 0 42 | unreachable 43 | end 44 | ) 45 | (global (;0;) (mut i64) i64.const 0) 46 | (export "gas_left" (global 0)) 47 | ) -------------------------------------------------------------------------------- /tests/expectations/gas/call_host_fn.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func (param i32 i32) (result i32))) 3 | (type (;1;) (func (param i64))) 4 | (import "env" "gas" (func (;0;) (type 1))) 5 | (func $add_locals (;1;) (type 0) (param $x i32) (param $y i32) (result i32) 6 | (local i32) 7 | i64.const 6 8 | call 0 9 | local.get $x 10 | local.get $y 11 | call $add 12 | local.set 2 13 | local.get 2 14 | ) 15 | (func $add (;2;) (type 0) (param i32 i32) (result i32) 16 | i64.const 3 17 | call 0 18 | local.get 0 19 | local.get 1 20 | i32.add 21 | ) 22 | ) -------------------------------------------------------------------------------- /tests/expectations/gas/call_mut_global.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func (param i32 i32) (result i32))) 3 | (type (;1;) (func (param i64))) 4 | (func $add_locals (;0;) (type 0) (param $x i32) (param $y i32) (result i32) 5 | (local $t i32) 6 | i64.const 17 7 | call 2 8 | local.get $x 9 | local.get $y 10 | call $add 11 | local.set $t 12 | local.get $t 13 | ) 14 | (func $add (;1;) (type 0) (param $x i32) (param $y i32) (result i32) 15 | i64.const 14 16 | call 2 17 | local.get $x 18 | local.get $y 19 | i32.add 20 | ) 21 | (func (;2;) (type 1) (param i64) 22 | global.get 0 23 | local.get 0 24 | i64.ge_u 25 | if ;; label = @1 26 | global.get 0 27 | local.get 0 28 | i64.sub 29 | global.set 0 30 | else 31 | i64.const -1 32 | global.set 0 33 | unreachable 34 | end 35 | ) 36 | (global (;0;) (mut i64) i64.const 0) 37 | (export "gas_left" (global 0)) 38 | ) -------------------------------------------------------------------------------- /tests/expectations/gas/ifs_host_fn.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func (param i32) (result i32))) 3 | (type (;1;) (func (param i64))) 4 | (import "env" "gas" (func (;0;) (type 1))) 5 | (func (;1;) (type 0) (param i32) (result i32) 6 | i64.const 2 7 | call 0 8 | i32.const 1 9 | if (result i32) ;; label = @1 10 | i64.const 3 11 | call 0 12 | local.get 0 13 | i32.const 1 14 | i32.add 15 | else 16 | i64.const 2 17 | call 0 18 | local.get 0 19 | i32.popcnt 20 | end 21 | ) 22 | ) -------------------------------------------------------------------------------- /tests/expectations/gas/ifs_mut_global.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func (param i32) (result i32))) 3 | (type (;1;) (func (param i64))) 4 | (func (;0;) (type 0) (param $x i32) (result i32) 5 | i64.const 13 6 | call 1 7 | i32.const 1 8 | if (result i32) ;; label = @1 9 | i64.const 14 10 | call 1 11 | local.get $x 12 | i32.const 1 13 | i32.add 14 | else 15 | i64.const 13 16 | call 1 17 | local.get $x 18 | i32.popcnt 19 | end 20 | ) 21 | (func (;1;) (type 1) (param i64) 22 | global.get 0 23 | local.get 0 24 | i64.ge_u 25 | if ;; label = @1 26 | global.get 0 27 | local.get 0 28 | i64.sub 29 | global.set 0 30 | else 31 | i64.const -1 32 | global.set 0 33 | unreachable 34 | end 35 | ) 36 | (global (;0;) (mut i64) i64.const 0) 37 | (export "gas_left" (global 0)) 38 | ) -------------------------------------------------------------------------------- /tests/expectations/gas/simple_host_fn.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func)) 3 | (type (;1;) (func (param i64))) 4 | (import "env" "gas" (func (;0;) (type 1))) 5 | (func (;1;) (type 0) 6 | i64.const 2 7 | call 0 8 | i32.const 1 9 | if ;; label = @1 10 | i64.const 1 11 | call 0 12 | loop ;; label = @2 13 | i64.const 2 14 | call 0 15 | i32.const 123 16 | drop 17 | end 18 | end 19 | ) 20 | (func (;2;) (type 0) 21 | i64.const 1 22 | call 0 23 | block ;; label = @1 24 | end 25 | ) 26 | (export "simple" (func 1)) 27 | ) -------------------------------------------------------------------------------- /tests/expectations/gas/simple_mut_global.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func)) 3 | (type (;1;) (func (param i64))) 4 | (func (;0;) (type 0) 5 | i64.const 13 6 | call 2 7 | i32.const 1 8 | if ;; label = @1 9 | i64.const 12 10 | call 2 11 | loop ;; label = @2 12 | i64.const 13 13 | call 2 14 | i32.const 123 15 | drop 16 | end 17 | end 18 | ) 19 | (func (;1;) (type 0) 20 | i64.const 12 21 | call 2 22 | block ;; label = @1 23 | end 24 | ) 25 | (func (;2;) (type 1) (param i64) 26 | global.get 0 27 | local.get 0 28 | i64.ge_u 29 | if ;; label = @1 30 | global.get 0 31 | local.get 0 32 | i64.sub 33 | global.set 0 34 | else 35 | i64.const -1 36 | global.set 0 37 | unreachable 38 | end 39 | ) 40 | (global (;0;) (mut i64) i64.const 0) 41 | (export "simple" (func 0)) 42 | (export "gas_left" (global 0)) 43 | ) -------------------------------------------------------------------------------- /tests/expectations/gas/start_host_fn.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func (param i32 i32))) 3 | (type (;1;) (func)) 4 | (type (;2;) (func (param i64))) 5 | (import "env" "ext_return" (func $ext_return (;0;) (type 0))) 6 | (import "env" "memory" (memory (;0;) 1 1)) 7 | (import "env" "gas" (func (;1;) (type 2))) 8 | (func $start (;2;) (type 1) 9 | i64.const 4 10 | call 1 11 | i32.const 8 12 | i32.const 4 13 | call $ext_return 14 | unreachable 15 | ) 16 | (func (;3;) (type 1)) 17 | (export "call" (func 3)) 18 | (start $start) 19 | (data (;0;) (i32.const 8) "\01\02\03\04") 20 | ) -------------------------------------------------------------------------------- /tests/expectations/gas/start_mut_global.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func (param i32 i32))) 3 | (type (;1;) (func)) 4 | (type (;2;) (func (param i64))) 5 | (import "env" "ext_return" (func $ext_return (;0;) (type 0))) 6 | (import "env" "memory" (memory (;0;) 1 1)) 7 | (func $start (;1;) (type 1) 8 | i64.const 15 9 | call 3 10 | i32.const 8 11 | i32.const 4 12 | call $ext_return 13 | unreachable 14 | ) 15 | (func (;2;) (type 1)) 16 | (func (;3;) (type 2) (param i64) 17 | global.get 0 18 | local.get 0 19 | i64.ge_u 20 | if ;; label = @1 21 | global.get 0 22 | local.get 0 23 | i64.sub 24 | global.set 0 25 | else 26 | i64.const -1 27 | global.set 0 28 | unreachable 29 | end 30 | ) 31 | (global (;0;) (mut i64) i64.const 0) 32 | (export "call" (func 2)) 33 | (export "gas_left" (global 0)) 34 | (start $start) 35 | (data (;0;) (i32.const 8) "\01\02\03\04") 36 | ) -------------------------------------------------------------------------------- /tests/expectations/stack-height/empty_functions.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func)) 3 | (func (;0;) (type 0) 4 | global.get 0 5 | i32.const 2 6 | i32.add 7 | global.set 0 8 | global.get 0 9 | i32.const 1024 10 | i32.gt_u 11 | if ;; label = @1 12 | unreachable 13 | end 14 | call 0 15 | global.get 0 16 | i32.const 2 17 | i32.sub 18 | global.set 0 19 | ) 20 | (func (;1;) (type 0) 21 | global.get 0 22 | i32.const 2 23 | i32.add 24 | global.set 0 25 | global.get 0 26 | i32.const 1024 27 | i32.gt_u 28 | if ;; label = @1 29 | unreachable 30 | end 31 | call 0 32 | global.get 0 33 | i32.const 2 34 | i32.sub 35 | global.set 0 36 | ) 37 | (func (;2;) (type 0) 38 | global.get 0 39 | i32.const 2 40 | i32.add 41 | global.set 0 42 | global.get 0 43 | i32.const 1024 44 | i32.gt_u 45 | if ;; label = @1 46 | unreachable 47 | end 48 | call 1 49 | global.get 0 50 | i32.const 2 51 | i32.sub 52 | global.set 0 53 | ) 54 | (global (;0;) (mut i32) i32.const 0) 55 | (export "main" (func 2)) 56 | ) -------------------------------------------------------------------------------- /tests/expectations/stack-height/global.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func)) 3 | (type (;1;) (func (param i32 i32) (result i32))) 4 | (type (;2;) (func (param i32))) 5 | (import "env" "foo" (func $foo (;0;) (type 0))) 6 | (func $i32.add (;1;) (type 1) (param i32 i32) (result i32) 7 | local.get 0 8 | local.get 1 9 | i32.add 10 | ) 11 | (func (;2;) (type 2) (param $arg i32) 12 | (local $tmp i32) 13 | global.get $counter 14 | i32.const 1 15 | i32.add 16 | local.tee $tmp 17 | global.set $counter 18 | local.get $tmp 19 | local.get $arg 20 | global.get 1 21 | i32.const 4 22 | i32.add 23 | global.set 1 24 | global.get 1 25 | i32.const 1024 26 | i32.gt_u 27 | if ;; label = @1 28 | unreachable 29 | end 30 | call $i32.add 31 | global.get 1 32 | i32.const 4 33 | i32.sub 34 | global.set 1 35 | drop 36 | ) 37 | (func (;3;) (type 1) (param i32 i32) (result i32) 38 | local.get 0 39 | local.get 1 40 | global.get 1 41 | i32.const 4 42 | i32.add 43 | global.set 1 44 | global.get 1 45 | i32.const 1024 46 | i32.gt_u 47 | if ;; label = @1 48 | unreachable 49 | end 50 | call $i32.add 51 | global.get 1 52 | i32.const 4 53 | i32.sub 54 | global.set 1 55 | ) 56 | (global $counter (;0;) (mut i32) i32.const 1) 57 | (global (;1;) (mut i32) i32.const 0) 58 | (export "i32.add" (func 3)) 59 | ) -------------------------------------------------------------------------------- /tests/expectations/stack-height/imports.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func)) 3 | (type (;1;) (func (param i32 i32) (result i32))) 4 | (import "env" "foo" (func $foo (;0;) (type 0))) 5 | (import "env" "boo" (func $boo (;1;) (type 0))) 6 | (func (;2;) (type 1) (param i32 i32) (result i32) 7 | call $foo 8 | call $boo 9 | local.get 0 10 | local.get 1 11 | i32.add 12 | ) 13 | (func (;3;) (type 1) (param i32 i32) (result i32) 14 | local.get 0 15 | local.get 1 16 | global.get 0 17 | i32.const 4 18 | i32.add 19 | global.set 0 20 | global.get 0 21 | i32.const 1024 22 | i32.gt_u 23 | if ;; label = @1 24 | unreachable 25 | end 26 | call 2 27 | global.get 0 28 | i32.const 4 29 | i32.sub 30 | global.set 0 31 | ) 32 | (global (;0;) (mut i32) i32.const 0) 33 | (export "i32.add" (func 3)) 34 | ) -------------------------------------------------------------------------------- /tests/expectations/stack-height/many_locals.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func)) 3 | (func $one-group-many-locals (;0;) (type 0) 4 | (local i64 i64 i32) 5 | ) 6 | (func $main (;1;) (type 0) 7 | global.get 0 8 | i32.const 5 9 | i32.add 10 | global.set 0 11 | global.get 0 12 | i32.const 1024 13 | i32.gt_u 14 | if ;; label = @1 15 | unreachable 16 | end 17 | call $one-group-many-locals 18 | global.get 0 19 | i32.const 5 20 | i32.sub 21 | global.set 0 22 | ) 23 | (global (;0;) (mut i32) i32.const 0) 24 | ) -------------------------------------------------------------------------------- /tests/expectations/stack-height/simple.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func)) 3 | (func (;0;) (type 0) 4 | i32.const 123 5 | drop 6 | ) 7 | (func (;1;) (type 0) 8 | global.get 0 9 | i32.const 3 10 | i32.add 11 | global.set 0 12 | global.get 0 13 | i32.const 1024 14 | i32.gt_u 15 | if ;; label = @1 16 | unreachable 17 | end 18 | call 0 19 | global.get 0 20 | i32.const 3 21 | i32.sub 22 | global.set 0 23 | ) 24 | (global (;0;) (mut i32) i32.const 0) 25 | (export "simple" (func 1)) 26 | ) -------------------------------------------------------------------------------- /tests/expectations/stack-height/start.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func (param i32 i32))) 3 | (type (;1;) (func)) 4 | (import "env" "ext_return" (func $ext_return (;0;) (type 0))) 5 | (import "env" "memory" (memory (;0;) 1 1)) 6 | (func $start (;1;) (type 1) 7 | (local i32) 8 | ) 9 | (func (;2;) (type 1)) 10 | (func (;3;) (type 1) 11 | global.get 0 12 | i32.const 3 13 | i32.add 14 | global.set 0 15 | global.get 0 16 | i32.const 1024 17 | i32.gt_u 18 | if ;; label = @1 19 | unreachable 20 | end 21 | call $start 22 | global.get 0 23 | i32.const 3 24 | i32.sub 25 | global.set 0 26 | ) 27 | (func (;4;) (type 1) 28 | global.get 0 29 | i32.const 2 30 | i32.add 31 | global.set 0 32 | global.get 0 33 | i32.const 1024 34 | i32.gt_u 35 | if ;; label = @1 36 | unreachable 37 | end 38 | call 2 39 | global.get 0 40 | i32.const 2 41 | i32.sub 42 | global.set 0 43 | ) 44 | (global (;0;) (mut i32) i32.const 0) 45 | (export "call" (func 4)) 46 | (start 3) 47 | ) -------------------------------------------------------------------------------- /tests/expectations/stack-height/table.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func)) 3 | (type (;1;) (func (param i32))) 4 | (type (;2;) (func (param i32 i32) (result i32))) 5 | (import "env" "foo" (func $foo (;0;) (type 0))) 6 | (func (;1;) (type 1) (param i32) 7 | local.get 0 8 | i32.const 0 9 | global.get 0 10 | i32.const 4 11 | i32.add 12 | global.set 0 13 | global.get 0 14 | i32.const 1024 15 | i32.gt_u 16 | if ;; label = @1 17 | unreachable 18 | end 19 | call $i32.add 20 | global.get 0 21 | i32.const 4 22 | i32.sub 23 | global.set 0 24 | drop 25 | ) 26 | (func $i32.add (;2;) (type 2) (param i32 i32) (result i32) 27 | local.get 0 28 | local.get 1 29 | i32.add 30 | ) 31 | (func (;3;) (type 1) (param i32) 32 | local.get 0 33 | global.get 0 34 | i32.const 4 35 | i32.add 36 | global.set 0 37 | global.get 0 38 | i32.const 1024 39 | i32.gt_u 40 | if ;; label = @1 41 | unreachable 42 | end 43 | call 1 44 | global.get 0 45 | i32.const 4 46 | i32.sub 47 | global.set 0 48 | ) 49 | (func (;4;) (type 2) (param i32 i32) (result i32) 50 | local.get 0 51 | local.get 1 52 | global.get 0 53 | i32.const 4 54 | i32.add 55 | global.set 0 56 | global.get 0 57 | i32.const 1024 58 | i32.gt_u 59 | if ;; label = @1 60 | unreachable 61 | end 62 | call $i32.add 63 | global.get 0 64 | i32.const 4 65 | i32.sub 66 | global.set 0 67 | ) 68 | (table (;0;) 10 funcref) 69 | (global (;0;) (mut i32) i32.const 0) 70 | (export "i32.add" (func 4)) 71 | (elem (;0;) (i32.const 0) func $foo 3 4) 72 | ) -------------------------------------------------------------------------------- /tests/fixtures/gas/branch.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (func $fibonacci_with_break (result i32) 3 | (local $x i32) (local $y i32) 4 | 5 | (block $unrolled_loop 6 | (local.set $x (i32.const 0)) 7 | (local.set $y (i32.const 1)) 8 | 9 | local.get $x 10 | local.get $y 11 | local.tee $x 12 | i32.add 13 | local.set $y 14 | 15 | i32.const 1 16 | br_if $unrolled_loop 17 | 18 | local.get $x 19 | local.get $y 20 | local.tee $x 21 | i32.add 22 | local.set $y 23 | ) 24 | 25 | local.get $y 26 | ) 27 | ) 28 | -------------------------------------------------------------------------------- /tests/fixtures/gas/call.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (func $add_locals (param $x i32) (param $y i32) (result i32) 3 | (local $t i32) 4 | 5 | local.get $x 6 | local.get $y 7 | call $add 8 | local.set $t 9 | 10 | local.get $t 11 | ) 12 | 13 | (func $add (param $x i32) (param $y i32) (result i32) 14 | (i32.add 15 | (local.get $x) 16 | (local.get $y) 17 | ) 18 | ) 19 | ) 20 | -------------------------------------------------------------------------------- /tests/fixtures/gas/ifs.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (func (param $x i32) (result i32) 3 | (if (result i32) 4 | (i32.const 1) 5 | (then (i32.add (local.get $x) (i32.const 1))) 6 | (else (i32.popcnt (local.get $x))) 7 | ) 8 | ) 9 | ) 10 | -------------------------------------------------------------------------------- /tests/fixtures/gas/simple.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (func (export "simple") 3 | (if (i32.const 1) 4 | (then 5 | (loop 6 | i32.const 123 7 | drop 8 | ) 9 | ) 10 | ) 11 | ) 12 | 13 | (func 14 | block 15 | end 16 | ) 17 | ) 18 | -------------------------------------------------------------------------------- /tests/fixtures/gas/start.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (import "env" "ext_return" (func $ext_return (param i32 i32))) 3 | (import "env" "memory" (memory 1 1)) 4 | 5 | (start $start) 6 | (func $start 7 | (call $ext_return 8 | (i32.const 8) 9 | (i32.const 4) 10 | ) 11 | (unreachable) 12 | ) 13 | 14 | (func (export "call") 15 | ) 16 | 17 | (data (i32.const 8) "\01\02\03\04") 18 | ) 19 | -------------------------------------------------------------------------------- /tests/fixtures/stack-height/empty_functions.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (func (;0;) 3 | call 0 4 | ) 5 | (func (;1;) (export "main") 6 | call 0 7 | ) 8 | ) -------------------------------------------------------------------------------- /tests/fixtures/stack-height/global.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (import "env" "foo" (func $foo)) 3 | 4 | ;; Declare a global. 5 | (global $counter (mut i32) (i32.const 1)) 6 | 7 | (func $i32.add (export "i32.add") (param i32 i32) (result i32) 8 | local.get 0 9 | local.get 1 10 | i32.add 11 | ) 12 | (func (param $arg i32) 13 | (local $tmp i32) 14 | 15 | global.get 0 16 | i32.const 1 17 | i32.add 18 | local.tee $tmp 19 | global.set $counter 20 | 21 | local.get $tmp 22 | local.get $arg 23 | call $i32.add 24 | drop 25 | ) 26 | ) 27 | -------------------------------------------------------------------------------- /tests/fixtures/stack-height/imports.wat: -------------------------------------------------------------------------------- 1 | ;; This test 2 | 3 | (module 4 | (import "env" "foo" (func $foo)) 5 | (import "env" "boo" (func $boo)) 6 | 7 | (func (export "i32.add") (param i32 i32) (result i32) 8 | call $foo 9 | call $boo 10 | 11 | local.get 0 12 | local.get 1 13 | i32.add 14 | ) 15 | ) 16 | -------------------------------------------------------------------------------- /tests/fixtures/stack-height/many_locals.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (func $one-group-many-locals 3 | (local i64) (local i64) (local i32) 4 | ) 5 | (func $main 6 | (call 7 | $one-group-many-locals 8 | ) 9 | ) 10 | ) 11 | -------------------------------------------------------------------------------- /tests/fixtures/stack-height/simple.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (func (export "simple") 3 | i32.const 123 4 | drop 5 | ) 6 | ) 7 | -------------------------------------------------------------------------------- /tests/fixtures/stack-height/start.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (import "env" "ext_return" (func $ext_return (param i32 i32))) 3 | (import "env" "memory" (memory 1 1)) 4 | 5 | (start $start) 6 | (func $start 7 | (local i32) 8 | ) 9 | (func (export "call") 10 | ) 11 | ) 12 | -------------------------------------------------------------------------------- /tests/fixtures/stack-height/table.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (import "env" "foo" (func $foo)) 3 | (func (param i32) 4 | local.get 0 5 | i32.const 0 6 | call $i32.add 7 | drop 8 | ) 9 | (func $i32.add (export "i32.add") (param i32 i32) (result i32) 10 | local.get 0 11 | local.get 1 12 | i32.add 13 | ) 14 | (table 10 funcref) 15 | 16 | ;; Refer all types of functions: imported, defined not exported and defined exported. 17 | (elem (i32.const 0) 0 1 2) 18 | ) 19 | -------------------------------------------------------------------------------- /tests/overhead.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fs::{read, read_dir, ReadDir}, 3 | path::PathBuf, 4 | }; 5 | use wasm_instrument::{ 6 | gas_metering::{self, host_function, mutable_global, ConstantCostRules}, 7 | inject_stack_limiter, 8 | parity_wasm::{deserialize_buffer, elements::Module, serialize}, 9 | }; 10 | 11 | fn fixture_dir() -> PathBuf { 12 | let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); 13 | path.push("benches"); 14 | path.push("fixtures"); 15 | path 16 | } 17 | 18 | use gas_metering::Backend; 19 | fn gas_metered_mod_len(orig_module: Module, backend: B) -> (Module, usize) { 20 | let module = gas_metering::inject(orig_module, backend, &ConstantCostRules::default()).unwrap(); 21 | let bytes = serialize(module.clone()).unwrap(); 22 | let len = bytes.len(); 23 | (module, len) 24 | } 25 | 26 | fn stack_limited_mod_len(module: Module) -> (Module, usize) { 27 | let module = inject_stack_limiter(module, 128).unwrap(); 28 | let bytes = serialize(module.clone()).unwrap(); 29 | let len = bytes.len(); 30 | (module, len) 31 | } 32 | 33 | struct InstrumentedWasmResults { 34 | filename: String, 35 | original_module_len: usize, 36 | stack_limited_len: usize, 37 | gas_metered_host_fn_len: usize, 38 | gas_metered_mut_glob_len: usize, 39 | gas_metered_host_fn_then_stack_limited_len: usize, 40 | gas_metered_mut_glob_then_stack_limited_len: usize, 41 | } 42 | 43 | fn size_overheads_all(files: ReadDir) -> Vec { 44 | files 45 | .map(|entry| { 46 | let entry = entry.unwrap(); 47 | let filename = entry.file_name().into_string().unwrap(); 48 | 49 | let (original_module_len, orig_module) = { 50 | let bytes = match entry.path().extension().unwrap().to_str() { 51 | Some("wasm") => read(entry.path()).unwrap(), 52 | Some("wat") => 53 | wat::parse_bytes(&read(entry.path()).unwrap()).unwrap().into_owned(), 54 | _ => panic!("expected fixture_dir containing .wasm or .wat files only"), 55 | }; 56 | 57 | let len = bytes.len(); 58 | let module: Module = deserialize_buffer(&bytes).unwrap(); 59 | (len, module) 60 | }; 61 | 62 | let (gm_host_fn_module, gas_metered_host_fn_len) = gas_metered_mod_len( 63 | orig_module.clone(), 64 | host_function::Injector::new("env", "gas"), 65 | ); 66 | 67 | let (gm_mut_global_module, gas_metered_mut_glob_len) = 68 | gas_metered_mod_len(orig_module.clone(), mutable_global::Injector::new("gas_left")); 69 | 70 | let stack_limited_len = stack_limited_mod_len(orig_module).1; 71 | 72 | let (_gm_hf_sl_mod, gas_metered_host_fn_then_stack_limited_len) = 73 | stack_limited_mod_len(gm_host_fn_module); 74 | 75 | let (_gm_mg_sl_module, gas_metered_mut_glob_then_stack_limited_len) = 76 | stack_limited_mod_len(gm_mut_global_module); 77 | 78 | InstrumentedWasmResults { 79 | filename, 80 | original_module_len, 81 | stack_limited_len, 82 | gas_metered_host_fn_len, 83 | gas_metered_mut_glob_len, 84 | gas_metered_host_fn_then_stack_limited_len, 85 | gas_metered_mut_glob_then_stack_limited_len, 86 | } 87 | }) 88 | .collect() 89 | } 90 | 91 | fn calc_size_overheads() -> Vec { 92 | let mut wasm_path = fixture_dir(); 93 | wasm_path.push("wasm"); 94 | 95 | let mut wat_path = fixture_dir(); 96 | wat_path.push("wat"); 97 | 98 | let mut results = size_overheads_all(read_dir(wasm_path).unwrap()); 99 | let results_wat = size_overheads_all(read_dir(wat_path).unwrap()); 100 | 101 | results.extend(results_wat); 102 | 103 | results 104 | } 105 | 106 | /// Print the overhead of applying gas metering, stack 107 | /// height limiting or both. 108 | /// 109 | /// Use `cargo test print_size_overhead -- --nocapture`. 110 | #[test] 111 | fn print_size_overhead() { 112 | let mut results = calc_size_overheads(); 113 | results.sort_unstable_by(|a, b| { 114 | b.gas_metered_mut_glob_then_stack_limited_len 115 | .cmp(&a.gas_metered_mut_glob_then_stack_limited_len) 116 | }); 117 | 118 | for r in results { 119 | let filename = r.filename; 120 | let original_size = r.original_module_len / 1024; 121 | let stack_limit = r.stack_limited_len * 100 / r.original_module_len; 122 | let host_fn = r.gas_metered_host_fn_len * 100 / r.original_module_len; 123 | let mut_glob = r.gas_metered_mut_glob_len * 100 / r.original_module_len; 124 | let host_fn_sl = r.gas_metered_host_fn_then_stack_limited_len * 100 / r.original_module_len; 125 | let mut_glob_sl = 126 | r.gas_metered_mut_glob_then_stack_limited_len * 100 / r.original_module_len; 127 | 128 | println!( 129 | "{filename:30}: orig = {original_size:4} kb, stack_limiter = {stack_limit} %, \ 130 | gas_metered_host_fn = {host_fn} %, both = {host_fn_sl} %,\n \ 131 | {:69} gas_metered_mut_global = {mut_glob} %, both = {mut_glob_sl} %", 132 | "" 133 | ); 134 | } 135 | } 136 | 137 | /// Compare module size overhead of applying gas metering with two methods. 138 | /// 139 | /// Use `cargo test print_gas_metered_sizes -- --nocapture`. 140 | #[test] 141 | fn print_gas_metered_sizes() { 142 | let overheads = calc_size_overheads(); 143 | let mut results = overheads 144 | .iter() 145 | .map(|r| { 146 | let diff = (r.gas_metered_mut_glob_len * 100 / r.gas_metered_host_fn_len) as i32 - 100; 147 | (diff, r) 148 | }) 149 | .collect::>(); 150 | results.sort_unstable_by(|a, b| b.0.cmp(&a.0)); 151 | 152 | println!( 153 | "| {:28} | {:^16} | gas metered/host fn | gas metered/mut global | size diff |", 154 | "fixture", "original size", 155 | ); 156 | println!("|{:-^30}|{:-^18}|{:-^21}|{:-^24}|{:-^11}|", "", "", "", "", "",); 157 | for r in results { 158 | let filename = &r.1.filename; 159 | let original_size = &r.1.original_module_len / 1024; 160 | let host_fn = &r.1.gas_metered_host_fn_len / 1024; 161 | let mut_glob = &r.1.gas_metered_mut_glob_len / 1024; 162 | let host_fn_percent = &r.1.gas_metered_host_fn_len * 100 / r.1.original_module_len; 163 | let mut_glob_percent = &r.1.gas_metered_mut_glob_len * 100 / r.1.original_module_len; 164 | let host_fn = format!("{host_fn} kb ({host_fn_percent:}%)"); 165 | let mut_glob = format!("{mut_glob} kb ({mut_glob_percent:}%)"); 166 | let diff = &r.0; 167 | println!( 168 | "| {filename:28} | {original_size:13} kb | {host_fn:>19} | {mut_glob:>22} | {diff:+8}% |" 169 | ); 170 | } 171 | } 172 | --------------------------------------------------------------------------------