Skip to content

Commit 234c8d4

Browse files
author
Fadi Hanna
committed
Original implementation of the dynamic dictionary expansion feature
1 parent 662817d commit 234c8d4

File tree

20 files changed

+1120
-239
lines changed

20 files changed

+1120
-239
lines changed

docs/design/coreclr/botr/shared-generics.md

Lines changed: 185 additions & 0 deletions
Large diffs are not rendered by default.

src/coreclr/src/debug/daccess/nidump.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6992,7 +6992,7 @@ NativeImageDumper::DumpMethodTable( PTR_MethodTable mt, const char * name,
69926992
//if there is a layout, use it to compute
69936993
//the size, otherwise there is just the one
69946994
//entry.
6995-
DictionaryLayout::GetFirstDictionaryBucketSize(mt->GetNumGenericArgs(), layout),
6995+
DictionaryLayout::GetDictionarySizeFromLayout(mt->GetNumGenericArgs(), layout),
69966996
METHODTABLES );
69976997

69986998
DisplayStartArrayWithOffset( m_pEntries, NULL, Dictionary,
@@ -8018,7 +8018,7 @@ void NativeImageDumper::DumpMethodDesc( PTR_MethodDesc md, PTR_Module module )
80188018
{
80198019
PTR_DictionaryLayout layout(wrapped->IsSharedByGenericMethodInstantiations()
80208020
? dac_cast<TADDR>(wrapped->GetDictLayoutRaw()) : NULL );
8021-
dictSize = DictionaryLayout::GetFirstDictionaryBucketSize(imd->GetNumGenericMethodArgs(),
8021+
dictSize = DictionaryLayout::GetDictionarySizeFromLayout(imd->GetNumGenericMethodArgs(),
80228022
layout);
80238023
}
80248024
}

src/coreclr/src/vm/appdomain.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2011,6 +2011,10 @@ void SystemDomain::LoadBaseSystemClasses()
20112011
g_pDelegateClass = MscorlibBinder::GetClass(CLASS__DELEGATE);
20122012
g_pMulticastDelegateClass = MscorlibBinder::GetClass(CLASS__MULTICAST_DELEGATE);
20132013

2014+
#ifndef CROSSGEN_COMPILE
2015+
CrossLoaderAllocatorHashSetup::EnsureTypesLoaded();
2016+
#endif
2017+
20142018
// used by IsImplicitInterfaceOfSZArray
20152019
MscorlibBinder::GetClass(CLASS__IENUMERABLEGENERIC);
20162020
MscorlibBinder::GetClass(CLASS__ICOLLECTIONGENERIC);
@@ -2026,10 +2030,6 @@ void SystemDomain::LoadBaseSystemClasses()
20262030
g_pUtf8StringClass = MscorlibBinder::GetClass(CLASS__UTF8_STRING);
20272031
#endif // FEATURE_UTF8STRING
20282032

2029-
#ifndef CROSSGEN_COMPILE
2030-
CrossLoaderAllocatorHashSetup::EnsureTypesLoaded();
2031-
#endif
2032-
20332033
#ifndef CROSSGEN_COMPILE
20342034
ECall::PopulateManagedStringConstructors();
20352035
#endif // CROSSGEN_COMPILE

src/coreclr/src/vm/ceeload.cpp

Lines changed: 326 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)