Skip to content

Commit 9fca0c3

Browse files
authored
Enable RuntimeType.GenericCache to hold multiple types of cache entries (#102034)
1 parent f6701e5 commit 9fca0c3

File tree

8 files changed

+256
-84
lines changed

8 files changed

+256
-84
lines changed

src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,8 @@
237237
<Compile Include="$(CommonPath)System\Collections\Generic\ArrayBuilder.cs">
238238
<Link>Common\System\Collections\Generic\ArrayBuilder.cs</Link>
239239
</Compile>
240-
<Compile Include="src\System\RuntimeType.CreateUninitializedCache.CoreCLR.cs" />
240+
<Compile Include="src\System\RuntimeType.CreateUninitializedCache.CoreCLR.cs" />
241+
<Compile Include="src\System\RuntimeType.GenericCache.cs" />
241242
</ItemGroup>
242243
<ItemGroup Condition="'$(FeatureComWrappers)' == 'true'">
243244
<Compile Include="$(BclSourcesRoot)\System\Runtime\InteropServices\ComWrappers.cs" />

src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -642,11 +642,7 @@ public unsafe void Initialize()
642642

643643
RuntimeType arrayType = (RuntimeType)GetType();
644644

645-
if (arrayType.GenericCache is not ArrayInitializeCache cache)
646-
{
647-
cache = new ArrayInitializeCache(arrayType);
648-
arrayType.GenericCache = cache;
649-
}
645+
ArrayInitializeCache cache = arrayType.GetOrCreateCacheEntry<ArrayInitializeCache>();
650646

651647
delegate*<ref byte, void> constructorFtn = cache.ConstructorEntrypoint;
652648
ref byte arrayRef = ref MemoryMarshal.GetArrayDataReference(this);
@@ -659,17 +655,21 @@ public unsafe void Initialize()
659655
}
660656
}
661657

662-
private sealed unsafe partial class ArrayInitializeCache
658+
internal sealed unsafe partial class ArrayInitializeCache : RuntimeType.IGenericCacheEntry<ArrayInitializeCache>
663659
{
664660
internal readonly delegate*<ref byte, void> ConstructorEntrypoint;
665661

666662
[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "Array_GetElementConstructorEntrypoint")]
667663
private static partial delegate*<ref byte, void> GetElementConstructorEntrypoint(QCallTypeHandle arrayType);
668664

669-
public ArrayInitializeCache(RuntimeType arrayType)
665+
private ArrayInitializeCache(delegate*<ref byte, void> constructorEntrypoint)
670666
{
671-
ConstructorEntrypoint = GetElementConstructorEntrypoint(new QCallTypeHandle(ref arrayType));
667+
ConstructorEntrypoint = constructorEntrypoint;
672668
}
669+
670+
public static ArrayInitializeCache Create(RuntimeType arrayType) => new(GetElementConstructorEntrypoint(new QCallTypeHandle(ref arrayType)));
671+
public void InitializeCompositeCache(RuntimeType.CompositeCacheEntry compositeEntry) => compositeEntry._arrayInitializeCache = this;
672+
public static ref ArrayInitializeCache? GetStorageRef(RuntimeType.CompositeCacheEntry compositeEntry) => ref compositeEntry._arrayInitializeCache;
673673
}
674674
}
675675

src/coreclr/System.Private.CoreLib/src/System/Enum.CoreCLR.cs

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -87,31 +87,49 @@ private static EnumInfo<TStorage> GetEnumInfo<TStorage>(RuntimeType enumType, bo
8787
typeof(TStorage) == typeof(nuint) || typeof(TStorage) == typeof(float) || typeof(TStorage) == typeof(double) || typeof(TStorage) == typeof(char),
8888
$"Unexpected {nameof(TStorage)} == {typeof(TStorage)}");
8989

90-
return enumType.GenericCache is EnumInfo<TStorage> info && (!getNames || info.Names is not null) ?
90+
return enumType.FindCacheEntry<EnumInfo<TStorage>>() is {} info && (!getNames || info.Names is not null) ?
9191
info :
9292
InitializeEnumInfo(enumType, getNames);
9393

