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

[mono] Add a managed cache for flags in RuntimeType. #78840

Merged
merged 1 commit into from
Dec 9, 2022
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
134 changes: 122 additions & 12 deletions src/mono/System.Private.CoreLib/src/System/RuntimeType.Mono.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1257,31 +1257,103 @@ public override bool IsEquivalentTo(Type? other)

#region Attributes

internal CorElementType GetCorElementType()
{
TypeCache cache = Cache;
if ((cache.Cached & (int)TypeCacheEntries.CorElementType) != 0)
return cache.CorElementType;

var type = this;
cache.CorElementType = RuntimeTypeHandle.GetCorElementType (new QCallTypeHandle(ref type));
Interlocked.MemoryBarrier ();
UpdateCached(TypeCacheEntries.CorElementType);
return cache.CorElementType;
}

internal TypeAttributes GetAttributes()
{
TypeCache cache = Cache;
if ((cache.Cached & (int)TypeCacheEntries.TypeAttributes) != 0)
return cache.TypeAttributes;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this going to work with hot-reload?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If hot reload can change the information that is cached here, then we need a way to either disable this, or a way to invalidate the cache. There is already a cache with some entries which could be affected by this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's an issue #50978 to add a MetadataUpdateHandler for mono to clear reflection caches. (we also have caching in native that we need to clear, and that's not done yet, too). /cc @fanyang-mono


var type = this;
cache.TypeAttributes = RuntimeTypeHandle.GetAttributes(new QCallTypeHandle(ref type));
Interlocked.MemoryBarrier ();
UpdateCached(TypeCacheEntries.TypeAttributes);
return cache.TypeAttributes;
}

internal bool IsDelegate()
{
return GetBaseType() == typeof(System.MulticastDelegate);
TypeCache cache = Cache;
if ((cache.Cached & (int)TypeCacheEntries.IsDelegate) != 0)
return (cache.Flags & (int)TypeCacheEntries.IsDelegate) != 0;

bool res = GetBaseType() == typeof(System.MulticastDelegate);
CacheFlag(TypeCacheEntries.IsDelegate, res);
return res;
}

protected override bool IsValueTypeImpl()
{
TypeCache cache = Cache;
if ((cache.Cached & (int)TypeCacheEntries.IsValueType) != 0)
return (cache.Flags & (int)TypeCacheEntries.IsValueType) != 0;

// We need to return true for generic parameters with the ValueType constraint.
// So we cannot use the faster RuntimeTypeHandle.IsValueType because it returns
// false for all generic parameters.
if (this == typeof(ValueType) || this == typeof(Enum))
return false;

return IsSubclassOf(typeof(ValueType));
bool res = false;
if (!(this == typeof(ValueType) || this == typeof(Enum)))
res = IsSubclassOf(typeof(ValueType));
CacheFlag(TypeCacheEntries.IsValueType, res);
return res;
}

// Returns true for generic parameters with the Enum constraint.
public override bool IsEnum => GetBaseType() == EnumType;

// Returns true for actual enum types only.
internal bool IsActualEnum => !IsGenericParameter && RuntimeTypeHandle.GetBaseType(this) == EnumType;
internal bool IsActualEnum
{
get
{
TypeCache cache = Cache;
if ((cache.Cached & (int)TypeCacheEntries.IsActualEnum) != 0)
return (cache.Flags & (int)TypeCacheEntries.IsActualEnum) != 0;
bool res = !IsGenericParameter && RuntimeTypeHandle.GetBaseType(this) == EnumType;
CacheFlag(TypeCacheEntries.IsActualEnum, res);
return res;
}
}

public override bool IsConstructedGenericType => IsGenericType && !IsGenericTypeDefinition;
public override bool IsGenericType => RuntimeTypeHandle.HasInstantiation(this);
public override bool IsGenericTypeDefinition => RuntimeTypeHandle.IsGenericTypeDefinition(this);

public override bool IsGenericType
{
get
{
TypeCache cache = Cache;
if ((cache.Cached & (int)TypeCacheEntries.IsGenericType) != 0)
return (cache.Flags & (int)TypeCacheEntries.IsGenericType) != 0;
bool res = RuntimeTypeHandle.HasInstantiation(this);
CacheFlag(TypeCacheEntries.IsGenericType, res);
return res;
}
}

