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}