Skip to content

Commit c0ddd1c

Browse files
authored
Adding public API for Pinned Object Heap allocations (#33526)
* Adding API for POH allocations and propagating flags all the way to Alloc. * make `AllocateUninitializedArray` and `AllocateArray` public * Added NYI implementations to Mono * moved tests to libraries * Actually use POH and more tests. * Disable tests for the new API on mono * mop up remaining TODOs * Fix build breaking whitespace. * Mono tabs and mark heavier tests as [Outerloop] * Mono space before openning parens and braces * Refactored AllocateArray * PR feedback * XML Doc comments
1 parent eb2477c commit c0ddd1c

File tree

18 files changed

+406
-478
lines changed

18 files changed

+406
-478
lines changed

src/coreclr/src/System.Private.CoreLib/ILLinkTrim.xml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@
88
<!-- Methods are used to register and unregister frozen segments. They are private and experimental. -->
99
<method name="_RegisterFrozenSegment" />
1010
<method name="_UnregisterFrozenSegment" />
11-
<!-- This is an internal API for now and is not yet used outside tests. -->
12-
<method name="AllocateUninitializedArray" />
1311
</type>
1412
<!-- Properties and methods used by a debugger. -->
1513
<type fullname="System.Threading.Tasks.Task">

src/coreclr/src/System.Private.CoreLib/src/System/GC.cs

Lines changed: 66 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,16 @@ public static GCMemoryInfo GetGCMemoryInfo()
8484
[DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
8585
internal static extern int _EndNoGCRegion();
8686

87+
// keep in sync with GC_ALLOC_FLAGS in gcinterface.h
88+
internal enum GC_ALLOC_FLAGS
89+
{
90+
GC_ALLOC_NO_FLAGS = 0,
91+
GC_ALLOC_ZEROING_OPTIONAL = 16,
92+
GC_ALLOC_PINNED_OBJECT_HEAP = 64,
93+
};
94+
8795
[MethodImpl(MethodImplOptions.InternalCall)]
88-
internal static extern Array AllocateNewArray(IntPtr typeHandle, int length, bool zeroingOptional);
96+
internal static extern Array AllocateNewArray(IntPtr typeHandle, int length, GC_ALLOC_FLAGS flags);
8997

9098
[MethodImpl(MethodImplOptions.InternalCall)]
9199
private static extern int GetGenerationWR(IntPtr handle);
@@ -651,31 +659,74 @@ internal static void UnregisterMemoryLoadChangeNotification(Action notification)
651659
}
652660

653661
/// <summary>
654-
/// Skips zero-initialization of the array if possible.
655-
/// If T contains object references, the array is always zero-initialized.
662+
/// Allocate an array while skipping zero-initialization if possible.
656663
/// </summary>
664+
/// <typeparam name="T">Specifies the type of the array element.</typeparam>
665+
/// <param name="length">Specifies the length of the array.</param>
666+
/// <param name="pinned">Specifies whether the allocated array must be pinned.</param>
667+
/// <remarks>
668+
/// If pinned is set to true, <typeparamref name="T"/> must not be a reference type or a type that contains object references.
669+
/// </remarks>
657670
[MethodImpl(MethodImplOptions.AggressiveInlining)] // forced to ensure no perf drop for small memory buffers (hot path)
658-
internal static T[] AllocateUninitializedArray<T>(int length)
671+
public static T[] AllocateUninitializedArray<T>(int length, bool pinned = false)
659672
{
660-
if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
673+
if (!pinned)
661674
{
662-
return new T[length];
663-
}
675+
if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
676+
{
677+
return new T[length];
678+
}
664679

665-
// for debug builds we always want to call AllocateNewArray to detect AllocateNewArray bugs
680+
// for debug builds we always want to call AllocateNewArray to detect AllocateNewArray bugs
666681
#if !DEBUG
667-
// small arrays are allocated using `new[]` as that is generally faster.
668-
if (length < 2048 / Unsafe.SizeOf<T>())
682+
// small arrays are allocated using `new[]` as that is generally faster.
683+
if (length < 2048 / Unsafe.SizeOf<T>())
684+
{
685+
return new T[length];
686+
}
687+
#endif
688+
}
689+
else if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
669690
{
670-
return new T[length];
691+
ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(T));
671692
}
672-
#endif
693+
673694
// kept outside of the small arrays hot path to have inlining without big size growth
674-
return AllocateNewUninitializedArray(length);
695+
return AllocateNewUninitializedArray(length, pinned);
675696

