Skip to main content

revmc_codegen/bytecode/
mod.rs

1//! Internal EVM bytecode and opcode representation.
2
3use 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/// Noop opcode used to test suspend-resume.
35#[cfg(any(feature = "__fuzzing", test))]
36pub(crate) const TEST_SUSPEND: u8 = 0x25;
37
38/// Implements `Display` for a nonmax index type using a format string.
39macro_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    /// An EVM instruction index into [`Bytecode`] instructions.
52    ///
53    /// Also known as `ic`, or instruction counter; not to be confused with SSA `inst`s.
54    pub(crate) struct Inst;
55}
56impl_index_display!(Inst, "ic{}");
57
58oxc_index::define_nonmax_u32_index_type! {
59    /// Index into the deduplicated U256 constant pool.
60    pub(crate) struct U256Idx;
61}
62impl_index_display!(U256Idx, "{}");
63
64/// Type alias for the bytecode-level U256 constant interner.
65pub(crate) type U256Interner = Interner<U256Idx, U256, alloy_primitives::map::FbBuildHasher<32>>;
66
67/// Compact handle to a `U256` constant.
68///
69/// Either a `u32` inline value or a reference into a [`U256Interner`].
70#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
71pub(crate) struct U256Imm(u32);
72
73impl U256Imm {
74    /// High bit set means the handle is an inline `u8` value.
75    const INLINE_TAG: u32 = 1 << 31;
76
77    /// Decodes a handle previously produced by [`Self::to_raw`].
78    #[inline]
79    pub(crate) fn from_raw(raw: u32) -> Self {
80        Self(raw)
81    }
82
83    /// Encodes this handle as a `u32` for storage in [`InstData::data`].
84    #[inline]
85    pub(crate) fn to_raw(self) -> u32 {
86        self.0
87    }
88
89    /// Constructs a handle for `value`, inlining `u8`-sized values and otherwise interning.
90    #[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    /// Returns the underlying `U256` value.
107    #[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    /// Returns the inline `u32` value, if any.
117    #[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    /// Controls which analysis passes run during [`Bytecode::analyze`].
134    #[derive(Clone, Copy, Debug)]
135    pub(crate) struct AnalysisConfig: u8 {
136        /// Run block deduplication.
137        const DEDUP = 1 << 0;
138        /// The stack is observable outside the function (`inspect_stack` mode).
139        /// When set, DSE must not assume diverging terminators kill the stack.
140        const INSPECT_STACK = 1 << 1;
141        /// Run dead store elimination.
142        const DSE = 1 << 2;
143
144        /// All passes enabled.
145        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
156/// Default compiler gas limit for compile-time evaluation (100k gas).
157pub(crate) const DEFAULT_COMPILER_GAS_LIMIT: u64 = 100_000;
158
159/// EVM bytecode.
160#[doc(hidden)] // Not public API.
161pub struct Bytecode<'a> {
162    /// The original bytecode.
163    pub(crate) code: Cow<'a, [u8]>,
164    /// The instructions.
165    insts: IndexVec<Inst, InstData>,
166    /// `JUMPDEST` opcode map. `jumpdests[pc]` is `true` if `code[pc] == op::JUMPDEST`.
167    jumpdests: BitVec,
168    /// The [`SpecId`].
169    pub(crate) spec_id: SpecId,
170    /// Gas parameters for dynamic gas folding. Defaults to `GasParams::new_spec(spec_id)`.
171    pub(crate) gas_params: GasParams,
172    /// Whether the bytecode contains dynamic jumps.
173    has_dynamic_jumps: bool,
174    /// Whether the bytecode may suspend execution.
175    may_suspend: bool,
176    /// Mapping from program counter to instruction.
177    pc_to_inst: FxHashMap<u32, Inst>,
178    /// Mapping from instruction index to its program counter (`code[pc]` is the opcode byte).
179    ///
180    /// `JUMPDEST` and `PC` mirror this in `data` for hot codegen access; everything else
181    /// reads it through [`Bytecode::pc`].
182    inst_to_pc: IndexVec<Inst, u32>,
183
184    /// Deduplicated constant pool for U256 values.
185    u256_interner: RefCell<U256Interner>,
186
187    /// Per-instruction operand snapshots computed by block analysis.
188    snapshots: Snapshots,
189    /// Per-instruction memory sections computed by section analysis.
190    memory_sections: IndexVec<Inst, MemorySection>,
191    /// Multi-target jump table: maps a JUMP/JUMPI instruction to its set of known targets.
192    /// Only populated for jumps resolved to multiple targets by block analysis.
193    multi_jump_targets: FxHashMap<Inst, SmallVec<[Inst; 4]>>,
194
195    /// Instruction index to 1-based line number in the formatted dump, built during formatting.
196    inst_lines: RefCell<IndexVec<Inst, u32>>,
197
198    /// Dead-block redirects: maps the first instruction of a dead/merged block to the target
199    /// instruction. Used mainly for fallthrough jumps.
200    pub(crate) redirects: FxHashMap<Inst, Inst>,
201    /// Basic-block CFG, rebuilt by [`Bytecode::rebuild_cfg`].
202    cfg: Cfg,
203    /// Controls which analysis passes are enabled.
204    pub(crate) config: AnalysisConfig,
205    /// Gas budget for compile-time evaluation of user-supplied bytecode.
206    ///
207    /// The compiler evaluates EVM operations at compile time during analysis passes. Without a
208    /// budget, adversarial bytecode (e.g. thousands of `EXP(U256::MAX, U256::MAX)`) can make
209    /// compilation arbitrarily slow. This limit uses the EVM gas schedule to bound work.
210    ///
211    /// When exhausted, further compile-time evaluation is skipped (values remain dynamic).
212    /// Defaults to 100k gas.
213    pub(crate) compiler_gas_limit: u64,
214    /// Cumulative compiler gas consumed so far.
215    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                    // `OpcodesIter` returns `None` for truncated EOF immediates; recover
269                    // the available bytes directly from `code` and right-pad with zeros
270                    // per EVM spec.
271                    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            // EIP-8024: JUMPDEST analysis is unchanged by DUPN/SWAPN/EXCHANGE. Their
306            // immediate byte is NOT masked, so 0x5b in that position is a valid jump
307            // target. When the immediate is 0x5b, rewind the iterator so it is re-yielded
308            // and processed as a normal JUMPDEST by the loop.
309            if matches!(opcode, op::DUPN | op::SWAPN | op::EXCHANGE)
310                && !info.is_unknown()
311                && !info.is_disabled()
312                && immediate == Some(&[op::JUMPDEST])
313            {
314                // SAFETY: we just consumed the 1-byte immediate.
315                unsafe { iter.rewind(1) };
316            }
317        }
318
319        // Pad code to ensure there is at least one diverging instruction.
320        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    /// Takes the instruction-to-line map built during formatting.
350    ///
351    /// If the bytecode has not been formatted yet, formats it first to build the map.
352    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    /// Returns an iterator over the opcodes.
360    #[inline]
361    #[allow(dead_code)]
362    pub(crate) fn opcodes(&self) -> OpcodesIter<'_> {
363        OpcodesIter::new(&self.code, self.spec_id)
364    }
365
366    /// Returns the instruction at the given instruction counter.
367    #[inline]
368    #[track_caller]
369    pub(crate) fn inst(&self, inst: Inst) -> &InstData {
370        &self.insts[inst]
371    }
372
373    /// Returns a mutable reference the instruction at the given instruction counter.
374    #[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    /// Returns the opcode + immediate slice for the given instruction.
382    #[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    /// Returns an iterator over the instructions.
388    #[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    /// Returns an iterator over the instructions.
394    #[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    /// Returns an iterator over all the instructions, including dead code.
403    #[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    /// Returns an iterator over all the instructions, including dead code.
411    #[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    /// Runs a list of analysis passes on the instructions.
420    #[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    /// Recomputes the `has_dynamic_jumps` flag based on the current instruction set.
462    #[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    /// Mark unreachable instructions as `DEAD_CODE` to not generate any code for them.
511    ///
512    /// This pass is technically unnecessary as the backend will very likely optimize any
513    /// unreachable code that we generate, but this is trivial for us to do and significantly speeds
514    /// up code generation.
515    ///
516    /// We can simply mark all instructions that are between diverging instructions and
517    /// `JUMPDEST`s.
518    #[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    /// Calculates whether the bytecode suspend suspend execution.
568    ///
569    /// This can only happen if the bytecode contains `*CALL*` or `*CREATE*` instructions.
570    #[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    /// Constructs the sections in the bytecode.
577    #[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    /// Constructs the memory sections in the bytecode.
590    #[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    /// Returns the immediate value of the given instruction, if any.
597    ///
598    /// For truncated immediates at EOF, returns the available bytes (which may be shorter than
599    /// `imm_len`). Returns `None` only when `imm_len` is 0 or `start` is completely out of bounds.
600    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    /// Returns the program counter (offset into the original bytecode) of the given instruction.
615    #[inline]
616    pub(crate) fn pc(&self, inst: Inst) -> u32 {
617        self.inst_to_pc[inst]
618    }
619
620    /// Returns the value of a PUSH instruction, right-padding truncated EOF immediates with zeros
621    /// per EVM spec.
622    #[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    /// Returns the length of the original bytecode in bytes.
629    #[inline]
630    pub(crate) fn codesize(&self) -> usize {
631        self.code.len()
632    }
633
634    /// Returns `true` if the given program counter is a valid jump destination.
635    fn is_valid_jump(&self, pc: usize) -> bool {
636        self.jumpdests.get(pc).as_deref().copied() == Some(true)
637    }
638
639    /// Returns `true` if the bytecode has dynamic jumps.
640    pub(crate) fn has_dynamic_jumps(&self) -> bool {
641        self.has_dynamic_jumps
642    }
643
644    /// Returns `true` if the bytecode may suspend execution, to be resumed later.
645    pub(crate) fn may_suspend(&self) -> bool {
646        self.may_suspend
647    }
648
649    /// Returns `true` if the stack argument's contents are observed by the caller after the
650    /// function returns (either through `inspect_stack` mode or because execution may suspend).
651    pub(crate) fn stack_observed(&self) -> bool {
652        self.config.contains(AnalysisConfig::INSPECT_STACK) || self.may_suspend()
653    }
654
655    /// Returns `true` if the instruction is diverging.
656    pub(crate) fn is_instr_diverging(&self, inst: Inst) -> bool {
657        self.insts[inst].is_diverging()
658    }
659
660    /// Converts a program counter (`self.code[pc]`) to an instruction (`self.inst(inst)`).
661    #[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    /// Returns the multi-target jump table entry for the given instruction, if any.
670    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    /// Returns the known constant value of a stack operand at the given instruction.
675    ///
676    /// `depth` 0 is TOS (first popped by this instruction), 1 is second, etc.
677    /// Returns `None` if the value is unknown or the analysis didn't cover this instruction.
678    #[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    /// Returns the known constant output value of the given instruction.
686    ///
687    /// Returns `None` if the output is unknown, the instruction has no output, or the
688    /// analysis didn't cover this instruction.
689    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    /// Returns the first constant EVM memory access `(offset, len)` for the given instruction.
695    #[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    /// Returns the constant EVM memory accesses `(offset, len)` for the given instruction.
701    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    /// Returns the memory section for the given instruction.
738    #[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    /// Logs per-opcode constant-input statistics at trace level.
744    #[inline(never)]
745    fn log_const_input_stats(&self) {
746        use op::*;
747        use std::fmt::Write;
748
749        // per_input[depth] = [total, const_count, fits_usize_count].
750        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    /// Collects IR statistics: instruction counts and block size distribution.
831    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    /// Logs IR statistics and top 5 longest blocks at trace level.
875    #[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    /// Returns the name for a basic block.
911    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
931/// Summary IR statistics for a compiled bytecode.
932pub(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/// A single instruction in the bytecode.
946#[derive(Clone, Default)]
947pub(crate) struct InstData {
948    /// The opcode byte.
949    pub(crate) opcode: u8,
950    /// Flags.
951    pub(crate) flags: InstFlags,
952    /// The base gas cost of the opcode.
953    ///
954    /// This may not be the final/full gas cost of the opcode as it may also have a dynamic cost.
955    base_gas: u16,
956    /// Stack inputs and outputs.
957    stack_io: (u8, u8),
958    /// Instruction-specific data, populated at parse time and refined by analysis.
959    /// Interpretation depends on `opcode`; access via the typed methods on [`InstData`]:
960    /// - `PUSH0..=PUSH32`, `DUPN | SWAPN | EXCHANGE`: encoded [`U256Imm`].
961    /// - `PC`: program counter of this instruction.
962    /// - `JUMPDEST`: program counter in low 31 bits, reachable flag in high bit
963    ///   ([`InstData::JUMPDEST_REACHABLE`]).
964    /// - `JUMP | JUMPI` with [`InstFlags::STATIC_JUMP`]: target instruction index.
965    /// - otherwise: unused.
966    pub(crate) data: u32,
967    /// The gas section this instruction belongs to.
968    pub(crate) gas_section: GasSection,
969    /// The stack section this instruction belongs to.
970    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    /// Creates a new instruction data with the given opcode byte.
989    /// Note that this may not be a valid instruction.
990    #[inline]
991    fn new(opcode: u8) -> Self {
992        Self { opcode, stack_io: stack_io(opcode), ..Default::default() }
993    }
994
995    /// Returns the length of the immediate data of this instruction.
996    #[inline]
997    pub(crate) const fn imm_len(&self) -> u8 {
998        min_imm_len(self.opcode)
999    }
1000
1001    /// Returns the number of input and output stack elements of this instruction.
1002    #[inline]
1003    pub(crate) fn stack_io(&self) -> (u8, u8) {
1004        self.stack_io
1005    }
1006
1007    /// Converts this instruction to a raw opcode. Note that the immediate data is not resolved.
1008    #[inline]
1009    pub(crate) const fn to_op(&self) -> Opcode<'static> {
1010        Opcode { opcode: self.opcode, immediate: None }
1011    }
1012
1013    /// Returns `true` if this instruction is a jump instruction (`JUMP`/`JUMPI`).
1014    #[inline]
1015    pub(crate) fn is_jump(&self) -> bool {
1016        matches!(self.opcode, op::JUMP | op::JUMPI)
1017    }
1018
1019    /// Returns `true` if this instruction is a jump instruction (`JUMP`/`JUMPI`), and the
1020    /// target known statically.
1021    #[inline]
1022    pub(crate) fn is_static_jump(&self) -> bool {
1023        self.is_jump() && self.flags.contains(InstFlags::STATIC_JUMP)
1024    }
1025
1026    /// Returns `true` if this instruction is a `JUMPI` whose condition is known statically.
1027    #[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    /// Returns `true` if this instruction is a `JUMPDEST`.
1033    #[inline]
1034    pub(crate) const fn is_jumpdest(&self) -> bool {
1035        self.opcode == op::JUMPDEST
1036    }
1037
1038    /// Returns `true` if this instruction is a reachable `JUMPDEST`.
1039    #[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    /// Returns the [`U256Imm`] of a `PUSH*`/`DUPN`/`SWAPN`/`EXCHANGE` instruction.
1045    #[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    /// Returns the inline `u8` immediate of a `DUPN`/`SWAPN`/`EXCHANGE` instruction.
1055    #[inline]
1056    pub(crate) fn imm_byte(&self) -> u8 {
1057        debug_assert!(matches!(self.opcode, op::DUPN | op::SWAPN | op::EXCHANGE));
1058        // Parse-time encoding for these opcodes always produces an inline `u8`.
1059        self.imm().get_inline().unwrap().try_into().unwrap()
1060    }
1061
1062    /// Returns the program counter stored inline for a `PC` instruction.
1063    #[inline]
1064    pub(crate) fn pc_imm(&self) -> u32 {
1065        debug_assert_eq!(self.opcode, op::PC);
1066        self.data
1067    }
1068
1069    /// High bit of `data` set when a `JUMPDEST` is a reachable static jump target.
1070    pub(crate) const JUMPDEST_REACHABLE: u32 = 1 << 31;
1071
1072    /// Returns the program counter of a `JUMPDEST`.
1073    #[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    /// Returns `true` if this `JUMPDEST` is the target of a resolved static jump.
1080    #[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    /// Marks this `JUMPDEST` as a reachable static jump target.
1087    #[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    /// Returns the static target of a `JUMP`/`JUMPI` with [`InstFlags::STATIC_JUMP`].
1100    #[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    /// Sets the static jump target. Caller must also set [`InstFlags::STATIC_JUMP`].
1107    #[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    /// Returns `true` if this instruction starts a new stack section.
1114    #[inline]
1115    pub(crate) fn is_stack_section_head(&self) -> bool {
1116        self.flags.contains(InstFlags::STACK_SECTION_HEAD)
1117    }
1118
1119    /// Returns `true` if this instruction is dead code.
1120    pub(crate) fn is_dead_code(&self) -> bool {
1121        self.flags.contains(InstFlags::DEAD_CODE)
1122    }
1123
1124    /// Returns `true` if this instruction requires to know `gasleft()`.
1125    /// Note that this does not include CALL and CREATE.
1126    #[inline]
1127    pub(crate) fn requires_gasleft(&self, spec_id: SpecId) -> bool {
1128        // For SSTORE, see `revm_interpreter::gas::sstore_cost`.
1129        self.opcode == op::GAS
1130            || (self.opcode == op::SSTORE && spec_id.is_enabled_in(SpecId::ISTANBUL))
1131    }
1132
1133    /// Returns `true` if execution can fall through to the next sequential instruction.
1134    #[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    /// Returns `true` if we know that this instruction will branch or stop execution.
1140    #[inline]
1141    pub(crate) fn is_branching(&self) -> bool {
1142        self.is_jump() || self.is_diverging()
1143    }
1144
1145    /// Returns `true` if we know that this instruction will stop execution.
1146    #[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    /// Returns `true` if this instruction may suspend execution.
1163    #[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    /// [`InstrData`] flags.
1179    #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
1180    pub(crate) struct InstFlags: u16 {
1181        /// The `JUMP`/`JUMPI` target is known at compile time.
1182        const STATIC_JUMP = 1 << 0;
1183        /// The jump target is known to be invalid.
1184        /// Always returns [`InstructionResult::InvalidJump`] at runtime.
1185        const INVALID_JUMP = 1 << 1;
1186        /// The jump has multiple known targets (see `Bytecode::multi_jump_targets`).
1187        /// The target value is still on the stack and must be popped and switched on at runtime.
1188        const MULTI_JUMP = 1 << 2;
1189        /// The `JUMPI` condition is known at compile time.
1190        const CONST_JUMP_CONDITION = 1 << 3;
1191
1192        /// The instruction is disabled in this EVM version.
1193        /// Always returns [`InstructionResult::NotActivated`] at runtime.
1194        const DISABLED = 1 << 4;
1195        /// The instruction is unknown.
1196        /// Always returns [`InstructionResult::NotFound`] at runtime.
1197        const UNKNOWN = 1 << 5;
1198
1199        /// Instruction is a no-op: skip generating logic, but keep the gas calculation.
1200        const NOOP = 1 << 6;
1201        /// This instruction starts a new stack section.
1202        const STACK_SECTION_HEAD = 1 << 7;
1203        /// Don't generate any code.
1204        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        // PUSH2 followed by a single byte 0x42 — truncated at EOF.
1231        // EVM spec: missing bytes are zero, so the value is 0x4200.
1232        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        // PUSH3 followed by two bytes — truncated at EOF.
1240        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        // PUSH1 with no immediate bytes at all.
1248        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        // Non-truncated PUSH2 — full immediate.
1256        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}