From f721cf4144005334827c02931317366f7f6ee114 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Thu, 17 Jun 2021 19:06:29 -0700 Subject: [PATCH] Implement NativeMemory (#54006) * Implement NativeMemory * Exposing additional APIs as approved * Ensure we have a test covering alignment and size being less than sizeof(void*) * Update src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Unix.cs Co-authored-by: Jan Kotas * Responding to PR feedback * Adding additional alignment test coverage for 1 to 16384 * Add coverage for 65k and 1/2/4MB alignments * Fixing the Native\Unix\System.Native\CMakeLists.txt * Update src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Unix.cs Co-authored-by: Jan Kotas * Don't call Buffer.Memmove in NativeMemory.AlignedRealloc if ptr is null * Updating NativeMemory.AlignedRealloc to correctly copy only the size of the last allocation * Ensure check_symbol_exists(HAVE_ALIGNED_ALLOC) is under the non-apple paths * Check for malloc_usable_size in malloc_np for FreeBSD and ensure tests compile * Fix the ReallocSmallerToLargerTest test * Handle that posix_memalign differs from aligned_alloc for size == 0 Co-authored-by: Jan Kotas --- THIRD-PARTY-NOTICES.TXT | 26 ++ .../Interop/Unix/System.Native/Interop.MemAlloc.cs | 26 +- .../src/Interop/Windows/Interop.Libraries.cs | 1 + .../Interop/Windows/Ucrtbase/Interop.MemAlloc.cs | 32 ++ src/libraries/Native/Unix/Common/pal_config.h.in | 5 + .../Native/Unix/System.Native/entrypoints.c | 11 +- .../Native/Unix/System.Native/pal_memory.c | 75 +++- .../Native/Unix/System.Native/pal_memory.h | 35 +- src/libraries/Native/Unix/configure.cmake | 32 +- .../src/Resources/Strings.resx | 5 +- .../src/System.Private.CoreLib.Shared.projitems | 6 + .../src/System/Numerics/BitOperations.cs | 14 +- .../System/Runtime/InteropServices/Marshal.Unix.cs | 86 ++-- .../Runtime/InteropServices/NativeMemory.Unix.cs | 210 ++++++++++ .../InteropServices/NativeMemory.Windows.cs | 175 ++++++++ .../System/Runtime/InteropServices/NativeMemory.cs | 53 +++ .../src/System/ThrowHelper.cs | 3 + .../ref/System.Runtime.InteropServices.cs | 21 + .../System.Runtime.InteropServices.Tests.csproj | 1 + .../Runtime/InteropServices/NativeMemoryTests.cs | 439 +++++++++++++++++++++ 20 files changed, 1175 insertions(+), 81 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.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.cs create mode 100644 src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/NativeMemoryTests.cs diff --git a/THIRD-PARTY-NOTICES.TXT b/THIRD-PARTY-NOTICES.TXT index b069427..a877e8f 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/Unix/System.Native/Interop.MemAlloc.cs b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.MemAlloc.cs index c5f6d1b..cb4a38f 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,27 @@ using System.Runtime.InteropServices; 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_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); + + [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 d3466d7..93c2bb7 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 Interop 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 0000000..23fe143 --- /dev/null +++ b/src/libraries/Common/src/Interop/Windows/Ucrtbase/Interop.MemAlloc.cs @@ -0,0 +1,32 @@ +// 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* _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); + + [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 034d57b..2b86ed1 100644 --- a/src/libraries/Native/Unix/Common/pal_config.h.in +++ b/src/libraries/Native/Unix/Common/pal_config.h.in @@ -122,6 +122,11 @@ #cmakedefine01 HAVE_GETGROUPLIST #cmakedefine01 HAVE_SYS_PROCINFO_H #cmakedefine01 HAVE_IOSS_H +#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 // provides the same 64-bit aware struct when targeting OS X > 10.5 diff --git a/src/libraries/Native/Unix/System.Native/entrypoints.c b/src/libraries/Native/Unix/System.Native/entrypoints.c index 8a1438b..cf6485e 100644 --- a/src/libraries/Native/Unix/System.Native/entrypoints.c +++ b/src/libraries/Native/Unix/System.Native/entrypoints.c @@ -108,10 +108,15 @@ 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_AlignedRealloc) + DllImportEntry(SystemNative_Calloc) + DllImportEntry(SystemNative_Free) + DllImportEntry(SystemNative_GetUsableSize) + 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 35757df..fd6a34d 100644 --- a/src/libraries/Native/Unix/System.Native/pal_memory.c +++ b/src/libraries/Native/Unix/System.Native/pal_memory.c @@ -1,27 +1,92 @@ // 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 #include -void* SystemNative_MemAlloc(uintptr_t 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 + +void* SystemNative_AlignedAlloc(uintptr_t alignment, uintptr_t size) { - return malloc(size); +#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. + 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_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_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); } +uintptr_t SystemNative_GetUsableSize(void* ptr) +{ +#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 +} + +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 83fc2f2..2cc4c1d 100644 --- a/src/libraries/Native/Unix/System.Native/pal_memory.h +++ b/src/libraries/Native/Unix/System.Native/pal_memory.h @@ -7,21 +7,46 @@ #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 + * Free for C runtime aligned_alloc + */ +PALEXPORT void SystemNative_AlignedFree(void* ptr); + +/** + * Realloc for C runtime aligned_alloc */ -PALEXPORT void* SystemNative_MemReAlloc(void* ptr, uintptr_t size); +PALEXPORT void* SystemNative_AlignedRealloc(void* ptr, uintptr_t alignment, uintptr_t size); + +/** + * 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); + +/** + * Get usable size of C runtime malloc + */ +PALEXPORT uintptr_t SystemNative_GetUsableSize(void* ptr); + +/** + * C runtime malloc + */ +PALEXPORT void* SystemNative_Malloc(uintptr_t size); /** * C runtime memset */ PALEXPORT void* SystemNative_MemSet(void* s, int c, uintptr_t n); + +/** + * C runtime realloc + */ +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 9d40db9..b2cfdb3 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( + malloc_size + malloc/malloc.h + HAVE_MALLOC_SIZE) +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 + 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,23 +557,35 @@ 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 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) +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+ + else() + check_symbol_exists( + aligned_alloc + stdlib.h + HAVE_ALIGNED_ALLOC) + endif() + check_c_source_runs( " #include diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index 99b0321..7c80c23 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 a189be9..a7ce085 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 @@ + @@ -1725,6 +1726,9 @@ Common\Interop\Windows\Shell32\Interop.SHGetKnownFolderPath.cs + + Common\Interop\Windows\Ucrtbase\Interop.MemAlloc.cs + Common\Interop\Windows\User32\Interop.Constants.cs @@ -1796,6 +1800,7 @@ + @@ -2067,6 +2072,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 49fbb26..b5ffd5e 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 @@ namespace System.Numerics 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 08ab87a..49ef822 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 @@ namespace System.Runtime.InteropServices bytes[actualByteLength] = 0; } - public static IntPtr AllocHGlobal(IntPtr cb) + public static unsafe IntPtr AllocHGlobal(IntPtr cb) { - nuint cbNative = (nuint)(nint)cb; - - // Avoid undefined malloc behavior by always allocating at least one byte - IntPtr pNewMem = Interop.Sys.MemAlloc((cbNative != 0) ? cbNative : 1); - if (pNewMem == IntPtr.Zero) - { - throw new OutOfMemoryException(); - } - - return pNewMem; + return (nint)NativeMemory.Alloc((nuint)(nint)cb); } - public static void FreeHGlobal(IntPtr hglobal) + public static unsafe void FreeHGlobal(IntPtr hglobal) { - if (hglobal != IntPtr.Zero) - { - Interop.Sys.MemFree(hglobal); - } + NativeMemory.Free((void*)(nint)hglobal); } - public static IntPtr ReAllocHGlobal(IntPtr pv, IntPtr cb) + public static unsafe IntPtr ReAllocHGlobal(IntPtr pv, IntPtr cb) { - nuint cbNative = (nuint)(nint)cb; - - if (cbNative == 0) - { - // ReAllocHGlobal never returns null, even for 0 size (different from standard C/C++ realloc) - - // Avoid undefined realloc behavior by always allocating at least one byte - cbNative = 1; - } - - IntPtr pNewMem = Interop.Sys.MemReAlloc(pv, cbNative); - if (pNewMem == IntPtr.Zero) - { - throw new OutOfMemoryException(); - } - return pNewMem; + return (nint)NativeMemory.Realloc((void*)(nint)pv, (nuint)(nint)cb); } public static IntPtr AllocCoTaskMem(int cb) => 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 @@ namespace System.Runtime.InteropServices 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 @@ namespace System.Runtime.InteropServices 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 0000000..4dc1f1b --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Unix.cs @@ -0,0 +1,210 @@ +// 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 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. + /// 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 or 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, 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*)); + nuint adjustedByteCount = (byteCount != 0) ? (byteCount + (adjustedAlignment - 1)) & ~(adjustedAlignment - 1) : adjustedAlignment; + + void* result = (adjustedByteCount < byteCount) ? null : Interop.Sys.AlignedAlloc(adjustedAlignment, adjustedByteCount); + + 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); + } + } + + /// 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 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, 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*)); + nuint adjustedByteCount = (byteCount != 0) ? (byteCount + (adjustedAlignment - 1)) & ~(adjustedAlignment - 1) : adjustedAlignment; + + void* result = (adjustedByteCount < byteCount) ? null : Interop.Sys.AlignedRealloc(ptr, adjustedAlignment, adjustedByteCount); + + 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. + /// 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 0000000..7fbaf93 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Windows.cs @@ -0,0 +1,175 @@ +// 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 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. + /// 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 or 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) + { + if (ptr != null) + { + 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. + /// 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) + { + if (ptr != null) + { + 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/Runtime/InteropServices/NativeMemory.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.cs new file mode 100644 index 0000000..b46655e --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.cs @@ -0,0 +1,53 @@ +// 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 + { + /// 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) + { + // 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.Private.CoreLib/src/System/ThrowHelper.cs b/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs index 94b2e6d..9a5ef78 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 @@ namespace System 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 @@ namespace System 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 0cf9aff..02e739a 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,27 @@ namespace System.Runtime.InteropServices 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* 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) { } + [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 e04dab6..9ed8c6f 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 0000000..9787751 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/NativeMemoryTests.cs @@ -0,0 +1,439 @@ +// 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 + { + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(4)] + [InlineData(8)] + [InlineData(16)] + [InlineData(32)] + [InlineData(64)] + [InlineData(128)] + [InlineData(256)] + [InlineData(512)] + [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); + + Assert.True(ptr != null); + Assert.True((nuint)ptr % alignment == 0); + + NativeMemory.AlignedFree(ptr); + } + + [Fact] + public void AlignedAllocLessThanVoidPtrAlignmentTest() + { + void* ptr = NativeMemory.AlignedAlloc(1, 1); + 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); + Assert.True((nuint)ptr % (uint)sizeof(nuint) == 0); + + NativeMemory.AlignedFree(ptr); + } + + [Fact] + public void AlignedFreeTest() + { + // This should not throw + NativeMemory.AlignedFree(null); + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(4)] + [InlineData(8)] + [InlineData(16)] + [InlineData(32)] + [InlineData(64)] + [InlineData(128)] + [InlineData(256)] + [InlineData(512)] + [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); + + Assert.True(ptr != null); + Assert.True((nuint)ptr % alignment == 0); + + void* newPtr = NativeMemory.AlignedRealloc(ptr, 1, alignment); + + Assert.True(newPtr != null); + Assert.True((nuint)newPtr % alignment == 0); + + NativeMemory.AlignedFree(newPtr); + } + + [Fact] + public void AlignedReallocLessThanVoidPtrAlignmentTest() + { + void* ptr = NativeMemory.AlignedAlloc(1, 1); + Assert.True(ptr != null); + + void* newPtr = NativeMemory.AlignedRealloc(ptr, 1, 1); + Assert.True(newPtr != null); + NativeMemory.AlignedFree(newPtr); + } + + [Fact] + 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); + } + + [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); + Assert.True((nuint)ptr % (uint)sizeof(nuint) == 0); + + NativeMemory.AlignedFree(ptr); + } + + [Fact] + 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); + } + + [Fact] + 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)); + NativeMemory.AlignedFree(ptr); + } + + [Fact] + 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); + } + + [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] = (byte)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() + { + void* ptr = NativeMemory.Alloc(1); + Assert.True(ptr != null); + NativeMemory.Free(ptr); + } + + [Fact] + 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 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); + NativeMemory.Free(ptr); + } + + [Fact] + 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); + + Assert.True(ptr != null); + Assert.Equal(expected: 0, actual: ((byte*)ptr)[0]); + + NativeMemory.Free(ptr); + } + + [Fact] + 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] + 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); + } + + [Fact] + public void ReallocSmallerToLargerTest() + { + void* ptr = NativeMemory.Alloc(16); + Assert.True(ptr != null); + + for (int i = 0; i < 16; i++) + { + ((byte*)ptr)[i] = (byte)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.Free(newPtr); + } + } +} -- 2.7.4