Skip to content

[cdac] SystemV-AMD64 struct-in-register classifier: enable ARGITER + zero-known-issues on Linux x64 - #130257

Merged
max-charlamb merged 7 commits into
dotnet:mainfrom
max-charlamb:cdac-sysv-classifier
Jul 8, 2026
Merged

[cdac] SystemV-AMD64 struct-in-register classifier: enable ARGITER + zero-known-issues on Linux x64#130257
max-charlamb merged 7 commits into
dotnet:mainfrom
max-charlamb:cdac-sysv-classifier

Conversation

@max-charlamb

@max-charlambmax-charlamb commented Jul 6, 2026

Copy link
Copy Markdown
Member

Fills in the SystemV-AMD64 struct-in-register classification path in the cDAC's ArgIterator port so ICallingConvention.TryComputeArgGCRefMapBlob produces correct output on Unix x64. Lights up Linux x64 for both the ARGITER stress sub-check and the zero-known-issues GCREFS assertion.

Follow-up to #130090. Advances the platform matrix tracked in #130008.

Approach

The runtime already pre-computes the eightbyte classification for every enregisterable managed value type at MethodTable construction time and caches it in EEClass::m_eightByteRegistersInfo (inside EEClassOptionalFields). Runtime callers -- ArgIterator, getSystemVAmd64PassStructInRegisterDescriptor -- read that cached info via SystemVRegDescriptorFromSystemVEightByteRegistersInfo rather than re-running ClassifyEightBytes. The cDAC now does the same.

Non-enregisterable structs (too large, or classified as SSEUP/X87/Memory) never reach StoreEightByteClassification, so their descriptor reads back with NumEightBytes == 0. That's the natural passedInRegisters = false signal the cDAC surfaces. Native P/Invoke layouts are not cached here (the runtime classifies them on demand) -- the cDAC never walks native transition frames for GC scanning, so this is fine.

Because the stress harness compares against ComputeCallRefMap -- which uses the runtime path -- reading the same cached info gives byte-for-byte parity by construction. No merge-rule / intrinsic-rejection / Int128 divergence to reason about.

macOS x64 uses the same SystemV path and works the same way if run locally, but we don't add osx_x64 to the Helix stress matrix in this PR.

Descriptor plumbing

C++:

  • class.h: cdac_data<EEClass>::OptionalFields; cdac_data<EEClassOptionalFields> (UNIX_AMD64_ABI-guarded) exposing the EightByteRegistersInfo offset. Friend declaration on EEClassOptionalFields.
  • methodtable.h: cdac_data<SystemVEightByteRegistersInfo> for NumEightBytes plus the two byte[2] eightbyte tables. Friend struct.
  • datadescriptor.inc: EEClass.OptionalFields field + new EEClassOptionalFields and SystemVEightByteRegistersInfo types, both under #ifdef UNIX_AMD64_ABI.

Managed:

  • DataType: new EEClassOptionalFields and SystemVEightByteRegistersInfo entries.
  • Data.EEClass: adds OptionalFields.
  • Data.EEClassOptionalFields: [Field] SystemVEightByteRegistersInfo inline sub-IData.
  • Data.SystemVEightByteRegistersInfo: [Field] NumEightBytes, OnInit-populated arrays.
  • CdacTypeHandle.GetSystemVAmd64PassStructInRegisterDescriptor: ~40 LOC inline. Reads EEClass -> OptionalFields -> EightByteRegistersInfo, then projects into SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR with eightByteOffsets synthesized as i * 8 (matches SystemVRegDescriptorFromSystemVEightByteRegistersInfo).

Test coverage

CallSignatures debuggee gains a SysVCategory exercising:

  • Single-eightbyte shapes: IntPair (Integer), FloatPair (SSE), IntFloat (merged Integer), SingleRef (IntegerReference)
  • Two-eightbyte shapes: LongLong, LongDouble, DoubleDouble, RefInt
  • Nested value type, ByRefLike (Span<byte>)
  • Rejection / stack-passed: 24-byte 3-ref struct, Vector128<int>, empty struct
  • Int128Wrapper (2 Integer eightbytes -- register-passed; runtime does NOT reject by name)
  • Unions (LayoutKind.Explicit), sub-eightbyte trailer, single-field wrapper

Same debuggee runs everywhere; only exercises the new descriptor read on Unix x64 (Windows targets don't emit the descriptor entries, so the classifier returns passedInRegisters = false immediately via a TryGetTypeInfo probe).

AssertAllPassed now folds KnownIssues into the Failed check unconditionally -- no more platform gate on the assertion side; any surviving known-issue count is a regression.

Local validation

  • dotnet build src/native/managed/cdac/cdac.slnx -c Release: clean
  • Unit tests: 2646 pass / 0 fail / 16 skipped
  • Windows x64 local stress smoke (RunStressTests.ps1):
    • GCREFS CallSignatures: 6150 / 6150 pass, 0 known-issue
    • ARGITER CallSignatures (with the SysVCategory): 383 pass / 0 fail / 0 skip / 0 error

The descriptor entries are UNIX_AMD64_ABI-only, so the descriptor-read path only activates on Linux/macOS x64 targets; no regression on any pre-existing platform. CI validates linux-x64 end-to-end via the byte-for-byte ARGITER comparison against runtime ComputeCallRefMap.

Note

This change was authored with assistance from GitHub Copilot.

CopilotAI 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

Note

Copilot couldn't run its full agentic review because no GitHub Actions runner was available. Make sure your repository has a runner available to run Copilot's review, or add a copilot-setup-steps.yml file specifying one with the runs-on attribute. See the docs for more details.

Enables SystemV-AMD64 struct-in-register classification for cDAC ArgIterator so GCRefMap generation matches runtime behavior on Unix x64, and flips related stress/GCREFS gates to an inverse “unsupported platforms” list.

Changes:

  • Add Unix x64 SystemV-AMD64 eightbyte struct classifier and wire it into CdacTypeHandle.
  • Expand CallSignatures stress debuggee with SysV-focused signature shapes.
  • Simplify platform gating logic by skipping only remaining unsupported architectures (RiscV64/LoongArch64/Wasm).

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 5 comments.

Show a summary per file
FileDescription
src/native/managed/cdac/tests/StressTests/Debuggees/CallSignatures/Program.csAdds SysV category signatures to exercise Unix x64 struct-in-register classification.
src/native/managed/cdac/tests/StressTests/CdacStressTests.csSwitches ARGITER stress gate to an inverse unsupported-arch list.
src/native/managed/cdac/tests/StressTests/CdacStressTestBase.csSwitches zero-known-issues assertion gate to an inverse unsupported-arch list.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanner.csAligns deferred-frame gating with new inverse unsupported-arch list.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.csAdds IsIntrinsicType contract impl, refactors intrinsic vector detection, bumps HFA recursion cap.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CallingConvention/SystemVStructClassifier.csIntroduces SystemV-AMD64 struct-in-register classifier implementation.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CallingConvention/CdacTypeHandle.csPlumbs the new classifier into the calling-convention surface.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.csAdds IsIntrinsicType(TypeHandle) to the contract.
docs/design/datacontracts/RuntimeTypeSystem.mdDocuments the new IsIntrinsicType API and its implementation.

@max-charlamb
max-charlambforce-pushed the cdac-sysv-classifier branch from fa16cf5 to 7460a2cCompareJuly 6, 2026 22:16
CopilotAI review requested due to automatic review settings July 6, 2026 22:36
@max-charlamb
max-charlambforce-pushed the cdac-sysv-classifier branch from 7460a2c to 00dc8d2CompareJuly 6, 2026 22:36

CopilotAI 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 9 out of 9 changed files in this pull request and generated 4 comments.

@max-charlamb
max-charlambforce-pushed the cdac-sysv-classifier branch from 00dc8d2 to b9a2860CompareJuly 6, 2026 22:49
CopilotAI review requested due to automatic review settings July 6, 2026 22:54
@max-charlamb
max-charlambforce-pushed the cdac-sysv-classifier branch from b9a2860 to aa33f63CompareJuly 6, 2026 22:54

CopilotAI 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 9 out of 9 changed files in this pull request and generated 3 comments.

Comment threadsrc/native/managed/cdac/tests/StressTests/CdacStressTestBase.cs Outdated
CopilotAI review requested due to automatic review settings July 6, 2026 23:36

CopilotAI 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 9 out of 9 changed files in this pull request and generated 2 comments.

@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 pushed a commit to max-charlamb/runtime that referenced this pull request Jul 7, 2026
Per David Wrighton's suggestion on PR dotnet#130257: instead of porting the
~430 LOC SystemV-AMD64 struct-in-register classifier from
crossgen2/methodtable.cpp, read the pre-classified data that the runtime
already caches on EEClass::m_eightByteRegistersInfo.
The runtime's MethodTableBuilder::StoreEightByteClassification (called
from SystemVAmd64CheckForPassStructInRegister at MethodTable build time)
populates m_eightByteRegistersInfo inside EEClassOptionalFields with the
enregisterable-struct classification: NumEightBytes, per-eightbyte
classification tags, per-eightbyte sizes. Callers (arg iterator, JIT-EE
interface) read that cached info via
SystemVRegDescriptorFromSystemVEightByteRegistersInfo instead of
re-running ClassifyEightBytes. The cDAC does the same.
Non-enregisterable structs (too large, or classified as SSEUP/X87/Memory)
never reach StoreEightByteClassification, so their m_eightByteRegistersInfo
reads back with NumEightBytes == 0. That's the natural
"passedInRegisters = false" signal the cDAC surfaces.
Descriptor plumbing:
* class.h: cdac_data<EEClass>::OptionalFields; cdac_data<EEClassOptionalFields>
(UNIX_AMD64_ABI-guarded, includes the EightByteRegistersInfo offset).
* methodtable.h: cdac_data<SystemVEightByteRegistersInfo> for NumEightBytes
plus the two byte[2] arrays; friend struct on the class so offsetof can
reach the private fields.
* datadescriptor.inc: EEClass.OptionalFields field + new
EEClassOptionalFields and SystemVEightByteRegistersInfo types, both
under #ifdef UNIX_AMD64_ABI.
Managed side:
* DataType: new EEClassOptionalFields and SystemVEightByteRegistersInfo entries.
* Data.EEClass: OptionalFields TargetPointer field.
* Data.EEClassOptionalFields: [Field] SystemVEightByteRegistersInfo inline sub-IData.
* Data.SystemVEightByteRegistersInfo: [Field] NumEightBytes; OnInit-populated
arrays for the two eightbyte tables.
* CdacTypeHandle.GetSystemVAmd64PassStructInRegisterDescriptor: reads
EEClass -> OptionalFields -> EightByteRegistersInfo directly (~40 LOC inline;
the standalone SystemVStructClassifier.cs file is deleted).
* MockDescriptors.RuntimeTypeSystem: mock EEClass layout gains OptionalFields
so unit tests build the same shape as the runtime.
Advantages vs the classifier port:
* Byte-for-byte matches ComputeCallRefMap by construction (the stress
harness compares against the runtime path).
* No more crossgen2-vs-runtime Int128/UInt128 divergence to reason about --
the classifier just doesn't run.
* ~371 LOC net removed.
* No maintenance burden as the runtime classifier evolves.
Only activates when the target runtime is UNIX_AMD64_ABI; on other
runtimes the descriptor entries are ifdef'd out and the classifier
returns "not passed in registers" immediately via the TryGetTypeInfo
probe.
Windows unit tests: all 2646 cDAC tests still pass.
Linux x64 stress: TBD (pushed for CI validation).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings July 7, 2026 19:25
@max-charlamb
max-charlambforce-pushed the cdac-sysv-classifier branch from 252c8b1 to 3423ebcCompareJuly 7, 2026 19:25
@max-charlamb
max-charlambforce-pushed the cdac-sysv-classifier branch from 3423ebc to 2b4cd5fCompareJuly 7, 2026 19:35

CopilotAI 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 14 out of 14 changed files in this pull request and generated 2 comments.

Rather than reading the two eightbyte tables as byte[] arrays via
ReadBuffer (indexed only by NumEightBytes, which is read from target
memory without validation), split each table into two explicitly-named
[Field] properties: EightByteClassification0/1 and EightByteSize0/1.
Two benefits:
1. The Data class is fully declarative -- no OnInit, no arrays. The
source generator emits an ordinary two-uint8 read per slot, so
there's no code path that could index past slot 1 regardless of
what NumEightBytes says.
2. Any consumer keyed off descriptor.eightByteCount is now safe against
a corrupted / mis-read NumEightBytes: the previously Debug.Assert-only
check in CdacTypeHandle is now a runtime guard that treats
NumEightBytes > 2 as "not passed in registers", so downstream loops
(e.g. ArgIterator, TransitionBlock) can't overrun a two-slot descriptor.
Also adds a static_assert in methodtable.h that
CLR_SYSTEMV_MAX_EIGHTBYTES_COUNT_TO_PASS_IN_REGISTERS is still 2, so if
the runtime ever grows the descriptor the cDAC-side split gets a build
break rather than silently truncating.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

CopilotAI 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 16 out of 16 changed files in this pull request and generated 2 comments.

CopilotAI review requested due to automatic review settings July 8, 2026 01:40
* GcScanner.PromoteCallerStack: reinstate a cheap arch/OS support gate
(now including Unix x64) so unsupported targets go straight to
RecordDeferredFrame instead of hitting the NotImplementedException
fallback inside CallingConvention_1.TryComputeArgGCRefMapBlob. Avoids
exception-driven control flow (and first-chance exceptions under
debuggers) on RiscV64 / LoongArch64 / Wasm targets. Tracks the same
supported-platform list as the runtime stress gate; TODO points at
dotnet#130008 for extending coverage.
* CdacTypeHandle: reuse
SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR.SYSTEMV_EIGHT_BYTE_SIZE_IN_BYTES
instead of a locally-defined 8 constant.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

CopilotAI 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 16 out of 16 changed files in this pull request and generated 3 comments.

Comment threadsrc/coreclr/vm/methodtable.h
CopilotAI review requested due to automatic review settings July 8, 2026 01:48

CopilotAI 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 16 out of 16 changed files in this pull request and generated 2 comments.

* DataType: move EEClassOptionalFields / SystemVEightByteRegistersInfo
back next to EEClass so type-system entries stay grouped together.
* CallingConvention_1.ComputeArgGCRefMapBlobCore: hoist the shared
TransitionBlock to the top of the method so the SysV struct-in-regs
branch and the pos/offset conversion loop reuse the same instance.
* CallingConvention_1.GetArgumentLayout: unwrap the ArgLocDesc? at the
call site with a throw-on-null instead of dereferencing Value later;
makes the invariant explicit at the point it's required.
* GcScanner.PromoteCallerStack: drop the arch/OS supported-target gate
again -- TryComputeArgGCRefMapBlob's own NotImplementedException path
is the sole decline point.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings July 8, 2026 14:27

CopilotAI 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 16 out of 16 changed files in this pull request and generated 2 comments.

The Debug.Assert this using existed for was replaced with a throw-on-null
in a prior commit; drop the now-unused import.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings July 8, 2026 14:52

CopilotAI 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 16 out of 16 changed files in this pull request and generated 2 comments.

