Author: Stefan Beyer <[email protected]>
Branch: cpyext-gc-trialdeletion
Changeset: r92281:2fb71fc31ef4
Date: 2017-08-06 13:48 +0200
http://bitbucket.org/pypy/pypy/changeset/2fb71fc31ef4/

Log:    Implemented non-incremental cycle detection, removed simple trial
        deletion

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
@@ -15,6 +15,10 @@
 from rpython.rlib.objectmodel import keepalive_until_here
 from rpython.rtyper.annlowlevel import llhelper
 from rpython.rlib import rawrefcount
+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 rpython.rlib.debug import fatalerror, debug_print
 from pypy.module.cpyext.api import slot_function
 from pypy.module.cpyext.typeobjectdefs import visitproc
@@ -190,9 +194,6 @@
     py_obj.c_ob_refcnt += rawrefcount.REFCNT_FROM_PYPY
     rawrefcount.create_link_pypy(w_obj, py_obj)
 
-
-w_marker_deallocating = W_Root()
-
 def from_ref(space, ref):
     """
     Finds the interpreter object corresponding to the given reference.  If the
@@ -203,7 +204,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"
@@ -250,7 +251,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 is_pyobj(x):
@@ -270,27 +271,6 @@
         hop.exception_cannot_occur()
         return hop.inputconst(lltype.Bool, hop.s_result.const)
 
-def _decref(pyobj):
-    if pyobj.c_ob_refcnt & rawrefcount.REFCNT_OVERFLOW == 0:
-        pyobj.c_ob_refcnt -= 1
-    else:
-        if pyobj.c_ob_refcnt & rawrefcount.REFCNT_MASK \
-           == rawrefcount.REFCNT_OVERFLOW:
-            pyobj.c_ob_refcnt -= 1
-        elif rawrefcount.overflow_sub(pyobj):
-            pyobj.c_ob_refcnt -= 1
-
-def _incref(pyobj):
-    if pyobj.c_ob_refcnt & rawrefcount.REFCNT_OVERFLOW == 0:
-        pyobj.c_ob_refcnt += 1
-    else:
-        if pyobj.c_ob_refcnt & rawrefcount.REFCNT_MASK \
-           == rawrefcount.REFCNT_OVERFLOW:
-            pyobj.c_ob_refcnt += 1
-            rawrefcount.overflow_new(pyobj)
-        else:
-            rawrefcount.overflow_add(pyobj)
-
 @specialize.ll()
 def make_ref(space, obj, w_userdata=None):
     """Increment the reference counter of the PyObject and return it.
@@ -301,7 +281,7 @@
     else:
         pyobj = as_pyobj(space, obj, w_userdata)
     if pyobj:
-        _incref(pyobj)
+        rawrefcount.incref(pyobj)
         if not is_pyobj(obj):
             keepalive_until_here(obj)
     return pyobj
@@ -321,7 +301,7 @@
         w_obj = obj
         pyobj = as_pyobj(space, w_obj)
     if pyobj:
-        _decref(pyobj)
+        rawrefcount.decref(pyobj)
         keepalive_until_here(w_obj)
     return w_obj
 
@@ -334,122 +314,30 @@
     if is_pyobj(obj):
         obj = rffi.cast(PyObject, obj)
         if obj:
-            _decref(obj)
-
-            if obj.c_ob_refcnt & rawrefcount.REFCNT_MASK == 0 and \
-               rawrefcount.get_trialdeletion_phase() != 1:
-                if obj.c_ob_refcnt & rawrefcount.REFCNT_FROM_PYPY == 0:
+            rawrefcount.decref(obj)
+            rc = obj.c_ob_refcnt
+            if (rc & REFCNT_MASK == 0):
+                if (rc & REFCNT_FROM_PYPY == 0 and
+                   rc & REFCNT_CLR_MASK != REFCNT_CLR_PURPLE):
                     _Py_Dealloc(space, obj)
