Skip to content

Commit 59a6cb1

Browse files
authored
Merge pull request #157 from elbeno/atomic-bitset
✨ Add `atomic_bitset`
2 parents 63cbed7 + 212b5cd commit 59a6cb1

16 files changed

+812
-60
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/atomic_bitset.adoc

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
2+
== `atomic_bitset.hpp`
3+
4+
https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/atomic_bitset.hpp[`atomic_bitset.hpp`]
5+
provides an implementation of a xref:bitset.adoc#_bitset_hpp[`bitset`] with atomic semantics.
6+
7+
An `atomic_bitset` is limited in size to the maximum integral type a platform
8+
can support while still using lock-free atomic instructions. Like `bitset`, it
9+
can be defined by selecting the underlying storage type automatically:
10+
[source,cpp]
11+
----
12+
using A = stdx::bitset<8>; // uses uint8_t
13+
using B = stdx::bitset<16>; // uses uint16_t
14+
using C = stdx::bitset<32>; // uses uint32_t
15+
using D = stdx::bitset<64>; // uses uint64_t
16+
----
17+
18+
`atomic_bitset` is constructed in the same way as `bitset`: with `all_bits`,
19+
`place_bits`, a value, or a `string_view`:
20+
[source,cpp]
21+
----
22+
using namespace std::string_view_literals;
23+
auto bs0 = stdx::atomic_bitset<8>{};
24+
auto bs1 = stdx::atomic_bitset<8>{stdx::all_bits}; // 0b1111'1111
25+
auto bs2 = stdx::atomic_bitset<8>{stdx::place_bits, 0, 1, 3}; // 0b1011
26+
auto bs3 = stdx::atomic_bitset<8>{0b1011};
27+
auto bs4 = stdx::atomic_bitset<8>{"1011"sv};
28+
----
29+
30+
NOTE: `atomic_bitset`​'s constructors are `constexpr`, but none of the other
31+
functions are.
32+
33+
Also like `bitset`, `atomic_bitset` supports conversion to integral types:
34+
[source,cpp]
35+
----
36+
auto bs = stdx::atomic_bitset<11>{0b101}; // 11 bits, value 5
37+
auto i = bs.to<std::uint64_t>(); // 5 (a std::uint64_t)
38+
auto j = bs.to_natural(); // 5 (a std::uint16_t)
39+
----
40+
41+
And operation with enumeration types:
42+
[source,cpp]
43+
----
44+
enum struct Bits { ZERO, ONE, TWO, THREE, MAX };
45+
auto bs = stdx::atomic_bitset<Bits::MAX>{stdx::all_bits}; // 4 bits, value 0b1111
46+
bs.set(Bits::ZERO);
47+
bs.reset(Bits::ZERO);
48+
bs.flip(Bits::ZERO);
49+
auto bit_zero = bs[Bits::ZERO];
50+
----
51+
52+
Unlike `bitset`, `atomic_bitset`​'s operations are atomic. For example, `load`
53+
and `store` are basic operations that return and take a corresponding `bitset`:
54+
55+
[source,cpp]
56+
----
57+
constexpr auto bs = stdx::atomic_bitset<8>{0b1010ul};
58+
auto copy = bs.load(); // a stdx::bitset<8>{0b1010ul};
59+
bs.store(copy);
60+
----
61+
62+
Like https://en.cppreference.com/w/cpp/atomic/atomic/load[`load`] and
63+
https://en.cppreference.com/w/cpp/atomic/atomic/store[`store`] on
64+
https://en.cppreference.com/w/cpp/atomic/atomic[`std::atomic`], the `load` and
65+
`store` operations on `stdx::atomic_bitset` take an optional
66+
https://en.cppreference.com/w/cpp/atomic/memory_order[`std::memory_order`].
67+
`stdx::atomic_bitset` is also implicitly convertible to a corresponding
68+
`stdx::bitset`; that operation is equivalent to `load()`.
69+
70+
The `set`, `reset` and `flip` operations also take an optional
71+
`std::memory_order`: these operations are equivalent to `store` in their
72+
semantics, except that they return the `stdx::bitset` that was the previous
73+
value.
74+
75+
[source,cpp]
76+
----
77+
constexpr auto bs = stdx::atomic_bitset<8>{0b1010ul};
78+
auto prev = bs.set(0);
79+
// bs == 1011
80+
// prev == 1010 (stdx::bitset<8>)
81+
----
82+
83+
NOTE: When `set` or `reset` are called without specifying bits, they return a
84+
reference to the `atomic_bitset`. This is because these operations result in a
85+
plain `store` which does not return the previous value.
86+
87+
`all`, `any`, `none` and `count` are also available on `atomic_bitset` and they
88+
are each equivalent to `load` followed by the respective operation. Like `load`,
89+
they also take an optional `std::memory_order`.
90+
91+
So what is _not_ available on `atomic_bitset`?
92+
93+
* any binary operation: equality, binary versions of `and`, `or`, etc.
94+
* bit shift operations
95+
* `for_each` and `lowest_unset`
96+
* unary `not`
97+
98+
These operations are not provided for varying reasons:
99+
100+
* atomic semantics are impossible or problematic to guarantee (binary operations)
101+
* atomic instructions are not available (bit shifts, `lowest_unset`)
102+
* atomic semantics are unclear (`for_each`)
103+
* the caller can easily achieve what they want (unary `not`)
104+
105+
In all of these cases though, the caller can make the right choice for them, and
106+
use the corresponding operations on `bitset` after correctly reasoning about the
107+
required semantics.

