Skip to content

Commit 1acc89c

Browse files
Copilotrcj1
andauthored
Implement DacDbi cDAC APIs and simplify generic type context APIs (#128263)
Implements four DacDbi APIs in the managed cDAC and adds the supporting `IRuntimeTypeSystem.IsSharedByGenericInstantiations` contract, with matching documentation. ### cDAC DacDbi implementations - `GetSimpleType` — resolves CorElementType to its metadata token + module via `_target.Contracts.RuntimeTypeSystem`. - `ResolveAssembly` — looks up an assembly ref token through the loader contract. - `ResolveExactGenericArgsToken` — walks the generic dictionary to recover the exact instantiation token. - `GetGenericArgTokenIndex` — mirrors the native three-state result (`S_FALSE` / `*pIndex = 0` / `*pIndex = TYPECTXT_ILNUM`). Each implementation is gated by the existing `#if DEBUG` legacy cross-check pattern. ### Contract addition - Added `IRuntimeTypeSystem.IsSharedByGenericInstantiations(MethodDescHandle)`. This is required by `GetGenericArgTokenIndex` to distinguish "not shared" (return `S_FALSE`) from "shared but acquires inst from `this`" (return `*pIndex = 0`); `RequiresInstArg` collapses both cases into `false` and is therefore insufficient on its own. ### Documentation - `docs/design/datacontracts/RuntimeTypeSystem.md`: documented the new public API next to `RequiresInstArg` (with a note on how the two differ), and exposed the existing `IsSharedByGenericInstantiations` helper as a public entry point in the implementation section so the doc matches the contract surface. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: rcj1 <77995559+rcj1@users.noreply.github.com> Co-authored-by: rcj1 <rachel.jarvi@gmail.com>
1 parent 88dbd11 commit 1acc89c

7 files changed

Lines changed: 243 additions & 31 deletions

File tree

docs/design/datacontracts/RuntimeTypeSystem.md

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,15 @@ public enum OptimizationTier
157157
}
158158
```
159159

160+
```csharp
161+
public enum GenericContextLoc
162+
{
163+
None,
164+
InstArg,
165+
ThisPtr,
166+
}
167+
```
168+
160169
```csharp
161170
partial interface IRuntimeTypeSystem : IContract
162171
{
@@ -199,9 +208,9 @@ partial interface IRuntimeTypeSystem : IContract
199208
// Return true if a MethodDesc represents an IL stub with a special MethodDesc context arg
200209
public virtual bool HasMDContextArg(MethodDescHandle);
201210

202-
// Return true if the method requires a hidden instantiation argument (generic context parameter).
203-
// Corresponds to native MethodDesc::RequiresInstArg().
204-
public virtual bool RequiresInstArg(MethodDescHandle methodDesc);
211+
// Determine where a shared generic method obtains its generic context.
212+
// Returns None if not shared, InstArg if via a hidden parameter, or ThisPtr if via the 'this' pointer's MethodTable.
213+
public virtual GenericContextLoc GetGenericContextLoc(MethodDescHandle methodDesc);
205214

206215
// Return true if the method uses the async calling convention.
207216
// Corresponds to native MethodDesc::IsAsyncMethod().
@@ -1637,26 +1646,36 @@ Determining if a method is an async thunk method:
16371646
}
16381647
```
16391648

1640-
Determining if a method requires a hidden instantiation argument (generic context parameter):
1649+
Determining where a shared generic method obtains its generic context:
16411650

16421651
```csharp
1643-
public bool RequiresInstArg(MethodDescHandle methodDescHandle)
1652+
public GenericContextLoc GetGenericContextLoc(MethodDescHandle methodDescHandle)
16441653
{
16451654
MethodDesc md = _methodDescs[methodDescHandle.Address];
1646-
1647-
// RequiresInstArg = IsSharedByGenericInstantiations && (HasMethodInstantiation || IsStatic || IsValueType || IsInterface)
16481655
if (!IsSharedByGenericInstantiations(md))
1649-
return false;
1656+
return GenericContextLoc.None;
1657+
else if (RequiresInstArg(md))
1658+
return GenericContextLoc.InstArg;
1659+
else
1660+
return GenericContextLoc.ThisPtr;
1661+
}
16501662

