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
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
126 changes: 126 additions & 0 deletions docs/design/features/byreflike-generics.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Up @@ -486,7 +486,7 @@ typarAttrib : '+' { $$ = gpCovariant;
| '-' { $$ = gpContravariant; }
| CLASS_ { $$ = gpReferenceTypeConstraint; }
| VALUETYPE_ { $$ = gpNotNullableValueTypeConstraint; }
| BYREFLIKE_ { $$ = gpAcceptByRefLike; }
| BYREFLIKE_ { $$ = gpAllowByRefLike; }
| _CTOR { $$ = gpDefaultConstructorConstraint; }
| FLAGS_ '(' int32 ')' { $$ = (CorGenericParamAttr)$3; }
;
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/ilasm/prebuilt/asmparse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/ildasm/dasm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/inc/corhdr.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
6 changes: 4 additions & 2 deletions src/coreclr/vm/siginfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/vm/typedesc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Up @@ -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>
Expand All @@ -52,6 +57,7 @@ public static bool IsSupported(string feature)
case PortablePdb:
case CovariantReturnsOfClasses:
case ByRefFields:
case ByRefLikeGenerics:
case UnmanagedSignatureCallingConvention:
case DefaultImplementationsOfInterfaces:
case VirtualStaticsInInterfaces:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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;
}
Expand Down
2 changes: 2 additions & 0 deletions src/libraries/System.Runtime/ref/System.Runtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11588,6 +11588,7 @@ public enum GenericParameterAttributes
NotNullableValueTypeConstraint = 8,
DefaultConstructorConstraint = 16,
SpecialConstraintMask = 28,
AllowByRefLike = 32,
}
public partial interface ICustomAttributeProvider
{
Expand Down Expand Up @@ -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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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":
Expand Down
Loading