// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. // // Volatile.h // // // Defines the Volatile type, which provides uniform volatile-ness on // Visual C++ and GNU C++. // // Visual C++ treats accesses to volatile variables as follows: no read or write // can be removed by the compiler, no global memory access can be moved backwards past // a volatile read, and no global memory access can be moved forward past a volatile // write. // // The GCC volatile semantic is straight out of the C standard: the compiler is not // allowed to remove accesses to volatile variables, and it is not allowed to reorder // volatile accesses relative to other volatile accesses. It is allowed to freely // reorder non-volatile accesses relative to volatile accesses. // // We have lots of code that assumes that ordering of non-volatile accesses will be // constrained relative to volatile accesses. For example, this pattern appears all // over the place: // // static volatile int lock = 0; // // while (InterlockedCompareExchange(&lock, 0, 1)) // { // //spin // } // // //read and write variables protected by the lock // // lock = 0; // // This depends on the reads and writes in the critical section not moving past the // final statement, which releases the lock. If this should happen, then you have an // unintended race. // // The solution is to ban the use of the "volatile" keyword, and instead define our // own type Volatile, which acts like a variable of type T except that accesses to // the variable are always given VC++'s volatile semantics. // // (NOTE: The code above is not intended to be an example of how a spinlock should be // implemented; it has many flaws, and should not be used. This code is intended only // to illustrate where we might get into trouble with GCC's volatile semantics.) // // @TODO: many of the variables marked volatile in the CLR do not actually need to be // volatile. For example, if a variable is just always passed to Interlocked functions // (such as a refcount variable), there is no need for it to be volatile. A future // cleanup task should be to examine each volatile variable and make them non-volatile // if possible. // // @TODO: link to a "Memory Models for CLR Devs" doc here (this doc does not yet exist). // #ifndef _VOLATILE_H_ #define _VOLATILE_H_ #include "staticcontract.h" // // This code is extremely compiler- and CPU-specific, and will need to be altered to // support new compilers and/or CPUs. Here we enforce that we can only compile using // VC++, or GCC on x86 or AMD64. // #if !defined(_MSC_VER) && !defined(__GNUC__) #error The Volatile type is currently only defined for Visual C++ and GNU C++ #endif #if defined(__GNUC__) && !defined(_X86_) && !defined(_AMD64_) && !defined(_ARM_) && !defined(_ARM64_) #error The Volatile type is currently only defined for GCC when targeting x86, AMD64, ARM or ARM64 CPUs #endif #if defined(__GNUC__) #if defined(_ARM_) || defined(_ARM64_) // This is functionally equivalent to the MemoryBarrier() macro used on ARM on Windows. #define VOLATILE_MEMORY_BARRIER() asm volatile ("dmb ish" : : : "memory") #else // // For GCC, we prevent reordering by the compiler by inserting the following after a volatile // load (to prevent subsequent operations from moving before the read), and before a volatile // write (to prevent prior operations from moving past the write). We don't need to do anything // special to prevent CPU reorderings, because the x86 and AMD64 architectures are already // sufficiently constrained for our purposes. If we ever need to run on weaker CPU architectures // (such as PowerPC), then we will need to do more work. // // Please do not use this macro outside of this file. It is subject to change or removal without // notice. // #define VOLATILE_MEMORY_BARRIER() asm volatile ("" : : : "memory") #endif // _ARM_ || _ARM64_ #elif (defined(_ARM_) || defined(_ARM64_)) && _ISO_VOLATILE // ARM & ARM64 have a very weak memory model and very few tools to control that model. We're forced to perform a full // memory barrier to preserve the volatile semantics. Technically this is only necessary on MP systems but we // currently don't have a cheap way to determine the number of CPUs from this header file. Revisit this if it // turns out to be a performance issue for the uni-proc case. #define VOLATILE_MEMORY_BARRIER() MemoryBarrier() #else // // On VC++, reorderings at the compiler and machine level are prevented by the use of the // "volatile" keyword in VolatileLoad and VolatileStore. This should work on any CPU architecture // targeted by VC++ with /iso_volatile-. // #define VOLATILE_MEMORY_BARRIER() #endif // __GNUC__ template struct RemoveVolatile { typedef T type; }; template struct RemoveVolatile { typedef T type; }; // // VolatileLoad loads a T from a pointer to T. It is guaranteed that this load will not be optimized // away by the compiler, and that any operation that occurs after this load, in program order, will // not be moved before this load. In general it is not guaranteed that the load will be atomic, though // this is the case for most aligned scalar data types. If you need atomic loads or stores, you need // to consult the compiler and CPU manuals to find which circumstances allow atomicity. // // Starting at version 3.8, clang errors out on initializing of type int * to volatile int *. To fix this, we add two templates to cast away volatility // Helper structures for casting away volatileness template inline T VolatileLoad(T const * pt) { STATIC_CONTRACT_SUPPORTS_DAC_HOST_ONLY; #ifndef DACCESS_COMPILE #if defined(_ARM64_) && defined(__GNUC__) T val; static const unsigned lockFreeAtomicSizeMask = (1 << 1) | (1 << 2) | (1 << 4) | (1 << 8); if((1 << sizeof(T)) & lockFreeAtomicSizeMask) { __atomic_load((T const *)pt, const_cast::type *>(&val), __ATOMIC_ACQUIRE); } else { val = *(T volatile const *)pt; asm volatile ("dmb ishld" : : : "memory"); } #else T val = *(T volatile const *)pt; VOLATILE_MEMORY_BARRIER(); #endif #else T val = *pt; #endif return val; } template inline T VolatileLoadWithoutBarrier(T const * pt) { STATIC_CONTRACT_SUPPORTS_DAC_HOST_ONLY; #ifndef DACCESS_COMPILE T val = *(T volatile const *)pt; #else T val = *pt; #endif return val; } template class Volatile; template inline T VolatileLoad(Volatile const * pt) { STATIC_CONTRACT_SUPPORTS_DAC; return pt->Load(); } // // VolatileStore stores a T into the target of a pointer to T. Is is guaranteed that this store will // not be optimized away by the compiler, and that any operation that occurs before this store, in program // order, will not be moved after this store. In general, it is not guaranteed that the store will be // atomic, though this is the case for most aligned scalar data types. If you need atomic loads or stores, // you need to consult the compiler and CPU manuals to find which circumstances allow atomicity. // template inline void VolatileStore(T* pt, T val) { STATIC_CONTRACT_SUPPORTS_DAC_HOST_ONLY; #ifndef DACCESS_COMPILE #if defined(_ARM64_) && defined(__GNUC__) static const unsigned lockFreeAtomicSizeMask = (1 << 1) | (1 << 2) | (1 << 4) | (1 << 8); if((1 << sizeof(T)) & lockFreeAtomicSizeMask) { __atomic_store((T volatile *)pt, &val, __ATOMIC_RELEASE); } else { VOLATILE_MEMORY_BARRIER(); *(T volatile *)pt = val; } #else VOLATILE_MEMORY_BARRIER(); *(T volatile *)pt = val; #endif #else *pt = val; #endif } template inline void VolatileStoreWithoutBarrier(T* pt, T val) { STATIC_CONTRACT_SUPPORTS_DAC_HOST_ONLY; #ifndef DACCESS_COMPILE *(T volatile *)pt = val; #else *pt = val; #endif } // // Volatile implements accesses with our volatile semantics over a variable of type T. // Wherever you would have used a "volatile Foo" or, equivalently, "Foo volatile", use Volatile // instead. If Foo is a pointer type, use VolatilePtr. // // Note that there are still some things that don't work with a Volatile, // that would have worked with a "volatile T". For example, you can't cast a Volatile to a float. // You must instead cast to an int, then to a float. Or you can call Load on the Volatile, and // cast the result to a float. In general, calling Load or Store explicitly will work around // any problems that can't be solved by operator overloading. // // @TODO: it's not clear that we actually *want* any operator overloading here. It's in here primarily // to ease the task of converting all of the old uses of the volatile keyword, but in the long // run it's probably better if users of this class are forced to call Load() and Store() explicitly. // This would make it much more clear where the memory barriers are, and which operations are actually // being performed, but it will have to wait for another cleanup effort. // template class Volatile { private: // // The data which we are treating as volatile // T m_val; public: // // Default constructor. Results in an unitialized value! // inline Volatile() { STATIC_CONTRACT_SUPPORTS_DAC; } // // Allow initialization of Volatile from a T // inline Volatile(const T& val) { STATIC_CONTRACT_SUPPORTS_DAC; ((volatile T &)m_val) = val; } // // Copy constructor // inline Volatile(const Volatile& other) { STATIC_CONTRACT_SUPPORTS_DAC; ((volatile T &)m_val) = other.Load(); } // // Loads the value of the volatile variable. See code:VolatileLoad for the semantics of this operation. // inline T Load() const { STATIC_CONTRACT_SUPPORTS_DAC; return VolatileLoad(&m_val); } // // Loads the value of the volatile variable atomically without erecting the memory barrier. // inline T LoadWithoutBarrier() const { STATIC_CONTRACT_SUPPORTS_DAC; return ((volatile T &)m_val); } // // Stores a new value to the volatile variable. See code:VolatileStore for the semantics of this // operation. // inline void Store(const T& val) { STATIC_CONTRACT_SUPPORTS_DAC; VolatileStore(&m_val, val); } // // Stores a new value to the volatile variable atomically without erecting the memory barrier. // inline void StoreWithoutBarrier(const T& val) const { STATIC_CONTRACT_SUPPORTS_DAC; ((volatile T &)m_val) = val; } // // Gets a pointer to the volatile variable. This is dangerous, as it permits the variable to be // accessed without using Load and Store, but it is necessary for passing Volatile to APIs like // InterlockedIncrement. // inline volatile T* GetPointer() { return (volatile T*)&m_val; } // // Gets the raw value of the variable. This is dangerous, as it permits the variable to be // accessed without using Load and Store // inline T& RawValue() { return m_val; } // // Allow casts from Volatile to T. Note that this allows implicit casts, so you can // pass a Volatile directly to a method that expects a T. // inline operator T() const { STATIC_CONTRACT_SUPPORTS_DAC; return this->Load(); } // // Assignment from T // inline Volatile& operator=(T val) {Store(val); return *this;} // // Get the address of the volatile variable. This is dangerous, as it allows the value of the // volatile variable to be accessed directly, without going through Load and Store, but it is // necessary for passing Volatile to APIs like InterlockedIncrement. Note that we are returning // a pointer to a volatile T here, so we cannot accidentally pass this pointer to an API that // expects a normal pointer. // inline T volatile * operator&() {return this->GetPointer();} inline T volatile const * operator&() const {return this->GetPointer();} // // Comparison operators // template inline bool operator==(const TOther& other) const {return this->Load() == other;} template inline bool operator!=(const TOther& other) const {return this->Load() != other;} // // Miscellaneous operators. Add more as necessary. // inline Volatile& operator+=(T val) {Store(this->Load() + val); return *this;} inline Volatile& operator-=(T val) {Store(this->Load() - val); return *this;} inline Volatile& operator|=(T val) {Store(this->Load() | val); return *this;} inline Volatile& operator&=(T val) {Store(this->Load() & val); return *this;} inline bool operator!() const { STATIC_CONTRACT_SUPPORTS_DAC; return !this->Load();} // // Prefix increment // inline Volatile& operator++() {this->Store(this->Load()+1); return *this;} // // Postfix increment // inline T operator++(int) {T val = this->Load(); this->Store(val+1); return val;} // // Prefix decrement // inline Volatile& operator--() {this->Store(this->Load()-1); return *this;} // // Postfix decrement // inline T operator--(int) {T val = this->Load(); this->Store(val-1); return val;} }; // // A VolatilePtr builds on Volatile by adding operators appropriate to pointers. // Wherever you would have used "Foo * volatile", use "VolatilePtr" instead. // // VolatilePtr also allows the substution of other types for the underlying pointer. This // allows you to wrap a VolatilePtr around a custom type that looks like a pointer. For example, // if what you want is a "volatile DPTR", use "VolatilePtr>". // template class VolatilePtr : public Volatile

