From 423d2a3b91feea18ab361da04d5cc24bdff157d0 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Thu, 14 Feb 2019 17:07:14 -0800 Subject: Replace multi-loaderallocator hash implementation in MethodDescBackpatchInfo (#22285) * GCHeapHash - Hashtable implementation for runtime use - Implementation written in C++ - Data storage in managed heap memory - Based on SHash design, but using managed memory CrossLoaderAllocatorHash - Hash for c++ Pointer to C++ pointer where the lifetimes are controlled by different loader allocators - Support for add/remove/visit all entries of 1 key/visit all entries/ remove all entries of 1 key - Supports holding data which is unmanaged, but data items themselves can be of any size (key/value are templated types) * Swap MethodDescBackpatchInfo to use the CrossLoaderAllocatorHash * The MethodDescBackpatchCrst needs to be around an allocation - Adjust the Crst so that it can safely be used around code which allocates - Required moving its use out from within the EESuspend logic used in rejit --- src/vm/appdomain.cpp | 11 +- src/vm/codeversion.cpp | 2 + src/vm/crossloaderallocatorhash.h | 197 ++++++ src/vm/crossloaderallocatorhash.inl | 1216 +++++++++++++++++++++++++++++++++++ src/vm/frames.h | 2 + src/vm/gcheaphashtable.h | 142 ++++ src/vm/gcheaphashtable.inl | 530 +++++++++++++++ src/vm/loaderallocator.cpp | 12 +- src/vm/loaderallocator.hpp | 3 + src/vm/method.cpp | 47 +- src/vm/methoddescbackpatchinfo.cpp | 153 +---- src/vm/methoddescbackpatchinfo.h | 326 +--------- src/vm/mscorlib.h | 19 + src/vm/object.cpp | 17 + src/vm/object.h | 133 ++++ src/vm/prestub.cpp | 2 + src/vm/rejit.cpp | 55 +- src/vm/tieredcompilation.cpp | 3 + 18 files changed, 2361 insertions(+), 509 deletions(-) create mode 100644 src/vm/crossloaderallocatorhash.h create mode 100644 src/vm/crossloaderallocatorhash.inl create mode 100644 src/vm/gcheaphashtable.h create mode 100644 src/vm/gcheaphashtable.inl (limited to 'src/vm') diff --git a/src/vm/appdomain.cpp b/src/vm/appdomain.cpp index f7bedff69f..42e26c99d1 100644 --- a/src/vm/appdomain.cpp +++ b/src/vm/appdomain.cpp @@ -2554,6 +2554,10 @@ void SystemDomain::LoadBaseSystemClasses() g_pByteArrayMT = ClassLoader::LoadArrayTypeThrowing( TypeHandle(MscorlibBinder::GetElementType(ELEMENT_TYPE_U1))).AsArray()->GetMethodTable(); +#ifndef CROSSGEN_COMPILE + CrossLoaderAllocatorHashSetup::EnsureTypesLoaded(); +#endif + #ifndef CROSSGEN_COMPILE ECall::PopulateManagedStringConstructors(); #endif // CROSSGEN_COMPILE @@ -3850,13 +3854,6 @@ void AppDomain::Terminate() } #endif // FEATURE_COMINTEROP -#ifndef CROSSGEN_COMPILE - // Recorded entry point slots may point into the virtual call stub manager's heaps, so clear it first - GetLoaderAllocator() - ->GetMethodDescBackpatchInfoTracker() - ->ClearDependencyMethodDescEntryPointSlots(GetLoaderAllocator()); -#endif - if (!IsAtProcessExit()) { // if we're not shutting down everything then clean up the string literals associated diff --git a/src/vm/codeversion.cpp b/src/vm/codeversion.cpp index e8ace2890d..5286815845 100644 --- a/src/vm/codeversion.cpp +++ b/src/vm/codeversion.cpp @@ -2187,6 +2187,8 @@ PCODE CodeVersionManager::PublishVersionableCodeIfNecessary(MethodDesc* pMethodD pCode = pMethodDesc->PrepareCode(activeVersion); } + MethodDescBackpatchInfoTracker::ConditionalLockHolder lockHolder(pMethodDesc->MayHaveEntryPointSlotsToBackpatch()); + // suspend in preparation for publishing if needed if (fEESuspend) { diff --git a/src/vm/crossloaderallocatorhash.h b/src/vm/crossloaderallocatorhash.h new file mode 100644 index 0000000000..09068752b0 --- /dev/null +++ b/src/vm/crossloaderallocatorhash.h @@ -0,0 +1,197 @@ +// 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. + +#ifndef CROSSLOADERALLOCATORHASH_H +#define CROSSLOADERALLOCATORHASH_H +#ifndef CROSSGEN_COMPILE + +#include "gcheaphashtable.h" + +class LoaderAllocator; + +template +class NoRemoveDefaultCrossLoaderAllocatorHashTraits +{ +public: + typedef TKey_ TKey; + typedef TValue_ TValue; + + static bool IsNull(const TValue &value) { return value == NULL; } + static TValue NullValue() { return NULL; } + +#ifndef DACCESS_COMPILE + static void SetUsedEntries(TValue* pStartOfValuesData, DWORD entriesInArrayTotal, DWORD usedEntries); + static bool AddToValuesInHeapMemory(OBJECTREF *pKeyValueStore, const TKey& key, const TValue& value); +#endif //!DACCESS_COMPILE + static DWORD ComputeUsedEntries(OBJECTREF *pKeyValueStore, DWORD *pEntriesInArrayTotal); + template + static bool VisitKeyValueStore(OBJECTREF *pLoaderAllocatorRef, OBJECTREF *pKeyValueStore, Visitor &visitor); + static TKey ReadKeyFromKeyValueStore(OBJECTREF *pKeyValueStore); +}; + +template +class DefaultCrossLoaderAllocatorHashTraits : public NoRemoveDefaultCrossLoaderAllocatorHashTraits +{ +public: + typedef TKey_ TKey; + typedef TValue_ TValue; + +#ifndef DACCESS_COMPILE + static void DeleteValueInHeapMemory(OBJECTREF keyValueStore, const TValue& value); +#endif //!DACCESS_COMPILE +}; + +struct GCHeapHashDependentHashTrackerHashTraits : public DefaultGCHeapHashTraits +{ + typedef LoaderAllocator* PtrTypeKey; + + static INT32 Hash(PtrTypeKey *pValue); + static INT32 Hash(PTRARRAYREF arr, INT32 index); + static bool DoesEntryMatchKey(PTRARRAYREF arr, INT32 index, PtrTypeKey *pKey); + static bool IsDeleted(PTRARRAYREF arr, INT32 index, GCHEAPHASHOBJECTREF gcHeap); +}; + +typedef GCHeapHash GCHeapHashDependentHashTrackerHash; + +template +struct KeyToValuesGCHeapHashTraits : public DefaultGCHeapHashTraits +{ + template + static INT32 Hash(TKey *pValue); + static INT32 Hash(PTRARRAYREF arr, INT32 index); + + template + static bool DoesEntryMatchKey(PTRARRAYREF arr, INT32 index, TKey *pKey); +}; + +// Hashtable of key to a list of values where the key may live in a different loader allocator +// than the value and this should not keep the loaderallocator of the value alive. The type of +// keys/values is defined via the TRAITS template argument, but must be non-gc pointers, and +// must be copyable without a copy constructor/require a destructor. +// +// This is managed via a series of different hashtables and data structures that are carefully +// engineered to be relatively memory efficient, yet still provide the ability to safely use +// the hashtable to hold relationships across LoaderAllocators which are not generally safe. +// +// In particular, given LoaderAllocator LA1 and LA2, where a reference to LA1 is not +// guaranteed to keep LA2 alive, this data structure can permit a pointer to an object which +// is defined as part of LA1 to be used as a key to find a pointer to an object that has the +// same lifetime as LA2. +// +// This data structure exposes Remove api's, but its primary use case is the combination of +// the Add and VisitValuesOfKey apis. +// +// To use Add, simply, call Add(TKey key, TValue value). This will add to the list of values +// associated with a key. The Add api should be called on a key's which are associated with +// the same LoaderAllocator as the CrossLoaderAllocatorHash. +// +// VisitValuesOfKey will visit all values that have the same key. +// +// IMPLEMENTATION DESIGN +// This data structure is a series of hashtables and lists. +// +// In general, this data structure builds a set of values associated with a key per +// LoaderAllocator. The lists per loader allocator are controlled via the TRAITS template. The +// TRAITS specify how the individual lists are handled, and do the copying in and out of the data +// structures. It is not expected that additional traits implementations will be needed for use, +// unless duplicate prevention is needed. +// +// BASIC STRUCTURE +// +// m_keyToDependentTrackersHash - Hashtable of key -> (list of values in primary loader allocator, +// hashtable of DependentTrackers) +// +// For each key in the table, there is at list of values in the primary loader allocator, +// and optionally there may be a hashtable of dependent trackers +// +// m_loaderAllocatorToDependentTrackerHash - Hashtable of LoaderAllocator to DependentTracker. Used to find +// dependent trackers for insertion into per key sets. +// +// The DependentTracker is an object (with a finalizer) which is associated with a specific +// LoaderAllocator, and uses a DependentHandle to hold onto a hashtable from Key to List of +// Values (for a specific LoaderAllocator). This dependent handle will keep that hashtable alive +// as long as the associated LoaderAllocator is live. +// +// The DependentTracker hashes (both the m_loaderAllocatorToDependentTrackerHash, and the per key hashes) are +// implemented via a hashtable which is "self-cleaning". In particular as the hashtable is +// walked for Add/Visit/Remove operations, if a DependentTracker is found which where the +// DependentHandle has detected that the LoaderAllocator has been freed, then the entry in +// the hashtable will set itself to the DELETED state. This cleaning operation will not occur +// eagerly, but it should prevent unbounded size growth as collectible LoaderAllocators are +// allocated and freed. +// +// Memory efficiency of this data structure. +// - This data structure is reasonably memory efficient. If many values share the same key +// then the memory efficiency per key trends toward 1.3333 * sizeof(Value). Otherwise basic +// cost per key/value pair (assuming they are pointer sized has an overhead of about 4 +// pointers + key/value data size.) +template +class CrossLoaderAllocatorHash +{ +private: + typedef typename TRAITS::TKey TKey; + typedef typename TRAITS::TValue TValue; + typedef GCHeapHash> KeyToValuesGCHeapHash; + +public: + +#ifndef DACCESS_COMPILE + // Add an entry to the CrossLoaderAllocatorHash, the default implementation of does DefaultCrossLoaderAllocatorHashTraits will not check for duplicates. + void Add(TKey key, TValue value, LoaderAllocator *pLoaderAllocatorOfValue); + + // Remove an entry to the CrossLoaderAllocatorHash, only removes one entry + void Remove(TKey key, TValue value, LoaderAllocator *pLoaderAllocatorOfValue); + + // Remove all entries that can be looked up by key + void RemoveAll(TKey key); +#endif + + // Using visitor walk all values associated with a given key. The visitor + // is expected to implement bool operator ()(OBJECTREF keepAlive, TKey key, TValue value). + // Return false from that function to stop visitation. + // This can be done simply by utilizing a lambda, or if a lambda cannot be used, a functor will do. + // The value of "value" in this case must not escape from the visitor object + // unless the keepAlive OBJECTREF is also kept alive + template + bool VisitValuesOfKey(TKey key, Visitor &visitor); + + // Visit all key/value pairs + template + bool VisitAllKeyValuePairs(Visitor &visitor); + + // Initialize this CrossLoaderAllocatorHash to be associated with a specific LoaderAllocator + // Must be called before any use of Add + void Init(LoaderAllocator *pAssociatedLoaderAllocator); + +private: +#ifndef DACCESS_COMPILE + void EnsureManagedObjectsInitted(); + LAHASHDEPENDENTHASHTRACKERREF GetDependentTrackerForLoaderAllocator(LoaderAllocator* pLoaderAllocator); + GCHEAPHASHOBJECTREF GetKeyToValueCrossLAHashForHashkeyToTrackers(LAHASHKEYTOTRACKERSREF hashKeyToTrackersUnsafe, LoaderAllocator* pValueLoaderAllocator); +#endif // !DACCESS_COMPILE + + template + static bool VisitKeyValueStore(OBJECTREF *pLoaderAllocatorRef, OBJECTREF *pKeyValueStore, Visitor &visitor); + template + static bool VisitTracker(TKey key, LAHASHDEPENDENTHASHTRACKERREF trackerUnsafe, Visitor &visitor); + template + static bool VisitTrackerAllEntries(LAHASHDEPENDENTHASHTRACKERREF trackerUnsafe, Visitor &visitor); + template + static bool VisitKeyToTrackerAllEntries(OBJECTREF hashKeyEntryUnsafe, Visitor &visitor); + static void DeleteEntryTracker(TKey key, LAHASHDEPENDENTHASHTRACKERREF trackerUnsafe); + +private: + LoaderAllocator *m_pLoaderAllocator = 0; + OBJECTHANDLE m_loaderAllocatorToDependentTrackerHash = 0; + OBJECTHANDLE m_keyToDependentTrackersHash = 0; +}; + +class CrossLoaderAllocatorHashSetup +{ +public: + inline static void EnsureTypesLoaded(); +}; + +#endif // !CROSSGEN_COMPILE +#endif // CROSSLOADERALLOCATORHASH_H diff --git a/src/vm/crossloaderallocatorhash.inl b/src/vm/crossloaderallocatorhash.inl new file mode 100644 index 0000000000..51bce4e1ae --- /dev/null +++ b/src/vm/crossloaderallocatorhash.inl @@ -0,0 +1,1216 @@ +// 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. + +#ifndef CROSSLOADERALLOCATORHASH_INL +#define CROSSLOADERALLOCATORHASH_INL +#ifdef CROSSLOADERALLOCATORHASH_H +#ifndef CROSSGEN_COMPILE + +#include "gcheaphashtable.inl" + +template +/*static*/ DWORD NoRemoveDefaultCrossLoaderAllocatorHashTraits::ComputeUsedEntries(OBJECTREF *pKeyValueStore, DWORD *pEntriesInArrayTotal) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + DWORD entriesInArrayTotal = (((I1ARRAYREF)*pKeyValueStore)->GetNumComponents() - sizeof(TKey))/sizeof(TValue); + DWORD usedEntries; + TValue* pStartOfValuesData = (TValue*)(((I1ARRAYREF)*pKeyValueStore)->GetDirectPointerToNonObjectElements() + sizeof(TKey)); + + if (entriesInArrayTotal == 0) + { + usedEntries = 0; + } + else if ((entriesInArrayTotal >= 2) && (pStartOfValuesData[entriesInArrayTotal - 2] == (TValue)0)) + { + usedEntries = (DWORD)pStartOfValuesData[entriesInArrayTotal - 1]; + } + else if (pStartOfValuesData[entriesInArrayTotal - 1] == (TValue)0) + { + usedEntries = entriesInArrayTotal - 1; + } + else + { + usedEntries = entriesInArrayTotal; + } + + *pEntriesInArrayTotal = entriesInArrayTotal; + return usedEntries; +} + +#ifndef DACCESS_COMPILE +template +/*static*/ void NoRemoveDefaultCrossLoaderAllocatorHashTraits::SetUsedEntries(TValue* pStartOfValuesData, DWORD entriesInArrayTotal, DWORD usedEntries) +{ + if (usedEntries < entriesInArrayTotal) + { + if (usedEntries == (entriesInArrayTotal - 1)) + { + pStartOfValuesData[entriesInArrayTotal - 1] = (TValue)0; + } + else + { + pStartOfValuesData[entriesInArrayTotal - 1] = (TValue)(usedEntries); + pStartOfValuesData[entriesInArrayTotal - 2] = (TValue)0; + } + } +} + +template +/*static*/ bool NoRemoveDefaultCrossLoaderAllocatorHashTraits::AddToValuesInHeapMemory(OBJECTREF *pKeyValueStore, const TKey& key, const TValue& value) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + static_assert(sizeof(TKey)==sizeof(TValue), "Assume keys and values are the same size"); + + bool updatedKeyValueStore = false; + + if (*pKeyValueStore == NULL) + { + *pKeyValueStore = AllocatePrimitiveArray(ELEMENT_TYPE_I1, IsNull(value) ? sizeof(TKey) : sizeof(TKey) + sizeof(TValue), FALSE); + updatedKeyValueStore = true; + TKey* pKeyLoc = (TKey*)((I1ARRAYREF)*pKeyValueStore)->GetDirectPointerToNonObjectElements(); + *pKeyLoc = key; + if (!IsNull(value)) + { + TValue* pValueLoc = (TValue*)(((I1ARRAYREF)*pKeyValueStore)->GetDirectPointerToNonObjectElements() + sizeof(TKey)); + *pValueLoc = value; + } + } + else if (!IsNull(value)) + { + DWORD entriesInArrayTotal; + DWORD usedEntries = ComputeUsedEntries(pKeyValueStore, &entriesInArrayTotal); + + if (usedEntries == entriesInArrayTotal) + { + // There isn't free space. Build a new, bigger array with the existing data + DWORD newSize; + if (usedEntries < 8) + newSize = usedEntries + 1; // Grow very slowly initially. The cost of allocation/copy is cheap, and this holds very tight on memory usage + else + newSize = usedEntries * 2; + + if (newSize < usedEntries) + COMPlusThrow(kOverflowException); + + // Allocate the new array. + I1ARRAYREF newKeyValueStore = (I1ARRAYREF)AllocatePrimitiveArray(ELEMENT_TYPE_I1, newSize*sizeof(TValue) + sizeof(TKey), FALSE); + + // Since, AllocatePrimitiveArray may have triggered a GC, recapture all data pointers from GC objects + void* pStartOfNewArray = newKeyValueStore->GetDirectPointerToNonObjectElements(); + void* pStartOfOldArray = ((I1ARRAYREF)*pKeyValueStore)->GetDirectPointerToNonObjectElements(); + + memcpyNoGCRefs(pStartOfNewArray, pStartOfOldArray, ((I1ARRAYREF)*pKeyValueStore)->GetNumComponents()); + + *pKeyValueStore = (OBJECTREF)newKeyValueStore; + updatedKeyValueStore = true; + + entriesInArrayTotal = newSize; + } + + // There is free space. Append on the end + TValue* pStartOfValuesData = (TValue*)(((I1ARRAYREF)*pKeyValueStore)->GetDirectPointerToNonObjectElements() + sizeof(TKey)); + SetUsedEntries(pStartOfValuesData, entriesInArrayTotal, usedEntries + 1); + pStartOfValuesData[usedEntries] = value; + } + + return updatedKeyValueStore; +} +#endif //!DACCESS_COMPILE + +template +/*static*/ TKey_ NoRemoveDefaultCrossLoaderAllocatorHashTraits::ReadKeyFromKeyValueStore(OBJECTREF *pKeyValueStore) +{ + WRAPPER_NO_CONTRACT; + + TKey* pKeyLoc = (TKey*)((I1ARRAYREF)*pKeyValueStore)->GetDirectPointerToNonObjectElements(); + return *pKeyLoc; +} + +template +template +/*static*/ bool NoRemoveDefaultCrossLoaderAllocatorHashTraits::VisitKeyValueStore(OBJECTREF *pLoaderAllocatorRef, OBJECTREF *pKeyValueStore, Visitor &visitor) +{ + WRAPPER_NO_CONTRACT; + + DWORD entriesInArrayTotal; + DWORD usedEntries = ComputeUsedEntries(pKeyValueStore, &entriesInArrayTotal); + + for (DWORD index = 0; index < usedEntries; ++index) + { + // Capture pKeyLoc and pStartOfValuesData inside of loop, as we aren't protecting these pointers into the GC heap, so they + // are not permitted to live across the call to visitor (in case visitor triggers a GC) + TKey* pKeyLoc = (TKey*)((I1ARRAYREF)*pKeyValueStore)->GetDirectPointerToNonObjectElements(); + TValue* pStartOfValuesData = (TValue*)(((I1ARRAYREF)*pKeyValueStore)->GetDirectPointerToNonObjectElements() + sizeof(TKey)); + + if (!visitor(*pLoaderAllocatorRef, *pKeyLoc, pStartOfValuesData[index])) + { + return false; + } + } + + return true; +} + +#ifndef DACCESS_COMPILE +template +/*static*/ void DefaultCrossLoaderAllocatorHashTraits::DeleteValueInHeapMemory(OBJECTREF keyValueStore, const TValue& value) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + // TODO: Consider optimizing this by changing the add to ensure that the + // values list is sorted, and then doing a binary search for the value instead + // of the linear search + + DWORD entriesInArrayTotal; + DWORD usedEntries = NoRemoveDefaultCrossLoaderAllocatorHashTraits::ComputeUsedEntries(&keyValueStore, &entriesInArrayTotal); + TValue* pStartOfValuesData = (TValue*)(((I1ARRAYREF)keyValueStore)->GetDirectPointerToNonObjectElements() + sizeof(TKey)); + + for (DWORD iEntry = 0; iEntry < usedEntries; iEntry++) + { + if (pStartOfValuesData[iEntry] == value) + { + memmove(pStartOfValuesData + iEntry, pStartOfValuesData + iEntry + 1, (usedEntries - iEntry - 1) * sizeof(TValue)); + SetUsedEntries(pStartOfValuesData, entriesInArrayTotal, usedEntries - 1); + return; + } + } +} +#endif //!DACCESS_COMPILE + +/*static*/ inline INT32 GCHeapHashDependentHashTrackerHashTraits::Hash(PtrTypeKey *pValue) +{ + LIMITED_METHOD_CONTRACT; + return (INT32)*pValue; +} + +/*static*/ inline INT32 GCHeapHashDependentHashTrackerHashTraits::Hash(PTRARRAYREF arr, INT32 index) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + LAHASHDEPENDENTHASHTRACKERREF value = (LAHASHDEPENDENTHASHTRACKERREF)arr->GetAt(index); + LoaderAllocator *pLoaderAllocator = value->GetLoaderAllocatorUnsafe(); + return Hash(&pLoaderAllocator); +} + +/*static*/ inline bool GCHeapHashDependentHashTrackerHashTraits::DoesEntryMatchKey(PTRARRAYREF arr, INT32 index, PtrTypeKey *pKey) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + LAHASHDEPENDENTHASHTRACKERREF value = (LAHASHDEPENDENTHASHTRACKERREF)arr->GetAt(index); + + return value->IsTrackerFor(*pKey); +} + +/*static*/ inline bool GCHeapHashDependentHashTrackerHashTraits::IsDeleted(PTRARRAYREF arr, INT32 index, GCHEAPHASHOBJECTREF gcHeap) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + OBJECTREF valueInHeap = arr->GetAt(index); + + if (valueInHeap == NULL) + return false; + + if (gcHeap == valueInHeap) + return true; + + // This is a tricky bit of logic used which detects freed loader allocators lazily + // and deletes them from the GCHeapHash while looking up or otherwise walking the hashtable + // for any purpose. + LAHASHDEPENDENTHASHTRACKERREF value = (LAHASHDEPENDENTHASHTRACKERREF)valueInHeap; + if (!value->IsLoaderAllocatorLive()) + { +#ifndef DACCESS_COMPILE + arr->SetAt(index, gcHeap); + gcHeap->DecrementCount(true); +#endif // DACCESS_COMPILE + + return true; + } + + return false; +} + +template +template +/*static*/ INT32 KeyToValuesGCHeapHashTraits::Hash(TKey *pValue) +{ + LIMITED_METHOD_CONTRACT; + return (INT32)(DWORD)*pValue; +} + +template +/*static*/ inline INT32 KeyToValuesGCHeapHashTraits::Hash(PTRARRAYREF arr, INT32 index) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + OBJECTREF hashKeyEntry = arr->GetAt(index); + LAHASHKEYTOTRACKERSREF hashKeyToTrackers; + OBJECTREF keyValueStore; + + if (hashKeyEntry->GetMethodTable() == MscorlibBinder::GetExistingClass(CLASS__LAHASHKEYTOTRACKERS)) + { + hashKeyToTrackers = (LAHASHKEYTOTRACKERSREF)hashKeyEntry; + keyValueStore = hashKeyToTrackers->_laLocalKeyValueStore; + } + else + { + keyValueStore = hashKeyEntry; + } + + typename TRAITS::TKey key = TRAITS::ReadKeyFromKeyValueStore(&keyValueStore); + return Hash(&key); +} + +template +template +/*static*/ bool KeyToValuesGCHeapHashTraits::DoesEntryMatchKey(PTRARRAYREF arr, INT32 index, TKey *pKey) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + OBJECTREF hashKeyEntry = arr->GetAt(index); + LAHASHKEYTOTRACKERSREF hashKeyToTrackers; + OBJECTREF keyValueStore; + + if (hashKeyEntry->GetMethodTable() == MscorlibBinder::GetExistingClass(CLASS__LAHASHKEYTOTRACKERS)) + { + hashKeyToTrackers = (LAHASHKEYTOTRACKERSREF)hashKeyEntry; + keyValueStore = hashKeyToTrackers->_laLocalKeyValueStore; + } + else + { + keyValueStore = hashKeyEntry; + } + + TKey key = TRAITS::ReadKeyFromKeyValueStore(&keyValueStore); + + return key == *pKey; +} + +#ifndef DACCESS_COMPILE +template +void CrossLoaderAllocatorHash::Add(TKey key, TValue value, LoaderAllocator *pLoaderAllocatorOfValue) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + + struct { + KeyToValuesGCHeapHash keyToTrackersHash; + KeyToValuesGCHeapHash keyToValuePerLAHash; + OBJECTREF keyValueStore; + OBJECTREF hashKeyEntry; + LAHASHKEYTOTRACKERSREF hashKeyToTrackers; + } gc; + ZeroMemory(&gc, sizeof(gc)); + GCPROTECT_BEGIN(gc) + { + EnsureManagedObjectsInitted(); + + bool addToKeyValuesHash = false; + // This data structure actually doesn't have this invariant, but it is expected that uses of this + // data structure will require that the key's loader allocator is the same as that of this data structure. + _ASSERTE(key->GetLoaderAllocator() == m_pLoaderAllocator); + + gc.keyToTrackersHash = KeyToValuesGCHeapHash((GCHEAPHASHOBJECTREF)ObjectFromHandle(m_keyToDependentTrackersHash)); + INT32 index = gc.keyToTrackersHash.GetValueIndex(&key); + + if (index == -1) + { + addToKeyValuesHash = true; + TRAITS::AddToValuesInHeapMemory(&gc.keyValueStore, key, pLoaderAllocatorOfValue == m_pLoaderAllocator ? value : TRAITS::NullValue()); + + if (pLoaderAllocatorOfValue != m_pLoaderAllocator) + { + gc.hashKeyToTrackers = (LAHASHKEYTOTRACKERSREF)AllocateObject(MscorlibBinder::GetExistingClass(CLASS__LAHASHKEYTOTRACKERS)); + SetObjectReference(&gc.hashKeyToTrackers->_laLocalKeyValueStore, gc.keyValueStore, GetAppDomain()); + gc.hashKeyEntry = gc.hashKeyToTrackers; + } + else + { + gc.hashKeyEntry = gc.keyValueStore; + } + + gc.keyToTrackersHash.Add(&key, [&gc](PTRARRAYREF arr, INT32 index) + { + arr->SetAt(index, (OBJECTREF)gc.hashKeyEntry); + }); + } + else + { + gc.keyToTrackersHash.GetElement(index, gc.hashKeyEntry); + + if (gc.hashKeyEntry->GetMethodTable() == MscorlibBinder::GetExistingClass(CLASS__LAHASHKEYTOTRACKERS)) + { + gc.hashKeyToTrackers = (LAHASHKEYTOTRACKERSREF)gc.hashKeyEntry; + gc.keyValueStore = gc.hashKeyToTrackers->_laLocalKeyValueStore; + } + else + { + gc.keyValueStore = gc.hashKeyEntry; + } + + bool updatedKeyValueStore = false; + + if (pLoaderAllocatorOfValue == m_pLoaderAllocator) + { + updatedKeyValueStore = TRAITS::AddToValuesInHeapMemory(&gc.keyValueStore, key, value); + } + + if (updatedKeyValueStore) + { + if (gc.hashKeyToTrackers != NULL) + { + SetObjectReference(&gc.hashKeyToTrackers->_laLocalKeyValueStore, gc.keyValueStore, GetAppDomain()); + } + else + { + gc.hashKeyEntry = gc.keyValueStore; + gc.keyToTrackersHash.SetElement(index, gc.hashKeyEntry); + } + } + } + + // If the LoaderAllocator matches, we've finished adding by now, otherwise, we need to get the remove hash and work with that + if (pLoaderAllocatorOfValue != m_pLoaderAllocator) + { + if (gc.hashKeyToTrackers == NULL) + { + // Nothing has yet caused the trackers proxy object to be setup. Create it now, and update the keyToTrackersHash + gc.hashKeyToTrackers = (LAHASHKEYTOTRACKERSREF)AllocateObject(MscorlibBinder::GetExistingClass(CLASS__LAHASHKEYTOTRACKERS)); + SetObjectReference(&gc.hashKeyToTrackers->_laLocalKeyValueStore, gc.keyValueStore, GetAppDomain()); + gc.hashKeyEntry = gc.hashKeyToTrackers; + gc.keyToTrackersHash.SetElement(index, gc.hashKeyEntry); + } + + // Must add it to the cross LA structure + GCHEAPHASHOBJECTREF gcheapKeyToValue = GetKeyToValueCrossLAHashForHashkeyToTrackers(gc.hashKeyToTrackers, pLoaderAllocatorOfValue); + + gc.keyToValuePerLAHash = KeyToValuesGCHeapHash(gcheapKeyToValue); + + INT32 indexInKeyValueHash = gc.keyToValuePerLAHash.GetValueIndex(&key); + if (indexInKeyValueHash != -1) + { + gc.keyToValuePerLAHash.GetElement(indexInKeyValueHash, gc.keyValueStore); + + if (TRAITS::AddToValuesInHeapMemory(&gc.keyValueStore, key, value)) + { + gc.keyToValuePerLAHash.SetElement(indexInKeyValueHash, gc.keyValueStore); + } + } + else + { + gc.keyValueStore = NULL; + TRAITS::AddToValuesInHeapMemory(&gc.keyValueStore, key, value); + + gc.keyToValuePerLAHash.Add(&key, [&gc](PTRARRAYREF arr, INT32 index) + { + arr->SetAt(index, gc.keyValueStore); + }); + } + } + } + GCPROTECT_END(); +} +#endif // !DACCESS_COMPILE + +#ifndef DACCESS_COMPILE +template +void CrossLoaderAllocatorHash::Remove(TKey key, TValue value, LoaderAllocator *pLoaderAllocatorOfValue) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + // This data structure actually doesn't have this invariant, but it is expected that uses of this + // data structure will require that the key's loader allocator is the same as that of this data structure. + _ASSERTE(key->GetLoaderAllocator() == m_pLoaderAllocator); + + if (m_keyToDependentTrackersHash == NULL) + { + // If the heap objects haven't been initted, then there is nothing to delete + return; + } + + struct { + KeyToValuesGCHeapHash keyToTrackersHash; + KeyToValuesGCHeapHash keyToValuePerLAHash; + OBJECTREF hashKeyEntry; + LAHASHKEYTOTRACKERSREF hashKeyToTrackers; + OBJECTREF keyValueStore; + } gc; + + ZeroMemory(&gc, sizeof(gc)); + GCPROTECT_BEGIN(gc) + { + gc.keyToTrackersHash = KeyToValuesGCHeapHash((GCHEAPHASHOBJECTREF)ObjectFromHandle(m_keyToDependentTrackersHash)); + INT32 index = gc.keyToTrackersHash.GetValueIndex(&key); + + if (index != -1) + { + gc.keyToTrackersHash.GetElement(index, gc.hashKeyEntry); + + if (gc.hashKeyEntry->GetMethodTable() == MscorlibBinder::GetExistingClass(CLASS__LAHASHKEYTOTRACKERS)) + { + gc.hashKeyToTrackers = (LAHASHKEYTOTRACKERSREF)gc.hashKeyEntry; + gc.keyValueStore = gc.hashKeyToTrackers->_laLocalKeyValueStore; + } + else + { + gc.keyValueStore = gc.hashKeyEntry; + } + + // Check to see if value can be added to this data structure directly. + if (m_pLoaderAllocator == pLoaderAllocatorOfValue) + { + TRAITS::DeleteValueInHeapMemory(gc.keyValueStore, value); + } + else if (gc.hashKeyToTrackers != NULL) + { + // Must remove it from the cross LA structure + GCHEAPHASHOBJECTREF gcheapKeyToValue = GetKeyToValueCrossLAHashForHashkeyToTrackers(gc.hashKeyToTrackers, pLoaderAllocatorOfValue); + + gc.keyToValuePerLAHash = KeyToValuesGCHeapHash(gcheapKeyToValue); + + INT32 indexInKeyValueHash = gc.keyToValuePerLAHash.GetValueIndex(&key); + if (indexInKeyValueHash != -1) + { + gc.keyToValuePerLAHash.GetElement(indexInKeyValueHash, gc.keyValueStore); + TRAITS::DeleteValueInHeapMemory(gc.keyValueStore, value); + } + } + } + } + GCPROTECT_END(); +} +#endif // !DACCESS_COMPILE + +template +template +bool CrossLoaderAllocatorHash::VisitValuesOfKey(TKey key, Visitor &visitor) +{ + WRAPPER_NO_CONTRACT; + + class VisitIndividualEntryKeyValueHash + { + public: + TKey m_key; + Visitor *m_pVisitor; + GCHeapHashDependentHashTrackerHash *m_pDependentTrackerHash; + + VisitIndividualEntryKeyValueHash(TKey key, Visitor *pVisitor, GCHeapHashDependentHashTrackerHash *pDependentTrackerHash) : + m_key(key), + m_pVisitor(pVisitor), + m_pDependentTrackerHash(pDependentTrackerHash) + {} + + bool operator()(INT32 index) + { + WRAPPER_NO_CONTRACT; + + LAHASHDEPENDENTHASHTRACKERREF dependentTracker; + m_pDependentTrackerHash->GetElement(index, dependentTracker); + return VisitTracker(m_key, dependentTracker, *m_pVisitor); + } + }; + + // This data structure actually doesn't have this invariant, but it is expected that uses of this + // data structure will require that the key's loader allocator is the same as that of this data structure. + _ASSERTE(key->GetLoaderAllocator() == m_pLoaderAllocator); + + // Check to see that something has been added + if (m_keyToDependentTrackersHash == NULL) + return true; + + bool result = true; + struct + { + KeyToValuesGCHeapHash keyToTrackersHash; + GCHeapHashDependentHashTrackerHash dependentTrackerHash; + LAHASHDEPENDENTHASHTRACKERREF dependentTrackerMaybe; + LAHASHDEPENDENTHASHTRACKERREF dependentTracker; + OBJECTREF hashKeyEntry; + LAHASHKEYTOTRACKERSREF hashKeyToTrackers; + OBJECTREF keyValueStore; + OBJECTREF nullref; + } gc; + ZeroMemory(&gc, sizeof(gc)); + GCPROTECT_BEGIN(gc) + { + gc.keyToTrackersHash = KeyToValuesGCHeapHash((GCHEAPHASHOBJECTREF)ObjectFromHandle(m_keyToDependentTrackersHash)); + INT32 index = gc.keyToTrackersHash.GetValueIndex(&key); + if (index != -1) + { + // We have an entry in the hashtable for the key/dependenthandle. + gc.keyToTrackersHash.GetElement(index, gc.hashKeyEntry); + + if (gc.hashKeyEntry->GetMethodTable() == MscorlibBinder::GetExistingClass(CLASS__LAHASHKEYTOTRACKERS)) + { + gc.hashKeyToTrackers = (LAHASHKEYTOTRACKERSREF)gc.hashKeyEntry; + gc.keyValueStore = gc.hashKeyToTrackers->_laLocalKeyValueStore; + } + else + { + gc.keyValueStore = gc.hashKeyEntry; + } + + // Now gc.hashKeyToTrackers is filled in and keyValueStore + + // visit local entries + result = VisitKeyValueStore(&gc.nullref, &gc.keyValueStore, visitor); + + if (gc.hashKeyToTrackers != NULL) + { + // Is there a single dependenttracker here, or a set. + + if (gc.hashKeyToTrackers->_trackerOrTrackerSet->GetMethodTable() == MscorlibBinder::GetExistingClass(CLASS__LAHASHDEPENDENTHASHTRACKER)) + { + gc.dependentTracker = (LAHASHDEPENDENTHASHTRACKERREF)gc.hashKeyToTrackers->_trackerOrTrackerSet; + result = VisitTracker(key, gc.dependentTracker, visitor); + } + else + { + gc.dependentTrackerHash = GCHeapHashDependentHashTrackerHash(gc.hashKeyToTrackers->_trackerOrTrackerSet); + VisitIndividualEntryKeyValueHash visitIndivididualKeys(key, &visitor, &gc.dependentTrackerHash); + result = gc.dependentTrackerHash.VisitAllEntryIndices(visitIndivididualKeys); + } + } + } + } + GCPROTECT_END(); + + return result; +} + +template +template +bool CrossLoaderAllocatorHash::VisitAllKeyValuePairs(Visitor &visitor) +{ + CONTRACTL + { + MODE_COOPERATIVE; + GC_NOTRIGGER; + } + CONTRACTL_END; + + class VisitAllEntryKeyToDependentTrackerHash + { + public: + Visitor *m_pVisitor; + KeyToValuesGCHeapHash *m_pKeyToTrackerHash; + + VisitAllEntryKeyToDependentTrackerHash(Visitor *pVisitor, KeyToValuesGCHeapHash *pKeyToTrackerHash) : + m_pVisitor(pVisitor), + m_pKeyToTrackerHash(pKeyToTrackerHash) + {} + + bool operator()(INT32 index) + { + WRAPPER_NO_CONTRACT; + + OBJECTREF hashKeyEntry; + m_pKeyToTrackerHash->GetElement(index, hashKeyEntry); + return VisitKeyToTrackerAllEntries(hashKeyEntry, *m_pVisitor); + } + }; + + class VisitAllEntryDependentTrackerHash + { + public: + Visitor *m_pVisitor; + GCHeapHashDependentHashTrackerHash *m_pDependentTrackerHash; + + VisitAllEntryDependentTrackerHash(Visitor *pVisitor, GCHeapHashDependentHashTrackerHash *pDependentTrackerHash) : + m_pVisitor(pVisitor), + m_pDependentTrackerHash(pDependentTrackerHash) + {} + + bool operator()(INT32 index) + { + WRAPPER_NO_CONTRACT; + + LAHASHDEPENDENTHASHTRACKERREF dependentTracker; + m_pDependentTrackerHash->GetElement(index, dependentTracker); + return VisitTrackerAllEntries(dependentTracker, *m_pVisitor); + } + }; + + struct + { + KeyToValuesGCHeapHash keyToTrackersHash; + GCHeapHashDependentHashTrackerHash dependentTrackerHash; + } gc; + ZeroMemory(&gc, sizeof(gc)); + bool result = true; + GCPROTECT_BEGIN(gc) + { + if (m_keyToDependentTrackersHash != NULL) + { + // Visit all local entries + gc.keyToTrackersHash = KeyToValuesGCHeapHash((GCHEAPHASHOBJECTREF)ObjectFromHandle(m_keyToDependentTrackersHash)); + VisitAllEntryKeyToDependentTrackerHash visitAllEntryKeys(&visitor, &gc.keyToTrackersHash); + result = gc.keyToTrackersHash.VisitAllEntryIndices(visitAllEntryKeys); + } + + if (m_loaderAllocatorToDependentTrackerHash != NULL) + { + // Visit the non-local data + gc.dependentTrackerHash = GCHeapHashDependentHashTrackerHash((GCHEAPHASHOBJECTREF)ObjectFromHandle(m_loaderAllocatorToDependentTrackerHash)); + VisitAllEntryDependentTrackerHash visitDependentTrackers(&visitor, &gc.dependentTrackerHash); + result = gc.dependentTrackerHash.VisitAllEntryIndices(visitDependentTrackers); + } + } + GCPROTECT_END(); + + return result; +} + +#ifndef DACCESS_COMPILE +template +void CrossLoaderAllocatorHash::RemoveAll(TKey key) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + class DeleteIndividualEntryKeyValueHash + { + public: + TKey m_key; + GCHeapHashDependentHashTrackerHash *m_pDependentTrackerHash; + + DeleteIndividualEntryKeyValueHash(TKey key, GCHeapHashDependentHashTrackerHash *pDependentTrackerHash) : + m_key(key), + m_pDependentTrackerHash(pDependentTrackerHash) + {} + + bool operator()(INT32 index) + { + WRAPPER_NO_CONTRACT; + + LAHASHDEPENDENTHASHTRACKERREF dependentTracker; + m_pDependentTrackerHash->GetElement(index, dependentTracker); + DeleteEntryTracker(m_key, dependentTracker); + return true; + } + }; + + // This data structure actually doesn't have this invariant, but it is expected that uses of this + // data structure will require that the key's loader allocator is the same as that of this data structure. + _ASSERTE(key->GetLoaderAllocator() == m_pLoaderAllocator); + + if (m_keyToDependentTrackersHash == NULL) + { + return; // Nothing was ever added, so removing all is easy + } + + struct + { + KeyToValuesGCHeapHash keyToTrackersHash; + GCHeapHashDependentHashTrackerHash dependentTrackerHash; + LAHASHDEPENDENTHASHTRACKERREF dependentTracker; + OBJECTREF hashKeyEntry; + LAHASHKEYTOTRACKERSREF hashKeyToTrackers; + OBJECTREF keyValueStore; + } gc; + ZeroMemory(&gc, sizeof(gc)); + GCPROTECT_BEGIN(gc) + { + gc.keyToTrackersHash = KeyToValuesGCHeapHash((GCHEAPHASHOBJECTREF)ObjectFromHandle(m_keyToDependentTrackersHash)); + INT32 index = gc.keyToTrackersHash.GetValueIndex(&key); + if (index != -1) + { + // We have an entry in the hashtable for the key/dependenthandle. + gc.keyToTrackersHash.GetElement(index, gc.hashKeyEntry); + + if (gc.hashKeyEntry->GetMethodTable() == MscorlibBinder::GetExistingClass(CLASS__LAHASHKEYTOTRACKERS)) + { + gc.hashKeyToTrackers = (LAHASHKEYTOTRACKERSREF)gc.hashKeyEntry; + gc.keyValueStore = gc.hashKeyToTrackers->_laLocalKeyValueStore; + } + else + { + gc.keyValueStore = gc.hashKeyEntry; + } + + // Now gc.hashKeyToTrackers is filled in + + if (gc.hashKeyToTrackers != NULL) + { + // Is there a single dependenttracker here, or a set. + + if (gc.hashKeyToTrackers->_trackerOrTrackerSet->GetMethodTable() == MscorlibBinder::GetExistingClass(CLASS__LAHASHDEPENDENTHASHTRACKER)) + { + gc.dependentTracker = (LAHASHDEPENDENTHASHTRACKERREF)gc.hashKeyToTrackers->_trackerOrTrackerSet; + DeleteEntryTracker(key, gc.dependentTracker); + } + else + { + gc.dependentTrackerHash = GCHeapHashDependentHashTrackerHash(gc.hashKeyToTrackers->_trackerOrTrackerSet); + DeleteIndividualEntryKeyValueHash deleteIndividualKeyValues(key, &gc.dependentTrackerHash); + gc.dependentTrackerHash.VisitAllEntryIndices(deleteIndividualKeyValues); + } + } + + // Remove entry from key to tracker hash + gc.keyToTrackersHash.DeleteEntry(&key); + } + } + GCPROTECT_END(); +} +#endif // !DACCESS_COMPILE + +template +void CrossLoaderAllocatorHash::Init(LoaderAllocator *pAssociatedLoaderAllocator) +{ + LIMITED_METHOD_CONTRACT; + m_pLoaderAllocator = pAssociatedLoaderAllocator; +} + +template +template +/*static*/ bool CrossLoaderAllocatorHash::VisitKeyValueStore(OBJECTREF *pLoaderAllocatorRef, OBJECTREF *pKeyValueStore, Visitor &visitor) +{ + WRAPPER_NO_CONTRACT; + + return TRAITS::VisitKeyValueStore(pLoaderAllocatorRef, pKeyValueStore, visitor); +} + +template +template +/*static*/ bool CrossLoaderAllocatorHash::VisitTracker(TKey key, LAHASHDEPENDENTHASHTRACKERREF trackerUnsafe, Visitor &visitor) +{ + CONTRACTL + { + MODE_COOPERATIVE; + GC_NOTRIGGER; + } + CONTRACTL_END; + + struct + { + LAHASHDEPENDENTHASHTRACKERREF tracker; + OBJECTREF loaderAllocatorRef; + GCHEAPHASHOBJECTREF keyToValuesHashObject; + KeyToValuesGCHeapHash keyToValuesHash; + OBJECTREF keyValueStore; + }gc; + + ZeroMemory(&gc, sizeof(gc)); + gc.tracker = trackerUnsafe; + + bool result = true; + + GCPROTECT_BEGIN(gc); + { + gc.tracker->GetDependentAndLoaderAllocator(&gc.loaderAllocatorRef, &gc.keyToValuesHashObject); + if (gc.keyToValuesHashObject != NULL) + { + gc.keyToValuesHash = KeyToValuesGCHeapHash(gc.keyToValuesHashObject); + INT32 indexInKeyValueHash = gc.keyToValuesHash.GetValueIndex(&key); + if (indexInKeyValueHash != -1) + { + gc.keyToValuesHash.GetElement(indexInKeyValueHash, gc.keyValueStore); + + result = VisitKeyValueStore(&gc.loaderAllocatorRef, &gc.keyValueStore, visitor); + } + } + } + GCPROTECT_END(); + + return result; +} + +template +template +/*static*/ bool CrossLoaderAllocatorHash::VisitTrackerAllEntries(LAHASHDEPENDENTHASHTRACKERREF trackerUnsafe, Visitor &visitor) +{ + CONTRACTL + { + MODE_COOPERATIVE; + GC_NOTRIGGER; + } + CONTRACTL_END; + + struct + { + LAHASHDEPENDENTHASHTRACKERREF tracker; + OBJECTREF loaderAllocatorRef; + GCHEAPHASHOBJECTREF keyToValuesHashObject; + KeyToValuesGCHeapHash keyToValuesHash; + OBJECTREF keyValueStore; + }gc; + + class VisitAllEntryKeyValueHash + { + public: + Visitor *m_pVisitor; + KeyToValuesGCHeapHash *m_pKeysToValueHash; + OBJECTREF *m_pKeyValueStore; + OBJECTREF *m_pLoaderAllocatorRef; + + VisitAllEntryKeyValueHash(Visitor *pVisitor, KeyToValuesGCHeapHash *pKeysToValueHash, OBJECTREF *pKeyValueStore, OBJECTREF *pLoaderAllocatorRef) : + m_pVisitor(pVisitor), + m_pKeysToValueHash(pKeysToValueHash), + m_pKeyValueStore(pKeyValueStore), + m_pLoaderAllocatorRef(pLoaderAllocatorRef) + {} + + bool operator()(INT32 index) + { + WRAPPER_NO_CONTRACT; + + m_pKeysToValueHash->GetElement(index, *m_pKeyValueStore); + return VisitKeyValueStore(m_pLoaderAllocatorRef, m_pKeyValueStore, visitor); + } + }; + + ZeroMemory(&gc, sizeof(gc)); + gc.tracker = trackerUnsafe; + + bool result = true; + + GCPROTECT_BEGIN(gc); + { + gc.tracker->GetDependentAndLoaderAllocator(&gc.loaderAllocatorRef, &gc.keyToValuesHashObject); + if (gc.keyToValuesHashObject != NULL) + { + gc.keyToValuesHash = KeyToValuesGCHeapHash(gc.keyToValuesHashObject); + result = gc.keyToValuesHash.VisitAllEntryIndices(VisitAllEntryKeyValueHash(&visitor, &gc.keyToValuesHash, &gc.keyValueStore, &gc.loaderAllocatorRef)); + } + } + GCPROTECT_END(); + + return result; +} + +template +template +/*static*/ bool CrossLoaderAllocatorHash::VisitKeyToTrackerAllEntries(OBJECTREF hashKeyEntryUnsafe, Visitor &visitor) +{ + CONTRACTL + { + MODE_COOPERATIVE; + GC_NOTRIGGER; + } + CONTRACTL_END; + + struct + { + OBJECTREF hashKeyEntry; + LAHASHKEYTOTRACKERSREF hashKeyToTrackers; + OBJECTREF keyValueStore; + OBJECTREF loaderAllocatorRef; + } gc; + + ZeroMemory(&gc, sizeof(gc)); + gc.hashKeyEntry = hashKeyEntryUnsafe; + + bool result = true; + + GCPROTECT_BEGIN(gc); + { + if (gc.hashKeyEntry->GetMethodTable() == MscorlibBinder::GetExistingClass(CLASS__LAHASHKEYTOTRACKERS)) + { + gc.hashKeyToTrackers = (LAHASHKEYTOTRACKERSREF)gc.hashKeyEntry; + gc.keyValueStore = gc.hashKeyToTrackers->_laLocalKeyValueStore; + } + else + { + gc.keyValueStore = gc.hashKeyEntry; + } + + result = VisitKeyValueStore(&gc.loaderAllocatorRef, &gc.keyValueStore, visitor); + } + GCPROTECT_END(); + + return result; +} + +template +/*static*/ void CrossLoaderAllocatorHash::DeleteEntryTracker(TKey key, LAHASHDEPENDENTHASHTRACKERREF trackerUnsafe) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + struct + { + LAHASHDEPENDENTHASHTRACKERREF tracker; + OBJECTREF loaderAllocatorRef; + GCHEAPHASHOBJECTREF keyToValuesHashObject; + KeyToValuesGCHeapHash keyToValuesHash; + }gc; + + ZeroMemory(&gc, sizeof(gc)); + gc.tracker = trackerUnsafe; + + GCPROTECT_BEGIN(gc); + { + gc.tracker->GetDependentAndLoaderAllocator(&gc.loaderAllocatorRef, &gc.keyToValuesHashObject); + if (gc.keyToValuesHashObject != NULL) + { + gc.keyToValuesHash = KeyToValuesGCHeapHash(gc.keyToValuesHashObject); + gc.keyToValuesHash.DeleteEntry(&key); + } + } + GCPROTECT_END(); +} + +#ifndef DACCESS_COMPILE +/*static */inline void CrossLoaderAllocatorHashSetup::EnsureTypesLoaded() +{ + STANDARD_VM_CONTRACT; + + // Force these types to be loaded, so that the hashtable logic can use MscorlibBinder::GetExistingClass + // throughout and avoid lock ordering issues + MscorlibBinder::GetClass(CLASS__LAHASHKEYTOTRACKERS); + MscorlibBinder::GetClass(CLASS__LAHASHDEPENDENTHASHTRACKER); + MscorlibBinder::GetClass(CLASS__GCHEAPHASH); + TypeHandle elemType = TypeHandle(MscorlibBinder::GetElementType(ELEMENT_TYPE_I1)); + TypeHandle typHnd = ClassLoader::LoadArrayTypeThrowing(elemType, ELEMENT_TYPE_SZARRAY, 0); + elemType = TypeHandle(MscorlibBinder::GetElementType(ELEMENT_TYPE_OBJECT)); + typHnd = ClassLoader::LoadArrayTypeThrowing(elemType, ELEMENT_TYPE_SZARRAY, 0); +} + +template +void CrossLoaderAllocatorHash::EnsureManagedObjectsInitted() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + if (m_loaderAllocatorToDependentTrackerHash == NULL) + { + OBJECTREF laToDependentHandleHashObject = AllocateObject(MscorlibBinder::GetExistingClass(CLASS__GCHEAPHASH)); + m_loaderAllocatorToDependentTrackerHash = m_pLoaderAllocator->GetDomain()->CreateHandle(laToDependentHandleHashObject); + m_pLoaderAllocator->RegisterHandleForCleanup(m_loaderAllocatorToDependentTrackerHash); + } + + if (m_keyToDependentTrackersHash == NULL) + { + OBJECTREF m_keyToDependentTrackersHashObject = AllocateObject(MscorlibBinder::GetExistingClass(CLASS__GCHEAPHASH)); + m_keyToDependentTrackersHash = m_pLoaderAllocator->GetDomain()->CreateHandle(m_keyToDependentTrackersHashObject); + m_pLoaderAllocator->RegisterHandleForCleanup(m_keyToDependentTrackersHash); + } +} +#endif // !DACCESS_COMPILE + +#ifndef DACCESS_COMPILE +template +LAHASHDEPENDENTHASHTRACKERREF CrossLoaderAllocatorHash::GetDependentTrackerForLoaderAllocator(LoaderAllocator* pLoaderAllocator) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + struct + { + GCHeapHashDependentHashTrackerHash dependentTrackerHash; + LAHASHDEPENDENTHASHTRACKERREF dependentTracker; + GCHEAPHASHOBJECTREF GCHeapHashForKeyToValueStore; + } gc; + ZeroMemory(&gc, sizeof(gc)); + GCPROTECT_BEGIN(gc) + { + gc.dependentTrackerHash = GCHeapHashDependentHashTrackerHash((GCHEAPHASHOBJECTREF)ObjectFromHandle(m_loaderAllocatorToDependentTrackerHash)); + INT32 index = gc.dependentTrackerHash.GetValueIndex(&pLoaderAllocator); + if (index != -1) + { + // We have an entry in the hashtable for the key/dependenthandle. + gc.dependentTrackerHash.GetElement(index, gc.dependentTracker); + } + else + { + gc.dependentTracker = (LAHASHDEPENDENTHASHTRACKERREF)AllocateObject(MscorlibBinder::GetExistingClass(CLASS__LAHASHDEPENDENTHASHTRACKER)); + gc.GCHeapHashForKeyToValueStore = (GCHEAPHASHOBJECTREF)AllocateObject(MscorlibBinder::GetExistingClass(CLASS__GCHEAPHASH)); + OBJECTHANDLE dependentHandle = GetAppDomain()->CreateDependentHandle(pLoaderAllocator->GetExposedObject(), gc.GCHeapHashForKeyToValueStore); + gc.dependentTracker->Init(dependentHandle, pLoaderAllocator); + gc.dependentTrackerHash.Add(&pLoaderAllocator, [&gc](PTRARRAYREF arr, INT32 index) + { + arr->SetAt(index, (OBJECTREF)gc.dependentTracker); + }); + } + } + GCPROTECT_END(); + + return gc.dependentTracker; +} +#endif // !DACCESS_COMPILE + +#ifndef DACCESS_COMPILE +template +GCHEAPHASHOBJECTREF CrossLoaderAllocatorHash::GetKeyToValueCrossLAHashForHashkeyToTrackers(LAHASHKEYTOTRACKERSREF hashKeyToTrackersUnsafe, LoaderAllocator* pValueLoaderAllocator) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + struct + { + GCHeapHashDependentHashTrackerHash dependentTrackerHash; + LAHASHDEPENDENTHASHTRACKERREF dependentTrackerMaybe; + LAHASHDEPENDENTHASHTRACKERREF dependentTracker; + LAHASHKEYTOTRACKERSREF hashKeyToTrackers; + GCHEAPHASHOBJECTREF returnValue; + } gc; + ZeroMemory(&gc, sizeof(gc)); + // Now gc.hashKeyToTrackers is filled in. + gc.hashKeyToTrackers = hashKeyToTrackersUnsafe; + GCPROTECT_BEGIN(gc) + { + EnsureManagedObjectsInitted(); + + // Is there a single dependenttracker here, or a set, or no dependenttracker at all + if (gc.hashKeyToTrackers->_trackerOrTrackerSet == NULL) + { + gc.dependentTracker = GetDependentTrackerForLoaderAllocator(pValueLoaderAllocator); + SetObjectReference(&gc.hashKeyToTrackers->_trackerOrTrackerSet, gc.dependentTracker, GetAppDomain()); + } + else if (gc.hashKeyToTrackers->_trackerOrTrackerSet->GetMethodTable() == MscorlibBinder::GetExistingClass(CLASS__LAHASHDEPENDENTHASHTRACKER)) + { + gc.dependentTrackerMaybe = (LAHASHDEPENDENTHASHTRACKERREF)gc.hashKeyToTrackers->_trackerOrTrackerSet; + if (gc.dependentTrackerMaybe->IsTrackerFor(pValueLoaderAllocator)) + { + // We've found the right dependent tracker. + gc.dependentTracker = gc.dependentTrackerMaybe; + } + else + { + gc.dependentTracker = GetDependentTrackerForLoaderAllocator(pValueLoaderAllocator); + if (!gc.dependentTrackerMaybe->IsLoaderAllocatorLive()) + { + SetObjectReference(&gc.hashKeyToTrackers->_trackerOrTrackerSet, gc.dependentTracker, GetAppDomain()); + } + else + { + // Allocate the dependent tracker hash + // Fill with the existing dependentTrackerMaybe, and gc.DependentTracker + gc.dependentTrackerHash = GCHeapHashDependentHashTrackerHash(AllocateObject(MscorlibBinder::GetExistingClass(CLASS__GCHEAPHASH))); + LoaderAllocator *pLoaderAllocatorKey = gc.dependentTracker->GetLoaderAllocatorUnsafe(); + gc.dependentTrackerHash.Add(&pLoaderAllocatorKey, [&gc](PTRARRAYREF arr, INT32 index) + { + arr->SetAt(index, (OBJECTREF)gc.dependentTracker); + }); + pLoaderAllocatorKey = gc.dependentTrackerMaybe->GetLoaderAllocatorUnsafe(); + gc.dependentTrackerHash.Add(&pLoaderAllocatorKey, [&gc](PTRARRAYREF arr, INT32 index) + { + arr->SetAt(index, (OBJECTREF)gc.dependentTrackerMaybe); + }); + SetObjectReference(&gc.hashKeyToTrackers->_trackerOrTrackerSet, gc.dependentTrackerHash.GetGCHeapRef(), GetAppDomain()); + } + } + } + else + { + gc.dependentTrackerHash = GCHeapHashDependentHashTrackerHash(gc.hashKeyToTrackers->_trackerOrTrackerSet); + + INT32 indexOfTracker = gc.dependentTrackerHash.GetValueIndex(&pValueLoaderAllocator); + if (indexOfTracker == -1) + { + // Dependent tracker not yet attached to this key + + // Get dependent tracker + gc.dependentTracker = GetDependentTrackerForLoaderAllocator(pValueLoaderAllocator); + gc.dependentTrackerHash.Add(&pValueLoaderAllocator, [&gc](PTRARRAYREF arr, INT32 index) + { + arr->SetAt(index, (OBJECTREF)gc.dependentTracker); + }); + } + else + { + gc.dependentTrackerHash.GetElement(indexOfTracker, gc.dependentTracker); + } + } + + // At this stage gc.dependentTracker is setup to have a good value + gc.dependentTracker->GetDependentAndLoaderAllocator(NULL, &gc.returnValue); + } + GCPROTECT_END(); + + return gc.returnValue; +} +#endif // !DACCESS_COMPILE + +#endif // !CROSSGEN_COMPILE +#endif // CROSSLOADERALLOCATORHASH_H +#endif // CROSSLOADERALLOCATORHASH_INL diff --git a/src/vm/frames.h b/src/vm/frames.h index 8847641198..3d092bd06e 100644 --- a/src/vm/frames.h +++ b/src/vm/frames.h @@ -3674,4 +3674,6 @@ public: #undef FPO_ON #endif +#include "crossloaderallocatorhash.inl" + #endif //__frames_h__ diff --git a/src/vm/gcheaphashtable.h b/src/vm/gcheaphashtable.h new file mode 100644 index 0000000000..a795ce6fa9 --- /dev/null +++ b/src/vm/gcheaphashtable.h @@ -0,0 +1,142 @@ +// 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. + +#ifndef GCHEAPHASHTABLE_H +#define GCHEAPHASHTABLE_H + +class GCHeapHashObject; + +template +struct DefaultGCHeapHashTraits +{ + typedef PTRARRAYREF THashArrayType; + static const INT32 s_growth_factor_numerator = 3; + static const INT32 s_growth_factor_denominator = 2; + + static const INT32 s_density_factor_numerator = 3; + static const INT32 s_density_factor_denominator = 4; + + static const INT32 s_densitywithdeletes_factor_numerator = 7; + static const INT32 s_densitywithdeletes_factor_denominator = 8; + + static const INT32 s_minimum_allocation = 7; + + static bool IsNull(PTRARRAYREF arr, INT32 index); + static bool IsDeleted(PTRARRAYREF arr, INT32 index, GCHEAPHASHOBJECTREF gcHeap); +#ifndef DACCESS_COMPILE + static THashArrayType AllocateArray(INT32 size); +#endif + + // Not a part of the traits api, but used to allow derived traits to save on code + static OBJECTREF GetValueAtIndex(GCHEAPHASHOBJECTREF *pgcHeap, INT32 index); + +#ifndef DACCESS_COMPILE + static void CopyValue(THashArrayType srcArray, INT32 indexSrc, THashArrayType destinationArray, INT32 indexDest); + static void DeleteEntry(GCHEAPHASHOBJECTREF *pgcHeap, INT32 index); +#endif // !DACCESS_COMPILE + + template + static void GetElement(GCHEAPHASHOBJECTREF *pgcHeap, INT32 index, TElement& foundElement); + +#ifndef DACCESS_COMPILE + template + static void SetElement(GCHEAPHASHOBJECTREF *pgcHeap, INT32 index, TElement& foundElement); +#endif // !DACCESS_COMPILE +}; + +template +struct GCHeapHashTraitsPointerToPointerList : public DefaultGCHeapHashTraits +{ + static INT32 Hash(PtrTypeKey *pValue); + static INT32 Hash(PTRARRAYREF arr, INT32 index); + static bool DoesEntryMatchKey(PTRARRAYREF arr, INT32 index, PtrTypeKey *pKey); +}; + + +// GCHeapHash is based on the logic of SHash, and utilizes the same basic structure (which allows the key/value +// to be one and the same, or other interesting memory tweaks.) To avoid GC pointer issues, responsibility for allocating +// the underlying arrays and manipulating the entries is entirely extracted to the traits class, and responsibility +// for creation of elements is deferred into the caller of the add function. (See example uses in CrossLoaderAllocatorHash) +// As the GCHeapHash is actually a managed object, but the code for manipulating the hash is written here in native code, +// allocating an instance of this class does not actually allocate a hashtable. Instead, the hashtable is allocated by +// allocating an instance of the GCHeapHash type, and then passing the allocated object into this type's constructor to +// assign the value. This class is designed to be used protected within a GC_PROTECT region. See examples in CrossLoaderAllocatorHash. +template +class GCHeapHash +{ + GCHEAPHASHOBJECTREF m_gcHeapHash; + + typedef typename TRAITS::THashArrayType THashArrayType; + typedef INT32 count_t; + + private: + // Insert into hashtable without growing. GCHEAPHASHOBJECTREF must be GC protected as must be TKey if needed + template + void Insert(TKey *pKey, const TValueSetter &valueSetter); + void CheckGrowth(); + void Grow(); + THashArrayType Grow_OnlyAllocateNewTable(); + + bool IsPrime(count_t number); + count_t NextPrime(count_t number); + + void ReplaceTable(THashArrayType newTable); + + template + count_t CallHash(TKey* pValue) + { + WRAPPER_NO_CONTRACT; + + count_t hash = TRAITS::Hash(pValue); + hash = hash < 0 ? -hash : hash; + if (hash < 0) + return 1; + else + return hash; + } + + count_t CallHash(THashArrayType arr, count_t index) + { + WRAPPER_NO_CONTRACT; + + count_t hash = TRAITS::Hash(arr, index); + hash = hash < 0 ? -hash : hash; + if (hash < 0) + return 1; + else + return hash; + } + + public: + + template + bool VisitAllEntryIndices(TVisitor &visitor); + + template + void Add(TKey *pKey, const TValueSetter &valueSetter); + + // Get the index in the hashtable of the value which matches key, or -1 if there are no matches + template + INT32 GetValueIndex(TKey *pKey); + + template + void GetElement(INT32 index, TElement& foundElement); + + // Use this to update an value within the hashtable directly. + // It is ONLY safe to do if the index already points at an element + // which already exists and has the same key as the newElementValue + template + void SetElement(INT32 index, TElement& newElementValue); + + template + void DeleteEntry(TKey *pKey); + + GCHEAPHASHOBJECTREF GetGCHeapRef() { LIMITED_METHOD_CONTRACT; return m_gcHeapHash; } + + GCHeapHash(GCHEAPHASHOBJECTREF gcHeap) : m_gcHeapHash(gcHeap) {} + GCHeapHash(OBJECTREF gcHeap) : m_gcHeapHash((GCHEAPHASHOBJECTREF)gcHeap) {} + GCHeapHash() : m_gcHeapHash((GCHEAPHASHOBJECTREF)TADDR(NULL)) {} +}; + +#endif // GCHEAPHASHTABLE_H diff --git a/src/vm/gcheaphashtable.inl b/src/vm/gcheaphashtable.inl new file mode 100644 index 0000000000..f2256bea1e --- /dev/null +++ b/src/vm/gcheaphashtable.inl @@ -0,0 +1,530 @@ +// 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. + +#ifdef GCHEAPHASHTABLE_H +#ifndef GCHEAPHASHTABLE_INL +#define GCHEAPHASHTABLE_INL + +template +/*static */bool DefaultGCHeapHashTraits::IsNull(PTRARRAYREF arr, INT32 index) +{ + LIMITED_METHOD_CONTRACT; + + return arr->GetAt(index) == 0; +} + +template +/*static */bool DefaultGCHeapHashTraits::IsDeleted(PTRARRAYREF arr, INT32 index, GCHEAPHASHOBJECTREF gcHeap) +{ + LIMITED_METHOD_CONTRACT; + + if (removeSupported) + { + return gcHeap == arr->GetAt(index); + } + else + { + return false; + } +} + +#ifndef DACCESS_COMPILE +template +/*static*/ typename DefaultGCHeapHashTraits::THashArrayType DefaultGCHeapHashTraits::AllocateArray(INT32 size) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + return (THashArrayType)AllocateObjectArray(size, g_pObjectClass); +} +#endif // !DACCESS_COMPILE + + // Not a part of the traits api, but used to allow derived traits to save on code +template +/*static*/ OBJECTREF DefaultGCHeapHashTraits::GetValueAtIndex(GCHEAPHASHOBJECTREF *pgcHeap, INT32 index) +{ + LIMITED_METHOD_CONTRACT; + + PTRARRAYREF arr((PTRARRAYREF)(*pgcHeap)->GetData()); + + OBJECTREF value = arr->GetAt(index); + + return value; +} + +#ifndef DACCESS_COMPILE +template +/*static*/ void DefaultGCHeapHashTraits::CopyValue(THashArrayType srcArray, INT32 indexSrc, THashArrayType destinationArray, INT32 indexDest) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + if (srcArray == NULL) + COMPlusThrow(kNullReferenceException); + + if ((INT32)srcArray->GetNumComponents() < indexSrc) + COMPlusThrow(kIndexOutOfRangeException); + + OBJECTREF value = srcArray->GetAt(indexSrc); + + if ((INT32)destinationArray->GetNumComponents() < indexDest) + COMPlusThrow(kIndexOutOfRangeException); + + destinationArray->SetAt(indexDest, value); +} +#endif // !DACCESS_COMPILE + +#ifndef DACCESS_COMPILE +template +/*static*/ void DefaultGCHeapHashTraits::DeleteEntry(GCHEAPHASHOBJECTREF *pgcHeap, INT32 index) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + static_assert(removeSupported, "This hash doesn't support remove"); + + PTRARRAYREF arr((PTRARRAYREF)(*pgcHeap)->GetData()); + + if (arr == NULL) + COMPlusThrow(kNullReferenceException); + + if ((INT32)arr->GetNumComponents() < index) + COMPlusThrow(kIndexOutOfRangeException); + + // The deleted sentinel is a self-pointer + arr->SetAt(index, *pgcHeap); +} +#endif // !DACCESS_COMPILE + +template +template +/*static*/ void DefaultGCHeapHashTraits::GetElement(GCHEAPHASHOBJECTREF *pgcHeap, INT32 index, TElement& foundElement) +{ + LIMITED_METHOD_CONTRACT; + + foundElement = (TElement)GetValueAtIndex(pgcHeap, index); +} + +#ifndef DACCESS_COMPILE +template +template +/*static*/ void DefaultGCHeapHashTraits::SetElement(GCHEAPHASHOBJECTREF *pgcHeap, INT32 index, TElement& foundElement) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + PTRARRAYREF arr((PTRARRAYREF)(*pgcHeap)->GetData()); + + if (arr == NULL) + COMPlusThrow(kNullReferenceException); + + if ((INT32)arr->GetNumComponents() < index) + COMPlusThrow(kIndexOutOfRangeException); + + arr->SetAt(index, foundElement); +} +#endif // !DACCESS_COMPILE + +template +/*static */INT32 GCHeapHashTraitsPointerToPointerList::Hash(PtrTypeKey *pValue) +{ + LIMITED_METHOD_CONTRACT; + return (INT32)*pValue; +} + +template +/*static */INT32 GCHeapHashTraitsPointerToPointerList::Hash(PTRARRAYREF arr, INT32 index) +{ + LIMITED_METHOD_CONTRACT; + + UPTRARRAYREF value = (UPTRARRAYREF)arr->GetAt(index); + + return (INT32)*value->GetDirectConstPointerToNonObjectElements(); +} + +template +/*static */bool GCHeapHashTraitsPointerToPointerList::DoesEntryMatchKey(PTRARRAYREF arr, INT32 index, PtrTypeKey *pKey) +{ + LIMITED_METHOD_CONTRACT; + + UPTRARRAYREF value = (UPTRARRAYREF)arr->GetAt(index); + UPTR uptrValue = *value->GetDirectConstPointerToNonObjectElements(); + + return ((UPTR)*pKey) == uptrValue; +} + +template +template +void GCHeapHash::Insert(TKey *pKey, const TValueSetter &valueSetter) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + count_t hash = CallHash(pKey); + count_t tableSize = m_gcHeapHash->GetCapacity(); + count_t index = hash % tableSize; + count_t increment = 0; // delay computation + + while (TRUE) + { + THashArrayType arr((THashArrayType)(m_gcHeapHash)->GetData()); + + bool isNull = TRAITS::IsNull(arr, index); + bool isDeleted = false; + if (!isNull && TRAITS::IsDeleted(arr, index, m_gcHeapHash)) + isDeleted = true; + + if (isNull || isDeleted) + { + if (arr == NULL) + COMPlusThrow(kNullReferenceException); + + if ((INT32)arr->GetNumComponents() < index) + COMPlusThrow(kIndexOutOfRangeException); + + valueSetter(arr, index); + m_gcHeapHash->IncrementCount(isDeleted); + return; + } + + if (increment == 0) + increment = (hash % (tableSize-1)) + 1; + + index += increment; + if (index >= tableSize) + index -= tableSize; + } +} + +template +void GCHeapHash::CheckGrowth() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + count_t tableMax = (count_t) (m_gcHeapHash->GetCapacity() * TRAITS::s_density_factor_numerator / TRAITS::s_density_factor_denominator); + if (m_gcHeapHash->GetCount() == tableMax) + Grow(); + else + { + tableMax = (count_t) (m_gcHeapHash->GetCapacity() * TRAITS::s_densitywithdeletes_factor_numerator / TRAITS::s_densitywithdeletes_factor_denominator); + if ((m_gcHeapHash->GetCount() + m_gcHeapHash->GetDeletedCount()) >= tableMax) + { + THashArrayType newTable = TRAITS::AllocateArray(m_gcHeapHash->GetCapacity()); + ReplaceTable(newTable); + } + } +} + +template +void GCHeapHash::Grow() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + THashArrayType newTable = Grow_OnlyAllocateNewTable(); + ReplaceTable(newTable); +} + +template +typename GCHeapHash::THashArrayType GCHeapHash::Grow_OnlyAllocateNewTable() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + INSTANCE_CHECK; + } + CONTRACTL_END; + + count_t newSize = (count_t) (m_gcHeapHash->GetCount() + * TRAITS::s_growth_factor_numerator / TRAITS::s_growth_factor_denominator + * TRAITS::s_density_factor_denominator / TRAITS::s_density_factor_numerator); + if (newSize < TRAITS::s_minimum_allocation) + newSize = TRAITS::s_minimum_allocation; + + // handle potential overflow + if (newSize < m_gcHeapHash->GetCount()) + ThrowOutOfMemory(); + + return TRAITS::AllocateArray(NextPrime(newSize)); +} + +template +bool GCHeapHash::IsPrime(count_t number) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + // This is a very low-tech check for primality, which doesn't scale very well. + // There are more efficient tests if this proves to be burdensome for larger + // tables. + + if ((number & 1) == 0) + return false; + + count_t factor = 3; + while (factor * factor <= number) + { + if ((number % factor) == 0) + return false; + factor += 2; + } + + return true; +} + +template +typename GCHeapHash::count_t GCHeapHash::NextPrime(count_t number) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + static_assert(sizeof(INT32) == sizeof(g_shash_primes[0]), "the cast below of g_shash_primes[] to INT32 isn't safe due to loss of precision."); + + for (int i = 0; i < (int) (sizeof(g_shash_primes) / sizeof(g_shash_primes[0])); i++) { + if ((INT32)g_shash_primes[i] >= number) + return (INT32)g_shash_primes[i]; + } + + if ((number&1) == 0) + number++; + + while (number != 1) { + if (IsPrime(number)) + return number; + number +=2; + } + + // overflow + ThrowOutOfMemory(); +} + +template +void GCHeapHash::ReplaceTable(THashArrayType newTable) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + GCPROTECT_BEGIN(newTable); + { + count_t newTableSize = (count_t)newTable->GetNumComponents(); + count_t oldTableSize = m_gcHeapHash->GetCapacity(); + + // Move all entries over to the new table + count_t capacity = m_gcHeapHash->GetCapacity(); + + for (count_t index = 0; index < capacity; ++index) + { + THashArrayType arr((THashArrayType)(m_gcHeapHash)->GetData()); + if (!TRAITS::IsNull(arr, index) && !TRAITS::IsDeleted(arr, index, m_gcHeapHash)) + { + count_t hash = CallHash(arr, index); + count_t tableSize = (count_t)newTable->GetNumComponents(); + count_t newIndex = hash % tableSize; + count_t increment = 0; // delay computation + + // Value to copy is in index + while (TRUE) + { + if (TRAITS::IsNull(newTable, newIndex)) + { + arr = (THashArrayType)(m_gcHeapHash)->GetData(); + TRAITS::CopyValue(arr, index, newTable, newIndex); + break; + } + + if (increment == 0) + increment = (hash % (tableSize-1)) + 1; + + newIndex += increment; + if (newIndex >= tableSize) + newIndex -= tableSize; + } + } + } + + m_gcHeapHash->SetTable((BASEARRAYREF)newTable); + + // We've just copied the table to a new table. There are no deleted items as + // we skipped them all, so reset the deleted count to zero + m_gcHeapHash->SetDeletedCountToZero(); + } + GCPROTECT_END(); +} + +template +template +bool GCHeapHash::VisitAllEntryIndices(TVisitor &visitor) +{ + WRAPPER_NO_CONTRACT; + + count_t capacity = m_gcHeapHash->GetCapacity(); + + for (count_t index = 0; index < capacity; ++index) + { + THashArrayType arr((THashArrayType)(m_gcHeapHash)->GetData()); + if (!TRAITS::IsNull(arr, index) && !TRAITS::IsDeleted(arr, index, m_gcHeapHash)) + { + if (!visitor(index)) + return false; + } + } + + return true; +} + +template +template +void GCHeapHash::Add(TKey *pKey, const TValueSetter &valueSetter) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + CheckGrowth(); + Insert(pKey, valueSetter); +} + + // Get the index in the hashtable of the value which matches key, or -1 if there are no matches +template +template +INT32 GCHeapHash::GetValueIndex(TKey *pKey) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + count_t hash = CallHash(pKey); + count_t tableSize = m_gcHeapHash->GetCapacity(); + + // If the table is empty, then there aren't any entries. Just return. + if (m_gcHeapHash->GetCount() == 0) + return -1; + + count_t index = hash % tableSize; + count_t increment = 0; // delay computation + + THashArrayType arr((THashArrayType)(m_gcHeapHash)->GetData()); + + while (m_gcHeapHash->GetCount() != 0) /* the TRAITS::IsDeleted function is allowed to reduce the count */ + { + if (TRAITS::IsNull(arr, index)) + { + return -1; + } + + if (!TRAITS::IsDeleted(arr, index, m_gcHeapHash) && TRAITS::DoesEntryMatchKey(arr, index, pKey)) + { + return index; + } + + if (increment == 0) + increment = (hash % (tableSize-1)) + 1; + + index += increment; + if (index >= tableSize) + index -= tableSize; + } + + return -1; +} + +template +template +void GCHeapHash::GetElement(INT32 index, TElement& foundElement) +{ + WRAPPER_NO_CONTRACT; + + TRAITS::GetElement(&m_gcHeapHash, index, foundElement); +} + + // Use this to update an value within the hashtable directly. + // It is ONLY safe to do if the index already points at an element + // which already exists and has the same key as the newElementValue +template +template +void GCHeapHash::SetElement(INT32 index, TElement& newElementValue) +{ + TRAITS::SetElement(&m_gcHeapHash, index, newElementValue); +} + +template +template +void GCHeapHash::DeleteEntry(TKey *pKey) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + INT32 index = GetValueIndex(pKey); + if (index != -1) + { + TRAITS::DeleteEntry(&m_gcHeapHash, index); + m_gcHeapHash->DecrementCount(true); + } +} + +#endif // GCHEAPHASHTABLE_INL +#endif // GCHEAPHASHTABLE_H diff --git a/src/vm/loaderallocator.cpp b/src/vm/loaderallocator.cpp index a54b0932b5..ae57e1f692 100644 --- a/src/vm/loaderallocator.cpp +++ b/src/vm/loaderallocator.cpp @@ -94,9 +94,6 @@ LoaderAllocator::~LoaderAllocator() #if !defined(DACCESS_COMPILE) && !defined(CROSSGEN_COMPILE) Terminate(); - // This info is cleaned up before the virtual call stub manager is uninitialized - _ASSERTE(!GetMethodDescBackpatchInfoTracker()->HasDependencyMethodDescEntryPointSlots()); - // Assert that VSD is not still active when the destructor is called. _ASSERTE(m_pVirtualCallStubManager == NULL); @@ -595,11 +592,6 @@ void LoaderAllocator::GCLoaderAllocators(LoaderAllocator* pOriginalLoaderAllocat pDomainLoaderAllocatorDestroyIterator->ReleaseManagedAssemblyLoadContext(); - // Recorded entry point slots may point into the virtual call stub manager's heaps, so clear it first - pDomainLoaderAllocatorDestroyIterator - ->GetMethodDescBackpatchInfoTracker() - ->ClearDependencyMethodDescEntryPointSlots(pDomainLoaderAllocatorDestroyIterator); - // The following code was previously happening on delete ~DomainAssembly->Terminate // We are moving this part here in order to make sure that we can unload a LoaderAllocator // that didn't have a DomainAssembly @@ -1080,6 +1072,10 @@ void LoaderAllocator::Init(BaseDomain *pDomain, BYTE *pExecutableHeapMemory) m_ComCallWrapperCrst.Init(CrstCOMCallWrapper); #endif +#ifndef CROSSGEN_COMPILE + m_methodDescBackpatchInfoTracker.Initialize(this); +#endif + // // Initialize the heaps // diff --git a/src/vm/loaderallocator.hpp b/src/vm/loaderallocator.hpp index dc63ab9ad9..100c0d7e27 100644 --- a/src/vm/loaderallocator.hpp +++ b/src/vm/loaderallocator.hpp @@ -22,6 +22,7 @@ class FuncPtrStubs; #include "callcounter.h" #include "methoddescbackpatchinfo.h" +#include "crossloaderallocatorhash.h" #define VPTRU_LoaderAllocator 0x3200 @@ -32,6 +33,8 @@ enum LoaderAllocatorType LAT_Assembly }; +typedef SHash> LoaderAllocatorSet; + class CLRPrivBinderAssemblyLoadContext; // Iterator over a DomainAssembly in the same ALC diff --git a/src/vm/method.cpp b/src/vm/method.cpp index 69849f3a9f..70d79ca515 100644 --- a/src/vm/method.cpp +++ b/src/vm/method.cpp @@ -4864,25 +4864,8 @@ void MethodDesc::RecordAndBackpatchEntryPointSlot_Locked( // current, entry point until another entry point change, which may never happen. _ASSERTE(currentEntryPoint == GetEntryPointToBackpatch_Locked()); - MethodDescBackpatchInfo *backpatchInfo = - mdLoaderAllocator->GetMethodDescBackpatchInfoTracker()->GetOrAddBackpatchInfo_Locked(this); - if (slotLoaderAllocator == mdLoaderAllocator) - { - // Entry point slots to backpatch are recorded in the backpatch info - backpatchInfo->GetSlots()->AddSlot_Locked(slot, slotType); - } - else - { - // Register the slot's loader allocator with the MethodDesc's backpatch info. Entry point slots to backpatch are - // recorded in the slot's LoaderAllocator. - backpatchInfo->AddDependentLoaderAllocator_Locked(slotLoaderAllocator); - slotLoaderAllocator - ->GetMethodDescBackpatchInfoTracker() - ->GetOrAddDependencyMethodDescEntryPointSlots_Locked(this) - ->AddSlot_Locked(slot, slotType); - } - - EntryPointSlots::Backpatch_Locked(slot, slotType, currentEntryPoint); + MethodDescBackpatchInfoTracker *backpatchTracker = mdLoaderAllocator->GetMethodDescBackpatchInfoTracker(); + backpatchTracker->AddSlotAndPatch_Locked(this, slotLoaderAllocator, slot, slotType, currentEntryPoint); } void MethodDesc::BackpatchEntryPointSlots(PCODE entryPoint, bool isPrestubEntryPoint) @@ -4891,10 +4874,10 @@ void MethodDesc::BackpatchEntryPointSlots(PCODE entryPoint, bool isPrestubEntryP _ASSERTE(entryPoint != NULL); _ASSERTE(MayHaveEntryPointSlotsToBackpatch()); _ASSERTE(isPrestubEntryPoint == (entryPoint == GetPrestubEntryPointToBackpatch())); + _ASSERTE(MethodDescBackpatchInfoTracker::IsLockedByCurrentThread()); LoaderAllocator *mdLoaderAllocator = GetLoaderAllocator(); MethodDescBackpatchInfoTracker *backpatchInfoTracker = mdLoaderAllocator->GetMethodDescBackpatchInfoTracker(); - MethodDescBackpatchInfoTracker::ConditionalLockHolder lockHolder; // Get the entry point to backpatch inside the lock to synchronize with backpatching in MethodDesc::DoBackpatch() if (GetEntryPointToBackpatch_Locked() == entryPoint) @@ -4923,29 +4906,7 @@ void MethodDesc::BackpatchEntryPointSlots(PCODE entryPoint, bool isPrestubEntryP } } - MethodDescBackpatchInfo *backpatchInfo = backpatchInfoTracker->GetBackpatchInfo_Locked(this); - if (backpatchInfo != nullptr) - { - // Backpatch slots from the same loader allocator - backpatchInfo->GetSlots()->Backpatch_Locked(entryPoint); - - // Backpatch slots from dependent loader allocators - backpatchInfo->ForEachDependentLoaderAllocator_Locked( - [&](LoaderAllocator *slotLoaderAllocator) // the loader allocator from which the slot's memory is allocated - { - _ASSERTE(slotLoaderAllocator != nullptr); - _ASSERTE(slotLoaderAllocator != mdLoaderAllocator); - - EntryPointSlots *slotsToBackpatch = - slotLoaderAllocator - ->GetMethodDescBackpatchInfoTracker() - ->GetDependencyMethodDescEntryPointSlots_Locked(this); - if (slotsToBackpatch != nullptr) - { - slotsToBackpatch->Backpatch_Locked(entryPoint); - } - }); - } + backpatchInfoTracker->Backpatch_Locked(this, entryPoint); // Set the entry point to backpatch inside the lock to synchronize with backpatching in MethodDesc::DoBackpatch(), and set // it last in case there are exceptions above, as setting the entry point indicates that all recorded slots have been diff --git a/src/vm/methoddescbackpatchinfo.cpp b/src/vm/methoddescbackpatchinfo.cpp index 386786c6ba..571007cdfc 100644 --- a/src/vm/methoddescbackpatchinfo.cpp +++ b/src/vm/methoddescbackpatchinfo.cpp @@ -17,24 +17,6 @@ #ifndef DACCESS_COMPILE -void EntryPointSlots::Backpatch_Locked(PCODE entryPoint) -{ - WRAPPER_NO_CONTRACT; - static_assert_no_msg(SlotType_Count <= sizeof(INT32)); - _ASSERTE(MethodDescBackpatchInfoTracker::IsLockedByCurrentThread()); - _ASSERTE(entryPoint != NULL); - - TADDR *slots = m_slots.GetElements(); - COUNT_T slotCount = m_slots.GetCount(); - for (COUNT_T i = 0; i < slotCount; ++i) - { - TADDR slot = slots[i]; - SlotType slotType = (SlotType)(slot & SlotType_Mask); - slot ^= slotType; - Backpatch_Locked(slot, slotType, entryPoint); - } -} - void EntryPointSlots::Backpatch_Locked(TADDR slot, SlotType slotType, PCODE entryPoint) { WRAPPER_NO_CONTRACT; @@ -81,55 +63,50 @@ void EntryPointSlots::Backpatch_Locked(TADDR slot, SlotType slotType, PCODE entr #endif // !DACCESS_COMPILE //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// MethodDescBackpatchInfo +// MethodDescBackpatchInfoTracker + +CrstStatic MethodDescBackpatchInfoTracker::s_lock; #ifndef DACCESS_COMPILE -void MethodDescBackpatchInfo::AddDependentLoaderAllocator_Locked(LoaderAllocator *dependentLoaderAllocator) +void MethodDescBackpatchInfoTracker::Backpatch_Locked(MethodDesc *pMethodDesc, PCODE entryPoint) { WRAPPER_NO_CONTRACT; - _ASSERTE(MethodDescBackpatchInfoTracker::IsLockedByCurrentThread()); - _ASSERTE(m_methodDesc != nullptr); - _ASSERTE(dependentLoaderAllocator != nullptr); - _ASSERTE(dependentLoaderAllocator != m_methodDesc->GetLoaderAllocator()); + _ASSERTE(IsLockedByCurrentThread()); + _ASSERTE(pMethodDesc != nullptr); - LoaderAllocatorSet *set = m_dependentLoaderAllocators; - if (set != nullptr) + GCX_COOP(); + + auto lambda = [&entryPoint](OBJECTREF obj, MethodDesc *pMethodDesc, UINT_PTR slotData) { - if (set->Lookup(dependentLoaderAllocator) != nullptr) - { - return; - } - set->Add(dependentLoaderAllocator); - return; - } - NewHolder setHolder = new LoaderAllocatorSet(); - setHolder->Add(dependentLoaderAllocator); - m_dependentLoaderAllocators = setHolder.Extract(); -} + TADDR slot; + EntryPointSlots::SlotType slotType; -void MethodDescBackpatchInfo::RemoveDependentLoaderAllocator_Locked(LoaderAllocator *dependentLoaderAllocator) -{ - WRAPPER_NO_CONTRACT; - _ASSERTE(MethodDescBackpatchInfoTracker::IsLockedByCurrentThread()); - _ASSERTE(m_methodDesc != nullptr); - _ASSERTE(dependentLoaderAllocator != nullptr); - _ASSERTE(dependentLoaderAllocator != m_methodDesc->GetLoaderAllocator()); - _ASSERTE(m_dependentLoaderAllocators != nullptr); - _ASSERTE(m_dependentLoaderAllocators->Lookup(dependentLoaderAllocator) == dependentLoaderAllocator); + EntryPointSlots::ConvertUINT_PTRToSlotAndTypePair(slotData, &slot, &slotType); + EntryPointSlots::Backpatch_Locked(slot, slotType, entryPoint); - m_dependentLoaderAllocators->Remove(dependentLoaderAllocator); + return true; // Keep walking + }; + + m_backpatchInfoHash.VisitValuesOfKey(pMethodDesc, lambda); } -#endif // !DACCESS_COMPILE +void MethodDescBackpatchInfoTracker::AddSlotAndPatch_Locked(MethodDesc *pMethodDesc, LoaderAllocator *pLoaderAllocatorOfSlot, TADDR slot, EntryPointSlots::SlotType slotType, PCODE currentEntryPoint) +{ + WRAPPER_NO_CONTRACT; + _ASSERTE(IsLockedByCurrentThread()); + _ASSERTE(pMethodDesc != nullptr); + _ASSERTE(pMethodDesc->MayHaveEntryPointSlotsToBackpatch()); -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// MethodDescBackpatchInfoTracker + GCX_COOP(); -CrstStatic MethodDescBackpatchInfoTracker::s_lock; + UINT_PTR slotData; + slotData = EntryPointSlots::ConvertSlotAndTypePairToUINT_PTR(slot, slotType); -#ifndef DACCESS_COMPILE + m_backpatchInfoHash.Add(pMethodDesc, slotData, pLoaderAllocatorOfSlot); + EntryPointSlots::Backpatch_Locked(slot, slotType, currentEntryPoint); +} void MethodDescBackpatchInfoTracker::StaticInitialize() { @@ -163,76 +140,4 @@ bool MethodDescBackpatchInfoTracker::MayHaveEntryPointSlotsToBackpatch(PTR_Metho #endif // _DEBUG -#ifndef DACCESS_COMPILE - -MethodDescBackpatchInfo *MethodDescBackpatchInfoTracker::AddBackpatchInfo_Locked(MethodDesc *methodDesc) -{ - WRAPPER_NO_CONTRACT; - _ASSERTE(IsLockedByCurrentThread()); - _ASSERTE(methodDesc != nullptr); - _ASSERTE(methodDesc->MayHaveEntryPointSlotsToBackpatch()); - _ASSERTE(m_backpatchInfoHash.Lookup(methodDesc) == nullptr); - - NewHolder backpatchInfoHolder = new MethodDescBackpatchInfo(methodDesc); - m_backpatchInfoHash.Add(backpatchInfoHolder); - return backpatchInfoHolder.Extract(); -} - -EntryPointSlots *MethodDescBackpatchInfoTracker::GetDependencyMethodDescEntryPointSlots_Locked(MethodDesc *methodDesc) -{ - WRAPPER_NO_CONTRACT; - _ASSERTE(IsLockedByCurrentThread()); - _ASSERTE(methodDesc != nullptr); - _ASSERTE(methodDesc->MayHaveEntryPointSlotsToBackpatch()); - - MethodDescEntryPointSlots *methodDescSlots = - m_dependencyMethodDescEntryPointSlotsHash.Lookup(methodDesc); - return methodDescSlots == nullptr ? nullptr : methodDescSlots->GetSlots(); -} - -EntryPointSlots *MethodDescBackpatchInfoTracker::GetOrAddDependencyMethodDescEntryPointSlots_Locked(MethodDesc *methodDesc) -{ - WRAPPER_NO_CONTRACT; - _ASSERTE(IsLockedByCurrentThread()); - _ASSERTE(methodDesc != nullptr); - _ASSERTE(methodDesc->MayHaveEntryPointSlotsToBackpatch()); - - MethodDescEntryPointSlots *methodDescSlots = m_dependencyMethodDescEntryPointSlotsHash.Lookup(methodDesc); - if (methodDescSlots != nullptr) - { - return methodDescSlots->GetSlots(); - } - - NewHolder methodDescSlotsHolder = new MethodDescEntryPointSlots(methodDesc); - m_dependencyMethodDescEntryPointSlotsHash.Add(methodDescSlotsHolder); - return methodDescSlotsHolder.Extract()->GetSlots(); -} - -void MethodDescBackpatchInfoTracker::ClearDependencyMethodDescEntryPointSlots(LoaderAllocator *loaderAllocator) -{ - WRAPPER_NO_CONTRACT; - _ASSERTE(loaderAllocator != nullptr); - _ASSERTE(loaderAllocator->GetMethodDescBackpatchInfoTracker() == this); - - ConditionalLockHolder lockHolder; - - for (MethodDescEntryPointSlotsHash::Iterator - it = m_dependencyMethodDescEntryPointSlotsHash.Begin(), - itEnd = m_dependencyMethodDescEntryPointSlotsHash.End(); - it != itEnd; - ++it) - { - MethodDesc *methodDesc = (*it)->GetMethodDesc(); - MethodDescBackpatchInfo *backpatchInfo = methodDesc->GetBackpatchInfoTracker()->GetBackpatchInfo_Locked(methodDesc); - if (backpatchInfo != nullptr) - { - backpatchInfo->RemoveDependentLoaderAllocator_Locked(loaderAllocator); - } - } - - m_dependencyMethodDescEntryPointSlotsHash.RemoveAll(); -} - -#endif // DACCESS_COMPILE - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/vm/methoddescbackpatchinfo.h b/src/vm/methoddescbackpatchinfo.h index 3aa2b13255..c5d92a29da 100644 --- a/src/vm/methoddescbackpatchinfo.h +++ b/src/vm/methoddescbackpatchinfo.h @@ -5,29 +5,7 @@ #pragma once #include "debugmacrosext.h" - -// MethodDescBackpatchInfoTracker: -// - Root container for all other types in this file -// - There is one instance per LoaderAllocator -// - Contains a collection of MethodDescBackpatchInfo objects -// - Contains a collection of MethodDescEntryPointSlots objects -// -// MethodDescBackpatchInfo: -// - Container for backpatch information for a MethodDesc allocated in the same LoaderAllocator -// - Contains an EntryPointSlots collection that contains slots allocated in the same LoaderAllocator. These are slots -// recorded for backpatching when the MethodDesc's code entry point changes. -// - Contains a LoaderAllocatorSet collection that contains dependent LoaderAllocators that in turn have slots recorded for -// backpatching when the MethodDesc's entry point changes. These are slots associated with the MethodDesc but allocated and -// recorded in a LoaderAllocator on the MethodDesc's LoaderAllocator. -// -// EntryPointSlots and MethodDescEntryPointSlots -// - Collection of slots recorded for backpatching -// - There is one instance per MethodDescBackpatchInfo for slots allocated in the MethodDesc's LoaderAllocator -// - There is one instance per MethodDesc in MethodDescBackpatchInfoTracker, for slots allocated in LoaderAllocators that are -// dependent on the MethodDesc's LoaderAllocator. The dependent LoaderAllocators are also recorded in the -// MethodDescBackPatchInfo associated with the MethodDesc's LoaderAllocator. - -typedef SHash> LoaderAllocatorSet; +#include "crossloaderallocatorhash.h" #ifndef CROSSGEN_COMPILE @@ -38,7 +16,6 @@ typedef SHash> LoaderAllocatorSet; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // EntryPointSlots -// See comment at the top of methoddescbackpatchinfo.h for a description of this and related data structures class EntryPointSlots { public: @@ -53,18 +30,6 @@ public: SlotType_Mask = SlotType_Vtable | SlotType_Executable | SlotType_ExecutableRel32 }; -private: - typedef SArray SlotArray; - -private: - SlotArray m_slots; - -public: - EntryPointSlots() - { - LIMITED_METHOD_CONTRACT; - } - #ifndef DACCESS_COMPILE private: static SIZE_T GetRequiredSlotAlignment(SlotType slotType) @@ -77,205 +42,52 @@ private: } public: - void AddSlot_Locked(TADDR slot, SlotType slotType); - void Backpatch_Locked(PCODE entryPoint); - static void Backpatch_Locked(TADDR slot, SlotType slotType, PCODE entryPoint); -#endif - - DISABLE_COPY(EntryPointSlots); -}; - -// See comment at the top of methoddescbackpatchinfo.h for a description of this and related data structures -class MethodDescEntryPointSlots -{ -private: - MethodDesc *m_methodDesc; - - // This field and its data is protected by MethodDescBackpatchInfoTracker's lock - EntryPointSlots m_slots; - -public: - MethodDescEntryPointSlots(MethodDesc *methodDesc) : m_methodDesc(methodDesc) - { - LIMITED_METHOD_CONTRACT; - _ASSERTE(methodDesc != nullptr); - } - -public: - MethodDesc *GetMethodDesc() const - { - LIMITED_METHOD_CONTRACT; - return m_methodDesc; - } - -#ifndef DACCESS_COMPILE - EntryPointSlots *GetSlots() - { - WRAPPER_NO_CONTRACT; - _ASSERTE(m_methodDesc != nullptr); - - return &m_slots; - } -#endif - - DISABLE_COPY(MethodDescEntryPointSlots); -}; - -class MethodDescEntryPointSlotsHashTraits - : public DeleteElementsOnDestructSHashTraits>> -{ -public: - typedef DeleteElementsOnDestructSHashTraits>> Base; - typedef Base::element_t element_t; - typedef Base::count_t count_t; - - typedef MethodDesc *key_t; - - static key_t GetKey(element_t e) - { - LIMITED_METHOD_CONTRACT; - return e->GetMethodDesc(); - } - - static BOOL Equals(key_t k1, key_t k2) - { - LIMITED_METHOD_CONTRACT; - return k1 == k2; - } - - static count_t Hash(key_t k) - { - LIMITED_METHOD_CONTRACT; - return (count_t)((size_t)dac_cast(k) >> 2); - } - - static const element_t Null() { LIMITED_METHOD_CONTRACT; return nullptr; } - static bool IsNull(const element_t &e) { LIMITED_METHOD_CONTRACT; return e == nullptr; } -}; - -typedef SHash MethodDescEntryPointSlotsHash; - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// MethodDescBackpatchInfo - -// See comment at the top of methoddescbackpatchinfo.h for a description of this and related data structures -class MethodDescBackpatchInfo -{ -private: - MethodDesc *m_methodDesc; - - // Entry point slots that need to be backpatched when the method's entry point changes. This may include vtable slots, slots - // from virtual stub dispatch for interface methods (slots from dispatch stubs and resolve cache entries), etc. This - // collection only contains slots allocated in this MethodDesc's LoaderAllocator. This field and its data is protected by - // MethodDescBackpatchInfoTracker's lock. - EntryPointSlots m_slots; - - // A set of LoaderAllocators from which slots that were allocated, are associated with the dependency MethodDesc and have - // been recorded for backpatching. For example, a derived type in a shorter-lifetime LoaderAllocator that inherits a - // MethodDesc from a longer-lifetime base type, would have its slot recorded in the slot's LoaderAllocator, and that - // LoaderAllocator would be recorded here in the MethodDesc's LoaderAllocator. This field and its data is protected by - // MethodDescBackpatchInfoTracker's lock. - LoaderAllocatorSet *m_dependentLoaderAllocators; - -public: - MethodDescBackpatchInfo(MethodDesc *methodDesc = nullptr); - -#ifndef DACCESS_COMPILE -public: - ~MethodDescBackpatchInfo() + static UINT_PTR ConvertSlotAndTypePairToUINT_PTR(TADDR slot, SlotType slotType) { - LIMITED_METHOD_CONTRACT; - - LoaderAllocatorSet *set = m_dependentLoaderAllocators; - if (set != nullptr) - { - delete set; - } + slot |= (TADDR)slotType; + return (UINT_PTR)slot; } -#endif -public: - MethodDesc *GetMethodDesc() const + static void ConvertUINT_PTRToSlotAndTypePair(UINT_PTR storedData, TADDR *pSlot, SlotType *pSlotType) { - LIMITED_METHOD_CONTRACT; - return m_methodDesc; + *pSlot = storedData; + *pSlotType = (SlotType)(*pSlot & SlotType_Mask); + *pSlot ^= *pSlotType; } -#ifndef DACCESS_COMPILE -public: - EntryPointSlots *GetSlots() - { - WRAPPER_NO_CONTRACT; - _ASSERTE(m_methodDesc != nullptr); - - return &m_slots; - } - -public: - template void ForEachDependentLoaderAllocator_Locked(Visit visit); - void AddDependentLoaderAllocator_Locked(LoaderAllocator *dependentLoaderAllocator); - void RemoveDependentLoaderAllocator_Locked(LoaderAllocator *dependentLoaderAllocator); + static void Backpatch_Locked(TADDR slot, SlotType slotType, PCODE entryPoint); #endif - - DISABLE_COPY(MethodDescBackpatchInfo); }; -class MethodDescBackpatchInfoHashTraits - : public DeleteElementsOnDestructSHashTraits>> -{ -public: - typedef DeleteElementsOnDestructSHashTraits>> Base; - typedef Base::element_t element_t; - typedef Base::count_t count_t; - - typedef MethodDesc *key_t; - - static key_t GetKey(element_t e) - { - LIMITED_METHOD_CONTRACT; - return e->GetMethodDesc(); - } - - static BOOL Equals(key_t k1, key_t k2) - { - LIMITED_METHOD_CONTRACT; - return k1 == k2; - } - - static count_t Hash(key_t k) - { - LIMITED_METHOD_CONTRACT; - return (count_t)((size_t)dac_cast(k) >> 2); - } - - static const element_t Null() { LIMITED_METHOD_CONTRACT; return nullptr; } - static bool IsNull(const element_t &e) { LIMITED_METHOD_CONTRACT; return e == nullptr; } -}; - -typedef SHash MethodDescBackpatchInfoHash; - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // MethodDescBackpatchInfoTracker -// See comment at the top of methoddescbackpatchinfo.h for a description of this and related data structures class MethodDescBackpatchInfoTracker { private: static CrstStatic s_lock; + class BackpatchInfoTrackerHashTraits : public NoRemoveDefaultCrossLoaderAllocatorHashTraits + { + }; + + typedef CrossLoaderAllocatorHash BackpatchInfoTrackerHash; + // Contains information about slots associated with the MethodDesc that were recorded for backpatching. This field and its // data is protected by s_lock. - MethodDescBackpatchInfoHash m_backpatchInfoHash; - - // Contains slots associated with a MethodDesc from a dependency LoaderAllocator, which are recorded for backpatching when - // the MethodDesc's entry point changes. This field and its data is protected by s_lock. - MethodDescEntryPointSlotsHash m_dependencyMethodDescEntryPointSlotsHash; + BackpatchInfoTrackerHash m_backpatchInfoHash; #ifndef DACCESS_COMPILE public: static void StaticInitialize(); #endif + void Initialize(LoaderAllocator *pLoaderAllocator) + { + WRAPPER_NO_CONTRACT; + m_backpatchInfoHash.Init(pLoaderAllocator); + } + #ifdef _DEBUG public: static bool IsLockedByCurrentThread(); @@ -311,44 +123,9 @@ public: #ifndef DACCESS_COMPILE public: - MethodDescBackpatchInfo *GetBackpatchInfo_Locked(MethodDesc *methodDesc) const - { - WRAPPER_NO_CONTRACT; - _ASSERTE(IsLockedByCurrentThread()); - _ASSERTE(methodDesc != nullptr); - _ASSERTE(MayHaveEntryPointSlotsToBackpatch(methodDesc)); - - return m_backpatchInfoHash.Lookup(methodDesc); - } - - MethodDescBackpatchInfo *GetOrAddBackpatchInfo_Locked(MethodDesc *methodDesc) - { - WRAPPER_NO_CONTRACT; - _ASSERTE(IsLockedByCurrentThread()); - _ASSERTE(methodDesc != nullptr); - _ASSERTE(MayHaveEntryPointSlotsToBackpatch(methodDesc)); - - MethodDescBackpatchInfo *backpatchInfo = m_backpatchInfoHash.Lookup(methodDesc); - if (backpatchInfo != nullptr) - { - return backpatchInfo; - } - return AddBackpatchInfo_Locked(methodDesc); - } - -private: - MethodDescBackpatchInfo *AddBackpatchInfo_Locked(MethodDesc *methodDesc); - + void Backpatch_Locked(MethodDesc *pMethodDesc, PCODE entryPoint); + void AddSlotAndPatch_Locked(MethodDesc *pMethodDesc, LoaderAllocator *pLoaderAllocatorOfSlot, TADDR slot, EntryPointSlots::SlotType slotType, PCODE currentEntryPoint); public: - bool HasDependencyMethodDescEntryPointSlots() const - { - WRAPPER_NO_CONTRACT; - return m_dependencyMethodDescEntryPointSlotsHash.GetCount() != 0; - } - - EntryPointSlots *GetDependencyMethodDescEntryPointSlots_Locked(MethodDesc *methodDesc); - EntryPointSlots *GetOrAddDependencyMethodDescEntryPointSlots_Locked(MethodDesc *methodDesc); - void ClearDependencyMethodDescEntryPointSlots(LoaderAllocator *loaderAllocator); #endif friend class ConditionalLockHolder; @@ -356,61 +133,6 @@ public: DISABLE_COPY(MethodDescBackpatchInfoTracker); }; -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Inline and template definitions - -#ifndef DACCESS_COMPILE - -inline void EntryPointSlots::AddSlot_Locked(TADDR slot, SlotType slotType) -{ - WRAPPER_NO_CONTRACT; - static_assert_no_msg(SlotType_Count <= sizeof(INT32)); - _ASSERTE(MethodDescBackpatchInfoTracker::IsLockedByCurrentThread()); - _ASSERTE(slot != NULL); - _ASSERTE(!(slot & SlotType_Mask)); - _ASSERTE(slotType >= SlotType_Normal); - _ASSERTE(slotType < SlotType_Count); - _ASSERTE(IS_ALIGNED((SIZE_T)slot, GetRequiredSlotAlignment(slotType))); - - m_slots.Append(slot | slotType); -} - -#endif // DACCESS_COMPILE - -inline MethodDescBackpatchInfo::MethodDescBackpatchInfo(MethodDesc *methodDesc) - : m_methodDesc(methodDesc), m_dependentLoaderAllocators(nullptr) -{ - LIMITED_METHOD_CONTRACT; - _ASSERTE( - methodDesc == nullptr || - MethodDescBackpatchInfoTracker::MayHaveEntryPointSlotsToBackpatch(PTR_MethodDesc(methodDesc))); -} - -#ifndef DACCESS_COMPILE - -template -inline void MethodDescBackpatchInfo::ForEachDependentLoaderAllocator_Locked(Visit visit) -{ - WRAPPER_NO_CONTRACT; - _ASSERTE(MethodDescBackpatchInfoTracker::IsLockedByCurrentThread()); - _ASSERTE(m_methodDesc != nullptr); - - LoaderAllocatorSet *set = m_dependentLoaderAllocators; - if (set == nullptr) - { - return; - } - - for (LoaderAllocatorSet::Iterator it = set->Begin(), itEnd = set->End(); it != itEnd; ++it) - { - visit(*it); - } -} - -#endif // DACCESS_COMPILE - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - #undef DISABLE_COPY #endif // !CROSSGEN_COMPILE diff --git a/src/vm/mscorlib.h b/src/vm/mscorlib.h index 3298de3eee..52e4508446 100644 --- a/src/vm/mscorlib.h +++ b/src/vm/mscorlib.h @@ -1393,6 +1393,25 @@ DEFINE_CLASS(OBJECT_EQUALITYCOMPARER, CollectionsGeneric, ObjectEqualityComparer DEFINE_CLASS(INATTRIBUTE, Interop, InAttribute) +DEFINE_CLASS_U(CompilerServices, GCHeapHash, GCHeapHashObject) +DEFINE_FIELD_U(_data, GCHeapHashObject, _data) +DEFINE_FIELD_U(_count, GCHeapHashObject, _count) +DEFINE_FIELD_U(_deletedCount, GCHeapHashObject, _deletedCount) + +DEFINE_CLASS(GCHEAPHASH, CompilerServices, GCHeapHash) + +DEFINE_CLASS_U(CompilerServices, LAHashDependentHashTracker, LAHashDependentHashTrackerObject) +DEFINE_FIELD_U(_dependentHandle, LAHashDependentHashTrackerObject,_dependentHandle) +DEFINE_FIELD_U(_loaderAllocator, LAHashDependentHashTrackerObject,_loaderAllocator) + +DEFINE_CLASS(LAHASHDEPENDENTHASHTRACKER, CompilerServices, LAHashDependentHashTracker) + +DEFINE_CLASS_U(CompilerServices, LAHashKeyToTrackers, LAHashKeyToTrackersObject) +DEFINE_FIELD_U(_trackerOrTrackerSet, LAHashKeyToTrackersObject, _trackerOrTrackerSet) +DEFINE_FIELD_U(_laLocalKeyValueStore, LAHashKeyToTrackersObject, _laLocalKeyValueStore) + +DEFINE_CLASS(LAHASHKEYTOTRACKERS, CompilerServices, LAHashKeyToTrackers) + #undef DEFINE_CLASS #undef DEFINE_METHOD #undef DEFINE_FIELD diff --git a/src/vm/object.cpp b/src/vm/object.cpp index fb1294d5f5..2949f79066 100644 --- a/src/vm/object.cpp +++ b/src/vm/object.cpp @@ -2220,3 +2220,20 @@ void ExceptionObject::GetStackTrace(StackTraceArray & stackTrace, PTRARRAYREF * #endif // !defined(DACCESS_COMPILE) } + +bool LAHashDependentHashTrackerObject::IsLoaderAllocatorLive() +{ + return (ObjectFromHandle(_dependentHandle) != NULL); +} + +void LAHashDependentHashTrackerObject::GetDependentAndLoaderAllocator(OBJECTREF *pLoaderAllocatorRef, GCHEAPHASHOBJECTREF *pGCHeapHash) +{ + OBJECTREF primary = ObjectFromHandle(_dependentHandle); + if (pLoaderAllocatorRef != NULL) + *pLoaderAllocatorRef = primary; + + IGCHandleManager *mgr = GCHandleUtilities::GetGCHandleManager(); + // Secondary is tracked only if primary is non-null + if (pGCHeapHash != NULL) + *pGCHeapHash = (GCHEAPHASHOBJECTREF)(OBJECTREF)((primary != NULL) ? mgr->GetDependentHandleSecondary(_dependentHandle) : NULL); +} diff --git a/src/vm/object.h b/src/vm/object.h index 4f5a0b81df..6bc3a74471 100644 --- a/src/vm/object.h +++ b/src/vm/object.h @@ -851,6 +851,7 @@ typedef Array U2Array; typedef Array CHARArray; typedef Array U4Array; typedef Array U8Array; +typedef Array UPTRArray; typedef PtrArray PTRArray; typedef DPTR(I1Array) PTR_I1Array; @@ -865,6 +866,7 @@ typedef DPTR(U2Array) PTR_U2Array; typedef DPTR(CHARArray) PTR_CHARArray; typedef DPTR(U4Array) PTR_U4Array; typedef DPTR(U8Array) PTR_U8Array; +typedef DPTR(UPTRArray) PTR_UPTRArray; typedef DPTR(PTRArray) PTR_PTRArray; class StringObject; @@ -882,6 +884,7 @@ typedef REF BOOLARRAYREF; typedef REF U2ARRAYREF; typedef REF U4ARRAYREF; typedef REF U8ARRAYREF; +typedef REF UPTRARRAYREF; typedef REF CHARARRAYREF; typedef REF PTRARRAYREF; // Warning: Use PtrArray only for single dimensional arrays, not multidim arrays. typedef REF STRINGREF; @@ -900,6 +903,7 @@ typedef PTR_BOOLArray BOOLARRAYREF; typedef PTR_U2Array U2ARRAYREF; typedef PTR_U4Array U4ARRAYREF; typedef PTR_U8Array U8ARRAYREF; +typedef PTR_UPTRArray UPTRARRAYREF; typedef PTR_CHARArray CHARARRAYREF; typedef PTR_PTRArray PTRARRAYREF; // Warning: Use PtrArray only for single dimensional arrays, not multidim arrays. typedef PTR_StringObject STRINGREF; @@ -2796,4 +2800,133 @@ typedef REF EXCEPTIONREF; typedef PTR_ExceptionObject EXCEPTIONREF; #endif // USE_CHECKED_OBJECTREFS +class GCHeapHashObject : public Object +{ +#ifdef DACCESS_COMPILE + friend class ClrDataAccess; +#endif + friend class GCHeap; + friend class JIT_TrialAlloc; + friend class CheckAsmOffsets; + friend class COMString; + friend class MscorlibBinder; + + private: + BASEARRAYREF _data; + INT32 _count; + INT32 _deletedCount; + + public: + INT32 GetCount() { LIMITED_METHOD_CONTRACT; return _count; } + void IncrementCount(bool replacingDeletedItem) + { + LIMITED_METHOD_CONTRACT; + ++_count; + if (replacingDeletedItem) + --_deletedCount; + } + + void DecrementCount(bool deletingItem) + { + LIMITED_METHOD_CONTRACT; + --_count; + if (deletingItem) + ++_deletedCount; + } + INT32 GetDeletedCount() { LIMITED_METHOD_CONTRACT; return _deletedCount; } + void SetDeletedCountToZero() { LIMITED_METHOD_CONTRACT; _deletedCount = 0; } + INT32 GetCapacity() { LIMITED_METHOD_CONTRACT; if (_data == NULL) return 0; else return (_data->GetNumComponents()); } + BASEARRAYREF GetData() { LIMITED_METHOD_CONTRACT; return _data; } + + void SetTable(BASEARRAYREF data) + { + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_MODE_COOPERATIVE; + + SetObjectReference((OBJECTREF*)&_data, (OBJECTREF)data, GetAppDomain()); + } + + protected: + GCHeapHashObject() {LIMITED_METHOD_CONTRACT; } + ~GCHeapHashObject() {LIMITED_METHOD_CONTRACT; } +}; + +typedef DPTR(GCHeapHashObject) PTR_GCHeapHashObject; + +#ifdef USE_CHECKED_OBJECTREFS +typedef REF GCHEAPHASHOBJECTREF; +#else // USE_CHECKED_OBJECTREFS +typedef PTR_GCHeapHashObject GCHEAPHASHOBJECTREF; +#endif // USE_CHECKED_OBJECTREFS + +class LAHashDependentHashTrackerObject : public Object +{ +#ifdef DACCESS_COMPILE + friend class ClrDataAccess; +#endif + friend class CheckAsmOffsets; + friend class MscorlibBinder; + + private: + OBJECTHANDLE _dependentHandle; + LoaderAllocator* _loaderAllocator; + + public: + bool IsLoaderAllocatorLive(); + bool IsTrackerFor(LoaderAllocator *pLoaderAllocator) + { + if (pLoaderAllocator != _loaderAllocator) + return false; + + return IsLoaderAllocatorLive(); + } + + void GetDependentAndLoaderAllocator(OBJECTREF *pLoaderAllocatorRef, GCHEAPHASHOBJECTREF *pGCHeapHash); + + // Be careful with this. This isn't safe to use unless something is keeping the LoaderAllocator live, or there is no intention to dereference this pointer + LoaderAllocator* GetLoaderAllocatorUnsafe() + { + return _loaderAllocator; + } + + void Init(OBJECTHANDLE dependentHandle, LoaderAllocator* loaderAllocator) + { + LIMITED_METHOD_CONTRACT; + _dependentHandle = dependentHandle; + _loaderAllocator = loaderAllocator; + } +}; + +class LAHashKeyToTrackersObject : public Object +{ +#ifdef DACCESS_COMPILE + friend class ClrDataAccess; +#endif + friend class CheckAsmOffsets; + friend class MscorlibBinder; + + public: + // _trackerOrTrackerSet is either a reference to a LAHashDependentHashTracker, or to a GCHeapHash of LAHashDependentHashTracker objects. + OBJECTREF _trackerOrTrackerSet; + // _laLocalKeyValueStore holds an object that represents a Key value (which must always be valid for the lifetime of the + // CrossLoaderAllocatorHeapHash, and the values which must also be valid for that entire lifetime. When a value might + // have a shorter lifetime it is accessed through the _trackerOrTrackerSet variable, which allows access to hashtables which + // are associated with that remote loaderallocator through a dependent handle, so that lifetime can be managed. + OBJECTREF _laLocalKeyValueStore; +}; + +typedef DPTR(LAHashDependentHashTrackerObject) PTR_LAHashDependentHashTrackerObject; +typedef DPTR(LAHashKeyToTrackersObject) PTR_LAHashKeyToTrackersObject; + + +#ifdef USE_CHECKED_OBJECTREFS +typedef REF LAHASHDEPENDENTHASHTRACKERREF; +typedef REF LAHASHKEYTOTRACKERSREF; +#else // USE_CHECKED_OBJECTREFS +typedef PTR_LAHashDependentHashTrackerObject LAHASHDEPENDENTHASHTRACKERREF; +typedef PTR_LAHashKeyToTrackersObject LAHASHKEYTOTRACKERSREF; +#endif // USE_CHECKED_OBJECTREFS + + #endif // _OBJECT_H_ diff --git a/src/vm/prestub.cpp b/src/vm/prestub.cpp index d04a5d0a54..a08a2d1a10 100644 --- a/src/vm/prestub.cpp +++ b/src/vm/prestub.cpp @@ -1964,6 +1964,8 @@ PCODE MethodDesc::DoPrestub(MethodTable *pDispatchingMT) return pCode; } + _ASSERTE(!MayHaveEntryPointSlotsToBackpatch()); // This path doesn't lock the MethodDescBackpatchTracker as it should only + // happen for jump-stampable or non-versionable methods SetCodeEntryPoint(pCode); } else diff --git a/src/vm/rejit.cpp b/src/vm/rejit.cpp index b7527bfe74..d0742840d4 100644 --- a/src/vm/rejit.cpp +++ b/src/vm/rejit.cpp @@ -613,39 +613,44 @@ HRESULT ReJitManager::UpdateActiveILVersions( BOOL fEESuspended = FALSE; SHash::Iterator beginIter = mgrToCodeActivationBatch.Begin(); SHash::Iterator endIter = mgrToCodeActivationBatch.End(); - for (SHash::Iterator iter = beginIter; iter != endIter; iter++) - { - CodeActivationBatch * pCodeActivationBatch = *iter; - CodeVersionManager * pCodeVersionManager = pCodeActivationBatch->m_pCodeVersionManager; - int cMethodsToActivate = pCodeActivationBatch->m_methodsToActivate.Count(); - if (cMethodsToActivate == 0) - { - continue; - } + { + MethodDescBackpatchInfoTracker::ConditionalLockHolder lockHolder; + for (SHash::Iterator iter = beginIter; iter != endIter; iter++) { - // SetActiveILCodeVersions takes the SystemDomain crst, which needs to be acquired before the - // ThreadStore crsts - SystemDomain::LockHolder lh; + CodeActivationBatch * pCodeActivationBatch = *iter; + CodeVersionManager * pCodeVersionManager = pCodeActivationBatch->m_pCodeVersionManager; - if(!fEESuspended) + int cMethodsToActivate = pCodeActivationBatch->m_methodsToActivate.Count(); + if (cMethodsToActivate == 0) { - // As a potential future optimization we could speculatively try to update the jump stamps without - // suspending the runtime. That needs to be plumbed through BatchUpdateJumpStamps though. - ThreadSuspend::SuspendEE(ThreadSuspend::SUSPEND_FOR_REJIT); - fEESuspended = TRUE; + continue; } - _ASSERTE(ThreadStore::HoldingThreadStore()); - hr = pCodeVersionManager->SetActiveILCodeVersions(pCodeActivationBatch->m_methodsToActivate.Ptr(), pCodeActivationBatch->m_methodsToActivate.Count(), fEESuspended, &errorRecords); - if (FAILED(hr)) - break; + { + // SetActiveILCodeVersions takes the SystemDomain crst, which needs to be acquired before the + // ThreadStore crsts + SystemDomain::LockHolder lh; + + if(!fEESuspended) + { + // As a potential future optimization we could speculatively try to update the jump stamps without + // suspending the runtime. That needs to be plumbed through BatchUpdateJumpStamps though. + ThreadSuspend::SuspendEE(ThreadSuspend::SUSPEND_FOR_REJIT); + fEESuspended = TRUE; + } + + _ASSERTE(ThreadStore::HoldingThreadStore()); + hr = pCodeVersionManager->SetActiveILCodeVersions(pCodeActivationBatch->m_methodsToActivate.Ptr(), pCodeActivationBatch->m_methodsToActivate.Count(), fEESuspended, &errorRecords); + if (FAILED(hr)) + break; + } + } + if (fEESuspended) + { + ThreadSuspend::RestartEE(FALSE, TRUE); } - } - if (fEESuspended) - { - ThreadSuspend::RestartEE(FALSE, TRUE); } if (FAILED(hr)) diff --git a/src/vm/tieredcompilation.cpp b/src/vm/tieredcompilation.cpp index dd40d8c3da..f091d0348d 100644 --- a/src/vm/tieredcompilation.cpp +++ b/src/vm/tieredcompilation.cpp @@ -505,6 +505,7 @@ void TieredCompilationManager::ResumeCountingCalls(MethodDesc* pMethodDesc) { WRAPPER_NO_CONTRACT; _ASSERTE(pMethodDesc != nullptr); + MethodDescBackpatchInfoTracker::ConditionalLockHolder lockHolder(pMethodDesc->MayHaveEntryPointSlotsToBackpatch()); EX_TRY { @@ -718,6 +719,8 @@ void TieredCompilationManager::ActivateCodeVersion(NativeCodeVersion nativeCodeV // code version will activate then. ILCodeVersion ilParent; HRESULT hr = S_OK; + MethodDescBackpatchInfoTracker::ConditionalLockHolder lockHolder; + { // As long as we are exclusively using precode publishing for tiered compilation // methods this first attempt should succeed -- cgit v1.2.3