Skip to content
This repository was archived by the owner on Feb 18, 2025. It is now read-only.

Commit d6a616f

Browse files
holimanminh-bq
authored andcommitted
core/vm: avoid memory expansion check for trivial ops (#24048)
commit ethereum/go-ethereum@155795b. This remove a nil function check in the interpreter as we have the assumption that the opcode has either 2 functions: memorySize and dynamicGas or none of these. 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%
1 parent dfbbfea commit d6a616f

File tree

2 files changed

+57
-40
lines changed

2 files changed

+57
-40
lines changed

core/vm/interpreter.go

+25-29
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,6 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
202202
// Capture pre-execution values for tracing.
203203
logged, pcCopy, gasCopy = false, pc, contract.Gas
204204
}
205-
206205
// Get the operation from the jump table and validate the stack to ensure there are
207206
// enough stack items available to perform the operation.
208207
op = contract.GetOp(pc)
@@ -212,6 +211,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
212211
}
213212
// increase context's counter
214213
in.evm.Context.Counter++
214+
cost = operation.constantGas // For tracing
215215
// Validate stack
216216
if sLen := stack.len(); sLen < operation.minStack {
217217
return nil, &ErrStackUnderflow{stackLen: sLen, required: operation.minStack}
@@ -230,47 +230,43 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
230230
}
231231
}
232232
// Static portion of gas
233-
cost = operation.constantGas // For tracing
234-
if !contract.UseGas(operation.constantGas) {
233+
if !contract.UseGas(cost) {
235234
return nil, ErrOutOfGas
236235
}
237-
238-
var memorySize uint64
239-
// calculate the new memory size and expand the memory to fit
240-
// the operation
241-
// Memory check needs to be done prior to evaluating the dynamic gas portion,
242-
// to detect calculation overflows
243-
if operation.memorySize != nil {
244-
memSize, overflow := operation.memorySize(stack)
245-
if overflow {
246-
return nil, ErrGasUintOverflow
247-
}
248-
// memory is expanded in words of 32 bytes. Gas
249-
// is also calculated in words.
250-
if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow {
251-
return nil, ErrGasUintOverflow
252-
}
253-
}
254-
// Dynamic portion of gas
255-
// consume the gas and return an error if not enough gas is available.
256-
// cost is explicitly set so that the capture state defer method can get the proper cost
257236
if operation.dynamicGas != nil {
237+
// All ops with a dynamic memory usage also has a dynamic gas cost.
238+
var memorySize uint64
239+
// calculate the new memory size and expand the memory to fit
240+
// the operation
241+
// Memory check needs to be done prior to evaluating the dynamic gas portion,
242+
// to detect calculation overflows
243+
if operation.memorySize != nil {
244+
memSize, overflow := operation.memorySize(stack)
245+
if overflow {
246+
return nil, ErrGasUintOverflow
247+
}
248+
// memory is expanded in words of 32 bytes. Gas
249+
// is also calculated in words.
250+
if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow {
251+
return nil, ErrGasUintOverflow
252+
}
253+
}
254+
// Consume the gas and return an error if not enough gas is available.
255+
// cost is explicitly set so that the capture state defer method can get the proper cost
258256
var dynamicCost uint64
259257
dynamicCost, err = operation.dynamicGas(in.evm, contract, stack, mem, memorySize)
260-
cost += dynamicCost // total cost, for debug tracing
258+
cost += dynamicCost // for tracing
261259
if err != nil || !contract.UseGas(dynamicCost) {
262260
return nil, ErrOutOfGas
263261
}
262+
if memorySize > 0 {
263+
mem.Resize(memorySize)
264+
}
264265
}
265-
if memorySize > 0 {
266-
mem.Resize(memorySize)
267-
}
268-
269266
if debug {
270267
in.cfg.Tracer.CaptureState(pc, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err)
271268
logged = true
272269
}
273-
274270
// execute the operation
275271
res, err = operation.execute(&pc, in, callContext)
276272
// if the operation clears the return data (e.g. it has returning data)

core/vm/jump_table.go

+32-11
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
package vm
1818

1919
import (
20+
"fmt"
21+
2022
"github.com/ethereum/go-ethereum/params"
2123
)
2224

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

70+
func validate(jt JumpTable) JumpTable {
71+
for i, op := range jt {
72+
if op == nil {
73+
continue
74+
}
75+
// The interpreter has an assumption that if the memorySize function is
76+
// set, then the dynamicGas function is also set. This is a somewhat
77+
// arbitrary assumption, and can be removed if we need to -- but it
78+
// allows us to avoid a condition check. As long as we have that assumption
79+
// in there, this little sanity check prevents us from merging in a
80+
// change which violates it.
81+
if op.memorySize != nil && op.dynamicGas == nil {
82+
panic(fmt.Sprintf("op %v has dynamic memory but not dynamic gas", OpCode(i).String()))
83+
}
84+
}
85+
return jt
86+
}
87+
6888
func newCancunInstructionSet() JumpTable {
6989
instructionSet := newShanghaiInstructionSet()
7090
enable4844(&instructionSet) // EIP-4844 (BLOBHASH opcode)
7191
enable7516(&instructionSet) // EIP-7516 (BLOBBASEFEE opcode)
7292
enable1153(&instructionSet) // EIP-1153 "Transient Storage"
7393
enable5656(&instructionSet) // EIP-5656 (MCOPY opcode)
7494
enable6780(&instructionSet) // EIP-6780 SELFDESTRUCT only in same transaction
75-
return instructionSet
95+
return validate(instructionSet)
7696
}
7797

7898
func newShanghaiInstructionSet() JumpTable {
7999
instructionSet := newLondonInstructionSet()
80100
enable3855(&instructionSet) // PUSH0 instruction
81101
enable3860(&instructionSet) // Limit and meter initcode
82-
return instructionSet
102+
return validate(instructionSet)
83103
}
84104

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

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

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

111-
return instructionSet
131+
return validate(instructionSet)
112132
}
113133

