nimbus-eth1/nimbus/vm2/interpreter_dispatch.nim

177 lines
5.4 KiB
Nim
Raw Normal View History

# Nimbus
# Copyright (c) 2018 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.
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
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()
proc executeOpcodes(c: Computation) =
let fork = c.fork
block:
if not c.continuation.isNil:
c.continuation = nil
elif c.execPrecompiles(fork):
break
try:
c.selectVM(fork)
except CatchableError as e:
c.setError(&"Opcode Dispatch Error msg={e.msg}, depth={c.msg.depth}", true)
if c.isError() and c.continuation.isNil:
if c.tracingEnabled: c.traceError()
debug "executeOpcodes error", msg=c.error.info