Skip to content

[cDAC] implement GetExactTypeHandle API#128616

Draft
Copilot wants to merge 1 commit into
mainfrom
copilot/plan-implement-cdac-dacdbi-api-getexacttypehandle
Draft

[cDAC] implement GetExactTypeHandle API#128616
Copilot wants to merge 1 commit into
mainfrom
copilot/plan-implement-cdac-dacdbi-api-getexacttypehandle

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented May 27, 2026

Implements the cDAC GetExactTypeHandle API, including the function-pointer branch. Extends GetConstructedType for function pointers, albeit only for the managed calling convention (equivalent with native).

Co-authored-by: rcj1 <77995559+rcj1@users.noreply.github.com>
@dotnet-policy-service
Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @steveisok, @tommcdon, @dotnet/dotnet-diag
See info in area-owners.md if you want to be subscribed.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends the cDAC / DataContractReader type-system and legacy DBI interop to support exact type-handle resolution (including function pointer types) and to read loader-allocator collectibility metadata directly from the runtime data descriptor. It also enables DAC-side function-pointer type comparisons by making CoreCLR’s EETypeHashTable::CompareFnPtrType DAC-compatible.

Changes:

  • Implemented IDacDbiInterface.GetExactTypeHandle in the managed Legacy DBI path, including constructed-type resolution (array/ptr/byref/generic instantiation/fnptr).
  • Extended IRuntimeTypeSystem.GetConstructedType with a callConv discriminator and added function-pointer loader-module selection logic (mirroring CoreCLR’s native algorithm).
  • Exposed and consumed LoaderAllocator.IsCollectible and LoaderAllocator.CreationNumber via cDAC data descriptors; adjusted CoreCLR DAC paths to read FnPtr ret/arg arrays via DAC-safe field access.
Show a summary per file
File Description
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs Adds ArgInfoList and updates GetExactTypeHandle signature to typed pointers.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs Implements GetExactTypeHandle and helpers for exact constructed-type resolution in managed cDAC.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/LoaderAllocator.cs Reads IsCollectible and CreationNumber unconditionally from the data descriptor.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs Adds callConv to constructed-type caching and implements FnPtr matching + loader-module selection.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs Extends GetConstructedType signature with optional callConv.
src/coreclr/vm/typehash.cpp Removes DACCESS_COMPILE guard to enable FnPtr comparisons under DAC builds.
src/coreclr/vm/typedesc.h Makes FnPtr ret/arg pointer retrieval use DAC-safe member-address access.
src/coreclr/vm/loaderallocator.hpp Exposes m_IsCollectible and m_nLoaderAllocator offsets in cdac_data<LoaderAllocator>.
src/coreclr/vm/datadescriptor/datadescriptor.inc Adds LoaderAllocator.IsCollectible and LoaderAllocator.CreationNumber fields to the descriptor.
docs/design/datacontracts/RuntimeTypeSystem.md Documents the updated constructed-type API and the new loader-allocator fields/algorithm.

Copilot's findings

Comments suppressed due to low confidence (2)

