Skip to main content

solar_codegen/mir/
inst.rs

1//! MIR instructions.
2
3use super::{BlockId, Function, FunctionId, MirType, Value, ValueId};
4use alloy_primitives::U256;
5use smallvec::SmallVec;
6use solar_interface::Span;
7use solar_sema::hir;
8use std::fmt;
9
10/// Extra information attached to a MIR instruction by lowering or analysis passes.
11#[derive(Clone, Debug, Default, PartialEq, Eq)]
12pub struct InstructionMetadata {
13    /// Proven storage alias key for `sload`/`sstore` instructions.
14    storage_alias: Option<Box<StorageAlias>>,
15    /// Source span that produced this instruction, when the lowerer can preserve it.
16    source_span: Span,
17    /// HIR expression that produced this instruction, when the lowerer can preserve it.
18    hir_expr: Option<hir::ExprId>,
19    /// Loop nesting depth attached by loop-aware analyses.
20    pub loop_depth: u16,
21    /// Packed optional memory region, effect kind, and unchecked flag.
22    flags: MetadataFlags,
23}
24
25impl InstructionMetadata {
26    /// Empty instruction metadata.
27    pub const EMPTY: Self = Self {
28        storage_alias: None,
29        hir_expr: None,
30        source_span: Span::DUMMY,
31        loop_depth: 0,
32        flags: MetadataFlags::EMPTY,
33    };
34
35    /// Returns the proven storage alias key.
36    #[must_use]
37    pub fn storage_alias(&self) -> Option<StorageAlias> {
38        self.storage_alias.as_deref().copied()
39    }
40
41    /// Sets the proven storage alias key.
42    pub fn set_storage_alias(&mut self, alias: Option<StorageAlias>) {
43        self.storage_alias = alias.map(Box::new);
44    }
45
46    /// Returns the HIR expression that produced this instruction.
47    #[must_use]
48    pub fn hir_expr(&self) -> Option<hir::ExprId> {
49        self.hir_expr
50    }
51
52    /// Sets the HIR expression that produced this instruction.
53    pub fn set_hir_expr(&mut self, expr: Option<hir::ExprId>) {
54        self.hir_expr = expr;
55    }
56
57    /// Returns the source span that produced this instruction.
58    #[must_use]
59    pub fn source_span(&self) -> Option<Span> {
60        (!self.source_span.is_dummy()).then_some(self.source_span)
61    }
62
63    /// Sets the source span that produced this instruction.
64    pub fn set_source_span(&mut self, span: Option<Span>) {
65        self.source_span = span.unwrap_or(Span::DUMMY);
66    }
67
68    /// Returns the proven memory region.
69    #[must_use]
70    pub fn memory_region(&self) -> Option<MemoryRegion> {
71        self.flags.memory_region()
72    }
73
74    /// Sets the proven memory region.
75    pub fn set_memory_region(&mut self, region: Option<MemoryRegion>) {
76        self.flags.set_memory_region(region);
77    }
78
79    /// Returns whether this instruction was lowered from an unchecked arithmetic context.
80    #[must_use]
81    pub fn unchecked(&self) -> bool {
82        self.flags.unchecked()
83    }
84
85    /// Sets whether this instruction was lowered from an unchecked arithmetic context.
86    pub fn set_unchecked(&mut self, unchecked: bool) {
87        self.flags.set_unchecked(unchecked);
88    }
89
90    /// Returns the conservative effect classification attached by lowering or analysis.
91    #[must_use]
92    pub fn effect(&self) -> Option<EffectKind> {
93        self.flags.effect()
94    }
95
96    /// Sets the conservative effect classification attached by lowering or analysis.
97    pub fn set_effect(&mut self, effect: Option<EffectKind>) {
98        self.flags.set_effect(effect);
99    }
100}
101
102#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
103struct MetadataFlags(u8);
104
105impl MetadataFlags {
106    const EMPTY: Self = Self(0);
107    const MEMORY_MASK: u8 = 0b0000_0111;
108    const EFFECT_MASK: u8 = 0b0111_1000;
109    const EFFECT_SHIFT: u8 = 3;
110    const UNCHECKED: u8 = 0b1000_0000;
111
112    fn memory_region(self) -> Option<MemoryRegion> {
113        match self.0 & Self::MEMORY_MASK {
114            0 => None,
115            1 => Some(MemoryRegion::Scratch),
116            2 => Some(MemoryRegion::AbiReturn),
117            3 => Some(MemoryRegion::Heap),
118            4 => Some(MemoryRegion::InternalFrame),
119            5 => Some(MemoryRegion::Unknown),
120            _ => unreachable!("invalid packed memory region"),
121        }
122    }
123
124    fn set_memory_region(&mut self, region: Option<MemoryRegion>) {
125        let bits = match region {
126            None => 0,
127            Some(MemoryRegion::Scratch) => 1,
128            Some(MemoryRegion::AbiReturn) => 2,
129            Some(MemoryRegion::Heap) => 3,
130            Some(MemoryRegion::InternalFrame) => 4,
131            Some(MemoryRegion::Unknown) => 5,
132        };
133        self.0 = (self.0 & !Self::MEMORY_MASK) | bits;
134    }
135
136    fn unchecked(self) -> bool {
137        self.0 & Self::UNCHECKED != 0
138    }
139
140    fn set_unchecked(&mut self, unchecked: bool) {
141        if unchecked {
142            self.0 |= Self::UNCHECKED;
143        } else {
144            self.0 &= !Self::UNCHECKED;
145        }
146    }
147
148    fn effect(self) -> Option<EffectKind> {
149        match (self.0 & Self::EFFECT_MASK) >> Self::EFFECT_SHIFT {
150            0 => None,
151            1 => Some(EffectKind::Pure),
152            2 => Some(EffectKind::MemoryRead),
153            3 => Some(EffectKind::MemoryWrite),
154            4 => Some(EffectKind::StorageRead),
155            5 => Some(EffectKind::StorageWrite),
156            6 => Some(EffectKind::TransientRead),
157            7 => Some(EffectKind::TransientWrite),
158            8 => Some(EffectKind::EnvironmentRead),
159            9 => Some(EffectKind::ExternalCall),
160            10 => Some(EffectKind::InternalCall),
161            11 => Some(EffectKind::Create),
162            12 => Some(EffectKind::Log),
163            _ => unreachable!("invalid packed effect kind"),
164        }
165    }
166
167    fn set_effect(&mut self, effect: Option<EffectKind>) {
168        let bits = match effect {
169            None => 0,
170            Some(EffectKind::Pure) => 1,
171            Some(EffectKind::MemoryRead) => 2,
172            Some(EffectKind::MemoryWrite) => 3,
173            Some(EffectKind::StorageRead) => 4,
174            Some(EffectKind::StorageWrite) => 5,
175            Some(EffectKind::TransientRead) => 6,
176            Some(EffectKind::TransientWrite) => 7,
177            Some(EffectKind::EnvironmentRead) => 8,
178            Some(EffectKind::ExternalCall) => 9,
179            Some(EffectKind::InternalCall) => 10,
180            Some(EffectKind::Create) => 11,
181            Some(EffectKind::Log) => 12,
182        } << Self::EFFECT_SHIFT;
183        self.0 = (self.0 & !Self::EFFECT_MASK) | bits;
184    }
185}
186
187/// A conservative storage alias key.
188#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
189pub enum StorageAlias {
190    /// A known absolute storage slot.
191    Slot(U256),
192    /// A loop-invariant symbolic slot value.
193    Symbolic(ValueId),
194    /// A loop-invariant symbolic base plus a known constant offset.
195    Offset {
196        /// Symbolic base slot.
197        base: ValueId,
198        /// Constant offset added to the base.
199        offset: U256,
200    },
201}
202
203impl StorageAlias {
204    /// Computes a conservative exact storage alias key for `value`.
205    #[must_use]
206    pub fn for_value(func: &Function, value: ValueId) -> Self {
207        match func.value(value) {
208            Value::Immediate(imm) => imm.as_u256().map_or(Self::Symbolic(value), Self::Slot),
209            Value::Inst(inst_id) => match func.instructions[*inst_id].kind {
210                InstKind::Add(lhs, rhs) => {
211                    if let Some(offset) = Self::immediate_u256(func, rhs) {
212                        Self::add_offset(func, lhs, offset)
213                    } else if let Some(offset) = Self::immediate_u256(func, lhs) {
214                        Self::add_offset(func, rhs, offset)
215                    } else {
216                        Self::Symbolic(value)
217                    }
218                }
219                InstKind::Sub(lhs, rhs) => {
220                    if let Some(offset) = Self::immediate_u256(func, rhs) {
221                        Self::add_offset(func, lhs, U256::ZERO.wrapping_sub(offset))
222                    } else {
223                        Self::Symbolic(value)
224                    }
225                }
226                _ => Self::Symbolic(value),
227            },
228            Value::Arg { .. } | Value::Undef(_) | Value::Error(_) => Self::Symbolic(value),
229        }
230    }
231
232    /// Returns true if two alias keys may refer to the same storage slot.
233    #[must_use]
234    pub fn may_alias(self, other: Self) -> bool {
235        match (self, other) {
236            (Self::Slot(a), Self::Slot(b)) => a == b,
237            (
238                Self::Offset { base: a, offset: a_offset },
239                Self::Offset { base: b, offset: b_offset },
240            ) if a == b => a_offset == b_offset,
241            (Self::Symbolic(_), Self::Symbolic(_)) => true,
242            (Self::Symbolic(a), Self::Offset { base, offset })
243            | (Self::Offset { base, offset }, Self::Symbolic(a))
244                if a == base =>
245            {
246                offset.is_zero()
247            }
248            _ => true,
249        }
250    }
251
252    /// Returns the symbolic base value, if this alias has one.
253    #[must_use]
254    pub const fn symbolic_base(self) -> Option<ValueId> {
255        match self {
256            Self::Symbolic(value) | Self::Offset { base: value, .. } => Some(value),
257            Self::Slot(_) => None,
258        }
259    }
260
261    fn add_offset(func: &Function, value: ValueId, offset: U256) -> Self {
262        match Self::for_value(func, value) {
263            Self::Slot(slot) => Self::Slot(slot.wrapping_add(offset)),
264            Self::Symbolic(base) if offset.is_zero() => Self::Symbolic(base),
265            Self::Symbolic(base) => Self::Offset { base, offset },
266            Self::Offset { base, offset: existing } => {
267                let offset = existing.wrapping_add(offset);
268                if offset.is_zero() { Self::Symbolic(base) } else { Self::Offset { base, offset } }
269            }
270        }
271    }
272
273    fn immediate_u256(func: &Function, value: ValueId) -> Option<U256> {
274        match func.value(value) {
275            Value::Immediate(imm) => imm.as_u256(),
276            _ => None,
277        }
278    }
279}
280
281/// A coarse memory region understood by MIR analyses.
282#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
283pub enum MemoryRegion {
284    /// Compiler-owned low-memory scratch space.
285    Scratch,
286    /// External ABI return buffer.
287    AbiReturn,
288    /// Solidity free-memory heap.
289    Heap,
290    /// Internal-call frame memory.
291    InternalFrame,
292    /// Region is known to be memory, but not classified more precisely.
293    Unknown,
294}
295
296impl MemoryRegion {
297    /// Returns the stable textual name used in MIR metadata.
298    #[must_use]
299    pub const fn name(&self) -> &'static str {
300        match self {
301            Self::Scratch => "scratch",
302            Self::AbiReturn => "abi_return",
303            Self::Heap => "heap",
304            Self::InternalFrame => "internal_frame",
305            Self::Unknown => "unknown",
306        }
307    }
308}
309
310/// Conservative side-effect class for an instruction.
311#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
312pub enum EffectKind {
313    /// Pure computation.
314    Pure,
315    /// Memory read.
316    MemoryRead,
317    /// Memory write.
318    MemoryWrite,
319    /// Persistent storage read.
320    StorageRead,
321    /// Persistent storage write.
322    StorageWrite,
323    /// Transient storage read.
324    TransientRead,
325    /// Transient storage write.
326    TransientWrite,
327    /// Read from calldata, code, return data, or block/account environment.
328    EnvironmentRead,
329    /// External call.
330    ExternalCall,
331    /// Internal MIR call.
332    InternalCall,
333    /// Contract creation.
334    Create,
335    /// Event emission.
336    Log,
337}
338
339impl EffectKind {
340    /// Returns the stable textual name used in MIR metadata.
341    #[must_use]
342    pub const fn name(&self) -> &'static str {
343        match self {
344            Self::Pure => "pure",
345            Self::MemoryRead => "memory_read",
346            Self::MemoryWrite => "memory_write",
347            Self::StorageRead => "storage_read",
348            Self::StorageWrite => "storage_write",
349            Self::TransientRead => "transient_read",
350            Self::TransientWrite => "transient_write",
351            Self::EnvironmentRead => "environment_read",
352            Self::ExternalCall => "external_call",
353            Self::InternalCall => "internal_call",
354            Self::Create => "create",
355            Self::Log => "log",
356        }
357    }
358}
359
360/// An instruction in the MIR.
361#[derive(Clone, Debug)]
362pub struct Instruction {
363    /// The kind of instruction.
364    pub kind: InstKind,
365    /// The result type (if any).
366    pub result_ty: Option<MirType>,
367    /// Metadata produced by lowering or analysis.
368    pub metadata: InstructionMetadata,
369}
370
371impl Instruction {
372    /// Creates a new instruction.
373    #[must_use]
374    pub const fn new(kind: InstKind, result_ty: Option<MirType>) -> Self {
375        Self { kind, result_ty, metadata: InstructionMetadata::EMPTY }
376    }
377
378    /// Returns the operands of this instruction.
379    #[must_use]
380    pub fn operands(&self) -> SmallVec<[ValueId; 8]> {
381        self.kind.operands()
382    }
383
384    /// Returns the metadata effect, or derives a conservative one from the instruction kind.
385    #[must_use]
386    pub fn effect_kind(&self) -> EffectKind {
387        self.metadata.effect().unwrap_or_else(|| self.kind.effect_kind())
388    }
389}
390
391/// The kind of an instruction.
392///
393/// TODO(codegen): Consider separating opcode and operands once the MIR shape stabilizes, e.g.
394/// `Instruction { opcode: Opcode, operands: SmallVec<[ValueId; 4]>, ... }`. That would make generic
395/// operand visitors and rewrites less variant-heavy.
396#[derive(Clone, Debug)]
397pub enum InstKind {
398    // Arithmetic operations
399    /// Addition: `a + b`
400    Add(ValueId, ValueId),
401    /// Subtraction: `a - b`
402    Sub(ValueId, ValueId),
403    /// Multiplication: `a * b`
404    Mul(ValueId, ValueId),
405    /// Unsigned division: `a / b`
406    Div(ValueId, ValueId),
407    /// Signed division: `a / b`
408    SDiv(ValueId, ValueId),
409    /// Unsigned modulo: `a % b`
410    Mod(ValueId, ValueId),
411    /// Signed modulo: `a % b`
412    SMod(ValueId, ValueId),
413    /// Exponentiation: `a ** b`
414    Exp(ValueId, ValueId),
415    /// Add modulo: `(a + b) % n`
416    AddMod(ValueId, ValueId, ValueId),
417    /// Multiply modulo: `(a * b) % n`
418    MulMod(ValueId, ValueId, ValueId),
419
420    // Bitwise operations
421    /// Bitwise AND: `a & b`
422    And(ValueId, ValueId),
423    /// Bitwise OR: `a | b`
424    Or(ValueId, ValueId),
425    /// Bitwise XOR: `a ^ b`
426    Xor(ValueId, ValueId),
427    /// Bitwise NOT: `~a`
428    Not(ValueId),
429    /// Left shift: `a << b`
430    Shl(ValueId, ValueId),
431    /// Logical right shift: `a >> b`
432    Shr(ValueId, ValueId),
433    /// Arithmetic right shift: `a >> b` (signed)
434    Sar(ValueId, ValueId),
435    /// Extract a byte: `byte(i, x)`
436    Byte(ValueId, ValueId),
437
438    // Comparison operations
439    /// Less than (unsigned): `a < b`
440    Lt(ValueId, ValueId),
441    /// Greater than (unsigned): `a > b`
442    Gt(ValueId, ValueId),
443    /// Less than (signed): `a < b`
444    SLt(ValueId, ValueId),
445    /// Greater than (signed): `a > b`
446    SGt(ValueId, ValueId),
447    /// Equality: `a == b`
448    Eq(ValueId, ValueId),
449    /// Check if zero: `a == 0`
450    IsZero(ValueId),
451
452    // Memory operations
453    /// Load from memory: `mload(offset)`
454    MLoad(ValueId),
455    /// Store to memory: `mstore(offset, value)`
456    MStore(ValueId, ValueId),
457    /// Store a single byte: `mstore8(offset, value)`
458    MStore8(ValueId, ValueId),
459    /// Get memory size: `msize()`
460    MSize,
461    /// Copy memory: `mcopy(dest, src, len)`
462    MCopy(ValueId, ValueId, ValueId),
463
464    // Storage operations
465    /// Load from storage: `sload(slot)`
466    SLoad(ValueId),
467    /// Store to storage: `sstore(slot, value)`
468    SStore(ValueId, ValueId),
469    /// Transient load: `tload(slot)`
470    TLoad(ValueId),
471    /// Transient store: `tstore(slot, value)`
472    TStore(ValueId, ValueId),
473
474    // Calldata operations
475    /// Load from calldata: `calldataload(offset)`
476    CalldataLoad(ValueId),
477    /// Copy calldata to memory: `calldatacopy(destOffset, offset, size)`
478    CalldataCopy(ValueId, ValueId, ValueId),
479    /// Get calldata size: `calldatasize()`
480    CalldataSize,
481    /// Address inside the current internal-call frame.
482    InternalFrameAddr(u64),
483
484    // Code operations
485    /// Get code size: `codesize()`
486    CodeSize,
487    /// Copy code to memory: `codecopy(destOffset, offset, size)`
488    CodeCopy(ValueId, ValueId, ValueId),
489    /// Get external code size: `extcodesize(addr)`
490    ExtCodeSize(ValueId),
491    /// Copy external code to memory: `extcodecopy(addr, destOffset, offset, size)`
492    ExtCodeCopy(ValueId, ValueId, ValueId, ValueId),
493    /// Get external code hash: `extcodehash(addr)`
494    ExtCodeHash(ValueId),
495    /// Read an immutable word identified by its byte offset: `loadimmutable <offset>`
496    ///
497    /// In runtime code this assembles to a `PUSH32` placeholder that the
498    /// constructor patches with the staged value before returning the runtime
499    /// code. In constructor code it reads the staged scratch word instead.
500    LoadImmutable(u32),
501
502    // Return data operations
503    /// Get return data size: `returndatasize()`
504    ReturnDataSize,
505    /// Copy return data to memory: `returndatacopy(destOffset, offset, size)`
506    ReturnDataCopy(ValueId, ValueId, ValueId),
507
508    // Environment operations
509    /// Get caller address: `caller()`
510    Caller,
511    /// Get call value: `callvalue()`
512    CallValue,
513    /// Get origin address: `origin()`
514    Origin,
515    /// Get gas price: `gasprice()`
516    GasPrice,
517    /// Get block hash: `blockhash(blockNum)`
518    BlockHash(ValueId),
519    /// Get coinbase address: `coinbase()`
520    Coinbase,
521    /// Get block timestamp: `timestamp()`
522    Timestamp,
523    /// Get block number: `number()`
524    BlockNumber,
525    /// Get previous randao: `prevrandao()`
526    PrevRandao,
527    /// Get gas limit: `gaslimit()`
528    GasLimit,
529    /// Get chain ID: `chainid()`
530    ChainId,
531    /// Get this contract's address: `address()`
532    Address,
533    /// Get balance: `balance(addr)`
534    Balance(ValueId),
535    /// Get self balance: `selfbalance()`
536    SelfBalance,
537    /// Get remaining gas: `gas()`
538    Gas,
539    /// Get base fee: `basefee()`
540    BaseFee,
541    /// Get blob base fee: `blobbasefee()`
542    BlobBaseFee,
543    /// Get blob hash: `blobhash(index)`
544    BlobHash(ValueId),
545
546    // Hashing
547    /// Keccak256 hash: `keccak256(offset, size)`
548    Keccak256(ValueId, ValueId),
549
550    // Call operations
551    // TODO(codegen): Consider unifying external calls as one instruction with a call-kind enum
552    // and shared operands once the MIR shape stabilizes.
553    /// External call: `call(gas, addr, value, argsOffset, argsSize, retOffset, retSize)`
554    Call {
555        gas: ValueId,
556        addr: ValueId,
557        value: ValueId,
558        args_offset: ValueId,
559        args_size: ValueId,
560        ret_offset: ValueId,
561        ret_size: ValueId,
562    },
563    /// Static call: `staticcall(gas, addr, argsOffset, argsSize, retOffset, retSize)`
564    StaticCall {
565        gas: ValueId,
566        addr: ValueId,
567        args_offset: ValueId,
568        args_size: ValueId,
569        ret_offset: ValueId,
570        ret_size: ValueId,
571    },
572    /// Delegate call: `delegatecall(gas, addr, argsOffset, argsSize, retOffset, retSize)`
573    DelegateCall {
574        gas: ValueId,
575        addr: ValueId,
576        args_offset: ValueId,
577        args_size: ValueId,
578        ret_offset: ValueId,
579        ret_size: ValueId,
580    },
581    /// Internal function call lowered to a direct jump.
582    InternalCall { function: FunctionId, args: Box<[ValueId]>, returns: u32 },
583
584    // Contract creation
585    /// Create contract: `create(value, offset, size)`
586    Create(ValueId, ValueId, ValueId),
587    /// Create2 contract: `create2(value, offset, size, salt)`
588    Create2(ValueId, ValueId, ValueId, ValueId),
589
590    // Log operations
591    // TODO(codegen): Consider unifying log0..log4 as one instruction with a topic list.
592    /// Log with no topics: `log0(offset, size)`
593    Log0(ValueId, ValueId),
594    /// Log with 1 topic: `log1(offset, size, topic1)`
595    Log1(ValueId, ValueId, ValueId),
596    /// Log with 2 topics: `log2(offset, size, topic1, topic2)`
597    Log2(ValueId, ValueId, ValueId, ValueId),
598    /// Log with 3 topics: `log3(offset, size, topic1, topic2, topic3)`
599    Log3(ValueId, ValueId, ValueId, ValueId, ValueId),
600    /// Log with 4 topics: `log4(offset, size, topic1, topic2, topic3, topic4)`
601    Log4(ValueId, ValueId, ValueId, ValueId, ValueId, ValueId),
602
603    // SSA operations
604    /// Phi node: merge values from different predecessors.
605    Phi(Vec<(BlockId, ValueId)>),
606    /// Select: `select(cond, true_val, false_val)`
607    Select(ValueId, ValueId, ValueId),
608
609    // Sign extension
610    /// Sign extend: `signextend(b, x)` - extends the sign bit from byte position b
611    SignExtend(ValueId, ValueId),
612}
613
614impl InstKind {
615    /// Collects all operands of this instruction into the provided vector.
616    /// This is the canonical way to get all operands for liveness analysis.
617    pub fn collect_operands(&self, out: &mut SmallVec<[ValueId; 8]>) {
618        match self {
619            // Binary operations
620            Self::Add(a, b)
621            | Self::Sub(a, b)
622            | Self::Mul(a, b)
623            | Self::Div(a, b)
624            | Self::SDiv(a, b)
625            | Self::Mod(a, b)
626            | Self::SMod(a, b)
627            | Self::Exp(a, b)
628            | Self::And(a, b)
629            | Self::Or(a, b)
630            | Self::Xor(a, b)
631            | Self::Shl(a, b)
632            | Self::Shr(a, b)
633            | Self::Sar(a, b)
634            | Self::Byte(a, b)
635            | Self::Lt(a, b)
636            | Self::Gt(a, b)
637            | Self::SLt(a, b)
638            | Self::SGt(a, b)
639            | Self::Eq(a, b)
640            | Self::MStore(a, b)
641            | Self::MStore8(a, b)
642            | Self::SStore(a, b)
643            | Self::TStore(a, b)
644            | Self::Keccak256(a, b)
645            | Self::Log0(a, b)
646            | Self::SignExtend(a, b) => {
647                out.push(*a);
648                out.push(*b);
649            }
650
651            // Unary operations
652            Self::Not(a)
653            | Self::IsZero(a)
654            | Self::MLoad(a)
655            | Self::SLoad(a)
656            | Self::TLoad(a)
657            | Self::CalldataLoad(a)
658            | Self::ExtCodeSize(a)
659            | Self::ExtCodeHash(a)
660            | Self::Balance(a)
661            | Self::BlockHash(a)
662            | Self::BlobHash(a) => {
663                out.push(*a);
664            }
665
666            // Ternary operations
667            Self::MCopy(a, b, c)
668            | Self::CalldataCopy(a, b, c)
669            | Self::CodeCopy(a, b, c)
670            | Self::ReturnDataCopy(a, b, c)
671            | Self::AddMod(a, b, c)
672            | Self::MulMod(a, b, c)
673            | Self::Create(a, b, c)
674            | Self::Log1(a, b, c)
675            | Self::Select(a, b, c) => {
676                out.push(*a);
677                out.push(*b);
678                out.push(*c);
679            }
680
681            // 4-operand operations
682            Self::ExtCodeCopy(a, b, c, d) | Self::Create2(a, b, c, d) | Self::Log2(a, b, c, d) => {
683                out.push(*a);
684                out.push(*b);
685                out.push(*c);
686                out.push(*d);
687            }
688
689            // 5-operand operations
690            Self::Log3(a, b, c, d, e) => {
691                out.push(*a);
692                out.push(*b);
693                out.push(*c);
694                out.push(*d);
695                out.push(*e);
696            }
697
698            // 6-operand operations
699            Self::Log4(a, b, c, d, e, f) => {
700                out.push(*a);
701                out.push(*b);
702                out.push(*c);
703                out.push(*d);
704                out.push(*e);
705                out.push(*f);
706            }
707
708            // Call operations
709            Self::Call { gas, addr, value, args_offset, args_size, ret_offset, ret_size } => {
710                out.push(*gas);
711                out.push(*addr);
712                out.push(*value);
713                out.push(*args_offset);
714                out.push(*args_size);
715                out.push(*ret_offset);
716                out.push(*ret_size);
717            }
718            Self::StaticCall { gas, addr, args_offset, args_size, ret_offset, ret_size } => {
719                out.push(*gas);
720                out.push(*addr);
721                out.push(*args_offset);
722                out.push(*args_size);
723                out.push(*ret_offset);
724                out.push(*ret_size);
725            }
726            Self::DelegateCall { gas, addr, args_offset, args_size, ret_offset, ret_size } => {
727                out.push(*gas);
728                out.push(*addr);
729                out.push(*args_offset);
730                out.push(*args_size);
731                out.push(*ret_offset);
732                out.push(*ret_size);
733            }
734            Self::InternalCall { args, .. } => {
735                out.extend(args.iter().copied());
736            }
737
738            // Phi node - operands are the incoming values
739            Self::Phi(incoming) => {
740                for (_, val) in incoming {
741                    out.push(*val);
742                }
743            }
744
745            // Nullary operations - no operands
746            Self::MSize
747            | Self::CalldataSize
748            | Self::InternalFrameAddr(_)
749            | Self::CodeSize
750            | Self::LoadImmutable(_)
751            | Self::ReturnDataSize
752            | Self::Caller
753            | Self::CallValue
754            | Self::Origin
755            | Self::GasPrice
756            | Self::Coinbase
757            | Self::Timestamp
758            | Self::BlockNumber
759            | Self::PrevRandao
760            | Self::GasLimit
761            | Self::ChainId
762            | Self::Address
763            | Self::SelfBalance
764            | Self::Gas
765            | Self::BaseFee
766            | Self::BlobBaseFee => {}
767        }
768    }
769
770    /// Returns the operands of this instruction.
771    #[must_use]
772    pub fn operands(&self) -> SmallVec<[ValueId; 8]> {
773        let mut out = SmallVec::new();
774        self.collect_operands(&mut out);
775        out
776    }
777
778    /// Visits every operand mutably.
779    pub fn visit_operands_mut(&mut self, mut f: impl FnMut(&mut ValueId)) {
780        match self {
781            Self::Add(a, b)
782            | Self::Sub(a, b)
783            | Self::Mul(a, b)
784            | Self::Div(a, b)
785            | Self::SDiv(a, b)
786            | Self::Mod(a, b)
787            | Self::SMod(a, b)
788            | Self::Exp(a, b)
789            | Self::And(a, b)
790            | Self::Or(a, b)
791            | Self::Xor(a, b)
792            | Self::Shl(a, b)
793            | Self::Shr(a, b)
794            | Self::Sar(a, b)
795            | Self::Byte(a, b)
796            | Self::Lt(a, b)
797            | Self::Gt(a, b)
798            | Self::SLt(a, b)
799            | Self::SGt(a, b)
800            | Self::Eq(a, b)
801            | Self::MStore(a, b)
802            | Self::MStore8(a, b)
803            | Self::SStore(a, b)
804            | Self::TStore(a, b)
805            | Self::Keccak256(a, b)
806            | Self::Log0(a, b)
807            | Self::SignExtend(a, b) => {
808                f(a);
809                f(b);
810            }
811
812            Self::Not(a)
813            | Self::IsZero(a)
814            | Self::MLoad(a)
815            | Self::SLoad(a)
816            | Self::TLoad(a)
817            | Self::CalldataLoad(a)
818            | Self::ExtCodeSize(a)
819            | Self::ExtCodeHash(a)
820            | Self::Balance(a)
821            | Self::BlockHash(a)
822            | Self::BlobHash(a) => f(a),
823
824            Self::MCopy(a, b, c)
825            | Self::CalldataCopy(a, b, c)
826            | Self::CodeCopy(a, b, c)
827            | Self::ReturnDataCopy(a, b, c)
828            | Self::AddMod(a, b, c)
829            | Self::MulMod(a, b, c)
830            | Self::Create(a, b, c)
831            | Self::Log1(a, b, c)
832            | Self::Select(a, b, c) => {
833                f(a);
834                f(b);
835                f(c);
836            }
837
838            Self::ExtCodeCopy(a, b, c, d) | Self::Create2(a, b, c, d) | Self::Log2(a, b, c, d) => {
839                f(a);
840                f(b);
841                f(c);
842                f(d);
843            }
844
845            Self::Log3(a, b, c, d, e) => {
846                f(a);
847                f(b);
848                f(c);
849                f(d);
850                f(e);
851            }
852
853            Self::Log4(a, b, c, d, e, g) => {
854                f(a);
855                f(b);
856                f(c);
857                f(d);
858                f(e);
859                f(g);
860            }
861
862            Self::Call { gas, addr, value, args_offset, args_size, ret_offset, ret_size } => {
863                f(gas);
864                f(addr);
865                f(value);
866                f(args_offset);
867                f(args_size);
868                f(ret_offset);
869                f(ret_size);
870            }
871            Self::StaticCall { gas, addr, args_offset, args_size, ret_offset, ret_size }
872            | Self::DelegateCall { gas, addr, args_offset, args_size, ret_offset, ret_size } => {
873                f(gas);
874                f(addr);
875                f(args_offset);
876                f(args_size);
877                f(ret_offset);
878                f(ret_size);
879            }
880            Self::InternalCall { args, .. } => {
881                for arg in args {
882                    f(arg);
883                }
884            }
885
886            Self::Phi(incoming) => {
887                for (_, value) in incoming {
888                    f(value);
889                }
890            }
891
892            Self::MSize
893            | Self::CalldataSize
894            | Self::InternalFrameAddr(_)
895            | Self::CodeSize
896            | Self::LoadImmutable(_)
897            | Self::ReturnDataSize
898            | Self::Caller
899            | Self::CallValue
900            | Self::Origin
901            | Self::GasPrice
902            | Self::Coinbase
903            | Self::Timestamp
904            | Self::BlockNumber
905            | Self::PrevRandao
906            | Self::GasLimit
907            | Self::ChainId
908            | Self::Address
909            | Self::SelfBalance
910            | Self::Gas
911            | Self::BaseFee
912            | Self::BlobBaseFee => {}
913        }
914    }
915
916    /// Returns true if this instruction may mutate persistent storage.
917    #[must_use]
918    pub const fn may_mutate_storage(&self) -> bool {
919        matches!(
920            self,
921            Self::SStore(_, _)
922                | Self::Call { .. }
923                | Self::DelegateCall { .. }
924                | Self::InternalCall { .. }
925                | Self::Create(_, _, _)
926                | Self::Create2(_, _, _, _)
927        )
928    }
929
930    /// Returns true if this instruction may mutate transient storage.
931    #[must_use]
932    pub const fn may_mutate_transient_storage(&self) -> bool {
933        matches!(
934            self,
935            Self::TStore(_, _)
936                | Self::Call { .. }
937                | Self::DelegateCall { .. }
938                | Self::InternalCall { .. }
939                | Self::Create(_, _, _)
940                | Self::Create2(_, _, _, _)
941        )
942    }
943
944    /// Returns true if this instruction writes or may write memory.
945    #[must_use]
946    pub const fn may_mutate_memory(&self) -> bool {
947        matches!(
948            self,
949            Self::MStore(_, _)
950                | Self::MStore8(_, _)
951                | Self::MCopy(_, _, _)
952                | Self::CalldataCopy(_, _, _)
953                | Self::CodeCopy(_, _, _)
954                | Self::ReturnDataCopy(_, _, _)
955                | Self::ExtCodeCopy(_, _, _, _)
956                | Self::Call { .. }
957                | Self::StaticCall { .. }
958                | Self::DelegateCall { .. }
959                | Self::InternalCall { .. }
960                | Self::Create(_, _, _)
961                | Self::Create2(_, _, _, _)
962        )
963    }
964
965    /// Returns the mnemonic for this instruction.
966    #[must_use]
967    pub const fn mnemonic(&self) -> &'static str {
968        match self {
969            Self::Add(_, _) => "add",
970            Self::Sub(_, _) => "sub",
971            Self::Mul(_, _) => "mul",
972            Self::Div(_, _) => "div",
973            Self::SDiv(_, _) => "sdiv",
974            Self::Mod(_, _) => "mod",
975            Self::SMod(_, _) => "smod",
976            Self::Exp(_, _) => "exp",
977            Self::AddMod(_, _, _) => "addmod",
978            Self::MulMod(_, _, _) => "mulmod",
979            Self::And(_, _) => "and",
980            Self::Or(_, _) => "or",
981            Self::Xor(_, _) => "xor",
982            Self::Not(_) => "not",
983            Self::Shl(_, _) => "shl",
984            Self::Shr(_, _) => "shr",
985            Self::Sar(_, _) => "sar",
986            Self::Byte(_, _) => "byte",
987            Self::Lt(_, _) => "lt",
988            Self::Gt(_, _) => "gt",
989            Self::SLt(_, _) => "slt",
990            Self::SGt(_, _) => "sgt",
991            Self::Eq(_, _) => "eq",
992            Self::IsZero(_) => "iszero",
993            Self::MLoad(_) => "mload",
994            Self::MStore(_, _) => "mstore",
995            Self::MStore8(_, _) => "mstore8",
996            Self::MSize => "msize",
997            Self::MCopy(_, _, _) => "mcopy",
998            Self::SLoad(_) => "sload",
999            Self::SStore(_, _) => "sstore",
1000            Self::TLoad(_) => "tload",
1001            Self::TStore(_, _) => "tstore",
1002            Self::CalldataLoad(_) => "calldataload",
1003            Self::CalldataCopy(_, _, _) => "calldatacopy",
1004            Self::CalldataSize => "calldatasize",
1005            Self::CodeSize => "codesize",
1006            Self::CodeCopy(_, _, _) => "codecopy",
1007            Self::LoadImmutable(_) => "loadimmutable",
1008            Self::ExtCodeSize(_) => "extcodesize",
1009            Self::ExtCodeCopy(_, _, _, _) => "extcodecopy",
1010            Self::ExtCodeHash(_) => "extcodehash",
1011            Self::ReturnDataSize => "returndatasize",
1012            Self::ReturnDataCopy(_, _, _) => "returndatacopy",
1013            Self::InternalFrameAddr(_) => "internal_frame_addr",
1014            Self::Caller => "caller",
1015            Self::CallValue => "callvalue",
1016            Self::Origin => "origin",
1017            Self::GasPrice => "gasprice",
1018            Self::BlockHash(_) => "blockhash",
1019            Self::Coinbase => "coinbase",
1020            Self::Timestamp => "timestamp",
1021            Self::BlockNumber => "number",
1022            Self::PrevRandao => "prevrandao",
1023            Self::GasLimit => "gaslimit",
1024            Self::ChainId => "chainid",
1025            Self::Address => "address",
1026            Self::Balance(_) => "balance",
1027            Self::SelfBalance => "selfbalance",
1028            Self::Gas => "gas",
1029            Self::BaseFee => "basefee",
1030            Self::BlobBaseFee => "blobbasefee",
1031            Self::BlobHash(_) => "blobhash",
1032            Self::Keccak256(_, _) => "keccak256",
1033            Self::Call { .. } => "call",
1034            Self::StaticCall { .. } => "staticcall",
1035            Self::DelegateCall { .. } => "delegatecall",
1036            Self::InternalCall { .. } => "internal_call",
1037            Self::Create(_, _, _) => "create",
1038            Self::Create2(_, _, _, _) => "create2",
1039            Self::Log0(_, _) => "log0",
1040            Self::Log1(_, _, _) => "log1",
1041            Self::Log2(_, _, _, _) => "log2",
1042            Self::Log3(_, _, _, _, _) => "log3",
1043            Self::Log4(_, _, _, _, _, _) => "log4",
1044            Self::Phi(_) => "phi",
1045            Self::Select(_, _, _) => "select",
1046            Self::SignExtend(_, _) => "signextend",
1047        }
1048    }
1049
1050    /// Returns true if this instruction has side effects.
1051    /// Side-effect instructions must not be eliminated by DCE.
1052    #[must_use]
1053    pub const fn has_side_effects(&self) -> bool {
1054        matches!(
1055            self,
1056            // Storage writes
1057            Self::SStore(_, _)
1058            | Self::TStore(_, _)
1059            // Memory writes (may affect external calls)
1060            | Self::MStore(_, _)
1061            | Self::MStore8(_, _)
1062            | Self::MCopy(_, _, _)
1063            // External calls
1064            | Self::Call { .. }
1065            | Self::StaticCall { .. }
1066            | Self::DelegateCall { .. }
1067            | Self::InternalCall { .. }
1068            // Contract creation
1069            | Self::Create(_, _, _)
1070            | Self::Create2(_, _, _, _)
1071            // Event emission
1072            | Self::Log0(_, _)
1073            | Self::Log1(_, _, _)
1074            | Self::Log2(_, _, _, _)
1075            | Self::Log3(_, _, _, _, _)
1076            | Self::Log4(_, _, _, _, _, _)
1077            // Data copy operations (write to memory)
1078            | Self::CalldataCopy(_, _, _)
1079            | Self::CodeCopy(_, _, _)
1080            | Self::ExtCodeCopy(_, _, _, _)
1081            | Self::ReturnDataCopy(_, _, _)
1082        )
1083    }
1084
1085    /// Returns a conservative effect classification for this instruction.
1086    #[must_use]
1087    pub const fn effect_kind(&self) -> EffectKind {
1088        match self {
1089            Self::MStore(_, _)
1090            | Self::MStore8(_, _)
1091            | Self::MCopy(_, _, _)
1092            | Self::CalldataCopy(_, _, _)
1093            | Self::CodeCopy(_, _, _)
1094            | Self::ExtCodeCopy(_, _, _, _)
1095            | Self::ReturnDataCopy(_, _, _) => EffectKind::MemoryWrite,
1096            Self::MLoad(_) | Self::MSize | Self::Keccak256(_, _) => EffectKind::MemoryRead,
1097            Self::SLoad(_) => EffectKind::StorageRead,
1098            Self::SStore(_, _) => EffectKind::StorageWrite,
1099            Self::TLoad(_) => EffectKind::TransientRead,
1100            Self::TStore(_, _) => EffectKind::TransientWrite,
1101            Self::Call { .. } | Self::StaticCall { .. } | Self::DelegateCall { .. } => {
1102                EffectKind::ExternalCall
1103            }
1104            Self::InternalCall { .. } => EffectKind::InternalCall,
1105            Self::Create(_, _, _) | Self::Create2(_, _, _, _) => EffectKind::Create,
1106            Self::Log0(_, _)
1107            | Self::Log1(_, _, _)
1108            | Self::Log2(_, _, _, _)
1109            | Self::Log3(_, _, _, _, _)
1110            | Self::Log4(_, _, _, _, _, _) => EffectKind::Log,
1111            Self::CalldataLoad(_)
1112            | Self::CalldataSize
1113            | Self::CodeSize
1114            | Self::LoadImmutable(_)
1115            | Self::ExtCodeSize(_)
1116            | Self::ExtCodeHash(_)
1117            | Self::ReturnDataSize
1118            | Self::Caller
1119            | Self::CallValue
1120            | Self::Origin
1121            | Self::GasPrice
1122            | Self::BlockHash(_)
1123            | Self::Coinbase
1124            | Self::Timestamp
1125            | Self::BlockNumber
1126            | Self::PrevRandao
1127            | Self::GasLimit
1128            | Self::ChainId
1129            | Self::Address
1130            | Self::Balance(_)
1131            | Self::SelfBalance
1132            | Self::Gas
1133            | Self::BaseFee
1134            | Self::BlobBaseFee
1135            | Self::BlobHash(_) => EffectKind::EnvironmentRead,
1136            Self::Add(_, _)
1137            | Self::Sub(_, _)
1138            | Self::Mul(_, _)
1139            | Self::Div(_, _)
1140            | Self::SDiv(_, _)
1141            | Self::Mod(_, _)
1142            | Self::SMod(_, _)
1143            | Self::Exp(_, _)
1144            | Self::AddMod(_, _, _)
1145            | Self::MulMod(_, _, _)
1146            | Self::And(_, _)
1147            | Self::Or(_, _)
1148            | Self::Xor(_, _)
1149            | Self::Not(_)
1150            | Self::Shl(_, _)
1151            | Self::Shr(_, _)
1152            | Self::Sar(_, _)
1153            | Self::Byte(_, _)
1154            | Self::Lt(_, _)
1155            | Self::Gt(_, _)
1156            | Self::SLt(_, _)
1157            | Self::SGt(_, _)
1158            | Self::Eq(_, _)
1159            | Self::IsZero(_)
1160            | Self::InternalFrameAddr(_)
1161            | Self::Phi(_)
1162            | Self::Select(_, _, _)
1163            | Self::SignExtend(_, _) => EffectKind::Pure,
1164        }
1165    }
1166}
1167
1168impl fmt::Display for InstKind {
1169    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1170        write!(f, "{}", self.mnemonic())
1171    }
1172}
1173
1174#[cfg(test)]
1175mod tests {
1176    use super::*;
1177    use crate::mir::{Function, Immediate, Value};
1178    use alloy_primitives::U256;
1179    use solar_interface::Ident;
1180
1181    #[test]
1182    fn phi_operands_include_incoming_values() {
1183        let mut func = Function::new(Ident::DUMMY);
1184        let pred_a = func.entry_block;
1185        let pred_b = func.alloc_block();
1186        let a = func.alloc_value(Value::Immediate(Immediate::uint256(U256::from(1))));
1187        let b = func.alloc_value(Value::Immediate(Immediate::uint256(U256::from(2))));
1188
1189        let phi = InstKind::Phi(vec![(pred_a, a), (pred_b, b)]);
1190
1191        assert_eq!(phi.operands().as_slice(), &[a, b]);
1192    }
1193
1194    #[test]
1195    #[cfg_attr(not(target_pointer_width = "64"), ignore = "64-bit only")]
1196    #[cfg_attr(feature = "nightly", ignore = "stable only")]
1197    fn instruction_layout_sizes() {
1198        use snapbox::{assert_data_eq, str};
1199
1200        #[track_caller]
1201        fn assert_size<T>(size: impl snapbox::IntoData) {
1202            assert_size_(std::mem::size_of::<T>(), size.into_data());
1203        }
1204
1205        #[track_caller]
1206        fn assert_size_(actual: usize, expected: snapbox::Data) {
1207            assert_data_eq!(actual.to_string(), expected);
1208        }
1209
1210        assert_size::<InstKind>(str!["32"]);
1211        assert_size::<InstructionMetadata>(str!["24"]);
1212        assert_size::<Instruction>(str!["64"]);
1213    }
1214}