llvm-project/libcxx/test/std/atomics/atomics.ref/is_always_lock_free.pass.cpp
Damien L-G 59ca618e3b
[libc++] Increase atomic_ref's required alignment for small types (#99654)
This patch increases the alignment requirement for std::atomic_ref
such that we can guarantee lockfree operations more often. Specifically,
we require types that are 1, 2, 4, 8, or 16 bytes in size to be aligned
to at least their size to be used with std::atomic_ref.

This is the case for most types, however a notable exception is
`long long` on x86, which is 8 bytes in length but has an alignment
of 4.

As a result of this patch, one has to be more careful about the
alignment of objects used with std::atomic_ref. Failure to provide
a properly-aligned object to std::atomic_ref is a precondition 
violation and is technically UB. On the flipside, this allows us
to provide an atomic_ref that is actually lockfree more often, 
which is an important QOI property.

More information in the discussion at https://github.com/llvm/llvm-project/pull/99570#issuecomment-2237668661.

Co-authored-by: Louis Dionne <ldionne.2@gmail.com>
2024-08-01 10:39:27 -04:00

98 lines
3.7 KiB
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
//
//===----------------------------------------------------------------------===//
// UNSUPPORTED: c++03, c++11, c++14, c++17
// <atomic>
//
// template <class T>
// class atomic_ref;
//
// static constexpr bool is_always_lock_free;
// bool is_lock_free() const noexcept;
#include <atomic>
#include <cassert>
#include <concepts>
#include "test_macros.h"
#include "atomic_helpers.h"
template <typename T>
void check_always_lock_free(std::atomic_ref<T> const& a) {
using InfoT = LockFreeStatusInfo<T>;
constexpr std::same_as<const bool> decltype(auto) is_always_lock_free = std::atomic_ref<T>::is_always_lock_free;
// If we know the status of T for sure, validate the exact result of the function.
if constexpr (InfoT::status_known) {
constexpr LockFreeStatus known_status = InfoT::value;
if constexpr (known_status == LockFreeStatus::always) {
static_assert(is_always_lock_free, "is_always_lock_free is inconsistent with known lock-free status");
assert(a.is_lock_free() && "is_lock_free() is inconsistent with known lock-free status");
} else if constexpr (known_status == LockFreeStatus::never) {
static_assert(!is_always_lock_free, "is_always_lock_free is inconsistent with known lock-free status");
assert(!a.is_lock_free() && "is_lock_free() is inconsistent with known lock-free status");
} else {
assert(a.is_lock_free() || !a.is_lock_free()); // This is kinda dumb, but we might as well call the function once.
}
}
// In all cases, also sanity-check it based on the implication always-lock-free => lock-free.
if (is_always_lock_free) {
std::same_as<bool> decltype(auto) is_lock_free = a.is_lock_free();
assert(is_lock_free);
}
ASSERT_NOEXCEPT(a.is_lock_free());
}
#define CHECK_ALWAYS_LOCK_FREE(T) \
do { \
typedef T type; \
alignas(std::atomic_ref<type>::required_alignment) type obj{}; \
std::atomic_ref<type> a(obj); \
check_always_lock_free(a); \
} while (0)
void test() {
char c = 'x';
check_always_lock_free(std::atomic_ref<char>(c));
int i = 0;
check_always_lock_free(std::atomic_ref<int>(i));
float f = 0.f;
check_always_lock_free(std::atomic_ref<float>(f));
int* p = &i;
check_always_lock_free(std::atomic_ref<int*>(p));
CHECK_ALWAYS_LOCK_FREE(struct Empty{});
CHECK_ALWAYS_LOCK_FREE(struct OneInt { int i; });
CHECK_ALWAYS_LOCK_FREE(struct IntArr2 { int i[2]; });
CHECK_ALWAYS_LOCK_FREE(struct FloatArr3 { float i[3]; });
CHECK_ALWAYS_LOCK_FREE(struct LLIArr2 { long long int i[2]; });
CHECK_ALWAYS_LOCK_FREE(struct LLIArr4 { long long int i[4]; });
CHECK_ALWAYS_LOCK_FREE(struct LLIArr8 { long long int i[8]; });
CHECK_ALWAYS_LOCK_FREE(struct LLIArr16 { long long int i[16]; });
CHECK_ALWAYS_LOCK_FREE(struct Padding {
char c; /* padding */
long long int i;
});
CHECK_ALWAYS_LOCK_FREE(union IntFloat {
int i;
float f;
});
CHECK_ALWAYS_LOCK_FREE(enum class CharEnumClass : char{foo});
}
int main(int, char**) {
test();
return 0;
}