Implement NativeMemory (#54006)
authorTanner Gooding <tagoo@outlook.com>
Fri, 18 Jun 2021 02:06:29 +0000 (19:06 -0700)
committerGitHub <noreply@github.com>
Fri, 18 Jun 2021 02:06:29 +0000 (19:06 -0700)
* 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 <jkotas@microsoft.com>
* 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 <jkotas@microsoft.com>
* 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 <jkotas@microsoft.com>
20 files changed:
THIRD-PARTY-NOTICES.TXT
src/libraries/Common/src/Interop/Unix/System.Native/Interop.MemAlloc.cs
src/libraries/Common/src/Interop/Windows/Interop.Libraries.cs
src/libraries/Common/src/Interop/Windows/Ucrtbase/Interop.MemAlloc.cs [new file with mode: 0644]
src/libraries/Native/Unix/Common/pal_config.h.in
src/libraries/Native/Unix/System.Native/entrypoints.c
src/libraries/Native/Unix/System.Native/pal_memory.c
src/libraries/Native/Unix/System.Native/pal_memory.h
src/libraries/Native/Unix/configure.cmake
src/libraries/System.Private.CoreLib/src/Resources/Strings.resx
src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
src/libraries/System.Private.CoreLib/src/System/Numerics/BitOperations.cs
src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshal.Unix.cs
src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Unix.cs [new file with mode: 0644]
src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Windows.cs [new file with mode: 0644]
src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.cs [new file with mode: 0644]
src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs
src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs
src/libraries/System.Runtime.InteropServices/tests/System.Runtime.InteropServices.Tests.csproj
src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/NativeMemoryTests.cs [new file with mode: 0644]

index b069427..a877e8f 100644 (file)
@@ -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.
index c5f6d1b..cb4a38f 100644 (file)
@@ -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);
     }
 }
index d3466d7..93c2bb7 100644 (file)
@@ -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 (file)
index 0000000..23fe143
--- /dev/null
@@ -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);
+    }
+}
index 034d57b..2b86ed1 100644 (file)
 #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
index 8a1438b..cf6485e 100644 (file)
@@ -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)
index 35757df..fd6a34d 100644 (file)
@@ -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 <assert.h>
 #include <stdlib.h>
 #include <string.h>
 
-void* SystemNative_MemAlloc(uintptr_t size)
+#if HAVE_MALLOC_SIZE
+    #include <malloc/malloc.h>
+#elif HAVE_MALLOC_USABLE_SIZE
+    #include <malloc.h>
+#elif HAVE_MALLOC_USABLE_SIZE_NP
+    #include <malloc_np.h>
+#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);
+}
index 83fc2f2..2cc4c1d 100644 (file)
@@ -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);
index 9d40db9..b2cfdb3 100644 (file)
@@ -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 <sys/mman.h>
index 99b0321..7c80c23 100644 (file)
   <data name="Argument_AdjustmentRulesOutOfOrder" xml:space="preserve">
     <value>The elements of the AdjustmentRule array must be in chronological order and must not overlap.</value>
   </data>
+  <data name="Argument_AlignmentMustBePow2" xml:space="preserve">
+    <value>The alignment must be a power of two.</value>
+  </data>
   <data name="Argument_AlreadyACCW" xml:space="preserve">
     <value>The object already has a CCW associated with it.</value>
   </data>
   <data name="Arg_MemberInfoNotFound" xml:space="preserve">
     <value>A MemberInfo that matches '{0}' could not be found.</value>
   </data>
-</root>
\ No newline at end of file
+</root>
index a189be9..a7ce085 100644 (file)
     <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\MarshalAsAttribute.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\MarshalDirectiveException.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\MemoryMarshal.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\NativeMemory.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\UnmanagedCallConvAttribute.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\UnmanagedCallersOnlyAttribute.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\NativeLibrary.cs" />
     <Compile Include="$(CommonPath)Interop\Windows\Shell32\Interop.SHGetKnownFolderPath.cs">
       <Link>Common\Interop\Windows\Shell32\Interop.SHGetKnownFolderPath.cs</Link>
     </Compile>
