[pypy-svn] r76852 - in pypy/trunk/pypy: rpython/lltypesystem rpython/lltypesystem/test rpython/memory rpython/memory/gc rpython/memory/gc/test rpython/memory/test translator translator/c/test

arigo at codespeak.net arigo at codespeak.net
Fri Sep 3 13:39:15 CEST 2010


Author: arigo
Date: Fri Sep  3 13:39:11 2010
New Revision: 76852

Modified:
   pypy/trunk/pypy/rpython/lltypesystem/llgroup.py
   pypy/trunk/pypy/rpython/lltypesystem/llmemory.py
   pypy/trunk/pypy/rpython/lltypesystem/opimpl.py
   pypy/trunk/pypy/rpython/lltypesystem/rffi.py
   pypy/trunk/pypy/rpython/lltypesystem/test/test_llgroup.py
   pypy/trunk/pypy/rpython/memory/gc/base.py
   pypy/trunk/pypy/rpython/memory/gc/generation.py
   pypy/trunk/pypy/rpython/memory/gc/markcompact.py
   pypy/trunk/pypy/rpython/memory/gc/test/test_direct.py
   pypy/trunk/pypy/rpython/memory/gctypelayout.py
   pypy/trunk/pypy/rpython/memory/gcwrapper.py
   pypy/trunk/pypy/rpython/memory/test/test_gc.py
   pypy/trunk/pypy/rpython/memory/test/test_transformed_gc.py
   pypy/trunk/pypy/translator/c/test/test_newgc.py
   pypy/trunk/pypy/translator/exceptiontransform.py
Log:
Merge branch/markcompact: re-enable and improve the mark&compact GC.
Its usefulness is still a bit unclear.


Modified: pypy/trunk/pypy/rpython/lltypesystem/llgroup.py
==============================================================================
--- pypy/trunk/pypy/rpython/lltypesystem/llgroup.py	(original)
+++ pypy/trunk/pypy/rpython/lltypesystem/llgroup.py	Fri Sep  3 13:39:11 2010
@@ -136,6 +136,10 @@
         assert (other & CombinedSymbolic.MASK) == 0
         return CombinedSymbolic(self.lowpart, self.rest - other)
 
+    def __rshift__(self, other):
+        assert other >= HALFSHIFT
+        return self.rest >> other
+
     def __eq__(self, other):
         if (isinstance(other, CombinedSymbolic) and
             self.lowpart is other.lowpart):

Modified: pypy/trunk/pypy/rpython/lltypesystem/llmemory.py
==============================================================================
--- pypy/trunk/pypy/rpython/lltypesystem/llmemory.py	(original)
+++ pypy/trunk/pypy/rpython/lltypesystem/llmemory.py	Fri Sep  3 13:39:11 2010
@@ -361,19 +361,27 @@
 
 # ____________________________________________________________
 
+def _sizeof_none(TYPE):
+    assert not TYPE._is_varsize()
+    return ItemOffset(TYPE)
+_sizeof_none._annspecialcase_ = 'specialize:memo'
+
+def _sizeof_int(TYPE, n):
+    "NOT_RPYTHON"
+    if isinstance(TYPE, lltype.Struct):
+        return FieldOffset(TYPE, TYPE._arrayfld) + \
+               itemoffsetof(TYPE._flds[TYPE._arrayfld], n)
+    else:
+        raise Exception("don't know how to take the size of a %r"%TYPE)
+
 def sizeof(TYPE, n=None):
     if n is None:
-        assert not TYPE._is_varsize()
-        return ItemOffset(TYPE)
+        return _sizeof_none(TYPE)
+    elif isinstance(TYPE, lltype.Array):
+        return itemoffsetof(TYPE) + _sizeof_none(TYPE.OF) * n
     else:
-        if isinstance(TYPE, lltype.Array):
-            return itemoffsetof(TYPE, n)
-        elif isinstance(TYPE, lltype.Struct):
-            return FieldOffset(TYPE, TYPE._arrayfld) + \
-                   itemoffsetof(TYPE._flds[TYPE._arrayfld], n)
-        else:
-            raise Exception("don't know how to take the size of a %r"%TYPE)
-sizeof._annspecialcase_ = 'specialize:memo'   # only for n == None
+        return _sizeof_int(TYPE, n)
+sizeof._annspecialcase_ = 'specialize:arg(0)'
 
 def offsetof(TYPE, fldname):
     assert fldname in TYPE._flds

Modified: pypy/trunk/pypy/rpython/lltypesystem/opimpl.py
==============================================================================
--- pypy/trunk/pypy/rpython/lltypesystem/opimpl.py	(original)
+++ pypy/trunk/pypy/rpython/lltypesystem/opimpl.py	Fri Sep  3 13:39:11 2010
@@ -197,6 +197,18 @@
     assert isinstance(y, int)
     return intmask(x - y)
 
+def op_int_ge(x, y):
+    # special case for 'AddressOffset >= 0'
+    assert isinstance(x, (int, llmemory.AddressOffset))
+    assert isinstance(y, int)
+    return x >= y
+
+def op_int_lt(x, y):
+    # special case for 'AddressOffset < 0'
+    assert isinstance(x, (int, llmemory.AddressOffset))
+    assert isinstance(y, int)
+    return x < y
+
 def op_int_between(a, b, c):
     assert lltype.typeOf(a) is lltype.Signed
     assert lltype.typeOf(b) is lltype.Signed
@@ -222,6 +234,13 @@
     assert isinstance(y, (int, llmemory.AddressOffset))
     return intmask(x * y)
 
+def op_int_rshift(x, y):
+    if not isinstance(x, int):
+        from pypy.rpython.lltypesystem import llgroup
+        assert isinstance(x, llgroup.CombinedSymbolic)
+    assert isinstance(y, int)
+    return x >> y
+
 def op_int_floordiv(x, y):
     assert isinstance(x, (int, llmemory.AddressOffset))
     assert isinstance(y, (int, llmemory.AddressOffset))

Modified: pypy/trunk/pypy/rpython/lltypesystem/rffi.py
==============================================================================
--- pypy/trunk/pypy/rpython/lltypesystem/rffi.py	(original)
+++ pypy/trunk/pypy/rpython/lltypesystem/rffi.py	Fri Sep  3 13:39:11 2010
@@ -593,9 +593,12 @@
         """ str -> char*
         """
         array = lltype.malloc(TYPEP.TO, len(s) + 1, flavor='raw')
-        for i in range(len(s)):
+        i = len(s)
+        array[i] = lastchar
+        i -= 1
+        while i >= 0:
             array[i] = s[i]
-        array[len(s)] = lastchar
+            i -= 1
         return array
     str2charp._annenforceargs_ = [strtype]
 

Modified: pypy/trunk/pypy/rpython/lltypesystem/test/test_llgroup.py
==============================================================================
--- pypy/trunk/pypy/rpython/lltypesystem/test/test_llgroup.py	(original)
+++ pypy/trunk/pypy/rpython/lltypesystem/test/test_llgroup.py	Fri Sep  3 13:39:11 2010
@@ -105,6 +105,8 @@
             assert p == test.p1b
         assert cslist[0] & ~MASK == 0x45 << HALFSHIFT
         assert cslist[1] & ~MASK == 0x41 << HALFSHIFT
+        assert cslist[0] >> HALFSHIFT == 0x45
+        assert cslist[1] >> (HALFSHIFT+1) == 0x41 >> 1
         #
         return 42
     return f

Modified: pypy/trunk/pypy/rpython/memory/gc/base.py
==============================================================================
--- pypy/trunk/pypy/rpython/memory/gc/base.py	(original)
+++ pypy/trunk/pypy/rpython/memory/gc/base.py	Fri Sep  3 13:39:11 2010
@@ -86,8 +86,7 @@
         addr -= self.gcheaderbuilder.size_gc_header
         return llmemory.cast_adr_to_ptr(addr, lltype.Ptr(self.HDR))
 
-    def get_size(self, obj):
-        typeid = self.get_type_id(obj)
+    def _get_size_for_typeid(self, obj, typeid):
         size = self.fixed_size(typeid)
         if self.is_varsize(typeid):
             lenaddr = obj + self.varsize_offset_to_length(typeid)
@@ -99,6 +98,9 @@
             # gctypelayout.encode_type_shape()
         return size
 
