mirror of
https://github.com/status-im/nimbus-eth1.git
synced 2025-01-13 05:44:40 +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.
151 lines
5.2 KiB
Nim
151 lines
5.2 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.
|
|
|
|
## EVM Opcode Handler Tables
|
|
## =========================
|
|
##
|
|
|
|
const
|
|
noisy {.intdefine.}: int = 0
|
|
# isNoisy {.used.} = noisy > 0
|
|
isChatty {.used.} = noisy > 1
|
|
|
|
import
|
|
strformat,
|
|
../../common/evmforks,
|
|
./op_codes,
|
|
./op_handlers/[oph_defs,
|
|
oph_arithmetic, oph_hash, oph_envinfo, oph_blockdata,
|
|
oph_memory, oph_push, oph_dup, oph_swap, oph_log,
|
|
oph_create, oph_call, oph_sysops]
|
|
|
|
const
|
|
allHandlersList = [
|
|
(VmOpExecArithmetic, "Arithmetic"),
|
|
(VmOpExecHash, "Hash"),
|
|
(VmOpExecEnvInfo, "EnvInfo"),
|
|
(VmOpExecBlockData, "BlockData"),
|
|
(VmOpExecMemory, "Memory"),
|
|
(VmOpExecPush, "Push"),
|
|
(VmOpExecPushZero, "PushZero"),
|
|
(VmOpExecDup, "Dup"),
|
|
(VmOpExecSwap, "Swap"),
|
|
(VmOpExecLog, "Log"),
|
|
(VmOpExecCreate, "Create"),
|
|
(VmOpExecCall, "Call"),
|
|
(VmOpExecSysOp, "SysOp")]
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Helper
|
|
# ------------------------------------------------------------------------------
|
|
|
|
proc mkOpTable(selected: EVMFork): array[Op,VmOpExec] {.compileTime.} =
|
|
|
|
# Collect selected <fork> entries
|
|
for (subList,subName) in allHandlersList:
|
|
for w in subList:
|
|
if selected notin w.forks:
|
|
continue
|
|
# definitions must be mutually exclusive
|
|
var prvInfo = result[w.opCode].info
|
|
if prvInfo != "" or 0 < result[w.opCode].forks.card:
|
|
echo &"*** {subName}: duplicate <{w.opCode}> entry: ",
|
|
&"\"{prvInfo}\" vs. \"{w.info}\""
|
|
doAssert result[w.opCode].info == ""
|
|
doAssert result[w.opCode].forks.card == 0
|
|
result[w.opCode] = w
|
|
|
|
# Initialise unused entries
|
|
for op in Op:
|
|
if selected notin result[op].forks:
|
|
result[op] = result[Invalid]
|
|
result[op].opCode = op
|
|
if op == Stop:
|
|
result[op].name = "toBeReplacedByBreak"
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Public functions
|
|
# ------------------------------------------------------------------------------
|
|
|
|
#const
|
|
# VmOpHandlers* = block:
|
|
# var rc: array[Fork, array[Op, VmOpExec]]
|
|
# for w in Fork:
|
|
# rc[w] = w.mkOpTable
|
|
# rc
|
|
|
|
type
|
|
vmOpHandlersRec* = tuple
|
|
name: string ## Name (or ID) of op handler
|
|
info: string ## Some op handler info
|
|
run: VmOpFn ## Executable handler
|
|
|
|
const
|
|
# Pack handler record.
|
|
#
|
|
# Important:
|
|
# As of NIM 1.2.10, this mapping to another record is crucial for
|
|
#
|
|
# vmOpHandlers[fork][op].run
|
|
#
|
|
# to pick right function when <op> is a variable . Using
|
|
#
|
|
# VmOpHandlers[fork][op].exec
|
|
#
|
|
# only works when <op> is a constant. There seems to be some optimisation
|
|
# that garbles the <exec> sub-structures elements <prep>, <run>, and <post>.
|
|
# Moreover, <post> is seen NULL under the debugger. It is untested yet
|
|
# under what circumstances the VmOpHandlers[] matrix is set up correctly.
|
|
# Linearising/flattening the index has no effect here.
|
|
#
|
|
vmOpHandlers* = ## Op handler records matrix indexed `fork` x `op`
|
|
block:
|
|
var rc: array[EVMFork, array[Op, vmOpHandlersRec]]
|
|
for fork in EVMFork:
|
|
var tab = fork.mkOpTable
|
|
for op in Op:
|
|
rc[fork][op].name = tab[op].name
|
|
rc[fork][op].info = tab[op].info
|
|
rc[fork][op].run = tab[op].exec
|
|
rc
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Debugging ...
|
|
# ------------------------------------------------------------------------------
|
|
|
|
when isMainModule and isChatty:
|
|
|
|
proc opHandlersRun(fork: EVMFork; op: Op; cpt: VmCpt) {.used.} =
|
|
## Given a particular `fork` and an `op`-code, run the associated handler
|
|
vmOpHandlers[fork][op].run(cpt)
|
|
|
|
proc opHandlersName(fork: EVMFork; op: Op): string {.used.} =
|
|
## Get name (or ID) of op handler
|
|
vmOpHandlers[fork][op].name
|
|
|
|
proc opHandlersInfo(fork: EVMFork; op: Op): string {.used.} =
|
|
## Get some op handler info
|
|
vmOpHandlers[fork][op].info
|
|
|
|
echo ">>> berlin[shl]: ", FkBerlin.opHandlersInfo(Shl)
|
|
echo ">>> berlin[push32]: ", FkBerlin.opHandlersInfo(Push32)
|
|
echo ">>> berlin[dup16]: ", FkBerlin.opHandlersInfo(Dup16)
|
|
echo ">>> berlin[swap16]: ", FkBerlin.opHandlersInfo(Swap16)
|
|
echo ">>> berlin[log4]: ", FkBerlin.opHandlersInfo(Log4)
|
|
|
|
echo ">>> frontier[sstore]: ", FkFrontier.opHandlersInfo(Sstore)
|
|
echo ">>> constantinople[sstore]: ", FkConstantinople.opHandlersInfo(Sstore)
|
|
echo ">>> berlin[sstore]: ", FkBerlin.opHandlersInfo(Sstore)
|
|
echo ">>> paris[sstore]: ", FkParis.opHandlersInfo(Sstore)
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# End
|
|
# ------------------------------------------------------------------------------
|