Add bit library

This commit is contained in:
2025-04-01 00:39:24 +02:00
parent 4f8cbd442a
commit 0776012d09
7 changed files with 454 additions and 36 deletions

View File

@ -11,6 +11,7 @@ cc_library(
hdrs = [
"annotations.hpp",
"assert.hpp",
"bit.hpp",
"config.hpp",
"defer.hpp",
"float.hpp",
@ -37,6 +38,7 @@ cc_library(
"//asl/types:box",
],
) for name in [
"bit",
"defer",
"float",
"functional",

174
asl/base/bit.hpp Normal file
View File

@ -0,0 +1,174 @@
// Copyright 2025 Steven Le Rouzic
//
// SPDX-License-Identifier: BSD-3-Clause
#pragma once
#include "asl/base/integers.hpp"
#include "asl/base/meta.hpp"
namespace asl
{
constexpr bool has_single_bit(is_unsigned_integer auto x)
{
return x != 0 && ((x - 1) & x) == 0;
}
// @Todo Move this to numeric library
template<is_integer T>
constexpr bool is_pow2(T x)
{
using unsigned_type = select_t<is_unsigned_integer<T>, T, as_unsigned_integer<T>>;
return x > 0 && has_single_bit(static_cast<unsigned_type>(x));
}
constexpr int popcount(uint8_t v)
{
v = v - ((v >> 1) & 0x55);
v = (v & 0x33) + ((v >> 2) & 0x33);
return (v + (v >> 4)) & 0x0F;
}
constexpr int popcount(uint16_t v)
{
v = v - ((v >> 1) & 0x5555);
v = (v & 0x3333) + ((v >> 2) & 0x3333);
return static_cast<uint16_t>((v + (v >> 4) & 0x0F0F) * uint16_t{0x0101}) >> 8;
}
constexpr int popcount(uint32_t v)
{
v = v - ((v >> 1) & 0x5555'5555);
v = (v & 0x3333'3333) + ((v >> 2) & 0x3333'3333);
return static_cast<int>(((v + (v >> 4) & 0x0F0F'0F0F) * 0x0101'0101) >> 24);
}
constexpr int popcount(uint64_t v)
{
v = v - ((v >> 1) & 0x5555'5555'5555'5555);
v = (v & 0x3333'3333'3333'3333) + ((v >> 2) & 0x3333'3333'3333'3333);
return static_cast<int>(((v + (v >> 4) & 0x0F0F'0F0F'0F0F'0F0F) * 0x0101'0101'0101'0101) >> 56);
}
constexpr uint8_t propagate_right_one(uint8_t v)
{
v = v | (v >> 1);
v = v | (v >> 2);
v = v | (v >> 4);
return v;
}
constexpr uint16_t propagate_right_one(uint16_t v)
{
v = v | (v >> 1);
v = v | (v >> 2);
v = v | (v >> 4);
v = v | (v >> 8);
return v;
}
constexpr uint32_t propagate_right_one(uint32_t v)
{
v = v | (v >> 1);
v = v | (v >> 2);
v = v | (v >> 4);
v = v | (v >> 8);
v = v | (v >> 16);
return v;
}
constexpr uint64_t propagate_right_one(uint64_t v)
{
v = v | (v >> 1);
v = v | (v >> 2);
v = v | (v >> 4);
v = v | (v >> 8);
v = v | (v >> 16);
v = v | (v >> 32);
return v;
}
constexpr int countr_zero(is_unsigned_integer auto v)
{
v = ~v & (v - 1);
return popcount(v);
}
constexpr int countr_one(is_unsigned_integer auto v)
{
v = v & (~v - 1);
return popcount(v);
}
constexpr int countl_zero(is_unsigned_integer auto v)
{
v = ~propagate_right_one(v);
return popcount(v);
}
constexpr int countl_one(is_unsigned_integer auto v)
{
v = ~v;
v = ~propagate_right_one(v);
return popcount(v);
}
template<is_unsigned_integer T>
constexpr T rotr(T v, int s);
template<is_unsigned_integer T>
constexpr T rotl(T v, int s) // NOLINT(*-no-recursion)
{
static constexpr int N = sizeof(decltype(v)) * 8;
s = s % N;
return (s >= 0) ? (v << s) | (v >> (N - s)) : rotr(v, -s);
}
template<is_unsigned_integer T>
constexpr T rotr(T v, int s) // NOLINT(*-no-recursion)
{
static constexpr int N = sizeof(decltype(v)) * 8;
s = s % N;
return (s >= 0) ? (v >> s) | (v << (N - s)) : rotl(v, -s);
}
constexpr uint16_t byteswap(uint16_t v)
{
return static_cast<uint16_t>((v << 8U) | (v >> 8U));
}
constexpr uint32_t byteswap(uint32_t v)
{
return rotr(v & 0x00ff'00ff, 8) | rotl(v & 0xff00'ff00, 8);
}
constexpr uint64_t byteswap(uint64_t v)
{
return (uint64_t{byteswap(static_cast<uint32_t>(v))} << 32)
| uint64_t{byteswap(static_cast<uint32_t>(v >> 32))};
}
constexpr auto bit_ceil(is_unsigned_integer auto v)
{
v -= 1;
v = propagate_right_one(v);
v += 1;
return v;
}
constexpr auto bit_floor(is_unsigned_integer auto v)
{
v = propagate_right_one(v);
v = v - (v >> 1);
return v;
}
constexpr int bit_width(is_unsigned_integer auto v)
{
static constexpr int N = sizeof(decltype(v)) * 8;
return N - countl_zero(v);
}
} // namespace asl

203
asl/base/bit_tests.cpp Normal file
View File

@ -0,0 +1,203 @@
// Copyright 2025 Steven Le Rouzic
//
// SPDX-License-Identifier: BSD-3-Clause
#include "asl/base/bit.hpp"
#include "asl/testing/testing.hpp"
ASL_TEST(has_single_bit)
{
ASL_TEST_EXPECT(asl::has_single_bit(4U));
ASL_TEST_EXPECT(asl::has_single_bit(1024U));
ASL_TEST_EXPECT(asl::has_single_bit(0x8000'0000U));
ASL_TEST_EXPECT(asl::has_single_bit(0x0000'8000'0000'0000ULL));
ASL_TEST_EXPECT(!asl::has_single_bit(0U));
ASL_TEST_EXPECT(!asl::has_single_bit(3U));
ASL_TEST_EXPECT(!asl::has_single_bit(341U));
}
ASL_TEST(is_pow2)
{
ASL_TEST_EXPECT(asl::is_pow2(4));
ASL_TEST_EXPECT(asl::is_pow2(65536));
ASL_TEST_EXPECT(!asl::is_pow2(6));
ASL_TEST_EXPECT(!asl::is_pow2(1978));
ASL_TEST_EXPECT(!asl::is_pow2(0));
}
ASL_TEST(popcount) // NOLINT(*-cognitive-complexity)
{
ASL_TEST_EXPECT(asl::popcount(uint8_t{0}) == 0);
ASL_TEST_EXPECT(asl::popcount(uint8_t{255}) == 8);
ASL_TEST_EXPECT(asl::popcount(uint8_t{46}) == 4);
ASL_TEST_EXPECT(asl::popcount(uint16_t{0}) == 0);
ASL_TEST_EXPECT(asl::popcount(uint16_t{255}) == 8);
ASL_TEST_EXPECT(asl::popcount(uint16_t{65535}) == 16);
ASL_TEST_EXPECT(asl::popcount(uint16_t{46}) == 4);
ASL_TEST_EXPECT(asl::popcount(uint32_t{0}) == 0);
ASL_TEST_EXPECT(asl::popcount(uint32_t{255}) == 8);
ASL_TEST_EXPECT(asl::popcount(uint32_t{65535}) == 16);
ASL_TEST_EXPECT(asl::popcount(uint32_t{65536}) == 1);
ASL_TEST_EXPECT(asl::popcount(uint32_t{46}) == 4);
ASL_TEST_EXPECT(asl::popcount(uint32_t{0xffff'ffff}) == 32);
ASL_TEST_EXPECT(asl::popcount(0x8421'1248'8421'1248) == 16);
ASL_TEST_EXPECT(asl::popcount(0xffff'ffff'ffff'ffff) == 64);
}
ASL_TEST(countr_zero)
{
ASL_TEST_EXPECT(asl::countr_zero(uint8_t{0}) == 8);
ASL_TEST_EXPECT(asl::countr_zero(uint8_t{255}) == 0);
ASL_TEST_EXPECT(asl::countr_zero(uint8_t{0b00011100}) == 2);
ASL_TEST_EXPECT(asl::countr_zero(uint8_t{0b10101010}) == 1);
ASL_TEST_EXPECT(asl::countr_zero(uint8_t{0b10101001}) == 0);
}
ASL_TEST(countr_one)
{
ASL_TEST_EXPECT(asl::countr_one(uint8_t{0}) == 0);
ASL_TEST_EXPECT(asl::countr_one(uint8_t{255}) == 8);
ASL_TEST_EXPECT(asl::countr_one(uint8_t{0b00011100}) == 0);
ASL_TEST_EXPECT(asl::countr_one(uint8_t{0b10101011}) == 2);
ASL_TEST_EXPECT(asl::countr_one(uint8_t{0b10101001}) == 1);
}
ASL_TEST(countl_zero)
{
ASL_TEST_EXPECT(asl::countl_zero(uint8_t{0}) == 8);
ASL_TEST_EXPECT(asl::countl_zero(uint8_t{255}) == 0);
ASL_TEST_EXPECT(asl::countl_zero(uint8_t{0b00011100}) == 3);
ASL_TEST_EXPECT(asl::countl_zero(uint8_t{0b10101010}) == 0);
ASL_TEST_EXPECT(asl::countl_zero(uint8_t{0b00001001}) == 4);
}
ASL_TEST(countl_one)
{
ASL_TEST_EXPECT(asl::countl_one(uint8_t{0}) == 0);
ASL_TEST_EXPECT(asl::countl_one(uint8_t{255}) == 8);
ASL_TEST_EXPECT(asl::countl_one(uint8_t{0b00011100}) == 0);
ASL_TEST_EXPECT(asl::countl_one(uint8_t{0b10101010}) == 1);
ASL_TEST_EXPECT(asl::countl_one(uint8_t{0b11101001}) == 3);
}
ASL_TEST(rotl)
{
ASL_TEST_EXPECT(asl::rotl(uint32_t{0x4000'0100}, 1) == 0x8000'0200);
ASL_TEST_EXPECT(asl::rotl(uint32_t{0x4000'0100}, 3) == 0x0000'0802);
ASL_TEST_EXPECT(asl::rotl(uint32_t{0x4000'0100}, 0) == 0x4000'0100);
ASL_TEST_EXPECT(asl::rotl(uint32_t{0x4000'0100}, -1) == 0x2000'0080);
ASL_TEST_EXPECT(asl::rotl(uint32_t{0x4000'0100}, -12) == 0x1004'0000);
}
ASL_TEST(rotr)
{
ASL_TEST_EXPECT(asl::rotr(uint32_t{0x4000'0100}, -1) == 0x8000'0200);
ASL_TEST_EXPECT(asl::rotr(uint32_t{0x4000'0100}, -3) == 0x0000'0802);
ASL_TEST_EXPECT(asl::rotr(uint32_t{0x4000'0100}, 0) == 0x4000'0100);
ASL_TEST_EXPECT(asl::rotr(uint32_t{0x4000'0100}, 1) == 0x2000'0080);
ASL_TEST_EXPECT(asl::rotr(uint32_t{0x4000'0100}, 12) == 0x1004'0000);
}
ASL_TEST(byteswap)
{
ASL_TEST_EXPECT(asl::byteswap(uint32_t{0x1234'5678}) == 0x7856'3412);
ASL_TEST_EXPECT(asl::byteswap(uint16_t{0x1234}) == 0x3412);
ASL_TEST_EXPECT(asl::byteswap(uint64_t{0x0123'4567'89ab'cdef}) == 0xefcd'ab89'6745'2301);
}
ASL_TEST(bit_ceil) // NOLINT(*-cognitive-complexity)
{
ASL_TEST_EXPECT(asl::bit_ceil(uint8_t{0}) == 0);
ASL_TEST_EXPECT(asl::bit_ceil(uint8_t{1}) == 1);
ASL_TEST_EXPECT(asl::bit_ceil(uint8_t{2}) == 2);
ASL_TEST_EXPECT(asl::bit_ceil(uint8_t{3}) == 4);
ASL_TEST_EXPECT(asl::bit_ceil(uint8_t{4}) == 4);
ASL_TEST_EXPECT(asl::bit_ceil(uint8_t{5}) == 8);
ASL_TEST_EXPECT(asl::bit_ceil(uint8_t{6}) == 8);
ASL_TEST_EXPECT(asl::bit_ceil(uint8_t{127}) == 128);
ASL_TEST_EXPECT(asl::bit_ceil(uint8_t{128}) == 128);
ASL_TEST_EXPECT(asl::bit_ceil(uint8_t{254}) == 0);
ASL_TEST_EXPECT(asl::bit_ceil(uint8_t{255}) == 0);
ASL_TEST_EXPECT(asl::bit_ceil(uint16_t{0}) == 0);
ASL_TEST_EXPECT(asl::bit_ceil(uint16_t{1}) == 1);
ASL_TEST_EXPECT(asl::bit_ceil(uint16_t{2}) == 2);
ASL_TEST_EXPECT(asl::bit_ceil(uint16_t{3}) == 4);
ASL_TEST_EXPECT(asl::bit_ceil(uint16_t{4}) == 4);
ASL_TEST_EXPECT(asl::bit_ceil(uint16_t{5}) == 8);
ASL_TEST_EXPECT(asl::bit_ceil(uint16_t{6}) == 8);
ASL_TEST_EXPECT(asl::bit_ceil(uint16_t{32000}) == 32768);
ASL_TEST_EXPECT(asl::bit_ceil(uint16_t{32768}) == 32768);
ASL_TEST_EXPECT(asl::bit_ceil(uint16_t{35000}) == 0);
ASL_TEST_EXPECT(asl::bit_ceil(uint16_t{65535}) == 0);
}
ASL_TEST(bit_floor) // NOLINT(*-cognitive-complexity)
{
ASL_TEST_EXPECT(asl::bit_floor(uint8_t{0}) == 0);
ASL_TEST_EXPECT(asl::bit_floor(uint8_t{1}) == 1);
ASL_TEST_EXPECT(asl::bit_floor(uint8_t{2}) == 2);
ASL_TEST_EXPECT(asl::bit_floor(uint8_t{3}) == 2);
ASL_TEST_EXPECT(asl::bit_floor(uint8_t{4}) == 4);
ASL_TEST_EXPECT(asl::bit_floor(uint8_t{5}) == 4);
ASL_TEST_EXPECT(asl::bit_floor(uint8_t{6}) == 4);
ASL_TEST_EXPECT(asl::bit_floor(uint8_t{127}) == 64);
ASL_TEST_EXPECT(asl::bit_floor(uint8_t{128}) == 128);
ASL_TEST_EXPECT(asl::bit_floor(uint8_t{254}) == 128);
ASL_TEST_EXPECT(asl::bit_floor(uint8_t{255}) == 128);
ASL_TEST_EXPECT(asl::bit_floor(uint16_t{0}) == 0);
ASL_TEST_EXPECT(asl::bit_floor(uint16_t{1}) == 1);
ASL_TEST_EXPECT(asl::bit_floor(uint16_t{2}) == 2);
ASL_TEST_EXPECT(asl::bit_floor(uint16_t{3}) == 2);
ASL_TEST_EXPECT(asl::bit_floor(uint16_t{4}) == 4);
ASL_TEST_EXPECT(asl::bit_floor(uint16_t{5}) == 4);
ASL_TEST_EXPECT(asl::bit_floor(uint16_t{6}) == 4);
ASL_TEST_EXPECT(asl::bit_floor(uint16_t{32000}) == 16384);
ASL_TEST_EXPECT(asl::bit_floor(uint16_t{32768}) == 32768);
ASL_TEST_EXPECT(asl::bit_floor(uint16_t{35000}) == 32768);
ASL_TEST_EXPECT(asl::bit_floor(uint16_t{65535}) == 32768);
}
ASL_TEST(bit_width) // NOLINT(*-cognitive-complexity)
{
ASL_TEST_EXPECT(asl::bit_width(uint8_t{0}) == 0);
ASL_TEST_EXPECT(asl::bit_width(uint8_t{1}) == 1);
ASL_TEST_EXPECT(asl::bit_width(uint8_t{2}) == 2);
ASL_TEST_EXPECT(asl::bit_width(uint8_t{3}) == 2);
ASL_TEST_EXPECT(asl::bit_width(uint8_t{4}) == 3);
ASL_TEST_EXPECT(asl::bit_width(uint8_t{5}) == 3);
ASL_TEST_EXPECT(asl::bit_width(uint8_t{6}) == 3);
ASL_TEST_EXPECT(asl::bit_width(uint16_t{0}) == 0);
ASL_TEST_EXPECT(asl::bit_width(uint16_t{1}) == 1);
ASL_TEST_EXPECT(asl::bit_width(uint16_t{2}) == 2);
ASL_TEST_EXPECT(asl::bit_width(uint16_t{3}) == 2);
ASL_TEST_EXPECT(asl::bit_width(uint16_t{4}) == 3);
ASL_TEST_EXPECT(asl::bit_width(uint16_t{5}) == 3);
ASL_TEST_EXPECT(asl::bit_width(uint16_t{6}) == 3);
ASL_TEST_EXPECT(asl::bit_width(uint16_t{65535}) == 16);
ASL_TEST_EXPECT(asl::bit_width(uint32_t{0}) == 0);
ASL_TEST_EXPECT(asl::bit_width(uint32_t{1}) == 1);
ASL_TEST_EXPECT(asl::bit_width(uint32_t{2}) == 2);
ASL_TEST_EXPECT(asl::bit_width(uint32_t{3}) == 2);
ASL_TEST_EXPECT(asl::bit_width(uint32_t{4}) == 3);
ASL_TEST_EXPECT(asl::bit_width(uint32_t{5}) == 3);
ASL_TEST_EXPECT(asl::bit_width(uint32_t{6}) == 3);
ASL_TEST_EXPECT(asl::bit_width(uint32_t{65535}) == 16);
ASL_TEST_EXPECT(asl::bit_width(uint32_t{65536}) == 17);
ASL_TEST_EXPECT(asl::bit_width(uint32_t{0xffff'ffff}) == 32);
ASL_TEST_EXPECT(asl::bit_width(uint64_t{0}) == 0);
ASL_TEST_EXPECT(asl::bit_width(uint64_t{1}) == 1);
ASL_TEST_EXPECT(asl::bit_width(uint64_t{2}) == 2);
ASL_TEST_EXPECT(asl::bit_width(uint64_t{3}) == 2);
ASL_TEST_EXPECT(asl::bit_width(uint64_t{4}) == 3);
ASL_TEST_EXPECT(asl::bit_width(uint64_t{5}) == 3);
ASL_TEST_EXPECT(asl::bit_width(uint64_t{6}) == 3);
ASL_TEST_EXPECT(asl::bit_width(uint64_t{65535}) == 16);
ASL_TEST_EXPECT(asl::bit_width(uint64_t{65536}) == 17);
ASL_TEST_EXPECT(asl::bit_width(uint64_t{0xffff'ffff}) == 32);
}

View File

@ -25,6 +25,9 @@ struct empty {};
template<typename T> struct id { using type = T; };
struct in_place_t {};
static constexpr in_place_t in_place{};
template<typename... Args> static constexpr isize_t types_count = sizeof...(Args);
template<typename T, T kValue> struct integral_constant { static constexpr T value = kValue; };
@ -234,17 +237,75 @@ template<> struct _is_floating_point_helper<double> : true_type {};
template<typename T> concept is_floating_point = _is_floating_point_helper<un_cv_t<T>>::value;
template<typename T> struct _is_integer_helper : false_type {};
template<> struct _is_integer_helper<int8_t> : true_type {};
template<> struct _is_integer_helper<int16_t> : true_type {};
template<> struct _is_integer_helper<int32_t> : true_type {};
template<> struct _is_integer_helper<int64_t> : true_type {};
template<> struct _is_integer_helper<uint8_t> : true_type {};
template<> struct _is_integer_helper<uint16_t> : true_type {};
template<> struct _is_integer_helper<uint32_t> : true_type {};
template<> struct _is_integer_helper<uint64_t> : true_type {};
template<typename T> struct _integer_traits
{
static constexpr bool kSigned = false;
static constexpr bool kUnsigned = false;
};
template<typename T> concept is_integer = _is_integer_helper<un_cv_t<T>>::value;
template<> struct _integer_traits<uint8_t>
{
static constexpr bool kSigned = false;
static constexpr bool kUnsigned = true;
using as_signed = int8_t;
};
template<> struct _integer_traits<uint16_t>
{
static constexpr bool kSigned = false;
static constexpr bool kUnsigned = true;
using as_signed = int16_t;
};
template<> struct _integer_traits<uint32_t>
{
static constexpr bool kSigned = false;
static constexpr bool kUnsigned = true;
using as_signed = int32_t;
};
template<> struct _integer_traits<uint64_t>
{
static constexpr bool kSigned = false;
static constexpr bool kUnsigned = true;
using as_signed = int64_t;
};
template<> struct _integer_traits<int8_t>
{
static constexpr bool kSigned = true;
static constexpr bool kUnsigned = false;
using as_unsigned = uint8_t;
};
template<> struct _integer_traits<int16_t>
{
static constexpr bool kSigned = true;
static constexpr bool kUnsigned = false;
using as_unsigned = uint16_t;
};
template<> struct _integer_traits<int32_t>
{
static constexpr bool kSigned = true;
static constexpr bool kUnsigned = false;
using as_unsigned = uint32_t;
};
template<> struct _integer_traits<int64_t>
{
static constexpr bool kSigned = true;
static constexpr bool kUnsigned = false;
using as_unsigned = uint64_t;
};
template<typename T> concept is_signed_integer = _integer_traits<T>::kSigned;
template<typename T> concept is_unsigned_integer = _integer_traits<T>::kUnsigned;
template<typename T> concept is_integer = is_signed_integer<T> || is_unsigned_integer<T>;
template<is_signed_integer T> using as_unsigned_integer = _integer_traits<T>::as_unsigned;
template<is_unsigned_integer T> using as_signed_integer = _integer_traits<T>::as_signed;
template<typename T> concept is_enum = __is_enum(T);

View File

@ -47,9 +47,6 @@ template<typename T>
namespace asl
{
struct in_place_t {};
static constexpr in_place_t in_place{};
template<moveable T>
constexpr void swap(T& a, T& b)
{
@ -84,27 +81,6 @@ constexpr T max(T a, T b)
return (a >= b) ? a : b;
}
constexpr uint64_t round_up_pow2(uint64_t v)
{
ASL_ASSERT(v <= 0x8000'0000'0000'0000);
v -= 1;
v |= v >> 1U;
v |= v >> 2U;
v |= v >> 4U;
v |= v >> 8U;
v |= v >> 16U;
v |= v >> 32U;
return v + 1;
}
constexpr bool is_pow2(isize_t v)
{
return v > 0 && ((v - 1) & v) == 0; // NOLINT
}
// NOLINTBEGIN(*-macro-parentheses)
#define ASL_DELETE_COPY(T) \
T(const T&) = delete; \

View File

@ -9,6 +9,7 @@
#include "asl/memory/memory.hpp"
#include "asl/base/annotations.hpp"
#include "asl/base/assert.hpp"
#include "asl/base/bit.hpp"
#include "asl/types/span.hpp"
#include "asl/hashing/hash.hpp"
@ -320,7 +321,7 @@ public:
if (new_capacity <= capacity()) { return; }
ASL_ASSERT(new_capacity > kInlineCapacity);
new_capacity = static_cast<isize_t>(round_up_pow2(static_cast<uint64_t>(new_capacity)));
new_capacity = static_cast<isize_t>(bit_ceil(static_cast<uint64_t>(new_capacity)));
T* old_data = data();
const isize_t old_capacity = capacity();

View File

@ -7,6 +7,7 @@
#include "asl/base/annotations.hpp"
#include "asl/base/utility.hpp"
#include "asl/base/meta.hpp"
#include "asl/base/bit.hpp"
#include "asl/memory/allocator.hpp"
#include "asl/memory/memory.hpp"
#include "asl/types/maybe_uninit.hpp"
@ -83,7 +84,7 @@ protected:
ASL_ASSERT(size > 0);
return max<isize_t>(
kMinCapacity,
static_cast<isize_t>(round_up_pow2((static_cast<uint64_t>(size) * 4 + 2) / 3)));
static_cast<isize_t>(bit_ceil((static_cast<uint64_t>(size) * 4 + 2) / 3)));
}
static void insert_inner(