[pypy-commit] pypy cpyext-gc-cycle: Added additional flags for objects

stevie_92 pypy.commits at gmail.com
Fri Jan 11 05:38:55 EST 2019


Author: Stefan Beyer <home at sbeyer.at>
Branch: cpyext-gc-cycle
Changeset: r95598:c03fe327893a
Date: 2018-03-19 10:52 +0100
http://bitbucket.org/pypy/pypy/changeset/c03fe327893a/

Log:	Added additional flags for objects Implemented refcount overhead
	(for non-cyclic refcount) Implemented buffer for potential roots of
	cycles Fixed assert to allow for recursive cpyext calls Added some
	cycle detection tests from experimental branch (disabled now)

diff --git a/pypy/module/cpyext/api.py b/pypy/module/cpyext/api.py
--- a/pypy/module/cpyext/api.py
+++ b/pypy/module/cpyext/api.py
@@ -208,10 +208,11 @@
 # running and should not themselves release the GIL).
 #
 # **make_generic_cpy_call():** RPython to C, with the GIL held.  Before
-# the call, must assert that the global variable is 0 and set the
-# current thread identifier into the global variable.  After the call,
-# assert that the global variable still contains the current thread id,
-# and reset it to 0.
+# the call, must assert that the global variable is 0 or the current
+# thread identifier (recursive call) and set the current thread identifier
+# into the global variable.  After the call, assert that the global variable
+# still contains the current thread id, and reset it to the value it held
+# before the call.
 #
 # **make_wrapper():** C to RPython; by default assume that the GIL is
 # held, but accepts gil="acquire", "release", "around",
@@ -1763,7 +1764,8 @@
 
         # see "Handling of the GIL" above
         tid = rthread.get_ident()
-        assert cpyext_glob_tid_ptr[0] == 0
+        tid_before = cpyext_glob_tid_ptr[0]
+        assert tid_before == 0 or tid_before == tid
         cpyext_glob_tid_ptr[0] = tid
 
         preexist_error = PyErr_Occurred(space)
@@ -1772,7 +1774,7 @@
             result = call_external_function(func, *boxed_args)
         finally:
             assert cpyext_glob_tid_ptr[0] == tid
-            cpyext_glob_tid_ptr[0] = 0
+            cpyext_glob_tid_ptr[0] = tid_before
             for i, ARG in unrolling_arg_types:
                 # note that this loop is nicely unrolled statically by RPython
                 _pyobj = to_decref[i]
diff --git a/pypy/module/cpyext/include/boolobject.h b/pypy/module/cpyext/include/boolobject.h
--- a/pypy/module/cpyext/include/boolobject.h
+++ b/pypy/module/cpyext/include/boolobject.h
@@ -13,8 +13,8 @@
 #define Py_True ((PyObject *) &_Py_TrueStruct)
 
 /* Macros for returning Py_True or Py_False, respectively */
-#define Py_RETURN_TRUE return Py_INCREF(Py_True), Py_True
-#define Py_RETURN_FALSE return Py_INCREF(Py_False), Py_False
+#define Py_RETURN_TRUE do { Py_INCREF(Py_True); return Py_True; } while(0)
+#define Py_RETURN_FALSE do { Py_INCREF(Py_False); return Py_False; } while(0)
 
 #ifdef __cplusplus
 }
diff --git a/pypy/module/cpyext/include/object.h b/pypy/module/cpyext/include/object.h
--- a/pypy/module/cpyext/include/object.h
+++ b/pypy/module/cpyext/include/object.h
@@ -2,6 +2,7 @@
 #define Py_OBJECT_H
 
 #include <stdio.h>
+#include <math.h>
 
 #ifdef __cplusplus
 extern "C" {
@@ -12,7 +13,13 @@
 #define PY_SSIZE_T_MAX ((Py_ssize_t)(((size_t)-1)>>1))
 #define PY_SSIZE_T_MIN (-PY_SSIZE_T_MAX-1)
 
-#define Py_RETURN_NONE return Py_INCREF(Py_None), Py_None
+#define PY_REFCNT_FROM_PYPY (4L << ((long)(log(PY_SSIZE_T_MAX) / log(2) - 2)))
+#define PY_REFCNT_GREEN (4L << ((long)(log(PY_SSIZE_T_MAX) / log(2) - 7)))
+#define PY_REFCNT_OVERFLOW (1L << ((long)(log(PY_SSIZE_T_MAX) / log(2) - 7) / 2L - 1L))
+#define PY_REFCNT_MASK ((PY_REFCNT_OVERFLOW << 1L) - 1L)
+#define Py_RETURN_NONE return (((((PyObject *)(Py_None))->ob_refcnt & PY_REFCNT_OVERFLOW) == 0) ? \
+                              ((PyObject *)(Py_None))->ob_refcnt++ : Py_IncRef((PyObject *)(Py_None))), Py_None
+
 
 /*
 CPython has this for backwards compatibility with really old extensions, and now
@@ -34,14 +41,21 @@
 #define Py_XDECREF(ob)  (Py_DecRef((PyObject *)(ob)))
 #else
 /* Fast version */
-#define Py_INCREF(ob)   (((PyObject *)(ob))->ob_refcnt++)
-#define Py_DECREF(op)                                   \
-    do {                                                \
-        if (--((PyObject *)(op))->ob_refcnt != 0)       \
-            ;                                           \
-        else                                            \
-            _Py_Dealloc((PyObject *)(op));              \
-    } while (0)
+#define Py_INCREF(ob)   do {                                                             \
+                            if (!(((PyObject *)(ob))->ob_refcnt & PY_REFCNT_OVERFLOW))   \
+                                ((PyObject *)(ob))->ob_refcnt++;                         \
+                            else                                                         \
+                                Py_IncRef((PyObject *)(ob));                            \
+                        } while (0)
+#define Py_DECREF(ob)   do {                                                                 \
+                            if (!(((PyObject *)(ob))->ob_refcnt & PY_REFCNT_GREEN) ||        \
+                                (((PyObject *)(ob))->ob_refcnt & PY_REFCNT_OVERFLOW))        \
+                                Py_DecRef((PyObject *)(ob));                                \
+                            else if (--((PyObject *)(ob))->ob_refcnt & PY_REFCNT_MASK)       \
+                                ;                                                            \
+                            else if ((!((PyObject *)(ob))->ob_refcnt) & PY_REFCNT_FROM_PYPY) \
+                                _Py_Dealloc((PyObject *)(ob));                               \
+                        } while (0)
 
 #define Py_XINCREF(op) do { if ((op) == NULL) ; else Py_INCREF(op); } while (0)
 #define Py_XDECREF(op) do { if ((op) == NULL) ; else Py_DECREF(op); } while (0)
@@ -61,7 +75,8 @@
                 }				\
         } while (0)
 
-#define Py_REFCNT(ob)		(((PyObject*)(ob))->ob_refcnt)
+#define Py_REFCNT(ob) ((((PyObject *)(ob))->ob_refcnt & PY_REFCNT_OVERFLOW == 0) ?  \
+                      (((PyObject*)(ob))->ob_refcnt & PY_REFCNT_MASK) : _Py_RefCnt_Overflow(ob))
 #define Py_TYPE(ob)		(((PyObject*)(ob))->ob_type)
 #define Py_SIZE(ob)		(((PyVarObject*)(ob))->ob_size)
 
diff --git a/pypy/module/cpyext/object.py b/pypy/module/cpyext/object.py
--- a/pypy/module/cpyext/object.py
+++ b/pypy/module/cpyext/object.py
@@ -11,6 +11,7 @@
 from pypy.module.cpyext.pyerrors import PyErr_NoMemory, PyErr_BadInternalCall
 from pypy.objspace.std.typeobject import W_TypeObject
 from pypy.interpreter.error import OperationError, oefmt
+from rpython.rlib.rawrefcount import REFCNT_MASK
 import pypy.module.__builtin__.operation as operation
 
 
@@ -50,7 +51,7 @@
 def _dealloc(space, obj):
     # This frees an object after its refcount dropped to zero, so we
     # assert that it is really zero here.
-    assert obj.c_ob_refcnt == 0
+    assert obj.c_ob_refcnt & REFCNT_MASK == 0
     pto = obj.c_ob_type
     obj_voidp = rffi.cast(rffi.VOIDP, obj)
     generic_cpy_call(space, pto.c_tp_free, obj_voidp)
diff --git a/pypy/module/cpyext/pyobject.py b/pypy/module/cpyext/pyobject.py
--- a/pypy/module/cpyext/pyobject.py
+++ b/pypy/module/cpyext/pyobject.py
@@ -17,8 +17,13 @@
 from rpython.rlib.objectmodel import keepalive_until_here
 from rpython.rtyper.annlowlevel import llhelper, cast_instance_to_base_ptr
 from rpython.rlib import rawrefcount, jit
-from rpython.rlib.debug import ll_assert, fatalerror
-
+from rpython.rlib.debug import ll_assert, fatalerror, debug_print
+from rpython.rlib.rawrefcount import (
+    REFCNT_MASK, REFCNT_FROM_PYPY, REFCNT_OVERFLOW, REFCNT_CYCLE_BUFFERED,
+    REFCNT_CLR_MASK, REFCNT_CLR_GREEN, REFCNT_CLR_PURPLE,
+    W_MARKER_DEALLOCATING)
+from pypy.module.cpyext.api import slot_function
+from pypy.module.cpyext.typeobjectdefs import visitproc
 
 #________________________________________________________
 # type description
@@ -249,8 +254,6 @@
     w_obj._cpyext_attach_pyobj(space, py_obj)
 
 
