re-implemented handler-call statement by doubly nested case statement
why: the new implementation lost more then 25% execution time on the test suite when compared to the original VM. so the handler call and the surrounding statements have been wrapped in a big case statement similar to the original VM implementation. on Linux/x64, the execution time of the new VM2 seems to be on par with the old VM. details: on Linux/x64, computed goto works and is activated with the -d:release flag. here the execution time of the new VM2 was tested short of 0.02% better than the old VM. without the computed goto, it is short of 0.4% slower than the old VM.
This commit is contained in:
parent
3ed234e0a1
commit
1b3117edbd
|
@ -14,6 +14,14 @@
|
|||
## See `here <../../ex/vm/interpreter/forks_list.html>`_ for an
|
||||
## overview.
|
||||
##
|
||||
## Assumptions on the naming of the fork list:
|
||||
## * each symbol start with the prefix "Fk"
|
||||
## * the the first word of the prettified text representaion is the same
|
||||
## text as the one following the "Fk" symbol name (irrespective of
|
||||
## character case.)
|
||||
|
||||
import
|
||||
strutils
|
||||
|
||||
type
|
||||
Fork* = enum
|
||||
|
@ -27,4 +35,9 @@ type
|
|||
FkIstanbul = "istanbul"
|
||||
FkBerlin = "berlin"
|
||||
|
||||
proc toSymbolName*(fork: Fork): string =
|
||||
## Given a `fork` argument, print the symbol name so that it can be used
|
||||
## im macro staements.
|
||||
"Fk" & ($fork).split(' ')[0]
|
||||
|
||||
# End
|
||||
|
|
|
@ -208,6 +208,12 @@ type
|
|||
SelfDestruct = 0xff ## Halt execution and register account for later
|
||||
## deletion.
|
||||
|
||||
|
||||
proc toSymbolName*(op: Op): string =
|
||||
## Given an `op` argument, print the symbol name so that it can be used
|
||||
## im macro staements.
|
||||
$op
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Verify that Op is contiguous and sym names follow some standards
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -108,10 +108,10 @@ proc mkOpTable(selected: Fork): array[Op,Vm2OpExec] {.compileTime.} =
|
|||
# rc
|
||||
|
||||
type
|
||||
hdlRec = tuple
|
||||
name: string
|
||||
info: string
|
||||
run: Vm2OpFn
|
||||
vmOpHandlersRec* = tuple
|
||||
name: string ## Name (or ID) of op handler
|
||||
info: string ## Some op handler info
|
||||
run: Vm2OpFn ## Executable handler
|
||||
|
||||
const
|
||||
# Pack handler record.
|
||||
|
@ -131,33 +131,35 @@ const
|
|||
# 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: var 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
|
||||
vmOpHandlers* = ## Op handler records matrix indexed `fork` x `op`
|
||||
block:
|
||||
var rc: array[Fork, array[Op, vmOpHandlersRec]]
|
||||
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
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Debugging ...
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
when isMainModule and isNoisy:
|
||||
|
||||
proc opHandlersRun(fork: Fork; op: Op; d: var Vm2Ctx) {.used.} =
|
||||
## Given a particular `fork` and an `op`-code, run the associated handler
|
||||
vmOpHandlers[fork][op].run(d)
|
||||
|
||||
proc opHandlersName(fork: Fork; op: Op): string {.used.} =
|
||||
## Get name (or ID) of op handler
|
||||
vmOpHandlers[fork][op].name
|
||||
|
||||
proc opHandlersInfo(fork: Fork; 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)
|
||||
|
|
|
@ -8,51 +8,177 @@
|
|||
# at your option. This file may not be copied, modified, or distributed except
|
||||
# according to those terms.
|
||||
|
||||
const
|
||||
# debugging flag
|
||||
kludge {.intdefine.}: int = 0
|
||||
breakCircularDependency {.used.} = kludge > 0
|
||||
|
||||
import
|
||||
./compu_helper,
|
||||
./interpreter/[forks_list, gas_costs, gas_meter,
|
||||
op_codes, op_handlers, op_handlers/oph_defs],
|
||||
./code_stream,
|
||||
./v2types,
|
||||
chronicles
|
||||
chronicles,
|
||||
macros
|
||||
|
||||
logScope:
|
||||
topics = "vm opcode"
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Helpers
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
template handleStopDirective(k: var Vm2Ctx) =
|
||||
trace "op: Stop"
|
||||
if not k.cpt.code.atEnd() and k.cpt.tracingEnabled:
|
||||
# we only trace `REAL STOP` and ignore `FAKE STOP`
|
||||
k.cpt.opIndex = k.cpt.traceOpCodeStarted(Stop)
|
||||
k.cpt.traceOpCodeEnded(Stop, k.cpt.opIndex)
|
||||
|
||||
|
||||
template handleFixedGasCostsDirective(fork: Fork; op: Op; k: var Vm2Ctx) =
|
||||
if k.cpt.tracingEnabled:
|
||||
k.cpt.opIndex = k.cpt.traceOpCodeStarted(op)
|
||||
|
||||
k.cpt.gasMeter.consumeGas(k.cpt.gasCosts[op].cost, reason = $op)
|
||||
vmOpHandlers[fork][op].run(k)
|
||||
|
||||
if k.cpt.tracingEnabled:
|
||||
k.cpt.traceOpCodeEnded(op, k.cpt.opIndex)
|
||||
|
||||
|
||||
template handleOtherDirective(fork: Fork; op: Op; k: var Vm2Ctx) =
|
||||
if k.cpt.tracingEnabled:
|
||||
k.cpt.opIndex = k.cpt.traceOpCodeStarted(op)
|
||||
|
||||
vmOpHandlers[fork][op].run(k)
|
||||
|
||||
if k.cpt.tracingEnabled:
|
||||
k.cpt.traceOpCodeEnded(op, k.cpt.opIndex)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Private, big nasty doubly nested case matrix generator
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# reminiscent of Mamy's opTableToCaseStmt() from original VM
|
||||
proc toCaseStmt(forkArg, opArg, k: NimNode): NimNode =
|
||||
|
||||
# Outer case/switch => Op
|
||||
let branchOnOp = quote do: `opArg`
|
||||
result = nnkCaseStmt.newTree(branchOnOp)
|
||||
for op in Op:
|
||||
|
||||
# Inner case/switch => Fork
|
||||
let branchOnFork = quote do: `forkArg`
|
||||
var forkCaseSubExpr = nnkCaseStmt.newTree(branchOnFork)
|
||||
for fork in Fork:
|
||||
|
||||
let
|
||||
asFork = quote do: Fork(`fork`)
|
||||
asOp = quote do: Op(`op`)
|
||||
|
||||
let branchStmt = block:
|
||||
if op == Stop:
|
||||
quote do:
|
||||
handleStopDirective(`k`)
|
||||
elif BaseGasCosts[op].kind == GckFixed:
|
||||
quote do:
|
||||
handleFixedGasCostsDirective(`asFork`,`asOp`,`k`)
|
||||
else:
|
||||
quote do:
|
||||
handleOtherDirective(`asFork`,`asOp`,`k`)
|
||||
|
||||
forkCaseSubExpr.add nnkOfBranch.newTree(
|
||||
newIdentNode(fork.toSymbolName),
|
||||
branchStmt)
|
||||
|
||||
# Wrap innner case/switch into outer case/switch
|
||||
let branchStmt = block:
|
||||
case op
|
||||
of Create, Create2, Call, CallCode, DelegateCall, StaticCall:
|
||||
quote do:
|
||||
`forkCaseSubExpr`
|
||||
if not c.continuation.isNil:
|
||||
break
|
||||
of Stop, Return, Revert, SelfDestruct:
|
||||
quote do:
|
||||
`forkCaseSubExpr`
|
||||
break
|
||||
else:
|
||||
quote do:
|
||||
`forkCaseSubExpr`
|
||||
|
||||
result.add nnkOfBranch.newTree(
|
||||
newIdentNode(op.toSymbolName),
|
||||
branchStmt)
|
||||
|
||||
when breakCircularDependency:
|
||||
echo ">>> ", result.repr
|
||||
|
||||
|
||||
macro genDispatchMatrix(fork: Fork; op: Op; k: Vm2Ctx): untyped =
|
||||
result = fork.toCaseStmt(op, k)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public functions
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc selectVM*(c: Computation, fork: Fork) {.gcsafe.} =
|
||||
## Op code execution handler main loop.
|
||||
var desc: Vm2Ctx
|
||||
desc.cpt = c
|
||||
|
||||
if c.tracingEnabled:
|
||||
c.prepareTracer()
|
||||
|
||||
while true:
|
||||
c.instr = c.code.next()
|
||||
var op = c.instr
|
||||
|
||||
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
|
||||
# Note Mamy's observation in opTableToCaseStmt() from original VM
|
||||
# regarding computed goto
|
||||
#
|
||||
# ackn:
|
||||
# #{.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)
|
||||
|
||||
if c.tracingEnabled:
|
||||
c.opIndex = c.traceOpCodeStarted(op)
|
||||
if BaseGasCosts[op].kind == GckFixed:
|
||||
c.gasMeter.consumeGas(c.gasCosts[op].cost, reason = $op)
|
||||
when defined(release):
|
||||
#
|
||||
# FIXME: OS case list below needs to be adjusted
|
||||
#
|
||||
when defined(windows):
|
||||
when defined(cpu64):
|
||||
{.warning: "*** Win64/VM2 handler switch => computedGoto".}
|
||||
{.computedGoto, optimization: speed.}
|
||||
else:
|
||||
# computedGoto is not compiling on github/ci (out of memory) -- jordan
|
||||
{.warning: "*** Win32/VM2 handler switch => optimisation disabled".}
|
||||
# {.computedGoto, optimization: speed.}
|
||||
|
||||
desc.cpt = c
|
||||
opHandlersRun(fork, op, desc)
|
||||
elif defined(linux):
|
||||
when defined(cpu64):
|
||||
{.warning: "*** Linux64/VM2 handler switch => computedGoto".}
|
||||
{.computedGoto, optimization: speed.}
|
||||
else:
|
||||
{.warning: "*** Linux32/VM2 handler switch => computedGoto".}
|
||||
{.computedGoto, optimization: speed.}
|
||||
|
||||
if c.tracingEnabled:
|
||||
c.traceOpCodeEnded(op, c.opIndex)
|
||||
elif defined(macosx):
|
||||
when defined(cpu64):
|
||||
{.warning: "*** MacOs64/VM2 handler switch => computedGoto".}
|
||||
{.computedGoto, optimization: speed.}
|
||||
else:
|
||||
{.warning: "*** MacOs32/VM2 handler switch => computedGoto".}
|
||||
{.computedGoto, optimization: speed.}
|
||||
|
||||
case op
|
||||
of Create, Create2, Call, CallCode, DelegateCall, StaticCall:
|
||||
if not c.continuation.isNil:
|
||||
break
|
||||
of Return, Revert, SelfDestruct:
|
||||
break
|
||||
else:
|
||||
discard
|
||||
else:
|
||||
{.warning: "*** Unsupported OS => no handler switch optimisation".}
|
||||
|
||||
genDispatchMatrix(fork, c.instr, desc)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# End
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
Loading…
Reference in New Issue