Bug #18394 ยป mmap.patch
| configure.ac | ||
|---|---|---|
| AC_CHECK_FUNCS(mkfifo) | ||
| AC_CHECK_FUNCS(mknod) | ||
| AC_CHECK_FUNCS(mktime) | ||
| AC_CHECK_FUNCS(mmap) | ||
| AC_CHECK_FUNCS(openat) | ||
| AC_CHECK_FUNCS(pipe2) | ||
| AC_CHECK_FUNCS(poll) | ||
| ... | ... | |
| 	rb_cv_fork_with_pthread=yes)]) | ||
|     test x$rb_cv_fork_with_pthread = xyes || AC_DEFINE(CANNOT_FORK_WITH_PTHREAD) | ||
| ]) | ||
| AC_CHECK_HEADERS([sys/user.h]) | ||
| AS_IF([test "x$ac_cv_func_mmap" = xyes], [ | ||
|     AC_CACHE_CHECK([whether PAGE_SIZE is compile-time const], rb_cv_const_page_size, | ||
| 	[malloc_headers=`sed -n '/MALLOC_HEADERS_BEGIN/,/MALLOC_HEADERS_END/p' ${srcdir}/gc.c` | ||
| 	AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[$malloc_headers | ||
|             typedef char conftest_page[PAGE_SIZE]; | ||
|         ]], [[]])], | ||
|         [rb_cv_const_page_size=yes], | ||
|         [rb_cv_const_page_size=no])]) | ||
| ]) | ||
| AS_IF([test "x$rb_cv_const_page_size" = xyes], | ||
|     [AC_DEFINE(HAVE_CONST_PAGE_SIZE, 1)], | ||
|     [AC_DEFINE(HAVE_CONST_PAGE_SIZE, 0)] | ||
| ) | ||
| } | ||
| : "runtime section" && { | ||
| gc.c | ||
|---|---|---|
| #include <stdarg.h> | ||
| #include <stdio.h> | ||
| /* MALLOC_HEADERS_BEGIN */ | ||
| #ifndef HAVE_MALLOC_USABLE_SIZE | ||
| # ifdef _WIN32 | ||
| #  define HAVE_MALLOC_USABLE_SIZE | ||
| ... | ... | |
| # endif | ||
| #endif | ||
| #if !defined(PAGE_SIZE) && defined(HAVE_SYS_USER_H) | ||
| /* LIST_HEAD conflicts with sys/queue.h on macOS */ | ||
| # include <sys/user.h> | ||
| #endif | ||
| /* MALLOC_HEADERS_END */ | ||
| #ifdef HAVE_SYS_TIME_H | ||
| # include <sys/time.h> | ||
| #endif | ||
| ... | ... | |
|     HEAP_PAGE_BITMAP_SIZE = (BITS_SIZE * HEAP_PAGE_BITMAP_LIMIT), | ||
|     HEAP_PAGE_BITMAP_PLANES = 4 /* RGENGC: mark, unprotected, uncollectible, marking */ | ||
| }; | ||
| #define HEAP_PAGE_ALIGN (1 << HEAP_PAGE_ALIGN_LOG) | ||
| #define HEAP_PAGE_SIZE HEAP_PAGE_ALIGN | ||
| #ifdef HAVE_MMAP | ||
| # if HAVE_CONST_PAGE_SIZE | ||
| /* If we have the HEAP_PAGE and it is a constant, then we can directly use it. */ | ||
| static const bool USE_MMAP_ALIGNED_ALLOC = (PAGE_SIZE <= HEAP_PAGE_SIZE); | ||
| # elif defined(PAGE_MAX_SIZE) && (PAGE_MAX_SIZE <= HEAP_PAGE_SIZE) | ||
| /* PAGE_SIZE <= HEAP_PAGE_SIZE */ | ||
| static const bool USE_MMAP_ALIGNED_ALLOC = true; | ||
| # else | ||
| /* Otherwise, fall back to determining if we can use mmap during runtime. */ | ||
| #  define USE_MMAP_ALIGNED_ALLOC (use_mmap_aligned_alloc != false) | ||
| static bool use_mmap_aligned_alloc; | ||
| # endif | ||
| #elif !defined(__MINGW32__) && !defined(_WIN32) | ||
| static const bool USE_MMAP_ALIGNED_ALLOC = false; | ||
| #endif | ||
| struct heap_page { | ||
|     short total_slots; | ||
| ... | ... | |
|     heap->total_slots -= page->total_slots; | ||
| } | ||
| static void rb_aligned_free(void *ptr); | ||
| static void rb_aligned_free(void *ptr, size_t size); | ||
| static void | ||
| heap_page_free(rb_objspace_t *objspace, struct heap_page *page) | ||
| { | ||
|     heap_allocated_pages--; | ||
|     objspace->profile.total_freed_pages++; | ||
|     rb_aligned_free(GET_PAGE_BODY(page->start)); | ||
|     rb_aligned_free(GET_PAGE_BODY(page->start), HEAP_PAGE_SIZE); | ||
|     free(page); | ||
| } | ||
| ... | ... | |
|     /* assign heap_page entry */ | ||
|     page = calloc1(sizeof(struct heap_page)); | ||
|     if (page == 0) { | ||
|         rb_aligned_free(page_body); | ||
|         rb_aligned_free(page_body, HEAP_PAGE_SIZE); | ||
| 	rb_memerror(); | ||
|     } | ||
| ... | ... | |
| { | ||
|     rb_objspace_t *objspace = &rb_objspace; | ||
| #if defined(HAVE_SYSCONF) && defined(_SC_PAGE_SIZE) | ||
|     /* If Ruby's heap pages are not a multiple of the system page size, we | ||
|      * cannot use mprotect for the read barrier, so we must disable automatic | ||
|      * compaction. */ | ||
|     int pagesize; | ||
|     pagesize = (int)sysconf(_SC_PAGE_SIZE); | ||
|     if ((HEAP_PAGE_SIZE % pagesize) != 0) { | ||
|         ruby_enable_autocompact = 0; | ||
|     } | ||
| #if defined(HAVE_MMAP) && !HAVE_CONST_PAGE_SIZE && !defined(PAGE_MAX_SIZE) | ||
|     /* Need to determine if we can use mmap at runtime. */ | ||
| # ifdef PAGE_SIZE | ||
|     /* If the PAGE_SIZE macro can be used. */ | ||
|     use_mmap_aligned_alloc = PAGE_SIZE <= HEAP_PAGE_SIZE; | ||
| # elif defined(HAVE_SYSCONF) && defined(_SC_PAGE_SIZE) | ||
|     /* If we can use sysconf to determine the page size. */ | ||
|     use_mmap_aligned_alloc = sysconf(_SC_PAGE_SIZE) <= HEAP_PAGE_SIZE; | ||
| # else | ||
|     /* Otherwise we can't determine the system page size, so don't use mmap. */ | ||
|     use_mmap_aligned_alloc = FALSE; | ||
| # endif | ||
| #endif | ||
|     objspace->next_object_id = INT2FIX(OBJ_ID_INITIAL); | ||
| ... | ... | |
|     /* For now, compact implies full mark / sweep, so ignore other flags */ | ||
|     if (RTEST(compact)) { | ||
|         /* If not MinGW, Windows, or does not have mmap, we cannot use mprotect for | ||
|          * the read barrier, so we must disable compaction. */ | ||
| #if !defined(__MINGW32__) && !defined(_WIN32) | ||
|         if (!USE_MMAP_ALIGNED_ALLOC) { | ||
|             rb_raise(rb_eNotImpError, "Compaction isn't available on this platform"); | ||
|         } | ||
| #endif | ||
|         reason |= GPR_FLAG_COMPACT; | ||
|     } else { | ||
|         if (!RTEST(full_mark))       reason &= ~GPR_FLAG_FULL_MARK; | ||
| ... | ... | |
| static VALUE | ||
| gc_set_auto_compact(rb_execution_context_t *ec, VALUE _, VALUE v) | ||
| { | ||
| #if defined(HAVE_SYSCONF) && defined(_SC_PAGE_SIZE) | ||
|     /* If Ruby's heap pages are not a multiple of the system page size, we | ||
|      * cannot use mprotect for the read barrier, so we must disable automatic | ||
|      * compaction. */ | ||
|     int pagesize; | ||
|     pagesize = (int)sysconf(_SC_PAGE_SIZE); | ||
|     if ((HEAP_PAGE_SIZE % pagesize) != 0) { | ||
|     /* If not MinGW, Windows, or does not have mmap, we cannot use mprotect for | ||
|      * the read barrier, so we must disable automatic compaction. */ | ||
| #if !defined(__MINGW32__) && !defined(_WIN32) | ||
|     if (!USE_MMAP_ALIGNED_ALLOC) { | ||
|         rb_raise(rb_eNotImpError, "Automatic compaction isn't available on this platform"); | ||
|     } | ||
| #endif | ||
|     ruby_enable_autocompact = RTEST(v); | ||
|     return v; | ||
| } | ||
| ... | ... | |
| #elif defined _WIN32 | ||
|     void *_aligned_malloc(size_t, size_t); | ||
|     res = _aligned_malloc(size, alignment); | ||
| #elif defined(HAVE_POSIX_MEMALIGN) | ||
|     if (posix_memalign(&res, alignment, size) == 0) { | ||
|         return res; | ||
| #else | ||
|     if (USE_MMAP_ALIGNED_ALLOC) { | ||
|         GC_ASSERT(alignment % sysconf(_SC_PAGE_SIZE) == 0); | ||
|         char *ptr = mmap(NULL, alignment + size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); | ||
|         if (ptr == MAP_FAILED) { | ||
|             return NULL; | ||
|         } | ||
|         char *aligned = ptr + alignment; | ||
|         aligned -= ((VALUE)aligned & (alignment - 1)); | ||
|         GC_ASSERT(aligned > ptr); | ||
|         GC_ASSERT(aligned <= ptr + alignment); | ||
|         size_t start_out_of_range_size = aligned - ptr; | ||
|         GC_ASSERT(start_out_of_range_size % sysconf(_SC_PAGE_SIZE) == 0); | ||
|         if (start_out_of_range_size > 0) { | ||
|             if (munmap(ptr, start_out_of_range_size)) { | ||
|                 rb_bug("rb_aligned_malloc: munmap failed for start"); | ||
|             } | ||
|         } | ||
|         size_t end_out_of_range_size = alignment - start_out_of_range_size; | ||
|         GC_ASSERT(end_out_of_range_size % sysconf(_SC_PAGE_SIZE) == 0); | ||
|         if (end_out_of_range_size > 0) { | ||
|             if (munmap(aligned + size, end_out_of_range_size)) { | ||
|                 rb_bug("rb_aligned_malloc: munmap failed for end"); | ||
|             } | ||
|         } | ||
|         res = (void *)aligned; | ||
|     } | ||
|     else { | ||
|         return NULL; | ||
| # if defined(HAVE_POSIX_MEMALIGN) | ||
|         if (posix_memalign(&res, alignment, size) != 0) { | ||
|             return NULL; | ||
|         } | ||
| # elif defined(HAVE_MEMALIGN) | ||
|         res = memalign(alignment, size); | ||
| # else | ||
|         char* aligned; | ||
|         res = malloc(alignment + size + sizeof(void*)); | ||
|         aligned = (char*)res + alignment + sizeof(void*); | ||
|         aligned -= ((VALUE)aligned & (alignment - 1)); | ||
|         ((void**)aligned)[-1] = res; | ||
|         res = (void*)aligned; | ||
| # endif | ||
|     } | ||
| #elif defined(HAVE_MEMALIGN) | ||
|     res = memalign(alignment, size); | ||
| #else | ||
|     char* aligned; | ||
|     res = malloc(alignment + size + sizeof(void*)); | ||
|     aligned = (char*)res + alignment + sizeof(void*); | ||
|     aligned -= ((VALUE)aligned & (alignment - 1)); | ||
|     ((void**)aligned)[-1] = res; | ||
|     res = (void*)aligned; | ||
| #endif | ||
|     /* alignment must be a power of 2 */ | ||
| ... | ... | |
| } | ||
| static void | ||
| rb_aligned_free(void *ptr) | ||
| rb_aligned_free(void *ptr, size_t size) | ||
| { | ||
| #if defined __MINGW32__ | ||
|     __mingw_aligned_free(ptr); | ||
| #elif defined _WIN32 | ||
|     _aligned_free(ptr); | ||
| #elif defined(HAVE_MEMALIGN) || defined(HAVE_POSIX_MEMALIGN) | ||
|     free(ptr); | ||
| #else | ||
|     free(((void**)ptr)[-1]); | ||
|     if (USE_MMAP_ALIGNED_ALLOC) { | ||
|         GC_ASSERT(size % sysconf(_SC_PAGE_SIZE) == 0); | ||
|         if (munmap(ptr, size)) { | ||
|             rb_bug("rb_aligned_free: munmap failed"); | ||
|         } | ||
|     } | ||
|     else { | ||
| # if defined(HAVE_POSIX_MEMALIGN) || defined(HAVE_MEMALIGN) | ||
|         free(ptr); | ||
| # else | ||
|         free(((void**)ptr)[-1]); | ||
| # endif | ||
|     } | ||
| #endif | ||
| } | ||
| test/ruby/test_gc_compact.rb | ||
|---|---|---|
| require 'etc' | ||
| class TestGCCompact < Test::Unit::TestCase | ||
|   class AutoCompact < Test::Unit::TestCase | ||
|   module SupportsCompact | ||
|     def setup | ||
|       skip "autocompact not supported on this platform" unless supports_auto_compact? | ||
|       super | ||
|     end | ||
|     private | ||
|     def supports_auto_compact? | ||
|       return true unless defined?(Etc::SC_PAGE_SIZE) | ||
|       begin | ||
|         return GC::INTERNAL_CONSTANTS[:HEAP_PAGE_SIZE] % Etc.sysconf(Etc::SC_PAGE_SIZE) == 0 | ||
|       rescue NotImplementedError | ||
|       rescue ArgumentError | ||
|       end | ||
|       true | ||
|     end | ||
|   end | ||
|   include SupportsCompact | ||
|   class AutoCompact < Test::Unit::TestCase | ||
|     include SupportsCompact | ||
|     def test_enable_autocompact | ||
|       before = GC.auto_compact | ||
|       GC.auto_compact = true | ||
| ... | ... | |
|     ensure | ||
|       GC.auto_compact = before | ||
|     end | ||
|     private | ||
|     def supports_auto_compact? | ||
|       return true unless defined?(Etc::SC_PAGE_SIZE) | ||
|       begin | ||
|         return GC::INTERNAL_CONSTANTS[:HEAP_PAGE_SIZE] % Etc.sysconf(Etc::SC_PAGE_SIZE) == 0 | ||
|       rescue NotImplementedError | ||
|       rescue ArgumentError | ||
|       end | ||
|       true | ||
|     end | ||
|   end | ||
|   def os_page_size | ||
|     return true unless defined?(Etc::SC_PAGE_SIZE) | ||
|   end | ||
|   def setup | ||
|     skip "autocompact not supported on this platform" unless supports_auto_compact? | ||
|     super | ||
|   end | ||
|   def test_gc_compact_stats | ||
|     list = [] | ||