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..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
@@ -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 AllocateTypeAssociatedMemoryAligned(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_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 541f62969ab526..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;
@@ -224,17 +225,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 +244,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 unsafe IntPtr AllocateTypeAssociatedMemory(Type type, int size, int alignment)
+ {
+ if (type is not RuntimeType)
+ {
+ 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..158a12e0433c3b 100644
--- a/src/coreclr/vm/qcallentrypoints.cpp
+++ b/src/coreclr/vm/qcallentrypoints.cpp
@@ -158,6 +158,7 @@ static const Entry s_QCall[] =
DllImportEntry(RuntimeTypeHandle_InternalAlloc)
DllImportEntry(RuntimeTypeHandle_InternalAllocNoChecks)
DllImportEntry(RuntimeTypeHandle_AllocateTypeAssociatedMemory)
+ DllImportEntry(RuntimeTypeHandle_AllocateTypeAssociatedMemoryAligned)
DllImportEntry(RuntimeTypeHandle_RegisterCollectibleTypeDependency)
DllImportEntry(MethodBase_GetCurrentMethod)
DllImportEntry(RuntimeMethodHandle_InvokeMethod)
diff --git a/src/coreclr/vm/runtimehandles.cpp b/src/coreclr/vm/runtimehandles.cpp
index a6e8b866011524..cd76192d29db70 100644
--- a/src/coreclr/vm/runtimehandles.cpp
+++ b/src/coreclr/vm/runtimehandles.cpp
@@ -1233,6 +1233,32 @@ extern "C" void* QCALLTYPE RuntimeTypeHandle_AllocateTypeAssociatedMemory(QCall:
return allocatedMemory;
}
+extern "C" void* QCALLTYPE RuntimeTypeHandle_AllocateTypeAssociatedMemoryAligned(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(size, alignment);
+
+ END_QCALL;
+
+ return allocatedMemory;
+}
+
extern "C" void QCALLTYPE RuntimeTypeHandle_RegisterCollectibleTypeDependency(QCall::TypeHandle pTypeHandle, QCall::AssemblyHandle pAssembly)
{
QCALL_CONTRACT;
diff --git a/src/coreclr/vm/runtimehandles.h b/src/coreclr/vm/runtimehandles.h
index 7d155046577d96..fb3bbf2b475c41 100644
--- a/src/coreclr/vm/runtimehandles.h
+++ b/src/coreclr/vm/runtimehandles.h
@@ -141,6 +141,7 @@ extern "C" void QCALLTYPE RuntimeTypeHandle_InternalAlloc(MethodTable* pMT, QCal
extern "C" void QCALLTYPE RuntimeTypeHandle_InternalAllocNoChecks(MethodTable* pMT, QCall::ObjectHandleOnStack allocated);
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/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..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
@@ -446,9 +446,49 @@ public static void AllocateTypeAssociatedMemoryInvalidArguments()
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 AllocateTypeAssociatedMemoryAlignedInvalidArguments()
+ {
+ Assert.Throws(() => { RuntimeHelpers.AllocateTypeAssociatedMemory(null, 10, 1); });
+ Assert.Throws(() => { RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(RuntimeHelpersTests), -1, 1); });
+ Assert.Throws(() => { RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(RuntimeHelpersTests), 10, 0); });
+ Assert.Throws(() => { RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(RuntimeHelpersTests), 10, 3); });
+ }
+
+ [Theory]
+ [InlineData(1)]
+ [InlineData(2)]
+ [InlineData(4)]
+ [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);
+
+ // 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]));
+
+ // 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 891e97640253c5..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;
@@ -139,10 +140,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 +159,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 unsafe IntPtr AllocateTypeAssociatedMemory(Type type, int size, int alignment)
+ {
+ if (type is not RuntimeType)
+ {
+ 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();