//===----------------------------------------------------------------------===//
//
// 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
// XFAIL: !has-64-bit-atomics
// bool compare_exchange_strong(T& expected, T desired,
// memory_order success, memory_order failure) volatile noexcept;
// bool compare_exchange_strong(T& expected, T desired,
// memory_order success, memory_order failure) noexcept;
// bool compare_exchange_strong(T& expected, T desired,
// memory_order order = memory_order::seq_cst) volatile noexcept;
// bool compare_exchange_strong(T& expected, T desired,
// memory_order order = memory_order::seq_cst) noexcept;
#include <atomic>
#include <cassert>
#include <concepts>
#include <type_traits>
#include "test_helper.h"
#include "test_macros.h"
template <class T, class... Args>
concept HasVolatileCompareExchangeStrong =
requires(volatile std::atomic<T>& a, T t, Args... args) { a.compare_exchange_strong(t, t, args...); };
template <class T, template <class> class MaybeVolatile, class... Args>
concept HasNoexceptCompareExchangeStrong = requires(MaybeVolatile<std::atomic<T>>& a, T t, Args... args) {
{ a.compare_exchange_strong(t, t, args...) } noexcept;
};
template <class T, template <class> class MaybeVolatile = std::type_identity_t, class... MemoryOrder>
void testBasic(MemoryOrder... memory_order) {
// Uncomment the test after P1831R1 is implemented
// static_assert(HasVolatileCompareExchangeStrong<T, MemoryOrder...> == std::atomic<T>::is_always_lock_free);
static_assert(HasNoexceptCompareExchangeStrong<T, MaybeVolatile, MemoryOrder...>);
// compare pass
{
MaybeVolatile<std::atomic<T>> a(T(1.2));
T expected(T(1.2));
const T desired(T(2.3));
std::same_as<bool> decltype(auto) r = a.compare_exchange_strong(expected, desired, memory_order...);
assert(r);
assert(a.load() == desired);
assert(expected == T(1.2));
}
// compare fail
{
MaybeVolatile<std::atomic<T>> a(T(1.2));
T expected(1.5);
const T desired(T(2.3));
std::same_as<bool> decltype(auto) r = a.compare_exchange_strong(expected, desired, memory_order...);
assert(!r);
assert(a.load() == T(1.2));
assert(expected == T(1.2));
}
}
template <class T, template <class> class MaybeVolatile = std::type_identity_t>
void test_impl() {
testBasic<T, MaybeVolatile>();
testBasic<T, MaybeVolatile>(std::memory_order::relaxed);
testBasic<T, MaybeVolatile>(std::memory_order::relaxed, std::memory_order_relaxed);
// test success memory order release
{
auto store = [](MaybeVolatile<std::atomic<T>>& x, T old_val, T new_val) {
auto r = x.compare_exchange_strong(old_val, new_val, std::memory_order::release, std::memory_order_relaxed);
assert(r);
};
auto load = [](MaybeVolatile<std::atomic<T>>& x) { return x.load(std::memory_order::acquire); };
test_acquire_release<T, MaybeVolatile>(store, load);
auto store_one_arg = [](MaybeVolatile<std::atomic<T>>& x, T old_val, T new_val) {
auto r = x.compare_exchange_strong(old_val, new_val, std::memory_order::release);
assert(r);
};
test_acquire_release<T, MaybeVolatile>(store_one_arg, load);
}
// test success memory order acquire
{
auto store = [](MaybeVolatile<std::atomic<T>>& x, T, T new_val) { x.store(new_val, std::memory_order::release); };
auto load = [](MaybeVolatile<std::atomic<T>>& x) {
auto val = x.load(std::memory_order::relaxed);
while (!x.compare_exchange_strong(val, val, std::memory_order::acquire, std::memory_order_relaxed)) {
}
return val;
};
test_acquire_release<T, MaybeVolatile>(store, load);
auto load_one_arg = [](MaybeVolatile<std::atomic<T>>& x) {
auto val = x.load(std::memory_order::relaxed);
while (!x.compare_exchange_strong(val, val, std::memory_order::acquire)) {
}
return val;
};
test_acquire_release<T, MaybeVolatile>(store, load_one_arg);
}
// test success memory order acq_rel
{
auto store = [](MaybeVolatile<std::atomic<T>>& x, T old_val, T new_val) {
auto r = x.compare_exchange_strong(old_val, new_val, std::memory_order::acq_rel, std::memory_order_relaxed);
assert(r);
};
auto load = [](MaybeVolatile<std::atomic<T>>& x) {
auto val = x.load(std::memory_order::relaxed);
while (!x.compare_exchange_strong(val, val, std::memory_order::acq_rel, std::memory_order_relaxed)) {
}
return val;
};
test_acquire_release<T, MaybeVolatile>(store, load);
auto store_one_arg = [](MaybeVolatile<std::atomic<T>>& x, T old_val, T new_val) {
auto r = x.compare_exchange_strong(old_val, new_val, std::memory_order::acq_rel);
assert(r);
};
auto load_one_arg = [](MaybeVolatile<std::atomic<T>>& x) {
auto val = x.load(std::memory_order::relaxed);
while (!x.compare_exchange_strong(val, val, std::memory_order::acq_rel)) {
}
return val;
};
test_acquire_release<T, MaybeVolatile>(store_one_arg, load_one_arg);
}
// test success memory seq_cst
{
auto store = [](MaybeVolatile<std::atomic<T>>& x, T old_val, T new_val) {
auto r = x.compare_exchange_strong(old_val, new_val, std::memory_order::seq_cst, std::memory_order_relaxed);
assert(r);
};
auto load = [](MaybeVolatile<std::atomic<T>>& x) {
auto val = x.load(std::memory_order::relaxed);
while (!x.compare_exchange_strong(val, val, std::memory_order::seq_cst, std::memory_order_relaxed)) {
}
return val;
};
test_seq_cst<T, MaybeVolatile>(store, load);
auto store_one_arg = [](MaybeVolatile<std::atomic<T>>& x, T old_val, T new_val) {
auto r = x.compare_exchange_strong(old_val, new_val, std::memory_order::seq_cst);
assert(r);
};
auto load_one_arg = [](MaybeVolatile<std::atomic<T>>& x) {
auto val = x.load(std::memory_order::relaxed);
while (!x.compare_exchange_strong(val, val, std::memory_order::seq_cst)) {
}
return val;
};
test_seq_cst<T, MaybeVolatile>(store_one_arg, load_one_arg);
}
// test fail memory order acquire
{
auto store = [](MaybeVolatile<std::atomic<T>>& x, T, T new_val) { x.store(new_val, std::memory_order::release); };
auto load = [](MaybeVolatile<std::atomic<T>>& x) {
auto result = x.load(std::memory_order::relaxed);
T unexpected(T(-9999.99));
bool r = x.compare_exchange_strong(unexpected, unexpected, std::memory_order_relaxed, std::memory_order_acquire);
assert(!r);
return result;
};
test_acquire_release<T, MaybeVolatile>(store, load);
auto load_one_arg = [](MaybeVolatile<std::atomic<T>>& x) {
auto result = x.load(std::memory_order::relaxed);
T unexpected(T(-9999.99));
bool r = x.compare_exchange_strong(unexpected, unexpected, std::memory_order_acquire);
assert(!r);
return result;
};
test_acquire_release<T, MaybeVolatile>(store, load_one_arg);
// acq_rel replaced by acquire
auto load_one_arg_acq_rel = [](MaybeVolatile<std::atomic<T>>& x) {
auto result = x.load(std::memory_order::relaxed);
T unexpected(T(-9999.99));
bool r = x.compare_exchange_strong(unexpected, unexpected, std::memory_order_acq_rel);
assert(!r);
return result;
};
test_acquire_release<T, MaybeVolatile>(store, load_one_arg_acq_rel);
}
// test fail memory order seq_cst
{
auto store = [](MaybeVolatile<std::atomic<T>>& x, T, T new_val) { x.store(new_val, std::memory_order::seq_cst); };
auto load = [](MaybeVolatile<std::atomic<T>>& x) {
auto result = x.load(std::memory_order::relaxed);
T unexpected(T(-9999.99));
bool r = x.compare_exchange_strong(unexpected, unexpected, std::memory_order_relaxed, std::memory_order::seq_cst);
assert(!r);
return result;
};
test_seq_cst<T, MaybeVolatile>(store, load);
}
}
template <class T>
void test() {
test_impl<T>();
if constexpr (std::atomic<T>::is_always_lock_free) {
test_impl<T, std::add_volatile_t>();
}
}
int main(int, char**) {
test<float>();
test<double>();
// TODO https://github.com/llvm/llvm-project/issues/47978
// test<long double>();
return 0;
}