Skip to content

Commit 03c4659

Browse files
authored
Merge pull request #62 from ntoskrnl7/feature/atomic-helper-coverage
Add atomic helper coverage
2 parents 08dc422 + b040350 commit 03c4659

5 files changed

Lines changed: 213 additions & 13 deletions

File tree

docs/cppreference-attribution.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ test harness, typically by moving the sample `main()` body into a namespaced
2727
- `std::atomic`: <https://en.cppreference.com/w/cpp/atomic/atomic>
2828
- `std::atomic_ref`: <https://en.cppreference.com/w/cpp/atomic/atomic_ref/atomic_ref>
2929
- `std::atomic_flag`: <https://en.cppreference.com/w/cpp/atomic/atomic_flag>
30+
- `std::atomic_thread_fence`: <https://en.cppreference.com/w/cpp/atomic/atomic_thread_fence>
31+
- `std::atomic_signal_fence`: <https://en.cppreference.com/w/cpp/atomic/atomic_signal_fence>
32+
- `std::atomic_fetch_add`: <https://en.cppreference.com/w/cpp/atomic/atomic_fetch_add>
33+
- `std::atomic_compare_exchange`: <https://en.cppreference.com/w/cpp/atomic/atomic_compare_exchange>
3034
- `std::latch`: <https://en.cppreference.com/w/cpp/thread/latch>
3135
- `std::barrier`: <https://en.cppreference.com/w/cpp/thread/barrier>
3236
- `std::counting_semaphore` / `std::binary_semaphore`: <https://en.cppreference.com/w/cpp/thread/counting_semaphore>

