Skip to content

Commit 32e2830

Browse files
committed
feat: add helpers for working with instruction tables
1 parent 22e15d3 commit 32e2830

File tree

5 files changed

+306
-295
lines changed

5 files changed

+306
-295
lines changed

crates/interpreter/src/opcode.rs

Lines changed: 8 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -2,128 +2,15 @@
22
33
pub mod eof_printer;
44

5-
use crate::{instructions::*, primitives::Spec, Host, Interpreter};
5+
mod tables;
6+
pub use tables::{
7+
make_boxed_instruction_table, make_instruction_table, update_boxed_instruction,
8+
BoxedInstruction, BoxedInstructionTable, DynInstruction, Instruction, InstructionTable,
9+
InstructionTables,
10+
};
11+
12+
use crate::{instructions::*, primitives::Spec, Host};
613
use core::{fmt, ptr::NonNull};
7-
use std::boxed::Box;
8-
9-
/// EVM opcode function signature.
10-
pub type Instruction<H> = fn(&mut Interpreter, &mut H);
11-
12-
/// Instruction table is list of instruction function pointers mapped to
13-
/// 256 EVM opcodes.
14-
pub type InstructionTable<H> = [Instruction<H>; 256];
15-
16-
/// EVM opcode function signature.
17-
pub type BoxedInstruction<'a, H> = Box<dyn Fn(&mut Interpreter, &mut H) + 'a>;
18-
19-
/// A table of instructions.
20-
pub type BoxedInstructionTable<'a, H> = [BoxedInstruction<'a, H>; 256];
21-
22-
/// Instruction set that contains plain instruction table that contains simple `fn` function pointer.
23-
/// and Boxed `Fn` variant that contains `Box<dyn Fn()>` function pointer that can be used with closured.
24-
///
25-
/// Note that `Plain` variant gives us 10-20% faster Interpreter execution.
26-
///
27-
/// Boxed variant can be used to wrap plain function pointer with closure.
28-
pub enum InstructionTables<'a, H> {
29-
Plain(InstructionTable<H>),
30-
Boxed(BoxedInstructionTable<'a, H>),
31-
}
32-
33-
impl<H: Host> InstructionTables<'_, H> {
34-
/// Creates a plain instruction table for the given spec.
35-
#[inline]
36-
pub const fn new_plain<SPEC: Spec>() -> Self {
37-
Self::Plain(make_instruction_table::<H, SPEC>())
38-
}
39-
}
40-
41-
impl<'a, H: Host + 'a> InstructionTables<'a, H> {
42-
/// Inserts a boxed instruction into the table with the specified index.
43-
///
44-
/// This will convert the table into the [BoxedInstructionTable] variant if it is currently a
45-
/// plain instruction table, before inserting the instruction.
46-
#[inline]
47-
pub fn insert_boxed(&mut self, opcode: u8, instruction: BoxedInstruction<'a, H>) {
48-
// first convert the table to boxed variant
49-
self.convert_boxed();
50-
51-
// now we can insert the instruction
52-
match self {
53-
Self::Plain(_) => {
54-
unreachable!("we already converted the table to boxed variant");
55-
}
56-
Self::Boxed(table) => {
57-
table[opcode as usize] = Box::new(instruction);
58-
}
59-
}
60-
}
61-
62-
/// Inserts the instruction into the table with the specified index.
63-
#[inline]
64-
pub fn insert(&mut self, opcode: u8, instruction: Instruction<H>) {
65-
match self {
66-
Self::Plain(table) => {
67-
table[opcode as usize] = instruction;
68-
}
69-
Self::Boxed(table) => {
70-
table[opcode as usize] = Box::new(instruction);
71-
}
72-
}
73-
}
74-
75-
/// Converts the current instruction table to a boxed variant. If the table is already boxed,
76-
/// this is a no-op.
77-
#[inline]
78-
pub fn convert_boxed(&mut self) {
79-
match self {
80-
Self::Plain(table) => {
81-
*self = Self::Boxed(core::array::from_fn(|i| {
82-
let instruction: BoxedInstruction<'a, H> = Box::new(table[i]);
83-
instruction
84-
}));
85-
}
86-
Self::Boxed(_) => {}
87-
};
88-
}
89-
}
90-
91-
/// Make instruction table.
92-
#[inline]
93-
pub const fn make_instruction_table<H: Host + ?Sized, SPEC: Spec>() -> InstructionTable<H> {
94-
// Force const-eval of the table creation, making this function trivial.
95-
// TODO: Replace this with a `const {}` block once it is stable.
96-
struct ConstTable<H: Host + ?Sized, SPEC: Spec> {
97-
_host: core::marker::PhantomData<H>,
98-
_spec: core::marker::PhantomData<SPEC>,
99-
}
100-
impl<H: Host + ?Sized, SPEC: Spec> ConstTable<H, SPEC> {
101-
const NEW: InstructionTable<H> = {
102-
let mut tables: InstructionTable<H> = [control::unknown; 256];
103-
let mut i = 0;
104-
while i < 256 {
105-
tables[i] = instruction::<H, SPEC>(i as u8);
106-
i += 1;
107-
}
108-
tables
109-
};
110-
}
111-
ConstTable::<H, SPEC>::NEW
112-
}
113-
114-
/// Make boxed instruction table that calls `outer` closure for every instruction.
115-
#[inline]
116-
pub fn make_boxed_instruction_table<'a, H, SPEC, FN>(
117-
table: InstructionTable<H>,
118-
mut outer: FN,
119-
) -> BoxedInstructionTable<'a, H>
120-
where
121-
H: Host,
122-
SPEC: Spec + 'a,
123-
FN: FnMut(Instruction<H>) -> BoxedInstruction<'a, H>,
124-
{
125-
core::array::from_fn(|i| outer(table[i]))
126-
}
12714

