File: Collections\BitVector.cs
Web Access
Project: src\src\Compilers\Core\Portable\Microsoft.CodeAnalysis.csproj (Microsoft.CodeAnalysis)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Roslyn.Utilities;
using Word = System.UInt64;
 
namespace Microsoft.CodeAnalysis
{
    [DebuggerDisplay("{GetDebuggerDisplay(), nq}")]
    internal struct BitVector : IEquatable<BitVector>
    {
        private const Word ZeroWord = 0;
        private const int Log2BitsPerWord = 6;
 
        public const int BitsPerWord = 1 << Log2BitsPerWord;
 
        // Cannot expose the following two field publicly because this structure is mutable
        // and might become not null/empty, unless we restrict access to it.
        private static Word[] s_emptyArray => Array.Empty<Word>();
        private static readonly BitVector s_nullValue = default;
        private static readonly BitVector s_emptyValue = new(0, s_emptyArray, 0);
 
        private Word _bits0;
        private Word[] _bits;
        private int _capacity;
 
        private BitVector(Word bits0, Word[] bits, int capacity)
        {
            int requiredWords = WordsForCapacity(capacity);
            Debug.Assert(requiredWords == 0 || requiredWords <= bits.Length);
            _bits0 = bits0;
            _bits = bits;
            _capacity = capacity;
            Check();
        }
 
        public bool Equals(BitVector other)
        {
            // Bit arrays only equal if their underlying sets are of the same size
            return _capacity == other._capacity
                // and have the same set of bits set
                && _bits0 == other._bits0
                && _bits.AsSpan().SequenceEqual(other._bits.AsSpan());
        }
 
        public override bool Equals(object? obj)
        {
            return obj is BitVector other && Equals(other);
        }
 
        public static bool operator ==(BitVector left, BitVector right)
        {
            return left.Equals(right);
        }
 
        public static bool operator !=(BitVector left, BitVector right)
        {
            return !left.Equals(right);
        }
 
        public override int GetHashCode()
        {
            int bitsHash = _bits0.GetHashCode();
 
            if (_bits != null)
            {
                for (int i = 0; i < _bits.Length; i++)
                {
                    bitsHash = Hash.Combine(_bits[i].GetHashCode(), bitsHash);
                }
            }
 
            return Hash.Combine(_capacity, bitsHash);
        }
 
        private static int WordsForCapacity(int capacity)
        {
            if (capacity <= 0) return 0;
            int lastIndex = (capacity - 1) >> Log2BitsPerWord;
            return lastIndex;
        }
 
        public int Capacity => _capacity;
 
        [Conditional("DEBUG_BITARRAY")]
        private void Check()
        {
            Debug.Assert(_capacity == 0 || WordsForCapacity(_capacity) <= _bits.Length);
        }
 
        public void EnsureCapacity(int newCapacity)
        {
            if (newCapacity > _capacity)
            {
                int requiredWords = WordsForCapacity(newCapacity);
                if (requiredWords > _bits.Length) Array.Resize(ref _bits, requiredWords);
                _capacity = newCapacity;
                Check();
            }
            Check();
        }
 
        public IEnumerable<Word> Words()
        {
            if (_capacity > 0)
            {
                yield return _bits0;
            }
 
            for (int i = 0, n = _bits?.Length ?? 0; i < n; i++)
            {
                yield return _bits![i];
            }
        }
 
        public IEnumerable<int> TrueBits()
        {
            Check();
            if (_bits0 != 0)
            {
                for (int bit = 0; bit < BitsPerWord; bit++)
                {
                    Word mask = ((Word)1) << bit;
                    if ((_bits0 & mask) != 0)
                    {
                        if (bit >= _capacity) yield break;
                        yield return bit;
                    }
                }
            }
            for (int i = 0; i < _bits.Length; i++)
            {
                Word w = _bits[i];
                if (w != 0)
                {
                    for (int b = 0; b < BitsPerWord; b++)
                    {
                        Word mask = ((Word)1) << b;
                        if ((w & mask) != 0)
                        {
                            int bit = ((i + 1) << Log2BitsPerWord) | b;
                            if (bit >= _capacity) yield break;
                            yield return bit;
                        }
                    }
                }
            }
        }
 
        public static BitVector FromWords(Word bits0, Word[] bits, int capacity)
        {
            return new BitVector(bits0, bits, capacity);
        }
 
        /// <summary>
        /// Create BitArray with at least the specified number of bits.
        /// </summary>
        public static BitVector Create(int capacity)
        {
            int requiredWords = WordsForCapacity(capacity);
            Word[] bits = (requiredWords == 0) ? s_emptyArray : new Word[requiredWords];
            return new BitVector(0, bits, capacity);
        }
 
        /// <summary>
        /// return a bit array with all bits set from index 0 through bitCount-1
        /// </summary>
        /// <param name="capacity"></param>
        /// <returns></returns>
        public static BitVector AllSet(int capacity)
        {
            if (capacity == 0)
            {
                return Empty;
            }
 
            int requiredWords = WordsForCapacity(capacity);
            Word[] bits = (requiredWords == 0) ? s_emptyArray : new Word[requiredWords];
            int lastWord = requiredWords - 1;
            Word bits0 = ~ZeroWord;
            for (int j = 0; j < lastWord; j++)
                bits[j] = ~ZeroWord;
            int numTrailingBits = capacity & ((BitsPerWord) - 1);
            if (numTrailingBits > 0)
            {
                Debug.Assert(numTrailingBits <= BitsPerWord);
                Word lastBits = ~((~ZeroWord) << numTrailingBits);
                if (lastWord < 0)
                {
                    bits0 = lastBits;
                }
                else
                {
                    bits[lastWord] = lastBits;
                }
            }
            else if (requiredWords > 0)
            {
                bits[lastWord] = ~ZeroWord;
            }
 
            return new BitVector(bits0, bits, capacity);
        }
 
