mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-02-26 07:37:20 +00:00
core/vm: add configurable jumpdest analysis cache (#32143)
This adds a method on vm.EVM to set the jumpdest cache implementation. It can be used to maintain an analysis cache across VM invocations, to improve performance by skipping the analysis for already known contracts. --------- Co-authored-by: lmittmann <lmittmann@users.noreply.github.com> Co-authored-by: Felix Lange <fjl@twurst.com>
This commit is contained in:
parent
23da91f73b
commit
17d65e9451
5 changed files with 74 additions and 23 deletions
|
|
@ -25,16 +25,16 @@ const (
|
|||
set7BitsMask = uint16(0b111_1111)
|
||||
)
|
||||
|
||||
// bitvec is a bit vector which maps bytes in a program.
|
||||
// BitVec is a bit vector which maps bytes in a program.
|
||||
// An unset bit means the byte is an opcode, a set bit means
|
||||
// it's data (i.e. argument of PUSHxx).
|
||||
type bitvec []byte
|
||||
type BitVec []byte
|
||||
|
||||
func (bits bitvec) set1(pos uint64) {
|
||||
func (bits BitVec) set1(pos uint64) {
|
||||
bits[pos/8] |= 1 << (pos % 8)
|
||||
}
|
||||
|
||||
func (bits bitvec) setN(flag uint16, pos uint64) {
|
||||
func (bits BitVec) setN(flag uint16, pos uint64) {
|
||||
a := flag << (pos % 8)
|
||||
bits[pos/8] |= byte(a)
|
||||
if b := byte(a >> 8); b != 0 {
|
||||
|
|
@ -42,13 +42,13 @@ func (bits bitvec) setN(flag uint16, pos uint64) {
|
|||
}
|
||||
}
|
||||
|
||||
func (bits bitvec) set8(pos uint64) {
|
||||
func (bits BitVec) set8(pos uint64) {
|
||||
a := byte(0xFF << (pos % 8))
|
||||
bits[pos/8] |= a
|
||||
bits[pos/8+1] = ^a
|
||||
}
|
||||
|
||||
func (bits bitvec) set16(pos uint64) {
|
||||
func (bits BitVec) set16(pos uint64) {
|
||||
a := byte(0xFF << (pos % 8))
|
||||
bits[pos/8] |= a
|
||||
bits[pos/8+1] = 0xFF
|
||||
|
|
@ -56,23 +56,23 @@ func (bits bitvec) set16(pos uint64) {
|
|||
}
|
||||
|
||||
// codeSegment checks if the position is in a code segment.
|
||||
func (bits *bitvec) codeSegment(pos uint64) bool {
|
||||
func (bits *BitVec) codeSegment(pos uint64) bool {
|
||||
return (((*bits)[pos/8] >> (pos % 8)) & 1) == 0
|
||||
}
|
||||
|
||||
// codeBitmap collects data locations in code.
|
||||
func codeBitmap(code []byte) bitvec {
|
||||
func codeBitmap(code []byte) BitVec {
|
||||
// The bitmap is 4 bytes longer than necessary, in case the code
|
||||
// ends with a PUSH32, the algorithm will set bits on the
|
||||
// bitvector outside the bounds of the actual code.
|
||||
bits := make(bitvec, len(code)/8+1+4)
|
||||
bits := make(BitVec, len(code)/8+1+4)
|
||||
return codeBitmapInternal(code, bits)
|
||||
}
|
||||
|
||||
// codeBitmapInternal is the internal implementation of codeBitmap.
|
||||
// It exists for the purpose of being able to run benchmark tests
|
||||
// without dynamic allocations affecting the results.
|
||||
func codeBitmapInternal(code, bits bitvec) bitvec {
|
||||
func codeBitmapInternal(code, bits BitVec) BitVec {
|
||||
for pc := uint64(0); pc < uint64(len(code)); {
|
||||
op := OpCode(code[pc])
|
||||
pc++
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ func BenchmarkJumpdestOpAnalysis(bench *testing.B) {
|
|||
for i := range code {
|
||||
code[i] = byte(op)
|
||||
}
|
||||
bits := make(bitvec, len(code)/8+1+4)
|
||||
bits := make(BitVec, len(code)/8+1+4)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
clear(bits)
|
||||
|
|
|
|||
|
|
@ -31,8 +31,8 @@ type Contract struct {
|
|||
caller common.Address
|
||||
address common.Address
|
||||
|
||||
jumpdests map[common.Hash]bitvec // Aggregated result of JUMPDEST analysis.
|
||||
analysis bitvec // Locally cached result of JUMPDEST analysis
|
||||
jumpDests JumpDestCache // Aggregated result of JUMPDEST analysis.
|
||||
analysis BitVec // Locally cached result of JUMPDEST analysis
|
||||
|
||||
Code []byte
|
||||
CodeHash common.Hash
|
||||
|
|
@ -47,15 +47,15 @@ type Contract struct {
|
|||
}
|
||||
|
||||
// NewContract returns a new contract environment for the execution of EVM.
|
||||
func NewContract(caller common.Address, address common.Address, value *uint256.Int, gas uint64, jumpDests map[common.Hash]bitvec) *Contract {
|
||||
// Initialize the jump analysis map if it's nil, mostly for tests
|
||||
func NewContract(caller common.Address, address common.Address, value *uint256.Int, gas uint64, jumpDests JumpDestCache) *Contract {
|
||||
// Initialize the jump analysis cache if it's nil, mostly for tests
|
||||
if jumpDests == nil {
|
||||
jumpDests = make(map[common.Hash]bitvec)
|
||||
jumpDests = newMapJumpDests()
|
||||
}
|
||||
return &Contract{
|
||||
caller: caller,
|
||||
address: address,
|
||||
jumpdests: jumpDests,
|
||||
jumpDests: jumpDests,
|
||||
Gas: gas,
|
||||
value: value,
|
||||
}
|
||||
|
|
@ -87,12 +87,12 @@ func (c *Contract) isCode(udest uint64) bool {
|
|||
// contracts ( not temporary initcode), we store the analysis in a map
|
||||
if c.CodeHash != (common.Hash{}) {
|
||||
// Does parent context have the analysis?
|
||||
analysis, exist := c.jumpdests[c.CodeHash]
|
||||
analysis, exist := c.jumpDests.Load(c.CodeHash)
|
||||
if !exist {
|
||||
// Do the analysis and save in parent context
|
||||
// We do not need to store it in c.analysis
|
||||
analysis = codeBitmap(c.Code)
|
||||
c.jumpdests[c.CodeHash] = analysis
|
||||
c.jumpDests.Store(c.CodeHash, analysis)
|
||||
}
|
||||
// Also stash it in current contract for faster access
|
||||
c.analysis = analysis
|
||||
|
|
|
|||
|
|
@ -122,9 +122,8 @@ type EVM struct {
|
|||
// precompiles holds the precompiled contracts for the current epoch
|
||||
precompiles map[common.Address]PrecompiledContract
|
||||
|
||||
// jumpDests is the aggregated result of JUMPDEST analysis made through
|
||||
// the life cycle of EVM.
|
||||
jumpDests map[common.Hash]bitvec
|
||||
// jumpDests stores results of JUMPDEST analysis.
|
||||
jumpDests JumpDestCache
|
||||
}
|
||||
|
||||
// NewEVM constructs an EVM instance with the supplied block context, state
|
||||
|
|
@ -138,7 +137,7 @@ func NewEVM(blockCtx BlockContext, statedb StateDB, chainConfig *params.ChainCon
|
|||
Config: config,
|
||||
chainConfig: chainConfig,
|
||||
chainRules: chainConfig.Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time),
|
||||
jumpDests: make(map[common.Hash]bitvec),
|
||||
jumpDests: newMapJumpDests(),
|
||||
}
|
||||
evm.precompiles = activePrecompiledContracts(evm.chainRules)
|
||||
evm.interpreter = NewEVMInterpreter(evm)
|
||||
|
|
@ -152,6 +151,11 @@ func (evm *EVM) SetPrecompiles(precompiles PrecompiledContracts) {
|
|||
evm.precompiles = precompiles
|
||||
}
|
||||
|
||||
// SetJumpDestCache configures the analysis cache.
|
||||
func (evm *EVM) SetJumpDestCache(jumpDests JumpDestCache) {
|
||||
evm.jumpDests = jumpDests
|
||||
}
|
||||
|
||||
// SetTxContext resets the EVM with a new transaction context.
|
||||
// This is not threadsafe and should only be done very cautiously.
|
||||
func (evm *EVM) SetTxContext(txCtx TxContext) {
|
||||
|
|
|
|||
47
core/vm/jumpdests.go
Normal file
47
core/vm/jumpdests.go
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
// Copyright 2024 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package vm
|
||||
|
||||
import "github.com/ethereum/go-ethereum/common"
|
||||
|
||||
// JumpDestCache represents the cache of jumpdest analysis results.
|
||||
type JumpDestCache interface {
|
||||
// Load retrieves the cached jumpdest analysis for the given code hash.
|
||||
// Returns the BitVec and true if found, or nil and false if not cached.
|
||||
Load(codeHash common.Hash) (BitVec, bool)
|
||||
|
||||
// Store saves the jumpdest analysis for the given code hash.
|
||||
Store(codeHash common.Hash, vec BitVec)
|
||||
}
|
||||
|
||||
// mapJumpDests is the default implementation of JumpDests using a map.
|
||||
// This implementation is not thread-safe and is meant to be used per EVM instance.
|
||||
type mapJumpDests map[common.Hash]BitVec
|
||||
|
||||
// newMapJumpDests creates a new map-based JumpDests implementation.
|
||||
func newMapJumpDests() JumpDestCache {
|
||||
return make(mapJumpDests)
|
||||
}
|
||||
|
||||
func (j mapJumpDests) Load(codeHash common.Hash) (BitVec, bool) {
|
||||
vec, ok := j[codeHash]
|
||||
return vec, ok
|
||||
}
|
||||
|
||||
func (j mapJumpDests) Store(codeHash common.Hash, vec BitVec) {
|
||||
j[codeHash] = vec
|
||||
}
|
||||
Loading…
Reference in a new issue