Skip to content
40 changes: 40 additions & 0 deletions docs/design/datacontracts/RuntimeTypeSystem.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>, ReadOnlySpan<T>, 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
Expand Down Expand Up @@ -339,6 +344,9 @@ internal partial struct RuntimeTypeSystem_1

IsByRefLike = 0x00001000, // value type that may contain managed pointers (e.g. Span<T>, ReadOnlySpan<T>)

IsHFA = 0x00000800, // ARM/ARM64 only (FEATURE_HFA): Homogeneous Floating-point Aggregate.
// On UNIX_AMD64_ABI this bit is reused as IsRegStructPassed.

StringArrayValues = GenericsMask_NonGeneric,
}

Expand Down Expand Up @@ -371,6 +379,7 @@ internal partial struct RuntimeTypeSystem_1
internal enum WFLAGS2_ENUM : uint
{
DynamicStatics = 0x0002,
IsIntrinsicType = 0x0020,
}

// Encapsulates the MethodTable flags v1 uses
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,15 @@ public interface IRuntimeTypeSystem : IContract
bool ContainsGCPointers(TypeHandle typeHandle) => throw new NotImplementedException();
// True if MethodTable represents a byreflike value (Span<T>, ReadOnlySpan<T>, 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();
}
Comment thread
max-charlamb marked this conversation as resolved.
Comment thread
max-charlamb marked this conversation as resolved.
Comment thread
max-charlamb marked this conversation as resolved.
// 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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;
}
Comment thread
max-charlamb marked this conversation as resolved.

public void GetSystemVAmd64PassStructInRegisterDescriptor(out SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR descriptor)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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++)
{
Comment thread
max-charlamb marked this conversation as resolved.
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)
Comment thread
max-charlamb marked this conversation as resolved.
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<TypeHandle> 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ internal enum WFLAGS_LOW : uint
GenericsMask_TypicalInstantiation = 0x00000030, // the type instantiated at its formal parameters, e.g. List<T>

IsByRefLike = 0x00001000, // value type that may contain managed pointers (e.g. Span<T>, ReadOnlySpan<T>)
IsHFA = 0x00000800, // ARM/ARM64 only (FEATURE_HFA); reused on UNIX_AMD64_ABI as IsRegStructPassed

StringArrayValues =
GenericsMask_NonGeneric |
Expand Down Expand Up @@ -62,6 +63,7 @@ internal enum WFLAGS_HIGH : uint
internal enum WFLAGS2_ENUM : uint
{
DynamicStatics = 0x0002,
IsIntrinsicType = 0x0020,
}

public uint MTFlags { get; init; }
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Comment thread
max-charlamb marked this conversation as resolved.
Comment thread
max-charlamb marked this conversation as resolved.
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 " +
Expand Down
Loading
Loading