5 Commits

Author SHA1 Message Date
Dmitry Sinyavin 10fd3529f2 Add conditional br count 2022-08-22 18:36:42 +02:00
Dmitry Sinyavin 32ae9f8478 Additional metrics 2022-08-19 12:24:52 +02:00
Dmitry Sinyavin 8bfdb41d01 Return detailed stats 2022-08-09 16:01:05 +02:00
Dmitry Sinyavin 659d3bf12c Reworked stack computation 2022-08-04 21:47:32 +02:00
Dmitry Sinyavin 5b2f75a066 Fix param count and rearrange code 2022-08-02 19:29:06 +02:00
59 changed files with 761 additions and 2259 deletions
+35 -50
View File
@@ -11,7 +11,7 @@ env:
jobs: jobs:
rustfmt: rustfmt:
runs-on: "ubuntu_20_64_core" runs-on: "ubuntu-latest"
steps: steps:
- name: Install Rust toolchain - name: Install Rust toolchain
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
@@ -20,9 +20,9 @@ jobs:
toolchain: nightly toolchain: nightly
components: rustfmt components: rustfmt
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Fmt - name: Cargo fmt
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
with: with:
toolchain: nightly toolchain: nightly
@@ -30,7 +30,7 @@ jobs:
args: --all -- --check args: --all -- --check
clippy: clippy:
runs-on: "ubuntu_20_64_core" runs-on: "ubuntu-latest"
steps: steps:
- name: Install Rust toolchain - name: Install Rust toolchain
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
@@ -38,75 +38,60 @@ jobs:
profile: minimal profile: minimal
toolchain: stable toolchain: stable
components: clippy components: clippy
default: true
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Clippy - name: Cargo clippy
uses: actions-rs/clippy-check@v1 uses: actions-rs/cargo@v1
with: with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --all-targets --all-features -- -D warnings
build:
runs-on: "ubuntu_20_64_core"
steps:
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
target: wasm32-unknown-unknown
toolchain: stable toolchain: stable
default: true command: clippy
args: --all-targets --all-features -- -D warnings
- uses: actions/checkout@v4
- name: Cargo build
uses: actions-rs/cargo@v1
with:
command: build
- name: Cargo build (std)
uses: actions-rs/cargo@v1
with:
command: build
args: --all-features
- name: Cargo build (no_std)
uses: actions-rs/cargo@v1
with:
command: build
args: --no-default-features
- name: Cargo build (wasm)
uses: actions-rs/cargo@v1
with:
command: build
args: --no-default-features --target wasm32-unknown-unknown
test: test:
strategy: strategy:
matrix: matrix:
os: ["ubuntu_20_64_core", "macos-latest", "windows-latest"] os: ["ubuntu-latest", "macos-latest", "windows-latest"]
toolchain: ["stable", "nightly"]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- name: Install Rust toolchain - name: Install Rust toolchain
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
with: with:
profile: minimal profile: minimal
toolchain: stable target: wasm32-unknown-unknown
default: true toolchain: ${{ matrix.toolchain }}
- name: Set git to use LF - name: Set git to use LF
run: | run: |
git config --global core.autocrlf false git config --global core.autocrlf false
git config --global core.eol lf git config --global core.eol lf
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Cargo build
uses: actions-rs/cargo@v1
with:
toolchain: ${{ matrix.toolchain }}
command: build
- name: Cargo build (no_std)
uses: actions-rs/cargo@v1
with:
toolchain: ${{ matrix.toolchain }}
command: build
args: --no-default-features
- name: Cargo build (wasm)
uses: actions-rs/cargo@v1
with:
toolchain: ${{ matrix.toolchain }}
command: build
args: --no-default-features --target wasm32-unknown-unknown
- name: Cargo test - name: Cargo test
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
with: with:
toolchain: ${{ matrix.toolchain }}
command: test command: test
args: --all-features args: --all-features
-1
View File
@@ -4,4 +4,3 @@ target
.DS_Store .DS_Store
.idea .idea
.vscode .vscode
*~
-91
View File
@@ -1,91 +0,0 @@
# Benchmarks
## Table of Contents
- [Benchmark Results](#benchmark-results)
- [coremark, instrumented](#coremark,-instrumented)
- [recursive_ok, instrumented](#recursive_ok,-instrumented)
- [fibonacci_recursive, instrumented](#fibonacci_recursive,-instrumented)
- [factorial_recursive, instrumented](#factorial_recursive,-instrumented)
- [count_until, instrumented](#count_until,-instrumented)
- [memory_vec_add, instrumented](#memory_vec_add,-instrumented)
- [wasm_kernel::tiny_keccak, instrumented](#wasm_kernel::tiny_keccak,-instrumented)
- [global_bump, instrumented](#global_bump,-instrumented)
## Instrumented Modules sizes
| fixture | original size | gas metered/host fn | gas metered/mut global | size diff |
|------------------------------|------------------|---------------------|------------------------|-----------|
| recursive_ok.wat | 0 kb | 0 kb (137%) | 0 kb (177%) | +29% |
| count_until.wat | 0 kb | 0 kb (125%) | 0 kb (153%) | +21% |
| global_bump.wat | 0 kb | 0 kb (123%) | 0 kb (145%) | +18% |
| memory-vec-add.wat | 0 kb | 0 kb (116%) | 0 kb (134%) | +15% |
| factorial.wat | 0 kb | 0 kb (125%) | 0 kb (145%) | +15% |
| fibonacci.wat | 0 kb | 0 kb (121%) | 0 kb (134%) | +10% |
| contract_terminate.wasm | 1 kb | 1 kb (110%) | 1 kb (112%) | +2% |
| coremark_minimal.wasm | 7 kb | 8 kb (114%) | 8 kb (115%) | +0% |
| trait_erc20.wasm | 10 kb | 11 kb (108%) | 11 kb (108%) | +0% |
| rand_extension.wasm | 4 kb | 5 kb (109%) | 5 kb (109%) | +0% |
| multisig.wasm | 27 kb | 30 kb (110%) | 30 kb (110%) | +0% |
| wasm_kernel.wasm | 779 kb | 787 kb (100%) | 795 kb (101%) | +0% |
| many_blocks.wasm | 1023 kb | 2389 kb (233%) | 2389 kb (233%) | +0% |
| contract_transfer.wasm | 7 kb | 8 kb (113%) | 8 kb (113%) | +0% |
| erc1155.wasm | 26 kb | 29 kb (111%) | 29 kb (111%) | +0% |
| erc20.wasm | 9 kb | 10 kb (108%) | 10 kb (109%) | +0% |
| dns.wasm | 10 kb | 11 kb (108%) | 11 kb (108%) | +0% |
| proxy.wasm | 3 kb | 4 kb (108%) | 4 kb (109%) | +0% |
| erc721.wasm | 13 kb | 14 kb (108%) | 14 kb (108%) | +0% |
## Benchmark Results
### coremark, instrumented
| | `with host_function::Injector` | `with mutable_global::Injector` |
|:-------|:----------------------------------------|:----------------------------------------- |
| | `20.81 s` (✅ **1.00x**) | `20.20 s` (✅ **1.03x faster**) |
### recursive_ok, instrumented
| | `with host_function::Injector` | `with mutable_global::Injector` |
|:-------|:----------------------------------------|:----------------------------------------- |
| | `367.11 us` (✅ **1.00x**) | `585.39 us` (❌ *1.59x slower*) |
### fibonacci_recursive, instrumented
| | `with host_function::Injector` | `with mutable_global::Injector` |
|:-------|:----------------------------------------|:----------------------------------------- |
| | `9.15 us` (✅ **1.00x**) | `13.56 us` (❌ *1.48x slower*) |
### factorial_recursive, instrumented
| | `with host_function::Injector` | `with mutable_global::Injector` |
|:-------|:----------------------------------------|:----------------------------------------- |
| | `1.50 us` (✅ **1.00x**) | `1.98 us` (❌ *1.32x slower*) |
### count_until, instrumented
| | `with host_function::Injector` | `with mutable_global::Injector` |
|:-------|:----------------------------------------|:----------------------------------------- |
| | `5.03 ms` (✅ **1.00x**) | `8.13 ms` (❌ *1.62x slower*) |
### memory_vec_add, instrumented
| | `with host_function::Injector` | `with mutable_global::Injector` |
|:-------|:----------------------------------------|:----------------------------------------- |
| | `6.21 ms` (✅ **1.00x**) | `8.45 ms` (❌ *1.36x slower*) |
### wasm_kernel::tiny_keccak, instrumented
| | `with host_function::Injector` | `with mutable_global::Injector` |
|:-------|:----------------------------------------|:----------------------------------------- |
| | `925.22 us` (✅ **1.00x**) | `1.08 ms` (❌ *1.17x slower*) |
### global_bump, instrumented
| | `with host_function::Injector` | `with mutable_global::Injector` |
|:-------|:----------------------------------------|:----------------------------------------- |
| | `3.79 ms` (✅ **1.00x**) | `7.03 ms` (❌ *1.86x slower*) |
---
Made with [criterion-table](https://github.com/nu11ptr/criterion-table)
-19
View File
@@ -16,26 +16,7 @@ The interface provided to smart contracts will adhere to semver with one excepti
major version bumps will be backwards compatible with regard to already deployed contracts. major version bumps will be backwards compatible with regard to already deployed contracts.
In other words: Upgrading this pallet will not break pre-existing contracts. In other words: Upgrading this pallet will not break pre-existing contracts.
## [Unreleased]
### New
- Add new gas metering method: mutable global + local gas function
[#34](https://github.com/paritytech/wasm-instrument/pull/34)
- Account for locals initialization costs
[#38](https://github.com/paritytech/wasm-instrument/pull/38)
## [v0.3.0]
### Changed
- Use 64bit arithmetic for per-block gas counter
[#30](https://github.com/paritytech/wasm-instrument/pull/30)
## [v0.2.0] 2022-06-06 ## [v0.2.0] 2022-06-06
### Changed
- Adjust debug information (if already parsed) when injecting gas metering - Adjust debug information (if already parsed) when injecting gas metering
[#16](https://github.com/paritytech/wasm-instrument/pull/16) [#16](https://github.com/paritytech/wasm-instrument/pull/16)
+7 -16
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "wasm-instrument" name = "wasm-instrument"
version = "0.4.0" version = "0.2.0"
edition = "2021" edition = "2021"
rust-version = "1.56.1" rust-version = "1.56.1"
authors = ["Parity Technologies <admin@parity.io>"] authors = ["Parity Technologies <admin@parity.io>"]
@@ -12,14 +12,9 @@ repository = "https://github.com/paritytech/wasm-instrument"
include = ["src/**/*", "LICENSE-*", "README.md"] include = ["src/**/*", "LICENSE-*", "README.md"]
[[bench]] [[bench]]
name = "instrumentation" name = "benches"
harness = false harness = false
path = "benches/instrumentation.rs" path = "benches/benches.rs"
[[bench]]
name = "execution"
harness = false
path = "benches/execution.rs"
[profile.bench] [profile.bench]
lto = "fat" lto = "fat"
@@ -27,22 +22,18 @@ codegen-units = 1
[dependencies] [dependencies]
parity-wasm = { version = "0.45", default-features = false } parity-wasm = { version = "0.45", default-features = false }
log = "0.4"
[dev-dependencies] [dev-dependencies]
binaryen = "0.12" binaryen = "0.12"
criterion = "0.5" criterion = "0.3"
diff = "0.1" diff = "0.1"
pretty_assertions = "1"
rand = "0.8" rand = "0.8"
wat = "1" wat = "1"
wasmparser = "0.206" wasmparser = "0.88"
wasmprinter = "0.200" wasmprinter = "0.2"
wasmi = "0.31"
[features] [features]
default = ["std"] default = ["std"]
std = ["parity-wasm/std"] std = ["parity-wasm/std"]
sign_ext = ["parity-wasm/sign_ext"] sign_ext = ["parity-wasm/sign_ext"]
[lib]
bench = false
@@ -7,8 +7,7 @@ use std::{
path::PathBuf, path::PathBuf,
}; };
use wasm_instrument::{ use wasm_instrument::{
gas_metering::{self, host_function, ConstantCostRules}, gas_metering, inject_stack_limiter,
inject_stack_limiter,
parity_wasm::{deserialize_buffer, elements::Module}, parity_wasm::{deserialize_buffer, elements::Module},
}; };
@@ -16,18 +15,17 @@ fn fixture_dir() -> PathBuf {
let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
path.push("benches"); path.push("benches");
path.push("fixtures"); path.push("fixtures");
path.push("wasm");
path path
} }
fn for_fixtures<F, M>(group: &mut BenchmarkGroup<M>, f: F) fn any_fixture<F, M>(group: &mut BenchmarkGroup<M>, f: F)
where where
F: Fn(Module), F: Fn(Module),
M: Measurement, M: Measurement,
{ {
for entry in read_dir(fixture_dir()).unwrap() { for entry in read_dir(fixture_dir()).unwrap() {
let entry = entry.unwrap(); let entry = entry.unwrap();
let bytes = read(entry.path()).unwrap(); let bytes = read(&entry.path()).unwrap();
group.throughput(Throughput::Bytes(bytes.len().try_into().unwrap())); group.throughput(Throughput::Bytes(bytes.len().try_into().unwrap()));
group.bench_with_input(entry.file_name().to_str().unwrap(), &bytes, |bench, input| { group.bench_with_input(entry.file_name().to_str().unwrap(), &bytes, |bench, input| {
bench.iter(|| f(deserialize_buffer(input).unwrap())) bench.iter(|| f(deserialize_buffer(input).unwrap()))
@@ -37,19 +35,14 @@ where
fn gas_metering(c: &mut Criterion) { fn gas_metering(c: &mut Criterion) {
let mut group = c.benchmark_group("Gas Metering"); let mut group = c.benchmark_group("Gas Metering");
for_fixtures(&mut group, |module| { any_fixture(&mut group, |module| {
gas_metering::inject( gas_metering::inject(module, &gas_metering::ConstantCostRules::default(), "env").unwrap();
module,
host_function::Injector::new("env", "gas"),
&ConstantCostRules::default(),
)
.unwrap();
}); });
} }
fn stack_height_limiter(c: &mut Criterion) { fn stack_height_limiter(c: &mut Criterion) {
let mut group = c.benchmark_group("Stack Height Limiter"); let mut group = c.benchmark_group("Stack Height Limiter");
for_fixtures(&mut group, |module| { any_fixture(&mut group, |module| {
inject_stack_limiter(module, 128).unwrap(); inject_stack_limiter(module, 128).unwrap();
}); });
} }
-437
View File
@@ -1,437 +0,0 @@
use criterion::{
criterion_group, criterion_main, measurement::Measurement, Bencher, BenchmarkGroup, Criterion,
};
use std::{
fs::read,
path::PathBuf,
time::{Duration, SystemTime, UNIX_EPOCH},
};
use wasm_instrument::{
gas_metering::{self, host_function, mutable_global, ConstantCostRules},
parity_wasm::{deserialize_buffer, elements::Module, serialize},
};
use wasmi::{
core::{Pages, TrapCode, F32},
Caller, Config, Engine, Instance, Linker, Memory, StackLimits, Store, TypedFunc, Value,
};
/// Describes a gas metering strategy we want to benchmark.
///
/// Most strategies just need a subset of these functions. Hence we added default
/// implementations for all of them.
trait MeteringStrategy {
/// The wasmi config we should be using for this strategy.
fn config() -> Config {
Config::default()
}
/// The strategy may or may not want to instrument the module.
fn instrument_module(module: Module) -> Module {
module
}
/// The strategy might need to define additional host functions.
fn define_host_funcs(_linker: &mut Linker<u64>) {}
/// The strategy might need to do some initialization of the wasm instance.
fn init_instance(_module: &mut BenchInstance) {}
}
/// Don't do any metering at all. This is helpful as a baseline.
struct NoMetering;
/// Use wasmi's builtin fuel metering.
struct WasmiMetering;
/// Instrument the module using [`host_function::Injector`].
struct HostFunctionMetering;
/// Instrument the module using [`mutable_global::Injector`].
struct MutableGlobalMetering;
impl MeteringStrategy for NoMetering {}
impl MeteringStrategy for WasmiMetering {
fn config() -> Config {
let mut config = Config::default();
config.consume_fuel(true);
config
}
fn init_instance(module: &mut BenchInstance) {
module.store.add_fuel(u64::MAX).unwrap();
}
}
impl MeteringStrategy for HostFunctionMetering {
fn instrument_module(module: Module) -> Module {
let backend = host_function::Injector::new("env", "gas");
gas_metering::inject(module, backend, &ConstantCostRules::default()).unwrap()
}
fn define_host_funcs(linker: &mut Linker<u64>) {
// the instrumentation relies on the existing of this function
linker
.func_wrap("env", "gas", |mut caller: Caller<'_, u64>, amount_consumed: u64| {
let gas_remaining = caller.data_mut();
*gas_remaining =
gas_remaining.checked_sub(amount_consumed).ok_or(TrapCode::OutOfFuel)?;
Ok(())
})
.unwrap();
}
}
impl MeteringStrategy for MutableGlobalMetering {
fn instrument_module(module: Module) -> Module {
let backend = mutable_global::Injector::new("gas_left");
gas_metering::inject(module, backend, &ConstantCostRules::default()).unwrap()
}
fn init_instance(module: &mut BenchInstance) {
// the instrumentation relies on the host to initialize the global with the gas limit
// we just init to the maximum so it will never run out
module
.instance
.get_global(&mut module.store, "gas_left")
.unwrap()
.set(&mut module.store, Value::I64(-1i64)) // the same as u64::MAX
.unwrap();
}
}
/// A wasm instance ready to be benchmarked.
struct BenchInstance {
store: Store<u64>,
instance: Instance,
}
impl BenchInstance {
/// Create a new instance for the supplied metering strategy.
///
/// `wasm`: The raw wasm module for the benchmark.
/// `define_host_func`: In here the caller can define additional host function.
fn new<S, H>(wasm: &[u8], define_host_funcs: &H) -> Self
where
S: MeteringStrategy,
H: Fn(&mut Linker<u64>),
{
let module = deserialize_buffer(wasm).unwrap();
let instrumented_module = S::instrument_module(module);
let input = serialize(instrumented_module).unwrap();
let mut config = S::config();
config.set_stack_limits(StackLimits::new(1024, 1024 * 1024, 64 * 1024).unwrap());
let engine = Engine::new(&config);
let module = wasmi::Module::new(&engine, &mut &input[..]).unwrap();
let mut linker = Linker::new(&engine);
S::define_host_funcs(&mut linker);
define_host_funcs(&mut linker);
// init host state with maximum gas_left (only used by host_function instrumentation)
let mut store = Store::new(&engine, u64::MAX);
let instance = linker.instantiate(&mut store, &module).unwrap().start(&mut store).unwrap();
let mut bench_module = Self { store, instance };
S::init_instance(&mut bench_module);
bench_module
}
}
/// Runs a benchmark for every strategy.
///
/// We require the closures to implement `Fn` as they are executed for every strategy and we
/// don't want them to change in between.
///
/// `group`: The benchmark group within the benchmarks will be executed.
/// `wasm`: The raw wasm module for the benchmark.
/// `define_host_func`: In here the caller can define additional host function.
/// `f`: In here the user should perform the benchmark. Will be executed for every strategy.
fn for_strategies<M, H, F>(group: &mut BenchmarkGroup<M>, wasm: &[u8], define_host_funcs: H, f: F)
where
M: Measurement,
H: Fn(&mut Linker<u64>),
F: Fn(&mut Bencher<M>, &mut BenchInstance),
{
let mut module = BenchInstance::new::<NoMetering, _>(wasm, &define_host_funcs);
group.bench_function("no_metering", |bench| f(bench, &mut module));
let mut module = BenchInstance::new::<WasmiMetering, _>(wasm, &define_host_funcs);
group.bench_function("wasmi_builtin", |bench| f(bench, &mut module));
let mut module = BenchInstance::new::<HostFunctionMetering, _>(wasm, &define_host_funcs);
group.bench_function("host_function", |bench| f(bench, &mut module));
let mut module = BenchInstance::new::<MutableGlobalMetering, _>(wasm, &define_host_funcs);
group.bench_function("mutable_global", |bench| f(bench, &mut module));
}
/// Converts the `.wat` encoded `bytes` into `.wasm` encoded bytes.
fn wat2wasm(bytes: &[u8]) -> Vec<u8> {
wat::parse_bytes(bytes).unwrap().into_owned()
}
fn fixture_dir() -> PathBuf {
let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
path.push("benches");
path.push("fixtures");
path.push("wasm");
path
}
fn gas_metered_coremark(c: &mut Criterion) {
let mut group = c.benchmark_group("coremark");
// Benchmark host_function::Injector
let wasm_filename = "coremark_minimal.wasm";
let bytes = read(fixture_dir().join(wasm_filename)).unwrap();
let define_host_funcs = |linker: &mut Linker<u64>| {
linker
.func_wrap("env", "clock_ms", || {
SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() as u64
})
.unwrap();
};
for_strategies(&mut group, &bytes, define_host_funcs, |bench, module| {
bench.iter(|| {
let run = module.instance.get_typed_func::<(), F32>(&mut module.store, "run").unwrap();
// Call the wasm!
run.call(&mut module.store, ()).unwrap();
})
});
}
fn gas_metered_recursive_ok(c: &mut Criterion) {
let mut group = c.benchmark_group("recursive_ok");
const RECURSIVE_DEPTH: i32 = 8000;
let wasm_bytes = wat2wasm(include_bytes!("fixtures/wat/recursive_ok.wat"));
for_strategies(
&mut group,
&wasm_bytes,
|_| {},
|bench, module| {
let bench_call =
module.instance.get_typed_func::<i32, i32>(&module.store, "call").unwrap();
bench.iter(|| {
let result = bench_call.call(&mut module.store, RECURSIVE_DEPTH).unwrap();
assert_eq!(result, 0);
})
},
);
}
fn gas_metered_fibonacci_recursive(c: &mut Criterion) {
let mut group = c.benchmark_group("fibonacci_recursive");
const FIBONACCI_REC_N: i64 = 10;
let wasm_bytes = wat2wasm(include_bytes!("fixtures/wat/fibonacci.wat"));
for_strategies(
&mut group,
&wasm_bytes,
|_| {},
|bench, module| {
let bench_call = module
.instance
.get_typed_func::<i64, i64>(&module.store, "fib_recursive")
.unwrap();
bench.iter(|| {
bench_call.call(&mut module.store, FIBONACCI_REC_N).unwrap();
});
},
);
}
fn gas_metered_fac_recursive(c: &mut Criterion) {
let mut group = c.benchmark_group("factorial_recursive");
let wasm_bytes = wat2wasm(include_bytes!("fixtures/wat/factorial.wat"));
for_strategies(
&mut group,
&wasm_bytes,
|_| {},
|bench, module| {
let fac = module
.instance
.get_typed_func::<i64, i64>(&module.store, "recursive_factorial")
.unwrap();
bench.iter(|| {
let result = fac.call(&mut module.store, 25).unwrap();
assert_eq!(result, 7034535277573963776);
})
},
);
}
fn gas_metered_count_until(c: &mut Criterion) {
const COUNT_UNTIL: i32 = 100_000;
let mut group = c.benchmark_group("count_until");
let wasm_bytes = wat2wasm(include_bytes!("fixtures/wat/count_until.wat"));
for_strategies(
&mut group,
&wasm_bytes,
|_| {},
|bench, module| {
let count_until = module
.instance
.get_typed_func::<i32, i32>(&module.store, "count_until")
.unwrap();
bench.iter(|| {
let result = count_until.call(&mut module.store, COUNT_UNTIL).unwrap();
assert_eq!(result, COUNT_UNTIL);
})
},
);
}
fn gas_metered_vec_add(c: &mut Criterion) {
fn test_for<A, B>(
b: &mut Bencher,
vec_add: TypedFunc<(i32, i32, i32, i32), ()>,
mut store: &mut Store<u64>,
mem: Memory,
len: usize,
vec_a: A,
vec_b: B,
) where
A: IntoIterator<Item = i32>,
B: IntoIterator<Item = i32>,
{
use core::mem::size_of;
let ptr_result = 10;
let len_result = len * size_of::<i64>();
let ptr_a = ptr_result + len_result;
let len_a = len * size_of::<i32>();
let ptr_b = ptr_a + len_a;
// Reset `result` buffer to zeros:
mem.data_mut(&mut store)[ptr_result..ptr_result + (len * size_of::<i32>())].fill(0);
// Initialize `a` buffer:
for (n, a) in vec_a.into_iter().take(len).enumerate() {
mem.write(&mut store, ptr_a + (n * size_of::<i32>()), &a.to_le_bytes()).unwrap();
}
// Initialize `b` buffer:
for (n, b) in vec_b.into_iter().take(len).enumerate() {
mem.write(&mut store, ptr_b + (n * size_of::<i32>()), &b.to_le_bytes()).unwrap();
}
// Prepare parameters and all Wasm `vec_add`:
let params = (ptr_result as i32, ptr_a as i32, ptr_b as i32, len as i32);
b.iter(|| {
vec_add.call(&mut store, params).unwrap();
});
// Validate the result buffer:
for n in 0..len {
let mut buffer4 = [0x00; 4];
let mut buffer8 = [0x00; 8];
let a = {
mem.read(&store, ptr_a + (n * size_of::<i32>()), &mut buffer4).unwrap();
i32::from_le_bytes(buffer4)
};
let b = {
mem.read(&store, ptr_b + (n * size_of::<i32>()), &mut buffer4).unwrap();
i32::from_le_bytes(buffer4)
};
let actual_result = {
mem.read(&store, ptr_result + (n * size_of::<i64>()), &mut buffer8).unwrap();
i64::from_le_bytes(buffer8)
};
let expected_result = (a as i64) + (b as i64);
assert_eq!(
expected_result, actual_result,
"given a = {a} and b = {b}, results diverge at index {n}"
);
}
}
let mut group = c.benchmark_group("memory_vec_add");
let wasm_bytes = wat2wasm(include_bytes!("fixtures/wat/memory-vec-add.wat"));
const LEN: usize = 100_000;
for_strategies(
&mut group,
&wasm_bytes,
|_| {},
|bench, module| {
let vec_add = module
.instance
.get_typed_func::<(i32, i32, i32, i32), ()>(&module.store, "vec_add")
.unwrap();
let mem = module.instance.get_memory(&module.store, "mem").unwrap();
mem.grow(&mut module.store, Pages::new(25).unwrap()).unwrap();
test_for(
bench,
vec_add,
&mut module.store,
mem,
LEN,
(0..LEN).map(|i| (i * i) as i32),
(0..LEN).map(|i| (i * 10) as i32),
)
},
);
}
fn gas_metered_tiny_keccak(c: &mut Criterion) {
let mut group = c.benchmark_group("wasm_kernel::tiny_keccak");
let wasm_filename = "wasm_kernel.wasm";
let wasm_bytes = read(fixture_dir().join(wasm_filename)).unwrap();
for_strategies(
&mut group,
&wasm_bytes,
|_| {},
|bench, module| {
let prepare = module
.instance
.get_typed_func::<(), i32>(&module.store, "prepare_tiny_keccak")
.unwrap();
let keccak = module
.instance
.get_typed_func::<i32, ()>(&module.store, "bench_tiny_keccak")
.unwrap();
let test_data_ptr = prepare.call(&mut module.store, ()).unwrap();
bench.iter(|| {
keccak.call(&mut module.store, test_data_ptr).unwrap();
})
},
);
}
fn gas_metered_global_bump(c: &mut Criterion) {
const BUMP_AMOUNT: i32 = 100_000;
let mut group = c.benchmark_group("global_bump");
let wasm_bytes = wat2wasm(include_bytes!("fixtures/wat/global_bump.wat"));
for_strategies(
&mut group,
&wasm_bytes,
|_| {},
|bench, module| {
let bump = module.instance.get_typed_func::<i32, i32>(&module.store, "bump").unwrap();
bench.iter(|| {
let result = bump.call(&mut module.store, BUMP_AMOUNT).unwrap();
assert_eq!(result, BUMP_AMOUNT);
})
},
);
}
criterion_group!(
name = coremark;
config = Criterion::default()
.sample_size(10)
.measurement_time(Duration::from_millis(275000))
.warm_up_time(Duration::from_millis(1000));
targets =
gas_metered_coremark,
);
criterion_group!(
name = wasmi_fixtures;
config = Criterion::default()
.sample_size(10)
.measurement_time(Duration::from_millis(250000))
.warm_up_time(Duration::from_millis(1000));
targets =
gas_metered_recursive_ok,
gas_metered_fibonacci_recursive,
gas_metered_fac_recursive,
gas_metered_count_until,
gas_metered_vec_add,
gas_metered_tiny_keccak,
gas_metered_global_bump,
);
criterion_main!(coremark, wasmi_fixtures);
Binary file not shown.
Binary file not shown.
-25
View File
@@ -1,25 +0,0 @@
;; Exports a function `count_until` that takes an input `n`.
;; The exported function counts an integer `n` times and then returns `n`.
(module
(func $count_until (export "count_until") (param $limit i32) (result i32)
(local $counter i32)
(block
(loop
(br_if
1
(i32.eq
(local.tee $counter
(i32.add
(local.get $counter)
(i32.const 1)
)
)
(local.get $limit)
)
)
(br 0)
)
)
(return (local.get $counter))
)
)
-35
View File
@@ -1,35 +0,0 @@
(module
;; Iterative factorial function, does not use recursion.
(func (export "iterative_factorial") (param i64) (result i64)
(local i64)
(local.set 1 (i64.const 1))
(block
(br_if 0 (i64.lt_s (local.get 0) (i64.const 2)))
(loop
(local.set 1 (i64.mul (local.get 1) (local.get 0)))
(local.set 0 (i64.add (local.get 0) (i64.const -1)))
(br_if 0 (i64.gt_s (local.get 0) (i64.const 1)))
)
)
(local.get 1)
)
;; Recursive trivial factorial function.
(func $rec_fac (export "recursive_factorial") (param i64) (result i64)
(if (result i64)
(i64.eq (local.get 0) (i64.const 0))
(then (i64.const 1))
(else
(i64.mul
(local.get 0)
(call $rec_fac
(i64.sub
(local.get 0)
(i64.const 1)
)
)
)
)
)
)
)
-47
View File
@@ -1,47 +0,0 @@
(module
(func $fib_recursive (export "fib_recursive") (param $N i64) (result i64)
(if
(i64.le_s (local.get $N) (i64.const 1))
(then (return (local.get $N)))
)
(return
(i64.add
(call $fib_recursive
(i64.sub (local.get $N) (i64.const 1))
)
(call $fib_recursive
(i64.sub (local.get $N) (i64.const 2))
)
)
)
)
(func $fib_iterative (export "fib_iterative") (param $N i64) (result i64)
(local $n1 i64)
(local $n2 i64)
(local $tmp i64)
(local $i i64)
;; return $N for N <= 1
(if
(i64.le_s (local.get $N) (i64.const 1))
(then (return (local.get $N)))
)
(local.set $n1 (i64.const 1))
(local.set $n2 (i64.const 1))
(local.set $i (i64.const 2))
;;since we normally return n2, handle n=1 case specially
(loop $again
(if
(i64.lt_s (local.get $i) (local.get $N))
(then
(local.set $tmp (i64.add (local.get $n1) (local.get $n2)))
(local.set $n1 (local.get $n2))
(local.set $n2 (local.get $tmp))
(local.set $i (i64.add (local.get $i) (i64.const 1)))
(br $again)
)
)
)
(local.get $n2)
)
)
-27
View File
@@ -1,27 +0,0 @@
;; Exports a function `bump` that takes an input `n`.
;; The exported function bumps a global variable `n` times and then returns it.
(module
(global $g (mut i32) (i32.const 0))
(func $bump (export "bump") (param $n i32) (result i32)
(global.set $g (i32.const 0))
(block $break
(loop $continue
(br_if ;; if $g == $n then break
$break
(i32.eq
(global.get $g)
(local.get $n)
)
)
(global.set $g ;; $g += 1
(i32.add
(global.get $g)
(i32.const 1)
)
)
(br $continue)
)
)
(return (global.get $g))
)
)
-58
View File
@@ -1,58 +0,0 @@
;; Exports a function `vec_add` that computes the addition of 2 vectors
;; of length `len` starting at `ptr_a` and `ptr_b` and stores the result
;; into a buffer of the same length starting at `ptr_result`.
(module
(memory (export "mem") 1)
(func (export "vec_add")
(param $ptr_result i32)
(param $ptr_a i32)
(param $ptr_b i32)
(param $len i32)
(local $n i32)
(block $exit
(loop $loop
(br_if ;; exit loop if $n == $len
$exit
(i32.eq
(local.get $n)
(local.get $len)
)
)
(i64.store offset=0 ;; ptr_result[n] = ptr_a[n] + ptr_b[n]
(i32.add
(local.get $ptr_result)
(i32.mul
(local.get $n)
(i32.const 8)
)
)
(i64.add
(i64.load32_s offset=0 ;; load ptr_a[n]
(i32.add
(local.get $ptr_a)
(i32.mul
(local.get $n)
(i32.const 4)
)
)
)
(i64.load32_s offset=0 ;; load ptr_b[n]
(i32.add
(local.get $ptr_b)
(i32.mul
(local.get $n)
(i32.const 4)
)
)
)
)
)
(local.set $n ;; increment n
(i32.add (local.get $n) (i32.const 1))
)
(br $loop) ;; continue loop
)
)
(return)
)
)
-22
View File
@@ -1,22 +0,0 @@
;; Exports a function `call` that takes an input `n`.
;; The exported function calls itself `n` times.
(module
(func $call (export "call") (param $n i32) (result i32)
(if (result i32)
(local.get $n)
(then
(return
(call $call
(i32.sub
(local.get $n)
(i32.const 1)
)
)
)
)
(else
(return (local.get $n))
)
)
)
)
-139
View File
@@ -1,139 +0,0 @@
//! Provides backends for the gas metering instrumentation
use parity_wasm::elements;
/// Implementation details of the specific method of the gas metering.
#[derive(Clone)]
pub enum GasMeter {
/// Gas metering with an external function.
External {
/// Name of the module to import the gas function from.
module: &'static str,
/// Name of the external gas function to be imported.
function: &'static str,
},
/// Gas metering with a local function and a mutable global.
Internal {
/// Name of the mutable global to be exported.
global: &'static str,
/// Body of the local gas counting function to be injected.
func_instructions: elements::Instructions,
/// Cost of the gas function execution.
cost: u64,
},
}
use super::Rules;
/// Under the hood part of the gas metering mechanics.
pub trait Backend {
/// Provides the gas metering implementation details.
fn gas_meter<R: Rules>(self, module: &elements::Module, rules: &R) -> GasMeter;
}
/// Gas metering with an external host function.
pub mod host_function {
use super::{Backend, GasMeter, Rules};
use parity_wasm::elements::Module;
/// Injects invocations of the gas charging host function into each metering block.
pub struct Injector {
/// The name of the module to import the gas function from.
module: &'static str,
/// The name of the gas function to import.
name: &'static str,
}
impl Injector {
pub fn new(module: &'static str, name: &'static str) -> Self {
Self { module, name }
}
}
impl Backend for Injector {
fn gas_meter<R: Rules>(self, _module: &Module, _rules: &R) -> GasMeter {
GasMeter::External { module: self.module, function: self.name }
}
}
}
/// Gas metering with a mutable global.
///
/// # Note
///
/// Not for all execution engines this method gives performance wins compared to using an [external
/// host function](host_function). See benchmarks and size overhead tests for examples of how to
/// make measurements needed to decide which gas metering method is better for your particular case.
///
/// # Warning
///
/// It is not recommended to apply [stack limiter](crate::inject_stack_limiter) instrumentation to a
/// module instrumented with this type of gas metering. This could lead to a massive module size
/// bloat. This is a known issue to be fixed in upcoming versions.
pub mod mutable_global {
use super::{Backend, GasMeter, Rules};
use alloc::vec;
use parity_wasm::elements::{self, Instruction, Module};
/// Injects a mutable global variable and a local function to the module to track
/// current gas left.
///
/// The function is called in every metering block. In case of falling out of gas, the global is
/// set to the sentinel value `U64::MAX` and `unreachable` instruction is called. The execution
/// engine should take care of getting the current global value and setting it back in order to
/// sync the gas left value during an execution.
pub struct Injector {
/// The export name of the gas tracking global.
pub global_name: &'static str,
}
impl Injector {
pub fn new(global_name: &'static str) -> Self {
Self { global_name }
}
}
impl Backend for Injector {
fn gas_meter<R: Rules>(self, module: &Module, rules: &R) -> GasMeter {
let gas_global_idx = module.globals_space() as u32;
let func_instructions = vec![
Instruction::GetGlobal(gas_global_idx),
Instruction::GetLocal(0),
Instruction::I64GeU,
Instruction::If(elements::BlockType::NoResult),
Instruction::GetGlobal(gas_global_idx),
Instruction::GetLocal(0),
Instruction::I64Sub,
Instruction::SetGlobal(gas_global_idx),
Instruction::Else,
// sentinel val u64::MAX
Instruction::I64Const(-1i64), // non-charged instruction
Instruction::SetGlobal(gas_global_idx), // non-charged instruction
Instruction::Unreachable, // non-charged instruction
Instruction::End,
Instruction::End,
];
// calculate gas used for the gas charging func execution itself
let mut gas_fn_cost = func_instructions.iter().fold(0, |cost: u64, instruction| {
cost.saturating_add(rules.instruction_cost(instruction).unwrap_or(u32::MAX).into())
});
// don't charge for the instructions used to fail when out of gas
let fail_cost = [
Instruction::I64Const(-1i64), // non-charged instruction
Instruction::SetGlobal(gas_global_idx), // non-charged instruction
Instruction::Unreachable, // non-charged instruction
]
.iter()
.fold(0, |cost: u64, instruction| {
cost.saturating_add(rules.instruction_cost(instruction).unwrap_or(u32::MAX).into())
});
// the fail costs are a subset of the overall costs and hence this never underflows
gas_fn_cost -= fail_cost;
GasMeter::Internal {
global: self.global_name,
func_instructions: elements::Instructions::new(func_instructions),
cost: gas_fn_cost,
}
}
}
}
+224 -536
View File
File diff suppressed because it is too large Load Diff
+8 -22
View File
@@ -23,10 +23,10 @@ struct ControlFlowNode {
first_instr_pos: Option<usize>, first_instr_pos: Option<usize>,
/// The actual gas cost of executing all instructions in the basic block. /// The actual gas cost of executing all instructions in the basic block.
actual_cost: u64, actual_cost: u32,
/// The amount of gas charged by the injected metering instructions within this basic block. /// The amount of gas charged by the injected metering instructions within this basic block.
charged_cost: u64, charged_cost: u32,
/// Whether there are any other nodes in the graph that loop back to this one. Every cycle in /// 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. /// the control flow graph contains at least one node with this flag set.
@@ -68,10 +68,10 @@ impl ControlFlowGraph {
} }
fn increment_actual_cost(&mut self, node_id: NodeId, cost: u32) { fn increment_actual_cost(&mut self, node_id: NodeId, cost: u32) {
self.get_node_mut(node_id).actual_cost += u64::from(cost); self.get_node_mut(node_id).actual_cost += cost;
} }
fn increment_charged_cost(&mut self, node_id: NodeId, cost: u64) { fn increment_charged_cost(&mut self, node_id: NodeId, cost: u32) {
self.get_node_mut(node_id).charged_cost += cost; self.get_node_mut(node_id).charged_cost += cost;
} }
@@ -134,14 +134,6 @@ fn build_control_flow_graph(
let mut stack = vec![ControlFrame::new(entry_node_id, terminal_node_id, false)]; let mut stack = vec![ControlFrame::new(entry_node_id, terminal_node_id, false)];
let mut metered_blocks_iter = blocks.iter().peekable(); let mut metered_blocks_iter = blocks.iter().peekable();
let locals_count = body
.locals()
.iter()
.try_fold(0u32, |count, val_type| count.checked_add(val_type.count()))
.ok_or(())?;
let locals_init_cost = rules.call_per_local_cost().checked_mul(locals_count).ok_or(())?;
for (cursor, instruction) in body.code().elements().iter().enumerate() { for (cursor, instruction) in body.code().elements().iter().enumerate() {
let active_node_id = stack let active_node_id = stack
.last() .last()
@@ -157,10 +149,6 @@ fn build_control_flow_graph(
graph.increment_charged_cost(active_node_id, next_metered_block.cost); graph.increment_charged_cost(active_node_id, next_metered_block.cost);
} }
// Add locals initialization cost to the function block.
if cursor == 0 {
graph.increment_actual_cost(active_node_id, locals_init_cost);
}
let instruction_cost = rules.instruction_cost(instruction).ok_or(())?; let instruction_cost = rules.instruction_cost(instruction).ok_or(())?;
match instruction { match instruction {
Instruction::Block(_) => { Instruction::Block(_) => {
@@ -279,9 +267,9 @@ fn validate_graph_gas_costs(graph: &ControlFlowGraph) -> bool {
fn visit( fn visit(
graph: &ControlFlowGraph, graph: &ControlFlowGraph,
node_id: NodeId, node_id: NodeId,
mut total_actual: u64, mut total_actual: u32,
mut total_charged: u64, mut total_charged: u32,
loop_costs: &mut Map<NodeId, (u64, u64)>, loop_costs: &mut Map<NodeId, (u32, u32)>,
) -> bool { ) -> bool {
let node = graph.get_node(node_id); let node = graph.get_node(node_id);
@@ -354,10 +342,8 @@ mod tests {
for func_body in module.code_section().iter().flat_map(|section| section.bodies()) { for func_body in module.code_section().iter().flat_map(|section| section.bodies()) {
let rules = ConstantCostRules::default(); let rules = ConstantCostRules::default();
let locals_count = func_body.locals().iter().map(|val_type| val_type.count()).sum();
let metered_blocks = let metered_blocks = determine_metered_blocks(func_body.code(), &rules).unwrap();
determine_metered_blocks(func_body.code(), &rules, locals_count).unwrap();
let success = let success =
validate_metering_injections(func_body, &rules, &metered_blocks).unwrap(); validate_metering_injections(func_body, &rules, &metered_blocks).unwrap();
assert!(success); assert!(success);
+3 -1
View File
@@ -1,6 +1,8 @@
#![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc; extern crate alloc;
#[macro_use]
extern crate log;
mod export_globals; mod export_globals;
pub mod gas_metering; pub mod gas_metering;
@@ -8,4 +10,4 @@ mod stack_limiter;
pub use export_globals::export_mutable_globals; pub use export_globals::export_mutable_globals;
pub use parity_wasm; pub use parity_wasm;
pub use stack_limiter::inject as inject_stack_limiter; pub use stack_limiter::{compute_stack_cost, inject as inject_stack_limiter};
+304 -203
View File
@@ -9,14 +9,28 @@ use parity_wasm::elements::SignExtInstruction;
// is a static cost that is added to each function call. This makes sense because even // is a static cost that is added to each function call. This makes sense because even
// if a function does not use any parameters or locals some stack space on the host // if a function does not use any parameters or locals some stack space on the host
// machine might be consumed to hold some context. // machine might be consumed to hold some context.
const ACTIVATION_FRAME_COST: u32 = 2; const ACTIVATION_FRAME_COST: u32 = 1;
#[derive(Debug,PartialEq,Default,Clone,Copy)]
pub struct StackHeightStats {
pub activation_cost: u32,
pub max_height: u32,
pub max_control_height: u32,
pub locals_count: u32,
pub params_count: u32,
pub blocks_count: u32,
pub condbr_count: u32,
pub push_count: u32,
pub local_set_count: u32,
pub opcode_count: u32,
pub total_cost: u32,
}
/// Control stack frame. /// Control stack frame.
#[derive(Debug)] #[derive(Debug)]
struct Frame { struct Frame {
/// Stack becomes polymorphic only after an instruction that /// Counts the nesting level of unreachable code. 0 if currently processed code is reachable
/// never passes control further was executed. unreachable_depth: u32,
is_polymorphic: bool,
/// Count of values which will be pushed after the exit /// Count of values which will be pushed after the exit
/// from the current block. /// from the current block.
@@ -42,7 +56,7 @@ struct Stack {
impl Stack { impl Stack {
fn new() -> Stack { fn new() -> Stack {
Stack { height: ACTIVATION_FRAME_COST, control_stack: Vec::new() } Stack { height: 0, control_stack: Vec::new() }
} }
/// Returns current height of the value stack. /// Returns current height of the value stack.
@@ -50,6 +64,10 @@ impl Stack {
self.height self.height
} }
fn control_height(&self) -> u32 {
self.control_stack.len() as u32
}
/// Returns a reference to a frame by specified depth relative to the top of /// Returns a reference to a frame by specified depth relative to the top of
/// control stack. /// control stack.
fn frame(&self, rel_depth: u32) -> Result<&Frame, &'static str> { fn frame(&self, rel_depth: u32) -> Result<&Frame, &'static str> {
@@ -60,14 +78,27 @@ impl Stack {
} }
/// Mark successive instructions as unreachable. /// Mark successive instructions as unreachable.
///
/// This effectively makes stack polymorphic.
fn mark_unreachable(&mut self) -> Result<(), &'static str> { fn mark_unreachable(&mut self) -> Result<(), &'static str> {
let top_frame = self.control_stack.last_mut().ok_or("stack must be non-empty")?; let top_frame = self.control_stack.last_mut().ok_or("control stack must be non-empty")?;
top_frame.is_polymorphic = true; top_frame.unreachable_depth = 1;
Ok(()) Ok(())
} }
/// Increase nesting level of unreachable code
fn push_unreachable(&mut self) -> Result<(), &'static str> {
let top_frame = self.control_stack.last_mut().ok_or("control stack must be non-empty")?;
top_frame.unreachable_depth += 1;
Ok(())
}
/// Decrease nesting level of unrechable code (probably making it reachable)
fn pop_unreachable(&mut self) -> Result<u32, &'static str> {
let top_frame = self.control_stack.last_mut().ok_or("control stack must be non-empty")?;
top_frame.unreachable_depth =
top_frame.unreachable_depth.checked_sub(1).ok_or("unreachable code underflow")?;
Ok(top_frame.unreachable_depth)
}
/// Push control frame into the control stack. /// Push control frame into the control stack.
fn push_frame(&mut self, frame: Frame) { fn push_frame(&mut self, frame: Frame) {
self.control_stack.push(frame); self.control_stack.push(frame);
@@ -101,19 +132,6 @@ impl Stack {
if value_count == 0 { if value_count == 0 {
return Ok(()) return Ok(())
} }
{
let top_frame = self.frame(0)?;
if self.height == top_frame.start_height {
// It is an error to pop more values than was pushed in the current frame
// (ie pop values pushed in the parent frame), unless the frame became
// polymorphic.
return if top_frame.is_polymorphic {
Ok(())
} else {
return Err("trying to pop more values than pushed")
}
}
}
self.height = self.height.checked_sub(value_count).ok_or("stack underflow")?; self.height = self.height.checked_sub(value_count).ok_or("stack underflow")?;
@@ -122,9 +140,11 @@ impl Stack {
} }
/// This function expects the function to be validated. /// This function expects the function to be validated.
pub fn compute(func_idx: u32, module: &elements::Module) -> Result<u32, &'static str> { pub fn compute(func_idx: u32, module: &elements::Module) -> Result<StackHeightStats, &'static str> {
use parity_wasm::elements::Instruction::*; use parity_wasm::elements::Instruction::*;
trace!("Processing function index {}", func_idx);
let func_section = module.function_section().ok_or("No function section")?; let func_section = module.function_section().ok_or("No function section")?;
let code_section = module.code_section().ok_or("No code section")?; let code_section = module.code_section().ok_or("No code section")?;
let type_section = module.type_section().ok_or("No type section")?; let type_section = module.type_section().ok_or("No type section")?;
@@ -147,51 +167,87 @@ pub fn compute(func_idx: u32, module: &elements::Module) -> Result<u32, &'static
let mut stack = Stack::new(); let mut stack = Stack::new();
let mut max_height: u32 = 0; let mut max_height: u32 = 0;
let mut pc = 0; let mut max_control_height: u32 = 0;
let mut locals_count: u32 = 0;
for local_group in body.locals() {
locals_count =
locals_count.checked_add(local_group.count()).ok_or("Overflow in local count")?;
}
let params_count = func_signature.params().len() as u32;
let mut blocks_count = 0u32;
let mut condbr_count = 0u32;
let mut push_count = 0u32;
let mut local_set_count = 0u32;
// Add implicit frame for the function. Breaks to this frame and execution of // Add implicit frame for the function. Breaks to this frame and execution of
// the last end should deal with this frame. // the last end should deal with this frame.
let func_arity = func_signature.results().len() as u32; let func_arity = func_signature.results().len() as u32;
stack.push_frame(Frame { stack.push_frame(Frame {
is_polymorphic: false, unreachable_depth: 0,
end_arity: func_arity, end_arity: func_arity,
branch_arity: func_arity, branch_arity: func_arity,
start_height: 0, start_height: 0,
}); });
loop { for opcode in instructions.elements() {
if pc >= instructions.elements().len() { if stack.frame(0)?.unreachable_depth > 0 {
break match opcode {
Block(_) | Loop(_) | If(_) => {
trace!("Entering unreachable block {:?}", opcode);
stack.push_unreachable()?;
continue
},
Else => {
let depth = stack.pop_unreachable()?;
if depth == 0 {
trace!("Transiting from unreachable If body to reachable Else block");
} else {
trace!("Processing unreachable Else");
stack.push_unreachable()?;
continue
}
},
End => {
let depth = stack.pop_unreachable()?;
if depth == 0 {
trace!("Exiting unreachable code");
} else {
trace!("Exiting unreachable block");
continue
}
},
_ => {
trace!("Skipping unreachable instruction {:?}", opcode);
continue
},
}
} }
// If current value stack is higher than maximal height observed so far, assert_eq!(stack.frame(0)?.unreachable_depth, 0);
// save the new height. trace!("Processing opcode {:?}", opcode);
// However, we don't increase maximal value in unreachable code.
if stack.height() > max_height && !stack.frame(0)?.is_polymorphic {
max_height = stack.height();
}
let opcode = &instructions.elements()[pc];
match opcode { match opcode {
Nop => {}, Nop => {},
Block(ty) | Loop(ty) | If(ty) => { Block(ty) | Loop(ty) | If(ty) => {
let end_arity = u32::from(*ty != BlockType::NoResult); let end_arity = if *ty == BlockType::NoResult { 0 } else { 1 };
let branch_arity = if let Loop(_) = *opcode { 0 } else { end_arity }; let branch_arity = if let Loop(_) = *opcode { 0 } else { end_arity };
if let If(_) = *opcode { if let If(_) = *opcode {
stack.pop_values(1)?; stack.pop_values(1)?;
} }
let height = stack.height();
stack.push_frame(Frame { stack.push_frame(Frame {
is_polymorphic: false, unreachable_depth: 0,
end_arity, end_arity,
branch_arity, branch_arity,
start_height: height, start_height: stack.height(),
}); });
blocks_count += 1;
}, },
Else => { Else => {
// The frame at the top should be pushed by `If`. So we leave let frame = stack.pop_frame()?;
// it as is. stack.trunc(frame.start_height);
stack.push_frame(frame);
}, },
End => { End => {
let frame = stack.pop_frame()?; let frame = stack.pop_frame()?;
@@ -220,18 +276,12 @@ pub fn compute(func_idx: u32, module: &elements::Module) -> Result<u32, &'static
// Push values back. // Push values back.
stack.push_values(target_arity)?; stack.push_values(target_arity)?;
condbr_count += 1;
}, },
BrTable(br_table_data) => { BrTable(br_table_data) => {
let arity_of_default = stack.frame(br_table_data.default)?.branch_arity; let arity_of_default = stack.frame(br_table_data.default)?.branch_arity;
// Check that all jump targets have an equal arities.
for target in &*br_table_data.table {
let arity = stack.frame(*target)?.branch_arity;
if arity != arity_of_default {
return Err("Arity of all jump-targets must be equal")
}
}
// Because all jump targets have an equal arities, we can just take arity of // Because all jump targets have an equal arities, we can just take arity of
// the default branch. // the default branch.
stack.pop_values(arity_of_default)?; stack.pop_values(arity_of_default)?;
@@ -239,6 +289,8 @@ pub fn compute(func_idx: u32, module: &elements::Module) -> Result<u32, &'static
// This instruction doesn't let control flow to go further, since the control flow // This instruction doesn't let control flow to go further, since the control flow
// should take either one of branches depending on the value or the default branch. // should take either one of branches depending on the value or the default branch.
stack.mark_unreachable()?; stack.mark_unreachable()?;
condbr_count += 1;
}, },
Return => { Return => {
// Pop return values of the function. Mark successive instructions as unreachable // Pop return values of the function. Mark successive instructions as unreachable
@@ -280,21 +332,26 @@ pub fn compute(func_idx: u32, module: &elements::Module) -> Result<u32, &'static
// Push the selected value. // Push the selected value.
stack.push_values(1)?; stack.push_values(1)?;
push_count += 1;
}, },
GetLocal(_) => { GetLocal(_) => {
stack.push_values(1)?; stack.push_values(1)?;
push_count += 1;
}, },
SetLocal(_) => { SetLocal(_) => {
stack.pop_values(1)?; stack.pop_values(1)?;
local_set_count += 1;
}, },
TeeLocal(_) => { TeeLocal(_) => {
// This instruction pops and pushes the value, so // This instruction pops and pushes the value, so
// effectively it doesn't modify the stack height. // effectively it doesn't modify the stack height.
stack.pop_values(1)?; stack.pop_values(1)?;
stack.push_values(1)?; stack.push_values(1)?;
local_set_count += 1;
}, },
GetGlobal(_) => { GetGlobal(_) => {
stack.push_values(1)?; stack.push_values(1)?;
push_count += 1;
}, },
SetGlobal(_) => { SetGlobal(_) => {
stack.pop_values(1)?; stack.pop_values(1)?;
@@ -317,6 +374,7 @@ pub fn compute(func_idx: u32, module: &elements::Module) -> Result<u32, &'static
// which effictively don't modify the stack height. // which effictively don't modify the stack height.
stack.pop_values(1)?; stack.pop_values(1)?;
stack.push_values(1)?; stack.push_values(1)?;
push_count += 1;
}, },
I32Store(_, _) | I32Store(_, _) |
@@ -335,16 +393,19 @@ pub fn compute(func_idx: u32, module: &elements::Module) -> Result<u32, &'static
CurrentMemory(_) => { CurrentMemory(_) => {
// Pushes current memory size // Pushes current memory size
stack.push_values(1)?; stack.push_values(1)?;
push_count += 1;
}, },
GrowMemory(_) => { GrowMemory(_) => {
// Grow memory takes the value of pages to grow and pushes // Grow memory takes the value of pages to grow and pushes
stack.pop_values(1)?; stack.pop_values(1)?;
stack.push_values(1)?; stack.push_values(1)?;
push_count += 1;
}, },
I32Const(_) | I64Const(_) | F32Const(_) | F64Const(_) => { I32Const(_) | I64Const(_) | F32Const(_) | F64Const(_) => {
// These instructions just push the single literal value onto the stack. // These instructions just push the single literal value onto the stack.
stack.push_values(1)?; stack.push_values(1)?;
push_count += 1;
}, },
I32Eqz | I64Eqz => { I32Eqz | I64Eqz => {
@@ -352,6 +413,7 @@ pub fn compute(func_idx: u32, module: &elements::Module) -> Result<u32, &'static
// the result of the comparison. // the result of the comparison.
stack.pop_values(1)?; stack.pop_values(1)?;
stack.push_values(1)?; stack.push_values(1)?;
push_count += 1;
}, },
I32Eq | I32Ne | I32LtS | I32LtU | I32GtS | I32GtU | I32LeS | I32LeU | I32GeS | I32Eq | I32Ne | I32LtS | I32LtU | I32GtS | I32GtU | I32LeS | I32LeU | I32GeS |
@@ -361,6 +423,7 @@ pub fn compute(func_idx: u32, module: &elements::Module) -> Result<u32, &'static
// Comparison operations take two operands and produce one result. // Comparison operations take two operands and produce one result.
stack.pop_values(2)?; stack.pop_values(2)?;
stack.push_values(1)?; stack.push_values(1)?;
push_count += 1;
}, },
I32Clz | I32Ctz | I32Popcnt | I64Clz | I64Ctz | I64Popcnt | F32Abs | F32Neg | I32Clz | I32Ctz | I32Popcnt | I64Clz | I64Ctz | I64Popcnt | F32Abs | F32Neg |
@@ -369,6 +432,7 @@ pub fn compute(func_idx: u32, module: &elements::Module) -> Result<u32, &'static
// Unary operators take one operand and produce one result. // Unary operators take one operand and produce one result.
stack.pop_values(1)?; stack.pop_values(1)?;
stack.push_values(1)?; stack.push_values(1)?;
push_count += 1;
}, },
I32Add | I32Sub | I32Mul | I32DivS | I32DivU | I32RemS | I32RemU | I32And | I32Or | I32Add | I32Sub | I32Mul | I32DivS | I32DivU | I32RemS | I32RemU | I32And | I32Or |
@@ -380,6 +444,7 @@ pub fn compute(func_idx: u32, module: &elements::Module) -> Result<u32, &'static
// Binary operators take two operands and produce one result. // Binary operators take two operands and produce one result.
stack.pop_values(2)?; stack.pop_values(2)?;
stack.push_values(1)?; stack.push_values(1)?;
push_count += 1;
}, },
I32WrapI64 | I32TruncSF32 | I32TruncUF32 | I32TruncSF64 | I32TruncUF64 | I32WrapI64 | I32TruncSF32 | I32TruncUF32 | I32TruncSF64 | I32TruncUF64 |
@@ -391,6 +456,7 @@ pub fn compute(func_idx: u32, module: &elements::Module) -> Result<u32, &'static
// Conversion operators take one value and produce one result. // Conversion operators take one value and produce one result.
stack.pop_values(1)?; stack.pop_values(1)?;
stack.push_values(1)?; stack.push_values(1)?;
push_count += 1;
}, },
#[cfg(feature = "sign_ext")] #[cfg(feature = "sign_ext")]
@@ -401,178 +467,213 @@ pub fn compute(func_idx: u32, module: &elements::Module) -> Result<u32, &'static
SignExt(SignExtInstruction::I64Extend32S) => { SignExt(SignExtInstruction::I64Extend32S) => {
stack.pop_values(1)?; stack.pop_values(1)?;
stack.push_values(1)?; stack.push_values(1)?;
push_count += 1;
}, },
} }
pc += 1;
// If current value/control stack is higher than maximal height observed so far,
// save the new height.
if stack.height() > max_height {
max_height = stack.height();
}
if stack.control_height() > max_control_height {
max_control_height = stack.control_height();
}
trace!(
" Stack height: {}, control stack height: {}",
stack.height(),
stack.control_height()
);
} }
Ok(max_height) assert_eq!(stack.control_height(), 0);
assert_eq!(stack.height(), func_signature.results().len() as u32);
let res = StackHeightStats {
activation_cost: ACTIVATION_FRAME_COST,
max_height: max_height,
max_control_height: max_control_height,
locals_count: locals_count,
params_count: params_count,
blocks_count: blocks_count,
condbr_count: condbr_count,
push_count: push_count,
local_set_count: local_set_count,
opcode_count: instructions.elements().len() as u32,
// total_cost: (11.749 * params_count as f64 - 0.4888 * locals_count as f64 + 14.8169 * max_height as f64 - 5.1594 * max_control_height as f64 - 24.4941) as u32
total_cost: ACTIVATION_FRAME_COST + 2 * max_height + max_control_height + locals_count + 2 * params_count,
};
trace!("Result: {:?}", res);
Ok(res)
} }
#[cfg(test)] // #[cfg(test)]
mod tests { // mod tests {
use super::*; // use super::*;
use parity_wasm::elements; // use parity_wasm::elements;
fn parse_wat(source: &str) -> elements::Module { // fn parse_wat(source: &str) -> elements::Module {
elements::deserialize_buffer(&wat::parse_str(source).expect("Failed to wat2wasm")) // elements::deserialize_buffer(&wat::parse_str(source).expect("Failed to wat2wasm"))
.expect("Failed to deserialize the module") // .expect("Failed to deserialize the module")
} // }
#[test] // #[test]
fn simple_test() { // fn simple_test() {
let module = parse_wat( // let module = parse_wat(
r#" // r#"
(module // (module
(func // (func
i32.const 1 // i32.const 1
i32.const 2 // i32.const 2
i32.const 3 // i32.const 3
drop // drop
drop // drop
drop // drop
) // )
) // )
"#, // "#,
); // );
let height = compute(0, &module).unwrap(); // let height = compute(0, &module).unwrap();
assert_eq!(height, 3 + ACTIVATION_FRAME_COST); // assert_eq!(height, ACTIVATION_FRAME_COST + 3 + 1 + 0 + 0);
} // }
#[test] // #[test]
fn implicit_and_explicit_return() { // fn implicit_and_explicit_return() {
let module = parse_wat( // let module = parse_wat(
r#" // r#"
(module // (module
(func (result i32) // (func (result i32)
i32.const 0 // i32.const 0
return // return
) // )
) // )
"#, // "#,
); // );
let height = compute(0, &module).unwrap(); // let height = compute(0, &module).unwrap();
assert_eq!(height, 1 + ACTIVATION_FRAME_COST); // assert_eq!(height, ACTIVATION_FRAME_COST + 1 + 1 + 0 + 0);
} // }
#[test] // #[test]
fn dont_count_in_unreachable() { // fn dont_count_in_unreachable() {
let module = parse_wat( // let module = parse_wat(
r#" // r#"
(module // (module
(memory 0) // (memory 0)
(func (result i32) // (func (result i32)
unreachable // unreachable
memory.grow // grow_memory
) // )
) // )
"#, // "#,
); // );
let height = compute(0, &module).unwrap(); // let height = compute(0, &module).unwrap();
assert_eq!(height, ACTIVATION_FRAME_COST); // assert_eq!(height, ACTIVATION_FRAME_COST + 0 + 1 + 0 + 0);
} // }
#[test] // #[test]
fn yet_another_test() { // fn yet_another_test() {
let module = parse_wat( // let module = parse_wat(
r#" // r#"
(module // (module
(memory 0) // (memory 0)
(func // (func
;; Push two values and then pop them. // ;; Push two values and then pop them.
;; This will make max depth to be equal to 2. // ;; This will make max depth to be equal to 2.
i32.const 0 // i32.const 0
i32.const 1 // i32.const 1
drop // drop
drop // drop
;; Code after `unreachable` shouldn't have an effect // ;; Code after `unreachable` shouldn't have an effect
;; on the max depth. // ;; on the max depth.
unreachable // unreachable
i32.const 0 // i32.const 0
i32.const 1 // i32.const 1
i32.const 2 // i32.const 2
) // )
) // )
"#, // "#,
); // );
let height = compute(0, &module).unwrap(); // let height = compute(0, &module).unwrap();
assert_eq!(height, 2 + ACTIVATION_FRAME_COST); // assert_eq!(height, 2 + ACTIVATION_FRAME_COST);
} // }
#[test] // #[test]
fn call_indirect() { // fn call_indirect() {
let module = parse_wat( // let module = parse_wat(
r#" // r#"
(module // (module
(table $ptr 1 1 funcref) // (table $ptr 1 1 funcref)
(elem $ptr (i32.const 0) func 1) // (elem $ptr (i32.const 0) func 1)
(func $main // (func $main
(call_indirect (i32.const 0)) // (call_indirect (i32.const 0))
(call_indirect (i32.const 0)) // (call_indirect (i32.const 0))
(call_indirect (i32.const 0)) // (call_indirect (i32.const 0))
) // )
(func $callee // (func $callee
i64.const 42 // i64.const 42
drop // drop
) // )
) // )
"#, // "#,
); // );
let height = compute(0, &module).unwrap(); // let height = compute(0, &module).unwrap();
assert_eq!(height, 1 + ACTIVATION_FRAME_COST); // assert_eq!(height, 1 + ACTIVATION_FRAME_COST);
} // }
#[test] // #[test]
fn breaks() { // fn breaks() {
let module = parse_wat( // let module = parse_wat(
r#" // r#"
(module // (module
(func $main // (func $main
block (result i32) // block (result i32)
block (result i32) // block (result i32)
i32.const 99 // i32.const 99
br 1 // br 1
end // end
end // end
drop // drop
) // )
) // )
"#, // "#,
); // );
let height = compute(0, &module).unwrap(); // let height = compute(0, &module).unwrap();
assert_eq!(height, 1 + ACTIVATION_FRAME_COST); // assert_eq!(height, 1 + ACTIVATION_FRAME_COST);
} // }
#[test] // #[test]
fn if_else_works() { // fn if_else_works() {
let module = parse_wat( // let module = parse_wat(
r#" // r#"
(module // (module
(func $main // (func $main
i32.const 7 // i32.const 7
i32.const 1 // i32.const 1
if (result i32) // if (result i32)
i32.const 42 // i32.const 42
else // else
i32.const 99 // i32.const 99
end // end
i32.const 97 // i32.const 97
drop // drop
drop // drop
drop // drop
) // )
) // )
"#, // "#,
); // );
let height = compute(0, &module).unwrap(); // let height = compute(0, &module).unwrap();
assert_eq!(height, 3 + ACTIVATION_FRAME_COST); // assert_eq!(height, 3 + ACTIVATION_FRAME_COST);
} // }
} // }
+18 -30
View File
@@ -6,6 +6,7 @@ use parity_wasm::{
builder, builder,
elements::{self, Instruction, Instructions, Type}, elements::{self, Instruction, Instructions, Type},
}; };
pub use max_height::StackHeightStats;
/// Macro to generate preamble and postamble. /// Macro to generate preamble and postamble.
macro_rules! instrument_call { macro_rules! instrument_call {
@@ -40,7 +41,7 @@ mod thunk;
pub struct Context { pub struct Context {
stack_height_global_idx: u32, stack_height_global_idx: u32,
func_stack_costs: Vec<u32>, func_stack_costs: Vec<StackHeightStats>,
stack_limit: u32, stack_limit: u32,
} }
@@ -52,7 +53,11 @@ impl Context {
/// Returns `stack_cost` for `func_idx`. /// Returns `stack_cost` for `func_idx`.
fn stack_cost(&self, func_idx: u32) -> Option<u32> { fn stack_cost(&self, func_idx: u32) -> Option<u32> {
self.func_stack_costs.get(func_idx as usize).cloned() if let Some(stats) = self.func_stack_costs.get(func_idx as usize) {
Some(stats.total_cost)
} else {
None
}
} }
/// Returns stack limit specified by the rules. /// Returns stack limit specified by the rules.
@@ -154,7 +159,7 @@ fn generate_stack_height_global(module: &mut elements::Module) -> u32 {
/// Calculate stack costs for all functions. /// Calculate stack costs for all functions.
/// ///
/// Returns a vector with a stack cost for each function, including imports. /// Returns a vector with a stack cost for each function, including imports.
fn compute_stack_costs(module: &elements::Module) -> Result<Vec<u32>, &'static str> { fn compute_stack_costs(module: &elements::Module) -> Result<Vec<StackHeightStats>, &'static str> {
let func_imports = module.import_count(elements::ImportCountType::Function); let func_imports = module.import_count(elements::ImportCountType::Function);
// TODO: optimize! // TODO: optimize!
@@ -162,7 +167,7 @@ fn compute_stack_costs(module: &elements::Module) -> Result<Vec<u32>, &'static s
.map(|func_idx| { .map(|func_idx| {
if func_idx < func_imports { if func_idx < func_imports {
// We can't calculate stack_cost of the import functions. // We can't calculate stack_cost of the import functions.
Ok(0) Ok(Default::default())
} else { } else {
compute_stack_cost(func_idx as u32, module) compute_stack_cost(func_idx as u32, module)
} }
@@ -173,7 +178,7 @@ fn compute_stack_costs(module: &elements::Module) -> Result<Vec<u32>, &'static s
/// Stack cost of the given *defined* function is the sum of it's locals count (that is, /// Stack cost of the given *defined* function is the sum of it's locals count (that is,
/// number of arguments plus number of local variables) and the maximal stack /// number of arguments plus number of local variables) and the maximal stack
/// height. /// height.
fn compute_stack_cost(func_idx: u32, module: &elements::Module) -> Result<u32, &'static str> { pub fn compute_stack_cost(func_idx: u32, module: &elements::Module) -> Result<StackHeightStats, &'static str> {
// To calculate the cost of a function we need to convert index from // To calculate the cost of a function we need to convert index from
// function index space to defined function spaces. // function index space to defined function spaces.
let func_imports = module.import_count(elements::ImportCountType::Function) as u32; let func_imports = module.import_count(elements::ImportCountType::Function) as u32;
@@ -181,24 +186,7 @@ fn compute_stack_cost(func_idx: u32, module: &elements::Module) -> Result<u32, &
.checked_sub(func_imports) .checked_sub(func_imports)
.ok_or("This should be a index of a defined function")?; .ok_or("This should be a index of a defined function")?;
let code_section = max_height::compute(defined_func_idx, module)
module.code_section().ok_or("Due to validation code section should exists")?;
let body = &code_section
.bodies()
.get(defined_func_idx as usize)
.ok_or("Function body is out of bounds")?;
let mut locals_count: u32 = 0;
for local_group in body.locals() {
locals_count =
locals_count.checked_add(local_group.count()).ok_or("Overflow in local count")?;
}
let max_stack_height = max_height::compute(defined_func_idx, module)?;
locals_count
.checked_add(max_stack_height)
.ok_or("Overflow in adding locals_count and max_stack_height")
} }
fn instrument_functions( fn instrument_functions(
@@ -222,8 +210,8 @@ fn instrument_functions(
/// Before: /// Before:
/// ///
/// ```text /// ```text
/// local.get 0 /// get_local 0
/// local.get 1 /// get_local 1
/// call 228 /// call 228
/// drop /// drop
/// ``` /// ```
@@ -231,8 +219,8 @@ fn instrument_functions(
/// After: /// After:
/// ///
/// ```text /// ```text
/// local.get 0 /// get_local 0
/// local.get 1 /// get_local 1
/// ///
/// < ... preamble ... > /// < ... preamble ... >
/// ///
@@ -366,9 +354,9 @@ mod tests {
r#" r#"
(module (module
(func (export "i32.add") (param i32 i32) (result i32) (func (export "i32.add") (param i32 i32) (result i32)
local.get 0 get_local 0
local.get 1 get_local 1
i32.add i32.add
) )
) )
"#, "#,
+5 -1
View File
@@ -1,8 +1,12 @@
use alloc::{collections::BTreeMap as Map, vec::Vec}; #[cfg(not(features = "std"))]
use alloc::collections::BTreeMap as Map;
use alloc::vec::Vec;
use parity_wasm::{ use parity_wasm::{
builder, builder,
elements::{self, FunctionType, Internal}, elements::{self, FunctionType, Internal},
}; };
#[cfg(features = "std")]
use std::collections::HashMap as Map;
use super::{resolve_func_type, Context}; use super::{resolve_func_type, Context};
+29 -64
View File
@@ -1,9 +1,10 @@
use parity_wasm::elements::Module;
use std::{ use std::{
fs, fs,
io::{self, Read, Write}, io::{self, Read, Write},
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
use wasm_instrument::{self as instrument, gas_metering, parity_wasm::elements}; use wasm_instrument::{self as instrument, parity_wasm::elements};
use wasmparser::validate; use wasmparser::validate;
fn slurp<P: AsRef<Path>>(path: P) -> io::Result<Vec<u8>> { fn slurp<P: AsRef<Path>>(path: P) -> io::Result<Vec<u8>> {
@@ -19,23 +20,18 @@ fn dump<P: AsRef<Path>>(path: P, buf: &[u8]) -> io::Result<()> {
Ok(()) Ok(())
} }
fn run_diff_test<F: FnOnce(&[u8]) -> Vec<u8>>( fn run_diff_test<F: FnOnce(&[u8]) -> Vec<u8>>(test_dir: &str, name: &str, test: F) {
test_dir: &str,
in_name: &str,
out_name: &str,
test: F,
) {
let mut fixture_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let mut fixture_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
fixture_path.push("tests"); fixture_path.push("tests");
fixture_path.push("fixtures"); fixture_path.push("fixtures");
fixture_path.push(test_dir); fixture_path.push(test_dir);
fixture_path.push(in_name); fixture_path.push(name);
let mut expected_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let mut expected_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
expected_path.push("tests"); expected_path.push("tests");
expected_path.push("expectations"); expected_path.push("expectations");
expected_path.push(test_dir); expected_path.push(test_dir);
expected_path.push(out_name); expected_path.push(name);
let fixture_wasm = wat::parse_file(&fixture_path).expect("Failed to read fixture"); let fixture_wasm = wat::parse_file(&fixture_path).expect("Failed to read fixture");
validate(&fixture_wasm).expect("Fixture is invalid"); validate(&fixture_wasm).expect("Fixture is invalid");
@@ -52,7 +48,7 @@ fn run_diff_test<F: FnOnce(&[u8]) -> Vec<u8>>(
if actual_wat != expected_wat { if actual_wat != expected_wat {
println!("difference!"); println!("difference!");
println!("--- {}", expected_path.display()); println!("--- {}", expected_path.display());
println!("+++ {} test {}", test_dir, out_name); println!("+++ {} test {}", test_dir, name);
for diff in diff::lines(expected_wat, &actual_wat) { for diff in diff::lines(expected_wat, &actual_wat) {
match diff { match diff {
diff::Result::Left(l) => println!("-{}", l), diff::Result::Left(l) => println!("-{}", l),
@@ -76,18 +72,13 @@ mod stack_height {
( $name:ident ) => { ( $name:ident ) => {
#[test] #[test]
fn $name() { fn $name() {
run_diff_test( run_diff_test("stack-height", concat!(stringify!($name), ".wat"), |input| {
"stack-height", let module =
concat!(stringify!($name), ".wat"), elements::deserialize_buffer(input).expect("Failed to deserialize");
concat!(stringify!($name), ".wat"), let instrumented = instrument::inject_stack_limiter(module, 1024)
|input| { .expect("Failed to instrument with stack counter");
let module = elements::serialize(instrumented).expect("Failed to serialize")
elements::deserialize_buffer(input).expect("Failed to deserialize"); });
let instrumented = instrument::inject_stack_limiter(module, 1024)
.expect("Failed to instrument with stack counter");
elements::serialize(instrumented).expect("Failed to serialize")
},
);
} }
}; };
} }
@@ -105,53 +96,27 @@ mod gas {
use super::*; use super::*;
macro_rules! def_gas_test { macro_rules! def_gas_test {
( ($input:ident, $name1:ident, $name2:ident) ) => { ( $name:ident ) => {
#[test] #[test]
fn $name1() { fn $name() {
run_diff_test( run_diff_test("gas", concat!(stringify!($name), ".wat"), |input| {
"gas", let rules = instrument::gas_metering::ConstantCostRules::default();
concat!(stringify!($input), ".wat"),
concat!(stringify!($name1), ".wat"),
|input| {
let rules = gas_metering::ConstantCostRules::default();
let module: elements::Module = let module: Module =
elements::deserialize_buffer(input).expect("Failed to deserialize"); elements::deserialize_buffer(input).expect("Failed to deserialize");
let module = module.parse_names().expect("Failed to parse names"); let module = module.parse_names().expect("Failed to parse names");
let backend = gas_metering::host_function::Injector::new("env", "gas");
let instrumented = gas_metering::inject(module, backend, &rules) let instrumented = instrument::gas_metering::inject(module, &rules, "env")
.expect("Failed to instrument with gas metering"); .expect("Failed to instrument with gas metering");
elements::serialize(instrumented).expect("Failed to serialize") elements::serialize(instrumented).expect("Failed to serialize")
}, });
);
}
#[test]
fn $name2() {
run_diff_test(
"gas",
concat!(stringify!($input), ".wat"),
concat!(stringify!($name2), ".wat"),
|input| {
let rules = gas_metering::ConstantCostRules::default();
let module: elements::Module =
elements::deserialize_buffer(input).expect("Failed to deserialize");
let module = module.parse_names().expect("Failed to parse names");
let backend = gas_metering::mutable_global::Injector::new("gas_left");
let instrumented = gas_metering::inject(module, backend, &rules)
.expect("Failed to instrument with gas metering");
elements::serialize(instrumented).expect("Failed to serialize")
},
);
} }
}; };
} }
def_gas_test!((ifs, ifs_host_fn, ifs_mut_global)); def_gas_test!(ifs);
def_gas_test!((simple, simple_host_fn, simple_mut_global)); def_gas_test!(simple);
def_gas_test!((start, start_host_fn, start_mut_global)); def_gas_test!(start);
def_gas_test!((call, call_host_fn, call_mut_global)); def_gas_test!(call);
def_gas_test!((branch, branch_host_fn, branch_mut_global)); def_gas_test!(branch);
} }
@@ -1,12 +1,12 @@
(module (module
(type (;0;) (func (result i32))) (type (;0;) (func (result i32)))
(type (;1;) (func (param i64))) (type (;1;) (func (param i32)))
(import "env" "gas" (func (;0;) (type 1))) (import "env" "gas" (func (;0;) (type 1)))
(func $fibonacci_with_break (;1;) (type 0) (result i32) (func $fibonacci_with_break (;1;) (type 0) (result i32)
(local i32 i32) (local i32 i32)
i64.const 15 i32.const 13
call 0 call 0
block ;; label = @1 block ;; label = @1
i32.const 0 i32.const 0
local.set 0 local.set 0
i32.const 1 i32.const 1
@@ -18,7 +18,7 @@
local.set 1 local.set 1
i32.const 1 i32.const 1
br_if 0 (;@1;) br_if 0 (;@1;)
i64.const 5 i32.const 5
call 0 call 0
local.get 0 local.get 0
local.get 1 local.get 1
@@ -1,47 +0,0 @@
(module
(type (;0;) (func (result i32)))
(type (;1;) (func (param i64)))
(func $fibonacci_with_break (;0;) (type 0) (result i32)
(local $x i32) (local $y i32)
i64.const 26
call 1
block ;; label = @1
i32.const 0
local.set $x
i32.const 1
local.set $y
local.get $x
local.get $y
local.tee $x
i32.add
local.set $y
i32.const 1
br_if 0 (;@1;)
i64.const 16
call 1
local.get $x
local.get $y
local.tee $x
i32.add
local.set $y
end
local.get $y
)
(func (;1;) (type 1) (param i64)
global.get 0
local.get 0
i64.ge_u
if ;; label = @1
global.get 0
local.get 0
i64.sub
global.set 0
else
i64.const -1
global.set 0
unreachable
end
)
(global (;0;) (mut i64) i64.const 0)
(export "gas_left" (global 0))
)
@@ -1,10 +1,10 @@
(module (module
(type (;0;) (func (param i32 i32) (result i32))) (type (;0;) (func (param i32 i32) (result i32)))
(type (;1;) (func (param i64))) (type (;1;) (func (param i32)))
(import "env" "gas" (func (;0;) (type 1))) (import "env" "gas" (func (;0;) (type 1)))
(func $add_locals (;1;) (type 0) (param $x i32) (param $y i32) (result i32) (func $add_locals (;1;) (type 0) (param $x i32) (param $y i32) (result i32)
(local i32) (local i32)
i64.const 6 i32.const 5
call 0 call 0
local.get $x local.get $x
local.get $y local.get $y
@@ -13,7 +13,7 @@
local.get 2 local.get 2
) )
(func $add (;2;) (type 0) (param i32 i32) (result i32) (func $add (;2;) (type 0) (param i32 i32) (result i32)
i64.const 3 i32.const 3
call 0 call 0
local.get 0 local.get 0
local.get 1 local.get 1
@@ -1,38 +0,0 @@
(module
(type (;0;) (func (param i32 i32) (result i32)))
(type (;1;) (func (param i64)))
(func $add_locals (;0;) (type 0) (param $x i32) (param $y i32) (result i32)
(local $t i32)
i64.const 17
call 2
local.get $x
local.get $y
call $add
local.set $t
local.get $t
)
(func $add (;1;) (type 0) (param $x i32) (param $y i32) (result i32)
i64.const 14
call 2
local.get $x
local.get $y
i32.add
)
(func (;2;) (type 1) (param i64)
global.get 0
local.get 0
i64.ge_u
if ;; label = @1
global.get 0
local.get 0
i64.sub
global.set 0
else
i64.const -1
global.set 0
unreachable
end
)
(global (;0;) (mut i64) i64.const 0)
(export "gas_left" (global 0))
)
@@ -1,19 +1,19 @@
(module (module
(type (;0;) (func (param i32) (result i32))) (type (;0;) (func (param i32) (result i32)))
(type (;1;) (func (param i64))) (type (;1;) (func (param i32)))
(import "env" "gas" (func (;0;) (type 1))) (import "env" "gas" (func (;0;) (type 1)))
(func (;1;) (type 0) (param i32) (result i32) (func (;1;) (type 0) (param i32) (result i32)
i64.const 2 i32.const 2
call 0 call 0
i32.const 1 i32.const 1
if (result i32) ;; label = @1 if (result i32) ;; label = @1
i64.const 3 i32.const 3
call 0 call 0
local.get 0 local.get 0
i32.const 1 i32.const 1
i32.add i32.add
else else
i64.const 2 i32.const 2
call 0 call 0
local.get 0 local.get 0
i32.popcnt i32.popcnt
-38
View File
@@ -1,38 +0,0 @@
(module
(type (;0;) (func (param i32) (result i32)))
(type (;1;) (func (param i64)))
(func (;0;) (type 0) (param $x i32) (result i32)
i64.const 13
call 1
i32.const 1
if (result i32) ;; label = @1
i64.const 14
call 1
local.get $x
i32.const 1
i32.add
else
i64.const 13
call 1
local.get $x
i32.popcnt
end
)
(func (;1;) (type 1) (param i64)
global.get 0
local.get 0
i64.ge_u
if ;; label = @1
global.get 0
local.get 0
i64.sub
global.set 0
else
i64.const -1
global.set 0
unreachable
end
)
(global (;0;) (mut i64) i64.const 0)
(export "gas_left" (global 0))
)
@@ -1,16 +1,16 @@
(module (module
(type (;0;) (func)) (type (;0;) (func))
(type (;1;) (func (param i64))) (type (;1;) (func (param i32)))
(import "env" "gas" (func (;0;) (type 1))) (import "env" "gas" (func (;0;) (type 1)))
(func (;1;) (type 0) (func (;1;) (type 0)
i64.const 2 i32.const 2
call 0 call 0
i32.const 1 i32.const 1
if ;; label = @1 if ;; label = @1
i64.const 1 i32.const 1
call 0 call 0
loop ;; label = @2 loop ;; label = @2
i64.const 2 i32.const 2
call 0 call 0
i32.const 123 i32.const 123
drop drop
@@ -18,9 +18,9 @@
end end
) )
(func (;2;) (type 0) (func (;2;) (type 0)
i64.const 1 i32.const 1
call 0 call 0
block ;; label = @1 block ;; label = @1
end end
) )
(export "simple" (func 1)) (export "simple" (func 1))
@@ -1,43 +0,0 @@
(module
(type (;0;) (func))
(type (;1;) (func (param i64)))
(func (;0;) (type 0)
i64.const 13
call 2
i32.const 1
if ;; label = @1
i64.const 12
call 2
loop ;; label = @2
i64.const 13
call 2
i32.const 123
drop
end
end
)
(func (;1;) (type 0)
i64.const 12
call 2
block ;; label = @1
end
)
(func (;2;) (type 1) (param i64)
global.get 0
local.get 0
i64.ge_u
if ;; label = @1
global.get 0
local.get 0
i64.sub
global.set 0
else
i64.const -1
global.set 0
unreachable
end
)
(global (;0;) (mut i64) i64.const 0)
(export "simple" (func 0))
(export "gas_left" (global 0))
)
@@ -1,12 +1,12 @@
(module (module
(type (;0;) (func (param i32 i32))) (type (;0;) (func (param i32 i32)))
(type (;1;) (func)) (type (;1;) (func))
(type (;2;) (func (param i64))) (type (;2;) (func (param i32)))
(import "env" "ext_return" (func $ext_return (;0;) (type 0))) (import "env" "ext_return" (func $ext_return (;0;) (type 0)))
(import "env" "memory" (memory (;0;) 1 1)) (import "env" "memory" (memory (;0;) 1 1))
(import "env" "gas" (func (;1;) (type 2))) (import "env" "gas" (func (;1;) (type 2)))
(func $start (;2;) (type 1) (func $start (;2;) (type 1)
i64.const 4 i32.const 4
call 1 call 1
i32.const 8 i32.const 8
i32.const 4 i32.const 4
@@ -1,36 +0,0 @@
(module
(type (;0;) (func (param i32 i32)))
(type (;1;) (func))
(type (;2;) (func (param i64)))
(import "env" "ext_return" (func $ext_return (;0;) (type 0)))
(import "env" "memory" (memory (;0;) 1 1))
(func $start (;1;) (type 1)
i64.const 15
call 3
i32.const 8
i32.const 4
call $ext_return
unreachable
)
(func (;2;) (type 1))
(func (;3;) (type 2) (param i64)
global.get 0
local.get 0
i64.ge_u
if ;; label = @1
global.get 0
local.get 0
i64.sub
global.set 0
else
i64.const -1
global.set 0
unreachable
end
)
(global (;0;) (mut i64) i64.const 0)
(export "call" (func 2))
(export "gas_left" (global 0))
(start $start)
(data (;0;) (i32.const 8) "\01\02\03\04")
)
@@ -8,7 +8,7 @@
global.get 0 global.get 0
i32.const 1024 i32.const 1024
i32.gt_u i32.gt_u
if ;; label = @1 if ;; label = @1
unreachable unreachable
end end
call 0 call 0
@@ -25,7 +25,7 @@
global.get 0 global.get 0
i32.const 1024 i32.const 1024
i32.gt_u i32.gt_u
if ;; label = @1 if ;; label = @1
unreachable unreachable
end end
call 0 call 0
@@ -42,7 +42,7 @@
global.get 0 global.get 0
i32.const 1024 i32.const 1024
i32.gt_u i32.gt_u
if ;; label = @1 if ;; label = @1
unreachable unreachable
end end
call 1 call 1
+2 -2
View File
@@ -24,7 +24,7 @@
global.get 1 global.get 1
i32.const 1024 i32.const 1024
i32.gt_u i32.gt_u
if ;; label = @1 if ;; label = @1
unreachable unreachable
end end
call $i32.add call $i32.add
@@ -44,7 +44,7 @@
global.get 1 global.get 1
i32.const 1024 i32.const 1024
i32.gt_u i32.gt_u
if ;; label = @1 if ;; label = @1
unreachable unreachable
end end
call $i32.add call $i32.add
+1 -1
View File
@@ -20,7 +20,7 @@
global.get 0 global.get 0
i32.const 1024 i32.const 1024
i32.gt_u i32.gt_u
if ;; label = @1 if ;; label = @1
unreachable unreachable
end end
call 2 call 2
@@ -11,7 +11,7 @@
global.get 0 global.get 0
i32.const 1024 i32.const 1024
i32.gt_u i32.gt_u
if ;; label = @1 if ;; label = @1
unreachable unreachable
end end
call $one-group-many-locals call $one-group-many-locals
+1 -1
View File
@@ -12,7 +12,7 @@
global.get 0 global.get 0
i32.const 1024 i32.const 1024
i32.gt_u i32.gt_u
if ;; label = @1 if ;; label = @1
unreachable unreachable
end end
call 0 call 0
+2 -2
View File
@@ -15,7 +15,7 @@
global.get 0 global.get 0
i32.const 1024 i32.const 1024
i32.gt_u i32.gt_u
if ;; label = @1 if ;; label = @1
unreachable unreachable
end end
call $start call $start
@@ -32,7 +32,7 @@
global.get 0 global.get 0
i32.const 1024 i32.const 1024
i32.gt_u i32.gt_u
if ;; label = @1 if ;; label = @1
unreachable unreachable
end end
call 2 call 2
+3 -3
View File
@@ -13,7 +13,7 @@
global.get 0 global.get 0
i32.const 1024 i32.const 1024
i32.gt_u i32.gt_u
if ;; label = @1 if ;; label = @1
unreachable unreachable
end end
call $i32.add call $i32.add
@@ -37,7 +37,7 @@
global.get 0 global.get 0
i32.const 1024 i32.const 1024
i32.gt_u i32.gt_u
if ;; label = @1 if ;; label = @1
unreachable unreachable
end end
call 1 call 1
@@ -56,7 +56,7 @@
global.get 0 global.get 0
i32.const 1024 i32.const 1024
i32.gt_u i32.gt_u
if ;; label = @1 if ;; label = @1
unreachable unreachable
end end
call $i32.add call $i32.add
+11 -11
View File
@@ -3,25 +3,25 @@
(local $x i32) (local $y i32) (local $x i32) (local $y i32)
(block $unrolled_loop (block $unrolled_loop
(local.set $x (i32.const 0)) (set_local $x (i32.const 0))
(local.set $y (i32.const 1)) (set_local $y (i32.const 1))
local.get $x get_local $x
local.get $y get_local $y
local.tee $x tee_local $x
i32.add i32.add
local.set $y set_local $y
i32.const 1 i32.const 1
br_if $unrolled_loop br_if $unrolled_loop
local.get $x get_local $x
local.get $y get_local $y
local.tee $x tee_local $x
i32.add i32.add
local.set $y set_local $y
) )
local.get $y get_local $y
) )
) )
+6 -6
View File
@@ -2,18 +2,18 @@
(func $add_locals (param $x i32) (param $y i32) (result i32) (func $add_locals (param $x i32) (param $y i32) (result i32)
(local $t i32) (local $t i32)
local.get $x get_local $x
local.get $y get_local $y
call $add call $add
local.set $t set_local $t
local.get $t get_local $t
) )
(func $add (param $x i32) (param $y i32) (result i32) (func $add (param $x i32) (param $y i32) (result i32)
(i32.add (i32.add
(local.get $x) (get_local $x)
(local.get $y) (get_local $y)
) )
) )
) )
+2 -2
View File
@@ -2,8 +2,8 @@
(func (param $x i32) (result i32) (func (param $x i32) (result i32)
(if (result i32) (if (result i32)
(i32.const 1) (i32.const 1)
(then (i32.add (local.get $x) (i32.const 1))) (then (i32.add (get_local $x) (i32.const 1)))
(else (i32.popcnt (local.get $x))) (else (i32.popcnt (get_local $x)))
) )
) )
) )
+13 -13
View File
@@ -5,22 +5,22 @@
(global $counter (mut i32) (i32.const 1)) (global $counter (mut i32) (i32.const 1))
(func $i32.add (export "i32.add") (param i32 i32) (result i32) (func $i32.add (export "i32.add") (param i32 i32) (result i32)
local.get 0 get_local 0
local.get 1 get_local 1
i32.add i32.add
) )
(func (param $arg i32) (func (param $arg i32)
(local $tmp i32) (local $tmp i32)
global.get 0 global.get 0
i32.const 1 i32.const 1
i32.add i32.add
local.tee $tmp tee_local $tmp
global.set $counter global.set $counter
local.get $tmp get_local $tmp
local.get $arg get_local $arg
call $i32.add call $i32.add
drop drop
) )
) )
+2 -2
View File
@@ -8,8 +8,8 @@
call $foo call $foo
call $boo call $boo
local.get 0 get_local 0
local.get 1 get_local 1
i32.add i32.add
) )
) )
+1 -1
View File
@@ -6,5 +6,5 @@
(call (call
$one-group-many-locals $one-group-many-locals
) )
) )
) )
+5 -5
View File
@@ -1,17 +1,17 @@
(module (module
(import "env" "foo" (func $foo)) (import "env" "foo" (func $foo))
(func (param i32) (func (param i32)
local.get 0 get_local 0
i32.const 0 i32.const 0
call $i32.add call $i32.add
drop drop
) )
(func $i32.add (export "i32.add") (param i32 i32) (result i32) (func $i32.add (export "i32.add") (param i32 i32) (result i32)
local.get 0 get_local 0
local.get 1 get_local 1
i32.add i32.add
) )
(table 10 funcref) (table 10 anyfunc)
;; Refer all types of functions: imported, defined not exported and defined exported. ;; Refer all types of functions: imported, defined not exported and defined exported.
(elem (i32.const 0) 0 1 2) (elem (i32.const 0) 0 1 2)
+47 -145
View File
@@ -1,10 +1,9 @@
use std::{ use std::{
fs::{read, read_dir, ReadDir}, fs::{read, read_dir},
path::PathBuf, path::PathBuf,
}; };
use wasm_instrument::{ use wasm_instrument::{
gas_metering::{self, host_function, mutable_global, ConstantCostRules}, gas_metering, inject_stack_limiter,
inject_stack_limiter,
parity_wasm::{deserialize_buffer, elements::Module, serialize}, parity_wasm::{deserialize_buffer, elements::Module, serialize},
}; };
@@ -15,157 +14,60 @@ fn fixture_dir() -> PathBuf {
path path
} }
use gas_metering::Backend; /// Print the overhead of applying gas metering, stack height limiting or both.
fn gas_metered_mod_len<B: Backend>(orig_module: Module, backend: B) -> (Module, usize) { ///
let module = gas_metering::inject(orig_module, backend, &ConstantCostRules::default()).unwrap(); /// Use `cargo test print_overhead -- --nocapture`.
let bytes = serialize(module.clone()).unwrap(); #[test]
let len = bytes.len(); fn print_size_overhead() {
(module, len) let mut results: Vec<_> = read_dir(fixture_dir())
} .unwrap()
fn stack_limited_mod_len(module: Module) -> (Module, usize) {
let module = inject_stack_limiter(module, 128).unwrap();
let bytes = serialize(module.clone()).unwrap();
let len = bytes.len();
(module, len)
}
struct InstrumentedWasmResults {
filename: String,
original_module_len: usize,
stack_limited_len: usize,
gas_metered_host_fn_len: usize,
gas_metered_mut_glob_len: usize,
gas_metered_host_fn_then_stack_limited_len: usize,
gas_metered_mut_glob_then_stack_limited_len: usize,
}
fn size_overheads_all(files: ReadDir) -> Vec<InstrumentedWasmResults> {
files
.map(|entry| { .map(|entry| {
let entry = entry.unwrap(); let entry = entry.unwrap();
let filename = entry.file_name().into_string().unwrap(); let (orig_len, orig_module) = {
let bytes = read(&entry.path()).unwrap();
let (original_module_len, orig_module) = {
let bytes = match entry.path().extension().unwrap().to_str() {
Some("wasm") => read(entry.path()).unwrap(),
Some("wat") =>
wat::parse_bytes(&read(entry.path()).unwrap()).unwrap().into_owned(),
_ => panic!("expected fixture_dir containing .wasm or .wat files only"),
};
let len = bytes.len(); let len = bytes.len();
let module: Module = deserialize_buffer(&bytes).unwrap(); let module: Module = deserialize_buffer(&bytes).unwrap();
(len, module) (len, module)
}; };
let (gas_metering_len, gas_module) = {
let module = gas_metering::inject(
orig_module.clone(),
&gas_metering::ConstantCostRules::default(),
"env",
)
.unwrap();
let bytes = serialize(module.clone()).unwrap();
let len = bytes.len();
(len, module)
};
let stack_height_len = {
let module = inject_stack_limiter(orig_module, 128).unwrap();
let bytes = serialize(module).unwrap();
bytes.len()
};
let both_len = {
let module = inject_stack_limiter(gas_module, 128).unwrap();
let bytes = serialize(module).unwrap();
bytes.len()
};
let (gm_host_fn_module, gas_metered_host_fn_len) = gas_metered_mod_len( let overhead = both_len * 100 / orig_len;
orig_module.clone(),
host_function::Injector::new("env", "gas"),
);
let (gm_mut_global_module, gas_metered_mut_glob_len) = (
gas_metered_mod_len(orig_module.clone(), mutable_global::Injector::new("gas_left")); overhead,
format!(
let stack_limited_len = stack_limited_mod_len(orig_module).1; "{:30}: orig = {:4} kb, gas_metering = {} %, stack_limiter = {} %, both = {} %",
entry.file_name().to_str().unwrap(),
let (_gm_hf_sl_mod, gas_metered_host_fn_then_stack_limited_len) = orig_len / 1024,
stack_limited_mod_len(gm_host_fn_module); gas_metering_len * 100 / orig_len,
stack_height_len * 100 / orig_len,
let (_gm_mg_sl_module, gas_metered_mut_glob_then_stack_limited_len) = overhead,
stack_limited_mod_len(gm_mut_global_module); ),
)
InstrumentedWasmResults {
filename,
original_module_len,
stack_limited_len,
gas_metered_host_fn_len,
gas_metered_mut_glob_len,
gas_metered_host_fn_then_stack_limited_len,
gas_metered_mut_glob_then_stack_limited_len,
}
}) })
.collect() .collect();
}
fn calc_size_overheads() -> Vec<InstrumentedWasmResults> {
let mut wasm_path = fixture_dir();
wasm_path.push("wasm");
let mut wat_path = fixture_dir();
wat_path.push("wat");
let mut results = size_overheads_all(read_dir(wasm_path).unwrap());
let results_wat = size_overheads_all(read_dir(wat_path).unwrap());
results.extend(results_wat);
results
}
/// Print the overhead of applying gas metering, stack
/// height limiting or both.
///
/// Use `cargo test print_size_overhead -- --nocapture`.
#[test]
fn print_size_overhead() {
let mut results = calc_size_overheads();
results.sort_unstable_by(|a, b| {
b.gas_metered_mut_glob_then_stack_limited_len
.cmp(&a.gas_metered_mut_glob_then_stack_limited_len)
});
for r in results {
let filename = r.filename;
let original_size = r.original_module_len / 1024;
let stack_limit = r.stack_limited_len * 100 / r.original_module_len;
let host_fn = r.gas_metered_host_fn_len * 100 / r.original_module_len;
let mut_glob = r.gas_metered_mut_glob_len * 100 / r.original_module_len;
let host_fn_sl = r.gas_metered_host_fn_then_stack_limited_len * 100 / r.original_module_len;
let mut_glob_sl =
r.gas_metered_mut_glob_then_stack_limited_len * 100 / r.original_module_len;
println!(
"{filename:30}: orig = {original_size:4} kb, stack_limiter = {stack_limit} %, \
gas_metered_host_fn = {host_fn} %, both = {host_fn_sl} %,\n \
{:69} gas_metered_mut_global = {mut_glob} %, both = {mut_glob_sl} %",
""
);
}
}
/// Compare module size overhead of applying gas metering with two methods.
///
/// Use `cargo test print_gas_metered_sizes -- --nocapture`.
#[test]
fn print_gas_metered_sizes() {
let overheads = calc_size_overheads();
let mut results = overheads
.iter()
.map(|r| {
let diff = (r.gas_metered_mut_glob_len * 100 / r.gas_metered_host_fn_len) as i32 - 100;
(diff, r)
})
.collect::<Vec<(i32, &InstrumentedWasmResults)>>();
results.sort_unstable_by(|a, b| b.0.cmp(&a.0)); results.sort_unstable_by(|a, b| b.0.cmp(&a.0));
for entry in results {
println!( println!("{}", entry.1);
"| {:28} | {:^16} | gas metered/host fn | gas metered/mut global | size diff |",
"fixture", "original size",
);
println!("|{:-^30}|{:-^18}|{:-^21}|{:-^24}|{:-^11}|", "", "", "", "", "",);
for r in results {
let filename = &r.1.filename;
let original_size = &r.1.original_module_len / 1024;
let host_fn = &r.1.gas_metered_host_fn_len / 1024;
let mut_glob = &r.1.gas_metered_mut_glob_len / 1024;
let host_fn_percent = &r.1.gas_metered_host_fn_len * 100 / r.1.original_module_len;
let mut_glob_percent = &r.1.gas_metered_mut_glob_len * 100 / r.1.original_module_len;
let host_fn = format!("{host_fn} kb ({host_fn_percent:}%)");
let mut_glob = format!("{mut_glob} kb ({mut_glob_percent:}%)");
let diff = &r.0;
println!(
"| {filename:28} | {original_size:13} kb | {host_fn:>19} | {mut_glob:>22} | {diff:+8}% |"
);
} }
} }