@max-charlamb
max-charlamb enabled auto-merge (squash) July 8, 2026 22:22
@max-charlamb
max-charlamb merged commit 2269602 into dotnet:mainJul 8, 2026
132 of 136 checks passed
@max-charlamb
max-charlamb deleted the cdac-sysv-classifier branch July 8, 2026 23:36
@dotnet-milestone-botdotnet-milestone-botBot added this to the 11.0-preview7 milestone Jul 10, 2026
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
…zero-known-issues on Linux x64 (#130257)
Fills in the SystemV-AMD64 struct-in-register classification path in the
cDAC's ArgIterator port so
`ICallingConvention.TryComputeArgGCRefMapBlob` produces correct output
on Unix x64. Lights up **Linux x64** for both the ARGITER stress
sub-check and the zero-known-issues GCREFS assertion.
Follow-up to #130090. Advances the platform matrix tracked in #130008.
## Approach
The runtime already pre-computes the eightbyte classification for every
enregisterable managed value type at `MethodTable` construction time and
caches it in `EEClass::m_eightByteRegistersInfo` (inside
`EEClassOptionalFields`). Runtime callers -- `ArgIterator`,
`getSystemVAmd64PassStructInRegisterDescriptor` -- read that cached info
via `SystemVRegDescriptorFromSystemVEightByteRegistersInfo` rather than
re-running `ClassifyEightBytes`. The cDAC now does the same.
Non-enregisterable structs (too large, or classified as
`SSEUP`/`X87`/`Memory`) never reach `StoreEightByteClassification`, so
their descriptor reads back with `NumEightBytes == 0`. That's the
natural `passedInRegisters = false` signal the cDAC surfaces. Native
P/Invoke layouts are not cached here (the runtime classifies them on
demand) -- the cDAC never walks native transition frames for GC
scanning, so this is fine.
Because the stress harness compares against `ComputeCallRefMap` -- which
uses the runtime path -- reading the same cached info gives
byte-for-byte parity by construction. No merge-rule /
intrinsic-rejection / `Int128` divergence to reason about.
macOS x64 uses the same SystemV path and works the same way if run
locally, but we don't add `osx_x64` to the Helix stress matrix in this
PR.
## Descriptor plumbing
**C++:**
- `class.h`: `cdac_data<EEClass>::OptionalFields`;
`cdac_data<EEClassOptionalFields>` (UNIX_AMD64_ABI-guarded) exposing the
`EightByteRegistersInfo` offset. Friend declaration on
`EEClassOptionalFields`.
- `methodtable.h`: `cdac_data<SystemVEightByteRegistersInfo>` for
`NumEightBytes` plus the two `byte[2]` eightbyte tables. Friend struct.
- `datadescriptor.inc`: `EEClass.OptionalFields` field + new
`EEClassOptionalFields` and `SystemVEightByteRegistersInfo` types, both
under `#ifdef UNIX_AMD64_ABI`.
**Managed:**
- `DataType`: new `EEClassOptionalFields` and
`SystemVEightByteRegistersInfo` entries.
- `Data.EEClass`: adds `OptionalFields`.
- `Data.EEClassOptionalFields`: `[Field] SystemVEightByteRegistersInfo`
inline sub-IData.
- `Data.SystemVEightByteRegistersInfo`: `[Field] NumEightBytes`,
`OnInit`-populated arrays.
- `CdacTypeHandle.GetSystemVAmd64PassStructInRegisterDescriptor`: ~40
LOC inline. Reads `EEClass -> OptionalFields -> EightByteRegistersInfo`,
then projects into `SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR`
with `eightByteOffsets` synthesized as `i * 8` (matches
`SystemVRegDescriptorFromSystemVEightByteRegistersInfo`).
## Test coverage
`CallSignatures` debuggee gains a `SysVCategory` exercising:
- Single-eightbyte shapes: `IntPair` (Integer), `FloatPair` (SSE),
`IntFloat` (merged Integer), `SingleRef` (IntegerReference)
- Two-eightbyte shapes: `LongLong`, `LongDouble`, `DoubleDouble`,
`RefInt`
- Nested value type, ByRefLike (`Span<byte>`)
- Rejection / stack-passed: 24-byte 3-ref struct, `Vector128<int>`,
empty struct
- `Int128Wrapper` (2 Integer eightbytes -- register-passed; runtime does
NOT reject by name)
- Unions (`LayoutKind.Explicit`), sub-eightbyte trailer, single-field
wrapper
Same debuggee runs everywhere; only exercises the new descriptor read on
Unix x64 (Windows targets don't emit the descriptor entries, so the
classifier returns `passedInRegisters = false` immediately via a
`TryGetTypeInfo` probe).
`AssertAllPassed` now folds `KnownIssues` into the `Failed` check
unconditionally -- no more platform gate on the assertion side; any
surviving known-issue count is a regression.
## Local validation
- `dotnet build src/native/managed/cdac/cdac.slnx -c Release`: clean
- Unit tests: 2646 pass / 0 fail / 16 skipped
- **Windows x64 local stress smoke** (`RunStressTests.ps1`):
- GCREFS `CallSignatures`: 6150 / 6150 pass, 0 known-issue
- ARGITER `CallSignatures` (with the `SysVCategory`): 383 pass / 0 fail
/ 0 skip / 0 error
The descriptor entries are `UNIX_AMD64_ABI`-only, so the descriptor-read
path only activates on Linux/macOS x64 targets; no regression on any
pre-existing platform. CI validates `linux-x64` end-to-end via the
byte-for-byte ARGITER comparison against runtime `ComputeCallRefMap`.
> [!NOTE]
> This change was authored with assistance from GitHub Copilot.
---------
Co-authored-by: Max Charlamb <maxcharlamb@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Aug 10, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@max-charlamb@davidwrighton
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
[cdac] SystemV-AMD64 struct-in-register classifier: enable ARGITER + zero-known-issues on Linux x64 by max-charlamb · Pull Request #130257 · dotnet/runtime · GitHub
Skip to content

[cdac] SystemV-AMD64 struct-in-register classifier: enable ARGITER + zero-known-issues on Linux x64 - #130257

Merged
max-charlamb merged 7 commits into
dotnet:mainfrom
max-charlamb:cdac-sysv-classifier
Jul 8, 2026
Merged

[cdac] SystemV-AMD64 struct-in-register classifier: enable ARGITER + zero-known-issues on Linux x64#130257
max-charlamb merged 7 commits into
dotnet:mainfrom
max-charlamb:cdac-sysv-classifier

Conversation

@max-charlamb

@max-charlambmax-charlamb commented Jul 6, 2026

Copy link
Copy Markdown
Member

Fills in the SystemV-AMD64 struct-in-register classification path in the cDAC's ArgIterator port so ICallingConvention.TryComputeArgGCRefMapBlob produces correct output on Unix x64. Lights up Linux x64 for both the ARGITER stress sub-check and the zero-known-issues GCREFS assertion.

Follow-up to #130090. Advances the platform matrix tracked in #130008.

Approach

The runtime already pre-computes the eightbyte classification for every enregisterable managed value type at MethodTable construction time and caches it in EEClass::m_eightByteRegistersInfo (inside EEClassOptionalFields). Runtime callers -- ArgIterator, getSystemVAmd64PassStructInRegisterDescriptor -- read that cached info via SystemVRegDescriptorFromSystemVEightByteRegistersInfo rather than re-running ClassifyEightBytes. The cDAC now does the same.

Non-enregisterable structs (too large, or classified as SSEUP/X87/Memory) never reach StoreEightByteClassification, so their descriptor reads back with NumEightBytes == 0. That's the natural passedInRegisters = false signal the cDAC surfaces. Native P/Invoke layouts are not cached here (the runtime classifies them on demand) -- the cDAC never walks native transition frames for GC scanning, so this is fine.

Because the stress harness compares against ComputeCallRefMap -- which uses the runtime path -- reading the same cached info gives byte-for-byte parity by construction. No merge-rule / intrinsic-rejection / Int128 divergence to reason about.

macOS x64 uses the same SystemV path and works the same way if run locally, but we don't add osx_x64 to the Helix stress matrix in this PR.

Descriptor plumbing

C++:

  • class.h: cdac_data<EEClass>::OptionalFields; cdac_data<EEClassOptionalFields> (UNIX_AMD64_ABI-guarded) exposing the EightByteRegistersInfo offset. Friend declaration on EEClassOptionalFields.
  • methodtable.h: cdac_data<SystemVEightByteRegistersInfo> for NumEightBytes plus the two byte[2] eightbyte tables. Friend struct.
  • datadescriptor.inc: EEClass.OptionalFields field + new EEClassOptionalFields and SystemVEightByteRegistersInfo types, both under #ifdef UNIX_AMD64_ABI.

Managed:

  • DataType: new EEClassOptionalFields and SystemVEightByteRegistersInfo entries.
  • Data.EEClass: adds OptionalFields.
  • Data.EEClassOptionalFields: [Field] SystemVEightByteRegistersInfo inline sub-IData.
  • Data.SystemVEightByteRegistersInfo: [Field] NumEightBytes, OnInit-populated arrays.
  • CdacTypeHandle.GetSystemVAmd64PassStructInRegisterDescriptor: ~40 LOC inline. Reads EEClass -> OptionalFields -> EightByteRegistersInfo, then projects into SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR with eightByteOffsets synthesized as i * 8 (matches SystemVRegDescriptorFromSystemVEightByteRegistersInfo).

Test coverage

CallSignatures debuggee gains a SysVCategory exercising:

  • Single-eightbyte shapes: IntPair (Integer), FloatPair (SSE), IntFloat (merged Integer), SingleRef (IntegerReference)
  • Two-eightbyte shapes: LongLong, LongDouble, DoubleDouble, RefInt
  • Nested value type, ByRefLike (Span<byte>)
  • Rejection / stack-passed: 24-byte 3-ref struct, Vector128<int>, empty struct
  • Int128Wrapper (2 Integer eightbytes -- register-passed; runtime does NOT reject by name)
  • Unions (LayoutKind.Explicit), sub-eightbyte trailer, single-field wrapper

Same debuggee runs everywhere; only exercises the new descriptor read on Unix x64 (Windows targets don't emit the descriptor entries, so the classifier returns passedInRegisters = false immediately via a TryGetTypeInfo probe).

AssertAllPassed now folds KnownIssues into the Failed check unconditionally -- no more platform gate on the assertion side; any surviving known-issue count is a regression.

Local validation

  • dotnet build src/native/managed/cdac/cdac.slnx -c Release: clean
  • Unit tests: 2646 pass / 0 fail / 16 skipped
  • Windows x64 local stress smoke (RunStressTests.ps1):
    • GCREFS CallSignatures: 6150 / 6150 pass, 0 known-issue
    • ARGITER CallSignatures (with the SysVCategory): 383 pass / 0 fail / 0 skip / 0 error

The descriptor entries are UNIX_AMD64_ABI-only, so the descriptor-read path only activates on Linux/macOS x64 targets; no regression on any pre-existing platform. CI validates linux-x64 end-to-end via the byte-for-byte ARGITER comparison against runtime ComputeCallRefMap.

Note

This change was authored with assistance from GitHub Copilot.

CopilotAI 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

Note

Copilot couldn't run its full agentic review because no GitHub Actions runner was available. Make sure your repository has a runner available to run Copilot's review, or add a copilot-setup-steps.yml file specifying one with the runs-on attribute. See the docs for more details.

Enables SystemV-AMD64 struct-in-register classification for cDAC ArgIterator so GCRefMap generation matches runtime behavior on Unix x64, and flips related stress/GCREFS gates to an inverse “unsupported platforms” list.

Changes:

  • Add Unix x64 SystemV-AMD64 eightbyte struct classifier and wire it into CdacTypeHandle.
  • Expand CallSignatures stress debuggee with SysV-focused signature shapes.
  • Simplify platform gating logic by skipping only remaining unsupported architectures (RiscV64/LoongArch64/Wasm).

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 5 comments.

Show a summary per file
FileDescription
src/native/managed/cdac/tests/StressTests/Debuggees/CallSignatures/Program.csAdds SysV category signatures to exercise Unix x64 struct-in-register classification.
src/native/managed/cdac/tests/StressTests/CdacStressTests.csSwitches ARGITER stress gate to an inverse unsupported-arch list.
src/native/managed/cdac/tests/StressTests/CdacStressTestBase.csSwitches zero-known-issues assertion gate to an inverse unsupported-arch list.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanner.csAligns deferred-frame gating with new inverse unsupported-arch list.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.csAdds IsIntrinsicType contract impl, refactors intrinsic vector detection, bumps HFA recursion cap.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CallingConvention/SystemVStructClassifier.csIntroduces SystemV-AMD64 struct-in-register classifier implementation.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CallingConvention/CdacTypeHandle.csPlumbs the new classifier into the calling-convention surface.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.csAdds IsIntrinsicType(TypeHandle) to the contract.
docs/design/datacontracts/RuntimeTypeSystem.mdDocuments the new IsIntrinsicType API and its implementation.

@max-charlamb
max-charlambforce-pushed the cdac-sysv-classifier branch from fa16cf5 to 7460a2cCompareJuly 6, 2026 22:16
CopilotAI review requested due to automatic review settings July 6, 2026 22:36
@max-charlamb
max-charlambforce-pushed the cdac-sysv-classifier branch from 7460a2c to 00dc8d2CompareJuly 6, 2026 22:36

CopilotAI 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 9 out of 9 changed files in this pull request and generated 4 comments.

@max-charlamb
max-charlambforce-pushed the cdac-sysv-classifier branch from 00dc8d2 to b9a2860CompareJuly 6, 2026 22:49
CopilotAI review requested due to automatic review settings July 6, 2026 22:54
@max-charlamb
max-charlambforce-pushed the cdac-sysv-classifier branch from b9a2860 to aa33f63CompareJuly 6, 2026 22:54

CopilotAI 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 9 out of 9 changed files in this pull request and generated 3 comments.

Comment threadsrc/native/managed/cdac/tests/StressTests/CdacStressTestBase.cs Outdated
CopilotAI review requested due to automatic review settings July 6, 2026 23:36

CopilotAI 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 9 out of 9 changed files in this pull request and generated 2 comments.

@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 pushed a commit to max-charlamb/runtime that referenced this pull request Jul 7, 2026
Per David Wrighton's suggestion on PR dotnet#130257: instead of porting the
~430 LOC SystemV-AMD64 struct-in-register classifier from
crossgen2/methodtable.cpp, read the pre-classified data that the runtime
already caches on EEClass::m_eightByteRegistersInfo.
The runtime's MethodTableBuilder::StoreEightByteClassification (called
from SystemVAmd64CheckForPassStructInRegister at MethodTable build time)
populates m_eightByteRegistersInfo inside EEClassOptionalFields with the
enregisterable-struct classification: NumEightBytes, per-eightbyte
classification tags, per-eightbyte sizes. Callers (arg iterator, JIT-EE
interface) read that cached info via
SystemVRegDescriptorFromSystemVEightByteRegistersInfo instead of
re-running ClassifyEightBytes. The cDAC does the same.
Non-enregisterable structs (too large, or classified as SSEUP/X87/Memory)
never reach StoreEightByteClassification, so their m_eightByteRegistersInfo
reads back with NumEightBytes == 0. That's the natural
"passedInRegisters = false" signal the cDAC surfaces.
Descriptor plumbing:
* class.h: cdac_data<EEClass>::OptionalFields; cdac_data<EEClassOptionalFields>
(UNIX_AMD64_ABI-guarded, includes the EightByteRegistersInfo offset).
* methodtable.h: cdac_data<SystemVEightByteRegistersInfo> for NumEightBytes
plus the two byte[2] arrays; friend struct on the class so offsetof can
reach the private fields.
* datadescriptor.inc: EEClass.OptionalFields field + new
EEClassOptionalFields and SystemVEightByteRegistersInfo types, both
under #ifdef UNIX_AMD64_ABI.
Managed side:
* DataType: new EEClassOptionalFields and SystemVEightByteRegistersInfo entries.
* Data.EEClass: OptionalFields TargetPointer field.
* Data.EEClassOptionalFields: [Field] SystemVEightByteRegistersInfo inline sub-IData.
* Data.SystemVEightByteRegistersInfo: [Field] NumEightBytes; OnInit-populated
arrays for the two eightbyte tables.
* CdacTypeHandle.GetSystemVAmd64PassStructInRegisterDescriptor: reads
EEClass -> OptionalFields -> EightByteRegistersInfo directly (~40 LOC inline;
the standalone SystemVStructClassifier.cs file is deleted).
* MockDescriptors.RuntimeTypeSystem: mock EEClass layout gains OptionalFields
so unit tests build the same shape as the runtime.
Advantages vs the classifier port:
* Byte-for-byte matches ComputeCallRefMap by construction (the stress
harness compares against the runtime path).
* No more crossgen2-vs-runtime Int128/UInt128 divergence to reason about --
the classifier just doesn't run.
* ~371 LOC net removed.
* No maintenance burden as the runtime classifier evolves.
Only activates when the target runtime is UNIX_AMD64_ABI; on other
runtimes the descriptor entries are ifdef'd out and the classifier
returns "not passed in registers" immediately via the TryGetTypeInfo
probe.
Windows unit tests: all 2646 cDAC tests still pass.
Linux x64 stress: TBD (pushed for CI validation).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings July 7, 2026 19:25
@max-charlamb
max-charlambforce-pushed the cdac-sysv-classifier branch from 252c8b1 to 3423ebcCompareJuly 7, 2026 19:25
@max-charlamb
max-charlambforce-pushed the cdac-sysv-classifier branch from 3423ebc to 2b4cd5fCompareJuly 7, 2026 19:35

CopilotAI 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 14 out of 14 changed files in this pull request and generated 2 comments.

Rather than reading the two eightbyte tables as byte[] arrays via
ReadBuffer (indexed only by NumEightBytes, which is read from target
memory without validation), split each table into two explicitly-named
[Field] properties: EightByteClassification0/1 and EightByteSize0/1.
Two benefits:
1. The Data class is fully declarative -- no OnInit, no arrays. The
source generator emits an ordinary two-uint8 read per slot, so
there's no code path that could index past slot 1 regardless of
what NumEightBytes says.
2. Any consumer keyed off descriptor.eightByteCount is now safe against
a corrupted / mis-read NumEightBytes: the previously Debug.Assert-only
check in CdacTypeHandle is now a runtime guard that treats
NumEightBytes > 2 as "not passed in registers", so downstream loops
(e.g. ArgIterator, TransitionBlock) can't overrun a two-slot descriptor.
Also adds a static_assert in methodtable.h that
CLR_SYSTEMV_MAX_EIGHTBYTES_COUNT_TO_PASS_IN_REGISTERS is still 2, so if
the runtime ever grows the descriptor the cDAC-side split gets a build
break rather than silently truncating.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

CopilotAI 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 16 out of 16 changed files in this pull request and generated 2 comments.

CopilotAI review requested due to automatic review settings July 8, 2026 01:40
* GcScanner.PromoteCallerStack: reinstate a cheap arch/OS support gate
(now including Unix x64) so unsupported targets go straight to
RecordDeferredFrame instead of hitting the NotImplementedException
fallback inside CallingConvention_1.TryComputeArgGCRefMapBlob. Avoids
exception-driven control flow (and first-chance exceptions under
debuggers) on RiscV64 / LoongArch64 / Wasm targets. Tracks the same
supported-platform list as the runtime stress gate; TODO points at
dotnet#130008 for extending coverage.
* CdacTypeHandle: reuse
SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR.SYSTEMV_EIGHT_BYTE_SIZE_IN_BYTES
instead of a locally-defined 8 constant.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

CopilotAI 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 16 out of 16 changed files in this pull request and generated 3 comments.

Comment threadsrc/coreclr/vm/methodtable.h
CopilotAI review requested due to automatic review settings July 8, 2026 01:48

CopilotAI 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 16 out of 16 changed files in this pull request and generated 2 comments.

* DataType: move EEClassOptionalFields / SystemVEightByteRegistersInfo
back next to EEClass so type-system entries stay grouped together.
* CallingConvention_1.ComputeArgGCRefMapBlobCore: hoist the shared
TransitionBlock to the top of the method so the SysV struct-in-regs
branch and the pos/offset conversion loop reuse the same instance.
* CallingConvention_1.GetArgumentLayout: unwrap the ArgLocDesc? at the
call site with a throw-on-null instead of dereferencing Value later;
makes the invariant explicit at the point it's required.
* GcScanner.PromoteCallerStack: drop the arch/OS supported-target gate
again -- TryComputeArgGCRefMapBlob's own NotImplementedException path
is the sole decline point.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings July 8, 2026 14:27

CopilotAI 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 16 out of 16 changed files in this pull request and generated 2 comments.

The Debug.Assert this using existed for was replaced with a throw-on-null
in a prior commit; drop the now-unused import.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings July 8, 2026 14:52

CopilotAI 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 16 out of 16 changed files in this pull request and generated 2 comments.

@max-charlamb
max-charlamb enabled auto-merge (squash) July 8, 2026 22:22
@max-charlamb
max-charlamb merged commit 2269602 into dotnet:mainJul 8, 2026
132 of 136 checks passed
@max-charlamb
max-charlamb deleted the cdac-sysv-classifier branch July 8, 2026 23:36
@dotnet-milestone-botdotnet-milestone-botBot added this to the 11.0-preview7 milestone Jul 10, 2026
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
…zero-known-issues on Linux x64 (#130257)
Fills in the SystemV-AMD64 struct-in-register classification path in the
cDAC's ArgIterator port so
`ICallingConvention.TryComputeArgGCRefMapBlob` produces correct output
on Unix x64. Lights up **Linux x64** for both the ARGITER stress
sub-check and the zero-known-issues GCREFS assertion.
Follow-up to #130090. Advances the platform matrix tracked in #130008.
## Approach
The runtime already pre-computes the eightbyte classification for every
enregisterable managed value type at `MethodTable` construction time and
caches it in `EEClass::m_eightByteRegistersInfo` (inside
`EEClassOptionalFields`). Runtime callers -- `ArgIterator`,
`getSystemVAmd64PassStructInRegisterDescriptor` -- read that cached info
via `SystemVRegDescriptorFromSystemVEightByteRegistersInfo` rather than
re-running `ClassifyEightBytes`. The cDAC now does the same.
Non-enregisterable structs (too large, or classified as
`SSEUP`/`X87`/`Memory`) never reach `StoreEightByteClassification`, so
their descriptor reads back with `NumEightBytes == 0`. That's the
natural `passedInRegisters = false` signal the cDAC surfaces. Native
P/Invoke layouts are not cached here (the runtime classifies them on
demand) -- the cDAC never walks native transition frames for GC
scanning, so this is fine.
Because the stress harness compares against `ComputeCallRefMap` -- which
uses the runtime path -- reading the same cached info gives
byte-for-byte parity by construction. No merge-rule /
intrinsic-rejection / `Int128` divergence to reason about.
macOS x64 uses the same SystemV path and works the same way if run
locally, but we don't add `osx_x64` to the Helix stress matrix in this
PR.
## Descriptor plumbing
**C++:**
- `class.h`: `cdac_data<EEClass>::OptionalFields`;
`cdac_data<EEClassOptionalFields>` (UNIX_AMD64_ABI-guarded) exposing the
`EightByteRegistersInfo` offset. Friend declaration on
`EEClassOptionalFields`.
- `methodtable.h`: `cdac_data<SystemVEightByteRegistersInfo>` for
`NumEightBytes` plus the two `byte[2]` eightbyte tables. Friend struct.
- `datadescriptor.inc`: `EEClass.OptionalFields` field + new
`EEClassOptionalFields` and `SystemVEightByteRegistersInfo` types, both
under `#ifdef UNIX_AMD64_ABI`.
**Managed:**
- `DataType`: new `EEClassOptionalFields` and
`SystemVEightByteRegistersInfo` entries.
- `Data.EEClass`: adds `OptionalFields`.
- `Data.EEClassOptionalFields`: `[Field] SystemVEightByteRegistersInfo`
inline sub-IData.
- `Data.SystemVEightByteRegistersInfo`: `[Field] NumEightBytes`,
`OnInit`-populated arrays.
- `CdacTypeHandle.GetSystemVAmd64PassStructInRegisterDescriptor`: ~40
LOC inline. Reads `EEClass -> OptionalFields -> EightByteRegistersInfo`,
then projects into `SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR`
with `eightByteOffsets` synthesized as `i * 8` (matches
`SystemVRegDescriptorFromSystemVEightByteRegistersInfo`).
## Test coverage
`CallSignatures` debuggee gains a `SysVCategory` exercising:
- Single-eightbyte shapes: `IntPair` (Integer), `FloatPair` (SSE),
`IntFloat` (merged Integer), `SingleRef` (IntegerReference)
- Two-eightbyte shapes: `LongLong`, `LongDouble`, `DoubleDouble`,
`RefInt`
- Nested value type, ByRefLike (`Span<byte>`)
- Rejection / stack-passed: 24-byte 3-ref struct, `Vector128<int>`,
empty struct
- `Int128Wrapper` (2 Integer eightbytes -- register-passed; runtime does
NOT reject by name)
- Unions (`LayoutKind.Explicit`), sub-eightbyte trailer, single-field
wrapper
Same debuggee runs everywhere; only exercises the new descriptor read on
Unix x64 (Windows targets don't emit the descriptor entries, so the
classifier returns `passedInRegisters = false` immediately via a
`TryGetTypeInfo` probe).
`AssertAllPassed` now folds `KnownIssues` into the `Failed` check
unconditionally -- no more platform gate on the assertion side; any
surviving known-issue count is a regression.
## Local validation
- `dotnet build src/native/managed/cdac/cdac.slnx -c Release`: clean
- Unit tests: 2646 pass / 0 fail / 16 skipped
- **Windows x64 local stress smoke** (`RunStressTests.ps1`):
- GCREFS `CallSignatures`: 6150 / 6150 pass, 0 known-issue
- ARGITER `CallSignatures` (with the `SysVCategory`): 383 pass / 0 fail
/ 0 skip / 0 error
The descriptor entries are `UNIX_AMD64_ABI`-only, so the descriptor-read
path only activates on Linux/macOS x64 targets; no regression on any
pre-existing platform. CI validates `linux-x64` end-to-end via the
byte-for-byte ARGITER comparison against runtime `ComputeCallRefMap`.
> [!NOTE]
> This change was authored with assistance from GitHub Copilot.
---------
Co-authored-by: Max Charlamb <maxcharlamb@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Aug 10, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@max-charlamb@davidwrighton
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' [cdac] SystemV-AMD64 struct-in-register classifier: enable ARGITER + zero-known-issues on Linux x64 by max-charlamb · Pull Request #130257 · dotnet/runtime · GitHub
Skip to content

[cdac] SystemV-AMD64 struct-in-register classifier: enable ARGITER + zero-known-issues on Linux x64 - #130257

Merged
max-charlamb merged 7 commits into
dotnet:mainfrom
max-charlamb:cdac-sysv-classifier
Jul 8, 2026
Merged

[cdac] SystemV-AMD64 struct-in-register classifier: enable ARGITER + zero-known-issues on Linux x64#130257
max-charlamb merged 7 commits into
dotnet:mainfrom
max-charlamb:cdac-sysv-classifier

Conversation

@max-charlamb

@max-charlambmax-charlamb commented Jul 6, 2026

Copy link
Copy Markdown
Member

Fills in the SystemV-AMD64 struct-in-register classification path in the cDAC's ArgIterator port so ICallingConvention.TryComputeArgGCRefMapBlob produces correct output on Unix x64. Lights up Linux x64 for both the ARGITER stress sub-check and the zero-known-issues GCREFS assertion.

Follow-up to #130090. Advances the platform matrix tracked in #130008.

Approach

The runtime already pre-computes the eightbyte classification for every enregisterable managed value type at MethodTable construction time and caches it in EEClass::m_eightByteRegistersInfo (inside EEClassOptionalFields). Runtime callers -- ArgIterator, getSystemVAmd64PassStructInRegisterDescriptor -- read that cached info via SystemVRegDescriptorFromSystemVEightByteRegistersInfo rather than re-running ClassifyEightBytes. The cDAC now does the same.

Non-enregisterable structs (too large, or classified as SSEUP/X87/Memory) never reach StoreEightByteClassification, so their descriptor reads back with NumEightBytes == 0. That's the natural passedInRegisters = false signal the cDAC surfaces. Native P/Invoke layouts are not cached here (the runtime classifies them on demand) -- the cDAC never walks native transition frames for GC scanning, so this is fine.

Because the stress harness compares against ComputeCallRefMap -- which uses the runtime path -- reading the same cached info gives byte-for-byte parity by construction. No merge-rule / intrinsic-rejection / Int128 divergence to reason about.

macOS x64 uses the same SystemV path and works the same way if run locally, but we don't add osx_x64 to the Helix stress matrix in this PR.

Descriptor plumbing

C++:

  • class.h: cdac_data<EEClass>::OptionalFields; cdac_data<EEClassOptionalFields> (UNIX_AMD64_ABI-guarded) exposing the EightByteRegistersInfo offset. Friend declaration on EEClassOptionalFields.
  • methodtable.h: cdac_data<SystemVEightByteRegistersInfo> for NumEightBytes plus the two byte[2] eightbyte tables. Friend struct.
  • datadescriptor.inc: EEClass.OptionalFields field + new EEClassOptionalFields and SystemVEightByteRegistersInfo types, both under #ifdef UNIX_AMD64_ABI.

Managed:

  • DataType: new EEClassOptionalFields and SystemVEightByteRegistersInfo entries.
  • Data.EEClass: adds OptionalFields.
  • Data.EEClassOptionalFields: [Field] SystemVEightByteRegistersInfo inline sub-IData.
  • Data.SystemVEightByteRegistersInfo: [Field] NumEightBytes, OnInit-populated arrays.
  • CdacTypeHandle.GetSystemVAmd64PassStructInRegisterDescriptor: ~40 LOC inline. Reads EEClass -> OptionalFields -> EightByteRegistersInfo, then projects into SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR with eightByteOffsets synthesized as i * 8 (matches SystemVRegDescriptorFromSystemVEightByteRegistersInfo).

Test coverage

CallSignatures debuggee gains a SysVCategory exercising:

  • Single-eightbyte shapes: IntPair (Integer), FloatPair (SSE), IntFloat (merged Integer), SingleRef (IntegerReference)
  • Two-eightbyte shapes: LongLong, LongDouble, DoubleDouble, RefInt
  • Nested value type, ByRefLike (Span<byte>)
  • Rejection / stack-passed: 24-byte 3-ref struct, Vector128<int>, empty struct
  • Int128Wrapper (2 Integer eightbytes -- register-passed; runtime does NOT reject by name)
  • Unions (LayoutKind.Explicit), sub-eightbyte trailer, single-field wrapper

Same debuggee runs everywhere; only exercises the new descriptor read on Unix x64 (Windows targets don't emit the descriptor entries, so the classifier returns passedInRegisters = false immediately via a TryGetTypeInfo probe).

AssertAllPassed now folds KnownIssues into the Failed check unconditionally -- no more platform gate on the assertion side; any surviving known-issue count is a regression.

Local validation

  • dotnet build src/native/managed/cdac/cdac.slnx -c Release: clean
  • Unit tests: 2646 pass / 0 fail / 16 skipped
  • Windows x64 local stress smoke (RunStressTests.ps1):
    • GCREFS CallSignatures: 6150 / 6150 pass, 0 known-issue
    • ARGITER CallSignatures (with the SysVCategory): 383 pass / 0 fail / 0 skip / 0 error

The descriptor entries are UNIX_AMD64_ABI-only, so the descriptor-read path only activates on Linux/macOS x64 targets; no regression on any pre-existing platform. CI validates linux-x64 end-to-end via the byte-for-byte ARGITER comparison against runtime ComputeCallRefMap.

Note

This change was authored with assistance from GitHub Copilot.

CopilotAI 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

Note

Copilot couldn't run its full agentic review because no GitHub Actions runner was available. Make sure your repository has a runner available to run Copilot's review, or add a copilot-setup-steps.yml file specifying one with the runs-on attribute. See the docs for more details.

Enables SystemV-AMD64 struct-in-register classification for cDAC ArgIterator so GCRefMap generation matches runtime behavior on Unix x64, and flips related stress/GCREFS gates to an inverse “unsupported platforms” list.

Changes:

  • Add Unix x64 SystemV-AMD64 eightbyte struct classifier and wire it into CdacTypeHandle.
  • Expand CallSignatures stress debuggee with SysV-focused signature shapes.
  • Simplify platform gating logic by skipping only remaining unsupported architectures (RiscV64/LoongArch64/Wasm).

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 5 comments.

Show a summary per file
FileDescription
src/native/managed/cdac/tests/StressTests/Debuggees/CallSignatures/Program.csAdds SysV category signatures to exercise Unix x64 struct-in-register classification.
src/native/managed/cdac/tests/StressTests/CdacStressTests.csSwitches ARGITER stress gate to an inverse unsupported-arch list.
src/native/managed/cdac/tests/StressTests/CdacStressTestBase.csSwitches zero-known-issues assertion gate to an inverse unsupported-arch list.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanner.csAligns deferred-frame gating with new inverse unsupported-arch list.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.csAdds IsIntrinsicType contract impl, refactors intrinsic vector detection, bumps HFA recursion cap.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CallingConvention/SystemVStructClassifier.csIntroduces SystemV-AMD64 struct-in-register classifier implementation.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CallingConvention/CdacTypeHandle.csPlumbs the new classifier into the calling-convention surface.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.csAdds IsIntrinsicType(TypeHandle) to the contract.
docs/design/datacontracts/RuntimeTypeSystem.mdDocuments the new IsIntrinsicType API and its implementation.

@max-charlamb
max-charlambforce-pushed the cdac-sysv-classifier branch from fa16cf5 to 7460a2cCompareJuly 6, 2026 22:16
CopilotAI review requested due to automatic review settings July 6, 2026 22:36
@max-charlamb
max-charlambforce-pushed the cdac-sysv-classifier branch from 7460a2c to 00dc8d2CompareJuly 6, 2026 22:36

CopilotAI 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 9 out of 9 changed files in this pull request and generated 4 comments.

@max-charlamb
max-charlambforce-pushed the cdac-sysv-classifier branch from 00dc8d2 to b9a2860CompareJuly 6, 2026 22:49
CopilotAI review requested due to automatic review settings July 6, 2026 22:54
@max-charlamb
max-charlambforce-pushed the cdac-sysv-classifier branch from b9a2860 to aa33f63CompareJuly 6, 2026 22:54

CopilotAI 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 9 out of 9 changed files in this pull request and generated 3 comments.

Comment threadsrc/native/managed/cdac/tests/StressTests/CdacStressTestBase.cs Outdated
CopilotAI review requested due to automatic review settings July 6, 2026 23:36

CopilotAI 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 9 out of 9 changed files in this pull request and generated 2 comments.

@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 pushed a commit to max-charlamb/runtime that referenced this pull request Jul 7, 2026
Per David Wrighton's suggestion on PR dotnet#130257: instead of porting the
~430 LOC SystemV-AMD64 struct-in-register classifier from
crossgen2/methodtable.cpp, read the pre-classified data that the runtime
already caches on EEClass::m_eightByteRegistersInfo.
The runtime's MethodTableBuilder::StoreEightByteClassification (called
from SystemVAmd64CheckForPassStructInRegister at MethodTable build time)
populates m_eightByteRegistersInfo inside EEClassOptionalFields with the
enregisterable-struct classification: NumEightBytes, per-eightbyte
classification tags, per-eightbyte sizes. Callers (arg iterator, JIT-EE
interface) read that cached info via
SystemVRegDescriptorFromSystemVEightByteRegistersInfo instead of
re-running ClassifyEightBytes. The cDAC does the same.
Non-enregisterable structs (too large, or classified as SSEUP/X87/Memory)
never reach StoreEightByteClassification, so their m_eightByteRegistersInfo
reads back with NumEightBytes == 0. That's the natural
"passedInRegisters = false" signal the cDAC surfaces.
Descriptor plumbing:
* class.h: cdac_data<EEClass>::OptionalFields; cdac_data<EEClassOptionalFields>
(UNIX_AMD64_ABI-guarded, includes the EightByteRegistersInfo offset).
* methodtable.h: cdac_data<SystemVEightByteRegistersInfo> for NumEightBytes
plus the two byte[2] arrays; friend struct on the class so offsetof can
reach the private fields.
* datadescriptor.inc: EEClass.OptionalFields field + new
EEClassOptionalFields and SystemVEightByteRegistersInfo types, both
under #ifdef UNIX_AMD64_ABI.
Managed side:
* DataType: new EEClassOptionalFields and SystemVEightByteRegistersInfo entries.
* Data.EEClass: OptionalFields TargetPointer field.
* Data.EEClassOptionalFields: [Field] SystemVEightByteRegistersInfo inline sub-IData.
* Data.SystemVEightByteRegistersInfo: [Field] NumEightBytes; OnInit-populated
arrays for the two eightbyte tables.
* CdacTypeHandle.GetSystemVAmd64PassStructInRegisterDescriptor: reads
EEClass -> OptionalFields -> EightByteRegistersInfo directly (~40 LOC inline;
the standalone SystemVStructClassifier.cs file is deleted).
* MockDescriptors.RuntimeTypeSystem: mock EEClass layout gains OptionalFields
so unit tests build the same shape as the runtime.
Advantages vs the classifier port:
* Byte-for-byte matches ComputeCallRefMap by construction (the stress
harness compares against the runtime path).
* No more crossgen2-vs-runtime Int128/UInt128 divergence to reason about --
the classifier just doesn't run.
* ~371 LOC net removed.
* No maintenance burden as the runtime classifier evolves.
Only activates when the target runtime is UNIX_AMD64_ABI; on other
runtimes the descriptor entries are ifdef'd out and the classifier
returns "not passed in registers" immediately via the TryGetTypeInfo
probe.
Windows unit tests: all 2646 cDAC tests still pass.
Linux x64 stress: TBD (pushed for CI validation).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings July 7, 2026 19:25
@max-charlamb
max-charlambforce-pushed the cdac-sysv-classifier branch from 252c8b1 to 3423ebcCompareJuly 7, 2026 19:25
@max-charlamb
max-charlambforce-pushed the cdac-sysv-classifier branch from 3423ebc to 2b4cd5fCompareJuly 7, 2026 19:35

CopilotAI 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 14 out of 14 changed files in this pull request and generated 2 comments.

Rather than reading the two eightbyte tables as byte[] arrays via
ReadBuffer (indexed only by NumEightBytes, which is read from target
memory without validation), split each table into two explicitly-named
[Field] properties: EightByteClassification0/1 and EightByteSize0/1.
Two benefits:
1. The Data class is fully declarative -- no OnInit, no arrays. The
source generator emits an ordinary two-uint8 read per slot, so
there's no code path that could index past slot 1 regardless of
what NumEightBytes says.
2. Any consumer keyed off descriptor.eightByteCount is now safe against
a corrupted / mis-read NumEightBytes: the previously Debug.Assert-only
check in CdacTypeHandle is now a runtime guard that treats
NumEightBytes > 2 as "not passed in registers", so downstream loops
(e.g. ArgIterator, TransitionBlock) can't overrun a two-slot descriptor.
Also adds a static_assert in methodtable.h that
CLR_SYSTEMV_MAX_EIGHTBYTES_COUNT_TO_PASS_IN_REGISTERS is still 2, so if
the runtime ever grows the descriptor the cDAC-side split gets a build
break rather than silently truncating.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

CopilotAI 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 16 out of 16 changed files in this pull request and generated 2 comments.

CopilotAI review requested due to automatic review settings July 8, 2026 01:40
* GcScanner.PromoteCallerStack: reinstate a cheap arch/OS support gate
(now including Unix x64) so unsupported targets go straight to
RecordDeferredFrame instead of hitting the NotImplementedException
fallback inside CallingConvention_1.TryComputeArgGCRefMapBlob. Avoids
exception-driven control flow (and first-chance exceptions under
debuggers) on RiscV64 / LoongArch64 / Wasm targets. Tracks the same
supported-platform list as the runtime stress gate; TODO points at
dotnet#130008 for extending coverage.
* CdacTypeHandle: reuse
SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR.SYSTEMV_EIGHT_BYTE_SIZE_IN_BYTES
instead of a locally-defined 8 constant.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

CopilotAI 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 16 out of 16 changed files in this pull request and generated 3 comments.

Comment threadsrc/coreclr/vm/methodtable.h
CopilotAI review requested due to automatic review settings July 8, 2026 01:48

CopilotAI 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 16 out of 16 changed files in this pull request and generated 2 comments.

* DataType: move EEClassOptionalFields / SystemVEightByteRegistersInfo
back next to EEClass so type-system entries stay grouped together.
* CallingConvention_1.ComputeArgGCRefMapBlobCore: hoist the shared
TransitionBlock to the top of the method so the SysV struct-in-regs
branch and the pos/offset conversion loop reuse the same instance.
* CallingConvention_1.GetArgumentLayout: unwrap the ArgLocDesc? at the
call site with a throw-on-null instead of dereferencing Value later;
makes the invariant explicit at the point it's required.
* GcScanner.PromoteCallerStack: drop the arch/OS supported-target gate
again -- TryComputeArgGCRefMapBlob's own NotImplementedException path
is the sole decline point.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings July 8, 2026 14:27

CopilotAI 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 16 out of 16 changed files in this pull request and generated 2 comments.

The Debug.Assert this using existed for was replaced with a throw-on-null
in a prior commit; drop the now-unused import.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings July 8, 2026 14:52

CopilotAI 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 16 out of 16 changed files in this pull request and generated 2 comments.

@max-charlamb
max-charlamb enabled auto-merge (squash) July 8, 2026 22:22
@max-charlamb
max-charlamb merged commit 2269602 into dotnet:mainJul 8, 2026
132 of 136 checks passed
@max-charlamb
max-charlamb deleted the cdac-sysv-classifier branch July 8, 2026 23:36
@dotnet-milestone-botdotnet-milestone-botBot added this to the 11.0-preview7 milestone Jul 10, 2026
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
…zero-known-issues on Linux x64 (#130257)
Fills in the SystemV-AMD64 struct-in-register classification path in the
cDAC's ArgIterator port so
`ICallingConvention.TryComputeArgGCRefMapBlob` produces correct output
on Unix x64. Lights up **Linux x64** for both the ARGITER stress
sub-check and the zero-known-issues GCREFS assertion.
Follow-up to #130090. Advances the platform matrix tracked in #130008.
## Approach
The runtime already pre-computes the eightbyte classification for every
enregisterable managed value type at `MethodTable` construction time and
caches it in `EEClass::m_eightByteRegistersInfo` (inside
`EEClassOptionalFields`). Runtime callers -- `ArgIterator`,
`getSystemVAmd64PassStructInRegisterDescriptor` -- read that cached info
via `SystemVRegDescriptorFromSystemVEightByteRegistersInfo` rather than
re-running `ClassifyEightBytes`. The cDAC now does the same.
Non-enregisterable structs (too large, or classified as
`SSEUP`/`X87`/`Memory`) never reach `StoreEightByteClassification`, so
their descriptor reads back with `NumEightBytes == 0`. That's the
natural `passedInRegisters = false` signal the cDAC surfaces. Native
P/Invoke layouts are not cached here (the runtime classifies them on
demand) -- the cDAC never walks native transition frames for GC
scanning, so this is fine.
Because the stress harness compares against `ComputeCallRefMap` -- which
uses the runtime path -- reading the same cached info gives
byte-for-byte parity by construction. No merge-rule /
intrinsic-rejection / `Int128` divergence to reason about.
macOS x64 uses the same SystemV path and works the same way if run
locally, but we don't add `osx_x64` to the Helix stress matrix in this
PR.
## Descriptor plumbing
**C++:**
- `class.h`: `cdac_data<EEClass>::OptionalFields`;
`cdac_data<EEClassOptionalFields>` (UNIX_AMD64_ABI-guarded) exposing the
`EightByteRegistersInfo` offset. Friend declaration on
`EEClassOptionalFields`.
- `methodtable.h`: `cdac_data<SystemVEightByteRegistersInfo>` for
`NumEightBytes` plus the two `byte[2]` eightbyte tables. Friend struct.
- `datadescriptor.inc`: `EEClass.OptionalFields` field + new
`EEClassOptionalFields` and `SystemVEightByteRegistersInfo` types, both
under `#ifdef UNIX_AMD64_ABI`.
**Managed:**
- `DataType`: new `EEClassOptionalFields` and
`SystemVEightByteRegistersInfo` entries.
- `Data.EEClass`: adds `OptionalFields`.
- `Data.EEClassOptionalFields`: `[Field] SystemVEightByteRegistersInfo`
inline sub-IData.
- `Data.SystemVEightByteRegistersInfo`: `[Field] NumEightBytes`,
`OnInit`-populated arrays.
- `CdacTypeHandle.GetSystemVAmd64PassStructInRegisterDescriptor`: ~40
LOC inline. Reads `EEClass -> OptionalFields -> EightByteRegistersInfo`,
then projects into `SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR`
with `eightByteOffsets` synthesized as `i * 8` (matches
`SystemVRegDescriptorFromSystemVEightByteRegistersInfo`).
## Test coverage
`CallSignatures` debuggee gains a `SysVCategory` exercising:
- Single-eightbyte shapes: `IntPair` (Integer), `FloatPair` (SSE),
`IntFloat` (merged Integer), `SingleRef` (IntegerReference)
- Two-eightbyte shapes: `LongLong`, `LongDouble`, `DoubleDouble`,
`RefInt`
- Nested value type, ByRefLike (`Span<byte>`)
- Rejection / stack-passed: 24-byte 3-ref struct, `Vector128<int>`,
empty struct
- `Int128Wrapper` (2 Integer eightbytes -- register-passed; runtime does
NOT reject by name)
- Unions (`LayoutKind.Explicit`), sub-eightbyte trailer, single-field
wrapper
Same debuggee runs everywhere; only exercises the new descriptor read on
Unix x64 (Windows targets don't emit the descriptor entries, so the
classifier returns `passedInRegisters = false` immediately via a
`TryGetTypeInfo` probe).
`AssertAllPassed` now folds `KnownIssues` into the `Failed` check
unconditionally -- no more platform gate on the assertion side; any
surviving known-issue count is a regression.
## Local validation
- `dotnet build src/native/managed/cdac/cdac.slnx -c Release`: clean
- Unit tests: 2646 pass / 0 fail / 16 skipped
- **Windows x64 local stress smoke** (`RunStressTests.ps1`):
- GCREFS `CallSignatures`: 6150 / 6150 pass, 0 known-issue
- ARGITER `CallSignatures` (with the `SysVCategory`): 383 pass / 0 fail
/ 0 skip / 0 error
The descriptor entries are `UNIX_AMD64_ABI`-only, so the descriptor-read
path only activates on Linux/macOS x64 targets; no regression on any
pre-existing platform. CI validates `linux-x64` end-to-end via the
byte-for-byte ARGITER comparison against runtime `ComputeCallRefMap`.
> [!NOTE]
> This change was authored with assistance from GitHub Copilot.
---------
Co-authored-by: Max Charlamb <maxcharlamb@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Aug 10, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@max-charlamb@davidwrighton
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' [cdac] SystemV-AMD64 struct-in-register classifier: enable ARGITER + zero-known-issues on Linux x64 by max-charlamb · Pull Request #130257 · dotnet/runtime · GitHub
Skip to content

[cdac] SystemV-AMD64 struct-in-register classifier: enable ARGITER + zero-known-issues on Linux x64 - #130257

Merged
max-charlamb merged 7 commits into
dotnet:mainfrom
max-charlamb:cdac-sysv-classifier
Jul 8, 2026
Merged

[cdac] SystemV-AMD64 struct-in-register classifier: enable ARGITER + zero-known-issues on Linux x64#130257
max-charlamb merged 7 commits into
dotnet:mainfrom
max-charlamb:cdac-sysv-classifier

Conversation

@max-charlamb

@max-charlambmax-charlamb commented Jul 6, 2026

Copy link
Copy Markdown
Member

Fills in the SystemV-AMD64 struct-in-register classification path in the cDAC's ArgIterator port so ICallingConvention.TryComputeArgGCRefMapBlob produces correct output on Unix x64. Lights up Linux x64 for both the ARGITER stress sub-check and the zero-known-issues GCREFS assertion.

Follow-up to #130090. Advances the platform matrix tracked in #130008.

Approach

The runtime already pre-computes the eightbyte classification for every enregisterable managed value type at MethodTable construction time and caches it in EEClass::m_eightByteRegistersInfo (inside EEClassOptionalFields). Runtime callers -- ArgIterator, getSystemVAmd64PassStructInRegisterDescriptor -- read that cached info via SystemVRegDescriptorFromSystemVEightByteRegistersInfo rather than re-running ClassifyEightBytes. The cDAC now does the same.

Non-enregisterable structs (too large, or classified as SSEUP/X87/Memory) never reach StoreEightByteClassification, so their descriptor reads back with NumEightBytes == 0. That's the natural passedInRegisters = false signal the cDAC surfaces. Native P/Invoke layouts are not cached here (the runtime classifies them on demand) -- the cDAC never walks native transition frames for GC scanning, so this is fine.

Because the stress harness compares against ComputeCallRefMap -- which uses the runtime path -- reading the same cached info gives byte-for-byte parity by construction. No merge-rule / intrinsic-rejection / Int128 divergence to reason about.

macOS x64 uses the same SystemV path and works the same way if run locally, but we don't add osx_x64 to the Helix stress matrix in this PR.

Descriptor plumbing

C++:

  • class.h: cdac_data<EEClass>::OptionalFields; cdac_data<EEClassOptionalFields> (UNIX_AMD64_ABI-guarded) exposing the EightByteRegistersInfo offset. Friend declaration on EEClassOptionalFields.
  • methodtable.h: cdac_data<SystemVEightByteRegistersInfo> for NumEightBytes plus the two byte[2] eightbyte tables. Friend struct.
  • datadescriptor.inc: EEClass.OptionalFields field + new EEClassOptionalFields and SystemVEightByteRegistersInfo types, both under #ifdef UNIX_AMD64_ABI.

Managed:

  • DataType: new EEClassOptionalFields and SystemVEightByteRegistersInfo entries.
  • Data.EEClass: adds OptionalFields.
  • Data.EEClassOptionalFields: [Field] SystemVEightByteRegistersInfo inline sub-IData.
  • Data.SystemVEightByteRegistersInfo: [Field] NumEightBytes, OnInit-populated arrays.
  • CdacTypeHandle.GetSystemVAmd64PassStructInRegisterDescriptor: ~40 LOC inline. Reads EEClass -> OptionalFields -> EightByteRegistersInfo, then projects into SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR with eightByteOffsets synthesized as i * 8 (matches SystemVRegDescriptorFromSystemVEightByteRegistersInfo).

Test coverage

CallSignatures debuggee gains a SysVCategory exercising:

  • Single-eightbyte shapes: IntPair (Integer), FloatPair (SSE), IntFloat (merged Integer), SingleRef (IntegerReference)
  • Two-eightbyte shapes: LongLong, LongDouble, DoubleDouble, RefInt
  • Nested value type, ByRefLike (Span<byte>)
  • Rejection / stack-passed: 24-byte 3-ref struct, Vector128<int>, empty struct
  • Int128Wrapper (2 Integer eightbytes -- register-passed; runtime does NOT reject by name)
  • Unions (LayoutKind.Explicit), sub-eightbyte trailer, single-field wrapper

Same debuggee runs everywhere; only exercises the new descriptor read on Unix x64 (Windows targets don't emit the descriptor entries, so the classifier returns passedInRegisters = false immediately via a TryGetTypeInfo probe).

AssertAllPassed now folds KnownIssues into the Failed check unconditionally -- no more platform gate on the assertion side; any surviving known-issue count is a regression.

Local validation

  • dotnet build src/native/managed/cdac/cdac.slnx -c Release: clean
  • Unit tests: 2646 pass / 0 fail / 16 skipped
  • Windows x64 local stress smoke (RunStressTests.ps1):
    • GCREFS CallSignatures: 6150 / 6150 pass, 0 known-issue
    • ARGITER CallSignatures (with the SysVCategory): 383 pass / 0 fail / 0 skip / 0 error

The descriptor entries are UNIX_AMD64_ABI-only, so the descriptor-read path only activates on Linux/macOS x64 targets; no regression on any pre-existing platform. CI validates linux-x64 end-to-end via the byte-for-byte ARGITER comparison against runtime ComputeCallRefMap.

Note

This change was authored with assistance from GitHub Copilot.

CopilotAI 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

Note

Copilot couldn't run its full agentic review because no GitHub Actions runner was available. Make sure your repository has a runner available to run Copilot's review, or add a copilot-setup-steps.yml file specifying one with the runs-on attribute. See the docs for more details.

Enables SystemV-AMD64 struct-in-register classification for cDAC ArgIterator so GCRefMap generation matches runtime behavior on Unix x64, and flips related stress/GCREFS gates to an inverse “unsupported platforms” list.

Changes:

  • Add Unix x64 SystemV-AMD64 eightbyte struct classifier and wire it into CdacTypeHandle.
  • Expand CallSignatures stress debuggee with SysV-focused signature shapes.
  • Simplify platform gating logic by skipping only remaining unsupported architectures (RiscV64/LoongArch64/Wasm).

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 5 comments.

Show a summary per file
FileDescription
src/native/managed/cdac/tests/StressTests/Debuggees/CallSignatures/Program.csAdds SysV category signatures to exercise Unix x64 struct-in-register classification.
src/native/managed/cdac/tests/StressTests/CdacStressTests.csSwitches ARGITER stress gate to an inverse unsupported-arch list.
src/native/managed/cdac/tests/StressTests/CdacStressTestBase.csSwitches zero-known-issues assertion gate to an inverse unsupported-arch list.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanner.csAligns deferred-frame gating with new inverse unsupported-arch list.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.csAdds IsIntrinsicType contract impl, refactors intrinsic vector detection, bumps HFA recursion cap.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CallingConvention/SystemVStructClassifier.csIntroduces SystemV-AMD64 struct-in-register classifier implementation.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CallingConvention/CdacTypeHandle.csPlumbs the new classifier into the calling-convention surface.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.csAdds IsIntrinsicType(TypeHandle) to the contract.
docs/design/datacontracts/RuntimeTypeSystem.mdDocuments the new IsIntrinsicType API and its implementation.

@max-charlamb
max-charlambforce-pushed the cdac-sysv-classifier branch from fa16cf5 to 7460a2cCompareJuly 6, 2026 22:16
CopilotAI review requested due to automatic review settings July 6, 2026 22:36
@max-charlamb
max-charlambforce-pushed the cdac-sysv-classifier branch from 7460a2c to 00dc8d2CompareJuly 6, 2026 22:36

CopilotAI 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 9 out of 9 changed files in this pull request and generated 4 comments.

@max-charlamb
max-charlambforce-pushed the cdac-sysv-classifier branch from 00dc8d2 to b9a2860CompareJuly 6, 2026 22:49
CopilotAI review requested due to automatic review settings July 6, 2026 22:54
@max-charlamb
max-charlambforce-pushed the cdac-sysv-classifier branch from b9a2860 to aa33f63CompareJuly 6, 2026 22:54

CopilotAI 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 9 out of 9 changed files in this pull request and generated 3 comments.

Comment threadsrc/native/managed/cdac/tests/StressTests/CdacStressTestBase.cs Outdated
CopilotAI review requested due to automatic review settings July 6, 2026 23:36

CopilotAI 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 9 out of 9 changed files in this pull request and generated 2 comments.

@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 pushed a commit to max-charlamb/runtime that referenced this pull request Jul 7, 2026
Per David Wrighton's suggestion on PR dotnet#130257: instead of porting the
~430 LOC SystemV-AMD64 struct-in-register classifier from
crossgen2/methodtable.cpp, read the pre-classified data that the runtime
already caches on EEClass::m_eightByteRegistersInfo.
The runtime's MethodTableBuilder::StoreEightByteClassification (called
from SystemVAmd64CheckForPassStructInRegister at MethodTable build time)
populates m_eightByteRegistersInfo inside EEClassOptionalFields with the
enregisterable-struct classification: NumEightBytes, per-eightbyte
classification tags, per-eightbyte sizes. Callers (arg iterator, JIT-EE
interface) read that cached info via
SystemVRegDescriptorFromSystemVEightByteRegistersInfo instead of
re-running ClassifyEightBytes. The cDAC does the same.
Non-enregisterable structs (too large, or classified as SSEUP/X87/Memory)
never reach StoreEightByteClassification, so their m_eightByteRegistersInfo
reads back with NumEightBytes == 0. That's the natural
"passedInRegisters = false" signal the cDAC surfaces.
Descriptor plumbing:
* class.h: cdac_data<EEClass>::OptionalFields; cdac_data<EEClassOptionalFields>
(UNIX_AMD64_ABI-guarded, includes the EightByteRegistersInfo offset).
* methodtable.h: cdac_data<SystemVEightByteRegistersInfo> for NumEightBytes
plus the two byte[2] arrays; friend struct on the class so offsetof can
reach the private fields.
* datadescriptor.inc: EEClass.OptionalFields field + new
EEClassOptionalFields and SystemVEightByteRegistersInfo types, both
under #ifdef UNIX_AMD64_ABI.
Managed side:
* DataType: new EEClassOptionalFields and SystemVEightByteRegistersInfo entries.
* Data.EEClass: OptionalFields TargetPointer field.
* Data.EEClassOptionalFields: [Field] SystemVEightByteRegistersInfo inline sub-IData.
* Data.SystemVEightByteRegistersInfo: [Field] NumEightBytes; OnInit-populated
arrays for the two eightbyte tables.
* CdacTypeHandle.GetSystemVAmd64PassStructInRegisterDescriptor: reads
EEClass -> OptionalFields -> EightByteRegistersInfo directly (~40 LOC inline;
the standalone SystemVStructClassifier.cs file is deleted).
* MockDescriptors.RuntimeTypeSystem: mock EEClass layout gains OptionalFields
so unit tests build the same shape as the runtime.
Advantages vs the classifier port:
* Byte-for-byte matches ComputeCallRefMap by construction (the stress
harness compares against the runtime path).
* No more crossgen2-vs-runtime Int128/UInt128 divergence to reason about --
the classifier just doesn't run.
* ~371 LOC net removed.
* No maintenance burden as the runtime classifier evolves.
Only activates when the target runtime is UNIX_AMD64_ABI; on other
runtimes the descriptor entries are ifdef'd out and the classifier
returns "not passed in registers" immediately via the TryGetTypeInfo
probe.
Windows unit tests: all 2646 cDAC tests still pass.
Linux x64 stress: TBD (pushed for CI validation).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings July 7, 2026 19:25
@max-charlamb
max-charlambforce-pushed the cdac-sysv-classifier branch from 252c8b1 to 3423ebcCompareJuly 7, 2026 19:25
@max-charlamb
max-charlambforce-pushed the cdac-sysv-classifier branch from 3423ebc to 2b4cd5fCompareJuly 7, 2026 19:35

CopilotAI 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 14 out of 14 changed files in this pull request and generated 2 comments.

Rather than reading the two eightbyte tables as byte[] arrays via
ReadBuffer (indexed only by NumEightBytes, which is read from target
memory without validation), split each table into two explicitly-named
[Field] properties: EightByteClassification0/1 and EightByteSize0/1.
Two benefits:
1. The Data class is fully declarative -- no OnInit, no arrays. The
source generator emits an ordinary two-uint8 read per slot, so
there's no code path that could index past slot 1 regardless of
what NumEightBytes says.
2. Any consumer keyed off descriptor.eightByteCount is now safe against
a corrupted / mis-read NumEightBytes: the previously Debug.Assert-only
check in CdacTypeHandle is now a runtime guard that treats
NumEightBytes > 2 as "not passed in registers", so downstream loops
(e.g. ArgIterator, TransitionBlock) can't overrun a two-slot descriptor.
Also adds a static_assert in methodtable.h that
CLR_SYSTEMV_MAX_EIGHTBYTES_COUNT_TO_PASS_IN_REGISTERS is still 2, so if
the runtime ever grows the descriptor the cDAC-side split gets a build
break rather than silently truncating.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

CopilotAI 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 16 out of 16 changed files in this pull request and generated 2 comments.

CopilotAI review requested due to automatic review settings July 8, 2026 01:40
* GcScanner.PromoteCallerStack: reinstate a cheap arch/OS support gate
(now including Unix x64) so unsupported targets go straight to
RecordDeferredFrame instead of hitting the NotImplementedException
fallback inside CallingConvention_1.TryComputeArgGCRefMapBlob. Avoids
exception-driven control flow (and first-chance exceptions under
debuggers) on RiscV64 / LoongArch64 / Wasm targets. Tracks the same
supported-platform list as the runtime stress gate; TODO points at
dotnet#130008 for extending coverage.
* CdacTypeHandle: reuse
SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR.SYSTEMV_EIGHT_BYTE_SIZE_IN_BYTES
instead of a locally-defined 8 constant.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

CopilotAI 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 16 out of 16 changed files in this pull request and generated 3 comments.

Comment threadsrc/coreclr/vm/methodtable.h
CopilotAI review requested due to automatic review settings July 8, 2026 01:48

CopilotAI 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 16 out of 16 changed files in this pull request and generated 2 comments.

* DataType: move EEClassOptionalFields / SystemVEightByteRegistersInfo
back next to EEClass so type-system entries stay grouped together.
* CallingConvention_1.ComputeArgGCRefMapBlobCore: hoist the shared
TransitionBlock to the top of the method so the SysV struct-in-regs
branch and the pos/offset conversion loop reuse the same instance.
* CallingConvention_1.GetArgumentLayout: unwrap the ArgLocDesc? at the
call site with a throw-on-null instead of dereferencing Value later;
makes the invariant explicit at the point it's required.
* GcScanner.PromoteCallerStack: drop the arch/OS supported-target gate
again -- TryComputeArgGCRefMapBlob's own NotImplementedException path
is the sole decline point.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings July 8, 2026 14:27

CopilotAI 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 16 out of 16 changed files in this pull request and generated 2 comments.

The Debug.Assert this using existed for was replaced with a throw-on-null
in a prior commit; drop the now-unused import.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings July 8, 2026 14:52

CopilotAI 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 16 out of 16 changed files in this pull request and generated 2 comments.

@max-charlamb
max-charlamb enabled auto-merge (squash) July 8, 2026 22:22
@max-charlamb
max-charlamb merged commit 2269602 into dotnet:mainJul 8, 2026
132 of 136 checks passed
@max-charlamb
max-charlamb deleted the cdac-sysv-classifier branch July 8, 2026 23:36
@dotnet-milestone-botdotnet-milestone-botBot added this to the 11.0-preview7 milestone Jul 10, 2026
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
…zero-known-issues on Linux x64 (#130257)
Fills in the SystemV-AMD64 struct-in-register classification path in the
cDAC's ArgIterator port so
`ICallingConvention.TryComputeArgGCRefMapBlob` produces correct output
on Unix x64. Lights up **Linux x64** for both the ARGITER stress
sub-check and the zero-known-issues GCREFS assertion.
Follow-up to #130090. Advances the platform matrix tracked in #130008.
## Approach
The runtime already pre-computes the eightbyte classification for every
enregisterable managed value type at `MethodTable` construction time and
caches it in `EEClass::m_eightByteRegistersInfo` (inside
`EEClassOptionalFields`). Runtime callers -- `ArgIterator`,
`getSystemVAmd64PassStructInRegisterDescriptor` -- read that cached info
via `SystemVRegDescriptorFromSystemVEightByteRegistersInfo` rather than
re-running `ClassifyEightBytes`. The cDAC now does the same.
Non-enregisterable structs (too large, or classified as
`SSEUP`/`X87`/`Memory`) never reach `StoreEightByteClassification`, so
their descriptor reads back with `NumEightBytes == 0`. That's the
natural `passedInRegisters = false` signal the cDAC surfaces. Native
P/Invoke layouts are not cached here (the runtime classifies them on
demand) -- the cDAC never walks native transition frames for GC
scanning, so this is fine.
Because the stress harness compares against `ComputeCallRefMap` -- which
uses the runtime path -- reading the same cached info gives
byte-for-byte parity by construction. No merge-rule /
intrinsic-rejection / `Int128` divergence to reason about.
macOS x64 uses the same SystemV path and works the same way if run
locally, but we don't add `osx_x64` to the Helix stress matrix in this
PR.
## Descriptor plumbing
**C++:**
- `class.h`: `cdac_data<EEClass>::OptionalFields`;
`cdac_data<EEClassOptionalFields>` (UNIX_AMD64_ABI-guarded) exposing the
`EightByteRegistersInfo` offset. Friend declaration on
`EEClassOptionalFields`.
- `methodtable.h`: `cdac_data<SystemVEightByteRegistersInfo>` for
`NumEightBytes` plus the two `byte[2]` eightbyte tables. Friend struct.
- `datadescriptor.inc`: `EEClass.OptionalFields` field + new
`EEClassOptionalFields` and `SystemVEightByteRegistersInfo` types, both
under `#ifdef UNIX_AMD64_ABI`.
**Managed:**
- `DataType`: new `EEClassOptionalFields` and
`SystemVEightByteRegistersInfo` entries.
- `Data.EEClass`: adds `OptionalFields`.
- `Data.EEClassOptionalFields`: `[Field] SystemVEightByteRegistersInfo`
inline sub-IData.
- `Data.SystemVEightByteRegistersInfo`: `[Field] NumEightBytes`,
`OnInit`-populated arrays.
- `CdacTypeHandle.GetSystemVAmd64PassStructInRegisterDescriptor`: ~40
LOC inline. Reads `EEClass -> OptionalFields -> EightByteRegistersInfo`,
then projects into `SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR`
with `eightByteOffsets` synthesized as `i * 8` (matches
`SystemVRegDescriptorFromSystemVEightByteRegistersInfo`).
## Test coverage
`CallSignatures` debuggee gains a `SysVCategory` exercising:
- Single-eightbyte shapes: `IntPair` (Integer), `FloatPair` (SSE),
`IntFloat` (merged Integer), `SingleRef` (IntegerReference)
- Two-eightbyte shapes: `LongLong`, `LongDouble`, `DoubleDouble`,
`RefInt`
- Nested value type, ByRefLike (`Span<byte>`)
- Rejection / stack-passed: 24-byte 3-ref struct, `Vector128<int>`,
empty struct
- `Int128Wrapper` (2 Integer eightbytes -- register-passed; runtime does
NOT reject by name)
- Unions (`LayoutKind.Explicit`), sub-eightbyte trailer, single-field
wrapper
Same debuggee runs everywhere; only exercises the new descriptor read on
Unix x64 (Windows targets don't emit the descriptor entries, so the
classifier returns `passedInRegisters = false` immediately via a
`TryGetTypeInfo` probe).
`AssertAllPassed` now folds `KnownIssues` into the `Failed` check
unconditionally -- no more platform gate on the assertion side; any
surviving known-issue count is a regression.
## Local validation
- `dotnet build src/native/managed/cdac/cdac.slnx -c Release`: clean
- Unit tests: 2646 pass / 0 fail / 16 skipped
- **Windows x64 local stress smoke** (`RunStressTests.ps1`):
- GCREFS `CallSignatures`: 6150 / 6150 pass, 0 known-issue
- ARGITER `CallSignatures` (with the `SysVCategory`): 383 pass / 0 fail
/ 0 skip / 0 error
The descriptor entries are `UNIX_AMD64_ABI`-only, so the descriptor-read
path only activates on Linux/macOS x64 targets; no regression on any
pre-existing platform. CI validates `linux-x64` end-to-end via the
byte-for-byte ARGITER comparison against runtime `ComputeCallRefMap`.
> [!NOTE]
> This change was authored with assistance from GitHub Copilot.
---------
Co-authored-by: Max Charlamb <maxcharlamb@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Aug 10, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@max-charlamb@davidwrighton
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' [cdac] SystemV-AMD64 struct-in-register classifier: enable ARGITER + zero-known-issues on Linux x64 by max-charlamb · Pull Request #130257 · dotnet/runtime · GitHub
Skip to content

[cdac] SystemV-AMD64 struct-in-register classifier: enable ARGITER + zero-known-issues on Linux x64 - #130257

Merged
max-charlamb merged 7 commits into
dotnet:mainfrom
max-charlamb:cdac-sysv-classifier
Jul 8, 2026
Merged

[cdac] SystemV-AMD64 struct-in-register classifier: enable ARGITER + zero-known-issues on Linux x64#130257
max-charlamb merged 7 commits into
dotnet:mainfrom
max-charlamb:cdac-sysv-classifier

Conversation

@max-charlamb

@max-charlambmax-charlamb commented Jul 6, 2026

Copy link
Copy Markdown
Member

Fills in the SystemV-AMD64 struct-in-register classification path in the cDAC's ArgIterator port so ICallingConvention.TryComputeArgGCRefMapBlob produces correct output on Unix x64. Lights up Linux x64 for both the ARGITER stress sub-check and the zero-known-issues GCREFS assertion.

Follow-up to #130090. Advances the platform matrix tracked in #130008.

Approach

The runtime already pre-computes the eightbyte classification for every enregisterable managed value type at MethodTable construction time and caches it in EEClass::m_eightByteRegistersInfo (inside EEClassOptionalFields). Runtime callers -- ArgIterator, getSystemVAmd64PassStructInRegisterDescriptor -- read that cached info via SystemVRegDescriptorFromSystemVEightByteRegistersInfo rather than re-running ClassifyEightBytes. The cDAC now does the same.

Non-enregisterable structs (too large, or classified as SSEUP/X87/Memory) never reach StoreEightByteClassification, so their descriptor reads back with NumEightBytes == 0. That's the natural passedInRegisters = false signal the cDAC surfaces. Native P/Invoke layouts are not cached here (the runtime classifies them on demand) -- the cDAC never walks native transition frames for GC scanning, so this is fine.

Because the stress harness compares against ComputeCallRefMap -- which uses the runtime path -- reading the same cached info gives byte-for-byte parity by construction. No merge-rule / intrinsic-rejection / Int128 divergence to reason about.

macOS x64 uses the same SystemV path and works the same way if run locally, but we don't add osx_x64 to the Helix stress matrix in this PR.

Descriptor plumbing

C++:

  • class.h: cdac_data<EEClass>::OptionalFields; cdac_data<EEClassOptionalFields> (UNIX_AMD64_ABI-guarded) exposing the EightByteRegistersInfo offset. Friend declaration on EEClassOptionalFields.
  • methodtable.h: cdac_data<SystemVEightByteRegistersInfo> for NumEightBytes plus the two byte[2] eightbyte tables. Friend struct.
  • datadescriptor.inc: EEClass.OptionalFields field + new EEClassOptionalFields and SystemVEightByteRegistersInfo types, both under #ifdef UNIX_AMD64_ABI.

Managed:

  • DataType: new EEClassOptionalFields and SystemVEightByteRegistersInfo entries.
  • Data.EEClass: adds OptionalFields.
  • Data.EEClassOptionalFields: [Field] SystemVEightByteRegistersInfo inline sub-IData.
  • Data.SystemVEightByteRegistersInfo: [Field] NumEightBytes, OnInit-populated arrays.
  • CdacTypeHandle.GetSystemVAmd64PassStructInRegisterDescriptor: ~40 LOC inline. Reads EEClass -> OptionalFields -> EightByteRegistersInfo, then projects into SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR with eightByteOffsets synthesized as i * 8 (matches SystemVRegDescriptorFromSystemVEightByteRegistersInfo).

Test coverage

CallSignatures debuggee gains a SysVCategory exercising:

  • Single-eightbyte shapes: IntPair (Integer), FloatPair (SSE), IntFloat (merged Integer), SingleRef (IntegerReference)
  • Two-eightbyte shapes: LongLong, LongDouble, DoubleDouble, RefInt
  • Nested value type, ByRefLike (Span<byte>)
  • Rejection / stack-passed: 24-byte 3-ref struct, Vector128<int>, empty struct
  • Int128Wrapper (2 Integer eightbytes -- register-passed; runtime does NOT reject by name)
  • Unions (LayoutKind.Explicit), sub-eightbyte trailer, single-field wrapper

Same debuggee runs everywhere; only exercises the new descriptor read on Unix x64 (Windows targets don't emit the descriptor entries, so the classifier returns passedInRegisters = false immediately via a TryGetTypeInfo probe).

AssertAllPassed now folds KnownIssues into the Failed check unconditionally -- no more platform gate on the assertion side; any surviving known-issue count is a regression.

Local validation

  • dotnet build src/native/managed/cdac/cdac.slnx -c Release: clean
  • Unit tests: 2646 pass / 0 fail / 16 skipped
  • Windows x64 local stress smoke (RunStressTests.ps1):
    • GCREFS CallSignatures: 6150 / 6150 pass, 0 known-issue
    • ARGITER CallSignatures (with the SysVCategory): 383 pass / 0 fail / 0 skip / 0 error

The descriptor entries are UNIX_AMD64_ABI-only, so the descriptor-read path only activates on Linux/macOS x64 targets; no regression on any pre-existing platform. CI validates linux-x64 end-to-end via the byte-for-byte ARGITER comparison against runtime ComputeCallRefMap.

Note

This change was authored with assistance from GitHub Copilot.

CopilotAI 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

Note

Copilot couldn't run its full agentic review because no GitHub Actions runner was available. Make sure your repository has a runner available to run Copilot's review, or add a copilot-setup-steps.yml file specifying one with the runs-on attribute. See the docs for more details.

Enables SystemV-AMD64 struct-in-register classification for cDAC ArgIterator so GCRefMap generation matches runtime behavior on Unix x64, and flips related stress/GCREFS gates to an inverse “unsupported platforms” list.

Changes:

  • Add Unix x64 SystemV-AMD64 eightbyte struct classifier and wire it into CdacTypeHandle.
  • Expand CallSignatures stress debuggee with SysV-focused signature shapes.
  • Simplify platform gating logic by skipping only remaining unsupported architectures (RiscV64/LoongArch64/Wasm).

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 5 comments.

Show a summary per file
FileDescription
src/native/managed/cdac/tests/StressTests/Debuggees/CallSignatures/Program.csAdds SysV category signatures to exercise Unix x64 struct-in-register classification.
src/native/managed/cdac/tests/StressTests/CdacStressTests.csSwitches ARGITER stress gate to an inverse unsupported-arch list.
src/native/managed/cdac/tests/StressTests/CdacStressTestBase.csSwitches zero-known-issues assertion gate to an inverse unsupported-arch list.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanner.csAligns deferred-frame gating with new inverse unsupported-arch list.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.csAdds IsIntrinsicType contract impl, refactors intrinsic vector detection, bumps HFA recursion cap.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CallingConvention/SystemVStructClassifier.csIntroduces SystemV-AMD64 struct-in-register classifier implementation.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CallingConvention/CdacTypeHandle.csPlumbs the new classifier into the calling-convention surface.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.csAdds IsIntrinsicType(TypeHandle) to the contract.
docs/design/datacontracts/RuntimeTypeSystem.mdDocuments the new IsIntrinsicType API and its implementation.

@max-charlamb
max-charlambforce-pushed the cdac-sysv-classifier branch from fa16cf5 to 7460a2cCompareJuly 6, 2026 22:16
CopilotAI review requested due to automatic review settings July 6, 2026 22:36
@max-charlamb
max-charlambforce-pushed the cdac-sysv-classifier branch from 7460a2c to 00dc8d2CompareJuly 6, 2026 22:36

CopilotAI 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 9 out of 9 changed files in this pull request and generated 4 comments.

@max-charlamb
max-charlambforce-pushed the cdac-sysv-classifier branch from 00dc8d2 to b9a2860CompareJuly 6, 2026 22:49
CopilotAI review requested due to automatic review settings July 6, 2026 22:54
@max-charlamb
max-charlambforce-pushed the cdac-sysv-classifier branch from b9a2860 to aa33f63CompareJuly 6, 2026 22:54

CopilotAI 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 9 out of 9 changed files in this pull request and generated 3 comments.

Comment threadsrc/native/managed/cdac/tests/StressTests/CdacStressTestBase.cs Outdated
CopilotAI review requested due to automatic review settings July 6, 2026 23:36

CopilotAI 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 9 out of 9 changed files in this pull request and generated 2 comments.

@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 pushed a commit to max-charlamb/runtime that referenced this pull request Jul 7, 2026
Per David Wrighton's suggestion on PR dotnet#130257: instead of porting the
~430 LOC SystemV-AMD64 struct-in-register classifier from
crossgen2/methodtable.cpp, read the pre-classified data that the runtime
already caches on EEClass::m_eightByteRegistersInfo.
The runtime's MethodTableBuilder::StoreEightByteClassification (called
from SystemVAmd64CheckForPassStructInRegister at MethodTable build time)
populates m_eightByteRegistersInfo inside EEClassOptionalFields with the
enregisterable-struct classification: NumEightBytes, per-eightbyte
classification tags, per-eightbyte sizes. Callers (arg iterator, JIT-EE
interface) read that cached info via
SystemVRegDescriptorFromSystemVEightByteRegistersInfo instead of
re-running ClassifyEightBytes. The cDAC does the same.
Non-enregisterable structs (too large, or classified as SSEUP/X87/Memory)
never reach StoreEightByteClassification, so their m_eightByteRegistersInfo
reads back with NumEightBytes == 0. That's the natural
"passedInRegisters = false" signal the cDAC surfaces.
Descriptor plumbing:
* class.h: cdac_data<EEClass>::OptionalFields; cdac_data<EEClassOptionalFields>
(UNIX_AMD64_ABI-guarded, includes the EightByteRegistersInfo offset).
* methodtable.h: cdac_data<SystemVEightByteRegistersInfo> for NumEightBytes
plus the two byte[2] arrays; friend struct on the class so offsetof can
reach the private fields.
* datadescriptor.inc: EEClass.OptionalFields field + new
EEClassOptionalFields and SystemVEightByteRegistersInfo types, both
under #ifdef UNIX_AMD64_ABI.
Managed side:
* DataType: new EEClassOptionalFields and SystemVEightByteRegistersInfo entries.
* Data.EEClass: OptionalFields TargetPointer field.
* Data.EEClassOptionalFields: [Field] SystemVEightByteRegistersInfo inline sub-IData.
* Data.SystemVEightByteRegistersInfo: [Field] NumEightBytes; OnInit-populated
arrays for the two eightbyte tables.
* CdacTypeHandle.GetSystemVAmd64PassStructInRegisterDescriptor: reads
EEClass -> OptionalFields -> EightByteRegistersInfo directly (~40 LOC inline;
the standalone SystemVStructClassifier.cs file is deleted).
* MockDescriptors.RuntimeTypeSystem: mock EEClass layout gains OptionalFields
so unit tests build the same shape as the runtime.
Advantages vs the classifier port:
* Byte-for-byte matches ComputeCallRefMap by construction (the stress
harness compares against the runtime path).
* No more crossgen2-vs-runtime Int128/UInt128 divergence to reason about --
the classifier just doesn't run.
* ~371 LOC net removed.
* No maintenance burden as the runtime classifier evolves.
Only activates when the target runtime is UNIX_AMD64_ABI; on other
runtimes the descriptor entries are ifdef'd out and the classifier
returns "not passed in registers" immediately via the TryGetTypeInfo
probe.
Windows unit tests: all 2646 cDAC tests still pass.
Linux x64 stress: TBD (pushed for CI validation).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings July 7, 2026 19:25
@max-charlamb
max-charlambforce-pushed the cdac-sysv-classifier branch from 252c8b1 to 3423ebcCompareJuly 7, 2026 19:25
@max-charlamb
max-charlambforce-pushed the cdac-sysv-classifier branch from 3423ebc to 2b4cd5fCompareJuly 7, 2026 19:35

CopilotAI 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 14 out of 14 changed files in this pull request and generated 2 comments.

Rather than reading the two eightbyte tables as byte[] arrays via
ReadBuffer (indexed only by NumEightBytes, which is read from target
memory without validation), split each table into two explicitly-named
[Field] properties: EightByteClassification0/1 and EightByteSize0/1.
Two benefits:
1. The Data class is fully declarative -- no OnInit, no arrays. The
source generator emits an ordinary two-uint8 read per slot, so
there's no code path that could index past slot 1 regardless of
what NumEightBytes says.
2. Any consumer keyed off descriptor.eightByteCount is now safe against
a corrupted / mis-read NumEightBytes: the previously Debug.Assert-only
check in CdacTypeHandle is now a runtime guard that treats
NumEightBytes > 2 as "not passed in registers", so downstream loops
(e.g. ArgIterator, TransitionBlock) can't overrun a two-slot descriptor.
Also adds a static_assert in methodtable.h that
CLR_SYSTEMV_MAX_EIGHTBYTES_COUNT_TO_PASS_IN_REGISTERS is still 2, so if
the runtime ever grows the descriptor the cDAC-side split gets a build
break rather than silently truncating.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

CopilotAI 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 16 out of 16 changed files in this pull request and generated 2 comments.

CopilotAI review requested due to automatic review settings July 8, 2026 01:40
* GcScanner.PromoteCallerStack: reinstate a cheap arch/OS support gate
(now including Unix x64) so unsupported targets go straight to
RecordDeferredFrame instead of hitting the NotImplementedException
fallback inside CallingConvention_1.TryComputeArgGCRefMapBlob. Avoids
exception-driven control flow (and first-chance exceptions under
debuggers) on RiscV64 / LoongArch64 / Wasm targets. Tracks the same
supported-platform list as the runtime stress gate; TODO points at
dotnet#130008 for extending coverage.
* CdacTypeHandle: reuse
SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR.SYSTEMV_EIGHT_BYTE_SIZE_IN_BYTES
instead of a locally-defined 8 constant.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

CopilotAI 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 16 out of 16 changed files in this pull request and generated 3 comments.

Comment threadsrc/coreclr/vm/methodtable.h
CopilotAI review requested due to automatic review settings July 8, 2026 01:48

CopilotAI 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 16 out of 16 changed files in this pull request and generated 2 comments.

* DataType: move EEClassOptionalFields / SystemVEightByteRegistersInfo
back next to EEClass so type-system entries stay grouped together.
* CallingConvention_1.ComputeArgGCRefMapBlobCore: hoist the shared
TransitionBlock to the top of the method so the SysV struct-in-regs
branch and the pos/offset conversion loop reuse the same instance.
* CallingConvention_1.GetArgumentLayout: unwrap the ArgLocDesc? at the
call site with a throw-on-null instead of dereferencing Value later;
makes the invariant explicit at the point it's required.
* GcScanner.PromoteCallerStack: drop the arch/OS supported-target gate
again -- TryComputeArgGCRefMapBlob's own NotImplementedException path
is the sole decline point.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings July 8, 2026 14:27

CopilotAI 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 16 out of 16 changed files in this pull request and generated 2 comments.

The Debug.Assert this using existed for was replaced with a throw-on-null
in a prior commit; drop the now-unused import.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings July 8, 2026 14:52

CopilotAI 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 16 out of 16 changed files in this pull request and generated 2 comments.

@max-charlamb
max-charlamb enabled auto-merge (squash) July 8, 2026 22:22
@max-charlamb
max-charlamb merged commit 2269602 into dotnet:mainJul 8, 2026
132 of 136 checks passed
@max-charlamb
max-charlamb deleted the cdac-sysv-classifier branch July 8, 2026 23:36
@dotnet-milestone-botdotnet-milestone-botBot added this to the 11.0-preview7 milestone Jul 10, 2026
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
…zero-known-issues on Linux x64 (#130257)
Fills in the SystemV-AMD64 struct-in-register classification path in the
cDAC's ArgIterator port so
`ICallingConvention.TryComputeArgGCRefMapBlob` produces correct output
on Unix x64. Lights up **Linux x64** for both the ARGITER stress
sub-check and the zero-known-issues GCREFS assertion.
Follow-up to #130090. Advances the platform matrix tracked in #130008.
## Approach
The runtime already pre-computes the eightbyte classification for every
enregisterable managed value type at `MethodTable` construction time and
caches it in `EEClass::m_eightByteRegistersInfo` (inside
`EEClassOptionalFields`). Runtime callers -- `ArgIterator`,
`getSystemVAmd64PassStructInRegisterDescriptor` -- read that cached info
via `SystemVRegDescriptorFromSystemVEightByteRegistersInfo` rather than
re-running `ClassifyEightBytes`. The cDAC now does the same.
Non-enregisterable structs (too large, or classified as
`SSEUP`/`X87`/`Memory`) never reach `StoreEightByteClassification`, so
their descriptor reads back with `NumEightBytes == 0`. That's the
natural `passedInRegisters = false` signal the cDAC surfaces. Native
P/Invoke layouts are not cached here (the runtime classifies them on
demand) -- the cDAC never walks native transition frames for GC
scanning, so this is fine.
Because the stress harness compares against `ComputeCallRefMap` -- which
uses the runtime path -- reading the same cached info gives
byte-for-byte parity by construction. No merge-rule /
intrinsic-rejection / `Int128` divergence to reason about.
macOS x64 uses the same SystemV path and works the same way if run
locally, but we don't add `osx_x64` to the Helix stress matrix in this
PR.
## Descriptor plumbing
**C++:**
- `class.h`: `cdac_data<EEClass>::OptionalFields`;
`cdac_data<EEClassOptionalFields>` (UNIX_AMD64_ABI-guarded) exposing the
`EightByteRegistersInfo` offset. Friend declaration on
`EEClassOptionalFields`.
- `methodtable.h`: `cdac_data<SystemVEightByteRegistersInfo>` for
`NumEightBytes` plus the two `byte[2]` eightbyte tables. Friend struct.
- `datadescriptor.inc`: `EEClass.OptionalFields` field + new
`EEClassOptionalFields` and `SystemVEightByteRegistersInfo` types, both
under `#ifdef UNIX_AMD64_ABI`.
**Managed:**
- `DataType`: new `EEClassOptionalFields` and
`SystemVEightByteRegistersInfo` entries.
- `Data.EEClass`: adds `OptionalFields`.
- `Data.EEClassOptionalFields`: `[Field] SystemVEightByteRegistersInfo`
inline sub-IData.
- `Data.SystemVEightByteRegistersInfo`: `[Field] NumEightBytes`,
`OnInit`-populated arrays.
- `CdacTypeHandle.GetSystemVAmd64PassStructInRegisterDescriptor`: ~40
LOC inline. Reads `EEClass -> OptionalFields -> EightByteRegistersInfo`,
then projects into `SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR`
with `eightByteOffsets` synthesized as `i * 8` (matches
`SystemVRegDescriptorFromSystemVEightByteRegistersInfo`).
## Test coverage
`CallSignatures` debuggee gains a `SysVCategory` exercising:
- Single-eightbyte shapes: `IntPair` (Integer), `FloatPair` (SSE),
`IntFloat` (merged Integer), `SingleRef` (IntegerReference)
- Two-eightbyte shapes: `LongLong`, `LongDouble`, `DoubleDouble`,
`RefInt`
- Nested value type, ByRefLike (`Span<byte>`)
- Rejection / stack-passed: 24-byte 3-ref struct, `Vector128<int>`,
empty struct
- `Int128Wrapper` (2 Integer eightbytes -- register-passed; runtime does
NOT reject by name)
- Unions (`LayoutKind.Explicit`), sub-eightbyte trailer, single-field
wrapper
Same debuggee runs everywhere; only exercises the new descriptor read on
Unix x64 (Windows targets don't emit the descriptor entries, so the
classifier returns `passedInRegisters = false` immediately via a
`TryGetTypeInfo` probe).
`AssertAllPassed` now folds `KnownIssues` into the `Failed` check
unconditionally -- no more platform gate on the assertion side; any
surviving known-issue count is a regression.
## Local validation
- `dotnet build src/native/managed/cdac/cdac.slnx -c Release`: clean
- Unit tests: 2646 pass / 0 fail / 16 skipped
- **Windows x64 local stress smoke** (`RunStressTests.ps1`):
- GCREFS `CallSignatures`: 6150 / 6150 pass, 0 known-issue
- ARGITER `CallSignatures` (with the `SysVCategory`): 383 pass / 0 fail
/ 0 skip / 0 error
The descriptor entries are `UNIX_AMD64_ABI`-only, so the descriptor-read
path only activates on Linux/macOS x64 targets; no regression on any
pre-existing platform. CI validates `linux-x64` end-to-end via the
byte-for-byte ARGITER comparison against runtime `ComputeCallRefMap`.
> [!NOTE]
> This change was authored with assistance from GitHub Copilot.
---------
Co-authored-by: Max Charlamb <maxcharlamb@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Aug 10, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@max-charlamb@davidwrighton
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' [cdac] SystemV-AMD64 struct-in-register classifier: enable ARGITER + zero-known-issues on Linux x64 by max-charlamb · Pull Request #130257 · dotnet/runtime · GitHub
Skip to content

[cdac] SystemV-AMD64 struct-in-register classifier: enable ARGITER + zero-known-issues on Linux x64 - #130257

Merged
max-charlamb merged 7 commits into
dotnet:mainfrom
max-charlamb:cdac-sysv-classifier
Jul 8, 2026
Merged

[cdac] SystemV-AMD64 struct-in-register classifier: enable ARGITER + zero-known-issues on Linux x64#130257
max-charlamb merged 7 commits into
dotnet:mainfrom
max-charlamb:cdac-sysv-classifier

Conversation

@max-charlamb

@max-charlambmax-charlamb commented Jul 6, 2026

Copy link
Copy Markdown
Member

Fills in the SystemV-AMD64 struct-in-register classification path in the cDAC's ArgIterator port so ICallingConvention.TryComputeArgGCRefMapBlob produces correct output on Unix x64. Lights up Linux x64 for both the ARGITER stress sub-check and the zero-known-issues GCREFS assertion.

Follow-up to #130090. Advances the platform matrix tracked in #130008.

Approach

The runtime already pre-computes the eightbyte classification for every enregisterable managed value type at MethodTable construction time and caches it in EEClass::m_eightByteRegistersInfo (inside EEClassOptionalFields). Runtime callers -- ArgIterator, getSystemVAmd64PassStructInRegisterDescriptor -- read that cached info via SystemVRegDescriptorFromSystemVEightByteRegistersInfo rather than re-running ClassifyEightBytes. The cDAC now does the same.

Non-enregisterable structs (too large, or classified as SSEUP/X87/Memory) never reach StoreEightByteClassification, so their descriptor reads back with NumEightBytes == 0. That's the natural passedInRegisters = false signal the cDAC surfaces. Native P/Invoke layouts are not cached here (the runtime classifies them on demand) -- the cDAC never walks native transition frames for GC scanning, so this is fine.

Because the stress harness compares against ComputeCallRefMap -- which uses the runtime path -- reading the same cached info gives byte-for-byte parity by construction. No merge-rule / intrinsic-rejection / Int128 divergence to reason about.

macOS x64 uses the same SystemV path and works the same way if run locally, but we don't add osx_x64 to the Helix stress matrix in this PR.

Descriptor plumbing

C++:

  • class.h: cdac_data<EEClass>::OptionalFields; cdac_data<EEClassOptionalFields> (UNIX_AMD64_ABI-guarded) exposing the EightByteRegistersInfo offset. Friend declaration on EEClassOptionalFields.
  • methodtable.h: cdac_data<SystemVEightByteRegistersInfo> for NumEightBytes plus the two byte[2] eightbyte tables. Friend struct.
  • datadescriptor.inc: EEClass.OptionalFields field + new EEClassOptionalFields and SystemVEightByteRegistersInfo types, both under #ifdef UNIX_AMD64_ABI.

Managed:

  • DataType: new EEClassOptionalFields and SystemVEightByteRegistersInfo entries.
  • Data.EEClass: adds OptionalFields.
  • Data.EEClassOptionalFields: [Field] SystemVEightByteRegistersInfo inline sub-IData.
  • Data.SystemVEightByteRegistersInfo: [Field] NumEightBytes, OnInit-populated arrays.
  • CdacTypeHandle.GetSystemVAmd64PassStructInRegisterDescriptor: ~40 LOC inline. Reads EEClass -> OptionalFields -> EightByteRegistersInfo, then projects into SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR with eightByteOffsets synthesized as i * 8 (matches SystemVRegDescriptorFromSystemVEightByteRegistersInfo).

Test coverage

CallSignatures debuggee gains a SysVCategory exercising:

  • Single-eightbyte shapes: IntPair (Integer), FloatPair (SSE), IntFloat (merged Integer), SingleRef (IntegerReference)
  • Two-eightbyte shapes: LongLong, LongDouble, DoubleDouble, RefInt
  • Nested value type, ByRefLike (Span<byte>)
  • Rejection / stack-passed: 24-byte 3-ref struct, Vector128<int>, empty struct
  • Int128Wrapper (2 Integer eightbytes -- register-passed; runtime does NOT reject by name)
  • Unions (LayoutKind.Explicit), sub-eightbyte trailer, single-field wrapper

Same debuggee runs everywhere; only exercises the new descriptor read on Unix x64 (Windows targets don't emit the descriptor entries, so the classifier returns passedInRegisters = false immediately via a TryGetTypeInfo probe).

AssertAllPassed now folds KnownIssues into the Failed check unconditionally -- no more platform gate on the assertion side; any surviving known-issue count is a regression.

Local validation

  • dotnet build src/native/managed/cdac/cdac.slnx -c Release: clean
  • Unit tests: 2646 pass / 0 fail / 16 skipped
  • Windows x64 local stress smoke (RunStressTests.ps1):
    • GCREFS CallSignatures: 6150 / 6150 pass, 0 known-issue
    • ARGITER CallSignatures (with the SysVCategory): 383 pass / 0 fail / 0 skip / 0 error

The descriptor entries are UNIX_AMD64_ABI-only, so the descriptor-read path only activates on Linux/macOS x64 targets; no regression on any pre-existing platform. CI validates linux-x64 end-to-end via the byte-for-byte ARGITER comparison against runtime ComputeCallRefMap.

Note

This change was authored with assistance from GitHub Copilot.

CopilotAI 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

Note

Copilot couldn't run its full agentic review because no GitHub Actions runner was available. Make sure your repository has a runner available to run Copilot's review, or add a copilot-setup-steps.yml file specifying one with the runs-on attribute. See the docs for more details.

Enables SystemV-AMD64 struct-in-register classification for cDAC ArgIterator so GCRefMap generation matches runtime behavior on Unix x64, and flips related stress/GCREFS gates to an inverse “unsupported platforms” list.

Changes:

  • Add Unix x64 SystemV-AMD64 eightbyte struct classifier and wire it into CdacTypeHandle.
  • Expand CallSignatures stress debuggee with SysV-focused signature shapes.
  • Simplify platform gating logic by skipping only remaining unsupported architectures (RiscV64/LoongArch64/Wasm).

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 5 comments.

Show a summary per file
FileDescription
src/native/managed/cdac/tests/StressTests/Debuggees/CallSignatures/Program.csAdds SysV category signatures to exercise Unix x64 struct-in-register classification.
src/native/managed/cdac/tests/StressTests/CdacStressTests.csSwitches ARGITER stress gate to an inverse unsupported-arch list.
src/native/managed/cdac/tests/StressTests/CdacStressTestBase.csSwitches zero-known-issues assertion gate to an inverse unsupported-arch list.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanner.csAligns deferred-frame gating with new inverse unsupported-arch list.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.csAdds IsIntrinsicType contract impl, refactors intrinsic vector detection, bumps HFA recursion cap.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CallingConvention/SystemVStructClassifier.csIntroduces SystemV-AMD64 struct-in-register classifier implementation.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CallingConvention/CdacTypeHandle.csPlumbs the new classifier into the calling-convention surface.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.csAdds IsIntrinsicType(TypeHandle) to the contract.
docs/design/datacontracts/RuntimeTypeSystem.mdDocuments the new IsIntrinsicType API and its implementation.

@max-charlamb
max-charlambforce-pushed the cdac-sysv-classifier branch from fa16cf5 to 7460a2cCompareJuly 6, 2026 22:16
CopilotAI review requested due to automatic review settings July 6, 2026 22:36
@max-charlamb
max-charlambforce-pushed the cdac-sysv-classifier branch from 7460a2c to 00dc8d2CompareJuly 6, 2026 22:36

CopilotAI 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 9 out of 9 changed files in this pull request and generated 4 comments.

@max-charlamb
max-charlambforce-pushed the cdac-sysv-classifier branch from 00dc8d2 to b9a2860CompareJuly 6, 2026 22:49
CopilotAI review requested due to automatic review settings July 6, 2026 22:54
@max-charlamb
max-charlambforce-pushed the cdac-sysv-classifier branch from b9a2860 to aa33f63CompareJuly 6, 2026 22:54

CopilotAI 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 9 out of 9 changed files in this pull request and generated 3 comments.

Comment threadsrc/native/managed/cdac/tests/StressTests/CdacStressTestBase.cs Outdated
CopilotAI review requested due to automatic review settings July 6, 2026 23:36

CopilotAI 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 9 out of 9 changed files in this pull request and generated 2 comments.

@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 pushed a commit to max-charlamb/runtime that referenced this pull request Jul 7, 2026
Per David Wrighton's suggestion on PR dotnet#130257: instead of porting the
~430 LOC SystemV-AMD64 struct-in-register classifier from
crossgen2/methodtable.cpp, read the pre-classified data that the runtime
already caches on EEClass::m_eightByteRegistersInfo.
The runtime's MethodTableBuilder::StoreEightByteClassification (called
from SystemVAmd64CheckForPassStructInRegister at MethodTable build time)
populates m_eightByteRegistersInfo inside EEClassOptionalFields with the
enregisterable-struct classification: NumEightBytes, per-eightbyte
classification tags, per-eightbyte sizes. Callers (arg iterator, JIT-EE
interface) read that cached info via
SystemVRegDescriptorFromSystemVEightByteRegistersInfo instead of
re-running ClassifyEightBytes. The cDAC does the same.
Non-enregisterable structs (too large, or classified as SSEUP/X87/Memory)
never reach StoreEightByteClassification, so their m_eightByteRegistersInfo
reads back with NumEightBytes == 0. That's the natural
"passedInRegisters = false" signal the cDAC surfaces.
Descriptor plumbing:
* class.h: cdac_data<EEClass>::OptionalFields; cdac_data<EEClassOptionalFields>
(UNIX_AMD64_ABI-guarded, includes the EightByteRegistersInfo offset).
* methodtable.h: cdac_data<SystemVEightByteRegistersInfo> for NumEightBytes
plus the two byte[2] arrays; friend struct on the class so offsetof can
reach the private fields.
* datadescriptor.inc: EEClass.OptionalFields field + new
EEClassOptionalFields and SystemVEightByteRegistersInfo types, both
under #ifdef UNIX_AMD64_ABI.
Managed side:
* DataType: new EEClassOptionalFields and SystemVEightByteRegistersInfo entries.
* Data.EEClass: OptionalFields TargetPointer field.
* Data.EEClassOptionalFields: [Field] SystemVEightByteRegistersInfo inline sub-IData.
* Data.SystemVEightByteRegistersInfo: [Field] NumEightBytes; OnInit-populated
arrays for the two eightbyte tables.
* CdacTypeHandle.GetSystemVAmd64PassStructInRegisterDescriptor: reads
EEClass -> OptionalFields -> EightByteRegistersInfo directly (~40 LOC inline;
the standalone SystemVStructClassifier.cs file is deleted).
* MockDescriptors.RuntimeTypeSystem: mock EEClass layout gains OptionalFields
so unit tests build the same shape as the runtime.
Advantages vs the classifier port:
* Byte-for-byte matches ComputeCallRefMap by construction (the stress
harness compares against the runtime path).
* No more crossgen2-vs-runtime Int128/UInt128 divergence to reason about --
the classifier just doesn't run.
* ~371 LOC net removed.
* No maintenance burden as the runtime classifier evolves.
Only activates when the target runtime is UNIX_AMD64_ABI; on other
runtimes the descriptor entries are ifdef'd out and the classifier
returns "not passed in registers" immediately via the TryGetTypeInfo
probe.
Windows unit tests: all 2646 cDAC tests still pass.
Linux x64 stress: TBD (pushed for CI validation).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings July 7, 2026 19:25
@max-charlamb
max-charlambforce-pushed the cdac-sysv-classifier branch from 252c8b1 to 3423ebcCompareJuly 7, 2026 19:25
@max-charlamb
max-charlambforce-pushed the cdac-sysv-classifier branch from 3423ebc to 2b4cd5fCompareJuly 7, 2026 19:35

CopilotAI 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 14 out of 14 changed files in this pull request and generated 2 comments.

Rather than reading the two eightbyte tables as byte[] arrays via
ReadBuffer (indexed only by NumEightBytes, which is read from target
memory without validation), split each table into two explicitly-named
[Field] properties: EightByteClassification0/1 and EightByteSize0/1.
Two benefits:
1. The Data class is fully declarative -- no OnInit, no arrays. The
source generator emits an ordinary two-uint8 read per slot, so
there's no code path that could index past slot 1 regardless of
what NumEightBytes says.
2. Any consumer keyed off descriptor.eightByteCount is now safe against
a corrupted / mis-read NumEightBytes: the previously Debug.Assert-only
check in CdacTypeHandle is now a runtime guard that treats
NumEightBytes > 2 as "not passed in registers", so downstream loops
(e.g. ArgIterator, TransitionBlock) can't overrun a two-slot descriptor.
Also adds a static_assert in methodtable.h that
CLR_SYSTEMV_MAX_EIGHTBYTES_COUNT_TO_PASS_IN_REGISTERS is still 2, so if
the runtime ever grows the descriptor the cDAC-side split gets a build
break rather than silently truncating.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

CopilotAI 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 16 out of 16 changed files in this pull request and generated 2 comments.

CopilotAI review requested due to automatic review settings July 8, 2026 01:40
* GcScanner.PromoteCallerStack: reinstate a cheap arch/OS support gate
(now including Unix x64) so unsupported targets go straight to
RecordDeferredFrame instead of hitting the NotImplementedException
fallback inside CallingConvention_1.TryComputeArgGCRefMapBlob. Avoids
exception-driven control flow (and first-chance exceptions under
debuggers) on RiscV64 / LoongArch64 / Wasm targets. Tracks the same
supported-platform list as the runtime stress gate; TODO points at
dotnet#130008 for extending coverage.
* CdacTypeHandle: reuse
SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR.SYSTEMV_EIGHT_BYTE_SIZE_IN_BYTES
instead of a locally-defined 8 constant.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

CopilotAI 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 16 out of 16 changed files in this pull request and generated 3 comments.

Comment threadsrc/coreclr/vm/methodtable.h
CopilotAI review requested due to automatic review settings July 8, 2026 01:48

CopilotAI 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 16 out of 16 changed files in this pull request and generated 2 comments.

* DataType: move EEClassOptionalFields / SystemVEightByteRegistersInfo
back next to EEClass so type-system entries stay grouped together.
* CallingConvention_1.ComputeArgGCRefMapBlobCore: hoist the shared
TransitionBlock to the top of the method so the SysV struct-in-regs
branch and the pos/offset conversion loop reuse the same instance.
* CallingConvention_1.GetArgumentLayout: unwrap the ArgLocDesc? at the
call site with a throw-on-null instead of dereferencing Value later;
makes the invariant explicit at the point it's required.
* GcScanner.PromoteCallerStack: drop the arch/OS supported-target gate
again -- TryComputeArgGCRefMapBlob's own NotImplementedException path
is the sole decline point.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings July 8, 2026 14:27

CopilotAI 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 16 out of 16 changed files in this pull request and generated 2 comments.

The Debug.Assert this using existed for was replaced with a throw-on-null
in a prior commit; drop the now-unused import.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings July 8, 2026 14:52

CopilotAI 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 16 out of 16 changed files in this pull request and generated 2 comments.

@max-charlamb
max-charlamb enabled auto-merge (squash) July 8, 2026 22:22
@max-charlamb
max-charlamb merged commit 2269602 into dotnet:mainJul 8, 2026
132 of 136 checks passed
@max-charlamb
max-charlamb deleted the cdac-sysv-classifier branch July 8, 2026 23:36
@dotnet-milestone-botdotnet-milestone-botBot added this to the 11.0-preview7 milestone Jul 10, 2026
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
…zero-known-issues on Linux x64 (#130257)
Fills in the SystemV-AMD64 struct-in-register classification path in the
cDAC's ArgIterator port so
`ICallingConvention.TryComputeArgGCRefMapBlob` produces correct output
on Unix x64. Lights up **Linux x64** for both the ARGITER stress
sub-check and the zero-known-issues GCREFS assertion.
Follow-up to #130090. Advances the platform matrix tracked in #130008.
## Approach
The runtime already pre-computes the eightbyte classification for every
enregisterable managed value type at `MethodTable` construction time and
caches it in `EEClass::m_eightByteRegistersInfo` (inside
`EEClassOptionalFields`). Runtime callers -- `ArgIterator`,
`getSystemVAmd64PassStructInRegisterDescriptor` -- read that cached info
via `SystemVRegDescriptorFromSystemVEightByteRegistersInfo` rather than
re-running `ClassifyEightBytes`. The cDAC now does the same.
Non-enregisterable structs (too large, or classified as
`SSEUP`/`X87`/`Memory`) never reach `StoreEightByteClassification`, so
their descriptor reads back with `NumEightBytes == 0`. That's the
natural `passedInRegisters = false` signal the cDAC surfaces. Native
P/Invoke layouts are not cached here (the runtime classifies them on
demand) -- the cDAC never walks native transition frames for GC
scanning, so this is fine.
Because the stress harness compares against `ComputeCallRefMap` -- which
uses the runtime path -- reading the same cached info gives
byte-for-byte parity by construction. No merge-rule /
intrinsic-rejection / `Int128` divergence to reason about.
macOS x64 uses the same SystemV path and works the same way if run
locally, but we don't add `osx_x64` to the Helix stress matrix in this
PR.
## Descriptor plumbing
**C++:**
- `class.h`: `cdac_data<EEClass>::OptionalFields`;
`cdac_data<EEClassOptionalFields>` (UNIX_AMD64_ABI-guarded) exposing the
`EightByteRegistersInfo` offset. Friend declaration on
`EEClassOptionalFields`.
- `methodtable.h`: `cdac_data<SystemVEightByteRegistersInfo>` for
`NumEightBytes` plus the two `byte[2]` eightbyte tables. Friend struct.
- `datadescriptor.inc`: `EEClass.OptionalFields` field + new
`EEClassOptionalFields` and `SystemVEightByteRegistersInfo` types, both
under `#ifdef UNIX_AMD64_ABI`.
**Managed:**
- `DataType`: new `EEClassOptionalFields` and
`SystemVEightByteRegistersInfo` entries.
- `Data.EEClass`: adds `OptionalFields`.
- `Data.EEClassOptionalFields`: `[Field] SystemVEightByteRegistersInfo`
inline sub-IData.
- `Data.SystemVEightByteRegistersInfo`: `[Field] NumEightBytes`,
`OnInit`-populated arrays.
- `CdacTypeHandle.GetSystemVAmd64PassStructInRegisterDescriptor`: ~40
LOC inline. Reads `EEClass -> OptionalFields -> EightByteRegistersInfo`,
then projects into `SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR`
with `eightByteOffsets` synthesized as `i * 8` (matches
`SystemVRegDescriptorFromSystemVEightByteRegistersInfo`).
## Test coverage
`CallSignatures` debuggee gains a `SysVCategory` exercising:
- Single-eightbyte shapes: `IntPair` (Integer), `FloatPair` (SSE),
`IntFloat` (merged Integer), `SingleRef` (IntegerReference)
- Two-eightbyte shapes: `LongLong`, `LongDouble`, `DoubleDouble`,
`RefInt`
- Nested value type, ByRefLike (`Span<byte>`)
- Rejection / stack-passed: 24-byte 3-ref struct, `Vector128<int>`,
empty struct
- `Int128Wrapper` (2 Integer eightbytes -- register-passed; runtime does
NOT reject by name)
- Unions (`LayoutKind.Explicit`), sub-eightbyte trailer, single-field
wrapper
Same debuggee runs everywhere; only exercises the new descriptor read on
Unix x64 (Windows targets don't emit the descriptor entries, so the
classifier returns `passedInRegisters = false` immediately via a
`TryGetTypeInfo` probe).
`AssertAllPassed` now folds `KnownIssues` into the `Failed` check
unconditionally -- no more platform gate on the assertion side; any
surviving known-issue count is a regression.
## Local validation
- `dotnet build src/native/managed/cdac/cdac.slnx -c Release`: clean
- Unit tests: 2646 pass / 0 fail / 16 skipped
- **Windows x64 local stress smoke** (`RunStressTests.ps1`):
- GCREFS `CallSignatures`: 6150 / 6150 pass, 0 known-issue
- ARGITER `CallSignatures` (with the `SysVCategory`): 383 pass / 0 fail
/ 0 skip / 0 error
The descriptor entries are `UNIX_AMD64_ABI`-only, so the descriptor-read
path only activates on Linux/macOS x64 targets; no regression on any
pre-existing platform. CI validates `linux-x64` end-to-end via the
byte-for-byte ARGITER comparison against runtime `ComputeCallRefMap`.
> [!NOTE]
> This change was authored with assistance from GitHub Copilot.
---------
Co-authored-by: Max Charlamb <maxcharlamb@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Aug 10, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@max-charlamb@davidwrighton
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' [cdac] SystemV-AMD64 struct-in-register classifier: enable ARGITER + zero-known-issues on Linux x64 by max-charlamb · Pull Request #130257 · dotnet/runtime · GitHub
Skip to content

[cdac] SystemV-AMD64 struct-in-register classifier: enable ARGITER + zero-known-issues on Linux x64 - #130257

Merged
max-charlamb merged 7 commits into
dotnet:mainfrom
max-charlamb:cdac-sysv-classifier
Jul 8, 2026
Merged

[cdac] SystemV-AMD64 struct-in-register classifier: enable ARGITER + zero-known-issues on Linux x64#130257
max-charlamb merged 7 commits into
dotnet:mainfrom
max-charlamb:cdac-sysv-classifier

Conversation

@max-charlamb

@max-charlambmax-charlamb commented Jul 6, 2026

Copy link
Copy Markdown
Member

Fills in the SystemV-AMD64 struct-in-register classification path in the cDAC's ArgIterator port so ICallingConvention.TryComputeArgGCRefMapBlob produces correct output on Unix x64. Lights up Linux x64 for both the ARGITER stress sub-check and the zero-known-issues GCREFS assertion.

Follow-up to #130090. Advances the platform matrix tracked in #130008.

Approach

The runtime already pre-computes the eightbyte classification for every enregisterable managed value type at MethodTable construction time and caches it in EEClass::m_eightByteRegistersInfo (inside EEClassOptionalFields). Runtime callers -- ArgIterator, getSystemVAmd64PassStructInRegisterDescriptor -- read that cached info via SystemVRegDescriptorFromSystemVEightByteRegistersInfo rather than re-running ClassifyEightBytes. The cDAC now does the same.

Non-enregisterable structs (too large, or classified as SSEUP/X87/Memory) never reach StoreEightByteClassification, so their descriptor reads back with NumEightBytes == 0. That's the natural passedInRegisters = false signal the cDAC surfaces. Native P/Invoke layouts are not cached here (the runtime classifies them on demand) -- the cDAC never walks native transition frames for GC scanning, so this is fine.

Because the stress harness compares against ComputeCallRefMap -- which uses the runtime path -- reading the same cached info gives byte-for-byte parity by construction. No merge-rule / intrinsic-rejection / Int128 divergence to reason about.

macOS x64 uses the same SystemV path and works the same way if run locally, but we don't add osx_x64 to the Helix stress matrix in this PR.

Descriptor plumbing

C++:

  • class.h: cdac_data<EEClass>::OptionalFields; cdac_data<EEClassOptionalFields> (UNIX_AMD64_ABI-guarded) exposing the EightByteRegistersInfo offset. Friend declaration on EEClassOptionalFields.
  • methodtable.h: cdac_data<SystemVEightByteRegistersInfo> for NumEightBytes plus the two byte[2] eightbyte tables. Friend struct.
  • datadescriptor.inc: EEClass.OptionalFields field + new EEClassOptionalFields and SystemVEightByteRegistersInfo types, both under #ifdef UNIX_AMD64_ABI.

Managed:

  • DataType: new EEClassOptionalFields and SystemVEightByteRegistersInfo entries.
  • Data.EEClass: adds OptionalFields.
  • Data.EEClassOptionalFields: [Field] SystemVEightByteRegistersInfo inline sub-IData.
  • Data.SystemVEightByteRegistersInfo: [Field] NumEightBytes, OnInit-populated arrays.
  • CdacTypeHandle.GetSystemVAmd64PassStructInRegisterDescriptor: ~40 LOC inline. Reads EEClass -> OptionalFields -> EightByteRegistersInfo, then projects into SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR with eightByteOffsets synthesized as i * 8 (matches SystemVRegDescriptorFromSystemVEightByteRegistersInfo).

Test coverage

CallSignatures debuggee gains a SysVCategory exercising:

  • Single-eightbyte shapes: IntPair (Integer), FloatPair (SSE), IntFloat (merged Integer), SingleRef (IntegerReference)
  • Two-eightbyte shapes: LongLong, LongDouble, DoubleDouble, RefInt
  • Nested value type, ByRefLike (Span<byte>)
  • Rejection / stack-passed: 24-byte 3-ref struct, Vector128<int>, empty struct
  • Int128Wrapper (2 Integer eightbytes -- register-passed; runtime does NOT reject by name)
  • Unions (LayoutKind.Explicit), sub-eightbyte trailer, single-field wrapper

Same debuggee runs everywhere; only exercises the new descriptor read on Unix x64 (Windows targets don't emit the descriptor entries, so the classifier returns passedInRegisters = false immediately via a TryGetTypeInfo probe).

AssertAllPassed now folds KnownIssues into the Failed check unconditionally -- no more platform gate on the assertion side; any surviving known-issue count is a regression.

Local validation

  • dotnet build src/native/managed/cdac/cdac.slnx -c Release: clean
  • Unit tests: 2646 pass / 0 fail / 16 skipped
  • Windows x64 local stress smoke (RunStressTests.ps1):
    • GCREFS CallSignatures: 6150 / 6150 pass, 0 known-issue
    • ARGITER CallSignatures (with the SysVCategory): 383 pass / 0 fail / 0 skip / 0 error

The descriptor entries are UNIX_AMD64_ABI-only, so the descriptor-read path only activates on Linux/macOS x64 targets; no regression on any pre-existing platform. CI validates linux-x64 end-to-end via the byte-for-byte ARGITER comparison against runtime ComputeCallRefMap.

Note

This change was authored with assistance from GitHub Copilot.

CopilotAI 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

Note

Copilot couldn't run its full agentic review because no GitHub Actions runner was available. Make sure your repository has a runner available to run Copilot's review, or add a copilot-setup-steps.yml file specifying one with the runs-on attribute. See the docs for more details.

Enables SystemV-AMD64 struct-in-register classification for cDAC ArgIterator so GCRefMap generation matches runtime behavior on Unix x64, and flips related stress/GCREFS gates to an inverse “unsupported platforms” list.

Changes:

  • Add Unix x64 SystemV-AMD64 eightbyte struct classifier and wire it into CdacTypeHandle.
  • Expand CallSignatures stress debuggee with SysV-focused signature shapes.
  • Simplify platform gating logic by skipping only remaining unsupported architectures (RiscV64/LoongArch64/Wasm).

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 5 comments.

Show a summary per file
FileDescription
src/native/managed/cdac/tests/StressTests/Debuggees/CallSignatures/Program.csAdds SysV category signatures to exercise Unix x64 struct-in-register classification.
src/native/managed/cdac/tests/StressTests/CdacStressTests.csSwitches ARGITER stress gate to an inverse unsupported-arch list.
src/native/managed/cdac/tests/StressTests/CdacStressTestBase.csSwitches zero-known-issues assertion gate to an inverse unsupported-arch list.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanner.csAligns deferred-frame gating with new inverse unsupported-arch list.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.csAdds IsIntrinsicType contract impl, refactors intrinsic vector detection, bumps HFA recursion cap.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CallingConvention/SystemVStructClassifier.csIntroduces SystemV-AMD64 struct-in-register classifier implementation.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CallingConvention/CdacTypeHandle.csPlumbs the new classifier into the calling-convention surface.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.csAdds IsIntrinsicType(TypeHandle) to the contract.
docs/design/datacontracts/RuntimeTypeSystem.mdDocuments the new IsIntrinsicType API and its implementation.

@max-charlamb
max-charlambforce-pushed the cdac-sysv-classifier branch from fa16cf5 to 7460a2cCompareJuly 6, 2026 22:16
CopilotAI review requested due to automatic review settings July 6, 2026 22:36
@max-charlamb
max-charlambforce-pushed the cdac-sysv-classifier branch from 7460a2c to 00dc8d2CompareJuly 6, 2026 22:36

CopilotAI 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 9 out of 9 changed files in this pull request and generated 4 comments.

@max-charlamb
max-charlambforce-pushed the cdac-sysv-classifier branch from 00dc8d2 to b9a2860CompareJuly 6, 2026 22:49
CopilotAI review requested due to automatic review settings July 6, 2026 22:54
@max-charlamb
max-charlambforce-pushed the cdac-sysv-classifier branch from b9a2860 to aa33f63CompareJuly 6, 2026 22:54

CopilotAI 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 9 out of 9 changed files in this pull request and generated 3 comments.

Comment threadsrc/native/managed/cdac/tests/StressTests/CdacStressTestBase.cs Outdated
CopilotAI review requested due to automatic review settings July 6, 2026 23:36

CopilotAI 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 9 out of 9 changed files in this pull request and generated 2 comments.

@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 pushed a commit to max-charlamb/runtime that referenced this pull request Jul 7, 2026
Per David Wrighton's suggestion on PR dotnet#130257: instead of porting the
~430 LOC SystemV-AMD64 struct-in-register classifier from
crossgen2/methodtable.cpp, read the pre-classified data that the runtime
already caches on EEClass::m_eightByteRegistersInfo.
The runtime's MethodTableBuilder::StoreEightByteClassification (called
from SystemVAmd64CheckForPassStructInRegister at MethodTable build time)
populates m_eightByteRegistersInfo inside EEClassOptionalFields with the
enregisterable-struct classification: NumEightBytes, per-eightbyte
classification tags, per-eightbyte sizes. Callers (arg iterator, JIT-EE
interface) read that cached info via
SystemVRegDescriptorFromSystemVEightByteRegistersInfo instead of
re-running ClassifyEightBytes. The cDAC does the same.
Non-enregisterable structs (too large, or classified as SSEUP/X87/Memory)
never reach StoreEightByteClassification, so their m_eightByteRegistersInfo
reads back with NumEightBytes == 0. That's the natural
"passedInRegisters = false" signal the cDAC surfaces.
Descriptor plumbing:
* class.h: cdac_data<EEClass>::OptionalFields; cdac_data<EEClassOptionalFields>
(UNIX_AMD64_ABI-guarded, includes the EightByteRegistersInfo offset).
* methodtable.h: cdac_data<SystemVEightByteRegistersInfo> for NumEightBytes
plus the two byte[2] arrays; friend struct on the class so offsetof can
reach the private fields.
* datadescriptor.inc: EEClass.OptionalFields field + new
EEClassOptionalFields and SystemVEightByteRegistersInfo types, both
under #ifdef UNIX_AMD64_ABI.
Managed side:
* DataType: new EEClassOptionalFields and SystemVEightByteRegistersInfo entries.
* Data.EEClass: OptionalFields TargetPointer field.
* Data.EEClassOptionalFields: [Field] SystemVEightByteRegistersInfo inline sub-IData.
* Data.SystemVEightByteRegistersInfo: [Field] NumEightBytes; OnInit-populated
arrays for the two eightbyte tables.
* CdacTypeHandle.GetSystemVAmd64PassStructInRegisterDescriptor: reads
EEClass -> OptionalFields -> EightByteRegistersInfo directly (~40 LOC inline;
the standalone SystemVStructClassifier.cs file is deleted).
* MockDescriptors.RuntimeTypeSystem: mock EEClass layout gains OptionalFields
so unit tests build the same shape as the runtime.
Advantages vs the classifier port:
* Byte-for-byte matches ComputeCallRefMap by construction (the stress
harness compares against the runtime path).
* No more crossgen2-vs-runtime Int128/UInt128 divergence to reason about --
the classifier just doesn't run.
* ~371 LOC net removed.
* No maintenance burden as the runtime classifier evolves.
Only activates when the target runtime is UNIX_AMD64_ABI; on other
runtimes the descriptor entries are ifdef'd out and the classifier
returns "not passed in registers" immediately via the TryGetTypeInfo
probe.
Windows unit tests: all 2646 cDAC tests still pass.
Linux x64 stress: TBD (pushed for CI validation).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings July 7, 2026 19:25
@max-charlamb
max-charlambforce-pushed the cdac-sysv-classifier branch from 252c8b1 to 3423ebcCompareJuly 7, 2026 19:25
@max-charlamb
max-charlambforce-pushed the cdac-sysv-classifier branch from 3423ebc to 2b4cd5fCompareJuly 7, 2026 19:35

CopilotAI 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 14 out of 14 changed files in this pull request and generated 2 comments.

Rather than reading the two eightbyte tables as byte[] arrays via
ReadBuffer (indexed only by NumEightBytes, which is read from target
memory without validation), split each table into two explicitly-named
[Field] properties: EightByteClassification0/1 and EightByteSize0/1.
Two benefits:
1. The Data class is fully declarative -- no OnInit, no arrays. The
source generator emits an ordinary two-uint8 read per slot, so
there's no code path that could index past slot 1 regardless of
what NumEightBytes says.
2. Any consumer keyed off descriptor.eightByteCount is now safe against
a corrupted / mis-read NumEightBytes: the previously Debug.Assert-only
check in CdacTypeHandle is now a runtime guard that treats
NumEightBytes > 2 as "not passed in registers", so downstream loops
(e.g. ArgIterator, TransitionBlock) can't overrun a two-slot descriptor.
Also adds a static_assert in methodtable.h that
CLR_SYSTEMV_MAX_EIGHTBYTES_COUNT_TO_PASS_IN_REGISTERS is still 2, so if
the runtime ever grows the descriptor the cDAC-side split gets a build
break rather than silently truncating.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

CopilotAI 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 16 out of 16 changed files in this pull request and generated 2 comments.

CopilotAI review requested due to automatic review settings July 8, 2026 01:40
* GcScanner.PromoteCallerStack: reinstate a cheap arch/OS support gate
(now including Unix x64) so unsupported targets go straight to
RecordDeferredFrame instead of hitting the NotImplementedException
fallback inside CallingConvention_1.TryComputeArgGCRefMapBlob. Avoids
exception-driven control flow (and first-chance exceptions under
debuggers) on RiscV64 / LoongArch64 / Wasm targets. Tracks the same
supported-platform list as the runtime stress gate; TODO points at
dotnet#130008 for extending coverage.
* CdacTypeHandle: reuse
SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR.SYSTEMV_EIGHT_BYTE_SIZE_IN_BYTES
instead of a locally-defined 8 constant.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

CopilotAI 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 16 out of 16 changed files in this pull request and generated 3 comments.

Comment threadsrc/coreclr/vm/methodtable.h
CopilotAI review requested due to automatic review settings July 8, 2026 01:48

CopilotAI 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 16 out of 16 changed files in this pull request and generated 2 comments.

* DataType: move EEClassOptionalFields / SystemVEightByteRegistersInfo
back next to EEClass so type-system entries stay grouped together.
* CallingConvention_1.ComputeArgGCRefMapBlobCore: hoist the shared
TransitionBlock to the top of the method so the SysV struct-in-regs
branch and the pos/offset conversion loop reuse the same instance.
* CallingConvention_1.GetArgumentLayout: unwrap the ArgLocDesc? at the
call site with a throw-on-null instead of dereferencing Value later;
makes the invariant explicit at the point it's required.
* GcScanner.PromoteCallerStack: drop the arch/OS supported-target gate
again -- TryComputeArgGCRefMapBlob's own NotImplementedException path
is the sole decline point.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings July 8, 2026 14:27

CopilotAI 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 16 out of 16 changed files in this pull request and generated 2 comments.

The Debug.Assert this using existed for was replaced with a throw-on-null
in a prior commit; drop the now-unused import.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings July 8, 2026 14:52

CopilotAI 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 16 out of 16 changed files in this pull request and generated 2 comments.

@max-charlamb
max-charlamb enabled auto-merge (squash) July 8, 2026 22:22
@max-charlamb
max-charlamb merged commit 2269602 into dotnet:mainJul 8, 2026
132 of 136 checks passed
@max-charlamb
max-charlamb deleted the cdac-sysv-classifier branch July 8, 2026 23:36
@dotnet-milestone-botdotnet-milestone-botBot added this to the 11.0-preview7 milestone Jul 10, 2026
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
…zero-known-issues on Linux x64 (#130257)
Fills in the SystemV-AMD64 struct-in-register classification path in the
cDAC's ArgIterator port so
`ICallingConvention.TryComputeArgGCRefMapBlob` produces correct output
on Unix x64. Lights up **Linux x64** for both the ARGITER stress
sub-check and the zero-known-issues GCREFS assertion.
Follow-up to #130090. Advances the platform matrix tracked in #130008.
## Approach
The runtime already pre-computes the eightbyte classification for every
enregisterable managed value type at `MethodTable` construction time and
caches it in `EEClass::m_eightByteRegistersInfo` (inside
`EEClassOptionalFields`). Runtime callers -- `ArgIterator`,
`getSystemVAmd64PassStructInRegisterDescriptor` -- read that cached info
via `SystemVRegDescriptorFromSystemVEightByteRegistersInfo` rather than
re-running `ClassifyEightBytes`. The cDAC now does the same.
Non-enregisterable structs (too large, or classified as
`SSEUP`/`X87`/`Memory`) never reach `StoreEightByteClassification`, so
their descriptor reads back with `NumEightBytes == 0`. That's the
natural `passedInRegisters = false` signal the cDAC surfaces. Native
P/Invoke layouts are not cached here (the runtime classifies them on
demand) -- the cDAC never walks native transition frames for GC
scanning, so this is fine.
Because the stress harness compares against `ComputeCallRefMap` -- which
uses the runtime path -- reading the same cached info gives
byte-for-byte parity by construction. No merge-rule /
intrinsic-rejection / `Int128` divergence to reason about.
macOS x64 uses the same SystemV path and works the same way if run
locally, but we don't add `osx_x64` to the Helix stress matrix in this
PR.
## Descriptor plumbing
**C++:**
- `class.h`: `cdac_data<EEClass>::OptionalFields`;
`cdac_data<EEClassOptionalFields>` (UNIX_AMD64_ABI-guarded) exposing the
`EightByteRegistersInfo` offset. Friend declaration on
`EEClassOptionalFields`.
- `methodtable.h`: `cdac_data<SystemVEightByteRegistersInfo>` for
`NumEightBytes` plus the two `byte[2]` eightbyte tables. Friend struct.
- `datadescriptor.inc`: `EEClass.OptionalFields` field + new
`EEClassOptionalFields` and `SystemVEightByteRegistersInfo` types, both
under `#ifdef UNIX_AMD64_ABI`.
**Managed:**
- `DataType`: new `EEClassOptionalFields` and
`SystemVEightByteRegistersInfo` entries.
- `Data.EEClass`: adds `OptionalFields`.
- `Data.EEClassOptionalFields`: `[Field] SystemVEightByteRegistersInfo`
inline sub-IData.
- `Data.SystemVEightByteRegistersInfo`: `[Field] NumEightBytes`,
`OnInit`-populated arrays.
- `CdacTypeHandle.GetSystemVAmd64PassStructInRegisterDescriptor`: ~40
LOC inline. Reads `EEClass -> OptionalFields -> EightByteRegistersInfo`,
then projects into `SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR`
with `eightByteOffsets` synthesized as `i * 8` (matches
`SystemVRegDescriptorFromSystemVEightByteRegistersInfo`).
## Test coverage
`CallSignatures` debuggee gains a `SysVCategory` exercising:
- Single-eightbyte shapes: `IntPair` (Integer), `FloatPair` (SSE),
`IntFloat` (merged Integer), `SingleRef` (IntegerReference)
- Two-eightbyte shapes: `LongLong`, `LongDouble`, `DoubleDouble`,
`RefInt`
- Nested value type, ByRefLike (`Span<byte>`)
- Rejection / stack-passed: 24-byte 3-ref struct, `Vector128<int>`,
empty struct
- `Int128Wrapper` (2 Integer eightbytes -- register-passed; runtime does
NOT reject by name)
- Unions (`LayoutKind.Explicit`), sub-eightbyte trailer, single-field
wrapper
Same debuggee runs everywhere; only exercises the new descriptor read on
Unix x64 (Windows targets don't emit the descriptor entries, so the
classifier returns `passedInRegisters = false` immediately via a
`TryGetTypeInfo` probe).
`AssertAllPassed` now folds `KnownIssues` into the `Failed` check
unconditionally -- no more platform gate on the assertion side; any
surviving known-issue count is a regression.
## Local validation
- `dotnet build src/native/managed/cdac/cdac.slnx -c Release`: clean
- Unit tests: 2646 pass / 0 fail / 16 skipped
- **Windows x64 local stress smoke** (`RunStressTests.ps1`):
- GCREFS `CallSignatures`: 6150 / 6150 pass, 0 known-issue
- ARGITER `CallSignatures` (with the `SysVCategory`): 383 pass / 0 fail
/ 0 skip / 0 error
The descriptor entries are `UNIX_AMD64_ABI`-only, so the descriptor-read
path only activates on Linux/macOS x64 targets; no regression on any
pre-existing platform. CI validates `linux-x64` end-to-end via the
byte-for-byte ARGITER comparison against runtime `ComputeCallRefMap`.
> [!NOTE]
> This change was authored with assistance from GitHub Copilot.
---------
Co-authored-by: Max Charlamb <maxcharlamb@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Aug 10, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@max-charlamb@davidwrighton
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); [cdac] SystemV-AMD64 struct-in-register classifier: enable ARGITER + zero-known-issues on Linux x64 by max-charlamb · Pull Request #130257 · dotnet/runtime · GitHub
Skip to content

[cdac] SystemV-AMD64 struct-in-register classifier: enable ARGITER + zero-known-issues on Linux x64 - #130257

Merged
max-charlamb merged 7 commits into
dotnet:mainfrom
max-charlamb:cdac-sysv-classifier
Jul 8, 2026
Merged

[cdac] SystemV-AMD64 struct-in-register classifier: enable ARGITER + zero-known-issues on Linux x64#130257
max-charlamb merged 7 commits into
dotnet:mainfrom
max-charlamb:cdac-sysv-classifier

Conversation

@max-charlamb

@max-charlambmax-charlamb commented Jul 6, 2026

Copy link
Copy Markdown
Member

Fills in the SystemV-AMD64 struct-in-register classification path in the cDAC's ArgIterator port so ICallingConvention.TryComputeArgGCRefMapBlob produces correct output on Unix x64. Lights up Linux x64 for both the ARGITER stress sub-check and the zero-known-issues GCREFS assertion.

Follow-up to #130090. Advances the platform matrix tracked in #130008.

Approach

The runtime already pre-computes the eightbyte classification for every enregisterable managed value type at MethodTable construction time and caches it in EEClass::m_eightByteRegistersInfo (inside EEClassOptionalFields). Runtime callers -- ArgIterator, getSystemVAmd64PassStructInRegisterDescriptor -- read that cached info via SystemVRegDescriptorFromSystemVEightByteRegistersInfo rather than re-running ClassifyEightBytes. The cDAC now does the same.

Non-enregisterable structs (too large, or classified as SSEUP/X87/Memory) never reach StoreEightByteClassification, so their descriptor reads back with NumEightBytes == 0. That's the natural passedInRegisters = false signal the cDAC surfaces. Native P/Invoke layouts are not cached here (the runtime classifies them on demand) -- the cDAC never walks native transition frames for GC scanning, so this is fine.

Because the stress harness compares against ComputeCallRefMap -- which uses the runtime path -- reading the same cached info gives byte-for-byte parity by construction. No merge-rule / intrinsic-rejection / Int128 divergence to reason about.

macOS x64 uses the same SystemV path and works the same way if run locally, but we don't add osx_x64 to the Helix stress matrix in this PR.

Descriptor plumbing

C++:

  • class.h: cdac_data<EEClass>::OptionalFields; cdac_data<EEClassOptionalFields> (UNIX_AMD64_ABI-guarded) exposing the EightByteRegistersInfo offset. Friend declaration on EEClassOptionalFields.
  • methodtable.h: cdac_data<SystemVEightByteRegistersInfo> for NumEightBytes plus the two byte[2] eightbyte tables. Friend struct.
  • datadescriptor.inc: EEClass.OptionalFields field + new EEClassOptionalFields and SystemVEightByteRegistersInfo types, both under #ifdef UNIX_AMD64_ABI.

Managed:

  • DataType: new EEClassOptionalFields and SystemVEightByteRegistersInfo entries.
  • Data.EEClass: adds OptionalFields.
  • Data.EEClassOptionalFields: [Field] SystemVEightByteRegistersInfo inline sub-IData.
  • Data.SystemVEightByteRegistersInfo: [Field] NumEightBytes, OnInit-populated arrays.
  • CdacTypeHandle.GetSystemVAmd64PassStructInRegisterDescriptor: ~40 LOC inline. Reads EEClass -> OptionalFields -> EightByteRegistersInfo, then projects into SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR with eightByteOffsets synthesized as i * 8 (matches SystemVRegDescriptorFromSystemVEightByteRegistersInfo).

Test coverage

CallSignatures debuggee gains a SysVCategory exercising:

  • Single-eightbyte shapes: IntPair (Integer), FloatPair (SSE), IntFloat (merged Integer), SingleRef (IntegerReference)
  • Two-eightbyte shapes: LongLong, LongDouble, DoubleDouble, RefInt
  • Nested value type, ByRefLike (Span<byte>)
  • Rejection / stack-passed: 24-byte 3-ref struct, Vector128<int>, empty struct
  • Int128Wrapper (2 Integer eightbytes -- register-passed; runtime does NOT reject by name)
  • Unions (LayoutKind.Explicit), sub-eightbyte trailer, single-field wrapper

Same debuggee runs everywhere; only exercises the new descriptor read on Unix x64 (Windows targets don't emit the descriptor entries, so the classifier returns passedInRegisters = false immediately via a TryGetTypeInfo probe).

AssertAllPassed now folds KnownIssues into the Failed check unconditionally -- no more platform gate on the assertion side; any surviving known-issue count is a regression.

Local validation

  • dotnet build src/native/managed/cdac/cdac.slnx -c Release: clean
  • Unit tests: 2646 pass / 0 fail / 16 skipped
  • Windows x64 local stress smoke (RunStressTests.ps1):
    • GCREFS CallSignatures: 6150 / 6150 pass, 0 known-issue
    • ARGITER CallSignatures (with the SysVCategory): 383 pass / 0 fail / 0 skip / 0 error

The descriptor entries are UNIX_AMD64_ABI-only, so the descriptor-read path only activates on Linux/macOS x64 targets; no regression on any pre-existing platform. CI validates linux-x64 end-to-end via the byte-for-byte ARGITER comparison against runtime ComputeCallRefMap.

Note

This change was authored with assistance from GitHub Copilot.

CopilotAI 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

Note

Copilot couldn't run its full agentic review because no GitHub Actions runner was available. Make sure your repository has a runner available to run Copilot's review, or add a copilot-setup-steps.yml file specifying one with the runs-on attribute. See the docs for more details.

Enables SystemV-AMD64 struct-in-register classification for cDAC ArgIterator so GCRefMap generation matches runtime behavior on Unix x64, and flips related stress/GCREFS gates to an inverse “unsupported platforms” list.

Changes:

  • Add Unix x64 SystemV-AMD64 eightbyte struct classifier and wire it into CdacTypeHandle.
  • Expand CallSignatures stress debuggee with SysV-focused signature shapes.
  • Simplify platform gating logic by skipping only remaining unsupported architectures (RiscV64/LoongArch64/Wasm).

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 5 comments.

Show a summary per file
FileDescription
src/native/managed/cdac/tests/StressTests/Debuggees/CallSignatures/Program.csAdds SysV category signatures to exercise Unix x64 struct-in-register classification.
src/native/managed/cdac/tests/StressTests/CdacStressTests.csSwitches ARGITER stress gate to an inverse unsupported-arch list.
src/native/managed/cdac/tests/StressTests/CdacStressTestBase.csSwitches zero-known-issues assertion gate to an inverse unsupported-arch list.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanner.csAligns deferred-frame gating with new inverse unsupported-arch list.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.csAdds IsIntrinsicType contract impl, refactors intrinsic vector detection, bumps HFA recursion cap.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CallingConvention/SystemVStructClassifier.csIntroduces SystemV-AMD64 struct-in-register classifier implementation.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CallingConvention/CdacTypeHandle.csPlumbs the new classifier into the calling-convention surface.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.csAdds IsIntrinsicType(TypeHandle) to the contract.
docs/design/datacontracts/RuntimeTypeSystem.mdDocuments the new IsIntrinsicType API and its implementation.

@max-charlamb
max-charlambforce-pushed the cdac-sysv-classifier branch from fa16cf5 to 7460a2cCompareJuly 6, 2026 22:16
CopilotAI review requested due to automatic review settings July 6, 2026 22:36
@max-charlamb
max-charlambforce-pushed the cdac-sysv-classifier branch from 7460a2c to 00dc8d2CompareJuly 6, 2026 22:36

CopilotAI 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 9 out of 9 changed files in this pull request and generated 4 comments.

@max-charlamb
max-charlambforce-pushed the cdac-sysv-classifier branch from 00dc8d2 to b9a2860CompareJuly 6, 2026 22:49
CopilotAI review requested due to automatic review settings July 6, 2026 22:54
@max-charlamb
max-charlambforce-pushed the cdac-sysv-classifier branch from b9a2860 to aa33f63CompareJuly 6, 2026 22:54

CopilotAI 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 9 out of 9 changed files in this pull request and generated 3 comments.

Comment threadsrc/native/managed/cdac/tests/StressTests/CdacStressTestBase.cs Outdated
CopilotAI review requested due to automatic review settings July 6, 2026 23:36

CopilotAI 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 9 out of 9 changed files in this pull request and generated 2 comments.

@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 pushed a commit to max-charlamb/runtime that referenced this pull request Jul 7, 2026
Per David Wrighton's suggestion on PR dotnet#130257: instead of porting the
~430 LOC SystemV-AMD64 struct-in-register classifier from
crossgen2/methodtable.cpp, read the pre-classified data that the runtime
already caches on EEClass::m_eightByteRegistersInfo.
The runtime's MethodTableBuilder::StoreEightByteClassification (called
from SystemVAmd64CheckForPassStructInRegister at MethodTable build time)
populates m_eightByteRegistersInfo inside EEClassOptionalFields with the
enregisterable-struct classification: NumEightBytes, per-eightbyte
classification tags, per-eightbyte sizes. Callers (arg iterator, JIT-EE
interface) read that cached info via
SystemVRegDescriptorFromSystemVEightByteRegistersInfo instead of
re-running ClassifyEightBytes. The cDAC does the same.
Non-enregisterable structs (too large, or classified as SSEUP/X87/Memory)
never reach StoreEightByteClassification, so their m_eightByteRegistersInfo
reads back with NumEightBytes == 0. That's the natural
"passedInRegisters = false" signal the cDAC surfaces.
Descriptor plumbing:
* class.h: cdac_data<EEClass>::OptionalFields; cdac_data<EEClassOptionalFields>
(UNIX_AMD64_ABI-guarded, includes the EightByteRegistersInfo offset).
* methodtable.h: cdac_data<SystemVEightByteRegistersInfo> for NumEightBytes
plus the two byte[2] arrays; friend struct on the class so offsetof can
reach the private fields.
* datadescriptor.inc: EEClass.OptionalFields field + new
EEClassOptionalFields and SystemVEightByteRegistersInfo types, both
under #ifdef UNIX_AMD64_ABI.
Managed side:
* DataType: new EEClassOptionalFields and SystemVEightByteRegistersInfo entries.
* Data.EEClass: OptionalFields TargetPointer field.
* Data.EEClassOptionalFields: [Field] SystemVEightByteRegistersInfo inline sub-IData.
* Data.SystemVEightByteRegistersInfo: [Field] NumEightBytes; OnInit-populated
arrays for the two eightbyte tables.
* CdacTypeHandle.GetSystemVAmd64PassStructInRegisterDescriptor: reads
EEClass -> OptionalFields -> EightByteRegistersInfo directly (~40 LOC inline;
the standalone SystemVStructClassifier.cs file is deleted).
* MockDescriptors.RuntimeTypeSystem: mock EEClass layout gains OptionalFields
so unit tests build the same shape as the runtime.
Advantages vs the classifier port:
* Byte-for-byte matches ComputeCallRefMap by construction (the stress
harness compares against the runtime path).
* No more crossgen2-vs-runtime Int128/UInt128 divergence to reason about --
the classifier just doesn't run.
* ~371 LOC net removed.
* No maintenance burden as the runtime classifier evolves.
Only activates when the target runtime is UNIX_AMD64_ABI; on other
runtimes the descriptor entries are ifdef'd out and the classifier
returns "not passed in registers" immediately via the TryGetTypeInfo
probe.
Windows unit tests: all 2646 cDAC tests still pass.
Linux x64 stress: TBD (pushed for CI validation).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings July 7, 2026 19:25
@max-charlamb
max-charlambforce-pushed the cdac-sysv-classifier branch from 252c8b1 to 3423ebcCompareJuly 7, 2026 19:25
@max-charlamb
max-charlambforce-pushed the cdac-sysv-classifier branch from 3423ebc to 2b4cd5fCompareJuly 7, 2026 19:35

CopilotAI 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 14 out of 14 changed files in this pull request and generated 2 comments.

Rather than reading the two eightbyte tables as byte[] arrays via
ReadBuffer (indexed only by NumEightBytes, which is read from target
memory without validation), split each table into two explicitly-named
[Field] properties: EightByteClassification0/1 and EightByteSize0/1.
Two benefits:
1. The Data class is fully declarative -- no OnInit, no arrays. The
source generator emits an ordinary two-uint8 read per slot, so
there's no code path that could index past slot 1 regardless of
what NumEightBytes says.
2. Any consumer keyed off descriptor.eightByteCount is now safe against
a corrupted / mis-read NumEightBytes: the previously Debug.Assert-only
check in CdacTypeHandle is now a runtime guard that treats
NumEightBytes > 2 as "not passed in registers", so downstream loops
(e.g. ArgIterator, TransitionBlock) can't overrun a two-slot descriptor.
Also adds a static_assert in methodtable.h that
CLR_SYSTEMV_MAX_EIGHTBYTES_COUNT_TO_PASS_IN_REGISTERS is still 2, so if
the runtime ever grows the descriptor the cDAC-side split gets a build
break rather than silently truncating.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

CopilotAI 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 16 out of 16 changed files in this pull request and generated 2 comments.

CopilotAI review requested due to automatic review settings July 8, 2026 01:40
* GcScanner.PromoteCallerStack: reinstate a cheap arch/OS support gate
(now including Unix x64) so unsupported targets go straight to
RecordDeferredFrame instead of hitting the NotImplementedException
fallback inside CallingConvention_1.TryComputeArgGCRefMapBlob. Avoids
exception-driven control flow (and first-chance exceptions under
debuggers) on RiscV64 / LoongArch64 / Wasm targets. Tracks the same
supported-platform list as the runtime stress gate; TODO points at
dotnet#130008 for extending coverage.
* CdacTypeHandle: reuse
SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR.SYSTEMV_EIGHT_BYTE_SIZE_IN_BYTES
instead of a locally-defined 8 constant.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

CopilotAI 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 16 out of 16 changed files in this pull request and generated 3 comments.

Comment threadsrc/coreclr/vm/methodtable.h
CopilotAI review requested due to automatic review settings July 8, 2026 01:48

CopilotAI 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 16 out of 16 changed files in this pull request and generated 2 comments.

* DataType: move EEClassOptionalFields / SystemVEightByteRegistersInfo
back next to EEClass so type-system entries stay grouped together.
* CallingConvention_1.ComputeArgGCRefMapBlobCore: hoist the shared
TransitionBlock to the top of the method so the SysV struct-in-regs
branch and the pos/offset conversion loop reuse the same instance.
* CallingConvention_1.GetArgumentLayout: unwrap the ArgLocDesc? at the
call site with a throw-on-null instead of dereferencing Value later;
makes the invariant explicit at the point it's required.
* GcScanner.PromoteCallerStack: drop the arch/OS supported-target gate
again -- TryComputeArgGCRefMapBlob's own NotImplementedException path
is the sole decline point.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings July 8, 2026 14:27

CopilotAI 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 16 out of 16 changed files in this pull request and generated 2 comments.

The Debug.Assert this using existed for was replaced with a throw-on-null
in a prior commit; drop the now-unused import.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings July 8, 2026 14:52

CopilotAI 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 16 out of 16 changed files in this pull request and generated 2 comments.

@max-charlamb
max-charlamb enabled auto-merge (squash) July 8, 2026 22:22
@max-charlamb
max-charlamb merged commit 2269602 into dotnet:mainJul 8, 2026
132 of 136 checks passed
@max-charlamb
max-charlamb deleted the cdac-sysv-classifier branch July 8, 2026 23:36
@dotnet-milestone-botdotnet-milestone-botBot added this to the 11.0-preview7 milestone Jul 10, 2026
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
…zero-known-issues on Linux x64 (#130257)
Fills in the SystemV-AMD64 struct-in-register classification path in the
cDAC's ArgIterator port so
`ICallingConvention.TryComputeArgGCRefMapBlob` produces correct output
on Unix x64. Lights up **Linux x64** for both the ARGITER stress
sub-check and the zero-known-issues GCREFS assertion.
Follow-up to #130090. Advances the platform matrix tracked in #130008.
## Approach
The runtime already pre-computes the eightbyte classification for every
enregisterable managed value type at `MethodTable` construction time and
caches it in `EEClass::m_eightByteRegistersInfo` (inside
`EEClassOptionalFields`). Runtime callers -- `ArgIterator`,
`getSystemVAmd64PassStructInRegisterDescriptor` -- read that cached info
via `SystemVRegDescriptorFromSystemVEightByteRegistersInfo` rather than
re-running `ClassifyEightBytes`. The cDAC now does the same.
Non-enregisterable structs (too large, or classified as
`SSEUP`/`X87`/`Memory`) never reach `StoreEightByteClassification`, so
their descriptor reads back with `NumEightBytes == 0`. That's the
natural `passedInRegisters = false` signal the cDAC surfaces. Native
P/Invoke layouts are not cached here (the runtime classifies them on
demand) -- the cDAC never walks native transition frames for GC
scanning, so this is fine.
Because the stress harness compares against `ComputeCallRefMap` -- which
uses the runtime path -- reading the same cached info gives
byte-for-byte parity by construction. No merge-rule /
intrinsic-rejection / `Int128` divergence to reason about.
macOS x64 uses the same SystemV path and works the same way if run
locally, but we don't add `osx_x64` to the Helix stress matrix in this
PR.
## Descriptor plumbing
**C++:**
- `class.h`: `cdac_data<EEClass>::OptionalFields`;
`cdac_data<EEClassOptionalFields>` (UNIX_AMD64_ABI-guarded) exposing the
`EightByteRegistersInfo` offset. Friend declaration on
`EEClassOptionalFields`.
- `methodtable.h`: `cdac_data<SystemVEightByteRegistersInfo>` for
`NumEightBytes` plus the two `byte[2]` eightbyte tables. Friend struct.
- `datadescriptor.inc`: `EEClass.OptionalFields` field + new
`EEClassOptionalFields` and `SystemVEightByteRegistersInfo` types, both
under `#ifdef UNIX_AMD64_ABI`.
**Managed:**
- `DataType`: new `EEClassOptionalFields` and
`SystemVEightByteRegistersInfo` entries.
- `Data.EEClass`: adds `OptionalFields`.
- `Data.EEClassOptionalFields`: `[Field] SystemVEightByteRegistersInfo`
inline sub-IData.
- `Data.SystemVEightByteRegistersInfo`: `[Field] NumEightBytes`,
`OnInit`-populated arrays.
- `CdacTypeHandle.GetSystemVAmd64PassStructInRegisterDescriptor`: ~40
LOC inline. Reads `EEClass -> OptionalFields -> EightByteRegistersInfo`,
then projects into `SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR`
with `eightByteOffsets` synthesized as `i * 8` (matches
`SystemVRegDescriptorFromSystemVEightByteRegistersInfo`).
## Test coverage
`CallSignatures` debuggee gains a `SysVCategory` exercising:
- Single-eightbyte shapes: `IntPair` (Integer), `FloatPair` (SSE),
`IntFloat` (merged Integer), `SingleRef` (IntegerReference)
- Two-eightbyte shapes: `LongLong`, `LongDouble`, `DoubleDouble`,
`RefInt`
- Nested value type, ByRefLike (`Span<byte>`)
- Rejection / stack-passed: 24-byte 3-ref struct, `Vector128<int>`,
empty struct
- `Int128Wrapper` (2 Integer eightbytes -- register-passed; runtime does
NOT reject by name)
- Unions (`LayoutKind.Explicit`), sub-eightbyte trailer, single-field
wrapper
Same debuggee runs everywhere; only exercises the new descriptor read on
Unix x64 (Windows targets don't emit the descriptor entries, so the
classifier returns `passedInRegisters = false` immediately via a
`TryGetTypeInfo` probe).
`AssertAllPassed` now folds `KnownIssues` into the `Failed` check
unconditionally -- no more platform gate on the assertion side; any
surviving known-issue count is a regression.
## Local validation
- `dotnet build src/native/managed/cdac/cdac.slnx -c Release`: clean
- Unit tests: 2646 pass / 0 fail / 16 skipped
- **Windows x64 local stress smoke** (`RunStressTests.ps1`):
- GCREFS `CallSignatures`: 6150 / 6150 pass, 0 known-issue
- ARGITER `CallSignatures` (with the `SysVCategory`): 383 pass / 0 fail
/ 0 skip / 0 error
The descriptor entries are `UNIX_AMD64_ABI`-only, so the descriptor-read
path only activates on Linux/macOS x64 targets; no regression on any
pre-existing platform. CI validates `linux-x64` end-to-end via the
byte-for-byte ARGITER comparison against runtime `ComputeCallRefMap`.
> [!NOTE]
> This change was authored with assistance from GitHub Copilot.
---------
Co-authored-by: Max Charlamb <maxcharlamb@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Aug 10, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@max-charlamb@davidwrighton