676697
// remove the local function when https://github.com/dotnet/coreclr/issues/5329 is implemented
677-
static T[] AllocateNewUninitializedArray(int length)
678-
=> Unsafe.As<T[]>(AllocateNewArray(typeof(T[]).TypeHandle.Value, length, zeroingOptional: true));
698+
static T[] AllocateNewUninitializedArray(int length, bool pinned)
699+
{
700+
GC_ALLOC_FLAGS flags = GC_ALLOC_FLAGS.GC_ALLOC_ZEROING_OPTIONAL;
701+
if (pinned)
702+
flags |= GC_ALLOC_FLAGS.GC_ALLOC_PINNED_OBJECT_HEAP;
703+
704+
return Unsafe.As<T[]>(AllocateNewArray(typeof(T[]).TypeHandle.Value, length, flags));
705+
}
706+
}
707+
708+
/// <summary>
709+
/// Allocate an array.
710+
/// </summary>
711+
/// <typeparam name="T">Specifies the type of the array element.</typeparam>
712+
/// <param name="length">Specifies the length of the array.</param>
713+
/// <param name="pinned">Specifies whether the allocated array must be pinned.</param>
714+
/// <remarks>
715+
/// If pinned is set to true, <typeparamref name="T"/> must not be a reference type or a type that contains object references.
716+
/// </remarks>
717+
public static T[] AllocateArray<T>(int length, bool pinned = false)
718+
{
719+
GC_ALLOC_FLAGS flags = GC_ALLOC_FLAGS.GC_ALLOC_NO_FLAGS;
720+
721+
if (pinned)
722+
{
723+
if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
724+
ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(T));
725+
726+
flags = GC_ALLOC_FLAGS.GC_ALLOC_PINNED_OBJECT_HEAP;
727+
}
728+
729+
return Unsafe.As<T[]>(AllocateNewArray(typeof(T[]).TypeHandle.Value, length, flags));
679730
}
680731
}
681732
}