9494
[MethodImpl(MethodImplOptions.NoInlining)]
9595
static EnumInfo<TStorage> InitializeEnumInfo(RuntimeType enumType, bool getNames)
96+
{
97+
// If we're asked to get the cache with names,
98+
// force that copy into the cache even if we already have a cache entry without names
99+
// so we don't have to recompute the names if asked again.
100+
return getNames
101+
? enumType.ReplaceCacheEntry(EnumInfo<TStorage>.Create(enumType, getNames: true))
102+
: enumType.GetOrCreateCacheEntry<EnumInfo<TStorage>>();
103+
}
104+
}
105+
106+
internal sealed partial class EnumInfo<TStorage> : RuntimeType.IGenericCacheEntry<EnumInfo<TStorage>>
107+
{
108+
public static EnumInfo<TStorage> Create(RuntimeType type, bool getNames)
96109
{
97110
TStorage[]? values = null;
98111
string[]? names = null;
99112

100113
GetEnumValuesAndNames(
101-
new QCallTypeHandle(ref enumType),
114+
new QCallTypeHandle(ref type),
102115
ObjectHandleOnStack.Create(ref values),
103116
ObjectHandleOnStack.Create(ref names),
104117
getNames ? Interop.BOOL.TRUE : Interop.BOOL.FALSE);
105118

106119
Debug.Assert(values!.GetType() == typeof(TStorage[]));
107-
Debug.Assert(!getNames || names!.GetType() == typeof(string[]));
108120

109-
bool hasFlagsAttribute = enumType.IsDefined(typeof(FlagsAttribute), inherit: false);
121+
bool hasFlagsAttribute = type.IsDefined(typeof(FlagsAttribute), inherit: false);
110122

111-
var entry = new EnumInfo<TStorage>(hasFlagsAttribute, values, names!);
112-
enumType.GenericCache = entry;
113-
return entry;
123+
return new EnumInfo<TStorage>(hasFlagsAttribute, values, names!);
114124
}
125+
126+
public static EnumInfo<TStorage> Create(RuntimeType type) => Create(type, getNames: false);
127+
128+
public void InitializeCompositeCache(RuntimeType.CompositeCacheEntry compositeEntry) => compositeEntry._enumInfo = this;
129+
130+
// This type is the only type that will be stored in the _enumInfo field, so we can use Unsafe.As here.
131+
public static ref EnumInfo<TStorage>? GetStorageRef(RuntimeType.CompositeCacheEntry compositeEntry)
132+
=> ref Unsafe.As<RuntimeType.IGenericCacheEntry?, EnumInfo<TStorage>?>(ref compositeEntry._enumInfo);
115133
}
116134
}
117135
}

