[pypy-commit] pypy cpyext-gc-cycle: Implemented tuple untracking for rrc objects

stevie_92 pypy.commits at gmail.com
Fri Jun 21 16:06:20 EDT 2019


Author: Stefan Beyer <home at sbeyer.at>
Branch: cpyext-gc-cycle
Changeset: r96835:a1dc8b9e7d98
Date: 2019-06-21 22:05 +0200
http://bitbucket.org/pypy/pypy/changeset/a1dc8b9e7d98/

Log:	Implemented tuple untracking for rrc objects Improved stability list
	traversal and removed unneccesary debug output Fixed inheritance
	issues with some cypthon slots

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
@@ -1216,6 +1216,15 @@
     state.C._PyPy_init_pyobj_list = rffi.llexternal(
         '_PyPy_init_pyobj_list', [], PyGC_HeadPtr,
         compilation_info=eci, _nowrapper=True)
+    state.C._PyPy_init_tuple_list = rffi.llexternal(
+        '_PyPy_init_tuple_list', [], PyGC_HeadPtr,
+        compilation_info=eci, _nowrapper=True)
+    state.C._PyTuple_MaybeUntrack = rffi.llexternal(
+        '_PyTuple_MaybeUntrack', [PyObject], lltype.Signed,
+        compilation_info=eci, _nowrapper=True)
+    state.C._PyList_CheckExact = rffi.llexternal(
+        '_PyList_CheckExact', [PyObject], lltype.Signed,
+        compilation_info=eci, _nowrapper=True)
     state.C._PyPy_gc_as_pyobj = rffi.llexternal(
         '_PyPy_gc_as_pyobj', [PyGC_HeadPtr], GCHdr_PyObject,
         compilation_info=eci, _nowrapper=True)
@@ -1348,7 +1357,9 @@
 
     # initialize the pyobj_list for the gc
     pyobj_list = space.fromcache(State).C._PyPy_init_pyobj_list()
+    pyobj_tuple_list = space.fromcache(State).C._PyPy_init_tuple_list()
     rawrefcount._init_pyobj_list(pyobj_list)
+    rawrefcount._init_pyobj_list(pyobj_tuple_list)
 
     # we need to call this *after* the init code above, because it might
     # indirectly call some functions which are attached to pypyAPI (e.g., we
@@ -1552,6 +1563,7 @@
                          source_dir / "object.c",
                          source_dir / "typeobject.c",
                          source_dir / "tupleobject.c",
+                         source_dir / "listobject.c",
                          ]
 
 def build_eci(code, use_micronumpy=False, translating=False):
diff --git a/pypy/module/cpyext/include/listobject.h b/pypy/module/cpyext/include/listobject.h
--- a/pypy/module/cpyext/include/listobject.h
+++ b/pypy/module/cpyext/include/listobject.h
@@ -1,4 +1,16 @@
-/* empty */
+#ifndef Py_LISTOBJECT_H
+#define Py_LISTOBJECT_H
+#ifdef __cplusplus
+extern "C" {
+#endif
+
 #define PyList_Check(op) \
 		 PyType_FastSubclass((op)->ob_type, Py_TPFLAGS_LIST_SUBCLASS)
 #define PyList_CheckExact(op) ((op)->ob_type == &PyList_Type)
+
+PyAPI_FUNC(Py_ssize_t) _PyList_CheckExact(PyObject *);
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* !Py_LISTOBJECT_H */
\ No newline at end of file
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
@@ -295,6 +295,7 @@
                 ( (type *) _PyObject_GC_NewVar(typeobj, size) )
 
 extern PyGC_Head *_pypy_rawrefcount_pyobj_list;
+extern PyGC_Head *_pypy_rawrefcount_tuple_list;
 
 #define _Py_AS_GC(o) ((PyGC_Head *)(o)-1)
 #define _Py_FROM_GC(g) ((PyObject *)(((PyGC_Head *)g)+1))
@@ -327,9 +328,15 @@
 #define _PyGC_REFS_REACHABLE               (-3)
 #define _PyGC_REFS_TENTATIVELY_UNREACHABLE (-4)
 
+#define PyType_IS_GC(t) PyType_HasFeature((t), Py_TPFLAGS_HAVE_GC)
+#define PyObject_IS_GC(o) \
+    (PyType_IS_GC(Py_TYPE(o)) \
+    && (Py_TYPE(o)->tp_is_gc == NULL || Py_TYPE(o)->tp_is_gc(o)))
+
 #define _PyGC_IS_TRACKED(o) (_PyGC_REFS(o) != _PyGC_REFS_UNTRACKED)
-
-#define PyType_IS_GC(t) PyType_HasFeature((t), Py_TPFLAGS_HAVE_GC)
+#define _PyObject_GC_MAY_BE_TRACKED(obj) \
+    (PyObject_IS_GC(obj) && \
+    (!PyTuple_CheckExact(obj) || _PyGC_IS_TRACKED(obj)))
 
 PyAPI_FUNC(void) PyObject_GC_Track(void *);
 PyAPI_FUNC(void) PyObject_GC_UnTrack(void *);
@@ -344,6 +351,16 @@
     ((PyGC_Head *)g->gc_prev)->gc_next = g; \
     _pypy_rawrefcount_pyobj_list->gc_prev = g; \
  } while(0)
+ #define _PyObject_GC_TRACK_Tuple(o)     do { \
+    PyGC_Head *g = _Py_AS_GC(o); \
+    if (_PyGCHead_REFS(g) != _PyGC_REFS_UNTRACKED) \
+        Py_FatalError("GC object already tracked"); \
+    _PyGCHead_SET_REFS(g, _PyGC_REFS_REACHABLE); \
+    g->gc_next = _pypy_rawrefcount_tuple_list; \
+    g->gc_prev = _pypy_rawrefcount_tuple_list->gc_prev; \
+    ((PyGC_Head *)g->gc_prev)->gc_next = g; \
+    _pypy_rawrefcount_tuple_list->gc_prev = g; \
+ } while(0)
 #define _PyObject_GC_UNTRACK(o)   do { \
     PyGC_Head *g = _Py_AS_GC(o); \
     assert(_PyGCHead_REFS(g) != _PyGC_REFS_UNTRACKED); \
@@ -438,6 +455,7 @@
 PyAPI_FUNC(void) _PyPy_subtype_dealloc(PyObject *);
 PyAPI_FUNC(void) _PyPy_object_dealloc(PyObject *);
 PyAPI_FUNC(PyGC_Head *) _PyPy_init_pyobj_list();
+PyAPI_FUNC(PyGC_Head *) _PyPy_init_tuple_list();
 PyAPI_FUNC(GCHdr_PyObject *) _PyPy_gc_as_pyobj(PyGC_Head *);
 PyAPI_FUNC(PyGC_Head *) _PyPy_pyobj_as_gc(GCHdr_PyObject *);
 PyAPI_FUNC(Py_ssize_t) _PyPy_finalizer_type(PyGC_Head *);
diff --git a/pypy/module/cpyext/include/tupleobject.h b/pypy/module/cpyext/include/tupleobject.h
--- a/pypy/module/cpyext/include/tupleobject.h
+++ b/pypy/module/cpyext/include/tupleobject.h
@@ -20,6 +20,7 @@
 PyAPI_FUNC(void) _PyPy_tuple_dealloc(PyObject *);
 PyAPI_FUNC(void) _PyPy_tuple_free(void *);
 PyAPI_FUNC(int) _PyPy_tuple_traverse(PyObject *ob, visitproc visit, void *arg);
+PyAPI_FUNC(Py_ssize_t) _PyTuple_MaybeUntrack(PyObject *);
 
 /* defined in varargswrapper.c */
 PyAPI_FUNC(PyObject *) PyTuple_Pack(Py_ssize_t, ...);
diff --git a/pypy/module/cpyext/src/listobject.c b/pypy/module/cpyext/src/listobject.c
new file mode 100644
--- /dev/null
+++ b/pypy/module/cpyext/src/listobject.c
@@ -0,0 +1,7 @@
+#include "Python.h"
+
+Py_ssize_t
+_PyList_CheckExact(PyObject *op)
+{
+    return op->ob_type == &PyList_Type;
+}
\ No newline at end of file
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
@@ -23,7 +23,10 @@
 void* _pypy_rawrefcount_w_marker_deallocating = (void*) 0xDEADFFF;
 
 static PyGC_Head _internal_pyobj_list;
+static PyGC_Head _internal_tuple_list;
+
 PyGC_Head *_pypy_rawrefcount_pyobj_list = &_internal_pyobj_list;
+PyGC_Head *_pypy_rawrefcount_tuple_list = &_internal_tuple_list;
 
 PyGC_Head *
 _PyPy_init_pyobj_list()
@@ -33,6 +36,14 @@
     return _pypy_rawrefcount_pyobj_list;
 }
 
+PyGC_Head *
+_PyPy_init_tuple_list()
+{
+    _pypy_rawrefcount_tuple_list->gc_next = _pypy_rawrefcount_tuple_list;
+    _pypy_rawrefcount_tuple_list->gc_prev = _pypy_rawrefcount_tuple_list;
+    return _pypy_rawrefcount_tuple_list;
+}
+
 GCHdr_PyObject *
 _PyPy_gc_as_pyobj(PyGC_Head *g)
 {
diff --git a/pypy/module/cpyext/src/tupleobject.c b/pypy/module/cpyext/src/tupleobject.c
--- a/pypy/module/cpyext/src/tupleobject.c
+++ b/pypy/module/cpyext/src/tupleobject.c
@@ -56,7 +56,7 @@
     }
     for (i=0; i < size; i++)
         op->ob_item[i] = NULL;
-    _PyObject_GC_TRACK(op);
+    _PyObject_GC_TRACK_Tuple(op);
     return (PyObject *) op;
 }
 
@@ -105,4 +105,33 @@
     for (i = Py_SIZE(o); --i >= 0; )
         Py_VISIT(o->ob_item[i]);
     return 0;
+}
+
+/* Return 0 if the tuple is untracked afterwards, return 1 if the tuple
+   should always be kept tracked and return 2 if the tuple was not fully
+   intialized yet. */
+Py_ssize_t
+_PyTuple_MaybeUntrack(PyObject *op)
+{
+    PyTupleObject *t;
+    Py_ssize_t i, n;
+
+    if (!PyTuple_CheckExact(op))
+        return 1;
+    if (!_PyGC_IS_TRACKED(op))
+        return 0;
+    t = (PyTupleObject *) op;
+    n = Py_SIZE(t);
+    for (i = 0; i < n; i++) {
+        PyObject *elt = PyTuple_GET_ITEM(t, i);
+        /* Tuple with NULL elements aren't
+           fully constructed, don't untrack
+           them yet. */
+        if (!elt)
+            return 2;
+        if (_PyObject_GC_MAY_BE_TRACKED(elt))
+            return 1;
+    }
+    _PyObject_GC_UNTRACK(op);
+    return 0;
 }
