Skip to content

Commit

Permalink
core/vm: improved stack swap performance (ethereum#30249)
Browse files Browse the repository at this point in the history
This PR adds the methods `Stack.swap1..16()` that faster than `Stack.swap(1..16)`. 

Co-authored-by: lmittmann <lmittmann@users.noreply.github.com>
  • Loading branch information
lmittmann and lmittmann authored Aug 6, 2024
1 parent e9981bc commit b37ac5c
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 29 deletions.
90 changes: 80 additions & 10 deletions core/vm/instructions.go
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,86 @@ func opGas(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte
return nil, nil
}

func opSwap1(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
scope.Stack.swap1()
return nil, nil
}

func opSwap2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
scope.Stack.swap2()
return nil, nil
}

func opSwap3(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
scope.Stack.swap3()
return nil, nil
}

func opSwap4(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
scope.Stack.swap4()
return nil, nil
}

func opSwap5(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
scope.Stack.swap5()
return nil, nil
}

func opSwap6(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
scope.Stack.swap6()
return nil, nil
}

func opSwap7(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
scope.Stack.swap7()
return nil, nil
}

func opSwap8(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
scope.Stack.swap8()
return nil, nil
}

func opSwap9(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
scope.Stack.swap9()
return nil, nil
}

func opSwap10(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
scope.Stack.swap10()
return nil, nil
}

func opSwap11(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
scope.Stack.swap11()
return nil, nil
}

func opSwap12(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
scope.Stack.swap12()
return nil, nil
}

func opSwap13(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
scope.Stack.swap13()
return nil, nil
}

func opSwap14(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
scope.Stack.swap14()
return nil, nil
}

func opSwap15(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
scope.Stack.swap15()
return nil, nil
}

func opSwap16(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
scope.Stack.swap16()
return nil, nil
}

func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
if interpreter.readOnly {
return nil, ErrWriteProtection
Expand Down Expand Up @@ -923,13 +1003,3 @@ func makeDup(size int64) executionFunc {
return nil, nil
}
}

// make swap instruction function
func makeSwap(size int64) executionFunc {
// switch n + 1 otherwise n would be swapped with n
size++
return func(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
scope.Stack.swap(int(size))
return nil, nil
}
}
32 changes: 16 additions & 16 deletions core/vm/jump_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -892,97 +892,97 @@ func newFrontierInstructionSet() JumpTable {
maxStack: maxDupStack(16),
},
SWAP1: {
execute: makeSwap(1),
execute: opSwap1,
constantGas: GasFastestStep,
minStack: minSwapStack(2),
maxStack: maxSwapStack(2),
},
SWAP2: {
execute: makeSwap(2),
execute: opSwap2,
constantGas: GasFastestStep,
minStack: minSwapStack(3),
maxStack: maxSwapStack(3),
},
SWAP3: {
execute: makeSwap(3),
execute: opSwap3,
constantGas: GasFastestStep,
minStack: minSwapStack(4),
maxStack: maxSwapStack(4),
},
SWAP4: {
execute: makeSwap(4),
execute: opSwap4,
constantGas: GasFastestStep,
minStack: minSwapStack(5),
maxStack: maxSwapStack(5),
},
SWAP5: {
execute: makeSwap(5),
execute: opSwap5,
constantGas: GasFastestStep,
minStack: minSwapStack(6),
maxStack: maxSwapStack(6),
},
SWAP6: {
execute: makeSwap(6),
execute: opSwap6,
constantGas: GasFastestStep,
minStack: minSwapStack(7),
maxStack: maxSwapStack(7),
},
SWAP7: {
execute: makeSwap(7),
execute: opSwap7,
constantGas: GasFastestStep,
minStack: minSwapStack(8),
maxStack: maxSwapStack(8),
},
SWAP8: {
execute: makeSwap(8),
execute: opSwap8,
constantGas: GasFastestStep,
minStack: minSwapStack(9),
maxStack: maxSwapStack(9),
},
SWAP9: {
execute: makeSwap(9),
execute: opSwap9,
constantGas: GasFastestStep,
minStack: minSwapStack(10),
maxStack: maxSwapStack(10),
},
SWAP10: {
execute: makeSwap(10),
execute: opSwap10,
constantGas: GasFastestStep,
minStack: minSwapStack(11),
maxStack: maxSwapStack(11),
},
SWAP11: {
execute: makeSwap(11),
execute: opSwap11,
constantGas: GasFastestStep,
minStack: minSwapStack(12),
maxStack: maxSwapStack(12),
},
SWAP12: {
execute: makeSwap(12),
execute: opSwap12,
constantGas: GasFastestStep,
minStack: minSwapStack(13),
maxStack: maxSwapStack(13),
},
SWAP13: {
execute: makeSwap(13),
execute: opSwap13,
constantGas: GasFastestStep,
minStack: minSwapStack(14),
maxStack: maxSwapStack(14),
},
SWAP14: {
execute: makeSwap(14),
execute: opSwap14,
constantGas: GasFastestStep,
minStack: minSwapStack(15),
maxStack: maxSwapStack(15),
},
SWAP15: {
execute: makeSwap(15),
execute: opSwap15,
constantGas: GasFastestStep,
minStack: minSwapStack(16),
maxStack: maxSwapStack(16),
},
SWAP16: {
execute: makeSwap(16),
execute: opSwap16,
constantGas: GasFastestStep,
minStack: minSwapStack(17),
maxStack: maxSwapStack(17),
Expand Down
29 changes: 29 additions & 0 deletions core/vm/runtime/runtime_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,35 @@ func BenchmarkEVM_CREATE2_1200(bench *testing.B) {
benchmarkEVM_Create(bench, "5b5862124f80600080f5600152600056")
}

func BenchmarkEVM_SWAP1(b *testing.B) {
// returns a contract that does n swaps (SWAP1)
swapContract := func(n uint64) []byte {
contract := []byte{
byte(vm.PUSH0), // PUSH0
byte(vm.PUSH0), // PUSH0
}
for i := uint64(0); i < n; i++ {
contract = append(contract, byte(vm.SWAP1))
}
return contract
}

state, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
contractAddr := common.BytesToAddress([]byte("contract"))

b.Run("10k", func(b *testing.B) {
contractCode := swapContract(10_000)
state.SetCode(contractAddr, contractCode)

for i := 0; i < b.N; i++ {
_, _, err := Call(contractAddr, []byte{}, &Config{State: state})
if err != nil {
b.Fatal(err)
}
}
})
}

func fakeHeader(n uint64, parentHash common.Hash) *types.Header {
header := types.Header{
Coinbase: common.HexToAddress("0x00000000000000000000000000000000deadbeef"),
Expand Down
51 changes: 48 additions & 3 deletions core/vm/stack.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ var stackPool = sync.Pool{

// 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
// initialised objects.
// initialized objects.
type Stack struct {
data []uint256.Int
}
Expand Down Expand Up @@ -64,8 +64,53 @@ func (st *Stack) len() int {
return len(st.data)
}

func (st *Stack) swap(n int) {
st.data[st.len()-n], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-n]
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 (st *Stack) dup(n int) {
Expand Down

0 comments on commit b37ac5c

Please sign in to comment.