-            elif obj.c_ob_refcnt & rawrefcount.REFCNT_CLR_GREEN == 0:
-                if rawrefcount.get_trialdeletion_phase() == 0:
-                    trial_delete(space, obj)
+            elif (rc & REFCNT_CLR_MASK != REFCNT_CLR_GREEN):
+                possible_root(space, obj)
     else:
         get_w_obj_and_decref(space, obj)
 
[email protected]()
-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 & rawrefcount.REFCNT_MASK ==
-           rawrefcount.REFCNT_OVERFLOW):
-            return rawrefcount.REFCNT_OVERFLOW
-        else:
-            return (pyobj.c_ob_refcnt & rawrefcount.REFCNT_MASK) \
-                + rawrefcount.overflow_get(pyobj)
-    return 0
-
-def traverse(space, obj, visit):
-    from pypy.module.cpyext.api import generic_cpy_call
-    if obj.c_ob_type and obj.c_ob_type.c_tp_traverse:
-        generic_cpy_call(space, obj.c_ob_type.c_tp_traverse, obj, visit,
-                         rffi.cast(rffi.VOIDP, obj))
-
-def clear(space, obj):
-    from pypy.module.cpyext.api import generic_cpy_call
-    if obj.c_ob_type:
-        generic_cpy_call(space, obj.c_ob_type.c_tp_clear, obj)
-
-@slot_function([PyObject, rffi.VOIDP], rffi.INT_real, error=-1)
-def visit_decref(space, obj, args):
-    _decref(obj)
-    debug_print("visited dec", obj, "new refcnt", obj.c_ob_refcnt)
-    if (obj not in rawrefcount.get_visited()):
-        rawrefcount.add_visited(obj)
-        from pypy.module.cpyext.slotdefs import llslot
-        traverse(space, obj, rffi.cast(visitproc, llslot(space, visit_decref)))
-    return 0
-
-@slot_function([PyObject, rffi.VOIDP], rffi.INT_real, error=-1)
-def visit_incref(space, obj, args):
-    _incref(obj)
-    debug_print("visited inc", obj, "new refcnt", obj.c_ob_refcnt)
-    if (obj not in rawrefcount.get_visited()):
-        rawrefcount.add_visited(obj)
-        from pypy.module.cpyext.slotdefs import llslot
-        traverse(space, obj, rffi.cast(visitproc, llslot(space, visit_incref)))
-    return 0
-
[email protected]()
-def trial_delete(space, obj):
+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:
-        obj.c_ob_refcnt = obj.c_ob_refcnt | rawrefcount.REFCNT_CLR_GREEN
-        return
-
-    from pypy.module.cpyext.slotdefs import llslot
-    visitproc_incref = rffi.cast(visitproc, llslot(space, visit_incref))
-    visitproc_decref = rffi.cast(visitproc, llslot(space, visit_decref))
-
-    rawrefcount.set_trialdeletion_phase(1)
-
-    debug_print("trial_delete", obj, "refct after decref", obj.c_ob_refcnt)
-
-    debug_print("decref phase")
-    rawrefcount.clear_visited()
-    rawrefcount.add_visited(obj)
-    traverse(space, obj, visitproc_decref)
-
-    debug_print("checkref phase")
-    visited = []
-    alive = []
-    for visited_obj in rawrefcount.get_visited():
-        visited.append(visited_obj)
-        if visited_obj.c_ob_refcnt != 0 and \
-           visited_obj.c_ob_refcnt != rawrefcount.REFCNT_FROM_PYPY:
-            alive.append(visited_obj)
-            debug_print("alive", visited_obj)
-
-    debug_print("incref phase")
-    rawrefcount.clear_visited()
-    for alive_obj in alive:
-        if alive_obj not in rawrefcount.get_visited():
-            rawrefcount.add_visited(alive_obj)
-            traverse(space, alive_obj, visitproc_incref)
-
-    alive = []
-    for alive_obj in rawrefcount.get_visited():
-        debug_print("alive", alive_obj, alive_obj.c_ob_refcnt)
-        alive.append(alive_obj)
-
-    for reachable_obj in visited:
-        if reachable_obj not in rawrefcount.get_visited():
-            rawrefcount.add_visited(reachable_obj)
-            traverse(space, reachable_obj, visitproc_incref)
-
-    debug_print("clear phase")
-    rawrefcount.set_trialdeletion_phase(2)
-
-    for reachable_obj in visited:
-        if reachable_obj not in alive:
-            if reachable_obj.c_ob_refcnt < rawrefcount.REFCNT_FROM_PYPY \
-               and reachable_obj.c_ob_refcnt > 0:
-                debug_print("clear", reachable_obj)
-                clear(space, reachable_obj)
-
-    rawrefcount.set_trialdeletion_phase(0)
-    rawrefcount.clear_visited()
+        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
 
 @cpython_api([PyObject], lltype.Void)
 def Py_IncRef(space, obj):
