/////////////////////////////////////////////////////////////////////////////// // Copyright (c) Electronic Arts Inc. All rights reserved. /////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// // Defines functionality for thread-safe primitive operations. // // EAThread atomics do NOT imply the use of read/write barriers. This is // partly due to historical reasons and partly due to EAThread's internal // code being optimized for not using barriers. // // In future, we are considering migrating the atomics interface which // defaults atomics to use full read/write barriers while allowing users // to opt-out of full barrier usage. The new C++11 interface already provides // similar interfaces. // // http://en.cppreference.com/w/cpp/atomic/memory_order ///////////////////////////////////////////////////////////////////////////// #ifndef EATHREAD_EATHREAD_ATOMIC_H #define EATHREAD_EATHREAD_ATOMIC_H #include EA_DISABLE_ALL_VC_WARNINGS() #include EA_RESTORE_ALL_VC_WARNINGS() #include #include #include #if !EA_THREADS_AVAILABLE // Do nothing. Let the default implementation below be used. #elif defined(EA_USE_COMMON_ATOMICINT_IMPLEMENTATION) && EA_USE_COMMON_ATOMICINT_IMPLEMENTATION #include #elif defined(EA_PROCESSOR_X86) || (defined(EA_PLATFORM_WINRT) && defined(EA_PROCESSOR_ARM)) #include #elif defined(EA_PROCESSOR_X86_64) #include #elif defined(EA_PLATFORM_ANDROID) && EATHREAD_C11_ATOMICS_AVAILABLE #include // Android API 21+ only support C11 atomics #else #error Platform not supported yet. #endif #if defined(EA_PRAGMA_ONCE_SUPPORTED) #pragma once // Some compilers (e.g. VC++) benefit significantly from using this. We've measured 3-4% build speed improvements in apps as a result. #endif // EATHREAD_ATOMIC_128_SUPPORTED // // Defined as 0 or 1. Defined as 1 whenever possible for the given compiler/platform combination. // Defines if 128 bit atomic operations are supported. // Such operations are only ever supported on 64 bit platforms. // #ifndef EATHREAD_ATOMIC_128_SUPPORTED // If not defined by one of the above headers... #define EATHREAD_ATOMIC_128_SUPPORTED 0 #endif namespace EA { namespace Thread { enum Atomic64Implementation { kAtomic64Emulated, kAtomic64Native }; /// SetDoubleWordAtomicsImplementation /// Some platforms have multiple implementations, some of which support /// double word atomics and some that don't. For example, certain ARM /// processors will support the ldrexd/strexd atomic instructions but /// others will not. EATHREADLIB_API void SetAtomic64Implementation(Atomic64Implementation implementation); } } #if !defined(EA_THREAD_ATOMIC_IMPLEMENTED) // If there wasn't a processor-specific version already defined... // Fail the build if atomics aren't being defined for the given platform/compiler. // If we need to add an exception here, we can add an appropriate ifdef. static_assert(false, "atomic operations must be defined for this platform."); namespace EA { namespace Thread { /// Standalone atomic functions /// These act the same as the class functions below. /// The T return values are the previous value, except for the /// AtomicFetchSwap function which returns the swapped out value. /// /// T AtomicGetValue(volatile T*); /// T AtomicGetValue(const volatile T*); /// void AtomicSetValue(volatile T*, T value); /// T AtomicFetchIncrement(volatile T*); /// T AtomicFetchDecrement(volatile T*); /// T AtomicFetchAdd(volatile T*, T value); /// T AtomicFetchSub(volatile T*, T value); /// T AtomicFetchOr(volatile T*, T value); /// T AtomicFetchAnd(volatile T*, T value); /// T AtomicFetchXor(volatile T*, T value); /// T AtomicFetchSwap(volatile T*, T value); /// T AtomicFetchSwapConditional(volatile T*, T value, T condition); /// bool AtomicSetValueConditional(volatile T*, T value, T condition); /// class AtomicInt /// /// Implements thread-safe access to an integer and primary operations on that integer. /// AtomicIntegers are commonly used as lightweight flags and signals between threads /// or as the synchronization object for spinlocks. Those familiar with the Win32 API /// will find that AtomicInt32 is essentially a platform independent interface to /// the Win32 InterlockedXXX family of functions. Those familiar with Linux may /// find that AtomicInt32 is essentially a platform independent interface to atomic_t /// functionality. /// /// Note that the reference implementation defined here is itself not thread-safe. /// A thread-safe version requires platform-specific code. /// /// Example usage /// AtomicInt32 i = 0; /// /// ++i; /// i--; /// i += 7; /// i -= 3; /// i = 2; /// /// int x = i.GetValue(); /// i.Increment(); /// bool oldValueWas6 = i.SetValueConditional(3, 6); /// i.Add(4); /// template class EATHREADLIB_API AtomicInt { public: /// ThisType /// A typedef for this class type itself, for usage clarity. typedef AtomicInt ThisType; /// ValueType /// A typedef for the basic object we work with. typedef T ValueType; /// AtomicInt /// Empty constructor. Intentionally leaves mValue in an unspecified state. /// This is done so that an AtomicInt acts like a standard built-in integer. AtomicInt() {} /// AtomicInt /// Constructs with an intial value. AtomicInt(ValueType n) : mValue(n) {} /// AtomicInt /// Copy ctor. Uses GetValue to read the value, and thus is synchronized. AtomicInt(const ThisType& x) : mValue(x.GetValue()) {} /// AtomicInt /// Assignment operator. Uses GetValue to read the value, and thus is synchronized. AtomicInt& operator=(const ThisType& x) { mValue = x.GetValue(); return *this; } /// GetValue /// Safely gets the current value. A platform-specific version of /// this might need to do something more than just read the value. ValueType GetValue() const { return mValue; } /// GetValueRaw /// "Unsafely" gets the current value. This is useful for algorithms /// that want to poll the value in a high performance way before /// reading or setting the value in a more costly thread-safe way. /// You should not use this function when attempting to do thread-safe /// atomic operations. ValueType GetValueRaw() const { return mValue; } /// SetValue /// Safely sets a new value. Returns the old value. Note that due to /// expected multithreaded accesses, a call to GetValue after SetValue /// might return a different value then what was set with SetValue. /// This of course depends on your situation. ValueType SetValue(ValueType n) { const ValueType nOldValue(mValue); mValue = n; return nOldValue; } /// SetValueConditional /// Safely set the value to a new value if the original value is equal to /// a condition value. Returns true if the condition was met and the /// assignment occurred. The comparison and value setting are done as /// an atomic operation and thus another thread cannot intervene between /// the two as would be the case with simple C code. bool SetValueConditional(ValueType n, ValueType condition) { if(mValue == condition) { mValue = n; return true; } return false; } /// Increment /// Safely increments the value. Returns the new value. /// This function acts the same as the C++ pre-increment operator. ValueType Increment() { return ++mValue; } /// Decrement /// Safely decrements the value. Returns the new value. /// This function acts the same as the C++ pre-decrement operator. ValueType Decrement() { return --mValue; } /// Add /// Safely adds a value, which can be negative. Returns the new value. /// You can implement subtraction with this function by using a negative argument. ValueType Add(ValueType n) { return (mValue += n); } /// operators /// These allow an AtomicInt object to safely act like a built-in type. /// /// Note: The operators for AtomicInt behaves differently than standard /// C++ operators in that it will always return a ValueType instead /// of a reference. /// /// cast operator /// Returns the AtomicInt value as an integral type. This allows the /// AtomicInt to behave like a standard built-in integer type. operator const ValueType() const { return mValue; } /// operator = /// Assigns a new value and returns the value after the operation. /// ValueType operator=(ValueType n) { mValue = n; return n; } /// pre-increment operator+= /// Adds a value to the AtomicInt and returns the value after the operation. /// /// This function doesn't obey the C++ standard in that it does not return /// a reference, but rather the value of the AtomicInt after the /// operation is complete. It must be noted that this design is motivated by /// the fact that it is unsafe to rely on the returned value being equal to /// the previous value + n, as another thread might have modified the AtomicInt /// immediately after the subtraction operation. So rather than returning the /// reference of AtomicInt, the function returns a copy of the AtomicInt value /// used in the function. ValueType operator+=(ValueType n) { mValue += n; return mValue; } /// pre-increment operator-= /// Subtracts a value to the AtomicInt and returns the value after the operation. /// /// This function doesn't obey the C++ standard in that it does not return // a reference, but rather the value of the AtomicInt after the /// operation is complete. It must be noted that this design is motivated by /// the fact that it is unsafe to rely on the returned value being equal to /// the previous value - n, as another thread might have modified the AtomicInt /// immediately after the subtraction operation. So rather than returning the /// reference of AtomicInt, the function returns a copy of the AtomicInt value /// used in the function. ValueType operator-=(ValueType n) { mValue -= n; return mValue; } /// pre-increment operator++ /// Increments the AtomicInt. /// /// This function doesn't obey the C++ standard in that it does not return // a reference, but rather the value of the AtomicInt after the /// operation is complete. It must be noted that this design is motivated by /// the fact that it is unsafe to rely on the returned value being equal to /// the previous value + 1, as another thread might have modified the AtomicInt /// immediately after the subtraction operation. So rather than returning the /// reference of AtomicInt, the function returns a copy of the AtomicInt value /// used in the function. ValueType operator++() { return ++mValue; } /// post-increment operator++ /// Increments the AtomicInt and returns the value of the AtomicInt before /// the increment operation. /// /// This function doesn't obey the C++ standard in that it does not return // a reference, but rather the value of the AtomicInt after the /// operation is complete. It must be noted that this design is motivated by /// the fact that it is unsafe to rely on the returned value being equal to /// the previous value, as another thread might have modified the AtomicInt /// immediately after the subtraction operation. So rather than returning the /// reference of AtomicInt, the function returns a copy of the AtomicInt value /// used in the function. ValueType operator++(int) { return mValue++; } /// pre-increment operator-- /// Decrements the AtomicInt. /// /// This function doesn't obey the C++ standard in that it does not return // a reference, but rather the value of the AtomicInt after the /// operation is complete. It must be noted that this design is motivated by /// the fact that it is unsafe to rely on the returned value being equal to /// the previous value - 1, as another thread might have modified the AtomicInt /// immediately after the subtraction operation. So rather than returning the /// reference of AtomicInt, the function returns a copy of the AtomicInt value /// used in the function. ValueType operator--() { return --mValue; } /// post-increment operator-- /// Increments the AtomicInt and returns the value of the AtomicInt before /// the increment operation. /// /// This function doesn't obey the C++ standard in that it does not return // a reference, but rather the value of the AtomicInt after the /// operation is complete. It must be noted that this design is motivated by /// the fact that it is unsafe to rely on the returned value being equal to /// the previous value, as another thread might have modified the AtomicInt /// immediately after the subtraction operation. So rather than returning the /// reference of AtomicInt, the function returns a copy of the AtomicInt value /// used in the function. ValueType operator--(int) { return mValue--;} protected: volatile ValueType mValue; /// May not be the same on all platforms. }; } // namespace Thread } // namespace EA #endif // #if EA_THREAD_ATOMIC_IMPLEMENTED namespace EA { namespace Thread { // Typedefs typedef AtomicInt AtomicInt32; /// int32_t atomic integer. typedef AtomicInt AtomicUint32; /// uint32_t atomic integer. typedef AtomicInt AtomicInt64; /// int64_t atomic integer. typedef AtomicInt AtomicUint64; /// uint64_t atomic integer. #if !defined(EA_PLATFORM_WORD_SIZE) || (EA_PLATFORM_WORD_SIZE == 4) typedef AtomicInt32 AtomicIWord; typedef AtomicUint32 AtomicUWord; #else typedef AtomicInt64 AtomicIWord; typedef AtomicUint64 AtomicUWord; #endif #if !defined(EA_PLATFORM_PTR_SIZE) || (EA_PLATFORM_PTR_SIZE == 4) typedef AtomicInt32 AtomicIntPtr; typedef AtomicUint32 AtomicUintPtr; #else typedef AtomicInt64 AtomicIntPtr; typedef AtomicUint64 AtomicUintPtr; #endif // VC++ yields spurious warnings about void* being cast to an integer type and vice-versa. // These warnings are baseless because we check for platform pointer size above. EA_DISABLE_VC_WARNING(4311 4312 4251) /// class AtomicPointer /// /// For simplicity of the current implementation, we simply have AtomicPointer map /// to AtomicInt32. This is reasonably safe because AtomicInt32 uses intptr_t /// as its ValueType and there are no foreseeble supported platforms in which /// intptr_t will not exist or be possible as a data type. /// class EATHREADLIB_API AtomicPointer : public AtomicIntPtr { public: typedef void* PointerValueType; AtomicPointer(void* p = NULL) : AtomicIntPtr(static_cast(reinterpret_cast(p))) {} AtomicPointer& operator=(void* p) { AtomicIntPtr::operator=(static_cast(reinterpret_cast(p))); return *this; } operator const void*() const // It's debateable whether this should be supported. { return (void*)AtomicIntPtr::GetValue(); } void* GetValue() const { return (void*)AtomicIntPtr::GetValue(); } void* GetValueRaw() const { return (void*)AtomicIntPtr::GetValueRaw(); } void* SetValue(void* p) { return (void*)AtomicIntPtr::SetValue(static_cast(reinterpret_cast(p))); } bool SetValueConditional(void* p, void* pCondition) { return AtomicIntPtr::SetValueConditional(static_cast(reinterpret_cast(p)), static_cast(reinterpret_cast(pCondition))); } }; EA_RESTORE_VC_WARNING() } // namespace Thread } // namespace EA #endif // EATHREAD_EATHREAD_ATOMIC_H