src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs:1111

  • GetConstructedType looks up the cache using a TypeKey that includes callConv, but some successful paths add to the cache using a TypeKey ctor overload that omits callConv (defaulting to 0). If a non-zero callConv is ever passed for non-FnPtr element types (even accidentally), caching becomes inconsistent. Consider normalizing callConv to 0 unless corElementType == FnPtr, and always using the same TypeKey construction (including callConv) for both lookup and insertion.
    TypeHandle IRuntimeTypeSystem.GetConstructedType(TypeHandle typeHandle, CorElementType corElementType, int rank, ImmutableArray<TypeHandle> typeArguments, byte callConv)
    {
        if (typeHandle.Address == TargetPointer.Null && corElementType != CorElementType.FnPtr)
            return new TypeHandle(TargetPointer.Null);
        if (_typeHandles.TryGetValue(new TypeKey(typeHandle, corElementType, rank, typeArguments, callConv), out TypeHandle existing))
            return existing;
        ILoader loaderContract = _target.Contracts.Loader;
        TargetPointer loaderModule;
        if (corElementType == CorElementType.FnPtr)
            loaderModule = FindFnPtrLoaderModule(typeArguments);
        else
            loaderModule = GetLoaderModule(typeHandle);

        ModuleHandle moduleHandle = loaderContract.GetModuleHandleFromModulePtr(loaderModule);
        TypeHandle potentialMatch;
        foreach (TargetPointer ptr in loaderContract.GetAvailableTypeParams(moduleHandle))
        {
            potentialMatch = GetTypeHandle(ptr);
            if (corElementType == CorElementType.GenericInst)
            {
                if (GenericInstantiationMatch(typeHandle, potentialMatch, typeArguments) && IsLoaded(potentialMatch))
                {
                    _ = _typeHandles.TryAdd(new TypeKey(typeHandle, corElementType, rank, typeArguments), potentialMatch);
                    return potentialMatch;
                }
            }
            else if (corElementType == CorElementType.FnPtr)
            {
                if (FnPtrMatch(potentialMatch, typeArguments, callConv) && IsLoaded(potentialMatch))
                {
                    _ = _typeHandles.TryAdd(new TypeKey(typeHandle, corElementType, rank, typeArguments, callConv), potentialMatch);
                    return potentialMatch;
                }
            }
            else if (ArrayPtrMatch(typeHandle, corElementType, rank, potentialMatch) && IsLoaded(potentialMatch))
            {
                _ = _typeHandles.TryAdd(new TypeKey(typeHandle, corElementType, rank, typeArguments), potentialMatch);
                return potentialMatch;

src/coreclr/vm/typehash.cpp:446

  • CompareFnPtrType now compiles under DACCESS_COMPILE, but it assigns GetRetAndArgTypesPointer() (PTR_TypeHandle/DPTR) into a raw TypeHandle* and then indexes it. Under DAC this is unsafe because DPTR->T* marshals a single element; indexing will read past the marshaled buffer and can produce incorrect comparisons or memory errors. Keep it as PTR_TypeHandle/ArrayDPTR and use DAC-safe indexing, or explicitly marshal (numArgs+1) elements into a local buffer before comparing.
    if (!t.IsFnPtrType())
        return FALSE;

    FnPtrTypeDesc* pTD = t.AsFnPtrType();

    if (pTD->GetNumArgs() != numArgs || pTD->GetCallConv() != callConv)
        return FALSE;

    // Now check the return and argument types. Some type arguments might be encoded.
    TypeHandle *retAndArgTypes2 = pTD->GetRetAndArgTypesPointer();
    for (DWORD i = 0; i <= numArgs; i++)
    {
        if (retAndArgTypes2[i] != retAndArgTypes[i])
        {
  • Files reviewed: 10/10 changed files
  • Comments generated: 7

Comment on lines +1873 to +1884
public int GetExactTypeHandle(DebuggerIPCE_ExpandedTypeData* pTypeData, ArgInfoList* pArgInfo, ulong* pVmTypeHandle)
{
if (pVmTypeHandle == null)
return HResults.E_POINTER;
*pVmTypeHandle = 0;
int hr = HResults.S_OK;
try
{
IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
TypeHandle th = default;
CorElementType et = (CorElementType)ReadLittleEndian(pTypeData->elementType);
switch (et)
Comment on lines +1873 to +1903
public int GetExactTypeHandle(DebuggerIPCE_ExpandedTypeData* pTypeData, ArgInfoList* pArgInfo, ulong* pVmTypeHandle)
{
if (pVmTypeHandle == null)
return HResults.E_POINTER;
*pVmTypeHandle = 0;
int hr = HResults.S_OK;
try
{
IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
TypeHandle th = default;
CorElementType et = (CorElementType)ReadLittleEndian(pTypeData->elementType);
switch (et)
{
case CorElementType.Array:
case CorElementType.SzArray:
th = GetExactArrayTypeHandle(rts, pTypeData, pArgInfo);
break;
case CorElementType.Ptr:
case CorElementType.Byref:
th = GetExactPtrOrByRefTypeHandle(rts, pTypeData, pArgInfo);
break;
case CorElementType.Class:
case CorElementType.ValueType:
th = GetExactClassTypeHandle(rts, pTypeData, pArgInfo);
break;
case CorElementType.FnPtr:
th = GetExactFnPtrTypeHandle(rts, pArgInfo);
break;
default:
th = rts.GetPrimitiveType(et);
break;
Comment on lines 90 to 107
@@ -101,7 +103,7 @@ public bool Equals(TypeKey other)

public override int GetHashCode()
{
int hash = HashCode.Combine(TypeHandle.GetHashCode(), (int)ElementType, Rank);
int hash = HashCode.Combine(TypeHandle.GetHashCode(), (int)ElementType, Rank, (int)CallConv);
foreach (TypeHandle th in TypeArgs)
Comment on lines +1117 to +1163
// Mirrors the native algorithm
// ClassLoader::ComputeLoaderModuleForFunctionPointer: the loader module is the collectible
// ret/arg type's loader module with the largest LoaderAllocator creation number, or CoreLib's
// loader module if none of the ret/arg types are collectible.
private TargetPointer FindFnPtrLoaderModule(ImmutableArray<TypeHandle> retAndArgTypes)
{
ILoader loaderContract = _target.Contracts.Loader;

TargetPointer loaderModulePtr = TargetPointer.Null;
ulong latestFoundNumber = 0;
bool anyCollectible = false;

foreach (TypeHandle arg in retAndArgTypes)
{
if (arg.Address == TargetPointer.Null)
continue;

TargetPointer argModulePtr = GetLoaderModule(arg);
if (argModulePtr == TargetPointer.Null)
continue;

ModuleHandle argModuleHandle = loaderContract.GetModuleHandleFromModulePtr(argModulePtr);
TargetPointer argLoaderAllocator = loaderContract.GetLoaderAllocator(argModuleHandle);
if (argLoaderAllocator == TargetPointer.Null)
continue;

Data.LoaderAllocator loaderAllocator = _target.ProcessedData.GetOrAdd<Data.LoaderAllocator>(argLoaderAllocator);
if (!loaderAllocator.IsCollectible)
continue;

if (!anyCollectible || loaderAllocator.CreationNumber > latestFoundNumber)
{
anyCollectible = true;
latestFoundNumber = loaderAllocator.CreationNumber;
loaderModulePtr = argModulePtr;
}
}

if (!anyCollectible)
{
TargetPointer systemAssembly = loaderContract.GetSystemAssembly();
ModuleHandle coreLibModuleHandle = loaderContract.GetModuleHandleFromAssemblyPtr(systemAssembly);
loaderModulePtr = loaderContract.GetModule(coreLibModuleHandle);
}

return loaderModulePtr;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants