Author: Armin Rigo <ar...@tunes.org>
Branch: cpyext-gc-support-2
Changeset: r81853:567f4931007c
Date: 2016-01-19 15:01 +0100
http://bitbucket.org/pypy/pypy/changeset/567f4931007c/

Log:    copy the basic GC support

diff --git a/pypy/doc/discussion/rawrefcount.rst 
b/pypy/doc/discussion/rawrefcount.rst
new file mode 100644
--- /dev/null
+++ b/pypy/doc/discussion/rawrefcount.rst
@@ -0,0 +1,153 @@
+======================
+Rawrefcount and the GC
+======================
+
+
+GC Interface
+------------
+
+"PyObject" is a raw structure with at least two fields, ob_refcnt and
+ob_pypy_link.  The ob_refcnt is the reference counter as used on
+CPython.  If the PyObject structure is linked to a live PyPy object,
+its current address is stored in ob_pypy_link and ob_refcnt is bumped
+by either the constant REFCNT_FROM_PYPY, or the constant
+REFCNT_FROM_PYPY_LIGHT (== REFCNT_FROM_PYPY + SOME_HUGE_VALUE)
+(to mean "light finalizer").
+
+Most PyPy objects exist outside cpyext, and conversely in cpyext it is
+possible that a lot of PyObjects exist without being seen by the rest
+of PyPy.  At the interface, however, we can "link" a PyPy object and a
+PyObject.  There are two kinds of link:
+
+rawrefcount.create_link_pypy(p, ob)
+
+    Makes a link between an exising object gcref 'p' and a newly
+    allocated PyObject structure 'ob'.  ob->ob_refcnt must be
+    initialized to either REFCNT_FROM_PYPY, or
+    REFCNT_FROM_PYPY_LIGHT.  (The second case is an optimization:
+    when the GC finds the PyPy object and PyObject no longer
+    referenced, it can just free() the PyObject.)
+
+rawrefcount.create_link_pyobj(p, ob)
+
+    Makes a link from an existing PyObject structure 'ob' to a newly
+    allocated W_CPyExtPlaceHolderObject 'p'.  You must also add
+    REFCNT_FROM_PYPY to ob->ob_refcnt.  For cases where the PyObject
+    contains all the data, and the PyPy object is just a proxy.  The
+    W_CPyExtPlaceHolderObject should have only a field that contains
+    the address of the PyObject, but that's outside the scope of the
+    GC.
+
+rawrefcount.from_obj(p)
+
+    If there is a link from object 'p' made with create_link_pypy(),
+    returns the corresponding 'ob'.  Otherwise, returns NULL.
+
+rawrefcount.to_obj(Class, ob)
+
+    Returns ob->ob_pypy_link, cast to an instance of 'Class'.
+
+
+Collection logic
+----------------
+
+Objects existing purely on the C side have ob->ob_pypy_link == 0;
+these are purely reference counted.  On the other hand, if
+ob->ob_pypy_link != 0, then ob->ob_refcnt is at least REFCNT_FROM_PYPY
+and the object is part of a "link".
+
+The idea is that links whose 'p' is not reachable from other PyPy
+objects *and* whose 'ob->ob_refcnt' is REFCNT_FROM_PYPY or
+REFCNT_FROM_PYPY_LIGHT are the ones who die.  But it is more messy
+because PyObjects still (usually) need to have a tp_dealloc called,
+and this cannot occur immediately (and can do random things like
+accessing other references this object points to, or resurrecting the
+object).
+
+Let P = list of links created with rawrefcount.create_link_pypy()
+and O = list of links created with rawrefcount.create_link_pyobj().
+The PyPy objects in the list O are all W_CPyExtPlaceHolderObject: all
+the data is in the PyObjects, and all references (if any) are regular
+CPython-like reference counts.
+
+So, during the collection we do this about P links:
+
+    for (p, ob) in P:
+        if ob->ob_refcnt != REFCNT_FROM_PYPY
+               and ob->ob_refcnt != REFCNT_FROM_PYPY_LIGHT:
+            mark 'p' as surviving, as well as all its dependencies
+
+At the end of the collection, the P and O links are both handled like
+this:
+
+    for (p, ob) in P + O:
+        if p is not surviving:
+            unlink p and ob
+            if ob->ob_refcnt == REFCNT_FROM_PYPY_LIGHT:
+                free(ob)
+            elif ob->ob_refcnt > REFCNT_FROM_PYPY_LIGHT:
+                ob->ob_refcnt -= REFCNT_FROM_PYPY_LIGHT
+            else:
+                ob->ob_refcnt -= REFCNT_FROM_PYPY
+                if ob->ob_refcnt == 0:
+                    invoke _Py_Dealloc(ob) later, outside the GC
+
+
+GC Implementation
+-----------------
+
+We need two copies of both the P list and O list, for young or old
+objects.  All four lists can be regular AddressLists of 'ob' objects.
+
+We also need an AddressDict mapping 'p' to 'ob' for all links in the P
+list, and update it when PyPy objects move.
+
+
+Further notes
+-------------
+
+For objects that are opaque in CPython, like <dict>, we always create
+a PyPy object, and then when needed we make an empty PyObject and
+attach it with create_link_pypy()/REFCNT_FROM_PYPY_LIGHT.
+
+For <int> and <float> objects, the corresponding PyObjects contain a
+"long" or "double" field too.  We link them with create_link_pypy()
+and we can use REFCNT_FROM_PYPY_LIGHT too: 'tp_dealloc' doesn't
+need to be called, and instead just calling free() is fine.
+
+For <type> objects, we need both a PyPy and a PyObject side.  These
+are made with create_link_pypy()/REFCNT_FROM_PYPY.
+
+For custom PyXxxObjects allocated from the C extension module, we
+need create_link_pyobj().
+
+For <str> or <unicode> objects coming from PyPy, we use
+create_link_pypy()/REFCNT_FROM_PYPY_LIGHT with a PyObject
+preallocated with the size of the string.  We copy the string
+lazily into that area if PyString_AS_STRING() is called.
+
+For <str>, <unicode>, <tuple> or <list> objects in the C extension
+module, we first allocate it as only a PyObject, which supports
+mutation of the data from C, like CPython.  When it is exported to
+PyPy we could make a W_CPyExtPlaceHolderObject with
+create_link_pyobj().
+
+For <tuple> objects coming from PyPy, if they are not specialized,
+then the PyPy side holds a regular reference to the items.  Then we
+can allocate a PyTupleObject and store in it borrowed PyObject
+pointers to the items.  Such a case is created with
+create_link_pypy()/REFCNT_FROM_PYPY_LIGHT.  If it is specialized,
+then it doesn't work because the items are created just-in-time on the
+PyPy side.  In this case, the PyTupleObject needs to hold real
+references to the PyObject items, and we use create_link_pypy()/
+REFCNT_FROM_PYPY.  In all cases, we have a C array of PyObjects
+that we can directly return from PySequence_Fast_ITEMS, PyTuple_ITEMS,
+PyTuple_GetItem, and so on.
+
+For <list> objects coming from PyPy, we can use a cpyext list
+strategy.  The list turns into a PyListObject, as if it had been
+allocated from C in the first place.  The special strategy can hold
+(only) a direct reference to the PyListObject, and we can use either
+create_link_pyobj() or create_link_pypy() (to be decided).
+PySequence_Fast_ITEMS then works for lists too, and PyList_GetItem
+can return a borrowed reference, and so on.
diff --git a/rpython/memory/gc/incminimark.py b/rpython/memory/gc/incminimark.py
--- a/rpython/memory/gc/incminimark.py
+++ b/rpython/memory/gc/incminimark.py
@@ -706,6 +706,7 @@
                 self.major_collection_step()
         else:
             self.minor_and_major_collection()
+        self.rrc_invoke_callback()
 
 
     def collect_and_reserve(self, totalsize):
@@ -783,12 +784,15 @@
                                self.threshold_reached()):  # ^^but only if 
still
                             self.minor_collection()        # the same 
collection
                             self.major_collection_step()
