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)]
40pub(crate) struct StackSection {
41 pub(crate) inputs: u16,
43 pub(crate) max_growth: i16,
45}
46
47impl fmt::Debug for StackSection {
48 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49 if self.is_empty() {
50 f.write_str("StackSection::EMPTY")
51 } else {
52 f.debug_struct("StackSection")
53 .field("inputs", &self.inputs)
54 .field("max_growth", &self.max_growth)
55 .finish()
56 }
57 }
58}
59
60impl StackSection {
61 #[inline]
63 pub(crate) fn is_empty(self) -> bool {
64 self == Self::default()
65 }
66
67 pub(crate) fn from_stack_io(iter: impl IntoIterator<Item = (u8, u8)>) -> Self {
69 let mut analysis = StackSectionAnalysis::default();
70 for (inp, out) in iter {
71 analysis.process(inp, out);
72 }
73 analysis.section()
74 }
75}
76
77pub(crate) struct GasSectionAnalysis {
79 gas_cost: u64,
80 start_inst: Inst,
81}
82
83impl Default for GasSectionAnalysis {
84 fn default() -> Self {
85 Self { gas_cost: 0, start_inst: Inst::from_usize(0) }
86 }
87}
88
89impl GasSectionAnalysis {
90 #[inline]
92 pub(crate) fn process(&mut self, base_gas: u16) {
93 self.gas_cost += base_gas as u64;
94 }
95
96 #[inline]
98 pub(crate) fn process_extra(&mut self, gas: u64) {
99 self.gas_cost += gas;
100 }
101
102 fn save_to_reset(&mut self, bytecode: &mut Bytecode<'_>, next_section_inst: Inst) {
103 self.save_to(bytecode, next_section_inst);
104 self.reset(next_section_inst);
105 }
106
107 fn save_to(&self, bytecode: &mut Bytecode<'_>, next_section_inst: Inst) {
109 if self.start_inst >= bytecode.insts.len_idx() {
110 return;
111 }
112 let section = self.section();
113 if !section.is_empty() {
114 let _ = next_section_inst;
115 let mut insts = bytecode.insts[self.start_inst..].iter_mut();
122 if let Some(inst) = insts.find(|inst| !inst.is_dead_code()) {
123 inst.gas_section = section;
124 }
125 }
126 }
127
128 pub(crate) fn reset(&mut self, inst: Inst) {
130 *self = Self { start_inst: inst, ..Default::default() };
131 }
132
133 pub(crate) fn section(&self) -> GasSection {
135 GasSection { gas_cost: self.gas_cost.try_into().unwrap_or(u32::MAX) }
136 }
137}
138
139pub(crate) struct StackSectionAnalysis {
141 inputs: i32,
142 diff: i32,
143 max_growth: i32,
144 start_inst: Inst,
145}
146
147impl Default for StackSectionAnalysis {
148 fn default() -> Self {
149 Self { inputs: 0, diff: 0, max_growth: 0, start_inst: Inst::from_usize(0) }
150 }
151}
152
153impl StackSectionAnalysis {
154 #[inline]
156 pub(crate) fn diff(&self) -> i32 {
157 self.diff
158 }
159
160 #[inline]
162 pub(crate) fn process(&mut self, inputs: u8, outputs: u8) {
163 self.inputs = self.inputs.max(inputs as i32 - self.diff);
164 self.diff += outputs as i32 - inputs as i32;
165 self.max_growth = self.max_growth.max(self.diff);
166 }
167
168 fn save_to_reset(&mut self, bytecode: &mut Bytecode<'_>, next_section_inst: Inst) {
169 self.save_to(bytecode, next_section_inst);
170 self.reset(next_section_inst);
171 }
172
173 fn save_to(&self, bytecode: &mut Bytecode<'_>, next_section_inst: Inst) {
175 if self.start_inst >= bytecode.insts.len_idx() {
176 return;
177 }
178 let section = self.section();
179 let _ = next_section_inst;
180 let mut insts = bytecode.insts[self.start_inst..].iter_mut();
187 if let Some(inst) = insts.find(|inst| !inst.is_dead_code()) {
188 inst.flags |= InstFlags::STACK_SECTION_HEAD;
189 inst.stack_section = section;
190 }
191 }
192
193 pub(crate) fn reset(&mut self, inst: Inst) {
195 *self = Self { start_inst: inst, ..Default::default() };
196 }
197
198 pub(crate) fn section(&self) -> StackSection {
200 StackSection {
201 inputs: self.inputs.try_into().unwrap_or(u16::MAX),
202 max_growth: self.max_growth.try_into().unwrap_or(i16::MAX),
203 }
204 }
205}
206
207#[derive(Default)]
209pub(crate) struct SectionsAnalysis {
210 gas: GasSectionAnalysis,
211 stack: StackSectionAnalysis,
212}
213
214impl SectionsAnalysis {
215 pub(crate) fn process(&mut self, bytecode: &mut Bytecode<'_>, inst: Inst) {
217 if bytecode.inst(inst).is_reachable_jumpdest(bytecode.has_dynamic_jumps()) {
219 self.stack.save_to_reset(bytecode, inst);
220 self.gas.save_to_reset(bytecode, inst);
221 }
222
223 let data = bytecode.inst(inst);
224 let (inp, out) = data.stack_io();
225 self.stack.process(inp, out);
226 self.gas.process(data.base_gas);
227
228 if data.opcode == op::EXP
232 && let Some(exponent) = bytecode.const_operand(inst, 1)
233 && (bytecode.const_output(inst).is_some() || exponent <= U256::from(2))
234 {
235 self.gas.process_extra(bytecode.gas_params.exp_cost(exponent));
236 }
237
238 let next = inst + 1;
240 if data.may_suspend() || data.is_branching() || data.requires_gasleft(bytecode.spec_id) {
241 self.stack.save_to_reset(bytecode, next);
242 self.gas.save_to_reset(bytecode, next);
243 }
244 }
245
246 pub(crate) fn finish(self, bytecode: &mut Bytecode<'_>) {
248 let last = bytecode.insts.len_idx() - 1;
249 self.gas.save_to(bytecode, last);
250 self.stack.save_to(bytecode, last);
251 if enabled!(tracing::Level::DEBUG) {
252 let mut max_len = 0usize;
253 let mut count = 0usize;
254 let mut section_len = 0usize;
255 for (_inst, data) in bytecode.iter_insts() {
256 section_len += 1;
257 if !data.is_stack_section_head() {
258 continue;
259 }
260 if count > 0 {
262 max_len = max_len.max(section_len - 1);
263 }
264 section_len = 1;
265 count += 1;
266 }
267 max_len = max_len.max(section_len);
269 debug!(count, max_len, "sections");
270 }
271 }
272}
273
274#[cfg(test)]
275mod tests {
276 use crate::bytecode::{
277 Inst,
278 passes::block_analysis::tests::{analyze_asm, analyze_asm_spec, analyze_code_spec},
279 };
280 use revm_bytecode::opcode as op;
281 use revm_primitives::hardfork::SpecId;
282
283 fn section_gas(src: &str) -> u32 {
285 let bytecode = analyze_asm(src);
286 bytecode.inst(Inst::from_usize(0)).gas_section.gas_cost
287 }
288
289 #[test]
293 fn exp_folded_gas_uses_exponent_not_base() {
294 let gas = section_gas("PUSH 256 PUSH 2 EXP PUSH0 MSTORE STOP");
298 assert_eq!(gas, 121, "dynamic gas must use exponent (256=2 bytes), not base (2=1 byte)");
299 }
300
301 #[test]
303 fn exp_folded_gas_zero_exponent() {
304 let gas = section_gas("PUSH 0 PUSH 2 EXP PUSH0 MSTORE STOP");
306 assert_eq!(gas, 20, "zero exponent should have no dynamic gas");
307 }
308
309 #[test]
311 fn exp_folded_gas_max_exponent() {
312 let gas = section_gas(
316 "PUSH 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff \
317 PUSH 2 EXP PUSH0 MSTORE STOP",
318 );
319 assert_eq!(gas, 1621, "max exponent should cost 50*32=1600 dynamic gas");
320 }
321
322 #[test]
326 fn disabled_opcode_does_not_poison_section() {
327 let bytecode = analyze_asm_spec("CALLDATASIZE TSTORE", SpecId::SHANGHAI);
329 let head = bytecode.inst(Inst::from_usize(0));
330 assert_eq!(
331 head.stack_section.inputs, 0,
332 "CALLDATASIZE needs 0 inputs; disabled TSTORE must not inflate the section"
333 );
334 assert_eq!(head.gas_section.gas_cost, 2, "only CALLDATASIZE gas (2) should be charged");
335 }
336
337 #[test]
342 fn invalid_dupn_imm_does_not_inflate_section() {
343 let bytecode = analyze_code_spec(
348 vec![op::PUSH0, op::DUPN, 0x5b, op::PUSH0, op::SSTORE, op::STOP],
349 SpecId::AMSTERDAM,
350 );
351 let dupn = bytecode.inst(Inst::from_usize(1));
352 assert_eq!(dupn.stack_io(), (0, 0), "DUPN with invalid immediate must have zero stack I/O");
353 }
354}