From 661b8b9bb77550099862ff09f68cde1609d7e1b6 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Tue, 4 Jun 2024 13:52:19 +0200 Subject: [PATCH] perf: cow it up --- crates/precompile/src/lib.rs | 4 +- crates/primitives/src/precompile.rs | 20 +- .../revm/src/context/context_precompiles.rs | 206 +++++++++++++----- crates/revm/src/context/evm_context.rs | 4 +- 4 files changed, 169 insertions(+), 65 deletions(-) diff --git a/crates/precompile/src/lib.rs b/crates/precompile/src/lib.rs index ca797c8118..e2b0d4a3db 100644 --- a/crates/precompile/src/lib.rs +++ b/crates/precompile/src/lib.rs @@ -184,13 +184,13 @@ impl Precompiles { /// Returns an iterator over the precompiles addresses. #[inline] - pub fn addresses(&self) -> impl Iterator { + pub fn addresses(&self) -> impl ExactSizeIterator { self.inner.keys() } /// Consumes the type and returns all precompile addresses. #[inline] - pub fn into_addresses(self) -> impl Iterator { + pub fn into_addresses(self) -> impl ExactSizeIterator { self.inner.into_keys() } diff --git a/crates/primitives/src/precompile.rs b/crates/primitives/src/precompile.rs index e585377537..b4f9b18d02 100644 --- a/crates/primitives/src/precompile.rs +++ b/crates/primitives/src/precompile.rs @@ -94,11 +94,25 @@ impl Precompile { /// Call the precompile with the given input and gas limit and return the result. pub fn call(&mut self, bytes: &Bytes, gas_price: u64, env: &Env) -> PrecompileResult { - match self { + match *self { + Precompile::Standard(p) => p(bytes, gas_price), + Precompile::Env(p) => p(bytes, gas_price, env), + Precompile::Stateful(ref p) => p.call(bytes, gas_price, env), + Precompile::StatefulMut(ref mut p) => p.call_mut(bytes, gas_price, env), + } + } + + /// Call the precompile with the given input and gas limit and return the result. + /// + /// # Panics + /// + /// Panics if the precompile is a mutable stateful precompile. + pub fn call_ref(&self, bytes: &Bytes, gas_price: u64, env: &Env) -> PrecompileResult { + match *self { Precompile::Standard(p) => p(bytes, gas_price), Precompile::Env(p) => p(bytes, gas_price, env), - Precompile::Stateful(p) => p.call(bytes, gas_price, env), - Precompile::StatefulMut(p) => p.call_mut(bytes, gas_price, env), + Precompile::Stateful(ref p) => p.call(bytes, gas_price, env), + Precompile::StatefulMut(_) => panic!("call_ref on mutable stateful precompile"), } } } diff --git a/crates/revm/src/context/context_precompiles.rs b/crates/revm/src/context/context_precompiles.rs index 6f18a9a37f..234c1bd966 100644 --- a/crates/revm/src/context/context_precompiles.rs +++ b/crates/revm/src/context/context_precompiles.rs @@ -1,15 +1,13 @@ +use super::InnerEvmContext; use crate::{ precompile::{Precompile, PrecompileResult}, primitives::{db::Database, Address, Bytes, HashMap}, }; -use core::ops::{Deref, DerefMut}; use dyn_clone::DynClone; use revm_precompile::{PrecompileSpecId, PrecompileWithAddress, Precompiles}; use std::{boxed::Box, sync::Arc}; -use super::InnerEvmContext; - -/// Precompile and its handlers. +/// A single precompile handler. pub enum ContextPrecompile { /// Ordinary precompiles Ordinary(Precompile), @@ -24,66 +22,164 @@ pub enum ContextPrecompile { impl Clone for ContextPrecompile { fn clone(&self) -> Self { match self { - Self::Ordinary(arg0) => Self::Ordinary(arg0.clone()), - Self::ContextStateful(arg0) => Self::ContextStateful(arg0.clone()), - Self::ContextStatefulMut(arg0) => Self::ContextStatefulMut(arg0.clone()), + Self::Ordinary(p) => Self::Ordinary(p.clone()), + Self::ContextStateful(p) => Self::ContextStateful(p.clone()), + Self::ContextStatefulMut(p) => Self::ContextStatefulMut(p.clone()), } } } -#[derive(Clone)] -pub struct ContextPrecompiles { - inner: HashMap>, +enum PrecompilesCow { + /// Default precompiles, returned by `Precompiles::new`. Used to fast-path the default case. + Default(&'static Precompiles), + Owned(HashMap>), } -impl Extend<(Address, ContextPrecompile)> for ContextPrecompiles { - fn extend)>>(&mut self, iter: T) { - self.inner.extend(iter.into_iter().map(Into::into)) +impl Clone for PrecompilesCow { + fn clone(&self) -> Self { + match *self { + PrecompilesCow::Default(p) => PrecompilesCow::Default(p), + PrecompilesCow::Owned(ref inner) => PrecompilesCow::Owned(inner.clone()), + } } } -impl Extend for ContextPrecompiles { - fn extend>(&mut self, iter: T) { - self.inner.extend(iter.into_iter().map(|precompile| { - let (address, precompile) = precompile.into(); - (address, precompile.into()) - })); +/// Precompiles context. +pub struct ContextPrecompiles { + inner: PrecompilesCow, +} + +impl Clone for ContextPrecompiles { + fn clone(&self) -> Self { + Self { + inner: self.inner.clone(), + } } } impl ContextPrecompiles { - /// Creates a new precompiles at the given spec ID. + /// Creates a new precompiles context at the given spec ID. + /// + /// This is a cheap operation that does not allocate by reusing the global precompiles. #[inline] pub fn new(spec_id: PrecompileSpecId) -> Self { - Precompiles::new(spec_id).into() + Self::from_static_precompiles(Precompiles::new(spec_id)) + } + + /// Creates a new precompiles context from the given static precompiles. + /// + /// NOTE: The internal precompiles must not be `StatefulMut` or `call` will panic. + /// This is done because the default precompiles are not stateful. + #[inline] + pub fn from_static_precompiles(precompiles: &'static Precompiles) -> Self { + Self { + inner: PrecompilesCow::Default(precompiles), + } + } + + /// Creates a new precompiles context from the given precompiles. + #[inline] + pub fn from_precompiles(precompiles: HashMap>) -> Self { + Self { + inner: PrecompilesCow::Owned(precompiles), + } } /// Returns precompiles addresses. #[inline] - pub fn addresses(&self) -> impl Iterator { - self.inner.keys() + pub fn addresses(&self) -> impl ExactSizeIterator { + // SAFETY: `keys` does not touch values. + unsafe { self.fake_as_owned() }.keys() + } + + /// Returns `true` if the precompiles contains the given address. + #[inline] + pub fn contains(&self, address: &Address) -> bool { + // SAFETY: `contains_key` does not touch values. + unsafe { self.fake_as_owned() }.contains_key(address) + } + + /// # Safety + /// + /// The resulting value type is wrong if this is `Default`, + /// but works for methods that operate only on keys. + #[inline] + unsafe fn fake_as_owned(&self) -> &HashMap> { + match self.inner { + PrecompilesCow::Default(inner) => unsafe { core::mem::transmute(inner) }, + PrecompilesCow::Owned(ref inner) => inner, + } } /// Call precompile and executes it. Returns the result of the precompile execution. - /// None if the precompile does not exist. + /// + /// Returns `None` if the precompile does not exist. + /// + /// Note that this does not #[inline] pub fn call( &mut self, - addess: Address, + address: &Address, bytes: &Bytes, gas_price: u64, evmctx: &mut InnerEvmContext, ) -> Option { - let precompile = self.inner.get_mut(&addess)?; + Some(match self.inner { + PrecompilesCow::Default(p) => p.get(address)?.call_ref(bytes, gas_price, &evmctx.env), + PrecompilesCow::Owned(ref mut owned) => match owned.get_mut(address)? { + ContextPrecompile::Ordinary(p) => p.call(bytes, gas_price, &evmctx.env), + ContextPrecompile::ContextStateful(p) => p.call(bytes, gas_price, evmctx), + ContextPrecompile::ContextStatefulMut(p) => p.call_mut(bytes, gas_price, evmctx), + }, + }) + } - match precompile { - ContextPrecompile::Ordinary(p) => Some(p.call(bytes, gas_price, &evmctx.env)), - ContextPrecompile::ContextStatefulMut(p) => Some(p.call_mut(bytes, gas_price, evmctx)), - ContextPrecompile::ContextStateful(p) => Some(p.call(bytes, gas_price, evmctx)), + /// Returns a mutable reference to the precompiles map. + /// + /// Clones the precompiles map if it is shared. + #[inline] + pub fn to_mut(&mut self) -> &mut HashMap> { + match self.inner { + PrecompilesCow::Default(_) => self.to_mut_slow(), + PrecompilesCow::Owned(ref mut inner) => inner, + } + } + + #[allow(clippy::wrong_self_convention)] + #[cold] + fn to_mut_slow(&mut self) -> &mut HashMap> { + let PrecompilesCow::Default(precompiles) = self.inner else { + unreachable!() + }; + self.inner = PrecompilesCow::Owned( + precompiles + .inner + .iter() + .map(|(k, v)| (*k, v.clone().into())) + .collect(), + ); + match self.inner { + PrecompilesCow::Default(_) => unreachable!(), + PrecompilesCow::Owned(ref mut inner) => inner, } } } +impl Extend<(Address, ContextPrecompile)> for ContextPrecompiles { + fn extend)>>(&mut self, iter: T) { + self.to_mut().extend(iter.into_iter().map(Into::into)) + } +} + +impl Extend for ContextPrecompiles { + fn extend>(&mut self, iter: T) { + self.to_mut().extend(iter.into_iter().map(|precompile| { + let (address, precompile) = precompile.into(); + (address, precompile.into()) + })); + } +} + impl Default for ContextPrecompiles { fn default() -> Self { Self { @@ -92,17 +188,9 @@ impl Default for ContextPrecompiles { } } -impl Deref for ContextPrecompiles { - type Target = HashMap>; - - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -impl DerefMut for ContextPrecompiles { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner +impl Default for PrecompilesCow { + fn default() -> Self { + Self::Owned(Default::default()) } } @@ -142,22 +230,24 @@ impl From for ContextPrecompile { } } -impl From for ContextPrecompiles { - fn from(p: Precompiles) -> Self { - ContextPrecompiles { - inner: p.inner.into_iter().map(|(k, v)| (k, v.into())).collect(), - } - } -} - -impl From<&Precompiles> for ContextPrecompiles { - fn from(p: &Precompiles) -> Self { - ContextPrecompiles { - inner: p - .inner - .iter() - .map(|(&k, v)| (k, v.clone().into())) - .collect(), - } +#[cfg(test)] +mod tests { + use super::*; + use crate::db::EmptyDB; + + #[test] + fn test_precompiles_context() { + let custom_address = Address::with_last_byte(0xff); + + let mut precompiles = ContextPrecompiles::::new(PrecompileSpecId::HOMESTEAD); + assert_eq!(precompiles.addresses().count(), 4); + assert!(matches!(precompiles.inner, PrecompilesCow::Default(_))); + assert!(!precompiles.contains(&custom_address)); + + let precompile = Precompile::Standard(|_, _| Ok(Default::default())); + precompiles.extend([(custom_address, precompile.into())]); + assert_eq!(precompiles.addresses().count(), 5); + assert!(matches!(precompiles.inner, PrecompilesCow::Owned(_))); + assert!(precompiles.contains(&custom_address)); } } diff --git a/crates/revm/src/context/evm_context.rs b/crates/revm/src/context/evm_context.rs index b92d711da8..83105a0e95 100644 --- a/crates/revm/src/context/evm_context.rs +++ b/crates/revm/src/context/evm_context.rs @@ -104,7 +104,7 @@ impl EvmContext { #[inline] fn call_precompile( &mut self, - address: Address, + address: &Address, input_data: &Bytes, gas: Gas, ) -> Option { @@ -194,7 +194,7 @@ impl EvmContext { _ => {} }; - if let Some(result) = self.call_precompile(inputs.bytecode_address, &inputs.input, gas) { + if let Some(result) = self.call_precompile(&inputs.bytecode_address, &inputs.input, gas) { if matches!(result.result, return_ok!()) { self.journaled_state.checkpoint_commit(); } else {