radix_wasm_instrument/gas_metering/
backend.rs

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