
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>
98 lines
3.7 KiB
C++
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;
|
|
}
|