186 Commits

Author SHA1 Message Date
Dmitry Sinyavin 10fd3529f2 Add conditional br count 2022-08-22 18:36:42 +02:00
Dmitry Sinyavin 32ae9f8478 Additional metrics 2022-08-19 12:24:52 +02:00
Dmitry Sinyavin 8bfdb41d01 Return detailed stats 2022-08-09 16:01:05 +02:00
Dmitry Sinyavin 659d3bf12c Reworked stack computation 2022-08-04 21:47:32 +02:00
Dmitry Sinyavin 5b2f75a066 Fix param count and rearrange code 2022-08-02 19:29:06 +02:00
dependabot[bot] 25ff883bbd Update wasmparser requirement from 0.87 to 0.88 (#26)
Updates the requirements on [wasmparser](https://github.com/bytecodealliance/wasm-tools) to permit the latest version.
- [Release notes](https://github.com/bytecodealliance/wasm-tools/releases)
- [Commits](https://github.com/bytecodealliance/wasm-tools/compare/wasmparser-0.87.0...wasmparser-0.88.0)

---
updated-dependencies:
- dependency-name: wasmparser
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-30 17:39:07 +02:00
dependabot[bot] 3b932b11ad Update wasmparser requirement from 0.86 to 0.87 (#24)
Updates the requirements on [wasmparser](https://github.com/bytecodealliance/wasm-tools) to permit the latest version.
- [Release notes](https://github.com/bytecodealliance/wasm-tools/releases)
- [Commits](https://github.com/bytecodealliance/wasm-tools/compare/wasmparser-0.86.0...wasmparser-0.87.0)

---
updated-dependencies:
- dependency-name: wasmparser
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-06 11:21:44 +02:00
Alexander Theißen d10bbdf554 Fix CODEOWNERS 2022-06-21 14:21:59 +02:00
dependabot[bot] 28ef7f550c Update wasmparser requirement from 0.84 to 0.86 (#23)
Updates the requirements on [wasmparser](https://github.com/bytecodealliance/wasm-tools) to permit the latest version.
- [Release notes](https://github.com/bytecodealliance/wasm-tools/releases)
- [Commits](https://github.com/bytecodealliance/wasm-tools/compare/wasmparser-0.84.0...wasmparser-0.86.0)

---
updated-dependencies:
- dependency-name: wasmparser
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-12 20:24:01 +01:00
Filipe Azevedo 4a394c5f88 bump version (#22) 2022-06-08 10:29:51 +02:00
Alexander Theißen d1648be274 Fix publish 2022-06-06 16:16:30 +01:00
Filipe Azevedo 4a51f16874 handle debug info (#16) 2022-06-06 15:42:52 +01:00
dependabot[bot] 8380823e62 Bump actions/checkout from 2 to 3 (#19)
Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-06 11:53:30 +03:00
Sergejs Kostjucenko 88d652e69a add gha to dependabot (#18) 2022-06-02 19:23:10 +03:00
dependabot[bot] 4713aa760f Update wasmparser requirement from 0.82 to 0.84 (#10)
* Update wasmparser requirement from 0.82 to 0.84

Updates the requirements on [wasmparser](https://github.com/bytecodealliance/wasm-tools) to permit the latest version.
- [Release notes](https://github.com/bytecodealliance/wasm-tools/releases)
- [Commits](https://github.com/bytecodealliance/wasm-tools/compare/wasmparser-0.82.0...wasmparser-0.84.0)

---
updated-dependencies:
- dependency-name: wasmparser
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* Adapt tests to new wasmparser

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Alexander Theißen <alex.theissen@me.com>
2022-04-07 15:50:15 +02:00
Alexander Theißen 4548a86329 Add test to measure size overhead (#8)
* Don't use parity-wasm directly

* Add test that output size over head of metering
2022-01-31 14:50:32 +01:00
Alexander Theißen ff68bee449 We should use bench_with_input so that a blackbox is used 2022-01-31 13:14:12 +01:00
Alexander Theißen 7c843842a7 Run CI on windows, too (#7)
* Run CI on windows, too

* Build path without `concat!` macro

* Fix ci on windows
2022-01-31 11:03:18 +01:00
Alexander Theißen 374afe5700 Replace wabt with rust tools (#5) 2022-01-30 13:05:07 +01:00
Alexander Theißen 8291876394 Add benchmarks and add tiny performance improvements (#6)
* Add some benchmarks

* Replace extend -> extend_from_slice (1-2% performance improvement)

* Add many_blocks benchmarks
2022-01-24 21:20:47 +01:00
Alexander Theißen 184b3f8b3a Prepare for releasing v0.1.1 2022-01-18 18:28:07 +01:00
Alexander Theißen 57da96fb50 Consider activation frame for stack height metering (#2)
* Charge a base cost for calling a function

* Added regression test for empty functions

* Satisfy clippy
2022-01-18 18:04:17 +01:00
Alexander Theißen 4e3e6b598a Added documentation link 2022-01-11 21:46:18 +02:00
Alexander Theißen c5eaffd229 Fix README typo 2022-01-11 21:37:12 +02:00
Alexander Theißen e882111f92 Remove everything not needed by substrate
Also rename to wasm-instrument
2022-01-11 17:50:24 +02:00
Leonardo Yvens b22696aaa5 sign_ext feature flag (#174)
* sign_ext feature flag

* Derive default to fix clippy warning
2021-12-21 15:23:16 +01:00
Dan Shields 5259f1c922 Merge pull request #173 from paritytech/NukeManDan-patch-1
version 0.19.0
rust 2021
MSRV 1.56.1
2021-11-21 16:01:06 -07:00
Dan Shields da2f0a0f23 version 0.19.0
edition = "2021"
rust-version = "1.56.1"
2021-11-20 23:46:06 -07:00
Keith Yeung 1d62dc1270 cargo fmt 2021-11-13 19:05:45 -08:00
Dan Shields abd5d4f6df move to rust 2021 2021-11-13 19:59:13 -07:00
Sergei Shulepov c9b837c80a Merge pull request #171 from paritytech/at-bump-version
Bump version and only include essential files in crate
2021-09-08 14:17:45 +02:00
Alexander Theißen 2bf8068571 Bump version and only include essential files in crate 2021-09-07 17:55:50 +02:00
Alexander Theißen 2293760964 Use linear time algorithm to inject stack height metering (#170) 2021-09-07 17:42:03 +02:00
Alexander Theißen 2f88f49ef6 Add test for empty loops (#169) 2021-09-06 16:25:50 +02:00
Alexander Theißen 6ff274f0b8 Add CODEOWNERS (#167) 2021-08-02 12:54:10 +03:00
Chevdor 3d3e3010b3 Add justfile (#166) 2021-07-28 16:29:33 +02:00
Chevdor ef4e09fff9 Typo and minor fixes (#165) 2021-07-28 16:28:25 +02:00
Chevdor b8b18be419 Fix linting related issues (#164) 2021-07-27 16:14:14 +02:00
Alexander Theißen 72626a566a Add a clippy job to the CI (#163)
* Add clippy CI check

* Fix remaining clippy lints

* Cargo fmt
2021-07-27 15:50:50 +02:00
dependabot[bot] b8e6b9e319 Update env_logger requirement from 0.8 to 0.9 (#159)
Updates the requirements on [env_logger](https://github.com/env-logger-rs/env_logger) to permit the latest version.
- [Release notes](https://github.com/env-logger-rs/env_logger/releases)
- [Changelog](https://github.com/env-logger-rs/env_logger/blob/main/CHANGELOG.md)
- [Commits](https://github.com/env-logger-rs/env_logger/compare/v0.8.0...v0.9.0)

---
updated-dependencies:
- dependency-name: env_logger
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-27 15:38:59 +02:00
Chevdor cb023973e8 Cleanup (#162)
* Remove unnecessary references

* Shorten expressions

* Remove unnecessary reference

* Simplify expression

* Fix formatting
2021-07-27 15:14:55 +02:00
Alexander Theißen a0b548b37d Add rustfmt.toml from substrate repo (#161)
* Add rustfmt.toml from substrate repo

* Apply rustfmt to code base

* Fix formatting

* Move rustfmt job to the top
2021-07-27 14:46:28 +02:00
Alexander Theißen 77ad07e347 Add github actions (#160)
* Add github actions

* s/toolchains/toolchain

* s/test/build

* Remove no longer need travis
2021-07-27 13:59:16 +02:00
Alexander Theißen c5043a47ac Add dependabot (#158) 2021-07-27 12:06:17 +02:00
NikVolf 9f15b8cd21 bump to 0.18.1 2021-06-10 14:31:47 +03:00
Nikolay Volf 1ef05d71ed Strip custom sections in wasm-prune (#150)
* strip custom sections

* line width
2021-06-10 14:29:59 +03:00
Sergei Shulepov 0cead7ba6d Merge pull request #151 from paritytech/ser-bless
Add BLESS env option
2021-06-10 11:13:04 +02:00
Sergei Shulepov e1c8ce90a1 Merge pull request #152 from paritytech/ser-fix-locals
Fix counting of the local variables
2021-06-10 10:49:36 +02:00
Sergei Shulepov 9e98400de0 Update tests/diff.rs
Co-authored-by: Alexander Theißen <alex.theissen@me.com>
2021-06-10 10:49:16 +02:00
Sergey Shulepov 19760b5835 Fix counting of the local variables
The code assumed that the number of `Local` and number of locals is the
same thing. In reality though it is not. `Local` actually represents a
group of locals with the same type. The group can declare more than one
variable and the number of them is returned by `Local::count`.

In this PR we acknowledge this fact. Along the way we add a checked
arithmetic for locals_count and max_stack_height summation.
2021-06-09 19:22:40 +02:00
Sergey Shulepov 717aa8f0cb Add BLESS env option
Right now if there is a diff between the actual and expected results,
the expected file will be overwritten. This may be annoying. To make it
controlled we introduce BLESS option.

This means that the expected files won't be regenerated unless this
environment variable is set.

Therefore to regenerate the tests use:

    BLESS=1 cargo test
2021-06-09 19:17:54 +02:00
Sergei Shulepov d891cddcb3 Merge pull request #149 from paritytech/at-export-parity-wasm
Export the complete `parity-wasm` crate
2021-05-27 12:18:15 +02:00
Alexander Theißen 95a711bc33 Re-export the whole parity_wasm crate 2021-05-26 15:10:15 +02:00
Alexander Theißen 1b0ed1b383 Transition to Rust 2018 style imports 2021-05-26 14:59:04 +02:00
Sergei Shulepov ca45220af5 Merge pull request #148 from paritytech/at-update-deps
Update dependencies
2021-04-22 12:42:51 +03:00
Alexander Theißen 2d1f4daed7 Update dependencies 2021-04-22 09:08:41 +02:00
Alexander Theißen ad01d9b41c Bump to 0.17.1 2021-04-21 14:12:12 +02:00
Alexander Theißen b2272f39bc stack_height: if instruction should pop one value from the stack (#147)
* stack_height: 'if' instruction should pop one value from the stack

* Fix indentation
2021-04-21 14:08:50 +02:00
Alexander Theißen d9432bafa9 Bump to 0.17 2020-12-12 18:45:19 +01:00
Alexander Theißen 1131240d39 Export parity_wasm::Instruction
We should export it because it is used in a public interface.
Otherwise every crate that depends on this needs to also
directly depend on parity_wasm.
2020-12-12 17:59:47 +01:00
Alexander Theißen 988ac32095 De-Bumo to 0.16.1
Last PR did not contain any change to an interface.
2020-12-10 10:53:30 +01:00
Sergei Shulepov 712c696c2d Merge pull request #145 from paritytech/cmichi-upgrade-to-parity-wasm-0.42.1
Upgrade to `parity-wasm` 0.42.1
2020-12-09 12:23:11 +01:00
Michael Mueller 9a0f992cb3 Use results length as arity 2020-12-08 17:24:59 +01:00
Michael Mueller d6127afd1d Upgrade to parity-wasm v0.42.1 2020-12-08 16:51:55 +01:00
Alexander Theißen 7da376062a bump to 0.16 2020-10-22 16:12:22 +02:00
Sergei Shulepov 3ab49836be Merge pull request #144 from paritytech/at-unify-packet
Merge pwasm-utils-cli into the main package
2020-10-22 14:34:09 +02:00
Alexander Theißen 2430b18633 Make feature "std" a requirement for the "cli" feature 2020-10-22 12:27:31 +02:00
Alexander Theißen aebfc0fbd7 Fixes necessary for having no_std imports work with edition2018 2020-10-22 12:26:24 +02:00
Alexander Theißen a2653cff5a Update CI script 2020-10-22 10:26:26 +02:00
Alexander Theißen 3142a74de2 Update README.md to reflect the new package unification 2020-10-21 20:49:34 +02:00
Alexander Theißen f556bde4a4 Ignore .vscode directory 2020-10-21 20:26:22 +02:00
Alexander Theißen f05f43b883 Unify pwasm-utils and pwasm-utils-cli packet 2020-10-21 20:24:49 +02:00
Alexander Theißen af761da031 Add repository metadata to Cargo.toml files 2020-10-21 11:37:30 +02:00
Alexander Theißen fbaae277fc bump to 0.15 2020-10-21 11:17:15 +02:00
Sergei Shulepov e0a05c6329 Merge pull request #143 from paritytech/at-instruction-costs
Make the rules passed to gas metering injection generic
2020-10-14 14:55:58 +02:00
Alexander Theißen e0dbaef676 Wrapped overlong line 2020-10-14 14:17:12 +02:00
Alexander Theißen 880d273861 Make the rules passed to gas metering injection generic 2020-10-14 13:26:48 +02:00
Sergei Shulepov 3568667ecb Merge pull request #142 from paritytech/at-cleanup
Address all clippy lints
2020-10-13 16:24:53 +02:00
Alexander Theißen c09a924a81 Address all clippy lints
These changes do not change the behaviour of the
code and should be non-controversial.
2020-10-13 15:42:01 +02:00
Sergei Shulepov f59eb121e5 Merge pull request #141 from paritytech/at-fixes
Fix issues with the stack height metering
2020-10-13 13:51:51 +02:00
Alexander Theißen 1f8e6dd5b2 Don't generate duplicate thunks
Previously, functions that appear in multiple places
(exported, start function, table) would generate a thunk
for each place they appear in. Those additional thunks are
identical and only only one of them would be referenced.
Main offender are tables with redundant entries.

This commit eliminates those duplicate thunks without adding
any additional overhead.
2020-10-13 11:43:59 +02:00
Alexander Theißen 5e3b06de05 Fix Instruction::CallIndirect stack height metering
The stack height metering for functions containing
CallIndirect was wrong. The code did not take into
consideration that is pops one value from the stack.

The effect was that the stack height of functions
using this instruction was higher than its real height.
2020-10-13 11:43:59 +02:00
Alexander Theißen 2306999c9c Fix using Write::write without checking the return value
Use write_all instead which garantues that the whole buffer
was written.
2020-10-13 11:43:40 +02:00
NikVolf 016425a25b bump to 0.14 2020-08-04 13:54:57 +03:00
Alexander Theißen 409ced002a Allow specifying the module of the imported 'gas' function (#140)
* No need for mem::replace when doing a simple assignment

* Allow specifying the module of the imported 'gas' function

This allows users to place the imported function inside a custom
module instead of the generic 'env' module.
2020-08-04 13:53:53 +03:00
NikVolf b61f6dd52f bump to 0.13 2020-08-03 14:59:57 +03:00
Nikolay Volf e026abe166 Export internal globals instrumentation (#138)
* export internal globals

* add test

* Update src/export_globals.rs

Co-authored-by: Sergei Shulepov <sergei@parity.io>

* address review

Co-authored-by: Sergei Shulepov <sergei@parity.io>
2020-08-03 14:58:24 +03:00
Nikolay Volf 8c6dec11a4 Merge pull request #139 from paritytech/update-wabt
Update wabt to 0.10
2020-08-03 14:20:20 +03:00
NikVolf 3f2935df9c update wabt 2020-08-03 14:00:07 +03:00
Nikolay Volf dcf189b822 Merge pull request #135 from osolmaz/correct-spelling
Corrected spelling error
2020-07-29 16:05:53 +03:00
Sergei Shulepov 19b5fb50cb Merge pull request #137 from osolmaz/correct-instruction-type
Corrected InstructionType's for GetGlobal and SetGlobal
2020-07-27 22:26:16 +02:00
Onur Solmaz 1e8953a9cb Corrected InstructionType's for GetGlobal and SetGlobal 2020-07-27 13:56:53 +02:00
Onur Solmaz 377684f9c7 Corrected spelling error 2020-07-23 20:08:03 +02:00
Nikolay Volf e89abb0c17 Merge pull request #134 from bddap/bddap-nostd-on-stable
Bump 'parity-wasm' version to enable no_std builds on stable.
2019-11-01 16:36:11 +03:00
Andrew Dirksen e6336a4a90 Travis-ci was configured to build with no_std only when using rust nightly.
This commit tells travis to attempt no_std builds for both nightly and stable.
2019-10-31 16:56:11 -07:00
Andrew Dirksen a881cf12bb Bump 'parity-wasm' version to enable no_std builds on stable.
Bump own version in preparation for cargo publish.
Bump cli verion to match.
2019-10-31 15:52:09 -07:00
NikVolf 2fe761f8c4 bump cli to 0.11 as well 2019-09-09 16:58:21 +03:00
Sergey Pepyakin 87761dad61 Bump pwasm-utils 2019-09-05 22:58:03 +02:00
Sergei Pepyakin a768692bbe Merge pull request #133 from paritytech/bump-parity-wasm
Bump parity-wasm to latest version
2019-09-05 22:50:02 +02:00
Demi M. Obenour ea4cde0e7d Bump parity-wasm to latest version
to allow Substrate to do the same
2019-09-04 18:14:10 -04:00
NikVolf f9d8b722b5 bump to 0.10 2019-08-29 18:57:46 +03:00
Nikolay Volf 39f234e441 Merge pull request #129 from oscoin/fix-pwasm
Preserve "deploy" when optimizing pwasm ctor module
2019-08-29 18:57:00 +03:00
Thomas Scholtes b4f9be733d Preserve "deploy" when optimizing pwasm ctor module
When optimizing the constructor module for a PWasm contract the "deploy" symbol is preserved instead of the "call"
symbol. Before this change `build` would error for PWasm contracts because `pack_instance` would not find the "deploy"
symbol in the optimized contract.

Fixes #128
2019-08-29 16:27:48 +02:00
NikVolf 155c7253c3 bump to 0.9 2019-08-02 15:21:44 +03:00
Nikolay Volf c9cdef4c51 Merge pull request #126 from paritytech/ser-update-pwasm
Update parity-wasm to 0.39
2019-08-02 15:20:32 +03:00
Sergey Pepyakin 2b5026a6c5 Add notice about parity-wasm features 2019-07-30 16:48:58 +02:00
Sergey Pepyakin a774a2cb29 Update parity-wasm to 0.39 2019-07-30 16:44:05 +02:00
Sergei Pepyakin 6fd636a41d Merge pull request #125 from jimpo/gas-fuzzing
Validate the gas metering algorithm using fuzzer.
2019-07-30 13:04:51 +02:00
Jim Posen 5792da28d5 Fix typo
Co-Authored-By: Sergei Pepyakin <s.pepyakin@gmail.com>
2019-07-25 12:37:57 +02:00
Jim Posen f8673d5b87 Fix dev dependency crate imports. 2019-07-25 11:05:25 +02:00
Jim Posen 5180d694ce Validate the gas metering algorithm using fuzzer. 2019-07-19 11:25:54 +02:00
NikVolf a150df8703 bump to 0.8.1 2019-07-17 18:40:00 +03:00
Sergei Pepyakin ae412c45f1 Merge pull request #124 from paritytech/fix-warnings
Fix warnings
2019-07-17 17:22:31 +03:00
Nikolay Volf 6f46ef5211 Update README.md 2019-07-17 17:02:55 +03:00
NikVolf 026b0502bb fix warnings 2019-07-17 17:01:15 +03:00
NikVolf 2c173fee26 bump to 0.8.0 due to change in gas counting 2019-07-17 16:57:28 +03:00
Sergei Pepyakin 0870ce6646 Merge pull request #122 from jimpo/basic-block
Rewrite gas metering algorithm to handle branches
2019-07-17 16:19:52 +03:00
Jim Posen 82bd972333 !fixup lowest_forward_br rename and field docs. 2019-07-12 10:45:01 +02:00
Jim Posen 93abbcfe56 Add gas injection unit test for correct else behavior. 2019-07-12 10:30:11 +02:00
Jim Posen ed7f31ec20 Use macro + WAT to make gas injection unit tests more readable. 2019-07-12 10:15:37 +02:00
Jim Posen b5472bcd8f !fixup Address review comments. 2019-07-11 16:45:46 +02:00
Jim Posen b3f8f62105 Update gas expectations. 2019-07-04 17:57:40 +02:00
Jim Posen 0cf7daa9e5 Update high level inject_gas_counter function documentation. 2019-07-04 17:57:40 +02:00
Jim Posen 24924f59ec Change gas metering injection code to handle branches properly. 2019-07-04 17:57:40 +02:00
Jim Posen de60f491b4 Fix ordering of actual and expected arguments in assert_eq!. 2019-07-04 17:57:40 +02:00
Jim Posen 4c0f42c6fc Perform gas metering injection step in linear time.
Previously the code was quadratic in the worst case as inserting into
the middle of a vector is a linear-time operation.
2019-07-04 17:57:40 +02:00
Jim Posen c3d10a2619 Merge pull request #121 from jimpo/gas-docs
Documentation of gas metering instrumentation process & cleanup.
2019-07-01 17:25:45 +02:00
Jim Posen 863744b1fc Add gas test confirming that br instructions do not end blocks. 2019-07-01 17:02:21 +02:00
Jim Posen 89e13ee901 Cleanup stack height Context.
Removes unnecessary Options and fixes typos.
2019-06-26 12:48:51 +02:00
Jim Posen 929e0ec2c0 Documentation of gas metering instrumentation process. 2019-06-26 12:47:30 +02:00
Nikolay Volf f6a1a6a066 Merge pull request #118 from paritytech/idents
Fix identation
2019-04-10 07:59:26 +03:00
NikVolf 0d40703c6e fix identation 2019-04-09 19:02:07 +03:00
NikVolf 124de6c2db update dependency 2019-04-09 18:57:16 +03:00
NikVolf 5a617c3aae bump cli to 0.7 2019-04-09 18:56:54 +03:00
NikVolf 80ea6ec7ad bump to 0.7.0 2019-04-09 18:56:32 +03:00
Sergei Pepyakin bbcc495ccc Merge pull request #116 from paritytech/small-tests
A couple of small tests.
2019-04-03 14:43:29 +02:00
Sergey Pepyakin 1b7a5d26ea A couple of small tests. 2019-04-03 14:24:01 +02:00
Nikolay Volf b1fbd2921e Merge pull request #115 from holygits/add-clap-version
Fix '-V/--version' output [EOM]
2019-02-27 10:33:52 +08:00
holygits 1e68a862f8 Fix '-V/--version' output 2019-02-26 16:43:33 +13:00
Nikolay Volf 466f5cceba Merge pull request #107 from paritytech/graph
Higher level wasm representation
2019-01-29 17:14:54 +03:00
NikVolf 38e0f254b0 use indoc! 2019-01-27 12:15:38 +03:00
NikVolf 5b2cd9c4c6 add example 2019-01-24 16:21:52 +03:00
NikVolf ad83ad17ee avoid panic when generating format 2019-01-24 16:10:39 +03:00
NikVolf 91036c0aff avoid panics when creating representation 2019-01-24 16:04:00 +03:00
NikVolf 728c935367 alter some tests to show correspondence 2019-01-24 15:36:09 +03:00
NikVolf 33785674dc simplify code 2019-01-24 15:32:14 +03:00
NikVolf 3e635514e4 some reformatting 2019-01-24 15:26:17 +03:00
NikVolf d8428327d5 simpler imports 2019-01-24 15:18:16 +03:00
NikVolf d695703146 more complicated opt and delete tests 2019-01-24 15:07:57 +03:00
NikVolf cda99e70da add much more complicated assertion 2019-01-24 14:42:53 +03:00
NikVolf 0a78a1ab8d complicate test 2019-01-24 14:35:05 +03:00
NikVolf 33c84edd78 Merge remote-tracking branch 'origin/master' into graph
# Conflicts:
#	src/lib.rs
2019-01-24 12:38:35 +03:00
NikVolf 8413e562cd more insert api and graph module logic upon 2019-01-24 12:37:03 +03:00
NikVolf 1bc4973e6e insert api in ref_list 2019-01-24 12:05:20 +03:00
Sergei Pepyakin 8ecbc8ddcc Merge pull request #114 from paritytech/ret
"return_ "-> "ret" in public api
2019-01-24 09:54:46 +01:00
NikVolf 56464c102f return_ -> ret 2019-01-24 11:20:31 +03:00
Nikolay Volf 6046e94b40 Merge pull request #113 from holygits/fix/preserve-optimize
Preserve deploy symbol on optimize for substrate target
2019-01-24 11:15:30 +03:00
holygits ec206fca64 Preserve deploy symbol on optimize for substrate target 2019-01-24 15:46:01 +13:00
Sergei Pepyakin 59384e09d0 Merge pull request #109 from holygits/fix/108
Preserve 'deploy' export for Substrate binaries
2019-01-23 22:21:17 +01:00
holygits 4f81bbc506 Don't pack Substrate ctor module 2019-01-24 10:18:18 +13:00
NikVolf f5890c2c7b more complex test 2019-01-23 15:01:08 +03:00
NikVolf 7504381419 fix linking for elements 2019-01-23 14:44:32 +03:00
NikVolf bb9832dba1 more docs and warnings 2019-01-23 13:57:26 +03:00
NikVolf 62ea903c3a add some docs 2019-01-23 13:47:44 +03:00
holygits c47adc1bd4 Refactor TargetRuntime as enum
Don't rename create symbol for substrate binaries
2019-01-23 17:03:53 +13:00
NikVolf c3833efca7 fix for nightly 2019-01-22 20:39:51 +03:00
NikVolf 4e871c65e2 generate instructions on module generation 2019-01-22 20:37:36 +03:00
NikVolf 48c1c6e72a code mapping 2019-01-22 20:30:50 +03:00
NikVolf d60340762b public api exposure and fix warnings 2019-01-22 18:28:15 +03:00
NikVolf da5b2ca5f6 rest of sections generation 2019-01-22 18:21:30 +03:00
NikVolf c520d334cd ordering and filtering 2019-01-22 17:14:37 +03:00
NikVolf 76b6743c64 generation of more sections 2019-01-22 16:40:28 +03:00
NikVolf d6c6cefcf1 generation - import 2019-01-22 16:11:04 +03:00
NikVolf 86da6439d1 data and elements 2019-01-22 15:15:17 +03:00
NikVolf ed1c7b1b51 better exports 2019-01-22 14:42:57 +03:00
NikVolf cf10b7d5d9 exports and fix for no-std 2019-01-22 14:31:21 +03:00
NikVolf dd9169e30f table and memory 2019-01-22 13:03:11 +03:00
NikVolf be40285a67 func and tests 2019-01-22 12:58:29 +03:00
NikVolf ba45e15567 remove unused 2019-01-22 12:39:09 +03:00
NikVolf 33ff0cbe1d refactor to reflist 2019-01-22 12:19:29 +03:00
NikVolf db4070b96c ref list impl 2019-01-22 12:08:25 +03:00
NikVolf 80d80a37d9 import rewiring 2019-01-21 17:56:30 +03:00
NikVolf 06277915da some graph structure definition 2019-01-21 17:04:31 +03:00
NikVolf 7c7a0713fc bump to 0.6.2 2019-01-15 14:18:45 +03:00
Nikolay Volf 31e3324015 Merge pull request #104 from laizy/master
rewire corresponding indices in name section
2019-01-15 14:17:13 +03:00
laizy dc993bdb1b rewire corresponding indices in name section 2019-01-13 09:53:59 +08:00
76 changed files with 3582 additions and 4495 deletions
+17 -1
View File
@@ -6,6 +6,22 @@ tab_width=4
end_of_line=lf
charset=utf-8
trim_trailing_whitespace=true
max_line_length=120
max_line_length=100
insert_final_newline=true
[*.md]
max_line_length=80
indent_style=space
indent_size=2
[*.yml]
indent_style=space
indent_size=2
tab_width=8
end_of_line=lf
[*.sh]
indent_style=space
indent_size=2
tab_width=8
end_of_line=lf
+4
View File
@@ -0,0 +1,4 @@
# For details about syntax, see:
# https://help.github.com/en/articles/about-code-owners
* @athei @pepyakin
+11
View File
@@ -0,0 +1,11 @@
version: 2
updates:
- package-ecosystem: cargo
directory: "/"
labels: []
schedule:
interval: "daily"
- package-ecosystem: github-actions
directory: '/'
schedule:
interval: daily
+97
View File
@@ -0,0 +1,97 @@
name: Check
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
env:
CARGO_TERM_COLOR: always
jobs:
rustfmt:
runs-on: "ubuntu-latest"
steps:
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly
components: rustfmt
- uses: actions/checkout@v3
- name: Cargo fmt
uses: actions-rs/cargo@v1
with:
toolchain: nightly
command: fmt
args: --all -- --check
clippy:
runs-on: "ubuntu-latest"
steps:
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
components: clippy
- uses: actions/checkout@v3
- name: Cargo clippy
uses: actions-rs/cargo@v1
with:
toolchain: stable
command: clippy
args: --all-targets --all-features -- -D warnings
test:
strategy:
matrix:
os: ["ubuntu-latest", "macos-latest", "windows-latest"]
toolchain: ["stable", "nightly"]
runs-on: ${{ matrix.os }}
steps:
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
target: wasm32-unknown-unknown
toolchain: ${{ matrix.toolchain }}
- name: Set git to use LF
run: |
git config --global core.autocrlf false
git config --global core.eol lf
- uses: actions/checkout@v3
- name: Cargo build
uses: actions-rs/cargo@v1
with:
toolchain: ${{ matrix.toolchain }}
command: build
- name: Cargo build (no_std)
uses: actions-rs/cargo@v1
with:
toolchain: ${{ matrix.toolchain }}
command: build
args: --no-default-features
- name: Cargo build (wasm)
uses: actions-rs/cargo@v1
with:
toolchain: ${{ matrix.toolchain }}
command: build
args: --no-default-features --target wasm32-unknown-unknown
- name: Cargo test
uses: actions-rs/cargo@v1
with:
toolchain: ${{ matrix.toolchain }}
command: test
args: --all-features
+1
View File
@@ -3,3 +3,4 @@ target
.cargo
.DS_Store
.idea
.vscode
-9
View File
@@ -1,9 +0,0 @@
language: rust
rust:
- nightly
- stable
script:
- cargo build --all --release --verbose
- cargo test --all --verbose
- if [ "$TRAVIS_RUST_VERSION" == "nightly" ]; then cargo build --no-default-features; fi
+34
View File
@@ -0,0 +1,34 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
The semantic versioning guarantees cover the interface to the substrate runtime which
includes this pallet as a dependency. This module will also add storage migrations whenever
changes require it. Stability with regard to offchain tooling is explicitly excluded from
this guarantee: For example adding a new field to an in-storage data structure will require
changes to frontends to properly display it. However, those changes will still be regarded
as a minor version bump.
The interface provided to smart contracts will adhere to semver with one exception: Even
major version bumps will be backwards compatible with regard to already deployed contracts.
In other words: Upgrading this pallet will not break pre-existing contracts.
## [v0.2.0] 2022-06-06
- Adjust debug information (if already parsed) when injecting gas metering
[#16](https://github.com/paritytech/wasm-instrument/pull/16)
## [v0.1.1] 2022-01-18
### Fixed
- Stack metering disregarded the activiation frame.
[#2](https://github.com/paritytech/wasm-instrument/pull/2)
## [v0.1.0] 2022-01-11
### Changed
- Created from [pwasm-utils](https://github.com/paritytech/wasm-utils) by removing unused code and cleaning up docs.
+31 -19
View File
@@ -1,27 +1,39 @@
[package]
name = "pwasm-utils"
version = "0.6.1"
authors = ["Nikolay Volf <nikvolf@gmail.com>", "Sergey Pepyakin <s.pepyakin@gmail.com>"]
license = "MIT/Apache-2.0"
readme = "README.md"
description = "Collection of command-line utilities and corresponding Rust api for producing pwasm-compatible executables"
keywords = ["wasm", "webassembly", "pwasm"]
name = "wasm-instrument"
version = "0.2.0"
edition = "2021"
rust-version = "1.56.1"
authors = ["Parity Technologies <admin@parity.io>"]
license = "MIT OR Apache-2.0"
description = "Instrument and transform wasm modules."
keywords = ["wasm", "webassembly", "blockchain", "gas-metering", "parity"]
categories = ["wasm", "no-std"]
repository = "https://github.com/paritytech/wasm-instrument"
include = ["src/**/*", "LICENSE-*", "README.md"]
[[bench]]
name = "benches"
harness = false
path = "benches/benches.rs"
[profile.bench]
lto = "fat"
codegen-units = 1
[dependencies]
parity-wasm = { version = "0.31", default-features = false }
log = { version = "0.4", default-features = false }
byteorder = { version = "1", default-features = false }
parity-wasm = { version = "0.45", default-features = false }
log = "0.4"
[dev-dependencies]
tempdir = "0.3"
wabt = "0.2"
diff = "0.1.11"
binaryen = "0.12"
criterion = "0.3"
diff = "0.1"
rand = "0.8"
wat = "1"
wasmparser = "0.88"
wasmprinter = "0.2"
[features]
default = ["std"]
std = ["parity-wasm/std", "log/std", "byteorder/std"]
[workspace]
members = [
"./cli",
]
std = ["parity-wasm/std"]
sign_ext = ["parity-wasm/sign_ext"]
-2
View File
@@ -1,5 +1,3 @@
Copyright (c) 2017 Nikolay Volf
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
+17 -48
View File
@@ -1,64 +1,33 @@
# wasm-utils
# wasm-instrument
[![Build Status](https://travis-ci.org/paritytech/wasm-utils.svg?branch=master)](https://travis-ci.org/paritytech/wasm-utils)
A Rust library containing a collection of wasm module instrumentations and transformations
mainly useful for wasm based block chains and smart contracts.
Collection of WASM utilities used in Parity and WASM contract development
## Provided functionality
## Build tools for cargo
This is a non exhaustive list of provided functionality. Please check out the [documentation](https://docs.rs/wasm-instrument/latest/wasm_instrument/) for details.
Easiest way to use is to install via `cargo install`:
### Gas Metering
```
cargo install pwasm-utils-cli --bin wasm-build
```
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).
## Symbols pruning (wasm-prune)
### Stack Height Limiter
```
cargo install pwasm-utils-cli --bin wasm-prune
wasm-prune <input_wasm_binary.wasm> <output_wasm_binary.wasm>
```
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.
This will optimize WASM symbols tree to leave only those elements that are used by contract `call` function entry.
This is the kind of indeterminism that can lead to consensus failures when used in a blockchain context.
## Gas counter (wasm-gas)
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.
For development puposes, raw WASM contract can be injected with gas counters (the same way as it done by Parity runtime when running contracts)
## License
```
cargo install pwasm-utils-cli --bin wasm-gas
wasm-gas <input_wasm_binary.wasm> <output_wasm_binary.wasm>
```
## Externalization (wasm-ext)
Parity WASM runtime provides some library functions that can be commonly found in libc. WASM binary size can be reduced and performance may be improved if these functions are used. This utility scans for invocations of the following functions inside the WASM binary:
- `_malloc`,
- `_free`,
- `_memcpy`,
- `_memset`,
- `_memmove`
And then substitutes them with invocations of the imported ones. Should be run before `wasm-opt` for better results.
```
cargo install pwasm-utils-cli --bin wasm-ext
wasm-ext <input_wasm_binary.wasm> <output_wasm_binary.wasm>
```
## API
All executables use corresponding api methods of the root crate and can be combined in other build tools.
# License
`wasm-utils` is primarily distributed under the terms of both the MIT
license and the Apache License (Version 2.0), at your choice.
`wasm-instrument` is distributed under the terms of both the MIT license and the
Apache License (Version 2.0), at your choice.
See LICENSE-APACHE, and LICENSE-MIT for details.
## Contribution
### Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in `wasm-utils` by you, as defined in the Apache-2.0 license, shall be
dual licensed as above, without any additional terms or conditions.
for inclusion in `wasm-instrument` by you, as defined in the Apache-2.0 license, shall be
dual licensed as above, without any additional terms or conditions.
+51
View File
@@ -0,0 +1,51 @@
use criterion::{
criterion_group, criterion_main, measurement::Measurement, BenchmarkGroup, Criterion,
Throughput,
};
use std::{
fs::{read, read_dir},
path::PathBuf,
};
use wasm_instrument::{
gas_metering, inject_stack_limiter,
parity_wasm::{deserialize_buffer, elements::Module},
};
fn fixture_dir() -> PathBuf {
let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
path.push("benches");
path.push("fixtures");
path
}
fn any_fixture<F, M>(group: &mut BenchmarkGroup<M>, f: F)
where
F: Fn(Module),
M: Measurement,
{
for entry in read_dir(fixture_dir()).unwrap() {
let entry = entry.unwrap();
let bytes = read(&entry.path()).unwrap();
group.throughput(Throughput::Bytes(bytes.len().try_into().unwrap()));
group.bench_with_input(entry.file_name().to_str().unwrap(), &bytes, |bench, input| {
bench.iter(|| f(deserialize_buffer(input).unwrap()))
});
}
}
fn gas_metering(c: &mut Criterion) {
let mut group = c.benchmark_group("Gas Metering");
any_fixture(&mut group, |module| {
gas_metering::inject(module, &gas_metering::ConstantCostRules::default(), "env").unwrap();
});
}
fn stack_height_limiter(c: &mut Criterion) {
let mut group = c.benchmark_group("Stack Height Limiter");
any_fixture(&mut group, |module| {
inject_stack_limiter(module, 128).unwrap();
});
}
criterion_group!(benches, gas_metering, stack_height_limiter);
criterion_main!(benches);
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
-50
View File
@@ -1,50 +0,0 @@
[package]
name = "pwasm-utils-cli"
version = "0.6.0"
authors = ["Nikolay Volf <nikvolf@gmail.com>", "Sergey Pepyakin <s.pepyakin@gmail.com>"]
license = "MIT/Apache-2.0"
readme = "README.md"
description = "Collection of command-line utilities and corresponding Rust api for producing pwasm-compatible executables"
keywords = ["wasm", "webassembly", "pwasm"]
[lib]
[[bin]]
name = "wasm-prune"
path = "prune/main.rs"
[[bin]]
name = "wasm-ext"
path = "ext/main.rs"
[[bin]]
name = "wasm-gas"
path = "gas/main.rs"
[[bin]]
name = "wasm-build"
path = "build/main.rs"
[[bin]]
name = "wasm-stack-height"
path = "stack_height/main.rs"
[[bin]]
name = "wasm-pack"
path = "pack/main.rs"
[[bin]]
name = "wasm-check"
path = "check/main.rs"
[dependencies]
parity-wasm = "0.31"
pwasm-utils = { path = "..", version = "0.6" }
glob = "0.2"
clap = "2.24"
log = "0.4"
env_logger = "0.5"
lazy_static = "1.0"
[dev-dependencies]
tempdir = "0.3"
-11
View File
@@ -1,11 +0,0 @@
# pwasm-utils-cli
Collection of WASM utilities used in Parity and WASM contract devepment
## Install
Easiest way to use is to install via `cargo install`:
```
cargo install pwasm-utils-cli
```
-242
View File
@@ -1,242 +0,0 @@
//! Experimental build tool for cargo
extern crate glob;
extern crate pwasm_utils as utils;
extern crate clap;
extern crate parity_wasm;
extern crate pwasm_utils_cli as logger;
mod source;
use std::{fs, io};
use std::path::PathBuf;
use clap::{App, Arg};
use parity_wasm::elements;
use utils::{build, BuildError, SourceTarget, TargetRuntime};
#[derive(Debug)]
pub enum Error {
Io(io::Error),
FailedToCopy(String),
Decoding(elements::Error, String),
Encoding(elements::Error),
Build(BuildError),
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
use self::Error::*;
match *self {
Io(ref io) => write!(f, "Generic i/o error: {}", io),
FailedToCopy(ref msg) => write!(f, "{}. Have you tried to run \"cargo build\"?", msg),
Decoding(ref err, ref file) => write!(f, "Decoding error ({}). Must be a valid wasm file {}. Pointed wrong file?", err, file),
Encoding(ref err) => write!(f, "Encoding error ({}). Almost impossible to happen, no free disk space?", err),
Build(ref err) => write!(f, "Build error: {}", err)
}
}
}
pub fn wasm_path(input: &source::SourceInput) -> String {
let mut path = PathBuf::from(input.target_dir());
path.push(format!("{}.wasm", input.final_name()));
path.to_string_lossy().to_string()
}
pub fn process_output(input: &source::SourceInput) -> Result<(), Error> {
let mut cargo_path = PathBuf::from(input.target_dir());
let wasm_name = input.bin_name().to_string().replace("-", "_");
cargo_path.push(
match input.target() {
SourceTarget::Emscripten => source::EMSCRIPTEN_TRIPLET,
SourceTarget::Unknown => source::UNKNOWN_TRIPLET,
}
);
cargo_path.push("release");
cargo_path.push(format!("{}.wasm", wasm_name));
let mut target_path = PathBuf::from(input.target_dir());
target_path.push(format!("{}.wasm", input.final_name()));
fs::copy(cargo_path.as_path(), target_path.as_path())
.map_err(|io| Error::FailedToCopy(
format!("Failed to copy '{}' to '{}': {}", cargo_path.display(), target_path.display(), io)
))?;
Ok(())
}
fn do_main() -> Result<(), Error> {
logger::init_log();
let matches = App::new("wasm-build")
.arg(Arg::with_name("target")
.index(1)
.required(true)
.help("Cargo target directory"))
.arg(Arg::with_name("wasm")
.index(2)
.required(true)
.help("Wasm binary name"))
.arg(Arg::with_name("target-runtime")
.help("What runtime we are compiling to")
.long("target-runtime")
.takes_value(true)
.default_value("pwasm")
.possible_values(&["substrate", "pwasm"]))
.arg(Arg::with_name("skip_optimization")
.help("Skip symbol optimization step producing final wasm")
.long("skip-optimization"))
.arg(Arg::with_name("enforce_stack_adjustment")
.help("Enforce stack size adjustment (used for old wasm32-unknown-unknown)")
.long("enforce-stack-adjustment"))
.arg(Arg::with_name("runtime_type")
.help("Injects RUNTIME_TYPE global export")
.takes_value(true)
.long("runtime-type"))
.arg(Arg::with_name("runtime_version")
.help("Injects RUNTIME_VERSION global export")
.takes_value(true)
.long("runtime-version"))
.arg(Arg::with_name("source_target")
.help("Cargo target type kind ('wasm32-unknown-unknown' or 'wasm32-unknown-emscripten'")
.takes_value(true)
.long("target"))
.arg(Arg::with_name("final_name")
.help("Final wasm binary name")
.takes_value(true)
.long("final"))
.arg(Arg::with_name("save_raw")
.help("Save intermediate raw bytecode to path")
.takes_value(true)
.long("save-raw"))
.arg(Arg::with_name("shrink_stack")
.help("Shrinks the new stack size for wasm32-unknown-unknown")
.takes_value(true)
.long("shrink-stack"))
.arg(Arg::with_name("public_api")
.help("Preserves specific imports in the library")
.takes_value(true)
.long("public-api"))
.get_matches();
let target_dir = matches.value_of("target").expect("is required; qed");
let wasm_binary = matches.value_of("wasm").expect("is required; qed");
let mut source_input = source::SourceInput::new(target_dir, wasm_binary);
let source_target_val = matches.value_of("source_target").unwrap_or_else(|| source::EMSCRIPTEN_TRIPLET);
if source_target_val == source::UNKNOWN_TRIPLET {
source_input = source_input.unknown()
} else if source_target_val == source::EMSCRIPTEN_TRIPLET {
source_input = source_input.emscripten()
} else {
eprintln!("--target can be: '{}' or '{}'", source::EMSCRIPTEN_TRIPLET, source::UNKNOWN_TRIPLET);
::std::process::exit(1);
}
if let Some(final_name) = matches.value_of("final_name") {
source_input = source_input.with_final(final_name);
}
process_output(&source_input)?;
let path = wasm_path(&source_input);
let module = parity_wasm::deserialize_file(&path)
.map_err(|e| Error::Decoding(e, path.to_string()))?;
let runtime_type_version = if let (Some(runtime_type), Some(runtime_version))
= (matches.value_of("runtime_type"), matches.value_of("runtime_version")) {
let mut ty: [u8; 4] = Default::default();
let runtime_bytes = runtime_type.as_bytes();
if runtime_bytes.len() != 4 {
panic!("--runtime-type should be equal to 4 bytes");
}
ty.copy_from_slice(runtime_bytes);
let version: u32 = runtime_version.parse()
.expect("--runtime-version should be a positive integer");
Some((ty, version))
} else {
None
};
let public_api_entries = matches.value_of("public_api")
.map(|val| val.split(",").collect())
.unwrap_or(Vec::new());
let target_runtime = match matches.value_of("target-runtime").expect("target-runtime has a default value; qed") {
"pwasm" => TargetRuntime::pwasm(),
"substrate" => TargetRuntime::substrate(),
_ => unreachable!("all possible values are enumerated in clap config; qed"),
};
let (module, ctor_module) = build(
module,
source_input.target(),
runtime_type_version,
&public_api_entries,
matches.is_present("enforce_stack_adjustment"),
matches.value_of("shrink_stack").unwrap_or_else(|| "49152").parse()
.expect("New stack size is not valid u32"),
matches.is_present("skip_optimization"),
&target_runtime,
).map_err(Error::Build)?;
if let Some(save_raw_path) = matches.value_of("save_raw") {
parity_wasm::serialize_to_file(save_raw_path, module.clone()).map_err(Error::Encoding)?;
}
if let Some(ctor_module) = ctor_module {
parity_wasm::serialize_to_file(
&path,
ctor_module,
).map_err(Error::Encoding)?;
} else {
parity_wasm::serialize_to_file(&path, module).map_err(Error::Encoding)?;
}
Ok(())
}
fn main() {
if let Err(e) = do_main() {
eprintln!("{}", e);
std::process::exit(1)
}
}
#[cfg(test)]
mod tests {
extern crate tempdir;
use self::tempdir::TempDir;
use std::fs;
use super::process_output;
use super::source::SourceInput;
#[test]
fn processes_cargo_output() {
let tmp_dir = TempDir::new("target").expect("temp dir failed");
let target_path = tmp_dir.path().join("wasm32-unknown-emscripten").join("release");
fs::create_dir_all(target_path.clone()).expect("create dir failed");
{
use std::io::Write;
let wasm_path = target_path.join("example_wasm.wasm");
let mut f = fs::File::create(wasm_path).expect("create fail failed");
f.write(b"\0asm").expect("write file failed");
}
let path = tmp_dir.path().to_string_lossy();
let input = SourceInput::new(&path, "example-wasm");
process_output(&input).expect("process output failed");
assert!(
fs::metadata(tmp_dir.path().join("example-wasm.wasm")).expect("metadata failed").is_file()
)
}
}
-57
View File
@@ -1,57 +0,0 @@
//! Configuration of source binaries
pub const UNKNOWN_TRIPLET: &str = "wasm32-unknown-unknown";
pub const EMSCRIPTEN_TRIPLET: &str = "wasm32-unknown-emscripten";
use utils::SourceTarget;
/// Configuration of previous build step (cargo compilation)
#[derive(Debug)]
pub struct SourceInput<'a> {
target_dir: &'a str,
bin_name: &'a str,
final_name: &'a str,
target: SourceTarget,
}
impl<'a> SourceInput<'a> {
pub fn new<'b>(target_dir: &'b str, bin_name: &'b str) -> SourceInput<'b> {
SourceInput {
target_dir: target_dir,
bin_name: bin_name,
final_name: bin_name,
target: SourceTarget::Emscripten,
}
}
pub fn unknown(mut self) -> Self {
self.target = SourceTarget::Unknown;
self
}
pub fn emscripten(mut self) -> Self {
self.target = SourceTarget::Emscripten;
self
}
pub fn with_final(mut self, final_name: &'a str) -> Self {
self.final_name = final_name;
self
}
pub fn target_dir(&self) -> &str {
self.target_dir
}
pub fn bin_name(&self) -> &str {
self.bin_name
}
pub fn final_name(&self) -> &str {
self.final_name
}
pub fn target(&self) -> SourceTarget {
self.target
}
}
-102
View File
@@ -1,102 +0,0 @@
extern crate parity_wasm;
extern crate pwasm_utils as utils;
extern crate pwasm_utils_cli as logger;
extern crate clap;
use clap::{App, Arg};
use parity_wasm::elements;
fn fail(msg: &str) -> ! {
eprintln!("{}", msg);
std::process::exit(1)
}
const ALLOWED_IMPORTS: &'static [&'static str] = &[
"ret",
"storage_read",
"storage_write",
"balance",
"sender",
"origin",
"fetch_input",
"input_length",
"ccall",
"dcall",
"scall",
"create",
"balance",
"blockhash",
"blocknumber",
"coinbase",
"timestamp",
"difficulty",
"gaslimit",
"address",
"value",
"suicide",
"panic",
"elog",
"abort"
];
fn main() {
logger::init_log();
let matches = App::new("wasm-check")
.arg(Arg::with_name("input")
.index(1)
.required(true)
.help("Input WASM file"))
.get_matches();
let input = matches.value_of("input").expect("is required; qed");
let module = parity_wasm::deserialize_file(&input).expect("Input module deserialization failed");
for section in module.sections() {
match *section {
elements::Section::Import(ref import_section) => {
let mut has_imported_memory_properly_named = false;
for entry in import_section.entries() {
if entry.module() != "env" {
fail("All imports should be from env");
}
match *entry.external() {
elements::External::Function(_) => {
if !ALLOWED_IMPORTS.contains(&entry.field()) {
fail(&format!("'{}' is not supported by the runtime", entry.field()));
}
},
elements::External::Memory(m) => {
if entry.field() == "memory" {
has_imported_memory_properly_named = true;
}
let max = if let Some(max) = m.limits().maximum() {
max
} else {
fail("There is a limit on memory in Parity runtime, and this program does not limit memory");
};
if max > 16 {
fail(&format!(
"Parity runtime has 1Mb limit (16 pages) on max contract memory, this program speicifies {}",
max
));
}
},
elements::External::Global(_) => {
fail("Parity runtime does not provide any globals")
},
_ => { continue; }
}
}
if !has_imported_memory_properly_named {
fail("No imported memory from env::memory in the contract");
}
}
_ => { continue; }
}
}
}
-23
View File
@@ -1,23 +0,0 @@
extern crate parity_wasm;
extern crate pwasm_utils as utils;
extern crate pwasm_utils_cli as logger;
use std::env;
fn main() {
logger::init_log();
let args = env::args().collect::<Vec<_>>();
if args.len() != 3 {
println!("Usage: {} input_file.wasm output_file.wasm", args[0]);
return;
}
let module = utils::externalize(
parity_wasm::deserialize_file(&args[1]).expect("Module to deserialize ok"),
vec!["_free", "_malloc", "_memcpy", "_memset", "_memmove"],
);
parity_wasm::serialize_to_file(&args[2], module).expect("Module to serialize ok");
}
-24
View File
@@ -1,24 +0,0 @@
extern crate parity_wasm;
extern crate pwasm_utils as utils;
extern crate pwasm_utils_cli as logger;
use std::env;
fn main() {
logger::init_log();
let args = env::args().collect::<Vec<_>>();
if args.len() != 3 {
println!("Usage: {} input_file.wasm output_file.wasm", args[0]);
return;
}
// Loading module
let module = parity_wasm::deserialize_file(&args[1]).expect("Module deserialization to succeed");
let result = utils::inject_gas_counter(
module, &Default::default()
).expect("Failed to inject gas. Some forbidden opcodes?");
parity_wasm::serialize_to_file(&args[2], result).expect("Module serialization to succeed")
}
-37
View File
@@ -1,37 +0,0 @@
extern crate parity_wasm;
extern crate pwasm_utils as utils;
extern crate pwasm_utils_cli as logger;
extern crate clap;
use clap::{App, Arg};
fn main() {
logger::init_log();
let target_runtime = utils::TargetRuntime::pwasm();
let matches = App::new("wasm-pack")
.arg(Arg::with_name("input")
.index(1)
.required(true)
.help("Input WASM file"))
.arg(Arg::with_name("output")
.index(2)
.required(true)
.help("Output WASM file"))
.get_matches();
let input = matches.value_of("input").expect("is required; qed");
let output = matches.value_of("output").expect("is required; qed");
let module = parity_wasm::deserialize_file(&input).expect("Input module deserialization failed");
let ctor_module = module.clone();
let raw_module = parity_wasm::serialize(module).expect("Serialization failed");
// Invoke packer
let mut result_module = utils::pack_instance(raw_module, ctor_module, &utils::TargetRuntime::pwasm()).expect("Packing failed");
// Optimize constructor, since it does not need everything
utils::optimize(&mut result_module, vec![target_runtime.call_symbol]).expect("Optimization failed");
parity_wasm::serialize_to_file(&output, result_module).expect("Serialization failed");
}
-47
View File
@@ -1,47 +0,0 @@
extern crate parity_wasm;
extern crate pwasm_utils as utils;
extern crate pwasm_utils_cli as logger;
extern crate clap;
use clap::{App, Arg};
fn main() {
logger::init_log();
let target_runtime = utils::TargetRuntime::pwasm();
let matches = App::new("wasm-prune")
.arg(Arg::with_name("input")
.index(1)
.required(true)
.help("Input WASM file"))
.arg(Arg::with_name("output")
.index(2)
.required(true)
.help("Output WASM file"))
.arg(Arg::with_name("exports")
.long("exports")
.short("e")
.takes_value(true)
.value_name("functions")
.help(&format!("Comma-separated list of exported functions to keep. Default: '{}'", target_runtime.call_symbol)))
.get_matches();
let exports = matches
.value_of("exports")
.unwrap_or(target_runtime.call_symbol)
.split(',')
.collect();
let input = matches.value_of("input").expect("is required; qed");
let output = matches.value_of("output").expect("is required; qed");
let mut module = parity_wasm::deserialize_file(&input).unwrap();
// Invoke optimizer
// Contract is supposed to have only these functions as public api
// All other symbols not usable by this list is optimized away
utils::optimize(&mut module, exports).expect("Optimizer failed");
parity_wasm::serialize_to_file(&output, module).expect("Serialization failed");
}
-27
View File
@@ -1,27 +0,0 @@
#[macro_use] extern crate log;
#[macro_use] extern crate lazy_static;
extern crate env_logger;
use std::env;
use log::LevelFilter;
use env_logger::Builder;
lazy_static! {
static ref LOG_DUMMY: bool = {
let mut builder = Builder::new();
builder.filter(None, LevelFilter::Info);
if let Ok(log) = env::var("RUST_LOG") {
builder.parse(&log);
}
builder.init();
trace!("logger initialized");
true
};
}
/// Intialize log with default settings
pub fn init_log() {
let _ = *LOG_DUMMY;
}
-28
View File
@@ -1,28 +0,0 @@
extern crate pwasm_utils as utils;
extern crate parity_wasm;
extern crate pwasm_utils_cli as logger;
use std::env;
use utils::stack_height;
fn main() {
logger::init_log();
let args = env::args().collect::<Vec<_>>();
if args.len() != 3 {
println!("Usage: {} input_file.wasm output_file.wasm", args[0]);
return;
}
let input_file = &args[1];
let output_file = &args[2];
// Loading module
let module = parity_wasm::deserialize_file(&input_file).expect("Module deserialization to succeed");
let result = stack_height::inject_limiter(
module, 1024
).expect("Failed to inject stack height counter");
parity_wasm::serialize_to_file(&output_file, result).expect("Module serialization to succeed")
}
+18
View File
@@ -0,0 +1,18 @@
_default:
just --list
# Run rustfmt and ensure the code meets the expectation of the checks in the CI
format:
cargo +nightly fmt --all
# Run basic checks similar to what the CI does to ensure your code is fine
check:
cargo +nightly fmt --all -- --check
cargo +stable clippy --all-targets --all-features -- -D warnings
# Run the tests
test:
cargo test --all-features
# So you are ready? This runs format, check and test
ready: format check test
+23
View File
@@ -0,0 +1,23 @@
# Basic
hard_tabs = true
max_width = 100
use_small_heuristics = "Max"
# Imports
imports_granularity = "Crate"
reorder_imports = true
# Consistency
newline_style = "Unix"
# Format comments
comment_width = 100
wrap_comments = true
# Misc
chain_width = 80
spaces_around_ranges = false
binop_separator = "Back"
reorder_impl_items = false
match_arm_leading_pipes = "Preserve"
match_arm_blocks = false
match_block_trailing_comma = true
trailing_comma = "Vertical"
trailing_semicolon = false
use_field_init_shorthand = true
-118
View File
@@ -1,118 +0,0 @@
use std;
use super::{
optimize,
pack_instance,
ununderscore_funcs,
externalize_mem,
shrink_unknown_stack,
inject_runtime_type,
PackingError,
OptimizerError,
TargetRuntime,
};
use parity_wasm;
use parity_wasm::elements;
#[derive(Debug)]
pub enum Error {
Encoding(elements::Error),
Packing(PackingError),
Optimizer,
}
impl From<OptimizerError> for Error {
fn from(_err: OptimizerError) -> Self {
Error::Optimizer
}
}
impl From<PackingError> for Error {
fn from(err: PackingError) -> Self {
Error::Packing(err)
}
}
#[derive(Debug, Clone, Copy)]
pub enum SourceTarget {
Emscripten,
Unknown,
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
use self::Error::*;
match *self {
Encoding(ref err) => write!(f, "Encoding error ({})", err),
Optimizer => write!(f, "Optimization error due to missing export section. Pointed wrong file?"),
Packing(ref e) => write!(f, "Packing failed due to module structure error: {}. Sure used correct libraries for building contracts?", e),
}
}
}
fn has_ctor(module: &elements::Module, target_runtime: &TargetRuntime) -> bool {
if let Some(ref section) = module.export_section() {
section.entries().iter().any(|e| target_runtime.create_symbol == e.field())
} else {
false
}
}
pub fn build(
mut module: elements::Module,
source_target: SourceTarget,
runtime_type_version: Option<([u8; 4], u32)>,
public_api_entries: &[&str],
enforce_stack_adjustment: bool,
stack_size: u32,
skip_optimization: bool,
target_runtime: &TargetRuntime,
) -> Result<(elements::Module, Option<elements::Module>), Error> {
if let SourceTarget::Emscripten = source_target {
module = ununderscore_funcs(module);
}
if let SourceTarget::Unknown = source_target {
// 49152 is 48kb!
if enforce_stack_adjustment {
assert!(stack_size <= 1024*1024);
let (new_module, new_stack_top) = shrink_unknown_stack(module, 1024 * 1024 - stack_size);
module = new_module;
let mut stack_top_page = new_stack_top / 65536;
if new_stack_top % 65536 > 0 { stack_top_page += 1 };
module = externalize_mem(module, Some(stack_top_page), 16);
} else {
module = externalize_mem(module, None, 16);
}
}
if let Some(runtime_type_version) = runtime_type_version {
let (runtime_type, runtime_version) = runtime_type_version;
module = inject_runtime_type(module, runtime_type, runtime_version);
}
let mut ctor_module = module.clone();
let mut public_api_entries = public_api_entries.to_vec();
public_api_entries.push(target_runtime.call_symbol);
if !skip_optimization {
optimize(
&mut module,
public_api_entries,
)?;
}
if has_ctor(&ctor_module, target_runtime) {
if !skip_optimization {
optimize(&mut ctor_module, vec![target_runtime.create_symbol])?;
}
let ctor_module = pack_instance(
parity_wasm::serialize(module.clone()).map_err(Error::Encoding)?,
ctor_module.clone(),
target_runtime,
)?;
Ok((module, Some(ctor_module)))
} else {
Ok((module, None))
}
}
+161
View File
@@ -0,0 +1,161 @@
use alloc::{format, vec::Vec};
use parity_wasm::elements;
/// Export all declared mutable globals as `prefix_index`.
///
/// This will export all internal mutable globals under the name of
/// concat(`prefix`, `"_"`, `i`) where i is the index inside the range of
/// [0..total number of internal mutable globals].
pub fn export_mutable_globals(module: &mut elements::Module, prefix: &str) {
let exports = global_section(module)
.map(|section| {
section
.entries()
.iter()
.enumerate()
.filter_map(
|(index, global)| {
if global.global_type().is_mutable() {
Some(index)
} else {
None
}
},
)
.collect::<Vec<_>>()
})
.unwrap_or_default();
if module.export_section().is_none() {
module
.sections_mut()
.push(elements::Section::Export(elements::ExportSection::default()));
}
for (symbol_index, export) in exports.into_iter().enumerate() {
let new_entry = elements::ExportEntry::new(
format!("{}_{}", prefix, symbol_index),
elements::Internal::Global(
(module.import_count(elements::ImportCountType::Global) + export) as _,
),
);
export_section(module)
.expect("added above if does not exists")
.entries_mut()
.push(new_entry);
}
}
fn export_section(module: &mut elements::Module) -> Option<&mut elements::ExportSection> {
for section in module.sections_mut() {
if let elements::Section::Export(sect) = section {
return Some(sect)
}
}
None
}
fn global_section(module: &mut elements::Module) -> Option<&mut elements::GlobalSection> {
for section in module.sections_mut() {
if let elements::Section::Global(sect) = section {
return Some(sect)
}
}
None
}
#[cfg(test)]
mod tests {
use super::export_mutable_globals;
use parity_wasm::elements;
fn parse_wat(source: &str) -> elements::Module {
let module_bytes = wat::parse_str(source).unwrap();
wasmparser::validate(&module_bytes).unwrap();
elements::deserialize_buffer(module_bytes.as_ref()).expect("failed to parse module")
}
macro_rules! test_export_global {
(name = $name:ident; input = $input:expr; expected = $expected:expr) => {
#[test]
fn $name() {
let mut input_module = parse_wat($input);
let expected_module = parse_wat($expected);
export_mutable_globals(&mut input_module, "exported_internal_global");
let actual_bytes = elements::serialize(input_module)
.expect("injected module must have a function body");
let expected_bytes = elements::serialize(expected_module)
.expect("injected module must have a function body");
let actual_wat = wasmprinter::print_bytes(actual_bytes).unwrap();
let expected_wat = wasmprinter::print_bytes(expected_bytes).unwrap();
if actual_wat != expected_wat {
for diff in diff::lines(&expected_wat, &actual_wat) {
match diff {
diff::Result::Left(l) => println!("-{}", l),
diff::Result::Both(l, _) => println!(" {}", l),
diff::Result::Right(r) => println!("+{}", r),
}
}
panic!()
}
}
};
}
test_export_global! {
name = simple;
input = r#"
(module
(global (;0;) (mut i32) (i32.const 1))
(global (;1;) (mut i32) (i32.const 0)))
"#;
expected = r#"
(module
(global (;0;) (mut i32) (i32.const 1))
(global (;1;) (mut i32) (i32.const 0))
(export "exported_internal_global_0" (global 0))
(export "exported_internal_global_1" (global 1)))
"#
}
test_export_global! {
name = with_import;
input = r#"
(module
(import "env" "global" (global $global i64))
(global (;0;) (mut i32) (i32.const 1))
(global (;1;) (mut i32) (i32.const 0)))
"#;
expected = r#"
(module
(import "env" "global" (global $global i64))
(global (;0;) (mut i32) (i32.const 1))
(global (;1;) (mut i32) (i32.const 0))
(export "exported_internal_global_0" (global 1))
(export "exported_internal_global_1" (global 2)))
"#
}
test_export_global! {
name = with_import_and_some_are_immutable;
input = r#"
(module
(import "env" "global" (global $global i64))
(global (;0;) i32 (i32.const 1))
(global (;1;) (mut i32) (i32.const 0)))
"#;
expected = r#"
(module
(import "env" "global" (global $global i64))
(global (;0;) i32 (i32.const 1))
(global (;1;) (mut i32) (i32.const 0))
(export "exported_internal_global_0" (global 2)))
"#
}
}
-198
View File
@@ -1,198 +0,0 @@
use std::string::String;
use std::vec::Vec;
use std::borrow::ToOwned;
use parity_wasm::{elements, builder};
use optimizer::{import_section, export_section};
use byteorder::{LittleEndian, ByteOrder};
type Insertion = (usize, u32, u32, String);
pub fn update_call_index(instructions: &mut elements::Instructions, original_imports: usize, inserts: &[Insertion]) {
use parity_wasm::elements::Instruction::*;
for instruction in instructions.elements_mut().iter_mut() {
if let &mut Call(ref mut call_index) = instruction {
if let Some(pos) = inserts.iter().position(|x| x.1 == *call_index) {
*call_index = (original_imports + pos) as u32;
} else if *call_index as usize > original_imports {
*call_index += inserts.len() as u32;
}
}
}
}
pub fn memory_section<'a>(module: &'a mut elements::Module) -> Option<&'a mut elements::MemorySection> {
for section in module.sections_mut() {
if let &mut elements::Section::Memory(ref mut sect) = section {
return Some(sect);
}
}
None
}
pub fn externalize_mem(mut module: elements::Module, adjust_pages: Option<u32>, max_pages: u32) -> elements::Module {
let mut entry = memory_section(&mut module)
.expect("Memory section to exist")
.entries_mut()
.pop()
.expect("Own memory entry to exist in memory section");
if let Some(adjust_pages) = adjust_pages {
assert!(adjust_pages <= max_pages);
entry = elements::MemoryType::new(adjust_pages, Some(max_pages));
}
if entry.limits().maximum().is_none() {
entry = elements::MemoryType::new(entry.limits().initial(), Some(max_pages));
}
let mut builder = builder::from_module(module);
builder.push_import(
elements::ImportEntry::new(
"env".to_owned(),
"memory".to_owned(),
elements::External::Memory(entry),
)
);
builder.build()
}
fn foreach_public_func_name<F>(mut module: elements::Module, f: F) -> elements::Module
where F: Fn(&mut String)
{
import_section(&mut module).map(|is| {
for entry in is.entries_mut() {
if let elements::External::Function(_) = *entry.external() {
f(entry.field_mut())
}
}
});
export_section(&mut module).map(|es| {
for entry in es.entries_mut() {
if let elements::Internal::Function(_) = *entry.internal() {
f(entry.field_mut())
}
}
});
module
}
pub fn underscore_funcs(module: elements::Module) -> elements::Module {
foreach_public_func_name(module, |n| n.insert(0, '_'))
}
pub fn ununderscore_funcs(module: elements::Module) -> elements::Module {
foreach_public_func_name(module, |n| { n.remove(0); })
}
pub fn shrink_unknown_stack(
mut module: elements::Module,
// for example, `shrink_amount = (1MB - 64KB)` will limit stack to 64KB
shrink_amount: u32,
) -> (elements::Module, u32) {
let mut new_stack_top = 0;
for section in module.sections_mut() {
match section {
&mut elements::Section::Data(ref mut data_section) => {
for ref mut data_segment in data_section.entries_mut() {
if data_segment.offset().code() == &[elements::Instruction::I32Const(4), elements::Instruction::End] {
assert_eq!(data_segment.value().len(), 4);
let current_val = LittleEndian::read_u32(data_segment.value());
let new_val = current_val - shrink_amount;
LittleEndian::write_u32(data_segment.value_mut(), new_val);
new_stack_top = new_val;
}
}
},
_ => continue
}
}
(module, new_stack_top)
}
pub fn externalize(
module: elements::Module,
replaced_funcs: Vec<&str>,
) -> elements::Module {
// Save import functions number for later
let import_funcs_total = module
.import_section().expect("Import section to exist")
.entries()
.iter()
.filter(|e| if let &elements::External::Function(_) = e.external() { true } else { false })
.count();
// First, we find functions indices that are to be rewired to externals
// Triple is (function_index (callable), type_index, function_name)
let mut replaces: Vec<Insertion> = replaced_funcs
.into_iter()
.filter_map(|f| {
let export = module
.export_section().expect("Export section to exist")
.entries().iter().enumerate()
.find(|&(_, entry)| entry.field() == f)
.expect("All functions of interest to exist");
if let &elements::Internal::Function(func_idx) = export.1.internal() {
let type_ref = module
.function_section().expect("Functions section to exist")
.entries()[func_idx as usize - import_funcs_total]
.type_ref();
Some((export.0, func_idx, type_ref, export.1.field().to_owned()))
} else {
None
}
})
.collect();
replaces.sort_by_key(|e| e.0);
// Second, we duplicate them as import definitions
let mut mbuilder = builder::from_module(module);
for &(_, _, type_ref, ref field) in replaces.iter() {
mbuilder.push_import(
builder::import()
.module("env")
.field(field)
.external().func(type_ref)
.build()
);
}
// Back to mutable access
let mut module = mbuilder.build();
// Third, rewire all calls to imported functions and update all other calls indices
for section in module.sections_mut() {
match section {
&mut elements::Section::Code(ref mut code_section) => {
for ref mut func_body in code_section.bodies_mut() {
update_call_index(func_body.code_mut(), import_funcs_total, &replaces);
}
},
&mut elements::Section::Export(ref mut export_section) => {
for ref mut export in export_section.entries_mut() {
if let &mut elements::Internal::Function(ref mut func_index) = export.internal_mut() {
if *func_index >= import_funcs_total as u32 { *func_index += replaces.len() as u32; }
}
}
},
&mut elements::Section::Element(ref mut elements_section) => {
for ref mut segment in elements_section.entries_mut() {
// update all indirect call addresses initial values
for func_index in segment.members_mut() {
if *func_index >= import_funcs_total as u32 { *func_index += replaces.len() as u32; }
}
}
},
_ => { }
}
}
module
}
-608
View File
@@ -1,608 +0,0 @@
use std::vec::Vec;
use parity_wasm::{elements, builder};
use rules;
pub fn update_call_index(instructions: &mut elements::Instructions, inserted_index: u32) {
use parity_wasm::elements::Instruction::*;
for instruction in instructions.elements_mut().iter_mut() {
if let &mut Call(ref mut call_index) = instruction {
if *call_index >= inserted_index { *call_index += 1}
}
}
}
/// A block of code represented by it's start position and cost.
///
/// The block typically starts with instructions such as `loop`, `block`, `if`, etc.
///
/// An example of block:
///
/// ```ignore
/// loop
/// i32.const 1
/// get_local 0
/// i32.sub
/// tee_local 0
/// br_if 0
/// end
/// ```
///
/// The start of the block is `i32.const 1`.
///
#[derive(Debug)]
struct BlockEntry {
/// Index of the first instruction (aka `Opcode`) in the block.
start_pos: usize,
/// Sum of costs of all instructions until end of the block.
cost: u32,
}
struct Counter {
/// All blocks in the order of theirs start position.
blocks: Vec<BlockEntry>,
// Stack of blocks. Each element is an index to a `self.blocks` vector.
stack: Vec<usize>,
}
impl Counter {
fn new() -> Counter {
Counter {
stack: Vec::new(),
blocks: Vec::new(),
}
}
/// Begin a new block.
fn begin(&mut self, cursor: usize) {
let block_idx = self.blocks.len();
self.blocks.push(BlockEntry {
start_pos: cursor,
cost: 1,
});
self.stack.push(block_idx);
}
/// Finalize the current block.
///
/// Finalized blocks have final cost which will not change later.
fn finalize(&mut self) -> Result<(), ()> {
self.stack.pop().ok_or_else(|| ())?;
Ok(())
}
/// Increment the cost of the current block by the specified value.
fn increment(&mut self, val: u32) -> Result<(), ()> {
let stack_top = self.stack.last_mut().ok_or_else(|| ())?;
let top_block = self.blocks.get_mut(*stack_top).ok_or_else(|| ())?;
top_block.cost = top_block.cost.checked_add(val).ok_or_else(|| ())?;
Ok(())
}
}
fn inject_grow_counter(instructions: &mut elements::Instructions, grow_counter_func: u32) -> usize {
use parity_wasm::elements::Instruction::*;
let mut counter = 0;
for instruction in instructions.elements_mut() {
if let GrowMemory(_) = *instruction {
*instruction = Call(grow_counter_func);
counter += 1;
}
}
counter
}
fn add_grow_counter(module: elements::Module, rules: &rules::Set, gas_func: u32) -> elements::Module {
use parity_wasm::elements::Instruction::*;
let mut b = builder::from_module(module);
b.push_function(
builder::function()
.signature().params().i32().build().with_return_type(Some(elements::ValueType::I32)).build()
.body()
.with_instructions(elements::Instructions::new(vec![
GetLocal(0),
GetLocal(0),
I32Const(rules.grow_cost() as i32),
I32Mul,
// todo: there should be strong guarantee that it does not return anything on stack?
Call(gas_func),
GrowMemory(0),
End,
]))
.build()
.build()
);
b.build()
}
pub fn inject_counter(
instructions: &mut elements::Instructions,
rules: &rules::Set,
gas_func: u32,
) -> Result<(), ()> {
use parity_wasm::elements::Instruction::*;
let mut counter = Counter::new();
// Begin an implicit function (i.e. `func...end`) block.
counter.begin(0);
for cursor in 0..instructions.elements().len() {
let instruction = &instructions.elements()[cursor];
match *instruction {
Block(_) | If(_) | Loop(_) => {
// Increment previous block with the cost of the current opcode.
let instruction_cost = rules.process(instruction)?;
counter.increment(instruction_cost)?;
// Begin new block. The cost of the following opcodes until `End` or `Else` will
// be included into this block.
counter.begin(cursor + 1);
}
End => {
// Just finalize current block.
counter.finalize()?;
},
Else => {
// `Else` opcode is being encountered. So the case we are looking at:
//
// if
// ...
// else <-- cursor
// ...
// end
//
// Finalize the current block ('then' part of the if statement),
// and begin another one for the 'else' part.
counter.finalize()?;
counter.begin(cursor + 1);
}
_ => {
// An ordinal non control flow instruction. Just increment the cost of the current block.
let instruction_cost = rules.process(instruction)?;
counter.increment(instruction_cost)?;
}
}
}
// Then insert metering calls.
let mut cumulative_offset = 0;
for block in counter.blocks {
let effective_pos = block.start_pos + cumulative_offset;
instructions.elements_mut().insert(effective_pos, I32Const(block.cost as i32));
instructions.elements_mut().insert(effective_pos+1, Call(gas_func));
// Take into account these two inserted instructions.
cumulative_offset += 2;
}
Ok(())
}
/// Injects gas counter.
///
/// Can only fail if encounters operation forbidden by gas rules,
/// in this case it returns error with the original module.
pub fn inject_gas_counter(module: elements::Module, rules: &rules::Set)
-> Result<elements::Module, elements::Module>
{
// Injecting gas counting external
let mut mbuilder = builder::from_module(module);
let import_sig = mbuilder.push_signature(
builder::signature()
.param().i32()
.build_sig()
);
mbuilder.push_import(
builder::import()
.module("env")
.field("gas")
.external().func(import_sig)
.build()
);
// back to plain module
let mut module = mbuilder.build();
// calculate actual function index of the imported definition
// (substract all imports that are NOT functions)
let gas_func = module.import_count(elements::ImportCountType::Function) as u32 - 1;
let total_func = module.functions_space() as u32;
let mut need_grow_counter = false;
let mut error = false;
// Updating calling addresses (all calls to function index >= `gas_func` should be incremented)
for section in module.sections_mut() {
match section {
&mut elements::Section::Code(ref mut code_section) => {
for ref mut func_body in code_section.bodies_mut() {
update_call_index(func_body.code_mut(), gas_func);
if let Err(_) = inject_counter(func_body.code_mut(), rules, gas_func) {
error = true;
break;
}
if rules.grow_cost() > 0 {
if inject_grow_counter(func_body.code_mut(), total_func) > 0 {
need_grow_counter = true;
}
}
}
},
&mut elements::Section::Export(ref mut export_section) => {
for ref mut export in export_section.entries_mut() {
if let &mut elements::Internal::Function(ref mut func_index) = export.internal_mut() {
if *func_index >= gas_func { *func_index += 1}
}
}
},
&mut elements::Section::Element(ref mut elements_section) => {
for ref mut segment in elements_section.entries_mut() {
// update all indirect call addresses initial values
for func_index in segment.members_mut() {
if *func_index >= gas_func { *func_index += 1}
}
}
},
&mut elements::Section::Start(ref mut start_idx) => {
if *start_idx >= gas_func { *start_idx += 1}
},
_ => { }
}
}
if error { return Err(module); }
if need_grow_counter { Ok(add_grow_counter(module, rules, gas_func)) } else { Ok(module) }
}
#[cfg(test)]
mod tests {
extern crate wabt;
use parity_wasm::{serialize, builder, elements};
use super::*;
use rules;
#[test]
fn simple_grow() {
use parity_wasm::elements::Instruction::*;
let module = builder::module()
.global()
.value_type().i32()
.build()
.function()
.signature().param().i32().build()
.body()
.with_instructions(elements::Instructions::new(
vec![
GetGlobal(0),
GrowMemory(0),
End
]
))
.build()
.build()
.build();
let injected_module = inject_gas_counter(module, &rules::Set::default().with_grow_cost(10000)).unwrap();
assert_eq!(
&vec![
I32Const(3),
Call(0),
GetGlobal(0),
Call(2),
End
][..],
injected_module
.code_section().expect("function section should exist").bodies()[0]
.code().elements()
);
assert_eq!(
&vec![
GetLocal(0),
GetLocal(0),
I32Const(10000),
I32Mul,
Call(0),
GrowMemory(0),
End,
][..],
injected_module
.code_section().expect("function section should exist").bodies()[1]
.code().elements()
);
let binary = serialize(injected_module).expect("serialization failed");
self::wabt::wasm2wat(&binary).unwrap();
}
#[test]
fn grow_no_gas_no_track() {
use parity_wasm::elements::Instruction::*;
let module = builder::module()
.global()
.value_type().i32()
.build()
.function()
.signature().param().i32().build()
.body()
.with_instructions(elements::Instructions::new(
vec![
GetGlobal(0),
GrowMemory(0),
End
]
))
.build()
.build()
.build();
let injected_module = inject_gas_counter(module, &rules::Set::default()).unwrap();
assert_eq!(
&vec![
I32Const(3),
Call(0),
GetGlobal(0),
GrowMemory(0),
End
][..],
injected_module
.code_section().expect("function section should exist").bodies()[0]
.code().elements()
);
assert_eq!(injected_module.functions_space(), 2);
let binary = serialize(injected_module).expect("serialization failed");
self::wabt::wasm2wat(&binary).unwrap();
}
#[test]
fn simple() {
use parity_wasm::elements::Instruction::*;
let module = builder::module()
.global()
.value_type().i32()
.build()
.function()
.signature().param().i32().build()
.body()
.with_instructions(elements::Instructions::new(
vec![
GetGlobal(0),
End
]
))
.build()
.build()
.build();
let injected_module = inject_gas_counter(module, &Default::default()).unwrap();
assert_eq!(
&vec![
I32Const(2),
Call(0),
GetGlobal(0),
End
][..],
injected_module
.code_section().expect("function section should exist").bodies()[0]
.code().elements()
);
}
#[test]
fn nested() {
use parity_wasm::elements::Instruction::*;
let module = builder::module()
.global()
.value_type().i32()
.build()
.function()
.signature().param().i32().build()
.body()
.with_instructions(elements::Instructions::new(
vec![
GetGlobal(0),
Block(elements::BlockType::NoResult),
GetGlobal(0),
GetGlobal(0),
GetGlobal(0),
End,
GetGlobal(0),
End
]
))
.build()
.build()
.build();
let injected_module = inject_gas_counter(module, &Default::default()).unwrap();
assert_eq!(
&vec![
I32Const(4),
Call(0),
GetGlobal(0),
Block(elements::BlockType::NoResult),
I32Const(4),
Call(0),
GetGlobal(0),
GetGlobal(0),
GetGlobal(0),
End,
GetGlobal(0),
End
][..],
injected_module
.code_section().expect("function section should exist").bodies()[0]
.code().elements()
);
}
#[test]
fn ifelse() {
use parity_wasm::elements::Instruction::*;
let module = builder::module()
.global()
.value_type().i32()
.build()
.function()
.signature().param().i32().build()
.body()
.with_instructions(elements::Instructions::new(
vec![
GetGlobal(0),
If(elements::BlockType::NoResult),
GetGlobal(0),
GetGlobal(0),
GetGlobal(0),
Else,
GetGlobal(0),
GetGlobal(0),
End,
GetGlobal(0),
End
]
))
.build()
.build()
.build();
let injected_module = inject_gas_counter(module, &Default::default()).unwrap();
assert_eq!(
&vec![
I32Const(4),
Call(0),
GetGlobal(0),
If(elements::BlockType::NoResult),
I32Const(4),
Call(0),
GetGlobal(0),
GetGlobal(0),
GetGlobal(0),
Else,
I32Const(3),
Call(0),
GetGlobal(0),
GetGlobal(0),
End,
GetGlobal(0),
End
][..],
injected_module
.code_section().expect("function section should exist").bodies()[0]
.code().elements()
);
}
#[test]
fn call_index() {
use parity_wasm::elements::Instruction::*;
let module = builder::module()
.global()
.value_type().i32()
.build()
.function()
.signature().param().i32().build()
.body().build()
.build()
.function()
.signature().param().i32().build()
.body()
.with_instructions(elements::Instructions::new(
vec![
Call(0),
If(elements::BlockType::NoResult),
Call(0),
Call(0),
Call(0),
Else,
Call(0),
Call(0),
End,
Call(0),
End
]
))
.build()
.build()
.build();
let injected_module = inject_gas_counter(module, &Default::default()).unwrap();
assert_eq!(
&vec![
I32Const(4),
Call(0),
Call(1),
If(elements::BlockType::NoResult),
I32Const(4),
Call(0),
Call(1),
Call(1),
Call(1),
Else,
I32Const(3),
Call(0),
Call(1),
Call(1),
End,
Call(1),
End
][..],
injected_module
.code_section().expect("function section should exist").bodies()[1]
.code().elements()
);
}
#[test]
fn forbidden() {
use parity_wasm::elements::Instruction::*;
let module = builder::module()
.global()
.value_type().i32()
.build()
.function()
.signature().param().i32().build()
.body()
.with_instructions(elements::Instructions::new(
vec![
F32Const(555555),
End
]
))
.build()
.build()
.build();
let rules = rules::Set::default().with_forbidden_floats();
if let Err(_) = inject_gas_counter(module, &rules) { }
else { panic!("Should be error because of the forbidden operation")}
}
}
File diff suppressed because it is too large Load Diff
+353
View File
@@ -0,0 +1,353 @@
//! This module is used to validate the correctness of the gas metering algorithm.
//!
//! Since the gas metering algorithm is complex, this checks correctness by fuzzing. The testing
//! strategy is to generate random, valid Wasm modules using Binaryen's translate-to-fuzz
//! functionality, then ensure for all functions defined, in all execution paths though the
//! function body that do not trap that the amount of gas charged by the proposed metering
//! instructions is correct. This is done by constructing a control flow graph and exhaustively
//! searching through all paths, which may take exponential time in the size of the function body in
//! the worst case.
use super::{ConstantCostRules, MeteredBlock, Rules};
use parity_wasm::elements::{FuncBody, Instruction};
use std::collections::BTreeMap as Map;
/// An ID for a node in a ControlFlowGraph.
type NodeId = usize;
/// A node in a control flow graph is commonly known as a basic block. This is a sequence of
/// operations that are always executed sequentially.
#[derive(Debug, Default)]
struct ControlFlowNode {
/// The index of the first instruction in the basic block. This is only used for debugging.
first_instr_pos: Option<usize>,
/// The actual gas cost of executing all instructions in the basic block.
actual_cost: u32,
/// The amount of gas charged by the injected metering instructions within this basic block.
charged_cost: u32,
/// Whether there are any other nodes in the graph that loop back to this one. Every cycle in
/// the control flow graph contains at least one node with this flag set.
is_loop_target: bool,
/// Edges in the "forward" direction of the graph. The graph of nodes and their forward edges
/// forms a directed acyclic graph (DAG).
forward_edges: Vec<NodeId>,
/// Edges in the "backwards" direction. These edges form cycles in the graph.
loopback_edges: Vec<NodeId>,
}
/// A control flow graph where nodes are basic blocks and edges represent possible transitions
/// between them in execution flow. The graph has two types of edges, forward and loop-back edges.
/// The subgraph with only the forward edges forms a directed acyclic graph (DAG); including the
/// loop-back edges introduces cycles.
#[derive(Debug)]
pub struct ControlFlowGraph {
nodes: Vec<ControlFlowNode>,
}
impl ControlFlowGraph {
fn new() -> Self {
ControlFlowGraph { nodes: Vec::new() }
}
fn get_node(&self, node_id: NodeId) -> &ControlFlowNode {
self.nodes.get(node_id).unwrap()
}
fn get_node_mut(&mut self, node_id: NodeId) -> &mut ControlFlowNode {
self.nodes.get_mut(node_id).unwrap()
}
fn add_node(&mut self) -> NodeId {
self.nodes.push(ControlFlowNode::default());
self.nodes.len() - 1
}
fn increment_actual_cost(&mut self, node_id: NodeId, cost: u32) {
self.get_node_mut(node_id).actual_cost += cost;
}
fn increment_charged_cost(&mut self, node_id: NodeId, cost: u32) {
self.get_node_mut(node_id).charged_cost += cost;
}
fn set_first_instr_pos(&mut self, node_id: NodeId, first_instr_pos: usize) {
self.get_node_mut(node_id).first_instr_pos = Some(first_instr_pos)
}
fn new_edge(&mut self, from_id: NodeId, target_frame: &ControlFrame) {
if target_frame.is_loop {
self.new_loopback_edge(from_id, target_frame.entry_node);
} else {
self.new_forward_edge(from_id, target_frame.exit_node);
}
}
fn new_forward_edge(&mut self, from_id: NodeId, to_id: NodeId) {
self.get_node_mut(from_id).forward_edges.push(to_id)
}
fn new_loopback_edge(&mut self, from_id: NodeId, to_id: NodeId) {
self.get_node_mut(from_id).loopback_edges.push(to_id);
self.get_node_mut(to_id).is_loop_target = true;
}
}
/// A control frame is opened upon entry into a function and by the `block`, `if`, and `loop`
/// instructions and is closed by `end` instructions.
struct ControlFrame {
is_loop: bool,
entry_node: NodeId,
exit_node: NodeId,
active_node: NodeId,
}
impl ControlFrame {
fn new(entry_node_id: NodeId, exit_node_id: NodeId, is_loop: bool) -> Self {
ControlFrame {
is_loop,
entry_node: entry_node_id,
exit_node: exit_node_id,
active_node: entry_node_id,
}
}
}
/// Construct a control flow graph from a function body and the metered blocks computed for it.
///
/// This assumes that the function body has been validated already, otherwise this may panic.
fn build_control_flow_graph(
body: &FuncBody,
rules: &impl Rules,
blocks: &[MeteredBlock],
) -> Result<ControlFlowGraph, ()> {
let mut graph = ControlFlowGraph::new();
let entry_node_id = graph.add_node();
let terminal_node_id = graph.add_node();
graph.set_first_instr_pos(entry_node_id, 0);
let mut stack = vec![ControlFrame::new(entry_node_id, terminal_node_id, false)];
let mut metered_blocks_iter = blocks.iter().peekable();
for (cursor, instruction) in body.code().elements().iter().enumerate() {
let active_node_id = stack
.last()
.expect("module is valid by pre-condition; control stack must not be empty; qed")
.active_node;
// Increment the charged cost if there are metering instructions to be inserted here.
let apply_block =
metered_blocks_iter.peek().map_or(false, |block| block.start_pos == cursor);
if apply_block {
let next_metered_block =
metered_blocks_iter.next().expect("peek returned an item; qed");
graph.increment_charged_cost(active_node_id, next_metered_block.cost);
}
let instruction_cost = rules.instruction_cost(instruction).ok_or(())?;
match instruction {
Instruction::Block(_) => {
graph.increment_actual_cost(active_node_id, instruction_cost);
let exit_node_id = graph.add_node();
stack.push(ControlFrame::new(active_node_id, exit_node_id, false));
},
Instruction::If(_) => {
graph.increment_actual_cost(active_node_id, instruction_cost);
let then_node_id = graph.add_node();
let exit_node_id = graph.add_node();
stack.push(ControlFrame::new(then_node_id, exit_node_id, false));
graph.new_forward_edge(active_node_id, then_node_id);
graph.set_first_instr_pos(then_node_id, cursor + 1);
},
Instruction::Loop(_) => {
graph.increment_actual_cost(active_node_id, instruction_cost);
let loop_node_id = graph.add_node();
let exit_node_id = graph.add_node();
stack.push(ControlFrame::new(loop_node_id, exit_node_id, true));
graph.new_forward_edge(active_node_id, loop_node_id);
graph.set_first_instr_pos(loop_node_id, cursor + 1);
},
Instruction::Else => {
let active_frame_idx = stack.len() - 1;
let prev_frame_idx = stack.len() - 2;
let else_node_id = graph.add_node();
stack[active_frame_idx].active_node = else_node_id;
let prev_node_id = stack[prev_frame_idx].active_node;
graph.new_forward_edge(prev_node_id, else_node_id);
graph.set_first_instr_pos(else_node_id, cursor + 1);
},
Instruction::End => {
let closing_frame = stack.pop()
.expect("module is valid by pre-condition; ends correspond to control stack frames; qed");
graph.new_forward_edge(active_node_id, closing_frame.exit_node);
graph.set_first_instr_pos(closing_frame.exit_node, cursor + 1);
if let Some(active_frame) = stack.last_mut() {
active_frame.active_node = closing_frame.exit_node;
}
},
Instruction::Br(label) => {
graph.increment_actual_cost(active_node_id, instruction_cost);
let active_frame_idx = stack.len() - 1;
let target_frame_idx = active_frame_idx - (*label as usize);
graph.new_edge(active_node_id, &stack[target_frame_idx]);
// Next instruction is unreachable, but carry on anyway.
let new_node_id = graph.add_node();
stack[active_frame_idx].active_node = new_node_id;
graph.set_first_instr_pos(new_node_id, cursor + 1);
},
Instruction::BrIf(label) => {
graph.increment_actual_cost(active_node_id, instruction_cost);
let active_frame_idx = stack.len() - 1;
let target_frame_idx = active_frame_idx - (*label as usize);
graph.new_edge(active_node_id, &stack[target_frame_idx]);
let new_node_id = graph.add_node();
stack[active_frame_idx].active_node = new_node_id;
graph.new_forward_edge(active_node_id, new_node_id);
graph.set_first_instr_pos(new_node_id, cursor + 1);
},
Instruction::BrTable(br_table_data) => {
graph.increment_actual_cost(active_node_id, instruction_cost);
let active_frame_idx = stack.len() - 1;
for &label in [br_table_data.default].iter().chain(br_table_data.table.iter()) {
let target_frame_idx = active_frame_idx - (label as usize);
graph.new_edge(active_node_id, &stack[target_frame_idx]);
}
let new_node_id = graph.add_node();
stack[active_frame_idx].active_node = new_node_id;
graph.set_first_instr_pos(new_node_id, cursor + 1);
},
Instruction::Return => {
graph.increment_actual_cost(active_node_id, instruction_cost);
graph.new_forward_edge(active_node_id, terminal_node_id);
let active_frame_idx = stack.len() - 1;
let new_node_id = graph.add_node();
stack[active_frame_idx].active_node = new_node_id;
graph.set_first_instr_pos(new_node_id, cursor + 1);
},
_ => graph.increment_actual_cost(active_node_id, instruction_cost),
}
}
assert!(stack.is_empty());
Ok(graph)
}
/// Exhaustively search through all paths in the control flow graph, starting from the first node
/// and ensure that 1) all paths with only forward edges ending with the terminal node have an
/// equal total actual gas cost and total charged gas cost, and 2) all cycles beginning with a loop
/// entry point and ending with a node with a loop-back edge to the entry point have equal actual
/// and charged gas costs. If this returns true, then the metered blocks used to construct the
/// control flow graph are correct with respect to the function body.
///
/// In the worst case, this runs in time exponential in the size of the graph.
fn validate_graph_gas_costs(graph: &ControlFlowGraph) -> bool {
fn visit(
graph: &ControlFlowGraph,
node_id: NodeId,
mut total_actual: u32,
mut total_charged: u32,
loop_costs: &mut Map<NodeId, (u32, u32)>,
) -> bool {
let node = graph.get_node(node_id);
total_actual += node.actual_cost;
total_charged += node.charged_cost;
if node.is_loop_target {
loop_costs.insert(node_id, (node.actual_cost, node.charged_cost));
}
if node.forward_edges.is_empty() && total_actual != total_charged {
return false
}
for loop_node_id in node.loopback_edges.iter() {
let (loop_actual, loop_charged) = loop_costs
.get_mut(loop_node_id)
.expect("cannot arrive at loopback edge without visiting loop entry node");
if loop_actual != loop_charged {
return false
}
}
for next_node_id in node.forward_edges.iter() {
if !visit(graph, *next_node_id, total_actual, total_charged, loop_costs) {
return false
}
}
if node.is_loop_target {
loop_costs.remove(&node_id);
}
true
}
// Recursively explore all paths through the execution graph starting from the entry node.
visit(graph, 0, 0, 0, &mut Map::new())
}
/// Validate that the metered blocks are correct with respect to the function body by exhaustively
/// searching all paths through the control flow graph.
///
/// This assumes that the function body has been validated already, otherwise this may panic.
fn validate_metering_injections(
body: &FuncBody,
rules: &impl Rules,
blocks: &[MeteredBlock],
) -> Result<bool, ()> {
let graph = build_control_flow_graph(body, rules, blocks)?;
Ok(validate_graph_gas_costs(&graph))
}
mod tests {
use super::{super::determine_metered_blocks, *};
use binaryen::tools::translate_to_fuzz_mvp;
use parity_wasm::elements;
use rand::{thread_rng, RngCore};
#[test]
fn test_build_control_flow_graph() {
for _ in 0..20 {
let mut rand_input = [0u8; 2048];
thread_rng().fill_bytes(&mut rand_input);
let module_bytes = translate_to_fuzz_mvp(&rand_input).write();
let module: elements::Module = elements::deserialize_buffer(&module_bytes)
.expect("failed to parse Wasm blob generated by translate_to_fuzz");
for func_body in module.code_section().iter().flat_map(|section| section.bodies()) {
let rules = ConstantCostRules::default();
let metered_blocks = determine_metered_blocks(func_body.code(), &rules).unwrap();
let success =
validate_metering_injections(func_body, &rules, &metered_blocks).unwrap();
assert!(success);
}
}
}
}
+8 -58
View File
@@ -1,63 +1,13 @@
#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(not(feature = "std"), feature(alloc))]
#[cfg(not(feature = "std"))]
#[macro_use]
extern crate alloc;
#[macro_use]
extern crate log;
extern crate parity_wasm;
extern crate byteorder;
#[macro_use] extern crate log;
mod export_globals;
pub mod gas_metering;
mod stack_limiter;
pub mod rules;
mod build;
mod optimizer;
mod gas;
mod symbols;
mod ext;
mod pack;
mod runtime_type;
pub mod stack_height;
pub use build::{build, SourceTarget, Error as BuildError};
pub use optimizer::{optimize, Error as OptimizerError};
pub use gas::inject_gas_counter;
pub use ext::{externalize, externalize_mem, underscore_funcs, ununderscore_funcs, shrink_unknown_stack};
pub use pack::{pack_instance, Error as PackingError};
pub use runtime_type::inject_runtime_type;
pub struct TargetRuntime {
pub create_symbol: &'static str,
pub call_symbol: &'static str,
pub return_symbol: &'static str,
}
impl TargetRuntime {
pub fn substrate() -> TargetRuntime {
TargetRuntime {
create_symbol: "deploy",
call_symbol: "call",
return_symbol: "ext_return",
}
}
pub fn pwasm() -> TargetRuntime {
TargetRuntime {
create_symbol: "deploy",
call_symbol: "call",
return_symbol: "ret",
}
}
}
#[cfg(not(feature = "std"))]
mod std {
pub use core::*;
pub use alloc::{vec, string, boxed, borrow};
pub mod collections {
pub use alloc::collections::{BTreeMap, BTreeSet};
}
}
pub use export_globals::export_mutable_globals;
pub use parity_wasm;
pub use stack_limiter::{compute_stack_cost, inject as inject_stack_limiter};
-599
View File
@@ -1,599 +0,0 @@
#[cfg(features = "std")]
use std::collections::{HashSet as Set};
#[cfg(not(features = "std"))]
use std::collections::{BTreeSet as Set};
use std::vec::Vec;
use parity_wasm::elements;
use symbols::{Symbol, expand_symbols, push_code_symbols, resolve_function};
#[derive(Debug)]
pub enum Error {
/// Since optimizer starts with export entries, export
/// section is supposed to exist.
NoExportSection,
}
pub fn optimize(
module: &mut elements::Module, // Module to optimize
used_exports: Vec<&str>, // List of only exports that will be usable after optimization
) -> Result<(), Error> {
// WebAssembly exports optimizer
// Motivation: emscripten compiler backend compiles in many unused exports
// which in turn compile in unused imports and leaves unused functions
// Algo starts from the top, listing all items that should stay
let mut stay = Set::new();
for (index, entry) in module.export_section().ok_or(Error::NoExportSection)?.entries().iter().enumerate() {
if used_exports.iter().find(|e| **e == entry.field()).is_some() {
stay.insert(Symbol::Export(index));
}
}
// If there is start function in module, it should stary
module.start_section().map(|ss| stay.insert(resolve_function(&module, ss)));
// All symbols used in data/element segments are also should be preserved
let mut init_symbols = Vec::new();
if let Some(data_section) = module.data_section() {
for segment in data_section.entries() {
push_code_symbols(&module, segment.offset().code(), &mut init_symbols);
}
}
if let Some(elements_section) = module.elements_section() {
for segment in elements_section.entries() {
push_code_symbols(&module, segment.offset().code(), &mut init_symbols);
for func_index in segment.members() {
stay.insert(resolve_function(&module, *func_index));
}
}
}
for symbol in init_symbols.drain(..) { stay.insert(symbol); }
// Call function which will traverse the list recursively, filling stay with all symbols
// that are already used by those which already there
expand_symbols(module, &mut stay);
for symbol in stay.iter() {
trace!("symbol to stay: {:?}", symbol);
}
// Keep track of referreable symbols to rewire calls/globals
let mut eliminated_funcs = Vec::new();
let mut eliminated_globals = Vec::new();
let mut eliminated_types = Vec::new();
// First, iterate through types
let mut index = 0;
let mut old_index = 0;
{
loop {
if type_section(module).map(|section| section.types_mut().len()).unwrap_or(0) == index { break; }
if stay.contains(&Symbol::Type(old_index)) {
index += 1;
} else {
type_section(module)
.expect("If type section does not exists, the loop will break at the beginning of first iteration")
.types_mut().remove(index);
eliminated_types.push(old_index);
trace!("Eliminated type({})", old_index);
}
old_index += 1;
}
}
// Second, iterate through imports
let mut top_funcs = 0;
let mut top_globals = 0;
index = 0;
old_index = 0;
if let Some(imports) = import_section(module) {
loop {
let mut remove = false;
match imports.entries()[index].external() {
&elements::External::Function(_) => {
if stay.contains(&Symbol::Import(old_index)) {
index += 1;
} else {
remove = true;
eliminated_funcs.push(top_funcs);
trace!("Eliminated import({}) func({}, {})", old_index, top_funcs, imports.entries()[index].field());
}
top_funcs += 1;
},
&elements::External::Global(_) => {
if stay.contains(&Symbol::Import(old_index)) {
index += 1;
} else {
remove = true;
eliminated_globals.push(top_globals);
trace!("Eliminated import({}) global({}, {})", old_index, top_globals, imports.entries()[index].field());
}
top_globals += 1;
},
_ => {
index += 1;
}
}
if remove {
imports.entries_mut().remove(index);
}
old_index += 1;
if index == imports.entries().len() { break; }
}
}
// Third, iterate through globals
if let Some(globals) = global_section(module) {
index = 0;
old_index = 0;
loop {
if globals.entries_mut().len() == index { break; }
if stay.contains(&Symbol::Global(old_index)) {
index += 1;
} else {
globals.entries_mut().remove(index);
eliminated_globals.push(top_globals + old_index);
trace!("Eliminated global({})", top_globals + old_index);
}
old_index += 1;
}
}
// Forth, delete orphaned functions
if function_section(module).is_some() && code_section(module).is_some() {
index = 0;
old_index = 0;
loop {
if function_section(module).expect("Functons section to exist").entries_mut().len() == index { break; }
if stay.contains(&Symbol::Function(old_index)) {
index += 1;
} else {
function_section(module).expect("Functons section to exist").entries_mut().remove(index);
code_section(module).expect("Code section to exist").bodies_mut().remove(index);
eliminated_funcs.push(top_funcs + old_index);
trace!("Eliminated function({})", top_funcs + old_index);
}
old_index += 1;
}
}
// Fifth, eliminate unused exports
{
let exports = export_section(module).ok_or(Error::NoExportSection)?;
index = 0;
old_index = 0;
loop {
if exports.entries_mut().len() == index { break; }
if stay.contains(&Symbol::Export(old_index)) {
index += 1;
} else {
trace!("Eliminated export({}, {})", old_index, exports.entries_mut()[index].field());
exports.entries_mut().remove(index);
}
old_index += 1;
}
}
if eliminated_globals.len() > 0 || eliminated_funcs.len() > 0 || eliminated_types.len() > 0 {
// Finaly, rewire all calls, globals references and types to the new indices
// (only if there is anything to do)
eliminated_globals.sort();
eliminated_funcs.sort();
eliminated_types.sort();
for section in module.sections_mut() {
match section {
&mut elements::Section::Start(ref mut func_index) if eliminated_funcs.len() > 0 => {
let totalle = eliminated_funcs.iter().take_while(|i| (**i as u32) < *func_index).count();
*func_index -= totalle as u32;
},
&mut elements::Section::Function(ref mut function_section) if eliminated_types.len() > 0 => {
for ref mut func_signature in function_section.entries_mut() {
let totalle = eliminated_types.iter().take_while(|i| (**i as u32) < func_signature.type_ref()).count();
*func_signature.type_ref_mut() -= totalle as u32;
}
},
&mut elements::Section::Import(ref mut import_section) if eliminated_types.len() > 0 => {
for ref mut import_entry in import_section.entries_mut() {
if let &mut elements::External::Function(ref mut type_ref) = import_entry.external_mut() {
let totalle = eliminated_types.iter().take_while(|i| (**i as u32) < *type_ref).count();
*type_ref -= totalle as u32;
}
}
},
&mut elements::Section::Code(ref mut code_section) if eliminated_globals.len() > 0 || eliminated_funcs.len() > 0 => {
for ref mut func_body in code_section.bodies_mut() {
if eliminated_funcs.len() > 0 {
update_call_index(func_body.code_mut(), &eliminated_funcs);
}
if eliminated_globals.len() > 0 {
update_global_index(func_body.code_mut().elements_mut(), &eliminated_globals)
}
if eliminated_types.len() > 0 {
update_type_index(func_body.code_mut(), &eliminated_types)
}
}
},
&mut elements::Section::Export(ref mut export_section) => {
for ref mut export in export_section.entries_mut() {
match export.internal_mut() {
&mut elements::Internal::Function(ref mut func_index) => {
let totalle = eliminated_funcs.iter().take_while(|i| (**i as u32) < *func_index).count();
*func_index -= totalle as u32;
},
&mut elements::Internal::Global(ref mut global_index) => {
let totalle = eliminated_globals.iter().take_while(|i| (**i as u32) < *global_index).count();
*global_index -= totalle as u32;
},
_ => {}
}
}
},
&mut elements::Section::Global(ref mut global_section) => {
for ref mut global_entry in global_section.entries_mut() {
update_global_index(global_entry.init_expr_mut().code_mut(), &eliminated_globals)
}
},
&mut elements::Section::Data(ref mut data_section) => {
for ref mut segment in data_section.entries_mut() {
update_global_index(segment.offset_mut().code_mut(), &eliminated_globals)
}
},
&mut elements::Section::Element(ref mut elements_section) => {
for ref mut segment in elements_section.entries_mut() {
update_global_index(segment.offset_mut().code_mut(), &eliminated_globals);
// update all indirect call addresses initial values
for func_index in segment.members_mut() {
let totalle = eliminated_funcs.iter().take_while(|i| (**i as u32) < *func_index).count();
*func_index -= totalle as u32;
}
}
},
_ => { }
}
}
}
Ok(())
}
pub fn update_call_index(instructions: &mut elements::Instructions, eliminated_indices: &[usize]) {
use parity_wasm::elements::Instruction::*;
for instruction in instructions.elements_mut().iter_mut() {
if let &mut Call(ref mut call_index) = instruction {
let totalle = eliminated_indices.iter().take_while(|i| (**i as u32) < *call_index).count();
trace!("rewired call {} -> call {}", *call_index, *call_index - totalle as u32);
*call_index -= totalle as u32;
}
}
}
/// Updates global references considering the _ordered_ list of eliminated indices
pub fn update_global_index(instructions: &mut Vec<elements::Instruction>, eliminated_indices: &[usize]) {
use parity_wasm::elements::Instruction::*;
for instruction in instructions.iter_mut() {
match instruction {
&mut GetGlobal(ref mut index) | &mut SetGlobal(ref mut index) => {
let totalle = eliminated_indices.iter().take_while(|i| (**i as u32) < *index).count();
trace!("rewired global {} -> global {}", *index, *index - totalle as u32);
*index -= totalle as u32;
},
_ => { },
}
}
}
/// Updates global references considering the _ordered_ list of eliminated indices
pub fn update_type_index(instructions: &mut elements::Instructions, eliminated_indices: &[usize]) {
use parity_wasm::elements::Instruction::*;
for instruction in instructions.elements_mut().iter_mut() {
if let &mut CallIndirect(ref mut call_index, _) = instruction {
let totalle = eliminated_indices.iter().take_while(|i| (**i as u32) < *call_index).count();
trace!("rewired call_indrect {} -> call_indirect {}", *call_index, *call_index - totalle as u32);
*call_index -= totalle as u32;
}
}
}
pub fn import_section<'a>(module: &'a mut elements::Module) -> Option<&'a mut elements::ImportSection> {
for section in module.sections_mut() {
if let &mut elements::Section::Import(ref mut sect) = section {
return Some(sect);
}
}
None
}
pub fn global_section<'a>(module: &'a mut elements::Module) -> Option<&'a mut elements::GlobalSection> {
for section in module.sections_mut() {
if let &mut elements::Section::Global(ref mut sect) = section {
return Some(sect);
}
}
None
}
pub fn function_section<'a>(module: &'a mut elements::Module) -> Option<&'a mut elements::FunctionSection> {
for section in module.sections_mut() {
if let &mut elements::Section::Function(ref mut sect) = section {
return Some(sect);
}
}
None
}
pub fn code_section<'a>(module: &'a mut elements::Module) -> Option<&'a mut elements::CodeSection> {
for section in module.sections_mut() {
if let &mut elements::Section::Code(ref mut sect) = section {
return Some(sect);
}
}
None
}
pub fn export_section<'a>(module: &'a mut elements::Module) -> Option<&'a mut elements::ExportSection> {
for section in module.sections_mut() {
if let &mut elements::Section::Export(ref mut sect) = section {
return Some(sect);
}
}
None
}
pub fn type_section<'a>(module: &'a mut elements::Module) -> Option<&'a mut elements::TypeSection> {
for section in module.sections_mut() {
if let &mut elements::Section::Type(ref mut sect) = section {
return Some(sect);
}
}
None
}
#[cfg(test)]
mod tests {
use parity_wasm::{builder, elements};
use super::*;
/// @spec 0
/// Optimizer presumes that export section exists and contains
/// all symbols passed as a second parameter. Since empty module
/// obviously contains no export section, optimizer should return
/// error on it.
#[test]
fn empty() {
let mut module = builder::module().build();
let result = optimize(&mut module, vec!["_call"]);
assert!(result.is_err());
}
/// @spec 1
/// Imagine the unoptimized module has two own functions, `_call` and `_random`
/// and exports both of them in the export section. During optimization, the `_random`
/// function should vanish completely, given we pass `_call` as the only function to stay
/// in the module.
#[test]
fn minimal() {
let mut module = builder::module()
.function()
.signature().param().i32().build()
.build()
.function()
.signature()
.param().i32()
.param().i32()
.build()
.build()
.export()
.field("_call")
.internal().func(0).build()
.export()
.field("_random")
.internal().func(1).build()
.build();
assert_eq!(module.export_section().expect("export section to be generated").entries().len(), 2);
optimize(&mut module, vec!["_call"]).expect("optimizer to succeed");
assert_eq!(
1,
module.export_section().expect("export section to be generated").entries().len(),
"There should only 1 (one) export entry in the optimized module"
);
assert_eq!(
1,
module.function_section().expect("functions section to be generated").entries().len(),
"There should 2 (two) functions in the optimized module"
);
}
/// @spec 2
/// Imagine there is one exported function in unoptimized module, `_call`, that we specify as the one
/// to stay during the optimization. The code of this function uses global during the execution.
/// This sayed global should survive the optimization.
#[test]
fn globals() {
let mut module = builder::module()
.global()
.value_type().i32()
.build()
.function()
.signature().param().i32().build()
.body()
.with_instructions(elements::Instructions::new(
vec![
elements::Instruction::GetGlobal(0),
elements::Instruction::End
]
))
.build()
.build()
.export()
.field("_call")
.internal().func(0).build()
.build();
optimize(&mut module, vec!["_call"]).expect("optimizer to succeed");
assert_eq!(
1,
module.global_section().expect("global section to be generated").entries().len(),
"There should 1 (one) global entry in the optimized module, since _call function uses it"
);
}
/// @spec 2
/// Imagine there is one exported function in unoptimized module, `_call`, that we specify as the one
/// to stay during the optimization. The code of this function uses one global during the execution,
/// but we have a bunch of other unused globals in the code. Last globals should not survive the optimization,
/// while the former should.
#[test]
fn globals_2() {
let mut module = builder::module()
.global()
.value_type().i32()
.build()
.global()
.value_type().i64()
.build()
.global()
.value_type().f32()
.build()
.function()
.signature().param().i32().build()
.body()
.with_instructions(elements::Instructions::new(
vec![
elements::Instruction::GetGlobal(1),
elements::Instruction::End
]
))
.build()
.build()
.export()
.field("_call")
.internal().func(0).build()
.build();
optimize(&mut module, vec!["_call"]).expect("optimizer to succeed");
assert_eq!(
1,
module.global_section().expect("global section to be generated").entries().len(),
"There should 1 (one) global entry in the optimized module, since _call function uses only one"
);
}
/// @spec 3
/// Imagine the unoptimized module has two own functions, `_call` and `_random`
/// and exports both of them in the export section. Function `_call` also calls `_random`
/// in its function body. The optimization should kick `_random` function from the export section
/// but preserve it's body.
#[test]
fn call_ref() {
let mut module = builder::module()
.function()
.signature().param().i32().build()
.body()
.with_instructions(elements::Instructions::new(
vec![
elements::Instruction::Call(1),
elements::Instruction::End
]
))
.build()
.build()
.function()
.signature()
.param().i32()
.param().i32()
.build()
.build()
.export()
.field("_call")
.internal().func(0).build()
.export()
.field("_random")
.internal().func(1).build()
.build();
assert_eq!(module.export_section().expect("export section to be generated").entries().len(), 2);
optimize(&mut module, vec!["_call"]).expect("optimizer to succeed");
assert_eq!(
1,
module.export_section().expect("export section to be generated").entries().len(),
"There should only 1 (one) export entry in the optimized module"
);
assert_eq!(
2,
module.function_section().expect("functions section to be generated").entries().len(),
"There should 2 (two) functions in the optimized module"
);
}
/// @spec 4
/// Imagine the unoptimized module has an indirect call to function of type 1
/// The type should persist so that indirect call would work
#[test]
fn call_indirect() {
let mut module = builder::module()
.function()
.signature().param().i32().param().i32().build()
.build()
.function()
.signature().param().i32().param().i32().param().i32().build()
.build()
.function()
.signature().param().i32().build()
.body()
.with_instructions(elements::Instructions::new(
vec![
elements::Instruction::CallIndirect(1, 0),
elements::Instruction::End
]
))
.build()
.build()
.export()
.field("_call")
.internal().func(2).build()
.build();
optimize(&mut module, vec!["_call"]).expect("optimizer to succeed");
assert_eq!(
2,
module.type_section().expect("type section to be generated").types().len(),
"There should 2 (two) types left in the module, 1 for indirect call and one for _call"
);
let indirect_opcode = &module.code_section().expect("code section to be generated").bodies()[0].code().elements()[0];
match *indirect_opcode {
elements::Instruction::CallIndirect(0, 0) => {},
_ => {
panic!(
"Expected call_indirect to use index 0 after optimization, since previois 0th was eliminated, but got {:?}",
indirect_opcode
);
}
}
}
}
-335
View File
@@ -1,335 +0,0 @@
use std::fmt;
use std::vec::Vec;
use std::borrow::ToOwned;
use parity_wasm::elements::{
self, Section, DataSection, Instruction, DataSegment, InitExpr, Internal, External,
ImportCountType,
};
use parity_wasm::builder;
use super::TargetRuntime;
use super::gas::update_call_index;
/// Pack error.
///
/// Pack has number of assumptions of passed module structure.
/// When they are violated, pack_instance returns one of these.
#[derive(Debug)]
pub enum Error {
MalformedModule,
NoTypeSection,
NoExportSection,
NoCodeSection,
InvalidCreateSignature(&'static str),
NoCreateSymbol(&'static str),
InvalidCreateMember(&'static str),
NoImportSection,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match *self {
Error::MalformedModule => write!(f, "Module internal references are inconsistent"),
Error::NoTypeSection => write!(f, "No type section in the module"),
Error::NoExportSection => write!(f, "No export section in the module"),
Error::NoCodeSection => write!(f, "No code section inthe module"),
Error::InvalidCreateSignature(sym) => write!(f, "Exported symbol `{}` has invalid signature, should be () -> ()", sym),
Error::InvalidCreateMember(sym) => write!(f, "Exported symbol `{}` should be a function", sym),
Error::NoCreateSymbol(sym) => write!(f, "No exported `{}` symbol", sym),
Error::NoImportSection => write!(f, "No import section in the module"),
}
}
}
/// If module has an exported "CREATE_SYMBOL" function we want to pack it into "constructor".
/// `raw_module` is the actual contract code
/// `ctor_module` is the constructor which should return `raw_module`
pub fn pack_instance(raw_module: Vec<u8>, mut ctor_module: elements::Module, target: &TargetRuntime) -> Result<elements::Module, Error> {
// Total number of constructor module import functions
let ctor_import_functions = ctor_module.import_section().map(|x| x.functions()).unwrap_or(0);
// We need to find an internal ID of function witch is exported as "CREATE_SYMBOL"
// in order to find it in the Code section of the module
let mut create_func_id = {
let found_entry = ctor_module.export_section().ok_or(Error::NoExportSection)?.entries().iter()
.find(|entry| target.create_symbol == entry.field()).ok_or_else(|| Error::NoCreateSymbol(target.create_symbol))?;
let function_index: usize = match found_entry.internal() {
&Internal::Function(index) => index as usize,
_ => { return Err(Error::InvalidCreateMember(target.create_symbol)) },
};
// Calculates a function index within module's function section
let function_internal_index = function_index - ctor_import_functions;
// Constructor should be of signature `func()` (void), fail otherwise
let type_id = ctor_module.function_section().ok_or(Error::NoCodeSection)?
.entries().get(function_index - ctor_import_functions).ok_or(Error::MalformedModule)?
.type_ref();
let &elements::Type::Function(ref func) = ctor_module.type_section().ok_or(Error::NoTypeSection)?
.types().get(type_id as usize).ok_or(Error::MalformedModule)?;
// Deploy should have no arguments and also should return nothing
if !func.params().is_empty() {
return Err(Error::InvalidCreateSignature(target.create_symbol));
}
if func.return_type().is_some() {
return Err(Error::InvalidCreateSignature(target.create_symbol));
}
function_internal_index
};
let ret_function_id = {
let mut id = 0;
let mut found = false;
for entry in ctor_module.import_section().ok_or(Error::NoImportSection)?.entries().iter() {
if let External::Function(_) = *entry.external() {
if entry.field() == target.return_symbol { found = true; break; }
else { id += 1; }
}
}
if !found {
let mut mbuilder = builder::from_module(ctor_module);
let import_sig = mbuilder.push_signature(
builder::signature()
.param().i32().param().i32()
.build_sig()
);
mbuilder.push_import(
builder::import()
.module("env")
.field(&target.return_symbol)
.external().func(import_sig)
.build()
);
ctor_module = mbuilder.build();
let ret_func = ctor_module.import_count(ImportCountType::Function) as u32 - 1;
for section in ctor_module.sections_mut() {
match *section {
elements::Section::Code(ref mut code_section) => {
for ref mut func_body in code_section.bodies_mut() {
update_call_index(func_body.code_mut(), ret_func);
}
},
elements::Section::Export(ref mut export_section) => {
for ref mut export in export_section.entries_mut() {
if let &mut elements::Internal::Function(ref mut func_index) = export.internal_mut() {
if *func_index >= ret_func { *func_index += 1}
}
}
},
elements::Section::Element(ref mut elements_section) => {
for ref mut segment in elements_section.entries_mut() {
// update all indirect call addresses initial values
for func_index in segment.members_mut() {
if *func_index >= ret_func { *func_index += 1}
}
}
},
_ => { }
}
}
create_func_id += 1;
ret_func
}
else { id }
};
// If new function is put in ctor module, it will have this callable index
let last_function_index = ctor_module.functions_space();
// We ensure here that module has the DataSection
if ctor_module
.sections()
.iter()
.find(|section| match **section { Section::Data(ref _d) => true, _ => false })
.is_none() {
// DataSection has to be the last non-custom section according the to the spec
ctor_module.sections_mut().push(Section::Data(DataSection::with_entries(vec![])));
}
// Code data address is an address where we put the contract's code (raw_module)
let mut code_data_address = 0i32;
for section in ctor_module.sections_mut() {
if let &mut Section::Data(ref mut data_section) = section {
let (index, offset) = if let Some(ref entry) = data_section.entries().iter().last() {
if let Instruction::I32Const(offst) = entry.offset().code()[0] {
let len = entry.value().len() as i32;
let offst = offst as i32;
(entry.index(), offst + (len + 4) - len % 4)
} else {
(0, 0)
}
} else {
(0, 0)
};
let code_data = DataSegment::new(
index,
InitExpr::new(vec![Instruction::I32Const(offset), Instruction::End]),
raw_module.clone()
);
data_section.entries_mut().push(code_data);
code_data_address = offset;
}
}
let mut new_module = builder::from_module(ctor_module)
.function()
.signature().build()
.body().with_instructions(elements::Instructions::new(
vec![
Instruction::Call((create_func_id + ctor_import_functions) as u32),
Instruction::I32Const(code_data_address),
Instruction::I32Const(raw_module.len() as i32),
Instruction::Call(ret_function_id as u32),
Instruction::End,
])).build()
.build()
.build();
for section in new_module.sections_mut() {
if let &mut Section::Export(ref mut export_section) = section {
for entry in export_section.entries_mut().iter_mut() {
if target.create_symbol == entry.field() {
// change `create_symbol` export name into default `call_symbol`.
*entry.field_mut() = target.call_symbol.to_owned();
*entry.internal_mut() = elements::Internal::Function(last_function_index as u32);
}
}
}
};
Ok(new_module)
}
#[cfg(test)]
mod test {
extern crate parity_wasm;
use parity_wasm::builder;
use super::*;
use super::super::optimize;
fn test_packer(mut module: elements::Module, target_runtime: &TargetRuntime) {
let mut ctor_module = module.clone();
optimize(&mut module, vec![target_runtime.call_symbol]).expect("Optimizer to finish without errors");
optimize(&mut ctor_module, vec![target_runtime.create_symbol]).expect("Optimizer to finish without errors");
let raw_module = parity_wasm::serialize(module).unwrap();
let ctor_module = pack_instance(raw_module.clone(), ctor_module, target_runtime).expect("Packing failed");
let data_section = ctor_module.data_section().expect("Packed module has to have a data section");
let data_segment = data_section.entries().iter().last().expect("Packed module has to have a data section with at least one entry");
assert!(data_segment.value() == AsRef::<[u8]>::as_ref(&raw_module), "Last data segment should be equal to the raw module");
}
#[test]
fn no_data_section() {
let target_runtime = TargetRuntime::pwasm();
test_packer(builder::module()
.import()
.module("env")
.field("memory")
.external().memory(1 as u32, Some(1 as u32))
.build()
.function()
.signature()
.params().i32().i32().build()
.build()
.body().build()
.build()
.function()
.signature().build()
.body()
.with_instructions(elements::Instructions::new(
vec![
elements::Instruction::End
]
))
.build()
.build()
.function()
.signature().build()
.body()
.with_instructions(elements::Instructions::new(
vec![
elements::Instruction::End
]
))
.build()
.build()
.export()
.field(target_runtime.call_symbol)
.internal().func(1)
.build()
.export()
.field(target_runtime.create_symbol)
.internal().func(2)
.build()
.build(),
&target_runtime,
);
}
#[test]
fn with_data_section() {
let target_runtime = TargetRuntime::pwasm();
test_packer(builder::module()
.import()
.module("env")
.field("memory")
.external().memory(1 as u32, Some(1 as u32))
.build()
.data()
.offset(elements::Instruction::I32Const(16)).value(vec![0u8])
.build()
.function()
.signature()
.params().i32().i32().build()
.build()
.body().build()
.build()
.function()
.signature().build()
.body()
.with_instructions(elements::Instructions::new(
vec![
elements::Instruction::End
]
))
.build()
.build()
.function()
.signature().build()
.body()
.with_instructions(elements::Instructions::new(
vec![
elements::Instruction::End
]
))
.build()
.build()
.export()
.field(target_runtime.call_symbol)
.internal().func(1)
.build()
.export()
.field(target_runtime.create_symbol)
.internal().func(2)
.build()
.build(),
&target_runtime,
);
}
}
-315
View File
@@ -1,315 +0,0 @@
#[cfg(features = "std")]
use std::collections::{HashMap as Map};
#[cfg(not(features = "std"))]
use std::collections::{BTreeMap as Map};
use parity_wasm::elements;
pub struct UnknownInstruction;
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum Metering {
Regular,
Forbidden,
Fixed(u32),
}
#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
pub enum InstructionType {
Bit,
Add,
Mul,
Div,
Load,
Store,
Const,
FloatConst,
Local,
Global,
ControlFlow,
IntegerComparsion,
FloatComparsion,
Float,
Conversion,
FloatConversion,
Reinterpretation,
Unreachable,
Nop,
CurrentMemory,
GrowMemory,
}
impl ::std::str::FromStr for InstructionType {
type Err = UnknownInstruction;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"bit" => Ok(InstructionType::Bit),
"add" => Ok(InstructionType::Add),
"mul" => Ok(InstructionType::Mul),
"div" => Ok(InstructionType::Div),
"load" => Ok(InstructionType::Load),
"store" => Ok(InstructionType::Store),
"const" => Ok(InstructionType::Const),
"local" => Ok(InstructionType::Local),
"global" => Ok(InstructionType::Global),
"flow" => Ok(InstructionType::ControlFlow),
"integer_comp" => Ok(InstructionType::IntegerComparsion),
"float_comp" => Ok(InstructionType::FloatComparsion),
"float" => Ok(InstructionType::Float),
"conversion" => Ok(InstructionType::Conversion),
"float_conversion" => Ok(InstructionType::FloatConversion),
"reinterpret" => Ok(InstructionType::Reinterpretation),
"unreachable" => Ok(InstructionType::Unreachable),
"nop" => Ok(InstructionType::Nop),
"current_mem" => Ok(InstructionType::CurrentMemory),
"grow_mem" => Ok(InstructionType::GrowMemory),
_ => Err(UnknownInstruction),
}
}
}
impl InstructionType {
pub fn op(instruction: &elements::Instruction) -> Self {
use parity_wasm::elements::Instruction::*;
match *instruction {
Unreachable => InstructionType::Unreachable,
Nop => InstructionType::Nop,
Block(_) => InstructionType::ControlFlow,
Loop(_) => InstructionType::ControlFlow,
If(_) => InstructionType::ControlFlow,
Else => InstructionType::ControlFlow,
End => InstructionType::ControlFlow,
Br(_) => InstructionType::ControlFlow,
BrIf(_) => InstructionType::ControlFlow,
BrTable(_, _) => InstructionType::ControlFlow,
Return => InstructionType::ControlFlow,
Call(_) => InstructionType::ControlFlow,
CallIndirect(_, _) => InstructionType::ControlFlow,
Drop => InstructionType::ControlFlow,
Select => InstructionType::ControlFlow,
GetLocal(_) => InstructionType::Local,
SetLocal(_) => InstructionType::Local,
TeeLocal(_) => InstructionType::Local,
GetGlobal(_) => InstructionType::Local,
SetGlobal(_) => InstructionType::Local,
I32Load(_, _) => InstructionType::Load,
I64Load(_, _) => InstructionType::Load,
F32Load(_, _) => InstructionType::Load,
F64Load(_, _) => InstructionType::Load,
I32Load8S(_, _) => InstructionType::Load,
I32Load8U(_, _) => InstructionType::Load,
I32Load16S(_, _) => InstructionType::Load,
I32Load16U(_, _) => InstructionType::Load,
I64Load8S(_, _) => InstructionType::Load,
I64Load8U(_, _) => InstructionType::Load,
I64Load16S(_, _) => InstructionType::Load,
I64Load16U(_, _) => InstructionType::Load,
I64Load32S(_, _) => InstructionType::Load,
I64Load32U(_, _) => InstructionType::Load,
I32Store(_, _) => InstructionType::Store,
I64Store(_, _) => InstructionType::Store,
F32Store(_, _) => InstructionType::Store,
F64Store(_, _) => InstructionType::Store,
I32Store8(_, _) => InstructionType::Store,
I32Store16(_, _) => InstructionType::Store,
I64Store8(_, _) => InstructionType::Store,
I64Store16(_, _) => InstructionType::Store,
I64Store32(_, _) => InstructionType::Store,
CurrentMemory(_) => InstructionType::CurrentMemory,
GrowMemory(_) => InstructionType::GrowMemory,
I32Const(_) => InstructionType::Const,
I64Const(_) => InstructionType::Const,
F32Const(_) => InstructionType::FloatConst,
F64Const(_) => InstructionType::FloatConst,
I32Eqz => InstructionType::IntegerComparsion,
I32Eq => InstructionType::IntegerComparsion,
I32Ne => InstructionType::IntegerComparsion,
I32LtS => InstructionType::IntegerComparsion,
I32LtU => InstructionType::IntegerComparsion,
I32GtS => InstructionType::IntegerComparsion,
I32GtU => InstructionType::IntegerComparsion,
I32LeS => InstructionType::IntegerComparsion,
I32LeU => InstructionType::IntegerComparsion,
I32GeS => InstructionType::IntegerComparsion,
I32GeU => InstructionType::IntegerComparsion,
I64Eqz => InstructionType::IntegerComparsion,
I64Eq => InstructionType::IntegerComparsion,
I64Ne => InstructionType::IntegerComparsion,
I64LtS => InstructionType::IntegerComparsion,
I64LtU => InstructionType::IntegerComparsion,
I64GtS => InstructionType::IntegerComparsion,
I64GtU => InstructionType::IntegerComparsion,
I64LeS => InstructionType::IntegerComparsion,
I64LeU => InstructionType::IntegerComparsion,
I64GeS => InstructionType::IntegerComparsion,
I64GeU => InstructionType::IntegerComparsion,
F32Eq => InstructionType::FloatComparsion,
F32Ne => InstructionType::FloatComparsion,
F32Lt => InstructionType::FloatComparsion,
F32Gt => InstructionType::FloatComparsion,
F32Le => InstructionType::FloatComparsion,
F32Ge => InstructionType::FloatComparsion,
F64Eq => InstructionType::FloatComparsion,
F64Ne => InstructionType::FloatComparsion,
F64Lt => InstructionType::FloatComparsion,
F64Gt => InstructionType::FloatComparsion,
F64Le => InstructionType::FloatComparsion,
F64Ge => InstructionType::FloatComparsion,
I32Clz => InstructionType::Bit,
I32Ctz => InstructionType::Bit,
I32Popcnt => InstructionType::Bit,
I32Add => InstructionType::Add,
I32Sub => InstructionType::Add,
I32Mul => InstructionType::Mul,
I32DivS => InstructionType::Div,
I32DivU => InstructionType::Div,
I32RemS => InstructionType::Div,
I32RemU => InstructionType::Div,
I32And => InstructionType::Bit,
I32Or => InstructionType::Bit,
I32Xor => InstructionType::Bit,
I32Shl => InstructionType::Bit,
I32ShrS => InstructionType::Bit,
I32ShrU => InstructionType::Bit,
I32Rotl => InstructionType::Bit,
I32Rotr => InstructionType::Bit,
I64Clz => InstructionType::Bit,
I64Ctz => InstructionType::Bit,
I64Popcnt => InstructionType::Bit,
I64Add => InstructionType::Add,
I64Sub => InstructionType::Add,
I64Mul => InstructionType::Mul,
I64DivS => InstructionType::Div,
I64DivU => InstructionType::Div,
I64RemS => InstructionType::Div,
I64RemU => InstructionType::Div,
I64And => InstructionType::Bit,
I64Or => InstructionType::Bit,
I64Xor => InstructionType::Bit,
I64Shl => InstructionType::Bit,
I64ShrS => InstructionType::Bit,
I64ShrU => InstructionType::Bit,
I64Rotl => InstructionType::Bit,
I64Rotr => InstructionType::Bit,
F32Abs => InstructionType::Float,
F32Neg => InstructionType::Float,
F32Ceil => InstructionType::Float,
F32Floor => InstructionType::Float,
F32Trunc => InstructionType::Float,
F32Nearest => InstructionType::Float,
F32Sqrt => InstructionType::Float,
F32Add => InstructionType::Float,
F32Sub => InstructionType::Float,
F32Mul => InstructionType::Float,
F32Div => InstructionType::Float,
F32Min => InstructionType::Float,
F32Max => InstructionType::Float,
F32Copysign => InstructionType::Float,
F64Abs => InstructionType::Float,
F64Neg => InstructionType::Float,
F64Ceil => InstructionType::Float,
F64Floor => InstructionType::Float,
F64Trunc => InstructionType::Float,
F64Nearest => InstructionType::Float,
F64Sqrt => InstructionType::Float,
F64Add => InstructionType::Float,
F64Sub => InstructionType::Float,
F64Mul => InstructionType::Float,
F64Div => InstructionType::Float,
F64Min => InstructionType::Float,
F64Max => InstructionType::Float,
F64Copysign => InstructionType::Float,
I32WrapI64 => InstructionType::Conversion,
I64ExtendSI32 => InstructionType::Conversion,
I64ExtendUI32 => InstructionType::Conversion,
I32TruncSF32 => InstructionType::FloatConversion,
I32TruncUF32 => InstructionType::FloatConversion,
I32TruncSF64 => InstructionType::FloatConversion,
I32TruncUF64 => InstructionType::FloatConversion,
I64TruncSF32 => InstructionType::FloatConversion,
I64TruncUF32 => InstructionType::FloatConversion,
I64TruncSF64 => InstructionType::FloatConversion,
I64TruncUF64 => InstructionType::FloatConversion,
F32ConvertSI32 => InstructionType::FloatConversion,
F32ConvertUI32 => InstructionType::FloatConversion,
F32ConvertSI64 => InstructionType::FloatConversion,
F32ConvertUI64 => InstructionType::FloatConversion,
F32DemoteF64 => InstructionType::FloatConversion,
F64ConvertSI32 => InstructionType::FloatConversion,
F64ConvertUI32 => InstructionType::FloatConversion,
F64ConvertSI64 => InstructionType::FloatConversion,
F64ConvertUI64 => InstructionType::FloatConversion,
F64PromoteF32 => InstructionType::FloatConversion,
I32ReinterpretF32 => InstructionType::Reinterpretation,
I64ReinterpretF64 => InstructionType::Reinterpretation,
F32ReinterpretI32 => InstructionType::Reinterpretation,
F64ReinterpretI64 => InstructionType::Reinterpretation,
}
}
}
#[derive(Debug)]
pub struct Set {
regular: u32,
entries: Map<InstructionType, Metering>,
grow: u32,
}
impl Default for Set {
fn default() -> Self {
Set {
regular: 1,
entries: Map::new(),
grow: 0,
}
}
}
impl Set {
pub fn new(regular: u32, entries: Map<InstructionType, Metering>) -> Self {
Set { regular: regular, entries: entries, grow: 0 }
}
pub fn process(&self, instruction: &elements::Instruction) -> Result<u32, ()> {
match self.entries.get(&InstructionType::op(instruction)).map(|x| *x) {
None | Some(Metering::Regular) => Ok(self.regular),
Some(Metering::Forbidden) => Err(()),
Some(Metering::Fixed(val)) => Ok(val),
}
}
pub fn grow_cost(&self) -> u32 {
self.grow
}
pub fn with_grow_cost(mut self, val: u32) -> Self {
self.grow = val;
self
}
pub fn with_forbidden_floats(mut self) -> Self {
self.entries.insert(InstructionType::Float, Metering::Forbidden);
self.entries.insert(InstructionType::FloatComparsion, Metering::Forbidden);
self.entries.insert(InstructionType::FloatConst, Metering::Forbidden);
self.entries.insert(InstructionType::FloatConversion, Metering::Forbidden);
self
}
}
-45
View File
@@ -1,45 +0,0 @@
use parity_wasm::{elements, builder};
use self::elements::{ Module, GlobalEntry, External, ExportEntry, GlobalType, ValueType, InitExpr, Instruction, Internal };
use byteorder::{ LittleEndian, ByteOrder };
pub fn inject_runtime_type(module: Module, runtime_type: [u8; 4], runtime_version: u32) -> Module {
let runtime_type: u32 = LittleEndian::read_u32(&runtime_type);
let globals_count: u32 = match module.global_section() {
Some(ref section) => section.entries().len() as u32,
None => 0
};
let imported_globals_count: u32 = match module.import_section() {
Some(ref section) => section.entries().iter().filter(|e| match *e.external() {
External::Global(ref _a) => true,
_ => false
}).count() as u32,
None => 0
};
let total_globals_count: u32 = globals_count + imported_globals_count;
builder::from_module(module)
.with_global(GlobalEntry::new(GlobalType::new(ValueType::I32, false), InitExpr::new(vec![Instruction::I32Const(runtime_type as i32), Instruction::End])))
.with_export(ExportEntry::new("RUNTIME_TYPE".into(), Internal::Global(total_globals_count)))
.with_global(GlobalEntry::new(GlobalType::new(ValueType::I32, false), InitExpr::new(vec![Instruction::I32Const(runtime_version as i32), Instruction::End])))
.with_export(ExportEntry::new("RUNTIME_VERSION".into(), Internal::Global(total_globals_count + 1)))
.build()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_injects() {
let mut module = builder::module()
.with_global(GlobalEntry::new(GlobalType::new(ValueType::I32, false), InitExpr::new(vec![Instruction::I32Const(42 as i32)])))
.build();
let mut runtime_type: [u8; 4] = Default::default();
runtime_type.copy_from_slice(b"emcc");
module = inject_runtime_type(module, runtime_type, 1);
let global_section = module.global_section().expect("Global section expected");
assert_eq!(3, global_section.entries().len());
let export_section = module.export_section().expect("Export section expected");
assert!(export_section.entries().iter().find(|e| e.field() == "RUNTIME_TYPE" ).is_some());
assert!(export_section.entries().iter().find(|e| e.field() == "RUNTIME_VERSION" ).is_some());
}
}
-526
View File
@@ -1,526 +0,0 @@
use std::vec::Vec;
use parity_wasm::elements::{self, BlockType, Type};
use super::{resolve_func_type, Error};
/// Control stack frame.
#[derive(Debug)]
struct Frame {
/// Stack becomes polymorphic only after an instruction that
/// never passes control further was executed.
is_polymorphic: bool,
/// Count of values which will be pushed after the exit
/// from the current block.
end_arity: u32,
/// Count of values which should be poped upon a branch to
/// this frame.
///
/// This might be diffirent from `end_arity` since branch
/// to the loop header can't take any values.
branch_arity: u32,
/// Stack height before entering in the block.
start_height: u32,
}
/// This is a compound stack that abstracts tracking height of the value stack
/// and manipulation of the control stack.
struct Stack {
height: u32,
control_stack: Vec<Frame>,
}
impl Stack {
fn new() -> Stack {
Stack {
height: 0,
control_stack: Vec::new(),
}
}
/// Returns current height of the value stack.
fn height(&self) -> u32 {
self.height
}
/// Returns a reference to a frame by specified depth relative to the top of
/// control stack.
fn frame(&self, rel_depth: u32) -> Result<&Frame, Error> {
let control_stack_height: usize = self.control_stack.len();
let last_idx = control_stack_height
.checked_sub(1)
.ok_or_else(|| Error("control stack is empty".into()))?;
let idx = last_idx
.checked_sub(rel_depth as usize)
.ok_or_else(|| Error("control stack out-of-bounds".into()))?;
Ok(&self.control_stack[idx])
}
/// Mark successive instructions as unreachable.
///
/// This effectively makes stack polymorphic.
fn mark_unreachable(&mut self) -> Result<(), Error> {
trace!(target: "max_height", "unreachable");
let top_frame = self.control_stack
.last_mut()
.ok_or_else(|| Error("stack must be non-empty".into()))?;
top_frame.is_polymorphic = true;
Ok(())
}
/// Push control frame into the control stack.
fn push_frame(&mut self, frame: Frame) {
trace!(target: "max_height", "push_frame: {:?}", frame);
self.control_stack.push(frame);
}
/// Pop control frame from the control stack.
///
/// Returns `Err` if the control stack is empty.
fn pop_frame(&mut self) -> Result<Frame, Error> {
trace!(target: "max_height", "pop_frame: {:?}", self.control_stack.last());
Ok(self.control_stack
.pop()
.ok_or_else(|| Error("stack must be non-empty".into()))?)
}
/// Truncate the height of value stack to the specified height.
fn trunc(&mut self, new_height: u32) {
trace!(target: "max_height", "trunc: {}", new_height);
self.height = new_height;
}
/// Push specified number of values into the value stack.
///
/// Returns `Err` if the height overflow usize value.
fn push_values(&mut self, value_count: u32) -> Result<(), Error> {
trace!(target: "max_height", "push: {}", value_count);
self.height = self.height
.checked_add(value_count)
.ok_or_else(|| Error("stack overflow".into()))?;
Ok(())
}
/// Pop specified number of values from the value stack.
///
/// Returns `Err` if the stack happen to be negative value after
/// values popped.
fn pop_values(&mut self, value_count: u32) -> Result<(), Error> {
trace!(target: "max_height", "pop: {}", value_count);
if value_count == 0 {
return Ok(());
}
{
let top_frame = self.frame(0)?;
if self.height == top_frame.start_height {
// It is an error to pop more values than was pushed in the current frame
// (ie pop values pushed in the parent frame), unless the frame became
// polymorphic.
return if top_frame.is_polymorphic {
Ok(())
} else {
Err(Error("trying to pop more values than pushed".into()))
}
}
}
self.height = self.height
.checked_sub(value_count)
.ok_or_else(|| Error("stack underflow".into()))?;
Ok(())
}
}
/// This function expects the function to be validated.
pub(crate) fn compute(func_idx: u32, module: &elements::Module) -> Result<u32, Error> {
use parity_wasm::elements::Instruction::*;
let func_section = module
.function_section()
.ok_or_else(|| Error("No function section".into()))?;
let code_section = module
.code_section()
.ok_or_else(|| Error("No code section".into()))?;
let type_section = module
.type_section()
.ok_or_else(|| Error("No type section".into()))?;
trace!(target: "max_height", "func_idx: {}", func_idx);
// Get a signature and a body of the specified function.
let func_sig_idx = func_section
.entries()
.get(func_idx as usize)
.ok_or_else(|| Error("Function is not found in func section".into()))?
.type_ref();
let Type::Function(ref func_signature) = *type_section
.types()
.get(func_sig_idx as usize)
.ok_or_else(|| Error("Function is not found in func section".into()))?;
let body = code_section
.bodies()
.get(func_idx as usize)
.ok_or_else(|| Error("Function body for the index isn't found".into()))?;
let instructions = body.code();
let mut stack = Stack::new();
let mut max_height: u32 = 0;
let mut pc = 0;
// Add implicit frame for the function. Breaks to this frame and execution of
// the last end should deal with this frame.
let func_arity: u32 = if func_signature.return_type().is_some() {
1
} else {
0
};
stack.push_frame(Frame {
is_polymorphic: false,
end_arity: func_arity,
branch_arity: func_arity,
start_height: 0,
});
loop {
if pc >= instructions.elements().len() {
break;
}
// If current value stack is higher than maximal height observed so far,
// save the new height.
// However, we don't increase maximal value in unreachable code.
if stack.height() > max_height && !stack.frame(0)?.is_polymorphic {
max_height = stack.height();
}
let opcode = &instructions.elements()[pc];
trace!(target: "max_height", "{:?}", opcode);
match *opcode {
Nop => {}
Block(ty) | Loop(ty) | If(ty) => {
let end_arity = if ty == BlockType::NoResult { 0 } else { 1 };
let branch_arity = if let Loop(_) = *opcode { 0 } else { end_arity };
let height = stack.height();
stack.push_frame(Frame {
is_polymorphic: false,
end_arity,
branch_arity,
start_height: height,
});
}
Else => {
// The frame at the top should be pushed by `If`. So we leave
// it as is.
}
End => {
let frame = stack.pop_frame()?;
stack.trunc(frame.start_height);
stack.push_values(frame.end_arity)?;
}
Unreachable => {
stack.mark_unreachable()?;
}
Br(target) => {
// Pop values for the destination block result.
let target_arity = stack.frame(target)?.branch_arity;
stack.pop_values(target_arity)?;
// This instruction unconditionally transfers control to the specified block,
// thus all instruction until the end of the current block is deemed unreachable
stack.mark_unreachable()?;
}
BrIf(target) => {
// Pop values for the destination block result.
let target_arity = stack.frame(target)?.branch_arity;
stack.pop_values(target_arity)?;
// Pop condition value.
stack.pop_values(1)?;
// Push values back.
stack.push_values(target_arity)?;
}
BrTable(ref targets, default_target) => {
let arity_of_default = stack.frame(default_target)?.branch_arity;
// Check that all jump targets have an equal arities.
for target in targets.iter() {
let arity = stack.frame(*target)?.branch_arity;
if arity != arity_of_default {
return Err(Error(
"Arity of all jump-targets must be equal".into()
))
}
}
// Because all jump targets have an equal arities, we can just take arity of
// the default branch.
stack.pop_values(arity_of_default)?;
// This instruction doesn't let control flow to go further, since the control flow
// should take either one of branches depending on the value or the default branch.
stack.mark_unreachable()?;
}
Return => {
// Pop return values of the function. Mark successive instructions as unreachable
// since this instruction doesn't let control flow to go further.
stack.pop_values(func_arity)?;
stack.mark_unreachable()?;
}
Call(idx) => {
let ty = resolve_func_type(idx, module)?;
// Pop values for arguments of the function.
stack.pop_values(ty.params().len() as u32)?;
// Push result of the function execution to the stack.
let callee_arity = if ty.return_type().is_some() { 1 } else { 0 };
stack.push_values(callee_arity)?;
}
CallIndirect(x, _) => {
let Type::Function(ref ty) = *type_section
.types()
.get(x as usize)
.ok_or_else(|| Error("Type not found".into()))?;
// Pop values for arguments of the function.
stack.pop_values(ty.params().len() as u32)?;
// Push result of the function execution to the stack.
let callee_arity = if ty.return_type().is_some() { 1 } else { 0 };
stack.push_values(callee_arity)?;
}
Drop => {
stack.pop_values(1)?;
}
Select => {
// Pop two values and one condition.
stack.pop_values(2)?;
stack.pop_values(1)?;
// Push the selected value.
stack.push_values(1)?;
}
GetLocal(_) => {
stack.push_values(1)?;
}
SetLocal(_) => {
stack.pop_values(1)?;
}
TeeLocal(_) => {
// This instruction pops and pushes the value, so
// effectively it doesn't modify the stack height.
stack.pop_values(1)?;
stack.push_values(1)?;
}
GetGlobal(_) => {
stack.push_values(1)?;
}
SetGlobal(_) => {
stack.pop_values(1)?;
}
I32Load(_, _)
| I64Load(_, _)
| F32Load(_, _)
| F64Load(_, _)
| I32Load8S(_, _)
| I32Load8U(_, _)
| I32Load16S(_, _)
| I32Load16U(_, _)
| I64Load8S(_, _)
| I64Load8U(_, _)
| I64Load16S(_, _)
| I64Load16U(_, _)
| I64Load32S(_, _)
| I64Load32U(_, _) => {
// These instructions pop the address and pushes the result,
// which effictively don't modify the stack height.
stack.pop_values(1)?;
stack.push_values(1)?;
}
I32Store(_, _)
| I64Store(_, _)
| F32Store(_, _)
| F64Store(_, _)
| I32Store8(_, _)
| I32Store16(_, _)
| I64Store8(_, _)
| I64Store16(_, _)
| I64Store32(_, _) => {
// These instructions pop the address and the value.
stack.pop_values(2)?;
}
CurrentMemory(_) => {
// Pushes current memory size
stack.push_values(1)?;
}
GrowMemory(_) => {
// Grow memory takes the value of pages to grow and pushes
stack.pop_values(1)?;
stack.push_values(1)?;
}
I32Const(_) | I64Const(_) | F32Const(_) | F64Const(_) => {
// These instructions just push the single literal value onto the stack.
stack.push_values(1)?;
}
I32Eqz | I64Eqz => {
// These instructions pop the value and compare it against zero, and pushes
// the result of the comparison.
stack.pop_values(1)?;
stack.push_values(1)?;
}
I32Eq | I32Ne | I32LtS | I32LtU | I32GtS | I32GtU | I32LeS | I32LeU | I32GeS
| I32GeU | I64Eq | I64Ne | I64LtS | I64LtU | I64GtS | I64GtU | I64LeS | I64LeU
| I64GeS | I64GeU | F32Eq | F32Ne | F32Lt | F32Gt | F32Le | F32Ge | F64Eq | F64Ne
| F64Lt | F64Gt | F64Le | F64Ge => {
// Comparison operations take two operands and produce one result.
stack.pop_values(2)?;
stack.push_values(1)?;
}
I32Clz | I32Ctz | I32Popcnt | I64Clz | I64Ctz | I64Popcnt | F32Abs | F32Neg
| F32Ceil | F32Floor | F32Trunc | F32Nearest | F32Sqrt | F64Abs | F64Neg | F64Ceil
| F64Floor | F64Trunc | F64Nearest | F64Sqrt => {
// Unary operators take one operand and produce one result.
stack.pop_values(1)?;
stack.push_values(1)?;
}
I32Add | I32Sub | I32Mul | I32DivS | I32DivU | I32RemS | I32RemU | I32And | I32Or
| I32Xor | I32Shl | I32ShrS | I32ShrU | I32Rotl | I32Rotr | I64Add | I64Sub
| I64Mul | I64DivS | I64DivU | I64RemS | I64RemU | I64And | I64Or | I64Xor | I64Shl
| I64ShrS | I64ShrU | I64Rotl | I64Rotr | F32Add | F32Sub | F32Mul | F32Div
| F32Min | F32Max | F32Copysign | F64Add | F64Sub | F64Mul | F64Div | F64Min
| F64Max | F64Copysign => {
// Binary operators take two operands and produce one result.
stack.pop_values(2)?;
stack.push_values(1)?;
}
I32WrapI64 | I32TruncSF32 | I32TruncUF32 | I32TruncSF64 | I32TruncUF64
| I64ExtendSI32 | I64ExtendUI32 | I64TruncSF32 | I64TruncUF32 | I64TruncSF64
| I64TruncUF64 | F32ConvertSI32 | F32ConvertUI32 | F32ConvertSI64 | F32ConvertUI64
| F32DemoteF64 | F64ConvertSI32 | F64ConvertUI32 | F64ConvertSI64 | F64ConvertUI64
| F64PromoteF32 | I32ReinterpretF32 | I64ReinterpretF64 | F32ReinterpretI32
| F64ReinterpretI64 => {
// Conversion operators take one value and produce one result.
stack.pop_values(1)?;
stack.push_values(1)?;
}
}
pc += 1;
}
Ok(max_height)
}
#[cfg(test)]
mod tests {
extern crate wabt;
use parity_wasm::elements;
use super::*;
fn parse_wat(source: &str) -> elements::Module {
elements::deserialize_buffer(&wabt::wat2wasm(source).expect("Failed to wat2wasm"))
.expect("Failed to deserialize the module")
}
#[test]
fn simple_test() {
let module = parse_wat(
r#"
(module
(func
i32.const 1
i32.const 2
i32.const 3
drop
drop
drop
)
)
"#,
);
let height = compute(0, &module).unwrap();
assert_eq!(height, 3);
}
#[test]
fn implicit_and_explicit_return() {
let module = parse_wat(
r#"
(module
(func (result i32)
i32.const 0
return
)
)
"#,
);
let height = compute(0, &module).unwrap();
assert_eq!(height, 1);
}
#[test]
fn dont_count_in_unreachable() {
let module = parse_wat(
r#"
(module
(memory 0)
(func (result i32)
unreachable
grow_memory
)
)
"#,
);
let height = compute(0, &module).unwrap();
assert_eq!(height, 0);
}
#[test]
fn yet_another_test() {
const SOURCE: &'static str = r#"
(module
(memory 0)
(func
;; Push two values and then pop them.
;; This will make max depth to be equal to 2.
i32.const 0
i32.const 1
drop
drop
;; Code after `unreachable` shouldn't have an effect
;; on the max depth.
unreachable
i32.const 0
i32.const 1
i32.const 2
)
)
"#;
let module = elements::deserialize_buffer(&wabt::Wat2Wasm::new()
.validate(false)
.convert(SOURCE)
.expect("Failed to wat2wasm")
.as_ref())
.expect("Failed to deserialize the module");
let height = compute(0, &module).unwrap();
assert_eq!(height, 2);
}
}
-431
View File
@@ -1,431 +0,0 @@
//! The pass that tries to make stack overflows deterministic, by introducing
//! an upper bound of the stack size.
//!
//! This pass introduces a global mutable variable to track stack height,
//! and instruments all calls with preamble and postamble.
//!
//! Stack height is increased prior the call. Otherwise, the check would
//! be made after the stack frame is allocated.
//!
//! The preamble is inserted before the call. It increments
//! the global stack height variable with statically determined "stack cost"
//! of the callee. If after the increment the stack height exceeds
//! the limit (specified by the `rules`) then execution traps.
//! Otherwise, the call is executed.
//!
//! The postamble is inserted after the call. The purpose of the postamble is to decrease
//! the stack height by the "stack cost" of the callee function.
//!
//! Note, that we can't instrument all possible ways to return from the function. The simplest
//! example would be a trap issued by the host function.
//! That means stack height global won't be equal to zero upon the next execution after such trap.
//!
//! # Thunks
//!
//! Because stack height is increased prior the call few problems arises:
//!
//! - Stack height isn't increased upon an entry to the first function, i.e. exported function.
//! - Start function is executed externally (similar to exported functions).
//! - It is statically unknown what function will be invoked in an indirect call.
//!
//! The solution for this problems is to generate a intermediate functions, called 'thunks', which
//! will increase before and decrease the stack height after the call to original function, and
//! then make exported function and table entries, start section to point to a corresponding thunks.
//!
//! # Stack cost
//!
//! Stack cost of the function is calculated as a sum of it's locals
//! and the maximal height of the value stack.
//!
//! All values are treated equally, as they have the same size.
//!
//! The rationale for this it makes it possible to use this very naive wasm executor, that is:
//!
//! - values are implemented by a union, so each value takes a size equal to
//! the size of the largest possible value type this union can hold. (In MVP it is 8 bytes)
//! - each value from the value stack is placed on the native stack.
//! - each local variable and function argument is placed on the native stack.
//! - arguments pushed by the caller are copied into callee stack rather than shared
//! between the frames.
//! - upon entry into the function entire stack frame is allocated.
use std::string::String;
use std::vec::Vec;
use parity_wasm::elements::{self, Type};
use parity_wasm::builder;
/// Macro to generate preamble and postamble.
macro_rules! instrument_call {
($callee_idx: expr, $callee_stack_cost: expr, $stack_height_global_idx: expr, $stack_limit: expr) => {{
use $crate::parity_wasm::elements::Instruction::*;
[
// stack_height += stack_cost(F)
GetGlobal($stack_height_global_idx),
I32Const($callee_stack_cost),
I32Add,
SetGlobal($stack_height_global_idx),
// if stack_counter > LIMIT: unreachable
GetGlobal($stack_height_global_idx),
I32Const($stack_limit as i32),
I32GtU,
If(elements::BlockType::NoResult),
Unreachable,
End,
// Original call
Call($callee_idx),
// stack_height -= stack_cost(F)
GetGlobal($stack_height_global_idx),
I32Const($callee_stack_cost),
I32Sub,
SetGlobal($stack_height_global_idx),
]
}};
}
mod max_height;
mod thunk;
/// Error that occured during processing the module.
///
/// This means that the module is invalid.
#[derive(Debug)]
pub struct Error(String);
pub(crate) struct Context {
stack_height_global_idx: Option<u32>,
func_stack_costs: Option<Vec<u32>>,
stack_limit: u32,
}
impl Context {
/// Returns index in a global index space of a stack_height global variable.
///
/// Panics if it haven't generated yet.
fn stack_height_global_idx(&self) -> u32 {
self.stack_height_global_idx.expect(
"stack_height_global_idx isn't yet generated;
Did you call `inject_stack_counter_global`",
)
}
/// Returns `stack_cost` for `func_idx`.
///
/// Panics if stack costs haven't computed yet or `func_idx` is greater
/// than the last function index.
fn stack_cost(&self, func_idx: u32) -> Option<u32> {
self.func_stack_costs
.as_ref()
.expect(
"func_stack_costs isn't yet computed;
Did you call `compute_stack_costs`?",
)
.get(func_idx as usize)
.cloned()
}
/// Returns stack limit specified by the rules.
fn stack_limit(&self) -> u32 {
self.stack_limit
}
}
/// Instrument a module with stack height limiter.
///
/// See module-level documentation for more details.
///
/// # Errors
///
/// Returns `Err` if module is invalid and can't be
pub fn inject_limiter(
mut module: elements::Module,
stack_limit: u32,
) -> Result<elements::Module, Error> {
let mut ctx = Context {
stack_height_global_idx: None,
func_stack_costs: None,
stack_limit,
};
generate_stack_height_global(&mut ctx, &mut module);
compute_stack_costs(&mut ctx, &module)?;
instrument_functions(&mut ctx, &mut module)?;
let module = thunk::generate_thunks(&mut ctx, module)?;
Ok(module)
}
/// Generate a new global that will be used for tracking current stack height.
fn generate_stack_height_global(ctx: &mut Context, module: &mut elements::Module) {
let global_entry = builder::global()
.value_type()
.i32()
.mutable()
.init_expr(elements::Instruction::I32Const(0))
.build();
// Try to find an existing global section.
for section in module.sections_mut() {
if let elements::Section::Global(ref mut gs) = *section {
gs.entries_mut().push(global_entry);
let stack_height_global_idx = (gs.entries().len() as u32) - 1;
ctx.stack_height_global_idx = Some(stack_height_global_idx);
return;
}
}
// Existing section not found, create one!
module.sections_mut().push(elements::Section::Global(
elements::GlobalSection::with_entries(vec![global_entry]),
));
ctx.stack_height_global_idx = Some(0);
}
/// Calculate stack costs for all functions.
///
/// Returns a vector with a stack cost for each function, including imports.
fn compute_stack_costs(ctx: &mut Context, module: &elements::Module) -> Result<(), Error> {
let func_imports = module.import_count(elements::ImportCountType::Function);
let mut func_stack_costs = vec![0; module.functions_space()];
// TODO: optimize!
for (func_idx, func_stack_cost) in func_stack_costs.iter_mut().enumerate() {
// We can't calculate stack_cost of the import functions.
if func_idx >= func_imports {
*func_stack_cost = compute_stack_cost(func_idx as u32, &module)?;
}
}
ctx.func_stack_costs = Some(func_stack_costs);
Ok(())
}
/// Stack cost of the given *defined* function is the sum of it's locals count (that is,
/// number of arguments plus number of local variables) and the maximal stack
/// height.
fn compute_stack_cost(func_idx: u32, module: &elements::Module) -> Result<u32, Error> {
// To calculate the cost of a function we need to convert index from
// function index space to defined function spaces.
let func_imports = module.import_count(elements::ImportCountType::Function) as u32;
let defined_func_idx = func_idx.checked_sub(func_imports).ok_or_else(|| {
Error("This should be a index of a defined function".into())
})?;
let code_section = module.code_section().ok_or_else(|| {
Error("Due to validation code section should exists".into())
})?;
let body = &code_section
.bodies()
.get(defined_func_idx as usize)
.ok_or_else(|| Error("Function body is out of bounds".into()))?;
let locals_count = body.locals().len() as u32;
let max_stack_height =
max_height::compute(
defined_func_idx,
module
)?;
Ok(locals_count + max_stack_height)
}
fn instrument_functions(ctx: &mut Context, module: &mut elements::Module) -> Result<(), Error> {
for section in module.sections_mut() {
if let elements::Section::Code(ref mut code_section) = *section {
for func_body in code_section.bodies_mut() {
let mut opcodes = func_body.code_mut();
instrument_function(ctx, opcodes)?;
}
}
}
Ok(())
}
/// This function searches `call` instructions and wrap each call
/// with preamble and postamble.
///
/// Before:
///
/// ```text
/// get_local 0
/// get_local 1
/// call 228
/// drop
/// ```
///
/// After:
///
/// ```text
/// get_local 0
/// get_local 1
///
/// < ... preamble ... >
///
/// call 228
///
/// < .. postamble ... >
///
/// drop
/// ```
fn instrument_function(
ctx: &mut Context,
instructions: &mut elements::Instructions,
) -> Result<(), Error> {
use parity_wasm::elements::Instruction::*;
let mut cursor = 0;
loop {
if cursor >= instructions.elements().len() {
break;
}
enum Action {
InstrumentCall {
callee_idx: u32,
callee_stack_cost: u32,
},
Nop,
}
let action: Action = {
let instruction = &instructions.elements()[cursor];
match *instruction {
Call(ref callee_idx) => {
let callee_stack_cost = ctx
.stack_cost(*callee_idx)
.ok_or_else(||
Error(
format!("Call to function that out-of-bounds: {}", callee_idx)
)
)?;
// Instrument only calls to a functions which stack_cost is
// non-zero.
if callee_stack_cost > 0 {
Action::InstrumentCall {
callee_idx: *callee_idx,
callee_stack_cost,
}
} else {
Action::Nop
}
},
_ => Action::Nop,
}
};
match action {
// We need to wrap a `call idx` instruction
// with a code that adjusts stack height counter
// and then restores it.
Action::InstrumentCall { callee_idx, callee_stack_cost } => {
let new_seq = instrument_call!(
callee_idx,
callee_stack_cost as i32,
ctx.stack_height_global_idx(),
ctx.stack_limit()
);
// Replace the original `call idx` instruction with
// a wrapped call sequence.
//
// To splice actually take a place, we need to consume iterator
// splice returns. So we just `count()` it.
let _ = instructions
.elements_mut()
.splice(cursor..(cursor + 1), new_seq.iter().cloned())
.count();
// Advance cursor to be after the inserted sequence.
cursor += new_seq.len();
}
// Do nothing for other instructions.
_ => {
cursor += 1;
}
}
}
Ok(())
}
fn resolve_func_type(
func_idx: u32,
module: &elements::Module,
) -> Result<&elements::FunctionType, Error> {
let types = module.type_section().map(|ts| ts.types()).unwrap_or(&[]);
let functions = module
.function_section()
.map(|fs| fs.entries())
.unwrap_or(&[]);
let func_imports = module.import_count(elements::ImportCountType::Function);
let sig_idx = if func_idx < func_imports as u32 {
module
.import_section()
.expect("function import count is not zero; import section must exists; qed")
.entries()
.iter()
.filter_map(|entry| match *entry.external() {
elements::External::Function(ref idx) => Some(*idx),
_ => None,
})
.nth(func_idx as usize)
.expect(
"func_idx is less than function imports count;
nth function import must be `Some`;
qed",
)
} else {
functions
.get(func_idx as usize - func_imports)
.ok_or_else(|| Error(format!("Function at index {} is not defined", func_idx)))?
.type_ref()
};
let Type::Function(ref ty) = *types.get(sig_idx as usize).ok_or_else(|| {
Error(format!(
"Signature {} (specified by func {}) isn't defined",
sig_idx, func_idx
))
})?;
Ok(ty)
}
#[cfg(test)]
mod tests {
extern crate wabt;
use parity_wasm::elements;
use super::*;
fn parse_wat(source: &str) -> elements::Module {
elements::deserialize_buffer(&wabt::wat2wasm(source).expect("Failed to wat2wasm"))
.expect("Failed to deserialize the module")
}
fn validate_module(module: elements::Module) {
let binary = elements::serialize(module).expect("Failed to serialize");
wabt::Module::read_binary(&binary, &Default::default())
.expect("Wabt failed to read final binary")
.validate()
.expect("Invalid module");
}
#[test]
fn test_with_params_and_result() {
let module = parse_wat(
r#"
(module
(func (export "i32.add") (param i32 i32) (result i32)
get_local 0
get_local 1
i32.add
)
)
"#,
);
let module = inject_limiter(module, 1024)
.expect("Failed to inject stack counter");
validate_module(module);
}
}
-167
View File
@@ -1,167 +0,0 @@
#[cfg(features = "std")]
use std::collections::{HashMap as Map};
#[cfg(not(features = "std"))]
use std::collections::{BTreeMap as Map};
use std::vec::Vec;
use parity_wasm::elements::{self, FunctionType, Internal};
use parity_wasm::builder;
use super::{resolve_func_type, Context, Error};
struct Thunk {
signature: FunctionType,
// Index in function space of this thunk.
idx: Option<u32>,
original_func_idx: u32,
callee_stack_cost: u32,
}
pub(crate) fn generate_thunks(
ctx: &mut Context,
module: elements::Module,
) -> Result<elements::Module, Error> {
// First, we need to collect all function indicies that should be replaced by thunks
// Function indicies which needs to generate thunks.
let mut need_thunks: Vec<u32> = Vec::new();
let mut replacement_map: Map<u32, Thunk> = {
let exports = module
.export_section()
.map(|es| es.entries())
.unwrap_or(&[]);
let elem_segments = module
.elements_section()
.map(|es| es.entries())
.unwrap_or(&[]);
let start_func_idx = module
.start_section();
let exported_func_indicies = exports.iter().filter_map(|entry| match *entry.internal() {
Internal::Function(ref function_idx) => Some(*function_idx),
_ => None,
});
let table_func_indicies = elem_segments
.iter()
.flat_map(|segment| segment.members())
.cloned();
// Replacement map is at least export section size.
let mut replacement_map: Map<u32, Thunk> = Map::new();
for func_idx in exported_func_indicies.chain(table_func_indicies).chain(start_func_idx.into_iter()) {
let callee_stack_cost = ctx.stack_cost(func_idx).ok_or_else(|| {
Error(format!("function with idx {} isn't found", func_idx))
})?;
// Don't generate a thunk if stack_cost of a callee is zero.
if callee_stack_cost != 0 {
need_thunks.push(func_idx);
replacement_map.insert(func_idx, Thunk {
signature: resolve_func_type(func_idx, &module)?.clone(),
idx: None,
callee_stack_cost,
original_func_idx: func_idx,
});
}
}
replacement_map
};
// Then, we generate a thunk for each original function.
// Save current func_idx
let mut next_func_idx = module.functions_space() as u32;
let mut mbuilder = builder::from_module(module);
for func_idx in need_thunks {
let mut thunk = replacement_map
.get_mut(&func_idx)
.expect(
"`func_idx` should come from `need_thunks`;
`need_thunks` is populated with the same items that in `replacement_map`;
qed"
);
let instrumented_call = instrument_call!(
thunk.original_func_idx as u32,
thunk.callee_stack_cost as i32,
ctx.stack_height_global_idx(),
ctx.stack_limit()
);
// Thunk body consist of:
// - argument pushing
// - instrumented call
// - end
let mut thunk_body: Vec<elements::Instruction> = Vec::with_capacity(
thunk.signature.params().len() +
instrumented_call.len() +
1
);
for (arg_idx, _) in thunk.signature.params().iter().enumerate() {
thunk_body.push(elements::Instruction::GetLocal(arg_idx as u32));
}
thunk_body.extend(instrumented_call.iter().cloned());
thunk_body.push(elements::Instruction::End);
// TODO: Don't generate a signature, but find an existing one.
mbuilder = mbuilder.function()
// Signature of the thunk should match the original function signature.
.signature()
.with_params(thunk.signature.params().to_vec())
.with_return_type(thunk.signature.return_type().clone())
.build()
.body()
.with_instructions(elements::Instructions::new(
thunk_body
))
.build()
.build();
thunk.idx = Some(next_func_idx);
next_func_idx += 1;
}
let mut module = mbuilder.build();
// And finally, fixup thunks in export and table sections.
// Fixup original function index to a index of a thunk generated earlier.
let fixup = |function_idx: &mut u32| {
// Check whether this function is in replacement_map, since
// we can skip thunk generation (e.g. if stack_cost of function is 0).
if let Some(ref thunk) = replacement_map.get(function_idx) {
*function_idx = thunk
.idx
.expect("At this point an index must be assigned to each thunk");
}
};
for section in module.sections_mut() {
match *section {
elements::Section::Export(ref mut export_section) => {
for entry in export_section.entries_mut() {
if let Internal::Function(ref mut function_idx) = *entry.internal_mut() {
fixup(function_idx)
}
}
}
elements::Section::Element(ref mut elem_section) => {
for segment in elem_section.entries_mut() {
for function_idx in segment.members_mut() {
fixup(function_idx)
}
}
}
elements::Section::Start(ref mut start_idx) => {
fixup(start_idx)
}
_ => {}
}
}
Ok(module)
}
+679
View File
@@ -0,0 +1,679 @@
use super::resolve_func_type;
use alloc::vec::Vec;
use parity_wasm::elements::{self, BlockType, Type};
#[cfg(feature = "sign_ext")]
use parity_wasm::elements::SignExtInstruction;
// The cost in stack items that should be charged per call of a function. This is
// is a static cost that is added to each function call. This makes sense because even
// if a function does not use any parameters or locals some stack space on the host
// machine might be consumed to hold some context.
const ACTIVATION_FRAME_COST: u32 = 1;
#[derive(Debug,PartialEq,Default,Clone,Copy)]
pub struct StackHeightStats {
pub activation_cost: u32,
pub max_height: u32,
pub max_control_height: u32,
pub locals_count: u32,
pub params_count: u32,
pub blocks_count: u32,
pub condbr_count: u32,
pub push_count: u32,
pub local_set_count: u32,
pub opcode_count: u32,
pub total_cost: u32,
}
/// Control stack frame.
#[derive(Debug)]
struct Frame {
/// Counts the nesting level of unreachable code. 0 if currently processed code is reachable
unreachable_depth: u32,
/// Count of values which will be pushed after the exit
/// from the current block.
end_arity: u32,
/// Count of values which should be poped upon a branch to
/// this frame.
///
/// This might be diffirent from `end_arity` since branch
/// to the loop header can't take any values.
branch_arity: u32,
/// Stack height before entering in the block.
start_height: u32,
}
/// This is a compound stack that abstracts tracking height of the value stack
/// and manipulation of the control stack.
struct Stack {
height: u32,
control_stack: Vec<Frame>,
}
impl Stack {
fn new() -> Stack {
Stack { height: 0, control_stack: Vec::new() }
}
/// Returns current height of the value stack.
fn height(&self) -> u32 {
self.height
}
fn control_height(&self) -> u32 {
self.control_stack.len() as u32
}
/// Returns a reference to a frame by specified depth relative to the top of
/// control stack.
fn frame(&self, rel_depth: u32) -> Result<&Frame, &'static str> {
let control_stack_height: usize = self.control_stack.len();
let last_idx = control_stack_height.checked_sub(1).ok_or("control stack is empty")?;
let idx = last_idx.checked_sub(rel_depth as usize).ok_or("control stack out-of-bounds")?;
Ok(&self.control_stack[idx])
}
/// Mark successive instructions as unreachable.
fn mark_unreachable(&mut self) -> Result<(), &'static str> {
let top_frame = self.control_stack.last_mut().ok_or("control stack must be non-empty")?;
top_frame.unreachable_depth = 1;
Ok(())
}
/// Increase nesting level of unreachable code
fn push_unreachable(&mut self) -> Result<(), &'static str> {
let top_frame = self.control_stack.last_mut().ok_or("control stack must be non-empty")?;
top_frame.unreachable_depth += 1;
Ok(())
}
/// Decrease nesting level of unrechable code (probably making it reachable)
fn pop_unreachable(&mut self) -> Result<u32, &'static str> {
let top_frame = self.control_stack.last_mut().ok_or("control stack must be non-empty")?;
top_frame.unreachable_depth =
top_frame.unreachable_depth.checked_sub(1).ok_or("unreachable code underflow")?;
Ok(top_frame.unreachable_depth)
}
/// Push control frame into the control stack.
fn push_frame(&mut self, frame: Frame) {
self.control_stack.push(frame);
}
/// Pop control frame from the control stack.
///
/// Returns `Err` if the control stack is empty.
fn pop_frame(&mut self) -> Result<Frame, &'static str> {
self.control_stack.pop().ok_or("stack must be non-empty")
}
/// Truncate the height of value stack to the specified height.
fn trunc(&mut self, new_height: u32) {
self.height = new_height;
}
/// Push specified number of values into the value stack.
///
/// Returns `Err` if the height overflow usize value.
fn push_values(&mut self, value_count: u32) -> Result<(), &'static str> {
self.height = self.height.checked_add(value_count).ok_or("stack overflow")?;
Ok(())
}
/// Pop specified number of values from the value stack.
///
/// Returns `Err` if the stack happen to be negative value after
/// values popped.
fn pop_values(&mut self, value_count: u32) -> Result<(), &'static str> {
if value_count == 0 {
return Ok(())
}
self.height = self.height.checked_sub(value_count).ok_or("stack underflow")?;
Ok(())
}
}
/// This function expects the function to be validated.
pub fn compute(func_idx: u32, module: &elements::Module) -> Result<StackHeightStats, &'static str> {
use parity_wasm::elements::Instruction::*;
trace!("Processing function index {}", func_idx);
let func_section = module.function_section().ok_or("No function section")?;
let code_section = module.code_section().ok_or("No code section")?;
let type_section = module.type_section().ok_or("No type section")?;
// Get a signature and a body of the specified function.
let func_sig_idx = func_section
.entries()
.get(func_idx as usize)
.ok_or("Function is not found in func section")?
.type_ref();
let Type::Function(func_signature) = type_section
.types()
.get(func_sig_idx as usize)
.ok_or("Function is not found in func section")?;
let body = code_section
.bodies()
.get(func_idx as usize)
.ok_or("Function body for the index isn't found")?;
let instructions = body.code();
let mut stack = Stack::new();
let mut max_height: u32 = 0;
let mut max_control_height: u32 = 0;
let mut locals_count: u32 = 0;
for local_group in body.locals() {
locals_count =
locals_count.checked_add(local_group.count()).ok_or("Overflow in local count")?;
}
let params_count = func_signature.params().len() as u32;
let mut blocks_count = 0u32;
let mut condbr_count = 0u32;
let mut push_count = 0u32;
let mut local_set_count = 0u32;
// Add implicit frame for the function. Breaks to this frame and execution of
// the last end should deal with this frame.
let func_arity = func_signature.results().len() as u32;
stack.push_frame(Frame {
unreachable_depth: 0,
end_arity: func_arity,
branch_arity: func_arity,
start_height: 0,
});
for opcode in instructions.elements() {
if stack.frame(0)?.unreachable_depth > 0 {
match opcode {
Block(_) | Loop(_) | If(_) => {
trace!("Entering unreachable block {:?}", opcode);
stack.push_unreachable()?;
continue
},
Else => {
let depth = stack.pop_unreachable()?;
if depth == 0 {
trace!("Transiting from unreachable If body to reachable Else block");
} else {
trace!("Processing unreachable Else");
stack.push_unreachable()?;
continue
}
},
End => {
let depth = stack.pop_unreachable()?;
if depth == 0 {
trace!("Exiting unreachable code");
} else {
trace!("Exiting unreachable block");
continue
}
},
_ => {
trace!("Skipping unreachable instruction {:?}", opcode);
continue
},
}
}
assert_eq!(stack.frame(0)?.unreachable_depth, 0);
trace!("Processing opcode {:?}", opcode);
match opcode {
Nop => {},
Block(ty) | Loop(ty) | If(ty) => {
let end_arity = if *ty == BlockType::NoResult { 0 } else { 1 };
let branch_arity = if let Loop(_) = *opcode { 0 } else { end_arity };
if let If(_) = *opcode {
stack.pop_values(1)?;
}
stack.push_frame(Frame {
unreachable_depth: 0,
end_arity,
branch_arity,
start_height: stack.height(),
});
blocks_count += 1;
},
Else => {
let frame = stack.pop_frame()?;
stack.trunc(frame.start_height);
stack.push_frame(frame);
},
End => {
let frame = stack.pop_frame()?;
stack.trunc(frame.start_height);
stack.push_values(frame.end_arity)?;
},
Unreachable => {
stack.mark_unreachable()?;
},
Br(target) => {
// Pop values for the destination block result.
let target_arity = stack.frame(*target)?.branch_arity;
stack.pop_values(target_arity)?;
// This instruction unconditionally transfers control to the specified block,
// thus all instruction until the end of the current block is deemed unreachable
stack.mark_unreachable()?;
},
BrIf(target) => {
// Pop values for the destination block result.
let target_arity = stack.frame(*target)?.branch_arity;
stack.pop_values(target_arity)?;
// Pop condition value.
stack.pop_values(1)?;
// Push values back.
stack.push_values(target_arity)?;
condbr_count += 1;
},
BrTable(br_table_data) => {
let arity_of_default = stack.frame(br_table_data.default)?.branch_arity;
// Because all jump targets have an equal arities, we can just take arity of
// the default branch.
stack.pop_values(arity_of_default)?;
// This instruction doesn't let control flow to go further, since the control flow
// should take either one of branches depending on the value or the default branch.
stack.mark_unreachable()?;
condbr_count += 1;
},
Return => {
// Pop return values of the function. Mark successive instructions as unreachable
// since this instruction doesn't let control flow to go further.
stack.pop_values(func_arity)?;
stack.mark_unreachable()?;
},
Call(idx) => {
let ty = resolve_func_type(*idx, module)?;
// Pop values for arguments of the function.
stack.pop_values(ty.params().len() as u32)?;
// Push result of the function execution to the stack.
let callee_arity = ty.results().len() as u32;
stack.push_values(callee_arity)?;
},
CallIndirect(x, _) => {
let Type::Function(ty) =
type_section.types().get(*x as usize).ok_or("Type not found")?;
// Pop the offset into the function table.
stack.pop_values(1)?;
// Pop values for arguments of the function.
stack.pop_values(ty.params().len() as u32)?;
// Push result of the function execution to the stack.
let callee_arity = ty.results().len() as u32;
stack.push_values(callee_arity)?;
},
Drop => {
stack.pop_values(1)?;
},
Select => {
// Pop two values and one condition.
stack.pop_values(2)?;
stack.pop_values(1)?;
// Push the selected value.
stack.push_values(1)?;
push_count += 1;
},
GetLocal(_) => {
stack.push_values(1)?;
push_count += 1;
},
SetLocal(_) => {
stack.pop_values(1)?;
local_set_count += 1;
},
TeeLocal(_) => {
// This instruction pops and pushes the value, so
// effectively it doesn't modify the stack height.
stack.pop_values(1)?;
stack.push_values(1)?;
local_set_count += 1;
},
GetGlobal(_) => {
stack.push_values(1)?;
push_count += 1;
},
SetGlobal(_) => {
stack.pop_values(1)?;
},
I32Load(_, _) |
I64Load(_, _) |
F32Load(_, _) |
F64Load(_, _) |
I32Load8S(_, _) |
I32Load8U(_, _) |
I32Load16S(_, _) |
I32Load16U(_, _) |
I64Load8S(_, _) |
I64Load8U(_, _) |
I64Load16S(_, _) |
I64Load16U(_, _) |
I64Load32S(_, _) |
I64Load32U(_, _) => {
// These instructions pop the address and pushes the result,
// which effictively don't modify the stack height.
stack.pop_values(1)?;
stack.push_values(1)?;
push_count += 1;
},
I32Store(_, _) |
I64Store(_, _) |
F32Store(_, _) |
F64Store(_, _) |
I32Store8(_, _) |
I32Store16(_, _) |
I64Store8(_, _) |
I64Store16(_, _) |
I64Store32(_, _) => {
// These instructions pop the address and the value.
stack.pop_values(2)?;
},
CurrentMemory(_) => {
// Pushes current memory size
stack.push_values(1)?;
push_count += 1;
},
GrowMemory(_) => {
// Grow memory takes the value of pages to grow and pushes
stack.pop_values(1)?;
stack.push_values(1)?;
push_count += 1;
},
I32Const(_) | I64Const(_) | F32Const(_) | F64Const(_) => {
// These instructions just push the single literal value onto the stack.
stack.push_values(1)?;
push_count += 1;
},
I32Eqz | I64Eqz => {
// These instructions pop the value and compare it against zero, and pushes
// the result of the comparison.
stack.pop_values(1)?;
stack.push_values(1)?;
push_count += 1;
},
I32Eq | I32Ne | I32LtS | I32LtU | I32GtS | I32GtU | I32LeS | I32LeU | I32GeS |
I32GeU | I64Eq | I64Ne | I64LtS | I64LtU | I64GtS | I64GtU | I64LeS | I64LeU |
I64GeS | I64GeU | F32Eq | F32Ne | F32Lt | F32Gt | F32Le | F32Ge | F64Eq | F64Ne |
F64Lt | F64Gt | F64Le | F64Ge => {
// Comparison operations take two operands and produce one result.
stack.pop_values(2)?;
stack.push_values(1)?;
push_count += 1;
},
I32Clz | I32Ctz | I32Popcnt | I64Clz | I64Ctz | I64Popcnt | F32Abs | F32Neg |
F32Ceil | F32Floor | F32Trunc | F32Nearest | F32Sqrt | F64Abs | F64Neg | F64Ceil |
F64Floor | F64Trunc | F64Nearest | F64Sqrt => {
// Unary operators take one operand and produce one result.
stack.pop_values(1)?;
stack.push_values(1)?;
push_count += 1;
},
I32Add | I32Sub | I32Mul | I32DivS | I32DivU | I32RemS | I32RemU | I32And | I32Or |
I32Xor | I32Shl | I32ShrS | I32ShrU | I32Rotl | I32Rotr | I64Add | I64Sub |
I64Mul | I64DivS | I64DivU | I64RemS | I64RemU | I64And | I64Or | I64Xor | I64Shl |
I64ShrS | I64ShrU | I64Rotl | I64Rotr | F32Add | F32Sub | F32Mul | F32Div |
F32Min | F32Max | F32Copysign | F64Add | F64Sub | F64Mul | F64Div | F64Min |
F64Max | F64Copysign => {
// Binary operators take two operands and produce one result.
stack.pop_values(2)?;
stack.push_values(1)?;
push_count += 1;
},
I32WrapI64 | I32TruncSF32 | I32TruncUF32 | I32TruncSF64 | I32TruncUF64 |
I64ExtendSI32 | I64ExtendUI32 | I64TruncSF32 | I64TruncUF32 | I64TruncSF64 |
I64TruncUF64 | F32ConvertSI32 | F32ConvertUI32 | F32ConvertSI64 | F32ConvertUI64 |
F32DemoteF64 | F64ConvertSI32 | F64ConvertUI32 | F64ConvertSI64 | F64ConvertUI64 |
F64PromoteF32 | I32ReinterpretF32 | I64ReinterpretF64 | F32ReinterpretI32 |
F64ReinterpretI64 => {
// Conversion operators take one value and produce one result.
stack.pop_values(1)?;
stack.push_values(1)?;
push_count += 1;
},
#[cfg(feature = "sign_ext")]
SignExt(SignExtInstruction::I32Extend8S) |
SignExt(SignExtInstruction::I32Extend16S) |
SignExt(SignExtInstruction::I64Extend8S) |
SignExt(SignExtInstruction::I64Extend16S) |
SignExt(SignExtInstruction::I64Extend32S) => {
stack.pop_values(1)?;
stack.push_values(1)?;
push_count += 1;
},
}
// If current value/control stack is higher than maximal height observed so far,
// save the new height.
if stack.height() > max_height {
max_height = stack.height();
}
if stack.control_height() > max_control_height {
max_control_height = stack.control_height();
}
trace!(
" Stack height: {}, control stack height: {}",
stack.height(),
stack.control_height()
);
}
assert_eq!(stack.control_height(), 0);
assert_eq!(stack.height(), func_signature.results().len() as u32);
let res = StackHeightStats {
activation_cost: ACTIVATION_FRAME_COST,
max_height: max_height,
max_control_height: max_control_height,
locals_count: locals_count,
params_count: params_count,
blocks_count: blocks_count,
condbr_count: condbr_count,
push_count: push_count,
local_set_count: local_set_count,
opcode_count: instructions.elements().len() as u32,
// total_cost: (11.749 * params_count as f64 - 0.4888 * locals_count as f64 + 14.8169 * max_height as f64 - 5.1594 * max_control_height as f64 - 24.4941) as u32
total_cost: ACTIVATION_FRAME_COST + 2 * max_height + max_control_height + locals_count + 2 * params_count,
};
trace!("Result: {:?}", res);
Ok(res)
}
// #[cfg(test)]
// mod tests {
// use super::*;
// use parity_wasm::elements;
// fn parse_wat(source: &str) -> elements::Module {
// elements::deserialize_buffer(&wat::parse_str(source).expect("Failed to wat2wasm"))
// .expect("Failed to deserialize the module")
// }
// #[test]
// fn simple_test() {
// let module = parse_wat(
// r#"
// (module
// (func
// i32.const 1
// i32.const 2
// i32.const 3
// drop
// drop
// drop
// )
// )
// "#,
// );
// let height = compute(0, &module).unwrap();
// assert_eq!(height, ACTIVATION_FRAME_COST + 3 + 1 + 0 + 0);
// }
// #[test]
// fn implicit_and_explicit_return() {
// let module = parse_wat(
// r#"
// (module
// (func (result i32)
// i32.const 0
// return
// )
// )
// "#,
// );
// let height = compute(0, &module).unwrap();
// assert_eq!(height, ACTIVATION_FRAME_COST + 1 + 1 + 0 + 0);
// }
// #[test]
// fn dont_count_in_unreachable() {
// let module = parse_wat(
// r#"
// (module
// (memory 0)
// (func (result i32)
// unreachable
// grow_memory
// )
// )
// "#,
// );
// let height = compute(0, &module).unwrap();
// assert_eq!(height, ACTIVATION_FRAME_COST + 0 + 1 + 0 + 0);
// }
// #[test]
// fn yet_another_test() {
// let module = parse_wat(
// r#"
// (module
// (memory 0)
// (func
// ;; Push two values and then pop them.
// ;; This will make max depth to be equal to 2.
// i32.const 0
// i32.const 1
// drop
// drop
// ;; Code after `unreachable` shouldn't have an effect
// ;; on the max depth.
// unreachable
// i32.const 0
// i32.const 1
// i32.const 2
// )
// )
// "#,
// );
// let height = compute(0, &module).unwrap();
// assert_eq!(height, 2 + ACTIVATION_FRAME_COST);
// }
// #[test]
// fn call_indirect() {
// let module = parse_wat(
// r#"
// (module
// (table $ptr 1 1 funcref)
// (elem $ptr (i32.const 0) func 1)
// (func $main
// (call_indirect (i32.const 0))
// (call_indirect (i32.const 0))
// (call_indirect (i32.const 0))
// )
// (func $callee
// i64.const 42
// drop
// )
// )
// "#,
// );
// let height = compute(0, &module).unwrap();
// assert_eq!(height, 1 + ACTIVATION_FRAME_COST);
// }
// #[test]
// fn breaks() {
// let module = parse_wat(
// r#"
// (module
// (func $main
// block (result i32)
// block (result i32)
// i32.const 99
// br 1
// end
// end
// drop
// )
// )
// "#,
// );
// let height = compute(0, &module).unwrap();
// assert_eq!(height, 1 + ACTIVATION_FRAME_COST);
// }
// #[test]
// fn if_else_works() {
// let module = parse_wat(
// r#"
// (module
// (func $main
// i32.const 7
// i32.const 1
// if (result i32)
// i32.const 42
// else
// i32.const 99
// end
// i32.const 97
// drop
// drop
// drop
// )
// )
// "#,
// );
// let height = compute(0, &module).unwrap();
// assert_eq!(height, 3 + ACTIVATION_FRAME_COST);
// }
// }
+368
View File
@@ -0,0 +1,368 @@
//! Contains the code for the stack height limiter instrumentation.
use alloc::{vec, vec::Vec};
use core::mem;
use parity_wasm::{
builder,
elements::{self, Instruction, Instructions, Type},
};
pub use max_height::StackHeightStats;
/// Macro to generate preamble and postamble.
macro_rules! instrument_call {
($callee_idx: expr, $callee_stack_cost: expr, $stack_height_global_idx: expr, $stack_limit: expr) => {{
use $crate::parity_wasm::elements::Instruction::*;
[
// stack_height += stack_cost(F)
GetGlobal($stack_height_global_idx),
I32Const($callee_stack_cost),
I32Add,
SetGlobal($stack_height_global_idx),
// if stack_counter > LIMIT: unreachable
GetGlobal($stack_height_global_idx),
I32Const($stack_limit as i32),
I32GtU,
If(elements::BlockType::NoResult),
Unreachable,
End,
// Original call
Call($callee_idx),
// stack_height -= stack_cost(F)
GetGlobal($stack_height_global_idx),
I32Const($callee_stack_cost),
I32Sub,
SetGlobal($stack_height_global_idx),
]
}};
}
mod max_height;
mod thunk;
pub struct Context {
stack_height_global_idx: u32,
func_stack_costs: Vec<StackHeightStats>,
stack_limit: u32,
}
impl Context {
/// Returns index in a global index space of a stack_height global variable.
fn stack_height_global_idx(&self) -> u32 {
self.stack_height_global_idx
}
/// Returns `stack_cost` for `func_idx`.
fn stack_cost(&self, func_idx: u32) -> Option<u32> {
if let Some(stats) = self.func_stack_costs.get(func_idx as usize) {
Some(stats.total_cost)
} else {
None
}
}
/// Returns stack limit specified by the rules.
fn stack_limit(&self) -> u32 {
self.stack_limit
}
}
/// Inject the instumentation that makes stack overflows deterministic, by introducing
/// an upper bound of the stack size.
///
/// This pass introduces a global mutable variable to track stack height,
/// and instruments all calls with preamble and postamble.
///
/// Stack height is increased prior the call. Otherwise, the check would
/// be made after the stack frame is allocated.
///
/// The preamble is inserted before the call. It increments
/// the global stack height variable with statically determined "stack cost"
/// of the callee. If after the increment the stack height exceeds
/// the limit (specified by the `rules`) then execution traps.
/// Otherwise, the call is executed.
///
/// The postamble is inserted after the call. The purpose of the postamble is to decrease
/// the stack height by the "stack cost" of the callee function.
///
/// Note, that we can't instrument all possible ways to return from the function. The simplest
/// example would be a trap issued by the host function.
/// That means stack height global won't be equal to zero upon the next execution after such trap.
///
/// # Thunks
///
/// Because stack height is increased prior the call few problems arises:
///
/// - Stack height isn't increased upon an entry to the first function, i.e. exported function.
/// - Start function is executed externally (similar to exported functions).
/// - It is statically unknown what function will be invoked in an indirect call.
///
/// The solution for this problems is to generate a intermediate functions, called 'thunks', which
/// will increase before and decrease the stack height after the call to original function, and
/// then make exported function and table entries, start section to point to a corresponding thunks.
///
/// # Stack cost
///
/// Stack cost of the function is calculated as a sum of it's locals
/// and the maximal height of the value stack.
///
/// All values are treated equally, as they have the same size.
///
/// The rationale is that this makes it possible to use the following very naive wasm executor:
///
/// - values are implemented by a union, so each value takes a size equal to the size of the largest
/// possible value type this union can hold. (In MVP it is 8 bytes)
/// - each value from the value stack is placed on the native stack.
/// - each local variable and function argument is placed on the native stack.
/// - arguments pushed by the caller are copied into callee stack rather than shared between the
/// frames.
/// - upon entry into the function entire stack frame is allocated.
pub fn inject(
mut module: elements::Module,
stack_limit: u32,
) -> Result<elements::Module, &'static str> {
let mut ctx = Context {
stack_height_global_idx: generate_stack_height_global(&mut module),
func_stack_costs: compute_stack_costs(&module)?,
stack_limit,
};
instrument_functions(&mut ctx, &mut module)?;
let module = thunk::generate_thunks(&mut ctx, module)?;
Ok(module)
}
/// Generate a new global that will be used for tracking current stack height.
fn generate_stack_height_global(module: &mut elements::Module) -> u32 {
let global_entry = builder::global()
.value_type()
.i32()
.mutable()
.init_expr(Instruction::I32Const(0))
.build();
// Try to find an existing global section.
for section in module.sections_mut() {
if let elements::Section::Global(gs) = section {
gs.entries_mut().push(global_entry);
return (gs.entries().len() as u32) - 1
}
}
// Existing section not found, create one!
module
.sections_mut()
.push(elements::Section::Global(elements::GlobalSection::with_entries(vec![global_entry])));
0
}
/// Calculate stack costs for all functions.
///
/// Returns a vector with a stack cost for each function, including imports.
fn compute_stack_costs(module: &elements::Module) -> Result<Vec<StackHeightStats>, &'static str> {
let func_imports = module.import_count(elements::ImportCountType::Function);
// TODO: optimize!
(0..module.functions_space())
.map(|func_idx| {
if func_idx < func_imports {
// We can't calculate stack_cost of the import functions.
Ok(Default::default())
} else {
compute_stack_cost(func_idx as u32, module)
}
})
.collect()
}
/// Stack cost of the given *defined* function is the sum of it's locals count (that is,
/// number of arguments plus number of local variables) and the maximal stack
/// height.
pub fn compute_stack_cost(func_idx: u32, module: &elements::Module) -> Result<StackHeightStats, &'static str> {
// To calculate the cost of a function we need to convert index from
// function index space to defined function spaces.
let func_imports = module.import_count(elements::ImportCountType::Function) as u32;
let defined_func_idx = func_idx
.checked_sub(func_imports)
.ok_or("This should be a index of a defined function")?;
max_height::compute(defined_func_idx, module)
}
fn instrument_functions(
ctx: &mut Context,
module: &mut elements::Module,
) -> Result<(), &'static str> {
for section in module.sections_mut() {
if let elements::Section::Code(code_section) = section {
for func_body in code_section.bodies_mut() {
let opcodes = func_body.code_mut();
instrument_function(ctx, opcodes)?;
}
}
}
Ok(())
}
/// This function searches `call` instructions and wrap each call
/// with preamble and postamble.
///
/// Before:
///
/// ```text
/// get_local 0
/// get_local 1
/// call 228
/// drop
/// ```
///
/// After:
///
/// ```text
/// get_local 0
/// get_local 1
///
/// < ... preamble ... >
///
/// call 228
///
/// < .. postamble ... >
///
/// drop
/// ```
fn instrument_function(ctx: &mut Context, func: &mut Instructions) -> Result<(), &'static str> {
use Instruction::*;
struct InstrumentCall {
offset: usize,
callee: u32,
cost: u32,
}
let calls: Vec<_> = func
.elements()
.iter()
.enumerate()
.filter_map(|(offset, instruction)| {
if let Call(callee) = instruction {
ctx.stack_cost(*callee).and_then(|cost| {
if cost > 0 {
Some(InstrumentCall { callee: *callee, offset, cost })
} else {
None
}
})
} else {
None
}
})
.collect();
// The `instrumented_call!` contains the call itself. This is why we need to subtract one.
let len = func.elements().len() + calls.len() * (instrument_call!(0, 0, 0, 0).len() - 1);
let original_instrs = mem::replace(func.elements_mut(), Vec::with_capacity(len));
let new_instrs = func.elements_mut();
let mut calls = calls.into_iter().peekable();
for (original_pos, instr) in original_instrs.into_iter().enumerate() {
// whether there is some call instruction at this position that needs to be instrumented
let did_instrument = if let Some(call) = calls.peek() {
if call.offset == original_pos {
let new_seq = instrument_call!(
call.callee,
call.cost as i32,
ctx.stack_height_global_idx(),
ctx.stack_limit()
);
new_instrs.extend_from_slice(&new_seq);
true
} else {
false
}
} else {
false
};
if did_instrument {
calls.next();
} else {
new_instrs.push(instr);
}
}
if calls.next().is_some() {
return Err("Not all calls were used")
}
Ok(())
}
fn resolve_func_type(
func_idx: u32,
module: &elements::Module,
) -> Result<&elements::FunctionType, &'static str> {
let types = module.type_section().map(|ts| ts.types()).unwrap_or(&[]);
let functions = module.function_section().map(|fs| fs.entries()).unwrap_or(&[]);
let func_imports = module.import_count(elements::ImportCountType::Function);
let sig_idx = if func_idx < func_imports as u32 {
module
.import_section()
.expect("function import count is not zero; import section must exists; qed")
.entries()
.iter()
.filter_map(|entry| match entry.external() {
elements::External::Function(idx) => Some(*idx),
_ => None,
})
.nth(func_idx as usize)
.expect(
"func_idx is less than function imports count;
nth function import must be `Some`;
qed",
)
} else {
functions
.get(func_idx as usize - func_imports)
.ok_or("Function at the specified index is not defined")?
.type_ref()
};
let Type::Function(ty) = types
.get(sig_idx as usize)
.ok_or("The signature as specified by a function isn't defined")?;
Ok(ty)
}
#[cfg(test)]
mod tests {
use super::*;
use parity_wasm::elements;
fn parse_wat(source: &str) -> elements::Module {
elements::deserialize_buffer(&wat::parse_str(source).expect("Failed to wat2wasm"))
.expect("Failed to deserialize the module")
}
fn validate_module(module: elements::Module) {
let binary = elements::serialize(module).expect("Failed to serialize");
wasmparser::validate(&binary).expect("Invalid module");
}
#[test]
fn test_with_params_and_result() {
let module = parse_wat(
r#"
(module
(func (export "i32.add") (param i32 i32) (result i32)
get_local 0
get_local 1
i32.add
)
)
"#,
);
let module = inject(module, 1024).expect("Failed to inject stack counter");
validate_module(module);
}
}
+139
View File
@@ -0,0 +1,139 @@
#[cfg(not(features = "std"))]
use alloc::collections::BTreeMap as Map;
use alloc::vec::Vec;
use parity_wasm::{
builder,
elements::{self, FunctionType, Internal},
};
#[cfg(features = "std")]
use std::collections::HashMap as Map;
use super::{resolve_func_type, Context};
struct Thunk {
signature: FunctionType,
// Index in function space of this thunk.
idx: Option<u32>,
callee_stack_cost: u32,
}
pub fn generate_thunks(
ctx: &mut Context,
module: elements::Module,
) -> Result<elements::Module, &'static str> {
// First, we need to collect all function indices that should be replaced by thunks
let mut replacement_map: Map<u32, Thunk> = {
let exports = module.export_section().map(|es| es.entries()).unwrap_or(&[]);
let elem_segments = module.elements_section().map(|es| es.entries()).unwrap_or(&[]);
let start_func_idx = module.start_section();
let exported_func_indices = exports.iter().filter_map(|entry| match entry.internal() {
Internal::Function(function_idx) => Some(*function_idx),
_ => None,
});
let table_func_indices =
elem_segments.iter().flat_map(|segment| segment.members()).cloned();
// Replacement map is at least export section size.
let mut replacement_map: Map<u32, Thunk> = Map::new();
for func_idx in exported_func_indices
.chain(table_func_indices)
.chain(start_func_idx.into_iter())
{
let callee_stack_cost = ctx.stack_cost(func_idx).ok_or("function index isn't found")?;
// Don't generate a thunk if stack_cost of a callee is zero.
if callee_stack_cost != 0 {
replacement_map.insert(
func_idx,
Thunk {
signature: resolve_func_type(func_idx, &module)?.clone(),
idx: None,
callee_stack_cost,
},
);
}
}
replacement_map
};
// Then, we generate a thunk for each original function.
// Save current func_idx
let mut next_func_idx = module.functions_space() as u32;
let mut mbuilder = builder::from_module(module);
for (func_idx, thunk) in replacement_map.iter_mut() {
let instrumented_call = instrument_call!(
*func_idx,
thunk.callee_stack_cost as i32,
ctx.stack_height_global_idx(),
ctx.stack_limit()
);
// Thunk body consist of:
// - argument pushing
// - instrumented call
// - end
let mut thunk_body: Vec<elements::Instruction> =
Vec::with_capacity(thunk.signature.params().len() + instrumented_call.len() + 1);
for (arg_idx, _) in thunk.signature.params().iter().enumerate() {
thunk_body.push(elements::Instruction::GetLocal(arg_idx as u32));
}
thunk_body.extend_from_slice(&instrumented_call);
thunk_body.push(elements::Instruction::End);
// TODO: Don't generate a signature, but find an existing one.
mbuilder = mbuilder
.function()
// Signature of the thunk should match the original function signature.
.signature()
.with_params(thunk.signature.params().to_vec())
.with_results(thunk.signature.results().to_vec())
.build()
.body()
.with_instructions(elements::Instructions::new(thunk_body))
.build()
.build();
thunk.idx = Some(next_func_idx);
next_func_idx += 1;
}
let mut module = mbuilder.build();
// And finally, fixup thunks in export and table sections.
// Fixup original function index to a index of a thunk generated earlier.
let fixup = |function_idx: &mut u32| {
// Check whether this function is in replacement_map, since
// we can skip thunk generation (e.g. if stack_cost of function is 0).
if let Some(thunk) = replacement_map.get(function_idx) {
*function_idx =
thunk.idx.expect("At this point an index must be assigned to each thunk");
}
};
for section in module.sections_mut() {
match section {
elements::Section::Export(export_section) =>
for entry in export_section.entries_mut() {
if let Internal::Function(function_idx) = entry.internal_mut() {
fixup(function_idx)
}
},
elements::Section::Element(elem_section) =>
for segment in elem_section.entries_mut() {
for function_idx in segment.members_mut() {
fixup(function_idx)
}
},
elements::Section::Start(start_idx) => fixup(start_idx),
_ => {},
}
}
Ok(module)
}
-148
View File
@@ -1,148 +0,0 @@
#[cfg(features = "std")]
use std::collections::{HashSet as Set};
#[cfg(not(features = "std"))]
use std::collections::{BTreeSet as Set};
use std::vec::Vec;
use parity_wasm::elements;
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone, Debug)]
pub enum Symbol {
Type(usize),
Import(usize),
Global(usize),
Function(usize),
Export(usize),
}
pub fn resolve_function(module: &elements::Module, index: u32) -> Symbol {
let mut functions = 0;
if let Some(import_section) = module.import_section() {
for (item_index, item) in import_section.entries().iter().enumerate() {
if let &elements::External::Function(_) = item.external() {
if functions == index {
return Symbol::Import(item_index as usize);
}
functions += 1;
}
}
}
Symbol::Function(index as usize - functions as usize)
}
pub fn resolve_global(module: &elements::Module, index: u32) -> Symbol {
let mut globals = 0;
if let Some(import_section) = module.import_section() {
for (item_index, item) in import_section.entries().iter().enumerate() {
if let &elements::External::Global(_) = item.external() {
if globals == index {
return Symbol::Import(item_index as usize);
}
globals += 1;
}
}
}
Symbol::Global(index as usize - globals as usize)
}
pub fn push_code_symbols(module: &elements::Module, instructions: &[elements::Instruction], dest: &mut Vec<Symbol>) {
use parity_wasm::elements::Instruction::*;
for instruction in instructions {
match instruction {
&Call(idx) => {
dest.push(resolve_function(module, idx));
},
&CallIndirect(idx, _) => {
dest.push(Symbol::Type(idx as usize));
},
&GetGlobal(idx) | &SetGlobal(idx) => {
dest.push(resolve_global(module, idx))
},
_ => { },
}
}
}
pub fn expand_symbols(module: &elements::Module, set: &mut Set<Symbol>) {
use self::Symbol::*;
// symbols that were already processed
let mut stop: Set<Symbol> = Set::new();
let mut fringe = set.iter().cloned().collect::<Vec<Symbol>>();
loop {
let next = match fringe.pop() {
Some(s) if stop.contains(&s) => { continue; }
Some(s) => s,
_ => { break; }
};
trace!("Processing symbol {:?}", next);
match next {
Export(idx) => {
let entry = &module.export_section().expect("Export section to exist").entries()[idx];
match entry.internal() {
&elements::Internal::Function(func_idx) => {
let symbol = resolve_function(module, func_idx);
if !stop.contains(&symbol) {
fringe.push(symbol);
}
set.insert(symbol);
},
&elements::Internal::Global(global_idx) => {
let symbol = resolve_global(module, global_idx);
if !stop.contains(&symbol) {
fringe.push(symbol);
}
set.insert(symbol);
},
_ => {}
}
},
Import(idx) => {
let entry = &module.import_section().expect("Import section to exist").entries()[idx];
if let &elements::External::Function(type_idx) = entry.external() {
let type_symbol = Symbol::Type(type_idx as usize);
if !stop.contains(&type_symbol) {
fringe.push(type_symbol);
}
set.insert(type_symbol);
}
},
Function(idx) => {
let body = &module.code_section().expect("Code section to exist").bodies()[idx];
let mut code_symbols = Vec::new();
push_code_symbols(module, body.code().elements(), &mut code_symbols);
for symbol in code_symbols.drain(..) {
if !stop.contains(&symbol) {
fringe.push(symbol);
}
set.insert(symbol);
}
let signature = &module.function_section().expect("Functions section to exist").entries()[idx];
let type_symbol = Symbol::Type(signature.type_ref() as usize);
if !stop.contains(&type_symbol) {
fringe.push(type_symbol);
}
set.insert(type_symbol);
},
Global(idx) => {
let entry = &module.global_section().expect("Global section to exist").entries()[idx];
let mut code_symbols = Vec::new();
push_code_symbols(module, entry.init_expr().code(), &mut code_symbols);
for symbol in code_symbols.drain(..) {
if !stop.contains(&symbol) {
fringe.push(symbol);
}
set.insert(symbol);
}
}
_ => {}
}
stop.insert(next);
}
}
+46 -44
View File
@@ -1,18 +1,17 @@
extern crate diff;
extern crate pwasm_utils as utils;
extern crate wabt;
extern crate parity_wasm;
use std::fs;
use std::io::{self, Read, Write};
use std::path::{Path, PathBuf};
use parity_wasm::elements;
use parity_wasm::elements::Module;
use std::{
fs,
io::{self, Read, Write},
path::{Path, PathBuf},
};
use wasm_instrument::{self as instrument, parity_wasm::elements};
use wasmparser::validate;
fn slurp<P: AsRef<Path>>(path: P) -> io::Result<Vec<u8>> {
let mut f = fs::File::open(path)?;
let mut buf = vec![];
f.read_to_end(&mut buf)?;
Ok(buf)
let mut f = fs::File::open(path)?;
let mut buf = vec![];
f.read_to_end(&mut buf)?;
Ok(buf)
}
fn dump<P: AsRef<Path>>(path: P, buf: &[u8]) -> io::Result<()> {
@@ -21,46 +20,36 @@ fn dump<P: AsRef<Path>>(path: P, buf: &[u8]) -> io::Result<()> {
Ok(())
}
fn validate_wasm(binary: &[u8]) -> Result<(), wabt::Error> {
wabt::Module::read_binary(
&binary,
&Default::default()
)?.validate()?;
Ok(())
}
fn run_diff_test<F: FnOnce(&[u8]) -> Vec<u8>>(test_dir: &str, name: &str, test: F) {
let mut fixture_path = PathBuf::from(concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/fixtures/",
));
let mut fixture_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
fixture_path.push("tests");
fixture_path.push("fixtures");
fixture_path.push(test_dir);
fixture_path.push(name);
let mut expected_path = PathBuf::from(concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/expectations/"
));
let mut expected_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
expected_path.push("tests");
expected_path.push("expectations");
expected_path.push(test_dir);
expected_path.push(name);
let fixture_wat = slurp(&fixture_path).expect("Failed to read fixture");
let fixture_wasm = wabt::wat2wasm(fixture_wat).expect("Failed to read fixture");
validate_wasm(&fixture_wasm).expect("Fixture is invalid");
let fixture_wasm = wat::parse_file(&fixture_path).expect("Failed to read fixture");
validate(&fixture_wasm).expect("Fixture is invalid");
let expected_wat = slurp(&expected_path).unwrap_or_default();
let expected_wat = String::from_utf8_lossy(&expected_wat);
let expected_wat = std::str::from_utf8(&expected_wat).expect("Failed to decode expected wat");
let actual_wasm = test(fixture_wasm.as_ref());
validate_wasm(&actual_wasm).expect("Result module is invalid");
validate(&actual_wasm).expect("Result module is invalid");
let actual_wat = wabt::wasm2wat(&actual_wasm).expect("Failed to convert result wasm to wat");
let actual_wat =
wasmprinter::print_bytes(&actual_wasm).expect("Failed to convert result wasm to wat");
if actual_wat != expected_wat {
println!("difference!");
println!("--- {}", expected_path.display());
println!("+++ {} test {}", test_dir, name);
for diff in diff::lines(&expected_wat, &actual_wat) {
for diff in diff::lines(expected_wat, &actual_wat) {
match diff {
diff::Result::Left(l) => println!("-{}", l),
diff::Result::Both(l, _) => println!(" {}", l),
@@ -68,9 +57,11 @@ fn run_diff_test<F: FnOnce(&[u8]) -> Vec<u8>>(test_dir: &str, name: &str, test:
}
}
dump(&expected_path, actual_wat.as_bytes()).expect("Failed to write to expected");
panic!();
if std::env::var_os("BLESS").is_some() {
dump(&expected_path, actual_wat.as_bytes()).expect("Failed to write to expected");
} else {
panic!();
}
}
}
@@ -82,8 +73,10 @@ mod stack_height {
#[test]
fn $name() {
run_diff_test("stack-height", concat!(stringify!($name), ".wat"), |input| {
let module = elements::deserialize_buffer(input).expect("Failed to deserialize");
let instrumented = utils::stack_height::inject_limiter(module, 1024).expect("Failed to instrument with stack counter");
let module =
elements::deserialize_buffer(input).expect("Failed to deserialize");
let instrumented = instrument::inject_stack_limiter(module, 1024)
.expect("Failed to instrument with stack counter");
elements::serialize(instrumented).expect("Failed to serialize")
});
}
@@ -95,6 +88,8 @@ mod stack_height {
def_stack_height_test!(table);
def_stack_height_test!(global);
def_stack_height_test!(imports);
def_stack_height_test!(many_locals);
def_stack_height_test!(empty_functions);
}
mod gas {
@@ -105,16 +100,23 @@ mod gas {
#[test]
fn $name() {
run_diff_test("gas", concat!(stringify!($name), ".wat"), |input| {
let rules = utils::rules::Set::default();
let rules = instrument::gas_metering::ConstantCostRules::default();
let module = elements::deserialize_buffer(input).expect("Failed to deserialize");
let instrumented = utils::inject_gas_counter(module, &rules).expect("Failed to instrument with gas metering");
let module: Module =
elements::deserialize_buffer(input).expect("Failed to deserialize");
let module = module.parse_names().expect("Failed to parse names");
let instrumented = instrument::gas_metering::inject(module, &rules, "env")
.expect("Failed to instrument with gas metering");
elements::serialize(instrumented).expect("Failed to serialize")
});
}
};
}
def_gas_test!(ifs);
def_gas_test!(simple);
def_gas_test!(start);
def_gas_test!(call);
def_gas_test!(branch);
}
+31
View File
@@ -0,0 +1,31 @@
(module
(type (;0;) (func (result i32)))
(type (;1;) (func (param i32)))
(import "env" "gas" (func (;0;) (type 1)))
(func $fibonacci_with_break (;1;) (type 0) (result i32)
(local i32 i32)
i32.const 13
call 0
block ;; label = @1
i32.const 0
local.set 0
i32.const 1
local.set 1
local.get 0
local.get 1
local.tee 0
i32.add
local.set 1
i32.const 1
br_if 0 (;@1;)
i32.const 5
call 0
local.get 0
local.get 1
local.tee 0
i32.add
local.set 1
end
local.get 1
)
)
+22
View File
@@ -0,0 +1,22 @@
(module
(type (;0;) (func (param i32 i32) (result i32)))
(type (;1;) (func (param i32)))
(import "env" "gas" (func (;0;) (type 1)))
(func $add_locals (;1;) (type 0) (param $x i32) (param $y i32) (result i32)
(local i32)
i32.const 5
call 0
local.get $x
local.get $y
call $add
local.set 2
local.get 2
)
(func $add (;2;) (type 0) (param i32 i32) (result i32)
i32.const 3
call 0
local.get 0
local.get 1
i32.add
)
)
+22
View File
@@ -0,0 +1,22 @@
(module
(type (;0;) (func (param i32) (result i32)))
(type (;1;) (func (param i32)))
(import "env" "gas" (func (;0;) (type 1)))
(func (;1;) (type 0) (param i32) (result i32)
i32.const 2
call 0
i32.const 1
if (result i32) ;; label = @1
i32.const 3
call 0
local.get 0
i32.const 1
i32.add
else
i32.const 2
call 0
local.get 0
i32.popcnt
end
)
)
+10 -9
View File
@@ -3,24 +3,25 @@
(type (;1;) (func (param i32)))
(import "env" "gas" (func (;0;) (type 1)))
(func (;1;) (type 0)
i32.const 3
i32.const 2
call 0
i32.const 1
if ;; label = @1
i32.const 2
i32.const 1
call 0
loop ;; label = @2
i32.const 3
i32.const 2
call 0
i32.const 123
drop
end
end)
end
)
(func (;2;) (type 0)
i32.const 2
i32.const 1
call 0
block ;; label = @1
i32.const 1
call 0
end)
(export "simple" (func 1)))
end
)
(export "simple" (func 1))
)
+10 -10
View File
@@ -2,19 +2,19 @@
(type (;0;) (func (param i32 i32)))
(type (;1;) (func))
(type (;2;) (func (param i32)))
(import "env" "ext_return" (func (;0;) (type 0)))
(import "env" "ext_return" (func $ext_return (;0;) (type 0)))
(import "env" "memory" (memory (;0;) 1 1))
(import "env" "gas" (func (;1;) (type 2)))
(func (;2;) (type 1)
i32.const 5
(func $start (;2;) (type 1)
i32.const 4
call 1
i32.const 8
i32.const 4
call 0
unreachable)
(func (;3;) (type 1)
i32.const 1
call 1)
call $ext_return
unreachable
)
(func (;3;) (type 1))
(export "call" (func 3))
(start 2)
(data (i32.const 8) "\01\02\03\04"))
(start $start)
(data (;0;) (i32.const 8) "\01\02\03\04")
)
@@ -0,0 +1,56 @@
(module
(type (;0;) (func))
(func (;0;) (type 0)
global.get 0
i32.const 2
i32.add
global.set 0
global.get 0
i32.const 1024
i32.gt_u
if ;; label = @1
unreachable
end
call 0
global.get 0
i32.const 2
i32.sub
global.set 0
)
(func (;1;) (type 0)
global.get 0
i32.const 2
i32.add
global.set 0
global.get 0
i32.const 1024
i32.gt_u
if ;; label = @1
unreachable
end
call 0
global.get 0
i32.const 2
i32.sub
global.set 0
)
(func (;2;) (type 0)
global.get 0
i32.const 2
i32.add
global.set 0
global.get 0
i32.const 1024
i32.gt_u
if ;; label = @1
unreachable
end
call 1
global.get 0
i32.const 2
i32.sub
global.set 0
)
(global (;0;) (mut i32) i32.const 0)
(export "main" (func 2))
)
+38 -34
View File
@@ -2,54 +2,58 @@
(type (;0;) (func))
(type (;1;) (func (param i32 i32) (result i32)))
(type (;2;) (func (param i32)))
(import "env" "foo" (func (;0;) (type 0)))
(func (;1;) (type 1) (param i32 i32) (result i32)
get_local 0
get_local 1
i32.add)
(func (;2;) (type 2) (param i32)
(local i32)
get_global 0
(import "env" "foo" (func $foo (;0;) (type 0)))
(func $i32.add (;1;) (type 1) (param i32 i32) (result i32)
local.get 0
local.get 1
i32.add
)
(func (;2;) (type 2) (param $arg i32)
(local $tmp i32)
global.get $counter
i32.const 1
i32.add
tee_local 1
set_global 0
get_local 1
get_local 0
get_global 1
i32.const 2
local.tee $tmp
global.set $counter
local.get $tmp
local.get $arg
global.get 1
i32.const 4
i32.add
set_global 1
get_global 1
global.set 1
global.get 1
i32.const 1024
i32.gt_u
if ;; label = @1
unreachable
end
call 1
get_global 1
i32.const 2
call $i32.add
global.get 1
i32.const 4
i32.sub
set_global 1
drop)
global.set 1
drop
)
(func (;3;) (type 1) (param i32 i32) (result i32)
get_local 0
get_local 1
get_global 1
i32.const 2
local.get 0
local.get 1
global.get 1
i32.const 4
i32.add
set_global 1
get_global 1
global.set 1
global.get 1
i32.const 1024
i32.gt_u
if ;; label = @1
unreachable
end
call 1
get_global 1
i32.const 2
call $i32.add
global.get 1
i32.const 4
i32.sub
set_global 1)
(global (;0;) (mut i32) (i32.const 1))
(global (;1;) (mut i32) (i32.const 0))
(export "i32.add" (func 3)))
global.set 1
)
(global $counter (;0;) (mut i32) i32.const 1)
(global (;1;) (mut i32) i32.const 0)
(export "i32.add" (func 3))
)
+22 -19
View File
@@ -1,31 +1,34 @@
(module
(type (;0;) (func))
(type (;1;) (func (param i32 i32) (result i32)))
(import "env" "foo" (func (;0;) (type 0)))
(import "env" "boo" (func (;1;) (type 0)))
(import "env" "foo" (func $foo (;0;) (type 0)))
(import "env" "boo" (func $boo (;1;) (type 0)))
(func (;2;) (type 1) (param i32 i32) (result i32)
call 0
call 1
get_local 0
get_local 1
i32.add)
(func (;3;) (type 1) (param i32 i32) (result i32)
get_local 0
get_local 1
get_global 0
i32.const 2
call $foo
call $boo
local.get 0
local.get 1
i32.add
set_global 0
get_global 0
)
(func (;3;) (type 1) (param i32 i32) (result i32)
local.get 0
local.get 1
global.get 0
i32.const 4
i32.add
global.set 0
global.get 0
i32.const 1024
i32.gt_u
if ;; label = @1
unreachable
end
call 2
get_global 0
i32.const 2
global.get 0
i32.const 4
i32.sub
set_global 0)
(global (;0;) (mut i32) (i32.const 0))
(export "i32.add" (func 3)))
global.set 0
)
(global (;0;) (mut i32) i32.const 0)
(export "i32.add" (func 3))
)
@@ -0,0 +1,24 @@
(module
(type (;0;) (func))
(func $one-group-many-locals (;0;) (type 0)
(local i64 i64 i32)
)
(func $main (;1;) (type 0)
global.get 0
i32.const 5
i32.add
global.set 0
global.get 0
i32.const 1024
i32.gt_u
if ;; label = @1
unreachable
end
call $one-group-many-locals
global.get 0
i32.const 5
i32.sub
global.set 0
)
(global (;0;) (mut i32) i32.const 0)
)
+13 -10
View File
@@ -2,22 +2,25 @@
(type (;0;) (func))
(func (;0;) (type 0)
i32.const 123
drop)
drop
)
(func (;1;) (type 0)
get_global 0
i32.const 1
global.get 0
i32.const 3
i32.add
set_global 0
get_global 0
global.set 0
global.get 0
i32.const 1024
i32.gt_u
if ;; label = @1
unreachable
end
call 0
get_global 0
i32.const 1
global.get 0
i32.const 3
i32.sub
set_global 0)
(global (;0;) (mut i32) (i32.const 0))
(export "simple" (func 1)))
global.set 0
)
(global (;0;) (mut i32) i32.const 0)
(export "simple" (func 1))
)
+26 -23
View File
@@ -1,44 +1,47 @@
(module
(type (;0;) (func (param i32 i32)))
(type (;1;) (func))
(import "env" "ext_return" (func (;0;) (type 0)))
(import "env" "ext_return" (func $ext_return (;0;) (type 0)))
(import "env" "memory" (memory (;0;) 1 1))
(func (;1;) (type 1)
(local i32))
(func $start (;1;) (type 1)
(local i32)
)
(func (;2;) (type 1))
(func (;3;) (type 1)
get_global 0
i32.const 1
global.get 0
i32.const 3
i32.add
set_global 0
get_global 0
global.set 0
global.get 0
i32.const 1024
i32.gt_u
if ;; label = @1
unreachable
end
call 1
get_global 0
i32.const 1
call $start
global.get 0
i32.const 3
i32.sub
set_global 0)
global.set 0
)
(func (;4;) (type 1)
get_global 0
i32.const 1
global.get 0
i32.const 2
i32.add
set_global 0
get_global 0
global.set 0
global.get 0
i32.const 1024
i32.gt_u
if ;; label = @1
unreachable
end
call 1
get_global 0
i32.const 1
call 2
global.get 0
i32.const 2
i32.sub
set_global 0)
(global (;0;) (mut i32) (i32.const 0))
(export "exported_start" (func 4))
(export "call" (func 2))
(start 4))
global.set 0
)
(global (;0;) (mut i32) i32.const 0)
(export "call" (func 4))
(start 3)
)
+43 -56
View File
@@ -2,84 +2,71 @@
(type (;0;) (func))
(type (;1;) (func (param i32)))
(type (;2;) (func (param i32 i32) (result i32)))
(import "env" "foo" (func (;0;) (type 0)))
(import "env" "foo" (func $foo (;0;) (type 0)))
(func (;1;) (type 1) (param i32)
get_local 0
local.get 0
i32.const 0
get_global 0
i32.const 2
global.get 0
i32.const 4
i32.add
set_global 0
get_global 0
global.set 0
global.get 0
i32.const 1024
i32.gt_u
if ;; label = @1
unreachable
end
call 2
get_global 0
i32.const 2
call $i32.add
global.get 0
i32.const 4
i32.sub
set_global 0
drop)
(func (;2;) (type 2) (param i32 i32) (result i32)
get_local 0
get_local 1
i32.add)
(func (;3;) (type 2) (param i32 i32) (result i32)
get_local 0
get_local 1
get_global 0
i32.const 2
global.set 0
drop
)
(func $i32.add (;2;) (type 2) (param i32 i32) (result i32)
local.get 0
local.get 1
i32.add
set_global 0
get_global 0
i32.const 1024
i32.gt_u
if ;; label = @1
unreachable
end
call 2
get_global 0
i32.const 2
i32.sub
set_global 0)
(func (;4;) (type 1) (param i32)
get_local 0
get_global 0
i32.const 2
)
(func (;3;) (type 1) (param i32)
local.get 0
global.get 0
i32.const 4
i32.add
set_global 0
get_global 0
global.set 0
global.get 0
i32.const 1024
i32.gt_u
if ;; label = @1
unreachable
end
call 1
get_global 0
i32.const 2
global.get 0
i32.const 4
i32.sub
set_global 0)
(func (;5;) (type 2) (param i32 i32) (result i32)
get_local 0
get_local 1
get_global 0
i32.const 2
global.set 0
)
(func (;4;) (type 2) (param i32 i32) (result i32)
local.get 0
local.get 1
global.get 0
i32.const 4
i32.add
set_global 0
get_global 0
global.set 0
global.get 0
i32.const 1024
i32.gt_u
if ;; label = @1
unreachable
end
call 2
get_global 0
i32.const 2
call $i32.add
global.get 0
i32.const 4
i32.sub
set_global 0)
(table (;0;) 10 anyfunc)
(global (;0;) (mut i32) (i32.const 0))
(export "i32.add" (func 5))
(elem (i32.const 0) 0 4 5))
global.set 0
)
(table (;0;) 10 funcref)
(global (;0;) (mut i32) i32.const 0)
(export "i32.add" (func 4))
(elem (;0;) (i32.const 0) func $foo 3 4)
)
+27
View File
@@ -0,0 +1,27 @@
(module
(func $fibonacci_with_break (result i32)
(local $x i32) (local $y i32)
(block $unrolled_loop
(set_local $x (i32.const 0))
(set_local $y (i32.const 1))
get_local $x
get_local $y
tee_local $x
i32.add
set_local $y
i32.const 1
br_if $unrolled_loop
get_local $x
get_local $y
tee_local $x
i32.add
set_local $y
)
get_local $y
)
)
+19
View File
@@ -0,0 +1,19 @@
(module
(func $add_locals (param $x i32) (param $y i32) (result i32)
(local $t i32)
get_local $x
get_local $y
call $add
set_local $t
get_local $t
)
(func $add (param $x i32) (param $y i32) (result i32)
(i32.add
(get_local $x)
(get_local $y)
)
)
)
+9
View File
@@ -0,0 +1,9 @@
(module
(func (param $x i32) (result i32)
(if (result i32)
(i32.const 1)
(then (i32.add (get_local $x) (i32.const 1)))
(else (i32.popcnt (get_local $x)))
)
)
)
+5 -3
View File
@@ -1,9 +1,11 @@
(module
(func (export "simple")
(if (i32.const 1)
(loop
i32.const 123
drop
(then
(loop
i32.const 123
drop
)
)
)
)
+8
View File
@@ -0,0 +1,8 @@
(module
(func (;0;)
call 0
)
(func (;1;) (export "main")
call 0
)
)
+5 -5
View File
@@ -6,17 +6,17 @@
(func $i32.add (export "i32.add") (param i32 i32) (result i32)
get_local 0
get_local 1
i32.add
get_local 1
i32.add
)
(func (param $arg i32)
(local $tmp i32)
get_global 0
global.get 0
i32.const 1
i32.add
tee_local $tmp
set_global $counter
tee_local $tmp
global.set $counter
get_local $tmp
get_local $arg
+10
View File
@@ -0,0 +1,10 @@
(module
(func $one-group-many-locals
(local i64) (local i64) (local i32)
)
(func $main
(call
$one-group-many-locals
)
)
)
+1 -1
View File
@@ -3,7 +3,7 @@
(import "env" "memory" (memory 1 1))
(start $start)
(func $start (export "exported_start")
(func $start
(local i32)
)
(func (export "call")
+6 -6
View File
@@ -1,15 +1,15 @@
(module
(import "env" "foo" (func $foo))
(func (param i32)
get_local 0
i32.const 0
call $i32.add
drop
get_local 0
i32.const 0
call $i32.add
drop
)
(func $i32.add (export "i32.add") (param i32 i32) (result i32)
get_local 0
get_local 1
i32.add
get_local 1
i32.add
)
(table 10 anyfunc)
+73
View File
@@ -0,0 +1,73 @@
use std::{
fs::{read, read_dir},
path::PathBuf,
};
use wasm_instrument::{
gas_metering, inject_stack_limiter,
parity_wasm::{deserialize_buffer, elements::Module, serialize},
};
fn fixture_dir() -> PathBuf {
let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
path.push("benches");
path.push("fixtures");
path
}
/// Print the overhead of applying gas metering, stack height limiting or both.
///
/// Use `cargo test print_overhead -- --nocapture`.
#[test]
fn print_size_overhead() {
let mut results: Vec<_> = read_dir(fixture_dir())
.unwrap()
.map(|entry| {
let entry = entry.unwrap();
let (orig_len, orig_module) = {
let bytes = read(&entry.path()).unwrap();
let len = bytes.len();
let module: Module = deserialize_buffer(&bytes).unwrap();
(len, module)
};
let (gas_metering_len, gas_module) = {
let module = gas_metering::inject(
orig_module.clone(),
&gas_metering::ConstantCostRules::default(),
"env",
)
.unwrap();
let bytes = serialize(module.clone()).unwrap();
let len = bytes.len();
(len, module)
};
let stack_height_len = {
let module = inject_stack_limiter(orig_module, 128).unwrap();
let bytes = serialize(module).unwrap();
bytes.len()
};
let both_len = {
let module = inject_stack_limiter(gas_module, 128).unwrap();
let bytes = serialize(module).unwrap();
bytes.len()
};
let overhead = both_len * 100 / orig_len;
(
overhead,
format!(
"{:30}: orig = {:4} kb, gas_metering = {} %, stack_limiter = {} %, both = {} %",
entry.file_name().to_str().unwrap(),
orig_len / 1024,
gas_metering_len * 100 / orig_len,
stack_height_len * 100 / orig_len,
overhead,
),
)
})
.collect();
results.sort_unstable_by(|a, b| b.0.cmp(&a.0));
for entry in results {
println!("{}", entry.1);
}
}