Skip to content

[cdac] x86 GCInfo decoder + PromoteCallerStack -> GCRefMap; enforce 0 known issues on windows-x86/x64#129858

Draft
max-charlamb wants to merge 1 commit into
dotnet:mainfrom
max-charlamb:x86-gc-info-w-argiterator
Draft

[cdac] x86 GCInfo decoder + PromoteCallerStack -> GCRefMap; enforce 0 known issues on windows-x86/x64#129858
max-charlamb wants to merge 1 commit into
dotnet:mainfrom
max-charlamb:x86-gc-info-w-argiterator

Conversation

@max-charlamb

@max-charlamb max-charlamb commented Jun 25, 2026

Copy link
Copy Markdown
Member

Note

This PR was authored with assistance from GitHub Copilot.

Summary

Brings the cDAC IGCInfoDecoder x86 implementation (originally on the now-closed PR #129547) plus the GcScanner.PromoteCallerStack rewire (using ICallingConvention.TryComputeArgGCRefMapBlob from #129769) into a single change, and tightens the GCREFS stress assertion so any non-zero KnownIssues count on Windows x86 / x64 fails the test.

After this change, x86 cDAC stack walking is at full parity with the runtime for the cdacstress GCREFS suite (BasicAlloc local run: 4798 / 4798 pass, 0 known, 0 fail).

Three logical groupings

1. x86 IGCInfoDecoder implementation (28 commits, the bulk of the diff)

Implements EnumerateLiveSlots and GetInterruptibleRanges on x86 by walking the legacy InfoHdr byte-stream and the per-offset Transitions already decoded in GCInfo.cs / GCArgTable.cs. Includes a long tail of correctness fixes uncovered by the cdacstress harness: 0xFB huge-encoding code-delta is cumulative; partial-EBP this-pointer-tag bytes do not record a call entry; ESP-frame untracked / VarPtr slots need a pushedSize bias; ApplyPointerTransition honors IsPtr=false for non-pointer pushes; etc.

2. GcScanner.PromoteCallerStack rewire (1 commit + 1 supporting commit)

Replaces the RecordDeferredFrame stub with a real implementation that synthesizes a GCRefMap blob via ICallingConvention.TryComputeArgGCRefMapBlob and runs the same token-iteration loop as the R2R-backed PromoteCallerStackUsingGCRefMap. Falls back to RecordDeferredFrame on unsupported targets (non-Windows or non-x86/x64) and on any failure to synthesize a blob. The supporting commit adds the PInvokeCalliFrame data class used by the x86 HandleTransitionFrame override (PInvokeCalliFrame has no MethodDesc so it falls back to VASigCookie.SizeOfArgs).

Also adds:

  • GCRefMapDecoder(byte[]) second constructor so synthesized blobs can be decoded with the same bit-stream reader as TargetPointer-resident blobs.
  • X86FrameHandler.HandleTransitionFrame override -- decodes the leading ReadStackPop() of the synthesized blob to recover cbStackPop on x86.
  • EnumerateGCRefMapTokens helper, factored out so both the R2R-blob and synthesized-blob paths share the same token-iteration loop.

3. Stress framework hardening

  • CdacStressTests.GCRefStress_AllVerificationsPass: removed the if (arch == Architecture.X86) throw new SkipTestException block.
  • CdacStressTestBase.AssertAllPassed: on Windows x86 / x64, fail the test if results.KnownIssues > 0. Every transition Frame's caller-stack scan must succeed via ICallingConvention.TryComputeArgGCRefMapBlob.

Local validation (windows-x86 Checked)

BasicAlloc smoke test:

Total verifications: 4,798
Passed: 4,798
Failed: 0
Known issues: 0
Frames examined: 64,406 (all matched)

cDAC unit tests: 2611 / 2611 pass.

Full GCRefStress suite will be validated by CI.

Scope notes

Copilot AI left a comment

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.

Pull request overview

This PR expands the cDAC stack-walking and stress infrastructure by wiring Windows x86 transition-frame caller scanning to the shared ICallingConvention/GCRefMap machinery, and by extending the in-proc cdacstress harness to support multiple sub-checks (GCREFS + ARGITER) with richer test/debuggee coverage.

Changes:

  • Reworked caller-stack promotion to optionally synthesize and decode GCRefMap blobs via a new ICallingConvention.TryComputeArgGCRefMapBlob path and shared token enumeration.
  • Extended cdacstress configuration/telemetry (flag layout, [GC_STATS] / [ARG_STATS]) and updated managed stress harness parsing/assertions; added multiple new stress debuggees.
  • Refactored/shared calling convention code (ArgIterator/TransitionBlock/ITypeHandle) for reuse by cDAC and ReadyToRun tooling.

Reviewed changes

Copilot reviewed 60 out of 60 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/native/managed/cdac/tests/StressTests/RunStressTests.ps1 Updates stress flag documentation/defaults to new WHERE/WHAT/MODIFIER layout.
src/native/managed/cdac/tests/StressTests/README.md Documents new flag regions, sub-check markers, and updated defaults.
src/native/managed/cdac/tests/StressTests/known-issues.md Updates defaults and adds documentation for an intermittent x86 flake.
src/native/managed/cdac/tests/StressTests/Debuggees/VarArgs/VarArgs.csproj Adds VarArgs debuggee project file.
src/native/managed/cdac/tests/StressTests/Debuggees/VarArgs/Program.cs Adds varargs-focused ARGITER debuggee.
src/native/managed/cdac/tests/StressTests/Debuggees/StructScenarios/Program.cs Adds nested-struct scenario to stress ArgIterator/GCDesc paths.
src/native/managed/cdac/tests/StressTests/Debuggees/CrossModule/Program.cs Adds cross-module signature/type-resolution debuggee.
src/native/managed/cdac/tests/StressTests/Debuggees/CrossModule/Lib/Types.cs Adds library types used by CrossModule debuggee to force cross-module resolution.
src/native/managed/cdac/tests/StressTests/Debuggees/CrossModule/Lib/CrossModuleLib.csproj Adds library project for CrossModule debuggee.
src/native/managed/cdac/tests/StressTests/Debuggees/CrossModule/CrossModule.csproj Adds main CrossModule debuggee project file + ProjectReference wiring.
src/native/managed/cdac/tests/StressTests/Debuggees/CallSignatures/Program.cs Adds comprehensive ARGITER signature-shape coverage debuggee.
src/native/managed/cdac/tests/StressTests/Debuggees/CallSignatures/CallSignatures.csproj Adds CallSignatures debuggee project file and warning suppressions.
src/native/managed/cdac/tests/StressTests/CdacStressTests.cs Replaces prior basic test entrypoint with expanded debuggee catalog and GCREFS/ARGITER theories.
src/native/managed/cdac/tests/StressTests/CdacStressTestBase.cs Adds per-mode stress runner and stricter assertions/target detection.
src/native/managed/cdac/tests/StressTests/CdacStressResults.cs Adds parsing of [GC_STATS] / [ARG_STATS] and captures ARGITER divergence lines.
src/native/managed/cdac/tests/StressTests/BasicCdacStressTests.cs Removes the older stress test harness class (superseded by CdacStressTests).
src/native/managed/cdac/tests/DumpTests/StackReferenceDumpTests.cs Removes x86 skip now that x86 GCInfo decoding is supported.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/StressTestApi/CdacStressApi.cs Adds legacy DAC request handlers for cdacstress private opcodes, including ARGITER blob request.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.IXCLRDataProcess.cs Routes stress private requests to the new CdacStressApi helper.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodTableFlags_1.cs Extends MethodTable flags helpers (shared instantiation + byreflike).
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Microsoft.Diagnostics.DataContractReader.Contracts.csproj Links in shared CallingConvention/ArgIterator sources into Contracts build.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/DataType.cs Adds PInvokeCalliFrame to the cDAC type discriminator enum.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/TransitionBlock.cs Adds OffsetOfArgs field address to support correct x86 GCRefMap pos->addr mapping.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/PInvokeCalliFrame.cs Adds cDAC data type to expose VASigCookiePtr for PInvokeCalliFrame.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/CoreCLRContracts.cs Registers the new ICallingConvention contract implementation.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.cs Swallows NotImplementedException per-frame to allow partial stack-walk results.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanner.cs Reworks caller-stack scanning to use synthesized GCRefMap blobs on Windows x86/x64; factors shared token iteration; fixes x86 DynamicHelperFrame arg-reg offsets; corrects x86 pos->addr mapping.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GCRefMapDecoder.cs Adds a byte[]-backed decoder constructor for host-synthesized blobs.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/X86FrameHandler.cs Computes x86 transition-frame caller SP by decoding stack-pop prefix (or VASigCookie for PInvokeCalliFrame).
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs Implements new RTS helpers (byreflike, unboxing stub, approx field type handle).
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCTransition.cs Fixes ctor assignments for IsThis/Iptr fields.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCArgTable.cs Fixes several x86 arg table decoding issues and removes debug prints; adjusts stack-depth transition deltas.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CallingConvention/CdacTypeHandle.cs Adds cDAC-backed ITypeHandle implementation for shared ArgIterator.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CallingConvention/ArgumentLocation.cs Adds internal argument-location record for encoder bookkeeping.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs Adds new contract APIs (byreflike, unboxing stub, approx field type handle).
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ICallingConvention.cs Introduces new calling-convention contract + public surface.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs Exposes CallingConvention contract property on registry.
src/coreclr/vm/frames.h Exposes PInvokeCalliFrame::m_pVASigCookie via cdac_data specialization.
src/coreclr/vm/datadescriptor/datadescriptor.inc Adds PInvokeCalliFrame descriptor and registers CallingConvention global contract.
src/coreclr/vm/cdacstress.cpp Adds WHAT/WHERE/MODIFIER flag regions, ARGITER sub-check (runtime-vs-cDAC blob comparison), and emits [GC_STATS]/[ARG_STATS].
src/coreclr/tools/Common/CallingConvention/TransitionBlock.cs Moves TransitionBlock into Internal.CallingConvention and refactors to use ITypeHandle.
src/coreclr/tools/Common/CallingConvention/SystemVAmd64PassingDescriptor.cs Adds extracted SystemV descriptor types for shared use.
src/coreclr/tools/Common/CallingConvention/ITypeHandle.cs Adds shared type abstraction for calling convention computation.
src/coreclr/tools/Common/CallingConvention/FpStructInRegistersInfo.cs Adds extracted RISC-V/LoongArch FP struct classification types for shared use.
src/coreclr/tools/Common/CallingConvention/ArgIterator.cs Refactors ArgIterator into Internal.CallingConvention and reworks it to use ITypeHandle + shared TransitionBlock.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/WasmLowering.ReadyToRun.cs Updates Wasm lowering code to use new ITypeHandle shape.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj Links in shared CallingConvention sources and adjusts ReadyToRun file list.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmR2RToInterpreterThunkNode.cs Updates using/aliasing for new ArgIterator namespace.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmInterpreterToR2RThunkNode.cs Updates using/aliasing for new ArgIterator namespace.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmImportThunk.cs Updates using/aliasing for new ArgIterator namespace.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TypeHandle.cs Adds crossgen2-backed ITypeHandle implementation.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/GCRefMapBuilder.cs Updates builder to use shared TransitionBlock/ArgIterator/ITypeHandle.
src/coreclr/inc/dacprivate.h Adds new private request opcode and request struct for ARGITER blob retrieval (CDAC_STRESS).
src/coreclr/inc/clrconfigvalues.h Simplifies CdacStress config description now that layout is more complex.
eng/pipelines/runtime-diagnostics.yml Adds windows_x86 to runtime-diagnostics pipeline parameter default list.
docs/design/datacontracts/RuntimeTypeSystem.md Documents new RTS APIs and flags.
docs/design/datacontracts/GCInfo.md Updates x86 behavior documentation and adds x86 specifics section.
docs/design/datacontracts/CallingConvention.md Adds new CallingConvention contract design doc.

Comment on lines +8 to +14
public interface ICallingConvention : IContract
{
static string IContract.Name => nameof(CallingConvention);

bool TryComputeArgGCRefMapBlob(MethodDescHandle methodDesc, out byte[] blob)
=> throw new NotImplementedException();
}
Comment on lines +65 to +66
if (inBuffer is null || inSize < (uint)Unsafe.SizeOf<DacStressArgGCRefMapRequest>())
return HResults.E_INVALIDARG;
Comment on lines +1 to +9
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<LangVersion>latest</LangVersion>
<!-- This debuggee deliberately defines many "test scaffolding" types and
enums where StyleCop's one-value-per-line / no-unassigned-fields
conventions don't add value. Extend the parent NoWarn. -->
<NoWarn>$(NoWarn);SA1136;CS0649</NoWarn>
</PropertyGroup>
</Project>
Comment on lines +62 to +86
public int GetSize()
{
// Constructed pointer/array/byref args always occupy one TADDR slot
// in the transition block (the actual pointee is reached via the
// pointer value, not stored inline). When _kindOverride is set, the
// underlying TypeHandle may be null (uncached PTR), so GetBaseSize
// would fault.
if (_kindOverride is CdacCorElementType.Ptr
or CdacCorElementType.Byref
or CdacCorElementType.SzArray
or CdacCorElementType.Array)
{
return PointerSize;
}

if (_typeHandle.IsNull)
return 0;

// GetBaseSize returns the full object size including object header and padding.
// For value types used in calling convention, we need the unboxed size.
// BaseSize = ObjHeader + MethodTable* + unboxed fields, aligned to pointer size.
// Unboxed size = BaseSize - 2 * PointerSize (subtract ObjHeader + MT pointer).
uint baseSize = Rts.GetBaseSize(_typeHandle);
return (int)(baseSize - (uint)(2 * PointerSize));
}

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

Comment on lines +167 to +169
/// the test. On other targets we still tolerate KnownIssues because
/// <c>PromoteCallerStack</c> falls back to <c>RecordDeferredFrame</c>
/// there (see <c>GcScanner.PromoteCallerStack</c>).
Comment on lines +358 to +382
IRuntimeInfo runtimeInfo = _target.Contracts.RuntimeInfo;
RuntimeInfoArchitecture arch = runtimeInfo.GetTargetArchitecture();
bool supportedByCallingConvention =
runtimeInfo.GetTargetOperatingSystem() == RuntimeInfoOperatingSystem.Windows
&& arch is RuntimeInfoArchitecture.X86 or RuntimeInfoArchitecture.X64;

if (!supportedByCallingConvention)
{
scanContext.RecordDeferredFrame(frameAddress);
return;
}

Data.FramedMethodFrame fmf = _target.ProcessedData.GetOrAdd<Data.FramedMethodFrame>(frameAddress);
if (fmf.MethodDescPtr == TargetPointer.Null)
{
scanContext.RecordDeferredFrame(frameAddress);
return;
}

MethodDescHandle md = _target.Contracts.RuntimeTypeSystem.GetMethodDescHandle(fmf.MethodDescPtr);
if (!_target.Contracts.CallingConvention.TryComputeArgGCRefMapBlob(md, out byte[] blob) || blob is null || blob.Length == 0)
{
scanContext.RecordDeferredFrame(frameAddress);
return;
}
@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.

@max-charlamb max-charlamb force-pushed the x86-gc-info-w-argiterator branch from 2869d31 to 2c648d9 Compare June 27, 2026 21:29
@max-charlamb max-charlamb changed the title [cdac] Wire PromoteCallerStack to ICallingConvention; enforce 0 known issues on windows-x64 [cdac] x86 GCInfo decoder + PromoteCallerStack -> GCRefMap; enforce 0 known issues on windows-x86/x64 Jun 27, 2026
- Add x86 IGCInfoDecoder (EnumerateLiveSlots, GetInterruptibleRanges)
- Rewire GcScanner.PromoteCallerStack to use ICallingConvention.TryComputeArgGCRefMapBlob
- Centralize x86 cbStackPop handling inside EnumerateGCRefMapTokens
- Encapsulate x86 ENUM_ARGUMENT_REGISTERS_BACKWARD layout in ArgSlotAddress helper
- GCRefMapDecoder: add byte[] ctor for synthesized blobs + MemberNotNullWhen attrs
- TransitionBlock.OffsetOfArgs uses [InstanceDataStart] (drop redundant datadescriptor field)
- Add PInvokeCalliFrame data class + datadescriptor wiring for X86FrameHandler fallback
- Stress tests: enforce KnownIssues == 0 on Windows x86/x64, remove x86 skip from GCRefStress

Verified: x86 + x64 GCRefStress 11/11 pass, 0 known issues.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 28, 2026 20:12
@max-charlamb max-charlamb force-pushed the x86-gc-info-w-argiterator branch from 2c648d9 to e335d6a Compare June 28, 2026 20:12

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 15 out of 15 changed files in this pull request and generated 6 comments.

Comment on lines 58 to 63
if (debuggee.SkipGCRefs)
throw new SkipTestException($"{debuggee.Name} is excluded from GCREFS pending follow-up work.");

// The GCREFS sub-check has only been validated on architectures where
// the cDAC GC root enumeration is at parity with the runtime. x86 has
// not been brought up yet (a separate effort); skip there until it is.
if (arch == Architecture.X86)
throw new SkipTestException("GCREFS stress is not yet validated on x86 (ARGITER stress runs there instead)");

CdacStressResults results = await RunGCRefStressAsync(debuggee.Name);
AssertAllPassed(results, debuggee.Name);
}
Comment on lines 58 to 63
if (debuggee.SkipGCRefs)
throw new SkipTestException($"{debuggee.Name} is excluded from GCREFS pending follow-up work.");