+    def get_size(self, obj):
+        return self._get_size_for_typeid(obj, self.get_type_id(obj))
+
     def malloc(self, typeid, length=0, zero=False):
         """For testing.  The interface used by the gctransformer is
         the four malloc_[fixed,var]size[_clear]() functions.
@@ -218,7 +220,6 @@
             pending = self._debug_pending
             while pending.non_empty():
                 obj = pending.pop()
-                self.debug_check_object(obj)
                 self.trace(obj, self._debug_callback2, None)
             self._debug_seen.delete()
             self._debug_pending.delete()
@@ -227,6 +228,7 @@
         seen = self._debug_seen
         if not seen.contains(obj):
             seen.add(obj)
+            self.debug_check_object(obj)
             self._debug_pending.append(obj)
     def _debug_callback(self, root):
         obj = root.address[0]
@@ -348,3 +350,23 @@
                         globals(), locals(), [classname])
     GCClass = getattr(module, classname)
     return GCClass, GCClass.TRANSLATION_PARAMS
+
+def read_from_env(varname):
+    import os
+    value = os.environ.get(varname)
+    if value:
+        realvalue = value[:-1]
+        if value[-1] in 'kK':
+            factor = 1024
+        elif value[-1] in 'mM':
+            factor = 1024*1024
+        elif value[-1] in 'gG':
+            factor = 1024*1024*1024
+        else:
+            factor = 1
+            realvalue = value
+        try:
+            return int(float(realvalue) * factor)
+        except ValueError:
+            pass
+    return -1

Modified: pypy/trunk/pypy/rpython/memory/gc/generation.py
==============================================================================
--- pypy/trunk/pypy/rpython/memory/gc/generation.py	(original)
+++ pypy/trunk/pypy/rpython/memory/gc/generation.py	Fri Sep  3 13:39:11 2010
@@ -2,6 +2,7 @@
 from pypy.rpython.memory.gc.semispace import SemiSpaceGC
 from pypy.rpython.memory.gc.semispace import GCFLAG_EXTERNAL, GCFLAG_FORWARDED
 from pypy.rpython.memory.gc.semispace import GC_HASH_TAKEN_ADDR
+from pypy.rpython.memory.gc.base import read_from_env
 from pypy.rpython.lltypesystem.llmemory import NULL, raw_malloc_usage
 from pypy.rpython.lltypesystem import lltype, llmemory, llarena
 from pypy.rpython.memory.support import DEFAULT_CHUNK_SIZE
@@ -625,18 +626,7 @@
 import os
 
 def nursery_size_from_env():
-    value = os.environ.get('PYPY_GENERATIONGC_NURSERY')
-    if value:
-        if value[-1] in 'kK':
-            factor = 1024
-            value = value[:-1]
-        else:
-            factor = 1
-        try:
-            return int(value) * factor
-        except ValueError:
-            pass
-    return -1
+    return read_from_env('PYPY_GENERATIONGC_NURSERY')
 
 def best_nursery_size_for_L2cache(L2cache):
     # Heuristically, the best nursery size to choose is about half

Modified: pypy/trunk/pypy/rpython/memory/gc/markcompact.py
==============================================================================
--- pypy/trunk/pypy/rpython/memory/gc/markcompact.py	(original)
+++ pypy/trunk/pypy/rpython/memory/gc/markcompact.py	Fri Sep  3 13:39:11 2010
@@ -1,27 +1,17 @@
-
-import time
-
 from pypy.rpython.lltypesystem import lltype, llmemory, llarena, llgroup
-from pypy.rpython.memory.gc.base import MovingGCBase
+from pypy.rpython.memory.gc.base import MovingGCBase, read_from_env
 from pypy.rlib.debug import ll_assert, have_debug_prints
 from pypy.rlib.debug import debug_print, debug_start, debug_stop
 from pypy.rpython.memory.support import DEFAULT_CHUNK_SIZE
 from pypy.rpython.memory.support import get_address_stack, get_address_deque
 from pypy.rpython.memory.support import AddressDict
 from pypy.rpython.lltypesystem.llmemory import NULL, raw_malloc_usage
-from pypy.rlib.rarithmetic import ovfcheck
+from pypy.rlib.rarithmetic import ovfcheck, LONG_BIT, intmask
 from pypy.rpython.lltypesystem.lloperation import llop
-from pypy.rlib.objectmodel import we_are_translated
+from pypy.rlib.objectmodel import we_are_translated, running_on_llinterp
 from pypy.rpython.lltypesystem import rffi
 from pypy.rpython.memory.gcheader import GCHeaderBuilder
 
-first_gcflag = 1 << 16
-GCFLAG_MARKBIT = first_gcflag << 0
-GCFLAG_HASHTAKEN = first_gcflag << 1      # someone already asked for the hash
-GCFLAG_HASHFIELD = first_gcflag << 2      # we have an extra hash field
-
-memoryError = MemoryError()
-
 # Mark'n'compact garbage collector
 #
 # main point of this GC is to save as much memory as possible
@@ -34,41 +24,44 @@
 # this gc works more or less like semispace, but has some essential
 # differencies. The main difference is that we have separate phases of
 # marking and assigning pointers, hence order of objects is preserved.
-# This means we can reuse the same space if it did not grow enough.
-# More importantly, in case we need to resize space we can copy it bit by
-# bit, hence avoiding double memory consumption at peak times
+# This means we can reuse the same space, overwriting it as we collect.
 
-# so the algorithm itself is performed in 3 stages (module weakrefs and
-# finalizers)
+# so the algorithm itself is performed in 3 stages (modulo weakrefs and
+# finalizers):
 
 # 1. We mark alive objects
 # 2. We walk all objects and assign forward pointers in the same order,
 #    also updating all references
-# 3. We compact the space by moving. In case we move to the same space,
-#    we use arena_new_view trick, which looks like new space to tests,
-#    but compiles to the same pointer. Also we use raw_memmove in case
-#    objects overlap.
-
-# Exact algorithm for space resizing: we keep allocated more space than needed
-# (2x, can be even more), but it's full of zeroes. After each collection,
-# we bump next_collect_after which is a marker where to start each collection.
-# It should be exponential (but less than 2) from the size occupied by objects
+# 3. We compact the space by moving.  We use 'arena_new_view' trick, which
+#    looks like new space to tests, but compiles to the same pointer.
+#    Also we use raw_memmove in case the object overlaps with its destination.
+
+# After each collection, we bump 'next_collect_after' which is a marker
+# where to start each collection.  It should be exponential (but less
+# than 2) from the size occupied by objects so far.
 
 # field optimization - we don't need forward pointer and flags at the same
-# time. Instead we copy list of tids when we know how many objects are alive
-# and store forward pointer there.
+# time. Instead we copy the TIDs in a list when we know how many objects are
+# alive, and store the forward pointer in the old object header.
 
+first_gcflag_bit = LONG_BIT//2
+first_gcflag = 1 << first_gcflag_bit
+GCFLAG_HASHTAKEN = first_gcflag << 0      # someone already asked for the hash
+GCFLAG_HASHFIELD = first_gcflag << 1      # we have an extra hash field
+# note that only the first 2 bits are preserved during a collection!
+GCFLAG_MARKBIT   = intmask(first_gcflag << (LONG_BIT//2-1))
+assert GCFLAG_MARKBIT < 0     # should be 0x80000000
+
+GCFLAG_SAVED_HASHTAKEN = GCFLAG_HASHTAKEN >> first_gcflag_bit
+GCFLAG_SAVED_HASHFIELD = GCFLAG_HASHFIELD >> first_gcflag_bit
 
-# in case we need to grow space, we use
-# current_space_size * FREE_SPACE_MULTIPLIER / FREE_SPACE_DIVIDER + needed
-FREE_SPACE_MULTIPLIER = 3
-FREE_SPACE_DIVIDER = 2
-FREE_SPACE_ADD = 256
-# XXX adjust
-GC_CLEARANCE = 32*1024
 
 TID_TYPE = llgroup.HALFWORD
 BYTES_PER_TID = rffi.sizeof(TID_TYPE)
+TID_BACKUP = rffi.CArray(TID_TYPE)
+
+def translated_to_c():
+    return we_are_translated() and not running_on_llinterp
 
 
 class MarkCompactGC(MovingGCBase):
@@ -77,37 +70,63 @@
     withhash_flag_is_in_field = 'tid', GCFLAG_HASHFIELD
     # ^^^ all prebuilt objects have GCFLAG_HASHTAKEN, but only some have
     #     GCFLAG_HASHFIELD (and then they are one word longer).
-    TID_BACKUP = lltype.Array(TID_TYPE, hints={'nolength':True})
-    WEAKREF_OFFSETS = lltype.Array(lltype.Signed)
 
+    # The default space size is 1.9375 GB, i.e. almost 2 GB, allocated as
+    # a big mmap.  The process does not actually consume that space until
+    # needed, of course.
+    TRANSLATION_PARAMS = {'space_size': int((1 + 15.0/16)*1024*1024*1024),
+                          'min_next_collect_after': 16*1024*1024}   # 16MB
 
-    TRANSLATION_PARAMS = {'space_size': 8*1024*1024} # XXX adjust
-
-    malloc_zero_filled = True
+    malloc_zero_filled = False
     inline_simple_malloc = True
     inline_simple_malloc_varsize = True
-    first_unused_gcflag = first_gcflag << 3
-    total_collection_time = 0.0
-    total_collection_count = 0
-
-    def __init__(self, config, chunk_size=DEFAULT_CHUNK_SIZE, space_size=4096):
-        import py; py.test.skip("Disabled for now, sorry")
-        self.param_space_size = space_size
+    #total_collection_time = 0.0
+    #total_collection_count = 0
+
+    free = NULL
+    next_collect_after = -1
+
+    def __init__(self, config, chunk_size=DEFAULT_CHUNK_SIZE, space_size=4096,
+                 min_next_collect_after=128):
         MovingGCBase.__init__(self, config, chunk_size)
+        self.space_size = space_size
+        self.min_next_collect_after = min_next_collect_after
 
-    def setup(self):
-        self.space_size = self.param_space_size
-        self.next_collect_after = self.param_space_size/2 # whatever...
+    def next_collection(self, used_space, num_objects_so_far, requested_size):
+        used_space += BYTES_PER_TID * num_objects_so_far
+        ll_assert(used_space <= self.space_size,
+                  "used_space + num_objects_so_far overflow")
+        try:
+            next = (used_space // 3) * 2 + requested_size
+        except OverflowError:
+            next = self.space_size
+        if next < self.min_next_collect_after:
+            next = self.min_next_collect_after
+        if next > self.space_size - used_space:
+            next = self.space_size - used_space
+        # The value we return guarantees that used_space + next <= space_size,
+        # with 'BYTES_PER_TID*num_objects_so_far' included in used_space.
+        # Normally, the value we return should also be at least requested_size
+        # unless we are out of memory.
+        return next
 
-        self.program_start_time = time.time()
-        self.space = llarena.arena_malloc(self.space_size, True)
-        ll_assert(bool(self.space), "couldn't allocate arena")
+    def setup(self):
+        envsize = read_from_env('PYPY_MARKCOMPACTGC_MAX')
+        if envsize >= 4096:
+            self.space_size = envsize & ~4095
+        mincollect = read_from_env('PYPY_MARKCOMPACTGC_MIN')
+        if mincollect >= 4096:
+            self.min_next_collect_after = mincollect
+
+        #self.program_start_time = time.time()
+        self.space = llarena.arena_malloc(self.space_size, False)
+        if not self.space:
+            raise CannotAllocateGCArena
         self.free = self.space
-        self.top_of_space = self.space + self.next_collect_after
         MovingGCBase.setup(self)
         self.objects_with_finalizers = self.AddressDeque()
-        self.objects_with_weakrefs = self.AddressStack()
-        self.tid_backup = lltype.nullptr(self.TID_BACKUP)
+        self.tid_backup = lltype.nullptr(TID_BACKUP)
+        self.next_collect_after = self.next_collection(0, 0, 0)
 
     def init_gc_object(self, addr, typeid16, flags=0):
         hdr = llmemory.cast_adr_to_ptr(addr, lltype.Ptr(self.HDR))
@@ -115,216 +134,204 @@
 
     def init_gc_object_immortal(self, addr, typeid16, flags=0):
         hdr = llmemory.cast_adr_to_ptr(addr, lltype.Ptr(self.HDR))
-        flags |= GCFLAG_HASHTAKEN
+        flags |= GCFLAG_HASHTAKEN | GCFLAG_MARKBIT
+        # All prebuilt GC objects have the GCFLAG_MARKBIT always set.
+        # That's convenient to make the GC always think that they
+        # survive the current collection.
         hdr.tid = self.combine(typeid16, flags)
-        # XXX we can store forward_ptr to itself, if we fix C backend
-        # so that get_forwarding_address(obj) returns
-        # obj itself if obj is a prebuilt object
 
-    def malloc_fixedsize_clear(self, typeid16, size, can_collect,
-                               has_finalizer=False, contains_weakptr=False):
-        size_gc_header = self.gcheaderbuilder.size_gc_header
-        totalsize = size_gc_header + size
-        result = self.free
-        if raw_malloc_usage(totalsize) > self.top_of_space - result:
-            result = self.obtain_free_space(totalsize)
+    def _get_memory(self, totalsize):
+        # also counts the space that will be needed during the following
+        # collection to store the TID
+        requested_size = raw_malloc_usage(totalsize) + BYTES_PER_TID
+        self.next_collect_after -= requested_size
+        if self.next_collect_after < 0:
+            result = self.obtain_free_space(requested_size)
+        else:
+            result = self.free
+        self.free += totalsize
         llarena.arena_reserve(result, totalsize)
+        return result
+    _get_memory._always_inline_ = True
+
+    def _get_totalsize_var(self, nonvarsize, itemsize, length):
+        try:
+            varsize = ovfcheck(itemsize * length)
+        except OverflowError:
+            raise MemoryError
+        # Careful to detect overflows.  The following works even if varsize
+        # is almost equal to sys.maxint; morever, self.space_size is known
+        # to be at least 4095 bytes smaller than sys.maxint, so this function
+        # always raises instead of returning an integer >= sys.maxint-4095.
+        if (raw_malloc_usage(varsize) > self.space_size -
+                                        raw_malloc_usage(nonvarsize)):
+            raise MemoryError
+        return llarena.round_up_for_allocation(nonvarsize + varsize)
+    _get_totalsize_var._always_inline_ = True
+
+    def _setup_object(self, result, typeid16, has_finalizer):
+        size_gc_header = self.gcheaderbuilder.size_gc_header
         self.init_gc_object(result, typeid16)
-        self.free += totalsize
         if has_finalizer:
             self.objects_with_finalizers.append(result + size_gc_header)
-        if contains_weakptr:
-            self.objects_with_weakrefs.append(result + size_gc_header)
         return llmemory.cast_adr_to_ptr(result+size_gc_header, llmemory.GCREF)
-    
+    _setup_object._always_inline_ = True
+
+    def malloc_fixedsize(self, typeid16, size, can_collect,
+                         has_finalizer=False, contains_weakptr=False):
+        size_gc_header = self.gcheaderbuilder.size_gc_header
+        totalsize = size_gc_header + size
+        result = self._get_memory(totalsize)
+        return self._setup_object(result, typeid16, has_finalizer)
+
+    def malloc_fixedsize_clear(self, typeid16, size, can_collect,
+                               has_finalizer=False, contains_weakptr=False):
+        size_gc_header = self.gcheaderbuilder.size_gc_header
+        totalsize = size_gc_header + size
+        result = self._get_memory(totalsize)
+        llmemory.raw_memclear(result, totalsize)
+        return self._setup_object(result, typeid16, has_finalizer)
+
     def malloc_varsize_clear(self, typeid16, length, size, itemsize,
                              offset_to_length, can_collect):
         size_gc_header = self.gcheaderbuilder.size_gc_header
         nonvarsize = size_gc_header + size
-        try:
-            varsize = ovfcheck(itemsize * length)
-            totalsize = ovfcheck(nonvarsize + varsize)
-        except OverflowError:
-            raise memoryError
-        result = self.free
-        if raw_malloc_usage(totalsize) > self.top_of_space - result:
-            result = self.obtain_free_space(totalsize)
-        llarena.arena_reserve(result, totalsize)
-        self.init_gc_object(result, typeid16)
+        totalsize = self._get_totalsize_var(nonvarsize, itemsize, length)
+        result = self._get_memory(totalsize)
+        llmemory.raw_memclear(result, totalsize)
         (result + size_gc_header + offset_to_length).signed[0] = length
-        self.free = result + llarena.round_up_for_allocation(totalsize)
-        return llmemory.cast_adr_to_ptr(result+size_gc_header, llmemory.GCREF)
+        return self._setup_object(result, typeid16, False)
 
-    def obtain_free_space(self, totalsize):
-        # a bit of tweaking to maximize the performance and minimize the
-        # amount of code in an inlined version of malloc_fixedsize_clear()
-        if not self.try_obtain_free_space(totalsize):
-            raise memoryError
+    def obtain_free_space(self, requested_size):
+        if self.free == NULL:
+            return self._emergency_initial_block(requested_size)
+        while True:
+            executed_some_finalizers = self.markcompactcollect(requested_size)
+            self.next_collect_after -= requested_size
+            if self.next_collect_after >= 0:
+                break    # ok
+            else:
+                if executed_some_finalizers:
+                    pass   # try again to do a collection
+                else:
+                    raise MemoryError
         return self.free
     obtain_free_space._dont_inline_ = True
 
-    def try_obtain_free_space(self, needed):
-        needed = raw_malloc_usage(needed)
-        while 1:
-            self.markcompactcollect(needed)
-            missing = needed - (self.top_of_space - self.free)
-            if missing < 0:
-                return True
-
-    def new_space_size(self, occupied, needed):
-        res = (occupied * FREE_SPACE_MULTIPLIER /
-               FREE_SPACE_DIVIDER + FREE_SPACE_ADD + needed)
-        # align it to 4096, which is somewhat around page size
-        return ((res/4096) + 1) * 4096
-
-    def double_space_size(self, minimal_size):
-        while self.space_size <= minimal_size:
-            self.space_size *= 2
-        toaddr = llarena.arena_malloc(self.space_size, True)
-        return toaddr
-
-    def compute_alive_objects(self):
-        fromaddr = self.space
-        addraftercollect = self.space
-        num = 1
-        while fromaddr < self.free:
-            size_gc_header = self.gcheaderbuilder.size_gc_header
-            tid = llmemory.cast_adr_to_ptr(fromaddr, lltype.Ptr(self.HDR)).tid
-            obj = fromaddr + size_gc_header
-            objsize = self.get_size(obj)
-            objtotalsize = size_gc_header + objsize
-            if self.marked(obj):
-                copy_has_hash_field = ((tid & GCFLAG_HASHFIELD) != 0 or
-                                       ((tid & GCFLAG_HASHTAKEN) != 0 and
-                                        addraftercollect < fromaddr))
-                addraftercollect += raw_malloc_usage(objtotalsize)
-                if copy_has_hash_field:
-                    addraftercollect += llmemory.sizeof(lltype.Signed)
-            num += 1
-            fromaddr += objtotalsize
-            if tid & GCFLAG_HASHFIELD:
-                fromaddr += llmemory.sizeof(lltype.Signed)
-        ll_assert(addraftercollect <= fromaddr,
-                  "markcompactcollect() is trying to increase memory usage")
-        self.totalsize_of_objs = addraftercollect - self.space
-        return num
+    def _emergency_initial_block(self, requested_size):
+        # xxx before the GC is fully setup, we might get there.  Hopefully
+        # we will only allocate a couple of strings, e.g. in read_from_env().
+        # Just allocate them raw and leak them.
+        debug_start("gc-initial-block")
+        debug_print("leaking", requested_size, "bytes")
+        debug_stop("gc-initial-block")
+        return llmemory.raw_malloc(requested_size)
 
     def collect(self, gen=0):
         self.markcompactcollect()
-    
-    def markcompactcollect(self, needed=0):
-        start_time = self.debug_collect_start()
+
+    def markcompactcollect(self, requested_size=0):
+        self.debug_collect_start(requested_size)
         self.debug_check_consistency()
-        self.to_see = self.AddressStack()
-        self.mark_roots_recursively()
-        if (self.objects_with_finalizers.non_empty() or
-            self.run_finalizers.non_empty()):
-            self.mark_objects_with_finalizers()
-            self._trace_and_mark()
+        #
+        # Mark alive objects
+        #
+        self.to_see = self.AddressDeque()
+        self.trace_from_roots()
         self.to_see.delete()
-        num_of_alive_objs = self.compute_alive_objects()
-        size_of_alive_objs = self.totalsize_of_objs
-        totalsize = self.new_space_size(size_of_alive_objs, needed +
-                                        num_of_alive_objs * BYTES_PER_TID)
-        tid_backup_size = (llmemory.sizeof(self.TID_BACKUP, 0) +
-                           llmemory.sizeof(TID_TYPE) * num_of_alive_objs)
-        used_space_now = self.next_collect_after + raw_malloc_usage(tid_backup_size)
-        if totalsize >= self.space_size or used_space_now >= self.space_size:
-            toaddr = self.double_space_size(totalsize)
-            llarena.arena_reserve(toaddr + size_of_alive_objs, tid_backup_size)
-            self.tid_backup = llmemory.cast_adr_to_ptr(
-                toaddr + size_of_alive_objs,
-                lltype.Ptr(self.TID_BACKUP))
-            resizing = True
-        else:
-            toaddr = llarena.arena_new_view(self.space)
-            llarena.arena_reserve(self.top_of_space, tid_backup_size)
-            self.tid_backup = llmemory.cast_adr_to_ptr(
-                self.top_of_space,
-                lltype.Ptr(self.TID_BACKUP))
-            resizing = False
-        self.next_collect_after = totalsize
-        weakref_offsets = self.collect_weakref_offsets()
-        finaladdr = self.update_forward_pointers(toaddr, num_of_alive_objs)
+        #
+        # Prepare new views on the same memory
+        #
+        toaddr = llarena.arena_new_view(self.space)
+        maxnum = self.space_size - (self.free - self.space)
+        maxnum /= BYTES_PER_TID
+        llarena.arena_reserve(self.free, llmemory.sizeof(TID_BACKUP, maxnum))
+        self.tid_backup = llmemory.cast_adr_to_ptr(self.free,
+                                                   lltype.Ptr(TID_BACKUP))
+        #
+        # Walk all objects and assign forward pointers in the same order,
+        # also updating all references
+        #
+        self.update_forward_pointers(toaddr, maxnum)
         if (self.run_finalizers.non_empty() or
             self.objects_with_finalizers.non_empty()):
             self.update_run_finalizers()
-        if self.objects_with_weakrefs.non_empty():
-            self.invalidate_weakrefs(weakref_offsets)
+
         self.update_objects_with_id()
-        self.compact(resizing)
-        if not resizing:
-            size = toaddr + self.space_size - finaladdr
-            llarena.arena_reset(finaladdr, size, True)
-        else:
-            if we_are_translated():
-                # because we free stuff already in raw_memmove, we
-                # would get double free here. Let's free it anyway
-                llarena.arena_free(self.space)
-            llarena.arena_reset(toaddr + size_of_alive_objs, tid_backup_size,
-                                True)
-        self.space        = toaddr
-        self.free         = finaladdr
-        self.top_of_space = toaddr + self.next_collect_after
+        self.compact()
+        #
+        self.tid_backup = lltype.nullptr(TID_BACKUP)
+        self.free = self.finaladdr
+        self.next_collect_after = self.next_collection(self.finaladdr - toaddr,
+                                                       self.num_alive_objs,
+                                                       requested_size)
+        #
+        if not translated_to_c():
+            remaining_size = (toaddr + self.space_size) - self.finaladdr
+            llarena.arena_reset(self.finaladdr, remaining_size, False)
+            llarena.arena_free(self.space)
+            self.space = toaddr
+        #
         self.debug_check_consistency()
-        self.tid_backup = lltype.nullptr(self.TID_BACKUP)
+        self.debug_collect_finish()
+        if self.next_collect_after < 0:
+            raise MemoryError
+        #
         if self.run_finalizers.non_empty():
             self.execute_finalizers()
-        self.debug_collect_finish(start_time)
-        
-    def collect_weakref_offsets(self):
-        weakrefs = self.objects_with_weakrefs
-        new_weakrefs = self.AddressStack()
-        weakref_offsets = lltype.malloc(self.WEAKREF_OFFSETS,
-                                        weakrefs.length(), flavor='raw')
-        i = 0
-        while weakrefs.non_empty():
-            obj = weakrefs.pop()
-            offset = self.weakpointer_offset(self.get_type_id(obj))
-            weakref_offsets[i] = offset
-            new_weakrefs.append(obj)
-            i += 1
-        self.objects_with_weakrefs = new_weakrefs
-        weakrefs.delete()
-        return weakref_offsets
+            return True      # executed some finalizers
+        else:
+            return False     # no finalizer executed
 
-    def debug_collect_start(self):
-        if have_debug_prints():
+    def debug_collect_start(self, requested_size):
+        if 1:# have_debug_prints():
             debug_start("gc-collect")
             debug_print()
-            debug_print(".----------- Full collection ------------------")
-            start_time = time.time()
-            return start_time
-        return -1
-
-    def debug_collect_finish(self, start_time):
-        if start_time != -1:
-            end_time = time.time()
-            elapsed_time = end_time - start_time
-            self.total_collection_time += elapsed_time
-            self.total_collection_count += 1
-            total_program_time = end_time - self.program_start_time
-            ct = self.total_collection_time
-            cc = self.total_collection_count
-            debug_print("| number of collections so far       ", 
-                        cc)
-            debug_print("| total collections per second:      ",
-                        cc / total_program_time)
-            debug_print("| total time in markcompact-collect: ",
-                        ct, "seconds")
-            debug_print("| percentage collection<->total time:",
-                        ct * 100.0 / total_program_time, "%")
+            debug_print(".----------- Full collection -------------------")
+            debug_print("| requested size:",
+                        requested_size)
+            #start_time = time.time()
+            #return start_time
+        #return -1
+
+    def debug_collect_finish(self):
+        if 1:# start_time != -1:
+            #end_time = time.time()
+            #elapsed_time = end_time - start_time
+            #self.total_collection_time += elapsed_time
+            #self.total_collection_count += 1
+            #total_program_time = end_time - self.program_start_time
+            #ct = self.total_collection_time
+            #cc = self.total_collection_count
+            #debug_print("| number of collections so far       ", 
+            #            cc)
+            debug_print("| total space size                   ", 
+                        self.space_size)
+            debug_print("| number of objects alive            ", 
+                        self.num_alive_objs)
+            debug_print("| used space size                    ", 
+                        self.free - self.space)
+            debug_print("| next collection after              ", 
+                        self.next_collect_after)
+            #debug_print("| total collections per second:      ",
+            #            cc / total_program_time)
+            #debug_print("| total time in markcompact-collect: ",
+            #            ct, "seconds")
+            #debug_print("| percentage collection<->total time:",
+            #            ct * 100.0 / total_program_time, "%")
             debug_print("`----------------------------------------------")
             debug_stop("gc-collect")
 
 
     def update_run_finalizers(self):
-        run_finalizers = self.AddressDeque()
-        while self.run_finalizers.non_empty():
-            obj = self.run_finalizers.popleft()
-            run_finalizers.append(self.get_forwarding_address(obj))
-        self.run_finalizers.delete()
-        self.run_finalizers = run_finalizers
+        if self.run_finalizers.non_empty():     # uncommon case
+            run_finalizers = self.AddressDeque()
+            while self.run_finalizers.non_empty():
+                obj = self.run_finalizers.popleft()
+                run_finalizers.append(self.get_forwarding_address(obj))
+            self.run_finalizers.delete()
+            self.run_finalizers = run_finalizers
+        #
         objects_with_finalizers = self.AddressDeque()
         while self.objects_with_finalizers.non_empty():
             obj = self.objects_with_finalizers.popleft()
@@ -353,90 +360,156 @@
         tid = self.header(addr).tid
         return llop.extract_ushort(llgroup.HALFWORD, tid)
 
-    def mark_roots_recursively(self):
+    def trace_from_roots(self):
         self.root_walker.walk_roots(
-            MarkCompactGC._mark_root_recursively,  # stack roots
-            MarkCompactGC._mark_root_recursively,  # static in prebuilt non-gc structures
-            MarkCompactGC._mark_root_recursively)  # static in prebuilt gc objects
+            MarkCompactGC._mark_root,  # stack roots
+            MarkCompactGC._mark_root,  # static in prebuilt non-gc structures
+            MarkCompactGC._mark_root)  # static in prebuilt gc objects
+        if (self.objects_with_finalizers.non_empty() or
+            self.run_finalizers.non_empty()):
+            self.trace_from_objects_with_finalizers()
         self._trace_and_mark()
 
     def _trace_and_mark(self):
-        # XXX depth-first tracing... it can consume a lot of rawmalloced
-        # memory for very long stacks in some cases
         while self.to_see.non_empty():
-            obj = self.to_see.pop()
+            obj = self.to_see.popleft()
             self.trace(obj, self._mark_obj, None)
 
     def _mark_obj(self, pointer, ignored):
-        obj = pointer.address[0]
-        if self.marked(obj):
-            return
-        self.mark(obj)
-        self.to_see.append(obj)
+        self.mark(pointer.address[0])
 
-    def _mark_root_recursively(self, root):
+    def _mark_root(self, root):
         self.mark(root.address[0])
-        self.to_see.append(root.address[0])
 
     def mark(self, obj):
-        self.header(obj).tid |= GCFLAG_MARKBIT
+        if not self.marked(obj):
+            self.header(obj).tid |= GCFLAG_MARKBIT
+            self.to_see.append(obj)
 
     def marked(self, obj):
-        return self.header(obj).tid & GCFLAG_MARKBIT
+        # should work both if tid contains a CombinedSymbolic (for dying
+        # objects, at this point), or a plain integer.
+        return MovingGCBase.header(self, obj).tid & GCFLAG_MARKBIT
+
+    def toaddr_smaller_than_fromaddr(self, toaddr, fromaddr):
+        if translated_to_c():
+            return toaddr < fromaddr
+        else:
+            # convert the addresses to integers, because they are
+            # theoretically not from the same arena
+            return toaddr - self.base_forwarding_addr < fromaddr - self.space
 
-    def update_forward_pointers(self, toaddr, num_of_alive_objs):
-        self.base_forwarding_addr = toaddr
+    def update_forward_pointers(self, toaddr, maxnum):
+        self.base_forwarding_addr = base_forwarding_addr = toaddr
         fromaddr = self.space
         size_gc_header = self.gcheaderbuilder.size_gc_header
-        i = 0
+        num = 0
         while fromaddr < self.free:
             hdr = llmemory.cast_adr_to_ptr(fromaddr, lltype.Ptr(self.HDR))
             obj = fromaddr + size_gc_header
-            objsize = self.get_size(obj)
-            totalsize = size_gc_header + objsize
-            if not self.marked(obj):
-                self.set_null_forwarding_address(obj, i)
-            else:
-                llarena.arena_reserve(toaddr, totalsize)
-                self.set_forwarding_address(obj, toaddr, i)
-                toaddr += totalsize
-            i += 1
-            fromaddr += totalsize
+            # compute the original object size, including the
+            # optional hash field
+            basesize = size_gc_header + self.get_size(obj)
+            totalsrcsize = basesize
+            if hdr.tid & GCFLAG_HASHFIELD:  # already a hash field, copy it too
+                totalsrcsize += llmemory.sizeof(lltype.Signed)
+            #
+            if self.marked(obj):
+                # the object is marked as suriving.  Compute the new object
+                # size
+                totaldstsize = totalsrcsize
+                if (hdr.tid & (GCFLAG_HASHTAKEN|GCFLAG_HASHFIELD) ==
+                               GCFLAG_HASHTAKEN):
+                    # grow a new hash field -- with the exception: if
+                    # the object actually doesn't move, don't
+                    # (otherwise, we get a bogus toaddr > fromaddr)
+                    if self.toaddr_smaller_than_fromaddr(toaddr, fromaddr):
+                        totaldstsize += llmemory.sizeof(lltype.Signed)
+                #
+                if not translated_to_c():
+                    llarena.arena_reserve(toaddr, basesize)
+                    if (raw_malloc_usage(totaldstsize) >
+                        raw_malloc_usage(basesize)):
+                        llarena.arena_reserve(toaddr + basesize,
+                                              llmemory.sizeof(lltype.Signed))
+                #
+                # save the field hdr.tid in the array tid_backup
+                ll_assert(num < maxnum, "overflow of the tid_backup table")
+                self.tid_backup[num] = self.get_type_id(obj)
+                num += 1
+                # compute forward_offset, the offset to the future copy
+                # of this object
+                forward_offset = toaddr - base_forwarding_addr
+                # copy the first two gc flags in forward_offset
+                ll_assert(forward_offset & 3 == 0, "misalignment!")
+                forward_offset |= (hdr.tid >> first_gcflag_bit) & 3
+                hdr.tid = forward_offset | GCFLAG_MARKBIT
+                ll_assert(self.marked(obj), "re-marking object failed!")
+                # done
+                toaddr += totaldstsize
+            #
+            fromaddr += totalsrcsize
+            if not translated_to_c():
+                assert toaddr - base_forwarding_addr <= fromaddr - self.space
+        self.num_alive_objs = num
+        self.finaladdr = toaddr
 
         # now update references
         self.root_walker.walk_roots(
-            MarkCompactGC._update_root,  # stack roots
-            MarkCompactGC._update_root,  # static in prebuilt non-gc structures
-            MarkCompactGC._update_root)  # static in prebuilt gc objects
+            MarkCompactGC._update_ref,  # stack roots
+            MarkCompactGC._update_ref,  # static in prebuilt non-gc structures
+            MarkCompactGC._update_ref)  # static in prebuilt gc objects
+        self.walk_marked_objects(MarkCompactGC.trace_and_update_ref)
+
+    def walk_marked_objects(self, callback):
+        num = 0
+        size_gc_header = self.gcheaderbuilder.size_gc_header
         fromaddr = self.space
-        i = 0
+        toaddr = self.base_forwarding_addr
         while fromaddr < self.free:
             hdr = llmemory.cast_adr_to_ptr(fromaddr, lltype.Ptr(self.HDR))
             obj = fromaddr + size_gc_header
-            objsize = self.get_size_from_backup(obj, i)
-            totalsize = size_gc_header + objsize
-            if not self.surviving(obj):
-                pass
+            survives = self.marked(obj)
+            if survives:
+                typeid = self.get_typeid_from_backup(num)
+                num += 1
             else:
-                self.trace_with_backup(obj, self._update_ref, i)
-            fromaddr += totalsize
-            i += 1
-        return toaddr
+                typeid = self.get_type_id(obj)
+            baseobjsize = self._get_size_for_typeid(obj, typeid)
+            basesize = size_gc_header + baseobjsize
+            totalsrcsize = basesize
+            #
+            if survives:
+                grow_hash_field = False
+                if hdr.tid & GCFLAG_SAVED_HASHFIELD:
+                    totalsrcsize += llmemory.sizeof(lltype.Signed)
+                totaldstsize = totalsrcsize
+                if (hdr.tid & (GCFLAG_SAVED_HASHTAKEN|GCFLAG_SAVED_HASHFIELD)
+                            == GCFLAG_SAVED_HASHTAKEN):
+                    if self.toaddr_smaller_than_fromaddr(toaddr, fromaddr):
+                        grow_hash_field = True
+                        totaldstsize += llmemory.sizeof(lltype.Signed)
+                callback(self, obj, typeid, basesize, toaddr, grow_hash_field)
+                toaddr += totaldstsize
+            else:
+                if hdr.tid & GCFLAG_HASHFIELD:
+                    totalsrcsize += llmemory.sizeof(lltype.Signed)
+            #
+            fromaddr += totalsrcsize
+    walk_marked_objects._annspecialcase_ = 'specialize:arg(1)'
 
-    def trace_with_backup(self, obj, callback, arg):
+    def trace_and_update_ref(self, obj, typeid, _1, _2, _3):
         """Enumerate the locations inside the given obj that can contain
         GC pointers.  For each such location, callback(pointer, arg) is
         called, where 'pointer' is an address inside the object.
         Typically, 'callback' is a bound method and 'arg' can be None.
         """
-        typeid = self.get_typeid_from_backup(arg)
         if self.is_gcarrayofgcptr(typeid):
             # a performance shortcut for GcArray(gcptr)
             length = (obj + llmemory.gcarrayofptr_lengthoffset).signed[0]
             item = obj + llmemory.gcarrayofptr_itemsoffset
             while length > 0:
-                if self.points_to_valid_gc_object(item):
-                    callback(item, arg)
+                self._update_ref(item)
                 item += llmemory.gcarrayofptr_singleitemoffset
                 length -= 1
             return
@@ -444,8 +517,7 @@
         i = 0
         while i < len(offsets):
             item = obj + offsets[i]
-            if self.points_to_valid_gc_object(item):
-                callback(item, arg)
+            self._update_ref(item)
             i += 1
         if self.has_gcptr_in_varsize(typeid):
             item = obj + self.varsize_offset_to_variable_part(typeid)
@@ -456,171 +528,122 @@
                 j = 0
                 while j < len(offsets):
                     itemobj = item + offsets[j]
-                    if self.points_to_valid_gc_object(itemobj):
-                        callback(itemobj, arg)
+                    self._update_ref(itemobj)
                     j += 1
                 item += itemlength
                 length -= 1
-    trace_with_backup._annspecialcase_ = 'specialize:arg(2)'
-
-    def _update_root(self, pointer):
-        if pointer.address[0] != NULL:
-            pointer.address[0] = self.get_forwarding_address(pointer.address[0])
-
-    def _update_ref(self, pointer, ignore):
-        if pointer.address[0] != NULL:
-            pointer.address[0] = self.get_forwarding_address(pointer.address[0])
+        else:
+            weakofs = self.weakpointer_offset(typeid)
+            if weakofs >= 0:
+                self._update_weakref(obj + weakofs)
+
+    def _update_ref(self, pointer):
+        if self.points_to_valid_gc_object(pointer):
+            pointer.address[0] = self.get_forwarding_address(
+                pointer.address[0])
+
+    def _update_weakref(self, pointer):
+        # either update the weak pointer's destination, or
+        # if it dies, write a NULL
+        if self.points_to_valid_gc_object(pointer):
+            if self.marked(pointer.address[0]):
+                pointer.address[0] = self.get_forwarding_address(
+                    pointer.address[0])
+            else:
+                pointer.address[0] = NULL
 
     def _is_external(self, obj):
-        return not (self.space <= obj < self.top_of_space)
+        return not (self.space <= obj < self.free)
 
     def get_forwarding_address(self, obj):
         if self._is_external(obj):
             return obj
         return self.get_header_forwarded_addr(obj)
 
-    def set_null_forwarding_address(self, obj, num):
-        self.backup_typeid(num, obj)
-        hdr = self.header(obj)
-        hdr.tid = -1          # make the object forwarded to NULL
-
-    def set_forwarding_address(self, obj, newobjhdr, num):
-        self.backup_typeid(num, obj)
-        forward_offset = newobjhdr - self.base_forwarding_addr
-        hdr = self.header(obj)
-        hdr.tid = forward_offset     # make the object forwarded to newobj
-
-    def restore_normal_header(self, obj, num):
-        # Reverse of set_forwarding_address().
-        typeid16 = self.get_typeid_from_backup(num)
-        hdr = self.header_forwarded(obj)
-        hdr.tid = self.combine(typeid16, 0)      # restore the normal header
-
     def get_header_forwarded_addr(self, obj):
-        return (self.base_forwarding_addr +
-                self.header_forwarded(obj).tid +
-                self.gcheaderbuilder.size_gc_header)
+        tid = self.header_forwarded(obj).tid
+        ll_assert(tid & GCFLAG_MARKBIT != 0, "dying object is not forwarded")
+        GCFLAG_MASK = ~(GCFLAG_MARKBIT | 3)
+        res = (self.base_forwarding_addr + (tid & GCFLAG_MASK) +
+               self.gcheaderbuilder.size_gc_header)
+        ll_assert(res < self.finaladdr, "forwarded address >= self.finaladdr")
+        return res
 
     def surviving(self, obj):
-        return self._is_external(obj) or self.header_forwarded(obj).tid != -1
-
-    def backup_typeid(self, num, obj):
-        self.tid_backup[num] = self.get_type_id(obj)
+        return self.marked(obj)
 
     def get_typeid_from_backup(self, num):
         return self.tid_backup[num]
 
-    def get_size_from_backup(self, obj, num):
-        typeid = self.get_typeid_from_backup(num)
-        size = self.fixed_size(typeid)
-        if self.is_varsize(typeid):
-            lenaddr = obj + self.varsize_offset_to_length(typeid)
-            length = lenaddr.signed[0]
-            size += length * self.varsize_item_sizes(typeid)
-            size = llarena.round_up_for_allocation(size)
-            # XXX maybe we should parametrize round_up_for_allocation()
-            # per GC; if we do, we also need to fix the call in
-            # gctypelayout.encode_type_shape()
-        return size
+    def compact(self):
+        self.walk_marked_objects(MarkCompactGC.copy_and_compact)
 
-    def compact(self, resizing):
-        fromaddr = self.space
-        size_gc_header = self.gcheaderbuilder.size_gc_header
-        start = fromaddr
-        end = fromaddr
-        num = 0
-        while fromaddr < self.free:
-            obj = fromaddr + size_gc_header
-            objsize = self.get_size_from_backup(obj, num)
-            totalsize = size_gc_header + objsize
-            if not self.surviving(obj): 
-                # this object dies. Following line is a noop in C,
-                # we clear it to make debugging easier
-                llarena.arena_reset(fromaddr, totalsize, False)
-            else:
-                if resizing:
-                    end = fromaddr
-                forward_obj = self.get_header_forwarded_addr(obj)
-                self.restore_normal_header(obj, num)
-                if obj != forward_obj:
-                    #llop.debug_print(lltype.Void, "Copying from to",
-                    #                 fromaddr, forward_ptr, totalsize)
-                    llmemory.raw_memmove(fromaddr,
-                                         forward_obj - size_gc_header,
-                                         totalsize)
-                if resizing and end - start > GC_CLEARANCE:
-                    diff = end - start
-                    #llop.debug_print(lltype.Void, "Cleaning", start, diff)
-                    diff = (diff / GC_CLEARANCE) * GC_CLEARANCE
-                    #llop.debug_print(lltype.Void, "Cleaning", start, diff)
-                    end = start + diff
-                    if we_are_translated():
-                        # XXX wuaaaaa.... those objects are freed incorrectly
-                        #                 here in case of test_gc
-                        llarena.arena_reset(start, diff, True)
-                    start += diff
-            num += 1
-            fromaddr += totalsize
+    def copy_and_compact(self, obj, typeid, basesize, toaddr, grow_hash_field):
+        # 'basesize' is the size without any hash field
+        # restore the normal header
+        hdr = self.header_forwarded(obj)
+        gcflags = hdr.tid & 3
+        if grow_hash_field:
+            gcflags |= GCFLAG_SAVED_HASHFIELD
+            hashvalue = self.get_identityhash_from_addr(obj)
+        elif gcflags & GCFLAG_SAVED_HASHFIELD:
+            fromaddr = llarena.getfakearenaaddress(obj)
+            fromaddr -= self.gcheaderbuilder.size_gc_header
+            hashvalue = (fromaddr + basesize).signed[0]
+        else:
+            hashvalue = 0     # not used
+        #
+        hdr.tid = self.combine(typeid, gcflags << first_gcflag_bit)
+        #
+        fromaddr = obj - self.gcheaderbuilder.size_gc_header
+        if translated_to_c():
+            llmemory.raw_memmove(fromaddr, toaddr, basesize)
+        else:
+            llmemory.raw_memcopy(fromaddr, toaddr, basesize)
+        #
+        if gcflags & GCFLAG_SAVED_HASHFIELD:
+            (toaddr + basesize).signed[0] = hashvalue
 
     def debug_check_object(self, obj):
-        # not sure what to check here
-        pass
-
-    def mark_objects_with_finalizers(self):
+        type_id = self.get_type_id(obj)
+        self.has_gcptr_in_varsize(type_id)   # checks that the type_id is valid
+        #
+        tid = self.header(obj).tid
+        if self._is_external(obj):
+            # All external objects have GCFLAG_MARKBIT and GCFLAG_HASHTAKEN
+            # set.
+            assert tid & GCFLAG_MARKBIT
+            assert tid & GCFLAG_HASHTAKEN
+        else:
+            # Non-external objects have GCFLAG_MARKBIT that should not be set
+            # at the very start or at the very end of a collection -- only
+            # temporarily during the collection.
+            assert tid & GCFLAG_MARKBIT == 0
+
+    def trace_from_objects_with_finalizers(self):
+        if self.run_finalizers.non_empty():   # uncommon case
+            new_run_finalizers = self.AddressDeque()
+            while self.run_finalizers.non_empty():
+                x = self.run_finalizers.popleft()
+                self.mark(x)
+                new_run_finalizers.append(x)
+            self.run_finalizers.delete()
+            self.run_finalizers = new_run_finalizers
+        #
+        # xxx we get to run the finalizers in a random order
+        self._trace_and_mark()
         new_with_finalizers = self.AddressDeque()
-        run_finalizers = self.run_finalizers
-        new_run_finalizers = self.AddressDeque()
-        while run_finalizers.non_empty():
-            x = run_finalizers.popleft()
-            self.mark(x)
-            self.to_see.append(x)
-            new_run_finalizers.append(x)
-        run_finalizers.delete()
-        self.run_finalizers = new_run_finalizers
         while self.objects_with_finalizers.non_empty():
             x = self.objects_with_finalizers.popleft()
             if self.marked(x):
                 new_with_finalizers.append(x)
             else:
-                new_run_finalizers.append(x)
+                self.run_finalizers.append(x)
                 self.mark(x)
-                self.to_see.append(x)
+                self._trace_and_mark()
         self.objects_with_finalizers.delete()
         self.objects_with_finalizers = new_with_finalizers
 
-    def invalidate_weakrefs(self, weakref_offsets):
-        # walk over list of objects that contain weakrefs
-        # if the object it references survives then update the weakref
-        # otherwise invalidate the weakref
-        new_with_weakref = self.AddressStack()
-        i = 0
-        while self.objects_with_weakrefs.non_empty():
-            obj = self.objects_with_weakrefs.pop()
-            if not self.surviving(obj):
-                continue # weakref itself dies
-            newobj = self.get_forwarding_address(obj)
-            offset = weakref_offsets[i]
-            pointing_to = (obj + offset).address[0]
-            # XXX I think that pointing_to cannot be NULL here
-            if pointing_to:
-                if self.surviving(pointing_to):
-                    (obj + offset).address[0] = self.get_forwarding_address(
-                        pointing_to)
-                    new_with_weakref.append(newobj)
-                else:
-                    (obj + offset).address[0] = NULL
-            i += 1
-        self.objects_with_weakrefs.delete()
-        self.objects_with_weakrefs = new_with_weakref
-        lltype.free(weakref_offsets, flavor='raw')
-
-    def get_size_incl_hash(self, obj):
-        size = self.get_size(obj)
-        hdr = self.header(obj)
-        if hdr.tid & GCFLAG_HASHFIELD:
-            size += llmemory.sizeof(lltype.Signed)
-        return size
-
     def identityhash(self, gcobj):
         # Unlike SemiSpaceGC.identityhash(), this function does not have
         # to care about reducing top_of_space.  The reason is as
@@ -635,8 +658,23 @@
         hdr = self.header(obj)
         #
         if hdr.tid & GCFLAG_HASHFIELD:  # the hash is in a field at the end
-            obj += self.get_size(obj)
+            obj = llarena.getfakearenaaddress(obj) + self.get_size(obj)
             return obj.signed[0]
         #
         hdr.tid |= GCFLAG_HASHTAKEN
-        return llmemory.cast_adr_to_int(obj)  # direct case
+        return self.get_identityhash_from_addr(obj)
+
+    def get_identityhash_from_addr(self, obj):
+        if translated_to_c():
+            return llmemory.cast_adr_to_int(obj)  # direct case
+        else:
+            try:
+                adr = llarena.getfakearenaaddress(obj)   # -> arena address
+            except RuntimeError:
+                return llmemory.cast_adr_to_int(obj)  # not in an arena...
+            return adr - self.space
+
+# ____________________________________________________________
+
+class CannotAllocateGCArena(Exception):
+    pass

Modified: pypy/trunk/pypy/rpython/memory/gc/test/test_direct.py
==============================================================================
--- pypy/trunk/pypy/rpython/memory/gc/test/test_direct.py	(original)
+++ pypy/trunk/pypy/rpython/memory/gc/test/test_direct.py	Fri Sep  3 13:39:11 2010
@@ -67,7 +67,10 @@
         from pypy.config.pypyoption import get_pypy_config
         config = get_pypy_config(translating=True).translation
         self.stackroots = []
-        self.gc = self.GCClass(config, **self.GC_PARAMS)
+        GC_PARAMS = self.GC_PARAMS.copy()
+        if hasattr(meth, 'GC_PARAMS'):
+            GC_PARAMS.update(meth.GC_PARAMS)
+        self.gc = self.GCClass(config, **GC_PARAMS)
         self.gc.DEBUG = True
         self.rootwalker = DirectRootWalker(self)
         self.gc.set_root_walker(self.rootwalker)
@@ -96,7 +99,7 @@
         p[index] = newvalue
 
     def malloc(self, TYPE, n=None):
-        addr = self.gc.malloc(self.get_type_id(TYPE), n)
+        addr = self.gc.malloc(self.get_type_id(TYPE), n, zero=True)
         return llmemory.cast_adr_to_ptr(addr, lltype.Ptr(TYPE))
 
     def test_simple(self):
@@ -311,7 +314,18 @@
         print hash
         assert isinstance(hash, (int, long))
         assert hash == self.gc.identityhash(p_const)
-
+        # (5) p is actually moving (for the markcompact gc)
+        p0 = self.malloc(S)
+        self.stackroots.append(p0)
+        p = self.malloc(S)
+        self.stackroots.append(p)
+        hash = self.gc.identityhash(p)
+        self.stackroots.pop(-2)
+        self.gc.collect()     # p0 goes away, p shifts left
+        assert hash == self.gc.identityhash(self.stackroots[-1])
+        self.gc.collect()
+        assert hash == self.gc.identityhash(self.stackroots[-1])
+        self.stackroots.pop()
 
 class TestSemiSpaceGC(DirectGCTest):
     from pypy.rpython.memory.gc.semispace import SemiSpaceGC as GCClass
@@ -431,3 +445,14 @@
 class TestMarkCompactGC(DirectGCTest):
     from pypy.rpython.memory.gc.markcompact import MarkCompactGC as GCClass
 
+    def test_many_objects(self):
+        DirectGCTest.test_many_objects(self)
+    test_many_objects.GC_PARAMS = {'space_size': 3 * 1024 * WORD}
+
+    def test_varsized_from_stack(self):
+        DirectGCTest.test_varsized_from_stack(self)
+    test_varsized_from_stack.GC_PARAMS = {'space_size': 2 * 1024 * WORD}
+
+    def test_varsized_from_prebuilt_gc(self):
+        DirectGCTest.test_varsized_from_prebuilt_gc(self)
+    test_varsized_from_prebuilt_gc.GC_PARAMS = {'space_size': 3 * 1024 * WORD}

Modified: pypy/trunk/pypy/rpython/memory/gctypelayout.py
==============================================================================
--- pypy/trunk/pypy/rpython/memory/gctypelayout.py	(original)
+++ pypy/trunk/pypy/rpython/memory/gctypelayout.py	Fri Sep  3 13:39:11 2010
@@ -44,16 +44,18 @@
         self.type_info_group_ptr = type_info_group._as_ptr()
 
     def get(self, typeid):
-        _check_typeid(typeid)
-        return llop.get_group_member(GCData.TYPE_INFO_PTR,
-                                     self.type_info_group_ptr,
-                                     typeid)
+        res = llop.get_group_member(GCData.TYPE_INFO_PTR,
+                                    self.type_info_group_ptr,
+                                    typeid)
+        _check_valid_type_info(res)
+        return res
 
     def get_varsize(self, typeid):
-        _check_typeid(typeid)
-        return llop.get_group_member(GCData.VARSIZE_TYPE_INFO_PTR,
-                                     self.type_info_group_ptr,
-                                     typeid)
+        res = llop.get_group_member(GCData.VARSIZE_TYPE_INFO_PTR,
+                                    self.type_info_group_ptr,
+                                    typeid)
+        _check_valid_type_info_varsize(res)
+        return res
 
     def q_is_varsize(self, typeid):
         infobits = self.get(typeid).infobits
@@ -115,15 +117,21 @@
 
 
 # the lowest 16bits are used to store group member index
-T_MEMBER_INDEX         = 0xffff
+T_MEMBER_INDEX         =  0xffff
 T_IS_VARSIZE           = 0x10000
 T_HAS_GCPTR_IN_VARSIZE = 0x20000
 T_IS_GCARRAY_OF_GCPTR  = 0x40000
 T_IS_WEAKREF           = 0x80000
+T_KEY_MASK          = 0xFF000000
+T_KEY_VALUE         = 0x7A000000    # bug detection only
 
-def _check_typeid(typeid):
-    ll_assert(llop.is_group_member_nonzero(lltype.Bool, typeid),
-              "invalid type_id")
+def _check_valid_type_info(p):
+    ll_assert(p.infobits & T_KEY_MASK == T_KEY_VALUE, "invalid type_id")
+
+def _check_valid_type_info_varsize(p):
+    ll_assert(p.header.infobits & (T_KEY_MASK | T_IS_VARSIZE) ==
+                                  (T_KEY_VALUE | T_IS_VARSIZE),
+              "invalid varsize type_id")
 
 
 def encode_type_shape(builder, info, TYPE, index):
@@ -167,7 +175,7 @@
         varinfo.varitemsize = llmemory.sizeof(ARRAY.OF)
     if TYPE == WEAKREF:
         infobits |= T_IS_WEAKREF
-    info.infobits = infobits
+    info.infobits = infobits | T_KEY_VALUE
 
 # ____________________________________________________________
 
@@ -250,14 +258,18 @@
                 _, TYPE = TYPE._first_struct()
 
     def get_info(self, type_id):
-        return llop.get_group_member(GCData.TYPE_INFO_PTR,
-                                     self.type_info_group._as_ptr(),
-                                     type_id)
+        res = llop.get_group_member(GCData.TYPE_INFO_PTR,
+                                    self.type_info_group._as_ptr(),
+                                    type_id)
+        _check_valid_type_info(res)
+        return res
 
     def get_info_varsize(self, type_id):
-        return llop.get_group_member(GCData.VARSIZE_TYPE_INFO_PTR,
-                                     self.type_info_group._as_ptr(),
-                                     type_id)
+        res = llop.get_group_member(GCData.VARSIZE_TYPE_INFO_PTR,
+                                    self.type_info_group._as_ptr(),
+                                    type_id)
+        _check_valid_type_info_varsize(res)
+        return res
 
     def is_weakref(self, type_id):
         return self.get_info(type_id).infobits & T_IS_WEAKREF

Modified: pypy/trunk/pypy/rpython/memory/gcwrapper.py
==============================================================================
--- pypy/trunk/pypy/rpython/memory/gcwrapper.py	(original)
+++ pypy/trunk/pypy/rpython/memory/gcwrapper.py	Fri Sep  3 13:39:11 2010
@@ -119,6 +119,9 @@
         else:
             return True
 
+    def pyobjectptr(self, klass):
+        raise NotImplementedError(klass)
+
 # ____________________________________________________________
 
 class LLInterpRootWalker:

Modified: pypy/trunk/pypy/rpython/memory/test/test_gc.py
==============================================================================
--- pypy/trunk/pypy/rpython/memory/test/test_gc.py	(original)
+++ pypy/trunk/pypy/rpython/memory/test/test_gc.py	Fri Sep  3 13:39:11 2010
@@ -639,12 +639,14 @@
 
 class TestMarkCompactGC(TestSemiSpaceGC):
     from pypy.rpython.memory.gc.markcompact import MarkCompactGC as GCClass
+    GC_PARAMS = {'space_size': 65536+16384}
+    GC_CAN_SHRINK_ARRAY = False
 
     def test_finalizer_order(self):
         py.test.skip("Not implemented yet")
-
-class TestMarkCompactGCGrowing(TestMarkCompactGC):
-    GC_PARAMS = {'space_size': 16*WORD}
+    def test_writebarrier_before_copy(self):
+        py.test.skip("Not relevant, and crashes because llarena does not "
+                     "support empty GcStructs")
 
 class TestHybridGC(TestGenerationalGC):
     from pypy.rpython.memory.gc.hybrid import HybridGC as GCClass

Modified: pypy/trunk/pypy/rpython/memory/test/test_transformed_gc.py
==============================================================================
--- pypy/trunk/pypy/rpython/memory/test/test_transformed_gc.py	(original)
+++ pypy/trunk/pypy/rpython/memory/test/test_transformed_gc.py	Fri Sep  3 13:39:11 2010
@@ -1138,15 +1138,16 @@
 class TestMarkCompactGC(GenericMovingGCTests):
     gcname = 'markcompact'
 
-    def setup_class(cls):
-        py.test.skip("Disabled for now, sorry")
-
     class gcpolicy(gc.FrameworkGcPolicy):
         class transformerclass(framework.FrameworkGCTransformer):
             from pypy.rpython.memory.gc.markcompact import MarkCompactGC as GCClass
-            GC_PARAMS = {'space_size': 512*WORD}
+            GC_PARAMS = {'space_size': 4096*WORD}
             root_stack_depth = 200
 
+    def test_writebarrier_before_copy(self):
+        py.test.skip("Not relevant, and crashes because llarena does not "
+                     "support empty GcStructs")
+
 class TestGenerationGC(GenericMovingGCTests):
     gcname = "generation"
     GC_CAN_SHRINK_ARRAY = True
@@ -1536,3 +1537,12 @@
             GC_PARAMS = {'space_size': 512*WORD,
                          'nursery_size': 32*WORD}
             root_stack_depth = 200
+
+class TestMarkCompactTaggedpointerGC(TaggedPointerGCTests):
+    gcname = 'markcompact'
+
+    class gcpolicy(gc.FrameworkGcPolicy):
+        class transformerclass(framework.FrameworkGCTransformer):
+            from pypy.rpython.memory.gc.markcompact import MarkCompactGC as GCClass
+            GC_PARAMS = {'space_size': 4096*WORD}
+            root_stack_depth = 200

Modified: pypy/trunk/pypy/translator/c/test/test_newgc.py
==============================================================================
--- pypy/trunk/pypy/translator/c/test/test_newgc.py	(original)
+++ pypy/trunk/pypy/translator/c/test/test_newgc.py	Fri Sep  3 13:39:11 2010
@@ -67,9 +67,8 @@
             if not fullname.startswith('define'):
                 continue
             keyword = conftest.option.keyword
-            if keyword:
-                if keyword.startswith('test_'):
-                    keyword = keyword[len('test_'):]
+            if keyword.startswith('test_'):
+                keyword = keyword[len('test_'):]
                 if keyword not in fullname:
                     continue
             prefix, name = fullname.split('_', 1)
@@ -1072,21 +1071,66 @@
     should_be_moving = True
     GC_CAN_SHRINK_ARRAY = False
 
-    def setup_class(cls):
-        py.test.skip("Disabled for now")
-
     def test_gc_set_max_heap_size(self):
         py.test.skip("not implemented")
 
+    def test_gc_heap_stats(self):
+        py.test.skip("not implemented")
+
     def test_finalizer_order(self):
         py.test.skip("not implemented")
 
+    def define_adding_a_hash(cls):
+        from pypy.rlib.objectmodel import compute_identity_hash
+        S1 = lltype.GcStruct('S1', ('x', lltype.Signed))
+        S2 = lltype.GcStruct('S2', ('p1', lltype.Ptr(S1)),
+                                   ('p2', lltype.Ptr(S1)),
+                                   ('p3', lltype.Ptr(S1)),
+                                   ('p4', lltype.Ptr(S1)),
+                                   ('p5', lltype.Ptr(S1)),
+                                   ('p6', lltype.Ptr(S1)),
+                                   ('p7', lltype.Ptr(S1)),
+                                   ('p8', lltype.Ptr(S1)),
+                                   ('p9', lltype.Ptr(S1)))
+        def g():
+            lltype.malloc(S1)   # forgotten, will be shifted over
+            s2 = lltype.malloc(S2)   # a big object, overlaps its old position
+            s2.p1 = lltype.malloc(S1); s2.p1.x = 1010
+            s2.p2 = lltype.malloc(S1); s2.p2.x = 1020
+            s2.p3 = lltype.malloc(S1); s2.p3.x = 1030
+            s2.p4 = lltype.malloc(S1); s2.p4.x = 1040
+            s2.p5 = lltype.malloc(S1); s2.p5.x = 1050
+            s2.p6 = lltype.malloc(S1); s2.p6.x = 1060
+            s2.p7 = lltype.malloc(S1); s2.p7.x = 1070
+            s2.p8 = lltype.malloc(S1); s2.p8.x = 1080
+            s2.p9 = lltype.malloc(S1); s2.p9.x = 1090
+            return s2
+        def f():
+            rgc.collect()
+            s2 = g()
+            h2 = compute_identity_hash(s2)
+            rgc.collect()    # shift s2 to the left, but add a hash field
+            assert s2.p1.x == 1010
+            assert s2.p2.x == 1020
+            assert s2.p3.x == 1030
+            assert s2.p4.x == 1040
+            assert s2.p5.x == 1050
+            assert s2.p6.x == 1060
+            assert s2.p7.x == 1070
+            assert s2.p8.x == 1080
+            assert s2.p9.x == 1090
+            return h2 - compute_identity_hash(s2)
+        return f
+
+    def test_adding_a_hash(self):
+        res = self.run("adding_a_hash")
+        assert res == 0
+
 # ____________________________________________________________________
 
-class TestHybridTaggedPointers(TestHybridGC):
+class TaggedPointersTest(object):
     taggedpointers = True
 
-
     def define_tagged(cls):
         class Unrelated(object):
             pass
@@ -1129,3 +1173,10 @@
     __slots__ = 'smallint'
     def meth(self, x):
         return self.smallint + x + 3
+
+
+class TestHybridTaggedPointers(TaggedPointersTest, TestHybridGC):
+    pass
+
+class TestMarkCompactGCMostCompact(TaggedPointersTest, TestMarkCompactGC):
+    removetypeptr = True

Modified: pypy/trunk/pypy/translator/exceptiontransform.py
==============================================================================
--- pypy/trunk/pypy/translator/exceptiontransform.py	(original)
+++ pypy/trunk/pypy/translator/exceptiontransform.py	Fri Sep  3 13:39:11 2010
@@ -197,7 +197,7 @@
         for graph in self.translator.graphs:
             self.create_exception_handling(graph)
 
-    def create_exception_handling(self, graph, always_exc_clear=False):
+    def create_exception_handling(self, graph):
         """After an exception in a direct_call (or indirect_call), that is not caught
         by an explicit
         except statement, we need to reraise the exception. So after this
