[pypy-commit] pypy concurrent-marksweep: The first few tests pass :-)

arigo noreply at buildbot.pypy.org
Fri Oct 7 16:17:33 CEST 2011


Author: Armin Rigo <arigo at tunes.org>
Branch: concurrent-marksweep
Changeset: r47855:2ee9ca05d5df
Date: 2011-10-07 15:54 +0200
http://bitbucket.org/pypy/pypy/changeset/2ee9ca05d5df/

Log:	The first few tests pass :-)

diff --git a/pypy/rpython/memory/gc/concurrentms.py b/pypy/rpython/memory/gc/concurrentms.py
--- a/pypy/rpython/memory/gc/concurrentms.py
+++ b/pypy/rpython/memory/gc/concurrentms.py
@@ -1,8 +1,7 @@
 import time, sys
 from pypy.rpython.lltypesystem import lltype, llmemory, llarena, llgroup, rffi
-from pypy.rpython.lltypesystem.lloperation import llop
 from pypy.rpython.lltypesystem.llmemory import raw_malloc_usage
-from pypy.rlib.objectmodel import we_are_translated, specialize
+from pypy.rlib.objectmodel import we_are_translated, running_on_llinterp
 from pypy.rlib.debug import ll_assert
 from pypy.rlib.rarithmetic import LONG_BIT, r_uint
 from pypy.rpython.memory.gc.base import GCBase
@@ -27,10 +26,17 @@
 WORD_POWER_2 = {32: 2, 64: 3}[LONG_BIT]
 assert 1 << WORD_POWER_2 == WORD
 size_of_addr = llmemory.sizeof(llmemory.Address)
