Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CoreCLR NativeAOT - ByRef generics and ByRef implementing interfaces #98070

Merged
merged 8 commits into from
Mar 2, 2024
Merged
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
126 changes: 126 additions & 0 deletions docs/design/features/byreflike-generics.md
Original file line number Diff line number Diff line change
@@ -127,3 +127,129 @@ The following are IL sequences involving the `box` instruction. They are used fo
`box` ; `isinst` ; `unbox.any` – The box, `isint`, and unbox target types are all equal.

`box` ; `isinst` ; `br_true/false` &ndash; The box target type is equal to the unboxed target type or the box target type is `Nullable<T>` and target type equalities can be computed.

## Examples

Below are valid and invalid examples of ByRefLike as Generic parameters. All examples use the **not official** syntax, `allows ref struct`, for indicating the Generic permits ByRefLike types.

**1) Valid**
```csharp
class A<T1> where T1: allows ref struct
{
public void M();
}

// The derived class is okay to lack the 'allows'
// because the base permits non-ByRefLike (default)
// _and_ ByRefLike types.
class B<T2> : A<T2>
{
public void N()
=> M(); // Any T2 satisfies the constraints from A<>
}
```

**2) Invalid**
```csharp
class A<T1>
{
public void M();
}

// The derived class cannot push up the allows
// constraint for ByRefLike types.
class B<T2> : A<T2> where T2: allows ref struct
{
public void N()
=> M(); // A<> may not permit a T2
}
```

**3) Valid**
```csharp
interface IA
{
void M();
}

ref struct A : IA
{
public void M() { }
}

class B
{
// This call is permitted because no boxing is needed
// to dispatch to the method - it is implemented on A.
public static void C<T>(T t) where T: IA, allows ref struct
=> t.M();
}
```

**4) Invalid**
```csharp
interface IA
{
public void M() { }
}

ref struct A : IA
{
// Relies on IA::M() implementation.
}

class B
{
// Reliance on a DIM forces the generic parameter
// to be boxed, which is invalid for ByRefLike types.
public static void C<T>(T t) where T: IA, allows ref struct
=> t.M();
}
```

**5) Valid**
```csharp
class A<T1> where T1: allows ref struct
{
}

class B<T2>
{
// The type parameter is okay to lack the 'allows'
// because the field permits non-ByRefLike (default)
// _and_ ByRefLike types.
A<T2> Field;
}
```

**6) Invalid**
```csharp
class A<T1>
{
}

class B<T2> where T2: allows ref struct
{
// The type parameter can be passed to
// the field type, but will fail if
// T2 is a ByRefLike type.
A<T2> Field;
}
```

