1use super::block_analysis::AbsValue;
4use crate::{
5 InstData,
6 bytecode::{U256Imm, U256Interner},
7};
8use revm_bytecode::opcode as op;
9use revm_interpreter::instructions::i256::{i256_cmp, i256_div, i256_mod};
10use revm_primitives::U256;
11use std::cmp::Ordering;
12
13pub(crate) fn const_fold_gas(
20 opcode: u8,
21 inputs: &[AbsValue],
22 interner: &U256Interner,
23) -> Option<u64> {
24 let gas: u64 = match opcode {
25 op::CODESIZE | op::PC => 2,
26 op::ISZERO | op::NOT => 3,
27 op::CLZ => 3,
28 op::ADD | op::SUB => 3,
29 op::MUL | op::DIV | op::SDIV | op::MOD | op::SMOD => 5,
30 op::ADDMOD | op::MULMOD => 8,
31 op::SIGNEXTEND => 5,
32 op::LT | op::GT | op::SLT | op::SGT | op::EQ => 3,
33 op::AND | op::OR | op::XOR => 3,
34 op::BYTE | op::SHL | op::SHR | op::SAR => 3,
35 op::EXP => {
36 let exponent_bytes = match inputs.first() {
39 Some(&AbsValue::Const(imm)) => {
40 let val = imm.get(interner);
41 (256 - val.leading_zeros()).div_ceil(8)
42 }
43 _ => return None,
44 };
45 10 + 50 * exponent_bytes as u64
46 }
47 _ => return None,
48 };
49 Some(gas)
50}
51
52pub(crate) fn try_const_fold(
56 inst: &InstData,
57 inputs: &[AbsValue],
58 interner: &mut U256Interner,
59 code_len: usize,
60) -> Option<AbsValue> {
61 let opcode = inst.opcode;
62 let result = match opcode {
63 op::CODESIZE => U256::from(code_len),
65 op::PC => U256::from(inst.pc_imm()),
66
67 op::ISZERO | op::NOT | op::CLZ => {
69 let &[AbsValue::Const(ai)] = inputs else {
70 return None;
71 };
72 let a = ai.get(interner);
73 match opcode {
74 op::ISZERO => U256::from(a.is_zero()),
75 op::NOT => !a,
76 op::CLZ => U256::from(a.leading_zeros()),
77 _ => unreachable!(),
78 }
79 }
80
81 op::ADD
83 | op::MUL
84 | op::SUB
85 | op::DIV
86 | op::SDIV
87 | op::MOD
88 | op::SMOD
89 | op::EXP
90 | op::SIGNEXTEND
91 | op::LT
92 | op::GT
93 | op::SLT
94 | op::SGT
95 | op::EQ
96 | op::AND
97 | op::OR
98 | op::XOR
99 | op::BYTE
100 | op::SHL
101 | op::SHR
102 | op::SAR => {
103 let &[AbsValue::Const(bi), AbsValue::Const(ai)] = inputs else {
104 return None;
105 };
106 let a = ai.get(interner);
107 let b = bi.get(interner);
108 match opcode {
109 op::ADD => a.wrapping_add(b),
110 op::MUL => a.wrapping_mul(b),
111 op::SUB => a.wrapping_sub(b),
112 op::DIV => {
113 if !b.is_zero() {
114 a.wrapping_div(b)
115 } else {
116 U256::ZERO
117 }
118 }
119 op::SDIV => i256_div(a, b),
120 op::MOD => {
121 if !b.is_zero() {
122 a.wrapping_rem(b)
123 } else {
124 U256::ZERO
125 }
126 }
127 op::SMOD => i256_mod(a, b),
128 op::EXP => a.pow(b),
129 op::SIGNEXTEND => {
130 if a < U256::from(31) {
131 let ext = a.as_limbs()[0];
132 let bit_index = (8 * ext + 7) as usize;
133 let bit = b.bit(bit_index);
134 let mask = (U256::from(1) << bit_index) - U256::from(1);
135 if bit { b | !mask } else { b & mask }
136 } else {
137 b
138 }
139 }
140 op::LT => U256::from(a < b),
141 op::GT => U256::from(a > b),
142 op::SLT => U256::from(i256_cmp(&a, &b) == Ordering::Less),
143 op::SGT => U256::from(i256_cmp(&a, &b) == Ordering::Greater),
144 op::EQ => U256::from(a == b),
145 op::AND => a & b,
146 op::OR => a | b,
147 op::XOR => a ^ b,
148 op::BYTE => {
149 let i = a.saturating_to::<usize>();
150 if i < 32 { U256::from(b.byte(31 - i)) } else { U256::ZERO }
151 }
152 op::SHL => {
153 let shift = a.saturating_to::<usize>();
154 if shift < 256 { b << shift } else { U256::ZERO }
155 }
156 op::SHR => {
157 let shift = a.saturating_to::<usize>();
158 if shift < 256 { b >> shift } else { U256::ZERO }
159 }
160 op::SAR => {
161 let shift = a.saturating_to::<usize>();
162 if shift < 256 {
163 b.arithmetic_shr(shift)
164 } else if b.bit(255) {
165 U256::MAX
166 } else {
167 U256::ZERO
168 }
169 }
170 _ => unreachable!(),
171 }
172 }
173
174 op::ADDMOD | op::MULMOD => {
176 let &[AbsValue::Const(ci), AbsValue::Const(bi), AbsValue::Const(ai)] = inputs else {
177 return None;
178 };
179 let a = ai.get(interner);
180 let b = bi.get(interner);
181 let n = ci.get(interner);
182 match opcode {
183 op::ADDMOD => a.add_mod(b, n),
184 op::MULMOD => a.mul_mod(b, n),
185 _ => unreachable!(),
186 }
187 }
188
189 _ => return None,
190 };
191 debug_assert!(
192 const_fold_gas(inst.opcode, inputs, interner).is_some(),
193 "try_const_fold handled opcode {} but const_fold_gas did not",
194 inst.opcode,
195 );
196 Some(AbsValue::Const(U256Imm::new(result, interner)))
197}
198
199#[cfg(test)]
200mod tests {
201 use super::*;
202 use crate::bytecode::{
203 Bytecode,
204 passes::block_analysis::tests::{Inst, analyze_asm},
205 };
206 use revm_bytecode::opcode::OpCode;
207 use revm_primitives::U256;
208 use std::{fmt::Write, time::Instant};
209
210 fn const_fold(opcode: u8, operands: &[U256]) -> Option<U256> {
216 use std::fmt::Write;
217 let mut src = String::new();
218 let mut num_insts = 0usize;
219 for v in operands.iter().rev() {
221 writeln!(src, "PUSH {v}").unwrap();
222 num_insts += 1;
223 }
224 writeln!(src, "{}", OpCode::new(opcode).unwrap()).unwrap();
225 num_insts += 1;
226 let mstore_inst = num_insts + 1;
228 writeln!(src, "PUSH0\nMSTORE\nSTOP").unwrap();
229
230 let bytecode = analyze_asm(&src);
231 bytecode.const_operand(Inst::from_usize(mstore_inst), 1)
233 }
234
235 #[test]
236 fn const_fold_0_to_1() {
237 assert_eq!(const_fold(op::CODESIZE, &[]), Some(U256::from(4)));
239
240 assert_eq!(const_fold(op::PC, &[]), Some(U256::ZERO));
242 }
243
244 #[test]
245 fn const_fold_1_to_1() {
246 assert_eq!(const_fold(op::ISZERO, &[U256::ZERO]), Some(U256::from(1)));
248 assert_eq!(const_fold(op::ISZERO, &[U256::from(42)]), Some(U256::ZERO));
249
250 assert_eq!(const_fold(op::NOT, &[U256::ZERO]), Some(U256::MAX));
252 assert_eq!(const_fold(op::NOT, &[U256::MAX]), Some(U256::ZERO));
253 }
254
255 #[test]
256 fn const_fold_add() {
257 assert_eq!(const_fold(op::ADD, &[U256::from(3), U256::from(4)]), Some(U256::from(7)));
258 assert_eq!(const_fold(op::ADD, &[U256::MAX, U256::from(1)]), Some(U256::ZERO));
259 }
260
261 #[test]
262 fn const_fold_mul() {
263 assert_eq!(const_fold(op::MUL, &[U256::from(3), U256::from(7)]), Some(U256::from(21)));
264 assert_eq!(const_fold(op::MUL, &[U256::from(5), U256::ZERO]), Some(U256::ZERO));
265 }
266
267 #[test]
268 fn const_fold_sub() {
269 assert_eq!(const_fold(op::SUB, &[U256::from(10), U256::from(3)]), Some(U256::from(7)));
271 assert_eq!(const_fold(op::SUB, &[U256::ZERO, U256::from(1)]), Some(U256::MAX));
272 }
273
274 #[test]
275 fn const_fold_div() {
276 assert_eq!(const_fold(op::DIV, &[U256::from(10), U256::from(3)]), Some(U256::from(3)));
278 assert_eq!(const_fold(op::DIV, &[U256::from(7), U256::ZERO]), Some(U256::ZERO));
279 }
280
281 #[test]
282 fn const_fold_sdiv() {
283 assert_eq!(const_fold(op::SDIV, &[U256::from(10), U256::from(3)]), Some(U256::from(3)));
284 let neg10 = U256::ZERO.wrapping_sub(U256::from(10));
285 let neg3 = U256::ZERO.wrapping_sub(U256::from(3));
286 assert_eq!(const_fold(op::SDIV, &[neg10, U256::from(3)]), Some(neg3));
288 assert_eq!(const_fold(op::SDIV, &[U256::from(7), U256::ZERO]), Some(U256::ZERO));
289 }
290
291 #[test]
292 fn const_fold_mod() {
293 assert_eq!(const_fold(op::MOD, &[U256::from(10), U256::from(3)]), Some(U256::from(1)));
295 assert_eq!(const_fold(op::MOD, &[U256::from(7), U256::ZERO]), Some(U256::ZERO));
296 }
297
298 #[test]
299 fn const_fold_smod() {
300 let neg8 = U256::ZERO.wrapping_sub(U256::from(8));
301 let neg3 = U256::ZERO.wrapping_sub(U256::from(3));
302 let neg2 = U256::ZERO.wrapping_sub(U256::from(2));
303 assert_eq!(const_fold(op::SMOD, &[neg8, U256::from(3)]), Some(neg2));
305 assert_eq!(const_fold(op::SMOD, &[neg8, neg3]), Some(neg2));
307 }
308
309 #[test]
310 fn const_fold_addmod() {
311 assert_eq!(
313 const_fold(op::ADDMOD, &[U256::from(10), U256::from(10), U256::from(8)]),
314 Some(U256::from(4)),
315 );
316 assert_eq!(
317 const_fold(op::ADDMOD, &[U256::from(1), U256::from(2), U256::ZERO]),
318 Some(U256::ZERO),
319 );
320 }
321
322 #[test]
323 fn const_fold_mulmod() {
324 assert_eq!(
326 const_fold(op::MULMOD, &[U256::from(10), U256::from(10), U256::from(8)]),
327 Some(U256::from(4)),
328 );
329 }
330
331 #[test]
332 fn const_fold_exp() {
333 assert_eq!(const_fold(op::EXP, &[U256::from(2), U256::from(10)]), Some(U256::from(1024)));
335 assert_eq!(const_fold(op::EXP, &[U256::from(5), U256::ZERO]), Some(U256::from(1)));
337 }
338
339 #[test]
340 fn const_fold_signextend() {
341 assert_eq!(const_fold(op::SIGNEXTEND, &[U256::ZERO, U256::from(0xFF)]), Some(U256::MAX));
343 assert_eq!(
345 const_fold(op::SIGNEXTEND, &[U256::ZERO, U256::from(0x7F)]),
346 Some(U256::from(0x7F)),
347 );
348 assert_eq!(
350 const_fold(op::SIGNEXTEND, &[U256::from(31), U256::from(0xFF)]),
351 Some(U256::from(0xFF)),
352 );
353 }
354
355 #[test]
356 fn const_fold_lt_gt_eq() {
357 assert_eq!(const_fold(op::LT, &[U256::from(1), U256::from(2)]), Some(U256::from(1)));
359 assert_eq!(const_fold(op::LT, &[U256::from(2), U256::from(1)]), Some(U256::ZERO));
360 assert_eq!(const_fold(op::GT, &[U256::from(2), U256::from(1)]), Some(U256::from(1)));
362 assert_eq!(const_fold(op::GT, &[U256::from(1), U256::from(2)]), Some(U256::ZERO));
363 assert_eq!(const_fold(op::EQ, &[U256::from(5), U256::from(5)]), Some(U256::from(1)));
364 assert_eq!(const_fold(op::EQ, &[U256::from(5), U256::from(6)]), Some(U256::ZERO));
365 }
366
367 #[test]
368 fn const_fold_slt_sgt() {
369 let neg1 = U256::MAX;
370 assert_eq!(const_fold(op::SLT, &[neg1, U256::from(1)]), Some(U256::from(1)));
372 assert_eq!(const_fold(op::SLT, &[U256::from(1), neg1]), Some(U256::ZERO));
374 assert_eq!(const_fold(op::SGT, &[U256::from(1), neg1]), Some(U256::from(1)));
376 }
377
378 #[test]
379 fn const_fold_and_or_xor() {
380 assert_eq!(
381 const_fold(op::AND, &[U256::from(0xFF), U256::from(0x0F)]),
382 Some(U256::from(0x0F)),
383 );
384 assert_eq!(
385 const_fold(op::OR, &[U256::from(0xF0), U256::from(0x0F)]),
386 Some(U256::from(0xFF)),
387 );
388 assert_eq!(const_fold(op::XOR, &[U256::from(0xFF), U256::from(0xFF)]), Some(U256::ZERO));
389 }
390
391 #[test]
392 fn const_fold_byte() {
393 assert_eq!(
395 const_fold(op::BYTE, &[U256::from(31), U256::from(0xFF)]),
396 Some(U256::from(0xFF)),
397 );
398 assert_eq!(const_fold(op::BYTE, &[U256::ZERO, U256::from(0xFF)]), Some(U256::ZERO));
400 assert_eq!(const_fold(op::BYTE, &[U256::from(32), U256::from(0xFF)]), Some(U256::ZERO));
402 }
403
404 #[test]
405 fn const_fold_shl_shr() {
406 assert_eq!(
408 const_fold(op::SHL, &[U256::from(1), U256::from(0x80)]),
409 Some(U256::from(0x100)),
410 );
411 assert_eq!(const_fold(op::SHL, &[U256::from(256), U256::from(1)]), Some(U256::ZERO));
412 assert_eq!(const_fold(op::SHR, &[U256::from(1), U256::from(0x80)]), Some(U256::from(0x40)),);
414 assert_eq!(const_fold(op::SHR, &[U256::from(256), U256::from(1)]), Some(U256::ZERO));
415 }
416
417 #[test]
418 fn const_fold_sar() {
419 assert_eq!(const_fold(op::SAR, &[U256::from(1), U256::from(2)]), Some(U256::from(1)));
421 assert_eq!(const_fold(op::SAR, &[U256::from(4), U256::MAX]), Some(U256::MAX));
423 assert_eq!(const_fold(op::SAR, &[U256::from(256), U256::MAX]), Some(U256::MAX));
425 assert_eq!(const_fold(op::SAR, &[U256::from(256), U256::from(1)]), Some(U256::ZERO));
427 }
428
429 fn build_exp_bomb(n: usize) -> Vec<u8> {
431 let mut src = String::new();
432 for _ in 0..n {
433 writeln!(src, "PUSH {}", U256::MAX).unwrap();
434 writeln!(src, "PUSH {}", U256::MAX).unwrap();
435 writeln!(src, "EXP").unwrap();
436 writeln!(src, "POP").unwrap();
437 }
438 writeln!(src, "STOP").unwrap();
439 crate::parse_asm(&src).unwrap()
440 }
441
442 #[test]
445 fn compiler_gas_limit_exp_bomb() {
446 crate::tests::init_tracing();
447
448 let code = build_exp_bomb(500);
449 let start = Instant::now();
450 let mut bytecode = Bytecode::test(code);
451 bytecode.analyze().unwrap();
452 let elapsed = start.elapsed();
453
454 assert!(
458 elapsed.as_secs() < 30,
459 "compilation took too long ({elapsed:?}), gas limit may not be working",
460 );
461 assert!(bytecode.compiler_gas_used <= bytecode.compiler_gas_limit);
462 }
463
464 #[test]
469 fn compiler_gas_limit_no_limit_is_slower() {
470 crate::tests::init_tracing();
471
472 let mut src = String::new();
474 for _ in 0..100_000 {
475 writeln!(src, "PUSH 0x01").unwrap();
476 writeln!(src, "PUSH 0x02").unwrap();
477 writeln!(src, "ADD").unwrap();
478 writeln!(src, "POP").unwrap();
479 }
480 writeln!(src, "STOP").unwrap();
481 let code = crate::parse_asm(&src).unwrap();
482
483 let start_limited = Instant::now();
485 let mut limited = Bytecode::test(code.clone());
486 limited.analyze().unwrap();
487 let elapsed_limited = start_limited.elapsed();
488
489 let start_unlimited = Instant::now();
491 let mut unlimited = Bytecode::test(code);
492 unlimited.compiler_gas_limit = u64::MAX;
493 unlimited.analyze().unwrap();
494 let elapsed_unlimited = start_unlimited.elapsed();
495
496 eprintln!("limited (100k): {elapsed_limited:?} (gas used: {})", limited.compiler_gas_used);
497 eprintln!(
498 "unlimited (MAX): {elapsed_unlimited:?} (gas used: {})",
499 unlimited.compiler_gas_used,
500 );
501
502 assert!(unlimited.compiler_gas_used > limited.compiler_gas_used);
504 assert!(limited.compiler_gas_used <= limited.compiler_gas_limit);
506 }
507
508 #[test]
510 fn compiler_gas_limit_cheap_exp_volume() {
511 crate::tests::init_tracing();
512
513 let mut src = String::new();
515 for _ in 0..20_000 {
516 writeln!(src, "PUSH {}", U256::MAX).unwrap();
517 writeln!(src, "PUSH 0xff").unwrap(); writeln!(src, "EXP").unwrap();
519 writeln!(src, "POP").unwrap();
520 }
521 writeln!(src, "STOP").unwrap();
522
523 let code = crate::parse_asm(&src).unwrap();
524 let start = Instant::now();
525 let mut bytecode = Bytecode::test(code);
526 bytecode.analyze().unwrap();
527 let elapsed = start.elapsed();
528
529 assert!(elapsed.as_secs() < 30, "compilation took too long ({elapsed:?})",);
530 assert!(bytecode.compiler_gas_used <= bytecode.compiler_gas_limit);
531 }
532
533 #[test]
535 fn compiler_gas_limit_zero_disables_folding() {
536 crate::tests::init_tracing();
537
538 let src = "PUSH 2\nPUSH 3\nADD\nPUSH0\nMSTORE\nSTOP\n";
539 let code = crate::parse_asm(src).unwrap();
540 let mut bytecode = Bytecode::test(code);
541 bytecode.compiler_gas_limit = 0;
542 bytecode.analyze().unwrap();
543
544 assert_eq!(bytecode.compiler_gas_used, 0);
546 assert!(bytecode.const_operand(Inst::from_usize(4), 1).is_none());
549 }
550
551 #[test]
553 fn compiler_gas_limit_unlimited() {
554 crate::tests::init_tracing();
555
556 let src = "PUSH 2\nPUSH 3\nADD\nPUSH0\nMSTORE\nSTOP\n";
557 let code = crate::parse_asm(src).unwrap();
558 let mut bytecode = Bytecode::test(code);
559 bytecode.compiler_gas_limit = u64::MAX;
560 bytecode.analyze().unwrap();
561
562 assert!(bytecode.compiler_gas_used > 0);
563 assert_eq!(bytecode.const_operand(Inst::from_usize(4), 1), Some(U256::from(5)));
565 }
566
567 #[test]
571 fn const_fold_gas_sync() {
572 let mut interner: crate::bytecode::U256Interner = crate::bytecode::Interner::new();
573 let one = U256Imm::new(U256::from(1), &mut interner);
574 let two = U256Imm::new(U256::from(2), &mut interner);
575 let three = U256Imm::new(U256::from(3), &mut interner);
576 let c1 = AbsValue::Const(one);
577 let c2 = AbsValue::Const(two);
578 let c3 = AbsValue::Const(three);
579
580 for opcode in 0..=u8::MAX {
581 let inst = crate::InstData { opcode, ..crate::InstData::new(opcode) };
582 let (inp, _) = inst.stack_io();
583
584 let inputs: &[AbsValue] = match inp {
585 0 => &[],
586 1 => &[c1],
587 2 => &[c1, c2],
588 3 => &[c1, c2, c3],
589 _ => continue,
590 };
591
592 let gas = const_fold_gas(opcode, inputs, &interner);
593 let fold = try_const_fold(&inst, inputs, &mut interner, 100);
594
595 assert_eq!(
596 gas.is_some(),
597 fold.is_some(),
598 "const_fold_gas and try_const_fold disagree on opcode {opcode:#04x} ({}): \
599 gas={gas:?}, fold={fold:?}",
600 revm_bytecode::opcode::OpCode::new(opcode).map_or("unknown", |o| o.as_str()),
601 );
602 }
603 }
604}