Blame SOURCES/ruby-3.1.0-Use-mmap-for-allocating-heap-pages-in-the-GC.patch

9a8c02
From bcab8c3cd877506de75f50e0f9ed98827ed554b0 Mon Sep 17 00:00:00 2001
9a8c02
From: Peter Zhu <peter@peterzhu.ca>
9a8c02
Date: Tue, 23 Feb 2021 16:28:56 -0500
9a8c02
Subject: [PATCH] Use mmap for allocating heap pages
9a8c02
9a8c02
---
9a8c02
 configure.ac                 |  16 ++++
9a8c02
 gc.c                         | 149 ++++++++++++++++++++++++++---------
9a8c02
 test/ruby/test_gc_compact.rb |  41 ++++++----
9a8c02
 3 files changed, 155 insertions(+), 51 deletions(-)
9a8c02
9a8c02
diff --git a/configure.ac b/configure.ac
9a8c02
index 2dcebdde9f..b1b190004d 100644
9a8c02
--- a/configure.ac
9a8c02
+++ b/configure.ac
9a8c02
@@ -1944,6 +1944,7 @@ AC_CHECK_FUNCS(memmem)
9a8c02
 AC_CHECK_FUNCS(mkfifo)
9a8c02
 AC_CHECK_FUNCS(mknod)
9a8c02
 AC_CHECK_FUNCS(mktime)
9a8c02
+AC_CHECK_FUNCS(mmap)
9a8c02
 AC_CHECK_FUNCS(openat)
9a8c02
 AC_CHECK_FUNCS(pipe2)
9a8c02
 AC_CHECK_FUNCS(poll)
9a8c02
@@ -2666,6 +2667,21 @@ main(int argc, char *argv[])
9a8c02
 	rb_cv_fork_with_pthread=yes)])
9a8c02
     test x$rb_cv_fork_with_pthread = xyes || AC_DEFINE(CANNOT_FORK_WITH_PTHREAD)
9a8c02
 ])
9a8c02
+
9a8c02
+AC_CHECK_HEADERS([sys/user.h])
9a8c02
+AS_IF([test "x$ac_cv_func_mmap" = xyes], [
9a8c02
+    AC_CACHE_CHECK([whether PAGE_SIZE is compile-time const], rb_cv_const_page_size,
9a8c02
+	[malloc_headers=`sed -n '/MALLOC_HEADERS_BEGIN/,/MALLOC_HEADERS_END/p' ${srcdir}/gc.c`
9a8c02
+	AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[$malloc_headers
9a8c02
+            typedef char conftest_page[PAGE_SIZE];
9a8c02
+        ]], [[]])],
9a8c02
+        [rb_cv_const_page_size=yes],
9a8c02
+        [rb_cv_const_page_size=no])])
9a8c02
+])
9a8c02
+AS_IF([test "x$rb_cv_const_page_size" = xyes],
9a8c02
+    [AC_DEFINE(HAVE_CONST_PAGE_SIZE, 1)],
9a8c02
+    [AC_DEFINE(HAVE_CONST_PAGE_SIZE, 0)]
9a8c02
+)
9a8c02
 }
9a8c02
 
