[MLton-commit] r6941

Wesley Terpstra wesley at mlton.org
Wed Oct 15 18:12:13 PDT 2008


This commit adds mremap support to cygwin and MinGW. There replaces the
previous mremap for MinGW that could only recover space in a shrunk heap. 

There are several changes necessary:

1. Allocations in windows must be aligned to sysinfo.dwAllocationGranularity.
   Previously, they were aligned only to the page size. By increasing MLton's
   page size to the allocation granularity, we can ensure our ability to map
   memory after an existing map.

2. Removed the obsolete GC_decommit method which was needed to release 
   memory after a heap shrink. The new GC_release method handles both heap
   shrink and complete heap release. It is considerably complicated by the
   possibility of multiple reservations within the released range.

3. Added two methods GC_extend{Head,Tail} that are supplied by platforms to
   increase/decrease the size of an existing heap. This matters for at least
   windows because it needs to be able to recommit memory that was put in the
   reserve state. A normal allocation must fail if it sees reserved memory as
   this indicates a conflicting map. Extending an existing map, however, can
   safely confirm that the reserved memory is owned by the map we are growing.

4. Simplify / lobotomize Windows_mmapAnon. It does not need to reserve memory.

5. Replace Windows_mremap with mremap.c, a generic mremap implementation.
   This new mremap scans memory before and after the existing mapping.
   The results are cached and used to inform all remap attempts during the
   GC remap backoff. It prefers to perform a moving expansion to prevent
   build-up of many small mmaps. It also prefers to grow towards increasing
   addresses. This is good since heaps are also shrunk in this direction.


----------------------------------------------------------------------

U   mlton/trunk/runtime/gc/heap.c
U   mlton/trunk/runtime/platform/cygwin.c
U   mlton/trunk/runtime/platform/cygwin.h
U   mlton/trunk/runtime/platform/mingw.c
A   mlton/trunk/runtime/platform/mremap.c
U   mlton/trunk/runtime/platform/solaris.c
U   mlton/trunk/runtime/platform/use-mmap.c
U   mlton/trunk/runtime/platform/windows.c
U   mlton/trunk/runtime/platform.h

----------------------------------------------------------------------

Modified: mlton/trunk/runtime/gc/heap.c
===================================================================
--- mlton/trunk/runtime/gc/heap.c	2008-10-16 00:59:18 UTC (rev 6940)
+++ mlton/trunk/runtime/gc/heap.c	2008-10-16 01:12:08 UTC (rev 6941)
@@ -150,7 +150,7 @@
                uintmaxToCommaString(keepWithMapsSize - keepSize));
     }
     assert (keepWithMapsSize <= h->withMapsSize);
-    GC_decommit (h->start + keepWithMapsSize, h->withMapsSize - keepWithMapsSize);
+    GC_release (h->start + keepWithMapsSize, h->withMapsSize - keepWithMapsSize);
     h->size = keepSize;
     h->withMapsSize = keepWithMapsSize;
   }

Modified: mlton/trunk/runtime/platform/cygwin.c
===================================================================
--- mlton/trunk/runtime/platform/cygwin.c	2008-10-16 00:59:18 UTC (rev 6940)
+++ mlton/trunk/runtime/platform/cygwin.c	2008-10-16 01:12:08 UTC (rev 6941)
@@ -6,6 +6,7 @@
 #include "mmap.c"
 #include "recv.nonblock.c"
 #include "windows.c"
+#include "mremap.c"
 
 /* 
  * The sysconf(_SC_PAGESIZE) is the necessary alignment for using
@@ -20,10 +21,19 @@
  * See: http://cygwin.com/ml/cygwin/2006-06/msg00341.html
  */ 
 static size_t GC_pageSize_sysconf (void) {
+  SYSTEM_INFO sysinfo;
   long int pageSize;
 
   pageSize = sysconf (_SC_PAGESIZE);
-  return (size_t)pageSize;
+  GetSystemInfo(&sysinfo);
+  
+  /* MLton_Platform_CygwinUseMmap is not set when this is called.
+   * Assume the worst; choose the larger allocation unit.
+   */
+  if ((size_t)pageSize < (size_t)sysinfo.dwAllocationGranularity)
+    return (size_t)sysinfo.dwAllocationGranularity;
+  else
+    return (size_t)pageSize;
 }
 
 static size_t GC_pageSize_windows (void) {
@@ -59,13 +69,6 @@
   return (uintmax_t)memstat.dwTotalPhys;
 }
 