// The GCREFS sub-check has only been validated on architectures where
// the cDAC GC root enumeration is at parity with the runtime. x86 has
// not been brought up yet (a separate effort); skip there until it is.
if (arch == Architecture.X86)
throw new SkipTestException("GCREFS stress is not yet validated on x86 (ARGITER stress runs there instead)");

CdacStressResults results = await RunGCRefStressAsync(debuggee.Name);
AssertAllPassed(results, debuggee.Name);
}
Comment on lines +196 to +202
$"GCREFS stress test '{debuggeeName}' had {results.KnownIssues} known issue(s) " +
$"out of {results.TotalVerifications} verifications. " +
"Windows x86 / x64 do not accept any deferred frames in this PR's scope -- " +
"every transition Frame's caller-stack scan must succeed via the shared " +
"ICallingConvention.TryComputeArgGCRefMapBlob path. A non-zero count likely " +
"indicates the encoder declined a method it previously handled (regression " +
"in CallingConvention_1.ComputeArgGCRefMapBlobCore).\n" +
Comment on lines 359 to 363
for (int i = 0; i < callPndTabCnt; i++)
{
uint pndOffs = _target.GCDecodeUnsigned(ref offset);

uint stkOffs = val & ~byref_OFFSET_FLAG;
uint lowBit = val & byref_OFFSET_FLAG;
Console.WriteLine($"stkOffs: {stkOffs}, lowBit: {lowBit}");

transition.PtrArgs.Add(new GcTransitionCall.PtrArg(pndOffs, 0));
}
Comment on lines +328 to +335
bool isEbpRelative = Header.EbpFrame;
if (Header.DoubleAlign &&
(uint)stkOffs >= _target.PointerSize * (Header.FrameSize + calleeSavedRegsCount))
{
// Double-aligned frame: offsets above the frame proper are EBP-relative.
isEbpRelative = true;
stkOffs -= (int)(_target.PointerSize * (Header.FrameSize + calleeSavedRegsCount));
}
Comment on lines 178 to 182
ExternalMethodFrame,
DynamicHelperFrame,
InterpreterFrame,
PInvokeCalliFrame,

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.

2 participants