Skip to content

Commit 9dea92b

Browse files
committed
✨ Add atomic_bitset
Problem: - It is useful to have atomic semantics on a bitset. Solution: - Add `atomic_bitset`. Notes: - `std::atomic<stdx::bitset<N, std::uintXX_t>>` is unwieldy and could not guarantee the use of atomic instructions. - `stdx::bitset<N, std::atomic<std::uintXX_t>>` would not provide the right atomic semantics. - The API of `atomic_bitset` differs from the API of `bitset` in some important ways, but is intended to provide for the use case of effectively using an atomic integral type as a bitset.
1 parent 63cbed7 commit 9dea92b

File tree

8 files changed

+586
-8
lines changed

8 files changed

+586
-8
lines changed

.github/workflows/unit_tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ env:
1616
DEFAULT_LLVM_VERSION: 18
1717
DEFAULT_GCC_VERSION: 13
1818
MULL_LLVM_VERSION: 17
19-
HYPOTHESIS_PROFILE: ci
19+
HYPOTHESIS_PROFILE: default
2020

2121
concurrency:
2222
group: ${{ github.head_ref || github.run_id }}

CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ target_sources(
3737
include
3838
FILES
3939
include/stdx/algorithm.hpp
40+
include/stdx/atomic_bitset.hpp
4041
include/stdx/bit.hpp
4142
include/stdx/bitset.hpp
4243
include/stdx/byterator.hpp
@@ -51,6 +52,7 @@ target_sources(
5152
include/stdx/cx_queue.hpp
5253
include/stdx/cx_set.hpp
5354
include/stdx/cx_vector.hpp
55+
include/stdx/detail/bitset_common.hpp
5456
include/stdx/detail/list_common.hpp
5557
include/stdx/for_each_n_args.hpp
5658
include/stdx/functional.hpp

docs/intrusive_forward_list.adoc

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22
== `intrusive_forward_list.hpp`
33

44
`intrusive_forward_list` is a singly-linked list designed for use at compile-time or
5-
with static objects. It supports pushing and popping at the front or back, and
6-
removal from the middle.
5+
with static objects. It supports pushing and popping at the front or back.
76

87
[source,cpp]
98
----

include/stdx/atomic_bitset.hpp

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
#pragma once
2+
3+
#include <stdx/bit.hpp>
4+
#include <stdx/bitset.hpp>
5+
#include <stdx/compiler.hpp>
6+
#include <stdx/concepts.hpp>
7+
#include <stdx/detail/bitset_common.hpp>
8+
#include <stdx/type_traits.hpp>
9+
#include <stdx/udls.hpp>
10+
11+
#include <algorithm>
12+
#include <atomic>
13+
#include <cstddef>
14+
#include <iterator>
15+
#include <limits>
16+
#include <string_view>
17+
18+
namespace stdx {
19+
inline namespace v1 {
20+
namespace detail {
21+
template <std::size_t N, typename StorageElem> class atomic_bitset {
22+
constexpr static auto bit = StorageElem{1U};
23+
24+
static_assert(N <= std::numeric_limits<StorageElem>::digits,
25+
"atomic_bitset is limited to a single storage element");
26+
static_assert(std::atomic<StorageElem>::is_always_lock_free,
27+
"atomic_bitset is not lock free");
28+
std::atomic<StorageElem> storage{};
29+
30+
constexpr static auto mask = bit_mask<StorageElem, N - 1>();
31+
StorageElem salient_value(std::memory_order order) const {
32+
return storage.load(order) & mask;
33+
}
34+
35+
[[nodiscard]] static constexpr auto
36+
value_from_string(std::string_view str, std::size_t pos, std::size_t n,
37+
char one) -> StorageElem {
38+
StorageElem ret{};
39+
auto const len = std::min(n, str.size() - pos);
40+
auto const s = str.substr(pos, std::min(len, N));
41+
auto i = bit;
42+
for (auto it = std::rbegin(s); it != std::rend(s); ++it) {
43+
if (*it == one) {
44+
ret |= i;
45+
}
46+
i <<= 1u;
47+
}
48+
return ret;
49+
}
50+
51+
using bitset_t = bitset<N, StorageElem>;
52+
53+
public:
54+
constexpr atomic_bitset() = default;
55+
constexpr explicit atomic_bitset(std::uint64_t value)
56+
: storage{static_cast<StorageElem>(value & mask)} {}
57+
58+
template <typename... Bs>
59+
constexpr explicit atomic_bitset(place_bits_t, Bs... bs)
60+
: storage{static_cast<StorageElem>(
61+
(StorageElem{} | ... |
62+
static_cast<StorageElem>(bit << to_underlying(bs))))} {}
63+
64+
constexpr explicit atomic_bitset(all_bits_t) : storage{mask} {}
65+
66+
constexpr explicit atomic_bitset(std::string_view str, std::size_t pos = 0,
67+
std::size_t n = std::string_view::npos,
68+
char one = '1')
69+
: storage{value_from_string(str, pos, n, one)} {}
70+
71+
template <typename T>
72+
[[nodiscard]] auto
73+
to(std::memory_order order = std::memory_order_seq_cst) const -> T {
74+
using U = underlying_type_t<T>;
75+
static_assert(
76+
unsigned_integral<U>,
77+
"Conversion must be to an unsigned integral type or enum!");
78+
static_assert(N <= std::numeric_limits<U>::digits,
79+
"atomic_bitset too big for conversion to T");
80+
return static_cast<T>(storage.load(order));
81+
}
82+
83+
[[nodiscard]] auto
84+
to_natural(std::memory_order order = std::memory_order_seq_cst) const {
85+
return storage.load(order);
86+
}
87+
88+
operator bitset_t() const { return bitset_t{storage.load()}; }
89+
90+
auto load(std::memory_order order = std::memory_order_seq_cst) const
91+
-> bitset_t {
92+
return bitset_t{storage.load(order)};
93+
}
94+
auto store(bitset_t b,
95+
std::memory_order order = std::memory_order_seq_cst) {
96+
storage.store(b.template to<StorageElem>(), order);
97+
}
98+
99+
constexpr static std::integral_constant<std::size_t, N> size{};
100+
101+
bool is_lock_free() const noexcept { return storage.is_lock_free(); }
102+
constexpr static std::bool_constant<
103+
std::atomic<StorageElem>::is_always_lock_free>
104+
is_always_lock_free{};
105+
106+
template <typename T> [[nodiscard]] auto operator[](T idx) const -> bool {
107+
auto const pos = static_cast<std::size_t>(to_underlying(idx));
108+
return (salient_value(std::memory_order_seq_cst) & (bit << pos)) != 0;
109+
}
110+
111+
template <typename T>
112+
auto set(T idx, bool value = true,
113+
std::memory_order order = std::memory_order_seq_cst) -> bitset_t {
114+
auto const pos = static_cast<std::size_t>(to_underlying(idx));
115+
if (value) {
116+
return bitset_t{
117+
storage.fetch_or(static_cast<StorageElem>(bit << pos), order)};
118+
}
119+
return bitset_t{
120+
storage.fetch_and(static_cast<StorageElem>(~(bit << pos)), order)};
121+
}
122+
123+
auto set(lsb_t lsb, msb_t msb, bool value = true,
124+
std::memory_order order = std::memory_order_seq_cst) -> bitset_t {
125+
auto const l = to_underlying(lsb);
126+
auto const m = to_underlying(msb);
127+
auto const shifted_value = bit_mask<StorageElem>(m, l);
128+
if (value) {
129+
return bitset_t{storage.fetch_or(shifted_value, order)};
130+
}
131+
return bitset_t{storage.fetch_and(~shifted_value, order)};
132+
}
133+
134+
auto set(lsb_t lsb, length_t len, bool value = true,
135+
std::memory_order order = std::memory_order_seq_cst) -> bitset_t {
136+
auto const l = to_underlying(lsb);
137+
auto const length = to_underlying(len);
138+
return set(lsb, static_cast<msb_t>(l + length - 1), value, order);
139+
}
140+
141+
auto set(std::memory_order order = std::memory_order_seq_cst)
142+
LIFETIMEBOUND -> atomic_bitset & {
143+
storage.store(mask, order);
144+
return *this;
145+
}
146+
147+
template <typename T> auto reset(T idx) -> bitset_t {
148+
auto const pos = static_cast<std::size_t>(to_underlying(idx));
149+
return bitset_t{
150+
storage.fetch_and(static_cast<StorageElem>(~(bit << pos)))};
151+
}
152+
153+
auto reset(std::memory_order order = std::memory_order_seq_cst)
154+
LIFETIMEBOUND -> atomic_bitset & {
155+
storage.store(StorageElem{}, order);
156+
return *this;
157+
}
158+
159+
auto
160+
reset(lsb_t lsb, msb_t msb,
161+
std::memory_order order = std::memory_order_seq_cst) -> bitset_t {
162+
return set(lsb, msb, false, order);
163+
}
164+
165+
auto
166+
reset(lsb_t lsb, length_t len,
167+
std::memory_order order = std::memory_order_seq_cst) -> bitset_t {
168+
return set(lsb, len, false, order);
169+
}
170+
171+
template <typename T>
172+
auto flip(T idx,
173+
std::memory_order order = std::memory_order_seq_cst) -> bitset_t {
174+
auto const pos = static_cast<std::size_t>(to_underlying(idx));
175+
return bitset_t{
176+
storage.fetch_xor(static_cast<StorageElem>(bit << pos), order)};
177+
}
178+
179+
auto flip(std::memory_order order = std::memory_order_seq_cst) -> bitset_t {
180+
return bitset_t{storage.fetch_xor(mask, order)};
181+
}
182+
183+
[[nodiscard]] auto
184+
all(std::memory_order order = std::memory_order_seq_cst) const -> bool {
185+
return salient_value(order) == mask;
186+
}
187+
[[nodiscard]] auto
188+
any(std::memory_order order = std::memory_order_seq_cst) const -> bool {
189+
return salient_value(order) != 0;
190+
}
191+
[[nodiscard]] auto
192+
none(std::memory_order order = std::memory_order_seq_cst) const -> bool {
193+
return salient_value(order) == 0;
194+
}
195+
196+
[[nodiscard]] auto
197+
count(std::memory_order order = std::memory_order_seq_cst) const
198+
-> std::size_t {
199+
return static_cast<std::size_t>(popcount(salient_value(order)));
200+
}
201+
};
202+
} // namespace detail
203+
204+
template <auto N, typename StorageElem = void>
205+
using atomic_bitset = detail::atomic_bitset<
206+
to_underlying(N), decltype(smallest_uint<to_underlying(N), StorageElem>())>;
207+
} // namespace v1
208+
} // namespace stdx

include/stdx/bitset.hpp

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include <stdx/bit.hpp>
44
#include <stdx/compiler.hpp>
55
#include <stdx/concepts.hpp>
6+
#include <stdx/detail/bitset_common.hpp>
67
#include <stdx/type_traits.hpp>
78
#include <stdx/udls.hpp>
89

@@ -17,11 +18,6 @@
1718

1819
namespace stdx {
1920
inline namespace v1 {
20-
struct place_bits_t {};
21-
constexpr inline auto place_bits = place_bits_t{};
22-
struct all_bits_t {};
23-
constexpr inline auto all_bits = all_bits_t{};
24-
2521
namespace detail {
2622
template <std::size_t N, typename StorageElem> class bitset {
2723
constexpr static auto storage_elem_size =
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#pragma once
2+
3+
namespace stdx {
4+
inline namespace v1 {
5+
struct place_bits_t {};
6+
constexpr inline auto place_bits = place_bits_t{};
7+
struct all_bits_t {};
8+
constexpr inline auto all_bits = all_bits_t{};
9+
} // namespace v1
10+
} // namespace stdx

test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ add_tests(
2121
FILES
2222
algorithm
2323
always_false
24+
atomic_bitset
2425
bind
2526
bit
2627
bitset

0 commit comments

Comments
 (0)