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();