@@ -463,6 +351,20 @@
 def _Py_RefCnt_Overflow(space, obj):
     return refcnt_overflow(space, obj)
 
[email protected]()
+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
+
 @cpython_api([PyObject], lltype.Void)
 def _Py_NewReference(space, obj):
     obj.c_ob_refcnt = 1
@@ -477,7 +379,7 @@
     pto = obj.c_ob_type
     #print >>sys.stderr, "Calling dealloc slot", pto.c_tp_dealloc, "of", obj, \
     #      "'s type which is", rffi.charp2str(pto.c_tp_name)
-    rawrefcount.mark_deallocating(w_marker_deallocating, obj)
+    rawrefcount.mark_deallocating(W_MARKER_DEALLOCATING, obj)
     generic_cpy_call(space, pto.c_tp_dealloc, obj)
 
 @cpython_api([rffi.VOIDP], lltype.Signed, error=CANNOT_FAIL)
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
@@ -73,6 +73,8 @@
 from rpython.rlib.debug import ll_assert, debug_print, debug_start, debug_stop
 from rpython.rlib.objectmodel import specialize
 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:
@@ -187,6 +189,105 @@
                               ('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))
+
+def visit_mark_gray(obj, args):
+    from rpython.rlib.rawrefcount import (REFCNT_CLR_GREEN,
+                                          REFCNT_CLR_MASK,
+                                          decref)
+    decref(obj)
+    rc = obj.c_ob_refcnt
+    if rc & REFCNT_CLR_MASK != REFCNT_CLR_GREEN:
+        mark_gray_recursive(obj)
+    return rffi.cast(rffi.INT_real, 0)
+
+def mark_gray_recursive(obj):
+    from rpython.rlib.rawrefcount import (REFCNT_CLR_GRAY,
+                                          REFCNT_CLR_MASK)
+    from rpython.rtyper.annlowlevel import llhelper
+    debug_print("mark_gray_recursive", obj)
+    rc = obj.c_ob_refcnt
+    if rc & REFCNT_CLR_MASK != REFCNT_CLR_GRAY:
+        obj.c_ob_refcnt = obj.c_ob_refcnt & ~REFCNT_CLR_MASK | REFCNT_CLR_GRAY
+        func_ptr = llhelper(VISIT_FUNCTYPE, visit_mark_gray)
+        traverse(obj, func_ptr)
+
+def visit_scan_black(obj, args):
+    from rpython.rlib.rawrefcount import (REFCNT_CLR_BLACK,
+                                          REFCNT_CLR_MASK,
+                                          REFCNT_CLR_GREEN,
+                                          incref)
+    incref(obj)
+    rc = obj.c_ob_refcnt
+    if (rc & REFCNT_CLR_MASK != REFCNT_CLR_BLACK and
+       rc & REFCNT_CLR_MASK != REFCNT_CLR_GREEN):
+        scan_black_recursive(obj)
+    return rffi.cast(rffi.INT_real, 0)
+
+def scan_black_recursive(obj):
+    from rpython.rlib.rawrefcount import (REFCNT_CLR_BLACK,
+                                          REFCNT_CLR_MASK)
+    from rpython.rtyper.annlowlevel import llhelper
+    debug_print("scan_black_recursive", obj)
+    rc = obj.c_ob_refcnt
+    obj.c_ob_refcnt = rc & ~REFCNT_CLR_MASK | REFCNT_CLR_BLACK
+    func_ptr = llhelper(VISIT_FUNCTYPE, visit_scan_black)
+    traverse(obj, func_ptr)
+
+def visit_scan(obj, args):
+    scan_recursive(obj)
+    return rffi.cast(rffi.INT_real, 0)
+
+def scan_recursive(obj):
+    from rpython.rlib.rawrefcount import (REFCNT_CLR_WHITE,
+                                          REFCNT_CLR_GRAY,
+                                          REFCNT_CLR_GREEN,
+                                          REFCNT_CLR_MASK,
+                                          REFCNT_MASK)
+    from rpython.rtyper.annlowlevel import llhelper
+    debug_print("scan_recursive", obj)
+    rc = obj.c_ob_refcnt
+    if (rc & REFCNT_CLR_MASK == REFCNT_CLR_GRAY or
+       rc & REFCNT_CLR_MASK == REFCNT_CLR_GREEN):
+        if rc & REFCNT_MASK > 0 and rc & REFCNT_CLR_MASK != REFCNT_CLR_GREEN:
+            scan_black_recursive(obj)
+        else:
+            obj.c_ob_refcnt = rc & ~REFCNT_CLR_MASK | REFCNT_CLR_WHITE
+            func_ptr = llhelper(VISIT_FUNCTYPE, visit_scan)
+            traverse(obj, func_ptr)
+
+def visit_collect_white(obj, args):
+    collect_white_recursive(obj)
+    return rffi.cast(rffi.INT_real, 0)
+
+def collect_white_recursive(obj):
+    from rpython.rlib.rawrefcount import (REFCNT_CLR_WHITE,
+                                          REFCNT_CLR_BLACK,
+                                          REFCNT_CLR_MASK,
+                                          REFCNT_CYCLE_BUFFERED,
+                                          REFCNT_FROM_PYPY)
+    from pypy.module.cpyext.api import generic_cpy_call
+    from rpython.rtyper.annlowlevel import llhelper
+    debug_print("collect_white_recursive", obj)
+    rc = obj.c_ob_refcnt
+    if (rc & REFCNT_CLR_MASK == REFCNT_CLR_WHITE and
+       rc & REFCNT_CYCLE_BUFFERED == 0):
+        obj.c_ob_refcnt = rc & ~REFCNT_CLR_MASK | REFCNT_CLR_BLACK
+        func_ptr = llhelper(VISIT_FUNCTYPE, visit_collect_white)
+        traverse(obj, func_ptr)
+        if (rc & REFCNT_FROM_PYPY == 0 and
+           obj.c_ob_type and obj.c_ob_type.c_tp_free):
+            debug_print("free", obj)
+            generic_cpy_call(True, obj.c_ob_type.c_tp_free, obj)
 
 # ____________________________________________________________
 
@@ -1685,6 +1786,7 @@
         #
         # visit the P list from rawrefcount, if enabled.
         if self.rrc_enabled:
+            self.rrc_collect_cycles()  # TODO only for testing
             self.rrc_minor_collection_trace()
         #
         # visit the "probably young" objects with finalizers.  They
@@ -2303,6 +2405,7 @@
                 self.visit_all_objects()
                 #
                 if self.rrc_enabled:
+                    self.rrc_collect_cycles()
                     self.rrc_major_collection_trace()
                 #
                 ll_assert(not (self.probably_young_objects_with_finalizers
@@ -2901,13 +3004,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
@@ -2916,6 +3019,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
@@ -2937,7 +3041,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):
@@ -2957,14 +3061,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_buffer_pyobj(self, pyobject):
+        self.rrc_buffered.append(pyobject)
+
     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_from_obj(self, gcobj):
         obj = llmemory.cast_ptr_to_adr(gcobj)
@@ -2975,7 +3082,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):
@@ -2996,15 +3103,13 @@
                                       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)
 