src/coreclr/src/gc/gc.cpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12198,7 +12198,7 @@ void gc_heap::adjust_limit_clr (uint8_t* start, size_t limit_size, size_t size,
1219812198
uint8_t* obj_start = acontext->alloc_ptr;
1219912199
assert(start >= obj_start);
1220012200
uint8_t* obj_end = obj_start + size - plug_skew;
12201-
assert(obj_end > clear_start);
12201+
assert(obj_end >= clear_start);
1220212202

1220312203
// if clearing at the object start, clear the syncblock.
1220412204
if(obj_start == start)
@@ -37204,7 +37204,9 @@ GCHeap::Alloc(gc_alloc_context* context, size_t size, uint32_t flags REQD_ALIGN_
3720437204
#endif //_PREFAST_
3720537205
#endif //MULTIPLE_HEAPS
3720637206

37207-
if (size >= loh_size_threshold || (flags & GC_ALLOC_LARGE_OBJECT_HEAP))
37207+
assert(size < loh_size_threshold || (flags & GC_ALLOC_LARGE_OBJECT_HEAP));
37208+
37209+
if (flags & GC_ALLOC_USER_OLD_HEAP)
3720837210
{
3720937211
// The LOH always guarantees at least 8-byte alignment, regardless of platform. Moreover it doesn't
3721037212
// support mis-aligned object headers so we can't support biased headers. Luckily for us
@@ -37213,7 +37215,8 @@ GCHeap::Alloc(gc_alloc_context* context, size_t size, uint32_t flags REQD_ALIGN_
3721337215
ASSERT((flags & GC_ALLOC_ALIGN8_BIAS) == 0);
3721437216
ASSERT(65536 < loh_size_threshold);
3721537217

37216-
newAlloc = (Object*) hp->allocate_uoh_object (size + ComputeMaxStructAlignPadLarge(requiredAlignment), flags, loh_generation, acontext->alloc_bytes_uoh);
37218+
int gen_num = (flags & GC_ALLOC_PINNED_OBJECT_HEAP) ? poh_generation : loh_generation;
37219+
newAlloc = (Object*) hp->allocate_uoh_object (size + ComputeMaxStructAlignPadLarge(requiredAlignment), flags, gen_num, acontext->alloc_bytes_uoh);
3721737220
ASSERT(((size_t)newAlloc & 7) == 0);
3721837221

3721937222
#ifdef FEATURE_STRUCTALIGN

src/coreclr/src/gc/gcinterface.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -890,7 +890,7 @@ void updateGCShadow(Object** ptr, Object* val);
890890
#define GC_CALL_INTERIOR 0x1
891891
#define GC_CALL_PINNED 0x2
892892

893-
//flags for IGCHeapAlloc(...)
893+
// keep in sync with GC_ALLOC_FLAGS in GC.cs
894894
enum GC_ALLOC_FLAGS
895895
{
896896
GC_ALLOC_NO_FLAGS = 0,
@@ -901,6 +901,7 @@ enum GC_ALLOC_FLAGS
901901
GC_ALLOC_ZEROING_OPTIONAL = 16,
902902
GC_ALLOC_LARGE_OBJECT_HEAP = 32,
903903
GC_ALLOC_PINNED_OBJECT_HEAP = 64,
904+
GC_ALLOC_USER_OLD_HEAP = GC_ALLOC_LARGE_OBJECT_HEAP | GC_ALLOC_PINNED_OBJECT_HEAP,
904905
};
905906

906907
inline GC_ALLOC_FLAGS operator|(GC_ALLOC_FLAGS a, GC_ALLOC_FLAGS b)

src/coreclr/src/vm/comutilnative.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1047,7 +1047,7 @@ FCIMPLEND
10471047
** zeroingOptional -> whether caller prefers to skip clearing the content of the array, if possible.
10481048
**Exceptions: IDS_EE_ARRAY_DIMENSIONS_EXCEEDED when size is too large. OOM if can't allocate.
10491049
==============================================================================*/
1050-
FCIMPL3(Object*, GCInterface::AllocateNewArray, void* arrayTypeHandle, INT32 length, CLR_BOOL zeroingOptional)
1050+
FCIMPL3(Object*, GCInterface::AllocateNewArray, void* arrayTypeHandle, INT32 length, INT32 flags)
10511051
{
10521052
CONTRACTL {
10531053
FCALL_CHECK;
@@ -1058,7 +1058,10 @@ FCIMPL3(Object*, GCInterface::AllocateNewArray, void* arrayTypeHandle, INT32 len
10581058

10591059
HELPER_METHOD_FRAME_BEGIN_RET_0();
10601060

1061-
pRet = AllocateSzArray(arrayType, length, zeroingOptional ? GC_ALLOC_ZEROING_OPTIONAL : GC_ALLOC_NO_FLAGS);
1061+
//Only the following flags are used by GC.cs, so we'll just assert it here.
1062+
_ASSERTE((flags & ~(GC_ALLOC_ZEROING_OPTIONAL | GC_ALLOC_PINNED_OBJECT_HEAP)) == 0);
1063+
1064+
pRet = AllocateSzArray(arrayType, length, (GC_ALLOC_FLAGS)flags);
10621065

10631066
HELPER_METHOD_FRAME_END();
10641067

src/coreclr/src/vm/comutilnative.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ class GCInterface {
128128
static FCDECL0(INT64, GetAllocatedBytesForCurrentThread);
129129
static FCDECL1(INT64, GetTotalAllocatedBytes, CLR_BOOL precise);
130130

131-
static FCDECL3(Object*, AllocateNewArray, void* elementTypeHandle, INT32 length, CLR_BOOL zeroingOptional);
131+
static FCDECL3(Object*, AllocateNewArray, void* elementTypeHandle, INT32 length, INT32 flags);
132132

133133
#ifdef FEATURE_BASICFREEZE
134134
static

0 commit comments

Comments
 (0)