-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Conversation
Here's the benchmark results: jsontests benchmarks:
Uint benchmark (master):
Uint benchmark (PR):
This gist contains the python code used to format the result. |
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.
Looks really good, couple of minor grumbles.
ethcore/evm/src/interpreter/mod.rs
Outdated
/// Execute a single step on the VM. | ||
#[inline(always)] | ||
pub fn step(&mut self, ext: &mut vm::Ext) -> InterpreterResult { | ||
macro_rules! try_or_done { |
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.
Is it really worth to have a macro for that?
Maybe sth like this:
#[inline(always)]
fn try_or_done<F>(x: Result<F, vm::Error>) -> Result<F, InterpreterResult> {
x.map_err(|e| InterpreterResult::Done(Err(e))
}
// and used like this:
try_or_done(self.verify_instruction(ext, instruction, info))?;
Or maybe even add From<vm::Error> for InterpreterResult
and convert errors automatically via ?
?
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.
The issue is that ?
only works for Result
like things with Ok
and Err
. So we can't use any ?
syntax in step
right now, unless we change InterpreterResult
to be a Result<A, B>
. This indeed can be done via:
- Return
Ok(result)
, which is the same case forInterpreterResult::Done(_)
. - Return error case for
InterpreterResult::Stopped
andInterpreterResult::Continue
.
However, I think it may be a little bit confusing as Ok
/Err
do not really apply here.
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.
Oh, that's true. Forgot that it only works for Result
on the return type as well, anyway might be worth extracting the stuff in inner
to a separate function and maybe use a helper function instead of a macro. What do you think?
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.
I think I found a way: we can return Result<(), InterpreterResult>
as the inner function and always return Err
. The compiler would happily accept ?
syntax with that. And we would just need to make sure the inner function never returns Ok(())
.
This detail is wrapped in step_inner
so it doesn't affect public interface.
ethcore/evm/src/interpreter/mod.rs
Outdated
|
||
let info = instruction.info(); | ||
self.verify_instruction(ext, instruction, info, &stack)?; | ||
if instruction.is_none() { |
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.
let instruction = match instruction {
Some(i) => i,
None => return vm::Error::BadInstruction { .. }.into();
}
ethcore/evm/src/interpreter/mod.rs
Outdated
let instruction = Instruction::from_u8(opcode); | ||
reader.position += 1; | ||
let result = { | ||
let mut inner = || { |
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.
Why do we need the inner
closure?
Oh, I see to be able to use return
maybe it's worth to move it to a separate function?
ethcore/evm/src/interpreter/mod.rs
Outdated
let result = { | ||
let mut inner = || { | ||
// This case is needed because code length can be zero. | ||
if self.reader.position >= self.reader.len() { |
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.
Seems that we are checking that twice now, can't this be moved as some initial step? The reader.position
is changed only during regular execution and jumps, right?
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.
I moved it up to check just reader.len() == 0
. That check still needs to happen every time step
is called, otherwise we'll need an additional "interpreter state" (which might actually cost more memory).
} | ||
|
||
fn verify_instruction(&self, ext: &vm::Ext, instruction: Instruction, info: &InstructionInfo, stack: &Stack<U256>) -> vm::Result<()> { | ||
fn verify_instruction(&self, ext: &vm::Ext, instruction: Instruction, info: &InstructionInfo) -> vm::Result<()> { | ||
let schedule = ext.schedule(); |
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.
Schedule could probably be cached on self
level, less virtual calls.
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.
The issue is that for some use cases of step
, like pausing, we would want to take back the Ext
reference, possibly do some modifications to it, before passing it back to continue the execution.
- If we take a reference of
Schedule
and put it on self level, it means that this singleExt
is not usable again for the whole lifecycle of the Interpreter. - Or we need to clone the
Schedule
, which may also be expensive.
I think virtual calls might not be that expensive. And with (1), one optimization we can do is to split Ext
up to a) interpreter-specific Ext
, and b) transaction-level Ext
. I think that may be a good trade-off compared with additional virtual calls during each step
.
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.
Makes sense. I thought that we already clone schedule on ext.schedule()
but now saw it's only borrowed.
ethcore/evm/src/interpreter/mod.rs
Outdated
@@ -102,127 +103,234 @@ enum InstructionResult<Gas> { | |||
StopExecution, | |||
} | |||
|
|||
/// ActionParams without code, so that it can be feed into CodeReader. | |||
#[derive(Debug)] | |||
struct InterpreterParams { |
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.
Why we need this? Couldn't we just use ActionParams
and add some info that the code is always None
?
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.
Actually on a second though, it's better to have it like that - prevents future errors of accessing the code.
A new change on this PR is that I made The rationale for this is that I found out if |
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.
LGTM
rel #6744, fixes #9035
This is a prerequisite for implementing EVM resume and callstack refactoring. In particular, a new function
Interpreter::step(&mut Ext)
is added. This function step one additional opcode, and can be used plainly. Other notable changes:exec
. This is because we need to allocate stack, gasometer, etc beforeexec
is called.