-
Notifications
You must be signed in to change notification settings - Fork 1.7k
evm: implement simple subroutine #11612
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -94,6 +94,8 @@ enum InstructionResult<Gas> { | |
Ok, | ||
UnusedGas(Gas), | ||
JumpToPosition(U256), | ||
JumpToSub(usize), | ||
ReturnFromSub(usize), | ||
StopExecutionNeedsReturn { | ||
/// Gas left. | ||
gas: Gas, | ||
|
@@ -179,8 +181,11 @@ pub struct Interpreter<Cost: CostType> { | |
do_trace: bool, | ||
done: bool, | ||
valid_jump_destinations: Option<Arc<BitSet>>, | ||
valid_sub_destinations: Option<Arc<BitSet>>, | ||
gasometer: Option<Gasometer<Cost>>, | ||
stack: VecStack<U256>, | ||
return_stack: Vec<usize>, | ||
ordian marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return_stack_limit: usize, | ||
resume_output_range: Option<(U256, U256)>, | ||
resume_result: Option<InstructionResult<Cost>>, | ||
last_stack_ret_len: usize, | ||
|
@@ -271,12 +276,16 @@ impl<Cost: CostType> Interpreter<Cost> { | |
let params = InterpreterParams::from(params); | ||
let informant = informant::EvmInformant::new(depth); | ||
let valid_jump_destinations = None; | ||
let valid_sub_destinations = None; | ||
let gasometer = Cost::from_u256(params.gas).ok().map(|gas| Gasometer::<Cost>::new(gas)); | ||
let stack = VecStack::with_capacity(schedule.stack_limit, U256::zero()); | ||
let return_stack_limit = schedule.stack_limit; | ||
let return_stack = Vec::with_capacity(schedule.stack_limit); | ||
|
||
Interpreter { | ||
cache, params, reader, informant, | ||
valid_jump_destinations, gasometer, stack, | ||
cache, params, reader, informant, return_stack, | ||
valid_jump_destinations, valid_sub_destinations, | ||
gasometer, stack, return_stack_limit, | ||
done: false, | ||
// Overridden in `step_inner` based on | ||
// the result of `ext.trace_next_instruction`. | ||
|
@@ -403,7 +412,12 @@ impl<Cost: CostType> Interpreter<Cost> { | |
match result { | ||
InstructionResult::JumpToPosition(position) => { | ||
if self.valid_jump_destinations.is_none() { | ||
self.valid_jump_destinations = Some(self.cache.jump_destinations(&self.params.code_hash, &self.reader.code)); | ||
let (jumpdests, subdests) = self.cache.dests( | ||
&self.params.code_hash, | ||
&self.reader.code | ||
); | ||
self.valid_jump_destinations = Some(jumpdests); | ||
self.valid_sub_destinations = Some(subdests); | ||
} | ||
let jump_destinations = self.valid_jump_destinations.as_ref().expect("jump_destinations are initialized on first jump; qed"); | ||
let pos = match self.verify_jump(position, jump_destinations) { | ||
|
@@ -412,6 +426,25 @@ impl<Cost: CostType> Interpreter<Cost> { | |
}; | ||
self.reader.position = pos; | ||
}, | ||
InstructionResult::JumpToSub(position) => { | ||
if self.valid_jump_destinations.is_none() { | ||
let (jumpdests, subdests) = self.cache.dests( | ||
&self.params.code_hash, | ||
&self.reader.code | ||
); | ||
self.valid_jump_destinations = Some(jumpdests); | ||
self.valid_sub_destinations = Some(subdests); | ||
} | ||
let sub_destinations = self.valid_sub_destinations.as_ref().expect("sub_destinations are initialized on first jumpsub; qed"); | ||
let pos = match self.verify_sub(position, sub_destinations) { | ||
Ok(x) => x, | ||
Err(e) => return InterpreterResult::Done(Err(e)), | ||
}; | ||
self.reader.position = pos; | ||
}, | ||
InstructionResult::ReturnFromSub(position) => { | ||
self.reader.position = position; | ||
}, | ||
InstructionResult::StopExecutionNeedsReturn {gas, init_off, init_size, apply} => { | ||
let mem = mem::replace(&mut self.mem, Vec::new()); | ||
return InterpreterResult::Done(Ok(GasLeft::NeedsReturn { | ||
|
@@ -525,6 +558,25 @@ impl<Cost: CostType> Interpreter<Cost> { | |
instructions::JUMPDEST => { | ||
// ignore | ||
}, | ||
instructions::JUMPSUB => { | ||
if self.return_stack.len() >= self.return_stack_limit - 1 { | ||
return Err(vm::Error::OutOfReturnStack) | ||
} | ||
let jump = self.stack.pop_back(); | ||
if jump >= U256::from(usize::max_value()) { | ||
return Err(vm::Error::InvalidSub) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why do we need a separate error type for this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can use errors from There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry, I meant why not using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm yeah you're right! |
||
} | ||
let jump = jump.as_usize(); | ||
self.return_stack.push(self.reader.position); | ||
return Ok(InstructionResult::JumpToSub(jump)) | ||
}, | ||
instructions::BEGINSUB => { | ||
// ignore | ||
}, | ||
instructions::RETURNSUB => { | ||
let pc = self.return_stack.pop().ok_or(vm::Error::ReturnStackUnderflow)?; | ||
return Ok(InstructionResult::ReturnFromSub(pc)) | ||
}, | ||
instructions::CREATE | instructions::CREATE2 => { | ||
let endowment = self.stack.pop_back(); | ||
let init_off = self.stack.pop_back(); | ||
|
@@ -1183,6 +1235,16 @@ impl<Cost: CostType> Interpreter<Cost> { | |
} | ||
} | ||
|
||
fn verify_sub(&self, sub: usize, valid_sub_destinations: &BitSet) -> vm::Result<usize> { | ||
if valid_sub_destinations.contains(sub) { | ||
Ok(sub) | ||
} else { | ||
Err(vm::Error::BadSubDestination { | ||
destination: sub | ||
}) | ||
} | ||
} | ||
|
||
fn bool_to_u256(val: bool) -> U256 { | ||
if val { | ||
U256::one() | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -36,9 +36,23 @@ impl MallocSizeOf for Bits { | |
} | ||
} | ||
|
||
/// Stub for holding JUMPDEST destinations, and Subroutine destinations. | ||
struct Dests { | ||
/// JUMPDEST destinations. | ||
pub jumpdests: Bits, | ||
/// Subroutine destinations. | ||
pub subdests: Bits, | ||
} | ||
|
||
impl MallocSizeOf for Dests { | ||
fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { | ||
self.jumpdests.size_of(ops) + self.subdests.size_of(ops) | ||
} | ||
} | ||
Comment on lines
+47
to
+51
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can't it be derived with |
||
|
||
/// Global cache for EVM interpreter | ||
pub struct SharedCache { | ||
jump_destinations: Mutex<MemoryLruCache<H256, Bits>>, | ||
jump_destinations: Mutex<MemoryLruCache<H256, Dests>>, | ||
} | ||
|
||
impl SharedCache { | ||
|
@@ -50,25 +64,29 @@ impl SharedCache { | |
} | ||
} | ||
|
||
/// Get jump destinations bitmap for a contract. | ||
pub fn jump_destinations(&self, code_hash: &Option<H256>, code: &[u8]) -> Arc<BitSet> { | ||
/// Get jump destinations bitmap and sub destinations bitmap for a contract. | ||
pub fn dests(&self, code_hash: &Option<H256>, code: &[u8]) -> (Arc<BitSet>, Arc<BitSet>) { | ||
if let Some(ref code_hash) = code_hash { | ||
if code_hash == &KECCAK_EMPTY { | ||
return Self::find_jump_destinations(code); | ||
return (Self::find_jump_destinations(code), Self::find_sub_destinations(code)); | ||
} | ||
|
||
if let Some(d) = self.jump_destinations.lock().get_mut(code_hash) { | ||
return d.0.clone(); | ||
return (d.jumpdests.0.clone(), d.subdests.0.clone()); | ||
} | ||
} | ||
|
||
let d = Self::find_jump_destinations(code); | ||
let s = Self::find_sub_destinations(code); | ||
|
||
if let Some(ref code_hash) = code_hash { | ||
self.jump_destinations.lock().insert(*code_hash, Bits(d.clone())); | ||
self.jump_destinations.lock().insert(*code_hash, Dests { | ||
jumpdests: Bits(d.clone()), | ||
subdests: Bits(s.clone()), | ||
}); | ||
} | ||
|
||
d | ||
(d, s) | ||
} | ||
|
||
fn find_jump_destinations(code: &[u8]) -> Arc<BitSet> { | ||
|
@@ -91,6 +109,27 @@ impl SharedCache { | |
jump_dests.shrink_to_fit(); | ||
Arc::new(jump_dests) | ||
} | ||
|
||
fn find_sub_destinations(code: &[u8]) -> Arc<BitSet> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this function is used together with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah you're right! |
||
let mut sub_dests = BitSet::with_capacity(code.len()); | ||
let mut position = 0; | ||
|
||
while position < code.len() { | ||
let instruction = Instruction::from_u8(code[position]); | ||
|
||
if let Some(instruction) = instruction { | ||
if instruction == instructions::BEGINSUB { | ||
sub_dests.insert(position); | ||
} else if let Some(push_bytes) = instruction.push_bytes() { | ||
position += push_bytes; | ||
} | ||
} | ||
position += 1; | ||
} | ||
|
||
sub_dests.shrink_to_fit(); | ||
Arc::new(sub_dests) | ||
} | ||
} | ||
|
||
impl Default for SharedCache { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://eips.ethereum.org/EIPS/eip-2315
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, cc @gcolvin. What are the integer values of those opcodes? The one used in Geth PR and in the spec are different.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sorpaas I'm on the road, after midnight, on the road until midnight tomorrow, then I can check. @holiman might be able to shed some light.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sorpaas I think we should follow the spec ethereum/go-ethereum#20619 (comment)