Skip to content

Commit

Permalink
Merge pull request #488 from lambdaclass/optimizations
Browse files Browse the repository at this point in the history
[EVM-Equivalence-YUL] Opcode Optimizations
  • Loading branch information
jrchatruc committed May 27, 2024
2 parents b4788f6 + a702849 commit a2b77bd
Show file tree
Hide file tree
Showing 3 changed files with 825 additions and 696 deletions.
272 changes: 207 additions & 65 deletions system-contracts/contracts/EvmInterpreterFunctions.template.yul
Original file line number Diff line number Diff line change
Expand Up @@ -75,25 +75,20 @@ function MAX_UINT() -> max_uint {
}

// It is the responsibility of the caller to ensure that ip >= BYTECODE_OFFSET + 32
function readIP(ip) -> opcode {
function readIP(ip,maxAcceptablePos) -> opcode {
// TODO: Why not do this at the beginning once instead of every time?
let bytecodeLen := mload(BYTECODE_OFFSET())

let maxAcceptablePos := add(add(BYTECODE_OFFSET(), bytecodeLen), 31)
if gt(ip, maxAcceptablePos) {
revert(0, 0)
}

opcode := and(mload(sub(ip, 31)), 0xff)
}

function readBytes(start, length) -> value {
let max := add(start, length)
for {} lt(start, max) { start := add(start, 1) } {
let next_byte := readIP(start)

value := or(shl(8, value), next_byte)
function readBytes(start, maxAcceptablePos,length) -> value {
if gt(add(start,sub(length,1)), maxAcceptablePos) {
revert(0, 0)
}
value := shr(mul(8,sub(32,length)),mload(start))
}

function dupStackItem(sp, evmGas, position) -> newSp, evmGasLeft {
Expand Down Expand Up @@ -469,12 +464,6 @@ function checkMemOverflow(location) {
}
}

// Note, that this function can overflow. It's up to the caller to ensure that it does not.
function memCost(memSizeWords) -> gasCost {
// The first term of the sum is the quadratic cost, the second one the linear one.
gasCost := add(div(mul(memSizeWords, memSizeWords), 512), mul(3, memSizeWords))
}

// This function can overflow, it is the job of the caller to ensure that it does not.
// The argument to this function is the offset into the memory region IN BYTES.
function expandMemory(newSize) -> gasCost {
Expand All @@ -488,12 +477,9 @@ function expandMemory(newSize) -> gasCost {
let newSizeInWords := div(add(newSize, 31), 32)

if gt(newSizeInWords, oldSizeInWords) {
// TODO: Check this, it feels like there might be a more optimized way
// of doing this cost calculation.
let oldCost := memCost(oldSizeInWords)
let newCost := memCost(newSizeInWords)
let new_minus_old := sub(newSizeInWords, oldSizeInWords)
gasCost := add(mul(3,new_minus_old), div(mul(new_minus_old,add(newSizeInWords,oldSizeInWords)),512))

gasCost := sub(newCost, oldCost)
mstore(MEM_OFFSET(), newSizeInWords)
}
}
Expand Down Expand Up @@ -688,6 +674,22 @@ function getNonce(addr) -> nonce {
nonce := mload(0)
}

function getRawNonce(addr) -> nonce {
mstore8(0, 0x5a)
mstore8(1, 0xa9)
mstore8(2, 0xb6)
mstore8(3, 0xb5)
mstore(4, addr)

let result := staticcall(gas(), NONCE_HOLDER_SYSTEM_CONTRACT(), 0, 36, 0, 32)

if iszero(result) {
revert(0, 0)
}

nonce := mload(0)
}

function _isEVM(_addr) -> isEVM {
// bytes4 selector = ACCOUNT_CODE_STORAGE_SYSTEM_CONTRACT.isAccountEVM.selector; (0x8c040477)
// function isAccountEVM(address _addr) external view returns (bool);
Expand Down Expand Up @@ -897,6 +899,46 @@ function getMaxExpansionMemory(retOffset,retSize,argsOffset,argsSize) -> maxExpa
}
}

function _performCall(addr,gasToPass,value,argsOffset,argsSize,retOffset,retSize,isStatic) -> success, frameGasLeft, gasToPassNew{
gasToPassNew := gasToPass
let is_evm := _isEVM(addr)
if isStatic {
if value {
revert(0, 0)
}
success, frameGasLeft:= _performStaticCall(
is_evm,
gasToPassNew,
addr,
argsOffset,
argsSize,
retOffset,
retSize
)
}

if and(is_evm, iszero(isStatic)) {
_pushEVMFrame(gasToPassNew, isStatic)
success := call(gasToPassNew, addr, value, argsOffset, argsSize, 0, 0)
frameGasLeft := _saveReturndataAfterEVMCall(retOffset, retSize)
_popEVMFrame()
}

// zkEVM native
if and(iszero(is_evm), iszero(isStatic)) {
gasToPassNew := _getZkEVMGas(gasToPassNew)
let zkevmGasBefore := gas()
success := call(gasToPassNew, addr, value, argsOffset, argsSize, retOffset, retSize)
_saveReturndataAfterZkEVMCall()
let gasUsed := _calcEVMGas(sub(zkevmGasBefore, gas()))

frameGasLeft := 0
if gt(gasToPassNew, gasUsed) {
frameGasLeft := sub(gasToPassNew, gasUsed)
}
}
}

function performCall(oldSp, evmGasLeft, isStatic) -> extraCost, sp {
let gasToPass,addr,value,argsOffset,argsSize,retOffset,retSize

Expand Down Expand Up @@ -940,44 +982,17 @@ function performCall(oldSp, evmGasLeft, isStatic) -> extraCost, sp {
checkMemOverflow(add(argsOffset, argsSize))
checkMemOverflow(add(retOffset, retSize))

let frameGasLeft
let success

if isStatic {
if value {
revert(0, 0)
}
success, frameGasLeft:= _performStaticCall(
_isEVM(addr),
gasToPass,
addr,
argsOffset,
argsSize,
retOffset,
retSize
)
}

if and(_isEVM(addr), iszero(isStatic)) {
_pushEVMFrame(gasToPass, isStatic)
success := call(gasToPass, addr, value, argsOffset, argsSize, 0, 0)
frameGasLeft := _saveReturndataAfterEVMCall(retOffset, retSize)
_popEVMFrame()
}

// zkEVM native
if and(iszero(_isEVM(addr)), iszero(isStatic)) {
gasToPass := _getZkEVMGas(gasToPass)
let zkevmGasBefore := gas()
success := call(gasToPass, addr, value, argsOffset, argsSize, retOffset, retSize)
_saveReturndataAfterZkEVMCall()
let gasUsed := _calcEVMGas(sub(zkevmGasBefore, gas()))

frameGasLeft := 0
if gt(gasToPass, gasUsed) {
frameGasLeft := sub(gasToPass, gasUsed)
}
}
let success, frameGasLeft
success, frameGasLeft, gasToPass:= _performCall(
addr,
gasToPass,
value,
argsOffset,
argsSize,
retOffset,
retSize,
isStatic
)

extraCost := add(extraCost,sub(gasToPass,frameGasLeft))
extraCost := add(extraCost, getGasForPrecompiles(addr, argsOffset, argsSize))
Expand Down Expand Up @@ -1112,12 +1127,12 @@ function _performStaticCall(

function isAddrEmpty(addr) -> isEmpty {
isEmpty := 0
if and( and(
iszero(balance(addr)),
iszero(extcodesize(addr)) ),
iszero(getNonce(addr))
) {
isEmpty := 1
if iszero(extcodesize(addr)) { // YUL doesn't have short-circuit evaluation
if iszero(balance(addr)) {
if iszero(getRawNonce(addr)) {
isEmpty := 1
}
}
}
}

Expand Down Expand Up @@ -1205,3 +1220,130 @@ function genericCreate(addr, offset, size, sp, value, evmGasLeftOld) -> result,
back, sp := popStackItem(sp)
mstore(sub(offset, 0x80), back)
}

function performExtCodeCopy(evmGas,oldSp) -> evmGasLeft, sp {
evmGasLeft := chargeGas(evmGas, 100)

let addr, dest, offset, len
addr, sp := popStackItem(oldSp)
dest, sp := popStackItem(sp)
offset, sp := popStackItem(sp)
len, sp := popStackItem(sp)

// dynamicGas = 3 * minimum_word_size + memory_expansion_cost + address_access_cost
// minimum_word_size = (size + 31) / 32

let dynamicGas := add(
mul(3, shr(5, add(len, 31))),
expandMemory(add(dest, len))
)
if iszero(warmAddress(addr)) {
dynamicGas := add(dynamicGas, 2500)
}
evmGasLeft := chargeGas(evmGasLeft, dynamicGas)

let len_32 := shr(5, len)
for {let i := 0} lt(i, len_32) { i := add(i, 1) } {
mstore(shl(5,i),0)
}
let size_32 := shl(5,len_32)
let rest_32 := sub(len, size_32)
for {let i := 0} lt(i, rest_32) { i := add(i, 1) } {
mstore8(add(size_32,i),0)
}
// Gets the code from the addr
pop(_fetchDeployedCode(addr, add(offset, MEM_OFFSET_INNER()), len))
}

function performCreate(evmGas,oldSp,isStatic) -> evmGasLeft, sp {
evmGasLeft := chargeGas(evmGas, 32000)

if isStatic {
revert(0, 0)
}

let value, offset, size

value, sp := popStackItem(oldSp)
offset, sp := popStackItem(sp)
size, sp := popStackItem(sp)

checkMemOverflow(add(MEM_OFFSET_INNER(), add(offset, size)))

if gt(size, mul(2, MAX_POSSIBLE_BYTECODE())) {
revert(0, 0)
}

if gt(value, balance(address())) {
revert(0, 0)
}

// dynamicGas = init_code_cost + memory_expansion_cost + deployment_code_execution_cost + code_deposit_cost
// minimum_word_size = (size + 31) / 32
// init_code_cost = 2 * minimum_word_size
// code_deposit_cost = 200 * deployed_code_size
let dynamicGas := add(
shr(4, add(size, 31)),
expandMemory(add(offset, size))
)
evmGasLeft := chargeGas(evmGasLeft, dynamicGas)

let addr := getNewAddress(address())

let result
result, evmGasLeft := genericCreate(addr, offset, size, sp, value, evmGasLeft)

switch result
case 0 { sp := pushStackItem(sp, 0) }
default { sp := pushStackItem(sp, addr) }
}

function performCreate2(evmGas, oldSp, isStatic) -> evmGasLeft, sp, result, addr{
evmGasLeft := chargeGas(evmGas, 32000)

if isStatic {
revert(0, 0)
}

let value, offset, size, salt

value, sp := popStackItem(oldSp)
offset, sp := popStackItem(sp)
size, sp := popStackItem(sp)
salt, sp := popStackItem(sp)

checkMemOverflow(add(MEM_OFFSET_INNER(), add(offset, size)))

if gt(size, mul(2, MAX_POSSIBLE_BYTECODE())) {
revert(0, 0)
}

if gt(value, balance(address())) {
revert(0, 0)
}

// dynamicGas = init_code_cost + hash_cost + memory_expansion_cost + deployment_code_execution_cost + code_deposit_cost
// minimum_word_size = (size + 31) / 32
// init_code_cost = 2 * minimum_word_size
// hash_cost = 6 * minimum_word_size
// code_deposit_cost = 200 * deployed_code_size
evmGasLeft := chargeGas(evmGasLeft, add(
expandMemory(add(offset, size)),
shr(2, add(size, 31))
))

{
let hashedBytecode := keccak256(add(MEM_OFFSET_INNER(), offset), size)
mstore8(0, 0xFF)
mstore(0x01, shl(0x60, address()))
mstore(0x15, salt)
mstore(0x35, hashedBytecode)
}

addr := and(
keccak256(0, 0x55),
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
)

result, evmGasLeft := genericCreate(addr, offset, size, sp, value, evmGasLeft)
}
Loading

0 comments on commit a2b77bd

Please sign in to comment.