Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 116 additions & 0 deletions pkg/inventory/bitmap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package inventory

import (
"math/bits"
)

// ToolBitmap represents a set of tools as a bitmap for O(1) set operations.
// Uses 4 uint64s (256 bits) to support up to 256 tools.
// Current tool count is ~130, so this provides headroom for growth.
type ToolBitmap [4]uint64

// EmptyBitmap returns an empty bitmap with no bits set.
func EmptyBitmap() ToolBitmap {
return ToolBitmap{}
}

// SetBit returns a new bitmap with the bit at position i set.
func (b ToolBitmap) SetBit(i int) ToolBitmap {
if i < 0 || i >= 256 {
return b
}
word, bit := i/64, uint(i%64) //nolint:gosec // bounds checked above
result := b
result[word] |= 1 << bit
return result
}

// ClearBit returns a new bitmap with the bit at position i cleared.
func (b ToolBitmap) ClearBit(i int) ToolBitmap {
if i < 0 || i >= 256 {
return b
}
word, bit := i/64, uint(i%64) //nolint:gosec // bounds checked above
result := b
result[word] &^= 1 << bit
return result
}

// IsSet returns true if the bit at position i is set.
func (b ToolBitmap) IsSet(i int) bool {
if i < 0 || i >= 256 {
return false
}
word, bit := i/64, uint(i%64) //nolint:gosec // bounds checked above
return (b[word] & (1 << bit)) != 0
}

// Or returns the union of two bitmaps.
func (b ToolBitmap) Or(other ToolBitmap) ToolBitmap {
return ToolBitmap{
b[0] | other[0],
b[1] | other[1],
b[2] | other[2],
b[3] | other[3],
}
}

// And returns the intersection of two bitmaps.
func (b ToolBitmap) And(other ToolBitmap) ToolBitmap {
return ToolBitmap{
b[0] & other[0],
b[1] & other[1],
b[2] & other[2],
b[3] & other[3],
}
}

// AndNot returns b AND NOT other (bits in b that are not in other).
func (b ToolBitmap) AndNot(other ToolBitmap) ToolBitmap {
return ToolBitmap{
b[0] &^ other[0],
b[1] &^ other[1],
b[2] &^ other[2],
b[3] &^ other[3],
}
}

// PopCount returns the number of set bits (population count).
func (b ToolBitmap) PopCount() int {
return bits.OnesCount64(b[0]) +
bits.OnesCount64(b[1]) +
bits.OnesCount64(b[2]) +
bits.OnesCount64(b[3])
}

// IsEmpty returns true if no bits are set.
func (b ToolBitmap) IsEmpty() bool {
return b[0] == 0 && b[1] == 0 && b[2] == 0 && b[3] == 0
}

// Iterate calls fn for each set bit position. Stops if fn returns false.
func (b ToolBitmap) Iterate(fn func(position int) bool) {
for word := 0; word < 4; word++ {
v := b[word]
base := word * 64
for v != 0 {
// Find position of lowest set bit
tz := bits.TrailingZeros64(v)
if !fn(base + tz) {
return
}
// Clear the lowest set bit
v &= v - 1
}
}
}

// Positions returns a slice of all set bit positions.
func (b ToolBitmap) Positions() []int {
result := make([]int, 0, b.PopCount())
b.Iterate(func(pos int) bool {
result = append(result, pos)
return true
})
return result
}
216 changes: 216 additions & 0 deletions pkg/inventory/bitmap_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
package inventory

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestToolBitmap_SetAndIsSet(t *testing.T) {
t.Parallel()

var bm ToolBitmap

// Initially empty
assert.False(t, bm.IsSet(0))
assert.False(t, bm.IsSet(63))
assert.False(t, bm.IsSet(64))
assert.False(t, bm.IsSet(127))

// Set some bits
bm = bm.SetBit(0)
bm = bm.SetBit(63)
bm = bm.SetBit(64)
bm = bm.SetBit(127)
bm = bm.SetBit(200)

assert.True(t, bm.IsSet(0))
assert.True(t, bm.IsSet(63))
assert.True(t, bm.IsSet(64))
assert.True(t, bm.IsSet(127))
assert.True(t, bm.IsSet(200))

// Unset bits should still be false
assert.False(t, bm.IsSet(1))
assert.False(t, bm.IsSet(62))
assert.False(t, bm.IsSet(128))
}

func TestToolBitmap_ClearBit(t *testing.T) {
t.Parallel()

bm := ToolBitmap{}.SetBit(5).SetBit(10).SetBit(100)
assert.True(t, bm.IsSet(5))
assert.True(t, bm.IsSet(10))
assert.True(t, bm.IsSet(100))

bm = bm.ClearBit(10)
assert.True(t, bm.IsSet(5))
assert.False(t, bm.IsSet(10))
assert.True(t, bm.IsSet(100))
}

