// 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 }