1663+
private bool RequiresInstArg(MethodDesc md)
1664+
{
16511665
if (HasMethodInstantiation(md))
16521666
return true;
16531667

1654-
// md.IsStatic reads from MethodDescFlags.Static (0x0080)
16551668
if (md.IsStatic)
16561669
return true;
16571670

16581671
MethodTable mt = _methodTables[md.MethodTable];
1659-
return mt.Flags.IsInterface || mt.Flags.IsValueType;
1672+
if (mt.Flags.IsValueType)
1673+
return true;
1674+
1675+
if (mt.Flags.IsInterface && !IsAbstract(md) /* checked from md classification and metadata attributes */)
1676+
return true;
1677+
1678+
return false;
16601679
}
16611680

16621681
private bool IsSharedByGenericInstantiations(MethodDesc md)

src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,13 @@ public enum OptimizationTier : uint
8989
OptimizationTier1Instrumented,
9090
}
9191

92+
public enum GenericContextLoc
93+
{
94+
None,
95+
InstArg,
96+
ThisPtr,
97+
}
98+
9299

93100
public interface IRuntimeTypeSystem : IContract
94101
{
@@ -193,9 +200,7 @@ public interface IRuntimeTypeSystem : IContract
193200
bool IsGenericMethodDefinition(MethodDescHandle methodDesc) => throw new NotImplementedException();
194201
ReadOnlySpan<TypeHandle> GetGenericMethodInstantiation(MethodDescHandle methodDesc) => throw new NotImplementedException();
195202

196-
// Return true if the method requires a hidden instantiation argument (generic context parameter).
197-
// This corresponds to native MethodDesc::RequiresInstArg().
198-
bool RequiresInstArg(MethodDescHandle methodDesc) => throw new NotImplementedException();
203+
GenericContextLoc GetGenericContextLoc(MethodDescHandle methodDescHandle) => throw new NotImplementedException();
199204

200205
// Return true if the method uses the async calling convention (CORINFO_CALLCONV_ASYNCCALL).
201206
// This corresponds to native MethodDesc::IsAsyncMethod().

src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/CorDbHResults.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@ public static class CorDbgHResults
1010
public const int CORDBG_E_READVIRTUAL_FAILURE = unchecked((int)0x80131c49);
1111
public const int ERROR_BUFFER_OVERFLOW = unchecked((int)0x8007006F); // HRESULT_FROM_WIN32(ERROR_BUFFER_OVERFLOW)
1212
public const int CORDBG_E_CLASS_NOT_LOADED = unchecked((int)0x80131303);
13+
public const int CORDBG_E_TARGET_INCONSISTENT = unchecked((int)0x80131c36);
1314
public const int CORDBG_S_NOT_ALL_BITS_SET = unchecked((int)0x00131c13);
1415
}

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

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1348,34 +1348,35 @@ public ReadOnlySpan<TypeHandle> GetGenericMethodInstantiation(MethodDescHandle m
13481348
return AsInstantiatedMethodDesc(methodDesc).Instantiation;
13491349
}
13501350

1351-
/// <summary>
1352-
/// Returns true if the method requires a hidden instantiation argument (generic context parameter).
1353-
/// Matches native MethodDesc::RequiresInstArg().
1354-
/// </summary>
1355-
public bool RequiresInstArg(MethodDescHandle methodDescHandle)
1351+
private bool RequiresInstArg(MethodDesc methodDesc)
13561352
{
1357-
MethodDesc methodDesc = _methodDescs[methodDescHandle.Address];
1358-
1359-
// RequiresInstArg = IsSharedByGenericInstantiations && (HasMethodInstantiation || IsStatic || IsValueType || IsInterface)
1360-
if (!IsSharedByGenericInstantiations(methodDesc))
1361-
return false;
1362-
13631353
if (HasMethodInstantiation(methodDesc))
13641354
return true;
13651355

13661356
if (methodDesc.IsStatic)
13671357
return true;
13681358

13691359
MethodTable mt = _methodTables[methodDesc.MethodTable];
1370-
if (mt.Flags.IsInterface)
1360+
if (mt.Flags.IsValueType)
13711361
return true;
13721362

1373-
if (mt.Flags.IsValueType)
1363+
if (mt.Flags.IsInterface && !IsAbstract(methodDesc))
13741364
return true;
13751365

13761366
return false;
13771367
}
13781368