{ public: // // Default constructor. Results in an uninitialized pointer! // inline VolatilePtr() { STATIC_CONTRACT_SUPPORTS_DAC; } // // Allow assignment from the pointer type. // inline VolatilePtr(P val) : Volatile

(val) { STATIC_CONTRACT_SUPPORTS_DAC; } // // Copy constructor // inline VolatilePtr(const VolatilePtr& other) : Volatile

(other) { STATIC_CONTRACT_SUPPORTS_DAC; } // // Cast to the pointer type // inline operator P() const { STATIC_CONTRACT_SUPPORTS_DAC; return (P)this->Load(); } // // Member access // inline P operator->() const { STATIC_CONTRACT_SUPPORTS_DAC; return (P)this->Load(); } // // Dereference the pointer // inline T& operator*() const { STATIC_CONTRACT_SUPPORTS_DAC; return *(P)this->Load(); } // // Access the pointer as an array // template inline T& operator[](TIndex index) { STATIC_CONTRACT_SUPPORTS_DAC; return ((P)this->Load())[index]; } }; // // From here on out, we ban the use of the "volatile" keyword. If you found this while trying to define // a volatile variable, go to the top of this file and start reading. // #ifdef volatile #undef volatile #endif // ***** Temporarily removing this to unblock integration with new VC++ bits //#define volatile (DoNotUseVolatileKeyword) volatile // The substitution for volatile above is defined in such a way that we can still explicitly access the // volatile keyword without error using the macros below. Use with care. //#define REMOVE_DONOTUSE_ERROR(x) //#define RAW_KEYWORD(x) REMOVE_DONOTUSE_ERROR x #define RAW_KEYWORD(x) x #ifdef DACCESS_COMPILE // No need to use volatile in DAC builds - DAC is single-threaded and the target // process is suspended. #define VOLATILE(T) T #else // Disable use of Volatile for GC/HandleTable code except on platforms where it's absolutely necessary. #if defined(_MSC_VER) && !defined(_ARM_) && !defined(_ARM64_) #define VOLATILE(T) T RAW_KEYWORD(volatile) #else #define VOLATILE(T) Volatile #endif #endif // DACCESS_COMPILE // VolatilePtr-specific clr::SafeAddRef and clr::SafeRelease namespace clr { template < typename ItfT, typename PtrT > inline #ifdef __checkReturn // Volatile.h is used in corunix headers, which don't define/nullify SAL. __checkReturn #endif VolatilePtr& SafeAddRef(VolatilePtr& pItf) { STATIC_CONTRACT_LIMITED_METHOD; SafeAddRef(pItf.Load()); return pItf; } template < typename ItfT, typename PtrT > inline ULONG SafeRelease(VolatilePtr& pItf) { STATIC_CONTRACT_LIMITED_METHOD; return SafeRelease(pItf.Load()); } } #endif //_VOLATILE_H_