zingen/
asm.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
//! Low level assembler implementation for EVM.
//!
//! TODO: refactor this module with Result as outputs. (issue-21)

use crate::{Buffer, Error, Result};
use opcodes::{for_each_cancun_operator, Cancun as OpCode, OpCode as _};

const MAX_STACK_SIZE: u16 = 1024;

/// Low level assembler implementation for EVM.
#[derive(Default, Clone, Debug)]
pub struct Assembler {
    /// Buffer of the assembler.
    buffer: Buffer,
    /// Gas counter.
    ///
    /// This is used to calculate the gas cost of the generated code.
    ///
    /// TODO: use a more precise type, eq `u256`. (issue-20)
    gas: u128,
    /// Memory pointer for byte offset.
    pub mp: usize,
    /// Stack pointer, maximum `MAX_STACK_SIZE` items.
    pub sp: u16,
}

impl Assembler {
    /// Buffer of the assembler.
    pub fn buffer(&self) -> &[u8] {
        &self.buffer
    }

    /// Mutable buffer of the assembler.
    pub fn buffer_mut(&mut self) -> &mut Buffer {
        &mut self.buffer
    }

    /// Increment the gas counter.
    ///
    /// TODO: use number bigger than `u256` for throwing proper errors. (#21)
    pub fn increment_gas(&mut self, gas: u128) {
        self.gas += gas;
    }

    /// Increment stack pointer
    pub fn increment_sp(&mut self, items: u16) -> Result<()> {
        if items == 0 {
            return Ok(());
        }

        tracing::trace!(
            "increment stack pointer {}.add({items}) -> {}",
            self.sp,
            self.sp + items
        );
        self.sp = self
            .sp
            .checked_add(items)
            .ok_or(Error::StackOverflow(self.sp, items))?;

        if self.sp > MAX_STACK_SIZE {
            return Err(Error::StackOverflow(self.sp, items));
        }

        Ok(())
    }

    /// Decrement stack pointer
    pub fn decrement_sp(&mut self, items: u16) -> Result<()> {
        if items == 0 {
            return Ok(());
        }

        tracing::trace!(
            "decrement stack pointer {}.sub({items}) -> {}",
            self.sp,
            self.sp - items
        );
        self.sp = if self.sp == items {
            0
        } else {
            self.sp
                .checked_sub(items)
                .ok_or(Error::StackUnderflow(self.sp, items))?
        };

        Ok(())
    }

    /// Increment memory pointer
    pub fn increment_mp(&mut self, offset: usize) -> Result<()> {
        self.mp = self
            .mp
            .checked_add(offset)
            .ok_or(Error::MemoryOutOfBounds)?;

        Ok(())
    }

    /// Decrement memory pointer
    pub fn decrement_mp(&mut self, offset: usize) -> Result<()> {
        self.mp = self
            .mp
            .checked_sub(offset)
            .ok_or(Error::MemoryOutOfBounds)?;
        Ok(())
    }

    /// Emit a byte.
    pub fn emit(&mut self, byte: u8) {
        self.buffer.push(byte);
    }

    /// Emit n bytes.
    pub fn emitn(&mut self, bytes: &[u8]) {
        self.buffer.extend_from_slice(bytes);
    }

    /// Emit a single opcode.
    ///
    /// Mock the stack input and output for checking
    /// the stack usages.
    pub fn emit_op(&mut self, opcode: OpCode) -> Result<()> {
        tracing::trace!("emit opcode: {:?}", opcode);
        self.decrement_sp(opcode.stack_in())?;
        self.emit(opcode.into());
        self.increment_gas(opcode.gas().into());
        self.increment_sp(opcode.stack_out())?;

        Ok(())
    }
}

macro_rules! impl_opcodes {
    ($($name:ident => $opcode:ident),+) => {
        $(
            #[doc = concat!(" Emit ", stringify!($opcode))]
            pub fn $name(&mut self) -> Result<()> {
                self.emit_op(OpCode::$opcode)?;
                Ok(())
            }
        )*
    };
}

/// Basic instruction implementations
impl Assembler {
    for_each_cancun_operator!(impl_opcodes);
}