Skip to content

Commit a0b3e1d

Browse files
authored
Implement spawning static constructor for closed generic types (#3235)
***NO_CI***
1 parent 7b050bb commit a0b3e1d

File tree

6 files changed

+378
-13
lines changed

6 files changed

+378
-13
lines changed

src/CLR/Core/CLR_RT_HeapBlock_Delegate.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ HRESULT CLR_RT_HeapBlock_Delegate::CreateInstance(
5656
#endif
5757

5858
dlg->m_object.SetObjectReference(nullptr);
59+
dlg->m_genericTypeSpec.Clear();
5960

6061
#if defined(NANOCLR_APPDOMAINS)
6162
dlg->m_appDomain = g_CLR_RT_ExecutionEngine.GetCurrentAppDomain();

src/CLR/Core/Execution.cpp

Lines changed: 214 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -678,6 +678,7 @@ HRESULT CLR_RT_ExecutionEngine::Execute(wchar_t *entryPointArgs, int maxContextS
678678
NANOCLR_CHECK_HRESULT(WaitForDebugger());
679679

680680
// m_cctorThread is nullptr before call and inialized by the SpawnStaticConstructor
681+
// This will execute both non-generic and generic type static constructors
681682
SpawnStaticConstructor(m_cctorThread);
682683

683684
while (true)
@@ -809,6 +810,28 @@ void CLR_RT_ExecutionEngine::StaticConstructorTerminationCallback(void *arg)
809810
(void)arg;
810811

811812
NATIVE_PROFILE_CLR_CORE();
813+
814+
// If the completed .cctor was for a generic type, mark it as executed
815+
CLR_RT_HeapBlock_Delegate *dlg = g_CLR_RT_ExecutionEngine.m_cctorThread->m_dlg;
816+
if (dlg != nullptr && dlg->m_genericTypeSpec.data != 0)
817+
{
818+
// This was a generic type .cctor - compute hash and mark as executed
819+
CLR_RT_TypeSpec_Instance genericTypeInstance{};
820+
if (genericTypeInstance.InitializeFromIndex(dlg->m_genericTypeSpec))
821+
{
822+
CLR_UINT32 hash = g_CLR_RT_TypeSystem.ComputeHashForClosedGenericType(genericTypeInstance);
823+
CLR_RT_GenericCctorExecutionRecord *record =
824+
g_CLR_RT_TypeSystem.FindOrCreateGenericCctorRecord(hash, nullptr);
825+
826+
if (record != nullptr)
827+
{
828+
// Clear scheduled flag and set executed flag
829+
record->m_flags &= ~CLR_RT_GenericCctorExecutionRecord::c_Scheduled;
830+
record->m_flags |= CLR_RT_GenericCctorExecutionRecord::c_Executed;
831+
}
832+
}
833+
}
834+
812835
g_CLR_RT_ExecutionEngine.SpawnStaticConstructor(g_CLR_RT_ExecutionEngine.m_cctorThread);
813836
}
814837

@@ -965,10 +988,136 @@ bool CLR_RT_ExecutionEngine::SpawnStaticConstructorHelper(
965988
}
966989
}
967990

991+
// Set flag to indicate regular static constructors have been processed
968992
assembly->flags |= CLR_RT_Assembly::StaticConstructorsExecuted;
969993
return false;
970994
}
971995