1369+
public GenericContextLoc GetGenericContextLoc(MethodDescHandle methodDescHandle)
1370+
{
1371+
MethodDesc methodDesc = _methodDescs[methodDescHandle.Address];
1372+
if (!IsSharedByGenericInstantiations(methodDesc))
1373+
return GenericContextLoc.None;
1374+
else if (RequiresInstArg(methodDesc))
1375+
return GenericContextLoc.InstArg;
1376+
else
1377+
return GenericContextLoc.ThisPtr;
1378+
}
1379+
13791380
private bool IsSharedByGenericInstantiations(MethodDesc methodDesc)
13801381
{
13811382
// Check method-level sharing: InstantiatedMethodDesc with SharedMethodInstantiation
@@ -1397,6 +1398,25 @@ private bool IsSharedByGenericInstantiations(MethodDesc methodDesc)
13971398
return mt.IsCanonMT && mt.Flags.HasInstantiation;
13981399
}
13991400

1401+
private bool IsAbstract(MethodDesc methodDesc)
1402+
{
1403+
if (methodDesc.Classification == MethodClassification.Array || methodDesc.Classification == MethodClassification.Dynamic)
1404+
return false;
1405+
uint token = methodDesc.Token;
1406+
if (EcmaMetadataUtils.GetRowId(token) == 0)
1407+
return false;
1408+
1409+
TargetPointer modulePtr = _methodTables[methodDesc.MethodTable].Module;
1410+
ModuleHandle moduleHandle = _target.Contracts.Loader.GetModuleHandleFromModulePtr(modulePtr);
1411+
MetadataReader? mdReader = _target.Contracts.EcmaMetadata.GetMetadata(moduleHandle);
1412+
if (mdReader is null)
1413+
return false;
1414+
1415+
MethodDefinitionHandle methodDefHandle = MetadataTokens.MethodDefinitionHandle((int)EcmaMetadataUtils.GetRowId(token));
1416+
MethodDefinition methodDef = mdReader.GetMethodDefinition(methodDefHandle);
1417+
return (methodDef.Attributes & MethodAttributes.Abstract) != 0;
1418+
}
1419+
14001420
public bool IsAsyncMethod(MethodDescHandle methodDescHandle)
14011421
{
14021422
MethodDesc methodDesc = _methodDescs[methodDescHandle.Address];

src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanner.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,7 @@ private void PromoteCallerStack(
388388

389389
try
390390
{
391-
requiresInstArg = rts.RequiresInstArg(mdh);
391+
requiresInstArg = rts.GetGenericContextLoc(mdh) == GenericContextLoc.InstArg;
392392
isAsync = rts.IsAsyncMethod(mdh);
393393
}
394394
catch

src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs

Lines changed: 166 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1044,7 +1044,37 @@ public int GetCurrentAppDomain(ulong* pRetVal)
10441044
}
10451045

10461046
public int ResolveAssembly(ulong vmScope, uint tkAssemblyRef, ulong* pRetVal)
1047-
=> LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.ResolveAssembly(vmScope, tkAssemblyRef, pRetVal) : HResults.E_NOTIMPL;
1047+
{
1048+
*pRetVal = 0;
1049+
int hr = HResults.S_OK;
1050+
try
1051+
{
1052+
Contracts.ILoader loader = _target.Contracts.Loader;
1053+
Contracts.ModuleHandle scopeModule = loader.GetModuleHandleFromAssemblyPtr(new TargetPointer(vmScope));
1054+
Contracts.ModuleLookupTables lookupTables = loader.GetLookupTables(scopeModule);
1055+
TargetPointer referencedModule = loader.GetModuleLookupMapElement(lookupTables.ManifestModuleReferences, tkAssemblyRef, out _);
1056+
if (referencedModule != TargetPointer.Null)
1057+
{
1058+
Contracts.ModuleHandle referencedModuleHandle = loader.GetModuleHandleFromModulePtr(referencedModule);
1059+
*pRetVal = loader.GetAssembly(referencedModuleHandle).Value;
1060+
}
1061+
}
1062+
catch (System.Exception ex)
1063+
{
1064+
hr = ex.HResult;
1065+
}
1066+
#if DEBUG
1067+
if (_legacy is not null)
1068+
{
1069+
ulong retValLocal;
1070+
int hrLocal = _legacy.ResolveAssembly(vmScope, tkAssemblyRef, &retValLocal);
1071+
Debug.ValidateHResult(hr, hrLocal);
1072+
if (hr == HResults.S_OK)
1073+
Debug.Assert(*pRetVal == retValLocal, $"cDAC: {*pRetVal}, DAC: {retValLocal}");
1074+
}
1075+
#endif
1076+
return hr;
1077+
}
10481078

10491079
public int GetNativeCodeSequencePointsAndVarInfo(ulong vmMethodDesc, ulong startAddress, Interop.BOOL fCodeAvailable, nint pNativeVarData, nint pSequencePoints)
10501080
=> LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetNativeCodeSequencePointsAndVarInfo(vmMethodDesc, startAddress, fCodeAvailable, pNativeVarData, pSequencePoints) : HResults.E_NOTIMPL;
@@ -1270,7 +1300,56 @@ public int RequiresAlign8(ulong thExact, Interop.BOOL* pResult)
12701300
}
12711301

