diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp
index 7d010edb1cfcc0..e280b6c999a7e6 100644
--- a/src/coreclr/jit/gentree.cpp
+++ b/src/coreclr/jit/gentree.cpp
@@ -13839,6 +13839,50 @@ GenTree* Compiler::gtFoldExprCall(GenTreeCall* call)
break;
}
+ case NI_System_Enum_Equals:
+ {
+ assert(call->AsCall()->gtArgs.CountUserArgs() == 2);
+ GenTree* arg0 = call->AsCall()->gtArgs.GetArgByIndex(0)->GetNode();
+ GenTree* arg1 = call->AsCall()->gtArgs.GetArgByIndex(1)->GetNode();
+
+ bool isArg0Exact;
+ bool isArg1Exact;
+ bool isNonNull; // Unused here.
+
+ CORINFO_CLASS_HANDLE cls0 = gtGetClassHandle(arg0, &isArg0Exact, &isNonNull);
+ CORINFO_CLASS_HANDLE cls1 = gtGetClassHandle(arg1, &isArg1Exact, &isNonNull);
+ if ((cls0 != cls1) || (cls0 == NO_CLASS_HANDLE) || !isArg0Exact || !isArg1Exact)
+ {
+ break;
+ }
+
+ assert(info.compCompHnd->isEnum(cls1, nullptr) == TypeCompareState::Must);
+
+ CorInfoType corTyp = info.compCompHnd->getTypeForPrimitiveValueClass(cls1);
+ if (corTyp == CORINFO_TYPE_UNDEF)
+ {
+ break;
+ }
+
+ var_types typ = JITtype2varType(corTyp);
+ if (!varTypeIsIntegral(typ))
+ {
+ // Ignore non-integral enums e.g. enums based on float/double
+ break;
+ }
+
+ // Unbox both integral arguments and compare their underlying values
+ GenTree* offset = gtNewIconNode(TARGET_POINTER_SIZE, TYP_I_IMPL);
+ GenTree* addr0 = gtNewOperNode(GT_ADD, TYP_BYREF, arg0, offset);
+ GenTree* addr1 = gtNewOperNode(GT_ADD, TYP_BYREF, arg1, gtCloneExpr(offset));
+ GenTree* cmpNode = gtNewOperNode(GT_EQ, TYP_INT, gtNewIndir(typ, addr0), gtNewIndir(typ, addr1));
+ JITDUMP("Optimized Enum.Equals call to comparison of underlying values:\n");
+ DISPTREE(cmpNode);
+ JITDUMP("\n");
+
+ return gtFoldExpr(cmpNode);
+ }
+
case NI_System_Type_op_Equality:
case NI_System_Type_op_Inequality:
{
diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp
index 22478354a3ab8d..c3f23487aa01c1 100644
--- a/src/coreclr/jit/importercalls.cpp
+++ b/src/coreclr/jit/importercalls.cpp
@@ -1003,6 +1003,13 @@ var_types Compiler::impImportCall(OPCODE opcode,
if ((callInfo->methodFlags & CORINFO_FLG_INTRINSIC) != 0)
{
call->AsCall()->gtCallMoreFlags |= GTF_CALL_M_SPECIAL_INTRINSIC;
+
+ GenTree* foldedCall = gtFoldExprCall(call->AsCall());
+ if ((foldedCall != call) || !call->IsCall())
+ {
+ impPushOnStack(foldedCall, typeInfo(foldedCall->TypeGet()));
+ return foldedCall->TypeGet();
+ }
}
}
}
@@ -1571,7 +1578,7 @@ var_types Compiler::impImportCall(OPCODE opcode,
}
//------------------------------------------------------------------------
-// impThrowIfNull: Remove redundandant boxing from ArgumentNullException_ThrowIfNull
+// impThrowIfNull: Remove redundant boxing from ArgumentNullException_ThrowIfNull
// it is done for Tier0 where we can't remove it without inlining otherwise.
//
// Arguments:
@@ -10344,7 +10351,11 @@ NamedIntrinsic Compiler::lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method)
{
if (strcmp(className, "Enum") == 0)
{
- if (strcmp(methodName, "HasFlag") == 0)
+ if (strcmp(methodName, "Equals") == 0)
+ {
+ result = NI_System_Enum_Equals;
+ }
+ else if (strcmp(methodName, "HasFlag") == 0)
{
result = NI_System_Enum_HasFlag;
}
diff --git a/src/coreclr/jit/namedintrinsiclist.h b/src/coreclr/jit/namedintrinsiclist.h
index 06c69ba2cd7c01..8aee4afb56c121 100644
--- a/src/coreclr/jit/namedintrinsiclist.h
+++ b/src/coreclr/jit/namedintrinsiclist.h
@@ -15,6 +15,7 @@ enum NamedIntrinsic : unsigned short
NI_System_ArgumentNullException_ThrowIfNull,
+ NI_System_Enum_Equals,
NI_System_Enum_HasFlag,
NI_System_BitConverter_DoubleToInt64Bits,
diff --git a/src/libraries/System.Private.CoreLib/src/System/Enum.cs b/src/libraries/System.Private.CoreLib/src/System/Enum.cs
index cb345ebac766e0..4e06f0e6da0ae4 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Enum.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Enum.cs
@@ -1187,6 +1187,7 @@ internal object GetValue()
}
///
+ [Intrinsic]
public override bool Equals([NotNullWhen(true)] object? obj)
{
if (obj is null)
diff --git a/src/tests/JIT/Intrinsics/EnumIntrinsics.cs b/src/tests/JIT/Intrinsics/EnumIntrinsics.cs
new file mode 100644
index 00000000000000..eae3911d563751
--- /dev/null
+++ b/src/tests/JIT/Intrinsics/EnumIntrinsics.cs
@@ -0,0 +1,108 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+//
+
+using System;
+using System.Runtime.CompilerServices;
+using Xunit;
+
+public class EnumIntrinsics
+{
+ public enum SByteEnum : sbyte { Min = sbyte.MinValue, Neg = -1, Zero = 0, Max = sbyte.MaxValue }
+ public enum ByteEnum : byte { Min = 0, Max = 255 }
+ public enum ShortEnum : short { Min = short.MinValue, Max = short.MaxValue }
+ public enum UShortEnum : ushort { Min = 0, Max = ushort.MaxValue }
+ public enum IntEnum : int { Min = int.MinValue, Zero = 0, Max = int.MaxValue }
+ public enum UIntEnum : uint { Min = 0, Max = uint.MaxValue }
+ public enum LongEnum : long { Min = long.MinValue, Max = long.MaxValue }
+ public enum ULongEnum : ulong { Min = 0, Max = ulong.MaxValue }
+ [Flags] public enum FlagsEnum { None = 0, A = 1, B = 2, All = 3 }
+
+ [Fact]
+ public static void TestEntryPoint()
+ {
+ TestSimpleEnums();
+ TestGenericEnums();
+ TestDifferentUnderlyingTypes();
+ TestCornerCases();
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static void TestSimpleEnums()
+ {
+ // Testing bit-pattern identities
+ Assert.True(SByteEnum.Neg.Equals((SByteEnum)(-1)));
+ Assert.True(ByteEnum.Max.Equals((ByteEnum)255));
+ Assert.False(SByteEnum.Max.Equals(SByteEnum.Min));
+
+ // Flags behavior (bitwise equality)
+ FlagsEnum flags = FlagsEnum.A | FlagsEnum.B;
+ Assert.True(flags.Equals(FlagsEnum.All));
+ Assert.False(flags.Equals(FlagsEnum.A));
+
+ // 64-bit boundaries
+ Assert.True(ULongEnum.Max.Equals((ULongEnum)ulong.MaxValue));
+ Assert.True(LongEnum.Min.Equals((LongEnum)long.MinValue));
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static void TestGenericEnums()
+ {
+ // Testing generic instantiation for every width
+ Assert.True(CheckGenericEquals(SByteEnum.Max, SByteEnum.Max));
+ Assert.True(CheckGenericEquals(UShortEnum.Max, UShortEnum.Max));
+ Assert.True(CheckGenericEquals(UIntEnum.Max, UIntEnum.Max));
+ Assert.True(CheckGenericEquals(ULongEnum.Max, ULongEnum.Max));
+
+ var container = new GenericEnumClass { field = IntEnum.Min };
+ Assert.True(CheckGenericEquals(container.field, IntEnum.Min));
+ Assert.False(CheckGenericEquals(container.field, IntEnum.Max));
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static bool CheckGenericEquals(T left, T right) where T : Enum => left.Equals(right);
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static void TestDifferentUnderlyingTypes()
+ {
+ // 0xFF pattern
+ Assert.False(((SByteEnum)(-1)).Equals((ByteEnum)255));
+
+ // 0x00 pattern
+ Assert.False(SByteEnum.Zero.Equals(ByteEnum.Min));
+
+ // 0xFFFF pattern
+ Assert.False(((ShortEnum)(-1)).Equals((UShortEnum)ushort.MaxValue));
+
+ // 0xFFFFFFFF pattern
+ Assert.False(((IntEnum)(-1)).Equals((UIntEnum)uint.MaxValue));
+
+ // Signed vs Unsigned same width
+ Assert.False(IntEnum.Max.Equals((UIntEnum)int.MaxValue));
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static void TestCornerCases()
+ {
+ // Ensure no false positives with primitive types (boxing checks)
+ Assert.False(IntEnum.Zero.Equals(0));
+ Assert.False(LongEnum.Max.Equals(long.MaxValue));
+
+ // Different enum types entirely
+ Assert.False(SimpleEnum.A.Equals(FlagsEnum.A));
+
+ // Null and Object references
+ object obj = new object();
+ Assert.False(SimpleEnum.B.Equals(obj));
+ Assert.False(SimpleEnum.C.Equals(null));
+
+ // Double boxing scenarios
+ object boxedA = SimpleEnum.A;
+ object boxedB = SimpleEnum.A;
+ Assert.True(boxedA.Equals(boxedB));
+ Assert.True(SimpleEnum.A.Equals(boxedB));
+ }
+
+ public class GenericEnumClass where T : Enum { public T field; }
+ public enum SimpleEnum { A, B, C }
+}
diff --git a/src/tests/JIT/Intrinsics/EnumIntrinsics.csproj b/src/tests/JIT/Intrinsics/EnumIntrinsics.csproj
new file mode 100644
index 00000000000000..2b5c043e93af92
--- /dev/null
+++ b/src/tests/JIT/Intrinsics/EnumIntrinsics.csproj
@@ -0,0 +1,9 @@
+
+
+ None
+ True
+
+
+
+
+