Skip to content

Commit 317d542

Browse files
committed
[SingleSource/Atomic] Add preliminary libatomic tests.
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 9ca97f5 commit 317d542

15 files changed

+1811
-1
lines changed

MultiSource/UnitTests/CMakeLists.txt

+1-1
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)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
set(FP_TOLERANCE 1000000)
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+
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${_path_to_libatomic} -Wl,-rpath=${_libatomic_dir}")
7+
8+
llvm_singlesource()
+122
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
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+
// The following text is present in each test file:
10+
//
11+
// These tests aim to capture real-world multithreaded use cases of atomic
12+
// builtins. Each test focuses on a single atomic operation. Those using
13+
// multiple operations can be compared with other tests using the same
14+
// operations to isolate bugs to a single atomic operation.
15+
//
16+
// Each test consists of a "looper" body and a test script. The test script
17+
// instantiates 10 threads, each running the looper. The loopers contend the
18+
// same memory address, performing atomic operations on it. Each looper executes
19+
// 10^6 times for a total of 10^7 operations. The resultant value in the
20+
// contended pointer is compared against a closed-form solution. It's expected
21+
// that the two values equate.
22+
//
23+
// For example, a looper that increments the shared pointer is expected to end
24+
// up with a value of 10^7. If its final value is not that, the test fails.
25+
//
26+
// Each test also tests the corresponding nonatomic operation with a separate
27+
// shared variable. Ideally, this value differs from the atomic "correct" value,
28+
// and the test can compare the two. In reality, some simpler operations (e.g.
29+
// those conducted through the ALU) can still end up with the correct answer,
30+
// even when performed nonatomically. These tests do not check the nonatomic
31+
// result, although it is still outputted to aid in debugging.
32+
//
33+
// Each test is performed on all relevant types.
34+
//
35+
//===----------------------------------------------------------------------===//
36+
//
37+
// This file tests atomic operations on big objects with aligned memory
38+
// addresses.
39+
//
40+
// The types tested are: bigs.
41+
// The ops tested are: xchg, cmpxchg.
42+
//
43+
//===----------------------------------------------------------------------===//
44+
45+
#include <cstdio>
46+
#include <iostream>
47+
#include <thread>
48+
#include <vector>
49+
50+
#include "util.h"
51+
52+
static constexpr int kBigSize = 10;
53+
struct big {
54+
int v[kBigSize];
55+
};
56+
57+
// The big struct cmpxchg test is identical to the numeric cmpxchg test, except
58+
// each element of the underlying array is incremented.
59+
void looper_big_cmpxchg(big *abig, big &bbig, int success_model,
60+
int fail_model) {
61+
for (int n = 0; n < kIterations; ++n) {
62+
big desired, expected = {};
63+
do {
64+
desired = expected;
65+
for (int k = 0; k < kBigSize; ++k) {
66+
desired.v[k]++;
67+
}
68+
} while (!__atomic_compare_exchange(abig, &expected, &desired, true,
69+
success_model, fail_model));
70+
for (int k = 0; k < kBigSize; ++k) {
71+
bbig.v[k]++;
72+
}
73+
}
74+
}
75+
76+
void test_big_cmpxchg() {
77+
std::vector<std::thread> pool;
78+
79+
for (int success_model : atomic_compare_exchange_models) {
80+
for (int fail_model : atomic_compare_exchange_models) {
81+
big abig = {};
82+
big bbig = {};
83+
for (int n = 0; n < kThreads; ++n) {
84+
pool.emplace_back(looper_big_cmpxchg, &abig, std::ref(bbig),
85+
success_model, fail_model);
86+
}
87+
for (int n = 0; n < kThreads; ++n) {
88+
pool[n].join();
89+
}
90+
pool.clear();
91+
std::cout << "CMPXCHG: ";
92+
std::cout << "atomic: ";
93+
for (int n = 0; n < kBigSize; ++n) {
94+
std::cout << abig.v[n] << " ";
95+
}
96+
std::cout << "\n ";
97+
std::cout << "nonatomic: ";
98+
for (int n = 0; n < kBigSize; ++n) {
99+
std::cout << bbig.v[n] << " ";
100+
}
101+
std::cout << "\n";
102+
for (int n = 0; n < kBigSize; ++n) {
103+
if (lt(abig.v[n], bbig.v[n]) || abig.v[n] != kExpected) {
104+
fail();
105+
}
106+
}
107+
}
108+
}
109+
}
110+
111+
void test_big() {
112+
printf("Testing big\n");
113+
test_big_cmpxchg();
114+
}
115+
116+
int main() {
117+
printf("%d threads; %d iterations each; total of %d\n", kThreads, kIterations,
118+
kExpected);
119+
120+
test_big();
121+
printf("PASSED\n");
122+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
10 threads; 1000000 iterations each; total of 10000000
2+
Testing big
3+
CMPXCHG: atomic: 10000000 10000000 10000000 10000000 10000000 10000000 10000000 10000000 10000000 10000000
4+
nonatomic: 9970397 9970397 9970397 9970397 9972269 9972269 9972269 9972269 9941180 9941180
5+
PASSED
6+
exit 0
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
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+
// The following text is present in each test file:
10+
//
11+
// These tests aim to capture real-world multithreaded use cases of atomic
12+
// builtins. Each test focuses on a single atomic operation. Those using
13+
// multiple operations can be compared with other tests using the same
14+
// operations to isolate bugs to a single atomic operation.
15+
//
16+
// Each test consists of a "looper" body and a test script. The test script
17+
// instantiates 10 threads, each running the looper. The loopers contend the
18+
// same memory address, performing atomic operations on it. Each looper executes
19+
// 10^6 times for a total of 10^7 operations. The resultant value in the
20+
// contended pointer is compared against a closed-form solution. It's expected
21+
// that the two values equate.
22+
//
23+
// For example, a looper that increments the shared pointer is expected to end
24+
// up with a value of 10^7. If its final value is not that, the test fails.
25+
//
26+
// Each test also tests the corresponding nonatomic operation with a separate
27+
// shared variable. Ideally, this value differs from the atomic "correct" value,
28+
// and the test can compare the two. In reality, some simpler operations (e.g.
29+
// those conducted through the ALU) can still end up with the correct answer,
30+
// even when performed nonatomically. These tests do not check the nonatomic
31+
// result, although it is still outputted to aid in debugging.
32+
//
33+
// Each test is performed on all relevant types.
34+
//
35+
//===----------------------------------------------------------------------===//
36+
//
37+
// This file tests atomic operations on floating point types with aligned
38+
// memory addresses.
39+
//
40+
// The types tested are: float, double.
41+
// The ops tested are: xchg, cmpxchg.
42+
//
43+
//===----------------------------------------------------------------------===//
44+
45+
#include <sys/stat.h>
46+
47+
#include <cstdio>
48+
#include <thread>
49+
#include <vector>
50+
51+
#include "numeric.h"
52+
#include "util.h"
53+
54+
// See numeric.h for an explanation of numeric xchg tests.
55+
template <typename T>
56+
void test_float_scalar_xchg() {
57+
static constexpr T val = V >> right_shift<T>();
58+
static constexpr T expected = val * kExpected;
59+
std::vector<std::thread> pool;
60+
61+
for (int model : atomic_exchange_models) {
62+
T afloat = 0;
63+
T ffloat = 0;
64+
for (int n = 0; n < kThreads; ++n) {
65+
pool.emplace_back(looper_numeric_xchg_atomic<T>, &afloat, model);
66+
}
67+
for (int n = 0; n < kThreads; ++n) {
68+
pool[n].join();
69+
}
70+
pool.clear();
71+
for (int n = 0; n < kThreads; ++n) {
72+
pool.emplace_back(looper_numeric_xchg_nonatomic<T>, std::ref(ffloat),
73+
model);
74+
}
75+
for (int n = 0; n < kThreads; ++n) {
76+
pool[n].join();
77+
}
78+
pool.clear();
79+
std::cout << "SCALAR (FETCH ADD): "
80+
<< "atomic: " << afloat << " "
81+
<< "nonatomic: " << ffloat << "\n";
82+
if (lt(afloat, ffloat) || afloat < expected * (1 - kEpsilon) ||
83+
afloat > expected * (1 + kEpsilon)) {
84+
fail();
85+
}
86+
}
87+
}
88+
89+
// See numeric.h for an explanation of numeric cmpxchg tests.
90+
template <typename T>
91+
void test_float_scalar_cmpxchg() {
92+
static constexpr T val = V >> right_shift<T>();
93+
static constexpr T expected = val * kExpected;
94+
std::vector<std::thread> pool;
95+
96+
for (int success_model : atomic_compare_exchange_models) {
97+
for (int fail_model : atomic_compare_exchange_models) {
98+
T afloat = 0;
99+
T ffloat = 0;
100+
for (int n = 0; n < kThreads; ++n) {
101+
pool.emplace_back(looper_numeric_cmpxchg<T>, &afloat, std::ref(ffloat),
102+
success_model, fail_model);
103+
}
104+
for (int n = 0; n < kThreads; ++n) {
105+
pool[n].join();
106+
}
107+
pool.clear();
108+
std::cout << "SCALAR (FETCH ADD): "
109+
<< "atomic: " << afloat << " "
110+
<< "nonatomic: " << ffloat << "\n";
111+
if (lt(afloat, ffloat) || afloat < expected * (1 - kEpsilon) ||
112+
afloat > expected * (1 + kEpsilon)) {
113+
fail();
114+
}
115+
}
116+
}
117+
}
118+
119+
void test_float() {
120+
printf("Testing float\n");
121+
test_float_scalar_xchg<float>();
122+
test_float_scalar_cmpxchg<float>();
123+
124+
printf("Testing double\n");
125+
test_float_scalar_xchg<double>();
126+
test_float_scalar_cmpxchg<double>();
127+
}
128+
129+
int main() {
130+
printf("%d threads; %d iterations each; total of %d\n", kThreads, kIterations,
131+
kExpected);
132+
133+
test_float();
134+
printf("PASSED\n");
135+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
10 threads; 1000000 iterations each; total of 10000000
2+
Testing float
3+
SCALAR (FETCH ADD): atomic: 6.50116e+08 nonatomic: 6.6e+07
4+
SCALAR (FETCH ADD): atomic: 6.41017e+08 nonatomic: 5.8511e+08
5+
Testing double
6+
SCALAR (FETCH ADD): atomic: 2.84596e+18 nonatomic: 2.84596e+17
7+
SCALAR (FETCH ADD): atomic: 2.84596e+18 nonatomic: 2.5513e+18
8+
PASSED
9+
exit 0

0 commit comments

Comments
 (0)