-void GC_decommit (void *base, size_t length) {
-        if (MLton_Platform_CygwinUseMmap)
-                munmap_safe (base, length);
-        else
-                Windows_decommit (base, length);
-}
-
 void *GC_mmapAnon (void *start, size_t length) {
         if (MLton_Platform_CygwinUseMmap)
                 return mmapAnon (start, length);
@@ -77,10 +80,23 @@
         if (MLton_Platform_CygwinUseMmap)
                 munmap_safe (base, length);
         else
-                Windows_release (base);
+                Windows_release (base, length);
 }
 
+void* GC_extendHead (void *base, size_t length) {
+        if (MLton_Platform_CygwinUseMmap)
+                return mmapAnon (base, length);
+        else
+                return Windows_mmapAnon (base, length);
+}
 
+void* GC_extendTail (void *base, size_t length) {
+        if (MLton_Platform_CygwinUseMmap)
+                return mmapAnon (base, length);
+        else
+                return Windows_extend (base, length);
+}
+
 HANDLE fileDesHandle (int fd) {
   // The temporary prevents a "cast does not match function type" warning.
   long t;

Modified: mlton/trunk/runtime/platform/cygwin.h
===================================================================
--- mlton/trunk/runtime/platform/cygwin.h	2008-10-16 00:59:18 UTC (rev 6940)
+++ mlton/trunk/runtime/platform/cygwin.h	2008-10-16 01:12:08 UTC (rev 6941)
@@ -34,7 +34,7 @@
 #define HAS_FEROUND FALSE
 #define HAS_FPCLASSIFY TRUE
 #define HAS_MSG_DONTWAIT FALSE
-#define HAS_REMAP FALSE
+#define HAS_REMAP TRUE
 #define HAS_SIGALTSTACK FALSE
 #define HAS_SIGNBIT TRUE
 #define HAS_SPAWN TRUE

Modified: mlton/trunk/runtime/platform/mingw.c
===================================================================
--- mlton/trunk/runtime/platform/mingw.c	2008-10-16 00:59:18 UTC (rev 6940)
+++ mlton/trunk/runtime/platform/mingw.c	2008-10-16 01:12:08 UTC (rev 6941)
@@ -3,22 +3,22 @@
 #include "platform.h"
 
 #include "windows.c"
+#include "mremap.c"
 
-void GC_decommit (void *base, size_t length) {
-        Windows_decommit (base, length);
+void *GC_mmapAnon (void *start, size_t length) {
+        return Windows_mmapAnon (start, length);
 }
 
-void *GC_mremap (void *base, size_t old, size_t new) {
-        return Windows_mremap (base, old, new);
+void GC_release (void *base, size_t length) {
+        Windows_release (base, length);
 }
 
-void *GC_mmapAnon (void *start, size_t length) {
-        return Windows_mmapAnon (start, length);
+void *GC_extendHead (void *base, size_t length) {
+        return Windows_mmapAnon (base, length);
 }
 
-void GC_release (void *base, 
-                 __attribute__ ((unused)) size_t length) {
-        Windows_release (base);
+void *GC_extendTail (void *base, size_t length) {
+        return Windows_extend (base, length);
 }
 
 uintmax_t GC_physMem (void) {
@@ -38,7 +38,7 @@
 size_t GC_pageSize (void) {
         SYSTEM_INFO sysinfo;
         GetSystemInfo(&sysinfo);
-        return (size_t)sysinfo.dwPageSize;
+        return (size_t)sysinfo.dwAllocationGranularity;
 }
 
 HANDLE fileDesHandle (int fd) {

Added: mlton/trunk/runtime/platform/mremap.c
===================================================================
--- mlton/trunk/runtime/platform/mremap.c	2008-10-16 00:59:18 UTC (rev 6940)
+++ mlton/trunk/runtime/platform/mremap.c	2008-10-16 01:12:08 UTC (rev 6941)
@@ -0,0 +1,149 @@
+#define GC_SMALLEST_PAGESIZE 4096
+/* #define DEBUG_MREMAP 1 */
+
+/* Used to allocate at the head and tail of an existing allocation */
+static void *GC_extendHead (void *base, size_t length);
+static void *GC_extendTail (void *base, size_t length);
+  
+void *GC_mremap (void *base, size_t oldLength, size_t newLength) {
+  static void*  cacheAddress = 0;
+  static size_t cacheOldLength = 0;
+  static size_t cacheNewLength = 0;
+  static size_t cacheTailSize;
+  static size_t cacheHeadSize;
+   
+  void* tail;
+  void* head;
+  void* alloc;
+  size_t growth, bsLow, bsHigh, bsTry;
+
+#ifdef DEBUG_MREMAP  
+  fprintf(stderr, "remap(%08X, %d, %d)\n", (size_t)base, oldLength, newLength);
+  fflush(stderr);
+#endif
+  
+  if (newLength == oldLength)
+    return base;
+  if (newLength < oldLength) {
+    GC_release((char*)base + newLength, oldLength-newLength);
+    return base;
+  }
+  
+  /* Prefer a moving remap -> it results in less mmapped regions */
+  alloc = GC_mmapAnon(0, newLength);
+  if (alloc != (void*)-1) {
+    memcpy(alloc, base, oldLength);
+    GC_release(base, oldLength);
+    return alloc;
+  }
+  
+  growth = newLength-oldLength;
+  
+  if (cacheAddress   == base      && 
+      cacheOldLength == oldLength &&
+      cacheNewLength >  newLength) /* cache only during backoff */
+    goto GC_mremap_cached;
+  
+  /* Start probing for the available tail length */
+  bsLow = 0;
+  bsHigh = (growth+GC_SMALLEST_PAGESIZE-1)/GC_SMALLEST_PAGESIZE;
+  /* Round bsHigh to a power of two -> allocation works with all page sizes */
+  for (bsTry = 1; bsTry <= bsHigh; bsTry += bsTry) { }
+  bsHigh = bsTry;
+  while (bsHigh - bsLow > 1) {
+    bsTry = (bsHigh + bsLow)/2;
+    tail = (char*)base + oldLength;
+    alloc = GC_extendTail(tail, bsTry*GC_SMALLEST_PAGESIZE);
+    
+    if (tail == alloc) {
+      GC_release(alloc, bsTry*GC_SMALLEST_PAGESIZE);
+      bsLow = bsTry;
+    } else {
+      if (alloc != (void*)-1)
+        GC_release(alloc, bsTry*GC_SMALLEST_PAGESIZE);
+      bsHigh = bsTry;
+    }
+  }
+  cacheTailSize = bsLow*GC_SMALLEST_PAGESIZE;
+  
+  /* Start probing for the available head length */
+  bsLow = 0;
+  bsHigh = (growth+GC_SMALLEST_PAGESIZE-1)/GC_SMALLEST_PAGESIZE;
+  /* Round bsHigh to a power of two -> allocation works with all page sizes */
+  for (bsTry = 1; bsTry <= bsHigh; bsTry += bsTry) { }
+  bsHigh = bsTry;
+  while (bsHigh - bsLow > 1) {
+    bsTry = (bsHigh + bsLow)/2;
+    head = (char*)base - bsTry*GC_SMALLEST_PAGESIZE;
+    alloc = GC_extendHead(head, bsTry*GC_SMALLEST_PAGESIZE);
+    
+    if (head == alloc) {
+      GC_release(alloc, bsTry*GC_SMALLEST_PAGESIZE);
+      bsLow = bsTry;
+    } else {
+      if (alloc != (void*)-1)
+        GC_release(alloc, bsTry*GC_SMALLEST_PAGESIZE);
+      bsHigh = bsTry;
+    }
+  }
+  cacheHeadSize = bsLow*GC_SMALLEST_PAGESIZE;
+  
+#ifdef DEBUG_MREMAP  
+  fprintf(stderr, "Expansion detects: %10d/%10d/%10d = %10d\n", 
+   cacheHeadSize, oldLength, cacheTailSize,
+   cacheHeadSize+ oldLength+ cacheTailSize);
+  fflush(stderr);
+#endif
+  
+  cacheAddress = base;
+  cacheOldLength = oldLength;
+  
+GC_mremap_cached:
+  cacheNewLength = newLength;
+  
+  /* Is there enough free space? */
+  if (cacheTailSize + cacheHeadSize < growth) {
+    return (void*)-1;
+  }
+  
+#ifdef DEBUG_MREMAP  
+  fprintf(stderr, "Expansion attempts %d bytes\n", newLength);
+  fflush(stderr);
+#endif
+  
+  if (growth <= cacheTailSize) {
+    tail = (char*)base + oldLength;
+    alloc = GC_extendTail(tail, growth);
+    if (alloc != tail) {
+      if (alloc != (void*)-1)
+        GC_release(alloc, growth);
+      return (void*)-1;
+    }
+    return base;
+  }
+  
+  if (cacheTailSize > 0) {
+    tail = (char*)base + oldLength;
+    alloc = GC_extendTail(tail, cacheTailSize);
+    if (alloc != tail) {
+      if (alloc != (void*)-1)
+        GC_release(alloc, cacheTailSize);
+      return (void*)-1;
+    }
+  } else {
+    tail = 0; /* quell warning */
+  }
+  
+  head = (char*)base - (growth-cacheTailSize);
+  alloc = GC_extendHead(head, growth-cacheTailSize);
+  if (alloc != head) {
+    if (alloc != (void*)-1)
+      GC_release(alloc, growth-cacheTailSize);
+    if (cacheTailSize > 0)
+      GC_release(tail, cacheTailSize);
+    return (void*)-1;
+  }
+  
+  memmove(head, base, oldLength);
+  return head;
+}

Modified: mlton/trunk/runtime/platform/solaris.c
===================================================================
--- mlton/trunk/runtime/platform/solaris.c	2008-10-16 00:59:18 UTC (rev 6940)
+++ mlton/trunk/runtime/platform/solaris.c	2008-10-16 01:12:08 UTC (rev 6941)
@@ -83,10 +83,6 @@
         sa->sa_sigaction = (void (*)(int, siginfo_t*, void*))catcher;
 }
 
-void GC_decommit (void *base, size_t length) {
-        munmap_safe (base, length);
-}
-
 /* On Solaris 5.7, MAP_ANON causes EINVAL and mmap requires a file descriptor.
  */
 void *GC_mmapAnon (void *start, size_t length) {

Modified: mlton/trunk/runtime/platform/use-mmap.c
===================================================================
--- mlton/trunk/runtime/platform/use-mmap.c	2008-10-16 00:59:18 UTC (rev 6940)
+++ mlton/trunk/runtime/platform/use-mmap.c	2008-10-16 01:12:08 UTC (rev 6941)
@@ -1,9 +1,5 @@
 #include "mmap.c"
 
-void GC_decommit (void *base, size_t length) {
-        munmap_safe (base, length);
-}
-
 void GC_release (void *base, size_t length) {
         munmap_safe (base, length);
 }

Modified: mlton/trunk/runtime/platform/windows.c
===================================================================
--- mlton/trunk/runtime/platform/windows.c	2008-10-16 00:59:18 UTC (rev 6940)
+++ mlton/trunk/runtime/platform/windows.c	2008-10-16 01:12:08 UTC (rev 6941)
@@ -206,37 +206,25 @@
         return dupd;
 }
 
-static inline void Windows_decommit (void *base, size_t length) {
-        if (0 == VirtualFree (base, length, MEM_DECOMMIT))
-                die ("VirtualFree decommit failed");
-}
+/* Windows memory is allocated in two phases: reserve and commit.
+ * A reservation makes the address space unavailable to other reservations.
+ * Commiting reserved memory actually maps the reserved memory for use.
+ * Decommitting a portion of a reservation releases the physical memory only.
+ * The complicating detail is that one cannot partially release a reservation.
+ *
+ * The management routines below manage a 'heap' that is composed of several
+ * distinct reservations, laid out in the following order:
+ *   0+ reservations set MEM_COMMIT
+ *   1  reservation starting MEM_COMMIT with an optional MEM_RESERVE tail
+ *
+ * The heap always starts on a reservation and ends at where the MEM_RESERVE
+ * region (if any) begins.
+ */
 
-static inline void *Windows_mremap (void *base, size_t old, size_t new) {
+/* Create a new heap */
+static inline void *Windows_mmapAnon (void *base, size_t length) {
         void *res;
-        void *tail;
 
-        /* Attempt to recover decommit'd memory */
-        tail = (void*)((intptr_t)base + old);
-        res = VirtualAlloc(tail, new - old, MEM_COMMIT, PAGE_READWRITE);
-        if (NULL == res)
-                return (void*)-1;
-
-        return base;
-}
-
-static inline void *Windows_mmapAnon (void *start, size_t length) {
-        void *res;
-        size_t reserve;
-
-        /* If length > 256MB on win32, we round up to the nearest 512MB.
-         * By reserving more than we need, we can later mremap to use it.
-         * This avoids fragmentation on 32 bit machines, near the 2GB limit.
-         * It doesn't hurt us in 64 bit mode either (lots of address space).
-         */
-        if (length > ((size_t)1 << 28))
-                reserve = align (length, ((size_t)1 << 29));
-        else    reserve = length;
-
         /* We prevoiusly used "0" instead of start, which lead to crashes.
          * After reading win32 documentation, the reason for these crashes
          * becomes clear: we were using only MEM_COMMIT! If there was memory
@@ -245,38 +233,115 @@
          * freed, it will kill the new heap as well. This bug will not happen
          * now because we reserve, then commit. Reserved memory cannot conflict.
          */
-        res = VirtualAlloc (start, reserve, MEM_RESERVE, PAGE_NOACCESS);
-
-        /* Try shifting the block left (to play well with MLton's scan) */
-        if (NULL == res) {
-                uintptr_t base = (uintptr_t)start;
-                size_t shift = reserve - length;
-                if (base > shift)
-                        res = VirtualAlloc ((void*)(base-shift), reserve, 
-                                            MEM_RESERVE, PAGE_NOACCESS);
-        }
-
-        /* Fall back to zero reserved allocation */
-        if (NULL == res)
-                res = VirtualAlloc (start, length, MEM_RESERVE, PAGE_NOACCESS);
-
-        /* Nothing more we can try at this offset */
-        if (NULL == res)
+        res = VirtualAlloc (base, length, MEM_RESERVE, PAGE_NOACCESS);
+        if (0 == res)
                 return (void*)-1;
 
         /* Actually get the memory for use */
-        res = VirtualAlloc (res, length, MEM_COMMIT, PAGE_READWRITE);
-        if (NULL == res)
-                die("VirtualAlloc MEM_COMMIT of MEM_RESERVEd memory failed!\n");
+        if (0 == VirtualAlloc (res, length, MEM_COMMIT, PAGE_READWRITE)) {
+                VirtualFree(res, 0, MEM_RELEASE);
+                return (void*)-1;
+        }
 
         return res;
 }
 
-static inline void Windows_release (void *base) {
-        if (0 == VirtualFree (base, 0, MEM_RELEASE))
-                die ("VirtualFree release failed");
+static inline void Windows_release (void *base, size_t length) {
+        MEMORY_BASIC_INFORMATION mi;
+        
+        if (length == 0) return;
+        
+        /* We might not be able to release the first reservation because
+         * it overlaps the base address we wish to keep. The idea is to
+         * decommit the part we don't need, and release all reservations
+         * that may be after this point.
+         */
+        
+        if (0 == VirtualQuery(base, &mi, sizeof(mi)))
+                die("VirtualQuery failed");
+        assert (mi.State != MEM_FREE);
+        assert (mi.RegionSize <= length);
+        
+        if (mi.AllocationBase != base) {
+                if (0 == VirtualFree(base, mi.RegionSize, MEM_DECOMMIT))
+                        die("VirtualFree(MEM_DECOMMIT)");
+                
+                /* Requery: the following region might also be decommit */
+                VirtualQuery(base, &mi, sizeof(mi));
+                assert (mi.State == MEM_RESERVE);
+                
+                /* It's possible the consolidated reserved space is larger
+                 * than the range we were asked to free. Bail out early.
+                 */
+                if (mi.RegionSize >= length) return;
+                
+                /* Skip decommited region and move to the next reservation */
+                base = (char*)base + mi.RegionSize;
+                length -= mi.RegionSize;
+        }
+        
+        /* Clean-up the remaining tail. */
+        while (length > 0) {
+                if (0 == VirtualQuery(base, &mi, sizeof(mi)))
+                        die("VirtualQuery");
+                
+                /* We should never have a completely decommitted alloc */
+                assert (mi.State == MEM_COMMIT);
+                /* This method is supposed to only do complete releases */
+                assert (mi.AllocationBase == base);
+                /* The committed region should never exceed the length */
+                assert (mi.RegionSize <= length);
+                
+                if (0 == VirtualFree(base, 0, MEM_RELEASE))
+                        die("VirtualFree(MEM_RELEASE) failed");
+                
+                base = (char*)base + mi.RegionSize;
+                length -= mi.RegionSize;
+        }
+        
+        /* The last release also handled the optional MEM_RESERVE region */
 }
 
+/* Extend an existing heap */
+static inline void* Windows_extend (void *base, size_t length) {
+        MEMORY_BASIC_INFORMATION mi;
+        void *end;
+        
+        /* Check the status of memory after the end of the allocation */
+        VirtualQuery(base, &mi, sizeof(mi));
+        
+        if (mi.State == MEM_FREE) {
+                /* No tail of reserved memory -> simply try to allocate */
+                return Windows_mmapAnon(base, length);
+        } else if (mi.State == MEM_RESERVE) {
+                assert (mi.AllocationBase <= base);
+                end = (char*)base + mi.RegionSize;
+                
+                if (mi.RegionSize > length) { /* only commit is needed */
+                        if (0 == VirtualAlloc(base, length, 
+                                              MEM_COMMIT, PAGE_READWRITE)) {
+                                return (void*)-1;
+                        } else {
+                                return base;
+                        }
+                } else if (end == Windows_mmapAnon(end, length-mi.RegionSize)) {
+                        if (0 == VirtualAlloc(base, mi.RegionSize, 
+                                              MEM_COMMIT, PAGE_READWRITE)) {
+                                VirtualFree(end, 0, MEM_RELEASE);
+                                return (void*)-1;
+                        } else {
+                                return base;
+                        }
+                } else {
+                        /* Failed to allocate tail */
+                        return (void*)-1;
+                }
+        } else {
+                /* The memory is used by another mapping */
+                return (void*)-1;
+        }
+}
+
 C_Errno_t(C_PId_t) 
 Windows_Process_create (NullString8_t cmds, NullString8_t args, NullString8_t envs,
                         C_Fd_t in, C_Fd_t out, C_Fd_t err) {

Modified: mlton/trunk/runtime/platform.h
===================================================================
--- mlton/trunk/runtime/platform.h	2008-10-16 00:59:18 UTC (rev 6940)
+++ mlton/trunk/runtime/platform.h	2008-10-16 01:12:08 UTC (rev 6941)
@@ -169,7 +169,6 @@
                                          size_t dead_low, size_t dead_high);
 PRIVATE void *GC_mremap (void *start, size_t oldLength, size_t newLength);
 PRIVATE void GC_release (void *base, size_t length);
-PRIVATE void GC_decommit (void *base, size_t length);
 
 PRIVATE size_t GC_pageSize (void);
 PRIVATE uintmax_t GC_physMem (void);




More information about the MLton-commit mailing list