95 Commits

Author SHA1 Message Date
Dmitry Sinyavin 842565595f WIP: Known values 2022-08-02 11:29:44 +02:00
Dmitry Sinyavin 6bf31c0331 Backward compatibility and tracing 2022-07-27 13:45:52 +02:00
Dmitry Sinyavin 8a552c033c Fix failing pipeline checks 2022-07-26 11:38:58 +02:00
Dmitry Sinyavin c55ea7bfb7 Weighted stack metering 2022-07-26 10:52:14 +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
74 changed files with 2851 additions and 6151 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.
+34 -24
View File
@@ -1,32 +1,42 @@
[package]
name = "pwasm-utils"
version = "0.11.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"]
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]
# If you add the feature "bulk", make sure you fixed all expects that say
# "parity-wasm is compiled without bulk-memory operations"
parity-wasm = { version = "0.40.1", 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 = { version = "0.4.8", default-features = false, optional = true }
test-log = { version = "0.2", optional = true }
env_logger = { version = "0.9", optional = true }
[dev-dependencies]
tempdir = "0.3"
wabt = "0.2"
diff = "0.1.11"
indoc = "0.3"
rand = "0.7"
binaryen = "0.8"
binaryen = "0.12"
criterion = "0.3"
diff = "0.1"
rand = "0.8"
wat = "1"
wasmparser = "0.87"
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"]
trace-log = ["dep:log", "dep:test-log", "dep:env_logger"]
-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
+16 -31
View File
@@ -1,48 +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 pwasm-ethereum and substrate 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 pwasm-ethereum/substrate runtime when running contracts)
## License
```
cargo install pwasm-utils-cli --bin wasm-gas
wasm-gas <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
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.10.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.40.1"
pwasm-utils = { path = "..", version = "0.11" }
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
```
-245
View File
@@ -1,245 +0,0 @@
//! Experimental build tool for cargo
#[macro_use]
extern crate clap;
extern crate glob;
extern crate pwasm_utils as utils;
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")
.version(crate_version!())
.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.symbols().call]).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.symbols().call)))
.get_matches();
let exports = matches
.value_of("exports")
.unwrap_or(target_runtime.symbols().call)
.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")
}
-28
View File
@@ -1,28 +0,0 @@
extern crate pwasm_utils as utils;
use std::env;
fn main() {
let args = env::args().collect::<Vec<_>>();
if args.len() != 3 {
println!("Usage: {} input_file.wasm output_file.wasm", args[0]);
return;
}
// Loading module
let mut module = utils::Module::from_elements(
&parity_wasm::deserialize_file(&args[1]).expect("Module deserialization to succeed")
).expect("Failed to parse parity-wasm format");
let mut delete_types = Vec::new();
for type_ in module.types.iter() {
if type_.link_count() == 0 {
delete_types.push(type_.order().expect("type in list should have index"));
}
}
module.types.delete(&delete_types[..]);
parity_wasm::serialize_to_file(&args[2],
module.generate().expect("Failed to generate valid format")
).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
-123
View File
@@ -1,123 +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.symbols().create == 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.symbols().call);
if !skip_optimization {
optimize(&mut module, public_api_entries)?;
}
if !has_ctor(&ctor_module, target_runtime) {
return Ok((module, None))
}
if !skip_optimization {
let preserved_exports = match target_runtime {
TargetRuntime::PWasm(_) => vec![target_runtime.symbols().create],
TargetRuntime::Substrate(_) => vec![target_runtime.symbols().call, target_runtime.symbols().create],
};
optimize(&mut ctor_module, preserved_exports)?;
}
if let TargetRuntime::PWasm(_) = target_runtime {
ctor_module = pack_instance(
parity_wasm::serialize(module.clone()).map_err(Error::Encoding)?,
ctor_module.clone(),
target_runtime,
)?;
}
Ok((module, Some(ctor_module)))
}
+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)))
"#
}
}
-203
View File
@@ -1,203 +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()
.as_ref()
.expect("parity-wasm is compiled without bulk-memory operations")
.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
}
File diff suppressed because it is too large Load Diff
@@ -8,18 +8,16 @@
//! searching through all paths, which may take exponential time in the size of the function body in
//! the worst case.
use super::MeteredBlock;
use rules::Set as RuleSet;
use super::{ConstantCostRules, MeteredBlock, Rules};
use parity_wasm::elements::{FuncBody, Instruction};
use std::collections::HashMap;
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)]
#[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>,
@@ -42,19 +40,6 @@ struct ControlFlowNode {
loopback_edges: Vec<NodeId>,
}
impl Default for ControlFlowNode {
fn default() -> Self {
ControlFlowNode {
first_instr_pos: None,
actual_cost: 0,
charged_cost: 0,
is_loop_target: false,
forward_edges: Vec::new(),
loopback_edges: Vec::new(),
}
}
}
/// 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
@@ -66,9 +51,7 @@ pub struct ControlFlowGraph {
impl ControlFlowGraph {
fn new() -> Self {
ControlFlowGraph {
nodes: Vec::new(),
}
ControlFlowGraph { nodes: Vec::new() }
}
fn get_node(&self, node_id: NodeId) -> &ControlFlowNode {
@@ -139,8 +122,8 @@ impl ControlFrame {
/// This assumes that the function body has been validated already, otherwise this may panic.
fn build_control_flow_graph(
body: &FuncBody,
rules: &RuleSet,
blocks: &[MeteredBlock]
rules: &impl Rules,
blocks: &[MeteredBlock],
) -> Result<ControlFlowGraph, ()> {
let mut graph = ControlFlowGraph::new();
@@ -149,32 +132,31 @@ fn build_control_flow_graph(
graph.set_first_instr_pos(entry_node_id, 0);
let mut stack = Vec::new();
stack.push(ControlFrame::new(entry_node_id, terminal_node_id, false));
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()
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);
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");
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.process(instruction)?;
match *instruction {
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);
@@ -184,7 +166,7 @@ fn build_control_flow_graph(
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);
@@ -194,7 +176,7 @@ fn build_control_flow_graph(
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;
@@ -205,7 +187,7 @@ fn build_control_flow_graph(
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");
@@ -216,32 +198,32 @@ fn build_control_flow_graph(
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);
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);
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(ref br_table_data) => {
},
Instruction::BrTable(br_table_data) => {
graph.increment_actual_cost(active_node_id, instruction_cost);
let active_frame_idx = stack.len() - 1;
@@ -253,7 +235,7 @@ fn build_control_flow_graph(
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);
@@ -263,7 +245,7 @@ fn build_control_flow_graph(
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),
}
}
@@ -287,7 +269,7 @@ fn validate_graph_gas_costs(graph: &ControlFlowGraph) -> bool {
node_id: NodeId,
mut total_actual: u32,
mut total_charged: u32,
loop_costs: &mut HashMap<NodeId, (u32, u32)>,
loop_costs: &mut Map<NodeId, (u32, u32)>,
) -> bool {
let node = graph.get_node(node_id);
@@ -299,20 +281,21 @@ fn validate_graph_gas_costs(graph: &ControlFlowGraph) -> bool {
}
if node.forward_edges.is_empty() && total_actual != total_charged {
return false;
return false
}
for loop_node_id in node.loopback_edges.iter() {
let (ref mut loop_actual, ref mut loop_charged) = loop_costs.get_mut(loop_node_id)
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;
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;
return false
}
}
@@ -324,7 +307,7 @@ fn validate_graph_gas_costs(graph: &ControlFlowGraph) -> bool {
}
// Recursively explore all paths through the execution graph starting from the entry node.
visit(graph, 0, 0, 0, &mut HashMap::new())
visit(graph, 0, 0, 0, &mut Map::new())
}
/// Validate that the metered blocks are correct with respect to the function body by exhaustively
@@ -333,19 +316,18 @@ fn validate_graph_gas_costs(graph: &ControlFlowGraph) -> bool {
/// This assumes that the function body has been validated already, otherwise this may panic.
fn validate_metering_injections(
body: &FuncBody,
rules: &RuleSet,
blocks: &[MeteredBlock]
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::*;
use super::super::determine_metered_blocks;
use super::{super::determine_metered_blocks, *};
use parity_wasm::elements;
use binaryen::tools::translate_to_fuzz_mvp;
use parity_wasm::elements;
use rand::{thread_rng, RngCore};
#[test]
@@ -359,10 +341,11 @@ mod tests {
.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 = RuleSet::default();
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();
let success =
validate_metering_injections(func_body, &rules, &metered_blocks).unwrap();
assert!(success);
}
}
-990
View File
@@ -1,990 +0,0 @@
//! Wasm binary graph format
#![warn(missing_docs)]
use parity_wasm::elements;
use super::ref_list::{RefList, EntryRef};
use std::{
vec::Vec,
borrow::ToOwned,
string::String,
collections::BTreeMap,
};
/// Imported or declared variant of the same thing.
///
/// In WebAssembly, function/global/memory/table instances can either be
/// imported or declared internally, forming united index space.
#[derive(Debug)]
pub enum ImportedOrDeclared<T=()> {
/// Variant for imported instances.
Imported(String, String),
/// Variant for instances declared internally in the module.
Declared(T),
}
impl<T> From<&elements::ImportEntry> for ImportedOrDeclared<T> {
fn from(v: &elements::ImportEntry) -> Self {
ImportedOrDeclared::Imported(v.module().to_owned(), v.field().to_owned())
}
}
/// Error for this module
#[derive(Debug)]
pub enum Error {
/// Inconsistent source representation
InconsistentSource,
/// Format error
Format(elements::Error),
/// Detached entry
DetachedEntry,
}
/// Function origin (imported or internal).
pub type FuncOrigin = ImportedOrDeclared<FuncBody>;
/// Global origin (imported or internal).
pub type GlobalOrigin = ImportedOrDeclared<Vec<Instruction>>;
/// Memory origin (imported or internal).
pub type MemoryOrigin = ImportedOrDeclared;
/// Table origin (imported or internal).
pub type TableOrigin = ImportedOrDeclared;
/// Function body.
///
/// Function consist of declaration (signature, i.e. type reference)
/// and the actual code. This part is the actual code.
#[derive(Debug)]
pub struct FuncBody {
pub locals: Vec<elements::Local>,
pub code: Vec<Instruction>,
}
/// Function declaration.
///
/// As with other instances, functions can be either imported or declared
/// within the module - `origin` field is handling this.
#[derive(Debug)]
pub struct Func {
/// Function signature/type reference.
pub type_ref: EntryRef<elements::Type>,
/// Where this function comes from (imported or declared).
pub origin: FuncOrigin,
}
/// Global declaration.
///
/// As with other instances, globals can be either imported or declared
/// within the module - `origin` field is handling this.
#[derive(Debug)]
pub struct Global {
pub content: elements::ValueType,
pub is_mut: bool,
pub origin: GlobalOrigin,
}
/// Instruction.
///
/// Some instructions don't reference any entities within the WebAssembly module,
/// while others do. This enum is for tracking references when required.
#[derive(Debug)]
pub enum Instruction {
/// WebAssembly instruction that does not reference any module entities.
Plain(elements::Instruction),
/// Call instruction which references the function.
Call(EntryRef<Func>),
/// Indirect call instruction which references function type (function signature).
CallIndirect(EntryRef<elements::Type>, u8),
/// get_global instruction which references the global.
GetGlobal(EntryRef<Global>),
/// set_global instruction which references the global.
SetGlobal(EntryRef<Global>),
}
/// Memory instance decriptor.
///
/// As with other similar instances, memory instances can be either imported
/// or declared within the module - `origin` field is handling this.
#[derive(Debug)]
pub struct Memory {
/// Declared limits of the table instance.
pub limits: elements::ResizableLimits,
/// Origin of the memory instance (internal or imported).
pub origin: MemoryOrigin,
}
/// Memory instance decriptor.
///
/// As with other similar instances, memory instances can be either imported
/// or declared within the module - `origin` field is handling this.
#[derive(Debug)]
pub struct Table {
/// Declared limits of the table instance.
pub limits: elements::ResizableLimits,
/// Origin of the table instance (internal or imported).
pub origin: TableOrigin,
}
/// Segment location.
///
/// Reserved for future use. Currenty only `Default` variant is supported.
#[derive(Debug)]
pub enum SegmentLocation {
/// Not used currently.
Passive,
/// Default segment location with index `0`.
Default(Vec<Instruction>),
/// Not used currently.
WithIndex(u32, Vec<Instruction>),
}
/// Data segment of data section.
#[derive(Debug)]
pub struct DataSegment {
/// Location of the segment in the linear memory.
pub location: SegmentLocation,
/// Raw value of the data segment.
pub value: Vec<u8>,
}
/// Element segment of element section.
#[derive(Debug)]
pub struct ElementSegment {
/// Location of the segment in the table space.
pub location: SegmentLocation,
/// Raw value (function indices) of the element segment.
pub value: Vec<EntryRef<Func>>,
}
/// Export entry reference.
///
/// Module can export function, global, table or memory instance
/// under specific name (field).
#[derive(Debug)]
pub enum ExportLocal {
/// Function reference.
Func(EntryRef<Func>),
/// Global reference.
Global(EntryRef<Global>),
/// Table reference.
Table(EntryRef<Table>),
/// Memory reference.
Memory(EntryRef<Memory>),
}
/// Export entry description.
#[derive(Debug)]
pub struct Export {
/// Name (field) of the export entry.
pub name: String,
/// What entity is exported.
pub local: ExportLocal,
}
/// Module
#[derive(Debug, Default)]
pub struct Module {
/// Refence-tracking list of types.
pub types: RefList<elements::Type>,
/// Refence-tracking list of funcs.
pub funcs: RefList<Func>,
/// Refence-tracking list of memory instances.
pub memory: RefList<Memory>,
/// Refence-tracking list of table instances.
pub tables: RefList<Table>,
/// Refence-tracking list of globals.
pub globals: RefList<Global>,
/// Reference to start function.
pub start: Option<EntryRef<Func>>,
/// References to exported objects.
pub exports: Vec<Export>,
/// List of element segments.
pub elements: Vec<ElementSegment>,
/// List of data segments.
pub data: Vec<DataSegment>,
/// Other module functions that are not decoded or processed.
pub other: BTreeMap<usize, elements::Section>,
}
impl Module {
fn map_instructions(&self, instructions: &[elements::Instruction]) -> Vec<Instruction> {
use parity_wasm::elements::Instruction::*;
instructions.iter().map(|instruction| match instruction {
Call(func_idx) => Instruction::Call(self.funcs.clone_ref(*func_idx as usize)),
CallIndirect(type_idx, arg2) =>
Instruction::CallIndirect(
self.types.clone_ref(*type_idx as usize),
*arg2,
),
SetGlobal(global_idx) =>
Instruction::SetGlobal(self.globals.clone_ref(*global_idx as usize)),
GetGlobal(global_idx) =>
Instruction::GetGlobal(self.globals.clone_ref(*global_idx as usize)),
other_instruction => Instruction::Plain(other_instruction.clone()),
}).collect()
}
fn generate_instructions(&self, instructions: &[Instruction]) -> Vec<elements::Instruction> {
use parity_wasm::elements::Instruction::*;
instructions.iter().map(|instruction| match instruction {
Instruction::Call(func_ref) => Call(func_ref.order().expect("detached instruction!") as u32),
Instruction::CallIndirect(type_ref, arg2) => CallIndirect(type_ref.order().expect("detached instruction!") as u32, *arg2),
Instruction::SetGlobal(global_ref) => SetGlobal(global_ref.order().expect("detached instruction!") as u32),
Instruction::GetGlobal(global_ref) => GetGlobal(global_ref.order().expect("detached instruction!") as u32),
Instruction::Plain(plain) => plain.clone(),
}).collect()
}
/// Initialize module from parity-wasm `Module`.
pub fn from_elements(module: &elements::Module) -> Result<Self, Error> {
let mut idx = 0;
let mut res = Module::default();
let mut imported_functions = 0;
for section in module.sections() {
match section {
elements::Section::Type(type_section) => {
res.types = RefList::from_slice(type_section.types());
},
elements::Section::Import(import_section) => {
for entry in import_section.entries() {
match *entry.external() {
elements::External::Function(f) => {
res.funcs.push(Func {
type_ref: res.types.get(f as usize).ok_or(Error::InconsistentSource)?.clone(),
origin: entry.into(),
});
imported_functions += 1;
},
elements::External::Memory(m) => {
res.memory.push(Memory {
limits: m.limits().clone(),
origin: entry.into(),
});
},
elements::External::Global(g) => {
res.globals.push(Global {
content: g.content_type(),
is_mut: g.is_mutable(),
origin: entry.into(),
});
},
elements::External::Table(t) => {
res.tables.push(Table {
limits: t.limits().clone(),
origin: entry.into(),
});
},
};
}
},
elements::Section::Function(function_section) => {
for f in function_section.entries() {
res.funcs.push(Func {
type_ref: res.types.get(f.type_ref() as usize)
.ok_or(Error::InconsistentSource)?.clone(),
origin: ImportedOrDeclared::Declared(FuncBody {
locals: Vec::new(),
// code will be populated later
code: Vec::new(),
}),
});
};
},
elements::Section::Table(table_section) => {
for t in table_section.entries() {
res.tables.push(Table {
limits: t.limits().clone(),
origin: ImportedOrDeclared::Declared(()),
});
}
},
elements::Section::Memory(table_section) => {
for t in table_section.entries() {
res.memory.push(Memory {
limits: t.limits().clone(),
origin: ImportedOrDeclared::Declared(()),
});
}
},
elements::Section::Global(global_section) => {
for g in global_section.entries() {
let init_code = res.map_instructions(g.init_expr().code());
res.globals.push(Global {
content: g.global_type().content_type(),
is_mut: g.global_type().is_mutable(),
origin: ImportedOrDeclared::Declared(init_code),
});
}
},
elements::Section::Export(export_section) => {
for e in export_section.entries() {
let local = match e.internal() {
&elements::Internal::Function(func_idx) => {
ExportLocal::Func(res.funcs.clone_ref(func_idx as usize))
},
&elements::Internal::Global(global_idx) => {
ExportLocal::Global(res.globals.clone_ref(global_idx as usize))
},
&elements::Internal::Memory(mem_idx) => {
ExportLocal::Memory(res.memory.clone_ref(mem_idx as usize))
},
&elements::Internal::Table(table_idx) => {
ExportLocal::Table(res.tables.clone_ref(table_idx as usize))
},
};
res.exports.push(Export { local: local, name: e.field().to_owned() })
}
},
elements::Section::Start(start_func) => {
res.start = Some(res.funcs.clone_ref(*start_func as usize));
},
elements::Section::Element(element_section) => {
for element_segment in element_section.entries() {
// let location = if element_segment.passive() {
// SegmentLocation::Passive
// } else if element_segment.index() == 0 {
// SegmentLocation::Default(Vec::new())
// } else {
// SegmentLocation::WithIndex(element_segment.index(), Vec::new())
// };
// TODO: update parity-wasm and uncomment the above instead
let init_expr = element_segment
.offset()
.as_ref()
.expect("parity-wasm is compiled without bulk-memory operations")
.code();
let location = SegmentLocation::Default(res.map_instructions(init_expr));
let funcs_map = element_segment
.members().iter()
.map(|idx| res.funcs.clone_ref(*idx as usize))
.collect::<Vec<EntryRef<Func>>>();
res.elements.push(ElementSegment {
value: funcs_map,
location: location,
});
}
},
elements::Section::Code(code_section) => {
let mut idx = 0;
for func_body in code_section.bodies() {
let code = res.map_instructions(func_body.code().elements());
let mut func = res.funcs.get_ref(imported_functions + idx).write();
match func.origin {
ImportedOrDeclared::Declared(ref mut body) => {
body.code = code;
body.locals = func_body.locals().iter().cloned().collect();
},
_ => { return Err(Error::InconsistentSource); }
}
idx += 1;
}
},
elements::Section::Data(data_section) => {
for data_segment in data_section.entries() {
// TODO: update parity-wasm and use the same logic as in
// commented element segment branch
let init_expr = data_segment
.offset()
.as_ref()
.expect("parity-wasm is compiled without bulk-memory operations")
.code();
let location = SegmentLocation::Default(res.map_instructions(init_expr));
res.data.push(DataSegment {
value: data_segment.value().to_vec(),
location: location,
});
}
},
_ => {
res.other.insert(idx, section.clone());
}
}
idx += 1;
}
Ok(res)
}
/// Generate raw format representation.
pub fn generate(&self) -> Result<elements::Module, Error> {
use self::ImportedOrDeclared::*;
let mut idx = 0;
let mut sections = Vec::new();
custom_round(&self.other, &mut idx, &mut sections);
if self.types.len() > 0 {
// TYPE SECTION (1)
let mut type_section = elements::TypeSection::default();
{
let types = type_section.types_mut();
for type_entry in self.types.iter() {
types.push(type_entry.read().clone())
}
}
sections.push(elements::Section::Type(type_section));
idx += 1;
custom_round(&self.other, &mut idx, &mut sections);
}
// IMPORT SECTION (2)
let mut import_section = elements::ImportSection::default();
let add = {
let imports = import_section.entries_mut();
for func in self.funcs.iter() {
match func.read().origin {
Imported(ref module, ref field) => {
imports.push(
elements::ImportEntry::new(
module.to_owned(),
field.to_owned(),
elements::External::Function(
func.read().type_ref.order().ok_or(Error::DetachedEntry)? as u32
),
)
)
},
_ => continue,
}
}
for global in self.globals.iter() {
match global.read().origin {
Imported(ref module, ref field) => {
imports.push(
elements::ImportEntry::new(
module.to_owned(),
field.to_owned(),
elements::External::Global(
elements::GlobalType::new(
global.read().content,
global.read().is_mut,
)
),
)
)
},
_ => continue,
}
}
for memory in self.memory.iter() {
match memory.read().origin {
Imported(ref module, ref field) => {
imports.push(
elements::ImportEntry::new(
module.to_owned(),
field.to_owned(),
elements::External::Memory(
elements::MemoryType::new(
memory.read().limits.initial(),
memory.read().limits.maximum(),
)
),
)
)
},
_ => continue,
}
}
for table in self.tables.iter() {
match table.read().origin {
Imported(ref module, ref field) => {
imports.push(
elements::ImportEntry::new(
module.to_owned(),
field.to_owned(),
elements::External::Table(
elements::TableType::new(
table.read().limits.initial(),
table.read().limits.maximum(),
)
),
)
)
},
_ => continue,
}
}
imports.len() > 0
};
if add {
sections.push(elements::Section::Import(import_section));
idx += 1;
custom_round(&self.other, &mut idx, &mut sections);
}
if self.funcs.len() > 0 {
// FUNC SECTION (3)
let mut func_section = elements::FunctionSection::default();
{
let funcs = func_section.entries_mut();
for func in self.funcs.iter() {
match func.read().origin {
Declared(_) => {
funcs.push(elements::Func::new(
func.read().type_ref.order().ok_or(Error::DetachedEntry)? as u32
));
},
_ => continue,
}
}
}
sections.push(elements::Section::Function(func_section));
idx += 1;
custom_round(&self.other, &mut idx, &mut sections);
}
if self.tables.len() > 0 {
// TABLE SECTION (4)
let mut table_section = elements::TableSection::default();
{
let tables = table_section.entries_mut();
for table in self.tables.iter() {
match table.read().origin {
Declared(_) => {
tables.push(elements::TableType::new(
table.read().limits.initial(),
table.read().limits.maximum(),
));
},
_ => continue,
}
}
}
sections.push(elements::Section::Table(table_section));
idx += 1;
custom_round(&self.other, &mut idx, &mut sections);
}
if self.memory.len() > 0 {
// MEMORY SECTION (5)
let mut memory_section = elements::MemorySection::default();
{
let memories = memory_section.entries_mut();
for memory in self.memory.iter() {
match memory.read().origin {
Declared(_) => {
memories.push(elements::MemoryType::new(
memory.read().limits.initial(),
memory.read().limits.maximum(),
));
},
_ => continue,
}
}
}
sections.push(elements::Section::Memory(memory_section));
idx += 1;
custom_round(&self.other, &mut idx, &mut sections);
}
if self.globals.len() > 0 {
// GLOBAL SECTION (6)
let mut global_section = elements::GlobalSection::default();
{
let globals = global_section.entries_mut();
for global in self.globals.iter() {
match global.read().origin {
Declared(ref init_code) => {
globals.push(elements::GlobalEntry::new(
elements::GlobalType::new(global.read().content, global.read().is_mut),
elements::InitExpr::new(self.generate_instructions(&init_code[..])),
));
},
_ => continue,
}
}
}
sections.push(elements::Section::Global(global_section));
idx += 1;
custom_round(&self.other, &mut idx, &mut sections);
}
if self.exports.len() > 0 {
// EXPORT SECTION (7)
let mut export_section = elements::ExportSection::default();
{
let exports = export_section.entries_mut();
for export in self.exports.iter() {
let internal = match export.local {
ExportLocal::Func(ref func_ref) => {
elements::Internal::Function(func_ref.order().ok_or(Error::DetachedEntry)? as u32)
},
ExportLocal::Global(ref global_ref) => {
elements::Internal::Global(global_ref.order().ok_or(Error::DetachedEntry)? as u32)
},
ExportLocal::Table(ref table_ref) => {
elements::Internal::Table(table_ref.order().ok_or(Error::DetachedEntry)? as u32)
},
ExportLocal::Memory(ref memory_ref) => {
elements::Internal::Memory(memory_ref.order().ok_or(Error::DetachedEntry)? as u32)
},
};
exports.push(elements::ExportEntry::new(export.name.to_owned(), internal));
}
}
sections.push(elements::Section::Export(export_section));
idx += 1;
custom_round(&self.other, &mut idx, &mut sections);
}
if let Some(ref func_ref) = self.start {
// START SECTION (8)
sections.push(elements::Section::Start(
func_ref.order().ok_or(Error::DetachedEntry)? as u32
));
}
if self.elements.len() > 0 {
// START SECTION (9)
let mut element_section = elements::ElementSection::default();
{
let element_segments = element_section.entries_mut();
for element in self.elements.iter() {
match element.location {
SegmentLocation::Default(ref offset_expr) => {
let mut elements_map = Vec::new();
for f in element.value.iter() {
elements_map.push(f.order().ok_or(Error::DetachedEntry)? as u32);
}
element_segments.push(
elements::ElementSegment::new(
0,
Some(elements::InitExpr::new(self.generate_instructions(&offset_expr[..]))),
elements_map,
)
);
},
_ => unreachable!("Other segment location types are never added"),
}
}
}
sections.push(elements::Section::Element(element_section));
idx += 1;
custom_round(&self.other, &mut idx, &mut sections);
}
if self.funcs.len() > 0 {
// CODE SECTION (10)
let mut code_section = elements::CodeSection::default();
{
let funcs = code_section.bodies_mut();
for func in self.funcs.iter() {
match func.read().origin {
Declared(ref body) => {
funcs.push(elements::FuncBody::new(
body.locals.clone(),
elements::Instructions::new(self.generate_instructions(&body.code[..])),
));
},
_ => continue,
}
}
}
sections.push(elements::Section::Code(code_section));
idx += 1;
custom_round(&self.other, &mut idx, &mut sections);
}
if self.data.len() > 0 {
// DATA SECTION (11)
let mut data_section = elements::DataSection::default();
{
let data_segments = data_section.entries_mut();
for data_entry in self.data.iter() {
match data_entry.location {
SegmentLocation::Default(ref offset_expr) => {
data_segments.push(
elements::DataSegment::new(
0,
Some(elements::InitExpr::new(self.generate_instructions(&offset_expr[..]))),
data_entry.value.clone(),
)
);
},
_ => unreachable!("Other segment location types are never added"),
}
}
}
sections.push(elements::Section::Data(data_section));
idx += 1;
custom_round(&self.other, &mut idx, &mut sections);
}
Ok(elements::Module::new(sections))
}
}
fn custom_round(
map: &BTreeMap<usize, elements::Section>,
idx: &mut usize,
sections: &mut Vec<elements::Section>,
) {
while let Some(other_section) = map.get(&idx) {
sections.push(other_section.clone());
*idx += 1;
}
}
/// New module from parity-wasm `Module`
pub fn parse(wasm: &[u8]) -> Result<Module, Error> {
Module::from_elements(&::parity_wasm::deserialize_buffer(wasm).map_err(Error::Format)?)
}
/// Generate parity-wasm `Module`
pub fn generate(f: &Module) -> Result<Vec<u8>, Error> {
let pm = f.generate()?;
::parity_wasm::serialize(pm).map_err(Error::Format)
}
#[cfg(test)]
mod tests {
extern crate wabt;
use parity_wasm::elements;
fn load_sample(wat: &'static str) -> super::Module {
super::parse(&wabt::wat2wasm(wat).expect("faled to parse wat!")[..])
.expect("error making representation")
}
fn validate_sample(module: &super::Module) {
let binary = super::generate(module).expect("Failed to generate binary");
wabt::Module::read_binary(&binary, &Default::default())
.expect("Wabt failed to read final binary")
.validate()
.expect("Invalid module");
}
#[test]
fn smoky() {
let sample = load_sample(indoc!(r#"
(module
(type (func))
(func (type 0))
(memory 0 1)
(export "simple" (func 0)))"#
));
assert_eq!(sample.types.len(), 1);
assert_eq!(sample.funcs.len(), 1);
assert_eq!(sample.tables.len(), 0);
assert_eq!(sample.memory.len(), 1);
assert_eq!(sample.exports.len(), 1);
assert_eq!(sample.types.get_ref(0).link_count(), 1);
assert_eq!(sample.funcs.get_ref(0).link_count(), 1);
}
#[test]
fn table() {
let mut sample = load_sample(indoc!(r#"
(module
(import "env" "foo" (func $foo))
(func (param i32)
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
)
(table 10 anyfunc)
;; Refer all types of functions: imported, defined not exported and defined exported.
(elem (i32.const 0) 0 1 2)
)"#
));
{
let element_func = &sample.elements[0].value[1];
let rfunc = element_func.read();
let rtype = &**rfunc.type_ref.read();
let elements::Type::Function(ref ftype) = rtype;
// it's func#1 in the function space
assert_eq!(rfunc.order(), Some(1));
// it's type#1
assert_eq!(ftype.params().len(), 1);
}
sample.funcs.begin_delete().push(0).done();
{
let element_func = &sample.elements[0].value[1];
let rfunc = element_func.read();
let rtype = &**rfunc.type_ref.read();
let elements::Type::Function(ref ftype) = rtype;
// import deleted so now it's func #0
assert_eq!(rfunc.order(), Some(0));
// type should be the same, #1
assert_eq!(ftype.params().len(), 1);
}
}
#[test]
fn new_import() {
let mut sample = load_sample(indoc!(r#"
(module
(type (;0;) (func))
(type (;1;) (func (param i32 i32) (result i32)))
(import "env" "foo" (func (type 1)))
(func (param i32)
get_local 0
i32.const 0
call 0
drop
)
(func (type 0)
i32.const 0
call 1
)
)"#
));
{
let type_ref_0 = sample.types.clone_ref(0);
let declared_func_2 = sample.funcs.clone_ref(2);
let mut tx = sample.funcs.begin_insert_not_until(
|f| match f.origin {
super::ImportedOrDeclared::Imported(_, _) => true,
_ => false,
}
);
let new_import_func = tx.push(super::Func {
type_ref: type_ref_0,
origin: super::ImportedOrDeclared::Imported("env".to_owned(), "bar".to_owned()),
});
tx.done();
assert_eq!(new_import_func.order(), Some(1));
assert_eq!(declared_func_2.order(), Some(3));
assert_eq!(
match &declared_func_2.read().origin {
super::ImportedOrDeclared::Declared(ref body) => {
match body.code[1] {
super::Instruction::Call(ref called_func) => called_func.order(),
_ => panic!("instruction #2 should be a call!"),
}
},
_ => panic!("func #3 should be declared!"),
},
Some(2),
"Call should be recalculated to 2"
);
}
validate_sample(&sample);
}
#[test]
fn simple_opt() {
let mut sample = load_sample(indoc!(r#"
(module
(type (;0;) (func))
(type (;1;) (func (param i32 i32) (result i32)))
(type (;2;) (func (param i32 i32) (result i32)))
(type (;3;) (func (param i32 i32) (result i32)))
(import "env" "foo" (func (type 1)))
(import "env" "foo2" (func (type 2)))
(import "env" "foo3" (func (type 3)))
(func (type 0)
i32.const 1
i32.const 1
call 0
drop
)
(func (type 0)
i32.const 2
i32.const 2
call 1
drop
)
(func (type 0)
i32.const 3
i32.const 3
call 2
drop
)
(func (type 0)
call 3
)
)"#
));
validate_sample(&sample);
// we'll delete functions #4 and #5, nobody references it so it should be fine;
sample.funcs.begin_delete().push(4).push(5).done();
validate_sample(&sample);
// now we'll delete functions #1 and #2 (imported and called from the deleted above),
// should also be fine
sample.funcs.begin_delete().push(1).push(2).done();
validate_sample(&sample);
// now the last declared function left should call another one before it (which is index #1)
let declared_func_2 = sample.funcs.clone_ref(2);
assert_eq!(
match &declared_func_2.read().origin {
super::ImportedOrDeclared::Declared(ref body) => {
match body.code[0] {
super::Instruction::Call(ref called_func) => called_func.order(),
ref wrong_instruction => panic!("instruction #2 should be a call but got {:?}!", wrong_instruction),
}
},
_ => panic!("func #0 should be declared!"),
},
Some(1),
"Call should be recalculated to 1"
);
}
}
+7 -85
View File
@@ -1,91 +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;
extern crate byteorder;
extern crate parity_wasm;
#[macro_use] extern crate log;
#[cfg(test)] #[macro_use] extern crate indoc;
#[cfg(test)] extern crate rand;
#[cfg(test)] extern crate binaryen;
mod export_globals;
pub mod gas_metering;
mod stack_limiter;
pub mod rules;
mod build;
mod ext;
mod gas;
mod optimizer;
mod pack;
mod runtime_type;
mod graph;
mod ref_list;
mod symbols;
pub mod stack_height;
pub use build::{build, Error as BuildError, SourceTarget};
pub use ext::{
externalize, externalize_mem, shrink_unknown_stack, underscore_funcs, ununderscore_funcs,
pub use export_globals::export_mutable_globals;
pub use parity_wasm;
pub use stack_limiter::{
compute_stack_cost, compute_stack_height_weight, inject as inject_stack_limiter,
};
pub use gas::inject_gas_counter;
pub use optimizer::{optimize, Error as OptimizerError};
pub use pack::{pack_instance, Error as PackingError};
pub use runtime_type::inject_runtime_type;
pub use graph::{Module, parse as graph_parse, generate as graph_generate};
pub use ref_list::{RefList, Entry, EntryRef, DeleteTransaction};
pub struct TargetSymbols {
pub create: &'static str,
pub call: &'static str,
pub ret: &'static str,
}
pub enum TargetRuntime {
Substrate(TargetSymbols),
PWasm(TargetSymbols),
}
impl TargetRuntime {
pub fn substrate() -> TargetRuntime {
TargetRuntime::Substrate(TargetSymbols {
create: "deploy",
call: "call",
ret: "ext_return",
})
}
pub fn pwasm() -> TargetRuntime {
TargetRuntime::PWasm(TargetSymbols {
create: "deploy",
call: "call",
ret: "ret",
})
}
pub fn symbols(&self) -> &TargetSymbols {
match self {
TargetRuntime::Substrate(s) => s,
TargetRuntime::PWasm(s) => s,
}
}
}
#[cfg(not(feature = "std"))]
mod std {
pub use alloc::{borrow, boxed, string, vec};
pub use core::*;
pub mod rc {
pub use alloc::rc::Rc;
}
pub mod collections {
pub use alloc::collections::{BTreeMap, BTreeSet};
}
}
-663
View File
@@ -1,663 +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 std::mem;
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
// try to parse name section
let module_temp = mem::replace(module, elements::Module::default());
let module_temp = module_temp
.parse_names()
.unwrap_or_else(|(_err, module)| module);
mem::replace(module, module_temp);
// 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()
.as_ref()
.expect("parity-wasm is compiled without bulk-memory operations")
.code(),
&mut init_symbols,
);
}
}
if let Some(elements_section) = module.elements_section() {
for segment in elements_section.entries() {
push_code_symbols(
&module,
segment
.offset()
.as_ref()
.expect("parity-wasm is compiled without bulk-memory operations")
.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()
.as_mut()
.expect("parity-wasm is compiled without bulk-memory operations")
.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()
.as_mut()
.expect("parity-wasm is compiled without bulk-memory operations")
.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;
}
}
},
&mut elements::Section::Name(ref mut name_section) => {
if let Some(ref mut func_name) = name_section.functions_mut() {
let mut func_name_map = mem::replace(func_name.names_mut(), elements::IndexMap::default());
for index in &eliminated_funcs {
func_name_map.remove(*index as u32);
}
let updated_map = func_name_map.into_iter().map(|(index, value)| {
let totalle = eliminated_funcs.iter().take_while(|i| (**i as u32) < index).count() as u32;
(index - totalle, value)
}).collect();
mem::replace(func_name.names_mut(), updated_map);
}
if let Some(ref mut local_name) = name_section.locals_mut() {
let mut local_names_map = mem::replace(local_name.local_names_mut(), elements::IndexMap::default());
for index in &eliminated_funcs {
local_names_map.remove(*index as u32);
}
let updated_map = local_names_map.into_iter().map(|(index, value)| {
let totalle = eliminated_funcs.iter().take_while(|i| (**i as u32) < index).count() as u32;
(index - totalle, value)
}).collect();
mem::replace(local_name.local_names_mut(), updated_map);
}
}
_ => { }
}
}
}
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
);
}
}
}
}
-340
View File
@@ -1,340 +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 a pwasm module has an exported function matching "create" symbol 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 which is exported as `symbols().create`
// 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.symbols().create == entry.field()).ok_or_else(|| Error::NoCreateSymbol(target.symbols().create))?;
let function_index: usize = match found_entry.internal() {
&Internal::Function(index) => index as usize,
_ => { return Err(Error::InvalidCreateMember(target.symbols().create)) },
};
// 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.symbols().create));
}
if func.return_type().is_some() {
return Err(Error::InvalidCreateSignature(target.symbols().create));
}
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.symbols().ret { 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.symbols().ret)
.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() {
let init_expr = entry
.offset()
.as_ref()
.expect("parity-wasm is compiled without bulk-memory operations")
.code();
if let Instruction::I32Const(offst) = init_expr[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,
Some(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.symbols().create == entry.field() {
// change `create` symbol export name into default `call` symbol name.
*entry.field_mut() = target.symbols().call.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.symbols().call]).expect("Optimizer to finish without errors");
optimize(&mut ctor_module, vec![target_runtime.symbols().create]).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.symbols().call)
.internal().func(1)
.build()
.export()
.field(target_runtime.symbols().create)
.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.symbols().call)
.internal().func(1)
.build()
.export()
.field(target_runtime.symbols().create)
.internal().func(2)
.build()
.build(),
&target_runtime,
);
}
}
-562
View File
@@ -1,562 +0,0 @@
#![warn(missing_docs)]
use std::rc::Rc;
use std::cell::RefCell;
use std::vec::Vec;
use std::slice;
#[derive(Debug)]
enum EntryOrigin {
Index(usize),
Detached,
}
impl From<usize> for EntryOrigin {
fn from(v: usize) -> Self {
EntryOrigin::Index(v)
}
}
/// Reference counting, link-handling object.
#[derive(Debug)]
pub struct Entry<T> {
val: T,
index: EntryOrigin,
}
impl<T> Entry<T> {
/// New entity.
pub fn new(val: T, index: usize) -> Entry<T> {
Entry {
val: val,
index: EntryOrigin::Index(index),
}
}
/// New detached entry.
pub fn new_detached(val: T) -> Entry<T> {
Entry {
val: val,
index: EntryOrigin::Detached,
}
}
/// Index of the element within the reference list.
pub fn order(&self) -> Option<usize> {
match self.index {
EntryOrigin::Detached => None,
EntryOrigin::Index(idx) => Some(idx),
}
}
}
impl<T> ::std::ops::Deref for Entry<T> {
type Target = T;
fn deref(&self) -> &T {
&self.val
}
}
impl<T> ::std::ops::DerefMut for Entry<T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.val
}
}
/// Reference to the entry in the rerence list.
#[derive(Debug)]
pub struct EntryRef<T>(Rc<RefCell<Entry<T>>>);
impl<T> Clone for EntryRef<T> {
fn clone(&self) -> Self {
EntryRef(self.0.clone())
}
}
impl<T> From<Entry<T>> for EntryRef<T> {
fn from(v: Entry<T>) -> Self {
EntryRef(Rc::new(RefCell::new(v)))
}
}
impl<T> EntryRef<T> {
/// Read the reference data.
pub fn read(&self) -> ::std::cell::Ref<Entry<T>> {
self.0.borrow()
}
/// Try to modify internal content of the referenced object.
///
/// May panic if it is already borrowed.
pub fn write(&self) -> ::std::cell::RefMut<Entry<T>> {
self.0.borrow_mut()
}
/// Index of the element within the reference list.
pub fn order(&self) -> Option<usize> {
self.0.borrow().order()
}
/// Number of active links to this entity.
pub fn link_count(&self) -> usize {
Rc::strong_count(&self.0) - 1
}
}
/// List that tracks references and indices.
#[derive(Debug)]
pub struct RefList<T> {
items: Vec<EntryRef<T>>,
}
impl<T> Default for RefList<T> {
fn default() -> Self {
RefList { items: Default::default() }
}
}
impl<T> RefList<T> {
/// New empty list.
pub fn new() -> Self { Self::default() }
/// Push new element in the list.
///
/// Returns refernce tracking entry.
pub fn push(&mut self, t: T) -> EntryRef<T> {
let idx = self.items.len();
let val: EntryRef<_> = Entry::new(t, idx).into();
self.items.push(val.clone());
val
}
/// Start deleting.
///
/// Start deleting some entries in the list. Returns transaction
/// that can be populated with number of removed entries.
/// When transaction is finailized, all entries are deleted and
/// internal indices of other entries are updated.
pub fn begin_delete(&mut self) -> DeleteTransaction<T> {
DeleteTransaction {
list: self,
deleted: Vec::new(),
}
}
/// Start inserting.
///
/// Start inserting some entries in the list at he designated position.
/// Returns transaction that can be populated with some entries.
/// When transaction is finailized, all entries are inserted and
/// internal indices of other entries might be updated.
pub fn begin_insert(&mut self, at: usize) -> InsertTransaction<T> {
InsertTransaction {
at: at,
list: self,
items: Vec::new(),
}
}
/// Start inserting after the condition first matches (or at the end).
///
/// Start inserting some entries in the list at he designated position.
/// Returns transaction that can be populated with some entries.
/// When transaction is finailized, all entries are inserted and
/// internal indices of other entries might be updated.
pub fn begin_insert_after<F>(&mut self, mut f: F) -> InsertTransaction<T>
where F : FnMut(&T) -> bool
{
let pos = self
.items.iter()
.position(|rf| f(&**rf.read())).map(|x| x + 1)
.unwrap_or(self.items.len());
self.begin_insert(pos)
}
/// Start inserting after the condition first no longer true (or at the end).
///
/// Start inserting some entries in the list at he designated position.
/// Returns transaction that can be populated with some entries.
/// When transaction is finailized, all entries are inserted and
/// internal indices of other entries might be updated.
pub fn begin_insert_not_until<F>(&mut self, mut f: F) -> InsertTransaction<T>
where F : FnMut(&T) -> bool
{
let pos = self.items.iter().take_while(|rf| f(&**rf.read())).count();
self.begin_insert(pos)
}
/// Get entry with index (checked).
///
/// Can return None when index out of bounts.
pub fn get(&self, idx: usize) -> Option<EntryRef<T>> {
self.items.get(idx).cloned()
}
fn done_delete(&mut self, indices: &[usize]) {
for entry in self.items.iter_mut() {
let mut entry = entry.write();
let total_less = indices.iter()
.take_while(|x| **x < entry.order().expect("Items in the list always have order; qed"))
.count();
match entry.index {
EntryOrigin::Detached => unreachable!("Items in the list always have order!"),
EntryOrigin::Index(ref mut idx) => { *idx -= total_less; },
};
}
let mut total_removed = 0;
for idx in indices {
let detached = self.items.remove(*idx - total_removed);
detached.write().index = EntryOrigin::Detached;
total_removed += 1;
}
}
fn done_insert(&mut self, index: usize, mut items: Vec<EntryRef<T>>) {
let mut offset = 0;
for item in items.drain(..) {
item.write().index = EntryOrigin::Index(index + offset);
self.items.insert(index + offset, item);
offset += 1;
}
for idx in (index+offset)..self.items.len() {
self.get_ref(idx).write().index = EntryOrigin::Index(idx);
}
}
/// Delete several items.
pub fn delete(&mut self, indices: &[usize]) {
self.done_delete(indices)
}
/// Delete one item.
pub fn delete_one(&mut self, index: usize) {
self.done_delete(&[index])
}
/// Initialize from slice.
///
/// Slice members are cloned.
pub fn from_slice(list: &[T]) -> Self
where T: Clone
{
let mut res = Self::new();
for t in list {
res.push(t.clone());
}
res
}
/// Length of the list.
pub fn len(&self) -> usize {
self.items.len()
}
/// Clone entry (reference counting object to item) by index.
///
/// Will panic if index out of bounds.
pub fn clone_ref(&self, idx: usize) -> EntryRef<T> {
self.items[idx].clone()
}
/// Get reference to entry by index.
///
/// Will panic if index out of bounds.
pub fn get_ref(&self, idx: usize) -> &EntryRef<T> {
&self.items[idx]
}
/// Iterate through entries.
pub fn iter(&self) -> slice::Iter<EntryRef<T>> {
self.items.iter()
}
}
/// Delete transaction.
#[must_use]
pub struct DeleteTransaction<'a, T> {
list: &'a mut RefList<T>,
deleted: Vec<usize>,
}
impl<'a, T> DeleteTransaction<'a, T> {
/// Add new element to the delete list.
pub fn push(self, idx: usize) -> Self {
let mut tx = self;
tx.deleted.push(idx);
tx
}
/// Commit transaction.
pub fn done(self) {
let indices = self.deleted;
let list = self.list;
list.done_delete(&indices[..]);
}
}
/// Insert transaction
#[must_use]
pub struct InsertTransaction<'a, T> {
at: usize,
list: &'a mut RefList<T>,
items: Vec<EntryRef<T>>,
}
impl<'a, T> InsertTransaction<'a, T> {
/// Add new element to the delete list.
pub fn push(&mut self, val: T) -> EntryRef<T> {
let val: EntryRef<_> = Entry::new_detached(val).into();
self.items.push(val.clone());
val
}
/// Commit transaction.
pub fn done(self) {
let items = self.items;
let list = self.list;
let at = self.at;
list.done_insert(at, items);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn order() {
let mut list = RefList::<u32>::new();
let item00 = list.push(0);
let item10 = list.push(10);
let item20 = list.push(20);
let item30 = list.push(30);
assert_eq!(item00.order(), Some(0));
assert_eq!(item10.order(), Some(1));
assert_eq!(item20.order(), Some(2));
assert_eq!(item30.order(), Some(3));
assert_eq!(**item00.read(), 0);
assert_eq!(**item10.read(), 10);
assert_eq!(**item20.read(), 20);
assert_eq!(**item30.read(), 30);
}
#[test]
fn delete() {
let mut list = RefList::<u32>::new();
let item00 = list.push(0);
let item10 = list.push(10);
let item20 = list.push(20);
let item30 = list.push(30);
list.begin_delete().push(2).done();
assert_eq!(item00.order(), Some(0));
assert_eq!(item10.order(), Some(1));
assert_eq!(item30.order(), Some(2));
// but this was detached
assert_eq!(item20.order(), None);
}
#[test]
fn complex_delete() {
let mut list = RefList::<u32>::new();
let item00 = list.push(0);
let item10 = list.push(10);
let item20 = list.push(20);
let item30 = list.push(30);
let item40 = list.push(40);
let item50 = list.push(50);
let item60 = list.push(60);
let item70 = list.push(70);
let item80 = list.push(80);
let item90 = list.push(90);
list.begin_delete().push(1).push(2).push(4).push(6).done();
assert_eq!(item00.order(), Some(0));
assert_eq!(item10.order(), None);
assert_eq!(item20.order(), None);
assert_eq!(item30.order(), Some(1));
assert_eq!(item40.order(), None);
assert_eq!(item50.order(), Some(2));
assert_eq!(item60.order(), None);
assert_eq!(item70.order(), Some(3));
assert_eq!(item80.order(), Some(4));
assert_eq!(item90.order(), Some(5));
}
#[test]
fn insert() {
let mut list = RefList::<u32>::new();
let item00 = list.push(0);
let item10 = list.push(10);
let item20 = list.push(20);
let item30 = list.push(30);
let mut insert_tx = list.begin_insert(3);
let item23 = insert_tx.push(23);
let item27 = insert_tx.push(27);
insert_tx.done();
assert_eq!(item00.order(), Some(0));
assert_eq!(item10.order(), Some(1));
assert_eq!(item20.order(), Some(2));
assert_eq!(item23.order(), Some(3));
assert_eq!(item27.order(), Some(4));
assert_eq!(item30.order(), Some(5));
}
#[test]
fn insert_end() {
let mut list = RefList::<u32>::new();
let mut insert_tx = list.begin_insert(0);
let item0 = insert_tx.push(0);
insert_tx.done();
assert_eq!(item0.order(), Some(0));
}
#[test]
fn insert_end_more() {
let mut list = RefList::<u32>::new();
let item0 = list.push(0);
let mut insert_tx = list.begin_insert(1);
let item1 = insert_tx.push(1);
insert_tx.done();
assert_eq!(item0.order(), Some(0));
assert_eq!(item1.order(), Some(1));
}
#[test]
fn insert_after() {
let mut list = RefList::<u32>::new();
let item00 = list.push(0);
let item10 = list.push(10);
let item20 = list.push(20);
let item30 = list.push(30);
let mut insert_tx = list.begin_insert_after(|i| *i == 20);
let item23 = insert_tx.push(23);
let item27 = insert_tx.push(27);
insert_tx.done();
assert_eq!(item00.order(), Some(0));
assert_eq!(item10.order(), Some(1));
assert_eq!(item20.order(), Some(2));
assert_eq!(item23.order(), Some(3));
assert_eq!(item27.order(), Some(4));
assert_eq!(item30.order(), Some(5));
}
#[test]
fn insert_not_until() {
let mut list = RefList::<u32>::new();
let item10 = list.push(10);
let item20 = list.push(20);
let item30 = list.push(30);
let mut insert_tx = list.begin_insert_not_until(|i| *i <= 20);
let item23 = insert_tx.push(23);
let item27 = insert_tx.push(27);
insert_tx.done();
assert_eq!(item10.order(), Some(0));
assert_eq!(item20.order(), Some(1));
assert_eq!(item23.order(), Some(2));
assert_eq!(item27.order(), Some(3));
assert_eq!(item30.order(), Some(4));
}
#[test]
fn insert_after_none() {
let mut list = RefList::<u32>::new();
let item10 = list.push(10);
let item20 = list.push(20);
let item30 = list.push(30);
let mut insert_tx = list.begin_insert_after(|i| *i == 50);
let item55 = insert_tx.push(23);
let item59 = insert_tx.push(27);
insert_tx.done();
assert_eq!(item10.order(), Some(0));
assert_eq!(item20.order(), Some(1));
assert_eq!(item30.order(), Some(2));
assert_eq!(item55.order(), Some(3));
assert_eq!(item59.order(), Some(4));
}
#[test]
fn insert_not_until_none() {
let mut list = RefList::<u32>::new();
let item10 = list.push(10);
let item20 = list.push(20);
let item30 = list.push(30);
let mut insert_tx = list.begin_insert_not_until(|i| *i < 50);
let item55 = insert_tx.push(23);
let item59 = insert_tx.push(27);
insert_tx.done();
assert_eq!(item10.order(), Some(0));
assert_eq!(item20.order(), Some(1));
assert_eq!(item30.order(), Some(2));
assert_eq!(item55.order(), Some(3));
assert_eq!(item59.order(), Some(4));
}
#[test]
fn insert_after_empty() {
let mut list = RefList::<u32>::new();
let mut insert_tx = list.begin_insert_after(|x| *x == 100);
let item0 = insert_tx.push(0);
insert_tx.done();
assert_eq!(item0.order(), Some(0));
}
#[test]
fn insert_more() {
let mut list = RefList::<u32>::new();
let item10 = list.push(10);
let item20 = list.push(20);
let item30 = list.push(30);
let item40 = list.push(10);
let item50 = list.push(20);
let item60 = list.push(30);
let mut insert_tx = list.begin_insert(3);
let item35 = insert_tx.push(23);
let item37 = insert_tx.push(27);
insert_tx.done();
assert_eq!(item10.order(), Some(0));
assert_eq!(item20.order(), Some(1));
assert_eq!(item30.order(), Some(2));
assert_eq!(item35.order(), Some(3));
assert_eq!(item37.order(), Some(4));
assert_eq!(item40.order(), Some(5));
assert_eq!(item50.order(), Some(6));
assert_eq!(item60.order(), Some(7));
}
}
-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 br_table_data) => {
let arity_of_default = stack.frame(br_table_data.default)?.branch_arity;
// Check that all jump targets have an equal arities.
for target in &*br_table_data.table {
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);
}
}
-412
View File
@@ -1,412 +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 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.
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: u32,
func_stack_costs: Vec<u32>,
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> {
self.func_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: 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(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);
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<u32>, Error> {
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(0)
} 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.
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 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 indices that should be replaced by thunks
// Function indices 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_indices = exports.iter().filter_map(|entry| match *entry.internal() {
Internal::Function(ref 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_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)
}
+780
View File
@@ -0,0 +1,780 @@
use super::resolve_func_type;
use alloc::{vec, vec::Vec};
use parity_wasm::elements::{self, BlockType, Type, ValueType};
#[cfg(feature = "sign_ext")]
use parity_wasm::elements::SignExtInstruction;
#[cfg(feature = "trace-log")]
macro_rules! trace {
($($tt:tt)*) => {
::log::trace!($($tt)*);
};
}
#[cfg(not(feature = "trace-log"))]
macro_rules! trace {
($($tt:tt)*) => {};
}
// 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_HEIGHT: u32 = 2;
// Weight of an activation frame.
const ACTIVATION_FRAME_WEIGHT: u32 = 32;
/// Control stack frame.
#[derive(Debug)]
struct Frame {
/// Stack becomes polymorphic only after an instruction that
/// never passes control further was executed.
is_polymorphic: bool,
unreachable_depth: u32,
/// Type of value which will be pushed after exiting
/// the current block or `None` if block does not return a result.
result_type: Option<ValueType>,
/// Type of value which should be poped upon a branch to
/// this frame or `None` if branching shouldn't affect the stack.
///
/// This might be diffirent from `result_type` since branch
/// to the loop header can't take any values.
branch_type: Option<ValueType>,
/// Stack height before entering in the block.
start_height: usize,
}
#[derive(Clone)]
struct StackValue (ValueType, bool);
/// This is a compound stack that abstracts tracking height and weight of the value stack
/// and manipulation of the control stack.
struct Stack {
values: Vec<StackValue>,
control_stack: Vec<Frame>,
}
impl Stack {
fn new() -> Stack {
Stack { values: Vec::new(), control_stack: Vec::new() }
}
// fn new_from(stack: &Stack) -> Stack {
// Stack { values: stack.values.clone(), control_stack: stack.control_stack.clone() }
// }
/// Returns current weight of the value stack.
fn weight(&self) -> u32 {
self.values.iter().map(|v| value_cost(v.0)).sum()
}
/// Returns current height of the value stack.
fn height(&self) -> usize {
self.values.len()
}
/// 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.
///
/// This effectively makes stack polymorphic.
fn mark_unreachable(&mut self) -> Result<(), &'static str> {
let top_frame = self.control_stack.last_mut().ok_or("stack must be non-empty")?;
top_frame.is_polymorphic = true;
top_frame.unreachable_depth = 1;
Ok(())
}
fn push_unreachable(&mut self) -> Result<(), &'static str> {
let top_frame = self.control_stack.last_mut().ok_or("stack must be non-empty")?;
top_frame.unreachable_depth += 1;
Ok(())
}
fn pop_unreachable(&mut self) -> Result<u32, &'static str> {
let top_frame = self.control_stack.last_mut().ok_or("stack must be non-empty")?;
top_frame.unreachable_depth -= 1;
Ok(top_frame.unreachable_depth)
}
/// Push control frame into the control stack.
fn push_frame(&mut self, frame: Frame) {
trace!(" Push control frame {:?}", frame);
self.control_stack.push(frame);
}
/// Pop control frame from the control stack.
///
/// Returns `Err` if the control stack is empty.
#[allow(clippy::let_and_return)]
fn pop_frame(&mut self) -> Result<Frame, &'static str> {
trace!(" Pop control frame");
let frame = self.control_stack.pop().ok_or("stack must be non-empty");
trace!(" {:?}", frame);
frame
}
/// Truncate the height of value stack to the specified height.
fn trunc(&mut self, new_height: usize) {
trace!(" Truncate value stack to {}", new_height);
self.values.truncate(new_height);
}
/// Push a value into the value stack.
fn push_value(&mut self, value: StackValue) -> Result<(), &'static str> {
trace!(" Push {:?} to value stack", value);
self.values.push(value);
if self.values.len() >= u32::MAX as usize {
return Err("stack overflow")
}
Ok(())
}
/// Pop a value from the value stack.
///
/// Returns `Err` if the stack happen to be negative value after
/// value popped.
fn pop_value(&mut self) -> Result<Option<StackValue>, &'static str> {
let top_frame = self.frame(0)?;
if self.height() == top_frame.start_height {
return if top_frame.is_polymorphic {
Ok(None)
} else {
Err("trying to pop more values than pushed")
}
}
if self.height() > 0 {
let vt = self.values.pop();
trace!("Pop {:?} from value stack", vt);
Ok(vt)
} else {
Err("trying to pop more values than pushed")
}
}
}
fn value_cost(val: ValueType) -> u32 {
match val {
ValueType::I32 | ValueType::F32 => 4,
ValueType::I64 | ValueType::F64 => 8,
}
}
// struct FunctionContext {
// globals: Vec<ValueType>,
// locals: Vec<ValueType>,
// stack: Stack,
// result_type: Option<ValueType>
// }
/// This function expects the function to be validated.
pub fn compute(func_idx: u32, module: &elements::Module) -> Result<(u32, u32), &'static str> {
use parity_wasm::elements::Instruction::*;
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();
// Get globals to resove their types
let globals: Vec<ValueType> = if let Some(global_section) = module.global_section() {
global_section
.entries()
.iter()
.map(|g| g.global_type().content_type())
.collect()
} else {
Vec::new()
};
let mut locals: Vec<StackValue> = func_signature.params().iter().map(|p| StackValue(*p, false)).collect();
locals.extend(body.locals().iter().flat_map(|l| vec![StackValue(l.value_type(), true); l.count() as usize]));
let mut stack = Stack::new();
let mut max_weight: u32 = 0;
let mut max_height: usize = 0;
// Add implicit frame for the function. Breaks to this frame and execution of
// the last end should deal with this frame.
let func_results = func_signature.results();
let param_weight: u32 = func_signature.params().iter().map(|v| value_cost(*v)).sum();
let func_result_type = if func_results.is_empty() { None } else { Some(func_results[0]) };
stack.push_frame(Frame {
is_polymorphic: false,
unreachable_depth: 0,
result_type: func_result_type,
branch_type: func_result_type,
start_height: 0,
});
for opcode in instructions.elements() {
let current_frame = stack.frame(0)?;
if current_frame.is_polymorphic {
match opcode {
Block(ty) | Loop(ty) | If(ty) => {
trace!("Entering unreachable block {:?}", opcode);
stack.push_unreachable()?;
},
End => {
let depth = stack.pop_unreachable()?;
if depth == 0 {
trace!("Exiting unreachable code");
stack.pop_frame()?;
} else {
trace!("Exiting unreachable block");
}
},
_ => {
trace!("Skipping unreachable instruction {:?}", opcode);
}
}
continue;
}
trace!("Processing opcode {:?}", opcode);
match opcode {
Nop => {},
Block(ty) | Loop(ty) | If(ty) => {
if let If(_) = *opcode {
stack.pop_value()?;
}
let height = stack.height();
let end_result = if let BlockType::Value(vt) = *ty { Some(vt) } else { None };
stack.push_frame(Frame {
is_polymorphic: false,
unreachable_depth: 0,
result_type: end_result,
branch_type: if let Loop(_) = *opcode { None } else { end_result },
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);
// if let Some(vt) = frame.result_type {
// stack.push_value(vt)?;
// }
// // Push the frame back for now to allow for stack calculations. We'll get rid of it
// // later
// stack.push_frame(frame);
},
Unreachable => {
stack.mark_unreachable()?;
},
Br(target) => {
// Pop values for the destination block result.
// if stack.frame(*target)?.branch_type.is_some() {
// stack.pop_value()?;
// }
// 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) => {
// let target_type = stack.frame(*target)?.branch_type;
// // Pop values for the destination block result.
// if target_type.is_some() {
// stack.pop_value()?;
// }
// Pop condition value.
stack.pop_value()?;
// Push values back.
// if let Some(vt) = target_type {
// stack.push_value(vt)?;
// }
},
BrTable(br_table_data) => {
// let default_type = stack.frame(br_table_data.default)?.branch_type;
// Check that all jump targets have an equal number of parameters
// for target in &*br_table_data.table {
// if stack.frame(*target)?.branch_type != default_type {
// return Err("Types of all jump-targets must be equal")
// }
// }
// Because all jump targets have equal types, we can just take type of
// the default branch.
// if default_type.is_some() {
// stack.pop_value()?;
// }
// 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.
if func_result_type.is_some() {
stack.pop_value()?;
}
stack.mark_unreachable()?;
},
Call(idx) => {
let ty = resolve_func_type(*idx, module)?;
// Pop values for arguments of the function.
for _ in ty.params() {
stack.pop_value()?;
}
// Push result of the function execution to the stack.
let callee_results = ty.results();
if !callee_results.is_empty() {
stack.push_value(StackValue(callee_results[0], false))?;
}
},
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_value()?;
// Pop values for arguments of the function.
for _ in ty.params() {
stack.pop_value()?;
}
// Push result of the function execution to the stack.
let callee_results = ty.results();
if !callee_results.is_empty() {
stack.push_value(StackValue(callee_results[0], false))?;
}
},
Drop => {
stack.pop_value()?;
},
Select => {
// Pop two values and one condition.
let val = stack.pop_value()?;
stack.pop_value()?;
stack.pop_value()?;
// Push the selected value.
if let Some(vt) = val {
stack.push_value(vt)?;
}
},
GetLocal(idx) => {
let idx = *idx as usize;
if idx >= locals.len() {
return Err("Reference to a global is out of bounds")
}
stack.push_value(locals[idx])?;
},
SetLocal(_) => {
stack.pop_value()?;
},
TeeLocal(idx) => {
// This instruction pops and pushes the value, so
// effectively it doesn't modify the stack height.
let idx = *idx as usize;
if idx >= locals.len() {
return Err("Reference to a local is out of bounds")
}
stack.pop_value()?;
stack.push_value(locals[idx])?;
},
GetGlobal(idx) => {
let idx = *idx as usize;
if idx >= globals.len() {
return Err("Reference to a global is out of bounds")
}
stack.push_value(StackValue(globals[idx], false))?;
},
SetGlobal(_) => {
stack.pop_value()?;
},
// These instructions pop the address and pushes the result
I32Load(_, _) |
I32Load8S(_, _) |
I32Load8U(_, _) |
I32Load16S(_, _) |
I32Load16U(_, _) => {
if let Some(sv) = stack.pop_value()? {
stack.push_value(StackValue(ValueType::I32, sv.1))?;
}
},
I64Load(_, _) |
I64Load8S(_, _) |
I64Load8U(_, _) |
I64Load16S(_, _) |
I64Load16U(_, _) |
I64Load32S(_, _) |
I64Load32U(_, _) => {
if let Some(sv) = stack.pop_value()? {
stack.push_value(StackValue(ValueType::I64, sv.1))?;
}
},
F32Load(_, _) => {
if let Some(sv) = stack.pop_value()? {
stack.push_value(StackValue(ValueType::F32, sv.1))?;
}
},
F64Load(_, _) => {
if let Some(sv) = stack.pop_value()? {
stack.push_value(StackValue(ValueType::F64, sv.1))?;
}
},
I32Store(_, _) |
I64Store(_, _) |
F32Store(_, _) |
F64Store(_, _) |
I32Store8(_, _) |
I32Store16(_, _) |
I64Store8(_, _) |
I64Store16(_, _) |
I64Store32(_, _) => {
// These instructions pop the address and the value.
stack.pop_value()?;
stack.pop_value()?;
},
CurrentMemory(_) => {
// Pushes current memory size
stack.push_value(StackValue(ValueType::I32, false))?;
},
GrowMemory(_) => {
// Grow memory takes the value of pages to grow and pushes
stack.pop_value()?;
stack.push_value(StackValue(ValueType::I32, false))?;
},
I32Const(_) => {
stack.push_value(StackValue(ValueType::I32, true))?;
},
I64Const(_) => {
stack.push_value(StackValue(ValueType::I64, true))?;
},
F32Const(_) => {
stack.push_value(StackValue(ValueType::F32, true))?;
},
F64Const(_) => {
stack.push_value(StackValue(ValueType::F64, true))?;
},
I32Eqz | I64Eqz => {
// These instructions pop the value and compare it against zero, and pushes
// the result of the comparison.
if let Some(sv) = stack.pop_value()? {
stack.push_value(StackValue(ValueType::I32, sv.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.
let Some(op1) = stack.pop_value()?;
let Some(op2) = stack.pop_value()?;
stack.push_value(StackValue(ValueType::I32, op1.1 && op2.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.
if let Some(sv) = stack.pop_value()? {
stack.push_value(sv)?;
}
},
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.
let Some(op1) = stack.pop_value()?;
let Some(op2) = stack.pop_value()?;
stack.push_value(StackValue(op1.0, op1.1 && op2.1))?;
},
// Conversion operators take one value and produce one result.
I32WrapI64 | I32TruncSF32 | I32TruncUF32 | I32TruncSF64 | I32TruncUF64 |
I32ReinterpretF32 => {
if let Some(sv) = stack.pop_value()? {
stack.push_value(StackValue(ValueType::I32, sv.1))?;
}
},
I64ExtendSI32 | I64ExtendUI32 | I64TruncSF32 | I64TruncUF32 | I64TruncSF64 |
I64TruncUF64 | I64ReinterpretF64 => {
if let Some(sv) = stack.pop_value()? {
stack.push_value(StackValue(ValueType::I64, sv.1))?;
}
},
F32ConvertSI32 | F32ConvertUI32 | F32ConvertSI64 | F32ConvertUI64 | F32DemoteF64 |
F32ReinterpretI32 => {
if let Some(sv) = stack.pop_value()? {
stack.push_value(StackValue(ValueType::F32, sv.1))?;
}
},
F64ConvertSI32 | F64ConvertUI32 | F64ConvertSI64 | F64ConvertUI64 | F64PromoteF32 |
F64ReinterpretI64 => {
if let Some(sv) = stack.pop_value()? {
stack.push_value(StackValue(ValueType::F64, sv.1))?;
}
},
#[cfg(feature = "sign_ext")]
SignExt(SignExtInstruction::I32Extend8S) |
SignExt(SignExtInstruction::I32Extend16S) |
SignExt(SignExtInstruction::I64Extend8S) |
SignExt(SignExtInstruction::I64Extend16S) |
SignExt(SignExtInstruction::I64Extend32S) =>
if let Some(sv) = stack.pop_value()? {
stack.push_value(sv)?;
},
}
// If current value stack is heavier than maximal weight observed so far,
// save the new weight.
// However, we don't increase maximal value in unreachable code.
if !stack.frame(0)?.is_polymorphic {
let (cur_weight, cur_height) = (stack.weight(), stack.height());
if cur_weight > max_weight {
max_weight = cur_weight;
trace!("Max weight is now {}", max_weight);
}
if cur_height > max_height {
max_height = cur_height;
trace!("Max height is now {}", max_height);
}
}
// Post-execution stage: pop a control frame if block is ended
if *opcode == End {
stack.pop_frame()?;
}
}
trace!("Final max stack height: {} + {}", ACTIVATION_FRAME_HEIGHT, max_height);
trace!(
"Final max stack weight: {} + {} + {}",
ACTIVATION_FRAME_WEIGHT,
max_weight,
param_weight
);
Ok((
ACTIVATION_FRAME_HEIGHT + max_height as u32,
ACTIVATION_FRAME_WEIGHT + max_weight + param_weight,
))
}
#[cfg(test)]
mod tests {
use super::*;
use parity_wasm::elements;
#[cfg(feature = "trace-log")]
use test_log::test;
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 res = compute(0, &module).unwrap();
assert_eq!(res, (ACTIVATION_FRAME_HEIGHT + 3, ACTIVATION_FRAME_WEIGHT + 12));
}
#[test]
fn implicit_and_explicit_return() {
let module = parse_wat(
r#"
(module
(func (result i32)
i64.const 0
return
)
)
"#,
);
let res = compute(0, &module).unwrap();
assert_eq!(res, (ACTIVATION_FRAME_HEIGHT + 1, ACTIVATION_FRAME_WEIGHT + 8));
}
#[test]
fn dont_count_in_unreachable() {
let module = parse_wat(
r#"
(module
(memory 0)
(func (result i32)
unreachable
grow_memory
)
)
"#,
);
let res = compute(0, &module).unwrap();
assert_eq!(res, (ACTIVATION_FRAME_HEIGHT, ACTIVATION_FRAME_WEIGHT));
}
#[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 res = compute(0, &module).unwrap();
assert_eq!(res, (ACTIVATION_FRAME_HEIGHT + 2, ACTIVATION_FRAME_WEIGHT + 8));
}
#[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 res = compute(0, &module).unwrap();
assert_eq!(res, (ACTIVATION_FRAME_HEIGHT + 1, ACTIVATION_FRAME_WEIGHT + 4));
}
#[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 res = compute(0, &module).unwrap();
assert_eq!(res, (ACTIVATION_FRAME_HEIGHT + 1, ACTIVATION_FRAME_WEIGHT + 4));
}
#[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 res = compute(0, &module).unwrap();
assert_eq!(res, (ACTIVATION_FRAME_HEIGHT + 3, ACTIVATION_FRAME_WEIGHT + 12));
}
}
+394
View File
@@ -0,0 +1,394 @@
//! 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},
};
/// 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<u32>,
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> {
self.func_stack_costs.get(func_idx as usize).cloned()
}
/// 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.
pub fn compute_stack_costs(module: &elements::Module) -> Result<Vec<u32>, &'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(0)
} 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<u32, &'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")?;
let code_section =
module.code_section().ok_or("Due to validation code section should exists")?;
let body = &code_section
.bodies()
.get(defined_func_idx as usize)
.ok_or("Function body is out of bounds")?;
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 (max_stack_height, _max_stack_weight) = max_height::compute(defined_func_idx, module)?;
locals_count
.checked_add(max_stack_height)
.ok_or("Overflow in adding locals_count and max_stack_height")
}
/// Stack height is the measurement maximum wasm stack height reached during function execution.
/// Stack weight is weighted value which approximates a real stack size on x64 architecture.
pub fn compute_stack_height_weight(
func_idx: u32,
module: &elements::Module,
) -> Result<(u32, u32), &'static str> {
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);
}
}
+39 -43
View File
@@ -1,12 +1,11 @@
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)?;
@@ -21,48 +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) {
// FIXME: not going to work on windows?
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);
// FIXME: not going to work on windows?
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),
@@ -70,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!();
}
}
}
@@ -84,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")
});
}
@@ -97,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 {
@@ -107,17 +100,20 @@ 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);
+14 -12
View File
@@ -2,28 +2,30 @@
(type (;0;) (func (result i32)))
(type (;1;) (func (param i32)))
(import "env" "gas" (func (;0;) (type 1)))
(func (;1;) (type 0) (result i32)
(func $fibonacci_with_break (;1;) (type 0) (result i32)
(local i32 i32)
i32.const 13
call 0
block ;; label = @1
i32.const 0
set_local 0
local.set 0
i32.const 1
set_local 1
get_local 0
get_local 1
tee_local 0
local.set 1
local.get 0
local.get 1
local.tee 0
i32.add
set_local 1
local.set 1
i32.const 1
br_if 0 (;@1;)
i32.const 5
call 0
get_local 0
get_local 1
tee_local 0
local.get 0
local.get 1
local.tee 0
i32.add
set_local 1
local.set 1
end
get_local 1))
local.get 1
)
)
+13 -10
View File
@@ -2,18 +2,21 @@
(type (;0;) (func (param i32 i32) (result i32)))
(type (;1;) (func (param i32)))
(import "env" "gas" (func (;0;) (type 1)))
(func (;1;) (type 0) (param i32 i32) (result i32)
(func $add_locals (;1;) (type 0) (param $x i32) (param $y i32) (result i32)
(local i32)
i32.const 5
call 0
get_local 0
get_local 1
call 2
set_local 2
get_local 2)
(func (;2;) (type 0) (param i32 i32) (result i32)
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
get_local 0
get_local 1
i32.add))
local.get 0
local.get 1
i32.add
)
)
+5 -3
View File
@@ -9,12 +9,14 @@
if (result i32) ;; label = @1
i32.const 3
call 0
get_local 0
local.get 0
i32.const 1
i32.add
else
i32.const 2
call 0
get_local 0
local.get 0
i32.popcnt
end))
end
)
)
+6 -3
View File
@@ -15,10 +15,13 @@
i32.const 123
drop
end
end)
end
)
(func (;2;) (type 0)
i32.const 1
call 0
block ;; label = @1
end)
(export "simple" (func 1)))
end
)
(export "simple" (func 1))
)
+8 -6
View File
@@ -2,17 +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)
(func $start (;2;) (type 1)
i32.const 4
call 1
i32.const 8
i32.const 4
call 0
unreachable)
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)
)
+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);
}
}