revmc_codegen/bytecode/passes/
sections.rs1use crate::bytecode::{Bytecode, Inst, InstFlags};
2use core::fmt;
3use revm_bytecode::opcode as op;
4use revm_primitives::U256;
5
6#[derive(Clone, Copy, Default, PartialEq, Eq)]
11pub(crate) struct GasSection {
12 pub(crate) gas_cost: u32,
14}
15
16impl fmt::Debug for GasSection {
17 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
18 if self.is_empty() {
19 f.write_str("GasSection::EMPTY")
20 } else {
21 f.debug_struct("GasSection").field("gas_cost", &self.gas_cost).finish()
22 }
23 }
24}
25
26impl GasSection {
27 #[inline]
29 pub(crate) fn is_empty(self) -> bool {
30 self == Self::default()
31 }
32}
33
34#[derive(Clone, Copy, Default, PartialEq, Eq)]
39pub(crate) struct StackSection {
40 pub(crate) inputs: u16,
42 pub(crate) max_growth: i16,
44}
45
46impl fmt::Debug for StackSection {
47 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48 if self.is_empty() {
49 f.write_str("StackSection::EMPTY")
50 } else {
51 f.debug_struct("StackSection")
52 .field("inputs", &self.inputs)
53 .field("max_growth", &self.max_growth)
54 .finish()
55 }
56 }
57}
58
59impl StackSection {
60 #[inline]
62 pub(crate) fn is_empty(self) -> bool {
63 self == Self::default()
64 }
65
66 pub(crate) fn from_stack_io(iter: impl IntoIterator<Item = (u8, u8)>) -> Self {
68 let mut analysis = StackSectionAnalysis::default();
69 for (inp, out) in iter {
70 analysis.process(inp, out);
71 }
72 analysis.section()
73 }
74}
75
76pub(crate) struct GasSectionAnalysis {
78 gas_cost: u64,
79 start_inst: Inst,
80}
81
82impl Default for GasSectionAnalysis {
83 fn default() -> Self {
84 Self { gas_cost: 0, start_inst: Inst::from_usize(0) }
85 }
86}
87
88impl GasSectionAnalysis {
89 #[inline]
91 pub(crate) fn process(&mut self, base_gas: u16) {
92 self.gas_cost += base_gas as u64;
93 }
94
95 #[inline]
97 pub(crate) fn process_extra(&mut self, gas: u64) {
98 self.gas_cost += gas;
99 }
100
101 fn save_to_reset(&mut self, bytecode: &mut Bytecode<'_>, next_section_inst: Inst) {
102 self.save_to(bytecode, next_section_inst);
103 self.reset(next_section_inst);
104 }
105
106 fn save_to(&self, bytecode: &mut Bytecode<'_>, next_section_inst: Inst) {
108 if self.start_inst >= bytecode.insts.len_idx() {
109 return;
110 }
111 let section = self.section();
112 if !section.is_empty() {
113 let _ = next_section_inst;
114 let mut insts = bytecode.insts[self.start_inst..].iter_mut();
121 if let Some(inst) = insts.find(|inst| !inst.is_dead_code()) {
122 inst.gas_section = section;
123 }
124 }
125 }
126
127 pub(crate) fn reset(&mut self, inst: Inst) {
129 *self = Self { start_inst: inst, ..Default::default() };
130 }
131
132 pub(crate) fn section(&self) -> GasSection {
134 GasSection { gas_cost: self.gas_cost.try_into().unwrap_or(u32::MAX) }
135 }
136}
137
138pub(crate) struct StackSectionAnalysis {
140 inputs: i32,
141 diff: i32,
142 max_growth: i32,
143 start_inst: Inst,
144}
145
146impl Default for StackSectionAnalysis {
147 fn default() -> Self {
148 Self { inputs: 0, diff: 0, max_growth: 0, start_inst: Inst::from_usize(0) }
149 }
150}
151
152impl StackSectionAnalysis {
153 #[inline]
155 pub(crate) fn diff(&self) -> i32 {
156 self.diff
157 }
158
159 #[inline]
161 pub(crate) fn process(&mut self, inputs: u8, outputs: u8) {
162 self.inputs = self.inputs.max(inputs as i32 - self.diff);
163 self.diff += outputs as i32 - inputs as i32;
164 self.max_growth = self.max_growth.max(self.diff);
165 }
166
167 fn save_to_reset(&mut self, bytecode: &mut Bytecode<'_>, next_section_inst: Inst) {
168 self.save_to(bytecode, next_section_inst);
169 self.reset(next_section_inst);
170 }
171
172 fn save_to(&self, bytecode: &mut Bytecode<'_>, next_section_inst: Inst) {
174 if self.start_inst >= bytecode.insts.len_idx() {
175 return;
176 }
177 let section = self.section();
178 let _ = next_section_inst;
179 let mut insts = bytecode.insts[self.start_inst..].iter_mut();
186 if let Some(inst) = insts.find(|inst| !inst.is_dead_code()) {
187 inst.flags |= InstFlags::STACK_SECTION_HEAD;
188 inst.stack_section = section;
189 }
190 }
191
192 pub(crate) fn reset(&mut self, inst: Inst) {
194 *self = Self { start_inst: inst, ..Default::default() };
195 }
196
197 pub(crate) fn section(&self) -> StackSection {
199 StackSection {
200 inputs: self.inputs.try_into().unwrap_or(u16::MAX),
201 max_growth: self.max_growth.try_into().unwrap_or(i16::MAX),
202 }
203 }
204}
205
206#[derive(Default)]
208pub(crate) struct SectionsAnalysis {
209 gas: GasSectionAnalysis,
210 stack: StackSectionAnalysis,
211}
212
213impl SectionsAnalysis {
214 pub(crate) fn process(&mut self, bytecode: &mut Bytecode<'_>, inst: Inst) {
216 if bytecode.inst(inst).is_reachable_jumpdest(bytecode.has_dynamic_jumps()) {
218 self.stack.save_to_reset(bytecode, inst);
219 self.gas.save_to_reset(bytecode, inst);
220 }
221
222 let data = bytecode.inst(inst);
223 let (inp, out) = data.stack_io();
224 self.stack.process(inp, out);
225 self.gas.process(data.base_gas);
226
227 if data.opcode == op::EXP
231 && let Some(exponent) = bytecode.const_operand(inst, 1)
232 && (bytecode.const_output(inst).is_some() || exponent <= U256::from(2))
233 {
234 self.gas.process_extra(bytecode.gas_params.exp_cost(exponent));
235 }
236
237 let next = inst + 1;
240 if data.may_suspend() || data.is_branching() {
241 self.stack.save_to_reset(bytecode, next);
242 self.gas.save_to_reset(bytecode, next);
243 } else if data.requires_gasleft(bytecode.spec_id) {
244 self.gas.save_to_reset(bytecode, next);
245 }
246 }
247
248 pub(crate) fn finish(self, bytecode: &mut Bytecode<'_>) {
250 let last = bytecode.insts.len_idx() - 1;
251 self.gas.save_to(bytecode, last);
252 self.stack.save_to(bytecode, last);
253 if enabled!(tracing::Level::DEBUG) {
254 let mut max_len = 0usize;
255 let mut count = 0usize;
256 let mut section_len = 0usize;
257 for (_inst, data) in bytecode.iter_insts() {
258 section_len += 1;
259 if !data.is_stack_section_head() {
260 continue;
261 }
262 if count > 0 {
264 max_len = max_len.max(section_len - 1);
265 }
266 section_len = 1;
267 count += 1;
268 }
269 max_len = max_len.max(section_len);
271 debug!(count, max_len, "sections");
272 }
273 }
274}
275
276#[cfg(test)]
277mod tests {
278 use crate::bytecode::{
279 Inst,
280 passes::block_analysis::tests::{analyze_asm, analyze_asm_spec, analyze_code_spec},
281 };
282 use revm_bytecode::opcode as op;
283 use revm_primitives::hardfork::SpecId;
284
285 fn section_gas(src: &str) -> u32 {
287 let bytecode = analyze_asm(src);
288 bytecode.inst(Inst::from_usize(0)).gas_section.gas_cost
289 }
290
291 #[test]
295 fn exp_folded_gas_uses_exponent_not_base() {
296 let gas = section_gas("PUSH 256 PUSH 2 EXP PUSH0 MSTORE STOP");
300 assert_eq!(gas, 121, "dynamic gas must use exponent (256=2 bytes), not base (2=1 byte)");
301 }
302
303 #[test]
305 fn exp_folded_gas_zero_exponent() {
306 let gas = section_gas("PUSH 0 PUSH 2 EXP PUSH0 MSTORE STOP");
308 assert_eq!(gas, 20, "zero exponent should have no dynamic gas");
309 }
310
311 #[test]
313 fn exp_folded_gas_max_exponent() {
314 let gas = section_gas(
318 "PUSH 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff \
319 PUSH 2 EXP PUSH0 MSTORE STOP",
320 );
321 assert_eq!(gas, 1621, "max exponent should cost 50*32=1600 dynamic gas");
322 }
323
324 #[test]
328 fn disabled_opcode_does_not_poison_section() {
329 let bytecode = analyze_asm_spec("CALLDATASIZE TSTORE", SpecId::SHANGHAI);
331 let head = bytecode.inst(Inst::from_usize(0));
332 assert_eq!(
333 head.stack_section.inputs, 0,
334 "CALLDATASIZE needs 0 inputs; disabled TSTORE must not inflate the section"
335 );
336 assert_eq!(head.gas_section.gas_cost, 2, "only CALLDATASIZE gas (2) should be charged");
337 }
338
339 #[test]
344 fn invalid_dupn_imm_does_not_inflate_section() {
345 let bytecode = analyze_code_spec(
350 vec![op::PUSH0, op::DUPN, 0x5b, op::PUSH0, op::SSTORE, op::STOP],
351 SpecId::AMSTERDAM,
352 );
353 let dupn = bytecode.inst(Inst::from_usize(1));
354 assert_eq!(dupn.stack_io(), (0, 0), "DUPN with invalid immediate must have zero stack I/O");
355 }
356}