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}