Fix size regression from enum sorting (#79845)
authorMichal Strehovský <MichalStrehovsky@users.noreply.github.com>
Fri, 23 Dec 2022 05:35:38 +0000 (14:35 +0900)
committerGitHub <noreply@github.com>
Fri, 23 Dec 2022 05:35:38 +0000 (14:35 +0900)
Use a specialized comparer instead of `Comparer<T>.Default` that brings the implementation of `IComparable.ComparerTo` on everything. Also get rid of the generic virtual method call.

Saves 0.9% on Hello World.

src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Reflection/Core/Execution/ExecutionEnvironment.cs
src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/General/ReflectionCoreCallbacksImplementation.cs
src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/ExecutionEnvironmentImplementation.Runtime.cs
src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/NativeFormatEnumInfo.cs

index 61713b1..e42fbee 100644 (file)
@@ -98,9 +98,7 @@ namespace Internal.Reflection.Core.Execution
         // Other
         //==============================================================================================
         public abstract FieldAccessor CreateLiteralFieldAccessor(object value, RuntimeTypeHandle fieldTypeHandle);
-        public abstract EnumInfo<TUnderlyingValue> GetEnumInfo<TUnderlyingValue>(RuntimeTypeHandle typeHandle)
-            where TUnderlyingValue : struct, INumber<TUnderlyingValue>;
-
+        public abstract void GetEnumInfo(RuntimeTypeHandle typeHandle, out string[] names, out object[] values, out bool isFlags);
         public abstract IntPtr GetDynamicInvokeThunk(MethodInvoker invoker);
 
         //==============================================================================================
index 53f932b..996f77b 100644 (file)
@@ -412,11 +412,40 @@ namespace System.Reflection.Runtime.General
             if (info != null)
                 return info;
 
-            info = ReflectionCoreExecution.ExecutionDomain.ExecutionEnvironment.GetEnumInfo<TUnderlyingValue>(runtimeType.TypeHandle);
+            ReflectionCoreExecution.ExecutionDomain.ExecutionEnvironment.GetEnumInfo(runtimeType.TypeHandle, out string[] unsortedNames, out object[] unsortedValues, out bool isFlags);
+
+            // Call into IntrospectiveSort directly to avoid the Comparer<T>.Default codepath.
+            // That codepath would bring functionality to compare everything that was ever allocated in the program.
+            ArraySortHelper<object, string>.IntrospectiveSort(unsortedValues, unsortedNames, EnumUnderlyingTypeComparer.Instance);
+
+            // Only after we've sorted, create the underlying array.
+            var values = new TUnderlyingValue[unsortedValues.Length];
+            for (int i = 0; i < unsortedValues.Length; i++)
+                values[i] = (TUnderlyingValue)unsortedValues[i];
+
+            info = new EnumInfo<TUnderlyingValue>(RuntimeAugments.GetEnumUnderlyingType(runtimeType.TypeHandle), values, unsortedNames, isFlags);
             runtimeType.GenericCache = info;
             return info;
         }
 
+        private class EnumUnderlyingTypeComparer : IComparer<object>
+        {
+            public static readonly EnumUnderlyingTypeComparer Instance = new EnumUnderlyingTypeComparer();
+
+            public int Compare(object? x, object? y)
+                => x switch
+                {
+                    int i => i.CompareTo((int)y!),
+                    uint ui => ui.CompareTo((uint)y!),
+                    byte b => b.CompareTo((byte)y!),
+                    ushort us => us.CompareTo((ushort)y!),
+                    short s => s.CompareTo((short)y!),
+                    sbyte sb => sb.CompareTo((sbyte)y!),
+                    long l => l.CompareTo((long)y!),
+                    _ => ((ulong)x!).CompareTo((ulong)y!),
+                };
+        }
+
         public sealed override DynamicInvokeInfo GetDelegateDynamicInvokeInfo(Type type)
         {
             RuntimeTypeInfo runtimeType = type.CastToRuntimeTypeInfo();
index 5abd1bc..c47e5f7 100644 (file)
@@ -128,7 +128,7 @@ namespace Internal.Reflection.Execution
             return new LiteralFieldAccessor(value, fieldTypeHandle);
         }
 