\ No newline at end of file
diff --git a/pypy/module/cpyext/state.py b/pypy/module/cpyext/state.py
--- a/pypy/module/cpyext/state.py
+++ b/pypy/module/cpyext/state.py
@@ -184,47 +184,28 @@
                     from pypy.module.cpyext.listobject import list_traverse
 
                     # convert to pointers with correct types (PyObject)
-                    callback_addr = llmemory.cast_ptr_to_adr(callback)
-                    callback_ptr = llmemory.cast_adr_to_ptr(callback_addr,
-                                                            visitproc)
                     pyobj_addr = llmemory.cast_ptr_to_adr(pyobj_ptr)
                     pyobj = llmemory.cast_adr_to_ptr(pyobj_addr, PyObject)
+
                     # now call tp_traverse (if possible)
                     debug_print("rrc check traverse", pyobj)
-                    pto = pyobj.c_ob_type
-                    if pto and pto.c_tp_name:
-                        tp_name = pto.c_tp_name
-                        name = rffi.charp2str(cts.cast('char*', tp_name))
-                        debug_print("rrc try traverse", pyobj, ": type", pto,
-                                    ": name", name)
-                        pto2 = pto
-                        i = 1
-                        while pto2.c_tp_base:
-                            base = pto2.c_tp_base
-                            if base.c_tp_name:
-                                tp_name = base.c_tp_name
-                                name = rffi.charp2str(cts.cast('char*', tp_name))
-                                debug_print(" " * i * 3, "basetype",
-                                            base, ": name",
-                                            name, "traverse",
-                                            base.c_tp_traverse)
-                            else:
-                                debug_print(" " * i * 3, "unknown base")
-                            pto2 = base
-                            i += 1
 
-                    if pyobj.c_ob_pypy_link != 0: # special traverse
-                        w_obj = from_ref(space, pyobj)
-                        w_obj_type = space.type(w_obj)
-                        if space.is_w(w_obj_type, space.w_list): # list
-                            debug_print('rrc list traverse ', pyobj)
-                            list_traverse(space, w_obj, callback, args)
-                            return
-
-                    if pto and pto.c_tp_traverse:
-                        debug_print("rrc do traverse", pyobj)
-                        generic_cpy_call(space, pto.c_tp_traverse, pyobj,
-                                         callback_ptr, args)
+                    # special traverse for list
+                    if self.C._PyList_CheckExact(pyobj) != 0:
+                        if pyobj.c_ob_pypy_link != 0:
+                            w_obj = from_ref(space, pyobj)
+                            if w_obj:
+                                debug_print('rrc list traverse ', pyobj)
+                                list_traverse(space, w_obj, callback, args)
+                    else:
+                        pto = pyobj.c_ob_type
+                        if pto and pto.c_tp_traverse:
+                            callback_addr = llmemory.cast_ptr_to_adr(callback)
+                            callback_ptr = llmemory.cast_adr_to_ptr(
+                                callback_addr, visitproc)
+                            debug_print("rrc do traverse", pyobj)
+                            generic_cpy_call(space, pto.c_tp_traverse, pyobj,
+                                             callback_ptr, args)
 
                 self.tp_traverse = (lambda o, v, a:_tp_traverse(o, v, a))
 
@@ -258,16 +239,18 @@
                 # This must be called in RPython, the untranslated version
                 # does something different. Sigh.
                 pypyobj_list = self.C._PyPy_init_pyobj_list()
+                pypyobj_tuple_list = self.C._PyPy_init_tuple_list()
                 rawrefcount.init(
                     llhelper(rawrefcount.RAWREFCOUNT_DEALLOC_TRIGGER,
                              self.dealloc_trigger),
                     llhelper(rawrefcount.RAWREFCOUNT_TRAVERSE,
                              self.tp_traverse),
-                    pypyobj_list,
+                    pypyobj_list, pypyobj_tuple_list,
                     self.C._PyPy_gc_as_pyobj, self.C._PyPy_pyobj_as_gc,
                     self.C._PyPy_finalizer_type,
                     llhelper(rawrefcount.RAWREFCOUNT_CLEAR_WR_TYPE,
-                             self.clear_weakref_callbacks))
+                             self.clear_weakref_callbacks),
+                    self.C._PyTuple_MaybeUntrack)
             self.builder.attach_all(space)
 
         setup_new_method_def(space)
diff --git a/pypy/module/cpyext/typeobject.py b/pypy/module/cpyext/typeobject.py
--- a/pypy/module/cpyext/typeobject.py
+++ b/pypy/module/cpyext/typeobject.py
@@ -740,6 +740,10 @@
             pto.c_tp_clear = base.c_tp_clear
         if not pto.c_tp_traverse:
             pto.c_tp_traverse = base.c_tp_traverse
+        if not pto.c_tp_is_gc:
+            pto.c_tp_is_gc = base.c_tp_is_gc
+        if not pto.c_tp_finalize:
+            pto.c_tp_finalize = base.c_tp_finalize
         # XXX check for correct GC flags!
         if not pto.c_tp_free:
             pto.c_tp_free = base.c_tp_free
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
@@ -3114,6 +3114,8 @@
                                                          lltype.Signed))
     RAWREFCOUNT_CLEAR_WR_TYPE = lltype.Ptr(lltype.FuncType([llmemory.GCREF],
                                                             lltype.Void))
+    RAWREFCOUNT_MAYBE_UNTRACK_TUPLE = \
+        lltype.Ptr(lltype.FuncType([PYOBJ_HDR_PTR], lltype.Signed))
     RAWREFCOUNT_FINALIZER_NONE = 0
     RAWREFCOUNT_FINALIZER_MODERN = 1
     RAWREFCOUNT_FINALIZER_LEGACY = 2
