summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Bertalan <dani@danielbertalan.dev>2021-06-19 16:28:42 +0200
committerAli Mohammad Pur <Ali.mpfard@gmail.com>2021-06-24 17:35:49 +0430
commitd6138df490389b313906f07c34ebdbc4b98183dc (patch)
treec2eed6de2a71a9aaacef385ed65bfbfeff34e1a9
parent985adcca38a4c5bbca259c9304433aca77f4b005 (diff)
downloadserenity-d6138df490389b313906f07c34ebdbc4b98183dc.zip
AK: Specialize `Atomic<Integral>` for clang compatibility
While Clang claims to implement GCC's atomics libcall API, a small incompatibility caused our builds to fail on Clang. Clang requires requires the operands to its fixed-size functions to be integer types, while GCC will take any type with the same size and alignment as the various integer primitives. This was problematic, as atomic `enum class`es would not compile. Furthermore, Clang does not like if only one operand pointer is marked volatile. Because it only affects the standalone atomic functions, that will be fixed in a later commit. As an added benefit, the code is more type-safe, as it won't let us perform arithmetic on non-integer types. Types with overloaded arithmetic types won't cause unexpected behavior anymore. The constructors for the various atomic types can now be used in constant expressions.
-rw-r--r--AK/Atomic.h75
1 files changed, 72 insertions, 3 deletions
diff --git a/AK/Atomic.h b/AK/Atomic.h
index 3d71844172..61a8e41385 100644
--- a/AK/Atomic.h
+++ b/AK/Atomic.h
@@ -6,6 +6,7 @@
#pragma once
+#include <AK/Concepts.h>
#include <AK/Platform.h>
#include <AK/Types.h>
@@ -143,7 +144,76 @@ public:
Atomic(const Atomic&) = delete;
Atomic(Atomic&&) = delete;
- Atomic(T val) noexcept
+ constexpr Atomic(T val) noexcept
+ : m_value(val)
+ {
+ }
+
+ volatile T* ptr() noexcept
+ {
+ return &m_value;
+ }
+
+ T exchange(T desired, MemoryOrder order = DefaultMemoryOrder) volatile noexcept
+ {
+ // We use this hack to prevent unnecessary initialization, even if T has a default constructor.
+ // NOTE: Will need to investigate if it pessimizes the generated assembly.
+ alignas(T) u8 buffer[sizeof(T)];
+ T* ret = reinterpret_cast<T*>(buffer);
+ __atomic_exchange(&m_value, &desired, ret, order);
+ return *ret;
+ }
+
+ [[nodiscard]] bool compare_exchange_strong(T& expected, T desired, MemoryOrder order = DefaultMemoryOrder) volatile noexcept
+ {
+ if (order == memory_order_acq_rel || order == memory_order_release)
+ return __atomic_compare_exchange(&m_value, &expected, &desired, false, memory_order_release, memory_order_acquire);
+ else
+ return __atomic_compare_exchange(&m_value, &expected, &desired, false, order, order);
+ }
+
+ ALWAYS_INLINE operator T() const volatile noexcept
+ {
+ return load();
+ }
+
+ ALWAYS_INLINE T load(MemoryOrder order = DefaultMemoryOrder) const volatile noexcept
+ {
+ alignas(T) u8 buffer[sizeof(T)];
+ T* ret = reinterpret_cast<T*>(buffer);
+ __atomic_load(&m_value, ret, order);
+ return *ret;
+ }
+
+ ALWAYS_INLINE T operator=(T desired) volatile noexcept
+ {
+ store(desired);
+ return desired;
+ }
+
+ ALWAYS_INLINE void store(T desired, MemoryOrder order = DefaultMemoryOrder) volatile noexcept
+ {
+ __atomic_store(&m_value, &desired, order);
+ }
+
+ ALWAYS_INLINE bool is_lock_free() const volatile noexcept
+ {
+ return __atomic_is_lock_free(sizeof(m_value), &m_value);
+ }
+};
+
+template<Integral T, MemoryOrder DefaultMemoryOrder>
+class Atomic<T, DefaultMemoryOrder> {
+ T m_value { 0 };
+
+public:
+ Atomic() noexcept = default;
+ Atomic& operator=(const Atomic&) volatile = delete;
+ Atomic& operator=(Atomic&&) volatile = delete;
+ Atomic(const Atomic&) = delete;
+ Atomic(Atomic&&) = delete;
+
+ constexpr Atomic(T val) noexcept
: m_value(val)
{
}
@@ -274,7 +344,7 @@ public:
Atomic(const Atomic&) = delete;
Atomic(Atomic&&) = delete;
- Atomic(T* val) noexcept
+ constexpr Atomic(T* val) noexcept
: m_value(val)
{
}
@@ -363,7 +433,6 @@ public:
return __atomic_is_lock_free(sizeof(m_value), &m_value);
}
};
-
}
using AK::Atomic;