Skip to content

Commit

Permalink
optimize stack usage for recursive call and create programs (#522)
Browse files Browse the repository at this point in the history
* Add stack memory profiling

* Move the CallInputs preparation into a separate function

This makes the stack footprint of the solidity function calls much
smaller

* Simplified the call_inner functions in Host and EVMImpl

Simplification of the functions ensures that the stack is being kept
more lean. Also boxed some variables.

These changes allow running the currently blowing transactions with the
default stack size.

* Remove debug logging

* Removed an unused import

* Optimized the stack allocations of create and call opcodes

* Remove the stacker crate

* Remove temp debug tracing

* Trace the EVM stack depth

* Fix the memory limit feature and the snailtracer bin

* Fix the formatting

* Remove the handle_ functions

These functions does not impact the stack memory footprint

* Remove the bytecode from the CALL preparation

The bytecode is included in the returned contract, so there is no need
to return it.

* Use let-else and match to handle error cases

* Cloning the bytecode structs across fn calls is ok

* Refactor the inner create return types

* Refactor the prepare call data into a struct

* In debug mode the revm needs 4MB of stack

After the latest simplifications of the stack optimizations, the stack
memory footprint increased.

* Fix clippy warnings

* Fix `cargo check` warnings

* Remove debug logging

* Make the internal structs private

* add newer cargo.lock

* cargo fmt with updated cargo

---------

Co-authored-by: rakita <dragan0rakita@gmail.com>
  • Loading branch information
valo and rakita authored Jul 3, 2023
1 parent c153428 commit 10f81ba
Show file tree
Hide file tree
Showing 6 changed files with 309 additions and 113 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ codegen-units = 1

[profile.ethtests]
inherits = "test"
opt-level = 3
opt-level = 3
2 changes: 1 addition & 1 deletion bins/revm-test/src/bin/snailtracer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ pub fn simple_example() {

let mut host = DummyHost::new(env);
microbench::bench(&bench_options, "Snailtracer Interpreter benchmark", || {
let mut interpreter = Interpreter::new(contract.clone(), u64::MAX, false);
let mut interpreter = Interpreter::new(Box::new(contract.clone()), u64::MAX, false);
interpreter.run::<_, BerlinSpec>(&mut host);
host.clear()
});
Expand Down
10 changes: 8 additions & 2 deletions bins/revme/src/statetest/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -355,9 +355,15 @@ pub fn run(
let console_bar = console_bar.clone();
let elapsed = elapsed.clone();

let mut thread = std::thread::Builder::new();

// Allow bigger stack in debug mode to prevent stack overflow errors
if cfg!(debug_assertions) {
thread = thread.stack_size(4 * 1024 * 1024);
}

joins.push(
std::thread::Builder::new()
.stack_size(50 * 1024 * 1024)
thread
.spawn(move || loop {
let (index, test_path) = {
let mut queue = queue.lock().unwrap();
Expand Down
71 changes: 55 additions & 16 deletions crates/interpreter/src/instructions/host.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::primitives::{Bytes, Spec, SpecId::*, B160, B256, U256};
use crate::MAX_INITCODE_SIZE;
use crate::{
alloc::boxed::Box,
alloc::vec::Vec,
gas::{self, COLD_ACCOUNT_ACCESS_COST, WARM_STORAGE_READ_COST},
interpreter::Interpreter,
Expand Down Expand Up @@ -229,9 +230,9 @@ pub fn selfdestruct<SPEC: Spec>(interpreter: &mut Interpreter, host: &mut dyn Ho
interpreter.instruction_result = InstructionResult::SelfDestruct;
}

pub fn create<const IS_CREATE2: bool, SPEC: Spec>(
pub fn prepare_create_inputs<const IS_CREATE2: bool, SPEC: Spec>(
interpreter: &mut Interpreter,
host: &mut dyn Host,
create_inputs: &mut Option<Box<CreateInputs>>,
) {
check_staticcall!(interpreter);
if IS_CREATE2 {
Expand Down Expand Up @@ -282,15 +283,28 @@ pub fn create<const IS_CREATE2: bool, SPEC: Spec>(
}
gas!(interpreter, gas_limit);

let mut create_input = CreateInputs {
*create_inputs = Some(Box::new(CreateInputs {
caller: interpreter.contract.address,
scheme,
value,
init_code: code,
gas_limit,
}));
}

pub fn create<const IS_CREATE2: bool, SPEC: Spec>(
interpreter: &mut Interpreter,
host: &mut dyn Host,
) {
let mut create_input: Option<Box<CreateInputs>> = None;
prepare_create_inputs::<IS_CREATE2, SPEC>(interpreter, &mut create_input);

let Some(mut create_input) = create_input else {
return;
};

let (return_reason, address, gas, return_data) = host.create(&mut create_input);

interpreter.return_data_buffer = match return_reason {
// Save data to return data buffer if the create reverted
return_revert!() => return_data,
Expand Down Expand Up @@ -337,18 +351,14 @@ pub fn static_call<SPEC: Spec>(interpreter: &mut Interpreter, host: &mut dyn Hos
call_inner::<SPEC>(interpreter, CallScheme::StaticCall, host);
}

pub fn call_inner<SPEC: Spec>(
fn prepare_call_inputs<SPEC: Spec>(
interpreter: &mut Interpreter,
scheme: CallScheme,
host: &mut dyn Host,
result_len: &mut usize,
result_offset: &mut usize,
result_call_inputs: &mut Option<Box<CallInputs>>,
) {
match scheme {
CallScheme::DelegateCall => check!(interpreter, SPEC::enabled(HOMESTEAD)), // EIP-7: DELEGATECALL
CallScheme::StaticCall => check!(interpreter, SPEC::enabled(BYZANTIUM)), // EIP-214: New opcode STATICCALL
_ => (),
}
interpreter.return_data_buffer = Bytes::new();

pop!(interpreter, local_gas_limit);
pop_address!(interpreter, to);
let local_gas_limit = u64::try_from(local_gas_limit).unwrap_or(u64::MAX);
Expand Down Expand Up @@ -381,14 +391,14 @@ pub fn call_inner<SPEC: Spec>(
Bytes::new()
};

let out_len = as_usize_or_fail!(interpreter, out_len, InstructionResult::InvalidOperandOOG);
let out_offset = if out_len != 0 {
*result_len = as_usize_or_fail!(interpreter, out_len, InstructionResult::InvalidOperandOOG);
*result_offset = if *result_len != 0 {
let out_offset = as_usize_or_fail!(
interpreter,
out_offset,
InstructionResult::InvalidOperandOOG
);
memory_resize!(interpreter, out_offset, out_len);
memory_resize!(interpreter, out_offset, *result_len);
out_offset
} else {
usize::MAX //unrealistic value so we are sure it is not used
Expand Down Expand Up @@ -476,16 +486,45 @@ pub fn call_inner<SPEC: Spec>(
}
let is_static = matches!(scheme, CallScheme::StaticCall) || interpreter.is_static;

let mut call_input = CallInputs {
*result_call_inputs = Some(Box::new(CallInputs {
contract: to,
transfer,
input,
gas_limit,
context,
is_static,
}));
}

pub fn call_inner<SPEC: Spec>(
interpreter: &mut Interpreter,
scheme: CallScheme,
host: &mut dyn Host,
) {
match scheme {
CallScheme::DelegateCall => check!(interpreter, SPEC::enabled(HOMESTEAD)), // EIP-7: DELEGATECALL
CallScheme::StaticCall => check!(interpreter, SPEC::enabled(BYZANTIUM)), // EIP-214: New opcode STATICCALL
_ => (),
}
interpreter.return_data_buffer = Bytes::new();

let mut out_offset: usize = 0;
let mut out_len: usize = 0;
let mut call_input: Option<Box<CallInputs>> = None;
prepare_call_inputs::<SPEC>(
interpreter,
scheme,
host,
&mut out_len,
&mut out_offset,
&mut call_input,
);

let Some(mut call_input) = call_input else {
return;
};

// Call host to interuct with target contract
// Call host to interact with target contract
let (reason, gas, return_data) = host.call(&mut call_input);

interpreter.return_data_buffer = return_data;
Expand Down
7 changes: 4 additions & 3 deletions crates/interpreter/src/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub use stack::Stack;

use crate::primitives::{Bytes, Spec};
use crate::{
alloc::boxed::Box,
instructions::{eval, InstructionResult},
Gas, Host,
};
Expand Down Expand Up @@ -42,7 +43,7 @@ pub struct Interpreter {
/// Is interpreter call static.
pub is_static: bool,
/// Contract information and invoking data
pub contract: Contract,
pub contract: Box<Contract>,
/// Memory limit. See [`crate::CfgEnv`].
#[cfg(feature = "memory_limit")]
pub memory_limit: u64,
Expand All @@ -55,7 +56,7 @@ impl Interpreter {
}

/// Create new interpreter
pub fn new(contract: Contract, gas_limit: u64, is_static: bool) -> Self {
pub fn new(contract: Box<Contract>, gas_limit: u64, is_static: bool) -> Self {
#[cfg(not(feature = "memory_limit"))]
{
Self {
Expand All @@ -79,7 +80,7 @@ impl Interpreter {

#[cfg(feature = "memory_limit")]
pub fn new_with_memory_limit(
contract: Contract,
contract: Box<Contract>,
gas_limit: u64,
is_static: bool,
memory_limit: u64,
Expand Down
Loading

0 comments on commit 10f81ba

Please sign in to comment.