simplify interpreter_dispatch.nim code

details:
  replace generated macro loop/switch by explicit call using the
  fork/op handler matrix (encapsulated via opHandlersRun() function)
This commit is contained in:
Jordan Hrycaj 2021-04-20 12:14:43 +01:00 committed by zah
parent 2fb18bf88c
commit b388e966cc
3 changed files with 96 additions and 173 deletions

View File

@ -63,7 +63,7 @@ proc mkOpTable(select: Fork): array[Op,Vm2OpExec] {.compileTime.} =
result[op].name = "toBeReplacedByBreak"
# ------------------------------------------------------------------------------
# Public handler tables
# Public functions
# ------------------------------------------------------------------------------
const
@ -73,39 +73,66 @@ const
rc[w] = w.mkOpTable
rc
type
hdlRec = tuple
name: string
info: string
run: Vm2OpFn
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
#
# vm2OpHandlers[fork][op].exec.run
#
# 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 vm2OpHandlers[] matrix is set up correctly.
# Linearising/flattening the index has no effect here.
#
vmOpHandlers = block:
var rc: array[Fork, array[Op, hdlRec]]
for fork in Fork:
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.run
rc
proc opHandlersRun*(fork: Fork; op: Op; d: Vm2Ctx) {.inline.} =
## Given a particular `fork` and an `op`-code, run the associated handler
vmOpHandlers[fork][op].run(d)
proc opHandlersName*(fork: Fork; op: Op): string =
## Get name (or ID) of op handler
vmOpHandlers[fork][op].name
proc opHandlersInfo*(fork: Fork; op: Op): string =
## Get some op handler info
vmOpHandlers[fork][op].info
# ------------------------------------------------------------------------------
# Debugging ...
# ------------------------------------------------------------------------------
when isMainModule and isNoisy:
var dummy = 0
proc gdbBPSink() =
dummy.inc
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)
gdbBPSink()
echo ">>> berlin[shl]: ",
vm2OpHandlers[FkBerlin][Shl].info
echo ">>> berlin[push32]: ",
vm2OpHandlers[FkBerlin][Push32].info
echo ">>> berlin[dup16]: ",
vm2OpHandlers[FkBerlin][Dup16].info
echo ">>> berlin[swap16]: ",
vm2OpHandlers[FkBerlin][Swap16].info
echo ">>> berlin[log4]: ",
vm2OpHandlers[FkBerlin][Log4].info
echo ">>> frontier[sstore]: ",
vm2OpHandlers[FkFrontier][Sstore].info
echo ">>> constantinople[sstore]: ",
vm2OpHandlers[FkConstantinople][Sstore].info
echo ">>> berlin[sstore]: ",
vm2OpHandlers[FkBerlin][Sstore].info
echo ">>> frontier[sstore]: ", FkFrontier.opHandlersInfo(Sstore)
echo ">>> constantinople[sstore]: ", FkConstantinople.opHandlersInfo(Sstore)
echo ">>> berlin[sstore]: ", FkBerlin.opHandlersInfo(Sstore)
# ------------------------------------------------------------------------------
# End

View File

@ -114,8 +114,7 @@ type
const
vm2OpIgnore*: Vm2OpFn = ## No operation, placeholder function
proc(k: Vm2Ctx) {.gcsafe.} =
discard
proc(k: Vm2Ctx) = discard
# similar to: toSeq(Fork).mapIt({it}).foldl(a+b)
Vm2OpAllForks* =

View File

