Skip to content

Commit 9569ab7

Browse files
committed
[SingleSource/Atomic] Add preliminary tests for atomic builtins.
There exist atomic IR unit tests and libatomic unit tests, but neither can test the atomicity and interoperability of atomic builtins and compiler-rt's atomic library. These tests aim to approximate behaviour encountered in user code. These tests have caught issues in Clang. See llvm/llvm-project#74349 and llvm/llvm-project#73176 for LLVM changes inspired by these tests.
1 parent f234989 commit 9569ab7

15 files changed

+1192
-1
lines changed

MultiSource/UnitTests/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
add_subdirectory(C++11)
2+
add_subdirectory(Float)
23
if(ARCH STREQUAL "Mips")
34
add_subdirectory(Mips)
45
endif()
5-
add_subdirectory(Float)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Link the Clang built libatomic.
2+
execute_process(COMMAND ${CMAKE_C_COMPILER} --print-file-name=libclang_rt.atomic.so
3+
OUTPUT_VARIABLE _path_to_libatomic
4+
OUTPUT_STRIP_TRAILING_WHITESPACE)
5+
get_filename_component(_libatomic_dir ${_path_to_libatomic} DIRECTORY)
6+
add_link_options("LINKER:${_path_to_libatomic},-rpath=${_libatomic_dir}")
7+
8+
llvm_singlesource()
9+
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
Atomic runtime library tests
2+
3+
========
4+
5+
These tests aim to capture real-world multithreaded use cases of atomic
6+
builtins. Each test focuses on a single atomic operation. Those using multiple
7+
operations can be compared with other tests using the same operations to isolate
8+
bugs to a single atomic operation.
9+
10+
Each test consists of a "looper" body and a test script. The test script
11+
instantiates 10 threads, each running the looper. The loopers contend the same
12+
memory address, performing atomic operations on it. Each looper executes
13+
10^6 times for a total of 10^7 operations. The resultant value in the contended
14+
pointer is compared against a closed-form solution. It's expected that the two
15+
values equate.
16+
17+
For example, a looper that increments the shared pointer is expected to end up
18+
with a value of 10^7. If its final value is not that, the test fails.
19+
20+
Each test is performed on all relevant types.
21+
22+
========
23+
24+
Future test writers should be aware that the set of all tests that appear to
25+
test atomicity is not the set of all tests that test atomicity. In fact, tests
26+
that may test atomicity on one processor may not test atomicity on a different
27+
processor.
28+
29+
As such, test writers are encouraged to write nonatomic variants of their tests,
30+
and verify that they pass in a variety of scenarios.
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
//===--- big_test.cc -- Testing big (17+ byte) objects ------------ C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
//
9+
// This file tests atomic operations on big objects with aligned memory
10+
// addresses.
11+
//
12+
// The types tested are: bigs.
13+
// The ops tested are: cmpxchg.
14+
// TODO: Test load/store, xchg.
15+
//
16+
// Please read the README before contributing.
17+
//
18+
//===----------------------------------------------------------------------===//
19+
20+
#include <iostream>
21+
#include <thread>
22+
#include <vector>
23+
24+
#include "util.h"
25+
26+
// V >> 56 = 66, so to prevent 32-bit overflow, kExpected must be less than
27+
// 2^31 / 66 = 32 x 10^6.
28+
#ifdef SMALL_PROBLEM_SIZE
29+
static constexpr int kIterations = 1'000'000;
30+
#else
31+
static constexpr int kIterations = 3'000'000;
32+
#endif
33+
static constexpr int kExpected = kThreads * kIterations;
34+
static constexpr int kBigSize = 10;
35+
struct big_t {
36+
int v[kBigSize];
37+
};
38+
39+
// The big struct cmpxchg test is identical to the numeric cmpxchg test, except
40+
// each element of the underlying array is incremented.
41+
void looper_big_cmpxchg(big_t *abig, int success_model, int fail_model) {
42+
for (int n = 0; n < kIterations; ++n) {
43+
big_t desired, expected = {};
44+
do {
45+
desired = expected;
46+
for (int k = 0; k < kBigSize; ++k)
47+
desired.v[k]++;
48+
} while (!__atomic_compare_exchange(abig, &expected, &desired, true,
49+
success_model, fail_model));
50+
}
51+
}
52+
53+
void test_big_cmpxchg() {
54+
std::vector<std::thread> pool;
55+
for (int success_model : atomic_compare_exchange_models) {
56+
for (int fail_model : atomic_compare_exchange_models) {
57+
big_t abig = {};
58+
for (int n = 0; n < kThreads; ++n)
59+
pool.emplace_back(looper_big_cmpxchg, &abig, success_model, fail_model);
60+
for (int n = 0; n < kThreads; ++n)
61+
pool[n].join();
62+
pool.clear();
63+
for (int n = 0; n < kBigSize; ++n)
64+
if (abig.v[n] != kExpected)
65+
fail();
66+
}
67+
}
68+
}
69+
70+
void test_big() {
71+
std::cout << "Testing big\n";
72+
test_big_cmpxchg();
73+
}
74+
75+
int main() {
76+
test_big();
77+
std::cout << "PASSED\n";
78+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Testing big
2+
PASSED
3+
exit 0
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
//===--- float_test.cc -- Testing aligned floating point numbers -- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
//
9+
// This file tests atomic operations on floating point types with aligned
10+
// memory addresses.
11+
//
12+
// The types tested are: float, double, float128.
13+
// The ops tested are: xchg, cmpxchg.
14+
//
15+
// Please read the README before contributing.
16+
//
17+
//===----------------------------------------------------------------------===//
18+
19+
#include <sys/stat.h>
20+
21+
#include <iostream>
22+
#include <thread>
23+
#include <vector>
24+
25+
#include "util.h"
26+
27+
// There are 23-bits in the mantissa of a single-precision float.
28+
// Therefore, kExpected cannot exceed 2^24.
29+
static constexpr int kIterations = 1'500'000;
30+
static constexpr int kExpected = kThreads * kIterations;
31+
32+
// See int_aligned_test.cc for an explanation of xchg tests.
33+
template <typename T>
34+
void looper_float_scalar_xchg(T *afloat, int model) {
35+
__int128_t error = 0;
36+
T next = *afloat + 1;
37+
T result;
38+
for (int n = 0; n < kIterations; ++n) {
39+
__atomic_exchange(afloat, &next, &result, model);
40+
error +=
41+
static_cast<__int128_t>(next) - static_cast<__int128_t>(result + 1);
42+
next = result + 1;
43+
}
44+
__atomic_fetch_sub(afloat, static_cast<T>(error), model);
45+
}
46+
47+
template <typename T>
48+
void test_float_scalar_xchg() {
49+
std::vector<std::thread> pool;
50+
for (int model : atomic_exchange_models) {
51+
T afloat = 0;
52+
for (int n = 0; n < kThreads; ++n)
53+
pool.emplace_back(looper_float_scalar_xchg<T>, &afloat, model);
54+
for (int n = 0; n < kThreads; ++n)
55+
pool[n].join();
56+
pool.clear();
57+
if (afloat != kExpected)
58+
fail();
59+
}
60+
}
61+
62+
// See int_aligned_test.cc for an explanation of cmpxchg tests.
63+
template <typename T>
64+
void looper_float_scalar_cmpxchg(T *afloat, int success_model, int fail_model) {
65+
for (int n = 0; n < kIterations; ++n) {
66+
T desired, expected = 0;
67+
do {
68+
desired = expected + 1;
69+
} while (!__atomic_compare_exchange(afloat, &expected, &desired, true,
70+
success_model, fail_model));
71+
}
72+
}
73+
74+
template <typename T>
75+
void test_float_scalar_cmpxchg() {
76+
std::vector<std::thread> pool;
77+
for (int success_model : atomic_compare_exchange_models) {
78+
for (int fail_model : atomic_compare_exchange_models) {
79+
T afloat = 0;
80+
for (int n = 0; n < kThreads; ++n)
81+
pool.emplace_back(looper_float_scalar_cmpxchg<T>, &afloat,
82+
success_model, fail_model);
83+
for (int n = 0; n < kThreads; ++n)
84+
pool[n].join();
85+
pool.clear();
86+
if (afloat != kExpected)
87+
fail();
88+
}
89+
}
90+
}
91+
92+
void test_floating_point() {
93+
std::cout << "Testing float\n";
94+
test_float_scalar_xchg<float>();
95+
test_float_scalar_cmpxchg<float>();
96+
97+
std::cout << "Testing double\n";
98+
test_float_scalar_xchg<double>();
99+
test_float_scalar_cmpxchg<double>();
100+
101+
std::cout << "Testing float128\n";
102+
test_float_scalar_xchg<__float128>();
103+
test_float_scalar_cmpxchg<__float128>();
104+
}
105+
106+
int main() {
107+
test_floating_point();
108+
std::cout << "PASSED\n";
109+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Testing float
2+
Testing double
3+
Testing float128
4+
PASSED
5+
exit 0

0 commit comments

Comments
 (0)