@@ -3021,14 +3126,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
@@ -3059,23 +3164,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)
@@ -3086,22 +3192,62 @@
                 # 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_collect_cycles(self):
+        self.rrc_buffered.foreach(self._rrc_cycle_mark_roots, None)
+        self.rrc_buffered.foreach(self._rrc_cycle_scan_roots, None)
+        self.rrc_buffered.foreach(self._rrc_cycle_collect_roots, None)
+
+    def _rrc_cycle_mark_roots(self, pyobject, ignore):
+        from pypy.module.cpyext.api import generic_cpy_call
+        from rpython.rlib.rawrefcount import (REFCNT_CYCLE_BUFFERED,
+                                              REFCNT_CLR_MASK,
+                                              REFCNT_CLR_PURPLE,
+                                              REFCNT_MASK,
+                                              W_MARKER_DEALLOCATING,
+                                              mark_deallocating)
+        obj = self._pyobj(pyobject)
+        rc = obj.c_ob_refcnt
+        debug_print("_rrc_cycle_mark_roots", obj)
+        if rc & REFCNT_CLR_MASK == REFCNT_CLR_PURPLE and \
+           rc & REFCNT_MASK > 0:
+            mark_gray_recursive(obj)
+        else:
+            obj.c_ob_refcnt = rc & ~REFCNT_CYCLE_BUFFERED
+            self.rrc_buffered.remove(pyobject)
+            if rc & REFCNT_MASK == 0:
+                mark_deallocating(W_MARKER_DEALLOCATING, obj)
+                generic_cpy_call(True, obj.c_ob_type.c_tp_dealloc, obj)
+
+    def _rrc_cycle_scan_roots(self, pyobject, ignore):
+        obj = self._pyobj(pyobject)
+        debug_print("_rrc_cycle_scan_roots", obj)
+        scan_recursive(obj)
+
+    def _rrc_cycle_collect_roots(self, pyobject, ignore):
+        from rpython.rlib.rawrefcount import REFCNT_CYCLE_BUFFERED
+        obj = self._pyobj(pyobject)
+        debug_print("_rrc_cycle_collect_roots", obj)
+        self.rrc_buffered.remove(pyobject)
+        obj.c_ob_refcnt = obj.c_ob_refcnt & ~REFCNT_CYCLE_BUFFERED
+        collect_white_recursive(obj)
+
     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_FROM_PYPY,
