blob: eb8cf908e456c61a49df0a1ab284f687cfa949b7 [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package bitutil
import (
"bytes"
"errors"
"math/bits"
"unsafe"
"github.com/apache/arrow-go/v18/arrow/endian"
"github.com/apache/arrow-go/v18/arrow/internal/debug"
"github.com/apache/arrow-go/v18/arrow/memory"
)
// BitmapReader is a simple bitmap reader for a byte slice.
type BitmapReader struct {
bitmap []byte
pos int
len int
current byte
byteOffset int
bitOffset int
}
// NewBitmapReader creates and returns a new bitmap reader for the given bitmap
func NewBitmapReader(bitmap []byte, offset, length int) *BitmapReader {
curbyte := byte(0)
if length > 0 && bitmap != nil {
curbyte = bitmap[offset/8]
}
return &BitmapReader{
bitmap: bitmap,
byteOffset: offset / 8,
bitOffset: offset % 8,
current: curbyte,
len: length,
}
}
// Set returns true if the current bit is set
func (b *BitmapReader) Set() bool {
return (b.current & (1 << b.bitOffset)) != 0
}
// NotSet returns true if the current bit is not set
func (b *BitmapReader) NotSet() bool {
return (b.current & (1 << b.bitOffset)) == 0
}
// Next advances the reader to the next bit in the bitmap.
func (b *BitmapReader) Next() {
b.bitOffset++
b.pos++
if b.bitOffset == 8 {
b.bitOffset = 0
b.byteOffset++
if b.pos < b.len {
b.current = b.bitmap[int(b.byteOffset)]
}
}
}
// Pos returns the current bit position in the bitmap that the reader is looking at
func (b *BitmapReader) Pos() int { return b.pos }
// Len returns the total number of bits in the bitmap
func (b *BitmapReader) Len() int { return b.len }
// BitmapWriter is a simple writer for writing bitmaps to byte slices
type BitmapWriter struct {
buf []byte
pos int
length int
curByte uint8
bitMask uint8
byteOffset int
}
// NewBitmapWriter returns a sequential bitwise writer that preserves surrounding
// bit values as it writes.
func NewBitmapWriter(bitmap []byte, start, length int) *BitmapWriter {
ret := &BitmapWriter{
buf: bitmap,
length: length,
byteOffset: start / 8,
bitMask: BitMask[start%8],
}
if length > 0 {
ret.curByte = bitmap[int(ret.byteOffset)]
}
return ret
}
// Reset resets the position and view of the slice to restart writing a bitmap
// to the same byte slice.
func (b *BitmapWriter) Reset(start, length int) {
b.pos = 0
b.byteOffset = start / 8
b.bitMask = BitMask[start%8]
b.length = length
if b.length > 0 {
b.curByte = b.buf[int(b.byteOffset)]
}
}
func (b *BitmapWriter) Pos() int { return b.pos }
func (b *BitmapWriter) Set() { b.curByte |= b.bitMask }
func (b *BitmapWriter) Clear() { b.curByte &= ^b.bitMask }
// Next increments the writer to the next bit for writing.
func (b *BitmapWriter) Next() {
b.bitMask = b.bitMask << 1
b.pos++
if b.bitMask == 0 {
b.bitMask = 0x01
b.buf[b.byteOffset] = b.curByte
b.byteOffset++
if b.pos < b.length {
b.curByte = b.buf[int(b.byteOffset)]
}
}
}
// AppendBools writes a series of booleans to the bitmapwriter and returns
// the number of remaining bytes left in the buffer for writing.
func (b *BitmapWriter) AppendBools(in []bool) int {
space := min(b.length-b.pos, len(in))
if space == 0 {
return 0
}
bitOffset := bits.TrailingZeros32(uint32(b.bitMask))
// location that the first byte needs to be written to for appending
appslice := b.buf[int(b.byteOffset) : b.byteOffset+int(BytesForBits(int64(bitOffset+space)))]
// update everything but curByte
appslice[0] = b.curByte
for i, b := range in[:space] {
if b {
SetBit(appslice, i+bitOffset)
} else {
ClearBit(appslice, i+bitOffset)
}
}
b.pos += space
b.bitMask = BitMask[(bitOffset+space)%8]
b.byteOffset += (bitOffset + space) / 8
b.curByte = appslice[len(appslice)-1]
return space
}
// AppendBitmap writes bits directly from a source bitmap to this bitmap writer,
// avoiding the intermediate []bool conversion. Returns the number of bits written.
func (b *BitmapWriter) AppendBitmap(srcBitmap []byte, srcOffset int64, length int64) int64 {
space := int64(min(b.length-b.pos, int(length)))
if space == 0 {
return 0
}
bitOffset := bits.TrailingZeros32(uint32(b.bitMask))
dstOffset := int64(b.byteOffset)*8 + int64(bitOffset)
// Flush curByte to buffer before CopyBitmap overwrites it
// Similar to how AppendBools writes curByte to appslice[0]
b.buf[b.byteOffset] = b.curByte
// Use CopyBitmap for efficient bit-level copying
CopyBitmap(srcBitmap, int(srcOffset), int(space), b.buf, int(dstOffset))
// Update writer state
b.pos += int(space)
newBitOffset := (bitOffset + int(space)) % 8
b.bitMask = BitMask[newBitOffset]
b.byteOffset += (bitOffset + int(space)) / 8
// Reload curByte to reflect the current byte's state after CopyBitmap
// We must reload even if pos == length, as Finish() may need to write curByte
if b.byteOffset < len(b.buf) {
b.curByte = b.buf[b.byteOffset]
}
return space
}
// Finish flushes the final byte out to the byteslice in case it was not already
// on a byte aligned boundary.
func (b *BitmapWriter) Finish() {
if b.length > 0 && (b.bitMask != 0x01 || b.pos < b.length) {
b.buf[int(b.byteOffset)] = b.curByte
}
}
// BitmapWordReader is a reader for bitmaps that reads a word at a time (a word being an 8 byte uint64)
// and then provides functions to grab the individual trailing bytes after the last word
type BitmapWordReader struct {
bitmap []byte
offset int
nwords int
trailingBits int
trailingBytes int
curword uint64
}
// NewBitmapWordReader sets up a word reader, calculates the number of trailing bits and
// number of trailing bytes, along with the number of words.
func NewBitmapWordReader(bitmap []byte, offset, length int) *BitmapWordReader {
bitoffset := offset % 8
byteOffset := offset / 8
bm := &BitmapWordReader{
offset: bitoffset,
bitmap: bitmap[byteOffset : byteOffset+int(BytesForBits(int64(bitoffset+length)))],
// decrement wordcount by 1 as we may touch two adjacent words in one iteration
nwords: length/int(unsafe.Sizeof(uint64(0))*8) - 1,
}
if bm.nwords < 0 {
bm.nwords = 0
}
bm.trailingBits = length - bm.nwords*int(unsafe.Sizeof(uint64(0)))*8
bm.trailingBytes = int(BytesForBits(int64(bm.trailingBits)))
if bm.nwords > 0 {
bm.curword = toFromLEFunc(endian.Native.Uint64(bm.bitmap))
} else if length > 0 {
setLSB(&bm.curword, bm.bitmap[0])
}
return bm
}
// NextWord returns the next full word read from the bitmap, should not be called
// if Words() is 0 as it will step outside of the bounds of the bitmap slice and panic.
//
// We don't perform the bounds checking in order to improve performance.
func (bm *BitmapWordReader) NextWord() uint64 {
bm.bitmap = bm.bitmap[unsafe.Sizeof(bm.curword):]
word := bm.curword
nextWord := toFromLEFunc(endian.Native.Uint64(bm.bitmap))
if bm.offset != 0 {
// combine two adjacent words into one word
// |<------ next ----->|<---- current ---->|
// +-------------+-----+-------------+-----+
// | --- | A | B | --- |
// +-------------+-----+-------------+-----+
// | | offset
// v v
// +-----+-------------+
// | A | B |
// +-----+-------------+
// |<------ word ----->|
word >>= uint64(bm.offset)
word |= nextWord << (int64(unsafe.Sizeof(uint64(0))*8) - int64(bm.offset))
}
bm.curword = nextWord
return word
}
// NextTrailingByte returns the next trailing byte of the bitmap after the last word
// along with the number of valid bits in that byte. When validBits < 8, that
// is the last byte.
//
// If the bitmap ends on a byte alignment, then the last byte can also return 8 valid bits.
// Thus the TrailingBytes function should be used to know how many trailing bytes to read.
func (bm *BitmapWordReader) NextTrailingByte() (val byte, validBits int) {
debug.Assert(bm.trailingBits > 0, "next trailing byte called with no trailing bits")
if bm.trailingBits <= 8 {
// last byte
validBits = bm.trailingBits
bm.trailingBits = 0
rdr := NewBitmapReader(bm.bitmap, bm.offset, validBits)
for i := 0; i < validBits; i++ {
val >>= 1
if rdr.Set() {
val |= 0x80
}
rdr.Next()
}
val >>= (8 - validBits)
return
}
bm.bitmap = bm.bitmap[1:]
nextByte := bm.bitmap[0]
val = getLSB(bm.curword)
if bm.offset != 0 {
val >>= byte(bm.offset)
val |= nextByte << (8 - bm.offset)
}
setLSB(&bm.curword, nextByte)
bm.trailingBits -= 8
bm.trailingBytes--
validBits = 8
return
}
func (bm *BitmapWordReader) Words() int { return bm.nwords }
func (bm *BitmapWordReader) TrailingBytes() int { return bm.trailingBytes }
// BitmapWordWriter is a bitmap writer for writing a full word at a time (a word being
// a uint64). After the last full word is written, PutNextTrailingByte can be used to
// write the remaining trailing bytes.
type BitmapWordWriter struct {
bitmap []byte
offset int
len int
bitMask uint64
currentWord uint64
}
// NewBitmapWordWriter initializes a new bitmap word writer which will start writing
// into the byte slice at bit offset start, expecting to write len bits.
func NewBitmapWordWriter(bitmap []byte, start, len int) *BitmapWordWriter {
ret := &BitmapWordWriter{
bitmap: bitmap[start/8:],
len: len,
offset: start % 8,
bitMask: (uint64(1) << uint64(start%8)) - 1,
}
if ret.offset != 0 {
if ret.len >= int(unsafe.Sizeof(uint64(0))*8) {
ret.currentWord = toFromLEFunc(endian.Native.Uint64(ret.bitmap))
} else if ret.len > 0 {
setLSB(&ret.currentWord, ret.bitmap[0])
}
}
return ret
}
// PutNextWord writes the given word to the bitmap, potentially splitting across
// two adjacent words.
func (bm *BitmapWordWriter) PutNextWord(word uint64) {
sz := int(unsafe.Sizeof(word))
if bm.offset != 0 {
// split one word into two adjacent words, don't touch unused bits
// |<------ word ----->|
// +-----+-------------+
// | A | B |
// +-----+-------------+
// | |
// v v offset
// +-------------+-----+-------------+-----+
// | --- | A | B | --- |
// +-------------+-----+-------------+-----+
// |<------ next ----->|<---- current ---->|
word = (word << uint64(bm.offset)) | (word >> (int64(sz*8) - int64(bm.offset)))
next := toFromLEFunc(endian.Native.Uint64(bm.bitmap[sz:]))
bm.currentWord = (bm.currentWord & bm.bitMask) | (word &^ bm.bitMask)
next = (next &^ bm.bitMask) | (word & bm.bitMask)
endian.Native.PutUint64(bm.bitmap, toFromLEFunc(bm.currentWord))
endian.Native.PutUint64(bm.bitmap[sz:], toFromLEFunc(next))
bm.currentWord = next
} else {
endian.Native.PutUint64(bm.bitmap, toFromLEFunc(word))
}
bm.bitmap = bm.bitmap[sz:]
}
// PutNextTrailingByte writes the number of bits indicated by validBits from b to
// the bitmap.
func (bm *BitmapWordWriter) PutNextTrailingByte(b byte, validBits int) {
curbyte := getLSB(bm.currentWord)
if validBits == 8 {
if bm.offset != 0 {
b = (b << bm.offset) | (b >> (8 - bm.offset))
next := bm.bitmap[1]
curbyte = (curbyte & byte(bm.bitMask)) | (b &^ byte(bm.bitMask))
next = (next &^ byte(bm.bitMask)) | (b & byte(bm.bitMask))
bm.bitmap[0] = curbyte
bm.bitmap[1] = next
bm.currentWord = uint64(next)
} else {
bm.bitmap[0] = b
}
bm.bitmap = bm.bitmap[1:]
} else {
debug.Assert(validBits > 0 && validBits < 8, "invalid valid bits in bitmap word writer")
debug.Assert(BytesForBits(int64(bm.offset+validBits)) <= int64(len(bm.bitmap)), "writing trailing byte outside of bounds of bitmap")
wr := NewBitmapWriter(bm.bitmap, int(bm.offset), validBits)
for i := 0; i < validBits; i++ {
if b&0x01 != 0 {
wr.Set()
} else {
wr.Clear()
}
wr.Next()
b >>= 1
}
wr.Finish()
}
}
type transferMode int8
const (
transferCopy transferMode = iota
transferInvert
)
func transferBitmap(mode transferMode, src []byte, srcOffset, length int, dst []byte, dstOffset int) {
if length == 0 {
// if there's nothing to write, end early.
return
}
bitOffset := srcOffset % 8
destBitOffset := dstOffset % 8
// slow path, one of the bitmaps are not byte aligned.
if bitOffset != 0 || destBitOffset != 0 {
rdr := NewBitmapWordReader(src, srcOffset, length)
wr := NewBitmapWordWriter(dst, dstOffset, length)
nwords := rdr.Words()
for nwords > 0 {
nwords--
if mode == transferInvert {
wr.PutNextWord(^rdr.NextWord())
} else {
wr.PutNextWord(rdr.NextWord())
}
}
nbytes := rdr.TrailingBytes()
for nbytes > 0 {
nbytes--
bt, validBits := rdr.NextTrailingByte()
if mode == transferInvert {
bt = ^bt
}
wr.PutNextTrailingByte(bt, validBits)
}
return
}
// fast path, both are starting with byte-aligned bitmaps
nbytes := int(BytesForBits(int64(length)))
// shift by its byte offset
src = src[srcOffset/8:]
dst = dst[dstOffset/8:]
// Take care of the trailing bits in the last byte
// E.g., if trailing_bits = 5, last byte should be
// - low 3 bits: new bits from last byte of data buffer
// - high 5 bits: old bits from last byte of dest buffer
trailingBits := nbytes*8 - length
trailMask := byte(uint(1)<<(8-trailingBits)) - 1
var lastData byte
if mode == transferInvert {
for i, b := range src[:nbytes-1] {
dst[i] = ^b
}
lastData = ^src[nbytes-1]
} else {
copy(dst, src[:nbytes-1])
lastData = src[nbytes-1]
}
dst[nbytes-1] &= ^trailMask
dst[nbytes-1] |= lastData & trailMask
}
// CopyBitmap copies the bitmap indicated by src, starting at bit offset srcOffset,
// and copying length bits into dst, starting at bit offset dstOffset.
func CopyBitmap(src []byte, srcOffset, length int, dst []byte, dstOffset int) {
transferBitmap(transferCopy, src, srcOffset, length, dst, dstOffset)
}
// InvertBitmap copies a bit range of a bitmap, inverting it as it copies
// over into the destination.
func InvertBitmap(src []byte, srcOffset, length int, dst []byte, dstOffset int) {
transferBitmap(transferInvert, src, srcOffset, length, dst, dstOffset)
}
type bitOp struct {
opWord func(uint64, uint64) uint64
opByte func(byte, byte) byte
opAligned func(l, r, o []byte)
}
var (
bitAndOp = bitOp{
opWord: func(l, r uint64) uint64 { return l & r },
opByte: func(l, r byte) byte { return l & r },
opAligned: alignedBitAndGo,
}
bitOrOp = bitOp{
opWord: func(l, r uint64) uint64 { return l | r },
opByte: func(l, r byte) byte { return l | r },
opAligned: alignedBitOrGo,
}
bitAndNotOp = bitOp{
opWord: func(l, r uint64) uint64 { return l &^ r },
opByte: func(l, r byte) byte { return l &^ r },
opAligned: alignedBitAndNotGo,
}
bitXorOp = bitOp{
opWord: func(l, r uint64) uint64 { return l ^ r },
opByte: func(l, r byte) byte { return l ^ r },
opAligned: alignedBitXorGo,
}
bitXnorOp = bitOp{
opWord: func(l, r uint64) uint64 { return ^(l ^ r) },
opByte: func(l, r byte) byte { return ^(l ^ r) },
opAligned: alignedBitXnorGo,
}
)
func alignedBitmapOp(op bitOp, left, right []byte, lOffset, rOffset int64, out []byte, outOffset int64, length int64) {
debug.Assert(lOffset%8 == rOffset%8, "aligned bitmap op called with unaligned offsets")
debug.Assert(lOffset%8 == outOffset%8, "aligned bitmap op called with unaligned output offset")
nbytes := BytesForBits(length + lOffset%8)
left = left[lOffset/8:]
right = right[rOffset/8:]
out = out[outOffset/8:]
endMask := (lOffset + length%8)
switch nbytes {
case 0:
return
case 1: // everything within a single byte
// (length+lOffset%8) <= 8
mask := PrecedingBitmask[lOffset%8]
if endMask != 0 {
mask |= TrailingBitmask[(lOffset+length)%8]
}
out[0] = (out[0] & mask) | (op.opByte(left[0], right[0]) &^ mask)
case 2: // don't send zero length to opAligned
firstByteMask := PrecedingBitmask[lOffset%8]
out[0] = (out[0] & firstByteMask) | (op.opByte(left[0], right[0]) &^ firstByteMask)
lastByteMask := byte(0)
if endMask != 0 {
lastByteMask = TrailingBitmask[(lOffset+length)%8]
}
out[1] = (out[1] & lastByteMask) | (op.opByte(left[1], right[1]) &^ lastByteMask)
default:
firstByteMask := PrecedingBitmask[lOffset%8]
out[0] = (out[0] & firstByteMask) | (op.opByte(left[0], right[0]) &^ firstByteMask)
op.opAligned(left[1:nbytes-1], right[1:nbytes-1], out[1:nbytes-1])
lastByteMask := byte(0)
if endMask != 0 {
lastByteMask = TrailingBitmask[(lOffset+length)%8]
}
out[nbytes-1] = (out[nbytes-1] & lastByteMask) | (op.opByte(left[nbytes-1], right[nbytes-1]) &^ lastByteMask)
}
}
func unalignedBitmapOp(op bitOp, left, right []byte, lOffset, rOffset int64, out []byte, outOffset int64, length int64) {
leftRdr := NewBitmapWordReader(left, int(lOffset), int(length))
rightRdr := NewBitmapWordReader(right, int(rOffset), int(length))
writer := NewBitmapWordWriter(out, int(outOffset), int(length))
for nwords := leftRdr.Words(); nwords > 0; nwords-- {
writer.PutNextWord(op.opWord(leftRdr.NextWord(), rightRdr.NextWord()))
}
for nbytes := leftRdr.TrailingBytes(); nbytes > 0; nbytes-- {
leftByte, leftValid := leftRdr.NextTrailingByte()
rightByte, rightValid := rightRdr.NextTrailingByte()
debug.Assert(leftValid == rightValid, "unexpected mismatch of valid bits")
writer.PutNextTrailingByte(op.opByte(leftByte, rightByte), leftValid)
}
}
func BitmapOp(op bitOp, left, right []byte, lOffset, rOffset int64, out []byte, outOffset, length int64) {
if (outOffset%8 == lOffset%8) && (outOffset%8 == rOffset%8) {
// fastcase!
alignedBitmapOp(op, left, right, lOffset, rOffset, out, outOffset, length)
} else {
unalignedBitmapOp(op, left, right, lOffset, rOffset, out, outOffset, length)
}
}
func BitmapOpAlloc(mem memory.Allocator, op bitOp, left, right []byte, lOffset, rOffset int64, length int64, outOffset int64) *memory.Buffer {
bits := length + outOffset
buf := memory.NewResizableBuffer(mem)
buf.Resize(int(BytesForBits(bits)))
BitmapOp(op, left, right, lOffset, rOffset, buf.Bytes(), outOffset, length)
return buf
}
func BitmapAnd(left, right []byte, lOffset, rOffset int64, out []byte, outOffset int64, length int64) {
BitmapOp(bitAndOp, left, right, lOffset, rOffset, out, outOffset, length)
}
func BitmapOr(left, right []byte, lOffset, rOffset int64, out []byte, outOffset int64, length int64) {
BitmapOp(bitOrOp, left, right, lOffset, rOffset, out, outOffset, length)
}
func BitmapAndAlloc(mem memory.Allocator, left, right []byte, lOffset, rOffset int64, length, outOffset int64) *memory.Buffer {
return BitmapOpAlloc(mem, bitAndOp, left, right, lOffset, rOffset, length, outOffset)
}
func BitmapOrAlloc(mem memory.Allocator, left, right []byte, lOffset, rOffset int64, length, outOffset int64) *memory.Buffer {
return BitmapOpAlloc(mem, bitOrOp, left, right, lOffset, rOffset, length, outOffset)
}
func BitmapAndNot(left, right []byte, lOffset, rOffset int64, out []byte, outOffset int64, length int64) {
BitmapOp(bitAndNotOp, left, right, lOffset, rOffset, out, outOffset, length)
}
func BitmapAndNotAlloc(mem memory.Allocator, left, right []byte, lOffset, rOffset int64, length, outOffset int64) *memory.Buffer {
return BitmapOpAlloc(mem, bitAndNotOp, left, right, lOffset, rOffset, length, outOffset)
}
func BitmapXor(left, right []byte, lOffset, rOffset int64, out []byte, outOffset int64, length int64) {
BitmapOp(bitXorOp, left, right, lOffset, rOffset, out, outOffset, length)
}
func BitmapXorAlloc(mem memory.Allocator, left, right []byte, lOffset, rOffset int64, length, outOffset int64) *memory.Buffer {
return BitmapOpAlloc(mem, bitXorOp, left, right, lOffset, rOffset, length, outOffset)
}
func BitmapXnor(left, right []byte, lOffset, rOffset int64, out []byte, outOffset int64, length int64) {
BitmapOp(bitXnorOp, left, right, lOffset, rOffset, out, outOffset, length)
}
func BitmapXnorAlloc(mem memory.Allocator, left, right []byte, lOffset, rOffset int64, length, outOffset int64) *memory.Buffer {
return BitmapOpAlloc(mem, bitXnorOp, left, right, lOffset, rOffset, length, outOffset)
}
func BitmapEquals(left, right []byte, lOffset, rOffset int64, length int64) bool {
if lOffset%8 == 0 && rOffset%8 == 0 {
// byte aligned, fast path, can use bytes.Equal (memcmp)
byteLen := length / 8
lStart := lOffset / 8
rStart := rOffset / 8
if !bytes.Equal(left[lStart:lStart+byteLen], right[rStart:rStart+byteLen]) {
return false
}
// check trailing bits
for i := (length / 8) * 8; i < length; i++ {
if BitIsSet(left, int(lOffset+i)) != BitIsSet(right, int(rOffset+i)) {
return false
}
}
return true
}
lrdr := NewBitmapWordReader(left, int(lOffset), int(length))
rrdr := NewBitmapWordReader(right, int(rOffset), int(length))
nwords := lrdr.Words()
for nwords > 0 {
nwords--
if lrdr.NextWord() != rrdr.NextWord() {
return false
}
}
nbytes := lrdr.TrailingBytes()
for nbytes > 0 {
nbytes--
lbt, _ := lrdr.NextTrailingByte()
rbt, _ := rrdr.NextTrailingByte()
if lbt != rbt {
return false
}
}
return true
}
// OptionalBitIndexer is a convenience wrapper for getting bits from
// a bitmap which may or may not be nil.
type OptionalBitIndexer struct {
Bitmap []byte
Offset int
}
func (b *OptionalBitIndexer) GetBit(i int) bool {
return b.Bitmap == nil || BitIsSet(b.Bitmap, b.Offset+i)
}
type Bitmap struct {
Data []byte
Offset, Len int64
}
func bitLength(bitmaps []Bitmap) (int64, error) {
for _, b := range bitmaps[1:] {
if b.Len != bitmaps[0].Len {
return -1, errors.New("bitmaps must be same length")
}
}
return bitmaps[0].Len, nil
}
func runVisitWordsAndWriteLoop(bitLen int64, rdrs []*BitmapWordReader, wrs []*BitmapWordWriter, visitor func(in, out []uint64)) {
const bitWidth int64 = int64(uint64SizeBits)
visited := make([]uint64, len(rdrs))
output := make([]uint64, len(wrs))
// every reader will have same number of words, since they are same
// length'ed. This will be inefficient in some cases. When there's
// offsets beyond the Word boundary, every word would have to be
// created from 2 adjoining words
nwords := int64(rdrs[0].Words())
bitLen -= nwords * bitWidth
for nwords > 0 {
nwords--
for i := range visited {
visited[i] = rdrs[i].NextWord()
}
visitor(visited, output)
for i := range output {
wrs[i].PutNextWord(output[i])
}
}
// every reader will have the same number of trailing bytes, because
// we already confirmed they have the same length. Because
// offsets beyond the Word boundary can cause adjoining words, the
// tailing portion could be more than one word remaining full/partial
// words to write.
if bitLen == 0 {
return
}
// convert the word visitor to a bytevisitor
byteVisitor := func(in, out []byte) {
for i, w := range in {
visited[i] = uint64(w)
}
visitor(visited, output)
for i, w := range output {
out[i] = byte(w)
}
}
visitedBytes := make([]byte, len(rdrs))
outputBytes := make([]byte, len(wrs))
nbytes := rdrs[0].trailingBytes
for nbytes > 0 {
nbytes--
memory.Set(visitedBytes, 0)
memory.Set(outputBytes, 0)
var validBits int
for i := range rdrs {
visitedBytes[i], validBits = rdrs[i].NextTrailingByte()
}
byteVisitor(visitedBytes, outputBytes)
for i, w := range outputBytes {
wrs[i].PutNextTrailingByte(w, validBits)
}
}
}
// VisitWordsAndWrite visits words of bits from each input bitmap and
// collects outputs to a slice of output Bitmaps.
//
// All bitmaps must have identical lengths. The first bit in a visited
// bitmap may be offset within the first visited word, but words will
// otherwise contain densely packed bits loaded from the bitmap. That
// offset within the first word is returned.
//
// NOTE: this function is efficient on 3+ sufficiently large bitmaps.
// It also has a large prolog/epilog overhead and should be used
// carefully in other cases. For 2 or fewer bitmaps, and/or smaller
// bitmaps, try BitmapReader and or other utilities.
func VisitWordsAndWrite(args []Bitmap, out []Bitmap, visitor func(in, out []uint64)) error {
bitLen, err := bitLength(args)
if err != nil {
return err
}
rdrs, wrs := make([]*BitmapWordReader, len(args)), make([]*BitmapWordWriter, len(out))
for i, in := range args {
rdrs[i] = NewBitmapWordReader(in.Data, int(in.Offset), int(in.Len))
}
for i, o := range out {
wrs[i] = NewBitmapWordWriter(o.Data, int(o.Offset), int(o.Len))
}
runVisitWordsAndWriteLoop(bitLen, rdrs, wrs, visitor)
return nil
}