Files
wasm-instrument/tests/diff.rs
T
Sasha Gryaznov a4dde28607 Add new gas metering method: mutable global + local gas function (#34)
* fix misprints in doc comments

* added global gas tracker variable and local gas fn

* all exported functions of the module to accept a new param and to set the gas_left global to its value at their very start

* make module support both gas metering methods

* tests fixed for the old metering method

* better naming

* MutableGlobal metering method implemented, tests for the old method pass

* gas_metering::tests updated and pass

* all tests udpdated and pass

* emacs backup files to .gitignore

* docs updated

* clippy fix

* iff = if and only if

* more clippy

* docs misprints fixes

* refactored to have Backend trait and two implementations in separate sub-modules

* docs updated

* fixed old benches (updating them is coming next)

* added bench for an instrumented wasm-coremark

* updated benches: added them for both gas_metering instrumentations

* benches contest first ver

* added debug prints to the bench

* refactored to better fit frontend-backend pattern

* docs update

* updated benches

* design updated on feedback

* re-structured sub-modules

re-structured sub-modules & updated docs

* docs improved

* addressed latest feedback comments

* re-writed the local gas function

* coremark benches show ~20% performance improvement

* fix ci: test + clippy

* save before re-factoring prepare_in_wasm()

* bare_call_16 shows 16% worse perf

* + fibonacci recursive bench

* refactored benchmarks

* + factorial recursive bench

* benches on wasmi fixtures show no perf improvement, coremark runs ~20% faster being instrumented with mutable_global gas metering

* charge gas for local gas func isntructions execution

* replaced benchmark which requires multi_value feature

* save: optimized gas func a bit (benches work, fixture tests fail)

* 1033% overhead on many_blocks.wasm when mut_global gas_metering together with stack_height

* size overhead test for both gas metering methods + stack limiter

* added more benches

* improved print_size_overhead test

* test for comparing size overheads of two gas_metering injectors

* before optimization: benches + size overhead

* optimization try-1: inline part of gas func instructions: +benches +size overheads

* optimization try-2: inline hot path of gas fn:  +benches +size overheads

* opt try-3: count for gas fn cost on the caller side: +benches +size overhead

* revert to initial version but with static gas fn cost on the caller side: +benches +sizes

* tests fixed

* use newest wasmi 0.20: +benches +docs updated

* use if-else block instead of Return: +benches

* fix tests

* clippy fix

* addressed review comments

* Update changelog

Co-authored-by: Alexander Theißen <alex.theissen@me.com>
2022-11-20 15:00:10 +01:00

158 lines
4.6 KiB
Rust

use std::{
fs,
io::{self, Read, Write},
path::{Path, PathBuf},
};
use wasm_instrument::{self as instrument, gas_metering, parity_wasm::elements};
use wasmparser::validate;
fn slurp<P: AsRef<Path>>(path: P) -> io::Result<Vec<u8>> {
let mut f = fs::File::open(path)?;
let mut buf = vec![];
f.read_to_end(&mut buf)?;
Ok(buf)
}
fn dump<P: AsRef<Path>>(path: P, buf: &[u8]) -> io::Result<()> {
let mut f = fs::File::create(path)?;
f.write_all(buf)?;
Ok(())
}
fn run_diff_test<F: FnOnce(&[u8]) -> Vec<u8>>(
test_dir: &str,
in_name: &str,
out_name: &str,
test: F,
) {
let mut fixture_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
fixture_path.push("tests");
fixture_path.push("fixtures");
fixture_path.push(test_dir);
fixture_path.push(in_name);
let mut expected_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
expected_path.push("tests");
expected_path.push("expectations");
expected_path.push(test_dir);
expected_path.push(out_name);
let fixture_wasm = wat::parse_file(&fixture_path).expect("Failed to read fixture");
validate(&fixture_wasm).expect("Fixture is invalid");
let expected_wat = slurp(&expected_path).unwrap_or_default();
let expected_wat = std::str::from_utf8(&expected_wat).expect("Failed to decode expected wat");
let actual_wasm = test(fixture_wasm.as_ref());
validate(&actual_wasm).expect("Result module is invalid");
let actual_wat =
wasmprinter::print_bytes(&actual_wasm).expect("Failed to convert result wasm to wat");
if actual_wat != expected_wat {
println!("difference!");
println!("--- {}", expected_path.display());
println!("+++ {} test {}", test_dir, out_name);
for diff in diff::lines(expected_wat, &actual_wat) {
match diff {
diff::Result::Left(l) => println!("-{}", l),
diff::Result::Both(l, _) => println!(" {}", l),
diff::Result::Right(r) => println!("+{}", r),
}
}
if std::env::var_os("BLESS").is_some() {
dump(&expected_path, actual_wat.as_bytes()).expect("Failed to write to expected");
} else {
panic!();
}
}
}
mod stack_height {
use super::*;
macro_rules! def_stack_height_test {
( $name:ident ) => {
#[test]
fn $name() {
run_diff_test(
"stack-height",
concat!(stringify!($name), ".wat"),
concat!(stringify!($name), ".wat"),
|input| {
let module =
elements::deserialize_buffer(input).expect("Failed to deserialize");
let instrumented = instrument::inject_stack_limiter(module, 1024)
.expect("Failed to instrument with stack counter");
elements::serialize(instrumented).expect("Failed to serialize")
},
);
}
};
}
def_stack_height_test!(simple);
def_stack_height_test!(start);
def_stack_height_test!(table);
def_stack_height_test!(global);
def_stack_height_test!(imports);
def_stack_height_test!(many_locals);
def_stack_height_test!(empty_functions);
}
mod gas {
use super::*;
macro_rules! def_gas_test {
( ($input:ident, $name1:ident, $name2:ident) ) => {
#[test]
fn $name1() {
run_diff_test(
"gas",
concat!(stringify!($input), ".wat"),
concat!(stringify!($name1), ".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::host_function::Injector::new("env", "gas");
let instrumented = gas_metering::inject(module, backend, &rules)
.expect("Failed to instrument with gas metering");
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!((simple, simple_host_fn, simple_mut_global));
def_gas_test!((start, start_host_fn, start_mut_global));
def_gas_test!((call, call_host_fn, call_mut_global));
def_gas_test!((branch, branch_host_fn, branch_mut_global));
}