mirror of
https://github.com/pezkuwichain/wasm-instrument.git
synced 2026-04-24 01:37:57 +00:00
Compare commits
138 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 87761dad61 | |||
| a768692bbe | |||
| ea4cde0e7d | |||
| f9d8b722b5 | |||
| 39f234e441 | |||
| b4f9be733d | |||
| 155c7253c3 | |||
| c9cdef4c51 | |||
| 2b5026a6c5 | |||
| a774a2cb29 | |||
| 6fd636a41d | |||
| 5792da28d5 | |||
| f8673d5b87 | |||
| 5180d694ce | |||
| a150df8703 | |||
| ae412c45f1 | |||
| 6f46ef5211 | |||
| 026b0502bb | |||
| 2c173fee26 | |||
| 0870ce6646 | |||
| 82bd972333 | |||
| 93abbcfe56 | |||
| ed7f31ec20 | |||
| b5472bcd8f | |||
| b3f8f62105 | |||
| 0cf7daa9e5 | |||
| 24924f59ec | |||
| de60f491b4 | |||
| 4c0f42c6fc | |||
| c3d10a2619 | |||
| 863744b1fc | |||
| 89e13ee901 | |||
| 929e0ec2c0 | |||
| f6a1a6a066 | |||
| 0d40703c6e | |||
| 124de6c2db | |||
| 5a617c3aae | |||
| 80ea6ec7ad | |||
| bbcc495ccc | |||
| 1b7a5d26ea | |||
| b1fbd2921e | |||
| 1e68a862f8 | |||
| 466f5cceba | |||
| 38e0f254b0 | |||
| 5b2cd9c4c6 | |||
| ad83ad17ee | |||
| 91036c0aff | |||
| 728c935367 | |||
| 33785674dc | |||
| 3e635514e4 | |||
| d8428327d5 | |||
| d695703146 | |||
| cda99e70da | |||
| 0a78a1ab8d | |||
| 33c84edd78 | |||
| 8413e562cd | |||
| 1bc4973e6e | |||
| 8ecbc8ddcc | |||
| 56464c102f | |||
| 6046e94b40 | |||
| ec206fca64 | |||
| 59384e09d0 | |||
| 4f81bbc506 | |||
| f5890c2c7b | |||
| 7504381419 | |||
| bb9832dba1 | |||
| 62ea903c3a | |||
| c47adc1bd4 | |||
| c3833efca7 | |||
| 4e871c65e2 | |||
| 48c1c6e72a | |||
| d60340762b | |||
| da5b2ca5f6 | |||
| c520d334cd | |||
| 76b6743c64 | |||
| d6c6cefcf1 | |||
| 86da6439d1 | |||
| ed1c7b1b51 | |||
| cf10b7d5d9 | |||
| dd9169e30f | |||
| be40285a67 | |||
| ba45e15567 | |||
| 33ff0cbe1d | |||
| db4070b96c | |||
| 80d80a37d9 | |||
| 06277915da | |||
| 7c7a0713fc | |||
| 31e3324015 | |||
| dc993bdb1b | |||
| b58e01ec67 | |||
| 8db40174ae | |||
| 471a9b3fcc | |||
| 3db0d60e70 | |||
| fe25beca2b | |||
| 24b97b517a | |||
| 836ec93008 | |||
| 5238b41af2 | |||
| 19ce379f64 | |||
| a9f5058b4f | |||
| 4b8b07a0b5 | |||
| 67d67f3fba | |||
| 3a7f8836dd | |||
| abb5ae6f22 | |||
| 9d0ad5b309 | |||
| 6c510a7f35 | |||
| e491789127 | |||
| 3e7946ab1c | |||
| 41839664bb | |||
| 735110e8d5 | |||
| 0fe96ee497 | |||
| 0837464ec4 | |||
| 7366384861 | |||
| db80363d56 | |||
| bbb6c6078a | |||
| f7e71718a4 | |||
| af2d61b9f8 | |||
| d6f82000ee | |||
| f4b75bd840 | |||
| 187844f79d | |||
| a4ff19d358 | |||
| e31f1040e1 | |||
| e6e340fa0a | |||
| de23bfac0a | |||
| 261c823b63 | |||
| ce865c1e8a | |||
| 2d60c0bb0e | |||
| 5609a08e99 | |||
| edabee0649 | |||
| 367514ae07 | |||
| 816f14eac7 | |||
| f9540c5423 | |||
| 04ac17c3d5 | |||
| f7e6631c83 | |||
| 20ff66c649 | |||
| 5ef171209b | |||
| 947f0b8bbb | |||
| 7f8811cb2c | |||
| 4112b4b961 |
+7
-2
@@ -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"]
|
||||
|
||||
@@ -2,50 +2,34 @@
|
||||
|
||||
[](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
@@ -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"
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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)))
|
||||
"#
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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");
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
@@ -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))
|
||||
@@ -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))
|
||||
@@ -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)))
|
||||
@@ -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"))
|
||||
@@ -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,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,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
|
||||
|
||||
@@ -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))
|
||||
@@ -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
|
||||
|
||||
Vendored
+27
@@ -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
|
||||
)
|
||||
)
|
||||
Vendored
+19
@@ -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)
|
||||
)
|
||||
)
|
||||
)
|
||||
Vendored
+9
@@ -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)))
|
||||
)
|
||||
)
|
||||
)
|
||||
Vendored
+17
@@ -0,0 +1,17 @@
|
||||
(module
|
||||
(func (export "simple")
|
||||
(if (i32.const 1)
|
||||
(then
|
||||
(loop
|
||||
i32.const 123
|
||||
drop
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(func
|
||||
block
|
||||
end
|
||||
)
|
||||
)
|
||||
Vendored
+18
@@ -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
@@ -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")
|
||||
)
|
||||
)
|
||||
Reference in New Issue
Block a user