Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf(interpreter): evaluate instruction table constructor at compile time #1140

Merged
merged 1 commit into from Mar 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
69 changes: 46 additions & 23 deletions crates/interpreter/src/instructions/opcode.rs
Expand Up @@ -28,11 +28,55 @@ pub type BoxedInstructionTable<'a, H> = [BoxedInstruction<'a, H>; 256];
/// Note that `Plain` variant gives us 10-20% faster Interpreter execution.
///
/// Boxed variant can be used to wrap plain function pointer with closure.
pub enum InstructionTables<'a, H: Host> {
pub enum InstructionTables<'a, H> {
Plain(InstructionTable<H>),
Boxed(BoxedInstructionTable<'a, H>),
}

impl<H: Host> InstructionTables<'_, H> {
/// Creates a plain instruction table for the given spec.
#[inline]
pub const fn new_plain<SPEC: Spec>() -> Self {
Self::Plain(make_instruction_table::<H, SPEC>())
}
}

/// Make instruction table.
#[inline]
pub const fn make_instruction_table<H: Host, SPEC: Spec>() -> InstructionTable<H> {
// Force const-eval of the table creation, making this function trivial.
// TODO: Replace this with a `const {}` block once it is stable.
struct ConstTable<H: Host, SPEC: Spec> {
_phantom: core::marker::PhantomData<(H, SPEC)>,
}
impl<H: Host, SPEC: Spec> ConstTable<H, SPEC> {
const NEW: InstructionTable<H> = {
let mut tables: InstructionTable<H> = [control::unknown; 256];
let mut i = 0;
while i < 256 {
tables[i] = instruction::<H, SPEC>(i as u8);
i += 1;
}
tables
};
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't know this was possible. Nice one!

llvm got fixed in 1.75 so we could use newest rust.
But will merge this as it is.

ConstTable::<H, SPEC>::NEW
}

/// Make boxed instruction table that calls `outer` closure for every instruction.
#[inline]
pub fn make_boxed_instruction_table<'a, H, SPEC, FN>(
table: InstructionTable<H>,
mut outer: FN,
) -> BoxedInstructionTable<'a, H>
where
H: Host,
SPEC: Spec + 'a,
FN: FnMut(Instruction<H>) -> BoxedInstruction<'a, H>,
{
core::array::from_fn(|i| outer(table[i]))
}

macro_rules! opcodes {
($($val:literal => $name:ident => $f:expr),* $(,)?) => {
// Constants for each opcode. This also takes care of duplicate names.
Expand All @@ -56,7 +100,7 @@ macro_rules! opcodes {
};

/// Returns the instruction function for the given opcode and spec.
pub fn instruction<H: Host, SPEC: Spec>(opcode: u8) -> Instruction<H> {
pub const fn instruction<H: Host, SPEC: Spec>(opcode: u8) -> Instruction<H> {
match opcode {
$($name => $f,)*
_ => control::unknown,
Expand All @@ -65,27 +109,6 @@ macro_rules! opcodes {
};
}

/// Make instruction table.
pub fn make_instruction_table<H: Host, SPEC: Spec>() -> InstructionTable<H> {
core::array::from_fn(|i| {
debug_assert!(i <= u8::MAX as usize);
instruction::<H, SPEC>(i as u8)
})
}

/// Make boxed instruction table that calls `outer` closure for every instruction.
pub fn make_boxed_instruction_table<'a, H, SPEC, FN>(
table: InstructionTable<H>,
mut outer: FN,
) -> BoxedInstructionTable<'a, H>
where
H: Host,
SPEC: Spec + 'static,
FN: FnMut(Instruction<H>) -> BoxedInstruction<'a, H>,
{
core::array::from_fn(|i| outer(table[i]))
}

// When adding new opcodes:
// 1. add the opcode to the list below; make sure it's sorted by opcode value
// 2. add its gas info in the `opcode_gas_info` function below
Expand Down
2 changes: 1 addition & 1 deletion crates/primitives/src/env/handler_cfg.rs
Expand Up @@ -41,7 +41,7 @@ impl HandlerCfg {
}
}

/// Returns true if the optimism feature is enabled and flag is set to true.
/// Returns `true` if the optimism feature is enabled and flag is set to `true`.
pub fn is_optimism(&self) -> bool {
cfg_if::cfg_if! {
if #[cfg(feature = "optimism")] {
Expand Down
2 changes: 1 addition & 1 deletion crates/primitives/src/specification.rs
Expand Up @@ -110,7 +110,7 @@ impl From<&str> for SpecId {
}
}

pub trait Spec: Sized {
pub trait Spec: Sized + 'static {
/// The specification ID.
const SPEC_ID: SpecId;

Expand Down
21 changes: 8 additions & 13 deletions crates/revm/src/handler.rs
Expand Up @@ -8,10 +8,7 @@ pub use handle_types::*;

// Includes.
use crate::{
interpreter::{
opcode::{make_instruction_table, InstructionTables},
Host,
},
interpreter::{opcode::InstructionTables, Host},
primitives::{db::Database, spec_to_generic, HandlerCfg, Spec, SpecId},
Evm,
};
Expand Down Expand Up @@ -58,14 +55,12 @@ impl<'a, EXT, DB: Database> EvmHandler<'a, EXT, DB> {
}
}
}
/// Handler for the mainnet
pub fn mainnet<SPEC: Spec + 'static>() -> Self {

/// Default handler for Ethereum mainnet.
pub fn mainnet<SPEC: Spec>() -> Self {
Self {
cfg: HandlerCfg::new(SPEC::SPEC_ID),
instruction_table: Some(InstructionTables::Plain(make_instruction_table::<
Evm<'a, EXT, DB>,
SPEC,
>())),
instruction_table: Some(InstructionTables::new_plain::<SPEC>()),
registers: Vec::new(),
validation: ValidationHandler::new::<SPEC>(),
pre_execution: PreExecutionHandler::new::<SPEC>(),
Expand All @@ -74,14 +69,14 @@ impl<'a, EXT, DB: Database> EvmHandler<'a, EXT, DB> {
}
}

/// Is optimism enabled.
/// Returns `true` if the optimism feature is enabled and flag is set to `true`.
pub fn is_optimism(&self) -> bool {
self.cfg.is_optimism()
}

/// Handler for optimism
#[cfg(feature = "optimism")]
pub fn optimism<SPEC: Spec + 'static>() -> Self {
pub fn optimism<SPEC: Spec>() -> Self {
let mut handler = Self::mainnet::<SPEC>();
handler.cfg.is_optimism = true;
handler.append_handler_register(HandleRegisters::Plain(
Expand Down Expand Up @@ -171,7 +166,7 @@ impl<'a, EXT, DB: Database> EvmHandler<'a, EXT, DB> {
}

/// Creates the Handler with Generic Spec.
pub fn create_handle_generic<SPEC: Spec + 'static>(&mut self) -> EvmHandler<'a, EXT, DB> {
pub fn create_handle_generic<SPEC: Spec>(&mut self) -> EvmHandler<'a, EXT, DB> {
let registers = core::mem::take(&mut self.registers);
let mut base_handler = Handler::mainnet::<SPEC>();
// apply all registers to default handeler and raw mainnet instruction table.
Expand Down