From ec03519fc878008b8dbcd92bc912ebb79f130958 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Mon, 20 Apr 2020 11:39:35 +0200 Subject: [PATCH] Speed up typeof(Foo) and Object.GetType by 50+% (#8095) This adds a general purpose concept of "writable data" to the EEType data structure. This points to a block of memory whose purpose is defined by the class library (we can potentially cache all sorts of stuff there). The writable data is used to hold a GC handle to the runtime `System.Type` instance associated with the EEType. This bypasses the hashtable lookup that has been used before. I'm seeing more than 50% TP improvement against best case scenarios with the hashtable (best case defined as "the thing we're looking for is the first entry in the hashtable bucket"). Both CoreCLR and Mono use a similar approach. CoreRT was lagging in perf because of the hashtable lookup: this brings us much closer. We're still not as fast as CoreCLR, but getting faster than this would have impacts elsewhere (in terms of working set and size on disk). The implementation I chose for CoreRT (optional relative pointer field) has minimal working set impact. --- dependencies.props | 2 +- .../src/Internal/Runtime/EEType.Constants.cs | 7 ++ src/Common/src/Internal/Runtime/EEType.cs | 60 +++++++++++++- .../Compiler/DependencyAnalysis/EETypeNode.cs | 18 +++++ .../GenericDefinitionEETypeNode.cs | 1 + .../DependencyAnalysis/NodeFactory.cs | 33 ++++++++ .../DependencyAnalysis/ObjectNodeSection.cs | 5 +- src/Native/Runtime/inc/eetype.h | 5 ++ src/Native/Runtime/inc/eetype.inl | 9 +++ .../shared/System/Nullable.cs | 2 +- .../Core/NonPortable/RuntimeTypeUnifier.cs | 81 ++++++++++--------- .../SynchronizedMethodHelpers.cs | 2 +- .../src/System/EETypePtr.cs | 8 ++ .../src/System/Object.CoreRT.cs | 2 +- .../CompilerServices/RuntimeHelpers.CoreRT.cs | 2 +- .../src/System/Type.CoreRT.cs | 45 ++++++++++- .../Runtime/TypeLoader/EETypeCreator.cs | 10 +++ 17 files changed, 244 insertions(+), 48 deletions(-) diff --git a/dependencies.props b/dependencies.props index 8e0520aa04c..85cfaa0aead 100644 --- a/dependencies.props +++ b/dependencies.props @@ -1,7 +1,7 @@ 5.0.0-preview.4.20205.13 - 1.0.0-alpha-28204-03 + 1.0.0-alpha-28820-01 5.0.0-preview.2.20153.3 4.7.0-preview6.19265.2 2.1.14 diff --git a/src/Common/src/Internal/Runtime/EEType.Constants.cs b/src/Common/src/Internal/Runtime/EEType.Constants.cs index 0dfe1026e19..c02238865ab 100644 --- a/src/Common/src/Internal/Runtime/EEType.Constants.cs +++ b/src/Common/src/Internal/Runtime/EEType.Constants.cs @@ -184,6 +184,7 @@ internal enum EETypeField { ETF_InterfaceMap, ETF_TypeManagerIndirection, + ETF_WritableData, ETF_Finalizer, ETF_OptionalFieldsPtr, ETF_SealedVirtualSlots, @@ -296,4 +297,10 @@ internal static class StringComponentSize { public const int Value = sizeof(char); } + + internal static class WritableData + { + public static int GetSize(int pointerSize) => pointerSize; + public static int GetAlignment(int pointerSize) => pointerSize; + } } diff --git a/src/Common/src/Internal/Runtime/EEType.cs b/src/Common/src/Internal/Runtime/EEType.cs index cefef771155..bbbe71d0ecc 100644 --- a/src/Common/src/Internal/Runtime/EEType.cs +++ b/src/Common/src/Internal/Runtime/EEType.cs @@ -184,6 +184,18 @@ internal static bool SupportsRelativePointers } } + /// + /// Gets a value indicating whether writable data is supported. + /// + internal static bool SupportsWritableData + { + get + { + // For now just key this off of SupportsRelativePointer to avoid this on both CppCodegen and WASM. + return SupportsRelativePointers; + } + } + private ushort _usComponentSize; private ushort _usFlags; private uint _uBaseSize; @@ -1248,6 +1260,35 @@ internal IntPtr PointerToTypeManager } #endif + /// + /// Gets a pointer to a segment of writable memory associated with this EEType. + /// The purpose of the segment is controlled by the class library. The runtime doesn't + /// use this memory for any purpose. + /// + internal IntPtr WritableData + { + get + { + Debug.Assert(SupportsWritableData); + + uint offset = GetFieldOffset(EETypeField.ETF_WritableData); + + if (!IsDynamicType) + return GetField(offset).Value; + else + return GetField(offset).Value; + } +#if TYPE_LOADER_IMPLEMENTATION + set + { + Debug.Assert(IsDynamicType && SupportsWritableData); + + uint cbOffset = GetFieldOffset(EETypeField.ETF_WritableData); + *(IntPtr*)((byte*)Unsafe.AsPointer(ref this) + cbOffset) = value; + } +#endif + } + internal unsafe EETypeRareFlags RareFlags { get @@ -1319,6 +1360,16 @@ public uint GetFieldOffset(EETypeField eField) } cbOffset += relativeOrFullPointerOffset; + // Followed by writable data. + if (SupportsWritableData) + { + if (eField == EETypeField.ETF_WritableData) + { + return cbOffset; + } + cbOffset += relativeOrFullPointerOffset; + } + // Followed by the pointer to the finalizer method. if (eField == EETypeField.ETF_Finalizer) { @@ -1421,8 +1472,12 @@ public uint GetFieldOffset(EETypeField eField) public ref T GetField(EETypeField eField) { - fixed (EEType* pThis = &this) - return ref Unsafe.AddByteOffset(ref Unsafe.As(ref *pThis), (IntPtr)GetFieldOffset(eField)); + return ref Unsafe.As(ref *((byte*)Unsafe.AsPointer(ref this) + GetFieldOffset(eField))); + } + + public ref T GetField(uint offset) + { + return ref Unsafe.As(ref *((byte*)Unsafe.AsPointer(ref this) + offset)); } #if TYPE_LOADER_IMPLEMENTATION @@ -1441,6 +1496,7 @@ internal static UInt32 GetSizeofEEType( (IntPtr.Size * cVirtuals) + (sizeof(EEInterfaceInfo) * cInterfaces) + sizeof(IntPtr) + // TypeManager + (SupportsWritableData ? sizeof(IntPtr) : 0) + // WritableData (fHasFinalizer ? sizeof(UIntPtr) : 0) + (fRequiresOptionalFields ? sizeof(IntPtr) : 0) + (fHasSealedVirtuals ? sizeof(IntPtr) : 0) + diff --git a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/EETypeNode.cs b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/EETypeNode.cs index 59562be0125..19d7765e722 100644 --- a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/EETypeNode.cs +++ b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/EETypeNode.cs @@ -46,6 +46,8 @@ namespace ILCompiler.DependencyAnalysis /// | /// [Relative ptr] | Pointer to containing TypeManager indirection cell /// | + /// [Relative ptr] | Pointer to writable data + /// | /// [Relative ptr] | Pointer to finalizer method (optional) /// | /// [Relative ptr] | Pointer to optional fields (optional) @@ -491,6 +493,7 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly) } OutputTypeManagerIndirection(factory, ref objData); + OutputWritableData(factory, ref objData); OutputFinalizerMethod(factory, ref objData); OutputOptionalFields(factory, ref objData); OutputSealedVTable(factory, relocsOnly, ref objData); @@ -825,6 +828,21 @@ protected void OutputTypeManagerIndirection(NodeFactory factory, ref ObjectDataB objData.EmitPointerReloc(factory.TypeManagerIndirection); } + protected void OutputWritableData(NodeFactory factory, ref ObjectDataBuilder objData) + { + if (factory.Target.SupportsRelativePointers) + { + Utf8StringBuilder writableDataBlobName = new Utf8StringBuilder(); + writableDataBlobName.Append("__writableData"); + writableDataBlobName.Append(factory.NameMangler.GetMangledTypeName(_type)); + + BlobNode blob = factory.UninitializedWritableDataBlob(writableDataBlobName.ToUtf8String(), + WritableData.GetSize(factory.Target.PointerSize), WritableData.GetAlignment(factory.Target.PointerSize)); + + objData.EmitReloc(blob, RelocType.IMAGE_REL_BASED_RELPTR32); + } + } + protected void OutputOptionalFields(NodeFactory factory, ref ObjectDataBuilder objData) { if (HasOptionalFields) diff --git a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/GenericDefinitionEETypeNode.cs b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/GenericDefinitionEETypeNode.cs index c2b9f2cbac1..fa93f388b41 100644 --- a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/GenericDefinitionEETypeNode.cs +++ b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/GenericDefinitionEETypeNode.cs @@ -64,6 +64,7 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) dataBuilder.EmitShort(0); // No interface map dataBuilder.EmitInt(_type.GetHashCode()); OutputTypeManagerIndirection(factory, ref dataBuilder); + OutputWritableData(factory, ref dataBuilder); OutputOptionalFields(factory, ref dataBuilder); return dataBuilder.ToObjectData(); diff --git a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/NodeFactory.cs b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/NodeFactory.cs index b9e41db43b6..5bf8167f1cc 100644 --- a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/NodeFactory.cs +++ b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/NodeFactory.cs @@ -230,6 +230,11 @@ private void CreateNodeCaches() return new BlobNode(key.Name, ObjectNodeSection.ReadOnlyDataSection, key.Data, key.Alignment); }); + _uninitializedWritableDataBlobs = new NodeCache(key => + { + return new BlobNode(key.Name, ObjectNodeSection.BssSection, new byte[key.Size], key.Alignment); + }); + _settableReadOnlyDataBlobs = new NodeCache(key => { return new SettableReadOnlyDataBlob(key, ObjectNodeSection.ReadOnlyDataSection); @@ -655,6 +660,13 @@ public ISymbolNode GCStaticEEType(GCPointerMap gcMap) return _GCStaticEETypes.GetOrAdd(gcMap); } + private NodeCache _uninitializedWritableDataBlobs; + + public BlobNode UninitializedWritableDataBlob(Utf8String name, int size, int alignment) + { + return _uninitializedWritableDataBlobs.GetOrAdd(new UninitializedWritableDataBlobKey(name, size, alignment)); + } + private NodeCache _readOnlyDataBlobs; public BlobNode ReadOnlyDataBlob(Utf8String name, byte[] blobData, int alignment) @@ -1205,5 +1217,26 @@ public ReadOnlyDataBlobKey(Utf8String name, byte[] data, int alignment) public override bool Equals(object obj) => obj is ReadOnlyDataBlobKey && Equals((ReadOnlyDataBlobKey)obj); public override int GetHashCode() => Name.GetHashCode(); } + + protected struct UninitializedWritableDataBlobKey : IEquatable + { + public readonly Utf8String Name; + public readonly int Size; + public readonly int Alignment; + + public UninitializedWritableDataBlobKey(Utf8String name, int size, int alignment) + { + Name = name; + Size = size; + Alignment = alignment; + } + + // The assumption here is that the name of the blob is unique. + // We can't emit two blobs with the same name and different contents. + // The name is part of the symbolic name and we don't do any mangling on it. + public bool Equals(UninitializedWritableDataBlobKey other) => Name.Equals(other.Name); + public override bool Equals(object obj) => obj is UninitializedWritableDataBlobKey && Equals((UninitializedWritableDataBlobKey)obj); + public override int GetHashCode() => Name.GetHashCode(); + } } } diff --git a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/ObjectNodeSection.cs b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/ObjectNodeSection.cs index b89b9482403..5a672f953c9 100644 --- a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/ObjectNodeSection.cs +++ b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/ObjectNodeSection.cs @@ -39,16 +39,17 @@ public bool IsStandardSection { get { - return this == DataSection || this == ReadOnlyDataSection || this == FoldableReadOnlyDataSection || this == TextSection || this == XDataSection; + return this == DataSection || this == ReadOnlyDataSection || this == FoldableReadOnlyDataSection || this == TextSection || this == XDataSection || this == BssSection; } } public static readonly ObjectNodeSection XDataSection = new ObjectNodeSection("xdata", SectionType.ReadOnly); public static readonly ObjectNodeSection DataSection = new ObjectNodeSection("data", SectionType.Writeable); public static readonly ObjectNodeSection ReadOnlyDataSection = new ObjectNodeSection("rdata", SectionType.ReadOnly); - public static readonly ObjectNodeSection FoldableReadOnlyDataSection = new ObjectNodeSection("rdata$F", SectionType.ReadOnly); + public static readonly ObjectNodeSection FoldableReadOnlyDataSection = new ObjectNodeSection("rdata", SectionType.ReadOnly); public static readonly ObjectNodeSection TextSection = new ObjectNodeSection("text", SectionType.Executable); public static readonly ObjectNodeSection TLSSection = new ObjectNodeSection("TLS", SectionType.Writeable); + public static readonly ObjectNodeSection BssSection = new ObjectNodeSection("bss", SectionType.Writeable); public static readonly ObjectNodeSection ManagedCodeWindowsContentSection = new ObjectNodeSection(".managedcode$I", SectionType.Executable); public static readonly ObjectNodeSection FoldableManagedCodeWindowsContentSection = new ObjectNodeSection(".managedcode$I", SectionType.Executable); public static readonly ObjectNodeSection ManagedCodeUnixContentSection = new ObjectNodeSection("__managedcode", SectionType.Executable); diff --git a/src/Native/Runtime/inc/eetype.h b/src/Native/Runtime/inc/eetype.h index 784a379f9cc..878acdd7248 100644 --- a/src/Native/Runtime/inc/eetype.h +++ b/src/Native/Runtime/inc/eetype.h @@ -16,6 +16,10 @@ struct TypeManagerHandle; class DynamicModule; struct EETypeRef; +#if !defined(USE_PORTABLE_HELPERS) +#define SUPPORTS_WRITABLE_DATA 1 +#endif + //------------------------------------------------------------------------------------------------- // Array of these represents the interfaces implemented by a type @@ -86,6 +90,7 @@ enum EETypeField { ETF_InterfaceMap, ETF_TypeManagerIndirection, + ETF_WritableData, ETF_Finalizer, ETF_OptionalFieldsPtr, ETF_SealedVirtualSlots, diff --git a/src/Native/Runtime/inc/eetype.inl b/src/Native/Runtime/inc/eetype.inl index e7bcf1f7944..0fd0829f3f3 100644 --- a/src/Native/Runtime/inc/eetype.inl +++ b/src/Native/Runtime/inc/eetype.inl @@ -165,6 +165,15 @@ __forceinline UInt32 EEType::GetFieldOffset(EETypeField eField) } cbOffset += relativeOrFullPointerOffset; +#if SUPPORTS_WRITABLE_DATA + // Followed by writable data. + if (eField == ETF_WritableData) + { + return cbOffset; + } + cbOffset += relativeOrFullPointerOffset; +#endif + // Followed by the pointer to the finalizer method. if (eField == ETF_Finalizer) { diff --git a/src/System.Private.CoreLib/shared/System/Nullable.cs b/src/System.Private.CoreLib/shared/System/Nullable.cs index 223a446ae8e..8a5416d7286 100644 --- a/src/System.Private.CoreLib/shared/System/Nullable.cs +++ b/src/System.Private.CoreLib/shared/System/Nullable.cs @@ -111,7 +111,7 @@ public static bool Equals(Nullable n1, Nullable n2) where T : struct { if (nullableEEType.IsNullable) { - return Internal.Reflection.Core.NonPortable.RuntimeTypeUnifier.GetRuntimeTypeForEEType(nullableEEType.NullableType); + return Type.GetTypeFromEETypePtr(nullableEEType.NullableType); } } return null; diff --git a/src/System.Private.CoreLib/src/Internal/Reflection/Core/NonPortable/RuntimeTypeUnifier.cs b/src/System.Private.CoreLib/src/Internal/Reflection/Core/NonPortable/RuntimeTypeUnifier.cs index 37c1e310b2f..6c347d4e647 100644 --- a/src/System.Private.CoreLib/src/Internal/Reflection/Core/NonPortable/RuntimeTypeUnifier.cs +++ b/src/System.Private.CoreLib/src/Internal/Reflection/Core/NonPortable/RuntimeTypeUnifier.cs @@ -43,11 +43,13 @@ internal static partial class RuntimeTypeUnifier // // Retrieves the unified Type object for given RuntimeTypeHandle (this is basically the Type.GetTypeFromHandle() api without the input validation.) // - public static Type GetTypeForRuntimeTypeHandle(RuntimeTypeHandle runtimeTypeHandle) - => RuntimeTypeHandleToTypeCache.Table.GetOrAdd(runtimeTypeHandle.RawValue); - internal static Type GetRuntimeTypeForEEType(EETypePtr eeType) - => RuntimeTypeHandleToTypeCache.Table.GetOrAdd(eeType.RawValue); + { + // If writable data is supported, we shouldn't be using the hashtable - the runtime type + // is accessible through a couple indirections from the EETypePtr which is much faster. + Debug.Assert(!Internal.Runtime.EEType.SupportsWritableData); + return RuntimeTypeHandleToTypeCache.Table.GetOrAdd(eeType.RawValue); + } // // TypeTable mapping raw RuntimeTypeHandles (normalized or otherwise) to Types. @@ -65,51 +67,54 @@ private RuntimeTypeHandleToTypeCache() { } protected sealed override Type Factory(IntPtr rawRuntimeTypeHandleKey) { EETypePtr eeType = new EETypePtr(rawRuntimeTypeHandleKey); - RuntimeTypeHandle runtimeTypeHandle = new RuntimeTypeHandle(eeType); + return GetRuntimeTypeBypassCache(eeType); + } + + public static readonly RuntimeTypeHandleToTypeCache Table = new RuntimeTypeHandleToTypeCache(); + } - // Desktop compat: Allows Type.GetTypeFromHandle(default(RuntimeTypeHandle)) to map to null. - if (runtimeTypeHandle.IsNull) - return null; + // This bypasses the CoreLib's unifier, but there's another unifier deeper within the reflection stack: + // this code is safe to call without locking. See comment above. + public static Type GetRuntimeTypeBypassCache(EETypePtr eeType) + { + RuntimeTypeHandle runtimeTypeHandle = new RuntimeTypeHandle(eeType); - ReflectionExecutionDomainCallbacks callbacks = RuntimeAugments.Callbacks; + ReflectionExecutionDomainCallbacks callbacks = RuntimeAugments.Callbacks; - if (eeType.IsDefType) - { - if (eeType.IsGenericTypeDefinition) - { - return callbacks.GetNamedTypeForHandle(runtimeTypeHandle, isGenericTypeDefinition: true); - } - else if (eeType.IsGeneric) - { - return callbacks.GetConstructedGenericTypeForHandle(runtimeTypeHandle); - } - else - { - return callbacks.GetNamedTypeForHandle(runtimeTypeHandle, isGenericTypeDefinition: false); - } - } - else if (eeType.IsArray) - { - if (!eeType.IsSzArray) - return callbacks.GetMdArrayTypeForHandle(runtimeTypeHandle, eeType.ArrayRank); - else - return callbacks.GetArrayTypeForHandle(runtimeTypeHandle); - } - else if (eeType.IsPointer) + if (eeType.IsDefType) + { + if (eeType.IsGenericTypeDefinition) { - return callbacks.GetPointerTypeForHandle(runtimeTypeHandle); + return callbacks.GetNamedTypeForHandle(runtimeTypeHandle, isGenericTypeDefinition: true); } - else if (eeType.IsByRef) + else if (eeType.IsGeneric) { - return callbacks.GetByRefTypeForHandle(runtimeTypeHandle); + return callbacks.GetConstructedGenericTypeForHandle(runtimeTypeHandle); } else { - throw new ArgumentException(SR.Arg_InvalidRuntimeTypeHandle); + return callbacks.GetNamedTypeForHandle(runtimeTypeHandle, isGenericTypeDefinition: false); } } - - public static readonly RuntimeTypeHandleToTypeCache Table = new RuntimeTypeHandleToTypeCache(); + else if (eeType.IsArray) + { + if (!eeType.IsSzArray) + return callbacks.GetMdArrayTypeForHandle(runtimeTypeHandle, eeType.ArrayRank); + else + return callbacks.GetArrayTypeForHandle(runtimeTypeHandle); + } + else if (eeType.IsPointer) + { + return callbacks.GetPointerTypeForHandle(runtimeTypeHandle); + } + else if (eeType.IsByRef) + { + return callbacks.GetByRefTypeForHandle(runtimeTypeHandle); + } + else + { + throw new ArgumentException(SR.Arg_InvalidRuntimeTypeHandle); + } } } } diff --git a/src/System.Private.CoreLib/src/Internal/Runtime/CompilerHelpers/SynchronizedMethodHelpers.cs b/src/System.Private.CoreLib/src/Internal/Runtime/CompilerHelpers/SynchronizedMethodHelpers.cs index 018c8005c3d..842fc37e0f3 100644 --- a/src/System.Private.CoreLib/src/Internal/Runtime/CompilerHelpers/SynchronizedMethodHelpers.cs +++ b/src/System.Private.CoreLib/src/Internal/Runtime/CompilerHelpers/SynchronizedMethodHelpers.cs @@ -56,7 +56,7 @@ private static void MonitorExitStatic(IntPtr pEEType, ref bool lockTaken) private static object GetStaticLockObject(IntPtr pEEType) { - return Internal.Reflection.Core.NonPortable.RuntimeTypeUnifier.GetRuntimeTypeForEEType(new System.EETypePtr(pEEType)); + return Type.GetTypeFromEETypePtr(new EETypePtr(pEEType)); } } } diff --git a/src/System.Private.CoreLib/src/System/EETypePtr.cs b/src/System.Private.CoreLib/src/System/EETypePtr.cs index ff511657642..412ed6bf5f9 100644 --- a/src/System.Private.CoreLib/src/System/EETypePtr.cs +++ b/src/System.Private.CoreLib/src/System/EETypePtr.cs @@ -16,6 +16,8 @@ using System.Runtime.InteropServices; using System.Runtime.CompilerServices; +using Internal.Runtime.CompilerServices; + using EEType = Internal.Runtime.EEType; using EETypeElementType = Internal.Runtime.EETypeElementType; using EETypeRef = Internal.Runtime.EETypeRef; @@ -436,6 +438,12 @@ internal RuntimeImports.RhCorElementTypeInfo CorElementTypeInfo } } + internal ref T GetWritableData() where T : unmanaged + { + Debug.Assert(Internal.Runtime.WritableData.GetSize(IntPtr.Size) == sizeof(T)); + return ref Unsafe.AsRef((void*)_value->WritableData); + } + [Intrinsic] [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static EETypePtr EETypePtrOf() diff --git a/src/System.Private.CoreLib/src/System/Object.CoreRT.cs b/src/System.Private.CoreLib/src/System/Object.CoreRT.cs index e5814f9d362..4f82228cfb8 100644 --- a/src/System.Private.CoreLib/src/System/Object.CoreRT.cs +++ b/src/System.Private.CoreLib/src/System/Object.CoreRT.cs @@ -43,7 +43,7 @@ internal unsafe EEType* EEType [Intrinsic] public Type GetType() { - return RuntimeTypeUnifier.GetRuntimeTypeForEEType(EETypePtr); + return Type.GetTypeFromEETypePtr(EETypePtr); } internal EETypePtr EETypePtr diff --git a/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreRT.cs b/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreRT.cs index 15309a3a3e1..79c9a653fa9 100644 --- a/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreRT.cs +++ b/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreRT.cs @@ -301,7 +301,7 @@ private static object GetUninitializedObjectInternal(Type type) if (eeTypePtr.IsNullable) { - return GetUninitializedObject(RuntimeTypeUnifier.GetRuntimeTypeForEEType(eeTypePtr.NullableType)); + return GetUninitializedObject(Type.GetTypeFromEETypePtr(eeTypePtr.NullableType)); } // Triggering the .cctor here is slightly different than desktop/CoreCLR, which diff --git a/src/System.Private.CoreLib/src/System/Type.CoreRT.cs b/src/System.Private.CoreLib/src/System/Type.CoreRT.cs index bda5cda9b71..b11410cf4f5 100644 --- a/src/System.Private.CoreLib/src/System/Type.CoreRT.cs +++ b/src/System.Private.CoreLib/src/System/Type.CoreRT.cs @@ -7,8 +7,10 @@ using System.Runtime.CompilerServices; using Internal.Runtime.Augments; +using Internal.Runtime.CompilerServices; using Internal.Reflection.Augments; using Internal.Reflection.Core.NonPortable; +using System.Runtime.InteropServices; namespace System { @@ -17,7 +19,48 @@ public abstract partial class Type : MemberInfo, IReflect public bool IsInterface => (GetAttributeFlagsImpl() & TypeAttributes.ClassSemanticsMask) == TypeAttributes.Interface; [Intrinsic] - public static Type GetTypeFromHandle(RuntimeTypeHandle handle) => RuntimeTypeUnifier.GetTypeForRuntimeTypeHandle(handle); + public static Type GetTypeFromHandle(RuntimeTypeHandle handle) => handle.IsNull ? null : GetTypeFromEETypePtr(handle.ToEETypePtr()); + + internal static Type GetTypeFromEETypePtr(EETypePtr eeType) + { + // If we support the writable data section on EETypes, the runtime type associated with the EEType + // 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.EEType.SupportsWritableData) + { + ref GCHandle handle = ref eeType.GetWritableData(); + if (handle.IsAllocated) + { + return Unsafe.As(handle.Target); + } + else + { + return GetTypeFromEETypePtrSlow(eeType, ref handle); + } + } + else + { + return RuntimeTypeUnifier.GetRuntimeTypeForEEType(eeType); + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static Type GetTypeFromEETypePtrSlow(EETypePtr eeType, ref GCHandle handle) + { + // Note: this is bypassing the "fast" unifier cache (based on a simple IntPtr + // identity of EEType pointers). There is another unifier behind that cache + // that ensures this code is race-free. + Type result = RuntimeTypeUnifier.GetRuntimeTypeBypassCache(eeType); + GCHandle tempHandle = GCHandle.Alloc(result); + + // We don't want to leak a handle if there's a race + if (Interlocked.CompareExchange(ref Unsafe.As(ref handle), (IntPtr)tempHandle, default) != default) + { + tempHandle.Free(); + } + + return result; + } [Intrinsic] public static Type GetType(string typeName) => GetType(typeName, throwOnError: false, ignoreCase: false); diff --git a/src/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/EETypeCreator.cs b/src/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/EETypeCreator.cs index efd0e062c1c..df7b5b38f36 100644 --- a/src/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/EETypeCreator.cs +++ b/src/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/EETypeCreator.cs @@ -144,6 +144,7 @@ private static void CreateEETypeWorker(EEType* pTemplateEEType, UInt32 hashCodeO bool successful = false; IntPtr eeTypePtrPlusGCDesc = IntPtr.Zero; IntPtr dynamicDispatchMapPtr = IntPtr.Zero; + IntPtr writableDataPtr = IntPtr.Zero; DynamicModule* dynamicModulePtr = null; IntPtr gcStaticData = IntPtr.Zero; IntPtr gcStaticsIndirection = IntPtr.Zero; @@ -566,6 +567,13 @@ private static void CreateEETypeWorker(EEType* pTemplateEEType, UInt32 hashCodeO } } + if (EEType.SupportsWritableData) + { + writableDataPtr = MemoryHelpers.AllocateMemory(WritableData.GetSize(IntPtr.Size)); + MemoryHelpers.Memset(writableDataPtr, WritableData.GetSize(IntPtr.Size), 0); + pEEType->WritableData = writableDataPtr; + } + // Create a new DispatchMap for the type if (requiresDynamicDispatchMap) { @@ -711,6 +719,8 @@ private static void CreateEETypeWorker(EEType* pTemplateEEType, UInt32 hashCodeO MemoryHelpers.FreeMemory(genericComposition); if (nonGcStaticData != IntPtr.Zero) MemoryHelpers.FreeMemory(nonGcStaticData); + if (writableDataPtr != IntPtr.Zero) + MemoryHelpers.FreeMemory(writableDataPtr); } } }