diff --git a/docs/design/datacontracts/RuntimeTypeSystem.md b/docs/design/datacontracts/RuntimeTypeSystem.md index c9d32f319f8fc7..d61fdbbf281a8f 100644 --- a/docs/design/datacontracts/RuntimeTypeSystem.md +++ b/docs/design/datacontracts/RuntimeTypeSystem.md @@ -75,6 +75,11 @@ partial interface IRuntimeTypeSystem : IContract public virtual bool ContainsGCPointers(TypeHandle typeHandle); // True if the MethodTable represents a byref-like value type (Span, ReadOnlySpan, any ref struct). public virtual bool IsByRefLike(TypeHandle typeHandle); + // If the type is an HFA (or HVA on ARM64), returns true and sets elementSize + // to 4, 8, or 16. Returns false otherwise (including on targets that don't + // define FEATURE_HFA). Mirrors MethodTable::GetHFAType in + // src/coreclr/vm/class.cpp. + public virtual bool TryGetHFAElementSize(TypeHandle typeHandle, out int elementSize); // True if the type requires 8-byte alignment on platforms that don't 8-byte align by default (FEATURE_64BIT_ALIGNMENT) public virtual bool RequiresAlign8(TypeHandle typeHandle); // True if the MethodTable represents a continuation type used by the async continuation feature @@ -339,6 +344,9 @@ internal partial struct RuntimeTypeSystem_1 IsByRefLike = 0x00001000, // value type that may contain managed pointers (e.g. Span, ReadOnlySpan) + IsHFA = 0x00000800, // ARM/ARM64 only (FEATURE_HFA): Homogeneous Floating-point Aggregate. + // On UNIX_AMD64_ABI this bit is reused as IsRegStructPassed. + StringArrayValues = GenericsMask_NonGeneric, } @@ -371,6 +379,7 @@ internal partial struct RuntimeTypeSystem_1 internal enum WFLAGS2_ENUM : uint { DynamicStatics = 0x0002, + IsIntrinsicType = 0x0020, } // Encapsulates the MethodTable flags v1 uses @@ -410,10 +419,12 @@ internal partial struct RuntimeTypeSystem_1 public bool RequiresAlign8 => GetFlag(WFLAGS_HIGH.RequiresAlign8) != 0; public bool IsCollectible => GetFlag(WFLAGS_HIGH.Collectible) != 0; public bool IsDynamicStatics => GetFlag(WFLAGS2_ENUM.DynamicStatics) != 0; + public bool IsIntrinsicType => GetFlag(WFLAGS2_ENUM.IsIntrinsicType) != 0; public bool IsTrackedReferenceWithFinalizer => GetFlag(WFLAGS_HIGH.IsTrackedReferenceWithFinalizer) != 0; public bool IsGenericTypeDefinition => TestFlagWithMask(WFLAGS_LOW.GenericsMask, WFLAGS_LOW.GenericsMask_TypicalInstantiation); public bool IsSharedByGenericInstantiations => TestFlagWithMask(WFLAGS_LOW.GenericsMask, WFLAGS_LOW.GenericsMask_SharedInst); public bool IsByRefLike => TestFlagWithMask(WFLAGS_LOW.IsByRefLike, WFLAGS_LOW.IsByRefLike); + public bool IsHFA => TestFlagWithMask(WFLAGS_LOW.IsHFA, WFLAGS_LOW.IsHFA); } [Flags] @@ -680,6 +691,35 @@ Contracts used: public bool IsByRefLike(TypeHandle typeHandle) => typeHandle.IsMethodTable() && _methodTables[typeHandle.Address].Flags.IsByRefLike; + // Mirrors MethodTable::GetHFAType in src/coreclr/vm/class.cpp. Pseudocode: + // + // TryGetHFAElementSize(th): + // if targetArch is not ARM/ARM64: return false + // if !th.Flags.IsHFA: return false + // if targetArch is ARM: return (true, th.Flags.RequiresAlign8 ? 8 : 4) + // mt = th + // loop (bounded depth): + // if (elem = GetVectorHFAElementSize(mt)): return (true, elem) + // field = first non-static field of mt + // if field is null: return false + // switch field.ElementType: + // R4: return (true, 4) + // R8: return (true, 8) + // ValueType: mt = GetFieldDescApproxTypeHandle(field); continue + // default: return false + // + // GetVectorHFAElementSize(mt): // detects HVA shapes + // if !mt.Flags.IsIntrinsicType: return 0 + // (ns, name) = typedef name+namespace via EcmaMetadata + // elem = match on (ns, name): + // "System.Numerics", "Vector`1": NumInstanceFieldBytes (8 or 16, else 0) + // "System.Runtime.Intrinsics", "Vector128`1": 16 + // "System.Runtime.Intrinsics", "Vector64`1": 8 + // _: return 0 + // if !CorIsNumericalType(GetInstantiation(mt)[0]): return 0 + // return elem + public bool TryGetHFAElementSize(TypeHandle typeHandle, out int elementSize) { ... } + public bool RequiresAlign8(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[typeHandle.Address].Flags.RequiresAlign8; public bool IsCanonicalMethodTable(TypeHandle typeHandle) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs index e5c6750e1a5406..253e4e2f0f3e6a 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs @@ -152,6 +152,15 @@ public interface IRuntimeTypeSystem : IContract bool ContainsGCPointers(TypeHandle typeHandle) => throw new NotImplementedException(); // True if MethodTable represents a byreflike value (Span, ReadOnlySpan, etc.). bool IsByRefLike(TypeHandle typeHandle) => throw new NotImplementedException(); + // If the type is an HFA (or HVA on ARM64), returns true and sets elementSize + // to 4, 8, or 16. Returns false otherwise (including on targets that don't + // define FEATURE_HFA). Mirrors MethodTable::GetHFAType in + // src/coreclr/vm/class.cpp. + bool TryGetHFAElementSize(TypeHandle typeHandle, out int elementSize) + { + elementSize = 0; + throw new NotImplementedException(); + } // True if the type requires 8-byte alignment on platforms that don't 8-byte align by default (FEATURE_64BIT_ALIGNMENT) bool RequiresAlign8(TypeHandle typeHandle) => throw new NotImplementedException(); // True if the MethodTable represents a continuation subtype that has no metadata of its own 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 a5b1c8882badcc..41988ead99c5a7 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 @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Diagnostics; using Internal.CallingConvention; using Internal.JitInterface; @@ -110,22 +111,12 @@ public bool RequiresAlign8() } public bool IsHomogeneousAggregate() - { - if (Arch is not RuntimeInfoArchitecture.Arm and not RuntimeInfoArchitecture.Arm64) - return false; - - // TODO(hfa): Implement HFA detection for ARM/ARM64. - // See crossgen2 TypeHandle.IsHomogeneousAggregate(). - throw new NotImplementedException("HFA detection for ARM/ARM64 is not yet implemented."); - } + => !_typeHandle.IsNull && Rts.TryGetHFAElementSize(_typeHandle, out _); public int GetHomogeneousAggregateElementSize() { - if (Arch is not RuntimeInfoArchitecture.Arm and not RuntimeInfoArchitecture.Arm64) - return 0; - - // TODO(hfa): Return 4 for float HFA, 8 for double HFA, 16 for Vector128 HFA. - throw new NotImplementedException("HFA element size for ARM/ARM64 is not yet implemented."); + Debug.Assert(IsHomogeneousAggregate()); + return Rts.TryGetHFAElementSize(_typeHandle, out int size) ? size : 0; } public void GetSystemVAmd64PassStructInRegisterDescriptor(out SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR descriptor) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs index cf079229ee4e22..e1a72c20e8eb27 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs @@ -614,6 +614,153 @@ public TargetPointer GetWellKnownMethodTable(WellKnownMethodTable kind) public bool ContainsGCPointers(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[typeHandle.Address].Flags.ContainsGCPointers; public bool IsByRefLike(TypeHandle typeHandle) => typeHandle.IsMethodTable() && _methodTables[typeHandle.Address].Flags.IsByRefLike; + + private bool IsFeatureHfaTarget(out RuntimeInfoArchitecture arch) + { + arch = _target.Contracts.RuntimeInfo.GetTargetArchitecture(); + return arch is RuntimeInfoArchitecture.Arm or RuntimeInfoArchitecture.Arm64; + } + + public bool TryGetHFAElementSize(TypeHandle typeHandle, out int elementSize) + { + elementSize = 0; + + if (!IsFeatureHfaTarget(out RuntimeInfoArchitecture arch)) + return false; + + if (!typeHandle.IsMethodTable() || !_methodTables[typeHandle.Address].Flags.IsHFA) + return false; + + // ARM shortcut: no HVA, and RequiresAlign8 encodes the R4-vs-R8 choice + // (CheckForHFA sets the alignment flag based on the resolved element + // type). Avoids walking fields. + if (arch == RuntimeInfoArchitecture.Arm) + { + elementSize = _methodTables[typeHandle.Address].Flags.RequiresAlign8 ? 8 : 4; + return true; + } + + TypeHandle current = typeHandle; + for (int depth = 0; depth < 16; depth++) + { + int vectorElem = GetVectorHFAElementSize(current); + if (vectorElem != 0) + { + elementSize = vectorElem; + return true; + } + + TargetPointer firstField = TargetPointer.Null; + foreach (TargetPointer fd in ((IRuntimeTypeSystem)this).GetFieldDescList(current)) + { + if (((IRuntimeTypeSystem)this).IsFieldDescStatic(fd)) + continue; + firstField = fd; + break; + } + + if (firstField == TargetPointer.Null) + return false; + + CorElementType ft = ((IRuntimeTypeSystem)this).GetFieldDescType(firstField); + switch (ft) + { + case CorElementType.R4: + elementSize = 4; + return true; + case CorElementType.R8: + elementSize = 8; + return true; + case CorElementType.ValueType: + current = ((IRuntimeTypeSystem)this).GetFieldDescApproxTypeHandle(firstField); + if (current.IsNull) + return false; + continue; + default: + return false; + } + } + + return false; + } + + // Mirrors MethodTable::GetVectorHFA in src/coreclr/vm/class.cpp. Any + // metadata decode failure returns 0 (treated as "not an HVA"). + private int GetVectorHFAElementSize(TypeHandle typeHandle) + { + if (!typeHandle.IsMethodTable() || !_methodTables[typeHandle.Address].Flags.IsIntrinsicType) + return 0; + + try + { + TargetPointer modulePtr = ((IRuntimeTypeSystem)this).GetModule(typeHandle); + if (modulePtr == TargetPointer.Null) + return 0; + + ModuleHandle moduleHandle = _target.Contracts.Loader.GetModuleHandleFromModulePtr(modulePtr); + MetadataReader? reader = _target.Contracts.EcmaMetadata.GetMetadata(moduleHandle); + if (reader is null) + return 0; + + uint typeDefToken = ((IRuntimeTypeSystem)this).GetTypeDefToken(typeHandle); + if (EcmaMetadataUtils.GetRowId(typeDefToken) == 0) + return 0; + + EntityHandle handle = (EntityHandle)MetadataTokens.Handle((int)typeDefToken); + if (handle.Kind != HandleKind.TypeDefinition) + return 0; + + TypeDefinition typeDef = reader.GetTypeDefinition((TypeDefinitionHandle)handle); + string className = reader.GetString(typeDef.Name); + string namespaceName = reader.GetString(typeDef.Namespace); + + int elemSize; + if (className == "Vector`1" && namespaceName == "System.Numerics") + { + elemSize = ((IRuntimeTypeSystem)this).GetNumInstanceFieldBytes(typeHandle) switch + { + 8 => 8, + 16 => 16, + _ => 0, + }; + } + else if (className == "Vector128`1" && namespaceName == "System.Runtime.Intrinsics") + { + elemSize = 16; + } + else if (className == "Vector64`1" && namespaceName == "System.Runtime.Intrinsics") + { + elemSize = 8; + } + else + { + return 0; + } + + if (elemSize == 0) + return 0; + + ReadOnlySpan instantiation = ((IRuntimeTypeSystem)this).GetInstantiation(typeHandle); + if (instantiation.Length < 1) + return 0; + + CorElementType argType = ((IRuntimeTypeSystem)this).GetSignatureCorElementType(instantiation[0]); + if (!IsCorNumericalType(argType)) + return 0; + + return elemSize; + } + catch + { + return 0; + } + } + + // Mirrors CorIsNumericalType in src/coreclr/inc/cor.h. + private static bool IsCorNumericalType(CorElementType t) + => (t >= CorElementType.I1 && t <= CorElementType.R8) + || t == CorElementType.I + || t == CorElementType.U; public bool RequiresAlign8(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[typeHandle.Address].Flags.RequiresAlign8; public bool IsContinuationWithoutMetadata(TypeHandle typeHandle) => typeHandle.IsMethodTable() && ContinuationMethodTablePointer != TargetPointer.Null 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 9b165587267952..d62d01e68bc1d4 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 @@ -340,12 +340,17 @@ private void PromoteCallerStack(TargetPointer frameAddress, GcScanContext scanCo IRuntimeInfo runtimeInfo = _target.Contracts.RuntimeInfo; RuntimeInfoArchitecture arch = runtimeInfo.GetTargetArchitecture(); RuntimeInfoOperatingSystem os = runtimeInfo.GetTargetOperatingSystem(); - // TODO(https://github.com/dotnet/runtime/issues/130008): extend ICallingConvention.TryComputeArgGCRefMapBlob - // coverage to non-Windows / ARM targets (SystemV-AMD64 / ARM64 struct-in-register classification, ARM32 ABI - // port) so this path is taken on those targets too instead of deferring to RecordDeferredFrame. + // 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; + (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) { diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodTableFlags_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodTableFlags_1.cs index e60f24bc90960e..a7b7e8e5c437fd 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodTableFlags_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodTableFlags_1.cs @@ -27,6 +27,7 @@ internal enum WFLAGS_LOW : uint GenericsMask_TypicalInstantiation = 0x00000030, // the type instantiated at its formal parameters, e.g. List IsByRefLike = 0x00001000, // value type that may contain managed pointers (e.g. Span, ReadOnlySpan) + IsHFA = 0x00000800, // ARM/ARM64 only (FEATURE_HFA); reused on UNIX_AMD64_ABI as IsRegStructPassed StringArrayValues = GenericsMask_NonGeneric | @@ -62,6 +63,7 @@ internal enum WFLAGS_HIGH : uint internal enum WFLAGS2_ENUM : uint { DynamicStatics = 0x0002, + IsIntrinsicType = 0x0020, } public uint MTFlags { get; init; } @@ -110,9 +112,11 @@ private bool TestFlagWithMask(WFLAGS2_ENUM mask, WFLAGS2_ENUM flag) public bool IsCollectible => GetFlag(WFLAGS_HIGH.Collectible) != 0; public bool IsTrackedReferenceWithFinalizer => GetFlag(WFLAGS_HIGH.IsTrackedReferenceWithFinalizer) != 0; public bool IsDynamicStatics => GetFlag(WFLAGS2_ENUM.DynamicStatics) != 0; + public bool IsIntrinsicType => GetFlag(WFLAGS2_ENUM.IsIntrinsicType) != 0; public bool IsGenericTypeDefinition => TestFlagWithMask(WFLAGS_LOW.GenericsMask, WFLAGS_LOW.GenericsMask_TypicalInstantiation); public bool IsSharedByGenericInstantiations => TestFlagWithMask(WFLAGS_LOW.GenericsMask, WFLAGS_LOW.GenericsMask_SharedInst); public bool IsByRefLike => TestFlagWithMask(WFLAGS_LOW.IsByRefLike, WFLAGS_LOW.IsByRefLike); + public bool IsHFA => TestFlagWithMask(WFLAGS_LOW.IsHFA, WFLAGS_LOW.IsHFA); public bool ContainsGenericVariables => GetFlag(WFLAGS_HIGH.ContainsGenericVariables) != 0; internal static EEClassOrCanonMTBits GetEEClassOrCanonMTBits(TargetPointer eeClassOrCanonMTPtr) diff --git a/src/native/managed/cdac/tests/StressTests/CdacStressTestBase.cs b/src/native/managed/cdac/tests/StressTests/CdacStressTestBase.cs index 02d1631d07a49d..b490417c0bef2b 100644 --- a/src/native/managed/cdac/tests/StressTests/CdacStressTestBase.cs +++ b/src/native/managed/cdac/tests/StressTests/CdacStressTestBase.cs @@ -186,16 +186,20 @@ internal static void AssertAllPassed(CdacStressResults results, string debuggeeN // 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; + (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. " + - "Windows x86 / x64 are expected to enumerate every transition Frame's " + + "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 " + diff --git a/src/native/managed/cdac/tests/StressTests/CdacStressTests.cs b/src/native/managed/cdac/tests/StressTests/CdacStressTests.cs index d71fc7fe901250..47d3132c39640e 100644 --- a/src/native/managed/cdac/tests/StressTests/CdacStressTests.cs +++ b/src/native/managed/cdac/tests/StressTests/CdacStressTests.cs @@ -74,14 +74,24 @@ public async Task ArgIterStress_AllVerificationsPass(Debuggee debuggee) if (debuggee.WindowsOnly && os != OSPlatform.Windows) throw new SkipTestException($"{debuggee.Name} debuggee is Windows-only."); - // Scope of this PR: ARGITER is validated on Windows x86 / x64 - // only. Other architectures hit known gaps that need follow-up - // work (SystemV-AMD64 / ARM64 struct-in-register classification, - // arm32 ABI port). Skip there until those land. - if (os != OSPlatform.Windows || arch is not (Architecture.X86 or Architecture.X64)) + // 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 is validated for windows-x86 / windows-x64 in this PR; " + - "other targets need follow-up work (SystemV / ARM64 struct-in-registers, ARM32 ABI port)."); + "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 83b3c4e5b645ad..82b40fbb26d034 100644 --- a/src/native/managed/cdac/tests/StressTests/Debuggees/CallSignatures/Program.cs +++ b/src/native/managed/cdac/tests/StressTests/Debuggees/CallSignatures/Program.cs @@ -3,8 +3,10 @@ using System; using System.IO; +using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; /// /// Exhaustive cdacstress ArgIterator debuggee. Covers a wide variety of @@ -26,6 +28,8 @@ /// - Generic value-type instance methods (interface dispatch) /// - Enum arguments (Int32-, Int64-, byte-backed) /// - Large-struct return (HasRetBuffArg) +/// - HFA / HVA: float, double, Vector64, Vector128, System.Numerics.Vector +/// plus negative shapes (too many fields, mixed FP types) /// - Mutually-recursive deep stack /// /// __arglist / vararg coverage lives in the dedicated VarArgs debuggee @@ -90,6 +94,7 @@ private static void Drive() GenericCategory(); EnumCategory(); ReturnCategory(); + HfaCategory(); DeepStackCategory(); } @@ -437,7 +442,102 @@ private static void ReturnCategory() [MethodImpl(MethodImplOptions.NoInlining)] private static BigStruct ReturnLarge() { AllocBurst(); return new BigStruct { A = 1 }; } [MethodImpl(MethodImplOptions.NoInlining)] private static Span ReturnSpan(Span s) { AllocBurst(); return s; } - // ===== Category 12: deep stack ===== + // ===== Category 12: HFA / HVA ===== + // HFAs (Homogeneous Floating-point Aggregates) and HVAs (Homogeneous Vector + // Aggregates) are classified at MethodTable layout time on FEATURE_HFA + // targets (ARM / ARM64). The ArgIterator code path for HFA/HVA args is + // distinct from regular structs, so these debuggee methods exist primarily + // to exercise the cDAC's TryGetHFAElementSize walker on ARM/ARM64. On + // Windows x86/x64 the runtime does not classify these types as HFAs, so + // they go through the regular struct path -- the cDAC matches that, and + // the ARGITER stress assertion still passes. + // + // Negative shapes (Float5, MixedR4R8) are included so the walker correctly + // returns "not an HFA" -- the runtime never sets the IsHFA flag on these. + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void HfaCategory() + { + // Float HFAs of legal arity (1..4 elements). + PassFloat2(default); + PassFloat3(default); + PassFloat4(default); + + // Double HFAs of legal arity. + PassDouble2(default); + PassDouble3(default); + PassDouble4(default); + + // Nested HFAs: still classified as HFAs because the recursive + // first-field walker reaches R4/R8. + PassNestedFloat3(default); + PassNestedDouble4(default); + + // Mixed-in args: HFA in register, refs elsewhere, ensures + // ArgIterator correctly accounts for FP register consumption + // when emitting GCRefMap tokens for the trailing INTEGER args. + HfaThenRef(default, "x"); + RefThenHfa("x", default); + TwoFloatHfas(default, default); + + // Returns: HFA returns use FP-register return paths on ARM/ARM64, + // not the integer-return path, so HasRetBuffArg should be false. + _ = ReturnFloat3(); + _ = ReturnDouble2(); + + // HVAs (ARM64 only at the runtime level; classification falls + // through to "not HFA" on other targets). All passed as default. + Vec64FloatArg(default); + Vec128FloatArg(default); + VecNumericsFloatArg(default); + + // Negative cases -- runtime does not flag these as HFAs. + PassFloat5(default); + PassMixedR4R8(default); + } + + // --- HFA struct shapes --- + private struct Float2 { public float A, B; } + private struct Float3 { public float A, B, C; } + private struct Float4 { public float A, B, C, D; } + private struct Float5 { public float A, B, C, D, E; } // >4 fields: not HFA + private struct Double2 { public double A, B; } + private struct Double3 { public double A, B, C; } + private struct Double4 { public double A, B, C, D; } + private struct MixedR4R8 { public float A; public double B; } // mixed FP: not HFA + + // Nested HFA: first field is itself an HFA, total still <=4 R4/R8 elements. + private struct NestedFloat3 { public Float2 Inner; public float C; } + private struct NestedDouble4 { public Double2 First; public Double2 Second; } + + // --- HFA arg / return helpers --- + [MethodImpl(MethodImplOptions.NoInlining)] private static void PassFloat2(Float2 s) { AllocBurst(); GC.KeepAlive((object)(s.A + s.B)); } + [MethodImpl(MethodImplOptions.NoInlining)] private static void PassFloat3(Float3 s) { AllocBurst(); GC.KeepAlive((object)(s.A + s.B + s.C)); } + [MethodImpl(MethodImplOptions.NoInlining)] private static void PassFloat4(Float4 s) { AllocBurst(); GC.KeepAlive((object)(s.A + s.B + s.C + s.D)); } + [MethodImpl(MethodImplOptions.NoInlining)] private static void PassFloat5(Float5 s) { AllocBurst(); GC.KeepAlive((object)(s.A + s.E)); } + [MethodImpl(MethodImplOptions.NoInlining)] private static void PassDouble2(Double2 s) { AllocBurst(); GC.KeepAlive((object)(s.A + s.B)); } + [MethodImpl(MethodImplOptions.NoInlining)] private static void PassDouble3(Double3 s) { AllocBurst(); GC.KeepAlive((object)(s.A + s.B + s.C)); } + [MethodImpl(MethodImplOptions.NoInlining)] private static void PassDouble4(Double4 s) { AllocBurst(); GC.KeepAlive((object)(s.A + s.B + s.C + s.D)); } + [MethodImpl(MethodImplOptions.NoInlining)] private static void PassMixedR4R8(MixedR4R8 s) { AllocBurst(); GC.KeepAlive((object)(s.A + s.B)); } + [MethodImpl(MethodImplOptions.NoInlining)] private static void PassNestedFloat3(NestedFloat3 s) { AllocBurst(); GC.KeepAlive((object)(s.Inner.A + s.C)); } + [MethodImpl(MethodImplOptions.NoInlining)] private static void PassNestedDouble4(NestedDouble4 s) { AllocBurst(); GC.KeepAlive((object)(s.First.A + s.Second.B)); } + + [MethodImpl(MethodImplOptions.NoInlining)] private static void HfaThenRef(Float3 s, string r) { AllocBurst(); GC.KeepAlive((object)(s.A)); GC.KeepAlive(r); } + [MethodImpl(MethodImplOptions.NoInlining)] private static void RefThenHfa(string r, Float3 s) { AllocBurst(); GC.KeepAlive(r); GC.KeepAlive((object)(s.A)); } + [MethodImpl(MethodImplOptions.NoInlining)] private static void TwoFloatHfas(Float2 a, Float2 b) { AllocBurst(); GC.KeepAlive((object)(a.A + b.A)); } + + [MethodImpl(MethodImplOptions.NoInlining)] private static Float3 ReturnFloat3() { AllocBurst(); return default; } + [MethodImpl(MethodImplOptions.NoInlining)] private static Double2 ReturnDouble2() { AllocBurst(); return default; } + + // --- HVA shapes (intrinsic Vector types). On non-FEATURE_HFA targets + // these go through the regular struct path; on ARM64 they hit the + // GetVectorHFAElementSize TypeDef-name match (Vector64/128 in + // System.Runtime.Intrinsics, Vector in System.Numerics). --- + [MethodImpl(MethodImplOptions.NoInlining)] private static void Vec64FloatArg(Vector64 v) { AllocBurst(); GC.KeepAlive((object)v.GetElement(0)); } + [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 ===== // 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