From 01ca1e8f9c3e969de5beecbe6e3f29d1393916fa Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 7 May 2024 17:26:19 -0700 Subject: [PATCH 01/17] Make the generic cache composite and encapsulate it --- .../System.Private.CoreLib.csproj | 3 +- .../src/System/Array.CoreCLR.cs | 19 +- .../src/System/Enum.CoreCLR.cs | 48 ++++- .../src/System/RuntimeType.ActivatorCache.cs | 19 +- .../src/System/RuntimeType.CoreCLR.cs | 86 ++++----- ...meType.CreateUninitializedCache.CoreCLR.cs | 7 +- .../src/System/RuntimeType.GenericCache.cs | 170 ++++++++++++++++++ .../src/System/Enum.EnumInfo.cs | 2 +- 8 files changed, 280 insertions(+), 74 deletions(-) create mode 100644 src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs diff --git a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj index a37abf7a0c116..f14f27121b6d3 100644 --- a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -237,7 +237,8 @@ Common\System\Collections\Generic\ArrayBuilder.cs - + + diff --git a/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs index 74e0739848168..644896dae17ef 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs @@ -642,11 +642,7 @@ public unsafe void Initialize() RuntimeType arrayType = (RuntimeType)GetType(); - if (arrayType.GenericCache is not ArrayInitializeCache cache) - { - cache = new ArrayInitializeCache(arrayType); - arrayType.GenericCache = cache; - } + ArrayInitializeCache cache = arrayType.GetOrCreateCacheEntry(); delegate* constructorFtn = cache.ConstructorEntrypoint; ref byte arrayRef = ref MemoryMarshal.GetArrayDataReference(this); @@ -659,16 +655,23 @@ public unsafe void Initialize() } } - private sealed unsafe partial class ArrayInitializeCache + internal sealed unsafe partial class ArrayInitializeCache : RuntimeType.IGenericCacheEntry { internal readonly delegate* ConstructorEntrypoint; [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "Array_GetElementConstructorEntrypoint")] private static partial delegate* GetElementConstructorEntrypoint(QCallTypeHandle arrayType); - public ArrayInitializeCache(RuntimeType arrayType) + public static RuntimeType.IGenericCacheEntry.GenericCacheKind Kind => RuntimeType.IGenericCacheEntry.GenericCacheKind.ArrayInitialize; + + private ArrayInitializeCache(delegate* constructorEntrypoint) + { + ConstructorEntrypoint = constructorEntrypoint; + } + + public static ArrayInitializeCache Create(RuntimeType arrayType) { - ConstructorEntrypoint = GetElementConstructorEntrypoint(new QCallTypeHandle(ref arrayType)); + return new(GetElementConstructorEntrypoint(new QCallTypeHandle(ref arrayType))); } } } diff --git a/src/coreclr/System.Private.CoreLib/src/System/Enum.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Enum.CoreCLR.cs index cc46e2a75d8b2..a8c08444d0cd1 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Enum.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Enum.CoreCLR.cs @@ -87,30 +87,62 @@ private static EnumInfo GetEnumInfo(RuntimeType enumType, bo typeof(TStorage) == typeof(nuint) || typeof(TStorage) == typeof(float) || typeof(TStorage) == typeof(double) || typeof(TStorage) == typeof(char), $"Unexpected {nameof(TStorage)} == {typeof(TStorage)}"); - return enumType.GenericCache is EnumInfo info && (!getNames || info.Names is not null) ? + return enumType.FindCacheEntry>() is {} info && (!getNames || info.Names is not null) ? info : InitializeEnumInfo(enumType, getNames); [MethodImpl(MethodImplOptions.NoInlining)] static EnumInfo InitializeEnumInfo(RuntimeType enumType, bool getNames) + { + // If we're asked to get the cache with names, + // force that copy into the cache even if we already have a cache entry without names + // so we don't have to recompute the names if asked again. + return getNames + ? enumType.OverwriteCacheEntry(EnumInfo.CreateWithNames(enumType)) + : enumType.GetOrCreateCacheEntry>(); + } + } + + internal sealed partial class EnumInfo : RuntimeType.IGenericCacheEntry> + { + public static RuntimeType.IGenericCacheEntry.GenericCacheKind Kind => RuntimeType.IGenericCacheEntry.GenericCacheKind.EnumInfo; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static EnumInfo CreateWithNames(RuntimeType type) + { + TStorage[]? values = null; + string[]? names = null; + + GetEnumValuesAndNames( + new QCallTypeHandle(ref type), + ObjectHandleOnStack.Create(ref values), + ObjectHandleOnStack.Create(ref names), + Interop.BOOL.TRUE); + + Debug.Assert(values!.GetType() == typeof(TStorage[])); + + bool hasFlagsAttribute = type.IsDefined(typeof(FlagsAttribute), inherit: false); + + return new EnumInfo(hasFlagsAttribute, values, names!); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static EnumInfo Create(RuntimeType type) { TStorage[]? values = null; string[]? names = null; GetEnumValuesAndNames( - new QCallTypeHandle(ref enumType), + new QCallTypeHandle(ref type), ObjectHandleOnStack.Create(ref values), ObjectHandleOnStack.Create(ref names), - getNames ? Interop.BOOL.TRUE : Interop.BOOL.FALSE); + Interop.BOOL.FALSE); Debug.Assert(values!.GetType() == typeof(TStorage[])); - Debug.Assert(!getNames || names!.GetType() == typeof(string[])); - bool hasFlagsAttribute = enumType.IsDefined(typeof(FlagsAttribute), inherit: false); + bool hasFlagsAttribute = type.IsDefined(typeof(FlagsAttribute), inherit: false); - var entry = new EnumInfo(hasFlagsAttribute, values, names!); - enumType.GenericCache = entry; - return entry; + return new EnumInfo(hasFlagsAttribute, values, null!); } } } diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.ActivatorCache.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.ActivatorCache.cs index 4d73cfad39143..978f43140c7ef 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.ActivatorCache.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.ActivatorCache.cs @@ -13,7 +13,7 @@ internal sealed partial class RuntimeType /// A cache which allows optimizing , /// , and related APIs. /// - private sealed unsafe class ActivatorCache + private sealed unsafe class ActivatorCache : IGenericCacheEntry { // The managed calli to the newobj allocator, plus its first argument (MethodTable*). // In the case of the COM allocator, first arg is ComClassFactory*, not MethodTable*. @@ -24,13 +24,15 @@ private sealed unsafe class ActivatorCache private readonly delegate* _pfnCtor; private readonly bool _ctorIsPublic; - private CreateUninitializedCache? _createUninitializedCache; - #if DEBUG private readonly RuntimeType _originalRuntimeType; #endif - internal ActivatorCache(RuntimeType rt) + public static IGenericCacheEntry.GenericCacheKind Kind => IGenericCacheEntry.GenericCacheKind.Activator; + + public static ActivatorCache Create(RuntimeType type) => new ActivatorCache(type); + + private ActivatorCache(RuntimeType rt) { Debug.Assert(rt != null); @@ -122,15 +124,6 @@ static void CtorNoopStub(object? uninitializedObject) { } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void CallConstructor(object? uninitializedObject) => _pfnCtor(uninitializedObject); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal CreateUninitializedCache GetCreateUninitializedCache(RuntimeType rt) - { -#if DEBUG - CheckOriginalRuntimeType(rt); -#endif - return _createUninitializedCache ??= new CreateUninitializedCache(rt); - } - #if DEBUG private void CheckOriginalRuntimeType(RuntimeType rt) { diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs index c6ea76fde9b85..083a974f066de 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs @@ -1501,30 +1501,35 @@ private MemberInfoCache GetMemberCache(ref MemberInfoCache? m_cache) #region Internal Members - - /// - /// Generic cache for rare scenario specific data. It is used to cache either Enum names, Enum values, - /// the Activator cache or function pointer parameters. - /// internal object? GenericCache { get => m_genericCache; set => m_genericCache = value; } + private sealed class FunctionPointerCache : IGenericCacheEntry + { + public static IGenericCacheEntry.GenericCacheKind Kind => IGenericCacheEntry.GenericCacheKind.FunctionPointer; + + public Type[] FunctionPointerReturnAndParameterTypes { get; } + + private FunctionPointerCache(Type[] functionPointerReturnAndParameterTypes) + { + FunctionPointerReturnAndParameterTypes = functionPointerReturnAndParameterTypes; + } + + public static FunctionPointerCache Create(RuntimeType type) + { + Debug.Assert(type.IsFunctionPointer); + return new(RuntimeTypeHandle.GetArgumentTypesFromFunctionPointer(type)); + } + } + internal Type[] FunctionPointerReturnAndParameterTypes { get { - Debug.Assert(m_runtimeType.IsFunctionPointer); - Type[]? value = (Type[]?)GenericCache; - if (value == null) - { - GenericCache = value = RuntimeTypeHandle.GetArgumentTypesFromFunctionPointer(m_runtimeType); - Debug.Assert(value.Length > 0); - } - - return value; + return m_runtimeType.GetOrCreateCacheEntry().FunctionPointerReturnAndParameterTypes; } } @@ -1930,12 +1935,34 @@ internal FieldInfo GetField(RuntimeFieldHandleInternal field) return retval; } + /// + /// Generic cache for rare scenario specific data. See for more information on what data can be cached here. + /// internal object? GenericCache { get => CacheIfExists?.GenericCache; set => Cache.GenericCache = value; } + internal T GetOrCreateCacheEntry() + where T : class, IGenericCacheEntry + { + return IGenericCacheEntry.GetOrCreate(this); + } + + internal T? FindCacheEntry() + where T : class, IGenericCacheEntry + { + return IGenericCacheEntry.Find(this); + } + + internal T OverwriteCacheEntry(T entry) + where T : class, IGenericCacheEntry + { + IGenericCacheEntry.Overwrite(this, entry); + return entry; + } + internal static FieldInfo GetFieldInfo(IRuntimeFieldInfo fieldHandle) { return GetFieldInfo(RuntimeFieldHandle.GetApproxDeclaringType(fieldHandle), fieldHandle); @@ -3886,22 +3913,7 @@ private void CreateInstanceCheckThis() [DebuggerHidden] internal object GetUninitializedObject() { - object? genericCache = GenericCache; - - if (genericCache is not CreateUninitializedCache cache) - { - if (genericCache is ActivatorCache activatorCache) - { - cache = activatorCache.GetCreateUninitializedCache(this); - } - else - { - cache = new CreateUninitializedCache(this); - GenericCache = cache; - } - } - - return cache.CreateUninitializedObject(this); + return GetOrCreateCacheEntry().CreateUninitializedObject(this); } /// @@ -3914,11 +3926,7 @@ internal object GetUninitializedObject() // Get or create the cached factory. Creating the cache will fail if one // of our invariant checks fails; e.g., no appropriate ctor found. - if (GenericCache is not ActivatorCache cache) - { - cache = new ActivatorCache(this); - GenericCache = cache; - } + ActivatorCache cache = GetOrCreateCacheEntry(); if (!cache.CtorIsPublic && publicOnly) { @@ -3947,18 +3955,14 @@ internal object GetUninitializedObject() [DebuggerHidden] internal object? CreateInstanceOfT() { - if (GenericCache is not ActivatorCache cache) - { - cache = new ActivatorCache(this); - GenericCache = cache; - } + ActivatorCache cache = GetOrCreateCacheEntry(); if (!cache.CtorIsPublic) { throw new MissingMethodException(SR.Format(SR.Arg_NoDefCTor, this)); } - object? obj = cache.CreateUninitializedObject(this); + object? obj = GetOrCreateCacheEntry().CreateUninitializedObject(this); try { cache.CallConstructor(obj); diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CreateUninitializedCache.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CreateUninitializedCache.CoreCLR.cs index 710b1b507e4ef..e5f8b6a23d7a5 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CreateUninitializedCache.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CreateUninitializedCache.CoreCLR.cs @@ -12,8 +12,11 @@ internal sealed partial class RuntimeType /// /// A cache which allows optimizing . /// - private sealed unsafe partial class CreateUninitializedCache + private sealed unsafe partial class CreateUninitializedCache : IGenericCacheEntry { + public static IGenericCacheEntry.GenericCacheKind Kind => IGenericCacheEntry.GenericCacheKind.CreateUninitialized; + public static CreateUninitializedCache Create(RuntimeType type) => new CreateUninitializedCache(type); + // The managed calli to the newobj allocator, plus its first argument (MethodTable*). private readonly delegate* _pfnAllocator; private readonly void* _allocatorFirstArg; @@ -22,7 +25,7 @@ private sealed unsafe partial class CreateUninitializedCache private readonly RuntimeType _originalRuntimeType; #endif - internal CreateUninitializedCache(RuntimeType rt) + private CreateUninitializedCache(RuntimeType rt) { Debug.Assert(rt != null); diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs new file mode 100644 index 0000000000000..22ae65f5e39b5 --- /dev/null +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs @@ -0,0 +1,170 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace System +{ + internal sealed partial class RuntimeType + { + /// + /// A base interface for all cache entries that can be stored in . + /// + internal interface IGenericCacheEntry + { + /// + /// The different kinds of entries that can be stored in . + /// + public enum GenericCacheKind + { + ArrayInitialize, + EnumInfo, + Activator, + CreateUninitialized, + FunctionPointer + } + + public abstract GenericCacheKind CacheKind { get; } + + public static abstract GenericCacheKind Kind { get; } + + /// + /// A composite cache entry that can store multiple cache entries of different kinds. + /// + /// + /// For better performance, each entry is stored as a separate field instead of something like a dictionary. + /// + protected sealed class CompositeCacheEntry + { + private IGenericCacheEntry? _arrayInitializeCache; + private IGenericCacheEntry? _enumInfoCache; + private IGenericCacheEntry? _activatorCache; + private IGenericCacheEntry? _createUninitializedCache; + private IGenericCacheEntry? _functionPointerCache; + + private ref IGenericCacheEntry? GetCacheFieldForKind(IGenericCacheEntry.GenericCacheKind kind) + { + switch (kind) + { + case IGenericCacheEntry.GenericCacheKind.ArrayInitialize: + return ref _arrayInitializeCache; + case IGenericCacheEntry.GenericCacheKind.EnumInfo: + return ref _enumInfoCache; + case IGenericCacheEntry.GenericCacheKind.Activator: + return ref _activatorCache; + case IGenericCacheEntry.GenericCacheKind.CreateUninitialized: + return ref _createUninitializedCache; + case IGenericCacheEntry.GenericCacheKind.FunctionPointer: + return ref _functionPointerCache; + default: + throw new ArgumentOutOfRangeException(nameof(kind)); + } + } + + public static CompositeCacheEntry Create(IGenericCacheEntry cache) + { + var composite = new CompositeCacheEntry(); + composite.GetCacheFieldForKind(cache.CacheKind) = cache; + return composite; + } + + public IGenericCacheEntry GetNestedCache(IGenericCacheEntry.GenericCacheKind kind) + { + return GetCacheFieldForKind(kind)!; + } + + public IGenericCacheEntry OverwriteNestedCache(T cache) + where T : IGenericCacheEntry + { + return GetCacheFieldForKind(T.Kind) = cache; + } + } + } + + /// + /// A typed cache entry. This type provides a base type that handles contruction of entries and maintenance of + /// the storage. + /// + /// The cache entry type. + internal interface IGenericCacheEntry : IGenericCacheEntry + where TCache: class, IGenericCacheEntry + { + GenericCacheKind IGenericCacheEntry.CacheKind => TCache.Kind; + + public static abstract TCache Create(RuntimeType type); + + public static TCache GetOrCreate(RuntimeType type) + { + if (type.GenericCache is null) + { + TCache newCache = TCache.Create(type); + type.GenericCache = newCache; + return newCache; + } + else if (type.GenericCache is IGenericCacheEntry existing && existing.CacheKind == TCache.Kind) + { + return (TCache)type.GenericCache; + } + + if (type.GenericCache is not CompositeCacheEntry composite) + { + // Convert the current cache into a composite cache. + composite = CompositeCacheEntry.Create((IGenericCacheEntry)type.GenericCache); + type.GenericCache = composite; + } + + if (composite.GetNestedCache(TCache.Kind) is TCache cache) + { + return cache; + } + + return (TCache)composite.OverwriteNestedCache(TCache.Create(type)); + } + + public static TCache? Find(RuntimeType type) + { + if (type.GenericCache is null) + { + return null; + } + else if (type.GenericCache is IGenericCacheEntry existing && existing.CacheKind == TCache.Kind) + { + return (TCache)type.GenericCache; + } + else if (type.GenericCache is CompositeCacheEntry composite) + { + return (TCache)composite.GetNestedCache(TCache.Kind); + } + else + { + return null; + } + } + + public static void Overwrite(RuntimeType type, TCache cache) + { + if (type.GenericCache is null) + { + type.GenericCache = cache; + return; + } + else if (type.GenericCache is IGenericCacheEntry existing && existing.CacheKind == TCache.Kind) + { + type.GenericCache = cache; + return; + } + + if (type.GenericCache is not CompositeCacheEntry composite) + { + // Convert the current cache into a composite cache. + composite = CompositeCacheEntry.Create((IGenericCacheEntry)type.GenericCache); + type.GenericCache = composite; + } + + composite.OverwriteNestedCache(cache); + } + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Enum.EnumInfo.cs b/src/libraries/System.Private.CoreLib/src/System/Enum.EnumInfo.cs index 419f1a0170a2f..2f349d8cb5961 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Enum.EnumInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Enum.EnumInfo.cs @@ -11,7 +11,7 @@ namespace System { public abstract partial class Enum { - internal sealed class EnumInfo + internal sealed partial class EnumInfo where TStorage : struct, INumber { public readonly bool HasFlagsAttribute; From b565beeb718f4ec3f076d1324074aed8cf9e96db Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 8 May 2024 15:46:43 -0700 Subject: [PATCH 02/17] Use InlineArray with Count to make the cache easier to expand. Scope down visibility of some of the types/members --- .../src/System/Array.CoreCLR.cs | 2 +- .../src/System/Enum.CoreCLR.cs | 2 +- .../src/System/RuntimeType.ActivatorCache.cs | 2 +- .../src/System/RuntimeType.CoreCLR.cs | 2 +- ...meType.CreateUninitializedCache.CoreCLR.cs | 2 +- .../src/System/RuntimeType.GenericCache.cs | 63 ++++++++----------- 6 files changed, 31 insertions(+), 42 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs index 644896dae17ef..7da9e5f64a440 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs @@ -662,7 +662,7 @@ internal sealed unsafe partial class ArrayInitializeCache : RuntimeType.IGeneric [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "Array_GetElementConstructorEntrypoint")] private static partial delegate* GetElementConstructorEntrypoint(QCallTypeHandle arrayType); - public static RuntimeType.IGenericCacheEntry.GenericCacheKind Kind => RuntimeType.IGenericCacheEntry.GenericCacheKind.ArrayInitialize; + static RuntimeType.IGenericCacheEntry.GenericCacheKind RuntimeType.IGenericCacheEntry.Kind => RuntimeType.IGenericCacheEntry.GenericCacheKind.ArrayInitialize; private ArrayInitializeCache(delegate* constructorEntrypoint) { diff --git a/src/coreclr/System.Private.CoreLib/src/System/Enum.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Enum.CoreCLR.cs index a8c08444d0cd1..c41dffdec66a2 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Enum.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Enum.CoreCLR.cs @@ -105,7 +105,7 @@ static EnumInfo InitializeEnumInfo(RuntimeType enumType, bool getNames internal sealed partial class EnumInfo : RuntimeType.IGenericCacheEntry> { - public static RuntimeType.IGenericCacheEntry.GenericCacheKind Kind => RuntimeType.IGenericCacheEntry.GenericCacheKind.EnumInfo; + static RuntimeType.IGenericCacheEntry.GenericCacheKind RuntimeType.IGenericCacheEntry.Kind => RuntimeType.IGenericCacheEntry.GenericCacheKind.EnumInfo; [MethodImpl(MethodImplOptions.NoInlining)] public static EnumInfo CreateWithNames(RuntimeType type) diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.ActivatorCache.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.ActivatorCache.cs index 978f43140c7ef..f923954aea948 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.ActivatorCache.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.ActivatorCache.cs @@ -28,7 +28,7 @@ private sealed unsafe class ActivatorCache : IGenericCacheEntry private readonly RuntimeType _originalRuntimeType; #endif - public static IGenericCacheEntry.GenericCacheKind Kind => IGenericCacheEntry.GenericCacheKind.Activator; + static IGenericCacheEntry.GenericCacheKind IGenericCacheEntry.Kind => IGenericCacheEntry.GenericCacheKind.Activator; public static ActivatorCache Create(RuntimeType type) => new ActivatorCache(type); diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs index 083a974f066de..55b486187d29a 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs @@ -1509,7 +1509,7 @@ internal object? GenericCache private sealed class FunctionPointerCache : IGenericCacheEntry { - public static IGenericCacheEntry.GenericCacheKind Kind => IGenericCacheEntry.GenericCacheKind.FunctionPointer; + static IGenericCacheEntry.GenericCacheKind IGenericCacheEntry.Kind => IGenericCacheEntry.GenericCacheKind.FunctionPointer; public Type[] FunctionPointerReturnAndParameterTypes { get; } diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CreateUninitializedCache.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CreateUninitializedCache.CoreCLR.cs index e5f8b6a23d7a5..d8c2a987b654b 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CreateUninitializedCache.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CreateUninitializedCache.CoreCLR.cs @@ -14,7 +14,7 @@ internal sealed partial class RuntimeType /// private sealed unsafe partial class CreateUninitializedCache : IGenericCacheEntry { - public static IGenericCacheEntry.GenericCacheKind Kind => IGenericCacheEntry.GenericCacheKind.CreateUninitialized; + static IGenericCacheEntry.GenericCacheKind IGenericCacheEntry.Kind => IGenericCacheEntry.GenericCacheKind.CreateUninitialized; public static CreateUninitializedCache Create(RuntimeType type) => new CreateUninitializedCache(type); // The managed calli to the newobj allocator, plus its first argument (MethodTable*). diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs index 22ae65f5e39b5..b1279f9001888 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -17,68 +18,56 @@ internal interface IGenericCacheEntry /// /// The different kinds of entries that can be stored in . /// - public enum GenericCacheKind + protected enum GenericCacheKind { ArrayInitialize, EnumInfo, Activator, CreateUninitialized, - FunctionPointer + FunctionPointer, + /// + /// The number of different kinds of cache entries. This should always be the last entry. + /// + Count } - public abstract GenericCacheKind CacheKind { get; } + protected abstract GenericCacheKind CacheKind { get; } - public static abstract GenericCacheKind Kind { get; } + protected static abstract GenericCacheKind Kind { get; } /// /// A composite cache entry that can store multiple cache entries of different kinds. /// - /// - /// For better performance, each entry is stored as a separate field instead of something like a dictionary. - /// - protected sealed class CompositeCacheEntry + [InlineArray((int)GenericCacheKind.Count)] + protected struct CompositeCacheEntry { - private IGenericCacheEntry? _arrayInitializeCache; - private IGenericCacheEntry? _enumInfoCache; - private IGenericCacheEntry? _activatorCache; - private IGenericCacheEntry? _createUninitializedCache; - private IGenericCacheEntry? _functionPointerCache; + // Typed as object as interfaces with static abstracts can't be + // used directly as generic types. + private object? _field; - private ref IGenericCacheEntry? GetCacheFieldForKind(IGenericCacheEntry.GenericCacheKind kind) + [UnscopedRef] + private ref object? GetCacheFieldForKind(IGenericCacheEntry.GenericCacheKind kind) { - switch (kind) - { - case IGenericCacheEntry.GenericCacheKind.ArrayInitialize: - return ref _arrayInitializeCache; - case IGenericCacheEntry.GenericCacheKind.EnumInfo: - return ref _enumInfoCache; - case IGenericCacheEntry.GenericCacheKind.Activator: - return ref _activatorCache; - case IGenericCacheEntry.GenericCacheKind.CreateUninitialized: - return ref _createUninitializedCache; - case IGenericCacheEntry.GenericCacheKind.FunctionPointer: - return ref _functionPointerCache; - default: - throw new ArgumentOutOfRangeException(nameof(kind)); - } + Span entries = this; + return ref entries[(int)kind]; } public static CompositeCacheEntry Create(IGenericCacheEntry cache) { - var composite = new CompositeCacheEntry(); + CompositeCacheEntry composite = default; composite.GetCacheFieldForKind(cache.CacheKind) = cache; return composite; } public IGenericCacheEntry GetNestedCache(IGenericCacheEntry.GenericCacheKind kind) { - return GetCacheFieldForKind(kind)!; + return (IGenericCacheEntry)GetCacheFieldForKind(kind)!; } public IGenericCacheEntry OverwriteNestedCache(T cache) where T : IGenericCacheEntry { - return GetCacheFieldForKind(T.Kind) = cache; + return (IGenericCacheEntry)(GetCacheFieldForKind(T.Kind) = cache); } } } @@ -103,9 +92,9 @@ public static TCache GetOrCreate(RuntimeType type) type.GenericCache = newCache; return newCache; } - else if (type.GenericCache is IGenericCacheEntry existing && existing.CacheKind == TCache.Kind) + else if (type.GenericCache is TCache existing) { - return (TCache)type.GenericCache; + return existing; } if (type.GenericCache is not CompositeCacheEntry composite) @@ -129,9 +118,9 @@ public static TCache GetOrCreate(RuntimeType type) { return null; } - else if (type.GenericCache is IGenericCacheEntry existing && existing.CacheKind == TCache.Kind) + else if (type.GenericCache is TCache existing) { - return (TCache)type.GenericCache; + return existing; } else if (type.GenericCache is CompositeCacheEntry composite) { @@ -150,7 +139,7 @@ public static void Overwrite(RuntimeType type, TCache cache) type.GenericCache = cache; return; } - else if (type.GenericCache is IGenericCacheEntry existing && existing.CacheKind == TCache.Kind) + else if (type.GenericCache is TCache) { type.GenericCache = cache; return; From 28e4107de9d24bdcd297441f189147ad8cb57077 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 8 May 2024 16:10:12 -0700 Subject: [PATCH 03/17] Update comment --- .../src/System/RuntimeType.CoreCLR.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs index 55b486187d29a..d6bc7d95279af 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs @@ -1454,10 +1454,8 @@ internal T[] GetMemberList(MemberListType listType, string? name, CacheType cach private static CerHashtable s_methodInstantiations; private static object? s_methodInstantiationsLock; private string? m_defaultMemberName; - // Generic cache for rare scenario specific data. Used for: - // - Enum names and values (EnumInfo) - // - Activator.CreateInstance (ActivatorCache) - // - Array.Initialize (ArrayInitializeCache) + // Generic cache for rare scenario specific data. + // See IGenericCacheEntry for more information. private object? m_genericCache; private object[]? _emptyArray; // Object array cache for Attribute.GetCustomAttributes() pathological no-result case. private RuntimeType? _genericTypeDefinition; From 893825ec31ea49f9805cb67fd7dceb4ccb7a8de9 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 9 May 2024 10:08:49 -0700 Subject: [PATCH 04/17] Make the GenericCache more resilient to multithreading races. --- .../src/System/RuntimeType.GenericCache.cs | 41 +++++++++++-------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs index b1279f9001888..526485e228d88 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs @@ -84,6 +84,23 @@ internal interface IGenericCacheEntry : IGenericCacheEntry public static abstract TCache Create(RuntimeType type); + private static CompositeCacheEntry GetOrUpgradeToCompositeCache(RuntimeType type) + { + // We may have multiple threads racing to add a new entry to the cache, causing an upgrade to a composite cache. + // If we lose the race, it's okay (we'll re-create the missing entry again on another thread) + // and trying to make the code resilient to this would be more expensive. + // Just make sure we read the cache field once and set it if we need to update it. + var maybeCompositeCache = type.GenericCache; + if (maybeCompositeCache is not CompositeCacheEntry composite) + { + // Convert the current cache into a composite cache. + composite = CompositeCacheEntry.Create((IGenericCacheEntry)maybeCompositeCache); + type.GenericCache = composite; + } + + return composite; + } + public static TCache GetOrCreate(RuntimeType type) { if (type.GenericCache is null) @@ -97,12 +114,7 @@ public static TCache GetOrCreate(RuntimeType type) return existing; } - if (type.GenericCache is not CompositeCacheEntry composite) - { - // Convert the current cache into a composite cache. - composite = CompositeCacheEntry.Create((IGenericCacheEntry)type.GenericCache); - type.GenericCache = composite; - } + CompositeCacheEntry composite = GetOrUpgradeToCompositeCache(type); if (composite.GetNestedCache(TCache.Kind) is TCache cache) { @@ -139,18 +151,13 @@ public static void Overwrite(RuntimeType type, TCache cache) type.GenericCache = cache; return; } - else if (type.GenericCache is TCache) - { - type.GenericCache = cache; - return; - } - if (type.GenericCache is not CompositeCacheEntry composite) - { - // Convert the current cache into a composite cache. - composite = CompositeCacheEntry.Create((IGenericCacheEntry)type.GenericCache); - type.GenericCache = composite; - } + // Always upgrade to a composite cache here. + // We would like to be able to avoid this when the GenericCache is the same type as the current cache, + // but we can't easily do a lock-free CompareExchange with the current design, + // and we can't assume that we won't have one thread adding another item to the cache + // while another is trying to overwrite the (currently) only entry in the cache. + CompositeCacheEntry composite = GetOrUpgradeToCompositeCache(type); composite.OverwriteNestedCache(cache); } From 2a59a5e331eb7beafe234b01f491cfa07404115a Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 9 May 2024 10:40:48 -0700 Subject: [PATCH 05/17] Add Debug.Assert and nonnull-assertion operator --- .../src/System/RuntimeType.GenericCache.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs index 526485e228d88..3ffb06c438318 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs @@ -91,10 +91,12 @@ private static CompositeCacheEntry GetOrUpgradeToCompositeCache(RuntimeType type // and trying to make the code resilient to this would be more expensive. // Just make sure we read the cache field once and set it if we need to update it. var maybeCompositeCache = type.GenericCache; + + Debug.Assert(maybeCompositeCache is not null); if (maybeCompositeCache is not CompositeCacheEntry composite) { // Convert the current cache into a composite cache. - composite = CompositeCacheEntry.Create((IGenericCacheEntry)maybeCompositeCache); + composite = CompositeCacheEntry.Create((IGenericCacheEntry)maybeCompositeCache!); type.GenericCache = composite; } From dd7ae9e03ac6f1cab46193da015923d281d6115e Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 14 May 2024 11:59:06 -0700 Subject: [PATCH 06/17] Use a by-ref and explicitly use Cache vs CacheIfExists. --- .../src/System/RuntimeType.CoreCLR.cs | 15 +------ .../src/System/RuntimeType.GenericCache.cs | 44 +++++++++---------- 2 files changed, 21 insertions(+), 38 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs index d6bc7d95279af..a904e9f63014a 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs @@ -1499,11 +1499,7 @@ private MemberInfoCache GetMemberCache(ref MemberInfoCache? m_cache) #region Internal Members - internal object? GenericCache - { - get => m_genericCache; - set => m_genericCache = value; - } + internal ref object? GenericCache => ref m_genericCache; private sealed class FunctionPointerCache : IGenericCacheEntry { @@ -1933,15 +1929,6 @@ internal FieldInfo GetField(RuntimeFieldHandleInternal field) return retval; } - /// - /// Generic cache for rare scenario specific data. See for more information on what data can be cached here. - /// - internal object? GenericCache - { - get => CacheIfExists?.GenericCache; - set => Cache.GenericCache = value; - } - internal T GetOrCreateCacheEntry() where T : class, IGenericCacheEntry { diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs index 3ffb06c438318..b680e2e543bbb 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs @@ -11,12 +11,12 @@ namespace System internal sealed partial class RuntimeType { /// - /// A base interface for all cache entries that can be stored in . + /// A base interface for all cache entries that can be stored in . /// internal interface IGenericCacheEntry { /// - /// The different kinds of entries that can be stored in . + /// The different kinds of entries that can be stored in . /// protected enum GenericCacheKind { @@ -74,7 +74,7 @@ public IGenericCacheEntry OverwriteNestedCache(T cache) /// /// A typed cache entry. This type provides a base type that handles contruction of entries and maintenance of - /// the storage. + /// the in a . /// /// The cache entry type. internal interface IGenericCacheEntry : IGenericCacheEntry @@ -84,20 +84,13 @@ internal interface IGenericCacheEntry : IGenericCacheEntry public static abstract TCache Create(RuntimeType type); - private static CompositeCacheEntry GetOrUpgradeToCompositeCache(RuntimeType type) + private static CompositeCacheEntry GetOrUpgradeToCompositeCache(ref object currentCache) { - // We may have multiple threads racing to add a new entry to the cache, causing an upgrade to a composite cache. - // If we lose the race, it's okay (we'll re-create the missing entry again on another thread) - // and trying to make the code resilient to this would be more expensive. - // Just make sure we read the cache field once and set it if we need to update it. - var maybeCompositeCache = type.GenericCache; - - Debug.Assert(maybeCompositeCache is not null); - if (maybeCompositeCache is not CompositeCacheEntry composite) + if (currentCache is not CompositeCacheEntry composite) { // Convert the current cache into a composite cache. - composite = CompositeCacheEntry.Create((IGenericCacheEntry)maybeCompositeCache!); - type.GenericCache = composite; + composite = CompositeCacheEntry.Create((IGenericCacheEntry)currentCache); + currentCache = composite; } return composite; @@ -105,18 +98,19 @@ private static CompositeCacheEntry GetOrUpgradeToCompositeCache(RuntimeType type public static TCache GetOrCreate(RuntimeType type) { - if (type.GenericCache is null) + ref object? genericCache = ref type.Cache.GenericCache; + if (genericCache is null) { TCache newCache = TCache.Create(type); - type.GenericCache = newCache; + genericCache = newCache; return newCache; } - else if (type.GenericCache is TCache existing) + else if (genericCache is TCache existing) { return existing; } - CompositeCacheEntry composite = GetOrUpgradeToCompositeCache(type); + CompositeCacheEntry composite = GetOrUpgradeToCompositeCache(ref genericCache); if (composite.GetNestedCache(TCache.Kind) is TCache cache) { @@ -128,15 +122,16 @@ public static TCache GetOrCreate(RuntimeType type) public static TCache? Find(RuntimeType type) { - if (type.GenericCache is null) + object? genericCache = type.CacheIfExists?.GenericCache; + if (genericCache is null) { return null; } - else if (type.GenericCache is TCache existing) + else if (genericCache is TCache existing) { return existing; } - else if (type.GenericCache is CompositeCacheEntry composite) + else if (genericCache is CompositeCacheEntry composite) { return (TCache)composite.GetNestedCache(TCache.Kind); } @@ -148,9 +143,10 @@ public static TCache GetOrCreate(RuntimeType type) public static void Overwrite(RuntimeType type, TCache cache) { - if (type.GenericCache is null) + ref object? genericCache = ref type.Cache.GenericCache; + if (genericCache is null) { - type.GenericCache = cache; + genericCache = cache; return; } @@ -159,7 +155,7 @@ public static void Overwrite(RuntimeType type, TCache cache) // but we can't easily do a lock-free CompareExchange with the current design, // and we can't assume that we won't have one thread adding another item to the cache // while another is trying to overwrite the (currently) only entry in the cache. - CompositeCacheEntry composite = GetOrUpgradeToCompositeCache(type); + CompositeCacheEntry composite = GetOrUpgradeToCompositeCache(ref genericCache); composite.OverwriteNestedCache(cache); } From e74cd87078c4bd66d6f44ae46b7588250f982adc Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 14 May 2024 12:05:09 -0700 Subject: [PATCH 07/17] Do only one read in GetOrCreate. --- .../src/System/RuntimeType.GenericCache.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs index b680e2e543bbb..d732cd9bff069 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs @@ -99,18 +99,23 @@ private static CompositeCacheEntry GetOrUpgradeToCompositeCache(ref object curre public static TCache GetOrCreate(RuntimeType type) { ref object? genericCache = ref type.Cache.GenericCache; - if (genericCache is null) + // Read the GenericCache once to avoid multiple reads of the same field. + object? currentCache = genericCache; + if (currentCache is null) { TCache newCache = TCache.Create(type); - genericCache = newCache; + currentCache = newCache; return newCache; } - else if (genericCache is TCache existing) + else if (currentCache is TCache existing) { return existing; } - CompositeCacheEntry composite = GetOrUpgradeToCompositeCache(ref genericCache); + CompositeCacheEntry composite = GetOrUpgradeToCompositeCache(ref currentCache); + // Update the GenericCache with the new composite cache if it changed. + // If we race here it's okay, we might just end up re-creating a new entry next time. + genericCache = currentCache; if (composite.GetNestedCache(TCache.Kind) is TCache cache) { From a7e673775c29a21a82f46e32edca78dcbe73bca7 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 14 May 2024 14:17:30 -0700 Subject: [PATCH 08/17] Fix assignment in Create case --- .../src/System/RuntimeType.GenericCache.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs index d732cd9bff069..35f1c6f21c4e7 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs @@ -104,7 +104,7 @@ public static TCache GetOrCreate(RuntimeType type) if (currentCache is null) { TCache newCache = TCache.Create(type); - currentCache = newCache; + genericCache = newCache; return newCache; } else if (currentCache is TCache existing) @@ -149,7 +149,8 @@ public static TCache GetOrCreate(RuntimeType type) public static void Overwrite(RuntimeType type, TCache cache) { ref object? genericCache = ref type.Cache.GenericCache; - if (genericCache is null) + object? currentCache = genericCache; + if (currentCache is null) { genericCache = cache; return; @@ -160,8 +161,8 @@ public static void Overwrite(RuntimeType type, TCache cache) // but we can't easily do a lock-free CompareExchange with the current design, // and we can't assume that we won't have one thread adding another item to the cache // while another is trying to overwrite the (currently) only entry in the cache. - CompositeCacheEntry composite = GetOrUpgradeToCompositeCache(ref genericCache); - + CompositeCacheEntry composite = GetOrUpgradeToCompositeCache(ref currentCache); + genericCache = currentCache; composite.OverwriteNestedCache(cache); } } From 0f76725b66fd2048d9df9b9f1d9bd61ef28ba9d6 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 15 May 2024 15:46:06 -0700 Subject: [PATCH 09/17] Genericize more and remove more casts. Return a ref into the boxed composite entry to make sure we're actually updating the composite entry. --- .../src/System/RuntimeType.GenericCache.cs | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs index 35f1c6f21c4e7..7a16516a0a447 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs @@ -59,15 +59,17 @@ public static CompositeCacheEntry Create(IGenericCacheEntry cache) return composite; } - public IGenericCacheEntry GetNestedCache(IGenericCacheEntry.GenericCacheKind kind) + public T? GetNestedCache() + where T: class, IGenericCacheEntry { - return (IGenericCacheEntry)GetCacheFieldForKind(kind)!; + return Unsafe.As(ref GetCacheFieldForKind(T.Kind)); } - public IGenericCacheEntry OverwriteNestedCache(T cache) - where T : IGenericCacheEntry + public T OverwriteNestedCache(T cache) + where T : class, IGenericCacheEntry { - return (IGenericCacheEntry)(GetCacheFieldForKind(T.Kind) = cache); + GetCacheFieldForKind(T.Kind) = cache; + return cache; } } } @@ -84,16 +86,15 @@ internal interface IGenericCacheEntry : IGenericCacheEntry public static abstract TCache Create(RuntimeType type); - private static CompositeCacheEntry GetOrUpgradeToCompositeCache(ref object currentCache) + private static ref CompositeCacheEntry GetOrUpgradeToCompositeCache(ref object currentCache) { - if (currentCache is not CompositeCacheEntry composite) + if (currentCache is not CompositeCacheEntry) { // Convert the current cache into a composite cache. - composite = CompositeCacheEntry.Create((IGenericCacheEntry)currentCache); - currentCache = composite; + currentCache = CompositeCacheEntry.Create((IGenericCacheEntry)currentCache); } - return composite; + return ref Unsafe.Unbox(currentCache); } public static TCache GetOrCreate(RuntimeType type) @@ -112,17 +113,17 @@ public static TCache GetOrCreate(RuntimeType type) return existing; } - CompositeCacheEntry composite = GetOrUpgradeToCompositeCache(ref currentCache); + ref CompositeCacheEntry composite = ref GetOrUpgradeToCompositeCache(ref currentCache); // Update the GenericCache with the new composite cache if it changed. // If we race here it's okay, we might just end up re-creating a new entry next time. genericCache = currentCache; - if (composite.GetNestedCache(TCache.Kind) is TCache cache) + if (composite.GetNestedCache() is {} cache) { return cache; } - return (TCache)composite.OverwriteNestedCache(TCache.Create(type)); + return composite.OverwriteNestedCache(TCache.Create(type)); } public static TCache? Find(RuntimeType type) @@ -138,7 +139,7 @@ public static TCache GetOrCreate(RuntimeType type) } else if (genericCache is CompositeCacheEntry composite) { - return (TCache)composite.GetNestedCache(TCache.Kind); + return composite.GetNestedCache(); } else { @@ -161,7 +162,7 @@ public static void Overwrite(RuntimeType type, TCache cache) // but we can't easily do a lock-free CompareExchange with the current design, // and we can't assume that we won't have one thread adding another item to the cache // while another is trying to overwrite the (currently) only entry in the cache. - CompositeCacheEntry composite = GetOrUpgradeToCompositeCache(ref currentCache); + ref CompositeCacheEntry composite = ref GetOrUpgradeToCompositeCache(ref currentCache); genericCache = currentCache; composite.OverwriteNestedCache(cache); } From 4013571a17e4291a5cd02424f78cc5ba421984c4 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 15 May 2024 21:19:50 -0700 Subject: [PATCH 10/17] Force-inline static members to ensure they get non-shared instantiations (faster in this case) --- .../src/System/RuntimeType.GenericCache.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs index 7a16516a0a447..0460dbccb1150 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs @@ -97,6 +97,7 @@ private static ref CompositeCacheEntry GetOrUpgradeToCompositeCache(ref object c return ref Unsafe.Unbox(currentCache); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TCache GetOrCreate(RuntimeType type) { ref object? genericCache = ref type.Cache.GenericCache; @@ -126,6 +127,7 @@ public static TCache GetOrCreate(RuntimeType type) return composite.OverwriteNestedCache(TCache.Create(type)); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TCache? Find(RuntimeType type) { object? genericCache = type.CacheIfExists?.GenericCache; @@ -147,6 +149,7 @@ public static TCache GetOrCreate(RuntimeType type) } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Overwrite(RuntimeType type, TCache cache) { ref object? genericCache = ref type.Cache.GenericCache; From be01bb34fe16e3b0fd0bf33e2952dfcc0b14775d Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 15 May 2024 21:20:22 -0700 Subject: [PATCH 11/17] Don't create two cache entries in the Activator.CreateInstance scenario --- .../System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs index a904e9f63014a..0e6d101ee36cf 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs @@ -3947,7 +3947,8 @@ internal object GetUninitializedObject() throw new MissingMethodException(SR.Format(SR.Arg_NoDefCTor, this)); } - object? obj = GetOrCreateCacheEntry().CreateUninitializedObject(this); + // We reuse ActivatorCache here to ensure that we aren't always creating two entries in the cache. + object? obj = GetOrCreateCacheEntry().CreateUninitializedObject(this); try { cache.CallConstructor(obj); From 022350d72504ec8a0d7b60e17c3c33dee5f5d751 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 16 May 2024 14:52:30 -0700 Subject: [PATCH 12/17] Remove unsafety and make CompositeCacheEntry implement IGenericCacheEntry --- .../src/System/Array.CoreCLR.cs | 2 +- .../src/System/Enum.CoreCLR.cs | 2 +- .../src/System/RuntimeType.ActivatorCache.cs | 2 +- .../src/System/RuntimeType.CoreCLR.cs | 7 +- ...meType.CreateUninitializedCache.CoreCLR.cs | 2 +- .../src/System/RuntimeType.GenericCache.cs | 72 +++++++++---------- 6 files changed, 41 insertions(+), 46 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs index 7da9e5f64a440..ea547ae69c93d 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs @@ -662,7 +662,7 @@ internal sealed unsafe partial class ArrayInitializeCache : RuntimeType.IGeneric [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "Array_GetElementConstructorEntrypoint")] private static partial delegate* GetElementConstructorEntrypoint(QCallTypeHandle arrayType); - static RuntimeType.IGenericCacheEntry.GenericCacheKind RuntimeType.IGenericCacheEntry.Kind => RuntimeType.IGenericCacheEntry.GenericCacheKind.ArrayInitialize; + static RuntimeType.IGenericCacheEntry.GenericCacheKind RuntimeType.IGenericCacheEntry.Kind => RuntimeType.IGenericCacheEntry.GenericCacheKind.ArrayInitialize; private ArrayInitializeCache(delegate* constructorEntrypoint) { diff --git a/src/coreclr/System.Private.CoreLib/src/System/Enum.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Enum.CoreCLR.cs index c41dffdec66a2..9c5a88fa1f44a 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Enum.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Enum.CoreCLR.cs @@ -105,7 +105,7 @@ static EnumInfo InitializeEnumInfo(RuntimeType enumType, bool getNames internal sealed partial class EnumInfo : RuntimeType.IGenericCacheEntry> { - static RuntimeType.IGenericCacheEntry.GenericCacheKind RuntimeType.IGenericCacheEntry.Kind => RuntimeType.IGenericCacheEntry.GenericCacheKind.EnumInfo; + static RuntimeType.IGenericCacheEntry.GenericCacheKind RuntimeType.IGenericCacheEntry>.Kind => RuntimeType.IGenericCacheEntry.GenericCacheKind.EnumInfo; [MethodImpl(MethodImplOptions.NoInlining)] public static EnumInfo CreateWithNames(RuntimeType type) diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.ActivatorCache.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.ActivatorCache.cs index f923954aea948..41082f07ad9fc 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.ActivatorCache.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.ActivatorCache.cs @@ -28,7 +28,7 @@ private sealed unsafe class ActivatorCache : IGenericCacheEntry private readonly RuntimeType _originalRuntimeType; #endif - static IGenericCacheEntry.GenericCacheKind IGenericCacheEntry.Kind => IGenericCacheEntry.GenericCacheKind.Activator; + static IGenericCacheEntry.GenericCacheKind IGenericCacheEntry.Kind => IGenericCacheEntry.GenericCacheKind.Activator; public static ActivatorCache Create(RuntimeType type) => new ActivatorCache(type); diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs index 0e6d101ee36cf..4bdbfa5191218 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs @@ -1455,8 +1455,7 @@ internal T[] GetMemberList(MemberListType listType, string? name, CacheType cach private static object? s_methodInstantiationsLock; private string? m_defaultMemberName; // Generic cache for rare scenario specific data. - // See IGenericCacheEntry for more information. - private object? m_genericCache; + private IGenericCacheEntry? m_genericCache; private object[]? _emptyArray; // Object array cache for Attribute.GetCustomAttributes() pathological no-result case. private RuntimeType? _genericTypeDefinition; #endregion @@ -1499,11 +1498,11 @@ private MemberInfoCache GetMemberCache(ref MemberInfoCache? m_cache) #region Internal Members - internal ref object? GenericCache => ref m_genericCache; + internal ref IGenericCacheEntry? GenericCache => ref m_genericCache; private sealed class FunctionPointerCache : IGenericCacheEntry { - static IGenericCacheEntry.GenericCacheKind IGenericCacheEntry.Kind => IGenericCacheEntry.GenericCacheKind.FunctionPointer; + static IGenericCacheEntry.GenericCacheKind IGenericCacheEntry.Kind => IGenericCacheEntry.GenericCacheKind.FunctionPointer; public Type[] FunctionPointerReturnAndParameterTypes { get; } diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CreateUninitializedCache.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CreateUninitializedCache.CoreCLR.cs index d8c2a987b654b..775fd4a32f55a 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CreateUninitializedCache.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CreateUninitializedCache.CoreCLR.cs @@ -14,7 +14,7 @@ internal sealed partial class RuntimeType /// private sealed unsafe partial class CreateUninitializedCache : IGenericCacheEntry { - static IGenericCacheEntry.GenericCacheKind IGenericCacheEntry.Kind => IGenericCacheEntry.GenericCacheKind.CreateUninitialized; + static IGenericCacheEntry.GenericCacheKind IGenericCacheEntry.Kind => IGenericCacheEntry.GenericCacheKind.CreateUninitialized; public static CreateUninitializedCache Create(RuntimeType type) => new CreateUninitializedCache(type); // The managed calli to the newobj allocator, plus its first argument (MethodTable*). diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs index 0460dbccb1150..2db9a4d71be01 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs @@ -31,44 +31,38 @@ protected enum GenericCacheKind Count } - protected abstract GenericCacheKind CacheKind { get; } - - protected static abstract GenericCacheKind Kind { get; } + protected abstract GenericCacheKind Kind { get; } /// /// A composite cache entry that can store multiple cache entries of different kinds. /// - [InlineArray((int)GenericCacheKind.Count)] - protected struct CompositeCacheEntry + protected sealed class CompositeCacheEntry : IGenericCacheEntry { - // Typed as object as interfaces with static abstracts can't be - // used directly as generic types. - private object? _field; + GenericCacheKind IGenericCacheEntry.Kind => throw new UnreachableException(); - [UnscopedRef] - private ref object? GetCacheFieldForKind(IGenericCacheEntry.GenericCacheKind kind) + [InlineArray((int)GenericCacheKind.Count)] + private struct Storage { - Span entries = this; - return ref entries[(int)kind]; + // Typed as object as interfaces with static abstracts can't be + // used directly as generic types. + private IGenericCacheEntry? _field; } - public static CompositeCacheEntry Create(IGenericCacheEntry cache) + private Storage _storage; + + public CompositeCacheEntry(IGenericCacheEntry cache) { - CompositeCacheEntry composite = default; - composite.GetCacheFieldForKind(cache.CacheKind) = cache; - return composite; + _storage[(int)cache.Kind] = cache; } - public T? GetNestedCache() - where T: class, IGenericCacheEntry + public IGenericCacheEntry? GetNestedCache(GenericCacheKind kind) { - return Unsafe.As(ref GetCacheFieldForKind(T.Kind)); + return _storage[(int)kind]; } - public T OverwriteNestedCache(T cache) - where T : class, IGenericCacheEntry + public IGenericCacheEntry OverwriteNestedCache(GenericCacheKind kind, IGenericCacheEntry cache) { - GetCacheFieldForKind(T.Kind) = cache; + _storage[(int)kind] = cache; return cache; } } @@ -82,27 +76,29 @@ public T OverwriteNestedCache(T cache) internal interface IGenericCacheEntry : IGenericCacheEntry where TCache: class, IGenericCacheEntry { - GenericCacheKind IGenericCacheEntry.CacheKind => TCache.Kind; + GenericCacheKind IGenericCacheEntry.Kind => TCache.Kind; + + protected static new abstract GenericCacheKind Kind { get; } public static abstract TCache Create(RuntimeType type); - private static ref CompositeCacheEntry GetOrUpgradeToCompositeCache(ref object currentCache) + private static CompositeCacheEntry GetOrUpgradeToCompositeCache(ref IGenericCacheEntry currentCache) { - if (currentCache is not CompositeCacheEntry) + if (currentCache is not CompositeCacheEntry composite) { // Convert the current cache into a composite cache. - currentCache = CompositeCacheEntry.Create((IGenericCacheEntry)currentCache); + currentCache = composite = new(currentCache); } - return ref Unsafe.Unbox(currentCache); + return composite; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TCache GetOrCreate(RuntimeType type) { - ref object? genericCache = ref type.Cache.GenericCache; + ref IGenericCacheEntry? genericCache = ref type.Cache.GenericCache; // Read the GenericCache once to avoid multiple reads of the same field. - object? currentCache = genericCache; + IGenericCacheEntry? currentCache = genericCache; if (currentCache is null) { TCache newCache = TCache.Create(type); @@ -114,23 +110,23 @@ public static TCache GetOrCreate(RuntimeType type) return existing; } - ref CompositeCacheEntry composite = ref GetOrUpgradeToCompositeCache(ref currentCache); + CompositeCacheEntry composite = GetOrUpgradeToCompositeCache(ref currentCache); // Update the GenericCache with the new composite cache if it changed. // If we race here it's okay, we might just end up re-creating a new entry next time. genericCache = currentCache; - if (composite.GetNestedCache() is {} cache) + if (composite.GetNestedCache(TCache.Kind) is TCache cache) { return cache; } - return composite.OverwriteNestedCache(TCache.Create(type)); + return (TCache)composite.OverwriteNestedCache(TCache.Kind, TCache.Create(type)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TCache? Find(RuntimeType type) { - object? genericCache = type.CacheIfExists?.GenericCache; + IGenericCacheEntry? genericCache = type.CacheIfExists?.GenericCache; if (genericCache is null) { return null; @@ -141,7 +137,7 @@ public static TCache GetOrCreate(RuntimeType type) } else if (genericCache is CompositeCacheEntry composite) { - return composite.GetNestedCache(); + return (TCache?)composite.GetNestedCache(TCache.Kind); } else { @@ -152,8 +148,8 @@ public static TCache GetOrCreate(RuntimeType type) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Overwrite(RuntimeType type, TCache cache) { - ref object? genericCache = ref type.Cache.GenericCache; - object? currentCache = genericCache; + ref IGenericCacheEntry? genericCache = ref type.Cache.GenericCache; + IGenericCacheEntry? currentCache = genericCache; if (currentCache is null) { genericCache = cache; @@ -165,9 +161,9 @@ public static void Overwrite(RuntimeType type, TCache cache) // but we can't easily do a lock-free CompareExchange with the current design, // and we can't assume that we won't have one thread adding another item to the cache // while another is trying to overwrite the (currently) only entry in the cache. - ref CompositeCacheEntry composite = ref GetOrUpgradeToCompositeCache(ref currentCache); + CompositeCacheEntry composite = GetOrUpgradeToCompositeCache(ref currentCache); genericCache = currentCache; - composite.OverwriteNestedCache(cache); + composite.OverwriteNestedCache(TCache.Kind, cache); } } } From 0886ce7f20bc5be4676009242d2fabc4f55e2775 Mon Sep 17 00:00:00 2001 From: Jan Kotas Date: Tue, 28 May 2024 16:44:20 -0700 Subject: [PATCH 13/17] Replace InlineArray with explicit fields --- .../src/System/Array.CoreCLR.cs | 9 +- .../src/System/Enum.CoreCLR.cs | 10 +- .../src/System/RuntimeType.ActivatorCache.cs | 8 +- .../src/System/RuntimeType.CoreCLR.cs | 12 +- ...meType.CreateUninitializedCache.CoreCLR.cs | 7 +- .../src/System/RuntimeType.GenericCache.cs | 186 ++++++------------ 6 files changed, 87 insertions(+), 145 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs index ea547ae69c93d..8c48595d4e2d3 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs @@ -662,17 +662,14 @@ internal sealed unsafe partial class ArrayInitializeCache : RuntimeType.IGeneric [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "Array_GetElementConstructorEntrypoint")] private static partial delegate* GetElementConstructorEntrypoint(QCallTypeHandle arrayType); - static RuntimeType.IGenericCacheEntry.GenericCacheKind RuntimeType.IGenericCacheEntry.Kind => RuntimeType.IGenericCacheEntry.GenericCacheKind.ArrayInitialize; - private ArrayInitializeCache(delegate* constructorEntrypoint) { ConstructorEntrypoint = constructorEntrypoint; } - public static ArrayInitializeCache Create(RuntimeType arrayType) - { - return new(GetElementConstructorEntrypoint(new QCallTypeHandle(ref arrayType))); - } + public static ArrayInitializeCache Create(RuntimeType arrayType) => new(GetElementConstructorEntrypoint(new QCallTypeHandle(ref arrayType))); + public void InitializeCompositeCache(RuntimeType.CompositeCacheEntry compositeEntry) => compositeEntry._arrayInitializeCache = this; + public static ref ArrayInitializeCache? GetStorageRef(RuntimeType.CompositeCacheEntry compositeEntry) => ref compositeEntry._arrayInitializeCache; } } diff --git a/src/coreclr/System.Private.CoreLib/src/System/Enum.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Enum.CoreCLR.cs index 9c5a88fa1f44a..0cb7da737873f 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Enum.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Enum.CoreCLR.cs @@ -87,6 +87,8 @@ private static EnumInfo GetEnumInfo(RuntimeType enumType, bo typeof(TStorage) == typeof(nuint) || typeof(TStorage) == typeof(float) || typeof(TStorage) == typeof(double) || typeof(TStorage) == typeof(char), $"Unexpected {nameof(TStorage)} == {typeof(TStorage)}"); + return EnumInfo.CreateWithNames(enumType); +#if false // TODO!!!!! return enumType.FindCacheEntry>() is {} info && (!getNames || info.Names is not null) ? info : InitializeEnumInfo(enumType, getNames); @@ -101,13 +103,11 @@ static EnumInfo InitializeEnumInfo(RuntimeType enumType, bool getNames ? enumType.OverwriteCacheEntry(EnumInfo.CreateWithNames(enumType)) : enumType.GetOrCreateCacheEntry>(); } +#endif } internal sealed partial class EnumInfo : RuntimeType.IGenericCacheEntry> { - static RuntimeType.IGenericCacheEntry.GenericCacheKind RuntimeType.IGenericCacheEntry>.Kind => RuntimeType.IGenericCacheEntry.GenericCacheKind.EnumInfo; - - [MethodImpl(MethodImplOptions.NoInlining)] public static EnumInfo CreateWithNames(RuntimeType type) { TStorage[]? values = null; @@ -126,7 +126,6 @@ public static EnumInfo CreateWithNames(RuntimeType type) return new EnumInfo(hasFlagsAttribute, values, names!); } - [MethodImpl(MethodImplOptions.NoInlining)] public static EnumInfo Create(RuntimeType type) { TStorage[]? values = null; @@ -144,6 +143,9 @@ public static EnumInfo Create(RuntimeType type) return new EnumInfo(hasFlagsAttribute, values, null!); } + + public void InitializeCompositeCache(RuntimeType.CompositeCacheEntry compositeEntry) => compositeEntry._enumInfo = this; + public static ref EnumInfo? GetStorageRef(RuntimeType.CompositeCacheEntry compositeEntry) => throw new Exception(); // TODO!!! } } } diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.ActivatorCache.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.ActivatorCache.cs index 41082f07ad9fc..17dd0f74d6879 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.ActivatorCache.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.ActivatorCache.cs @@ -13,7 +13,7 @@ internal sealed partial class RuntimeType /// A cache which allows optimizing , /// , and related APIs. /// - private sealed unsafe class ActivatorCache : IGenericCacheEntry + internal sealed unsafe class ActivatorCache : IGenericCacheEntry { // The managed calli to the newobj allocator, plus its first argument (MethodTable*). // In the case of the COM allocator, first arg is ComClassFactory*, not MethodTable*. @@ -28,9 +28,9 @@ private sealed unsafe class ActivatorCache : IGenericCacheEntry private readonly RuntimeType _originalRuntimeType; #endif - static IGenericCacheEntry.GenericCacheKind IGenericCacheEntry.Kind => IGenericCacheEntry.GenericCacheKind.Activator; - - public static ActivatorCache Create(RuntimeType type) => new ActivatorCache(type); + public static ActivatorCache Create(RuntimeType type) => new(type); + public void InitializeCompositeCache(RuntimeType.CompositeCacheEntry compositeEntry) => compositeEntry._activatorCache = this; + public static ref ActivatorCache? GetStorageRef(RuntimeType.CompositeCacheEntry compositeEntry) => ref compositeEntry._activatorCache; private ActivatorCache(RuntimeType rt) { diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs index 4bdbfa5191218..ac43ba7f83356 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs @@ -1500,10 +1500,8 @@ private MemberInfoCache GetMemberCache(ref MemberInfoCache? m_cache) internal ref IGenericCacheEntry? GenericCache => ref m_genericCache; - private sealed class FunctionPointerCache : IGenericCacheEntry + internal sealed class FunctionPointerCache : IGenericCacheEntry { - static IGenericCacheEntry.GenericCacheKind IGenericCacheEntry.Kind => IGenericCacheEntry.GenericCacheKind.FunctionPointer; - public Type[] FunctionPointerReturnAndParameterTypes { get; } private FunctionPointerCache(Type[] functionPointerReturnAndParameterTypes) @@ -1516,6 +1514,8 @@ public static FunctionPointerCache Create(RuntimeType type) Debug.Assert(type.IsFunctionPointer); return new(RuntimeTypeHandle.GetArgumentTypesFromFunctionPointer(type)); } + public void InitializeCompositeCache(RuntimeType.CompositeCacheEntry compositeEntry) => compositeEntry._functionPointerCache = this; + public static ref FunctionPointerCache? GetStorageRef(RuntimeType.CompositeCacheEntry compositeEntry) => ref compositeEntry._functionPointerCache; } internal Type[] FunctionPointerReturnAndParameterTypes @@ -1934,6 +1934,7 @@ internal T GetOrCreateCacheEntry() return IGenericCacheEntry.GetOrCreate(this); } +#if false internal T? FindCacheEntry() where T : class, IGenericCacheEntry { @@ -1946,6 +1947,7 @@ internal T OverwriteCacheEntry(T entry) IGenericCacheEntry.Overwrite(this, entry); return entry; } +#endif internal static FieldInfo GetFieldInfo(IRuntimeFieldInfo fieldHandle) { @@ -2060,7 +2062,7 @@ private static void SplitName(string? fullname, out string? name, out string? ns name = fullname; } } - #endregion +#endregion #region Filters internal static BindingFlags FilterPreCalculate(bool isPublic, bool isInherited, bool isStatic) @@ -2387,7 +2389,7 @@ private static bool FilterApplyMethodBase( #endregion - #endregion +#endregion #region Private Data Members diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CreateUninitializedCache.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CreateUninitializedCache.CoreCLR.cs index 775fd4a32f55a..e70078403f89c 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CreateUninitializedCache.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CreateUninitializedCache.CoreCLR.cs @@ -12,10 +12,11 @@ internal sealed partial class RuntimeType /// /// A cache which allows optimizing . /// - private sealed unsafe partial class CreateUninitializedCache : IGenericCacheEntry + internal sealed unsafe partial class CreateUninitializedCache : IGenericCacheEntry { - static IGenericCacheEntry.GenericCacheKind IGenericCacheEntry.Kind => IGenericCacheEntry.GenericCacheKind.CreateUninitialized; - public static CreateUninitializedCache Create(RuntimeType type) => new CreateUninitializedCache(type); + public static CreateUninitializedCache Create(RuntimeType type) => new(type); + public void InitializeCompositeCache(RuntimeType.CompositeCacheEntry compositeEntry) => compositeEntry._createUninitializedCache = this; + public static ref CreateUninitializedCache? GetStorageRef(RuntimeType.CompositeCacheEntry compositeEntry) => ref compositeEntry._createUninitializedCache; // The managed calli to the newobj allocator, plus its first argument (MethodTable*). private readonly delegate* _pfnAllocator; diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs index 2db9a4d71be01..cdcade2dac2a5 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs @@ -5,67 +5,33 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Threading; +using static System.RuntimeType; namespace System { internal sealed partial class RuntimeType { /// - /// A base interface for all cache entries that can be stored in . + /// A composite cache entry that can store multiple cache entries of different kinds. /// - internal interface IGenericCacheEntry + internal sealed class CompositeCacheEntry : IGenericCacheEntry { - /// - /// The different kinds of entries that can be stored in . - /// - protected enum GenericCacheKind - { - ArrayInitialize, - EnumInfo, - Activator, - CreateUninitialized, - FunctionPointer, - /// - /// The number of different kinds of cache entries. This should always be the last entry. - /// - Count - } - - protected abstract GenericCacheKind Kind { get; } - - /// - /// A composite cache entry that can store multiple cache entries of different kinds. - /// - protected sealed class CompositeCacheEntry : IGenericCacheEntry - { - GenericCacheKind IGenericCacheEntry.Kind => throw new UnreachableException(); - - [InlineArray((int)GenericCacheKind.Count)] - private struct Storage - { - // Typed as object as interfaces with static abstracts can't be - // used directly as generic types. - private IGenericCacheEntry? _field; - } - - private Storage _storage; - - public CompositeCacheEntry(IGenericCacheEntry cache) - { - _storage[(int)cache.Kind] = cache; - } + internal ActivatorCache? _activatorCache; + internal CreateUninitializedCache? _createUninitializedCache; + internal RuntimeTypeCache.FunctionPointerCache? _functionPointerCache; + internal Array.ArrayInitializeCache? _arrayInitializeCache; + internal IGenericCacheEntry? _enumInfo; - public IGenericCacheEntry? GetNestedCache(GenericCacheKind kind) - { - return _storage[(int)kind]; - } + void IGenericCacheEntry.InitializeCompositeCache(CompositeCacheEntry compositeEntry) => throw new UnreachableException(); + } - public IGenericCacheEntry OverwriteNestedCache(GenericCacheKind kind, IGenericCacheEntry cache) - { - _storage[(int)kind] = cache; - return cache; - } - } + /// + /// A base interface for all cache entries that can be stored in . + /// + internal interface IGenericCacheEntry + { + public void InitializeCompositeCache(CompositeCacheEntry compositeEntry); } /// @@ -74,24 +40,11 @@ public IGenericCacheEntry OverwriteNestedCache(GenericCacheKind kind, IGenericCa /// /// The cache entry type. internal interface IGenericCacheEntry : IGenericCacheEntry - where TCache: class, IGenericCacheEntry + where TCache : class, IGenericCacheEntry { - GenericCacheKind IGenericCacheEntry.Kind => TCache.Kind; - - protected static new abstract GenericCacheKind Kind { get; } - public static abstract TCache Create(RuntimeType type); - private static CompositeCacheEntry GetOrUpgradeToCompositeCache(ref IGenericCacheEntry currentCache) - { - if (currentCache is not CompositeCacheEntry composite) - { - // Convert the current cache into a composite cache. - currentCache = composite = new(currentCache); - } - - return composite; - } + public static abstract ref TCache? GetStorageRef(CompositeCacheEntry compositeEntry); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TCache GetOrCreate(RuntimeType type) @@ -99,71 +52,58 @@ public static TCache GetOrCreate(RuntimeType type) ref IGenericCacheEntry? genericCache = ref type.Cache.GenericCache; // Read the GenericCache once to avoid multiple reads of the same field. IGenericCacheEntry? currentCache = genericCache; - if (currentCache is null) - { - TCache newCache = TCache.Create(type); - genericCache = newCache; - return newCache; - } - else if (currentCache is TCache existing) + if (currentCache is not null) { - return existing; + if (currentCache is TCache existing) + { + return existing; + } + if (currentCache is CompositeCacheEntry composite) + { + TCache? existingComposite = TCache.GetStorageRef(composite); + if (existingComposite != null) + return existingComposite; + } } - CompositeCacheEntry composite = GetOrUpgradeToCompositeCache(ref currentCache); - // Update the GenericCache with the new composite cache if it changed. - // If we race here it's okay, we might just end up re-creating a new entry next time. - genericCache = currentCache; - - if (composite.GetNestedCache(TCache.Kind) is TCache cache) - { - return cache; - } - - return (TCache)composite.OverwriteNestedCache(TCache.Kind, TCache.Create(type)); + return CreateAndCache(type); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TCache? Find(RuntimeType type) + [MethodImpl(MethodImplOptions.NoInlining)] + private static TCache CreateAndCache(RuntimeType type) { - IGenericCacheEntry? genericCache = type.CacheIfExists?.GenericCache; - if (genericCache is null) - { - return null; - } - else if (genericCache is TCache existing) + while (true) { - return existing; + ref IGenericCacheEntry? genericCache = ref type.Cache.GenericCache; + + // Update to CompositeCacheEntry if necessary + IGenericCacheEntry? existing = genericCache; + if (existing is null) + { + TCache newEntry = TCache.Create(type); + if (Interlocked.CompareExchange(ref genericCache, newEntry, null) == null) + return newEntry; + // We lost the race, try again. + } + else + { + if (existing is TCache existingTyped) + return existingTyped; + + if (existing is not CompositeCacheEntry compositeCache) + { + compositeCache = new CompositeCacheEntry(); + existing.InitializeCompositeCache(compositeCache); + if (Interlocked.CompareExchange(ref genericCache, compositeCache, existing) != existing) + continue; // We lost the race, try again. + } + + TCache newEntry = TCache.Create(type); + if (Interlocked.CompareExchange(ref TCache.GetStorageRef(compositeCache), newEntry, null) == null) + return newEntry; + // We lost the race, try again. + } } - else if (genericCache is CompositeCacheEntry composite) - { - return (TCache?)composite.GetNestedCache(TCache.Kind); - } - else - { - return null; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Overwrite(RuntimeType type, TCache cache) - { - ref IGenericCacheEntry? genericCache = ref type.Cache.GenericCache; - IGenericCacheEntry? currentCache = genericCache; - if (currentCache is null) - { - genericCache = cache; - return; - } - - // Always upgrade to a composite cache here. - // We would like to be able to avoid this when the GenericCache is the same type as the current cache, - // but we can't easily do a lock-free CompareExchange with the current design, - // and we can't assume that we won't have one thread adding another item to the cache - // while another is trying to overwrite the (currently) only entry in the cache. - CompositeCacheEntry composite = GetOrUpgradeToCompositeCache(ref currentCache); - genericCache = currentCache; - composite.OverwriteNestedCache(TCache.Kind, cache); } } } From bdeb28bc9b6f0e623ccc63b77827b740490a4f71 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 28 May 2024 17:00:23 -0700 Subject: [PATCH 14/17] Reimplement EnumInfo support --- .../src/System/Enum.CoreCLR.cs | 10 ++-- .../src/System/RuntimeType.CoreCLR.cs | 6 +-- .../src/System/RuntimeType.GenericCache.cs | 54 +++++++++++++++++++ 3 files changed, 61 insertions(+), 9 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Enum.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Enum.CoreCLR.cs index 0cb7da737873f..85c66d41be6bd 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Enum.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Enum.CoreCLR.cs @@ -87,8 +87,6 @@ private static EnumInfo GetEnumInfo(RuntimeType enumType, bo typeof(TStorage) == typeof(nuint) || typeof(TStorage) == typeof(float) || typeof(TStorage) == typeof(double) || typeof(TStorage) == typeof(char), $"Unexpected {nameof(TStorage)} == {typeof(TStorage)}"); - return EnumInfo.CreateWithNames(enumType); -#if false // TODO!!!!! return enumType.FindCacheEntry>() is {} info && (!getNames || info.Names is not null) ? info : InitializeEnumInfo(enumType, getNames); @@ -100,10 +98,9 @@ static EnumInfo InitializeEnumInfo(RuntimeType enumType, bool getNames // force that copy into the cache even if we already have a cache entry without names // so we don't have to recompute the names if asked again. return getNames - ? enumType.OverwriteCacheEntry(EnumInfo.CreateWithNames(enumType)) + ? enumType.ReplaceCacheEntry(EnumInfo.CreateWithNames(enumType)) : enumType.GetOrCreateCacheEntry>(); } -#endif } internal sealed partial class EnumInfo : RuntimeType.IGenericCacheEntry> @@ -145,7 +142,10 @@ public static EnumInfo Create(RuntimeType type) } public void InitializeCompositeCache(RuntimeType.CompositeCacheEntry compositeEntry) => compositeEntry._enumInfo = this; - public static ref EnumInfo? GetStorageRef(RuntimeType.CompositeCacheEntry compositeEntry) => throw new Exception(); // TODO!!! + + // This type is the only type that will be stored in the _enumInfo field, so we can use Unsafe.As here. + public static ref EnumInfo? GetStorageRef(RuntimeType.CompositeCacheEntry compositeEntry) + => ref Unsafe.As?>(ref compositeEntry._enumInfo); } } } diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs index ac43ba7f83356..3ba7f99d68cc1 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs @@ -1934,20 +1934,18 @@ internal T GetOrCreateCacheEntry() return IGenericCacheEntry.GetOrCreate(this); } -#if false internal T? FindCacheEntry() where T : class, IGenericCacheEntry { return IGenericCacheEntry.Find(this); } - internal T OverwriteCacheEntry(T entry) + internal T ReplaceCacheEntry(T entry) where T : class, IGenericCacheEntry { - IGenericCacheEntry.Overwrite(this, entry); + IGenericCacheEntry.Replace(this, entry); return entry; } -#endif internal static FieldInfo GetFieldInfo(IRuntimeFieldInfo fieldHandle) { diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs index cdcade2dac2a5..53f1ddb781291 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs @@ -69,6 +69,60 @@ public static TCache GetOrCreate(RuntimeType type) return CreateAndCache(type); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TCache? Find(RuntimeType type) + { + ref IGenericCacheEntry? genericCache = ref type.Cache.GenericCache; + // Read the GenericCache once to avoid multiple reads of the same field. + IGenericCacheEntry? currentCache = genericCache; + if (currentCache is TCache existing) + { + return existing; + } + if (currentCache is CompositeCacheEntry composite) + { + return TCache.GetStorageRef(composite); + } + + return null; + } + + public static TCache Replace(RuntimeType type, TCache newEntry) + { + ref IGenericCacheEntry? genericCache = ref type.Cache.GenericCache; + + // If the existing cache is of the same type, we can replace it directly, + // as long as it is not upgraded to a composite cache simultaneously. + while (true) + { + IGenericCacheEntry? existing = genericCache; + if (existing is not (null or TCache)) + break; // We lost the race and we can no longer replace the cache directly. + + if (Interlocked.CompareExchange(ref genericCache, newEntry, existing) == existing) + return newEntry; + // We lost the race, try again. + } + + // If we get here, either we have a composite cache or we need to upgrade to a composite cache. + while (true) + { + IGenericCacheEntry existing = genericCache!; + if (existing is not CompositeCacheEntry compositeCache) + { + compositeCache = new CompositeCacheEntry(); + existing.InitializeCompositeCache(compositeCache); + if (Interlocked.CompareExchange(ref genericCache, compositeCache, existing) != existing) + continue; // We lost the race, try again. + } + + TCache? existingEntry = TCache.GetStorageRef(compositeCache); + if (Interlocked.CompareExchange(ref TCache.GetStorageRef(compositeCache), newEntry, existingEntry) == existingEntry) + return newEntry; + // We lost the race, try again. + } + } + [MethodImpl(MethodImplOptions.NoInlining)] private static TCache CreateAndCache(RuntimeType type) { From ae2be47a590e3791c54d9896ffe51319f12ad70f Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 30 May 2024 09:39:59 -0700 Subject: [PATCH 15/17] PR feedback --- .../src/System/Enum.CoreCLR.cs | 8 ++++---- .../src/System/RuntimeType.CoreCLR.cs | 2 +- .../src/System/RuntimeType.GenericCache.cs | 15 +++++++++------ 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Enum.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Enum.CoreCLR.cs index 85c66d41be6bd..329a6c9343a1b 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Enum.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Enum.CoreCLR.cs @@ -98,14 +98,14 @@ static EnumInfo InitializeEnumInfo(RuntimeType enumType, bool getNames // force that copy into the cache even if we already have a cache entry without names // so we don't have to recompute the names if asked again. return getNames - ? enumType.ReplaceCacheEntry(EnumInfo.CreateWithNames(enumType)) + ? enumType.ReplaceCacheEntry(EnumInfo.Create(enumType, getNames: true)) : enumType.GetOrCreateCacheEntry>(); } } internal sealed partial class EnumInfo : RuntimeType.IGenericCacheEntry> { - public static EnumInfo CreateWithNames(RuntimeType type) + public static EnumInfo Create(RuntimeType type. bool getNames) { TStorage[]? values = null; string[]? names = null; @@ -114,7 +114,7 @@ public static EnumInfo CreateWithNames(RuntimeType type) new QCallTypeHandle(ref type), ObjectHandleOnStack.Create(ref values), ObjectHandleOnStack.Create(ref names), - Interop.BOOL.TRUE); + getNames ? Interop.BOOL.TRUE : Interop.BOOL.FALSE); Debug.Assert(values!.GetType() == typeof(TStorage[])); @@ -123,7 +123,7 @@ public static EnumInfo CreateWithNames(RuntimeType type) return new EnumInfo(hasFlagsAttribute, values, names!); } - public static EnumInfo Create(RuntimeType type) + public static EnumInfo Create(RuntimeType type) => Create(type, getNames: false); { TStorage[]? values = null; string[]? names = null; diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs index 3ba7f99d68cc1..2cfe32e20cf34 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs @@ -2060,7 +2060,7 @@ private static void SplitName(string? fullname, out string? name, out string? ns name = fullname; } } -#endregion + #endregion #region Filters internal static BindingFlags FilterPreCalculate(bool isPublic, bool isInherited, bool isStatic) diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs index 53f1ddb781291..f7cb6b6060c79 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs @@ -75,13 +75,16 @@ public static TCache GetOrCreate(RuntimeType type) ref IGenericCacheEntry? genericCache = ref type.Cache.GenericCache; // Read the GenericCache once to avoid multiple reads of the same field. IGenericCacheEntry? currentCache = genericCache; - if (currentCache is TCache existing) - { - return existing; - } - if (currentCache is CompositeCacheEntry composite) + if (currentCache is not null) { - return TCache.GetStorageRef(composite); + if (currentCache is TCache existing) + { + return existing; + } + if (currentCache is CompositeCacheEntry composite) + { + return TCache.GetStorageRef(composite); + } } return null; From 7adc3a6b3f4b1b023a54c5b93b753240e08bdd68 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 30 May 2024 10:36:53 -0700 Subject: [PATCH 16/17] Cleanup --- .../src/System/Enum.CoreCLR.cs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Enum.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Enum.CoreCLR.cs index 329a6c9343a1b..981d9f25fe16c 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Enum.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Enum.CoreCLR.cs @@ -124,22 +124,6 @@ public static EnumInfo Create(RuntimeType type. bool getNames) } public static EnumInfo Create(RuntimeType type) => Create(type, getNames: false); - { - TStorage[]? values = null; - string[]? names = null; - - GetEnumValuesAndNames( - new QCallTypeHandle(ref type), - ObjectHandleOnStack.Create(ref values), - ObjectHandleOnStack.Create(ref names), - Interop.BOOL.FALSE); - - Debug.Assert(values!.GetType() == typeof(TStorage[])); - - bool hasFlagsAttribute = type.IsDefined(typeof(FlagsAttribute), inherit: false); - - return new EnumInfo(hasFlagsAttribute, values, null!); - } public void InitializeCompositeCache(RuntimeType.CompositeCacheEntry compositeEntry) => compositeEntry._enumInfo = this; From 982a1c458157644923cd843c337f06e260e83db7 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 30 May 2024 13:10:44 -0700 Subject: [PATCH 17/17] Fix typo --- src/coreclr/System.Private.CoreLib/src/System/Enum.CoreCLR.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Enum.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Enum.CoreCLR.cs index 981d9f25fe16c..9c92874f8ec27 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Enum.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Enum.CoreCLR.cs @@ -105,7 +105,7 @@ static EnumInfo InitializeEnumInfo(RuntimeType enumType, bool getNames internal sealed partial class EnumInfo : RuntimeType.IGenericCacheEntry> { - public static EnumInfo Create(RuntimeType type. bool getNames) + public static EnumInfo Create(RuntimeType type, bool getNames) { TStorage[]? values = null; string[]? names = null;