Speed up typeof (#85962)
authorMichal Strehovský <MichalStrehovsky@users.noreply.github.com>
Tue, 9 May 2023 19:25:08 +0000 (04:25 +0900)
committerGitHub <noreply@github.com>
Tue, 9 May 2023 19:25:08 +0000 (12:25 -0700)
Saw it in BasicMinimalApi profiles. The ultimate fix is to make `RuntimeType` a frozen object, but this will do for now.

Also made a bit of progress in eradicating `EETypePtr` while I'm touching this.

13 files changed:
src/coreclr/nativeaot/Common/src/Internal/Runtime/MethodTable.cs
src/coreclr/nativeaot/Runtime/inc/MethodTable.h
src/coreclr/nativeaot/Runtime/inc/MethodTable.inl
src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/CompilerHelpers/LdTokenHelpers.cs
src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/CompilerHelpers/SynchronizedMethodHelpers.cs
src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/IDynamicInterfaceCastableSupport.cs
src/coreclr/nativeaot/System.Private.CoreLib/src/System/Object.NativeAot.cs
src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/DynamicInvokeInfo.cs
src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.NativeAot.cs
src/coreclr/nativeaot/System.Private.CoreLib/src/System/Type.NativeAot.cs
src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/EETypeCreator.cs
src/coreclr/tools/Common/Internal/Runtime/MethodTable.Constants.cs
src/libraries/System.Private.CoreLib/src/System/Nullable.cs

index 4debbad..13b0100 100644 (file)
@@ -1216,7 +1216,7 @@ namespace Internal.Runtime
         /// The purpose of the segment is controlled by the class library. The runtime doesn't
         /// use this memory for any purpose.
         /// </summary>
-        internal IntPtr WritableData
+        internal void* WritableData
         {
             get
             {
@@ -1225,9 +1225,9 @@ namespace Internal.Runtime
                 uint offset = GetFieldOffset(EETypeField.ETF_WritableData);
 
                 if (!IsDynamicType)
-                    return GetField<RelativePointer>(offset).Value;
+                    return (void*)GetField<RelativePointer>(offset).Value;
                 else
-                    return GetField<Pointer>(offset).Value;
+                    return (void*)GetField<Pointer>(offset).Value;
             }
 #if TYPE_LOADER_IMPLEMENTATION
             set
@@ -1235,7 +1235,7 @@ namespace Internal.Runtime
                 Debug.Assert(IsDynamicType && SupportsWritableData);
 
                 uint cbOffset = GetFieldOffset(EETypeField.ETF_WritableData);
-                *(IntPtr*)((byte*)Unsafe.AsPointer(ref this) + cbOffset) = value;
+                *(void**)((byte*)Unsafe.AsPointer(ref this) + cbOffset) = value;
             }
 #endif
         }
@@ -1296,17 +1296,14 @@ namespace Internal.Runtime
             }
         }
 
+        // This method is always called with a known constant and there's a lot of benefit in inlining it.
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         public uint GetFieldOffset(EETypeField eField)
         {
             // First part of MethodTable consists of the fixed portion followed by the vtable.
             uint cbOffset = (uint)(sizeof(MethodTable) + (IntPtr.Size * _usNumVtableSlots));
 
-            // Then we have the interface map.
-            if (eField == EETypeField.ETF_InterfaceMap)
-            {
-                Debug.Assert(NumInterfaces > 0);
-                return cbOffset;
-            }
+            // Followed by list of implemented interfaces
             cbOffset += (uint)(sizeof(MethodTable*) * NumInterfaces);
 
             uint relativeOrFullPointerOffset = (IsDynamicType || !SupportsRelativePointers ? (uint)IntPtr.Size : 4);
index 311fdef..e77f656 100644 (file)
@@ -65,7 +65,6 @@ enum EETypeElementType : uint8_t
 // GetFieldOffset() APIs.
 enum EETypeField
 {
-    ETF_InterfaceMap,
     ETF_TypeManagerIndirection,
     ETF_WritableData,
     ETF_Finalizer,
index 56f922f..a3ab924 100644 (file)
@@ -106,12 +106,7 @@ __forceinline uint32_t MethodTable::GetFieldOffset(EETypeField eField)
     // First part of MethodTable consists of the fixed portion followed by the vtable.
     uint32_t cbOffset = offsetof(MethodTable, m_VTable) + (sizeof(UIntTarget) * m_usNumVtableSlots);
 
-    // Then we have the interface map.
-    if (eField == ETF_InterfaceMap)
-    {
-        ASSERT(GetNumInterfaces() > 0);
-        return cbOffset;
-    }
+    // Followed by interface list
     cbOffset += sizeof(MethodTable*) * GetNumInterfaces();
 
     const uint32_t relativeOrFullPointerOffset =
index aba24c0..81b96f8 100644 (file)
@@ -31,9 +31,9 @@ namespace Internal.Runtime.CompilerHelpers
             return returnValue;
         }
 
-        private static Type GetRuntimeType(IntPtr pEEType)
+        private static unsafe Type GetRuntimeType(MethodTable* pMT)
         {
-            return Type.GetTypeFromEETypePtr(new EETypePtr(pEEType));
+            return Type.GetTypeFromMethodTable(pMT);
         }
     }
 }
