mirror of
https://github.com/status-im/nimbus-eth1.git
synced 2025-01-15 23:04:34 +00:00
8857fccb44
In the current VM opcode dispatcher, a two-level case statement is generated that first matches the opcode and then uses another nested case statement to select the actual implementation based on which fork it is, causing the dispatcher to grow by `O(opcodes) * O(forks)`. The fork does not change between instructions causing significant inefficiency for this approach - not only because it repeats the fork lookup but also because of code size bloat and missed optimizations. A second source of inefficiency in dispatching is the tracer code which in the vast majority of cases is disabled but nevertheless sees multiple conditionals being evaluated for each instruction only to remain disabled throughout exeuction. This PR rewrites the opcode dispatcher macro to generate a separate dispatcher for each fork and tracer setting and goes on to pick the right one at the start of the computation. This has many advantages: * much smaller dispatcher * easier to compile * better inlining * fewer pointlessly repeated instruction * simplified macro (!) * slow "low-compiler-memory" dispatcher code can be removed Net block import improvement at about 4-6% depending on the contract - synthetic EVM benchmnarks would show an even better result most likely.
47 lines
1.8 KiB
Nim
47 lines
1.8 KiB
Nim
# Nimbus
|
|
# Copyright (c) 2018-2024 Status Research & Development GmbH
|
|
# Licensed under either of
|
|
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
|
|
# http://www.apache.org/licenses/LICENSE-2.0)
|
|
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or
|
|
# http://opensource.org/licenses/MIT)
|
|
# at your option. This file may not be copied, modified, or distributed except
|
|
# according to those terms.
|
|
|
|
{.push raises: [].}
|
|
|
|
import
|
|
eth/common, # GasInt
|
|
../evm_errors,
|
|
../types
|
|
|
|
func init*(m: var GasMeter, startGas: GasInt) =
|
|
m.gasRemaining = startGas
|
|
m.gasRefunded = 0
|
|
|
|
func consumeGas*(
|
|
gasMeter: var GasMeter; amount: GasInt; reason: static string): EvmResultVoid {.inline.} =
|
|
# consumeGas is a hotspot in the vm due to it being called for every
|
|
# instruction
|
|
# TODO report reason - consumeGas is a hotspot in EVM execution so it has to
|
|
# be done carefully
|
|
if amount > gasMeter.gasRemaining:
|
|
return err(gasErr(OutOfGas))
|
|
gasMeter.gasRemaining -= amount
|
|
ok()
|
|
|
|
func returnGas*(gasMeter: var GasMeter; amount: GasInt) =
|
|
gasMeter.gasRemaining += amount
|
|
|
|
func refundGas*(gasMeter: var GasMeter; amount: int64) =
|
|
# EIP-2183 Net gas metering for sstore is built upon idea
|
|
# that the refund counter is only one in an EVM like geth does.
|
|
# EIP-2183 gurantee that the counter can never go below zero.
|
|
# But nimbus, EVMC, and emvone taken different route, the refund counter
|
|
# is present at each level of recursion. That's why EVMC/evmone is using
|
|
# int64 while geth using uint64 for their gas calculation.
|
|
# After nimbus converting GasInt to uint64, the gas refund
|
|
# cannot be converted to uint64 too, because the sum of all children gas refund,
|
|
# no matter positive or negative will be >= 0 when EVM finish execution.
|
|
gasMeter.gasRefunded += amount
|