More flexible gas cost definitions, start registering more opcodes in the runner

This commit is contained in:
Alexander Ivanov 2018-01-29 15:56:51 +02:00
parent 38a02250b5
commit fba1052b4c
8 changed files with 96 additions and 20 deletions

View File

@ -11,6 +11,8 @@ proc memoryGasCost*(sizeInBytes: Int256): Int256 =
totalCost = linearCost + quadraticCost
result = totalCost
const VARIABLE_GAS_COST_OPS* = {Op.Exp}
type
BaseComputation* = ref object of RootObj
# The execution computation
@ -36,8 +38,11 @@ type
erasesReturnData*: bool
Opcode* = ref object of RootObj
kind*: Op
gasCost*: Int256
case kind*: Op
of VARIABLE_GAS_COST_OPS:
gasCostHandler*: proc(computation: var BaseComputation): Int256
else:
gasCostConstant*: Int256
runLogic*: proc(computation: var BaseComputation)
proc newBaseComputation*(vmState: BaseVMState, message: Message): BaseComputation =

View File

@ -10,6 +10,9 @@ type
proc int256*(i: int): Int256 =
i.initBigInt
template i256*(i: int): Int256 =
i.int256
# TODO
# We'll have a fast fixed int256, for now this works
@ -47,6 +50,10 @@ proc `mod`*(a: Int256, b: int): Int256 =
proc `div`*(a: Int256, b: int): Int256 =
a div b.int256
proc log256*(a: Int256): Int256 =
# TODO
2.int256
proc setXLen[T](s: var seq[T]; newlen: Natural) =
if s.isNil:
s = newSeq[T](newlen)

View File

@ -61,6 +61,10 @@ type
## Error raised to indicate an attempt was made to read data beyond the
## boundaries of the buffer (such as with RETURNDATACOPY)
TypeError* = object of VMError
## Error when invalid values are found
proc makeVMError*(): VMError =
result.burnsGas = true
result.erasesReturnData = true

View File

@ -76,20 +76,20 @@ proc sdiv*(computation: var BaseComputation) =
pushRes()
# no curry
proc exp*(computation: var BaseComputation, gasPerByte: Int256) =
proc exp*(computation: var BaseComputation) =
# Exponentiation
var (base, exponent) = computation.stack.popInt(2)
var bitSize = 0.int256 # TODO exponent.bitLength()
var byteSize = ceil8(bitSize) div 8
var res = if base == 0: 0.int256 else: (base ^ exponent.getInt) mod constants.UINT_256_CEILING
computation.gasMeter.consumeGas(
gasPerByte * byteSize,
reason="EXP: exponent bytes"
)
# computation.gasMeter.consumeGas(
# gasPerByte * byteSize,
# reason="EXP: exponent bytes"
# )
pushRes()
proc signextend(computation: var BaseComputation) =
proc signextend*(computation: var BaseComputation) =
# Signed Extend
var (bits, value) = computation.stack.popInt(2)

View File

@ -4,18 +4,30 @@ import
template run*(opcode: Opcode, computation: var BaseComputation) =
# Hook for performing the actual VM execution
computation.gasMeter.consumeGas(opcode.gasCost, reason = $opcode.kind)
computation.gasMeter.consumeGas(opcode.gasCost(computation), reason = $opcode.kind)
opcode.runLogic(computation)
method logger*(opcode: Opcode): Logger =
logging.getLogger(&"vm.opcode.{opcode.kind}")
proc newOpcode*(kind: Op, gasCost: Int256, logic: proc(computation: var BaseComputation)): Opcode =
Opcode(kind: kind, gasCost: gasCost, runLogic: logic)
method gasCost*(opcode: Opcode, computation: var BaseComputation): Int256 =
if opcode.kind in VARIABLE_GAS_COST_OPS:
opcode.gasCostHandler(computation)
else:
opcode.gasCostConstant
template newOpcode*(kind: Op, gasCost: Int256, logic: proc(computation: var BaseComputation)): Opcode =
Opcode(kind: kind, gasCostConstant: gasCost, runLogic: logic)
template newOpcode*(kind: Op, gasHandler: proc(computation: var BaseComputation): Int256, logic: proc(computation: var BaseComputation)): Opcode =
Opcode(kind: kind, gasCostHandler: gasHandler, runLogic: logic)
method `$`*(opcode: Opcode): string =
&"{opcode.kind}(0x{opcode.kind.int.toHex(2)}: {opcode.gasCost})"
let gasCost = if opcode.kind in VARIABLE_GAS_COST_OPS:
"variable"
else:
$opcode.gasCostConstant
&"{opcode.kind}(0x{opcode.kind.int.toHex(2)}: {gasCost})"
macro initOpcodes*(spec: untyped): untyped =
var value = ident("value")
@ -27,8 +39,12 @@ macro initOpcodes*(spec: untyped): untyped =
let op = child[0]
let gasCost = child[1][0][0]
let handler = child[1][0][1]
var opcode = quote:
`value`[`op`] = newOpcode(`op`, `gasCost`, `handler`)
let opcode = if gasCost.repr[0].isLowerAscii():
quote:
`value`[`op`] = Opcode(kind: `op`, gasCostHandler: `gasCost`, runLogic: `handler`)
else:
quote:
`value`[`op`] = Opcode(kind: `op`, gasCostConstant: `gasCost`, runLogic: `handler`)
result[1].add(opcode)
result[1].add(value)