public override bool IsGenericTypeDefinition
{
get
{
TypeCache cache = Cache;
if ((cache.Cached & (int)TypeCacheEntries.IsGenericTypeDef) != 0)
return (cache.Flags & (int)TypeCacheEntries.IsGenericTypeDef) != 0;
bool res = RuntimeTypeHandle.IsGenericTypeDefinition(this);
CacheFlag(TypeCacheEntries.IsGenericTypeDef, res);
return res;
}
}

public override Type GetGenericTypeDefinition()
{
Expand Down Expand Up @@ -1549,6 +1621,19 @@ private void CreateInstanceCheckThis()
Interlocked.CompareExchange(ref cache, new TypeCache(), null) ??
cache;

[Flags]
// Types of entries cached in TypeCache
private enum TypeCacheEntries {
IsGenericTypeDef = 1,
IsDelegate = 2,
IsValueType = 4,
IsActualEnum = 8,
IsGenericType = 16,
CorElementType = 32,
TypeAttributes = 64,
DefaultCtor = 128
}

internal sealed class TypeCache
{
public object? EnumInfo;
Expand All @@ -1557,10 +1642,34 @@ internal sealed class TypeCache
// ,+*&*[]\ in the identifier portions of the names
// have been escaped with a leading backslash (\)
public string? full_name;
public bool default_ctor_cached;
public RuntimeConstructorInfo? default_ctor;
public CorElementType CorElementType;
public TypeAttributes TypeAttributes;
// TypeCacheEntries, accessed using CAS
public int Flags;
// TypeCacheEntries, accessed using CAS
public int Cached;
}

private void UpdateCached(TypeCacheEntries entry)
{
TypeCache cache = Cache;
int oldCached = cache.Cached;
int newCached = oldCached | (int)entry;
// This CAS will ensure ordering with the the store into the cache
// If this fails, we will just take the slowpath again
Interlocked.CompareExchange(ref cache.Cached, newCached, oldCached);
}

private void CacheFlag(TypeCacheEntries flag, bool value)
{
TypeCache cache = Cache;
int oldFlags = cache.Flags;
int newFlags = value ? (oldFlags | (int)flag) : oldFlags;
// If this fails, we will just take the slowpath again
if (Interlocked.CompareExchange(ref cache.Flags, newFlags, oldFlags) == oldFlags)
UpdateCached(flag);
}

internal RuntimeType(object obj)
{
Expand All @@ -1569,10 +1678,10 @@ internal RuntimeType(object obj)

internal RuntimeConstructorInfo? GetDefaultConstructor()
{
TypeCache? cache = Cache;
TypeCache cache = Cache;
RuntimeConstructorInfo? ctor = null;

if (Volatile.Read(ref cache.default_ctor_cached))
if ((cache.Cached & (int)TypeCacheEntries.DefaultCtor) != 0)
return cache.default_ctor;

ListBuilder<ConstructorInfo> ctors = GetConstructorCandidates(
Expand All @@ -1582,9 +1691,10 @@ internal RuntimeType(object obj)

if (ctors.Count == 1)
cache.default_ctor = ctor = (RuntimeConstructorInfo)ctors[0];
Interlocked.MemoryBarrier ();

// Note down even if we found no constructors
Volatile.Write(ref cache.default_ctor_cached, true);
UpdateCached(TypeCacheEntries.DefaultCtor);

return ctor;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ public override int GetHashCode()

internal static TypeAttributes GetAttributes(RuntimeType type)
{
return GetAttributes(new QCallTypeHandle(ref type));
return type.GetAttributes();
}

public ModuleHandle GetModuleHandle()
Expand Down Expand Up @@ -224,7 +224,7 @@ internal static bool HasReferences(RuntimeType type)

internal static CorElementType GetCorElementType(RuntimeType type)
{
return GetCorElementType (new QCallTypeHandle(ref type));
return type.GetCorElementType();
}

internal static bool HasInstantiation(RuntimeType type)
Expand Down