@@ -3127,8 +3129,9 @@
         return llmemory.cast_adr_to_ptr(pygchdraddr, self.PYOBJ_GC_HDR_PTR)
 
     def rawrefcount_init(self, dealloc_trigger_callback, tp_traverse,
-                         pyobj_list, gc_as_pyobj, pyobj_as_gc, finalizer_type,
-                         clear_weakref_callback):
+                         pyobj_list, tuple_list, gc_as_pyobj, pyobj_as_gc,
+                         finalizer_type, clear_weakref_callback,
+                         tuple_maybe_untrack):
         # see pypy/doc/discussion/rawrefcount.rst
         if not self.rrc_enabled:
             self.rrc_p_list_young = self.AddressStack()
@@ -3141,6 +3144,7 @@
             self.rrc_dealloc_pending = self.AddressStack()
             self.rrc_tp_traverse = tp_traverse
             self.rrc_pyobj_list = self._pygchdr(pyobj_list)
+            self.rrc_tuple_list = self._pygchdr(tuple_list)
             self.rrc_pyobj_old_list = self._rrc_gc_list_new()
             self.rrc_pyobj_isolate_list = self._rrc_gc_list_new()
             self.rrc_pyobj_dead_list = self._rrc_gc_list_new()
@@ -3150,6 +3154,7 @@
             self.rrc_pyobj_as_gc = pyobj_as_gc
             self.rrc_finalizer_type = finalizer_type
             self.rrc_clear_weakref_callback = clear_weakref_callback
+            self.rrc_tuple_maybe_untrack = tuple_maybe_untrack
             self.rrc_enabled = True
             self.rrc_cycle_enabled = True
             self.rrc_state = self.RAWREFCOUNT_STATE_DEFAULT
@@ -3226,6 +3231,10 @@
             gchdr = self._rrc_gc_list_pop(self.rrc_pyobj_isolate_list)
             self._rrc_gc_list_add(self.rrc_pyobj_old_list, gchdr)
             return llmemory.cast_ptr_to_adr(self.rrc_gc_as_pyobj(gchdr))
+#        if not self._rrc_gc_list_is_empty(self.rrc_tuple_isolate_list):
+#            gchdr = self._rrc_gc_list_pop(self.rrc_tuple_isolate_list)
+#            self._rrc_gc_list_add(self.rrc_tuple_old_list, gchdr)
+#            return llmemory.cast_ptr_to_adr(self.rrc_gc_as_pyobj(gchdr))
         return llmemory.NULL
 
     def rawrefcount_cyclic_garbage_head(self):
