138 Commits

Author SHA1 Message Date
Sergey Pepyakin 87761dad61 Bump pwasm-utils 2019-09-05 22:58:03 +02:00
Sergei Pepyakin a768692bbe Merge pull request #133 from paritytech/bump-parity-wasm
Bump parity-wasm to latest version
2019-09-05 22:50:02 +02:00
Demi M. Obenour ea4cde0e7d Bump parity-wasm to latest version
to allow Substrate to do the same
2019-09-04 18:14:10 -04:00
NikVolf f9d8b722b5 bump to 0.10 2019-08-29 18:57:46 +03:00
Nikolay Volf 39f234e441 Merge pull request #129 from oscoin/fix-pwasm
Preserve "deploy" when optimizing pwasm ctor module
2019-08-29 18:57:00 +03:00
Thomas Scholtes b4f9be733d Preserve "deploy" when optimizing pwasm ctor module
When optimizing the constructor module for a PWasm contract the "deploy" symbol is preserved instead of the "call"
symbol. Before this change `build` would error for PWasm contracts because `pack_instance` would not find the "deploy"
symbol in the optimized contract.

Fixes #128
2019-08-29 16:27:48 +02:00
NikVolf 155c7253c3 bump to 0.9 2019-08-02 15:21:44 +03:00
Nikolay Volf c9cdef4c51 Merge pull request #126 from paritytech/ser-update-pwasm
Update parity-wasm to 0.39
2019-08-02 15:20:32 +03:00
Sergey Pepyakin 2b5026a6c5 Add notice about parity-wasm features 2019-07-30 16:48:58 +02:00
Sergey Pepyakin a774a2cb29 Update parity-wasm to 0.39 2019-07-30 16:44:05 +02:00
Sergei Pepyakin 6fd636a41d Merge pull request #125 from jimpo/gas-fuzzing
Validate the gas metering algorithm using fuzzer.
2019-07-30 13:04:51 +02:00
Jim Posen 5792da28d5 Fix typo
Co-Authored-By: Sergei Pepyakin <s.pepyakin@gmail.com>
2019-07-25 12:37:57 +02:00
Jim Posen f8673d5b87 Fix dev dependency crate imports. 2019-07-25 11:05:25 +02:00
Jim Posen 5180d694ce Validate the gas metering algorithm using fuzzer. 2019-07-19 11:25:54 +02:00
NikVolf a150df8703 bump to 0.8.1 2019-07-17 18:40:00 +03:00
Sergei Pepyakin ae412c45f1 Merge pull request #124 from paritytech/fix-warnings
Fix warnings
2019-07-17 17:22:31 +03:00
Nikolay Volf 6f46ef5211 Update README.md 2019-07-17 17:02:55 +03:00
NikVolf 026b0502bb fix warnings 2019-07-17 17:01:15 +03:00
NikVolf 2c173fee26 bump to 0.8.0 due to change in gas counting 2019-07-17 16:57:28 +03:00
Sergei Pepyakin 0870ce6646 Merge pull request #122 from jimpo/basic-block
Rewrite gas metering algorithm to handle branches
2019-07-17 16:19:52 +03:00
Jim Posen 82bd972333 !fixup lowest_forward_br rename and field docs. 2019-07-12 10:45:01 +02:00
Jim Posen 93abbcfe56 Add gas injection unit test for correct else behavior. 2019-07-12 10:30:11 +02:00
Jim Posen ed7f31ec20 Use macro + WAT to make gas injection unit tests more readable. 2019-07-12 10:15:37 +02:00
Jim Posen b5472bcd8f !fixup Address review comments. 2019-07-11 16:45:46 +02:00
Jim Posen b3f8f62105 Update gas expectations. 2019-07-04 17:57:40 +02:00
Jim Posen 0cf7daa9e5 Update high level inject_gas_counter function documentation. 2019-07-04 17:57:40 +02:00
Jim Posen 24924f59ec Change gas metering injection code to handle branches properly. 2019-07-04 17:57:40 +02:00
Jim Posen de60f491b4 Fix ordering of actual and expected arguments in assert_eq!. 2019-07-04 17:57:40 +02:00
Jim Posen 4c0f42c6fc Perform gas metering injection step in linear time.
Previously the code was quadratic in the worst case as inserting into
the middle of a vector is a linear-time operation.
2019-07-04 17:57:40 +02:00
Jim Posen c3d10a2619 Merge pull request #121 from jimpo/gas-docs
Documentation of gas metering instrumentation process & cleanup.
2019-07-01 17:25:45 +02:00
Jim Posen 863744b1fc Add gas test confirming that br instructions do not end blocks. 2019-07-01 17:02:21 +02:00
Jim Posen 89e13ee901 Cleanup stack height Context.
Removes unnecessary Options and fixes typos.
2019-06-26 12:48:51 +02:00
Jim Posen 929e0ec2c0 Documentation of gas metering instrumentation process. 2019-06-26 12:47:30 +02:00
Nikolay Volf f6a1a6a066 Merge pull request #118 from paritytech/idents
Fix identation
2019-04-10 07:59:26 +03:00
NikVolf 0d40703c6e fix identation 2019-04-09 19:02:07 +03:00
NikVolf 124de6c2db update dependency 2019-04-09 18:57:16 +03:00
NikVolf 5a617c3aae bump cli to 0.7 2019-04-09 18:56:54 +03:00
NikVolf 80ea6ec7ad bump to 0.7.0 2019-04-09 18:56:32 +03:00
Sergei Pepyakin bbcc495ccc Merge pull request #116 from paritytech/small-tests
A couple of small tests.
2019-04-03 14:43:29 +02:00
Sergey Pepyakin 1b7a5d26ea A couple of small tests. 2019-04-03 14:24:01 +02:00
Nikolay Volf b1fbd2921e Merge pull request #115 from holygits/add-clap-version
Fix '-V/--version' output [EOM]
2019-02-27 10:33:52 +08:00
holygits 1e68a862f8 Fix '-V/--version' output 2019-02-26 16:43:33 +13:00
Nikolay Volf 466f5cceba Merge pull request #107 from paritytech/graph
Higher level wasm representation
2019-01-29 17:14:54 +03:00
NikVolf 38e0f254b0 use indoc! 2019-01-27 12:15:38 +03:00
NikVolf 5b2cd9c4c6 add example 2019-01-24 16:21:52 +03:00
NikVolf ad83ad17ee avoid panic when generating format 2019-01-24 16:10:39 +03:00
NikVolf 91036c0aff avoid panics when creating representation 2019-01-24 16:04:00 +03:00
NikVolf 728c935367 alter some tests to show correspondence 2019-01-24 15:36:09 +03:00
NikVolf 33785674dc simplify code 2019-01-24 15:32:14 +03:00
NikVolf 3e635514e4 some reformatting 2019-01-24 15:26:17 +03:00
NikVolf d8428327d5 simpler imports 2019-01-24 15:18:16 +03:00
NikVolf d695703146 more complicated opt and delete tests 2019-01-24 15:07:57 +03:00
NikVolf cda99e70da add much more complicated assertion 2019-01-24 14:42:53 +03:00
NikVolf 0a78a1ab8d complicate test 2019-01-24 14:35:05 +03:00
NikVolf 33c84edd78 Merge remote-tracking branch 'origin/master' into graph
# Conflicts:
#	src/lib.rs
2019-01-24 12:38:35 +03:00
NikVolf 8413e562cd more insert api and graph module logic upon 2019-01-24 12:37:03 +03:00
NikVolf 1bc4973e6e insert api in ref_list 2019-01-24 12:05:20 +03:00
Sergei Pepyakin 8ecbc8ddcc Merge pull request #114 from paritytech/ret
"return_ "-> "ret" in public api
2019-01-24 09:54:46 +01:00
NikVolf 56464c102f return_ -> ret 2019-01-24 11:20:31 +03:00
Nikolay Volf 6046e94b40 Merge pull request #113 from holygits/fix/preserve-optimize
Preserve deploy symbol on optimize for substrate target
2019-01-24 11:15:30 +03:00
holygits ec206fca64 Preserve deploy symbol on optimize for substrate target 2019-01-24 15:46:01 +13:00
Sergei Pepyakin 59384e09d0 Merge pull request #109 from holygits/fix/108
Preserve 'deploy' export for Substrate binaries
2019-01-23 22:21:17 +01:00
holygits 4f81bbc506 Don't pack Substrate ctor module 2019-01-24 10:18:18 +13:00
NikVolf f5890c2c7b more complex test 2019-01-23 15:01:08 +03:00
NikVolf 7504381419 fix linking for elements 2019-01-23 14:44:32 +03:00
NikVolf bb9832dba1 more docs and warnings 2019-01-23 13:57:26 +03:00
NikVolf 62ea903c3a add some docs 2019-01-23 13:47:44 +03:00
holygits c47adc1bd4 Refactor TargetRuntime as enum
Don't rename create symbol for substrate binaries
2019-01-23 17:03:53 +13:00
NikVolf c3833efca7 fix for nightly 2019-01-22 20:39:51 +03:00
NikVolf 4e871c65e2 generate instructions on module generation 2019-01-22 20:37:36 +03:00
NikVolf 48c1c6e72a code mapping 2019-01-22 20:30:50 +03:00
NikVolf d60340762b public api exposure and fix warnings 2019-01-22 18:28:15 +03:00
NikVolf da5b2ca5f6 rest of sections generation 2019-01-22 18:21:30 +03:00
NikVolf c520d334cd ordering and filtering 2019-01-22 17:14:37 +03:00
NikVolf 76b6743c64 generation of more sections 2019-01-22 16:40:28 +03:00
NikVolf d6c6cefcf1 generation - import 2019-01-22 16:11:04 +03:00
NikVolf 86da6439d1 data and elements 2019-01-22 15:15:17 +03:00
NikVolf ed1c7b1b51 better exports 2019-01-22 14:42:57 +03:00
NikVolf cf10b7d5d9 exports and fix for no-std 2019-01-22 14:31:21 +03:00
NikVolf dd9169e30f table and memory 2019-01-22 13:03:11 +03:00
NikVolf be40285a67 func and tests 2019-01-22 12:58:29 +03:00
NikVolf ba45e15567 remove unused 2019-01-22 12:39:09 +03:00
NikVolf 33ff0cbe1d refactor to reflist 2019-01-22 12:19:29 +03:00
NikVolf db4070b96c ref list impl 2019-01-22 12:08:25 +03:00
NikVolf 80d80a37d9 import rewiring 2019-01-21 17:56:30 +03:00
NikVolf 06277915da some graph structure definition 2019-01-21 17:04:31 +03:00
NikVolf 7c7a0713fc bump to 0.6.2 2019-01-15 14:18:45 +03:00
Nikolay Volf 31e3324015 Merge pull request #104 from laizy/master
rewire corresponding indices in name section
2019-01-15 14:17:13 +03:00
laizy dc993bdb1b rewire corresponding indices in name section 2019-01-13 09:53:59 +08:00
Sergey Pepyakin b58e01ec67 Bump version to 0.6.1 2019-01-04 14:38:08 +01:00
Sergey Pepyakin 8db40174ae Add gas tests. 2018-12-24 20:43:23 +01:00
Sergey Pepyakin 471a9b3fcc Account start function in gas func patching. 2018-12-24 19:28:53 +01:00
Sergey Pepyakin 3db0d60e70 Teach stack limiter to handle start fn 2018-12-24 19:20:33 +01:00
NikVolf fe25beca2b bump all to 0.6 2018-09-30 19:02:14 +01:00
Nikolay Volf 24b97b517a Merge pull request #102 from paritytech/ser-introduce-substrate-contracts
Introduce substrate contracts support.
2018-09-30 19:00:44 +01:00
Sergey Pepyakin 836ec93008 Introduce substrate contracts support. 2018-09-30 18:24:36 +01:00
NikVolf 5238b41af2 bump cli to 0.4.1 2018-08-06 16:51:35 +03:00
Nikolay Volf 19ce379f64 Merge pull request #101 from paritytech/fix-output
Fix not saving raw module when no constructor specified
2018-08-06 16:50:57 +03:00
NikVolf a9f5058b4f fix not saving raw module when no constructor specified 2018-08-06 16:09:11 +03:00
NikVolf 4b8b07a0b5 bump versions 2018-08-06 15:43:46 +03:00
Nikolay Volf 67d67f3fba Merge pull request #100 from paritytech/no-constructor
The constructor arg removed from
2018-08-06 15:40:04 +03:00
fro 3a7f8836dd the constructor arg removed from 2018-08-06 15:19:01 +03:00
NikVolf abb5ae6f22 bump cli to 0.3.0 2018-08-03 14:11:29 +03:00
NikVolf 9d0ad5b309 bump to 0.4.0 2018-08-03 14:10:51 +03:00
NikVolf 6c510a7f35 bump to 0.3.2 2018-08-01 17:29:42 +03:00
Alexey e491789127 Move build logic to lib (#97)
* refactored out build to lib

* save_raw returns

* fix indentations and other small fixes

* fix build API

* rename Target to SourceTarget

* fix formatting

* make join runtime_type into runtime_type and runtime_version
2018-08-01 17:26:22 +03:00
Alexey 3e7946ab1c fix call_indirect test (#98)
Merged
2018-08-01 13:48:38 +03:00
Nikolay Volf 41839664bb wasm-check utility (#94)
* wasm-check utility

* more runtime externs

* couple more imports
2018-07-12 18:49:09 +03:00
NikVolf 735110e8d5 bump to 0.3.1 2018-07-09 18:31:58 +03:00
Nikolay Volf 0fe96ee497 Merge pull request #93 from paritytech/fix-nightly
Fix nightly compilation
2018-07-09 18:31:20 +03:00
NikVolf 0837464ec4 change imports 2018-07-09 17:44:04 +03:00
NikVolf 7366384861 bump to lib to 0.3 and cli to 0.2 2018-07-04 12:17:42 +03:00
Sergey Pepyakin db80363d56 Merge pull request #91 from paritytech/bump-version-0.2.2
Bump version up to 0.2.2
2018-07-02 16:39:06 +03:00
Sergey Pepyakin bbb6c6078a Bump version up to 0.2.2. 2018-07-02 15:21:37 +03:00
Nikolay Volf f7e71718a4 Merge pull request #90 from paritytech/parity-wasm-bump
Update parity-wasm dependency to 0.31
2018-06-29 17:23:11 +03:00
Wei Tang af2d61b9f8 Fix tests and cli 2018-06-29 19:06:33 +08:00
Wei Tang d6f82000ee Update parity-wasm dependency to 0.31 2018-06-29 19:01:06 +08:00
Sergey Pepyakin f4b75bd840 Merge pull request #88 from sphinxc0re/patch-1
Fixed typo
2018-06-11 16:55:26 +03:00
Julian Laubstein 187844f79d Fixed typo 2018-06-06 16:27:20 +02:00
NikVolf a4ff19d358 bump to 0.2.1 2018-05-31 15:40:23 +02:00
Nikolay Volf e31f1040e1 Merge pull request #87 from HCastano/use-if-let-statements
Update matches with single arm to be if-let statements
2018-05-31 16:37:47 +03:00
Hernando Castano e6e340fa0a Update matches with single arm to be if-let statements 2018-05-29 22:46:11 -04:00
Nikolay Volf de23bfac0a Merge pull request #84 from paritytech/public-api
Add public api switch to wasm-build
2018-05-21 22:57:27 +03:00
Nikolay Volf 261c823b63 Merge pull request #82 from paritytech/pack-cli
Standalone wasm-pack binary
2018-05-20 22:51:40 +04:00
NikVolf ce865c1e8a add public api switch 2018-05-18 17:51:37 +04:00
Nikolay Volf 2d60c0bb0e Merge pull request #81 from paritytech/small-fixes
Small fixes in comments
2018-05-18 17:10:55 +04:00
NikVolf 5609a08e99 change to expect 2018-05-18 16:47:06 +04:00
NikVolf edabee0649 more fixes to fixes 2018-05-18 16:45:04 +04:00
NikVolf 367514ae07 actual implementation of packer 2018-05-18 16:37:49 +04:00
NikVolf 816f14eac7 also call/deploy update 2018-05-18 16:29:35 +04:00
NikVolf f9540c5423 some fixes 2018-05-18 16:22:41 +04:00
NikVolf 04ac17c3d5 additional binary 2018-05-18 16:21:25 +04:00
NikVolf f7e6631c83 add contribution section 2018-05-16 20:52:54 +04:00
Alexey 20ff66c649 Merge pull request #79 from elopio/patch-1
Fix typo
2018-05-16 19:04:26 +03:00
Leo Arias 5ef171209b Fix typo 2018-05-16 10:01:51 -06:00
Nikolay Volf 947f0b8bbb Update README.md 2018-05-16 17:39:34 +04:00
NikVolf 7f8811cb2c readme for cli 2018-05-15 18:18:49 +04:00
NikVolf 4112b4b961 bump/set versions 2018-05-15 18:14:23 +04:00
43 changed files with 4473 additions and 1577 deletions
+7 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "pwasm-utils"
version = "0.1.5"
version = "0.11.0"
authors = ["Nikolay Volf <nikvolf@gmail.com>", "Sergey Pepyakin <s.pepyakin@gmail.com>"]
license = "MIT/Apache-2.0"
readme = "README.md"
@@ -8,7 +8,9 @@ description = "Collection of command-line utilities and corresponding Rust api f
keywords = ["wasm", "webassembly", "pwasm"]
[dependencies]
parity-wasm = { version = "0.30", default-features = false }
# 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 }
@@ -16,6 +18,9 @@ byteorder = { version = "1", default-features = false }
tempdir = "0.3"
wabt = "0.2"
diff = "0.1.11"
indoc = "0.3"
rand = "0.7"
binaryen = "0.8"
[features]
default = ["std"]
+12 -22
View File
@@ -2,50 +2,34 @@
[![Build Status](https://travis-ci.org/paritytech/wasm-utils.svg?branch=master)](https://travis-ci.org/paritytech/wasm-utils)
Collection of WASM utilities used in Parity and WASM contract devepment
Collection of WASM utilities used in pwasm-ethereum and substrate contract development
## Build tools for cargo
Easiest way to use is to install via `cargo install`:
```
cargo install pwasm-utils --bin wasm-build
cargo install pwasm-utils-cli --bin wasm-build
```
## Symbols pruning (wasm-prune)
```
cargo install pwasm-utils
cargo install pwasm-utils-cli --bin wasm-prune
wasm-prune <input_wasm_binary.wasm> <output_wasm_binary.wasm>
```
This will optimize WASM symbols tree to leave only those elements that are used by contract `_call` function entry.
This will optimize WASM symbols tree to leave only those elements that are used by contract `call` function entry.
## Gas counter (wasm-gas)
For development puposes, raw WASM contract can be injected with gas counters (the same way as it done by Parity runtime when running contracts)
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)
```
cargo install pwasm-utils
cargo install pwasm-utils-cli --bin wasm-gas
wasm-gas <input_wasm_binary.wasm> <output_wasm_binary.wasm>
```
## Externalization (wasm-ext)
Parity WASM runtime provides some library functions that can be commonly found in libc. WASM binary size can be reduced and performance may be improved if these functions are used. This utility scans for invocations of the following functions inside the WASM binary:
- `_malloc`,
- `_free`,
- `_memcpy`,
- `_memset`,
- `_memmove`
And then substitutes them with invocations of the imported ones. Should be run before `wasm-opt` for better results.
```
cargo install pwasm-utils
wasm-ext <input_wasm_binary.wasm> <output_wasm_binary.wasm>
```
## API
All executables use corresponding api methods of the root crate and can be combined in other build tools.
@@ -56,3 +40,9 @@ All executables use corresponding api methods of the root crate and can be combi
license and the Apache License (Version 2.0), at your choice.
See LICENSE-APACHE, and LICENSE-MIT for details.
## Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in `wasm-utils` by you, as defined in the Apache-2.0 license, shall be
dual licensed as above, without any additional terms or conditions.
+11 -3
View File
@@ -1,6 +1,6 @@
[package]
name = "pwasm-utils-cli"
version = "0.1.5"
version = "0.10.0"
authors = ["Nikolay Volf <nikvolf@gmail.com>", "Sergey Pepyakin <s.pepyakin@gmail.com>"]
license = "MIT/Apache-2.0"
readme = "README.md"
@@ -29,9 +29,17 @@ path = "build/main.rs"
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.30"
pwasm-utils = { path = ".." }
parity-wasm = "0.40.1"
pwasm-utils = { path = "..", version = "0.11" }
glob = "0.2"
clap = "2.24"
log = "0.4"
+11
View File
@@ -0,0 +1,11 @@
# 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
```
+61 -85
View File
@@ -1,21 +1,20 @@
//! Experimental build tool for cargo
#[macro_use]
extern crate clap;
extern crate glob;
extern crate pwasm_utils as utils;
extern crate clap;
extern crate parity_wasm;
extern crate pwasm_utils_cli as logger;
mod source;
use std::{fs, io};
use std::io::Write;
use std::path::PathBuf;
use clap::{App, Arg};
use parity_wasm::elements;
use utils::{CREATE_SYMBOL, CALL_SYMBOL, ununderscore_funcs, externalize_mem, shrink_unknown_stack};
use utils::{build, BuildError, SourceTarget, TargetRuntime};
#[derive(Debug)]
pub enum Error {
@@ -23,38 +22,18 @@ pub enum Error {
FailedToCopy(String),
Decoding(elements::Error, String),
Encoding(elements::Error),
Packing(utils::PackingError),
Optimizer,
}
impl From<io::Error> for Error {
fn from(err: io::Error) -> Self {
Error::Io(err)
}
}
impl From<utils::OptimizerError> for Error {
fn from(_err: utils::OptimizerError) -> Self {
Error::Optimizer
}
}
impl From<utils::PackingError> for Error {
fn from(err: utils::PackingError) -> Self {
Error::Packing(err)
}
Build(BuildError),
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
use 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),
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),
Build(ref err) => write!(f, "Build error: {}", err)
}
}
}
@@ -70,8 +49,8 @@ pub fn process_output(input: &source::SourceInput) -> Result<(), Error> {
let wasm_name = input.bin_name().to_string().replace("-", "_");
cargo_path.push(
match input.target() {
source::SourceTarget::Emscripten => source::EMSCRIPTEN_TRIPLET,
source::SourceTarget::Unknown => source::UNKNOWN_TRIPLET,
SourceTarget::Emscripten => source::EMSCRIPTEN_TRIPLET,
SourceTarget::Unknown => source::UNKNOWN_TRIPLET,
}
);
cargo_path.push("release");
@@ -87,18 +66,11 @@ pub fn process_output(input: &source::SourceInput) -> Result<(), Error> {
Ok(())
}
fn has_ctor(module: &elements::Module) -> bool {
if let Some(ref section) = module.export_section() {
section.entries().iter().any(|e| CREATE_SYMBOL == e.field())
} else {
false
}
}
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)
@@ -107,6 +79,12 @@ fn do_main() -> Result<(), Error> {
.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"))
@@ -137,10 +115,16 @@ fn do_main() -> Result<(), Error> {
.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 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);
@@ -161,65 +145,57 @@ fn do_main() -> Result<(), Error> {
let path = wasm_path(&source_input);
let mut module = parity_wasm::deserialize_file(&path)
let module = parity_wasm::deserialize_file(&path)
.map_err(|e| Error::Decoding(e, path.to_string()))?;
if let source::SourceTarget::Emscripten = source_input.target() {
module = ununderscore_funcs(module);
}
if let source::SourceTarget::Unknown = source_input.target() {
// 49152 is 48kb!
if matches.is_present("enforce_stack_adjustment") {
let stack_size: u32 = matches.value_of("shrink_stack").unwrap_or_else(|| "49152").parse().expect("New stack size is not valid u32");
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) = matches.value_of("runtime_type") {
let runtime_type: &[u8] = runtime_type.as_bytes();
if runtime_type.len() != 4 {
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");
}
let runtime_version: u32 = matches.value_of("runtime_version").unwrap_or("1").parse()
ty.copy_from_slice(runtime_bytes);
let version: u32 = runtime_version.parse()
.expect("--runtime-version should be a positive integer");
module = utils::inject_runtime_type(module, &runtime_type, runtime_version);
}
Some((ty, version))
} else {
None
};
let mut ctor_module = module.clone();
let public_api_entries = matches.value_of("public_api")
.map(|val| val.split(",").collect())
.unwrap_or(Vec::new());
if !matches.is_present("skip_optimization") {
utils::optimize(
&mut module,
vec![CALL_SYMBOL]
)?;
}
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)?;
}
let raw_module = parity_wasm::serialize(module).map_err(Error::Encoding)?;
// If module has an exported function with name=CREATE_SYMBOL
// build will pack the module (raw_module) into this funciton and export as CALL_SYMBOL.
// Otherwise it will just save an optimised raw_module
if has_ctor(&ctor_module) {
if !matches.is_present("skip_optimization") {
utils::optimize(&mut ctor_module, vec![CREATE_SYMBOL])?;
}
let ctor_module = utils::pack_instance(raw_module, ctor_module)?;
parity_wasm::serialize_to_file(&path, ctor_module).map_err(Error::Encoding)?;
if let Some(ctor_module) = ctor_module {
parity_wasm::serialize_to_file(
&path,
ctor_module,
).map_err(Error::Encoding)?;
} else {
let mut file = fs::File::create(&path)?;
file.write_all(&raw_module)?;
parity_wasm::serialize_to_file(&path, module).map_err(Error::Encoding)?;
}
Ok(())
+2 -7
View File
@@ -3,12 +3,7 @@
pub const UNKNOWN_TRIPLET: &str = "wasm32-unknown-unknown";
pub const EMSCRIPTEN_TRIPLET: &str = "wasm32-unknown-emscripten";
/// Target configiration of previous build step
#[derive(Debug, Clone, Copy)]
pub enum SourceTarget {
Emscripten,
Unknown,
}
use utils::SourceTarget;
/// Configuration of previous build step (cargo compilation)
#[derive(Debug)]
@@ -59,4 +54,4 @@ impl<'a> SourceInput<'a> {
pub fn target(&self) -> SourceTarget {
self.target
}
}
}
+102
View File
@@ -0,0 +1,102 @@
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; }
}
}
}
+37
View File
@@ -0,0 +1,37 @@
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");
}
+32 -30
View File
@@ -6,40 +6,42 @@ extern crate clap;
use clap::{App, Arg};
fn main() {
logger::init_log();
logger::init_log();
let matches = App::new("wasm-opt")
.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("Comma-separated list of exported functions to keep. Default: _call"))
.get_matches();
let target_runtime = utils::TargetRuntime::pwasm();
let exports = matches
.value_of("exports")
.unwrap_or("_call")
.split(',')
.collect();
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 input = matches.value_of("input").expect("is required; qed");
let output = matches.value_of("output").expect("is required; qed");
let exports = matches
.value_of("exports")
.unwrap_or(target_runtime.symbols().call)
.split(',')
.collect();
let mut module = parity_wasm::deserialize_file(&input).unwrap();
let input = matches.value_of("input").expect("is required; qed");
let output = matches.value_of("output").expect("is required; qed");
// 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 to finish without errors");
let mut module = parity_wasm::deserialize_file(&input).unwrap();
parity_wasm::serialize_to_file(&output, module).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");
}
+28
View File
@@ -0,0 +1,28 @@
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")
}
+123
View File
@@ -0,0 +1,123 @@
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)))
}
+20 -24
View File
@@ -8,29 +8,23 @@ use byteorder::{LittleEndian, ByteOrder};
type Insertion = (usize, u32, u32, String);
pub fn update_call_index(opcodes: &mut elements::Opcodes, original_imports: usize, inserts: &[Insertion]) {
use parity_wasm::elements::Opcode::*;
for opcode in opcodes.elements_mut().iter_mut() {
match opcode {
&mut Call(ref mut call_index) => {
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 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() {
match section {
&mut elements::Section::Memory(ref mut sect) => {
return Some(sect);
},
_ => { }
for section in module.sections_mut() {
if let &mut elements::Section::Memory(ref mut sect) = section {
return Some(sect);
}
}
None
@@ -104,7 +98,12 @@ pub fn shrink_unknown_stack(
match section {
&mut elements::Section::Data(ref mut data_section) => {
for ref mut data_segment in data_section.entries_mut() {
if data_segment.offset().code() == &[elements::Opcode::I32Const(4), elements::Opcode::End] {
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;
@@ -182,11 +181,8 @@ pub fn externalize(
},
&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) => {
if *func_index >= import_funcs_total as u32 { *func_index += replaces.len() as u32; }
},
_ => {}
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; }
}
}
},
-614
View File
@@ -1,614 +0,0 @@
use std::vec::Vec;
use parity_wasm::{elements, builder};
use rules;
pub fn update_call_index(opcodes: &mut elements::Opcodes, inserted_index: u32) {
use parity_wasm::elements::Opcode::*;
for opcode in opcodes.elements_mut().iter_mut() {
match opcode {
&mut Call(ref mut call_index) => {
if *call_index >= inserted_index { *call_index += 1}
},
_ => { },
}
}
}
/// A block of code represented by it's start position and cost.
///
/// The block typically starts with instructions such as `loop`, `block`, `if`, etc.
///
/// An example of block:
///
/// ```ignore
/// loop
/// i32.const 1
/// get_local 0
/// i32.sub
/// tee_local 0
/// br_if 0
/// end
/// ```
///
/// The start of the block is `i32.const 1`.
///
#[derive(Debug)]
struct BlockEntry {
/// Index of the first instruction (aka `Opcode`) in the block.
start_pos: usize,
/// Sum of costs of all instructions until end of the block.
cost: u32,
}
struct Counter {
/// All blocks in the order of theirs start position.
blocks: Vec<BlockEntry>,
// Stack of blocks. Each element is an index to a `self.blocks` vector.
stack: Vec<usize>,
}
impl Counter {
fn new() -> Counter {
Counter {
stack: Vec::new(),
blocks: Vec::new(),
}
}
/// Begin a new block.
fn begin(&mut self, cursor: usize) {
let block_idx = self.blocks.len();
self.blocks.push(BlockEntry {
start_pos: cursor,
cost: 1,
});
self.stack.push(block_idx);
}
/// Finalize the current block.
///
/// Finalized blocks have final cost which will not change later.
fn finalize(&mut self) -> Result<(), ()> {
self.stack.pop().ok_or_else(|| ())?;
Ok(())
}
/// Increment the cost of the current block by the specified value.
fn increment(&mut self, val: u32) -> Result<(), ()> {
let stack_top = self.stack.last_mut().ok_or_else(|| ())?;
let top_block = self.blocks.get_mut(*stack_top).ok_or_else(|| ())?;
top_block.cost = top_block.cost.checked_add(val).ok_or_else(|| ())?;
Ok(())
}
}
fn inject_grow_counter(opcodes: &mut elements::Opcodes, grow_counter_func: u32) -> usize {
use parity_wasm::elements::Opcode::*;
let mut counter = 0;
for opcode in opcodes.elements_mut() {
match *opcode {
GrowMemory(_) => {
*opcode = Call(grow_counter_func);
counter += 1;
},
_ => {}
}
}
counter
}
fn add_grow_counter(module: elements::Module, rules: &rules::Set, gas_func: u32) -> elements::Module {
use parity_wasm::elements::Opcode::*;
let mut b = builder::from_module(module);
b.push_function(
builder::function()
.signature().params().i32().build().with_return_type(Some(elements::ValueType::I32)).build()
.body()
.with_opcodes(elements::Opcodes::new(vec![
GetLocal(0),
GetLocal(0),
I32Const(rules.grow_cost() as i32),
I32Mul,
// todo: there should be strong guarantee that it does not return anything on stack?
Call(gas_func),
GrowMemory(0),
End,
]))
.build()
.build()
);
b.build()
}
pub fn inject_counter(
opcodes: &mut elements::Opcodes,
rules: &rules::Set,
gas_func: u32,
) -> Result<(), ()> {
use parity_wasm::elements::Opcode::*;
let mut counter = Counter::new();
// Begin an implicit function (i.e. `func...end`) block.
counter.begin(0);
for cursor in 0..opcodes.elements().len() {
let opcode = &opcodes.elements()[cursor];
match *opcode {
Block(_) | If(_) | Loop(_) => {
// Increment previous block with the cost of the current opcode.
let opcode_cost = rules.process(opcode)?;
counter.increment(opcode_cost)?;
// Begin new block. The cost of the following opcodes until `End` or `Else` will
// be included into this block.
counter.begin(cursor + 1);
}
End => {
// Just finalize current block.
counter.finalize()?;
},
Else => {
// `Else` opcode is being encountered. So the case we are looking at:
//
// if
// ...
// else <-- cursor
// ...
// end
//
// Finalize the current block ('then' part of the if statement),
// and begin another one for the 'else' part.
counter.finalize()?;
counter.begin(cursor + 1);
}
_ => {
// An ordinal non control flow instruction. Just increment the cost of the current block.
let opcode_cost = rules.process(opcode)?;
counter.increment(opcode_cost)?;
}
}
}
// Then insert metering calls.
let mut cumulative_offset = 0;
for block in counter.blocks {
let effective_pos = block.start_pos + cumulative_offset;
opcodes.elements_mut().insert(effective_pos, I32Const(block.cost as i32));
opcodes.elements_mut().insert(effective_pos+1, Call(gas_func));
// Take into account these two inserted instructions.
cumulative_offset += 2;
}
Ok(())
}
/// Injects gas counter.
///
/// Can only fail if encounters operation forbidden by gas rules,
/// in this case it returns error with the original module.
pub fn inject_gas_counter(module: elements::Module, rules: &rules::Set)
-> Result<elements::Module, elements::Module>
{
// Injecting gas counting external
let mut mbuilder = builder::from_module(module);
let import_sig = mbuilder.push_signature(
builder::signature()
.param().i32()
.build_sig()
);
mbuilder.push_import(
builder::import()
.module("env")
.field("gas")
.external().func(import_sig)
.build()
);
// back to plain module
let mut module = mbuilder.build();
// calculate actual function index of the imported definition
// (substract all imports that are NOT functions)
let gas_func = module.import_count(elements::ImportCountType::Function) as u32 - 1;
let total_func = module.functions_space() as u32;
let mut need_grow_counter = false;
let mut error = false;
// Updating calling addresses (all calls to function index >= `gas_func` should be incremented)
for section in module.sections_mut() {
match section {
&mut elements::Section::Code(ref mut code_section) => {
for ref mut func_body in code_section.bodies_mut() {
update_call_index(func_body.code_mut(), gas_func);
if let Err(_) = inject_counter(func_body.code_mut(), rules, gas_func) {
error = true;
break;
}
if rules.grow_cost() > 0 {
if inject_grow_counter(func_body.code_mut(), total_func) > 0 {
need_grow_counter = true;
}
}
}
},
&mut elements::Section::Export(ref mut export_section) => {
for ref mut export in export_section.entries_mut() {
match export.internal_mut() {
&mut elements::Internal::Function(ref mut func_index) => {
if *func_index >= gas_func { *func_index += 1}
},
_ => {}
}
}
},
&mut elements::Section::Element(ref mut elements_section) => {
for ref mut segment in elements_section.entries_mut() {
// update all indirect call addresses initial values
for func_index in segment.members_mut() {
if *func_index >= gas_func { *func_index += 1}
}
}
},
_ => { }
}
}
if error { return Err(module); }
if need_grow_counter { Ok(add_grow_counter(module, rules, gas_func)) } else { Ok(module) }
}
#[cfg(test)]
mod tests {
extern crate wabt;
use parity_wasm::{serialize, builder, elements};
use super::*;
use rules;
#[test]
fn simple_grow() {
use parity_wasm::elements::Opcode::*;
let module = builder::module()
.global()
.value_type().i32()
.build()
.function()
.signature().param().i32().build()
.body()
.with_opcodes(elements::Opcodes::new(
vec![
GetGlobal(0),
GrowMemory(0),
End
]
))
.build()
.build()
.build();
let injected_module = inject_gas_counter(module, &rules::Set::default().with_grow_cost(10000)).unwrap();
assert_eq!(
&vec![
I32Const(3),
Call(0),
GetGlobal(0),
Call(2),
End
][..],
injected_module
.code_section().expect("function section should exist").bodies()[0]
.code().elements()
);
assert_eq!(
&vec![
GetLocal(0),
GetLocal(0),
I32Const(10000),
I32Mul,
Call(0),
GrowMemory(0),
End,
][..],
injected_module
.code_section().expect("function section should exist").bodies()[1]
.code().elements()
);
let binary = serialize(injected_module).expect("serialization failed");
self::wabt::wasm2wat(&binary).unwrap();
}
#[test]
fn grow_no_gas_no_track() {
use parity_wasm::elements::Opcode::*;
let module = builder::module()
.global()
.value_type().i32()
.build()
.function()
.signature().param().i32().build()
.body()
.with_opcodes(elements::Opcodes::new(
vec![
GetGlobal(0),
GrowMemory(0),
End
]
))
.build()
.build()
.build();
let injected_module = inject_gas_counter(module, &rules::Set::default()).unwrap();
assert_eq!(
&vec![
I32Const(3),
Call(0),
GetGlobal(0),
GrowMemory(0),
End
][..],
injected_module
.code_section().expect("function section should exist").bodies()[0]
.code().elements()
);
assert_eq!(injected_module.functions_space(), 2);
let binary = serialize(injected_module).expect("serialization failed");
self::wabt::wasm2wat(&binary).unwrap();
}
#[test]
fn simple() {
use parity_wasm::elements::Opcode::*;
let module = builder::module()
.global()
.value_type().i32()
.build()
.function()
.signature().param().i32().build()
.body()
.with_opcodes(elements::Opcodes::new(
vec![
GetGlobal(0),
End
]
))
.build()
.build()
.build();
let injected_module = inject_gas_counter(module, &Default::default()).unwrap();
assert_eq!(
&vec![
I32Const(2),
Call(0),
GetGlobal(0),
End
][..],
injected_module
.code_section().expect("function section should exist").bodies()[0]
.code().elements()
);
}
#[test]
fn nested() {
use parity_wasm::elements::Opcode::*;
let module = builder::module()
.global()
.value_type().i32()
.build()
.function()
.signature().param().i32().build()
.body()
.with_opcodes(elements::Opcodes::new(
vec![
GetGlobal(0),
Block(elements::BlockType::NoResult),
GetGlobal(0),
GetGlobal(0),
GetGlobal(0),
End,
GetGlobal(0),
End
]
))
.build()
.build()
.build();
let injected_module = inject_gas_counter(module, &Default::default()).unwrap();
assert_eq!(
&vec![
I32Const(4),
Call(0),
GetGlobal(0),
Block(elements::BlockType::NoResult),
I32Const(4),
Call(0),
GetGlobal(0),
GetGlobal(0),
GetGlobal(0),
End,
GetGlobal(0),
End
][..],
injected_module
.code_section().expect("function section should exist").bodies()[0]
.code().elements()
);
}
#[test]
fn ifelse() {
use parity_wasm::elements::Opcode::*;
let module = builder::module()
.global()
.value_type().i32()
.build()
.function()
.signature().param().i32().build()
.body()
.with_opcodes(elements::Opcodes::new(
vec![
GetGlobal(0),
If(elements::BlockType::NoResult),
GetGlobal(0),
GetGlobal(0),
GetGlobal(0),
Else,
GetGlobal(0),
GetGlobal(0),
End,
GetGlobal(0),
End
]
))
.build()
.build()
.build();
let injected_module = inject_gas_counter(module, &Default::default()).unwrap();
assert_eq!(
&vec![
I32Const(4),
Call(0),
GetGlobal(0),
If(elements::BlockType::NoResult),
I32Const(4),
Call(0),
GetGlobal(0),
GetGlobal(0),
GetGlobal(0),
Else,
I32Const(3),
Call(0),
GetGlobal(0),
GetGlobal(0),
End,
GetGlobal(0),
End
][..],
injected_module
.code_section().expect("function section should exist").bodies()[0]
.code().elements()
);
}
#[test]
fn call_index() {
use parity_wasm::elements::Opcode::*;
let module = builder::module()
.global()
.value_type().i32()
.build()
.function()
.signature().param().i32().build()
.body().build()
.build()
.function()
.signature().param().i32().build()
.body()
.with_opcodes(elements::Opcodes::new(
vec![
Call(0),
If(elements::BlockType::NoResult),
Call(0),
Call(0),
Call(0),
Else,
Call(0),
Call(0),
End,
Call(0),
End
]
))
.build()
.build()
.build();
let injected_module = inject_gas_counter(module, &Default::default()).unwrap();
assert_eq!(
&vec![
I32Const(4),
Call(0),
Call(1),
If(elements::BlockType::NoResult),
I32Const(4),
Call(0),
Call(1),
Call(1),
Call(1),
Else,
I32Const(3),
Call(0),
Call(1),
Call(1),
End,
Call(1),
End
][..],
injected_module
.code_section().expect("function section should exist").bodies()[1]
.code().elements()
);
}
#[test]
fn forbidden() {
use parity_wasm::elements::Opcode::*;
let module = builder::module()
.global()
.value_type().i32()
.build()
.function()
.signature().param().i32().build()
.body()
.with_opcodes(elements::Opcodes::new(
vec![
F32Const(555555),
End
]
))
.build()
.build()
.build();
let rules = rules::Set::default().with_forbidden_floats();
if let Err(_) = inject_gas_counter(module, &rules) { }
else { panic!("Should be error because of the forbidden operation")}
}
}
+968
View File
@@ -0,0 +1,968 @@
//! This module is used to instrument a Wasm module with gas metering code.
//!
//! The primary public interface is the `inject_gas_counter` function which transforms a given
//! module into one that charges gas for code to be executed. See function documentation for usage
//! and details.
#[cfg(test)]
mod validation;
use std::cmp::min;
use std::mem;
use std::vec::Vec;
use parity_wasm::{elements, builder};
use rules;
pub fn update_call_index(instructions: &mut elements::Instructions, inserted_index: u32) {
use parity_wasm::elements::Instruction::*;
for instruction in instructions.elements_mut().iter_mut() {
if let &mut Call(ref mut call_index) = instruction {
if *call_index >= inserted_index { *call_index += 1}
}
}
}
/// A control flow block is opened with the `block`, `loop`, and `if` instructions and is closed
/// with `end`. Each block implicitly defines a new label. The control blocks form a stack during
/// program execution.
///
/// An example of block:
///
/// ```ignore
/// loop
/// i32.const 1
/// get_local 0
/// i32.sub
/// tee_local 0
/// br_if 0
/// end
/// ```
///
/// The start of the block is `i32.const 1`.
///
#[derive(Debug)]
struct ControlBlock {
/// The lowest control stack index corresponding to a forward jump targeted by a br, br_if, or
/// br_table instruction within this control block. The index must refer to a control block
/// that is not a loop, meaning it is a forward jump. Given the way Wasm control flow is
/// structured, the lowest index on the stack represents the furthest forward branch target.
///
/// This value will always be at most the index of the block itself, even if there is no
/// explicit br instruction targeting this control block. This does not affect how the value is
/// used in the metering algorithm.
lowest_forward_br_target: usize,
/// The active metering block that new instructions contribute a gas cost towards.
active_metered_block: MeteredBlock,
/// Whether the control block is a loop. Loops have the distinguishing feature that branches to
/// them jump to the beginning of the block, not the end as with the other control blocks.
is_loop: bool,
}
/// A block of code that metering instructions will be inserted at the beginning of. Metered blocks
/// are constructed with the property that, in the absence of any traps, either all instructions in
/// the block are executed or none are.
#[derive(Debug)]
pub(crate) struct MeteredBlock {
/// Index of the first instruction (aka `Opcode`) in the block.
start_pos: usize,
/// Sum of costs of all instructions until end of the block.
cost: u32,
}
/// Counter is used to manage state during the gas metering algorithm implemented by
/// `inject_counter`.
struct Counter {
/// A stack of control blocks. This stack grows when new control blocks are opened with
/// `block`, `loop`, and `if` and shrinks when control blocks are closed with `end`. The first
/// block on the stack corresponds to the function body, not to any labelled block. Therefore
/// the actual Wasm label index associated with each control block is 1 less than its position
/// in this stack.
stack: Vec<ControlBlock>,
/// A list of metered blocks that have been finalized, meaning they will no longer change.
finalized_blocks: Vec<MeteredBlock>,
}
impl Counter {
fn new() -> Counter {
Counter {
stack: Vec::new(),
finalized_blocks: Vec::new(),
}
}
/// Open a new control block. The cursor is the position of the first instruction in the block.
fn begin_control_block(&mut self, cursor: usize, is_loop: bool) {
let index = self.stack.len();
self.stack.push(ControlBlock {
lowest_forward_br_target: index,
active_metered_block: MeteredBlock {
start_pos: cursor,
cost: 0,
},
is_loop,
})
}
/// Close the last control block. The cursor is the position of the final (pseudo-)instruction
/// in the block.
fn finalize_control_block(&mut self, cursor: usize) -> Result<(), ()> {
// This either finalizes the active metered block or merges its cost into the active
// metered block in the previous control block on the stack.
self.finalize_metered_block(cursor)?;
// Pop the control block stack.
let closing_control_block = self.stack.pop().ok_or_else(|| ())?;
let closing_control_index = self.stack.len();
if self.stack.is_empty() {
return Ok(())
}
// Update the lowest_forward_br_target for the control block now on top of the stack.
{
let control_block = self.stack.last_mut().ok_or_else(|| ())?;
control_block.lowest_forward_br_target = min(
control_block.lowest_forward_br_target,
closing_control_block.lowest_forward_br_target
);
}
// If there may have been a branch to a lower index, then also finalize the active metered
// block for the previous control block. Otherwise, finalize it and begin a new one.
let may_br_out = closing_control_block.lowest_forward_br_target < closing_control_index;
if may_br_out {
self.finalize_metered_block(cursor)?;
}
Ok(())
}
/// Finalize the current active metered block.
///
/// Finalized blocks have final cost which will not change later.
fn finalize_metered_block(&mut self, cursor: usize) -> Result<(), ()> {
let closing_metered_block = {
let control_block = self.stack.last_mut().ok_or_else(|| ())?;
mem::replace(
&mut control_block.active_metered_block,
MeteredBlock {
start_pos: cursor + 1,
cost: 0,
}
)
};
// If the block was opened with a `block`, then its start position will be set to that of
// the active metered block in the control block one higher on the stack. This is because
// any instructions between a `block` and the first branch are part of the same basic block
// as the preceding instruction. In this case, instead of finalizing the block, merge its
// cost into the other active metered block to avoid injecting unnecessary instructions.
let last_index = self.stack.len() - 1;
if last_index > 0 {
let prev_control_block = self.stack.get_mut(last_index - 1)
.expect("last_index is greater than 0; last_index is stack size - 1; qed");
let prev_metered_block = &mut prev_control_block.active_metered_block;
if closing_metered_block.start_pos == prev_metered_block.start_pos {
prev_metered_block.cost += closing_metered_block.cost;
return Ok(())
}
}
if closing_metered_block.cost > 0 {
self.finalized_blocks.push(closing_metered_block);
}
Ok(())
}
/// Handle a branch instruction in the program. The cursor is the index of the branch
/// instruction in the program. The indices are the stack positions of the target control
/// blocks. Recall that the index is 0 for a `return` and relatively indexed from the top of
/// the stack by the label of `br`, `br_if`, and `br_table` instructions.
fn branch(&mut self, cursor: usize, indices: &[usize]) -> Result<(), ()> {
self.finalize_metered_block(cursor)?;
// Update the lowest_forward_br_target of the current control block.
for &index in indices {
let target_is_loop = {
let target_block = self.stack.get(index).ok_or_else(|| ())?;
target_block.is_loop
};
if target_is_loop {
continue;
}
let control_block = self.stack.last_mut().ok_or_else(|| ())?;
control_block.lowest_forward_br_target =
min(control_block.lowest_forward_br_target, index);
}
Ok(())
}
/// Returns the stack index of the active control block. Returns None if stack is empty.
fn active_control_block_index(&self) -> Option<usize> {
self.stack.len().checked_sub(1)
}
/// Get a reference to the currently active metered block.
fn active_metered_block(&mut self) -> Result<&mut MeteredBlock, ()> {
let top_block = self.stack.last_mut().ok_or_else(|| ())?;
Ok(&mut top_block.active_metered_block)
}
/// Increment the cost of the current block by the specified value.
fn increment(&mut self, val: u32) -> Result<(), ()> {
let top_block = self.active_metered_block()?;
top_block.cost = top_block.cost.checked_add(val).ok_or_else(|| ())?;
Ok(())
}
}
fn inject_grow_counter(instructions: &mut elements::Instructions, grow_counter_func: u32) -> usize {
use parity_wasm::elements::Instruction::*;
let mut counter = 0;
for instruction in instructions.elements_mut() {
if let GrowMemory(_) = *instruction {
*instruction = Call(grow_counter_func);
counter += 1;
}
}
counter
}
fn add_grow_counter(module: elements::Module, rules: &rules::Set, gas_func: u32) -> elements::Module {
use parity_wasm::elements::Instruction::*;
let mut b = builder::from_module(module);
b.push_function(
builder::function()
.signature().params().i32().build().with_return_type(Some(elements::ValueType::I32)).build()
.body()
.with_instructions(elements::Instructions::new(vec![
GetLocal(0),
GetLocal(0),
I32Const(rules.grow_cost() as i32),
I32Mul,
// todo: there should be strong guarantee that it does not return anything on stack?
Call(gas_func),
GrowMemory(0),
End,
]))
.build()
.build()
);
b.build()
}
pub(crate) fn determine_metered_blocks(
instructions: &elements::Instructions,
rules: &rules::Set,
) -> Result<Vec<MeteredBlock>, ()> {
use parity_wasm::elements::Instruction::*;
let mut counter = Counter::new();
// Begin an implicit function (i.e. `func...end`) block.
counter.begin_control_block(0, false);
for cursor in 0..instructions.elements().len() {
let instruction = &instructions.elements()[cursor];
let instruction_cost = rules.process(instruction)?;
match *instruction {
Block(_) => {
counter.increment(instruction_cost)?;
// Begin new block. The cost of the following opcodes until `end` or `else` will
// be included into this block. The start position is set to that of the previous
// active metered block to signal that they should be merged in order to reduce
// unnecessary metering instructions.
let top_block_start_pos = counter.active_metered_block()?.start_pos;
counter.begin_control_block(top_block_start_pos, false);
}
If(_) => {
counter.increment(instruction_cost)?;
counter.begin_control_block(cursor + 1, false);
}
Loop(_) => {
counter.increment(instruction_cost)?;
counter.begin_control_block(cursor + 1, true);
}
End => {
counter.finalize_control_block(cursor)?;
},
Else => {
counter.finalize_metered_block(cursor)?;
}
Br(label) | BrIf(label) => {
counter.increment(instruction_cost)?;
// Label is a relative index into the control stack.
let active_index = counter.active_control_block_index().ok_or_else(|| ())?;
let target_index = active_index.checked_sub(label as usize).ok_or_else(|| ())?;
counter.branch(cursor, &[target_index])?;
}
BrTable(ref br_table_data) => {
counter.increment(instruction_cost)?;
let active_index = counter.active_control_block_index().ok_or_else(|| ())?;
let target_indices = [br_table_data.default]
.iter()
.chain(br_table_data.table.iter())
.map(|label| active_index.checked_sub(*label as usize))
.collect::<Option<Vec<_>>>()
.ok_or_else(|| ())?;
counter.branch(cursor, &target_indices)?;
}
Return => {
counter.increment(instruction_cost)?;
counter.branch(cursor, &[0])?;
}
_ => {
// An ordinal non control flow instruction increments the cost of the current block.
counter.increment(instruction_cost)?;
}
}
}
counter.finalized_blocks.sort_unstable_by_key(|block| block.start_pos);
Ok(counter.finalized_blocks)
}
pub fn inject_counter(
instructions: &mut elements::Instructions,
rules: &rules::Set,
gas_func: u32,
) -> Result<(), ()> {
let blocks = determine_metered_blocks(instructions, rules)?;
insert_metering_calls(instructions, blocks, gas_func)
}
// Then insert metering calls into a sequence of instructions given the block locations and costs.
fn insert_metering_calls(
instructions: &mut elements::Instructions,
blocks: Vec<MeteredBlock>,
gas_func: u32,
)
-> Result<(), ()>
{
use parity_wasm::elements::Instruction::*;
// To do this in linear time, construct a new vector of instructions, copying over old
// instructions one by one and injecting new ones as required.
let new_instrs_len = instructions.elements().len() + 2 * blocks.len();
let original_instrs = mem::replace(
instructions.elements_mut(), Vec::with_capacity(new_instrs_len)
);
let new_instrs = instructions.elements_mut();
let mut block_iter = blocks.into_iter().peekable();
for (original_pos, instr) in original_instrs.into_iter().enumerate() {
// If there the next block starts at this position, inject metering instructions.
let used_block = if let Some(ref block) = block_iter.peek() {
if block.start_pos == original_pos {
new_instrs.push(I32Const(block.cost as i32));
new_instrs.push(Call(gas_func));
true
} else { false }
} else { false };
if used_block {
block_iter.next();
}
// Copy over the original instruction.
new_instrs.push(instr);
}
if block_iter.next().is_some() {
return Err(());
}
Ok(())
}
/// Transforms a given module into one that charges gas for code to be executed by proxy of an
/// imported gas metering function.
///
/// The output module imports a function "gas" from the module "env" with type signature
/// [i32] -> []. The argument is the amount of gas required to continue execution. The external
/// function is meant to keep track of the total amount of gas used and trap or otherwise halt
/// execution of the runtime if the gas usage exceeds some allowed limit.
///
/// The body of each function is divided into metered blocks, and the calls to charge gas are
/// inserted at the beginning of every such block of code. A metered block is defined so that,
/// unless there is a trap, either all of the instructions are executed or none are. These are
/// similar to basic blocks in a control flow graph, except that in some cases multiple basic
/// blocks can be merged into a single metered block. This is the case if any path through the
/// control flow graph containing one basic block also contains another.
///
/// Charging gas is at the beginning of each metered block ensures that 1) all instructions
/// executed are already paid for, 2) instructions that will not be executed are not charged for
/// unless execution traps, and 3) the number of calls to "gas" is minimized. The corollary is that
/// modules instrumented with this metering code may charge gas for instructions not executed in
/// the event of a trap.
///
/// Additionally, each `memory.grow` instruction found in the module is instrumented to first make
/// a call to charge gas for the additional pages requested. This cannot be done as part of the
/// block level gas charges as the gas cost is not static and depends on the stack argument to
/// `memory.grow`.
///
/// The above transformations are performed for every function body defined in the module. This
/// function also rewrites all function indices references by code, table elements, etc., since
/// the addition of an imported functions changes the indices of module-defined functions.
///
/// This routine runs in time linear in the size of the input module.
///
/// The function fails if the module contains any operation forbidden by gas rule set, returning
/// the original module as an Err.
pub fn inject_gas_counter(module: elements::Module, rules: &rules::Set)
-> Result<elements::Module, elements::Module>
{
// Injecting gas counting external
let mut mbuilder = builder::from_module(module);
let import_sig = mbuilder.push_signature(
builder::signature()
.param().i32()
.build_sig()
);
mbuilder.push_import(
builder::import()
.module("env")
.field("gas")
.external().func(import_sig)
.build()
);
// back to plain module
let mut module = mbuilder.build();
// calculate actual function index of the imported definition
// (subtract all imports that are NOT functions)
let gas_func = module.import_count(elements::ImportCountType::Function) as u32 - 1;
let total_func = module.functions_space() as u32;
let mut need_grow_counter = false;
let mut error = false;
// Updating calling addresses (all calls to function index >= `gas_func` should be incremented)
for section in module.sections_mut() {
match section {
&mut elements::Section::Code(ref mut code_section) => {
for ref mut func_body in code_section.bodies_mut() {
update_call_index(func_body.code_mut(), gas_func);
if let Err(_) = inject_counter(func_body.code_mut(), rules, gas_func) {
error = true;
break;
}
if rules.grow_cost() > 0 {
if inject_grow_counter(func_body.code_mut(), total_func) > 0 {
need_grow_counter = true;
}
}
}
},
&mut elements::Section::Export(ref mut export_section) => {
for ref mut export in export_section.entries_mut() {
if let &mut elements::Internal::Function(ref mut func_index) = export.internal_mut() {
if *func_index >= gas_func { *func_index += 1}
}
}
},
&mut elements::Section::Element(ref mut elements_section) => {
// Note that we do not need to check the element type referenced because in the
// WebAssembly 1.0 spec, the only allowed element type is funcref.
for ref mut segment in elements_section.entries_mut() {
// update all indirect call addresses initial values
for func_index in segment.members_mut() {
if *func_index >= gas_func { *func_index += 1}
}
}
},
&mut elements::Section::Start(ref mut start_idx) => {
if *start_idx >= gas_func { *start_idx += 1}
},
_ => { }
}
}
if error { return Err(module); }
if need_grow_counter { Ok(add_grow_counter(module, rules, gas_func)) } else { Ok(module) }
}
#[cfg(test)]
mod tests {
extern crate wabt;
use parity_wasm::{serialize, builder, elements};
use parity_wasm::elements::Instruction::*;
use super::*;
use rules;
pub fn get_function_body(module: &elements::Module, index: usize)
-> Option<&[elements::Instruction]>
{
module.code_section()
.and_then(|code_section| code_section.bodies().get(index))
.map(|func_body| func_body.code().elements())
}
#[test]
fn simple_grow() {
let module = builder::module()
.global()
.value_type().i32()
.build()
.function()
.signature().param().i32().build()
.body()
.with_instructions(elements::Instructions::new(
vec![
GetGlobal(0),
GrowMemory(0),
End
]
))
.build()
.build()
.build();
let injected_module = inject_gas_counter(module, &rules::Set::default().with_grow_cost(10000)).unwrap();
assert_eq!(
get_function_body(&injected_module, 0).unwrap(),
&vec![
I32Const(2),
Call(0),
GetGlobal(0),
Call(2),
End
][..]
);
assert_eq!(
get_function_body(&injected_module, 1).unwrap(),
&vec![
GetLocal(0),
GetLocal(0),
I32Const(10000),
I32Mul,
Call(0),
GrowMemory(0),
End,
][..]
);
let binary = serialize(injected_module).expect("serialization failed");
self::wabt::wasm2wat(&binary).unwrap();
}
#[test]
fn grow_no_gas_no_track() {
let module = builder::module()
.global()
.value_type().i32()
.build()
.function()
.signature().param().i32().build()
.body()
.with_instructions(elements::Instructions::new(
vec![
GetGlobal(0),
GrowMemory(0),
End
]
))
.build()
.build()
.build();
let injected_module = inject_gas_counter(module, &rules::Set::default()).unwrap();
assert_eq!(
get_function_body(&injected_module, 0).unwrap(),
&vec![
I32Const(2),
Call(0),
GetGlobal(0),
GrowMemory(0),
End
][..]
);
assert_eq!(injected_module.functions_space(), 2);
let binary = serialize(injected_module).expect("serialization failed");
self::wabt::wasm2wat(&binary).unwrap();
}
#[test]
fn call_index() {
let module = builder::module()
.global()
.value_type().i32()
.build()
.function()
.signature().param().i32().build()
.body().build()
.build()
.function()
.signature().param().i32().build()
.body()
.with_instructions(elements::Instructions::new(
vec![
Call(0),
If(elements::BlockType::NoResult),
Call(0),
Call(0),
Call(0),
Else,
Call(0),
Call(0),
End,
Call(0),
End
]
))
.build()
.build()
.build();
let injected_module = inject_gas_counter(module, &Default::default()).unwrap();
assert_eq!(
get_function_body(&injected_module, 1).unwrap(),
&vec![
I32Const(3),
Call(0),
Call(1),
If(elements::BlockType::NoResult),
I32Const(3),
Call(0),
Call(1),
Call(1),
Call(1),
Else,
I32Const(2),
Call(0),
Call(1),
Call(1),
End,
Call(1),
End
][..]
);
}
#[test]
fn forbidden() {
let module = builder::module()
.global()
.value_type().i32()
.build()
.function()
.signature().param().i32().build()
.body()
.with_instructions(elements::Instructions::new(
vec![
F32Const(555555),
End
]
))
.build()
.build()
.build();
let rules = rules::Set::default().with_forbidden_floats();
if let Err(_) = inject_gas_counter(module, &rules) { }
else { panic!("Should be error because of the forbidden operation")}
}
fn parse_wat(source: &str) -> elements::Module {
let module_bytes = wabt::Wat2Wasm::new()
.validate(false)
.convert(source)
.expect("failed to parse module");
elements::deserialize_buffer(module_bytes.as_ref())
.expect("failed to parse module")
}
macro_rules! test_gas_counter_injection {
(name = $name:ident; input = $input:expr; expected = $expected:expr) => {
#[test]
fn $name() {
let input_module = parse_wat($input);
let expected_module = parse_wat($expected);
let injected_module = inject_gas_counter(input_module, &Default::default())
.expect("inject_gas_counter call failed");
let actual_func_body = get_function_body(&injected_module, 0)
.expect("injected module must have a function body");
let expected_func_body = get_function_body(&expected_module, 0)
.expect("post-module must have a function body");
assert_eq!(actual_func_body, expected_func_body);
}
}
}
test_gas_counter_injection! {
name = simple;
input = r#"
(module
(func (result i32)
(get_global 0)))
"#;
expected = r#"
(module
(func (result i32)
(call 0 (i32.const 1))
(get_global 0)))
"#
}
test_gas_counter_injection! {
name = nested;
input = r#"
(module
(func (result i32)
(get_global 0)
(block
(get_global 0)
(get_global 0)
(get_global 0))
(get_global 0)))
"#;
expected = r#"
(module
(func (result i32)
(call 0 (i32.const 6))
(get_global 0)
(block
(get_global 0)
(get_global 0)
(get_global 0))
(get_global 0)))
"#
}
test_gas_counter_injection! {
name = ifelse;
input = r#"
(module
(func (result i32)
(get_global 0)
(if
(then
(get_global 0)
(get_global 0)
(get_global 0))
(else
(get_global 0)
(get_global 0)))
(get_global 0)))
"#;
expected = r#"
(module
(func (result i32)
(call 0 (i32.const 3))
(get_global 0)
(if
(then
(call 0 (i32.const 3))
(get_global 0)
(get_global 0)
(get_global 0))
(else
(call 0 (i32.const 2))
(get_global 0)
(get_global 0)))
(get_global 0)))
"#
}
test_gas_counter_injection! {
name = branch_innermost;
input = r#"
(module
(func (result i32)
(get_global 0)
(block
(get_global 0)
(drop)
(br 0)
(get_global 0)
(drop))
(get_global 0)))
"#;
expected = r#"
(module
(func (result i32)
(call 0 (i32.const 6))
(get_global 0)
(block
(get_global 0)
(drop)
(br 0)
(call 0 (i32.const 2))
(get_global 0)
(drop))
(get_global 0)))
"#
}
test_gas_counter_injection! {
name = branch_outer_block;
input = r#"
(module
(func (result i32)
(get_global 0)
(block
(get_global 0)
(if
(then
(get_global 0)
(get_global 0)
(drop)
(br_if 1)))
(get_global 0)
(drop))
(get_global 0)))
"#;
expected = r#"
(module
(func (result i32)
(call 0 (i32.const 5))
(get_global 0)
(block
(get_global 0)
(if
(then
(call 0 (i32.const 4))
(get_global 0)
(get_global 0)
(drop)
(br_if 1)))
(call 0 (i32.const 2))
(get_global 0)
(drop))
(get_global 0)))
"#
}
test_gas_counter_injection! {
name = branch_outer_loop;
input = r#"
(module
(func (result i32)
(get_global 0)
(loop
(get_global 0)
(if
(then
(get_global 0)
(br_if 0))
(else
(get_global 0)
(get_global 0)
(drop)
(br_if 1)))
(get_global 0)
(drop))
(get_global 0)))
"#;
expected = r#"
(module
(func (result i32)
(call 0 (i32.const 3))
(get_global 0)
(loop
(call 0 (i32.const 4))
(get_global 0)
(if
(then
(call 0 (i32.const 2))
(get_global 0)
(br_if 0))
(else
(call 0 (i32.const 4))
(get_global 0)
(get_global 0)
(drop)
(br_if 1)))
(get_global 0)
(drop))
(get_global 0)))
"#
}
test_gas_counter_injection! {
name = return_from_func;
input = r#"
(module
(func (result i32)
(get_global 0)
(if
(then
(return)))
(get_global 0)))
"#;
expected = r#"
(module
(func (result i32)
(call 0 (i32.const 2))
(get_global 0)
(if
(then
(call 0 (i32.const 1))
(return)))
(call 0 (i32.const 1))
(get_global 0)))
"#
}
test_gas_counter_injection! {
name = branch_from_if_not_else;
input = r#"
(module
(func (result i32)
(get_global 0)
(block
(get_global 0)
(if
(then (br 1))
(else (br 0)))
(get_global 0)
(drop))
(get_global 0)))
"#;
expected = r#"
(module
(func (result i32)
(call 0 (i32.const 5))
(get_global 0)
(block
(get_global 0)
(if
(then
(call 0 (i32.const 1))
(br 1))
(else
(call 0 (i32.const 1))
(br 0)))
(call 0 (i32.const 2))
(get_global 0)
(drop))
(get_global 0)))
"#
}
}
+370
View File
@@ -0,0 +1,370 @@
//! This module is used to validate the correctness of the gas metering algorithm.
//!
//! Since the gas metering algorithm is complex, this checks correctness by fuzzing. The testing
//! strategy is to generate random, valid Wasm modules using Binaryen's translate-to-fuzz
//! functionality, then ensure for all functions defined, in all execution paths though the
//! function body that do not trap that the amount of gas charged by the proposed metering
//! instructions is correct. This is done by constructing a control flow graph and exhaustively
//! searching through all paths, which may take exponential time in the size of the function body in
//! the worst case.
use super::MeteredBlock;
use rules::Set as RuleSet;
use parity_wasm::elements::{FuncBody, Instruction};
use std::collections::HashMap;
/// 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)]
struct ControlFlowNode {
/// The index of the first instruction in the basic block. This is only used for debugging.
first_instr_pos: Option<usize>,
/// The actual gas cost of executing all instructions in the basic block.
actual_cost: u32,
/// The amount of gas charged by the injected metering instructions within this basic block.
charged_cost: u32,
/// Whether there are any other nodes in the graph that loop back to this one. Every cycle in
/// the control flow graph contains at least one node with this flag set.
is_loop_target: bool,
/// Edges in the "forward" direction of the graph. The graph of nodes and their forward edges
/// forms a directed acyclic graph (DAG).
forward_edges: Vec<NodeId>,
/// Edges in the "backwards" direction. These edges form cycles in the graph.
loopback_edges: Vec<NodeId>,
}
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
/// loop-back edges introduces cycles.
#[derive(Debug)]
pub struct ControlFlowGraph {
nodes: Vec<ControlFlowNode>,
}
impl ControlFlowGraph {
fn new() -> Self {
ControlFlowGraph {
nodes: Vec::new(),
}
}
fn get_node(&self, node_id: NodeId) -> &ControlFlowNode {
self.nodes.get(node_id).unwrap()
}
fn get_node_mut(&mut self, node_id: NodeId) -> &mut ControlFlowNode {
self.nodes.get_mut(node_id).unwrap()
}
fn add_node(&mut self) -> NodeId {
self.nodes.push(ControlFlowNode::default());
self.nodes.len() - 1
}
fn increment_actual_cost(&mut self, node_id: NodeId, cost: u32) {
self.get_node_mut(node_id).actual_cost += cost;
}
fn increment_charged_cost(&mut self, node_id: NodeId, cost: u32) {
self.get_node_mut(node_id).charged_cost += cost;
}
fn set_first_instr_pos(&mut self, node_id: NodeId, first_instr_pos: usize) {
self.get_node_mut(node_id).first_instr_pos = Some(first_instr_pos)
}
fn new_edge(&mut self, from_id: NodeId, target_frame: &ControlFrame) {
if target_frame.is_loop {
self.new_loopback_edge(from_id, target_frame.entry_node);
} else {
self.new_forward_edge(from_id, target_frame.exit_node);
}
}
fn new_forward_edge(&mut self, from_id: NodeId, to_id: NodeId) {
self.get_node_mut(from_id).forward_edges.push(to_id)
}
fn new_loopback_edge(&mut self, from_id: NodeId, to_id: NodeId) {
self.get_node_mut(from_id).loopback_edges.push(to_id);
self.get_node_mut(to_id).is_loop_target = true;
}
}
/// A control frame is opened upon entry into a function and by the `block`, `if`, and `loop`
/// instructions and is closed by `end` instructions.
struct ControlFrame {
is_loop: bool,
entry_node: NodeId,
exit_node: NodeId,
active_node: NodeId,
}
impl ControlFrame {
fn new(entry_node_id: NodeId, exit_node_id: NodeId, is_loop: bool) -> Self {
ControlFrame {
is_loop,
entry_node: entry_node_id,
exit_node: exit_node_id,
active_node: entry_node_id,
}
}
}
/// Construct a control flow graph from a function body and the metered blocks computed for it.
///
/// This assumes that the function body has been validated already, otherwise this may panic.
fn build_control_flow_graph(
body: &FuncBody,
rules: &RuleSet,
blocks: &[MeteredBlock]
) -> Result<ControlFlowGraph, ()> {
let mut graph = ControlFlowGraph::new();
let entry_node_id = graph.add_node();
let terminal_node_id = graph.add_node();
graph.set_first_instr_pos(entry_node_id, 0);
let mut stack = Vec::new();
stack.push(ControlFrame::new(entry_node_id, terminal_node_id, false));
let mut metered_blocks_iter = blocks.iter().peekable();
for (cursor, instruction) in body.code().elements().iter().enumerate() {
let active_node_id = stack.last()
.expect("module is valid by pre-condition; control stack must not be empty; qed")
.active_node;
// Increment the charged cost if there are metering instructions to be inserted here.
let apply_block = metered_blocks_iter.peek()
.map_or(false, |block| block.start_pos == cursor);
if apply_block {
let next_metered_block = metered_blocks_iter.next()
.expect("peek returned an item; qed");
graph.increment_charged_cost(active_node_id, next_metered_block.cost);
}
let instruction_cost = rules.process(instruction)?;
match *instruction {
Instruction::Block(_) => {
graph.increment_actual_cost(active_node_id, instruction_cost);
let exit_node_id = graph.add_node();
stack.push(ControlFrame::new(active_node_id, exit_node_id, false));
}
Instruction::If(_) => {
graph.increment_actual_cost(active_node_id, instruction_cost);
let then_node_id = graph.add_node();
let exit_node_id = graph.add_node();
stack.push(ControlFrame::new(then_node_id, exit_node_id, false));
graph.new_forward_edge(active_node_id, then_node_id);
graph.set_first_instr_pos(then_node_id, cursor + 1);
}
Instruction::Loop(_) => {
graph.increment_actual_cost(active_node_id, instruction_cost);
let loop_node_id = graph.add_node();
let exit_node_id = graph.add_node();
stack.push(ControlFrame::new(loop_node_id, exit_node_id, true));
graph.new_forward_edge(active_node_id, loop_node_id);
graph.set_first_instr_pos(loop_node_id, cursor + 1);
}
Instruction::Else => {
let active_frame_idx = stack.len() - 1;
let prev_frame_idx = stack.len() - 2;
let else_node_id = graph.add_node();
stack[active_frame_idx].active_node = else_node_id;
let prev_node_id = stack[prev_frame_idx].active_node;
graph.new_forward_edge(prev_node_id, else_node_id);
graph.set_first_instr_pos(else_node_id, cursor + 1);
}
Instruction::End => {
let closing_frame = stack.pop()
.expect("module is valid by pre-condition; ends correspond to control stack frames; qed");
graph.new_forward_edge(active_node_id, closing_frame.exit_node);
graph.set_first_instr_pos(closing_frame.exit_node, cursor + 1);
if let Some(active_frame) = stack.last_mut() {
active_frame.active_node = closing_frame.exit_node;
}
}
Instruction::Br(label) => {
graph.increment_actual_cost(active_node_id, instruction_cost);
let active_frame_idx = stack.len() - 1;
let target_frame_idx = active_frame_idx - (label as usize);
graph.new_edge(active_node_id, &stack[target_frame_idx]);
// Next instruction is unreachable, but carry on anyway.
let new_node_id = graph.add_node();
stack[active_frame_idx].active_node = new_node_id;
graph.set_first_instr_pos(new_node_id, cursor + 1);
}
Instruction::BrIf(label) => {
graph.increment_actual_cost(active_node_id, instruction_cost);
let active_frame_idx = stack.len() - 1;
let target_frame_idx = active_frame_idx - (label as usize);
graph.new_edge(active_node_id, &stack[target_frame_idx]);
let new_node_id = graph.add_node();
stack[active_frame_idx].active_node = new_node_id;
graph.new_forward_edge(active_node_id, new_node_id);
graph.set_first_instr_pos(new_node_id, cursor + 1);
}
Instruction::BrTable(ref br_table_data) => {
graph.increment_actual_cost(active_node_id, instruction_cost);
let active_frame_idx = stack.len() - 1;
for &label in [br_table_data.default].iter().chain(br_table_data.table.iter()) {
let target_frame_idx = active_frame_idx - (label as usize);
graph.new_edge(active_node_id, &stack[target_frame_idx]);
}
let new_node_id = graph.add_node();
stack[active_frame_idx].active_node = new_node_id;
graph.set_first_instr_pos(new_node_id, cursor + 1);
}
Instruction::Return => {
graph.increment_actual_cost(active_node_id, instruction_cost);
graph.new_forward_edge(active_node_id, terminal_node_id);
let active_frame_idx = stack.len() - 1;
let new_node_id = graph.add_node();
stack[active_frame_idx].active_node = new_node_id;
graph.set_first_instr_pos(new_node_id, cursor + 1);
}
_ => graph.increment_actual_cost(active_node_id, instruction_cost),
}
}
assert!(stack.is_empty());
Ok(graph)
}
/// Exhaustively search through all paths in the control flow graph, starting from the first node
/// and ensure that 1) all paths with only forward edges ending with the terminal node have an
/// equal total actual gas cost and total charged gas cost, and 2) all cycles beginning with a loop
/// entry point and ending with a node with a loop-back edge to the entry point have equal actual
/// and charged gas costs. If this returns true, then the metered blocks used to construct the
/// control flow graph are correct with respect to the function body.
///
/// In the worst case, this runs in time exponential in the size of the graph.
fn validate_graph_gas_costs(graph: &ControlFlowGraph) -> bool {
fn visit(
graph: &ControlFlowGraph,
node_id: NodeId,
mut total_actual: u32,
mut total_charged: u32,
loop_costs: &mut HashMap<NodeId, (u32, u32)>,
) -> bool {
let node = graph.get_node(node_id);
total_actual += node.actual_cost;
total_charged += node.charged_cost;
if node.is_loop_target {
loop_costs.insert(node_id, (node.actual_cost, node.charged_cost));
}
if node.forward_edges.is_empty() && total_actual != total_charged {
return false;
}
for loop_node_id in node.loopback_edges.iter() {
let (ref mut loop_actual, ref mut loop_charged) = loop_costs.get_mut(loop_node_id)
.expect("cannot arrive at loopback edge without visiting loop entry node");
if loop_actual != loop_charged {
return false;
}
}
for next_node_id in node.forward_edges.iter() {
if !visit(graph, *next_node_id, total_actual, total_charged, loop_costs) {
return false;
}
}
if node.is_loop_target {
loop_costs.remove(&node_id);
}
true
}
// Recursively explore all paths through the execution graph starting from the entry node.
visit(graph, 0, 0, 0, &mut HashMap::new())
}
/// Validate that the metered blocks are correct with respect to the function body by exhaustively
/// searching all paths through the control flow graph.
///
/// This assumes that the function body has been validated already, otherwise this may panic.
fn validate_metering_injections(
body: &FuncBody,
rules: &RuleSet,
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 parity_wasm::elements;
use binaryen::tools::translate_to_fuzz_mvp;
use rand::{thread_rng, RngCore};
#[test]
fn test_build_control_flow_graph() {
for _ in 0..20 {
let mut rand_input = [0u8; 2048];
thread_rng().fill_bytes(&mut rand_input);
let module_bytes = translate_to_fuzz_mvp(&rand_input).write();
let module: elements::Module = elements::deserialize_buffer(&module_bytes)
.expect("failed to parse Wasm blob generated by translate_to_fuzz");
for func_body in module.code_section().iter().flat_map(|section| section.bodies()) {
let rules = RuleSet::default();
let metered_blocks = determine_metered_blocks(func_body.code(), &rules).unwrap();
let success = validate_metering_injections(func_body, &rules, &metered_blocks).unwrap();
assert!(success);
}
}
}
}
+990
View File
@@ -0,0 +1,990 @@
//! 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"
);
}
}
+61 -11
View File
@@ -5,37 +5,87 @@
#[macro_use]
extern crate alloc;
extern crate parity_wasm;
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;
pub static CREATE_SYMBOL: &'static str = "deploy";
pub static CALL_SYMBOL: &'static str = "call";
pub static RET_SYMBOL: &'static str = "ret";
pub mod rules;
mod optimizer;
mod gas;
mod symbols;
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 optimizer::{optimize, Error as OptimizerError};
pub use build::{build, Error as BuildError, SourceTarget};
pub use ext::{
externalize, externalize_mem, shrink_unknown_stack, underscore_funcs, ununderscore_funcs,
};
pub use gas::inject_gas_counter;
pub use ext::{externalize, externalize_mem, underscore_funcs, ununderscore_funcs, shrink_unknown_stack};
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 use alloc::{vec, string, boxed, borrow};
pub mod rc {
pub use alloc::rc::Rc;
}
pub mod collections {
pub use alloc::{BTreeMap, BTreeSet};
pub use alloc::collections::{BTreeMap, BTreeSet};
}
}
+112 -72
View File
@@ -3,6 +3,7 @@ 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;
@@ -23,6 +24,13 @@ pub fn optimize(
// 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() {
@@ -38,12 +46,28 @@ pub fn optimize(
let mut init_symbols = Vec::new();
if let Some(data_section) = module.data_section() {
for segment in data_section.entries() {
push_code_symbols(&module, segment.offset().code(), &mut init_symbols);
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().code(), &mut init_symbols);
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));
}
@@ -248,12 +272,26 @@ pub fn optimize(
},
&mut elements::Section::Data(ref mut data_section) => {
for ref mut segment in data_section.entries_mut() {
update_global_index(segment.offset_mut().code_mut(), &eliminated_globals)
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().code_mut(), &eliminated_globals);
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();
@@ -261,6 +299,32 @@ pub fn optimize(
}
}
},
&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);
}
}
_ => { }
}
}
@@ -270,25 +334,22 @@ pub fn optimize(
}
pub fn update_call_index(opcodes: &mut elements::Opcodes, eliminated_indices: &[usize]) {
use parity_wasm::elements::Opcode::*;
for opcode in opcodes.elements_mut().iter_mut() {
match opcode {
&mut Call(ref mut call_index) => {
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;
},
_ => { },
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(opcodes: &mut Vec<elements::Opcode>, eliminated_indices: &[usize]) {
use parity_wasm::elements::Opcode::*;
for opcode in opcodes.iter_mut() {
match opcode {
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);
@@ -300,27 +361,21 @@ pub fn update_global_index(opcodes: &mut Vec<elements::Opcode>, eliminated_indic
}
/// Updates global references considering the _ordered_ list of eliminated indices
pub fn update_type_index(opcodes: &mut elements::Opcodes, eliminated_indices: &[usize]) {
use parity_wasm::elements::Opcode::*;
for opcode in opcodes.elements_mut().iter_mut() {
match opcode {
&mut CallIndirect(ref mut call_index, _) => {
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 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() {
match section {
&mut elements::Section::Import(ref mut sect) => {
return Some(sect);
},
_ => { }
if let &mut elements::Section::Import(ref mut sect) = section {
return Some(sect);
}
}
None
@@ -328,11 +383,8 @@ pub fn import_section<'a>(module: &'a mut elements::Module) -> Option<&'a mut el
pub fn global_section<'a>(module: &'a mut elements::Module) -> Option<&'a mut elements::GlobalSection> {
for section in module.sections_mut() {
match section {
&mut elements::Section::Global(ref mut sect) => {
return Some(sect);
},
_ => { }
if let &mut elements::Section::Global(ref mut sect) = section {
return Some(sect);
}
}
None
@@ -340,11 +392,8 @@ pub fn global_section<'a>(module: &'a mut elements::Module) -> Option<&'a mut el
pub fn function_section<'a>(module: &'a mut elements::Module) -> Option<&'a mut elements::FunctionSection> {
for section in module.sections_mut() {
match section {
&mut elements::Section::Function(ref mut sect) => {
return Some(sect);
},
_ => { }
if let &mut elements::Section::Function(ref mut sect) = section {
return Some(sect);
}
}
None
@@ -352,11 +401,8 @@ pub fn function_section<'a>(module: &'a mut elements::Module) -> Option<&'a mut
pub fn code_section<'a>(module: &'a mut elements::Module) -> Option<&'a mut elements::CodeSection> {
for section in module.sections_mut() {
match section {
&mut elements::Section::Code(ref mut sect) => {
return Some(sect);
},
_ => { }
if let &mut elements::Section::Code(ref mut sect) = section {
return Some(sect);
}
}
None
@@ -364,11 +410,8 @@ pub fn code_section<'a>(module: &'a mut elements::Module) -> Option<&'a mut elem
pub fn export_section<'a>(module: &'a mut elements::Module) -> Option<&'a mut elements::ExportSection> {
for section in module.sections_mut() {
match section {
&mut elements::Section::Export(ref mut sect) => {
return Some(sect);
},
_ => { }
if let &mut elements::Section::Export(ref mut sect) = section {
return Some(sect);
}
}
None
@@ -376,11 +419,8 @@ pub fn export_section<'a>(module: &'a mut elements::Module) -> Option<&'a mut el
pub fn type_section<'a>(module: &'a mut elements::Module) -> Option<&'a mut elements::TypeSection> {
for section in module.sections_mut() {
match section {
&mut elements::Section::Type(ref mut sect) => {
return Some(sect);
},
_ => { }
if let &mut elements::Section::Type(ref mut sect) = section {
return Some(sect);
}
}
None
@@ -459,10 +499,10 @@ mod tests {
.function()
.signature().param().i32().build()
.body()
.with_opcodes(elements::Opcodes::new(
.with_instructions(elements::Instructions::new(
vec![
elements::Opcode::GetGlobal(0),
elements::Opcode::End
elements::Instruction::GetGlobal(0),
elements::Instruction::End
]
))
.build()
@@ -501,10 +541,10 @@ mod tests {
.function()
.signature().param().i32().build()
.body()
.with_opcodes(elements::Opcodes::new(
.with_instructions(elements::Instructions::new(
vec![
elements::Opcode::GetGlobal(1),
elements::Opcode::End
elements::Instruction::GetGlobal(1),
elements::Instruction::End
]
))
.build()
@@ -534,10 +574,10 @@ mod tests {
.function()
.signature().param().i32().build()
.body()
.with_opcodes(elements::Opcodes::new(
.with_instructions(elements::Instructions::new(
vec![
elements::Opcode::Call(1),
elements::Opcode::End
elements::Instruction::Call(1),
elements::Instruction::End
]
))
.build()
@@ -582,15 +622,15 @@ mod tests {
.signature().param().i32().param().i32().build()
.build()
.function()
.signature().param().i32().param().i32().build()
.signature().param().i32().param().i32().param().i32().build()
.build()
.function()
.signature().param().i32().build()
.body()
.with_opcodes(elements::Opcodes::new(
.with_instructions(elements::Instructions::new(
vec![
elements::Opcode::CallIndirect(1, 0),
elements::Opcode::End
elements::Instruction::CallIndirect(1, 0),
elements::Instruction::End
]
))
.build()
@@ -610,7 +650,7 @@ mod tests {
let indirect_opcode = &module.code_section().expect("code section to be generated").bodies()[0].code().elements()[0];
match *indirect_opcode {
elements::Opcode::CallIndirect(0, 0) => {},
elements::Instruction::CallIndirect(0, 0) => {},
_ => {
panic!(
"Expected call_indirect to use index 0 after optimization, since previois 0th was eliminated, but got {:?}",
+286 -284
View File
@@ -3,11 +3,11 @@ use std::vec::Vec;
use std::borrow::ToOwned;
use parity_wasm::elements::{
self, Section, DataSection, Opcode, DataSegment, InitExpr, Internal, External,
ImportCountType,
self, Section, DataSection, Instruction, DataSegment, InitExpr, Internal, External,
ImportCountType,
};
use parity_wasm::builder;
use super::{CREATE_SYMBOL, CALL_SYMBOL, RET_SYMBOL};
use super::TargetRuntime;
use super::gas::update_call_index;
/// Pack error.
@@ -16,323 +16,325 @@ use super::gas::update_call_index;
/// When they are violated, pack_instance returns one of these.
#[derive(Debug)]
pub enum Error {
MalformedModule,
NoTypeSection,
NoExportSection,
NoCodeSection,
InvalidCreateSignature,
NoCreateSymbol,
InvalidCreateMember,
NoImportSection,
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 => write!(f, "Exported symbol `deploy` has invalid signature, should be () -> ()"),
Error::InvalidCreateMember => write!(f, "Exported symbol `deploy` should be a function"),
Error::NoCreateSymbol => write!(f, "No exported `deploy` symbol"),
Error::NoImportSection => write!(f, "No import section in the module"),
}
}
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match *self {
Error::MalformedModule => write!(f, "Module internal references are inconsistent"),
Error::NoTypeSection => write!(f, "No type section in the module"),
Error::NoExportSection => write!(f, "No export section in the module"),
Error::NoCodeSection => write!(f, "No code section inthe module"),
Error::InvalidCreateSignature(sym) => write!(f, "Exported symbol `{}` has invalid signature, should be () -> ()", sym),
Error::InvalidCreateMember(sym) => write!(f, "Exported symbol `{}` should be a function", sym),
Error::NoCreateSymbol(sym) => write!(f, "No exported `{}` symbol", sym),
Error::NoImportSection => write!(f, "No import section in the module"),
}
}
}
/// If module has an exported "_create" function we want to pack it into "constructor".
/// 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) -> Result<elements::Module, Error> {
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);
// Total number of constructor module import functions
let ctor_import_functions = ctor_module.import_section().map(|x| x.functions()).unwrap_or(0);
// We need to find an internal ID of function witch is exported as "_create"
// 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| CREATE_SYMBOL == entry.field()).ok_or(Error::NoCreateSymbol)?;
// 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) },
};
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;
// Calculates a function index within module's function section
let function_internal_index = function_index - ctor_import_functions;
// Constructor should be of signature `func(i32)` (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();
// 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)?;
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);
}
if func.return_type().is_some() {
return Err(Error::InvalidCreateSignature);
}
// 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
};
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() == RET_SYMBOL { found = true; break; }
else { id += 1; }
}
}
if !found {
let mut mbuilder = builder::from_module(ctor_module);
let import_sig = mbuilder.push_signature(
builder::signature()
.param().i32().param().i32()
.build_sig()
);
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("ret")
.external().func(import_sig)
.build()
);
mbuilder.push_import(
builder::import()
.module("env")
.field(&target.symbols().ret)
.external().func(import_sig)
.build()
);
ctor_module = mbuilder.build();
ctor_module = mbuilder.build();
let ret_func = ctor_module.import_count(ImportCountType::Function) as u32 - 1;
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() {
match export.internal_mut() {
&mut elements::Internal::Function(ref mut func_index) => {
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}
}
}
},
_ => { }
}
}
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 }
};
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();
// 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![])));
}
// 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;
// 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() {
match section {
&mut Section::Data(ref mut data_section) => {
let (index, offset) = if let Some(ref entry) = data_section.entries().iter().last() {
if let Opcode::I32Const(offst) = entry.offset().code()[0] {
let len = entry.value().len() as i32;
let offst = offst as i32;
(entry.index(), offst + (len + 4) - len % 4)
} else {
(0, 0)
}
} else {
(0, 0)
};
let code_data = DataSegment::new(
index,
InitExpr::new(vec![Opcode::I32Const(offset), Opcode::End]),
raw_module.clone()
);
data_section.entries_mut().push(code_data);
code_data_address = offset;
},
_ => {;}
}
}
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_opcodes(elements::Opcodes::new(
vec![
Opcode::Call((create_func_id + ctor_import_functions) as u32),
Opcode::I32Const(code_data_address),
Opcode::I32Const(raw_module.len() as i32),
Opcode::Call(ret_function_id as u32),
Opcode::End,
])).build()
.build()
.build();
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() {
match section {
&mut Section::Export(ref mut export_section) => {
for entry in export_section.entries_mut().iter_mut() {
if CREATE_SYMBOL == entry.field() {
// change _create export name into default _call
*entry.field_mut() = CALL_SYMBOL.to_owned();
*entry.internal_mut() = elements::Internal::Function(last_function_index as u32);
}
}
},
_ => { },
}
};
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)
Ok(new_module)
}
#[cfg(test)]
mod test {
extern crate parity_wasm;
extern crate parity_wasm;
use parity_wasm::builder;
use super::*;
use super::super::optimize;
use parity_wasm::builder;
use super::*;
use super::super::optimize;
fn test_packer(mut module: elements::Module) {
let mut ctor_module = module.clone();
optimize(&mut module, vec![CALL_SYMBOL]).expect("Optimizer to finish without errors");
optimize(&mut ctor_module, vec![CREATE_SYMBOL]).expect("Optimizer to finish without errors");
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).expect("Packing failed");
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");
}
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() {
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_opcodes(elements::Opcodes::new(
vec![
elements::Opcode::End
]
))
.build()
.build()
.function()
.signature().build()
.body()
.with_opcodes(elements::Opcodes::new(
vec![
elements::Opcode::End
]
))
.build()
.build()
.export()
.field(CALL_SYMBOL)
.internal().func(1)
.build()
.export()
.field(CREATE_SYMBOL)
.internal().func(2)
.build()
.build()
);
}
#[test]
fn no_data_section() {
let target_runtime = TargetRuntime::pwasm();
#[test]
fn with_data_section() {
test_packer(builder::module()
.import()
.module("env")
.field("memory")
.external().memory(1 as u32, Some(1 as u32))
.build()
.data()
.offset(elements::Opcode::I32Const(16)).value(vec![0u8])
.build()
.function()
.signature()
.params().i32().i32().build()
.build()
.body().build()
.build()
.function()
.signature().build()
.body()
.with_opcodes(elements::Opcodes::new(
vec![
elements::Opcode::End
]
))
.build()
.build()
.function()
.signature().build()
.body()
.with_opcodes(elements::Opcodes::new(
vec![
elements::Opcode::End
]
))
.build()
.build()
.export()
.field(CALL_SYMBOL)
.internal().func(1)
.build()
.export()
.field(CREATE_SYMBOL)
.internal().func(2)
.build()
.build()
);
}
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
@@ -0,0 +1,562 @@
#![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));
}
}
+261 -261
View File
@@ -9,307 +9,307 @@ pub struct UnknownInstruction;
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum Metering {
Regular,
Forbidden,
Fixed(u32),
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,
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;
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),
"currrent_mem" => Ok(InstructionType::CurrentMemory),
"grow_mem" => Ok(InstructionType::GrowMemory),
_ => 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(opcode: &elements::Opcode) -> Self {
use parity_wasm::elements::Opcode::*;
pub fn op(instruction: &elements::Instruction) -> Self {
use parity_wasm::elements::Instruction::*;
match *opcode {
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,
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,
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,
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,
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,
CurrentMemory(_) => InstructionType::CurrentMemory,
GrowMemory(_) => InstructionType::GrowMemory,
I32Const(_) => InstructionType::Const,
I64Const(_) => InstructionType::Const,
I32Const(_) => InstructionType::Const,
I64Const(_) => InstructionType::Const,
F32Const(_) => InstructionType::FloatConst,
F64Const(_) => InstructionType::FloatConst,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
}
}
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,
regular: u32,
entries: Map<InstructionType, Metering>,
grow: u32,
}
impl Default for Set {
fn default() -> Self {
Set {
regular: 1,
entries: Map::new(),
grow: 0,
}
}
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 new(regular: u32, entries: Map<InstructionType, Metering>) -> Self {
Set { regular: regular, entries: entries, grow: 0 }
}
pub fn process(&self, opcode: &elements::Opcode) -> Result<u32, ()> {
match self.entries.get(&InstructionType::op(opcode)).map(|x| *x) {
None | Some(Metering::Regular) => Ok(self.regular),
Some(Metering::Forbidden) => Err(()),
Some(Metering::Fixed(val)) => Ok(val),
}
}
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 grow_cost(&self) -> u32 {
self.grow
}
pub fn with_grow_cost(mut self, val: u32) -> Self {
self.grow = val;
self
}
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
}
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
}
}
+8 -6
View File
@@ -1,8 +1,8 @@
use parity_wasm::{elements, builder};
use self::elements::{ Module, GlobalEntry, External, ExportEntry, GlobalType, ValueType, InitExpr, Opcode, Internal };
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], runtime_version: u32) -> Module {
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,
@@ -18,9 +18,9 @@ pub fn inject_runtime_type(module: Module, runtime_type: &[u8], runtime_version:
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![Opcode::I32Const(runtime_type as i32), Opcode::End])))
.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![Opcode::I32Const(runtime_version as i32), Opcode::End])))
.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()
}
@@ -31,9 +31,11 @@ mod tests {
#[test]
fn it_injects() {
let mut module = builder::module()
.with_global(GlobalEntry::new(GlobalType::new(ValueType::I32, false), InitExpr::new(vec![Opcode::I32Const(42 as i32)])))
.with_global(GlobalEntry::new(GlobalType::new(ValueType::I32, false), InitExpr::new(vec![Instruction::I32Const(42 as i32)])))
.build();
module = inject_runtime_type(module, b"emcc", 1);
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");
+19 -19
View File
@@ -136,7 +136,7 @@ impl Stack {
/// 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::Opcode::*;
use parity_wasm::elements::Instruction::*;
let func_section = module
.function_section()
@@ -165,7 +165,7 @@ pub(crate) fn compute(func_idx: u32, module: &elements::Module) -> Result<u32, E
.bodies()
.get(func_idx as usize)
.ok_or_else(|| Error("Function body for the index isn't found".into()))?;
let opcodes = body.code();
let instructions = body.code();
let mut stack = Stack::new();
let mut max_height: u32 = 0;
@@ -186,7 +186,7 @@ pub(crate) fn compute(func_idx: u32, module: &elements::Module) -> Result<u32, E
});
loop {
if pc >= opcodes.elements().len() {
if pc >= instructions.elements().len() {
break;
}
@@ -197,7 +197,7 @@ pub(crate) fn compute(func_idx: u32, module: &elements::Module) -> Result<u32, E
max_height = stack.height();
}
let opcode = &opcodes.elements()[pc];
let opcode = &instructions.elements()[pc];
trace!(target: "max_height", "{:?}", opcode);
match *opcode {
@@ -245,11 +245,11 @@ pub(crate) fn compute(func_idx: u32, module: &elements::Module) -> Result<u32, E
// Push values back.
stack.push_values(target_arity)?;
}
BrTable(ref targets, default_target) => {
let arity_of_default = stack.frame(default_target)?.branch_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 targets.iter() {
for target in &*br_table_data.table {
let arity = stack.frame(*target)?.branch_arity;
if arity != arity_of_default {
return Err(Error(
@@ -497,19 +497,19 @@ mod tests {
(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
;; 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
;; Code after `unreachable` shouldn't have an effect
;; on the max depth.
unreachable
i32.const 0
i32.const 1
i32.const 2
)
)
"#;
+41 -65
View File
@@ -25,11 +25,12 @@
//! 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 to point to a corresponding thunks.
//! then make exported function and table entries, start section to point to a corresponding thunks.
//!
//! # Stack cost
//!
@@ -38,7 +39,7 @@
//!
//! All values are treated equally, as they have the same size.
//!
//! The rationale for this it makes it possible to use this very naive wasm executor, that is:
//! 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)
@@ -57,7 +58,7 @@ 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::Opcode::*;
use $crate::parity_wasm::elements::Instruction::*;
[
// stack_height += stack_cost(F)
GetGlobal($stack_height_global_idx),
@@ -92,35 +93,20 @@ mod thunk;
pub struct Error(String);
pub(crate) struct Context {
stack_height_global_idx: Option<u32>,
func_stack_costs: Option<Vec<u32>>,
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.
///
/// Panics if it haven't generated yet.
fn stack_height_global_idx(&self) -> u32 {
self.stack_height_global_idx.expect(
"stack_height_global_idx isn't yet generated;
Did you call `inject_stack_counter_global`",
)
self.stack_height_global_idx
}
/// Returns `stack_cost` for `func_idx`.
///
/// Panics if stack costs haven't computed yet or `func_idx` is greater
/// than the last function index.
fn stack_cost(&self, func_idx: u32) -> Option<u32> {
self.func_stack_costs
.as_ref()
.expect(
"func_stack_costs isn't yet computed;
Did you call `compute_stack_costs`?",
)
.get(func_idx as usize)
.cloned()
self.func_stack_costs.get(func_idx as usize).cloned()
}
/// Returns stack limit specified by the rules.
@@ -141,13 +127,11 @@ pub fn inject_limiter(
stack_limit: u32,
) -> Result<elements::Module, Error> {
let mut ctx = Context {
stack_height_global_idx: None,
func_stack_costs: None,
stack_height_global_idx: generate_stack_height_global(&mut module),
func_stack_costs: compute_stack_costs(&module)?,
stack_limit,
};
generate_stack_height_global(&mut ctx, &mut module);
compute_stack_costs(&mut ctx, &module)?;
instrument_functions(&mut ctx, &mut module)?;
let module = thunk::generate_thunks(&mut ctx, module)?;
@@ -155,25 +139,19 @@ pub fn inject_limiter(
}
/// Generate a new global that will be used for tracking current stack height.
fn generate_stack_height_global(ctx: &mut Context, module: &mut elements::Module) {
fn generate_stack_height_global(module: &mut elements::Module) -> u32 {
let global_entry = builder::global()
.value_type()
.i32()
.mutable()
.init_expr(elements::Opcode::I32Const(0))
.init_expr(elements::Instruction::I32Const(0))
.build();
// Try to find an existing global section.
for section in module.sections_mut() {
match *section {
elements::Section::Global(ref mut gs) => {
gs.entries_mut().push(global_entry);
let stack_height_global_idx = (gs.entries().len() as u32) - 1;
ctx.stack_height_global_idx = Some(stack_height_global_idx);
return;
}
_ => {}
if let elements::Section::Global(ref mut gs) = *section {
gs.entries_mut().push(global_entry);
return (gs.entries().len() as u32) - 1;
}
}
@@ -181,25 +159,26 @@ fn generate_stack_height_global(ctx: &mut Context, module: &mut elements::Module
module.sections_mut().push(elements::Section::Global(
elements::GlobalSection::with_entries(vec![global_entry]),
));
ctx.stack_height_global_idx = Some(0);
0
}
/// Calculate stack costs for all functions.
///
/// Returns a vector with a stack cost for each function, including imports.
fn compute_stack_costs(ctx: &mut Context, module: &elements::Module) -> Result<(), Error> {
fn compute_stack_costs(module: &elements::Module) -> Result<Vec<u32>, Error> {
let func_imports = module.import_count(elements::ImportCountType::Function);
let mut func_stack_costs = vec![0; module.functions_space()];
// TODO: optimize!
for (func_idx, func_stack_cost) in func_stack_costs.iter_mut().enumerate() {
// We can't calculate stack_cost of the import functions.
if func_idx >= func_imports {
*func_stack_cost = compute_stack_cost(func_idx as u32, &module)?;
}
}
ctx.func_stack_costs = Some(func_stack_costs);
Ok(())
// 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,
@@ -233,14 +212,11 @@ fn compute_stack_cost(func_idx: u32, module: &elements::Module) -> Result<u32, E
fn instrument_functions(ctx: &mut Context, module: &mut elements::Module) -> Result<(), Error> {
for section in module.sections_mut() {
match *section {
elements::Section::Code(ref mut code_section) => {
for func_body in code_section.bodies_mut() {
let mut opcodes = func_body.code_mut();
instrument_function(ctx, opcodes)?;
}
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(())
@@ -274,13 +250,13 @@ fn instrument_functions(ctx: &mut Context, module: &mut elements::Module) -> Res
/// ```
fn instrument_function(
ctx: &mut Context,
opcodes: &mut elements::Opcodes,
instructions: &mut elements::Instructions,
) -> Result<(), Error> {
use parity_wasm::elements::Opcode::*;
use parity_wasm::elements::Instruction::*;
let mut cursor = 0;
loop {
if cursor >= opcodes.elements().len() {
if cursor >= instructions.elements().len() {
break;
}
@@ -293,8 +269,8 @@ fn instrument_function(
}
let action: Action = {
let opcode = &opcodes.elements()[cursor];
match *opcode {
let instruction = &instructions.elements()[cursor];
match *instruction {
Call(ref callee_idx) => {
let callee_stack_cost = ctx
.stack_cost(*callee_idx)
@@ -336,7 +312,7 @@ fn instrument_function(
//
// To splice actually take a place, we need to consume iterator
// splice returns. So we just `count()` it.
let _ = opcodes
let _ = instructions
.elements_mut()
.splice(cursor..(cursor + 1), new_seq.iter().cloned())
.count();
@@ -420,11 +396,11 @@ mod tests {
let module = parse_wat(
r#"
(module
(func (export "i32.add") (param i32 i32) (result i32)
get_local 0
(func (export "i32.add") (param i32 i32) (result i32)
get_local 0
get_local 1
i32.add
)
)
)
"#,
);
+16 -12
View File
@@ -21,9 +21,9 @@ pub(crate) fn generate_thunks(
ctx: &mut Context,
module: elements::Module,
) -> Result<elements::Module, Error> {
// First, we need to collect all function indicies that should be replaced by thunks
// First, we need to collect all function indices that should be replaced by thunks
// Function indicies which needs to generate thunks.
// Function indices which needs to generate thunks.
let mut need_thunks: Vec<u32> = Vec::new();
let mut replacement_map: Map<u32, Thunk> = {
@@ -35,12 +35,14 @@ pub(crate) fn generate_thunks(
.elements_section()
.map(|es| es.entries())
.unwrap_or(&[]);
let start_func_idx = module
.start_section();
let exported_func_indicies = exports.iter().filter_map(|entry| match *entry.internal() {
let exported_func_indices = exports.iter().filter_map(|entry| match *entry.internal() {
Internal::Function(ref function_idx) => Some(*function_idx),
_ => None,
});
let table_func_indicies = elem_segments
let table_func_indices = elem_segments
.iter()
.flat_map(|segment| segment.members())
.cloned();
@@ -48,7 +50,7 @@ pub(crate) fn generate_thunks(
// Replacement map is at least export section size.
let mut replacement_map: Map<u32, Thunk> = Map::new();
for func_idx in exported_func_indicies.chain(table_func_indicies) {
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))
})?;
@@ -93,17 +95,17 @@ pub(crate) fn generate_thunks(
// - argument pushing
// - instrumented call
// - end
let mut thunk_body: Vec<elements::Opcode> = Vec::with_capacity(
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::Opcode::GetLocal(arg_idx as u32));
thunk_body.push(elements::Instruction::GetLocal(arg_idx as u32));
}
thunk_body.extend(instrumented_call.iter().cloned());
thunk_body.push(elements::Opcode::End);
thunk_body.push(elements::Instruction::End);
// TODO: Don't generate a signature, but find an existing one.
@@ -114,7 +116,7 @@ pub(crate) fn generate_thunks(
.with_return_type(thunk.signature.return_type().clone())
.build()
.body()
.with_opcodes(elements::Opcodes::new(
.with_instructions(elements::Instructions::new(
thunk_body
))
.build()
@@ -142,9 +144,8 @@ pub(crate) fn generate_thunks(
match *section {
elements::Section::Export(ref mut export_section) => {
for entry in export_section.entries_mut() {
match *entry.internal_mut() {
Internal::Function(ref mut function_idx) => fixup(function_idx),
_ => {}
if let Internal::Function(ref mut function_idx) = *entry.internal_mut() {
fixup(function_idx)
}
}
}
@@ -155,6 +156,9 @@ pub(crate) fn generate_thunks(
}
}
}
elements::Section::Start(ref mut start_idx) => {
fixup(start_idx)
}
_ => {}
}
}
+20 -29
View File
@@ -19,14 +19,11 @@ 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() {
match item.external() {
&elements::External::Function(_) => {
if functions == index {
return Symbol::Import(item_index as usize);
}
functions += 1;
},
_ => {}
if let &elements::External::Function(_) = item.external() {
if functions == index {
return Symbol::Import(item_index as usize);
}
functions += 1;
}
}
}
@@ -38,14 +35,11 @@ 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() {
match item.external() {
&elements::External::Global(_) => {
if globals == index {
return Symbol::Import(item_index as usize);
}
globals += 1;
},
_ => {}
if let &elements::External::Global(_) = item.external() {
if globals == index {
return Symbol::Import(item_index as usize);
}
globals += 1;
}
}
}
@@ -53,11 +47,11 @@ pub fn resolve_global(module: &elements::Module, index: u32) -> Symbol {
Symbol::Global(index as usize - globals as usize)
}
pub fn push_code_symbols(module: &elements::Module, opcodes: &[elements::Opcode], dest: &mut Vec<Symbol>) {
use parity_wasm::elements::Opcode::*;
pub fn push_code_symbols(module: &elements::Module, instructions: &[elements::Instruction], dest: &mut Vec<Symbol>) {
use parity_wasm::elements::Instruction::*;
for opcode in opcodes {
match opcode {
for instruction in instructions {
match instruction {
&Call(idx) => {
dest.push(resolve_function(module, idx));
},
@@ -109,15 +103,12 @@ pub fn expand_symbols(module: &elements::Module, set: &mut Set<Symbol>) {
},
Import(idx) => {
let entry = &module.import_section().expect("Import section to exist").entries()[idx];
match entry.external() {
&elements::External::Function(type_idx) => {
let type_symbol = Symbol::Type(type_idx as usize);
if !stop.contains(&type_symbol) {
fringe.push(type_symbol);
}
set.insert(type_symbol);
},
_ => {}
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) => {
+52 -19
View File
@@ -9,10 +9,10 @@ use std::path::{Path, PathBuf};
use parity_wasm::elements;
fn slurp<P: AsRef<Path>>(path: P) -> io::Result<Vec<u8>> {
let mut f = fs::File::open(path)?;
let mut buf = vec![];
f.read_to_end(&mut buf)?;
Ok(buf)
let mut f = fs::File::open(path)?;
let mut buf = vec![];
f.read_to_end(&mut buf)?;
Ok(buf)
}
fn dump<P: AsRef<Path>>(path: P, buf: &[u8]) -> io::Result<()> {
@@ -30,6 +30,7 @@ fn validate_wasm(binary: &[u8]) -> Result<(), wabt::Error> {
}
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/",
@@ -37,6 +38,7 @@ fn run_diff_test<F: FnOnce(&[u8]) -> Vec<u8>>(test_dir: &str, name: &str, test:
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/"
@@ -74,20 +76,51 @@ fn run_diff_test<F: FnOnce(&[u8]) -> Vec<u8>>(test_dir: &str, name: &str, test:
}
}
macro_rules! def_stack_height_test {
( $name:ident ) => {
#[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");
elements::serialize(instrumented).expect("Failed to serialize")
});
}
};
mod stack_height {
use super::*;
macro_rules! def_stack_height_test {
( $name:ident ) => {
#[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");
elements::serialize(instrumented).expect("Failed to serialize")
});
}
};
}
def_stack_height_test!(simple);
def_stack_height_test!(start);
def_stack_height_test!(table);
def_stack_height_test!(global);
def_stack_height_test!(imports);
}
def_stack_height_test!(simple);
def_stack_height_test!(table);
def_stack_height_test!(global);
def_stack_height_test!(imports);
mod gas {
use super::*;
macro_rules! def_gas_test {
( $name:ident ) => {
#[test]
fn $name() {
run_diff_test("gas", concat!(stringify!($name), ".wat"), |input| {
let rules = utils::rules::Set::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");
elements::serialize(instrumented).expect("Failed to serialize")
});
}
};
}
def_gas_test!(ifs);
def_gas_test!(simple);
def_gas_test!(start);
def_gas_test!(call);
def_gas_test!(branch);
}
+29
View File
@@ -0,0 +1,29 @@
(module
(type (;0;) (func (result i32)))
(type (;1;) (func (param i32)))
(import "env" "gas" (func (;0;) (type 1)))
(func (;1;) (type 0) (result i32)
(local i32 i32)
i32.const 13
call 0
block ;; label = @1
i32.const 0
set_local 0
i32.const 1
set_local 1
get_local 0
get_local 1
tee_local 0
i32.add
set_local 1
i32.const 1
br_if 0 (;@1;)
i32.const 5
call 0
get_local 0
get_local 1
tee_local 0
i32.add
set_local 1
end
get_local 1))
+19
View File
@@ -0,0 +1,19 @@
(module
(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)
(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)
i32.const 3
call 0
get_local 0
get_local 1
i32.add))
+20
View File
@@ -0,0 +1,20 @@
(module
(type (;0;) (func (param i32) (result i32)))
(type (;1;) (func (param i32)))
(import "env" "gas" (func (;0;) (type 1)))
(func (;1;) (type 0) (param i32) (result i32)
i32.const 2
call 0
i32.const 1
if (result i32) ;; label = @1
i32.const 3
call 0
get_local 0
i32.const 1
i32.add
else
i32.const 2
call 0
get_local 0
i32.popcnt
end))
+24
View File
@@ -0,0 +1,24 @@
(module
(type (;0;) (func))
(type (;1;) (func (param i32)))
(import "env" "gas" (func (;0;) (type 1)))
(func (;1;) (type 0)
i32.const 2
call 0
i32.const 1
if ;; label = @1
i32.const 1
call 0
loop ;; label = @2
i32.const 2
call 0
i32.const 123
drop
end
end)
(func (;2;) (type 0)
i32.const 1
call 0
block ;; label = @1
end)
(export "simple" (func 1)))
+18
View File
@@ -0,0 +1,18 @@
(module
(type (;0;) (func (param i32 i32)))
(type (;1;) (func))
(type (;2;) (func (param i32)))
(import "env" "ext_return" (func (;0;) (type 0)))
(import "env" "memory" (memory (;0;) 1 1))
(import "env" "gas" (func (;1;) (type 2)))
(func (;2;) (type 1)
i32.const 4
call 1
i32.const 8
i32.const 4
call 0
unreachable)
(func (;3;) (type 1))
(export "call" (func 3))
(start 2)
(data (i32.const 8) "\01\02\03\04"))
+1 -2
View File
@@ -2,7 +2,6 @@
(type (;0;) (func))
(type (;1;) (func (param i32 i32) (result i32)))
(type (;2;) (func (param i32)))
(type (;3;) (func (param i32 i32) (result i32)))
(import "env" "foo" (func (;0;) (type 0)))
(func (;1;) (type 1) (param i32 i32) (result i32)
get_local 0
@@ -33,7 +32,7 @@
i32.sub
set_global 1
drop)
(func (;3;) (type 3) (param i32 i32) (result i32)
(func (;3;) (type 1) (param i32 i32) (result i32)
get_local 0
get_local 1
get_global 1
+1 -2
View File
@@ -1,7 +1,6 @@
(module
(type (;0;) (func))
(type (;1;) (func (param i32 i32) (result i32)))
(type (;2;) (func (param i32 i32) (result i32)))
(import "env" "foo" (func (;0;) (type 0)))
(import "env" "boo" (func (;1;) (type 0)))
(func (;2;) (type 1) (param i32 i32) (result i32)
@@ -10,7 +9,7 @@
get_local 0
get_local 1
i32.add)
(func (;3;) (type 2) (param i32 i32) (result i32)
(func (;3;) (type 1) (param i32 i32) (result i32)
get_local 0
get_local 1
get_global 0
+1 -2
View File
@@ -1,10 +1,9 @@
(module
(type (;0;) (func))
(type (;1;) (func))
(func (;0;) (type 0)
i32.const 123
drop)
(func (;1;) (type 1)
(func (;1;) (type 0)
get_global 0
i32.const 1
i32.add
+44
View File
@@ -0,0 +1,44 @@
(module
(type (;0;) (func (param i32 i32)))
(type (;1;) (func))
(import "env" "ext_return" (func (;0;) (type 0)))
(import "env" "memory" (memory (;0;) 1 1))
(func (;1;) (type 1)
(local i32))
(func (;2;) (type 1))
(func (;3;) (type 1)
get_global 0
i32.const 1
i32.add
set_global 0
get_global 0
i32.const 1024
i32.gt_u
if ;; label = @1
unreachable
end
call 1
get_global 0
i32.const 1
i32.sub
set_global 0)
(func (;4;) (type 1)
get_global 0
i32.const 1
i32.add
set_global 0
get_global 0
i32.const 1024
i32.gt_u
if ;; label = @1
unreachable
end
call 1
get_global 0
i32.const 1
i32.sub
set_global 0)
(global (;0;) (mut i32) (i32.const 0))
(export "exported_start" (func 4))
(export "call" (func 2))
(start 4))
+3 -6
View File
@@ -2,9 +2,6 @@
(type (;0;) (func))
(type (;1;) (func (param i32)))
(type (;2;) (func (param i32 i32) (result i32)))
(type (;3;) (func (param i32 i32) (result i32)))
(type (;4;) (func (param i32)))
(type (;5;) (func (param i32 i32) (result i32)))
(import "env" "foo" (func (;0;) (type 0)))
(func (;1;) (type 1) (param i32)
get_local 0
@@ -29,7 +26,7 @@
get_local 0
get_local 1
i32.add)
(func (;3;) (type 3) (param i32 i32) (result i32)
(func (;3;) (type 2) (param i32 i32) (result i32)
get_local 0
get_local 1
get_global 0
@@ -47,7 +44,7 @@
i32.const 2
i32.sub
set_global 0)
(func (;4;) (type 4) (param i32)
(func (;4;) (type 1) (param i32)
get_local 0
get_global 0
i32.const 2
@@ -64,7 +61,7 @@
i32.const 2
i32.sub
set_global 0)
(func (;5;) (type 5) (param i32 i32) (result i32)
(func (;5;) (type 2) (param i32 i32) (result i32)
get_local 0
get_local 1
get_global 0
+27
View File
@@ -0,0 +1,27 @@
(module
(func $fibonacci_with_break (result i32)
(local $x i32) (local $y i32)
(block $unrolled_loop
(set_local $x (i32.const 0))
(set_local $y (i32.const 1))
get_local $x
get_local $y
tee_local $x
i32.add
set_local $y
i32.const 1
br_if $unrolled_loop
get_local $x
get_local $y
tee_local $x
i32.add
set_local $y
)
get_local $y
)
)
+19
View File
@@ -0,0 +1,19 @@
(module
(func $add_locals (param $x i32) (param $y i32) (result i32)
(local $t i32)
get_local $x
get_local $y
call $add
set_local $t
get_local $t
)
(func $add (param $x i32) (param $y i32) (result i32)
(i32.add
(get_local $x)
(get_local $y)
)
)
)
+9
View File
@@ -0,0 +1,9 @@
(module
(func (param $x i32) (result i32)
(if (result i32)
(i32.const 1)
(then (i32.add (get_local $x) (i32.const 1)))
(else (i32.popcnt (get_local $x)))
)
)
)
+17
View File
@@ -0,0 +1,17 @@
(module
(func (export "simple")
(if (i32.const 1)
(then
(loop
i32.const 123
drop
)
)
)
)
(func
block
end
)
)
+18
View File
@@ -0,0 +1,18 @@
(module
(import "env" "ext_return" (func $ext_return (param i32 i32)))
(import "env" "memory" (memory 1 1))
(start $start)
(func $start
(call $ext_return
(i32.const 8)
(i32.const 4)
)
(unreachable)
)
(func (export "call")
)
(data (i32.const 8) "\01\02\03\04")
)
+11
View File
@@ -0,0 +1,11 @@
(module
(import "env" "ext_return" (func $ext_return (param i32 i32)))
(import "env" "memory" (memory 1 1))
(start $start)
(func $start (export "exported_start")
(local i32)
)
(func (export "call")
)
)