Skip to content

Commit

Permalink
[SingleSource/Atomic] Add preliminary libatomic tests.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Logikable committed Jan 19, 2024
1 parent 9ca97f5 commit 317d542
Show file tree
Hide file tree
Showing 15 changed files with 1,811 additions and 1 deletion.
2 changes: 1 addition & 1 deletion MultiSource/UnitTests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
add_subdirectory(C++11)
add_subdirectory(Float)
if(ARCH STREQUAL "Mips")
add_subdirectory(Mips)
endif()
add_subdirectory(Float)
8 changes: 8 additions & 0 deletions SingleSource/UnitTests/Atomic/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
set(FP_TOLERANCE 1000000)
execute_process(COMMAND ${CMAKE_C_COMPILER} --print-file-name=libclang_rt.atomic.so
OUTPUT_VARIABLE _path_to_libatomic
OUTPUT_STRIP_TRAILING_WHITESPACE)
get_filename_component(_libatomic_dir ${_path_to_libatomic} DIRECTORY)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${_path_to_libatomic} -Wl,-rpath=${_libatomic_dir}")

llvm_singlesource()
122 changes: 122 additions & 0 deletions SingleSource/UnitTests/Atomic/big_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
//===--- big_test.cc -- Testing big (17+ byte) objects ------------ C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// The following text is present in each test file:
//
// These tests aim to capture real-world multithreaded use cases of atomic
// builtins. Each test focuses on a single atomic operation. Those using
// multiple operations can be compared with other tests using the same
// operations to isolate bugs to a single atomic operation.
//
// Each test consists of a "looper" body and a test script. The test script
// instantiates 10 threads, each running the looper. The loopers contend the
// same memory address, performing atomic operations on it. Each looper executes
// 10^6 times for a total of 10^7 operations. The resultant value in the
// contended pointer is compared against a closed-form solution. It's expected
// that the two values equate.
//
// For example, a looper that increments the shared pointer is expected to end
// up with a value of 10^7. If its final value is not that, the test fails.
//
// Each test also tests the corresponding nonatomic operation with a separate
// shared variable. Ideally, this value differs from the atomic "correct" value,
// and the test can compare the two. In reality, some simpler operations (e.g.
// those conducted through the ALU) can still end up with the correct answer,
// even when performed nonatomically. These tests do not check the nonatomic
// result, although it is still outputted to aid in debugging.
//
// Each test is performed on all relevant types.
//
//===----------------------------------------------------------------------===//
//
// This file tests atomic operations on big objects with aligned memory
// addresses.
//
// The types tested are: bigs.
// The ops tested are: xchg, cmpxchg.
//
//===----------------------------------------------------------------------===//

#include <cstdio>
#include <iostream>
#include <thread>
#include <vector>

#include "util.h"

static constexpr int kBigSize = 10;
struct big {
int v[kBigSize];
};

// The big struct cmpxchg test is identical to the numeric cmpxchg test, except
// each element of the underlying array is incremented.
void looper_big_cmpxchg(big *abig, big &bbig, int success_model,
int fail_model) {
for (int n = 0; n < kIterations; ++n) {
big desired, expected = {};
do {
desired = expected;
for (int k = 0; k < kBigSize; ++k) {
desired.v[k]++;
}
} while (!__atomic_compare_exchange(abig, &expected, &desired, true,
success_model, fail_model));
for (int k = 0; k < kBigSize; ++k) {
bbig.v[k]++;
}
}
}

void test_big_cmpxchg() {
std::vector<std::thread> pool;

for (int success_model : atomic_compare_exchange_models) {
for (int fail_model : atomic_compare_exchange_models) {
big abig = {};
big bbig = {};
for (int n = 0; n < kThreads; ++n) {
pool.emplace_back(looper_big_cmpxchg, &abig, std::ref(bbig),
success_model, fail_model);
}
for (int n = 0; n < kThreads; ++n) {
pool[n].join();
}
pool.clear();
std::cout << "CMPXCHG: ";
std::cout << "atomic: ";
for (int n = 0; n < kBigSize; ++n) {
std::cout << abig.v[n] << " ";
}
std::cout << "\n ";
std::cout << "nonatomic: ";
for (int n = 0; n < kBigSize; ++n) {
std::cout << bbig.v[n] << " ";
}
std::cout << "\n";
for (int n = 0; n < kBigSize; ++n) {
if (lt(abig.v[n], bbig.v[n]) || abig.v[n] != kExpected) {
fail();
}
}
}
}
}

void test_big() {
printf("Testing big\n");
test_big_cmpxchg();
}

