diff --git a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj index a37abf7a0c1167..f14f27121b6d35 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 74e07398481681..8c48595d4e2d3c 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,17 +655,21 @@ 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) + private ArrayInitializeCache(delegate* constructorEntrypoint) { - ConstructorEntrypoint = GetElementConstructorEntrypoint(new QCallTypeHandle(ref arrayType)); + ConstructorEntrypoint = constructorEntrypoint; } + + 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 cc46e2a75d8b2c..9c92874f8ec279 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Enum.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Enum.CoreCLR.cs @@ -87,31 +87,49 @@ 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.ReplaceCacheEntry(EnumInfo.Create(enumType, getNames: true)) + : enumType.GetOrCreateCacheEntry>(); + } + } + + internal sealed partial class EnumInfo : RuntimeType.IGenericCacheEntry> + { + public static EnumInfo Create(RuntimeType type, bool getNames) { 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); 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, names!); } + + public static EnumInfo Create(RuntimeType type) => Create(type, getNames: false); + + public void InitializeCompositeCache(RuntimeType.CompositeCacheEntry compositeEntry) => compositeEntry._enumInfo = this; + + // 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.ActivatorCache.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.ActivatorCache.cs index 4d73cfad391430..17dd0f74d6879a 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 + 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*. @@ -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 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) { 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 c6ea76fde9b85f..2cfe32e20cf34c 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs @@ -1454,11 +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) - private object? m_genericCache; + // Generic cache for rare scenario specific data. + private IGenericCacheEntry? m_genericCache; private object[]? _emptyArray; // Object array cache for Attribute.GetCustomAttributes() pathological no-result case. private RuntimeType? _genericTypeDefinition; #endregion @@ -1501,30 +1498,31 @@ private MemberInfoCache GetMemberCache(ref MemberInfoCache? m_cache) #region Internal Members + internal ref IGenericCacheEntry? GenericCache => ref m_genericCache; - /// - /// 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 + internal sealed class FunctionPointerCache : IGenericCacheEntry { - get => m_genericCache; - set => m_genericCache = value; + 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)); + } + public void InitializeCompositeCache(RuntimeType.CompositeCacheEntry compositeEntry) => compositeEntry._functionPointerCache = this; + public static ref FunctionPointerCache? GetStorageRef(RuntimeType.CompositeCacheEntry compositeEntry) => ref compositeEntry._functionPointerCache; } 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,10 +1928,23 @@ internal FieldInfo GetField(RuntimeFieldHandleInternal field) return retval; } - internal object? GenericCache + internal T GetOrCreateCacheEntry() + where T : class, IGenericCacheEntry + { + return IGenericCacheEntry.GetOrCreate(this); + } + + internal T? FindCacheEntry() + where T : class, IGenericCacheEntry + { + return IGenericCacheEntry.Find(this); + } + + internal T ReplaceCacheEntry(T entry) + where T : class, IGenericCacheEntry { - get => CacheIfExists?.GenericCache; - set => Cache.GenericCache = value; + IGenericCacheEntry.Replace(this, entry); + return entry; } internal static FieldInfo GetFieldInfo(IRuntimeFieldInfo fieldHandle) @@ -2376,7 +2387,7 @@ private static bool FilterApplyMethodBase( #endregion - #endregion +#endregion #region Private Data Members @@ -3886,22 +3897,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 +3910,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 +3939,15 @@ 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); + // 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); 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 710b1b507e4ef2..e70078403f89c7 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,12 @@ internal sealed partial class RuntimeType /// /// A cache which allows optimizing . /// - private sealed unsafe partial class CreateUninitializedCache + internal sealed unsafe partial class CreateUninitializedCache : IGenericCacheEntry { + 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; private readonly void* _allocatorFirstArg; @@ -22,7 +26,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 00000000000000..f7cb6b6060c79f --- /dev/null +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs @@ -0,0 +1,167 @@ +// 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.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 composite cache entry that can store multiple cache entries of different kinds. + /// + internal sealed class CompositeCacheEntry : IGenericCacheEntry + { + internal ActivatorCache? _activatorCache; + internal CreateUninitializedCache? _createUninitializedCache; + internal RuntimeTypeCache.FunctionPointerCache? _functionPointerCache; + internal Array.ArrayInitializeCache? _arrayInitializeCache; + internal IGenericCacheEntry? _enumInfo; + + void IGenericCacheEntry.InitializeCompositeCache(CompositeCacheEntry compositeEntry) => throw new UnreachableException(); + } + + /// + /// A base interface for all cache entries that can be stored in . + /// + internal interface IGenericCacheEntry + { + public void InitializeCompositeCache(CompositeCacheEntry compositeEntry); + } + + /// + /// A typed cache entry. This type provides a base type that handles contruction of entries and maintenance of + /// the in a . + /// + /// The cache entry type. + internal interface IGenericCacheEntry : IGenericCacheEntry + where TCache : class, IGenericCacheEntry + { + public static abstract TCache Create(RuntimeType type); + + public static abstract ref TCache? GetStorageRef(CompositeCacheEntry compositeEntry); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + 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 not null) + { + if (currentCache is TCache existing) + { + return existing; + } + if (currentCache is CompositeCacheEntry composite) + { + TCache? existingComposite = TCache.GetStorageRef(composite); + if (existingComposite != null) + return existingComposite; + } + } + + 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 not null) + { + 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) + { + while (true) + { + 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. + } + } + } + } + } +} 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 419f1a0170a2f2..2f349d8cb59617 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;