-first_gcflag = 1 << (LONG_BIT//2)
 
-GCFLAG_MARK_TOGGLE = first_gcflag << 0
-GCFLAG_FINALIZATION_ORDERING = first_gcflag << 1
+# XXX assumes little-endian machines for now: the byte at offset 0 in
+# the object is either a mark byte (equal to an odd value), or if the
+# location is free, it is the low byte of a pointer to the next free
+# location (and then it is an even value, by pointer alignment).
+assert sys.byteorder == 'little'
+
+
+MARK_VALUE_1      = 'M'     #  77, 0x4D
+MARK_VALUE_2      = 'k'     # 107, 0x6B
+GCFLAG_WITH_HASH  = 0x01
 
 
 class MostlyConcurrentMarkSweepGC(GCBase):
@@ -40,10 +46,13 @@
     needs_write_barrier = True
     prebuilt_gc_objects_are_static_roots = False
     malloc_zero_filled = True
-    gcflag_extra = GCFLAG_FINALIZATION_ORDERING
+    #gcflag_extra = GCFLAG_FINALIZATION_ORDERING
 
-    HDR = lltype.Struct('header', ('tid', lltype.Signed))
-    typeid_is_in_field = 'tid'
+    HDR = lltype.Struct('header', ('mark', lltype.Char),  # MARK_VALUE_{1,2}
+                                  ('flags', lltype.Char),
+                                  ('typeid16', llgroup.HALFWORD))
+    typeid_is_in_field = 'typeid16'
+    withhash_flag_is_in_field = 'flags', GCFLAG_WITH_HASH
 
     TRANSLATION_PARAMS = {'page_size': 4096,
                           'small_request_threshold': 35*WORD,
@@ -59,20 +68,27 @@
         self.small_request_threshold = small_request_threshold
         self.page_size = page_size
         self.free_pages = NULL
-        length = small_request_threshold // WORD + 1
-        self.free_lists = lltype.malloc(rffi.CArray(llmemory.Address),
-                                        length, flavor='raw', zero=True,
-                                        immortal=True)
-        self.current_mark = 0
+        self.pagelists_length = small_request_threshold // WORD + 1
+        #
+        def list_of_addresses_per_small_size():
+            return lltype.malloc(rffi.CArray(llmemory.Address),
+                                 self.pagelists_length, flavor='raw',
+                                 zero=True, immortal=True)
+        self.nonfree_pages = list_of_addresses_per_small_size()
+        self.collect_pages = list_of_addresses_per_small_size()
+        self.free_lists    = list_of_addresses_per_small_size()
+        self.collect_heads = list_of_addresses_per_small_size()
+        self.collect_tails = list_of_addresses_per_small_size()
+        self.current_mark = MARK_VALUE_1
         #
         # When the mutator thread wants to trigger the next collection,
         # it scans its own stack roots and prepares everything, then
-        # sets 'collection_running' to True, and releases
+        # 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 False.
-        self.collection_running = False
+        # responsible for resetting 'collection_running' to 0.
+        self.collection_running = 0
         self.ready_to_start_lock = ll_thread.allocate_lock()
         self.finished_lock = ll_thread.allocate_lock()
         #
@@ -109,11 +125,7 @@
         self.collect()
 
     def get_type_id(self, obj):
-        tid = self.header(obj).tid
-        return llop.extract_ushort(llgroup.HALFWORD, tid)
-
-    def combine(self, typeid16, flags):
-        return llop.combine_ushort(lltype.Signed, typeid16, flags)
+        return self.header(obj).typeid16
 
     def malloc_fixedsize_clear(self, typeid, size,
                                needs_finalizer=False, contains_weakptr=False):
@@ -131,7 +143,9 @@
                 llarena.arena_reset(result, size_of_addr, 0)
                 llarena.arena_reserve(result, totalsize)
                 hdr = llmemory.cast_adr_to_ptr(result, lltype.Ptr(self.HDR))
-                hdr.tid = self.combine(typeid, self.current_mark)
+                hdr.typeid16 = typeid
+                hdr.mark = self.current_mark
+                hdr.flags = '\x00'
                 #
                 obj = result + size_gc_header
                 return llmemory.cast_adr_to_ptr(obj, llmemory.GCREF)
@@ -151,16 +165,21 @@
                 self.allocate_next_arena()
                 newpage = self.free_pages
             self.free_pages = newpage.address[0]
-            llarena.arena_reset(newpage, size_of_addr, 0)
+            #
+            # Put the free page in the list 'nonfree_pages[n]'.  This is
+            # a linked list chained through the first word of each page.
+            n = (rawtotalsize + WORD - 1) >> WORD_POWER_2
+            newpage.address[0] = 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.
-            n = (rawtotalsize + WORD - 1) >> WORD_POWER_2
             head = self.free_lists[n]
             ll_assert(not head, "_malloc_slowpath: unexpected free_lists[n]")
             i = self.page_size - rawtotalsize
-            while i >= rawtotalsize:
+            limit = rawtotalsize + raw_malloc_usage(size_of_addr)
+            while i >= limit:
                 llarena.arena_reserve(newpage + i, size_of_addr)
                 (newpage + i).address[0] = head
                 head = newpage + i
@@ -168,14 +187,17 @@
             self.free_lists[n] = head
             result = head - rawtotalsize
             #
-            # 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 a multiple of 2, there are a few wasted
-            # WORDs, which we place at the start of the page rather than
-            # at the end (Hans Boehm, xxx ref).
+            # 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).
             llarena.arena_reserve(result, totalsize)
             hdr = llmemory.cast_adr_to_ptr(result, lltype.Ptr(self.HDR))
-            hdr.tid = self.combine(typeid, self.current_mark)
+            hdr.typeid16 = typeid
+            hdr.mark = self.current_mark
+            hdr.flags = '\x00'
             #
             obj = result + size_gc_header
             return llmemory.cast_adr_to_ptr(obj, llmemory.GCREF)
@@ -196,19 +218,23 @@
 
 
     def write_barrier(self, newvalue, addr_struct):
-        flag = self.header(addr_struct).tid & GCFLAG_MARK_TOGGLE
-        if flag != self.current_mark:
+        mark = self.header(addr_struct).mark
+        if mark != self.current_mark:
             self.force_scan(addr_struct)
 
     def _init_writebarrier_logic(self):
         #
         def force_scan(obj):
             self.mutex_lock.acquire(True)
-            if self.current_mark:
-                self.set_mark_flag(obj, GCFLAG_MARK_TOGGLE)
-            else:
-                self.set_mark_flag(obj, 0)
-            self.trace(obj, self._barrier_add_extra, None)
+            if not self.is_marked(obj, self.current_mark):
+                # 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?")
+                #
+                self.set_mark(obj, self.current_mark)
+                self.trace(obj, self._barrier_add_extra, None)
             self.mutex_lock.release()
         #
         force_scan._dont_inline_ = True
@@ -218,24 +244,35 @@
         self.extra_objects_to_mark.append(root.address[0])
 
 
+    def wait_for_the_end_of_collection(self):
+        """In the mutator thread: wait for the collection currently
+        running (if any) to finish."""
+        if self.collection_running != 0:
+            self.acquire(self.finished_lock)
+            self.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:
+                if self.collect_tails[n] != NULL:
+                    self.collect_tails[n].address[0] = self.free_lists[n]
+                    self.free_lists[n] = self.collect_heads[n]
+                n += 1
+
+
     def collect(self, gen=0):
         """Trigger a complete collection, and wait for it to finish."""
         self.trigger_next_collection()
         self.wait_for_the_end_of_collection()
 
-    def wait_for_the_end_of_collection(self):
-        """In the mutator thread: wait for the collection currently
-        running (if any) to finish."""
-        if self.collection_running:
-            self.acquire(self.finished_lock)
-            self.collection_running = False
-            #
-            # It's possible that an object was added to 'extra_objects_to_mark'
-            # by the write barrier but not taken out by the collector thread,
-            # because it finished in the meantime.  The result is still
-            # correct, but we need to clear the list.
-            self.extra_objects_to_mark.clear()
-
     def trigger_next_collection(self):
         """In the mutator thread: triggers the next collection."""
         #
@@ -250,10 +287,19 @@
         #
         # Invert this global variable, which has the effect that on all
         # objects' state go instantly from "marked" to "non marked"
-        self.current_mark ^= GCFLAG_MARK_TOGGLE
+        self.current_mark = self.other_mark(self.current_mark)
+        #
+        # 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 = 1
+        while n < self.pagelists_length:
+            self.collect_pages[n] = self.nonfree_pages[n]
+            n += 1
         #
         # Start the collector thread
-        self.collection_running = True
+        self.collection_running = 1
         self.ready_to_start_lock.release()
 
     def _add_stack_root(self, root):
@@ -291,23 +337,29 @@
                 #
                 # Mark
                 self.collector_mark()
+                self.collection_running = 2
                 #
                 # Sweep
                 self.collector_sweep()
+                self.finished_lock.release()
+                #
         except Exception, e:
             print 'Crash!', e.__class__.__name__, e
             self._exc_info = sys.exc_info()
 
-    @specialize.arg(2)
+    def other_mark(self, mark):
+        ll_assert(mark == MARK_VALUE_1 or mark == MARK_VALUE_2,
+                  "bad mark value")
+        return chr(ord(mark) ^ (ord(MARK_VALUE_1) ^ ord(MARK_VALUE_2)))
+
     def is_marked(self, obj, current_mark):
-        return (self.header(obj).tid & GCFLAG_MARK_TOGGLE) == current_mark
+        mark = self.header(obj).mark
+        ll_assert(mark == MARK_VALUE_1 or mark == MARK_VALUE_2,
+                  "bad mark byte in object")
+        return mark == current_mark
 
-    @specialize.arg(2)
-    def set_mark_flag(self, obj, current_mark):
-        if current_mark:
-            self.header(obj).tid |= GCFLAG_MARK_TOGGLE
-        else:
-            self.header(obj).tid &= ~GCFLAG_MARK_TOGGLE
+    def set_mark(self, obj, current_mark):
+        self.header(obj).mark = current_mark
 
     def collector_mark(self):
         while True:
@@ -315,10 +367,7 @@
             # Do marking.  The following function call is interrupted
             # if the mutator's write barrier adds new objects to
             # 'extra_objects_to_mark'.
-            if self.current_mark:
-                self._collect_mark(GCFLAG_MARK_TOGGLE)
-            else:
-                self._collect_mark(0)
+            self._collect_mark()
             #
             # Move the objects from 'extra_objects_to_mark' to
             # 'gray_objects'.  This requires the mutex lock.
@@ -339,12 +388,12 @@
             if not self.gray_objects.non_empty():
                 return
 
-    @specialize.arg(1)
-    def _collect_mark(self, current_mark):
+    def _collect_mark(self):
+        current_mark = self.current_mark
         while self.gray_objects.non_empty():
             obj = self.gray_objects.pop()
             if not self.is_marked(obj, current_mark):
-                self.set_mark_flag(obj, current_mark)
+                self.set_mark(obj, current_mark)
                 self.trace(obj, self._collect_add_pending, None)
                 #
                 # Interrupt early if the mutator's write barrier adds stuff
@@ -360,4 +409,49 @@
         self.gray_objects.append(root.address[0])
 
     def collector_sweep(self):
-        xxx
+        n = 1
+        while n < self.pagelists_length:
+            self._collect_sweep_pages(n)
+            n += 1
+
+    def _collect_sweep_pages(self, n):
+        # sweep all pages from the linked list starting at 'page',
+        # containing objects of fixed size 'object_size'.
+        page = self.collect_pages[n]
+        object_size = n << WORD_POWER_2
+        linked_list = NULL
+        first_freed_object = NULL
+        nonmarked = self.other_mark(self.current_mark)
+        while page != llmemory.NULL:
+            i = self.page_size - object_size
+            limit = raw_malloc_usage(size_of_addr)
+            while i >= limit:
+                hdr = page + i
+                #
+                if maybe_read_mark_byte(hdr) == nonmarked:
+                    # 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.
+                    llarena.arena_reset(hdr, object_size, 0)
+                    llarena.arena_reserve(hdr, size_of_addr)
+                    hdr.address[0] = linked_list
+                    linked_list = hdr
+                    if first_freed_object == NULL:
+                        first_freed_object = hdr
+                    # XXX detect when the whole page is freed again
+                #
+                i -= object_size
+            #
+            page = page.address[0]
+        #
+        self.collect_heads[n] = linked_list
+        self.collect_tails[n] = first_freed_object
+
+
+def maybe_read_mark_byte(addr):
+    "NOT_RPYTHON"
+    try:
+        return addr.ptr.mark
+    except AttributeError:
+        return '\x00'


More information about the pypy-commit mailing list