Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions crates/interpreter/src/instructions/opcode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,24 @@ impl<H: Host> InstructionTables<'_, H> {
}

impl<'a, H: Host + 'a> InstructionTables<'a, H> {
/// Inserts a boxed instruction into the table with the specified index.
///
/// Returns an error if the table is not boxed.
#[inline]
pub fn insert_boxed(
&mut self,
opcode: u8,
instruction: BoxedInstruction<'a, H>,
) -> Result<(), InsertError> {
match self {
Self::Plain(_) => Err(InsertError::NotBoxed),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we convert to boxed instead?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would panic here. Silently converting can have a side-effect on performance

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but there's no other way to use custom instructions, the only way is to box all of them, so I think it'd be reasonable to convert here

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be intentionally called with to_boxed not silently converted.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this would make evm setup fallible and force unwrap with a preceding call to to_boxed, i think this makes it a lot less convenient to use, and since additional instructions are only supported by boxing all of them, converting them here seems reasonable to me

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is the point to fail and make it explicit to call, this would look something like.

table.convert_to_box();
table.insert_boxed(instr);

It is redundant but explicit on what is going on.

Not the point I would die on, so lets convert it to boxed inside insert_boxed.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to convert into the boxed variant inside insert_boxed. updated docs and the test which no longer needs a map

Self::Boxed(table) => {
table[opcode as usize] = Box::new(instruction);
Ok(())
}
}
}

/// Inserts the instruction into the table with the specified index.
#[inline]
pub fn insert(&mut self, opcode: u8, instruction: Instruction<H>) {
Expand All @@ -54,6 +72,28 @@ impl<'a, H: Host + 'a> InstructionTables<'a, H> {
}
}
}

/// Converts the instruction table to a boxed variant. If the table is already boxed, this does
/// nothing and returns the table unchanged.
#[inline]
pub fn to_boxed(self) -> InstructionTables<'a, H> {
let boxed_tables = match self {
Self::Plain(table) => core::array::from_fn(|i| {
let instruction: BoxedInstruction<'a, H> = Box::new(table[i]);
instruction
}),
Self::Boxed(table) => table,
};

Self::Boxed(boxed_tables)
}
}

/// An error that can occur when trying to insert instructions into the instruction table.
#[derive(Debug)]
pub enum InsertError {
/// Tried to insert a boxed instruction into an instruction table which was not boxed.
NotBoxed,
}

/// Make instruction table.
Expand Down
59 changes: 57 additions & 2 deletions crates/revm/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -448,8 +448,63 @@ mod test {
},
Context, ContextPrecompile, ContextStatefulPrecompile, Evm, InMemoryDB, InnerEvmContext,
};
use revm_interpreter::{Host, Interpreter};
use std::sync::Arc;
use revm_interpreter::{opcode::InstructionTables, Host, Interpreter};
use std::{cell::RefCell, rc::Rc, sync::Arc};

/// Custom evm context
#[derive(Default, Clone, Debug)]
pub(crate) struct CustomContext {
pub(crate) inner: Rc<RefCell<u8>>,
}

#[test]
fn simple_add_stateful_instruction() {
let code = Bytecode::new_raw([0xEF, 0x00].into());
let code_hash = code.hash_slow();
let to_addr = address!("ffffffffffffffffffffffffffffffffffffffff");

// initialize the custom context and make sure it's zero
let custom_context = CustomContext::default();
assert_eq!(*custom_context.inner.borrow(), 0);

let to_capture = custom_context.clone();
let mut evm = Evm::builder()
.with_db(InMemoryDB::default())
.modify_db(|db| {
db.insert_account_info(to_addr, AccountInfo::new(U256::ZERO, 0, code_hash, code))
})
.modify_tx_env(|tx| tx.transact_to = TransactTo::Call(to_addr))
// we need to use handle register box to capture the custom context in the handle
// register
.append_handler_register_box(Box::new(move |handler| {
let custom_context = to_capture.clone();

// we need to use a box to capture the custom context in the instruction
let custom_instruction = Box::new(
move |_interp: &mut Interpreter, _host: &mut Evm<'_, (), InMemoryDB>| {
// modify the value
let mut inner = custom_context.inner.borrow_mut();
*inner += 1;
},
);

// need to make esure the instruction table is a boxed instruction table so that we
// can insert the custom instruction as a boxed instruction
let mut table = handler.take_instruction_table();
table = table.map(InstructionTables::to_boxed).map(|mut table| {
// now we can finally insert
table.insert_boxed(0xEF, custom_instruction).unwrap();
table
});
handler.instruction_table = table;
}))
.build();

let _result_and_state = evm.transact().unwrap();

// ensure the custom context was modified
assert_eq!(*custom_context.inner.borrow(), 1);
}

#[test]
fn simple_add_instruction() {
Expand Down