+    <Compile Include="$(CommonPath)Interop\Windows\Ucrtbase\Interop.MemAlloc.cs">
+      <Link>Common\Interop\Windows\Ucrtbase\Interop.MemAlloc.cs</Link>
+    </Compile>
     <Compile Include="$(CommonPath)Interop\Windows\User32\Interop.Constants.cs">
       <Link>Common\Interop\Windows\User32\Interop.Constants.cs</Link>
     </Compile>
     <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\Loader\LibraryNameVariation.Windows.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\MemoryFailPoint.Windows.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\Marshal.Windows.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\NativeMemory.Windows.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\StandardOleMarshalObject.Windows.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Security\SecureString.Windows.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Threading\LowLevelMonitor.Windows.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\Loader\LibraryNameVariation.Unix.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\MemoryFailPoint.Unix.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\Marshal.Unix.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\NativeMemory.Unix.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\StandardOleMarshalObject.Unix.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Security\SecureString.Unix.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Threading\LowLevelMonitor.Unix.cs" />
index 49fbb26..b5ffd5e 100644 (file)
@@ -72,9 +72,21 @@ namespace System.Numerics
         public static bool IsPow2(ulong value) => (value & (value - 1)) == 0 && value != 0;
 
         /// <summary>
-        /// Round the given integral value up to a power of 2.
+        /// Evaluate whether a given integral value is a power of 2.
         /// </summary>
         /// <param name="value">The value.</param>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal static bool IsPow2(nint value) => (value & (value - 1)) == 0 && value > 0;
+
+        /// <summary>
+        /// Evaluate whether a given integral value is a power of 2.
+        /// </summary>
+        /// <param name="value">The value.</param>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal static bool IsPow2(nuint value) => (value & (value - 1)) == 0 && value != 0;
+
+        /// <summary>Round the given integral value up to a power of 2.</summary>
+        /// <param name="value">The value.</param>
         /// <returns>
         /// The smallest power of 2 which is greater than or equal to <paramref name="value"/>.
         /// If <paramref name="value"/> is 0 or the result overflows, returns 0.
