[libc-commits] [libc] 9243f99 - [libc] Add support for C++20 'atomic_ref' type (#132302)
via libc-commits
libc-commits at lists.llvm.org
Tue Mar 25 11:28:52 PDT 2025
Author: Joseph Huber
Date: 2025-03-25T13:28:49-05:00
New Revision: 9243f99d17c0165800fd1f2f92c5c975cf702414
URL: https://github.com/llvm/llvm-project/commit/9243f99d17c0165800fd1f2f92c5c975cf702414
DIFF: https://github.com/llvm/llvm-project/commit/9243f99d17c0165800fd1f2f92c5c975cf702414.diff
LOG: [libc] Add support for C++20 'atomic_ref' type (#132302)
Summary:
C++20 introduced an atomic reference type, which more easily wraps
around the standard way of dealing with atomics. Instead of a dedicated
type, it allows you to treat an existing allocation as atomic.
This has no users yet, but I'm hoping to use it when I start finalizing
my GPU allocation interface, as it will need to handle atomic values
in-place that can't be done with placement new. Hopefully this is small
enough that we can just keep it in-tree until it's needed, but I'll
accept holding it here until it has a user.
I added one extension to allow implicit conversion and CTAD.
Added:
Modified:
libc/src/__support/CPP/atomic.h
libc/test/src/__support/CPP/atomic_test.cpp
Removed:
################################################################################
diff --git a/libc/src/__support/CPP/atomic.h b/libc/src/__support/CPP/atomic.h
index 287dcac98fbb6..15242a131c63b 100644
--- a/libc/src/__support/CPP/atomic.h
+++ b/libc/src/__support/CPP/atomic.h
@@ -229,6 +229,154 @@ template <typename T> struct Atomic {
LIBC_INLINE void set(T rhs) { val = rhs; }
};
+template <typename T> struct AtomicRef {
+ static_assert(is_trivially_copyable_v<T> && is_copy_constructible_v<T> &&
+ is_move_constructible_v<T> && is_copy_assignable_v<T> &&
+ is_move_assignable_v<T>,
+ "AtomicRef<T> requires T to be trivially copyable, copy "
+ "constructible, move constructible, copy assignable, "
+ "and move assignable.");
+
+ static_assert(cpp::has_unique_object_representations_v<T>,
+ "AtomicRef<T> only supports types with unique object "
+ "representations.");
+
+private:
+ T *ptr;
+
+ LIBC_INLINE static int order(MemoryOrder mem_ord) {
+ return static_cast<int>(mem_ord);
+ }
+
+ LIBC_INLINE static int scope(MemoryScope mem_scope) {
+ return static_cast<int>(mem_scope);
+ }
+
+public:
+ // Constructor from T reference
+ LIBC_INLINE explicit constexpr AtomicRef(T &obj) : ptr(&obj) {}
+
+ // Non-standard Implicit conversion from T*
+ LIBC_INLINE constexpr AtomicRef(T *obj) : ptr(obj) {}
+
+ LIBC_INLINE AtomicRef(const AtomicRef &) = default;
+ LIBC_INLINE AtomicRef &operator=(const AtomicRef &) = default;
+
+ // Atomic load
+ LIBC_INLINE operator T() const { return load(); }
+
+ LIBC_INLINE T
+ load(MemoryOrder mem_ord = MemoryOrder::SEQ_CST,
+ [[maybe_unused]] MemoryScope mem_scope = MemoryScope::DEVICE) const {
+ T res;
+#if __has_builtin(__scoped_atomic_load)
+ __scoped_atomic_load(ptr, &res, order(mem_ord), scope(mem_scope));
+#else
+ __atomic_load(ptr, &res, order(mem_ord));
+#endif
+ return res;
+ }
+
+ // Atomic store
+ LIBC_INLINE T operator=(T rhs) const {
+ store(rhs);
+ return rhs;
+ }
+
+ LIBC_INLINE void
+ store(T rhs, MemoryOrder mem_ord = MemoryOrder::SEQ_CST,
+ [[maybe_unused]] MemoryScope mem_scope = MemoryScope::DEVICE) const {
+#if __has_builtin(__scoped_atomic_store)
+ __scoped_atomic_store(ptr, &rhs, order(mem_ord), scope(mem_scope));
+#else
+ __atomic_store(ptr, &rhs, order(mem_ord));
+#endif
+ }
+
+ // Atomic compare exchange (strong)
+ LIBC_INLINE bool compare_exchange_strong(
+ T &expected, T desired, MemoryOrder mem_ord = MemoryOrder::SEQ_CST,
+ [[maybe_unused]] MemoryScope mem_scope = MemoryScope::DEVICE) const {
+ return __atomic_compare_exchange(ptr, &expected, &desired, false,
+ order(mem_ord), order(mem_ord));
+ }
+
+ // Atomic compare exchange (strong, separate success/failure memory orders)
+ LIBC_INLINE bool compare_exchange_strong(
+ T &expected, T desired, MemoryOrder success_order,
+ MemoryOrder failure_order,
+ [[maybe_unused]] MemoryScope mem_scope = MemoryScope::DEVICE) const {
+ return __atomic_compare_exchange(ptr, &expected, &desired, false,
+ order(success_order),
+ order(failure_order));
+ }
+
+ // Atomic exchange
+ LIBC_INLINE T
+ exchange(T desired, MemoryOrder mem_ord = MemoryOrder::SEQ_CST,
+ [[maybe_unused]] MemoryScope mem_scope = MemoryScope::DEVICE) const {
+ T ret;
+#if __has_builtin(__scoped_atomic_exchange)
+ __scoped_atomic_exchange(ptr, &desired, &ret, order(mem_ord),
+ scope(mem_scope));
+#else
+ __atomic_exchange(ptr, &desired, &ret, order(mem_ord));
+#endif
+ return ret;
+ }
+
+ LIBC_INLINE T fetch_add(
+ T increment, MemoryOrder mem_ord = MemoryOrder::SEQ_CST,
+ [[maybe_unused]] MemoryScope mem_scope = MemoryScope::DEVICE) const {
+ static_assert(cpp::is_integral_v<T>, "T must be an integral type.");
+#if __has_builtin(__scoped_atomic_fetch_add)
+ return __scoped_atomic_fetch_add(ptr, increment, order(mem_ord),
+ scope(mem_scope));
+#else
+ return __atomic_fetch_add(ptr, increment, order(mem_ord));
+#endif
+ }
+
+ LIBC_INLINE T
+ fetch_or(T mask, MemoryOrder mem_ord = MemoryOrder::SEQ_CST,
+ [[maybe_unused]] MemoryScope mem_scope = MemoryScope::DEVICE) const {
+ static_assert(cpp::is_integral_v<T>, "T must be an integral type.");
+#if __has_builtin(__scoped_atomic_fetch_or)
+ return __scoped_atomic_fetch_or(ptr, mask, order(mem_ord),
+ scope(mem_scope));
+#else
+ return __atomic_fetch_or(ptr, mask, order(mem_ord));
+#endif
+ }
+
+ LIBC_INLINE T fetch_and(
+ T mask, MemoryOrder mem_ord = MemoryOrder::SEQ_CST,
+ [[maybe_unused]] MemoryScope mem_scope = MemoryScope::DEVICE) const {
+ static_assert(cpp::is_integral_v<T>, "T must be an integral type.");
+#if __has_builtin(__scoped_atomic_fetch_and)
+ return __scoped_atomic_fetch_and(ptr, mask, order(mem_ord),
+ scope(mem_scope));
+#else
+ return __atomic_fetch_and(ptr, mask, order(mem_ord));
+#endif
+ }
+
+ LIBC_INLINE T fetch_sub(
+ T decrement, MemoryOrder mem_ord = MemoryOrder::SEQ_CST,
+ [[maybe_unused]] MemoryScope mem_scope = MemoryScope::DEVICE) const {
+ static_assert(cpp::is_integral_v<T>, "T must be an integral type.");
+#if __has_builtin(__scoped_atomic_fetch_sub)
+ return __scoped_atomic_fetch_sub(ptr, decrement, order(mem_ord),
+ scope(mem_scope));
+#else
+ return __atomic_fetch_sub(ptr, decrement, order(mem_ord));
+#endif
+ }
+};
+
+// Permit CTAD when generating an atomic reference.
+template <typename T> AtomicRef(T &) -> AtomicRef<T>;
+
// Issue a thread fence with the given memory ordering.
LIBC_INLINE void atomic_thread_fence(
MemoryOrder mem_ord,
diff --git a/libc/test/src/__support/CPP/atomic_test.cpp b/libc/test/src/__support/CPP/atomic_test.cpp
index 5c3f60e9a68cd..7b2a929bdb004 100644
--- a/libc/test/src/__support/CPP/atomic_test.cpp
+++ b/libc/test/src/__support/CPP/atomic_test.cpp
@@ -50,3 +50,18 @@ TEST(LlvmLibcAtomicTest, TrivialCompositeData) {
ASSERT_EQ(old.a, 'a');
ASSERT_EQ(old.b, 'b');
}
+
+TEST(LlvmLibcAtomicTest, AtomicRefTest) {
+ int val = 123;
+ LIBC_NAMESPACE::cpp::AtomicRef aint(val);
+ ASSERT_EQ(aint.load(LIBC_NAMESPACE::cpp::MemoryOrder::RELAXED), 123);
+ ASSERT_EQ(aint.fetch_add(1, LIBC_NAMESPACE::cpp::MemoryOrder::RELAXED), 123);
+ aint = 1234;
+ ASSERT_EQ(aint.load(LIBC_NAMESPACE::cpp::MemoryOrder::RELAXED), 1234);
+
+ // Test the implicit construction from pointer.
+ auto fn = [](LIBC_NAMESPACE::cpp::AtomicRef<int> aint) -> int {
+ return aint.load(LIBC_NAMESPACE::cpp::MemoryOrder::RELAXED);
+ };
+ ASSERT_EQ(fn(&val), 1234);
+}
More information about the libc-commits
mailing list