-w_marker_deallocating = W_Root()
-
 @jit.dont_look_inside
 def from_ref(space, ref):
     """
@@ -262,7 +265,7 @@
         return None
     w_obj = rawrefcount.to_obj(W_Root, ref)
     if w_obj is not None:
-        if w_obj is not w_marker_deallocating:
+        if w_obj is not W_MARKER_DEALLOCATING:
             return w_obj
         fatalerror(
             "*** Invalid usage of a dying CPython object ***\n"
@@ -315,7 +318,7 @@
 
 def pyobj_has_w_obj(pyobj):
     w_obj = rawrefcount.to_obj(W_Root, pyobj)
-    return w_obj is not None and w_obj is not w_marker_deallocating
+    return w_obj is not None and w_obj is not W_MARKER_DEALLOCATING
 
 def w_obj_has_pyobj(w_obj):
     return bool(rawrefcount.from_obj(PyObject, w_obj))
@@ -341,7 +344,7 @@
     pyobj = as_pyobj(space, w_obj, w_userdata, immortal=immortal)
     if pyobj:  # != NULL
         assert pyobj.c_ob_refcnt >= rawrefcount.REFCNT_FROM_PYPY
-        pyobj.c_ob_refcnt += 1
+        rawrefcount.incref(pyobj)
         keepalive_until_here(w_obj)
     return pyobj
 
@@ -375,7 +378,7 @@
     pyobj = rffi.cast(PyObject, pyobj)
     w_obj = from_ref(space, pyobj)
     if pyobj:
-        pyobj.c_ob_refcnt -= 1
+        rawrefcount.decref(pyobj)
         assert pyobj.c_ob_refcnt >= rawrefcount.REFCNT_FROM_PYPY
         keepalive_until_here(w_obj)
     return w_obj
@@ -386,7 +389,7 @@
     assert is_pyobj(pyobj)
     pyobj = rffi.cast(PyObject, pyobj)
     assert pyobj.c_ob_refcnt >= 1
-    pyobj.c_ob_refcnt += 1
+    rawrefcount.incref(pyobj)
 
 @specialize.ll()
 def decref(space, pyobj):
@@ -394,23 +397,64 @@
     assert is_pyobj(pyobj)
     pyobj = rffi.cast(PyObject, pyobj)
     if pyobj:
-        assert pyobj.c_ob_refcnt > 0
-        assert (pyobj.c_ob_pypy_link == 0 or
-                pyobj.c_ob_refcnt > rawrefcount.REFCNT_FROM_PYPY)
-        pyobj.c_ob_refcnt -= 1
-        if pyobj.c_ob_refcnt == 0:
-            state = space.fromcache(State)
-            generic_cpy_call(space, state.C._Py_Dealloc, pyobj)
+        rawrefcount.decref(pyobj)
+        rc = pyobj.c_ob_refcnt
+        if rc & REFCNT_MASK == 0:
+            if rc & REFCNT_FROM_PYPY == 0 and rc & REFCNT_CLR_MASK != REFCNT_CLR_PURPLE:
+                state = space.fromcache(State)
+                generic_cpy_call(space, state.C._Py_Dealloc, pyobj)
+        elif rc & REFCNT_CLR_MASK != REFCNT_CLR_GREEN:
+            possible_root(space, pyobj)
         #else:
         #    w_obj = rawrefcount.to_obj(W_Root, ref)
         #    if w_obj is not None:
         #        assert pyobj.c_ob_refcnt >= rawrefcount.REFCNT_FROM_PYPY
 
+ at jit.dont_look_inside
+def possible_root(space, obj):
+    #debug_print("possible root", obj)
+    rc = obj.c_ob_refcnt
+    if not obj.c_ob_type or not obj.c_ob_type.c_tp_traverse:
+        #debug_print("mark green", obj)
+        rc = rc & ~REFCNT_CLR_MASK | REFCNT_CLR_GREEN
+    elif rc & REFCNT_CLR_MASK != REFCNT_CLR_PURPLE:
+        rc = rc & ~REFCNT_CLR_MASK | REFCNT_CLR_PURPLE
+        if rc & REFCNT_CYCLE_BUFFERED == 0:
+            #debug_print("mark purple", obj)
+            rawrefcount.buffer_pyobj(obj)
+            rc = rc | REFCNT_CYCLE_BUFFERED
+    obj.c_ob_refcnt = rc
+
+ at cpython_api([PyObject], lltype.Void)
+def Py_IncRef(space, obj):
+    incref(space, obj)
+
+ at cpython_api([PyObject], lltype.Void)
+def Py_DecRef(space, obj):
+    decref(space, obj)
+
+ at cpython_api([PyObject], lltype.SignedLongLong, error=CANNOT_FAIL)
+def _Py_RefCnt_Overflow(space, obj):
+    return refcnt_overflow(space, obj)
+
+ at specialize.ll()
+def refcnt_overflow(space, obj):
+    if is_pyobj(obj):
+        pyobj = rffi.cast(PyObject, obj)
+    else:
+        pyobj = as_pyobj(space, obj, None)
+    if pyobj:
+        if pyobj.c_ob_refcnt & REFCNT_MASK == REFCNT_OVERFLOW:
+            return REFCNT_OVERFLOW
+        else:
+            return (pyobj.c_ob_refcnt & REFCNT_MASK) + \
+                rawrefcount.overflow_get(pyobj)
+    return 0
 
 @init_function
 def write_w_marker_deallocating(space):
     if we_are_translated():
-        llptr = cast_instance_to_base_ptr(w_marker_deallocating)
+        llptr = cast_instance_to_base_ptr(W_MARKER_DEALLOCATING)
         state = space.fromcache(State)
         state.C.set_marker(rffi.cast(Py_ssize_t, llptr))
 
diff --git a/pypy/module/cpyext/src/object.c b/pypy/module/cpyext/src/object.c
--- a/pypy/module/cpyext/src/object.c
+++ b/pypy/module/cpyext/src/object.c
@@ -5,6 +5,7 @@
 extern void _PyPy_Free(void *ptr);
 extern void *_PyPy_Malloc(Py_ssize_t size);
 
+/*
 void
 Py_IncRef(PyObject *o)
 {
@@ -16,6 +17,7 @@
 {
     Py_XDECREF(o);
 }
+*/
 
 /* 
  * The actual value of this variable will be the address of
diff --git a/pypy/module/cpyext/test/test_bytesobject.py b/pypy/module/cpyext/test/test_bytesobject.py
--- a/pypy/module/cpyext/test/test_bytesobject.py
+++ b/pypy/module/cpyext/test/test_bytesobject.py
@@ -9,10 +9,12 @@
     PyString_ConcatAndDel, PyString_Format, PyString_InternFromString,
     PyString_AsEncodedObject, PyString_AsDecodedObject, _PyString_Eq,
     _PyString_Join)
-from pypy.module.cpyext.api import PyObjectP, PyObject, Py_ssize_tP, generic_cpy_call
-from pypy.module.cpyext.pyobject import decref, from_ref, make_ref
+from pypy.module.cpyext.api import (
+    PyObjectP, PyObject, Py_ssize_tP, generic_cpy_call)
+from pypy.module.cpyext.pyobject import (
+    Py_DecRef, Py_IncRef, _Py_RefCnt_Overflow, from_ref, make_ref, decref)
 from pypy.module.cpyext.buffer import PyObject_AsCharBuffer
-from pypy.module.cpyext.api import PyTypeObjectPtr
+from rpython.rlib import rawrefcount
 
 
 class AppTestBytesObject(AppTestCpythonExtensionBase):
@@ -510,9 +512,9 @@
         ref = make_ref(space, space.wrap('abc'))
         ptr = lltype.malloc(PyObjectP.TO, 1, flavor='raw')
         ptr[0] = ref
-        prev_refcnt = ref.c_ob_refcnt
+        prev_refcnt = ref.c_ob_refcnt & rawrefcount.REFCNT_MASK
         PyString_Concat(space, ptr, space.wrap('def'))
-        assert ref.c_ob_refcnt == prev_refcnt - 1
+        assert ref.c_ob_refcnt & rawrefcount.REFCNT_MASK == prev_refcnt - 1
         assert space.str_w(from_ref(space, ptr[0])) == 'abcdef'
         with pytest.raises(OperationError):
             PyString_Concat(space, ptr, space.w_None)
@@ -548,9 +550,9 @@
 
         w_text = space.wrap("text")
         ref = make_ref(space, w_text)
-        prev_refcnt = ref.c_ob_refcnt
+        prev_refcnt = ref.c_ob_refcnt & rawrefcount.REFCNT_MASK
         assert PyObject_AsCharBuffer(space, ref, bufp, lenp) == 0
-        assert ref.c_ob_refcnt == prev_refcnt
+        assert ref.c_ob_refcnt & rawrefcount.REFCNT_MASK == prev_refcnt
         assert lenp[0] == 4
         assert rffi.charp2str(bufp[0]) == 'text'
         lltype.free(bufp, flavor='raw')
@@ -609,3 +611,53 @@
         w_seq = space.wrap(['a', 'b'])
         w_joined = _PyString_Join(space, w_sep, w_seq)
         assert space.unwrap(w_joined) == 'a<sep>b'
+
+    def test_refcnt_overflow(self, space):
+        ref1 = make_ref(space, space.wrap('foo'))
+        ref1.c_ob_refcnt = rawrefcount.REFCNT_OVERFLOW - 1
+
+        Py_IncRef(space, ref1)
+        assert ref1.c_ob_refcnt & rawrefcount.REFCNT_MASK \
+            == rawrefcount.REFCNT_OVERFLOW
+        assert _Py_RefCnt_Overflow(space, ref1) \
+            == rawrefcount.REFCNT_OVERFLOW
+
+        Py_IncRef(space, ref1)
+        assert ref1.c_ob_refcnt & rawrefcount.REFCNT_MASK \
+            == rawrefcount.REFCNT_OVERFLOW + 1
+        assert _Py_RefCnt_Overflow(space, ref1) \
+            == rawrefcount.REFCNT_OVERFLOW + 1
+
+        Py_IncRef(space, ref1)
+        assert ref1.c_ob_refcnt & rawrefcount.REFCNT_MASK \
+            == rawrefcount.REFCNT_OVERFLOW + 1
+        assert _Py_RefCnt_Overflow(space, ref1) \
+            == rawrefcount.REFCNT_OVERFLOW + 2
+
+        Py_IncRef(space, ref1)
+        assert ref1.c_ob_refcnt & rawrefcount.REFCNT_MASK \
+            == rawrefcount.REFCNT_OVERFLOW + 1
+        assert _Py_RefCnt_Overflow(space, ref1) \
+            == rawrefcount.REFCNT_OVERFLOW + 3
+
+        Py_DecRef(space, ref1)
+        assert ref1.c_ob_refcnt & rawrefcount.REFCNT_MASK \
+            == rawrefcount.REFCNT_OVERFLOW + 1
+        assert _Py_RefCnt_Overflow(space, ref1) \
+            == rawrefcount.REFCNT_OVERFLOW + 2
+
+        Py_DecRef(space, ref1)
+        assert ref1.c_ob_refcnt & rawrefcount.REFCNT_MASK \
+            == rawrefcount.REFCNT_OVERFLOW + 1
+        assert _Py_RefCnt_Overflow(space, ref1) \
+            == rawrefcount.REFCNT_OVERFLOW + 1
+
+        Py_DecRef(space, ref1)
+        assert ref1.c_ob_refcnt & rawrefcount.REFCNT_MASK \
+            == rawrefcount.REFCNT_OVERFLOW
+        assert _Py_RefCnt_Overflow(space, ref1)  \
+            == rawrefcount.REFCNT_OVERFLOW
+
+        Py_DecRef(space, ref1)
+        assert ref1.c_ob_refcnt & rawrefcount.REFCNT_MASK \
+            == rawrefcount.REFCNT_OVERFLOW - 1
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
@@ -74,6 +74,8 @@
 from rpython.rlib.objectmodel import specialize
 from rpython.rlib import rgc
 from rpython.memory.gc.minimarkpage import out_of_memory
+from pypy.module.cpyext.api import slot_function, PyObject
+from rpython.rtyper.lltypesystem import rffi
 
 #
 # Handles the objects in 2 generations:
@@ -188,6 +190,16 @@
                               ('forw', llmemory.Address))
 FORWARDSTUBPTR = lltype.Ptr(FORWARDSTUB)
 NURSARRAY = lltype.Array(llmemory.Address)
+VISIT_FUNCTYPE = rffi.CCallback([PyObject, rffi.VOIDP],
+                                rffi.INT_real)
+
+def traverse(obj, func_ptr):
+    from pypy.module.cpyext.api import generic_cpy_call
+    from pypy.module.cpyext.typeobjectdefs import visitproc
+    if obj.c_ob_type and obj.c_ob_type.c_tp_traverse:
+        visitproc_ptr = rffi.cast(visitproc, func_ptr)
+        generic_cpy_call(True, obj.c_ob_type.c_tp_traverse, obj,
+                         visitproc_ptr, rffi.cast(rffi.VOIDP, obj))
 
 # ____________________________________________________________
 
@@ -2990,13 +3002,13 @@
 
     _ADDRARRAY = lltype.Array(llmemory.Address, hints={'nolength': True})
     PYOBJ_HDR = lltype.Struct('GCHdr_PyObject',
-                              ('ob_refcnt', lltype.Signed),
-                              ('ob_pypy_link', lltype.Signed))
+                              ('c_ob_refcnt', lltype.Signed),
+                              ('c_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)
+        return llmemory.cast_adr_to_ptr(pyobjaddr, lltype.Ptr(PyObject.TO))
 
     def rawrefcount_init(self, dealloc_trigger_callback):
         # see pypy/doc/discussion/rawrefcount.rst
@@ -3005,6 +3017,7 @@
             self.rrc_p_list_old   = self.AddressStack()
             self.rrc_o_list_young = self.AddressStack()
             self.rrc_o_list_old   = self.AddressStack()
+            self.rrc_buffered     = self.AddressStack()
             self.rrc_p_dict       = self.AddressDict()  # non-nursery keys only
             self.rrc_p_dict_nurs  = self.AddressDict()  # nursery keys only
             self.rrc_dealloc_trigger_callback = dealloc_trigger_callback
@@ -3026,7 +3039,7 @@
         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
+        self._pyobj(pyobject).c_ob_pypy_link = objint
         #
         lst = self.rrc_p_list_young
         if self.is_in_nursery(obj):
@@ -3046,14 +3059,17 @@
         else:
             self.rrc_o_list_old.append(pyobject)
         objint = llmemory.cast_adr_to_int(obj, "symbolic")
-        self._pyobj(pyobject).ob_pypy_link = objint
+        self._pyobj(pyobject).c_ob_pypy_link = objint
         # there is no rrc_o_dict
 
     def rawrefcount_mark_deallocating(self, gcobj, pyobject):
         ll_assert(self.rrc_enabled, "rawrefcount.init not called")
         obj = llmemory.cast_ptr_to_adr(gcobj)   # should be a prebuilt obj
         objint = llmemory.cast_adr_to_int(obj, "symbolic")
-        self._pyobj(pyobject).ob_pypy_link = objint
+        self._pyobj(pyobject).c_ob_pypy_link = objint
+
+    def rawrefcount_buffer_pyobj(self, pyobject):
+        self.rrc_buffered.append(pyobject)
 
     def rawrefcount_from_obj(self, gcobj):
         obj = llmemory.cast_ptr_to_adr(gcobj)
@@ -3064,7 +3080,7 @@
         return dct.get(obj)
 
     def rawrefcount_to_obj(self, pyobject):
-        obj = llmemory.cast_int_to_adr(self._pyobj(pyobject).ob_pypy_link)
+        obj = llmemory.cast_int_to_adr(self._pyobj(pyobject).c_ob_pypy_link)
         return llmemory.cast_adr_to_ptr(obj, llmemory.GCREF)
 
     def rawrefcount_next_dead(self):
@@ -3085,15 +3101,14 @@
                                       self.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
+        from rpython.rlib.rawrefcount import REFCNT_MASK
         #
-        rc = self._pyobj(pyobject).ob_refcnt
-        if rc == REFCNT_FROM_PYPY or rc == REFCNT_FROM_PYPY_LIGHT:
+        rc = self._pyobj(pyobject).c_ob_refcnt
+        if rc & REFCNT_MASK == 0:
             pass     # the corresponding object may die
         else:
             # force the corresponding object to be alive
-            intobj = self._pyobj(pyobject).ob_pypy_link
+            intobj = self._pyobj(pyobject).c_ob_pypy_link
             singleaddr.address[0] = llmemory.cast_int_to_adr(intobj)
             self._trace_drag_out1(singleaddr)
 
@@ -3110,14 +3125,14 @@
                                             no_o_dict)
 
     def _rrc_minor_free(self, pyobject, surviving_list, surviving_dict):
-        intobj = self._pyobj(pyobject).ob_pypy_link
+        intobj = self._pyobj(pyobject).c_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
+                self._pyobj(pyobject).c_ob_pypy_link = intobj
                 surviving = True
                 if surviving_dict:
                     # Surviving nursery object: was originally in
@@ -3148,23 +3163,24 @@
     def _rrc_free(self, pyobject):
         from rpython.rlib.rawrefcount import REFCNT_FROM_PYPY
         from rpython.rlib.rawrefcount import REFCNT_FROM_PYPY_LIGHT
+        from rpython.rlib.rawrefcount import REFCNT_MASK
         #
-        rc = self._pyobj(pyobject).ob_refcnt
+        rc = self._pyobj(pyobject).c_ob_refcnt
         if rc >= REFCNT_FROM_PYPY_LIGHT:
             rc -= REFCNT_FROM_PYPY_LIGHT
-            if rc == 0:
+            if rc & REFCNT_MASK == 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
+                self._pyobj(pyobject).c_ob_refcnt = rc
+                self._pyobj(pyobject).c_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_pypy_link = 0
-            if rc == 0:
+            self._pyobj(pyobject).c_ob_pypy_link = 0
+            if rc & REFCNT_MASK == 0:
                 self.rrc_dealloc_pending.append(pyobject)
                 # an object with refcnt == 0 cannot stay around waiting
                 # for its deallocator to be called.  Some code (lxml)
@@ -3175,22 +3191,21 @@
                 # because after a Py_INCREF()/Py_DECREF() on it, its
                 # tp_dealloc is also called!
                 rc = 1
-            self._pyobj(pyobject).ob_refcnt = rc
+            self._pyobj(pyobject).c_ob_refcnt = rc
     _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
+        from rpython.rlib.rawrefcount import REFCNT_MASK
         #
-        rc = self._pyobj(pyobject).ob_refcnt
-        if rc == REFCNT_FROM_PYPY or rc == REFCNT_FROM_PYPY_LIGHT:
+        rc = self._pyobj(pyobject).c_ob_refcnt
+        if rc & REFCNT_MASK == 0:
             pass     # the corresponding object may die
         else:
             # force the corresponding object to be alive
-            intobj = self._pyobj(pyobject).ob_pypy_link
+            intobj = self._pyobj(pyobject).c_ob_pypy_link
             obj = llmemory.cast_int_to_adr(intobj)
             self.objects_to_trace.append(obj)
             self.visit_all_objects()
@@ -3220,7 +3235,7 @@
         # This is true if the obj has one of the following two flags:
         #  * GCFLAG_VISITED: was seen during tracing
         #  * GCFLAG_NO_HEAP_PTRS: immortal object never traced (so far)
-        intobj = self._pyobj(pyobject).ob_pypy_link
+        intobj = self._pyobj(pyobject).c_ob_pypy_link
         obj = llmemory.cast_int_to_adr(intobj)
         if self.header(obj).tid & (GCFLAG_VISITED | GCFLAG_NO_HEAP_PTRS):
             surviving_list.append(pyobject)
diff --git a/rpython/memory/gc/test/test_rawrefcount.py b/rpython/memory/gc/test/test_rawrefcount.py
--- a/rpython/memory/gc/test/test_rawrefcount.py
+++ b/rpython/memory/gc/test/test_rawrefcount.py
@@ -2,9 +2,15 @@
 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
-
+from rpython.rlib.rawrefcount import (REFCNT_FROM_PYPY, REFCNT_FROM_PYPY_LIGHT,
+                                      REFCNT_MASK)
+from pypy.module.cpyext.api import (PyObject, PyTypeObject, PyTypeObjectPtr,
+                                    PyObjectFields, cpython_struct)
+from pypy.module.cpyext.complexobject import PyComplexObject
+from rpython.rtyper.lltypesystem import rffi
+from pypy.module.cpyext.typeobjectdefs import visitproc, traverseproc
+from rpython.rtyper.annlowlevel import llhelper
+from rpython.rtyper.tool import rffi_platform
 PYOBJ_HDR = IncrementalMiniMarkGC.PYOBJ_HDR
 PYOBJ_HDR_PTR = IncrementalMiniMarkGC.PYOBJ_HDR_PTR
 
@@ -14,6 +20,17 @@
                          ('prev', lltype.Ptr(S)),
                          ('next', lltype.Ptr(S))))
 
+T = lltype.Ptr(lltype.ForwardReference())
+T.TO.become(lltype.Struct('test',
+                          ('base', PyObject.TO),
+                          ('next', T),
+                          ('prev', T),
+                          ('value', lltype.Signed)))
+
+TRAVERSE_FUNCTYPE = rffi.CCallback([PyObject, visitproc, rffi.VOIDP],
+                                   rffi.INT_real)
+t1 = lltype.malloc(PyTypeObject, flavor='raw', immortal=True)
+
 
 class TestRawRefCount(BaseDirectGCTest):
     GCClass = IncrementalMiniMarkGC
@@ -56,21 +73,22 @@
             self._collect(major=False)
             p1 = self.stackroots.pop()
         p1ref = lltype.cast_opaque_ptr(llmemory.GCREF, p1)
-        r1 = lltype.malloc(PYOBJ_HDR, flavor='raw', immortal=create_immortal)
-        r1.ob_refcnt = rc
-        r1.ob_pypy_link = 0
+        r1 = lltype.malloc(PyObject.TO, flavor='raw', immortal=create_immortal)
+        r1.c_ob_refcnt = rc
+        r1.c_ob_pypy_link = 0
+        r1.c_ob_type = lltype.nullptr(PyTypeObject)
         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
+        assert r1.c_ob_refcnt == rc
+        assert r1.c_ob_pypy_link != 0
 
         def check_alive(extra_refcount):
-            assert r1.ob_refcnt == rc + extra_refcount
-            assert r1.ob_pypy_link != 0
+            assert r1.c_ob_refcnt == rc + extra_refcount
+            assert r1.c_ob_pypy_link != 0
             p1ref = self.gc.rawrefcount_to_obj(r1addr)
             p1 = lltype.cast_opaque_ptr(lltype.Ptr(S), p1ref)
             assert p1.x == intval
@@ -81,19 +99,53 @@
             return p1
         return p1, p1ref, r1, r1addr, check_alive
 
+    def _rawrefcount_cycle_obj(self):
+
+        def test_tp_traverse(obj, visit, args):
+            test = rffi.cast(T, obj)
+            vret = 0
+            if llmemory.cast_ptr_to_adr(test.next).ptr is not None:
+                next = rffi.cast(PyObject, test.next)
+                vret = visit(next, args)
+                if vret != 0:
+                    return vret
+            if llmemory.cast_ptr_to_adr(test.prev).ptr is not None:
+                next = rffi.cast(PyObject, test.prev)
+                vret = visit(next, args)
+                if vret != 0:
+                    return vret
+            return vret
+
+        func_ptr = llhelper(TRAVERSE_FUNCTYPE, test_tp_traverse)
+        rffi_func_ptr = rffi.cast(traverseproc, func_ptr)
+        t1.c_tp_traverse = rffi_func_ptr
+
+        r1 = lltype.malloc(T.TO, flavor='raw', immortal=True)
+        r1.base.c_ob_pypy_link = 0
+        r1.base.c_ob_type = t1
+        r1.base.c_ob_refcnt = 1
+        return r1
+
+    def _rawrefcount_buffer_obj(self, obj):
+        from rpython.rlib.rawrefcount import REFCNT_CLR_MASK, REFCNT_CLR_PURPLE
+        rc = obj.base.c_ob_refcnt
+        obj.base.c_ob_refcnt = rc & ~REFCNT_CLR_MASK | REFCNT_CLR_PURPLE
+        objaddr = llmemory.cast_ptr_to_adr(obj)
+        self.gc.rawrefcount_buffer_pyobj(objaddr)
+
     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
+        r2 = lltype.malloc(PyObject.TO, flavor='raw')
+        r2.c_ob_refcnt = 1
+        r2.c_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 r1.c_ob_pypy_link != 0
+        assert r2.c_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
@@ -106,16 +158,16 @@
         p1, p1ref, r1, r1addr, check_alive = (
             self._rawrefcount_pair(42, is_light=True, create_old=old))
         check_alive(0)
-        r1.ob_refcnt += 1
+        r1.c_ob_refcnt += 1
         self._collect(major=False)
         check_alive(+1)
         self._collect(major=True)
         check_alive(+1)
-        r1.ob_refcnt -= 1
+        r1.c_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, "r1.c_ob_refcnt")    # dead
         py.test.raises(RuntimeError, "p1.x")            # dead
         self.gc.check_no_more_rawrefcount_state()
         assert self.trigger == []
@@ -129,7 +181,7 @@
         if old:
             check_alive(0)
             self._collect(major=True)
-        py.test.raises(RuntimeError, "r1.ob_refcnt")    # dead
+        py.test.raises(RuntimeError, "r1.c_ob_refcnt")    # dead
         py.test.raises(RuntimeError, "p1.x")            # dead
         self.gc.check_no_more_rawrefcount_state()
 
@@ -147,7 +199,7 @@
         check_alive(0)
         assert p1.x == 42
         self._collect(major=True)
-        py.test.raises(RuntimeError, "r1.ob_refcnt")    # dead
+        py.test.raises(RuntimeError, "r1.c_ob_refcnt")    # dead
         py.test.raises(RuntimeError, "p1.x")            # dead
         self.gc.check_no_more_rawrefcount_state()
 
@@ -164,18 +216,18 @@
         p1, p1ref, r1, r1addr, check_alive = (
             self._rawrefcount_pair(42, is_light=False, create_old=old))
         check_alive(0)
-        r1.ob_refcnt += 1
+        r1.c_ob_refcnt += 1
         self._collect(major=False)
         check_alive(+1)
         self._collect(major=True)
         check_alive(+1)
-        r1.ob_refcnt -= 1
+        r1.c_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 == 1       # in the pending list
-        assert r1.ob_pypy_link == 0
+        assert r1.c_ob_refcnt == 1       # in the pending list
+        assert r1.c_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
@@ -197,8 +249,8 @@
         assert p1.x == 42
         self._collect(major=True, expected_trigger=1)
         py.test.raises(RuntimeError, "p1.x")            # dead
-        assert r1.ob_refcnt == 1
-        assert r1.ob_pypy_link == 0
+        assert r1.c_ob_refcnt == 1
+        assert r1.c_ob_pypy_link == 0
         assert self.gc.rawrefcount_next_dead() == r1addr
         self.gc.check_no_more_rawrefcount_state()
         lltype.free(r1, flavor='raw')
@@ -214,8 +266,8 @@
         else:
             self._collect(major=False, expected_trigger=1)
         py.test.raises(RuntimeError, "p1.x")            # dead
-        assert r1.ob_refcnt == 1
-        assert r1.ob_pypy_link == 0
+        assert r1.c_ob_refcnt == 1
+        assert r1.c_ob_pypy_link == 0
         assert self.gc.rawrefcount_next_dead() == r1addr
         self.gc.check_no_more_rawrefcount_state()
         lltype.free(r1, flavor='raw')
@@ -232,10 +284,10 @@
         p1, p1ref, r1, r1addr, check_alive = (
             self._rawrefcount_pair(42, is_pyobj=True, force_external=external))
         check_alive(0)
-        r1.ob_refcnt += 1            # the pyobject is kept alive
+        r1.c_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
+        assert r1.c_ob_refcnt == 1     # refcnt dropped to 1
+        assert r1.c_ob_pypy_link == 0  # detached
         self.gc.check_no_more_rawrefcount_state()
         lltype.free(r1, flavor='raw')
 
@@ -252,8 +304,8 @@
             self._collect(major=True, expected_trigger=1)
         else:
             self._collect(major=False, expected_trigger=1)
-        assert r1.ob_refcnt == 1     # refcnt 1, in the pending list
-        assert r1.ob_pypy_link == 0  # detached
+        assert r1.c_ob_refcnt == 1     # refcnt 1, in the pending list
+        assert r1.c_ob_pypy_link == 0  # detached
         assert self.gc.rawrefcount_next_dead() == r1addr
         self.gc.check_no_more_rawrefcount_state()
         lltype.free(r1, flavor='raw')
@@ -277,8 +329,8 @@
         assert self.trigger == []
         self._collect(major=True, expected_trigger=1)
         py.test.raises(RuntimeError, "p1.x")            # dead
-        assert r1.ob_refcnt == 1
-        assert r1.ob_pypy_link == 0
+        assert r1.c_ob_refcnt == 1
+        assert r1.c_ob_pypy_link == 0
         assert self.gc.rawrefcount_next_dead() == r1addr
         self.gc.check_no_more_rawrefcount_state()
         lltype.free(r1, flavor='raw')
@@ -289,3 +341,146 @@
         check_alive(0)
         self._collect(major=True)
         check_alive(0)
+
+    # def test_cycle_self_reference_free(self):
+    #     self.gc.rawrefcount_init(lambda: self.trigger.append(1))
+    #     r1 = self._rawrefcount_cycle_obj()
+    #     r1.next = r1
+    #     self._rawrefcount_buffer_obj(r1)
+    #     self.gc.rrc_collect_cycles()
+    #     assert r1.base.c_ob_refcnt & REFCNT_MASK == 0
+    #
+    # def test_cycle_self_reference_not_free(self):
+    #     self.gc.rawrefcount_init(lambda: self.trigger.append(1))
+    #     r1 = self._rawrefcount_cycle_obj()
+    #     r1.base.c_ob_refcnt += 1
+    #     r1.next = r1
+    #     self._rawrefcount_buffer_obj(r1)
+    #     self.gc.rrc_collect_cycles()
+    #     assert r1.base.c_ob_refcnt & REFCNT_MASK == 2
+    #
+    # def test_simple_cycle_free(self):
+    #     self.gc.rawrefcount_init(lambda: self.trigger.append(1))
+    #     r1 = self._rawrefcount_cycle_obj()
+    #     r2 = self._rawrefcount_cycle_obj()
+    #     r1.next = r2
+    #     r2.next = r1
+    #     self._rawrefcount_buffer_obj(r1)
+    #     self.gc.rrc_collect_cycles()
+    #     assert r1.base.c_ob_refcnt & REFCNT_MASK == 0
+    #     assert r2.base.c_ob_refcnt & REFCNT_MASK == 0
+    #
+    # def test_simple_cycle_not_free(self):
+    #     self.gc.rawrefcount_init(lambda: self.trigger.append(1))
+    #     r1 = self._rawrefcount_cycle_obj()
+    #     r2 = self._rawrefcount_cycle_obj()
+    #     r1.next = r2
+    #     r2.next = r1
+    #     r2.base.c_ob_refcnt += 1
+    #     self._rawrefcount_buffer_obj(r1)
+    #     self.gc.rrc_collect_cycles()
+    #     assert r1.base.c_ob_refcnt & REFCNT_MASK == 1
+    #     assert r2.base.c_ob_refcnt & REFCNT_MASK == 2
+    #
+    # def test_complex_cycle_free(self):
+    #     self.gc.rawrefcount_init(lambda: self.trigger.append(1))
+    #     r1 = self._rawrefcount_cycle_obj()
+    #     r2 = self._rawrefcount_cycle_obj()
+    #     r3 = self._rawrefcount_cycle_obj()
+    #     r1.next = r2
+    #     r1.prev = r2
+    #     r2.base.c_ob_refcnt += 1
+    #     r2.next = r3
+    #     r3.prev = r1
+    #     self._rawrefcount_buffer_obj(r1)
+    #     self.gc.rrc_collect_cycles()
+    #     assert r1.base.c_ob_refcnt & REFCNT_MASK == 0
+    #     assert r2.base.c_ob_refcnt & REFCNT_MASK == 0
+    #     assert r3.base.c_ob_refcnt & REFCNT_MASK == 0
+    #
+    # def test_complex_cycle_not_free(self):
+    #     self.gc.rawrefcount_init(lambda: self.trigger.append(1))
+    #     r1 = self._rawrefcount_cycle_obj()
+    #     r2 = self._rawrefcount_cycle_obj()
+    #     r3 = self._rawrefcount_cycle_obj()
+    #     r1.next = r2
+    #     r1.prev = r2
+    #     r2.base.c_ob_refcnt += 1
+    #     r2.next = r3
+    #     r3.prev = r1
+    #     r3.base.c_ob_refcnt += 1
+    #     self._rawrefcount_buffer_obj(r1)
+    #     self.gc.rrc_collect_cycles()
+    #     assert r1.base.c_ob_refcnt & REFCNT_MASK == 1
+    #     assert r2.base.c_ob_refcnt & REFCNT_MASK == 2
+    #     assert r3.base.c_ob_refcnt & REFCNT_MASK == 2
+    #
+    # def test_cycle_2_buffered_free(self):
+    #     self.gc.rawrefcount_init(lambda: self.trigger.append(1))
+    #     r1 = self._rawrefcount_cycle_obj()
+    #     r2 = self._rawrefcount_cycle_obj()
+    #     r1.next = r2
+    #     r2.prev = r1
+    #     self._rawrefcount_buffer_obj(r1)
+    #     self._rawrefcount_buffer_obj(r2)
+    #     self.gc.rrc_collect_cycles()
+    #     assert r1.base.c_ob_refcnt & REFCNT_MASK == 0
+    #     assert r2.base.c_ob_refcnt & REFCNT_MASK == 0
+    #
+    # def test_cycle_2_buffered_not_free(self):
+    #     self.gc.rawrefcount_init(lambda: self.trigger.append(1))
+    #     r1 = self._rawrefcount_cycle_obj()
+    #     r2 = self._rawrefcount_cycle_obj()
+    #     r1.next = r2
+    #     r2.prev = r1
+    #     r1.base.c_ob_refcnt += 1
+    #     self._rawrefcount_buffer_obj(r1)
+    #     self._rawrefcount_buffer_obj(r2)
+    #     self.gc.rrc_collect_cycles()
+    #     assert r1.base.c_ob_refcnt & REFCNT_MASK == 2
+    #     assert r2.base.c_ob_refcnt & REFCNT_MASK == 1
+    #
+    # def test_multiple_cycles_partial_free(self):
+    #     self.gc.rawrefcount_init(lambda: self.trigger.append(1))
+    #     r1 = self._rawrefcount_cycle_obj()
+    #     r2 = self._rawrefcount_cycle_obj()
+    #     r3 = self._rawrefcount_cycle_obj()
+    #     r4 = self._rawrefcount_cycle_obj()
+    #     r5 = self._rawrefcount_cycle_obj()
+    #     r1.next = r2
+    #     r2.next = r3
+    #     r3.next = r1
+    #     r2.prev = r5
+    #     r5.next = r4
+    #     r4.next = r5
+    #     r5.base.c_ob_refcnt += 1
+    #     r4.base.c_ob_refcnt += 1
+    #     self._rawrefcount_buffer_obj(r1)
+    #     self.gc.rrc_collect_cycles()
+    #     assert r1.base.c_ob_refcnt & REFCNT_MASK == 0
+    #     assert r2.base.c_ob_refcnt & REFCNT_MASK == 0
+    #     assert r3.base.c_ob_refcnt & REFCNT_MASK == 0
+    #     assert r4.base.c_ob_refcnt & REFCNT_MASK == 2
+    #     assert r5.base.c_ob_refcnt & REFCNT_MASK == 1
+    #
+    # def test_multiple_cycles_all_free(self):
+    #     self.gc.rawrefcount_init(lambda: self.trigger.append(1))
+    #     r1 = self._rawrefcount_cycle_obj()
+    #     r2 = self._rawrefcount_cycle_obj()
+    #     r3 = self._rawrefcount_cycle_obj()
+    #     r4 = self._rawrefcount_cycle_obj()
+    #     r5 = self._rawrefcount_cycle_obj()
+    #     r1.next = r2
+    #     r2.next = r3
+    #     r3.next = r1
+    #     r2.prev = r5
+    #     r5.next = r4
+    #     r4.next = r5
+    #     r5.base.c_ob_refcnt += 1
+    #     self._rawrefcount_buffer_obj(r1)
+    #     self.gc.rrc_collect_cycles()
+    #     assert r1.base.c_ob_refcnt & REFCNT_MASK == 0
+    #     assert r2.base.c_ob_refcnt & REFCNT_MASK == 0
+    #     assert r3.base.c_ob_refcnt & REFCNT_MASK == 0
+    #     assert r4.base.c_ob_refcnt & REFCNT_MASK == 0
+    #     assert r5.base.c_ob_refcnt & REFCNT_MASK == 0
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
@@ -489,6 +489,10 @@
                 GCClass.rawrefcount_mark_deallocating,
                 [s_gc, s_gcref, SomeAddress()],
                 annmodel.s_None)
+            self.rawrefcount_buffer_pyobj = getfn(
+                GCClass.rawrefcount_buffer_pyobj,
+                [s_gc, SomeAddress()],
+                annmodel.s_None)
             self.rawrefcount_from_obj_ptr = getfn(
                 GCClass.rawrefcount_from_obj, [s_gc, s_gcref], SomeAddress(),
                 inline = True)
@@ -1339,6 +1343,13 @@
                   [self.rawrefcount_mark_deallocating, self.c_const_gc,
                    v_gcobj, v_pyobject])
 
+    def gct_gc_rawrefcount_buffer_pyobj(self, hop):
+        [v_pyobject] = hop.spaceop.args
+        assert v_pyobject.concretetype == llmemory.Address
+        hop.genop("direct_call",
+                  [self.rawrefcount_buffer_pyobj, self.c_const_gc,
+                   v_pyobject])
+
     def gct_gc_rawrefcount_from_obj(self, hop):
         [v_gcobj] = hop.spaceop.args
         assert v_gcobj.concretetype == llmemory.GCREF
diff --git a/rpython/rlib/rawrefcount.py b/rpython/rlib/rawrefcount.py
--- a/rpython/rlib/rawrefcount.py
+++ b/rpython/rlib/rawrefcount.py
@@ -4,18 +4,49 @@
 #  This is meant for pypy's cpyext module, but is a generally
 #  useful interface over our GC.  XXX "pypy" should be removed here
 #
-import sys, weakref, py
+import sys, weakref, py, math
 from rpython.rtyper.lltypesystem import lltype, llmemory, rffi
 from rpython.rlib.objectmodel import we_are_translated, specialize, not_rpython
 from rpython.rtyper.extregistry import ExtRegistryEntry
 from rpython.translator.tool.cbuild import ExternalCompilationInfo
-from rpython.rlib import rgc
+from rpython.rlib import rgc, objectmodel
+from pypy.interpreter.baseobjspace import W_Root
 
 
-REFCNT_FROM_PYPY       = sys.maxint // 4 + 1
-REFCNT_FROM_PYPY_LIGHT = REFCNT_FROM_PYPY + (sys.maxint // 2 + 1)
+MAX_BIT = int(math.log(sys.maxint, 2))
+
+# Flags
+REFCNT_FROM_PYPY = 1 << MAX_BIT - 2                             # Reference from a pypy object
+REFCNT_FROM_PYPY_LIGHT = (1 << MAX_BIT - 1) + REFCNT_FROM_PYPY  # Light reference from a pypy object
+REFCNT_CYCLE_BUFFERED = 1 << MAX_BIT - 3                        # Object in roots buffer (for potential cycles)
+REFCNT_IN_WAVEFRONT = 1 << MAX_BIT - 4                          # Object in any wavefront
+
+# Offsets and sizes
+REFCNT_CLR_OFFS = MAX_BIT - 7
+REFCNT_CRC_OFFS = REFCNT_CLR_OFFS / 2
+REFCNT_BITS = REFCNT_CRC_OFFS - 1
+
+# Concurrent cycle collection colors
+REFCNT_CLR_BLACK = 0 << REFCNT_CLR_OFFS   # In use or free (default)
+REFCNT_CLR_GRAY = 1 << REFCNT_CLR_OFFS    # Possible member of cycle
+REFCNT_CLR_YELLOW = 2 << REFCNT_CLR_OFFS  # Member of garbage cycle
+REFCNT_CLR_PURPLE = 3 << REFCNT_CLR_OFFS  # Possible root of cycle
+REFCNT_CLR_GREEN = 4 << REFCNT_CLR_OFFS   # Acyclic
+REFCNT_CLR_ORANGE = 5 << REFCNT_CLR_OFFS  # In orange wavefront (might change to YELLOW + IN_WAVEFRONT + phase = 3)
+REFCNT_CLR_MASK = 7 << REFCNT_CLR_OFFS
+
+# Cyclic reference count with overflow bit
+REFCNT_CRC_OVERFLOW = 1 << REFCNT_CRC_OFFS + REFCNT_BITS
+REFCNT_CRC_MASK = (1 << REFCNT_CRC_OFFS + REFCNT_BITS + 1) - 1
+REFCNT_CRC = 1 < REFCNT_CRC_OFFS
+
+# True reference count with overflow bit
+REFCNT_OVERFLOW = 1 << REFCNT_BITS
+REFCNT_MASK = (1 << REFCNT_BITS + 1) - 1
+
 
 RAWREFCOUNT_DEALLOC_TRIGGER = lltype.Ptr(lltype.FuncType([], lltype.Void))
+W_MARKER_DEALLOCATING = W_Root()
 
 
 def _build_pypy_link(p):
@@ -23,6 +54,47 @@
     _adr2pypy.append(p)
     return res
 
+def incref(pyobj):
+    if pyobj.c_ob_refcnt & REFCNT_OVERFLOW == 0:
+        pyobj.c_ob_refcnt += 1
+    else:
+        if pyobj.c_ob_refcnt & REFCNT_MASK == REFCNT_OVERFLOW:
+            pyobj.c_ob_refcnt += 1
+            overflow_new(pyobj)
+        else:
+            overflow_add(pyobj)
+
+def decref(pyobj):
+    if pyobj.c_ob_refcnt & REFCNT_OVERFLOW == 0:
+        pyobj.c_ob_refcnt -= 1
+    else:
+        if pyobj.c_ob_refcnt & REFCNT_MASK == REFCNT_OVERFLOW:
+            pyobj.c_ob_refcnt -= 1
+        elif overflow_sub(pyobj):
+            pyobj.c_ob_refcnt -= 1
+
+_refcount_overflow = dict()
+
+def overflow_new(obj):
+    _refcount_overflow[objectmodel.current_object_addr_as_int(obj)] = 0
+
+def overflow_add(obj):
+    _refcount_overflow[objectmodel.current_object_addr_as_int(obj)] += 1
+
+def overflow_sub(obj):
+    addr = objectmodel.current_object_addr_as_int(obj)
+    c = _refcount_overflow[addr]
+    if c > 0:
+        _refcount_overflow[addr] = c - 1
+        return False
+    else:
+        _refcount_overflow.pop(addr)
+        return True
+
+def overflow_get(obj):
+    return _refcount_overflow[objectmodel.current_object_addr_as_int(obj)]
+
+# TODO: _cyclic_refcount_overflow = dict()
 
 @not_rpython
 def init(dealloc_trigger_callback=None):
@@ -72,6 +144,10 @@
     ob.c_ob_pypy_link = _build_pypy_link(marker)
 
 @not_rpython
+def buffer_pyobj(ob):
+    pass  # TODO: implement?
+
+ at not_rpython
 def from_obj(OB_PTR_TYPE, p):
     ob = _pypy2ob.get(p)
     if ob is None:
@@ -122,7 +198,8 @@
     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):
+        if ob.c_ob_refcnt & REFCNT_MASK > 0 \
+           or ob.c_ob_refcnt & REFCNT_FROM_PYPY == 0:
             new_p_list.append(ob)
         else:
             p = detach(ob, wr_p_list)
@@ -155,7 +232,8 @@
             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:
+                if ob.c_ob_refcnt & REFCNT_MASK == 0 \
+                   and ob.c_ob_refcnt < REFCNT_FROM_PYPY:
                     lltype.free(ob, flavor='raw',
                                 track_allocation=track_allocation)
             else:
@@ -163,8 +241,9 @@
                 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:
-                    ob.c_ob_refcnt = 1
+                if ob.c_ob_refcnt & REFCNT_MASK == 0 \
+                   and ob.c_ob_refcnt < REFCNT_FROM_PYPY:
+                    ob.c_ob_refcnt += 1
                     _d_list.append(ob)
             return None
 
@@ -252,6 +331,17 @@
                                     func_boehm_eci)
             hop.genop('direct_call', [c_func])
 
+class Entry(ExtRegistryEntry):
+    _about_ = buffer_pyobj
+
+    def compute_result_annotation(self, s_ob):
+        pass
+
+    def specialize_call(self, hop):
+        name = 'gc_rawrefcount_buffer_pyobj'
+        hop.exception_cannot_occur()
+        v_ob = hop.inputarg(hop.args_r[0], arg=0)
+        hop.genop(name, [_unspec_ob(hop, v_ob)])
 
 class Entry(ExtRegistryEntry):
     _about_ = from_obj
diff --git a/rpython/rtyper/llinterp.py b/rpython/rtyper/llinterp.py
--- a/rpython/rtyper/llinterp.py
+++ b/rpython/rtyper/llinterp.py
@@ -969,6 +969,9 @@
     def op_gc_rawrefcount_mark_deallocating(self, *args):
         raise NotImplementedError("gc_rawrefcount_mark_deallocating")
 
+    def op_gc_rawrefcount_buffer_pyobj(self, *args):
+        raise NotImplementedError("gc_rawrefcount_buffer_pyobj")
+
     def op_gc_rawrefcount_next_dead(self, *args):
         raise NotImplementedError("gc_rawrefcount_next_dead")
 
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
@@ -494,6 +494,7 @@
     'gc_rawrefcount_create_link_pypy':  LLOp(),
     'gc_rawrefcount_create_link_pyobj': LLOp(),
     'gc_rawrefcount_mark_deallocating': LLOp(),
+    'gc_rawrefcount_buffer_pyobj':      LLOp(),
     'gc_rawrefcount_from_obj':          LLOp(sideeffects=False),
     'gc_rawrefcount_to_obj':            LLOp(sideeffects=False),
     'gc_rawrefcount_next_dead':         LLOp(),
diff --git a/rpython/rtyper/lltypesystem/lltype.py b/rpython/rtyper/lltypesystem/lltype.py
--- a/rpython/rtyper/lltypesystem/lltype.py
+++ b/rpython/rtyper/lltypesystem/lltype.py
@@ -564,7 +564,7 @@
     def _container_example(self):
         def ex(*args):
             return self.RESULT._defl()
-        return _func(self, _callable=ex)
+        return _func(self, {'_callable': ex})
 
     def _trueargs(self):
         return [arg for arg in self.ARGS if arg is not Void]
@@ -2094,7 +2094,7 @@
 
 
 class _func(_container):
-    def __init__(self, TYPE, **attrs):
+    def __init__(self, TYPE, attrs):
         attrs.setdefault('_TYPE', TYPE)
         attrs.setdefault('_name', '?')
         attrs.setdefault('_callable', None)
@@ -2303,7 +2303,8 @@
         hash(tuple(attrs.items()))
     except TypeError:
         raise TypeError("'%r' must be hashable"%attrs)
-    o = _func(TYPE, _name=name, **attrs)
+    attrs['_name'] = name
+    o = _func(TYPE, attrs)
     return _ptr(Ptr(TYPE), o)
 
 def _getconcretetype(v):


More information about the pypy-commit mailing list