Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/coreclr/vm/class.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<EEClassOptionalFields>;

//
// GENERICS RELATED FIELDS.
Expand Down Expand Up @@ -1785,6 +1786,14 @@ template<> struct cdac_data<EEClass>
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<EEClassOptionalFields>
{
#if defined(UNIX_AMD64_ABI)
static constexpr size_t EightByteRegistersInfo = offsetof(EEClassOptionalFields, m_eightByteRegistersInfo);
#endif // UNIX_AMD64_ABI
};

// --------------------------------------------------------------------------------------------
Expand Down
19 changes: 19 additions & 0 deletions src/coreclr/vm/datadescriptor/datadescriptor.inc
Original file line number Diff line number Diff line change
Expand Up @@ -572,8 +572,27 @@ CDAC_TYPE_FIELD(EEClass, T_UINT16, NumStaticFields, cdac_data<EEClass>::NumStati
CDAC_TYPE_FIELD(EEClass, T_UINT16, NumThreadStaticFields, cdac_data<EEClass>::NumThreadStaticFields)
CDAC_TYPE_FIELD(EEClass, T_UINT16, NumNonVirtualSlots, cdac_data<EEClass>::NumNonVirtualSlots)
CDAC_TYPE_FIELD(EEClass, T_UINT8, BaseSizePadding, cdac_data<EEClass>::BaseSizePadding)
CDAC_TYPE_FIELD(EEClass, T_POINTER, OptionalFields, cdac_data<EEClass>::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<EEClassOptionalFields>::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<SystemVEightByteRegistersInfo>::NumEightBytes)
CDAC_TYPE_FIELD(SystemVEightByteRegistersInfo, T_UINT8, EightByteClassification0, cdac_data<SystemVEightByteRegistersInfo>::EightByteClassification0)
CDAC_TYPE_FIELD(SystemVEightByteRegistersInfo, T_UINT8, EightByteClassification1, cdac_data<SystemVEightByteRegistersInfo>::EightByteClassification1)
CDAC_TYPE_FIELD(SystemVEightByteRegistersInfo, T_UINT8, EightByteSize0, cdac_data<SystemVEightByteRegistersInfo>::EightByteSize0)
CDAC_TYPE_FIELD(SystemVEightByteRegistersInfo, T_UINT8, EightByteSize1, cdac_data<SystemVEightByteRegistersInfo>::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))
Expand Down
12 changes: 12 additions & 0 deletions src/coreclr/vm/methodtable.h
Original file line number Diff line number Diff line change
Expand Up @@ -921,6 +921,18 @@ struct SystemVEightByteRegistersInfo
_ASSERTE(index < NumEightBytes);
return EightByteSizes[index];
}

friend struct ::cdac_data<SystemVEightByteRegistersInfo>;
};

template<> struct cdac_data<SystemVEightByteRegistersInfo>
{
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

Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -29,4 +31,12 @@ internal readonly struct ArgumentLocation
// the open generic MethodTable (e.g. Span<T> for a Span<int> 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; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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");

Comment thread
max-charlamb marked this conversation as resolved.
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]);
Expand Down Expand Up @@ -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<int, GCRefMapToken> tokens = new();
ArgumentLayout enumeration = GetArgumentLayout(methodDesc);
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Comment thread
max-charlamb marked this conversation as resolved.
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<Data.EEClass>(eeClassPtr);
if (eeClass.OptionalFields == TargetPointer.Null)
return;

Data.EEClassOptionalFields optFields = _target.ProcessedData.GetOrAdd<Data.EEClassOptionalFields>(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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -337,27 +337,6 @@ private TargetPointer FindGCRefMap(TargetPointer indirection)
/// </summary>
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<Data.FramedMethodFrame>(frameAddress);
if (fmf.MethodDescPtr == TargetPointer.Null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@ internal sealed partial class EEClass : IData<EEClass>
[Field] public TargetPointer FieldDescList { get; }
[Field] public ushort NumNonVirtualSlots { get; }
[Field] public byte BaseSizePadding { get; }
[Field] public TargetPointer OptionalFields { get; }
}
Original file line number Diff line number Diff line change
@@ -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<EEClassOptionalFields>
{
[Field] public SystemVEightByteRegistersInfo? EightByteRegistersInfo { get; }
}
Original file line number Diff line number Diff line change
@@ -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<SystemVEightByteRegistersInfo>
{
// 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; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ public enum DataType
MethodTable,
DynamicStaticsInfo,
EEClass,
EEClassOptionalFields,
SystemVEightByteRegistersInfo,
Comment thread
max-charlamb marked this conversation as resolved.
CoreLibBinder,
Comment thread
max-charlamb marked this conversation as resolved.
Comment thread
max-charlamb marked this conversation as resolved.
MethodTableAuxiliaryData,
GenericsDictInfo,
Expand Down
6 changes: 6 additions & 0 deletions src/native/managed/cdac/gen/Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>? field: emitter treats it as optional (ContainsKey
// guard + default(null) when the descriptor omits the field).
isNullable = true;
}

string fqn = type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);

Expand Down
33 changes: 5 additions & 28 deletions src/native/managed/cdac/tests/StressTests/CdacStressTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,8 @@ private async Task<CdacStressResults> RunStressAsync(string debuggeeName, Stress

/// <summary>
/// Asserts the GCREFS stress run produced a <c>[GC_STATS]</c> summary
/// with at least one verification and no hard failures.
/// with at least one verification, no hard failures, and no deferred
/// (known-issue) frames.
/// </summary>
internal static void AssertAllPassed(CdacStressResults results, string debuggeeName)
{
Expand All @@ -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}");
}
}
Expand Down
Loading
Loading