@@ -212,7 +212,6 @@
             self.raise_analyzer.analyze_direct_call(graph)
             graph.exceptiontransformed = self.exc_data_ptr
 
-        self.always_exc_clear = always_exc_clear
         join_blocks(graph)
         # collect the blocks before changing them
         n_need_exc_matching_blocks = 0
@@ -455,13 +454,18 @@
         block.recloseblock(l0, l)
 
         insert_zeroing_op = False
-        # XXX this is not right. it also inserts zero_gc_pointers_inside
-        # XXX on a path that malloc_nonmovable returns null, but does not raise
-        # XXX which might end up with a segfault. But we don't have such gc now
-        if spaceop.opname == 'malloc' or spaceop.opname == 'malloc_nonmovable':
+        if spaceop.opname == 'malloc':
             flavor = spaceop.args[1].value['flavor']
             if flavor == 'gc':
                 insert_zeroing_op = True
+        elif spaceop.opname == 'malloc_nonmovable':
+            # xxx we cannot insert zero_gc_pointers_inside after
+            # malloc_nonmovable, because it can return null.  For now
+            # we simply always force the zero=True flag on
+            # malloc_nonmovable.
+            c_flags = spaceop.args[1]
+            c_flags.value = c_flags.value.copy()
+            spaceop.args[1].value['zero'] = True
 
         if insert_zeroing_op:
             if normalafterblock is None:
@@ -479,16 +483,6 @@
                                   [v_result_after],
                                   varoftype(lltype.Void)))
 
-        if self.always_exc_clear:
-            # insert code that clears the exception even in the non-exceptional
-            # case...  this is a hint for the JIT, but pointless otherwise
-            if normalafterblock is None:
-                normalafterblock = insert_empty_block(None, l0)
-            llops = rtyper.LowLevelOpList(None)
-            self.gen_setfield('exc_value', self.c_null_evalue, llops)
-            self.gen_setfield('exc_type',  self.c_null_etype,  llops)
-            normalafterblock.operations[:0] = llops
-
 
 class LLTypeExceptionTransformer(BaseExceptionTransformer):
 



More information about the Pypy-commit mailing list