Skip to content

cDAC: Add GetGCDescSeries contract API and continuation pretty-printing#127419

Open
Copilot wants to merge 4 commits intomainfrom
copilot/implement-continuation-pretty-printing
Open

cDAC: Add GetGCDescSeries contract API and continuation pretty-printing#127419
Copilot wants to merge 4 commits intomainfrom
copilot/implement-continuation-pretty-printing

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 25, 2026

Summary

Implements continuation pretty-printing in the cDAC, mirroring AsyncContinuationsManager::PrintContinuationName from asynccontinuations.h, and adds a new GetGCDescSeries API to the RuntimeTypeSystem contract.

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).


// 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.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.)

Comment thread docs/design/datacontracts/RuntimeTypeSystem.md
Copilot AI review requested due to automatic review settings April 25, 2026 18:43
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 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.GetGCDescSeries and implement it in RuntimeTypeSystem_1 to enumerate GC pointer runs from the GCDesc preceding a MethodTable.
  • Add continuation synthetic-name formatting to TypeNameBuilder using GetGCDescSeries.
  • Add a new ContinuationObject data 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.

Comment thread src/native/managed/cdac/tests/MethodTableTests.cs
@rcj1 rcj1 requested review from max-charlamb and noahfalk April 25, 2026 19:41
@rcj1 rcj1 marked this pull request as ready for review April 25, 2026 19:42
@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.

@hoyosjs
Copy link
Copy Markdown
Member

hoyosjs commented Apr 27, 2026

Might be missing https://github.com/hoyosjs/runtime/blob/9ef63b95ace0796ca363bc53687ddbb670f1119b/src/coreclr/gc/gcdesc.h#L227-L231. NVM - looks like this is not stored inline with CGCDescSeries array

long absNumSeries = -numSeries;
ulong startOffset = _target.ReadPointer(mtAddress - 2 * pointerSize).Value;

ulong currentOffset = startOffset;
Copy link
Copy Markdown
Member

@jkotas jkotas Apr 27, 2026

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.

pMT->SetBaseSize(OBJECT_BASESIZE + startOfDataInInstance + dataSize);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Sounds good

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

@hoyosjs
Copy link
Copy Markdown
Member

hoyosjs commented Apr 27, 2026

dotnet/diagnostics#5815

Copilot AI review requested due to automatic review settings April 28, 2026 23:20
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

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.

/// </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>
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

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

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).

Suggested change
/// <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>

Copilot uses AI. Check for mistakes.
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.

6 participants