added new bitvec implementation

* updated benchmarks to compare performance
This commit is contained in:
lmittmann 2024-02-14 08:42:33 +01:00
parent 04593bb71a
commit 8a9807da9a
2 changed files with 214 additions and 36 deletions

View file

@ -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,
}

View file

@ -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))
})
}