The Ethereum virtual machine is a little different from most other virtual machines. In my previous post I have already explained how it is used and described some of its characteristics.
The Ethereum virtual machine (EVM) is a simple but powerful complete 256 -bit virtual machine that allows anyone to execute arbitrary EVM byte code.
The Go-Ethereum project contains two implementations of the EVM. A simple and simple VM of the byte code And a more sophisticated Jit-vm. In this article, I will explain some of the differences between the two implementations and describe some of the characteristics of the JIT EVM and why it can be so much faster than the EVM of the byte code.
Go-etreum’s virtual bytes code
The interns of the EVM are quite simple; He has a unique loop that will try to execute the instruction at present Program counter (PC in short). In this loop, the Gas is calculated for each instruction, the memory is extended if necessary and performs the instruction if the preamble succeeds. This will continue until the virtual machine ends free of charge or returns with an error by launching an exception (for example out of gas).
for op = contract(pc) {
if !sufficientGas(op) {
return error("insufficient gas for op:", or)
}
switch op {
case ...:
/* execute */
case RETURN:
return memory(stack(-1), stack(-2))
}
pc++
}
At the end of the execution loop, the program counter obtains the increasing to execute the following instructions and continues to do so until it is finished.
EVM has another way of change program compensation through something called leap-instructions (LEAP & JUCI). Instead of leaving the program’s increment (PC ++), the EVM can also move to arbitrary positions in the contract code. The EVM knows two jump instructions, a normal jump that can be read as “Jump in position X“And a conditional jump that has read like”jump to position X if the condition is true». When such a jump occurs, he must always land on a jump. If the program lands on an instruction other than a jumping destination, the program fails – in other words, for a jump to be valid, it must always be followed by a jump instruction if the condition has given true.
Before executing any Ethereum program, the EVM itery on the code and finds all the possible jumping distains, it then puts them on a card which can be referenced by the program counter to find them. Whenever the EVM meets jump instructions, the validity of the jump is verified.
As you can see, the execution code is relatively easy and simply interpreted by the virtual byte code machine, we can even conclude that by its simplicity, it is actually quite stupid.
Welcome Jit VM
JIT-EVM adopts a different approach to the execution of the EVM byte code and is by definition initially slower than the virtual machine of the byte code. Before the virtual machine can execute any code, it must first compile The byte code in components that can be understood by the Jit virtual machine.
The initialization and execution procedure is done in 3 steps:
- We check if there is a jit program ready to be executed using the hash code –H (C) is used as an identifier to identify the program;
- If a program was found, we execute the program and refer the result;
- If no program has been found, we execute the byte code And We comply a jit program in the background.
At the start, I tried to check if the JIT program had finished the compilation and move the execution to the JIT – all this occurred during the execution in the same loop using Go’s atomic Package – Unfortunately, it turned out to be slower than letting the virtual byte code machine run and use the JIT program for each sequential call once the program compilation is completed.
By compiling the byte code in the logical parts, the JIT has the capacity to analyze the code more precisely and to optimize where and whenever necessary.
For example, an incredible simple optimization that I made was to compile several push operation in a single instruction. Let’s take the CALL instruction; The call requires 7 push instructions-that is to say gas, address, value, entry, entry size, return-off, return and return-size-before running it, and what I did instead of making a loop via these 7 instructions, executing them one by one, I optimized by taking the 7 instructions and adding the 7 values in a single tranche. Now, whenever the to start Of the 7 push instructions are executed, he rather performs the only optimized instruction by immediately adding the static edge to the VM battery. Now, of course, it only works for static values (that is to say Push 0x10), But these are much present in the code.
I also optimized the static jump instructions. Static jumps are jumps that always jump to the same position (that is, Push 0x1, jump) And never change in any case. By determining which jumps are static, we can predict a jump is valid and is within the limits of the contract and if this is the case, we create new instructions which replace both the push And leapinstruction and is reported as valid. This prevents the virtual machine from having two instructions and prevents it from checking if the jump is valid and doing an expensive hash-map search for a valid jump position.
Following steps
A complete analysis of the battery and memory would also adapt to this model where large pieces of code could adapt to unique instructions. In addition, I would like to add Symbolic execution And transform the Jit into an appropriate jit-VM. I think this would be a next logical step once the programs will become large enough to take advantage of these optimizations.
Conclusion
OUr Jit-VM is much smarter than the virtual byte code machine, but is far from completely finished (if ever). There are many other smart tips that we could add with this structure, but are simply not realistic at the moment. The execution time is within the limits of being rapid “reasonable”. That the need arises to further optimize the virtual machine, we have the tools to do so.
More code reading
Published cross of –