From b45bcbca45b0aa37b17ff7bfcf99d4e6bc495071 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Thu, 10 Jun 2021 10:51:11 -0700 Subject: [PATCH 01/15] Implement NativeMemory --- .../Unix/System.Native/Interop.MemAlloc.cs | 23 ++- .../src/Interop/Windows/Interop.Libraries.cs | 1 + .../Windows/Ucrtbase/Interop.MemAlloc.cs | 29 +++ .../Native/Unix/Common/pal_config.h.in | 2 + .../Native/Unix/System.Native/CMakeLists.txt | 3 + .../Native/Unix/System.Native/entrypoints.c | 9 +- .../Native/Unix/System.Native/pal_memory.c | 37 +++- .../Native/Unix/System.Native/pal_memory.h | 25 ++- src/libraries/Native/Unix/configure.cmake | 3 + .../src/Resources/Strings.resx | 5 +- .../System.Private.CoreLib.Shared.projitems | 5 + .../src/System/Numerics/BitOperations.cs | 14 +- .../Runtime/InteropServices/Marshal.Unix.cs | 86 +++----- .../InteropServices/NativeMemory.Unix.cs | 158 +++++++++++++++ .../InteropServices/NativeMemory.Windows.cs | 136 +++++++++++++ .../src/System/ThrowHelper.cs | 3 + .../ref/System.Runtime.InteropServices.cs | 15 ++ ...ystem.Runtime.InteropServices.Tests.csproj | 1 + .../InteropServices/NativeMemoryTests.cs | 185 ++++++++++++++++++ 19 files changed, 660 insertions(+), 80 deletions(-) create mode 100644 src/libraries/Common/src/Interop/Windows/Ucrtbase/Interop.MemAlloc.cs create mode 100644 src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Unix.cs create mode 100644 src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Windows.cs create mode 100644 src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/NativeMemoryTests.cs diff --git a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.MemAlloc.cs b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.MemAlloc.cs index c5f6d1baa2adb0..871baa2bedd135 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.MemAlloc.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.MemAlloc.cs @@ -6,15 +6,24 @@ internal static partial class Interop { - internal static partial class Sys + internal static unsafe partial class Sys { - [DllImport(Interop.Libraries.SystemNative, EntryPoint = "SystemNative_MemAlloc")] - internal static extern IntPtr MemAlloc(nuint sizeInBytes); + [DllImport(Interop.Libraries.SystemNative, EntryPoint = "SystemNative_AlignedAlloc")] + internal static extern void* AlignedAlloc(nuint alignment, nuint size); - [DllImport(Interop.Libraries.SystemNative, EntryPoint = "SystemNative_MemReAlloc")] - internal static extern IntPtr MemReAlloc(IntPtr ptr, nuint newSize); + [DllImport(Interop.Libraries.SystemNative, EntryPoint = "SystemNative_AlignedFree")] + internal static extern void AlignedFree(void* ptr); - [DllImport(Interop.Libraries.SystemNative, EntryPoint = "SystemNative_MemFree")] - internal static extern void MemFree(IntPtr ptr); + [DllImport(Interop.Libraries.SystemNative, EntryPoint = "SystemNative_Calloc")] + internal static extern void* Calloc(nuint num, nuint size); + + [DllImport(Interop.Libraries.SystemNative, EntryPoint = "SystemNative_Free")] + internal static extern void Free(void* ptr); + + [DllImport(Interop.Libraries.SystemNative, EntryPoint = "SystemNative_Malloc")] + internal static extern void* Malloc(nuint size); + + [DllImport(Interop.Libraries.SystemNative, EntryPoint = "SystemNative_Realloc")] + internal static extern void* Realloc(void* ptr, nuint new_size); } } diff --git a/src/libraries/Common/src/Interop/Windows/Interop.Libraries.cs b/src/libraries/Common/src/Interop/Windows/Interop.Libraries.cs index d3466d74a12edf..93c2bb72a806c4 100644 --- a/src/libraries/Common/src/Interop/Windows/Interop.Libraries.cs +++ b/src/libraries/Common/src/Interop/Windows/Interop.Libraries.cs @@ -44,5 +44,6 @@ internal static partial class Libraries internal const string GlobalizationNative = "System.Globalization.Native"; internal const string MsQuic = "msquic.dll"; internal const string HostPolicy = "hostpolicy.dll"; + internal const string Ucrtbase = "ucrtbase.dll"; } } diff --git a/src/libraries/Common/src/Interop/Windows/Ucrtbase/Interop.MemAlloc.cs b/src/libraries/Common/src/Interop/Windows/Ucrtbase/Interop.MemAlloc.cs new file mode 100644 index 00000000000000..be8a1af882b3d3 --- /dev/null +++ b/src/libraries/Common/src/Interop/Windows/Ucrtbase/Interop.MemAlloc.cs @@ -0,0 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static unsafe partial class Ucrtbase + { + [DllImport(Libraries.Ucrtbase, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + internal static extern void* _aligned_malloc(nuint size, nuint alignment); + + [DllImport(Libraries.Ucrtbase, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + internal static extern void _aligned_free(void* ptr); + + [DllImport(Libraries.Ucrtbase, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + internal static extern void* calloc(nuint num, nuint size); + + [DllImport(Libraries.Ucrtbase, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + internal static extern void free(void* ptr); + + [DllImport(Libraries.Ucrtbase, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + internal static extern void* malloc(nuint size); + + [DllImport(Libraries.Ucrtbase, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + internal static extern void* realloc(void* ptr, nuint new_size); + } +} diff --git a/src/libraries/Native/Unix/Common/pal_config.h.in b/src/libraries/Native/Unix/Common/pal_config.h.in index 034d57b1e1c98b..adef17508cdf51 100644 --- a/src/libraries/Native/Unix/Common/pal_config.h.in +++ b/src/libraries/Native/Unix/Common/pal_config.h.in @@ -122,6 +122,8 @@ #cmakedefine01 HAVE_GETGROUPLIST #cmakedefine01 HAVE_SYS_PROCINFO_H #cmakedefine01 HAVE_IOSS_H +#cmakedefine01 HAVE_ALIGNED_ALLOC +#cmakedefine01 HAVE_POSIX_MEMALIGN // Mac OS X has stat64, but it is deprecated since plain stat now // provides the same 64-bit aware struct when targeting OS X > 10.5 diff --git a/src/libraries/Native/Unix/System.Native/CMakeLists.txt b/src/libraries/Native/Unix/System.Native/CMakeLists.txt index d21e27a561b0f4..7dc5203a311390 100644 --- a/src/libraries/Native/Unix/System.Native/CMakeLists.txt +++ b/src/libraries/Native/Unix/System.Native/CMakeLists.txt @@ -1,5 +1,8 @@ project(System.Native C) +include(configure.cmake) +include_directories(${CMAKE_CURRENT_BINARY_DIR}) + if (NOT CLR_CMAKE_TARGET_MACCATALYST AND NOT CLR_CMAKE_TARGET_IOS AND NOT CLR_CMAKE_TARGET_TVOS) add_definitions(-DHAS_CONSOLE_SIGNALS) endif () diff --git a/src/libraries/Native/Unix/System.Native/entrypoints.c b/src/libraries/Native/Unix/System.Native/entrypoints.c index 8a1438b6c3d18b..354ee8d67d5de3 100644 --- a/src/libraries/Native/Unix/System.Native/entrypoints.c +++ b/src/libraries/Native/Unix/System.Native/entrypoints.c @@ -108,10 +108,13 @@ static const Entry s_sysNative[] = DllImportEntry(SystemNative_LChflagsCanSetHiddenFlag) DllImportEntry(SystemNative_ReadProcessStatusInfo) DllImportEntry(SystemNative_Log) - DllImportEntry(SystemNative_MemAlloc) - DllImportEntry(SystemNative_MemReAlloc) - DllImportEntry(SystemNative_MemFree) + DllImportEntry(SystemNative_AlignedAlloc) + DllImportEntry(SystemNative_AlignedFree) + DllImportEntry(SystemNative_Calloc) + DllImportEntry(SystemNative_Free) + DllImportEntry(SystemNative_Malloc) DllImportEntry(SystemNative_MemSet) + DllImportEntry(SystemNative_Realloc) DllImportEntry(SystemNative_GetSpaceInfoForMountPoint) DllImportEntry(SystemNative_GetFormatInfoForMountPoint) DllImportEntry(SystemNative_GetAllMountPoints) diff --git a/src/libraries/Native/Unix/System.Native/pal_memory.c b/src/libraries/Native/Unix/System.Native/pal_memory.c index 35757dff03b9dd..9b1b34d8c065a8 100644 --- a/src/libraries/Native/Unix/System.Native/pal_memory.c +++ b/src/libraries/Native/Unix/System.Native/pal_memory.c @@ -1,27 +1,54 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +#include "pal_config.h" #include "pal_memory.h" #include #include -void* SystemNative_MemAlloc(uintptr_t size) +void* SystemNative_AlignedAlloc(uintptr_t alignment, uintptr_t size) { - return malloc(size); +#if HAVE_ALIGNED_ALLOC && !defined(__APPLE__) + // We want to prefer the standardized aligned_alloc function. However + // it cannot be used on __APPLE__ since we target 10.13 and it was + // only added in 10.15, but we might be compiling on a 10.15 box. + return aligned_alloc(alignment, size); +#elif HAVE_POSIX_MEMALIGN + void* result = NULL; + posix_memalign(&result, alignment, size); + return result; +#else + #error "Platform doesn't support aligned_alloc or posix_memalign" +#endif +} + +void SystemNative_AlignedFree(void* ptr) +{ + free(ptr); } -void* SystemNative_MemReAlloc(void* ptr, uintptr_t size) +void* SystemNative_Calloc(uintptr_t num, uintptr_t size) { - return realloc(ptr, size); + return calloc(num, size); } -void SystemNative_MemFree(void* ptr) +void SystemNative_Free(void* ptr) { free(ptr); } +void* SystemNative_Malloc(uintptr_t size) +{ + return malloc(size); +} + void* SystemNative_MemSet(void* s, int c, uintptr_t n) { return memset(s, c, n); } + +void* SystemNative_Realloc(void* ptr, uintptr_t new_size) +{ + return realloc(ptr, new_size); +} diff --git a/src/libraries/Native/Unix/System.Native/pal_memory.h b/src/libraries/Native/Unix/System.Native/pal_memory.h index 83fc2f2f9f48a0..81037b2037b811 100644 --- a/src/libraries/Native/Unix/System.Native/pal_memory.h +++ b/src/libraries/Native/Unix/System.Native/pal_memory.h @@ -7,21 +7,36 @@ #include "pal_types.h" /** - * C runtime malloc + * C runtime aligned_alloc */ -PALEXPORT void* SystemNative_MemAlloc(uintptr_t size); +PALEXPORT void* SystemNative_AlignedAlloc(uintptr_t alignment, uintptr_t size); /** - * C runtime realloc + * C runtime free for aligned_alloc */ -PALEXPORT void* SystemNative_MemReAlloc(void* ptr, uintptr_t size); +PALEXPORT void SystemNative_AlignedFree(void* ptr); + +/** + * C runtime calloc + */ +PALEXPORT void* SystemNative_Calloc(uintptr_t num, uintptr_t size); /** * C runtime free */ -PALEXPORT void SystemNative_MemFree(void* ptr); +PALEXPORT void SystemNative_Free(void* ptr); /** * C runtime memset */ PALEXPORT void* SystemNative_MemSet(void* s, int c, uintptr_t n); + +/** + * C runtime malloc + */ +PALEXPORT void* SystemNative_Malloc(uintptr_t size); + +/** + * C runtime realloc + */ +PALEXPORT void* SystemNative_Realloc(void* ptr, uintptr_t size); diff --git a/src/libraries/Native/Unix/configure.cmake b/src/libraries/Native/Unix/configure.cmake index 9d40db9dbf2122..38cd45a7d76363 100644 --- a/src/libraries/Native/Unix/configure.cmake +++ b/src/libraries/Native/Unix/configure.cmake @@ -1039,6 +1039,9 @@ check_c_source_compiles( " HAVE_BUILTIN_MUL_OVERFLOW) +check_symbol_exists(aligned_alloc stdlib.h HAVE_ALIGNED_ALLOC) +check_symbol_exists(posix_memalign stdlib.h HAVE_POSIX_MEMALIGN) + configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/Common/pal_config.h.in ${CMAKE_CURRENT_BINARY_DIR}/Common/pal_config.h) diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index 99b032174387f5..7c80c23f632414 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -813,6 +813,9 @@ The elements of the AdjustmentRule array must be in chronological order and must not overlap. + + The alignment must be a power of two. + The object already has a CCW associated with it. @@ -3784,4 +3787,4 @@ A MemberInfo that matches '{0}' could not be found. - \ No newline at end of file + diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index a189be9ac1517a..f29a212a09820a 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -1725,6 +1725,9 @@ Common\Interop\Windows\Shell32\Interop.SHGetKnownFolderPath.cs + + Common\Interop\Windows\Ucrtbase\Interop.MemAlloc.cs + Common\Interop\Windows\User32\Interop.Constants.cs @@ -1796,6 +1799,7 @@ + @@ -2067,6 +2071,7 @@ + diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/BitOperations.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/BitOperations.cs index 49fbb26ff4ca48..b5ffd5e777d984 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/BitOperations.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/BitOperations.cs @@ -72,9 +72,21 @@ public static class BitOperations public static bool IsPow2(ulong value) => (value & (value - 1)) == 0 && value != 0; /// - /// Round the given integral value up to a power of 2. + /// Evaluate whether a given integral value is a power of 2. /// /// The value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool IsPow2(nint value) => (value & (value - 1)) == 0 && value > 0; + + /// + /// Evaluate whether a given integral value is a power of 2. + /// + /// The value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool IsPow2(nuint value) => (value & (value - 1)) == 0 && value != 0; + + /// Round the given integral value up to a power of 2. + /// The value. /// /// The smallest power of 2 which is greater than or equal to . /// If is 0 or the result overflows, returns 0. diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshal.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshal.Unix.cs index 08ab87a390fffc..49ef8223c370f9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshal.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshal.Unix.cs @@ -62,73 +62,37 @@ internal static unsafe void GetAnsiStringBytes(ReadOnlySpan chars, Span AllocHGlobal((nint)(uint)cb); public static void FreeCoTaskMem(IntPtr ptr) => FreeHGlobal(ptr); - public static IntPtr ReAllocCoTaskMem(IntPtr pv, int cb) + public static unsafe IntPtr ReAllocCoTaskMem(IntPtr pv, int cb) { nuint cbNative = (nuint)(uint)cb; + void* pvNative = (void*)(nint)pv; - if (cbNative == 0) + if ((cbNative == 0) && (pvNative != null)) { - if (pv != IntPtr.Zero) - { - Interop.Sys.MemFree(pv); - return IntPtr.Zero; - } - // Avoid undefined realloc behavior by always allocating at least one byte - cbNative = 1; + Interop.Sys.Free(pvNative); + return IntPtr.Zero; } - IntPtr pNewMem = Interop.Sys.MemReAlloc(pv, cbNative); - if (pNewMem == IntPtr.Zero) - { - throw new OutOfMemoryException(); - } - return pNewMem; + return (nint)NativeMemory.Realloc((void*)(nint)pv, cbNative); } internal static unsafe IntPtr AllocBSTR(int length) @@ -137,22 +101,24 @@ internal static unsafe IntPtr AllocBSTR(int length) const nuint WIN32_ALLOC_ALIGN = 15; ulong cbNative = 2 * (ulong)(uint)length + (uint)sizeof(IntPtr) + (uint)sizeof(char) + WIN32_ALLOC_ALIGN; + if (cbNative > uint.MaxValue) { throw new OutOfMemoryException(); } - IntPtr p = Interop.Sys.MemAlloc((nuint)cbNative & ~WIN32_ALLOC_ALIGN); - if (p == IntPtr.Zero) + void* p = Interop.Sys.Malloc((nuint)cbNative & ~WIN32_ALLOC_ALIGN); + + if (p == null) { throw new OutOfMemoryException(); } - IntPtr s = p + sizeof(IntPtr); + void* s = (byte*)p + sizeof(nuint); *(((uint*)s) - 1) = (uint)(length * sizeof(char)); ((char*)s)[length] = '\0'; - return s; + return (nint)s; } internal static unsafe IntPtr AllocBSTRByteLen(uint length) @@ -161,32 +127,36 @@ internal static unsafe IntPtr AllocBSTRByteLen(uint length) const nuint WIN32_ALLOC_ALIGN = 15; ulong cbNative = (ulong)(uint)length + (uint)sizeof(IntPtr) + (uint)sizeof(char) + WIN32_ALLOC_ALIGN; + if (cbNative > uint.MaxValue) { throw new OutOfMemoryException(); } - IntPtr p = Interop.Sys.MemAlloc((nuint)cbNative & ~WIN32_ALLOC_ALIGN); - if (p == IntPtr.Zero) + void* p = Interop.Sys.Malloc((nuint)cbNative & ~WIN32_ALLOC_ALIGN); + + if (p == null) { throw new OutOfMemoryException(); } - IntPtr s = p + sizeof(IntPtr); + void* s = (byte*)p + sizeof(nuint); *(((uint*)s) - 1) = (uint)length; // NULL-terminate with both a narrow and wide zero. *(byte*)((byte*)s + length) = (byte)'\0'; *(short*)((byte*)s + ((length + 1) & ~1)) = 0; - return s; + return (nint)s; } public static unsafe void FreeBSTR(IntPtr ptr) { - if (ptr != IntPtr.Zero) + void* ptrNative = (void*)(nint)ptr; + + if (ptrNative != null) { - Interop.Sys.MemFree(ptr - sizeof(IntPtr)); + Interop.Sys.Free((byte*)ptr - sizeof(nuint)); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Unix.cs new file mode 100644 index 00000000000000..55c1bb88eb1be2 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Unix.cs @@ -0,0 +1,158 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace System.Runtime.InteropServices +{ + /// This class contains methods that are mainly used to manage native memory. + public static unsafe class NativeMemory + { + /// Allocates an aligned block of memory of the specified size and alignment, in bytes. + /// The size, in bytes, of the block to allocate. + /// The alignment, in bytes, of the block to allocate. This must be a power of 2. + /// A pointer to the allocated aligned block of memory. + /// is not a power of two. + /// Allocating of memory with failed. + /// + /// This method allows to be 0 and will return a valid pointer that should not be dereferenced and that should be passed to free to avoid memory leaks. + /// This method is a thin wrapper over the C aligned_alloc API or a platform dependent aligned allocation API such as _aligned_malloc on Win32. + /// This method is not compatible with or , instead should be called. + /// + [CLSCompliant(false)] + public static void* AlignedAlloc(nuint byteCount, nuint alignment) + { + if (!BitOperations.IsPow2(alignment)) + { + // The C standard doesn't define what a valid alignment is, however Windows and POSIX requires a power of 2 + ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_AlignmentMustBePow2); + } + + // The C standard and POSIX requires size to be a multiple of alignment and we want an "empty" allocation for zero + // POSIX additionally requires alignment to be at least sizeof(void*) + + // The adjustment for byteCount can overflow here, but such overflow is "harmless". This is because of the requirement + // that alignment be a power of two and that byteCount be a multiple of alignment. Given both of these constraints + // we should only overflow for byteCount > (nuint.MaxValue & ~(alignment - 1)). When such an overflow occurs we will + // get a result that is less than alignment which will cause the allocation to fail. + + void* result = Interop.Sys.AlignedAlloc(Math.Max(alignment, sizeof(void*)), (byteCount != 0) ? (byteCount + (alignment - 1)) & ~(alignment - 1) : alignment); + + if (result == null) + { + ThrowHelper.ThrowOutOfMemoryException(); + } + + return result; + } + + /// Frees an aligned block of memory. + /// A pointer to the aligned block of memory that should be freed. + /// + /// This method does nothing if is null. + /// This method is a thin wrapper over the C free API or a platform dependent aligned free API such as _aligned_free on Win32. + /// + [CLSCompliant(false)] + public static void AlignedFree(void* ptr) + { + if (ptr != null) + { + Interop.Sys.AlignedFree(ptr); + } + } + + /// Allocates a block of memory of the specified size, in bytes. + /// The size, in bytes, of the block to allocate. + /// A pointer to the allocated block of memory. + /// Allocating of memory failed. + /// + /// This method allows to be 0 and will return a valid pointer that should not be dereferenced and that should be passed to free to avoid memory leaks. + /// This method is a thin wrapper over the C malloc API. + /// + [CLSCompliant(false)] + public static void* Alloc(nuint byteCount) + { + // The C standard does not define what happens when size == 0, we want an "empty" allocation + void* result = Interop.Sys.Malloc((byteCount != 0) ? byteCount : 1); + + if (result == null) + { + ThrowHelper.ThrowOutOfMemoryException(); + } + + return result; + } + + /// Allocates and zeroes a block of memory of the specified size, in elements. + /// The count, in elements, of the block to allocate. + /// The size, in bytes, of each element in the allocation. + /// A pointer to the allocated and zeroed block of memory. + /// Allocating * bytes of memory failed. + /// + /// This method allows and/or to be 0 and will return a valid pointer that should not be dereferenced and that should be passed to free to avoid memory leaks. + /// This method is a thin wrapper over the C calloc API. + /// + [CLSCompliant(false)] + public static void* AllocZeroed(nuint elementCount, nuint elementSize) + { + void* result = null; + + if ((elementCount != 0) && (elementSize != 0)) + { + result = Interop.Sys.Calloc(elementCount, elementSize); + } + else + { + // The C standard does not define what happens when num == 0 or size == 0, we want an "empty" allocation + result = Interop.Sys.Malloc(1); + } + + if (result == null) + { + ThrowHelper.ThrowOutOfMemoryException(); + } + + return result; + } + + /// Frees a block of memory. + /// A pointer to the block of memory that should be freed. + /// + /// This method does nothing if is null. + /// This method is a thin wrapper over the C free API. + /// + [CLSCompliant(false)] + public static void Free(void* ptr) + { + if (ptr != null) + { + Interop.Sys.Free(ptr); + } + } + + /// Reallocates a block of memory to be the specified size, in bytes. + /// The previously allocated block of memory. + /// The size, in bytes, of the reallocated block. + /// A pointer to the reallocated block of memory. + /// Reallocating of memory failed. + /// + /// This method acts as if is null. + /// This method allows to be 0 and will return a valid pointer that should not be dereferenced and that should be passed to free to avoid memory leaks. + /// This method is a thin wrapper over the C realloc API. + /// + [CLSCompliant(false)] + public static void* Realloc(void* ptr, nuint byteCount) + { + // The C standard does not define what happens when size == 0, we want an "empty" allocation + void* result = Interop.Sys.Realloc(ptr, (byteCount != 0) ? byteCount : 1); + + if (result == null) + { + ThrowHelper.ThrowOutOfMemoryException(); + } + + return result; + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Windows.cs new file mode 100644 index 00000000000000..02a990e6b54a66 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Windows.cs @@ -0,0 +1,136 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace System.Runtime.InteropServices +{ + /// This class contains methods that are mainly used to manage native memory. + public static unsafe class NativeMemory + { + /// Allocates an aligned block of memory of the specified size and alignment, in bytes. + /// The size, in bytes, of the block to allocate. + /// The alignment, in bytes, of the block to allocate. This must be a power of 2. + /// A pointer to the allocated aligned block of memory. + /// is not a power of two. + /// Allocating of memory with failed. + /// + /// This method allows to be 0 and will return a valid pointer that should not be dereferenced and that should be passed to free to avoid memory leaks. + /// This method is a thin wrapper over the C aligned_alloc API or a platform dependent aligned allocation API such as _aligned_malloc on Win32. + /// This method is not compatible with or , instead should be called. + /// + [CLSCompliant(false)] + public static void* AlignedAlloc(nuint byteCount, nuint alignment) + { + if (!BitOperations.IsPow2(alignment)) + { + // The C standard doesn't define what a valid alignment is, however Windows and POSIX The Windows implementation requires a power of 2 + ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_AlignmentMustBePow2); + } + + // Unlike the C standard and POSIX, Windows does not requires size to be a multiple of alignment. However, we do want an "empty" allocation for zero + void* result = Interop.Ucrtbase._aligned_malloc((byteCount != 0) ? byteCount : 1, alignment); + + if (result == null) + { + ThrowHelper.ThrowOutOfMemoryException(); + } + + return result; + } + + /// Frees an aligned block of memory. + /// A pointer to the aligned block of memory that should be freed. + /// + /// This method does nothing if is null. + /// This method is a thin wrapper over the C free API or a platform dependent aligned free API such as _aligned_free on Win32. + /// + [CLSCompliant(false)] + public static void AlignedFree(void* ptr) + { + Interop.Ucrtbase._aligned_free(ptr); + } + + /// Allocates a block of memory of the specified size, in bytes. + /// The size, in bytes, of the block to allocate. + /// A pointer to the allocated block of memory. + /// Allocating of memory failed. + /// + /// This method allows to be 0 and will return a valid pointer that should not be dereferenced and that should be passed to free to avoid memory leaks. + /// This method is a thin wrapper over the C malloc API. + /// + [CLSCompliant(false)] + public static void* Alloc(nuint byteCount) + { + // The Windows implementation handles size == 0 as we expect + void* result = Interop.Ucrtbase.malloc(byteCount); + + if (result == null) + { + ThrowHelper.ThrowOutOfMemoryException(); + } + + return result; + } + + /// Allocates and zeroes a block of memory of the specified size, in elements. + /// The count, in elements, of the block to allocate. + /// The size, in bytes, of each element in the allocation. + /// A pointer to the allocated and zeroed block of memory. + /// Allocating * bytes of memory failed. + /// + /// This method allows and/or to be 0 and will return a valid pointer that should not be dereferenced and that should be passed to free to avoid memory leaks. + /// This method is a thin wrapper over the C calloc API. + /// + [CLSCompliant(false)] + public static void* AllocZeroed(nuint elementCount, nuint elementSize) + { + // The Windows implementation handles num == 0 && size == 0 as we expect + void* result = Interop.Ucrtbase.calloc(elementCount, elementSize); + + if (result == null) + { + ThrowHelper.ThrowOutOfMemoryException(); + } + + return result; + } + + /// Frees a block of memory. + /// A pointer to the block of memory that should be freed. + /// + /// This method does nothing if is null. + /// This method is a thin wrapper over the C free API. + /// + [CLSCompliant(false)] + public static void Free(void* ptr) + { + Interop.Ucrtbase.free(ptr); + } + + /// Reallocates a block of memory to be the specified size, in bytes. + /// The previously allocated block of memory. + /// The size, in bytes, of the reallocated block. + /// A pointer to the reallocated block of memory. + /// Reallocating of memory failed. + /// + /// This method acts as if is null. + /// This method allows to be 0 and will return a valid pointer that should not be dereferenced and that should be passed to free to avoid memory leaks. + /// This method is a thin wrapper over the C realloc API. + /// + [CLSCompliant(false)] + public static void* Realloc(void* ptr, nuint byteCount) + { + // The Windows implementation treats size == 0 as Free, we want an "empty" allocation + void* result = Interop.Ucrtbase.realloc(ptr, (byteCount != 0) ? byteCount : 1); + + if (result == null) + { + ThrowHelper.ThrowOutOfMemoryException(); + } + + return result; + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs b/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs index 94b2e6d0b18d7e..9a5ef787d7c788 100644 --- a/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs +++ b/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs @@ -975,6 +975,8 @@ private static string GetResourceString(ExceptionResource resource) return SR.Argument_InvalidFlag; case ExceptionResource.CancellationTokenSource_Disposed: return SR.CancellationTokenSource_Disposed; + case ExceptionResource.Argument_AlignmentMustBePow2: + return SR.Argument_AlignmentMustBePow2; default: Debug.Fail("The enum value is not defined, please check the ExceptionResource Enum."); return ""; @@ -1158,5 +1160,6 @@ internal enum ExceptionResource Argument_SpansMustHaveSameLength, Argument_InvalidFlag, CancellationTokenSource_Disposed, + Argument_AlignmentMustBePow2, } } diff --git a/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs b/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs index 0cf9affe8da760..2a350ac9aa2751 100644 --- a/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs +++ b/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs @@ -760,6 +760,21 @@ public static void SetDllImportResolver(System.Reflection.Assembly assembly, Sys public static bool TryLoad(string libraryPath, out System.IntPtr handle) { throw null; } public static bool TryLoad(string libraryName, System.Reflection.Assembly assembly, System.Runtime.InteropServices.DllImportSearchPath? searchPath, out System.IntPtr handle) { throw null; } } + public static unsafe partial class NativeMemory + { + [System.CLSCompliantAttribute(false)] + public static void* AlignedAlloc(nuint byteCount, nuint alignment) { throw null; } + [System.CLSCompliantAttribute(false)] + public static void AlignedFree(void* ptr) { } + [System.CLSCompliantAttribute(false)] + public static void* Alloc(nuint byteCount) { throw null; } + [System.CLSCompliantAttribute(false)] + public static void* AllocZeroed(nuint elementCount, nuint elementSize) { throw null; } + [System.CLSCompliantAttribute(false)] + public static void Free(void* ptr) { } + [System.CLSCompliantAttribute(false)] + public static void* Realloc(void* ptr, nuint byteCount) { throw null; } + } public readonly struct NFloat : IEquatable { public NFloat(float value) { } diff --git a/src/libraries/System.Runtime.InteropServices/tests/System.Runtime.InteropServices.Tests.csproj b/src/libraries/System.Runtime.InteropServices/tests/System.Runtime.InteropServices.Tests.csproj index e04dab6046036c..9ed8c6ff8a776f 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/System.Runtime.InteropServices.Tests.csproj +++ b/src/libraries/System.Runtime.InteropServices/tests/System.Runtime.InteropServices.Tests.csproj @@ -14,6 +14,7 @@ + diff --git a/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/NativeMemoryTests.cs b/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/NativeMemoryTests.cs new file mode 100644 index 00000000000000..99576ac72c8121 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/NativeMemoryTests.cs @@ -0,0 +1,185 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Xunit; + +namespace System.Runtime.InteropServices.Tests +{ + public unsafe class NativeMemoryTests + { + [Fact] + public void AlignedAllocTest() + { + void* ptr = NativeMemory.AlignedAlloc(1, (uint)sizeof(nuint)); + Assert.True(ptr != null); + NativeMemory.AlignedFree(ptr); + } + + [Fact] + public void AlignedAllocOOMTest() + { + Assert.Throws(() => NativeMemory.AlignedAlloc(nuint.MaxValue - ((uint)sizeof(nuint) - 1), (uint)sizeof(nuint))); + } + + [Fact] + public void AlignedAllocZeroAlignmentTest() + { + Assert.Throws(() => NativeMemory.AlignedAlloc((uint)sizeof(nuint), 0)); + } + + [Fact] + public void AlignedAllocNonPowerOfTwoAlignmentTest() + { + Assert.Throws(() => NativeMemory.AlignedAlloc((uint)sizeof(nuint), (uint)sizeof(nuint) + 1)); + Assert.Throws(() => NativeMemory.AlignedAlloc((uint)sizeof(nuint), (uint)sizeof(nuint) * 3)); + } + + [Fact] + public void AlignedAllocOverflowByteCountTest() + { + // POSIX requires byteCount to be a multiple of alignment and so we will internally upsize. + // This upsizing can overflow for certain values since we do (byteCount + (alignment - 1)) & ~(alignment - 1) + // + // However, this overflow is "harmless" since it will result in a value that is less than alignment + // given that alignment is a power of two and will ultimately be a value less than alignment which + // will be treated as invalid and result in OOM. + // + // Take for example a 64-bit system where the max power of two is (1UL << 63): 9223372036854775808 + // * 9223372036854775808 + 9223372036854775807 == ulong.MaxValue, so no overflow + // * 9223372036854775809 + 9223372036854775807 == 0, so overflows and is less than alignment + // * ulong.MaxValue + 9223372036854775807 == 9223372036854775806, so overflows and is less than alignment + // + // Likewise, for small alignments such as 8 (which is the smallest on a 64-bit system for POSIX): + // * 18446744073709551608 + 7 == ulong.MaxValue, so no overflow + // * 18446744073709551609 + 7 == 0, so overflows and is less than alignment + // * ulong.MaxValue + 7 == 6, so overflows and is less than alignment + + nuint maxAlignment = (nuint)1 << ((sizeof(nuint) * 8) - 1); + Assert.Throws(() => NativeMemory.AlignedAlloc(maxAlignment + 1, maxAlignment)); + + Assert.Throws(() => NativeMemory.AlignedAlloc(nuint.MaxValue, (uint)sizeof(nuint))); + } + + [Fact] + public void AlignedAllocZeroSizeTest() + { + void* ptr = NativeMemory.AlignedAlloc(0, (uint)sizeof(nuint)); + Assert.True(ptr != null); + NativeMemory.AlignedFree(ptr); + } + + [Fact] + public void AlignedFreeTest() + { + // This should not throw + NativeMemory.AlignedFree(null); + } + + [Fact] + public void AllocTest() + { + void* ptr = NativeMemory.Alloc(1); + Assert.True(ptr != null); + NativeMemory.Free(ptr); + } + + [Fact] + public void AllocOOMTest() + { + Assert.Throws(() => NativeMemory.Alloc(nuint.MaxValue)); + } + + [Fact] + public void AllocZeroSizeTest() + { + void* ptr = NativeMemory.Alloc(0); + Assert.True(ptr != null); + NativeMemory.Free(ptr); + } + + [Fact] + public void AllocZeroedTest() + { + void* ptr = NativeMemory.AllocZeroed(1, 1); + + Assert.True(ptr != null); + Assert.Equal(expected: 0, actual: ((byte*)ptr)[0]); + + NativeMemory.Free(ptr); + } + + [Fact] + public void AllocZeroedOOMTest() + { + Assert.Throws(() => NativeMemory.AllocZeroed(1, nuint.MaxValue)); + Assert.Throws(() => NativeMemory.AllocZeroed(nuint.MaxValue, 1)); + } + + [Fact] + public void AllocZeroedZeroElementCountTest() + { + void* ptr = NativeMemory.AllocZeroed(0, 1); + Assert.True(ptr != null); + NativeMemory.Free(ptr); + } + + [Fact] + public void AllocZeroedZeroElementSizeTest() + { + void* ptr = NativeMemory.AllocZeroed(1, 0); + Assert.True(ptr != null); + NativeMemory.Free(ptr); + } + + [Fact] + public void FreeTest() + { + // This should not throw + NativeMemory.Free(null); + } + + [Fact] + public void ReallocTest() + { + void* ptr = NativeMemory.Alloc(1); + Assert.True(ptr != null); + + void* newPtr = NativeMemory.Realloc(ptr, 1); + Assert.True(newPtr != null); + NativeMemory.Free(newPtr); + } + + [Fact] + public void ReallocNullPtrTest() + { + void* ptr = NativeMemory.Realloc(null, 1); + Assert.True(ptr != null); + NativeMemory.Free(ptr); + } + + [Fact] + public void ReallocNullPtrOOMTest() + { + Assert.Throws(() => NativeMemory.Realloc(null, nuint.MaxValue)); + } + + [Fact] + public void ReallocNullPtrZeroSizeTest() + { + void* ptr = NativeMemory.Realloc(null, 0); + Assert.True(ptr != null); + NativeMemory.Free(ptr); + } + + [Fact] + public void ReallocZeroSizeTest() + { + void* ptr = NativeMemory.Alloc(1); + Assert.True(ptr != null); + + void* newPtr = NativeMemory.Realloc(ptr, 0); + Assert.True(newPtr != null); + NativeMemory.Free(newPtr); + } + } +} From d8920f7f8b13bea538ae6b59257537c2ba8647d0 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Wed, 16 Jun 2021 09:40:24 -0700 Subject: [PATCH 02/15] Exposing additional APIs as approved --- THIRD-PARTY-NOTICES.TXT | 26 ++++ .../Windows/Ucrtbase/Interop.MemAlloc.cs | 3 + .../System.Private.CoreLib.Shared.projitems | 1 + .../InteropServices/NativeMemory.Unix.cs | 57 +++++++- .../InteropServices/NativeMemory.Windows.cs | 67 ++++++++- .../Runtime/InteropServices/NativeMemory.cs | 23 +++ .../ref/System.Runtime.InteropServices.cs | 6 + .../InteropServices/NativeMemoryTests.cs | 133 +++++++++++++++++- 8 files changed, 307 insertions(+), 9 deletions(-) create mode 100644 src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.cs diff --git a/THIRD-PARTY-NOTICES.TXT b/THIRD-PARTY-NOTICES.TXT index b0694275b3dbb3..a877e8fb7abfb4 100644 --- a/THIRD-PARTY-NOTICES.TXT +++ b/THIRD-PARTY-NOTICES.TXT @@ -952,3 +952,29 @@ by constants, including codegen instructions. The unsigned division incorporates "round down" optimization per ridiculous_fish. This is free and unencumbered software. Any copyright is dedicated to the Public Domain. + + +License notice for mimalloc +----------------------------------- + +MIT License + +Copyright (c) 2019 Microsoft Corporation, Daan Leijen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/libraries/Common/src/Interop/Windows/Ucrtbase/Interop.MemAlloc.cs b/src/libraries/Common/src/Interop/Windows/Ucrtbase/Interop.MemAlloc.cs index be8a1af882b3d3..23fe143abad308 100644 --- a/src/libraries/Common/src/Interop/Windows/Ucrtbase/Interop.MemAlloc.cs +++ b/src/libraries/Common/src/Interop/Windows/Ucrtbase/Interop.MemAlloc.cs @@ -14,6 +14,9 @@ internal static unsafe partial class Ucrtbase [DllImport(Libraries.Ucrtbase, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] internal static extern void _aligned_free(void* ptr); + [DllImport(Libraries.Ucrtbase, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + internal static extern void* _aligned_realloc(void* ptr, nuint size, nuint alignment); + [DllImport(Libraries.Ucrtbase, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] internal static extern void* calloc(nuint num, nuint size); diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index f29a212a09820a..a7ce085d9a0651 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -824,6 +824,7 @@ + diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Unix.cs index 55c1bb88eb1be2..c26a62230be362 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Unix.cs @@ -7,7 +7,7 @@ namespace System.Runtime.InteropServices { /// This class contains methods that are mainly used to manage native memory. - public static unsafe class NativeMemory + public static unsafe partial class NativeMemory { /// Allocates an aligned block of memory of the specified size and alignment, in bytes. /// The size, in bytes, of the block to allocate. @@ -18,7 +18,7 @@ public static unsafe class NativeMemory /// /// This method allows to be 0 and will return a valid pointer that should not be dereferenced and that should be passed to free to avoid memory leaks. /// This method is a thin wrapper over the C aligned_alloc API or a platform dependent aligned allocation API such as _aligned_malloc on Win32. - /// This method is not compatible with or , instead should be called. + /// This method is not compatible with or , instead or should be called. /// [CLSCompliant(false)] public static void* AlignedAlloc(nuint byteCount, nuint alignment) @@ -62,6 +62,29 @@ public static void AlignedFree(void* ptr) } } + /// Reallocates an aligned block of memory of the specified size and alignment, in bytes. + /// The previously allocated block of memory. + /// The size, in bytes, of the block to allocate. + /// The alignment, in bytes, of the block to allocate. This must be a power of 2. + /// A pointer to the reallocated aligned block of memory. + /// is not a power of two. + /// Reallocating of memory with failed. + /// + /// This method acts as if is null. + /// This method allows to be 0 and will return a valid pointer that should not be dereferenced and that should be passed to free to avoid memory leaks. + /// This method is a platform dependent aligned reallocation API such as _aligned_realloc on Win32. + /// This method is not compatible with or , instead or should be called. + /// + [CLSCompliant(false)] + public static void* AlignedRealloc(void* ptr, nuint byteCount, nuint alignment) + { + void* newPtr = AlignedAlloc(byteCount, alignment); + Buffer.Memmove(ref *(byte*)newPtr, ref *(byte*)ptr, byteCount); + + AlignedFree(ptr); + return newPtr; + } + /// Allocates a block of memory of the specified size, in bytes. /// The size, in bytes, of the block to allocate. /// A pointer to the allocated block of memory. @@ -84,6 +107,36 @@ public static void AlignedFree(void* ptr) return result; } + /// Allocates a block of memory of the specified size, in elements. + /// The count, in elements, of the block to allocate. + /// The size, in bytes, of each element in the allocation. + /// A pointer to the allocated block of memory. + /// Allocating * bytes of memory failed. + /// + /// This method allows and/or to be 0 and will return a valid pointer that should not be dereferenced and that should be passed to free to avoid memory leaks. + /// This method is a thin wrapper over the C malloc API. + /// + [CLSCompliant(false)] + public static void* Alloc(nuint elementCount, nuint elementSize) + { + nuint byteCount = GetByteCount(elementCount, elementSize); + return Alloc(byteCount); + } + + /// Allocates and zeroes a block of memory of the specified size, in bytes. + /// The size, in bytes, of the block to allocate. + /// A pointer to the allocated and zeroed block of memory. + /// Allocating of memory failed. + /// + /// This method allows to be 0 and will return a valid pointer that should not be dereferenced and that should be passed to free to avoid memory leaks. + /// This method is a thin wrapper over the C calloc API. + /// + [CLSCompliant(false)] + public static void* AllocZeroed(nuint byteCount) + { + return AllocZeroed(byteCount, elementSize: 1); + } + /// Allocates and zeroes a block of memory of the specified size, in elements. /// The count, in elements, of the block to allocate. /// The size, in bytes, of each element in the allocation. diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Windows.cs index 02a990e6b54a66..4936a65805ea0c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Windows.cs @@ -7,7 +7,7 @@ namespace System.Runtime.InteropServices { /// This class contains methods that are mainly used to manage native memory. - public static unsafe class NativeMemory + public static unsafe partial class NativeMemory { /// Allocates an aligned block of memory of the specified size and alignment, in bytes. /// The size, in bytes, of the block to allocate. @@ -18,7 +18,7 @@ public static unsafe class NativeMemory /// /// This method allows to be 0 and will return a valid pointer that should not be dereferenced and that should be passed to free to avoid memory leaks. /// This method is a thin wrapper over the C aligned_alloc API or a platform dependent aligned allocation API such as _aligned_malloc on Win32. - /// This method is not compatible with or , instead should be called. + /// This method is not compatible with or , instead or should be called. /// [CLSCompliant(false)] public static void* AlignedAlloc(nuint byteCount, nuint alignment) @@ -52,6 +52,39 @@ public static void AlignedFree(void* ptr) Interop.Ucrtbase._aligned_free(ptr); } + /// Reallocates an aligned block of memory of the specified size and alignment, in bytes. + /// The previously allocated block of memory. + /// The size, in bytes, of the block to allocate. + /// The alignment, in bytes, of the block to allocate. This must be a power of 2. + /// A pointer to the reallocated aligned block of memory. + /// is not a power of two. + /// Reallocating of memory with failed. + /// + /// This method acts as if is null. + /// This method allows to be 0 and will return a valid pointer that should not be dereferenced and that should be passed to free to avoid memory leaks. + /// This method is a platform dependent aligned reallocation API such as _aligned_realloc on Win32. + /// This method is not compatible with or , instead or should be called. + /// + [CLSCompliant(false)] + public static void* AlignedRealloc(void* ptr, nuint byteCount, nuint alignment) + { + if (!BitOperations.IsPow2(alignment)) + { + // The C standard doesn't define what a valid alignment is, however Windows and POSIX The Windows implementation requires a power of 2 + ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_AlignmentMustBePow2); + } + + // Unlike the C standard and POSIX, Windows does not requires size to be a multiple of alignment. However, we do want an "empty" allocation for zero + void* result = Interop.Ucrtbase._aligned_realloc(ptr, (byteCount != 0) ? byteCount : 1, alignment); + + if (result == null) + { + ThrowHelper.ThrowOutOfMemoryException(); + } + + return result; + } + /// Allocates a block of memory of the specified size, in bytes. /// The size, in bytes, of the block to allocate. /// A pointer to the allocated block of memory. @@ -74,6 +107,36 @@ public static void AlignedFree(void* ptr) return result; } + /// Allocates a block of memory of the specified size, in elements. + /// The count, in elements, of the block to allocate. + /// The size, in bytes, of each element in the allocation. + /// A pointer to the allocated block of memory. + /// Allocating * bytes of memory failed. + /// + /// This method allows and/or to be 0 and will return a valid pointer that should not be dereferenced and that should be passed to free to avoid memory leaks. + /// This method is a thin wrapper over the C malloc API. + /// + [CLSCompliant(false)] + public static void* Alloc(nuint elementCount, nuint elementSize) + { + nuint byteCount = GetByteCount(elementCount, elementSize); + return Alloc(byteCount); + } + + /// Allocates and zeroes a block of memory of the specified size, in bytes. + /// The size, in bytes, of the block to allocate. + /// A pointer to the allocated and zeroed block of memory. + /// Allocating of memory failed. + /// + /// This method allows to be 0 and will return a valid pointer that should not be dereferenced and that should be passed to free to avoid memory leaks. + /// This method is a thin wrapper over the C calloc API. + /// + [CLSCompliant(false)] + public static void* AllocZeroed(nuint byteCount) + { + return AllocZeroed(byteCount, elementSize: 1); + } + /// Allocates and zeroes a block of memory of the specified size, in elements. /// The count, in elements, of the block to allocate. /// The size, in bytes, of each element in the allocation. diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.cs new file mode 100644 index 00000000000000..277ea7f8ae0a27 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace System.Runtime.InteropServices +{ + public static unsafe partial class NativeMemory + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static nuint GetByteCount(nuint elementCount, nuint elementSize) + { + // This is based on the `mi_count_size_overflow` and `mi_mul_overflow` methods from microsoft/mimalloc. + // Original source is Copyright (c) 2019 Microsoft Corporation, Daan Leijen. Licensed under the MIT license + + // sqrt(nuint.MaxValue) + nuint multiplyNoOverflow = (nuint)1 << (4 * sizeof(nuint)); + + return ((elementSize >= multiplyNoOverflow) || (elementCount >= multiplyNoOverflow)) && (elementSize > 0) && ((nuint.MaxValue / elementSize) < elementCount) ? nuint.MaxValue : (elementCount * elementSize); + } + } +} diff --git a/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs b/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs index 2a350ac9aa2751..02e739ad731733 100644 --- a/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs +++ b/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs @@ -767,8 +767,14 @@ public static unsafe partial class NativeMemory [System.CLSCompliantAttribute(false)] public static void AlignedFree(void* ptr) { } [System.CLSCompliantAttribute(false)] + public static void* AlignedRealloc(void* ptr, nuint byteCount, nuint alignment) { throw null; } + [System.CLSCompliantAttribute(false)] public static void* Alloc(nuint byteCount) { throw null; } [System.CLSCompliantAttribute(false)] + public static void* Alloc(nuint elementCount, nuint elementSize) { throw null; } + [System.CLSCompliantAttribute(false)] + public static void* AllocZeroed(nuint byteCount) { throw null; } + [System.CLSCompliantAttribute(false)] public static void* AllocZeroed(nuint elementCount, nuint elementSize) { throw null; } [System.CLSCompliantAttribute(false)] public static void Free(void* ptr) { } diff --git a/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/NativeMemoryTests.cs b/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/NativeMemoryTests.cs index 99576ac72c8121..5ebe4316d506df 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/NativeMemoryTests.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/NativeMemoryTests.cs @@ -76,7 +76,72 @@ public void AlignedFreeTest() } [Fact] - public void AllocTest() + public void AlignedReallocTest() + { + void* ptr = NativeMemory.AlignedAlloc(1, (uint)sizeof(nuint)); + Assert.True(ptr != null); + + void* newPtr = NativeMemory.AlignedRealloc(ptr, 1, (uint)sizeof(nuint)); + Assert.True(newPtr != null); + NativeMemory.AlignedFree(newPtr); + } + + [Fact] + public void AlignedReallocNullPtrTest() + { + void* ptr = NativeMemory.AlignedRealloc(null, 1, (uint)sizeof(nuint)); + Assert.True(ptr != null); + NativeMemory.AlignedFree(ptr); + } + + [Fact] + public void AlignedReallocNullPtrOOMTest() + { + Assert.Throws(() => NativeMemory.AlignedRealloc(null, nuint.MaxValue, (uint)sizeof(nuint))); + } + + [Fact] + public void AlignedReallocNullPtrZeroSizeTest() + { + void* ptr = NativeMemory.AlignedRealloc(null, 0, (uint)sizeof(nuint)); + Assert.True(ptr != null); + NativeMemory.AlignedFree(ptr); + } + + [Fact] + public void AlignedReallocZeroAlignmentTest() + { + void* ptr = NativeMemory.AlignedAlloc(1, (uint)sizeof(nuint)); + Assert.True(ptr != null); + + Assert.Throws(() => NativeMemory.AlignedRealloc(ptr, (uint)sizeof(nuint), 0)); + NativeMemory.AlignedFree(ptr); + } + + [Fact] + public void AlignedReallocNonPowerOfTwoAlignmentTest() + { + void* ptr = NativeMemory.AlignedAlloc(1, (uint)sizeof(nuint)); + Assert.True(ptr != null); + + Assert.Throws(() => NativeMemory.AlignedRealloc(ptr, (uint)sizeof(nuint), (uint)sizeof(nuint) + 1)); + Assert.Throws(() => NativeMemory.AlignedRealloc(ptr, (uint)sizeof(nuint), (uint)sizeof(nuint) * 3)); + NativeMemory.AlignedFree(ptr); + } + + [Fact] + public void AlignedReallocZeroSizeTest() + { + void* ptr = NativeMemory.AlignedAlloc(1, (uint)sizeof(nuint)); + Assert.True(ptr != null); + + void* newPtr = NativeMemory.AlignedRealloc(ptr, 0, (uint)sizeof(nuint)); + Assert.True(newPtr != null); + NativeMemory.AlignedFree(newPtr); + } + + [Fact] + public void AllocByteCountTest() { void* ptr = NativeMemory.Alloc(1); Assert.True(ptr != null); @@ -84,13 +149,29 @@ public void AllocTest() } [Fact] - public void AllocOOMTest() + public void AllocElementCountTest() + { + void* ptr = NativeMemory.Alloc(1, 1); + Assert.True(ptr != null); + NativeMemory.Free(ptr); + } + + [Fact] + public void AllocByteCountOOMTest() { Assert.Throws(() => NativeMemory.Alloc(nuint.MaxValue)); } [Fact] - public void AllocZeroSizeTest() + public void AllocElementCountOOMTest() + { + Assert.Throws(() => NativeMemory.Alloc(1, nuint.MaxValue)); + Assert.Throws(() => NativeMemory.Alloc(nuint.MaxValue, 1)); + Assert.Throws(() => NativeMemory.Alloc(nuint.MaxValue, nuint.MaxValue)); + } + + [Fact] + public void AllocZeroByteCountTest() { void* ptr = NativeMemory.Alloc(0); Assert.True(ptr != null); @@ -98,7 +179,34 @@ public void AllocZeroSizeTest() } [Fact] - public void AllocZeroedTest() + public void AllocZeroElementCountTest() + { + void* ptr = NativeMemory.Alloc(0, 1); + Assert.True(ptr != null); + NativeMemory.Free(ptr); + } + + [Fact] + public void AllocZeroElementSizeTest() + { + void* ptr = NativeMemory.Alloc(1, 0); + Assert.True(ptr != null); + NativeMemory.Free(ptr); + } + + [Fact] + public void AllocZeroedByteCountTest() + { + void* ptr = NativeMemory.AllocZeroed(1); + + Assert.True(ptr != null); + Assert.Equal(expected: 0, actual: ((byte*)ptr)[0]); + + NativeMemory.Free(ptr); + } + + [Fact] + public void AllocZeroedElementCountTest() { void* ptr = NativeMemory.AllocZeroed(1, 1); @@ -109,10 +217,25 @@ public void AllocZeroedTest() } [Fact] - public void AllocZeroedOOMTest() + public void AllocZeroedByteCountOOMTest() + { + Assert.Throws(() => NativeMemory.AllocZeroed(nuint.MaxValue)); + } + + [Fact] + public void AllocZeroedElementCountOOMTest() { Assert.Throws(() => NativeMemory.AllocZeroed(1, nuint.MaxValue)); Assert.Throws(() => NativeMemory.AllocZeroed(nuint.MaxValue, 1)); + Assert.Throws(() => NativeMemory.AllocZeroed(nuint.MaxValue, nuint.MaxValue)); + } + + [Fact] + public void AllocZeroedZeroByteCountTest() + { + void* ptr = NativeMemory.AllocZeroed(0); + Assert.True(ptr != null); + NativeMemory.Free(ptr); } [Fact] From 7c03e374b04686888ecbbbfdcd98e23c8dc7bffc Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Wed, 16 Jun 2021 09:44:19 -0700 Subject: [PATCH 03/15] Ensure we have a test covering alignment and size being less than sizeof(void*) --- .../InteropServices/NativeMemory.Unix.cs | 3 ++- .../InteropServices/NativeMemoryTests.cs | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Unix.cs index c26a62230be362..f7c4a0a5e26a1e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Unix.cs @@ -37,7 +37,8 @@ public static unsafe partial class NativeMemory // we should only overflow for byteCount > (nuint.MaxValue & ~(alignment - 1)). When such an overflow occurs we will // get a result that is less than alignment which will cause the allocation to fail. - void* result = Interop.Sys.AlignedAlloc(Math.Max(alignment, sizeof(void*)), (byteCount != 0) ? (byteCount + (alignment - 1)) & ~(alignment - 1) : alignment); + var adjustedAlignment = Math.Max(alignment, sizeof(void*)); + void* result = Interop.Sys.AlignedAlloc(adjustedAlignment, (byteCount != 0) ? (byteCount + (alignment - 1)) & ~(alignment - 1) : adjustedAlignment); if (result == null) { diff --git a/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/NativeMemoryTests.cs b/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/NativeMemoryTests.cs index 5ebe4316d506df..3fb2f6724dc62d 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/NativeMemoryTests.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/NativeMemoryTests.cs @@ -15,6 +15,14 @@ public void AlignedAllocTest() NativeMemory.AlignedFree(ptr); } + [Fact] + public void AlignedAllocLessThanVoidPtrAlignmentTest() + { + void* ptr = NativeMemory.AlignedAlloc(1, 1); + Assert.True(ptr != null); + NativeMemory.AlignedFree(ptr); + } + [Fact] public void AlignedAllocOOMTest() { @@ -86,6 +94,17 @@ public void AlignedReallocTest() NativeMemory.AlignedFree(newPtr); } + [Fact] + public void AlignedReallocLessThanVoidPtrAlignmentTest() + { + void* ptr = NativeMemory.AlignedAlloc(1, (uint)sizeof(nuint)); + Assert.True(ptr != null); + + void* newPtr = NativeMemory.AlignedRealloc(ptr, 1, 1); + Assert.True(newPtr != null); + NativeMemory.AlignedFree(newPtr); + } + [Fact] public void AlignedReallocNullPtrTest() { From 5cb44d90102f8236f31cdb92e6f62c2920ae23ec Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Wed, 16 Jun 2021 10:48:52 -0700 Subject: [PATCH 04/15] Update src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Unix.cs Co-authored-by: Jan Kotas --- .../src/System/Runtime/InteropServices/NativeMemory.Unix.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Unix.cs index f7c4a0a5e26a1e..f87a1989d89b9f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Unix.cs @@ -38,7 +38,7 @@ public static unsafe partial class NativeMemory // get a result that is less than alignment which will cause the allocation to fail. var adjustedAlignment = Math.Max(alignment, sizeof(void*)); - void* result = Interop.Sys.AlignedAlloc(adjustedAlignment, (byteCount != 0) ? (byteCount + (alignment - 1)) & ~(alignment - 1) : adjustedAlignment); + void* result = Interop.Sys.AlignedAlloc(adjustedAlignment, (byteCount != 0) ? (byteCount + (adjustedAlignment - 1)) & ~(adjustedAlignment - 1) : adjustedAlignment); if (result == null) { From f04bc2e1564ce75a4f9a704ed5b087bb33de693b Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Wed, 16 Jun 2021 10:50:51 -0700 Subject: [PATCH 05/15] Responding to PR feedback --- .../Native/Unix/System.Native/pal_memory.h | 8 ++-- .../InteropServices/NativeMemory.Unix.cs | 30 -------------- .../InteropServices/NativeMemory.Windows.cs | 40 ++++--------------- .../Runtime/InteropServices/NativeMemory.cs | 30 ++++++++++++++ 4 files changed, 42 insertions(+), 66 deletions(-) diff --git a/src/libraries/Native/Unix/System.Native/pal_memory.h b/src/libraries/Native/Unix/System.Native/pal_memory.h index 81037b2037b811..f739c3ed996e50 100644 --- a/src/libraries/Native/Unix/System.Native/pal_memory.h +++ b/src/libraries/Native/Unix/System.Native/pal_memory.h @@ -27,14 +27,14 @@ PALEXPORT void* SystemNative_Calloc(uintptr_t num, uintptr_t size); PALEXPORT void SystemNative_Free(void* ptr); /** - * C runtime memset + * C runtime malloc */ -PALEXPORT void* SystemNative_MemSet(void* s, int c, uintptr_t n); +PALEXPORT void* SystemNative_Malloc(uintptr_t size); /** - * C runtime malloc + * C runtime memset */ -PALEXPORT void* SystemNative_Malloc(uintptr_t size); +PALEXPORT void* SystemNative_MemSet(void* s, int c, uintptr_t n); /** * C runtime realloc diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Unix.cs index f87a1989d89b9f..94f02da24a9be6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Unix.cs @@ -108,36 +108,6 @@ public static void AlignedFree(void* ptr) return result; } - /// Allocates a block of memory of the specified size, in elements. - /// The count, in elements, of the block to allocate. - /// The size, in bytes, of each element in the allocation. - /// A pointer to the allocated block of memory. - /// Allocating * bytes of memory failed. - /// - /// This method allows and/or to be 0 and will return a valid pointer that should not be dereferenced and that should be passed to free to avoid memory leaks. - /// This method is a thin wrapper over the C malloc API. - /// - [CLSCompliant(false)] - public static void* Alloc(nuint elementCount, nuint elementSize) - { - nuint byteCount = GetByteCount(elementCount, elementSize); - return Alloc(byteCount); - } - - /// Allocates and zeroes a block of memory of the specified size, in bytes. - /// The size, in bytes, of the block to allocate. - /// A pointer to the allocated and zeroed block of memory. - /// Allocating of memory failed. - /// - /// This method allows to be 0 and will return a valid pointer that should not be dereferenced and that should be passed to free to avoid memory leaks. - /// This method is a thin wrapper over the C calloc API. - /// - [CLSCompliant(false)] - public static void* AllocZeroed(nuint byteCount) - { - return AllocZeroed(byteCount, elementSize: 1); - } - /// Allocates and zeroes a block of memory of the specified size, in elements. /// The count, in elements, of the block to allocate. /// The size, in bytes, of each element in the allocation. diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Windows.cs index 4936a65805ea0c..7fbaf933ef924f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Windows.cs @@ -49,7 +49,10 @@ public static unsafe partial class NativeMemory [CLSCompliant(false)] public static void AlignedFree(void* ptr) { - Interop.Ucrtbase._aligned_free(ptr); + if (ptr != null) + { + Interop.Ucrtbase._aligned_free(ptr); + } } /// Reallocates an aligned block of memory of the specified size and alignment, in bytes. @@ -107,36 +110,6 @@ public static void AlignedFree(void* ptr) return result; } - /// Allocates a block of memory of the specified size, in elements. - /// The count, in elements, of the block to allocate. - /// The size, in bytes, of each element in the allocation. - /// A pointer to the allocated block of memory. - /// Allocating * bytes of memory failed. - /// - /// This method allows and/or to be 0 and will return a valid pointer that should not be dereferenced and that should be passed to free to avoid memory leaks. - /// This method is a thin wrapper over the C malloc API. - /// - [CLSCompliant(false)] - public static void* Alloc(nuint elementCount, nuint elementSize) - { - nuint byteCount = GetByteCount(elementCount, elementSize); - return Alloc(byteCount); - } - - /// Allocates and zeroes a block of memory of the specified size, in bytes. - /// The size, in bytes, of the block to allocate. - /// A pointer to the allocated and zeroed block of memory. - /// Allocating of memory failed. - /// - /// This method allows to be 0 and will return a valid pointer that should not be dereferenced and that should be passed to free to avoid memory leaks. - /// This method is a thin wrapper over the C calloc API. - /// - [CLSCompliant(false)] - public static void* AllocZeroed(nuint byteCount) - { - return AllocZeroed(byteCount, elementSize: 1); - } - /// Allocates and zeroes a block of memory of the specified size, in elements. /// The count, in elements, of the block to allocate. /// The size, in bytes, of each element in the allocation. @@ -169,7 +142,10 @@ public static void AlignedFree(void* ptr) [CLSCompliant(false)] public static void Free(void* ptr) { - Interop.Ucrtbase.free(ptr); + if (ptr != null) + { + Interop.Ucrtbase.free(ptr); + } } /// Reallocates a block of memory to be the specified size, in bytes. diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.cs index 277ea7f8ae0a27..b46655ec58c00a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.cs @@ -8,6 +8,36 @@ namespace System.Runtime.InteropServices { public static unsafe partial class NativeMemory { + /// Allocates a block of memory of the specified size, in elements. + /// The count, in elements, of the block to allocate. + /// The size, in bytes, of each element in the allocation. + /// A pointer to the allocated block of memory. + /// Allocating * bytes of memory failed. + /// + /// This method allows and/or to be 0 and will return a valid pointer that should not be dereferenced and that should be passed to free to avoid memory leaks. + /// This method is a thin wrapper over the C malloc API. + /// + [CLSCompliant(false)] + public static void* Alloc(nuint elementCount, nuint elementSize) + { + nuint byteCount = GetByteCount(elementCount, elementSize); + return Alloc(byteCount); + } + + /// Allocates and zeroes a block of memory of the specified size, in bytes. + /// The size, in bytes, of the block to allocate. + /// A pointer to the allocated and zeroed block of memory. + /// Allocating of memory failed. + /// + /// This method allows to be 0 and will return a valid pointer that should not be dereferenced and that should be passed to free to avoid memory leaks. + /// This method is a thin wrapper over the C calloc API. + /// + [CLSCompliant(false)] + public static void* AllocZeroed(nuint byteCount) + { + return AllocZeroed(byteCount, elementSize: 1); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static nuint GetByteCount(nuint elementCount, nuint elementSize) { From 05fc04794ebb8a0132e803fd669d7adfc4a93da4 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Wed, 16 Jun 2021 11:02:25 -0700 Subject: [PATCH 06/15] Adding additional alignment test coverage for 1 to 16384 --- .../InteropServices/NativeMemoryTests.cs | 76 ++++++++++++++++--- 1 file changed, 66 insertions(+), 10 deletions(-) diff --git a/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/NativeMemoryTests.cs b/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/NativeMemoryTests.cs index 3fb2f6724dc62d..2f38aeb50a3be7 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/NativeMemoryTests.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/NativeMemoryTests.cs @@ -7,11 +7,29 @@ namespace System.Runtime.InteropServices.Tests { public unsafe class NativeMemoryTests { - [Fact] - public void AlignedAllocTest() - { - void* ptr = NativeMemory.AlignedAlloc(1, (uint)sizeof(nuint)); + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(4)] + [InlineData(8)] + [InlineData(16)] + [InlineData(32)] + [InlineData(64)] + [InlineData(128)] + [InlineData(256)] + [InlineData(512)] + [InlineData(1024)] + [InlineData(2048)] + [InlineData(4096)] + [InlineData(8192)] + [InlineData(16384)] + public void AlignedAllocTest(uint alignment) + { + void* ptr = NativeMemory.AlignedAlloc(1, alignment); + Assert.True(ptr != null); + Assert.True((nuint)ptr % alignment == 0); + NativeMemory.AlignedFree(ptr); } @@ -72,7 +90,10 @@ public void AlignedAllocOverflowByteCountTest() public void AlignedAllocZeroSizeTest() { void* ptr = NativeMemory.AlignedAlloc(0, (uint)sizeof(nuint)); + Assert.True(ptr != null); + Assert.True((nuint)ptr % (uint)sizeof(nuint) == 0); + NativeMemory.AlignedFree(ptr); } @@ -83,21 +104,41 @@ public void AlignedFreeTest() NativeMemory.AlignedFree(null); } - [Fact] - public void AlignedReallocTest() - { - void* ptr = NativeMemory.AlignedAlloc(1, (uint)sizeof(nuint)); + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(4)] + [InlineData(8)] + [InlineData(16)] + [InlineData(32)] + [InlineData(64)] + [InlineData(128)] + [InlineData(256)] + [InlineData(512)] + [InlineData(1024)] + [InlineData(2048)] + [InlineData(4096)] + [InlineData(8192)] + [InlineData(16384)] + public void AlignedReallocTest(uint alignment) + { + void* ptr = NativeMemory.AlignedAlloc(1, alignment); + Assert.True(ptr != null); + Assert.True((nuint)ptr % alignment == 0); + + void* newPtr = NativeMemory.AlignedRealloc(ptr, 1, alignment); - void* newPtr = NativeMemory.AlignedRealloc(ptr, 1, (uint)sizeof(nuint)); Assert.True(newPtr != null); + Assert.True((nuint)newPtr % alignment == 0); + NativeMemory.AlignedFree(newPtr); } [Fact] public void AlignedReallocLessThanVoidPtrAlignmentTest() { - void* ptr = NativeMemory.AlignedAlloc(1, (uint)sizeof(nuint)); + void* ptr = NativeMemory.AlignedAlloc(1, 1); Assert.True(ptr != null); void* newPtr = NativeMemory.AlignedRealloc(ptr, 1, 1); @@ -109,7 +150,10 @@ public void AlignedReallocLessThanVoidPtrAlignmentTest() public void AlignedReallocNullPtrTest() { void* ptr = NativeMemory.AlignedRealloc(null, 1, (uint)sizeof(nuint)); + Assert.True(ptr != null); + Assert.True((nuint)ptr % (uint)sizeof(nuint) == 0); + NativeMemory.AlignedFree(ptr); } @@ -123,7 +167,10 @@ public void AlignedReallocNullPtrOOMTest() public void AlignedReallocNullPtrZeroSizeTest() { void* ptr = NativeMemory.AlignedRealloc(null, 0, (uint)sizeof(nuint)); + Assert.True(ptr != null); + Assert.True((nuint)ptr % (uint)sizeof(nuint) == 0); + NativeMemory.AlignedFree(ptr); } @@ -131,7 +178,9 @@ public void AlignedReallocNullPtrZeroSizeTest() public void AlignedReallocZeroAlignmentTest() { void* ptr = NativeMemory.AlignedAlloc(1, (uint)sizeof(nuint)); + Assert.True(ptr != null); + Assert.True((nuint)ptr % (uint)sizeof(nuint) == 0); Assert.Throws(() => NativeMemory.AlignedRealloc(ptr, (uint)sizeof(nuint), 0)); NativeMemory.AlignedFree(ptr); @@ -141,7 +190,9 @@ public void AlignedReallocZeroAlignmentTest() public void AlignedReallocNonPowerOfTwoAlignmentTest() { void* ptr = NativeMemory.AlignedAlloc(1, (uint)sizeof(nuint)); + Assert.True(ptr != null); + Assert.True((nuint)ptr % (uint)sizeof(nuint) == 0); Assert.Throws(() => NativeMemory.AlignedRealloc(ptr, (uint)sizeof(nuint), (uint)sizeof(nuint) + 1)); Assert.Throws(() => NativeMemory.AlignedRealloc(ptr, (uint)sizeof(nuint), (uint)sizeof(nuint) * 3)); @@ -152,10 +203,15 @@ public void AlignedReallocNonPowerOfTwoAlignmentTest() public void AlignedReallocZeroSizeTest() { void* ptr = NativeMemory.AlignedAlloc(1, (uint)sizeof(nuint)); + Assert.True(ptr != null); + Assert.True((nuint)ptr % (uint)sizeof(nuint) == 0); void* newPtr = NativeMemory.AlignedRealloc(ptr, 0, (uint)sizeof(nuint)); + Assert.True(newPtr != null); + Assert.True((nuint)newPtr % (uint)sizeof(nuint) == 0); + NativeMemory.AlignedFree(newPtr); } From 7edb6ccb9e058a9e6e78d7ebbed49eb1928b8f7b Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Wed, 16 Jun 2021 11:35:15 -0700 Subject: [PATCH 07/15] Add coverage for 65k and 1/2/4MB alignments --- .../InteropServices/NativeMemoryTests.cs | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/NativeMemoryTests.cs b/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/NativeMemoryTests.cs index 2f38aeb50a3be7..55b86cea833e4a 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/NativeMemoryTests.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/NativeMemoryTests.cs @@ -18,11 +18,15 @@ public unsafe class NativeMemoryTests [InlineData(128)] [InlineData(256)] [InlineData(512)] - [InlineData(1024)] - [InlineData(2048)] - [InlineData(4096)] - [InlineData(8192)] - [InlineData(16384)] + [InlineData(1 * 1024)] + [InlineData(2 * 1024)] + [InlineData(4 * 1024)] + [InlineData(8 * 1024)] + [InlineData(16 * 1024)] + [InlineData(64 * 1024)] + [InlineData(1 * 1024 * 1024)] + [InlineData(2 * 1024 * 1024)] + [InlineData(4 * 1024 * 1024)] public void AlignedAllocTest(uint alignment) { void* ptr = NativeMemory.AlignedAlloc(1, alignment); @@ -115,11 +119,15 @@ public void AlignedFreeTest() [InlineData(128)] [InlineData(256)] [InlineData(512)] - [InlineData(1024)] - [InlineData(2048)] - [InlineData(4096)] - [InlineData(8192)] - [InlineData(16384)] + [InlineData(1 * 1024)] + [InlineData(2 * 1024)] + [InlineData(4 * 1024)] + [InlineData(8 * 1024)] + [InlineData(16 * 1024)] + [InlineData(64 * 1024)] + [InlineData(1 * 1024 * 1024)] + [InlineData(2 * 1024 * 1024)] + [InlineData(4 * 1024 * 1024)] public void AlignedReallocTest(uint alignment) { void* ptr = NativeMemory.AlignedAlloc(1, alignment); From 924ea3c46ac7d2996c39c02d432f6a9ea968f037 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Wed, 16 Jun 2021 12:24:54 -0700 Subject: [PATCH 08/15] Fixing the Native\Unix\System.Native\CMakeLists.txt --- src/libraries/Native/Unix/System.Native/CMakeLists.txt | 3 --- .../src/System/Runtime/InteropServices/NativeMemory.Unix.cs | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/libraries/Native/Unix/System.Native/CMakeLists.txt b/src/libraries/Native/Unix/System.Native/CMakeLists.txt index 7dc5203a311390..d21e27a561b0f4 100644 --- a/src/libraries/Native/Unix/System.Native/CMakeLists.txt +++ b/src/libraries/Native/Unix/System.Native/CMakeLists.txt @@ -1,8 +1,5 @@ project(System.Native C) -include(configure.cmake) -include_directories(${CMAKE_CURRENT_BINARY_DIR}) - if (NOT CLR_CMAKE_TARGET_MACCATALYST AND NOT CLR_CMAKE_TARGET_IOS AND NOT CLR_CMAKE_TARGET_TVOS) add_definitions(-DHAS_CONSOLE_SIGNALS) endif () diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Unix.cs index 94f02da24a9be6..3874de97734f5f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Unix.cs @@ -37,7 +37,7 @@ public static unsafe partial class NativeMemory // we should only overflow for byteCount > (nuint.MaxValue & ~(alignment - 1)). When such an overflow occurs we will // get a result that is less than alignment which will cause the allocation to fail. - var adjustedAlignment = Math.Max(alignment, sizeof(void*)); + var adjustedAlignment = Math.Max(alignment, (uint)sizeof(void*)); void* result = Interop.Sys.AlignedAlloc(adjustedAlignment, (byteCount != 0) ? (byteCount + (adjustedAlignment - 1)) & ~(adjustedAlignment - 1) : adjustedAlignment); if (result == null) From 1e075152dfcd26aed3bc47f04f3eb24ab31c630f Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Wed, 16 Jun 2021 13:12:23 -0700 Subject: [PATCH 09/15] Update src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Unix.cs Co-authored-by: Jan Kotas --- .../src/System/Runtime/InteropServices/NativeMemory.Unix.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Unix.cs index 3874de97734f5f..78df6211c2efe5 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Unix.cs @@ -37,7 +37,7 @@ public static unsafe partial class NativeMemory // we should only overflow for byteCount > (nuint.MaxValue & ~(alignment - 1)). When such an overflow occurs we will // get a result that is less than alignment which will cause the allocation to fail. - var adjustedAlignment = Math.Max(alignment, (uint)sizeof(void*)); + nuint adjustedAlignment = Math.Max(alignment, (uint)sizeof(void*)); void* result = Interop.Sys.AlignedAlloc(adjustedAlignment, (byteCount != 0) ? (byteCount + (adjustedAlignment - 1)) & ~(adjustedAlignment - 1) : adjustedAlignment); if (result == null) From 242ecca74ee798a980a6807b11ebe117fc8c2c3a Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Wed, 16 Jun 2021 14:18:18 -0700 Subject: [PATCH 10/15] Don't call Buffer.Memmove in NativeMemory.AlignedRealloc if ptr is null --- .../System/Runtime/InteropServices/NativeMemory.Unix.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Unix.cs index 78df6211c2efe5..5fe2ecc2c7162b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Unix.cs @@ -80,9 +80,13 @@ public static void AlignedFree(void* ptr) public static void* AlignedRealloc(void* ptr, nuint byteCount, nuint alignment) { void* newPtr = AlignedAlloc(byteCount, alignment); - Buffer.Memmove(ref *(byte*)newPtr, ref *(byte*)ptr, byteCount); - AlignedFree(ptr); + if (ptr != null) + { + Buffer.Memmove(ref *(byte*)newPtr, ref *(byte*)ptr, byteCount); + Interop.Sys.AlignedFree(ptr); + } + return newPtr; } From b2601fe0311e4ae758b240833dee472d3b0508a5 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Wed, 16 Jun 2021 15:09:09 -0700 Subject: [PATCH 11/15] Updating NativeMemory.AlignedRealloc to correctly copy only the size of the last allocation --- .../Unix/System.Native/Interop.MemAlloc.cs | 3 ++ .../Native/Unix/Common/pal_config.h.in | 2 + .../Native/Unix/System.Native/entrypoints.c | 1 + .../Native/Unix/System.Native/pal_memory.c | 38 ++++++++++++++- .../Native/Unix/System.Native/pal_memory.h | 14 +++++- src/libraries/Native/Unix/configure.cmake | 29 +++++++++-- .../InteropServices/NativeMemory.Unix.cs | 24 ++++++++-- .../InteropServices/NativeMemoryTests.cs | 48 +++++++++++++++++++ 8 files changed, 147 insertions(+), 12 deletions(-) diff --git a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.MemAlloc.cs b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.MemAlloc.cs index 871baa2bedd135..cb4a38ff4ea0fb 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.MemAlloc.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.MemAlloc.cs @@ -14,6 +14,9 @@ internal static unsafe partial class Sys [DllImport(Interop.Libraries.SystemNative, EntryPoint = "SystemNative_AlignedFree")] internal static extern void AlignedFree(void* ptr); + [DllImport(Interop.Libraries.SystemNative, EntryPoint = "SystemNative_AlignedRealloc")] + internal static extern void* AlignedRealloc(void* ptr, nuint alignment, nuint new_size); + [DllImport(Interop.Libraries.SystemNative, EntryPoint = "SystemNative_Calloc")] internal static extern void* Calloc(nuint num, nuint size); diff --git a/src/libraries/Native/Unix/Common/pal_config.h.in b/src/libraries/Native/Unix/Common/pal_config.h.in index adef17508cdf51..8d386a33280024 100644 --- a/src/libraries/Native/Unix/Common/pal_config.h.in +++ b/src/libraries/Native/Unix/Common/pal_config.h.in @@ -123,6 +123,8 @@ #cmakedefine01 HAVE_SYS_PROCINFO_H #cmakedefine01 HAVE_IOSS_H #cmakedefine01 HAVE_ALIGNED_ALLOC +#cmakedefine01 HAVE_MALLOC_SIZE +#cmakedefine01 HAVE_MALLOC_USABLE_SIZE #cmakedefine01 HAVE_POSIX_MEMALIGN // Mac OS X has stat64, but it is deprecated since plain stat now diff --git a/src/libraries/Native/Unix/System.Native/entrypoints.c b/src/libraries/Native/Unix/System.Native/entrypoints.c index 354ee8d67d5de3..84c1bcc387fd54 100644 --- a/src/libraries/Native/Unix/System.Native/entrypoints.c +++ b/src/libraries/Native/Unix/System.Native/entrypoints.c @@ -110,6 +110,7 @@ static const Entry s_sysNative[] = DllImportEntry(SystemNative_Log) DllImportEntry(SystemNative_AlignedAlloc) DllImportEntry(SystemNative_AlignedFree) + DllImportEntry(SystemNative_AlignedRealloc) DllImportEntry(SystemNative_Calloc) DllImportEntry(SystemNative_Free) DllImportEntry(SystemNative_Malloc) diff --git a/src/libraries/Native/Unix/System.Native/pal_memory.c b/src/libraries/Native/Unix/System.Native/pal_memory.c index 9b1b34d8c065a8..b77ab1bc450895 100644 --- a/src/libraries/Native/Unix/System.Native/pal_memory.c +++ b/src/libraries/Native/Unix/System.Native/pal_memory.c @@ -4,12 +4,21 @@ #include "pal_config.h" #include "pal_memory.h" +#include #include #include +#if HAVE_MALLOC_USABLE_SIZE + #include +#elif HAVE_MALLOC_SIZE + #include +#else + #error "Platform doesn't support malloc_usable_size or malloc_size" +#endif + void* SystemNative_AlignedAlloc(uintptr_t alignment, uintptr_t size) { -#if HAVE_ALIGNED_ALLOC && !defined(__APPLE__) +#if HAVE_ALIGNED_ALLOC // We want to prefer the standardized aligned_alloc function. However // it cannot be used on __APPLE__ since we target 10.13 and it was // only added in 10.15, but we might be compiling on a 10.15 box. @@ -28,6 +37,22 @@ void SystemNative_AlignedFree(void* ptr) free(ptr); } +void* SystemNative_AlignedRealloc(void* ptr, uintptr_t alignment, uintptr_t new_size) +{ + void* result = SystemNative_AlignedAlloc(alignment, new_size); + + if (result != NULL) + { + uintptr_t old_size = SystemNative_GetUsableSize(ptr); + assert((ptr != NULL) || (old_size == 0)); + + memcpy(result, ptr, (new_size < old_size) ? new_size : old_size); + SystemNative_AlignedFree(ptr); + } + + return result; +} + void* SystemNative_Calloc(uintptr_t num, uintptr_t size) { return calloc(num, size); @@ -38,6 +63,17 @@ void SystemNative_Free(void* ptr) free(ptr); } +uintptr_t SystemNative_GetUsableSize(void* ptr) +{ +#if HAVE_MALLOC_USABLE_SIZE + return malloc_usable_size(ptr); +#elif HAVE_MALLOC_SIZE + return malloc_size(ptr); +#else + #error "Platform doesn't support malloc_usable_size or malloc_size" +#endif +} + void* SystemNative_Malloc(uintptr_t size) { return malloc(size); diff --git a/src/libraries/Native/Unix/System.Native/pal_memory.h b/src/libraries/Native/Unix/System.Native/pal_memory.h index f739c3ed996e50..2cc4c1d8635be9 100644 --- a/src/libraries/Native/Unix/System.Native/pal_memory.h +++ b/src/libraries/Native/Unix/System.Native/pal_memory.h @@ -12,10 +12,15 @@ PALEXPORT void* SystemNative_AlignedAlloc(uintptr_t alignment, uintptr_t size); /** - * C runtime free for aligned_alloc + * Free for C runtime aligned_alloc */ PALEXPORT void SystemNative_AlignedFree(void* ptr); +/** + * Realloc for C runtime aligned_alloc + */ +PALEXPORT void* SystemNative_AlignedRealloc(void* ptr, uintptr_t alignment, uintptr_t size); + /** * C runtime calloc */ @@ -26,6 +31,11 @@ PALEXPORT void* SystemNative_Calloc(uintptr_t num, uintptr_t size); */ PALEXPORT void SystemNative_Free(void* ptr); +/** + * Get usable size of C runtime malloc + */ +PALEXPORT uintptr_t SystemNative_GetUsableSize(void* ptr); + /** * C runtime malloc */ @@ -39,4 +49,4 @@ PALEXPORT void* SystemNative_MemSet(void* s, int c, uintptr_t n); /** * C runtime realloc */ -PALEXPORT void* SystemNative_Realloc(void* ptr, uintptr_t size); +PALEXPORT void* SystemNative_Realloc(void* ptr, uintptr_t new_size); diff --git a/src/libraries/Native/Unix/configure.cmake b/src/libraries/Native/Unix/configure.cmake index 38cd45a7d76363..b919edb8e5a9d2 100644 --- a/src/libraries/Native/Unix/configure.cmake +++ b/src/libraries/Native/Unix/configure.cmake @@ -529,9 +529,27 @@ if (CLR_CMAKE_TARGET_LINUX) set(HAVE_SUPPORT_FOR_DUAL_MODE_IPV4_PACKET_INFO 1) endif () +check_symbol_exists( + aligned_alloc + stdlib.h + HAVE_ALIGNED_ALLOC) +check_symbol_exists( + malloc_size + malloc/malloc.h + HAVE_MALLOC_SIZE) +check_symbol_exists( + malloc_usable_size + malloc.h + HAVE_MALLOC_USABLE_SIZE) +check_symbol_exists( + posix_memalign + stdlib.h + HAVE_POSIX_MEMALIGN) + if(CLR_CMAKE_TARGET_IOS) # Manually set results from check_c_source_runs() since it's not possible to actually run it during CMake configure checking unset(HAVE_SHM_OPEN_THAT_WORKS_WELL_ENOUGH_WITH_MMAP) + unset(HAVE_ALIGNED_ALLOC) # only exists on iOS 13+ unset(HAVE_CLOCK_MONOTONIC) # only exists on iOS 10+ unset(HAVE_CLOCK_REALTIME) # only exists on iOS 10+ unset(HAVE_FORK) # exists but blocked by kernel @@ -539,12 +557,14 @@ elseif(CLR_CMAKE_TARGET_MACCATALYST) # Manually set results from check_c_source_runs() since it's not possible to actually run it during CMake configure checking # TODO: test to see if these all actually hold true on Mac Catalyst unset(HAVE_SHM_OPEN_THAT_WORKS_WELL_ENOUGH_WITH_MMAP) + unset(HAVE_ALIGNED_ALLOC) # only exists on iOS 13+ unset(HAVE_CLOCK_MONOTONIC) # only exists on iOS 10+ unset(HAVE_CLOCK_REALTIME) # only exists on iOS 10+ unset(HAVE_FORK) # exists but blocked by kernel elseif(CLR_CMAKE_TARGET_TVOS) # Manually set results from check_c_source_runs() since it's not possible to actually run it during CMake configure checking unset(HAVE_SHM_OPEN_THAT_WORKS_WELL_ENOUGH_WITH_MMAP) + unset(HAVE_ALIGNED_ALLOC) # only exists on iOS 13+ unset(HAVE_CLOCK_MONOTONIC) # only exists on iOS 10+ unset(HAVE_CLOCK_REALTIME) # only exists on iOS 10+ unset(HAVE_FORK) # exists but blocked by kernel @@ -553,9 +573,13 @@ elseif(CLR_CMAKE_TARGET_ANDROID) unset(HAVE_SHM_OPEN_THAT_WORKS_WELL_ENOUGH_WITH_MMAP) set(HAVE_CLOCK_MONOTONIC 1) set(HAVE_CLOCK_REALTIME 1) -elseif (CLR_CMAKE_TARGET_BROWSER) +elseif(CLR_CMAKE_TARGET_BROWSER) set(HAVE_FORK 0) else() + if(CLR_CMAKE_TARGET_OSX) + unset(HAVE_ALIGNED_ALLOC) # only exists on OSX 10.15+ + endif() + check_c_source_runs( " #include @@ -1039,9 +1063,6 @@ check_c_source_compiles( " HAVE_BUILTIN_MUL_OVERFLOW) -check_symbol_exists(aligned_alloc stdlib.h HAVE_ALIGNED_ALLOC) -check_symbol_exists(posix_memalign stdlib.h HAVE_POSIX_MEMALIGN) - configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/Common/pal_config.h.in ${CMAKE_CURRENT_BINARY_DIR}/Common/pal_config.h) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Unix.cs index 5fe2ecc2c7162b..e4869a7aaf3b0c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Unix.cs @@ -79,15 +79,29 @@ public static void AlignedFree(void* ptr) [CLSCompliant(false)] public static void* AlignedRealloc(void* ptr, nuint byteCount, nuint alignment) { - void* newPtr = AlignedAlloc(byteCount, alignment); + if (!BitOperations.IsPow2(alignment)) + { + // The C standard doesn't define what a valid alignment is, however Windows and POSIX requires a power of 2 + ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_AlignmentMustBePow2); + } - if (ptr != null) + // The C standard and POSIX requires size to be a multiple of alignment and we want an "empty" allocation for zero + // POSIX additionally requires alignment to be at least sizeof(void*) + + // The adjustment for byteCount can overflow here, but such overflow is "harmless". This is because of the requirement + // that alignment be a power of two and that byteCount be a multiple of alignment. Given both of these constraints + // we should only overflow for byteCount > (nuint.MaxValue & ~(alignment - 1)). When such an overflow occurs we will + // get a result that is less than alignment which will cause the allocation to fail. + + nuint adjustedAlignment = Math.Max(alignment, (uint)sizeof(void*)); + void* result = Interop.Sys.AlignedRealloc(ptr, adjustedAlignment, (byteCount != 0) ? (byteCount + (adjustedAlignment - 1)) & ~(adjustedAlignment - 1) : adjustedAlignment); + + if (result == null) { - Buffer.Memmove(ref *(byte*)newPtr, ref *(byte*)ptr, byteCount); - Interop.Sys.AlignedFree(ptr); + ThrowHelper.ThrowOutOfMemoryException(); } - return newPtr; + return result; } /// Allocates a block of memory of the specified size, in bytes. diff --git a/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/NativeMemoryTests.cs b/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/NativeMemoryTests.cs index 55b86cea833e4a..71cc9194f44734 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/NativeMemoryTests.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/NativeMemoryTests.cs @@ -223,6 +223,32 @@ public void AlignedReallocZeroSizeTest() NativeMemory.AlignedFree(newPtr); } + [Fact] + public void AlignedReallocSmallerToLargerTest() + { + void* ptr = NativeMemory.AlignedAlloc(16, 16); + + Assert.True(ptr != null); + Assert.True((nuint)ptr % 16 == 0); + + for (int i = 0; i < 16; i++) + { + ((byte*)ptr)[i] = i; + } + + void* newPtr = NativeMemory.AlignedRealloc(ptr, 32, 16); + + Assert.True(newPtr != null); + Assert.True((nuint)newPtr % 16 == 0); + + for (int i = 0; i < 16; i++) + { + Assert.True(((byte*)newPtr)[i] == i); + } + + NativeMemory.AlignedFree(newPtr); + } + [Fact] public void AllocByteCountTest() { @@ -387,5 +413,27 @@ public void ReallocZeroSizeTest() Assert.True(newPtr != null); NativeMemory.Free(newPtr); } + + [Fact] + public void ReallocSmallerToLargerTest() + { + void* ptr = NativeMemory.Alloc(16); + Assert.True(ptr != null); + + for (int i = 0; i < 16; i++) + { + ((byte*)ptr)[i] = i; + } + + void* newPtr = NativeMemory.Realloc(ptr, 32); + Assert.True(newPtr != null); + + for (int i = 0; i < 16; i++) + { + Assert.True(((byte*)newPtr)[i] == i); + } + + NativeMemory.AlignedFree(newPtr); + } } } From 2ee0409aebb3c104896c7df88c28dfd241b6bee8 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Wed, 16 Jun 2021 15:35:06 -0700 Subject: [PATCH 12/15] Ensure check_symbol_exists(HAVE_ALIGNED_ALLOC) is under the non-apple paths --- src/libraries/Native/Unix/configure.cmake | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/libraries/Native/Unix/configure.cmake b/src/libraries/Native/Unix/configure.cmake index b919edb8e5a9d2..f131ddc2a83cb9 100644 --- a/src/libraries/Native/Unix/configure.cmake +++ b/src/libraries/Native/Unix/configure.cmake @@ -529,10 +529,6 @@ if (CLR_CMAKE_TARGET_LINUX) set(HAVE_SUPPORT_FOR_DUAL_MODE_IPV4_PACKET_INFO 1) endif () -check_symbol_exists( - aligned_alloc - stdlib.h - HAVE_ALIGNED_ALLOC) check_symbol_exists( malloc_size malloc/malloc.h @@ -571,6 +567,7 @@ elseif(CLR_CMAKE_TARGET_TVOS) elseif(CLR_CMAKE_TARGET_ANDROID) # Manually set results from check_c_source_runs() since it's not possible to actually run it during CMake configure checking unset(HAVE_SHM_OPEN_THAT_WORKS_WELL_ENOUGH_WITH_MMAP) + unset(HAVE_ALIGNED_ALLOC) # only exists on newer Android set(HAVE_CLOCK_MONOTONIC 1) set(HAVE_CLOCK_REALTIME 1) elseif(CLR_CMAKE_TARGET_BROWSER) @@ -578,6 +575,11 @@ elseif(CLR_CMAKE_TARGET_BROWSER) else() if(CLR_CMAKE_TARGET_OSX) unset(HAVE_ALIGNED_ALLOC) # only exists on OSX 10.15+ + else() + check_symbol_exists( + aligned_alloc + stdlib.h + HAVE_ALIGNED_ALLOC) endif() check_c_source_runs( From 1266f4d04eeffd787e90566b876174958f1ce758 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Thu, 17 Jun 2021 07:19:33 -0700 Subject: [PATCH 13/15] Check for malloc_usable_size in malloc_np for FreeBSD and ensure tests compile --- src/libraries/Native/Unix/Common/pal_config.h.in | 1 + .../Native/Unix/System.Native/entrypoints.c | 1 + .../Native/Unix/System.Native/pal_memory.c | 14 ++++++++------ src/libraries/Native/Unix/configure.cmake | 4 ++++ .../Runtime/InteropServices/NativeMemoryTests.cs | 4 ++-- 5 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/libraries/Native/Unix/Common/pal_config.h.in b/src/libraries/Native/Unix/Common/pal_config.h.in index 8d386a33280024..2b86ed15d4cae7 100644 --- a/src/libraries/Native/Unix/Common/pal_config.h.in +++ b/src/libraries/Native/Unix/Common/pal_config.h.in @@ -125,6 +125,7 @@ #cmakedefine01 HAVE_ALIGNED_ALLOC #cmakedefine01 HAVE_MALLOC_SIZE #cmakedefine01 HAVE_MALLOC_USABLE_SIZE +#cmakedefine01 HAVE_MALLOC_USABLE_SIZE_NP #cmakedefine01 HAVE_POSIX_MEMALIGN // Mac OS X has stat64, but it is deprecated since plain stat now diff --git a/src/libraries/Native/Unix/System.Native/entrypoints.c b/src/libraries/Native/Unix/System.Native/entrypoints.c index 84c1bcc387fd54..cf6485ecf8430a 100644 --- a/src/libraries/Native/Unix/System.Native/entrypoints.c +++ b/src/libraries/Native/Unix/System.Native/entrypoints.c @@ -113,6 +113,7 @@ static const Entry s_sysNative[] = DllImportEntry(SystemNative_AlignedRealloc) DllImportEntry(SystemNative_Calloc) DllImportEntry(SystemNative_Free) + DllImportEntry(SystemNative_GetUsableSize) DllImportEntry(SystemNative_Malloc) DllImportEntry(SystemNative_MemSet) DllImportEntry(SystemNative_Realloc) diff --git a/src/libraries/Native/Unix/System.Native/pal_memory.c b/src/libraries/Native/Unix/System.Native/pal_memory.c index b77ab1bc450895..fd6a34de2c39a8 100644 --- a/src/libraries/Native/Unix/System.Native/pal_memory.c +++ b/src/libraries/Native/Unix/System.Native/pal_memory.c @@ -8,10 +8,12 @@ #include #include -#if HAVE_MALLOC_USABLE_SIZE - #include -#elif HAVE_MALLOC_SIZE +#if HAVE_MALLOC_SIZE #include +#elif HAVE_MALLOC_USABLE_SIZE + #include +#elif HAVE_MALLOC_USABLE_SIZE_NP + #include #else #error "Platform doesn't support malloc_usable_size or malloc_size" #endif @@ -65,10 +67,10 @@ void SystemNative_Free(void* ptr) uintptr_t SystemNative_GetUsableSize(void* ptr) { -#if HAVE_MALLOC_USABLE_SIZE - return malloc_usable_size(ptr); -#elif HAVE_MALLOC_SIZE +#if HAVE_MALLOC_SIZE return malloc_size(ptr); +#elif HAVE_MALLOC_USABLE_SIZE || HAVE_MALLOC_USABLE_SIZE_NP + return malloc_usable_size(ptr); #else #error "Platform doesn't support malloc_usable_size or malloc_size" #endif diff --git a/src/libraries/Native/Unix/configure.cmake b/src/libraries/Native/Unix/configure.cmake index f131ddc2a83cb9..b2cfdb3bc40c2a 100644 --- a/src/libraries/Native/Unix/configure.cmake +++ b/src/libraries/Native/Unix/configure.cmake @@ -537,6 +537,10 @@ check_symbol_exists( malloc_usable_size malloc.h HAVE_MALLOC_USABLE_SIZE) +check_symbol_exists( + malloc_usable_size + malloc_np.h + HAVE_MALLOC_USABLE_SIZE_NP) check_symbol_exists( posix_memalign stdlib.h diff --git a/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/NativeMemoryTests.cs b/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/NativeMemoryTests.cs index 71cc9194f44734..9256bb98cf9b09 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/NativeMemoryTests.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/NativeMemoryTests.cs @@ -233,7 +233,7 @@ public void AlignedReallocSmallerToLargerTest() for (int i = 0; i < 16; i++) { - ((byte*)ptr)[i] = i; + ((byte*)ptr)[i] = (byte)i; } void* newPtr = NativeMemory.AlignedRealloc(ptr, 32, 16); @@ -422,7 +422,7 @@ public void ReallocSmallerToLargerTest() for (int i = 0; i < 16; i++) { - ((byte*)ptr)[i] = i; + ((byte*)ptr)[i] = (byte)i; } void* newPtr = NativeMemory.Realloc(ptr, 32); From 05f6b92e83dc65dd389925210f95a738007e3b90 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Thu, 17 Jun 2021 09:18:01 -0700 Subject: [PATCH 14/15] Fix the ReallocSmallerToLargerTest test --- .../tests/System/Runtime/InteropServices/NativeMemoryTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/NativeMemoryTests.cs b/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/NativeMemoryTests.cs index 9256bb98cf9b09..9787751d87ae24 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/NativeMemoryTests.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/NativeMemoryTests.cs @@ -433,7 +433,7 @@ public void ReallocSmallerToLargerTest() Assert.True(((byte*)newPtr)[i] == i); } - NativeMemory.AlignedFree(newPtr); + NativeMemory.Free(newPtr); } } } From 805882b2c28045f2f27b7777b5296b7163ae0e30 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Thu, 17 Jun 2021 10:53:21 -0700 Subject: [PATCH 15/15] Handle that posix_memalign differs from aligned_alloc for size == 0 --- .../InteropServices/NativeMemory.Unix.cs | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Unix.cs index e4869a7aaf3b0c..4dc1f1b7f5aa58 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Unix.cs @@ -32,13 +32,18 @@ public static unsafe partial class NativeMemory // The C standard and POSIX requires size to be a multiple of alignment and we want an "empty" allocation for zero // POSIX additionally requires alignment to be at least sizeof(void*) - // The adjustment for byteCount can overflow here, but such overflow is "harmless". This is because of the requirement - // that alignment be a power of two and that byteCount be a multiple of alignment. Given both of these constraints - // we should only overflow for byteCount > (nuint.MaxValue & ~(alignment - 1)). When such an overflow occurs we will - // get a result that is less than alignment which will cause the allocation to fail. + // The adjustment for byteCount can overflow here, and such overflow is generally "harmless". This is because of the + // requirement that alignment be a power of two and that byteCount be a multiple of alignment. Given both of these + // constraints we should only overflow for byteCount > (nuint.MaxValue & ~(alignment - 1)). When such an overflow + // occurs we will get a result that is less than alignment which will cause the allocation to fail. + // + // However, posix_memalign differs from aligned_alloc in that it may return a valid pointer for zero and we need to + // ensure we OOM for this scenario (which can occur for `nuint.MaxValue`) and so we have to check the adjusted size. nuint adjustedAlignment = Math.Max(alignment, (uint)sizeof(void*)); - void* result = Interop.Sys.AlignedAlloc(adjustedAlignment, (byteCount != 0) ? (byteCount + (adjustedAlignment - 1)) & ~(adjustedAlignment - 1) : adjustedAlignment); + nuint adjustedByteCount = (byteCount != 0) ? (byteCount + (adjustedAlignment - 1)) & ~(adjustedAlignment - 1) : adjustedAlignment; + + void* result = (adjustedByteCount < byteCount) ? null : Interop.Sys.AlignedAlloc(adjustedAlignment, adjustedByteCount); if (result == null) { @@ -88,13 +93,18 @@ public static void AlignedFree(void* ptr) // The C standard and POSIX requires size to be a multiple of alignment and we want an "empty" allocation for zero // POSIX additionally requires alignment to be at least sizeof(void*) - // The adjustment for byteCount can overflow here, but such overflow is "harmless". This is because of the requirement - // that alignment be a power of two and that byteCount be a multiple of alignment. Given both of these constraints - // we should only overflow for byteCount > (nuint.MaxValue & ~(alignment - 1)). When such an overflow occurs we will - // get a result that is less than alignment which will cause the allocation to fail. + // The adjustment for byteCount can overflow here, and such overflow is generally "harmless". This is because of the + // requirement that alignment be a power of two and that byteCount be a multiple of alignment. Given both of these + // constraints we should only overflow for byteCount > (nuint.MaxValue & ~(alignment - 1)). When such an overflow + // occurs we will get a result that is less than alignment which will cause the allocation to fail. + // + // However, posix_memalign differs from aligned_alloc in that it may return a valid pointer for zero and we need to + // ensure we OOM for this scenario (which can occur for `nuint.MaxValue`) and so we have to check the adjusted size. nuint adjustedAlignment = Math.Max(alignment, (uint)sizeof(void*)); - void* result = Interop.Sys.AlignedRealloc(ptr, adjustedAlignment, (byteCount != 0) ? (byteCount + (adjustedAlignment - 1)) & ~(adjustedAlignment - 1) : adjustedAlignment); + nuint adjustedByteCount = (byteCount != 0) ? (byteCount + (adjustedAlignment - 1)) & ~(adjustedAlignment - 1) : adjustedAlignment; + + void* result = (adjustedByteCount < byteCount) ? null : Interop.Sys.AlignedRealloc(ptr, adjustedAlignment, adjustedByteCount); if (result == null) {