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

feat: parse opcodes from strings #1358

Merged
merged 1 commit into from
May 1, 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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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(());
onbjerg marked this conversation as resolved.
Show resolved Hide resolved

#[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));
}
}
}
}
Loading