core/vm: implement stack arena (#33960)

Here, we change the EVM stack implementation to use an 'arena', i.e.
a shared allocation pool for sub-call stacks. The stack is now more
GC-friendly, since it is a slice of uint256 values instead of a slice of pointers.

Code that pushes an item to the stack has been changed to get() the top
item, then overwrite it.

The PR is a rewrite/rebase of #30362.

---------

Co-authored-by: Martin Holst Swende <martin@swende.se>
Co-authored-by: Marius van der Wijden <m.vanderwijden@live.de>
This commit is contained in:
Guillaume Ballet 2026-04-28 11:10:44 +02:00 committed by GitHub
parent 51c97216c5
commit 4dc7d46155
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 366 additions and 212 deletions

View file

@ -93,6 +93,7 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c
}
// Execute the message to preload the implicit touched states
evm := vm.NewEVM(NewEVMBlockContext(header, p.chain, nil), stateCpy, p.config, cfg)
defer evm.Release()
// Convert the transaction into an executable message and pre-cache its sender
msg, err := TransactionToMessage(tx, signer, header.BaseFee)

View file

@ -88,6 +88,7 @@ func (p *StateProcessor) Process(ctx context.Context, block *types.Block, stated
// Apply pre-execution system calls.
context = NewEVMBlockContext(header, p.chain, nil)
evm := vm.NewEVM(context, tracingStateDB, config, cfg)
defer evm.Release()
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
ProcessBeaconBlockRoot(*beaconRoot, evm)

View file

@ -24,7 +24,6 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256"
)
var activators = map[int]func(*JumpTable){
@ -92,8 +91,7 @@ func enable1884(jt *JumpTable) {
}
func opSelfBalance(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
balance := evm.StateDB.GetBalance(scope.Contract.Address())
scope.Stack.push(balance)
scope.Stack.get().Set(evm.StateDB.GetBalance(scope.Contract.Address()))
return nil, nil
}
@ -111,8 +109,7 @@ func enable1344(jt *JumpTable) {
// opChainID implements CHAINID opcode
func opChainID(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
chainId, _ := uint256.FromBig(evm.chainConfig.ChainID)
scope.Stack.push(chainId)
scope.Stack.get().SetFromBig(evm.chainConfig.ChainID)
return nil, nil
}
@ -222,8 +219,7 @@ func opTstore(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
// opBaseFee implements BASEFEE opcode
func opBaseFee(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
baseFee, _ := uint256.FromBig(evm.Context.BaseFee)
scope.Stack.push(baseFee)
scope.Stack.get().SetFromBig(evm.Context.BaseFee)
return nil, nil
}
@ -240,7 +236,7 @@ func enable3855(jt *JumpTable) {
// opPush0 implements the PUSH0 opcode
func opPush0(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
scope.Stack.push(new(uint256.Int))
scope.Stack.get().Clear()
return nil, nil
}
@ -291,8 +287,7 @@ func opBlobHash(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
// opBlobBaseFee implements BLOBBASEFEE opcode
func opBlobBaseFee(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
blobBaseFee, _ := uint256.FromBig(evm.Context.BlobBaseFee)
scope.Stack.push(blobBaseFee)
scope.Stack.get().SetFromBig(evm.Context.BlobBaseFee)
return nil, nil
}
@ -397,11 +392,11 @@ func opExtCodeCopyEIP4762(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, er
func opPush1EIP4762(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
var (
codeLen = uint64(len(scope.Contract.Code))
integer = new(uint256.Int)
elem = scope.Stack.get()
)
*pc += 1
if *pc < codeLen {
scope.Stack.push(integer.SetUint64(uint64(scope.Contract.Code[*pc])))
elem.SetUint64(uint64(scope.Contract.Code[*pc]))
if !scope.Contract.IsDeployment && !scope.Contract.IsSystemCall && *pc%31 == 0 {
// touch next chunk if PUSH1 is at the boundary. if so, *pc has
@ -414,7 +409,7 @@ func opPush1EIP4762(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
}
}
} else {
scope.Stack.push(integer.Clear())
elem.Clear()
}
return nil, nil
}
@ -426,12 +421,11 @@ func makePushEIP4762(size uint64, pushByteSize int) executionFunc {
start = min(codeLen, int(*pc+1))
end = min(codeLen, start+pushByteSize)
)
scope.Stack.push(new(uint256.Int).SetBytes(
scope.Stack.get().SetBytes(
common.RightPadBytes(
scope.Contract.Code[start:end],
pushByteSize,
)),
)
))
if !scope.Contract.IsDeployment && !scope.Contract.IsSystemCall {
contractAddr := scope.Contract.Address()
@ -583,7 +577,7 @@ func enable7702(jt *JumpTable) {
// opSlotNum enables the SLOTNUM opcode
func opSlotNum(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
scope.Stack.push(uint256.NewInt(evm.Context.SlotNum))
scope.Stack.get().SetUint64(evm.Context.SlotNum)
return nil, nil
}

View file

@ -127,6 +127,8 @@ type EVM struct {
readOnly bool // Whether to throw on stateful modifications
returnData []byte // Last CALL's return data for subsequent reuse
arena *stackArena
}
// NewEVM constructs an EVM instance with the supplied block context, state
@ -141,6 +143,7 @@ func NewEVM(blockCtx BlockContext, statedb StateDB, chainConfig *params.ChainCon
chainConfig: chainConfig,
chainRules: chainConfig.Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time),
jumpDests: newMapJumpDests(),
arena: newArena(),
}
evm.precompiles = activePrecompiledContracts(evm.chainRules)
@ -223,6 +226,12 @@ func (evm *EVM) Cancel() {
evm.abort.Store(true)
}
// Release returns some memory allocated by the EVM, should be called after the EVM was used
// for the last time. Not necessary, but an improvement.
func (evm *EVM) Release() {
returnStack(evm.arena)
}
// Cancelled returns true if Cancel has been called
func (evm *EVM) Cancelled() bool {
return evm.abort.Load()

View file

@ -71,7 +71,7 @@ func memoryCopierGas(stackpos int) gasFunc {
return GasCosts{}, err
}
// And gas for copying data, charged per word at param.CopyGas
words, overflow := stack.Back(stackpos).Uint64WithOverflow()
words, overflow := stack.back(stackpos).Uint64WithOverflow()
if overflow {
return GasCosts{}, ErrGasUintOverflow
}
@ -100,7 +100,7 @@ func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySi
return GasCosts{}, ErrWriteProtection
}
var (
y, x = stack.Back(1), stack.Back(0)
y, x = stack.back(1), stack.back(0)
current, original = evm.StateDB.GetStateAndCommittedState(contract.Address(), x.Bytes32())
)
// The legacy gas metering only takes into consideration the current state
@ -192,7 +192,7 @@ func gasSStoreEIP2200(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m
}
// Gas sentry honoured, do the actual gas calculation based on the stored value
var (
y, x = stack.Back(1), stack.Back(0)
y, x = stack.back(1), stack.back(0)
current, original = evm.StateDB.GetStateAndCommittedState(contract.Address(), x.Bytes32())
)
value := common.Hash(y.Bytes32())
@ -228,7 +228,7 @@ func gasSStoreEIP2200(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m
func makeGasLog(n uint64) gasFunc {
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
requestedSize, overflow := stack.Back(1).Uint64WithOverflow()
requestedSize, overflow := stack.back(1).Uint64WithOverflow()
if overflow {
return GasCosts{}, ErrGasUintOverflow
}
@ -261,7 +261,7 @@ func gasKeccak256(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memor
if err != nil {
return GasCosts{}, err
}
wordGas, overflow := stack.Back(1).Uint64WithOverflow()
wordGas, overflow := stack.back(1).Uint64WithOverflow()
if overflow {
return GasCosts{}, ErrGasUintOverflow
}
@ -299,7 +299,7 @@ func gasCreate2(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memoryS
if err != nil {
return GasCosts{}, err
}
wordGas, overflow := stack.Back(2).Uint64WithOverflow()
wordGas, overflow := stack.back(2).Uint64WithOverflow()
if overflow {
return GasCosts{}, ErrGasUintOverflow
}
@ -317,7 +317,7 @@ func gasCreateEip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m
if err != nil {
return GasCosts{}, err
}
size, overflow := stack.Back(2).Uint64WithOverflow()
size, overflow := stack.back(2).Uint64WithOverflow()
if overflow {
return GasCosts{}, ErrGasUintOverflow
}
@ -336,7 +336,7 @@ func gasCreate2Eip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory,
if err != nil {
return GasCosts{}, err
}
size, overflow := stack.Back(2).Uint64WithOverflow()
size, overflow := stack.back(2).Uint64WithOverflow()
if overflow {
return GasCosts{}, ErrGasUintOverflow
}
@ -352,7 +352,7 @@ func gasCreate2Eip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory,
}
func gasExpFrontier(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
expByteLen := uint64((stack.data[stack.len()-2].BitLen() + 7) / 8)
expByteLen := uint64((stack.back(1).BitLen() + 7) / 8)
var (
gas = expByteLen * params.ExpByteFrontier // no overflow check required. Max is 256 * ExpByte gas
@ -365,7 +365,7 @@ func gasExpFrontier(evm *EVM, contract *Contract, stack *Stack, mem *Memory, mem
}
func gasExpEIP158(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
expByteLen := uint64((stack.data[stack.len()-2].BitLen() + 7) / 8)
expByteLen := uint64((stack.back(1).BitLen() + 7) / 8)
var (
gas = expByteLen * params.ExpByteEIP158 // no overflow check required. Max is 256 * ExpByte gas
@ -390,7 +390,7 @@ func makeCallVariantGasCost(intrinsicFunc gasFunc) gasFunc {
if err != nil {
return GasCosts{}, err
}
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas.RegularGas, intrinsic.RegularGas, stack.Back(0))
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas.RegularGas, intrinsic.RegularGas, stack.back(0))
if err != nil {
return GasCosts{}, err
}
@ -405,8 +405,8 @@ func makeCallVariantGasCost(intrinsicFunc gasFunc) gasFunc {
func gasCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
var (
gas uint64
transfersValue = !stack.Back(2).IsZero()
address = common.Address(stack.Back(1).Bytes20())
transfersValue = !stack.back(2).IsZero()
address = common.Address(stack.back(1).Bytes20())
)
if evm.readOnly && transfersValue {
return GasCosts{}, ErrWriteProtection
@ -453,7 +453,7 @@ func gasCallCodeIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memor
gas uint64
overflow bool
)
if stack.Back(2).Sign() != 0 && !evm.chainRules.IsEIP4762 {
if stack.back(2).Sign() != 0 && !evm.chainRules.IsEIP4762 {
gas += params.CallValueTransferGas
}
if gas, overflow = math.SafeAdd(gas, memoryGas); overflow {
@ -487,7 +487,7 @@ func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me
// EIP150 homestead gas reprice fork:
if evm.chainRules.IsEIP150 {
gas = params.SelfdestructGasEIP150
var address = common.Address(stack.Back(0).Bytes20())
var address = common.Address(stack.back(0).Bytes20())
if evm.chainRules.IsEIP158 {
// if empty and transfers value

View file

@ -24,7 +24,6 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256"
)
func opAdd(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
@ -244,7 +243,7 @@ func opKeccak256(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
}
func opAddress(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
scope.Stack.push(new(uint256.Int).SetBytes(scope.Contract.Address().Bytes()))
scope.Stack.get().SetBytes(scope.Contract.Address().Bytes())
return nil, nil
}
@ -256,17 +255,17 @@ func opBalance(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
}
func opOrigin(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
scope.Stack.push(new(uint256.Int).SetBytes(evm.Origin.Bytes()))
scope.Stack.get().SetBytes(evm.Origin.Bytes())
return nil, nil
}
func opCaller(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
scope.Stack.push(new(uint256.Int).SetBytes(scope.Contract.Caller().Bytes()))
scope.Stack.get().SetBytes(scope.Contract.Caller().Bytes())
return nil, nil
}
func opCallValue(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
scope.Stack.push(scope.Contract.value)
scope.Stack.get().Set(scope.Contract.value)
return nil, nil
}
@ -282,7 +281,7 @@ func opCallDataLoad(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
}
func opCallDataSize(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
scope.Stack.push(new(uint256.Int).SetUint64(uint64(len(scope.Contract.Input))))
scope.Stack.get().SetUint64(uint64(len(scope.Contract.Input)))
return nil, nil
}
@ -305,7 +304,7 @@ func opCallDataCopy(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
}
func opReturnDataSize(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
scope.Stack.push(new(uint256.Int).SetUint64(uint64(len(evm.returnData))))
scope.Stack.get().SetUint64(uint64(len(evm.returnData)))
return nil, nil
}
@ -338,7 +337,7 @@ func opExtCodeSize(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
}
func opCodeSize(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
scope.Stack.push(new(uint256.Int).SetUint64(uint64(len(scope.Contract.Code))))
scope.Stack.get().SetUint64(uint64(len(scope.Contract.Code)))
return nil, nil
}
@ -416,7 +415,7 @@ func opExtCodeHash(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
}
func opGasprice(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
scope.Stack.push(evm.GasPrice.Clone())
scope.Stack.get().Set(evm.GasPrice)
return nil, nil
}
@ -451,35 +450,32 @@ func opBlockhash(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
}
func opCoinbase(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
scope.Stack.push(new(uint256.Int).SetBytes(evm.Context.Coinbase.Bytes()))
scope.Stack.get().SetBytes(evm.Context.Coinbase.Bytes())
return nil, nil
}
func opTimestamp(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
scope.Stack.push(new(uint256.Int).SetUint64(evm.Context.Time))
scope.Stack.get().SetUint64(evm.Context.Time)
return nil, nil
}
func opNumber(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
v, _ := uint256.FromBig(evm.Context.BlockNumber)
scope.Stack.push(v)
scope.Stack.get().SetFromBig(evm.Context.BlockNumber)
return nil, nil
}
func opDifficulty(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
v, _ := uint256.FromBig(evm.Context.Difficulty)
scope.Stack.push(v)
scope.Stack.get().SetFromBig(evm.Context.Difficulty)
return nil, nil
}
func opRandom(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
v := new(uint256.Int).SetBytes(evm.Context.Random.Bytes())
scope.Stack.push(v)
scope.Stack.get().SetBytes(evm.Context.Random.Bytes())
return nil, nil
}
func opGasLimit(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
scope.Stack.push(new(uint256.Int).SetUint64(evm.Context.GasLimit))
scope.Stack.get().SetUint64(evm.Context.GasLimit)
return nil, nil
}
@ -556,17 +552,17 @@ func opJumpdest(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
}
func opPc(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
scope.Stack.push(new(uint256.Int).SetUint64(*pc))
scope.Stack.get().SetUint64(*pc)
return nil, nil
}
func opMsize(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
scope.Stack.push(new(uint256.Int).SetUint64(uint64(scope.Memory.Len())))
scope.Stack.get().SetUint64(uint64(scope.Memory.Len()))
return nil, nil
}
func opGas(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
scope.Stack.push(new(uint256.Int).SetUint64(scope.Contract.Gas.RegularGas))
scope.Stack.get().SetUint64(scope.Contract.Gas.RegularGas)
return nil, nil
}
@ -1007,7 +1003,7 @@ func opDupN(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
}
//The nth stack item is duplicated at the top of the stack.
scope.Stack.push(scope.Stack.Back(n - 1))
scope.Stack.push(scope.Stack.back(n - 1))
*pc += 1
return nil, nil
}
@ -1034,10 +1030,10 @@ func opSwapN(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
return nil, &ErrStackUnderflow{stackLen: scope.Stack.len(), required: n + 1}
}
// The (n+1)th stack item is swapped with the top of the stack.
indexTop := scope.Stack.len() - 1
indexN := scope.Stack.len() - 1 - n
scope.Stack.data[indexTop], scope.Stack.data[indexN] = scope.Stack.data[indexN], scope.Stack.data[indexTop]
// The (n+1)th stack item is swapped with the top of the stack.
top := scope.Stack.peek()
nth := scope.Stack.back(n)
*top, *nth = *nth, *top
*pc += 1
return nil, nil
}
@ -1067,10 +1063,10 @@ func opExchange(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
return nil, &ErrStackUnderflow{stackLen: scope.Stack.len(), required: need}
}
// The (n+1)th stack item is swapped with the (m+1)th stack item.
indexN := scope.Stack.len() - 1 - n
indexM := scope.Stack.len() - 1 - m
scope.Stack.data[indexN], scope.Stack.data[indexM] = scope.Stack.data[indexM], scope.Stack.data[indexN]
// The (n+1)th stack item is swapped with the (m+1)th stack item.
nth := scope.Stack.back(n)
mth := scope.Stack.back(m)
*nth, *mth = *mth, *nth
*pc += 1
return nil, nil
}
@ -1106,13 +1102,13 @@ func makeLog(size int) executionFunc {
func opPush1(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
var (
codeLen = uint64(len(scope.Contract.Code))
integer = new(uint256.Int)
elem = scope.Stack.get()
)
*pc += 1
if *pc < codeLen {
scope.Stack.push(integer.SetUint64(uint64(scope.Contract.Code[*pc])))
elem.SetUint64(uint64(scope.Contract.Code[*pc]))
} else {
scope.Stack.push(integer.Clear())
elem.Clear()
}
return nil, nil
}
@ -1121,14 +1117,14 @@ func opPush1(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
func opPush2(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
var (
codeLen = uint64(len(scope.Contract.Code))
integer = new(uint256.Int)
elem = scope.Stack.get()
)
if *pc+2 < codeLen {
scope.Stack.push(integer.SetBytes2(scope.Contract.Code[*pc+1 : *pc+3]))
elem.SetBytes2(scope.Contract.Code[*pc+1 : *pc+3])
} else if *pc+1 < codeLen {
scope.Stack.push(integer.SetUint64(uint64(scope.Contract.Code[*pc+1]) << 8))
elem.SetUint64(uint64(scope.Contract.Code[*pc+1]) << 8)
} else {
scope.Stack.push(integer.Clear())
elem.Clear()
}
*pc += 2
return nil, nil
@ -1142,13 +1138,13 @@ func makePush(size uint64, pushByteSize int) executionFunc {
start = min(codeLen, int(*pc+1))
end = min(codeLen, start+pushByteSize)
)
a := new(uint256.Int).SetBytes(scope.Contract.Code[start:end])
a := scope.Stack.get()
a.SetBytes(scope.Contract.Code[start:end])
// Missing bytes: pushByteSize - len(pushData)
if missing := pushByteSize - (end - start); missing > 0 {
a.Lsh(a, uint(8*missing))
}
scope.Stack.push(a)
*pc += size
return nil, nil
}

View file

@ -25,6 +25,7 @@ import (
"os"
"strings"
"testing"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
@ -98,7 +99,7 @@ func init() {
func testTwoOperandOp(t *testing.T, tests []TwoOperandTestcase, opFn executionFunc, name string) {
var (
evm = NewEVM(BlockContext{}, nil, params.TestChainConfig, Config{})
stack = newstack()
stack = newStackForTesting()
pc = uint64(0)
)
@ -109,8 +110,8 @@ func testTwoOperandOp(t *testing.T, tests []TwoOperandTestcase, opFn executionFu
stack.push(x)
stack.push(y)
opFn(&pc, evm, &ScopeContext{nil, stack, nil})
if len(stack.data) != 1 {
t.Errorf("Expected one item on stack after %v, got %d: ", name, len(stack.data))
if stack.len() != 1 {
t.Errorf("Expected one item on stack after %v, got %d: ", name, stack.len())
}
actual := stack.pop()
@ -196,7 +197,7 @@ func TestSAR(t *testing.T) {
func TestAddMod(t *testing.T) {
var (
evm = NewEVM(BlockContext{}, nil, params.TestChainConfig, Config{})
stack = newstack()
stack = newStackForTesting()
pc = uint64(0)
)
tests := []struct {
@ -239,7 +240,7 @@ func TestWriteExpectedValues(t *testing.T) {
getResult := func(args []*twoOperandParams, opFn executionFunc) []TwoOperandTestcase {
var (
evm = NewEVM(BlockContext{}, nil, params.TestChainConfig, Config{})
stack = newstack()
stack = newStackForTesting()
pc = uint64(0)
)
result := make([]TwoOperandTestcase, len(args))
@ -282,23 +283,40 @@ func TestJsonTestcases(t *testing.T) {
func opBenchmark(bench *testing.B, op executionFunc, args ...string) {
var (
evm = NewEVM(BlockContext{}, nil, params.TestChainConfig, Config{})
stack = newstack()
scope = &ScopeContext{nil, stack, nil}
evm = NewEVM(BlockContext{}, nil, params.TestChainConfig, Config{})
stack = newStackForTesting()
code = []byte{}
opPush32 = makePush(32, 32)
)
// convert args
intArgs := make([]*uint256.Int, len(args))
for i, arg := range args {
code = append(code, common.LeftPadBytes(common.Hex2Bytes(arg), 32)...)
intArgs[i] = new(uint256.Int).SetBytes(common.Hex2Bytes(arg))
}
pc := uint64(0)
for bench.Loop() {
for _, arg := range intArgs {
stack.push(arg)
scope := &ScopeContext{nil, stack, &Contract{Code: code}}
start := time.Now()
bench.ResetTimer()
for i := 0; i < bench.N; i++ {
for range len(args) {
opPush32(&pc, evm, scope)
pc += 32
}
op(&pc, evm, scope)
stack.pop()
opPop(&pc, evm, scope)
}
bench.StopTimer()
elapsed := uint64(time.Since(start))
if elapsed < 1 {
elapsed = 1
}
reqGas := uint64(len(args))*GasFastestStep + GasFastestStep + GasQuickStep
gasUsed := reqGas * uint64(bench.N)
bench.ReportMetric(float64(reqGas), "gas/op")
// Keep it as uint64, multiply 100 to get two digit float later
mgasps := (100 * 1000 * gasUsed) / elapsed
bench.ReportMetric(float64(mgasps)/100, "mgas/s")
for i, arg := range args {
want := new(uint256.Int).SetBytes(common.Hex2Bytes(arg))
@ -519,7 +537,7 @@ func BenchmarkOpIsZero(b *testing.B) {
func TestOpMstore(t *testing.T) {
var (
evm = NewEVM(BlockContext{}, nil, params.TestChainConfig, Config{})
stack = newstack()
stack = newStackForTesting()
mem = NewMemory()
)
mem.Resize(64)
@ -542,7 +560,7 @@ func TestOpMstore(t *testing.T) {
func BenchmarkOpMstore(bench *testing.B) {
var (
evm = NewEVM(BlockContext{}, nil, params.TestChainConfig, Config{})
stack = newstack()
stack = newStackForTesting()
mem = NewMemory()
)
mem.Resize(64)
@ -561,7 +579,7 @@ func TestOpTstore(t *testing.T) {
var (
statedb, _ = state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
evm = NewEVM(BlockContext{}, statedb, params.TestChainConfig, Config{})
stack = newstack()
stack = newStackForTesting()
mem = NewMemory()
caller = common.Address{}
to = common.Address{1}
@ -600,7 +618,7 @@ func TestOpTstore(t *testing.T) {
func BenchmarkOpKeccak256(bench *testing.B) {
var (
evm = NewEVM(BlockContext{}, nil, params.TestChainConfig, Config{})
stack = newstack()
stack = newStackForTesting()
mem = NewMemory()
)
mem.Resize(32)
@ -672,7 +690,7 @@ func TestCreate2Addresses(t *testing.T) {
codeHash := crypto.Keccak256(code)
address := crypto.CreateAddress2(origin, salt, codeHash)
/*
stack := newstack()
stack := newStackForTesting()
// salt, but we don't need that for this test
stack.push(big.NewInt(int64(len(code)))) //size
stack.push(big.NewInt(0)) // memstart
@ -701,12 +719,12 @@ func TestRandom(t *testing.T) {
} {
var (
evm = NewEVM(BlockContext{Random: &tt.random}, nil, params.TestChainConfig, Config{})
stack = newstack()
stack = newStackForTesting()
pc = uint64(0)
)
opRandom(&pc, evm, &ScopeContext{nil, stack, nil})
if len(stack.data) != 1 {
t.Errorf("Expected one item on stack after %v, got %d: ", tt.name, len(stack.data))
if have, want := stack.len(), 1; have != want {
t.Errorf("test '%v': want %d item(s) on stack, have %d: ", tt.name, have, want)
}
actual := stack.pop()
expected, overflow := uint256.FromBig(new(big.Int).SetBytes(tt.random.Bytes()))
@ -741,14 +759,14 @@ func TestBlobHash(t *testing.T) {
} {
var (
evm = NewEVM(BlockContext{}, nil, params.TestChainConfig, Config{})
stack = newstack()
stack = newStackForTesting()
pc = uint64(0)
)
evm.SetTxContext(TxContext{BlobHashes: tt.hashes})
stack.push(uint256.NewInt(tt.idx))
opBlobHash(&pc, evm, &ScopeContext{nil, stack, nil})
if len(stack.data) != 1 {
t.Errorf("Expected one item on stack after %v, got %d: ", tt.name, len(stack.data))
if have, want := stack.len(), 1; have != want {
t.Errorf("test '%v': want %d item(s) on stack, have %d: ", tt.name, have, want)
}
actual := stack.pop()
expected, overflow := uint256.FromBig(new(big.Int).SetBytes(tt.expect.Bytes()))
@ -844,7 +862,7 @@ func TestOpMCopy(t *testing.T) {
} {
var (
evm = NewEVM(BlockContext{}, nil, params.TestChainConfig, Config{})
stack = newstack()
stack = newStackForTesting()
pc = uint64(0)
)
data := common.FromHex(strings.ReplaceAll(tc.pre, " ", ""))
@ -907,7 +925,7 @@ func TestPush(t *testing.T) {
scope := &ScopeContext{
Memory: nil,
Stack: newstack(),
Stack: newStackForTesting(),
Contract: &Contract{
Code: code,
},
@ -988,7 +1006,7 @@ func TestOpCLZ(t *testing.T) {
}
for _, tc := range tests {
// prepare a fresh stack and PC
stack := newstack()
stack := newStackForTesting()
pc := uint64(0)
// parse input
@ -1111,7 +1129,7 @@ func TestEIP8024_Execution(t *testing.T) {
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
code := common.FromHex(tc.codeHex)
stack := newstack()
stack := newStackForTesting()
pc := uint64(0)
scope := &ScopeContext{Stack: stack, Contract: &Contract{Code: code}}
var err error
@ -1189,8 +1207,9 @@ func TestEIP8024_Execution(t *testing.T) {
t.Fatalf("unexpected error: %v", err)
}
got := make([]uint64, 0, stack.len())
for i := stack.len() - 1; i >= 0; i-- {
got = append(got, stack.data[i].Uint64())
data := stack.Data()
for i := len(data) - 1; i >= 0; i-- {
got = append(got, data[i].Uint64())
}
if len(got) != len(tc.wantVals) {
t.Fatalf("stack len=%d; want %d", len(got), len(tc.wantVals))

View file

@ -116,8 +116,8 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte
var (
op OpCode // current opcode
jumpTable *JumpTable = evm.table
mem = NewMemory() // bound memory
stack = newstack() // local stack
mem = NewMemory() // bound memory
stack = evm.arena.stack() // local stack
callContext = &ScopeContext{
Memory: mem,
Stack: stack,
@ -140,7 +140,7 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte
// so that it gets executed _after_: the OnOpcode needs the stacks before
// they are returned to the pools
defer func() {
returnStack(stack)
stack.release()
mem.Free()
}()
contract.Input = input

View file

@ -83,7 +83,7 @@ func BenchmarkInterpreter(b *testing.B) {
evm = NewEVM(BlockContext{BlockNumber: big.NewInt(1), Time: 1, Random: &common.Hash{}}, statedb, params.MergedTestChainConfig, Config{})
startGas uint64 = 100_000_000
value = uint256.NewInt(0)
stack = newstack()
stack = newStackForTesting()
mem = NewMemory()
contract = NewContract(common.Address{}, common.Address{}, value, NewGasBudget(startGas), nil)
)

View file

@ -17,59 +17,59 @@
package vm
func memoryKeccak256(stack *Stack) (uint64, bool) {
return calcMemSize64(stack.Back(0), stack.Back(1))
return calcMemSize64(stack.back(0), stack.back(1))
}
func memoryCallDataCopy(stack *Stack) (uint64, bool) {
return calcMemSize64(stack.Back(0), stack.Back(2))
return calcMemSize64(stack.back(0), stack.back(2))
}
func memoryReturnDataCopy(stack *Stack) (uint64, bool) {
return calcMemSize64(stack.Back(0), stack.Back(2))
return calcMemSize64(stack.back(0), stack.back(2))
}
func memoryCodeCopy(stack *Stack) (uint64, bool) {
return calcMemSize64(stack.Back(0), stack.Back(2))
return calcMemSize64(stack.back(0), stack.back(2))
}
func memoryExtCodeCopy(stack *Stack) (uint64, bool) {
return calcMemSize64(stack.Back(1), stack.Back(3))
return calcMemSize64(stack.back(1), stack.back(3))
}
func memoryMLoad(stack *Stack) (uint64, bool) {
return calcMemSize64WithUint(stack.Back(0), 32)
return calcMemSize64WithUint(stack.back(0), 32)
}
func memoryMStore8(stack *Stack) (uint64, bool) {
return calcMemSize64WithUint(stack.Back(0), 1)
return calcMemSize64WithUint(stack.back(0), 1)
}
func memoryMStore(stack *Stack) (uint64, bool) {
return calcMemSize64WithUint(stack.Back(0), 32)
return calcMemSize64WithUint(stack.back(0), 32)
}
func memoryMcopy(stack *Stack) (uint64, bool) {
mStart := stack.Back(0) // stack[0]: dest
if stack.Back(1).Gt(mStart) {
mStart = stack.Back(1) // stack[1]: source
mStart := stack.back(0) // stack[0]: dest
if stack.back(1).Gt(mStart) {
mStart = stack.back(1) // stack[1]: source
}
return calcMemSize64(mStart, stack.Back(2)) // stack[2]: length
return calcMemSize64(mStart, stack.back(2)) // stack[2]: length
}
func memoryCreate(stack *Stack) (uint64, bool) {
return calcMemSize64(stack.Back(1), stack.Back(2))
return calcMemSize64(stack.back(1), stack.back(2))
}
func memoryCreate2(stack *Stack) (uint64, bool) {
return calcMemSize64(stack.Back(1), stack.Back(2))
return calcMemSize64(stack.back(1), stack.back(2))
}
func memoryCall(stack *Stack) (uint64, bool) {
x, overflow := calcMemSize64(stack.Back(5), stack.Back(6))
x, overflow := calcMemSize64(stack.back(5), stack.back(6))
if overflow {
return 0, true
}
y, overflow := calcMemSize64(stack.Back(3), stack.Back(4))
y, overflow := calcMemSize64(stack.back(3), stack.back(4))
if overflow {
return 0, true
}
@ -80,11 +80,11 @@ func memoryCall(stack *Stack) (uint64, bool) {
}
func memoryDelegateCall(stack *Stack) (uint64, bool) {
x, overflow := calcMemSize64(stack.Back(4), stack.Back(5))
x, overflow := calcMemSize64(stack.back(4), stack.back(5))
if overflow {
return 0, true
}
y, overflow := calcMemSize64(stack.Back(2), stack.Back(3))
y, overflow := calcMemSize64(stack.back(2), stack.back(3))
if overflow {
return 0, true
}
@ -95,11 +95,11 @@ func memoryDelegateCall(stack *Stack) (uint64, bool) {
}
func memoryStaticCall(stack *Stack) (uint64, bool) {
x, overflow := calcMemSize64(stack.Back(4), stack.Back(5))
x, overflow := calcMemSize64(stack.back(4), stack.back(5))
if overflow {
return 0, true
}
y, overflow := calcMemSize64(stack.Back(2), stack.Back(3))
y, overflow := calcMemSize64(stack.back(2), stack.back(3))
if overflow {
return 0, true
}
@ -110,13 +110,13 @@ func memoryStaticCall(stack *Stack) (uint64, bool) {
}
func memoryReturn(stack *Stack) (uint64, bool) {
return calcMemSize64(stack.Back(0), stack.Back(1))
return calcMemSize64(stack.back(0), stack.back(1))
}
func memoryRevert(stack *Stack) (uint64, bool) {
return calcMemSize64(stack.Back(0), stack.Back(1))
return calcMemSize64(stack.back(0), stack.back(1))
}
func memoryLog(stack *Stack) (uint64, bool) {
return calcMemSize64(stack.Back(0), stack.Back(1))
return calcMemSize64(stack.back(0), stack.back(1))
}

View file

@ -37,7 +37,7 @@ func makeGasSStoreFunc(clearingRefund uint64) gasFunc {
}
// Gas sentry honoured, do the actual gas calculation based on the stored value
var (
y, x = stack.Back(1), stack.peek()
y, x = stack.back(1), stack.peek()
slot = common.Hash(x.Bytes32())
current, original = evm.StateDB.GetStateAndCommittedState(contract.Address(), slot)
cost = uint64(0)
@ -158,7 +158,7 @@ func gasEip2929AccountCheck(evm *EVM, contract *Contract, stack *Stack, mem *Mem
func makeCallVariantGasCallEIP2929(oldCalculator gasFunc, addressPosition int) gasFunc {
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
addr := common.Address(stack.Back(addressPosition).Bytes20())
addr := common.Address(stack.back(addressPosition).Bytes20())
// Check slot presence in the access list
warmAccess := evm.StateDB.AddressInAccessList(addr)
// The WarmStorageReadCostEIP2929 (100) is already deducted in the form of a constant cost, so
@ -269,7 +269,7 @@ func gasCallEIP7702(evm *EVM, contract *Contract, stack *Stack, mem *Memory, mem
// Although it's checked in `gasCall`, EIP-7702 loads the target's code before
// to determine if it is resolving a delegation. This could incorrectly record
// the target in the block access list (BAL) if the call later fails.
transfersValue := !stack.Back(2).IsZero()
transfersValue := !stack.back(2).IsZero()
if evm.readOnly && transfersValue {
return GasCosts{}, ErrWriteProtection
}
@ -281,7 +281,7 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc gasFunc) gasFunc {
var (
eip2929Cost uint64
eip7702Cost uint64
addr = common.Address(stack.Back(1).Bytes20())
addr = common.Address(stack.back(1).Bytes20())
)
// Perform EIP-2929 checks (stateless), checking address presence
// in the accessList and charge the cold access accordingly.
@ -330,7 +330,7 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc gasFunc) gasFunc {
}
// Calculate the gas budget for the nested call. The costs defined by
// EIP-2929 and EIP-7702 have already been applied.
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas.RegularGas, intrinsicCost.RegularGas, stack.Back(0))
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas.RegularGas, intrinsicCost.RegularGas, stack.back(0))
if err != nil {
return GasCosts{}, err
}

View file

@ -56,7 +56,7 @@ func gasExtCodeHash4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory,
func makeCallVariantGasEIP4762(oldCalculator gasFunc, withTransferCosts bool) gasFunc {
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
var (
target = common.Address(stack.Back(1).Bytes20())
target = common.Address(stack.back(1).Bytes20())
witnessGas uint64
_, isPrecompile = evm.precompile(target)
isSystemContract = target == params.HistoryStorageAddress
@ -64,7 +64,7 @@ func makeCallVariantGasEIP4762(oldCalculator gasFunc, withTransferCosts bool) ga
// If value is transferred, it is charged before 1/64th
// is subtracted from the available gas pool.
if withTransferCosts && !stack.Back(2).IsZero() {
if withTransferCosts && !stack.back(2).IsZero() {
wantedValueTransferWitnessGas := evm.AccessEvents.ValueTransferGas(contract.Address(), target, contract.Gas.RegularGas)
if wantedValueTransferWitnessGas > contract.Gas.RegularGas {
return GasCosts{RegularGas: wantedValueTransferWitnessGas}, nil
@ -168,8 +168,8 @@ func gasCodeCopyEip4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory,
gas := gasCost.RegularGas
if !contract.IsDeployment && !contract.IsSystemCall {
var (
codeOffset = stack.Back(1)
length = stack.Back(2)
codeOffset = stack.back(1)
length = stack.back(2)
)
uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow()
if overflow {

View file

@ -959,3 +959,63 @@ func TestDelegatedAccountAccessCost(t *testing.T) {
}
}
}
func TestManyLargeStacks(t *testing.T) {
// This piece of code will push 512 items to the stack, and then call itself
// recursively.
code := make([]byte, 10)
for i := range code {
code[i] = byte(vm.PUSH0)
}
code = append(code, []byte{
byte(vm.ADDRESS), // address to call
byte(vm.GAS),
byte(vm.CALL),
}...)
main := common.HexToAddress("0xbb")
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
statedb.SetCode(main, code, tracing.CodeChangeUnspecified)
//tracer := logger.NewJSONLogger(nil, os.Stdout)
var tracer *tracing.Hooks
_, _, err := Call(main, nil, &Config{
GasLimit: 10_000_000,
State: statedb,
EVMConfig: vm.Config{
Tracer: tracer,
}})
if err != nil {
t.Fatal("didn't expect error", err)
}
}
func BenchmarkLargeDeepStacks(b *testing.B) {
// This piece of code will push 512 items to the stack, and then call itself
// recursively.
code := make([]byte, 512)
for i := range code {
code[i] = byte(vm.PUSH0)
}
code = append(code, []byte{
byte(vm.ADDRESS), // address to call
byte(vm.GAS),
byte(vm.CALL),
}...)
benchmarkNonModifyingCode(10_000_000, code, "deep-large-stacks-10M", "", b)
}
func BenchmarkShortDeepStacks(b *testing.B) {
// This piece of code will push a few items to the stack, and then call itself
// recursively.
code := make([]byte, 8)
for i := range code {
code[i] = byte(vm.PUSH0)
}
code = append(code, []byte{
byte(vm.ADDRESS), // address to call
byte(vm.GAS),
byte(vm.CALL),
}...)
benchmarkNonModifyingCode(10_000_000, code, "deep-short-stacks-10M", "", b)
}

View file

@ -17,111 +17,170 @@
package vm
import (
"slices"
"sync"
"github.com/holiman/uint256"
)
// stackArena is an arena which actual evm stacks use for data storage
type stackArena struct {
data []uint256.Int
top int // first free slot
}
func newArena() *stackArena {
return stackPool.Get().(*stackArena)
}
// 1025, because in stack() there is a condition check
// for the stack size that would fail if it was set to
// 1024.
const initialStackSize = 1025
var stackPool = sync.Pool{
New: func() interface{} {
return &Stack{data: make([]uint256.Int, 0, 16)}
New: func() any {
return &stackArena{
data: make([]uint256.Int, initialStackSize),
}
},
}
func returnStack(arena *stackArena) {
arena.top = 0 // defensive, not strictly needed as s.inner.top = s.bottom in release()
stackPool.Put(arena)
}
// stack returns an instance of a stack which uses the underlying arena. The instance
// must be released by invoking the (*Stack).release() method
func (sa *stackArena) stack() *Stack {
// make sure every substack has at least 1024 elements
if len(sa.data) <= sa.top+1024 {
// we need to grow the arena
sa.data = slices.Grow(sa.data, 1024)
sa.data = sa.data[:cap(sa.data)]
}
return &Stack{
bottom: sa.top,
size: 0,
inner: sa,
}
}
// newStackForTesting is meant to be used solely for testing. It creates a stack
// backed by a newly allocated arena.
func newStackForTesting() *Stack {
arena := &stackArena{
data: make([]uint256.Int, 1025),
}
return arena.stack()
}
// Stack is an object for basic stack operations. Items popped to the stack are
// expected to be changed and modified. stack does not take care of adding newly
// initialized objects.
type Stack struct {
data []uint256.Int
bottom int // bottom is the index of the first element of this stack
size int // size is the number of elements in this stack
inner *stackArena
}
func newstack() *Stack {
return stackPool.Get().(*Stack)
}
func returnStack(s *Stack) {
s.data = s.data[:0]
stackPool.Put(s)
// release un-claims the area of the arena which was claimed by the stack.
func (s *Stack) release() {
// When the stack is returned, need to notify the arena that the new 'top' is
// the returned stack's bottom.
s.inner.top = s.bottom
}
// Data returns the underlying uint256.Int array.
func (st *Stack) Data() []uint256.Int {
return st.data
func (s *Stack) Data() []uint256.Int {
return s.inner.data[s.bottom : s.bottom+s.size]
}
func (st *Stack) push(d *uint256.Int) {
// NOTE push limit (1024) is checked in baseCheck
st.data = append(st.data, *d)
func (s *Stack) push(d *uint256.Int) {
elem := s.get()
*elem = *d
}
func (st *Stack) pop() (ret uint256.Int) {
ret = st.data[len(st.data)-1]
st.data = st.data[:len(st.data)-1]
return
// get returns a pointer to a newly created element
// on top of the stack
func (s *Stack) get() *uint256.Int {
elem := &s.inner.data[s.inner.top]
s.inner.top++
s.size++
return elem
}
func (st *Stack) len() int {
return len(st.data)
func (s *Stack) pop() uint256.Int {
s.inner.top--
s.size--
return s.inner.data[s.inner.top]
}
func (st *Stack) swap1() {
st.data[st.len()-2], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-2]
}
func (st *Stack) swap2() {
st.data[st.len()-3], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-3]
}
func (st *Stack) swap3() {
st.data[st.len()-4], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-4]
}
func (st *Stack) swap4() {
st.data[st.len()-5], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-5]
}
func (st *Stack) swap5() {
st.data[st.len()-6], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-6]
}
func (st *Stack) swap6() {
st.data[st.len()-7], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-7]
}
func (st *Stack) swap7() {
st.data[st.len()-8], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-8]
}
func (st *Stack) swap8() {
st.data[st.len()-9], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-9]
}
func (st *Stack) swap9() {
st.data[st.len()-10], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-10]
}
func (st *Stack) swap10() {
st.data[st.len()-11], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-11]
}
func (st *Stack) swap11() {
st.data[st.len()-12], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-12]
}
func (st *Stack) swap12() {
st.data[st.len()-13], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-13]
}
func (st *Stack) swap13() {
st.data[st.len()-14], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-14]
}
func (st *Stack) swap14() {
st.data[st.len()-15], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-15]
}
func (st *Stack) swap15() {
st.data[st.len()-16], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-16]
}
func (st *Stack) swap16() {
st.data[st.len()-17], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-17]
func (s *Stack) len() int {
return s.size
}
func (st *Stack) dup(n int) {
st.push(&st.data[st.len()-n])
func (s *Stack) swap1() {
s.inner.data[s.bottom+s.size-2], s.inner.data[s.bottom+s.size-1] = s.inner.data[s.bottom+s.size-1], s.inner.data[s.bottom+s.size-2]
}
func (s *Stack) swap2() {
s.inner.data[s.bottom+s.size-3], s.inner.data[s.bottom+s.size-1] = s.inner.data[s.bottom+s.size-1], s.inner.data[s.bottom+s.size-3]
}
func (s *Stack) swap3() {
s.inner.data[s.bottom+s.size-4], s.inner.data[s.bottom+s.size-1] = s.inner.data[s.bottom+s.size-1], s.inner.data[s.bottom+s.size-4]
}
func (s *Stack) swap4() {
s.inner.data[s.bottom+s.size-5], s.inner.data[s.bottom+s.size-1] = s.inner.data[s.bottom+s.size-1], s.inner.data[s.bottom+s.size-5]
}
func (s *Stack) swap5() {
s.inner.data[s.bottom+s.size-6], s.inner.data[s.bottom+s.size-1] = s.inner.data[s.bottom+s.size-1], s.inner.data[s.bottom+s.size-6]
}
func (s *Stack) swap6() {
s.inner.data[s.bottom+s.size-7], s.inner.data[s.bottom+s.size-1] = s.inner.data[s.bottom+s.size-1], s.inner.data[s.bottom+s.size-7]
}
func (s *Stack) swap7() {
s.inner.data[s.bottom+s.size-8], s.inner.data[s.bottom+s.size-1] = s.inner.data[s.bottom+s.size-1], s.inner.data[s.bottom+s.size-8]
}
func (s *Stack) swap8() {
s.inner.data[s.bottom+s.size-9], s.inner.data[s.bottom+s.size-1] = s.inner.data[s.bottom+s.size-1], s.inner.data[s.bottom+s.size-9]
}
func (s *Stack) swap9() {
s.inner.data[s.bottom+s.size-10], s.inner.data[s.bottom+s.size-1] = s.inner.data[s.bottom+s.size-1], s.inner.data[s.bottom+s.size-10]
}
func (s *Stack) swap10() {
s.inner.data[s.bottom+s.size-11], s.inner.data[s.bottom+s.size-1] = s.inner.data[s.bottom+s.size-1], s.inner.data[s.bottom+s.size-11]
}
func (s *Stack) swap11() {
s.inner.data[s.bottom+s.size-12], s.inner.data[s.bottom+s.size-1] = s.inner.data[s.bottom+s.size-1], s.inner.data[s.bottom+s.size-12]
}
func (s *Stack) swap12() {
s.inner.data[s.bottom+s.size-13], s.inner.data[s.bottom+s.size-1] = s.inner.data[s.bottom+s.size-1], s.inner.data[s.bottom+s.size-13]
}
func (s *Stack) swap13() {
s.inner.data[s.bottom+s.size-14], s.inner.data[s.bottom+s.size-1] = s.inner.data[s.bottom+s.size-1], s.inner.data[s.bottom+s.size-14]
}
func (s *Stack) swap14() {
s.inner.data[s.bottom+s.size-15], s.inner.data[s.bottom+s.size-1] = s.inner.data[s.bottom+s.size-1], s.inner.data[s.bottom+s.size-15]
}
func (s *Stack) swap15() {
s.inner.data[s.bottom+s.size-16], s.inner.data[s.bottom+s.size-1] = s.inner.data[s.bottom+s.size-1], s.inner.data[s.bottom+s.size-16]
}
func (s *Stack) swap16() {
s.inner.data[s.bottom+s.size-17], s.inner.data[s.bottom+s.size-1] = s.inner.data[s.bottom+s.size-1], s.inner.data[s.bottom+s.size-17]
}
func (st *Stack) peek() *uint256.Int {
return &st.data[st.len()-1]
func (s *Stack) dup(n int) {
s.inner.data[s.bottom+s.size] = s.inner.data[s.bottom+s.size-n]
s.size++
s.inner.top++
}
// Back returns the n'th item in stack
func (st *Stack) Back(n int) *uint256.Int {
return &st.data[st.len()-n-1]
func (s *Stack) peek() *uint256.Int {
return &s.inner.data[s.bottom+s.size-1]
}
// back returns the n'th item in stack
func (s *Stack) back(n int) *uint256.Int {
return &s.inner.data[s.bottom+s.size-n-1]
}

View file

@ -244,6 +244,7 @@ func run(ctx context.Context, call *core.Message, opts *Options) (*core.Executio
evmContext.BlobBaseFee = new(big.Int)
}
evm := vm.NewEVM(evmContext, dirtyState, opts.Config, vm.Config{NoBaseFee: true})
defer evm.Release()
// Monitor the outer context and interrupt the EVM upon cancellation. To avoid
// a dangling goroutine until the outer estimation finishes, create an internal

View file

@ -247,6 +247,7 @@ func (eth *Ethereum) stateAtTransaction(ctx context.Context, block *types.Block,
// Insert parent beacon block root in the state as per EIP-4788.
context := core.NewEVMBlockContext(block.Header(), eth.blockchain, nil)
evm := vm.NewEVM(context, statedb, eth.blockchain.Config(), vm.Config{})
defer evm.Release()
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
core.ProcessBeaconBlockRoot(*beaconRoot, evm)
}

View file

@ -379,6 +379,7 @@ func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed
if api.backend.ChainConfig().IsPrague(next.Number(), next.Time()) {
core.ProcessParentBlockHash(next.ParentHash(), evm)
}
evm.Release()
// Clean out any pending release functions of trace state. Note this
// step must be done after constructing tracing state, because the
// tracing state of block next depends on the parent state and construction
@ -524,6 +525,7 @@ func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config
deleteEmptyObjects = chainConfig.IsEIP158(block.Number())
)
evm := vm.NewEVM(vmctx, statedb, chainConfig, vm.Config{})
defer evm.Release()
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
core.ProcessBeaconBlockRoot(*beaconRoot, evm)
}
@ -584,6 +586,7 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
evm := vm.NewEVM(blockCtx, statedb, api.backend.ChainConfig(), vm.Config{})
defer evm.Release()
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
core.ProcessBeaconBlockRoot(*beaconRoot, evm)
}
@ -673,6 +676,7 @@ func (api *API) traceBlockParallel(ctx context.Context, block *types.Block, stat
var failed error
blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
evm := vm.NewEVM(blockCtx, statedb, api.backend.ChainConfig(), vm.Config{})
defer evm.Release()
txloop:
for i, tx := range txs {
@ -758,6 +762,7 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block
}
evm := vm.NewEVM(vmctx, statedb, chainConfig, vm.Config{})
defer evm.Release()
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
core.ProcessBeaconBlockRoot(*beaconRoot, evm)
}
@ -805,6 +810,7 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block
tracer.OnTxStart(evm.GetVMContext(), tx, msg.From)
}
_, err = core.ApplyMessage(evm, msg, nil)
evm.Release()
if writer != nil {
writer.Flush()
}
@ -817,7 +823,7 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block
}
// Finalize the state so any modifications are written to the trie
// Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect
statedb.Finalise(evm.ChainConfig().IsEIP158(block.Number()))
statedb.Finalise(chainConfig.IsEIP158(block.Number()))
// If we've traced the transaction we were looking for, abort
if tx.Hash() == txHash {
@ -999,6 +1005,7 @@ func (api *API) traceTx(ctx context.Context, tx *types.Transaction, message *cor
}
tracingStateDB := state.NewHookedState(statedb, tracer.Hooks)
evm := vm.NewEVM(vmctx, tracingStateDB, api.backend.ChainConfig(), vm.Config{Tracer: tracer.Hooks, NoBaseFee: true})
defer evm.Release()
if precompiles != nil {
evm.SetPrecompiles(precompiles)
}

View file

@ -775,6 +775,7 @@ func applyMessage(ctx context.Context, b Backend, args TransactionArgs, state *s
blockContext.BlobBaseFee = new(big.Int)
}
evm := b.GetEVM(ctx, state, header, vmConfig, blockContext)
defer evm.Release()
if precompiles != nil {
evm.SetPrecompiles(precompiles)
}
@ -1390,6 +1391,7 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH
evm.Context.BlobBaseFee = new(big.Int)
}
res, err := core.ApplyMessage(evm, msg, nil)
evm.Release()
if err != nil {
return nil, 0, nil, fmt.Errorf("failed to apply transaction: %v err: %v", args.ToTransaction(types.LegacyTxType).Hash(), err)
}

View file

@ -312,6 +312,7 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
tracingStateDB = state.NewHookedState(sim.state, hooks)
}
evm := vm.NewEVM(blockContext, tracingStateDB, sim.chainConfig, *vmConfig)
defer evm.Release()
// It is possible to override precompiles with EVM bytecode, or
// move them to another address.
if precompiles != nil {

View file

@ -83,6 +83,9 @@ func (env *environment) txFitsSize(tx *types.Transaction) bool {
// discard terminates the background threads before discarding it.
func (env *environment) discard() {
env.state.StopPrefetcher()
if env.evm != nil {
env.evm.Release()
}
}
const (