Skip to main content

revmc_codegen/compiler/translate/
peephole.rs

1//! Peephole optimizations applied during translation.
2//!
3//! These fire when abstract interpretation has proven one or more operands are constant,
4//! replacing expensive opaque builtins (DIV, MOD, SDIV, SMOD, ADDMOD, MULMOD, EXP) with native
5//! LLVM operations that it can optimize further (e.g. pow2 udiv → lshr, pow2 urem → and).
6
7use super::FunctionCx;
8use crate::{Backend, Builder, InstData, IntCC};
9use revm_bytecode::opcode as op;
10use revm_interpreter::InstructionResult;
11use revm_primitives::U256;
12use revmc_builtins::Builtin;
13
14/// i256 INT_MIN: 1 << 255.
15const INT_MIN: U256 = U256::ONE.wrapping_shl(255);
16
17impl<'a, B: Backend> FunctionCx<'a, B> {
18    /// Tries to emit optimized inline code for an instruction whose operands are partially known.
19    ///
20    /// Returns `true` if the peephole fired and code was emitted, `false` to fall through to the
21    /// normal translation.
22    pub(super) fn try_peephole(&mut self, data: &InstData) -> bool {
23        match data.opcode {
24            op::DIV => self.peephole_div(),
25            op::SDIV => self.peephole_sdiv(),
26            op::MOD => self.peephole_mod(),
27            op::SMOD => self.peephole_smod(),
28            op::ADDMOD => self.peephole_addmod(),
29            op::MULMOD => self.peephole_mulmod(),
30            op::EXP => self.peephole_exp(),
31            op::SIGNEXTEND => self.peephole_signextend(),
32            op::BYTE => self.peephole_byte(),
33
34            op::CALLDATALOAD | op::SLOAD => self.peephole_load(data.opcode),
35
36            op::KECCAK256 => self.peephole_keccak256(),
37            op::RETURN | op::REVERT => self.peephole_return(data.opcode),
38
39            _ => false,
40        }
41    }
42
43    /// DIV a, b => a / b.
44    ///
45    /// General constant divisors could use native LLVM `udiv`, but i256 division
46    /// generates very bloated code (~100+ instructions for the reciprocal multiply),
47    /// so we only emit native ops for powers of two where LLVM lowers to `lshr`.
48    fn peephole_div(&mut self) -> bool {
49        let [dividend, divisor] = self.const_operands();
50        match divisor {
51            // x / 0 => 0.
52            Some(U256::ZERO) => self.fold_const(0),
53            // x / 1 => x.
54            Some(U256::ONE) => {
55                let [a, _] = self.popn();
56                self.push(a);
57            }
58            // x / C (pow2) => native udiv, LLVM lowers to lshr.
59            Some(d) if d.is_power_of_two() => {
60                let [a, _] = self.popn();
61                let d = self.bcx.iconst_256(d);
62                let r = self.bcx.udiv(a, d);
63                self.push(r);
64            }
65            // 0 / x => 0 (EVM: 0 / 0 = 0).
66            _ if dividend == Some(U256::ZERO) => self.fold_const(0),
67            _ => return false,
68        }
69        true
70    }
71
72    /// SDIV a, b => signed(a) / signed(b), rounded toward zero.
73    ///
74    /// Pow2 cases are intentionally skipped: signed division rounds toward zero while
75    /// arithmetic shift right rounds toward negative infinity, so the semantics differ.
76    fn peephole_sdiv(&mut self) -> bool {
77        let [dividend, divisor] = self.const_operands();
78        match divisor {
79            // x / 0 => 0.
80            Some(U256::ZERO) => self.fold_const(0),
81            // x / 1 => x.
82            Some(U256::ONE) => {
83                let [a, _] = self.popn();
84                self.push(a);
85            }
86            // x / -1 => -x.
87            Some(U256::MAX) => {
88                let [a, _] = self.popn();
89                let zero = self.bcx.iconst_256(0);
90                let r = self.bcx.isub(zero, a);
91                self.push(r);
92            }
93            // x / INT_MIN => zext(x == INT_MIN).
94            Some(INT_MIN) => {
95                let [a, _] = self.popn();
96                let min = self.bcx.iconst_256(INT_MIN);
97                let cmp = self.bcx.icmp(IntCC::Equal, a, min);
98                let r = self.bcx.zext(self.word_type, cmp);
99                self.push(r);
100            }
101            // 0 / x => 0 (EVM: 0 / 0 = 0).
102            _ if dividend == Some(U256::ZERO) => self.fold_const(0),
103            _ => return false,
104        }
105        true
106    }
107
108    /// MOD a, b => a % b.
109    ///
110    /// Same as DIV: only pow2 moduli use native `urem` (LLVM lowers to `and`).
111    fn peephole_mod(&mut self) -> bool {
112        let [dividend, modulus] = self.const_operands();
113        match modulus {
114            // x % 0 => 0, x % 1 => 0.
115            Some(U256::ZERO) | Some(U256::ONE) => self.fold_const(0),
116            // x % C (pow2) => native urem, LLVM lowers to and.
117            Some(m) if m.is_power_of_two() => {
118                let [a, _] = self.popn();
119                let m = self.bcx.iconst_256(m);
120                let r = self.bcx.urem(a, m);
121                self.push(r);
122            }
123            // 0 % x => 0 (EVM: 0 % 0 = 0).
124            _ if dividend == Some(U256::ZERO) => self.fold_const(0),
125            _ => return false,
126        }
127        true
128    }
129
130    /// SMOD a, b => signed(a) % signed(b). Result sign matches dividend.
131    fn peephole_smod(&mut self) -> bool {
132        let [dividend, modulus] = self.const_operands();
133        match modulus {
134            // x % 0 => 0, x % 1 => 0, x % -1 => 0.
135            Some(U256::ZERO) | Some(U256::ONE) | Some(U256::MAX) => self.fold_const(0),
136            // 0 % x => 0 (EVM: 0 % 0 = 0).
137            _ if dividend == Some(U256::ZERO) => self.fold_const(0),
138            _ => return false,
139        }
140        true
141    }
142
143    /// ADDMOD a, b, N => (a + b) % N.
144    fn peephole_addmod(&mut self) -> bool {
145        let [a, b, modulus] = self.const_operands();
146        match modulus {
147            // (a + b) % 0 => 0, (a + b) % 1 => 0.
148            Some(U256::ZERO) | Some(U256::ONE) => self.fold_const(0),
149            _ => match (a, b) {
150                // (0 + 0) % N => 0.
151                (Some(U256::ZERO), Some(U256::ZERO)) => self.fold_const(0),
152                _ => return false,
153            },
154        }
155        true
156    }
157
158    /// MULMOD a, b, N => (a * b) % N.
159    fn peephole_mulmod(&mut self) -> bool {
160        let [a, b, modulus] = self.const_operands();
161        match modulus {
162            // (a * b) % 0 => 0, (a * b) % 1 => 0.
163            Some(U256::ZERO) | Some(U256::ONE) => self.fold_const(0),
164            _ => {
165                // a * 0 => 0, 0 * b => 0.
166                if a == Some(U256::ZERO) || b == Some(U256::ZERO) {
167                    self.fold_const(0);
168                } else {
169                    return false;
170                }
171            }
172        }
173        true
174    }
175
176    /// EXP base, exp => base ** exp.
177    ///
178    /// Dynamic gas is folded into the section gas cost by `SectionsAnalysis` when the
179    /// exponent is a compile-time constant, so the section gas check already covers it.
180    /// For constant-base cases with a dynamic exponent, call `ExpGas` to charge only the
181    /// dynamic gas and compute the result inline.
182    fn peephole_exp(&mut self) -> bool {
183        let [base, exponent] = self.const_operands();
184        match exponent {
185            // x ** 0 => 1.
186            Some(U256::ZERO) => self.fold_const(1),
187            // x ** 1 => x.
188            Some(U256::ONE) => {
189                let [a, _] = self.popn();
190                self.push(a);
191            }
192            // x ** 2 => x * x.
193            Some(e) if e == 2 => {
194                let [a, _] = self.popn();
195                let r = self.bcx.imul(a, a);
196                self.push(r);
197            }
198            Some(_) => return false,
199            None => {}
200        }
201        if exponent.is_some() {
202            return true;
203        }
204        match base {
205            // 0 ** x => x == 0 ? 1 : 0.
206            Some(U256::ZERO) => {
207                self.pay_exp_dynamic_gas();
208                let [_, exponent] = self.popn();
209                let is_zero = self.bcx.icmp_imm(IntCC::Equal, exponent, 0);
210                let one = self.bcx.iconst_256(1);
211                let zero = self.bcx.iconst_256(0);
212                let r = self.bcx.select(is_zero, one, zero);
213                self.push(r);
214            }
215            // 1 ** x => 1.
216            Some(U256::ONE) => {
217                self.pay_exp_dynamic_gas();
218                self.fold_const(1);
219            }
220            // (-1) ** x => x % 2 == 0 ? 1 : -1.
221            Some(U256::MAX) => {
222                self.pay_exp_dynamic_gas();
223                let [_, exponent] = self.popn();
224                let is_odd = self.bcx.bitand_imm(exponent, 1);
225                let is_even = self.bcx.icmp_imm(IntCC::Equal, is_odd, 0);
226                let one = self.bcx.iconst_256(1);
227                let minus_one = self.bcx.iconst_256(U256::MAX);
228                let r = self.bcx.select(is_even, one, minus_one);
229                self.push(r);
230            }
231            // (2 ** k) ** x => x < ceil(256 / k) ? 1 << (k * x) : 0.
232            Some(base) if base.is_power_of_two() => {
233                self.pay_exp_dynamic_gas();
234                let k = base.trailing_zeros();
235                let threshold = i64::from(256_u16.div_ceil(k as u16));
236                let [_, exponent] = self.popn();
237                let in_range = self.bcx.icmp_imm(IntCC::UnsignedLessThan, exponent, threshold);
238                let shift = if k == 1 { exponent } else { self.bcx.imul_imm(exponent, k as i64) };
239                let one = self.bcx.iconst_256(1);
240                let shifted = self.bcx.ishl(one, shift);
241                let zero = self.bcx.iconst_256(0);
242                let r = self.bcx.select(in_range, shifted, zero);
243                self.push(r);
244            }
245            _ => return false,
246        }
247        true
248    }
249
250    fn pay_exp_dynamic_gas(&mut self) {
251        let exponent = self.sp_after_inputs_with(&[1]);
252        self.call_fallible_builtin(Builtin::ExpGas, &[self.ecx, exponent]);
253    }
254
255    /// SIGNEXTEND ext, x => sign-extend x from (ext+1) bytes.
256    fn peephole_signextend(&mut self) -> bool {
257        let [ext, _x] = self.const_operands();
258        match ext {
259            // SIGNEXTEND(e, x) with e >= 31 => x (no-op, value already fills 32 bytes).
260            Some(e) if e >= 31 => {
261                let [_, x] = self.popn();
262                self.push(x);
263            }
264            _ => return false,
265        }
266        true
267    }
268
269    /// BYTE index, value => `value[index]`.
270    fn peephole_byte(&mut self) -> bool {
271        let [index, _value] = self.const_operands();
272        match index {
273            // BYTE(i, x) with i >= 32 => 0.
274            Some(i) if i >= 32 => self.fold_const(0),
275            _ => return false,
276        }
277        true
278    }
279
280    fn peephole_load(&mut self, op: u8) -> bool {
281        if let [Some(offset)] = self.const_operands()
282            && let Ok(offset) = u64::try_from(offset)
283        {
284            let builtin = match op {
285                op::SLOAD => Builtin::SloadC,
286                op::CALLDATALOAD => Builtin::CallDataLoadC,
287                _ => unreachable!(),
288            };
289            let sp = self.sp_from_top(1);
290            let offset = self.bcx.iconst(self.isize_type, offset as i64);
291            let args = &[self.ecx, sp, offset];
292            if op == op::CALLDATALOAD {
293                let _ = self.call_builtin(builtin, args);
294            } else {
295                self.call_fallible_builtin(builtin, args);
296            }
297            // Builtin wrote output to sp; reload into virtual stack.
298            let off = self.section_len_offset - 1;
299            let value = self.load_word(sp, "builtin.out");
300            self.vstack.set_at_offset(off, value);
301            true
302        } else {
303            false
304        }
305    }
306
307    fn peephole_keccak256(&mut self) -> bool {
308        if let Some((offset, len)) = self.const_memory_operands(self.const_operands()) {
309            let offset = self.bcx.iconst(self.isize_type, offset as i64);
310            let len = self.bcx.iconst(self.isize_type, len as i64);
311            let sp = self.sp_after_inputs_with(&[]);
312            self.call_fallible_builtin(Builtin::Keccak256CC, &[self.ecx, sp, offset, len]);
313            true
314        } else {
315            false
316        }
317    }
318
319    fn peephole_return(&mut self, op: u8) -> bool {
320        if let Some((offset, len)) = self.const_memory_operands(self.const_operands()) {
321            // Both operands constant; consume from virtual stack.
322            self.pop_ignore(2);
323            let ir = match op {
324                op::RETURN => InstructionResult::Return,
325                op::REVERT => InstructionResult::Revert,
326                _ => unreachable!(),
327            };
328            let ir_const = self.bcx.iconst(self.i8_type, ir as i64);
329            let offset = self.bcx.iconst(self.isize_type, offset as i64);
330            let len = self.bcx.iconst(self.isize_type, len as i64);
331            let _ = self.call_builtin(Builtin::DoReturnCC, &[self.ecx, offset, len, ir_const]);
332            self.bcx.unreachable();
333            true
334        } else {
335            false
336        }
337    }
338
339    /* utils */
340
341    fn const_memory_operands(&self, args: [Option<U256>; 2]) -> Option<(u64, u64)> {
342        if let [offset, Some(len)] = args
343            && let Ok(len) = u64::try_from(len)
344            // For len == 0 offset is ignored.
345            && let Some(offset) = offset
346                .and_then(|offset| u64::try_from(offset).ok())
347                .or_else(|| (len == 0).then_some(0))
348        {
349            Some((offset, len))
350        } else {
351            None
352        }
353    }
354}