mirror of
https://github.com/pezkuwichain/wasm-instrument.git
synced 2026-04-22 11:27:59 +00:00
Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0b222d76fd | |||
| 2e29897786 | |||
| b974199ad5 | |||
| 5f293e47ff | |||
| 82f3638ba4 | |||
| 74945dc206 | |||
| 1b7b54ca83 | |||
| 39fa497aee | |||
| 0c739d92c4 | |||
| 060317f70d | |||
| 998bbb8bd5 | |||
| fd3b1f856b | |||
| fdce5c64f1 | |||
| 6307588b3d | |||
| ca0a83ff91 | |||
| 16469520a4 | |||
| 1d83a81c05 | |||
| 7fa2cd3cd2 | |||
| 28e964d9d1 | |||
| c07099b8d5 | |||
| 6039337457 | |||
| 51daef9eba | |||
| 3ba9c2cfa1 | |||
| 16297ab942 | |||
| 325cdb8969 | |||
| 53f640af63 | |||
| a667e38b5a | |||
| b67ac31310 | |||
| bee3aebeef | |||
| 74716698f5 | |||
| 54213f77f6 | |||
| 23a2347f1f | |||
| 54c4f8f878 |
+41
-26
@@ -11,7 +11,7 @@ env:
|
||||
|
||||
jobs:
|
||||
rustfmt:
|
||||
runs-on: "ubuntu-latest"
|
||||
runs-on: "ubuntu_20_64_core"
|
||||
steps:
|
||||
- name: Install Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
@@ -20,9 +20,9 @@ jobs:
|
||||
toolchain: nightly
|
||||
components: rustfmt
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Cargo fmt
|
||||
- name: Fmt
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
toolchain: nightly
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
args: --all -- --check
|
||||
|
||||
clippy:
|
||||
runs-on: "ubuntu-latest"
|
||||
runs-on: "ubuntu_20_64_core"
|
||||
steps:
|
||||
- name: Install Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
@@ -38,60 +38,75 @@ jobs:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
components: clippy
|
||||
default: true
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Cargo clippy
|
||||
uses: actions-rs/cargo@v1
|
||||
- name: Clippy
|
||||
uses: actions-rs/clippy-check@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
command: clippy
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
args: --all-targets --all-features -- -D warnings
|
||||
|
||||
test:
|
||||
strategy:
|
||||
matrix:
|
||||
os: ["ubuntu-latest", "macos-latest", "windows-latest"]
|
||||
toolchain: ["stable", "nightly"]
|
||||
runs-on: ${{ matrix.os }}
|
||||
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: ${{ matrix.toolchain }}
|
||||
toolchain: stable
|
||||
default: true
|
||||
|
||||
- name: Set git to use LF
|
||||
run: |
|
||||
git config --global core.autocrlf false
|
||||
git config --global core.eol lf
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Cargo build
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.toolchain }}
|
||||
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:
|
||||
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
|
||||
|
||||
test:
|
||||
strategy:
|
||||
matrix:
|
||||
os: ["ubuntu_20_64_core", "macos-latest", "windows-latest"]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Install Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
default: true
|
||||
|
||||
- name: Set git to use LF
|
||||
run: |
|
||||
git config --global core.autocrlf false
|
||||
git config --global core.eol lf
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Cargo test
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.toolchain }}
|
||||
command: test
|
||||
args: --all-features
|
||||
|
||||
|
||||
+11
-6
@@ -12,9 +12,14 @@ repository = "https://github.com/paritytech/wasm-instrument"
|
||||
include = ["src/**/*", "LICENSE-*", "README.md"]
|
||||
|
||||
[[bench]]
|
||||
name = "benches"
|
||||
name = "instrumentation"
|
||||
harness = false
|
||||
path = "benches/benches.rs"
|
||||
path = "benches/instrumentation.rs"
|
||||
|
||||
[[bench]]
|
||||
name = "execution"
|
||||
harness = false
|
||||
path = "benches/execution.rs"
|
||||
|
||||
[profile.bench]
|
||||
lto = "fat"
|
||||
@@ -25,14 +30,14 @@ parity-wasm = { version = "0.45", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
binaryen = "0.12"
|
||||
criterion = "0.4"
|
||||
criterion = "0.5"
|
||||
diff = "0.1"
|
||||
pretty_assertions = "1"
|
||||
rand = "0.8"
|
||||
wat = "1"
|
||||
wasmparser = "0.95"
|
||||
wasmprinter = "0.2"
|
||||
wasmi = "0.20"
|
||||
wasmparser = "0.206"
|
||||
wasmprinter = "0.200"
|
||||
wasmi = "0.31"
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
|
||||
@@ -1,574 +0,0 @@
|
||||
use criterion::{
|
||||
criterion_group, criterion_main, measurement::Measurement, Bencher, BenchmarkGroup, Criterion,
|
||||
Throughput,
|
||||
};
|
||||
use std::{
|
||||
fs::{read, read_dir},
|
||||
path::PathBuf,
|
||||
slice,
|
||||
};
|
||||
use wasm_instrument::{
|
||||
gas_metering::{self, host_function, mutable_global, Backend, ConstantCostRules},
|
||||
inject_stack_limiter,
|
||||
parity_wasm::{deserialize_buffer, elements::Module, serialize},
|
||||
};
|
||||
|
||||
fn fixture_dir() -> PathBuf {
|
||||
let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
path.push("benches");
|
||||
path.push("fixtures");
|
||||
path.push("wasm");
|
||||
path
|
||||
}
|
||||
|
||||
fn any_fixture<F, M>(group: &mut BenchmarkGroup<M>, f: F)
|
||||
where
|
||||
F: Fn(Module),
|
||||
M: Measurement,
|
||||
{
|
||||
for entry in read_dir(fixture_dir()).unwrap() {
|
||||
let entry = entry.unwrap();
|
||||
let bytes = read(&entry.path()).unwrap();
|
||||
group.throughput(Throughput::Bytes(bytes.len().try_into().unwrap()));
|
||||
group.bench_with_input(entry.file_name().to_str().unwrap(), &bytes, |bench, input| {
|
||||
bench.iter(|| f(deserialize_buffer(input).unwrap()))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn gas_metering(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("Gas Metering");
|
||||
any_fixture(&mut group, |module| {
|
||||
gas_metering::inject(
|
||||
module,
|
||||
host_function::Injector::new("env", "gas"),
|
||||
&ConstantCostRules::default(),
|
||||
)
|
||||
.unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
fn stack_height_limiter(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("Stack Height Limiter");
|
||||
any_fixture(&mut group, |module| {
|
||||
inject_stack_limiter(module, 128).unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||
use wasmi::{
|
||||
self,
|
||||
core::{Pages, Value, F32},
|
||||
Caller, Config, Engine, Extern, Func, Instance, Linker, Memory, StackLimits, Store,
|
||||
};
|
||||
fn prepare_module<P: Backend>(backend: P, input: &[u8]) -> (wasmi::Module, Store<u64>) {
|
||||
let module = deserialize_buffer(input).unwrap();
|
||||
let instrumented_module =
|
||||
gas_metering::inject(module, backend, &ConstantCostRules::default()).unwrap();
|
||||
let input = serialize(instrumented_module).unwrap();
|
||||
// Prepare wasmi
|
||||
let engine = Engine::new(&bench_config());
|
||||
let module = wasmi::Module::new(&engine, &mut &input[..]).unwrap();
|
||||
// Init host state with maximum gas_left
|
||||
let store = Store::new(&engine, u64::MAX);
|
||||
|
||||
(module, store)
|
||||
}
|
||||
|
||||
fn add_gas_host_func(linker: &mut Linker<u64>, store: &mut Store<u64>) {
|
||||
// Create gas host function
|
||||
let host_gas = Func::wrap(store, |mut caller: Caller<'_, u64>, param: u64| {
|
||||
*caller.host_data_mut() -= param;
|
||||
});
|
||||
// Link the gas host function
|
||||
linker.define("env", "gas", host_gas).unwrap();
|
||||
}
|
||||
|
||||
fn add_gas_left_global(instance: &Instance, mut store: Store<u64>) -> Store<u64> {
|
||||
instance
|
||||
.get_export(&mut store, "gas_left")
|
||||
.and_then(Extern::into_global)
|
||||
.unwrap()
|
||||
.set(&mut store, Value::I64(-1i64)) // the same as u64::MAX
|
||||
.unwrap();
|
||||
store
|
||||
}
|
||||
|
||||
fn gas_metered_coremark(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("coremark, instrumented");
|
||||
// Benchmark host_function::Injector
|
||||
let wasm_filename = "coremark_minimal.wasm";
|
||||
let bytes = read(fixture_dir().join(wasm_filename)).unwrap();
|
||||
group.bench_function("with host_function::Injector", |bench| {
|
||||
let backend = host_function::Injector::new("env", "gas");
|
||||
let (module, mut store) = prepare_module(backend, &bytes);
|
||||
// Link the host functions with the imported ones
|
||||
let mut linker = <Linker<u64>>::new();
|
||||
add_gas_host_func(&mut linker, &mut store);
|
||||
// Create clock_ms host function.
|
||||
let host_clock_ms = Func::wrap(&mut store, || {
|
||||
SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() as u64
|
||||
});
|
||||
// Link the time measurer for the coremark wasm
|
||||
linker.define("env", "clock_ms", host_clock_ms).unwrap();
|
||||
|
||||
let instance = linker.instantiate(&mut store, &module).unwrap().start(&mut store).unwrap();
|
||||
|
||||
bench.iter(|| {
|
||||
let run = instance
|
||||
.get_export(&mut store, "run")
|
||||
.and_then(Extern::into_func)
|
||||
.unwrap()
|
||||
.typed::<(), F32>(&mut store)
|
||||
.unwrap();
|
||||
// Call the wasm!
|
||||
run.call(&mut store, ()).unwrap();
|
||||
})
|
||||
});
|
||||
|
||||
group.bench_function("with mutable_global::Injector", |bench| {
|
||||
let backend = mutable_global::Injector::new("gas_left");
|
||||
let (module, mut store) = prepare_module(backend, &bytes);
|
||||
// Add the gas_left mutable global
|
||||
let mut linker = <Linker<u64>>::new();
|
||||
// Create clock_ms host function.
|
||||
let host_clock_ms = Func::wrap(&mut store, || {
|
||||
SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() as u64
|
||||
});
|
||||
// Link the time measurer for the coremark wasm
|
||||
linker.define("env", "clock_ms", host_clock_ms).unwrap();
|
||||
|
||||
let instance = linker.instantiate(&mut store, &module).unwrap().start(&mut store).unwrap();
|
||||
let mut store = add_gas_left_global(&instance, store);
|
||||
|
||||
bench.iter(|| {
|
||||
let run = instance
|
||||
.get_export(&mut store, "run")
|
||||
.and_then(Extern::into_func)
|
||||
.unwrap()
|
||||
.typed::<(), F32>(&mut store)
|
||||
.unwrap();
|
||||
// Call the wasm!
|
||||
run.call(&mut store, ()).unwrap();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/// Converts the `.wat` encoded `bytes` into `.wasm` encoded bytes.
|
||||
pub fn wat2wasm(bytes: &[u8]) -> Vec<u8> {
|
||||
wat::parse_bytes(bytes).unwrap().into_owned()
|
||||
}
|
||||
|
||||
/// Returns a [`Config`] useful for benchmarking.
|
||||
fn bench_config() -> Config {
|
||||
let mut config = Config::default();
|
||||
config.set_stack_limits(StackLimits::new(1024, 1024 * 1024, 64 * 1024).unwrap());
|
||||
config
|
||||
}
|
||||
|
||||
fn gas_metered_recursive_ok(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("recursive_ok, instrumented");
|
||||
const RECURSIVE_DEPTH: i32 = 8000;
|
||||
let wasm_bytes = wat2wasm(include_bytes!("fixtures/wat/recursive_ok.wat"));
|
||||
|
||||
group.bench_function("with host_function::Injector", |bench| {
|
||||
let backend = host_function::Injector::new("env", "gas");
|
||||
let (module, mut store) = prepare_module(backend, &wasm_bytes);
|
||||
// Link the host function with the imported one
|
||||
let mut linker = <Linker<u64>>::new();
|
||||
add_gas_host_func(&mut linker, &mut store);
|
||||
let instance = linker.instantiate(&mut store, &module).unwrap().start(&mut store).unwrap();
|
||||
|
||||
let bench_call = instance.get_export(&store, "call").and_then(Extern::into_func).unwrap();
|
||||
let mut result = [Value::I32(0)];
|
||||
|
||||
bench.iter(|| {
|
||||
bench_call
|
||||
.call(&mut store, &[Value::I32(RECURSIVE_DEPTH)], &mut result)
|
||||
.unwrap();
|
||||
assert_eq!(result, [Value::I32(0)]);
|
||||
})
|
||||
});
|
||||
|
||||
group.bench_function("with mutable_global::Injector", |bench| {
|
||||
let backend = mutable_global::Injector::new("gas_left");
|
||||
let (module, mut store) = prepare_module(backend, &wasm_bytes);
|
||||
// Add the gas_left mutable global
|
||||
let linker = <Linker<u64>>::new();
|
||||
let instance = linker.instantiate(&mut store, &module).unwrap().start(&mut store).unwrap();
|
||||
let mut store = add_gas_left_global(&instance, store);
|
||||
|
||||
let bench_call = instance.get_export(&store, "call").and_then(Extern::into_func).unwrap();
|
||||
let mut result = [Value::I32(0)];
|
||||
|
||||
bench.iter(|| {
|
||||
bench_call
|
||||
.call(&mut store, &[Value::I32(RECURSIVE_DEPTH)], &mut result)
|
||||
.unwrap();
|
||||
assert_eq!(result, [Value::I32(0)]);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn gas_metered_fibonacci_recursive(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("fibonacci_recursive, instrumented");
|
||||
const FIBONACCI_REC_N: i64 = 10;
|
||||
let wasm_bytes = wat2wasm(include_bytes!("fixtures/wat/fibonacci.wat"));
|
||||
|
||||
group.bench_function("with host_function::Injector", |bench| {
|
||||
let backend = host_function::Injector::new("env", "gas");
|
||||
let (module, mut store) = prepare_module(backend, &wasm_bytes);
|
||||
// Link the host function with the imported one
|
||||
let mut linker = <Linker<u64>>::new();
|
||||
add_gas_host_func(&mut linker, &mut store);
|
||||
let instance = linker.instantiate(&mut store, &module).unwrap().start(&mut store).unwrap();
|
||||
|
||||
let bench_call = instance
|
||||
.get_export(&store, "fib_recursive")
|
||||
.and_then(Extern::into_func)
|
||||
.unwrap();
|
||||
let mut result = [Value::I32(0)];
|
||||
|
||||
bench.iter(|| {
|
||||
bench_call
|
||||
.call(&mut store, &[Value::I64(FIBONACCI_REC_N)], &mut result)
|
||||
.unwrap();
|
||||
});
|
||||
});
|
||||
|
||||
group.bench_function("with mutable_global::Injector", |bench| {
|
||||
let backend = mutable_global::Injector::new("gas_left");
|
||||
let (module, mut store) = prepare_module(backend, &wasm_bytes);
|
||||
|
||||
// Add the gas_left mutable global
|
||||
let linker = <Linker<u64>>::new();
|
||||
let instance = linker.instantiate(&mut store, &module).unwrap().start(&mut store).unwrap();
|
||||
let mut store = add_gas_left_global(&instance, store);
|
||||
|
||||
let bench_call = instance
|
||||
.get_export(&store, "fib_recursive")
|
||||
.and_then(Extern::into_func)
|
||||
.unwrap();
|
||||
let mut result = [Value::I32(0)];
|
||||
|
||||
bench.iter(|| {
|
||||
bench_call
|
||||
.call(&mut store, &[Value::I64(FIBONACCI_REC_N)], &mut result)
|
||||
.unwrap();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn gas_metered_fac_recursive(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("factorial_recursive, instrumented");
|
||||
let wasm_bytes = wat2wasm(include_bytes!("fixtures/wat/factorial.wat"));
|
||||
|
||||
group.bench_function("with host_function::Injector", |b| {
|
||||
let backend = host_function::Injector::new("env", "gas");
|
||||
let (module, mut store) = prepare_module(backend, &wasm_bytes);
|
||||
// Link the host function with the imported one
|
||||
let mut linker = <Linker<u64>>::new();
|
||||
add_gas_host_func(&mut linker, &mut store);
|
||||
let instance = linker.instantiate(&mut store, &module).unwrap().start(&mut store).unwrap();
|
||||
let fac = instance
|
||||
.get_export(&store, "recursive_factorial")
|
||||
.and_then(Extern::into_func)
|
||||
.unwrap();
|
||||
let mut result = [Value::I64(0)];
|
||||
|
||||
b.iter(|| {
|
||||
fac.call(&mut store, &[Value::I64(25)], &mut result).unwrap();
|
||||
assert_eq!(result, [Value::I64(7034535277573963776)]);
|
||||
})
|
||||
});
|
||||
|
||||
group.bench_function("with mutable_global::Injector", |b| {
|
||||
let backend = mutable_global::Injector::new("gas_left");
|
||||
let (module, mut store) = prepare_module(backend, &wasm_bytes);
|
||||
|
||||
// Add the gas_left mutable global
|
||||
let linker = <Linker<u64>>::new();
|
||||
let instance = linker.instantiate(&mut store, &module).unwrap().start(&mut store).unwrap();
|
||||
let mut store = add_gas_left_global(&instance, store);
|
||||
let fac = instance
|
||||
.get_export(&store, "recursive_factorial")
|
||||
.and_then(Extern::into_func)
|
||||
.unwrap();
|
||||
let mut result = [Value::I64(0)];
|
||||
|
||||
b.iter(|| {
|
||||
fac.call(&mut store, &[Value::I64(25)], &mut result).unwrap();
|
||||
assert_eq!(result, [Value::I64(7034535277573963776)]);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn gas_metered_count_until(c: &mut Criterion) {
|
||||
const COUNT_UNTIL: i32 = 100_000;
|
||||
let mut group = c.benchmark_group("count_until, instrumented");
|
||||
let wasm_bytes = wat2wasm(include_bytes!("fixtures/wat/count_until.wat"));
|
||||
|
||||
group.bench_function("with host_function::Injector", |b| {
|
||||
let backend = host_function::Injector::new("env", "gas");
|
||||
let (module, mut store) = prepare_module(backend, &wasm_bytes);
|
||||
// Link the host function with the imported one
|
||||
let mut linker = <Linker<u64>>::new();
|
||||
add_gas_host_func(&mut linker, &mut store);
|
||||
let instance = linker.instantiate(&mut store, &module).unwrap().start(&mut store).unwrap();
|
||||
let count_until =
|
||||
instance.get_export(&store, "count_until").and_then(Extern::into_func).unwrap();
|
||||
let mut result = [Value::I32(0)];
|
||||
|
||||
b.iter(|| {
|
||||
count_until.call(&mut store, &[Value::I32(COUNT_UNTIL)], &mut result).unwrap();
|
||||
assert_eq!(result, [Value::I32(COUNT_UNTIL)]);
|
||||
})
|
||||
});
|
||||
|
||||
group.bench_function("with mutable_global::Injector", |b| {
|
||||
let backend = mutable_global::Injector::new("gas_left");
|
||||
let (module, mut store) = prepare_module(backend, &wasm_bytes);
|
||||
|
||||
// Add the gas_left mutable global
|
||||
let linker = <Linker<u64>>::new();
|
||||
let instance = linker.instantiate(&mut store, &module).unwrap().start(&mut store).unwrap();
|
||||
let mut store = add_gas_left_global(&instance, store);
|
||||
let count_until =
|
||||
instance.get_export(&store, "count_until").and_then(Extern::into_func).unwrap();
|
||||
let mut result = [Value::I32(0)];
|
||||
|
||||
b.iter(|| {
|
||||
count_until.call(&mut store, &[Value::I32(COUNT_UNTIL)], &mut result).unwrap();
|
||||
assert_eq!(result, [Value::I32(COUNT_UNTIL)]);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn gas_metered_vec_add(c: &mut Criterion) {
|
||||
fn test_for<A, B>(
|
||||
b: &mut Bencher,
|
||||
vec_add: Func,
|
||||
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 = [
|
||||
Value::I32(ptr_result as i32),
|
||||
Value::I32(ptr_a as i32),
|
||||
Value::I32(ptr_b as i32),
|
||||
Value::I32(len as i32),
|
||||
];
|
||||
b.iter(|| {
|
||||
vec_add.call(&mut store, ¶ms, &mut []).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, instrumented");
|
||||
let wasm_bytes = wat2wasm(include_bytes!("fixtures/wat/memory-vec-add.wat"));
|
||||
const LEN: usize = 100_000;
|
||||
|
||||
group.bench_function("with host_function::Injector", |b| {
|
||||
let backend = host_function::Injector::new("env", "gas");
|
||||
let (module, mut store) = prepare_module(backend, &wasm_bytes);
|
||||
// Link the host function with the imported one
|
||||
let mut linker = <Linker<u64>>::new();
|
||||
add_gas_host_func(&mut linker, &mut store);
|
||||
let instance = linker.instantiate(&mut store, &module).unwrap().start(&mut store).unwrap();
|
||||
let vec_add = instance.get_export(&store, "vec_add").and_then(Extern::into_func).unwrap();
|
||||
let mem = instance.get_export(&store, "mem").and_then(Extern::into_memory).unwrap();
|
||||
mem.grow(&mut store, Pages::new(25).unwrap()).unwrap();
|
||||
test_for(
|
||||
b,
|
||||
vec_add,
|
||||
&mut store,
|
||||
mem,
|
||||
LEN,
|
||||
(0..LEN).map(|i| (i * i) as i32),
|
||||
(0..LEN).map(|i| (i * 10) as i32),
|
||||
)
|
||||
});
|
||||
|
||||
group.bench_function("with mutable_global::Injector", |b| {
|
||||
let backend = mutable_global::Injector::new("gas_left");
|
||||
let (module, mut store) = prepare_module(backend, &wasm_bytes);
|
||||
// Add the gas_left mutable global
|
||||
let linker = <Linker<u64>>::new();
|
||||
let instance = linker.instantiate(&mut store, &module).unwrap().start(&mut store).unwrap();
|
||||
let mut store = add_gas_left_global(&instance, store);
|
||||
let vec_add = instance.get_export(&store, "vec_add").and_then(Extern::into_func).unwrap();
|
||||
let mem = instance.get_export(&store, "mem").and_then(Extern::into_memory).unwrap();
|
||||
mem.grow(&mut store, Pages::new(25).unwrap()).unwrap();
|
||||
test_for(
|
||||
b,
|
||||
vec_add,
|
||||
&mut 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, instrumented");
|
||||
let wasm_filename = "wasm_kernel.wasm";
|
||||
let wasm_bytes = read(fixture_dir().join(wasm_filename)).unwrap();
|
||||
|
||||
group.bench_function("with host_function::Injector", |b| {
|
||||
let backend = host_function::Injector::new("env", "gas");
|
||||
let (module, mut store) = prepare_module(backend, &wasm_bytes);
|
||||
// Link the host function with the imported one
|
||||
let mut linker = <Linker<u64>>::new();
|
||||
add_gas_host_func(&mut linker, &mut store);
|
||||
let instance = linker.instantiate(&mut store, &module).unwrap().start(&mut store).unwrap();
|
||||
let prepare = instance
|
||||
.get_export(&store, "prepare_tiny_keccak")
|
||||
.and_then(Extern::into_func)
|
||||
.unwrap();
|
||||
let keccak = instance
|
||||
.get_export(&store, "bench_tiny_keccak")
|
||||
.and_then(Extern::into_func)
|
||||
.unwrap();
|
||||
let mut test_data_ptr = Value::I32(0);
|
||||
prepare.call(&mut store, &[], slice::from_mut(&mut test_data_ptr)).unwrap();
|
||||
b.iter(|| {
|
||||
keccak.call(&mut store, slice::from_ref(&test_data_ptr), &mut []).unwrap();
|
||||
})
|
||||
});
|
||||
|
||||
group.bench_function("with mutable_global::Injector", |b| {
|
||||
let backend = mutable_global::Injector::new("gas_left");
|
||||
let (module, mut store) = prepare_module(backend, &wasm_bytes);
|
||||
// Add the gas_left mutable global
|
||||
let linker = <Linker<u64>>::new();
|
||||
let instance = linker.instantiate(&mut store, &module).unwrap().start(&mut store).unwrap();
|
||||
let mut store = add_gas_left_global(&instance, store);
|
||||
let prepare = instance
|
||||
.get_export(&store, "prepare_tiny_keccak")
|
||||
.and_then(Extern::into_func)
|
||||
.unwrap();
|
||||
let keccak = instance
|
||||
.get_export(&store, "bench_tiny_keccak")
|
||||
.and_then(Extern::into_func)
|
||||
.unwrap();
|
||||
let mut test_data_ptr = Value::I32(0);
|
||||
prepare.call(&mut store, &[], slice::from_mut(&mut test_data_ptr)).unwrap();
|
||||
b.iter(|| {
|
||||
keccak.call(&mut store, slice::from_ref(&test_data_ptr), &mut []).unwrap();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn gas_metered_global_bump(c: &mut Criterion) {
|
||||
const BUMP_AMOUNT: i32 = 100_000;
|
||||
let mut group = c.benchmark_group("global_bump, instrumented");
|
||||
let wasm_bytes = wat2wasm(include_bytes!("fixtures/wat/global_bump.wat"));
|
||||
|
||||
group.bench_function("with host_function::Injector", |b| {
|
||||
let backend = host_function::Injector::new("env", "gas");
|
||||
let (module, mut store) = prepare_module(backend, &wasm_bytes);
|
||||
// Link the host function with the imported one
|
||||
let mut linker = <Linker<u64>>::new();
|
||||
add_gas_host_func(&mut linker, &mut store);
|
||||
let instance = linker.instantiate(&mut store, &module).unwrap().start(&mut store).unwrap();
|
||||
let bump = instance.get_export(&store, "bump").and_then(Extern::into_func).unwrap();
|
||||
let mut result = [Value::I32(0)];
|
||||
|
||||
b.iter(|| {
|
||||
bump.call(&mut store, &[Value::I32(BUMP_AMOUNT)], &mut result).unwrap();
|
||||
assert_eq!(result, [Value::I32(BUMP_AMOUNT)]);
|
||||
})
|
||||
});
|
||||
|
||||
group.bench_function("with mutable_global::Injector", |b| {
|
||||
let backend = mutable_global::Injector::new("gas_left");
|
||||
let (module, mut store) = prepare_module(backend, &wasm_bytes);
|
||||
// Add the gas_left mutable global
|
||||
let linker = <Linker<u64>>::new();
|
||||
let instance = linker.instantiate(&mut store, &module).unwrap().start(&mut store).unwrap();
|
||||
let mut store = add_gas_left_global(&instance, store);
|
||||
let bump = instance.get_export(&store, "bump").and_then(Extern::into_func).unwrap();
|
||||
let mut result = [Value::I32(0)];
|
||||
|
||||
b.iter(|| {
|
||||
bump.call(&mut store, &[Value::I32(BUMP_AMOUNT)], &mut result).unwrap();
|
||||
assert_eq!(result, [Value::I32(BUMP_AMOUNT)]);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(benches, gas_metering, stack_height_limiter);
|
||||
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);
|
||||
@@ -0,0 +1,437 @@
|
||||
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);
|
||||
@@ -0,0 +1,58 @@
|
||||
use criterion::{
|
||||
criterion_group, criterion_main, measurement::Measurement, BenchmarkGroup, Criterion,
|
||||
Throughput,
|
||||
};
|
||||
use std::{
|
||||
fs::{read, read_dir},
|
||||
path::PathBuf,
|
||||
};
|
||||
use wasm_instrument::{
|
||||
gas_metering::{self, host_function, ConstantCostRules},
|
||||
inject_stack_limiter,
|
||||
parity_wasm::{deserialize_buffer, elements::Module},
|
||||
};
|
||||
|
||||
fn fixture_dir() -> PathBuf {
|
||||
let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
path.push("benches");
|
||||
path.push("fixtures");
|
||||
path.push("wasm");
|
||||
path
|
||||
}
|
||||
|
||||
fn for_fixtures<F, M>(group: &mut BenchmarkGroup<M>, f: F)
|
||||
where
|
||||
F: Fn(Module),
|
||||
M: Measurement,
|
||||
{
|
||||
for entry in read_dir(fixture_dir()).unwrap() {
|
||||
let entry = entry.unwrap();
|
||||
let bytes = read(entry.path()).unwrap();
|
||||
group.throughput(Throughput::Bytes(bytes.len().try_into().unwrap()));
|
||||
group.bench_with_input(entry.file_name().to_str().unwrap(), &bytes, |bench, input| {
|
||||
bench.iter(|| f(deserialize_buffer(input).unwrap()))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn gas_metering(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("Gas Metering");
|
||||
for_fixtures(&mut group, |module| {
|
||||
gas_metering::inject(
|
||||
module,
|
||||
host_function::Injector::new("env", "gas"),
|
||||
&ConstantCostRules::default(),
|
||||
)
|
||||
.unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
fn stack_height_limiter(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("Stack Height Limiter");
|
||||
for_fixtures(&mut group, |module| {
|
||||
inject_stack_limiter(module, 128).unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(benches, gas_metering, stack_height_limiter);
|
||||
criterion_main!(benches);
|
||||
@@ -112,20 +112,21 @@ pub mod mutable_global {
|
||||
];
|
||||
|
||||
// calculate gas used for the gas charging func execution itself
|
||||
let mut gas_fn_cost = func_instructions.iter().fold(0, |cost, instruction| {
|
||||
cost + (rules.instruction_cost(instruction).unwrap_or(0) as u64)
|
||||
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 = vec![
|
||||
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, instruction| {
|
||||
cost + (rules.instruction_cost(instruction).unwrap_or(0) as u64)
|
||||
.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 {
|
||||
|
||||
+98
-98
@@ -166,7 +166,7 @@ pub fn inject<R: Rules, B: Backend>(
|
||||
let functions_space = module.functions_space() as u32;
|
||||
let gas_global_idx = module.globals_space() as u32;
|
||||
|
||||
let mut mbuilder = builder::from_module(module);
|
||||
let mut mbuilder = builder::from_module(module.clone());
|
||||
|
||||
// Calculate the indexes and gas function cost,
|
||||
// for external gas function the cost is counted on the host side
|
||||
@@ -224,15 +224,14 @@ pub fn inject<R: Rules, B: Backend>(
|
||||
};
|
||||
|
||||
// We need the built the module for making injections to its blocks
|
||||
let mut module = mbuilder.build();
|
||||
let mut resulting_module = mbuilder.build();
|
||||
|
||||
let mut need_grow_counter = false;
|
||||
let mut error = false;
|
||||
|
||||
let mut result = Ok(());
|
||||
// Iterate over module sections and perform needed transformations.
|
||||
// Indexes are needed to be fixed up in `GasMeter::External` case, as it adds an imported
|
||||
// function, which goes to the beginning of the module's functions space.
|
||||
for section in module.sections_mut() {
|
||||
'outer: for section in resulting_module.sections_mut() {
|
||||
match section {
|
||||
elements::Section::Code(code_section) => {
|
||||
let injection_targets = match gas_meter {
|
||||
@@ -257,19 +256,22 @@ pub fn inject<R: Rules, B: Backend>(
|
||||
}
|
||||
}
|
||||
}
|
||||
let locals_count =
|
||||
func_body.locals().iter().map(|val_type| val_type.count()).sum();
|
||||
if inject_counter(
|
||||
func_body.code_mut(),
|
||||
gas_fn_cost,
|
||||
locals_count,
|
||||
rules,
|
||||
gas_func_idx,
|
||||
)
|
||||
.is_err()
|
||||
{
|
||||
error = true;
|
||||
break
|
||||
result = func_body
|
||||
.locals()
|
||||
.iter()
|
||||
.try_fold(0u32, |count, val_type| count.checked_add(val_type.count()))
|
||||
.ok_or(())
|
||||
.and_then(|locals_count| {
|
||||
inject_counter(
|
||||
func_body.code_mut(),
|
||||
gas_fn_cost,
|
||||
locals_count,
|
||||
rules,
|
||||
gas_func_idx,
|
||||
)
|
||||
});
|
||||
if result.is_err() {
|
||||
break 'outer
|
||||
}
|
||||
if rules.memory_grow_cost().enabled() &&
|
||||
inject_grow_counter(func_body.code_mut(), total_func) > 0
|
||||
@@ -325,14 +327,12 @@ pub fn inject<R: Rules, B: Backend>(
|
||||
}
|
||||
}
|
||||
|
||||
if error {
|
||||
return Err(module)
|
||||
}
|
||||
result.map_err(|_| module)?;
|
||||
|
||||
if need_grow_counter {
|
||||
Ok(add_grow_counter(module, rules, gas_func_idx))
|
||||
Ok(add_grow_counter(resulting_module, rules, gas_func_idx))
|
||||
} else {
|
||||
Ok(module)
|
||||
Ok(resulting_module)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,12 +342,12 @@ pub fn inject<R: Rules, B: Backend>(
|
||||
///
|
||||
/// An example of block:
|
||||
///
|
||||
/// ```ignore
|
||||
/// ```wasm
|
||||
/// loop
|
||||
/// i32.const 1
|
||||
/// get_local 0
|
||||
/// local.get 0
|
||||
/// i32.sub
|
||||
/// tee_local 0
|
||||
/// local.tee 0
|
||||
/// br_if 0
|
||||
/// end
|
||||
/// ```
|
||||
@@ -1082,13 +1082,13 @@ mod tests {
|
||||
input = r#"
|
||||
(module
|
||||
(func (result i32)
|
||||
(get_global 0)))
|
||||
(global.get 0)))
|
||||
"#;
|
||||
expected = r#"
|
||||
(module
|
||||
(func (result i32)
|
||||
(call 0 (i64.const 1))
|
||||
(get_global 0)))
|
||||
(global.get 0)))
|
||||
"#
|
||||
}
|
||||
|
||||
@@ -1097,23 +1097,23 @@ mod tests {
|
||||
input = r#"
|
||||
(module
|
||||
(func (result i32)
|
||||
(get_global 0)
|
||||
(global.get 0)
|
||||
(block
|
||||
(get_global 0)
|
||||
(get_global 0)
|
||||
(get_global 0))
|
||||
(get_global 0)))
|
||||
(global.get 0)
|
||||
(global.get 0)
|
||||
(global.get 0))
|
||||
(global.get 0)))
|
||||
"#;
|
||||
expected = r#"
|
||||
(module
|
||||
(func (result i32)
|
||||
(call 0 (i64.const 6))
|
||||
(get_global 0)
|
||||
(global.get 0)
|
||||
(block
|
||||
(get_global 0)
|
||||
(get_global 0)
|
||||
(get_global 0))
|
||||
(get_global 0)))
|
||||
(global.get 0)
|
||||
(global.get 0)
|
||||
(global.get 0))
|
||||
(global.get 0)))
|
||||
"#
|
||||
}
|
||||
|
||||
@@ -1122,33 +1122,33 @@ mod tests {
|
||||
input = r#"
|
||||
(module
|
||||
(func (result i32)
|
||||
(get_global 0)
|
||||
(global.get 0)
|
||||
(if
|
||||
(then
|
||||
(get_global 0)
|
||||
(get_global 0)
|
||||
(get_global 0))
|
||||
(global.get 0)
|
||||
(global.get 0)
|
||||
(global.get 0))
|
||||
(else
|
||||
(get_global 0)
|
||||
(get_global 0)))
|
||||
(get_global 0)))
|
||||
(global.get 0)
|
||||
(global.get 0)))
|
||||
(global.get 0)))
|
||||
"#;
|
||||
expected = r#"
|
||||
(module
|
||||
(func (result i32)
|
||||
(call 0 (i64.const 3))
|
||||
(get_global 0)
|
||||
(global.get 0)
|
||||
(if
|
||||
(then
|
||||
(call 0 (i64.const 3))
|
||||
(get_global 0)
|
||||
(get_global 0)
|
||||
(get_global 0))
|
||||
(global.get 0)
|
||||
(global.get 0)
|
||||
(global.get 0))
|
||||
(else
|
||||
(call 0 (i64.const 2))
|
||||
(get_global 0)
|
||||
(get_global 0)))
|
||||
(get_global 0)))
|
||||
(global.get 0)
|
||||
(global.get 0)))
|
||||
(global.get 0)))
|
||||
"#
|
||||
}
|
||||
|
||||
@@ -1157,28 +1157,28 @@ mod tests {
|
||||
input = r#"
|
||||
(module
|
||||
(func (result i32)
|
||||
(get_global 0)
|
||||
(global.get 0)
|
||||
(block
|
||||
(get_global 0)
|
||||
(global.get 0)
|
||||
(drop)
|
||||
(br 0)
|
||||
(get_global 0)
|
||||
(global.get 0)
|
||||
(drop))
|
||||
(get_global 0)))
|
||||
(global.get 0)))
|
||||
"#;
|
||||
expected = r#"
|
||||
(module
|
||||
(func (result i32)
|
||||
(call 0 (i64.const 6))
|
||||
(get_global 0)
|
||||
(global.get 0)
|
||||
(block
|
||||
(get_global 0)
|
||||
(global.get 0)
|
||||
(drop)
|
||||
(br 0)
|
||||
(call 0 (i64.const 2))
|
||||
(get_global 0)
|
||||
(global.get 0)
|
||||
(drop))
|
||||
(get_global 0)))
|
||||
(global.get 0)))
|
||||
"#
|
||||
}
|
||||
|
||||
@@ -1187,37 +1187,37 @@ mod tests {
|
||||
input = r#"
|
||||
(module
|
||||
(func (result i32)
|
||||
(get_global 0)
|
||||
(global.get 0)
|
||||
(block
|
||||
(get_global 0)
|
||||
(global.get 0)
|
||||
(if
|
||||
(then
|
||||
(get_global 0)
|
||||
(get_global 0)
|
||||
(global.get 0)
|
||||
(global.get 0)
|
||||
(drop)
|
||||
(br_if 1)))
|
||||
(get_global 0)
|
||||
(global.get 0)
|
||||
(drop))
|
||||
(get_global 0)))
|
||||
(global.get 0)))
|
||||
"#;
|
||||
expected = r#"
|
||||
(module
|
||||
(func (result i32)
|
||||
(call 0 (i64.const 5))
|
||||
(get_global 0)
|
||||
(global.get 0)
|
||||
(block
|
||||
(get_global 0)
|
||||
(global.get 0)
|
||||
(if
|
||||
(then
|
||||
(call 0 (i64.const 4))
|
||||
(get_global 0)
|
||||
(get_global 0)
|
||||
(global.get 0)
|
||||
(global.get 0)
|
||||
(drop)
|
||||
(br_if 1)))
|
||||
(call 0 (i64.const 2))
|
||||
(get_global 0)
|
||||
(global.get 0)
|
||||
(drop))
|
||||
(get_global 0)))
|
||||
(global.get 0)))
|
||||
"#
|
||||
}
|
||||
|
||||
@@ -1226,44 +1226,44 @@ mod tests {
|
||||
input = r#"
|
||||
(module
|
||||
(func (result i32)
|
||||
(get_global 0)
|
||||
(global.get 0)
|
||||
(loop
|
||||
(get_global 0)
|
||||
(global.get 0)
|
||||
(if
|
||||
(then
|
||||
(get_global 0)
|
||||
(global.get 0)
|
||||
(br_if 0))
|
||||
(else
|
||||
(get_global 0)
|
||||
(get_global 0)
|
||||
(global.get 0)
|
||||
(global.get 0)
|
||||
(drop)
|
||||
(br_if 1)))
|
||||
(get_global 0)
|
||||
(global.get 0)
|
||||
(drop))
|
||||
(get_global 0)))
|
||||
(global.get 0)))
|
||||
"#;
|
||||
expected = r#"
|
||||
(module
|
||||
(func (result i32)
|
||||
(call 0 (i64.const 3))
|
||||
(get_global 0)
|
||||
(global.get 0)
|
||||
(loop
|
||||
(call 0 (i64.const 4))
|
||||
(get_global 0)
|
||||
(global.get 0)
|
||||
(if
|
||||
(then
|
||||
(call 0 (i64.const 2))
|
||||
(get_global 0)
|
||||
(global.get 0)
|
||||
(br_if 0))
|
||||
(else
|
||||
(call 0 (i64.const 4))
|
||||
(get_global 0)
|
||||
(get_global 0)
|
||||
(global.get 0)
|
||||
(global.get 0)
|
||||
(drop)
|
||||
(br_if 1)))
|
||||
(get_global 0)
|
||||
(global.get 0)
|
||||
(drop))
|
||||
(get_global 0)))
|
||||
(global.get 0)))
|
||||
"#
|
||||
}
|
||||
|
||||
@@ -1272,23 +1272,23 @@ mod tests {
|
||||
input = r#"
|
||||
(module
|
||||
(func (result i32)
|
||||
(get_global 0)
|
||||
(global.get 0)
|
||||
(if
|
||||
(then
|
||||
(return)))
|
||||
(get_global 0)))
|
||||
(global.get 0)))
|
||||
"#;
|
||||
expected = r#"
|
||||
(module
|
||||
(func (result i32)
|
||||
(call 0 (i64.const 2))
|
||||
(get_global 0)
|
||||
(global.get 0)
|
||||
(if
|
||||
(then
|
||||
(call 0 (i64.const 1))
|
||||
(return)))
|
||||
(call 0 (i64.const 1))
|
||||
(get_global 0)))
|
||||
(global.get 0)))
|
||||
"#
|
||||
}
|
||||
|
||||
@@ -1297,23 +1297,23 @@ mod tests {
|
||||
input = r#"
|
||||
(module
|
||||
(func (result i32)
|
||||
(get_global 0)
|
||||
(global.get 0)
|
||||
(block
|
||||
(get_global 0)
|
||||
(global.get 0)
|
||||
(if
|
||||
(then (br 1))
|
||||
(else (br 0)))
|
||||
(get_global 0)
|
||||
(global.get 0)
|
||||
(drop))
|
||||
(get_global 0)))
|
||||
(global.get 0)))
|
||||
"#;
|
||||
expected = r#"
|
||||
(module
|
||||
(func (result i32)
|
||||
(call 0 (i64.const 5))
|
||||
(get_global 0)
|
||||
(global.get 0)
|
||||
(block
|
||||
(get_global 0)
|
||||
(global.get 0)
|
||||
(if
|
||||
(then
|
||||
(call 0 (i64.const 1))
|
||||
@@ -1322,9 +1322,9 @@ mod tests {
|
||||
(call 0 (i64.const 1))
|
||||
(br 0)))
|
||||
(call 0 (i64.const 2))
|
||||
(get_global 0)
|
||||
(global.get 0)
|
||||
(drop))
|
||||
(get_global 0)))
|
||||
(global.get 0)))
|
||||
"#
|
||||
}
|
||||
|
||||
|
||||
@@ -135,8 +135,12 @@ fn build_control_flow_graph(
|
||||
let mut stack = vec![ControlFrame::new(entry_node_id, terminal_node_id, false)];
|
||||
let mut metered_blocks_iter = blocks.iter().peekable();
|
||||
|
||||
let locals_count = body.locals().iter().fold(0, |count, val_type| count + val_type.count());
|
||||
let locals_init_cost = (rules.call_per_local_cost()).checked_mul(locals_count).ok_or(())?;
|
||||
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() {
|
||||
let active_node_id = stack
|
||||
@@ -350,8 +354,7 @@ mod tests {
|
||||
|
||||
for func_body in module.code_section().iter().flat_map(|section| section.bodies()) {
|
||||
let rules = ConstantCostRules::default();
|
||||
let locals_count =
|
||||
func_body.locals().iter().fold(0, |count, val_type| count + val_type.count());
|
||||
let locals_count = func_body.locals().iter().map(|val_type| val_type.count()).sum();
|
||||
|
||||
let metered_blocks =
|
||||
determine_metered_blocks(func_body.code(), &rules, locals_count).unwrap();
|
||||
|
||||
@@ -176,7 +176,7 @@ pub fn compute(func_idx: u32, module: &elements::Module) -> Result<u32, &'static
|
||||
match opcode {
|
||||
Nop => {},
|
||||
Block(ty) | Loop(ty) | If(ty) => {
|
||||
let end_arity = if *ty == BlockType::NoResult { 0 } else { 1 };
|
||||
let end_arity = u32::from(*ty != BlockType::NoResult);
|
||||
let branch_arity = if let Loop(_) = *opcode { 0 } else { end_arity };
|
||||
if let If(_) = *opcode {
|
||||
stack.pop_values(1)?;
|
||||
@@ -465,7 +465,7 @@ mod tests {
|
||||
(memory 0)
|
||||
(func (result i32)
|
||||
unreachable
|
||||
grow_memory
|
||||
memory.grow
|
||||
)
|
||||
)
|
||||
"#,
|
||||
|
||||
@@ -222,8 +222,8 @@ fn instrument_functions(
|
||||
/// Before:
|
||||
///
|
||||
/// ```text
|
||||
/// get_local 0
|
||||
/// get_local 1
|
||||
/// local.get 0
|
||||
/// local.get 1
|
||||
/// call 228
|
||||
/// drop
|
||||
/// ```
|
||||
@@ -231,8 +231,8 @@ fn instrument_functions(
|
||||
/// After:
|
||||
///
|
||||
/// ```text
|
||||
/// get_local 0
|
||||
/// get_local 1
|
||||
/// local.get 0
|
||||
/// local.get 1
|
||||
///
|
||||
/// < ... preamble ... >
|
||||
///
|
||||
@@ -366,9 +366,9 @@ mod tests {
|
||||
r#"
|
||||
(module
|
||||
(func (export "i32.add") (param i32 i32) (result i32)
|
||||
get_local 0
|
||||
get_local 1
|
||||
i32.add
|
||||
local.get 0
|
||||
local.get 1
|
||||
i32.add
|
||||
)
|
||||
)
|
||||
"#,
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
#[cfg(not(features = "std"))]
|
||||
use alloc::collections::BTreeMap as Map;
|
||||
use alloc::vec::Vec;
|
||||
use alloc::{collections::BTreeMap as Map, vec::Vec};
|
||||
use parity_wasm::{
|
||||
builder,
|
||||
elements::{self, FunctionType, Internal},
|
||||
};
|
||||
#[cfg(features = "std")]
|
||||
use std::collections::HashMap as Map;
|
||||
|
||||
use super::{resolve_func_type, Context};
|
||||
|
||||
|
||||
Vendored
+11
-11
@@ -3,25 +3,25 @@
|
||||
(local $x i32) (local $y i32)
|
||||
|
||||
(block $unrolled_loop
|
||||
(set_local $x (i32.const 0))
|
||||
(set_local $y (i32.const 1))
|
||||
(local.set $x (i32.const 0))
|
||||
(local.set $y (i32.const 1))
|
||||
|
||||
get_local $x
|
||||
get_local $y
|
||||
tee_local $x
|
||||
local.get $x
|
||||
local.get $y
|
||||
local.tee $x
|
||||
i32.add
|
||||
set_local $y
|
||||
local.set $y
|
||||
|
||||
i32.const 1
|
||||
br_if $unrolled_loop
|
||||
|
||||
get_local $x
|
||||
get_local $y
|
||||
tee_local $x
|
||||
local.get $x
|
||||
local.get $y
|
||||
local.tee $x
|
||||
i32.add
|
||||
set_local $y
|
||||
local.set $y
|
||||
)
|
||||
|
||||
get_local $y
|
||||
local.get $y
|
||||
)
|
||||
)
|
||||
|
||||
Vendored
+6
-6
@@ -2,18 +2,18 @@
|
||||
(func $add_locals (param $x i32) (param $y i32) (result i32)
|
||||
(local $t i32)
|
||||
|
||||
get_local $x
|
||||
get_local $y
|
||||
local.get $x
|
||||
local.get $y
|
||||
call $add
|
||||
set_local $t
|
||||
local.set $t
|
||||
|
||||
get_local $t
|
||||
local.get $t
|
||||
)
|
||||
|
||||
(func $add (param $x i32) (param $y i32) (result i32)
|
||||
(i32.add
|
||||
(get_local $x)
|
||||
(get_local $y)
|
||||
(local.get $x)
|
||||
(local.get $y)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
Vendored
+2
-2
@@ -2,8 +2,8 @@
|
||||
(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)))
|
||||
(then (i32.add (local.get $x) (i32.const 1)))
|
||||
(else (i32.popcnt (local.get $x)))
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
+13
-13
@@ -5,22 +5,22 @@
|
||||
(global $counter (mut i32) (i32.const 1))
|
||||
|
||||
(func $i32.add (export "i32.add") (param i32 i32) (result i32)
|
||||
get_local 0
|
||||
get_local 1
|
||||
i32.add
|
||||
local.get 0
|
||||
local.get 1
|
||||
i32.add
|
||||
)
|
||||
(func (param $arg i32)
|
||||
(local $tmp i32)
|
||||
(local $tmp i32)
|
||||
|
||||
global.get 0
|
||||
i32.const 1
|
||||
i32.add
|
||||
tee_local $tmp
|
||||
global.set $counter
|
||||
global.get 0
|
||||
i32.const 1
|
||||
i32.add
|
||||
local.tee $tmp
|
||||
global.set $counter
|
||||
|
||||
get_local $tmp
|
||||
get_local $arg
|
||||
call $i32.add
|
||||
drop
|
||||
local.get $tmp
|
||||
local.get $arg
|
||||
call $i32.add
|
||||
drop
|
||||
)
|
||||
)
|
||||
|
||||
+2
-2
@@ -8,8 +8,8 @@
|
||||
call $foo
|
||||
call $boo
|
||||
|
||||
get_local 0
|
||||
get_local 1
|
||||
local.get 0
|
||||
local.get 1
|
||||
i32.add
|
||||
)
|
||||
)
|
||||
|
||||
+1
-1
@@ -6,5 +6,5 @@
|
||||
(call
|
||||
$one-group-many-locals
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
+5
-5
@@ -1,17 +1,17 @@
|
||||
(module
|
||||
(import "env" "foo" (func $foo))
|
||||
(func (param i32)
|
||||
get_local 0
|
||||
local.get 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
|
||||
local.get 0
|
||||
local.get 1
|
||||
i32.add
|
||||
)
|
||||
(table 10 anyfunc)
|
||||
(table 10 funcref)
|
||||
|
||||
;; Refer all types of functions: imported, defined not exported and defined exported.
|
||||
(elem (i32.const 0) 0 1 2)
|
||||
|
||||
+2
-2
@@ -48,9 +48,9 @@ fn size_overheads_all(files: ReadDir) -> Vec<InstrumentedWasmResults> {
|
||||
|
||||
let (original_module_len, orig_module) = {
|
||||
let bytes = match entry.path().extension().unwrap().to_str() {
|
||||
Some("wasm") => read(&entry.path()).unwrap(),
|
||||
Some("wasm") => read(entry.path()).unwrap(),
|
||||
Some("wat") =>
|
||||
wat::parse_bytes(&read(&entry.path()).unwrap()).unwrap().into_owned(),
|
||||
wat::parse_bytes(&read(entry.path()).unwrap()).unwrap().into_owned(),
|
||||
_ => panic!("expected fixture_dir containing .wasm or .wat files only"),
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user