mirror of
https://github.com/CraftyBoss/SuperMarioOdysseyOnline.git
synced 2024-11-28 22:23:05 +00:00
288 lines
8.2 KiB
C
288 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
|