docs/feature-coverage.md

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,12 @@ are tracked in the [cppreference attribution note](./cppreference-attribution.md
111111
[(cppreference example)](../test/cmake/driver/src/cpp/stl/atomic.cpp)
112112
- [x] [std::atomic_flag](https://en.cppreference.com/w/cpp/atomic/atomic_flag)
113113
[(cppreference example)](../test/cmake/driver/src/cpp/stl/atomic.cpp)
114+
- [x] Atomic fences and free functions:
115+
[`std::atomic_thread_fence`](https://en.cppreference.com/w/cpp/atomic/atomic_thread_fence),
116+
[`std::atomic_signal_fence`](https://en.cppreference.com/w/cpp/atomic/atomic_signal_fence),
117+
[`std::atomic_fetch_add`](https://en.cppreference.com/w/cpp/atomic/atomic_fetch_add),
118+
[`std::atomic_compare_exchange`](https://en.cppreference.com/w/cpp/atomic/atomic_compare_exchange)
119+
[(cppreference example/API coverage)](../test/cmake/driver/src/cpp/stl/atomic.cpp)
114120
- [x] [std::chrono](https://en.cppreference.com/w/cpp/chrono)
115121
[(tested)](../test/cmake/driver/src/cpp/stl/chrono.cpp#L15)
116122
- C++20 timezone paths for
@@ -506,13 +512,6 @@ dependencies.
506512
and timed shared-lock edge cases
507513
- Additional `std::future` / `std::shared_future` error-path and timeout
508514
edge cases beyond the default examples
509-
- [ ] Atomic helpers
510-
- [`std::atomic_thread_fence`](https://en.cppreference.com/w/cpp/atomic/atomic_thread_fence),
511-
[`std::atomic_signal_fence`](https://en.cppreference.com/w/cpp/atomic/atomic_signal_fence)
512-
- Free atomic operations such as
513-
[`std::atomic_fetch_add`](https://en.cppreference.com/w/cpp/atomic/atomic_fetch_add)
514-
and
515-
[`std::atomic_compare_exchange`](https://en.cppreference.com/w/cpp/atomic/atomic_compare_exchange)
516515
- [ ] Additional ranges
517516
- Range adaptors not yet split into explicit driver examples, such as
518517
`take`, `drop`, `reverse`, `join`, `split`, `keys`, `values`,

docs/ko-kr-feature-coverage.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,12 @@ cppreference Example 코드를 이식한 항목은
112112
[(cppreference example)](../test/cmake/driver/src/cpp/stl/atomic.cpp)
113113
- [x] [std::atomic_flag](https://en.cppreference.com/w/cpp/atomic/atomic_flag)
114114
[(cppreference example)](../test/cmake/driver/src/cpp/stl/atomic.cpp)
115+
- [x] Atomic fence 및 free function:
116+
[`std::atomic_thread_fence`](https://en.cppreference.com/w/cpp/atomic/atomic_thread_fence),
117+
[`std::atomic_signal_fence`](https://en.cppreference.com/w/cpp/atomic/atomic_signal_fence),
118+
[`std::atomic_fetch_add`](https://en.cppreference.com/w/cpp/atomic/atomic_fetch_add),
119+
[`std::atomic_compare_exchange`](https://en.cppreference.com/w/cpp/atomic/atomic_compare_exchange)
120+
[(cppreference example/API coverage)](../test/cmake/driver/src/cpp/stl/atomic.cpp)
115121
- [x] [std::chrono](https://en.cppreference.com/w/cpp/chrono)
116122
[(tested)](../test/cmake/driver/src/cpp/stl/chrono.cpp#L15)
117123
- C++20 timezone 경로인
@@ -505,12 +511,6 @@ cppreference Example 코드를 이식한 항목은
505511
및 timed shared-lock edge case
506512
- 기본 예제보다 더 넓은 `std::future` / `std::shared_future` error-path
507513
및 timeout edge case
508-
- [ ] Atomic helper
509-
- [`std::atomic_thread_fence`](https://en.cppreference.com/w/cpp/atomic/atomic_thread_fence),
510-
[`std::atomic_signal_fence`](https://en.cppreference.com/w/cpp/atomic/atomic_signal_fence)
511-
- [`std::atomic_fetch_add`](https://en.cppreference.com/w/cpp/atomic/atomic_fetch_add),
512-
[`std::atomic_compare_exchange`](https://en.cppreference.com/w/cpp/atomic/atomic_compare_exchange)
513-
같은 free atomic operation
514514
- [ ] Ranges 추가 예제
515515
- 아직 명시 driver 예제로 분리하지 않은 range adaptor 후보:
516516
`take`, `drop`, `reverse`, `join`, `split`, `keys`, `values`,

test/cmake/driver/src/all.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,18 @@ void run();
151151
namespace atomic_flag_test {
152152
void run();
153153
}
154+
namespace atomic_thread_fence_test {
155+
void run();
156+
}
157+
namespace atomic_signal_fence_test {
158+
void run();
159+
}
160+
namespace atomic_fetch_add_test {
161+
void run();
162+
}
163+
namespace atomic_compare_exchange_test {
164+
void run();
165+
}
154166
namespace accumulate_test {
155167
void run();
156168
}
@@ -806,6 +818,10 @@ void cpp_std_tests() {
806818
CRTSYS_RUN_CPPREFERENCE_TEST(atomic_test);
807819
CRTSYS_RUN_CPPREFERENCE_TEST(atomic_ref_test);
808820
CRTSYS_RUN_CPPREFERENCE_TEST(atomic_flag_test);
821+
CRTSYS_RUN_CPPREFERENCE_TEST(atomic_thread_fence_test);
822+
CRTSYS_RUN_CPPREFERENCE_TEST(atomic_signal_fence_test);
823+
CRTSYS_RUN_CPPREFERENCE_TEST(atomic_fetch_add_test);
824+
CRTSYS_RUN_CPPREFERENCE_TEST(atomic_compare_exchange_test);
809825
CRTSYS_RUN_CPPREFERENCE_TEST(exception_ptr_test);
810826
CRTSYS_RUN_CPPREFERENCE_TEST(optional_test);
811827
CRTSYS_RUN_CPPREFERENCE_TEST(expected_test);

test/cmake/driver/src/cpp/stl/atomic.cpp

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@
22
// https://en.cppreference.com/w/cpp/atomic/atomic#Example
33
//
44
#include <atomic>
5+
#include <chrono>
6+
#include <cassert>
57
#include <functional>
68
#include <iostream>
79
#include <mutex>
810
#include <numeric>
911
#include <stdexcept>
12+
#include <string>
1013
#include <thread>
1114
#include <vector>
1215

@@ -137,3 +140,181 @@ void run() {
137140
}
138141
}
139142
} // namespace atomic_flag_test
143+
144+
//
145+
// https://en.cppreference.com/w/cpp/atomic/atomic_thread_fence#Example
146+
//
147+
namespace atomic_thread_fence_test {
148+
constexpr int num_mailboxes = 32;
149+
std::atomic<int> mailbox_receiver[num_mailboxes];
150+
std::string mailbox_data[num_mailboxes];
151+
152+
void run() {
153+
constexpr int my_id = 7;
154+
for (int i = 0; i < num_mailboxes; ++i) {
155+
mailbox_receiver[i].store(-1, std::memory_order_relaxed);
156+
mailbox_data[i].clear();
157+
}
158+
159+
// cppreference presents this as a snippet with "..." placeholders. The
160+
// driver test supplies concrete mailbox data so the fence path is executable.
161+
mailbox_data[11] = "fence-protected message";
162+
std::atomic_store_explicit(&mailbox_receiver[11], my_id,
163+
std::memory_order_release);
164+
165+
bool observed = false;
166+
for (int i = 0; i < num_mailboxes; ++i) {
167+
if (std::atomic_load_explicit(&mailbox_receiver[i],
168+
std::memory_order_relaxed) == my_id) {
169+
// synchronize with just one writer
170+
std::atomic_thread_fence(std::memory_order_acquire);
171+
// guaranteed to observe everything done in the writer thread
172+
// before the atomic_store_explicit()
173+
std::cout << mailbox_data[i] << '\n';
174+
observed = mailbox_data[i] == "fence-protected message";
175+
}
176+
}
177+
assert(observed);
178+
}
179+
} // namespace atomic_thread_fence_test
180+
181+
//
182+
// https://en.cppreference.com/w/cpp/atomic/atomic_signal_fence
183+
//
184+
namespace atomic_signal_fence_test {
185+
void run() {
186+
// cppreference documents atomic_signal_fence as an API page without a
187+
// standalone runnable example; keep direct API coverage here.
188+
std::atomic_signal_fence(std::memory_order_seq_cst);
189+
std::atomic_signal_fence(std::memory_order_acquire);
190+
std::cout << "atomic_signal_fence calls completed\n";
191+
}
192+
} // namespace atomic_signal_fence_test
193+
194+
//
195+
// https://en.cppreference.com/w/cpp/atomic/atomic_fetch_add#Example
196+
//
197+
namespace atomic_fetch_add_test {
198+
using namespace std::chrono_literals;
199+
// meaning of cnt:
200+
// 5: readers and writer are in race. There are no active readers or writers.
201+
// 4...0: there are 1...5 active readers, The writer is blocked.
202+
// -1: writer won the race and readers are blocked.
203+
204+
const int N = 5; // four concurrent readers are allowed
205+
std::atomic<int> cnt(N);
206+
207+
std::vector<int> data;
208+
void reader(int id) {
209+
for (;;) {
210+
// lock
211+
while (std::atomic_fetch_sub(&cnt, 1) <= 0) {
212+
std::atomic_fetch_add(&cnt, 1);
213+
}
214+
215+
// read
216+
if (!data.empty()) {
217+
std::cout << ("reader " + std::to_string(id) + " sees " +
218+
std::to_string(*data.rbegin()) + '\n');
219+
}
220+
if (data.size() == 25) {
221+
break;
222+
}
223+
224+
// unlock
225+
std::atomic_fetch_add(&cnt, 1);
226+
// pause
227+
std::this_thread::sleep_for(1ms);
228+
}
229+
}
230+
231+
void writer() {
232+
for (int n = 0; n < 25; ++n) {
233+
// lock
234+
while (std::atomic_fetch_sub(&cnt, N + 1) != N) {
235+
std::atomic_fetch_add(&cnt, N + 1);
236+
}
237+
238+
// write
239+
data.push_back(n);
240+
std::cout << "writer pushed back " << n << '\n';
241+
242+
// unlock
243+
std::atomic_fetch_add(&cnt, N + 1);
244+
// pause
245+
std::this_thread::sleep_for(1ms);
246+
}
247+
}
248+
249+
void run() {
250+
cnt = N;
251+
data.clear();
252+
253+
std::vector<std::thread> v;
254+
for (int n = 0; n < N; ++n) {
255+
v.emplace_back(reader, n);
256+
}
257+
v.emplace_back(writer);
258+
259+
for (auto &t : v) {
260+
t.join();
261+
}
262+
263+
if (data.size() != 25) {
264+
throw std::runtime_error("unexpected atomic_fetch_add data size");
265+
}
266+
}
267+
} // namespace atomic_fetch_add_test
268+
269+
//
270+
// https://en.cppreference.com/w/cpp/atomic/atomic_compare_exchange#Example
271+
//
272+
namespace atomic_compare_exchange_test {
273+
template <class T> struct node {
274+
T data;
275+
node *next;
276+
node(const T &data) : data(data), next(nullptr) {}
277+
};
278+
279+
template <class T> class stack {
280+
std::atomic<node<T> *> head{nullptr};
281+
282+
public:
283+
~stack() {
284+
node<T> *current = head.load(std::memory_order_relaxed);
285+
while (current != nullptr) {
286+
node<T> *next = current->next;
287+
delete current;
288+
current = next;
289+
}
290+
}
291+
292+
void push(const T &data) {
293+
node<T> *new_node = new node<T>(data);
294+
// put the current value of head into new_node->next
295+
new_node->next = head.load(std::memory_order_relaxed);
296+
// now make new_node the new head, but if the head
297+
// is no longer what's stored in new_node->next
298+
// (some other thread must have inserted a node just now)
299+
// then put that new head into new_node->next and try again
300+
while (!std::atomic_compare_exchange_weak_explicit(
301+
&head, &new_node->next, new_node, std::memory_order_release,
302+
std::memory_order_relaxed)) {
303+
; // the body of the loop is empty
304+
}
305+
// note: the above loop is not thread-safe in at least
306+
// GCC prior to 4.8.3 (bug 60272), clang prior to 2014-05-05 (bug 18899)
307+
// MSVC prior to 2014-03-17 (bug 819819). See member function version for
308+
// workaround
309+
}
310+
};
311+
312+
void run() {
313+
// cppreference's minimal program exits immediately after main(); the driver
314+
// harness initializes head explicitly and frees the nodes before unload.
315+
stack<int> s;
316+
s.push(1);
317+
s.push(2);
318+
s.push(3);
319+
}
320+
} // namespace atomic_compare_exchange_test

0 commit comments

Comments
 (0)