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