Skip to content

Commit

Permalink
core/vm: avoid memory expansion check for trivial ops (#24048)
Browse files Browse the repository at this point in the history
commit ethereum/go-ethereum@155795b.

This moves the memorySize and memory resize into dynamicGas branch based on the
assumption that an opcode that have memorySize function will have dynamicGas
function.

goos: linux
goarch: amd64
pkg: github.com/ethereum/go-ethereum/core/vm
cpu: AMD EPYC 7763 64-Core Processor
                          │   old.txt    │              new.txt               │
                          │    sec/op    │   sec/op     vs base               │
EvmInsertionSort-4          77.40m ± 13%   74.33m ± 5%  -3.97% (p=0.007 n=10)
EvmQuickSort-4              4.952m ±  1%   4.766m ± 1%  -3.76% (p=0.000 n=10)
EvmSignatureValidation-4    11.36µ ±  3%   11.04µ ± 1%  -2.78% (p=0.000 n=10)
EvmMulticallErcTransfer-4   4.625m ±  1%   4.529m ± 2%  -2.07% (p=0.003 n=10)
EvmRedBlackTree-4           223.8m ±  2%   215.3m ± 3%  -3.82% (p=0.001 n=10)
geomean                     5.380m         5.203m       -3.28%
  • Loading branch information
holiman authored and minh-bq committed Dec 25, 2024
1 parent dfbbfea commit a2e6aac
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 40 deletions.
54 changes: 25 additions & 29 deletions core/vm/interpreter.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,6 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
// Capture pre-execution values for tracing.
logged, pcCopy, gasCopy = false, pc, contract.Gas
}

