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:
parent
2fb18bf88c
commit
b388e966cc
|
@ -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
|
||||
|
|
|
@ -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* =
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue