1use crate::FxHashMap;
4use bitvec::vec::BitVec;
5use oxc_index::IndexVec;
6use revm_bytecode::opcode as op;
7use revm_primitives::{U256, hardfork::SpecId};
8use revmc_backend::Result;
9use smallvec::SmallVec;
10use std::{borrow::Cow, cell::RefCell};
11
12pub(crate) use revm_context_interface::cfg::GasParams;
13
14mod passes;
15pub(crate) use passes::{
16 Block, Cfg, GasSection, MemorySection, MemorySectionAnalysis, SectionsAnalysis, Snapshots,
17 StackSection,
18};
19
20mod asm;
21pub use asm::parse_asm;
22
23mod fmt;
24
25mod interner;
26pub(crate) use interner::Interner;
27
28mod info;
29pub use info::*;
30
31mod opcode;
32pub use opcode::*;
33
34#[cfg(any(feature = "__fuzzing", test))]
36pub(crate) const TEST_SUSPEND: u8 = 0x25;
37
38macro_rules! impl_index_display {
40 ($ty:ty, $fmt:literal) => {
41 impl std::fmt::Display for $ty {
42 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43 write!(f, $fmt, self.index())
44 }
45 }
46 };
47}
48pub(crate) use impl_index_display;
49
50oxc_index::define_nonmax_u32_index_type! {
51 pub(crate) struct Inst;
55}
56impl_index_display!(Inst, "ic{}");
57
58oxc_index::define_nonmax_u32_index_type! {
59 pub(crate) struct U256Idx;
61}
62impl_index_display!(U256Idx, "{}");
63
64pub(crate) type U256Interner = Interner<U256Idx, U256, alloy_primitives::map::FbBuildHasher<32>>;
66
67#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
71pub(crate) struct U256Imm(u32);
72
73impl U256Imm {
74 const INLINE_TAG: u32 = 1 << 31;
76
77 #[inline]
79 pub(crate) fn from_raw(raw: u32) -> Self {
80 Self(raw)
81 }
82
83 #[inline]
85 pub(crate) fn to_raw(self) -> u32 {
86 self.0
87 }
88
89 #[inline]
91 pub(crate) fn new(value: U256, interner: &mut U256Interner) -> Self {
92 if let Ok(x) = u32::try_from(value)
93 && x & Self::INLINE_TAG == 0
94 {
95 Self(Self::INLINE_TAG | x)
96 } else {
97 let idx = interner.intern(value).index() as u32;
98 debug_assert!(
99 idx & Self::INLINE_TAG == 0,
100 "U256 interner exceeded 2^31 entries; bump `U256Imm` encoding",
101 );
102 Self(idx)
103 }
104 }
105
106 #[inline]
108 pub(crate) fn get(self, interner: &U256Interner) -> U256 {
109 if self.0 & Self::INLINE_TAG != 0 {
110 U256::from(self.0 & !Self::INLINE_TAG)
111 } else {
112 *interner.get(U256Idx::from_usize(self.0 as usize))
113 }
114 }
115
116 #[inline]
118 pub(crate) fn get_inline(self) -> Option<u32> {
119 (self.0 & Self::INLINE_TAG != 0).then_some(self.0 & !Self::INLINE_TAG)
120 }
121}
122
123impl std::fmt::Debug for U256Imm {
124 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
125 match self.get_inline() {
126 Some(x) => write!(f, "U256Imm::Inline({x})"),
127 None => write!(f, "U256Imm::Idx({})", self.0),
128 }
129 }
130}
131
132bitflags::bitflags! {
133 #[derive(Clone, Copy, Debug)]
135 pub(crate) struct AnalysisConfig: u8 {
136 const DEDUP = 1 << 0;
138 const INSPECT_STACK = 1 << 1;
141 const DSE = 1 << 2;
143
144 const ALL = Self::DEDUP.bits() | Self::DSE.bits();
146 }
147}
148
149impl Default for AnalysisConfig {
150 #[inline]
151 fn default() -> Self {
152 Self::ALL
153 }
154}
155
156pub(crate) const DEFAULT_COMPILER_GAS_LIMIT: u64 = 100_000;
158
159#[doc(hidden)] pub struct Bytecode<'a> {
162 pub(crate) code: Cow<'a, [u8]>,
164 insts: IndexVec<Inst, InstData>,
166 jumpdests: BitVec,
168 pub(crate) spec_id: SpecId,
170 pub(crate) gas_params: GasParams,
172 has_dynamic_jumps: bool,
174 may_suspend: bool,
176 pc_to_inst: FxHashMap<u32, Inst>,
178 inst_to_pc: IndexVec<Inst, u32>,
183
184 u256_interner: RefCell<U256Interner>,
186
187 snapshots: Snapshots,
189 memory_sections: IndexVec<Inst, MemorySection>,
191 multi_jump_targets: FxHashMap<Inst, SmallVec<[Inst; 4]>>,
194
195 inst_lines: RefCell<IndexVec<Inst, u32>>,
197
198 pub(crate) redirects: FxHashMap<Inst, Inst>,
201 cfg: Cfg,
203 pub(crate) config: AnalysisConfig,
205 pub(crate) compiler_gas_limit: u64,
214 pub(crate) compiler_gas_used: u64,
216}
217
218impl<'a> Bytecode<'a> {
219 pub(crate) fn new(
220 code: impl Into<Cow<'a, [u8]>>,
221 spec_id: SpecId,
222 gas_params: Option<GasParams>,
223 ) -> Self {
224 Self::new_mono(code.into(), spec_id, gas_params)
225 }
226
227 #[cfg(test)]
228 pub(crate) fn test(code: impl Into<Cow<'a, [u8]>>) -> Self {
229 Self::new(code, crate::tests::DEF_SPEC, None)
230 }
231
232 #[instrument(name = "Bytecode::new", level = "debug", skip_all)]
233 fn new_mono(code: Cow<'a, [u8]>, spec_id: SpecId, gas_params: Option<GasParams>) -> Self {
234 let gas_params = gas_params.unwrap_or_else(|| GasParams::new_spec(spec_id));
235 let mut insts = IndexVec::with_capacity(code.len() + 8);
236 let mut inst_to_pc = IndexVec::with_capacity(code.len() + 8);
237 let mut jumpdests = BitVec::repeat(false, code.len());
238 let mut pc_to_inst = FxHashMap::with_capacity_and_hasher(code.len(), Default::default());
239 let mut u256_interner = U256Interner::new();
240 let op_infos = op_info_map(spec_id);
241 let mut iter = OpcodesIter::new(&code, spec_id).with_pc();
242 while let Some((pc, Opcode { opcode, immediate })) = iter.next() {
243 let inst: Inst = insts.next_idx();
244 pc_to_inst.insert(pc as u32, inst);
245 inst_to_pc.push(pc as u32);
246
247 if opcode == op::JUMPDEST {
248 jumpdests.set(pc, true)
249 }
250
251 let mut flags = InstFlags::empty();
252 let info = op_infos[opcode as usize];
253 if info.is_unknown() {
254 flags |= InstFlags::UNKNOWN;
255 }
256 if info.is_disabled() {
257 flags |= InstFlags::DISABLED;
258 }
259 let (base_gas, stack_io) = if info.is_unknown() || info.is_disabled() {
260 (0, (0, 0))
261 } else {
262 (info.base_gas(), compute_stack_io(opcode, immediate))
263 };
264
265 let data = match opcode {
266 op::PUSH0..=op::PUSH32 | op::DUPN | op::SWAPN | op::EXCHANGE => {
267 let imm_len = min_imm_len(opcode) as usize;
268 let start = pc + 1;
272 let avail = code.get(start..).map(|s| &s[..s.len().min(imm_len)]);
273 let value = match avail {
274 Some(slice) if slice.len() == imm_len => U256::from_be_slice(slice),
275 Some(slice) if !slice.is_empty() => {
276 let mut padded = [0u8; 32];
277 padded[..slice.len()].copy_from_slice(slice);
278 U256::from_be_slice(&padded[..imm_len])
279 }
280 _ => U256::ZERO,
281 };
282 U256Imm::new(value, &mut u256_interner).to_raw()
283 }
284 op::JUMPDEST | op::PC => {
285 let pc = pc as u32;
286 debug_assert!(pc & InstData::JUMPDEST_REACHABLE == 0, "pc too large");
287 pc
288 }
289 _ => 0,
290 };
291
292 let gas_section = GasSection::default();
293 let stack_section = StackSection::default();
294
295 insts.push(InstData {
296 opcode,
297 flags,
298 base_gas,
299 stack_io,
300 data,
301 gas_section,
302 stack_section,
303 });
304
305 if matches!(opcode, op::DUPN | op::SWAPN | op::EXCHANGE)
310 && !info.is_unknown()
311 && !info.is_disabled()
312 && immediate == Some(&[op::JUMPDEST])
313 {
314 unsafe { iter.rewind(1) };
316 }
317 }
318
319 if insts.last().is_none_or(|last| last.can_fall_through()) {
321 trace!("adding STOP padding");
322 insts.push(InstData::new(op::STOP));
323 inst_to_pc.push(code.len() as u32);
324 }
325
326 Self {
327 code,
328 insts,
329 jumpdests,
330 spec_id,
331 gas_params,
332 has_dynamic_jumps: false,
333 may_suspend: false,
334 snapshots: Snapshots::default(),
335 memory_sections: IndexVec::new(),
336 u256_interner: RefCell::new(u256_interner),
337 multi_jump_targets: FxHashMap::default(),
338 pc_to_inst,
339 inst_to_pc,
340 inst_lines: RefCell::new(IndexVec::new()),
341 redirects: FxHashMap::default(),
342 cfg: Cfg::default(),
343 config: AnalysisConfig::default(),
344 compiler_gas_limit: 100_000,
345 compiler_gas_used: 0,
346 }
347 }
348
349 pub(crate) fn take_inst_lines(&self) -> IndexVec<Inst, u32> {
353 if self.inst_lines.borrow().is_empty() {
354 self.collect_lines();
355 }
356 self.inst_lines.take()
357 }
358
359 #[inline]
361 #[allow(dead_code)]
362 pub(crate) fn opcodes(&self) -> OpcodesIter<'_> {
363 OpcodesIter::new(&self.code, self.spec_id)
364 }
365
366 #[inline]
368 #[track_caller]
369 pub(crate) fn inst(&self, inst: Inst) -> &InstData {
370 &self.insts[inst]
371 }
372
373 #[inline]
375 #[track_caller]
376 #[allow(dead_code)]
377 pub(crate) fn inst_mut(&mut self, inst: Inst) -> &mut InstData {
378 &mut self.insts[inst]
379 }
380
381 #[inline]
383 pub(crate) fn opcode(&self, inst: Inst) -> Opcode<'_> {
384 Opcode { opcode: self.inst(inst).opcode, immediate: self.get_imm(inst) }
385 }
386
387 #[inline]
389 pub(crate) fn iter_insts(&self) -> impl DoubleEndedIterator<Item = (Inst, &InstData)> + Clone {
390 self.iter_all_insts().filter(|(_, data)| !data.is_dead_code())
391 }
392
393 #[inline]
395 #[allow(dead_code)]
396 pub(crate) fn iter_mut_insts(
397 &mut self,
398 ) -> impl DoubleEndedIterator<Item = (Inst, &mut InstData)> {
399 self.iter_mut_all_insts().filter(|(_, data)| !data.is_dead_code())
400 }
401
402 #[inline]
404 pub(crate) fn iter_all_insts(
405 &self,
406 ) -> impl DoubleEndedIterator<Item = (Inst, &InstData)> + ExactSizeIterator + Clone {
407 self.insts.iter_enumerated()
408 }
409
410 #[inline]
412 #[allow(dead_code)]
413 pub(crate) fn iter_mut_all_insts(
414 &mut self,
415 ) -> impl DoubleEndedIterator<Item = (Inst, &mut InstData)> + ExactSizeIterator {
416 self.insts.iter_mut_enumerated()
417 }
418
419 #[instrument(level = "debug", skip_all)]
421 pub(crate) fn analyze(&mut self) -> Result<()> {
422 self.refresh_cfg();
423
424 self.block_analysis_local();
425 self.refresh_cfg();
426
427 let local_snapshots = self.snapshots.clone();
428
429 self.block_analysis(&local_snapshots);
430 self.refresh_cfg();
431
432 if self.config.contains(AnalysisConfig::DEDUP) {
433 self.dedup_blocks(&local_snapshots);
434 self.refresh_cfg();
435 }
436 drop(local_snapshots);
437
438 if self.config.contains(AnalysisConfig::DSE) {
439 self.dead_store_elim();
440 }
441
442 self.calc_may_suspend();
443
444 self.construct_sections();
445 self.construct_memory_sections();
446
447 debug!(
448 compiler_gas_used = self.compiler_gas_used,
449 compiler_gas_limit = self.compiler_gas_limit,
450 "constant folding gas budget",
451 );
452
453 if tracing::enabled!(tracing::Level::TRACE) {
454 self.log_ir_stats();
455 self.log_const_input_stats();
456 }
457
458 Ok(())
459 }
460
461 #[instrument(name = "dyn_jumps", level = "debug", skip_all)]
463 #[inline(never)]
464 pub(crate) fn recompute_has_dynamic_jumps(&mut self) {
465 let mut unresolved = self.insts.iter().filter(|inst| {
466 inst.is_jump() && !inst.flags.contains(InstFlags::STATIC_JUMP) && !inst.is_dead_code()
467 });
468 self.has_dynamic_jumps = unresolved.next().is_some();
469 if self.has_dynamic_jumps {
470 debug!(n = 1 + unresolved.count(), "unresolved dynamic jumps remain");
471 }
472 }
473
474 #[instrument(name = "jumpdests", level = "debug", skip_all)]
475 #[inline(never)]
476 pub(crate) fn recompute_reachable_jumpdests(&mut self) {
477 for data in &mut self.insts.raw {
478 if data.opcode == op::JUMPDEST {
479 data.clear_jumpdest_reachable();
480 }
481 }
482
483 let mut targets = Vec::new();
484 for (inst, data) in self.insts.iter_enumerated() {
485 if !data.is_static_jump()
486 || data.flags.contains(InstFlags::INVALID_JUMP)
487 || data.is_dead_code()
488 {
489 continue;
490 }
491 if data.flags.contains(InstFlags::MULTI_JUMP) {
492 if let Some(multi_targets) = self.multi_jump_targets.get(&inst) {
493 targets.extend(multi_targets.iter().copied());
494 }
495 } else {
496 targets.push(data.static_jump_target());
497 }
498 }
499
500 for mut target in targets {
501 while let Some(&next) = self.redirects.get(&target) {
502 target = next;
503 }
504 if self.insts[target].opcode == op::JUMPDEST {
505 self.insts[target].set_jumpdest_reachable();
506 }
507 }
508 }
509
510 #[instrument(name = "dce", level = "debug", skip_all)]
519 #[inline(never)]
520 fn mark_dead_code(&mut self) {
521 loop {
522 if !self.mark_dead_code_once() {
523 break;
524 }
525 self.recompute_has_dynamic_jumps();
526 self.recompute_reachable_jumpdests();
527 }
528 }
529
530 fn mark_dead_code_once(&mut self) -> bool {
531 let mut iter = self.insts.iter_mut_enumerated();
532 let mut marked_jump_dead = false;
533 while let Some((i, data)) = iter.next() {
534 if !data.can_fall_through() {
535 let static_target = data
536 .is_static_jump()
537 .then(|| data.static_jump_target())
538 .filter(|&target| target > i);
539 if static_target == Some(i + 1) {
540 continue;
541 }
542 let mut end = i;
543 let mut any_new = false;
544 for (j, data) in &mut iter {
545 end = j;
546 if static_target == Some(j) {
547 break;
548 }
549 if data.is_reachable_jumpdest(self.has_dynamic_jumps) {
550 break;
551 }
552 if !data.flags.contains(InstFlags::DEAD_CODE) {
553 any_new = true;
554 marked_jump_dead |= data.is_jump();
555 }
556 data.flags |= InstFlags::DEAD_CODE;
557 }
558 let start = i + 1;
559 if any_new && end > start {
560 debug!("found dead code: {start}..{end}");
561 }
562 }
563 }
564 marked_jump_dead
565 }
566
567 #[instrument(name = "suspend", level = "debug", skip_all)]
571 fn calc_may_suspend(&mut self) {
572 let may_suspend = self.iter_insts().any(|(_, data)| data.may_suspend());
573 self.may_suspend = may_suspend;
574 }
575
576 #[instrument(name = "sections", level = "debug", skip_all)]
578 #[inline(never)]
579 fn construct_sections(&mut self) {
580 let mut analysis = SectionsAnalysis::default();
581 for inst in self.insts.indices() {
582 if !self.inst(inst).is_dead_code() {
583 analysis.process(self, inst);
584 }
585 }
586 analysis.finish(self);
587 }
588
589 #[instrument(name = "memory_sections", level = "debug", skip_all)]
591 #[inline(never)]
592 fn construct_memory_sections(&mut self) {
593 self.memory_sections = MemorySectionAnalysis::new(self).run(self);
594 }
595
596 pub(crate) fn get_imm(&self, inst: Inst) -> Option<&[u8]> {
601 let data = self.inst(inst);
602 let imm_len = data.imm_len() as usize;
603 if imm_len == 0 {
604 return None;
605 }
606 let start = self.pc(inst) as usize + 1;
607 let end = (start + imm_len).min(self.code.len());
608 if start >= end {
609 return None;
610 }
611 Some(&self.code[start..end])
612 }
613
614 #[inline]
616 pub(crate) fn pc(&self, inst: Inst) -> u32 {
617 self.inst_to_pc[inst]
618 }
619
620 #[cfg(test)]
623 pub(crate) fn get_push_value(&self, data: &InstData) -> U256 {
624 debug_assert!(matches!(data.opcode, op::PUSH0..=op::PUSH32));
625 data.imm().get(&self.u256_interner.borrow())
626 }
627
628 #[inline]
630 pub(crate) fn codesize(&self) -> usize {
631 self.code.len()
632 }
633
634 fn is_valid_jump(&self, pc: usize) -> bool {
636 self.jumpdests.get(pc).as_deref().copied() == Some(true)
637 }
638
639 pub(crate) fn has_dynamic_jumps(&self) -> bool {
641 self.has_dynamic_jumps
642 }
643
644 pub(crate) fn may_suspend(&self) -> bool {
646 self.may_suspend
647 }
648
649 pub(crate) fn stack_observed(&self) -> bool {
652 self.config.contains(AnalysisConfig::INSPECT_STACK) || self.may_suspend()
653 }
654
655 pub(crate) fn is_instr_diverging(&self, inst: Inst) -> bool {
657 self.insts[inst].is_diverging()
658 }
659
660 #[inline]
662 pub(crate) fn pc_to_inst(&self, pc: usize) -> Inst {
663 match self.pc_to_inst.get(&(pc as u32)) {
664 Some(&inst) => inst,
665 None => panic!("pc out of bounds: {pc}"),
666 }
667 }
668
669 pub(crate) fn multi_jump_targets(&self, inst: Inst) -> Option<&[Inst]> {
671 self.multi_jump_targets.get(&inst).map(|v| v.as_slice())
672 }
673
674 #[allow(dead_code)]
679 pub(crate) fn const_operand(&self, inst: Inst, depth: usize) -> Option<U256> {
680 let snap = &self.snapshots.inputs[inst];
681 let imm = snap.get(snap.len().checked_sub(1 + depth)?)?.as_const()?;
682 Some(imm.get(&self.u256_interner.borrow()))
683 }
684
685 pub(crate) fn const_output(&self, inst: Inst) -> Option<U256> {
690 let imm = self.snapshots.outputs[inst]?.as_const()?;
691 Some(imm.get(&self.u256_interner.borrow()))
692 }
693
694 #[allow(dead_code)]
696 pub(crate) fn const_memory_access(&self, inst: Inst) -> Option<(Option<u64>, Option<u64>)> {
697 self.const_memory_accesses(inst).into_iter().flatten().next()
698 }
699
700 pub(crate) fn const_memory_accesses(
702 &self,
703 inst: Inst,
704 ) -> [Option<(Option<u64>, Option<u64>)>; 2] {
705 let data = self.inst(inst);
706 if data.flags.intersects(InstFlags::DISABLED | InstFlags::UNKNOWN) {
707 return [None, None];
708 }
709
710 let operand =
711 |depth| self.const_operand(inst, depth).and_then(|value| u64::try_from(value).ok());
712 match data.opcode {
713 op::KECCAK256 | op::RETURN | op::REVERT | op::LOG0..=op::LOG4 => {
714 [Some((operand(0), operand(1))), None]
715 }
716 op::MLOAD | op::MSTORE => [Some((operand(0), Some(32))), None],
717 op::MSTORE8 => [Some((operand(0), Some(1))), None],
718 op::CALLDATACOPY | op::CODECOPY | op::RETURNDATACOPY => {
719 [Some((operand(0), operand(2))), None]
720 }
721 op::EXTCODECOPY => [Some((operand(1), operand(3))), None],
722 op::MCOPY => [
723 Some((operand(0).zip(operand(1)).map(|(dst, src)| dst.max(src)), operand(2))),
724 None,
725 ],
726 op::CREATE | op::CREATE2 => [Some((operand(1), operand(2))), None],
727 op::CALL | op::CALLCODE => {
728 [Some((operand(3), operand(4))), Some((operand(5), operand(6)))]
729 }
730 op::DELEGATECALL | op::STATICCALL => {
731 [Some((operand(2), operand(3))), Some((operand(4), operand(5)))]
732 }
733 _ => [None, None],
734 }
735 }
736
737 #[allow(dead_code)]
739 pub(crate) fn memory_section(&self, inst: Inst) -> MemorySection {
740 self.memory_sections.get(inst).copied().unwrap_or_default()
741 }
742
743 #[inline(never)]
745 fn log_const_input_stats(&self) {
746 use op::*;
747 use std::fmt::Write;
748
749 struct Entry {
751 inputs: usize,
752 outputs: usize,
753 total: u32,
754 all_const: u32,
755 const_output: u32,
756 per_input: Vec<[u32; 3]>,
757 }
758 let mut stats = [const { None }; 256];
759 for (inst, data) in self.iter_insts() {
760 let (inputs, outputs) = data.stack_io();
761 if matches!(data.opcode, PUSH0..=PUSH32 | DUP1..=DUP16 | SWAP1..=SWAP16 | DUPN | SWAPN)
762 {
763 continue;
764 }
765 let entry = stats[data.opcode as usize].get_or_insert_with(|| Entry {
766 inputs: inputs as usize,
767 outputs: outputs as usize,
768 total: 0,
769 all_const: 0,
770 const_output: 0,
771 per_input: vec![[0u32; 3]; inputs as usize],
772 });
773 entry.total += 1;
774 let mut all = true;
775 for depth in 0..inputs as usize {
776 entry.per_input[depth][0] += 1;
777 if let Some(val) = self.const_operand(inst, depth) {
778 entry.per_input[depth][1] += 1;
779 if val <= usize::MAX {
780 entry.per_input[depth][2] += 1;
781 }
782 } else {
783 all = false;
784 }
785 }
786 if all {
787 entry.all_const += 1;
788 }
789 if self.const_output(inst).is_some() {
790 entry.const_output += 1;
791 }
792 }
793
794 let mut buf = String::from("const input stats:");
795 for (opcode, entry) in stats.iter().enumerate() {
796 let Some(entry) = entry else { continue };
797 if entry.total == 0 {
798 continue;
799 }
800 let name = OpCode::new(opcode as u8)
801 .map_or_else(|| format!("0x{opcode:02x}"), |o| o.as_str().to_string());
802 let all_pct = entry.all_const as f64 / entry.total as f64 * 100.0;
803 let _ = write!(
804 buf,
805 "\n {name:<16} 0x{opcode:02x}, {total} occ, {inputs} inputs, all_const={ac}/{total} ({all_pct:.0}%)",
806 opcode = opcode,
807 total = entry.total,
808 inputs = entry.inputs,
809 ac = entry.all_const,
810 );
811 if entry.outputs > 0 {
812 let co = entry.const_output;
813 let co_pct = co as f64 / entry.total as f64 * 100.0;
814 let _ = write!(buf, ", const_output={co}/{t} ({co_pct:.0}%)", t = entry.total);
815 }
816 for (i, [t, c, usize_c]) in entry.per_input.iter().enumerate() {
817 if *t > 0 {
818 let pct = *c as f64 / *t as f64 * 100.0;
819 let usize_pct = *usize_c as f64 / *t as f64 * 100.0;
820 let _ = write!(
821 buf,
822 "\n [{i}]: const={c}/{t} ({pct:.0}%), fits_usize={usize_c}/{t} ({usize_pct:.0}%)",
823 );
824 }
825 }
826 }
827 trace!("{buf}");
828 }
829
830 fn ir_stats(&self) -> IrStats {
832 let total = self.insts.len();
833 let mut live = 0usize;
834 let mut noops = 0usize;
835 let mut dead = 0usize;
836 let mut suspends = 0usize;
837 for (_inst, data) in self.iter_all_insts() {
838 if data.is_dead_code() {
839 dead += 1;
840 } else {
841 live += 1;
842 if data.flags.contains(InstFlags::NOOP) {
843 noops += 1;
844 }
845 if data.may_suspend() {
846 suspends += 1;
847 }
848 }
849 }
850
851 let mut lens: Vec<usize> = self.cfg.blocks.iter().map(|data| data.insts().len()).collect();
852 let n = lens.len();
853 lens.sort_unstable();
854 let block_min = lens.first().copied().unwrap_or(0);
855 let block_max = lens.last().copied().unwrap_or(0);
856 let sum: usize = lens.iter().sum();
857 let block_avg = if n > 0 { sum as f64 / n as f64 } else { 0.0 };
858 let block_median = if n > 0 { lens[n / 2] } else { 0 };
859
860 IrStats {
861 total,
862 live,
863 dead,
864 noops,
865 suspends,
866 blocks: n,
867 block_min,
868 block_max,
869 block_avg,
870 block_median,
871 }
872 }
873
874 #[inline(never)]
876 fn log_ir_stats(&self) {
877 use std::fmt::Write;
878
879 let s = self.ir_stats();
880 trace!(
881 total_insts = s.total,
882 live = s.live,
883 dead = s.dead,
884 noops = s.noops,
885 suspends = s.suspends,
886 blocks = s.blocks,
887 block_min = s.block_min,
888 block_max = s.block_max,
889 block_avg = format_args!("{:.1}", s.block_avg),
890 block_median = s.block_median,
891 "ir stats",
892 );
893
894 let mut lens: Vec<(Block, usize)> = self
895 .cfg
896 .blocks
897 .iter_enumerated()
898 .map(|(block, data)| (block, data.insts().len()))
899 .collect();
900 lens.sort_unstable_by_key(|b| std::cmp::Reverse(b.1));
901 lens.truncate(5);
902 let mut buf = String::from("top 5 longest blocks:");
903 for (block, len) in &lens {
904 let data = &self.cfg.blocks[*block];
905 let _ = write!(buf, "\n {block} (pc={}, len={len})", self.pc(data.insts.start));
906 }
907 trace!("{buf}");
908 }
909
910 pub(crate) fn op_block_name(&self, inst: Option<Inst>, name: &str) -> String {
912 use std::fmt::Write;
913
914 let Some(inst) = inst else {
915 return format!("entry.{name}");
916 };
917 let data = self.inst(inst);
918
919 let mut s = String::with_capacity(64);
920 if let Some(block) = self.cfg.inst_to_block[inst] {
921 let _ = write!(s, "{block}.");
922 }
923 let _ = write!(s, "{inst}.{}", data.to_op());
924 if !name.is_empty() {
925 let _ = write!(s, ".{name}");
926 }
927 s
928 }
929}
930
931pub(crate) struct IrStats {
933 pub(crate) total: usize,
934 pub(crate) live: usize,
935 pub(crate) dead: usize,
936 pub(crate) noops: usize,
937 pub(crate) suspends: usize,
938 pub(crate) blocks: usize,
939 pub(crate) block_min: usize,
940 pub(crate) block_max: usize,
941 pub(crate) block_avg: f64,
942 pub(crate) block_median: usize,
943}
944
945#[derive(Clone, Default)]
947pub(crate) struct InstData {
948 pub(crate) opcode: u8,
950 pub(crate) flags: InstFlags,
952 base_gas: u16,
956 stack_io: (u8, u8),
958 pub(crate) data: u32,
967 pub(crate) gas_section: GasSection,
969 pub(crate) stack_section: StackSection,
971}
972
973impl PartialEq<u8> for InstData {
974 #[inline]
975 fn eq(&self, other: &u8) -> bool {
976 self.opcode == *other
977 }
978}
979
980impl PartialEq<InstData> for u8 {
981 #[inline]
982 fn eq(&self, other: &InstData) -> bool {
983 *self == other.opcode
984 }
985}
986
987impl InstData {
988 #[inline]
991 fn new(opcode: u8) -> Self {
992 Self { opcode, stack_io: stack_io(opcode), ..Default::default() }
993 }
994
995 #[inline]
997 pub(crate) const fn imm_len(&self) -> u8 {
998 min_imm_len(self.opcode)
999 }
1000
1001 #[inline]
1003 pub(crate) fn stack_io(&self) -> (u8, u8) {
1004 self.stack_io
1005 }
1006
1007 #[inline]
1009 pub(crate) const fn to_op(&self) -> Opcode<'static> {
1010 Opcode { opcode: self.opcode, immediate: None }
1011 }
1012
1013 #[inline]
1015 pub(crate) fn is_jump(&self) -> bool {
1016 matches!(self.opcode, op::JUMP | op::JUMPI)
1017 }
1018
1019 #[inline]
1022 pub(crate) fn is_static_jump(&self) -> bool {
1023 self.is_jump() && self.flags.contains(InstFlags::STATIC_JUMP)
1024 }
1025
1026 #[inline]
1028 pub(crate) fn has_const_jumpi_condition(&self) -> bool {
1029 self.opcode == op::JUMPI && self.flags.contains(InstFlags::CONST_JUMP_CONDITION)
1030 }
1031
1032 #[inline]
1034 pub(crate) const fn is_jumpdest(&self) -> bool {
1035 self.opcode == op::JUMPDEST
1036 }
1037
1038 #[inline]
1040 pub(crate) fn is_reachable_jumpdest(&self, has_dynamic_jumps: bool) -> bool {
1041 self.is_jumpdest() && (has_dynamic_jumps || self.is_jumpdest_reachable())
1042 }
1043
1044 #[inline]
1046 pub(crate) fn imm(&self) -> U256Imm {
1047 debug_assert!(matches!(
1048 self.opcode,
1049 op::PUSH0..=op::PUSH32 | op::DUPN | op::SWAPN | op::EXCHANGE
1050 ));
1051 U256Imm::from_raw(self.data)
1052 }
1053
1054 #[inline]
1056 pub(crate) fn imm_byte(&self) -> u8 {
1057 debug_assert!(matches!(self.opcode, op::DUPN | op::SWAPN | op::EXCHANGE));
1058 self.imm().get_inline().unwrap().try_into().unwrap()
1060 }
1061
1062 #[inline]
1064 pub(crate) fn pc_imm(&self) -> u32 {
1065 debug_assert_eq!(self.opcode, op::PC);
1066 self.data
1067 }
1068
1069 pub(crate) const JUMPDEST_REACHABLE: u32 = 1 << 31;
1071
1072 #[inline]
1074 pub(crate) fn jumpdest_pc(&self) -> u32 {
1075 debug_assert_eq!(self.opcode, op::JUMPDEST);
1076 self.data & !Self::JUMPDEST_REACHABLE
1077 }
1078
1079 #[inline]
1081 pub(crate) fn is_jumpdest_reachable(&self) -> bool {
1082 debug_assert_eq!(self.opcode, op::JUMPDEST);
1083 self.data & Self::JUMPDEST_REACHABLE != 0
1084 }
1085
1086 #[inline]
1088 pub(crate) fn set_jumpdest_reachable(&mut self) {
1089 debug_assert_eq!(self.opcode, op::JUMPDEST);
1090 self.data |= Self::JUMPDEST_REACHABLE;
1091 }
1092
1093 #[inline]
1094 pub(crate) fn clear_jumpdest_reachable(&mut self) {
1095 debug_assert_eq!(self.opcode, op::JUMPDEST);
1096 self.data &= !Self::JUMPDEST_REACHABLE;
1097 }
1098
1099 #[inline]
1101 pub(crate) fn static_jump_target(&self) -> Inst {
1102 debug_assert!(self.is_static_jump());
1103 Inst::from_usize(self.data as usize)
1104 }
1105
1106 #[inline]
1108 pub(crate) fn set_static_jump_target(&mut self, target: Inst) {
1109 debug_assert!(self.is_jump());
1110 self.data = target.index() as u32;
1111 }
1112
1113 #[inline]
1115 pub(crate) fn is_stack_section_head(&self) -> bool {
1116 self.flags.contains(InstFlags::STACK_SECTION_HEAD)
1117 }
1118
1119 pub(crate) fn is_dead_code(&self) -> bool {
1121 self.flags.contains(InstFlags::DEAD_CODE)
1122 }
1123
1124 #[inline]
1127 pub(crate) fn requires_gasleft(&self, spec_id: SpecId) -> bool {
1128 self.opcode == op::GAS
1130 || (self.opcode == op::SSTORE && spec_id.is_enabled_in(SpecId::ISTANBUL))
1131 }
1132
1133 #[inline]
1135 pub(crate) fn can_fall_through(&self) -> bool {
1136 !self.is_diverging() && self.opcode != op::JUMP && !self.has_const_jumpi_condition()
1137 }
1138
1139 #[inline]
1141 pub(crate) fn is_branching(&self) -> bool {
1142 self.is_jump() || self.is_diverging()
1143 }
1144
1145 #[inline]
1147 pub(crate) fn is_diverging(&self) -> bool {
1148 #[cfg(test)]
1149 if self.opcode == TEST_SUSPEND {
1150 return false;
1151 }
1152
1153 (self.opcode == op::JUMP && self.flags.contains(InstFlags::INVALID_JUMP))
1154 || self.flags.contains(InstFlags::DISABLED)
1155 || self.flags.contains(InstFlags::UNKNOWN)
1156 || matches!(
1157 self.opcode,
1158 op::STOP | op::RETURN | op::REVERT | op::INVALID | op::SELFDESTRUCT
1159 )
1160 }
1161
1162 #[inline]
1164 pub(crate) const fn may_suspend(&self) -> bool {
1165 #[cfg(test)]
1166 if self.opcode == TEST_SUSPEND {
1167 return true;
1168 }
1169
1170 matches!(
1171 self.opcode,
1172 op::CALL | op::CALLCODE | op::DELEGATECALL | op::STATICCALL | op::CREATE | op::CREATE2
1173 )
1174 }
1175}
1176
1177bitflags::bitflags! {
1178 #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
1180 pub(crate) struct InstFlags: u16 {
1181 const STATIC_JUMP = 1 << 0;
1183 const INVALID_JUMP = 1 << 1;
1186 const MULTI_JUMP = 1 << 2;
1189 const CONST_JUMP_CONDITION = 1 << 3;
1191
1192 const DISABLED = 1 << 4;
1195 const UNKNOWN = 1 << 5;
1198
1199 const NOOP = 1 << 6;
1201 const STACK_SECTION_HEAD = 1 << 7;
1203 const DEAD_CODE = 1 << 8;
1205 }
1206}
1207
1208fn bitvec_as_bytes<T: bitvec::store::BitStore, O: bitvec::order::BitOrder>(
1209 bitvec: &BitVec<T, O>,
1210) -> &[u8] {
1211 slice_as_bytes(bitvec.as_raw_slice())
1212}
1213
1214fn slice_as_bytes<T>(a: &[T]) -> &[u8] {
1215 unsafe { std::slice::from_raw_parts(a.as_ptr().cast(), std::mem::size_of_val(a)) }
1216}
1217
1218#[cfg(test)]
1219mod tests {
1220 use super::*;
1221 use revm_bytecode::opcode::OPCODE_INFO;
1222
1223 #[test]
1224 fn test_suspend_is_free() {
1225 assert_eq!(OPCODE_INFO[TEST_SUSPEND as usize], None);
1226 }
1227
1228 #[test]
1229 fn truncated_push_imm_right_pads_with_zeros() {
1230 let code = [op::PUSH2, 0x42];
1233 let bc = Bytecode::test(&code);
1234 let inst = Inst::from_usize(0);
1235 let data = bc.inst(inst);
1236 assert_eq!(bc.get_imm(inst), Some([0x42].as_slice()));
1237 assert_eq!(bc.get_push_value(data), U256::from(0x4200));
1238
1239 let code = [op::PUSH3, 0xAB, 0xCD];
1241 let bc = Bytecode::test(&code);
1242 let inst = Inst::from_usize(0);
1243 let data = bc.inst(inst);
1244 assert_eq!(bc.get_imm(inst), Some([0xAB, 0xCD].as_slice()));
1245 assert_eq!(bc.get_push_value(data), U256::from(0xABCD00));
1246
1247 let code = [op::PUSH1];
1249 let bc = Bytecode::test(&code);
1250 let inst = Inst::from_usize(0);
1251 let data = bc.inst(inst);
1252 assert_eq!(bc.get_imm(inst), None);
1253 assert_eq!(bc.get_push_value(data), U256::ZERO);
1254
1255 let code = [op::PUSH2, 0x42, 0xFF];
1257 let bc = Bytecode::test(&code);
1258 let data = bc.inst(Inst::from_usize(0));
1259 assert_eq!(bc.get_push_value(data), U256::from(0x42FF));
1260 }
1261
1262 #[test]
1263 fn false_jumpi_fallthrough_terminator_marks_following_dead_code() {
1264 let mut bc = Bytecode::test(&[
1265 op::PUSH0,
1266 op::CALLDATASIZE,
1267 op::JUMPI,
1268 op::STOP,
1269 op::CALLDATALOAD,
1270 op::JUMP,
1271 ]);
1272 let jumpi = Inst::from_usize(2);
1273 let fallthrough = jumpi + 1;
1274 let dynamic_jump = Inst::from_usize(5);
1275
1276 bc.inst_mut(jumpi).flags |= InstFlags::STATIC_JUMP | InstFlags::CONST_JUMP_CONDITION;
1277 bc.inst_mut(jumpi).set_static_jump_target(fallthrough);
1278 bc.has_dynamic_jumps = true;
1279 bc.mark_dead_code();
1280
1281 assert!(!bc.inst(fallthrough).is_dead_code());
1282 assert!(bc.inst(Inst::from_usize(4)).is_dead_code());
1283 assert!(bc.inst(dynamic_jump).is_dead_code());
1284 assert!(!bc.has_dynamic_jumps);
1285 }
1286
1287 #[test]
1288 fn dead_fallthrough_jump_does_not_keep_jumpdest_reachable() {
1289 let mut bc = Bytecode::test(&[
1290 op::PUSH1,
1291 5,
1292 op::PUSH1,
1293 1,
1294 op::JUMPI,
1295 op::PUSH1,
1296 8,
1297 op::JUMP,
1298 op::JUMPDEST,
1299 op::STOP,
1300 op::JUMPDEST,
1301 op::STOP,
1302 ]);
1303 let jumpi = Inst::from_usize(2);
1304 let dead_jump = Inst::from_usize(4);
1305 let live_target = Inst::from_usize(5);
1306 let stale_target = Inst::from_usize(7);
1307
1308 bc.inst_mut(jumpi).flags |= InstFlags::STATIC_JUMP | InstFlags::CONST_JUMP_CONDITION;
1309 bc.inst_mut(jumpi).set_static_jump_target(live_target);
1310 bc.inst_mut(dead_jump).flags |= InstFlags::STATIC_JUMP;
1311 bc.inst_mut(dead_jump).set_static_jump_target(stale_target);
1312 bc.inst_mut(live_target).set_jumpdest_reachable();
1313 bc.inst_mut(stale_target).set_jumpdest_reachable();
1314
1315 bc.mark_dead_code();
1316
1317 assert!(bc.inst(dead_jump).is_dead_code());
1318 assert!(bc.inst(live_target).is_jumpdest_reachable());
1319 assert!(!bc.inst(stale_target).is_jumpdest_reachable());
1320 assert!(bc.inst(stale_target).is_dead_code());
1321 }
1322}