int main() {
printf("%d threads; %d iterations each; total of %d\n", kThreads, kIterations,
kExpected);

test_big();
printf("PASSED\n");
}
6 changes: 6 additions & 0 deletions SingleSource/UnitTests/Atomic/big_test.reference_output
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
10 threads; 1000000 iterations each; total of 10000000
Testing big
CMPXCHG: atomic: 10000000 10000000 10000000 10000000 10000000 10000000 10000000 10000000 10000000 10000000
nonatomic: 9970397 9970397 9970397 9970397 9972269 9972269 9972269 9972269 9941180 9941180
PASSED
exit 0
135 changes: 135 additions & 0 deletions SingleSource/UnitTests/Atomic/float_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
//===--- float_test.cc -- Testing aligned floating point numbers -- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// The following text is present in each test file:
//
// These tests aim to capture real-world multithreaded use cases of atomic
// builtins. Each test focuses on a single atomic operation. Those using
// multiple operations can be compared with other tests using the same
// operations to isolate bugs to a single atomic operation.
//
// Each test consists of a "looper" body and a test script. The test script
// instantiates 10 threads, each running the looper. The loopers contend the
// same memory address, performing atomic operations on it. Each looper executes
// 10^6 times for a total of 10^7 operations. The resultant value in the
// contended pointer is compared against a closed-form solution. It's expected
// that the two values equate.
//
// For example, a looper that increments the shared pointer is expected to end
// up with a value of 10^7. If its final value is not that, the test fails.
//
// Each test also tests the corresponding nonatomic operation with a separate
// shared variable. Ideally, this value differs from the atomic "correct" value,
// and the test can compare the two. In reality, some simpler operations (e.g.
// those conducted through the ALU) can still end up with the correct answer,
// even when performed nonatomically. These tests do not check the nonatomic
// result, although it is still outputted to aid in debugging.
//
// Each test is performed on all relevant types.
//
//===----------------------------------------------------------------------===//
//
// This file tests atomic operations on floating point types with aligned
// memory addresses.
//
// The types tested are: float, double.
// The ops tested are: xchg, cmpxchg.
//
//===----------------------------------------------------------------------===//

#include <sys/stat.h>

#include <cstdio>
#include <thread>
#include <vector>

#include "numeric.h"
#include "util.h"

// See numeric.h for an explanation of numeric xchg tests.
template <typename T>
void test_float_scalar_xchg() {
static constexpr T val = V >> right_shift<T>();
static constexpr T expected = val * kExpected;
std::vector<std::thread> pool;

for (int model : atomic_exchange_models) {
T afloat = 0;
T ffloat = 0;
for (int n = 0; n < kThreads; ++n) {
pool.emplace_back(looper_numeric_xchg_atomic<T>, &afloat, model);
}
for (int n = 0; n < kThreads; ++n) {
pool[n].join();
}
pool.clear();
for (int n = 0; n < kThreads; ++n) {
pool.emplace_back(looper_numeric_xchg_nonatomic<T>, std::ref(ffloat),
model);
}
for (int n = 0; n < kThreads; ++n) {
pool[n].join();
}
pool.clear();
std::cout << "SCALAR (FETCH ADD): "
<< "atomic: " << afloat << " "
<< "nonatomic: " << ffloat << "\n";
if (lt(afloat, ffloat) || afloat < expected * (1 - kEpsilon) ||
afloat > expected * (1 + kEpsilon)) {
fail();
}
}
}

// See numeric.h for an explanation of numeric cmpxchg tests.
template <typename T>
void test_float_scalar_cmpxchg() {
static constexpr T val = V >> right_shift<T>();
static constexpr T expected = val * kExpected;
std::vector<std::thread> pool;

for (int success_model : atomic_compare_exchange_models) {
for (int fail_model : atomic_compare_exchange_models) {
T afloat = 0;
T ffloat = 0;
for (int n = 0; n < kThreads; ++n) {
pool.emplace_back(looper_numeric_cmpxchg<T>, &afloat, std::ref(ffloat),
success_model, fail_model);
}
for (int n = 0; n < kThreads; ++n) {
pool[n].join();
}
pool.clear();
std::cout << "SCALAR (FETCH ADD): "
<< "atomic: " << afloat << " "
<< "nonatomic: " << ffloat << "\n";
if (lt(afloat, ffloat) || afloat < expected * (1 - kEpsilon) ||
afloat > expected * (1 + kEpsilon)) {
fail();
}
}
}
}

void test_float() {
printf("Testing float\n");
test_float_scalar_xchg<float>();
test_float_scalar_cmpxchg<float>();

printf("Testing double\n");
test_float_scalar_xchg<double>();
test_float_scalar_cmpxchg<double>();
}

int main() {
printf("%d threads; %d iterations each; total of %d\n", kThreads, kIterations,
kExpected);

test_float();
printf("PASSED\n");
}
9 changes: 9 additions & 0 deletions SingleSource/UnitTests/Atomic/float_test.reference_output
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
10 threads; 1000000 iterations each; total of 10000000
Testing float
SCALAR (FETCH ADD): atomic: 6.50116e+08 nonatomic: 6.6e+07
SCALAR (FETCH ADD): atomic: 6.41017e+08 nonatomic: 5.8511e+08
Testing double
SCALAR (FETCH ADD): atomic: 2.84596e+18 nonatomic: 2.84596e+17
SCALAR (FETCH ADD): atomic: 2.84596e+18 nonatomic: 2.5513e+18
PASSED
exit 0
Loading

0 comments on commit 317d542

Please sign in to comment.