[pypy-commit] pypy concurrent-marksweep: Starting to write the "3/4th concurrent" generational mark&sweep GC.

arigo noreply at buildbot.pypy.org
Sat Oct 22 16:38:42 CEST 2011


Author: Armin Rigo <arigo at tunes.org>
Branch: concurrent-marksweep
Changeset: r48341:1739e282bf89
Date: 2011-10-22 16:38 +0200
http://bitbucket.org/pypy/pypy/changeset/1739e282bf89/

Log:	Starting to write the "3/4th concurrent" generational mark&sweep GC.

diff --git a/pypy/rpython/memory/gc/concurrentgen.py b/pypy/rpython/memory/gc/concurrentgen.py
--- a/pypy/rpython/memory/gc/concurrentgen.py
+++ b/pypy/rpython/memory/gc/concurrentgen.py
@@ -1,112 +1,1240 @@
-"""
-************************************************************
-  Minor collection cycles of the "concurrentgen" collector
-************************************************************
+import time, sys
+from pypy.rpython.lltypesystem import lltype, llmemory, llarena, llgroup, rffi
+from pypy.rpython.lltypesystem.llmemory import raw_malloc_usage
+from pypy.rpython.lltypesystem.lloperation import llop
+from pypy.rpython.annlowlevel import llhelper
+from pypy.translator.tool.cbuild import ExternalCompilationInfo
+from pypy.rlib.objectmodel import we_are_translated, running_on_llinterp
+from pypy.rlib.debug import ll_assert, debug_print, debug_start, debug_stop
+from pypy.rlib.rarithmetic import ovfcheck, LONG_BIT, r_uint
+from pypy.rpython.memory.gc.base import GCBase
+from pypy.rpython.memory import gctypelayout
+from pypy.module.thread import ll_thread
 
+#
+# A "3/4th concurrent" generational mark&sweep GC.
+#
+# This uses a separate thread to run the minor collections in parallel,
+# as well as half of the major collections (the sweep phase).  The mark
+# phase is not parallelized.  See concurrentgen.txt for some details.
+#
+# Based on observations that the timing of collections with "minimark"
+# (on translate.py) is: about 15% of the time in minor collections
+# (including 2% in walk_roots), and about 7% in major collections (with
+# probably 3-4% in the marking phase).  So out of a total of 22% this
+# should parallelize 16-17%, i.e. 3/4th.
+#
+# This is an entirely non-moving collector, with a generational write
+# barrier adapted to the concurrent marking done by the collector thread.
+#
 
-Objects mark byte:
+WORD = LONG_BIT // 8
+WORD_POWER_2 = {32: 2, 64: 3}[LONG_BIT]
+assert 1 << WORD_POWER_2 == WORD
+MAXIMUM_SIZE = sys.maxint - (3*WORD-1)
 
-    cym in 'mK': young objs (and all flagged objs)
-    cam in 'Km': aging objs
-    '#'        : old objs
-    'S'        : static prebuilt objs with no heap pointer
 
-'cym' is the current young marker
-'cam' is the current aging marker
+# Objects start with an integer 'tid', which is decomposed as follows.
+# Lowest byte: one of the following values (which are all odd, so
+# let us know if the 'tid' is valid or is just a word-aligned address):
+MARK_BYTE_1       = 0x6D    # 'm', 109
+MARK_BYTE_2       = 0x4B    # 'K', 75
+MARK_BYTE_OLD     = 0x23    # '#', 35
+MARK_BYTE_STATIC  = 0x53    # 'S', 83
+# Next lower byte: a combination of flags.
+FL_WITHHASH       = 0x0100
+FL_EXTRA          = 0x0200
+# And the high half of the word contains the numeric typeid.
 
-The write barrier activates when writing into an object whose
-mark byte is different from 'cym'.
 
+class ConcurrentGenGC(GCBase):
+    _alloc_flavor_ = "raw"
+    inline_simple_malloc = True
+    inline_simple_malloc_varsize = True
+    needs_deletion_barrier = True
+    needs_weakref_read_barrier = True
+    prebuilt_gc_objects_are_static_roots = False
+    malloc_zero_filled = True
+    gcflag_extra = FL_EXTRA
 
-------------------------------------------------------------
+    HDR = lltype.Struct('header', ('tid', lltype.Signed))
+    HDRPTR = lltype.Ptr(HDR)
+    HDRSIZE = llmemory.sizeof(HDR)
+    NULL = lltype.nullptr(HDR)
+    typeid_is_in_field = 'tid', llgroup.HALFSHIFT
+    withhash_flag_is_in_field = 'tid', FL_WITHHASH
+    # ^^^ prebuilt objects may have the flag FL_WITHHASH;
+    #     then they are one word longer, the extra word storing the hash.
 
-Step 1.  Only the mutator runs.
+    TRANSLATION_PARAMS = {'page_size': 16384,
+                          'small_request_threshold': 35*WORD,
+                          }
 
-   old obj    flagged obj     old obj
-                    |
-                    |
-                    v
-                young obj...
+    def __init__(self, config, page_size=64, small_request_threshold=24,
+                 **kwds):
+        # 'small_request_threshold' is the largest size that we will
+        # satisfy using our own pages mecanism.  Larger requests just
+        # go to the system malloc().
+        self.addressstack_lock_object = SyncLock()
+        kwds['lock'] = self.addressstack_lock_object
+        GCBase.__init__(self, config, **kwds)
+        assert small_request_threshold % WORD == 0
+        self.small_request_threshold = small_request_threshold
+        self.page_size = page_size
+        self.pagelists_length = small_request_threshold // WORD + 1
+        #
+        # The following are arrays of 36 linked lists: the linked lists
+        # at indices 1 to 35 correspond to pages that store objects of
+        # size  1 * WORD  to  35 * WORD,  and the linked list at index 0
+        # is a list of all larger objects.
+        def list_of_addresses_per_small_size():
+            return lltype.malloc(rffi.CArray(self.HDRPTR),
+                                 self.pagelists_length, flavor='raw',
+                                 immortal=True)
+        # 1-35: a linked list of all pages; 0: a linked list of all larger objs
+        self.nonfree_pages = list_of_addresses_per_small_size()
+        # a snapshot of 'nonfree_pages' done when the collection starts
+        self.collect_pages = list_of_addresses_per_small_size()
+        # 1-35: free list of non-allocated locations; 0: unused
+        self.free_lists    = list_of_addresses_per_small_size()
+        # 1-35: head and tail of the free list built by the collector thread
+        # 0: head and tail of the linked list of surviving large objects
+        self.collect_heads = list_of_addresses_per_small_size()
+        self.collect_tails = list_of_addresses_per_small_size()
+        #
+        def collector_start():
+            if we_are_translated():
+                self.collector_run()
+            else:
+                self.collector_run_nontranslated()
+        #
+        collector_start._should_never_raise_ = True
+        self.collector_start = collector_start
+        #
+        self.gray_objects = self.AddressStack()
+        self.extra_objects_to_mark = self.AddressStack()
+        self.flagged_objects = self.AddressStack()
+        self.prebuilt_root_objects = self.AddressStack()
+        #
+        self._initialize()
+        #
+        # Write barrier: actually a deletion barrier, triggered when there
+        # is a collection running and the mutator tries to change an object
+        # that was not scanned yet.
+        self._init_writebarrier_logic()
 
-Write barrier: change "old obj" to "flagged obj"
-    (if mark != cym:
-         mark = cym (used to be '#' or 'S')
-         record the object in the "flagged" list)
-    - note that we consider that flagged old objs are again young objects
+    def _clear_list(self, array):
+        i = 0
+        while i < self.pagelists_length:
+            array[i] = self.NULL
+            i += 1
 
-------------------------------------------------------------
+    def _initialize(self):
+        self.free_pages = self.NULL
+        #
+        # Clear the lists
+        self._clear_list(self.nonfree_pages)
+        self._clear_list(self.collect_pages)
+        self._clear_list(self.free_lists)
+        self._clear_list(self.collect_heads)
+        self._clear_list(self.collect_tails)
+        #
+        self.finalizer_pages = self.NULL
+        self.collect_finalizer_pages = self.NULL
+        self.collect_finalizer_tails = self.NULL
+        self.collect_run_finalizers_head = self.NULL
+        self.collect_run_finalizers_tail = self.NULL
+        self.objects_with_finalizers_to_run = self.NULL
+        #
+        self.weakref_pages = self.NULL
+        self.collect_weakref_pages = self.NULL
+        self.collect_weakref_tails = self.NULL
+        #
+        # See concurrentgen.txt for more information about these fields.
+        self.current_young_marker = MARK_BYTE_1
+        self.current_aging_marker = MARK_BYTE_2
+        #
+        # When the mutator thread wants to trigger the next collection,
+        # it scans its own stack roots and prepares everything, then
+        # sets 'collection_running' to 1, and releases
+        # 'ready_to_start_lock'.  This triggers the collector thread,
+        # which re-acquires 'ready_to_start_lock' and does its job.
+        # When done it releases 'finished_lock'.  The mutator thread is
+        # responsible for resetting 'collection_running' to 0.
+        #
+        # The collector thread's state can be found (with careful locking)
+        # by inspecting the same variable from the mutator thread:
+        #   * collection_running == 1: Marking.  [Deletion barrier active.]
+        #   * collection_running == 2: Clearing weakrefs.
+        #   * collection_running == 3: Marking from unreachable finalizers.
+        #   * collection_running == 4: Sweeping.
+        #   * collection_running == -1: Done.
+        # The mutex_lock is acquired to go from 1 to 2, and from 2 to 3.
+        self.collection_running = 0
+        #self.ready_to_start_lock = ...built in setup()
+        #self.finished_lock = ...built in setup()
+        #
+        #self.mutex_lock = ...built in setup()
+        self.gray_objects.clear()
+        self.extra_objects_to_mark.clear()
+        self.flagged_objects.clear()
+        self.prebuilt_root_objects.clear()
 
-Step 2.  Preparation of running the collector.  (Still single-threaded.)
+    def setup(self):
+        "Start the concurrent collector thread."
+        # don't call GCBase.setup(self), because we don't need
+        # 'run_finalizers' as a deque
+        self.finalizer_lock_count = 0
+        #
+        self.main_thread_ident = ll_thread.get_ident()
+        self.ready_to_start_lock = ll_thread.allocate_ll_lock()
+        self.finished_lock = ll_thread.allocate_ll_lock()
+        self.mutex_lock = ll_thread.allocate_ll_lock()
+        self.addressstack_lock_object.setup()
+        #
+        self.acquire(self.finished_lock)
+        self.acquire(self.ready_to_start_lock)
+        #
+        self.collector_ident = ll_thread.c_thread_start_nowrapper(
+            llhelper(ll_thread.CALLBACK, self.collector_start))
+        assert self.collector_ident != -1
 
-   - young objs -> aging objs
-         (exchange the values of 'cam' and 'cym'.
-          there was no 'cam' object, so now there is no 'cym' object)
+    def _teardown(self):
+        "Stop the collector thread after tests have run."
+        self.wait_for_the_end_of_collection()
+        #
+        # start the next collection, but with collection_running set to 42,
+        # which should shut down the collector thread
+        self.collection_running = 42
+        debug_print("teardown!")
+        self.release(self.ready_to_start_lock)
+        self.acquire(self.finished_lock)
+        self._initialize()
 
-   - collect roots; add roots and flagged objs to the "gray objs" list
+    def get_type_id(self, obj):
+        tid = self.header(obj).tid
+        return llop.extract_high_ushort(llgroup.HALFWORD, tid)
 
-   - unflag objs (i.e. empty the "flagged" list)
+    def combine(self, typeid16, mark, flags):
+        return llop.combine_high_ushort(lltype.Signed, typeid16, mark | flags)
 
-------------------------------------------------------------
+    def init_gc_object_immortal(self, addr, typeid, flags=0):
+        # 'flags' is ignored here
+        hdr = llmemory.cast_adr_to_ptr(addr, lltype.Ptr(self.HDR))
+        hdr.tid = self.combine(typeid, MARK_BYTE_STATIC, 0)
 
-Step 3.  Parallel execution of the collector, mark phase
+    def malloc_fixedsize_clear(self, typeid, size,
+                               needs_finalizer=False, contains_weakptr=False):
+        # contains_weakptr: detected during collection
+        #
+        # Case of finalizers (test constant-folded)
+        if needs_finalizer:
+            ll_assert(not contains_weakptr,
+                     "'needs_finalizer' and 'contains_weakptr' both specified")
+            return self._malloc_with_finalizer(typeid, size)
+        #
+        # Case of weakreferences (test constant-folded)
+        if contains_weakptr:
+            return self._malloc_weakref(typeid, size)
+        #
+        # Regular case
+        size_gc_header = self.gcheaderbuilder.size_gc_header
+        totalsize = size_gc_header + size
+        rawtotalsize = raw_malloc_usage(totalsize)
+        if rawtotalsize <= self.small_request_threshold:
+            ll_assert(rawtotalsize & (WORD - 1) == 0,
+                      "fixedsize not properly rounded")
+            #
+            n = rawtotalsize >> WORD_POWER_2
+            result = self.free_lists[n]
+            if result != self.NULL:
+                self.free_lists[n] = list_next(result)
+                obj = self.grow_reservation(result, totalsize)
+                hdr = self.header(obj)
+                hdr.tid = self.combine(typeid, self.current_young_marker, 0)
+                #debug_print("malloc_fixedsize_clear", obj)
+                return llmemory.cast_adr_to_ptr(obj, llmemory.GCREF)
+                #
+        return self._malloc_slowpath(typeid, size)
 
-   old obj    old obj     old obj
+    def malloc_varsize_clear(self, typeid, length, size, itemsize,
+                             offset_to_length):
+        size_gc_header = self.gcheaderbuilder.size_gc_header
+        nonvarsize = size_gc_header + size
+        #
+        # Compute the maximal length that makes the object still below
+        # 'small_request_threshold'.  All the following logic is usually
+        # constant-folded because size and itemsize are constants (due
+        # to inlining).
+        maxsize = self.small_request_threshold - raw_malloc_usage(nonvarsize)
+        if maxsize < 0:
+            toobig = r_uint(0)    # the nonvarsize alone is too big
+        elif raw_malloc_usage(itemsize):
+            toobig = r_uint(maxsize // raw_malloc_usage(itemsize)) + 1
+        else:
+            toobig = r_uint(sys.maxint) + 1
 
-         aging obj   aging obj
+        if r_uint(length) < r_uint(toobig):
+            # With the above checks we know now that totalsize cannot be more
+            # than 'small_request_threshold'; in particular, the + and *
+            # cannot overflow.
+            totalsize = nonvarsize + itemsize * length
+            totalsize = llarena.round_up_for_allocation(totalsize)
+            rawtotalsize = raw_malloc_usage(totalsize)
+            ll_assert(rawtotalsize & (WORD - 1) == 0,
+                      "round_up_for_allocation failed")
+            #
+            n = rawtotalsize >> WORD_POWER_2
+            result = self.free_lists[n]
+            if result != self.NULL:
+                self.free_lists[n] = list_next(result)
+                obj = self.grow_reservation(result, totalsize)
+                hdr = self.header(obj)
+                hdr.tid = self.combine(typeid, self.current_young_marker, 0)
+                (obj + offset_to_length).signed[0] = length
+                #debug_print("malloc_varsize_clear", obj)
+                return llmemory.cast_adr_to_ptr(obj, llmemory.GCREF)
+        #
+        # If the total size of the object would be larger than
+        # 'small_request_threshold', or if the free_list is empty,
+        # then allocate it externally.  We also go there if 'length'
+        # is actually negative.
+        return self._malloc_varsize_slowpath(typeid, length)
 
-   new young obj...
+    def _malloc_slowpath(self, typeid, size):
+        # Slow-path malloc.  Call this with 'size' being a valid and
+        # rounded number, between WORD and up to MAXIMUM_SIZE.
+        #
+        # For now, we always start the next collection immediately.
+        if self.collection_running <= 0:
+            self.trigger_next_collection()
+        #
+        size_gc_header = self.gcheaderbuilder.size_gc_header
+        totalsize = size_gc_header + size
+        rawtotalsize = raw_malloc_usage(totalsize)
+        #
+        if rawtotalsize <= self.small_request_threshold:
+            #
+            # Case 1: unless trigger_next_collection() happened to get us
+            # more locations in free_lists[n], we have run out of them
+            ll_assert(rawtotalsize & (WORD - 1) == 0,
+                      "malloc_slowpath: non-rounded size")
+            n = rawtotalsize >> WORD_POWER_2
+            head = self.free_lists[n]
+            if head:
+                self.free_lists[n] = list_next(head)
+                obj = self.grow_reservation(head, totalsize)
+                hdr = self.header(obj)
+                hdr.tid = self.combine(typeid, self.current_young_marker, 0)
+                return llmemory.cast_adr_to_ptr(obj, llmemory.GCREF)
+            #
+            # We really have run out of the free list corresponding to
+            # the size.  Grab the next free page.
+            newpage = self.free_pages
+            if newpage == self.NULL:
+                self.allocate_next_arena()
+                newpage = self.free_pages
+            self.free_pages = list_next(newpage)
+            #
+            # Put the free page in the list 'nonfree_pages[n]'.  This is
+            # a linked list chained through the first word of each page.
+            set_next(newpage, self.nonfree_pages[n])
+            self.nonfree_pages[n] = newpage
+            #
+            # Initialize the free page to contain objects of the given
+            # size.  This requires setting up all object locations in the
+            # page, linking them in the free list.
+            i = self.page_size - rawtotalsize
+            limit = rawtotalsize + raw_malloc_usage(self.HDRSIZE)
+            newpageadr = llmemory.cast_ptr_to_adr(newpage)
+            newpageadr = llarena.getfakearenaaddress(newpageadr)
+            while i >= limit:
+                adr = newpageadr + i
+                llarena.arena_reserve(adr, self.HDRSIZE)
+                p = llmemory.cast_adr_to_ptr(adr, self.HDRPTR)
+                set_next(p, head)
+                head = p
+                i -= rawtotalsize
+            self.free_lists[n] = head
+            result = newpageadr + i
+            #
+            # Done: all object locations are linked, apart from
+            # 'result', which is the first object location in the page.
+            # Note that if the size is not an exact divisor of
+            # 4096-WORD, there are a few wasted WORDs, which we place at
+            # the start of the page rather than at the end (Hans Boehm,
+            # xxx ref).
+            #
+            return self._malloc_result(typeid, totalsize, result)
+        else:
+            # Case 2: the object is too large, so allocate it directly
+            # with the system malloc().
+            return self._malloc_large_object(typeid, size, 0)
+        #
+    _malloc_slowpath._dont_inline_ = True
 
+    def _malloc_result(self, typeid, totalsize, result):
+        llarena.arena_reserve(result, totalsize)
+        hdr = llmemory.cast_adr_to_ptr(result, self.HDRPTR)
+        hdr.tid = self.combine(typeid, self.current_young_marker, 0)
+        obj = result + self.gcheaderbuilder.size_gc_header
+        #debug_print("malloc_slowpath", obj)
+        return llmemory.cast_adr_to_ptr(obj, llmemory.GCREF)
 
-Collector thread:
+    def _malloc_large_object(self, typeid, size, linked_list):
+        # xxx on 32-bit, we'll prefer 64-bit alignment of the object by
+        # always allocating an 8-bytes header
+        totalsize = self.gcheaderbuilder.size_gc_header + size
+        rawtotalsize = raw_malloc_usage(totalsize)
+        rawtotalsize += 8
+        block = llarena.arena_malloc(rawtotalsize, 2)
+        if not block:
+            raise MemoryError
+        llarena.arena_reserve(block, self.HDRSIZE)
+        blockhdr = llmemory.cast_adr_to_ptr(block, self.HDRPTR)
+        if linked_list == 0:
+            set_next(blockhdr, self.nonfree_pages[0])
+            self.nonfree_pages[0] = blockhdr
+        elif linked_list == 1:
+            set_next(blockhdr, self.finalizer_pages)
+            self.finalizer_pages = blockhdr
+        elif linked_list == 2:
+            set_next(blockhdr, self.weakref_pages)
+            self.weakref_pages = blockhdr
+        else:
+            ll_assert(0, "bad linked_list")
+        return self._malloc_result(typeid, totalsize, block + 8)
+    _malloc_large_object._annspecialcase_ = 'specialize:arg(3)'
+    _malloc_large_object._dont_inline_ = True
 
-    for each gray obj:
-        skip obj if not an aging obj    (i.e. if mark != cam: continue)
-        for each obj found by tracing:
-            add to gray objs      (if not an aging obj, will be skipped later)
-        gray obj -> black obj     (i.e. mark = '#')
+    def _malloc_varsize_slowpath(self, typeid, length):
+        #
+        if length < 0:
+            # negative length!  This likely comes from an overflow
+            # earlier.  We will just raise MemoryError here.
+            raise MemoryError
+        #
+        # Compute the total size, carefully checking for overflows.
+        nonvarsize = self.fixed_size(typeid)
+        itemsize = self.varsize_item_sizes(typeid)
+        try:
+            varsize = ovfcheck(itemsize * length)
+            totalsize = ovfcheck(nonvarsize + varsize)
+        except OverflowError:
+            raise MemoryError
+        #
+        # Detect very rare cases of overflows
+        if raw_malloc_usage(totalsize) > MAXIMUM_SIZE:
+            raise MemoryError("rare case of overflow")
+        #
+        totalsize = llarena.round_up_for_allocation(totalsize)
+        result = self._malloc_slowpath(typeid, totalsize)
+        #
+        offset_to_length = self.varsize_offset_to_length(typeid)
+        obj = llmemory.cast_ptr_to_adr(result)
+        (obj + offset_to_length).signed[0] = length
+        return result
+    _malloc_varsize_slowpath._dont_inline_ = True
 
-Write barrier:
+    def _malloc_with_finalizer(self, typeid, size):
+        return self._malloc_large_object(typeid, size, 1)
 
-   - perform as a "deletion barrier", detecting changes done to aging objs
-        (i.e. if mark == cam,
-                  mark = '#'
-                  trace and add to gray objs)
-   - also flag old-or-aging objs that point to new young objs
-        (if mark != cym:
-             mark = cym (used to be '#' or 'S')
-             record the object in the "flagged" list)
+    def _malloc_weakref(self, typeid, size):
+        return self._malloc_large_object(typeid, size, 2)
 
-Threading issues:
+    # ----------
+    # Other functions in the GC API
 
-   - it's possible that both threads will trace the same object, if we're
-     unlucky, but it does not have buggy effects
-   - the "mark = '#'" in the collector thread can conflict with the
-     "mark = cym" in the mutator write barrier, but again, it should not
-     have buggy effects beyond occasionally triggering the write barrier
-     twice on the same object, adding it twice in "flagged" (and never more)
-   - it is essential to have "mark = '#'" _after_ tracing in the collector
-     thread; otherwise, the write barrier in the mutator thread would be
-     ignored in case it occurs between the two, and then the tracing done
-     by the collector thread doesn't see the original values any more.
-   - the detection of "we are done" in the collector thread needs to
-     account for the write barrier currently tracing and adding more
-     objects to "gray objs".
+    #def set_max_heap_size(self, size):
+    #    XXX
 
-------------------------------------------------------------
+    #def raw_malloc_memory_pressure(self, sizehint):
+    #    XXX
 
-Step 4.  Parallel execution of the collector, sweep phase
+    #def shrink_array(self, obj, smallerlength):
+    #    no real point in supporting this, but if you think it's a good
+    #    idea, remember that changing the array length at run-time needs
+    #    extra care for the collector thread
 
-    for obj in previous nursery:
-        if obj is "black":     (i.e. if mark != cam)
-            make the obj old   (         nothing to do here, mark already ok)
+    def enumerate_all_roots(self, callback, arg):
+        self.flagged_objects.foreach(callback, arg)
+        GCBase.enumerate_all_roots(self, callback, arg)
+    enumerate_all_roots._annspecialcase_ = 'specialize:arg(1)'
+
+    def identityhash(self, obj):
+        obj = llmemory.cast_ptr_to_adr(obj)
+        if self.header(obj).tid & FL_WITHHASH:
+            obj += self.get_size(obj)
+            return obj.signed[0]
         else:
-            clear the object space and return it to the available list
-    after this there are no more aging objects
+            return llmemory.cast_adr_to_int(obj)
 
-Write barrier:
+    # ----------
 
-   - flag old objs that point to new young objs
-        (should not see any 'cam' object any more here)
+    def allocate_next_arena(self):
+        # xxx for now, allocate one page at a time with the system malloc()
+        page = llarena.arena_malloc(self.page_size, 2)     # zero-filled
+        if not page:
+            raise MemoryError
+        llarena.arena_reserve(page, self.HDRSIZE)
+        page = llmemory.cast_adr_to_ptr(page, self.HDRPTR)
+        page.tid = 0
+        self.free_pages = page
 
-------------------------------------------------------------
-"""
+    def grow_reservation(self, hdr, totalsize):
+        # Transform 'hdr', which used to point to just a HDR,
+        # into a pointer to a full object of size 'totalsize'.
+        # This is a no-op after translation.  Returns the
+        # address of the full object.
+        adr = llmemory.cast_ptr_to_adr(hdr)
+        adr = llarena.getfakearenaaddress(adr)
+        llarena.arena_reserve(adr, totalsize)
+        return adr + self.gcheaderbuilder.size_gc_header
+    grow_reservation._always_inline_ = True
+
+    def deletion_barrier(self, addr_struct):
+        # XXX check the assembler
+        mark = self.header(addr_struct).tid & 0xFF
+        if mark != self.current_young_marker:
+            self.force_scan(addr_struct)
+        #else:
+        #    debug_print("deletion_barrier (off)", addr_struct)
+
+    def assume_young_pointers(self, addr_struct):
+        pass # XXX
+
+    def _init_writebarrier_logic(self):
+        #
+        def force_scan(obj):
+            #debug_print("deletion_barrier  ON  ", obj)
+            cym = self.current_young_marker
+            mark = self.get_mark(obj)
+            #
+            if mark == MARK_BYTE_OLD:
+                #
+                self.set_mark(obj, cym)
+                #
+            elif mark == MARK_BYTE_STATIC:
+                # This is the first write into a prebuilt GC object.
+                # Record it in 'prebuilt_root_objects'.
+                self.set_mark(obj, cym)
+                self.prebuilt_root_objects.append(obj)
+                #
+            else:
+                #
+                # Only acquire the mutex_lock if necessary
+                self.acquire(self.mutex_lock)
+                #
+                # Reload the possibly changed marker from the object header,
+                # and set it to 'cym'
+                mark = self.get_mark(obj)
+                self.set_mark(obj, cym)
+                #
+                if mark == self.current_aging_marker:
+                    #
+                    # it is only possible to reach this point if there is
+                    # a collection running in collector_mark(), before it
+                    # does mutex_lock itself.  Check this:
+                    ll_assert(self.collection_running == 1,
+                              "write barrier: wrong call?")
+                    #
+                    # It's fine to set the mark before tracing, because
+                    # we are anyway in a 'mutex_lock' critical section.
+                    # The collector thread will not exit from the phase
+                    # 'collection_running == 1' here.
+                    self.trace(obj, self._barrier_add_extra, None)
+                    #
+                    # Still at 1:
+                    ll_assert(self.collection_running == 1,
+                              "write barrier: oups!?")
+                    #
+                else:
+                    # MARK_BYTE_OLD is possible here: the collector thread
+                    # sets it in parallel to objects.  In that case it has
+                    # been handled already.
+                    ll_assert(mark == MARK_BYTE_OLD,
+                              "write barrier: bogus object mark")
+                #
+                self.release(self.mutex_lock)
+            #
+            # In all cases, the object is now flagged
+            self.flagged_objects.append(obj)
+        #
+        force_scan._dont_inline_ = True
+        self.force_scan = force_scan
+
+    def _barrier_add_extra(self, root, ignored):
+        obj = root.address[0]
+        self.get_mark(obj)
+        self.extra_objects_to_mark.append(obj)
+
+
+    def wait_for_the_end_of_collection(self):
+        """In the mutator thread: wait for the minor collection currently
+        running (if any) to finish."""
+        if self.collection_running != 0:
+            debug_start("gc-stop")
+            #
+            self.acquire(self.finished_lock)
+            self.collection_running = 0
+            #debug_print("collection_running = 0")
+            #
+            # Check invariants
+            ll_assert(not self.extra_objects_to_mark.non_empty(),
+                      "objs left behind in extra_objects_to_mark")
+            ll_assert(not self.gray_objects.non_empty(),
+                      "objs left behind in gray_objects")
+            #
+            # Grab the results of the last collection: read the collector's
+            # 'collect_heads/collect_tails' and merge them with the mutator's
+            # 'free_lists'.
+            n = 1
+            while n < self.pagelists_length:
+                self.free_lists[n] = self.join_lists(self.free_lists[n],
+                                                     self.collect_heads[n],
+                                                     self.collect_tails[n])
+                n += 1
+            #
+            # Do the same with 'collect_heads[0]/collect_tails[0]'.
+            self.nonfree_pages[0] = self.join_lists(self.nonfree_pages[0],
+                                                    self.collect_heads[0],
+                                                    self.collect_tails[0])
+            #
+            # Do the same with 'collect_weakref_pages/tails'
+            self.weakref_pages = self.join_lists(self.weakref_pages,
+                                                 self.collect_weakref_pages,
+                                                 self.collect_weakref_tails)
+            #
+            # Do the same with 'collect_finalizer_pages/tails'
+            self.finalizer_pages = self.join_lists(self.finalizer_pages,
+                                                  self.collect_finalizer_pages,
+                                                  self.collect_finalizer_tails)
+            #
+            # Do the same with 'collect_run_finalizers_head/tail'
+            self.objects_with_finalizers_to_run = self.join_lists(
+                self.objects_with_finalizers_to_run,
+                self.collect_run_finalizers_head,
+                self.collect_run_finalizers_tail)
+            #
+            if self.DEBUG:
+                self.debug_check_lists()
+            #
+            debug_stop("gc-stop")
+            #
+            # We must *not* run execute_finalizers_ll() here, because it
+            # can start the next collection, and then this function returns
+            # with a collection in progress, which it should not.  Be careful
+            # to call execute_finalizers_ll() in the caller somewhere.
+            ll_assert(self.collection_running == 0,
+                      "collector thread not paused?")
+
+    def join_lists(self, list1, list2head, list2tail):
+        if list2tail == self.NULL:
+            ll_assert(list2head == self.NULL, "join_lists/1")
+            return list1
+        else:
+            ll_assert(list2head != self.NULL, "join_lists/2")
+            set_next(list2tail, list1)
+            return list2head
+
+
+    def execute_finalizers_ll(self):
+        self.finalizer_lock_count += 1
+        try:
+            while self.objects_with_finalizers_to_run != self.NULL:
+                if self.finalizer_lock_count > 1:
+                    # the outer invocation of execute_finalizers() will do it
+                    break
+                #
+                x = llmemory.cast_ptr_to_adr(
+                        self.objects_with_finalizers_to_run)
+                x = llarena.getfakearenaaddress(x) + 8
+                obj = x + self.gcheaderbuilder.size_gc_header
+                self.objects_with_finalizers_to_run = list_next(
+                    self.objects_with_finalizers_to_run)
+                #
+                finalizer = self.getfinalizer(self.get_type_id(obj))
+                finalizer(obj, llmemory.NULL)
+        finally:
+            self.finalizer_lock_count -= 1
+
+
+    def collect(self, gen=3):
+        """
+        gen=0: Trigger a minor collection if none is running.  Never blocks.
+        
+        gen=1: The same, but if a minor collection is running, wait for
+        it to finish before triggering the next one.  Guarantees that
+        young objects not reachable when collect() is called will soon
+        be freed.
+
+        gen=2: The same, but wait for the triggered collection to
+        finish.  Guarantees that young objects not reachable when
+        collect() is called will be freed by the time collect() returns.
+
+        gen>=3: Do a (synchronous) major collection.
+        """
+        if gen >= 1 or self.collection_running <= 0:
+            self.trigger_next_collection()
+            if gen >= 2:
+                self.wait_for_the_end_of_collection()
+                if gen >= 3:
+                    self.major_collection()
+        self.execute_finalizers_ll()
+
+    def trigger_next_collection(self):
+        """In the mutator thread: triggers the next minor collection."""
+        #
+        # In case the previous collection is not over yet, wait for it
+        self.wait_for_the_end_of_collection()
+        #
+        debug_start("gc-start")
+        #
+        # Scan the stack roots and the refs in non-GC objects
+        self.root_walker.walk_roots(
+            ConcurrentGenGC._add_stack_root,  # stack roots
+            ConcurrentGenGC._add_stack_root,  # in prebuilt non-gc
+            None)                         # static in prebuilt gc
+        #
+        # Add the prebuilt root objects that have been written to
+        self.flagged_objects.foreach(self._add_prebuilt_root, None)
+        #
+        # Add the objects still waiting in 'objects_with_finalizers_to_run'
+        p = self.objects_with_finalizers_to_run
+        while p != self.NULL:
+            x = llmemory.cast_ptr_to_adr(p)
+            x = llarena.getfakearenaaddress(x) + 8
+            obj = x + self.gcheaderbuilder.size_gc_header
+            #debug_print("_objects_with_finalizers_to_run", obj)
+            self.get_mark(obj)
+            self.gray_objects.append(obj)
+            p = list_next(p)
+        #
+        # Exchange the meanings of 'cym' and 'cam'
+        other = self.current_young_marker
+        self.current_young_marker = self.current_aging_marker
+        self.current_aging_marker = other
+        #
+        # Copy a few 'mutator' fields to 'collector' fields:
+        # 'collect_pages' make linked lists of all nonfree pages at the
+        # start of the collection (unlike the 'nonfree_pages' lists, which
+        # the mutator will continue to grow).
+        n = 0
+        while n < self.pagelists_length:
+            self.collect_pages[n] = self.nonfree_pages[n]
+            n += 1
+        self.collect_weakref_pages = self.weakref_pages
+        self.collect_finalizer_pages = self.finalizer_pages
+        #
+        # Clear the following lists.  When the collector thread finishes,
+        # it will give back (in collect_{pages,tails}[0] and
+        # collect_finalizer_{pages,tails}) all the original items that survive.
+        self.nonfree_pages[0] = self.NULL
+        self.weakref_pages = self.NULL
+        self.finalizer_pages = self.NULL
+        #
+        # Start the collector thread
+        self.collection_running = 1
+        #debug_print("collection_running = 1")
+        self.release(self.ready_to_start_lock)
+        #
+        debug_stop("gc-start")
+        #
+        self.execute_finalizers_ll()
+
+    def _add_stack_root(self, root):
+        obj = root.address[0]
+        #debug_print("_add_stack_root", obj)
+        self.get_mark(obj)
+        self.gray_objects.append(obj)
+
+    def _add_prebuilt_root(self, obj, ignored):
+        #debug_print("_add_prebuilt_root", obj)
+        self.get_mark(obj)
+        self.gray_objects.append(obj)
+
+    def debug_check_lists(self):
+        # just check that they are correct, non-infinite linked lists
+        self.debug_check_list(self.nonfree_pages[0])
+        n = 1
+        while n < self.pagelists_length:
+            self.debug_check_list(self.free_lists[n])
+            n += 1
+        self.debug_check_list(self.weakref_pages)
+        self.debug_check_list(self.finalizer_pages)
+        self.debug_check_list(self.objects_with_finalizers_to_run)
+
+    def debug_check_list(self, page):
+        try:
+            previous_page = self.NULL
+            count = 0
+            while page != self.NULL:
+                # prevent constant-folding, and detects loops of length 1
+                ll_assert(page != previous_page, "loop!")
+                previous_page = page
+                page = list_next(page)
+                count += 1
+            return count
+        except KeyboardInterrupt:
+            ll_assert(False, "interrupted")
+            raise
+
+    def acquire(self, lock):
+        if (we_are_translated() or
+                ll_thread.get_ident() != self.main_thread_ident):
+            ll_thread.c_thread_acquirelock(lock, 1)
+        else:
+            while rffi.cast(lltype.Signed,
+                            ll_thread.c_thread_acquirelock(lock, 0)) == 0:
+                time.sleep(0.05)
+                # ---------- EXCEPTION FROM THE COLLECTOR THREAD ----------
+                if hasattr(self, '_exc_info'):
+                    self._reraise_from_collector_thread()
+
+    def release(self, lock):
+        ll_thread.c_thread_releaselock(lock)
+
+    def _reraise_from_collector_thread(self):
+        exc, val, tb = self._exc_info
+        raise exc, val, tb
+
+
+    def collector_run_nontranslated(self):
+        try:
+            if not hasattr(self, 'currently_running_in_rtyper'):
+                self.collector_run()     # normal tests
+            else:
+                # this case is for test_transformed_gc: we need to spawn
+                # another LLInterpreter for this new thread.
+                from pypy.rpython.llinterp import LLInterpreter
+                llinterp = LLInterpreter(self.currently_running_in_rtyper)
+                # XXX FISH HORRIBLY for the graph...
+                graph = sys._getframe(2).f_locals['self']._obj.graph
+                llinterp.eval_graph(graph)
+        except Exception, e:
+            print 'Crash!', e.__class__.__name__, e
+            #import pdb; pdb.post_mortem(sys.exc_info()[2])
+            self._exc_info = sys.exc_info()
+
+    def collector_run(self):
+        """Main function of the collector's thread."""
+        #
+        # hack: this is an infinite loop in practice.  During tests it can
+        # be interrupted.  In all cases the annotator must not conclude that
+        # it is an infinite loop: otherwise, the caller would automatically
+        # end in a "raise AssertionError", annoyingly, because we don't want
+        # any exception in this thread
+        while True:
+            #
+            # Wait for the lock to be released
+            self.acquire(self.ready_to_start_lock)
+            #
+            # For tests: detect when we have to shut down
+            if self.collection_running == 42:
+                self.release(self.finished_lock)
+                break
+            #
+            # Mark                                # collection_running == 1
+            self.collector_mark()
+            #                                     # collection_running == 2
+            self.deal_with_weakrefs()
+            #                                     # collection_running == 3
+            self.deal_with_objects_with_finalizers()
+            # Sweep                               # collection_running == 4
+            self.collector_sweep()
+            # Done!                               # collection_running == -1
+            self.release(self.finished_lock)
+
+
+    def set_mark(self, obj, newmark):
+        _set_mark(self.header(obj), newmark)
+
+    def get_mark(self, obj):
+        mark = self.header(obj).tid & 0xFF
+        ll_assert(mark == MARK_BYTE_1 or
+                  mark == MARK_BYTE_2 or
+                  mark == MARK_BYTE_OLD or
+                  mark == MARK_BYTE_STATIC, "bad mark byte in object")
+        return mark
+
+    def collector_mark(self):
+        while True:
+            #
+            # Do marking.  The following function call is interrupted
+            # if the mutator's write barrier adds new objects to
+            # 'extra_objects_to_mark'.
+            self._collect_mark()
+            #
+            # Move the objects from 'extra_objects_to_mark' to
+            # 'gray_objects'.  This requires the mutex lock.
+            # There are typically only a few objects to move here,
+            # unless XXX we've hit the write barrier of a large array
+            self.acquire(self.mutex_lock)
+            #debug_print("...collector thread has mutex_lock")
+            while self.extra_objects_to_mark.non_empty():
+                obj = self.extra_objects_to_mark.pop()
+                self.get_mark(obj)
+                self.gray_objects.append(obj)
+            #
+            # If 'gray_objects' is empty, we are done: there should be
+            # no possible case in which more objects are being added to
+            # 'extra_objects_to_mark' concurrently, because 'gray_objects'
+            # and 'extra_objects_to_mark' were already empty before we
+            # acquired the 'mutex_lock', so all reachable objects have
+            # been marked.
+            if not self.gray_objects.non_empty():
+                break
+            #
+            # Else release mutex_lock and try again.
+            self.release(self.mutex_lock)
+        #
+        self.collection_running = 2
+        #debug_print("collection_running = 2")
+        self.release(self.mutex_lock)
+
+    def _collect_mark(self):
+        cam = self.current_aging_marker
+        while self.gray_objects.non_empty():
+            obj = self.gray_objects.pop()
+            if self.get_mark(obj) == cam:
+                #
+                # Scan the content of 'obj'.  We use a snapshot-at-the-
+                # beginning order, meaning that we want to scan the state
+                # of the object as it was at the beginning of the current
+                # collection --- and not the current state, which might have
+                # been modified.  That's why we have a deletion barrier:
+                # when the mutator thread is about to change an object that
+                # is not yet marked, it will itself do the scanning of just
+                # this object, and mark the object.  But this function is not
+                # synchronized, which means that in some rare cases it's
+                # possible that the object is scanned a second time here
+                # (harmlessly).
+                #
+                # The order of the next two lines is essential!  *First*
+                # scan the object, adding all objects found to gray_objects;
+                # and *only then* set the mark.  This is essential, because
+                # otherwise, we might set the mark, then the main thread
+                # thinks a force_scan() is not necessary and modifies the
+                # content of 'obj', and then here in the collector thread
+                # we scan a modified content --- and the original content
+                # is never scanned.
+                #
+                self.trace(obj, self._collect_add_pending, None)
+                self.set_mark(obj, MARK_BYTE_OLD)
+                #
+                # Interrupt early if the mutator's write barrier adds stuff
+                # to that list.  Note that the check is imprecise because
+                # it is not lock-protected, but that's good enough.  The
+                # idea is that we trace in priority objects flagged with
+                # the write barrier, because they are more likely to
+                # reference further objects that will soon be accessed too.
+                if self.extra_objects_to_mark.non_empty():
+                    return
+
+    def _collect_add_pending(self, root, ignored):
+        obj = root.address[0]
+        self.get_mark(obj)
+        self.gray_objects.append(obj)
+
+    def collector_sweep(self):
+        self._collect_sweep_large_objects()
+        #
+        n = 1
+        while n < self.pagelists_length:
+            self._collect_sweep_pages(n)
+            n += 1
+        #
+        self.collection_running = -1
+        #debug_print("collection_running = -1")
+
+    def _collect_sweep_large_objects(self):
+        block = self.collect_pages[0]
+        cam = self.current_aging_marker
+        linked_list = self.NULL
+        first_block_in_linked_list = self.NULL
+        while block != self.NULL:
+            nextblock = list_next(block)
+            blockadr = llmemory.cast_ptr_to_adr(block)
+            blockadr = llarena.getfakearenaaddress(blockadr)
+            hdr = llmemory.cast_adr_to_ptr(blockadr + 8, self.HDRPTR)
+            mark = hdr.tid & 0xFF
+            if mark == cam:
+                # the object is still not marked.  Free it.
+                llarena.arena_free(blockadr)
+                #
+            else:
+                # the object was marked: relink it
+                ll_assert(mark == MARK_BYTE_OLD,
+                          "bad mark in large object")
+                set_next(block, linked_list)
+                linked_list = block
+                if first_block_in_linked_list == self.NULL:
+                    first_block_in_linked_list = block
+            block = nextblock
+        #
+        self.collect_heads[0] = linked_list
+        self.collect_tails[0] = first_block_in_linked_list
+
+    def _collect_sweep_pages(self, n):
+        # sweep all pages from the linked list starting at 'page',
+        # containing objects of fixed size 'object_size'.
+        size_gc_header = self.gcheaderbuilder.size_gc_header
+        page = self.collect_pages[n]
+        object_size = n << WORD_POWER_2
+        linked_list = self.NULL
+        first_loc_in_linked_list = self.NULL
+        cam = self.current_aging_marker
+        while page != self.NULL:
+            i = self.page_size - object_size
+            limit = raw_malloc_usage(self.HDRSIZE)
+            pageadr = llmemory.cast_ptr_to_adr(page)
+            pageadr = llarena.getfakearenaaddress(pageadr)
+            while i >= limit:
+                adr = pageadr + i
+                hdr = llmemory.cast_adr_to_ptr(adr, self.HDRPTR)
+                mark = hdr.tid & 0xFF
+                #
+                if mark == cam:
+                    # the location contains really an object (and is not just
+                    # part of a linked list of free locations), and moreover
+                    # the object is still not marked.  Free it by inserting
+                    # it into the linked list.
+                    #debug_print("sweeps", adr + size_gc_header)
+                    llarena.arena_reset(adr, object_size, 0)
+                    llarena.arena_reserve(adr, self.HDRSIZE)
+                    hdr = llmemory.cast_adr_to_ptr(adr, self.HDRPTR)
+                    set_next(hdr, linked_list)
+                    linked_list = hdr
+                    if first_loc_in_linked_list == self.NULL:
+                        first_loc_in_linked_list = hdr
+                    # XXX detect when the whole page is freed again
+                    #
+                    # Clear the data, in prevision for the following
+                    # malloc_fixedsize_clear().
+                    size_of_int = raw_malloc_usage(
+                        llmemory.sizeof(lltype.Signed))
+                    llarena.arena_reset(adr + size_of_int,
+                                        object_size - size_of_int, 2)
+                #
+                i -= object_size
+            #
+            page = list_next(page)
+        #
+        self.collect_heads[n] = linked_list
+        self.collect_tails[n] = first_loc_in_linked_list
+
+
+    # ----------
+    # Major collections
+
+    def major_collection(self):
+        pass #XXX
+
+
+    # ----------
+    # Weakrefs
+
+    def weakref_deref(self, wrobj):
+        # Weakrefs need some care.  This code acts as a read barrier.
+        # The only way I found is to acquire the mutex_lock to prevent
+        # the collection thread from going from collection_running==1
+        # to collection_running==2, or from collection_running==2 to
+        # collection_running==3.
+        #
+        self.acquire(self.mutex_lock)
+        #
+        targetobj = gctypelayout.ll_weakref_deref(wrobj)
+        if targetobj != llmemory.NULL:
+            #
+            if self.collection_running == 1:
+                # If we are in the phase collection_running==1, we don't
+                # know if the object will be scanned a bit later or
+                # not; so we have to assume that it survives, and
+                # force it to be scanned.
+                self.get_mark(targetobj)
+                self.extra_objects_to_mark.append(targetobj)
+                #
+            elif self.collection_running == 2:
+                # In the phase collection_running==2, if the object is
+                # not marked it's too late; we have to detect that case
+                # and return NULL instead here, as if the corresponding
+                # collector phase was already finished (deal_with_weakrefs).
+                # Otherwise we would be returning an object that is about to
+                # be swept away.
+                if not self.is_marked_or_static(targetobj, self.current_mark):
+                    targetobj = llmemory.NULL
+                #
+            else:
+                # In other phases we are fine.
+                pass
+        #
+        self.release(self.mutex_lock)
+        #
+        return targetobj
+
+    def deal_with_weakrefs(self):
+        self.collection_running = 3; return
+        # ^XXX^
+        size_gc_header = self.gcheaderbuilder.size_gc_header
+        current_mark = self.current_mark
+        weakref_page = self.collect_weakref_pages
+        self.collect_weakref_pages = self.NULL
+        self.collect_weakref_tails = self.NULL
+        while weakref_page != self.NULL:
+            next_page = list_next(weakref_page)
+            #
+            # If the weakref points to a dead object, make it point to NULL.
+            x = llmemory.cast_ptr_to_adr(weakref_page)
+            x = llarena.getfakearenaaddress(x) + 8
+            hdr = llmemory.cast_adr_to_ptr(x, self.HDRPTR)
+            type_id = llop.extract_high_ushort(llgroup.HALFWORD, hdr.tid)
+            offset = self.weakpointer_offset(type_id)
+            ll_assert(offset >= 0, "bad weakref")
+            obj = x + size_gc_header
+            pointing_to = (obj + offset).address[0]
+            ll_assert(pointing_to != llmemory.NULL, "null weakref?")
+            if not self.is_marked_or_static(pointing_to, current_mark):
+                # 'pointing_to' dies: relink to self.collect_pages[0]
+                (obj + offset).address[0] = llmemory.NULL
+                set_next(weakref_page, self.collect_pages[0])
+                self.collect_pages[0] = weakref_page
+            else:
+                # the weakref stays alive
+                set_next(weakref_page, self.collect_weakref_pages)
+                self.collect_weakref_pages = weakref_page
+                if self.collect_weakref_tails == self.NULL:
+                    self.collect_weakref_tails = weakref_page
+            #
+            weakref_page = next_page
+        #
+        self.acquire(self.mutex_lock)
+        self.collection_running = 3
+        #debug_print("collection_running = 3")
+        self.release(self.mutex_lock)
+
+
+    # ----------
+    # Finalizers
+
+    def deal_with_objects_with_finalizers(self):
+        self.collection_running = 4; return
+        # ^XXX^
+        
+        # XXX needs to be done correctly; for now we'll call finalizers
+        # in random order
+        size_gc_header = self.gcheaderbuilder.size_gc_header
+        marked = self.current_mark
+        finalizer_page = self.collect_finalizer_pages
+        self.collect_run_finalizers_head = self.NULL
+        self.collect_run_finalizers_tail = self.NULL
+        self.collect_finalizer_pages = self.NULL
+        self.collect_finalizer_tails = self.NULL
+        while finalizer_page != self.NULL:
+            next_page = list_next(finalizer_page)
+            #
+            x = llmemory.cast_ptr_to_adr(finalizer_page)
+            x = llarena.getfakearenaaddress(x) + 8
+            hdr = llmemory.cast_adr_to_ptr(x, self.HDRPTR)
+            if (hdr.tid & 0xFF) != marked:
+                # non-marked: add to collect_run_finalizers,
+                # and mark the object and its dependencies
+                set_next(finalizer_page, self.collect_run_finalizers_head)
+                self.collect_run_finalizers_head = finalizer_page
+                if self.collect_run_finalizers_tail == self.NULL:
+                    self.collect_run_finalizers_tail = finalizer_page
+                obj = x + size_gc_header
+                self.get_mark(obj)
+                self.gray_objects.append(obj)
+            else:
+                # marked: relink into the collect_finalizer_pages list
+                set_next(finalizer_page, self.collect_finalizer_pages)
+                self.collect_finalizer_pages = finalizer_page
+                if self.collect_finalizer_tails == self.NULL:
+                    self.collect_finalizer_tails = finalizer_page
+            #
+            finalizer_page = next_page
+        #
+        self._collect_mark()
+        #
+        ll_assert(not self.extra_objects_to_mark.non_empty(),
+                  "should not see objects only reachable from finalizers "
+                  "before we run them")
+        #
+        self.collection_running = 4
+        #debug_print("collection_running = 4")
+
+
+# ____________________________________________________________
+#
+# Support for linked lists (used here because AddressStack is not thread-safe)
+
+def list_next(hdr):
+    return llmemory.cast_adr_to_ptr(llmemory.cast_int_to_adr(hdr.tid),
+                                    ConcurrentGenGC.HDRPTR)
+
+def set_next(hdr, nexthdr):
+    hdr.tid = llmemory.cast_adr_to_int(llmemory.cast_ptr_to_adr(nexthdr),
+                                       "symbolic")
+
+
+# ____________________________________________________________
+#
+# Hack to write the 'mark' or the 'flags' bytes of an object header
+# without overwriting the whole word.  Essential in the rare case where
+# the other thread might be concurrently writing the other byte.
+
+concurrent_setter_lock = ll_thread.allocate_lock()
+
+def emulate_set_mark(p, v):
+    "NOT_RPYTHON"
+    assert v in (MARK_BYTE_1, MARK_BYTE_2, MARK_BYTE_OLD, MARK_BYTE_STATIC)
+    concurrent_setter_lock.acquire(True)
+    p.tid = (p.tid &~ 0xFF) | v
+    concurrent_setter_lock.release()
+
+def emulate_set_flags(p, v):
+    "NOT_RPYTHON"
+    assert (v & ~0xFF00) == 0
+    concurrent_setter_lock.acquire(True)
+    p.tid = (p.tid &~ 0xFF00) | v
+    concurrent_setter_lock.release()
+
+if sys.byteorder == 'little':
+    eci = ExternalCompilationInfo(
+        post_include_bits = ["""
+#define pypy_concurrentms_set_mark(p, v)   ((char*)p)[0] = v
+#define pypy_concurrentms_set_flags(p, v)  ((char*)p)[1] = v
+        """])
+elif sys.byteorder == 'big':
+    eci = ExternalCompilationInfo(
+        post_include_bits = [r"""
+#define pypy_concurrentms_set_mark(p, v)   ((char*)p)[sizeof(long)-1] = v
+#define pypy_concurrentms_set_flags(p, v)  ((char*)p)[sizeof(long)-2] = v
+        """])
+else:
+    raise NotImplementedError(sys.byteorder)
+
+_set_mark = rffi.llexternal("pypy_concurrentms_set_mark",
+                           [ConcurrentGenGC.HDRPTR, lltype.Signed],
+                           lltype.Void, compilation_info=eci, _nowrapper=True,
+                           _callable=emulate_set_mark)
+_set_flags = rffi.llexternal("pypy_concurrentms_set_flags",
+                           [ConcurrentGenGC.HDRPTR, lltype.Signed],
+                           lltype.Void, compilation_info=eci, _nowrapper=True,
+                           _callable=emulate_set_flags)
+
+# ____________________________________________________________
+#
+# A lock to synchronize access to AddressStack's free pages
+
+class SyncLock:
+    _alloc_flavor_ = "raw"
+    _lock = lltype.nullptr(ll_thread.TLOCKP.TO)
+    def setup(self):
+        self._lock = ll_thread.allocate_ll_lock()
+    def acquire(self):
+        if self._lock:
+            ll_thread.c_thread_acquirelock(self._lock, 1)
+    def release(self):
+        if self._lock:
+            ll_thread.c_thread_releaselock(self._lock)
diff --git a/pypy/rpython/memory/gc/concurrentgen.py b/pypy/rpython/memory/gc/concurrentgen.txt
copy from pypy/rpython/memory/gc/concurrentgen.py
copy to pypy/rpython/memory/gc/concurrentgen.txt
--- a/pypy/rpython/memory/gc/concurrentgen.py
+++ b/pypy/rpython/memory/gc/concurrentgen.txt
@@ -1,4 +1,3 @@
-"""
 ************************************************************
   Minor collection cycles of the "concurrentgen" collector
 ************************************************************
@@ -11,8 +10,8 @@
     '#'        : old objs
     'S'        : static prebuilt objs with no heap pointer
 
-'cym' is the current young marker
-'cam' is the current aging marker
+cym = current_young_marker
+cam = current_aging_marker
 
 The write barrier activates when writing into an object whose
 mark byte is different from 'cym'.
@@ -109,4 +108,3 @@
         (should not see any 'cam' object any more here)
 
 ------------------------------------------------------------
-"""
diff --git a/pypy/rpython/memory/gc/test/test_direct.py b/pypy/rpython/memory/gc/test/test_direct.py
--- a/pypy/rpython/memory/gc/test/test_direct.py
+++ b/pypy/rpython/memory/gc/test/test_direct.py
@@ -609,3 +609,7 @@
 class TestMostlyConcurrentMarkSweepGC(DirectGCTest):
     from pypy.rpython.memory.gc.concurrentms \
             import MostlyConcurrentMarkSweepGC as GCClass
+
+class TestConcurrentGenGC(DirectGCTest):
+    from pypy.rpython.memory.gc.concurrentgen \
+            import ConcurrentGenGC as GCClass


More information about the pypy-commit mailing list