SuperMarioOdysseyOnline/include/sead/thread/seadAtomic.h
2022-06-16 14:33:18 -07:00

287 lines
8.2 KiB
C++

#pragma once
#ifdef NNSDK
#include <atomic>
#endif
namespace sead
{
struct AtomicDirectInitTag
{
};
template <class T>
struct AtomicBase
{
public:
AtomicBase(T value = {}); // NOLINT(google-explicit-constructor)
/// Directly initialises the underlying atomic with the specified value.
/// Note that initialisation is not atomic.
AtomicBase(AtomicDirectInitTag, T value);
AtomicBase(const AtomicBase& rhs) { *this = rhs; }
operator T() const { return load(); }
AtomicBase& operator=(const AtomicBase& rhs)
{
store(rhs.load());
return *this;
}
AtomicBase& operator=(T value)
{
store(value);
return *this;
}
/// Load the current value, as if with memory_order_relaxed.
T load() const;
/// Store a new value, as if with memory_order_relaxed.
void store(T value);
/// Non-atomically store a new value.
void storeNonAtomic(T value);
/// Exchange/swap the current value, as if with memory_order_relaxed.
/// @return the previous value
T exchange(T value);
/// Load the current value and if it is equal to `expected`, store `desired`
/// as if with memory_order_relaxed.
/// Otherwise, this sets `original` to the current value.
/// @param expected The value expected to be found in the atomic object, and to be replaced.
/// @param desired The new value to store in the atomic object if `expected` was found.
/// @param original The value that was found in the atomic object if the comparison fails. May
/// be null. Note that this is only updated when false is returned.
/// @return true if and only if the value was modified
bool compareExchange(T expected, T desired, T* original = nullptr);
protected:
#ifdef NNSDK
// Nintendo appears to have manually implemented atomics with volatile and platform specific
// intrinsics (e.g. __builtin_arm_ldrex).
// For ease of implementation and portability, we will use std::atomic and cast to volatile
// when necessary. That is formally undefined behavior, but it should be safe because
// sead is built with -fno-strict-aliasing and because of the following static assertions.
std::atomic<T> mValue;
// static_assert(sizeof(mValue) == sizeof(T),
// "std::atomic<T> and T do not have the same size; unsupported case");
// static_assert(alignof(decltype(mValue)) == alignof(volatile T),
// "std::atomic<T> and T do not have the same alignment; unsupported case");
// static_assert(std::atomic<T>::is_always_lock_free,
// "std::atomic<T>::is_always_lock_free is not true; unsupported case");
const volatile T* getValuePtr() const { return reinterpret_cast<const volatile T*>(&mValue); }
volatile T* getValuePtr() { return reinterpret_cast<volatile T*>(&mValue); }
#endif
};
template <class T>
struct Atomic : AtomicBase<T>
{
using AtomicBase<T>::AtomicBase;
using AtomicBase<T>::operator=;
T fetchAdd(T x);
T fetchSub(T x);
T fetchAnd(T x);
T fetchOr(T x);
T fetchXor(T x);
T increment() { return fetchAdd(1); }
T decrement() { return fetchSub(1); }
bool isBitOn(unsigned int bit) const;
/// @return whether the bit was cleared and is now set.
bool setBitOn(unsigned int bit);
/// @return whether the bit was set and is now cleared.
bool setBitOff(unsigned int bit);
T operator+=(T x) { return fetchAdd(x); }
T operator-=(T x) { return fetchSub(x); }
T operator&=(T x) { return fetchAnd(x); }
T operator|=(T x) { return fetchOr(x); }
T operator^=(T x) { return fetchXor(x); }
T operator++() { return fetchAdd(1) + 1; }
T operator++(int) { return fetchAdd(1); }
T operator--() { return fetchSub(1) - 1; }
T operator--(int) { return fetchSub(1); }
};
/// Specialization for pointer types.
template <class T>
struct Atomic<T*> : AtomicBase<T*>
{
using AtomicBase<T*>::AtomicBase;
using AtomicBase<T*>::operator=;
T& operator*() const { return *this->load(); }
T* operator->() const { return this->load(); }
};
// Implementation.
#ifdef NNSDK
template <class T>
inline AtomicBase<T>::AtomicBase(T value)
{
storeNonAtomic(value);
}
template <class T>
inline AtomicBase<T>::AtomicBase(AtomicDirectInitTag, T value) : mValue(value)
{
}
template <class T>
inline T AtomicBase<T>::load() const
{
#ifdef MATCHING_HACK_NX_CLANG
// Using std::atomic<T>::load prevents LLVM from folding ldr+sext into ldrsw.
return *getValuePtr();
#else
return mValue.load(std::memory_order_relaxed);
#endif
}
template <class T>
inline void AtomicBase<T>::store(T value)
{
mValue.store(value, std::memory_order_relaxed);
}
template <class T>
inline void AtomicBase<T>::storeNonAtomic(T value)
{
*getValuePtr() = value;
}
template <class T>
inline T AtomicBase<T>::exchange(T value)
{
return mValue.exchange(value, std::memory_order_relaxed);
}
template <class T>
inline bool AtomicBase<T>::compareExchange(T expected, T desired, T* original)
{
#ifdef MATCHING_HACK_NX_CLANG
// Unlike Clang (https://reviews.llvm.org/D13033), Nintendo's implementation does not use clrex.
do
{
T value = __builtin_arm_ldrex(getValuePtr());
if (value != expected)
{
if (original)
*original = value;
return false;
}
} while (__builtin_arm_strex(desired, getValuePtr()));
return true;
#else
T value = expected;
if (mValue.compare_exchange_strong(value, desired, std::memory_order_relaxed))
return true;
if (original)
*original = value;
return false;
#endif
}
#ifdef MATCHING_HACK_NX_CLANG
namespace detail
{
// To match Nintendo's implementation of atomics.
template <typename T, typename F>
inline T atomicReadModifyWrite(volatile T* value_ptr, F op)
{
T value;
do
{
value = __builtin_arm_ldrex(value_ptr);
} while (__builtin_arm_strex(op(value), value_ptr));
return value;
}
} // namespace detail
#endif
template <class T>
inline T Atomic<T>::fetchAdd(T x)
{
#ifdef MATCHING_HACK_NX_CLANG
return detail::atomicReadModifyWrite(this->getValuePtr(), [&](T val) { return val + x; });
#else
return this->mValue.fetch_add(x, std::memory_order_relaxed);
#endif
}
template <class T>
inline T Atomic<T>::fetchSub(T x)
{
#ifdef MATCHING_HACK_NX_CLANG
return detail::atomicReadModifyWrite(this->getValuePtr(), [&](T val) { return val - x; });
#else
return this->mValue.fetch_sub(x, std::memory_order_relaxed);
#endif
}
template <class T>
inline T Atomic<T>::fetchAnd(T x)
{
#ifdef MATCHING_HACK_NX_CLANG
return detail::atomicReadModifyWrite(this->getValuePtr(), [&](T val) { return val & x; });
#else
return this->mValue.fetch_and(x, std::memory_order_relaxed);
#endif
}
template <class T>
inline T Atomic<T>::fetchOr(T x)
{
#ifdef MATCHING_HACK_NX_CLANG
return detail::atomicReadModifyWrite(this->getValuePtr(), [&](T val) { return val | x; });
#else
return this->mValue.fetch_or(x, std::memory_order_relaxed);
#endif
}
template <class T>
inline T Atomic<T>::fetchXor(T x)
{
#ifdef MATCHING_HACK_NX_CLANG
return detail::atomicReadModifyWrite(this->getValuePtr(), [&](T val) { return val ^ x; });
#else
return this->mValue.fetch_xor(x, std::memory_order_relaxed);
#endif
}
template <class T>
bool Atomic<T>::isBitOn(unsigned int bit) const
{
return (this->load() & (1 << bit)) != 0;
}
template <class T>
bool Atomic<T>::setBitOn(unsigned int bit)
{
#ifdef MATCHING_HACK_NX_CLANG
const auto old = detail::atomicReadModifyWrite(this->getValuePtr(),
[bit](T val) { return val | (1 << bit); });
#else
const auto old = this->mValue.fetch_or(1 << bit, std::memory_order_relaxed);
#endif
return (old & (1 << bit)) == 0;
}
template <class T>
bool Atomic<T>::setBitOff(unsigned int bit)
{
#ifdef MATCHING_HACK_NX_CLANG
const auto old = detail::atomicReadModifyWrite(this->getValuePtr(),
[bit](T val) { return val & ~(1 << bit); });
#else
const auto old = this->mValue.fetch_and(~(1 << bit), std::memory_order_relaxed);
#endif
return (old & (1 << bit)) != 0;
}
#else // NNSDK
#error "Unknown platform"
#endif
} // namespace sead