[pypy-commit] pypy gc-forkfriendly-2: in-progress, seems to translate and run simple examples

arigo pypy.commits at gmail.com
Thu Feb 23 12:53:21 EST 2017


Author: Armin Rigo <arigo at tunes.org>
Branch: gc-forkfriendly-2
Changeset: r90329:e63f85f1dc45
Date: 2017-02-23 18:52 +0100
http://bitbucket.org/pypy/pypy/changeset/e63f85f1dc45/

Log:	in-progress, seems to translate and run simple examples

diff --git a/rpython/config/translationoption.py b/rpython/config/translationoption.py
--- a/rpython/config/translationoption.py
+++ b/rpython/config/translationoption.py
@@ -92,6 +92,9 @@
                  }),
     BoolOption("gcremovetypeptr", "Remove the typeptr from every object",
                default=IS_64_BITS, cmdline="--gcremovetypeptr"),
+    BoolOption("gcforkfriendly", "A fork-friendly variant of incminimark",
+               default=False, cmdline="--gcforkfriendly",
+               requires=[("translation.gc", "incminimark")]),
     ChoiceOption("gcrootfinder",
                  "Strategy for finding GC Roots (framework GCs only)",
                  ["n/a", "shadowstack", "asmgcc"],
diff --git a/rpython/memory/gc/incminimark.py b/rpython/memory/gc/incminimark.py
--- a/rpython/memory/gc/incminimark.py
+++ b/rpython/memory/gc/incminimark.py
@@ -288,7 +288,6 @@
                  card_page_indices=0,
                  large_object=8*WORD,
                  ArenaCollectionClass=None,
-                 offline_visited_flags=False,
                  **kwds):
         "NOT_RPYTHON"
         MovingGCBase.__init__(self, config, **kwds)
@@ -315,7 +314,6 @@
         # 'large_object' limit how big objects can be in the nursery, so
         # it gives a lower bound on the allowed size of the nursery.
         self.nonlarge_max = large_object - 1
-        self.offline_visited_flags = offline_visited_flags
         #
         self.nursery      = llmemory.NULL
         self.nursery_free = llmemory.NULL
@@ -325,11 +323,13 @@
         self.extra_threshold = 0
         #
         # The ArenaCollection() handles the nonmovable objects allocation.
+        self.offline_visited_flags = config.gcforkfriendly
         if ArenaCollectionClass is None:
             from rpython.memory.gc import minimarkpage
             ArenaCollectionClass = minimarkpage.ArenaCollection
         self.ac = ArenaCollectionClass(arena_size, page_size,
-                                       small_request_threshold)
+                                       small_request_threshold,
+                                       self.offline_visited_flags)
         #
         # Used by minor collection: a list of (mostly non-young) objects that
         # (may) contain a pointer to a young object.  Populated by
diff --git a/rpython/memory/gc/minimarkpage.py b/rpython/memory/gc/minimarkpage.py
--- a/rpython/memory/gc/minimarkpage.py
+++ b/rpython/memory/gc/minimarkpage.py
@@ -2,7 +2,9 @@
 from rpython.rtyper.lltypesystem import lltype, llmemory, llarena, rffi
 from rpython.rlib.rarithmetic import LONG_BIT, r_uint
 from rpython.rlib.objectmodel import we_are_translated, not_rpython
+from rpython.rlib.objectmodel import always_inline
 from rpython.rlib.debug import ll_assert, fatalerror
+from rpython.translator.tool.cbuild import ExternalCompilationInfo
 
 WORD = LONG_BIT // 8
 NULL = llmemory.NULL
@@ -89,7 +91,16 @@
     _alloc_flavor_ = "raw"
 
     @not_rpython
