diff options
Diffstat (limited to 'src/vm/syncblk.h')
-rw-r--r-- | src/vm/syncblk.h | 1390 |
1 files changed, 1390 insertions, 0 deletions
diff --git a/src/vm/syncblk.h b/src/vm/syncblk.h new file mode 100644 index 0000000000..6d32e3eafa --- /dev/null +++ b/src/vm/syncblk.h @@ -0,0 +1,1390 @@ +// 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. +// +// SYNCBLK.H +// + +// +// Definition of a SyncBlock and the SyncBlockCache which manages it + +// See file:#SyncBlockOverview Sync block overview + +#ifndef _SYNCBLK_H_ +#define _SYNCBLK_H_ + +#include "util.hpp" +#include "slist.h" +#include "crst.h" +#include "handletable.h" +#include "vars.hpp" + +// #SyncBlockOverview +// +// Every Object is preceded by an ObjHeader (at a negative offset). The code:ObjHeader has an index to a +// code:SyncBlock. This index is 0 for the bulk of all instances, which indicates that the object shares a +// dummy SyncBlock with most other objects. +// +// The SyncBlock is primarily responsible for object synchronization. However, it is also a "kitchen sink" of +// sparsely allocated instance data. For instance, the default implementation of Hash() is based on the +// existence of a code:SyncTableEntry. And objects exposed to or from COM, or through context boundaries, can +// store sparse data here. +// +// SyncTableEntries and SyncBlocks are allocated in non-GC memory. A weak pointer from the SyncTableEntry to +// the instance is used to ensure that the SyncBlock and SyncTableEntry are reclaimed (recycled) when the +// instance dies. +// +// The organization of the SyncBlocks isn't intuitive (at least to me). Here's the explanation: +// +// Before each Object is an code:ObjHeader. If the object has a code:SyncBlock, the code:ObjHeader contains a +// non-0 index to it. +// +// The index is looked up in the code:g_pSyncTable of SyncTableEntries. This means the table is consecutive +// for all outstanding indices. Whenever it needs to grow, it doubles in size and copies all the original +// entries. The old table is kept until GC time, when it can be safely discarded. +// +// Each code:SyncTableEntry has a backpointer to the object and a forward pointer to the actual SyncBlock. +// The SyncBlock is allocated out of a SyncBlockArray which is essentially just a block of SyncBlocks. +// +// The code:SyncBlockArray s are managed by a code:SyncBlockCache that handles the actual allocations and +// frees of the blocks. +// +// So... +// +// Each allocation and release has to handle free lists in the table of entries and the table of blocks. +// +// We burn an extra 4 bytes for the pointer from the SyncTableEntry to the SyncBlock. +// +// The reason for this is that many objects have a SyncTableEntry but no SyncBlock. That's because someone +// (e.g. HashTable) called Hash() on them. +// +// Incidentally, there's a better write-up of all this stuff in the archives. + +#ifdef _TARGET_X86_ +#include <pshpack4.h> +#endif // _TARGET_X86_ + +// forwards: +class SyncBlock; +class SyncBlockCache; +class SyncTableEntry; +class SyncBlockArray; +class AwareLock; +class Thread; +class AppDomain; + +#ifdef EnC_SUPPORTED +class EnCSyncBlockInfo; +typedef DPTR(EnCSyncBlockInfo) PTR_EnCSyncBlockInfo; + +#endif // EnC_SUPPORTED + +#include "eventstore.hpp" + +#include "eventstore.hpp" + +#include "synch.h" + + +// At a negative offset from each Object is an ObjHeader. The 'size' of the +// object includes these bytes. However, we rely on the previous object allocation +// to zero out the ObjHeader for the current allocation. And the limits of the +// GC space are initialized to respect this "off by one" error. + +// m_SyncBlockValue is carved up into an index and a set of bits. Steal bits by +// reducing the mask. We use the very high bit, in _DEBUG, to be sure we never forget +// to mask the Value to obtain the Index + + // These first three are only used on strings (If the first one is on, we know whether + // the string has high byte characters, and the second bit tells which way it is. + // Note that we are reusing the FINALIZER_RUN bit since strings don't have finalizers, + // so the value of this bit does not matter for strings +#define BIT_SBLK_STRING_HAS_NO_HIGH_CHARS 0x80000000 + +// Used as workaround for infinite loop case. Will set this bit in the sblk if we have already +// seen this sblk in our agile checking logic. Problem is seen when object 1 has a ref to object 2 +// and object 2 has a ref to object 1. The agile checker will infinitely loop on these references. +#define BIT_SBLK_AGILE_IN_PROGRESS 0x80000000 +#define BIT_SBLK_STRING_HIGH_CHARS_KNOWN 0x40000000 +#define BIT_SBLK_STRING_HAS_SPECIAL_SORT 0xC0000000 +#define BIT_SBLK_STRING_HIGH_CHAR_MASK 0xC0000000 + +#define BIT_SBLK_FINALIZER_RUN 0x40000000 +#define BIT_SBLK_GC_RESERVE 0x20000000 + +// This lock is only taken when we need to modify the index value in m_SyncBlockValue. +// It should not be taken if the object already has a real syncblock index. +#define BIT_SBLK_SPIN_LOCK 0x10000000 + +#define BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX 0x08000000 + +// if BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX is clear, the rest of the header dword is layed out as follows: +// - lower ten bits (bits 0 thru 9) is thread id used for the thin locks +// value is zero if no thread is holding the lock +// - following six bits (bits 10 thru 15) is recursion level used for the thin locks +// value is zero if lock is not taken or only taken once by the same thread +// - following 11 bits (bits 16 thru 26) is app domain index +// value is zero if no app domain index is set for the object +#define SBLK_MASK_LOCK_THREADID 0x000003FF // special value of 0 + 1023 thread ids +#define SBLK_MASK_LOCK_RECLEVEL 0x0000FC00 // 64 recursion levels +#define SBLK_LOCK_RECLEVEL_INC 0x00000400 // each level is this much higher than the previous one +#define SBLK_APPDOMAIN_SHIFT 16 // shift right this much to get appdomain index +#define SBLK_RECLEVEL_SHIFT 10 // shift right this much to get recursion level +#define SBLK_MASK_APPDOMAININDEX 0x000007FF // 2048 appdomain indices + +// add more bits here... (adjusting the following mask to make room) + +// if BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX is set, +// then if BIT_SBLK_IS_HASHCODE is also set, the rest of the dword is the hash code (bits 0 thru 25), +// otherwise the rest of the dword is the sync block index (bits 0 thru 25) +#define BIT_SBLK_IS_HASHCODE 0x04000000 + +#define HASHCODE_BITS 26 + +#define MASK_HASHCODE ((1<<HASHCODE_BITS)-1) +#define SYNCBLOCKINDEX_BITS 26 +#define MASK_SYNCBLOCKINDEX ((1<<SYNCBLOCKINDEX_BITS)-1) + +// Spin for about 1000 cycles before waiting longer. +#define BIT_SBLK_SPIN_COUNT 1000 + +// The GC is highly dependent on SIZE_OF_OBJHEADER being exactly the sizeof(ObjHeader) +// We define this macro so that the preprocessor can calculate padding structures. +#ifdef _WIN64 +#define SIZEOF_OBJHEADER 8 +#else // !_WIN64 +#define SIZEOF_OBJHEADER 4 +#endif // !_WIN64 + + +inline void InitializeSpinConstants() +{ + WRAPPER_NO_CONTRACT; + +#if !defined(DACCESS_COMPILE) + g_SpinConstants.dwInitialDuration = g_pConfig->SpinInitialDuration(); + g_SpinConstants.dwMaximumDuration = min(g_pConfig->SpinLimitProcCap(), g_SystemInfo.dwNumberOfProcessors) * g_pConfig->SpinLimitProcFactor() + g_pConfig->SpinLimitConstant(); + g_SpinConstants.dwBackoffFactor = g_pConfig->SpinBackoffFactor(); + g_SpinConstants.dwRepetitions = g_pConfig->SpinRetryCount(); +#endif +} + +// this is a 'GC-aware' Lock. It is careful to enable preemptive GC before it +// attempts any operation that can block. Once the operation is finished, it +// restores the original state of GC. + +// AwareLocks can only be created inside SyncBlocks, since they depend on the +// enclosing SyncBlock for coordination. This is enforced by the private ctor. +typedef DPTR(class AwareLock) PTR_AwareLock; + +class AwareLock +{ + friend class CheckAsmOffsets; + + friend class SyncBlockCache; + friend class SyncBlock; + +public: + Volatile<LONG> m_MonitorHeld; + ULONG m_Recursion; + PTR_Thread m_HoldingThread; + + private: + LONG m_TransientPrecious; + + + // This is a backpointer from the syncblock to the synctable entry. This allows + // us to recover the object that holds the syncblock. + DWORD m_dwSyncIndex; + + CLREvent m_SemEvent; + + // Only SyncBlocks can create AwareLocks. Hence this private constructor. + AwareLock(DWORD indx) + : m_MonitorHeld(0), + m_Recursion(0), +#ifndef DACCESS_COMPILE +// PreFAST has trouble with intializing a NULL PTR_Thread. + m_HoldingThread(NULL), +#endif // DACCESS_COMPILE + m_TransientPrecious(0), + m_dwSyncIndex(indx) + { + LIMITED_METHOD_CONTRACT; + } + + ~AwareLock() + { + LIMITED_METHOD_CONTRACT; + // We deliberately allow this to remain incremented if an exception blows + // through a lock attempt. This simply prevents the GC from aggressively + // reclaiming a particular syncblock until the associated object is garbage. + // From a perf perspective, it's not worth using SEH to prevent this from + // happening. + // + // _ASSERTE(m_TransientPrecious == 0); + } + +#if defined(ENABLE_CONTRACTS_IMPL) + // The LOCK_TAKEN/RELEASED macros need a "pointer" to the lock object to do + // comparisons between takes & releases (and to provide debugging info to the + // developer). Since AwareLocks are always allocated embedded inside SyncBlocks, + // and since SyncBlocks don't move (unlike the GC objects that use + // the syncblocks), it's safe for us to just use the AwareLock pointer directly + void * GetPtrForLockContract() + { + return (void *) this; + } +#endif // defined(ENABLE_CONTRACTS_IMPL) + +public: + enum EnterHelperResult { + EnterHelperResult_Entered, + EnterHelperResult_Contention, + EnterHelperResult_UseSlowPath + }; + + enum LeaveHelperAction { + LeaveHelperAction_None, + LeaveHelperAction_Signal, + LeaveHelperAction_Yield, + LeaveHelperAction_Contention, + LeaveHelperAction_Error, + }; + + // Helper encapsulating the fast path entering monitor. Returns what kind of result was achieved. + AwareLock::EnterHelperResult EnterHelper(Thread* pCurThread); + AwareLock::EnterHelperResult EnterHelperSpin(Thread* pCurThread, INT32 timeOut = -1); + + // Helper encapsulating the core logic for leaving monitor. Returns what kind of + // follow up action is necessary + AwareLock::LeaveHelperAction LeaveHelper(Thread* pCurThread); + + void Enter(); + BOOL TryEnter(INT32 timeOut = 0); + BOOL EnterEpilog(Thread *pCurThread, INT32 timeOut = INFINITE); + BOOL EnterEpilogHelper(Thread *pCurThread, INT32 timeOut); + BOOL Leave(); + + void Signal() + { + WRAPPER_NO_CONTRACT; + + // CLREvent::SetMonitorEvent works even if the event has not been intialized yet + m_SemEvent.SetMonitorEvent(); + } + + bool Contention(INT32 timeOut = INFINITE); + void AllocLockSemEvent(); + LONG LeaveCompletely(); + BOOL OwnedByCurrentThread(); + + void IncrementTransientPrecious() + { + LIMITED_METHOD_CONTRACT; + FastInterlockIncrement(&m_TransientPrecious); + _ASSERTE(m_TransientPrecious > 0); + } + + void DecrementTransientPrecious() + { + LIMITED_METHOD_CONTRACT; + _ASSERTE(m_TransientPrecious > 0); + FastInterlockDecrement(&m_TransientPrecious); + } + + DWORD GetSyncBlockIndex(); + + void SetPrecious(); + + // Provide access to the object associated with this awarelock, so client can + // protect it. + inline OBJECTREF GetOwningObject(); + + // Provide access to the Thread object that owns this awarelock. This is used + // to provide a host to find out owner of a lock. + inline PTR_Thread GetOwningThread() + { + LIMITED_METHOD_CONTRACT; + return m_HoldingThread; + } + + // Do we have waiters? + inline BOOL HasWaiters() + { + LIMITED_METHOD_CONTRACT; + return (m_MonitorHeld >> 1) > 0; + } +}; + +#ifdef FEATURE_COMINTEROP +class ComCallWrapper; +class ComClassFactory; +struct RCW; +class RCWHolder; +typedef DPTR(class ComCallWrapper) PTR_ComCallWrapper; +#endif // FEATURE_COMINTEROP + +class InteropSyncBlockInfo +{ + friend class RCWHolder; + +public: +#ifndef FEATURE_PAL + // List of InteropSyncBlockInfo instances that have been freed since the last syncblock cleanup. + static SLIST_HEADER s_InteropInfoStandbyList; +#endif // !FEATURE_PAL + + InteropSyncBlockInfo() + { + LIMITED_METHOD_CONTRACT; + ZeroMemory(this, sizeof(InteropSyncBlockInfo)); + } +#ifndef DACCESS_COMPILE + ~InteropSyncBlockInfo(); +#endif + +#ifndef FEATURE_PAL + // Deletes all items in code:s_InteropInfoStandbyList. + static void FlushStandbyList(); +#endif // !FEATURE_PAL + +#ifdef FEATURE_COMINTEROP + + // + // We'll be using the sentinel value of 0x1 to indicate that a particular + // field was set at one time, but is now NULL. + +#ifndef DACCESS_COMPILE + RCW* GetRawRCW() + { + LIMITED_METHOD_CONTRACT; + return (RCW *)((size_t)m_pRCW & ~1); + } + + // Returns either NULL or an RCW on which AcquireLock has been called. + RCW* GetRCWAndIncrementUseCount(); + + // Sets the m_pRCW field in a thread-safe manner, pRCW can be NULL. + void SetRawRCW(RCW* pRCW); + + bool RCWWasUsed() + { + LIMITED_METHOD_CONTRACT; + + return (m_pRCW != NULL); + } +#else // !DACCESS_COMPILE + TADDR DacGetRawRCW() + { + return (TADDR)((size_t)m_pRCW & ~1); + } +#endif // DACCESS_COMPILE + +#ifndef DACCESS_COMPILE + void SetCCW(ComCallWrapper* pCCW) + { + LIMITED_METHOD_CONTRACT; + + if (pCCW == NULL) + pCCW = (ComCallWrapper*) 0x1; + + m_pCCW = pCCW; + } +#endif // !DACCESS_COMPILE + + PTR_ComCallWrapper GetCCW() + { + LIMITED_METHOD_DAC_CONTRACT; + + if (m_pCCW == (PTR_ComCallWrapper)0x1) + return NULL; + + return m_pCCW; + } + + bool CCWWasUsed() + { + LIMITED_METHOD_CONTRACT; + + if (m_pCCW == NULL) + return false; + + return true; + } + +#ifdef FEATURE_COMINTEROP_UNMANAGED_ACTIVATION + void SetComClassFactory(ComClassFactory* pCCF) + { + LIMITED_METHOD_CONTRACT; + + if (pCCF == NULL) + pCCF = (ComClassFactory*)0x1; + + m_pCCF = pCCF; + } + + ComClassFactory* GetComClassFactory() + { + LIMITED_METHOD_CONTRACT; + + if (m_pCCF == (ComClassFactory*)0x1) + return NULL; + + return m_pCCF; + } + + bool CCFWasUsed() + { + LIMITED_METHOD_CONTRACT; + + if (m_pCCF == NULL) + return false; + + return true; + } +#endif // FEATURE_COMINTEROP_UNMANAGED_ACTIVATION +#endif // FEATURE_COMINTEROP + +#if !defined(DACCESS_COMPILE) + // set m_pUMEntryThunkOrInterceptStub if not already set - return true if not already set + bool SetUMEntryThunk(void* pUMEntryThunk) + { + WRAPPER_NO_CONTRACT; + return (FastInterlockCompareExchangePointer(&m_pUMEntryThunkOrInterceptStub, + pUMEntryThunk, + NULL) == NULL); + } + + // set m_pUMEntryThunkOrInterceptStub if not already set - return true if not already set + bool SetInterceptStub(Stub* pInterceptStub) + { + WRAPPER_NO_CONTRACT; + void *pPtr = (void *)((UINT_PTR)pInterceptStub | 1); + return (FastInterlockCompareExchangePointer(&m_pUMEntryThunkOrInterceptStub, + pPtr, + NULL) == NULL); + } + + void FreeUMEntryThunkOrInterceptStub(); + + void OnADUnload(); + +#endif // DACCESS_COMPILE + + void* GetUMEntryThunk() + { + LIMITED_METHOD_CONTRACT; + return (((UINT_PTR)m_pUMEntryThunkOrInterceptStub & 1) ? NULL : m_pUMEntryThunkOrInterceptStub); + } + + Stub* GetInterceptStub() + { + LIMITED_METHOD_CONTRACT; + return (((UINT_PTR)m_pUMEntryThunkOrInterceptStub & 1) ? (Stub *)((UINT_PTR)m_pUMEntryThunkOrInterceptStub & ~1) : NULL); + } + +private: + // If this is a delegate marshalled out to unmanaged code, this points + // to the thunk generated for unmanaged code to call back on. + // If this is a delegate representing an unmanaged function pointer, + // this may point to a stub that intercepts calls to the unmng target. + // It is currently used for pInvokeStackImbalance MDA and host hook. + // We differentiate between the two by setting the lowest bit if it's + // an intercept stub. + void* m_pUMEntryThunkOrInterceptStub; + +#ifdef FEATURE_COMINTEROP + // If this object is being exposed to COM, it will have an associated CCW object + PTR_ComCallWrapper m_pCCW; + +#ifdef FEATURE_COMINTEROP_UNMANAGED_ACTIVATION + // If this object represents a type object, it will have an associated class factory + ComClassFactory* m_pCCF; +#endif // FEATURE_COMINTEROP_UNMANAGED_ACTIVATION + +public: +#ifndef DACCESS_COMPILE + // If this is a __ComObject, it will have an associated RCW object + RCW* m_pRCW; +#else + // We can't define this as PTR_RCW, as this would create a typedef cycle. Use TADDR + // instead. + TADDR m_pRCW; +#endif +#endif // FEATURE_COMINTEROP + +}; + +typedef DPTR(InteropSyncBlockInfo) PTR_InteropSyncBlockInfo; + +// this is a lazily created additional block for an object which contains +// synchronzation information and other "kitchen sink" data +typedef DPTR(SyncBlock) PTR_SyncBlock; +// See code:#SyncBlockOverview for more +class SyncBlock +{ + // ObjHeader creates our Mutex and Event + friend class ObjHeader; + friend class SyncBlockCache; + friend struct ThreadQueue; +#ifdef DACCESS_COMPILE + friend class ClrDataAccess; +#endif + friend class CheckAsmOffsets; + + protected: + AwareLock m_Monitor; // the actual monitor + + public: + // If this object is exposed to unmanaged code, we keep some extra info here. + PTR_InteropSyncBlockInfo m_pInteropInfo; + + protected: +#ifdef EnC_SUPPORTED + // And if the object has new fields added via EnC, this is a list of them + PTR_EnCSyncBlockInfo m_pEnCInfo; +#endif // EnC_SUPPORTED + + // We thread two different lists through this link. When the SyncBlock is + // active, we create a list of waiting threads here. When the SyncBlock is + // released (we recycle them), the SyncBlockCache maintains a free list of + // SyncBlocks here. + // + // We can't afford to use an SList<> here because we only want to burn + // space for the minimum, which is the pointer within an SLink. + SLink m_Link; + + // This is the index for the appdomain to which the object belongs. If we + // can't set it in the object header, then we set it here. Note that an + // object doesn't always have this filled in. Only for COM interop, + // finalizers and objects in handles + ADIndex m_dwAppDomainIndex; + + // This is the hash code for the object. It can either have been transfered + // from the header dword, in which case it will be limited to 26 bits, or + // have been generated right into this member variable here, when it will + // be a full 32 bits. + + // A 0 in this variable means no hash code has been set yet - this saves having + // another flag to express this state, and it enables us to use a 32-bit interlocked + // operation to set the hash code, on the other hand it means that hash codes + // can never be 0. ObjectNative::GetHashCode in COMObject.cpp makes sure to enforce this. + DWORD m_dwHashCode; + +#if CHECK_APP_DOMAIN_LEAKS + DWORD m_dwFlags; + + enum { + IsObjectAppDomainAgile = 1, + IsObjectCheckedForAppDomainAgile = 2, + }; +#endif + // In some early version of VB when there were no arrays developers used to use BSTR as arrays + // The way this was done was by adding a trail byte at the end of the BSTR + // To support this scenario, we need to use the sync block for this special case and + // save the trail character in here. + // This stores the trail character when a BSTR is used as an array + WCHAR m_BSTRTrailByte; + + public: + SyncBlock(DWORD indx) + : m_Monitor(indx) +#ifdef EnC_SUPPORTED + , m_pEnCInfo(PTR_NULL) +#endif // EnC_SUPPORTED + , m_dwHashCode(0) +#if CHECK_APP_DOMAIN_LEAKS + , m_dwFlags(0) +#endif + , m_BSTRTrailByte(0) + { + LIMITED_METHOD_CONTRACT; + + m_pInteropInfo = NULL; + + // The monitor must be 32-bit aligned for atomicity to be guaranteed. + _ASSERTE((((size_t) &m_Monitor) & 3) == 0); + } + + DWORD GetSyncBlockIndex() + { + LIMITED_METHOD_CONTRACT; + return m_Monitor.GetSyncBlockIndex(); + } + + // As soon as a syncblock acquires some state that cannot be recreated, we latch + // a bit. + void SetPrecious() + { + WRAPPER_NO_CONTRACT; + m_Monitor.SetPrecious(); + } + + BOOL IsPrecious() + { + LIMITED_METHOD_CONTRACT; + return (m_Monitor.m_dwSyncIndex & SyncBlockPrecious) != 0; + } + + void OnADUnload(); + + // True is the syncblock and its index are disposable. + // If new members are added to the syncblock, this + // method needs to be modified accordingly + BOOL IsIDisposable() + { + WRAPPER_NO_CONTRACT; + return (!IsPrecious() && + m_Monitor.m_MonitorHeld.RawValue() == (LONG)0 && + m_Monitor.m_TransientPrecious == 0); + } + + // Gets the InteropInfo block, creates a new one if none is present. + InteropSyncBlockInfo* GetInteropInfo() + { + CONTRACT (InteropSyncBlockInfo*) + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + POSTCONDITION(CheckPointer(RETVAL)); + } + CONTRACT_END; + + if (!m_pInteropInfo) + { + NewHolder<InteropSyncBlockInfo> pInteropInfo; +#ifndef FEATURE_PAL + pInteropInfo = (InteropSyncBlockInfo *)InterlockedPopEntrySList(&InteropSyncBlockInfo::s_InteropInfoStandbyList); + + if (pInteropInfo != NULL) + { + // cache hit - reinitialize the data structure + new (pInteropInfo) InteropSyncBlockInfo(); + } + else +#endif // !FEATURE_PAL + { + pInteropInfo = new InteropSyncBlockInfo(); + } + + if (SetInteropInfo(pInteropInfo)) + pInteropInfo.SuppressRelease(); + } + + RETURN m_pInteropInfo; + } + + PTR_InteropSyncBlockInfo GetInteropInfoNoCreate() + { + CONTRACT (PTR_InteropSyncBlockInfo) + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + SO_TOLERANT; + SUPPORTS_DAC; + POSTCONDITION(CheckPointer(RETVAL, NULL_OK)); + } + CONTRACT_END; + + RETURN m_pInteropInfo; + } + + // Returns false if the InteropInfo block was already set - does not overwrite the previous value. + // True if the InteropInfo block was successfully set with the passed in value. + bool SetInteropInfo(InteropSyncBlockInfo* pInteropInfo); + +#ifdef EnC_SUPPORTED + // Get information about fields added to this object by the Debugger's Edit and Continue support + PTR_EnCSyncBlockInfo GetEnCInfo() + { + LIMITED_METHOD_DAC_CONTRACT; + return m_pEnCInfo; + } + + // Store information about fields added to this object by the Debugger's Edit and Continue support + void SetEnCInfo(EnCSyncBlockInfo *pEnCInfo); +#endif // EnC_SUPPORTED + + ADIndex GetAppDomainIndex() + { + LIMITED_METHOD_DAC_CONTRACT; + return m_dwAppDomainIndex; + } + + void SetAppDomainIndex(ADIndex dwAppDomainIndex) + { + WRAPPER_NO_CONTRACT; + SetPrecious(); + m_dwAppDomainIndex = dwAppDomainIndex; + } + + void SetAwareLock(Thread *holdingThread, DWORD recursionLevel) + { + LIMITED_METHOD_CONTRACT; + // <NOTE> + // DO NOT SET m_MonitorHeld HERE! THIS IS NOT PROTECTED BY ANY LOCK!! + // </NOTE> + m_Monitor.m_HoldingThread = PTR_Thread(holdingThread); + m_Monitor.m_Recursion = recursionLevel; + } + + DWORD GetHashCode() + { + LIMITED_METHOD_CONTRACT; + return m_dwHashCode; + } + + DWORD SetHashCode(DWORD hashCode) + { + WRAPPER_NO_CONTRACT; + DWORD result = FastInterlockCompareExchange((LONG*)&m_dwHashCode, hashCode, 0); + if (result == 0) + { + // the sync block now holds a hash code, which we can't afford to lose. + SetPrecious(); + return hashCode; + } + else + return result; + } + + void *operator new (size_t sz, void* p) + { + LIMITED_METHOD_CONTRACT; + return p ; + } + void operator delete(void *p) + { + LIMITED_METHOD_CONTRACT; + // We've already destructed. But retain the memory. + } + + void EnterMonitor() + { + WRAPPER_NO_CONTRACT; + m_Monitor.Enter(); + } + + BOOL TryEnterMonitor(INT32 timeOut = 0) + { + WRAPPER_NO_CONTRACT; + return m_Monitor.TryEnter(timeOut); + } + + // leave the monitor + BOOL LeaveMonitor() + { + WRAPPER_NO_CONTRACT; + return m_Monitor.Leave(); + } + + AwareLock* GetMonitor() + { + WRAPPER_NO_CONTRACT; + SUPPORTS_DAC; + //hold the syncblock +#ifndef DACCESS_COMPILE + SetPrecious(); +#endif + + //Note that for DAC we did not return a PTR_ type. This pointer is interior and + //the SyncBlock has already been marshaled so that GetMonitor could be called. + return &m_Monitor; + } + + AwareLock* QuickGetMonitor() + { + LIMITED_METHOD_CONTRACT; + // Note that the syncblock isn't marked precious, so use caution when + // calling this method. + return &m_Monitor; + } + + BOOL DoesCurrentThreadOwnMonitor() + { + WRAPPER_NO_CONTRACT; + return m_Monitor.OwnedByCurrentThread(); + } + + LONG LeaveMonitorCompletely() + { + WRAPPER_NO_CONTRACT; + return m_Monitor.LeaveCompletely(); + } + + BOOL Wait(INT32 timeOut, BOOL exitContext); + void Pulse(); + void PulseAll(); + + enum + { + // This bit indicates that the syncblock is valuable and can neither be discarded + // nor re-created. + SyncBlockPrecious = 0x80000000, + }; + +#if CHECK_APP_DOMAIN_LEAKS + BOOL IsAppDomainAgile() + { + LIMITED_METHOD_CONTRACT; + return m_dwFlags & IsObjectAppDomainAgile; + } + void SetIsAppDomainAgile() + { + LIMITED_METHOD_CONTRACT; + m_dwFlags |= IsObjectAppDomainAgile; + } + void UnsetIsAppDomainAgile() + { + LIMITED_METHOD_CONTRACT; + m_dwFlags = m_dwFlags & ~IsObjectAppDomainAgile; + } + BOOL IsCheckedForAppDomainAgile() + { + LIMITED_METHOD_CONTRACT; + return m_dwFlags & IsObjectCheckedForAppDomainAgile; + } + void SetIsCheckedForAppDomainAgile() + { + LIMITED_METHOD_CONTRACT; + m_dwFlags |= IsObjectCheckedForAppDomainAgile; + } +#endif //CHECK_APP_DOMAIN_LEAKS + + BOOL HasCOMBstrTrailByte() + { + LIMITED_METHOD_CONTRACT; + return (m_BSTRTrailByte!=0); + } + WCHAR GetCOMBstrTrailByte() + { + return m_BSTRTrailByte; + } + void SetCOMBstrTrailByte(WCHAR trailByte) + { + WRAPPER_NO_CONTRACT; + m_BSTRTrailByte = trailByte; + SetPrecious(); + } + + protected: + // <NOTE> + // This should ONLY be called when initializing a SyncBlock (i.e. ONLY from + // ObjHeader::GetSyncBlock()), otherwise we'll have a race condition. + // </NOTE> + void InitState() + { + LIMITED_METHOD_CONTRACT; + m_Monitor.m_MonitorHeld.RawValue() = 1; + } + +#if defined(ENABLE_CONTRACTS_IMPL) + // The LOCK_TAKEN/RELEASED macros need a "pointer" to the lock object to do + // comparisons between takes & releases (and to provide debugging info to the + // developer). Use the AwareLock (m_Monitor) + void * GetPtrForLockContract() + { + return m_Monitor.GetPtrForLockContract(); + } +#endif // defined(ENABLE_CONTRACTS_IMPL) +}; + +class SyncTableEntry +{ + public: + PTR_SyncBlock m_SyncBlock; + VolatilePtr<Object, PTR_Object> m_Object; + static PTR_SyncTableEntry GetSyncTableEntry(); +#ifndef DACCESS_COMPILE + static SyncTableEntry*& GetSyncTableEntryByRef(); +#endif +}; + +#ifdef _DEBUG +extern void DumpSyncBlockCache(); +#endif + +// this class stores free sync blocks after they're allocated and +// unused + +typedef DPTR(SyncBlockCache) PTR_SyncBlockCache; + +// The SyncBlockCache is the data structure that manages SyncBlocks +// as well as SyncTableEntries (See explaintation at top of this file). +// +// There is only one process global SyncBlockCache (SyncBlockCache::s_pSyncBlockCache) +// and SyncTableEntry table (g_pSyncTable). +// +// see code:#SyncBlockOverview for more +class SyncBlockCache +{ +#ifdef DACCESS_COMPILE + friend class ClrDataAccess; +#endif + + friend class SyncBlock; + + + private: + PTR_SLink m_pCleanupBlockList; // list of sync blocks that need cleanup + SLink* m_FreeBlockList; // list of free sync blocks + Crst m_CacheLock; // cache lock + DWORD m_FreeCount; // count of active sync blocks + DWORD m_ActiveCount; // number active + SyncBlockArray *m_SyncBlocks; // Array of new SyncBlocks. + DWORD m_FreeSyncBlock; // Next Free Syncblock in the array + + // The next variables deal with SyncTableEntries. Instead of having the object-header + // point directly at SyncBlocks, the object points a a syncTableEntry, which points at + // the syncBlock. This is done because in a common case (need a hash code for an object) + // you just need a syncTableEntry. + + DWORD m_FreeSyncTableIndex; // We allocate a large array of SyncTableEntry structures. + // This index points at the boundry between used, and never-been + // used SyncTableEntries. + size_t m_FreeSyncTableList; // index of the first free SyncTableEntry in our free list. + // The entry at this index has its m_object field to the index + // of the next element (shifted by 1, low bit marks not in use) + DWORD m_SyncTableSize; + SyncTableEntry *m_OldSyncTables; // Next old SyncTable + + BOOL m_bSyncBlockCleanupInProgress; // A flag indicating if sync block cleanup is in progress. + DWORD* m_EphemeralBitmap; // card table for ephemeral scanning + + BOOL GCWeakPtrScanElement(int elindex, HANDLESCANPROC scanProc, LPARAM lp1, LPARAM lp2, BOOL& cleanup); + + void SetCard (size_t card); + void ClearCard (size_t card); + BOOL CardSetP (size_t card); + void CardTableSetBit (size_t idx); + void Grow(); + + + public: + SPTR_DECL(SyncBlockCache, s_pSyncBlockCache); + static SyncBlockCache*& GetSyncBlockCache(); + + void *operator new(size_t size, void *pInPlace) + { + LIMITED_METHOD_CONTRACT; + return pInPlace; + } + + void operator delete(void *p) + { + LIMITED_METHOD_CONTRACT; + } + + SyncBlockCache(); + ~SyncBlockCache(); + + static void Attach(); + static void Detach(); + void DoDetach(); + + static void Start(); + static void Stop(); + + // returns and removes next from free list + SyncBlock* GetNextFreeSyncBlock(); + // returns and removes the next from cleanup list + SyncBlock* GetNextCleanupSyncBlock(); + // inserts a syncblock into the cleanup list + void InsertCleanupSyncBlock(SyncBlock* psb); + + // Obtain a new syncblock slot in the SyncBlock table. Used as a hash code + DWORD NewSyncBlockSlot(Object *obj); + + // return sync block to cache or delete + void DeleteSyncBlock(SyncBlock *sb); + + // returns the sync block memory to the free pool but does not destruct sync block (must own cache lock already) + void DeleteSyncBlockMemory(SyncBlock *sb); + + // return sync block to cache or delete, called from GC + void GCDeleteSyncBlock(SyncBlock *sb); + + void GCWeakPtrScan(HANDLESCANPROC scanProc, uintptr_t lp1, uintptr_t lp2); + + void GCDone(BOOL demoting, int max_gen); + + void CleanupSyncBlocks(); + + void CleanupSyncBlocksInAppDomain(AppDomain *pDomain); + + int GetTableEntryCount() + { + LIMITED_METHOD_CONTRACT; + return m_FreeSyncTableIndex - 1; + } + + // Determines if a sync block cleanup is in progress. + BOOL IsSyncBlockCleanupInProgress() + { + LIMITED_METHOD_CONTRACT; + return m_bSyncBlockCleanupInProgress; + } + + DWORD GetActiveCount() + { + return m_ActiveCount; + } + + // Encapsulate a CrstHolder, so that clients of our lock don't have to know + // the details of our implementation. + class LockHolder : public CrstHolder + { + public: + LockHolder(SyncBlockCache *pCache) + : CrstHolder(&pCache->m_CacheLock) + { + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + CAN_TAKE_LOCK; + } + CONTRACTL_END; + } + }; + friend class LockHolder; + +#if CHECK_APP_DOMAIN_LEAKS + void CheckForUnloadedInstances(ADIndex unloadingIndex); +#endif +#ifdef _DEBUG + friend void DumpSyncBlockCache(); +#endif + +#ifdef VERIFY_HEAP + void VerifySyncTableEntry(); +#endif +}; + +// See code:#SyncBlockOverView for more +class ObjHeader +{ + friend class CheckAsmOffsets; + + private: + // !!! Notice: m_SyncBlockValue *MUST* be the last field in ObjHeader. +#ifdef _WIN64 + DWORD m_alignpad; +#endif // _WIN64 + + Volatile<DWORD> m_SyncBlockValue; // the Index and the Bits + +#if defined(_WIN64) && defined(_DEBUG) + void IllegalAlignPad(); +#endif // _WIN64 && _DEBUG + + INCONTRACT(void * GetPtrForLockContract()); + + public: + + // Access to the Sync Block Index, by masking the Value. + FORCEINLINE DWORD GetHeaderSyncBlockIndex() + { + LIMITED_METHOD_DAC_CONTRACT; +#if defined(_WIN64) && defined(_DEBUG) && !defined(DACCESS_COMPILE) + // On WIN64 this field is never modified, but was initialized to 0 + if (m_alignpad != 0) + IllegalAlignPad(); +#endif // _WIN64 && _DEBUG && !DACCESS_COMPILE + + // pull the value out before checking it to avoid race condition + DWORD value = m_SyncBlockValue.LoadWithoutBarrier(); + if ((value & (BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX | BIT_SBLK_IS_HASHCODE)) != BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX) + return 0; + return value & MASK_SYNCBLOCKINDEX; + } + // Ditto for setting the index, which is careful not to disturb the underlying + // bit field -- even in the presence of threaded access. + // + // This service can only be used to transition from a 0 index to a non-0 index. + void SetIndex(DWORD indx) + { + CONTRACTL + { + INSTANCE_CHECK; + NOTHROW; + GC_NOTRIGGER; + FORBID_FAULT; + MODE_ANY; + PRECONDITION(GetHeaderSyncBlockIndex() == 0); + PRECONDITION(m_SyncBlockValue & BIT_SBLK_SPIN_LOCK); + } + CONTRACTL_END + + +#ifdef _DEBUG + // if we have an index here, make sure we already transferred it to the syncblock + // before we clear it out + ADIndex adIndex = GetRawAppDomainIndex(); + if (adIndex.m_dwIndex) + { + SyncBlock *pSyncBlock = SyncTableEntry::GetSyncTableEntry() [indx & ~BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX].m_SyncBlock; + _ASSERTE(pSyncBlock && pSyncBlock->GetAppDomainIndex() == adIndex); + } +#endif + + LONG newValue; + LONG oldValue; + while (TRUE) { + oldValue = m_SyncBlockValue.LoadWithoutBarrier(); + _ASSERTE(GetHeaderSyncBlockIndex() == 0); + // or in the old value except any index that is there - + // note that indx could be carrying the BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX bit that we need to preserve + newValue = (indx | + (oldValue & ~(BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX | BIT_SBLK_IS_HASHCODE | MASK_SYNCBLOCKINDEX))); + if (FastInterlockCompareExchange((LONG*)&m_SyncBlockValue, + newValue, + oldValue) + == oldValue) + { + return; + } + } + } + + // Used only during shutdown + void ResetIndex() + { + LIMITED_METHOD_CONTRACT; + + _ASSERTE(m_SyncBlockValue & BIT_SBLK_SPIN_LOCK); + FastInterlockAnd(&m_SyncBlockValue, ~(BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX | BIT_SBLK_IS_HASHCODE | MASK_SYNCBLOCKINDEX)); + } + + // Used only GC + void GCResetIndex() + { + LIMITED_METHOD_CONTRACT; + + m_SyncBlockValue.RawValue() &=~(BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX | BIT_SBLK_IS_HASHCODE | MASK_SYNCBLOCKINDEX); + } + + void SetAppDomainIndex(ADIndex); + void ResetAppDomainIndex(ADIndex); + void ResetAppDomainIndexNoFailure(ADIndex); + ADIndex GetRawAppDomainIndex(); + ADIndex GetAppDomainIndex(); + + // For now, use interlocked operations to twiddle bits in the bitfield portion. + // If we ever have high-performance requirements where we can guarantee that no + // other threads are accessing the ObjHeader, this can be reconsidered for those + // particular bits. + void SetBit(DWORD bit) + { + LIMITED_METHOD_CONTRACT; + + _ASSERTE((bit & MASK_SYNCBLOCKINDEX) == 0); + FastInterlockOr(&m_SyncBlockValue, bit); + } + void ClrBit(DWORD bit) + { + LIMITED_METHOD_CONTRACT; + + _ASSERTE((bit & MASK_SYNCBLOCKINDEX) == 0); + FastInterlockAnd(&m_SyncBlockValue, ~bit); + } + //GC accesses this bit when all threads are stopped. + void SetGCBit() + { + LIMITED_METHOD_CONTRACT; + + m_SyncBlockValue.RawValue() |= BIT_SBLK_GC_RESERVE; + } + void ClrGCBit() + { + LIMITED_METHOD_CONTRACT; + + m_SyncBlockValue.RawValue() &= ~BIT_SBLK_GC_RESERVE; + } + + // Don't bother masking out the index since anyone who wants bits will presumably + // restrict the bits they consider. + DWORD GetBits() + { + LIMITED_METHOD_CONTRACT; + SUPPORTS_DAC; + +#if defined(_WIN64) && defined(_DEBUG) && !defined(DACCESS_COMPILE) + // On WIN64 this field is never modified, but was initialized to 0 + if (m_alignpad != 0) + IllegalAlignPad(); +#endif // _WIN64 && _DEBUG && !DACCESS_COMPILE + + return m_SyncBlockValue.LoadWithoutBarrier(); + } + + + DWORD SetBits(DWORD newBits, DWORD oldBits) + { + LIMITED_METHOD_CONTRACT; + + _ASSERTE((oldBits & BIT_SBLK_SPIN_LOCK) == 0); + DWORD result = FastInterlockCompareExchange((LONG*)&m_SyncBlockValue, newBits, oldBits); + return result; + } + +#ifdef _DEBUG + BOOL HasEmptySyncBlockInfo() + { + WRAPPER_NO_CONTRACT; + return m_SyncBlockValue.LoadWithoutBarrier() == 0; + } +#endif + + // TRUE if the header has a real SyncBlockIndex (i.e. it has an entry in the + // SyncTable, though it doesn't necessarily have an entry in the SyncBlockCache) + BOOL HasSyncBlockIndex() + { + LIMITED_METHOD_DAC_CONTRACT; + return (GetHeaderSyncBlockIndex() != 0); + } + + // retrieve or allocate a sync block for this object + SyncBlock *GetSyncBlock(); + + // retrieve sync block but don't allocate + PTR_SyncBlock PassiveGetSyncBlock() + { + LIMITED_METHOD_DAC_CONTRACT; + return g_pSyncTable [(int)GetHeaderSyncBlockIndex()].m_SyncBlock; + } + + DWORD GetSyncBlockIndex(); + + // this enters the monitor of an object + void EnterObjMonitor(); + + // non-blocking version of above + BOOL TryEnterObjMonitor(INT32 timeOut = 0); + + // Inlineable fast path of EnterObjMonitor/TryEnterObjMonitor + AwareLock::EnterHelperResult EnterObjMonitorHelper(Thread* pCurThread); + AwareLock::EnterHelperResult EnterObjMonitorHelperSpin(Thread* pCurThread); + + // leaves the monitor of an object + BOOL LeaveObjMonitor(); + + // should be called only from unwind code + BOOL LeaveObjMonitorAtException(); + + // Helper encapsulating the core logic for releasing monitor. Returns what kind of + // follow up action is necessary + AwareLock::LeaveHelperAction LeaveObjMonitorHelper(Thread* pCurThread); + + // Returns TRUE if the lock is owned and FALSE otherwise + // threadId is set to the ID (Thread::GetThreadId()) of the thread which owns the lock + // acquisitionCount is set to the number of times the lock needs to be released before + // it is unowned + BOOL GetThreadOwningMonitorLock(DWORD *pThreadId, DWORD *pAcquisitionCount); + + PTR_Object GetBaseObject() + { + LIMITED_METHOD_DAC_CONTRACT; + return dac_cast<PTR_Object>(dac_cast<TADDR>(this + 1)); + } + + BOOL Wait(INT32 timeOut, BOOL exitContext); + void Pulse(); + void PulseAll(); + + void EnterSpinLock(); + void ReleaseSpinLock(); + + BOOL Validate (BOOL bVerifySyncBlkIndex = TRUE); +}; + + +typedef DPTR(class ObjHeader) PTR_ObjHeader; + + +#define ENTER_SPIN_LOCK(pOh) \ + pOh->EnterSpinLock(); + +#define LEAVE_SPIN_LOCK(pOh) \ + pOh->ReleaseSpinLock(); + + +#ifdef DACCESS_COMPILE +// A visitor function used to enumerate threads in the ThreadQueue below +typedef void (*FP_TQ_THREAD_ENUMERATION_CALLBACK)(PTR_Thread pThread, VOID* pUserData); +#endif + +// A SyncBlock contains an m_Link field that is used for two purposes. One +// is to manage a FIFO queue of threads that are waiting on this synchronization +// object. The other is to thread free SyncBlocks into a list for recycling. +// We don't want to burn anything else on the SyncBlock instance, so we can't +// use an SList or similar data structure. So here's the encapsulation for the +// queue of waiting threads. +// +// Note that Enqueue is slower than it needs to be, because we don't want to +// burn extra space in the SyncBlock to remember the head and the tail of the Q. +// An alternate approach would be to treat the list as a LIFO stack, which is not +// a fair policy because it permits to starvation. +// +// Important!!! While there is a lock that is used in process to keep multiple threads +// from altering the queue simultaneously, the queue must still be consistent at all +// times, even when the lock is held. The debugger inspects the queue from out of process +// and just looks at the memory...it must be valid even if the lock is held. Be careful if you +// change the way the queue is updated. +struct ThreadQueue +{ + // Given a link in the chain, get the Thread that it represents + static PTR_WaitEventLink WaitEventLinkForLink(PTR_SLink pLink); + + // Unlink the head of the Q. We are always in the SyncBlock's critical + // section. + static WaitEventLink *DequeueThread(SyncBlock *psb); + + // Enqueue is the slow one. We have to find the end of the Q since we don't + // want to burn storage for this in the SyncBlock. + static void EnqueueThread(WaitEventLink *pWaitEventLink, SyncBlock *psb); + + // Wade through the SyncBlock's list of waiting threads and remove the + // specified thread. + static BOOL RemoveThread (Thread *pThread, SyncBlock *psb); + +#ifdef DACCESS_COMPILE + // Enumerates the threads in the queue from front to back by calling + // pCallbackFunction on each one + static void EnumerateThreads(SyncBlock *psb, + FP_TQ_THREAD_ENUMERATION_CALLBACK pCallbackFunction, + void* pUserData); +#endif +}; + + +// The true size of an object is whatever C++ thinks, plus the ObjHeader we +// allocate before it. + +#define ObjSizeOf(c) (sizeof(c) + sizeof(ObjHeader)) + + +inline void AwareLock::SetPrecious() +{ + LIMITED_METHOD_CONTRACT; + + m_dwSyncIndex |= SyncBlock::SyncBlockPrecious; +} + +inline DWORD AwareLock::GetSyncBlockIndex() +{ + LIMITED_METHOD_CONTRACT; + return (m_dwSyncIndex & ~SyncBlock::SyncBlockPrecious); +} + +#ifdef _TARGET_X86_ +#include <poppack.h> +#endif // _TARGET_X86_ + +#endif // _SYNCBLK_H_ + + |