Skip to content

Commit

Permalink
perf: cow it up
Browse files Browse the repository at this point in the history
  • Loading branch information
DaniPopes committed Jun 4, 2024
1 parent aed1176 commit 64a27a9
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 64 deletions.
4 changes: 2 additions & 2 deletions crates/precompile/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,13 +184,13 @@ impl Precompiles {

/// Returns an iterator over the precompiles addresses.
#[inline]
pub fn addresses(&self) -> impl Iterator<Item = &Address> {
pub fn addresses(&self) -> impl ExactSizeIterator<Item = &Address> {
self.inner.keys()
}

/// Consumes the type and returns all precompile addresses.
#[inline]
pub fn into_addresses(self) -> impl Iterator<Item = Address> {
pub fn into_addresses(self) -> impl ExactSizeIterator<Item = Address> {
self.inner.into_keys()
}

Expand Down
20 changes: 17 additions & 3 deletions crates/primitives/src/precompile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
}
}
}
Expand Down
209 changes: 152 additions & 57 deletions crates/revm/src/context/context_precompiles.rs
Original file line number Diff line number Diff line change
@@ -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<DB: Database> {
/// Ordinary precompiles
Ordinary(Precompile),
Expand All @@ -24,64 +22,167 @@ pub enum ContextPrecompile<DB: Database> {
impl<DB: Database> Clone for ContextPrecompile<DB> {
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<DB: Database> {
inner: HashMap<Address, ContextPrecompile<DB>>,
enum PrecompilesCow<DB: Database> {
/// Default precompiles, returned by `Precompiles::new`. Used to fast-path the default case.
Default(&'static Precompiles),
Owned(HashMap<Address, ContextPrecompile<DB>>),
}

impl<DB: Database> Extend<(Address, ContextPrecompile<DB>)> for ContextPrecompiles<DB> {
fn extend<T: IntoIterator<Item = (Address, ContextPrecompile<DB>)>>(&mut self, iter: T) {
self.inner.extend(iter.into_iter().map(Into::into))
impl<DB: Database> Clone for PrecompilesCow<DB> {
fn clone(&self) -> Self {
match *self {
PrecompilesCow::Default(p) => PrecompilesCow::Default(p),
PrecompilesCow::Owned(ref inner) => PrecompilesCow::Owned(inner.clone()),
}
}
}

impl<DB: Database> Extend<PrecompileWithAddress> for ContextPrecompiles<DB> {
fn extend<T: IntoIterator<Item = PrecompileWithAddress>>(&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<DB: Database> {
inner: PrecompilesCow<DB>,
}

impl<DB: Database> Clone for ContextPrecompiles<DB> {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
}
}
}

impl<DB: Database> ContextPrecompiles<DB> {
/// 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<Address, ContextPrecompile<DB>>) -> Self {
Self {
inner: PrecompilesCow::Owned(precompiles),
}
}

/// Returns precompiles addresses.
#[inline]
pub fn addresses(&self) -> impl Iterator<Item = &Address> {
self.inner.keys()
pub fn addresses(&self) -> impl ExactSizeIterator<Item = &Address> {
// 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 because both maps' internal value types have
/// the same size.
#[inline]
unsafe fn fake_as_owned(&self) -> &HashMap<Address, ContextPrecompile<DB>> {
const _: () = assert!(
core::mem::size_of::<ContextPrecompile<crate::db::EmptyDB>>()
== core::mem::size_of::<Precompile>()
);
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<DB>,
) -> Option<PrecompileResult> {
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<Address, ContextPrecompile<DB>> {
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<Address, ContextPrecompile<DB>> {
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<DB: Database> Extend<(Address, ContextPrecompile<DB>)> for ContextPrecompiles<DB> {
fn extend<T: IntoIterator<Item = (Address, ContextPrecompile<DB>)>>(&mut self, iter: T) {
self.to_mut().extend(iter.into_iter().map(Into::into))
}
}

impl<DB: Database> Extend<PrecompileWithAddress> for ContextPrecompiles<DB> {
fn extend<T: IntoIterator<Item = PrecompileWithAddress>>(&mut self, iter: T) {
self.to_mut().extend(iter.into_iter().map(|precompile| {
let (address, precompile) = precompile.into();
(address, precompile.into())
}));
}
}

impl<DB: Database> Default for ContextPrecompiles<DB> {
Expand All @@ -92,17 +193,9 @@ impl<DB: Database> Default for ContextPrecompiles<DB> {
}
}

impl<DB: Database> Deref for ContextPrecompiles<DB> {
type Target = HashMap<Address, ContextPrecompile<DB>>;

fn deref(&self) -> &Self::Target {
&self.inner
}
}

impl<DB: Database> DerefMut for ContextPrecompiles<DB> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
impl<DB: Database> Default for PrecompilesCow<DB> {
fn default() -> Self {
Self::Owned(Default::default())
}
}

Expand Down Expand Up @@ -142,22 +235,24 @@ impl<DB: Database> From<Precompile> for ContextPrecompile<DB> {
}
}

impl<DB: Database> From<Precompiles> for ContextPrecompiles<DB> {
fn from(p: Precompiles) -> Self {
ContextPrecompiles {
inner: p.inner.into_iter().map(|(k, v)| (k, v.into())).collect(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::db::EmptyDB;

impl<DB: Database> From<&Precompiles> for ContextPrecompiles<DB> {
fn from(p: &Precompiles) -> Self {
ContextPrecompiles {
inner: p
.inner
.iter()
.map(|(&k, v)| (k, v.clone().into()))
.collect(),
}
#[test]
fn test_precompiles_context() {
let custom_address = Address::with_last_byte(0xff);

let mut precompiles = ContextPrecompiles::<EmptyDB>::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));
}
}
4 changes: 2 additions & 2 deletions crates/revm/src/context/evm_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ impl<DB: Database> EvmContext<DB> {
#[inline]
fn call_precompile(
&mut self,
address: Address,
address: &Address,
input_data: &Bytes,
gas: Gas,
) -> Option<InterpreterResult> {
Expand Down Expand Up @@ -194,7 +194,7 @@ impl<DB: Database> EvmContext<DB> {
_ => {}
};

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 {
Expand Down

0 comments on commit 64a27a9

Please sign in to comment.