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);
}