Skip to content

Commit 2ee0646

Browse files
committed
Adding support for aligning AllocateTypeAssociatedMemory
1 parent 3871ff9 commit 2ee0646

File tree

8 files changed

+161
-14
lines changed

8 files changed

+161
-14
lines changed

src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Buffers.Binary;
55
using System.Diagnostics;
66
using System.Diagnostics.CodeAnalysis;
7+
using System.Numerics;
78
using System.Reflection;
89
using System.Runtime.InteropServices;
910
using System.Runtime.Serialization;
@@ -467,26 +468,56 @@ internal static unsafe bool ObjectHasComponentSize(object obj)
467468
[return: MarshalAs(UnmanagedType.Bool)]
468469
internal static unsafe partial bool AreTypesEquivalent(MethodTable* pMTa, MethodTable* pMTb);
469470

470-
/// <summary>
471-
/// Allocate memory that is associated with the <paramref name="type"/> and
472-
/// will be freed if and when the <see cref="Type"/> is unloaded.
473-
/// </summary>
474-
/// <param name="type">Type associated with the allocated memory.</param>
475-
/// <param name="size">Amount of memory in bytes to allocate.</param>
476-
/// <returns>The allocated memory</returns>
471+
/// <summary>Allocates memory that's associated with the <paramref name="type" /> and is freed if and when the <see cref="Type" /> is unloaded.</summary>
472+
/// <param name="type">The type associated with the allocated memory.</param>
473+
/// <param name="size">The amount of memory to allocate, in bytes.</param>
474+
/// <returns>The allocated memory.</returns>
475+
/// <exception cref="ArgumentException"><paramref name="type" /> must be a type provided by the runtime.</exception>
476+
/// <exception cref="ArgumentOutOfRangeException"><paramref name="size" /> is negative.</exception>
477477
public static IntPtr AllocateTypeAssociatedMemory(Type type, int size)
478478
{
479479
if (type is not RuntimeType rt)
480+
{
480481
throw new ArgumentException(SR.Arg_MustBeType, nameof(type));
482+
}
481483

482484
ArgumentOutOfRangeException.ThrowIfNegative(size);
483485

484486
return AllocateTypeAssociatedMemory(new QCallTypeHandle(ref rt), (uint)size);
485487
}
486488

489+
/// <summary>Allocates aligned memory that's associated with the <paramref name="type" /> and is freed if and when the <see cref="Type" /> is unloaded.</summary>
490+
/// <param name="type">The type associated with the allocated memory.</param>
491+
/// <param name="size">The amount of memory to allocate, in bytes.</param>
492+
/// <param name="alignment">The alignment, in bytes, of the memory to allocate. This must be a power of <c>2</c>.</param>
493+
/// <returns>The allocated aligned memory.</returns>
494+
/// <exception cref="ArgumentException"><paramref name="type" /> must be a type provided by the runtime.</exception>
495+
/// <exception cref="ArgumentOutOfRangeException"><paramref name="size" /> is negative.</exception>
496+
/// <exception cref="ArgumentException"><paramref name="alignment" /> is not a power of <c>2</c>.</exception>
497+
public static IntPtr AllocateTypeAssociatedMemory(Type type, int size, int alignment)
498+
{
499+
if (type is not RuntimeType rt)
500+
{
501+
throw new ArgumentException(SR.Arg_MustBeType, nameof(type));
502+
}
503+
504+
ArgumentOutOfRangeException.ThrowIfNegative(size);
505+
506+
if (!BitOperations.IsPow2(alignment))
507+
{
508+
// The C standard doesn't define what a valid alignment is, however Windows and POSIX implementation requires a power of 2
509+
ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_AlignmentMustBePow2);
510+
}
511+
512+
return AlignedAllocateTypeAssociatedMemory(new QCallTypeHandle(ref rt), (uint)size, (uint)alignment);
513+
}
514+
487515
[LibraryImport(QCall, EntryPoint = "RuntimeTypeHandle_AllocateTypeAssociatedMemory")]
488516
private static partial IntPtr AllocateTypeAssociatedMemory(QCallTypeHandle type, uint size);
489517

518+
[LibraryImport(QCall, EntryPoint = "RuntimeTypeHandle_AlignedAllocateTypeAssociatedMemory")]
519+
private static partial IntPtr AlignedAllocateTypeAssociatedMemory(QCallTypeHandle type, uint size, uint alignment);
520+
490521
[MethodImpl(MethodImplOptions.InternalCall)]
491522
private static extern unsafe TailCallArgBuffer* GetTailCallArgBuffer();
492523

