diff --git a/src/coreclr/System.Private.CoreLib/src/System/GC.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/GC.CoreCLR.cs index d59ad132ef59ee..f25856d438cffe 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/GC.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/GC.CoreCLR.cs @@ -732,9 +732,6 @@ internal static void UnregisterMemoryLoadChangeNotification(Action notification) /// Specifies the type of the array element. /// Specifies the length of the array. /// Specifies whether the allocated array must be pinned. - /// - /// If pinned is set to true, must not be a reference type or a type that contains object references. - /// [MethodImpl(MethodImplOptions.AggressiveInlining)] // forced to ensure no perf drop for small memory buffers (hot path) public static unsafe T[] AllocateUninitializedArray(int length, bool pinned = false) // T[] rather than T?[] to match `new T[length]` behavior { @@ -757,11 +754,8 @@ public static unsafe T[] AllocateUninitializedArray(int length, bool pinned = #endif } - else if (RuntimeHelpers.IsReferenceOrContainsReferences()) - { - ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(T)); - } + // Runtime overrides GC_ALLOC_ZEROING_OPTIONAL if the type contains references, so we don't need to worry about that. GC_ALLOC_FLAGS flags = GC_ALLOC_FLAGS.GC_ALLOC_ZEROING_OPTIONAL; if (pinned) flags |= GC_ALLOC_FLAGS.GC_ALLOC_PINNED_OBJECT_HEAP; @@ -775,22 +769,16 @@ public static unsafe T[] AllocateUninitializedArray(int length, bool pinned = /// Specifies the type of the array element. /// Specifies the length of the array. /// Specifies whether the allocated array must be pinned. - /// - /// If pinned is set to true, must not be a reference type or a type that contains object references. - /// public static T[] AllocateArray(int length, bool pinned = false) // T[] rather than T?[] to match `new T[length]` behavior { GC_ALLOC_FLAGS flags = GC_ALLOC_FLAGS.GC_ALLOC_NO_FLAGS; if (pinned) { - if (RuntimeHelpers.IsReferenceOrContainsReferences()) - ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(T)); - flags = GC_ALLOC_FLAGS.GC_ALLOC_PINNED_OBJECT_HEAP; } - return Unsafe.As(AllocateNewArray(typeof(T[]).TypeHandle.Value, length, flags)); + return Unsafe.As(AllocateNewArray(RuntimeTypeHandle.ToIntPtr(typeof(T[]).TypeHandle), length, flags)); } [MethodImpl(MethodImplOptions.InternalCall)] diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/GC.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/GC.NativeAot.cs index a9bf5a945fb663..54934f10be61f8 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/GC.NativeAot.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/GC.NativeAot.cs @@ -795,9 +795,6 @@ internal static ulong GetSegmentSize() /// Specifies the type of the array element. /// Specifies the length of the array. /// Specifies whether the allocated array must be pinned. - /// - /// If pinned is set to true, must not be a reference type or a type that contains object references. - /// [MethodImpl(MethodImplOptions.AggressiveInlining)] // forced to ensure no perf drop for small memory buffers (hot path) public static unsafe T[] AllocateUninitializedArray(int length, bool pinned = false) { @@ -819,10 +816,6 @@ public static unsafe T[] AllocateUninitializedArray(int length, bool pinned = } #endif } - else if (RuntimeHelpers.IsReferenceOrContainsReferences()) - { - ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(T)); - } // kept outside of the small arrays hot path to have inlining without big size growth return AllocateNewUninitializedArray(length, pinned); @@ -837,7 +830,7 @@ static T[] AllocateNewUninitializedArray(int length, bool pinned) throw new OverflowException(); T[]? array = null; - RuntimeImports.RhAllocateNewArray(EETypePtr.EETypePtrOf().RawValue, (uint)length, (uint)flags, Unsafe.AsPointer(ref array)); + RuntimeImports.RhAllocateNewArray(MethodTable.Of(), (uint)length, (uint)flags, Unsafe.AsPointer(ref array)); if (array == null) throw new OutOfMemoryException(); @@ -851,18 +844,12 @@ static T[] AllocateNewUninitializedArray(int length, bool pinned) /// Specifies the type of the array element. /// Specifies the length of the array. /// Specifies whether the allocated array must be pinned. - /// - /// If pinned is set to true, must not be a reference type or a type that contains object references. - /// public static unsafe T[] AllocateArray(int length, bool pinned = false) { GC_ALLOC_FLAGS flags = GC_ALLOC_FLAGS.GC_ALLOC_NO_FLAGS; if (pinned) { - if (RuntimeHelpers.IsReferenceOrContainsReferences()) - ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(T)); - flags = GC_ALLOC_FLAGS.GC_ALLOC_PINNED_OBJECT_HEAP; } @@ -870,7 +857,7 @@ public static unsafe T[] AllocateArray(int length, bool pinned = false) throw new OverflowException(); T[]? array = null; - RuntimeImports.RhAllocateNewArray(EETypePtr.EETypePtrOf().RawValue, (uint)length, (uint)flags, Unsafe.AsPointer(ref array)); + RuntimeImports.RhAllocateNewArray(MethodTable.Of(), (uint)length, (uint)flags, Unsafe.AsPointer(ref array)); if (array == null) throw new OutOfMemoryException(); diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs index 6e903753f1a47f..305c5db303df93 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs @@ -239,7 +239,7 @@ internal struct GCHeapHardLimitInfo internal static extern void RhGetMemoryInfo(ref byte info, GCKind kind); [LibraryImport(RuntimeLibrary)] - internal static unsafe partial void RhAllocateNewArray(IntPtr pArrayEEType, uint numElements, uint flags, void* pResult); + internal static unsafe partial void RhAllocateNewArray(MethodTable* pArrayEEType, uint numElements, uint flags, void* pResult); [LibraryImport(RuntimeLibrary)] internal static unsafe partial void RhAllocateNewObject(IntPtr pEEType, uint flags, void* pResult); diff --git a/src/libraries/System.Runtime/tests/System/GCTests.cs b/src/libraries/System.Runtime/tests/System/GCTests.cs index e59aea9ccdd1fb..d161b3bbdde5bb 100644 --- a/src/libraries/System.Runtime/tests/System/GCTests.cs +++ b/src/libraries/System.Runtime/tests/System/GCTests.cs @@ -1058,11 +1058,60 @@ private static void AllocateArrayTooLarge() Assert.Throws(() => GC.AllocateUninitializedArray(int.MaxValue, pinned: true)); } - [Fact] - private static void AllocateArrayRefType() + [StructLayout(LayoutKind.Sequential)] + struct EmbeddedValueType + { + // There's a few extra fields here to ensure any reads and writes to Value reasonably are not just offset 0. + // This is a low-level smoke check that can give a bit of confidence in pointer arithmetic. + // The CLR is permitted to reorder fields and ignore the sequential consistency of value types if they contain + // managed references, but it will never hurt the test. + object _1; + byte _2; + public T Value; + int _3; + string _4; + } + + [Theory] + [InlineData(false), InlineData(true)] + private static void AllocateArray_UninitializedOrNot_WithManagedType_DoesNotThrow(bool pinned) + { + void TryType() + { + GC.AllocateUninitializedArray(100, pinned); + GC.AllocateArray(100, pinned); + + GC.AllocateArray>(100, pinned); + GC.AllocateUninitializedArray>(100, pinned); + } + + TryType(); + TryType(); + } + + [Theory] + [InlineData(false), InlineData(true)] + private unsafe static void AllocateArrayPinned_ManagedValueType_CanRoundtripThroughPointer(bool uninitialized) { - GC.AllocateUninitializedArray(100); - Assert.Throws(() => GC.AllocateUninitializedArray(100, pinned: true)); + const int length = 100; + var rng = new Random(0xAF); + + EmbeddedValueType[] array = uninitialized ? GC.AllocateUninitializedArray>(length, pinned: true) : GC.AllocateArray>(length, pinned: true); + byte* pointer = (byte*)Unsafe.AsPointer(ref array[0]); + var size = Unsafe.SizeOf>(); + + for(int i = 0; i < length; ++i) + { + int idx = rng.Next(length); + ref EmbeddedValueType evt = ref Unsafe.AsRef>(pointer + size * idx); + + string stringValue = rng.NextSingle().ToString(); + evt.Value = stringValue; + + Assert.Equal(evt.Value, array[idx].Value); + } + + GC.KeepAlive(array); } [Fact] diff --git a/src/mono/System.Private.CoreLib/src/System/GC.Mono.cs b/src/mono/System.Private.CoreLib/src/System/GC.Mono.cs index c87e0ee05c0f92..6d094d9bf9785d 100644 --- a/src/mono/System.Private.CoreLib/src/System/GC.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/GC.Mono.cs @@ -281,8 +281,6 @@ public static T[] AllocateUninitializedArray(int length, bool pinned = false) public static T[] AllocateArray(int length, bool pinned = false) { if (pinned) { - if (RuntimeHelpers.IsReferenceOrContainsReferences()) - ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(T)); return Unsafe.As(AllocPinnedArray(typeof(T[]), length)); }