// Get the operation from the jump table and validate the stack to ensure there are
// enough stack items available to perform the operation.
op = contract.GetOp(pc)
Expand All @@ -212,6 +211,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
}
// increase context's counter
in.evm.Context.Counter++
cost = operation.constantGas // For tracing
// Validate stack
if sLen := stack.len(); sLen < operation.minStack {
return nil, &ErrStackUnderflow{stackLen: sLen, required: operation.minStack}
Expand All @@ -230,47 +230,43 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
}
}
// Static portion of gas
cost = operation.constantGas // For tracing
if !contract.UseGas(operation.constantGas) {
if !contract.UseGas(cost) {
return nil, ErrOutOfGas
}

var memorySize uint64
// calculate the new memory size and expand the memory to fit
// the operation
// Memory check needs to be done prior to evaluating the dynamic gas portion,
// to detect calculation overflows
if operation.memorySize != nil {
memSize, overflow := operation.memorySize(stack)
if overflow {
return nil, ErrGasUintOverflow
}
// memory is expanded in words of 32 bytes. Gas
// is also calculated in words.
if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow {
return nil, ErrGasUintOverflow
}
}
// Dynamic portion of gas
// consume the gas and return an error if not enough gas is available.
// cost is explicitly set so that the capture state defer method can get the proper cost
if operation.dynamicGas != nil {
// All ops with a dynamic memory usage also has a dynamic gas cost.
var memorySize uint64
// calculate the new memory size and expand the memory to fit
// the operation
// Memory check needs to be done prior to evaluating the dynamic gas portion,
// to detect calculation overflows
if operation.memorySize != nil {
memSize, overflow := operation.memorySize(stack)
if overflow {
return nil, ErrGasUintOverflow
}
// memory is expanded in words of 32 bytes. Gas
// is also calculated in words.
if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow {
return nil, ErrGasUintOverflow
}
}
// Consume the gas and return an error if not enough gas is available.
// cost is explicitly set so that the capture state defer method can get the proper cost
var dynamicCost uint64
dynamicCost, err = operation.dynamicGas(in.evm, contract, stack, mem, memorySize)
cost += dynamicCost // total cost, for debug tracing
cost += dynamicCost // for tracing
if err != nil || !contract.UseGas(dynamicCost) {
return nil, ErrOutOfGas
}
if memorySize > 0 {
mem.Resize(memorySize)
}
}
if memorySize > 0 {
mem.Resize(memorySize)
}

if debug {
in.cfg.Tracer.CaptureState(pc, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err)
logged = true
}

// execute the operation
res, err = operation.execute(&pc, in, callContext)
// if the operation clears the return data (e.g. it has returning data)
Expand Down
43 changes: 32 additions & 11 deletions core/vm/jump_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
package vm

import (
"fmt"

"github.com/ethereum/go-ethereum/params"
)

Expand Down Expand Up @@ -65,21 +67,39 @@ var (
// JumpTable contains the EVM opcodes supported at a given fork.
type JumpTable [256]*operation

func validate(jt JumpTable) JumpTable {
for i, op := range jt {
if op == nil {
continue
}
// The interpreter has an assumption that if the memorySize function is
// set, then the dynamicGas function is also set. This is a somewhat
// arbitrary assumption, and can be removed if we need to -- but it
// allows us to avoid a condition check. As long as we have that assumption
// in there, this little sanity check prevents us from merging in a
// change which violates it.
if op.memorySize != nil && op.dynamicGas == nil {
panic(fmt.Sprintf("op %v has dynamic memory but not dynamic gas", OpCode(i).String()))
}
}
return jt
}

func newCancunInstructionSet() JumpTable {
instructionSet := newShanghaiInstructionSet()
enable4844(&instructionSet) // EIP-4844 (BLOBHASH opcode)
enable7516(&instructionSet) // EIP-7516 (BLOBBASEFEE opcode)
enable1153(&instructionSet) // EIP-1153 "Transient Storage"
enable5656(&instructionSet) // EIP-5656 (MCOPY opcode)
enable6780(&instructionSet) // EIP-6780 SELFDESTRUCT only in same transaction
return instructionSet
return validate(instructionSet)
}

func newShanghaiInstructionSet() JumpTable {
instructionSet := newLondonInstructionSet()
enable3855(&instructionSet) // PUSH0 instruction
enable3860(&instructionSet) // Limit and meter initcode
return instructionSet
return validate(instructionSet)
}

// newLondonInstructionSet returns the frontier, homestead, byzantium,
Expand All @@ -88,15 +108,15 @@ func newLondonInstructionSet() JumpTable {
instructionSet := newBerlinInstructionSet()
enable3529(&instructionSet) // EIP-3529: Reduction in refunds https://eips.ethereum.org/EIPS/eip-3529
enable3198(&instructionSet) // Base fee opcode https://eips.ethereum.org/EIPS/eip-3198
return instructionSet
return validate(instructionSet)
}

// newBerlinInstructionSet returns the frontier, homestead, byzantium,
// contantinople, istanbul, petersburg and berlin instructions.
func newBerlinInstructionSet() JumpTable {
instructionSet := newIstanbulInstructionSet()
enable2929(&instructionSet) // Access lists for trie accesses https://eips.ethereum.org/EIPS/eip-2929
return instructionSet
return validate(instructionSet)
}

// newIstanbulInstructionSet returns the frontier, homestead, byzantium,
Expand All @@ -108,7 +128,7 @@ func newIstanbulInstructionSet() JumpTable {
enable1884(&instructionSet) // Reprice reader opcodes - https://eips.ethereum.org/EIPS/eip-1884
enable2200(&instructionSet) // Net metered SSTORE - https://eips.ethereum.org/EIPS/eip-2200

return instructionSet
return validate(instructionSet)
}

// newConstantinopleInstructionSet returns the frontier, homestead,
Expand Down Expand Up @@ -149,7 +169,7 @@ func newConstantinopleInstructionSet() JumpTable {
writes: true,
returns: true,
}
return instructionSet
return validate(instructionSet)
}

// newByzantiumInstructionSet returns the frontier, homestead and
Expand Down Expand Up @@ -188,14 +208,14 @@ func newByzantiumInstructionSet() JumpTable {
reverts: true,
returns: true,
}
return instructionSet
return validate(instructionSet)
}

// EIP 158 a.k.a Spurious Dragon
func newSpuriousDragonInstructionSet() JumpTable {
instructionSet := newTangerineWhistleInstructionSet()
instructionSet[EXP].dynamicGas = gasExpEIP158
return instructionSet
return validate(instructionSet)

}

Expand All @@ -209,7 +229,7 @@ func newTangerineWhistleInstructionSet() JumpTable {
instructionSet[CALL].constantGas = params.CallGasEIP150
instructionSet[CALLCODE].constantGas = params.CallGasEIP150
instructionSet[DELEGATECALL].constantGas = params.CallGasEIP150
return instructionSet
return validate(instructionSet)
}

// newHomesteadInstructionSet returns the frontier and homestead
Expand All @@ -225,13 +245,13 @@ func newHomesteadInstructionSet() JumpTable {
memorySize: memoryDelegateCall,
returns: true,
}
return instructionSet
return validate(instructionSet)
}

// newFrontierInstructionSet returns the frontier instructions
// that can be executed during the frontier phase.
func newFrontierInstructionSet() JumpTable {
return JumpTable{
tlb := JumpTable{
STOP: {
execute: opStop,
constantGas: 0,
Expand Down Expand Up @@ -1049,6 +1069,7 @@ func newFrontierInstructionSet() JumpTable {
writes: true,
},
}
return validate(tlb)
}

func copyJumpTable(source *JumpTable) *JumpTable {
Expand Down

0 comments on commit a2e6aac

Please sign in to comment.