@@ -556,6 +556,7 @@ void Module::Initialize(AllocMemTracker *pamTracker, LPCWSTR szName)
556556 m_FixupCrst.Init (CrstModuleFixup, (CrstFlags)(CRST_HOST_BREAKABLE|CRST_REENTRANCY));
557557 m_InstMethodHashTableCrst.Init (CrstInstMethodHashTable, CRST_REENTRANCY);
558558 m_ISymUnmanagedReaderCrst.Init (CrstISymUnmanagedReader, CRST_DEBUGGER_THREAD);
559+ m_DictionaryCrst.Init (CrstDomainLocalBlock);
559560
560561 if (!m_file->HasNativeImage ())
561562 {
@@ -687,8 +688,12 @@ void Module::Initialize(AllocMemTracker *pamTracker, LPCWSTR szName)
687688 }
688689#endif // defined (PROFILING_SUPPORTED) &&!defined(DACCESS_COMPILE) && !defined(CROSSGEN_COMPILE)
689690
690- LOG ((LF_CLASSLOADER, LL_INFO10, " Loaded pModule: \" %ws\" .\n " , GetDebugName ()));
691+ #ifndef CROSSGEN_COMPILE
692+ m_dynamicSlotsHashForTypes.Init (GetLoaderAllocator ());
693+ m_dynamicSlotsHashForMethods.Init (GetLoaderAllocator ());
694+ #endif
691695
696+ LOG ((LF_CLASSLOADER, LL_INFO10, " Loaded pModule: \" %ws\" .\n " , GetDebugName ()));
692697}
693698
694699#endif // DACCESS_COMPILE
@@ -13212,6 +13217,326 @@ void ReflectionModule::ResumeMetadataCapture()
1321213217 CaptureModuleMetaDataToMemory ();
1321313218}
1321413219
13220+ TypeHandle* AllocateNewMethodDictionaryForExpansion (InstantiatedMethodDesc* pIMD, DWORD cbSize)
13221+ {
13222+ TypeHandle* pInstOrPerInstInfo = (TypeHandle*)(void *)pIMD->GetLoaderAllocator ()->GetHighFrequencyHeap ()->AllocMem (S_SIZE_T (cbSize + sizeof (void *)));
13223+ ZeroMemory (pInstOrPerInstInfo, cbSize + sizeof (void *));
13224+
13225+ // Slot[-1] points at previous dictionary to help with diagnostics when investigating crashes
13226+ *(byte**)pInstOrPerInstInfo = (byte*)pIMD->m_pPerInstInfo .GetValue () + 1 ;
13227+ pInstOrPerInstInfo++;
13228+
13229+ // Copy old dictionary entry contents
13230+ memcpy (pInstOrPerInstInfo, (const void *)pIMD->m_pPerInstInfo .GetValue (), pIMD->GetDictionarySlotsSize ());
13231+
13232+ ULONG_PTR* pSizeSlot = ((ULONG_PTR*)pInstOrPerInstInfo) + pIMD->GetNumGenericMethodArgs ();
13233+ *pSizeSlot = cbSize;
13234+
13235+ return pInstOrPerInstInfo;
13236+ }
13237+
13238+ TypeHandle* AllocateNewTypeDictionaryForExpansion (MethodTable* pMT, DWORD cbSize)
13239+ {
13240+ TypeHandle* pInstOrPerInstInfo = (TypeHandle*)(void *)pMT->GetLoaderAllocator ()->GetHighFrequencyHeap ()->AllocMem (S_SIZE_T (cbSize + sizeof (void *)));
13241+ ZeroMemory (pInstOrPerInstInfo, cbSize + sizeof (void *));
13242+
13243+ // Slot[-1] points at previous dictionary to help with diagnostics when investigating crashes
13244+ *(byte**)pInstOrPerInstInfo = (byte*)pMT->GetPerInstInfo ()[pMT->GetNumDicts () - 1 ].GetValue () + 1 ;
13245+ pInstOrPerInstInfo++;
13246+
13247+ // Copy old dictionary entry contents
13248+ memcpy (pInstOrPerInstInfo, (const void *)pMT->GetPerInstInfo ()[pMT->GetNumDicts () - 1 ].GetValue (), pMT->GetDictionarySlotsSize ());
13249+
13250+ ULONG_PTR* pSizeSlot = ((ULONG_PTR*)pInstOrPerInstInfo) + pMT->GetNumGenericArgs ();
13251+ *pSizeSlot = cbSize;
13252+
13253+ return pInstOrPerInstInfo;
13254+ }
13255+
13256+ #ifdef _DEBUG
13257+ void Module::EnsureTypeRecorded (MethodTable* pMT)
13258+ {
13259+ _ASSERTE (SystemDomain::SystemModule ()->m_DictionaryCrst .OwnedByCurrentThread ());
13260+
13261+ BOOL typeExistsInHashtable = FALSE ;
13262+ auto lamda = [&typeExistsInHashtable, pMT](OBJECTREF obj, MethodTable* pMTKey, MethodTable* pMTValue)
13263+ {
13264+ typeExistsInHashtable = (pMT == pMTValue);
13265+ return pMT != pMTValue;
13266+ };
13267+
13268+ m_dynamicSlotsHashForTypes.VisitValuesOfKey (pMT->GetCanonicalMethodTable (), lamda);
13269+ _ASSERTE (typeExistsInHashtable);
13270+ }
13271+
13272+ void Module::EnsureMethodRecorded (MethodDesc* pMD)
13273+ {
13274+ _ASSERTE (SystemDomain::SystemModule ()->m_DictionaryCrst .OwnedByCurrentThread ());
13275+
13276+ BOOL methodExistsInHashtable = FALSE ;
13277+ auto lamda = [&methodExistsInHashtable, pMD](OBJECTREF obj, MethodDesc* pMDKey, MethodDesc* pMDValue)
13278+ {
13279+ methodExistsInHashtable = (pMD== pMDValue);
13280+ return pMD != pMDValue;
13281+ };
13282+
13283+ m_dynamicSlotsHashForMethods.VisitValuesOfKey (pMD->GetExistingWrappedMethodDesc (), lamda);
13284+ _ASSERTE (methodExistsInHashtable);
13285+ }
13286+ #endif
13287+
13288+ void Module::RecordTypeForDictionaryExpansion_Locked (MethodTable* pGenericParentMT, MethodTable* pDependencyMT)
13289+ {
13290+ CONTRACTL
13291+ {
13292+ GC_TRIGGERS;
13293+ PRECONDITION (CheckPointer (pGenericParentMT) && CheckPointer (pDependencyMT));
13294+ PRECONDITION (pGenericParentMT->HasInstantiation () && pGenericParentMT != pGenericParentMT->GetCanonicalMethodTable ());
13295+ PRECONDITION (SystemDomain::SystemModule ()->m_DictionaryCrst .OwnedByCurrentThread ());
13296+ }
13297+ CONTRACTL_END
13298+
13299+ DictionaryLayout* pDictLayout = pDependencyMT->GetClass ()->GetDictionaryLayout ();
13300+ if (pDictLayout != NULL && pDictLayout->GetMaxSlots () > 0 )
13301+ {
13302+ DWORD sizeFromDictLayout = DictionaryLayout::GetDictionarySizeFromLayout (pDependencyMT->GetNumGenericArgs (), pDictLayout);
13303+ if (pDependencyMT->GetDictionarySlotsSize () != sizeFromDictLayout)
13304+ {
13305+ _ASSERT (pDependencyMT->GetDictionarySlotsSize () < sizeFromDictLayout);
13306+
13307+ //
13308+ // Another thread got a chance to expand the dictionary layout and expand the dictionary slots of
13309+ // other types, but not for this one yet because we're still in the process of recording it for
13310+ // expansions.
13311+ // Expand the dictionary slots here before finally adding the type to the hashtable.
13312+ //
13313+
13314+ TypeHandle* pInstOrPerInstInfo = AllocateNewTypeDictionaryForExpansion (pDependencyMT, sizeFromDictLayout);
13315+
13316+ // Publish the new dictionary slots to the type.
13317+ TypeHandle** pPerInstInfo = (TypeHandle**)pDependencyMT->GetPerInstInfo ()->GetValuePtr ();
13318+ FastInterlockExchangePointer (pPerInstInfo + (pDependencyMT->GetNumDicts () - 1 ), pInstOrPerInstInfo);
13319+
13320+ FlushProcessWriteBuffers ();
13321+ }
13322+ }
13323+
13324+ GCX_COOP ();
13325+ m_dynamicSlotsHashForTypes.Add (pGenericParentMT->GetCanonicalMethodTable (), pDependencyMT, GetLoaderAllocator ());
13326+ }
13327+
13328+ void Module::RecordMethodForDictionaryExpansion_Locked (MethodDesc* pDependencyMD)
13329+ {
13330+ CONTRACTL
13331+ {
13332+ GC_TRIGGERS;
13333+ PRECONDITION (CheckPointer (pDependencyMD) && pDependencyMD->HasMethodInstantiation () && pDependencyMD->IsInstantiatingStub ());
13334+ PRECONDITION (pDependencyMD->GetDictionaryLayout () != NULL && pDependencyMD->GetDictionaryLayout ()->GetMaxSlots () > 0 );
13335+ PRECONDITION (SystemDomain::SystemModule ()->m_DictionaryCrst .OwnedByCurrentThread ());
13336+ }
13337+ CONTRACTL_END
13338+
13339+ DictionaryLayout* pDictLayout = pDependencyMD->GetDictionaryLayout ();
13340+ InstantiatedMethodDesc* pIMDDependency = pDependencyMD->AsInstantiatedMethodDesc ();
13341+
13342+ DWORD sizeFromDictLayout = DictionaryLayout::GetDictionarySizeFromLayout (pDependencyMD->GetNumGenericMethodArgs (), pDictLayout);
13343+ if (pIMDDependency->GetDictionarySlotsSize () != sizeFromDictLayout)
13344+ {
13345+ _ASSERT (pIMDDependency->GetDictionarySlotsSize () < sizeFromDictLayout);
13346+
13347+ //
13348+ // Another thread got a chance to expand the dictionary layout and expand the dictionary slots of
13349+ // other methods, but not for this one yet because we're still in the process of recording it for
13350+ // expansions.
13351+ // Expand the dictionary slots here before finally adding the method to the hashtable.
13352+ //
13353+
13354+ TypeHandle* pInstOrPerInstInfo = AllocateNewMethodDictionaryForExpansion (pIMDDependency, sizeFromDictLayout);
13355+
13356+ FastInterlockExchangePointer ((TypeHandle**)pIMDDependency->m_pPerInstInfo .GetValuePtr (), pInstOrPerInstInfo);
13357+
13358+ FlushProcessWriteBuffers ();
13359+ }
13360+
13361+ GCX_COOP ();
13362+
13363+ _ASSERTE (pDependencyMD->GetExistingWrappedMethodDesc () != NULL );
13364+ m_dynamicSlotsHashForMethods.Add (pDependencyMD->GetExistingWrappedMethodDesc (), pDependencyMD, GetLoaderAllocator ());
13365+ }
13366+
13367+ void Module::ExpandTypeDictionaries_Locked (MethodTable* pMT, DictionaryLayout* pOldLayout, DictionaryLayout* pNewLayout)
13368+ {
13369+ CONTRACTL
13370+ {
13371+ STANDARD_VM_CHECK;
13372+ INJECT_FAULT (ThrowOutOfMemory (););
13373+ PRECONDITION (CheckPointer (pOldLayout) && CheckPointer (pNewLayout));
13374+ PRECONDITION (CheckPointer (pMT) && pMT->HasInstantiation () && pMT->GetClass ()->GetDictionaryLayout () == pOldLayout);
13375+ PRECONDITION (SystemDomain::SystemModule ()->m_DictionaryCrst .OwnedByCurrentThread ());
13376+ }
13377+ CONTRACTL_END
13378+
13379+ GCX_COOP ();
13380+
13381+ MethodTable* pCanonMT = pMT->GetCanonicalMethodTable ();
13382+ DWORD oldInfoSize = DictionaryLayout::GetDictionarySizeFromLayout (pMT->GetNumGenericArgs (), pOldLayout);
13383+ DWORD newInfoSize = DictionaryLayout::GetDictionarySizeFromLayout (pMT->GetNumGenericArgs (), pNewLayout);
13384+
13385+ //
13386+ // Dictionary expansion for types needs to be done in multiple steps, given how derived types do not directly embed dictionaries
13387+ // from parent types, but instead reference them from directly from the parent types. Also, this is necessary to ensure correctness
13388+ // for lock-free read operations for dictionary slots:
13389+ // 1) Allocate new dictionaries for all instantiated types of the same typedef as the one being expanded.
13390+ // 2) After all allocations and initializations are completed, publish the dictionaries to the types in #1 after
13391+ // flushing write buffers
13392+ // 3) For all types that derive from #1, update the embedded dictinoary pointer to the newly allocated one.
13393+ //
13394+
13395+ struct NewDictionary
13396+ {
13397+ MethodTable* pMT;
13398+ TypeHandle* pDictSlots;
13399+ };
13400+ StackSArray<NewDictionary> dictionaryUpdates;
13401+
13402+ #ifdef _DEBUG
13403+ auto expandPerInstInfos = [oldInfoSize, newInfoSize, &dictionaryUpdates](OBJECTREF obj, MethodTable* pMTKey, MethodTable* pMTToUpdate)
13404+ #else
13405+ auto expandPerInstInfos = [newInfoSize, &dictionaryUpdates](OBJECTREF obj, MethodTable* pMTKey, MethodTable* pMTToUpdate)
13406+ #endif
13407+ {
13408+ if (!pMTToUpdate->HasSameTypeDefAs (pMTKey))
13409+ return true ;
13410+
13411+ _ASSERTE (pMTToUpdate != pMTToUpdate->GetCanonicalMethodTable () && pMTToUpdate->GetCanonicalMethodTable () == pMTKey);
13412+ _ASSERTE (pMTToUpdate->GetDictionarySlotsSize () == oldInfoSize);
13413+
13414+ TypeHandle* pInstOrPerInstInfo = AllocateNewTypeDictionaryForExpansion (pMTToUpdate, newInfoSize);
13415+
13416+ NewDictionary entry;
13417+ entry.pMT = pMTToUpdate;
13418+ entry.pDictSlots = pInstOrPerInstInfo;
13419+ dictionaryUpdates.Append (entry);
13420+
13421+ return true ; // Keep walking
13422+ };
13423+
13424+ m_dynamicSlotsHashForTypes.VisitValuesOfKey (pCanonMT, expandPerInstInfos);
13425+
13426+ // Flush write buffers to ensure new dictionaries are fully writted and initalized before publishing them
13427+ FlushProcessWriteBuffers ();
13428+
13429+ for (SArray<NewDictionary>::Iterator i = dictionaryUpdates.Begin (); i != dictionaryUpdates.End (); i++)
13430+ {
13431+ MethodTable* pMT = i->pMT ;
13432+ TypeHandle** pPerInstInfo = (TypeHandle**)pMT->GetPerInstInfo ()->GetValuePtr ();
13433+ FastInterlockExchangePointer (pPerInstInfo + (pMT->GetNumDicts () - 1 ), i->pDictSlots );
13434+ _ASSERTE (pMT->GetDictionarySlotsSize () == newInfoSize);
13435+ _ASSERTE ((TypeHandle*)pMT->GetPerInstInfo ()[pMT->GetNumDicts () - 1 ].GetValue () == i->pDictSlots );
13436+ }
13437+
13438+ auto updateDependentDicts = [](OBJECTREF obj, MethodTable* pMTKey, MethodTable* pMTToUpdate)
13439+ {
13440+ if (pMTToUpdate->HasSameTypeDefAs (pMTKey))
13441+ return true ;
13442+
13443+ MethodTable* pCurrentMT = pMTToUpdate->GetParentMethodTable ();
13444+ while (pCurrentMT)
13445+ {
13446+ if (pCurrentMT->HasSameTypeDefAs (pMTKey))
13447+ {
13448+ DWORD dictToUpdate = pCurrentMT->GetNumDicts () - 1 ;
13449+ Dictionary* pUpdatedParentDict = pCurrentMT->GetPerInstInfo ()[dictToUpdate].GetValue ();
13450+ TypeHandle** pPerInstInfo = (TypeHandle**)pMTToUpdate->GetPerInstInfo ()->GetValuePtr ();
13451+ FastInterlockExchangePointer (pPerInstInfo + dictToUpdate, (TypeHandle*)pUpdatedParentDict);
13452+ _ASSERTE (pMTToUpdate->GetPerInstInfo ()[dictToUpdate].GetValue () == pUpdatedParentDict);
13453+
13454+ return true ; // Keep walking
13455+ }
13456+ pCurrentMT = pCurrentMT->GetParentMethodTable ();
13457+ }
13458+
13459+ UNREACHABLE ();
13460+ };
13461+
13462+ m_dynamicSlotsHashForTypes.VisitValuesOfKey (pCanonMT, updateDependentDicts);
13463+
13464+ // Ensure no other thread uses old dictionary pointers
13465+ FlushProcessWriteBuffers ();
13466+ }
13467+
13468+ void Module::ExpandMethodDictionaries_Locked (MethodDesc* pMD, DictionaryLayout* pOldLayout, DictionaryLayout* pNewLayout)
13469+ {
13470+ CONTRACTL
13471+ {
13472+ STANDARD_VM_CHECK;
13473+ INJECT_FAULT (ThrowOutOfMemory (););
13474+ PRECONDITION (CheckPointer (pOldLayout) && CheckPointer (pNewLayout));
13475+ PRECONDITION (CheckPointer (pMD));
13476+ PRECONDITION (pMD->HasMethodInstantiation () && pMD->GetDictionaryLayout () == pOldLayout);
13477+ PRECONDITION (SystemDomain::SystemModule ()->m_DictionaryCrst .OwnedByCurrentThread ());
13478+ }
13479+ CONTRACTL_END
13480+
13481+ GCX_COOP ();
13482+
13483+ //
13484+ // Dictionary expansion for methods needs to be done in two steps to ensure correctness for lock-free read operations
13485+ // for dictionary slots:
13486+ // 1) Allocate new dictionaries for all instantiated methods sharing the same canonical form as the input method
13487+ // 2) After all allocations and initializations are completed, publish the dictionaries to the methods after
13488+ // flushing write buffers
13489+ //
13490+
13491+ MethodDesc* pCanonMD = pMD->IsInstantiatingStub () ? pMD->GetExistingWrappedMethodDesc () : pMD;
13492+ _ASSERTE (pCanonMD != NULL );
13493+ DWORD oldInfoSize = DictionaryLayout::GetDictionarySizeFromLayout (pMD->GetNumGenericMethodArgs (), pOldLayout);
13494+ DWORD newInfoSize = DictionaryLayout::GetDictionarySizeFromLayout (pMD->GetNumGenericMethodArgs (), pNewLayout);
13495+
13496+ struct NewDictionary
13497+ {
13498+ InstantiatedMethodDesc* pIMD;
13499+ TypeHandle* pDictSlots;
13500+ };
13501+ StackSArray<NewDictionary> dictionaryUpdates;
13502+
13503+ #ifdef _DEBUG
13504+ auto lambda = [oldInfoSize, newInfoSize, &dictionaryUpdates](OBJECTREF obj, MethodDesc* pMDKey, MethodDesc* pMDToUpdate)
13505+ #else
13506+ auto lambda = [newInfoSize, &dictionaryUpdates](OBJECTREF obj, MethodDesc* pMDKey, MethodDesc* pMDToUpdate)
13507+ #endif
13508+ {
13509+ // Update m_pPerInstInfo for the pMethodDesc being visited here
13510+ _ASSERTE (pMDToUpdate->IsInstantiatingStub () && pMDToUpdate->GetExistingWrappedMethodDesc () == pMDKey);
13511+
13512+ InstantiatedMethodDesc* pInstantiatedMD = pMDToUpdate->AsInstantiatedMethodDesc ();
13513+ _ASSERTE (pInstantiatedMD->GetDictionarySlotsSize () == oldInfoSize);
13514+
13515+ TypeHandle* pInstOrPerInstInfo = AllocateNewMethodDictionaryForExpansion (pInstantiatedMD, newInfoSize);
13516+
13517+ NewDictionary entry;
13518+ entry.pIMD = pInstantiatedMD;
13519+ entry.pDictSlots = pInstOrPerInstInfo;
13520+ dictionaryUpdates.Append (entry);
13521+
13522+ return true ; // Keep walking
13523+ };
13524+
13525+ m_dynamicSlotsHashForMethods.VisitValuesOfKey (pCanonMD, lambda);
13526+
13527+ // Flush write buffers to ensure new dictionaries are fully writted and initalized before publishing them
13528+ FlushProcessWriteBuffers ();
13529+
13530+ for (SArray<NewDictionary>::Iterator i = dictionaryUpdates.Begin (); i != dictionaryUpdates.End (); i++)
13531+ {
13532+ FastInterlockExchangePointer ((TypeHandle**)i->pIMD ->m_pPerInstInfo .GetValuePtr (), i->pDictSlots );
13533+ _ASSERTE ((TypeHandle*)i->pIMD ->m_pPerInstInfo .GetValue () == i->pDictSlots );
13534+ _ASSERTE (i->pIMD ->GetDictionarySlotsSize () == newInfoSize);
13535+ }
13536+
13537+ // Ensure no other thread uses old dictionary pointers
13538+ FlushProcessWriteBuffers ();
13539+ }
1321513540#endif // !CROSSGEN_COMPILE
1321613541
1321713542#endif // !DACCESS_COMPILE
0 commit comments