| File: Internal\IntrinsicSupport\EqualityComparerHelpers.cs | Web Access |
| Project: src\runtime\src\coreclr\nativeaot\System.Private.CoreLib\src\System.Private.CoreLib.csproj (System.Private.CoreLib) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // The algoritm to choose the default equality comparer is duplicated in the IL compiler. The compiler will replace the code within // EqualityComparer<T>.Create method with more specific implementation based on what sort of type is being compared where possible. // // In addition, there are a set of generic functions which are used by Array.IndexOf<T> to perform equality checking // in a similar manner. Array.IndexOf<T> uses these functions instead of the EqualityComparer<T> infrastructure because constructing // a full EqualityComparer<T> has substantial size costs due to Array.IndexOf<T> use within all arrays. using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using Internal.Runtime; using Internal.Runtime.Augments; namespace Internal.IntrinsicSupport { internal static class EqualityComparerHelpers { private static unsafe bool ImplementsIEquatable(RuntimeTypeHandle t) { MethodTable* objectType = t.ToMethodTable(); MethodTable* iequatableType = typeof(IEquatable<>).TypeHandle.ToMethodTable(); int interfaceCount = objectType->NumInterfaces; for (int i = 0; i < interfaceCount; i++) { MethodTable* interfaceType = objectType->InterfaceMap[i]; if (!interfaceType->IsGeneric) continue; if (interfaceType->GenericDefinition == iequatableType) { if (interfaceType->GenericArity != 1) continue; if (interfaceType->GenericArguments[0] == objectType) { return true; } } } return false; } internal static unsafe bool IsEnum(RuntimeTypeHandle t) { return t.ToMethodTable()->IsEnum; } internal static unsafe bool IsString(RuntimeTypeHandle t) { return t.ToMethodTable()->IsString; } // this function utilizes the template type loader to generate new // EqualityComparer types on the fly internal static object GetComparer(RuntimeTypeHandle t) { RuntimeTypeHandle comparerType; RuntimeTypeHandle openComparerType = default(RuntimeTypeHandle); RuntimeTypeHandle comparerTypeArgument = default(RuntimeTypeHandle); if (IsString(t)) { return new StringEqualityComparer(); } if (RuntimeAugments.IsNullable(t)) { RuntimeTypeHandle nullableType = RuntimeAugments.GetNullableType(t); openComparerType = typeof(NullableEqualityComparer<>).TypeHandle; comparerTypeArgument = nullableType; } if (IsEnum(t)) { openComparerType = typeof(EnumEqualityComparer<>).TypeHandle; comparerTypeArgument = t; } if (openComparerType.Equals(default(RuntimeTypeHandle))) { if (ImplementsIEquatable(t)) { openComparerType = typeof(GenericEqualityComparer<>).TypeHandle; comparerTypeArgument = t; } else { openComparerType = typeof(ObjectEqualityComparer<>).TypeHandle; comparerTypeArgument = t; } } bool success = RuntimeAugments.TypeLoaderCallbacks.TryGetConstructedGenericTypeForComponents(openComparerType, new RuntimeTypeHandle[] { comparerTypeArgument }, out comparerType); if (!success) { Environment.FailFast("Unable to create comparer"); } return RuntimeAugments.RawNewObject(comparerType); } //----------------------------------------------------------------------- // Redirection target functions for redirecting behavior of Array.IndexOf //----------------------------------------------------------------------- // This one is an intrinsic that is used to make enum comparisons more efficient. [Intrinsic] internal static bool EnumOnlyEquals<T>(T x, T y) where T : struct { return x.Equals(y); } private static bool StructOnlyEqualsIEquatable<T>(T x, T y) where T : IEquatable<T> { return x.Equals(y); } private static bool StructOnlyEqualsNullable<T>(Nullable<T> x, Nullable<T> y) where T : struct, IEquatable<T> { if (x.HasValue) { if (y.HasValue) return x.Value.Equals(y.Value); return false; } if (y.HasValue) return false; return true; } // These functions look odd, as they are part of a complex series of compiler intrinsics // designed to produce very high quality code for equality comparison cases without utilizing // reflection like other platforms. The major complication is that the specification of // IndexOf is that it is supposed to use IEquatable<T> if possible, but that requirement // cannot be expressed in IL directly due to the lack of constraints. // Instead, specialization at call time is used within the compiler. // // General Approach // - Perform fancy redirection for EqualityComparerHelpers.GetComparerForReferenceTypesOnly<T>(). If T is a reference // type or UniversalCanon, have this redirect to EqualityComparer<T>.get_Default, Otherwise, use // the function as is. (will return null in that case) // - Change the contents of the IndexOf functions to have a pair of loops. One for if // GetComparerForReferenceTypesOnly returns null, and one for when it does not. // - If it does not return null, call the EqualityComparer<T> code. // - If it does return null, use a special function StructOnlyEquals<T>(). // - Calls to that function result in calls to a pair of helper function in // EqualityComparerHelpers (StructOnlyEqualsIEquatable, or StructOnlyEqualsNullable) // depending on whether or not they are the right function to call. // - The end result is that in optimized builds, we have the same single function compiled size // characteristics that the old EqualsOnlyComparer<T>.Equals function had, but we maintain // correctness as well. [Intrinsic] internal static EqualityComparer<T> GetComparerForReferenceTypesOnly<T>() { return EqualityComparer<T>.Default; } private static bool StructOnlyNormalEquals<T>(T left, T right) where T : notnull { return left.Equals(right); } [Intrinsic] internal static bool StructOnlyEquals<T>(T left, T right) { return EqualityComparer<T>.Default.Equals(left, right); } } }