From a2e6aac4a5ab221c36c6c4bf8afd328590b15bef Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 14 Dec 2021 11:30:20 +0100 Subject: [PATCH] core/vm: avoid memory expansion check for trivial ops (#24048) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit https://github.com/ethereum/go-ethereum/commit/155795be99f232a583696b21ce294b77f0bdffc1. 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% --- core/vm/interpreter.go | 54 +++++++++++++++++++----------------------- core/vm/jump_table.go | 43 ++++++++++++++++++++++++--------- 2 files changed, 57 insertions(+), 40 deletions(-) diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 4f669b97f..987c95e73 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -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) @@ -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} @@ -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) diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index d18ede350..4826ec45d 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -17,6 +17,8 @@ package vm import ( + "fmt" + "github.com/ethereum/go-ethereum/params" ) @@ -65,6 +67,24 @@ 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) @@ -72,14 +92,14 @@ func newCancunInstructionSet() JumpTable { 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, @@ -88,7 +108,7 @@ 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, @@ -96,7 +116,7 @@ func newLondonInstructionSet() JumpTable { 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, @@ -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, @@ -149,7 +169,7 @@ func newConstantinopleInstructionSet() JumpTable { writes: true, returns: true, } - return instructionSet + return validate(instructionSet) } // newByzantiumInstructionSet returns the frontier, homestead and @@ -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) } @@ -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 @@ -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, @@ -1049,6 +1069,7 @@ func newFrontierInstructionSet() JumpTable { writes: true, }, } + return validate(tlb) } func copyJumpTable(source *JumpTable) *JumpTable {