Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable RuntimeType.GenericCache to hold multiple types of cache entries #102034

Merged
merged 17 commits into from
May 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,8 @@
<Compile Include="$(CommonPath)System\Collections\Generic\ArrayBuilder.cs">
<Link>Common\System\Collections\Generic\ArrayBuilder.cs</Link>
</Compile>
<Compile Include="src\System\RuntimeType.CreateUninitializedCache.CoreCLR.cs" />
<Compile Include="src\System\RuntimeType.CreateUninitializedCache.CoreCLR.cs" />
<Compile Include="src\System\RuntimeType.GenericCache.cs" />
</ItemGroup>
<ItemGroup Condition="'$(FeatureComWrappers)' == 'true'">
<Compile Include="$(BclSourcesRoot)\System\Runtime\InteropServices\ComWrappers.cs" />
Expand Down
16 changes: 8 additions & 8 deletions src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ArrayInitializeCache>();

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

private sealed unsafe partial class ArrayInitializeCache
internal sealed unsafe partial class ArrayInitializeCache : RuntimeType.IGenericCacheEntry<ArrayInitializeCache>
{
internal readonly delegate*<ref byte, void> ConstructorEntrypoint;

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

public ArrayInitializeCache(RuntimeType arrayType)
private ArrayInitializeCache(delegate*<ref byte, void> 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;
}
}

Expand Down
32 changes: 25 additions & 7 deletions src/coreclr/System.Private.CoreLib/src/System/Enum.CoreCLR.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,31 +87,49 @@ private static EnumInfo<TStorage> GetEnumInfo<TStorage>(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<TStorage> info && (!getNames || info.Names is not null) ?
return enumType.FindCacheEntry<EnumInfo<TStorage>>() is {} info && (!getNames || info.Names is not null) ?
info :
InitializeEnumInfo(enumType, getNames);

[MethodImpl(MethodImplOptions.NoInlining)]
static EnumInfo<TStorage> 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<TStorage>.Create(enumType, getNames: true))
: enumType.GetOrCreateCacheEntry<EnumInfo<TStorage>>();
}
}

internal sealed partial class EnumInfo<TStorage> : RuntimeType.IGenericCacheEntry<EnumInfo<TStorage>>
{
public static EnumInfo<TStorage> 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<TStorage>(hasFlagsAttribute, values, names!);
enumType.GenericCache = entry;
return entry;
return new EnumInfo<TStorage>(hasFlagsAttribute, values, names!);
}

public static EnumInfo<TStorage> 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<TStorage>? GetStorageRef(RuntimeType.CompositeCacheEntry compositeEntry)
=> ref Unsafe.As<RuntimeType.IGenericCacheEntry?, EnumInfo<TStorage>?>(ref compositeEntry._enumInfo);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ internal sealed partial class RuntimeType
/// A cache which allows optimizing <see cref="Activator.CreateInstance"/>,
/// <see cref="CreateInstanceDefaultCtor"/>, and related APIs.
/// </summary>
private sealed unsafe class ActivatorCache
internal sealed unsafe class ActivatorCache : IGenericCacheEntry<ActivatorCache>
{
// 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*.
Expand All @@ -24,13 +24,15 @@ private sealed unsafe class ActivatorCache
private readonly delegate*<object?, void> _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);

Expand Down Expand Up @@ -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)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1454,11 +1454,8 @@ internal T[] GetMemberList(MemberListType listType, string? name, CacheType cach
private static CerHashtable<RuntimeMethodInfo, RuntimeMethodInfo> 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
Expand Down Expand Up @@ -1501,30 +1498,31 @@ private MemberInfoCache<T> GetMemberCache<T>(ref MemberInfoCache<T>? m_cache)

#region Internal Members

internal ref IGenericCacheEntry? GenericCache => ref m_genericCache;

/// <summary>
/// Generic cache for rare scenario specific data. It is used to cache either Enum names, Enum values,
/// the Activator cache or function pointer parameters.
/// </summary>
internal object? GenericCache
internal sealed class FunctionPointerCache : IGenericCacheEntry<FunctionPointerCache>
{
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<FunctionPointerCache>().FunctionPointerReturnAndParameterTypes;
}
}

Expand Down Expand Up @@ -1930,10 +1928,23 @@ internal FieldInfo GetField(RuntimeFieldHandleInternal field)
return retval;
}

internal object? GenericCache
internal T GetOrCreateCacheEntry<T>()
where T : class, IGenericCacheEntry<T>
{
return IGenericCacheEntry<T>.GetOrCreate(this);
}

internal T? FindCacheEntry<T>()
where T : class, IGenericCacheEntry<T>
{
return IGenericCacheEntry<T>.Find(this);
}

internal T ReplaceCacheEntry<T>(T entry)
where T : class, IGenericCacheEntry<T>
{
get => CacheIfExists?.GenericCache;
set => Cache.GenericCache = value;
IGenericCacheEntry<T>.Replace(this, entry);
return entry;
}

internal static FieldInfo GetFieldInfo(IRuntimeFieldInfo fieldHandle)
Expand Down Expand Up @@ -2376,7 +2387,7 @@ private static bool FilterApplyMethodBase(

#endregion

#endregion
#endregion

#region Private Data Members

Expand Down Expand Up @@ -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<CreateUninitializedCache>().CreateUninitializedObject(this);
}

/// <summary>
Expand All @@ -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<ActivatorCache>();

if (!cache.CtorIsPublic && publicOnly)
{
Expand Down Expand Up @@ -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<ActivatorCache>();

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<ActivatorCache>().CreateUninitializedObject(this);
try
{
cache.CallConstructor(obj);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,12 @@ internal sealed partial class RuntimeType
/// <summary>
/// A cache which allows optimizing <see cref="RuntimeHelpers.GetUninitializedObject(Type)"/>.
/// </summary>
private sealed unsafe partial class CreateUninitializedCache
internal sealed unsafe partial class CreateUninitializedCache : IGenericCacheEntry<CreateUninitializedCache>
{
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*<void*, object> _pfnAllocator;
private readonly void* _allocatorFirstArg;
Expand All @@ -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);

Expand Down
Loading
Loading