index 030ba70..b179248 100644 (file)
@@ -44,10 +44,10 @@ namespace Internal.Runtime.CompilerHelpers
             lockTaken = false;
         }
 
-        private static void MonitorEnterStatic(IntPtr pEEType, ref bool lockTaken)
+        private static unsafe void MonitorEnterStatic(MethodTable* pMT, ref bool lockTaken)
         {
             // Inlined Monitor.Enter with a few tweaks
-            object obj = GetStaticLockObject(pEEType);
+            object obj = GetStaticLockObject(pMT);
             int resultOrIndex = ObjectHeader.Acquire(obj);
             if (resultOrIndex < 0)
             {
@@ -68,20 +68,20 @@ namespace Internal.Runtime.CompilerHelpers
             Monitor.TryAcquireContended(lck, obj, Timeout.Infinite);
             lockTaken = true;
         }
-        private static void MonitorExitStatic(IntPtr pEEType, ref bool lockTaken)
+        private static unsafe void MonitorExitStatic(MethodTable* pMT, ref bool lockTaken)
         {
             // Inlined Monitor.Exit with a few tweaks
             if (!lockTaken)
                 return;
 
-            object obj = GetStaticLockObject(pEEType);
+            object obj = GetStaticLockObject(pMT);
             ObjectHeader.Release(obj);
             lockTaken = false;
         }
 
-        private static Type GetStaticLockObject(IntPtr pEEType)
+        private static unsafe Type GetStaticLockObject(MethodTable* pMT)
         {
-            return Type.GetTypeFromEETypePtr(new EETypePtr(pEEType));
+            return Type.GetTypeFromMethodTable(pMT);
         }
     }
 }
