Skip to content

Commit

Permalink
feat: parse opcodes from strings (bluealloy#1358)
Browse files Browse the repository at this point in the history
  • Loading branch information
DaniPopes authored May 1, 2024
1 parent a2279f2 commit 67c13f3
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 4 deletions.
50 changes: 50 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 8 additions & 2 deletions crates/interpreter/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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]]
Expand All @@ -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.
Expand Down
69 changes: 67 additions & 2 deletions crates/interpreter/src/opcode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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, Self::Err> {
Self::parse(s).ok_or(OpCodeError(()))
}
}

impl OpCode {
/// Instantiate a new opcode from a u8.
#[inline]
Expand All @@ -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<Self> {
NAME_TO_OPCODE.get(s).copied()
}

/// Returns true if the opcode is a jump destination.
#[inline]
pub const fn is_jumpdest(&self) -> bool {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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.
Expand All @@ -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<OpCodeInfo>; 256] = {
let mut map = [None; 256];
let mut prev: u8 = 0;
Expand All @@ -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<H: Host + ?Sized, SPEC: Spec>(opcode: u8) -> Instruction<H> {
match opcode {
Expand Down Expand Up @@ -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));
}
}
}
}

0 comments on commit 67c13f3

Please sign in to comment.