@@ -3288,6 +3297,8 @@
         if self.rrc_enabled and (self.rrc_dealloc_pending.non_empty() or
                                  not self._rrc_gc_list_is_empty(
                                      self.rrc_pyobj_isolate_list) or
+#                                 not self._rrc_gc_list_is_empty(
+#                                     self.rrc_tuple_isolate_list) or
                                  not self._rrc_gc_list_is_empty(
                                      self.rrc_pyobj_dead_list) or
                                  not self._rrc_gc_list_is_empty(
@@ -3415,6 +3426,10 @@
         if not self.rrc_cycle_enabled:
             self._rrc_debug_check_consistency(print_label="begin-mark")
 
+        # First, untrack all tuples with only non-gc rrc objects and promote
+        # all other tuples to the pyobj_list
+        self._rrc_untrack_tuples()
+
         # Only trace and mark rawrefcounted object if we are not doing
         # something special, like building gc.garbage.
         if (self.rrc_state == self.RAWREFCOUNT_STATE_DEFAULT and
@@ -3424,7 +3439,7 @@
             if not self._rrc_gc_list_is_empty(self.rrc_pyobj_old_list):
                 merged_old_list = self._rrc_check_finalizer()
             # collect all rawrefcounted roots
-            self._rrc_collect_rawrefcount_roots(self.rrc_pyobj_list)
+            self._rrc_collect_roots(self.rrc_pyobj_list)
             if merged_old_list:
                 # set all refcounts to zero for objects in dead list
                 # (might have been incremented) by fix_refcnt
@@ -3449,13 +3464,7 @@
         # now mark all pypy objects at the border, depending on the results
         debug_print("use_cylicrc", use_cylicrc)
         self.rrc_p_list_old.foreach(self._rrc_major_trace, use_cylicrc)
-
-        # TODO: check again, if some new border objects have been marked and
-        #       continue marking recursively... why needed? -> wrapper for
-        #       pypy-obj is no pygc-obj??? ...KI
-
         self._rrc_debug_check_consistency(print_label="end-mark")
-        #self.rrc_o_list_old.foreach(self._rrc_major_trace, use_cylicrc) # TODO: remove?
 
     def _rrc_major_trace(self, pyobject, use_cylicrefcnt):
         from rpython.rlib.rawrefcount import REFCNT_FROM_PYPY
@@ -3513,12 +3522,12 @@
 
     def rrc_major_collection_free(self):
         if self.rrc_state == self.RAWREFCOUNT_STATE_DEFAULT:
+            self._rrc_debug_check_consistency()
             if not self._rrc_gc_list_is_empty(self.rrc_pyobj_old_list):
-                self._rrc_debug_check_consistency()
                 self._rrc_clear_weakref_callbacks()
                 self._rrc_gc_list_merge(self.rrc_pyobj_old_list,
                                         self.rrc_pyobj_dead_list)
-                self._rrc_debug_check_consistency(print_label="before-sweep")
+            self._rrc_debug_check_consistency(print_label="before-sweep")
 
         ll_assert(self.rrc_p_dict_nurs.length() == 0, "p_dict_nurs not empty 2")
         length_estimate = self.rrc_p_dict.length()
@@ -3543,12 +3552,26 @@
         # Look for any weakrefs within the trash cycle and remove the callback.
         # This is only needed for weakrefs created from rawrefcounted objects
         # because weakrefs from gc-managed objects are going away anyway.
-        gchdr = self.rrc_pyobj_old_list.c_gc_next
-        while gchdr <> self.rrc_pyobj_old_list:
+        list = self.rrc_pyobj_old_list
+        gchdr = list.c_gc_next
+        while gchdr <> list:
             pyobj = self.rrc_gc_as_pyobj(gchdr)
             self._rrc_traverse_weakref(pyobj)
             gchdr = gchdr.c_gc_next
 
+    def _rrc_untrack_tuples(self):
+        gchdr = self.rrc_tuple_list.c_gc_next
+        while gchdr <> self.rrc_tuple_list:
+            gchdr_next = gchdr.c_gc_next
+            pyobj = self.rrc_gc_as_pyobj(gchdr)
+            result = self.rrc_tuple_maybe_untrack(pyobj)
+            if result == 1: # contains gc objects -> promote to pyobj list
+                next = gchdr.c_gc_next
+                next.c_gc_prev = gchdr.c_gc_prev
+                gchdr.c_gc_prev.c_gc_next = next
+                self._rrc_gc_list_add(self.rrc_pyobj_list, gchdr)
+            gchdr = gchdr_next
+
     def _rrc_visit_weakref(pyobj, self_ptr):
         from rpython.rtyper.annlowlevel import cast_adr_to_nongc_instance
         #
@@ -3591,11 +3614,31 @@
         else:
             self._rrc_free(pyobject, True)
 
-    def _rrc_collect_rawrefcount_roots(self, pygclist):
+    def _rrc_collect_roots(self, pygclist):
+        # Initialize the cyclic refcount with the real refcount.
+        self._rrc_collect_roots_init_list(pygclist)
+
+        # For all non-gc pyobjects which have a refcount > 0,
+        # mark all reachable objects on the pypy side
+        self.rrc_p_list_old.foreach(self._rrc_major_trace_nongc, None)
+
+        # For every object in this set, if it is marked, add 1 as a real
+        # refcount (p_list => pyobj stays alive if obj stays alive).
+        self.rrc_p_list_old.foreach(self._rrc_obj_fix_refcnt, None)
+        self.rrc_o_list_old.foreach(self._rrc_obj_fix_refcnt, None)
+
+        # Subtract all internal refcounts from the cyclic refcount
+        # of rawrefcounted objects
+        self._rrc_collect_roots_subtract_internal(pygclist)
+
+        # now all rawrefcounted roots or live border objects have a
+        # refcount > 0
+        self._rrc_debug_check_consistency(print_label="rc-initialized")
+
+
+    def _rrc_collect_roots_init_list(self, pygclist):
         from rpython.rlib.rawrefcount import REFCNT_FROM_PYPY
         from rpython.rlib.rawrefcount import REFCNT_FROM_PYPY_LIGHT
-        #
-        # Initialize the cyclic refcount with the real refcount.
         pygchdr = pygclist.c_gc_next
         while pygchdr <> pygclist:
             refcnt = self.rrc_gc_as_pyobj(pygchdr).c_ob_refcnt
@@ -3606,28 +3649,13 @@
             self._rrc_pyobj_gc_refcnt_set(pygchdr, refcnt)
             pygchdr = pygchdr.c_gc_next
 
-        # For all non-gc pyobjects which have a refcount > 0,
-        # mark all reachable objects on the pypy side
-        self.rrc_p_list_old.foreach(self._rrc_major_trace_nongc, None)
-
-        # For every object in this set, if it is marked, add 1 as a real
-        # refcount (p_list => pyobj stays alive if obj stays alive).
-        self.rrc_p_list_old.foreach(self._rrc_obj_fix_refcnt, None)
-        self.rrc_o_list_old.foreach(self._rrc_obj_fix_refcnt, None)
-
-        self._rrc_debug_check_consistency(print_label="rc-initialized")
-
-        # Subtract all internal refcounts from the cyclic refcount
-        # of rawrefcounted objects
+    def _rrc_collect_roots_subtract_internal(self, pygclist):
         pygchdr = pygclist.c_gc_next
         while pygchdr <> pygclist:
             pyobj = self.rrc_gc_as_pyobj(pygchdr)
             self._rrc_traverse(pyobj, -1)
             pygchdr = pygchdr.c_gc_next
 
-        # now all rawrefcounted roots or live border objects have a
-        # refcount > 0
-
     def _rrc_pyobj_gc_refcnt_set(self, pygchdr, refcnt):
         pygchdr.c_gc_refs &= self.RAWREFCOUNT_REFS_MASK_FINALIZED
         pygchdr.c_gc_refs |= refcnt << self.RAWREFCOUNT_REFS_SHIFT
@@ -3662,49 +3690,54 @@
     def _rrc_mark_rawrefcount(self):
         if self._rrc_gc_list_is_empty(self.rrc_pyobj_list):
             self._rrc_gc_list_init(self.rrc_pyobj_old_list)
-            return
+        else:
+            self._rrc_gc_list_move(self.rrc_pyobj_list,
+                                   self.rrc_pyobj_old_list)
         # as long as new objects with cyclic a refcount > 0 or alive border
         # objects are found, increment the refcount of all referenced objects
         # of those newly found objects
-        self._rrc_gc_list_move(self.rrc_pyobj_list, self.rrc_pyobj_old_list)
         found_alive = True
+        pyobj_old = self.rrc_pyobj_list
         #
         while found_alive:
             found_alive = False
             gchdr = self.rrc_pyobj_old_list.c_gc_next
             while gchdr <> self.rrc_pyobj_old_list:
                 next_old = gchdr.c_gc_next
-                alive = (gchdr.c_gc_refs >> self.RAWREFCOUNT_REFS_SHIFT) > 0
-                pyobj = self.rrc_gc_as_pyobj(gchdr)
-                if pyobj.c_ob_pypy_link <> 0:
-                    intobj = pyobj.c_ob_pypy_link
-                    obj = llmemory.cast_int_to_adr(intobj)
-                    if not alive and self.header(obj).tid & (
-                            GCFLAG_VISITED | GCFLAG_NO_HEAP_PTRS):
-                        # add fake refcount, to mark it as live
-                        gchdr.c_gc_refs += 1 << self.RAWREFCOUNT_REFS_SHIFT
-                        alive = True
-                if alive:
-                    # remove from old list
-                    next = gchdr.c_gc_next
-                    next.c_gc_prev = gchdr.c_gc_prev
-                    gchdr.c_gc_prev.c_gc_next = next
-                    # add to new list
-                    self._rrc_gc_list_add(self.rrc_pyobj_list, gchdr)
-                    # increment refcounts
-                    self._rrc_traverse(pyobj, 1)
-                    # mark recursively, if it is a pypyobj
-                    if pyobj.c_ob_pypy_link <> 0:
-                        intobj = pyobj.c_ob_pypy_link
-                        obj = llmemory.cast_int_to_adr(intobj)
-                        self.objects_to_trace.append(obj)
-                        self.visit_all_objects()
-                    found_alive = True
+                found_alive |= self._rrc_mark_rawrefcount_obj(gchdr, pyobj_old)
                 gchdr = next_old
         #
         # now all rawrefcounted objects, which are alive, have a cyclic
         # refcount > 0 or are marked
 
+    def _rrc_mark_rawrefcount_obj(self, gchdr, gchdr_move):
+        alive = (gchdr.c_gc_refs >> self.RAWREFCOUNT_REFS_SHIFT) > 0
+        pyobj = self.rrc_gc_as_pyobj(gchdr)
+        if pyobj.c_ob_pypy_link <> 0:
+            intobj = pyobj.c_ob_pypy_link
+            obj = llmemory.cast_int_to_adr(intobj)
+            if not alive and self.header(obj).tid & (
+                    GCFLAG_VISITED | GCFLAG_NO_HEAP_PTRS):
+                # add fake refcount, to mark it as live
+                gchdr.c_gc_refs += 1 << self.RAWREFCOUNT_REFS_SHIFT
+                alive = True
+        if alive:
+            # remove from old list
+            next = gchdr.c_gc_next
+            next.c_gc_prev = gchdr.c_gc_prev
+            gchdr.c_gc_prev.c_gc_next = next
+            # add to new list (or not, if it is a tuple)
+            self._rrc_gc_list_add(gchdr_move, gchdr)
+            # increment refcounts
+            self._rrc_traverse(pyobj, 1)
+            # mark recursively, if it is a pypyobj
+            if pyobj.c_ob_pypy_link <> 0:
+                intobj = pyobj.c_ob_pypy_link
+                obj = llmemory.cast_int_to_adr(intobj)
+                self.objects_to_trace.append(obj)
+                self.visit_all_objects()
+        return alive
+
     def _rrc_find_garbage(self):
         found_garbage = False
         gchdr = self.rrc_pyobj_old_list.c_gc_next
@@ -3788,7 +3821,7 @@
         # Check, if the cyclic isolate from the last collection cycle
         # is reachable from outside, after the finalizers have been
         # executed.
-        self._rrc_collect_rawrefcount_roots(self.rrc_pyobj_old_list)
+        self._rrc_collect_roots(self.rrc_pyobj_old_list)
         found_alive = False
         gchdr = self.rrc_pyobj_old_list.c_gc_next
         while gchdr <> self.rrc_pyobj_old_list:
@@ -3899,6 +3932,8 @@
                 debug_start("rrc-lists " + print_label)
             self._rrc_debug_check_list(self.rrc_pyobj_list,
                                        should_print, "rrc_pyobj_list")
+            self._rrc_debug_check_list(self.rrc_tuple_list,
+                                       should_print, "rrc_tuple_list")
             self._rrc_debug_check_list(self.rrc_pyobj_old_list,
                                        should_print, "rrc_pyobj_old_list")
             self._rrc_debug_check_list(self.rrc_pyobj_dead_list,
diff --git a/rpython/memory/gc/test/dot/free_cpython_simple.dot b/rpython/memory/gc/test/dot/free_cpython_tuple_1.dot
copy from rpython/memory/gc/test/dot/free_cpython_simple.dot
copy to rpython/memory/gc/test/dot/free_cpython_tuple_1.dot
--- a/rpython/memory/gc/test/dot/free_cpython_simple.dot
+++ b/rpython/memory/gc/test/dot/free_cpython_tuple_1.dot
@@ -1,5 +1,5 @@
 digraph G {
-    "a" [type=C, alive=n];
+    "a" [type=C, alive=n, tuple=y];
     "b" [type=C, alive=n];
     "a" -> "b";
     "b" -> "a";
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
@@ -78,17 +78,32 @@
                         weakref.callback_cleared = True
                         cleared = True
 
+        def rawrefcount_tuple_maybe_untrack(obj):
+            #if foo:
+            #    gchdr = rawrefcount_pyobj_as_gc(obj)
+            #    next = gchdr.c_gc_next
+            #    next.c_gc_prev = gchdr.c_gc_prev
+            #    gchdr.c_gc_prev.c_gc_next = next
+            #    return 0
+            return 1 # TODO: add tests for 0 ("plain" tuple) and 2 (uninitialized)
+
         self.pyobj_list = lltype.malloc(PYOBJ_GC_HDR_PTR.TO, flavor='raw',
                                         immortal=True)
         self.pyobj_list.c_gc_next = self.pyobj_list
         self.pyobj_list.c_gc_prev = self.pyobj_list
+        self.tuple_list = lltype.malloc(PYOBJ_GC_HDR_PTR.TO, flavor='raw',
+                                        immortal=True)
+        self.tuple_list.c_gc_next = self.tuple_list
+        self.tuple_list.c_gc_prev = self.tuple_list
         self.gc.rawrefcount_init(lambda: self.trigger.append(1),
                                  rawrefcount_tp_traverse,
                                  llmemory.cast_ptr_to_adr(self.pyobj_list),
+                                 llmemory.cast_ptr_to_adr(self.tuple_list),
                                  rawrefcount_gc_as_pyobj,
                                  rawrefcount_pyobj_as_gc,
                                  rawrefcount_finalizer_type,
-                                 rawrefcount_clear_wr)
+                                 rawrefcount_clear_wr,
+                                 rawrefcount_tuple_maybe_untrack)
 
     def _collect(self, major, expected_trigger=0):
         if major:
@@ -138,7 +153,7 @@
         return p1, p1ref, check_alive
 
     def _rawrefcount_pyobj(self, create_immortal=False, is_gc=True,
-                           tracked=True):
+                           tracked=True, tuple=tuple):
         r1 = lltype.malloc(PYOBJ_HDR, flavor='raw',
                            immortal=create_immortal)
         r1.c_ob_refcnt = 0
@@ -146,7 +161,7 @@
         r1addr = llmemory.cast_ptr_to_adr(r1)
 
         if is_gc:
-            self._rawrefcount_add_gc(tracked)
+            self._rawrefcount_add_gc(tracked, tuple)
 
         self.pyobjs.append(r1)
         self.is_pygc.append(is_gc)
@@ -161,7 +176,7 @@
     def _rawrefcount_pair(self, intval, is_light=False, is_pyobj=False,
                           create_old=False, create_immortal=False,
                           rooted=False, force_external=False, is_gc=True,
-                          tracked=True):
+                          tracked=True, tuple=tuple):
         if is_light:
             rc = REFCNT_FROM_PYPY_LIGHT
         else:
@@ -194,7 +209,7 @@
         r1addr = llmemory.cast_ptr_to_adr(r1)
 
         if is_gc:
-            self._rawrefcount_add_gc(tracked)
+            self._rawrefcount_add_gc(tracked, tuple)
 
         self.pyobjs.append(r1)
         self.is_pygc.append(is_gc)
@@ -222,16 +237,22 @@
             return p1
         return p1, p1ref, r1, r1addr, check_alive
 
-    def _rawrefcount_add_gc(self, tracked):
+    def _rawrefcount_add_gc(self, tracked, tuple):
         r1gc = lltype.malloc(PYOBJ_GC_HDR, flavor='raw',
                              immortal=True)
         self.gcobjs.append(r1gc)
         if tracked:
             r1gc.c_gc_refs = 0
-            r1gc.c_gc_next = self.pyobj_list
-            r1gc.c_gc_prev = self.pyobj_list.c_gc_prev
-            r1gc.c_gc_prev.c_gc_next = r1gc
-            self.pyobj_list.c_gc_prev = r1gc
+            if tuple:
+                r1gc.c_gc_next = self.tuple_list
+                r1gc.c_gc_prev = self.tuple_list.c_gc_prev
+                r1gc.c_gc_prev.c_gc_next = r1gc
+                self.tuple_list.c_gc_prev = r1gc
+            else:
+                r1gc.c_gc_next = self.pyobj_list
+                r1gc.c_gc_prev = self.pyobj_list.c_gc_prev
+                r1gc.c_gc_prev.c_gc_next = r1gc
+                self.pyobj_list.c_gc_prev = r1gc
         else:
             r1gc.c_gc_refs = RAWREFCOUNT_REFS_UNTRACKED
 
@@ -485,7 +506,7 @@
 
         class NodeInfo:
             def __init__(self, type, alive, ext_refcnt, finalizer, resurrect,
-                         delete, garbage):
+                         delete, garbage, tuple):
                 self.type = type
                 self.alive = alive
                 self.ext_refcnt = ext_refcnt
@@ -493,6 +514,7 @@
                 self.resurrect = resurrect
                 self.delete = delete
                 self.garbage = garbage
+                self.tuple = tuple
 
         class WeakrefNode(BorderNode):
             def __init__(self, p, pref, r, raddr, check_alive, info, r_dest,
@@ -529,11 +551,12 @@
             resurrect = attr['resurrect'] if 'resurrect' in attr else None
             delete = attr['delete'] if 'delete' in attr else None
             garbage = True if 'garbage' in attr else False
+            tuple = attr['tuple'] == "y" if 'tuple' in attr else False
             info = NodeInfo(type, alive, ext_refcnt, finalizer, resurrect,
-                            delete, garbage)
+                            delete, garbage, tuple)
             if type == "C":
                 r, raddr, check_alive = self._rawrefcount_pyobj(
-                    tracked=tracked)
+                    tracked=tracked, tuple=tuple)
                 r.c_ob_refcnt += ext_refcnt
                 nodes[name] = CPythonNode(r, raddr, check_alive, info)
             elif type == "P":