src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.NativeAot.cs

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -224,24 +224,54 @@ public static void PrepareMethod(RuntimeMethodHandle method, RuntimeTypeHandle[]
224224
throw new ArgumentException(SR.InvalidOperation_HandleIsNotInitialized, nameof(method));
225225
}
226226

227-
/// <summary>
228-
/// Allocate memory that is associated with the <paramref name="type"/> and
229-
/// will be freed if and when the <see cref="System.Type"/> is unloaded.
230-
/// </summary>
231-
/// <param name="type">Type associated with the allocated memory.</param>
232-
/// <param name="size">Amount of memory in bytes to allocate.</param>
233-
/// <returns>The allocated memory</returns>
227+
/// <summary>Allocates memory that's associated with the <paramref name="type" /> and is freed if and when the <see cref="Type" /> is unloaded.</summary>
228+
/// <param name="type">The type associated with the allocated memory.</param>
229+
/// <param name="size">The amount of memory to allocate, in bytes.</param>
230+
/// <returns>The allocated memory.</returns>
231+
/// <exception cref="ArgumentException"><paramref name="type" /> must be a type provided by the runtime.</exception>
232+
/// <exception cref="ArgumentOutOfRangeException"><paramref name="size" /> is negative.</exception>
234233
public static unsafe IntPtr AllocateTypeAssociatedMemory(Type type, int size)
235234
{
236235
if (type is not RuntimeType)
236+
{
237237
throw new ArgumentException(SR.Arg_MustBeType, nameof(type));
238+
}
238239

239240
ArgumentOutOfRangeException.ThrowIfNegative(size);
240241

241242
// We don't support unloading; the memory will never be freed.
242243
return (IntPtr)NativeMemory.AllocZeroed((uint)size);
243244
}
244245

246+
/// <summary>Allocates aligned memory that's associated with the <paramref name="type" /> and is freed if and when the <see cref="Type" /> is unloaded.</summary>
247+
/// <param name="type">The type associated with the allocated memory.</param>
248+
/// <param name="size">The amount of memory to allocate, in bytes.</param>
249+
/// <param name="alignment">The alignment, in bytes, of the memory to allocate. This must be a power of <c>2</c>.</param>
250+
/// <returns>The allocated aligned memory.</returns>
251+
/// <exception cref="ArgumentException"><paramref name="type" /> must be a type provided by the runtime.</exception>
252+
/// <exception cref="ArgumentOutOfRangeException"><paramref name="size" /> is negative.</exception>
253+
/// <exception cref="ArgumentException"><paramref name="alignment" /> is not a power of <c>2</c>.</exception>
254+
public static IntPtr AllocateTypeAssociatedMemory(Type type, int size, int alignment)
255+
{
256+
if (type is not RuntimeType rt)
257+
{
258+
throw new ArgumentException(SR.Arg_MustBeType, nameof(type));
259+
}
260+
261+
ArgumentOutOfRangeException.ThrowIfNegative(size);
262+
263+
if (!BitOperations.IsPow2(alignment))
264+
{
265+
// The C standard doesn't define what a valid alignment is, however Windows and POSIX implementation requires a power of 2
266+
ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_AlignmentMustBePow2);
267+
}
268+
269+
// We don't support unloading; the memory will never be freed.
270+
void* result = NativeMemory.AlignedAlloc((uint)size, (uint)alignment);
271+
NativeMemory.Clear(result, (uint)size);
272+
return (IntPtr)result;
273+
}
274+
245275
public static void PrepareDelegate(Delegate d)
246276
{
247277
}

src/coreclr/vm/qcallentrypoints.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ static const Entry s_QCall[] =
157157
DllImportEntry(RuntimeTypeHandle_CreateInstanceForAnotherGenericParameter)
158158
DllImportEntry(RuntimeTypeHandle_InternalAlloc)
159159
DllImportEntry(RuntimeTypeHandle_InternalAllocNoChecks)
160+
DllImportEntry(RuntimeTypeHandle_AlignedAllocateTypeAssociatedMemory)
160161
DllImportEntry(RuntimeTypeHandle_AllocateTypeAssociatedMemory)
161162
DllImportEntry(RuntimeTypeHandle_RegisterCollectibleTypeDependency)
162163
DllImportEntry(MethodBase_GetCurrentMethod)

