diff --git a/core/vm/analysis.go b/core/vm/analysis.go index 555d00a04d..b5ef0b6cbe 100644 --- a/core/vm/analysis.go +++ b/core/vm/analysis.go @@ -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] diff --git a/core/vm/analysis_test.go b/core/vm/analysis_test.go index f9ceebfb19..135739e255 100644 --- a/core/vm/analysis_test.go +++ b/core/vm/analysis_test.go @@ -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,34 +110,20 @@ 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) + 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) - - bv := newCodeBitVec(code) - b.SetBytes(int64(len(code))) - b.ResetTimer() - for i := 0; i < b.N; i++ { - bv.codeBitVec(code) - } - }) + bv := newCodeBitVec(code) + b.SetBytes(int64(len(code))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + 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 := newCodeBitVec(codeWETH9) + b.SetBytes(int64(len(codeWETH9))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + bv.codeBitvecInternal(codeWETH9) + } } func BenchmarkJumpdestHashing_1200k(bench *testing.B) { @@ -192,55 +152,24 @@ 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) - 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) + var op OpCode + bencher := func(b *testing.B) { + code := bytes.Repeat([]byte{byte(op)}, analysisCodeSize) + bv := newCodeBitVec(code) + b.SetBytes(analysisCodeSize) + b.ResetTimer() + for i := 0; i < b.N; i++ { + for j := range bv { + bv[j] = 0 } + bv.codeBitvecInternal(code) } - for op = PUSH1; op <= PUSH32; op++ { - b.Run(op.String(), bencher) - } - op = JUMPDEST + } + for op = PUSH1; op <= PUSH32; op++ { 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)) - }) + } + op = JUMPDEST + b.Run(op.String(), bencher) + op = STOP + b.Run(op.String(), bencher) } diff --git a/core/vm/contract.go b/core/vm/contract.go index 16b669ebca..da63620e60 100644 --- a/core/vm/contract.go +++ b/core/vm/contract.go @@ -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