From a9fc0135f585bf3e44434c0d3356fe7bb36a2687 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Wed, 8 Oct 2025 18:13:52 +0100 Subject: [PATCH 1/5] Add static fields for generic types - TypeSpec cross reference now stores instances of static fields and field defs. - Assembly now has GetGenericStaticField() and GetStaticFieldByFieldDef(). - TypeSpec instance now has a helper API IsClosedGenericType(). - Update handlers for stsfld, ldsfld and ldsflda to deal with static fields of closed generic types. --- src/CLR/Core/Interpreter.cpp | 72 +++++++- src/CLR/Core/TypeSystem.cpp | 295 +++++++++++++++++++++++++++++- src/CLR/Include/nanoCLR_Runtime.h | 21 ++- 3 files changed, 378 insertions(+), 10 deletions(-) diff --git a/src/CLR/Core/Interpreter.cpp b/src/CLR/Core/Interpreter.cpp index 9b2be3f137..4de83aac4b 100644 --- a/src/CLR/Core/Interpreter.cpp +++ b/src/CLR/Core/Interpreter.cpp @@ -2728,13 +2728,33 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) { FETCH_ARG_COMPRESSED_FIELDTOKEN(arg, ip); - CLR_RT_FieldDef_Instance field; + CLR_RT_FieldDef_Instance field{}; + CLR_RT_HeapBlock *ptr = nullptr; + + // resolve field token if (field.ResolveToken(arg, assm, &stack->m_call) == false) { NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE); } - CLR_RT_HeapBlock *ptr = CLR_RT_ExecutionEngine::AccessStaticField(field); + if (field.genericType && NANOCLR_INDEX_IS_VALID(*field.genericType)) + { + // access static field of a generic instance + CLR_RT_FieldDef_Index genericField{}; + genericField.data = field.data; + + // find the assembly where the generic type is instantiated + CLR_RT_Assembly *typeSpecAsm = + g_CLR_RT_TypeSystem.m_assemblies[field.genericType->Assembly() - 1]; + + // now access the generic static field + ptr = typeSpecAsm->GetGenericStaticField(*field.genericType, genericField); + } + else + { + // static field of a non-generic class + ptr = CLR_RT_ExecutionEngine::AccessStaticField(field); + } if (ptr == nullptr) { @@ -2756,13 +2776,33 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) { FETCH_ARG_COMPRESSED_FIELDTOKEN(arg, ip); - CLR_RT_FieldDef_Instance field; + CLR_RT_FieldDef_Instance field{}; + CLR_RT_HeapBlock *ptr = nullptr; + + // resolve field token if (field.ResolveToken(arg, assm, &stack->m_call) == false) { NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE); } - CLR_RT_HeapBlock *ptr = CLR_RT_ExecutionEngine::AccessStaticField(field); + if (field.genericType && NANOCLR_INDEX_IS_VALID(*field.genericType)) + { + // access static field of a generic instance + CLR_RT_FieldDef_Index genericField{}; + genericField.data = field.data; + + // find the assembly where the generic type is instantiated + CLR_RT_Assembly *typeSpecAsm = + g_CLR_RT_TypeSystem.m_assemblies[field.genericType->Assembly() - 1]; + + // now access the generic static field + ptr = typeSpecAsm->GetGenericStaticField(*field.genericType, genericField); + } + else + { + // static field of a non-generic class + ptr = CLR_RT_ExecutionEngine::AccessStaticField(field); + } if (ptr == nullptr) { @@ -2783,13 +2823,33 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) { FETCH_ARG_COMPRESSED_FIELDTOKEN(arg, ip); - CLR_RT_FieldDef_Instance field; + CLR_RT_FieldDef_Instance field{}; + CLR_RT_HeapBlock *ptr = nullptr; + + // resolve field token if (field.ResolveToken(arg, assm, &stack->m_call) == false) { NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE); } - CLR_RT_HeapBlock *ptr = CLR_RT_ExecutionEngine::AccessStaticField(field); + if (field.genericType && NANOCLR_INDEX_IS_VALID(*field.genericType)) + { + // access static field of a generic instance + CLR_RT_FieldDef_Index genericField{}; + genericField.data = field.data; + + // find the assembly where the generic type is instantiated + CLR_RT_Assembly *typeSpecAsm = + g_CLR_RT_TypeSystem.m_assemblies[field.genericType->Assembly() - 1]; + + // now access the generic static field + ptr = typeSpecAsm->GetGenericStaticField(*field.genericType, genericField); + } + else + { + // static field of a non-generic class + ptr = CLR_RT_ExecutionEngine::AccessStaticField(field); + } if (ptr == nullptr) { diff --git a/src/CLR/Core/TypeSystem.cpp b/src/CLR/Core/TypeSystem.cpp index 1b04b3bc6f..c74fec43b1 100644 --- a/src/CLR/Core/TypeSystem.cpp +++ b/src/CLR/Core/TypeSystem.cpp @@ -704,9 +704,8 @@ bool CLR_RT_TypeSpec_Instance::InitializeFromIndex(const CLR_RT_TypeSpec_Index & CLR_RT_SignatureParser parser; parser.Initialize_TypeSpec(assembly, assembly->GetTypeSpec(index.TypeSpec())); - CLR_RT_SignatureParser::Element element; + CLR_RT_SignatureParser::Element element{}; - // if this is a generic, advance another one if (FAILED(parser.Advance(element))) { ClearInstance(); @@ -718,7 +717,7 @@ bool CLR_RT_TypeSpec_Instance::InitializeFromIndex(const CLR_RT_TypeSpec_Index & if (element.DataType == DATATYPE_GENERICINST) { - // this is a generic instance, so we need to advance one more time + // this is a generic instance, advance one more time to get the generic type definition parser.Advance(element); genericTypeDef = element.Class; @@ -825,6 +824,80 @@ bool CLR_RT_TypeSpec_Instance::ResolveToken( return false; } +bool CLR_RT_TypeSpec_Instance::IsClosedGenericType() +{ + if (!NANOCLR_INDEX_IS_VALID(genericTypeDef)) + { + return false; // not a generic instantiation + } + + CLR_RT_SignatureParser parser{}; + parser.Initialize_TypeSpec(*this); + + CLR_RT_SignatureParser::Element elem{}; + if (FAILED(parser.Advance(elem)) || elem.DataType != DATATYPE_GENERICINST) + { + return false; + } + + // Skip the generic type definition; elem.GenParamCount carries the arg count after this advance. + if (FAILED(parser.Advance(elem))) + { + return false; + } + + const int argCount = elem.GenParamCount; + + // Helper: fully consume one type from the parser and report if it contains VAR/MVAR anywhere. + auto hasOpenParam = [&](auto &self) -> bool { + CLR_RT_SignatureParser::Element a{}; + + if (FAILED(parser.Advance(a))) + { + // treat malformed as open to avoid false "closed" + return true; + } + + if (a.DataType == DATATYPE_VAR || a.DataType == DATATYPE_MVAR) + { + return true; + } + + if (a.DataType == DATATYPE_GENERICINST) + { + // Skip the generic type definition and walk all nested arguments. + if (FAILED(parser.Advance(a))) + { + return true; + } + + int nested = a.GenParamCount; + + for (int j = 0; j < nested; j++) + { + if (self(self)) + { + return true; + } + } + } + + // Arrays are represented via Levels in 'a' and already reduced to their element kind above. + return false; + }; + + for (int i = 0; i < argCount; i++) + { + if (hasOpenParam(hasOpenParam)) + { + return false; + } + } + + // all arguments are concrete types, this is a closed generic type + return true; +} + ////////////////////////////// bool CLR_RT_TypeDef_Instance::InitializeFromReflection(const CLR_RT_ReflectionDef_Index &reflex, CLR_UINT32 *levels) @@ -2853,6 +2926,9 @@ void CLR_RT_Assembly::AssemblyInitialize(CLR_RT_Assembly::Offsets &offsets) for (i = 0; i < this->tablesSize[TBL_TypeSpec]; i++, src++, dst++) { dst->genericType.data = 0; + dst->genericStaticFields = nullptr; + dst->genericStaticFieldDefs = nullptr; + dst->genericStaticFieldsCount = 0; } } @@ -4844,6 +4920,201 @@ HRESULT CLR_RT_Assembly::ResolveAllocateStaticFields(CLR_RT_HeapBlock *pStaticFi NANOCLR_NOCLEANUP(); } +HRESULT CLR_RT_Assembly::ResolveAllocateGenericTypeStaticFields() +{ + NATIVE_PROFILE_CLR_CORE(); + NANOCLR_HEADER(); + + // For each TypeSpec that represents a closed generic instantiation, count the static fields bound to it, + // allocate CLR_RT_HeapBlock array and initialize references into it. + CLR_RT_TypeSpec_CrossReference *tsCross = this->crossReferenceTypeSpec; + int numTypeSpec = this->tablesSize[TBL_TypeSpec]; + + for (int iTs = 0; iTs < numTypeSpec; iTs++, tsCross++) + { + // skip if already allocated or if TypeSpec is not a generic instantiation + if (tsCross->genericStaticFields != nullptr) + { + continue; + } + + // Build a TypeSpec_Instance from the TypeSpec index to see if this is a closed generic instance + CLR_RT_TypeSpec_Instance genericTypeInstance{}; + CLR_RT_TypeSpec_Index tsIndex; + tsIndex.Set(assemblyIndex, iTs); + + if (!genericTypeInstance.InitializeFromIndex(tsIndex)) + { + // can't initialize, skip + continue; + } + + // Only for closed generic instantiations (have genericTypeDef) + if (!genericTypeInstance.IsClosedGenericType()) + { + continue; + } + + // The generic definition TypeDef that declares the static fields: + CLR_RT_TypeDef_Index ownerTypeDef = genericTypeInstance.genericTypeDef; + CLR_RT_Assembly *ownerAsm = g_CLR_RT_TypeSystem.m_assemblies[ownerTypeDef.Assembly() - 1]; + const CLR_RECORD_TYPEDEF *ownerTd = ownerAsm->GetTypeDef(ownerTypeDef.Type()); + +#ifdef NANOCLR_INSTANCE_NAMES + const char *typeName = ownerAsm->GetString(ownerTd->name); +#endif + + // get how many static FieldDefs are bound to the generic type definition + int count = ownerTd->staticFieldsCount; + + if (count == 0) + { + // no static fields in this generic type definition, nothing to do + continue; + } + + CLR_RT_HeapBlock *pHeap = g_CLR_RT_ExecutionEngine.ExtractHeapBlocksForObjects( + DATATYPE_OBJECT, // heapblock kind (object/value choice is OK; we just need heapblocks) + 0, // flags + count); // number of CLR_RT_HeapBlock entries + CHECK_ALLOCATION(pHeap); + + CLR_RT_FieldDef_Index *pFieldDefs = + (CLR_RT_FieldDef_Index *)g_CLR_RT_ExecutionEngine.ExtractHeapBytesForObjects( + DATATYPE_BOOLEAN, + 0, + (sizeof(CLR_RT_FieldDef_Index) * count)); + CHECK_ALLOCATION(pFieldDefs); + + // fill mapping and initialize each heap slot using the owner assembly's field defs + for (int staticField = 0; staticField < count; staticField++) + { + CLR_INDEX fieldIndex = ownerTd->firstStaticField + staticField; + pFieldDefs[staticField].Set(ownerAsm->assemblyIndex, fieldIndex); + + // initialize the storage: call InitializeReference for that field def (ownerAsm context) + const CLR_RECORD_FIELDDEF *pFd = ownerAsm->GetFieldDef(fieldIndex); + + CLR_RT_SignatureParser parser{}; + parser.Initialize_FieldDef(ownerAsm, pFd); + + CLR_RT_SignatureParser::Element element{}; + + NANOCLR_CHECK_HRESULT(parser.Advance(element)); + + if (element.Levels > 0) + { + // this is an array + pHeap[staticField].SetDataId(CLR_RT_HEAPBLOCK_RAW_ID(DATATYPE_OBJECT, CLR_RT_HeapBlock::HB_Alive, 1)); + pHeap[staticField].ClearData(); + + NanoCLRDataType dt; + CLR_RT_TypeDef_Index realTypeDef{}; + dt = element.DataType; + realTypeDef.data = element.Class.data; + + if (element.DataType == DATATYPE_CLASS || element.DataType == DATATYPE_VALUETYPE) + { + realTypeDef = element.Class; + } + if (element.DataType == DATATYPE_VAR) + { + genericTypeInstance.assembly->FindGenericParamAtTypeSpec( + genericTypeInstance.data, + element.GenericParamPosition, + realTypeDef, + dt); + + // goto process_datatype; + } + else if (element.DataType == DATATYPE_MVAR) + { + _ASSERTE(true); + + // goto process_datatype; + } + else + { + _ASSERTE(true); + } + + CLR_RT_HeapBlock_Array::CreateInstance(pHeap[staticField], 0, realTypeDef); + } + else + { + g_CLR_RT_ExecutionEngine.InitializeReference(pHeap[staticField], pFd, ownerAsm, &genericTypeInstance); + } + } + + // store in typespec crossref (this storage is per-assembly typespec) + tsCross->genericStaticFields = pHeap; + tsCross->genericStaticFieldDefs = pFieldDefs; + tsCross->genericStaticFieldsCount = (CLR_UINT32)count; + } + + NANOCLR_NOCLEANUP(); +} + +CLR_RT_HeapBlock *CLR_RT_Assembly::GetGenericStaticField( + const CLR_RT_TypeSpec_Index &typeSpecIndex, + const CLR_RT_FieldDef_Index &fdIndex) +{ + NATIVE_PROFILE_CLR_CORE(); + + if (!NANOCLR_INDEX_IS_VALID(typeSpecIndex)) + { + return nullptr; + } + + // Use the assembly that owns the TypeSpec row. + CLR_RT_Assembly *tsOwner = g_CLR_RT_TypeSystem.m_assemblies[typeSpecIndex.Assembly() - 1]; + CLR_RT_TypeSpec_CrossReference &ts = tsOwner->crossReferenceTypeSpec[typeSpecIndex.TypeSpec()]; + + if (ts.genericStaticFields == nullptr || ts.genericStaticFieldsCount == 0) + { + return nullptr; + } + + for (CLR_UINT32 i = 0; i < ts.genericStaticFieldsCount; i++) + { + if (ts.genericStaticFieldDefs[i].data == fdIndex.data) + { + return &ts.genericStaticFields[i]; + } + } + + return nullptr; +} + +CLR_RT_HeapBlock *CLR_RT_Assembly::GetStaticFieldByFieldDef( + const CLR_RT_FieldDef_Index &fdIndex, + const CLR_RT_TypeSpec_Index *genericType) +{ + NATIVE_PROFILE_CLR_CORE(); + + // If genericType is provided and valid, try per-TypeSpec lookup first + if (genericType != nullptr && NANOCLR_INDEX_IS_VALID(*genericType)) + { + CLR_RT_HeapBlock *hb = GetGenericStaticField(*genericType, fdIndex); + + if (hb != nullptr) + { + return hb; + } + } + + // fallback to assembly static fields (use offset stored on crossReferenceFieldDef) + const CLR_RT_FieldDef_CrossReference &fdCross = crossReferenceFieldDef[fdIndex.Field()]; + + _ASSERTE(fdCross.offset != CLR_EmptyIndex); + +#if defined(NANOCLR_APPDOMAINS) + return GetStaticField(fdCross.offset); // existing method that uses current AppDomain's m_pStaticFields +#else + return GetStaticField(fdCross.offset); +#endif +} + HRESULT CLR_RT_Assembly::PrepareForExecution() { NATIVE_PROFILE_CLR_CORE(); @@ -5638,6 +5909,23 @@ void CLR_RT_Assembly::Relocate() CLR_RT_GarbageCollector::Heap_Relocate(staticFields, staticFieldsCount); #endif + // relocate per-TypeSpec generic statics if any + for (int i = 0; i < tablesSize[TBL_TypeSpec]; i++) + { + CLR_RT_TypeSpec_CrossReference &ts = crossReferenceTypeSpec[i]; + + if (ts.genericStaticFields) + { + CLR_RT_GarbageCollector::Heap_Relocate(ts.genericStaticFields, ts.genericStaticFieldsCount); + } + + if (ts.genericStaticFieldDefs) + { + // pointer relocation for the indices array + CLR_RT_GarbageCollector::Heap_Relocate((void **)&ts.genericStaticFieldDefs); + } + } + CLR_RT_GarbageCollector::Heap_Relocate((void **)&header); CLR_RT_GarbageCollector::Heap_Relocate((void **)&name); CLR_RT_GarbageCollector::Heap_Relocate((void **)&file); @@ -6130,6 +6418,7 @@ HRESULT CLR_RT_TypeSystem::ResolveAll() #if !defined(NANOCLR_APPDOMAINS) NANOCLR_CHECK_HRESULT(pASSM->ResolveAllocateStaticFields(pASSM->staticFields)); + NANOCLR_CHECK_HRESULT(pASSM->ResolveAllocateGenericTypeStaticFields()); #endif pASSM->flags |= CLR_RT_Assembly::ResolutionCompleted; diff --git a/src/CLR/Include/nanoCLR_Runtime.h b/src/CLR/Include/nanoCLR_Runtime.h index 8842c8bce8..1349b9de77 100644 --- a/src/CLR/Include/nanoCLR_Runtime.h +++ b/src/CLR/Include/nanoCLR_Runtime.h @@ -963,6 +963,18 @@ struct CLR_RT_TypeSpec_CrossReference { CLR_RT_TypeSpec_Index genericType; CLR_RT_TypeDef_Index ownerType; + + /// @brief Array of static fields (one entry per bound static field) + /// + CLR_RT_HeapBlock *genericStaticFields; + + /// @brief Array of indices (one entry per bound static field, parallel with genericStaticFields) + /// + CLR_RT_FieldDef_Index *genericStaticFieldDefs; + + /// @brief Number of static fields + /// + CLR_UINT32 genericStaticFieldsCount; }; struct CLR_RT_MethodDef_Patch @@ -1418,7 +1430,13 @@ struct CLR_RT_Assembly : public CLR_RT_HeapBlock_Node // EVENT HEAP - NO RELOCAT void ResolveLink(); HRESULT ResolveComputeHashes(); HRESULT ResolveAllocateStaticFields(CLR_RT_HeapBlock *pStaticFields); - + HRESULT ResolveAllocateGenericTypeStaticFields(); + CLR_RT_HeapBlock *GetGenericStaticField( + const CLR_RT_TypeSpec_Index &typeSpecIndex, + const CLR_RT_FieldDef_Index &fdIndex); + CLR_RT_HeapBlock *GetStaticFieldByFieldDef( + const CLR_RT_FieldDef_Index &fdIndex, + const CLR_RT_TypeSpec_Index *genericType); HRESULT PrepareForExecution(); CLR_UINT32 ComputeAssemblyHash(); @@ -2100,6 +2118,7 @@ struct CLR_RT_TypeSpec_Instance : public CLR_RT_TypeSpec_Index void ClearInstance(); bool ResolveToken(CLR_UINT32 tk, CLR_RT_Assembly *assm, const CLR_RT_MethodDef_Instance *caller = nullptr); + bool IsClosedGenericType(); }; //--// From 6b1ab9c0fabb4b82b195e6ef4b5ce2c498249ce0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Thu, 9 Oct 2025 00:04:52 +0100 Subject: [PATCH 2/5] Static fields in closed generic types now simply initialized the ref --- src/CLR/Core/TypeSystem.cpp | 50 +------------------------------------ 1 file changed, 1 insertion(+), 49 deletions(-) diff --git a/src/CLR/Core/TypeSystem.cpp b/src/CLR/Core/TypeSystem.cpp index c74fec43b1..654f0ba8cb 100644 --- a/src/CLR/Core/TypeSystem.cpp +++ b/src/CLR/Core/TypeSystem.cpp @@ -4995,55 +4995,7 @@ HRESULT CLR_RT_Assembly::ResolveAllocateGenericTypeStaticFields() // initialize the storage: call InitializeReference for that field def (ownerAsm context) const CLR_RECORD_FIELDDEF *pFd = ownerAsm->GetFieldDef(fieldIndex); - CLR_RT_SignatureParser parser{}; - parser.Initialize_FieldDef(ownerAsm, pFd); - - CLR_RT_SignatureParser::Element element{}; - - NANOCLR_CHECK_HRESULT(parser.Advance(element)); - - if (element.Levels > 0) - { - // this is an array - pHeap[staticField].SetDataId(CLR_RT_HEAPBLOCK_RAW_ID(DATATYPE_OBJECT, CLR_RT_HeapBlock::HB_Alive, 1)); - pHeap[staticField].ClearData(); - - NanoCLRDataType dt; - CLR_RT_TypeDef_Index realTypeDef{}; - dt = element.DataType; - realTypeDef.data = element.Class.data; - - if (element.DataType == DATATYPE_CLASS || element.DataType == DATATYPE_VALUETYPE) - { - realTypeDef = element.Class; - } - if (element.DataType == DATATYPE_VAR) - { - genericTypeInstance.assembly->FindGenericParamAtTypeSpec( - genericTypeInstance.data, - element.GenericParamPosition, - realTypeDef, - dt); - - // goto process_datatype; - } - else if (element.DataType == DATATYPE_MVAR) - { - _ASSERTE(true); - - // goto process_datatype; - } - else - { - _ASSERTE(true); - } - - CLR_RT_HeapBlock_Array::CreateInstance(pHeap[staticField], 0, realTypeDef); - } - else - { - g_CLR_RT_ExecutionEngine.InitializeReference(pHeap[staticField], pFd, ownerAsm, &genericTypeInstance); - } + g_CLR_RT_ExecutionEngine.InitializeReference(pHeap[staticField], pFd, ownerAsm, &genericTypeInstance); } // store in typespec crossref (this storage is per-assembly typespec) From 6b81038a053ce5a974dce0efc9d1b32407d604ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Thu, 9 Oct 2025 00:15:09 +0100 Subject: [PATCH 3/5] AssemblyMark in GC now marks generic static fields --- src/CLR/Core/GarbageCollector.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/CLR/Core/GarbageCollector.cpp b/src/CLR/Core/GarbageCollector.cpp index 8a3386c164..484201f56e 100644 --- a/src/CLR/Core/GarbageCollector.cpp +++ b/src/CLR/Core/GarbageCollector.cpp @@ -706,6 +706,17 @@ void CLR_RT_GarbageCollector::Assembly_Mark() #if !defined(NANOCLR_APPDOMAINS) CheckMultipleBlocks(pASSM->staticFields, pASSM->staticFieldsCount); + + // Mark generic static fields for each TypeSpec + for (int i = 0; i < pASSM->tablesSize[TBL_TypeSpec]; i++) + { + CLR_RT_TypeSpec_CrossReference &ts = pASSM->crossReferenceTypeSpec[i]; + + if (ts.genericStaticFields != nullptr && ts.genericStaticFieldsCount > 0) + { + CheckMultipleBlocks(ts.genericStaticFields, ts.genericStaticFieldsCount); + } + } #endif CheckSingleBlock(&pASSM->file); From c32964c52e82f530832b584c79e9bb939523a119 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Thu, 9 Oct 2025 00:22:41 +0100 Subject: [PATCH 4/5] Improve code per coderabbitai review --- src/CLR/Core/Interpreter.cpp | 30 +++--------------------------- 1 file changed, 3 insertions(+), 27 deletions(-) diff --git a/src/CLR/Core/Interpreter.cpp b/src/CLR/Core/Interpreter.cpp index 4de83aac4b..a56339d1de 100644 --- a/src/CLR/Core/Interpreter.cpp +++ b/src/CLR/Core/Interpreter.cpp @@ -2740,15 +2740,7 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) if (field.genericType && NANOCLR_INDEX_IS_VALID(*field.genericType)) { // access static field of a generic instance - CLR_RT_FieldDef_Index genericField{}; - genericField.data = field.data; - - // find the assembly where the generic type is instantiated - CLR_RT_Assembly *typeSpecAsm = - g_CLR_RT_TypeSystem.m_assemblies[field.genericType->Assembly() - 1]; - - // now access the generic static field - ptr = typeSpecAsm->GetGenericStaticField(*field.genericType, genericField); + ptr = field.assembly->GetStaticFieldByFieldDef(field, field.genericType); } else { @@ -2788,15 +2780,7 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) if (field.genericType && NANOCLR_INDEX_IS_VALID(*field.genericType)) { // access static field of a generic instance - CLR_RT_FieldDef_Index genericField{}; - genericField.data = field.data; - - // find the assembly where the generic type is instantiated - CLR_RT_Assembly *typeSpecAsm = - g_CLR_RT_TypeSystem.m_assemblies[field.genericType->Assembly() - 1]; - - // now access the generic static field - ptr = typeSpecAsm->GetGenericStaticField(*field.genericType, genericField); + ptr = field.assembly->GetStaticFieldByFieldDef(field, field.genericType); } else { @@ -2835,15 +2819,7 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) if (field.genericType && NANOCLR_INDEX_IS_VALID(*field.genericType)) { // access static field of a generic instance - CLR_RT_FieldDef_Index genericField{}; - genericField.data = field.data; - - // find the assembly where the generic type is instantiated - CLR_RT_Assembly *typeSpecAsm = - g_CLR_RT_TypeSystem.m_assemblies[field.genericType->Assembly() - 1]; - - // now access the generic static field - ptr = typeSpecAsm->GetGenericStaticField(*field.genericType, genericField); + ptr = field.assembly->GetStaticFieldByFieldDef(field, field.genericType); } else { From 4a8f5c4d4fce7c6801ba173bab84ed0991e84585 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Thu, 9 Oct 2025 13:49:48 +0100 Subject: [PATCH 5/5] Storage of static fields for generic types is now global --- src/CLR/Core/TypeSystem.cpp | 360 ++++++++++++++++++++++++++---- src/CLR/Include/nanoCLR_Runtime.h | 46 ++++ 2 files changed, 366 insertions(+), 40 deletions(-) diff --git a/src/CLR/Core/TypeSystem.cpp b/src/CLR/Core/TypeSystem.cpp index 654f0ba8cb..e8706b4b1a 100644 --- a/src/CLR/Core/TypeSystem.cpp +++ b/src/CLR/Core/TypeSystem.cpp @@ -4020,6 +4020,32 @@ void CLR_RT_Assembly::ResolveLink() } } +CLR_RT_TypeDef_Index CLR_RT_Assembly::GenericDefFromHash(CLR_UINT32 hash) +{ + NATIVE_PROFILE_CLR_CORE(); + + // Search through all TypeDefs in the assembly to find one with a matching hash + for (int i = 0; i < tablesSize[TBL_TypeDef]; i++) + { + CLR_RT_TypeDef_Index typeDef; + typeDef.Set(assemblyIndex, i); + + CLR_RT_TypeDef_Instance inst; + if (inst.InitializeFromIndex(typeDef)) + { + if (inst.CrossReference().hash == hash) + { + return typeDef; + } + } + } + + // No match found + CLR_RT_TypeDef_Index emptyIndex; + emptyIndex.Clear(); + return emptyIndex; +} + //--// #if defined(NANOCLR_APPDOMAINS) @@ -4926,13 +4952,13 @@ HRESULT CLR_RT_Assembly::ResolveAllocateGenericTypeStaticFields() NANOCLR_HEADER(); // For each TypeSpec that represents a closed generic instantiation, count the static fields bound to it, - // allocate CLR_RT_HeapBlock array and initialize references into it. + // and either allocate new CLR_RT_HeapBlock array or reuse existing global registry entry. CLR_RT_TypeSpec_CrossReference *tsCross = this->crossReferenceTypeSpec; int numTypeSpec = this->tablesSize[TBL_TypeSpec]; for (int iTs = 0; iTs < numTypeSpec; iTs++, tsCross++) { - // skip if already allocated or if TypeSpec is not a generic instantiation + // Skip if already allocated if (tsCross->genericStaticFields != nullptr) { continue; @@ -4945,7 +4971,7 @@ HRESULT CLR_RT_Assembly::ResolveAllocateGenericTypeStaticFields() if (!genericTypeInstance.InitializeFromIndex(tsIndex)) { - // can't initialize, skip + // Can't initialize, skip continue; } @@ -4964,44 +4990,125 @@ HRESULT CLR_RT_Assembly::ResolveAllocateGenericTypeStaticFields() const char *typeName = ownerAsm->GetString(ownerTd->name); #endif - // get how many static FieldDefs are bound to the generic type definition - int count = ownerTd->staticFieldsCount; + // Get how many static FieldDefs are bound to the generic type definition + uint8_t count = ownerTd->staticFieldsCount; if (count == 0) { - // no static fields in this generic type definition, nothing to do + // No static fields in this generic type definition, nothing to do + continue; + } + + // Compute a hash for the closed generic type + CLR_UINT32 hash = g_CLR_RT_TypeSystem.ComputeHashForClosedGenericType(genericTypeInstance); + + // Look for existing entry in the global registry + bool found = false; + + for (CLR_UINT32 i = 0; i < g_CLR_RT_TypeSystem.m_genericStaticFieldsCount; i++) + { + if (g_CLR_RT_TypeSystem.m_genericStaticFields[i].m_hash == hash) + { + // Found existing entry, reuse it + tsCross->genericStaticFields = g_CLR_RT_TypeSystem.m_genericStaticFields[i].m_fields; + tsCross->genericStaticFieldDefs = g_CLR_RT_TypeSystem.m_genericStaticFields[i].m_fieldDefs; + tsCross->genericStaticFieldsCount = g_CLR_RT_TypeSystem.m_genericStaticFields[i].m_count; + + found = true; + break; + } + } + + if (found) + { + // Already found and reused an existing entry continue; } - CLR_RT_HeapBlock *pHeap = g_CLR_RT_ExecutionEngine.ExtractHeapBlocksForObjects( - DATATYPE_OBJECT, // heapblock kind (object/value choice is OK; we just need heapblocks) + // Need to create a new entry + + // Check if we need to expand the array + if (g_CLR_RT_TypeSystem.m_genericStaticFieldsCount >= g_CLR_RT_TypeSystem.m_genericStaticFieldsMaxCount) + { + // Expand the array + CLR_UINT32 newMax = g_CLR_RT_TypeSystem.m_genericStaticFieldsMaxCount * 2; + + if (newMax == 0) + { + // use 4 as the initial minimum size to avoid too many reallocs + newMax = 4; + } + + CLR_RT_GenericStaticFieldRecord *newArray = + (CLR_RT_GenericStaticFieldRecord *)platform_malloc(sizeof(CLR_RT_GenericStaticFieldRecord) * newMax); + + if (newArray == nullptr) + { + NANOCLR_SET_AND_LEAVE(CLR_E_OUT_OF_MEMORY); + } + + // Copy existing records + if (g_CLR_RT_TypeSystem.m_genericStaticFields != nullptr && + g_CLR_RT_TypeSystem.m_genericStaticFieldsCount > 0) + { + memcpy( + newArray, + g_CLR_RT_TypeSystem.m_genericStaticFields, + sizeof(CLR_RT_GenericStaticFieldRecord) * g_CLR_RT_TypeSystem.m_genericStaticFieldsCount); + + platform_free(g_CLR_RT_TypeSystem.m_genericStaticFields); + } + + g_CLR_RT_TypeSystem.m_genericStaticFields = newArray; + g_CLR_RT_TypeSystem.m_genericStaticFieldsMaxCount = newMax; + } + + // Allocate storage for the static fields + CLR_RT_HeapBlock *fields = g_CLR_RT_ExecutionEngine.ExtractHeapBlocksForObjects( + DATATYPE_OBJECT, // heapblock kind 0, // flags count); // number of CLR_RT_HeapBlock entries - CHECK_ALLOCATION(pHeap); - CLR_RT_FieldDef_Index *pFieldDefs = - (CLR_RT_FieldDef_Index *)g_CLR_RT_ExecutionEngine.ExtractHeapBytesForObjects( - DATATYPE_BOOLEAN, - 0, - (sizeof(CLR_RT_FieldDef_Index) * count)); - CHECK_ALLOCATION(pFieldDefs); + if (fields == nullptr) + { + NANOCLR_SET_AND_LEAVE(CLR_E_OUT_OF_MEMORY); + } + + // Allocate mapping for field definitions + CLR_RT_FieldDef_Index *fieldDefs = + (CLR_RT_FieldDef_Index *)platform_malloc(sizeof(CLR_RT_FieldDef_Index) * count); - // fill mapping and initialize each heap slot using the owner assembly's field defs - for (int staticField = 0; staticField < count; staticField++) + if (fieldDefs == nullptr) { - CLR_INDEX fieldIndex = ownerTd->firstStaticField + staticField; - pFieldDefs[staticField].Set(ownerAsm->assemblyIndex, fieldIndex); + // Free already allocated fields + // Since we don't have a direct ReleaseHeapBlocksForObjects function, + // we'll need to have the GC clean it up later + NANOCLR_SET_AND_LEAVE(CLR_E_OUT_OF_MEMORY); + } - // initialize the storage: call InitializeReference for that field def (ownerAsm context) - const CLR_RECORD_FIELDDEF *pFd = ownerAsm->GetFieldDef(fieldIndex); + // Initialize the record + CLR_RT_GenericStaticFieldRecord *record = + &g_CLR_RT_TypeSystem.m_genericStaticFields[g_CLR_RT_TypeSystem.m_genericStaticFieldsCount++]; + record->m_hash = hash; + record->m_fields = fields; + record->m_fieldDefs = fieldDefs; + record->m_count = count; - g_CLR_RT_ExecutionEngine.InitializeReference(pHeap[staticField], pFd, ownerAsm, &genericTypeInstance); + // Initialize field definitions and values + for (CLR_UINT32 i = 0; i < count; i++) + { + CLR_INDEX fieldIndex = ownerTd->firstStaticField + i; + fieldDefs[i].Set(ownerAsm->assemblyIndex, fieldIndex); + + // Initialize the storage using the field definition + const CLR_RECORD_FIELDDEF *pFd = ownerAsm->GetFieldDef(fieldIndex); + g_CLR_RT_ExecutionEngine.InitializeReference(fields[i], pFd, ownerAsm); } - // store in typespec crossref (this storage is per-assembly typespec) - tsCross->genericStaticFields = pHeap; - tsCross->genericStaticFieldDefs = pFieldDefs; - tsCross->genericStaticFieldsCount = (CLR_UINT32)count; + // Link this assembly's cross-reference to the global registry entry + tsCross->genericStaticFields = record->m_fields; + tsCross->genericStaticFieldDefs = record->m_fieldDefs; + tsCross->genericStaticFieldsCount = record->m_count; } NANOCLR_NOCLEANUP(); @@ -5861,21 +5968,10 @@ void CLR_RT_Assembly::Relocate() CLR_RT_GarbageCollector::Heap_Relocate(staticFields, staticFieldsCount); #endif - // relocate per-TypeSpec generic statics if any - for (int i = 0; i < tablesSize[TBL_TypeSpec]; i++) + // Relocate all generic static field entries + for (CLR_UINT32 i = 0; i < g_CLR_RT_TypeSystem.m_genericStaticFieldsCount; i++) { - CLR_RT_TypeSpec_CrossReference &ts = crossReferenceTypeSpec[i]; - - if (ts.genericStaticFields) - { - CLR_RT_GarbageCollector::Heap_Relocate(ts.genericStaticFields, ts.genericStaticFieldsCount); - } - - if (ts.genericStaticFieldDefs) - { - // pointer relocation for the indices array - CLR_RT_GarbageCollector::Heap_Relocate((void **)&ts.genericStaticFieldDefs); - } + CLR_RT_GarbageCollector::RelocateGenericStaticField(&g_CLR_RT_TypeSystem.m_genericStaticFields[i]); } CLR_RT_GarbageCollector::Heap_Relocate((void **)&header); @@ -5891,6 +5987,11 @@ void CLR_RT_TypeSystem::TypeSystem_Initialize() NATIVE_PROFILE_CLR_CORE(); NANOCLR_CLEAR(g_CLR_RT_TypeSystem); NANOCLR_CLEAR(g_CLR_RT_WellKnownTypes); + + // Initialize generic static field registry + g_CLR_RT_TypeSystem.m_genericStaticFields = nullptr; + g_CLR_RT_TypeSystem.m_genericStaticFieldsCount = 0; + g_CLR_RT_TypeSystem.m_genericStaticFieldsMaxCount = 0; } void CLR_RT_TypeSystem::TypeSystem_Cleanup() @@ -5905,6 +6006,25 @@ void CLR_RT_TypeSystem::TypeSystem_Cleanup() NANOCLR_FOREACH_ASSEMBLY_END(); m_assembliesMax = 0; + + // Clean up generic static field registry + if (m_genericStaticFields != nullptr) + { + // Free individual field definitions memory + for (CLR_UINT32 i = 0; i < m_genericStaticFieldsCount; i++) + { + if (m_genericStaticFields[i].m_fieldDefs != nullptr) + { + platform_free(m_genericStaticFields[i].m_fieldDefs); + } + } + + // Free the entire array + platform_free(m_genericStaticFields); + m_genericStaticFields = nullptr; + m_genericStaticFieldsCount = 0; + m_genericStaticFieldsMaxCount = 0; + } } //--// @@ -7422,6 +7542,166 @@ CLR_UINT32 CLR_RT_TypeSystem::MapDataTypeToElementType(NanoCLRDataType dt) return c_CLR_RT_DataTypeLookup[dt].m_convertToElementType; } +CLR_RT_GenericStaticFieldRecord *CLR_RT_TypeSystem::FindOrCreateGenericStaticFields( + CLR_UINT32 hash, + CLR_RT_Assembly *ownerAssembly, + CLR_UINT32 staticFieldCount) +{ + NATIVE_PROFILE_CLR_CORE(); + + // Check if we already have this hash in the registry + for (CLR_UINT32 i = 0; i < m_genericStaticFieldsCount; i++) + { + if (m_genericStaticFields[i].m_hash == hash) + { + return &m_genericStaticFields[i]; + } + } + + // Need to create a new entry + + // First, ensure we have room in the registry + if (m_genericStaticFieldsCount >= m_genericStaticFieldsMaxCount) + { + // Need to grow the registry + CLR_UINT32 newMax = m_genericStaticFieldsMaxCount * 2; + if (newMax == 0) + newMax = 16; // Initial size + + CLR_RT_GenericStaticFieldRecord *newArray = + (CLR_RT_GenericStaticFieldRecord *)platform_malloc(sizeof(CLR_RT_GenericStaticFieldRecord) * newMax); + + if (newArray == nullptr) + { + return nullptr; // Out of memory + } + + // Copy existing records + if (m_genericStaticFields != nullptr && m_genericStaticFieldsCount > 0) + { + memcpy( + newArray, + m_genericStaticFields, + sizeof(CLR_RT_GenericStaticFieldRecord) * m_genericStaticFieldsCount); + + platform_free(m_genericStaticFields); + } + + m_genericStaticFields = newArray; + m_genericStaticFieldsMaxCount = newMax; + } + + // Find the generic type definition by hash + CLR_RT_TypeDef_Index typeDef = ownerAssembly->GenericDefFromHash(hash); + if (!NANOCLR_INDEX_IS_VALID(typeDef)) + { + // Could not find the type definition + return nullptr; + } + + // Get the type definition record + const CLR_RECORD_TYPEDEF *ownerTd = ownerAssembly->GetTypeDef(typeDef.Type()); + + // Allocate storage for the static fields + CLR_RT_HeapBlock *pFields = g_CLR_RT_ExecutionEngine.ExtractHeapBlocksForObjects( + DATATYPE_OBJECT, // Use OBJECT type for proper GC management + 0, // No special flags + staticFieldCount // Number of fields + ); + + if (pFields == nullptr) + { + return nullptr; // Out of memory + } + + // Allocate mapping for field definitions using platform_malloc since we need to manage this memory separately + CLR_RT_FieldDef_Index *pFieldDefs = + (CLR_RT_FieldDef_Index *)platform_malloc(sizeof(CLR_RT_FieldDef_Index) * staticFieldCount); + + if (pFieldDefs == nullptr) + { + // Unable to allocate field definitions, must clean up the fields we already allocated + // Since ExtractHeapBlocksForObjects allocates memory that's managed by the GC, + // we don't explicitly free it. The next GC cycle will reclaim it. + + // Reset the allocated fields to null to ensure no dangling references + for (CLR_UINT32 i = 0; i < staticFieldCount; i++) + { + pFields[i].SetObjectReference(nullptr); + } + + return nullptr; // Out of memory + } + + // Initialize the record + CLR_RT_GenericStaticFieldRecord *record = &m_genericStaticFields[m_genericStaticFieldsCount++]; + record->m_hash = hash; + record->m_fields = pFields; + record->m_fieldDefs = pFieldDefs; + record->m_count = staticFieldCount; + + // Initialize field definitions and values + for (CLR_UINT32 i = 0; i < staticFieldCount; i++) + { + CLR_INDEX fieldIndex = ownerTd->firstStaticField + i; + pFieldDefs[i].Set(ownerAssembly->assemblyIndex, fieldIndex); + + // Initialize the storage using the field definition + const CLR_RECORD_FIELDDEF *pFd = ownerAssembly->GetFieldDef(fieldIndex); + g_CLR_RT_ExecutionEngine.InitializeReference(pFields[i], pFd, ownerAssembly); + } + + return record; +} + +CLR_UINT32 CLR_RT_TypeSystem::ComputeHashForClosedGenericType(CLR_RT_TypeSpec_Instance &typeInstance) +{ + CLR_UINT32 hash = 0; + + // Start with the generic type definition + hash = SUPPORT_ComputeCRC(&typeInstance.genericTypeDef.data, sizeof(CLR_UINT32), hash); + + // Parse the TypeSpec signature to extract generic arguments + CLR_RT_SignatureParser parser; + parser.Initialize_TypeSpec(typeInstance); + + CLR_RT_SignatureParser::Element elem; + + // Advance to the generic instance marker + if (FAILED(parser.Advance(elem)) || elem.DataType != DATATYPE_GENERICINST) + { + return hash; + } + + // Advance to the generic type definition + if (FAILED(parser.Advance(elem))) + { + return hash; + } + + // Get argument count + int argCount = elem.GenParamCount; + + // Process each generic argument + for (int i = 0; i < argCount; i++) + { + if (FAILED(parser.Advance(elem))) + { + break; + } + + // Add each argument's type information to the hash + hash = SUPPORT_ComputeCRC(&elem.DataType, sizeof(elem.DataType), hash); + + if (elem.DataType == DATATYPE_CLASS || elem.DataType == DATATYPE_VALUETYPE) + { + hash = SUPPORT_ComputeCRC(&elem.Class.data, sizeof(elem.Class.data), hash); + } + } + + return hash ? hash : 0xFFFFFFFF; // Don't allow zero as a hash value +} + //--// void CLR_RT_AttributeEnumerator::Initialize(CLR_RT_Assembly *assm) diff --git a/src/CLR/Include/nanoCLR_Runtime.h b/src/CLR/Include/nanoCLR_Runtime.h index 1349b9de77..efac6be268 100644 --- a/src/CLR/Include/nanoCLR_Runtime.h +++ b/src/CLR/Include/nanoCLR_Runtime.h @@ -1493,6 +1493,7 @@ struct CLR_RT_Assembly : public CLR_RT_HeapBlock_Node // EVENT HEAP - NO RELOCAT //--// + CLR_RT_TypeDef_Index GenericDefFromHash(CLR_UINT32 hash); CLR_RT_HeapBlock *GetStaticField(const int index); //--// @@ -1970,6 +1971,23 @@ extern const CLR_RT_LogicalOpcodeLookup c_CLR_RT_LogicalOpcodeLookup[]; //--// +struct CLR_RT_GenericStaticFieldRecord +{ + // Unique identifier for this closed generic type + CLR_UINT32 m_hash; + + // Storage for static fields + CLR_RT_HeapBlock *m_fields; + + // Field definition mapping + CLR_RT_FieldDef_Index *m_fieldDefs; + + // Number of static fields + CLR_UINT32 m_count; +}; + +//--// + struct CLR_RT_TypeSystem // EVENT HEAP - NO RELOCATION - { struct CompatibilityLookup @@ -1996,6 +2014,11 @@ struct CLR_RT_TypeSystem // EVENT HEAP - NO RELOCATION - CLR_RT_MethodDef_Index m_entryPoint; + // Global registry for generic static fields + CLR_RT_GenericStaticFieldRecord *m_genericStaticFields; + CLR_UINT32 m_genericStaticFieldsCount; + CLR_UINT32 m_genericStaticFieldsMaxCount; + //--// void TypeSystem_Initialize(); @@ -2083,6 +2106,14 @@ struct CLR_RT_TypeSystem // EVENT HEAP - NO RELOCATION - void Dump(const wchar_t *szFileName, bool fNoByteCode); #endif + CLR_RT_GenericStaticFieldRecord *FindOrCreateGenericStaticFields( + CLR_UINT32 hash, + CLR_RT_Assembly *ownerAssembly, + CLR_UINT32 staticFieldCount); + + // Helper to compute hash for a closed generic type + static CLR_UINT32 ComputeHashForClosedGenericType(CLR_RT_TypeSpec_Instance &typeInstance); + //--// PROHIBIT_COPY_CONSTRUCTORS(CLR_RT_TypeSystem); @@ -3006,6 +3037,21 @@ struct CLR_RT_GarbageCollector //--// + static void RelocateGenericStaticField(CLR_RT_GenericStaticFieldRecord *field) + { + if (field->m_fields) + { + CLR_RT_GarbageCollector::Heap_Relocate(field->m_fields, field->m_count); + } + + if (field->m_fieldDefs) + { + CLR_RT_GarbageCollector::Heap_Relocate((void **)&field->m_fieldDefs); + } + } + + //--// + #if NANOCLR_VALIDATE_HEAP >= NANOCLR_VALIDATE_HEAP_3_Compaction static bool Relocation_JustCheck(void **ref);