Files
2023-10-18 01:56:01 -07:00

590 lines
17 KiB
Go

// Copyright 2023 Dolthub, Inc.
//
// Licensed 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.
// Copyright 2018 The Cockroach Authors.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
package utils
import (
"bytes"
"fmt"
"math/rand"
"unsafe"
"github.com/cockroachdb/errors"
"github.com/dolthub/doltgresql/postgres/parser/pgcode"
"github.com/dolthub/doltgresql/postgres/parser/pgerror"
)
// BitArray implements a bit string of arbitrary length.
//
// This uses a packed encoding (i.e. groups of 64 bits at a time) for
// memory efficiency and speed of bitwise operations (enables use of
// full machine registers for comparisons and logical operations),
// akin to the big.nat type.
//
// There is something fancy needed to handle sorting values properly:
// the last group of bits must be padded right (start on the MSB)
// inside its word to compare properly according to pg semantics.
//
// This type is designed for immutable instances. The functions and
// methods defined below never write to a bit array in-place. Of note,
// the ToWidth() and Next() functions will share the backing array
// between their operand and their result in some cases.
//
// For portability, the size of the backing word is guaranteed to be 64
// bits.
type BitArray struct {
// words is the backing array.
//
// The leftmost bits in the literal representation are placed in the
// MSB of each word.
//
// The last word contain the rightmost bits in the literal
// representation, right-padded. For example if there are 3 bits
// to store, the 3 MSB bits of the last word will be set and the
// remaining LSB bits will be set to zero.
//
// The number of stored bits is actually:
// 0 if lastBitsUsed = 0 or len(word) == 0
// otherwise, (len(words)-1)*numBitsPerWord + lastBitsUsed
//
// TODO(jutin, nathan): consider using the trick in bytes.Buffer of
// keeping a static [1]word which word can initially point to to
// avoid heap allocations in the common case of small arrays.
words []word
// lastBitsUsed is the number of bits in the last word that
// participate in the value stored. It can only be zero
// for empty bit arrays; otherwise it's always between 1 and
// numBitsPerWord.
//
// For example:
// - 0 bits in array: len(words) == 0, lastBitsUsed = 0
// - 1 bits in array: len(words) == 1, lastBitsUsed = 1
// - 64 bits in array: len(words) == 1, lastBitsUsed = 64
// - 65 bits in array: len(words) == 2, lastBitsUsed = 1
lastBitsUsed uint8
}
type word = uint64
const numBytesPerWord = 8
const numBitsPerWord = 64
// BitLen returns the number of bits stored.
func (d BitArray) BitLen() uint {
if len(d.words) == 0 {
return 0
}
return d.nonEmptyBitLen()
}
func (d BitArray) nonEmptyBitLen() uint {
return uint(len(d.words)-1)*numBitsPerWord + uint(d.lastBitsUsed)
}
// String implements the fmt.Stringer interface.
func (d BitArray) String() string {
var buf bytes.Buffer
d.Format(&buf)
return buf.String()
}
// Clone makes a copy of the bit array.
func (d BitArray) Clone() BitArray {
return BitArray{
words: append([]word(nil), d.words...),
lastBitsUsed: d.lastBitsUsed,
}
}
// MakeZeroBitArray creates a bit array with the specified bit size.
func MakeZeroBitArray(bitLen uint) BitArray {
a, b := EncodingPartsForBitLen(bitLen)
return mustFromEncodingParts(a, b)
}
// ToWidth resizes the bit array to the specified size.
// If the specified width is shorter, bits on the right are truncated away.
// If the specified width is larger, zero bits are added on the right.
func (d BitArray) ToWidth(desiredLen uint) BitArray {
bitlen := d.BitLen()
if bitlen == desiredLen {
// Nothing to do; fast path.
return d
}
if desiredLen == 0 {
// Nothing to do; fast path.
return BitArray{}
}
if desiredLen < bitlen {
// Destructive, we have to copy.
words, lastBitsUsed := EncodingPartsForBitLen(desiredLen)
copy(words, d.words[:len(words)])
words[len(words)-1] &= (^word(0) << (numBitsPerWord - lastBitsUsed))
return mustFromEncodingParts(words, lastBitsUsed)
}
// New length is larger.
numWords, lastBitsUsed := SizesForBitLen(desiredLen)
var words []word
if numWords <= uint(cap(d.words)) {
words = d.words[0:numWords]
} else {
words = make([]word, numWords)
copy(words, d.words)
}
return mustFromEncodingParts(words, lastBitsUsed)
}
// Sizeof returns the size in bytes of the bit array and its components.
func (d BitArray) Sizeof() uintptr {
return unsafe.Sizeof(d) + uintptr(numBytesPerWord*cap(d.words))
}
// IsEmpty returns true iff the array is empty.
func (d BitArray) IsEmpty() bool {
return d.lastBitsUsed == 0
}
// MakeBitArrayFromInt64 creates a bit array with the specified
// size. The bits from the integer are written to the right of the bit
// array and the sign bit is extended.
func MakeBitArrayFromInt64(bitLen uint, val int64, valWidth uint) BitArray {
if bitLen == 0 {
return BitArray{}
}
d := MakeZeroBitArray(bitLen)
if bitLen < valWidth {
// Fast path, no sign extension to compute.
d.words[len(d.words)-1] = word(val << (numBitsPerWord - bitLen))
return d
}
if val&(1<<(valWidth-1)) != 0 {
// Sign extend, fill ones in every word but the last.
for i := 0; i < len(d.words)-1; i++ {
d.words[i] = ^word(0)
}
}
// Shift the value to its given number of bits, to position the sign
// bit to the left.
val = val << (numBitsPerWord - valWidth)
// Shift right back with arithmetic shift to extend the sign bit.
val = val >> (numBitsPerWord - valWidth)
// Store the right part of the value in the last word.
d.words[len(d.words)-1] = word(val << (numBitsPerWord - d.lastBitsUsed))
// Store the left part in the next-to-last word, if any.
if valWidth > uint(d.lastBitsUsed) {
d.words[len(d.words)-2] = word(val >> d.lastBitsUsed)
}
return d
}
// AsInt64 returns the int constituted from the rightmost bits in the
// bit array.
func (d BitArray) AsInt64(nbits uint) int64 {
if d.lastBitsUsed == 0 {
// Fast path.
return 0
}
lowPart := d.words[len(d.words)-1] >> (numBitsPerWord - d.lastBitsUsed)
highPart := word(0)
if nbits > uint(d.lastBitsUsed) && len(d.words) > 1 {
highPart = d.words[len(d.words)-2] << d.lastBitsUsed
}
combined := lowPart | highPart
signExtended := int64(combined<<(numBitsPerWord-nbits)) >> (numBitsPerWord - nbits)
return signExtended
}
// LeftShiftAny performs a logical left shift, with a possible
// negative count.
// The number of bits to shift can be arbitrarily large (i.e. possibly
// larger than 64 in absolute value).
func (d BitArray) LeftShiftAny(n int64) BitArray {
bitlen := d.BitLen()
if n == 0 || bitlen == 0 {
// Fast path.
return d
}
r := MakeZeroBitArray(bitlen)
if (n > 0 && n > int64(bitlen)) || (n < 0 && -n > int64(bitlen)) {
// Fast path.
return r
}
if n > 0 {
// This is a left shift.
dstWord := uint(0)
srcWord := uint(uint64(n) / numBitsPerWord)
srcShift := uint(uint64(n) % numBitsPerWord)
for i, j := srcWord, dstWord; i < uint(len(d.words)); i++ {
r.words[j] = d.words[i] << srcShift
j++
}
for i, j := srcWord+1, dstWord; i < uint(len(d.words)); i++ {
r.words[j] |= d.words[i] >> (numBitsPerWord - srcShift)
j++
}
} else {
// A right shift.
n = -n
srcWord := uint(0)
dstWord := uint(uint64(n) / numBitsPerWord)
srcShift := uint(uint64(n) % numBitsPerWord)
for i, j := srcWord, dstWord; j < uint(len(r.words)); i++ {
r.words[j] = d.words[i] >> srcShift
j++
}
for i, j := srcWord, dstWord+1; j < uint(len(r.words)); i++ {
r.words[j] |= d.words[i] << (numBitsPerWord - srcShift)
j++
}
// Erase the trailing bits that are not used any more.
// See #36606.
if len(r.words) > 0 {
r.words[len(r.words)-1] &= ^word(0) << (numBitsPerWord - r.lastBitsUsed)
}
}
return r
}
// byteReprs contains the bit representation of the 256 possible
// groups of 8 bits.
var byteReprs = func() (ret [256]string) {
for i := range ret {
// Change this format if numBitsPerWord changes.
ret[i] = fmt.Sprintf("%08b", i)
}
return ret
}()
// Format prints out the bit array to the buffer.
func (d BitArray) Format(buf *bytes.Buffer) {
bitLen := d.BitLen()
buf.Grow(int(bitLen))
for i := uint(0); i < bitLen/numBitsPerWord; i++ {
w := d.words[i]
// Change this loop if numBitsPerWord changes.
buf.WriteString(byteReprs[(w>>56)&0xff])
buf.WriteString(byteReprs[(w>>48)&0xff])
buf.WriteString(byteReprs[(w>>40)&0xff])
buf.WriteString(byteReprs[(w>>32)&0xff])
buf.WriteString(byteReprs[(w>>24)&0xff])
buf.WriteString(byteReprs[(w>>16)&0xff])
buf.WriteString(byteReprs[(w>>8)&0xff])
buf.WriteString(byteReprs[(w>>0)&0xff])
}
remainingBits := bitLen % numBitsPerWord
if remainingBits > 0 {
lastWord := d.words[bitLen/numBitsPerWord]
minShift := numBitsPerWord - 1 - remainingBits
for i := numBitsPerWord - 1; i > int(minShift); i-- {
bitVal := (lastWord >> uint(i)) & 1
buf.WriteByte('0' + byte(bitVal))
}
}
}
// EncodingPartsForBitLen creates a word backing array and the
// "last bits used" value given the given total number of bits.
func EncodingPartsForBitLen(bitLen uint) ([]uint64, uint64) {
if bitLen == 0 {
return nil, 0
}
numWords, lastBitsUsed := SizesForBitLen(bitLen)
words := make([]word, numWords)
return words, lastBitsUsed
}
// SizesForBitLen computes the number of words and last bits used for
// the requested bit array size.
func SizesForBitLen(bitLen uint) (uint, uint64) {
// This computes ceil(bitLen / numBitsPerWord).
numWords := (bitLen + numBitsPerWord - 1) / numBitsPerWord
lastBitsUsed := uint64(bitLen % numBitsPerWord)
if lastBitsUsed == 0 {
lastBitsUsed = numBitsPerWord
}
return numWords, lastBitsUsed
}
// Parse parses a bit array from the specified string.
func Parse(s string) (res BitArray, err error) {
if len(s) == 0 {
return res, nil
}
words, lastBitsUsed := EncodingPartsForBitLen(uint(len(s)))
// Parse the bits.
wordIdx := 0
bitIdx := uint(0)
curWord := word(0)
for _, c := range s {
val := word(c - '0')
bitVal := val & 1
if bitVal != val {
// Note: the prefix "could not parse" is important as it is used
// to detect parsing errors in tests.
err := fmt.Errorf(`could not parse string as bit array: "%c" is not a valid binary digit`, c)
return res, pgerror.WithCandidateCode(err, pgcode.InvalidTextRepresentation)
}
curWord |= bitVal << (63 - bitIdx)
bitIdx = (bitIdx + 1) % numBitsPerWord
if bitIdx == 0 {
words[wordIdx] = curWord
curWord = 0
wordIdx++
}
}
if bitIdx > 0 {
// Ensure the last word is stored.
words[wordIdx] = curWord
}
return FromEncodingParts(words, lastBitsUsed)
}
// Concat concatenates two bit arrays.
func Concat(lhs, rhs BitArray) BitArray {
if lhs.lastBitsUsed == 0 {
return rhs
}
if rhs.lastBitsUsed == 0 {
return lhs
}
words := make([]word, (lhs.nonEmptyBitLen()+rhs.nonEmptyBitLen()+numBitsPerWord-1)/numBitsPerWord)
// The first bits come from the lhs unchanged.
copy(words, lhs.words)
var lastBitsUsed uint8
if lhs.lastBitsUsed == numBitsPerWord {
// Fast path. Just concatenate.
copy(words[len(lhs.words):], rhs.words)
lastBitsUsed = rhs.lastBitsUsed
} else {
// We need to shift all the words in the RHS
// by the lastBitsUsed of the LHS.
rhsShift := lhs.lastBitsUsed
targetWordIdx := len(lhs.words) - 1
trailingBits := words[targetWordIdx]
for _, w := range rhs.words {
headingBits := w >> rhsShift
combinedBits := trailingBits | headingBits
words[targetWordIdx] = combinedBits
targetWordIdx++
trailingBits = w << (numBitsPerWord - rhsShift)
}
lastBitsUsed = lhs.lastBitsUsed + rhs.lastBitsUsed
if lastBitsUsed > numBitsPerWord {
// Some bits from the RHS didn't fill a
// word, we need to fit them in the last word.
words[targetWordIdx] = trailingBits
}
// Compute the final thing.
lastBitsUsed %= numBitsPerWord
if lastBitsUsed == 0 {
lastBitsUsed = numBitsPerWord
}
}
return BitArray{words: words, lastBitsUsed: lastBitsUsed}
}
// Not computes the complement of a bit array.
func Not(d BitArray) BitArray {
res := d.Clone()
for i, w := range res.words {
res.words[i] = ^w
}
if res.lastBitsUsed > 0 {
lastWord := len(res.words) - 1
res.words[lastWord] &= (^word(0) << (numBitsPerWord - res.lastBitsUsed))
}
return res
}
// And computes the logical AND of two bit arrays.
// The caller must ensure they have the same bit size.
func And(lhs, rhs BitArray) BitArray {
res := lhs.Clone()
for i, w := range rhs.words {
res.words[i] &= w
}
return res
}
// Or computes the logical OR of two bit arrays.
// The caller must ensure they have the same bit size.
func Or(lhs, rhs BitArray) BitArray {
res := lhs.Clone()
for i, w := range rhs.words {
res.words[i] |= w
}
return res
}
// Xor computes the logical XOR of two bit arrays.
// The caller must ensure they have the same bit size.
func Xor(lhs, rhs BitArray) BitArray {
res := lhs.Clone()
for i, w := range rhs.words {
res.words[i] ^= w
}
return res
}
// Compare compares two bit arrays. They can have mixed sizes.
func Compare(lhs, rhs BitArray) int {
n := len(lhs.words)
if n > len(rhs.words) {
n = len(rhs.words)
}
i := 0
for ; i < n; i++ {
lw := lhs.words[i]
rw := rhs.words[i]
if lw < rw {
return -1
}
if lw > rw {
return 1
}
}
if i < len(rhs.words) {
// lhs is shorter.
return -1
}
if i < len(lhs.words) {
// rhs is shorter.
return 1
}
// Same length.
if lhs.lastBitsUsed < rhs.lastBitsUsed {
return -1
}
if lhs.lastBitsUsed > rhs.lastBitsUsed {
return 1
}
return 0
}
// EncodingParts retrieves the encoding bits from the bit array. The
// words are presented in big-endian order, with the leftmost bits of
// the bitarray (MSB) in the MSB of each word.
func (d BitArray) EncodingParts() ([]uint64, uint64) {
return d.words, uint64(d.lastBitsUsed)
}
// FromEncodingParts creates a bit array from the encoding parts.
func FromEncodingParts(words []uint64, lastBitsUsed uint64) (BitArray, error) {
if lastBitsUsed > numBitsPerWord {
err := fmt.Errorf("FromEncodingParts: lastBitsUsed must not exceed %d, got %d",
errors.Safe(numBitsPerWord), errors.Safe(lastBitsUsed))
return BitArray{}, pgerror.WithCandidateCode(err, pgcode.InvalidParameterValue)
}
return BitArray{
words: words,
lastBitsUsed: uint8(lastBitsUsed),
}, nil
}
// mustFromEncodingParts is like FromEncodingParts but errors cause a panic.
func mustFromEncodingParts(words []uint64, lastBitsUsed uint64) BitArray {
ba, err := FromEncodingParts(words, lastBitsUsed)
if err != nil {
panic(err)
}
return ba
}
// Rand generates a random bit array of the specified length.
func Rand(rng *rand.Rand, bitLen uint) BitArray {
d := MakeZeroBitArray(bitLen)
for i := range d.words {
d.words[i] = rng.Uint64()
}
if len(d.words) > 0 {
d.words[len(d.words)-1] <<= (numBitsPerWord - d.lastBitsUsed)
}
return d
}
// Next returns the next possible bit array in lexicographic order.
// The backing array of words is shared if possible.
func Next(d BitArray) BitArray {
if d.lastBitsUsed == 0 {
return BitArray{words: []word{0}, lastBitsUsed: 1}
}
if d.lastBitsUsed < numBitsPerWord {
res := d
res.lastBitsUsed++
return res
}
res := BitArray{
words: make([]word, len(d.words)+1),
lastBitsUsed: 1,
}
copy(res.words, d.words)
return res
}
// GetBitAtIndex extract bit at given index in the BitArray.
func (d BitArray) GetBitAtIndex(index int) (int, error) {
// Check whether index asked is inside BitArray.
if index < 0 || uint(index) >= d.BitLen() {
err := fmt.Errorf("GetBitAtIndex: bit index %d out of valid range (0..%d)", index, int(d.BitLen())-1)
return 0, pgerror.WithCandidateCode(err, pgcode.ArraySubscript)
}
// To extract bit at the given index, we have to determine the
// position within words array, i.e. index/numBitsPerWord after
// that checked the bit at residual index.
if d.words[index/numBitsPerWord]&(word(1)<<(numBitsPerWord-1-uint(index)%numBitsPerWord)) != 0 {
return 1, nil
}
return 0, nil
}
// SetBitAtIndex returns the BitArray with an updated bit at a given index.
func (d BitArray) SetBitAtIndex(index, toSet int) (BitArray, error) {
res := d.Clone()
// Check whether index asked is inside BitArray.
if index < 0 || uint(index) >= res.BitLen() {
err := fmt.Errorf("SetBitAtIndex: bit index %d out of valid range (0..%d)", index, int(res.BitLen())-1)
return BitArray{}, pgerror.WithCandidateCode(err, pgcode.ArraySubscript)
}
// To update bit at the given index, we have to determine the
// position within words array, i.e. index/numBitsPerWord after
// that updated the bit at residual index.
// Forcefully making bit at the index to 0.
res.words[index/numBitsPerWord] &= ^(word(1) << (numBitsPerWord - 1 - uint(index)%numBitsPerWord))
// Updating value at the index to toSet.
res.words[index/numBitsPerWord] |= word(toSet) << (numBitsPerWord - 1 - uint(index)%numBitsPerWord)
return res, nil
}