+                                              REFCNT_FROM_PYPY_LIGHT,
+                                              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()
@@ -3131,7 +3277,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
@@ -4,6 +4,7 @@
 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 pypy.module.cpyext.api import PyObject, PyTypeObject
 
 PYOBJ_HDR = IncrementalMiniMarkGC.PYOBJ_HDR
 PYOBJ_HDR_PTR = IncrementalMiniMarkGC.PYOBJ_HDR_PTR
@@ -56,21 +57,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
@@ -87,13 +89,13 @@
         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 +108,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 +131,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 +149,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 +166,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 +199,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 +216,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 +234,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 +254,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 +279,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 +291,10 @@
         check_alive(0)
         self._collect(major=True)
         check_alive(0)
+
+    def test_cycle(self):
+        p1, p1ref, r1, r1addr, check_alive = (
+            self._rawrefcount_pair(42, is_pyobj=True))
+        self.gc.rawrefcount_buffer_pyobj(r1addr)
+        self.gc.rrc_collect_cycles()
+        lltype.free(r1, flavor='raw')
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
@@ -482,6 +482,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)
@@ -1292,6 +1296,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
@@ -9,7 +9,8 @@
 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
 
 
 MAX_BIT = int(math.log(sys.maxint, 2))
@@ -33,6 +34,7 @@
 REFCNT_CLR_GREEN = 4 << REFCNT_CLR_OFFS   # Acyclic
 REFCNT_CLR_RED = 5 << REFCNT_CLR_OFFS     # Cand cycle undergoing SIGMA-comp.
 REFCNT_CLR_ORANGE = 6 << REFCNT_CLR_OFFS  # Cand cycle awaiting epoch boundary
