[mono] Add a managed cache for flags in RuntimeType. (#78840)
authorZoltan Varga <vargaz@gmail.com>
Fri, 9 Dec 2022 16:45:37 +0000 (11:45 -0500)
committerGitHub <noreply@github.com>
Fri, 9 Dec 2022 16:45:37 +0000 (11:45 -0500)
src/mono/System.Private.CoreLib/src/System/RuntimeType.Mono.cs
src/mono/System.Private.CoreLib/src/System/RuntimeTypeHandle.cs

index f8cadc9..d9c35cb 100644 (file)
@@ -1257,31 +1257,103 @@ namespace System
 
         #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;
+
+            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()
         {
@@ -1549,6 +1621,19 @@ namespace System
             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;
@@ -1557,10 +1642,34 @@ namespace System
             // ,+*&*[]\ 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)
         {
@@ -1569,10 +1678,10 @@ namespace System
 
         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(
@@ -1582,9 +1691,10 @@ namespace System
 
             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;
         }
index 4678097..62245b8 100644 (file)
@@ -119,7 +119,7 @@ namespace System
 
         internal static TypeAttributes GetAttributes(RuntimeType type)
         {
-            return GetAttributes(new QCallTypeHandle(ref type));
+            return type.GetAttributes();
         }
 
         public ModuleHandle GetModuleHandle()
@@ -223,7 +223,7 @@ namespace System
 
         internal static CorElementType GetCorElementType(RuntimeType type)
         {
-            return GetCorElementType (new QCallTypeHandle(ref type));
+            return type.GetCorElementType();
         }
 
         internal static bool HasInstantiation(RuntimeType type)