Skip to main content

solar_codegen/mir/
parser.rs

1//! Parser for the textual MIR format produced by [`Function::to_text`] and [`Module::to_text`].
2//!
3//! # Format
4//!
5//! ```text
6//! ; module @Counter
7//! fn @increment() {
8//!   bb0 (entry):
9//!     v0 = sload 0
10//!     v1 = add v0, 1
11//!     sstore 0, v1
12//!     stop
13//! }
14//! ```
15//!
16//! # Session requirement
17//!
18//! Both [`parse_module`] and [`parse_function`] intern function and module
19//! names via [`Symbol::intern`], which requires an active
20//! [`solar_interface::Session`]. Wrap calls in `sess.enter(|| ...)`.
21//!
22//! # Caveats
23//!
24//! - This parser produces a *semantically* equivalent [`Function`]; the actual `ValueId` numbers in
25//!   the result may differ from the labels in the source text. Round-tripping `parse →
26//!   Function::to_text → parse` is supported, but the textual form may shift on the second print
27//!   (different v-numbers).
28//! - Address and fixed-bytes immediate literals are not currently parsed — they're allocated as
29//!   `Immediate::uint256(0)`. If you need them, extend `parse_value`.
30//! - Phi nodes are represented only as phi *instructions* (`InstKind::Phi`).
31
32use super::{
33    BasicBlock, BlockId, EffectKind, Function, FunctionId, InstKind, Instruction,
34    InstructionMetadata, MemoryRegion, Module, StorageAlias, Terminator, Value, ValueId,
35};
36use crate::mir::{Immediate, MirType};
37use alloy_primitives::U256;
38use solar_data_structures::map::FxHashMap;
39use solar_interface::{BytePos, Ident, Span, Symbol};
40use solar_sema::hir;
41use std::fmt;
42
43// =============================================================================
44// Public API
45// =============================================================================
46
47/// Parses a textual MIR module.
48///
49/// # Errors
50///
51/// Returns a [`ParseError`] if the input does not conform to the MIR
52/// textual format produced by [`Module::to_text`](super::Module::to_text).
53///
54/// # Session
55///
56/// Must be called inside an active `solar_interface::Session::enter`,
57/// because module and function names are interned via [`Symbol::intern`].
58pub fn parse_module(input: &str) -> Result<Module, ParseError> {
59    let mut p = Parser::new(input);
60    p.parse_module()
61}
62
63/// Parses a single textual MIR function.
64///
65/// # Errors
66///
67/// Returns a [`ParseError`] on malformed input.
68///
69/// # Session
70///
71/// Must be called inside an active `solar_interface::Session::enter`.
72pub fn parse_function(input: &str) -> Result<Function, ParseError> {
73    let mut p = Parser::new(input);
74    p.skip_blank_and_comments();
75    let func = p.parse_function()?;
76    p.skip_blank_and_comments();
77    if !p.is_eof() {
78        return Err(p.error("trailing input after function"));
79    }
80    Ok(func)
81}
82
83/// An error produced while parsing textual MIR.
84#[derive(Clone, Debug)]
85pub struct ParseError {
86    /// 1-based line number.
87    pub line: usize,
88    /// 1-based column number (codepoints, not bytes).
89    pub col: usize,
90    /// Human-readable message.
91    pub msg: String,
92    /// The offending source line (without trailing newline), captured at
93    /// the time the error was constructed. Used by [`fmt::Display`] to render
94    /// a rustc/clang-style snippet with a caret.
95    pub line_text: String,
96}
97
98impl fmt::Display for ParseError {
99    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100        writeln!(f, "MIR parse error at line {}, col {}: {}", self.line, self.col, self.msg)?;
101        if !self.line_text.is_empty() {
102            writeln!(f, "   |")?;
103            writeln!(f, "{:>3} | {}", self.line, self.line_text)?;
104            // Caret aligned under col-1 spaces. This isn't tab-aware, but
105            // the printer never produces tabs in MIR text so it's fine.
106            let caret_pad = " ".repeat(self.col.saturating_sub(1));
107            write!(f, "   | {caret_pad}^")?;
108        }
109        Ok(())
110    }
111}
112
113impl std::error::Error for ParseError {}
114
115// =============================================================================
116// Parser implementation
117// =============================================================================
118
119/// A simple line-and-column-tracking parser over a `&str`.
120struct Parser<'a> {
121    input: &'a str,
122    pos: usize,
123    line: usize,
124    col: usize,
125}
126
127impl<'a> Parser<'a> {
128    fn new(input: &'a str) -> Self {
129        Self { input, pos: 0, line: 1, col: 1 }
130    }
131
132    // ----- low-level cursor primitives -----
133
134    fn is_eof(&self) -> bool {
135        self.pos >= self.input.len()
136    }
137
138    fn peek_char(&self) -> Option<char> {
139        self.input[self.pos..].chars().next()
140    }
141
142    fn advance(&mut self) -> Option<char> {
143        let c = self.peek_char()?;
144        self.pos += c.len_utf8();
145        if c == '\n' {
146            self.line += 1;
147            self.col = 1;
148        } else {
149            self.col += 1;
150        }
151        Some(c)
152    }
153
154    fn skip_inline_whitespace(&mut self) {
155        while let Some(c) = self.peek_char() {
156            if c == ' ' || c == '\t' {
157                self.advance();
158            } else {
159                break;
160            }
161        }
162    }
163
164    /// Skip whitespace, newlines, and comments (`//...` and `;...`).
165    ///
166    /// Note: `;` is treated as a comment EXCEPT when followed by ` module @`,
167    /// which is the module header marker (`; module @Name`). This lets the
168    /// parser recover the module name even though `;` is otherwise comment-only.
169    fn skip_blank_and_comments(&mut self) {
170        loop {
171            self.skip_inline_whitespace();
172            match self.peek_char() {
173                Some('\n') | Some('\r') => {
174                    self.advance();
175                }
176                Some('/') if self.input[self.pos..].starts_with("//") => {
177                    self.skip_to_eol();
178                }
179                Some(';') => {
180                    // Don't eat the module header — let parse_module handle it.
181                    if self.input[self.pos..]
182                        .trim_start_matches(';')
183                        .trim_start()
184                        .starts_with("module")
185                    {
186                        break;
187                    }
188                    self.skip_to_eol();
189                }
190                _ => break,
191            }
192        }
193    }
194
195    fn skip_to_eol(&mut self) {
196        while let Some(c) = self.peek_char() {
197            if c == '\n' {
198                break;
199            }
200            self.advance();
201        }
202    }
203
204    fn error(&self, msg: impl Into<String>) -> ParseError {
205        ParseError {
206            line: self.line,
207            col: self.col,
208            msg: msg.into(),
209            line_text: self.current_line_text(),
210        }
211    }
212
213    /// Like [`Self::error`] but uses the supplied (line, col, pos) instead of
214    /// `self.line/col/pos`. Useful when the parser has already advanced past
215    /// the offending token (e.g. an unknown mnemonic) and we want the caret
216    /// to point back to its start.
217    fn error_at(&self, line: usize, col: usize, pos: usize, msg: impl Into<String>) -> ParseError {
218        // Capture the line text at `pos`, not at `self.pos`.
219        let bytes = self.input.as_bytes();
220        let p = pos.min(bytes.len());
221        let mut start = p;
222        while start > 0 && bytes[start - 1] != b'\n' {
223            start -= 1;
224        }
225        let mut end = start;
226        while end < bytes.len() && bytes[end] != b'\n' {
227            end += 1;
228        }
229        let line_text = self.input[start..end].trim_end_matches('\r').to_string();
230        ParseError { line, col, msg: msg.into(), line_text }
231    }
232
233    /// Returns the contents of the current source line (without the trailing
234    /// newline). Used by [`Self::error`] to populate
235    /// [`ParseError::line_text`] for snippet rendering.
236    fn current_line_text(&self) -> String {
237        let bytes = self.input.as_bytes();
238        let pos = self.pos.min(bytes.len());
239        // Walk backward to start-of-line.
240        let mut start = pos;
241        while start > 0 && bytes[start - 1] != b'\n' {
242            start -= 1;
243        }
244        // Walk forward to end-of-line.
245        let mut end = start;
246        while end < bytes.len() && bytes[end] != b'\n' {
247            end += 1;
248        }
249        self.input[start..end].trim_end_matches('\r').to_string()
250    }
251
252    // ----- token-level helpers -----
253
254    /// Skip non-newline whitespace.
255    /// Used between tokens *within* a logical line (instruction).
256    /// Inline comments are NOT supported on instruction lines (the printer
257    /// never produces them).
258    fn skip_inline(&mut self) {
259        self.skip_inline_whitespace();
260    }
261
262    /// Consume an exact literal string. Returns an error if it doesn't match.
263    fn expect_keyword(&mut self, kw: &str) -> Result<(), ParseError> {
264        self.skip_inline();
265        if self.input[self.pos..].starts_with(kw) {
266            for _ in 0..kw.chars().count() {
267                self.advance();
268            }
269            Ok(())
270        } else {
271            Err(self.error(format!("expected `{kw}`")))
272        }
273    }
274
275    /// Consume one of the given punctuation characters. Returns it on success.
276    fn expect_punct(&mut self, expected: char) -> Result<(), ParseError> {
277        self.skip_inline();
278        match self.peek_char() {
279            Some(c) if c == expected => {
280                self.advance();
281                Ok(())
282            }
283            Some(c) => Err(self.error(format!("expected `{expected}`, found `{c}`"))),
284            None => Err(self.error(format!("expected `{expected}`, found EOF"))),
285        }
286    }
287
288    /// Try to consume a punctuation character. Returns true on success.
289    fn try_punct(&mut self, expected: char) -> bool {
290        self.skip_inline();
291        if self.peek_char() == Some(expected) {
292            self.advance();
293            true
294        } else {
295            false
296        }
297    }
298
299    /// Consume an identifier: `[a-zA-Z_][a-zA-Z0-9_]*`.
300    fn parse_ident(&mut self) -> Result<&'a str, ParseError> {
301        self.skip_inline();
302        let start = self.pos;
303        match self.peek_char() {
304            Some(c) if c.is_ascii_alphabetic() || c == '_' => {
305                self.advance();
306            }
307            _ => return Err(self.error("expected identifier")),
308        }
309        while let Some(c) = self.peek_char() {
310            if c.is_ascii_alphanumeric() || c == '_' {
311                self.advance();
312            } else {
313                break;
314            }
315        }
316        Ok(&self.input[start..self.pos])
317    }
318
319    /// Consume an unsigned integer literal: decimal `123` or hex `0xABC`.
320    fn parse_uint_literal(&mut self) -> Result<U256, ParseError> {
321        self.skip_inline();
322        let start = self.pos;
323        if self.input[self.pos..].starts_with("0x") || self.input[self.pos..].starts_with("0X") {
324            self.advance();
325            self.advance();
326            while let Some(c) = self.peek_char() {
327                if c.is_ascii_hexdigit() {
328                    self.advance();
329                } else {
330                    break;
331                }
332            }
333            let s = &self.input[start..self.pos];
334            U256::from_str_radix(&s[2..], 16).map_err(|e| self.error(format!("invalid hex: {e}")))
335        } else if matches!(self.peek_char(), Some(c) if c.is_ascii_digit()) {
336            while let Some(c) = self.peek_char() {
337                if c.is_ascii_digit() {
338                    self.advance();
339                } else {
340                    break;
341                }
342            }
343            let s = &self.input[start..self.pos];
344            s.parse::<U256>().map_err(|e| self.error(format!("invalid integer: {e}")))
345        } else {
346            Err(self.error("expected integer literal"))
347        }
348    }
349
350    // ----- module / function parsing -----
351
352    fn parse_module(&mut self) -> Result<Module, ParseError> {
353        self.skip_blank_and_comments();
354
355        // Optional `; module @Name` header (the printer always emits it,
356        // but we accept input without it).
357        let module_name = if self.try_punct(';') {
358            self.expect_keyword("module")?;
359            self.skip_inline();
360            self.expect_punct('@')?;
361            let name = self.parse_ident()?.to_string();
362            self.skip_to_eol();
363            self.skip_blank_and_comments();
364            name
365        } else {
366            "module".to_string()
367        };
368
369        let module_ident = Ident::with_dummy_span(Symbol::intern(&module_name));
370        let mut module = Module::new(module_ident);
371
372        while !self.is_eof() {
373            self.skip_blank_and_comments();
374            if self.is_eof() {
375                break;
376            }
377            // Skip stray `// === ... ===` comment headers between functions.
378            if self.input[self.pos..].starts_with("//") {
379                self.skip_to_eol();
380                continue;
381            }
382            let func = self.parse_function()?;
383            module.add_function(func);
384        }
385
386        Ok(module)
387    }
388
389    fn parse_function(&mut self) -> Result<Function, ParseError> {
390        self.skip_blank_and_comments();
391        self.expect_keyword("fn")?;
392        self.skip_inline();
393        self.expect_punct('@')?;
394        let name = self.parse_ident()?.to_string();
395        let func_ident = Ident::with_dummy_span(Symbol::intern(&name));
396        let mut func = Function::new(func_ident);
397
398        // Parse parameters: `(arg0: ty, arg1: ty, ...)` or `()`
399        self.expect_punct('(')?;
400        let mut arg_values: Vec<ValueId> = Vec::new();
401        if !self.try_punct(')') {
402            loop {
403                let arg_name = self.parse_ident()?;
404                if !arg_name.starts_with("arg") {
405                    return Err(self.error(format!("expected `argN`, got `{arg_name}`")));
406                }
407                let _idx: u32 = arg_name[3..]
408                    .parse()
409                    .map_err(|_| self.error(format!("invalid arg index in `{arg_name}`")))?;
410                self.expect_punct(':')?;
411                let ty = self.parse_type()?;
412                let index = func.params.len() as u32;
413                func.params.push(ty);
414                let val = func.alloc_value(Value::Arg { index, ty });
415                arg_values.push(val);
416                if self.try_punct(',') {
417                    continue;
418                }
419                self.expect_punct(')')?;
420                break;
421            }
422        }
423
424        // Optional return type: `-> ty` or `-> (ty, ty, ...)`
425        self.skip_inline();
426        if self.input[self.pos..].starts_with("->") {
427            self.advance();
428            self.advance();
429            self.skip_inline();
430            if self.try_punct('(') {
431                if !self.try_punct(')') {
432                    loop {
433                        let ty = self.parse_type()?;
434                        func.returns.push(ty);
435                        if self.try_punct(',') {
436                            continue;
437                        }
438                        self.expect_punct(')')?;
439                        break;
440                    }
441                }
442            } else {
443                let ty = self.parse_type()?;
444                func.returns.push(ty);
445            }
446        }
447
448        self.parse_function_attributes(&mut func)?;
449        self.expect_punct('{')?;
450        self.skip_blank_and_comments();
451
452        // Two-pass: first scan for all `bbN:` headers to pre-allocate
453        // BlockIds (so jumps to later blocks resolve correctly).
454        let mut block_labels: FxHashMap<u32, BlockId> = FxHashMap::default();
455
456        // Save position so we can rewind after the scan.
457        let scan_start = self.pos;
458        let scan_line = self.line;
459        let scan_col = self.col;
460
461        // First pass: walk lines, find `bbN:` patterns.
462        let mut first_block: Option<u32> = None;
463        loop {
464            self.skip_blank_and_comments();
465            if self.is_eof() {
466                return Err(self.error("unterminated function body"));
467            }
468            if self.peek_char() == Some('}') {
469                break;
470            }
471            // Try to parse a block header at the start of a line.
472            let line_start_pos = self.pos;
473            let line_start_line = self.line;
474            let line_start_col = self.col;
475            self.skip_inline_whitespace();
476            if let Some('b') = self.peek_char()
477                && self.input[self.pos..].starts_with("bb")
478            {
479                let save = self.pos;
480                let save_line = self.line;
481                let save_col = self.col;
482                self.advance();
483                self.advance();
484                let mut idx_str = String::new();
485                while let Some(c) = self.peek_char() {
486                    if c.is_ascii_digit() {
487                        idx_str.push(c);
488                        self.advance();
489                    } else {
490                        break;
491                    }
492                }
493                if idx_str.is_empty() {
494                    self.pos = save;
495                    self.line = save_line;
496                    self.col = save_col;
497                } else {
498                    // Skip optional ` (entry)` marker
499                    self.skip_inline_whitespace();
500                    if self.peek_char() == Some('(') {
501                        while let Some(c) = self.peek_char() {
502                            self.advance();
503                            if c == ')' {
504                                break;
505                            }
506                        }
507                    }
508                    if self.peek_char() == Some(':') {
509                        let idx: u32 =
510                            idx_str.parse().map_err(|_| self.error("invalid block index"))?;
511                        if first_block.is_none() {
512                            first_block = Some(idx);
513                        }
514                        // Don't allocate the entry block again — Function::new
515                        // already created bb0. We need to map any first-encountered
516                        // bbN to the entry block's id.
517                        if block_labels.is_empty() {
518                            block_labels.insert(idx, func.entry_block);
519                        } else {
520                            let id = func.alloc_block();
521                            block_labels.insert(idx, id);
522                        }
523                        // Skip to end of line; the `:` consumes block header.
524                        self.advance();
525                        self.skip_to_eol();
526                        continue;
527                    } else {
528                        self.pos = save;
529                        self.line = save_line;
530                        self.col = save_col;
531                    }
532                }
533            }
534            // Not a block header — restore to start of line and skip the line.
535            self.pos = line_start_pos;
536            self.line = line_start_line;
537            self.col = line_start_col;
538            self.skip_to_eol();
539        }
540
541        // Rewind to start of function body for the real parsing pass.
542        self.pos = scan_start;
543        self.line = scan_line;
544        self.col = scan_col;
545
546        // Second pass: parse blocks, instructions, and terminators.
547        let mut value_labels: FxHashMap<u32, ValueId> = FxHashMap::default();
548        let mut current_block: Option<BlockId> = None;
549
550        loop {
551            self.skip_blank_and_comments();
552            if self.is_eof() {
553                return Err(self.error("unterminated function body"));
554            }
555            if self.try_punct('}') {
556                break;
557            }
558
559            // Try to parse a block header.
560            self.skip_inline_whitespace();
561            if self.input[self.pos..].starts_with("bb") {
562                let save = self.pos;
563                let save_line = self.line;
564                let save_col = self.col;
565                self.advance();
566                self.advance();
567                let mut idx_str = String::new();
568                while let Some(c) = self.peek_char() {
569                    if c.is_ascii_digit() {
570                        idx_str.push(c);
571                        self.advance();
572                    } else {
573                        break;
574                    }
575                }
576                if !idx_str.is_empty() {
577                    self.skip_inline_whitespace();
578                    // Optional ` (entry)`
579                    if self.peek_char() == Some('(') {
580                        while let Some(c) = self.peek_char() {
581                            self.advance();
582                            if c == ')' {
583                                break;
584                            }
585                        }
586                        self.skip_inline_whitespace();
587                    }
588                    if self.peek_char() == Some(':') {
589                        self.advance();
590                        let idx: u32 = idx_str.parse().unwrap();
591                        let bid = *block_labels
592                            .get(&idx)
593                            .ok_or_else(|| self.error(format!("unknown block bb{idx}")))?;
594                        current_block = Some(bid);
595                        self.skip_to_eol();
596                        continue;
597                    }
598                    self.pos = save;
599                    self.line = save_line;
600                    self.col = save_col;
601                } else {
602                    self.pos = save;
603                    self.line = save_line;
604                    self.col = save_col;
605                }
606            }
607
608            // Not a block header — must be an instruction or terminator.
609            let block =
610                current_block.ok_or_else(|| self.error("instruction outside of any block"))?;
611            self.parse_instruction_or_terminator(
612                &mut func,
613                block,
614                &arg_values,
615                &block_labels,
616                &mut value_labels,
617            )?;
618        }
619
620        self.reject_unresolved_value_labels(&func, &value_labels)?;
621
622        Ok(func)
623    }
624
625    fn parse_function_attributes(&mut self, func: &mut Function) -> Result<(), ParseError> {
626        self.skip_inline();
627        if !self.try_punct('[') {
628            return Ok(());
629        }
630
631        loop {
632            let key = self.parse_ident()?.to_string();
633            match key.as_str() {
634                "selector" => {
635                    self.expect_punct('=')?;
636                    let selector = self.parse_uint_literal()?;
637                    let selector = self.u256_to_u32(selector)?;
638                    func.selector = Some(selector.to_be_bytes());
639                }
640                _ => return Err(self.error(format!("unknown function attribute `{key}`"))),
641            }
642
643            if self.try_punct(',') {
644                continue;
645            }
646            self.expect_punct(']')?;
647            break;
648        }
649
650        Ok(())
651    }
652
653    fn parse_type(&mut self) -> Result<MirType, ParseError> {
654        self.skip_inline();
655        let id = self.parse_ident()?;
656        // u8..u256, i8..i256, bytes1..bytes32 — split into prefix + number.
657        let ty = if let Some(rest) = id.strip_prefix('u') {
658            let bits: u16 =
659                rest.parse().map_err(|_| self.error(format!("invalid u-type `{id}`")))?;
660            MirType::UInt(bits)
661        } else if let Some(rest) = id.strip_prefix('i') {
662            let bits: u16 =
663                rest.parse().map_err(|_| self.error(format!("invalid i-type `{id}`")))?;
664            MirType::Int(bits)
665        } else if let Some(rest) = id.strip_prefix("bytes") {
666            let n: u8 =
667                rest.parse().map_err(|_| self.error(format!("invalid bytes type `{id}`")))?;
668            MirType::FixedBytes(n)
669        } else {
670            match id {
671                "bool" => MirType::Bool,
672                "address" => MirType::Address,
673                "memptr" => MirType::MemPtr,
674                "storageptr" => MirType::StoragePtr,
675                "calldataptr" => MirType::CalldataPtr,
676                "function" => MirType::Function,
677                "void" => MirType::Void,
678                _ => return Err(self.error(format!("unknown type `{id}`"))),
679            }
680        };
681        Ok(ty)
682    }
683
684    /// Parses a single value reference: `argN`, `vN`, integer literal, or `true`/`false`.
685    /// Allocates a fresh `Immediate` for literals.
686    fn parse_value(
687        &mut self,
688        func: &mut Function,
689        arg_values: &[ValueId],
690        value_labels: &mut FxHashMap<u32, ValueId>,
691    ) -> Result<ValueId, ParseError> {
692        self.skip_inline();
693        // Integer literal? (decimal or 0x…)
694        if matches!(self.peek_char(), Some(c) if c.is_ascii_digit()) {
695            let v = self.parse_uint_literal()?;
696            return Ok(func.alloc_value(Value::Immediate(Immediate::uint256(v))));
697        }
698        // Identifier-like — could be argN, vN, true, false.
699        let ident = self.parse_ident()?;
700        if ident == "true" {
701            return Ok(func.alloc_value(Value::Immediate(Immediate::bool(true))));
702        }
703        if ident == "false" {
704            return Ok(func.alloc_value(Value::Immediate(Immediate::bool(false))));
705        }
706        if ident == "err" {
707            // Reconstructing an already-reported error state from text: there
708            // is no live diagnostic to propagate here.
709            let guar = solar_interface::diagnostics::ErrorGuaranteed::new_unchecked();
710            return Ok(func.alloc_value(Value::Error(guar)));
711        }
712        if let Some(rest) = ident.strip_prefix("arg") {
713            let idx: usize =
714                rest.parse().map_err(|_| self.error(format!("invalid arg `{ident}`")))?;
715            return arg_values
716                .get(idx)
717                .copied()
718                .ok_or_else(|| self.error(format!("arg{idx} out of range")));
719        }
720        if let Some(rest) = ident.strip_prefix('v') {
721            let idx: u32 = rest
722                .parse()
723                .map_err(|_| self.error(format!("invalid value reference `{ident}`")))?;
724            if let Some(value) = value_labels.get(&idx).copied() {
725                return Ok(value);
726            }
727            let value = func.alloc_value(Value::Undef(MirType::uint256()));
728            value_labels.insert(idx, value);
729            return Ok(value);
730        }
731        Err(self.error(format!("expected value reference, got `{ident}`")))
732    }
733
734    fn resolve_result_label(
735        &self,
736        func: &mut Function,
737        value_labels: &mut FxHashMap<u32, ValueId>,
738        label: u32,
739        inst_id: super::InstId,
740    ) -> Result<(), ParseError> {
741        if let Some(value) = value_labels.get(&label).copied() {
742            if matches!(func.values[value], Value::Undef(_)) {
743                func.values[value] = Value::Inst(inst_id);
744                return Ok(());
745            }
746            return Err(self.error(format!("duplicate value `v{label}`")));
747        }
748
749        let value = func.alloc_value(Value::Inst(inst_id));
750        value_labels.insert(label, value);
751        Ok(())
752    }
753
754    fn reject_unresolved_value_labels(
755        &self,
756        func: &Function,
757        value_labels: &FxHashMap<u32, ValueId>,
758    ) -> Result<(), ParseError> {
759        let mut unresolved: Vec<_> = value_labels
760            .iter()
761            .filter_map(|(&label, &value)| {
762                matches!(func.values[value], Value::Undef(_)).then_some(label)
763            })
764            .collect();
765        unresolved.sort_unstable();
766        if let Some(label) = unresolved.first() {
767            return Err(self.error(format!("undefined value `v{label}`")));
768        }
769        Ok(())
770    }
771
772    fn parse_block_id(
773        &mut self,
774        block_labels: &FxHashMap<u32, BlockId>,
775    ) -> Result<BlockId, ParseError> {
776        self.skip_inline();
777        let id = self.parse_ident()?;
778        let rest = id
779            .strip_prefix("bb")
780            .ok_or_else(|| self.error(format!("expected `bbN`, got `{id}`")))?;
781        let idx: u32 =
782            rest.parse().map_err(|_| self.error(format!("invalid block index `{id}`")))?;
783        block_labels.get(&idx).copied().ok_or_else(|| self.error(format!("unknown block `{id}`")))
784    }
785
786    fn parse_function_id(&mut self) -> Result<FunctionId, ParseError> {
787        self.skip_inline();
788        let id = self.parse_ident()?;
789        let rest = id
790            .strip_prefix("fn")
791            .ok_or_else(|| self.error(format!("expected `fnN`, got `{id}`")))?;
792        let idx: usize =
793            rest.parse().map_err(|_| self.error(format!("invalid function index `{id}`")))?;
794        Ok(FunctionId::from_usize(idx))
795    }
796
797    /// Parses one instruction line (with optional `vN =` result) or a terminator.
798    fn parse_instruction_or_terminator(
799        &mut self,
800        func: &mut Function,
801        block: BlockId,
802        arg_values: &[ValueId],
803        block_labels: &FxHashMap<u32, BlockId>,
804        value_labels: &mut FxHashMap<u32, ValueId>,
805    ) -> Result<(), ParseError> {
806        self.skip_inline_whitespace();
807
808        // Optional result: `vN = ...`
809        let result_label: Option<u32> = if self.input[self.pos..].starts_with('v')
810            && self.input[self.pos..].chars().nth(1).is_some_and(|c| c.is_ascii_digit())
811        {
812            // Try to parse as `vN =`. If no `=` follows, it's a terminator using vN.
813            let save_pos = self.pos;
814            let save_line = self.line;
815            let save_col = self.col;
816            self.advance();
817            let mut idx_str = String::new();
818            while let Some(c) = self.peek_char() {
819                if c.is_ascii_digit() {
820                    idx_str.push(c);
821                    self.advance();
822                } else {
823                    break;
824                }
825            }
826            self.skip_inline_whitespace();
827            if self.peek_char() == Some('=') {
828                self.advance();
829                Some(idx_str.parse().unwrap())
830            } else {
831                self.pos = save_pos;
832                self.line = save_line;
833                self.col = save_col;
834                None
835            }
836        } else {
837            None
838        };
839
840        self.skip_inline_whitespace();
841        // Save the position of the mnemonic so we can produce a snippet
842        // pointing at it (instead of just past it) if it turns out to be
843        // unknown.
844        let mnemonic_line = self.line;
845        let mnemonic_col = self.col;
846        let mnemonic_pos = self.pos;
847        let mnemonic = self.parse_ident()?.to_string();
848
849        // Terminators (no result).
850        match mnemonic.as_str() {
851            "jump" => {
852                let target = self.parse_block_id(block_labels)?;
853                self.set_terminator(func, block, Terminator::Jump(target));
854                self.skip_to_eol();
855                return Ok(());
856            }
857            "br" => {
858                let condition = self.parse_value(func, arg_values, value_labels)?;
859                self.expect_punct(',')?;
860                let then_block = self.parse_block_id(block_labels)?;
861                self.expect_punct(',')?;
862                let else_block = self.parse_block_id(block_labels)?;
863                self.set_terminator(
864                    func,
865                    block,
866                    Terminator::Branch { condition, then_block, else_block },
867                );
868                self.skip_to_eol();
869                return Ok(());
870            }
871            "switch" => {
872                let value = self.parse_value(func, arg_values, value_labels)?;
873                self.expect_punct(',')?;
874                self.expect_keyword("default")?;
875                let default = self.parse_block_id(block_labels)?;
876                self.expect_punct(',')?;
877                self.expect_punct('[')?;
878                let mut cases = Vec::new();
879                if !self.try_punct(']') {
880                    loop {
881                        let val = self.parse_value(func, arg_values, value_labels)?;
882                        self.expect_keyword("=>")?;
883                        let bid = self.parse_block_id(block_labels)?;
884                        cases.push((val, bid));
885                        if self.try_punct(',') {
886                            continue;
887                        }
888                        self.expect_punct(']')?;
889                        break;
890                    }
891                }
892                self.set_terminator(func, block, Terminator::Switch { value, default, cases });
893                self.skip_to_eol();
894                return Ok(());
895            }
896            "ret" => {
897                use smallvec::SmallVec;
898                let mut values: SmallVec<[ValueId; 2]> = SmallVec::new();
899                self.skip_inline_whitespace();
900                // Empty ret?
901                if self.peek_char() != Some('\n') && !self.is_eof() {
902                    loop {
903                        values.push(self.parse_value(func, arg_values, value_labels)?);
904                        if !self.try_punct(',') {
905                            break;
906                        }
907                    }
908                }
909                self.set_terminator(func, block, Terminator::Return { values });
910                self.skip_to_eol();
911                return Ok(());
912            }
913            "revert" => {
914                let offset = self.parse_value(func, arg_values, value_labels)?;
915                self.expect_punct(',')?;
916                let size = self.parse_value(func, arg_values, value_labels)?;
917                self.set_terminator(func, block, Terminator::Revert { offset, size });
918                self.skip_to_eol();
919                return Ok(());
920            }
921            "returndata" => {
922                let offset = self.parse_value(func, arg_values, value_labels)?;
923                self.expect_punct(',')?;
924                let size = self.parse_value(func, arg_values, value_labels)?;
925                self.set_terminator(func, block, Terminator::ReturnData { offset, size });
926                self.skip_to_eol();
927                return Ok(());
928            }
929            "stop" => {
930                self.set_terminator(func, block, Terminator::Stop);
931                self.skip_to_eol();
932                return Ok(());
933            }
934            "selfdestruct" => {
935                let recipient = self.parse_value(func, arg_values, value_labels)?;
936                self.set_terminator(func, block, Terminator::SelfDestruct { recipient });
937                self.skip_to_eol();
938                return Ok(());
939            }
940            "invalid" => {
941                self.set_terminator(func, block, Terminator::Invalid);
942                self.skip_to_eol();
943                return Ok(());
944            }
945            _ => {}
946        }
947
948        // Otherwise — instruction.
949        let (kind, result_ty) = self
950            .parse_inst_kind(&mnemonic, func, arg_values, block_labels, value_labels)
951            .map_err(|e| {
952                // For "unknown instruction" errors, repoint the caret at the
953                // start of the mnemonic instead of after it.
954                if e.msg.starts_with("unknown instruction") {
955                    self.error_at(mnemonic_line, mnemonic_col, mnemonic_pos, e.msg)
956                } else {
957                    e
958                }
959            })?;
960
961        let metadata = self.parse_metadata(func, arg_values, value_labels)?;
962        let mut inst = Instruction::new(kind, result_ty);
963        inst.metadata = metadata;
964        let inst_id = func.alloc_inst(inst);
965        func.blocks[block].instructions.push(inst_id);
966        if let Some(label) = result_label {
967            self.resolve_result_label(func, value_labels, label, inst_id)?;
968        }
969        self.skip_to_eol();
970        Ok(())
971    }
972
973    fn parse_metadata(
974        &mut self,
975        func: &mut Function,
976        arg_values: &[ValueId],
977        value_labels: &mut FxHashMap<u32, ValueId>,
978    ) -> Result<InstructionMetadata, ParseError> {
979        let mut metadata = InstructionMetadata::EMPTY;
980        self.skip_inline();
981        if !self.try_punct('!') {
982            return Ok(metadata);
983        }
984        self.expect_keyword("metadata")?;
985        self.expect_punct('(')?;
986        if self.try_punct(')') {
987            return Ok(metadata);
988        }
989
990        loop {
991            let key = self.parse_ident()?.to_string();
992            match key.as_str() {
993                "unchecked" => {
994                    metadata.set_unchecked(true);
995                }
996                "storage" => {
997                    self.expect_punct('=')?;
998                    metadata.set_storage_alias(Some(self.parse_storage_alias(
999                        func,
1000                        arg_values,
1001                        value_labels,
1002                    )?));
1003                }
1004                "memory" => {
1005                    self.expect_punct('=')?;
1006                    let value = self.parse_ident()?;
1007                    metadata.set_memory_region(Some(self.parse_memory_region(value)?));
1008                }
1009                "effect" => {
1010                    self.expect_punct('=')?;
1011                    let value = self.parse_ident()?;
1012                    metadata.set_effect(Some(self.parse_effect_kind(value)?));
1013                }
1014                "loop_depth" => {
1015                    self.expect_punct('=')?;
1016                    let value = self.parse_uint_literal()?;
1017                    metadata.loop_depth = self.u256_to_u16(value)?;
1018                }
1019                "hir" => {
1020                    self.expect_punct('=')?;
1021                    let value = self.parse_uint_literal()?;
1022                    metadata.set_hir_expr(Some(hir::ExprId::from_usize(
1023                        self.u256_to_u32(value)? as usize
1024                    )));
1025                }
1026                "span" => {
1027                    self.expect_punct('=')?;
1028                    let lo = self.parse_uint_literal()?;
1029                    let lo = self.u256_to_u32(lo)?;
1030                    self.expect_punct('.')?;
1031                    self.expect_punct('.')?;
1032                    let hi = self.parse_uint_literal()?;
1033                    let hi = self.u256_to_u32(hi)?;
1034                    metadata.set_source_span(Some(Span::new(BytePos(lo), BytePos(hi))));
1035                }
1036                _ => return Err(self.error(format!("unknown metadata key `{key}`"))),
1037            }
1038
1039            if self.try_punct(',') {
1040                continue;
1041            }
1042            self.expect_punct(')')?;
1043            break;
1044        }
1045
1046        Ok(metadata)
1047    }
1048
1049    fn parse_storage_alias(
1050        &mut self,
1051        func: &mut Function,
1052        arg_values: &[ValueId],
1053        value_labels: &mut FxHashMap<u32, ValueId>,
1054    ) -> Result<StorageAlias, ParseError> {
1055        let kind = self.parse_ident()?.to_string();
1056        self.expect_punct('(')?;
1057        let alias = match kind.as_str() {
1058            "slot" => StorageAlias::Slot(self.parse_uint_literal()?),
1059            "symbolic" => {
1060                StorageAlias::Symbolic(self.parse_value(func, arg_values, value_labels)?)
1061            }
1062            "offset" => {
1063                let base = self.parse_value(func, arg_values, value_labels)?;
1064                self.expect_punct(',')?;
1065                let offset = self.parse_uint_literal()?;
1066                StorageAlias::Offset { base, offset }
1067            }
1068            _ => return Err(self.error(format!("unknown storage metadata value `{kind}`"))),
1069        };
1070        self.expect_punct(')')?;
1071        Ok(alias)
1072    }
1073
1074    fn parse_memory_region(&self, value: &str) -> Result<MemoryRegion, ParseError> {
1075        Ok(match value {
1076            "scratch" => MemoryRegion::Scratch,
1077            "abi_return" => MemoryRegion::AbiReturn,
1078            "heap" => MemoryRegion::Heap,
1079            "internal_frame" => MemoryRegion::InternalFrame,
1080            "unknown" => MemoryRegion::Unknown,
1081            _ => return Err(self.error(format!("unknown memory metadata value `{value}`"))),
1082        })
1083    }
1084
1085    fn parse_effect_kind(&self, value: &str) -> Result<EffectKind, ParseError> {
1086        Ok(match value {
1087            "pure" => EffectKind::Pure,
1088            "memory_read" => EffectKind::MemoryRead,
1089            "memory_write" => EffectKind::MemoryWrite,
1090            "storage_read" => EffectKind::StorageRead,
1091            "storage_write" => EffectKind::StorageWrite,
1092            "transient_read" => EffectKind::TransientRead,
1093            "transient_write" => EffectKind::TransientWrite,
1094            "environment_read" => EffectKind::EnvironmentRead,
1095            "external_call" => EffectKind::ExternalCall,
1096            "internal_call" => EffectKind::InternalCall,
1097            "create" => EffectKind::Create,
1098            "log" => EffectKind::Log,
1099            _ => return Err(self.error(format!("unknown effect metadata value `{value}`"))),
1100        })
1101    }
1102
1103    fn u256_to_u32(&self, value: U256) -> Result<u32, ParseError> {
1104        value.try_into().map_err(|_| self.error(format!("integer `{value}` does not fit in u32")))
1105    }
1106
1107    fn u256_to_u16(&self, value: U256) -> Result<u16, ParseError> {
1108        value.try_into().map_err(|_| self.error(format!("integer `{value}` does not fit in u16")))
1109    }
1110
1111    fn set_terminator(&self, func: &mut Function, block: BlockId, term: Terminator) {
1112        // Update predecessors so downstream passes see a valid CFG.
1113        let succs = term.successors();
1114        for s in succs {
1115            func.blocks[s].predecessors.push(block);
1116        }
1117        func.blocks[block].terminator = Some(term);
1118    }
1119
1120    /// Parses one instruction by mnemonic. Returns the constructed [`InstKind`]
1121    /// and its result type (or `None` if it produces no value).
1122    #[allow(clippy::too_many_lines)]
1123    fn parse_inst_kind(
1124        &mut self,
1125        mnemonic: &str,
1126        func: &mut Function,
1127        arg_values: &[ValueId],
1128        block_labels: &FxHashMap<u32, BlockId>,
1129        value_labels: &mut FxHashMap<u32, ValueId>,
1130    ) -> Result<(InstKind, Option<MirType>), ParseError> {
1131        // Operand parsing helpers.
1132        macro_rules! v {
1133            () => {
1134                self.parse_value(func, arg_values, value_labels)?
1135            };
1136        }
1137        macro_rules! comma {
1138            () => {
1139                self.expect_punct(',')?
1140            };
1141        }
1142
1143        Ok(match mnemonic {
1144            // ----- arithmetic (all uint256 result) -----
1145            "add" => {
1146                let a = v!();
1147                comma!();
1148                let b = v!();
1149                (InstKind::Add(a, b), Some(MirType::uint256()))
1150            }
1151            "sub" => {
1152                let a = v!();
1153                comma!();
1154                let b = v!();
1155                (InstKind::Sub(a, b), Some(MirType::uint256()))
1156            }
1157            "mul" => {
1158                let a = v!();
1159                comma!();
1160                let b = v!();
1161                (InstKind::Mul(a, b), Some(MirType::uint256()))
1162            }
1163            "div" => {
1164                let a = v!();
1165                comma!();
1166                let b = v!();
1167                (InstKind::Div(a, b), Some(MirType::uint256()))
1168            }
1169            "sdiv" => {
1170                let a = v!();
1171                comma!();
1172                let b = v!();
1173                (InstKind::SDiv(a, b), Some(MirType::int256()))
1174            }
1175            "mod" => {
1176                let a = v!();
1177                comma!();
1178                let b = v!();
1179                (InstKind::Mod(a, b), Some(MirType::uint256()))
1180            }
1181            "smod" => {
1182                let a = v!();
1183                comma!();
1184                let b = v!();
1185                (InstKind::SMod(a, b), Some(MirType::int256()))
1186            }
1187            "exp" => {
1188                let a = v!();
1189                comma!();
1190                let b = v!();
1191                (InstKind::Exp(a, b), Some(MirType::uint256()))
1192            }
1193            "addmod" => {
1194                let a = v!();
1195                comma!();
1196                let b = v!();
1197                comma!();
1198                let c = v!();
1199                (InstKind::AddMod(a, b, c), Some(MirType::uint256()))
1200            }
1201            "mulmod" => {
1202                let a = v!();
1203                comma!();
1204                let b = v!();
1205                comma!();
1206                let c = v!();
1207                (InstKind::MulMod(a, b, c), Some(MirType::uint256()))
1208            }
1209
1210            // ----- bitwise -----
1211            "and" => {
1212                let a = v!();
1213                comma!();
1214                let b = v!();
1215                (InstKind::And(a, b), Some(MirType::uint256()))
1216            }
1217            "or" => {
1218                let a = v!();
1219                comma!();
1220                let b = v!();
1221                (InstKind::Or(a, b), Some(MirType::uint256()))
1222            }
1223            "xor" => {
1224                let a = v!();
1225                comma!();
1226                let b = v!();
1227                (InstKind::Xor(a, b), Some(MirType::uint256()))
1228            }
1229            "not" => {
1230                let a = v!();
1231                (InstKind::Not(a), Some(MirType::uint256()))
1232            }
1233            "shl" => {
1234                let a = v!();
1235                comma!();
1236                let b = v!();
1237                (InstKind::Shl(a, b), Some(MirType::uint256()))
1238            }
1239            "shr" => {
1240                let a = v!();
1241                comma!();
1242                let b = v!();
1243                (InstKind::Shr(a, b), Some(MirType::uint256()))
1244            }
1245            "sar" => {
1246                let a = v!();
1247                comma!();
1248                let b = v!();
1249                (InstKind::Sar(a, b), Some(MirType::int256()))
1250            }
1251            "byte" => {
1252                let a = v!();
1253                comma!();
1254                let b = v!();
1255                (InstKind::Byte(a, b), Some(MirType::uint256()))
1256            }
1257            "signextend" => {
1258                let a = v!();
1259                comma!();
1260                let b = v!();
1261                (InstKind::SignExtend(a, b), Some(MirType::int256()))
1262            }
1263
1264            // ----- comparison -----
1265            "lt" => {
1266                let a = v!();
1267                comma!();
1268                let b = v!();
1269                (InstKind::Lt(a, b), Some(MirType::Bool))
1270            }
1271            "gt" => {
1272                let a = v!();
1273                comma!();
1274                let b = v!();
1275                (InstKind::Gt(a, b), Some(MirType::Bool))
1276            }
1277            "slt" => {
1278                let a = v!();
1279                comma!();
1280                let b = v!();
1281                (InstKind::SLt(a, b), Some(MirType::Bool))
1282            }
1283            "sgt" => {
1284                let a = v!();
1285                comma!();
1286                let b = v!();
1287                (InstKind::SGt(a, b), Some(MirType::Bool))
1288            }
1289            "eq" => {
1290                let a = v!();
1291                comma!();
1292                let b = v!();
1293                (InstKind::Eq(a, b), Some(MirType::Bool))
1294            }
1295            "iszero" => {
1296                let a = v!();
1297                (InstKind::IsZero(a), Some(MirType::Bool))
1298            }
1299
1300            // ----- memory -----
1301            "mload" => {
1302                let a = v!();
1303                (InstKind::MLoad(a), Some(MirType::uint256()))
1304            }
1305            "mstore" => {
1306                let a = v!();
1307                comma!();
1308                let b = v!();
1309                (InstKind::MStore(a, b), None)
1310            }
1311            "mstore8" => {
1312                let a = v!();
1313                comma!();
1314                let b = v!();
1315                (InstKind::MStore8(a, b), None)
1316            }
1317            "msize" => (InstKind::MSize, Some(MirType::uint256())),
1318            "mcopy" => {
1319                let a = v!();
1320                comma!();
1321                let b = v!();
1322                comma!();
1323                let c = v!();
1324                (InstKind::MCopy(a, b, c), None)
1325            }
1326
1327            // ----- storage -----
1328            "sload" => {
1329                let a = v!();
1330                (InstKind::SLoad(a), Some(MirType::uint256()))
1331            }
1332            "sstore" => {
1333                let a = v!();
1334                comma!();
1335                let b = v!();
1336                (InstKind::SStore(a, b), None)
1337            }
1338            "tload" => {
1339                let a = v!();
1340                (InstKind::TLoad(a), Some(MirType::uint256()))
1341            }
1342            "tstore" => {
1343                let a = v!();
1344                comma!();
1345                let b = v!();
1346                (InstKind::TStore(a, b), None)
1347            }
1348
1349            // ----- calldata -----
1350            "calldataload" => {
1351                let a = v!();
1352                (InstKind::CalldataLoad(a), Some(MirType::uint256()))
1353            }
1354            "calldatasize" => (InstKind::CalldataSize, Some(MirType::uint256())),
1355            "calldatacopy" => {
1356                let a = v!();
1357                comma!();
1358                let b = v!();
1359                comma!();
1360                let c = v!();
1361                (InstKind::CalldataCopy(a, b, c), None)
1362            }
1363
1364            // ----- code -----
1365            "codesize" => (InstKind::CodeSize, Some(MirType::uint256())),
1366            "codecopy" => {
1367                let a = v!();
1368                comma!();
1369                let b = v!();
1370                comma!();
1371                let c = v!();
1372                (InstKind::CodeCopy(a, b, c), None)
1373            }
1374            "loadimmutable" => {
1375                let offset = self.parse_uint_literal()?;
1376                let offset = self.u256_to_u32(offset)?;
1377                (InstKind::LoadImmutable(offset), Some(MirType::uint256()))
1378            }
1379            "extcodesize" => {
1380                let a = v!();
1381                (InstKind::ExtCodeSize(a), Some(MirType::uint256()))
1382            }
1383            "extcodecopy" => {
1384                let a = v!();
1385                comma!();
1386                let b = v!();
1387                comma!();
1388                let c = v!();
1389                comma!();
1390                let d = v!();
1391                (InstKind::ExtCodeCopy(a, b, c, d), None)
1392            }
1393            "extcodehash" => {
1394                let a = v!();
1395                (InstKind::ExtCodeHash(a), Some(MirType::uint256()))
1396            }
1397
1398            // ----- return data -----
1399            "returndatasize" => (InstKind::ReturnDataSize, Some(MirType::uint256())),
1400            "returndatacopy" => {
1401                let a = v!();
1402                comma!();
1403                let b = v!();
1404                comma!();
1405                let c = v!();
1406                (InstKind::ReturnDataCopy(a, b, c), None)
1407            }
1408
1409            // ----- environment (nullary) -----
1410            "caller" => (InstKind::Caller, Some(MirType::Address)),
1411            "callvalue" => (InstKind::CallValue, Some(MirType::uint256())),
1412            "origin" => (InstKind::Origin, Some(MirType::Address)),
1413            "gasprice" => (InstKind::GasPrice, Some(MirType::uint256())),
1414            "coinbase" => (InstKind::Coinbase, Some(MirType::Address)),
1415            "timestamp" => (InstKind::Timestamp, Some(MirType::uint256())),
1416            "number" => (InstKind::BlockNumber, Some(MirType::uint256())),
1417            "prevrandao" => (InstKind::PrevRandao, Some(MirType::uint256())),
1418            "gaslimit" => (InstKind::GasLimit, Some(MirType::uint256())),
1419            "chainid" => (InstKind::ChainId, Some(MirType::uint256())),
1420            "address" => (InstKind::Address, Some(MirType::Address)),
1421            "selfbalance" => (InstKind::SelfBalance, Some(MirType::uint256())),
1422            "gas" => (InstKind::Gas, Some(MirType::uint256())),
1423            "basefee" => (InstKind::BaseFee, Some(MirType::uint256())),
1424            "blobbasefee" => (InstKind::BlobBaseFee, Some(MirType::uint256())),
1425
1426            // ----- environment (unary) -----
1427            "blockhash" => {
1428                let a = v!();
1429                (InstKind::BlockHash(a), Some(MirType::FixedBytes(32)))
1430            }
1431            "balance" => {
1432                let a = v!();
1433                (InstKind::Balance(a), Some(MirType::uint256()))
1434            }
1435            "blobhash" => {
1436                let a = v!();
1437                (InstKind::BlobHash(a), Some(MirType::FixedBytes(32)))
1438            }
1439
1440            // ----- hashing -----
1441            "keccak256" => {
1442                let a = v!();
1443                comma!();
1444                let b = v!();
1445                (InstKind::Keccak256(a, b), Some(MirType::bytes32()))
1446            }
1447
1448            // ----- calls -----
1449            "call" => {
1450                let gas = v!();
1451                comma!();
1452                let addr = v!();
1453                comma!();
1454                let value = v!();
1455                comma!();
1456                let args_offset = v!();
1457                comma!();
1458                let args_size = v!();
1459                comma!();
1460                let ret_offset = v!();
1461                comma!();
1462                let ret_size = v!();
1463                (
1464                    InstKind::Call {
1465                        gas,
1466                        addr,
1467                        value,
1468                        args_offset,
1469                        args_size,
1470                        ret_offset,
1471                        ret_size,
1472                    },
1473                    Some(MirType::uint256()),
1474                )
1475            }
1476            "staticcall" => {
1477                let gas = v!();
1478                comma!();
1479                let addr = v!();
1480                comma!();
1481                let args_offset = v!();
1482                comma!();
1483                let args_size = v!();
1484                comma!();
1485                let ret_offset = v!();
1486                comma!();
1487                let ret_size = v!();
1488                (
1489                    InstKind::StaticCall {
1490                        gas,
1491                        addr,
1492                        args_offset,
1493                        args_size,
1494                        ret_offset,
1495                        ret_size,
1496                    },
1497                    Some(MirType::uint256()),
1498                )
1499            }
1500            "delegatecall" => {
1501                let gas = v!();
1502                comma!();
1503                let addr = v!();
1504                comma!();
1505                let args_offset = v!();
1506                comma!();
1507                let args_size = v!();
1508                comma!();
1509                let ret_offset = v!();
1510                comma!();
1511                let ret_size = v!();
1512                (
1513                    InstKind::DelegateCall {
1514                        gas,
1515                        addr,
1516                        args_offset,
1517                        args_size,
1518                        ret_offset,
1519                        ret_size,
1520                    },
1521                    Some(MirType::uint256()),
1522                )
1523            }
1524            "internal_call" => {
1525                let function = self.parse_function_id()?;
1526                comma!();
1527                let returns = self.parse_uint_literal()?.to::<u32>();
1528                let mut args = Vec::new();
1529                while self.try_punct(',') {
1530                    args.push(v!());
1531                }
1532                let result_ty = (returns > 0).then(MirType::uint256);
1533                (InstKind::InternalCall { function, args: args.into(), returns }, result_ty)
1534            }
1535            "internal_frame_addr" => {
1536                let offset = self.parse_uint_literal()?.to::<u64>();
1537                (InstKind::InternalFrameAddr(offset), Some(MirType::MemPtr))
1538            }
1539
1540            // ----- create -----
1541            "create" => {
1542                let a = v!();
1543                comma!();
1544                let b = v!();
1545                comma!();
1546                let c = v!();
1547                (InstKind::Create(a, b, c), Some(MirType::Address))
1548            }
1549            "create2" => {
1550                let a = v!();
1551                comma!();
1552                let b = v!();
1553                comma!();
1554                let c = v!();
1555                comma!();
1556                let d = v!();
1557                (InstKind::Create2(a, b, c, d), Some(MirType::Address))
1558            }
1559
1560            // ----- logs -----
1561            "log0" => {
1562                let a = v!();
1563                comma!();
1564                let b = v!();
1565                (InstKind::Log0(a, b), None)
1566            }
1567            "log1" => {
1568                let a = v!();
1569                comma!();
1570                let b = v!();
1571                comma!();
1572                let c = v!();
1573                (InstKind::Log1(a, b, c), None)
1574            }
1575            "log2" => {
1576                let a = v!();
1577                comma!();
1578                let b = v!();
1579                comma!();
1580                let c = v!();
1581                comma!();
1582                let d = v!();
1583                (InstKind::Log2(a, b, c, d), None)
1584            }
1585            "log3" => {
1586                let a = v!();
1587                comma!();
1588                let b = v!();
1589                comma!();
1590                let c = v!();
1591                comma!();
1592                let d = v!();
1593                comma!();
1594                let e = v!();
1595                (InstKind::Log3(a, b, c, d, e), None)
1596            }
1597            "log4" => {
1598                let a = v!();
1599                comma!();
1600                let b = v!();
1601                comma!();
1602                let c = v!();
1603                comma!();
1604                let d = v!();
1605                comma!();
1606                let e = v!();
1607                comma!();
1608                let f = v!();
1609                (InstKind::Log4(a, b, c, d, e, f), None)
1610            }
1611
1612            // ----- ssa -----
1613            "select" => {
1614                let cond = v!();
1615                comma!();
1616                let then_v = v!();
1617                comma!();
1618                let else_v = v!();
1619                (InstKind::Select(cond, then_v, else_v), Some(MirType::uint256()))
1620            }
1621            "phi" => {
1622                // Format: `phi [bb0: v1], [bb1: v2]` — matches the printer in display.rs.
1623                // Each pair is `[blockId: valueId]` separated by commas.
1624                let mut incoming: Vec<(BlockId, ValueId)> = Vec::new();
1625                loop {
1626                    self.expect_punct('[')?;
1627                    let bid = self.parse_block_id(block_labels)?;
1628                    self.expect_punct(':')?;
1629                    let val = v!();
1630                    self.expect_punct(']')?;
1631                    incoming.push((bid, val));
1632                    if !self.try_punct(',') {
1633                        break;
1634                    }
1635                }
1636                (InstKind::Phi(incoming), Some(MirType::uint256()))
1637            }
1638
1639            other => return Err(self.error(format!("unknown instruction `{other}`"))),
1640        })
1641    }
1642}
1643
1644// =============================================================================
1645// Suppress the unused `BasicBlock` import warning when this module is built
1646// without tests. The struct is used transitively through `Function`, but the
1647// import keeps the file self-documenting.
1648// =============================================================================
1649
1650#[allow(dead_code)]
1651const _BLOCK_TYPE_REFERENCE: Option<BasicBlock> = None;
1652
1653// =============================================================================
1654// Tests
1655// =============================================================================
1656
1657#[cfg(test)]
1658mod tests {
1659    use super::*;
1660    use solar_interface::{ColorChoice, Session};
1661
1662    fn with_session<F: FnOnce() + Send>(f: F) {
1663        let sess = Session::builder().with_buffer_emitter(ColorChoice::Never).build();
1664        sess.enter(f);
1665    }
1666
1667    #[test]
1668    fn parse_linear_function() {
1669        with_session(|| {
1670            let src = "\
1671fn @add(arg0: u256, arg1: u256) -> u256 {
1672  bb0 (entry):
1673    v2 = add arg0, arg1
1674    ret v2
1675}
1676";
1677            let func = parse_function(src).unwrap();
1678            assert_eq!(func.blocks.len(), 1);
1679            assert_eq!(func.params.len(), 2);
1680            assert_eq!(func.returns.len(), 1);
1681            // Round-trip: print and re-parse should not error.
1682            let printed = func.to_text().to_string();
1683            let _func2 = parse_function(&printed).unwrap();
1684        });
1685    }
1686
1687    #[test]
1688    fn parse_storage_ops() {
1689        with_session(|| {
1690            let src = "\
1691fn @increment() {
1692  bb0 (entry):
1693    v0 = sload 0
1694    v1 = add v0, 1
1695    sstore 0, v1
1696    stop
1697}
1698";
1699            let func = parse_function(src).unwrap();
1700            assert_eq!(func.blocks.len(), 1);
1701            assert_eq!(func.params.len(), 0);
1702            // sstore + stop are the only "no result" things; sload, add produce results.
1703            // So we expect 4 instructions total.
1704            assert_eq!(func.instructions.len(), 3);
1705            // 0 + 1 are immediates; v0, v1 are inst results.
1706            assert!(func.values.len() >= 4);
1707        });
1708    }
1709
1710    #[test]
1711    fn parse_branch() {
1712        with_session(|| {
1713            let src = "\
1714fn @max(arg0: u256, arg1: u256) -> u256 {
1715  bb0 (entry):
1716    v2 = gt arg0, arg1
1717    br v2, bb1, bb2
1718  bb1:
1719    ret arg0
1720  bb2:
1721    ret arg1
1722}
1723";
1724            let func = parse_function(src).unwrap();
1725            assert_eq!(func.blocks.len(), 3);
1726            // bb0 should have 2 successors.
1727            assert_eq!(func.blocks[func.entry_block].terminator().unwrap().successors().len(), 2);
1728        });
1729    }
1730
1731    #[test]
1732    fn parse_loop_with_jump() {
1733        with_session(|| {
1734            let src = "\
1735fn @count_down(arg0: u256) -> u256 {
1736  bb0 (entry):
1737    jump bb1
1738  bb1:
1739    v1 = lt 0, arg0
1740    br v1, bb2, bb3
1741  bb2:
1742    jump bb1
1743  bb3:
1744    ret arg0
1745}
1746";
1747            let func = parse_function(src).unwrap();
1748            assert_eq!(func.blocks.len(), 4);
1749        });
1750    }
1751
1752    #[test]
1753    fn parse_call_instruction() {
1754        with_session(|| {
1755            let src = "\
1756fn @do_call(arg0: address, arg1: u256) -> u256 {
1757  bb0 (entry):
1758    v2 = call 100, arg0, arg1, 0, 0, 0, 0
1759    ret v2
1760}
1761";
1762            let func = parse_function(src).unwrap();
1763            assert_eq!(func.instructions.len(), 1);
1764        });
1765    }
1766
1767    #[test]
1768    fn parse_keccak_and_mload_mstore() {
1769        with_session(|| {
1770            let src = "\
1771fn @hash() -> u256 {
1772  bb0 (entry):
1773    mstore 0, 1
1774    mstore 32, 2
1775    v1 = keccak256 0, 64
1776    ret v1
1777}
1778";
1779            let func = parse_function(src).unwrap();
1780            assert_eq!(func.instructions.len(), 3);
1781        });
1782    }
1783
1784    #[test]
1785    fn parse_round_trip_module() {
1786        with_session(|| {
1787            let src = "\
1788; module @Counter
1789fn @count() -> u256 {
1790  bb0 (entry):
1791    v1 = sload 0
1792    ret v1
1793}
1794
1795fn @set(arg0: u256) {
1796  bb0 (entry):
1797    sstore 0, arg0
1798    stop
1799}
1800";
1801            let module = parse_module(src).unwrap();
1802            assert_eq!(module.functions.len(), 2);
1803            // Round-trip the printed form.
1804            let printed = module.to_text().to_string();
1805            let module2 = parse_module(&printed).unwrap();
1806            assert_eq!(module2.functions.len(), 2);
1807        });
1808    }
1809
1810    #[test]
1811    fn parse_unknown_instruction_errors() {
1812        with_session(|| {
1813            let src = "\
1814fn @bad() {
1815  bb0 (entry):
1816    v1 = bogus arg0
1817    stop
1818}
1819";
1820            let err = parse_function(src).unwrap_err();
1821            assert!(err.msg.contains("bogus") || err.msg.contains("unknown"));
1822        });
1823    }
1824
1825    #[test]
1826    fn error_includes_source_snippet() {
1827        with_session(|| {
1828            let src = "\
1829fn @bad() {
1830  bb0 (entry):
1831    v1 = bogus arg0
1832    stop
1833}
1834";
1835            let err = parse_function(src).unwrap_err();
1836            // line_text should contain the offending line.
1837            assert!(err.line_text.contains("bogus arg0"), "got line_text: {:?}", err.line_text);
1838            // Display should include both the line and a caret marker.
1839            let formatted = err.to_string();
1840            assert!(formatted.contains("bogus"), "missing line in:\n{formatted}");
1841            assert!(formatted.contains("|"), "missing snippet bar in:\n{formatted}");
1842            assert!(formatted.contains("^"), "missing caret in:\n{formatted}");
1843            // The line number should appear in the snippet header.
1844            assert!(formatted.contains(&format!("{} | ", err.line)), "missing line number");
1845        });
1846    }
1847
1848    #[test]
1849    fn error_snippet_format_is_clang_like() {
1850        // Verify the precise format users will see, end-to-end.
1851        with_session(|| {
1852            let src = "fn @x() -> notatype {\n  bb0 (entry):\n    stop\n}\n";
1853            let err = parse_function(src).unwrap_err();
1854            let formatted = err.to_string();
1855            // Roughly:
1856            //   MIR parse error at line 1, col N: ...
1857            //      |
1858            //    1 | fn @x() -> notatype {
1859            //      |            ^
1860            assert!(formatted.starts_with("MIR parse error at line "));
1861            assert!(formatted.contains("\n   |\n"));
1862            assert!(formatted.contains("notatype"));
1863        });
1864    }
1865
1866    #[test]
1867    fn parse_revert_terminator() {
1868        with_session(|| {
1869            let src = "\
1870fn @oops() {
1871  bb0 (entry):
1872    revert 0, 0
1873}
1874";
1875            let func = parse_function(src).unwrap();
1876            assert!(matches!(
1877                func.blocks[func.entry_block].terminator,
1878                Some(Terminator::Revert { .. })
1879            ));
1880        });
1881    }
1882
1883    #[test]
1884    fn parse_environment_nullary() {
1885        with_session(|| {
1886            let src = "\
1887fn @env() -> u256 {
1888  bb0 (entry):
1889    v0 = caller
1890    v1 = callvalue
1891    v2 = gas
1892    v3 = chainid
1893    ret v3
1894}
1895";
1896            let func = parse_function(src).unwrap();
1897            assert_eq!(func.instructions.len(), 4);
1898        });
1899    }
1900
1901    #[test]
1902    fn parse_select_and_logs() {
1903        with_session(|| {
1904            let src = "\
1905fn @sel(arg0: bool, arg1: u256, arg2: u256) -> u256 {
1906  bb0 (entry):
1907    v3 = select arg0, arg1, arg2
1908    log1 0, 32, v3
1909    ret v3
1910}
1911";
1912            let func = parse_function(src).unwrap();
1913            assert_eq!(func.instructions.len(), 2);
1914        });
1915    }
1916
1917    #[test]
1918    fn parse_phi_node() {
1919        with_session(|| {
1920            let src = "\
1921fn @diamond(arg0: bool) -> u256 {
1922  bb0 (entry):
1923    br arg0, bb1, bb2
1924  bb1:
1925    jump bb3
1926  bb2:
1927    jump bb3
1928  bb3:
1929    v1 = phi [bb1: 10], [bb2: 20]
1930    ret v1
1931}
1932";
1933            let func = parse_function(src).unwrap();
1934            assert_eq!(func.blocks.len(), 4);
1935            // Find the phi instruction.
1936            let phi_inst =
1937                func.instructions.iter().find(|i| matches!(i.kind, InstKind::Phi(_))).unwrap();
1938            if let InstKind::Phi(args) = &phi_inst.kind {
1939                assert_eq!(args.len(), 2);
1940            } else {
1941                panic!("expected phi");
1942            }
1943        });
1944    }
1945
1946    #[test]
1947    fn parse_switch_terminator() {
1948        with_session(|| {
1949            let src = "\
1950fn @dispatch(arg0: u256) -> u256 {
1951  bb0 (entry):
1952    switch arg0, default bb4, [1 => bb1, 2 => bb2, 3 => bb3]
1953  bb1:
1954    ret arg0
1955  bb2:
1956    ret 0
1957  bb3:
1958    ret 1
1959  bb4:
1960    ret 2
1961}
1962";
1963            let func = parse_function(src).unwrap();
1964            assert_eq!(func.blocks.len(), 5);
1965            let term = func.blocks[func.entry_block].terminator.as_ref().unwrap();
1966            if let Terminator::Switch { cases, .. } = term {
1967                assert_eq!(cases.len(), 3);
1968            } else {
1969                panic!("expected switch terminator");
1970            }
1971        });
1972    }
1973
1974    #[test]
1975    fn parse_phi_round_trip_with_printer() {
1976        // Build a function with InstKind::Phi via the parser, print it, and
1977        // verify the printer output uses the `[bbN: vN]` format we expect.
1978        with_session(|| {
1979            let src = "\
1980fn @diamond(arg0: bool) -> u256 {
1981  bb0 (entry):
1982    br arg0, bb1, bb2
1983  bb1:
1984    jump bb3
1985  bb2:
1986    jump bb3
1987  bb3:
1988    v1 = phi [bb1: 10], [bb2: 20]
1989    ret v1
1990}
1991";
1992            let func = parse_function(src).unwrap();
1993            let printed = func.to_text().to_string();
1994            // The printer's exact format: `[bbN: <val>]`.
1995            assert!(
1996                printed.contains("phi [bb1:") && printed.contains("], [bb2:"),
1997                "expected `phi [bb1: ..], [bb2: ..]`, got:\n{printed}"
1998            );
1999            // Round-trip: re-parse the printer output, must succeed.
2000            let _func2 = parse_function(&printed).unwrap();
2001        });
2002    }
2003}