**7) Invalid**
```csharp
class A
{
virtual void M<T1>() where T1: allows ref struct;
}

class B : A
{
// Override methods need to match be at least
// as restrictive with respect to constraints.
// If a user has an instance of A, they are
// not aware they could be calling B.
override void M<T2>();
}
```
2 changes: 1 addition & 1 deletion src/coreclr/ilasm/asmparse.y
Original file line number Diff line number Diff line change
@@ -486,7 +486,7 @@ typarAttrib : '+' { $$ = gpCovariant;
| '-' { $$ = gpContravariant; }
| CLASS_ { $$ = gpReferenceTypeConstraint; }
| VALUETYPE_ { $$ = gpNotNullableValueTypeConstraint; }
| BYREFLIKE_ { $$ = gpAcceptByRefLike; }
| BYREFLIKE_ { $$ = gpAllowByRefLike; }
| _CTOR { $$ = gpDefaultConstructorConstraint; }
| FLAGS_ '(' int32 ')' { $$ = (CorGenericParamAttr)$3; }
;
2 changes: 1 addition & 1 deletion src/coreclr/ilasm/prebuilt/asmparse.cpp
Original file line number Diff line number Diff line change
@@ -2523,7 +2523,7 @@ case 152:
{ yyval.int32 = gpNotNullableValueTypeConstraint; } break;
case 153:
#line 489 "asmparse.y"
{ yyval.int32 = gpAcceptByRefLike; } break;
{ yyval.int32 = gpAllowByRefLike; } break;
case 154:
#line 490 "asmparse.y"
{ yyval.int32 = gpDefaultConstructorConstraint; } break;
2 changes: 1 addition & 1 deletion src/coreclr/ildasm/dasm.cpp
Original file line number Diff line number Diff line change
@@ -3081,7 +3081,7 @@ char *DumpGenericPars(_Inout_updates_(SZSTRING_SIZE) char* szString, mdToken tok
if ((attr & gpNotNullableValueTypeConstraint) != 0)
szptr += sprintf_s(szptr,SZSTRING_REMAINING_SIZE(szptr), "valuetype ");
CHECK_REMAINING_SIZE;
if ((attr & gpAcceptByRefLike) != 0)
if ((attr & gpAllowByRefLike) != 0)
szptr += sprintf_s(szptr,SZSTRING_REMAINING_SIZE(szptr), "byreflike ");
CHECK_REMAINING_SIZE;
if ((attr & gpDefaultConstructorConstraint) != 0)
2 changes: 1 addition & 1 deletion src/coreclr/inc/corhdr.h
Original file line number Diff line number Diff line change
@@ -847,7 +847,7 @@ typedef enum CorGenericParamAttr
gpReferenceTypeConstraint = 0x0004, // type argument must be a reference type
gpNotNullableValueTypeConstraint = 0x0008, // type argument must be a value type but not Nullable
gpDefaultConstructorConstraint = 0x0010, // type argument must have a public default constructor
gpAcceptByRefLike = 0x0020, // type argument can be ByRefLike
gpAllowByRefLike = 0x0020, // type argument can be ByRefLike
} CorGenericParamAttr;

// structures and enums moved from COR.H
Original file line number Diff line number Diff line change
@@ -13,9 +13,9 @@ internal static partial class ConstraintValidator
{
private static bool SatisfiesConstraints(this Type genericVariable, SigTypeContext typeContextOfConstraintDeclarer, Type typeArg)
{
GenericParameterAttributes specialConstraints = genericVariable.GenericParameterAttributes & GenericParameterAttributes.SpecialConstraintMask;
GenericParameterAttributes attributes = genericVariable.GenericParameterAttributes;

if ((specialConstraints & GenericParameterAttributes.NotNullableValueTypeConstraint) != 0)
if ((attributes & GenericParameterAttributes.NotNullableValueTypeConstraint) != 0)
{
if (!typeArg.IsValueType)
{
@@ -30,19 +30,19 @@ private static bool SatisfiesConstraints(this Type genericVariable, SigTypeConte
}
}

if ((specialConstraints & GenericParameterAttributes.ReferenceTypeConstraint) != 0)
if ((attributes & GenericParameterAttributes.ReferenceTypeConstraint) != 0)
{
if (typeArg.IsValueType)
return false;
}

if ((specialConstraints & GenericParameterAttributes.DefaultConstructorConstraint) != 0)
if ((attributes & GenericParameterAttributes.DefaultConstructorConstraint) != 0)
{
if (!typeArg.HasExplicitOrImplicitPublicDefaultConstructor())
return false;
}

if (typeArg.IsByRefLike && (specialConstraints & (GenericParameterAttributes)0x20 /* GenericParameterAttributes.AcceptByRefLike */) == 0)
if (typeArg.IsByRefLike && (attributes & (GenericParameterAttributes)0x20 /* GenericParameterAttributes.AllowByRefLike */) == 0)
return false;

// Now check general subtype constraints
Original file line number Diff line number Diff line change
@@ -61,7 +61,7 @@ public enum GenericConstraints
/// <summary>
/// A type is permitted to be ByRefLike.
/// </summary>
AcceptByRefLike = 0x20,
AllowByRefLike = 0x20,
}

public abstract partial class GenericParameterDesc : TypeDesc
@@ -159,13 +159,13 @@ public bool HasDefaultConstructorConstraint
}

/// <summary>
/// Does this generic parameter have the AcceptByRefLike flag
/// Does this generic parameter have the AllowByRefLike flag
/// </summary>
public bool HasAcceptByRefLikeConstraint
public bool HasAllowByRefLikeConstraint
{
get
{
return (Constraints & GenericConstraints.AcceptByRefLike) != 0;
return (Constraints & GenericConstraints.AllowByRefLike) != 0;
}
}

Original file line number Diff line number Diff line change
@@ -50,7 +50,7 @@ private static bool VerifyGenericParamConstraint(InstantiationContext genericPar
}

// Check for ByRefLike support
if (instantiationParam.IsByRefLike && (constraints & GenericConstraints.AcceptByRefLike) == 0)
if (instantiationParam.IsByRefLike && (constraints & GenericConstraints.AllowByRefLike) == 0)
return false;

var instantiatedConstraints = default(ArrayBuilder<TypeDesc>);
Original file line number Diff line number Diff line change
@@ -113,7 +113,7 @@ public override GenericConstraints Constraints
{
Debug.Assert((int)GenericConstraints.DefaultConstructorConstraint == (int)GenericParameterAttributes.DefaultConstructorConstraint);
GenericParameter parameter = _module.MetadataReader.GetGenericParameter(_handle);
const GenericParameterAttributes mask = GenericParameterAttributes.SpecialConstraintMask | (GenericParameterAttributes)GenericConstraints.AcceptByRefLike;
const GenericParameterAttributes mask = GenericParameterAttributes.SpecialConstraintMask | (GenericParameterAttributes)GenericConstraints.AllowByRefLike;
return (GenericConstraints)(parameter.Attributes & mask);
}
}
Original file line number Diff line number Diff line change
@@ -59,7 +59,7 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false)
if ((genericParameter.Attributes & GenericParameterAttributes.VarianceMask) != GenericParameterAttributes.None)
hasVariance = true;