src/coreclr/vm/runtimehandles.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1210,6 +1210,32 @@ FCIMPL1(FC_BOOL_RET, RuntimeTypeHandle::ContainsGenericVariables, PTR_ReflectCla
12101210
}
12111211
FCIMPLEND
12121212

1213+
extern "C" void* QCALLTYPE RuntimeTypeHandle_AlignedAllocateTypeAssociatedMemory(QCall::TypeHandle type, uint32_t size, uint32_t alignment)
1214+
{
1215+
QCALL_CONTRACT;
1216+
1217+
void *allocatedMemory = nullptr;
1218+
1219+
BEGIN_QCALL;
1220+
1221+
TypeHandle typeHandle = type.AsTypeHandle();
1222+
_ASSERTE(!typeHandle.IsNull());
1223+
1224+
_ASSERTE(alignment != 0);
1225+
_ASSERTE(0 == (alignment & (alignment - 1))); // require power of 2
1226+
1227+
// Get the loader allocator for the associated type.
1228+
// Allocating using the type's associated loader allocator means
1229+
// that the memory will be freed when the type is unloaded.
1230+
PTR_LoaderAllocator loaderAllocator = typeHandle.GetMethodTable()->GetLoaderAllocator();
1231+
LoaderHeap* loaderHeap = loaderAllocator->GetHighFrequencyHeap();
1232+
allocatedMemory = loaderHeap->AllocAlignedMem(S_SIZE_T(size), S_SIZE_T(alignment));
1233+
1234+
END_QCALL;
1235+
1236+
return allocatedMemory;
1237+
}
1238+
12131239
extern "C" void* QCALLTYPE RuntimeTypeHandle_AllocateTypeAssociatedMemory(QCall::TypeHandle type, uint32_t size)
12141240
{
12151241
QCALL_CONTRACT;

src/coreclr/vm/runtimehandles.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ extern "C" void QCALLTYPE RuntimeTypeHandle_CreateInstanceForAnotherGenericParam
140140
extern "C" void QCALLTYPE RuntimeTypeHandle_InternalAlloc(MethodTable* pMT, QCall::ObjectHandleOnStack allocated);
141141

142142
extern "C" void QCALLTYPE RuntimeTypeHandle_InternalAllocNoChecks(MethodTable* pMT, QCall::ObjectHandleOnStack allocated);
143+
extern "C" void* QCALLTYPE RuntimeTypeHandle_AlignedAllocateTypeAssociatedMemory(QCall::TypeHandle type, uint32_t size, uint32_t alignment);
143144
extern "C" void* QCALLTYPE RuntimeTypeHandle_AllocateTypeAssociatedMemory(QCall::TypeHandle type, uint32_t size);
144145

145146
extern "C" PVOID QCALLTYPE QCall_GetGCHandleForTypeHandle(QCall::TypeHandle pTypeHandle, INT32 handleType);

src/libraries/System.Runtime/ref/System.Runtime.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14117,6 +14117,7 @@ public static partial class RuntimeHelpers
1411714117
[System.ObsoleteAttribute("OffsetToStringData has been deprecated. Use string.GetPinnableReference() instead.")]
1411814118
public static int OffsetToStringData { get { throw null; } }
1411914119
public static System.IntPtr AllocateTypeAssociatedMemory(System.Type type, int size) { throw null; }
14120+
public static System.IntPtr AllocateTypeAssociatedMemory(System.Type type, int size, int alignment) { throw null; }
1412014121
public static object? Box(ref byte target, System.RuntimeTypeHandle type) { throw null; }
1412114122
public static System.ReadOnlySpan<T> CreateSpan<T>(System.RuntimeFieldHandle fldHandle) { throw null; }
1412214123
public static void EnsureSufficientExecutionStack() { }

src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Runtime/CompilerServices/RuntimeHelpersTests.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,7 @@ public static void AllocateTypeAssociatedMemoryInvalidArguments()
442442
Assert.Throws<ArgumentOutOfRangeException>(() => { RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(RuntimeHelpersTests), -1); });
443443
}
444444

445+
445446
[Fact]
446447
public static unsafe void AllocateTypeAssociatedMemoryValidArguments()
447448
{
@@ -451,6 +452,25 @@ public static unsafe void AllocateTypeAssociatedMemoryValidArguments()
451452
Assert.True(new Span<byte>((void*)memory, 32).SequenceEqual(new byte[32]));
452453
}
453454

