1use alloy_primitives::U256;
12use solar_data_structures::{fmt, index::IndexVec, newtype_index};
13
14mod display;
15mod parse;
16mod passes;
17mod verify;
18
19pub use parse::{EvmIrParseError, parse_evm_ir_module};
20pub use passes::{EVM_IR_PASSES, EvmIrPass};
21pub use verify::{EvmIrVerifyError, verify_evm_ir_module};
22
23newtype_index! {
24 pub struct EvmIrBlockId;
26}
27
28newtype_index! {
29 pub struct EvmIrValueId;
31}
32
33#[derive(Clone, Debug, Default, PartialEq, Eq)]
35pub struct EvmIrModule {
36 pub name: String,
38 pub blocks: IndexVec<EvmIrBlockId, EvmIrBlock>,
40 pub entry_block: Option<EvmIrBlockId>,
42 pub values: IndexVec<EvmIrValueId, EvmIrValue>,
44}
45
46impl EvmIrModule {
47 #[must_use]
49 pub fn new(name: impl Into<String>) -> Self {
50 let name = name.into();
51 assert!(is_valid_ident(&name), "invalid EVM IR program name `{name}`");
52 Self { name, blocks: IndexVec::new(), entry_block: None, values: IndexVec::new() }
53 }
54
55 pub fn add_block(&mut self, block: EvmIrBlock) -> EvmIrBlockId {
57 let id = self.blocks.push(block);
58 if self.entry_block.is_none() {
59 self.entry_block = Some(id);
60 }
61 id
62 }
63
64 pub fn add_value(&mut self, name: impl Into<String>) -> EvmIrValueId {
66 let name = name.into();
67 assert!(is_valid_value_name(&name), "invalid EVM IR value name `%{name}`");
68 self.values.push(EvmIrValue { name })
69 }
70
71 #[must_use]
73 pub fn block(&self, id: EvmIrBlockId) -> &EvmIrBlock {
74 &self.blocks[id]
75 }
76
77 pub fn block_mut(&mut self, id: EvmIrBlockId) -> &mut EvmIrBlock {
79 &mut self.blocks[id]
80 }
81
82 #[must_use]
84 pub fn value(&self, id: EvmIrValueId) -> &EvmIrValue {
85 &self.values[id]
86 }
87}
88
89#[derive(Clone, Debug, PartialEq, Eq)]
91pub struct EvmIrBlock {
92 pub label: String,
94 pub metadata: EvmIrBlockMetadata,
97 pub instructions: Vec<EvmIrInstruction>,
99 pub terminator: Option<EvmIrTerminator>,
101 pub entry_stack: Vec<EvmIrValueId>,
109}
110
111impl EvmIrBlock {
112 #[must_use]
114 pub fn new(label: impl Into<String>) -> Self {
115 let label = label.into();
116 assert!(is_valid_block_label(&label), "invalid EVM IR block label `{label}`");
117 Self {
118 label,
119 metadata: EvmIrBlockMetadata::default(),
120 instructions: Vec::new(),
121 terminator: None,
122 entry_stack: Vec::new(),
123 }
124 }
125}
126
127#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
129pub struct EvmIrBlockMetadata {
130 pub hotness: EvmIrBlockHotness,
132}
133
134#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
136pub enum EvmIrBlockHotness {
137 #[default]
139 Hot,
140 Cold,
142}
143
144impl EvmIrBlockHotness {
145 fn parse(value: &str) -> Option<Self> {
146 Some(match value {
147 "hot" => Self::Hot,
148 "cold" => Self::Cold,
149 _ => return None,
150 })
151 }
152}
153
154#[derive(Clone, Debug, PartialEq, Eq)]
156pub struct EvmIrValue {
157 pub name: String,
159}
160
161#[derive(Clone, Debug, PartialEq, Eq)]
163pub struct EvmIrInstruction {
164 pub result: Option<EvmIrValueId>,
166 pub kind: EvmIrInstructionKind,
168 pub operands: Vec<EvmIrOperand>,
170 pub metadata: EvmIrMetadata,
172}
173
174impl EvmIrInstruction {
175 #[must_use]
177 pub fn new(mnemonic: impl Into<String>, operands: Vec<EvmIrOperand>) -> Self {
178 Self {
179 result: None,
180 kind: EvmIrInstructionKind::Operation(mnemonic.into()),
181 operands,
182 metadata: EvmIrMetadata::default(),
183 }
184 }
185
186 #[must_use]
188 pub fn stack_op(op: EvmIrStackOp) -> Self {
189 Self {
190 result: None,
191 kind: EvmIrInstructionKind::Stack(op),
192 operands: Vec::new(),
193 metadata: EvmIrMetadata::default(),
194 }
195 }
196
197 #[must_use]
199 pub fn mnemonic(&self) -> impl fmt::Display + '_ {
200 fmt::from_fn(move |f| match &self.kind {
201 EvmIrInstructionKind::Operation(mnemonic) => write!(f, "{mnemonic}"),
202 EvmIrInstructionKind::Stack(op) => write!(f, "{}", op.mnemonic()),
203 })
204 }
205
206 #[must_use]
208 pub const fn is_physical_stack_op(&self) -> bool {
209 matches!(self.kind, EvmIrInstructionKind::Stack(_))
210 }
211}
212
213#[derive(Clone, Debug, PartialEq, Eq, Hash)]
215pub enum EvmIrInstructionKind {
216 Operation(String),
218 Stack(EvmIrStackOp),
220}
221
222#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
228pub enum EvmIrStackOp {
229 Dup(u8),
231 Swap(u8),
233 Pop,
235}
236
237impl EvmIrStackOp {
238 #[must_use]
240 pub const fn dup(n: u8) -> Option<Self> {
241 if n >= 1 && n <= 16 { Some(Self::Dup(n)) } else { None }
242 }
243
244 #[must_use]
246 pub const fn swap(n: u8) -> Option<Self> {
247 if n >= 1 && n <= 16 { Some(Self::Swap(n)) } else { None }
248 }
249
250 #[must_use]
252 pub const fn stack_effect(self) -> EvmIrStackEffect {
253 match self {
254 Self::Dup(_) => EvmIrStackEffect::new(0, 1),
255 Self::Swap(_) => EvmIrStackEffect::new(0, 0),
256 Self::Pop => EvmIrStackEffect::new(1, 0),
257 }
258 }
259
260 fn parse(mnemonic: &str) -> Option<Self> {
261 if mnemonic == "pop" {
262 return Some(Self::Pop);
263 }
264 if let Some(n) = mnemonic.strip_prefix("dup").and_then(|s| s.parse::<u8>().ok()) {
265 return Self::dup(n);
266 }
267 if let Some(n) = mnemonic.strip_prefix("swap").and_then(|s| s.parse::<u8>().ok()) {
268 return Self::swap(n);
269 }
270 None
271 }
272
273 fn mnemonic(self) -> impl fmt::Display {
274 fmt::from_fn(move |f| match self {
275 Self::Dup(n) => write!(f, "dup{n}"),
276 Self::Swap(n) => write!(f, "swap{n}"),
277 Self::Pop => write!(f, "pop"),
278 })
279 }
280}
281
282#[derive(Clone, Debug, PartialEq, Eq)]
284pub struct EvmIrTerminator {
285 pub kind: EvmIrTerminatorKind,
287 pub metadata: EvmIrMetadata,
289}
290
291impl EvmIrTerminator {
292 #[must_use]
294 pub const fn new(kind: EvmIrTerminatorKind) -> Self {
295 Self { kind, metadata: EvmIrMetadata::EMPTY }
296 }
297}
298
299#[derive(Clone, Debug, PartialEq, Eq)]
301pub enum EvmIrTerminatorKind {
302 Fallthrough(EvmIrBlockId),
304 Jump(EvmIrBlockId),
306 Branch {
308 condition: EvmIrOperand,
310 then_block: EvmIrBlockId,
312 else_block: EvmIrBlockId,
314 },
315 Switch {
317 value: EvmIrOperand,
319 default: EvmIrBlockId,
321 cases: Vec<(EvmIrOperand, EvmIrBlockId)>,
323 },
324 Return {
326 offset: EvmIrOperand,
328 size: EvmIrOperand,
330 },
331 Revert {
333 offset: EvmIrOperand,
335 size: EvmIrOperand,
337 },
338 Stop,
340 Invalid,
342 SelfDestruct {
344 recipient: EvmIrOperand,
346 },
347 RawOpcode(u8),
349}
350
351#[derive(Clone, Debug, PartialEq, Eq, Hash)]
353pub enum EvmIrOperand {
354 Value(EvmIrValueId),
356 Immediate(U256),
358 Block(EvmIrBlockId),
360 Symbol(String),
362}
363
364#[derive(Clone, Debug, Default, PartialEq, Eq)]
366pub struct EvmIrMetadata {
367 pub stack: Option<EvmIrStackEffect>,
369 pub attrs: Vec<EvmIrMetadataItem>,
371}
372
373impl EvmIrMetadata {
374 pub const EMPTY: Self = Self { stack: None, attrs: Vec::new() };
376
377 #[must_use]
379 pub fn is_empty(&self) -> bool {
380 self.stack.is_none() && self.attrs.is_empty()
381 }
382}
383
384#[derive(Clone, Debug, PartialEq, Eq)]
386pub struct EvmIrMetadataItem {
387 pub key: String,
389 pub value: Option<String>,
391}
392
393#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
395pub struct EvmIrStackEffect {
396 pub inputs: u16,
398 pub outputs: u16,
400}
401
402impl EvmIrStackEffect {
403 #[must_use]
405 pub const fn new(inputs: u16, outputs: u16) -> Self {
406 Self { inputs, outputs }
407 }
408}
409
410pub(super) fn default_instruction_stack_effect(inst: &EvmIrInstruction) -> EvmIrStackEffect {
411 match &inst.kind {
412 EvmIrInstructionKind::Stack(op) => op.stack_effect(),
413 EvmIrInstructionKind::Operation(_) if is_encoded_push_instruction(inst) => {
414 EvmIrStackEffect::new(0, 1)
415 }
416 EvmIrInstructionKind::Operation(_) => EvmIrStackEffect::new(
417 inst.operands.len().try_into().unwrap_or(u16::MAX),
418 u16::from(inst.result.is_some()),
419 ),
420 }
421}
422
423fn default_terminator_stack_effect(kind: &EvmIrTerminatorKind) -> EvmIrStackEffect {
424 match kind {
425 EvmIrTerminatorKind::Branch { .. } => EvmIrStackEffect::new(1, 0),
426 EvmIrTerminatorKind::Switch { .. } => EvmIrStackEffect::new(1, 0),
427 EvmIrTerminatorKind::Return { .. } | EvmIrTerminatorKind::Revert { .. } => {
428 EvmIrStackEffect::new(2, 0)
429 }
430 EvmIrTerminatorKind::SelfDestruct { .. } => EvmIrStackEffect::new(1, 0),
431 EvmIrTerminatorKind::Fallthrough(_)
432 | EvmIrTerminatorKind::Jump(_)
433 | EvmIrTerminatorKind::Stop
434 | EvmIrTerminatorKind::Invalid
435 | EvmIrTerminatorKind::RawOpcode(_) => EvmIrStackEffect::new(0, 0),
436 }
437}
438
439pub(super) fn is_encoded_push_instruction(inst: &EvmIrInstruction) -> bool {
440 matches!(
441 &inst.kind,
442 EvmIrInstructionKind::Operation(mnemonic)
443 if matches!(mnemonic.as_str(), "push" | "push_deferred" | "push_immutable")
444 )
445}
446
447fn is_ident_start(c: char) -> bool {
448 c.is_ascii_alphabetic() || c == '_' || c == '$' || c == '.'
449}
450
451fn is_ident_continue(c: char) -> bool {
452 is_ident_start(c) || c.is_ascii_digit()
453}
454
455fn is_valid_ident(name: &str) -> bool {
456 let mut chars = name.chars();
457 matches!(chars.next(), Some(c) if is_ident_start(c)) && chars.all(is_ident_continue)
458}
459
460fn is_valid_value_name(name: &str) -> bool {
461 let mut chars = name.chars();
462 matches!(chars.next(), Some(c) if is_ident_start(c) || c.is_ascii_digit())
463 && chars.all(is_ident_continue)
464}
465
466fn is_valid_block_label(label: &str) -> bool {
467 let Some(digits) = label.strip_prefix("bb") else {
468 return false;
469 };
470 !digits.is_empty() && digits.bytes().all(|b| b.is_ascii_digit())
471}