From afbcd4e957ad5e268523c5947eb2f2d9f16f1ac4 Mon Sep 17 00:00:00 2001 From: lmittmann Date: Thu, 3 Jul 2025 14:13:29 +0200 Subject: [PATCH 1/4] added JumpDest interface + basic map implementation --- core/vm/jumpdests.go | 51 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 core/vm/jumpdests.go diff --git a/core/vm/jumpdests.go b/core/vm/jumpdests.go new file mode 100644 index 000000000000..9c228dade6e6 --- /dev/null +++ b/core/vm/jumpdests.go @@ -0,0 +1,51 @@ +// 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 . + +package vm + +import "github.com/ethereum/go-ethereum/common" + +// JumpDests is an interface for managing the jumpdest analysis cache. +// It provides methods to store and retrieve the results of JUMPDEST analysis +// for contract bytecode, which is used to determine valid jump destinations. +type JumpDests 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() JumpDests { + return make(mapJumpDests) +} + +// Load retrieves the cached jumpdest analysis for the given code hash. +func (j mapJumpDests) Load(codeHash common.Hash) (BitVec, bool) { + vec, ok := j[codeHash] + return vec, ok +} + +// Store saves the jumpdest analysis for the given code hash. +func (j mapJumpDests) Store(codeHash common.Hash, vec BitVec) { + j[codeHash] = vec +} From b358b593fa50f255da6f3c6b9b7f61ca4c044a35 Mon Sep 17 00:00:00 2001 From: lmittmann Date: Thu, 3 Jul 2025 14:15:40 +0200 Subject: [PATCH 2/4] switched from `map[common.Hash]bitvec` to `JumpDests` - renamed `bitvec` to `BitVec` - `BitVec` needs to be exported to support thirdparty `JumpDests` implementations --- core/vm/analysis_legacy.go | 20 ++++++++++---------- core/vm/analysis_legacy_test.go | 2 +- core/vm/contract.go | 16 ++++++++-------- core/vm/evm.go | 29 +++++++++++++++++++++++++---- 4 files changed, 44 insertions(+), 23 deletions(-) diff --git a/core/vm/analysis_legacy.go b/core/vm/analysis_legacy.go index 38af9084aca1..a445e2048e3f 100644 --- a/core/vm/analysis_legacy.go +++ b/core/vm/analysis_legacy.go @@ -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++ diff --git a/core/vm/analysis_legacy_test.go b/core/vm/analysis_legacy_test.go index 471d2b4ffbac..f84a4abc9262 100644 --- a/core/vm/analysis_legacy_test.go +++ b/core/vm/analysis_legacy_test.go @@ -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) diff --git a/core/vm/contract.go b/core/vm/contract.go index 0eaa91d9596c..6cc7d74df32d 100644 --- a/core/vm/contract.go +++ b/core/vm/contract.go @@ -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 JumpDests // 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 JumpDests) *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 diff --git a/core/vm/evm.go b/core/vm/evm.go index b45a43454531..bdf3e2aeb252 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -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 JumpDests } // 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: nil, } evm.precompiles = activePrecompiledContracts(evm.chainRules) evm.interpreter = NewEVMInterpreter(evm) @@ -152,6 +151,13 @@ func (evm *EVM) SetPrecompiles(precompiles PrecompiledContracts) { evm.precompiles = precompiles } +// SetJumpDests sets a custom JumpDests implementation for the EVM. +// This allows for flexible caching strategies, including global caches +// that can be shared across multiple EVM instances. +func (evm *EVM) SetJumpDests(jumpDests JumpDests) { + 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) { @@ -238,6 +244,9 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g ret, err = nil, nil // gas is unchanged } else { // The contract is a scoped environment for this execution context only. + if evm.jumpDests == nil { + evm.jumpDests = newMapJumpDests() + } contract := NewContract(caller, addr, value, gas, evm.jumpDests) contract.IsSystemCall = isSystemCall(caller) contract.SetCallCode(evm.resolveCodeHash(addr), code) @@ -298,6 +307,9 @@ func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byt } else { // Initialise a new contract and set the code that is to be used by the EVM. // The contract is a scoped environment for this execution context only. + if evm.jumpDests == nil { + evm.jumpDests = newMapJumpDests() + } contract := NewContract(caller, caller, value, gas, evm.jumpDests) contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr)) ret, err = evm.interpreter.Run(contract, input, false) @@ -342,6 +354,9 @@ func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address, // Initialise a new contract and make initialise the delegate values // // Note: The value refers to the original value from the parent call. + if evm.jumpDests == nil { + evm.jumpDests = newMapJumpDests() + } contract := NewContract(originCaller, caller, value, gas, evm.jumpDests) contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr)) ret, err = evm.interpreter.Run(contract, input, false) @@ -393,6 +408,9 @@ func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []b } else { // Initialise a new contract and set the code that is to be used by the EVM. // The contract is a scoped environment for this execution context only. + if evm.jumpDests == nil { + evm.jumpDests = newMapJumpDests() + } contract := NewContract(caller, addr, new(uint256.Int), gas, evm.jumpDests) contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr)) @@ -500,6 +518,9 @@ func (evm *EVM) create(caller common.Address, code []byte, gas uint64, value *ui // Initialise a new contract and set the code that is to be used by the EVM. // The contract is a scoped environment for this execution context only. + if evm.jumpDests == nil { + evm.jumpDests = newMapJumpDests() + } contract := NewContract(caller, address, value, gas, evm.jumpDests) // Explicitly set the code to a null hash to prevent caching of jump analysis From fd6d6d7fb3143621473c1c0ac25901194ec1a737 Mon Sep 17 00:00:00 2001 From: lmittmann Date: Thu, 17 Jul 2025 10:50:18 +0200 Subject: [PATCH 3/4] core/vm: always init `mapJumpDests` in `NewEVM` --- core/vm/evm.go | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/core/vm/evm.go b/core/vm/evm.go index bdf3e2aeb252..cae2b47b9885 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -137,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: nil, + jumpDests: newMapJumpDests(), } evm.precompiles = activePrecompiledContracts(evm.chainRules) evm.interpreter = NewEVMInterpreter(evm) @@ -244,9 +244,6 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g ret, err = nil, nil // gas is unchanged } else { // The contract is a scoped environment for this execution context only. - if evm.jumpDests == nil { - evm.jumpDests = newMapJumpDests() - } contract := NewContract(caller, addr, value, gas, evm.jumpDests) contract.IsSystemCall = isSystemCall(caller) contract.SetCallCode(evm.resolveCodeHash(addr), code) @@ -307,9 +304,6 @@ func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byt } else { // Initialise a new contract and set the code that is to be used by the EVM. // The contract is a scoped environment for this execution context only. - if evm.jumpDests == nil { - evm.jumpDests = newMapJumpDests() - } contract := NewContract(caller, caller, value, gas, evm.jumpDests) contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr)) ret, err = evm.interpreter.Run(contract, input, false) @@ -354,9 +348,6 @@ func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address, // Initialise a new contract and make initialise the delegate values // // Note: The value refers to the original value from the parent call. - if evm.jumpDests == nil { - evm.jumpDests = newMapJumpDests() - } contract := NewContract(originCaller, caller, value, gas, evm.jumpDests) contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr)) ret, err = evm.interpreter.Run(contract, input, false) @@ -408,9 +399,6 @@ func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []b } else { // Initialise a new contract and set the code that is to be used by the EVM. // The contract is a scoped environment for this execution context only. - if evm.jumpDests == nil { - evm.jumpDests = newMapJumpDests() - } contract := NewContract(caller, addr, new(uint256.Int), gas, evm.jumpDests) contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr)) @@ -518,9 +506,6 @@ func (evm *EVM) create(caller common.Address, code []byte, gas uint64, value *ui // Initialise a new contract and set the code that is to be used by the EVM. // The contract is a scoped environment for this execution context only. - if evm.jumpDests == nil { - evm.jumpDests = newMapJumpDests() - } contract := NewContract(caller, address, value, gas, evm.jumpDests) // Explicitly set the code to a null hash to prevent caching of jump analysis From 2f96b908465a5700aa7cdd4c09b07e34376b8ddf Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 31 Jul 2025 17:40:10 +0200 Subject: [PATCH 4/4] core/vm: rename to JumpDestCache --- core/vm/contract.go | 6 +++--- core/vm/evm.go | 8 +++----- core/vm/jumpdests.go | 10 +++------- 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/core/vm/contract.go b/core/vm/contract.go index 6cc7d74df32d..165ca833f885 100644 --- a/core/vm/contract.go +++ b/core/vm/contract.go @@ -31,8 +31,8 @@ type Contract struct { caller common.Address address common.Address - jumpDests JumpDests // 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,7 +47,7 @@ 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 JumpDests) *Contract { +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 = newMapJumpDests() diff --git a/core/vm/evm.go b/core/vm/evm.go index cae2b47b9885..143b7e08a22a 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -123,7 +123,7 @@ type EVM struct { precompiles map[common.Address]PrecompiledContract // jumpDests stores results of JUMPDEST analysis. - jumpDests JumpDests + jumpDests JumpDestCache } // NewEVM constructs an EVM instance with the supplied block context, state @@ -151,10 +151,8 @@ func (evm *EVM) SetPrecompiles(precompiles PrecompiledContracts) { evm.precompiles = precompiles } -// SetJumpDests sets a custom JumpDests implementation for the EVM. -// This allows for flexible caching strategies, including global caches -// that can be shared across multiple EVM instances. -func (evm *EVM) SetJumpDests(jumpDests JumpDests) { +// SetJumpDestCache configures the analysis cache. +func (evm *EVM) SetJumpDestCache(jumpDests JumpDestCache) { evm.jumpDests = jumpDests } diff --git a/core/vm/jumpdests.go b/core/vm/jumpdests.go index 9c228dade6e6..1a30c1943f26 100644 --- a/core/vm/jumpdests.go +++ b/core/vm/jumpdests.go @@ -18,10 +18,8 @@ package vm import "github.com/ethereum/go-ethereum/common" -// JumpDests is an interface for managing the jumpdest analysis cache. -// It provides methods to store and retrieve the results of JUMPDEST analysis -// for contract bytecode, which is used to determine valid jump destinations. -type JumpDests interface { +// 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) @@ -35,17 +33,15 @@ type JumpDests interface { type mapJumpDests map[common.Hash]BitVec // newMapJumpDests creates a new map-based JumpDests implementation. -func newMapJumpDests() JumpDests { +func newMapJumpDests() JumpDestCache { return make(mapJumpDests) } -// Load retrieves the cached jumpdest analysis for the given code hash. func (j mapJumpDests) Load(codeHash common.Hash) (BitVec, bool) { vec, ok := j[codeHash] return vec, ok } -// Store saves the jumpdest analysis for the given code hash. func (j mapJumpDests) Store(codeHash common.Hash, vec BitVec) { j[codeHash] = vec }