index d149dba..38526fb 100644 (file)
@@ -19,16 +19,16 @@ namespace Internal.Runtime
         internal static IntPtr IDynamicCastableGetInterfaceImplementation(IDynamicInterfaceCastable instance, MethodTable* interfaceType, ushort slot)
         {
             RuntimeTypeHandle handle = instance.GetInterfaceImplementation(new RuntimeTypeHandle(new EETypePtr(interfaceType)));
-            EETypePtr implType = handle.ToEETypePtr();
-            if (implType.IsNull)
+            MethodTable* implType = handle.ToMethodTable();
+            if (implType == null)
             {
                 ThrowInvalidCastException(instance, interfaceType);
             }
-            if (!implType.IsInterface)
+            if (!implType->IsInterface)
             {
                 ThrowInvalidOperationException(implType);
             }
-            IntPtr result = RuntimeImports.RhResolveDispatchOnType(implType, new EETypePtr(interfaceType), slot);
+            IntPtr result = RuntimeImports.RhResolveDispatchOnType(new EETypePtr(implType), new EETypePtr(interfaceType), slot);
             if (result == IntPtr.Zero)
             {
                 IDynamicCastableGetInterfaceImplementationFailure(instance, interfaceType, implType);
@@ -38,24 +38,24 @@ namespace Internal.Runtime
 
         private static void ThrowInvalidCastException(object instance, MethodTable* interfaceType)
         {
-            throw new InvalidCastException(SR.Format(SR.InvalidCast_FromTo, instance.GetType(), Type.GetTypeFromEETypePtr(new EETypePtr(interfaceType))));
+            throw new InvalidCastException(SR.Format(SR.InvalidCast_FromTo, instance.GetType(), Type.GetTypeFromMethodTable(interfaceType)));
         }
 
-        private static void ThrowInvalidOperationException(EETypePtr resolvedImplType)
+        private static void ThrowInvalidOperationException(MethodTable* resolvedImplType)
         {
-            throw new InvalidOperationException(SR.Format(SR.IDynamicInterfaceCastable_NotInterface, Type.GetTypeFromEETypePtr(resolvedImplType)));
+            throw new InvalidOperationException(SR.Format(SR.IDynamicInterfaceCastable_NotInterface, Type.GetTypeFromMethodTable(resolvedImplType)));
         }
 
-        private static void IDynamicCastableGetInterfaceImplementationFailure(object instance, MethodTable* interfaceType, EETypePtr resolvedImplType)
+        private static void IDynamicCastableGetInterfaceImplementationFailure(object instance, MethodTable* interfaceType, MethodTable* resolvedImplType)
         {
-            if (resolvedImplType.DispatchMap == IntPtr.Zero)
-                throw new InvalidOperationException(SR.Format(SR.IDynamicInterfaceCastable_MissingImplementationAttribute, Type.GetTypeFromEETypePtr(resolvedImplType), nameof(DynamicInterfaceCastableImplementationAttribute)));
+            if (resolvedImplType->DispatchMap == null)
+                throw new InvalidOperationException(SR.Format(SR.IDynamicInterfaceCastable_MissingImplementationAttribute, Type.GetTypeFromMethodTable(resolvedImplType), nameof(DynamicInterfaceCastableImplementationAttribute)));
 
             bool implementsInterface = false;
-            var interfaces = resolvedImplType.Interfaces;
-            for (int i = 0; i < interfaces.Count; i++)
+            var interfaces = resolvedImplType->InterfaceMap;
+            for (int i = 0; i < resolvedImplType->NumInterfaces; i++)
             {
-                if (interfaces[i] == new EETypePtr(interfaceType))
+                if (interfaces[i] == interfaceType)
                 {
                     implementsInterface = true;
                     break;
@@ -63,7 +63,7 @@ namespace Internal.Runtime
             }
 
             if (!implementsInterface)
-                throw new InvalidOperationException(SR.Format(SR.IDynamicInterfaceCastable_DoesNotImplementRequested, Type.GetTypeFromEETypePtr(resolvedImplType), Type.GetTypeFromEETypePtr(new EETypePtr(interfaceType))));
+                throw new InvalidOperationException(SR.Format(SR.IDynamicInterfaceCastable_DoesNotImplementRequested, Type.GetTypeFromMethodTable(resolvedImplType), Type.GetTypeFromMethodTable(interfaceType)));
 
             throw new EntryPointNotFoundException();
         }
index caf1773..1d68cad 100644 (file)
@@ -22,7 +22,7 @@ namespace System
         [Intrinsic]
         public Type GetType()
         {
-            return Type.GetTypeFromEETypePtr(this.GetEETypePtr());
+            return Type.GetTypeFromMethodTable(m_pEEType);
         }
 
         [Intrinsic]
index b1f7b76..698b7a7 100644 (file)
@@ -324,7 +324,7 @@ namespace System.Reflection
             return ref ret;
         }
 
-        private object? GetCoercedDefaultValue(int index, in ArgumentInfo argumentInfo)
+        private unsafe object? GetCoercedDefaultValue(int index, in ArgumentInfo argumentInfo)
         {
             object? defaultValue = Method.GetParametersNoCopy()[index].DefaultValue;
             if (defaultValue == DBNull.Value)
@@ -337,7 +337,7 @@ namespace System.Reflection
                 EETypePtr nullableType = argumentInfo.Type.NullableType;
                 if (nullableType.IsEnum)
                 {
-                    defaultValue = Enum.ToObject(Type.GetTypeFromEETypePtr(nullableType), defaultValue);
+                    defaultValue = Enum.ToObject(Type.GetTypeFromMethodTable(nullableType.ToPointer()), defaultValue);
                 }
             }
 
@@ -444,7 +444,7 @@ namespace System.Reflection
                 {
                     if ((transform & Transform.Pointer) != 0)
                     {
-                        Type type = Type.GetTypeFromEETypePtr(argumentInfo.Type);
+                        Type type = Type.GetTypeFromMethodTable(argumentInfo.Type.ToPointer());
                         Debug.Assert(type.IsPointer);
                         obj = Pointer.Box((void*)Unsafe.As<byte, IntPtr>(ref obj.GetRawData()), type);
                     }
@@ -476,7 +476,7 @@ namespace System.Reflection
             object obj;
             if ((_returnTransform & Transform.Pointer) != 0)
             {
-                Type type = Type.GetTypeFromEETypePtr(_returnType);
+                Type type = Type.GetTypeFromMethodTable(_returnType.ToPointer());
                 Debug.Assert(type.IsPointer);
                 obj = Pointer.Box((void*)Unsafe.As<byte, IntPtr>(ref byref), type);
             }
index 551a067..fde9740 100644 (file)
@@ -360,7 +360,7 @@ namespace System.Runtime.CompilerServices
             if (mt->IsNullable)
             {
                 mt = mt->NullableType;
-                return GetUninitializedObject(Type.GetTypeFromEETypePtr(new EETypePtr(mt)));
+                return GetUninitializedObject(Type.GetTypeFromMethodTable(mt));
             }
 
             // Triggering the .cctor here is slightly different than desktop/CoreCLR, which
index d7475aa..9e175f2 100644 (file)
@@ -10,6 +10,7 @@ using System.Threading;
 
 using Internal.Reflection.Augments;
 using Internal.Reflection.Core.NonPortable;
+using Internal.Runtime;
 using Internal.Runtime.Augments;
 using Internal.Runtime.CompilerServices;
 
@@ -20,38 +21,39 @@ namespace System
         public bool IsInterface => (GetAttributeFlagsImpl() & TypeAttributes.ClassSemanticsMask) == TypeAttributes.Interface;
 
         [Intrinsic]
-        public static Type? GetTypeFromHandle(RuntimeTypeHandle handle) => handle.IsNull ? null : GetTypeFromEETypePtr(handle.ToEETypePtr());
+        public static unsafe Type? GetTypeFromHandle(RuntimeTypeHandle handle) => handle.IsNull ? null : GetTypeFromMethodTable(handle.ToMethodTable());
 
-        internal static Type GetTypeFromEETypePtr(EETypePtr eeType)
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal static unsafe Type GetTypeFromMethodTable(MethodTable* pMT)
         {
-            // If we support the writable data section on EETypes, the runtime type associated with the MethodTable
+            // If we support the writable data section on MethodTables, the runtime type associated with the MethodTable
             // is cached there. If writable data is not supported, we need to do a lookup in the runtime type
             // unifier's hash table.
-            if (Internal.Runtime.MethodTable.SupportsWritableData)
+            if (MethodTable.SupportsWritableData)
             {
-                ref GCHandle handle = ref eeType.GetWritableData<GCHandle>();
+                ref GCHandle handle = ref Unsafe.AsRef<GCHandle>(pMT->WritableData);
                 if (handle.IsAllocated)
                 {
                     return Unsafe.As<Type>(handle.Target);
                 }
                 else
                 {
-                    return GetTypeFromEETypePtrSlow(eeType, ref handle);
+                    return GetTypeFromMethodTableSlow(pMT, ref handle);
                 }
             }
             else
             {
-                return RuntimeTypeUnifier.GetRuntimeTypeForEEType(eeType);
+                return RuntimeTypeUnifier.GetRuntimeTypeForEEType(new EETypePtr(pMT));
             }
         }
 
         [MethodImpl(MethodImplOptions.NoInlining)]
-        private static Type GetTypeFromEETypePtrSlow(EETypePtr eeType, ref GCHandle handle)
+        private static unsafe Type GetTypeFromMethodTableSlow(MethodTable* pMT, ref GCHandle handle)
         {
             // Note: this is bypassing the "fast" unifier cache (based on a simple IntPtr
             // identity of MethodTable pointers). There is another unifier behind that cache
             // that ensures this code is race-free.
-            Type result = RuntimeTypeUnifier.GetRuntimeTypeBypassCache(eeType);
+            Type result = RuntimeTypeUnifier.GetRuntimeTypeBypassCache(new EETypePtr(pMT));
             GCHandle tempHandle = GCHandle.Alloc(result);
 
             // We don't want to leak a handle if there's a race
index fe136fa..85db576 100644 (file)
@@ -324,7 +324,7 @@ namespace Internal.Runtime.TypeLoader
                 {
                     writableDataPtr = MemoryHelpers.AllocateMemory(WritableData.GetSize(IntPtr.Size));
                     MemoryHelpers.Memset(writableDataPtr, WritableData.GetSize(IntPtr.Size), 0);
-                    pEEType->WritableData = writableDataPtr;
+                    pEEType->WritableData = (void*)writableDataPtr;
                 }
 
                 pEEType->DynamicTemplateType = pTemplateEEType;
index 98b77cc..866c142 100644 (file)
@@ -177,7 +177,6 @@ namespace Internal.Runtime
 
     internal enum EETypeField
     {
-        ETF_InterfaceMap,
         ETF_TypeManagerIndirection,
         ETF_WritableData,
         ETF_DispatchMap,
index 3d13a77..a7d6954 100644 (file)
@@ -103,21 +103,6 @@ namespace System
         {
             ArgumentNullException.ThrowIfNull(nullableType);
 
-#if NATIVEAOT
-            // This is necessary to handle types without reflection metadata
-            if (nullableType.TryGetEEType(out EETypePtr nullableEEType))
-            {
-                if (nullableEEType.IsGeneric)
-                {
-                    if (nullableEEType.IsNullable)
-                    {
-                        return Type.GetTypeFromEETypePtr(nullableEEType.NullableType);
-                    }
-                }
-                return null;
-            }
-#endif
-
             if (nullableType.IsGenericType && !nullableType.IsGenericTypeDefinition)
             {
                 // Instantiated generic type only