docs/bitset.adoc

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ platform.
1414

1515
* Stream input and output operators are not implemented.
1616
* A `std::hash` specialization is not implemented.
17-
* `to_string`, `to_ulong` and `to_ullong` are not implemented
17+
* `to_string`, `to_ulong` and `to_ullong` are not implemented -- but `to` and
18+
`to_natural` provide ways to convert to integral types.
1819
* `operator[]` is read-only: it does not return a proxy reference type
1920

2021
A bitset has two template parameters: the size of the bitset and the storage
@@ -70,8 +71,8 @@ auto i = bs.to<std::uint64_t>(); // 5 (a std::uint64_t)
7071
auto j = bs.to_natural(); // 5 (a std::uint16_t)
7172
----
7273

73-
Bitsets support all the usual bitwise operators (`and`, `or`, `xor` and `not`)
74-
and also support `operator-` meaning set difference, or `a & ~b`.
74+
Bitsets support all the usual bitwise operators (`and`, `or`, `xor` and `not`,
75+
shifts) and also support `operator-` meaning set difference, or `a & ~b`.
7576

7677
A bitset can also be used with an enumeration that represents bits:
7778
[source,cpp]
@@ -86,3 +87,19 @@ auto bit_zero = bs[Bits::ZERO];
8687

8788
NOTE: The enumeration values are the bit positions, not the bits themselves (the
8889
enumeration values are not fixed to powers-of-2).
90+
91+
A bitset also supports efficient iteration with `for_each`, which calls a
92+
function with each set bit in turn, working from LSB to MSB:
93+
[source,cpp]
94+
----
95+
auto bs = stdx::bitset<8>{0b1010'1010ul};
96+
for_each([&](auto i) { /* i == 1, 3, 5, 7 */ }, bs);
97+
----
98+
99+
To support "external" iteration, or use cases like using a bitset to track used
100+
objects, `lowest_unset` is also provided:
101+
[source,cpp]
102+
----
103+
auto bs = stdx::bitset<8>{0b11'0111ul};
104+
auto i = bs.lowest_unset(); // i == 3
105+
----

docs/index.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
:toc: left
88

99
include::intro.adoc[]
10+
include::atomic_bitset.adoc[]
1011
include::algorithm.adoc[]
1112
include::bit.adoc[]
1213
include::bitset.adoc[]
@@ -33,6 +34,7 @@ include::numeric.adoc[]
3334
include::optional.adoc[]
3435
include::panic.adoc[]
3536
include::priority.adoc[]
37+
include::ranges.adoc[]
3638
include::span.adoc[]
3739
include::tuple.adoc[]
3840
include::tuple_algorithms.adoc[]

docs/intro.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ into headers whose names match the standard.
3535
The following headers are available:
3636

3737
* https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/algorithm.hpp[`algorithm.hpp`]
38+
* https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/atomic_bitset.hpp[`atomic_bitset.hpp`]
3839
* https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/bit.hpp[`bit.hpp`]
3940
* https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/bitset.hpp[`bitset.hpp`]
4041
* https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/byterator.hpp[`byterator.hpp`]
@@ -60,6 +61,7 @@ The following headers are available:
6061
* https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/optional.hpp[`optional.hpp`]
6162
* https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/panic.hpp[`panic.hpp`]
6263
* https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/priority.hpp[`priority.hpp`]
64+
* https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/ranges.hpp[`ranges.hpp`]
6365
* https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/span.hpp[`span.hpp`]
6466
* https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/tuple.hpp[`tuple.hpp`]
6567
* https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/tuple_algorithms.hpp[`tuple_algorithms.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
----

docs/ranges.adoc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2+
== `ranges.hpp`
3+
4+
https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/ranges.hpp[`ranges.hpp`]
5+
contains a single concept: `range`. A type models the `stdx::range` concept if
6+
`std::begin` and `std::end` are defined for that type.

0 commit comments

Comments
 (0)