Skip to content
Merged
2 changes: 1 addition & 1 deletion execution/vm/gas_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -716,7 +716,7 @@ func gasSelfdestruct(evm *EVM, callContext *CallContext, availableGas mdgas.MdGa
// TangerineWhistle (EIP150) gas reprice fork:
if evm.ChainRules().IsTangerineWhistle {
gas.Regular = params.SelfdestructGasEIP150
var address = accounts.InternAddress(callContext.Stack.Back(0).Bytes20())
var address = callContext.peekAddress()

if evm.ChainRules().IsSpuriousDragon {
// if empty and transfers value
Expand Down
35 changes: 17 additions & 18 deletions execution/vm/instructions.go
Original file line number Diff line number Diff line change
Expand Up @@ -382,8 +382,8 @@ func opAddress(pc uint64, evm *EVM, scope *CallContext) (uint64, []byte, error)
}

func opBalance(pc uint64, evm *EVM, scope *CallContext) (uint64, []byte, error) {
address := scope.peekAddress()
slot := scope.Stack.peek()
address := accounts.InternAddress(slot.Bytes20())
// BAL: BALANCE is a real state access per EIP-7928 — mark as non-revertable
// so the system address is included when explicitly queried by user txs.
evm.IntraBlockState().MarkAddressAccess(address, false)
Expand Down Expand Up @@ -543,8 +543,8 @@ func stReturnDataCopy(_ uint64, scope *CallContext) string {
}

func opExtCodeSize(pc uint64, evm *EVM, scope *CallContext) (uint64, []byte, error) {
addr := scope.peekAddress()
slot := scope.Stack.peek()
addr := accounts.InternAddress(slot.Bytes20())
// BAL: EXTCODESIZE is a real state access per EIP-7928.
evm.IntraBlockState().MarkAddressAccess(addr, false)
codeSize, err := evm.IntraBlockState().GetCodeSize(addr)
Expand Down Expand Up @@ -577,14 +577,12 @@ func opCodeCopy(pc uint64, evm *EVM, scope *CallContext) (uint64, []byte, error)
}

func opExtCodeCopy(pc uint64, evm *EVM, scope *CallContext) (uint64, []byte, error) {
var (
stack = &scope.Stack
a = stack.pop()
memOffset = stack.pop()
codeOffset = stack.pop()
length = stack.pop()
)
addr := accounts.InternAddress(a.Bytes20())
addr := scope.peekAddress()
stack := &scope.Stack
stack.pop() // consume addr
memOffset := stack.pop()
codeOffset := stack.pop()
length := stack.pop()
// BAL: EXTCODECOPY is a real state access per EIP-7928.
evm.IntraBlockState().MarkAddressAccess(addr, false)
len64 := length.Uint64()
Expand Down Expand Up @@ -640,8 +638,8 @@ func opExtCodeCopy(pc uint64, evm *EVM, scope *CallContext) (uint64, []byte, err
//
// equal the result of calling extcodehash on the account directly.
func opExtCodeHash(pc uint64, evm *EVM, scope *CallContext) (uint64, []byte, error) {
address := scope.peekAddress()
slot := scope.Stack.peek()
address := accounts.InternAddress(slot.Bytes20())

// BAL: EXTCODEHASH is a real state access per EIP-7928 — mark as
// non-revertable. Also ensures non-existent accounts appear in the BAL
Expand Down Expand Up @@ -791,7 +789,7 @@ func opMstore8(pc uint64, evm *EVM, scope *CallContext) (uint64, []byte, error)

func opSload(pc uint64, evm *EVM, scope *CallContext) (_ uint64, _ []byte, err error) {
loc := scope.Stack.peek()
*loc, err = evm.IntraBlockState().GetState(scope.Contract.Address(), accounts.InternKey(loc.Bytes32()))
*loc, err = evm.IntraBlockState().GetState(scope.Contract.Address(), scope.peekStorageKey())
return pc, nil, err
}

Expand All @@ -804,9 +802,10 @@ func opSstore(pc uint64, evm *EVM, scope *CallContext) (uint64, []byte, error) {
if evm.readOnly {
return pc, nil, ErrWriteProtection
}
loc := scope.Stack.pop()
key := scope.peekStorageKey()
scope.Stack.pop()
val := scope.Stack.pop()
return pc, nil, evm.IntraBlockState().SetState(scope.Contract.Address(), accounts.InternKey(loc.Bytes32()), val)
return pc, nil, evm.IntraBlockState().SetState(scope.Contract.Address(), key, val)
}

func stSstore(_ uint64, scope *CallContext) string {
Expand Down Expand Up @@ -1305,9 +1304,9 @@ func opSelfdestruct(pc uint64, evm *EVM, scope *CallContext) (uint64, []byte, er
if evm.readOnly {
return pc, nil, ErrWriteProtection
}
beneficiary := scope.Stack.pop()
beneficiaryAddr := scope.peekAddress()
scope.Stack.pop()
self := scope.Contract.Address()
beneficiaryAddr := accounts.InternAddress(beneficiary.Bytes20())
ibs := evm.IntraBlockState()
balance, err := ibs.GetBalance(self)
if err != nil {
Expand All @@ -1330,9 +1329,9 @@ func opSelfdestruct6780(pc uint64, evm *EVM, scope *CallContext) (uint64, []byte
if evm.readOnly {
return pc, nil, ErrWriteProtection
}
beneficiary := scope.Stack.pop()
beneficiaryAddr := scope.peekAddress()
scope.Stack.pop()
self := scope.Contract.Address()
beneficiaryAddr := accounts.InternAddress(beneficiary.Bytes20())
ibs := evm.IntraBlockState()
balance, err := ibs.GetBalance(self)
if err != nil {
Expand Down
50 changes: 50 additions & 0 deletions execution/vm/interpreter.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,50 @@ type CallContext struct {
stateGas uint64
input []byte
Memory Memory

// Opcode-scoped key/address intern cache. cacheGen is incremented once per
// opcode dispatch in the interpreter loop; cachedKeyGen/cachedAddrGen hold
// the generation at which the entry was populated. An entry is valid only
// when its gen equals cacheGen, giving the gas phase and execute phase of
// the same opcode a shared interned value without a second unique.Make call.
// Placed before Stack so these fields stay in L1D rather than being pushed
// out by Stack.data (32 KB).
cacheGen uint64
cachedKeyGen uint64
cachedAddrGen uint64
cachedKey accounts.StorageKey
cachedAddr accounts.Address

Stack Stack
Contract Contract
}

// peekStorageKey returns the top-of-stack value as an interned StorageKey.
// The result is cached for the lifetime of one opcode dispatch (gas phase +
// execute phase share the same cacheGen), so unique.Make is called at most
// once per opcode. Callers must invoke this before any stack mutation
// (pop/push/swap) within the same dispatch — the cache is keyed by generation
// only and will not detect a changed stack top within the same opcode.
func (ctx *CallContext) peekStorageKey() accounts.StorageKey {
if ctx.cachedKeyGen == ctx.cacheGen {
return ctx.cachedKey
}
ctx.cachedKey = accounts.InternKey(ctx.Stack.peek().Bytes32())
ctx.cachedKeyGen = ctx.cacheGen
return ctx.cachedKey
}

// peekAddress returns the top-of-stack value as an interned Address.
// Cached like peekStorageKey; same constraint: call before any stack mutation.
func (ctx *CallContext) peekAddress() accounts.Address {
if ctx.cachedAddrGen == ctx.cacheGen {
return ctx.cachedAddr
}
ctx.cachedAddr = accounts.InternAddress(ctx.Stack.peek().Bytes20())
ctx.cachedAddrGen = ctx.cacheGen
return ctx.cachedAddr
}
Comment thread
AskAlexSharov marked this conversation as resolved.

var contextPool = sync.Pool{
New: func() any {
return &CallContext{}
Expand All @@ -87,6 +127,15 @@ func getCallContext(contract Contract, input []byte, gas mdgas.MdGas) *CallConte
func (c *CallContext) put() {
c.Memory.reset()
c.Stack.Reset()
c.cacheGen = 0
// Use sentinel values so that a peek call before the first cacheGen++ is
// always a miss rather than returning a stale handle from a prior use.
c.cachedKeyGen = ^uint64(0)
c.cachedAddrGen = ^uint64(0)
// Zero the handles to release their canonMap pins while the context is
// idle in the pool; unique.Handle values keep interned entries alive.
c.cachedKey = accounts.NilKey
c.cachedAddr = accounts.NilAddress
contextPool.Put(c)
}

Expand Down Expand Up @@ -378,6 +427,7 @@ func (evm *EVM) Run(contract Contract, gas mdgas.MdGas, input []byte, readOnly b
anyTrace := dbg.TraceDynamicGas || debug || trace

for {
callContext.cacheGen++
if anyTrace {
// Capture pre-execution values for tracing.
logged, pcCopy, gasCopy = false, pc, callContext.gas
Expand Down
16 changes: 7 additions & 9 deletions execution/vm/operations_acl.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ func makeGasSStoreFunc(clearingRefund uint64) gasFunc {
}
// Gas sentry honoured, do the actual gas calculation based on the stored value
var (
y, x = callContext.Stack.Back(1), callContext.Stack.peek()
slot = accounts.InternKey(x.Bytes32())
y = callContext.Stack.Back(1)
slot = callContext.peekStorageKey()
current uint256.Int
cost = uint64(0)
)
Expand All @@ -70,8 +70,7 @@ func makeGasSStoreFunc(clearingRefund uint64) gasFunc {
return mdgas.MdGas{Regular: cost + params.WarmStorageReadCostEIP2929}, nil // SLOAD_GAS
}

slotCommited := accounts.InternKey(x.Bytes32())
var original, _ = evm.IntraBlockState().GetCommittedState(callContext.Address(), slotCommited)
var original, _ = evm.IntraBlockState().GetCommittedState(callContext.Address(), slot)
if original.Eq(&current) {
if original.IsZero() { // create slot (2.1.1)
if rules.IsAmsterdam {
Expand Down Expand Up @@ -125,10 +124,9 @@ func makeGasSStoreFunc(clearingRefund uint64) gasFunc {
// charge 2100 gas and add the pair to accessed_storage_keys.
// If the pair is already in accessed_storage_keys, charge 100 gas.
func gasSLoadEIP2929(evm *EVM, callContext *CallContext, scopeGas mdgas.MdGas, memorySize uint64) (mdgas.MdGas, error) {
loc := callContext.Stack.peek()
// If the caller cannot afford the cost, this change will be rolled back
// If he does afford it, we can skip checking the same thing later on, during execution
if _, slotMod := evm.IntraBlockState().AddSlotToAccessList(callContext.Address(), accounts.InternKey(loc.Bytes32())); slotMod {
if _, slotMod := evm.IntraBlockState().AddSlotToAccessList(callContext.Address(), callContext.peekStorageKey()); slotMod {
return mdgas.MdGas{Regular: params.ColdSloadCostEIP2929}, nil
}
return mdgas.MdGas{Regular: params.WarmStorageReadCostEIP2929}, nil
Expand All @@ -145,7 +143,7 @@ func gasExtCodeCopyEIP2929(evm *EVM, callContext *CallContext, scopeGas mdgas.Md
if err != nil {
return mdgas.MdGas{}, err
}
addr := accounts.InternAddress(callContext.Stack.peek().Bytes20())
addr := callContext.peekAddress()
// Check slot presence in the access list
if evm.IntraBlockState().AddAddressToAccessList(addr) {
var overflow bool
Expand All @@ -166,7 +164,7 @@ func gasExtCodeCopyEIP2929(evm *EVM, callContext *CallContext, scopeGas mdgas.Md
// - extcodesize,
// - (ext) balance
func gasEip2929AccountCheck(evm *EVM, callContext *CallContext, scopeGas mdgas.MdGas, memorySize uint64) (mdgas.MdGas, error) {
addr := accounts.InternAddress(callContext.Stack.peek().Bytes20())
addr := callContext.peekAddress()
// If the caller cannot afford the cost, this change will be rolled back
if evm.IntraBlockState().AddAddressToAccessList(addr) {
// The warm storage read cost is already charged as constantGas
Expand Down Expand Up @@ -243,7 +241,7 @@ func makeSelfdestructGasFn(refundsEnabled bool) gasFunc {
gasFunc := func(evm *EVM, callContext *CallContext, scopeGas mdgas.MdGas, memorySize uint64) (mdgas.MdGas, error) {
var (
gas mdgas.MdGas
address = accounts.InternAddress(callContext.Stack.peek().Bytes20())
address = callContext.peekAddress()
)
if evm.readOnly {
return mdgas.MdGas{}, ErrWriteProtection
Expand Down
Loading