diff --git a/nimbus/vm2/interpreter/op_handlers.nim b/nimbus/vm2/interpreter/op_handlers.nim index 26ca4ba7a..afaba978b 100644 --- a/nimbus/vm2/interpreter/op_handlers.nim +++ b/nimbus/vm2/interpreter/op_handlers.nim @@ -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 is a variable . Using + # + # vm2OpHandlers[fork][op].exec.run + # + # only works when is a constant. There seems to be some optimisation + # that garbles the sub-structures elements , , and . + # Moreover, 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 diff --git a/nimbus/vm2/interpreter/op_handlers/oph_defs.nim b/nimbus/vm2/interpreter/op_handlers/oph_defs.nim index 43c9c762f..98a4752bc 100644 --- a/nimbus/vm2/interpreter/op_handlers/oph_defs.nim +++ b/nimbus/vm2/interpreter/op_handlers/oph_defs.nim @@ -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* = diff --git a/nimbus/vm2/interpreter_dispatch.nim b/nimbus/vm2/interpreter_dispatch.nim index b87453109..819bea9fc 100644 --- a/nimbus/vm2/interpreter_dispatch.nim +++ b/nimbus/vm2/interpreter_dispatch.nim @@ -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