core/vm: use a callcontext struct (#20761)
* core/vm: use a callcontext struct * core/vm: fix tests * core/vm/runtime: benchmark * core/vm: make intpool push inlineable, unexpose callcontext
This commit is contained in:
parent
0bec6a43f6
commit
8dc8941551
|
@ -60,9 +60,9 @@ func enable1884(jt *JumpTable) {
|
|||
}
|
||||
}
|
||||
|
||||
func opSelfBalance(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
|
||||
balance := interpreter.intPool.get().Set(interpreter.evm.StateDB.GetBalance(contract.Address()))
|
||||
stack.push(balance)
|
||||
func opSelfBalance(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
|
||||
balance := interpreter.intPool.get().Set(interpreter.evm.StateDB.GetBalance(callContext.contract.Address()))
|
||||
callContext.stack.push(balance)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
@ -80,9 +80,9 @@ func enable1344(jt *JumpTable) {
|
|||
}
|
||||
|
||||
// opChainID implements CHAINID opcode
|
||||
func opChainID(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
|
||||
func opChainID(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
|
||||
chainId := interpreter.intPool.get().Set(interpreter.evm.chainConfig.ChainID)
|
||||
stack.push(chainId)
|
||||
callContext.stack.push(chainId)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -109,7 +109,7 @@ func testTwoOperandOp(t *testing.T, tests []TwoOperandTestcase, opFn executionFu
|
|||
expected := new(big.Int).SetBytes(common.Hex2Bytes(test.Expected))
|
||||
stack.push(x)
|
||||
stack.push(y)
|
||||
opFn(&pc, evmInterpreter, nil, nil, stack)
|
||||
opFn(&pc, evmInterpreter, &callCtx{nil, stack, nil})
|
||||
actual := stack.pop()
|
||||
|
||||
if actual.Cmp(expected) != 0 {
|
||||
|
@ -223,7 +223,7 @@ func getResult(args []*twoOperandParams, opFn executionFunc) []TwoOperandTestcas
|
|||
y := new(big.Int).SetBytes(common.Hex2Bytes(param.y))
|
||||
stack.push(x)
|
||||
stack.push(y)
|
||||
opFn(&pc, interpreter, nil, nil, stack)
|
||||
opFn(&pc, interpreter, &callCtx{nil, stack, nil})
|
||||
actual := stack.pop()
|
||||
result[i] = TwoOperandTestcase{param.x, param.y, fmt.Sprintf("%064x", actual)}
|
||||
}
|
||||
|
@ -260,7 +260,7 @@ func TestJsonTestcases(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func opBenchmark(bench *testing.B, op func(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error), args ...string) {
|
||||
func opBenchmark(bench *testing.B, op executionFunc, args ...string) {
|
||||
var (
|
||||
env = NewEVM(Context{}, nil, params.TestChainConfig, Config{})
|
||||
stack = newstack()
|
||||
|
@ -281,7 +281,7 @@ func opBenchmark(bench *testing.B, op func(pc *uint64, interpreter *EVMInterpret
|
|||
a := new(big.Int).SetBytes(arg)
|
||||
stack.push(a)
|
||||
}
|
||||
op(&pc, evmInterpreter, nil, nil, stack)
|
||||
op(&pc, evmInterpreter, &callCtx{nil, stack, nil})
|
||||
stack.pop()
|
||||
}
|
||||
poolOfIntPools.put(evmInterpreter.intPool)
|
||||
|
@ -509,12 +509,12 @@ func TestOpMstore(t *testing.T) {
|
|||
pc := uint64(0)
|
||||
v := "abcdef00000000000000abba000000000deaf000000c0de00100000000133700"
|
||||
stack.pushN(new(big.Int).SetBytes(common.Hex2Bytes(v)), big.NewInt(0))
|
||||
opMstore(&pc, evmInterpreter, nil, mem, stack)
|
||||
opMstore(&pc, evmInterpreter, &callCtx{mem, stack, nil})
|
||||
if got := common.Bytes2Hex(mem.GetCopy(0, 32)); got != v {
|
||||
t.Fatalf("Mstore fail, got %v, expected %v", got, v)
|
||||
}
|
||||
stack.pushN(big.NewInt(0x1), big.NewInt(0))
|
||||
opMstore(&pc, evmInterpreter, nil, mem, stack)
|
||||
opMstore(&pc, evmInterpreter, &callCtx{mem, stack, nil})
|
||||
if common.Bytes2Hex(mem.GetCopy(0, 32)) != "0000000000000000000000000000000000000000000000000000000000000001" {
|
||||
t.Fatalf("Mstore failed to overwrite previous value")
|
||||
}
|
||||
|
@ -539,7 +539,7 @@ func BenchmarkOpMstore(bench *testing.B) {
|
|||
bench.ResetTimer()
|
||||
for i := 0; i < bench.N; i++ {
|
||||
stack.pushN(value, memStart)
|
||||
opMstore(&pc, evmInterpreter, nil, mem, stack)
|
||||
opMstore(&pc, evmInterpreter, &callCtx{mem, stack, nil})
|
||||
}
|
||||
poolOfIntPools.put(evmInterpreter.intPool)
|
||||
}
|
||||
|
@ -560,7 +560,7 @@ func BenchmarkOpSHA3(bench *testing.B) {
|
|||
bench.ResetTimer()
|
||||
for i := 0; i < bench.N; i++ {
|
||||
stack.pushN(big.NewInt(32), start)
|
||||
opSha3(&pc, evmInterpreter, nil, mem, stack)
|
||||
opSha3(&pc, evmInterpreter, &callCtx{mem, stack, nil})
|
||||
}
|
||||
poolOfIntPools.put(evmInterpreter.intPool)
|
||||
}
|
||||
|
|
|
@ -63,6 +63,14 @@ type Interpreter interface {
|
|||
CanRun([]byte) bool
|
||||
}
|
||||
|
||||
// callCtx contains the things that are per-call, such as stack and memory,
|
||||
// but not transients like pc and gas
|
||||
type callCtx struct {
|
||||
memory *Memory
|
||||
stack *Stack
|
||||
contract *Contract
|
||||
}
|
||||
|
||||
// keccakState wraps sha3.state. In addition to the usual hash methods, it also supports
|
||||
// Read to get a variable amount of data from the hash state. Read is faster than Sum
|
||||
// because it doesn't copy the internal state, but also modifies the internal state.
|
||||
|
@ -160,9 +168,14 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
|
|||
}
|
||||
|
||||
var (
|
||||
op OpCode // current opcode
|
||||
mem = NewMemory() // bound memory
|
||||
stack = newstack() // local stack
|
||||
op OpCode // current opcode
|
||||
mem = NewMemory() // bound memory
|
||||
stack = newstack() // local stack
|
||||
callContext = &callCtx{
|
||||
memory: mem,
|
||||
stack: stack,
|
||||
contract: contract,
|
||||
}
|
||||
// For optimisation reason we're using uint64 as the program counter.
|
||||
// It's theoretically possible to go above 2^64. The YP defines the PC
|
||||
// to be uint256. Practically much less so feasible.
|
||||
|
@ -194,7 +207,12 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
|
|||
// explicit STOP, RETURN or SELFDESTRUCT is executed, an error occurred during
|
||||
// the execution of one of the operations or until the done flag is set by the
|
||||
// parent context.
|
||||
for atomic.LoadInt32(&in.evm.abort) == 0 {
|
||||
steps := 0
|
||||
for {
|
||||
steps++
|
||||
if steps%1000 == 0 && atomic.LoadInt32(&in.evm.abort) != 0 {
|
||||
break
|
||||
}
|
||||
if in.cfg.Debug {
|
||||
// Capture pre-execution values for tracing.
|
||||
logged, pcCopy, gasCopy = false, pc, contract.Gas
|
||||
|
@ -267,7 +285,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
|
|||
}
|
||||
|
||||
// execute the operation
|
||||
res, err = operation.execute(&pc, in, contract, mem, stack)
|
||||
res, err = operation.execute(&pc, in, callContext)
|
||||
// verifyPool is a build flag. Pool verification makes sure the integrity
|
||||
// of the integer pool by comparing values to a default value.
|
||||
if verifyPool {
|
||||
|
|
|
@ -53,6 +53,17 @@ func (p *intPool) getZero() *big.Int {
|
|||
return new(big.Int)
|
||||
}
|
||||
|
||||
// putOne returns an allocated big int to the pool to be later reused by get calls.
|
||||
// Note, the values as saved as is; neither put nor get zeroes the ints out!
|
||||
// As opposed to 'put' with variadic args, this method becomes inlined by the
|
||||
// go compiler
|
||||
func (p *intPool) putOne(i *big.Int) {
|
||||
if len(p.pool.data) > poolLimit {
|
||||
return
|
||||
}
|
||||
p.pool.push(i)
|
||||
}
|
||||
|
||||
// put returns an allocated big int to the pool to be later reused by get calls.
|
||||
// Note, the values as saved as is; neither put nor get zeroes the ints out!
|
||||
func (p *intPool) put(is ...*big.Int) {
|
||||
|
|
|
@ -23,7 +23,7 @@ import (
|
|||
)
|
||||
|
||||
type (
|
||||
executionFunc func(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error)
|
||||
executionFunc func(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error)
|
||||
gasFunc func(*EVM, *Contract, *Stack, *Memory, uint64) (uint64, error) // last parameter is the requested memory size as a uint64
|
||||
// memorySizeFunc returns the required size, and whether the operation overflowed a uint64
|
||||
memorySizeFunc func(*Stack) (size uint64, overflow bool)
|
||||
|
|
|
@ -316,3 +316,31 @@ func TestBlockhash(t *testing.T) {
|
|||
t.Errorf("suboptimal; too much chain iteration, expected %d, got %d", exp, got)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkSimpleLoop test a pretty simple loop which loops
|
||||
// 1M (1 048 575) times.
|
||||
// Takes about 200 ms
|
||||
func BenchmarkSimpleLoop(b *testing.B) {
|
||||
// 0xfffff = 1048575 loops
|
||||
code := []byte{
|
||||
byte(vm.PUSH3), 0x0f, 0xff, 0xff,
|
||||
byte(vm.JUMPDEST), // [ count ]
|
||||
byte(vm.PUSH1), 1, // [count, 1]
|
||||
byte(vm.SWAP1), // [1, count]
|
||||
byte(vm.SUB), // [ count -1 ]
|
||||
byte(vm.DUP1), // [ count -1 , count-1]
|
||||
byte(vm.PUSH1), 4, // [count-1, count -1, label]
|
||||
byte(vm.JUMPI), // [ 0 ]
|
||||
byte(vm.STOP),
|
||||
}
|
||||
//tracer := vm.NewJSONLogger(nil, os.Stdout)
|
||||
//Execute(code, nil, &Config{
|
||||
// EVMConfig: vm.Config{
|
||||
// Debug: true,
|
||||
// Tracer: tracer,
|
||||
// }})
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
Execute(code, nil, nil)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue