wasm_instrument/gas_metering/
backend.rs

1//! Provides backends for the gas metering instrumentation
2use parity_wasm::elements;
3
4/// Implementation details of the specific method of the gas metering.
5#[derive(Clone)]
6pub enum GasMeter {
7	/// Gas metering with an external function.
8	External {
9		/// Name of the module to import the gas function from.
10		module: &'static str,
11		/// Name of the external gas function to be imported.
12		function: &'static str,
13	},
14	/// Gas metering with a local function and a mutable global.
15	Internal {
16		/// Name of the mutable global to be exported.
17		global: &'static str,
18		/// Body of the local gas counting function to be injected.
19		func_instructions: elements::Instructions,
20		/// Cost of the gas function execution.
21		cost: u64,
22	},
23}
24
25use super::Rules;
26/// Under the hood part of the gas metering mechanics.
27pub trait Backend {
28	/// Provides the gas metering implementation details.  
29	fn gas_meter<R: Rules>(self, module: &elements::Module, rules: &R) -> GasMeter;
30}
31
32/// Gas metering with an external host function.
33pub mod host_function {
34	use super::{Backend, GasMeter, Rules};
35	use parity_wasm::elements::Module;
36	/// Injects invocations of the gas charging host function into each metering block.
37	pub struct Injector {
38		/// The name of the module to import the gas function from.
39		module: &'static str,
40		/// The name of the gas function to import.
41		name: &'static str,
42	}
43
44	impl Injector {
45		pub fn new(module: &'static str, name: &'static str) -> Self {
46			Self { module, name }
47		}
48	}
49
50	impl Backend for Injector {
51		fn gas_meter<R: Rules>(self, _module: &Module, _rules: &R) -> GasMeter {
52			GasMeter::External { module: self.module, function: self.name }
53		}
54	}
55}
56
57/// Gas metering with a mutable global.
58///
59/// # Note
60///
61/// Not for all execution engines this method gives performance wins compared to using an [external
62/// host function](host_function). See benchmarks and size overhead tests for examples of how to
63/// make measurements needed to decide which gas metering method is better for your particular case.
64///
65/// # Warning
66///
67/// It is not recommended to apply [stack limiter](crate::inject_stack_limiter) instrumentation to a
68/// module instrumented with this type of gas metering. This could lead to a massive module size
69/// bloat. This is a known issue to be fixed in upcoming versions.
70pub mod mutable_global {
71	use super::{Backend, GasMeter, Rules};
72	use alloc::vec;
73	use parity_wasm::elements::{self, Instruction, Module};
74	/// Injects a mutable global variable and a local function to the module to track
75	/// current gas left.
76	///
77	/// The function is called in every metering block. In case of falling out of gas, the global is
78	/// set to the sentinel value `U64::MAX` and `unreachable` instruction is called. The execution
79	/// engine should take care of getting the current global value and setting it back in order to
80	/// sync the gas left value during an execution.
81	pub struct Injector {
82		/// The export name of the gas tracking global.
83		pub global_name: &'static str,
84	}
85
86	impl Injector {
87		pub fn new(global_name: &'static str) -> Self {
88			Self { global_name }
89		}
90	}
91
92	impl Backend for Injector {
93		fn gas_meter<R: Rules>(self, module: &Module, rules: &R) -> GasMeter {
94			let gas_global_idx = module.globals_space() as u32;
95
96			let func_instructions = vec![
97				Instruction::GetGlobal(gas_global_idx),
98				Instruction::GetLocal(0),
99				Instruction::I64GeU,
100				Instruction::If(elements::BlockType::NoResult),
101				Instruction::GetGlobal(gas_global_idx),
102				Instruction::GetLocal(0),
103				Instruction::I64Sub,
104				Instruction::SetGlobal(gas_global_idx),
105				Instruction::Else,
106				// sentinel val u64::MAX
107				Instruction::I64Const(-1i64),           // non-charged instruction
108				Instruction::SetGlobal(gas_global_idx), // non-charged instruction
109				Instruction::Unreachable,               // non-charged instruction
110				Instruction::End,
111				Instruction::End,
112			];
113
114			// calculate gas used for the gas charging func execution itself
115			let mut gas_fn_cost = func_instructions.iter().fold(0, |cost, instruction| {
116				cost + (rules.instruction_cost(instruction).unwrap_or(0) as u64)
117			});
118			// don't charge for the instructions used to fail when out of gas
119			let fail_cost = vec![
120				Instruction::I64Const(-1i64),           // non-charged instruction
121				Instruction::SetGlobal(gas_global_idx), // non-charged instruction
122				Instruction::Unreachable,               // non-charged instruction
123			]
124			.iter()
125			.fold(0, |cost, instruction| {
126				cost + (rules.instruction_cost(instruction).unwrap_or(0) as u64)
127			});
128
129			gas_fn_cost -= fail_cost;
130
131			GasMeter::Internal {
132				global: self.global_name,
133				func_instructions: elements::Instructions::new(func_instructions),
134				cost: gas_fn_cost,
135			}
136		}
137	}
138}