func TestToolBitmap_Or(t *testing.T) {
t.Parallel()

a := ToolBitmap{}.SetBit(1).SetBit(3).SetBit(65)
b := ToolBitmap{}.SetBit(2).SetBit(3).SetBit(130)

result := a.Or(b)

assert.True(t, result.IsSet(1))
assert.True(t, result.IsSet(2))
assert.True(t, result.IsSet(3))
assert.True(t, result.IsSet(65))
assert.True(t, result.IsSet(130))
assert.False(t, result.IsSet(0))
assert.False(t, result.IsSet(4))
}

func TestToolBitmap_And(t *testing.T) {
t.Parallel()

a := ToolBitmap{}.SetBit(1).SetBit(3).SetBit(5).SetBit(65)
b := ToolBitmap{}.SetBit(3).SetBit(5).SetBit(7).SetBit(65)

result := a.And(b)

assert.False(t, result.IsSet(1)) // only in a
assert.True(t, result.IsSet(3)) // in both
assert.True(t, result.IsSet(5)) // in both
assert.False(t, result.IsSet(7)) // only in b
assert.True(t, result.IsSet(65)) // in both
}

func TestToolBitmap_AndNot(t *testing.T) {
t.Parallel()

a := ToolBitmap{}.SetBit(1).SetBit(3).SetBit(5).SetBit(65)
b := ToolBitmap{}.SetBit(3).SetBit(7).SetBit(65)

result := a.AndNot(b)

assert.True(t, result.IsSet(1)) // in a, not in b
assert.False(t, result.IsSet(3)) // in both, removed
assert.True(t, result.IsSet(5)) // in a, not in b
assert.False(t, result.IsSet(7)) // not in a
assert.False(t, result.IsSet(65)) // in both, removed
}

func TestToolBitmap_PopCount(t *testing.T) {
t.Parallel()

assert.Equal(t, 0, ToolBitmap{}.PopCount())
assert.Equal(t, 1, ToolBitmap{}.SetBit(0).PopCount())
assert.Equal(t, 2, ToolBitmap{}.SetBit(0).SetBit(100).PopCount())
assert.Equal(t, 4, ToolBitmap{}.SetBit(0).SetBit(63).SetBit(64).SetBit(255).PopCount())
}

func TestToolBitmap_IsEmpty(t *testing.T) {
t.Parallel()

assert.True(t, ToolBitmap{}.IsEmpty())
assert.False(t, ToolBitmap{}.SetBit(0).IsEmpty())
assert.False(t, ToolBitmap{}.SetBit(200).IsEmpty())
}

func TestToolBitmap_Iterate(t *testing.T) {
t.Parallel()

bm := ToolBitmap{}.SetBit(3).SetBit(10).SetBit(64).SetBit(100).SetBit(200)

var positions []int
bm.Iterate(func(pos int) bool {
positions = append(positions, pos)
return true
})

assert.Equal(t, []int{3, 10, 64, 100, 200}, positions)
}

func TestToolBitmap_Iterate_EarlyStop(t *testing.T) {
t.Parallel()

bm := ToolBitmap{}.SetBit(1).SetBit(5).SetBit(10).SetBit(20)

var positions []int
bm.Iterate(func(pos int) bool {
positions = append(positions, pos)
return pos < 10 // stop after 10
})

assert.Equal(t, []int{1, 5, 10}, positions)
}

func TestToolBitmap_Positions(t *testing.T) {
t.Parallel()

bm := ToolBitmap{}.SetBit(5).SetBit(63).SetBit(64).SetBit(128)
positions := bm.Positions()

assert.Equal(t, []int{5, 63, 64, 128}, positions)
}

func TestToolBitmap_BoundaryConditions(t *testing.T) {
t.Parallel()

var bm ToolBitmap

// Negative index should be no-op
bm = bm.SetBit(-1)
assert.False(t, bm.IsSet(-1))

// Index >= 256 should be no-op
bm = bm.SetBit(256)
assert.False(t, bm.IsSet(256))

// Maximum valid index
bm = bm.SetBit(255)
assert.True(t, bm.IsSet(255))
}

func BenchmarkToolBitmap_Or(b *testing.B) {
a := ToolBitmap{0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0, 0}
c := ToolBitmap{0, 0, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF}

b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = a.Or(c)
}
}

func BenchmarkToolBitmap_And(b *testing.B) {
a := ToolBitmap{0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xAAAAAAAAAAAAAAAA, 0}
c := ToolBitmap{0xAAAAAAAAAAAAAAAA, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0}

b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = a.And(c)
}
}

func BenchmarkToolBitmap_PopCount(b *testing.B) {
bm := ToolBitmap{0xAAAAAAAAAAAAAAAA, 0x5555555555555555, 0xFFFFFFFF00000000, 0x00000000FFFFFFFF}

b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = bm.PopCount()
}
}

func BenchmarkToolBitmap_Iterate130Bits(b *testing.B) {
// Simulate ~130 tools
var bm ToolBitmap
for i := 0; i < 130; i++ {
bm = bm.SetBit(i)
}

b.ResetTimer()
for i := 0; i < b.N; i++ {
count := 0
bm.Iterate(func(_ int) bool {
count++
return true
})
}
}
Loading