Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SingleSource/Atomic] Add preliminary tests for atomic builtins. #94

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
16 changes: 16 additions & 0 deletions SingleSource/UnitTests/Atomic/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Link the Clang built libatomic.
execute_process(COMMAND ${CMAKE_C_COMPILER} --print-file-name=libclang_rt.atomic.so
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That library isn't built by default, so almost nobody will have a copy. I assume we need to have some conditionals here to skip testing if the library isn't present?

Potentially we should additionally integration-test against -latomic (even if that comes via GCC), since that's what real users actually specify.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. I'm not great with CMake -- what are better ways to do what I have here?

OUTPUT_VARIABLE _path_to_libatomic
OUTPUT_STRIP_TRAILING_WHITESPACE)
if(NOT _path_to_libatomic STREQUAL "libclang_rt.atomic.so")
get_directory_property(CURRENT_LINK_OPTIONS LINK_OPTIONS)
get_filename_component(_libatomic_dir ${_path_to_libatomic} DIRECTORY)
add_link_options("LINKER:${_path_to_libatomic},-rpath=${_libatomic_dir}")

llvm_singlesource(PREFIX "libclang_rt-atomic-")
set_directory_properties(LINK_OPTIONS ${CURRENT_LINK_OPTIONS})
endif()

add_link_options("LINKER:-latomic")
llvm_singlesource(PREFIX "latomic-")

30 changes: 30 additions & 0 deletions SingleSource/UnitTests/Atomic/README.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
Atomic runtime library tests

========

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 is performed on all relevant types.

========

Future test writers should be aware that the set of all tests that appear to
test atomicity is not the set of all tests that test atomicity. In fact, tests
that may test atomicity on one processor may not test atomicity on a different
processor.

As such, test writers are encouraged to write nonatomic variants of their tests,
and verify that they pass in a variety of scenarios.
78 changes: 78 additions & 0 deletions SingleSource/UnitTests/Atomic/big_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
//===--- 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
//
//===----------------------------------------------------------------------===//
//
// This file tests atomic operations on big objects with aligned memory
// addresses.
//
// The types tested are: bigs.
// The ops tested are: cmpxchg.
// TODO: Test load/store, xchg.
//
// Please read the README before contributing.
//
//===----------------------------------------------------------------------===//

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

#include "util.h"

// V >> 56 = 66, so to prevent 32-bit overflow, kExpected must be less than
// 2^31 / 66 = 32 x 10^6.
#ifdef SMALL_PROBLEM_SIZE
static constexpr int kIterations = 1'000'000;
#else
static constexpr int kIterations = 3'000'000;
#endif
static constexpr int kExpected = kThreads * kIterations;
static constexpr int kBigSize = 10;
struct big_t {
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_t *abig, int success_model, int fail_model) {
for (int n = 0; n < kIterations; ++n) {
big_t 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));
}
}

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_t abig = {};
for (int n = 0; n < kThreads; ++n)
pool.emplace_back(looper_big_cmpxchg, &abig, success_model, fail_model);
for (int n = 0; n < kThreads; ++n)
pool[n].join();
pool.clear();
for (int n = 0; n < kBigSize; ++n)
if (abig.v[n] != kExpected)
fail();
}
}
}

void test_big() {
std::cout << "Testing big\n";
test_big_cmpxchg();
}

int main() {
test_big();
std::cout << "PASSED\n";
}
3 changes: 3 additions & 0 deletions SingleSource/UnitTests/Atomic/big_test.reference_output
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Testing big
PASSED
exit 0
109 changes: 109 additions & 0 deletions SingleSource/UnitTests/Atomic/float_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
//===--- 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
//
//===----------------------------------------------------------------------===//
//
// This file tests atomic operations on floating point types with aligned
// memory addresses.
//
// The types tested are: float, double, float128.
// The ops tested are: xchg, cmpxchg.
//
// Please read the README before contributing.
//
//===----------------------------------------------------------------------===//

#include <sys/stat.h>

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

#include "util.h"

// There are 23-bits in the mantissa of a single-precision float.
// Therefore, kExpected cannot exceed 2^24.
static constexpr int kIterations = 1'500'000;
static constexpr int kExpected = kThreads * kIterations;

// See int_aligned_test.cc for an explanation of xchg tests.
template <typename T>
void looper_float_scalar_xchg(T *afloat, int model) {
__int128_t error = 0;
T next = *afloat + 1;
T result;
for (int n = 0; n < kIterations; ++n) {
__atomic_exchange(afloat, &next, &result, model);
error +=
static_cast<__int128_t>(next) - static_cast<__int128_t>(result + 1);
next = result + 1;
}
__atomic_fetch_sub(afloat, static_cast<T>(error), model);
}

template <typename T>
void test_float_scalar_xchg() {
std::vector<std::thread> pool;
for (int model : atomic_exchange_models) {
T afloat = 0;
for (int n = 0; n < kThreads; ++n)
pool.emplace_back(looper_float_scalar_xchg<T>, &afloat, model);
for (int n = 0; n < kThreads; ++n)
pool[n].join();
pool.clear();
if (afloat != kExpected)
fail();
}
}

// See int_aligned_test.cc for an explanation of cmpxchg tests.
template <typename T>
void looper_float_scalar_cmpxchg(T *afloat, int success_model, int fail_model) {
for (int n = 0; n < kIterations; ++n) {
T desired, expected = 0;
do {
desired = expected + 1;
} while (!__atomic_compare_exchange(afloat, &expected, &desired, true,
success_model, fail_model));
}
}

template <typename T>
void test_float_scalar_cmpxchg() {
std::vector<std::thread> pool;
for (int success_model : atomic_compare_exchange_models) {
for (int fail_model : atomic_compare_exchange_models) {
T afloat = 0;
for (int n = 0; n < kThreads; ++n)
pool.emplace_back(looper_float_scalar_cmpxchg<T>, &afloat,
success_model, fail_model);
for (int n = 0; n < kThreads; ++n)
pool[n].join();
pool.clear();
if (afloat != kExpected)
fail();
}
}
}

void test_floating_point() {
std::cout << "Testing float\n";
test_float_scalar_xchg<float>();
test_float_scalar_cmpxchg<float>();

std::cout << "Testing double\n";
test_float_scalar_xchg<double>();
test_float_scalar_cmpxchg<double>();

std::cout << "Testing float128\n";
test_float_scalar_xchg<__float128>();
test_float_scalar_cmpxchg<__float128>();
}

int main() {
test_floating_point();
std::cout << "PASSED\n";
}
5 changes: 5 additions & 0 deletions SingleSource/UnitTests/Atomic/float_test.reference_output
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Testing float
Testing double
Testing float128
PASSED
exit 0
Loading