dropped old code

This commit is contained in:
lmittmann 2024-04-16 17:14:03 +02:00
parent 36a4609150
commit 071bb70827
3 changed files with 93 additions and 258 deletions

View file

@ -16,120 +16,26 @@
package vm
const (
set2BitsMask = uint16(0b11)
set3BitsMask = uint16(0b111)
set4BitsMask = uint16(0b1111)
set5BitsMask = uint16(0b1_1111)
set6BitsMask = uint16(0b11_1111)
set7BitsMask = uint16(0b111_1111)
)
// bitvec is a bit vector which maps bytes in a program.
// An unset bit means the byte is an opcode, a set bit means
// it's data (i.e. argument of PUSHxx).
type bitvec []byte
type bitvec []uint32
func (bits bitvec) set1(pos uint64) {
bits[pos/8] |= 1 << (pos % 8)
func (bv bitvec) isCode(pc uint64) bool {
return (bv[pc/32] & (1 << (pc % 32))) == 0
}
func (bits bitvec) setN(flag uint16, pos uint64) {
a := flag << (pos % 8)
bits[pos/8] |= byte(a)
if b := byte(a >> 8); b != 0 {
bits[pos/8+1] = b
}
}
func (bits bitvec) set8(pos uint64) {
a := byte(0xFF << (pos % 8))
bits[pos/8] |= a
bits[pos/8+1] = ^a
}
func (bits bitvec) set16(pos uint64) {
a := byte(0xFF << (pos % 8))
bits[pos/8] |= a
bits[pos/8+1] = 0xFF
bits[pos/8+2] = ^a
}
// codeSegment checks if the position is in a code segment.
func (bits *bitvec) codeSegment(pos uint64) bool {
return (((*bits)[pos/8] >> (pos % 8)) & 1) == 0
}
// codeBitmap collects data locations in code.
func codeBitmap(code []byte) bitvec {
// The bitmap is 4 bytes longer than necessary, in case the code
// ends with a PUSH32, the algorithm will set bits on the
// bitvector outside the bounds of the actual code.
bits := make(bitvec, len(code)/8+1+4)
return codeBitmapInternal(code, bits)
}
// codeBitmapInternal is the internal implementation of codeBitmap.
// It exists for the purpose of being able to run benchmark tests
// without dynamic allocations affecting the results.
func codeBitmapInternal(code, bits bitvec) bitvec {
for pc := uint64(0); pc < uint64(len(code)); {
op := OpCode(code[pc])
pc++
if int8(op) < int8(PUSH1) { // If not PUSH (the int8(op) > int(PUSH32) is always false).
continue
}
numbits := op - PUSH1 + 1
if numbits >= 8 {
for ; numbits >= 16; numbits -= 16 {
bits.set16(pc)
pc += 16
}
for ; numbits >= 8; numbits -= 8 {
bits.set8(pc)
pc += 8
}
}
switch numbits {
case 1:
bits.set1(pc)
pc += 1
case 2:
bits.setN(set2BitsMask, pc)
pc += 2
case 3:
bits.setN(set3BitsMask, pc)
pc += 3
case 4:
bits.setN(set4BitsMask, pc)
pc += 4
case 5:
bits.setN(set5BitsMask, pc)
pc += 5
case 6:
bits.setN(set6BitsMask, pc)
pc += 6
case 7:
bits.setN(set7BitsMask, pc)
pc += 7
}
}
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)
// newCodeBitVec collects data locations in code.
func newCodeBitVec(code []byte) (bv bitvec) {
bv = make(bitvec, len(code)/32+2)
bv.codeBitvecInternal(code)
return bv
}
func (bv bitVec) codeBitVec(code []byte) bitVec {
// codeBitvecInternal is the internal implementation of codeBitmap.
// It exists for the purpose of being able to run benchmark tests
// without dynamic allocations affecting the results.
func (bv bitvec) codeBitvecInternal(code []byte) bitvec {
var pc uint64
for pc < uint64(len(code)) {
op := code[pc]

View file

@ -17,9 +17,9 @@
package vm
import (
"bytes"
"crypto/rand"
_ "embed"
"math/bits"
"strconv"
"testing"
@ -28,68 +28,67 @@ import (
"golang.org/x/exp/slices"
)
func TestJumpDestAnalysis(t *testing.T) {
tests := []struct {
code []byte
exp byte
which int
}{
{[]byte{byte(PUSH1), 0x01, 0x01, 0x01}, 0b0000_0010, 0},
{[]byte{byte(PUSH1), byte(PUSH1), byte(PUSH1), byte(PUSH1)}, 0b0000_1010, 0},
{[]byte{0x00, byte(PUSH1), 0x00, byte(PUSH1), 0x00, byte(PUSH1), 0x00, byte(PUSH1)}, 0b0101_0100, 0},
{[]byte{byte(PUSH8), byte(PUSH8), byte(PUSH8), byte(PUSH8), byte(PUSH8), byte(PUSH8), byte(PUSH8), byte(PUSH8), 0x01, 0x01, 0x01}, bits.Reverse8(0x7F), 0},
{[]byte{byte(PUSH8), 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}, 0b0000_0001, 1},
{[]byte{0x01, 0x01, 0x01, 0x01, 0x01, byte(PUSH2), byte(PUSH2), byte(PUSH2), 0x01, 0x01, 0x01}, 0b1100_0000, 0},
{[]byte{0x01, 0x01, 0x01, 0x01, 0x01, byte(PUSH2), 0x01, 0x01, 0x01, 0x01, 0x01}, 0b0000_0000, 1},
{[]byte{byte(PUSH3), 0x01, 0x01, 0x01, byte(PUSH1), 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}, 0b0010_1110, 0},
{[]byte{byte(PUSH3), 0x01, 0x01, 0x01, byte(PUSH1), 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}, 0b0000_0000, 1},
{[]byte{0x01, byte(PUSH8), 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}, 0b1111_1100, 0},
{[]byte{0x01, byte(PUSH8), 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}, 0b0000_0011, 1},
{[]byte{byte(PUSH16), 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}, 0b1111_1110, 0},
{[]byte{byte(PUSH16), 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}, 0b1111_1111, 1},
{[]byte{byte(PUSH16), 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}, 0b0000_0001, 2},
{[]byte{byte(PUSH8), 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, byte(PUSH1), 0x01}, 0b1111_1110, 0},
{[]byte{byte(PUSH8), 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, byte(PUSH1), 0x01}, 0b0000_0101, 1},
{[]byte{byte(PUSH32)}, 0b1111_1110, 0},
{[]byte{byte(PUSH32)}, 0b1111_1111, 1},
{[]byte{byte(PUSH32)}, 0b1111_1111, 2},
{[]byte{byte(PUSH32)}, 0b1111_1111, 3},
{[]byte{byte(PUSH32)}, 0b0000_0001, 4},
}
for i, test := range tests {
ret := codeBitmap(test.code)
if ret[test.which] != test.exp {
t.Fatalf("test %d: expected %x, got %02x", i, test.exp, ret[test.which])
}
}
}
func TestBitVec(t *testing.T) {
func TestBitvec(t *testing.T) {
tests := []struct {
Code []byte
Want bitVec
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}},
{Code: []byte{}, Want: bitvec{0, 0}},
{Code: []byte{byte(PUSH1), 0x01, 0x01, 0x01}, Want: bitvec{0b00000000_00000000_00000000_00000010, 0}},
{Code: []byte{byte(PUSH2), 0x01, 0x01, 0x01}, Want: bitvec{0b00000000_00000000_00000000_00000110, 0}},
{
[]byte{
Code: []byte{byte(PUSH1), byte(PUSH1), byte(PUSH1), byte(PUSH1)},
Want: bitvec{0b00000000_00000000_00000000_00001010, 0},
},
{
Code: []byte{0x00, byte(PUSH1), 0x00, byte(PUSH1), 0x00, byte(PUSH1), 0x00, byte(PUSH1)},
Want: bitvec{0b00000000_00000000_00000001_01010100, 0},
},
{
Code: []byte{byte(PUSH8), byte(PUSH8), byte(PUSH8), byte(PUSH8), byte(PUSH8), byte(PUSH8), byte(PUSH8), byte(PUSH8), 0x01, 0x01, 0x01},
Want: bitvec{0b00000000_00000000_00000001_11111110, 0},
},
{
Code: []byte{0x01, 0x01, 0x01, 0x01, 0x01, byte(PUSH2), 0x01, 0x01, 0x01, 0x01, 0x01},
Want: bitvec{0b00000000_00000000_00000000_11000000, 0},
},
{
Code: []byte{byte(PUSH3), 0x01, 0x01, 0x01, byte(PUSH1), 0x01, 0x01, 0x01, 0x01, 0x01, 0x01},
Want: bitvec{0b00000000_00000000_00000000_00101110, 0},
},
{
Code: []byte{0x01, byte(PUSH8), 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01},
Want: bitvec{0b00000000_00000000_00000011_11111100, 0},
},
{
Code: []byte{byte(PUSH16), 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01},
Want: bitvec{0b00000000_00000001_11111111_11111110, 0},
},
{
Code: []byte{byte(PUSH8), 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, byte(PUSH1), 0x01},
Want: bitvec{0b_00000000_00000000_00000101_11111110, 0},
},
{Code: []byte{byte(PUSH32)}, Want: bitvec{0b11111111_11111111_11111111_11111110, 0b00000000_00000000_00000000_00000001}},
{
Code: []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},
Want: bitvec{0b10000000_00000000_00000000_00000000, 0b00000000_00000000_00000000_00000001, 0},
},
{
[]byte{
Code: []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},
Want: bitvec{0b00000000_00000000_00000000_00000000, 0b11111111_11111111_11111111_11111111, 0},
},
}
@ -103,20 +102,6 @@ func TestBitVec(t *testing.T) {
}
}
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) {
@ -125,24 +110,11 @@ func BenchmarkJumpdestAnalysis_1200k(bench *testing.B) {
bench.SetBytes(analysisCodeSize)
bench.ResetTimer()
for i := 0; i < bench.N; i++ {
codeBitmap(code)
newCodeBitVec(code)
}
}
func BenchmarkJumpdestAnalysis_rand(b *testing.B) {
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)
}
})
b.Run("v=new", func(b *testing.B) {
code := make([]byte, analysisCodeSize)
rand.Read(code)
@ -150,9 +122,8 @@ func BenchmarkJumpdestAnalysis_rand(b *testing.B) {
b.SetBytes(int64(len(code)))
b.ResetTimer()
for i := 0; i < b.N; i++ {
bv.codeBitVec(code)
bv.codeBitvecInternal(code)
}
})
}
var (
@ -162,23 +133,12 @@ var (
)
func BenchmarkJumpdestAnalysis_weth9(b *testing.B) {
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)
bv.codeBitvecInternal(codeWETH9)
}
})
}
func BenchmarkJumpdestHashing_1200k(bench *testing.B) {
@ -192,21 +152,17 @@ func BenchmarkJumpdestHashing_1200k(bench *testing.B) {
}
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)
code := bytes.Repeat([]byte{byte(op)}, analysisCodeSize)
bv := newCodeBitVec(code)
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
for j := range bv {
bv[j] = 0
}
codeBitmapInternal(code, bits)
bv.codeBitvecInternal(code)
}
}
for op = PUSH1; op <= PUSH32; op++ {
@ -216,31 +172,4 @@ func BenchmarkJumpdestOpAnalysis(b *testing.B) {
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))
})
}

