switched from map[common.Hash]bitvec to JumpDests

- renamed `bitvec` to `BitVec`
- `BitVec` needs to be exported to support thirdparty `JumpDests` implementations
This commit is contained in:
lmittmann 2025-07-03 14:15:40 +02:00
parent afbcd4e957
commit b358b593fa
4 changed files with 44 additions and 23 deletions

View file

@ -25,16 +25,16 @@ const (
set7BitsMask = uint16(0b111_1111) 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 // An unset bit means the byte is an opcode, a set bit means
// it's data (i.e. argument of PUSHxx). // 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) 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) a := flag << (pos % 8)
bits[pos/8] |= byte(a) bits[pos/8] |= byte(a)
if b := byte(a >> 8); b != 0 { 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)) a := byte(0xFF << (pos % 8))
bits[pos/8] |= a bits[pos/8] |= a
bits[pos/8+1] = ^a bits[pos/8+1] = ^a
} }
func (bits bitvec) set16(pos uint64) { func (bits BitVec) set16(pos uint64) {
a := byte(0xFF << (pos % 8)) a := byte(0xFF << (pos % 8))
bits[pos/8] |= a bits[pos/8] |= a
bits[pos/8+1] = 0xFF 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. // 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 return (((*bits)[pos/8] >> (pos % 8)) & 1) == 0
} }
// codeBitmap collects data locations in code. // 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 // The bitmap is 4 bytes longer than necessary, in case the code
// ends with a PUSH32, the algorithm will set bits on the // ends with a PUSH32, the algorithm will set bits on the
// bitvector outside the bounds of the actual code. // 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) return codeBitmapInternal(code, bits)
} }
// codeBitmapInternal is the internal implementation of codeBitmap. // codeBitmapInternal is the internal implementation of codeBitmap.
// It exists for the purpose of being able to run benchmark tests // It exists for the purpose of being able to run benchmark tests
// without dynamic allocations affecting the results. // 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)); { for pc := uint64(0); pc < uint64(len(code)); {
op := OpCode(code[pc]) op := OpCode(code[pc])
pc++ pc++

View file

@ -90,7 +90,7 @@ func BenchmarkJumpdestOpAnalysis(bench *testing.B) {
for i := range code { for i := range code {
code[i] = byte(op) code[i] = byte(op)
} }
bits := make(bitvec, len(code)/8+1+4) bits := make(BitVec, len(code)/8+1+4)
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
clear(bits) clear(bits)

View file

@ -31,8 +31,8 @@ type Contract struct {
caller common.Address caller common.Address
address common.Address address common.Address
jumpdests map[common.Hash]bitvec // Aggregated result of JUMPDEST analysis. jumpDests JumpDests // Aggregated result of JUMPDEST analysis.
analysis bitvec // Locally cached result of JUMPDEST analysis analysis BitVec // Locally cached result of JUMPDEST analysis
Code []byte Code []byte
CodeHash common.Hash CodeHash common.Hash
@ -47,15 +47,15 @@ type Contract struct {
} }
// NewContract returns a new contract environment for the execution of EVM. // 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 { func NewContract(caller common.Address, address common.Address, value *uint256.Int, gas uint64, jumpDests JumpDests) *Contract {
// Initialize the jump analysis map if it's nil, mostly for tests // Initialize the jump analysis cache if it's nil, mostly for tests
if jumpDests == nil { if jumpDests == nil {
jumpDests = make(map[common.Hash]bitvec) jumpDests = newMapJumpDests()
} }
return &Contract{ return &Contract{
caller: caller, caller: caller,
address: address, address: address,
jumpdests: jumpDests, jumpDests: jumpDests,
Gas: gas, Gas: gas,
value: value, value: value,
} }
@ -87,12 +87,12 @@ func (c *Contract) isCode(udest uint64) bool {
// contracts ( not temporary initcode), we store the analysis in a map // contracts ( not temporary initcode), we store the analysis in a map
if c.CodeHash != (common.Hash{}) { if c.CodeHash != (common.Hash{}) {
// Does parent context have the analysis? // Does parent context have the analysis?
analysis, exist := c.jumpdests[c.CodeHash] analysis, exist := c.jumpDests.Load(c.CodeHash)
if !exist { if !exist {
// Do the analysis and save in parent context // Do the analysis and save in parent context
// We do not need to store it in c.analysis // We do not need to store it in c.analysis
analysis = codeBitmap(c.Code) analysis = codeBitmap(c.Code)
c.jumpdests[c.CodeHash] = analysis c.jumpDests.Store(c.CodeHash, analysis)
} }
// Also stash it in current contract for faster access // Also stash it in current contract for faster access
c.analysis = analysis c.analysis = analysis

View file

@ -122,9 +122,8 @@ type EVM struct {
// precompiles holds the precompiled contracts for the current epoch // precompiles holds the precompiled contracts for the current epoch
precompiles map[common.Address]PrecompiledContract precompiles map[common.Address]PrecompiledContract
// jumpDests is the aggregated result of JUMPDEST analysis made through // jumpDests stores results of JUMPDEST analysis.
// the life cycle of EVM. jumpDests JumpDests
jumpDests map[common.Hash]bitvec
} }
// NewEVM constructs an EVM instance with the supplied block context, state // 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, Config: config,
chainConfig: chainConfig, chainConfig: chainConfig,
chainRules: chainConfig.Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time), chainRules: chainConfig.Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time),
jumpDests: make(map[common.Hash]bitvec), jumpDests: nil,
} }
evm.precompiles = activePrecompiledContracts(evm.chainRules) evm.precompiles = activePrecompiledContracts(evm.chainRules)
evm.interpreter = NewEVMInterpreter(evm) evm.interpreter = NewEVMInterpreter(evm)
@ -152,6 +151,13 @@ func (evm *EVM) SetPrecompiles(precompiles PrecompiledContracts) {
evm.precompiles = precompiles 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. // SetTxContext resets the EVM with a new transaction context.
// This is not threadsafe and should only be done very cautiously. // This is not threadsafe and should only be done very cautiously.
func (evm *EVM) SetTxContext(txCtx TxContext) { 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 ret, err = nil, nil // gas is unchanged
} else { } else {
// The contract is a scoped environment for this execution context only. // 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 := NewContract(caller, addr, value, gas, evm.jumpDests)
contract.IsSystemCall = isSystemCall(caller) contract.IsSystemCall = isSystemCall(caller)
contract.SetCallCode(evm.resolveCodeHash(addr), code) contract.SetCallCode(evm.resolveCodeHash(addr), code)
@ -298,6 +307,9 @@ func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byt
} else { } else {
// Initialise a new contract and set the code that is to be used by the EVM. // 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. // 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 := NewContract(caller, caller, value, gas, evm.jumpDests)
contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr)) contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr))
ret, err = evm.interpreter.Run(contract, input, false) 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 // Initialise a new contract and make initialise the delegate values
// //
// Note: The value refers to the original value from the parent call. // 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 := NewContract(originCaller, caller, value, gas, evm.jumpDests)
contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr)) contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr))
ret, err = evm.interpreter.Run(contract, input, false) 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 { } else {
// Initialise a new contract and set the code that is to be used by the EVM. // 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. // 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 := NewContract(caller, addr, new(uint256.Int), gas, evm.jumpDests)
contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr)) 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. // 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. // 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) contract := NewContract(caller, address, value, gas, evm.jumpDests)
// Explicitly set the code to a null hash to prevent caching of jump analysis // Explicitly set the code to a null hash to prevent caching of jump analysis