From 8a9807da9a448459b383beb56cb7de693fb100fe Mon Sep 17 00:00:00 2001 From: lmittmann Date: Wed, 14 Feb 2024 08:42:33 +0100 Subject: [PATCH] added new bitvec implementation * updated benchmarks to compare performance --- core/vm/analysis.go | 70 +++++++++++++++ core/vm/analysis_test.go | 180 +++++++++++++++++++++++++++++++-------- 2 files changed, 214 insertions(+), 36 deletions(-) diff --git a/core/vm/analysis.go b/core/vm/analysis.go index 38af9084ac..64b0eebece 100644 --- a/core/vm/analysis.go +++ b/core/vm/analysis.go @@ -116,3 +116,73 @@ func codeBitmapInternal(code, bits bitvec) bitvec { } return bits } + +type bitVec []uint32 + +func (b bitVec) isCode(pc uint64) bool { + return (b[pc/32] & (1 << (pc % 32))) == 0 +} + +func newCodeBitVec(code []byte) (bv bitVec) { + bv = make(bitVec, len(code)/32+2) + bv.codeBitVec(code) + return bv +} + +func (bv bitVec) codeBitVec(code []byte) bitVec { + var pc uint64 + for pc < uint64(len(code)) { + op := code[pc] + if int8(op) < 0x60 { + pc++ + continue // continue if the OpCode is not PUSH1..32 + } + numBytes := op - 0x5f // number of data bytes pushed + + // set bits + mask := masks[numBytes] // optimization for: (1 << numBytes) - 1 + shift := uint8(pc+1) % 32 + + i := (pc + 1) / 32 + bv[i+1] = mask >> (32 - shift) + bv[i] |= mask << shift + + pc += uint64(numBytes + 1) + } + return bv +} + +var masks = [256]uint32{ + 1: 0b00000000_00000000_00000000_00000001, + 2: 0b00000000_00000000_00000000_00000011, + 3: 0b00000000_00000000_00000000_00000111, + 4: 0b00000000_00000000_00000000_00001111, + 5: 0b00000000_00000000_00000000_00011111, + 6: 0b00000000_00000000_00000000_00111111, + 7: 0b00000000_00000000_00000000_01111111, + 8: 0b00000000_00000000_00000000_11111111, + 9: 0b00000000_00000000_00000001_11111111, + 10: 0b00000000_00000000_00000011_11111111, + 11: 0b00000000_00000000_00000111_11111111, + 12: 0b00000000_00000000_00001111_11111111, + 13: 0b00000000_00000000_00011111_11111111, + 14: 0b00000000_00000000_00111111_11111111, + 15: 0b00000000_00000000_01111111_11111111, + 16: 0b00000000_00000000_11111111_11111111, + 17: 0b00000000_00000001_11111111_11111111, + 18: 0b00000000_00000011_11111111_11111111, + 19: 0b00000000_00000111_11111111_11111111, + 20: 0b00000000_00001111_11111111_11111111, + 21: 0b00000000_00011111_11111111_11111111, + 22: 0b00000000_00111111_11111111_11111111, + 23: 0b00000000_01111111_11111111_11111111, + 24: 0b00000000_11111111_11111111_11111111, + 25: 0b00000001_11111111_11111111_11111111, + 26: 0b00000011_11111111_11111111_11111111, + 27: 0b00000111_11111111_11111111_11111111, + 28: 0b00001111_11111111_11111111_11111111, + 29: 0b00011111_11111111_11111111_11111111, + 30: 0b00111111_11111111_11111111_11111111, + 31: 0b01111111_11111111_11111111_11111111, + 32: 0b11111111_11111111_11111111_11111111, +} diff --git a/core/vm/analysis_test.go b/core/vm/analysis_test.go index f34cb98666..f9ceebfb19 100644 --- a/core/vm/analysis_test.go +++ b/core/vm/analysis_test.go @@ -20,10 +20,12 @@ import ( "crypto/rand" _ "embed" "math/bits" + "strconv" "testing" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" + "golang.org/x/exp/slices" ) func TestJumpDestAnalysis(t *testing.T) { @@ -62,6 +64,59 @@ func TestJumpDestAnalysis(t *testing.T) { } } +func TestBitVec(t *testing.T) { + tests := []struct { + Code []byte + Want bitVec + }{ + {[]byte{}, bitVec{0, 0}}, + {[]byte{byte(PUSH1), 0xff, 0x00, 0x00}, bitVec{0b00000000_00000000_00000000_00000010, 0}}, + {[]byte{byte(PUSH2), 0xff, 0xff, 0x00}, bitVec{0b00000000_00000000_00000000_00000110, 0}}, + { + []byte{ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, byte(PUSH2), 0xff, + 0xff, + }, + bitVec{0b10000000_00000000_00000000_00000000, 0b00000000_00000000_00000000_00000001, 0}, + }, + { + []byte{ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, byte(PUSH32), + }, + bitVec{0b00000000_00000000_00000000_00000000, 0b11111111_11111111_11111111_11111111, 0}, + }, + } + + for i, test := range tests { + t.Run(strconv.Itoa(i), func(t *testing.T) { + got := newCodeBitVec(test.Code) + if !slices.Equal(test.Want, got) { + t.Fatalf("(-want +got)\n- %32b\n+ %32b\n", test.Want, got) + } + }) + } +} + +func FuzzBitVec(f *testing.F) { + f.Add([]byte{0x1}) + f.Fuzz(func(t *testing.T, code []byte) { + newBitVec := newCodeBitVec(code) + oldBitVec := codeBitmap(code) + + for i := range code { + if newBitVec.isCode(uint64(i)) != oldBitVec.codeSegment(uint64(i)) { + t.Fatalf("mismatch at %d", i) + } + } + }) +} + const analysisCodeSize = 1200 * 1024 func BenchmarkJumpdestAnalysis_1200k(bench *testing.B) { @@ -75,15 +130,29 @@ func BenchmarkJumpdestAnalysis_1200k(bench *testing.B) { } func BenchmarkJumpdestAnalysis_rand(b *testing.B) { - code := make([]byte, analysisCodeSize) - rand.Read(code) + b.Run("v=old", func(b *testing.B) { + code := make([]byte, analysisCodeSize) + rand.Read(code) - bv := codeBitmap(code) - b.SetBytes(int64(len(code))) - b.ResetTimer() - for i := 0; i < b.N; i++ { - codeBitmapInternal(code, bv) - } + bv := codeBitmap(code) + b.SetBytes(int64(len(code))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + codeBitmapInternal(code, bv) + } + }) + + b.Run("v=new", func(b *testing.B) { + code := make([]byte, analysisCodeSize) + rand.Read(code) + + bv := newCodeBitVec(code) + b.SetBytes(int64(len(code))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + bv.codeBitVec(code) + } + }) } var ( @@ -93,12 +162,23 @@ var ( ) func BenchmarkJumpdestAnalysis_weth9(b *testing.B) { - bv := codeBitmap(codeWETH9) - b.SetBytes(int64(len(codeWETH9))) - b.ResetTimer() - for i := 0; i < b.N; i++ { - codeBitmapInternal(codeWETH9, bv) - } + b.Run("v=old", func(b *testing.B) { + bv := codeBitmap(codeWETH9) + b.SetBytes(int64(len(codeWETH9))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + codeBitmapInternal(codeWETH9, bv) + } + }) + + b.Run("v=new", func(b *testing.B) { + bv := newCodeBitVec(codeWETH9) + b.SetBytes(int64(len(codeWETH9))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + bv.codeBitVec(codeWETH9) + } + }) } func BenchmarkJumpdestHashing_1200k(bench *testing.B) { @@ -111,28 +191,56 @@ func BenchmarkJumpdestHashing_1200k(bench *testing.B) { } } -func BenchmarkJumpdestOpAnalysis(bench *testing.B) { - var op OpCode - bencher := func(b *testing.B) { - code := make([]byte, analysisCodeSize) - b.SetBytes(analysisCodeSize) - for i := range code { - code[i] = byte(op) - } - bits := make(bitvec, len(code)/8+1+4) - b.ResetTimer() - for i := 0; i < b.N; i++ { - for j := range bits { - bits[j] = 0 +func BenchmarkJumpdestOpAnalysis(b *testing.B) { + b.Run("v=old", func(b *testing.B) { + var op OpCode + bencher := func(b *testing.B) { + code := make([]byte, analysisCodeSize) + b.SetBytes(analysisCodeSize) + for i := range code { + code[i] = byte(op) + } + bits := make(bitvec, len(code)/8+1+4) + b.ResetTimer() + for i := 0; i < b.N; i++ { + for j := range bits { + bits[j] = 0 + } + codeBitmapInternal(code, bits) } - codeBitmapInternal(code, bits) } - } - for op = PUSH1; op <= PUSH32; op++ { - bench.Run(op.String(), bencher) - } - op = JUMPDEST - bench.Run(op.String(), bencher) - op = STOP - bench.Run(op.String(), bencher) + for op = PUSH1; op <= PUSH32; op++ { + b.Run(op.String(), bencher) + } + op = JUMPDEST + b.Run(op.String(), bencher) + op = STOP + b.Run(op.String(), bencher) + }) + + b.Run("v=new", func(b *testing.B) { + bencher := func(op OpCode) (string, func(b *testing.B)) { + return op.String(), func(b *testing.B) { + code := make([]byte, analysisCodeSize) + b.SetBytes(analysisCodeSize) + + for i := range code { + code[i] = byte(op) + } + bv := newCodeBitVec(code) + b.ResetTimer() + for i := 0; i < b.N; i++ { + for j := range bv { + bv[j] = 0 + } + bv.codeBitVec(code) + } + } + } + for op := PUSH1; op <= PUSH32; op++ { + b.Run(bencher(op)) + } + b.Run(bencher(JUMPDEST)) + b.Run(bencher(STOP)) + }) }