@ -6,156 +6,53 @@
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
tables, macros,
chronicles,
./interpreter/op_handlers/oph_defs,
./interpreter/[forks_list, op_handlers, op_codes, v2gas_costs, gas_meter],
./code_stream, ./v2types, ./v2precompiles, ./stack
./interpreter/[gas_meter, op_handlers, op_handlers/oph_defs, v2gas_costs],
./code_stream, ./v2types, ./v2precompiles
logScope:
topics = "vm opcode"
proc opTableToCaseStmt(c: NimNode; fork: Fork): NimNode =
let instr = quote do: `c`.instr
result = nnkCaseStmt.newTree(instr)
# Add a branch for each (opcode, proc) pair
# We dispatch to the next instruction at the end of each branch
for op in Op:
let asOp = quote do: Op(`op`) # TODO: unfortunately when passing to runtime, ops are transformed into int
let branchStmt = block:
if op == Stop:
quote do:
trace "op: Stop"
if not `c`.code.atEnd() and `c`.tracingEnabled:
# we only trace `REAL STOP` and ignore `FAKE STOP`
`c`.opIndex = `c`.traceOpCodeStarted(`asOp`)
`c`.traceOpCodeEnded(`asOp`, `c`.opIndex)
break
else:
if BaseGasCosts[op].kind == GckFixed:
quote do:
if `c`.tracingEnabled:
`c`.opIndex = `c`.traceOpCodeStarted(`asOp`)
`c`.gasMeter.consumeGas(`c`.gasCosts[`asOp`].cost, reason = $`asOp`)
var desc: Vm2Ctx
desc.cpt = `c`
vm2OpHandlers[Fork(`fork`)][`asOp`].exec.run(desc)
if `c`.tracingEnabled:
`c`.traceOpCodeEnded(`asOp`, `c`.opIndex)
when `asOp` in {Create, Create2, Call, CallCode, DelegateCall, StaticCall}:
if not `c`.continuation.isNil:
return
else:
quote do:
if `c`.tracingEnabled:
`c`.opIndex = `c`.traceOpCodeStarted(`asOp`)
var desc: Vm2Ctx
desc.cpt = `c`
vm2OpHandlers[Fork(`fork`)][`asOp`].exec.run(desc)
if `c`.tracingEnabled:
`c`.traceOpCodeEnded(`asOp`, `c`.opIndex)
when `asOp` in {Create, Create2, Call, CallCode, DelegateCall, StaticCall}:
if not `c`.continuation.isNil:
return
when `asOp` in {Return, Revert, SelfDestruct}:
break
result.add nnkOfBranch.newTree(
newIdentNode($op),
branchStmt
)
# Wrap the case statement in while true + computed goto
result = quote do:
if `c`.tracingEnabled:
`c`.prepareTracer()
while true:
`instr` = `c`.code.next()
#{.computedGoto.}
# computed goto causing stack overflow, it consumes a lot of space
# we could use manual jump table instead
# TODO lots of macro magic here to unravel, with chronicles...
# `c`.logger.log($`c`.stack & "\n\n", fgGreen)
`result`
macro genFrontierDispatch(c: Computation): untyped =
result = opTableToCaseStmt(c, FkFrontier)
macro genHomesteadDispatch(c: Computation): untyped =
result = opTableToCaseStmt(c, FkHomestead)
macro genTangerineDispatch(c: Computation): untyped =
result = opTableToCaseStmt(c, FkTangerine)
macro genSpuriousDispatch(c: Computation): untyped =
result = opTableToCaseStmt(c, FkSpurious)
macro genByzantiumDispatch(c: Computation): untyped =
result = opTableToCaseStmt(c, FkByzantium)
macro genConstantinopleDispatch(c: Computation): untyped =
result = opTableToCaseStmt(c, FkConstantinople)
macro genPetersburgDispatch(c: Computation): untyped =
result = opTableToCaseStmt(c, FkPetersburg)
macro genIstanbulDispatch(c: Computation): untyped =
result = opTableToCaseStmt(c, FkIstanbul)
macro genBerlinDispatch(c: Computation): untyped =
result = opTableToCaseStmt(c, FkBerlin)
proc frontierVM(c: Computation) =
genFrontierDispatch(c)
proc homesteadVM(c: Computation) =
genHomesteadDispatch(c)
proc tangerineVM(c: Computation) =
genTangerineDispatch(c)
proc spuriousVM(c: Computation) {.gcsafe.} =
genSpuriousDispatch(c)
proc byzantiumVM(c: Computation) {.gcsafe.} =
genByzantiumDispatch(c)
proc constantinopleVM(c: Computation) {.gcsafe.} =
genConstantinopleDispatch(c)
proc petersburgVM(c: Computation) {.gcsafe.} =
genPetersburgDispatch(c)
proc istanbulVM(c: Computation) {.gcsafe.} =
genIstanbulDispatch(c)
proc berlinVM(c: Computation) {.gcsafe.} =
genBerlinDispatch(c)
proc selectVM(c: Computation, fork: Fork) {.gcsafe.} =
# TODO: Optimise getting fork and updating opCodeExec only when necessary
case fork
of FkFrontier:
c.frontierVM()
of FkHomestead:
c.homesteadVM()
of FkTangerine:
c.tangerineVM()
of FkSpurious:
c.spuriousVM()
of FkByzantium:
c.byzantiumVM()
of FkConstantinople:
c.constantinopleVM()
of FkPetersburg:
c.petersburgVM()
of FkIstanbul:
c.istanbulVM()
else:
c.berlinVM()
if c.tracingEnabled:
c.prepareTracer()
while true:
c.instr = c.code.next()
var
op = c.instr
desc: Vm2Ctx
desc.cpt = c
if op == Stop:
trace "op: Stop"
if not c.code.atEnd() and c.tracingEnabled:
c.opIndex = c.traceOpCodeStarted(Stop)
c.traceOpCodeEnded(Stop, c.opIndex)
break
if c.tracingEnabled:
c.opIndex = c.traceOpCodeStarted(op)
if BaseGasCosts[op].kind == GckFixed:
c.gasMeter.consumeGas(c.gasCosts[op].cost, reason = $op)
opHandlersRun(fork, op, desc)
if c.tracingEnabled:
c.traceOpCodeEnded(op, c.opIndex)
case op
of Create, Create2, Call, CallCode, DelegateCall, StaticCall:
if not c.continuation.isNil:
return
of Return, Revert, SelfDestruct:
break
else:
discard
proc executeOpcodes(c: Computation) =
let fork = c.fork