@@ -545,7 +568,8 @@
             elif type == "B":
                 p, pref, r, raddr, check_alive =\
                     self._rawrefcount_pair(42 + i, rooted=rooted,
-                                           create_old=True, tracked=tracked)
+                                           create_old=True, tracked=tracked,
+                                           tuple=tuple)
                 r.c_ob_refcnt += ext_refcnt
                 nodes[name] = BorderNode(p, pref, r, raddr, check_alive, info)
                 i += 1
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
@@ -483,11 +483,13 @@
             self.rawrefcount_init_ptr = getfn(
                 GCClass.rawrefcount_init,
                 [s_gc, SomePtr(GCClass.RAWREFCOUNT_DEALLOC_TRIGGER),
-                 SomePtr(GCClass.RAWREFCOUNT_TRAVERSE), SomeAddress(),
+                 SomePtr(GCClass.RAWREFCOUNT_TRAVERSE),
+                 SomeAddress(), SomeAddress(),
                  SomePtr(GCClass.RAWREFCOUNT_GC_AS_PYOBJ),
                  SomePtr(GCClass.RAWREFCOUNT_PYOBJ_AS_GC),
                  SomePtr(GCClass.RAWREFCOUNT_FINALIZER_TYPE),
-                 SomePtr(GCClass.RAWREFCOUNT_CLEAR_WR_TYPE)],
+                 SomePtr(GCClass.RAWREFCOUNT_CLEAR_WR_TYPE),
+                 SomePtr(GCClass.RAWREFCOUNT_MAYBE_UNTRACK_TUPLE)],
                 annmodel.s_None)
             self.rawrefcount_create_link_pypy_ptr = getfn(
                 GCClass.rawrefcount_create_link_pypy,
@@ -1365,8 +1367,8 @@
         self.pop_roots(hop, livevars)
 
     def gct_gc_rawrefcount_init(self, hop):
-        [v_fnptr, v_fnptr2, v_pyobj_list, v_fnptr3, v_fnptr4,
-         v_fnptr5, v_fnptr6] = hop.spaceop.args
+        [v_fnptr, v_fnptr2, v_pyobj_list, v_tuple_list, v_fnptr3, v_fnptr4,
+         v_fnptr5, v_fnptr6, v_fnptr7] = hop.spaceop.args
         assert v_fnptr.concretetype == self.GCClass.RAWREFCOUNT_DEALLOC_TRIGGER
         assert v_fnptr2.concretetype == self.GCClass.RAWREFCOUNT_TRAVERSE
         # TODO add assert for v_pyobj_list, improve asserts (types not same but equal)
@@ -1374,8 +1376,8 @@
         # assert v_fnptr4.concretetype == self.GCClass.RAWREFCOUNT_PYOBJ_AS_GC
         hop.genop("direct_call",
                   [self.rawrefcount_init_ptr, self.c_const_gc, v_fnptr,
-                   v_fnptr2, v_pyobj_list, v_fnptr3, v_fnptr4, v_fnptr5,
-                   v_fnptr6])
+                   v_fnptr2, v_pyobj_list, v_tuple_list, v_fnptr3, v_fnptr4,
+                   v_fnptr5, v_fnptr6, v_fnptr7])
 
     def gct_gc_rawrefcount_create_link_pypy(self, hop):
         [v_gcobj, v_pyobject] = hop.spaceop.args