-        public sealed override EnumInfo<TUnderlyingValue> GetEnumInfo<TUnderlyingValue>(RuntimeTypeHandle typeHandle)
+        public sealed override void GetEnumInfo(RuntimeTypeHandle typeHandle, out string[] names, out object[] values, out bool isFlags)
         {
             // Handle the weird case of an enum type nested under a generic type that makes the
             // enum itself generic
@@ -141,7 +141,10 @@ namespace Internal.Reflection.Execution
             // If the type is reflection blocked, we pretend there are no enum values defined
             if (ReflectionExecution.ExecutionEnvironment.IsReflectionBlocked(typeDefHandle))
             {
-                return new EnumInfo<TUnderlyingValue>(RuntimeAugments.GetEnumUnderlyingType(typeHandle), Array.Empty<TUnderlyingValue>(), Array.Empty<string>(), false);
+                names = Array.Empty<string>();
+                values = Array.Empty<object>();
+                isFlags = false;
+                return;
             }
 
             QTypeDefinition qTypeDefinition;
@@ -152,7 +155,13 @@ namespace Internal.Reflection.Execution
 
             if (qTypeDefinition.IsNativeFormatMetadataBased)
             {
-                return NativeFormatEnumInfo.Create<TUnderlyingValue>(typeHandle, qTypeDefinition.NativeFormatReader, qTypeDefinition.NativeFormatHandle);
+                NativeFormatEnumInfo.GetEnumValuesAndNames(
+                    qTypeDefinition.NativeFormatReader,
+                    qTypeDefinition.NativeFormatHandle,
+                    out values,
+                    out names,
+                    out isFlags);
+                return;
             }
 #if ECMA_METADATA_SUPPORT
             if (qTypeDefinition.IsEcmaFormatMetadataBased)
@@ -160,7 +169,10 @@ namespace Internal.Reflection.Execution
                 return EcmaFormatEnumInfo.Create<TUnderlyingValue>(typeHandle, qTypeDefinition.EcmaFormatReader, qTypeDefinition.EcmaFormatHandle);
             }
 #endif
-            return null;
+            names = Array.Empty<string>();
+            values = Array.Empty<object>();
+            isFlags = false;
+            return;
         }
 
         public override IntPtr GetDynamicInvokeThunk(MethodInvoker invoker)
index 0156308..b7378bd 100644 (file)
@@ -13,8 +13,8 @@ namespace Internal.Reflection.Execution
 {
     static class NativeFormatEnumInfo
     {
-        private static void GetEnumValuesAndNames(MetadataReader reader, TypeDefinitionHandle typeDefHandle,
-            out object[] sortedBoxedValues, out string[] sortedNames, out bool isFlags)
+        public static void GetEnumValuesAndNames(MetadataReader reader, TypeDefinitionHandle typeDefHandle,
+            out object[] unsortedBoxedValues, out string[] unsortedNames, out bool isFlags)
         {
             TypeDefinition typeDef = reader.GetTypeDefinition(typeDefHandle);
 
@@ -30,8 +30,8 @@ namespace Internal.Reflection.Execution
                 }
             }
 
-            var names = new string[staticFieldCount];
-            var boxedValues = new object[staticFieldCount]; // TODO: Avoid boxing the values
+            unsortedNames = new string[staticFieldCount];
+            unsortedBoxedValues = new object[staticFieldCount]; // TODO: Avoid boxing the values
 
             int i = 0;
             foreach (FieldHandle fieldHandle in typeDef.Fields)
@@ -39,18 +39,12 @@ namespace Internal.Reflection.Execution
                 Field field = fieldHandle.GetField(reader);
                 if (0 != (field.Flags & FieldAttributes.Static))
                 {
-                    names[i] = field.Name.GetString(reader);
-                    boxedValues[i] = field.DefaultValue.ParseConstantNumericValue(reader);
+                    unsortedNames[i] = field.Name.GetString(reader);
+                    unsortedBoxedValues[i] = field.DefaultValue.ParseConstantNumericValue(reader);
                     i++;
                 }
             }
 
-            // Using object overload to avoid generic expansion for every underlying enum type
-            Array.Sort<object, string>(boxedValues, names);
-
-            sortedBoxedValues = boxedValues;
-            sortedNames = names;
-
             isFlags = false;
             foreach (CustomAttributeHandle cah in typeDef.CustomAttributes)
             {
@@ -58,17 +52,5 @@ namespace Internal.Reflection.Execution
                     isFlags = true;
             }
         }
-
-        public static EnumInfo<TUnderlyingValue> Create<TUnderlyingValue>(RuntimeTypeHandle typeHandle, MetadataReader reader, TypeDefinitionHandle typeDefHandle)
-            where TUnderlyingValue : struct, INumber<TUnderlyingValue>
-        {
-            GetEnumValuesAndNames(reader, typeDefHandle, out object[] boxedValues, out string[] names, out bool isFlags);
-
-            var values = new TUnderlyingValue[boxedValues.Length];
-            for (int i = 0; i < boxedValues.Length; i++)
-                values[i] = (TUnderlyingValue)boxedValues[i];
-
-            return new EnumInfo<TUnderlyingValue>(RuntimeAugments.GetEnumUnderlyingType(typeHandle), values, names, isFlags);
-        }
     }
 }