if ((genericParameter.Attributes & (GenericParameterAttributes.SpecialConstraintMask | (GenericParameterAttributes)GenericConstraints.AcceptByRefLike)) != default(GenericParameterAttributes) ||
if ((genericParameter.Attributes & (GenericParameterAttributes.SpecialConstraintMask | (GenericParameterAttributes)GenericConstraints.AllowByRefLike)) != default(GenericParameterAttributes) ||
(genericParameter.GetConstraints().Count > 0))
{
hasConstraints = true;
Original file line number Diff line number Diff line change
@@ -107,7 +107,7 @@ Task<bool> ValidateTypeWorkerHelper(TypeDesc typeToCheckForSkipValidation)
// The runtime has a number of checks in the type loader which it will skip running if the SkipValidation flag is set
// This function attempts to document all of them, and implement *some* of them.

// This function performs a portion of the validation skipping that has been found to have some importance, or to serve as
// This function performs a portion of the validation skipping that has been found to have some importance, or to serve as
// In addition, there are comments about all validation skipping activities that the runtime will perform.
try
{
@@ -488,8 +488,9 @@ static bool CompareGenericParameterConstraint(MethodDesc declMethod, GenericPara
if (!parameterOfDecl.HasReferenceTypeConstraint)
return false;

if (parameterOfDecl.HasAcceptByRefLikeConstraint)
if (!parameterOfImpl.HasAcceptByRefLikeConstraint)
// Constraints that 'allow' must check the impl first
if (parameterOfImpl.HasAllowByRefLikeConstraint)
if (!parameterOfDecl.HasAllowByRefLikeConstraint)
return false;

HashSet<TypeDesc> constraintsOnDecl = new HashSet<TypeDesc>();
Original file line number Diff line number Diff line change
@@ -244,6 +244,7 @@ public class CallConvSuppressGCTransition { }
public static class RuntimeFeature
{
public const string ByRefFields = nameof(ByRefFields);
public const string ByRefLikeGenerics = nameof(ByRefLikeGenerics);
public const string UnmanagedSignatureCallingConvention = nameof(UnmanagedSignatureCallingConvention);
public const string VirtualStaticsInInterfaces = nameof(VirtualStaticsInInterfaces);
}
6 changes: 4 additions & 2 deletions src/coreclr/vm/siginfo.cpp
Original file line number Diff line number Diff line change
@@ -4813,9 +4813,11 @@ BOOL MetaSig::CompareVariableConstraints(const Substitution *pSubst1,
if ((specialConstraints2 & (gpDefaultConstructorConstraint | gpNotNullableValueTypeConstraint)) == 0)
return FALSE;
}
if ((specialConstraints1 & gpAcceptByRefLike) != 0)

// Constraints that 'allow' must check the overridden first
if ((specialConstraints2 & gpAllowByRefLike) != 0)
{
if ((specialConstraints2 & gpAcceptByRefLike) == 0)
if ((specialConstraints1 & gpAllowByRefLike) == 0)
return FALSE;
}
}
2 changes: 1 addition & 1 deletion src/coreclr/vm/typedesc.cpp
Original file line number Diff line number Diff line change
@@ -1512,7 +1512,7 @@ BOOL TypeVarTypeDesc::SatisfiesConstraints(SigTypeContext *pTypeContextOfConstra
return FALSE;
}

if (thArg.IsByRefLike() && (specialConstraints & gpAcceptByRefLike) == 0)
if (thArg.IsByRefLike() && (specialConstraints & gpAllowByRefLike) == 0)
return FALSE;
}

Original file line number Diff line number Diff line change
@@ -14,5 +14,6 @@ public enum GenericParameterAttributes
ReferenceTypeConstraint = 0x0004,
NotNullableValueTypeConstraint = 0x0008,
DefaultConstructorConstraint = 0x0010,
AllowByRefLike = 0x0020,
}
}
Original file line number Diff line number Diff line change
@@ -32,6 +32,11 @@ public static partial class RuntimeFeature
/// </summary>
public const string ByRefFields = nameof(ByRefFields);

