Skip to main content

solar_codegen/backend/evm/ir/
display.rs

1//! EVM IR text formatting.
2
3use super::*;
4use solar_data_structures::fmt::FmtIteratorExt;
5
6impl EvmIrModule {
7    /// Returns the canonical EVM IR text-format representation.
8    pub fn to_text(&self) -> impl fmt::Display + '_ {
9        fmt::from_fn(move |f| {
10            write!(
11                f,
12                "{}",
13                self.blocks.iter_enumerated().format_with("", |f, (block_id, block)| {
14                    write!(f, "{}", display_block(self, block_id, block))
15                })
16            )
17        })
18    }
19}
20
21fn display_block<'a>(
22    module: &'a EvmIrModule,
23    block_id: EvmIrBlockId,
24    block: &'a EvmIrBlock,
25) -> impl fmt::Display + 'a {
26    fmt::from_fn(move |f| {
27        let entry = if module.entry_block == Some(block_id) { " (entry)" } else { "" };
28        let cold = if block.metadata.hotness == EvmIrBlockHotness::Cold { " [cold]" } else { "" };
29        write!(f, "{}{}{}", block.label, entry, cold)?;
30        if !block.entry_stack.is_empty() {
31            write!(f, " (in ")?;
32            for (i, &value) in block.entry_stack.iter().enumerate() {
33                if i != 0 {
34                    write!(f, ", ")?;
35                }
36                write!(f, "{}", display_value(module, value))?;
37            }
38            write!(f, ")")?;
39        }
40        writeln!(f, ":")?;
41        for inst in &block.instructions {
42            writeln!(f, "  {}", display_instruction(module, inst))?;
43        }
44        if let Some(term) = &block.terminator {
45            writeln!(f, "  {}", display_terminator(module, term))?;
46        }
47        Ok(())
48    })
49}
50
51fn display_instruction<'a>(
52    module: &'a EvmIrModule,
53    inst: &'a EvmIrInstruction,
54) -> impl fmt::Display + 'a {
55    fmt::from_fn(move |f| {
56        if let Some(result) = inst.result {
57            write!(f, "{} = ", display_value(module, result))?;
58        }
59        write!(f, "{}", inst.mnemonic())?;
60        if !inst.operands.is_empty() {
61            write!(
62                f,
63                " {}",
64                inst.operands.iter().map(|operand| display_operand(module, operand)).format(", ")
65            )?;
66        }
67        write!(
68            f,
69            "{}",
70            display_metadata(&inst.metadata, Some(default_instruction_stack_effect(inst)))
71        )
72    })
73}
74
75fn display_terminator<'a>(
76    module: &'a EvmIrModule,
77    term: &'a EvmIrTerminator,
78) -> impl fmt::Display + 'a {
79    fmt::from_fn(move |f| {
80        match &term.kind {
81            EvmIrTerminatorKind::Fallthrough(target) => {
82                write!(f, "fallthrough {}", display_block_id(module, *target))?;
83            }
84            EvmIrTerminatorKind::Jump(target) => {
85                write!(f, "jump {}", display_block_id(module, *target))?;
86            }
87            EvmIrTerminatorKind::Branch { condition, then_block, else_block } => {
88                write!(
89                    f,
90                    "br {}, {}, {}",
91                    display_operand(module, condition),
92                    display_block_id(module, *then_block),
93                    display_block_id(module, *else_block)
94                )?;
95            }
96            EvmIrTerminatorKind::Switch { value, default, cases } => {
97                write!(
98                    f,
99                    "switch {}, default {}, [",
100                    display_operand(module, value),
101                    display_block_id(module, *default)
102                )?;
103                write!(
104                    f,
105                    "{}",
106                    cases.iter().format_with(", ", |f, (case, target)| {
107                        write!(
108                            f,
109                            "{} => {}",
110                            display_operand(module, case),
111                            display_block_id(module, *target)
112                        )
113                    })
114                )?;
115                write!(f, "]")?;
116            }
117            EvmIrTerminatorKind::Return { offset, size } => {
118                write!(
119                    f,
120                    "return {}, {}",
121                    display_operand(module, offset),
122                    display_operand(module, size)
123                )?;
124            }
125            EvmIrTerminatorKind::Revert { offset, size } => {
126                write!(
127                    f,
128                    "revert {}, {}",
129                    display_operand(module, offset),
130                    display_operand(module, size)
131                )?;
132            }
133            EvmIrTerminatorKind::Stop => write!(f, "stop")?,
134            EvmIrTerminatorKind::Invalid => write!(f, "invalid")?,
135            EvmIrTerminatorKind::SelfDestruct { recipient } => {
136                write!(f, "selfdestruct {}", display_operand(module, recipient))?;
137            }
138            EvmIrTerminatorKind::RawOpcode(opcode) => {
139                write!(f, "terminal 0x{opcode:02x}")?;
140            }
141        }
142        write!(
143            f,
144            "{}",
145            display_metadata(&term.metadata, Some(default_terminator_stack_effect(&term.kind)))
146        )
147    })
148}
149
150fn display_metadata(
151    metadata: &EvmIrMetadata,
152    default_stack: Option<EvmIrStackEffect>,
153) -> impl fmt::Display + '_ {
154    enum Field<'a> {
155        Stack(EvmIrStackEffect),
156        Attr(&'a EvmIrMetadataItem),
157    }
158
159    fn display_field(field: Field<'_>) -> impl fmt::Display + '_ {
160        fmt::from_fn(move |f| match field {
161            Field::Stack(effect) => write!(f, "stack={}->{}", effect.inputs, effect.outputs),
162            Field::Attr(item) => {
163                write!(f, "{}", item.key)?;
164                if let Some(value) = &item.value {
165                    write!(f, "={value}")?;
166                }
167                Ok(())
168            }
169        })
170    }
171
172    fmt::from_fn(move |f| {
173        if metadata.is_empty() {
174            return Ok(());
175        }
176        let mut fields =
177            Vec::with_capacity(metadata.attrs.len() + usize::from(metadata.stack.is_some()));
178        if let Some(stack) = metadata.stack
179            && Some(stack) != default_stack
180        {
181            fields.push(Field::Stack(stack));
182        }
183        fields.extend(metadata.attrs.iter().map(Field::Attr));
184        if fields.is_empty() {
185            return Ok(());
186        }
187        write!(f, " !meta({})", fields.into_iter().map(display_field).format(", "))
188    })
189}
190
191fn display_operand<'a>(
192    module: &'a EvmIrModule,
193    operand: &'a EvmIrOperand,
194) -> impl fmt::Display + 'a {
195    fmt::from_fn(move |f| match operand {
196        EvmIrOperand::Value(value) => write!(f, "{}", display_value(module, *value)),
197        EvmIrOperand::Immediate(value) => write!(f, "{}", display_u256(*value)),
198        EvmIrOperand::Block(block) => write!(f, "{}", display_block_id(module, *block)),
199        EvmIrOperand::Symbol(symbol) => write!(f, "{symbol}"),
200    })
201}
202
203fn display_value(module: &EvmIrModule, value: EvmIrValueId) -> impl fmt::Display + '_ {
204    fmt::from_fn(move |f| write!(f, "%{}", module.values[value].name))
205}
206
207fn display_block_id(module: &EvmIrModule, block: EvmIrBlockId) -> impl fmt::Display + '_ {
208    fmt::from_fn(move |f| write!(f, "{}", module.blocks[block].label))
209}
210
211fn display_u256(value: U256) -> impl fmt::Display {
212    fmt::from_fn(move |f| {
213        if let Ok(value) = u64::try_from(value)
214            && value < 1000
215        {
216            write!(f, "{value}")
217        } else {
218            write!(f, "{value:#x}")
219        }
220    })
221}