9a8c02
 : "runtime section" && {
9a8c02
diff --git a/gc.c b/gc.c
9a8c02
index f6acf3e117..6f8e5f242d 100644
9a8c02
--- a/gc.c
9a8c02
+++ b/gc.c
9a8c02
@@ -32,6 +32,7 @@
9a8c02
 #include <stdarg.h>
9a8c02
 #include <stdio.h>
9a8c02
 
9a8c02
+/* MALLOC_HEADERS_BEGIN */
9a8c02
 #ifndef HAVE_MALLOC_USABLE_SIZE
9a8c02
 # ifdef _WIN32
9a8c02
 #  define HAVE_MALLOC_USABLE_SIZE
9a8c02
@@ -54,6 +55,12 @@
9a8c02
 # endif
9a8c02
 #endif
9a8c02
 
9a8c02
+#if !defined(PAGE_SIZE) && defined(HAVE_SYS_USER_H)
9a8c02
+/* LIST_HEAD conflicts with sys/queue.h on macOS */
9a8c02
+# include <sys/user.h>
9a8c02
+#endif
9a8c02
+/* MALLOC_HEADERS_END */
9a8c02
+
9a8c02
 #ifdef HAVE_SYS_TIME_H
9a8c02
 # include <sys/time.h>
9a8c02
 #endif
9a8c02
@@ -821,6 +828,25 @@ enum {
9a8c02
     HEAP_PAGE_BITMAP_SIZE = (BITS_SIZE * HEAP_PAGE_BITMAP_LIMIT),
9a8c02
     HEAP_PAGE_BITMAP_PLANES = 4 /* RGENGC: mark, unprotected, uncollectible, marking */
9a8c02
 };
9a8c02
+#define HEAP_PAGE_ALIGN (1 << HEAP_PAGE_ALIGN_LOG)
9a8c02
+#define HEAP_PAGE_SIZE HEAP_PAGE_ALIGN
9a8c02
+
9a8c02
+#ifdef HAVE_MMAP
9a8c02
+# if HAVE_CONST_PAGE_SIZE
9a8c02
+/* If we have the HEAP_PAGE and it is a constant, then we can directly use it. */
9a8c02
+static const bool USE_MMAP_ALIGNED_ALLOC = (PAGE_SIZE <= HEAP_PAGE_SIZE);
9a8c02
+# elif defined(PAGE_MAX_SIZE) && (PAGE_MAX_SIZE <= HEAP_PAGE_SIZE)
9a8c02
+/* PAGE_SIZE <= HEAP_PAGE_SIZE */
9a8c02
+static const bool USE_MMAP_ALIGNED_ALLOC = true;
9a8c02
+# else
9a8c02
+/* Otherwise, fall back to determining if we can use mmap during runtime. */
9a8c02
+#  define USE_MMAP_ALIGNED_ALLOC (use_mmap_aligned_alloc != false)
9a8c02
+
9a8c02
+static bool use_mmap_aligned_alloc;
9a8c02
+# endif
9a8c02
+#elif !defined(__MINGW32__) && !defined(_WIN32)
9a8c02
+static const bool USE_MMAP_ALIGNED_ALLOC = false;
9a8c02
+#endif
9a8c02
 
9a8c02
 struct heap_page {
9a8c02
     short total_slots;
9a8c02
@@ -1760,14 +1786,14 @@ heap_unlink_page(rb_objspace_t *objspace, rb_heap_t *heap, struct heap_page *pag
9a8c02
     heap->total_slots -= page->total_slots;
9a8c02
 }
9a8c02
 
9a8c02
-static void rb_aligned_free(void *ptr);
9a8c02
+static void rb_aligned_free(void *ptr, size_t size);
9a8c02
 
9a8c02
 static void
9a8c02
 heap_page_free(rb_objspace_t *objspace, struct heap_page *page)
9a8c02
 {
9a8c02
     heap_allocated_pages--;
9a8c02
     objspace->profile.total_freed_pages++;
9a8c02
-    rb_aligned_free(GET_PAGE_BODY(page->start));
9a8c02
+    rb_aligned_free(GET_PAGE_BODY(page->start), HEAP_PAGE_SIZE);
9a8c02
     free(page);
9a8c02
 }
9a8c02
 
9a8c02
@@ -1819,7 +1845,7 @@ heap_page_allocate(rb_objspace_t *objspace)
9a8c02
     /* assign heap_page entry */
9a8c02
     page = calloc1(sizeof(struct heap_page));
9a8c02
     if (page == 0) {
9a8c02
-        rb_aligned_free(page_body);
9a8c02
+        rb_aligned_free(page_body, HEAP_PAGE_SIZE);
9a8c02
 	rb_memerror();
9a8c02
     }
9a8c02
 
9a8c02
@@ -3159,15 +3185,18 @@ Init_heap(void)
9a8c02
 {
9a8c02
     rb_objspace_t *objspace = &rb_objspace;
9a8c02
 
9a8c02
-#if defined(HAVE_SYSCONF) && defined(_SC_PAGE_SIZE)
9a8c02
-    /* If Ruby's heap pages are not a multiple of the system page size, we
9a8c02
-     * cannot use mprotect for the read barrier, so we must disable automatic
9a8c02
-     * compaction. */
9a8c02
-    int pagesize;
9a8c02
-    pagesize = (int)sysconf(_SC_PAGE_SIZE);
9a8c02
-    if ((HEAP_PAGE_SIZE % pagesize) != 0) {
9a8c02
-        ruby_enable_autocompact = 0;
9a8c02
-    }
9a8c02
+#if defined(HAVE_MMAP) && !HAVE_CONST_PAGE_SIZE && !defined(PAGE_MAX_SIZE)
9a8c02
+    /* Need to determine if we can use mmap at runtime. */
9a8c02
+# ifdef PAGE_SIZE
9a8c02
+    /* If the PAGE_SIZE macro can be used. */
9a8c02
+    use_mmap_aligned_alloc = PAGE_SIZE <= HEAP_PAGE_SIZE;
9a8c02
+# elif defined(HAVE_SYSCONF) && defined(_SC_PAGE_SIZE)
9a8c02
+    /* If we can use sysconf to determine the page size. */
9a8c02
+    use_mmap_aligned_alloc = sysconf(_SC_PAGE_SIZE) <= HEAP_PAGE_SIZE;
9a8c02
+# else
9a8c02
+    /* Otherwise we can't determine the system page size, so don't use mmap. */
9a8c02
+    use_mmap_aligned_alloc = FALSE;
9a8c02
+# endif
9a8c02
 #endif
9a8c02
 
9a8c02
     objspace->next_object_id = INT2FIX(OBJ_ID_INITIAL);
9a8c02
@@ -8533,6 +8562,14 @@ gc_start_internal(rb_execution_context_t *ec, VALUE self, VALUE full_mark, VALUE
9a8c02
 
9a8c02
     /* For now, compact implies full mark / sweep, so ignore other flags */
9a8c02
     if (RTEST(compact)) {
9a8c02
+        /* If not MinGW, Windows, or does not have mmap, we cannot use mprotect for
9a8c02
+         * the read barrier, so we must disable compaction. */
9a8c02
+#if !defined(__MINGW32__) && !defined(_WIN32)
9a8c02
+        if (!USE_MMAP_ALIGNED_ALLOC) {
9a8c02
+            rb_raise(rb_eNotImpError, "Compaction isn't available on this platform");
9a8c02
+        }
9a8c02
+#endif
9a8c02
+
9a8c02
         reason |= GPR_FLAG_COMPACT;
9a8c02
     } else {
9a8c02
         if (!RTEST(full_mark))       reason &= ~GPR_FLAG_FULL_MARK;
9a8c02
@@ -9944,16 +9981,14 @@ gc_disable(rb_execution_context_t *ec, VALUE _)
9a8c02
 static VALUE
9a8c02
 gc_set_auto_compact(rb_execution_context_t *ec, VALUE _, VALUE v)
9a8c02
 {
9a8c02
-#if defined(HAVE_SYSCONF) && defined(_SC_PAGE_SIZE)
9a8c02
-    /* If Ruby's heap pages are not a multiple of the system page size, we
9a8c02
-     * cannot use mprotect for the read barrier, so we must disable automatic
9a8c02
-     * compaction. */
9a8c02
-    int pagesize;
9a8c02
-    pagesize = (int)sysconf(_SC_PAGE_SIZE);
9a8c02
-    if ((HEAP_PAGE_SIZE % pagesize) != 0) {
9a8c02
+    /* If not MinGW, Windows, or does not have mmap, we cannot use mprotect for
9a8c02
+     * the read barrier, so we must disable automatic compaction. */
9a8c02
+#if !defined(__MINGW32__) && !defined(_WIN32)
9a8c02
+    if (!USE_MMAP_ALIGNED_ALLOC) {
9a8c02
         rb_raise(rb_eNotImpError, "Automatic compaction isn't available on this platform");
9a8c02
     }
9a8c02
 #endif
9a8c02
+
9a8c02
     ruby_enable_autocompact = RTEST(v);
9a8c02
     return v;
9a8c02
 }
9a8c02
@@ -10350,22 +10385,54 @@ rb_aligned_malloc(size_t alignment, size_t size)
9a8c02
 #elif defined _WIN32
9a8c02
     void *_aligned_malloc(size_t, size_t);
9a8c02
     res = _aligned_malloc(size, alignment);
9a8c02
-#elif defined(HAVE_POSIX_MEMALIGN)
9a8c02
-    if (posix_memalign(&res, alignment, size) == 0) {
9a8c02
-        return res;
9a8c02
+#else
9a8c02
+    if (USE_MMAP_ALIGNED_ALLOC) {
9a8c02
+        GC_ASSERT(alignment % sysconf(_SC_PAGE_SIZE) == 0);
9a8c02
+
9a8c02
+        char *ptr = mmap(NULL, alignment + size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
9a8c02
+        if (ptr == MAP_FAILED) {
9a8c02
+            return NULL;
9a8c02
+        }
9a8c02
+
9a8c02
+        char *aligned = ptr + alignment;
9a8c02
+        aligned -= ((VALUE)aligned & (alignment - 1));
9a8c02
+        GC_ASSERT(aligned > ptr);
9a8c02
+        GC_ASSERT(aligned <= ptr + alignment);
9a8c02
+
9a8c02
+        size_t start_out_of_range_size = aligned - ptr;
9a8c02
+        GC_ASSERT(start_out_of_range_size % sysconf(_SC_PAGE_SIZE) == 0);
9a8c02
+        if (start_out_of_range_size > 0) {
9a8c02
+            if (munmap(ptr, start_out_of_range_size)) {
9a8c02
+                rb_bug("rb_aligned_malloc: munmap failed for start");
9a8c02
+            }
9a8c02
+        }
9a8c02
+
9a8c02
+        size_t end_out_of_range_size = alignment - start_out_of_range_size;
9a8c02
+        GC_ASSERT(end_out_of_range_size % sysconf(_SC_PAGE_SIZE) == 0);
9a8c02
+        if (end_out_of_range_size > 0) {
9a8c02
+            if (munmap(aligned + size, end_out_of_range_size)) {
9a8c02
+                rb_bug("rb_aligned_malloc: munmap failed for end");
9a8c02
+            }
9a8c02
+        }
9a8c02
+
9a8c02
+        res = (void *)aligned;
9a8c02
     }
9a8c02
     else {
9a8c02
-        return NULL;
9a8c02
+# if defined(HAVE_POSIX_MEMALIGN)
9a8c02
+        if (posix_memalign(&res, alignment, size) != 0) {
9a8c02
+            return NULL;
9a8c02
+        }
9a8c02
+# elif defined(HAVE_MEMALIGN)
9a8c02
+        res = memalign(alignment, size);
9a8c02
+# else
9a8c02
+        char* aligned;
9a8c02
+        res = malloc(alignment + size + sizeof(void*));
9a8c02
+        aligned = (char*)res + alignment + sizeof(void*);
9a8c02
+        aligned -= ((VALUE)aligned & (alignment - 1));
9a8c02
+        ((void**)aligned)[-1] = res;
9a8c02
+        res = (void*)aligned;
9a8c02
+# endif
9a8c02
     }
9a8c02
-#elif defined(HAVE_MEMALIGN)
9a8c02
-    res = memalign(alignment, size);
9a8c02
-#else
9a8c02
-    char* aligned;
9a8c02
-    res = malloc(alignment + size + sizeof(void*));
9a8c02
-    aligned = (char*)res + alignment + sizeof(void*);
9a8c02
-    aligned -= ((VALUE)aligned & (alignment - 1));
9a8c02
-    ((void**)aligned)[-1] = res;
9a8c02
-    res = (void*)aligned;
9a8c02
 #endif
9a8c02
 
9a8c02
     /* alignment must be a power of 2 */
9a8c02
@@ -10375,16 +10442,26 @@ rb_aligned_malloc(size_t alignment, size_t size)
9a8c02
 }
9a8c02
 
9a8c02
 static void
9a8c02
-rb_aligned_free(void *ptr)
9a8c02
+rb_aligned_free(void *ptr, size_t size)
9a8c02
 {
9a8c02
 #if defined __MINGW32__
9a8c02
     __mingw_aligned_free(ptr);
9a8c02
 #elif defined _WIN32
9a8c02
     _aligned_free(ptr);
9a8c02
-#elif defined(HAVE_MEMALIGN) || defined(HAVE_POSIX_MEMALIGN)
9a8c02
-    free(ptr);
9a8c02
 #else
9a8c02
-    free(((void**)ptr)[-1]);
9a8c02
+    if (USE_MMAP_ALIGNED_ALLOC) {
9a8c02
+        GC_ASSERT(size % sysconf(_SC_PAGE_SIZE) == 0);
9a8c02
+        if (munmap(ptr, size)) {
9a8c02
+            rb_bug("rb_aligned_free: munmap failed");
9a8c02
+        }
9a8c02
+    }
9a8c02
+    else {
9a8c02
+# if defined(HAVE_POSIX_MEMALIGN) || defined(HAVE_MEMALIGN)
9a8c02
+        free(ptr);
9a8c02
+# else
9a8c02
+        free(((void**)ptr)[-1]);
9a8c02
+# endif
9a8c02
+    }
9a8c02
 #endif
9a8c02
 }
9a8c02
 
9a8c02
diff --git a/test/ruby/test_gc_compact.rb b/test/ruby/test_gc_compact.rb
9a8c02
index 4a8cff33f4..f5cab55ba7 100644
9a8c02
--- a/test/ruby/test_gc_compact.rb
9a8c02
+++ b/test/ruby/test_gc_compact.rb
9a8c02
@@ -4,12 +4,32 @@
9a8c02
 require 'etc'
9a8c02
 
9a8c02
 class TestGCCompact < Test::Unit::TestCase
9a8c02
-  class AutoCompact < Test::Unit::TestCase
9a8c02
+  module SupportsCompact
9a8c02
     def setup
9a8c02
       skip "autocompact not supported on this platform" unless supports_auto_compact?
9a8c02
       super
9a8c02
     end
9a8c02
 
9a8c02
+    private
9a8c02
+
9a8c02
+    def supports_auto_compact?
9a8c02
+      return true unless defined?(Etc::SC_PAGE_SIZE)
9a8c02
+
9a8c02
+      begin
9a8c02
+        return GC::INTERNAL_CONSTANTS[:HEAP_PAGE_SIZE] % Etc.sysconf(Etc::SC_PAGE_SIZE) == 0
9a8c02
+      rescue NotImplementedError
9a8c02
+      rescue ArgumentError
9a8c02
+      end
9a8c02
+
9a8c02
+      true
9a8c02
+    end
9a8c02
+  end
9a8c02
+
9a8c02
+  include SupportsCompact
9a8c02
+
9a8c02
+  class AutoCompact < Test::Unit::TestCase
9a8c02
+    include SupportsCompact
9a8c02
+
9a8c02
     def test_enable_autocompact
9a8c02
       before = GC.auto_compact
9a8c02
       GC.auto_compact = true
9a8c02
@@ -59,26 +79,17 @@ def test_implicit_compaction_does_something
9a8c02
     ensure
9a8c02
       GC.auto_compact = before
9a8c02
     end
9a8c02
-
9a8c02
-    private
9a8c02
-
9a8c02
-    def supports_auto_compact?
9a8c02
-      return true unless defined?(Etc::SC_PAGE_SIZE)
9a8c02
-
9a8c02
-      begin
9a8c02
-        return GC::INTERNAL_CONSTANTS[:HEAP_PAGE_SIZE] % Etc.sysconf(Etc::SC_PAGE_SIZE) == 0
9a8c02
-      rescue NotImplementedError
9a8c02
-      rescue ArgumentError
9a8c02
-      end
9a8c02
-
9a8c02
-      true
9a8c02
-    end
9a8c02
   end
9a8c02
 
9a8c02
   def os_page_size
9a8c02
     return true unless defined?(Etc::SC_PAGE_SIZE)
9a8c02
   end
9a8c02
 
9a8c02
+  def setup
9a8c02
+    skip "autocompact not supported on this platform" unless supports_auto_compact?
9a8c02
+    super
9a8c02
+  end
9a8c02
+
9a8c02
   def test_gc_compact_stats
9a8c02
     list = []
9a8c02
 
9a8c02
-- 
9a8c02
2.30.1 (Apple Git-130)
9a8c02