revm_interpreter/opcode/
tables.rs

1#![allow(clippy::wrong_self_convention)]
2
3use super::instruction;
4use crate::{instructions::control, primitives::Spec, Host, Interpreter};
5use std::boxed::Box;
6
7/// EVM opcode function signature.
8pub type Instruction<H> = fn(&mut Interpreter, &mut H);
9
10/// Instruction table is list of instruction function pointers mapped to 256 EVM opcodes.
11pub type InstructionTable<H> = [Instruction<H>; 256];
12
13/// EVM dynamic opcode function signature.
14pub type DynInstruction<'a, H> = dyn Fn(&mut Interpreter, &mut H) + 'a;
15
16/// EVM boxed dynamic opcode function signature.
17pub type BoxedInstruction<'a, H> = Box<DynInstruction<'a, H>>;
18
19/// A table of boxed instructions.
20pub type BoxedInstructionTable<'a, H> = [BoxedInstruction<'a, H>; 256];
21
22/// Either a plain, static instruction table, or a boxed, dynamic instruction table.
23///
24/// Note that `Plain` variant is about 10-20% faster in Interpreter execution.
25pub enum InstructionTables<'a, H: ?Sized> {
26    Plain(InstructionTable<H>),
27    Boxed(BoxedInstructionTable<'a, H>),
28}
29
30impl<H: Host + ?Sized> InstructionTables<'_, H> {
31    /// Creates a plain instruction table for the given spec. See [`make_instruction_table`].
32    #[inline]
33    pub const fn new_plain<SPEC: Spec>() -> Self {
34        Self::Plain(make_instruction_table::<H, SPEC>())
35    }
36}
37
38impl<'a, H: Host + ?Sized + 'a> InstructionTables<'a, H> {
39    /// Inserts the instruction into the table with the specified index.
40    #[inline]
41    pub fn insert(&mut self, opcode: u8, instruction: Instruction<H>) {
42        match self {
43            Self::Plain(table) => table[opcode as usize] = instruction,
44            Self::Boxed(table) => table[opcode as usize] = Box::new(instruction),
45        }
46    }
47
48    /// Converts the current instruction table to a boxed variant if it is not already, and returns
49    /// a mutable reference to the boxed table.
50    #[inline]
51    pub fn to_boxed(&mut self) -> &mut BoxedInstructionTable<'a, H> {
52        self.to_boxed_with(|i| Box::new(i))
53    }
54
55    /// Converts the current instruction table to a boxed variant if it is not already with `f`,
56    /// and returns a mutable reference to the boxed table.
57    #[inline]
58    pub fn to_boxed_with<F>(&mut self, f: F) -> &mut BoxedInstructionTable<'a, H>
59    where
60        F: FnMut(Instruction<H>) -> BoxedInstruction<'a, H>,
61    {
62        match self {
63            Self::Plain(_) => self.to_boxed_with_slow(f),
64            Self::Boxed(boxed) => boxed,
65        }
66    }
67
68    #[cold]
69    fn to_boxed_with_slow<F>(&mut self, f: F) -> &mut BoxedInstructionTable<'a, H>
70    where
71        F: FnMut(Instruction<H>) -> BoxedInstruction<'a, H>,
72    {
73        let Self::Plain(table) = self else {
74            unreachable!()
75        };
76        *self = Self::Boxed(make_boxed_instruction_table(table, f));
77        let Self::Boxed(boxed) = self else {
78            unreachable!()
79        };
80        boxed
81    }
82
83    /// Returns a mutable reference to the boxed instruction at the specified index.
84    #[inline]
85    pub fn get_boxed(&mut self, opcode: u8) -> &mut BoxedInstruction<'a, H> {
86        &mut self.to_boxed()[opcode as usize]
87    }
88
89    /// Inserts a boxed instruction into the table at the specified index.
90    #[inline]
91    pub fn insert_boxed(&mut self, opcode: u8, instruction: BoxedInstruction<'a, H>) {
92        *self.get_boxed(opcode) = instruction;
93    }
94
95    /// Replaces a boxed instruction into the table at the specified index, returning the previous
96    /// instruction.
97    #[inline]
98    pub fn replace_boxed(
99        &mut self,
100        opcode: u8,
101        instruction: BoxedInstruction<'a, H>,
102    ) -> BoxedInstruction<'a, H> {
103        core::mem::replace(self.get_boxed(opcode), instruction)
104    }
105
106    /// Updates a single instruction in the table at the specified index with `f`.
107    #[inline]
108    pub fn update_boxed<F>(&mut self, opcode: u8, f: F)
109    where
110        F: Fn(&DynInstruction<'a, H>, &mut Interpreter, &mut H) + 'a,
111    {
112        update_boxed_instruction(self.get_boxed(opcode), f)
113    }
114
115    /// Updates every instruction in the table by calling `f`.
116    #[inline]
117    pub fn update_all<F>(&mut self, f: F)
118    where
119        F: Fn(&DynInstruction<'a, H>, &mut Interpreter, &mut H) + Copy + 'a,
120    {
121        // Don't go through `to_boxed` to avoid allocating the plain table twice.
122        match self {
123            Self::Plain(_) => {
124                self.to_boxed_with(|prev| Box::new(move |i, h| f(&prev, i, h)));
125            }
126            Self::Boxed(boxed) => boxed
127                .iter_mut()
128                .for_each(|instruction| update_boxed_instruction(instruction, f)),
129        }
130    }
131}
132
133/// Make instruction table.
134#[inline]
135pub const fn make_instruction_table<H: Host + ?Sized, SPEC: Spec>() -> InstructionTable<H> {
136    const {
137        let mut tables: InstructionTable<H> = [control::unknown; 256];
138        let mut i = 0;
139        while i < 256 {
140            tables[i] = instruction::<H, SPEC>(i as u8);
141            i += 1;
142        }
143        tables
144    }
145}
146
147/// Make boxed instruction table that calls `f` closure for every instruction.
148#[inline]
149pub fn make_boxed_instruction_table<'a, H, FN>(
150    table: &InstructionTable<H>,
151    mut f: FN,
152) -> BoxedInstructionTable<'a, H>
153where
154    H: Host + ?Sized,
155    FN: FnMut(Instruction<H>) -> BoxedInstruction<'a, H>,
156{
157    core::array::from_fn(|i| f(table[i]))
158}
159
160/// Updates a boxed instruction with a new one.
161#[inline]
162pub fn update_boxed_instruction<'a, H, F>(instruction: &mut BoxedInstruction<'a, H>, f: F)
163where
164    H: Host + ?Sized + 'a,
165    F: Fn(&DynInstruction<'a, H>, &mut Interpreter, &mut H) + 'a,
166{
167    // NOTE: This first allocation gets elided by the compiler.
168    let prev = core::mem::replace(instruction, Box::new(|_, _| {}));
169    *instruction = Box::new(move |i, h| f(&prev, i, h));
170}