/// <summary>
/// Represents a runtime feature where byref-like types can be used in Generic parameters.
/// </summary>
public const string ByRefLikeGenerics = nameof(ByRefLikeGenerics);

/// <summary>
/// Indicates that this version of runtime supports virtual static members of interfaces.
/// </summary>
@@ -52,6 +57,7 @@ public static bool IsSupported(string feature)
case PortablePdb:
case CovariantReturnsOfClasses:
case ByRefFields:
case ByRefLikeGenerics:
case UnmanagedSignatureCallingConvention:
case DefaultImplementationsOfInterfaces:
case VirtualStaticsInInterfaces:
Original file line number Diff line number Diff line change
@@ -692,7 +692,7 @@ public override bool IsAssignableFrom([NotNullWhen(true)] Type? c)

if (constraint.IsGenericParameter)
{
GenericParameterAttributes special = constraint.GenericParameterAttributes & GenericParameterAttributes.SpecialConstraintMask;
GenericParameterAttributes special = constraint.GenericParameterAttributes;

if ((special & GenericParameterAttributes.ReferenceTypeConstraint) == 0 &&
(special & GenericParameterAttributes.NotNullableValueTypeConstraint) == 0)
@@ -704,7 +704,7 @@ public override bool IsAssignableFrom([NotNullWhen(true)] Type? c)

if (baseType == ObjectType)
{
GenericParameterAttributes special = GenericParameterAttributes & GenericParameterAttributes.SpecialConstraintMask;
GenericParameterAttributes special = GenericParameterAttributes;
if ((special & GenericParameterAttributes.NotNullableValueTypeConstraint) != 0)
baseType = ValueType;
}
2 changes: 2 additions & 0 deletions src/libraries/System.Runtime/ref/System.Runtime.cs
Original file line number Diff line number Diff line change
@@ -11588,6 +11588,7 @@ public enum GenericParameterAttributes
NotNullableValueTypeConstraint = 8,
DefaultConstructorConstraint = 16,
SpecialConstraintMask = 28,
AllowByRefLike = 32,
}
public partial interface ICustomAttributeProvider
{
@@ -13102,6 +13103,7 @@ public RuntimeCompatibilityAttribute() { }
public static partial class RuntimeFeature
{
public const string ByRefFields = "ByRefFields";
public const string ByRefLikeGenerics = "ByRefLikeGenerics";
public const string CovariantReturnsOfClasses = "CovariantReturnsOfClasses";
public const string DefaultImplementationsOfInterfaces = "DefaultImplementationsOfInterfaces";
public const string NumericIntPtr = "NumericIntPtr";
Original file line number Diff line number Diff line change
@@ -806,6 +806,10 @@ private static IEnumerable<ITestInfo> GetTestMethodInfosForMethod(IMethodSymbol
// If we're building tests not for Mono, we can skip handling the specifics of the SkipOnMonoAttribute.
continue;
}
if (filterAttribute.ConstructorArguments.Length <= 1)
{
return ImmutableArray<ITestInfo>.Empty;
}
testInfos = DecorateWithSkipOnPlatform(testInfos, (int)filterAttribute.ConstructorArguments[1].Value!, options);
break;
case "Xunit.SkipOnPlatformAttribute":
Loading