cDAC: Add GetGCDescSeries contract API and continuation pretty-printing#127419
cDAC: Add GetGCDescSeries contract API and continuation pretty-printing#127419
Conversation
…ation pretty-printing Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/6e988c50-2e06-4957-952d-9b624dd862ca Co-authored-by: rcj1 <77995559+rcj1@users.noreply.github.com>
|
|
||
| // NumSeries is stored one pointer-width before the method table. When interpreted as a signed | ||
| // value, a negative count indicates a value-class (repeating) series, which uses a different | ||
| // layout and is not supported by this API. |
There was a problem hiding this comment.
We may want to have a general purpose GCDesc enumeration, something that can replace
https://github.com/microsoft/clrmd/blob/main/src/Microsoft.Diagnostics.Runtime/GCDesc.cs
(GCDesc is one of those internal data contracts that CLRMD and SOS have hardcoded implicit dependency on that's not ideal.)
There was a problem hiding this comment.
Pull request overview
This PR extends the cDAC RuntimeTypeSystem contract surface to expose GCDesc “series” (GC pointer runs) and uses that information to pretty-print dynamically-created continuation types in a way that mirrors the runtime’s AsyncContinuationsManager::PrintContinuationName.
Changes:
- Add
IRuntimeTypeSystem.GetGCDescSeriesand implement it inRuntimeTypeSystem_1to enumerate GC pointer runs from the GCDesc preceding a MethodTable. - Add continuation synthetic-name formatting to
TypeNameBuilderusingGetGCDescSeries. - Add a new
ContinuationObjectdata descriptor/type entry plus mocks, tests, and contract documentation updates.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.RuntimeTypeSystem.cs | Adds mock helpers to allocate MethodTables with GCDesc (regular + repeating/value-class layouts) and exposes ContinuationObjectSize. |
| src/native/managed/cdac/tests/MethodTableTests.cs | Adds unit tests covering GetGCDescSeries behaviors across MethodTable/non-MethodTable, no-GC, regular series, and repeating series cases. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/TypeNameBuilder.cs | Uses IsContinuation + GetGCDescSeries to generate the Continuation_<dataSize>[_<gcOffset>_<gcCount>]* synthetic name instead of (dynamicClass). |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs | Implements GetGCDescSeries by reading GCDesc memory preceding the MethodTable and normalizing sizes. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs | Adds DataType.ContinuationObject for retrieving continuation header sizing info from the contract descriptor. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs | Adds the GetGCDescSeries(TypeHandle, uint objectSize) contract API. |
| src/coreclr/vm/datadescriptor/datadescriptor.inc | Adds a ContinuationObject type descriptor entry so the reader can query its size. |
| docs/design/datacontracts/RuntimeTypeSystem.md | Updates contract documentation to include the new API and describe GCDesc series layouts. |
|
Tagging subscribers to this area: @steveisok, @tommcdon, @dotnet/dotnet-diag |
|
|
| long absNumSeries = -numSeries; | ||
| ulong startOffset = _target.ReadPointer(mtAddress - 2 * pointerSize).Value; | ||
|
|
||
| ulong currentOffset = startOffset; |
There was a problem hiding this comment.
If this is trying to replicate https://github.com/microsoft/clrmd/blob/main/src/Microsoft.Diagnostics.Runtime/GCDesc.cs exactly, this would need to take the passed in object size into account too and keep repeating the sequence.
I am not sure whether the API shape that takes object size works best for all cases. It assumes that you have object size that is not always the case, like for the Continuation pretty printing.
There was a problem hiding this comment.
Ok. I don't see an issue with taking the object size; Continuation method tables do have the total object size stored on the method table flags, accessible through the GetBaseSize API.
runtime/src/coreclr/vm/asynccontinuations.cpp
Line 157 in 23d2e12
There was a problem hiding this comment.
Could we take an optional numComponents instead of objectSize
For non-array types, baseSize==objectSize, for array types objectSize can be found from the numComponents and TypeHandle.
This removes redundant lookups at the call sites. The non-array path in AppendContinuationName does GetBaseSize only to pass it back in the
contract that already knows BaseSize.
Suggested shape:
// Default 0 covers non-arrays cleanly: objectSize == BaseSize.
IEnumerable<(uint SeriesOffset, uint SeriesSize)> GetGCDescSeries(
TypeHandle typeHandle, uint numComponents = 0);
Inside the implementation:
uint baseSize = GetBaseSize(typeHandle);
uint componentSize = GetComponentSize(typeHandle);
uint objectSize = baseSize + numComponents * componentSize;
// ...existing logic...
This also matches native's CGCDesc::GetNumPointers(MT*, ObjectSize, NumComponents) more closely in spirit, and lets AppendContinuationName
simplify to GetGCDescSeries(typeHandle) (no baseSize local needed).
There was a problem hiding this comment.
IEnumerable<(uint SeriesOffset, uint SeriesSize)> GetGCDescSeries(TypeHandle typeHandle, uint numComponents = 0);
This shape works well for enumerating all GC references in the given object instance. It does not work well for dumping GCInfo without an object instance, or for efficient or partial enumeration of GC references.
We can start with this shape; I am just pointing its short-comings.
| /// </summary> | ||
| /// <param name="name">Descriptive name for the allocation.</param> | ||
| /// <param name="baseSize">Value to store in the method table's <c>BaseSize</c> field.</param> | ||
| /// <param name="startOffset">The <c>startoffset</c> field of the CGCDescSeries (offset from object start).</param> |
There was a problem hiding this comment.
The startOffset here is described as an offset from the "object start", but GCDesc startoffset is an offset from the managed object pointer (MethodTable) and excludes the syncblock/ObjectHeader prefix. Clarifying this in the parameter documentation (e.g., "offset from the object pointer / first byte of the Object", not including ObjectHeader) would help prevent callers from accidentally including OBJHEADER_SIZE (which would shift all series offsets).
| /// <param name="startOffset">The <c>startoffset</c> field of the CGCDescSeries (offset from object start).</param> | |
| /// <param name="startOffset"> | |
| /// The <c>startoffset</c> field of the CGCDescSeries, as an offset from the managed object | |
| /// pointer (the first byte of the object). This does not include any sync block / | |
| /// <c>ObjectHeader</c> prefix. | |
| /// </param> |
Summary
Implements continuation pretty-printing in the cDAC, mirroring
AsyncContinuationsManager::PrintContinuationNamefromasynccontinuations.h, and adds a newGetGCDescSeriesAPI to theRuntimeTypeSystemcontract.Fixes the WebApp3 test failure seen in recent runtime-diagnostics legs such as this: https://dev.azure.com/dnceng-public/public/_build/results?buildId=1395576
Note
This PR was created with the assistance of GitHub Copilot (AI-generated content).