-    def __init__(self, arena_size, page_size, small_request_threshold):
+    def __init__(self, arena_size, page_size, small_request_threshold,
+                 offline_visited_flags=False):
+        # If we ask for 'offline_visited_flags', then we'll allocate
+        # arenas that are always OFFL_RATIO pages in size, and fully aligned.
+        # In this case, page_size should be 4096 or 8192.  The first page is
+        # used for the offline_visited_flags.  See rpy_allocate_new_arena().
+        if offline_visited_flags:
+            arena_size = OFFL_ARENA_SIZE - OFFL_SYSTEM_PAGE_SIZE
+        self.offline_visited_flags = offline_visited_flags
+        #
         # 'small_request_threshold' is the largest size that we
         # can ask with self.malloc().
         self.arena_size = arena_size
@@ -290,21 +301,30 @@
         if not we_are_translated():
             for a in self._all_arenas():
                 assert a.nfreepages == 0
+
+        if not self.offline_visited_flags:
+            #
+            # 'arena_base' points to the start of malloced memory; it might not
+            # be a page-aligned address
+            arena_base = llarena.arena_malloc(self.arena_size, False)
+            if not arena_base:
+                out_of_memory("out of memory: couldn't allocate the next arena")
+            arena_end = arena_base + self.arena_size
+            #
+            # 'firstpage' points to the first unused page
+            firstpage = start_of_page(arena_base + self.page_size - 1,
+                                      self.page_size)
+        else:
+            assert OFFL_SYSTEM_PAGE_SIZE == llarena.posixpagesize.get()
+            arena_base = rpy_allocate_new_arena()
+            arena_end = arena_base + OFFL_ARENA_SIZE
+            firstpage = arena_base + max(self.page_size, OFFL_SYSTEM_PAGE_SIZE)
         #
-        # 'arena_base' points to the start of malloced memory; it might not
-        # be a page-aligned address
-        arena_base = llarena.arena_malloc(self.arena_size, False)
-        if not arena_base:
-            out_of_memory("out of memory: couldn't allocate the next arena")
+        # 'npages' is the number of full pages just allocated
+        npages = (arena_end - firstpage) // self.page_size
+
         if not we_are_translated():
             arena_base.arena._from_minimarkpage = True
-        arena_end = arena_base + self.arena_size
-        #
-        # 'firstpage' points to the first unused page
-        firstpage = start_of_page(arena_base + self.page_size - 1,
-                                  self.page_size)
-        # 'npages' is the number of full pages just allocated
-        npages = (arena_end - firstpage) // self.page_size
         #
         # Allocate an ARENA object and initialize it
         arena = lltype.malloc(ARENA, flavor='raw', track_allocation=False)
@@ -399,7 +419,10 @@
                     #
                     # The whole arena is empty.  Free it.
                     llarena.arena_reset(arena.base, self.arena_size, 4)
-                    llarena.arena_free(arena.base)
+                    if not self.offline_visited_flags:
+                        llarena.arena_free(arena.base)
+                    else:
+                        rpy_free_arena(arena.base)
                     lltype.free(arena, flavor='raw', track_allocation=False)
                     #
                 else:
@@ -510,6 +533,9 @@
         surviving = 0    # initially
         skip_free_blocks = page.nfree
         #
+        if self.offline_visited_flags:
+            ok_to_free_func = self.get_unvisited
+        #
         while True:
             #
             if obj == freeblock:
@@ -555,6 +581,9 @@
         # Update the global total size of objects.
         self.total_memory_used += r_uint(surviving * block_size)
         #
+        if self.offline_visited_flags:
+            self.reset_block_of_visited_flags_for_one_page(page)
+        #
         # Return the number of surviving objects.
         return surviving
 
@@ -584,6 +613,50 @@
         return getattr(arena, '_from_minimarkpage', False)
 
 
