[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