Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions core/vm/analysis.go → core/vm/analysis_legacy.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,54 +25,54 @@ 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 {
bits[pos/8+1] = b
}
}

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
bits[pos/8+2] = ^a
}

// 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++
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
14 changes: 7 additions & 7 deletions core/vm/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,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
Expand All @@ -44,15 +44,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 {
func NewContract(caller common.Address, address common.Address, value *uint256.Int, gas uint64, jumpDests JumpDestCache) *Contract {
// Initialize the jump analysis map 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,
}
Expand Down Expand Up @@ -84,12 +84,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
Expand Down
12 changes: 8 additions & 4 deletions core/vm/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,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

hasher crypto.KeccakState // Keccak256 hasher instance shared across opcodes
hasherBuf common.Hash // Keccak256 hasher result array shared across opcodes
Expand All @@ -144,7 +143,7 @@ func NewEVM(blockCtx BlockContext, statedb StateDB, tradingStateDB *tradingstate
Config: config,
chainConfig: chainConfig,
chainRules: chainConfig.Rules(blockCtx.BlockNumber),
jumpDests: make(map[common.Hash]bitvec),
jumpDests: newMapJumpDests(),
hasher: crypto.NewKeccakState(),
}
evm.precompiles = activePrecompiledContracts(evm.chainRules)
Expand Down Expand Up @@ -206,6 +205,11 @@ func (evm *EVM) SetPrecompiles(precompiles PrecompiledContracts) {
evm.precompiles = precompiles
}

// SetJumpDestCache configures the analysis cache.
func (evm *EVM) SetJumpDestCache(jumpDests JumpDestCache) {
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SetJumpDestCache allows setting evm.jumpDests to nil. If a caller accidentally passes nil, subsequent NewContract(..., evm.jumpDests) calls will fall back to creating a fresh map-based cache per contract (via NewContract's nil check) instead of sharing one cache per EVM instance, which is a surprising semantic change and can increase allocations. Consider treating nil as “reset to default” (e.g. set to newMapJumpDests()), or explicitly document/rename if nil is intended to disable shared caching.

Suggested change
func (evm *EVM) SetJumpDestCache(jumpDests JumpDestCache) {
func (evm *EVM) SetJumpDestCache(jumpDests JumpDestCache) {
if jumpDests == nil {
evm.jumpDests = newMapJumpDests()
return
}

Copilot uses AI. Check for mistakes.
evm.jumpDests = jumpDests
}

// SetTxContext updates the EVM with a new transaction context.
// This is not threadsafe and should only be done very cautiously.
func (evm *EVM) SetTxContext(txCtx TxContext) {
Expand Down
49 changes: 49 additions & 0 deletions core/vm/jumpdests.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// 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/XinFinOrg/XDPoSChain/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.
Comment on lines +33 to +37
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comments refer to “JumpDests” even though the introduced type is JumpDestCache (e.g. “default implementation of JumpDests” / “new map-based JumpDests implementation”). Please align the wording with JumpDestCache to avoid confusion for future readers.

Suggested change
// 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.
// mapJumpDests is the default implementation of JumpDestCache 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 JumpDestCache implementation.

Copilot uses AI. Check for mistakes.
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