455+
[Fact]
456+
public static void AlignedAllocateTypeAssociatedMemoryInvalidArguments()
457+
{
458+
Assert.Throws<ArgumentException>(() => { RuntimeHelpers.AllocateTypeAssociatedMemory(null, 10, 1); });
459+
Assert.Throws<ArgumentOutOfRangeException>(() => { RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(RuntimeHelpersTests), -1, 1); });
460+
Assert.Throws<ArgumentException>(() => { RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(RuntimeHelpersTests), -1, 0); });
461+
Assert.Throws<ArgumentException>(() => { RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(RuntimeHelpersTests), -1, 3); });
462+
}
463+
464+
465+
[Fact]
466+
public static unsafe void AlignedAllocateTypeAssociatedMemoryValidArguments()
467+
{
468+
IntPtr memory = RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(RuntimeHelpersTests), 32, 16);
469+
Assert.NotEqual(memory, IntPtr.Zero);
470+
// Validate that the memory is zeroed out
471+
Assert.True(new Span<byte>((void*)memory, 32).SequenceEqual(new byte[32]));
472+
}
473+
454474
#pragma warning disable CS0649
455475
[StructLayoutAttribute(LayoutKind.Sequential)]
456476
private struct StructWithoutReferences

src/mono/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,17 +139,54 @@ public static void RunModuleConstructor(ModuleHandle module)
139139
RunModuleConstructor(module.Value);
140140
}
141141

142+
/// <summary>Allocates memory that's associated with the <paramref name="type" /> and is freed if and when the <see cref="Type" /> is unloaded.</summary>
143+
/// <param name="type">The type associated with the allocated memory.</param>
144+
/// <param name="size">The amount of memory to allocate, in bytes.</param>
145+
/// <returns>The allocated memory.</returns>
146+
/// <exception cref="ArgumentException"><paramref name="type" /> must be a type provided by the runtime.</exception>
147+
/// <exception cref="ArgumentOutOfRangeException"><paramref name="size" /> is negative.</exception>
142148
public static unsafe IntPtr AllocateTypeAssociatedMemory(Type type, int size)
143149
{
144150
if (type is not RuntimeType)
151+
{
145152
throw new ArgumentException(SR.Arg_MustBeType, nameof(type));
153+
}
146154

147155
ArgumentOutOfRangeException.ThrowIfNegative(size);
148156

149157
// We don't support unloading; the memory will never be freed.
150158
return (IntPtr)NativeMemory.AllocZeroed((uint)size);
151159
}
152160

161+
/// <summary>Allocates aligned memory that's associated with the <paramref name="type" /> and is freed if and when the <see cref="Type" /> is unloaded.</summary>
162+
/// <param name="type">The type associated with the allocated memory.</param>
163+
/// <param name="size">The amount of memory to allocate, in bytes.</param>
164+
/// <param name="alignment">The alignment, in bytes, of the memory to allocate. This must be a power of <c>2</c>.</param>
165+
/// <returns>The allocated aligned memory.</returns>
166+
/// <exception cref="ArgumentException"><paramref name="type" /> must be a type provided by the runtime.</exception>
167+
/// <exception cref="ArgumentOutOfRangeException"><paramref name="size" /> is negative.</exception>
168+
/// <exception cref="ArgumentException"><paramref name="alignment" /> is not a power of <c>2</c>.</exception>
169+
public static IntPtr AllocateTypeAssociatedMemory(Type type, int size, int alignment)
170+
{
171+
if (type is not RuntimeType rt)
172+
{
173+
throw new ArgumentException(SR.Arg_MustBeType, nameof(type));
174+
}
175+
176+
ArgumentOutOfRangeException.ThrowIfNegative(size);
177+
178+
if (!BitOperations.IsPow2(alignment))
179+
{
180+
// The C standard doesn't define what a valid alignment is, however Windows and POSIX implementation requires a power of 2
181+
ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_AlignmentMustBePow2);
182+
}
183+
184+
// We don't support unloading; the memory will never be freed.
185+
void* result = NativeMemory.AlignedAlloc((uint)size, (uint)alignment);
186+
NativeMemory.Clear(result, (uint)size);
187+
return (IntPtr)result;
188+
}
189+
153190
[Intrinsic]
154191
internal static ref byte GetRawData(this object obj) => ref obj.GetRawData();
155192

0 commit comments

Comments
 (0)