Skip to content

Commit 8d3070f

Browse files
Fix size regression from enum sorting (#79845)
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.
1 parent acd76bc commit 8d3070f

File tree

4 files changed

+53
-32
lines changed

4 files changed

+53
-32
lines changed

src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Reflection/Core/Execution/ExecutionEnvironment.cs

+1-3
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,7 @@ public abstract class ExecutionEnvironment
9898
// Other
9999
//==============================================================================================
100100
public abstract FieldAccessor CreateLiteralFieldAccessor(object value, RuntimeTypeHandle fieldTypeHandle);
101-
public abstract EnumInfo<TUnderlyingValue> GetEnumInfo<TUnderlyingValue>(RuntimeTypeHandle typeHandle)
102-
where TUnderlyingValue : struct, INumber<TUnderlyingValue>;
103-
101+
public abstract void GetEnumInfo(RuntimeTypeHandle typeHandle, out string[] names, out object[] values, out bool isFlags);
104102
public abstract IntPtr GetDynamicInvokeThunk(MethodInvoker invoker);
105103

106104
//==============================================================================================

src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/General/ReflectionCoreCallbacksImplementation.cs

+30-1
Original file line numberDiff line numberDiff line change
@@ -412,11 +412,40 @@ public sealed override EnumInfo<TUnderlyingValue> GetEnumInfo<TUnderlyingValue>(
412412
if (info != null)
413413
return info;
414414

415-
info = ReflectionCoreExecution.ExecutionDomain.ExecutionEnvironment.GetEnumInfo<TUnderlyingValue>(runtimeType.TypeHandle);
415+
ReflectionCoreExecution.ExecutionDomain.ExecutionEnvironment.GetEnumInfo(runtimeType.TypeHandle, out string[] unsortedNames, out object[] unsortedValues, out bool isFlags);
416+
417+
// Call into IntrospectiveSort directly to avoid the Comparer<T>.Default codepath.
418+
// That codepath would bring functionality to compare everything that was ever allocated in the program.
419+
ArraySortHelper<object, string>.IntrospectiveSort(unsortedValues, unsortedNames, EnumUnderlyingTypeComparer.Instance);
420+
421+
// Only after we've sorted, create the underlying array.
422+
var values = new TUnderlyingValue[unsortedValues.Length];
423+
for (int i = 0; i < unsortedValues.Length; i++)
424+
values[i] = (TUnderlyingValue)unsortedValues[i];
425+
426+
info = new EnumInfo<TUnderlyingValue>(RuntimeAugments.GetEnumUnderlyingType(runtimeType.TypeHandle), values, unsortedNames, isFlags);
416427
runtimeType.GenericCache = info;
417428
return info;
418429
}
419430

431+
private class EnumUnderlyingTypeComparer : IComparer<object>
432+
{
433+
public static readonly EnumUnderlyingTypeComparer Instance = new EnumUnderlyingTypeComparer();
434+
435+
public int Compare(object? x, object? y)
436+
=> x switch
437+
{
438+
int i => i.CompareTo((int)y!),
439+
uint ui => ui.CompareTo((uint)y!),
440+
byte b => b.CompareTo((byte)y!),
441+
ushort us => us.CompareTo((ushort)y!),
442+
short s => s.CompareTo((short)y!),
443+
sbyte sb => sb.CompareTo((sbyte)y!),
444+
long l => l.CompareTo((long)y!),
445+
_ => ((ulong)x!).CompareTo((ulong)y!),
446+
};
447+
}
448+
420449
public sealed override DynamicInvokeInfo GetDelegateDynamicInvokeInfo(Type type)
421450
{
422451
RuntimeTypeInfo runtimeType = type.CastToRuntimeTypeInfo();

src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/ExecutionEnvironmentImplementation.Runtime.cs

+16-4
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ public sealed override FieldAccessor CreateLiteralFieldAccessor(object value, Ru
128128
return new LiteralFieldAccessor(value, fieldTypeHandle);
129129
}
130130

131-
public sealed override EnumInfo<TUnderlyingValue> GetEnumInfo<TUnderlyingValue>(RuntimeTypeHandle typeHandle)
131+
public sealed override void GetEnumInfo(RuntimeTypeHandle typeHandle, out string[] names, out object[] values, out bool isFlags)
132132
{
133133
// Handle the weird case of an enum type nested under a generic type that makes the
134134
// enum itself generic
@@ -141,7 +141,10 @@ public sealed override EnumInfo<TUnderlyingValue> GetEnumInfo<TUnderlyingValue>(
141141
// If the type is reflection blocked, we pretend there are no enum values defined
142142
if (ReflectionExecution.ExecutionEnvironment.IsReflectionBlocked(typeDefHandle))
143143
{
144-
return new EnumInfo<TUnderlyingValue>(RuntimeAugments.GetEnumUnderlyingType(typeHandle), Array.Empty<TUnderlyingValue>(), Array.Empty<string>(), false);
144+
names = Array.Empty<string>();
145+
values = Array.Empty<object>();
146+
isFlags = false;
147+
return;
145148
}
146149

147150
QTypeDefinition qTypeDefinition;
@@ -152,15 +155,24 @@ public sealed override EnumInfo<TUnderlyingValue> GetEnumInfo<TUnderlyingValue>(
152155

153156
if (qTypeDefinition.IsNativeFormatMetadataBased)
154157
{
155-
return NativeFormatEnumInfo.Create<TUnderlyingValue>(typeHandle, qTypeDefinition.NativeFormatReader, qTypeDefinition.NativeFormatHandle);
158+
NativeFormatEnumInfo.GetEnumValuesAndNames(
159+
qTypeDefinition.NativeFormatReader,
160+
qTypeDefinition.NativeFormatHandle,
161+
out values,
162+
out names,
163+
out isFlags);
164+
return;
156165
}
157166
#if ECMA_METADATA_SUPPORT
158167
if (qTypeDefinition.IsEcmaFormatMetadataBased)
159168
{
160169
return EcmaFormatEnumInfo.Create<TUnderlyingValue>(typeHandle, qTypeDefinition.EcmaFormatReader, qTypeDefinition.EcmaFormatHandle);
161170
}
162171
#endif
163-
return null;
172+
names = Array.Empty<string>();
173+
values = Array.Empty<object>();
174+
isFlags = false;
175+
return;
164176
}
165177

166178
public override IntPtr GetDynamicInvokeThunk(MethodInvoker invoker)

src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/NativeFormatEnumInfo.cs

+6-24
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ namespace Internal.Reflection.Execution
1313
{
1414
static class NativeFormatEnumInfo
1515
{
16-
private static void GetEnumValuesAndNames(MetadataReader reader, TypeDefinitionHandle typeDefHandle,
17-
out object[] sortedBoxedValues, out string[] sortedNames, out bool isFlags)
16+
public static void GetEnumValuesAndNames(MetadataReader reader, TypeDefinitionHandle typeDefHandle,
17+
out object[] unsortedBoxedValues, out string[] unsortedNames, out bool isFlags)
1818
{
1919
TypeDefinition typeDef = reader.GetTypeDefinition(typeDefHandle);
2020

@@ -30,45 +30,27 @@ private static void GetEnumValuesAndNames(MetadataReader reader, TypeDefinitionH
3030
}
3131
}
3232

33-
var names = new string[staticFieldCount];
34-
var boxedValues = new object[staticFieldCount]; // TODO: Avoid boxing the values
33+
unsortedNames = new string[staticFieldCount];
34+
unsortedBoxedValues = new object[staticFieldCount]; // TODO: Avoid boxing the values
3535

3636
int i = 0;
3737
foreach (FieldHandle fieldHandle in typeDef.Fields)
3838
{
3939
Field field = fieldHandle.GetField(reader);
4040
if (0 != (field.Flags & FieldAttributes.Static))
4141
{
42-
names[i] = field.Name.GetString(reader);
43-
boxedValues[i] = field.DefaultValue.ParseConstantNumericValue(reader);
42+
unsortedNames[i] = field.Name.GetString(reader);
43+
unsortedBoxedValues[i] = field.DefaultValue.ParseConstantNumericValue(reader);
4444
i++;
4545
}
4646
}
4747

48-
// Using object overload to avoid generic expansion for every underlying enum type
49-
Array.Sort<object, string>(boxedValues, names);
50-
51-
sortedBoxedValues = boxedValues;
52-
sortedNames = names;
53-
5448
isFlags = false;
5549
foreach (CustomAttributeHandle cah in typeDef.CustomAttributes)
5650
{
5751
if (cah.IsCustomAttributeOfType(reader, "System", "FlagsAttribute"))
5852
isFlags = true;
5953
}
6054
}
61-
62-
public static EnumInfo<TUnderlyingValue> Create<TUnderlyingValue>(RuntimeTypeHandle typeHandle, MetadataReader reader, TypeDefinitionHandle typeDefHandle)
63-
where TUnderlyingValue : struct, INumber<TUnderlyingValue>
64-
{
65-
GetEnumValuesAndNames(reader, typeDefHandle, out object[] boxedValues, out string[] names, out bool isFlags);
66-
67-
var values = new TUnderlyingValue[boxedValues.Length];
68-
for (int i = 0; i < boxedValues.Length; i++)
69-
values[i] = (TUnderlyingValue)boxedValues[i];
70-
71-
return new EnumInfo<TUnderlyingValue>(RuntimeAugments.GetEnumUnderlyingType(typeHandle), values, names, isFlags);
72-
}
7355
}
7456
}

0 commit comments

Comments
 (0)