12721302
public int ResolveExactGenericArgsToken(uint dwExactGenericArgsTokenIndex, ulong rawToken, ulong* pRetVal)
1273-
=> LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.ResolveExactGenericArgsToken(dwExactGenericArgsTokenIndex, rawToken, pRetVal) : HResults.E_NOTIMPL;
1303+
{
1304+
*pRetVal = 0;
1305+
int hr = HResults.S_OK;
1306+
try
1307+
{
1308+
if (dwExactGenericArgsTokenIndex == 0)
1309+
{
1310+
// In a rare case of VS4Mac debugging VS4Mac ARM64 optimized code we get a null
1311+
// generics argument token. We aren't sure why the token is null, it may be a bug
1312+
// or it may be by design in the runtime. In the interest of time we are working
1313+
// around the issue rather than investigating the root cause.
1314+
if (rawToken == 0)
1315+
{
1316+
*pRetVal = rawToken;
1317+
}
1318+
else
1319+
{
1320+
// The real generics type token is the MethodTable of the "this" object.
1321+
// The incoming rawToken is a target address of the 'this' Object.
1322+
*pRetVal = _target.Contracts.Object.GetMethodTableAddress(new TargetPointer(rawToken)).Value;
1323+
}
1324+
}
1325+
else if (dwExactGenericArgsTokenIndex == unchecked((uint)IlNum.TYPECTXT_ILNUM))
1326+
{
1327+
// rawToken is already the real generics type token. Nothing to do.
1328+
*pRetVal = rawToken;
1329+
}
1330+
else
1331+
{
1332+
// The index of the generics type token should not be anything else.
1333+
Debug.Fail($"Unexpected generics type token index: {dwExactGenericArgsTokenIndex}");
1334+
throw Marshal.GetExceptionForHR(CorDbgHResults.CORDBG_E_TARGET_INCONSISTENT)!;
1335+
}
1336+
}
1337+
catch (System.Exception ex)
1338+
{
1339+
hr = ex.HResult;
1340+
}
1341+
#if DEBUG
1342+
if (_legacy is not null)
1343+
{
1344+
ulong retValLocal;
1345+
int hrLocal = _legacy.ResolveExactGenericArgsToken(dwExactGenericArgsTokenIndex, rawToken, &retValLocal);
1346+
Debug.ValidateHResult(hr, hrLocal);
1347+
if (hr == HResults.S_OK)
1348+
Debug.Assert(*pRetVal == retValLocal, $"cDAC: {*pRetVal}, DAC: {retValLocal}");
1349+
}
1350+
#endif
1351+
return hr;
1352+
}
12741353

12751354
public int GetILCodeAndSig(ulong vmAssembly, uint functionToken, DacDbiTargetBuffer* pTargetBuffer, uint* pLocalSigToken)
12761355
=> LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetILCodeAndSig(vmAssembly, functionToken, pTargetBuffer, pLocalSigToken) : HResults.E_NOTIMPL;
@@ -1443,7 +1522,49 @@ public int GetTypeHandleParams(ulong vmTypeHandle, nint pParams)
14431522
=> LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetTypeHandleParams(vmTypeHandle, pParams) : HResults.E_NOTIMPL;
14441523

