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)
)
// 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++

View file

@ -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)

View file

@ -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

View file

@ -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