From 9b29f9680c896f28a5c5e3e0a7652bd8c4f6c8db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Thu, 8 Dec 2022 06:54:00 +0900 Subject: [PATCH] Use static sealed vtable for dynamically created types (#79332) The sealed vtable is identical for all canonically equivalent types. We had a to make a copy because in the presence of universal shared generics, new sealed vtables could be created for dynamic types and those need to use pointer-sized slots (the slots are relative pointers in the static case and have been since Redhawk times). --- .../src/Internal/Runtime/MethodTable.cs | 46 ++++--- .../Runtime/TypeLoader/EETypeCreator.cs | 124 ++++++++---------- .../Runtime/TypeLoader/TypeBuilder.cs | 5 - .../Runtime/TypeLoader/TypeBuilderState.cs | 2 - .../Internal/NativeFormat/NativeFormat.cs | 2 +- .../NativeLayoutVertexNode.cs | 10 -- 6 files changed, 76 insertions(+), 113 deletions(-) diff --git a/src/coreclr/nativeaot/Common/src/Internal/Runtime/MethodTable.cs b/src/coreclr/nativeaot/Common/src/Internal/Runtime/MethodTable.cs index e1756cf18e23e..0f3393241e4b5 100644 --- a/src/coreclr/nativeaot/Common/src/Internal/Runtime/MethodTable.cs +++ b/src/coreclr/nativeaot/Common/src/Internal/Runtime/MethodTable.cs @@ -1091,41 +1091,39 @@ private static IntPtr FollowRelativePointer(int* pDist) return result; } - internal IntPtr GetSealedVirtualSlot(ushort slotNumber) +#if TYPE_LOADER_IMPLEMENTATION + internal +#else + private +#endif + void* GetSealedVirtualTable() { Debug.Assert((RareFlags & EETypeRareFlags.HasSealedVTableEntriesFlag) != 0); - fixed (MethodTable* pThis = &this) + uint cbSealedVirtualSlotsTypeOffset = GetFieldOffset(EETypeField.ETF_SealedVirtualSlots); + byte* pThis = (byte*)Unsafe.AsPointer(ref this); + if (IsDynamicType || !SupportsRelativePointers) { - if (IsDynamicType || !SupportsRelativePointers) - { - uint cbSealedVirtualSlotsTypeOffset = GetFieldOffset(EETypeField.ETF_SealedVirtualSlots); - IntPtr* pSealedVirtualsSlotTable = *(IntPtr**)((byte*)pThis + cbSealedVirtualSlotsTypeOffset); - return pSealedVirtualsSlotTable[slotNumber]; - } - else - { - uint cbSealedVirtualSlotsTypeOffset = GetFieldOffset(EETypeField.ETF_SealedVirtualSlots); - int* pSealedVirtualsSlotTable = (int*)FollowRelativePointer((int*)((byte*)pThis + cbSealedVirtualSlotsTypeOffset)); - IntPtr result = FollowRelativePointer(&pSealedVirtualsSlotTable[slotNumber]); - return result; - } + return *(void**)(pThis + cbSealedVirtualSlotsTypeOffset); + } + else + { + return (void*)FollowRelativePointer((int*)(pThis + cbSealedVirtualSlotsTypeOffset)); } } -#if TYPE_LOADER_IMPLEMENTATION - internal void SetSealedVirtualSlot(IntPtr value, ushort slotNumber) + internal IntPtr GetSealedVirtualSlot(ushort slotNumber) { - Debug.Assert(IsDynamicType); - - fixed (MethodTable* pThis = &this) + void* pSealedVtable = GetSealedVirtualTable(); + if (!SupportsRelativePointers) { - uint cbSealedVirtualSlotsTypeOffset = GetFieldOffset(EETypeField.ETF_SealedVirtualSlots); - IntPtr* pSealedVirtualsSlotTable = *(IntPtr**)((byte*)pThis + cbSealedVirtualSlotsTypeOffset); - pSealedVirtualsSlotTable[slotNumber] = value; + return ((IntPtr*)pSealedVtable)[slotNumber]; + } + else + { + return FollowRelativePointer(&((int*)pSealedVtable)[slotNumber]); } } -#endif internal byte* OptionalFieldsPtr { diff --git a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/EETypeCreator.cs b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/EETypeCreator.cs index a1672d10f1b45..2255fde37cf09 100644 --- a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/EETypeCreator.cs +++ b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/EETypeCreator.cs @@ -237,82 +237,76 @@ private static void CreateEETypeWorker(MethodTable* pTemplateEEType, uint hashCo // Optional fields encoding int cbOptionalFieldsSize; - OptionalFieldsRuntimeBuilder optionalFields; - { - optionalFields = new OptionalFieldsRuntimeBuilder(pTemplateEEType != null ? pTemplateEEType->OptionalFieldsPtr : null); - - uint rareFlags = optionalFields.GetFieldValue(EETypeOptionalFieldTag.RareFlags, 0); + OptionalFieldsRuntimeBuilder optionalFields = new OptionalFieldsRuntimeBuilder(pTemplateEEType->OptionalFieldsPtr); - if (state.NumSealedVTableEntries > 0) - rareFlags |= (uint)EETypeRareFlags.HasSealedVTableEntriesFlag; + uint rareFlags = optionalFields.GetFieldValue(EETypeOptionalFieldTag.RareFlags, 0); - if (state.NonGcDataSize != 0) - rareFlags |= (uint)EETypeRareFlags.IsDynamicTypeWithNonGcStatics; + if (state.NonGcDataSize != 0) + rareFlags |= (uint)EETypeRareFlags.IsDynamicTypeWithNonGcStatics; - if (state.GcDataSize != 0) - rareFlags |= (uint)EETypeRareFlags.IsDynamicTypeWithGcStatics; + if (state.GcDataSize != 0) + rareFlags |= (uint)EETypeRareFlags.IsDynamicTypeWithGcStatics; - if (state.ThreadDataSize != 0) - rareFlags |= (uint)EETypeRareFlags.IsDynamicTypeWithThreadStatics; + if (state.ThreadDataSize != 0) + rareFlags |= (uint)EETypeRareFlags.IsDynamicTypeWithThreadStatics; #if TARGET_ARM - if (state.FieldAlignment == 8) - rareFlags |= (uint)EETypeRareFlags.RequiresAlign8Flag; - else - rareFlags &= ~(uint)EETypeRareFlags.RequiresAlign8Flag; + if (state.FieldAlignment == 8) + rareFlags |= (uint)EETypeRareFlags.RequiresAlign8Flag; + else + rareFlags &= ~(uint)EETypeRareFlags.RequiresAlign8Flag; #endif #if TARGET_ARM || TARGET_ARM64 - if (state.IsHFA) - rareFlags |= (uint)EETypeRareFlags.IsHFAFlag; - else - rareFlags &= ~(uint)EETypeRareFlags.IsHFAFlag; + if (state.IsHFA) + rareFlags |= (uint)EETypeRareFlags.IsHFAFlag; + else + rareFlags &= ~(uint)EETypeRareFlags.IsHFAFlag; #endif - if (state.HasStaticConstructor) - rareFlags |= (uint)EETypeRareFlags.HasCctorFlag; - else - rareFlags &= ~(uint)EETypeRareFlags.HasCctorFlag; + if (state.HasStaticConstructor) + rareFlags |= (uint)EETypeRareFlags.HasCctorFlag; + else + rareFlags &= ~(uint)EETypeRareFlags.HasCctorFlag; - if (isAbstractClass) - rareFlags |= (uint)EETypeRareFlags.IsAbstractClassFlag; - else - rareFlags &= ~(uint)EETypeRareFlags.IsAbstractClassFlag; + if (isAbstractClass) + rareFlags |= (uint)EETypeRareFlags.IsAbstractClassFlag; + else + rareFlags &= ~(uint)EETypeRareFlags.IsAbstractClassFlag; - if (isByRefLike) - rareFlags |= (uint)EETypeRareFlags.IsByRefLikeFlag; - else - rareFlags &= ~(uint)EETypeRareFlags.IsByRefLikeFlag; + if (isByRefLike) + rareFlags |= (uint)EETypeRareFlags.IsByRefLikeFlag; + else + rareFlags &= ~(uint)EETypeRareFlags.IsByRefLikeFlag; - if (isNullable) - { - uint nullableValueOffset = state.NullableValueOffset; + if (isNullable) + { + uint nullableValueOffset = state.NullableValueOffset; - // The stored offset is never zero (Nullable has a boolean there indicating whether the value is valid). - // If the real offset is one, then the field isn't set. Otherwise the offset is encoded - 1 to save space. - if (nullableValueOffset == 1) - optionalFields.ClearField(EETypeOptionalFieldTag.NullableValueOffset); - else - optionalFields.SetFieldValue(EETypeOptionalFieldTag.NullableValueOffset, checked(nullableValueOffset - 1)); - } - else - { + // The stored offset is never zero (Nullable has a boolean there indicating whether the value is valid). + // If the real offset is one, then the field isn't set. Otherwise the offset is encoded - 1 to save space. + if (nullableValueOffset == 1) optionalFields.ClearField(EETypeOptionalFieldTag.NullableValueOffset); - } + else + optionalFields.SetFieldValue(EETypeOptionalFieldTag.NullableValueOffset, checked(nullableValueOffset - 1)); + } + else + { + optionalFields.ClearField(EETypeOptionalFieldTag.NullableValueOffset); + } - optionalFields.SetFieldValue(EETypeOptionalFieldTag.RareFlags, rareFlags); + optionalFields.SetFieldValue(EETypeOptionalFieldTag.RareFlags, rareFlags); - // Dispatch map is fetched from template type - optionalFields.ClearField(EETypeOptionalFieldTag.DispatchMap); + // Dispatch map is fetched from template type + optionalFields.ClearField(EETypeOptionalFieldTag.DispatchMap); - optionalFields.ClearField(EETypeOptionalFieldTag.ValueTypeFieldPadding); + optionalFields.ClearField(EETypeOptionalFieldTag.ValueTypeFieldPadding); - if (valueTypeFieldPaddingEncoded != 0) - optionalFields.SetFieldValue(EETypeOptionalFieldTag.ValueTypeFieldPadding, valueTypeFieldPaddingEncoded); + if (valueTypeFieldPaddingEncoded != 0) + optionalFields.SetFieldValue(EETypeOptionalFieldTag.ValueTypeFieldPadding, valueTypeFieldPaddingEncoded); - // Compute size of optional fields encoding - cbOptionalFieldsSize = optionalFields.Encode(); - Debug.Assert(cbOptionalFieldsSize > 0); - } + // Compute size of optional fields encoding + cbOptionalFieldsSize = optionalFields.Encode(); + Debug.Assert(cbOptionalFieldsSize > 0); // Note: The number of vtable slots on the MethodTable to create is not necessary equal to the number of // vtable slots on the template type for universal generics (see ComputeVTableLayout) @@ -332,7 +326,7 @@ private static void CreateEETypeWorker(MethodTable* pTemplateEEType, uint hashCo runtimeInterfacesLength, hasFinalizer, true, - state.NumSealedVTableEntries > 0, + (rareFlags & (int)EETypeRareFlags.HasSealedVTableEntriesFlag) != 0, isGeneric, state.NonGcDataSize != 0, state.GcDataSize != 0, @@ -385,18 +379,10 @@ private static void CreateEETypeWorker(MethodTable* pTemplateEEType, uint hashCo } // Copy the sealed vtable entries if they exist on the template type - if (state.NumSealedVTableEntries > 0) + if ((rareFlags & (int)EETypeRareFlags.HasSealedVTableEntriesFlag) != 0) { - state.HalfBakedSealedVTable = MemoryHelpers.AllocateMemory((int)state.NumSealedVTableEntries * IntPtr.Size); - uint cbSealedVirtualSlotsTypeOffset = pEEType->GetFieldOffset(EETypeField.ETF_SealedVirtualSlots); - *((IntPtr*)((byte*)pEEType + cbSealedVirtualSlotsTypeOffset)) = state.HalfBakedSealedVTable; - - for (ushort i = 0; i < state.NumSealedVTableEntries; i++) - { - IntPtr value = pTemplateEEType->GetSealedVirtualSlot(i); - pEEType->SetSealedVirtualSlot(value, i); - } + *((void**)((byte*)pEEType + cbSealedVirtualSlotsTypeOffset)) = pTemplateEEType->GetSealedVirtualTable(); } if (MethodTable.SupportsWritableData) @@ -455,7 +441,6 @@ private static void CreateEETypeWorker(MethodTable* pTemplateEEType, uint hashCo state.HalfBakedDictionary = state.Dictionary.Allocate(); Debug.Assert(!state.HalfBakedRuntimeTypeHandle.IsNull()); - Debug.Assert((state.NumSealedVTableEntries == 0 && state.HalfBakedSealedVTable == IntPtr.Zero) || (state.NumSealedVTableEntries > 0 && state.HalfBakedSealedVTable != IntPtr.Zero)); Debug.Assert((state.Dictionary == null && state.HalfBakedDictionary == IntPtr.Zero) || (state.Dictionary != null && state.HalfBakedDictionary != IntPtr.Zero)); successful = true; @@ -466,8 +451,6 @@ private static void CreateEETypeWorker(MethodTable* pTemplateEEType, uint hashCo { if (eeTypePtrPlusGCDesc != IntPtr.Zero) MemoryHelpers.FreeMemory(eeTypePtrPlusGCDesc); - if (state.HalfBakedSealedVTable != IntPtr.Zero) - MemoryHelpers.FreeMemory(state.HalfBakedSealedVTable); if (state.HalfBakedDictionary != IntPtr.Zero) MemoryHelpers.FreeMemory(state.HalfBakedDictionary); if (gcStaticData != IntPtr.Zero) @@ -749,7 +732,6 @@ public static RuntimeTypeHandle CreateEEType(TypeDesc type, TypeBuilderState sta Debug.Assert(false == state.HasStaticConstructor); Debug.Assert(0 == state.GcDataSize); Debug.Assert(0 == state.ThreadStaticOffset); - Debug.Assert(0 == state.NumSealedVTableEntries); Debug.Assert(IntPtr.Zero == state.GcStaticDesc); Debug.Assert(IntPtr.Zero == state.ThreadStaticDesc); diff --git a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeBuilder.cs b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeBuilder.cs index db04640b392e4..4d0da15912ae4 100644 --- a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeBuilder.cs +++ b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeBuilder.cs @@ -473,11 +473,6 @@ internal void ParseNativeLayoutInfo(TypeBuilderState state, TypeDesc type) typeInfoParser.SkipInteger(); // Handled in type layout algorithm break; - case BagElementKind.SealedVTableEntries: - TypeLoaderLogger.WriteLine("Found BagElementKind.SealedVTableEntries"); - state.NumSealedVTableEntries = typeInfoParser.GetUnsigned(); - break; - case BagElementKind.DictionaryLayout: TypeLoaderLogger.WriteLine("Found BagElementKind.DictionaryLayout"); Debug.Assert(!isTemplateUniversalCanon, "Universal template nativelayout do not have DictionaryLayout"); diff --git a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeBuilderState.cs b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeBuilderState.cs index e16517a118424..8305f1e492916 100644 --- a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeBuilderState.cs +++ b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeBuilderState.cs @@ -45,7 +45,6 @@ public TypeBuilderState(TypeDesc typeBeingBuilt) public RuntimeTypeHandle HalfBakedRuntimeTypeHandle; public IntPtr HalfBakedDictionary; - public IntPtr HalfBakedSealedVTable; private bool _templateComputed; private bool _nativeLayoutTokenComputed; @@ -344,7 +343,6 @@ public bool HasStaticConstructor public IntPtr GcStaticDesc; public IntPtr ThreadStaticDesc; public uint ThreadStaticOffset; - public uint NumSealedVTableEntries; public GenericVariance[] GenericVarianceFlags; // Sentinel static to allow us to initialize _instanceLayout to something diff --git a/src/coreclr/tools/Common/Internal/NativeFormat/NativeFormat.cs b/src/coreclr/tools/Common/Internal/NativeFormat/NativeFormat.cs index 534447d3e7dec..a5645f39f0ade 100644 --- a/src/coreclr/tools/Common/Internal/NativeFormat/NativeFormat.cs +++ b/src/coreclr/tools/Common/Internal/NativeFormat/NativeFormat.cs @@ -52,7 +52,7 @@ enum BagElementKind : uint ThreadStaticOffset = 0x4a, FieldLayout = 0x4b, // unused = 0x4c, - SealedVTableEntries = 0x4d, + // unused = 0x4d, ClassConstructorPointer = 0x4e, // unused = 0x4f, GenericVarianceInfo = 0x50, diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NativeLayoutVertexNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NativeLayoutVertexNode.cs index 514c6fcd61a58..de53e842a9ad7 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NativeLayoutVertexNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NativeLayoutVertexNode.cs @@ -1229,16 +1229,6 @@ public override Vertex WriteVertex(NodeFactory factory) layoutInfo.Append(BagElementKind.BaseType, factory.NativeLayout.PlacedSignatureVertex(factory.NativeLayout.TypeSignatureVertex(_type.BaseType)).WriteVertex(factory)); } - if (!_type.IsArrayTypeWithoutGenericInterfaces() && ConstructedEETypeNode.CreationAllowed(_type)) - { - SealedVTableNode sealedVTable = factory.SealedVTable(_type.ConvertToCanonForm(CanonicalFormKind.Specific)); - - sealedVTable.BuildSealedVTableSlots(factory, relocsOnly: false /* This is the final emission phase */); - - if (sealedVTable.NumSealedVTableEntries > 0) - layoutInfo.AppendUnsigned(BagElementKind.SealedVTableEntries, (uint)sealedVTable.NumSealedVTableEntries); - } - if (_type.GetTypeDefinition().HasVariance || factory.TypeSystemContext.IsGenericArrayInterfaceType(_type)) { // Runtime casting logic relies on all interface types implemented on arrays