Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

optimize stack usage for recursive call and create programs #522

Merged
merged 25 commits into from
Jul 3, 2023
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
d7a8b1d
Add stack memory profiling
valo Jun 7, 2023
4a1d5f1
Move the CallInputs preparation into a separate function
valo Jun 9, 2023
5beac17
Simplified the call_inner functions in Host and EVMImpl
valo Jun 14, 2023
8836a47
Remove debug logging
valo Jun 14, 2023
b51827d
Removed an unused import
valo Jun 17, 2023
a661180
Optimized the stack allocations of create and call opcodes
valo Jun 19, 2023
5e41ba3
Remove the stacker crate
valo Jun 19, 2023
d9775e8
Remove temp debug tracing
valo Jun 19, 2023
a8bbdb1
Trace the EVM stack depth
valo Jun 19, 2023
0092c37
Fix the memory limit feature and the snailtracer bin
valo Jun 19, 2023
3c77dfe
Fix the formatting
valo Jun 20, 2023
d2e5c17
Remove the handle_ functions
valo Jun 23, 2023
a63b42d
Remove the bytecode from the CALL preparation
valo Jun 23, 2023
e9665d4
Use let-else and match to handle error cases
valo Jun 27, 2023
1ed97ca
Cloning the bytecode structs across fn calls is ok
valo Jun 27, 2023
f73f23d
Refactor the inner create return types
valo Jun 27, 2023
6189cba
Refactor the prepare call data into a struct
valo Jun 27, 2023
57a6a8d
In debug mode the revm needs 4MB of stack
valo Jun 27, 2023
faf778e
Fix clippy warnings
valo Jun 27, 2023
9ac3d71
Fix `cargo check` warnings
valo Jun 28, 2023
d7d535d
Remove debug logging
valo Jun 29, 2023
ff8ac1a
Make the internal structs private
valo Jun 29, 2023
d61bf30
add newer cargo.lock
rakita Jul 2, 2023
6d40af8
Merge remote-tracking branch 'origin/main' into stack_memory
rakita Jul 2, 2023
f630ec8
cargo fmt with updated cargo
rakita Jul 2, 2023
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
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(3 * 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
116 changes: 93 additions & 23 deletions crates/interpreter/src/instructions/host.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use crate::primitives::{Bytes, Spec, SpecId::*, B160, B256, U256};
use crate::MAX_INITCODE_SIZE;
use crate::{
alloc::vec::Vec,
gas::{self, COLD_ACCOUNT_ACCESS_COST, WARM_STORAGE_READ_COST},
interpreter::Interpreter,
return_ok, return_revert, CallContext, CallInputs, CallScheme, CreateInputs, CreateScheme,
Host, InstructionResult, Transfer,
};
use crate::{Gas, MAX_INITCODE_SIZE};
use core::cmp::min;

pub fn balance<SPEC: Spec>(interpreter: &mut Interpreter, host: &mut dyn Host) {
Expand Down Expand Up @@ -229,9 +229,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 +282,21 @@ 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 handle_create_result(
interpreter: &mut Interpreter,
result: (InstructionResult, Option<B160>, Gas, Bytes),
) {
let (return_reason, address, gas, return_data) = result;

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 @@ -321,6 +327,24 @@ pub fn create<const IS_CREATE2: bool, SPEC: Spec>(
}
}

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

if create_input.is_none() {
return;
}

let mut create_input = create_input.unwrap();
valo marked this conversation as resolved.
Show resolved Hide resolved

let ret = host.create(&mut create_input);

handle_create_result(interpreter, ret);
valo marked this conversation as resolved.
Show resolved Hide resolved
}

pub fn call<SPEC: Spec>(interpreter: &mut Interpreter, host: &mut dyn Host) {
call_inner::<SPEC>(interpreter, CallScheme::Call, host);
}
Expand All @@ -337,18 +361,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 +401,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,19 +496,25 @@ 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,
};

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

interpreter.return_data_buffer = return_data;
fn handle_call_result(
interpreter: &mut Interpreter,
out_offset: usize,
out_len: usize,
reason: &InstructionResult,
gas: &Gas,
return_data: &Bytes,
) {
interpreter.return_data_buffer = return_data.clone();

let target_len = min(out_len, interpreter.return_data_buffer.len());

Expand Down Expand Up @@ -521,3 +547,47 @@ pub fn call_inner<SPEC: Spec>(
}
}
}

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

if call_input.is_none() {
return;
}

// The Option is not needed form this point
let mut call_input = call_input.unwrap();
valo marked this conversation as resolved.
Show resolved Hide resolved

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

handle_call_result(
interpreter,
out_offset,
out_len,
&reason,
&gas,
&return_data,
);
}
6 changes: 3 additions & 3 deletions crates/interpreter/src/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,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 +55,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 +79,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
16 changes: 12 additions & 4 deletions crates/interpreter/src/interpreter/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,16 @@ pub struct Contract {
}

impl Contract {
pub fn new(input: Bytes, bytecode: Bytecode, address: B160, caller: B160, value: U256) -> Self {
let bytecode = to_analysed(bytecode).try_into().expect("it is analyzed");
pub fn new(
input: Bytes,
bytecode: &Bytecode,
address: B160,
caller: B160,
value: U256,
) -> Self {
let bytecode = to_analysed(bytecode.clone())
valo marked this conversation as resolved.
Show resolved Hide resolved
.try_into()
.expect("it is analyzed");

Self {
input,
Expand All @@ -39,7 +47,7 @@ impl Contract {
};
Self::new(
env.tx.data.clone(),
bytecode,
&bytecode,
contract_address,
env.tx.caller,
env.tx.value,
Expand All @@ -50,7 +58,7 @@ impl Contract {
self.bytecode.jump_map().is_valid(possition)
}

pub fn new_with_context(input: Bytes, bytecode: Bytecode, call_context: &CallContext) -> Self {
pub fn new_with_context(input: Bytes, bytecode: &Bytecode, call_context: &CallContext) -> Self {
Self::new(
input,
bytecode,
Expand Down
Loading