View File

@ -1,14 +1,30 @@
import
strformat, strutils, tables, macros,
constants, bigints, errors, logging, vm_state,
vm / [gas_meter, stack, code_stream, memory, message, value], db / chain, computation, opcode, opcode_values, utils / [header, address],
vm / [gas_meter, stack, code_stream, memory, message, value, gas_costs], db / chain, computation, opcode, opcode_values, utils / [header, address],
logic / [arithmetic, comparison]
var opcodes = initOpcodes:
Op.Add: GAS_VERY_LOW add
Op.Sub: GAS_VERY_LOW sub
Op.Mul: GAS_LOW mul
Op.Div: GAS_LOW divide
# arithmetic
Op.Add: GAS_VERY_LOW add
Op.Mul: GAS_LOW mul
Op.Sub: GAS_VERY_LOW sub
Op.Div: GAS_LOW divide
Op.SDiv: GAS_LOW sdiv
Op.Mod: GAS_LOW modulo
Op.SMod: GAS_LOW smod
Op.AddMod: GAS_MID addmod
Op.MulMod: GAS_MID mulmod
Op.Exp: expGasCost arithmetic.exp
Op.SignExtend: GAS_LOW signextend
# comparison
Op.Lt: GAS_VERY_LOW lt
Op.Gt: GAS_VERY_LOW gt
Op.SLt: GAS_VERY_LOW slt
Op.SGt: GAS_VERY_LOW sgt
Op.Eq: GAS_VERY_LOW eq
var mem = newMemory(pow(1024.int256, 2))
@ -81,8 +97,12 @@ macro runOpcodes*(computation: untyped, program: untyped): untyped =
# useful for testing simple cases
runOpcodes(c):
stack: @[2.vint, 2.vint, 2.vint]
stack: @[2.vint, 2.vint, 2.vint, 2.vint, 2.vint, 2.vint, 4.vint]
Op.Add
Op.Mul
Op.Div
Op.Sub
Op.Mul
Op.Mul

8
src/vm/gas_costs.nim Normal file
View File

@ -0,0 +1,8 @@
import
strformat, bigints,
../constants, ../opcode, ../computation, stack
proc expGasCost*(computation: var BaseComputation): Int256 =
let arg = computation.stack.getInt(0)
result = if arg == 0: 10.i256 else: (10.i256 + 10.i256 * (1.i256 + arg.log256))

View File

@ -168,6 +168,22 @@ proc dup*(stack: var Stack; position: int) =
&"Insufficient stack items for DUP{position}")
proc getInt*(stack: Stack, position: int): Int256 =
let element = stack.values[position]
case element.kind:
of VInt:
result = element.i
else:
raise newException(TypeError, "Expected int")
proc getBinary*(stack: Stack, position: int): cstring =
let element = stack.values[position]
case element.kind:
of VBinary:
result = element.b
else:
raise newException(TypeError, "Expected binary")
proc `$`*(stack: Stack): string =
let values = stack.values.mapIt(&" {$it}").join("\n")
&"Stack:\n{values}"