index 08ab87a..49ef822 100644 (file)
@@ -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 (file)
index 0000000..4dc1f1b
--- /dev/null
@@ -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
+{
+    /// <summary>This class contains methods that are mainly used to manage native memory.</summary>
+    public static unsafe partial class NativeMemory
+    {
+        /// <summary>Allocates an aligned block of memory of the specified size and alignment, in bytes.</summary>
+        /// <param name="byteCount">The size, in bytes, of the block to allocate.</param>
+        /// <param name="alignment">The alignment, in bytes, of the block to allocate. This must be a power of <c>2</c>.</param>
+        /// <returns>A pointer to the allocated aligned block of memory.</returns>
+        /// <exception cref="ArgumentException"><paramref name="alignment" /> is not a power of two.</exception>
+        /// <exception cref="OutOfMemoryException">Allocating <paramref name="byteCount" /> of memory with <paramref name="alignment" /> failed.</exception>
+        /// <remarks>
+        ///     <para>This method allows <paramref name="byteCount" /> to be <c>0</c> and will return a valid pointer that should not be dereferenced and that should be passed to free to avoid memory leaks.</para>
+        ///     <para>This method is a thin wrapper over the C <c>aligned_alloc</c> API or a platform dependent aligned allocation API such as <c>_aligned_malloc</c> on Win32.</para>
+        ///     <para>This method is not compatible with <see cref="Free" /> or <see cref="Realloc" />, instead <see cref="AlignedFree" /> or <see cref="AlignedRealloc" /> should be called.</para>
+        /// </remarks>
+        [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;
+        }
+
+        /// <summary>Frees an aligned block of memory.</summary>
+        /// <param name="ptr">A pointer to the aligned block of memory that should be freed.</param>
+        /// <remarks>
+        ///    <para>This method does nothing if <paramref name="ptr" /> is <c>null</c>.</para>
+        ///    <para>This method is a thin wrapper over the C <c>free</c> API or a platform dependent aligned free API such as <c>_aligned_free</c> on Win32.</para>
+        /// </remarks>
+        [CLSCompliant(false)]
+        public static void AlignedFree(void* ptr)
+        {
+            if (ptr != null)
+            {
+                Interop.Sys.AlignedFree(ptr);
+            }
+        }
+
+        /// <summary>Reallocates an aligned block of memory of the specified size and alignment, in bytes.</summary>
+        /// <param name="ptr">The previously allocated block of memory.</param>
+        /// <param name="byteCount">The size, in bytes, of the block to allocate.</param>
+        /// <param name="alignment">The alignment, in bytes, of the block to allocate. This must be a power of <c>2</c>.</param>
+        /// <returns>A pointer to the reallocated aligned block of memory.</returns>
+        /// <exception cref="ArgumentException"><paramref name="alignment" /> is not a power of two.</exception>
+        /// <exception cref="OutOfMemoryException">Reallocating <paramref name="byteCount" /> of memory with <paramref name="alignment" /> failed.</exception>
+        /// <remarks>
+        ///     <para>This method acts as <see cref="AlignedAlloc" /> if <paramref name="ptr" /> is <c>null</c>.</para>
+        ///     <para>This method allows <paramref name="byteCount" /> to be <c>0</c> and will return a valid pointer that should not be dereferenced and that should be passed to free to avoid memory leaks.</para>
+        ///     <para>This method is a platform dependent aligned reallocation API such as <c>_aligned_realloc</c> on Win32.</para>
+        ///     <para>This method is not compatible with <see cref="Free" /> or <see cref="Realloc" />, instead <see cref="AlignedFree" /> or <see cref="AlignedRealloc" /> should be called.</para>
+        /// </remarks>
+        [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;
+        }
+
+        /// <summary>Allocates a block of memory of the specified size, in bytes.</summary>
+        /// <param name="byteCount">The size, in bytes, of the block to allocate.</param>
+        /// <returns>A pointer to the allocated block of memory.</returns>
+        /// <exception cref="OutOfMemoryException">Allocating <paramref name="byteCount" /> of memory failed.</exception>
+        /// <remarks>
+        ///     <para>This method allows <paramref name="byteCount" /> to be <c>0</c> and will return a valid pointer that should not be dereferenced and that should be passed to free to avoid memory leaks.</para>
+        ///     <para>This method is a thin wrapper over the C <c>malloc</c> API.</para>
+        /// </remarks>
+        [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;
+        }
+
+        /// <summary>Allocates and zeroes a block of memory of the specified size, in elements.</summary>
+        /// <param name="elementCount">The count, in elements, of the block to allocate.</param>
+        /// <param name="elementSize">The size, in bytes, of each element in the allocation.</param>
+        /// <returns>A pointer to the allocated and zeroed block of memory.</returns>
+        /// <exception cref="OutOfMemoryException">Allocating <paramref name="elementCount" /> * <paramref name="elementSize" /> bytes of memory failed.</exception>
+        /// <remarks>
+        ///     <para>This method allows <paramref name="elementCount" /> and/or <paramref name="elementSize" /> to be <c>0</c> and will return a valid pointer that should not be dereferenced and that should be passed to free to avoid memory leaks.</para>
+        ///     <para>This method is a thin wrapper over the C <c>calloc</c> API.</para>
+        /// </remarks>
+        [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;
+        }
+
+        /// <summary>Frees a block of memory.</summary>
+        /// <param name="ptr">A pointer to the block of memory that should be freed.</param>
+        /// <remarks>
+        ///    <para>This method does nothing if <paramref name="ptr" /> is <c>null</c>.</para>
+        ///    <para>This method is a thin wrapper over the C <c>free</c> API.</para>
+        /// </remarks>
+        [CLSCompliant(false)]
+        public static void Free(void* ptr)
+        {
+            if (ptr != null)
+            {
+                Interop.Sys.Free(ptr);
+            }
+        }
+
+        /// <summary>Reallocates a block of memory to be the specified size, in bytes.</summary>
+        /// <param name="ptr">The previously allocated block of memory.</param>
+        /// <param name="byteCount">The size, in bytes, of the reallocated block.</param>
+        /// <returns>A pointer to the reallocated block of memory.</returns>
+        /// <exception cref="OutOfMemoryException">Reallocating <paramref name="byteCount" /> of memory failed.</exception>
+        /// <remarks>
+        ///     <para>This method acts as <see cref="Alloc" /> if <paramref name="ptr" /> is <c>null</c>.</para>
+        ///     <para>This method allows <paramref name="byteCount" /> to be <c>0</c> and will return a valid pointer that should not be dereferenced and that should be passed to free to avoid memory leaks.</para>
+        ///     <para>This method is a thin wrapper over the C <c>realloc</c> API.</para>
+        /// </remarks>
+        [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 (file)
index 0000000..7fbaf93
--- /dev/null
@@ -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
+{
+    /// <summary>This class contains methods that are mainly used to manage native memory.</summary>
+    public static unsafe partial class NativeMemory
+    {
+        /// <summary>Allocates an aligned block of memory of the specified size and alignment, in bytes.</summary>
+        /// <param name="byteCount">The size, in bytes, of the block to allocate.</param>
+        /// <param name="alignment">The alignment, in bytes, of the block to allocate. This must be a power of <c>2</c>.</param>
+        /// <returns>A pointer to the allocated aligned block of memory.</returns>
+        /// <exception cref="ArgumentException"><paramref name="alignment" /> is not a power of two.</exception>
+        /// <exception cref="OutOfMemoryException">Allocating <paramref name="byteCount" /> of memory with <paramref name="alignment" /> failed.</exception>
+        /// <remarks>
+        ///     <para>This method allows <paramref name="byteCount" /> to be <c>0</c> and will return a valid pointer that should not be dereferenced and that should be passed to free to avoid memory leaks.</para>
+        ///     <para>This method is a thin wrapper over the C <c>aligned_alloc</c> API or a platform dependent aligned allocation API such as <c>_aligned_malloc</c> on Win32.</para>
+        ///     <para>This method is not compatible with <see cref="Free" /> or <see cref="Realloc" />, instead <see cref="AlignedFree" /> or <see cref="AlignedRealloc" /> should be called.</para>
+        /// </remarks>
+        [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;
+        }
+
+        /// <summary>Frees an aligned block of memory.</summary>
+        /// <param name="ptr">A pointer to the aligned block of memory that should be freed.</param>
+        /// <remarks>
+        ///    <para>This method does nothing if <paramref name="ptr" /> is <c>null</c>.</para>
+        ///    <para>This method is a thin wrapper over the C <c>free</c> API or a platform dependent aligned free API such as <c>_aligned_free</c> on Win32.</para>
+        /// </remarks>
+        [CLSCompliant(false)]
+        public static void AlignedFree(void* ptr)
+        {
+            if (ptr != null)
+            {
+                Interop.Ucrtbase._aligned_free(ptr);
+            }
+        }
+
+        /// <summary>Reallocates an aligned block of memory of the specified size and alignment, in bytes.</summary>
+        /// <param name="ptr">The previously allocated block of memory.</param>
+        /// <param name="byteCount">The size, in bytes, of the block to allocate.</param>
+        /// <param name="alignment">The alignment, in bytes, of the block to allocate. This must be a power of <c>2</c>.</param>
+        /// <returns>A pointer to the reallocated aligned block of memory.</returns>
+        /// <exception cref="ArgumentException"><paramref name="alignment" /> is not a power of two.</exception>
+        /// <exception cref="OutOfMemoryException">Reallocating <paramref name="byteCount" /> of memory with <paramref name="alignment" /> failed.</exception>
+        /// <remarks>
+        ///     <para>This method acts as <see cref="AlignedAlloc" /> if <paramref name="ptr" /> is <c>null</c>.</para>
+        ///     <para>This method allows <paramref name="byteCount" /> to be <c>0</c> and will return a valid pointer that should not be dereferenced and that should be passed to free to avoid memory leaks.</para>
+        ///     <para>This method is a platform dependent aligned reallocation API such as <c>_aligned_realloc</c> on Win32.</para>
+        ///     <para>This method is not compatible with <see cref="Free" /> or <see cref="Realloc" />, instead <see cref="AlignedFree" /> or <see cref="AlignedRealloc" /> should be called.</para>
+        /// </remarks>
+        [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;
+        }
+
+        /// <summary>Allocates a block of memory of the specified size, in bytes.</summary>
+        /// <param name="byteCount">The size, in bytes, of the block to allocate.</param>
+        /// <returns>A pointer to the allocated block of memory.</returns>
+        /// <exception cref="OutOfMemoryException">Allocating <paramref name="byteCount" /> of memory failed.</exception>
+        /// <remarks>
+        ///     <para>This method allows <paramref name="byteCount" /> to be <c>0</c> and will return a valid pointer that should not be dereferenced and that should be passed to free to avoid memory leaks.</para>
+        ///     <para>This method is a thin wrapper over the C <c>malloc</c> API.</para>
+        /// </remarks>
+        [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;
+        }
+
+        /// <summary>Allocates and zeroes a block of memory of the specified size, in elements.</summary>
+        /// <param name="elementCount">The count, in elements, of the block to allocate.</param>
+        /// <param name="elementSize">The size, in bytes, of each element in the allocation.</param>
+        /// <returns>A pointer to the allocated and zeroed block of memory.</returns>
+        /// <exception cref="OutOfMemoryException">Allocating <paramref name="elementCount" /> * <paramref name="elementSize" /> bytes of memory failed.</exception>
+        /// <remarks>
+        ///     <para>This method allows <paramref name="elementCount" /> and/or <paramref name="elementSize" /> to be <c>0</c> and will return a valid pointer that should not be dereferenced and that should be passed to free to avoid memory leaks.</para>
+        ///     <para>This method is a thin wrapper over the C <c>calloc</c> API.</para>
+        /// </remarks>
+        [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;
+        }
+
+        /// <summary>Frees a block of memory.</summary>
+        /// <param name="ptr">A pointer to the block of memory that should be freed.</param>
+        /// <remarks>
+        ///    <para>This method does nothing if <paramref name="ptr" /> is <c>null</c>.</para>
+        ///    <para>This method is a thin wrapper over the C <c>free</c> API.</para>
+        /// </remarks>
+        [CLSCompliant(false)]
+        public static void Free(void* ptr)
+        {
+            if (ptr != null)
+            {
+                Interop.Ucrtbase.free(ptr);
+            }
+        }
+
+        /// <summary>Reallocates a block of memory to be the specified size, in bytes.</summary>
+        /// <param name="ptr">The previously allocated block of memory.</param>
+        /// <param name="byteCount">The size, in bytes, of the reallocated block.</param>
+        /// <returns>A pointer to the reallocated block of memory.</returns>
+        /// <exception cref="OutOfMemoryException">Reallocating <paramref name="byteCount" /> of memory failed.</exception>
+        /// <remarks>
+        ///     <para>This method acts as <see cref="Alloc" /> if <paramref name="ptr" /> is <c>null</c>.</para>
+        ///     <para>This method allows <paramref name="byteCount" /> to be <c>0</c> and will return a valid pointer that should not be dereferenced and that should be passed to free to avoid memory leaks.</para>
+        ///     <para>This method is a thin wrapper over the C <c>realloc</c> API.</para>
+        /// </remarks>
+        [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 (file)
index 0000000..b46655e
--- /dev/null
@@ -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
+    {
+        /// <summary>Allocates a block of memory of the specified size, in elements.</summary>
+        /// <param name="elementCount">The count, in elements, of the block to allocate.</param>
+        /// <param name="elementSize">The size, in bytes, of each element in the allocation.</param>
+        /// <returns>A pointer to the allocated block of memory.</returns>
+        /// <exception cref="OutOfMemoryException">Allocating <paramref name="elementCount" /> * <paramref name="elementSize" /> bytes of memory failed.</exception>
+        /// <remarks>
+        ///     <para>This method allows <paramref name="elementCount" /> and/or <paramref name="elementSize" /> to be <c>0</c> and will return a valid pointer that should not be dereferenced and that should be passed to free to avoid memory leaks.</para>
+        ///     <para>This method is a thin wrapper over the C <c>malloc</c> API.</para>
+        /// </remarks>
+        [CLSCompliant(false)]
+        public static void* Alloc(nuint elementCount, nuint elementSize)
+        {
+            nuint byteCount = GetByteCount(elementCount, elementSize);
+            return Alloc(byteCount);
+        }
+
+        /// <summary>Allocates and zeroes a block of memory of the specified size, in bytes.</summary>
+        /// <param name="byteCount">The size, in bytes, of the block to allocate.</param>
+        /// <returns>A pointer to the allocated and zeroed block of memory.</returns>
+        /// <exception cref="OutOfMemoryException">Allocating <paramref name="byteCount" /> of memory failed.</exception>
+        /// <remarks>
+        ///     <para>This method allows <paramref name="byteCount" /> to be <c>0</c> and will return a valid pointer that should not be dereferenced and that should be passed to free to avoid memory leaks.</para>
+        ///     <para>This method is a thin wrapper over the C <c>calloc</c> API.</para>
+        /// </remarks>
+        [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);
+        }
+    }
+}
index 94b2e6d..9a5ef78 100644 (file)
@@ -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,
     }
 }
index 0cf9aff..02e739a 100644 (file)
@@ -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<NFloat>
     {
         public NFloat(float value) { }
index e04dab6..9ed8c6f 100644 (file)
@@ -14,6 +14,7 @@
     <Compile Include="System\Runtime\InteropServices\BStrWrapperTests.cs" />
     <Compile Include="System\Runtime\InteropServices\CallingConventionTests.cs" />
     <Compile Include="System\Runtime\InteropServices\ClassInterfaceAttributeTests.cs" />
+    <Compile Include="System\Runtime\InteropServices\NativeMemoryTests.cs" />
     <Compile Include="System\Runtime\InteropServices\NFloatTests.cs" />
     <Compile Include="System\Runtime\InteropServices\CULongTests.cs" />
     <Compile Include="System\Runtime\InteropServices\CLongTests.cs" />
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 (file)
index 0000000..9787751
--- /dev/null
@@ -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<OutOfMemoryException>(() => NativeMemory.AlignedAlloc(nuint.MaxValue - ((uint)sizeof(nuint) - 1), (uint)sizeof(nuint)));
+        }
+
+        [Fact]
+        public void AlignedAllocZeroAlignmentTest()
+        {
+            Assert.Throws<ArgumentException>(() => NativeMemory.AlignedAlloc((uint)sizeof(nuint), 0));
+        }
+
+        [Fact]
+        public void AlignedAllocNonPowerOfTwoAlignmentTest()
+        {
+            Assert.Throws<ArgumentException>(() => NativeMemory.AlignedAlloc((uint)sizeof(nuint), (uint)sizeof(nuint) + 1));
+            Assert.Throws<ArgumentException>(() => 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<OutOfMemoryException>(() => NativeMemory.AlignedAlloc(maxAlignment + 1, maxAlignment));
+
+            Assert.Throws<OutOfMemoryException>(() => 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<OutOfMemoryException>(() => 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<ArgumentException>(() => 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<ArgumentException>(() => NativeMemory.AlignedRealloc(ptr, (uint)sizeof(nuint), (uint)sizeof(nuint) + 1));
+            Assert.Throws<ArgumentException>(() => 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<OutOfMemoryException>(() => NativeMemory.Alloc(nuint.MaxValue));
+        }
+
+        [Fact]
+        public void AllocElementCountOOMTest()
+        {
+            Assert.Throws<OutOfMemoryException>(() => NativeMemory.Alloc(1, nuint.MaxValue));
+            Assert.Throws<OutOfMemoryException>(() => NativeMemory.Alloc(nuint.MaxValue, 1));
+            Assert.Throws<OutOfMemoryException>(() => 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<OutOfMemoryException>(() => NativeMemory.AllocZeroed(nuint.MaxValue));
+        }
+
+        [Fact]
+        public void AllocZeroedElementCountOOMTest()
+        {
+            Assert.Throws<OutOfMemoryException>(() => NativeMemory.AllocZeroed(1, nuint.MaxValue));
+            Assert.Throws<OutOfMemoryException>(() => NativeMemory.AllocZeroed(nuint.MaxValue, 1));
+            Assert.Throws<OutOfMemoryException>(() => 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<OutOfMemoryException>(() => 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);
+        }
+    }
+}