Skip to content

Commit 17d65e9

Browse files
lmittmannfjl
andauthored
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 <[email protected]> Co-authored-by: Felix Lange <[email protected]>
1 parent 23da91f commit 17d65e9

File tree

5 files changed

+74
-23
lines changed

5 files changed

+74
-23
lines changed

core/vm/analysis_legacy.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,54 +25,54 @@ const (
2525
set7BitsMask = uint16(0b111_1111)
2626
)
2727

28-
// bitvec is a bit vector which maps bytes in a program.
28+
// BitVec is a bit vector which maps bytes in a program.
2929
// An unset bit means the byte is an opcode, a set bit means
3030
// it's data (i.e. argument of PUSHxx).
31-
type bitvec []byte
31+
type BitVec []byte
3232

33-
func (bits bitvec) set1(pos uint64) {
33+
func (bits BitVec) set1(pos uint64) {
3434
bits[pos/8] |= 1 << (pos % 8)
3535
}
3636

37-
func (bits bitvec) setN(flag uint16, pos uint64) {
37+
func (bits BitVec) setN(flag uint16, pos uint64) {
3838
a := flag << (pos % 8)
3939
bits[pos/8] |= byte(a)
4040
if b := byte(a >> 8); b != 0 {
4141
bits[pos/8+1] = b
4242
}
4343
}
4444

45-
func (bits bitvec) set8(pos uint64) {
45+
func (bits BitVec) set8(pos uint64) {
4646
a := byte(0xFF << (pos % 8))
4747
bits[pos/8] |= a
4848
bits[pos/8+1] = ^a
4949
}
5050

51-
func (bits bitvec) set16(pos uint64) {
51+
func (bits BitVec) set16(pos uint64) {
5252
a := byte(0xFF << (pos % 8))
5353
bits[pos/8] |= a
5454
bits[pos/8+1] = 0xFF
5555
bits[pos/8+2] = ^a
5656
}
5757

5858
// codeSegment checks if the position is in a code segment.
59-
func (bits *bitvec) codeSegment(pos uint64) bool {
59+
func (bits *BitVec) codeSegment(pos uint64) bool {
6060
return (((*bits)[pos/8] >> (pos % 8)) & 1) == 0
6161
}
6262

6363
// codeBitmap collects data locations in code.
64-
func codeBitmap(code []byte) bitvec {
64+
func codeBitmap(code []byte) BitVec {
6565
// The bitmap is 4 bytes longer than necessary, in case the code
6666
// ends with a PUSH32, the algorithm will set bits on the
6767
// bitvector outside the bounds of the actual code.
68-
bits := make(bitvec, len(code)/8+1+4)
68+
bits := make(BitVec, len(code)/8+1+4)
6969
return codeBitmapInternal(code, bits)
7070
}
7171

7272
// codeBitmapInternal is the internal implementation of codeBitmap.
7373
// It exists for the purpose of being able to run benchmark tests
7474
// without dynamic allocations affecting the results.
75-
func codeBitmapInternal(code, bits bitvec) bitvec {
75+
func codeBitmapInternal(code, bits BitVec) BitVec {
7676
for pc := uint64(0); pc < uint64(len(code)); {
7777
op := OpCode(code[pc])
7878
pc++

core/vm/analysis_legacy_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ func BenchmarkJumpdestOpAnalysis(bench *testing.B) {
9090
for i := range code {
9191
code[i] = byte(op)
9292
}
93-
bits := make(bitvec, len(code)/8+1+4)
93+
bits := make(BitVec, len(code)/8+1+4)
9494
b.ResetTimer()
9595
for i := 0; i < b.N; i++ {
9696
clear(bits)

core/vm/contract.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ type Contract struct {
3131
caller common.Address
3232
address common.Address
3333

34-
jumpdests map[common.Hash]bitvec // Aggregated result of JUMPDEST analysis.
35-
analysis bitvec // Locally cached result of JUMPDEST analysis
34+
jumpDests JumpDestCache // Aggregated result of JUMPDEST analysis.
35+
analysis BitVec // Locally cached result of JUMPDEST analysis
3636

3737
Code []byte
3838
CodeHash common.Hash
@@ -47,15 +47,15 @@ type Contract struct {
4747
}
4848

4949
// NewContract returns a new contract environment for the execution of EVM.
50-
func NewContract(caller common.Address, address common.Address, value *uint256.Int, gas uint64, jumpDests map[common.Hash]bitvec) *Contract {
51-
// Initialize the jump analysis map if it's nil, mostly for tests
50+
func NewContract(caller common.Address, address common.Address, value *uint256.Int, gas uint64, jumpDests JumpDestCache) *Contract {
51+
// Initialize the jump analysis cache if it's nil, mostly for tests
5252
if jumpDests == nil {
53-
jumpDests = make(map[common.Hash]bitvec)
53+
jumpDests = newMapJumpDests()
5454
}
5555
return &Contract{
5656
caller: caller,
5757
address: address,
58-
jumpdests: jumpDests,
58+
jumpDests: jumpDests,
5959
Gas: gas,
6060
value: value,
6161
}
@@ -87,12 +87,12 @@ func (c *Contract) isCode(udest uint64) bool {
8787
// contracts ( not temporary initcode), we store the analysis in a map
8888
if c.CodeHash != (common.Hash{}) {
8989
// Does parent context have the analysis?
90-
analysis, exist := c.jumpdests[c.CodeHash]
90+
analysis, exist := c.jumpDests.Load(c.CodeHash)
9191
if !exist {
9292
// Do the analysis and save in parent context
9393
// We do not need to store it in c.analysis
9494
analysis = codeBitmap(c.Code)
95-
c.jumpdests[c.CodeHash] = analysis
95+
c.jumpDests.Store(c.CodeHash, analysis)
9696
}
9797
// Also stash it in current contract for faster access
9898
c.analysis = analysis

core/vm/evm.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,8 @@ type EVM struct {
122122
// precompiles holds the precompiled contracts for the current epoch
123123
precompiles map[common.Address]PrecompiledContract
124124

125-
// jumpDests is the aggregated result of JUMPDEST analysis made through
126-
// the life cycle of EVM.
127-
jumpDests map[common.Hash]bitvec
125+
// jumpDests stores results of JUMPDEST analysis.
126+
jumpDests JumpDestCache
128127
}
129128

130129
// NewEVM constructs an EVM instance with the supplied block context, state
@@ -138,7 +137,7 @@ func NewEVM(blockCtx BlockContext, statedb StateDB, chainConfig *params.ChainCon
138137
Config: config,
139138
chainConfig: chainConfig,
140139
chainRules: chainConfig.Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time),
141-
jumpDests: make(map[common.Hash]bitvec),
140+
jumpDests: newMapJumpDests(),
142141
}
143142
evm.precompiles = activePrecompiledContracts(evm.chainRules)
144143
evm.interpreter = NewEVMInterpreter(evm)
@@ -152,6 +151,11 @@ func (evm *EVM) SetPrecompiles(precompiles PrecompiledContracts) {
152151
evm.precompiles = precompiles
153152
}
154153

154+
// SetJumpDestCache configures the analysis cache.
155+
func (evm *EVM) SetJumpDestCache(jumpDests JumpDestCache) {
156+
evm.jumpDests = jumpDests
157+
}
158+
155159
// SetTxContext resets the EVM with a new transaction context.
156160
// This is not threadsafe and should only be done very cautiously.
157161
func (evm *EVM) SetTxContext(txCtx TxContext) {

core/vm/jumpdests.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright 2024 The go-ethereum Authors
2+
// This file is part of the go-ethereum library.
3+
//
4+
// The go-ethereum library is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Lesser General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// The go-ethereum library is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Lesser General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Lesser General Public License
15+
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package vm
18+
19+
import "github.com/ethereum/go-ethereum/common"
20+
21+
// JumpDestCache represents the cache of jumpdest analysis results.
22+
type JumpDestCache interface {
23+
// Load retrieves the cached jumpdest analysis for the given code hash.
24+
// Returns the BitVec and true if found, or nil and false if not cached.
25+
Load(codeHash common.Hash) (BitVec, bool)
26+
27+
// Store saves the jumpdest analysis for the given code hash.
28+
Store(codeHash common.Hash, vec BitVec)
29+
}
30+
31+
// mapJumpDests is the default implementation of JumpDests using a map.
32+
// This implementation is not thread-safe and is meant to be used per EVM instance.
33+
type mapJumpDests map[common.Hash]BitVec
34+
35+
// newMapJumpDests creates a new map-based JumpDests implementation.
36+
func newMapJumpDests() JumpDestCache {
37+
return make(mapJumpDests)
38+
}
39+
40+
func (j mapJumpDests) Load(codeHash common.Hash) (BitVec, bool) {
41+
vec, ok := j[codeHash]
42+
return vec, ok
43+
}
44+
45+
func (j mapJumpDests) Store(codeHash common.Hash, vec BitVec) {
46+
j[codeHash] = vec
47+
}

0 commit comments

Comments
 (0)