12815
/// An error indicating that an opcode is invalid.
12916
#[derive(Debug, PartialEq, Eq)]
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
#![allow(clippy::wrong_self_convention)]
2+
3+
use super::instruction;
4+
use crate::{instructions::control, primitives::Spec, Host, Interpreter};
5+
use std::boxed::Box;
6+
7+
/// EVM opcode function signature.
8+
pub type Instruction<H> = fn(&mut Interpreter, &mut H);
9+
10+
/// Instruction table is list of instruction function pointers mapped to 256 EVM opcodes.
11+
pub type InstructionTable<H> = [Instruction<H>; 256];
12+
13+
/// EVM dynamic opcode function signature.
14+
pub type DynInstruction<'a, H> = dyn Fn(&mut Interpreter, &mut H) + 'a;
15+
16+
/// EVM boxed dynamic opcode function signature.
17+
pub type BoxedInstruction<'a, H> = Box<DynInstruction<'a, H>>;
18+
19+
/// A table of boxed instructions.
20+
pub 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.
25+
pub enum InstructionTables<'a, H: ?Sized> {
26+
Plain(InstructionTable<H>),
27+
Boxed(BoxedInstructionTable<'a, H>),
28+
}
29+
30+
impl<'a, H: Host + ?Sized> InstructionTables<'a, 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+
38+
impl<'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+
/// Replaces 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+
/// Replaces 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]
135+
pub const fn make_instruction_table<H: Host + ?Sized, SPEC: Spec>() -> InstructionTable<H> {
136+
// Force const-eval of the table creation, making this function trivial.
137+
// TODO: Replace this with a `const {}` block once it is stable.
138+
struct ConstTable<H: Host + ?Sized, SPEC: Spec> {
139+
_host: core::marker::PhantomData<H>,
140+
_spec: core::marker::PhantomData<SPEC>,
141+
}
142+
impl<H: Host + ?Sized, SPEC: Spec> ConstTable<H, SPEC> {
143+
const NEW: InstructionTable<H> = {
144+
let mut tables: InstructionTable<H> = [control::unknown; 256];
145+
let mut i = 0;
146+
while i < 256 {
147+
tables[i] = instruction::<H, SPEC>(i as u8);
148+
i += 1;
149+
}
150+
tables
151+
};
152+
}
153+
ConstTable::<H, SPEC>::NEW
154+
}
155+
156+
/// Make boxed instruction table that calls `f` closure for every instruction.
157+
#[inline]
158+
pub fn make_boxed_instruction_table<'a, H, FN>(
159+
table: &InstructionTable<H>,
160+
mut f: FN,
161+
) -> BoxedInstructionTable<'a, H>
162+
where
163+
H: Host + ?Sized,
164+
FN: FnMut(Instruction<H>) -> BoxedInstruction<'a, H>,
165+
{
166+
core::array::from_fn(|i| f(table[i]))
167+
}
168+
169+
/// Updates a boxed instruction with a new one.
170+
#[inline]
171+
pub fn update_boxed_instruction<'a, H, F>(instruction: &mut BoxedInstruction<'a, H>, f: F)
172+
where
173+
H: Host + ?Sized + 'a,
174+
F: Fn(&DynInstruction<'a, H>, &mut Interpreter, &mut H) + 'a,
175+
{
176+
// NOTE: This first allocation gets elided by the compiler.
177+
let prev = core::mem::replace(instruction, Box::new(|_, _| {}));
178+
*instruction = Box::new(move |i, h| f(&prev, i, h));
179+
}

crates/revm/src/inspector.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ mod noop;
1515

1616
// Exports.
1717

18-
pub use handler_register::{inspector_handle_register, inspector_instruction, GetInspector};
18+
pub use handler_register::{inspector_handle_register, GetInspector};
1919
use revm_interpreter::{CallOutcome, CreateOutcome};
2020

2121
/// [Inspector] implementations.

0 commit comments

Comments
 (0)