This EIP introduces multibyte opcodes prefixed by C0
for 64-bit arithmetic (C001
-C00B
), comparison (C010
-C015
), bitwise (C016
-C019
) and flow (C056
and C057
in "legacy", or C0E1
and C0E2
in EOFv1) operations.
Not all computations in EVM can utilize the full 256-bit integer width. It can therefore be beneficial to have a "64-bit mode" to avoid unnecessary cycles. This EIP uses a "prefix" opcode C0
, essentially forming multibyte opcodes to avoid polluting the EVM opcode space too much.
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174.
This EIP uses the prefix opcode C0
, and it only occupies this single EVM opcode space. Upon the interpreter encountering opcode C0
, it MUST continue to seek the next byte in code. It then executes things in "64-bit mode", based on the second byte, described below. If the execution is successful, then the interpreter MUST increase PC
by 2 (instead of 1).
If the second byte is not a valid 64-bit mode operation, then the interpreter MUST OOG.
In 64-bit mode, all operations only work on the least significant 64-bit of each stack value. The most significant 192-bit is discarded. When a result value is pushed back onto the stack, then it MUST ensures that observable effects will see that the most significant 192-bit is zero. Note that here it's not necessary for an interpreter to reset the most significant 192-bit to zero every time -- if the next opcode is still in 64-bit mode, then the most significant 192-bit is still unobservable. We discuss the full details in the "rationale" section. The interpreter only needs to reproduce the full 256-bit value upon entering non-64-bit mode. If the full computational heavy part can be written in pure 64-bit mode, then this can result in noticable performance gain.
We define the following gas cost constants:
G_BASE64
: 1G_VERYLOW64
: 2G_LOW64
: 3G_MID64
: 5G_HIGH64
: 7G_EXP64_STATIC
: 5G_EXP64_DYNAMIC
: 25G_RJUMPIV64
: 3The 64-bit mode arithmetic opcodes are defined the same as non-64-bit mode, except that it only operates on the least significant 64-bits. In the below definition, a
, b
, N
is a mod 2^64
, b mod 2^64
and N mod 2^64
.
C001
) and SUB (C003
): a op b mod 2^64
, gas cost G_VERYLOW64
.C002
), DIV (C004
), SDIV (C005
), MOD (C006
), SMOD (C007
), SIGNEXTEND (C00B
): a op b mod 2^64
, gas cost G_LOW64
.C008
), MULMOD (C009
): a op b % N mod 2^64
, gas cost G_MID64
.C00A
): a EXP b mod 2^64
, gas cost static_gas = G_EXP64_STATIC, dynamic_gas = G_EXP64_DYNAMIC * exponent_byte_size
.The 64-bit mode comparison and bitwise opcodes are defined the same as non-64-bit mode, except that they only operates on the least significant 64 bits.
C010
), GT (C011
), SLT (C012
), SGT (C013
), EQ (C014
), AND (C016
), OR (C017
), XOR (C018
): a op b mod 2^64
, gas cost G_VERYLOW64
C015
), NOT (C019
): op a mod 2^64
, gas cost G_VERYLOW64
C01B
), SHR (C01C
), SAR (C01D
): a op N mod 2^64
, gas cost G_VERYLOW64
Note that:
C014
) may return true for two different integers because it'll only compare the least significant 64 bits.C015
) may return true for non-zero 256-bit integers as long as the last 64 bits are zero.1A
) does not have 64-bit mode because it affects endianness.For flow operations JUMP and JUMPI, the behavior is as follows:
JUMP
will only read the last 64 bits from the stack value. The rest 192 bits are discarded without reading. Gas cost is G_MID64
.JUMPI
will only read the last 64 bits from the stack as destination, and the condition check will only read the last 64 bits. Gas cost is G_HIGH64
.JUMPDEST
validation phrase, the next byte followed by C0
is considered data (same as in PUSH*
opcodes) and does not mark a valid JUMPDEST
destination.If EOFv1 mode is entered, then an additional validation step is added. If the opcode C0
is encountered and it is not part of PUSH opcode's data, then the interpreter MUST validate that:
For flow operations RJUMPI and RJUMPV, the 64-bit mode has following changes:
RJUMPI
, the condition popped from stack is only read for the last 64 bits. Gas cost is G_RJUMPIV64
.RJUMPV
, the case popped from stack is only read for the last 64 bits. Gas cost is G_RJUMPIV64
.Note that:
RJUMP
is automatically in 64-bit mode because it does not read or write the stack.When a smart contract uses the 64-bit mode, it's expected that once entered, it will want to stay in 64-bit mode, and only exit to non-64-bit mode when the computationally intensive function is finished. This EIP is designed particularly with this fact in mind.
All 64-bit opcodes only operates on the 64-bit value. It totally discards the rest 192 bits. The interpreter only needs to ensure that when it exits into non-64-bit mode and next time when a value result is read, that value has the first 192 bits reset to zero. The EVM interpreter can therefore use a typed stack for optmization:
type StackItem = Value U256 | Value64 U64
The typed stack can also be implemented as a bitmap for memory alignment.
For all inputs of 64-bit opcodes, it will either read in a Value
, when it'll only take the last 64 bits, or a Value64
, which is what is needed. It then always outputs a Value64
. After exiting into non-64-bit mode and upon a Value64
is read, the interpreter then translate the value back to 256-bit Value
by extending the first 192 bits with zero.
The 64-bit mode does not contain any opcodes which depends on the value's endianness, therefore Value64
can also be stored in the optimal endianness of the architecture.
The 64-bit mode will not save any memory usage.
This EIP also recommends that we reserve C0
-CF
for prefix (modes) opcodes. For example, an additional modes OVERFLOW can be envisioned that changes the behavior of arithmetic opcodes from wrapping to overflow OOG, which can help to reduce, for example, the extra cycles needed for SafeMath
.
This EIP assumes that the majority of EVM implementations target native little-endian 64-bit architectures (like x86_64
, arm64
and riscv64
). A 256-bit stack item is stored efficiently internally as 4 items of 64-bit unsigned integer [u64; 4]
. This EIP works best, in terms of optimizations, for those implementations. The opcodes simply operates on the last u64
, instead of the whole [u64; 4]
. There are basically no other changes.
In 64-bit mode, the principle of the specification is that the endianness should be kept strictly as an implementation detail. There should not be any opcodes that depends on endian. This is important, because on the one hand, we must enable easy and fast interop for 64-bit and non-64-bit opcodes. On the other hand, the interpreter should be able to do optimizations internally whether it uses little or big endian.
Due to the issue of endianness, this EIP currently does not contain 64-bit mode BYTE64
, memory opcodes MLOAD64
and MSTORE64
, and stack push opcodes PUSH*64
(PUSH1
to PUSH8
).
It is possible to extend EVM64 where "64-bit mode" is instead defined as "little-endian 64-bit mode", which can be separately discussed and specified:
BYTE64
will be little-endian, so instead of (x >> (248 - i * 8)) & 0xFF
, it will be (x >> i * 8) & 0xFF
.MLOAD64
and MSTORE64
loads and store little-endian 64-bit values in memory.PUSH*64
(PUSH1
to PUSH8
) accepts literal little-endian bytes.In specification there is no issue with the design, but this can bring signficant confusions to developers. So the author advise caution.
POP
, DUP*
, SWAP*
There is no explicit 64-bit mode for stack operations:
POP
is "automatically 64-bit" because it only removes values from the stack.DUP*
and SWAP*
will copy or swap stack items optimized for 64-bit as is. They are kept optimized, so there's no need for seperate 64-bit mode for those opcodes.Known drawbacks and tradeoffs of 64-bit mode includes:
This EIP introduces a new (prefix) opcode C0
. C0
was previously an invalid opcode that has little usage, and thus the backward compatibility issues are minimal.
Test cases are orgnized as [stack_item_1, stack_item_2] C0 opcode => [result_stack_item_1, result_stack_item_2]
.
[ff00000000000000 000000000000000 0000000000000ff 000000000000001, ff0000000000000 000000000000000 0000000000000ff f0000000000000f] C0 SHR => [<0> 780000000000007]
To be added.
To be added.
Needs discussion.
Copyright and related rights waived via CC0.