114134
// newConstantinopleInstructionSet returns the frontier, homestead,
@@ -149,7 +169,7 @@ func newConstantinopleInstructionSet() JumpTable {
149169
writes: true,
150170
returns: true,
151171
}
152-
return instructionSet
172+
return validate(instructionSet)
153173
}
154174

155175
// newByzantiumInstructionSet returns the frontier, homestead and
@@ -188,14 +208,14 @@ func newByzantiumInstructionSet() JumpTable {
188208
reverts: true,
189209
returns: true,
190210
}
191-
return instructionSet
211+
return validate(instructionSet)
192212
}
193213

194214
// EIP 158 a.k.a Spurious Dragon
195215
func newSpuriousDragonInstructionSet() JumpTable {
196216
instructionSet := newTangerineWhistleInstructionSet()
197217
instructionSet[EXP].dynamicGas = gasExpEIP158
198-
return instructionSet
218+
return validate(instructionSet)
199219

200220
}
201221

@@ -209,7 +229,7 @@ func newTangerineWhistleInstructionSet() JumpTable {
209229
instructionSet[CALL].constantGas = params.CallGasEIP150
210230
instructionSet[CALLCODE].constantGas = params.CallGasEIP150
211231
instructionSet[DELEGATECALL].constantGas = params.CallGasEIP150
212-
return instructionSet
232+
return validate(instructionSet)
213233
}
214234

215235
// newHomesteadInstructionSet returns the frontier and homestead
@@ -225,13 +245,13 @@ func newHomesteadInstructionSet() JumpTable {
225245
memorySize: memoryDelegateCall,
226246
returns: true,
227247
}
228-
return instructionSet
248+
return validate(instructionSet)
229249
}
230250

231251
// newFrontierInstructionSet returns the frontier instructions
232252
// that can be executed during the frontier phase.
233253
func newFrontierInstructionSet() JumpTable {
234-
return JumpTable{
254+
tlb := JumpTable{
235255
STOP: {
236256
execute: opStop,
237257
constantGas: 0,
@@ -1049,6 +1069,7 @@ func newFrontierInstructionSet() JumpTable {
10491069
writes: true,
10501070
},
10511071
}
1072+
return validate(tlb)
10521073
}
10531074

10541075
func copyJumpTable(source *JumpTable) *JumpTable {

0 commit comments

Comments
 (0)