diff --git a/rpython/rlib/rawrefcount.py b/rpython/rlib/rawrefcount.py
--- a/rpython/rlib/rawrefcount.py
+++ b/rpython/rlib/rawrefcount.py
@@ -308,8 +308,10 @@
     _about_ = init
 
     def compute_result_annotation(self, s_dealloc_callback, s_tp_traverse,
-                                  s_pyobj_list, s_as_gc, s_as_pyobj,
-                                  a_finalizer_type, a_clear_wr):
+                                  s_pyobj_list, v_tuple_list,
+                                  s_as_gc, s_as_pyobj,
+                                  a_finalizer_type, a_clear_wr,
+                                  a_maybe_untrack_tuple):
         from rpython.rtyper.llannotation import SomePtr
         assert isinstance(s_dealloc_callback, SomePtr)   # ll-ptr-to-function
         assert isinstance(s_tp_traverse, SomePtr)
@@ -317,14 +319,18 @@
         assert isinstance(s_as_pyobj, SomePtr)
         assert isinstance(a_finalizer_type, SomePtr)
         assert isinstance(a_clear_wr, SomePtr)
+        assert isinstance(a_maybe_untrack_tuple, SomePtr)
 
     def specialize_call(self, hop):
         hop.exception_cannot_occur()
-        v_dealloc_callback, v_tp_traverse, v_pyobj_list, v_as_gc, \
-        v_as_pyobj, v_finalizer_type, v_clear_wr = hop.inputargs(*hop.args_r)
+        v_dealloc_callback, v_tp_traverse, v_pyobj_list, v_tuple_list, \
+        v_as_gc, v_as_pyobj, v_finalizer_type, \
+        v_clear_wr, v_maybe_untrack_tuple = hop.inputargs(*hop.args_r)
         hop.genop('gc_rawrefcount_init', [v_dealloc_callback, v_tp_traverse,
-                                          v_pyobj_list, v_as_gc, v_as_pyobj,
-                                          v_finalizer_type, v_clear_wr])
+                                          v_pyobj_list, v_tuple_list,
+                                          v_as_gc, v_as_pyobj,
+                                          v_finalizer_type, v_clear_wr,
+                                          v_maybe_untrack_tuple])
 
 
 class Entry(ExtRegistryEntry):


More information about the pypy-commit mailing list