@@ -5860,6 +5860,39 @@ HRESULT CLR_RT_Assembly::ResolveAllocateStaticFields(CLR_RT_HeapBlock *pStaticFi
58605860 NANOCLR_NOCLEANUP ();
58615861}
58625862
5863+ // The slots are indexed as an array, so the run has to stay contiguous and unmoved: see CLAUDE.md
5864+ // "Static fields on generic types".
5865+ static CLR_RT_HeapBlock *CLR_AllocateGenericStaticFieldStorage (CLR_UINT32 count)
5866+ {
5867+ NATIVE_PROFILE_CLR_CORE ();
5868+
5869+ const CLR_UINT32 headerBlocks = CONVERTFROMSIZETOHEAPBLOCKS (sizeof (CLR_RT_HeapBlock_BinaryBlob));
5870+
5871+ auto *blob = (CLR_RT_HeapBlock_BinaryBlob *)g_CLR_RT_ExecutionEngine.ExtractHeapBlocksForEvents (
5872+ DATATYPE_BINARY_BLOB_HEAD ,
5873+ 0 ,
5874+ headerBlocks + count);
5875+
5876+ if (blob == nullptr )
5877+ {
5878+ return nullptr ;
5879+ }
5880+
5881+ // no handlers: the registry marks and relocates the payload, see CLR_RT_TypeSystem::Relocate
5882+ blob->SetBinaryBlobHandlers (nullptr , nullptr );
5883+ blob->m_assembly = nullptr ;
5884+
5885+ CLR_RT_HeapBlock *fields = (CLR_RT_HeapBlock *)blob + headerBlocks;
5886+
5887+ // the record is registered before the slots are typed, so they have to be walkable right away
5888+ for (CLR_UINT32 i = 0 ; i < count; i++)
5889+ {
5890+ fields[i].SetObjectReference (nullptr );
5891+ }
5892+
5893+ return fields;
5894+ }
5895+
58635896HRESULT CLR_RT_Assembly::ResolveAllocateGenericTypeStaticFields ()
58645897{
58655898 NATIVE_PROFILE_CLR_CORE ();
@@ -5977,26 +6010,24 @@ HRESULT CLR_RT_Assembly::ResolveAllocateGenericTypeStaticFields()
59776010 g_CLR_RT_TypeSystem.m_genericStaticFieldsMaxCount = newMax;
59786011 }
59796012
5980- // Allocate storage for the static fields
5981- CLR_RT_HeapBlock *fields = g_CLR_RT_ExecutionEngine. ExtractHeapBlocksForObjects (
5982- DATATYPE_OBJECT , // heapblock kind
5983- 0 , // flags
5984- count); // number of CLR_RT_HeapBlock entries
6013+ // Allocate mapping for field definitions first: a platform_malloc failure here is trivial to
6014+ // unwind, whereas the GC heap blob allocated below has no direct release path and would
6015+ // otherwise leak until the next GC cycle if allocated first and this failed.
6016+ CLR_RT_FieldDef_Index *fieldDefs =
6017+ (CLR_RT_FieldDef_Index *) platform_malloc ( sizeof (CLR_RT_FieldDef_Index) * count);
59856018
5986- if (fields == nullptr )
6019+ if (fieldDefs == nullptr )
59876020 {
59886021 NANOCLR_SET_AND_LEAVE (CLR_E_OUT_OF_MEMORY );
59896022 }
59906023
5991- // Allocate mapping for field definitions
5992- CLR_RT_FieldDef_Index *fieldDefs =
5993- (CLR_RT_FieldDef_Index *)platform_malloc (sizeof (CLR_RT_FieldDef_Index) * count);
6024+ // Allocate storage for the static fields
6025+ CLR_RT_HeapBlock *fields = CLR_AllocateGenericStaticFieldStorage (count);
59946026
5995- if (fieldDefs == nullptr )
6027+ if (fields == nullptr )
59966028 {
5997- // Free already allocated fields
5998- // Since we don't have a direct ReleaseHeapBlocksForObjects function,
5999- // we'll need to have the GC clean it up later
6029+ // Field defs mapping isn't published anywhere yet, so it can be freed directly
6030+ platform_free (fieldDefs);
60006031 NANOCLR_SET_AND_LEAVE (CLR_E_OUT_OF_MEMORY );
60016032 }
60026033
@@ -6246,22 +6277,22 @@ HRESULT CLR_RT_Assembly::AllocateGenericStaticFieldsOnDemand(
62466277 g_CLR_RT_TypeSystem.m_genericStaticFieldsMaxCount = newMax;
62476278 }
62486279
6249- // Allocate storage for the static fields
6250- fields = g_CLR_RT_ExecutionEngine.ExtractHeapBlocksForObjects (
6251- DATATYPE_OBJECT , // heapblock kind
6252- 0 , // flags
6253- count); // number of CLR_RT_HeapBlock entries
6280+ // Allocate mapping for field definitions first: a platform_malloc failure here is trivial to
6281+ // unwind, whereas the GC heap blob allocated below has no direct release path.
6282+ fieldDefs = (CLR_RT_FieldDef_Index *)platform_malloc (sizeof (CLR_RT_FieldDef_Index) * count);
62546283
6255- if (fields == nullptr )
6284+ if (fieldDefs == nullptr )
62566285 {
62576286 NANOCLR_SET_AND_LEAVE (CLR_E_OUT_OF_MEMORY );
62586287 }
62596288
6260- // Allocate mapping for field definitions
6261- fieldDefs = (CLR_RT_FieldDef_Index *) platform_malloc ( sizeof (CLR_RT_FieldDef_Index) * count);
6289+ // Allocate storage for the static fields
6290+ fields = CLR_AllocateGenericStaticFieldStorage ( count);
62626291
6263- if (fieldDefs == nullptr )
6292+ if (fields == nullptr )
62646293 {
6294+ // Field defs mapping isn't published anywhere yet, so it can be freed directly
6295+ platform_free (fieldDefs);
62656296 NANOCLR_SET_AND_LEAVE (CLR_E_OUT_OF_MEMORY );
62666297 }
62676298
@@ -7200,36 +7231,6 @@ void CLR_RT_Assembly::Relocate()
72007231 CLR_RT_GarbageCollector::Heap_Relocate (staticFields, staticFieldsCount);
72017232#endif
72027233
7203- // Relocate all generic static field entries
7204- for (CLR_UINT32 i = 0 ; i < g_CLR_RT_TypeSystem.m_genericStaticFieldsCount ; i++)
7205- {
7206- CLR_RT_GarbageCollector::RelocateGenericStaticField (&g_CLR_RT_TypeSystem.m_genericStaticFields [i]);
7207- }
7208-
7209- // Resync TypeSpec cross-ref caches into the relocated generic static field arrays
7210- // (they are platform_malloc'd, so GC relocation above does not update them).
7211- for (CLR_UINT32 i = 0 ; i < g_CLR_RT_TypeSystem.m_genericStaticFieldsCount ; i++)
7212- {
7213- const CLR_RT_GenericStaticFieldRecord &record = g_CLR_RT_TypeSystem.m_genericStaticFields [i];
7214-
7215- // Walk every assembly, every TypeSpec cross-reference, and update the cache if it was
7216- // pointing at this record's old field block.
7217- NANOCLR_FOREACH_ASSEMBLY (g_CLR_RT_TypeSystem)
7218- {
7219- for (int tsIdx = 0 ; tsIdx < pASSM->tablesSize [TBL_TypeSpec]; tsIdx++)
7220- {
7221- CLR_RT_TypeSpec_CrossReference &tsCross = pASSM->crossReferenceTypeSpec [tsIdx];
7222-
7223- if (tsCross.genericStaticFields != nullptr && tsCross.genericStaticFieldsCount == record.m_count &&
7224- tsCross.genericStaticFieldDefs == record.m_fieldDefs )
7225- {
7226- tsCross.genericStaticFields = record.m_fields ;
7227- }
7228- }
7229- }
7230- NANOCLR_FOREACH_ASSEMBLY_END ();
7231- }
7232-
72337234 CLR_RT_GarbageCollector::Heap_Relocate ((void **)&header);
72347235 CLR_RT_GarbageCollector::Heap_Relocate ((void **)&name);
72357236 CLR_RT_GarbageCollector::Heap_Relocate ((void **)&file);
@@ -7238,6 +7239,20 @@ void CLR_RT_Assembly::Relocate()
72387239
72397240// //////////////////////////////////////////////////////////////////////////////////////////////////
72407241
7242+ void CLR_RT_TypeSystem::Relocate ()
7243+ {
7244+ NATIVE_PROFILE_CLR_CORE ();
7245+
7246+ // The registry is global, so this runs once per relocation pass, never per assembly:
7247+ // see CLAUDE.md "Static fields on generic types".
7248+ for (CLR_UINT32 i = 0 ; i < m_genericStaticFieldsCount; i++)
7249+ {
7250+ CLR_RT_GarbageCollector::RelocateGenericStaticField (&m_genericStaticFields[i]);
7251+ }
7252+ }
7253+
7254+ // //////////////////////////////////////////////////////////////////////////////////////////////////
7255+
72417256void CLR_RT_TypeSystem::TypeSystem_Initialize ()
72427257{
72437258 NATIVE_PROFILE_CLR_CORE ();
@@ -9325,34 +9340,24 @@ CLR_RT_GenericStaticFieldRecord *CLR_RT_TypeSystem::FindOrCreateGenericStaticFie
93259340 // Get the type definition record
93269341 const CLR_RECORD_TYPEDEF *ownerTd = ownerAssembly->GetTypeDef (typeDef.Type ());
93279342
9328- // Allocate storage for the static fields
9329- CLR_RT_HeapBlock *pFields = g_CLR_RT_ExecutionEngine.ExtractHeapBlocksForObjects (
9330- DATATYPE_OBJECT , // Use OBJECT type for proper GC management
9331- 0 , // No special flags
9332- staticFieldCount // Number of fields
9333- );
9334-
9335- if (pFields == nullptr )
9336- {
9337- return nullptr ; // Out of memory
9338- }
9339-
9340- // Allocate mapping for field definitions using platform_malloc since we need to manage this memory separately
9343+ // Allocate mapping for field definitions first using platform_malloc: if this fails there's
9344+ // nothing to unwind. Doing this before the GC heap blob avoids leaking GC-managed memory
9345+ // that has no direct release path.
93419346 CLR_RT_FieldDef_Index *pFieldDefs =
93429347 (CLR_RT_FieldDef_Index *)platform_malloc (sizeof (CLR_RT_FieldDef_Index) * staticFieldCount);
93439348
93449349 if (pFieldDefs == nullptr )
93459350 {
9346- // Unable to allocate field definitions, must clean up the fields we already allocated
9347- // Since ExtractHeapBlocksForObjects allocates memory that's managed by the GC,
9348- // we don't explicitly free it. The next GC cycle will reclaim it.
9351+ return nullptr ; // Out of memory
9352+ }
93499353
9350- // Reset the allocated fields to null to ensure no dangling references
9351- for (CLR_UINT32 i = 0 ; i < staticFieldCount; i++)
9352- {
9353- pFields[i].SetObjectReference (nullptr );
9354- }
9354+ // Allocate storage for the static fields
9355+ CLR_RT_HeapBlock *pFields = CLR_AllocateGenericStaticFieldStorage (staticFieldCount);
93559356
9357+ if (pFields == nullptr )
9358+ {
9359+ // Field defs mapping isn't published anywhere yet, so it can be freed directly
9360+ platform_free (pFieldDefs);
93569361 return nullptr ; // Out of memory
93579362 }
93589363
0 commit comments