From f78de42018eb97a0dfebeb10e9824eec7769f278 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Thu, 6 Nov 2025 14:24:02 +0000 Subject: [PATCH 01/61] Implements dispatching of IList generic methods to SZArrayHelper --- src/CLR/Core/TypeSystem.cpp | 49 +++++++++++++++++++++++++++++++ src/CLR/Include/nanoCLR_Runtime.h | 1 + 2 files changed, 50 insertions(+) diff --git a/src/CLR/Core/TypeSystem.cpp b/src/CLR/Core/TypeSystem.cpp index f1948f1da0..7adff19313 100644 --- a/src/CLR/Core/TypeSystem.cpp +++ b/src/CLR/Core/TypeSystem.cpp @@ -4765,6 +4765,7 @@ static const TypeIndexLookup c_TypeIndexLookup[] = { TIL("System", "MulticastDelegate", MulticastDelegate), TIL("System", "Array", Array), + TIL(nullptr, "SZArrayHelper", SZArrayHelper), TIL("System.Collections", "ArrayList", ArrayList), TIL("System", "ICloneable", ICloneable), TIL("System.Collections", "IList", IList), @@ -7548,6 +7549,8 @@ bool CLR_RT_TypeSystem::FindVirtualMethodDef( CLR_RT_MethodDef_Instance calleeInst{}; calleeInst.InitializeFromIndex(calleeMD); + const bool isArrayClass = (cls.data == g_CLR_RT_WellKnownTypes.Array.data); + const CLR_RECORD_METHODDEF *calleeMDR = calleeInst.target; CLR_UINT8 calleeArgumentsCount = calleeMDR->argumentsCount; @@ -7592,6 +7595,52 @@ bool CLR_RT_TypeSystem::FindVirtualMethodDef( clsInst.SwitchToParent(); } + // SZ arrays expose IList generic interfaces through System.Array+SZArrayHelper, so fall back to that helper type + if (isArrayClass) + { + const CLR_RT_TypeDef_Index &arrayHelperIdx = g_CLR_RT_WellKnownTypes.SZArrayHelper; + + if (NANOCLR_INDEX_IS_VALID(arrayHelperIdx)) + { + CLR_RT_TypeDef_Instance helperInst{}; + helperInst.InitializeFromIndex(arrayHelperIdx); + + CLR_RT_Assembly *arrayHelperAssm = helperInst.assembly; + const CLR_RECORD_TYPEDEF *arrayHelperTypeDef = helperInst.target; + + if (arrayHelperAssm != nullptr && arrayHelperTypeDef != nullptr) + { + int methodCount = arrayHelperTypeDef->virtualMethodCount + arrayHelperTypeDef->instanceMethodCount; + + if (methodCount > 0 && arrayHelperTypeDef->firstMethod != CLR_EmptyIndex) + { + const CLR_RECORD_METHODDEF *arrayHelperMethodDef = + arrayHelperAssm->GetMethodDef(arrayHelperTypeDef->firstMethod); + + for (int i = 0; i < methodCount; i++, arrayHelperMethodDef++) + { + if ((arrayHelperMethodDef->flags & CLR_RECORD_METHODDEF::MD_Static) != 0) + { + continue; + } + + if (arrayHelperMethodDef->argumentsCount != calleeArgumentsCount) + { + continue; + } + + const char *methodNameAtHelper = arrayHelperAssm->GetString(arrayHelperMethodDef->name); + if (methodNameAtHelper != nullptr && strcmp(methodNameAtHelper, calleeName) == 0) + { + index.Set(arrayHelperAssm->assemblyIndex, arrayHelperTypeDef->firstMethod + i); + return true; + } + } + } + } + } + } + index.Clear(); return false; diff --git a/src/CLR/Include/nanoCLR_Runtime.h b/src/CLR/Include/nanoCLR_Runtime.h index b22d291ea1..ca0d1ae3d9 100644 --- a/src/CLR/Include/nanoCLR_Runtime.h +++ b/src/CLR/Include/nanoCLR_Runtime.h @@ -1743,6 +1743,7 @@ struct CLR_RT_WellKnownTypes CLR_RT_TypeDef_Index MulticastDelegate; CLR_RT_TypeDef_Index Array; + CLR_RT_TypeDef_Index SZArrayHelper; CLR_RT_TypeDef_Index ArrayList; CLR_RT_TypeDef_Index ICloneable; CLR_RT_TypeDef_Index IList; From 450552e8e19833980bf2569f40cdea1fde9dfdf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Fri, 7 Nov 2025 12:02:32 +0000 Subject: [PATCH 02/61] Fix building type name with MVAR param --- src/CLR/Core/TypeSystem.cpp | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/CLR/Core/TypeSystem.cpp b/src/CLR/Core/TypeSystem.cpp index 7adff19313..5e61183459 100644 --- a/src/CLR/Core/TypeSystem.cpp +++ b/src/CLR/Core/TypeSystem.cpp @@ -7082,19 +7082,29 @@ HRESULT CLR_RT_TypeSystem::BuildTypeName( if (element.DataType == DATATYPE_VAR) { - // resolve the !T against our *closed* typeIndex - CLR_RT_TypeDef_Index realTd; - NanoCLRDataType realDt; + // resolve the !T against our *closed* typeIndex, if possible + CLR_RT_TypeDef_Index paramTd; + NanoCLRDataType paramDt; // this will bind !T→System.Int32, etc. typeSpecInstance.assembly->FindGenericParamAtTypeSpec( typeIndex.data, element.GenericParamPosition, // the !N slot - realTd, - realDt); + paramTd, + paramDt); - // now print the *actual* type name - BuildTypeName(realTd, szBuffer, iBuffer); + if (paramDt == DATATYPE_VAR) + { + // couldn't be resolved, print encoded form (!N) + char encodedParam[6]; + snprintf(encodedParam, ARRAYSIZE(encodedParam), "!%d", element.GenericParamPosition); + NANOCLR_CHECK_HRESULT(QueueStringToBuffer(szBuffer, iBuffer, encodedParam)); + } + else + { + // now print the *actual* type name + BuildTypeName(paramTd, szBuffer, iBuffer); + } } else { @@ -7376,6 +7386,7 @@ HRESULT CLR_RT_TypeSystem::BuildMethodRefName( NANOCLR_NOCLEANUP(); } + HRESULT CLR_RT_TypeSystem::BuildMethodSpecName(const CLR_RT_MethodSpec_Index &ms, char *&szBuffer, size_t &iBuffer) { NATIVE_PROFILE_CLR_CORE(); From c2e0be8e199b070cdb80f13d39829ea75942418d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Fri, 7 Nov 2025 14:52:00 +0000 Subject: [PATCH 03/61] Implement runtime array element type resolution for generic parameters --- src/CLR/Core/TypeSystem.cpp | 110 +++++++++++++++++++++--------- src/CLR/Include/nanoCLR_Runtime.h | 3 + 2 files changed, 82 insertions(+), 31 deletions(-) diff --git a/src/CLR/Core/TypeSystem.cpp b/src/CLR/Core/TypeSystem.cpp index 5e61183459..2f20fe1cfb 100644 --- a/src/CLR/Core/TypeSystem.cpp +++ b/src/CLR/Core/TypeSystem.cpp @@ -1229,25 +1229,54 @@ bool CLR_RT_TypeDef_Instance::ResolveToken( // Use the *caller's* bound genericType (Stack, etc.) if (caller == nullptr || caller->genericType == nullptr) { - return false; + // No generic context available, try arrayElementType fallback + if (caller && NANOCLR_INDEX_IS_VALID(caller->arrayElementType) && pos == 0) + { + // For SZArrayHelper, position 0 is the array element type + data = caller->arrayElementType.data; + assembly = + g_CLR_RT_TypeSystem.m_assemblies[caller->arrayElementType.Assembly() - 1]; + target = assembly->GetTypeDef(caller->arrayElementType.Type()); + } + else + { + return false; + } + } + else + { + CLR_RT_TypeDef_Index realTypeDef; + NanoCLRDataType realDataType; + + // Try to map using the generic context (e.g. !T→Int32) + g_CLR_RT_TypeSystem.m_assemblies[caller->genericType->Assembly() - 1] + ->FindGenericParamAtTypeSpec( + caller->genericType->data, + (CLR_UINT32)pos, + realTypeDef, + realDataType); + + // Check if FindGenericParamAtTypeSpec succeeded + if (NANOCLR_INDEX_IS_VALID(realTypeDef)) + { + // Successfully resolved from generic context + data = realTypeDef.data; + assembly = g_CLR_RT_TypeSystem.m_assemblies[realTypeDef.Assembly() - 1]; + target = assembly->GetTypeDef(realTypeDef.Type()); + } + else if (NANOCLR_INDEX_IS_VALID(caller->arrayElementType) && pos == 0) + { + // Fallback to arrayElementType for SZArrayHelper scenarios + data = caller->arrayElementType.data; + assembly = + g_CLR_RT_TypeSystem.m_assemblies[caller->arrayElementType.Assembly() - 1]; + target = assembly->GetTypeDef(caller->arrayElementType.Type()); + } + else + { + return false; + } } - - CLR_RT_TypeDef_Index realTypeDef; - NanoCLRDataType realDataType; - - // Only call this once to map (e.g. !T→Int32) - - g_CLR_RT_TypeSystem.m_assemblies[caller->genericType->Assembly() - 1] - ->FindGenericParamAtTypeSpec( - caller->genericType->data, - (CLR_UINT32)pos, - realTypeDef, - realDataType); - - // populate this instance - data = realTypeDef.data; - assembly = g_CLR_RT_TypeSystem.m_assemblies[realTypeDef.Assembly() - 1]; - target = assembly->GetTypeDef(realTypeDef.Type()); } else if (elem.DataType == DATATYPE_MVAR) @@ -1591,6 +1620,7 @@ bool CLR_RT_MethodDef_Instance::InitializeFromIndex(const CLR_RT_MethodDef_Index assembly = g_CLR_RT_TypeSystem.m_assemblies[Assembly() - 1]; target = assembly->GetMethodDef(Method()); genericType = nullptr; + arrayElementType.Clear(); #if defined(NANOCLR_INSTANCE_NAMES) name = assembly->GetString(target->name); @@ -1602,6 +1632,7 @@ bool CLR_RT_MethodDef_Instance::InitializeFromIndex(const CLR_RT_MethodDef_Index assembly = nullptr; target = nullptr; genericType = nullptr; + arrayElementType.Clear(); return false; } @@ -1726,6 +1757,7 @@ void CLR_RT_MethodDef_Instance::ClearInstance() assembly = nullptr; target = nullptr; genericType = nullptr; + arrayElementType.Clear(); } bool CLR_RT_MethodDef_Instance::ResolveToken( @@ -2385,9 +2417,27 @@ HRESULT CLR_RT_TypeDescriptor::InitializeFromSignatureToken( CLR_RT_TypeDef_Index td; NanoCLRDataType dt; - g_CLR_RT_TypeSystem.m_assemblies[caller->genericType->Assembly() - 1] - ->FindGenericParamAtTypeSpec(caller->genericType->data, elem.GenericParamPosition, td, dt); - this->InitializeFromTypeDef(td); + // Try to resolve from generic context first + if (caller && caller->genericType && NANOCLR_INDEX_IS_VALID(*caller->genericType)) + { + g_CLR_RT_TypeSystem.m_assemblies[caller->genericType->Assembly() - 1] + ->FindGenericParamAtTypeSpec(caller->genericType->data, elem.GenericParamPosition, td, dt); + } + + // Check if resolution succeeded, otherwise try arrayElementType fallback + if (NANOCLR_INDEX_IS_VALID(td)) + { + this->InitializeFromTypeDef(td); + } + else if (caller && NANOCLR_INDEX_IS_VALID(caller->arrayElementType) && elem.GenericParamPosition == 0) + { + // Fallback for SZArrayHelper scenarios where position 0 is the array element type + this->InitializeFromTypeDef(caller->arrayElementType); + } + else + { + NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE); + } } else if (elem.DataType == DATATYPE_MVAR) { @@ -4282,11 +4332,10 @@ HRESULT CLR_RT_AppDomain::GetManagedObject(CLR_RT_HeapBlock &res) pRes = res.Dereference(); - NANOCLR_CHECK_HRESULT( - CLR_RT_ObjectToEvent_Source::CreateInstance( - this, - *pRes, - pRes[Library_corlib_native_System_AppDomain::FIELD___appDomain])); + NANOCLR_CHECK_HRESULT(CLR_RT_ObjectToEvent_Source::CreateInstance( + this, + *pRes, + pRes[Library_corlib_native_System_AppDomain::FIELD___appDomain])); pRes[Library_corlib_native_System_AppDomain::FIELD___friendlyName].SetObjectReference(m_strName); } @@ -8119,11 +8168,10 @@ HRESULT CLR_RT_AttributeParser::Next(Value *&res) } // instantiate array to hold parameters values - NANOCLR_CHECK_HRESULT( - CLR_RT_HeapBlock_Array::CreateInstance( - m_lastValue.m_value, - paramCount, - g_CLR_RT_WellKnownTypes.Object)); + NANOCLR_CHECK_HRESULT(CLR_RT_HeapBlock_Array::CreateInstance( + m_lastValue.m_value, + paramCount, + g_CLR_RT_WellKnownTypes.Object)); // get a pointer to the first element auto *currentParam = (CLR_RT_HeapBlock *)m_lastValue.m_value.DereferenceArray()->GetFirstElement(); diff --git a/src/CLR/Include/nanoCLR_Runtime.h b/src/CLR/Include/nanoCLR_Runtime.h index ca0d1ae3d9..21d68a3e92 100644 --- a/src/CLR/Include/nanoCLR_Runtime.h +++ b/src/CLR/Include/nanoCLR_Runtime.h @@ -2251,6 +2251,9 @@ struct CLR_RT_MethodDef_Instance : public CLR_RT_MethodDef_Index const CLR_RT_TypeSpec_Index *genericType; CLR_RT_MethodSpec_Index methodSpec; + + // For SZArrayHelper rebind: stores the array element TypeDef when dispatching from arrays + CLR_RT_TypeDef_Index arrayElementType; #if defined(NANOCLR_INSTANCE_NAMES) const char *name; From 52426c9d481828b61e83346bb61c86d9d8a2e2ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Fri, 7 Nov 2025 14:52:27 +0000 Subject: [PATCH 04/61] Fix propagation of array element type during execution --- src/CLR/Core/Interpreter.cpp | 88 +++++++++++++++++++++++++++++++++++- 1 file changed, 86 insertions(+), 2 deletions(-) diff --git a/src/CLR/Core/Interpreter.cpp b/src/CLR/Core/Interpreter.cpp index a56339d1de..baee00b101 100644 --- a/src/CLR/Core/Interpreter.cpp +++ b/src/CLR/Core/Interpreter.cpp @@ -2108,7 +2108,18 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) { FETCH_ARG_COMPRESSED_METHODTOKEN(arg, ip); + // Save arrayElementType for propagation through the call chain + // This will be used by ResolveToken and later restored after InitializeFromIndex calls + CLR_RT_TypeDef_Index propagatedArrayElementType{}; + if (NANOCLR_INDEX_IS_VALID(stack->m_call.arrayElementType)) + { + propagatedArrayElementType = stack->m_call.arrayElementType; + } + CLR_RT_MethodDef_Instance calleeInst{}; + // Set arrayElementType before ResolveToken so generic type resolution can use it + calleeInst.arrayElementType = propagatedArrayElementType; + if (calleeInst.ResolveToken(arg, assm, stack->m_call.genericType) == false) { NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE); @@ -2132,6 +2143,12 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) { calleeInst.InitializeFromIndex(dlg->DelegateFtn()); + // Restore propagated arrayElementType after InitializeFromIndex + if (NANOCLR_INDEX_IS_VALID(propagatedArrayElementType)) + { + calleeInst.arrayElementType = propagatedArrayElementType; + } + if ((calleeInst.target->flags & CLR_RECORD_METHODDEF::MD_Static) == 0) { pThis->Assign(dlg->m_object); @@ -2211,6 +2228,36 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE); } + // If this is an SZArrayHelper dispatch, populate arrayElementType from runtime + // array + CLR_RT_TypeDef_Index savedArrayElementType{}; + CLR_RT_HeapBlock_Array *pArray = + (CLR_RT_HeapBlock_Array *)pThis[0].Dereference(); + if (pArray && pArray->DataType() == DATATYPE_SZARRAY) + { + // Check if the dispatched method belongs to SZArrayHelper + CLR_RT_MethodDef_Instance calleeRealInst{}; + calleeRealInst.InitializeFromIndex(calleeReal); + + CLR_RT_TypeDef_Instance calleeType{}; + if (calleeType.InitializeFromMethod(calleeRealInst)) + { + if (calleeType.data == g_CLR_RT_WellKnownTypes.SZArrayHelper.data) + { + // Map the runtime element type to a TypeDef + NanoCLRDataType elemDataType = + (NanoCLRDataType)pArray->m_typeOfElement; + const CLR_RT_DataTypeLookup *lookup = + &c_CLR_RT_DataTypeLookup[elemDataType]; + + if (lookup->m_cls) + { + savedArrayElementType = *lookup->m_cls; + } + } + } + } + if (calleeInst.genericType && NANOCLR_INDEX_IS_VALID(*calleeInst.genericType) && calleeInst.genericType->data != CLR_EmptyToken) { @@ -2221,6 +2268,17 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) { calleeInst.InitializeFromIndex(calleeReal); } + + // Restore the array element type after reinitializing calleeInst + if (NANOCLR_INDEX_IS_VALID(savedArrayElementType)) + { + calleeInst.arrayElementType = savedArrayElementType; + } + else if (NANOCLR_INDEX_IS_VALID(propagatedArrayElementType)) + { + // Restore propagated arrayElementType from parent frame + calleeInst.arrayElementType = propagatedArrayElementType; + } } #if defined(NANOCLR_APPDOMAINS) @@ -2243,6 +2301,14 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) else #endif // NANOCLR_APPDOMAINS { + // Restore propagated arrayElementType before any path (inline or push) + // Only restore if not already set (e.g., by SZArrayHelper detection in virtual dispatch) + if (!NANOCLR_INDEX_IS_VALID(calleeInst.arrayElementType) && + NANOCLR_INDEX_IS_VALID(propagatedArrayElementType)) + { + calleeInst.arrayElementType = propagatedArrayElementType; + } + #ifndef NANOCLR_NO_IL_INLINE if (stack->PushInline(ip, assm, evalPos, calleeInst, pThis)) { @@ -2252,6 +2318,7 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) #endif WRITEBACK(stack, evalPos, ip, fDirty); + NANOCLR_CHECK_HRESULT(CLR_RT_StackFrame::Push(th, calleeInst, -1)); } @@ -2279,13 +2346,20 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) WRITEBACK(stack, evalPos, ip, fDirty); + // + // Preserve arrayElementType from returning frame to caller frame + // + CLR_RT_StackFrame *stackNext = stack->Caller(); + if (stackNext && NANOCLR_INDEX_IS_VALID(stack->m_call.arrayElementType)) + { + stackNext->m_call.arrayElementType = stack->m_call.arrayElementType; + } + // // Same kind of handler, no need to pop back out, just restart execution in place. // if (stack->m_flags & CLR_RT_StackFrame::c_CallerIsCompatibleForRet) { - CLR_RT_StackFrame *stackNext = stack->Caller(); - stack->Pop(); stack = stackNext; @@ -2379,7 +2453,17 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) { FETCH_ARG_COMPRESSED_METHODTOKEN(arg, ip); + // Save arrayElementType for propagation through the call chain + CLR_RT_TypeDef_Index propagatedArrayElementType{}; + if (NANOCLR_INDEX_IS_VALID(stack->m_call.arrayElementType)) + { + propagatedArrayElementType = stack->m_call.arrayElementType; + } + CLR_RT_MethodDef_Instance calleeInst{}; + // Set arrayElementType before ResolveToken so generic type resolution can use it + calleeInst.arrayElementType = propagatedArrayElementType; + if (calleeInst.ResolveToken(arg, assm, stack->m_call.genericType) == false) { NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE); From d92bdaa562839e89d11c7e250924fe7b40af1ed7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Mon, 10 Nov 2025 18:36:39 +0000 Subject: [PATCH 05/61] Fix resolving array element type - Now extracts the element TypeDef directly from the array's ReflectionData(), which contains the actual element type. --- src/CLR/Core/Interpreter.cpp | 14 ++++++-------- src/CLR/Core/TypeSystem.cpp | 31 +++++++++++++++++-------------- 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/src/CLR/Core/Interpreter.cpp b/src/CLR/Core/Interpreter.cpp index baee00b101..f1750c47d2 100644 --- a/src/CLR/Core/Interpreter.cpp +++ b/src/CLR/Core/Interpreter.cpp @@ -2244,15 +2244,13 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) { if (calleeType.data == g_CLR_RT_WellKnownTypes.SZArrayHelper.data) { - // Map the runtime element type to a TypeDef - NanoCLRDataType elemDataType = - (NanoCLRDataType)pArray->m_typeOfElement; - const CLR_RT_DataTypeLookup *lookup = - &c_CLR_RT_DataTypeLookup[elemDataType]; - - if (lookup->m_cls) + // Get the element type from the array's reflection data + const CLR_RT_ReflectionDef_Index &reflex = pArray->ReflectionDataConst(); + + // For a 1D array, levels == 1 and data.type is the element TypeDef + if (reflex.levels == 1 && reflex.kind == REFLECTION_TYPE) { - savedArrayElementType = *lookup->m_cls; + savedArrayElementType = reflex.data.type; } } } diff --git a/src/CLR/Core/TypeSystem.cpp b/src/CLR/Core/TypeSystem.cpp index 2f20fe1cfb..893713fb3e 100644 --- a/src/CLR/Core/TypeSystem.cpp +++ b/src/CLR/Core/TypeSystem.cpp @@ -33,7 +33,7 @@ int s_CLR_RT_fTrace_Exceptions = NANOCLR_TRACE_DEFAULT(c_CLR_RT_Trace_Info, c_CL #endif #if defined(NANOCLR_TRACE_INSTRUCTIONS) -int s_CLR_RT_fTrace_Instructions = NANOCLR_TRACE_DEFAULT(c_CLR_RT_Trace_Info, c_CLR_RT_Trace_None); +int s_CLR_RT_fTrace_Instructions = NANOCLR_TRACE_DEFAULT(c_CLR_RT_Trace_Verbose, c_CLR_RT_Trace_None); #endif #if defined(NANOCLR_GC_VERBOSE) @@ -2417,22 +2417,25 @@ HRESULT CLR_RT_TypeDescriptor::InitializeFromSignatureToken( CLR_RT_TypeDef_Index td; NanoCLRDataType dt; - // Try to resolve from generic context first - if (caller && caller->genericType && NANOCLR_INDEX_IS_VALID(*caller->genericType)) + // For SZArrayHelper scenarios, arrayElementType is authoritative for position 0 + if (caller && NANOCLR_INDEX_IS_VALID(caller->arrayElementType) && elem.GenericParamPosition == 0) { - g_CLR_RT_TypeSystem.m_assemblies[caller->genericType->Assembly() - 1] - ->FindGenericParamAtTypeSpec(caller->genericType->data, elem.GenericParamPosition, td, dt); - } - - // Check if resolution succeeded, otherwise try arrayElementType fallback - if (NANOCLR_INDEX_IS_VALID(td)) - { - this->InitializeFromTypeDef(td); + this->InitializeFromTypeDef(caller->arrayElementType); } - else if (caller && NANOCLR_INDEX_IS_VALID(caller->arrayElementType) && elem.GenericParamPosition == 0) + // Otherwise try to resolve from generic context + else if (caller && caller->genericType && NANOCLR_INDEX_IS_VALID(*caller->genericType)) { - // Fallback for SZArrayHelper scenarios where position 0 is the array element type - this->InitializeFromTypeDef(caller->arrayElementType); + g_CLR_RT_TypeSystem.m_assemblies[caller->genericType->Assembly() - 1] + ->FindGenericParamAtTypeSpec(caller->genericType->data, elem.GenericParamPosition, td, dt); + + if (NANOCLR_INDEX_IS_VALID(td)) + { + this->InitializeFromTypeDef(td); + } + else + { + NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE); + } } else { From f1cee64a90e12a0c54a29c8bc01370c60ab1883f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Mon, 10 Nov 2025 18:46:21 +0000 Subject: [PATCH 06/61] Revert unwanted change --- src/CLR/Core/TypeSystem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CLR/Core/TypeSystem.cpp b/src/CLR/Core/TypeSystem.cpp index 893713fb3e..48b3c57709 100644 --- a/src/CLR/Core/TypeSystem.cpp +++ b/src/CLR/Core/TypeSystem.cpp @@ -33,7 +33,7 @@ int s_CLR_RT_fTrace_Exceptions = NANOCLR_TRACE_DEFAULT(c_CLR_RT_Trace_Info, c_CL #endif #if defined(NANOCLR_TRACE_INSTRUCTIONS) -int s_CLR_RT_fTrace_Instructions = NANOCLR_TRACE_DEFAULT(c_CLR_RT_Trace_Verbose, c_CLR_RT_Trace_None); +int s_CLR_RT_fTrace_Instructions = NANOCLR_TRACE_DEFAULT(c_CLR_RT_Trace_Info, c_CLR_RT_Trace_None); #endif #if defined(NANOCLR_GC_VERBOSE) From 9db589edcad79f2a8bcf4c8be8f6d53f8ba2f5c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Mon, 10 Nov 2025 19:07:57 +0000 Subject: [PATCH 07/61] HasStaticConstructor now is const member --- src/CLR/Core/TypeSystem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CLR/Core/TypeSystem.cpp b/src/CLR/Core/TypeSystem.cpp index 48b3c57709..81381c04b3 100644 --- a/src/CLR/Core/TypeSystem.cpp +++ b/src/CLR/Core/TypeSystem.cpp @@ -2427,7 +2427,7 @@ HRESULT CLR_RT_TypeDescriptor::InitializeFromSignatureToken( { g_CLR_RT_TypeSystem.m_assemblies[caller->genericType->Assembly() - 1] ->FindGenericParamAtTypeSpec(caller->genericType->data, elem.GenericParamPosition, td, dt); - + if (NANOCLR_INDEX_IS_VALID(td)) { this->InitializeFromTypeDef(td); From 6259080da7f50fc96ae888f59e83b026444eaeba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Mon, 10 Nov 2025 19:11:38 +0000 Subject: [PATCH 08/61] Generic TypeSpec isn't stored iin a movable delegate field anymore --- src/CLR/Include/nanoCLR_Runtime.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/CLR/Include/nanoCLR_Runtime.h b/src/CLR/Include/nanoCLR_Runtime.h index 21d68a3e92..88fe3faa27 100644 --- a/src/CLR/Include/nanoCLR_Runtime.h +++ b/src/CLR/Include/nanoCLR_Runtime.h @@ -2602,6 +2602,11 @@ struct CLR_RT_StackFrame : public CLR_RT_HeapBlock_Node // EVENT HEAP - NO RELOC CLR_UINT32 m_flags; CLR_RT_MethodDef_Instance m_call; + + // Stable storage for generic type context (not GC-relocated) + // Used when m_call.genericType needs to point to a value that would otherwise + // be inside a GC-managed object (e.g., delegate's m_genericTypeSpec) + CLR_RT_TypeSpec_Index m_genericTypeSpecStorage; // Stable storage for generic type context (not GC-relocated) // Used when m_call.genericType needs to point to a value that would otherwise From 9e3b3df093ffad7bd6cca1447cb44ebff15bd0b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Mon, 10 Nov 2025 19:41:39 +0000 Subject: [PATCH 09/61] Add guard against duplicate execution of static generic cctor --- src/CLR/Include/nanoCLR_Runtime.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/CLR/Include/nanoCLR_Runtime.h b/src/CLR/Include/nanoCLR_Runtime.h index 88fe3faa27..0d634185ff 100644 --- a/src/CLR/Include/nanoCLR_Runtime.h +++ b/src/CLR/Include/nanoCLR_Runtime.h @@ -2035,6 +2035,11 @@ struct CLR_RT_TypeSystem // EVENT HEAP - NO RELOCATION - CLR_RT_GenericStaticFieldRecord *m_genericStaticFields; CLR_UINT32 m_genericStaticFieldsCount; CLR_UINT32 m_genericStaticFieldsMaxCount; + + // Global registry for generic .cctor execution tracking + CLR_RT_GenericCctorExecutionRecord *m_genericCctorRegistry; + CLR_UINT32 m_genericCctorRegistryCount; + CLR_UINT32 m_genericCctorRegistryMaxCount; // Global registry for generic .cctor execution tracking CLR_RT_GenericCctorExecutionRecord *m_genericCctorRegistry; @@ -2135,6 +2140,9 @@ struct CLR_RT_TypeSystem // EVENT HEAP - NO RELOCATION - // Helper to compute hash for a closed generic type static CLR_UINT32 ComputeHashForClosedGenericType(CLR_RT_TypeSpec_Instance &typeInstance); + + // Helper to find or create a generic .cctor execution record by hash + static CLR_RT_GenericCctorExecutionRecord *FindOrCreateGenericCctorRecord(CLR_UINT32 hash, bool *created); // Helper to find or create a generic .cctor execution record by hash static CLR_RT_GenericCctorExecutionRecord *FindOrCreateGenericCctorRecord(CLR_UINT32 hash, bool *created); From 2e021506124b4b1d9857283ce9c7bb2e2b9de1ce Mon Sep 17 00:00:00 2001 From: nfbot Date: Mon, 10 Nov 2025 19:46:28 +0000 Subject: [PATCH 10/61] Code style fixes Automated fixes for code style. --- src/CLR/Include/nanoCLR_Runtime.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/CLR/Include/nanoCLR_Runtime.h b/src/CLR/Include/nanoCLR_Runtime.h index 0d634185ff..fd877b1e47 100644 --- a/src/CLR/Include/nanoCLR_Runtime.h +++ b/src/CLR/Include/nanoCLR_Runtime.h @@ -2035,7 +2035,7 @@ struct CLR_RT_TypeSystem // EVENT HEAP - NO RELOCATION - CLR_RT_GenericStaticFieldRecord *m_genericStaticFields; CLR_UINT32 m_genericStaticFieldsCount; CLR_UINT32 m_genericStaticFieldsMaxCount; - + // Global registry for generic .cctor execution tracking CLR_RT_GenericCctorExecutionRecord *m_genericCctorRegistry; CLR_UINT32 m_genericCctorRegistryCount; @@ -2140,7 +2140,7 @@ struct CLR_RT_TypeSystem // EVENT HEAP - NO RELOCATION - // Helper to compute hash for a closed generic type static CLR_UINT32 ComputeHashForClosedGenericType(CLR_RT_TypeSpec_Instance &typeInstance); - + // Helper to find or create a generic .cctor execution record by hash static CLR_RT_GenericCctorExecutionRecord *FindOrCreateGenericCctorRecord(CLR_UINT32 hash, bool *created); @@ -2610,7 +2610,7 @@ struct CLR_RT_StackFrame : public CLR_RT_HeapBlock_Node // EVENT HEAP - NO RELOC CLR_UINT32 m_flags; CLR_RT_MethodDef_Instance m_call; - + // Stable storage for generic type context (not GC-relocated) // Used when m_call.genericType needs to point to a value that would otherwise // be inside a GC-managed object (e.g., delegate's m_genericTypeSpec) From 0727158a0c4bdf699db992fe657a9bad0da40ec9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Tue, 11 Nov 2025 00:35:41 +0000 Subject: [PATCH 11/61] Remove duplicated declarations --- src/CLR/Include/nanoCLR_Runtime.h | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/CLR/Include/nanoCLR_Runtime.h b/src/CLR/Include/nanoCLR_Runtime.h index fd877b1e47..17ce5660cb 100644 --- a/src/CLR/Include/nanoCLR_Runtime.h +++ b/src/CLR/Include/nanoCLR_Runtime.h @@ -2041,11 +2041,6 @@ struct CLR_RT_TypeSystem // EVENT HEAP - NO RELOCATION - CLR_UINT32 m_genericCctorRegistryCount; CLR_UINT32 m_genericCctorRegistryMaxCount; - // Global registry for generic .cctor execution tracking - CLR_RT_GenericCctorExecutionRecord *m_genericCctorRegistry; - CLR_UINT32 m_genericCctorRegistryCount; - CLR_UINT32 m_genericCctorRegistryMaxCount; - //--// void TypeSystem_Initialize(); @@ -2144,9 +2139,6 @@ struct CLR_RT_TypeSystem // EVENT HEAP - NO RELOCATION - // Helper to find or create a generic .cctor execution record by hash static CLR_RT_GenericCctorExecutionRecord *FindOrCreateGenericCctorRecord(CLR_UINT32 hash, bool *created); - // Helper to find or create a generic .cctor execution record by hash - static CLR_RT_GenericCctorExecutionRecord *FindOrCreateGenericCctorRecord(CLR_UINT32 hash, bool *created); - //--// PROHIBIT_COPY_CONSTRUCTORS(CLR_RT_TypeSystem); @@ -2612,10 +2604,6 @@ struct CLR_RT_StackFrame : public CLR_RT_HeapBlock_Node // EVENT HEAP - NO RELOC CLR_RT_MethodDef_Instance m_call; // Stable storage for generic type context (not GC-relocated) - // Used when m_call.genericType needs to point to a value that would otherwise - // be inside a GC-managed object (e.g., delegate's m_genericTypeSpec) - CLR_RT_TypeSpec_Index m_genericTypeSpecStorage; - // Stable storage for generic type context (not GC-relocated) // Used when m_call.genericType needs to point to a value that would otherwise // be inside a GC-managed object (e.g., delegate's m_genericTypeSpec) From 16e3238500dc4d576cbdba52f6c47062a1463fbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Wed, 12 Nov 2025 23:44:17 +0000 Subject: [PATCH 12/61] Fix box/unbox of generic types - Add checks for null objects and perform accordingly. --- src/CLR/Core/Interpreter.cpp | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/CLR/Core/Interpreter.cpp b/src/CLR/Core/Interpreter.cpp index f1750c47d2..df11636174 100644 --- a/src/CLR/Core/Interpreter.cpp +++ b/src/CLR/Core/Interpreter.cpp @@ -2987,7 +2987,28 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) } else { - NANOCLR_CHECK_HRESULT(evalPos[0].PerformBoxing(typeInst)); + // Check if we're trying to box a null reference for a reference type + // For generic types, the value might already be null (DATATYPE_OBJECT with null reference) + if (evalPos[0].DataType() == DATATYPE_OBJECT) + { + CLR_RT_HeapBlock *ptr = evalPos[0].Dereference(); + if (ptr == nullptr) + { + // Already a null reference, no need to box + // Just ensure it stays as a null object reference + evalPos[0].SetObjectReference(nullptr); + } + else + { + // Non-null object reference, perform boxing + NANOCLR_CHECK_HRESULT(evalPos[0].PerformBoxing(typeInst)); + } + } + else + { + // Value type or other type, perform normal boxing + NANOCLR_CHECK_HRESULT(evalPos[0].PerformBoxing(typeInst)); + } } } else @@ -3029,6 +3050,17 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) } else { + // Check if we're trying to unbox a null reference + if (evalPos[0].DataType() == DATATYPE_OBJECT) + { + CLR_RT_HeapBlock *ptr = evalPos[0].Dereference(); + if (ptr == nullptr) + { + // Attempting to unbox a null reference throws NullReferenceException + NANOCLR_SET_AND_LEAVE(CLR_E_NULL_REFERENCE); + } + } + NANOCLR_CHECK_HRESULT(evalPos[0].PerformUnboxing(typeInst)); } } From a1baeca1e5e4a497eb290f29f9b031b4a8807b27 Mon Sep 17 00:00:00 2001 From: nfbot Date: Wed, 12 Nov 2025 23:49:34 +0000 Subject: [PATCH 13/61] Code style fixes Automated fixes for code style. --- src/CLR/Core/Interpreter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CLR/Core/Interpreter.cpp b/src/CLR/Core/Interpreter.cpp index df11636174..cc4fb7ca64 100644 --- a/src/CLR/Core/Interpreter.cpp +++ b/src/CLR/Core/Interpreter.cpp @@ -3060,7 +3060,7 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) NANOCLR_SET_AND_LEAVE(CLR_E_NULL_REFERENCE); } } - + NANOCLR_CHECK_HRESULT(evalPos[0].PerformUnboxing(typeInst)); } } From 59e489e629c8418f557941622a8b1e8a9d021c40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Thu, 13 Nov 2025 14:23:23 +0000 Subject: [PATCH 14/61] Fix instanciation of generic static fields on demand - Now uses generic context for correct and complete type resolution. - Fix hash computation for field instance. - Fix call for static field getter in ldsfld, ldsflda, stsfld and callvirt. *** PR separately *** --- src/CLR/Core/CLR_RT_StackFrame.cpp | 3 + src/CLR/Core/Interpreter.cpp | 119 ++++++++++- src/CLR/Core/TypeSystem.cpp | 307 ++++++++++++++++++++++++++++- src/CLR/Include/nanoCLR_Runtime.h | 13 +- 4 files changed, 428 insertions(+), 14 deletions(-) diff --git a/src/CLR/Core/CLR_RT_StackFrame.cpp b/src/CLR/Core/CLR_RT_StackFrame.cpp index c2691bc1dc..bd26cbbdb7 100644 --- a/src/CLR/Core/CLR_RT_StackFrame.cpp +++ b/src/CLR/Core/CLR_RT_StackFrame.cpp @@ -118,6 +118,9 @@ HRESULT CLR_RT_StackFrame::Push(CLR_RT_Thread *th, const CLR_RT_MethodDef_Instan // void* m_customPointer; // }; // + // Initialize generic type context storage to invalid + stack->m_genericTypeSpecStorage.Clear(); + // #ifndef NANOCLR_NO_IL_INLINE stack->m_inlineFrame = nullptr; #endif diff --git a/src/CLR/Core/Interpreter.cpp b/src/CLR/Core/Interpreter.cpp index cc4fb7ca64..e0e4b1b332 100644 --- a/src/CLR/Core/Interpreter.cpp +++ b/src/CLR/Core/Interpreter.cpp @@ -2245,8 +2245,9 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) if (calleeType.data == g_CLR_RT_WellKnownTypes.SZArrayHelper.data) { // Get the element type from the array's reflection data - const CLR_RT_ReflectionDef_Index &reflex = pArray->ReflectionDataConst(); - + const CLR_RT_ReflectionDef_Index &reflex = + pArray->ReflectionDataConst(); + // For a 1D array, levels == 1 and data.type is the element TypeDef if (reflex.levels == 1 && reflex.kind == REFLECTION_TYPE) { @@ -2318,6 +2319,14 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) WRITEBACK(stack, evalPos, ip, fDirty); NANOCLR_CHECK_HRESULT(CLR_RT_StackFrame::Push(th, calleeInst, -1)); + + // Store the caller's generic context in the new stack frame's m_genericTypeSpecStorage + // This allows the callee to resolve open generic parameters (VAR/MVAR) in TypeSpecs + if (stack->m_call.genericType && NANOCLR_INDEX_IS_VALID(*stack->m_call.genericType)) + { + CLR_RT_StackFrame *newStack = th->CurrentFrame(); + newStack->m_genericTypeSpecStorage = *stack->m_call.genericType; + } } goto Execute_Restart; @@ -2822,7 +2831,10 @@ 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 - ptr = field.assembly->GetStaticFieldByFieldDef(field, field.genericType); + ptr = field.assembly->GetStaticFieldByFieldDef( + field, + field.genericType, + &stack->m_genericTypeSpecStorage); } else { @@ -2834,6 +2846,41 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) { NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE); } + else if (field.genericType && NANOCLR_INDEX_IS_VALID(*field.genericType)) + { + CLR_RT_HeapBlock *obj = ptr; + NanoCLRDataType dt; + + CLR_RT_TypeDescriptor::ExtractObjectAndDataType(obj, dt); + + // Field not found - but if this is a generic type with + // a .cctor that's scheduled, + // reschedule to allow the .cctor to complete field initialization + if (obj == nullptr) + { + // Check if there's a pending .cctor for this generic type + CLR_RT_TypeSpec_Instance tsInst; + if (tsInst.InitializeFromIndex(*field.genericType)) + { + CLR_UINT32 hash = g_CLR_RT_TypeSystem.ComputeHashForClosedGenericType( + tsInst, + &stack->m_genericTypeSpecStorage); + CLR_RT_GenericCctorExecutionRecord *cctorRecord = + g_CLR_RT_TypeSystem.FindOrCreateGenericCctorRecord(hash, nullptr); + + if (cctorRecord != nullptr && + (cctorRecord->m_flags & CLR_RT_GenericCctorExecutionRecord::c_Scheduled) && + !(cctorRecord->m_flags & CLR_RT_GenericCctorExecutionRecord::c_Executed)) + { + // .cctor is scheduled but not yet executed + // Rewind ip to before this instruction so it will be retried + // (1 byte for opcode + 2 bytes for compressed field token) + ip -= 3; + NANOCLR_SET_AND_LEAVE(CLR_E_RESCHEDULE); + } + } + } + } evalPos++; CHECKSTACK(stack, evalPos); @@ -2862,7 +2909,10 @@ 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 - ptr = field.assembly->GetStaticFieldByFieldDef(field, field.genericType); + ptr = field.assembly->GetStaticFieldByFieldDef( + field, + field.genericType, + &stack->m_genericTypeSpecStorage); } else { @@ -2872,6 +2922,34 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) if (ptr == nullptr) { + // Field not found - but if this is a generic type with a .cctor that's scheduled, + // reschedule to allow the .cctor to complete field initialization + if (field.genericType && NANOCLR_INDEX_IS_VALID(*field.genericType)) + { + // Check if there's a pending .cctor for this generic type + CLR_RT_TypeSpec_Instance tsInst; + if (tsInst.InitializeFromIndex(*field.genericType)) + { + CLR_UINT32 hash = g_CLR_RT_TypeSystem.ComputeHashForClosedGenericType( + tsInst, + &stack->m_genericTypeSpecStorage); + CLR_RT_GenericCctorExecutionRecord *cctorRecord = + g_CLR_RT_TypeSystem.FindOrCreateGenericCctorRecord(hash, nullptr); + + if (cctorRecord != nullptr && + (cctorRecord->m_flags & CLR_RT_GenericCctorExecutionRecord::c_Scheduled) && + !(cctorRecord->m_flags & CLR_RT_GenericCctorExecutionRecord::c_Executed)) + { + // .cctor is scheduled but not yet executed + // Rewind ip to before this instruction so it will be retried + // (1 byte for opcode + 2 bytes for compressed field token) + ip -= 3; + NANOCLR_SET_AND_LEAVE(CLR_E_RESCHEDULE); + } + } + } + + // Not a pending .cctor case - this is a real error NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE); } @@ -2901,7 +2979,10 @@ 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 - ptr = field.assembly->GetStaticFieldByFieldDef(field, field.genericType); + ptr = field.assembly->GetStaticFieldByFieldDef( + field, + field.genericType, + &stack->m_genericTypeSpecStorage); } else { @@ -2911,6 +2992,34 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) if (ptr == nullptr) { + // Field not found - but if this is a generic type with a .cctor that's scheduled, + // reschedule to allow the .cctor to complete field initialization + if (field.genericType && NANOCLR_INDEX_IS_VALID(*field.genericType)) + { + // Check if there's a pending .cctor for this generic type + CLR_RT_TypeSpec_Instance tsInst; + if (tsInst.InitializeFromIndex(*field.genericType)) + { + CLR_UINT32 hash = g_CLR_RT_TypeSystem.ComputeHashForClosedGenericType( + tsInst, + &stack->m_genericTypeSpecStorage); + CLR_RT_GenericCctorExecutionRecord *cctorRecord = + g_CLR_RT_TypeSystem.FindOrCreateGenericCctorRecord(hash, nullptr); + + if (cctorRecord != nullptr && + (cctorRecord->m_flags & CLR_RT_GenericCctorExecutionRecord::c_Scheduled) && + !(cctorRecord->m_flags & CLR_RT_GenericCctorExecutionRecord::c_Executed)) + { + // .cctor is scheduled but not yet executed + // Rewind ip to before this instruction so it will be retried + // (1 byte for opcode + 2 bytes for compressed field token) + ip -= 3; + NANOCLR_SET_AND_LEAVE(CLR_E_RESCHEDULE); + } + } + } + + // Not a pending .cctor case - this is a real error NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE); } diff --git a/src/CLR/Core/TypeSystem.cpp b/src/CLR/Core/TypeSystem.cpp index 81381c04b3..9f1cb74337 100644 --- a/src/CLR/Core/TypeSystem.cpp +++ b/src/CLR/Core/TypeSystem.cpp @@ -5242,7 +5242,8 @@ CLR_RT_HeapBlock *CLR_RT_Assembly::GetGenericStaticField( CLR_RT_HeapBlock *CLR_RT_Assembly::GetStaticFieldByFieldDef( const CLR_RT_FieldDef_Index &fdIndex, - const CLR_RT_TypeSpec_Index *genericType) + const CLR_RT_TypeSpec_Index *genericType, + const CLR_RT_TypeSpec_Index *contextTypeSpec) { NATIVE_PROFILE_CLR_CORE(); @@ -5255,6 +5256,32 @@ CLR_RT_HeapBlock *CLR_RT_Assembly::GetStaticFieldByFieldDef( { return hb; } + + // On-demand allocation: if this is an open generic type that needs runtime binding, + // allocate static fields now (closed generics should already be allocated via metadata) + CLR_RT_TypeSpec_Instance tsInst; + if (tsInst.InitializeFromIndex(*genericType) && !tsInst.IsClosedGenericType()) + { + // Get the generic type definition to check if it has static fields + CLR_RT_TypeDef_Instance genericTypeDef; + if (genericTypeDef.InitializeFromIndex(tsInst.genericTypeDef)) + { + if (genericTypeDef.target->staticFieldsCount > 0) + { + // Allocate static fields on-demand for this runtime-bound generic + // Pass the caller context so we can resolve generic parameters + if (SUCCEEDED(AllocateGenericStaticFieldsOnDemand(*genericType, genericTypeDef, contextTypeSpec))) + { + // Retry the lookup after allocation + hb = GetGenericStaticField(*genericType, fdIndex); + if (hb != nullptr) + { + return hb; + } + } + } + } + } } // fallback to assembly static fields (use offset stored on crossReferenceFieldDef) @@ -5269,6 +5296,239 @@ CLR_RT_HeapBlock *CLR_RT_Assembly::GetStaticFieldByFieldDef( #endif } +HRESULT CLR_RT_Assembly::AllocateGenericStaticFieldsOnDemand( + const CLR_RT_TypeSpec_Index &typeSpecIndex, + const CLR_RT_TypeDef_Instance &genericTypeDef, + const CLR_RT_TypeSpec_Index *contextTypeSpec) +{ + NATIVE_PROFILE_CLR_CORE(); + NANOCLR_HEADER(); + + CLR_UINT32 hash; + CLR_RT_TypeSpec_Instance tsInstance; + CLR_RT_Assembly *tsOwner; + CLR_RT_TypeSpec_CrossReference *tsCross; + CLR_RT_GenericStaticFieldRecord *record; + CLR_RT_Assembly *ownerAsm; + const CLR_RECORD_TYPEDEF *ownerTd; + CLR_UINT32 count; + CLR_UINT32 newMax; + CLR_RT_GenericStaticFieldRecord *newArray; + CLR_RT_HeapBlock *fields; + CLR_RT_FieldDef_Index *fieldDefs; + const CLR_RECORD_METHODDEF *md; + int methodCount; + + // Initialize TypeSpec instance for hash computation + if (!tsInstance.InitializeFromIndex(typeSpecIndex)) + { + NANOCLR_SET_AND_LEAVE(CLR_E_FAIL); + } + + // Compute hash for this closed generic type, using context to resolve VAR/MVAR if needed + hash = g_CLR_RT_TypeSystem.ComputeHashForClosedGenericType(tsInstance, contextTypeSpec); + + // Check if already allocated (shouldn't happen if called from GetStaticFieldByFieldDef, but be safe) + for (CLR_UINT32 i = 0; i < g_CLR_RT_TypeSystem.m_genericStaticFieldsCount; i++) + { + if (g_CLR_RT_TypeSystem.m_genericStaticFields[i].m_hash == hash) + { + // Already exists, link to cross-reference if needed + tsOwner = g_CLR_RT_TypeSystem.m_assemblies[typeSpecIndex.Assembly() - 1]; + tsCross = &tsOwner->crossReferenceTypeSpec[typeSpecIndex.TypeSpec()]; + + if (tsCross->genericStaticFields == nullptr) + { + record = &g_CLR_RT_TypeSystem.m_genericStaticFields[i]; + tsCross->genericStaticFields = record->m_fields; + tsCross->genericStaticFieldDefs = record->m_fieldDefs; + tsCross->genericStaticFieldsCount = record->m_count; + } + + NANOCLR_SET_AND_LEAVE(S_OK); + } + } + + // Get owner assembly and typedef + ownerAsm = genericTypeDef.assembly; + ownerTd = genericTypeDef.target; + count = ownerTd->staticFieldsCount; + + if (count == 0) + { + // No static fields to allocate + NANOCLR_SET_AND_LEAVE(S_OK); + } + + // Grow global registry if needed + if (g_CLR_RT_TypeSystem.m_genericStaticFieldsCount >= g_CLR_RT_TypeSystem.m_genericStaticFieldsMaxCount) + { + newMax = g_CLR_RT_TypeSystem.m_genericStaticFieldsMaxCount + 10; + 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 + fields = g_CLR_RT_ExecutionEngine.ExtractHeapBlocksForObjects( + DATATYPE_OBJECT, // heapblock kind + 0, // flags + count); // number of CLR_RT_HeapBlock entries + + if (fields == nullptr) + { + NANOCLR_SET_AND_LEAVE(CLR_E_OUT_OF_MEMORY); + } + + // Allocate mapping for field definitions + fieldDefs = (CLR_RT_FieldDef_Index *)platform_malloc(sizeof(CLR_RT_FieldDef_Index) * count); + + if (fieldDefs == nullptr) + { + NANOCLR_SET_AND_LEAVE(CLR_E_OUT_OF_MEMORY); + } + + // Initialize the record in global registry + 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; + + // 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); + } + + // Link this assembly's cross-reference to the global registry entry + tsOwner = g_CLR_RT_TypeSystem.m_assemblies[typeSpecIndex.Assembly() - 1]; + tsCross = &tsOwner->crossReferenceTypeSpec[typeSpecIndex.TypeSpec()]; + + tsCross->genericStaticFields = record->m_fields; + tsCross->genericStaticFieldDefs = record->m_fieldDefs; + tsCross->genericStaticFieldsCount = record->m_count; + + // Now that static fields are allocated, schedule the static constructor to initialize them + // Find the .cctor method for the generic type definition + md = ownerAsm->GetMethodDef(ownerTd->firstMethod); + methodCount = ownerTd->virtualMethodCount + ownerTd->instanceMethodCount + ownerTd->staticMethodCount; + + for (int i = 0; i < methodCount; i++, md++) + { + if (md->flags & CLR_RECORD_METHODDEF::MD_StaticConstructor) + { + // Found the .cctor - check execution status + CLR_RT_GenericCctorExecutionRecord *cctorRecord = + g_CLR_RT_TypeSystem.FindOrCreateGenericCctorRecord(hash, nullptr); + + if (cctorRecord != nullptr) + { + // Check if .cctor is already executed + if (cctorRecord->m_flags & CLR_RT_GenericCctorExecutionRecord::c_Executed) + { + // .cctor already completed - fields should be initialized + NANOCLR_SET_AND_LEAVE(S_OK); + } + + // Check if .cctor is already scheduled + if (cctorRecord->m_flags & CLR_RT_GenericCctorExecutionRecord::c_Scheduled) + { + // .cctor is already scheduled/running - nothing more to do + // The caller will retry after the .cctor completes + NANOCLR_SET_AND_LEAVE(S_OK); + } + + // Need to schedule the .cctor - mark it as scheduled + cctorRecord->m_flags |= CLR_RT_GenericCctorExecutionRecord::c_Scheduled; + } + + // Schedule the .cctor to run + CLR_RT_MethodDef_Index cctorIndex; + cctorIndex.Set(ownerAsm->assemblyIndex, ownerTd->firstMethod + i); + + // Ensure the .cctor thread exists (it may have been destroyed after initial startup) + if (g_CLR_RT_ExecutionEngine.EnsureSystemThread( + g_CLR_RT_ExecutionEngine.m_cctorThread, + ThreadPriority::System_Highest)) + { + // Create delegate for the static constructor + CLR_RT_HeapBlock refDlg; + refDlg.SetObjectReference(nullptr); + CLR_RT_ProtectFromGC gc(refDlg); + + if (SUCCEEDED(CLR_RT_HeapBlock_Delegate::CreateInstance(refDlg, cctorIndex, nullptr))) + { + CLR_RT_HeapBlock_Delegate *dlg = refDlg.DereferenceDelegate(); + + // Store the TypeSpec index so the .cctor runs with the correct generic binding + // If the TypeSpec has unresolved parameters (!0), also store the caller's context + dlg->m_genericTypeSpec = typeSpecIndex; + + // Push to the .cctor thread and schedule for execution + if (SUCCEEDED(g_CLR_RT_ExecutionEngine.m_cctorThread->PushThreadProcDelegate(dlg))) + { + g_CLR_RT_ExecutionEngine.m_cctorThread->m_terminationCallback = + CLR_RT_ExecutionEngine::StaticConstructorTerminationCallback; + + // The .cctor is now scheduled and will run when this thread yields + // The caller will get nullptr and should reschedule to allow .cctor to complete + } + else + { + // Failed to schedule - clear the flag + if (cctorRecord != nullptr) + { + cctorRecord->m_flags &= ~CLR_RT_GenericCctorExecutionRecord::c_Scheduled; + } + } + } + else + { + // Failed to create delegate - clear the flag + if (cctorRecord != nullptr) + { + cctorRecord->m_flags &= ~CLR_RT_GenericCctorExecutionRecord::c_Scheduled; + } + } + } + else + { + // Failed to ensure thread - clear the flag + if (cctorRecord != nullptr) + { + cctorRecord->m_flags &= ~CLR_RT_GenericCctorExecutionRecord::c_Scheduled; + } + } + break; + } + } + + NANOCLR_NOCLEANUP(); +} + HRESULT CLR_RT_Assembly::PrepareForExecution() { NATIVE_PROFILE_CLR_CORE(); @@ -7846,7 +8106,9 @@ CLR_RT_GenericStaticFieldRecord *CLR_RT_TypeSystem::FindOrCreateGenericStaticFie return record; } -CLR_UINT32 CLR_RT_TypeSystem::ComputeHashForClosedGenericType(CLR_RT_TypeSpec_Instance &typeInstance) +CLR_UINT32 CLR_RT_TypeSystem::ComputeHashForClosedGenericType( + CLR_RT_TypeSpec_Instance &typeInstance, + const CLR_RT_TypeSpec_Index *contextTypeSpec) { CLR_UINT32 hash = 0; @@ -7882,12 +8144,45 @@ CLR_UINT32 CLR_RT_TypeSystem::ComputeHashForClosedGenericType(CLR_RT_TypeSpec_In break; } - // Add each argument's type information to the hash - hash = SUPPORT_ComputeCRC(&elem.DataType, sizeof(elem.DataType), hash); + // Check if this is an unresolved generic parameter (VAR or MVAR) + if ((elem.DataType == DATATYPE_VAR || elem.DataType == DATATYPE_MVAR) && contextTypeSpec && + NANOCLR_INDEX_IS_VALID(*contextTypeSpec)) + { + // Resolve VAR from context TypeSpec using existing helper + CLR_RT_TypeDef_Index resolvedTypeDef; + NanoCLRDataType resolvedDataType; - if (elem.DataType == DATATYPE_CLASS || elem.DataType == DATATYPE_VALUETYPE) + CLR_RT_Assembly *contextAssm = g_CLR_RT_TypeSystem.m_assemblies[contextTypeSpec->Assembly() - 1]; + + if (contextAssm->FindGenericParamAtTypeSpec( + contextTypeSpec->TypeSpec(), + elem.GenericParamPosition, + resolvedTypeDef, + resolvedDataType)) + { + // Use the resolved type from context + hash = SUPPORT_ComputeCRC(&resolvedDataType, sizeof(resolvedDataType), hash); + + if (resolvedDataType == DATATYPE_CLASS || resolvedDataType == DATATYPE_VALUETYPE) + { + hash = SUPPORT_ComputeCRC(&resolvedTypeDef.data, sizeof(resolvedTypeDef.data), hash); + } + } + else + { + // couldn't resolve, reset hash to indicate failure + hash = 0; + } + } + else { - hash = SUPPORT_ComputeCRC(&elem.Class.data, sizeof(elem.Class.data), hash); + // 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); + } } } diff --git a/src/CLR/Include/nanoCLR_Runtime.h b/src/CLR/Include/nanoCLR_Runtime.h index 17ce5660cb..85c00f403c 100644 --- a/src/CLR/Include/nanoCLR_Runtime.h +++ b/src/CLR/Include/nanoCLR_Runtime.h @@ -1437,7 +1437,12 @@ struct CLR_RT_Assembly : public CLR_RT_HeapBlock_Node // EVENT HEAP - NO RELOCAT const CLR_RT_FieldDef_Index &fdIndex); CLR_RT_HeapBlock *GetStaticFieldByFieldDef( const CLR_RT_FieldDef_Index &fdIndex, - const CLR_RT_TypeSpec_Index *genericType); + const CLR_RT_TypeSpec_Index *genericType, + const CLR_RT_TypeSpec_Index *contextTypeSpec = nullptr); + HRESULT AllocateGenericStaticFieldsOnDemand( + const CLR_RT_TypeSpec_Index &typeSpecIndex, + const CLR_RT_TypeDef_Instance &genericTypeDef, + const CLR_RT_TypeSpec_Index *contextTypeSpec = nullptr); HRESULT PrepareForExecution(); CLR_UINT32 ComputeAssemblyHash(); @@ -2134,7 +2139,9 @@ struct CLR_RT_TypeSystem // EVENT HEAP - NO RELOCATION - CLR_UINT32 staticFieldCount); // Helper to compute hash for a closed generic type - static CLR_UINT32 ComputeHashForClosedGenericType(CLR_RT_TypeSpec_Instance &typeInstance); + static CLR_UINT32 ComputeHashForClosedGenericType( + CLR_RT_TypeSpec_Instance &typeInstance, + const CLR_RT_TypeSpec_Index *contextTypeSpec = nullptr); // Helper to find or create a generic .cctor execution record by hash static CLR_RT_GenericCctorExecutionRecord *FindOrCreateGenericCctorRecord(CLR_UINT32 hash, bool *created); @@ -2251,7 +2258,7 @@ struct CLR_RT_MethodDef_Instance : public CLR_RT_MethodDef_Index const CLR_RT_TypeSpec_Index *genericType; CLR_RT_MethodSpec_Index methodSpec; - + // For SZArrayHelper rebind: stores the array element TypeDef when dispatching from arrays CLR_RT_TypeDef_Index arrayElementType; From f93571fb2612d566216bdea2cd16c64ce833b70e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Thu, 13 Nov 2025 15:17:03 +0000 Subject: [PATCH 15/61] Fix newobj handler for generic constructors - Now setting the correct context to allow NewObject to properly resolve all generic types. *** PR separately *** --- src/CLR/Core/Interpreter.cpp | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/src/CLR/Core/Interpreter.cpp b/src/CLR/Core/Interpreter.cpp index e0e4b1b332..f69fae0081 100644 --- a/src/CLR/Core/Interpreter.cpp +++ b/src/CLR/Core/Interpreter.cpp @@ -2555,21 +2555,34 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) } top->SetObjectReference(nullptr); - // Stack: ... ... -> ... - // ^ - // Top points here. + // For constructors on generic classes, we need to use the class's generic type + // from the calling context, not the method's generic type. + // The calleeInst.genericType might point to an open generic from the MethodRef, + // but we need the closed generic from the caller's context (stack->m_call.genericType). + const CLR_RT_TypeSpec_Index *genericTypeForContext = nullptr; + + // Prefer the caller's generic type if available and valid + if (stack->m_call.genericType != nullptr && NANOCLR_INDEX_IS_VALID(*stack->m_call.genericType)) + { + genericTypeForContext = stack->m_call.genericType; + } + // Fallback to the callee's generic type if caller's is not available + else if (calleeInst.genericType != nullptr && NANOCLR_INDEX_IS_VALID(*calleeInst.genericType)) + { + genericTypeForContext = calleeInst.genericType; + } - if (calleeInst.genericType == nullptr) + if (genericTypeForContext == nullptr) { NANOCLR_CHECK_HRESULT(g_CLR_RT_ExecutionEngine.NewObject(top[0], cls)); } else { - CLR_RT_TypeSpec_Instance calleeInstGenericType; - calleeInstGenericType.InitializeFromIndex(*calleeInst.genericType); + CLR_RT_TypeSpec_Instance genericTypeInstance; + genericTypeInstance.InitializeFromIndex(*genericTypeForContext); NANOCLR_CHECK_HRESULT( - g_CLR_RT_ExecutionEngine.NewObject(top[0], cls, &calleeInstGenericType)); + g_CLR_RT_ExecutionEngine.NewObject(top[0], cls, &genericTypeInstance)); } // From d0487c593d811ed8acbcbf6f345bbf26dd3502e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Thu, 13 Nov 2025 16:04:07 +0000 Subject: [PATCH 16/61] Fix accessing a null element in a byref array - Code now grabs element type from array instead of element when element is null. --- src/CLR/Core/TypeSystem.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/CLR/Core/TypeSystem.cpp b/src/CLR/Core/TypeSystem.cpp index 9f1cb74337..50b3cb1918 100644 --- a/src/CLR/Core/TypeSystem.cpp +++ b/src/CLR/Core/TypeSystem.cpp @@ -2599,6 +2599,16 @@ HRESULT CLR_RT_TypeDescriptor::InitializeFromObject(const CLR_RT_HeapBlock &ref) { obj = (CLR_RT_HeapBlock *)array->GetElement(obj->ArrayIndex()); + // For reference arrays, if the element is null, we need to get the type from the array's element + // type rather than trying to dereference a null object + if (obj->Dereference() == nullptr) + { + // Use the array's element type. + // Keep 'reflex' null to avoid carrying array levels when returning an element type. + cls = &(array->ReflectionDataConst().data.type); + break; + } + NANOCLR_SET_AND_LEAVE(InitializeFromObject(*obj)); } From 15b31645818bcb87d312576516b0a95959df9bfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Thu, 13 Nov 2025 16:45:55 +0000 Subject: [PATCH 17/61] Fix ldelem to provide generic context in generic arrays --- src/CLR/Core/Interpreter.cpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/CLR/Core/Interpreter.cpp b/src/CLR/Core/Interpreter.cpp index f69fae0081..9e86e08b31 100644 --- a/src/CLR/Core/Interpreter.cpp +++ b/src/CLR/Core/Interpreter.cpp @@ -3366,16 +3366,27 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) CLR_RT_TypeDef_Instance type{}; CLR_RT_TypeDef_Index cls; + // Propagate the array element type into the current call context so generic VAR can resolve + // against a closed type (e.g., List[] -> Int32). This mirrors the SZArrayHelper flow + // used in method dispatch, but scoped to this instruction. + CLR_RT_TypeDef_Index previousArrayElemType = stack->m_call.arrayElementType; + + NANOCLR_CHECK_HRESULT(CLR_RT_TypeDescriptor::ExtractTypeIndexFromObject(evalPos[0], cls)); + + stack->m_call.arrayElementType = cls; + if (!type.ResolveToken(arg, assm, &stack->m_call)) { + // Restore previous context before bailing out + stack->m_call.arrayElementType = previousArrayElemType; NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE); } - NANOCLR_CHECK_HRESULT(CLR_RT_TypeDescriptor::ExtractTypeIndexFromObject(evalPos[0], cls)); - // Check this is an object of the requested type. if (!g_CLR_RT_ExecutionEngine.IsInstanceOfToken(arg, evalPos[0], stack->m_call)) { + // Restore previous context before leaving + stack->m_call.arrayElementType = previousArrayElemType; NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE); } @@ -3389,6 +3400,9 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) NANOCLR_CHECK_HRESULT(evalPos[0].LoadFromReference(safeSource)); } + // Restore previous arrayElementType context + stack->m_call.arrayElementType = previousArrayElemType; + goto Execute_LoadAndPromote; // } From 789aa1bef6709d199a83b064bdc152016cf17647 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Thu, 13 Nov 2025 16:55:58 +0000 Subject: [PATCH 18/61] Fix box, unbox and stelem for generic types inside interface adapter --- src/CLR/Core/Interpreter.cpp | 60 ++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/src/CLR/Core/Interpreter.cpp b/src/CLR/Core/Interpreter.cpp index 9e86e08b31..11dc2ed93e 100644 --- a/src/CLR/Core/Interpreter.cpp +++ b/src/CLR/Core/Interpreter.cpp @@ -3051,11 +3051,31 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) FETCH_ARG_COMPRESSED_TYPETOKEN(arg, ip); CLR_RT_TypeDef_Instance typeInst{}; + CLR_RT_TypeDef_Index previousArrayElemType = stack->m_call.arrayElementType; + + // For BOXing a generic VAR (!0) inside an interface adapter (e.g., IList.get_Item) + // we may lack a closed generic TypeSpec in stack->m_call.genericType. In that case + // use the runtime type of the value being boxed to populate arrayElementType so + // TypeDef::ResolveToken can fall back and close the VAR slot. + if (!NANOCLR_INDEX_IS_VALID(stack->m_call.arrayElementType)) + { + CLR_RT_TypeDef_Index valueTypeIdx; + if (SUCCEEDED(CLR_RT_TypeDescriptor::ExtractTypeIndexFromObject(evalPos[0], valueTypeIdx))) + { + stack->m_call.arrayElementType = valueTypeIdx; + } + } + if (typeInst.ResolveToken(arg, assm, &stack->m_call) == false) { + // restore previous context before bailing + stack->m_call.arrayElementType = previousArrayElemType; NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE); } + // Restore previous arrayElementType (don't leak temporary inference) + stack->m_call.arrayElementType = previousArrayElemType; + UPDATESTACK(stack, evalPos); // check if value is a nullable type @@ -3204,11 +3224,31 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) // TODO: still not handling Nullable types here CLR_RT_TypeDef_Instance typeInst{}; + CLR_RT_TypeDef_Index previousArrayElemType = stack->m_call.arrayElementType; + + // For UNBOX.ANY of a generic VAR (!0) inside an interface adapter (e.g., IList.set_Item) + // we may lack a closed generic TypeSpec in stack->m_call.genericType. In that case + // use the runtime type of the boxed value to populate arrayElementType so + // TypeDef::ResolveToken can fall back and close the VAR slot. + if (!NANOCLR_INDEX_IS_VALID(stack->m_call.arrayElementType)) + { + CLR_RT_TypeDef_Index valueTypeIdx; + if (SUCCEEDED(CLR_RT_TypeDescriptor::ExtractTypeIndexFromObject(evalPos[0], valueTypeIdx))) + { + stack->m_call.arrayElementType = valueTypeIdx; + } + } + if (typeInst.ResolveToken(arg, assm, &stack->m_call) == false) { + // restore previous context before bailing + stack->m_call.arrayElementType = previousArrayElemType; NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE); } + // Restore previous arrayElementType (don't leak temporary inference) + stack->m_call.arrayElementType = previousArrayElemType; + UPDATESTACK(stack, evalPos); if (((typeInst.target->flags & CLR_RECORD_TYPEDEF::TD_Semantics_Mask) == @@ -3556,11 +3596,31 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) // Resolve the IL's element type in the context of any generics CLR_RT_TypeDef_Instance expectedType; + CLR_RT_TypeDef_Index previousArrayElemType = stack->m_call.arrayElementType; + + // For STELEM of a generic VAR (!0) inside an interface adapter (e.g., IList.set_Item) + // we may lack a closed generic TypeSpec in stack->m_call.genericType. In that case + // use the runtime type of the array element to populate arrayElementType so + // TypeDef::ResolveToken can fall back and close the VAR slot. + if (!NANOCLR_INDEX_IS_VALID(stack->m_call.arrayElementType)) + { + CLR_RT_TypeDef_Index elemTypeIdx; + if (SUCCEEDED(CLR_RT_TypeDescriptor::ExtractTypeIndexFromObject(evalPos[1], elemTypeIdx))) + { + stack->m_call.arrayElementType = elemTypeIdx; + } + } + if (!expectedType.ResolveToken(arg, assm, &stack->m_call)) { + // restore previous context before bailing + stack->m_call.arrayElementType = previousArrayElemType; NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE); } + // Restore previous arrayElementType (don't leak temporary inference) + stack->m_call.arrayElementType = previousArrayElemType; + NanoCLRDataType elemDT = (NanoCLRDataType)expectedType.target->dataType; // Promote the value if it's a reference or boxed struct From 0befd34a6529a6ad80b0ff9b503144de0c575821 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Fri, 14 Nov 2025 00:28:26 +0000 Subject: [PATCH 19/61] Fix TypeDescriptor initialization from TypeSpec --- src/CLR/Core/TypeSystem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CLR/Core/TypeSystem.cpp b/src/CLR/Core/TypeSystem.cpp index 50b3cb1918..4c923c21b5 100644 --- a/src/CLR/Core/TypeSystem.cpp +++ b/src/CLR/Core/TypeSystem.cpp @@ -2220,7 +2220,7 @@ HRESULT CLR_RT_TypeDescriptor::InitializeFromTypeSpec(const CLR_RT_TypeSpec_Inde NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE); } - parser.Initialize_TypeSpec(inst.assembly, inst.target); + parser.Initialize_TypeSpec(inst.assembly, inst.assembly->GetTypeSpec(inst.TypeSpec())); NANOCLR_SET_AND_LEAVE(InitializeFromSignatureParser(parser)); From 049421b24bfb0028440c4c86ac7ed4e431fa9e59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Mon, 17 Nov 2025 11:58:55 +0000 Subject: [PATCH 20/61] Fix initializing TypeDescriptor from signature parser - Now getting generic context from parameter. - When finding a VAR or MVAR nows decodes type from context. --- src/CLR/Core/TypeSystem.cpp | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/src/CLR/Core/TypeSystem.cpp b/src/CLR/Core/TypeSystem.cpp index 4c923c21b5..4c551526f5 100644 --- a/src/CLR/Core/TypeSystem.cpp +++ b/src/CLR/Core/TypeSystem.cpp @@ -2343,7 +2343,9 @@ HRESULT CLR_RT_TypeDescriptor::InitializeFromFieldDefinition(const CLR_RT_FieldD NANOCLR_NOCLEANUP(); } -HRESULT CLR_RT_TypeDescriptor::InitializeFromSignatureParser(CLR_RT_SignatureParser &parser) +HRESULT CLR_RT_TypeDescriptor::InitializeFromSignatureParser( + CLR_RT_SignatureParser &parser, + const CLR_RT_TypeSpec_Index *contextTypeSpec) { NATIVE_PROFILE_CLR_CORE(); NANOCLR_HEADER(); @@ -2359,7 +2361,33 @@ HRESULT CLR_RT_TypeDescriptor::InitializeFromSignatureParser(CLR_RT_SignaturePar if (res.DataType == DATATYPE_GENERICINST) { - NANOCLR_CHECK_HRESULT(InitializeFromGenericType(res.TypeSpec)); + // generic type, advance again to get the type + parser.Advance(res); + } + + // Check if this is an unresolved generic parameter (VAR) and we have a context + if (res.DataType == DATATYPE_VAR && contextTypeSpec && NANOCLR_INDEX_IS_VALID(*contextTypeSpec)) + { + // Resolve VAR from context TypeSpec using existing helper + CLR_RT_TypeDef_Index resolvedTypeDef; + NanoCLRDataType resolvedDataType; + + CLR_RT_Assembly *contextAssm = g_CLR_RT_TypeSystem.m_assemblies[contextTypeSpec->Assembly() - 1]; + + if (contextAssm->FindGenericParamAtTypeSpec( + contextTypeSpec->TypeSpec(), + res.GenericParamPosition, + resolvedTypeDef, + resolvedDataType)) + { + // Use the resolved type from context + NANOCLR_CHECK_HRESULT(InitializeFromType(resolvedTypeDef)); + } + else + { + // Couldn't resolve, fall back to original behavior + NANOCLR_CHECK_HRESULT(InitializeFromType(res.Class)); + } } else { From e42f17693e9fc3155d8f4f55ea29dba5089c4433 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Mon, 17 Nov 2025 12:05:04 +0000 Subject: [PATCH 21/61] TypeDescriptor intialization from TypeSpec now received generic context as parameter --- src/CLR/Core/TypeSystem.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/CLR/Core/TypeSystem.cpp b/src/CLR/Core/TypeSystem.cpp index 4c551526f5..9a30b1456c 100644 --- a/src/CLR/Core/TypeSystem.cpp +++ b/src/CLR/Core/TypeSystem.cpp @@ -2207,7 +2207,9 @@ HRESULT CLR_RT_TypeDescriptor::InitializeFromReflection(const CLR_RT_ReflectionD NANOCLR_NOCLEANUP(); } -HRESULT CLR_RT_TypeDescriptor::InitializeFromTypeSpec(const CLR_RT_TypeSpec_Index &sig) +HRESULT CLR_RT_TypeDescriptor::InitializeFromTypeSpec( + const CLR_RT_TypeSpec_Index &sig, + const CLR_RT_TypeSpec_Index *contextTypeSpec) { NATIVE_PROFILE_CLR_CORE(); NANOCLR_HEADER(); @@ -2222,7 +2224,7 @@ HRESULT CLR_RT_TypeDescriptor::InitializeFromTypeSpec(const CLR_RT_TypeSpec_Inde parser.Initialize_TypeSpec(inst.assembly, inst.assembly->GetTypeSpec(inst.TypeSpec())); - NANOCLR_SET_AND_LEAVE(InitializeFromSignatureParser(parser)); + NANOCLR_SET_AND_LEAVE(InitializeFromSignatureParser(parser, contextTypeSpec)); NANOCLR_NOCLEANUP(); } From dead55759b4253fd98e7997e893079d47b85e13b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Mon, 17 Nov 2025 12:05:50 +0000 Subject: [PATCH 22/61] FIx TypeDescriptor initialization from signature token - Now when it encounters a GENERICINST it passes the generic context (if available) and the resolution is left to the callee. --- src/CLR/Core/TypeSystem.cpp | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/src/CLR/Core/TypeSystem.cpp b/src/CLR/Core/TypeSystem.cpp index 9a30b1456c..8d19300cad 100644 --- a/src/CLR/Core/TypeSystem.cpp +++ b/src/CLR/Core/TypeSystem.cpp @@ -2482,20 +2482,11 @@ HRESULT CLR_RT_TypeDescriptor::InitializeFromSignatureToken( } else if (elem.DataType == DATATYPE_GENERICINST) { - // full generic instantiation: read it out - // CLASS/VALUETYPE - parser.Advance(elem); - // generic-definition token - parser.Advance(elem); - - CLR_RT_TypeSpec_Index tsInst{}; - tsInst.Set(elem.Class.Assembly(), elem.Class.Type()); - - // argument count - parser.Advance(elem); - - // now read each argument and record in tsInst.m_data.GenericArguments - this->InitializeFromTypeSpec(tsInst); + // full generic instantiation: parse it directly from the signature + // Pass caller's generic type as context to resolve VAR parameters in the generic arguments + const CLR_RT_TypeSpec_Index *contextTypeSpec = + (caller && NANOCLR_INDEX_IS_VALID(*caller->genericType)) ? caller->genericType : nullptr; + this->InitializeFromSignatureParser(parser, contextTypeSpec); } else { From 0bf6457dcea4bfea5822d78c14660c0a9001e2a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Mon, 17 Nov 2025 12:06:35 +0000 Subject: [PATCH 23/61] Resolving a token in for a MethodDef instance now preserves generic type from caller --- src/CLR/Core/TypeSystem.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/CLR/Core/TypeSystem.cpp b/src/CLR/Core/TypeSystem.cpp index 8d19300cad..26f718cb8d 100644 --- a/src/CLR/Core/TypeSystem.cpp +++ b/src/CLR/Core/TypeSystem.cpp @@ -1868,7 +1868,7 @@ bool CLR_RT_MethodDef_Instance::ResolveToken( } else { - // owner is TypeRef + // owner is TypeRef (e.g., interface method call) // get data for MethodRef (from index) data = assm->crossReferenceMethodRef[index].target.data; @@ -1877,8 +1877,10 @@ bool CLR_RT_MethodDef_Instance::ResolveToken( // grab the MethodDef target = assembly->GetMethodDef(Method()); - // invalidate GenericType - genericType = nullptr; + // Preserve caller's generic context for interface method calls + // When calling a interface method (e.g. IList.Remove() on a List object) we need the closed + // generic context + genericType = callerGeneric; } #if defined(NANOCLR_INSTANCE_NAMES) From 4e67fe7ea9ae5a760724989eeab4f314baf78892 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Mon, 17 Nov 2025 12:07:08 +0000 Subject: [PATCH 24/61] Update several declarations for previous commits --- src/CLR/Include/nanoCLR_Runtime.h | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/CLR/Include/nanoCLR_Runtime.h b/src/CLR/Include/nanoCLR_Runtime.h index 85c00f403c..ab517e7402 100644 --- a/src/CLR/Include/nanoCLR_Runtime.h +++ b/src/CLR/Include/nanoCLR_Runtime.h @@ -2443,12 +2443,14 @@ struct CLR_RT_TypeDescriptor HRESULT InitializeFromDataType(NanoCLRDataType dt); HRESULT InitializeFromReflection(const CLR_RT_ReflectionDef_Index &reflex); - HRESULT InitializeFromTypeSpec(const CLR_RT_TypeSpec_Index &sig); + HRESULT InitializeFromTypeSpec(const CLR_RT_TypeSpec_Index &sig, const CLR_RT_TypeSpec_Index *contextTypeSpec = nullptr); HRESULT InitializeFromType(const CLR_RT_TypeDef_Index &cls); HRESULT InitializeFromTypeDef(const CLR_RT_TypeDef_Index &cls); HRESULT InitializeFromGenericType(const CLR_RT_TypeSpec_Index &genericType); HRESULT InitializeFromFieldDefinition(const CLR_RT_FieldDef_Instance &fd); - HRESULT InitializeFromSignatureParser(CLR_RT_SignatureParser &parser); + HRESULT InitializeFromSignatureParser( + CLR_RT_SignatureParser &parser, + const CLR_RT_TypeSpec_Index *contextTypeSpec = nullptr); HRESULT InitializeFromSignatureToken( CLR_RT_Assembly *assm, CLR_UINT32 token, @@ -4216,10 +4218,20 @@ struct CLR_RT_ExecutionEngine static bool IsInstanceOf(CLR_RT_TypeDescriptor &desc, CLR_RT_TypeDescriptor &descTarget, bool isInstInstruction); static bool IsInstanceOf(const CLR_RT_TypeDef_Index &cls, const CLR_RT_TypeDef_Index &clsTarget); static bool IsInstanceOf(CLR_RT_HeapBlock &obj, const CLR_RT_TypeDef_Index &clsTarget); - static bool IsInstanceOf(CLR_RT_HeapBlock &obj, CLR_RT_Assembly *assm, CLR_UINT32 token, bool isInstInstruction); + static bool IsInstanceOf( + CLR_RT_HeapBlock &obj, + CLR_RT_Assembly *assm, + CLR_UINT32 token, + bool isInstInstruction, + const CLR_RT_MethodDef_Instance *caller = nullptr); bool IsInstanceOfToken(CLR_UINT32 token, CLR_RT_HeapBlock &obj, const CLR_RT_MethodDef_Instance &caller); - static HRESULT CastToType(CLR_RT_HeapBlock &ref, CLR_UINT32 tk, CLR_RT_Assembly *assm, bool isInstInstruction); + static HRESULT CastToType( + CLR_RT_HeapBlock &ref, + CLR_UINT32 tk, + CLR_RT_Assembly *assm, + bool isInstInstruction, + const CLR_RT_MethodDef_Instance *caller); void DebuggerLoop(); From d5eb9b3e54a7b1ce572b3926671eef87c9c67c5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Mon, 17 Nov 2025 12:08:28 +0000 Subject: [PATCH 25/61] IsInstanceOf now receives caller as parameter - Alse reworked the code to remove redundant processing as now everything is handled in the call to InitializeFromSignatureToken. --- src/CLR/Core/Execution.cpp | 27 +++++---------------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/src/CLR/Core/Execution.cpp b/src/CLR/Core/Execution.cpp index 55a14a0bdd..74503f57e1 100644 --- a/src/CLR/Core/Execution.cpp +++ b/src/CLR/Core/Execution.cpp @@ -3538,37 +3538,20 @@ bool CLR_RT_ExecutionEngine::IsInstanceOf( CLR_RT_HeapBlock &obj, CLR_RT_Assembly *assm, CLR_UINT32 token, - bool isInstInstruction) + bool isInstInstruction, + const CLR_RT_MethodDef_Instance *caller) { NATIVE_PROFILE_CLR_CORE(); + CLR_RT_TypeDescriptor desc{}; CLR_RT_TypeDescriptor descTarget{}; - CLR_RT_TypeDef_Instance clsTarget{}; - CLR_RT_TypeSpec_Instance defTarget{}; if (FAILED(desc.InitializeFromObject(obj))) return false; - if (clsTarget.ResolveToken(token, assm)) - { - // - // Shortcut for identity. - // - if (desc.m_handlerCls.data == clsTarget.data) - return true; - - if (FAILED(descTarget.InitializeFromType(clsTarget))) - return false; - } - else if (defTarget.ResolveToken(token, assm)) - { - if (FAILED(descTarget.InitializeFromTypeSpec(defTarget))) - return false; - } - else - { + // Use InitializeFromSignatureToken to properly resolve VAR/MVAR tokens + if (FAILED(descTarget.InitializeFromSignatureToken(assm, token, caller))) return false; - } return IsInstanceOf(desc, descTarget, isInstInstruction); } From 6d55384e65ef460e2168d5e82bcdcd33b1a492e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Mon, 17 Nov 2025 12:09:16 +0000 Subject: [PATCH 26/61] CastToType now passes caller parameter to IsInstanceOf --- src/CLR/Core/Execution.cpp | 5 +++-- src/CLR/Core/Interpreter.cpp | 11 ++++++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/CLR/Core/Execution.cpp b/src/CLR/Core/Execution.cpp index 74503f57e1..c34d6ea855 100644 --- a/src/CLR/Core/Execution.cpp +++ b/src/CLR/Core/Execution.cpp @@ -3592,7 +3592,8 @@ HRESULT CLR_RT_ExecutionEngine::CastToType( CLR_RT_HeapBlock &ref, CLR_UINT32 tk, CLR_RT_Assembly *assm, - bool isInstInstruction) + bool isInstInstruction, + const CLR_RT_MethodDef_Instance *caller) { NATIVE_PROFILE_CLR_CORE(); NANOCLR_HEADER(); @@ -3601,7 +3602,7 @@ HRESULT CLR_RT_ExecutionEngine::CastToType( { ; } - else if (g_CLR_RT_ExecutionEngine.IsInstanceOf(ref, assm, tk, isInstInstruction) == true) + else if (g_CLR_RT_ExecutionEngine.IsInstanceOf(ref, assm, tk, isInstInstruction, caller) == true) { ; } diff --git a/src/CLR/Core/Interpreter.cpp b/src/CLR/Core/Interpreter.cpp index 11dc2ed93e..49a3986129 100644 --- a/src/CLR/Core/Interpreter.cpp +++ b/src/CLR/Core/Interpreter.cpp @@ -2654,8 +2654,12 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) { FETCH_ARG_COMPRESSED_TYPETOKEN(arg, ip); - NANOCLR_CHECK_HRESULT( - CLR_RT_ExecutionEngine::CastToType(evalPos[0], arg, assm, (op == CEE_ISINST))); + NANOCLR_CHECK_HRESULT(CLR_RT_ExecutionEngine::CastToType( + evalPos[0], + arg, + assm, + (op == CEE_ISINST), + &stack->m_call)); break; } @@ -3274,7 +3278,8 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) else { //"castclass" - NANOCLR_CHECK_HRESULT(CLR_RT_ExecutionEngine::CastToType(evalPos[0], arg, assm, false)); + NANOCLR_CHECK_HRESULT( + CLR_RT_ExecutionEngine::CastToType(evalPos[0], arg, assm, false, &stack->m_call)); } break; From 2fca24495078d0bad16faefce691669949425dad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Mon, 17 Nov 2025 12:27:37 +0000 Subject: [PATCH 27/61] Fix CALL_VIRT handler to find generic context and pass it to calles --- src/CLR/Core/Interpreter.cpp | 102 +++++++++++++++++++++++++++++++---- 1 file changed, 93 insertions(+), 9 deletions(-) diff --git a/src/CLR/Core/Interpreter.cpp b/src/CLR/Core/Interpreter.cpp index 49a3986129..20dd7096d2 100644 --- a/src/CLR/Core/Interpreter.cpp +++ b/src/CLR/Core/Interpreter.cpp @@ -2120,7 +2120,71 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) // Set arrayElementType before ResolveToken so generic type resolution can use it calleeInst.arrayElementType = propagatedArrayElementType; - if (calleeInst.ResolveToken(arg, assm, stack->m_call.genericType) == false) + // For interface method calls on generic instances, try to extract the closed generic TypeSpec + // from the caller's assembly by matching the object's TypeDef + const CLR_RT_TypeSpec_Index *effectiveCallerGeneric = stack->m_call.genericType; + + // Only perform expensive TypeSpec search if ALL conditions are met: + // 1. This is a virtual call (interfaces use CALLVIRT) + // 2. No generic context exists yet + // 3. The token is a MethodRef (not direct MethodDef) + if (op == CEE_CALLVIRT && stack->m_call.genericType == nullptr && + CLR_TypeFromTk(arg) == TBL_MethodRef) + { + const CLR_RECORD_METHODREF *mr = assm->GetMethodRef(CLR_DataFromTk(arg)); + + // 4. The method owner is a TypeRef (interface method) + // 5. Not a static method (interfaces don't have static methods, but safety check) + if (mr && mr->Owner() == TBL_TypeRef) + { + // Temporarily resolve to get argument count and check if instance method + CLR_RT_MethodDef_Instance tempInst{}; + if (tempInst.ResolveToken(arg, assm, nullptr) && + (tempInst.target->flags & CLR_RECORD_METHODDEF::MD_Static) == 0) + { + CLR_RT_HeapBlock *pThisTemp = &evalPos[1 - tempInst.target->argumentsCount]; + + // 6. 'this' is an object reference (not a value type byref) + if (pThisTemp->DataType() == DATATYPE_OBJECT || pThisTemp->DataType() == DATATYPE_BYREF) + { + CLR_RT_HeapBlock *obj = pThisTemp->Dereference(); + + // 7. Object is a class instance (generic or not) + if (obj && obj->DataType() == DATATYPE_CLASS) + { + CLR_RT_TypeDef_Index objCls = obj->ObjectCls(); + + // 8. Object has a valid TypeDef + if (NANOCLR_INDEX_IS_VALID(objCls)) + { + // NOW search for a TypeSpec that matches this object's TypeDef + // This is only needed for closed generic instances like List + for (int i = 0; i < assm->tablesSize[TBL_TypeSpec]; i++) + { + const CLR_RT_TypeSpec_Index *tsIdx = + &assm->crossReferenceTypeSpec[i].genericType; + if (NANOCLR_INDEX_IS_VALID(*tsIdx)) + { + CLR_RT_TypeSpec_Instance tsInst{}; + if (tsInst.InitializeFromIndex(*tsIdx) && + NANOCLR_INDEX_IS_VALID(tsInst.genericTypeDef) && + tsInst.genericTypeDef.data == objCls.data) + { + // Found a TypeSpec in the caller's assembly that matches the + // object's class + effectiveCallerGeneric = tsIdx; + break; + } + } + } + } + } + } + } + } + } + + if (calleeInst.ResolveToken(arg, assm, effectiveCallerGeneric) == false) { NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE); } @@ -2257,10 +2321,11 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) } } - if (calleeInst.genericType && NANOCLR_INDEX_IS_VALID(*calleeInst.genericType) && - calleeInst.genericType->data != CLR_EmptyToken) + // Initialize the dispatched method, preserving the generic context from + // calleeInst The genericType was set by ResolveToken from the MethodRef's owner + // TypeSpec + if (calleeInst.genericType && NANOCLR_INDEX_IS_VALID(*calleeInst.genericType)) { - // store the current generic context (if any) calleeInst.InitializeFromIndex(calleeReal, *calleeInst.genericType); } else @@ -2320,12 +2385,31 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) NANOCLR_CHECK_HRESULT(CLR_RT_StackFrame::Push(th, calleeInst, -1)); - // Store the caller's generic context in the new stack frame's m_genericTypeSpecStorage - // This allows the callee to resolve open generic parameters (VAR/MVAR) in TypeSpecs - if (stack->m_call.genericType && NANOCLR_INDEX_IS_VALID(*stack->m_call.genericType)) + // Set up the new stack frame's generic context + // Prefer calleeInst.genericType (from MethodRef TypeSpec) over caller's generic context + CLR_RT_StackFrame *newStack = th->CurrentFrame(); + const CLR_RT_TypeSpec_Index *effectiveGenericContext = nullptr; + + if (calleeInst.genericType && NANOCLR_INDEX_IS_VALID(*calleeInst.genericType)) + { + effectiveGenericContext = calleeInst.genericType; + } + else if (stack->m_call.genericType && NANOCLR_INDEX_IS_VALID(*stack->m_call.genericType)) + { + effectiveGenericContext = stack->m_call.genericType; + } + + if (effectiveGenericContext) + { + // CRITICAL: Copy the value to stable storage and update the pointer ATOMICALLY + // to prevent the pointer from pointing to stale/overwritten memory + newStack->m_genericTypeSpecStorage = *effectiveGenericContext; + newStack->m_call.genericType = &newStack->m_genericTypeSpecStorage; + } + else { - CLR_RT_StackFrame *newStack = th->CurrentFrame(); - newStack->m_genericTypeSpecStorage = *stack->m_call.genericType; + // Ensure genericType doesn't point to garbage + newStack->m_call.genericType = nullptr; } } From 377d7e8c23bc8444bbf08c39ea6e1114d5e7d7b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Mon, 17 Nov 2025 14:10:22 +0000 Subject: [PATCH 28/61] Fix getting the declaring type of MethodDef instance - Now checks if the method is generic and there is a generic context available. --- src/CLR/Core/TypeSystem.cpp | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/CLR/Core/TypeSystem.cpp b/src/CLR/Core/TypeSystem.cpp index 26f718cb8d..640089b496 100644 --- a/src/CLR/Core/TypeSystem.cpp +++ b/src/CLR/Core/TypeSystem.cpp @@ -1999,7 +1999,17 @@ bool CLR_RT_MethodDef_Instance::GetDeclaringType(CLR_RT_TypeDef_Instance &declTy { NATIVE_PROFILE_CLR_CORE(); - if (genericType && NANOCLR_INDEX_IS_VALID(*genericType)) + // First, get the method's owner type to check if it's actually generic + CLR_RT_TypeDef_Instance ownerType{}; + if (!ownerType.InitializeFromMethod(*this)) + { + return false; + } + + // Only use the generic type context if: + // 1. We have a generic type context available AND + // 2. The method's declaring type is actually generic + if (genericType && NANOCLR_INDEX_IS_VALID(*genericType) && ownerType.target->genericParamCount > 0) { // Look up the assembly that actually owns that TypeSpec auto tsAsm = g_CLR_RT_TypeSystem.m_assemblies[genericType->Assembly() - 1]; @@ -2035,13 +2045,10 @@ bool CLR_RT_MethodDef_Instance::GetDeclaringType(CLR_RT_TypeDef_Instance &declTy return declType.InitializeFromIndex(td); } } - else - { - // Normal (non‐generic or open‐generic) - return declType.InitializeFromMethod(*this); - } - return false; + // For non-generic types or when no generic context is available, + // just return the declaring type + return declType.InitializeFromMethod(*this); } ////////////////////////////// From d2648ed7a24ad12810d9d5e1e7e9185080b5eb2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Mon, 17 Nov 2025 18:47:38 +0000 Subject: [PATCH 29/61] Fix preservation of generic context in callvirt --- src/CLR/Core/Interpreter.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/CLR/Core/Interpreter.cpp b/src/CLR/Core/Interpreter.cpp index 20dd7096d2..b05c78434b 100644 --- a/src/CLR/Core/Interpreter.cpp +++ b/src/CLR/Core/Interpreter.cpp @@ -2386,11 +2386,19 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) NANOCLR_CHECK_HRESULT(CLR_RT_StackFrame::Push(th, calleeInst, -1)); // Set up the new stack frame's generic context - // Prefer calleeInst.genericType (from MethodRef TypeSpec) over caller's generic context + // Priority order: + // 1. effectiveCallerGeneric (extracted from TypeSpec search for interface calls) - HIGHEST PRIORITY + // This is the concrete closed generic type (e.g., List) not the interface (e.g., IEnumerable) + // 2. calleeInst.genericType (from MethodRef TypeSpec or virtual dispatch) + // 3. stack->m_call.genericType (inherited from caller) CLR_RT_StackFrame *newStack = th->CurrentFrame(); const CLR_RT_TypeSpec_Index *effectiveGenericContext = nullptr; - if (calleeInst.genericType && NANOCLR_INDEX_IS_VALID(*calleeInst.genericType)) + if (effectiveCallerGeneric && NANOCLR_INDEX_IS_VALID(*effectiveCallerGeneric)) + { + effectiveGenericContext = effectiveCallerGeneric; + } + else if (calleeInst.genericType && NANOCLR_INDEX_IS_VALID(*calleeInst.genericType)) { effectiveGenericContext = calleeInst.genericType; } From 4bca5eacb721f6b840c17b72b817b131ed7b38b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Tue, 18 Nov 2025 18:26:14 +0000 Subject: [PATCH 30/61] Add missing clear methodSpec element in MethodDef_Instance struct --- src/CLR/Core/TypeSystem.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/CLR/Core/TypeSystem.cpp b/src/CLR/Core/TypeSystem.cpp index 640089b496..732898b095 100644 --- a/src/CLR/Core/TypeSystem.cpp +++ b/src/CLR/Core/TypeSystem.cpp @@ -1621,6 +1621,7 @@ bool CLR_RT_MethodDef_Instance::InitializeFromIndex(const CLR_RT_MethodDef_Index target = assembly->GetMethodDef(Method()); genericType = nullptr; arrayElementType.Clear(); + methodSpec.Clear(); #if defined(NANOCLR_INSTANCE_NAMES) name = assembly->GetString(target->name); @@ -1643,6 +1644,8 @@ bool CLR_RT_MethodDef_Instance::InitializeFromIndex( { NATIVE_PROFILE_CLR_CORE(); + methodSpec.Clear(); + CLR_RT_TypeSpec_Instance tsInst; if (!tsInst.InitializeFromIndex(typeSpec)) @@ -1766,6 +1769,9 @@ bool CLR_RT_MethodDef_Instance::ResolveToken( const CLR_RT_TypeSpec_Index *callerGeneric) { NATIVE_PROFILE_CLR_CORE(); + + ClearInstance(); + if (assm) { CLR_UINT32 index = CLR_DataFromTk(tk); From 19ca18181ce13312c12527f420855a62c147d649 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Tue, 18 Nov 2025 18:28:01 +0000 Subject: [PATCH 31/61] Add API to get generic argument from MethodSpec_Instance --- src/CLR/Core/TypeSystem.cpp | 31 +++++++++++++++++++++++++++++++ src/CLR/Include/nanoCLR_Runtime.h | 5 +++++ 2 files changed, 36 insertions(+) diff --git a/src/CLR/Core/TypeSystem.cpp b/src/CLR/Core/TypeSystem.cpp index 732898b095..e1ad091774 100644 --- a/src/CLR/Core/TypeSystem.cpp +++ b/src/CLR/Core/TypeSystem.cpp @@ -2149,6 +2149,37 @@ void CLR_RT_MethodSpec_Instance::ClearInstance() target = nullptr; } +bool CLR_RT_MethodSpec_Instance::GetGenericParameter( + CLR_INT32 genericParameterPosition, + CLR_RT_TypeDef_Index& typeDef, + NanoCLRDataType& dataType) +{ + CLR_RT_SignatureParser parser; + parser.Initialize_MethodSignature(this); + + // sanity check + if (genericParameterPosition >= parser.ParamCount) + { + return false; + } + + CLR_RT_SignatureParser::Element elem; + + // loop through parameters to find the desired one + for (CLR_INT32 i = 0; i <= genericParameterPosition; i++) + { + if (FAILED(parser.Advance(elem))) + { + return false; + } + } + + typeDef = elem.Class; + dataType = elem.DataType; + + return true; +} + //////////////////////////////////////////////////////////////////////////////////////////////////// void CLR_RT_TypeDescriptor::TypeDescriptor_Initialize() diff --git a/src/CLR/Include/nanoCLR_Runtime.h b/src/CLR/Include/nanoCLR_Runtime.h index ab517e7402..7fe2d2db13 100644 --- a/src/CLR/Include/nanoCLR_Runtime.h +++ b/src/CLR/Include/nanoCLR_Runtime.h @@ -2337,6 +2337,11 @@ struct CLR_RT_MethodSpec_Instance : public CLR_RT_MethodSpec_Index } CLR_EncodedMethodDefOrRef InstanceOfMethod; + + bool GetGenericParameter( + CLR_INT32 genericParameterPosition, + CLR_RT_TypeDef_Index &typeDef, + NanoCLRDataType &dataType); }; //////////////////////////////////////////////////////////////////////////////////////////////////// From 878fb37a031d9dfce99531b4c5b004eac14ac219 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Tue, 18 Nov 2025 18:59:11 +0000 Subject: [PATCH 32/61] MethodDef Instance added as parameter to several APIs to allow resolving MVAR arguments --- src/CLR/Core/Interpreter.cpp | 27 +++++++++++++++++++++------ src/CLR/Core/TypeSystem.cpp | 26 ++++++++++++++++++++------ src/CLR/Include/nanoCLR_Runtime.h | 9 ++++++--- 3 files changed, 47 insertions(+), 15 deletions(-) diff --git a/src/CLR/Core/Interpreter.cpp b/src/CLR/Core/Interpreter.cpp index b05c78434b..89716a84ee 100644 --- a/src/CLR/Core/Interpreter.cpp +++ b/src/CLR/Core/Interpreter.cpp @@ -2940,10 +2940,12 @@ 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 + // Pass both TypeSpec context (for VAR resolution) and MethodDef context (for MVAR resolution) ptr = field.assembly->GetStaticFieldByFieldDef( field, field.genericType, - &stack->m_genericTypeSpecStorage); + &stack->m_genericTypeSpecStorage, + &stack->m_call); } else { @@ -2973,7 +2975,14 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) { CLR_UINT32 hash = g_CLR_RT_TypeSystem.ComputeHashForClosedGenericType( tsInst, - &stack->m_genericTypeSpecStorage); + &stack->m_genericTypeSpecStorage, + &stack->m_call); + + if (hash == 0xFFFFFFFF) + { + NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE); + } + CLR_RT_GenericCctorExecutionRecord *cctorRecord = g_CLR_RT_TypeSystem.FindOrCreateGenericCctorRecord(hash, nullptr); @@ -3018,10 +3027,12 @@ 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 + // Pass both TypeSpec context (for VAR resolution) and MethodDef context (for MVAR resolution) ptr = field.assembly->GetStaticFieldByFieldDef( field, field.genericType, - &stack->m_genericTypeSpecStorage); + &stack->m_genericTypeSpecStorage, + &stack->m_call); } else { @@ -3041,7 +3052,8 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) { CLR_UINT32 hash = g_CLR_RT_TypeSystem.ComputeHashForClosedGenericType( tsInst, - &stack->m_genericTypeSpecStorage); + &stack->m_genericTypeSpecStorage, + &stack->m_call); CLR_RT_GenericCctorExecutionRecord *cctorRecord = g_CLR_RT_TypeSystem.FindOrCreateGenericCctorRecord(hash, nullptr); @@ -3088,10 +3100,12 @@ 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 + // Pass both TypeSpec context (for VAR resolution) and MethodDef context (for MVAR resolution) ptr = field.assembly->GetStaticFieldByFieldDef( field, field.genericType, - &stack->m_genericTypeSpecStorage); + &stack->m_genericTypeSpecStorage, + &stack->m_call); } else { @@ -3111,7 +3125,8 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) { CLR_UINT32 hash = g_CLR_RT_TypeSystem.ComputeHashForClosedGenericType( tsInst, - &stack->m_genericTypeSpecStorage); + &stack->m_genericTypeSpecStorage, + &stack->m_call); CLR_RT_GenericCctorExecutionRecord *cctorRecord = g_CLR_RT_TypeSystem.FindOrCreateGenericCctorRecord(hash, nullptr); diff --git a/src/CLR/Core/TypeSystem.cpp b/src/CLR/Core/TypeSystem.cpp index e1ad091774..cdd84f8f57 100644 --- a/src/CLR/Core/TypeSystem.cpp +++ b/src/CLR/Core/TypeSystem.cpp @@ -5320,7 +5320,8 @@ CLR_RT_HeapBlock *CLR_RT_Assembly::GetGenericStaticField( CLR_RT_HeapBlock *CLR_RT_Assembly::GetStaticFieldByFieldDef( const CLR_RT_FieldDef_Index &fdIndex, const CLR_RT_TypeSpec_Index *genericType, - const CLR_RT_TypeSpec_Index *contextTypeSpec) + const CLR_RT_TypeSpec_Index *contextTypeSpec, + const CLR_RT_MethodDef_Instance *contextMethod) { NATIVE_PROFILE_CLR_CORE(); @@ -5346,8 +5347,12 @@ CLR_RT_HeapBlock *CLR_RT_Assembly::GetStaticFieldByFieldDef( if (genericTypeDef.target->staticFieldsCount > 0) { // Allocate static fields on-demand for this runtime-bound generic - // Pass the caller context so we can resolve generic parameters - if (SUCCEEDED(AllocateGenericStaticFieldsOnDemand(*genericType, genericTypeDef, contextTypeSpec))) + // Pass both context parameters for proper VAR and MVAR resolution + if (SUCCEEDED(AllocateGenericStaticFieldsOnDemand( + *genericType, + genericTypeDef, + contextTypeSpec, + contextMethod))) { // Retry the lookup after allocation hb = GetGenericStaticField(*genericType, fdIndex); @@ -5376,7 +5381,8 @@ CLR_RT_HeapBlock *CLR_RT_Assembly::GetStaticFieldByFieldDef( HRESULT CLR_RT_Assembly::AllocateGenericStaticFieldsOnDemand( const CLR_RT_TypeSpec_Index &typeSpecIndex, const CLR_RT_TypeDef_Instance &genericTypeDef, - const CLR_RT_TypeSpec_Index *contextTypeSpec) + const CLR_RT_TypeSpec_Index *contextTypeSpec, + const CLR_RT_MethodDef_Instance *contextMethod) { NATIVE_PROFILE_CLR_CORE(); NANOCLR_HEADER(); @@ -5403,7 +5409,14 @@ HRESULT CLR_RT_Assembly::AllocateGenericStaticFieldsOnDemand( } // Compute hash for this closed generic type, using context to resolve VAR/MVAR if needed - hash = g_CLR_RT_TypeSystem.ComputeHashForClosedGenericType(tsInstance, contextTypeSpec); + // Pass both TypeSpec context (for VAR) and MethodDef context (for MVAR) + hash = g_CLR_RT_TypeSystem.ComputeHashForClosedGenericType(tsInstance, contextTypeSpec, contextMethod); + + // If hash computation failed (returned 0), we can't create unique storage for this generic type + if (hash == 0) + { + NANOCLR_SET_AND_LEAVE(CLR_E_NOT_SUPPORTED); + } // Check if already allocated (shouldn't happen if called from GetStaticFieldByFieldDef, but be safe) for (CLR_UINT32 i = 0; i < g_CLR_RT_TypeSystem.m_genericStaticFieldsCount; i++) @@ -8185,7 +8198,8 @@ CLR_RT_GenericStaticFieldRecord *CLR_RT_TypeSystem::FindOrCreateGenericStaticFie CLR_UINT32 CLR_RT_TypeSystem::ComputeHashForClosedGenericType( CLR_RT_TypeSpec_Instance &typeInstance, - const CLR_RT_TypeSpec_Index *contextTypeSpec) + const CLR_RT_TypeSpec_Index *contextTypeSpec, + const CLR_RT_MethodDef_Instance *contextMethod) { CLR_UINT32 hash = 0; diff --git a/src/CLR/Include/nanoCLR_Runtime.h b/src/CLR/Include/nanoCLR_Runtime.h index 7fe2d2db13..44014d2c62 100644 --- a/src/CLR/Include/nanoCLR_Runtime.h +++ b/src/CLR/Include/nanoCLR_Runtime.h @@ -1438,11 +1438,13 @@ struct CLR_RT_Assembly : public CLR_RT_HeapBlock_Node // EVENT HEAP - NO RELOCAT CLR_RT_HeapBlock *GetStaticFieldByFieldDef( const CLR_RT_FieldDef_Index &fdIndex, const CLR_RT_TypeSpec_Index *genericType, - const CLR_RT_TypeSpec_Index *contextTypeSpec = nullptr); + const CLR_RT_TypeSpec_Index *contextTypeSpec = nullptr, + const CLR_RT_MethodDef_Instance *contextMethod = nullptr); HRESULT AllocateGenericStaticFieldsOnDemand( const CLR_RT_TypeSpec_Index &typeSpecIndex, const CLR_RT_TypeDef_Instance &genericTypeDef, - const CLR_RT_TypeSpec_Index *contextTypeSpec = nullptr); + const CLR_RT_TypeSpec_Index *contextTypeSpec = nullptr, + const CLR_RT_MethodDef_Instance *contextMethod = nullptr); HRESULT PrepareForExecution(); CLR_UINT32 ComputeAssemblyHash(); @@ -2141,7 +2143,8 @@ struct CLR_RT_TypeSystem // EVENT HEAP - NO RELOCATION - // Helper to compute hash for a closed generic type static CLR_UINT32 ComputeHashForClosedGenericType( CLR_RT_TypeSpec_Instance &typeInstance, - const CLR_RT_TypeSpec_Index *contextTypeSpec = nullptr); + const CLR_RT_TypeSpec_Index *contextTypeSpec = nullptr, + const CLR_RT_MethodDef_Instance *contextMethod = nullptr); // Helper to find or create a generic .cctor execution record by hash static CLR_RT_GenericCctorExecutionRecord *FindOrCreateGenericCctorRecord(CLR_UINT32 hash, bool *created); From dbe1528b1998d236e64ec3702db90e512c1e5cb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Tue, 18 Nov 2025 19:00:17 +0000 Subject: [PATCH 33/61] ComputeHashForClosedGenericType now properly resolves MVAR and VAR types - Also improve processing of return on error. --- src/CLR/Core/TypeSystem.cpp | 39 ++++++++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/src/CLR/Core/TypeSystem.cpp b/src/CLR/Core/TypeSystem.cpp index cdd84f8f57..b15e5ef007 100644 --- a/src/CLR/Core/TypeSystem.cpp +++ b/src/CLR/Core/TypeSystem.cpp @@ -8202,6 +8202,7 @@ CLR_UINT32 CLR_RT_TypeSystem::ComputeHashForClosedGenericType( const CLR_RT_MethodDef_Instance *contextMethod) { CLR_UINT32 hash = 0; + int argCount; // Start with the generic type definition hash = SUPPORT_ComputeCRC(&typeInstance.genericTypeDef.data, sizeof(CLR_UINT32), hash); @@ -8215,17 +8216,17 @@ CLR_UINT32 CLR_RT_TypeSystem::ComputeHashForClosedGenericType( // Advance to the generic instance marker if (FAILED(parser.Advance(elem)) || elem.DataType != DATATYPE_GENERICINST) { - return hash; + goto ComputeHash_End; } // Advance to the generic type definition if (FAILED(parser.Advance(elem))) { - return hash; + goto ComputeHash_End; } // Get argument count - int argCount = elem.GenParamCount; + argCount = elem.GenParamCount; // Process each generic argument for (int i = 0; i < argCount; i++) @@ -8236,10 +8237,9 @@ CLR_UINT32 CLR_RT_TypeSystem::ComputeHashForClosedGenericType( } // Check if this is an unresolved generic parameter (VAR or MVAR) - if ((elem.DataType == DATATYPE_VAR || elem.DataType == DATATYPE_MVAR) && contextTypeSpec && - NANOCLR_INDEX_IS_VALID(*contextTypeSpec)) + if (elem.DataType == DATATYPE_VAR && contextTypeSpec && NANOCLR_INDEX_IS_VALID(*contextTypeSpec)) { - // Resolve VAR from context TypeSpec using existing helper + // Resolve VAR (type parameter) from context TypeSpec CLR_RT_TypeDef_Index resolvedTypeDef; NanoCLRDataType resolvedDataType; @@ -8261,8 +8261,32 @@ CLR_UINT32 CLR_RT_TypeSystem::ComputeHashForClosedGenericType( } else { - // couldn't resolve, reset hash to indicate failure + // couldn't resolve VAR, return failure hash = 0; + goto ComputeHash_End; + } + } + else if (elem.DataType == DATATYPE_MVAR && contextMethod && NANOCLR_INDEX_IS_VALID(contextMethod->methodSpec)) + { + // Resolve MVAR (method parameter) from MethodSpec instance + + CLR_RT_MethodSpec_Instance methodSpecInst{}; + if (methodSpecInst.InitializeFromIndex(contextMethod->methodSpec)) + { + CLR_RT_TypeDef_Index resolvedTypeDef; + NanoCLRDataType resolvedDataType; + + if (methodSpecInst.GetGenericParameter(elem.GenericParamPosition, resolvedTypeDef, resolvedDataType)) + { + // Use the resolved type from MethodSpec + hash = SUPPORT_ComputeCRC(&resolvedDataType, sizeof(resolvedDataType), hash); + } + else + { + // couldn't resolve MVAR, return failure + hash = 0; + goto ComputeHash_End; + } } } else @@ -8277,6 +8301,7 @@ CLR_UINT32 CLR_RT_TypeSystem::ComputeHashForClosedGenericType( } } +ComputeHash_End: return hash ? hash : 0xFFFFFFFF; // Don't allow zero as a hash value } From 378c820fbb099fbeae4fe905e063918f4af5d0f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Tue, 18 Nov 2025 19:01:08 +0000 Subject: [PATCH 34/61] Code style fixes --- src/CLR/Core/TypeSystem.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/CLR/Core/TypeSystem.cpp b/src/CLR/Core/TypeSystem.cpp index b15e5ef007..20898756fc 100644 --- a/src/CLR/Core/TypeSystem.cpp +++ b/src/CLR/Core/TypeSystem.cpp @@ -1644,7 +1644,7 @@ bool CLR_RT_MethodDef_Instance::InitializeFromIndex( { NATIVE_PROFILE_CLR_CORE(); - methodSpec.Clear(); + methodSpec.Clear(); CLR_RT_TypeSpec_Instance tsInst; @@ -2151,8 +2151,8 @@ void CLR_RT_MethodSpec_Instance::ClearInstance() bool CLR_RT_MethodSpec_Instance::GetGenericParameter( CLR_INT32 genericParameterPosition, - CLR_RT_TypeDef_Index& typeDef, - NanoCLRDataType& dataType) + CLR_RT_TypeDef_Index &typeDef, + NanoCLRDataType &dataType) { CLR_RT_SignatureParser parser; parser.Initialize_MethodSignature(this); From 9479d930db587515233c5ae62d2de2648aa2e0d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Tue, 18 Nov 2025 19:07:42 +0000 Subject: [PATCH 35/61] BuildTypeName for TypeSpec now takes optional MethodDef instance as parameter - Add code to resolve and print MVAR args. --- src/CLR/Core/TypeSystem.cpp | 65 +++++++++++++++++++++++++------ src/CLR/Include/nanoCLR_Runtime.h | 8 +++- 2 files changed, 60 insertions(+), 13 deletions(-) diff --git a/src/CLR/Core/TypeSystem.cpp b/src/CLR/Core/TypeSystem.cpp index 20898756fc..0c701d2ab6 100644 --- a/src/CLR/Core/TypeSystem.cpp +++ b/src/CLR/Core/TypeSystem.cpp @@ -7438,7 +7438,9 @@ HRESULT CLR_RT_TypeSystem::BuildTypeName( const CLR_RT_TypeSpec_Index &typeIndex, char *&szBuffer, size_t &iBuffer, - CLR_UINT32 levels) + CLR_UINT32 levels, + const CLR_RT_TypeSpec_Index *contextTypeSpec, + const CLR_RT_MethodDef_Instance *contextMethodDef) { NATIVE_PROFILE_CLR_CORE(); NANOCLR_HEADER(); @@ -7485,27 +7487,66 @@ HRESULT CLR_RT_TypeSystem::BuildTypeName( if (element.DataType == DATATYPE_VAR) { // resolve the !T against our *closed* typeIndex, if possible - CLR_RT_TypeDef_Index paramTd; - NanoCLRDataType paramDt; + if (contextTypeSpec != nullptr && NANOCLR_INDEX_IS_VALID(*contextTypeSpec)) + { + // generic type parameter - // this will bind !T→System.Int32, etc. - typeSpecInstance.assembly->FindGenericParamAtTypeSpec( - typeIndex.data, - element.GenericParamPosition, // the !N slot - paramTd, - paramDt); + CLR_RT_TypeDef_Index paramTd; + NanoCLRDataType paramDt; - if (paramDt == DATATYPE_VAR) + CLR_RT_Assembly *typeContextAssm = g_CLR_RT_TypeSystem.m_assemblies[contextTypeSpec->Assembly() - 1]; + // this will bind !T→System.Int32, etc. + typeContextAssm->FindGenericParamAtTypeSpec( + contextTypeSpec->TypeSpec(), + element.GenericParamPosition, // the !N slot + paramTd, + paramDt); + + if (paramDt == DATATYPE_VAR) + { + // couldn't be resolved, print encoded form (!N) + char encodedParam[6]; + snprintf(encodedParam, ARRAYSIZE(encodedParam), "!%d", element.GenericParamPosition); + NANOCLR_CHECK_HRESULT(QueueStringToBuffer(szBuffer, iBuffer, encodedParam)); + } + else + { + // now print the *actual* type name + BuildTypeName(paramTd, szBuffer, iBuffer); + } + } + else { // couldn't be resolved, print encoded form (!N) char encodedParam[6]; snprintf(encodedParam, ARRAYSIZE(encodedParam), "!%d", element.GenericParamPosition); NANOCLR_CHECK_HRESULT(QueueStringToBuffer(szBuffer, iBuffer, encodedParam)); } + } + else if (element.DataType == DATATYPE_MVAR) + { + // method generic parameter + if (contextMethodDef != nullptr && NANOCLR_INDEX_IS_VALID(*contextMethodDef)) + { + if (NANOCLR_INDEX_IS_VALID(contextMethodDef->methodSpec)) + { + CLR_RT_MethodSpec_Instance methodSpec{}; + methodSpec.InitializeFromIndex(contextMethodDef->methodSpec); + + if (!methodSpec.GetGenericParameter(element.GenericParamPosition, typeDef, element.DataType)) + { + NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE); + } + + BuildTypeName(typeDef, szBuffer, iBuffer); + } + } else { - // now print the *actual* type name - BuildTypeName(paramTd, szBuffer, iBuffer); + // couldn't be resolved, print encoded form (!!N) + char encodedParam[7]; + snprintf(encodedParam, ARRAYSIZE(encodedParam), "!!%d", element.GenericParamPosition); + NANOCLR_CHECK_HRESULT(QueueStringToBuffer(szBuffer, iBuffer, encodedParam)); } } else diff --git a/src/CLR/Include/nanoCLR_Runtime.h b/src/CLR/Include/nanoCLR_Runtime.h index 44014d2c62..b6473409c0 100644 --- a/src/CLR/Include/nanoCLR_Runtime.h +++ b/src/CLR/Include/nanoCLR_Runtime.h @@ -2077,7 +2077,13 @@ struct CLR_RT_TypeSystem // EVENT HEAP - NO RELOCATION - const CLR_RECORD_RESOURCE *&res, CLR_UINT32 &size); - HRESULT BuildTypeName(const CLR_RT_TypeSpec_Index &typeIndex, char *&szBuffer, size_t &iBuffer, CLR_UINT32 levels); + HRESULT BuildTypeName( + const CLR_RT_TypeSpec_Index &typeIndex, + char *&szBuffer, + size_t &iBuffer, + CLR_UINT32 levels, + const CLR_RT_TypeSpec_Index *contextTypeSpec = nullptr, + const CLR_RT_MethodDef_Instance *contextMethodDef = nullptr); HRESULT BuildTypeName( const CLR_RT_TypeDef_Index &cls, char *&szBuffer, From a4135b535c37bb63ecc767523573025c4ab58136 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Tue, 18 Nov 2025 19:16:45 +0000 Subject: [PATCH 36/61] Add BuildMethodName for MethodDef instance --- src/CLR/Core/TypeSystem.cpp | 160 ++++++++++++++++++++++++++++++ src/CLR/Include/nanoCLR_Runtime.h | 5 + 2 files changed, 165 insertions(+) diff --git a/src/CLR/Core/TypeSystem.cpp b/src/CLR/Core/TypeSystem.cpp index 0c701d2ab6..87efbdf50d 100644 --- a/src/CLR/Core/TypeSystem.cpp +++ b/src/CLR/Core/TypeSystem.cpp @@ -7713,6 +7713,166 @@ HRESULT CLR_RT_TypeSystem::BuildMethodName( NANOCLR_NOCLEANUP(); } +HRESULT CLR_RT_TypeSystem::BuildMethodName( + const CLR_RT_MethodDef_Instance &mdInst, + const CLR_RT_TypeSpec_Index *genericType, + char *&szBuffer, + size_t &iBuffer) +{ + NATIVE_PROFILE_CLR_CORE(); + NANOCLR_HEADER(); + + CLR_RT_TypeDef_Instance declTypeInst{}; + CLR_RT_TypeDef_Index declTypeIdx; + CLR_RT_TypeDef_Instance instOwner{}; + bool useGeneric = false; + + if (!declTypeInst.InitializeFromMethod(mdInst)) + { + NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE); + } + + declTypeIdx.Set(mdInst.Assembly(), declTypeInst.assembly->crossReferenceMethodDef[mdInst.Method()].GetOwner()); + + if (genericType != nullptr && NANOCLR_INDEX_IS_VALID(*genericType) && genericType->data != CLR_EmptyToken) + { + // parse TypeSpec to get its TypeDef + CLR_RT_TypeSpec_Instance tsInst = {}; + + if (tsInst.InitializeFromIndex(*genericType)) + { + if (tsInst.genericTypeDef.Type() == declTypeIdx.Type()) + { + useGeneric = true; + } + } + } + + if (!useGeneric) + { + if (instOwner.InitializeFromMethod(mdInst) == false) + { + NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE); + } + + NANOCLR_CHECK_HRESULT(BuildTypeName(instOwner, szBuffer, iBuffer)); + } + else + { + // First, build the type name (either from genericType or from the method's declaring type) + if (genericType != nullptr && NANOCLR_INDEX_IS_VALID(*genericType) && genericType->data != CLR_EmptyToken) + { + // Use the provided generic type context + NANOCLR_CHECK_HRESULT(BuildTypeName(*genericType, szBuffer, iBuffer, 0)); + } + else if ( + mdInst.genericType != nullptr && NANOCLR_INDEX_IS_VALID(*mdInst.genericType) && + mdInst.genericType->data != CLR_EmptyToken) + { + // Use the method instance's generic type + NANOCLR_CHECK_HRESULT(BuildTypeName(*mdInst.genericType, szBuffer, iBuffer, 0)); + } + else + { + // Fall back to the declaring type + if (instOwner.InitializeFromMethod(mdInst) == false) + { + NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE); + } + NANOCLR_CHECK_HRESULT(BuildTypeName(instOwner, szBuffer, iBuffer)); + } + + // Append the method name + CLR_SafeSprintf(szBuffer, iBuffer, "::%s", mdInst.assembly->GetString(mdInst.target->name)); + + // If this method has generic parameters (methodSpec is valid), append them + if (NANOCLR_INDEX_IS_VALID(mdInst.methodSpec)) + { + CLR_RT_MethodSpec_Instance msInst{}; + if (msInst.InitializeFromIndex(mdInst.methodSpec)) + { + // Parse the methodSpec instantiation signature to get the generic arguments + CLR_RT_SignatureParser parser{}; + parser.Initialize_MethodSignature(&msInst); + + CLR_SafeSprintf(szBuffer, iBuffer, "<"); + + for (int i = 0; i < parser.ParamCount; i++) + { + CLR_RT_SignatureParser::Element elem{}; + if (FAILED(parser.Advance(elem))) + { + break; + } + + if (i > 0) + { + CLR_SafeSprintf(szBuffer, iBuffer, ", "); + } + + // Build the type name for this generic argument + // Use the method's declaring type as context for VAR resolution + const CLR_RT_TypeSpec_Index *context = + (mdInst.genericType && NANOCLR_INDEX_IS_VALID(*mdInst.genericType)) ? mdInst.genericType + : nullptr; + + if (elem.DataType == DATATYPE_VAR || elem.DataType == DATATYPE_MVAR) + { + // Generic parameter - try to resolve it + if (context != nullptr) + { + CLR_RT_TypeDef_Index resolvedType{}; + NanoCLRDataType resolvedDT; + + if (mdInst.assembly->FindGenericParamAtTypeSpec( + context->TypeSpec(), + elem.GenericParamPosition, + resolvedType, + resolvedDT)) + { + NANOCLR_CHECK_HRESULT(BuildTypeName(resolvedType, szBuffer, iBuffer)); + } + else + { + // Couldn't resolve - show as !n or !!n + if (elem.DataType == DATATYPE_VAR) + { + CLR_SafeSprintf(szBuffer, iBuffer, "!%d", elem.GenericParamPosition); + } + else + { + CLR_SafeSprintf(szBuffer, iBuffer, "!!%d", elem.GenericParamPosition); + } + } + } + else + { + // No context - show as !n or !!n + if (elem.DataType == DATATYPE_VAR) + { + CLR_SafeSprintf(szBuffer, iBuffer, "!%d", elem.GenericParamPosition); + } + else + { + CLR_SafeSprintf(szBuffer, iBuffer, "!!%d", elem.GenericParamPosition); + } + } + } + else if (NANOCLR_INDEX_IS_VALID(elem.Class)) + { + // Concrete type + NANOCLR_CHECK_HRESULT(BuildTypeName(elem.Class, szBuffer, iBuffer)); + } + } + + CLR_SafeSprintf(szBuffer, iBuffer, ">"); + } + } + } + + NANOCLR_NOCLEANUP(); +} + HRESULT CLR_RT_TypeSystem::BuildFieldName(const CLR_RT_FieldDef_Index &fd, char *&szBuffer, size_t &iBuffer) { NATIVE_PROFILE_CLR_CORE(); diff --git a/src/CLR/Include/nanoCLR_Runtime.h b/src/CLR/Include/nanoCLR_Runtime.h index b6473409c0..93f86482f9 100644 --- a/src/CLR/Include/nanoCLR_Runtime.h +++ b/src/CLR/Include/nanoCLR_Runtime.h @@ -2096,6 +2096,11 @@ struct CLR_RT_TypeSystem // EVENT HEAP - NO RELOCATION - const CLR_RT_TypeSpec_Index *genericType, char *&szBuffer, size_t &size); + HRESULT BuildMethodName( + const CLR_RT_MethodDef_Instance &mdInst, + const CLR_RT_TypeSpec_Index *genericType, + char *&szBuffer, + size_t &size); HRESULT BuildFieldName(const CLR_RT_FieldDef_Index &fd, char *&szBuffer, size_t &size); HRESULT BuildMethodRefName(const CLR_RT_MethodRef_Index &method, char *&szBuffer, size_t &iBuffer); HRESULT BuildMethodRefName( From 5b24217fa5d3b270a31e43e0c583098e992288b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Tue, 18 Nov 2025 19:18:04 +0000 Subject: [PATCH 37/61] Add dump method for MethodDef instance --- src/CLR/Diagnostics/Diagnostics_stub.cpp | 8 ++++++++ src/CLR/Diagnostics/Info.cpp | 12 ++++++++++++ src/CLR/Include/nanoCLR_Checks.h | 1 + 3 files changed, 21 insertions(+) diff --git a/src/CLR/Diagnostics/Diagnostics_stub.cpp b/src/CLR/Diagnostics/Diagnostics_stub.cpp index 2ca278ea77..24a4ace5e7 100644 --- a/src/CLR/Diagnostics/Diagnostics_stub.cpp +++ b/src/CLR/Diagnostics/Diagnostics_stub.cpp @@ -190,6 +190,14 @@ __nfweak void CLR_RT_DUMP::METHOD(const CLR_RT_MethodDef_Index &method, const CL NATIVE_PROFILE_CLR_DIAGNOSTICS(); } +__nfweak void CLR_RT_DUMP::METHOD(const CLR_RT_MethodDef_Instance &mdInst, const CLR_RT_TypeSpec_Index *genericType) +{ + (void)mdInst; + (void)genericType; + + NATIVE_PROFILE_CLR_DIAGNOSTICS(); +} + __nfweak void CLR_RT_DUMP::FIELD(const CLR_RT_FieldDef_Index &field) { (void)field; diff --git a/src/CLR/Diagnostics/Info.cpp b/src/CLR/Diagnostics/Info.cpp index 1d0f44ada1..3c7a2e75b4 100644 --- a/src/CLR/Diagnostics/Info.cpp +++ b/src/CLR/Diagnostics/Info.cpp @@ -1025,6 +1025,18 @@ void CLR_RT_DUMP::METHOD(const CLR_RT_MethodDef_Index &method, const CLR_RT_Type CLR_Debug::Printf("%s", rgBuffer); } +void CLR_RT_DUMP::METHOD(const CLR_RT_MethodDef_Instance &mdInst, const CLR_RT_TypeSpec_Index *genericType) +{ + NATIVE_PROFILE_CLR_DIAGNOSTICS(); + char rgBuffer[512]; + char *szBuffer = rgBuffer; + size_t iBuffer = MAXSTRLEN(rgBuffer); + + g_CLR_RT_TypeSystem.BuildMethodName(mdInst, genericType, szBuffer, iBuffer); + + CLR_Debug::Printf("%s", rgBuffer); +} + void CLR_RT_DUMP::FIELD(const CLR_RT_FieldDef_Index &field) { NATIVE_PROFILE_CLR_DIAGNOSTICS(); diff --git a/src/CLR/Include/nanoCLR_Checks.h b/src/CLR/Include/nanoCLR_Checks.h index f13a26126c..8048974bb1 100644 --- a/src/CLR/Include/nanoCLR_Checks.h +++ b/src/CLR/Include/nanoCLR_Checks.h @@ -27,6 +27,7 @@ struct CLR_RT_DUMP static void TYPE (const CLR_RT_TypeDef_Index& cls ) DECL_POSTFIX; static void TYPE (const CLR_RT_ReflectionDef_Index& reflex ) DECL_POSTFIX; static void METHOD (const CLR_RT_MethodDef_Index& method, const CLR_RT_TypeSpec_Index *genericType) DECL_POSTFIX; + static void METHOD (const CLR_RT_MethodDef_Instance& mdInst, const CLR_RT_TypeSpec_Index *genericType) DECL_POSTFIX; static void FIELD (const CLR_RT_FieldDef_Index& field ) DECL_POSTFIX; static void OBJECT ( CLR_RT_HeapBlock* ptr , const char* text ) DECL_POSTFIX; static void METHODREF (const CLR_RT_MethodRef_Index& method ) DECL_POSTFIX; From ce35fc2be4cbb63b31d0350a775cd169ff55963a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Tue, 18 Nov 2025 19:21:21 +0000 Subject: [PATCH 38/61] Fix DumpToken processing for FieldDef - Now processes correctly fields with TypeSpec owner. --- src/CLR/Diagnostics/Info.cpp | 39 +++++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/src/CLR/Diagnostics/Info.cpp b/src/CLR/Diagnostics/Info.cpp index 3c7a2e75b4..93958a0090 100644 --- a/src/CLR/Diagnostics/Info.cpp +++ b/src/CLR/Diagnostics/Info.cpp @@ -446,17 +446,38 @@ void CLR_RT_Assembly::DumpToken(CLR_UINT32 token, const CLR_RT_MethodDef_Instanc const CLR_RECORD_FIELDREF *fr = GetFieldRef(index); const auto &xref = crossReferenceFieldRef[index]; - // If the caller passed in a closed‐generic TypeSpec, use that … + // If the caller passed in a closed‐generic TypeSpec, use that if (methodDefInstance.genericType != nullptr && methodDefInstance.genericType->data != CLR_EmptyToken) { - // Build the closed‐generic owner name - char rgType[256], *sz = rgType; - size_t cb = sizeof(rgType); - g_CLR_RT_TypeSystem.BuildTypeName(*methodDefInstance.genericType, sz, cb, 0); - - // Append the field name - CLR_SafeSprintf(sz, cb, "::%s", GetString(fr->name)); - CLR_Debug::Printf("%s", rgType); + // The field's encodedOwner points to the TypeSpec we want to build the name for (e.g., EmptyArray) + // and methodDefInstance.genericType is the closed generic type that provides context (e.g., + // EmptyArray) + if (fr->Owner() == TBL_TypeSpec) + { + static CLR_RT_TypeSpec_Index s_ownerTypeSpec; + s_ownerTypeSpec.Set(assemblyIndex, fr->OwnerIndex()); + + // Build the type name using the closed generic as context to resolve VAR parameters + char rgType[256], *sz = rgType; + size_t cb = sizeof(rgType); + g_CLR_RT_TypeSystem + .BuildTypeName(s_ownerTypeSpec, sz, cb, 0, methodDefInstance.genericType, &methodDefInstance); + + // Append the field name + CLR_SafeSprintf(sz, cb, "::%s", GetString(fr->name)); + CLR_Debug::Printf("%s", rgType); + } + else + { + // TypeRef case - just use the existing genericType + char rgType[256], *sz = rgType; + size_t cb = sizeof(rgType); + g_CLR_RT_TypeSystem.BuildTypeName(*methodDefInstance.genericType, sz, cb, 0, nullptr); + + // Append the field name + CLR_SafeSprintf(sz, cb, "::%s", GetString(fr->name)); + CLR_Debug::Printf("%s", rgType); + } } else { From 7082704c17ea1b689b3f05cc3a038f5497b89e85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Tue, 18 Nov 2025 19:56:25 +0000 Subject: [PATCH 39/61] HeapBlock_Delegate now also stores a MethodSpec Index to allow resolution of method generic args --- src/CLR/Core/CLR_RT_HeapBlock_Delegate.cpp | 1 + src/CLR/Core/Thread.cpp | 16 ++++++++++++++++ src/CLR/Core/TypeSystem.cpp | 6 ++++-- src/CLR/Include/nanoCLR_Runtime__HeapBlock.h | 7 ++++++- 4 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/CLR/Core/CLR_RT_HeapBlock_Delegate.cpp b/src/CLR/Core/CLR_RT_HeapBlock_Delegate.cpp index 0fd44b5cae..f40915abac 100644 --- a/src/CLR/Core/CLR_RT_HeapBlock_Delegate.cpp +++ b/src/CLR/Core/CLR_RT_HeapBlock_Delegate.cpp @@ -57,6 +57,7 @@ HRESULT CLR_RT_HeapBlock_Delegate::CreateInstance( dlg->m_object.SetObjectReference(nullptr); dlg->m_genericTypeSpec.Clear(); + dlg->m_genericMethodSpec.Clear(); #if defined(NANOCLR_APPDOMAINS) dlg->m_appDomain = g_CLR_RT_ExecutionEngine.GetCurrentAppDomain(); diff --git a/src/CLR/Core/Thread.cpp b/src/CLR/Core/Thread.cpp index 1461930ef9..52b73bb8a1 100644 --- a/src/CLR/Core/Thread.cpp +++ b/src/CLR/Core/Thread.cpp @@ -167,13 +167,21 @@ HRESULT CLR_RT_Thread::PushThreadProcDelegate(CLR_RT_HeapBlock_Delegate *pDelega // Note: We temporarily set inst.genericType to the delegate's interior pointer here, // but will copy it to stable storage in the stack frame after Push() below CLR_RT_TypeSpec_Index delegateTypeSpec; + CLR_RT_MethodSpec_Index delegateMethodSpec; delegateTypeSpec.Clear(); + delegateMethodSpec.Clear(); if (pDelegate->m_genericTypeSpec.data != 0) { delegateTypeSpec = pDelegate->m_genericTypeSpec; inst.genericType = &delegateTypeSpec; } + + if (pDelegate->m_genericMethodSpec.data != 0) + { + delegateMethodSpec = pDelegate->m_genericMethodSpec; + inst.methodSpec = delegateMethodSpec; + } #if defined(NANOCLR_APPDOMAINS) @@ -201,6 +209,14 @@ HRESULT CLR_RT_Thread::PushThreadProcDelegate(CLR_RT_HeapBlock_Delegate *pDelega stackTop->m_genericTypeSpecStorage = delegateTypeSpec; stackTop->m_call.genericType = &stackTop->m_genericTypeSpecStorage; } + + // If we have a generic method context, copy it to the stack frame + // This enables MVAR resolution for .cctor triggered from generic methods + if (delegateMethodSpec.data != 0) + { + CLR_RT_StackFrame *stackTop = this->CurrentFrame(); + stackTop->m_call.methodSpec = delegateMethodSpec; + } if ((inst.target->flags & CLR_RECORD_METHODDEF::MD_Static) == 0) { diff --git a/src/CLR/Core/TypeSystem.cpp b/src/CLR/Core/TypeSystem.cpp index 87efbdf50d..4ae6de4e55 100644 --- a/src/CLR/Core/TypeSystem.cpp +++ b/src/CLR/Core/TypeSystem.cpp @@ -5573,9 +5573,11 @@ HRESULT CLR_RT_Assembly::AllocateGenericStaticFieldsOnDemand( { CLR_RT_HeapBlock_Delegate *dlg = refDlg.DereferenceDelegate(); - // Store the TypeSpec index so the .cctor runs with the correct generic binding - // If the TypeSpec has unresolved parameters (!0), also store the caller's context + // Store the TypeSpec index so the .cctor can resolve type generic parameters dlg->m_genericTypeSpec = typeSpecIndex; + + // Store the caller's MethodSpec (if any) to enable reolution of method generic parameters + dlg->m_genericMethodSpec = contextMethod->methodSpec; // Push to the .cctor thread and schedule for execution if (SUCCEEDED(g_CLR_RT_ExecutionEngine.m_cctorThread->PushThreadProcDelegate(dlg))) diff --git a/src/CLR/Include/nanoCLR_Runtime__HeapBlock.h b/src/CLR/Include/nanoCLR_Runtime__HeapBlock.h index e799d0884b..d18bacfd36 100644 --- a/src/CLR/Include/nanoCLR_Runtime__HeapBlock.h +++ b/src/CLR/Include/nanoCLR_Runtime__HeapBlock.h @@ -1910,8 +1910,13 @@ struct CLR_RT_HeapBlock_Delegate : public CLR_RT_HeapBlock_Node // OBJECT HEAP - CLR_RT_AppDomain *m_appDomain; #endif - // Optional TypeSpec index for generic type static constructors (data == 0 means not set) + // Optional TypeSpec index for resolving type generic parameter (VARs like !0) + // (data == 0 means not set) CLR_RT_TypeSpec_Index m_genericTypeSpec; + + // Optional MethodSpec index for resolving method generic parameters (MVAR like !!0) + // (data == 0 means not set) + CLR_RT_MethodSpec_Index m_genericMethodSpec; //--// From f8eb33cac434fe9d20bd459efb44ad75b12cb205 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Wed, 19 Nov 2025 10:22:56 +0000 Subject: [PATCH 40/61] Rename API and parameter for coherence sake --- src/CLR/Core/TypeSystem.cpp | 14 +++++++------- src/CLR/Include/nanoCLR_Runtime.h | 5 +---- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/CLR/Core/TypeSystem.cpp b/src/CLR/Core/TypeSystem.cpp index 4ae6de4e55..69bbbfbf4a 100644 --- a/src/CLR/Core/TypeSystem.cpp +++ b/src/CLR/Core/TypeSystem.cpp @@ -2149,8 +2149,8 @@ void CLR_RT_MethodSpec_Instance::ClearInstance() target = nullptr; } -bool CLR_RT_MethodSpec_Instance::GetGenericParameter( - CLR_INT32 genericParameterPosition, +bool CLR_RT_MethodSpec_Instance::GetGenericArgument( + CLR_INT32 argumentPosition, CLR_RT_TypeDef_Index &typeDef, NanoCLRDataType &dataType) { @@ -2158,7 +2158,7 @@ bool CLR_RT_MethodSpec_Instance::GetGenericParameter( parser.Initialize_MethodSignature(this); // sanity check - if (genericParameterPosition >= parser.ParamCount) + if (argumentPosition >= parser.ParamCount) { return false; } @@ -2166,7 +2166,7 @@ bool CLR_RT_MethodSpec_Instance::GetGenericParameter( CLR_RT_SignatureParser::Element elem; // loop through parameters to find the desired one - for (CLR_INT32 i = 0; i <= genericParameterPosition; i++) + for (CLR_INT32 i = 0; i <= argumentPosition; i++) { if (FAILED(parser.Advance(elem))) { @@ -5575,7 +5575,7 @@ HRESULT CLR_RT_Assembly::AllocateGenericStaticFieldsOnDemand( // Store the TypeSpec index so the .cctor can resolve type generic parameters dlg->m_genericTypeSpec = typeSpecIndex; - + // Store the caller's MethodSpec (if any) to enable reolution of method generic parameters dlg->m_genericMethodSpec = contextMethod->methodSpec; @@ -7535,7 +7535,7 @@ HRESULT CLR_RT_TypeSystem::BuildTypeName( CLR_RT_MethodSpec_Instance methodSpec{}; methodSpec.InitializeFromIndex(contextMethodDef->methodSpec); - if (!methodSpec.GetGenericParameter(element.GenericParamPosition, typeDef, element.DataType)) + if (!methodSpec.GetGenericArgument(element.GenericParamPosition, typeDef, element.DataType)) { NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE); } @@ -8479,7 +8479,7 @@ CLR_UINT32 CLR_RT_TypeSystem::ComputeHashForClosedGenericType( CLR_RT_TypeDef_Index resolvedTypeDef; NanoCLRDataType resolvedDataType; - if (methodSpecInst.GetGenericParameter(elem.GenericParamPosition, resolvedTypeDef, resolvedDataType)) + if (methodSpecInst.GetGenericArgument(elem.GenericParamPosition, resolvedTypeDef, resolvedDataType)) { // Use the resolved type from MethodSpec hash = SUPPORT_ComputeCRC(&resolvedDataType, sizeof(resolvedDataType), hash); diff --git a/src/CLR/Include/nanoCLR_Runtime.h b/src/CLR/Include/nanoCLR_Runtime.h index 93f86482f9..8dc3dc0a99 100644 --- a/src/CLR/Include/nanoCLR_Runtime.h +++ b/src/CLR/Include/nanoCLR_Runtime.h @@ -2352,10 +2352,7 @@ struct CLR_RT_MethodSpec_Instance : public CLR_RT_MethodSpec_Index CLR_EncodedMethodDefOrRef InstanceOfMethod; - bool GetGenericParameter( - CLR_INT32 genericParameterPosition, - CLR_RT_TypeDef_Index &typeDef, - NanoCLRDataType &dataType); + bool GetGenericArgument(CLR_INT32 argumentPosition, CLR_RT_TypeDef_Index &typeDef, NanoCLRDataType &dataType); }; //////////////////////////////////////////////////////////////////////////////////////////////////// From c0fa57848f01f968499e74ece0565a530e549c07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Wed, 19 Nov 2025 11:12:19 +0000 Subject: [PATCH 41/61] Code style fixes --- src/CLR/Core/Execution.cpp | 2 +- src/CLR/Core/Interpreter.cpp | 8 ++------ src/CLR/Include/nanoCLR_Runtime.h | 4 +++- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/CLR/Core/Execution.cpp b/src/CLR/Core/Execution.cpp index c34d6ea855..bed13adbcc 100644 --- a/src/CLR/Core/Execution.cpp +++ b/src/CLR/Core/Execution.cpp @@ -3542,7 +3542,7 @@ bool CLR_RT_ExecutionEngine::IsInstanceOf( const CLR_RT_MethodDef_Instance *caller) { NATIVE_PROFILE_CLR_CORE(); - + CLR_RT_TypeDescriptor desc{}; CLR_RT_TypeDescriptor descTarget{}; diff --git a/src/CLR/Core/Interpreter.cpp b/src/CLR/Core/Interpreter.cpp index 89716a84ee..f942829755 100644 --- a/src/CLR/Core/Interpreter.cpp +++ b/src/CLR/Core/Interpreter.cpp @@ -2746,12 +2746,8 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) { FETCH_ARG_COMPRESSED_TYPETOKEN(arg, ip); - NANOCLR_CHECK_HRESULT(CLR_RT_ExecutionEngine::CastToType( - evalPos[0], - arg, - assm, - (op == CEE_ISINST), - &stack->m_call)); + NANOCLR_CHECK_HRESULT( + CLR_RT_ExecutionEngine::CastToType(evalPos[0], arg, assm, (op == CEE_ISINST), &stack->m_call)); break; } diff --git a/src/CLR/Include/nanoCLR_Runtime.h b/src/CLR/Include/nanoCLR_Runtime.h index 8dc3dc0a99..42bfd0be99 100644 --- a/src/CLR/Include/nanoCLR_Runtime.h +++ b/src/CLR/Include/nanoCLR_Runtime.h @@ -2459,7 +2459,9 @@ struct CLR_RT_TypeDescriptor HRESULT InitializeFromDataType(NanoCLRDataType dt); HRESULT InitializeFromReflection(const CLR_RT_ReflectionDef_Index &reflex); - HRESULT InitializeFromTypeSpec(const CLR_RT_TypeSpec_Index &sig, const CLR_RT_TypeSpec_Index *contextTypeSpec = nullptr); + HRESULT InitializeFromTypeSpec( + const CLR_RT_TypeSpec_Index &sig, + const CLR_RT_TypeSpec_Index *contextTypeSpec = nullptr); HRESULT InitializeFromType(const CLR_RT_TypeDef_Index &cls); HRESULT InitializeFromTypeDef(const CLR_RT_TypeDef_Index &cls); HRESULT InitializeFromGenericType(const CLR_RT_TypeSpec_Index &genericType); From 0a1dce93c4bc24e36fd38365a9209ca71708615c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Wed, 19 Nov 2025 12:14:06 +0000 Subject: [PATCH 42/61] BuildMethodName from MethodDef instance now considers genericType in MethodDef instance --- src/CLR/Core/TypeSystem.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/CLR/Core/TypeSystem.cpp b/src/CLR/Core/TypeSystem.cpp index 69bbbfbf4a..2661165974 100644 --- a/src/CLR/Core/TypeSystem.cpp +++ b/src/CLR/Core/TypeSystem.cpp @@ -7736,7 +7736,11 @@ HRESULT CLR_RT_TypeSystem::BuildMethodName( declTypeIdx.Set(mdInst.Assembly(), declTypeInst.assembly->crossReferenceMethodDef[mdInst.Method()].GetOwner()); - if (genericType != nullptr && NANOCLR_INDEX_IS_VALID(*genericType) && genericType->data != CLR_EmptyToken) + if (mdInst.genericType && NANOCLR_INDEX_IS_VALID(*mdInst.genericType)) + { + useGeneric = true; + } + else if (genericType && NANOCLR_INDEX_IS_VALID(*genericType)) { // parse TypeSpec to get its TypeDef CLR_RT_TypeSpec_Instance tsInst = {}; From cda3c5da95f402a0f65880e8f1625216d77e9ce8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Wed, 19 Nov 2025 14:28:31 +0000 Subject: [PATCH 43/61] Rename and moved GetGenericParam to CLR_RT_TypeSpec_Instance --- src/CLR/Core/Execution.cpp | 46 +++-- src/CLR/Core/Interpreter.cpp | 26 ++- src/CLR/Core/TypeSystem.cpp | 281 +++++++++++++++--------------- src/CLR/Diagnostics/Info.cpp | 55 +++--- src/CLR/Include/nanoCLR_Runtime.h | 7 +- 5 files changed, 215 insertions(+), 200 deletions(-) diff --git a/src/CLR/Core/Execution.cpp b/src/CLR/Core/Execution.cpp index bed13adbcc..e5d2f6c1ba 100644 --- a/src/CLR/Core/Execution.cpp +++ b/src/CLR/Core/Execution.cpp @@ -2063,8 +2063,20 @@ HRESULT CLR_RT_ExecutionEngine::InitializeReference( if (dt == DATATYPE_VAR) { - genericInstance->assembly - ->FindGenericParamAtTypeSpec(genericInstance->data, res.GenericParamPosition, realTypeDef, dt); + CLR_RT_TypeSpec_Instance genericTs; + if (!genericTs.InitializeFromIndex(*genericInstance)) + { + NANOCLR_SET_AND_LEAVE(CLR_E_FAIL); + } + + CLR_RT_SignatureParser::Element paramElement; + if (!genericTs.GetGenericParam(res.GenericParamPosition, paramElement)) + { + NANOCLR_SET_AND_LEAVE(CLR_E_FAIL); + } + + realTypeDef = paramElement.Class; + dt = paramElement.DataType; goto process_datatype; } @@ -2297,26 +2309,28 @@ HRESULT CLR_RT_ExecutionEngine::InitializeLocals( // type-level generic parameter in a locals signature (e.g. 'T' inside a generic type) CLR_INT8 genericParamPosition = *sig++; + // First, try to resolve using the method's generic type context if (methodDefInstance.genericType && NANOCLR_INDEX_IS_VALID(*methodDefInstance.genericType) && methodDefInstance.genericType->data != CLR_EmptyToken) { - CLR_RT_TypeSpec_Instance typeSpec{}; - typeSpec.InitializeFromIndex( - (const CLR_RT_TypeSpec_Index &)methodDefInstance.genericType->data); - - typeSpec.assembly->FindGenericParamAtTypeSpec( - methodDefInstance.genericType->data, - genericParamPosition, - cls, - dt); + CLR_RT_TypeSpec_Instance typeSpec; + if (!typeSpec.InitializeFromIndex(*methodDefInstance.genericType)) + { + NANOCLR_SET_AND_LEAVE(CLR_E_FAIL); + } + + CLR_RT_SignatureParser::Element paramElement; + if (!typeSpec.GetGenericParam(genericParamPosition, paramElement)) + { + NANOCLR_SET_AND_LEAVE(CLR_E_FAIL); + } + + cls = paramElement.Class; + dt = paramElement.DataType; } else { - assembly->FindGenericParamAtTypeSpec( - methodDefInstance.genericType->data, - genericParamPosition, - cls, - dt); + NANOCLR_SET_AND_LEAVE(CLR_E_FAIL); } goto done; diff --git a/src/CLR/Core/Interpreter.cpp b/src/CLR/Core/Interpreter.cpp index f942829755..b38c92c835 100644 --- a/src/CLR/Core/Interpreter.cpp +++ b/src/CLR/Core/Interpreter.cpp @@ -3877,13 +3877,6 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) // resolve the generic parameter in the context of the caller's generic type, if different // from the caller's assembly. - CLR_RT_Assembly *resolveAsm = assm; - if (stack->m_call.genericType && NANOCLR_INDEX_IS_VALID(*stack->m_call.genericType)) - { - resolveAsm = - g_CLR_RT_TypeSystem.m_assemblies[stack->m_call.genericType->Assembly() - 1]; - } - if (stack->m_call.genericType != nullptr) { CLR_UINT32 rawGenericParamRow = CLR_DataFromTk(arg); @@ -3912,19 +3905,20 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) { // closed TypeSpec const CLR_RT_TypeSpec_Index *callerTypeSpec = stack->m_call.genericType; - CLR_RT_TypeDef_Index resolvedTypeDef; - NanoCLRDataType dummyDataType; - - if (!resolveAsm->FindGenericParamAtTypeSpec( - callerTypeSpec->TypeSpec(), - genericParam.target->number, - resolvedTypeDef, - dummyDataType)) + + CLR_RT_TypeSpec_Instance typeSpec; + if (!typeSpec.InitializeFromIndex(*callerTypeSpec)) + { + NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE); + } + + CLR_RT_SignatureParser::Element paramElement; + if (!typeSpec.GetGenericParam(genericParam.target->number, paramElement)) { NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE); } - NANOCLR_CHECK_HRESULT(evalPos[0].SetReflection(resolvedTypeDef)); + NANOCLR_CHECK_HRESULT(evalPos[0].SetReflection(paramElement.Class)); } } } diff --git a/src/CLR/Core/TypeSystem.cpp b/src/CLR/Core/TypeSystem.cpp index 2661165974..82b6e34094 100644 --- a/src/CLR/Core/TypeSystem.cpp +++ b/src/CLR/Core/TypeSystem.cpp @@ -794,16 +794,8 @@ bool CLR_RT_TypeSpec_Instance::ResolveToken( Set(caller->genericType->Assembly(), closedTsRow); assembly = g_CLR_RT_TypeSystem.m_assemblies[caller->genericType->Assembly() - 1]; - target = assm->GetTypeSpec(closedTsRow); - - NanoCLRDataType realDataType; - - g_CLR_RT_TypeSystem.m_assemblies[caller->genericType->Assembly() - 1]->FindGenericParamAtTypeSpec( - caller->genericType->data, - (CLR_UINT32)pos, - cachedElementType, - realDataType); + cachedElementType = paramElement.Class; } else if (element.DataType == DATATYPE_MVAR) { @@ -898,6 +890,49 @@ bool CLR_RT_TypeSpec_Instance::IsClosedGenericType() return true; } +bool CLR_RT_TypeSpec_Instance::GetGenericParam(CLR_INT32 parameterPosition, CLR_RT_SignatureParser::Element &element) +{ + NATIVE_PROFILE_CLR_CORE(); + + CLR_RT_SignatureParser parser; + parser.Initialize_TypeSpec(assembly, target); + + if (FAILED(parser.Advance(element))) + { + return false; + } + + // sanity check for GENERICINST + if (element.DataType != DATATYPE_GENERICINST) + { + return false; + } + + // move to type + if (FAILED(parser.Advance(element))) + { + return false; + } + + // sanity check for invalid parameter position + if (parameterPosition >= element.GenParamCount) + { + // not enough parameters!! + return false; + } + + // walk to the requested parameter position + for (int32_t i = 0; i <= parameterPosition; i++) + { + if (FAILED(parser.Advance(element))) + { + return false; + } + } + + return true; +} + ////////////////////////////// bool CLR_RT_TypeDef_Instance::InitializeFromReflection(const CLR_RT_ReflectionDef_Index &reflex, CLR_UINT32 *levels) @@ -1245,24 +1280,28 @@ bool CLR_RT_TypeDef_Instance::ResolveToken( } else { - CLR_RT_TypeDef_Index realTypeDef; - NanoCLRDataType realDataType; + CLR_RT_TypeSpec_Instance callerTypeSpec; + if (!callerTypeSpec.InitializeFromIndex(*caller->genericType)) + { + return false; + } + + CLR_RT_SignatureParser::Element paramElement; // Try to map using the generic context (e.g. !T→Int32) - g_CLR_RT_TypeSystem.m_assemblies[caller->genericType->Assembly() - 1] - ->FindGenericParamAtTypeSpec( - caller->genericType->data, - (CLR_UINT32)pos, - realTypeDef, - realDataType); - - // Check if FindGenericParamAtTypeSpec succeeded - if (NANOCLR_INDEX_IS_VALID(realTypeDef)) + if (callerTypeSpec.GetGenericParam((CLR_UINT32)pos, paramElement)) { // Successfully resolved from generic context - data = realTypeDef.data; - assembly = g_CLR_RT_TypeSystem.m_assemblies[realTypeDef.Assembly() - 1]; - target = assembly->GetTypeDef(realTypeDef.Type()); + if (NANOCLR_INDEX_IS_VALID(paramElement.Class)) + { + data = paramElement.Class.data; + assembly = g_CLR_RT_TypeSystem.m_assemblies[paramElement.Class.Assembly() - 1]; + target = assembly->GetTypeDef(paramElement.Class.Type()); + } + else + { + return false; + } } else if (NANOCLR_INDEX_IS_VALID(caller->arrayElementType) && pos == 0) { @@ -1384,17 +1423,22 @@ bool CLR_RT_TypeDef_Instance::ResolveNullableType( return false; } - CLR_RT_TypeDef_Index realTypeDef; - NanoCLRDataType realDataType; + CLR_RT_TypeSpec_Instance callerTypeSpec; + if (!callerTypeSpec.InitializeFromIndex(*caller->genericType)) + { + return false; + } - // Only call this once to map (e.g. !T→Int32) - caller->assembly - ->FindGenericParamAtTypeSpec(caller->genericType->data, (CLR_UINT32)pos, realTypeDef, realDataType); + CLR_RT_SignatureParser::Element paramElement; + if (!callerTypeSpec.GetGenericParam((CLR_UINT32)pos, paramElement)) + { + return false; + } // populate this instance - data = realTypeDef.data; - assembly = g_CLR_RT_TypeSystem.m_assemblies[realTypeDef.Assembly() - 1]; - target = assembly->GetTypeDef(realTypeDef.Type()); + data = paramElement.Class.data; + assembly = g_CLR_RT_TypeSystem.m_assemblies[paramElement.Class.Assembly() - 1]; + target = assembly->GetTypeDef(paramElement.Class.Type()); return true; } @@ -1679,15 +1723,14 @@ bool CLR_RT_MethodDef_Instance::InitializeFromIndex( if (elem.DataType == DATATYPE_VAR) { - CLR_RT_TypeDef_Index realOwner; - NanoCLRDataType dummyDT; + CLR_RT_SignatureParser::Element paramElement; - if (!tsAsm->FindGenericParamAtTypeSpec(typeSpec.data, elem.GenericParamPosition, realOwner, dummyDT)) + if (!tsInst.GetGenericParam(elem.GenericParamPosition, paramElement)) { return false; } - ownerTypeIdx = realOwner; + ownerTypeIdx = paramElement.Class; } else { @@ -2040,15 +2083,19 @@ bool CLR_RT_MethodDef_Instance::GetDeclaringType(CLR_RT_TypeDef_Instance &declTy // generic type, advance to get the type int pos = elem.GenericParamPosition; // Use the *caller's* bound genericType (Stack, etc.) - CLR_RT_TypeDef_Index td; - NanoCLRDataType dt; - if (tsAsm == nullptr || - tsAsm->FindGenericParamAtTypeSpec(genericType->data, (CLR_UINT32)pos, td, dt) == false) + CLR_RT_TypeSpec_Instance callerTypeSpec; + if (!callerTypeSpec.InitializeFromIndex(*genericType)) + { + return false; + } + + CLR_RT_SignatureParser::Element paramElement; + if (!callerTypeSpec.GetGenericParam((CLR_UINT32)pos, paramElement)) { return false; } - return declType.InitializeFromIndex(td); + return declType.InitializeFromIndex(paramElement.Class); } } @@ -2417,19 +2464,17 @@ HRESULT CLR_RT_TypeDescriptor::InitializeFromSignatureParser( if (res.DataType == DATATYPE_VAR && contextTypeSpec && NANOCLR_INDEX_IS_VALID(*contextTypeSpec)) { // Resolve VAR from context TypeSpec using existing helper - CLR_RT_TypeDef_Index resolvedTypeDef; - NanoCLRDataType resolvedDataType; - - CLR_RT_Assembly *contextAssm = g_CLR_RT_TypeSystem.m_assemblies[contextTypeSpec->Assembly() - 1]; + CLR_RT_TypeSpec_Instance contextTs; + if (!contextTs.InitializeFromIndex(*contextTypeSpec)) + { + NANOCLR_SET_AND_LEAVE(CLR_E_FAIL); + } - if (contextAssm->FindGenericParamAtTypeSpec( - contextTypeSpec->TypeSpec(), - res.GenericParamPosition, - resolvedTypeDef, - resolvedDataType)) + CLR_RT_SignatureParser::Element paramElement; + if (contextTs.GetGenericParam(res.GenericParamPosition, paramElement)) { // Use the resolved type from context - NANOCLR_CHECK_HRESULT(InitializeFromType(resolvedTypeDef)); + NANOCLR_CHECK_HRESULT(InitializeFromType(paramElement.Class)); } else { @@ -2490,8 +2535,6 @@ HRESULT CLR_RT_TypeDescriptor::InitializeFromSignatureToken( if (elem.DataType == DATATYPE_VAR) { // !T: ask the CLR to map that slot into the *actual* argument - CLR_RT_TypeDef_Index td; - NanoCLRDataType dt; // For SZArrayHelper scenarios, arrayElementType is authoritative for position 0 if (caller && NANOCLR_INDEX_IS_VALID(caller->arrayElementType) && elem.GenericParamPosition == 0) @@ -2501,12 +2544,23 @@ HRESULT CLR_RT_TypeDescriptor::InitializeFromSignatureToken( // Otherwise try to resolve from generic context else if (caller && caller->genericType && NANOCLR_INDEX_IS_VALID(*caller->genericType)) { - g_CLR_RT_TypeSystem.m_assemblies[caller->genericType->Assembly() - 1] - ->FindGenericParamAtTypeSpec(caller->genericType->data, elem.GenericParamPosition, td, dt); + CLR_RT_TypeSpec_Instance callerTypeSpec; + if (!callerTypeSpec.InitializeFromIndex(*caller->genericType)) + { + return false; + } - if (NANOCLR_INDEX_IS_VALID(td)) + CLR_RT_SignatureParser::Element paramElement; + if (callerTypeSpec.GetGenericParam(elem.GenericParamPosition, paramElement)) { - this->InitializeFromTypeDef(td); + if (NANOCLR_INDEX_IS_VALID(paramElement.Class)) + { + this->InitializeFromTypeDef(paramElement.Class); + } + else + { + NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE); + } } else { @@ -5844,54 +5898,6 @@ bool CLR_RT_Assembly::FindGenericParam(CLR_INDEX typeSpecIndex, CLR_RT_GenericPa return false; } -bool CLR_RT_Assembly::FindGenericParamAtTypeSpec( - CLR_UINT32 typeSpecIndex, - CLR_INT32 genericParameterPosition, - CLR_RT_TypeDef_Index &typeDef, - NanoCLRDataType &dataType) -{ - NATIVE_PROFILE_CLR_CORE(); - - CLR_RT_SignatureParser parser; - parser.Initialize_TypeSpec(this, GetTypeSpec(typeSpecIndex)); - - CLR_RT_SignatureParser::Element element; - - // get into the GENERICINST - if (FAILED(parser.Advance(element))) - { - return false; - } - - // move to type - if (FAILED(parser.Advance(element))) - { - return false; - } - - // sanity check for invalid parameter position - if (genericParameterPosition > element.GenParamCount) - { - // not enough parameters!! - return false; - } - - // walk to the requested parameter position - for (int32_t i = 0; i <= genericParameterPosition; i++) - { - if (FAILED(parser.Advance(element))) - { - return false; - } - } - - // element.Class was filled from the VAR position - typeDef = element.Class; - dataType = element.DataType; - - return true; -} - bool CLR_RT_Assembly::FindGenericParamAtMethodDef( CLR_RT_MethodDef_Instance md, CLR_INT32 genericParameterPosition, @@ -7493,18 +7499,22 @@ HRESULT CLR_RT_TypeSystem::BuildTypeName( { // generic type parameter - CLR_RT_TypeDef_Index paramTd; - NanoCLRDataType paramDt; + CLR_RT_TypeSpec_Instance typeContextTs; + if (!typeContextTs.InitializeFromIndex(*contextTypeSpec)) + { + NANOCLR_SET_AND_LEAVE(CLR_E_FAIL); + } - CLR_RT_Assembly *typeContextAssm = g_CLR_RT_TypeSystem.m_assemblies[contextTypeSpec->Assembly() - 1]; + CLR_RT_SignatureParser::Element paramElement; // this will bind !T→System.Int32, etc. - typeContextAssm->FindGenericParamAtTypeSpec( - contextTypeSpec->TypeSpec(), - element.GenericParamPosition, // the !N slot - paramTd, - paramDt); - - if (paramDt == DATATYPE_VAR) + if (!typeContextTs.GetGenericParam(element.GenericParamPosition, paramElement)) + { + // couldn't be resolved, print encoded form (!N) + char encodedParam[6]; + snprintf(encodedParam, ARRAYSIZE(encodedParam), "!%d", element.GenericParamPosition); + NANOCLR_CHECK_HRESULT(QueueStringToBuffer(szBuffer, iBuffer, encodedParam)); + } + else if (paramElement.DataType == DATATYPE_VAR) { // couldn't be resolved, print encoded form (!N) char encodedParam[6]; @@ -7514,7 +7524,7 @@ HRESULT CLR_RT_TypeSystem::BuildTypeName( else { // now print the *actual* type name - BuildTypeName(paramTd, szBuffer, iBuffer); + BuildTypeName(paramElement.Class, szBuffer, iBuffer); } } else @@ -7827,16 +7837,16 @@ HRESULT CLR_RT_TypeSystem::BuildMethodName( // Generic parameter - try to resolve it if (context != nullptr) { - CLR_RT_TypeDef_Index resolvedType{}; - NanoCLRDataType resolvedDT; - - if (mdInst.assembly->FindGenericParamAtTypeSpec( - context->TypeSpec(), - elem.GenericParamPosition, - resolvedType, - resolvedDT)) + CLR_RT_TypeSpec_Instance contextTs; + if (!contextTs.InitializeFromIndex(*context)) { - NANOCLR_CHECK_HRESULT(BuildTypeName(resolvedType, szBuffer, iBuffer)); + NANOCLR_SET_AND_LEAVE(CLR_E_FAIL); + } + + CLR_RT_SignatureParser::Element paramElement; + if (contextTs.GetGenericParam(elem.GenericParamPosition, paramElement)) + { + NANOCLR_CHECK_HRESULT(BuildTypeName(paramElement.Class, szBuffer, iBuffer)); } else { @@ -8447,23 +8457,22 @@ CLR_UINT32 CLR_RT_TypeSystem::ComputeHashForClosedGenericType( if (elem.DataType == DATATYPE_VAR && contextTypeSpec && NANOCLR_INDEX_IS_VALID(*contextTypeSpec)) { // Resolve VAR (type parameter) from context TypeSpec - CLR_RT_TypeDef_Index resolvedTypeDef; - NanoCLRDataType resolvedDataType; - - CLR_RT_Assembly *contextAssm = g_CLR_RT_TypeSystem.m_assemblies[contextTypeSpec->Assembly() - 1]; + CLR_RT_TypeSpec_Instance contextTs; + if (!contextTs.InitializeFromIndex(*contextTypeSpec)) + { + hash = 0; + goto ComputeHash_End; + } - if (contextAssm->FindGenericParamAtTypeSpec( - contextTypeSpec->TypeSpec(), - elem.GenericParamPosition, - resolvedTypeDef, - resolvedDataType)) + CLR_RT_SignatureParser::Element paramElement; + if (contextTs.GetGenericParam(elem.GenericParamPosition, paramElement)) { // Use the resolved type from context - hash = SUPPORT_ComputeCRC(&resolvedDataType, sizeof(resolvedDataType), hash); + hash = SUPPORT_ComputeCRC(¶mElement.DataType, sizeof(paramElement.DataType), hash); - if (resolvedDataType == DATATYPE_CLASS || resolvedDataType == DATATYPE_VALUETYPE) + if (paramElement.DataType == DATATYPE_CLASS || paramElement.DataType == DATATYPE_VALUETYPE) { - hash = SUPPORT_ComputeCRC(&resolvedTypeDef.data, sizeof(resolvedTypeDef.data), hash); + hash = SUPPORT_ComputeCRC(¶mElement.Class.data, sizeof(paramElement.Class.data), hash); } } else diff --git a/src/CLR/Diagnostics/Info.cpp b/src/CLR/Diagnostics/Info.cpp index 93958a0090..81e624d227 100644 --- a/src/CLR/Diagnostics/Info.cpp +++ b/src/CLR/Diagnostics/Info.cpp @@ -600,22 +600,26 @@ void CLR_RT_Assembly::DumpToken(CLR_UINT32 token, const CLR_RT_MethodDef_Instanc // if the caller's genericType is non‐null, ask the CLR to map !n→actual argument: if (methodDefInstance.genericType != nullptr && NANOCLR_INDEX_IS_VALID(*methodDefInstance.genericType)) { - CLR_RT_TypeDef_Index tdArg{}; - NanoCLRDataType dtArg; - bool ok = g_CLR_RT_TypeSystem.m_assemblies[methodDefInstance.genericType->Assembly() - 1] - ->FindGenericParamAtTypeSpec( - methodDefInstance.genericType->TypeSpec(), - gpPosition, - tdArg, - dtArg); - if (ok) + CLR_RT_TypeSpec_Instance typeSpec; + if (!typeSpec.InitializeFromIndex(*methodDefInstance.genericType)) + { + CLR_Debug::Printf("!%d", gpPosition); + break; + } + + CLR_RT_SignatureParser::Element paramElement; + if (typeSpec.GetGenericParam(gpPosition, paramElement)) { char bufArg[256]{}; char *pArg = bufArg; size_t cbArg = sizeof(bufArg); - g_CLR_RT_TypeSystem - .BuildTypeName(tdArg, pArg, cbArg, CLR_RT_TypeSystem::TYPENAME_FLAGS_FULL, elem.Levels); + g_CLR_RT_TypeSystem.BuildTypeName( + paramElement.Class, + pArg, + cbArg, + CLR_RT_TypeSystem::TYPENAME_FLAGS_FULL, + elem.Levels); CLR_Debug::Printf("%s", bufArg); @@ -684,23 +688,20 @@ void CLR_RT_Assembly::DumpToken(CLR_UINT32 token, const CLR_RT_MethodDef_Instanc if (methodDefInstance.genericType != nullptr && NANOCLR_INDEX_IS_VALID(*methodDefInstance.genericType)) { - CLR_RT_TypeDef_Index tdArg{}; - NanoCLRDataType dtArg; - - bool genericParamFound = tsInst.assembly->FindGenericParamAtTypeSpec( - methodDefInstance.genericType->TypeSpec(), - gpIndex, - tdArg, - dtArg); - if (genericParamFound) + CLR_RT_TypeSpec_Instance typeSpec; + if (typeSpec.InitializeFromIndex(*methodDefInstance.genericType)) { - // print "I4[]" or the bound argument plus [] - char bufArg[256]; - char *pArg = bufArg; - size_t cbArg = sizeof(bufArg); - g_CLR_RT_TypeSystem.BuildTypeName(tdArg, pArg, cbArg); - CLR_Debug::Printf("%s[]", bufArg); - break; + CLR_RT_SignatureParser::Element paramElement; + if (typeSpec.GetGenericParam(gpIndex, paramElement)) + { + // print "I4[]" or the bound argument plus [] + char bufArg[256]; + char *pArg = bufArg; + size_t cbArg = sizeof(bufArg); + g_CLR_RT_TypeSystem.BuildTypeName(paramElement.Class, pArg, cbArg); + CLR_Debug::Printf("%s[]", bufArg); + break; + } } } diff --git a/src/CLR/Include/nanoCLR_Runtime.h b/src/CLR/Include/nanoCLR_Runtime.h index 42bfd0be99..dc5f5ad5e9 100644 --- a/src/CLR/Include/nanoCLR_Runtime.h +++ b/src/CLR/Include/nanoCLR_Runtime.h @@ -1455,11 +1455,6 @@ struct CLR_RT_Assembly : public CLR_RT_HeapBlock_Node // EVENT HEAP - NO RELOCAT bool FindTypeDef(CLR_UINT32 hash, CLR_RT_TypeDef_Index &index); bool FindTypeSpec(const CLR_PMETADATA sig, CLR_RT_TypeSpec_Index &index); - bool FindGenericParamAtTypeSpec( - CLR_UINT32 typeSpecIndex, - CLR_INT32 genericParameterPosition, - CLR_RT_TypeDef_Index &typeDef, - NanoCLRDataType &dataType); bool FindGenericParamAtMethodDef( CLR_RT_MethodDef_Instance md, CLR_INT32 genericParameterPosition, @@ -2196,6 +2191,8 @@ struct CLR_RT_TypeSpec_Instance : public CLR_RT_TypeSpec_Index bool ResolveToken(CLR_UINT32 tk, CLR_RT_Assembly *assm, const CLR_RT_MethodDef_Instance *caller = nullptr); bool IsClosedGenericType(); + + bool GetGenericParam(CLR_INT32 parameterPosition, CLR_RT_SignatureParser::Element &element); }; //--// From 06294b7d962725d32ba7e035cd6cf9793b4a2d10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Wed, 19 Nov 2025 14:29:25 +0000 Subject: [PATCH 44/61] Fix processing of MVAR when initializing locals --- src/CLR/Core/Execution.cpp | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/src/CLR/Core/Execution.cpp b/src/CLR/Core/Execution.cpp index e5d2f6c1ba..6d067b4971 100644 --- a/src/CLR/Core/Execution.cpp +++ b/src/CLR/Core/Execution.cpp @@ -2338,17 +2338,36 @@ HRESULT CLR_RT_ExecutionEngine::InitializeLocals( case DATATYPE_MVAR: { + // Method-level generic parameter (e.g., '!!T' in a generic method like Array.Empty()) CLR_UINT8 genericParamPosition = *sig++; - CLR_RT_GenericParam_Index gpIndex; + // For generic methods, use the MethodSpec's signature to get the concrete type + if (NANOCLR_INDEX_IS_VALID(methodDefInstance.methodSpec)) + { + CLR_RT_MethodSpec_Instance methodSpec; + if (!methodSpec.InitializeFromIndex(methodDefInstance.methodSpec)) + { + NANOCLR_SET_AND_LEAVE(CLR_E_FAIL); + } - assembly->FindGenericParamAtMethodDef(methodDefInstance, genericParamPosition, gpIndex); + // Use GetGenericArgument to get the concrete type from MethodSpec's signature + if (!methodSpec.GetGenericArgument(genericParamPosition, cls, dt)) + { + NANOCLR_SET_AND_LEAVE(CLR_E_FAIL); + } + } + else + { + // Fallback: try to resolve using GenericParam table (for open generic methods) + CLR_RT_GenericParam_Index gpIndex; + assembly->FindGenericParamAtMethodDef(methodDefInstance, genericParamPosition, gpIndex); - CLR_RT_GenericParam_CrossReference gp = - assembly->crossReferenceGenericParam[gpIndex.GenericParam()]; + CLR_RT_GenericParam_CrossReference gp = + assembly->crossReferenceGenericParam[gpIndex.GenericParam()]; - cls = gp.classTypeDef; - dt = gp.dataType; + cls = gp.classTypeDef; + dt = gp.dataType; + } goto done; } From a24b99f1d2428fce2b1f41c7203875d6fa312469 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Wed, 19 Nov 2025 15:04:00 +0000 Subject: [PATCH 45/61] Fixes in CLR_RT_TypeSpec_Instance API --- src/CLR/Core/TypeSystem.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/CLR/Core/TypeSystem.cpp b/src/CLR/Core/TypeSystem.cpp index 82b6e34094..917d70a66a 100644 --- a/src/CLR/Core/TypeSystem.cpp +++ b/src/CLR/Core/TypeSystem.cpp @@ -722,6 +722,10 @@ bool CLR_RT_TypeSpec_Instance::InitializeFromIndex(const CLR_RT_TypeSpec_Index & genericTypeDef = element.Class; } + else + { + genericTypeDef.Clear(); + } return true; } @@ -789,6 +793,21 @@ bool CLR_RT_TypeSpec_Instance::ResolveToken( return false; } + CLR_RT_TypeSpec_Instance callerTypeSpec; + if (!callerTypeSpec.InitializeFromIndex(*caller->genericType)) + { + ClearInstance(); + return false; + } + + CLR_RT_SignatureParser::Element paramElement; + if (!callerTypeSpec.GetGenericParam((CLR_UINT32)pos, paramElement)) + { + ClearInstance(); + return false; + } + + // Use the resolved parameter's type for this TypeSpec auto &tsi = *caller->genericType; CLR_UINT32 closedTsRow = tsi.TypeSpec(); From c15d89f487169d6369493df5f2e4d571c75773d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Wed, 19 Nov 2025 17:40:37 +0000 Subject: [PATCH 46/61] Several improvements in BuildTypeName and BuildMethodName --- src/CLR/Core/TypeSystem.cpp | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/src/CLR/Core/TypeSystem.cpp b/src/CLR/Core/TypeSystem.cpp index 917d70a66a..ab95ba82e1 100644 --- a/src/CLR/Core/TypeSystem.cpp +++ b/src/CLR/Core/TypeSystem.cpp @@ -7497,7 +7497,7 @@ HRESULT CLR_RT_TypeSystem::BuildTypeName( CLR_RT_TypeDef_Index typeDef; typeDef.data = element.Class.data; - BuildTypeName(typeDef, szBuffer, iBuffer); + NANOCLR_CHECK_HRESULT(BuildTypeName(typeDef, szBuffer, iBuffer)); if (element.GenParamCount > 0) { @@ -7569,7 +7569,7 @@ HRESULT CLR_RT_TypeSystem::BuildTypeName( NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE); } - BuildTypeName(typeDef, szBuffer, iBuffer); + NANOCLR_CHECK_HRESULT(BuildTypeName(typeDef, szBuffer, iBuffer)); } } else @@ -7586,7 +7586,7 @@ HRESULT CLR_RT_TypeSystem::BuildTypeName( CLR_RT_TypeDef_Index td; td.data = element.Class.data; - BuildTypeName(td, szBuffer, iBuffer); + NANOCLR_CHECK_HRESULT(BuildTypeName(td, szBuffer, iBuffer)); } if (i + 1 < element.GenParamCount) @@ -7798,14 +7798,28 @@ HRESULT CLR_RT_TypeSystem::BuildMethodName( if (genericType != nullptr && NANOCLR_INDEX_IS_VALID(*genericType) && genericType->data != CLR_EmptyToken) { // Use the provided generic type context - NANOCLR_CHECK_HRESULT(BuildTypeName(*genericType, szBuffer, iBuffer, 0)); + if (!SUCCEEDED(BuildTypeName(*genericType, szBuffer, iBuffer, 0, nullptr, &mdInst))) + { + // Fall back to the declaring type + if (instOwner.InitializeFromMethod(mdInst) == false) + { + NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE); + } + NANOCLR_CHECK_HRESULT(BuildTypeName(instOwner, szBuffer, iBuffer)); + } } - else if ( - mdInst.genericType != nullptr && NANOCLR_INDEX_IS_VALID(*mdInst.genericType) && - mdInst.genericType->data != CLR_EmptyToken) + else if (mdInst.genericType != nullptr && NANOCLR_INDEX_IS_VALID(*mdInst.genericType)) { // Use the method instance's generic type - NANOCLR_CHECK_HRESULT(BuildTypeName(*mdInst.genericType, szBuffer, iBuffer, 0)); + if (!SUCCEEDED(BuildTypeName(*mdInst.genericType, szBuffer, iBuffer, 0))) + { + // Fall back to the declaring type + if (instOwner.InitializeFromMethod(mdInst) == false) + { + NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE); + } + NANOCLR_CHECK_HRESULT(BuildTypeName(instOwner, szBuffer, iBuffer)); + } } else { @@ -8244,7 +8258,8 @@ bool CLR_RT_TypeSystem::FindVirtualMethodDef( clsInst.SwitchToParent(); } - // SZ arrays expose IList generic interfaces through System.Array+SZArrayHelper, so fall back to that helper type + // SZ arrays expose IList generic interfaces through System.Array+SZArrayHelper, so fall back to that helper + // type if (isArrayClass) { const CLR_RT_TypeDef_Index &arrayHelperIdx = g_CLR_RT_WellKnownTypes.SZArrayHelper; From 3bfc5d66025f9c36781915d576cb696f9802966c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Thu, 20 Nov 2025 07:48:28 +0000 Subject: [PATCH 47/61] ResolveToken for TypeDef instance now handles MVAR inside a VAR resolution - Included some refactoring to remove wrong code. --- src/CLR/Core/TypeSystem.cpp | 85 +++++++++++++++++++++---------------- 1 file changed, 49 insertions(+), 36 deletions(-) diff --git a/src/CLR/Core/TypeSystem.cpp b/src/CLR/Core/TypeSystem.cpp index ab95ba82e1..32127a6e7e 100644 --- a/src/CLR/Core/TypeSystem.cpp +++ b/src/CLR/Core/TypeSystem.cpp @@ -1280,63 +1280,76 @@ bool CLR_RT_TypeDef_Instance::ResolveToken( { int pos = elem.GenericParamPosition; - // Use the *caller's* bound genericType (Stack, etc.) - if (caller == nullptr || caller->genericType == nullptr) + CLR_RT_TypeSpec_Instance callerTypeSpec; + if (!callerTypeSpec.InitializeFromIndex(*caller->genericType)) { - // No generic context available, try arrayElementType fallback - if (caller && NANOCLR_INDEX_IS_VALID(caller->arrayElementType) && pos == 0) - { - // For SZArrayHelper, position 0 is the array element type - data = caller->arrayElementType.data; - assembly = - g_CLR_RT_TypeSystem.m_assemblies[caller->arrayElementType.Assembly() - 1]; - target = assembly->GetTypeDef(caller->arrayElementType.Type()); - } - else - { - return false; - } + return false; } - else + + CLR_RT_SignatureParser::Element paramElement; + + // Try to map using the generic context (e.g. !T→Int32) + if (callerTypeSpec.GetGenericParam((CLR_UINT32)pos, paramElement)) { - CLR_RT_TypeSpec_Instance callerTypeSpec; - if (!callerTypeSpec.InitializeFromIndex(*caller->genericType)) + // Successfully resolved from generic context + if (NANOCLR_INDEX_IS_VALID(paramElement.Class)) { - return false; + data = paramElement.Class.data; + assembly = g_CLR_RT_TypeSystem.m_assemblies[paramElement.Class.Assembly() - 1]; + target = assembly->GetTypeDef(paramElement.Class.Type()); } - - CLR_RT_SignatureParser::Element paramElement; - - // Try to map using the generic context (e.g. !T→Int32) - if (callerTypeSpec.GetGenericParam((CLR_UINT32)pos, paramElement)) + else if (paramElement.DataType == DATATYPE_MVAR) { - // Successfully resolved from generic context - if (NANOCLR_INDEX_IS_VALID(paramElement.Class)) + // resolve from methodspec context + if (NANOCLR_INDEX_IS_VALID(caller->methodSpec)) { - data = paramElement.Class.data; - assembly = g_CLR_RT_TypeSystem.m_assemblies[paramElement.Class.Assembly() - 1]; - target = assembly->GetTypeDef(paramElement.Class.Type()); + CLR_RT_MethodSpec_Instance methodSpecInstance; + if (methodSpecInstance.InitializeFromIndex(caller->methodSpec)) + { + NanoCLRDataType dataType; + CLR_RT_TypeDef_Index typeDef; + methodSpecInstance.GetGenericArgument( + paramElement.GenericParamPosition, + typeDef, + dataType); + + data = typeDef.data; + assembly = g_CLR_RT_TypeSystem.m_assemblies[typeDef.Assembly() - 1]; + target = assembly->GetTypeDef(typeDef.Type()); + } + else + { + return false; + } } else { return false; } } - else if (NANOCLR_INDEX_IS_VALID(caller->arrayElementType) && pos == 0) + else if (paramElement.DataType == DATATYPE_VAR) { - // Fallback to arrayElementType for SZArrayHelper scenarios - data = caller->arrayElementType.data; - assembly = - g_CLR_RT_TypeSystem.m_assemblies[caller->arrayElementType.Assembly() - 1]; - target = assembly->GetTypeDef(caller->arrayElementType.Type()); + // nested VAR not implemented + _ASSERT(false); + return false; } else { return false; } } + else if (NANOCLR_INDEX_IS_VALID(caller->arrayElementType) && pos == 0) + { + // Fallback to arrayElementType for SZArrayHelper scenarios + data = caller->arrayElementType.data; + assembly = g_CLR_RT_TypeSystem.m_assemblies[caller->arrayElementType.Assembly() - 1]; + target = assembly->GetTypeDef(caller->arrayElementType.Type()); + } + else + { + return false; + } } - else if (elem.DataType == DATATYPE_MVAR) { // Use the caller bound genericType (Stack, etc.) From f992c7a294077e6e245428a75d715fe6560efd04 Mon Sep 17 00:00:00 2001 From: nfbot Date: Thu, 20 Nov 2025 09:14:35 +0000 Subject: [PATCH 48/61] Code style fixes Automated fixes for code style. --- src/CLR/Core/CLR_RT_StackFrame.cpp | 6 +++--- src/CLR/Core/Interpreter.cpp | 6 ++++-- src/CLR/Core/Thread.cpp | 4 ++-- src/CLR/Core/TypeSystem.cpp | 18 ++++++++++-------- src/CLR/Include/nanoCLR_Runtime__HeapBlock.h | 2 +- 5 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/CLR/Core/CLR_RT_StackFrame.cpp b/src/CLR/Core/CLR_RT_StackFrame.cpp index bd26cbbdb7..3a6678dd2e 100644 --- a/src/CLR/Core/CLR_RT_StackFrame.cpp +++ b/src/CLR/Core/CLR_RT_StackFrame.cpp @@ -127,9 +127,9 @@ HRESULT CLR_RT_StackFrame::Push(CLR_RT_Thread *th, const CLR_RT_MethodDef_Instan #if defined(NANOCLR_PROFILE_NEW_CALLS) stack->m_callchain.Enter(stack); // CLR_PROF_CounterCallChain m_callchain; #endif - // - // CLR_RT_HeapBlock m_extension[1]; - // + // + // CLR_RT_HeapBlock m_extension[1]; + // #if defined(ENABLE_NATIVE_PROFILER) stack->m_fNativeProfiled = stack->m_owningThread->m_fNativeProfiled; #endif diff --git a/src/CLR/Core/Interpreter.cpp b/src/CLR/Core/Interpreter.cpp index b38c92c835..e0a08194bc 100644 --- a/src/CLR/Core/Interpreter.cpp +++ b/src/CLR/Core/Interpreter.cpp @@ -2387,8 +2387,10 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) // Set up the new stack frame's generic context // Priority order: - // 1. effectiveCallerGeneric (extracted from TypeSpec search for interface calls) - HIGHEST PRIORITY - // This is the concrete closed generic type (e.g., List) not the interface (e.g., IEnumerable) + // 1. effectiveCallerGeneric (extracted from TypeSpec search for interface calls) - HIGHEST + // PRIORITY + // This is the concrete closed generic type (e.g., List) not the interface (e.g., + // IEnumerable) // 2. calleeInst.genericType (from MethodRef TypeSpec or virtual dispatch) // 3. stack->m_call.genericType (inherited from caller) CLR_RT_StackFrame *newStack = th->CurrentFrame(); diff --git a/src/CLR/Core/Thread.cpp b/src/CLR/Core/Thread.cpp index 52b73bb8a1..48fbebaa0c 100644 --- a/src/CLR/Core/Thread.cpp +++ b/src/CLR/Core/Thread.cpp @@ -176,7 +176,7 @@ HRESULT CLR_RT_Thread::PushThreadProcDelegate(CLR_RT_HeapBlock_Delegate *pDelega delegateTypeSpec = pDelegate->m_genericTypeSpec; inst.genericType = &delegateTypeSpec; } - + if (pDelegate->m_genericMethodSpec.data != 0) { delegateMethodSpec = pDelegate->m_genericMethodSpec; @@ -209,7 +209,7 @@ HRESULT CLR_RT_Thread::PushThreadProcDelegate(CLR_RT_HeapBlock_Delegate *pDelega stackTop->m_genericTypeSpecStorage = delegateTypeSpec; stackTop->m_call.genericType = &stackTop->m_genericTypeSpecStorage; } - + // If we have a generic method context, copy it to the stack frame // This enables MVAR resolution for .cctor triggered from generic methods if (delegateMethodSpec.data != 0) diff --git a/src/CLR/Core/TypeSystem.cpp b/src/CLR/Core/TypeSystem.cpp index 32127a6e7e..c9c0983dde 100644 --- a/src/CLR/Core/TypeSystem.cpp +++ b/src/CLR/Core/TypeSystem.cpp @@ -4498,10 +4498,11 @@ HRESULT CLR_RT_AppDomain::GetManagedObject(CLR_RT_HeapBlock &res) pRes = res.Dereference(); - NANOCLR_CHECK_HRESULT(CLR_RT_ObjectToEvent_Source::CreateInstance( - this, - *pRes, - pRes[Library_corlib_native_System_AppDomain::FIELD___appDomain])); + NANOCLR_CHECK_HRESULT( + CLR_RT_ObjectToEvent_Source::CreateInstance( + this, + *pRes, + pRes[Library_corlib_native_System_AppDomain::FIELD___appDomain])); pRes[Library_corlib_native_System_AppDomain::FIELD___friendlyName].SetObjectReference(m_strName); } @@ -8845,10 +8846,11 @@ HRESULT CLR_RT_AttributeParser::Next(Value *&res) } // instantiate array to hold parameters values - NANOCLR_CHECK_HRESULT(CLR_RT_HeapBlock_Array::CreateInstance( - m_lastValue.m_value, - paramCount, - g_CLR_RT_WellKnownTypes.Object)); + NANOCLR_CHECK_HRESULT( + CLR_RT_HeapBlock_Array::CreateInstance( + m_lastValue.m_value, + paramCount, + g_CLR_RT_WellKnownTypes.Object)); // get a pointer to the first element auto *currentParam = (CLR_RT_HeapBlock *)m_lastValue.m_value.DereferenceArray()->GetFirstElement(); diff --git a/src/CLR/Include/nanoCLR_Runtime__HeapBlock.h b/src/CLR/Include/nanoCLR_Runtime__HeapBlock.h index d18bacfd36..d9a22795b4 100644 --- a/src/CLR/Include/nanoCLR_Runtime__HeapBlock.h +++ b/src/CLR/Include/nanoCLR_Runtime__HeapBlock.h @@ -1913,7 +1913,7 @@ struct CLR_RT_HeapBlock_Delegate : public CLR_RT_HeapBlock_Node // OBJECT HEAP - // Optional TypeSpec index for resolving type generic parameter (VARs like !0) // (data == 0 means not set) CLR_RT_TypeSpec_Index m_genericTypeSpec; - + // Optional MethodSpec index for resolving method generic parameters (MVAR like !!0) // (data == 0 means not set) CLR_RT_MethodSpec_Index m_genericMethodSpec; From dfd4e3b00fd31c1b4d75ff2748367f9ee5eaf5e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Thu, 20 Nov 2025 09:39:48 +0000 Subject: [PATCH 49/61] Fix macro name --- src/CLR/Core/TypeSystem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CLR/Core/TypeSystem.cpp b/src/CLR/Core/TypeSystem.cpp index c9c0983dde..af1656cf03 100644 --- a/src/CLR/Core/TypeSystem.cpp +++ b/src/CLR/Core/TypeSystem.cpp @@ -1330,7 +1330,7 @@ bool CLR_RT_TypeDef_Instance::ResolveToken( else if (paramElement.DataType == DATATYPE_VAR) { // nested VAR not implemented - _ASSERT(false); + ASSERT(false); return false; } else From b91acdd94e17a55f65a1de270712c5ce96702ad7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Thu, 20 Nov 2025 10:01:45 +0000 Subject: [PATCH 50/61] Increase CODE region size in TI_CC1352R1_LAUNCHXL --- .../TI_CC1352R1_LAUNCHXL/common/Device_BlockStorage.c | 4 ++-- .../TI_CC1352R1_LAUNCHXL/nanoCLR/CC13x2_26x2_CLR.ld | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/targets/TI_SimpleLink/TI_CC1352R1_LAUNCHXL/common/Device_BlockStorage.c b/targets/TI_SimpleLink/TI_CC1352R1_LAUNCHXL/common/Device_BlockStorage.c index 81655f6435..b73b30d075 100644 --- a/targets/TI_SimpleLink/TI_CC1352R1_LAUNCHXL/common/Device_BlockStorage.c +++ b/targets/TI_SimpleLink/TI_CC1352R1_LAUNCHXL/common/Device_BlockStorage.c @@ -10,8 +10,8 @@ const BlockRange BlockRange1[] = { // the last block is reserved for Customer Configuration Area and Bootloader Backdoor configuration // so we don't take it into account for the map - {BlockRange_BLOCKTYPE_CODE, 0, 25}, // 0x00000000 nanoCLR - {BlockRange_BLOCKTYPE_DEPLOYMENT, 26, 42}, // 0x00034000 deployment + {BlockRange_BLOCKTYPE_CODE, 0, 26}, // 0x00000000 nanoCLR + {BlockRange_BLOCKTYPE_DEPLOYMENT, 27, 42}, // 0x00036000 deployment }; const BlockRegionInfo BlockRegions[] = {{ diff --git a/targets/TI_SimpleLink/TI_CC1352R1_LAUNCHXL/nanoCLR/CC13x2_26x2_CLR.ld b/targets/TI_SimpleLink/TI_CC1352R1_LAUNCHXL/nanoCLR/CC13x2_26x2_CLR.ld index db6c8d1a3c..daa6672a5b 100644 --- a/targets/TI_SimpleLink/TI_CC1352R1_LAUNCHXL/nanoCLR/CC13x2_26x2_CLR.ld +++ b/targets/TI_SimpleLink/TI_CC1352R1_LAUNCHXL/nanoCLR/CC13x2_26x2_CLR.ld @@ -40,7 +40,7 @@ HEAPSIZE = 0x2500; /* Size of heap buffer used by HeapMem */ MEMORY { /* original flash LENGTH was 0x00057fa8 */ - FLASH (RX) : ORIGIN = 0x00000000, LENGTH = 0x00034000 + FLASH (RX) : ORIGIN = 0x00000000, LENGTH = 0x00036000 /* * Customer Configuration Area and Bootloader Backdoor configuration in * flash, 40 bytes From de6ce524b56852bd0f643515636403a2c9752b5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Thu, 20 Nov 2025 13:50:17 +0000 Subject: [PATCH 51/61] Refactor to add helper function for HandleGenericCctorReschedule - Adjust code accordingly (and add it where missing it). --- src/CLR/Core/Interpreter.cpp | 91 ++++++++++++++---------------------- 1 file changed, 34 insertions(+), 57 deletions(-) diff --git a/src/CLR/Core/Interpreter.cpp b/src/CLR/Core/Interpreter.cpp index e0a08194bc..9fab21ea7d 100644 --- a/src/CLR/Core/Interpreter.cpp +++ b/src/CLR/Core/Interpreter.cpp @@ -1002,6 +1002,37 @@ HRESULT CLR_RT_Thread::Execute_DelegateInvoke(CLR_RT_StackFrame &stackArg) NANOCLR_NOCLEANUP(); } +// Helper function to handle generic .cctor rescheduling for static field operations +// Returns CLR_E_RESCHEDULE if rescheduling is needed, CLR_E_WRONG_TYPE if hash is invalid, +// or S_OK if no rescheduling is needed. +static HRESULT HandleGenericCctorReschedule( + CLR_RT_TypeSpec_Instance &tsInst, + CLR_RT_StackFrame *stack, + CLR_PMETADATA *pIp) +{ + CLR_UINT32 hash = + g_CLR_RT_TypeSystem.ComputeHashForClosedGenericType(tsInst, &stack->m_genericTypeSpecStorage, &stack->m_call); + + if (hash == 0xFFFFFFFF) + { + return CLR_E_WRONG_TYPE; + } + + CLR_RT_GenericCctorExecutionRecord *cctorRecord = g_CLR_RT_TypeSystem.FindOrCreateGenericCctorRecord(hash, nullptr); + + if (cctorRecord != nullptr && (cctorRecord->m_flags & CLR_RT_GenericCctorExecutionRecord::c_Scheduled) && + !(cctorRecord->m_flags & CLR_RT_GenericCctorExecutionRecord::c_Executed)) + { + // .cctor is scheduled but not yet executed + // Rewind ip to before this instruction so it will be retried + // (1 byte for opcode + 2 bytes for compressed field token) + *pIp -= 3; + return CLR_E_RESCHEDULE; + } + + return S_OK; +} + HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) { NATIVE_PROFILE_CLR_CORE(); @@ -2971,29 +3002,7 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) CLR_RT_TypeSpec_Instance tsInst; if (tsInst.InitializeFromIndex(*field.genericType)) { - CLR_UINT32 hash = g_CLR_RT_TypeSystem.ComputeHashForClosedGenericType( - tsInst, - &stack->m_genericTypeSpecStorage, - &stack->m_call); - - if (hash == 0xFFFFFFFF) - { - NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE); - } - - CLR_RT_GenericCctorExecutionRecord *cctorRecord = - g_CLR_RT_TypeSystem.FindOrCreateGenericCctorRecord(hash, nullptr); - - if (cctorRecord != nullptr && - (cctorRecord->m_flags & CLR_RT_GenericCctorExecutionRecord::c_Scheduled) && - !(cctorRecord->m_flags & CLR_RT_GenericCctorExecutionRecord::c_Executed)) - { - // .cctor is scheduled but not yet executed - // Rewind ip to before this instruction so it will be retried - // (1 byte for opcode + 2 bytes for compressed field token) - ip -= 3; - NANOCLR_SET_AND_LEAVE(CLR_E_RESCHEDULE); - } + NANOCLR_CHECK_HRESULT(HandleGenericCctorReschedule(tsInst, stack, &ip)); } } } @@ -3048,23 +3057,7 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) CLR_RT_TypeSpec_Instance tsInst; if (tsInst.InitializeFromIndex(*field.genericType)) { - CLR_UINT32 hash = g_CLR_RT_TypeSystem.ComputeHashForClosedGenericType( - tsInst, - &stack->m_genericTypeSpecStorage, - &stack->m_call); - CLR_RT_GenericCctorExecutionRecord *cctorRecord = - g_CLR_RT_TypeSystem.FindOrCreateGenericCctorRecord(hash, nullptr); - - if (cctorRecord != nullptr && - (cctorRecord->m_flags & CLR_RT_GenericCctorExecutionRecord::c_Scheduled) && - !(cctorRecord->m_flags & CLR_RT_GenericCctorExecutionRecord::c_Executed)) - { - // .cctor is scheduled but not yet executed - // Rewind ip to before this instruction so it will be retried - // (1 byte for opcode + 2 bytes for compressed field token) - ip -= 3; - NANOCLR_SET_AND_LEAVE(CLR_E_RESCHEDULE); - } + NANOCLR_CHECK_HRESULT(HandleGenericCctorReschedule(tsInst, stack, &ip)); } } @@ -3121,23 +3114,7 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) CLR_RT_TypeSpec_Instance tsInst; if (tsInst.InitializeFromIndex(*field.genericType)) { - CLR_UINT32 hash = g_CLR_RT_TypeSystem.ComputeHashForClosedGenericType( - tsInst, - &stack->m_genericTypeSpecStorage, - &stack->m_call); - CLR_RT_GenericCctorExecutionRecord *cctorRecord = - g_CLR_RT_TypeSystem.FindOrCreateGenericCctorRecord(hash, nullptr); - - if (cctorRecord != nullptr && - (cctorRecord->m_flags & CLR_RT_GenericCctorExecutionRecord::c_Scheduled) && - !(cctorRecord->m_flags & CLR_RT_GenericCctorExecutionRecord::c_Executed)) - { - // .cctor is scheduled but not yet executed - // Rewind ip to before this instruction so it will be retried - // (1 byte for opcode + 2 bytes for compressed field token) - ip -= 3; - NANOCLR_SET_AND_LEAVE(CLR_E_RESCHEDULE); - } + NANOCLR_CHECK_HRESULT(HandleGenericCctorReschedule(tsInst, stack, &ip)); } } From eb258d2efb9b80f44cc660d599108770acb6689c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Thu, 20 Nov 2025 13:54:10 +0000 Subject: [PATCH 52/61] Fix token resolution for TypeSpec instance when resolving VAR - Was getting the typespec from the wrong assembly. --- src/CLR/Core/TypeSystem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CLR/Core/TypeSystem.cpp b/src/CLR/Core/TypeSystem.cpp index af1656cf03..917fbe9fba 100644 --- a/src/CLR/Core/TypeSystem.cpp +++ b/src/CLR/Core/TypeSystem.cpp @@ -813,7 +813,7 @@ bool CLR_RT_TypeSpec_Instance::ResolveToken( Set(caller->genericType->Assembly(), closedTsRow); assembly = g_CLR_RT_TypeSystem.m_assemblies[caller->genericType->Assembly() - 1]; - target = assm->GetTypeSpec(closedTsRow); + target = assembly->GetTypeSpec(closedTsRow); cachedElementType = paramElement.Class; } else if (element.DataType == DATATYPE_MVAR) From c56d7353e1534cdf86f7331339a3add441bf3cea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Thu, 20 Nov 2025 14:04:40 +0000 Subject: [PATCH 53/61] Add null check in AllocateGenericStaticFieldsOnDemand --- src/CLR/Core/TypeSystem.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/CLR/Core/TypeSystem.cpp b/src/CLR/Core/TypeSystem.cpp index 917fbe9fba..783ed5055e 100644 --- a/src/CLR/Core/TypeSystem.cpp +++ b/src/CLR/Core/TypeSystem.cpp @@ -5664,7 +5664,14 @@ HRESULT CLR_RT_Assembly::AllocateGenericStaticFieldsOnDemand( dlg->m_genericTypeSpec = typeSpecIndex; // Store the caller's MethodSpec (if any) to enable reolution of method generic parameters - dlg->m_genericMethodSpec = contextMethod->methodSpec; + if (contextMethod != nullptr) + { + dlg->m_genericMethodSpec = contextMethod->methodSpec; + } + else + { + dlg->m_genericMethodSpec.Clear(); + } // Push to the .cctor thread and schedule for execution if (SUCCEEDED(g_CLR_RT_ExecutionEngine.m_cctorThread->PushThreadProcDelegate(dlg))) From 094b778c672376ebfa05bb8895c65449723c8a2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Thu, 20 Nov 2025 14:07:41 +0000 Subject: [PATCH 54/61] Add missing null check --- src/CLR/Core/Execution.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/CLR/Core/Execution.cpp b/src/CLR/Core/Execution.cpp index 6d067b4971..c546b9e1cf 100644 --- a/src/CLR/Core/Execution.cpp +++ b/src/CLR/Core/Execution.cpp @@ -2063,6 +2063,11 @@ HRESULT CLR_RT_ExecutionEngine::InitializeReference( if (dt == DATATYPE_VAR) { + if (genericInstance == nullptr || !NANOCLR_INDEX_IS_VALID(*genericInstance)) + { + NANOCLR_SET_AND_LEAVE(CLR_E_FAIL); + } + CLR_RT_TypeSpec_Instance genericTs; if (!genericTs.InitializeFromIndex(*genericInstance)) { From c0001311f004edc1afba7671199fdc2a6433ec94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Thu, 20 Nov 2025 14:14:10 +0000 Subject: [PATCH 55/61] Simplification in PushThreadProcDelegate --- src/CLR/Core/Thread.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/CLR/Core/Thread.cpp b/src/CLR/Core/Thread.cpp index 48fbebaa0c..bcf3ea834f 100644 --- a/src/CLR/Core/Thread.cpp +++ b/src/CLR/Core/Thread.cpp @@ -167,9 +167,7 @@ HRESULT CLR_RT_Thread::PushThreadProcDelegate(CLR_RT_HeapBlock_Delegate *pDelega // Note: We temporarily set inst.genericType to the delegate's interior pointer here, // but will copy it to stable storage in the stack frame after Push() below CLR_RT_TypeSpec_Index delegateTypeSpec; - CLR_RT_MethodSpec_Index delegateMethodSpec; delegateTypeSpec.Clear(); - delegateMethodSpec.Clear(); if (pDelegate->m_genericTypeSpec.data != 0) { @@ -179,8 +177,7 @@ HRESULT CLR_RT_Thread::PushThreadProcDelegate(CLR_RT_HeapBlock_Delegate *pDelega if (pDelegate->m_genericMethodSpec.data != 0) { - delegateMethodSpec = pDelegate->m_genericMethodSpec; - inst.methodSpec = delegateMethodSpec; + inst.methodSpec = pDelegate->m_genericMethodSpec; } #if defined(NANOCLR_APPDOMAINS) @@ -212,10 +209,10 @@ HRESULT CLR_RT_Thread::PushThreadProcDelegate(CLR_RT_HeapBlock_Delegate *pDelega // If we have a generic method context, copy it to the stack frame // This enables MVAR resolution for .cctor triggered from generic methods - if (delegateMethodSpec.data != 0) + if (pDelegate->m_genericMethodSpec.data != 0) { CLR_RT_StackFrame *stackTop = this->CurrentFrame(); - stackTop->m_call.methodSpec = delegateMethodSpec; + stackTop->m_call.methodSpec = pDelegate->m_genericMethodSpec; } if ((inst.target->flags & CLR_RECORD_METHODDEF::MD_Static) == 0) From d061317aa9c13af8b84521d7ee98901945762bec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Thu, 20 Nov 2025 14:18:32 +0000 Subject: [PATCH 56/61] Add defensive check --- src/CLR/Diagnostics/Info.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/CLR/Diagnostics/Info.cpp b/src/CLR/Diagnostics/Info.cpp index 81e624d227..29e1438565 100644 --- a/src/CLR/Diagnostics/Info.cpp +++ b/src/CLR/Diagnostics/Info.cpp @@ -954,7 +954,8 @@ void CLR_RT_Assembly::DumpOpcodeDirect( if (op == CEE_CALL || op == CEE_CALLVIRT) { CLR_RT_MethodDef_Instance mdInst{}; - if (mdInst.ResolveToken(token, call.assembly, call.genericType)) + if (NANOCLR_INDEX_IS_VALID(call) && + mdInst.ResolveToken(token, call.assembly, call.genericType)) { // mdInst now holds the target MethodDef (or MethodSpec) plus any genericType. CLR_RT_DUMP::METHOD(mdInst, call.genericType); From 1879a8adba52f6497e569264afd6e78044334c07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Thu, 20 Nov 2025 14:20:28 +0000 Subject: [PATCH 57/61] Enhanced delegate diagnostics in CLR_RT_DUMP::OBJECT --- src/CLR/Diagnostics/Info.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/CLR/Diagnostics/Info.cpp b/src/CLR/Diagnostics/Info.cpp index 29e1438565..498bf2095d 100644 --- a/src/CLR/Diagnostics/Info.cpp +++ b/src/CLR/Diagnostics/Info.cpp @@ -1118,7 +1118,19 @@ void CLR_RT_DUMP::OBJECT(CLR_RT_HeapBlock *ptr, const char *text) { auto *dlg = (CLR_RT_HeapBlock_Delegate *)ptr; - CLR_RT_DUMP::METHOD(dlg->DelegateFtn(), nullptr); + CLR_RT_MethodDef_Instance mdInst; + if (mdInst.InitializeFromIndex(dlg->DelegateFtn())) + { + // Use the delegate's stored generic context for more informative diagnostics + const CLR_RT_TypeSpec_Index *genericType = + (dlg->m_genericTypeSpec.data != 0) ? &dlg->m_genericTypeSpec : nullptr; + CLR_RT_DUMP::METHOD(mdInst, genericType); + } + else + { + // Fallback if initialization fails + CLR_RT_DUMP::METHOD(dlg->DelegateFtn(), nullptr); + } } break; From 3265bb3a9e4a8502607da3c6ada5473c293ff005 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Thu, 20 Nov 2025 14:22:06 +0000 Subject: [PATCH 58/61] Fix comment --- src/CLR/Core/Execution.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CLR/Core/Execution.cpp b/src/CLR/Core/Execution.cpp index c546b9e1cf..1e20990c53 100644 --- a/src/CLR/Core/Execution.cpp +++ b/src/CLR/Core/Execution.cpp @@ -2314,7 +2314,7 @@ HRESULT CLR_RT_ExecutionEngine::InitializeLocals( // type-level generic parameter in a locals signature (e.g. 'T' inside a generic type) CLR_INT8 genericParamPosition = *sig++; - // First, try to resolve using the method's generic type context + // Resolve type-level generic parameter (VAR) using the method's enclosing type context if (methodDefInstance.genericType && NANOCLR_INDEX_IS_VALID(*methodDefInstance.genericType) && methodDefInstance.genericType->data != CLR_EmptyToken) { From e254835d29d2e59330732b89fe8581bd0e94313d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Thu, 20 Nov 2025 14:26:29 +0000 Subject: [PATCH 59/61] Created a helper function ResolveGenericTypeParameter - Adjust callers accordingly. --- src/CLR/Core/Execution.cpp | 72 +++++++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 28 deletions(-) diff --git a/src/CLR/Core/Execution.cpp b/src/CLR/Core/Execution.cpp index 1e20990c53..6af9dcf01e 100644 --- a/src/CLR/Core/Execution.cpp +++ b/src/CLR/Core/Execution.cpp @@ -2028,6 +2028,40 @@ CLR_RT_HeapBlock *CLR_RT_ExecutionEngine::AccessStaticField(const CLR_RT_FieldDe return nullptr; } +// Helper function to resolve generic type parameters (VAR/MVAR) to their concrete types +// Used by both InitializeReference and InitializeLocals to reduce code duplication +static HRESULT ResolveGenericTypeParameter( + const CLR_RT_TypeSpec_Index &genericTypeIndex, + CLR_UINT8 paramPosition, + CLR_RT_TypeDef_Index &outClass, + NanoCLRDataType &outDataType) +{ + NATIVE_PROFILE_CLR_CORE(); + NANOCLR_HEADER(); + + if (!NANOCLR_INDEX_IS_VALID(genericTypeIndex)) + { + NANOCLR_SET_AND_LEAVE(CLR_E_FAIL); + } + + CLR_RT_TypeSpec_Instance typeSpec; + if (!typeSpec.InitializeFromIndex(genericTypeIndex)) + { + NANOCLR_SET_AND_LEAVE(CLR_E_FAIL); + } + + CLR_RT_SignatureParser::Element paramElement; + if (!typeSpec.GetGenericParam(paramPosition, paramElement)) + { + NANOCLR_SET_AND_LEAVE(CLR_E_FAIL); + } + + outClass = paramElement.Class; + outDataType = paramElement.DataType; + + NANOCLR_NOCLEANUP(); +} + HRESULT CLR_RT_ExecutionEngine::InitializeReference( CLR_RT_HeapBlock &ref, CLR_RT_SignatureParser &parser, @@ -2068,20 +2102,11 @@ HRESULT CLR_RT_ExecutionEngine::InitializeReference( NANOCLR_SET_AND_LEAVE(CLR_E_FAIL); } - CLR_RT_TypeSpec_Instance genericTs; - if (!genericTs.InitializeFromIndex(*genericInstance)) - { - NANOCLR_SET_AND_LEAVE(CLR_E_FAIL); - } - - CLR_RT_SignatureParser::Element paramElement; - if (!genericTs.GetGenericParam(res.GenericParamPosition, paramElement)) - { - NANOCLR_SET_AND_LEAVE(CLR_E_FAIL); - } - - realTypeDef = paramElement.Class; - dt = paramElement.DataType; + NANOCLR_CHECK_HRESULT(ResolveGenericTypeParameter( + *genericInstance, + res.GenericParamPosition, + realTypeDef, + dt)); goto process_datatype; } @@ -2318,20 +2343,11 @@ HRESULT CLR_RT_ExecutionEngine::InitializeLocals( if (methodDefInstance.genericType && NANOCLR_INDEX_IS_VALID(*methodDefInstance.genericType) && methodDefInstance.genericType->data != CLR_EmptyToken) { - CLR_RT_TypeSpec_Instance typeSpec; - if (!typeSpec.InitializeFromIndex(*methodDefInstance.genericType)) - { - NANOCLR_SET_AND_LEAVE(CLR_E_FAIL); - } - - CLR_RT_SignatureParser::Element paramElement; - if (!typeSpec.GetGenericParam(genericParamPosition, paramElement)) - { - NANOCLR_SET_AND_LEAVE(CLR_E_FAIL); - } - - cls = paramElement.Class; - dt = paramElement.DataType; + NANOCLR_CHECK_HRESULT(ResolveGenericTypeParameter( + *methodDefInstance.genericType, + genericParamPosition, + cls, + dt)); } else { From 46a7d2dde9b826c5814b902eebae6e3a87c3c1d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Thu, 20 Nov 2025 14:28:55 +0000 Subject: [PATCH 60/61] Remove duplicated comment --- src/CLR/Include/nanoCLR_Runtime.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/CLR/Include/nanoCLR_Runtime.h b/src/CLR/Include/nanoCLR_Runtime.h index dc5f5ad5e9..bcda8ff63b 100644 --- a/src/CLR/Include/nanoCLR_Runtime.h +++ b/src/CLR/Include/nanoCLR_Runtime.h @@ -2627,7 +2627,6 @@ struct CLR_RT_StackFrame : public CLR_RT_HeapBlock_Node // EVENT HEAP - NO RELOC CLR_RT_MethodDef_Instance m_call; - // Stable storage for generic type context (not GC-relocated) // Stable storage for generic type context (not GC-relocated) // Used when m_call.genericType needs to point to a value that would otherwise // be inside a GC-managed object (e.g., delegate's m_genericTypeSpec) From 43d7fcdb8cc71d66769edb76ce06f969806b1b8a Mon Sep 17 00:00:00 2001 From: nfbot Date: Thu, 20 Nov 2025 14:32:26 +0000 Subject: [PATCH 61/61] Code style fixes Automated fixes for code style. --- src/CLR/Core/Execution.cpp | 16 +++++----------- src/CLR/Diagnostics/Info.cpp | 3 +-- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/src/CLR/Core/Execution.cpp b/src/CLR/Core/Execution.cpp index 6af9dcf01e..9d5d87f078 100644 --- a/src/CLR/Core/Execution.cpp +++ b/src/CLR/Core/Execution.cpp @@ -2046,7 +2046,7 @@ static HRESULT ResolveGenericTypeParameter( CLR_RT_TypeSpec_Instance typeSpec; if (!typeSpec.InitializeFromIndex(genericTypeIndex)) - { + { NANOCLR_SET_AND_LEAVE(CLR_E_FAIL); } @@ -2102,11 +2102,8 @@ HRESULT CLR_RT_ExecutionEngine::InitializeReference( NANOCLR_SET_AND_LEAVE(CLR_E_FAIL); } - NANOCLR_CHECK_HRESULT(ResolveGenericTypeParameter( - *genericInstance, - res.GenericParamPosition, - realTypeDef, - dt)); + NANOCLR_CHECK_HRESULT( + ResolveGenericTypeParameter(*genericInstance, res.GenericParamPosition, realTypeDef, dt)); goto process_datatype; } @@ -2343,11 +2340,8 @@ HRESULT CLR_RT_ExecutionEngine::InitializeLocals( if (methodDefInstance.genericType && NANOCLR_INDEX_IS_VALID(*methodDefInstance.genericType) && methodDefInstance.genericType->data != CLR_EmptyToken) { - NANOCLR_CHECK_HRESULT(ResolveGenericTypeParameter( - *methodDefInstance.genericType, - genericParamPosition, - cls, - dt)); + NANOCLR_CHECK_HRESULT( + ResolveGenericTypeParameter(*methodDefInstance.genericType, genericParamPosition, cls, dt)); } else { diff --git a/src/CLR/Diagnostics/Info.cpp b/src/CLR/Diagnostics/Info.cpp index 498bf2095d..d48d0bc71e 100644 --- a/src/CLR/Diagnostics/Info.cpp +++ b/src/CLR/Diagnostics/Info.cpp @@ -954,8 +954,7 @@ void CLR_RT_Assembly::DumpOpcodeDirect( if (op == CEE_CALL || op == CEE_CALLVIRT) { CLR_RT_MethodDef_Instance mdInst{}; - if (NANOCLR_INDEX_IS_VALID(call) && - mdInst.ResolveToken(token, call.assembly, call.genericType)) + if (NANOCLR_INDEX_IS_VALID(call) && mdInst.ResolveToken(token, call.assembly, call.genericType)) { // mdInst now holds the target MethodDef (or MethodSpec) plus any genericType. CLR_RT_DUMP::METHOD(mdInst, call.genericType);