        /// <summary>
        /// Make a copy of a bit array.
        /// </summary>
        /// <returns></returns>
        public BitVector Clone()
        {
            Word[] newBits;
            if (_bits is null || _bits.Length == 0)
            {
                newBits = s_emptyArray;
            }
            else
            {
                newBits = (Word[])_bits.Clone();
            }
 
            return new BitVector(_bits0, newBits, _capacity);
        }
 
        /// <summary>
        /// Invert all the bits in the vector.
        /// </summary>
        public void Invert()
        {
            _bits0 = ~_bits0;
            if (!(_bits is null))
            {
                for (int i = 0; i < _bits.Length; i++)
                {
                    _bits[i] = ~_bits[i];
                }
            }
        }
 
        /// <summary>
        /// Is the given bit array null?
        /// </summary>
        public bool IsNull
        {
            get
            {
                return _bits == null;
            }
        }
 
        public static BitVector Null => s_nullValue;
 
        public static BitVector Empty => s_emptyValue;
 
        /// <summary>
        /// Modify this bit vector by bitwise AND-ing each element with the other bit vector.
        /// For the purposes of the intersection, any bits beyond the current length will be treated as zeroes.
        /// Return true if any changes were made to the bits of this bit vector.
        /// </summary>
        public bool IntersectWith(in BitVector other)
        {
            bool anyChanged = false;
            int otherLength = other._bits.Length;
            var thisBits = _bits;
            int thisLength = thisBits.Length;
 
            if (otherLength > thisLength)
                otherLength = thisLength;
 
            // intersect the inline portion
            {
                var oldV = _bits0;
                var newV = oldV & other._bits0;
                if (newV != oldV)
                {
                    _bits0 = newV;
                    anyChanged = true;
                }
            }
            // intersect up to their common length.
            for (int i = 0; i < otherLength; i++)
            {
                var oldV = thisBits[i];
                var newV = oldV & other._bits[i];
                if (newV != oldV)
                {
                    thisBits[i] = newV;
                    anyChanged = true;
                }
            }
 
            // treat the other bit array as being extended with zeroes
            for (int i = otherLength; i < thisLength; i++)
            {
                if (thisBits[i] != 0)
                {
                    thisBits[i] = 0;
                    anyChanged = true;
                }
            }
 
            Check();
            return anyChanged;
        }
 
        /// <summary>
        /// Modify this bit vector by '|'ing each element with the other bit vector.
        /// </summary>
        /// <returns>
        /// True if any bits were set as a result of the union.
        /// </returns>
        public bool UnionWith(in BitVector other)
        {
            bool anyChanged = false;
 
            if (other._capacity > _capacity)
                EnsureCapacity(other._capacity);
 
            Word oldbits = _bits0;
            _bits0 |= other._bits0;
 
            if (oldbits != _bits0)
                anyChanged = true;
 
            for (int i = 0; i < other._bits.Length; i++)
            {
                oldbits = _bits[i];
                _bits[i] |= other._bits[i];
 
                if (_bits[i] != oldbits)
                    anyChanged = true;
            }
 
            Check();
 
            return anyChanged;
        }
 
        public bool this[int index]
        {
            get
            {
                if (index < 0)
                    throw new IndexOutOfRangeException();
                if (index >= _capacity)
                    return false;
                int i = (index >> Log2BitsPerWord) - 1;
                var word = (i < 0) ? _bits0 : _bits[i];
 
                return IsTrue(word, index);
            }
 
            set
            {
                if (index < 0)
                    throw new IndexOutOfRangeException();
                if (index >= _capacity)
                    EnsureCapacity(index + 1);
                int i = (index >> Log2BitsPerWord) - 1;
                int b = index & (BitsPerWord - 1);
                Word mask = ((Word)1) << b;
                if (i < 0)
                {
                    if (value)
                        _bits0 |= mask;
                    else
                        _bits0 &= ~mask;
                }
                else
                {
                    if (value)
                        _bits[i] |= mask;
                    else
                        _bits[i] &= ~mask;
                }
            }
        }
 
        public void Clear()
        {
            _bits0 = 0;
            if (_bits != null) Array.Clear(_bits, 0, _bits.Length);
        }
 
        public static bool IsTrue(Word word, int index)
        {
            int b = index & (BitsPerWord - 1);
            Word mask = ((Word)1) << b;
            return (word & mask) != 0;
        }
 
        public static int WordsRequired(int capacity)
        {
            if (capacity <= 0) return 0;
            return WordsForCapacity(capacity) + 1;
        }
 
        internal string GetDebuggerDisplay()
        {
            var value = new char[_capacity];
            for (int i = 0; i < _capacity; i++)
            {
                value[_capacity - i - 1] = this[i] ? '1' : '0';
            }
            return new string(value);
        }
    }
}