src/coreclr/System.Private.CoreLib/src/System/RuntimeType.ActivatorCache.cs

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ internal sealed partial class RuntimeType
1313
/// A cache which allows optimizing <see cref="Activator.CreateInstance"/>,
1414
/// <see cref="CreateInstanceDefaultCtor"/>, and related APIs.
1515
/// </summary>
16-
private sealed unsafe class ActivatorCache
16+
internal sealed unsafe class ActivatorCache : IGenericCacheEntry<ActivatorCache>
1717
{
1818
// The managed calli to the newobj allocator, plus its first argument (MethodTable*).
1919
// In the case of the COM allocator, first arg is ComClassFactory*, not MethodTable*.
@@ -24,13 +24,15 @@ private sealed unsafe class ActivatorCache
2424
private readonly delegate*<object?, void> _pfnCtor;
2525
private readonly bool _ctorIsPublic;
2626

27-
private CreateUninitializedCache? _createUninitializedCache;
28-
2927
#if DEBUG
3028
private readonly RuntimeType _originalRuntimeType;
3129
#endif
3230

33-
internal ActivatorCache(RuntimeType rt)
31+
public static ActivatorCache Create(RuntimeType type) => new(type);
32+
public void InitializeCompositeCache(RuntimeType.CompositeCacheEntry compositeEntry) => compositeEntry._activatorCache = this;
33+
public static ref ActivatorCache? GetStorageRef(RuntimeType.CompositeCacheEntry compositeEntry) => ref compositeEntry._activatorCache;
34+
35+
private ActivatorCache(RuntimeType rt)
3436
{
3537
Debug.Assert(rt != null);
3638

@@ -122,15 +124,6 @@ static void CtorNoopStub(object? uninitializedObject) { }
122124
[MethodImpl(MethodImplOptions.AggressiveInlining)]
123125
internal void CallConstructor(object? uninitializedObject) => _pfnCtor(uninitializedObject);
124126

125-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
126-
internal CreateUninitializedCache GetCreateUninitializedCache(RuntimeType rt)
127-
{
128-
#if DEBUG
129-
CheckOriginalRuntimeType(rt);
130-
#endif
131-
return _createUninitializedCache ??= new CreateUninitializedCache(rt);
132-
}
133-
134127
#if DEBUG
135128
private void CheckOriginalRuntimeType(RuntimeType rt)
136129
{

src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs

Lines changed: 41 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1454,11 +1454,8 @@ internal T[] GetMemberList(MemberListType listType, string? name, CacheType cach
14541454
private static CerHashtable<RuntimeMethodInfo, RuntimeMethodInfo> s_methodInstantiations;
14551455
private static object? s_methodInstantiationsLock;
14561456
private string? m_defaultMemberName;
1457-
// Generic cache for rare scenario specific data. Used for:
1458-
// - Enum names and values (EnumInfo)
1459-
// - Activator.CreateInstance (ActivatorCache)
1460-
// - Array.Initialize (ArrayInitializeCache)
1461-
private object? m_genericCache;
1457+
// Generic cache for rare scenario specific data.
1458+
private IGenericCacheEntry? m_genericCache;
14621459
private object[]? _emptyArray; // Object array cache for Attribute.GetCustomAttributes() pathological no-result case.
14631460
private RuntimeType? _genericTypeDefinition;
14641461
#endregion
@@ -1501,30 +1498,31 @@ private MemberInfoCache<T> GetMemberCache<T>(ref MemberInfoCache<T>? m_cache)
15011498

15021499
#region Internal Members
15031500

1501+
internal ref IGenericCacheEntry? GenericCache => ref m_genericCache;
15041502

1505-
/// <summary>
1506-
/// Generic cache for rare scenario specific data. It is used to cache either Enum names, Enum values,
1507-
/// the Activator cache or function pointer parameters.
1508-
/// </summary>
1509-
internal object? GenericCache
1503+
internal sealed class FunctionPointerCache : IGenericCacheEntry<FunctionPointerCache>
15101504
{
1511-
get => m_genericCache;
1512-
set => m_genericCache = value;
1505+
public Type[] FunctionPointerReturnAndParameterTypes { get; }
1506+
1507+
private FunctionPointerCache(Type[] functionPointerReturnAndParameterTypes)
1508+
{
1509+
FunctionPointerReturnAndParameterTypes = functionPointerReturnAndParameterTypes;
1510+
}
1511+
1512+
public static FunctionPointerCache Create(RuntimeType type)
1513+
{
1514+
Debug.Assert(type.IsFunctionPointer);
1515+
return new(RuntimeTypeHandle.GetArgumentTypesFromFunctionPointer(type));
1516+
}
1517+
public void InitializeCompositeCache(RuntimeType.CompositeCacheEntry compositeEntry) => compositeEntry._functionPointerCache = this;
1518+
public static ref FunctionPointerCache? GetStorageRef(RuntimeType.CompositeCacheEntry compositeEntry) => ref compositeEntry._functionPointerCache;
15131519
}
15141520

15151521
internal Type[] FunctionPointerReturnAndParameterTypes
15161522
{
15171523
get
15181524
{
1519-
Debug.Assert(m_runtimeType.IsFunctionPointer);
1520-
Type[]? value = (Type[]?)GenericCache;
1521-
if (value == null)
1522-
{
1523-
GenericCache = value = RuntimeTypeHandle.GetArgumentTypesFromFunctionPointer(m_runtimeType);
1524-
Debug.Assert(value.Length > 0);
1525-
}
1526-
1527-
return value;
1525+
return m_runtimeType.GetOrCreateCacheEntry<FunctionPointerCache>().FunctionPointerReturnAndParameterTypes;
15281526
}
15291527
}
15301528

@@ -1930,10 +1928,23 @@ internal FieldInfo GetField(RuntimeFieldHandleInternal field)
19301928
return retval;
19311929
}
19321930

1933-
internal object? GenericCache
1931+
internal T GetOrCreateCacheEntry<T>()
1932+
where T : class, IGenericCacheEntry<T>
1933+
{
1934+
return IGenericCacheEntry<T>.GetOrCreate(this);
1935+
}
1936+
1937+
internal T? FindCacheEntry<T>()
1938+
where T : class, IGenericCacheEntry<T>
1939+
{
1940+
return IGenericCacheEntry<T>.Find(this);
1941+
}
1942+
1943+
internal T ReplaceCacheEntry<T>(T entry)
1944+
where T : class, IGenericCacheEntry<T>
19341945
{
1935-
get => CacheIfExists?.GenericCache;
1936-
set => Cache.GenericCache = value;
1946+
IGenericCacheEntry<T>.Replace(this, entry);
1947+
return entry;
19371948
}
19381949

19391950
internal static FieldInfo GetFieldInfo(IRuntimeFieldInfo fieldHandle)
@@ -2376,7 +2387,7 @@ private static bool FilterApplyMethodBase(
23762387

23772388
#endregion
23782389

2379-
#endregion
2390+
#endregion
23802391

23812392
#region Private Data Members
23822393

@@ -3886,22 +3897,7 @@ private void CreateInstanceCheckThis()
38863897
[DebuggerHidden]
38873898
internal object GetUninitializedObject()
38883899
{
3889-
object? genericCache = GenericCache;
3890-
3891-
if (genericCache is not CreateUninitializedCache cache)
3892-
{
3893-
if (genericCache is ActivatorCache activatorCache)
3894-
{
3895-
cache = activatorCache.GetCreateUninitializedCache(this);
3896-
}
3897-
else
3898-
{
3899-
cache = new CreateUninitializedCache(this);
3900-
GenericCache = cache;
3901-
}
3902-
}
3903-
3904-
return cache.CreateUninitializedObject(this);
3900+
return GetOrCreateCacheEntry<CreateUninitializedCache>().CreateUninitializedObject(this);
39053901
}
39063902

39073903
/// <summary>
@@ -3914,11 +3910,7 @@ internal object GetUninitializedObject()
39143910
// Get or create the cached factory. Creating the cache will fail if one
39153911
// of our invariant checks fails; e.g., no appropriate ctor found.
39163912

3917-
if (GenericCache is not ActivatorCache cache)
3918-
{
3919-
cache = new ActivatorCache(this);
3920-
GenericCache = cache;
3921-
}
3913+
ActivatorCache cache = GetOrCreateCacheEntry<ActivatorCache>();
39223914

39233915
if (!cache.CtorIsPublic && publicOnly)
39243916
{
@@ -3947,18 +3939,15 @@ internal object GetUninitializedObject()
39473939
[DebuggerHidden]
39483940
internal object? CreateInstanceOfT()
39493941
{
3950-
if (GenericCache is not ActivatorCache cache)
3951-
{
3952-
cache = new ActivatorCache(this);
3953-
GenericCache = cache;
3954-
}
3942+
ActivatorCache cache = GetOrCreateCacheEntry<ActivatorCache>();
39553943

39563944
if (!cache.CtorIsPublic)
39573945
{
39583946
throw new MissingMethodException(SR.Format(SR.Arg_NoDefCTor, this));
39593947
}
39603948

3961-
object? obj = cache.CreateUninitializedObject(this);
3949+
// We reuse ActivatorCache here to ensure that we aren't always creating two entries in the cache.
3950+
object? obj = GetOrCreateCacheEntry<ActivatorCache>().CreateUninitializedObject(this);
39623951
try
39633952
{
39643953
cache.CallConstructor(obj);

src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CreateUninitializedCache.CoreCLR.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,12 @@ internal sealed partial class RuntimeType
1212
/// <summary>
1313
/// A cache which allows optimizing <see cref="RuntimeHelpers.GetUninitializedObject(Type)"/>.
1414
/// </summary>
15-
private sealed unsafe partial class CreateUninitializedCache
15+
internal sealed unsafe partial class CreateUninitializedCache : IGenericCacheEntry<CreateUninitializedCache>
1616
{
17+
public static CreateUninitializedCache Create(RuntimeType type) => new(type);
18+
public void InitializeCompositeCache(RuntimeType.CompositeCacheEntry compositeEntry) => compositeEntry._createUninitializedCache = this;
19+
public static ref CreateUninitializedCache? GetStorageRef(RuntimeType.CompositeCacheEntry compositeEntry) => ref compositeEntry._createUninitializedCache;
20+
1721
// The managed calli to the newobj allocator, plus its first argument (MethodTable*).
1822
private readonly delegate*<void*, object> _pfnAllocator;
1923
private readonly void* _allocatorFirstArg;
@@ -22,7 +26,7 @@ private sealed unsafe partial class CreateUninitializedCache
2226
private readonly RuntimeType _originalRuntimeType;
2327
#endif
2428

25-
internal CreateUninitializedCache(RuntimeType rt)
29+
private CreateUninitializedCache(RuntimeType rt)
2630
{
2731
Debug.Assert(rt != null);
2832

0 commit comments

Comments
 (0)