+REFCNT_CLR_MASK = 7 << REFCNT_CLR_OFFS
 
 # Cyclic reference count with overflow bit
 REFCNT_CRC_OVERFLOW = 1 << REFCNT_CRC_OFFS + REFCNT_BITS
@@ -46,6 +48,8 @@
 
 RAWREFCOUNT_DEALLOC_TRIGGER = lltype.Ptr(lltype.FuncType([], lltype.Void))
 
+W_MARKER_DEALLOCATING = W_Root()
+
 
 def _build_pypy_link(p):
     res = len(_adr2pypy)
@@ -70,21 +74,41 @@
 
 _refcount_overflow = dict()
 
+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
+
 # TODO: if object moves, address changes!
 def overflow_new(obj):
-    _refcount_overflow[id(obj)] = 0
+    _refcount_overflow[objectmodel.current_object_addr_as_int(obj)] = 0
 def overflow_add(obj):
-    _refcount_overflow[id(obj)] += 1
+    _refcount_overflow[objectmodel.current_object_addr_as_int(obj)] += 1
 def overflow_sub(obj):
-    c = _refcount_overflow[id(obj)]
+    addr = objectmodel.current_object_addr_as_int(obj)
+    c = _refcount_overflow[addr]
     if c > 0:
-        _refcount_overflow[id(obj)] = c - 1
+        _refcount_overflow[addr] = c - 1
         return False
     else:
-        _refcount_overflow.pop(id(obj))
+        _refcount_overflow.pop(addr)
         return True
 def overflow_get(obj):
-    return _refcount_overflow[id(obj)]
+    return _refcount_overflow[objectmodel.current_object_addr_as_int(obj)]
 
 # TODO: _cyclic_refcount_overflow = dict()
 
@@ -136,6 +160,10 @@
     ob.c_ob_pypy_link = _build_pypy_link(marker)
 
 @not_rpython
+def buffer_pyobj(ob):
+    pass  # TODO: implement?
+
+@not_rpython
 def from_obj(OB_PTR_TYPE, p):
     ob = _pypy2ob.get(p)
     if ob is None:
@@ -321,6 +349,19 @@
 
 
 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
 
     def compute_result_annotation(self, s_OB_PTR_TYPE, s_p):
diff --git a/rpython/rlib/rgc.py b/rpython/rlib/rgc.py
--- a/rpython/rlib/rgc.py
+++ b/rpython/rlib/rgc.py
@@ -522,8 +522,8 @@
         translator = hop.rtyper.annotator.translator
         fq = hop.args_s[0].const
         graph = translator._graphof(fq.finalizer_trigger.im_func)
-        #InstanceRepr.check_graph_of_del_does_not_call_too_much(hop.rtyper,
-        #                                                       graph)
+        InstanceRepr.check_graph_of_del_does_not_call_too_much(hop.rtyper,
+                                                               graph)
         hop.exception_cannot_occur()
         return hop.inputconst(lltype.Signed, hop.s_result.const)
 
diff --git a/rpython/rtyper/llinterp.py b/rpython/rtyper/llinterp.py
--- a/rpython/rtyper/llinterp.py
+++ b/rpython/rtyper/llinterp.py
@@ -966,6 +966,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
@@ -492,6 +492,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):
diff --git a/rpython/rtyper/rclass.py b/rpython/rtyper/rclass.py
--- a/rpython/rtyper/rclass.py
+++ b/rpython/rtyper/rclass.py
@@ -585,8 +585,8 @@
                 assert len(s_func.descriptions) == 1
                 funcdesc, = s_func.descriptions
                 graph = funcdesc.getuniquegraph()
-                #self.check_graph_of_del_does_not_call_too_much(self.rtyper,
-                #                                               graph)
+                self.check_graph_of_del_does_not_call_too_much(self.rtyper,
+                                                               graph)
                 FUNCTYPE = FuncType([Ptr(source_repr.object_type)], Void)
                 destrptr = functionptr(FUNCTYPE, graph.name,
                                        graph=graph,
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to