View file

@ -99,7 +99,7 @@ func (c *Contract) validJumpdest(dest *uint256.Int) bool {
func (c *Contract) isCode(udest uint64) bool {
// Do we already have an analysis laying around?
if c.analysis != nil {
return c.analysis.codeSegment(udest)
return c.analysis.isCode(udest)
}
// Do we have a contract hash already?
// If we do have a hash, that means it's a 'regular' contract. For regular
@ -110,21 +110,21 @@ func (c *Contract) isCode(udest uint64) bool {
if !exist {
// Do the analysis and save in parent context
// We do not need to store it in c.analysis
analysis = codeBitmap(c.Code)
analysis = newCodeBitVec(c.Code)
c.jumpdests[c.CodeHash] = analysis
}
// Also stash it in current contract for faster access
c.analysis = analysis
return analysis.codeSegment(udest)
return analysis.isCode(udest)
}
// We don't have the code hash, most likely a piece of initcode not already
// in state trie. In that case, we do an analysis, and save it locally, so
// we don't have to recalculate it for every JUMP instruction in the execution
// However, we don't save it within the parent context
if c.analysis == nil {
c.analysis = codeBitmap(c.Code)
c.analysis = newCodeBitVec(c.Code)
}
return c.analysis.codeSegment(udest)
return c.analysis.isCode(udest)
}
// AsDelegate sets the contract to be a delegate call and returns the current