14451524
public int GetSimpleType(int simpleType, uint* pMetadataToken, ulong* pVmModule)
1446-
=> LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetSimpleType(simpleType, pMetadataToken, pVmModule) : HResults.E_NOTIMPL;
1525+
{
1526+
Debug.Assert(pVmModule != null);
1527+
*pVmModule = 0;
1528+
int hr = HResults.S_OK;
1529+
try
1530+
{
1531+
Contracts.IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
1532+
Contracts.TypeHandle typeHandle = rts.GetPrimitiveType((CorElementType)simpleType);
1533+
1534+
if (typeHandle.IsNull)
1535+
{
1536+
throw Marshal.GetExceptionForHR(CorDbgHResults.CORDBG_E_CLASS_NOT_LOADED)!;
1537+
}
1538+
1539+
Debug.Assert(pMetadataToken != null);
1540+
*pMetadataToken = rts.GetTypeDefToken(typeHandle);
1541+
1542+
TargetPointer module = rts.GetModule(typeHandle);
1543+
if (module == TargetPointer.Null)
1544+
throw Marshal.GetExceptionForHR(CorDbgHResults.CORDBG_E_TARGET_INCONSISTENT)!;
1545+
1546+
*pVmModule = module.Value;
1547+
}
1548+
catch (System.Exception ex)
1549+
{
1550+
hr = ex.HResult;
1551+
}
1552+
#if DEBUG
1553+
if (_legacy is not null)
1554+
{
1555+
uint metadataTokenLocal;
1556+
ulong vmModuleLocal;
1557+
int hrLocal = _legacy.GetSimpleType(simpleType, &metadataTokenLocal, &vmModuleLocal);
1558+
Debug.ValidateHResult(hr, hrLocal);
1559+
if (hr == HResults.S_OK)
1560+
{
1561+
Debug.Assert(*pMetadataToken == metadataTokenLocal, $"cDAC: {*pMetadataToken}, DAC: {metadataTokenLocal}");
1562+
Debug.Assert(*pVmModule == vmModuleLocal, $"cDAC: {*pVmModule}, DAC: {vmModuleLocal}");
1563+
}
1564+
}
1565+
#endif
1566+
return hr;
1567+
}
14471568

14481569
public int IsExceptionObject(ulong vmObject, Interop.BOOL* pResult)
14491570
=> LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.IsExceptionObject(vmObject, pResult) : HResults.E_NOTIMPL;
@@ -2226,6 +2347,47 @@ public int GetAsyncLocals(ulong vmMethod, ulong codeAddr, uint state, nint pAsyn
22262347
=> LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetAsyncLocals(vmMethod, codeAddr, state, pAsyncLocals) : HResults.E_NOTIMPL;
22272348

22282349
public int GetGenericArgTokenIndex(ulong vmMethod, uint* pIndex)
2229-
=> LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetGenericArgTokenIndex(vmMethod, pIndex) : HResults.E_NOTIMPL;
2350+
{
2351+
int hr = HResults.S_OK;
2352+
try
2353+
{
2354+
if (vmMethod == 0)
2355+
throw new ArgumentException("vmMethod must not be zero.", nameof(vmMethod));
2356+
if (pIndex is null)
2357+
throw new ArgumentException("pIndex must not be null.", nameof(pIndex));
2358+
Contracts.IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
2359+
Contracts.MethodDescHandle md = rts.GetMethodDescHandle(new TargetPointer(vmMethod));
2360+
2361+
switch (rts.GetGenericContextLoc(md))
2362+
{
2363+
case GenericContextLoc.None:
2364+
hr = HResults.S_FALSE;
2365+
break;
2366+
case GenericContextLoc.InstArg:
2367+
*pIndex = unchecked((uint)IlNum.TYPECTXT_ILNUM);
2368+
break;
2369+
case GenericContextLoc.ThisPtr:
2370+
*pIndex = 0u;
2371+
break;
2372+
default:
2373+
throw new InvalidOperationException($"Unexpected generic context location: {rts.GetGenericContextLoc(md)}");
2374+
}
2375+
}
2376+
catch (System.Exception ex)
2377+
{
2378+
hr = ex.HResult;
2379+
}
2380+
#if DEBUG
2381+
if (_legacy is not null)
2382+
{
2383+
uint indexLocal;
2384+
int hrLocal = _legacy.GetGenericArgTokenIndex(vmMethod, &indexLocal);
2385+
Debug.ValidateHResult(hr, hrLocal);
2386+
if (hr == HResults.S_OK)
2387+
Debug.Assert(*pIndex == indexLocal, $"cDAC: {*pIndex}, DAC: {indexLocal}");
2388+
}
2389+
#endif
2390+
return hr;
2391+
}
22302392

22312393
}

src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,11 @@ public enum CorDebugUserState
176176
USER_THREADPOOL = 0x100,
177177
}
178178

179+
public enum IlNum : int
180+
{
181+
TYPECTXT_ILNUM = -3,
182+
}
183+
179184
// Name-surface projection of IDacDbiInterface in native method order for COM binding validation.
180185
// Parameter shapes are intentionally coarse placeholders and will be refined with method implementation work.
181186
[GeneratedComInterface]

0 commit comments

Comments
 (0)