+    @staticmethod
+    @always_inline
+    def get_visited(obj):
+        numeric = rffi.cast(lltype.Unsigned, obj)
+        base = rffi.cast(rffi.CCHARP, numeric & ~(OFFL_ARENA_SIZE - 1))
+        ofs = (numeric // OFFL_RATIO) & (OFFL_SYSTEM_PAGE_SIZE - 1)
+        singlebit = 1 << ((numeric // (OFFL_RATIO/8)) & 7)
+        return (ord(base[ofs]) & singlebit) != 0
+
+    @staticmethod
+    @always_inline
+    def set_visited(obj):
+        numeric = rffi.cast(lltype.Unsigned, obj)
+        base = rffi.cast(rffi.CCHARP, numeric & ~(OFFL_ARENA_SIZE - 1))
+        ofs = (numeric // OFFL_RATIO) & (OFFL_SYSTEM_PAGE_SIZE - 1)
+        singlebit = 1 << ((numeric // (OFFL_RATIO/8)) & 7)
+        base[ofs] = chr(ord(base[ofs]) | singlebit)
+
+    @staticmethod
+    @always_inline
+    def clear_visited(obj):
+        numeric = rffi.cast(lltype.Unsigned, obj)
+        base = rffi.cast(rffi.CCHARP, numeric & ~(OFFL_ARENA_SIZE - 1))
+        ofs = (numeric // OFFL_RATIO) & (OFFL_SYSTEM_PAGE_SIZE - 1)
+        singlebit = 1 << ((numeric // (OFFL_RATIO/8)) & 7)
+        base[ofs] = chr(ord(base[ofs]) & ~singlebit)
+
+    @staticmethod
+    @always_inline
+    def get_unvisited(obj):
+        numeric = rffi.cast(lltype.Unsigned, obj)
+        base = rffi.cast(rffi.CCHARP, numeric & ~(OFFL_ARENA_SIZE - 1))
+        ofs = (numeric // OFFL_RATIO) & (OFFL_SYSTEM_PAGE_SIZE - 1)
+        singlebit = 1 << ((numeric // (OFFL_RATIO/8)) & 7)
+        return (ord(base[ofs]) & singlebit) == 0
+
+    @staticmethod
+    def reset_block_of_visited_flags_for_one_page(page):
+        numeric = rffi.cast(lltype.Unsigned, page)
+        base = rffi.cast(rffi.CCHARP, numeric & ~(OFFL_ARENA_SIZE - 1))
+        ofs = (numeric // OFFL_RATIO) & (OFFL_SYSTEM_PAGE_SIZE - 1)
+        rpy_memset(base, 0, OFFL_SYSTEM_PAGE_SIZE // OFFL_RATIO)
+
+
 # ____________________________________________________________
 # Helpers to go from a pointer to the start of its page
 
@@ -615,3 +688,95 @@
     exception gracefully.
     """
     fatalerror(errmsg)
+
+
+# ____________________________________________________________
+# Helpers for the 'offline visited flags' mode
+
+
+# xxx make the number 4096 not hard-coded, but it has implications on
+# the other numbers too
+OFFL_SYSTEM_PAGE_SIZE = 4096
+OFFL_RATIO = WORD * 2 * 8
+OFFL_ARENA_SIZE = OFFL_SYSTEM_PAGE_SIZE * OFFL_RATIO
+
+# Idea: mmap N bytes, where N is just smaller than '2 * arena_size'.
+# This is just the right size to ensure that the allocated region
+# contains exactly one block of 'arena_size' pages that is fully
+# aligned to an address multiple of 'arena_size'.  Then we munmap
+# the extra bits at both ends.  This approach should ensure that
+# after the first arena was allocated, the next one is likely to be
+# placed by the system either just before or just after it; when
+# it is the case, the side that connects with the previous arena
+# is aligned already, and so we only have to remove the extra bit
+# at the other end.  This should ensure that our aligned arenas
+# grow next to each other (and in a single VMA, in Linux terms).
+
+# The number OFFL_RATIO comes from the fact that we can use one
+# visited bit for every two words of memory.  Most objects are at
+# least two words in length.  If we have a page that contains
+# single-word objects, then the visited bits clash: the same bit
+# is used for two objects.  However, that's a very minor problem,
+# because the objects are too small to contain further references
+# anyway, so at most we leak one of the two objects when the other
+# is still in use (i.e. at most one word of memory per word-sized
+# object alive).  So, OFFL_RATIO is equal to 64 on 32-bit and 128
+# on 64-bit machines.  That's also equal to the number of pages in
+# one arena: we reserve the first page for visited bits, and all
+# remaining pages are used for real objects.
+
+eci = ExternalCompilationInfo(
+    post_include_bits=[
+        'RPY_EXTERN void *rpy_allocate_new_arena(void);',
+        'RPY_EXTERN void rpy_free_arena(void *);',
+        'RPY_EXTERN char rpy_get_visited(void *);',
+        'RPY_EXTERN void rpy_set_visited(void *);',
+        'RPY_EXTERN void rpy_clear_visited(void *);',
+        ],
+    separate_module_sources=['''
+#include <stdlib.h>
+#include <sys/mman.h>
+
+#define OFFL_SYSTEM_PAGE_SIZE  %(OFFL_SYSTEM_PAGE_SIZE)d
+#define OFFL_RATIO             %(OFFL_RATIO)d
+#define OFFL_ARENA_SIZE        %(OFFL_ARENA_SIZE)d
+
+RPY_EXTERN void *rpy_allocate_new_arena(void)
+{
+    size_t arena_size = OFFL_ARENA_SIZE;
+    size_t map_size = arena_size * 2 - OFFL_SYSTEM_PAGE_SIZE;
+    void *p = mmap(NULL, map_size, PROT_READ | PROT_WRITE,
+                   MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+    if (p == MAP_FAILED) {
+        perror("Fatal RPython error: mmap");
+        abort();
+    }
+
+    char *result = (char *)((((long)p) + arena_size - 1) & ~(arena_size-1));
+    if (result > (char *)p)
+        munmap(p, result - (char *)p);
+    long free_end = ((char *)p + map_size) - (result + arena_size);
+    if (free_end > 0)
+        munmap(result + arena_size, free_end);
+
+    /* 'result' is freshly mmap()ed so it contains zeroes at this point */
+    return result;
+}
+
+RPY_EXTERN void rpy_free_arena(void *base)
+{
+    munmap(base, OFFL_ARENA_SIZE);
+}
+''' % globals()])
+
+rpy_allocate_new_arena = rffi.llexternal(
+    'rpy_allocate_new_arena', [], llmemory.Address,
+    compilation_info=eci, _nowrapper=True)
+
+rpy_free_arena = rffi.llexternal(
+    'rpy_free_arena', [llmemory.Address], lltype.Void,
+    compilation_info=eci, _nowrapper=True)
+
+rpy_memset = rffi.llexternal(
+    'memset', [rffi.CCHARP, lltype.Signed, lltype.Signed], lltype.Void,
+    _nowrapper=True)
diff --git a/rpython/translator/c/test/test_newgc.py b/rpython/translator/c/test/test_newgc.py
--- a/rpython/translator/c/test/test_newgc.py
+++ b/rpython/translator/c/test/test_newgc.py
@@ -25,6 +25,7 @@
     taggedpointers = False
     GC_CAN_MOVE = False
     GC_CAN_SHRINK_ARRAY = False
+    gcforkfriendly = False
 
     _isolated_func = None
     c_allfuncs = None
@@ -44,7 +45,8 @@
 
         t = Translation(main, gc=cls.gcpolicy,
                         taggedpointers=cls.taggedpointers,
-                        gcremovetypeptr=cls.removetypeptr)
+                        gcremovetypeptr=cls.removetypeptr,
+                        gcforkfriendly=cls.gcforkfriendly)
         t.disable(['backendopt'])
         t.set_backend_extra_options(c_debug_defines=True)
         t.rtype()
@@ -1667,6 +1669,7 @@
 
 class TestIncrementalMiniMarkGC(TestMiniMarkGC):
     gcpolicy = "incminimark"
+    gcforkfriendly = True
 
     def define_random_pin(self):
         class A:


More information about the pypy-commit mailing list