├── .gitignore ├── bitarray-impl.hh ├── bitarray-test.cc ├── bitarray.hh ├── meson.build ├── pdep-pext.hh ├── readme.md ├── simd.md ├── test.sh └── todo.txt /.gitignore: -------------------------------------------------------------------------------- 1 | out/ 2 | out-gcc/ 3 | out-clang/ 4 | subprojects/ 5 | -------------------------------------------------------------------------------- /bitarray-impl.hh: -------------------------------------------------------------------------------- 1 | template 2 | concept VariableSize = requires(T t) { 3 | t._size; 4 | }; 5 | 6 | template 7 | struct bitarray_impl 8 | { 9 | using Container = typename Traits::Container; 10 | using WordType = Container::value_type; 11 | using self_type = bitarray_impl; 12 | static constexpr auto variable_sized = VariableSize; 13 | Container data_; 14 | 15 | bitarray_impl() = default; 16 | bitarray_impl(Container c) : data_(c) {} 17 | template 18 | bitarray_impl(Other l) 19 | { 20 | std::copy(l.begin(), l.begin() + std::min(l.size(), data_.size()), data_.begin()); 21 | sanitize(); 22 | } 23 | 24 | static constexpr auto WordBits = std::numeric_limits::digits; 25 | 26 | template 27 | friend std::basic_ostream &operator<<(std::basic_ostream &os, const self_type &x) 28 | { 29 | int base_digits = 0; 30 | if (os.flags() & std::ios_base::dec) { 31 | base_digits = 1; //XXX this is a hack, rather than default to decimal, we default to binary 32 | os << "0b"; 33 | } else if (os.flags() & std::ios_base::oct) { 34 | base_digits = 3; 35 | os << "0o"; 36 | } else if (os.flags() & std::ios_base::hex) { 37 | base_digits = 4; 38 | os << "0x"; 39 | } 40 | for (ssize_t i = x.size(); i -= base_digits;) { 41 | unsigned char digit = 0; 42 | for (int j = 0; j < base_digits; j++) { 43 | digit <<= 1; 44 | digit += x[i + j]; 45 | } 46 | os << std::to_string(digit); 47 | } 48 | return os; 49 | } 50 | 51 | size_t size() const 52 | { 53 | if constexpr (!variable_sized) 54 | { 55 | return std::size(data_) * WordBits; 56 | } 57 | else 58 | { 59 | if (Specific::_size == std::dynamic_extent) 60 | { 61 | return std::size(data_) * WordBits; 62 | } 63 | else 64 | { 65 | return Specific::_size; 66 | } 67 | } 68 | } 69 | 70 | Container &data() 71 | { 72 | return data_; 73 | } 74 | 75 | private: 76 | constexpr WordType zero() const { 77 | return static_cast(0); 78 | } 79 | constexpr WordType one() const { 80 | return static_cast(1); 81 | } 82 | constexpr WordType ones() const { 83 | return ~static_cast(0); 84 | } 85 | 86 | protected: 87 | void sanitize() { 88 | #pragma GCC diagnostic push 89 | #pragma GCC diagnostic ignored "-Wshift-count-overflow" 90 | if (size() % WordBits != 0) { 91 | data_.back() &= ones() >> (WordBits - size() % WordBits); 92 | } 93 | #pragma GCC diagnostic pop 94 | } 95 | 96 | public: 97 | bool all() const { 98 | if (size() % WordBits == 0) { 99 | for (size_t i = 0; i < std::size(data_); i++) 100 | if (data_[i] != ones()) 101 | return false; 102 | } else { 103 | if (data_.back() != ones() >> (WordBits - size() % WordBits)) 104 | return false; 105 | for (size_t i = 0; i + 1 < std::size(data_); i++) 106 | if (data_[i] != ones()) 107 | return false; 108 | } 109 | return true; 110 | } 111 | bool any() const { 112 | for (auto &x : data_) 113 | if (x != 0) 114 | return true; 115 | return false; 116 | } 117 | bool none() const { 118 | for (auto &x : data_) 119 | if (x != 0) 120 | return false; 121 | return true; 122 | } 123 | int popcount() const { 124 | return count(); 125 | } 126 | size_t count() const { 127 | size_t count = 0; 128 | for (auto &x : data_) 129 | if constexpr (sizeof(WordType) <= 8) { 130 | count += std::popcount(x); 131 | } else if (sizeof(WordType) <= 16) { 132 | count += std::popcount(static_cast(x >> 64)) + std::popcount(static_cast(x)); 133 | } 134 | return count; 135 | } 136 | bool has_single_bit() const { 137 | return count() == 1; 138 | } 139 | int countr_zero() const { 140 | for (size_t i = 0; i < std::size(data_); i++) 141 | if (data_[i] != 0) 142 | return i * WordBits + std::countr_zero(data_[i]); 143 | return size(); 144 | } 145 | int countr_one() const { 146 | for (size_t i = 0; i < std::size(data_); i++) 147 | if (data_[i] != ones()) 148 | return i * WordBits + std::countr_one(data_[i]); 149 | return size(); 150 | } 151 | int countl_zero() const { 152 | for (size_t i = std::size(data_); i--;) 153 | if (data_[i] != 0) 154 | return size() - i * WordBits - (WordBits - std::countl_zero(data_[i])); 155 | return size(); 156 | } 157 | int countl_one() const { 158 | size_t i = std::size(data_); 159 | if (size() % WordBits != 0) { 160 | if (data_.back() != (ones() >> (WordBits - size() % WordBits))) 161 | return std::countl_one(data_.back() << (WordBits - size() % WordBits)); 162 | i--; 163 | } 164 | for (; i--;) 165 | if (data_[i] != ones()) 166 | return size() - i * WordBits - (WordBits - std::countl_one(data_[i])); 167 | return size(); 168 | } 169 | int bit_width() const { 170 | return size() - countl_zero(); 171 | } 172 | self_type bit_floor() const { 173 | int w = bit_width(); 174 | reset(); 175 | if (w != 0) { 176 | set(w - 1); 177 | } 178 | return *this; 179 | } 180 | self_type bit_ceil() const { 181 | int w = bit_width(); 182 | bool x = has_single_bit(); 183 | reset(); 184 | if (x) { 185 | set(w - 1); 186 | } else { 187 | set(w); 188 | } 189 | return *this; 190 | } 191 | 192 | 193 | 194 | 195 | 196 | void wordswap() { 197 | std::reverse(std::begin(data_), std::end(data_)); 198 | } 199 | void byteswap() { 200 | wordswap(); 201 | for (auto &x : data_) 202 | { 203 | //std::byteswap(x); 204 | } 205 | } 206 | void bitswap() { 207 | byteswap(); 208 | //TODO bitswap 209 | } 210 | self_type set() { 211 | for (auto &x : data_) 212 | x = ones(); 213 | sanitize(); 214 | return *this; 215 | } 216 | constexpr self_type set(size_t pos, bool value = true) { 217 | if (pos >= size()) { 218 | throw std::out_of_range{"set() called with pos " + std::to_string(pos) + " on bitset of size " + std::to_string(size())}; 219 | } 220 | if (value) { 221 | data_[pos / WordBits] |= one() << (pos % WordBits); 222 | } else { 223 | data_[pos / WordBits] &= ~(one() << (pos % WordBits)); 224 | } 225 | return *this; 226 | } 227 | self_type reset() { 228 | for (auto &x : data_) 229 | x = zero(); 230 | return *this; 231 | } 232 | self_type reset(size_t pos) { 233 | if (pos >= size()) { 234 | throw std::out_of_range{"reset() called with pos " + std::to_string(pos) + " on bitset of size " + std::to_string(size())}; 235 | } 236 | data_[pos / WordBits] &= ~(one() << (pos % WordBits)); 237 | return *this; 238 | } 239 | self_type flip() { 240 | for (auto &x : data_) 241 | x = ~x; 242 | sanitize(); 243 | return *this; 244 | } 245 | self_type flip(size_t pos) { 246 | if (pos >= size()) { 247 | throw std::out_of_range{"flip() called with pos " + std::to_string(pos) + " on bitset of size " + std::to_string(size())}; 248 | } 249 | data_[pos / WordBits] ^= one() << (pos % WordBits); 250 | return *this; 251 | } 252 | void set_word_at_pos(WordType x, size_t pos) { 253 | if (pos >= size()) { 254 | return; 255 | } 256 | size_t offset = pos % WordBits; 257 | data_[pos / WordBits] |= x << offset; 258 | if (offset != 0 && pos / WordBits + 1 < std::size(data_)) 259 | { 260 | data_[pos / WordBits + 1] |= x >> (WordBits - offset); 261 | } 262 | } 263 | WordType get_word_at_pos(size_t pos) const { 264 | if (pos >= size()) { 265 | throw std::out_of_range{"get_word_at_pos() called with pos " + std::to_string(pos) + " on bitset of size " + std::to_string(size())}; 266 | } 267 | size_t offset = pos % WordBits; 268 | WordType out = data_[pos / WordBits] >> offset; 269 | if (offset != 0 && pos / WordBits + 1 < std::size(data_)) 270 | { 271 | out |= data_[pos / WordBits + 1] << (WordBits - offset); 272 | } 273 | return out; 274 | } 275 | bool operator==(const self_type& rhs) const { 276 | for (size_t i = 0; i < std::size(data_); i++) 277 | if (data_[i] != rhs.data_[i]) 278 | return false; 279 | return true; 280 | } 281 | bool operator!=(const self_type& rhs) const { 282 | return !(*this == rhs); 283 | } 284 | constexpr bool at(size_t pos) const { 285 | if (pos >= size()) { 286 | throw std::out_of_range{"at() called with pos " + std::to_string(pos) + " on bitset of size " + std::to_string(size())}; 287 | } 288 | return *this[pos]; 289 | } 290 | constexpr bool operator[](size_t pos) const { 291 | return static_cast((data_[pos / WordBits] >> (pos % WordBits)) & 1); 292 | } 293 | 294 | self_type operator&=(const self_type& rhs) { 295 | for (size_t i = 0; i < std::size(data_); i++) 296 | data_[i] &= rhs.data_[i]; 297 | return *this; 298 | } 299 | self_type operator|=(const self_type& rhs) { 300 | for (size_t i = 0; i < std::size(data_); i++) 301 | data_[i] |= rhs.data_[i]; 302 | return *this; 303 | } 304 | self_type operator^=(const self_type& rhs) { 305 | for (size_t i = 0; i < std::size(data_); i++) 306 | data_[i] ^= rhs.data_[i]; 307 | sanitize(); 308 | return *this; 309 | } 310 | self_type operator<<=(size_t shift) { 311 | for (size_t i = std::size(data_); i--;) 312 | { 313 | auto x = data_[i]; 314 | data_[i] = 0; 315 | set_word_at_pos(x, i * WordBits + shift); 316 | } 317 | return *this; 318 | } 319 | self_type operator<<(size_t shift) { 320 | self_type x = *this; 321 | x <<= shift; 322 | return x; 323 | } 324 | self_type operator>>=(size_t shift) { 325 | for (size_t i = 0; i < std::size(data_); i++) 326 | { 327 | if (shift + i * WordBits < size()) { 328 | data_[i] = get_word_at_pos(shift + i * WordBits); 329 | } else { 330 | data_[i] = 0; 331 | } 332 | } 333 | return *this; 334 | } 335 | self_type operator>>(size_t shift) { 336 | self_type x = *this; 337 | x >>= shift; 338 | return x; 339 | } 340 | self_type rotl(int shift) { 341 | if (shift < 0) { 342 | return rotr(-shift); 343 | } 344 | shift %= size(); 345 | //FIXME implement without temporaries 346 | //return (*this << shift) | (*this >> (size() - shift)); 347 | return *this; 348 | } 349 | self_type rotr(int shift) { 350 | if (shift < 0) { 351 | return rotl(-shift); 352 | } 353 | shift %= size(); 354 | //FIXME implement without temporaries 355 | //return (*this >> shift) | (*this << (size() - shift)); 356 | return *this; 357 | } 358 | 359 | /* 360 | FIXME 361 | template 362 | bitarray gather(bitarray mask) { 363 | static_assert(M <= size(), "gather operation mask length must be <= input length"); 364 | bitarray output {}; 365 | for (size_t i = 0, pos = 0; i < mask.std::size(data_); i++) { 366 | output.set_word_at_pos(pext( 367 | data_[i], 368 | mask.data_[i] 369 | ), pos); 370 | pos += std::popcount(mask.data_[i]); 371 | } 372 | return output; 373 | } 374 | template 375 | bitarray scatter(bitarray mask) { 376 | static_assert(M >= size(), "scatter operation mask length must be >= input length"); 377 | bitarray output {}; 378 | for (size_t i = 0, pos = 0; i < output.std::size(data_); i++) { 379 | output.data_[i] = pdep( 380 | get_word_at_pos(pos), 381 | mask.data_[i] 382 | ); 383 | pos += std::popcount(mask.data_[i]); 384 | } 385 | return output; 386 | } 387 | 388 | 389 | template 390 | static constexpr std::array, Num> interleave_masks() { 391 | std::array, Num> x{}; 392 | for (size_t i = 0; i < Num; i++) { 393 | for (size_t j = i; j < Len; j += Num) { 394 | x[i].set(j); 395 | } 396 | } 397 | return x; 398 | } 399 | 400 | template 401 | static bitarray interleave(std::array, Num> input) { 402 | bitarray output {}; 403 | std::array, Num> masks = interleave_masks(); 404 | for (size_t j = 0; j < input.size(); j++) { 405 | output |= input[j].template scatter(masks[j]); 406 | } 407 | return output; 408 | } 409 | template 410 | static std::array, Num> deinterleave(bitarray input) { 411 | std::array, Num> output {}; 412 | std::array, Num> masks = interleave_masks(); 413 | for (size_t j = 0; j < output.size(); j++) { 414 | output[j] = input.template gather(masks[j]); 415 | } 416 | return output; 417 | } 418 | */ 419 | }; 420 | -------------------------------------------------------------------------------- /bitarray-test.cc: -------------------------------------------------------------------------------- 1 | #include "bitarray.hh" 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #ifdef TYPE 11 | using type = TYPE; 12 | #else 13 | using type = uint64_t; 14 | #endif 15 | 16 | TEST(bitarray, bit_word_calculations) { 17 | ASSERT_EQ(bitarray::detail::words_needed(0), 0); 18 | ASSERT_EQ(bitarray::detail::words_needed(1), 1); 19 | ASSERT_EQ(bitarray::detail::words_needed(8), 1); 20 | ASSERT_EQ(bitarray::detail::words_needed(9), 2); 21 | ASSERT_EQ(bitarray::detail::words_needed(16), 2); 22 | ASSERT_EQ(bitarray::detail::words_needed(17), 3); 23 | } 24 | 25 | TEST(bitarray, ctors) { 26 | { 27 | bitarray::bitvector b(2); 28 | ASSERT_EQ(b.size(), 2); 29 | ASSERT_EQ(b.data().size(), 1); 30 | b.set(1, 1); 31 | } 32 | 33 | { 34 | bitarray::bitarray<100> b; 35 | b.set(1, 1); 36 | } 37 | 38 | { 39 | /* 40 | TODO 41 | std::vector vector(100); 42 | bitarray::bitspan b(std::span{vector}); 43 | b.set(1, 1); 44 | ASSERT_EQ(b.size(), 100); 45 | */ 46 | } 47 | 48 | { 49 | std::vector vector(100); 50 | bitarray::bitvector b(vector); 51 | b.set(1, 1); 52 | b.resize(3000); 53 | b.set(2000, 1); 54 | } 55 | 56 | { 57 | bitarray::bitvector b(2, {100, 100}); 58 | b.set(1, 1); 59 | b.resize(3000); 60 | b.set(2000, 1); 61 | } 62 | 63 | { 64 | bitarray::bitvector b(2, std::vector(100)); 65 | b.set(1, 1); 66 | b.resize(3000); 67 | b.set(2000, 1); 68 | } 69 | 70 | { 71 | bitarray::bitarray<128> b; 72 | ASSERT_EQ(b.size(), 128); 73 | } 74 | 75 | { 76 | bitarray::bitvector b{128}; 77 | ASSERT_EQ(b.size(), 128); 78 | } 79 | 80 | { 81 | bitarray::bitvector b(100); 82 | ASSERT_EQ(b.size(), 100); 83 | b.resize(200); 84 | ASSERT_EQ(b.size(), 200); 85 | } 86 | } 87 | 88 | TEST(bitarray, basic_functions){ 89 | bitarray::bitarray<129>{{~0LLU, ~0LLU, ~0LLU}}; 90 | ASSERT_TRUE((bitarray::bitarray<129>{{~0LLU, ~0LLU, ~0LLU}}).all()); 91 | ASSERT_TRUE((bitarray::bitarray<129>{{0LLU, 0LLU, 0LLU}}).none()); 92 | ASSERT_FALSE((bitarray::bitarray<129>{{0LLU, 0LLU, 0LLU}}).any()); 93 | ASSERT_TRUE((bitarray::bitarray<129>{{1LLU, 0LLU, 0LLU}}).any()); 94 | ASSERT_EQ((bitarray::bitarray<129>{{3LLU, 2LLU, 1LLU}}).count(), 4); 95 | } 96 | 97 | TEST(bitarray, fuzz_count){ 98 | { 99 | constexpr size_t len = 128 + 7; 100 | for(size_t i = 0; i < len; i++) { 101 | bitarray::bitarray x {}; 102 | size_t pos = i; 103 | x.set(pos); 104 | ASSERT_EQ(x.countl_zero(), len - 1 - pos); 105 | ASSERT_EQ(x.countr_zero(), pos); 106 | x.flip(); 107 | ASSERT_EQ(x.countl_one(), len - 1 - pos); 108 | ASSERT_EQ(x.countr_one(), pos); 109 | } 110 | } 111 | { 112 | constexpr size_t len = 128; 113 | for(size_t i = 0; i < len; i++) { 114 | bitarray::bitarray x {}; 115 | size_t pos = i; 116 | x.set(pos); 117 | ASSERT_EQ(x.countl_zero(), len - 1 - pos); 118 | ASSERT_EQ(x.countr_zero(), pos); 119 | x.flip(); 120 | ASSERT_EQ(x.countl_one(), len - 1 - pos); 121 | ASSERT_EQ(x.countr_one(), pos); 122 | } 123 | } 124 | } 125 | 126 | TEST(bitarray, fuzz_shift){ 127 | constexpr size_t len = 128; 128 | for(size_t i = 0; i < len; i++) { 129 | auto input = bitarray::bitarray{}; 130 | for (size_t j = 1; j < len; j *= 2) { 131 | input.set(j); 132 | } 133 | auto shift = i; 134 | auto output = input >> shift; 135 | decltype(output) expected {}; 136 | for (size_t x = shift, y = 0; x < input.size() && y < output.size(); x++, y++) { 137 | expected.set(y, input[x]); 138 | } 139 | ASSERT_EQ(output, expected); 140 | output = input << shift; 141 | expected = {}; 142 | for (size_t x = 0, y = shift; x < input.size() && y < output.size(); x++, y++) { 143 | expected.set(y, input[x]); 144 | } 145 | ASSERT_EQ(output, expected); 146 | } 147 | } 148 | 149 | #if 0 150 | TEST(bitarray, fuzz_rotate){ 151 | constexpr int len = 128; 152 | for(int i = -len; i < len; i++) { 153 | auto input = bitarray::bitarray{}; 154 | for (size_t j = 1; j < len; j *= 2) { 155 | input.set(j); 156 | } 157 | auto shift = i; 158 | auto output = input.rotl(shift); 159 | decltype(output) expected {}; 160 | for (size_t x = 0; x < input.size(); x++) { 161 | expected.set((x + shift) % input.size(), input[x]); 162 | } 163 | ASSERT_EQ(output, expected); 164 | } 165 | } 166 | 167 | TEST(bitarray, fuzz_ctors){ 168 | constexpr size_t l = 120; 169 | bitarray::bitarray a {~0LLU, ~0LLU}; 170 | bitarray::bitarray b {}; 171 | b.set(); 172 | bitarray::bitarray c {}; 173 | for (size_t i = 0; i < c.size(); i++) { 174 | c.set(i, 1); 175 | } 176 | ASSERT_EQ(a, b); 177 | ASSERT_EQ(b, c); 178 | } 179 | 180 | TEST(bitarray, fuzz_gather){ 181 | constexpr size_t len = 128; 182 | for(size_t i = 0; i < len; i++) { 183 | auto input = bitarray::bitarray{}; 184 | input.set(i); 185 | auto mask = bitarray::bitarray{}; 186 | for (size_t j = 1; j < len; j *= 2) { 187 | mask.set(j); 188 | } 189 | auto output = input.template gather(mask); 190 | decltype(output) expected {}; 191 | for (size_t x = 0, y = 0; x < mask.size() && x < input.size() && y < output.size(); x++) { 192 | if (mask[x]) { 193 | expected.set(y, input[x]); 194 | y++; 195 | } 196 | } 197 | ASSERT_EQ(output, expected); 198 | } 199 | } 200 | 201 | TEST(bitarray, fuzz_scatter){ 202 | constexpr size_t len = 128; 203 | for(size_t i = 0; i < len; i++) { 204 | auto input = bitarray::bitarray{}; 205 | input.set(i); 206 | auto mask = bitarray::bitarray{}; 207 | for (size_t j = 1; j < len; j *= 2) { 208 | mask.set(j); 209 | } 210 | auto output = input.template scatter(mask); 211 | decltype(output) expected {}; 212 | for (size_t x = 0, y = 0; x < mask.size() && x < output.size() && y < input.size(); x++) { 213 | if (mask[x]) { 214 | expected.set(x, input[y]); 215 | y++; 216 | } 217 | } 218 | ASSERT_EQ(output, expected); 219 | } 220 | } 221 | 222 | TEST(bitarray, fuzz_interleave_deinterleave){ 223 | constexpr size_t len = 128; 224 | auto inputs = std::array, 3>{ 225 | bitarray::bitarray{~0ULL, ~0ULL}, 226 | bitarray::bitarray{0ULL, 0ULL}, 227 | bitarray::bitarray{}, 228 | }; 229 | for (size_t j = 1; j < len; j *= 2) { 230 | inputs[2].set(j); 231 | } 232 | auto output = bitarray::bitarray::interleave(inputs); 233 | decltype(output) expected {}; 234 | for (size_t x = 0; x < output.size(); x++) { 235 | expected.set(x, inputs[x % 3][x / 3]); 236 | } 237 | ASSERT_EQ(output, expected); 238 | auto outputs = bitarray::bitarray::deinterleave(output); 239 | ASSERT_EQ(outputs, inputs); 240 | } 241 | #endif 242 | -------------------------------------------------------------------------------- /bitarray.hh: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "pdep-pext.hh" 13 | #include "bitarray-impl.hh" 14 | 15 | namespace bitarray::detail 16 | { 17 | template 18 | constexpr size_t words_needed(size_t bits) { 19 | if (bits == std::dynamic_extent) 20 | { 21 | return std::dynamic_extent; 22 | } 23 | else 24 | { 25 | size_t WordBits = std::numeric_limits::digits; 26 | return (bits + WordBits - 1) / WordBits; 27 | } 28 | } 29 | template 30 | constexpr size_t bits_in_container(Container data) { 31 | return data.size() * std::numeric_limits::digits; 32 | } 33 | } 34 | 35 | namespace bitarray { 36 | template 37 | struct bitarray_traits 38 | { 39 | using Container = std::array(Bits)>; 40 | }; 41 | 42 | template 43 | struct bitarray : bitarray_impl, bitarray_traits> 44 | { 45 | using self_type = bitarray; 46 | using base_type = bitarray_impl, bitarray_traits>; 47 | 48 | bitarray() 49 | { 50 | base_type::sanitize(); 51 | } 52 | bitarray(std::initializer_list l) : base_type(l) {} 53 | }; 54 | 55 | template 56 | struct bitvector_traits 57 | { 58 | using Container = std::vector; 59 | }; 60 | 61 | template 62 | struct bitvector : bitarray_impl, bitvector_traits> 63 | { 64 | using self_type = bitvector; 65 | using base_type = bitarray_impl, bitvector_traits>; 66 | 67 | size_t _size = std::dynamic_extent; 68 | 69 | bitvector() {} 70 | bitvector(std::vector v) : base_type(v) 71 | { 72 | _size = detail::bits_in_container(base_type::data_); 73 | } 74 | bitvector(size_t size) : _size(size) 75 | { 76 | resize(_size); 77 | } 78 | bitvector(size_t size, std::vector v) : base_type(v), _size(size) 79 | { 80 | base_type::sanitize(); 81 | } 82 | void resize(size_t s) 83 | { 84 | size_t needed = detail::words_needed(s); 85 | if (std::size(base_type::data_) < needed) 86 | { 87 | base_type::data_.resize(needed); 88 | } 89 | _size = s; 90 | } 91 | }; 92 | 93 | template 94 | struct bitspan_traits 95 | { 96 | using Container = std::span(Bits)>; 97 | }; 98 | 99 | template 100 | struct bitspan : bitarray_impl, bitspan_traits> 101 | { 102 | using self_type = bitspan; 103 | using base_type = bitarray_impl, bitspan_traits>; 104 | 105 | size_t _size; 106 | 107 | bitspan(std::span s) : base_type(s), _size(Bits) {} 108 | bitspan(size_t size, std::span s) : base_type(s), _size(size) 109 | { 110 | base_type::sanitize(); 111 | } 112 | void resize(size_t s) 113 | { 114 | size_t needed = detail::words_needed(s); 115 | if (std::size(base_type::data_) < needed) 116 | { 117 | base_type::data_.resize(needed); 118 | } 119 | _size = s; 120 | } 121 | 122 | self_type operator<<(size_t) = delete; 123 | self_type operator>>(size_t) = delete; 124 | }; 125 | } 126 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('bitarray', 'cpp', 2 | default_options: [ 3 | 'buildtype=debugoptimized', 4 | 'warning_level=3', 5 | 'cpp_std=c++20', 6 | 'b_sanitize=address,undefined', 7 | 'b_lundef=false', 8 | ] 9 | ) 10 | 11 | add_project_arguments( 12 | '-mbmi2', 13 | language: 'cpp', 14 | ) 15 | 16 | gtest = dependency('gtest_main') 17 | 18 | test_args = [ 19 | {'name': 'u128', 'args': ['-DTYPE=__uint128_t']}, 20 | {'name': 'u64', 'args': ['-DTYPE=uint64_t']}, 21 | {'name': 'u32', 'args': ['-DTYPE=uint32_t']}, 22 | {'name': 'u16', 'args': ['-DTYPE=uint16_t']}, 23 | {'name': 'u8', 'args': ['-DTYPE=uint8_t']}, 24 | ] 25 | foreach test_arg : test_args 26 | test('bitarray-test-' + test_arg['name'], 27 | executable('bitarray-test-' + test_arg['name'], 'bitarray-test.cc', dependencies: [gtest], cpp_args: [test_arg['args']]) 28 | ) 29 | endforeach 30 | -------------------------------------------------------------------------------- /pdep-pext.hh: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | namespace { 6 | template 7 | T pext(T data, T mask) { 8 | if constexpr (sizeof(T) <= 4) { 9 | return _pext_u32(data, mask); 10 | } else if (sizeof(T) <= 8) { 11 | return _pext_u64(data, mask); 12 | } else if (sizeof(T) <= 16) { 13 | #pragma GCC diagnostic push 14 | #pragma GCC diagnostic ignored "-Wshift-count-overflow" 15 | T out0 = _pext_u64(data, mask); 16 | T out1 = _pext_u64(data >> 64, mask >> 64); 17 | return out0 | (out1 << std::popcount(static_cast(mask))); 18 | #pragma GCC diagnostic pop 19 | } 20 | } 21 | 22 | template 23 | T pdep(T data, T mask) { 24 | if constexpr (sizeof(T) <= 4) { 25 | return _pdep_u32(data, mask); 26 | } else if (sizeof(T) <= 8) { 27 | return _pdep_u64(data, mask); 28 | } else if (sizeof(T) <= 16) { 29 | #pragma GCC diagnostic push 30 | #pragma GCC diagnostic ignored "-Wshift-count-overflow" 31 | T out0 = _pdep_u64(data, mask); 32 | T out1 = _pdep_u64(data >> std::popcount(static_cast(mask)), mask >> 64); 33 | return out0 | (out1 << 64); 34 | #pragma GCC diagnostic pop 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # bitarray / bitvector / bitspan 2 | 3 | This repo provides an alternative to C++'s [`std::bitset`](https://en.cppreference.com/w/cpp/utility/bitset) with all the features of C++20's [`` header](https://en.cppreference.com/w/cpp/header/bit). 4 | 5 | As with `std::bitset`, `bitarray` supports arbitrary length arrays of bits. It also supports dynamic bit lengths `bitvector` (on `std::vector`). It supports mmap'd bitsets `bitspan` (via `std::span`). 6 | 7 | It aims to be simpler, faster, and more featureful. 8 | 9 | Faster, because: 10 | - most operations are in-place, to eliminate large copies 11 | - the word size can be larger to reduce the number of iterations. potentially doing half as many iterations, std::bitset uses long 12 | - or the word size can be smaller to reduce wasted space (relevant for many small bitsets) 13 | 14 | The added features are (or will be): 15 | - [ ] Easy casts between `bitarray`, `bitvector`, `bitspan` 16 | - [ ] Easy constructors from `std::array`, `std::vector`, `std::span`, `std::initializer_list` 17 | - [x] Directly access all the underlying words 18 | - bitset only allows you to get the lowest unsigned long long's worth of bits 19 | - or output to a string of '1's and '0's... 20 | - [x] Easily change the underlying word type for whatever performance/storage needs (8/16/32/64/128 bit types are all supported and tested) 21 | - [x] Supports all [C++20 ](https://en.cppreference.com/w/cpp/header/bit) bitwise operations 22 | - popcount, rotl, rotr, count\_{l,r}\_{zero,one} 23 | - [x] deposit/extract and interleave/deinterleave, supported by [pdep/pext](https://en.wikipedia.org/wiki/Bit\_Manipulation\_Instruction\_Sets#BMI2) 24 | 25 | ## Dependencies 26 | 27 | - C++20 28 | - Target CPU with support for the [BMI2 instruction set](https://en.wikipedia.org/wiki/Bit\_Manipulation\_Instruction\_Sets#BMI2) (for pdep/pext support, which gather/scatter and interleave/deinterleave rely on) 29 | 30 | For testing and installing: 31 | - [Meson](https://mesonbuild.com/) `sudo apt install meson` 32 | 33 | ## Usage 34 | 35 | Include `bitarray.hh` and use `bitarray::bitarray` instead of `std::bitset`, or `bitarray::bitvector(N)`, or `bitarray::bitspan(span)` 36 | 37 | ## Testing 38 | 39 | `test.sh` 40 | -------------------------------------------------------------------------------- /simd.md: -------------------------------------------------------------------------------- 1 | choosing between various simd libraries to add simd support 2 | 3 | axes: 4 | - does it have recent activity? 5 | - overloaded operators 6 | - bitwise operators 7 | - reference / non-simd implementations 8 | - static dispatch 9 | - dynamic dispatch 10 | - variable length data 11 | - instruction set coverage 12 | - architecture coverage 13 | 14 | | library | active | operators | bitwise | non-simd | static | dynamic | variable | isa | arch | 15 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | 16 | | [highway](https://github.com/google/highway) | :heavy_check_mark: | :grey_question: | :grey_question: | :grey_question: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | 17 | | [simde](https://github.com/simd-everywhere/simde) | :heavy_check_mark: | :x: | :x: | :heavy_check_mark: | :heavy_check_mark: | :x: | 18 | | [xsimd](https://github.com/xtensor-stack/xsimd) | :heavy_check_mark: | :heavy_check_mark: | :grey_question: | :grey_question: | :heavy_check_mark: | [:heavy_check_mark:](https://xsimd.readthedocs.io/en/latest/api/dispatching.html) | 19 | | [libsimdpp](https://github.com/p12tic/libsimdpp) | :x: | :heavy_check_mark: | :heavy_check_mark: | :grey_question: | :heavy_check_mark: | [:heavy_check_mark:](http://p12tic.github.io/libsimdpp/v2.2-dev/libsimdpp/w/arch/dispatch.html) | 20 | | [std-simd](https://github.com/VcDevel/std-simd) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | [:x:](https://en.cppreference.com/w/cpp/experimental/simd/deduce) | :x: | 21 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | if [ ! -d subprojects ]; then 3 | meson wrap install gtest 4 | fi 5 | if [ ! -d out-clang ]; then 6 | CXX=clang++ \ 7 | meson out-clang 8 | fi 9 | if [ ! -d out-gcc ]; then 10 | CXX=g++ \ 11 | meson out-gcc 12 | fi 13 | meson test -C out-gcc --print-errorlogs 14 | meson test -C out-clang --print-errorlogs 15 | -------------------------------------------------------------------------------- /todo.txt: -------------------------------------------------------------------------------- 1 | static inherit from base with most functionality, derived just has data storage rather than janky include impl, or CRTP? 2 | and/or range/container adapter 3 | simd 4 | inc/dec by find first set and find first zero 5 | benchmarks versus other libraries 6 | forget interleave/spread? but expose scatter/gather 7 | 8 | bits::bitarray = std::array 9 | copy, move ctors 10 | everything implemented 11 | bits::bitvector = std::vector 12 | performance warning copy operators 13 | bits::bitspan = std::span 14 | disable copy operators 15 | 16 | can they all be implemented on the same underlying type? 17 | 18 | they should interoperate 19 | bitarray op bitvector should work 20 | easiest if there's a single underlying type 21 | 22 | casts between sizes, storage types? 23 | 24 | what if two bitvectors have different dynamic sizes? 25 | zero-extend? one-extend? sign-extend? 26 | make it the users problem: 27 | error if the sizes differ 28 | virtual zero bits could work? 29 | assuming ones are important in most cases 30 | 31 | 32 | 33 | https://github.com/ClaasBontus/bitset2 34 | 35 | fix test suite 36 | reference type https://en.cppreference.com/w/cpp/utility/bitset/reference 37 | iterators for for loops? 38 | 39 | implement casts between bitarray, bitvector, bitspan 40 | std::bitset is not trivially copyable by the standard, but is in practice (big three std libs), use std::bit_cast to cast from/to it 41 | see https://quuxplusone.github.io/blog/2019/02/20/p1144-what-types-are-relocatable/ 42 | pretty much like std::array "This container is an aggregate type with the same semantics as a struct holding a C-style array T[N] as its only non-static data member." 43 | implement span_cast and array_cast for conversions 44 | --------------------------------------------------------------------------------