996+
bool CLR_RT_ExecutionEngine::SpawnGenericTypeStaticConstructorsHelper(
997+
CLR_RT_Assembly *assembly,
998+
const CLR_RT_TypeSpec_Index &startTypeSpecIndex)
999+
{
1000+
NATIVE_PROFILE_CLR_CORE();
1001+
1002+
_ASSERTE(m_cctorThread != nullptr);
1003+
_ASSERTE(m_cctorThread->CanThreadBeReused());
1004+
_ASSERTE(assembly != nullptr);
1005+
1006+
// Crawl TypeSpecs in this assembly to find closed generic instantiations that need .cctor execution
1007+
int numTypeSpec = assembly->tablesSize[TBL_TypeSpec];
1008+
1009+
// Start from the specified TypeSpec index (to resume iteration after a .cctor completes)
1010+
CLR_UINT32 startIndex = startTypeSpecIndex.TypeSpec();
1011+
CLR_RT_TypeSpec_CrossReference *tsCross = assembly->crossReferenceTypeSpec + startIndex;
1012+
1013+
for (int iTs = startIndex; iTs < numTypeSpec; iTs++, tsCross++)
1014+
{
1015+
// Build a TypeSpec_Instance to check if this is a closed generic instantiation
1016+
CLR_RT_TypeSpec_Instance genericTypeInstance{};
1017+
CLR_RT_TypeSpec_Index tsIndex;
1018+
tsIndex.Set(assembly->assemblyIndex, iTs);
1019+
1020+
if (!genericTypeInstance.InitializeFromIndex(tsIndex))
1021+
{
1022+
continue;
1023+
}
1024+
1025+
// Only for closed generic instantiations (have genericTypeDef)
1026+
if (!genericTypeInstance.IsClosedGenericType())
1027+
{
1028+
continue;
1029+
}
1030+
1031+
// Get the generic type definition
1032+
CLR_RT_TypeDef_Index typeDef = genericTypeInstance.genericTypeDef;
1033+
1034+
// Check if the generic type definition has a static constructor
1035+
CLR_RT_Assembly *ownerAsm = g_CLR_RT_TypeSystem.m_assemblies[typeDef.Assembly() - 1];
1036+
if (!ownerAsm->HasStaticConstructor(typeDef))
1037+
{
1038+
continue;
1039+
}
1040+
1041+
// Find the static constructor method for this generic type definition
1042+
const CLR_RECORD_TYPEDEF *ownerTd = ownerAsm->GetTypeDef(typeDef.Type());
1043+
const CLR_RECORD_METHODDEF *md = ownerAsm->GetMethodDef(ownerTd->firstMethod);
1044+
1045+
// Calculate total method count for this type
1046+
int methodCount = ownerTd->virtualMethodCount + ownerTd->instanceMethodCount + ownerTd->staticMethodCount;
1047+
1048+
CLR_RT_MethodDef_Index cctorIndex;
1049+
bool foundCctor = false;
1050+
1051+
for (int i = 0; i < methodCount; i++, md++)
1052+
{
1053+
if (md->flags & CLR_RECORD_METHODDEF::MD_StaticConstructor)
1054+
{
1055+
cctorIndex.Set(ownerAsm->assemblyIndex, ownerTd->firstMethod + i);
1056+
foundCctor = true;
1057+
break;
1058+
}
1059+
}
1060+
1061+
if (!foundCctor)
1062+
{
1063+
continue;
1064+
}
1065+
1066+
// Compute hash for the closed generic type to check if .cctor already scheduled/executed
1067+
CLR_UINT32 hash = g_CLR_RT_TypeSystem.ComputeHashForClosedGenericType(genericTypeInstance);
1068+
1069+
// Find or create the .cctor execution record for this closed type
1070+
bool recordCreated = false;
1071+
CLR_RT_GenericCctorExecutionRecord *record =
1072+
g_CLR_RT_TypeSystem.FindOrCreateGenericCctorRecord(hash, &recordCreated);
1073+
1074+
if (record == nullptr)
1075+
{
1076+
// Out of memory - skip this .cctor
1077+
continue;
1078+
}
1079+
1080+
// Check if .cctor already scheduled or executed
1081+
if (record->m_flags &
1082+
(CLR_RT_GenericCctorExecutionRecord::c_Scheduled | CLR_RT_GenericCctorExecutionRecord::c_Executed))
1083+
{
1084+
// Already handled - skip to next TypeSpec
1085+
continue;
1086+
}
1087+
1088+
// Mark as scheduled to prevent duplicate scheduling
1089+
record->m_flags |= CLR_RT_GenericCctorExecutionRecord::c_Scheduled;
1090+
1091+
// Create delegate for the generic type .cctor
1092+
CLR_RT_HeapBlock_Delegate *dlg;
1093+
CLR_RT_HeapBlock refDlg;
1094+
1095+
refDlg.SetObjectReference(nullptr);
1096+
CLR_RT_ProtectFromGC gc(refDlg);
1097+
1098+
if (SUCCEEDED(CLR_RT_HeapBlock_Delegate::CreateInstance(refDlg, cctorIndex, nullptr)))
1099+
{
1100+
dlg = refDlg.DereferenceDelegate();
1101+
1102+
// Store the current closed generic TypeSpec index for correct resumption
1103+
dlg->m_genericTypeSpec = tsIndex;
1104+
1105+
if (SUCCEEDED(m_cctorThread->PushThreadProcDelegate(dlg)))
1106+
{
1107+
m_cctorThread->m_terminationCallback = StaticConstructorTerminationCallback;
1108+
return true;
1109+
}
1110+
}
1111+
1112+
// If we failed to schedule, clear the scheduled flag
1113+
record->m_flags &= ~CLR_RT_GenericCctorExecutionRecord::c_Scheduled;
1114+
}
1115+
1116+
// No more generic type .cctors for this assembly - set flag
1117+
assembly->flags |= CLR_RT_Assembly::StaticGenericConstructorsExecuted;
1118+
return false;
1119+
}
1120+
9721121
void CLR_RT_ExecutionEngine::SpawnStaticConstructor(CLR_RT_Thread *&pCctorThread)
9731122
{
9741123
NATIVE_PROFILE_CLR_CORE();
@@ -988,37 +1137,91 @@ void CLR_RT_ExecutionEngine::SpawnStaticConstructor(CLR_RT_Thread *&pCctorThread
9881137
_ASSERTE(NANOCLR_INDEX_IS_VALID(index));
9891138
_SIDE_ASSERTE(inst.InitializeFromIndex(index));
9901139

991-
// This is ok if index is no longer valid. SpawnStaticConstructorHelper will call FindNextStaticConstructor
992-
// which will fail
993-
index.data++;
1140+
// Check if this is a generic type .cctor (has m_genericTypeSpec.data != 0 in the delegate)
1141+
if (dlg->m_genericTypeSpec.data != 0)
1142+
{
1143+
// Extract the TypeSpec index from the delegate and increment to next TypeSpec
1144+
CLR_RT_TypeSpec_Index tsIndex = dlg->m_genericTypeSpec;
1145+
CLR_RT_Assembly *assembly = g_CLR_RT_TypeSystem.m_assemblies[tsIndex.Assembly() - 1];
9941146

995-
if (SpawnStaticConstructorHelper(inst.assembly, index))
996-
return;
1147+
// Increment to next TypeSpec (same pattern as regular .cctor)
1148+
tsIndex.data++;
1149+
1150+
if (SpawnGenericTypeStaticConstructorsHelper(assembly, tsIndex))
1151+
{
1152+
return;
1153+
}
1154+
}
1155+
else
1156+
{
1157+
// Regular static constructor - increment to next method index
1158+
index.data++;
1159+
1160+
if (SpawnStaticConstructorHelper(inst.assembly, index))
1161+
{
1162+
return;
1163+
}
1164+
}
9971165
}
9981166

9991167
// first, find the AppDomainAssembly to run. (what about appdomains!!!)
10001168
NANOCLR_FOREACH_ASSEMBLY(g_CLR_RT_TypeSystem)
10011169
{
1002-
// Find an AppDomainAssembly that does not have it's static constructor bit set...
1170+
// Check if regular static constructors need to be executed
10031171
if ((pASSM->flags & CLR_RT_Assembly::StaticConstructorsExecuted) == 0)
10041172
{
10051173
CLR_RT_MethodDef_Index index;
10061174
index.Set(pASSM->assemblyIndex, 0);
1007-
bool fDepedenciesRun = true;
1175+
bool dependenciesSatisfied = true;
10081176

1009-
// Check that all dependent assemblies have had static constructors run.
1177+
// Check that all dependent assemblies have had regular static constructors run
10101178
CLR_RT_AssemblyRef_CrossReference *ar = pASSM->crossReferenceAssemblyRef;
10111179
for (int i = 0; i < pASSM->tablesSize[TBL_AssemblyRef]; i++, ar++)
10121180
{
10131181
if ((ar->target->flags & CLR_RT_Assembly::StaticConstructorsExecuted) == 0)
10141182
{
1015-
fDepedenciesRun = true;
1183+
dependenciesSatisfied = false;
10161184
break;
10171185
}
10181186
}
10191187

1020-
if (fDepedenciesRun && SpawnStaticConstructorHelper(pASSM, index))
1021-
return;
1188+
if (dependenciesSatisfied)
1189+
{
1190+
// Run regular static constructors for this assembly
1191+
if (SpawnStaticConstructorHelper(pASSM, index))
1192+
{
1193+
return;
1194+
}
1195+
}
1196+
}
1197+
1198+
// Check if generic type static constructors need to be executed
1199+
if ((pASSM->flags & CLR_RT_Assembly::StaticGenericConstructorsExecuted) == 0)
1200+
{
1201+
bool dependenciesSatisfied = true;
1202+
1203+
// Check that all dependent assemblies have had regular static constructors run
1204+
CLR_RT_AssemblyRef_CrossReference *ar = pASSM->crossReferenceAssemblyRef;
1205+
for (int i = 0; i < pASSM->tablesSize[TBL_AssemblyRef]; i++, ar++)
1206+
{
1207+
if ((ar->target->flags & CLR_RT_Assembly::StaticConstructorsExecuted) == 0)
1208+
{
1209+
dependenciesSatisfied = false;
1210+
break;
1211+
}
1212+
}
1213+
1214+
if (dependenciesSatisfied)
1215+
{
1216+
// Run generic type static constructors for this assembly (starting from index 0)
1217+
CLR_RT_TypeSpec_Index startIndex;
1218+
startIndex.Set(pASSM->assemblyIndex, 0);
1219+
1220+
if (SpawnGenericTypeStaticConstructorsHelper(pASSM, startIndex))
1221+
{
1222+
return;
1223+
}
1224+
}
10221225
}
10231226
}
10241227
NANOCLR_FOREACH_ASSEMBLY_END();

src/CLR/Core/Thread.cpp

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,18 @@ HRESULT CLR_RT_Thread::PushThreadProcDelegate(CLR_RT_HeapBlock_Delegate *pDelega
163163
NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE);
164164
}
165165

