Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);

/// <summary>
/// Allocate memory that is associated with the <paramref name="type"/> and
/// will be freed if and when the <see cref="Type"/> is unloaded.
/// </summary>
/// <param name="type">Type associated with the allocated memory.</param>
/// <param name="size">Amount of memory in bytes to allocate.</param>
/// <returns>The allocated memory</returns>
/// <summary>Allocates memory that's associated with the <paramref name="type" /> and is freed if and when the <see cref="Type" /> is unloaded.</summary>
/// <param name="type">The type associated with the allocated memory.</param>
/// <param name="size">The amount of memory to allocate, in bytes.</param>
/// <returns>The allocated memory.</returns>
/// <exception cref="ArgumentException"><paramref name="type" /> must be a type provided by the runtime.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="size" /> is negative.</exception>
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);
}

/// <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>
/// <param name="type">The type associated with the allocated memory.</param>
/// <param name="size">The amount of memory to allocate, in bytes.</param>
/// <param name="alignment">The alignment, in bytes, of the memory to allocate. This must be a power of <c>2</c>.</param>
/// <returns>The allocated aligned memory.</returns>
/// <exception cref="ArgumentException"><paramref name="type" /> must be a type provided by the runtime.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="size" /> is negative.</exception>
/// <exception cref="ArgumentException"><paramref name="alignment" /> is not a power of <c>2</c>.</exception>
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();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -224,24 +225,54 @@ public static void PrepareMethod(RuntimeMethodHandle method, RuntimeTypeHandle[]
throw new ArgumentException(SR.InvalidOperation_HandleIsNotInitialized, nameof(method));
}

/// <summary>
/// Allocate memory that is associated with the <paramref name="type"/> and
/// will be freed if and when the <see cref="System.Type"/> is unloaded.
/// </summary>
/// <param name="type">Type associated with the allocated memory.</param>
/// <param name="size">Amount of memory in bytes to allocate.</param>
/// <returns>The allocated memory</returns>
/// <summary>Allocates memory that's associated with the <paramref name="type" /> and is freed if and when the <see cref="Type" /> is unloaded.</summary>
/// <param name="type">The type associated with the allocated memory.</param>
/// <param name="size">The amount of memory to allocate, in bytes.</param>
/// <returns>The allocated memory.</returns>
/// <exception cref="ArgumentException"><paramref name="type" /> must be a type provided by the runtime.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="size" /> is negative.</exception>
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);

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

/// <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>
/// <param name="type">The type associated with the allocated memory.</param>
/// <param name="size">The amount of memory to allocate, in bytes.</param>
/// <param name="alignment">The alignment, in bytes, of the memory to allocate. This must be a power of <c>2</c>.</param>
/// <returns>The allocated aligned memory.</returns>
/// <exception cref="ArgumentException"><paramref name="type" /> must be a type provided by the runtime.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="size" /> is negative.</exception>
/// <exception cref="ArgumentException"><paramref name="alignment" /> is not a power of <c>2</c>.</exception>
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)
{
}
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/vm/qcallentrypoints.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
26 changes: 26 additions & 0 deletions src/coreclr/vm/runtimehandles.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/vm/runtimehandles.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions src/libraries/System.Runtime/ref/System.Runtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T> CreateSpan<T>(System.RuntimeFieldHandle fldHandle) { throw null; }
public static void EnsureSufficientExecutionStack() { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<byte>((void*)memory, 32).SequenceEqual(new byte[32]));
}

[Fact]
public static void AllocateTypeAssociatedMemoryAlignedInvalidArguments()
{
Assert.Throws<ArgumentException>(() => { RuntimeHelpers.AllocateTypeAssociatedMemory(null, 10, 1); });
Assert.Throws<ArgumentOutOfRangeException>(() => { RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(RuntimeHelpersTests), -1, 1); });
Assert.Throws<ArgumentException>(() => { RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(RuntimeHelpersTests), 10, 0); });
Assert.Throws<ArgumentException>(() => { 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<byte>((void*)memory, 32).SequenceEqual(new byte[32]));

// Validate that the memory is aligned
Assert.True((memory % alignment) == 0);
}

#pragma warning disable CS0649
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -139,17 +140,54 @@ public static void RunModuleConstructor(ModuleHandle module)
RunModuleConstructor(module.Value);
}

/// <summary>Allocates memory that's associated with the <paramref name="type" /> and is freed if and when the <see cref="Type" /> is unloaded.</summary>
/// <param name="type">The type associated with the allocated memory.</param>
/// <param name="size">The amount of memory to allocate, in bytes.</param>
/// <returns>The allocated memory.</returns>
/// <exception cref="ArgumentException"><paramref name="type" /> must be a type provided by the runtime.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="size" /> is negative.</exception>
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);

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

/// <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>
/// <param name="type">The type associated with the allocated memory.</param>
/// <param name="size">The amount of memory to allocate, in bytes.</param>
/// <param name="alignment">The alignment, in bytes, of the memory to allocate. This must be a power of <c>2</c>.</param>
/// <returns>The allocated aligned memory.</returns>
/// <exception cref="ArgumentException"><paramref name="type" /> must be a type provided by the runtime.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="size" /> is negative.</exception>
/// <exception cref="ArgumentException"><paramref name="alignment" /> is not a power of <c>2</c>.</exception>
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();

Expand Down
Loading