use crate::{Buffer, Error, Result};
use opcodes::{for_each_cancun_operator, Cancun as OpCode, OpCode as _};
const MAX_STACK_SIZE: u16 = 1024;
#[derive(Default, Clone, Debug)]
pub struct Assembler {
buffer: Buffer,
gas: u128,
pub mp: usize,
pub sp: u16,
}
impl Assembler {
pub fn buffer(&self) -> &[u8] {
&self.buffer
}
pub fn buffer_mut(&mut self) -> &mut Buffer {
&mut self.buffer
}
pub fn increment_gas(&mut self, gas: u128) {
self.gas += gas;
}
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(())
}
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(())
}
pub fn increment_mp(&mut self, offset: usize) -> Result<()> {
self.mp = self
.mp
.checked_add(offset)
.ok_or(Error::MemoryOutOfBounds)?;
Ok(())
}
pub fn decrement_mp(&mut self, offset: usize) -> Result<()> {
self.mp = self
.mp
.checked_sub(offset)
.ok_or(Error::MemoryOutOfBounds)?;
Ok(())
}
pub fn emit(&mut self, byte: u8) {
self.buffer.push(byte);
}
pub fn emitn(&mut self, bytes: &[u8]) {
self.buffer.extend_from_slice(bytes);
}
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(())
}
)*
};
}
impl Assembler {
for_each_cancun_operator!(impl_opcodes);
}