AuROXTL/Include/auROXTL/auAtomic.hpp

699 lines
18 KiB
C++

/***
Copyright (C) 2022 J Reece Wilson (a/k/a "Reece"). All rights reserved.
File: auAtomic.hpp
Date: 2022-2-1
Author: Reece
***/
#pragma once
#if defined(AURORA_COMPILER_CLANG)
#include <stdatomic.h>
#endif
template <class T>
struct AuAtomicUtils
{
/**
* @brief Generic bitwise (1 << offset)
* @return original value
* @warning T is bound by platform and compiler constraints
*/
static T Set(T *in, AuUInt8 offset);
/**
* @brief
* @param in
* @param orValue
* @return original value
*/
static T Or(T *in, T orValue);
/**
* @brief
* @param in
* @param andValue
* @return original value
*/
static T And(T *in, T andValue);
/**
* @brief Adds addend to in
* @return updated value
* @warning T is bound by platform and compiler constraints
*/
static T Add(T *in, T addend);
/**
* @brief Subtracts the minuend from in
* @return updated value
* @warning T is bound by platform and compiler constraints
*/
static T Sub(T *in, T minuend);
/**
* @brief Generic compare exchange
* @param replace replacement value for in if in matches compare
* @param compare required reference value
* @return original value
* @warning T is bound by platform and compiler constraints
*/
static T CompareExchange(T *in, T replace, T compare);
/**
* @brief { return *in & (1 << offset); in |= (1 << offset) }
* @param in
* @param offset Bit index
* @return *in & (1 << offset)
* @warning T is bound by platform and compiler constraints
*/
static bool TestAndSet(T *in, const AuUInt8 offset);
//
static T Load(T *in);
//
static void Store(T *in, T value);
//
static void ClearU8Lock(T *in);
};
#if defined(AURORA_COMPILER_MSVC)
template <>
inline auline AuUInt64 AuAtomicUtils<AuUInt64>::CompareExchange(AuUInt64 *in, AuUInt64 replace, AuUInt64 compare)
{
return static_cast<AuUInt64>(_InterlockedCompareExchange64(reinterpret_cast<long long volatile *>(in), static_cast<long long>(replace), static_cast<long long>(compare)));
}
template <>
inline auline AuUInt32 AuAtomicUtils<AuUInt32>::CompareExchange(AuUInt32 *in, AuUInt32 replace, AuUInt32 compare)
{
return static_cast<AuUInt32>(_InterlockedCompareExchange(reinterpret_cast<long volatile *>(in), static_cast<long>(replace), static_cast<long>(compare)));
}
template <>
inline auline AuUInt16 AuAtomicUtils<AuUInt16>::CompareExchange(AuUInt16 *in, AuUInt16 replace, AuUInt16 compare)
{
return static_cast<AuUInt16>(_InterlockedCompareExchange16(reinterpret_cast<short volatile *>(in), static_cast<short>(replace), static_cast<short>(compare)));
}
template <>
inline auline AuInt64 AuAtomicUtils<AuInt64>::CompareExchange(AuInt64 *in, AuInt64 replace, AuInt64 compare)
{
return _InterlockedCompareExchange64(reinterpret_cast<long long volatile *>(in), static_cast<long long>(replace), static_cast<long long>(compare));
}
template <>
inline auline AuInt32 AuAtomicUtils<AuInt32>::CompareExchange(AuInt32 *in, AuInt32 replace, AuInt32 compare)
{
return _InterlockedCompareExchange(reinterpret_cast<long volatile *>(in), static_cast<long>(replace), static_cast<long>(compare));
}
template <>
inline auline AuInt16 AuAtomicUtils<AuInt16>::CompareExchange(AuInt16 *in, AuInt16 replace, AuInt16 compare)
{
return _InterlockedCompareExchange16(reinterpret_cast<short volatile *>(in), static_cast<short>(replace), static_cast<short>(compare));
}
#if !defined(AURORA_IS_32BIT)
template <>
inline auline AuUInt64 AuAtomicUtils<AuUInt64>::Add(AuUInt64 *in, AuUInt64 addend)
{
return static_cast<AuUInt64>(_InterlockedExchangeAdd64(reinterpret_cast<long long volatile *>(in), static_cast<long long>(addend)) + static_cast<long long>(addend));
}
#endif
template <>
inline auline AuUInt32 AuAtomicUtils<AuUInt32>::Add(AuUInt32 *in, AuUInt32 addend)
{
return static_cast<AuUInt32>(_InterlockedExchangeAdd(reinterpret_cast<long volatile *>(in), static_cast<long>(addend)) + static_cast<long>(addend));
}
#if !defined(AURORA_IS_32BIT)
template <>
inline auline AuInt64 AuAtomicUtils<AuInt64>::Add(AuInt64 *in, AuInt64 addend)
{
return _InterlockedExchangeAdd64(reinterpret_cast<long long volatile *>(in), static_cast<long long>(addend)) + static_cast<long long>(addend);
}
#endif
template <>
inline auline AuInt32 AuAtomicUtils<AuInt32>::Add(AuInt32 *in, AuInt32 addend)
{
return _InterlockedExchangeAdd(reinterpret_cast<long volatile *>(in), static_cast<long>(addend)) + static_cast<long>(addend);
}
// TODO:
#if 0
template <>
inline auline AuUInt16 AuAtomicUtils<AuUInt16>::Add(AuUInt16 *in, AuUInt16 addend)
{
return {};
}
#endif
template <>
inline auline AuUInt64 AuAtomicUtils<AuUInt64>::Sub(AuUInt64 *in, AuUInt64 minuend)
{
return Add(in, AuUInt64(0) - minuend);
}
template <>
inline auline AuUInt32 AuAtomicUtils<AuUInt32>::Sub(AuUInt32 *in, AuUInt32 minuend)
{
return Add(in, AuUInt32(0) - minuend);
}
template <>
inline auline AuInt64 AuAtomicUtils<AuInt64>::Sub(AuInt64 *in, AuInt64 minuend)
{
return Add(in, AuInt64(0) - minuend);
}
template <>
inline auline AuInt32 AuAtomicUtils<AuInt32>::Sub(AuInt32 *in, AuInt32 minuend)
{
return Add(in, AuInt32(0) - minuend);
}
// TODO:
#if 0
template <>
inline auline AuUInt16 AuAtomicUtils<AuUInt16>::Sub(AuUInt16 *in, AuUInt16 minuend)
{
return {};
}
#endif
#if !defined(AURORA_IS_32BIT)
template <>
inline auline AuUInt64 AuAtomicUtils<AuUInt64>::Or(AuUInt64 *in, AuUInt64 orValue)
{
return _InterlockedOr64(reinterpret_cast<long long volatile *>(in), orValue);
}
#endif
template <>
inline auline AuUInt32 AuAtomicUtils<AuUInt32>::Or(AuUInt32 *in, AuUInt32 orValue)
{
return _InterlockedOr(reinterpret_cast<long volatile *>(in), orValue);
}
template <>
inline auline AuUInt16 AuAtomicUtils<AuUInt16>::Or(AuUInt16 *in, AuUInt16 orValue)
{
return _InterlockedOr16(reinterpret_cast<short volatile *>(in), orValue);
}
#if !defined(AURORA_IS_32BIT)
template <>
inline auline AuInt64 AuAtomicUtils<AuInt64>::Or(AuInt64 *in, AuInt64 orValue)
{
return _InterlockedOr64(reinterpret_cast<long long volatile *>(in), orValue);
}
#endif
template <>
inline auline AuInt32 AuAtomicUtils<AuInt32>::Or(AuInt32 *in, AuInt32 orValue)
{
return _InterlockedOr(reinterpret_cast<long volatile *>(in), orValue);
}
template <>
inline auline long AuAtomicUtils<long>::Or(long *in, long orValue)
{
return _InterlockedOr(reinterpret_cast<long volatile *>(in), orValue);
}
template <>
inline auline unsigned long AuAtomicUtils<unsigned long>::Or(unsigned long *in, unsigned long orValue)
{
return _InterlockedOr(reinterpret_cast<long volatile *>(in), orValue);
}
template <>
inline auline AuInt16 AuAtomicUtils<AuInt16>::Or(AuInt16 *in, AuInt16 orValue)
{
return _InterlockedOr16(reinterpret_cast<short volatile *>(in), orValue);
}
template <class T>
inline auline T AuAtomicUtils<T>::Set(T *in, AuUInt8 offset)
{
return AuAtomicUtils<T>::Or(in, T(1) << offset);
}
#if !defined(AURORA_IS_32BIT)
template <>
inline auline AuUInt64 AuAtomicUtils<AuUInt64>::And(AuUInt64 *in, AuUInt64 AndValue)
{
return _InterlockedAnd64(reinterpret_cast<long long volatile *>(in), AndValue);
}
#endif
template <>
inline auline AuUInt32 AuAtomicUtils<AuUInt32>::And(AuUInt32 *in, AuUInt32 AndValue)
{
return _InterlockedAnd(reinterpret_cast<long volatile *>(in), AndValue);
}
template <>
inline auline AuUInt16 AuAtomicUtils<AuUInt16>::And(AuUInt16 *in, AuUInt16 AndValue)
{
return _InterlockedAnd16(reinterpret_cast<short volatile *>(in), AndValue);
}
#if !defined(AURORA_IS_32BIT)
template <>
inline auline AuInt64 AuAtomicUtils<AuInt64>::And(AuInt64 *in, AuInt64 AndValue)
{
return _InterlockedAnd64(reinterpret_cast<long long volatile *>(in), AndValue);
}
#endif
template <>
inline auline AuInt32 AuAtomicUtils<AuInt32>::And(AuInt32 *in, AuInt32 AndValue)
{
return _InterlockedAnd(reinterpret_cast<long volatile *>(in), AndValue);
}
template <>
inline auline long AuAtomicUtils<long>::And(long *in, long AndValue)
{
return _InterlockedAnd(reinterpret_cast<long volatile *>(in), AndValue);
}
template <>
inline auline unsigned long AuAtomicUtils<unsigned long>::And(unsigned long *in, unsigned long AndValue)
{
return _InterlockedAnd(reinterpret_cast<long volatile *>(in), AndValue);
}
template <>
inline auline AuInt16 AuAtomicUtils<AuInt16>::And(AuInt16 *in, AuInt16 AndValue)
{
return _InterlockedAnd16(reinterpret_cast<short volatile *>(in), AndValue);
}
#elif defined(AURORA_COMPILER_CLANG) || defined(AURORA_COMPILER_GCC)
template <class T>
inline auline T AuAtomicUtils<T>::CompareExchange(T *in, T replace, T compare)
{
return __sync_val_compare_and_swap(in, compare, replace);
}
template <class T>
inline auline T AuAtomicUtils<T>::Add(T *in, T addend)
{
return __sync_add_and_fetch(in, addend);
}
template <class T>
inline auline T AuAtomicUtils<T>::Sub(T *in, T minuend)
{
return __sync_sub_and_fetch(in, minuend);
}
template <class T>
inline auline T AuAtomicUtils<T>::Set(T *in, AuUInt8 offset)
{
return __sync_fetch_and_or(in, T(1) << offset);
}
template <class T>
inline auline T AuAtomicUtils<T>::Or(T *in, T value)
{
return __sync_fetch_and_or(in, value);
}
template <class T>
inline auline T AuAtomicUtils<T>::And(T *in, T value)
{
return __sync_fetch_and_and(in, value);
}
#endif
#if defined(AURORA_COMPILER_CLANG)
#define ATOMIC_PREFIX_HAX(name) __c11_ ## name
#else
#define ATOMIC_PREFIX_HAX(name) __ ## name ## _explicit
#endif
template <class T>
inline auline T AuAtomicUtils<T>::Load(T *in)
{
#if defined(AURORA_COMPILER_MSVC) && (defined(AURORA_ARCH_X64) || defined(AURORA_ARCH_X86))
::_ReadWriteBarrier(); // compile-time only! we use this to prevent optimizations. x86/64 does not care so long as we're aligned.
return *in;
#elif defined(AURORA_COMPILER_MSVC)
::MemoryBarrier(); // works on all legacy MSVC targets including AMD64, IA64, and POWER
return *in;
#else
#if defined(AURORA_COMPILER_CLANG)
#if !(defined(AURORA_ARCH_X64) || defined(AURORA_ARCH_X86))
__sync_synchronize(); // brute force on unknown archs. gcc-like compilers will accept this
#endif
if constexpr (AuIsSame_v<AuUInt8, T>)
{
return ATOMIC_PREFIX_HAX(atomic_load)((atomic_uint_fast8_t *)in, __ATOMIC_ACQUIRE);
}
else if constexpr (AuIsSame_v<AuInt8, T>)
{
return ATOMIC_PREFIX_HAX(atomic_load)((atomic_int_fast8_t *)in, __ATOMIC_ACQUIRE);
}
else if constexpr (AuIsSame_v<AuUInt16, T>)
{
return ATOMIC_PREFIX_HAX(atomic_load)((atomic_uint_fast16_t *)in, __ATOMIC_ACQUIRE);
}
else if constexpr (AuIsSame_v<AuInt16, T>)
{
return ATOMIC_PREFIX_HAX(atomic_load)((atomic_int_fast16_t *)in, __ATOMIC_ACQUIRE);
}
else if constexpr (AuIsSame_v<AuUInt32, T>)
{
return ATOMIC_PREFIX_HAX(atomic_load)((atomic_uint_fast32_t *)in, __ATOMIC_ACQUIRE);
}
else if constexpr (AuIsSame_v<AuInt32, T>)
{
return ATOMIC_PREFIX_HAX(atomic_load)((atomic_int_fast32_t *)in, __ATOMIC_ACQUIRE);
}
else
{
static_assert(AuIsVoid_v<T>, "T");
}
#else
return __sync_val_compare_and_swap(*in, 0, 0);
#endif
#endif
}
template <class T>
inline auline void AuAtomicUtils<T>::Store(T *in, T val)
{
#if defined(AURORA_COMPILER_MSVC) && (defined(AURORA_ARCH_X64) || defined(AURORA_ARCH_X86))
*in = val;
#elif defined(AURORA_COMPILER_MSVC)
*in = val;
::MemoryBarrier();
#else
if constexpr (AuIsSame_v<AuUInt8, T>)
{
ATOMIC_PREFIX_HAX(atomic_store)((atomic_uint_fast8_t *)in, val, __ATOMIC_RELEASE);
}
else if constexpr (AuIsSame_v<AuInt8, T>)
{
ATOMIC_PREFIX_HAX(atomic_store)((atomic_int_fast8_t *)in, val, __ATOMIC_RELEASE);
}
else if constexpr (AuIsSame_v<AuUInt16, T>)
{
ATOMIC_PREFIX_HAX(atomic_store)((atomic_uint_fast16_t *)in, val, __ATOMIC_RELEASE);
}
else if constexpr (AuIsSame_v<AuInt16, T>)
{
ATOMIC_PREFIX_HAX(atomic_store)((atomic_int_fast16_t *)in, val, __ATOMIC_RELEASE);
}
else if constexpr (AuIsSame_v<AuUInt32, T>)
{
ATOMIC_PREFIX_HAX(atomic_store)((atomic_uint_fast32_t *)in, val, __ATOMIC_RELEASE);
}
else if constexpr (AuIsSame_v<AuInt32, T>)
{
ATOMIC_PREFIX_HAX(atomic_store)((atomic_int_fast32_t *)in, val, __ATOMIC_RELEASE);
}
else
{
static_assert(AuIsVoid_v<T>, "T");
}
#if !(defined(AURORA_ARCH_X64) || defined(AURORA_ARCH_X86))
__sync_synchronize();
#endif
#endif
}
template <>
inline auline
void AuAtomicUtils<AuUInt8>::ClearU8Lock(AuUInt8 *in)
{
#if defined(AURORA_COMPILER_MSVC) && (defined(AURORA_ARCH_X64) || defined(AURORA_ARCH_X86))
*in = 0;
::_ReadWriteBarrier();
#elif defined(AURORA_COMPILER_MSVC)
// i think this will work on aarch64 and most risc architectures
InterlockedAndRelease((volatile LONG *)in, ~0xFF);
#else
ATOMIC_PREFIX_HAX(atomic_store)((atomic_uint_fast8_t *)in, 0, __ATOMIC_RELEASE);
#if !(defined(AURORA_ARCH_X64) || defined(AURORA_ARCH_X86))
__sync_synchronize();
#endif
#endif
}
template <>
inline auline
void AuAtomicUtils<AuUInt32>::ClearU8Lock(AuUInt32 *in)
{
#if defined(AU_CPU_ENDIAN_LITTLE)
AuAtomicUtils<AuUInt8>::ClearU8Lock((AuUInt8 *)in);
#else
AuAtomicUtils<AuUInt8>::ClearU8Lock(((AuUInt8 *)in) + 3);
#endif
}
template <class T>
inline auline bool AuAtomicUtils<T>::TestAndSet(T *in, const AuUInt8 offset)
{
return AuAtomicUtils<T>::Set(in, offset) & (1 << offset);
}
#if defined(AURORA_COMPILER_MSVC) && (defined(AURORA_ARCH_X64) || defined(AURORA_ARCH_X86))
template <>
inline auline bool AuAtomicUtils<unsigned long>::TestAndSet(unsigned long *in, const AuUInt8 offset)
{
return _interlockedbittestandset(reinterpret_cast<volatile long *>(in), offset);
}
template <>
inline auline bool AuAtomicUtils<long>::TestAndSet(long *in, const AuUInt8 offset)
{
return _interlockedbittestandset(reinterpret_cast<volatile long *>(in), offset);
}
template <>
inline auline bool AuAtomicUtils<AuUInt32>::TestAndSet(AuUInt32 *in, const AuUInt8 offset)
{
return _interlockedbittestandset(reinterpret_cast<volatile long *>(in), offset);
}
template <>
inline auline bool AuAtomicUtils<AuInt32>::TestAndSet(AuInt32 *in, const AuUInt8 offset)
{
return _interlockedbittestandset(reinterpret_cast<volatile long *>(in), offset);
}
#if !defined(AURORA_IS_32BIT)
template <>
inline auline bool AuAtomicUtils<AuUInt64>::TestAndSet(AuUInt64 *in, const AuUInt8 offset)
{
return _interlockedbittestandset64(reinterpret_cast<volatile long long *>(in), offset);
}
template <>
inline auline bool AuAtomicUtils<AuInt64>::TestAndSet(AuInt64 *in, const AuUInt8 offset)
{
return _interlockedbittestandset64(reinterpret_cast<volatile long long *>(in), offset);
}
#endif
#endif
template <class T>
auline
void AuAtomicStore(T *in, T value)
{
AuAtomicUtils<T>::Store(in, value);
}
template <class T>
auline
T AuAtomicLoad(T *in)
{
return AuAtomicUtils<T>::Load(in);
}
template <class T>
auline
void AuAtomicClearU8Lock(T *in)
{
AuAtomicUtils<T>::ClearU8Lock(in);
}
template <class T>
auline
T AuAtomicOrSetBit(T *in, AuUInt8 offset)
{
return AuAtomicUtils<T>::Set(in, offset);
}
template <class T>
auline
bool AuAtomicSet(T *in, AuUInt8 offset)
{
return AuAtomicOrSetBit(in, offset) & (T(1) << offset);
}
template <class T>
auline
T AuAtomicAndUnsetBit(T *in, AuUInt8 offset)
{
return AuAtomicUtils<T>::And(in, ~(T(1) << T(offset)));
}
template <class T>
auline
bool AuAtomicUnset(T *in, AuUInt8 offset)
{
auto uBit = T(1) << T(offset);
return AuAtomicUtils<T>::And(in, ~(uBit)) & uBit;
}
template <class T>
auline
T AuAtomicOr(T *in, T value)
{
return AuAtomicUtils<T>::Or(in, value);
}
template <class T>
auline
T AuAtomicAnd(T *in, T value)
{
return AuAtomicUtils<T>::And(in, value);
}
template <class T>
auline
T AuAtomicAdd(T *in, T addend)
{
return AuAtomicUtils<T>::Add(in, addend);
}
template <class T>
auline
T AuAtomicSub(T *in, T minuend)
{
return AuAtomicUtils<T>::Sub(in, minuend);
}
template <class T>
auline
T AuAtomicCompareExchange(T *in, T replace, T compare)
{
return AuAtomicUtils<T>::CompareExchange(in, replace, compare);
}
template <class T>
auline
bool AuAtomicTestAndSet(T *in, AuUInt8 offset)
{
return AuAtomicUtils<T>::TestAndSet(in, offset);
}
template <class T>
auline
void AuAtomicStore(volatile T *in, T value)
{
AuAtomicUtils<T>::Store((T *)in, value);
}
template <class T>
auline
T AuAtomicLoad(volatile T *in)
{
return AuAtomicUtils<T>::Load((T *)in);
}
template <class T>
auline
void AuAtomicClearU8Lock(volatile T *in)
{
AuAtomicUtils<T>::ClearU8Lock((T *)in);
}
template <class T>
auline
T AuAtomicOrSetBit(volatile T *in, AuUInt8 offset)
{
return AuAtomicOrSetBit((T *)in, offset);
}
template <class T>
auline
bool AuAtomicSet(volatile T *in, AuUInt8 offset)
{
return AuAtomicSet((T *)in, offset);
}
template <class T>
auline
T AuAtomicAndUnsetBit(volatile T *in, AuUInt8 offset)
{
return AuAtomicAndUnsetBit((T *)in, offset);
}
template <class T>
auline
bool AuAtomicUnset(volatile T *in, AuUInt8 offset)
{
return AuAtomicUnset((T *)in, offset);
}
template <class T>
auline
T AuAtomicOr(volatile T *in, T value)
{
return AuAtomicOr((T *)in, value);
}
template <class T>
auline
T AuAtomicAnd(volatile T *in, T value)
{
return AuAtomicAnd((T *)in, value);
}
template <class T>
auline
T AuAtomicAdd(volatile T *in, T addend)
{
return AuAtomicUtils<T>::Add((T *)(in), addend);
}
template <class T>
auline
T AuAtomicSub(volatile T *in, T minuend)
{
return AuAtomicUtils<T>::Sub((T *)(in), minuend);
}
template <class T>
auline
T AuAtomicCompareExchange(volatile T *in, T replace, T compare)
{
return AuAtomicUtils<T>::CompareExchange((T *)(in), replace, compare);
}
template <class T>
auline
bool AuAtomicTestAndSet(volatile T *in, AuUInt8 offset)
{
return AuAtomicUtils<T>::TestAndSet((T *)(in), offset);
}