166+
// Set generic context if the delegate has a TypeSpec stored (for generic type static constructors)
167+
// Note: We temporarily set inst.genericType to the delegate's interior pointer here,
168+
// but will copy it to stable storage in the stack frame after Push() below
169+
CLR_RT_TypeSpec_Index delegateTypeSpec;
170+
delegateTypeSpec.Clear();
171+
172+
if (pDelegate->m_genericTypeSpec.data != 0)
173+
{
174+
delegateTypeSpec = pDelegate->m_genericTypeSpec;
175+
inst.genericType = &delegateTypeSpec;
176+
}
177+
166178
#if defined(NANOCLR_APPDOMAINS)
167179

168180
if (!pDelegate->m_appDomain->IsLoaded())
@@ -181,6 +193,15 @@ HRESULT CLR_RT_Thread::PushThreadProcDelegate(CLR_RT_HeapBlock_Delegate *pDelega
181193

182194
NANOCLR_CHECK_HRESULT(CLR_RT_StackFrame::Push(this, inst, inst.target->argumentsCount));
183195

196+
// If we have a generic type context, copy it to stable storage in the stack frame
197+
// and update the pointer to avoid GC relocation issues with the delegate object
198+
if (delegateTypeSpec.data != 0)
199+
{
200+
CLR_RT_StackFrame *stackTop = this->CurrentFrame();
201+
stackTop->m_genericTypeSpecStorage = delegateTypeSpec;
202+
stackTop->m_call.genericType = &stackTop->m_genericTypeSpecStorage;
203+
}
204+
184205
if ((inst.target->flags & CLR_RECORD_METHODDEF::MD_Static) == 0)
185206
{
186207
CLR_RT_StackFrame *stackTop = this->CurrentFrame();
@@ -821,8 +842,8 @@ HRESULT CLR_RT_Thread::ProcessException_Phase1()
821842
us.GetPhase() < UnwindStack::p_1_SearchingForHandler_2_SentUsersChance && stack->m_IP)
822843
{
823844
// We have a debugger attached and we need to send some messages before we start searching.
824-
// These messages should only get sent when the search reaches managed code. Stack::Push sets m_IP to nullptr
825-
// for native code, so therefore we need IP to be non-nullptr
845+
// These messages should only get sent when the search reaches managed code. Stack::Push sets m_IP to
846+
// nullptr for native code, so therefore we need IP to be non-nullptr
826847

827848
us.m_handlerStack = stack;
828849

0 commit comments

Comments
 (0)