If you have been following the Ethereum hard fork progress, you’ll notice that a considerable number of proposed EIPs are about performance optimizations. Take EIP-145 as an example, it adds three new opcodes, SHL, SHR and SAR, defining some bitwise operations. Reading the specification, you’ll notice that two of them, SHL and SHR is equivalent to opcode sequences we already support, PUSH1 2 EXP MUL and PUSH1 2 EXP DIV. The other one, SAR, is also mostly equivalent to PUSH1 2 EXP SDIV with the only difference being the rounding.

Look deeper at the specification, you’ll also notice that the document was already drafted back in 2017, but it took more than 2 years for developers to get it on mainnet. In comparison, standard bodies such as web browsers can get hundreds of new features into production each year. I think most of us would agree that the process just took too long for a performance optimization feature like this.

In the mean time, because of the new opcodes introduced, it becomes increasingly harder for dapp developers that work on multiple blockchains, such as Ethereum and Ethereum Classic. It is not possible to compile one contract bytecode and make it run everywhere. Instead, developers must carefully tweak their compiler configurations, and make sure things are always targeted to the correct blockchain, while all of the generated bytecodes are functionally equivalent, with the only difference being the optimization.

We explored two problems above – that client implementors do not have a good method to implement performance optimizations without hard forks, thus making the process take too long, and that it is increasingly harder to compile once and run everywhere, even when the only difference is performance optimization. The second problem we described, can be solved using things like feature probe, as described in versionless EVM. In this post, however, I want to explain a different approach to solve the above two issues, by using HINT instructions, or originally known as EVM Jets.


HINT instruction always has a 4-byte tag appended to it. During execution, the opcode, together with the tag, is ignored. It executes as no-op, does not cost gas, and the only side effect bring incresing the program counter.

Hint in Practice

Many optimizations can be implemented as substitutions. For example, when the virtual machine sees PUSH1 2 EXP MUL, it can replaces it with a shift left operation which is functionally equivalent. Without hint, searching for those substitutions are not an easy thing to do. The things we want to substitute may be of different length, and the collection may be big.

With hint, compilers can emit bytecode sequence such as HINT [tag for SAR] PUSH1 2 EXP MUL. The virtual machine will only need to check the sequence specified by the given tag. This can be implemented as an one-pass against the execution code when the VM starts. We already require an one-pass for the JUMPDEST analysis, so the hint check will nearly have no performance impact at all.

Out-of-order Feature Introduction

Hard fork requires features to be introduced in order, with a fixed date for activation. HINT, on the other hand, allows certain performance optimizations to be introduced out of order. Client implementations can then implement those optimizations when they want, and deploy them when they’re ready. Contract developers can also use those hint opcodes without worrying whether each clients have added support for them.

Next: Choosing Mining Algorithms

November 19, 2019

Three criteria for Proof of Work mining algorithm selection.

Previous: Extending the Ethoxy Initiative

May 5, 2018

A New Project called multi-geth

Up: Classic in Orbit