Skip to content

Commit 67c13f3

Browse files
authored
feat: parse opcodes from strings (bluealloy#1358)
1 parent a2279f2 commit 67c13f3

File tree

3 files changed

+125
-4
lines changed

3 files changed

+125
-4
lines changed

Cargo.lock

+50
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/interpreter/Cargo.toml

+8-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ rustdoc-args = ["--cfg", "docsrs"]
1616
[dependencies]
1717
revm-primitives = { path = "../primitives", version = "3.1.1", default-features = false }
1818

19+
paste = { version = "1.0", optional = true }
20+
phf = { version = "0.11", default-features = false, optional = true, features = [
21+
"macros",
22+
] }
23+
1924
# optional
2025
serde = { version = "1.0", default-features = false, features = [
2126
"derive",
@@ -24,7 +29,7 @@ serde = { version = "1.0", default-features = false, features = [
2429

2530
[dev-dependencies]
2631
walkdir = "2.5"
27-
serde_json = { version = "1.0"}
32+
serde_json = "1.0"
2833
bincode = "1.3"
2934

3035
[[test]]
@@ -33,13 +38,14 @@ path = "tests/eof.rs"
3338
required-features = ["serde"]
3439

3540
[features]
36-
default = ["std"]
41+
default = ["std", "parse"]
3742
std = ["serde?/std", "revm-primitives/std"]
3843
hashbrown = ["revm-primitives/hashbrown"]
3944
serde = ["dep:serde", "revm-primitives/serde"]
4045
arbitrary = ["std", "revm-primitives/arbitrary"]
4146
asm-keccak = ["revm-primitives/asm-keccak"]
4247
portable = ["revm-primitives/portable"]
48+
parse = ["dep:paste", "dep:phf"]
4349

4450
optimism = ["revm-primitives/optimism"]
4551
# Optimism default handler enabled Optimism handler register by default in EvmBuilder.

crates/interpreter/src/opcode.rs

+67-2
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,21 @@ where
125125
core::array::from_fn(|i| outer(table[i]))
126126
}
127127

128+
/// An error indicating that an opcode is invalid.
129+
#[derive(Debug, PartialEq, Eq)]
130+
#[cfg(feature = "parse")]
131+
pub struct OpCodeError(());
132+
133+
#[cfg(feature = "parse")]
134+
impl fmt::Display for OpCodeError {
135+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
136+
f.write_str("invalid opcode")
137+
}
138+
}
139+
140+
#[cfg(all(feature = "std", feature = "parse"))]
141+
impl std::error::Error for OpCodeError {}
142+
128143
/// An EVM opcode.
129144
///
130145
/// This is always a valid opcode, as declared in the [`opcode`][self] module or the
@@ -144,6 +159,16 @@ impl fmt::Display for OpCode {
144159
}
145160
}
146161

162+
#[cfg(feature = "parse")]
163+
impl core::str::FromStr for OpCode {
164+
type Err = OpCodeError;
165+
166+
#[inline]
167+
fn from_str(s: &str) -> Result<Self, Self::Err> {
168+
Self::parse(s).ok_or(OpCodeError(()))
169+
}
170+
}
171+
147172
impl OpCode {
148173
/// Instantiate a new opcode from a u8.
149174
#[inline]
@@ -154,6 +179,13 @@ impl OpCode {
154179
}
155180
}
156181

182+
/// Parses an opcode from a string. This is the inverse of [`as_str`](Self::as_str).
183+
#[inline]
184+
#[cfg(feature = "parse")]
185+
pub fn parse(s: &str) -> Option<Self> {
186+
NAME_TO_OPCODE.get(s).copied()
187+
}
188+
157189
/// Returns true if the opcode is a jump destination.
158190
#[inline]
159191
pub const fn is_jumpdest(&self) -> bool {
@@ -213,7 +245,7 @@ impl OpCode {
213245
Self(opcode)
214246
}
215247

216-
/// Returns the opcode as a string.
248+
/// Returns the opcode as a string. This is the inverse of [`parse`](Self::parse).
217249
#[doc(alias = "name")]
218250
#[inline]
219251
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
416448
/// Alias for the [`JUMPDEST`] opcode.
417449
pub const NOP: u8 = JUMPDEST;
418450

451+
/// Callback for creating a [`phf`] map with `stringify_with_cb`.
452+
#[cfg(feature = "parse")]
453+
macro_rules! phf_map_cb {
454+
($(#[doc = $s:literal] $id:ident)*) => {
455+
phf::phf_map! {
456+
$($s => OpCode::$id),*
457+
}
458+
};
459+
}
460+
461+
/// Stringifies identifiers with `paste` so that they are available as literals.
462+
/// This doesn't work with `stringify!` because it cannot be expanded inside of another macro.
463+
#[cfg(feature = "parse")]
464+
macro_rules! stringify_with_cb {
465+
($callback:ident; $($id:ident)*) => { paste::paste! {
466+
$callback! { $(#[doc = "" $id ""] $id)* }
467+
}};
468+
}
469+
419470
macro_rules! opcodes {
420471
($($val:literal => $name:ident => $f:expr => $($modifier:ident $(( $($modifier_arg:expr),* ))?),*);* $(;)?) => {
421472
// Constants for each opcode. This also takes care of duplicate names.
@@ -428,7 +479,7 @@ macro_rules! opcodes {
428479
pub const $name: Self = Self($val);
429480
)*}
430481

431-
/// Maps each opcode to its name.
482+
/// Maps each opcode to its info.
432483
pub const OPCODE_INFO_JUMPTABLE: [Option<OpCodeInfo>; 256] = {
433484
let mut map = [None; 256];
434485
let mut prev: u8 = 0;
@@ -446,6 +497,10 @@ macro_rules! opcodes {
446497
map
447498
};
448499

500+
/// Maps each name to its opcode.
501+
#[cfg(feature = "parse")]
502+
static NAME_TO_OPCODE: phf::Map<&'static str, OpCode> = stringify_with_cb! { phf_map_cb; $($name)* };
503+
449504
/// Returns the instruction function for the given opcode and spec.
450505
pub const fn instruction<H: Host + ?Sized, SPEC: Spec>(opcode: u8) -> Instruction<H> {
451506
match opcode {
@@ -839,4 +894,14 @@ mod tests {
839894
);
840895
}
841896
}
897+
898+
#[test]
899+
#[cfg(feature = "parse")]
900+
fn test_parsing() {
901+
for i in 0..=u8::MAX {
902+
if let Some(op) = OpCode::new(i) {
903+
assert_eq!(OpCode::parse(op.as_str()), Some(op));
904+
}
905+
}
906+
}
842907
}

0 commit comments

Comments
 (0)