Skip to content
This repository has been archived by the owner on Nov 6, 2020. It is now read-only.

evm: implement simple subroutine #11612

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all 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
10 changes: 10 additions & 0 deletions ethcore/evm/src/instructions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,13 @@ enum_with_from_u8! {
#[doc = "Makes a log entry, 4 topics."]
LOG4 = 0xa4,

#[doc = "Jump to a subrountine."]
JUMPSUB = 0xb3,
#[doc = "Begin a subrountine."]
BEGINSUB = 0xb5,
#[doc = "Return from a subrountine."]
RETURNSUB = 0xb7,
Comment on lines +324 to +329
Copy link
Member

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

We suggest the following opcodes:
0xb2 BEGINSUB
0xb3 JUMPSUB
0xb7 RETURNSUB

Copy link
Collaborator Author

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.

Copy link

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.

Copy link
Member

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)


#[doc = "create a new account with associated code"]
CREATE = 0xf0,
#[doc = "message-call into an account"]
Expand Down Expand Up @@ -595,6 +602,9 @@ lazy_static! {
arr[SUICIDE as usize] = Some(InstructionInfo::new("SUICIDE", 1, 0, GasPriceTier::Special));
arr[CREATE2 as usize] = Some(InstructionInfo::new("CREATE2", 4, 1, GasPriceTier::Special));
arr[REVERT as usize] = Some(InstructionInfo::new("REVERT", 2, 0, GasPriceTier::Zero));
arr[BEGINSUB as usize] = Some(InstructionInfo::new("BEGINSUB", 0, 0, GasPriceTier::Base));
arr[JUMPSUB as usize] = Some(InstructionInfo::new("JUMPSUB", 1, 0, GasPriceTier::Low));
arr[RETURNSUB as usize] = Some(InstructionInfo::new("RETURNSUB", 0, 0, GasPriceTier::VeryLow));
arr
};
}
Expand Down
68 changes: 65 additions & 3 deletions ethcore/evm/src/interpreter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ enum InstructionResult<Gas> {
Ok,
UnusedGas(Gas),
JumpToPosition(U256),
JumpToSub(usize),
ReturnFromSub(usize),
StopExecutionNeedsReturn {
/// Gas left.
gas: Gas,
Expand Down Expand Up @@ -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>,
return_stack_limit: usize,
resume_output_range: Option<(U256, U256)>,
resume_result: Option<InstructionResult<Cost>>,
last_stack_ret_len: usize,
Expand Down Expand Up @@ -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`.
Expand Down Expand Up @@ -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) {
Expand All @@ -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 {
Expand Down Expand Up @@ -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)
Copy link
Member

Choose a reason for hiding this comment

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

why do we need a separate error type for this?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We can use errors from JUMPDEST of course, but I think using separate error types is just more descriptive. It does not hurt anyway!

Copy link
Member

Choose a reason for hiding this comment

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

Sorry, I meant why not using BadSubDestination as we do return BadJumpDestination for verify_jump in such a case?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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();
Expand Down Expand Up @@ -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()
Expand Down
53 changes: 46 additions & 7 deletions ethcore/evm/src/interpreter/shared_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

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

can't it be derived with #[derive(MallocSizeOf)]?


/// Global cache for EVM interpreter
pub struct SharedCache {
jump_destinations: Mutex<MemoryLruCache<H256, Bits>>,
jump_destinations: Mutex<MemoryLruCache<H256, Dests>>,
}

impl SharedCache {
Expand All @@ -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> {
Expand All @@ -91,6 +109,27 @@ impl SharedCache {
jump_dests.shrink_to_fit();
Arc::new(jump_dests)
}

fn find_sub_destinations(code: &[u8]) -> Arc<BitSet> {
Copy link
Member

Choose a reason for hiding this comment

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

this function is used together with find_jump_destinations, so I guess it could be merge into one avoiding extra pass

Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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 {
Expand Down
16 changes: 16 additions & 0 deletions ethcore/vm/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ pub enum Error {
/// Position the code tried to jump to.
destination: usize
},
/// `BadSubDestination` is returned when execution tried to move
/// to position that wasn't marked with BEGINSUB instruction
BadSubDestination {
/// Position the code tried to jump to.
destination: usize
},
/// `BadInstructions` is returned when given instruction is not supported
BadInstruction {
/// Unrecognized opcode
Expand All @@ -71,6 +77,12 @@ pub enum Error {
/// What was the stack limit
limit: usize
},
/// Return stack overflowed.
OutOfReturnStack,
/// Invalid subroutine target.
InvalidSub,
/// Return stack underflowed.
ReturnStackUnderflow,
/// Built-in contract failed on given input
BuiltIn(&'static str),
/// When execution tries to modify the state in static context
Expand Down Expand Up @@ -103,9 +115,13 @@ impl fmt::Display for Error {
match *self {
OutOfGas => write!(f, "Out of gas"),
BadJumpDestination { destination } => write!(f, "Bad jump destination {:x}", destination),
BadSubDestination { destination } => write!(f, "Bad sub destination {:x}", destination),
BadInstruction { instruction } => write!(f, "Bad instruction {:x}", instruction),
StackUnderflow { instruction, wanted, on_stack } => write!(f, "Stack underflow {} {}/{}", instruction, wanted, on_stack),
OutOfStack { instruction, wanted, limit } => write!(f, "Out of stack {} {}/{}", instruction, wanted, limit),
OutOfReturnStack => write!(f, "Out of return stack"),
InvalidSub => write!(f, "Invalid subrountine target"),
ReturnStackUnderflow => write!(f, "Return stack underflow"),
BuiltIn(name) => write!(f, "Built-in failed: {}", name),
Internal(ref msg) => write!(f, "Internal error: {}", msg),
MutableCallInStaticContext => write!(f, "Mutable call in static context"),
Expand Down