Skip to main content

revmc_codegen/bytecode/
asm.rs

1//! EVM bytecode assembler.
2
3use crate::{
4    U256, encode_pair, encode_single,
5    eyre::{self, Result},
6};
7use revm_bytecode::opcode::{self as op, OpCode};
8use revm_primitives::map::HashMap;
9use std::cmp::Ordering;
10
11/// Parse EVM assembly from a string into bytecode.
12///
13/// Assembles EVM mnemonics from a string into raw bytecode. Supports:
14/// - Standard EVM opcodes (`ADD`, `PUSH1 0x42`, etc.)
15/// - Auto-sized pushes (`PUSH 0x1234` picks the smallest encoding)
16/// - Labels: `name:` defines a label at the current PC, `PUSH %name` / `PUSHn %name` resolves to
17///   the label's byte offset
18/// - Comments starting with `;`
19/// - C-style `#define` macros (textual expansion before parsing), with optional parameters
20///
21/// ```evm
22/// #define PUSH_TWO(a, b) PUSH $a PUSH $b
23///
24/// entry:
25///   PUSH_TWO(1, 2)
26///   ADD
27///   PUSH %target
28///   JUMP
29///
30/// target:
31///   JUMPDEST
32///   STOP
33/// ```
34pub fn parse_asm(s: &str) -> Result<Vec<u8>> {
35    let tokens = preprocess(s)?;
36    let items = parse_items(&tokens)?;
37    layout_and_emit(&items)
38}
39
40// ———————————————————————————————————————————————————————————————————————
41// Tokenizer
42// ———————————————————————————————————————————————————————————————————————
43
44/// A token produced by the tokenizer.
45#[derive(Debug, Clone, PartialEq, Eq)]
46pub(super) struct Token<'a> {
47    /// The source text slice this token was produced from.
48    pub src: &'a str,
49    /// The kind of token.
50    pub kind: TokenKind,
51}
52
53/// The kind of a [`Token`].
54#[derive(Debug, Clone, PartialEq, Eq)]
55pub(super) enum TokenKind {
56    /// An identifier (opcode name, macro name, etc.).
57    Ident,
58    /// A label definition (`name:`).
59    Label,
60    /// A label reference (`%name`).
61    LabelRef,
62    /// A numeric literal.
63    Number(U256),
64    /// A comma.
65    Comma,
66    /// Opening parenthesis.
67    LParen,
68    /// Closing parenthesis.
69    RParen,
70    /// A macro parameter reference (`$name`).
71    ParamRef,
72    /// Whitespace.
73    Whitespace,
74    /// Comment text including the leading `;`.
75    Comment,
76    /// Unrecognized input.
77    Unknown,
78}
79
80/// Character-by-character tokenizer over source text.
81///
82/// Always emits all tokens including whitespace and comments.
83pub(super) struct Tokenizer<'a> {
84    src: &'a str,
85    pos: usize,
86}
87
88impl<'a> Tokenizer<'a> {
89    pub(super) fn new(src: &'a str) -> Self {
90        Self { src, pos: 0 }
91    }
92
93    fn remaining(&self) -> &'a str {
94        &self.src[self.pos..]
95    }
96
97    fn peek_char(&self) -> Option<char> {
98        self.remaining().chars().next()
99    }
100
101    fn advance(&mut self, n: usize) {
102        self.pos += n;
103    }
104
105    /// Read a contiguous word of alphanumeric/underscore characters.
106    fn read_word(&mut self) -> &'a str {
107        let rest = self.remaining();
108        let end = rest.find(|c: char| !c.is_ascii_alphanumeric() && c != '_').unwrap_or(rest.len());
109        let word = &rest[..end];
110        self.advance(end);
111        word
112    }
113
114    /// Read a numeric literal (decimal or 0x hex).
115    fn read_number(&mut self) -> Token<'a> {
116        let rest = self.remaining();
117        let end = if rest.starts_with("0x") || rest.starts_with("0X") {
118            2 + rest[2..].find(|c: char| !c.is_ascii_hexdigit()).unwrap_or(rest.len() - 2)
119        } else {
120            rest.find(|c: char| !c.is_ascii_digit()).unwrap_or(rest.len())
121        };
122        let src = &rest[..end];
123        self.advance(end);
124        match src.parse::<U256>() {
125            Ok(n) => Token { src, kind: TokenKind::Number(n) },
126            Err(_) => Token { src, kind: TokenKind::Unknown },
127        }
128    }
129}
130
131impl<'a> Iterator for Tokenizer<'a> {
132    type Item = Token<'a>;
133
134    fn next(&mut self) -> Option<Token<'a>> {
135        // Emit whitespace as a token.
136        let rest = self.remaining();
137        let trimmed = rest.trim_start_matches(|c: char| c.is_ascii_whitespace());
138        let ws_len = rest.len() - trimmed.len();
139        if ws_len > 0 {
140            let src = &self.src[self.pos..self.pos + ws_len];
141            self.advance(ws_len);
142            return Some(Token { src, kind: TokenKind::Whitespace });
143        }
144
145        let c = self.peek_char()?;
146
147        match c {
148            ';' => {
149                let start = self.pos;
150                if let Some(nl) = self.remaining().find('\n') {
151                    self.advance(nl);
152                } else {
153                    self.pos = self.src.len();
154                }
155                Some(Token { src: &self.src[start..self.pos], kind: TokenKind::Comment })
156            }
157
158            '%' => {
159                let start = self.pos;
160                self.advance(1);
161                let name = self.read_word();
162                Some(if name.is_empty() {
163                    Token { src: &self.src[start..self.pos], kind: TokenKind::Unknown }
164                } else {
165                    Token { src: name, kind: TokenKind::LabelRef }
166                })
167            }
168
169            '$' => {
170                let start = self.pos;
171                self.advance(1);
172                let name = self.read_word();
173                Some(if name.is_empty() {
174                    Token { src: &self.src[start..self.pos], kind: TokenKind::Unknown }
175                } else {
176                    Token { src: name, kind: TokenKind::ParamRef }
177                })
178            }
179
180            ',' => {
181                let src = &self.src[self.pos..self.pos + 1];
182                self.advance(1);
183                Some(Token { src, kind: TokenKind::Comma })
184            }
185            '(' => {
186                let src = &self.src[self.pos..self.pos + 1];
187                self.advance(1);
188                Some(Token { src, kind: TokenKind::LParen })
189            }
190            ')' => {
191                let src = &self.src[self.pos..self.pos + 1];
192                self.advance(1);
193                Some(Token { src, kind: TokenKind::RParen })
194            }
195
196            '0'..='9' => Some(self.read_number()),
197
198            _ if c.is_ascii_alphabetic() || c == '_' => {
199                let word = self.read_word();
200                if self.peek_char() == Some(':') {
201                    self.advance(1);
202                    Some(Token { src: word, kind: TokenKind::Label })
203                } else {
204                    Some(Token { src: word, kind: TokenKind::Ident })
205                }
206            }
207
208            ':' => {
209                self.advance(1);
210                Some(Token { src: "", kind: TokenKind::Label })
211            }
212
213            _ => {
214                let start = self.pos;
215                // Consume consecutive unrecognized characters.
216                while let Some(c) = self.peek_char() {
217                    if c.is_ascii_whitespace()
218                        || c.is_ascii_alphanumeric()
219                        || matches!(c, '_' | ';' | '%' | '$' | ',' | '(' | ')' | ':')
220                    {
221                        break;
222                    }
223                    self.advance(c.len_utf8());
224                }
225                Some(Token { src: &self.src[start..self.pos], kind: TokenKind::Unknown })
226            }
227        }
228    }
229}
230
231// ———————————————————————————————————————————————————————————————————————
232// Preprocessor (#define macros)
233// ———————————————————————————————————————————————————————————————————————
234
235/// A macro definition: parameter names and body tokens.
236struct MacroDef<'a> {
237    /// Whether this is a function-like macro (invoked with parentheses).
238    is_fn: bool,
239    params: Vec<&'a str>,
240    body: Vec<Token<'a>>,
241}
242
243/// Builtin macros available in all assembly sources.
244fn builtin_macros() -> HashMap<&'static str, MacroDef<'static>> {
245    let mut m = HashMap::default();
246    m.insert(
247        "RET_WORD",
248        MacroDef {
249            is_fn: false,
250            params: vec![],
251            body: vec![
252                Token { src: "PUSH0", kind: TokenKind::Ident },
253                Token { src: "MSTORE", kind: TokenKind::Ident },
254                Token { src: "PUSH1", kind: TokenKind::Ident },
255                Token { src: "0x20", kind: TokenKind::Number(U256::from(0x20)) },
256                Token { src: "PUSH0", kind: TokenKind::Ident },
257                Token { src: "RETURN", kind: TokenKind::Ident },
258            ],
259        },
260    );
261    m
262}
263
264/// Preprocess source text: extract `#define` directives (line-scoped), tokenize the rest,
265/// then expand macro invocations on the token stream.
266fn preprocess(s: &str) -> Result<Vec<Token<'_>>> {
267    let mut macros = builtin_macros();
268
269    // Extract #define lines (tokenize their bodies in-place); keep remaining lines.
270    // Note: `#define` bodies borrow from `s` since their source text lives in `s`.
271    let mut rest_start = Vec::new();
272    for line in s.lines() {
273        let trimmed = line.trim();
274        if let Some(after) = trimmed.strip_prefix("#define")
275            && (after.is_empty() || after.starts_with(|c: char| c.is_ascii_whitespace()))
276        {
277            parse_define(after, &mut macros)?;
278        } else {
279            // Record (start, end) byte offsets into `s` for non-directive lines.
280            let offset = trimmed.as_ptr() as usize - s.as_ptr() as usize;
281            rest_start.push((offset, offset + trimmed.len()));
282        }
283    }
284
285    // Tokenize non-directive lines (borrowing from `s`), skipping whitespace and comments.
286    let mut raw = Vec::new();
287    for &(start, end) in &rest_start {
288        let line = &s[start..end];
289        raw.extend(
290            Tokenizer::new(line)
291                .filter(|t| !matches!(t.kind, TokenKind::Whitespace | TokenKind::Comment)),
292        );
293    }
294
295    if macros.is_empty() {
296        return Ok(raw);
297    }
298
299    expand_macros(raw, &macros)
300}
301
302/// Parse a `#define` directive body (everything after `#define`) into the macro table.
303fn parse_define<'a>(after: &'a str, macros: &mut HashMap<&'a str, MacroDef<'a>>) -> Result<()> {
304    let mut tok = Tokenizer::new(after)
305        .filter(|t| !matches!(t.kind, TokenKind::Whitespace | TokenKind::Comment))
306        .peekable();
307
308    let name = match tok.next() {
309        Some(Token { src, kind: TokenKind::Ident }) => src,
310        Some(other) => return Err(eyre::eyre!("expected macro name after #define, got {other:?}")),
311        None => return Err(eyre::eyre!("expected macro name after #define")),
312    };
313
314    // Function-like macro: NAME(a, b).
315    // Only if '(' immediately follows the name (no whitespace), matching C preprocessor semantics.
316    let is_fn = after.as_bytes().get(name.as_ptr() as usize - after.as_ptr() as usize + name.len())
317        == Some(&b'(');
318
319    let all_tokens: Vec<Token<'a>> = tok.collect();
320    let mut i = 0;
321
322    let mut params = Vec::new();
323    if is_fn && matches!(all_tokens.get(i), Some(Token { kind: TokenKind::LParen, .. })) {
324        i += 1; // consume '('
325        if !matches!(all_tokens.get(i), Some(Token { kind: TokenKind::RParen, .. })) {
326            loop {
327                match all_tokens.get(i) {
328                    Some(Token { src: p, kind: TokenKind::Ident }) => {
329                        params.push(*p);
330                        i += 1;
331                    }
332                    other => {
333                        return Err(eyre::eyre!(
334                            "expected parameter name in #define {name}, got {other:?}"
335                        ));
336                    }
337                }
338                match all_tokens.get(i) {
339                    Some(Token { kind: TokenKind::RParen, .. }) => {
340                        i += 1;
341                        break;
342                    }
343                    Some(Token { kind: TokenKind::Comma, .. }) => i += 1,
344                    other => {
345                        return Err(eyre::eyre!(
346                            "expected ',' or ')' in #define {name} parameter list, got {other:?}"
347                        ));
348                    }
349                }
350            }
351        } else {
352            i += 1; // consume ')'
353        }
354    }
355
356    let body = all_tokens[i..].to_vec();
357    macros.insert(name, MacroDef { is_fn, params, body });
358    Ok(())
359}
360
361/// Expand macro invocations in a token stream.
362fn expand_macros<'a>(
363    tokens: Vec<Token<'a>>,
364    macros: &HashMap<&str, MacroDef<'a>>,
365) -> Result<Vec<Token<'a>>> {
366    let mut out = Vec::with_capacity(tokens.len());
367    let mut iter = tokens.into_iter().peekable();
368
369    while let Some(tok) = iter.next() {
370        let TokenKind::Ident = &tok.kind else {
371            out.push(tok);
372            continue;
373        };
374        let Some(mac) = macros.get(tok.src) else {
375            out.push(tok);
376            continue;
377        };
378        let name = tok.src;
379
380        if !mac.is_fn {
381            // Object-like macro: simple body substitution.
382            out.extend(mac.body.iter().cloned());
383        } else {
384            // Function-like macro: consume `(arg1, arg2, ...)`.
385            eyre::ensure!(
386                matches!(iter.next(), Some(Token { kind: TokenKind::LParen, .. })),
387                "macro {name:?} expects arguments",
388            );
389
390            // Parse arguments, handling nested parens.
391            let mut args: Vec<Vec<Token<'a>>> = vec![vec![]];
392            let mut depth = 1u32;
393            loop {
394                let t = iter
395                    .next()
396                    .ok_or_else(|| eyre::eyre!("unclosed '(' in macro invocation {name:?}"))?;
397                match &t.kind {
398                    TokenKind::LParen => {
399                        depth += 1;
400                        args.last_mut().unwrap().push(t);
401                    }
402                    TokenKind::RParen => {
403                        depth -= 1;
404                        if depth == 0 {
405                            break;
406                        }
407                        args.last_mut().unwrap().push(t);
408                    }
409                    TokenKind::Comma if depth == 1 => args.push(vec![]),
410                    _ => args.last_mut().unwrap().push(t),
411                }
412            }
413
414            // Zero-arg function-like: `FOO()` produces args = [[]]; expect 0.
415            if mac.params.is_empty() {
416                eyre::ensure!(
417                    args.len() == 1 && args[0].is_empty(),
418                    "macro {name:?} takes no arguments"
419                );
420            } else {
421                eyre::ensure!(
422                    args.len() == mac.params.len(),
423                    "macro {name:?} expects {} argument(s), got {}",
424                    mac.params.len(),
425                    args.len()
426                );
427            }
428
429            // Substitute $param refs in the body.
430            for body_tok in &mac.body {
431                if let TokenKind::ParamRef = body_tok.kind
432                    && let Some(idx) = mac.params.iter().position(|p| *p == body_tok.src)
433                {
434                    out.extend(args[idx].iter().cloned());
435                } else {
436                    out.push(body_tok.clone());
437                }
438            }
439        }
440    }
441    Ok(out)
442}
443
444// ———————————————————————————————————————————————————————————————————————
445// Parser (tokens → items)
446// ———————————————————————————————————————————————————————————————————————
447
448/// A parsed item from the source.
449enum Item<'a> {
450    /// A label definition (`name:`).
451    Label(&'a str),
452    /// An instruction.
453    Inst(Inst<'a>),
454}
455
456/// A parsed instruction.
457struct Inst<'a> {
458    opcode: u8,
459    imm: Option<Imm<'a>>,
460    push_kind: PushKind,
461}
462
463/// An immediate value.
464enum Imm<'a> {
465    /// A numeric literal.
466    Number(U256),
467    /// A label reference (resolved during layout).
468    Label(&'a str),
469}
470
471/// How the push width is determined.
472enum PushKind {
473    /// Not a push instruction.
474    None,
475    /// Fixed width (`PUSH1`..`PUSH32`).
476    Fixed(u8),
477    /// Auto-sized (`PUSH`).
478    Auto,
479}
480
481/// Parse a token stream into items.
482fn parse_items<'a>(tokens: &[Token<'a>]) -> Result<Vec<Item<'a>>> {
483    let mut items = Vec::new();
484    let mut i = 0;
485    while i < tokens.len() {
486        match &tokens[i].kind {
487            TokenKind::Label => {
488                let name = tokens[i].src;
489                eyre::ensure!(!name.is_empty(), "empty label name");
490                items.push(Item::Label(name));
491                i += 1;
492            }
493            TokenKind::Ident => {
494                let word = tokens[i].src;
495                if word == "PUSH" {
496                    i += 1;
497                    let imm = expect_imm(tokens, &mut i, "PUSH")?;
498                    items.push(Item::Inst(Inst {
499                        opcode: 0,
500                        imm: Some(imm),
501                        push_kind: PushKind::Auto,
502                    }));
503                } else if word == "DUP" || word == "SWAP" {
504                    let is_swap = word == "SWAP";
505                    i += 1;
506                    let n = expect_number_u8(tokens, &mut i, word)?;
507                    eyre::ensure!(n >= 1, "{word} index must be >= 1, got {n}");
508                    if n <= 16 {
509                        let base = if is_swap { op::SWAP1 } else { op::DUP1 };
510                        items.push(Item::Inst(Inst {
511                            opcode: base + n - 1,
512                            imm: None,
513                            push_kind: PushKind::None,
514                        }));
515                    } else {
516                        let eof_op = if is_swap { op::SWAPN } else { op::DUPN };
517                        let raw = encode_single(n).ok_or_else(|| {
518                            eyre::eyre!("{word} index {n} out of valid range [1, 235]")
519                        })?;
520                        items.push(Item::Inst(Inst {
521                            opcode: eof_op,
522                            imm: Some(Imm::Number(U256::from(raw))),
523                            push_kind: PushKind::Fixed(1),
524                        }));
525                    }
526                } else {
527                    let opc = OpCode::parse(word)
528                        .ok_or_else(|| eyre::eyre!("invalid opcode: {word:?}"))?;
529                    let opcode = opc.get();
530                    i += 1;
531
532                    if opcode == op::DUPN || opcode == op::SWAPN {
533                        let n = expect_number_u8(tokens, &mut i, opc)?;
534                        let raw = encode_single(n).ok_or_else(|| {
535                            eyre::eyre!("{opc} index {n} out of valid range [17, 235]")
536                        })?;
537                        items.push(Item::Inst(Inst {
538                            opcode,
539                            imm: Some(Imm::Number(U256::from(raw))),
540                            push_kind: PushKind::Fixed(1),
541                        }));
542                    } else if opcode == op::EXCHANGE {
543                        let n = expect_number_u8(tokens, &mut i, opc)?;
544                        let m = expect_number_u8(tokens, &mut i, opc)?;
545                        let raw = encode_pair(n, m).ok_or_else(|| {
546                            eyre::eyre!("EXCHANGE pair ({n}, {m}) cannot be encoded")
547                        })?;
548                        items.push(Item::Inst(Inst {
549                            opcode,
550                            imm: Some(Imm::Number(U256::from(raw))),
551                            push_kind: PushKind::Fixed(1),
552                        }));
553                    } else {
554                        let imm_len = opc.info().immediate_size();
555                        if imm_len > 0 {
556                            let imm = expect_imm(tokens, &mut i, opc)?;
557                            items.push(Item::Inst(Inst {
558                                opcode,
559                                imm: Some(imm),
560                                push_kind: PushKind::Fixed(imm_len),
561                            }));
562                        } else {
563                            if matches!(
564                                tokens.get(i),
565                                Some(Token { kind: TokenKind::Number(_), .. })
566                            ) {
567                                eyre::bail!("unexpected immediate for opcode {opc}");
568                            }
569                            items.push(Item::Inst(Inst {
570                                opcode,
571                                imm: None,
572                                push_kind: PushKind::None,
573                            }));
574                        }
575                    }
576                }
577            }
578            TokenKind::Unknown => return Err(eyre::eyre!("unexpected token: {:?}", tokens[i].src)),
579            _ => return Err(eyre::eyre!("unexpected token: {:?}", tokens[i])),
580        }
581    }
582    Ok(items)
583}
584
585/// Consume the next token as a number and convert to u8.
586fn expect_number_u8(
587    tokens: &[Token<'_>],
588    i: &mut usize,
589    ctx: impl std::fmt::Display,
590) -> Result<u8> {
591    let tok = tokens.get(*i).ok_or_else(|| eyre::eyre!("missing immediate for opcode {ctx}"))?;
592    *i += 1;
593    match &tok.kind {
594        TokenKind::Number(n) => {
595            let v: u64 =
596                n.try_into().map_err(|_| eyre::eyre!("invalid {ctx} immediate: too large"))?;
597            u8::try_from(v).map_err(|_| eyre::eyre!("invalid {ctx} immediate: too large"))
598        }
599        _ => Err(eyre::eyre!("expected numeric immediate for {ctx}, got {tok:?}")),
600    }
601}
602
603/// Consume the next token as an immediate (number or label ref).
604fn expect_imm<'a>(
605    tokens: &[Token<'a>],
606    i: &mut usize,
607    ctx: impl std::fmt::Display,
608) -> Result<Imm<'a>> {
609    let tok = tokens.get(*i).ok_or_else(|| eyre::eyre!("missing immediate for opcode {ctx}"))?;
610    *i += 1;
611    match &tok.kind {
612        TokenKind::Number(n) => Ok(Imm::Number(*n)),
613        TokenKind::LabelRef => Ok(Imm::Label(tok.src)),
614        _ => Err(eyre::eyre!("expected immediate for {ctx}, got {tok:?}")),
615    }
616}
617
618// ———————————————————————————————————————————————————————————————————————
619// Layout and emit
620// ———————————————————————————————————————————————————————————————————————
621
622/// Encode a U256 as big-endian bytes with optional fixed size.
623fn encode_imm(num: U256, size: Option<u8>) -> Result<Vec<u8>> {
624    let mut bytes = num.to_be_bytes_trimmed_vec();
625    if let Some(size) = size {
626        debug_assert!(size <= 32);
627        match bytes.len().cmp(&(size as usize)) {
628            Ordering::Less => {
629                let extend = size as usize - bytes.len();
630                bytes.splice(0..0, std::iter::repeat_n(0, extend));
631            }
632            Ordering::Equal => {}
633            Ordering::Greater => {
634                eyre::bail!("expected at most {size} immediate bytes, got {}", bytes.len());
635            }
636        }
637    }
638    debug_assert!(bytes.len() <= 32);
639    Ok(bytes)
640}
641
642/// Compute the minimum push width for a value (0 for zero, 1 for 1..=0xff, etc.).
643fn min_push_width(val: usize) -> u8 {
644    if val == 0 {
645        0
646    } else {
647        let bits = usize::BITS - val.leading_zeros();
648        bits.div_ceil(8) as u8
649    }
650}
651
652/// Layout items with label resolution (fixed-point for auto-sized label pushes) and emit bytecode.
653fn layout_and_emit(items: &[Item<'_>]) -> Result<Vec<u8>> {
654    let mut auto_label_indices = Vec::new();
655    let mut has_any_label = false;
656
657    for (i, item) in items.iter().enumerate() {
658        match item {
659            Item::Label(_) => has_any_label = true,
660            Item::Inst(inst) => {
661                if matches!(inst.imm, Some(Imm::Label(_))) {
662                    has_any_label = true;
663                    if matches!(inst.push_kind, PushKind::Auto) {
664                        auto_label_indices.push(i);
665                    }
666                }
667            }
668        }
669    }
670
671    // If no labels at all, just emit directly.
672    if !has_any_label {
673        let mut code = Vec::with_capacity(32);
674        for item in items {
675            if let Item::Inst(inst) = item {
676                emit_inst_no_labels(inst, &mut code)?;
677            }
678        }
679        return Ok(code);
680    }
681
682    // Fixed-point layout: auto-push widths start at 0 and grow monotonically.
683    let mut auto_widths = vec![0u8; items.len()];
684    let mut label_pcs = HashMap::<&str, usize>::default();
685
686    loop {
687        label_pcs.clear();
688        let mut pc = 0usize;
689        for (i, item) in items.iter().enumerate() {
690            match item {
691                Item::Label(name) => {
692                    label_pcs.insert(name, pc);
693                }
694                Item::Inst(inst) => {
695                    pc += 1;
696                    match &inst.push_kind {
697                        PushKind::None => {}
698                        PushKind::Fixed(n) => pc += *n as usize,
699                        PushKind::Auto => {
700                            let width = match &inst.imm {
701                                Some(Imm::Label(_)) => auto_widths[i],
702                                Some(Imm::Number(n)) => {
703                                    let bytes = n.to_be_bytes_trimmed_vec();
704                                    bytes.len() as u8
705                                }
706                                None => 0,
707                            };
708                            pc += width as usize;
709                        }
710                    }
711                }
712            }
713        }
714
715        let mut changed = false;
716        for &i in &auto_label_indices {
717            if let Item::Inst(inst) = &items[i]
718                && let Some(Imm::Label(name)) = &inst.imm
719            {
720                let target_pc =
721                    *label_pcs.get(name).ok_or_else(|| eyre::eyre!("undefined label: {name:?}"))?;
722                let needed = min_push_width(target_pc);
723                if needed > auto_widths[i] {
724                    auto_widths[i] = needed;
725                    changed = true;
726                }
727            }
728        }
729
730        if !changed {
731            break;
732        }
733    }
734
735    // Final emit.
736    let mut code = Vec::with_capacity(64);
737    for (i, item) in items.iter().enumerate() {
738        if let Item::Inst(inst) = item {
739            match &inst.push_kind {
740                PushKind::None => {
741                    code.push(inst.opcode);
742                }
743                PushKind::Fixed(size) => {
744                    code.push(inst.opcode);
745                    let val = resolve_imm(inst.imm.as_ref().unwrap(), &label_pcs)?;
746                    let bytes = encode_imm(val, Some(*size))?;
747                    code.extend_from_slice(&bytes);
748                }
749                PushKind::Auto => {
750                    let val = resolve_imm(inst.imm.as_ref().unwrap(), &label_pcs)?;
751                    let width = match &inst.imm {
752                        Some(Imm::Label(_)) => auto_widths[i],
753                        _ => {
754                            let bytes = val.to_be_bytes_trimmed_vec();
755                            bytes.len() as u8
756                        }
757                    };
758                    let push0 = OpCode::PUSH0.get();
759                    code.push(push0 + width);
760                    if width > 0 {
761                        let bytes = encode_imm(val, Some(width))?;
762                        code.extend_from_slice(&bytes);
763                    }
764                }
765            }
766        }
767    }
768
769    Ok(code)
770}
771
772/// Resolve an immediate value, substituting label PCs.
773fn resolve_imm(imm: &Imm<'_>, label_pcs: &HashMap<&str, usize>) -> Result<U256> {
774    match imm {
775        Imm::Number(n) => Ok(*n),
776        Imm::Label(name) => {
777            let pc = label_pcs.get(name).ok_or_else(|| eyre::eyre!("undefined label: {name:?}"))?;
778            Ok(U256::from(*pc))
779        }
780    }
781}
782
783/// Emit a single instruction (no-label fast path).
784fn emit_inst_no_labels(inst: &Inst<'_>, code: &mut Vec<u8>) -> Result<()> {
785    match &inst.push_kind {
786        PushKind::None => {
787            code.push(inst.opcode);
788        }
789        PushKind::Fixed(size) => {
790            code.push(inst.opcode);
791            let Imm::Number(n) = inst.imm.as_ref().unwrap() else {
792                unreachable!();
793            };
794            let bytes = encode_imm(*n, Some(*size))?;
795            code.extend_from_slice(&bytes);
796        }
797        PushKind::Auto => {
798            let Imm::Number(n) = inst.imm.as_ref().unwrap() else {
799                unreachable!();
800            };
801            let bytes = encode_imm(*n, None)?;
802            let push0 = OpCode::PUSH0.get();
803            code.push(push0 + bytes.len() as u8);
804            code.extend_from_slice(&bytes);
805        }
806    }
807    Ok(())
808}
809
810#[cfg(test)]
811mod tests {
812    use super::*;
813    use revm_bytecode::opcode as op;
814
815    #[test]
816    fn basic_opcodes() {
817        let cases: &[(&str, Vec<u8>)] = &[
818            ("ADD ; ADD\n ADD", vec![op::ADD, op::ADD]),
819            ("PUSH1 0", vec![op::PUSH1, 0]),
820            ("PUSH3 0x000069", vec![op::PUSH3, 0, 0, 0x69]),
821            ("PUSH3 0x69 ; padded", vec![op::PUSH3, 0, 0, 0x69]),
822            ("PUSH 0", vec![op::PUSH0]),
823            ("PUSH 1", vec![op::PUSH1, 1]),
824            ("PUSH 2", vec![op::PUSH1, 2]),
825            ("PUSH 69", vec![op::PUSH1, 69]),
826            ("PUSH 0x2222", vec![op::PUSH2, 0x22, 0x22]),
827        ];
828        for (s, expected) in cases.iter() {
829            let code = match parse_asm(s) {
830                Ok(code) => code,
831                Err(e) => panic!("code: {s:?}\n\n err: {e}"),
832            };
833            assert_eq!(code, *expected, "{s:?}");
834        }
835    }
836
837    #[test]
838    fn label_forward_ref() {
839        let code = parse_asm(
840            "
841            PUSH %target
842            JUMP
843        target:
844            JUMPDEST
845            STOP
846        ",
847        )
848        .unwrap();
849        assert_eq!(code, vec![op::PUSH1, 3, op::JUMP, op::JUMPDEST, op::STOP]);
850    }
851
852    #[test]
853    fn label_backward_ref() {
854        let code = parse_asm(
855            "
856        target:
857            JUMPDEST
858            PUSH %target
859            JUMP
860        ",
861        )
862        .unwrap();
863        assert_eq!(code, vec![op::JUMPDEST, op::PUSH0, op::JUMP]);
864    }
865
866    #[test]
867    fn label_fixed_width() {
868        let code = parse_asm(
869            "
870            PUSH1 %target
871            JUMP
872        target:
873            JUMPDEST
874            STOP
875        ",
876        )
877        .unwrap();
878        assert_eq!(code, vec![op::PUSH1, 3, op::JUMP, op::JUMPDEST, op::STOP]);
879    }
880
881    #[test]
882    fn multiple_labels_same_pc() {
883        let code = parse_asm(
884            "
885        a:
886        b:
887            JUMPDEST
888            PUSH %a
889            PUSH %b
890            STOP
891        ",
892        )
893        .unwrap();
894        assert_eq!(code, vec![op::JUMPDEST, op::PUSH0, op::PUSH0, op::STOP]);
895    }
896
897    #[test]
898    fn dup_auto() {
899        // DUP 1..16 → DUP1..DUP16 (no immediate).
900        assert_eq!(parse_asm("DUP 1").unwrap(), vec![op::DUP1]);
901        assert_eq!(parse_asm("DUP 16").unwrap(), vec![op::DUP16]);
902        // DUP 17+ → DUPN with encoded immediate.
903        assert_eq!(parse_asm("DUP 17").unwrap(), vec![op::DUPN, 0x80]);
904        assert_eq!(parse_asm("DUP 108").unwrap(), vec![op::DUPN, 0xDB]);
905        // DUP 0 is invalid.
906        assert!(parse_asm("DUP 0").is_err());
907        // DUP 236 is out of range.
908        assert!(parse_asm("DUP 236").is_err());
909    }
910
911    #[test]
912    fn swap_auto() {
913        // SWAP 1..16 → SWAP1..SWAP16 (no immediate).
914        assert_eq!(parse_asm("SWAP 1").unwrap(), vec![op::SWAP1]);
915        assert_eq!(parse_asm("SWAP 16").unwrap(), vec![op::SWAP16]);
916        // SWAP 17+ → SWAPN with encoded immediate.
917        assert_eq!(parse_asm("SWAP 17").unwrap(), vec![op::SWAPN, 0x80]);
918        assert_eq!(parse_asm("SWAP 108").unwrap(), vec![op::SWAPN, 0xDB]);
919        // SWAP 0 is invalid.
920        assert!(parse_asm("SWAP 0").is_err());
921    }
922
923    #[test]
924    fn dupn() {
925        // Explicit DUPN only accepts 17+.
926        assert_eq!(parse_asm("DUPN 17").unwrap(), vec![op::DUPN, 0x80]);
927        assert_eq!(parse_asm("DUPN 108").unwrap(), vec![op::DUPN, 0xDB]);
928        assert!(parse_asm("DUPN 16").is_err());
929        assert!(parse_asm("DUPN 0").is_err());
930        assert!(parse_asm("DUPN 236").is_err());
931        assert!(parse_asm("DUPN abc").is_err());
932    }
933
934    #[test]
935    fn swapn() {
936        assert_eq!(parse_asm("SWAPN 17").unwrap(), vec![op::SWAPN, 0x80]);
937        assert_eq!(parse_asm("SWAPN 108").unwrap(), vec![op::SWAPN, 0xDB]);
938        assert!(parse_asm("SWAPN 16").is_err());
939        assert!(parse_asm("SWAPN 0").is_err());
940    }
941
942    #[test]
943    fn exchange() {
944        // Two separate number tokens.
945        assert_eq!(parse_asm("EXCHANGE 1 2").unwrap(), vec![op::EXCHANGE, 0x8E]);
946        assert!(parse_asm("EXCHANGE 1 14").is_ok());
947        // (2, 1) cannot be encoded.
948        assert!(parse_asm("EXCHANGE 2 1").is_err());
949        // (0, 1) is invalid (zero index).
950        assert!(parse_asm("EXCHANGE 0 1").is_err());
951        // Missing second operand.
952        assert!(parse_asm("EXCHANGE 1").is_err());
953        // Non-numeric.
954        assert!(parse_asm("EXCHANGE a b").is_err());
955    }
956
957    #[test]
958    fn slotnum() {
959        assert_eq!(parse_asm("SLOTNUM").unwrap(), vec![op::SLOTNUM]);
960    }
961
962    #[test]
963    fn undefined_label() {
964        assert!(parse_asm("PUSH %missing JUMP").is_err());
965    }
966
967    #[test]
968    fn empty_label() {
969        assert!(parse_asm(": STOP").is_err());
970    }
971
972    #[test]
973    fn empty_label_ref() {
974        assert!(parse_asm("PUSH % JUMP").is_err());
975    }
976
977    #[test]
978    fn define_macro() {
979        let code = parse_asm(
980            "
981            #define TWO PUSH 2
982            TWO
983            TWO
984            ADD
985        ",
986        )
987        .unwrap();
988        assert_eq!(code, vec![op::PUSH1, 2, op::PUSH1, 2, op::ADD]);
989    }
990
991    #[test]
992    fn builtin_ret_word() {
993        let code = parse_asm("CALLVALUE RET_WORD").unwrap();
994        assert_eq!(
995            code,
996            vec![op::CALLVALUE, op::PUSH0, op::MSTORE, op::PUSH1, 0x20, op::PUSH0, op::RETURN]
997        );
998    }
999
1000    #[test]
1001    fn define_override_builtin() {
1002        let code = parse_asm(
1003            "
1004            #define RET_WORD STOP
1005            RET_WORD
1006        ",
1007        )
1008        .unwrap();
1009        assert_eq!(code, vec![op::STOP]);
1010    }
1011
1012    #[test]
1013    fn define_with_args() {
1014        let code = parse_asm(
1015            "
1016            #define PUSH_TWO(a, b) PUSH $a PUSH $b
1017            PUSH_TWO(1, 2)
1018            ADD
1019        ",
1020        )
1021        .unwrap();
1022        assert_eq!(code, vec![op::PUSH1, 1, op::PUSH1, 2, op::ADD]);
1023    }
1024
1025    #[test]
1026    fn define_single_arg() {
1027        let code = parse_asm(
1028            "
1029            #define PUSH_AND_STORE(val) PUSH $val PUSH0 MSTORE
1030            PUSH_AND_STORE(0x42)
1031        ",
1032        )
1033        .unwrap();
1034        assert_eq!(code, vec![op::PUSH1, 0x42, op::PUSH0, op::MSTORE]);
1035    }
1036
1037    #[test]
1038    fn define_missing_args() {
1039        assert!(
1040            parse_asm(
1041                "
1042            #define FOO(a) PUSH $a
1043            FOO
1044        "
1045            )
1046            .is_err()
1047        );
1048    }
1049
1050    #[test]
1051    fn define_wrong_arg_count() {
1052        assert!(
1053            parse_asm(
1054                "
1055            #define FOO(a, b) PUSH $a PUSH $b
1056            FOO(1)
1057        "
1058            )
1059            .is_err()
1060        );
1061    }
1062
1063    #[test]
1064    fn define_empty_name() {
1065        assert!(parse_asm("#define").is_err());
1066        assert!(parse_asm("#define  ").is_err());
1067    }
1068
1069    #[test]
1070    fn define_invalid_name() {
1071        // `-` is tokenized as Unknown, so it can't be a macro name.
1072        assert!(parse_asm("#define - STOP").is_err());
1073        // `#define` with no name at all.
1074        assert!(parse_asm("#define (a) STOP").is_err());
1075    }
1076
1077    #[test]
1078    fn define_no_space_after_keyword() {
1079        // `#defineFOO` should not be treated as a directive.
1080        assert!(parse_asm("#defineFOO STOP").is_err());
1081    }
1082
1083    #[test]
1084    fn define_zero_arg_fn() {
1085        let code = parse_asm(
1086            "
1087            #define NOP() ADD
1088            NOP()
1089        ",
1090        )
1091        .unwrap();
1092        assert_eq!(code, vec![op::ADD]);
1093    }
1094
1095    #[test]
1096    fn define_zero_arg_fn_bare_is_error() {
1097        // Function-like macro requires parentheses.
1098        assert!(
1099            parse_asm(
1100                "
1101            #define NOP() ADD
1102            NOP
1103        "
1104            )
1105            .is_err()
1106        );
1107    }
1108
1109    #[test]
1110    fn define_zero_arg_fn_with_args_is_error() {
1111        assert!(
1112            parse_asm(
1113                "
1114            #define NOP() ADD
1115            NOP(1)
1116        "
1117            )
1118            .is_err()
1119        );
1120    }
1121
1122    #[test]
1123    fn define_malformed_param_list() {
1124        // Missing closing paren.
1125        assert!(parse_asm("#define FOO(a STOP").is_err());
1126        // Missing comma between params.
1127        assert!(parse_asm("#define FOO(a b) STOP").is_err());
1128        // Number as param name.
1129        assert!(parse_asm("#define FOO(1) STOP").is_err());
1130        // Trailing comma.
1131        assert!(parse_asm("#define FOO(a,) STOP").is_err());
1132    }
1133
1134    #[test]
1135    fn define_space_before_paren_is_object_like() {
1136        // `#define FOO (a) STOP` — space before `(` makes it object-like with body `(a) STOP`.
1137        // Using FOO should expand to `(a) STOP`, and `(` is not a valid opcode.
1138        assert!(
1139            parse_asm(
1140                "
1141            #define FOO (a) STOP
1142            FOO
1143        "
1144            )
1145            .is_err()
1146        );
1147    }
1148}