From 67c13f3daaba8620829d949b93fb0583d8f8fcd7 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Wed, 1 May 2024 08:47:22 +0200 Subject: [PATCH] feat: parse opcodes from strings (#1358) --- Cargo.lock | 50 +++++++++++++++++++++++ crates/interpreter/Cargo.toml | 10 ++++- crates/interpreter/src/opcode.rs | 69 +++++++++++++++++++++++++++++++- 3 files changed, 125 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b86ba8d2f2..8a57162cde 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2462,6 +2462,48 @@ dependencies = [ "rustc_version 0.4.0", ] +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.55", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project" version = "1.1.5" @@ -2903,6 +2945,8 @@ name = "revm-interpreter" version = "4.0.0" dependencies = [ "bincode", + "paste", + "phf", "revm-primitives", "serde", "serde_json", @@ -3438,6 +3482,12 @@ dependencies = [ "time", ] +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + [[package]] name = "slab" version = "0.4.9" diff --git a/crates/interpreter/Cargo.toml b/crates/interpreter/Cargo.toml index e9fe68cb3b..1b59e7d33a 100644 --- a/crates/interpreter/Cargo.toml +++ b/crates/interpreter/Cargo.toml @@ -16,6 +16,11 @@ rustdoc-args = ["--cfg", "docsrs"] [dependencies] revm-primitives = { path = "../primitives", version = "3.1.1", default-features = false } +paste = { version = "1.0", optional = true } +phf = { version = "0.11", default-features = false, optional = true, features = [ + "macros", +] } + # optional serde = { version = "1.0", default-features = false, features = [ "derive", @@ -24,7 +29,7 @@ serde = { version = "1.0", default-features = false, features = [ [dev-dependencies] walkdir = "2.5" -serde_json = { version = "1.0"} +serde_json = "1.0" bincode = "1.3" [[test]] @@ -33,13 +38,14 @@ path = "tests/eof.rs" required-features = ["serde"] [features] -default = ["std"] +default = ["std", "parse"] std = ["serde?/std", "revm-primitives/std"] hashbrown = ["revm-primitives/hashbrown"] serde = ["dep:serde", "revm-primitives/serde"] arbitrary = ["std", "revm-primitives/arbitrary"] asm-keccak = ["revm-primitives/asm-keccak"] portable = ["revm-primitives/portable"] +parse = ["dep:paste", "dep:phf"] optimism = ["revm-primitives/optimism"] # Optimism default handler enabled Optimism handler register by default in EvmBuilder. diff --git a/crates/interpreter/src/opcode.rs b/crates/interpreter/src/opcode.rs index eb4ff27363..725f86942b 100644 --- a/crates/interpreter/src/opcode.rs +++ b/crates/interpreter/src/opcode.rs @@ -125,6 +125,21 @@ where core::array::from_fn(|i| outer(table[i])) } +/// An error indicating that an opcode is invalid. +#[derive(Debug, PartialEq, Eq)] +#[cfg(feature = "parse")] +pub struct OpCodeError(()); + +#[cfg(feature = "parse")] +impl fmt::Display for OpCodeError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("invalid opcode") + } +} + +#[cfg(all(feature = "std", feature = "parse"))] +impl std::error::Error for OpCodeError {} + /// An EVM opcode. /// /// This is always a valid opcode, as declared in the [`opcode`][self] module or the @@ -144,6 +159,16 @@ impl fmt::Display for OpCode { } } +#[cfg(feature = "parse")] +impl core::str::FromStr for OpCode { + type Err = OpCodeError; + + #[inline] + fn from_str(s: &str) -> Result { + Self::parse(s).ok_or(OpCodeError(())) + } +} + impl OpCode { /// Instantiate a new opcode from a u8. #[inline] @@ -154,6 +179,13 @@ impl OpCode { } } + /// Parses an opcode from a string. This is the inverse of [`as_str`](Self::as_str). + #[inline] + #[cfg(feature = "parse")] + pub fn parse(s: &str) -> Option { + NAME_TO_OPCODE.get(s).copied() + } + /// Returns true if the opcode is a jump destination. #[inline] pub const fn is_jumpdest(&self) -> bool { @@ -213,7 +245,7 @@ impl OpCode { Self(opcode) } - /// Returns the opcode as a string. + /// Returns the opcode as a string. This is the inverse of [`parse`](Self::parse). #[doc(alias = "name")] #[inline] pub const fn as_str(self) -> &'static str { @@ -416,6 +448,25 @@ pub const fn stack_io(mut op: OpCodeInfo, inputs: u8, outputs: u8) -> OpCodeInfo /// Alias for the [`JUMPDEST`] opcode. pub const NOP: u8 = JUMPDEST; +/// Callback for creating a [`phf`] map with `stringify_with_cb`. +#[cfg(feature = "parse")] +macro_rules! phf_map_cb { + ($(#[doc = $s:literal] $id:ident)*) => { + phf::phf_map! { + $($s => OpCode::$id),* + } + }; +} + +/// Stringifies identifiers with `paste` so that they are available as literals. +/// This doesn't work with `stringify!` because it cannot be expanded inside of another macro. +#[cfg(feature = "parse")] +macro_rules! stringify_with_cb { + ($callback:ident; $($id:ident)*) => { paste::paste! { + $callback! { $(#[doc = "" $id ""] $id)* } + }}; +} + macro_rules! opcodes { ($($val:literal => $name:ident => $f:expr => $($modifier:ident $(( $($modifier_arg:expr),* ))?),*);* $(;)?) => { // Constants for each opcode. This also takes care of duplicate names. @@ -428,7 +479,7 @@ macro_rules! opcodes { pub const $name: Self = Self($val); )*} - /// Maps each opcode to its name. + /// Maps each opcode to its info. pub const OPCODE_INFO_JUMPTABLE: [Option; 256] = { let mut map = [None; 256]; let mut prev: u8 = 0; @@ -446,6 +497,10 @@ macro_rules! opcodes { map }; + /// Maps each name to its opcode. + #[cfg(feature = "parse")] + static NAME_TO_OPCODE: phf::Map<&'static str, OpCode> = stringify_with_cb! { phf_map_cb; $($name)* }; + /// Returns the instruction function for the given opcode and spec. pub const fn instruction(opcode: u8) -> Instruction { match opcode { @@ -839,4 +894,14 @@ mod tests { ); } } + + #[test] + #[cfg(feature = "parse")] + fn test_parsing() { + for i in 0..=u8::MAX { + if let Some(op) = OpCode::new(i) { + assert_eq!(OpCode::parse(op.as_str()), Some(op)); + } + } + } }