mirror of
https://github.com/pezkuwichain/wasm-instrument.git
synced 2026-04-22 02:07:58 +00:00
a4dde28607
* 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>
139 lines
4.7 KiB
Rust
139 lines
4.7 KiB
Rust
//! 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, instruction| {
|
|
cost + (rules.instruction_cost(instruction).unwrap_or(0) as u64)
|
|
});
|
|
// don't charge for the instructions used to fail when out of gas
|
|
let fail_cost = vec![
|
|
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)
|
|
});
|
|
|
|
gas_fn_cost -= fail_cost;
|
|
|
|
GasMeter::Internal {
|
|
global: self.global_name,
|
|
func_instructions: elements::Instructions::new(func_instructions),
|
|
cost: gas_fn_cost,
|
|
}
|
|
}
|
|
}
|
|
}
|