/*** 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 #endif #if defined(AURORA_COMPILER_MSVC) || 1 using AuAInt8 = volatile AuInt8; using AuAUInt8 = volatile AuUInt8; using AuAInt16 = volatile AuInt16; using AuAUInt16 = volatile AuUInt16; using AuAInt32 = volatile AuInt32; using AuAUInt32 = volatile AuUInt32; #if defined(AURORA_IS_64BIT) using AuAInt64 = volatile AuInt64; using AuAUInt64 = volatile AuUInt64; #endif #define AU_ATOMIC_INIT(n) n #else using AuAInt8 = _Atomic(int8_t); using AuAUInt8 = _Atomic(uint8_t); using AuAInt16 = _Atomic(int16_t); using AuAUInt16 = _Atomic(uint16_t); using AuAInt32 = _Atomic(int32_t); using AuAUInt32 = _Atomic(uint32_t); #if defined(AURORA_IS_64BIT) using AuAInt64 = _Atomic(int64_t); using AuAUInt64 = _Atomic(uint64_t); #endif #define AU_ATOMIC_INIT ATOMIC_VAR_INIT #endif // Defines: // AuAtomicCompareExchange (no weak variants yet) // - // AuAtomicTestAndSet // AuAtomicClearU8Lock // AuAtomicSet // AuAtomicUnset (ret: bool) // AuAtomicAndUnsetBit (ret: T) // - // AuAtomicLoadWeak (tbd) // (no weak store) (tbd) // - // AuAtomicLoad // AuAtomicStore // - // AuAtomicOrSetBit // AuAtomicOr // AuAtomicAnd // AuAtomicAdd // AuAtomicSub template 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); // static T LoadWeak(T *in); }; #if defined(AURORA_COMPILER_MSVC) template <> inline auline AuUInt64 AuAtomicUtils::CompareExchange(AuUInt64 *in, AuUInt64 replace, AuUInt64 compare) { return static_cast(_InterlockedCompareExchange64(reinterpret_cast(in), static_cast(replace), static_cast(compare))); } template <> inline auline AuUInt32 AuAtomicUtils::CompareExchange(AuUInt32 *in, AuUInt32 replace, AuUInt32 compare) { return static_cast(_InterlockedCompareExchange(reinterpret_cast(in), static_cast(replace), static_cast(compare))); } template <> inline auline AuUInt16 AuAtomicUtils::CompareExchange(AuUInt16 *in, AuUInt16 replace, AuUInt16 compare) { return static_cast(_InterlockedCompareExchange16(reinterpret_cast(in), static_cast(replace), static_cast(compare))); } template <> inline auline AuUInt8 AuAtomicUtils::CompareExchange(AuUInt8 *in, AuUInt8 replace, AuUInt8 compare) { return static_cast(_InterlockedCompareExchange8(reinterpret_cast(in), static_cast(replace), static_cast(compare))); } template <> inline auline AuInt64 AuAtomicUtils::CompareExchange(AuInt64 *in, AuInt64 replace, AuInt64 compare) { return _InterlockedCompareExchange64(reinterpret_cast(in), static_cast(replace), static_cast(compare)); } template <> inline auline AuInt32 AuAtomicUtils::CompareExchange(AuInt32 *in, AuInt32 replace, AuInt32 compare) { return _InterlockedCompareExchange(reinterpret_cast(in), static_cast(replace), static_cast(compare)); } template <> inline auline AuInt16 AuAtomicUtils::CompareExchange(AuInt16 *in, AuInt16 replace, AuInt16 compare) { return _InterlockedCompareExchange16(reinterpret_cast(in), static_cast(replace), static_cast(compare)); } template <> inline auline AuInt8 AuAtomicUtils::CompareExchange(AuInt8 *in, AuInt8 replace, AuInt8 compare) { return _InterlockedCompareExchange8(reinterpret_cast(in), static_cast(replace), static_cast(compare)); } #if !defined(AURORA_IS_32BIT) template <> inline auline AuUInt64 AuAtomicUtils::Add(AuUInt64 *in, AuUInt64 addend) { return static_cast(_InterlockedExchangeAdd64(reinterpret_cast(in), static_cast(addend)) + static_cast(addend)); } #endif template <> inline auline AuUInt32 AuAtomicUtils::Add(AuUInt32 *in, AuUInt32 addend) { return static_cast(_InterlockedExchangeAdd(reinterpret_cast(in), static_cast(addend)) + static_cast(addend)); } #if !defined(AURORA_IS_32BIT) template <> inline auline AuInt64 AuAtomicUtils::Add(AuInt64 *in, AuInt64 addend) { return _InterlockedExchangeAdd64(reinterpret_cast(in), static_cast(addend)) + static_cast(addend); } #endif template <> inline auline AuInt32 AuAtomicUtils::Add(AuInt32 *in, AuInt32 addend) { return _InterlockedExchangeAdd(reinterpret_cast(in), static_cast(addend)) + static_cast(addend); } // TODO: #if 0 template <> inline auline AuUInt16 AuAtomicUtils::Add(AuUInt16 *in, AuUInt16 addend) { return {}; } #endif template <> inline auline AuUInt64 AuAtomicUtils::Sub(AuUInt64 *in, AuUInt64 minuend) { return Add(in, AuUInt64(0) - minuend); } template <> inline auline AuUInt32 AuAtomicUtils::Sub(AuUInt32 *in, AuUInt32 minuend) { return Add(in, AuUInt32(0) - minuend); } template <> inline auline AuInt64 AuAtomicUtils::Sub(AuInt64 *in, AuInt64 minuend) { return Add(in, AuInt64(0) - minuend); } template <> inline auline AuInt32 AuAtomicUtils::Sub(AuInt32 *in, AuInt32 minuend) { return Add(in, AuInt32(0) - minuend); } // TODO: #if 0 template <> inline auline AuUInt16 AuAtomicUtils::Sub(AuUInt16 *in, AuUInt16 minuend) { return {}; } #endif #if !defined(AURORA_IS_32BIT) template <> inline auline AuUInt64 AuAtomicUtils::Or(AuUInt64 *in, AuUInt64 orValue) { return _InterlockedOr64(reinterpret_cast(in), orValue); } #endif template <> inline auline AuUInt32 AuAtomicUtils::Or(AuUInt32 *in, AuUInt32 orValue) { return _InterlockedOr(reinterpret_cast(in), orValue); } template <> inline auline AuUInt16 AuAtomicUtils::Or(AuUInt16 *in, AuUInt16 orValue) { return _InterlockedOr16(reinterpret_cast(in), orValue); } #if !defined(AURORA_IS_32BIT) template <> inline auline AuInt64 AuAtomicUtils::Or(AuInt64 *in, AuInt64 orValue) { return _InterlockedOr64(reinterpret_cast(in), orValue); } #endif template <> inline auline AuInt32 AuAtomicUtils::Or(AuInt32 *in, AuInt32 orValue) { return _InterlockedOr(reinterpret_cast(in), orValue); } template <> inline auline long AuAtomicUtils::Or(long *in, long orValue) { return _InterlockedOr(reinterpret_cast(in), orValue); } template <> inline auline unsigned long AuAtomicUtils::Or(unsigned long *in, unsigned long orValue) { return _InterlockedOr(reinterpret_cast(in), orValue); } template <> inline auline AuInt16 AuAtomicUtils::Or(AuInt16 *in, AuInt16 orValue) { return _InterlockedOr16(reinterpret_cast(in), orValue); } template inline auline T AuAtomicUtils::Set(T *in, AuUInt8 offset) { return AuAtomicUtils::Or(in, T(1) << offset); } #if !defined(AURORA_IS_32BIT) template <> inline auline AuUInt64 AuAtomicUtils::And(AuUInt64 *in, AuUInt64 AndValue) { return _InterlockedAnd64(reinterpret_cast(in), AndValue); } #endif template <> inline auline AuUInt32 AuAtomicUtils::And(AuUInt32 *in, AuUInt32 AndValue) { return _InterlockedAnd(reinterpret_cast(in), AndValue); } template <> inline auline AuUInt16 AuAtomicUtils::And(AuUInt16 *in, AuUInt16 AndValue) { return _InterlockedAnd16(reinterpret_cast(in), AndValue); } #if !defined(AURORA_IS_32BIT) template <> inline auline AuInt64 AuAtomicUtils::And(AuInt64 *in, AuInt64 AndValue) { return _InterlockedAnd64(reinterpret_cast(in), AndValue); } #endif template <> inline auline AuInt32 AuAtomicUtils::And(AuInt32 *in, AuInt32 AndValue) { return _InterlockedAnd(reinterpret_cast(in), AndValue); } template <> inline auline long AuAtomicUtils::And(long *in, long AndValue) { return _InterlockedAnd(reinterpret_cast(in), AndValue); } template <> inline auline unsigned long AuAtomicUtils::And(unsigned long *in, unsigned long AndValue) { return _InterlockedAnd(reinterpret_cast(in), AndValue); } template <> inline auline AuInt16 AuAtomicUtils::And(AuInt16 *in, AuInt16 AndValue) { return _InterlockedAnd16(reinterpret_cast(in), AndValue); } #elif defined(AURORA_COMPILER_CLANG) || defined(AURORA_COMPILER_GCC) template inline auline T AuAtomicUtils::CompareExchange(T *in, T replace, T compare) { return __sync_val_compare_and_swap(in, compare, replace); } template inline auline T AuAtomicUtils::Add(T *in, T addend) { return __sync_add_and_fetch(in, addend); } template inline auline T AuAtomicUtils::Sub(T *in, T minuend) { return __sync_sub_and_fetch(in, minuend); } template inline auline T AuAtomicUtils::Set(T *in, AuUInt8 offset) { return __sync_fetch_and_or(in, T(1) << offset); } template inline auline T AuAtomicUtils::Or(T *in, T value) { return __sync_fetch_and_or(in, value); } template inline auline T AuAtomicUtils::And(T *in, T value) { return __sync_fetch_and_and(in, value); } #endif template inline auline T AuAtomicUtils::LoadWeak(T *in) { return AuAtomicUtils::Load(in); } #if defined(AURORA_COMPILER_CLANG) #define ATOMIC_PREFIX_HAX(name) __c11_ ## name #else #define ATOMIC_PREFIX_HAX(name) __ ## name ## _explicit #endif template inline auline T AuAtomicUtils::Load(T *in) { #if defined(AURORA_COMPILER_MSVC) && (defined(AURORA_ARCH_X64) || defined(AURORA_ARCH_X86)) const auto read = *in; ::_ReadWriteBarrier(); return read; #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) { return ATOMIC_PREFIX_HAX(atomic_load)((_Atomic(AuUInt8) *)(in), __ATOMIC_ACQUIRE); } else if constexpr (AuIsSame_v) { return ATOMIC_PREFIX_HAX(atomic_load)((_Atomic(AuInt8) *)(in), __ATOMIC_ACQUIRE); } else if constexpr (AuIsSame_v) { return ATOMIC_PREFIX_HAX(atomic_load)((_Atomic(AuUInt16) *)(in), __ATOMIC_ACQUIRE); } else if constexpr (AuIsSame_v) { return ATOMIC_PREFIX_HAX(atomic_load)((_Atomic(AuInt16) *)(in), __ATOMIC_ACQUIRE); } else if constexpr (AuIsSame_v) { return ATOMIC_PREFIX_HAX(atomic_load)((_Atomic(AuUInt32) *)(in), __ATOMIC_ACQUIRE); } else if constexpr (AuIsSame_v) { return ATOMIC_PREFIX_HAX(atomic_load)((_Atomic(AuInt32) *)(in), __ATOMIC_ACQUIRE); } else if constexpr (AuIsSame_v) { return ATOMIC_PREFIX_HAX(atomic_load)((_Atomic(AuUInt64) *)(in), __ATOMIC_ACQUIRE); } else if constexpr (AuIsSame_v) { return ATOMIC_PREFIX_HAX(atomic_load)((_Atomic(AuInt64) *)(in), __ATOMIC_ACQUIRE); } else { static_assert(AuIsVoid_v, "T"); } #else return __sync_val_compare_and_swap(*in, 0, 0); #endif #endif } template inline auline void AuAtomicUtils::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) #if 0 * in = val; ::MemoryBarrier(); #else if constexpr (AuIsSame_v) { ::InterlockedExchange8((CHAR volatile *)in, (CHAR)val); } else if constexpr (AuIsSame_v) { ::InterlockedExchange8((CHAR volatile *)in, (CHAR)val); } else if constexpr (AuIsSame_v) { ::InterlockedExchange16((SHORT volatile *)in, (SHORT)val); } else if constexpr (AuIsSame_v) { ::InterlockedExchange16((SHORT volatile *)in, (SHORT)val); } else if constexpr (AuIsSame_v) { ::InterlockedExchange32((LONG32 volatile *)in, (LONG32)val); } else if constexpr (AuIsSame_v) { ::InterlockedExchange32((LONG32 volatile *)in, (LONG32)val); } else if constexpr (AuIsSame_v) { ::InterlockedExchange64((LONG64 volatile *)in, (LONG64)val); } else if constexpr (AuIsSame_v) { ::InterlockedExchange64((LONG64 volatile *)in, (LONG64)val); } else { static_assert(AuIsVoid_v, "T"); } #endif #else if constexpr (AuIsSame_v) { ATOMIC_PREFIX_HAX(atomic_store)((_Atomic(AuUInt8) *)(in), val, __ATOMIC_RELEASE); } else if constexpr (AuIsSame_v) { ATOMIC_PREFIX_HAX(atomic_store)((_Atomic(AuInt8) *)(in), val, __ATOMIC_RELEASE); } else if constexpr (AuIsSame_v) { ATOMIC_PREFIX_HAX(atomic_store)((_Atomic(AuUInt16) *)(in), val, __ATOMIC_RELEASE); } else if constexpr (AuIsSame_v) { ATOMIC_PREFIX_HAX(atomic_store)((_Atomic(AuInt16) *)(in), val, __ATOMIC_RELEASE); } else if constexpr (AuIsSame_v) { ATOMIC_PREFIX_HAX(atomic_store)((_Atomic(AuUInt32) *)(in), val, __ATOMIC_RELEASE); } else if constexpr (AuIsSame_v) { ATOMIC_PREFIX_HAX(atomic_store)((_Atomic(AuInt32) *)(in), val, __ATOMIC_RELEASE); } else if constexpr (AuIsSame_v) { ATOMIC_PREFIX_HAX(atomic_store)((_Atomic(AuUInt64) *)(in), val, __ATOMIC_RELEASE); } else if constexpr (AuIsSame_v) { ATOMIC_PREFIX_HAX(atomic_store)((_Atomic(AuInt64) *)(in), val, __ATOMIC_RELEASE); } else { static_assert(AuIsVoid_v, "T"); } #if !(defined(AURORA_ARCH_X64) || defined(AURORA_ARCH_X86)) __sync_synchronize(); #endif #endif } template <> inline auline void AuAtomicUtils::ClearU8Lock(AuUInt8 *in) { #if defined(AURORA_COMPILER_MSVC) && (defined(AURORA_ARCH_X64) || defined(AURORA_ARCH_X86)) *in = 0; ::_ReadWriteBarrier(); #elif defined(AURORA_COMPILER_MSVC) ::_InterlockedExchange8((volatile char *)in, 0); // i think this will work on aarch64 and most risc architectures //InterlockedAndRelease((volatile LONG *)in, ~0xFF); #else ATOMIC_PREFIX_HAX(atomic_store)((_Atomic(AuUInt8) *)(in), 0, __ATOMIC_RELEASE); #if !(defined(AURORA_ARCH_X64) || defined(AURORA_ARCH_X86)) __sync_synchronize(); #endif #endif } template <> inline auline void AuAtomicUtils::ClearU8Lock(AuUInt32 *in) { #if defined(AU_CPU_ENDIAN_LITTLE) AuAtomicUtils::ClearU8Lock((AuUInt8 *)in); #else AuAtomicUtils::ClearU8Lock(((AuUInt8 *)in) + 3); #endif } template inline auline bool AuAtomicUtils::TestAndSet(T *in, const AuUInt8 offset) { return AuAtomicUtils::Set(in, offset) & (1 << offset); } #if defined(AURORA_COMPILER_MSVC) && (defined(AURORA_ARCH_X64) || defined(AURORA_ARCH_X86)) template <> inline auline bool AuAtomicUtils::TestAndSet(unsigned long *in, const AuUInt8 offset) { return _interlockedbittestandset(reinterpret_cast(in), offset); } template <> inline auline bool AuAtomicUtils::TestAndSet(long *in, const AuUInt8 offset) { return _interlockedbittestandset(reinterpret_cast(in), offset); } template <> inline auline bool AuAtomicUtils::TestAndSet(AuUInt32 *in, const AuUInt8 offset) { return _interlockedbittestandset(reinterpret_cast(in), offset); } template <> inline auline bool AuAtomicUtils::TestAndSet(AuInt32 *in, const AuUInt8 offset) { return _interlockedbittestandset(reinterpret_cast(in), offset); } #if !defined(AURORA_IS_32BIT) template <> inline auline bool AuAtomicUtils::TestAndSet(AuUInt64 *in, const AuUInt8 offset) { return _interlockedbittestandset64(reinterpret_cast(in), offset); } template <> inline auline bool AuAtomicUtils::TestAndSet(AuInt64 *in, const AuUInt8 offset) { return _interlockedbittestandset64(reinterpret_cast(in), offset); } #endif #endif template auline void AuAtomicStore(T *in, T value) { AuAtomicUtils::Store(in, value); } template auline T AuAtomicLoad(T *in) { return AuAtomicUtils::Load(in); } template auline T AuAtomicLoadWeak(T *in) { return AuAtomicUtils::LoadWeak(in); } template auline void AuAtomicClearU8Lock(T *in) { AuAtomicUtils::ClearU8Lock(in); } template auline T AuAtomicOrSetBit(T *in, AuUInt8 offset) { return AuAtomicUtils::Set(in, offset); } template auline bool AuAtomicSet(T *in, AuUInt8 offset) { return AuAtomicOrSetBit(in, offset) & (T(1) << offset); } template auline T AuAtomicAndUnsetBit(T *in, AuUInt8 offset) { return AuAtomicUtils::And(in, ~(T(1) << T(offset))); } template auline bool AuAtomicUnset(T *in, AuUInt8 offset) { auto uBit = T(1) << T(offset); return AuAtomicUtils::And(in, ~(uBit)) & uBit; } template auline T AuAtomicOr(T *in, T value) { return AuAtomicUtils::Or(in, value); } template auline T AuAtomicAnd(T *in, T value) { return AuAtomicUtils::And(in, value); } template auline T AuAtomicAdd(T *in, T addend) { return AuAtomicUtils::Add(in, addend); } template auline T AuAtomicSub(T *in, T minuend) { return AuAtomicUtils::Sub(in, minuend); } template auline T AuAtomicCompareExchange(T *in, T replace, T compare) { return AuAtomicUtils::CompareExchange(in, replace, compare); } template auline bool AuAtomicTestAndSet(T *in, AuUInt8 offset) { return AuAtomicUtils::TestAndSet(in, offset); } template auline void AuAtomicStore(volatile T *in, T value) { AuAtomicUtils::Store((T *)in, value); } template auline T AuAtomicLoad(volatile T *in) { return AuAtomicUtils::Load((T *)in); } template auline T AuAtomicLoadWeak(volatile T *in) { return AuAtomicUtils::LoadWeak((T *)in); } template auline void AuAtomicClearU8Lock(volatile T *in) { AuAtomicUtils::ClearU8Lock((T *)in); } template auline T AuAtomicOrSetBit(volatile T *in, AuUInt8 offset) { return AuAtomicOrSetBit((T *)in, offset); } template auline bool AuAtomicSet(volatile T *in, AuUInt8 offset) { return AuAtomicSet((T *)in, offset); } template auline T AuAtomicAndUnsetBit(volatile T *in, AuUInt8 offset) { return AuAtomicAndUnsetBit((T *)in, offset); } template auline bool AuAtomicUnset(volatile T *in, AuUInt8 offset) { return AuAtomicUnset((T *)in, offset); } template auline T AuAtomicOr(volatile T *in, T value) { return AuAtomicOr((T *)in, value); } template auline T AuAtomicAnd(volatile T *in, T value) { return AuAtomicAnd((T *)in, value); } template auline T AuAtomicAdd(volatile T *in, T addend) { return AuAtomicUtils::Add((T *)(in), addend); } template auline T AuAtomicSub(volatile T *in, T minuend) { return AuAtomicUtils::Sub((T *)(in), minuend); } template auline T AuAtomicCompareExchange(volatile T *in, T replace, T compare) { return AuAtomicUtils::CompareExchange((T *)(in), replace, compare); } template auline bool AuAtomicTestAndSet(volatile T *in, AuUInt8 offset) { return AuAtomicUtils::TestAndSet((T *)(in), offset); }