diff --git a/src/coreclr/vm/class.h b/src/coreclr/vm/class.h index cb0aa94f1650ac..131dd6ebba0c37 100644 --- a/src/coreclr/vm/class.h +++ b/src/coreclr/vm/class.h @@ -613,6 +613,7 @@ class EEClassOptionalFields // for MethodTableBuilder and NativeImageDumper, which need raw field-level access. friend class EEClass; friend class MethodTableBuilder; + friend struct ::cdac_data; // // GENERICS RELATED FIELDS. @@ -1785,6 +1786,14 @@ template<> struct cdac_data static constexpr size_t NumThreadStaticFields = offsetof(EEClass, m_NumThreadStaticFields); static constexpr size_t NumNonVirtualSlots = offsetof(EEClass, m_NumNonVirtualSlots); static constexpr size_t BaseSizePadding = offsetof(EEClass, m_cbBaseSizePadding); + static constexpr size_t OptionalFields = offsetof(EEClass, m_rpOptionalFields); +}; + +template<> struct cdac_data +{ +#if defined(UNIX_AMD64_ABI) + static constexpr size_t EightByteRegistersInfo = offsetof(EEClassOptionalFields, m_eightByteRegistersInfo); +#endif // UNIX_AMD64_ABI }; // -------------------------------------------------------------------------------------------- diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index 8481dd43b88885..10d8174282d240 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -572,8 +572,27 @@ CDAC_TYPE_FIELD(EEClass, T_UINT16, NumStaticFields, cdac_data::NumStati CDAC_TYPE_FIELD(EEClass, T_UINT16, NumThreadStaticFields, cdac_data::NumThreadStaticFields) CDAC_TYPE_FIELD(EEClass, T_UINT16, NumNonVirtualSlots, cdac_data::NumNonVirtualSlots) CDAC_TYPE_FIELD(EEClass, T_UINT8, BaseSizePadding, cdac_data::BaseSizePadding) +CDAC_TYPE_FIELD(EEClass, T_POINTER, OptionalFields, cdac_data::OptionalFields) CDAC_TYPE_END(EEClass) +CDAC_TYPE_BEGIN(EEClassOptionalFields) +CDAC_TYPE_INDETERMINATE(EEClassOptionalFields) +#ifdef UNIX_AMD64_ABI +CDAC_TYPE_FIELD(EEClassOptionalFields, TYPE(SystemVEightByteRegistersInfo), EightByteRegistersInfo, cdac_data::EightByteRegistersInfo) +#endif // UNIX_AMD64_ABI +CDAC_TYPE_END(EEClassOptionalFields) + +#ifdef UNIX_AMD64_ABI +CDAC_TYPE_BEGIN(SystemVEightByteRegistersInfo) +CDAC_TYPE_INDETERMINATE(SystemVEightByteRegistersInfo) +CDAC_TYPE_FIELD(SystemVEightByteRegistersInfo, T_UINT8, NumEightBytes, cdac_data::NumEightBytes) +CDAC_TYPE_FIELD(SystemVEightByteRegistersInfo, T_UINT8, EightByteClassification0, cdac_data::EightByteClassification0) +CDAC_TYPE_FIELD(SystemVEightByteRegistersInfo, T_UINT8, EightByteClassification1, cdac_data::EightByteClassification1) +CDAC_TYPE_FIELD(SystemVEightByteRegistersInfo, T_UINT8, EightByteSize0, cdac_data::EightByteSize0) +CDAC_TYPE_FIELD(SystemVEightByteRegistersInfo, T_UINT8, EightByteSize1, cdac_data::EightByteSize1) +CDAC_TYPE_END(SystemVEightByteRegistersInfo) +#endif // UNIX_AMD64_ABI + CDAC_TYPE_BEGIN(GenericsDictInfo) CDAC_TYPE_INDETERMINATE(GenericsDictInfo) CDAC_TYPE_FIELD(GenericsDictInfo, T_UINT16, NumDicts, offsetof(GenericsDictInfo, m_wNumDicts)) diff --git a/src/coreclr/vm/methodtable.h b/src/coreclr/vm/methodtable.h index 563cc497ec4d94..10706c80144bf3 100644 --- a/src/coreclr/vm/methodtable.h +++ b/src/coreclr/vm/methodtable.h @@ -921,6 +921,18 @@ struct SystemVEightByteRegistersInfo _ASSERTE(index < NumEightBytes); return EightByteSizes[index]; } + + friend struct ::cdac_data; +}; + +template<> struct cdac_data +{ + static constexpr size_t NumEightBytes = offsetof(SystemVEightByteRegistersInfo, NumEightBytes); + static constexpr size_t EightByteClassification0 = offsetof(SystemVEightByteRegistersInfo, EightByteClassifications) + 0 * sizeof(SystemVClassificationType); + static constexpr size_t EightByteClassification1 = offsetof(SystemVEightByteRegistersInfo, EightByteClassifications) + 1 * sizeof(SystemVClassificationType); + static constexpr size_t EightByteSize0 = offsetof(SystemVEightByteRegistersInfo, EightByteSizes) + 0; + static constexpr size_t EightByteSize1 = offsetof(SystemVEightByteRegistersInfo, EightByteSizes) + 1; + static_assert(CLR_SYSTEMV_MAX_EIGHTBYTES_COUNT_TO_PASS_IN_REGISTERS == 2, "cdac descriptor exposes exactly two eightbyte slots"); }; #endif diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CallingConvention/ArgumentLocation.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CallingConvention/ArgumentLocation.cs index 2d1871d4840ede..c00ac4284a9ec9 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CallingConvention/ArgumentLocation.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CallingConvention/ArgumentLocation.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Internal.JitInterface; + namespace Microsoft.Diagnostics.DataContractReader.Contracts; internal readonly struct ArgumentLocation @@ -29,4 +31,12 @@ internal readonly struct ArgumentLocation // the open generic MethodTable (e.g. Span for a Span arg) so // encoders can inspect type structure as a fallback. public TypeHandle OpenGenericType { get; init; } + + // SystemV-AMD64 struct passed in registers. Offset is the StructInRegsOffset + // sentinel; the encoder consumes SysVEightByteDescriptor + SysVIdxGenReg. + public bool IsStructPassedInRegs { get; init; } + public SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR SysVEightByteDescriptor { get; init; } + // Index of the first general-purpose argument register assigned to this + // struct (0 = RDI, 1 = RSI, ...). + public int SysVIdxGenReg { get; init; } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CallingConvention/CallingConvention_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CallingConvention/CallingConvention_1.cs index 969d2875f6217d..86be2e82a66fb4 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CallingConvention/CallingConvention_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CallingConvention/CallingConvention_1.cs @@ -8,6 +8,7 @@ using System.Reflection.Metadata.Ecma335; using Internal.CallingConvention; using Internal.CorConstants; +using Internal.JitInterface; using Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; using Microsoft.Diagnostics.DataContractReader.SignatureHelpers; @@ -222,7 +223,25 @@ private ArgumentLayout GetArgumentLayout(MethodDescHandle methodDesc) } if (argOffset == TransitionBlock.StructInRegsOffset) - throw new NotImplementedException("SystemV AMD64 struct-in-registers is not yet supported by the cDAC."); + { + // SystemV-AMD64 struct-in-registers. + SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR sysvDesc; + parameterTypes[argIndex].GetSystemVAmd64PassStructInRegisterDescriptor(out sysvDesc); + ArgLocDesc loc = argit.GetArgLoc(argOffset) ?? throw new InvalidOperationException("ArgIterator returned null ArgLocDesc for struct-in-registers argument"); + + arguments.Add(new ArgumentLocation + { + Offset = argOffset, + ElementType = elemType, + TypeHandle = methodSig.ParameterTypes[argIndex], + IsStructPassedInRegs = true, + SysVEightByteDescriptor = sysvDesc, + SysVIdxGenReg = loc.m_idxGenReg, + OpenGenericType = paramInfo[argIndex].OpenGenericType, + }); + argIndex++; + continue; + } bool passedByRef = elemType == CdacCorElementType.ValueType && transitionBlock.IsArgPassedByRef(parameterTypes[argIndex]); @@ -602,6 +621,7 @@ public MethodAndTypeContextProvider(Target target, ModuleHandle moduleHandle, IR bool isX86 = arch is RuntimeInfoArchitecture.X86; int pointerSize = _target.PointerSize; + TransitionBlock tb = BuildTransitionBlock(runtimeInfo); SortedDictionary tokens = new(); ArgumentLayout enumeration = GetArgumentLayout(methodDesc); @@ -619,6 +639,33 @@ public MethodAndTypeContextProvider(Target target, ModuleHandle moduleHandle, IR { token = GCRefMapToken.VASigCookie; } + else if (arg.IsStructPassedInRegs) + { + // Mirrors ArgDestination::ReportPointersFromStructInRegisters + // in src/coreclr/vm/argdestination.h. + int genRegOffset = tb.OffsetOfArgumentRegisters + arg.SysVIdxGenReg * pointerSize; + for (int i = 0; i < arg.SysVEightByteDescriptor.eightByteCount; i++) + { + SystemVClassificationType cls = (i == 0) + ? arg.SysVEightByteDescriptor.eightByteClassifications0 + : arg.SysVEightByteDescriptor.eightByteClassifications1; + int size = (i == 0) + ? arg.SysVEightByteDescriptor.eightByteSizes0 + : arg.SysVEightByteDescriptor.eightByteSizes1; + + // SSE eightbytes go to XMM regs; don't advance genRegOffset. + if (cls == SystemVClassificationType.SystemVClassificationTypeSSE) + continue; + + if (cls == SystemVClassificationType.SystemVClassificationTypeIntegerReference) + tokens[genRegOffset] = GCRefMapToken.Ref; + else if (cls == SystemVClassificationType.SystemVClassificationTypeIntegerByRef) + tokens[genRegOffset] = GCRefMapToken.Interior; + + genRegOffset += size; + } + continue; + } else if (arg.IsParamType) { // Resolve InstArgMethodDesc vs InstArgMethodTable on demand @@ -741,7 +788,6 @@ public MethodAndTypeContextProvider(Target target, ModuleHandle moduleHandle, IR // the lowest positions). On non-x86 the mapping is monotonic so we // could iterate the offset map directly, but using OffsetFromGCRefMapPos // for both keeps the code path uniform. - TransitionBlock tb = BuildTransitionBlock(runtimeInfo); // For x86 we need to know how many slot positions exist (we'd otherwise // miss high-pos register slots when the offset map's max is on the diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CallingConvention/CdacTypeHandle.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CallingConvention/CdacTypeHandle.cs index 41988ead99c5a7..750b6012365365 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CallingConvention/CdacTypeHandle.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CallingConvention/CdacTypeHandle.cs @@ -121,7 +121,48 @@ public int GetHomogeneousAggregateElementSize() public void GetSystemVAmd64PassStructInRegisterDescriptor(out SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR descriptor) { - throw new NotImplementedException("SystemV AMD64 struct-in-registers is not yet supported by the cDAC."); + descriptor = default; + descriptor.passedInRegisters = false; + + if (_typeHandle.IsNull) + return; + + // Read the runtime-cached classification from EEClassOptionalFields; + // mirrors SystemVRegDescriptorFromSystemVEightByteRegistersInfo in + // jitinterface.cpp. Field only populated on UNIX_AMD64_ABI builds. + TargetPointer eeClassPtr = Rts.GetClassPointer(_typeHandle); + if (eeClassPtr == TargetPointer.Null) + return; + + Data.EEClass eeClass = _target.ProcessedData.GetOrAdd(eeClassPtr); + if (eeClass.OptionalFields == TargetPointer.Null) + return; + + Data.EEClassOptionalFields optFields = _target.ProcessedData.GetOrAdd(eeClass.OptionalFields); + if (optFields.EightByteRegistersInfo is not Data.SystemVEightByteRegistersInfo info) + return; + + if (info.NumEightBytes == 0) + return; + + // Runtime contract: at most CLR_SYSTEMV_MAX_EIGHTBYTES_COUNT_TO_PASS_IN_REGISTERS (2) + // eightbytes. Treat a corrupted / out-of-range count as "not passed in + // registers" so downstream loops keyed off eightByteCount can't overrun. + if (info.NumEightBytes > 2) + return; + + descriptor.passedInRegisters = true; + descriptor.eightByteCount = info.NumEightBytes; + descriptor.eightByteClassifications0 = (SystemVClassificationType)info.EightByteClassification0; + descriptor.eightByteSizes0 = info.EightByteSize0; + descriptor.eightByteOffsets0 = 0; + + if (info.NumEightBytes > 1) + { + descriptor.eightByteClassifications1 = (SystemVClassificationType)info.EightByteClassification1; + descriptor.eightByteSizes1 = info.EightByteSize1; + descriptor.eightByteOffsets1 = SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR.SYSTEMV_EIGHT_BYTE_SIZE_IN_BYTES; + } } public FpStructInRegistersInfo GetFpStructInRegistersInfo(Internal.TypeSystem.TargetArchitecture architecture) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanner.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanner.cs index d62d01e68bc1d4..99adf052797a1c 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanner.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanner.cs @@ -337,27 +337,6 @@ private TargetPointer FindGCRefMap(TargetPointer indirection) /// private void PromoteCallerStack(TargetPointer frameAddress, GcScanContext scanContext) { - IRuntimeInfo runtimeInfo = _target.Contracts.RuntimeInfo; - RuntimeInfoArchitecture arch = runtimeInfo.GetTargetArchitecture(); - RuntimeInfoOperatingSystem os = runtimeInfo.GetTargetOperatingSystem(); - // Matches the ARGITER stress-test gate in CdacStressTests.cs. Platforms - // still missing calling-convention coverage (SystemV-AMD64 / RISC-V / - // LoongArch / WASM) fall through to RecordDeferredFrame. - // TODO(https://github.com/dotnet/runtime/issues/130008): extend - // ICallingConvention.TryComputeArgGCRefMapBlob coverage to the - // remaining targets so this path fires everywhere. - bool supportedByCallingConvention = - (os is RuntimeInfoOperatingSystem.Windows - && arch is RuntimeInfoArchitecture.X86 or RuntimeInfoArchitecture.X64 or RuntimeInfoArchitecture.Arm64) - || (os is RuntimeInfoOperatingSystem.Unix - && arch is RuntimeInfoArchitecture.Arm or RuntimeInfoArchitecture.Arm64); - - if (!supportedByCallingConvention) - { - scanContext.RecordDeferredFrame(frameAddress); - return; - } - Data.FramedMethodFrame fmf = _target.ProcessedData.GetOrAdd(frameAddress); if (fmf.MethodDescPtr == TargetPointer.Null) { diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EEClass.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EEClass.cs index 2fcbd027729c4b..2b8f495c507d73 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EEClass.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EEClass.cs @@ -25,4 +25,5 @@ internal sealed partial class EEClass : IData [Field] public TargetPointer FieldDescList { get; } [Field] public ushort NumNonVirtualSlots { get; } [Field] public byte BaseSizePadding { get; } + [Field] public TargetPointer OptionalFields { get; } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EEClassOptionalFields.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EEClassOptionalFields.cs new file mode 100644 index 00000000000000..a35baa6c0d3f7a --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EEClassOptionalFields.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +[CdacType(nameof(DataType.EEClassOptionalFields))] +internal sealed partial class EEClassOptionalFields : IData +{ + [Field] public SystemVEightByteRegistersInfo? EightByteRegistersInfo { get; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/SystemVEightByteRegistersInfo.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/SystemVEightByteRegistersInfo.cs new file mode 100644 index 00000000000000..f11fd63462d198 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/SystemVEightByteRegistersInfo.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +[CdacType(nameof(DataType.SystemVEightByteRegistersInfo))] +internal sealed partial class SystemVEightByteRegistersInfo : IData +{ + // Slots beyond NumEightBytes are undefined. + [Field] public byte NumEightBytes { get; } + [Field] public byte EightByteClassification0 { get; } + [Field] public byte EightByteClassification1 { get; } + [Field] public byte EightByteSize0 { get; } + [Field] public byte EightByteSize1 { get; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/DataType.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/DataType.cs index e7d2e874b36bc4..365c5c142e86bc 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/DataType.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/DataType.cs @@ -62,6 +62,8 @@ public enum DataType MethodTable, DynamicStaticsInfo, EEClass, + EEClassOptionalFields, + SystemVEightByteRegistersInfo, CoreLibBinder, MethodTableAuxiliaryData, GenericsDictInfo, diff --git a/src/native/managed/cdac/gen/Parser.cs b/src/native/managed/cdac/gen/Parser.cs index f6fc1739596760..9a30e8ce8d0840 100644 --- a/src/native/managed/cdac/gen/Parser.cs +++ b/src/native/managed/cdac/gen/Parser.cs @@ -357,6 +357,12 @@ private static (FieldReadKind, string?, bool) ClassifyFieldRead(IPropertySymbol isNullable = true; type = named.TypeArguments[0]; } + else if (ImplementsIData(type) && prop.NullableAnnotation == NullableAnnotation.Annotated) + { + // IData? field: emitter treats it as optional (ContainsKey + // guard + default(null) when the descriptor omits the field). + isNullable = true; + } string fqn = type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); diff --git a/src/native/managed/cdac/tests/StressTests/CdacStressTestBase.cs b/src/native/managed/cdac/tests/StressTests/CdacStressTestBase.cs index b490417c0bef2b..5caa35b130d11d 100644 --- a/src/native/managed/cdac/tests/StressTests/CdacStressTestBase.cs +++ b/src/native/managed/cdac/tests/StressTests/CdacStressTestBase.cs @@ -157,7 +157,8 @@ private async Task RunStressAsync(string debuggeeName, Stress /// /// Asserts the GCREFS stress run produced a [GC_STATS] summary - /// with at least one verification and no hard failures. + /// with at least one verification, no hard failures, and no deferred + /// (known-issue) frames. /// internal static void AssertAllPassed(CdacStressResults results, string debuggeeName) { @@ -173,37 +174,13 @@ internal static void AssertAllPassed(CdacStressResults results, string debuggeeN "did not initialize correctly.\n" + $"Log: {results.LogFilePath}"); - if (results.Failed > 0) + if (results.Failed > 0 || results.KnownIssues > 0) { string analysis = results.AnalyzeFailures(maxFailures: 3); Assert.Fail( $"GCREFS stress test '{debuggeeName}' had {results.Failed} failure(s) " + - $"out of {results.TotalVerifications} verifications " + - $"({results.KnownIssues} known issue(s) tolerated).\n" + - $"Log: {results.LogFilePath}\n\n{analysis}"); - } - - // On supported targets every Frame's caller-arg refs are enumerated via - // the GCRefMap blob synthesized by ICallingConvention -- there should be - // no deferred frames at all, so any KnownIssue count is a regression. - // Keep this gate in sync with the ARGITER-support gate in - // ArgIterStress_AllVerificationsPass below and with the - // supportedByCallingConvention gate in GcScanner.PromoteCallerStack. - GetTargetPlatform(out OSPlatform os, out Architecture arch); - bool requiresZeroKnownIssues = - (os == OSPlatform.Windows && arch is Architecture.X86 or Architecture.X64 or Architecture.Arm64) - || (os == OSPlatform.Linux && arch is Architecture.Arm or Architecture.Arm64); - if (requiresZeroKnownIssues && results.KnownIssues > 0) - { - string analysis = results.AnalyzeFailures(maxFailures: 3); - Assert.Fail( - $"GCREFS stress test '{debuggeeName}' had {results.KnownIssues} known issue(s) " + - $"out of {results.TotalVerifications} verifications. " + - "This platform is expected to enumerate every transition Frame's " + - "caller-stack refs via ICallingConvention.TryComputeArgGCRefMapBlob with no " + - "deferred frames. A non-zero KnownIssues count indicates the encoder declined " + - "a method it should support (e.g. a regression in ComputeArgGCRefMapBlobCore " + - "or a new code path returning E_NOTIMPL).\n" + + $"and {results.KnownIssues} known issue(s) out of " + + $"{results.TotalVerifications} verifications.\n" + $"Log: {results.LogFilePath}\n\n{analysis}"); } } diff --git a/src/native/managed/cdac/tests/StressTests/CdacStressTests.cs b/src/native/managed/cdac/tests/StressTests/CdacStressTests.cs index 47d3132c39640e..eeaa46877e0002 100644 --- a/src/native/managed/cdac/tests/StressTests/CdacStressTests.cs +++ b/src/native/managed/cdac/tests/StressTests/CdacStressTests.cs @@ -52,9 +52,6 @@ public async Task GCRefStress_AllVerificationsPass(Debuggee debuggee) { GetTargetPlatform(out OSPlatform os, out _); - // TODO(https://github.com/dotnet/runtime/issues/130008): extend GCREFS stress coverage to non-Windows / ARM - // targets once ICallingConvention.TryComputeArgGCRefMapBlob supports them (SystemV-AMD64 / ARM64 - // struct-in-register classification, ARM32 ABI port). if (debuggee.WindowsOnly && os != OSPlatform.Windows) throw new SkipTestException($"{debuggee.Name} debuggee is Windows-only."); @@ -69,30 +66,11 @@ public async Task GCRefStress_AllVerificationsPass(Debuggee debuggee) [MemberData(nameof(Debuggees))] public async Task ArgIterStress_AllVerificationsPass(Debuggee debuggee) { - GetTargetPlatform(out OSPlatform os, out Architecture arch); + GetTargetPlatform(out OSPlatform os, out _); if (debuggee.WindowsOnly && os != OSPlatform.Windows) throw new SkipTestException($"{debuggee.Name} debuggee is Windows-only."); - // ARGITER stress requires a CdacTypeHandle whose calling-convention - // helpers are filled in for the target ABI. Currently validated: - // - Windows x86 / x64 (TransitionBlock + IsTrivialPointerSizedStruct) - // - Windows ARM64 (HFA via MethodTable enum_flag_IsHFA) - // - Linux ARM / ARM64 (HFA, same path) - // Still skipped: - // - Linux / macOS x64 (SystemV-AMD64 eightbyte classifier not ported) - // - RISC-V / LoongArch64 (FP struct classifier not ported) - // - WASM32 (GetFieldAlignment not ported) - bool argIterSupported = - (os == OSPlatform.Windows && arch is Architecture.X86 or Architecture.X64 or Architecture.Arm64) - || (os == OSPlatform.Linux && arch is Architecture.Arm or Architecture.Arm64); - if (!argIterSupported) - throw new SkipTestException( - "ARGITER stress: needs follow-up work for this platform " + - "(SystemV-AMD64 struct classifier for linux/macOS x64, " + - "Windows ARM32 ABI port, RISC-V / LoongArch FP struct classifier, " + - "or WASM GetFieldAlignment)."); - CdacStressResults results = await RunArgIterStressAsync(debuggee.Name); AssertAllArgIterPassed(results, debuggee.Name); } diff --git a/src/native/managed/cdac/tests/StressTests/Debuggees/CallSignatures/Program.cs b/src/native/managed/cdac/tests/StressTests/Debuggees/CallSignatures/Program.cs index 82b40fbb26d034..95521b24c47cfd 100644 --- a/src/native/managed/cdac/tests/StressTests/Debuggees/CallSignatures/Program.cs +++ b/src/native/managed/cdac/tests/StressTests/Debuggees/CallSignatures/Program.cs @@ -95,6 +95,7 @@ private static void Drive() EnumCategory(); ReturnCategory(); HfaCategory(); + SysVCategory(); DeepStackCategory(); } @@ -537,7 +538,105 @@ private struct NestedDouble4 { public Double2 First; public Double2 Second; } [MethodImpl(MethodImplOptions.NoInlining)] private static void Vec128FloatArg(Vector128 v) { AllocBurst(); GC.KeepAlive((object)v.GetElement(0)); } [MethodImpl(MethodImplOptions.NoInlining)] private static void VecNumericsFloatArg(Vector v) { AllocBurst(); GC.KeepAlive((object)v[0]); } - // ===== Category 13: deep stack ===== + // ===== Category 13: SystemV-AMD64 struct-in-register shapes ===== + // On non-Unix-x64 targets the cDAC's descriptor is absent so classification + // returns "not in registers" and the runtime path agrees trivially. Linux/ + // macOS x64 read the cached SystemVEightByteRegistersInfo and produce + // different GCRefMap tokens per eightbyte classification. + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void SysVCategory() + { + // Single-eightbyte shapes. + SysVIntPair(default); // Integer + SysVFloatPair(default); // SSE + SysVIntFloat(default); // mixed -> Integer (Integer wins) + SysVSingleRef(default); // IntegerReference + + // Two-eightbyte shapes. + SysVLongLong(default); // Integer + Integer + SysVLongDouble(default); // Integer + SSE + SysVDoubleDouble(default); // SSE + SSE + SysVRefInt(default); // IntegerReference + Integer + + SysVNested(default); // nested value type + + // Span: IntegerByRef + Integer. + Span sp = stackalloc byte[8]; + SysVSpanByte(sp); + + SysVThreeRefs(default); // 24 bytes -> stack + SysVVector128Arg(default); // intrinsic Vector -> stack + SysVInt128Arg(default); // 2 Integer eightbytes (runtime doesn't reject Int128 by name) + SysVEmpty(default); // 1-byte empty -> 1 Integer eightbyte (NoClass normalized to Integer) + // SSE + IntegerReference: ref goes in RDI, not RSI -- SSE eightbytes + // don't advance the general-register cursor. + SysVDoubleRef(default); + + // Unions (LayoutKind.Explicit) -- exercises the classifier's merge path. + SysVIntFloatUnion(default); // int + float @ 0 -> Integer + SysVIntIntUnion(default); // int + int @ 0 -> Integer + + SysVSmall(default); // { int a; byte b; } -> Integer (unaligned tail) + SysVWrapInt(default); // { int a; } single-field wrapper + } + + // ---- SystemV struct shapes ---- + private struct IntPair { public int A, B; } + private struct FloatPair { public float A, B; } + private struct IntFloat { public int A; public float B; } + private struct SingleRef { public object? R; } + private struct LongLong { public long A, B; } + private struct LongDouble { public long A; public double B; } + private struct DoubleDouble { public double A, B; } + private struct RefInt { public object? R; public long Padding; } + private struct DoubleRef { public double D; public object? R; } + private struct SysVNestedStruct { public IntPair Inner; public int Trailing; } + private struct SysVThreeRefsStruct { public object? R1, R2, R3; } + private struct Int128Wrapper { public System.Int128 V; } + private struct EmptyStruct { } + private struct SmallUnaligned { public int A; public byte B; } + private struct WrapInt { public int A; } + + // Union: int and float at the same offset. Classifier sees both as + // unique-offset fields and merges via ReClassifyField (Integer+SSE = Integer). + [StructLayout(LayoutKind.Explicit)] + private struct IntFloatUnion + { + [FieldOffset(0)] public int I; + [FieldOffset(0)] public float F; + } + + // Union: two ints at the same offset. Merge collapses to Integer. + [StructLayout(LayoutKind.Explicit)] + private struct IntIntUnion + { + [FieldOffset(0)] public int A; + [FieldOffset(0)] public int B; + } + + // ---- SystemV arg helpers ---- + [MethodImpl(MethodImplOptions.NoInlining)] private static void SysVIntPair(IntPair s) { AllocBurst(); GC.KeepAlive((object)(s.A + s.B)); } + [MethodImpl(MethodImplOptions.NoInlining)] private static void SysVFloatPair(FloatPair s) { AllocBurst(); GC.KeepAlive((object)(s.A + s.B)); } + [MethodImpl(MethodImplOptions.NoInlining)] private static void SysVIntFloat(IntFloat s) { AllocBurst(); GC.KeepAlive((object)(s.A + s.B)); } + [MethodImpl(MethodImplOptions.NoInlining)] private static void SysVSingleRef(SingleRef s) { AllocBurst(); GC.KeepAlive(s.R); } + [MethodImpl(MethodImplOptions.NoInlining)] private static void SysVLongLong(LongLong s) { AllocBurst(); GC.KeepAlive((object)(s.A + s.B)); } + [MethodImpl(MethodImplOptions.NoInlining)] private static void SysVLongDouble(LongDouble s) { AllocBurst(); GC.KeepAlive((object)(s.A + s.B)); } + [MethodImpl(MethodImplOptions.NoInlining)] private static void SysVDoubleDouble(DoubleDouble s) { AllocBurst(); GC.KeepAlive((object)(s.A + s.B)); } + [MethodImpl(MethodImplOptions.NoInlining)] private static void SysVRefInt(RefInt s) { AllocBurst(); GC.KeepAlive(s.R); GC.KeepAlive((object)s.Padding); } + [MethodImpl(MethodImplOptions.NoInlining)] private static void SysVDoubleRef(DoubleRef s) { AllocBurst(); GC.KeepAlive(s.R); GC.KeepAlive((object)s.D); } + [MethodImpl(MethodImplOptions.NoInlining)] private static void SysVNested(SysVNestedStruct s) { AllocBurst(); GC.KeepAlive((object)(s.Inner.A + s.Trailing)); } + [MethodImpl(MethodImplOptions.NoInlining)] private static void SysVSpanByte(Span s) { AllocBurst(); GC.KeepAlive((object)s.Length); } + [MethodImpl(MethodImplOptions.NoInlining)] private static void SysVThreeRefs(SysVThreeRefsStruct s) { AllocBurst(); GC.KeepAlive(s.R1); GC.KeepAlive(s.R2); GC.KeepAlive(s.R3); } + [MethodImpl(MethodImplOptions.NoInlining)] private static void SysVVector128Arg(Vector128 v) { AllocBurst(); GC.KeepAlive((object)v.GetElement(0)); } + [MethodImpl(MethodImplOptions.NoInlining)] private static void SysVInt128Arg(Int128Wrapper s) { AllocBurst(); GC.KeepAlive((object)s.V.ToString()); } + [MethodImpl(MethodImplOptions.NoInlining)] private static void SysVEmpty(EmptyStruct s) { AllocBurst(); GC.KeepAlive((object)s); } + [MethodImpl(MethodImplOptions.NoInlining)] private static void SysVSmall(SmallUnaligned s) { AllocBurst(); GC.KeepAlive((object)(s.A + s.B)); } + [MethodImpl(MethodImplOptions.NoInlining)] private static void SysVWrapInt(WrapInt s) { AllocBurst(); GC.KeepAlive((object)s.A); } + [MethodImpl(MethodImplOptions.NoInlining)] private static void SysVIntFloatUnion(IntFloatUnion s) { AllocBurst(); GC.KeepAlive((object)s.I); } + [MethodImpl(MethodImplOptions.NoInlining)] private static void SysVIntIntUnion(IntIntUnion s) { AllocBurst(); GC.KeepAlive((object)s.A); } + + // ===== Category 14: deep stack ===== // Mutually-recursive chains of methods with mixed signatures. At any // given allocation trigger many frames are simultaneously live, so a // single stack-walk verification run touches multiple MDs across diff --git a/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.RuntimeTypeSystem.cs b/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.RuntimeTypeSystem.cs index 182f5935b60ed8..9e58b5b2d2bfae 100644 --- a/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.RuntimeTypeSystem.cs +++ b/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.RuntimeTypeSystem.cs @@ -108,6 +108,7 @@ internal sealed class MockEEClass : TypedView private const string FieldDescListFieldName = nameof(Data.EEClass.FieldDescList); private const string NumNonVirtualSlotsFieldName = nameof(Data.EEClass.NumNonVirtualSlots); private const string BaseSizePaddingFieldName = nameof(Data.EEClass.BaseSizePadding); + private const string OptionalFieldsFieldName = nameof(Data.EEClass.OptionalFields); public static Layout CreateLayout(MockTarget.Architecture architecture) => new SequentialLayoutBuilder("EEClass", architecture) @@ -122,6 +123,7 @@ public static Layout CreateLayout(MockTarget.Architecture architect .AddPointerField(FieldDescListFieldName) .AddUInt16Field(NumNonVirtualSlotsFieldName) .AddByteField(BaseSizePaddingFieldName) + .AddPointerField(OptionalFieldsFieldName) .Build(); public ulong MethodTable