diff --git a/Documentation/botr/shared-generics.md b/Documentation/botr/shared-generics.md deleted file mode 100644 index 1fd37439b57d..000000000000 --- a/Documentation/botr/shared-generics.md +++ /dev/null @@ -1,208 +0,0 @@ -Shared Generics Design -=== - -Author: Fadi Hanna - 2019 - -# Introduction - -Shared generics is a runtime+JIT feature aimed at reducing the amount of code the runtime generates for generic methods of various instantiations (supports methods on generic types and generic methods). The idea is that for certain instantiations, the generated code will almost be identical with the exception of a few instructions, so in order to reduce the memory footprint, and the amount of time we spend jitting these generic methods, the runtime will generate a single special canonical version of the code, which can be used by all compatible instantiations of the method. - -More information on the design can be found in the original MSR paper at https://www.microsoft.com/en-us/research/publication/design-and-implementation-of-generics-for-the-net-common-language-runtime. - -### Canonical Codegen and Generic Dictionaries - -Consider the following C# code sample: - -``` c# -string Func() -{ - return typeof(List).ToString(); -} -``` - -Without shared generics, the code for instantiations like `Func` or `Func` would look identical except for one single instruction: the one that loads the correct TypeHandle of type `List`: -``` asm - mov rcx, type handle of List or List - call ToString() - ret -``` - -With shared generics, the canonical code will not have any hard-coded versions of the type handle of List, but instead looks up the exact type handle either through a call to a runtime helper API, or by loading it up from the *generic dictionary* of the instantiation of Func that is executing. The code would look more like the following: -``` asm - mov rcx, generic context // MethodDesc of Func or Func - mov rcx, [rcx + offset of InstantiatedMethodDesc::m_pPerInstInfo] // This is the generic dictionary - mov rcx, [rcx + dictionary slot containing type handle of List] - call ToString() - ret -``` - -The generic context in this example is the InstantiatedMethodDesc of `Func` or `Func`. The generic dictionary is a data structure used by shared generic code to fetch instantiation-specific information. It is basically an array where the entries are instantiation-specific type handles, method handles, field handles, method entry points, etc... The "PerInstInfo" fields on MethodTable and InstantiatedMethodDesc structures point at the generic dictionary structure for a generic type and method respectively. - -In this example, the generic dictionary for Func will contain a slot with the type handle for type List, and the generic dictionary for Func will contain a slot with the type handle for type List. - -This feature is currently only supported for instantiations over reference types because they all have the same size/properties/layout/etc... For instantiations over primitive types or value types, the runtime will generate separate code bodies for each instantiation. - - -# Layouts and Algorithms - -### Dictionaries Pointers on Types and Methods - -The dictionary used by any given generic method is pointed at by the `m_pPerInstInfo` field on the `InstantiatedMethodDesc` structure of that method. It's a direct pointer to the contents of the generic dictionary data. - -On generic types, there's an extra level of indirection: the 'm_pPerInstInfo' field on the `MethodTable` structure is a pointer to a table of dictionaries, and each entry in that table is a pointer to the actual generic dictionary data. This is because types have inheritance, and derived generic types inherit the dictionary pointers of their base types. - -Here's an example: -```c# -class BaseClass { } - -class DerivedClass : BaseClass { } - -class AnotherDerivedClass : DerivedClass { } -``` - -The MethodTables of each of these types will look like the following: - -| **BaseClass[T]'s MethodTable** | -|-------------------------------------------------------| -| ... | -| `m_PerInstInfo`: points at dictionary table below | -| ... | -| `dictionaryTable[0]`: points at dictionary data below | -| `BaseClass's dictionary data here` | - -| **DerivedClass[U]'s MethodTable ** | -|----------------------------------------------------------------| -| ... | -| `m_PerInstInfo`: points at dictionary table below | -| ... | -| `dictionaryTable[0]`: points at dictionary data of `BaseClass` | -| `dictionaryTable[1]`: points at dictionary data below | -| `DerivedClass's dictionary data here` | - -| **AnotherDerivedClass's MethodTable** | -|-------------------------------------------------------------------| -| ... | -| `m_PerInstInfo`: points at dictionary table below | -| ... | -| `dictionaryTable[0]`: points at dictionary data of `BaseClass` | -| `dictionaryTable[1]`: points at dictionary data of `DerivedClass` | - -Note that `AnotherDerivedClass` doesn't have a dictionary of its own given that it is not a generic type, but inherits the dictionary pointers of its base types. - -### Dictionary Slots - -As described earlier, a generic dictionary is an array of multiple slots containing instantiation-specific information. When a dictionary is initially allocated for a certain generic type or method, all of its slots are initialized to NULL, and are lazily populated on demand as code executes (see: `Dictionary::PopulateEntry(...)`). - -The first N slots in an instantiation of N arguments are always going to be the type handles of the instantiation type arguments (this is kind of an optimization as well). The slots that follow contain instantiation-based information. - -For instance, here is an example of the contents of the generic dictionary for our `Func` example: - -| `Func's dicionary` | -|--------------------------------------| -| slot[0]: TypeHandle(`string`) | -| slot[1]: Total dictionary size | -| slot[2]: TypeHandle(`List`) | -| slot[3]: NULL (not used) | -| slot[4]: NULL (not used) | - -Note: the size slot is never used by generic code, and is part of the dynamic dictionary expansion feature. More on that below. - -When this dictionary is first allocated, only slot[0] is initialized because it contains the instantiation type arguments (and of course the size slot after the dictionary expansion feature), but the rest of the slots (example slot[2]) are NULL, and get lazily populated with values if we ever hit a code path that attempts to use them. - -When loading information from a slot that is still NULL, the generic code will call one of these runtime helper functions to populate the dictionary slot with a value: -- `JIT_GenericHandleClass`: Used to lookup a value in a generic type dictionary. This helper is used by all instance methods on generic types. -- `JIT_GenericHandleMethod`: Used to lookup a value in a generic method dictionary. This helper used by all generic methods, or non-generic static methods on generic types. - -When generating shared generic code, the JIT knows which slots to use for the various lookups, and the kind of information contained in each slot using the help of the `DictionaryLayout` implementation (https://github.com/dotnet/coreclr/blob/master/src/vm/genericdict.cpp). - -### Dictionary Layouts - -The `DictionaryLayout` structure is what tells the JIT which slot to use when performing a dictionary lookup. This `DictionaryLayout` structure has a couple of important properties: -- It is shared accross all compatible instantiations of a certain type of method. In other words, a dictionary layout is associated with the canonical instantiation of a type or a method. For instance, in our example above, `Func` and `Func` are compatible instantiations, each with their own **separate dictionaries**, however they all share the **same dictionary layout**, which is associated with the canonical instantiation `Func<__Canon>`. -- The dictionaries of generic types or methods have the same number of slots as their dictionary layouts. Note: historically before the introduction of the dynamic dictionary expansion feature, the generic dictionaries could be smaller than their layouts, meaning that for certain lookups, we had to use invoke some runtime helper APIs (slow path). - -When a generic type or method is first created, its dictionary layout contains 'unassigned' slots. Assignments happen as part of code generation, whenever the JIT needs to emit a dictionary lookup sequence. This assignment happens during the calls to the `DictionaryLayout::FindToken(...)` APIs. Once a slot has been assigned, it becomes associated with a certain signature, which describes the kind of value that will go in every instantiatied dictionary at that slot index. - -Given an input signature, slot assignment is performed with the following algorithm: - -``` -Begin with slot = 0 -Foreach entry in dictionary layout - If entry.signature != NULL - If entry.signature == inputSignature - return slot - EndIf - Else - entry.signature = inputSignature - return slot - EndIf - slot++ -EndForeach -``` - -So what happens when the above algorithm runs, but no existing slot with the same signature is found, and we're out of 'unassigned' slots? This is where the dynamic dictionary expansion kicks in to resize the layout by adding more slots to it, and resizing all dictionaries associated with this layout. - -# Dynamic Dictionary Expansion - -### History - -Before the dynamic dictionary expansion feature, dictionary layouts were organized into buckets (a linked list of fixed-size `DictionaryLayout` structures). The size of the initial layout bucket was always fixed to some number which was computed based on some heuristics for generic types, and always fixed to 4 slots for generic methods. The generic types and methods also had fixed-size generic dictionaries which could be used for lookups (also known as "fast lookup slots"). - -When a bucket gets filled with entries, we would just allocate a new `DictionaryLayout` bucket, and add it to the list. The problem however is that we could not resize the generic dictionaries of types or methods, because they had already been allocated with a fixed size, and the JIT does not support generating instructions that could indirect into a linked-list of dictionaries. Given that limitation, we could only lookup a generic dictionary for a fixed number of values (the ones associated with the entries of the first `DictionaryLayout` bucket), and were forced to go through a slower runtime helper for additional lookups. This implementation design is from the time of fragile NGen images, and having expandable dictionary structures would have been very complicated. - -When the [ReadyToRun](https://github.com/dotnet/coreclr/blob/master/Documentation/botr/readytorun-overview.md) and the Tiered Compilation technologies were introduced, some performance problems started to show. Dictionary slots were getting assigned quickly when used by ReadyToRun code, and when the runtime decided to re-jit certain methods for better performance, it could not in some cases find any remaining "fast lookup slots", and was forced to generate code that goes through the slower runtime helpers. This ended up hurting performance in some scenarios, and a decision was made to not use the fast lookup slots for ReadyToRun code, and instead keep them reserved for re-jitted code. This decision however hurt the ReadyToRun performance, but it was a necessary compromise since we cared more about re-jitted code throughput over R2R throughput. - -For this reason, the dynamic dictionary expansion feature was introduced. - -### Description - -The feature is simple in concept: change dictionary layouts from a linked list of buckets into dynamically expandable arrays instead. Sounds simple, but great care had to be taken when impementing it, because: -- We can't just resize `DictionaryLayout` structures alone. If the size of the layout is larger than the size of the actual generic dictionary, this would cause the JIT to generate indirection instructions that do not match the size of the dictionary data, leading to access violations. -- We can't just resize generic dictionaries on types and methods: - - They were already allocated with a fixed number of slots, and cannot be resized in place. - - They can be in use by some generic code. -- We can't just resize the generic dictionary for a single instantiation. For instance, in our example above, let's say we wanted to expand the dictionary for `Func`. The resizing of the layout would have an impact on the shared canonical code that the JIT generates for `Func<__Canon>`. If we only resized the dictionary of `Func`, the shared generic code would work for that instantiation only, but when we attempt to use it with another instantiation like `Func`, the jitted instructions would no longer match the size of the dictionary structure, and would cause access violations. -- The runtime is multithreaded, which adds to the complexity. - -### Expansion Algorithm - -##### Step 1 - Type and Method Registration - -The first step in this feature is to insert all generic types and methods with dictionaries into a hashtable, where the key is the canonical instantiation. For instance, with our example, `Func` and `Func` would be added to the hashtable as values under the `Func<__Canon>` key. This ensures that if we ever need to resize the dictionary layout, we would have a way of finding all existing instantiations to resize their dictionaries as well (remember, a dictionary size has to match the size of the layout now). This is achieved by calls to the `Module::RecordTypeForDictionaryExpansion_Locked` and `Module::RecordMethodForDictionaryExpansion_Locked` APIs, every time a new generic type or method is created, just before they get published for usage by other threads. - -##### Step 2 - Dictionary Layout Expansion - -Resizing of the dictionary layouts takes place in `DictionaryLayout::ExpandDictionaryLayout`. A new `DictionaryLayout` structure is allocated with a larger size, and the contents of the old layout are copied over. At this point, we **cannot yet** associate that new layout with the canonical instantiation: we need to resize the dictionaries of all related instantiations (because of multi-threading). - -##### Step 3 - Type and Method Dictionaries Expansion - -Resizing of the dictionaries of all related types or methods takes place in `Module::ExpandTypeDictionaries_Locked` and `Module::ExpandMethodDictionaries_Locked`. New dictionaries are allocated for each affected type or method, and the contents of their old dictionaries are copied over. These new dictionaries then get published on the corresponding `MethodTable` or `InstantiatedMethodDesc` structures (the "PerInstInfo" field). Great care is taken to perform all the dictionary allocations and initializations first before publishing them, with a call to `FlushProcessWriteBuffers()` in the middle to ensure correct ordering of read/write operations in multi-threading. - -One thing to note is that old dictionaries are not deallocated, but once a new dictionary gets published on a MethodTable or MethodDesc, any subsequent dictionary lookup by generic code will make use of that newly allocated dictionary. Deallocating old dictionaries would be extremely complicated, especially in a multi-threaded environment, and won't give any useful benefit. - -##### Step 4 - Publishing New Dictionary Layout - -Finally, after resizing all generic dictionaries, the last step is to publish the newly allocated `DictionaryLayout` structure by associating it with the canonical instantiation. - -##### Note on Thread Synchronization - -Thread synchronization is done by protecting all places where dictionaries are read/written in a meaninful way using a critical section implementation: -``` c++ -CrstHolder ch(&SystemDomain::SystemModule()->m_DictionaryCrst); -``` - -Here is the list of places that require synchronization: -- Before recording any new generic type or method into hashtable to track them for dictionary expansions (`RecordTypeForDictionaryExpansion_Locked` and `RecordMethodForDictionaryExpansion_Locked`). This prevents any expansion from taking place on another thread before we get a chance to add the type/method into the hashtable and track them. -- Before any unassigned slot in a DictionaryLayout structure gets assigned a certain lookup kind/signature (see `DictionaryLayout::FindToken` implementations). No two threads are allowed to update a DictionaryLayout structure at the same time. -- Before any uninitialized dictionary slot gets populated with a value as a result of a dictionary lookup (see `Dictionary::PopulateEntry`). This is because during expansion, the contents of an existing dictionary are copied over to the newly allocated one, so the lock synchronizes read and write operations. - -### Diagnostics - -During feature development, an interesting set of bugs were hit, all having to do with multi-threaded executions. The main root cause behind these bugs was that some threads started to make use of newly allocated generic MethodTables or MethodDescs, and started to expand their dictionary layouts before we ever got a chance to insert these new types/methods into the hashtable to correctly track them for dictionary resizing. In other words, some thread was still in the process of constructing these MethodTables/MethodDescs, got them to a usable state and published them, making them available for other threads to start using, but did not yet reach the point of recording them into the hashtable of dictionary expansion tracking. The effect is that some shared generic code started accessing slots beyond the size limits of the generic dictionaries of these types/methods, causing access violations. - -The most useful piece of data that made it easy to diagnose these access violations was a pointer in each dynamically allocated dictionary to its predecessor. Tracking back dictionaries using these pointers led to the location in memory where the incorrect lookup value was loaded from, and helped root cause the bug. - -These predecessor pointers are allocated at the begining of each dynamically allocated dictionary, but are not part of the dictionary itself (so think of it as slot[-1]). - -The plan is to also add an SOS command that could help diagnose dictionary contents accross the chain of dynamically allocated dictionaries (dotnet/diagnostics#588). - diff --git a/src/debug/daccess/nidump.cpp b/src/debug/daccess/nidump.cpp index 7cb74f6ae2e5..4ada77f69b30 100644 --- a/src/debug/daccess/nidump.cpp +++ b/src/debug/daccess/nidump.cpp @@ -6984,7 +6984,7 @@ NativeImageDumper::DumpMethodTable( PTR_MethodTable mt, const char * name, if( currentDictionary != NULL ) { PTR_DictionaryEntry entry(currentDictionary->EntryAddr(0)); - + PTR_DictionaryLayout layout( clazz->GetDictionaryLayout() ); DisplayStartStructure( "Dictionary", @@ -6992,7 +6992,7 @@ NativeImageDumper::DumpMethodTable( PTR_MethodTable mt, const char * name, //if there is a layout, use it to compute //the size, otherwise there is just the one //entry. - DictionaryLayout::GetDictionarySizeFromLayout(mt->GetNumGenericArgs(), layout), + DictionaryLayout::GetFirstDictionaryBucketSize(mt->GetNumGenericArgs(), layout), METHODTABLES ); DisplayStartArrayWithOffset( m_pEntries, NULL, Dictionary, @@ -8018,7 +8018,7 @@ void NativeImageDumper::DumpMethodDesc( PTR_MethodDesc md, PTR_Module module ) { PTR_DictionaryLayout layout(wrapped->IsSharedByGenericMethodInstantiations() ? dac_cast(wrapped->GetDictLayoutRaw()) : NULL ); - dictSize = DictionaryLayout::GetDictionarySizeFromLayout(imd->GetNumGenericMethodArgs(), + dictSize = DictionaryLayout::GetFirstDictionaryBucketSize(imd->GetNumGenericMethodArgs(), layout); } } diff --git a/src/vm/appdomain.cpp b/src/vm/appdomain.cpp index 270612b1b440..9c855655c770 100644 --- a/src/vm/appdomain.cpp +++ b/src/vm/appdomain.cpp @@ -2068,10 +2068,6 @@ void SystemDomain::LoadBaseSystemClasses() g_pDelegateClass = MscorlibBinder::GetClass(CLASS__DELEGATE); g_pMulticastDelegateClass = MscorlibBinder::GetClass(CLASS__MULTICAST_DELEGATE); -#ifndef CROSSGEN_COMPILE - CrossLoaderAllocatorHashSetup::EnsureTypesLoaded(); -#endif - // used by IsImplicitInterfaceOfSZArray MscorlibBinder::GetClass(CLASS__IENUMERABLEGENERIC); MscorlibBinder::GetClass(CLASS__ICOLLECTIONGENERIC); @@ -2087,6 +2083,10 @@ void SystemDomain::LoadBaseSystemClasses() g_pUtf8StringClass = MscorlibBinder::GetClass(CLASS__UTF8_STRING); #endif // FEATURE_UTF8STRING +#ifndef CROSSGEN_COMPILE + CrossLoaderAllocatorHashSetup::EnsureTypesLoaded(); +#endif + #ifndef CROSSGEN_COMPILE ECall::PopulateManagedStringConstructors(); #endif // CROSSGEN_COMPILE diff --git a/src/vm/ceeload.cpp b/src/vm/ceeload.cpp index f95520ee5032..d108813a35e4 100644 --- a/src/vm/ceeload.cpp +++ b/src/vm/ceeload.cpp @@ -556,7 +556,6 @@ void Module::Initialize(AllocMemTracker *pamTracker, LPCWSTR szName) m_FixupCrst.Init(CrstModuleFixup, (CrstFlags)(CRST_HOST_BREAKABLE|CRST_REENTRANCY)); m_InstMethodHashTableCrst.Init(CrstInstMethodHashTable, CRST_REENTRANCY); m_ISymUnmanagedReaderCrst.Init(CrstISymUnmanagedReader, CRST_DEBUGGER_THREAD); - m_DictionaryCrst.Init(CrstDomainLocalBlock); if (!m_file->HasNativeImage()) { @@ -688,12 +687,8 @@ void Module::Initialize(AllocMemTracker *pamTracker, LPCWSTR szName) } #endif // defined (PROFILING_SUPPORTED) &&!defined(DACCESS_COMPILE) && !defined(CROSSGEN_COMPILE) -#ifndef CROSSGEN_COMPILE - m_dynamicSlotsHashForTypes.Init(GetLoaderAllocator()); - m_dynamicSlotsHashForMethods.Init(GetLoaderAllocator()); -#endif - LOG((LF_CLASSLOADER, LL_INFO10, "Loaded pModule: \"%ws\".\n", GetDebugName())); + } #endif // DACCESS_COMPILE @@ -13238,327 +13233,6 @@ void ReflectionModule::ReleaseILData() Module::ReleaseILData(); } - -TypeHandle* AllocateNewMethodDictionaryForExpansion(InstantiatedMethodDesc* pIMD, DWORD cbSize) -{ - TypeHandle* pInstOrPerInstInfo = (TypeHandle*)(void*)pIMD->GetLoaderAllocator()->GetHighFrequencyHeap()->AllocMem(S_SIZE_T(cbSize + sizeof(void*))); - ZeroMemory(pInstOrPerInstInfo, cbSize + sizeof(void*)); - - // Slot[-1] points at previous dictionary to help with diagnostics when investigating crashes - *(byte**)pInstOrPerInstInfo = (byte*)pIMD->m_pPerInstInfo.GetValue() + 1; - pInstOrPerInstInfo++; - - // Copy old dictionary entry contents - memcpy(pInstOrPerInstInfo, (const void*)pIMD->m_pPerInstInfo.GetValue(), pIMD->GetDictionarySlotsSize()); - - ULONG_PTR* pSizeSlot = ((ULONG_PTR*)pInstOrPerInstInfo) + pIMD->GetNumGenericMethodArgs(); - *pSizeSlot = cbSize; - - return pInstOrPerInstInfo; -} - -TypeHandle* AllocateNewTypeDictionaryForExpansion(MethodTable* pMT, DWORD cbSize) -{ - TypeHandle* pInstOrPerInstInfo = (TypeHandle*)(void*)pMT->GetLoaderAllocator()->GetHighFrequencyHeap()->AllocMem(S_SIZE_T(cbSize + sizeof(void*))); - ZeroMemory(pInstOrPerInstInfo, cbSize + sizeof(void*)); - - // Slot[-1] points at previous dictionary to help with diagnostics when investigating crashes - *(byte**)pInstOrPerInstInfo = (byte*)pMT->GetPerInstInfo()[pMT->GetNumDicts() - 1].GetValue() + 1; - pInstOrPerInstInfo++; - - // Copy old dictionary entry contents - memcpy(pInstOrPerInstInfo, (const void*)pMT->GetPerInstInfo()[pMT->GetNumDicts() - 1].GetValue(), pMT->GetDictionarySlotsSize()); - - ULONG_PTR* pSizeSlot = ((ULONG_PTR*)pInstOrPerInstInfo) + pMT->GetNumGenericArgs(); - *pSizeSlot = cbSize; - - return pInstOrPerInstInfo; -} - -#ifdef _DEBUG -void Module::EnsureTypeRecorded(MethodTable* pMT) -{ - _ASSERTE(SystemDomain::SystemModule()->m_DictionaryCrst.OwnedByCurrentThread()); - - BOOL typeExistsInHashtable = FALSE; - auto lamda = [&typeExistsInHashtable, pMT](OBJECTREF obj, MethodTable* pMTKey, MethodTable* pMTValue) - { - typeExistsInHashtable = (pMT == pMTValue); - return pMT != pMTValue; - }; - - m_dynamicSlotsHashForTypes.VisitValuesOfKey(pMT->GetCanonicalMethodTable(), lamda); - _ASSERTE(typeExistsInHashtable); -} - -void Module::EnsureMethodRecorded(MethodDesc* pMD) -{ - _ASSERTE(SystemDomain::SystemModule()->m_DictionaryCrst.OwnedByCurrentThread()); - - BOOL methodExistsInHashtable = FALSE; - auto lamda = [&methodExistsInHashtable, pMD](OBJECTREF obj, MethodDesc* pMDKey, MethodDesc* pMDValue) - { - methodExistsInHashtable = (pMD== pMDValue); - return pMD != pMDValue; - }; - - m_dynamicSlotsHashForMethods.VisitValuesOfKey(pMD->GetExistingWrappedMethodDesc(), lamda); - _ASSERTE(methodExistsInHashtable); -} -#endif - -void Module::RecordTypeForDictionaryExpansion_Locked(MethodTable* pGenericParentMT, MethodTable* pDependencyMT) -{ - CONTRACTL - { - GC_TRIGGERS; - PRECONDITION(CheckPointer(pGenericParentMT) && CheckPointer(pDependencyMT)); - PRECONDITION(pGenericParentMT->HasInstantiation() && pGenericParentMT != pGenericParentMT->GetCanonicalMethodTable()); - PRECONDITION(SystemDomain::SystemModule()->m_DictionaryCrst.OwnedByCurrentThread()); - } - CONTRACTL_END - - DictionaryLayout* pDictLayout = pDependencyMT->GetClass()->GetDictionaryLayout(); - if (pDictLayout != NULL && pDictLayout->GetMaxSlots() > 0) - { - DWORD sizeFromDictLayout = DictionaryLayout::GetDictionarySizeFromLayout(pDependencyMT->GetNumGenericArgs(), pDictLayout); - if (pDependencyMT->GetDictionarySlotsSize() != sizeFromDictLayout) - { - _ASSERT(pDependencyMT->GetDictionarySlotsSize() < sizeFromDictLayout); - - // - // Another thread got a chance to expand the dictionary layout and expand the dictionary slots of - // other types, but not for this one yet because we're still in the process of recording it for - // expansions. - // Expand the dictionary slots here before finally adding the type to the hashtable. - // - - TypeHandle* pInstOrPerInstInfo = AllocateNewTypeDictionaryForExpansion(pDependencyMT, sizeFromDictLayout); - - // Publish the new dictionary slots to the type. - TypeHandle** pPerInstInfo = (TypeHandle**)pDependencyMT->GetPerInstInfo()->GetValuePtr(); - FastInterlockExchangePointer(pPerInstInfo + (pDependencyMT->GetNumDicts() - 1), pInstOrPerInstInfo); - - FlushProcessWriteBuffers(); - } - } - - GCX_COOP(); - m_dynamicSlotsHashForTypes.Add(pGenericParentMT->GetCanonicalMethodTable(), pDependencyMT, GetLoaderAllocator()); -} - -void Module::RecordMethodForDictionaryExpansion_Locked(MethodDesc* pDependencyMD) -{ - CONTRACTL - { - GC_TRIGGERS; - PRECONDITION(CheckPointer(pDependencyMD) && pDependencyMD->HasMethodInstantiation() && pDependencyMD->IsInstantiatingStub()); - PRECONDITION(pDependencyMD->GetDictionaryLayout() != NULL && pDependencyMD->GetDictionaryLayout()->GetMaxSlots() > 0); - PRECONDITION(SystemDomain::SystemModule()->m_DictionaryCrst.OwnedByCurrentThread()); - } - CONTRACTL_END - - DictionaryLayout* pDictLayout = pDependencyMD->GetDictionaryLayout(); - InstantiatedMethodDesc* pIMDDependency = pDependencyMD->AsInstantiatedMethodDesc(); - - DWORD sizeFromDictLayout = DictionaryLayout::GetDictionarySizeFromLayout(pDependencyMD->GetNumGenericMethodArgs(), pDictLayout); - if (pIMDDependency->GetDictionarySlotsSize() != sizeFromDictLayout) - { - _ASSERT(pIMDDependency->GetDictionarySlotsSize() < sizeFromDictLayout); - - // - // Another thread got a chance to expand the dictionary layout and expand the dictionary slots of - // other methods, but not for this one yet because we're still in the process of recording it for - // expansions. - // Expand the dictionary slots here before finally adding the method to the hashtable. - // - - TypeHandle* pInstOrPerInstInfo = AllocateNewMethodDictionaryForExpansion(pIMDDependency, sizeFromDictLayout); - - FastInterlockExchangePointer((TypeHandle**)pIMDDependency->m_pPerInstInfo.GetValuePtr(), pInstOrPerInstInfo); - - FlushProcessWriteBuffers(); - } - - GCX_COOP(); - - _ASSERTE(pDependencyMD->GetExistingWrappedMethodDesc() != NULL); - m_dynamicSlotsHashForMethods.Add(pDependencyMD->GetExistingWrappedMethodDesc(), pDependencyMD, GetLoaderAllocator()); -} - -void Module::ExpandTypeDictionaries_Locked(MethodTable* pMT, DictionaryLayout* pOldLayout, DictionaryLayout* pNewLayout) -{ - CONTRACTL - { - STANDARD_VM_CHECK; - INJECT_FAULT(ThrowOutOfMemory();); - PRECONDITION(CheckPointer(pOldLayout) && CheckPointer(pNewLayout)); - PRECONDITION(CheckPointer(pMT) && pMT->HasInstantiation() && pMT->GetClass()->GetDictionaryLayout() == pOldLayout); - PRECONDITION(SystemDomain::SystemModule()->m_DictionaryCrst.OwnedByCurrentThread()); - } - CONTRACTL_END - - GCX_COOP(); - - MethodTable* pCanonMT = pMT->GetCanonicalMethodTable(); - DWORD oldInfoSize = DictionaryLayout::GetDictionarySizeFromLayout(pMT->GetNumGenericArgs(), pOldLayout); - DWORD newInfoSize = DictionaryLayout::GetDictionarySizeFromLayout(pMT->GetNumGenericArgs(), pNewLayout); - - // - // Dictionary expansion for types needs to be done in multiple steps, given how derived types do not directly embed dictionaries - // from parent types, but instead reference them from directly from the parent types. Also, this is necessary to ensure correctness - // for lock-free read operations for dictionary slots: - // 1) Allocate new dictionaries for all instantiated types of the same typedef as the one being expanded. - // 2) After all allocations and initializations are completed, publish the dictionaries to the types in #1 after - // flushing write buffers - // 3) For all types that derive from #1, update the embedded dictinoary pointer to the newly allocated one. - // - - struct NewDictionary - { - MethodTable* pMT; - TypeHandle* pDictSlots; - }; - StackSArray dictionaryUpdates; - -#ifdef _DEBUG - auto expandPerInstInfos = [oldInfoSize, newInfoSize, &dictionaryUpdates](OBJECTREF obj, MethodTable* pMTKey, MethodTable* pMTToUpdate) -#else - auto expandPerInstInfos = [newInfoSize, &dictionaryUpdates](OBJECTREF obj, MethodTable* pMTKey, MethodTable* pMTToUpdate) -#endif - { - if (!pMTToUpdate->HasSameTypeDefAs(pMTKey)) - return true; - - _ASSERTE(pMTToUpdate != pMTToUpdate->GetCanonicalMethodTable() && pMTToUpdate->GetCanonicalMethodTable() == pMTKey); - _ASSERTE(pMTToUpdate->GetDictionarySlotsSize() == oldInfoSize); - - TypeHandle* pInstOrPerInstInfo = AllocateNewTypeDictionaryForExpansion(pMTToUpdate, newInfoSize); - - NewDictionary entry; - entry.pMT = pMTToUpdate; - entry.pDictSlots = pInstOrPerInstInfo; - dictionaryUpdates.Append(entry); - - return true; // Keep walking - }; - - m_dynamicSlotsHashForTypes.VisitValuesOfKey(pCanonMT, expandPerInstInfos); - - // Flush write buffers to ensure new dictionaries are fully writted and initalized before publishing them - FlushProcessWriteBuffers(); - - for (SArray::Iterator i = dictionaryUpdates.Begin(); i != dictionaryUpdates.End(); i++) - { - MethodTable* pMT = i->pMT; - TypeHandle** pPerInstInfo = (TypeHandle**)pMT->GetPerInstInfo()->GetValuePtr(); - FastInterlockExchangePointer(pPerInstInfo + (pMT->GetNumDicts() - 1), i->pDictSlots); - _ASSERTE(pMT->GetDictionarySlotsSize() == newInfoSize); - _ASSERTE((TypeHandle*)pMT->GetPerInstInfo()[pMT->GetNumDicts() - 1].GetValue() == i->pDictSlots); - } - - auto updateDependentDicts = [](OBJECTREF obj, MethodTable* pMTKey, MethodTable* pMTToUpdate) - { - if (pMTToUpdate->HasSameTypeDefAs(pMTKey)) - return true; - - MethodTable* pCurrentMT = pMTToUpdate->GetParentMethodTable(); - while (pCurrentMT) - { - if (pCurrentMT->HasSameTypeDefAs(pMTKey)) - { - DWORD dictToUpdate = pCurrentMT->GetNumDicts() - 1; - Dictionary* pUpdatedParentDict = pCurrentMT->GetPerInstInfo()[dictToUpdate].GetValue(); - TypeHandle** pPerInstInfo = (TypeHandle**)pMTToUpdate->GetPerInstInfo()->GetValuePtr(); - FastInterlockExchangePointer(pPerInstInfo + dictToUpdate, (TypeHandle*)pUpdatedParentDict); - _ASSERTE(pMTToUpdate->GetPerInstInfo()[dictToUpdate].GetValue() == pUpdatedParentDict); - - return true; // Keep walking - } - pCurrentMT = pCurrentMT->GetParentMethodTable(); - } - - UNREACHABLE(); - }; - - m_dynamicSlotsHashForTypes.VisitValuesOfKey(pCanonMT, updateDependentDicts); - - // Ensure no other thread uses old dictionary pointers - FlushProcessWriteBuffers(); -} - -void Module::ExpandMethodDictionaries_Locked(MethodDesc* pMD, DictionaryLayout* pOldLayout, DictionaryLayout* pNewLayout) -{ - CONTRACTL - { - STANDARD_VM_CHECK; - INJECT_FAULT(ThrowOutOfMemory();); - PRECONDITION(CheckPointer(pOldLayout) && CheckPointer(pNewLayout)); - PRECONDITION(CheckPointer(pMD)); - PRECONDITION(pMD->HasMethodInstantiation() && pMD->GetDictionaryLayout() == pOldLayout); - PRECONDITION(SystemDomain::SystemModule()->m_DictionaryCrst.OwnedByCurrentThread()); - } - CONTRACTL_END - - GCX_COOP(); - - // - // Dictionary expansion for methods needs to be done in two steps to ensure correctness for lock-free read operations - // for dictionary slots: - // 1) Allocate new dictionaries for all instantiated methods sharing the same canonical form as the input method - // 2) After all allocations and initializations are completed, publish the dictionaries to the methods after - // flushing write buffers - // - - MethodDesc* pCanonMD = pMD->IsInstantiatingStub() ? pMD->GetExistingWrappedMethodDesc() : pMD; - _ASSERTE(pCanonMD != NULL); - DWORD oldInfoSize = DictionaryLayout::GetDictionarySizeFromLayout(pMD->GetNumGenericMethodArgs(), pOldLayout); - DWORD newInfoSize = DictionaryLayout::GetDictionarySizeFromLayout(pMD->GetNumGenericMethodArgs(), pNewLayout); - - struct NewDictionary - { - InstantiatedMethodDesc* pIMD; - TypeHandle* pDictSlots; - }; - StackSArray dictionaryUpdates; - -#ifdef _DEBUG - auto lambda = [oldInfoSize, newInfoSize, &dictionaryUpdates](OBJECTREF obj, MethodDesc* pMDKey, MethodDesc* pMDToUpdate) -#else - auto lambda = [newInfoSize, &dictionaryUpdates](OBJECTREF obj, MethodDesc* pMDKey, MethodDesc* pMDToUpdate) -#endif - { - // Update m_pPerInstInfo for the pMethodDesc being visited here - _ASSERTE(pMDToUpdate->IsInstantiatingStub() && pMDToUpdate->GetExistingWrappedMethodDesc() == pMDKey); - - InstantiatedMethodDesc* pInstantiatedMD = pMDToUpdate->AsInstantiatedMethodDesc(); - _ASSERTE(pInstantiatedMD->GetDictionarySlotsSize() == oldInfoSize); - - TypeHandle* pInstOrPerInstInfo = AllocateNewMethodDictionaryForExpansion(pInstantiatedMD, newInfoSize); - - NewDictionary entry; - entry.pIMD = pInstantiatedMD; - entry.pDictSlots = pInstOrPerInstInfo; - dictionaryUpdates.Append(entry); - - return true; // Keep walking - }; - - m_dynamicSlotsHashForMethods.VisitValuesOfKey(pCanonMD, lambda); - - // Flush write buffers to ensure new dictionaries are fully writted and initalized before publishing them - FlushProcessWriteBuffers(); - - for (SArray::Iterator i = dictionaryUpdates.Begin(); i != dictionaryUpdates.End(); i++) - { - FastInterlockExchangePointer((TypeHandle**)i->pIMD->m_pPerInstInfo.GetValuePtr(), i->pDictSlots); - _ASSERTE((TypeHandle*)i->pIMD->m_pPerInstInfo.GetValue() == i->pDictSlots); - _ASSERTE(i->pIMD->GetDictionarySlotsSize() == newInfoSize); - } - - // Ensure no other thread uses old dictionary pointers - FlushProcessWriteBuffers(); -} #endif // !CROSSGEN_COMPILE #endif // !DACCESS_COMPILE diff --git a/src/vm/ceeload.h b/src/vm/ceeload.h index d620dfc4655b..d2b237eb2fa8 100644 --- a/src/vm/ceeload.h +++ b/src/vm/ceeload.h @@ -3237,33 +3237,6 @@ class Module void SetNativeMetadataAssemblyRefInCache(DWORD rid, PTR_Assembly pAssembly); #endif // !defined(DACCESS_COMPILE) - - // For protecting dictionary layout slot additions, and additions to the m_dynamicSlotsHashFor{Types/Methods} below - CrstExplicitInit m_DictionaryCrst; - -#ifndef CROSSGEN_COMPILE -private: - class DynamicDictSlotsForTypesTrackerHashTraits : public NoRemoveDefaultCrossLoaderAllocatorHashTraits { }; - typedef CrossLoaderAllocatorHash DynamicDictSlotsForTypesTrackerHash; - - class DynamicDictSlotsForMethodsTrackerHashTraits : public NoRemoveDefaultCrossLoaderAllocatorHashTraits { }; - typedef CrossLoaderAllocatorHash DynamicDictSlotsForMethodsTrackerHash; - - DynamicDictSlotsForTypesTrackerHash m_dynamicSlotsHashForTypes; - DynamicDictSlotsForMethodsTrackerHash m_dynamicSlotsHashForMethods; - -public: - void RecordTypeForDictionaryExpansion_Locked(MethodTable* pGenericParentMT, MethodTable* pDependencyMT); - void RecordMethodForDictionaryExpansion_Locked(MethodDesc* pDependencyMD); - - void ExpandTypeDictionaries_Locked(MethodTable* pMT, DictionaryLayout* pOldLayout, DictionaryLayout* pNewLayout); - void ExpandMethodDictionaries_Locked(MethodDesc* pMD, DictionaryLayout* pOldLayout, DictionaryLayout* pNewLayout); - -#ifdef _DEBUG - void EnsureTypeRecorded(MethodTable* pMT); - void EnsureMethodRecorded(MethodDesc* pMD); -#endif -#endif //!CROSSGEN_COMPILE }; // diff --git a/src/vm/class.cpp b/src/vm/class.cpp index 4210374436ed..33b8ea5c1686 100644 --- a/src/vm/class.cpp +++ b/src/vm/class.cpp @@ -967,87 +967,12 @@ void ClassLoader::LoadExactParents(MethodTable *pMT) MethodTableBuilder::CopyExactParentSlots(pMT, pApproxParentMT); - // Record this type for dynamic dictionary expansion (if applicable) - RecordDependenciesForDictionaryExpansion(pMT); - // We can now mark this type as having exact parents pMT->SetHasExactParent(); RETURN; } -/*static*/ -void ClassLoader::RecordDependenciesForDictionaryExpansion(MethodTable* pMT) -{ - CONTRACT_VOID - { - STANDARD_VM_CHECK; - PRECONDITION(CheckPointer(pMT)); - PRECONDITION(pMT->CheckLoadLevel(CLASS_LOAD_APPROXPARENTS)); - } - CONTRACT_END; - - -#ifndef CROSSGEN_COMPILE - if (pMT->GetNumDicts() == 0) - RETURN; - - // Check if there are no dependencies that need tracking. There is no point in taking the lock - // below if we don't need to track anything. - { - bool hasSharedMethodTables = false; - MethodTable* pCurrentMT = pMT; - while (pCurrentMT) - { - if (pCurrentMT->HasInstantiation() && !pCurrentMT->IsCanonicalMethodTable()) - { - hasSharedMethodTables = true; - break; - } - pCurrentMT = pCurrentMT->GetParentMethodTable(); - } - - if (!hasSharedMethodTables) - RETURN; - } - - { - CrstHolder ch(&SystemDomain::SystemModule()->m_DictionaryCrst); - - MethodTable* pParentMT = pMT->GetParentMethodTable(); - if (pParentMT != NULL && pParentMT->HasPerInstInfo()) - { - // Copy/update down all inherited dictionary pointers which we could not embed. - // This step has to be done under the dictionary lock, to prevent other threads from making updates - // the the dictionaries of the parent types while we're also copying them over to the derived type here. - - DWORD nDicts = pParentMT->GetNumDicts(); - for (DWORD iDict = 0; iDict < nDicts; iDict++) - { - if (pMT->GetPerInstInfo()[iDict].GetValueMaybeNull() != pParentMT->GetPerInstInfo()[iDict].GetValueMaybeNull()) - { - pMT->GetPerInstInfo()[iDict].SetValueMaybeNull(pParentMT->GetPerInstInfo()[iDict].GetValueMaybeNull()); - } - } - } - - // Add the current type as a dependency to its canonical version, as well as a dependency to all parent - // types in the hierarchy with dictionaries, so that if one of the base types gets a dictionary expansion, we make - // sure to update the derived type's parent dictionary pointer. - MethodTable* pCurrentMT = pMT; - while (pCurrentMT) - { - if (pCurrentMT->HasInstantiation() && !pCurrentMT->IsCanonicalMethodTable()) - pCurrentMT->GetModule()->RecordTypeForDictionaryExpansion_Locked(pCurrentMT, pMT); - - pCurrentMT = pCurrentMT->GetParentMethodTable(); - } - } -#endif - - RETURN; -} - //******************************************************************************* // This is the routine that computes the internal type of a given type. It normalizes // structs that have only one field (of int/ptr sized values), to be that underlying type. diff --git a/src/vm/class.h b/src/vm/class.h index 4ece86142fa5..39ebcb31fb66 100644 --- a/src/vm/class.h +++ b/src/vm/class.h @@ -1797,8 +1797,6 @@ class EEClass // DO NOT CREATE A NEW EEClass USING NEW! public: - // This API is not multi-threaded safe: the dictionary layout pointer can be updated by another - // thread during a generic dictionary size expansion. PTR_DictionaryLayout GetDictionaryLayout() { SUPPORTS_DAC; diff --git a/src/vm/clsload.cpp b/src/vm/clsload.cpp index c7e283a47157..af459a348a6c 100644 --- a/src/vm/clsload.cpp +++ b/src/vm/clsload.cpp @@ -3268,13 +3268,14 @@ TypeHandle ClassLoader::CreateTypeHandleForTypeKey(TypeKey* pKey, AllocMemTracke if (IsCanonicalGenericInstantiation(pKey->GetInstantiation())) { typeHnd = CreateTypeHandleForTypeDefThrowing(pKey->GetModule(), - pKey->GetTypeToken(), - pKey->GetInstantiation(), - pamTracker); + pKey->GetTypeToken(), + pKey->GetInstantiation(), + pamTracker); } else { - typeHnd = CreateTypeHandleForNonCanonicalGenericInstantiation(pKey, pamTracker); + typeHnd = CreateTypeHandleForNonCanonicalGenericInstantiation(pKey, + pamTracker); } #if defined(_DEBUG) && !defined(CROSSGEN_COMPILE) if (Nullable::IsNullableType(typeHnd)) diff --git a/src/vm/clsload.hpp b/src/vm/clsload.hpp index b62945f8c262..79f406c3e3b2 100644 --- a/src/vm/clsload.hpp +++ b/src/vm/clsload.hpp @@ -1007,8 +1007,6 @@ class ClassLoader // Load exact parents and interfaces and dependent structures (generics dictionary, vtable fixes) static void LoadExactParents(MethodTable *pMT); - static void RecordDependenciesForDictionaryExpansion(MethodTable* pMT); - static void LoadExactParentAndInterfacesTransitively(MethodTable *pMT); // Create a non-canonical instantiation of a generic type based off the canonical instantiation diff --git a/src/vm/genericdict.cpp b/src/vm/genericdict.cpp index f121727dc92a..e573988c7982 100644 --- a/src/vm/genericdict.cpp +++ b/src/vm/genericdict.cpp @@ -32,7 +32,7 @@ #include "sigbuilder.h" #include "compile.h" -#ifndef DACCESS_COMPILE +#ifndef DACCESS_COMPILE //--------------------------------------------------------------------------------------- // @@ -63,6 +63,9 @@ DictionaryLayout::Allocate( DictionaryLayout * pD = (DictionaryLayout *)(void *)ptr; + // When bucket spills we'll allocate another layout structure + pD->m_pNext = NULL; + // This is the number of slots excluding the type parameters pD->m_numSlots = numSlots; @@ -73,24 +76,21 @@ DictionaryLayout::Allocate( //--------------------------------------------------------------------------------------- // -// Count the number of bytes that are required in a dictionary with the specified layout -// +// Count the number of bytes that are required by the first bucket in a dictionary with the specified layout +// //static -DWORD -DictionaryLayout::GetDictionarySizeFromLayout( - DWORD numGenericArgs, +DWORD +DictionaryLayout::GetFirstDictionaryBucketSize( + DWORD numGenericArgs, PTR_DictionaryLayout pDictLayout) { LIMITED_METHOD_DAC_CONTRACT; PRECONDITION(numGenericArgs > 0); PRECONDITION(CheckPointer(pDictLayout, NULL_OK)); - DWORD bytes = numGenericArgs * sizeof(TypeHandle); // Slots for instantiation arguments + DWORD bytes = numGenericArgs * sizeof(TypeHandle); if (pDictLayout != NULL) - { - bytes += sizeof(ULONG_PTR*); // Slot for dictionary size - bytes += pDictLayout->m_numSlots * sizeof(void*); // Slots for dictionary slots based on a dictionary layout - } + bytes += pDictLayout->m_numSlots * sizeof(void*); return bytes; } @@ -109,334 +109,183 @@ DictionaryLayout::GetDictionarySizeFromLayout( // Optimize the case of a token being !i (for class dictionaries) or !!i (for method dictionaries) // /* static */ -BOOL DictionaryLayout::FindTokenWorker(LoaderAllocator* pAllocator, - DWORD numGenericArgs, - DictionaryLayout* pDictLayout, - SigBuilder* pSigBuilder, - BYTE* pSig, - DWORD cbSig, - int nFirstOffset, - DictionaryEntrySignatureSource signatureSource, - CORINFO_RUNTIME_LOOKUP* pResult, - WORD* pSlotOut, - DWORD scanFromSlot /* = 0 */, - BOOL useEmptySlotIfFound /* = FALSE */) - +BOOL +DictionaryLayout::FindTokenWorker(LoaderAllocator * pAllocator, + DWORD numGenericArgs, + DictionaryLayout * pDictLayout, + CORINFO_RUNTIME_LOOKUP * pResult, + SigBuilder * pSigBuilder, + BYTE * pSig, + DWORD cbSig, + int nFirstOffset, + DictionaryEntrySignatureSource signatureSource, + WORD * pSlotOut) { CONTRACTL { STANDARD_VM_CHECK; PRECONDITION(numGenericArgs > 0); - PRECONDITION(scanFromSlot >= 0 && scanFromSlot <= pDictLayout->m_numSlots); PRECONDITION(CheckPointer(pDictLayout)); - PRECONDITION(CheckPointer(pResult) && CheckPointer(pSlotOut)); + PRECONDITION(CheckPointer(pSlotOut)); PRECONDITION(CheckPointer(pSig)); PRECONDITION((pSigBuilder == NULL && cbSig == -1) || (CheckPointer(pSigBuilder) && cbSig > 0)); } CONTRACTL_END - // First slots contain the type parameters - _ASSERTE(FitsIn(numGenericArgs + 1 + scanFromSlot)); - WORD slot = static_cast(numGenericArgs + 1 + scanFromSlot); - -#if _DEBUG - if (scanFromSlot > 0) +#ifndef FEATURE_NATIVE_IMAGE_GENERATION + // If the tiered compilation is on, save the fast dictionary slots for the hot Tier1 code + if (g_pConfig->TieredCompilation() && signatureSource == FromReadyToRunImage) { - _ASSERT(useEmptySlotIfFound); + pResult->signature = pSig; + return FALSE; + } +#endif + + BOOL isFirstBucket = TRUE; - for (DWORD iSlot = 0; iSlot < scanFromSlot; iSlot++) + // First bucket also contains type parameters + _ASSERTE(FitsIn(numGenericArgs)); + WORD slot = static_cast(numGenericArgs); + for (;;) + { + for (DWORD iSlot = 0; iSlot < pDictLayout->m_numSlots; iSlot++) { - // Verify that no entry before scanFromSlot matches the entry we're searching for - BYTE* pCandidate = (BYTE*)pDictLayout->m_slots[iSlot].m_signature; - if (pSigBuilder != NULL) + RetryMatch: + BYTE * pCandidate = (BYTE *)pDictLayout->m_slots[iSlot].m_signature; + if (pCandidate != NULL) { - if (pDictLayout->m_slots[iSlot].m_signatureSource != FromReadyToRunImage) + bool signaturesMatch = false; + + if (pSigBuilder != NULL) + { + // JIT case: compare signatures by comparing the bytes in them. We exclude + // any ReadyToRun signatures from the JIT case. + + if (pDictLayout->m_slots[iSlot].m_signatureSource != FromReadyToRunImage) + { + // Compare the signatures. We do not need to worry about the size of pCandidate. + // As long as we are comparing one byte at a time we are guaranteed to not overrun. + DWORD j; + for (j = 0; j < cbSig; j++) + { + if (pCandidate[j] != pSig[j]) + break; + } + signaturesMatch = (j == cbSig); + } + } + else + { + // ReadyToRun case: compare signatures by comparing their pointer values + signaturesMatch = (pCandidate == pSig); + } + + // We've found it + if (signaturesMatch) { - DWORD j; - for (j = 0; j < cbSig; j++) + pResult->signature = pDictLayout->m_slots[iSlot].m_signature; + + // We don't store entries outside the first bucket in the layout in the dictionary (they'll be cached in a hash + // instead). + if (!isFirstBucket) { - if (pCandidate[j] != pSig[j]) - break; + return FALSE; } - _ASSERT(j != cbSig); + _ASSERTE(FitsIn(nFirstOffset + 1)); + pResult->indirections = static_cast(nFirstOffset + 1); + pResult->offsets[nFirstOffset] = slot * sizeof(DictionaryEntry); + *pSlotOut = slot; + return TRUE; } } + // If we hit an empty slot then there's no more so use it else { - _ASSERT(pCandidate != pSig); - } - } - } -#endif + { + BaseDomain::LockHolder lh(pAllocator->GetDomain()); - for (DWORD iSlot = scanFromSlot; iSlot < pDictLayout->m_numSlots; iSlot++) - { - BYTE* pCandidate = (BYTE*)pDictLayout->m_slots[iSlot].m_signature; - if (pCandidate != NULL) - { - bool signaturesMatch = false; + if (pDictLayout->m_slots[iSlot].m_signature != NULL) + goto RetryMatch; - if (pSigBuilder != NULL) - { - // JIT case: compare signatures by comparing the bytes in them. We exclude - // any ReadyToRun signatures from the JIT case. + PVOID pResultSignature = pSig; - if (pDictLayout->m_slots[iSlot].m_signatureSource != FromReadyToRunImage) - { - // Compare the signatures. We do not need to worry about the size of pCandidate. - // As long as we are comparing one byte at a time we are guaranteed to not overrun. - DWORD j; - for (j = 0; j < cbSig; j++) + if (pSigBuilder != NULL) { - if (pCandidate[j] != pSig[j]) - break; + pSigBuilder->AppendData(isFirstBucket ? slot : 0); + + DWORD cbNewSig; + PVOID pNewSig = pSigBuilder->GetSignature(&cbNewSig); + + pResultSignature = pAllocator->GetLowFrequencyHeap()->AllocMem(S_SIZE_T(cbNewSig)); + memcpy(pResultSignature, pNewSig, cbNewSig); } - signaturesMatch = (j == cbSig); + + pDictLayout->m_slots[iSlot].m_signature = pResultSignature; + pDictLayout->m_slots[iSlot].m_signatureSource = signatureSource; } - } - else - { - // ReadyToRun case: compare signatures by comparing their pointer values - signaturesMatch = (pCandidate == pSig); - } - // We've found it - if (signaturesMatch) - { pResult->signature = pDictLayout->m_slots[iSlot].m_signature; + // Again, we only store entries in the first layout bucket in the dictionary. + if (!isFirstBucket) + { + return FALSE; + } _ASSERTE(FitsIn(nFirstOffset + 1)); pResult->indirections = static_cast(nFirstOffset + 1); pResult->offsets[nFirstOffset] = slot * sizeof(DictionaryEntry); *pSlotOut = slot; return TRUE; } + slot++; } - // If we hit an empty slot then there's no more so use it - else - { - if (!useEmptySlotIfFound) - { - *pSlotOut = static_cast(iSlot); - return FALSE; - } - - // A lock should be taken by FindToken before being allowed to use an empty slot in the layout - _ASSERT(SystemDomain::SystemModule()->m_DictionaryCrst.OwnedByCurrentThread()); - - PVOID pResultSignature = pSigBuilder == NULL ? pSig : CreateSignatureWithSlotData(pSigBuilder, pAllocator, slot); - pDictLayout->m_slots[iSlot].m_signature = pResultSignature; - pDictLayout->m_slots[iSlot].m_signatureSource = signatureSource; - - pResult->signature = pDictLayout->m_slots[iSlot].m_signature; - - _ASSERTE(FitsIn(nFirstOffset + 1)); - pResult->indirections = static_cast(nFirstOffset + 1); - pResult->offsets[nFirstOffset] = slot * sizeof(DictionaryEntry); - *pSlotOut = slot; - return TRUE; - } - - slot++; - } - - *pSlotOut = pDictLayout->m_numSlots; - return FALSE; -} - -#ifndef CROSSGEN_COMPILE -/* static */ -DictionaryLayout* DictionaryLayout::ExpandDictionaryLayout(LoaderAllocator* pAllocator, - DictionaryLayout* pCurrentDictLayout, - DWORD numGenericArgs, - SigBuilder* pSigBuilder, - BYTE* pSig, - int nFirstOffset, - DictionaryEntrySignatureSource signatureSource, - CORINFO_RUNTIME_LOOKUP* pResult, - WORD* pSlotOut) -{ - CONTRACTL - { - STANDARD_VM_CHECK; - INJECT_FAULT(ThrowOutOfMemory();); - PRECONDITION(SystemDomain::SystemModule()->m_DictionaryCrst.OwnedByCurrentThread()); - PRECONDITION(CheckPointer(pResult) && CheckPointer(pSlotOut)); - } - CONTRACTL_END - - // There shouldn't be any empty slots remaining in the current dictionary. - _ASSERTE(pCurrentDictLayout->m_slots[pCurrentDictLayout->m_numSlots - 1].m_signature != NULL); - -#ifdef _DEBUG - // Stress debug mode by increasing size by only 1 slot - if (!FitsIn((DWORD)pCurrentDictLayout->m_numSlots + 1)) - return NULL; - DictionaryLayout* pNewDictionaryLayout = Allocate(pCurrentDictLayout->m_numSlots + 1, pAllocator, NULL); -#else - if (!FitsIn((DWORD)pCurrentDictLayout->m_numSlots * 2)) - return NULL; - DictionaryLayout* pNewDictionaryLayout = Allocate(pCurrentDictLayout->m_numSlots * 2, pAllocator, NULL); -#endif - - for (DWORD iSlot = 0; iSlot < pCurrentDictLayout->m_numSlots; iSlot++) - pNewDictionaryLayout->m_slots[iSlot] = pCurrentDictLayout->m_slots[iSlot]; - - WORD layoutSlotIndex = pCurrentDictLayout->m_numSlots; - WORD slot = static_cast(numGenericArgs) + 1 + layoutSlotIndex; - - PVOID pResultSignature = pSigBuilder == NULL ? pSig : CreateSignatureWithSlotData(pSigBuilder, pAllocator, slot); - pNewDictionaryLayout->m_slots[layoutSlotIndex].m_signature = pResultSignature; - pNewDictionaryLayout->m_slots[layoutSlotIndex].m_signatureSource = signatureSource; - - pResult->signature = pNewDictionaryLayout->m_slots[layoutSlotIndex].m_signature; - - _ASSERTE(FitsIn(nFirstOffset + 1)); - pResult->indirections = static_cast(nFirstOffset + 1); - pResult->offsets[nFirstOffset] = slot * sizeof(DictionaryEntry); - *pSlotOut = slot; - - return pNewDictionaryLayout; -} -#endif - -/* static */ -BOOL DictionaryLayout::FindToken(MethodTable* pMT, - LoaderAllocator* pAllocator, - int nFirstOffset, - SigBuilder* pSigBuilder, - BYTE* pSig, - DictionaryEntrySignatureSource signatureSource, - CORINFO_RUNTIME_LOOKUP* pResult, - WORD* pSlotOut) -{ - CONTRACTL - { - STANDARD_VM_CHECK; - PRECONDITION(CheckPointer(pMT)); - PRECONDITION(CheckPointer(pAllocator)); - PRECONDITION(CheckPointer(pResult)); - PRECONDITION(pMT->HasInstantiation()); - } - CONTRACTL_END - - DWORD cbSig = -1; - pSig = pSigBuilder != NULL ? (BYTE*)pSigBuilder->GetSignature(&cbSig) : pSig; - if (FindTokenWorker(pAllocator, pMT->GetNumGenericArgs(), pMT->GetClass()->GetDictionaryLayout(), pSigBuilder, pSig, cbSig, nFirstOffset, signatureSource, pResult, pSlotOut, 0, FALSE)) - return TRUE; - - CrstHolder ch(&SystemDomain::SystemModule()->m_DictionaryCrst); - { - // Try again under lock in case another thread already expanded the dictionaries or filled an empty slot - if (FindTokenWorker(pMT->GetLoaderAllocator(), pMT->GetNumGenericArgs(), pMT->GetClass()->GetDictionaryLayout(), pSigBuilder, pSig, cbSig, nFirstOffset, signatureSource, pResult, pSlotOut, *pSlotOut, TRUE)) - return TRUE; - - -#ifndef CROSSGEN_COMPILE - DictionaryLayout* pOldLayout = pMT->GetClass()->GetDictionaryLayout(); - DictionaryLayout* pNewLayout = ExpandDictionaryLayout(pAllocator, pOldLayout, pMT->GetNumGenericArgs(), pSigBuilder, pSig, nFirstOffset, signatureSource, pResult, pSlotOut); - if (pNewLayout == NULL) - { - pResult->signature = pSigBuilder == NULL ? pSig : CreateSignatureWithSlotData(pSigBuilder, pAllocator, 0); - return FALSE; - } - - // First, expand the PerInstInfo dictionaries on types that were using the dictionary layout that just got expanded, and expand their slots - pMT->GetModule()->ExpandTypeDictionaries_Locked(pMT, pOldLayout, pNewLayout); - // Finally, update the dictionary layout pointer after all dictionaries of instantiated types have expanded, so that subsequent calls to - // DictionaryLayout::FindToken can use this. It is important to update the dictionary layout at the very last step, otherwise some other threads - // can start using newly added dictionary layout slots on types where the PerInstInfo hasn't expanded yet, and cause runtime failures. - pMT->GetClass()->SetDictionaryLayout(pNewLayout); + // If we've reached the end of the chain we need to allocate another bucket. Make the pointer update carefully to avoid + // orphaning a bucket in a race. We leak the loser in such a race (since the allocation comes from the loader heap) but both + // the race and the overflow should be very rare. + if (pDictLayout->m_pNext == NULL) + FastInterlockCompareExchangePointer(&pDictLayout->m_pNext, Allocate(4, pAllocator, NULL), 0); - return TRUE; -#else - pResult->signature = pSigBuilder == NULL ? pSig : CreateSignatureWithSlotData(pSigBuilder, pAllocator, 0); - return FALSE; -#endif + pDictLayout = pDictLayout->m_pNext; + isFirstBucket = FALSE; } -} +} // DictionaryLayout::FindToken /* static */ -BOOL DictionaryLayout::FindToken(MethodDesc* pMD, - LoaderAllocator* pAllocator, - int nFirstOffset, - SigBuilder* pSigBuilder, - BYTE* pSig, - DictionaryEntrySignatureSource signatureSource, - CORINFO_RUNTIME_LOOKUP* pResult, - WORD* pSlotOut) +BOOL +DictionaryLayout::FindToken(LoaderAllocator * pAllocator, + DWORD numGenericArgs, + DictionaryLayout * pDictLayout, + CORINFO_RUNTIME_LOOKUP * pResult, + SigBuilder * pSigBuilder, + int nFirstOffset, + DictionaryEntrySignatureSource signatureSource) { - CONTRACTL - { - STANDARD_VM_CHECK; - PRECONDITION(CheckPointer(pMD)); - PRECONDITION(CheckPointer(pAllocator)); - PRECONDITION(CheckPointer(pResult)); - PRECONDITION(pMD->HasMethodInstantiation()); - } - CONTRACTL_END - - DWORD cbSig = -1; - pSig = pSigBuilder != NULL ? (BYTE*)pSigBuilder->GetSignature(&cbSig) : pSig; - if (FindTokenWorker(pAllocator, pMD->GetNumGenericMethodArgs(), pMD->GetDictionaryLayout(), pSigBuilder, pSig, cbSig, nFirstOffset, signatureSource, pResult, pSlotOut, 0, FALSE)) - return TRUE; - - CrstHolder ch(&SystemDomain::SystemModule()->m_DictionaryCrst); - { - // Try again under lock in case another thread already expanded the dictionaries or filled an empty slot - if (FindTokenWorker(pAllocator, pMD->GetNumGenericMethodArgs(), pMD->GetDictionaryLayout(), pSigBuilder, pSig, cbSig, nFirstOffset, signatureSource, pResult, pSlotOut, *pSlotOut, TRUE)) - return TRUE; - -#ifndef CROSSGEN_COMPILE - DictionaryLayout* pOldLayout = pMD->GetDictionaryLayout(); - DictionaryLayout* pNewLayout = ExpandDictionaryLayout(pAllocator, pOldLayout, pMD->GetNumGenericMethodArgs(), pSigBuilder, pSig, nFirstOffset, signatureSource, pResult, pSlotOut); - if (pNewLayout == NULL) - { - pResult->signature = pSigBuilder == NULL ? pSig : CreateSignatureWithSlotData(pSigBuilder, pAllocator, 0); - return FALSE; - } - - // First, expand the PerInstInfo dictionaries on methods that were using the dictionary layout that just got expanded, and expand their slots - pMD->GetModule()->ExpandMethodDictionaries_Locked(pMD, pOldLayout, pNewLayout); + WRAPPER_NO_CONTRACT; - // Finally, update the dictionary layout pointer after all dictionaries of instantiated methods have expanded, so that subsequent calls to - // DictionaryLayout::FindToken can use this. It is important to update the dictionary layout at the very last step, otherwise some other threads - // can start using newly added dictionary layout slots on methods where the PerInstInfo hasn't expanded yet, and cause runtime failures. - pMD->AsInstantiatedMethodDesc()->IMD_SetDictionaryLayout(pNewLayout); + DWORD cbSig; + BYTE * pSig = (BYTE *)pSigBuilder->GetSignature(&cbSig); - return TRUE; -#else - pResult->signature = pSigBuilder == NULL ? pSig : CreateSignatureWithSlotData(pSigBuilder, pAllocator, 0); - return FALSE; -#endif - } + WORD slotDummy; + return FindTokenWorker(pAllocator, numGenericArgs, pDictLayout, pResult, pSigBuilder, pSig, cbSig, nFirstOffset, signatureSource, &slotDummy); } /* static */ -PVOID DictionaryLayout::CreateSignatureWithSlotData(SigBuilder* pSigBuilder, LoaderAllocator* pAllocator, WORD slot) +BOOL +DictionaryLayout::FindToken(LoaderAllocator * pAllocator, + DWORD numGenericArgs, + DictionaryLayout * pDictLayout, + CORINFO_RUNTIME_LOOKUP * pResult, + BYTE * signature, + int nFirstOffset, + DictionaryEntrySignatureSource signatureSource, + WORD * pSlotOut) { - CONTRACTL - { - STANDARD_VM_CHECK; - PRECONDITION(CheckPointer(pSigBuilder) && CheckPointer(pAllocator)); - } - CONTRACTL_END - - pSigBuilder->AppendData(slot); - - DWORD cbNewSig; - PVOID pNewSig = pSigBuilder->GetSignature(&cbNewSig); - - PVOID pResultSignature = pAllocator->GetLowFrequencyHeap()->AllocMem(S_SIZE_T(cbNewSig)); - _ASSERT(pResultSignature != NULL); + WRAPPER_NO_CONTRACT; - memcpy(pResultSignature, pNewSig, cbNewSig); - - return pResultSignature; + return FindTokenWorker(pAllocator, numGenericArgs, pDictLayout, pResult, NULL, signature, -1, nFirstOffset, signatureSource, pSlotOut); } - #endif //!DACCESS_COMPILE //--------------------------------------------------------------------------------------- @@ -498,13 +347,21 @@ DictionaryLayout::GetObjectSize() //--------------------------------------------------------------------------------------- // // Save the dictionary layout for prejitting -// -void -DictionaryLayout::Save(DataImage * image) +// +void +DictionaryLayout::Save( + DataImage * image) { STANDARD_VM_CONTRACT; - image->StoreStructure(this, GetObjectSize(), DataImage::ITEM_DICTIONARY_LAYOUT); + DictionaryLayout *pDictLayout = this; + + while (pDictLayout) + { + image->StoreStructure(pDictLayout, pDictLayout->GetObjectSize(), DataImage::ITEM_DICTIONARY_LAYOUT); + pDictLayout = pDictLayout->m_pNext; + } + } //--------------------------------------------------------------------------------------- @@ -521,10 +378,15 @@ DictionaryLayout::Trim() } CONTRACTL_END; + // Only the last bucket in the chain may have unused entries + DictionaryLayout *pDictLayout = this; + while (pDictLayout->m_pNext) + pDictLayout = pDictLayout->m_pNext; + // Trim down the size to what's actually used - DWORD dwSlots = GetNumUsedSlots(); + DWORD dwSlots = pDictLayout->GetNumUsedSlots(); _ASSERTE(FitsIn(dwSlots)); - m_numSlots = static_cast(dwSlots); + pDictLayout->m_numSlots = static_cast(dwSlots); } @@ -539,14 +401,21 @@ DictionaryLayout::Fixup( { STANDARD_VM_CONTRACT; - for (DWORD i = 0; i < m_numSlots; i++) + DictionaryLayout *pDictLayout = this; + + while (pDictLayout) { - PVOID signature = m_slots[i].m_signature; - if (signature != NULL) + for (DWORD i = 0; i < pDictLayout->m_numSlots; i++) { - image->FixupFieldToNode(this, (BYTE *)&m_slots[i].m_signature - (BYTE *)this, - image->GetGenericSignature(signature, fMethod)); + PVOID signature = pDictLayout->m_slots[i].m_signature; + if (signature != NULL) + { + image->FixupFieldToNode(pDictLayout, (BYTE *)&pDictLayout->m_slots[i].m_signature - (BYTE *)pDictLayout, + image->GetGenericSignature(signature, fMethod)); + } } + image->FixupPointerField(pDictLayout, offsetof(DictionaryLayout, m_pNext)); + pDictLayout = pDictLayout->m_pNext; } } @@ -789,6 +658,7 @@ Dictionary::PopulateEntry( } CONTRACTL_END; CORINFO_GENERIC_HANDLE result = NULL; + Dictionary * pDictionary = NULL; *ppSlot = NULL; bool isReadyToRunModule = (pModule != NULL && pModule->IsReadyToRun()); @@ -879,6 +749,7 @@ Dictionary::PopulateEntry( // prepare for every possible derived type of the type containing the method). So instead we have to locate the exactly // instantiated (non-shared) super-type of the class passed in. + pDictionary = pMT->GetDictionary(); ULONG dictionaryIndex = 0; @@ -890,15 +761,13 @@ Dictionary::PopulateEntry( { IfFailThrow(ptr.GetData(&dictionaryIndex)); } - -#if _DEBUG - // Lock is needed because dictionary pointers can get updated during dictionary size expansion - CrstHolder ch(&SystemDomain::SystemModule()->m_DictionaryCrst); // MethodTable is expected to be normalized - Dictionary* pDictionary = pMT->GetDictionary(); _ASSERTE(pDictionary == pMT->GetPerInstInfo()[dictionaryIndex].GetValueMaybeNull()); -#endif + } + else + { + pDictionary = pMD->GetMethodDictionary(); } { @@ -1376,18 +1245,6 @@ Dictionary::PopulateEntry( if ((slotIndex != 0) && !IsCompilationProcess()) { - // Lock is needed because dictionary pointers can get updated during dictionary size expansion - CrstHolder ch(&SystemDomain::SystemModule()->m_DictionaryCrst); - -#if !defined(CROSSGEN_COMPILE) && defined(_DEBUG) - if (pMT != NULL) - pMT->GetModule()->EnsureTypeRecorded(pMT); - else - pMD->GetModule()->EnsureMethodRecorded(pMD); -#endif - - Dictionary* pDictionary = pMT != NULL ? pMT->GetDictionary() : pMD->GetMethodDictionary(); - *(pDictionary->GetSlotAddr(0, slotIndex)) = result; *ppSlot = pDictionary->GetSlotAddr(0, slotIndex); } diff --git a/src/vm/genericdict.h b/src/vm/genericdict.h index b3e7d73ba87f..44993886bbdf 100644 --- a/src/vm/genericdict.h +++ b/src/vm/genericdict.h @@ -92,13 +92,6 @@ typedef DPTR(DictionaryEntryLayout) PTR_DictionaryEntryLayout; class DictionaryLayout; typedef DPTR(DictionaryLayout) PTR_DictionaryLayout; -// Number of slots to initially allocate in a generic method dictionary layout. -#if _DEBUG -#define NUM_DICTIONARY_SLOTS 1 // Smaller number to stress the dictionary expansion logic -#else -#define NUM_DICTIONARY_SLOTS 4 -#endif - // The type of dictionary layouts. We don't include the number of type // arguments as this is obtained elsewhere class DictionaryLayout @@ -108,63 +101,51 @@ class DictionaryLayout friend class NativeImageDumper; #endif private: + // Next bucket of slots (only used to track entries that won't fit in the dictionary) + DictionaryLayout* m_pNext; + // Number of non-type-argument slots in this bucket WORD m_numSlots; // m_numSlots of these DictionaryEntryLayout m_slots[1]; - static BOOL FindTokenWorker(LoaderAllocator* pAllocator, - DWORD numGenericArgs, - DictionaryLayout* pDictLayout, - SigBuilder* pSigBuilder, - BYTE* pSig, - DWORD cbSig, - int nFirstOffset, - DictionaryEntrySignatureSource signatureSource, - CORINFO_RUNTIME_LOOKUP* pResult, - WORD* pSlotOut, - DWORD scanFromSlot, - BOOL useEmptySlotIfFound); - - - static DictionaryLayout* ExpandDictionaryLayout(LoaderAllocator* pAllocator, - DictionaryLayout* pCurrentDictLayout, - DWORD numGenericArgs, - SigBuilder* pSigBuilder, - BYTE* pSig, - int nFirstOffset, - DictionaryEntrySignatureSource signatureSource, - CORINFO_RUNTIME_LOOKUP* pResult, - WORD* pSlotOut); - - static PVOID CreateSignatureWithSlotData(SigBuilder* pSigBuilder, LoaderAllocator* pAllocator, WORD slot); + static BOOL FindTokenWorker(LoaderAllocator *pAllocator, + DWORD numGenericArgs, + DictionaryLayout *pDictLayout, + CORINFO_RUNTIME_LOOKUP *pResult, + SigBuilder * pSigBuilder, + BYTE * pSig, + DWORD cbSig, + int nFirstOffset, + DictionaryEntrySignatureSource signatureSource, + WORD * pSlotOut); + public: // Create an initial dictionary layout with a single bucket containing numSlots slots static DictionaryLayout* Allocate(WORD numSlots, LoaderAllocator *pAllocator, AllocMemTracker *pamTracker); - // Bytes used for this dictionary, which might be stored inline in + // Bytes used for the first bucket of this dictionary, which might be stored inline in // another structure (e.g. MethodTable) - static DWORD GetDictionarySizeFromLayout(DWORD numGenericArgs, PTR_DictionaryLayout pDictLayout); - - static BOOL FindToken(MethodTable* pMT, - LoaderAllocator* pAllocator, - int nFirstOffset, - SigBuilder* pSigBuilder, - BYTE* pSig, - DictionaryEntrySignatureSource signatureSource, - CORINFO_RUNTIME_LOOKUP* pResult, - WORD* pSlotOut); - - static BOOL FindToken(MethodDesc* pMD, - LoaderAllocator* pAllocator, - int nFirstOffset, - SigBuilder* pSigBuilder, - BYTE* pSig, - DictionaryEntrySignatureSource signatureSource, - CORINFO_RUNTIME_LOOKUP* pResult, - WORD* pSlotOut); + static DWORD GetFirstDictionaryBucketSize(DWORD numGenericArgs, PTR_DictionaryLayout pDictLayout); + + static BOOL FindToken(LoaderAllocator *pAllocator, + DWORD numGenericArgs, + DictionaryLayout *pDictLayout, + CORINFO_RUNTIME_LOOKUP *pResult, + SigBuilder * pSigBuilder, + int nFirstOffset, + DictionaryEntrySignatureSource signatureSource); + + static BOOL FindToken(LoaderAllocator * pAllocator, + DWORD numGenericArgs, + DictionaryLayout * pDictLayout, + CORINFO_RUNTIME_LOOKUP * pResult, + BYTE * signature, + int nFirstOffset, + DictionaryEntrySignatureSource signatureSource, + WORD * pSlotOut); DWORD GetMaxSlots(); DWORD GetNumUsedSlots(); @@ -177,6 +158,7 @@ class DictionaryLayout dac_cast(this) + offsetof(DictionaryLayout, m_slots) + sizeof(DictionaryEntryLayout) * i); } + DictionaryLayout* GetNextLayout() { LIMITED_METHOD_CONTRACT; return m_pNext; } #ifdef FEATURE_PREJIT DWORD GetObjectSize(); diff --git a/src/vm/generics.cpp b/src/vm/generics.cpp index 986e2d546e44..c018f8a8a7b1 100644 --- a/src/vm/generics.cpp +++ b/src/vm/generics.cpp @@ -278,11 +278,6 @@ ClassLoader::CreateTypeHandleForNonCanonicalGenericInstantiation( DWORD cbPerInst = sizeof(GenericsDictInfo) + pOldMT->GetPerInstInfoSize(); // Finally we need space for the instantiation/dictionary for this type - // Note that this is an unsafe operation because it uses the dictionary layout to compute the size needed, - // and the dictionary layout can be updated by other threads during a dictionary size expansion. To account for - // this rare race condition, right before registering this type for dictionary expansions, we will check that its - // dictionary has enough slots to match its dictionary layout if it got updated. - // See: Module::RecordTypeForDictionaryExpansion_Locked. DWORD cbInstAndDict = pOldMT->GetInstAndDictSize(); // Allocate from the high frequence heap of the correct domain @@ -493,13 +488,6 @@ ClassLoader::CreateTypeHandleForNonCanonicalGenericInstantiation( pInstDest[iArg] = inst[iArg]; } - if (cbInstAndDict != 0 && pOldMT->GetClass()->GetDictionaryLayout() != NULL && pOldMT->GetClass()->GetDictionaryLayout()->GetMaxSlots() > 0) - { - ULONG_PTR* pDictionarySlots = (ULONG_PTR*)pMT->GetPerInstInfo()[pOldMT->GetNumDicts() - 1].GetValue(); - ULONG_PTR* pSizeSlot = pDictionarySlots + ntypars; - *pSizeSlot = cbInstAndDict; - } - // Copy interface map across InterfaceInfo_t * pInterfaceMap = (InterfaceInfo_t *)(pMemory + cbMT + cbOptional + (fHasDynamicInterfaceMap ? sizeof(DWORD_PTR) : 0)); diff --git a/src/vm/genmeth.cpp b/src/vm/genmeth.cpp index 1d48618bb600..3eb3ac1bebbb 100644 --- a/src/vm/genmeth.cpp +++ b/src/vm/genmeth.cpp @@ -63,6 +63,7 @@ // should be the normalized representative genericMethodArgs (see typehandle.h) // + // Helper method that creates a method-desc off a template method desc static MethodDesc* CreateMethodDesc(LoaderAllocator *pAllocator, MethodTable *pMT, @@ -296,8 +297,7 @@ InstantiatedMethodDesc::NewInstantiatedMethodDesc(MethodTable *pExactMT, MethodDesc* pGenericMDescInRepMT, MethodDesc* pWrappedMD, Instantiation methodInst, - BOOL getWrappedCode, - BOOL recordForDictionaryExpansion) + BOOL getWrappedCode) { CONTRACT(InstantiatedMethodDesc*) { @@ -373,41 +373,28 @@ InstantiatedMethodDesc::NewInstantiatedMethodDesc(MethodTable *pExactMT, { if (pWrappedMD->IsSharedByGenericMethodInstantiations()) { - // It is ok to not take a lock here while reading the dictionary layout pointer. This is because - // when we reach the point of registering the newly created MethodDesc, we take the lock and - // check if the dictionary layout was expanded, and if so, we expand the dictionary of the method - // before recording it for future dictionary expansions and publishing it. pDL = pWrappedMD->AsInstantiatedMethodDesc()->GetDictLayoutRaw(); } } else if (getWrappedCode) { - pDL = DictionaryLayout::Allocate(NUM_DICTIONARY_SLOTS, pAllocator, &amt); -#ifdef _DEBUG + // 4 seems like a good number + pDL = DictionaryLayout::Allocate(4, pAllocator, &amt); +#ifdef _DEBUG { SString name; TypeString::AppendMethodDebug(name, pGenericMDescInRepMT); LOG((LF_JIT, LL_INFO1000, "GENERICS: Created new dictionary layout for dictionary of size %d for %S\n", - DictionaryLayout::GetDictionarySizeFromLayout(pGenericMDescInRepMT->GetNumGenericMethodArgs(), pDL), name.GetUnicode())); + DictionaryLayout::GetFirstDictionaryBucketSize(pGenericMDescInRepMT->GetNumGenericMethodArgs(), pDL), name.GetUnicode())); } #endif // _DEBUG } // Allocate space for the instantiation and dictionary - infoSize = DictionaryLayout::GetDictionarySizeFromLayout(methodInst.GetNumArgs(), pDL); - pInstOrPerInstInfo = (TypeHandle*)(void*)amt.Track(pAllocator->GetHighFrequencyHeap()->AllocMem(S_SIZE_T(infoSize))); + infoSize = DictionaryLayout::GetFirstDictionaryBucketSize(methodInst.GetNumArgs(), pDL); + pInstOrPerInstInfo = (TypeHandle *) (void*) amt.Track(pAllocator->GetHighFrequencyHeap()->AllocMem(S_SIZE_T(infoSize))); for (DWORD i = 0; i < methodInst.GetNumArgs(); i++) pInstOrPerInstInfo[i] = methodInst[i]; - - if (pDL != NULL && pDL->GetMaxSlots() > 0) - { - // Has to be at least larger than the first slots containing the instantiation arguments, - // and the slot with size information. Otherwise, we shouldn't really have a size slot - _ASSERTE(infoSize > (sizeof(TypeHandle*) * methodInst.GetNumArgs() + sizeof(ULONG_PTR*))); - - ULONG_PTR* pDictSizeSlot = ((ULONG_PTR*)pInstOrPerInstInfo) + methodInst.GetNumArgs(); - *pDictSizeSlot = infoSize; - } } BOOL forComInterop = FALSE; @@ -495,8 +482,8 @@ InstantiatedMethodDesc::NewInstantiatedMethodDesc(MethodTable *pExactMT, const char* verb = "Created"; if (pWrappedMD) LOG((LF_CLASSLOADER, LL_INFO1000, - "GENERICS: %s instantiating-stub method desc %s with dictionary size %d\n", - verb, pDebugNameUTF8, infoSize)); + "GENERICS: %s instantiating-stub method desc %s with dictionary size %d\n", + verb, pDebugNameUTF8, infoSize)); else LOG((LF_CLASSLOADER, LL_INFO1000, "GENERICS: %s instantiated method desc %s\n", @@ -519,15 +506,6 @@ InstantiatedMethodDesc::NewInstantiatedMethodDesc(MethodTable *pExactMT, // Verify that we are not creating redundant MethodDescs _ASSERTE(!pNewMD->IsTightlyBoundToMethodTable()); -#ifndef CROSSGEN_COMPILE - if (recordForDictionaryExpansion && pNewMD->HasMethodInstantiation()) - { - // Recording needs to happen before the MD gets published to the hashtable of InstantiatedMethodDescs - CrstHolder ch(&SystemDomain::SystemModule()->m_DictionaryCrst); - pNewMD->GetModule()->RecordMethodForDictionaryExpansion_Locked(pNewMD); - } -#endif - // The method desc is fully set up; now add to the table InstMethodHashTable* pTable = pExactMDLoaderModule->GetInstMethodHashTable(); pTable->InsertMethodDesc(pNewMD); @@ -573,7 +551,6 @@ InstantiatedMethodDesc::FindOrCreateExactClassMethod(MethodTable *pExactMT, pCanonicalMD, pCanonicalMD, Instantiation(), - FALSE, FALSE); } @@ -1173,8 +1150,7 @@ MethodDesc::FindOrCreateAssociatedMethodDesc(MethodDesc* pDefMD, pMDescInCanonMT, NULL, Instantiation(repInst, methodInst.GetNumArgs()), - TRUE, - FALSE); + TRUE); } } else if (getWrappedThenStub) @@ -1209,8 +1185,7 @@ MethodDesc::FindOrCreateAssociatedMethodDesc(MethodDesc* pDefMD, pMDescInCanonMT, pWrappedMD, methodInst, - FALSE, - TRUE); + FALSE); } } else @@ -1235,7 +1210,6 @@ MethodDesc::FindOrCreateAssociatedMethodDesc(MethodDesc* pDefMD, pMDescInCanonMT, NULL, methodInst, - FALSE, FALSE); } } @@ -1724,19 +1698,4 @@ BOOL MethodDesc::SatisfiesMethodConstraints(TypeHandle thParent, BOOL fThrowIfNo return TRUE; } -DWORD InstantiatedMethodDesc::GetDictionarySlotsSize() -{ - CONTRACTL - { - PRECONDITION(SystemDomain::SystemModule()->m_DictionaryCrst.OwnedByCurrentThread()); - } - CONTRACTL_END - - ULONG_PTR* pDictionarySlots = (ULONG_PTR*)IMD_GetMethodDictionary(); - if (pDictionarySlots == NULL) - return 0; - ULONG_PTR* pSizeSlot = pDictionarySlots + m_wNumGenericArgs; - return (DWORD)(*pSizeSlot); -} - #endif // !DACCESS_COMPILE diff --git a/src/vm/jitinterface.cpp b/src/vm/jitinterface.cpp index 954815812afd..654cfef9a191 100644 --- a/src/vm/jitinterface.cpp +++ b/src/vm/jitinterface.cpp @@ -3488,15 +3488,13 @@ void CEEInfo::ComputeRuntimeLookupForSharedGenericToken(DictionaryEntryKind entr DictionaryEntrySignatureSource signatureSource = (IsCompilationProcess() ? FromZapImage : FromJIT); - WORD dummySlot; - // It's a method dictionary lookup if (pResultLookup->lookupKind.runtimeLookupKind == CORINFO_LOOKUP_METHODPARAM) { _ASSERTE(pContextMD != NULL); _ASSERTE(pContextMD->HasMethodInstantiation()); - if (DictionaryLayout::FindToken(pContextMD, pContextMD->GetLoaderAllocator(), 1, &sigBuilder, NULL, signatureSource, pResult, &dummySlot)) + if (DictionaryLayout::FindToken(pContextMD->GetLoaderAllocator(), pContextMD->GetNumGenericMethodArgs(), pContextMD->GetDictionaryLayout(), pResult, &sigBuilder, 1, signatureSource)) { pResult->testForNull = 1; pResult->testForFixup = 0; @@ -3514,7 +3512,7 @@ void CEEInfo::ComputeRuntimeLookupForSharedGenericToken(DictionaryEntryKind entr // It's a class dictionary lookup (CORINFO_LOOKUP_CLASSPARAM or CORINFO_LOOKUP_THISOBJ) else { - if (DictionaryLayout::FindToken(pContextMT, pContextMT->GetLoaderAllocator(), 2, &sigBuilder, NULL, signatureSource, pResult, &dummySlot)) + if (DictionaryLayout::FindToken(pContextMT->GetLoaderAllocator(), pContextMT->GetNumGenericArgs(), pContextMT->GetClass()->GetDictionaryLayout(), pResult, &sigBuilder, 2, signatureSource)) { pResult->testForNull = 1; pResult->testForFixup = 0; diff --git a/src/vm/method.cpp b/src/vm/method.cpp index af3b3ca1ba06..b36c78c5422f 100644 --- a/src/vm/method.cpp +++ b/src/vm/method.cpp @@ -2642,10 +2642,10 @@ void MethodDesc::Save(DataImage *image) } } } - + if (GetMethodDictionary()) { - DWORD cBytes = DictionaryLayout::GetDictionarySizeFromLayout(GetNumGenericMethodArgs(), GetDictionaryLayout()); + DWORD cBytes = DictionaryLayout::GetFirstDictionaryBucketSize(GetNumGenericMethodArgs(), GetDictionaryLayout()); void* pBytes = GetMethodDictionary()->AsPtr(); LOG((LF_ZAP, LL_INFO10000, " MethodDesc::Save dictionary size %d\n", cBytes)); diff --git a/src/vm/method.hpp b/src/vm/method.hpp index 35f97143a5a9..377b8996667e 100644 --- a/src/vm/method.hpp +++ b/src/vm/method.hpp @@ -500,8 +500,6 @@ class MethodDesc // Return a pointer to the method dictionary for an instantiated generic method // The initial slots in a method dictionary are the type arguments themselves // Return NULL if not an instantiated method - // These APIs are not multi-threaded safe: the dictionary and dictionary layout pointers - // can be updated by other threads during a dictionary size expansion. Dictionary* GetMethodDictionary(); DictionaryLayout* GetDictionaryLayout(); @@ -3470,11 +3468,7 @@ class InstantiatedMethodDesc : public MethodDesc Instantiation IMD_GetMethodInstantiation() { LIMITED_METHOD_DAC_CONTRACT; - - // No lock needed here. This is considered a safe operation here because in the case of a generic dictionary - // expansion, the values of the old dictionary slots are copied to the newly allocated dictionary, and the old - // dictionary is kept around, so whether IMD_GetMethodDictionary returns the new or old dictionaries, the - // values of the instantiation arguments will always be the same. + return Instantiation(IMD_GetMethodDictionary()->GetInstantiation(), m_wNumGenericArgs); } @@ -3485,10 +3479,6 @@ class InstantiatedMethodDesc : public MethodDesc return ReadPointerMaybeNull(this, &InstantiatedMethodDesc::m_pPerInstInfo); } -#ifndef DACCESS_COMPILE - DWORD GetDictionarySlotsSize(); -#endif - PTR_Dictionary IMD_GetMethodDictionaryNonNull() { LIMITED_METHOD_DAC_CONTRACT; @@ -3584,25 +3574,12 @@ class InstantiatedMethodDesc : public MethodDesc InstantiatedMethodDesc* pIMD = IMD_GetWrappedMethodDesc()->AsInstantiatedMethodDesc(); return pIMD->m_pDictLayout.GetValueMaybeNull(); } - else if (IMD_IsSharedByGenericMethodInstantiations()) + else + if (IMD_IsSharedByGenericMethodInstantiations()) return m_pDictLayout.GetValueMaybeNull(); else return NULL; } - - void IMD_SetDictionaryLayout(DictionaryLayout* pNewLayout) - { - WRAPPER_NO_CONTRACT; - if (IMD_IsWrapperStubWithInstantiations() && IMD_HasMethodInstantiation()) - { - InstantiatedMethodDesc* pIMD = IMD_GetWrappedMethodDesc()->AsInstantiatedMethodDesc(); - pIMD->m_pDictLayout.SetValueMaybeNull(pNewLayout); - } - else if (IMD_IsSharedByGenericMethodInstantiations()) - { - m_pDictLayout.SetValueMaybeNull(pNewLayout); - } - } #endif // !DACCESS_COMPILE // Setup the IMD as shared code @@ -3690,8 +3667,7 @@ class InstantiatedMethodDesc : public MethodDesc MethodDesc* pGenericMDescInRepMT, MethodDesc* pSharedMDescForStub, Instantiation methodInst, - BOOL getSharedNotStub, - BOOL recordForDictionaryExpansion); + BOOL getSharedNotStub); }; diff --git a/src/vm/methodtable.cpp b/src/vm/methodtable.cpp index 68a07c2ca8f6..95c6ced83e42 100644 --- a/src/vm/methodtable.cpp +++ b/src/vm/methodtable.cpp @@ -475,25 +475,6 @@ void MethodTable::SetModule(Module * pModule) _ASSERTE(GetModule() == pModule); } - -DWORD MethodTable::GetDictionarySlotsSize() -{ - CONTRACTL - { - PRECONDITION(SystemDomain::SystemModule()->m_DictionaryCrst.OwnedByCurrentThread()); - } - CONTRACTL_END - - if (!HasPerInstInfo() || GetGenericsDictInfo()->m_wNumTyPars == 0 || GetClass()->GetDictionaryLayout() == NULL) - return 0; - - if (GetClass()->GetDictionaryLayout()->GetMaxSlots() == 0) - return 0; - - ULONG_PTR* pDictionarySlots = (ULONG_PTR*)GetPerInstInfo()[GetGenericsDictInfo()->m_wNumDicts - 1].GetValue(); - ULONG_PTR* pSizeSlot = pDictionarySlots + GetGenericsDictInfo()->m_wNumTyPars; - return (DWORD)(*pSizeSlot); -} #endif // DACCESS_COMPILE //========================================================================================== diff --git a/src/vm/methodtable.h b/src/vm/methodtable.h index 3bad40b07ffd..fa62981d624d 100644 --- a/src/vm/methodtable.h +++ b/src/vm/methodtable.h @@ -2946,9 +2946,6 @@ class MethodTable pInfo->m_wNumDicts = numDicts; pInfo->m_wNumTyPars = numTyPars; } - - DWORD GetDictionarySlotsSize(); - #endif // !DACCESS_COMPILE PTR_GenericsDictInfo GetGenericsDictInfo() { @@ -2960,8 +2957,6 @@ class MethodTable // Get a pointer to the dictionary for this instantiated type // (The instantiation is stored in the initial slots of the dictionary) // If not instantiated, return NULL - // This operation is not multi-threaded safe: other thread can update the - // dictionary pointer during a dictionary size expansion. PTR_Dictionary GetDictionary(); #ifdef FEATURE_PREJIT @@ -4066,10 +4061,7 @@ public : inline DWORD GetInterfaceMapSize(); // The instantiation/dictionary comes at the end of the MethodTable after - // the interface map. - // This operation is not multi-threaded safe: it uses the dictionary layout to compute - // the size, and the dictionary layout can be updated by other threads in the case of a - // generic dictionary size expansion. + // the interface map. inline DWORD GetInstAndDictSize(); private: diff --git a/src/vm/methodtable.inl b/src/vm/methodtable.inl index 656a0994b9a6..e3fd0a8d8ffc 100644 --- a/src/vm/methodtable.inl +++ b/src/vm/methodtable.inl @@ -1261,7 +1261,7 @@ inline DWORD MethodTable::GetInstAndDictSize() if (!HasInstantiation()) return 0; else - return DictionaryLayout::GetDictionarySizeFromLayout(GetNumGenericArgs(), GetClass()->GetDictionaryLayout()); + return DictionaryLayout::GetFirstDictionaryBucketSize(GetNumGenericArgs(), GetClass()->GetDictionaryLayout()); } //========================================================================================== diff --git a/src/vm/methodtablebuilder.cpp b/src/vm/methodtablebuilder.cpp index 253c596c8b34..82518eafdcb7 100644 --- a/src/vm/methodtablebuilder.cpp +++ b/src/vm/methodtablebuilder.cpp @@ -10163,7 +10163,7 @@ MethodTableBuilder::SetupMethodTable2( EEClass *pClass = GetHalfBakedClass(); DWORD cbDict = bmtGenerics->HasInstantiation() - ? DictionaryLayout::GetDictionarySizeFromLayout( + ? DictionaryLayout::GetFirstDictionaryBucketSize( bmtGenerics->GetNumGenericArgs(), pClass->GetDictionaryLayout()) : 0; @@ -10461,13 +10461,6 @@ MethodTableBuilder::SetupMethodTable2( { pInstDest[j] = inst[j]; } - - if (pClass->GetDictionaryLayout() != NULL && pClass->GetDictionaryLayout()->GetMaxSlots() > 0) - { - ULONG_PTR* pDictionarySlots = (ULONG_PTR*)pMT->GetPerInstInfo()[bmtGenerics->numDicts - 1].GetValue(); - ULONG_PTR* pSizeSlot = pDictionarySlots + bmtGenerics->GetNumGenericArgs(); - *pSizeSlot = cbDict; - } } CorElementType normalizedType = ELEMENT_TYPE_CLASS; diff --git a/src/vm/prestub.cpp b/src/vm/prestub.cpp index 612508f35556..509d3d315b58 100644 --- a/src/vm/prestub.cpp +++ b/src/vm/prestub.cpp @@ -2899,12 +2899,12 @@ void ProcessDynamicDictionaryLookup(TransitionBlock * pTransitionBlock // are used for the dictionary index, and the lower 16 bits for the slot number. *pDictionaryIndexAndSlot = (pContextMT == NULL ? 0 : pContextMT->GetNumDicts() - 1); *pDictionaryIndexAndSlot <<= 16; - + WORD dictionarySlot; if (kind == ENCODE_DICTIONARY_LOOKUP_METHOD) { - if (DictionaryLayout::FindToken(pContextMD, pModule->GetLoaderAllocator(), 1, NULL, (BYTE*)pBlobStart, FromReadyToRunImage, pResult, &dictionarySlot)) + if (DictionaryLayout::FindToken(pModule->GetLoaderAllocator(), numGenericArgs, pContextMD->GetDictionaryLayout(), pResult, (BYTE*)pBlobStart, 1, FromReadyToRunImage, &dictionarySlot)) { pResult->testForNull = 1; @@ -2923,7 +2923,7 @@ void ProcessDynamicDictionaryLookup(TransitionBlock * pTransitionBlock // It's a class dictionary lookup (CORINFO_LOOKUP_CLASSPARAM or CORINFO_LOOKUP_THISOBJ) else { - if (DictionaryLayout::FindToken(pContextMT, pModule->GetLoaderAllocator(), 2, NULL, (BYTE*)pBlobStart, FromReadyToRunImage, pResult, &dictionarySlot)) + if (DictionaryLayout::FindToken(pModule->GetLoaderAllocator(), numGenericArgs, pContextMT->GetClass()->GetDictionaryLayout(), pResult, (BYTE*)pBlobStart, 2, FromReadyToRunImage, &dictionarySlot)) { pResult->testForNull = 1;