From 2ee064687955db0fdd57eac91f2be15d3319e51c Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Tue, 2 Dec 2025 07:02:07 -0800 Subject: [PATCH 1/5] Adding support for aligning AllocateTypeAssociatedMemory --- .../RuntimeHelpers.CoreCLR.cs | 45 ++++++++++++++++--- .../RuntimeHelpers.NativeAot.cs | 44 +++++++++++++++--- src/coreclr/vm/qcallentrypoints.cpp | 1 + src/coreclr/vm/runtimehandles.cpp | 26 +++++++++++ src/coreclr/vm/runtimehandles.h | 1 + .../System.Runtime/ref/System.Runtime.cs | 1 + .../CompilerServices/RuntimeHelpersTests.cs | 20 +++++++++ .../CompilerServices/RuntimeHelpers.Mono.cs | 37 +++++++++++++++ 8 files changed, 161 insertions(+), 14 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs index 9fa1f1f004d9b5..6ee505b1eba79c 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs @@ -4,6 +4,7 @@ using System.Buffers.Binary; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Numerics; using System.Reflection; using System.Runtime.InteropServices; using System.Runtime.Serialization; @@ -467,26 +468,56 @@ internal static unsafe bool ObjectHasComponentSize(object obj) [return: MarshalAs(UnmanagedType.Bool)] internal static unsafe partial bool AreTypesEquivalent(MethodTable* pMTa, MethodTable* pMTb); - /// - /// Allocate memory that is associated with the and - /// will be freed if and when the is unloaded. - /// - /// Type associated with the allocated memory. - /// Amount of memory in bytes to allocate. - /// The allocated memory + /// Allocates memory that's associated with the and is freed if and when the is unloaded. + /// The type associated with the allocated memory. + /// The amount of memory to allocate, in bytes. + /// The allocated memory. + /// must be a type provided by the runtime. + /// is negative. public static IntPtr AllocateTypeAssociatedMemory(Type type, int size) { if (type is not RuntimeType rt) + { throw new ArgumentException(SR.Arg_MustBeType, nameof(type)); + } ArgumentOutOfRangeException.ThrowIfNegative(size); return AllocateTypeAssociatedMemory(new QCallTypeHandle(ref rt), (uint)size); } + /// Allocates aligned memory that's associated with the and is freed if and when the is unloaded. + /// The type associated with the allocated memory. + /// The amount of memory to allocate, in bytes. + /// The alignment, in bytes, of the memory to allocate. This must be a power of 2. + /// The allocated aligned memory. + /// must be a type provided by the runtime. + /// is negative. + /// is not a power of 2. + public static IntPtr AllocateTypeAssociatedMemory(Type type, int size, int alignment) + { + if (type is not RuntimeType rt) + { + throw new ArgumentException(SR.Arg_MustBeType, nameof(type)); + } + + ArgumentOutOfRangeException.ThrowIfNegative(size); + + if (!BitOperations.IsPow2(alignment)) + { + // The C standard doesn't define what a valid alignment is, however Windows and POSIX implementation requires a power of 2 + ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_AlignmentMustBePow2); + } + + return AlignedAllocateTypeAssociatedMemory(new QCallTypeHandle(ref rt), (uint)size, (uint)alignment); + } + [LibraryImport(QCall, EntryPoint = "RuntimeTypeHandle_AllocateTypeAssociatedMemory")] private static partial IntPtr AllocateTypeAssociatedMemory(QCallTypeHandle type, uint size); + [LibraryImport(QCall, EntryPoint = "RuntimeTypeHandle_AlignedAllocateTypeAssociatedMemory")] + private static partial IntPtr AlignedAllocateTypeAssociatedMemory(QCallTypeHandle type, uint size, uint alignment); + [MethodImpl(MethodImplOptions.InternalCall)] private static extern unsafe TailCallArgBuffer* GetTailCallArgBuffer(); diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.NativeAot.cs index 541f62969ab526..d1dbb04765864a 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.NativeAot.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.NativeAot.cs @@ -224,17 +224,18 @@ public static void PrepareMethod(RuntimeMethodHandle method, RuntimeTypeHandle[] throw new ArgumentException(SR.InvalidOperation_HandleIsNotInitialized, nameof(method)); } - /// - /// Allocate memory that is associated with the and - /// will be freed if and when the is unloaded. - /// - /// Type associated with the allocated memory. - /// Amount of memory in bytes to allocate. - /// The allocated memory + /// Allocates memory that's associated with the and is freed if and when the is unloaded. + /// The type associated with the allocated memory. + /// The amount of memory to allocate, in bytes. + /// The allocated memory. + /// must be a type provided by the runtime. + /// is negative. public static unsafe IntPtr AllocateTypeAssociatedMemory(Type type, int size) { if (type is not RuntimeType) + { throw new ArgumentException(SR.Arg_MustBeType, nameof(type)); + } ArgumentOutOfRangeException.ThrowIfNegative(size); @@ -242,6 +243,35 @@ public static unsafe IntPtr AllocateTypeAssociatedMemory(Type type, int size) return (IntPtr)NativeMemory.AllocZeroed((uint)size); } + /// Allocates aligned memory that's associated with the and is freed if and when the is unloaded. + /// The type associated with the allocated memory. + /// The amount of memory to allocate, in bytes. + /// The alignment, in bytes, of the memory to allocate. This must be a power of 2. + /// The allocated aligned memory. + /// must be a type provided by the runtime. + /// is negative. + /// is not a power of 2. + public static IntPtr AllocateTypeAssociatedMemory(Type type, int size, int alignment) + { + if (type is not RuntimeType rt) + { + throw new ArgumentException(SR.Arg_MustBeType, nameof(type)); + } + + ArgumentOutOfRangeException.ThrowIfNegative(size); + + if (!BitOperations.IsPow2(alignment)) + { + // The C standard doesn't define what a valid alignment is, however Windows and POSIX implementation requires a power of 2 + ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_AlignmentMustBePow2); + } + + // We don't support unloading; the memory will never be freed. + void* result = NativeMemory.AlignedAlloc((uint)size, (uint)alignment); + NativeMemory.Clear(result, (uint)size); + return (IntPtr)result; + } + public static void PrepareDelegate(Delegate d) { } diff --git a/src/coreclr/vm/qcallentrypoints.cpp b/src/coreclr/vm/qcallentrypoints.cpp index eeb41a5efa2b32..28378217ab5c41 100644 --- a/src/coreclr/vm/qcallentrypoints.cpp +++ b/src/coreclr/vm/qcallentrypoints.cpp @@ -157,6 +157,7 @@ static const Entry s_QCall[] = DllImportEntry(RuntimeTypeHandle_CreateInstanceForAnotherGenericParameter) DllImportEntry(RuntimeTypeHandle_InternalAlloc) DllImportEntry(RuntimeTypeHandle_InternalAllocNoChecks) + DllImportEntry(RuntimeTypeHandle_AlignedAllocateTypeAssociatedMemory) DllImportEntry(RuntimeTypeHandle_AllocateTypeAssociatedMemory) DllImportEntry(RuntimeTypeHandle_RegisterCollectibleTypeDependency) DllImportEntry(MethodBase_GetCurrentMethod) diff --git a/src/coreclr/vm/runtimehandles.cpp b/src/coreclr/vm/runtimehandles.cpp index a6e8b866011524..6a2512496d0007 100644 --- a/src/coreclr/vm/runtimehandles.cpp +++ b/src/coreclr/vm/runtimehandles.cpp @@ -1210,6 +1210,32 @@ FCIMPL1(FC_BOOL_RET, RuntimeTypeHandle::ContainsGenericVariables, PTR_ReflectCla } FCIMPLEND +extern "C" void* QCALLTYPE RuntimeTypeHandle_AlignedAllocateTypeAssociatedMemory(QCall::TypeHandle type, uint32_t size, uint32_t alignment) +{ + QCALL_CONTRACT; + + void *allocatedMemory = nullptr; + + BEGIN_QCALL; + + TypeHandle typeHandle = type.AsTypeHandle(); + _ASSERTE(!typeHandle.IsNull()); + + _ASSERTE(alignment != 0); + _ASSERTE(0 == (alignment & (alignment - 1))); // require power of 2 + + // Get the loader allocator for the associated type. + // Allocating using the type's associated loader allocator means + // that the memory will be freed when the type is unloaded. + PTR_LoaderAllocator loaderAllocator = typeHandle.GetMethodTable()->GetLoaderAllocator(); + LoaderHeap* loaderHeap = loaderAllocator->GetHighFrequencyHeap(); + allocatedMemory = loaderHeap->AllocAlignedMem(S_SIZE_T(size), S_SIZE_T(alignment)); + + END_QCALL; + + return allocatedMemory; +} + extern "C" void* QCALLTYPE RuntimeTypeHandle_AllocateTypeAssociatedMemory(QCall::TypeHandle type, uint32_t size) { QCALL_CONTRACT; diff --git a/src/coreclr/vm/runtimehandles.h b/src/coreclr/vm/runtimehandles.h index 7d155046577d96..822cedd5428769 100644 --- a/src/coreclr/vm/runtimehandles.h +++ b/src/coreclr/vm/runtimehandles.h @@ -140,6 +140,7 @@ extern "C" void QCALLTYPE RuntimeTypeHandle_CreateInstanceForAnotherGenericParam extern "C" void QCALLTYPE RuntimeTypeHandle_InternalAlloc(MethodTable* pMT, QCall::ObjectHandleOnStack allocated); extern "C" void QCALLTYPE RuntimeTypeHandle_InternalAllocNoChecks(MethodTable* pMT, QCall::ObjectHandleOnStack allocated); +extern "C" void* QCALLTYPE RuntimeTypeHandle_AlignedAllocateTypeAssociatedMemory(QCall::TypeHandle type, uint32_t size, uint32_t alignment); extern "C" void* QCALLTYPE RuntimeTypeHandle_AllocateTypeAssociatedMemory(QCall::TypeHandle type, uint32_t size); extern "C" PVOID QCALLTYPE QCall_GetGCHandleForTypeHandle(QCall::TypeHandle pTypeHandle, INT32 handleType); diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index ca5fe6da7a9d57..176b1a056f9630 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -14117,6 +14117,7 @@ public static partial class RuntimeHelpers [System.ObsoleteAttribute("OffsetToStringData has been deprecated. Use string.GetPinnableReference() instead.")] public static int OffsetToStringData { get { throw null; } } public static System.IntPtr AllocateTypeAssociatedMemory(System.Type type, int size) { throw null; } + public static System.IntPtr AllocateTypeAssociatedMemory(System.Type type, int size, int alignment) { throw null; } public static object? Box(ref byte target, System.RuntimeTypeHandle type) { throw null; } public static System.ReadOnlySpan CreateSpan(System.RuntimeFieldHandle fldHandle) { throw null; } public static void EnsureSufficientExecutionStack() { } diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Runtime/CompilerServices/RuntimeHelpersTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Runtime/CompilerServices/RuntimeHelpersTests.cs index 347c01b7a3c410..b43e99cdfa45d4 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Runtime/CompilerServices/RuntimeHelpersTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Runtime/CompilerServices/RuntimeHelpersTests.cs @@ -442,6 +442,7 @@ public static void AllocateTypeAssociatedMemoryInvalidArguments() Assert.Throws(() => { RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(RuntimeHelpersTests), -1); }); } + [Fact] public static unsafe void AllocateTypeAssociatedMemoryValidArguments() { @@ -451,6 +452,25 @@ public static unsafe void AllocateTypeAssociatedMemoryValidArguments() Assert.True(new Span((void*)memory, 32).SequenceEqual(new byte[32])); } + [Fact] + public static void AlignedAllocateTypeAssociatedMemoryInvalidArguments() + { + Assert.Throws(() => { RuntimeHelpers.AllocateTypeAssociatedMemory(null, 10, 1); }); + Assert.Throws(() => { RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(RuntimeHelpersTests), -1, 1); }); + Assert.Throws(() => { RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(RuntimeHelpersTests), -1, 0); }); + Assert.Throws(() => { RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(RuntimeHelpersTests), -1, 3); }); + } + + + [Fact] + public static unsafe void AlignedAllocateTypeAssociatedMemoryValidArguments() + { + IntPtr memory = RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(RuntimeHelpersTests), 32, 16); + Assert.NotEqual(memory, IntPtr.Zero); + // Validate that the memory is zeroed out + Assert.True(new Span((void*)memory, 32).SequenceEqual(new byte[32])); + } + #pragma warning disable CS0649 [StructLayoutAttribute(LayoutKind.Sequential)] private struct StructWithoutReferences diff --git a/src/mono/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs index 891e97640253c5..876d990decad29 100644 --- a/src/mono/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs @@ -139,10 +139,18 @@ public static void RunModuleConstructor(ModuleHandle module) RunModuleConstructor(module.Value); } + /// Allocates memory that's associated with the and is freed if and when the is unloaded. + /// The type associated with the allocated memory. + /// The amount of memory to allocate, in bytes. + /// The allocated memory. + /// must be a type provided by the runtime. + /// is negative. public static unsafe IntPtr AllocateTypeAssociatedMemory(Type type, int size) { if (type is not RuntimeType) + { throw new ArgumentException(SR.Arg_MustBeType, nameof(type)); + } ArgumentOutOfRangeException.ThrowIfNegative(size); @@ -150,6 +158,35 @@ public static unsafe IntPtr AllocateTypeAssociatedMemory(Type type, int size) return (IntPtr)NativeMemory.AllocZeroed((uint)size); } + /// Allocates aligned memory that's associated with the and is freed if and when the is unloaded. + /// The type associated with the allocated memory. + /// The amount of memory to allocate, in bytes. + /// The alignment, in bytes, of the memory to allocate. This must be a power of 2. + /// The allocated aligned memory. + /// must be a type provided by the runtime. + /// is negative. + /// is not a power of 2. + public static IntPtr AllocateTypeAssociatedMemory(Type type, int size, int alignment) + { + if (type is not RuntimeType rt) + { + throw new ArgumentException(SR.Arg_MustBeType, nameof(type)); + } + + ArgumentOutOfRangeException.ThrowIfNegative(size); + + if (!BitOperations.IsPow2(alignment)) + { + // The C standard doesn't define what a valid alignment is, however Windows and POSIX implementation requires a power of 2 + ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_AlignmentMustBePow2); + } + + // We don't support unloading; the memory will never be freed. + void* result = NativeMemory.AlignedAlloc((uint)size, (uint)alignment); + NativeMemory.Clear(result, (uint)size); + return (IntPtr)result; + } + [Intrinsic] internal static ref byte GetRawData(this object obj) => ref obj.GetRawData(); From 583446be714f4cfcddcf246409049fdee66f6df3 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Tue, 2 Dec 2025 08:13:34 -0800 Subject: [PATCH 2/5] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Runtime/CompilerServices/RuntimeHelpers.NativeAot.cs | 5 +++-- src/coreclr/vm/runtimehandles.cpp | 2 +- .../System/Runtime/CompilerServices/RuntimeHelpersTests.cs | 5 +++-- .../System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs | 5 +++-- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.NativeAot.cs index d1dbb04765864a..5d25312088a767 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.NativeAot.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.NativeAot.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics.CodeAnalysis; +using System.Numerics; using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Threading; @@ -251,9 +252,9 @@ public static unsafe IntPtr AllocateTypeAssociatedMemory(Type type, int size) /// must be a type provided by the runtime. /// is negative. /// is not a power of 2. - public static IntPtr AllocateTypeAssociatedMemory(Type type, int size, int alignment) + public static unsafe IntPtr AllocateTypeAssociatedMemory(Type type, int size, int alignment) { - if (type is not RuntimeType rt) + if (type is not RuntimeType) { throw new ArgumentException(SR.Arg_MustBeType, nameof(type)); } diff --git a/src/coreclr/vm/runtimehandles.cpp b/src/coreclr/vm/runtimehandles.cpp index 6a2512496d0007..c41e52050b61f2 100644 --- a/src/coreclr/vm/runtimehandles.cpp +++ b/src/coreclr/vm/runtimehandles.cpp @@ -1229,7 +1229,7 @@ extern "C" void* QCALLTYPE RuntimeTypeHandle_AlignedAllocateTypeAssociatedMemory // that the memory will be freed when the type is unloaded. PTR_LoaderAllocator loaderAllocator = typeHandle.GetMethodTable()->GetLoaderAllocator(); LoaderHeap* loaderHeap = loaderAllocator->GetHighFrequencyHeap(); - allocatedMemory = loaderHeap->AllocAlignedMem(S_SIZE_T(size), S_SIZE_T(alignment)); + allocatedMemory = loaderHeap->AllocAlignedMem(size, alignment); END_QCALL; diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Runtime/CompilerServices/RuntimeHelpersTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Runtime/CompilerServices/RuntimeHelpersTests.cs index b43e99cdfa45d4..25a99a632b9999 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Runtime/CompilerServices/RuntimeHelpersTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Runtime/CompilerServices/RuntimeHelpersTests.cs @@ -457,8 +457,8 @@ public static void AlignedAllocateTypeAssociatedMemoryInvalidArguments() { Assert.Throws(() => { RuntimeHelpers.AllocateTypeAssociatedMemory(null, 10, 1); }); Assert.Throws(() => { RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(RuntimeHelpersTests), -1, 1); }); - Assert.Throws(() => { RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(RuntimeHelpersTests), -1, 0); }); - Assert.Throws(() => { RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(RuntimeHelpersTests), -1, 3); }); + Assert.Throws(() => { RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(RuntimeHelpersTests), 10, 0); }); + Assert.Throws(() => { RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(RuntimeHelpersTests), 10, 3); }); } @@ -469,6 +469,7 @@ public static unsafe void AlignedAllocateTypeAssociatedMemoryValidArguments() Assert.NotEqual(memory, IntPtr.Zero); // Validate that the memory is zeroed out Assert.True(new Span((void*)memory, 32).SequenceEqual(new byte[32])); + Assert.True((memory % 16) == 0); } #pragma warning disable CS0649 diff --git a/src/mono/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs index 876d990decad29..c689cc5f954ddd 100644 --- a/src/mono/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Tracing; +using System.Numerics; using System.Runtime.InteropServices; using System.Runtime.Serialization; @@ -166,9 +167,9 @@ public static unsafe IntPtr AllocateTypeAssociatedMemory(Type type, int size) /// must be a type provided by the runtime. /// is negative. /// is not a power of 2. - public static IntPtr AllocateTypeAssociatedMemory(Type type, int size, int alignment) + public static unsafe IntPtr AllocateTypeAssociatedMemory(Type type, int size, int alignment) { - if (type is not RuntimeType rt) + if (type is not RuntimeType) { throw new ArgumentException(SR.Arg_MustBeType, nameof(type)); } From 2c85fe771c726d5347995710b5c8fc426ec813c8 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Thu, 4 Dec 2025 09:45:04 -0800 Subject: [PATCH 3/5] Resolve PR feedback --- .../RuntimeHelpers.CoreCLR.cs | 8 +++-- .../RuntimeHelpers.NativeAot.cs | 4 +++ src/coreclr/vm/runtimehandles.cpp | 14 ++++----- src/coreclr/vm/runtimehandles.h | 2 +- .../CompilerServices/RuntimeHelpersTests.cs | 29 ++++++++++++++----- .../CompilerServices/RuntimeHelpers.Mono.cs | 4 +++ 6 files changed, 43 insertions(+), 18 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs index 6ee505b1eba79c..55d57a48978975 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs @@ -494,6 +494,10 @@ public static IntPtr AllocateTypeAssociatedMemory(Type type, int size) /// must be a type provided by the runtime. /// is negative. /// is not a power of 2. + /// + /// This method is guaranteed to support any natural alignment, that is any alignment that is less than or equal to the size of a pointer. + /// Support for greater alignments is implementation dependent, but will always result in an if unsupported. + /// public static IntPtr AllocateTypeAssociatedMemory(Type type, int size, int alignment) { if (type is not RuntimeType rt) @@ -515,8 +519,8 @@ public static IntPtr AllocateTypeAssociatedMemory(Type type, int size, int align [LibraryImport(QCall, EntryPoint = "RuntimeTypeHandle_AllocateTypeAssociatedMemory")] private static partial IntPtr AllocateTypeAssociatedMemory(QCallTypeHandle type, uint size); - [LibraryImport(QCall, EntryPoint = "RuntimeTypeHandle_AlignedAllocateTypeAssociatedMemory")] - private static partial IntPtr AlignedAllocateTypeAssociatedMemory(QCallTypeHandle type, uint size, uint alignment); + [LibraryImport(QCall, EntryPoint = "RuntimeTypeHandle_AllocateTypeAssociatedMemoryAligned")] + private static partial IntPtr AllocateTypeAssociatedMemoryAligned(QCallTypeHandle type, uint size, uint alignment); [MethodImpl(MethodImplOptions.InternalCall)] private static extern unsafe TailCallArgBuffer* GetTailCallArgBuffer(); diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.NativeAot.cs index 5d25312088a767..ac9346e4808618 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.NativeAot.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.NativeAot.cs @@ -252,6 +252,10 @@ public static unsafe IntPtr AllocateTypeAssociatedMemory(Type type, int size) /// must be a type provided by the runtime. /// is negative. /// is not a power of 2. + /// + /// This method is guaranteed to support any natural alignment, that is any alignment that is less than or equal to the size of a pointer. + /// Support for greater alignments is implementation dependent, but will always result in an if unsupported. + /// public static unsafe IntPtr AllocateTypeAssociatedMemory(Type type, int size, int alignment) { if (type is not RuntimeType) diff --git a/src/coreclr/vm/runtimehandles.cpp b/src/coreclr/vm/runtimehandles.cpp index c41e52050b61f2..cd76192d29db70 100644 --- a/src/coreclr/vm/runtimehandles.cpp +++ b/src/coreclr/vm/runtimehandles.cpp @@ -1210,7 +1210,7 @@ FCIMPL1(FC_BOOL_RET, RuntimeTypeHandle::ContainsGenericVariables, PTR_ReflectCla } FCIMPLEND -extern "C" void* QCALLTYPE RuntimeTypeHandle_AlignedAllocateTypeAssociatedMemory(QCall::TypeHandle type, uint32_t size, uint32_t alignment) +extern "C" void* QCALLTYPE RuntimeTypeHandle_AllocateTypeAssociatedMemory(QCall::TypeHandle type, uint32_t size) { QCALL_CONTRACT; @@ -1221,22 +1221,19 @@ extern "C" void* QCALLTYPE RuntimeTypeHandle_AlignedAllocateTypeAssociatedMemory TypeHandle typeHandle = type.AsTypeHandle(); _ASSERTE(!typeHandle.IsNull()); - _ASSERTE(alignment != 0); - _ASSERTE(0 == (alignment & (alignment - 1))); // require power of 2 - // Get the loader allocator for the associated type. // Allocating using the type's associated loader allocator means // that the memory will be freed when the type is unloaded. PTR_LoaderAllocator loaderAllocator = typeHandle.GetMethodTable()->GetLoaderAllocator(); LoaderHeap* loaderHeap = loaderAllocator->GetHighFrequencyHeap(); - allocatedMemory = loaderHeap->AllocAlignedMem(size, alignment); + allocatedMemory = loaderHeap->AllocMem(S_SIZE_T(size)); END_QCALL; return allocatedMemory; } -extern "C" void* QCALLTYPE RuntimeTypeHandle_AllocateTypeAssociatedMemory(QCall::TypeHandle type, uint32_t size) +extern "C" void* QCALLTYPE RuntimeTypeHandle_AllocateTypeAssociatedMemoryAligned(QCall::TypeHandle type, uint32_t size, uint32_t alignment) { QCALL_CONTRACT; @@ -1247,12 +1244,15 @@ extern "C" void* QCALLTYPE RuntimeTypeHandle_AllocateTypeAssociatedMemory(QCall: TypeHandle typeHandle = type.AsTypeHandle(); _ASSERTE(!typeHandle.IsNull()); + _ASSERTE(alignment != 0); + _ASSERTE(0 == (alignment & (alignment - 1))); // require power of 2 + // Get the loader allocator for the associated type. // Allocating using the type's associated loader allocator means // that the memory will be freed when the type is unloaded. PTR_LoaderAllocator loaderAllocator = typeHandle.GetMethodTable()->GetLoaderAllocator(); LoaderHeap* loaderHeap = loaderAllocator->GetHighFrequencyHeap(); - allocatedMemory = loaderHeap->AllocMem(S_SIZE_T(size)); + allocatedMemory = loaderHeap->AllocAlignedMem(size, alignment); END_QCALL; diff --git a/src/coreclr/vm/runtimehandles.h b/src/coreclr/vm/runtimehandles.h index 822cedd5428769..fb3bbf2b475c41 100644 --- a/src/coreclr/vm/runtimehandles.h +++ b/src/coreclr/vm/runtimehandles.h @@ -140,8 +140,8 @@ extern "C" void QCALLTYPE RuntimeTypeHandle_CreateInstanceForAnotherGenericParam extern "C" void QCALLTYPE RuntimeTypeHandle_InternalAlloc(MethodTable* pMT, QCall::ObjectHandleOnStack allocated); extern "C" void QCALLTYPE RuntimeTypeHandle_InternalAllocNoChecks(MethodTable* pMT, QCall::ObjectHandleOnStack allocated); -extern "C" void* QCALLTYPE RuntimeTypeHandle_AlignedAllocateTypeAssociatedMemory(QCall::TypeHandle type, uint32_t size, uint32_t alignment); extern "C" void* QCALLTYPE RuntimeTypeHandle_AllocateTypeAssociatedMemory(QCall::TypeHandle type, uint32_t size); +extern "C" void* QCALLTYPE RuntimeTypeHandle_AllocateTypeAssociatedMemoryAligned(QCall::TypeHandle type, uint32_t size, uint32_t alignment); extern "C" PVOID QCALLTYPE QCall_GetGCHandleForTypeHandle(QCall::TypeHandle pTypeHandle, INT32 handleType); extern "C" void QCALLTYPE QCall_FreeGCHandleForTypeHandle(QCall::TypeHandle pTypeHandle, OBJECTHANDLE objHandle); diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Runtime/CompilerServices/RuntimeHelpersTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Runtime/CompilerServices/RuntimeHelpersTests.cs index 25a99a632b9999..f889498765a903 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Runtime/CompilerServices/RuntimeHelpersTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Runtime/CompilerServices/RuntimeHelpersTests.cs @@ -442,18 +442,20 @@ public static void AllocateTypeAssociatedMemoryInvalidArguments() Assert.Throws(() => { RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(RuntimeHelpersTests), -1); }); } - [Fact] public static unsafe void AllocateTypeAssociatedMemoryValidArguments() { IntPtr memory = RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(RuntimeHelpersTests), 32); + + // Validate that the allocation succeeded Assert.NotEqual(memory, IntPtr.Zero); + // Validate that the memory is zeroed out Assert.True(new Span((void*)memory, 32).SequenceEqual(new byte[32])); } [Fact] - public static void AlignedAllocateTypeAssociatedMemoryInvalidArguments() + public static void AllocateTypeAssociatedMemoryAlignedInvalidArguments() { Assert.Throws(() => { RuntimeHelpers.AllocateTypeAssociatedMemory(null, 10, 1); }); Assert.Throws(() => { RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(RuntimeHelpersTests), -1, 1); }); @@ -461,15 +463,26 @@ public static void AlignedAllocateTypeAssociatedMemoryInvalidArguments() Assert.Throws(() => { RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(RuntimeHelpersTests), 10, 3); }); } - - [Fact] - public static unsafe void AlignedAllocateTypeAssociatedMemoryValidArguments() - { - IntPtr memory = RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(RuntimeHelpersTests), 32, 16); + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(4)] + [InlineData(8)] + [InlineData(16)] + [InlineData(32)] + [InlineData(64)] + public static unsafe void AllocateTypeAssociatedMemoryAlignedValidArguments(int alignment) + { + IntPtr memory = RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(RuntimeHelpersTests), 32, alignment); + + // Validate that the allocation succeeded Assert.NotEqual(memory, IntPtr.Zero); + // Validate that the memory is zeroed out Assert.True(new Span((void*)memory, 32).SequenceEqual(new byte[32])); - Assert.True((memory % 16) == 0); + + // Validate that the memory is aligned + Assert.True((memory % alignment) == 0); } #pragma warning disable CS0649 diff --git a/src/mono/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs index c689cc5f954ddd..ed4b313251b39d 100644 --- a/src/mono/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs @@ -167,6 +167,10 @@ public static unsafe IntPtr AllocateTypeAssociatedMemory(Type type, int size) /// must be a type provided by the runtime. /// is negative. /// is not a power of 2. + /// + /// This method is guaranteed to support any natural alignment, that is any alignment that is less than or equal to the size of a pointer. + /// Support for greater alignments is implementation dependent, but will always result in an if unsupported. + /// public static unsafe IntPtr AllocateTypeAssociatedMemory(Type type, int size, int alignment) { if (type is not RuntimeType) From 5dcd52a2b40d9f0d491f1ddd48bc6473aeeecfd4 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Thu, 4 Dec 2025 14:08:01 -0800 Subject: [PATCH 4/5] Remove note and add more tests --- .../CompilerServices/RuntimeHelpers.CoreCLR.cs | 4 ---- .../CompilerServices/RuntimeHelpers.NativeAot.cs | 4 ---- .../CompilerServices/RuntimeHelpersTests.cs | 14 ++++++++++---- .../CompilerServices/RuntimeHelpers.Mono.cs | 4 ---- 4 files changed, 10 insertions(+), 16 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs index 55d57a48978975..8a47cca0f7f59b 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs @@ -494,10 +494,6 @@ public static IntPtr AllocateTypeAssociatedMemory(Type type, int size) /// must be a type provided by the runtime. /// is negative. /// is not a power of 2. - /// - /// This method is guaranteed to support any natural alignment, that is any alignment that is less than or equal to the size of a pointer. - /// Support for greater alignments is implementation dependent, but will always result in an if unsupported. - /// public static IntPtr AllocateTypeAssociatedMemory(Type type, int size, int alignment) { if (type is not RuntimeType rt) diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.NativeAot.cs index ac9346e4808618..5d25312088a767 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.NativeAot.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.NativeAot.cs @@ -252,10 +252,6 @@ public static unsafe IntPtr AllocateTypeAssociatedMemory(Type type, int size) /// must be a type provided by the runtime. /// is negative. /// is not a power of 2. - /// - /// This method is guaranteed to support any natural alignment, that is any alignment that is less than or equal to the size of a pointer. - /// Support for greater alignments is implementation dependent, but will always result in an if unsupported. - /// public static unsafe IntPtr AllocateTypeAssociatedMemory(Type type, int size, int alignment) { if (type is not RuntimeType) diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Runtime/CompilerServices/RuntimeHelpersTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Runtime/CompilerServices/RuntimeHelpersTests.cs index f889498765a903..2076198d225a39 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Runtime/CompilerServices/RuntimeHelpersTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Runtime/CompilerServices/RuntimeHelpersTests.cs @@ -467,10 +467,16 @@ public static void AllocateTypeAssociatedMemoryAlignedInvalidArguments() [InlineData(1)] [InlineData(2)] [InlineData(4)] - [InlineData(8)] - [InlineData(16)] - [InlineData(32)] - [InlineData(64)] + [InlineData(8)] // .NET largest natural alignment + [InlineData(16)] // V128, typical max_align_t + [InlineData(32)] // V256 + [InlineData(64)] // V512, typical cache line size + [InlineData(128)] // less typical cache line size + [InlineData(512)] // historical disk sector size + [InlineData(4096)] // typical disk sector and page size + [InlineData(16384)] // less typical disk sector and page size + [InlineData(65536)] // typical texture and buffer alignment for GPU + [InlineData(262144)] // typical non-temporal chunk alignment public static unsafe void AllocateTypeAssociatedMemoryAlignedValidArguments(int alignment) { IntPtr memory = RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(RuntimeHelpersTests), 32, alignment); diff --git a/src/mono/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs index ed4b313251b39d..c689cc5f954ddd 100644 --- a/src/mono/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs @@ -167,10 +167,6 @@ public static unsafe IntPtr AllocateTypeAssociatedMemory(Type type, int size) /// must be a type provided by the runtime. /// is negative. /// is not a power of 2. - /// - /// This method is guaranteed to support any natural alignment, that is any alignment that is less than or equal to the size of a pointer. - /// Support for greater alignments is implementation dependent, but will always result in an if unsupported. - /// public static unsafe IntPtr AllocateTypeAssociatedMemory(Type type, int size, int alignment) { if (type is not RuntimeType) From 9b69689ca06aa14a186359f6839e7fdf2cc93029 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Thu, 4 Dec 2025 15:01:58 -0800 Subject: [PATCH 5/5] Fix name --- .../System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs | 2 +- src/coreclr/vm/qcallentrypoints.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs index 8a47cca0f7f59b..7cf1e9cc28a7f0 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs @@ -509,7 +509,7 @@ public static IntPtr AllocateTypeAssociatedMemory(Type type, int size, int align ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_AlignmentMustBePow2); } - return AlignedAllocateTypeAssociatedMemory(new QCallTypeHandle(ref rt), (uint)size, (uint)alignment); + return AllocateTypeAssociatedMemoryAligned(new QCallTypeHandle(ref rt), (uint)size, (uint)alignment); } [LibraryImport(QCall, EntryPoint = "RuntimeTypeHandle_AllocateTypeAssociatedMemory")] diff --git a/src/coreclr/vm/qcallentrypoints.cpp b/src/coreclr/vm/qcallentrypoints.cpp index 28378217ab5c41..158a12e0433c3b 100644 --- a/src/coreclr/vm/qcallentrypoints.cpp +++ b/src/coreclr/vm/qcallentrypoints.cpp @@ -157,8 +157,8 @@ static const Entry s_QCall[] = DllImportEntry(RuntimeTypeHandle_CreateInstanceForAnotherGenericParameter) DllImportEntry(RuntimeTypeHandle_InternalAlloc) DllImportEntry(RuntimeTypeHandle_InternalAllocNoChecks) - DllImportEntry(RuntimeTypeHandle_AlignedAllocateTypeAssociatedMemory) DllImportEntry(RuntimeTypeHandle_AllocateTypeAssociatedMemory) + DllImportEntry(RuntimeTypeHandle_AllocateTypeAssociatedMemoryAligned) DllImportEntry(RuntimeTypeHandle_RegisterCollectibleTypeDependency) DllImportEntry(MethodBase_GetCurrentMethod) DllImportEntry(RuntimeMethodHandle_InvokeMethod)