-                        #
-                        # The nursery might not be empty now, because of
-                        # execute_finalizers().  If it is almost full again,
-                        # we need to fix it with another call to 
minor_collection().
-                        if self.nursery_free + totalsize > self.nursery_top:
-                            self.minor_collection()
+                    #
+                    self.rrc_invoke_callback()
+                    #
+                    # The nursery might not be empty now, because of
+                    # execute_finalizers() or rrc_invoke_callback().
+                    # If it is almost full again,
+                    # we need to fix it with another call to 
minor_collection().
+                    if self.nursery_free + totalsize > self.nursery_top:
+                        self.minor_collection()
                     #
                 else:
                     ll_assert(minor_collection_count == 2,
@@ -861,6 +865,7 @@
             if self.threshold_reached(raw_malloc_usage(totalsize) +
                                       self.nursery_size // 2):
                 self.major_collection_step(raw_malloc_usage(totalsize))
+            self.rrc_invoke_callback()
             # note that this loop should not be infinite: when the
             # last step of a major collection is done but
             # threshold_reached(totalsize) is still true, then
@@ -1080,35 +1085,19 @@
                   "odd-valued (i.e. tagged) pointer unexpected here")
         return self.nursery <= addr < self.nursery + self.nursery_size
 
-    def appears_to_be_young(self, addr):
-        # "is a valid addr to a young object?"
-        # but it's ok to occasionally return True accidentally.
-        # Maybe the best implementation would be a bloom filter
-        # of some kind instead of the dictionary lookup that is
-        # sometimes done below.  But the expected common answer
-        # is "Yes" because addr points to the nursery, so it may
-        # not be useful to optimize the other case too much.
-        #
-        # First, if 'addr' appears to be a pointer to some place within
-        # the nursery, return True
-        if not self.translated_to_c:
-            # When non-translated, filter out tagged pointers explicitly.
-            # When translated, it may occasionally give a wrong answer
-            # of True if 'addr' is a tagged pointer with just the wrong value.
-            if not self.is_valid_gc_object(addr):
-                return False
-
+    def is_young_object(self, addr):
+        # Check if the object at 'addr' is young.
+        if not self.is_valid_gc_object(addr):
+            return False     # filter out tagged pointers explicitly.
         if self.nursery <= addr < self.nursery_top:
             return True      # addr is in the nursery
-        #
         # Else, it may be in the set 'young_rawmalloced_objects'
         return (bool(self.young_rawmalloced_objects) and
                 self.young_rawmalloced_objects.contains(addr))
-    appears_to_be_young._always_inline_ = True
 
     def debug_is_old_object(self, addr):
         return (self.is_valid_gc_object(addr)
-                and not self.appears_to_be_young(addr))
+                and not self.is_young_object(addr))
 
     def is_forwarded(self, obj):
         """Returns True if the nursery obj is marked as forwarded.
@@ -1618,6 +1607,10 @@
                 self._visit_old_objects_pointing_to_pinned, None)
             current_old_objects_pointing_to_pinned.delete()
         #
+        # visit the P list from rawrefcount, if enabled.
+        if self.rrc_enabled:
+            self.rrc_minor_collection_trace()
+        #
         while True:
             # If we are using card marking, do a partial trace of the arrays
             # that are flagged with GCFLAG_CARDS_SET.
@@ -1666,6 +1659,10 @@
         if self.young_rawmalloced_objects:
             self.free_young_rawmalloced_objects()
         #
+        # visit the P and O lists from rawrefcount, if enabled.
+        if self.rrc_enabled:
+            self.rrc_minor_collection_free()
+        #
         # All live nursery objects are out of the nursery or pinned inside
         # the nursery.  Create nursery barriers to protect the pinned objects,
         # fill the rest of the nursery with zeros and reset the current nursery
@@ -2178,9 +2175,13 @@
             # finalizers/weak references are rare and short which means that
             # they do not need a separate state and do not need to be
             # made incremental.
+            # For now, the same applies to rawrefcount'ed objects.
             if (not self.objects_to_trace.non_empty() and
                 not self.more_objects_to_trace.non_empty()):
                 #
+                if self.rrc_enabled:
+                    self.rrc_major_collection_trace()
+                #
                 if self.objects_with_finalizers.non_empty():
                     self.deal_with_objects_with_finalizers()
                 elif self.old_objects_with_weakrefs.non_empty():
@@ -2215,6 +2216,10 @@
                     self.old_objects_pointing_to_pinned = \
                             new_old_objects_pointing_to_pinned
                     self.updated_old_objects_pointing_to_pinned = True
+                #
+                if self.rrc_enabled:
+                    self.rrc_major_collection_free()
+                #
                 self.gc_state = STATE_SWEEPING
             #END MARKING
         elif self.gc_state == STATE_SWEEPING:
@@ -2745,3 +2750,234 @@
                 (obj + offset).address[0] = llmemory.NULL
         self.old_objects_with_weakrefs.delete()
         self.old_objects_with_weakrefs = new_with_weakref
+
+
+    # ----------
+    # RawRefCount
+
+    rrc_enabled = False
+
+    _ADDRARRAY = lltype.Array(llmemory.Address, hints={'nolength': True})
+    PYOBJ_HDR = lltype.Struct('GCHdr_PyObject',
+                              ('ob_refcnt', lltype.Signed),
+                              ('ob_pypy_link', lltype.Signed))
+    PYOBJ_HDR_PTR = lltype.Ptr(PYOBJ_HDR)
+    RAWREFCOUNT_DEALLOC_TRIGGER = lltype.Ptr(lltype.FuncType([], lltype.Void))
+
+    def _pyobj(self, pyobjaddr):
+        return llmemory.cast_adr_to_ptr(pyobjaddr, self.PYOBJ_HDR_PTR)
+
+    def rawrefcount_init(self, dealloc_trigger_callback):
+        # see pypy/doc/discussion/rawrefcount.rst
+        if not self.rrc_enabled:
+            self.rrc_p_list_young = self.AddressStack()
+            self.rrc_p_list_old   = self.AddressStack()
+            self.rrc_o_list_young = self.AddressStack()
+            self.rrc_o_list_old   = self.AddressStack()
+            self.rrc_p_dict       = self.AddressDict()  # non-nursery keys only
+            self.rrc_p_dict_nurs  = self.AddressDict()  # nursery keys only
+            p = lltype.malloc(self._ADDRARRAY, 1, flavor='raw',
+                              track_allocation=False)
+            self.rrc_singleaddr = llmemory.cast_ptr_to_adr(p)
+            self.rrc_dealloc_trigger_callback = dealloc_trigger_callback
+            self.rrc_dealloc_pending = self.AddressStack()
+            self.rrc_enabled = True
+
+    def check_no_more_rawrefcount_state(self):
+        "NOT_RPYTHON: for tests"
+        assert self.rrc_p_list_young.length() == 0
+        assert self.rrc_p_list_old  .length() == 0
+        assert self.rrc_o_list_young.length() == 0
+        assert self.rrc_o_list_old  .length() == 0
+        def check_value_is_null(key, value, ignore):
+            assert value == llmemory.NULL
+        self.rrc_p_dict.foreach(check_value_is_null, None)
+        self.rrc_p_dict_nurs.foreach(check_value_is_null, None)
+
+    def rawrefcount_create_link_pypy(self, gcobj, pyobject):
+        ll_assert(self.rrc_enabled, "rawrefcount.init not called")
+        obj = llmemory.cast_ptr_to_adr(gcobj)
+        objint = llmemory.cast_adr_to_int(obj, "symbolic")
+        self._pyobj(pyobject).ob_pypy_link = objint
+        #
+        lst = self.rrc_p_list_young
+        if self.is_in_nursery(obj):
+            dct = self.rrc_p_dict_nurs
+        else:
+            dct = self.rrc_p_dict
+            if not self.is_young_object(obj):
+                lst = self.rrc_p_list_old
+        lst.append(pyobject)
+        dct.setitem(obj, pyobject)
+
+    def rawrefcount_create_link_pyobj(self, gcobj, pyobject):
+        ll_assert(self.rrc_enabled, "rawrefcount.init not called")
+        obj = llmemory.cast_ptr_to_adr(gcobj)
+        if self.is_young_object(obj):
+            self.rrc_o_list_young.append(pyobject)
+        else:
+            self.rrc_o_list_old.append(pyobject)
+        objint = llmemory.cast_adr_to_int(obj, "symbolic")
+        self._pyobj(pyobject).ob_pypy_link = objint
+        # there is no rrc_o_dict
+
+    def rawrefcount_from_obj(self, gcobj):
+        obj = llmemory.cast_ptr_to_adr(gcobj)
+        if self.is_in_nursery(obj):
+            dct = self.rrc_p_dict_nurs
+        else:
+            dct = self.rrc_p_dict
+        return dct.get(obj)
+
+    def rawrefcount_to_obj(self, pyobject):
+        obj = llmemory.cast_int_to_adr(self._pyobj(pyobject).ob_pypy_link)
+        return llmemory.cast_adr_to_ptr(obj, llmemory.GCREF)
+
+    def rawrefcount_next_dead(self):
+        if self.rrc_dealloc_pending.non_empty():
+            return self.rrc_dealloc_pending.pop()
+        return llmemory.NULL
+
+
+    def rrc_invoke_callback(self):
+        if self.rrc_enabled and self.rrc_dealloc_pending.non_empty():
+            self.rrc_dealloc_trigger_callback()
+
+    def rrc_minor_collection_trace(self):
+        length_estimate = self.rrc_p_dict_nurs.length()
+        self.rrc_p_dict_nurs.delete()
+        self.rrc_p_dict_nurs = self.AddressDict(length_estimate)
+        self.rrc_p_list_young.foreach(self._rrc_minor_trace,
+                                      self.rrc_singleaddr)
+
+    def _rrc_minor_trace(self, pyobject, singleaddr):
+        from rpython.rlib.rawrefcount import REFCNT_FROM_PYPY
+        from rpython.rlib.rawrefcount import REFCNT_FROM_PYPY_LIGHT
+        #
+        rc = self._pyobj(pyobject).ob_refcnt
+        if rc == REFCNT_FROM_PYPY or rc == REFCNT_FROM_PYPY_LIGHT:
+            pass     # the corresponding object may die
+        else:
+            # force the corresponding object to be alive
+            intobj = self._pyobj(pyobject).ob_pypy_link
+            singleaddr.address[0] = llmemory.cast_int_to_adr(intobj)
+            self._trace_drag_out(singleaddr, llmemory.NULL)
+
+    def rrc_minor_collection_free(self):
+        ll_assert(self.rrc_p_dict_nurs.length() == 0, "p_dict_nurs not empty 
1")
+        lst = self.rrc_p_list_young
+        while lst.non_empty():
+            self._rrc_minor_free(lst.pop(), self.rrc_p_list_old,
+                                            self.rrc_p_dict)
+        lst = self.rrc_o_list_young
+        no_o_dict = self.null_address_dict()
+        while lst.non_empty():
+            self._rrc_minor_free(lst.pop(), self.rrc_o_list_old,
+                                            no_o_dict)
+
+    def _rrc_minor_free(self, pyobject, surviving_list, surviving_dict):
+        intobj = self._pyobj(pyobject).ob_pypy_link
+        obj = llmemory.cast_int_to_adr(intobj)
+        if self.is_in_nursery(obj):
+            if self.is_forwarded(obj):
+                # Common case: survives and moves
+                obj = self.get_forwarding_address(obj)
+                intobj = llmemory.cast_adr_to_int(obj, "symbolic")
+                self._pyobj(pyobject).ob_pypy_link = intobj
+                surviving = True
+                if surviving_dict:
+                    # Surviving nursery object: was originally in
+                    # rrc_p_dict_nurs and now must be put into rrc_p_dict
+                    surviving_dict.setitem(obj, pyobject)
+            else:
+                surviving = False
+        elif (bool(self.young_rawmalloced_objects) and
+              self.young_rawmalloced_objects.contains(obj)):
+            # young weakref to a young raw-malloced object
+            if self.header(obj).tid & GCFLAG_VISITED_RMY:
+                surviving = True    # survives, but does not move
+            else:
+                surviving = False
+                if surviving_dict:
+                    # Dying young large object: was in rrc_p_dict,
+                    # must be deleted
+                    surviving_dict.setitem(obj, llmemory.NULL)
+        else:
+            ll_assert(False, "rrc_X_list_young contains non-young obj")
+            return
+        #
+        if surviving:
+            surviving_list.append(pyobject)
+        else:
+            self._rrc_free(pyobject)
+
+    def _rrc_free(self, pyobject):
+        from rpython.rlib.rawrefcount import REFCNT_FROM_PYPY
+        from rpython.rlib.rawrefcount import REFCNT_FROM_PYPY_LIGHT
+        #
+        rc = self._pyobj(pyobject).ob_refcnt
+        if rc >= REFCNT_FROM_PYPY_LIGHT:
+            rc -= REFCNT_FROM_PYPY_LIGHT
+            if rc == 0:
+                lltype.free(self._pyobj(pyobject), flavor='raw')
+            else:
+                # can only occur if LIGHT is used in create_link_pyobj()
+                self._pyobj(pyobject).ob_refcnt = rc
+                self._pyobj(pyobject).ob_pypy_link = 0
+        else:
+            ll_assert(rc >= REFCNT_FROM_PYPY, "refcount underflow?")
+            ll_assert(rc < int(REFCNT_FROM_PYPY_LIGHT * 0.99),
+                      "refcount underflow from REFCNT_FROM_PYPY_LIGHT?")
+            rc -= REFCNT_FROM_PYPY
+            self._pyobj(pyobject).ob_refcnt = rc
+            self._pyobj(pyobject).ob_pypy_link = 0
+            if rc == 0:
+                self.rrc_dealloc_pending.append(pyobject)
+    _rrc_free._always_inline_ = True
+
+    def rrc_major_collection_trace(self):
+        self.rrc_p_list_old.foreach(self._rrc_major_trace, None)
+
+    def _rrc_major_trace(self, pyobject, ignore):
+        from rpython.rlib.rawrefcount import REFCNT_FROM_PYPY
+        from rpython.rlib.rawrefcount import REFCNT_FROM_PYPY_LIGHT
+        #
+        rc = self._pyobj(pyobject).ob_refcnt
+        if rc == REFCNT_FROM_PYPY or rc == REFCNT_FROM_PYPY_LIGHT:
+            pass     # the corresponding object may die
+        else:
+            # force the corresponding object to be alive
+            intobj = self._pyobj(pyobject).ob_pypy_link
+            obj = llmemory.cast_int_to_adr(intobj)
+            self.objects_to_trace.append(obj)
+            self.visit_all_objects()
+
+    def rrc_major_collection_free(self):
+        ll_assert(self.rrc_p_dict_nurs.length() == 0, "p_dict_nurs not empty 
2")
+        length_estimate = self.rrc_p_dict.length()
+        self.rrc_p_dict.delete()
+        self.rrc_p_dict = new_p_dict = self.AddressDict(length_estimate)
+        new_p_list = self.AddressStack()
+        while self.rrc_p_list_old.non_empty():
+            self._rrc_major_free(self.rrc_p_list_old.pop(), new_p_list,
+                                                            new_p_dict)
+        self.rrc_p_list_old.delete()
+        self.rrc_p_list_old = new_p_list
+        #
+        new_o_list = self.AddressStack()
+        no_o_dict = self.null_address_dict()
+        while self.rrc_o_list_old.non_empty():
+            self._rrc_major_free(self.rrc_o_list_old.pop(), new_o_list,
+                                                            no_o_dict)
+        self.rrc_o_list_old.delete()
+        self.rrc_o_list_old = new_o_list
+
+    def _rrc_major_free(self, pyobject, surviving_list, surviving_dict):
+        intobj = self._pyobj(pyobject).ob_pypy_link
+        obj = llmemory.cast_int_to_adr(intobj)
+        if self.header(obj).tid & GCFLAG_VISITED:
+            surviving_list.append(pyobject)
+            if surviving_dict:
+                surviving_dict.insertclean(obj, pyobject)
+        else:
+            self._rrc_free(pyobject)
diff --git a/rpython/memory/gc/test/test_rawrefcount.py 
b/rpython/memory/gc/test/test_rawrefcount.py
new file mode 100644
--- /dev/null
+++ b/rpython/memory/gc/test/test_rawrefcount.py
@@ -0,0 +1,270 @@
+import py
+from rpython.rtyper.lltypesystem import lltype, llmemory
+from rpython.memory.gc.incminimark import IncrementalMiniMarkGC
+from rpython.memory.gc.test.test_direct import BaseDirectGCTest
+from rpython.rlib.rawrefcount import REFCNT_FROM_PYPY
+from rpython.rlib.rawrefcount import REFCNT_FROM_PYPY_LIGHT
+
+PYOBJ_HDR = IncrementalMiniMarkGC.PYOBJ_HDR
+PYOBJ_HDR_PTR = IncrementalMiniMarkGC.PYOBJ_HDR_PTR
+
+S = lltype.GcForwardReference()
+S.become(lltype.GcStruct('S',
+                         ('x', lltype.Signed),
+                         ('prev', lltype.Ptr(S)),
+                         ('next', lltype.Ptr(S))))
+
+
+class TestRawRefCount(BaseDirectGCTest):
+    GCClass = IncrementalMiniMarkGC
+
+    def _collect(self, major, expected_trigger=0):
+        if major:
+            self.gc.collect()
+        else:
+            self.gc.minor_collection()
+        count1 = len(self.trigger)
+        self.gc.rrc_invoke_callback()
+        count2 = len(self.trigger)
+        assert count2 - count1 == expected_trigger
+
+    def _rawrefcount_pair(self, intval, is_light=False, is_pyobj=False,
+                          create_old=False):
+        if is_light:
+            rc = REFCNT_FROM_PYPY_LIGHT
+        else:
+            rc = REFCNT_FROM_PYPY
+        self.trigger = []
+        self.gc.rawrefcount_init(lambda: self.trigger.append(1))
+        #
+        p1 = self.malloc(S)
+        p1.x = intval
+        if create_old:
+            self.stackroots.append(p1)
+            self._collect(major=False)
+            p1 = self.stackroots.pop()
+        p1ref = lltype.cast_opaque_ptr(llmemory.GCREF, p1)
+        r1 = lltype.malloc(PYOBJ_HDR, flavor='raw')
+        r1.ob_refcnt = rc
+        r1.ob_pypy_link = 0
+        r1addr = llmemory.cast_ptr_to_adr(r1)
+        if is_pyobj:
+            assert not is_light
+            self.gc.rawrefcount_create_link_pyobj(p1ref, r1addr)
+        else:
+            self.gc.rawrefcount_create_link_pypy(p1ref, r1addr)
+        assert r1.ob_refcnt == rc
+        assert r1.ob_pypy_link != 0
+
+        def check_alive(extra_refcount):
+            assert r1.ob_refcnt == rc + extra_refcount
+            assert r1.ob_pypy_link != 0
+            p1ref = self.gc.rawrefcount_to_obj(r1addr)
+            p1 = lltype.cast_opaque_ptr(lltype.Ptr(S), p1ref)
+            assert p1.x == intval
+            if not is_pyobj:
+                assert self.gc.rawrefcount_from_obj(p1ref) == r1addr
+            else:
+                assert self.gc.rawrefcount_from_obj(p1ref) == llmemory.NULL
+            return p1
+        return p1, p1ref, r1, r1addr, check_alive
+
+    def test_rawrefcount_objects_basic(self, old=False):
+        p1, p1ref, r1, r1addr, check_alive = (
+            self._rawrefcount_pair(42, is_light=True, create_old=old))
+        p2 = self.malloc(S)
+        p2.x = 84
+        p2ref = lltype.cast_opaque_ptr(llmemory.GCREF, p2)
+        r2 = lltype.malloc(PYOBJ_HDR, flavor='raw')
+        r2.ob_refcnt = 1
+        r2.ob_pypy_link = 0
+        r2addr = llmemory.cast_ptr_to_adr(r2)
+        # p2 and r2 are not linked
+        assert r1.ob_pypy_link != 0
+        assert r2.ob_pypy_link == 0
+        assert self.gc.rawrefcount_from_obj(p1ref) == r1addr
+        assert self.gc.rawrefcount_from_obj(p2ref) == llmemory.NULL
+        assert self.gc.rawrefcount_to_obj(r1addr) == p1ref
+        assert self.gc.rawrefcount_to_obj(r2addr) == lltype.nullptr(
+            llmemory.GCREF.TO)
+        lltype.free(r1, flavor='raw')
+        lltype.free(r2, flavor='raw')
+
+    def test_rawrefcount_objects_collection_survives_from_raw(self, old=False):
+        p1, p1ref, r1, r1addr, check_alive = (
+            self._rawrefcount_pair(42, is_light=True, create_old=old))
+        check_alive(0)
+        r1.ob_refcnt += 1
+        self._collect(major=False)
+        check_alive(+1)
+        self._collect(major=True)
+        check_alive(+1)
+        r1.ob_refcnt -= 1
+        self._collect(major=False)
+        p1 = check_alive(0)
+        self._collect(major=True)
+        py.test.raises(RuntimeError, "r1.ob_refcnt")    # dead
+        py.test.raises(RuntimeError, "p1.x")            # dead
+        self.gc.check_no_more_rawrefcount_state()
+        assert self.trigger == []
+        assert self.gc.rawrefcount_next_dead() == llmemory.NULL
+
+    def test_rawrefcount_dies_quickly(self, old=False):
+        p1, p1ref, r1, r1addr, check_alive = (
+            self._rawrefcount_pair(42, is_light=True, create_old=old))
+        check_alive(0)
+        self._collect(major=False)
+        if old:
+            check_alive(0)
+            self._collect(major=True)
+        py.test.raises(RuntimeError, "r1.ob_refcnt")    # dead
+        py.test.raises(RuntimeError, "p1.x")            # dead
+        self.gc.check_no_more_rawrefcount_state()
+
+    def test_rawrefcount_objects_collection_survives_from_obj(self, old=False):
+        p1, p1ref, r1, r1addr, check_alive = (
+            self._rawrefcount_pair(42, is_light=True, create_old=old))
+        check_alive(0)
+        self.stackroots.append(p1)
+        self._collect(major=False)
+        check_alive(0)
+        self._collect(major=True)
+        check_alive(0)
+        p1 = self.stackroots.pop()
+        self._collect(major=False)
+        check_alive(0)
+        assert p1.x == 42
+        self._collect(major=True)
+        py.test.raises(RuntimeError, "r1.ob_refcnt")    # dead
+        py.test.raises(RuntimeError, "p1.x")            # dead
+        self.gc.check_no_more_rawrefcount_state()
+
+    def test_rawrefcount_objects_basic_old(self):
+        self.test_rawrefcount_objects_basic(old=True)
+    def test_rawrefcount_objects_collection_survives_from_raw_old(self):
+        self.test_rawrefcount_objects_collection_survives_from_raw(old=True)
+    def test_rawrefcount_dies_quickly_old(self):
+        self.test_rawrefcount_dies_quickly(old=True)
+    def test_rawrefcount_objects_collection_survives_from_obj_old(self):
+        self.test_rawrefcount_objects_collection_survives_from_obj(old=True)
+
+    def test_pypy_nonlight_survives_from_raw(self, old=False):
+        p1, p1ref, r1, r1addr, check_alive = (
+            self._rawrefcount_pair(42, is_light=False, create_old=old))
+        check_alive(0)
+        r1.ob_refcnt += 1
+        self._collect(major=False)
+        check_alive(+1)
+        self._collect(major=True)
+        check_alive(+1)
+        r1.ob_refcnt -= 1
+        self._collect(major=False)
+        p1 = check_alive(0)
+        self._collect(major=True, expected_trigger=1)
+        py.test.raises(RuntimeError, "p1.x")            # dead
+        assert r1.ob_refcnt == 0
+        assert r1.ob_pypy_link == 0
+        assert self.gc.rawrefcount_next_dead() == r1addr
+        assert self.gc.rawrefcount_next_dead() == llmemory.NULL
+        assert self.gc.rawrefcount_next_dead() == llmemory.NULL
+        self.gc.check_no_more_rawrefcount_state()
+        lltype.free(r1, flavor='raw')
+
+    def test_pypy_nonlight_survives_from_obj(self, old=False):
+        p1, p1ref, r1, r1addr, check_alive = (
+            self._rawrefcount_pair(42, is_light=False, create_old=old))
+        check_alive(0)
+        self.stackroots.append(p1)
+        self._collect(major=False)
+        check_alive(0)
+        self._collect(major=True)
+        check_alive(0)
+        p1 = self.stackroots.pop()
+        self._collect(major=False)
+        check_alive(0)
+        assert p1.x == 42
+        self._collect(major=True, expected_trigger=1)
+        py.test.raises(RuntimeError, "p1.x")            # dead
+        assert r1.ob_refcnt == 0
+        assert r1.ob_pypy_link == 0
+        assert self.gc.rawrefcount_next_dead() == r1addr
+        self.gc.check_no_more_rawrefcount_state()
+        lltype.free(r1, flavor='raw')
+
+    def test_pypy_nonlight_dies_quickly(self, old=False):
+        p1, p1ref, r1, r1addr, check_alive = (
+            self._rawrefcount_pair(42, is_light=False, create_old=old))
+        check_alive(0)
+        if old:
+            self._collect(major=False)
+            check_alive(0)
+            self._collect(major=True, expected_trigger=1)
+        else:
+            self._collect(major=False, expected_trigger=1)
+        py.test.raises(RuntimeError, "p1.x")            # dead
+        assert r1.ob_refcnt == 0
+        assert r1.ob_pypy_link == 0
+        assert self.gc.rawrefcount_next_dead() == r1addr
+        self.gc.check_no_more_rawrefcount_state()
+        lltype.free(r1, flavor='raw')
+
+    def test_pypy_nonlight_survives_from_raw_old(self):
+        self.test_pypy_nonlight_survives_from_raw(old=True)
+    def test_pypy_nonlight_survives_from_obj_old(self):
+        self.test_pypy_nonlight_survives_from_obj(old=True)
+    def test_pypy_nonlight_dies_quickly_old(self):
+        self.test_pypy_nonlight_dies_quickly(old=True)
+
+    def test_pyobject_pypy_link_dies_on_minor_collection(self):
+        p1, p1ref, r1, r1addr, check_alive = (
+            self._rawrefcount_pair(42, is_pyobj=True))
+        check_alive(0)
+        r1.ob_refcnt += 1            # the pyobject is kept alive
+        self._collect(major=False)
+        assert r1.ob_refcnt == 1     # refcnt dropped to 1
+        assert r1.ob_pypy_link == 0  # detached
+        self.gc.check_no_more_rawrefcount_state()
+        lltype.free(r1, flavor='raw')
+
+    def test_pyobject_dies(self, old=False):
+        p1, p1ref, r1, r1addr, check_alive = (
+            self._rawrefcount_pair(42, is_pyobj=True, create_old=old))
+        check_alive(0)
+        if old:
+            self._collect(major=False)
+            check_alive(0)
+            self._collect(major=True, expected_trigger=1)
+        else:
+            self._collect(major=False, expected_trigger=1)
+        assert r1.ob_refcnt == 0     # refcnt dropped to 0
+        assert r1.ob_pypy_link == 0  # detached
+        assert self.gc.rawrefcount_next_dead() == r1addr
+        self.gc.check_no_more_rawrefcount_state()
+        lltype.free(r1, flavor='raw')
+
+    def test_pyobject_survives_from_obj(self, old=False):
+        p1, p1ref, r1, r1addr, check_alive = (
+            self._rawrefcount_pair(42, is_pyobj=True, create_old=old))
+        check_alive(0)
+        self.stackroots.append(p1)
+        self._collect(major=False)
+        check_alive(0)
+        self._collect(major=True)
+        check_alive(0)
+        p1 = self.stackroots.pop()
+        self._collect(major=False)
+        check_alive(0)
+        assert p1.x == 42
+        assert self.trigger == []
+        self._collect(major=True, expected_trigger=1)
+        py.test.raises(RuntimeError, "p1.x")            # dead
+        assert r1.ob_refcnt == 0
+        assert r1.ob_pypy_link == 0
+        assert self.gc.rawrefcount_next_dead() == r1addr
+        self.gc.check_no_more_rawrefcount_state()
+        lltype.free(r1, flavor='raw')
+
+    def test_pyobject_dies_old(self):
+        self.test_pyobject_dies(old=True)
+    def test_pyobject_survives_from_obj_old(self):
+        self.test_pyobject_survives_from_obj(old=True)
diff --git a/rpython/memory/gctransform/framework.py 
b/rpython/memory/gctransform/framework.py
--- a/rpython/memory/gctransform/framework.py
+++ b/rpython/memory/gctransform/framework.py
@@ -153,6 +153,7 @@
         else:
             # for regular translation: pick the GC from the config
             GCClass, GC_PARAMS = choose_gc_from_config(translator.config)
+            self.GCClass = GCClass
 
         if hasattr(translator, '_jit2gc'):
             self.layoutbuilder = translator._jit2gc['layoutbuilder']
@@ -483,6 +484,29 @@
                                             annmodel.SomeInteger(nonneg=True)],
                                            annmodel.s_None)
 
+        if hasattr(GCClass, 'rawrefcount_init'):
+            self.rawrefcount_init_ptr = getfn(
+                GCClass.rawrefcount_init,
+                [s_gc, SomePtr(GCClass.RAWREFCOUNT_DEALLOC_TRIGGER)],
+                annmodel.s_None)
+            self.rawrefcount_create_link_pypy_ptr = getfn(
+                GCClass.rawrefcount_create_link_pypy,
+                [s_gc, s_gcref, SomeAddress()],
+                annmodel.s_None)
+            self.rawrefcount_create_link_pyobj_ptr = getfn(
+                GCClass.rawrefcount_create_link_pyobj,
+                [s_gc, s_gcref, SomeAddress()],
+                annmodel.s_None)
+            self.rawrefcount_from_obj_ptr = getfn(
+                GCClass.rawrefcount_from_obj, [s_gc, s_gcref], SomeAddress(),
+                inline = True)
+            self.rawrefcount_to_obj_ptr = getfn(
+                GCClass.rawrefcount_to_obj, [s_gc, SomeAddress()], s_gcref,
+                inline = True)
+            self.rawrefcount_next_dead_ptr = getfn(
+                GCClass.rawrefcount_next_dead, [s_gc], SomeAddress(),
+                inline = True)
+
         if GCClass.can_usually_pin_objects:
             self.pin_ptr = getfn(GCClass.pin,
                                  [s_gc, SomeAddress()],
@@ -1228,6 +1252,50 @@
                   resultvar=hop.spaceop.result)
         self.pop_roots(hop, livevars)
 
+    def gct_gc_rawrefcount_init(self, hop):
+        [v_fnptr] = hop.spaceop.args
+        assert v_fnptr.concretetype == self.GCClass.RAWREFCOUNT_DEALLOC_TRIGGER
+        hop.genop("direct_call",
+                  [self.rawrefcount_init_ptr, self.c_const_gc, v_fnptr])
+
+    def gct_gc_rawrefcount_create_link_pypy(self, hop):
+        [v_gcobj, v_pyobject] = hop.spaceop.args
+        assert v_gcobj.concretetype == llmemory.GCREF
+        assert v_pyobject.concretetype == llmemory.Address
+        hop.genop("direct_call",
+                  [self.rawrefcount_create_link_pypy_ptr, self.c_const_gc,
+                   v_gcobj, v_pyobject])
+
+    def gct_gc_rawrefcount_create_link_pyobj(self, hop):
+        [v_gcobj, v_pyobject] = hop.spaceop.args
+        assert v_gcobj.concretetype == llmemory.GCREF
+        assert v_pyobject.concretetype == llmemory.Address
+        hop.genop("direct_call",
+                  [self.rawrefcount_create_link_pyobj_ptr, self.c_const_gc,
+                   v_gcobj, v_pyobject])
+
+    def gct_gc_rawrefcount_from_obj(self, hop):
+        [v_gcobj] = hop.spaceop.args
+        assert v_gcobj.concretetype == llmemory.GCREF
+        assert hop.spaceop.result.concretetype == llmemory.Address
+        hop.genop("direct_call",
+                  [self.rawrefcount_from_obj_ptr, self.c_const_gc, v_gcobj],
+                  resultvar=hop.spaceop.result)
+
+    def gct_gc_rawrefcount_to_obj(self, hop):
+        [v_pyobject] = hop.spaceop.args
+        assert v_pyobject.concretetype == llmemory.Address
+        assert hop.spaceop.result.concretetype == llmemory.GCREF
+        hop.genop("direct_call",
+                  [self.rawrefcount_to_obj_ptr, self.c_const_gc, v_pyobject],
+                  resultvar=hop.spaceop.result)
+
+    def gct_gc_rawrefcount_next_dead(self, hop):
+        assert hop.spaceop.result.concretetype == llmemory.Address
+        hop.genop("direct_call",
+                  [self.rawrefcount_next_dead_ptr, self.c_const_gc],
+                  resultvar=hop.spaceop.result)
+
     def _set_into_gc_array_part(self, op):
         if op.opname == 'setarrayitem':
             return op.args[1]
diff --git a/rpython/rlib/exports.py b/rpython/rlib/exports.py
--- a/rpython/rlib/exports.py
+++ b/rpython/rlib/exports.py
@@ -1,5 +1,7 @@
 from rpython.rtyper.lltypesystem.lltype import typeOf, ContainerType
 
+# XXX kill me
+
 def export_struct(name, struct):
     assert name not in EXPORTS_names, "Duplicate export " + name
     assert isinstance(typeOf(struct), ContainerType)
diff --git a/rpython/rlib/rawrefcount.py b/rpython/rlib/rawrefcount.py
new file mode 100644
--- /dev/null
+++ b/rpython/rlib/rawrefcount.py
@@ -0,0 +1,262 @@
+#
+#  See documentation in pypy/doc/discussion/rawrefcount.rst
+#
+import sys, weakref
+from rpython.rtyper.lltypesystem import lltype, llmemory
+from rpython.rlib.objectmodel import we_are_translated, specialize
+from rpython.rtyper.extregistry import ExtRegistryEntry
+from rpython.rlib import rgc
+
+
+REFCNT_FROM_PYPY       = 80
+REFCNT_FROM_PYPY_LIGHT = REFCNT_FROM_PYPY + (sys.maxint//2+1)
+
+RAWREFCOUNT_DEALLOC_TRIGGER = lltype.Ptr(lltype.FuncType([], lltype.Void))
+
+
+def _build_pypy_link(p):
+    res = len(_adr2pypy)
+    _adr2pypy.append(p)
+    return res
+
+
+def init(dealloc_trigger_callback=None):
+    """NOT_RPYTHON: set up rawrefcount with the GC.  This is only used
+    for tests; it should not be called at all during translation.
+    """
+    global _p_list, _o_list, _adr2pypy, _pypy2ob
+    global _d_list, _dealloc_trigger_callback
+    _p_list = []
+    _o_list = []
+    _adr2pypy = [None]
+    _pypy2ob = {}
+    _d_list = []
+    _dealloc_trigger_callback = dealloc_trigger_callback
+
+def create_link_pypy(p, ob):
+    "NOT_RPYTHON: a link where the PyPy object contains some or all the data"
+    #print 'create_link_pypy\n\t%s\n\t%s' % (p, ob)
+    assert p not in _pypy2ob
+    #assert not ob.c_ob_pypy_link
+    ob.c_ob_pypy_link = _build_pypy_link(p)
+    _pypy2ob[p] = ob
+    _p_list.append(ob)
+
+def create_link_pyobj(p, ob):
+    """NOT_RPYTHON: a link where the PyObject contains all the data.
+       from_obj() will not work on this 'p'."""
+    #print 'create_link_pyobj\n\t%s\n\t%s' % (p, ob)
+    assert p not in _pypy2ob
+    #assert not ob.c_ob_pypy_link
+    ob.c_ob_pypy_link = _build_pypy_link(p)
+    _o_list.append(ob)
+
+def from_obj(OB_PTR_TYPE, p):
+    "NOT_RPYTHON"
+    ob = _pypy2ob.get(p)
+    if ob is None:
+        return lltype.nullptr(OB_PTR_TYPE.TO)
+    assert lltype.typeOf(ob) == OB_PTR_TYPE
+    return ob
+
+def to_obj(Class, ob):
+    "NOT_RPYTHON"
+    link = ob.c_ob_pypy_link
+    if link == 0:
+        return None
+    p = _adr2pypy[link]
+    assert isinstance(p, Class)
+    return p
+
+def next_dead(OB_PTR_TYPE):
+    if len(_d_list) == 0:
+        return lltype.nullptr(OB_PTR_TYPE.TO)
+    ob = _d_list.pop()
+    assert lltype.typeOf(ob) == OB_PTR_TYPE
+    return ob
+
+def _collect(track_allocation=True):
+    """NOT_RPYTHON: for tests only.  Emulates a GC collection.
+    Will invoke dealloc_trigger_callback() once if there are objects
+    whose _Py_Dealloc() should be called.
+    """
+    def detach(ob, wr_list):
+        assert ob.c_ob_refcnt >= REFCNT_FROM_PYPY
+        assert ob.c_ob_pypy_link
+        p = _adr2pypy[ob.c_ob_pypy_link]
+        assert p is not None
+        _adr2pypy[ob.c_ob_pypy_link] = None
+        wr_list.append((ob, weakref.ref(p)))
+        return p
+
+    global _p_list, _o_list
+    wr_p_list = []
+    new_p_list = []
+    for ob in reversed(_p_list):
+        if ob.c_ob_refcnt not in (REFCNT_FROM_PYPY, REFCNT_FROM_PYPY_LIGHT):
+            new_p_list.append(ob)
+        else:
+            p = detach(ob, wr_p_list)
+            del _pypy2ob[p]
+            del p
+        ob = None
+    _p_list = Ellipsis
+
+    wr_o_list = []
+    for ob in reversed(_o_list):
+        detach(ob, wr_o_list)
+    _o_list = Ellipsis
+
+    rgc.collect()  # forces the cycles to be resolved and the weakrefs to die
+    rgc.collect()
+    rgc.collect()
+
+    def attach(ob, wr, final_list):
+        assert ob.c_ob_refcnt >= REFCNT_FROM_PYPY
+        p = wr()
+        if p is not None:
+            assert ob.c_ob_pypy_link
+            _adr2pypy[ob.c_ob_pypy_link] = p
+            final_list.append(ob)
+            return p
+        else:
+            ob.c_ob_pypy_link = 0
+            if ob.c_ob_refcnt >= REFCNT_FROM_PYPY_LIGHT:
+                ob.c_ob_refcnt -= REFCNT_FROM_PYPY_LIGHT
+                ob.c_ob_pypy_link = 0
+                if ob.c_ob_refcnt == 0:
+                    lltype.free(ob, flavor='raw',
+                                track_allocation=track_allocation)
+            else:
+                assert ob.c_ob_refcnt >= REFCNT_FROM_PYPY
+                assert ob.c_ob_refcnt < int(REFCNT_FROM_PYPY_LIGHT * 0.99)
+                ob.c_ob_refcnt -= REFCNT_FROM_PYPY
+                ob.c_ob_pypy_link = 0
+                if ob.c_ob_refcnt == 0:
+                    _d_list.append(ob)
+            return None
+
+    _p_list = new_p_list
+    for ob, wr in wr_p_list:
+        p = attach(ob, wr, _p_list)
+        if p is not None:
+            _pypy2ob[p] = ob
+    _o_list = []
+    for ob, wr in wr_o_list:
+        attach(ob, wr, _o_list)
+
+    if _d_list:
+        res = _dealloc_trigger_callback()
+        if res == "RETRY":
+            _collect(track_allocation=track_allocation)
+
+_keepalive_forever = set()
+def _dont_free_any_more():
+    "Make sure that any object still referenced won't be freed any more."
+    for ob in _p_list + _o_list:
+        _keepalive_forever.add(to_obj(object, ob))
+    del _d_list[:]
+
+# ____________________________________________________________
+
+
+def _unspec_p(hop, v_p):
+    assert isinstance(v_p.concretetype, lltype.Ptr)
+    assert v_p.concretetype.TO._gckind == 'gc'
+    return hop.genop('cast_opaque_ptr', [v_p], resulttype=llmemory.GCREF)
+
+def _unspec_ob(hop, v_ob):
+    assert isinstance(v_ob.concretetype, lltype.Ptr)
+    assert v_ob.concretetype.TO._gckind == 'raw'
+    return hop.genop('cast_ptr_to_adr', [v_ob], resulttype=llmemory.Address)
+
+def _spec_p(hop, v_p):
+    assert v_p.concretetype == llmemory.GCREF
+    return hop.genop('cast_opaque_ptr', [v_p],
+                     resulttype=hop.r_result.lowleveltype)
+
+def _spec_ob(hop, v_ob):
+    assert v_ob.concretetype == llmemory.Address
+    return hop.genop('cast_adr_to_ptr', [v_ob],
+                     resulttype=hop.r_result.lowleveltype)
+
+
+class Entry(ExtRegistryEntry):
+    _about_ = init
+
+    def compute_result_annotation(self, s_dealloc_callback):
+        from rpython.rtyper.llannotation import SomePtr
+        assert isinstance(s_dealloc_callback, SomePtr)   # ll-ptr-to-function
+
+    def specialize_call(self, hop):
+        hop.exception_cannot_occur()
+        [v_dealloc_callback] = hop.inputargs(hop.args_r[0])
+        hop.genop('gc_rawrefcount_init', [v_dealloc_callback])
+
+
+class Entry(ExtRegistryEntry):
+    _about_ = (create_link_pypy, create_link_pyobj)
+
+    def compute_result_annotation(self, s_p, s_ob):
+        pass
+
+    def specialize_call(self, hop):
+        if self.instance is create_link_pypy:
+            name = 'gc_rawrefcount_create_link_pypy'
+        elif self.instance is create_link_pyobj:
+            name = 'gc_rawrefcount_create_link_pyobj'
+        v_p, v_ob = hop.inputargs(*hop.args_r)
+        hop.exception_cannot_occur()
+        hop.genop(name, [_unspec_p(hop, v_p), _unspec_ob(hop, v_ob)])
+
+
+class Entry(ExtRegistryEntry):
+    _about_ = from_obj
+
+    def compute_result_annotation(self, s_OB_PTR_TYPE, s_p):
+        from rpython.annotator import model as annmodel
+        from rpython.rtyper.llannotation import lltype_to_annotation
+        assert (isinstance(s_p, annmodel.SomeInstance) or
+                    annmodel.s_None.contains(s_p))
+        assert s_OB_PTR_TYPE.is_constant()
+        return lltype_to_annotation(s_OB_PTR_TYPE.const)
+
+    def specialize_call(self, hop):
+        hop.exception_cannot_occur()
+        v_p = hop.inputarg(hop.args_r[1], arg=1)
+        v_ob = hop.genop('gc_rawrefcount_from_obj', [_unspec_p(hop, v_p)],
+                         resulttype = llmemory.Address)
+        return _spec_ob(hop, v_ob)
+
+class Entry(ExtRegistryEntry):
+    _about_ = to_obj
+
+    def compute_result_annotation(self, s_Class, s_ob):
+        from rpython.annotator import model as annmodel
+        from rpython.rtyper.llannotation import SomePtr
+        assert isinstance(s_ob, SomePtr)
+        assert s_Class.is_constant()
+        classdef = self.bookkeeper.getuniqueclassdef(s_Class.const)
+        return annmodel.SomeInstance(classdef, can_be_None=True)
+
+    def specialize_call(self, hop):
+        hop.exception_cannot_occur()
+        v_ob = hop.inputarg(hop.args_r[1], arg=1)
+        v_p = hop.genop('gc_rawrefcount_to_obj', [_unspec_ob(hop, v_ob)],
+                        resulttype = llmemory.GCREF)
+        return _spec_p(hop, v_p)
+
+class Entry(ExtRegistryEntry):
+    _about_ = next_dead
+
+    def compute_result_annotation(self, s_OB_PTR_TYPE):
+        from rpython.annotator import model as annmodel
+        from rpython.rtyper.llannotation import lltype_to_annotation
+        assert s_OB_PTR_TYPE.is_constant()
+        return lltype_to_annotation(s_OB_PTR_TYPE.const)
+
+    def specialize_call(self, hop):
+        hop.exception_cannot_occur()
+        v_ob = hop.genop('gc_rawrefcount_next_dead', [],
+                         resulttype = llmemory.Address)
+        return _spec_ob(hop, v_ob)
diff --git a/rpython/rlib/rgc.py b/rpython/rlib/rgc.py
--- a/rpython/rlib/rgc.py
+++ b/rpython/rlib/rgc.py
@@ -487,6 +487,7 @@
 class _GcRef(object):
     # implementation-specific: there should not be any after translation
     __slots__ = ['_x', '_handle']
+    _TYPE = llmemory.GCREF
     def __init__(self, x):
         self._x = x
     def __hash__(self):
diff --git a/rpython/rlib/test/test_rawrefcount.py 
b/rpython/rlib/test/test_rawrefcount.py
new file mode 100644
--- /dev/null
+++ b/rpython/rlib/test/test_rawrefcount.py
@@ -0,0 +1,268 @@
+import weakref
+from rpython.rlib import rawrefcount, objectmodel, rgc
+from rpython.rlib.rawrefcount import REFCNT_FROM_PYPY, REFCNT_FROM_PYPY_LIGHT
+from rpython.rtyper.lltypesystem import lltype, llmemory
+from rpython.rtyper.annlowlevel import llhelper
+from rpython.translator.c.test.test_standalone import StandaloneTests
+from rpython.config.translationoption import get_combined_translation_config
+
+
+class W_Root(object):
+    def __init__(self, intval=0):
+        self.intval = intval
+    def __nonzero__(self):
+        raise Exception("you cannot do that, you must use space.is_true()")
+
+PyObjectS = lltype.Struct('PyObjectS',
+                          ('c_ob_refcnt', lltype.Signed),
+                          ('c_ob_pypy_link', lltype.Signed))
+PyObject = lltype.Ptr(PyObjectS)
+
+
+class TestRawRefCount:
+
+    def setup_method(self, meth):
+        rawrefcount.init()
+
+    def test_create_link_pypy(self):
+        p = W_Root(42)
+        ob = lltype.malloc(PyObjectS, flavor='raw', zero=True)
+        assert rawrefcount.from_obj(PyObject, p) == lltype.nullptr(PyObjectS)
+        assert rawrefcount.to_obj(W_Root, ob) == None
+        rawrefcount.create_link_pypy(p, ob)
+        assert ob.c_ob_refcnt == 0
+        ob.c_ob_refcnt += REFCNT_FROM_PYPY_LIGHT
+        assert rawrefcount.from_obj(PyObject, p) == ob
+        assert rawrefcount.to_obj(W_Root, ob) == p
+        lltype.free(ob, flavor='raw')
+
+    def test_create_link_pyobj(self):
+        p = W_Root(42)
+        ob = lltype.malloc(PyObjectS, flavor='raw', zero=True)
+        assert rawrefcount.from_obj(PyObject, p) == lltype.nullptr(PyObjectS)
+        assert rawrefcount.to_obj(W_Root, ob) == None
+        rawrefcount.create_link_pyobj(p, ob)
+        assert ob.c_ob_refcnt == 0
+        ob.c_ob_refcnt += REFCNT_FROM_PYPY
+        assert rawrefcount.from_obj(PyObject, p) == lltype.nullptr(PyObjectS)
+        assert rawrefcount.to_obj(W_Root, ob) == p
+        lltype.free(ob, flavor='raw')
+
+    def test_collect_p_dies(self):
+        p = W_Root(42)
+        ob = lltype.malloc(PyObjectS, flavor='raw', zero=True)
+        rawrefcount.create_link_pypy(p, ob)
+        ob.c_ob_refcnt += REFCNT_FROM_PYPY_LIGHT
+        assert rawrefcount._p_list == [ob]
+        wr_ob = weakref.ref(ob)
+        wr_p = weakref.ref(p)
+        del ob, p
+        rawrefcount._collect()
+        assert rawrefcount._p_list == []
+        assert wr_ob() is None
+        assert wr_p() is None
+
+    def test_collect_p_keepalive_pyobject(self):
+        p = W_Root(42)
+        ob = lltype.malloc(PyObjectS, flavor='raw', zero=True)
+        rawrefcount.create_link_pypy(p, ob)
+        ob.c_ob_refcnt += REFCNT_FROM_PYPY_LIGHT
+        assert rawrefcount._p_list == [ob]
+        wr_ob = weakref.ref(ob)
+        wr_p = weakref.ref(p)
+        ob.c_ob_refcnt += 1      # <=
+        del ob, p
+        rawrefcount._collect()
+        ob = wr_ob()
+        p = wr_p()
+        assert ob is not None and p is not None
+        assert rawrefcount._p_list == [ob]
+        assert rawrefcount.to_obj(W_Root, ob) == p
+        assert rawrefcount.from_obj(PyObject, p) == ob
+        lltype.free(ob, flavor='raw')
+
+    def test_collect_p_keepalive_w_root(self):
+        p = W_Root(42)
+        ob = lltype.malloc(PyObjectS, flavor='raw', zero=True)
+        rawrefcount.create_link_pypy(p, ob)
+        ob.c_ob_refcnt += REFCNT_FROM_PYPY_LIGHT
+        assert rawrefcount._p_list == [ob]
+        wr_ob = weakref.ref(ob)
+        del ob       # p remains
+        rawrefcount._collect()
+        ob = wr_ob()
+        assert ob is not None
+        assert rawrefcount._p_list == [ob]
+        assert rawrefcount.to_obj(W_Root, ob) == p
+        assert rawrefcount.from_obj(PyObject, p) == ob
+        lltype.free(ob, flavor='raw')
+
+    def test_collect_o_dies(self):
+        trigger = []; rawrefcount.init(lambda: trigger.append(1))
+        p = W_Root(42)
+        ob = lltype.malloc(PyObjectS, flavor='raw', zero=True)
+        rawrefcount.create_link_pyobj(p, ob)
+        ob.c_ob_refcnt += REFCNT_FROM_PYPY
+        assert rawrefcount._o_list == [ob]
+        wr_ob = weakref.ref(ob)
+        wr_p = weakref.ref(p)
+        del ob, p
+        rawrefcount._collect()
+        ob = wr_ob()
+        assert ob is not None
+        assert trigger == [1]
+        assert rawrefcount.next_dead(PyObject) == ob
+        assert rawrefcount.next_dead(PyObject) == lltype.nullptr(PyObjectS)
+        assert rawrefcount.next_dead(PyObject) == lltype.nullptr(PyObjectS)
+        assert rawrefcount._o_list == []
+        assert wr_p() is None
+        assert ob.c_ob_refcnt == 0
+        assert ob.c_ob_pypy_link == 0
+        lltype.free(ob, flavor='raw')
+
+    def test_collect_o_keepalive_pyobject(self):
+        p = W_Root(42)
+        ob = lltype.malloc(PyObjectS, flavor='raw', zero=True)
+        p.pyobj = ob
+        rawrefcount.create_link_pyobj(p, ob)
+        ob.c_ob_refcnt += REFCNT_FROM_PYPY
+        assert rawrefcount._o_list == [ob]
+        wr_ob = weakref.ref(ob)
+        wr_p = weakref.ref(p)
+        ob.c_ob_refcnt += 1      # <=
+        del p
+        rawrefcount._collect()
+        p = wr_p()
+        assert p is None            # was unlinked
+        assert ob.c_ob_refcnt == 1    # != REFCNT_FROM_PYPY_OBJECT + 1
+        assert rawrefcount._o_list == []
+        assert rawrefcount.to_obj(W_Root, ob) == None
+        lltype.free(ob, flavor='raw')
+
+    def test_collect_o_keepalive_w_root(self):
+        p = W_Root(42)
+        ob = lltype.malloc(PyObjectS, flavor='raw', zero=True)
+        p.pyobj = ob
+        rawrefcount.create_link_pyobj(p, ob)
+        ob.c_ob_refcnt += REFCNT_FROM_PYPY
+        assert rawrefcount._o_list == [ob]
+        wr_ob = weakref.ref(ob)
+        del ob       # p remains
+        rawrefcount._collect()
+        ob = wr_ob()
+        assert ob is not None
+        assert rawrefcount._o_list == [ob]
+        assert rawrefcount.to_obj(W_Root, ob) == p
+        assert p.pyobj == ob
+        lltype.free(ob, flavor='raw')
+
+    def test_collect_s_dies(self):
+        trigger = []; rawrefcount.init(lambda: trigger.append(1))
+        p = W_Root(42)
+        ob = lltype.malloc(PyObjectS, flavor='raw', zero=True)
+        rawrefcount.create_link_pypy(p, ob)
+        ob.c_ob_refcnt += REFCNT_FROM_PYPY
+        assert rawrefcount._p_list == [ob]
+        wr_ob = weakref.ref(ob)
+        wr_p = weakref.ref(p)
+        del ob, p
+        rawrefcount._collect()
+        ob = wr_ob()
+        assert ob is not None
+        assert trigger == [1]
+        assert rawrefcount._d_list == [ob]
+        assert rawrefcount._p_list == []
+        assert wr_p() is None
+        assert ob.c_ob_refcnt == 0
+        assert ob.c_ob_pypy_link == 0
+        lltype.free(ob, flavor='raw')
+
+    def test_collect_s_keepalive_pyobject(self):
+        p = W_Root(42)
+        ob = lltype.malloc(PyObjectS, flavor='raw', zero=True)
+        p.pyobj = ob
+        rawrefcount.create_link_pypy(p, ob)
+        ob.c_ob_refcnt += REFCNT_FROM_PYPY
+        assert rawrefcount._p_list == [ob]
+        wr_ob = weakref.ref(ob)
+        wr_p = weakref.ref(p)
+        ob.c_ob_refcnt += 1      # <=
+        del ob, p
+        rawrefcount._collect()
+        ob = wr_ob()
+        p = wr_p()
+        assert ob is not None and p is not None
+        assert rawrefcount._p_list == [ob]
+        assert rawrefcount.to_obj(W_Root, ob) == p
+        lltype.free(ob, flavor='raw')
+
+    def test_collect_s_keepalive_w_root(self):
+        p = W_Root(42)
+        ob = lltype.malloc(PyObjectS, flavor='raw', zero=True)
+        p.pyobj = ob
+        rawrefcount.create_link_pypy(p, ob)
+        ob.c_ob_refcnt += REFCNT_FROM_PYPY
+        assert rawrefcount._p_list == [ob]
+        wr_ob = weakref.ref(ob)
+        del ob       # p remains
+        rawrefcount._collect()
+        ob = wr_ob()
+        assert ob is not None
+        assert rawrefcount._p_list == [ob]
+        assert rawrefcount.to_obj(W_Root, ob) == p
+        lltype.free(ob, flavor='raw')
+
+
+class TestTranslated(StandaloneTests):
+
+    def test_full_translation(self):
+        class State:
+            pass
+        state = State()
+        state.seen = []
+        def dealloc_trigger():
+            state.seen.append(1)
+
+        def make_p():
+            p = W_Root(42)
+            ob = lltype.malloc(PyObjectS, flavor='raw', zero=True)
+            rawrefcount.create_link_pypy(p, ob)
+            ob.c_ob_refcnt += REFCNT_FROM_PYPY
+            assert rawrefcount.from_obj(PyObject, p) == ob
+            assert rawrefcount.to_obj(W_Root, ob) == p
+            return ob, p
+
+        FTYPE = rawrefcount.RAWREFCOUNT_DEALLOC_TRIGGER
+
+        def entry_point(argv):
+            ll_dealloc_trigger_callback = llhelper(FTYPE, dealloc_trigger)
+            rawrefcount.init(ll_dealloc_trigger_callback)
+            ob, p = make_p()
+            if state.seen != []:
+                print "OB COLLECTED REALLY TOO SOON"
+                return 1
+            rgc.collect()
+            if state.seen != []:
+                print "OB COLLECTED TOO SOON"
+                return 1
+            objectmodel.keepalive_until_here(p)
+            p = None
+            rgc.collect()
+            if state.seen != [1]:
+                print "OB NOT COLLECTED"
+                return 1
+            if rawrefcount.next_dead(PyObject) != ob:
+                print "NEXT_DEAD != OB"
+                return 1
+            if rawrefcount.next_dead(PyObject) != lltype.nullptr(PyObjectS):
+                print "NEXT_DEAD second time != NULL"
+                return 1
+            print "OK!"
+            lltype.free(ob, flavor='raw')
+            return 0
+
+        self.config = get_combined_translation_config(translating=True)
+        self.config.translation.gc = "incminimark"
+        t, cbuilder = self.compile(entry_point)
+        data = cbuilder.cmdexec('hi there')
+        assert data.startswith('OK!\n')
diff --git a/rpython/rtyper/lltypesystem/ll2ctypes.py 
b/rpython/rtyper/lltypesystem/ll2ctypes.py
--- a/rpython/rtyper/lltypesystem/ll2ctypes.py
+++ b/rpython/rtyper/lltypesystem/ll2ctypes.py
@@ -515,8 +515,10 @@
                 struct_use_ctypes_storage(struct_container, struct_storage)
                 struct_container._setparentstructure(container, field_name)
             elif isinstance(FIELDTYPE, lltype.Array):
-                assert FIELDTYPE._hints.get('nolength', False) == False
-                arraycontainer = _array_of_known_length(FIELDTYPE)
+                if FIELDTYPE._hints.get('nolength', False):
+                    arraycontainer = _array_of_unknown_length(FIELDTYPE)
+                else:
+                    arraycontainer = _array_of_known_length(FIELDTYPE)
                 arraycontainer._storage = ctypes.pointer(
                     getattr(ctypes_storage.contents, field_name))
                 arraycontainer._setparentstructure(container, field_name)
@@ -567,6 +569,7 @@
             raise Exception("internal ll2ctypes error - "
                             "double conversion from lltype to ctypes?")
         # XXX don't store here immortal structures
+        print "LL2CTYPES:", addr
         ALLOCATED[addr] = self
 
     def _addressof_storage(self):
@@ -579,6 +582,7 @@
         self._check()   # no double-frees
         # allow the ctypes object to go away now
         addr = ctypes.cast(self._storage, ctypes.c_void_p).value
+        print "LL2C FREE:", addr
         try:
             del ALLOCATED[addr]
         except KeyError:
@@ -613,11 +617,14 @@
             return object.__hash__(self)
 
     def __repr__(self):
+        if '__str__' in self._TYPE._adtmeths:
+            r = self._TYPE._adtmeths['__str__'](self)
+        else:
+            r = 'C object %s' % (self._TYPE,)
         if self._storage is None:
-            return '<freed C object %s>' % (self._TYPE,)
+            return '<freed %s>' % (r,)
         else:
-            return '<C object %s at 0x%x>' % (self._TYPE,
-                                              fixid(self._addressof_storage()))
+            return '<%s at 0x%x>' % (r, fixid(self._addressof_storage()))
 
     def __str__(self):
         return repr(self)
@@ -942,7 +949,8 @@
                 REAL_TYPE = T.TO
                 if T.TO._arrayfld is not None:
                     carray = getattr(cobj.contents, T.TO._arrayfld)
-                    container = lltype._struct(T.TO, carray.length)
+                    length = getattr(carray, 'length', 9999)   # XXX
+                    container = lltype._struct(T.TO, length)
                 else:
                     # special treatment of 'OBJECT' subclasses
                     if get_rtyper() and lltype._castdepth(REAL_TYPE, OBJECT) 
>= 0:
diff --git a/rpython/rtyper/lltypesystem/lloperation.py 
b/rpython/rtyper/lltypesystem/lloperation.py
--- a/rpython/rtyper/lltypesystem/lloperation.py
+++ b/rpython/rtyper/lltypesystem/lloperation.py
@@ -503,6 +503,12 @@
     'gc_gcflag_extra'     : LLOp(),
     'gc_add_memory_pressure': LLOp(),
 
+    'gc_rawrefcount_init':              LLOp(),
+    'gc_rawrefcount_create_link_pypy':  LLOp(),
+    'gc_rawrefcount_create_link_pyobj': LLOp(),
+    'gc_rawrefcount_from_obj':          LLOp(sideeffects=False),
+    'gc_rawrefcount_to_obj':            LLOp(sideeffects=False),
+
     # ------- JIT & GC interaction, only for some GCs ----------
 
     'gc_adr_of_nursery_free' : LLOp(),
diff --git a/rpython/rtyper/lltypesystem/rffi.py 
b/rpython/rtyper/lltypesystem/rffi.py
--- a/rpython/rtyper/lltypesystem/rffi.py
+++ b/rpython/rtyper/lltypesystem/rffi.py
@@ -631,7 +631,8 @@
 
 def CExternVariable(TYPE, name, eci, _CConstantClass=CConstant,
                     sandboxsafe=False, _nowrapper=False,
-                    c_type=None, getter_only=False):
+                    c_type=None, getter_only=False,
+                    declare_as_extern=(sys.platform != 'win32')):
     """Return a pair of functions - a getter and a setter - to access
     the given global C variable.
     """
@@ -661,7 +662,7 @@
     c_setter = "void %(setter_name)s (%(c_type)s v) { %(name)s = v; }" % 
locals()
 
     lines = ["#include <%s>" % i for i in eci.includes]
-    if sys.platform != 'win32':
+    if declare_as_extern:
         lines.append('extern %s %s;' % (c_type, name))
     lines.append(c_getter)
     if not getter_only:
@@ -790,6 +791,12 @@
         return length
     str2chararray._annenforceargs_ = [strtype, None, int]
 
+    # s[start:start+length] -> already-existing char[],
+    # all characters including zeros
+    def str2rawmem(s, array, start, length):
+        ll_s = llstrtype(s)
+        copy_string_to_raw(ll_s, array, start, length)
+
     # char* -> str
     # doesn't free char*
     def charp2str(cp):
@@ -940,19 +947,19 @@
     return (str2charp, free_charp, charp2str,
             get_nonmovingbuffer, free_nonmovingbuffer,
             alloc_buffer, str_from_buffer, keep_buffer_alive_until_here,
-            charp2strn, charpsize2str, str2chararray,
+            charp2strn, charpsize2str, str2chararray, str2rawmem,
             )
 
 (str2charp, free_charp, charp2str,
  get_nonmovingbuffer, free_nonmovingbuffer,
  alloc_buffer, str_from_buffer, keep_buffer_alive_until_here,
- charp2strn, charpsize2str, str2chararray,
+ charp2strn, charpsize2str, str2chararray, str2rawmem,
  ) = make_string_mappings(str)
 
 (unicode2wcharp, free_wcharp, wcharp2unicode,
  get_nonmoving_unicodebuffer, free_nonmoving_unicodebuffer,
  alloc_unicodebuffer, unicode_from_buffer, keep_unicodebuffer_alive_until_here,
- wcharp2unicoden, wcharpsize2unicode, unicode2wchararray,
+ wcharp2unicoden, wcharpsize2unicode, unicode2wchararray, unicode2rawmem,
  ) = make_string_mappings(unicode)
 
 # char**
diff --git a/rpython/rtyper/tool/rffi_platform.py 
b/rpython/rtyper/tool/rffi_platform.py
--- a/rpython/rtyper/tool/rffi_platform.py
+++ b/rpython/rtyper/tool/rffi_platform.py
@@ -263,10 +263,11 @@
     """An entry in a CConfig class that stands for an externally
     defined structure.
     """
-    def __init__(self, name, interesting_fields, ifdef=None):
+    def __init__(self, name, interesting_fields, ifdef=None, adtmeths={}):
         self.name = name
         self.interesting_fields = interesting_fields
         self.ifdef = ifdef
+        self.adtmeths = adtmeths
 
     def prepare_code(self):
         if self.ifdef is not None:
@@ -313,7 +314,9 @@
                 offset = info['fldofs '  + fieldname]
                 size   = info['fldsize ' + fieldname]
                 sign   = info.get('fldunsigned ' + fieldname, False)
-                if (size, sign) != rffi.size_and_sign(fieldtype):
+                if is_array_nolength(fieldtype):
+                    pass       # ignore size and sign
+                elif (size, sign) != rffi.size_and_sign(fieldtype):
                     fieldtype = fixup_ctype(fieldtype, fieldname, (size, sign))
                 layout_addfield(layout, offset, fieldtype, fieldname)
 
@@ -353,7 +356,7 @@
             name = name[7:]
         else:
             hints['typedef'] = True
-        kwds = {'hints': hints}
+        kwds = {'hints': hints, 'adtmeths': self.adtmeths}
         return rffi.CStruct(name, *fields, **kwds)
 
 class SimpleType(CConfigEntry):
@@ -682,8 +685,14 @@
     def __repr__(self):
         return '<field %s: %s>' % (self.name, self.ctype)
 
+def is_array_nolength(TYPE):
+    return isinstance(TYPE, lltype.Array) and TYPE._hints.get('nolength', 
False)
+
 def layout_addfield(layout, offset, ctype, prefix):
-    size = _sizeof(ctype)
+    if is_array_nolength(ctype):
+        size = len(layout) - offset    # all the rest of the struct
+    else:
+        size = _sizeof(ctype)
     name = prefix
     i = 0
     while name in layout:
diff --git a/rpython/rtyper/tool/test/test_rffi_platform.py 
b/rpython/rtyper/tool/test/test_rffi_platform.py
--- a/rpython/rtyper/tool/test/test_rffi_platform.py
+++ b/rpython/rtyper/tool/test/test_rffi_platform.py
@@ -270,6 +270,19 @@
                                        [("d_name", 
lltype.FixedSizeArray(rffi.CHAR, 1))])
     assert dirent.c_d_name.length == 32
 
+def test_array_varsized_struct():
+    dirent = rffi_platform.getstruct("struct dirent",
+                                       """
+           struct dirent  /* for this example only, not the exact dirent */
+           {
+               int d_off;
+               char d_name[1];
+           };
+                                       """,
+                                       [("d_name", rffi.CArray(rffi.CHAR))])
+    assert rffi.offsetof(dirent, 'c_d_name') == 4
+    assert dirent.c_d_name == rffi.CArray(rffi.CHAR)
+
 def test_has_0001():
     assert rffi_platform.has("x", "int x = 3;")
     assert not rffi_platform.has("x", "")
_______________________________________________
pypy-commit mailing list
pypy-commit@python.org
https://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to