Author: Stefan Beyer <h...@sbeyer.at>
Branch: cpyext-gc-cycle
Changeset: r95599:fb1c6fe11349
Date: 2018-03-20 16:38 +0100
http://bitbucket.org/pypy/pypy/changeset/fb1c6fe11349/

Log:    Call tp_traverse from incminimark Mark cpython objects reachable by
        pypy objects

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
@@ -1293,7 +1293,10 @@
     # if do tuple_attach of the prebuilt empty tuple, we need to call
     # _PyPy_Malloc)
     builder.attach_all(space)
-    
+
+    #import rpython.rlib.rawrefcount
+    #rawrefcount.init_traverse(generic_cpy_call_gc)
+
     setup_init_functions(eci, prefix)
     return modulename.new(ext='')
 
@@ -1716,6 +1719,11 @@
     return make_generic_cpy_call(FT, False)(space, func, *args)
 
 @specialize.ll()
+def generic_cpy_call_gc(func, *args):
+    FT = lltype.typeOf(func).TO
+    return make_generic_cpy_call_gc(FT, False)(func, *args)
+
+@specialize.ll()
 def generic_cpy_call_expect_null(space, func, *args):
     FT = lltype.typeOf(func).TO
     return make_generic_cpy_call(FT, True)(space, func, *args)
@@ -1815,3 +1823,75 @@
         return result
 
     return generic_cpy_call
+
+@specialize.memo()
+def make_generic_cpy_call_gc(FT, expect_null):
+    from pypy.module.cpyext.pyobject import is_pyobj, make_ref, decref
+    from pypy.module.cpyext.pyobject import get_w_obj_and_decref
+    from pypy.module.cpyext.pyerrors import PyErr_Occurred
+    unrolling_arg_types = unrolling_iterable(enumerate(FT.ARGS))
+    RESULT_TYPE = FT.RESULT
+
+    # copied and modified from rffi.py
+    # We need tons of care to ensure that no GC operation and no
+    # exception checking occurs in call_external_function.
+    argnames = ', '.join(['a%d' % i for i in range(len(FT.ARGS))])
+    source = py.code.Source("""
+        def cpy_call_external(funcptr, %(argnames)s):
+            # NB. it is essential that no exception checking occurs here!
+            res = funcptr(%(argnames)s)
+            return res
+    """ % locals())
+    miniglobals = {'__name__':    __name__, # for module name propagation
+                   }
+    exec source.compile() in miniglobals
+    call_external_function = specialize.ll()(miniglobals['cpy_call_external'])
+    call_external_function._dont_inline_ = True
+    call_external_function._gctransformer_hint_close_stack_ = True
+    # don't inline, as a hack to guarantee that no GC pointer is alive
+    # anywhere in call_external_function
+
+    @specialize.ll()
+    def generic_cpy_call(func, *args):
+        boxed_args = ()
+        to_decref = ()
+        assert len(args) == len(FT.ARGS)
+        for i, ARG in unrolling_arg_types:
+            arg = args[i]
+            _pyobj = None
+            if is_PyObject(ARG):
+                assert is_pyobj(arg)
+
+            boxed_args += (arg,)
+            to_decref += (_pyobj,)
+
+        # see "Handling of the GIL" above
+        tid = rthread.get_ident()
+        tid_before = cpyext_glob_tid_ptr[0]
+        assert tid_before == 0 or tid_before == tid
+        cpyext_glob_tid_ptr[0] = tid
+
+        try:
+            # Call the function
+            result = call_external_function(func, *boxed_args)
+        finally:
+            assert cpyext_glob_tid_ptr[0] == tid
+            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]
+                if _pyobj is not None:
+                    pyobj = rffi.cast(PyObject, _pyobj)
+                    rawrefcount.decref(pyobj)
+
+        if is_PyObject(RESULT_TYPE):
+            ret = None
+
+            # Check for exception consistency
+            # XXX best attempt, will miss preexisting error that is
+            # overwritten with a new error of the same type
+
+            return ret
+        return result
+
+    return generic_cpy_call
\ 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
@@ -321,6 +321,8 @@
 #define _PyGC_FINALIZED(o) 1
 #define PyType_IS_GC(tp) 1
 
+/* TODO: implement like in cpython
+   (see 
https://github.com/python/cpython/blob/517da1e58f4c489d4b31579852cde5f7113da08e/Include/objimpl.h#L295)
 */
 #define PyObject_GC_Track(o)      do { } while(0)
 #define PyObject_GC_UnTrack(o)    do { } while(0)
 #define _PyObject_GC_TRACK(o)     do { } while(0)
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
@@ -193,13 +193,27 @@
 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_trace_non_rc_roots(pyobj, self_ptr):
+    from rpython.rlib.rawrefcount import (REFCNT_CLR_BLACK,
+                                          REFCNT_CLR_MASK)
+    from rpython.rtyper.annlowlevel import cast_adr_to_nongc_instance
+
+    self_adr = rffi.cast(llmemory.Address, self_ptr)
+    self = cast_adr_to_nongc_instance(IncrementalMiniMarkGC, self_adr)
+
+    # if the pyobj is not marked, remember it and if there is a linked pypy
+    # object also remember it
+    if pyobj.c_ob_refcnt & REFCNT_CLR_MASK != REFCNT_CLR_BLACK:
+        pyobject = llmemory.cast_ptr_to_adr(pyobj)
+        self.rrc_more_pyobjects_to_scan.append(pyobject)
+        intobj = pyobj.c_ob_pypy_link
+        if intobj != 0:
+            obj = llmemory.cast_int_to_adr(intobj)
+            hdr = self.header(obj)
+            if not (hdr.tid & GCFLAG_VISITED):
+                self.objects_to_trace.append(obj)
+    return rffi.cast(rffi.INT_real, 0)
 
 # ____________________________________________________________
 
@@ -2346,7 +2360,7 @@
                 self.visit_all_objects()
                 #
                 if self.rrc_enabled:
-                    self.rrc_major_collection_trace()
+                   self.rrc_major_collection_trace()
                 #
                 ll_assert(not (self.probably_young_objects_with_finalizers
                                .non_empty()),
@@ -3022,6 +3036,9 @@
             self.rrc_p_dict_nurs  = self.AddressDict()  # nursery keys only
             self.rrc_dealloc_trigger_callback = dealloc_trigger_callback
             self.rrc_dealloc_pending = self.AddressStack()
+            self.rrc_pyobjects_to_scan = self.AddressStack()
+            self.rrc_more_pyobjects_to_scan = self.AddressStack()
+            self.rrc_pyobjects_to_trace = self.AddressStack()
             self.rrc_enabled = True
 
     def check_no_more_rawrefcount_state(self):
@@ -3194,8 +3211,13 @@
             self._pyobj(pyobject).c_ob_refcnt = rc
     _rrc_free._always_inline_ = True
 
+    NO_CYCLE_DETECTION = False
+
     def rrc_major_collection_trace(self):
-        self.rrc_p_list_old.foreach(self._rrc_major_trace, None)
+        if self.NO_CYCLE_DETECTION:
+            self.rrc_p_list_old.foreach(self._rrc_major_trace, None)
+        else:
+            self.rrc_major_collection_trace_cycle()
 
     def _rrc_major_trace(self, pyobject, ignore):
         from rpython.rlib.rawrefcount import REFCNT_MASK
@@ -3210,6 +3232,71 @@
             self.objects_to_trace.append(obj)
             self.visit_all_objects()
 
+    def rrc_major_collection_trace_cycle(self):
+        assert not self.objects_to_trace.non_empty()
+        assert not self.rrc_pyobjects_to_scan.non_empty()
+        assert not self.rrc_more_pyobjects_to_scan.non_empty()
+        assert not self.rrc_pyobjects_to_trace.non_empty()
+
+        # initially, scan all old pyobjects which are linked to objects
+        self.rrc_p_list_old.foreach(self._rrc_major_scan_non_rc_roots, None)
+
+        # as long as we find new pyobjects which should be marked, recursively
+        # mark them
+        while self.rrc_pyobjects_to_trace.non_empty():
+            while self.rrc_pyobjects_to_trace.non_empty():
+                pyobj = self.rrc_pyobjects_to_trace.pop()
+                self._rrc_major_trace_non_rc_roots(pyobj)
+
+            # see if we found new pypy objects to trace
+            if self.objects_to_trace.non_empty():
+                self.visit_all_objects()
+            self.objects_to_trace.delete()
+
+            # look if there are some pyobjects with linked objects which were
+            # not marked previously, but are marked now
+            swap = self.rrc_pyobjects_to_scan
+            self.rrc_pyobjects_to_scan = self.rrc_more_pyobjects_to_scan
+            self.rrc_more_pyobjects_to_scan = swap
+            self.rrc_pyobjects_to_scan.foreach(
+                self._rrc_major_scan_non_rc_roots, None)
+            self.rrc_pyobjects_to_scan.delete()
+
+    def traverse(self, pyobject, func_ptr):
+        from pypy.module.cpyext.api import generic_cpy_call_gc
+        from pypy.module.cpyext.typeobjectdefs import visitproc
+        from rpython.rtyper.annlowlevel import cast_nongc_instance_to_adr
+        self_addr = cast_nongc_instance_to_adr(self)
+        pyobj = self._pyobj(pyobject)
+        if pyobj.c_ob_type and pyobj.c_ob_type.c_tp_traverse:
+            visitproc_ptr = rffi.cast(visitproc, func_ptr)
+            generic_cpy_call_gc(pyobj.c_ob_type.c_tp_traverse, pyobj,
+                            visitproc_ptr, rffi.cast(rffi.VOIDP, self_addr))
+            #cast_nongc_instance_to_adr(self)
+
+    def _rrc_major_trace_non_rc_roots(self, pyobject):
+        from rpython.rtyper.annlowlevel import llhelper
+        func_ptr = llhelper(VISIT_FUNCTYPE, visit_trace_non_rc_roots)
+        self.traverse(pyobject, func_ptr)
+
+    def _rrc_major_scan_non_rc_roots(self, pyobject, ignore):
+        from rpython.rlib.rawrefcount import (REFCNT_CLR_BLACK,
+                                              REFCNT_CLR_MASK)
+        # check in the object header of the linked pypy object, if it is marked
+        # or not
+        pyobj = self._pyobj(pyobject)
+        intobj = pyobj.c_ob_pypy_link
+        obj = llmemory.cast_int_to_adr(intobj)
+        hdr = self.header(obj)
+        if hdr.tid & GCFLAG_VISITED:
+            if pyobj.c_ob_refcnt & REFCNT_CLR_MASK != REFCNT_CLR_BLACK:
+                # process the pyobject now
+                self.rrc_pyobjects_to_trace.append(pyobject)
+        else:
+            # save the pyobject for later, in case its linked object becomes
+            # marked
+            self.rrc_more_pyobjects_to_scan.append(pyobject)
+
     def rrc_major_collection_free(self):
         ll_assert(self.rrc_p_dict_nurs.length() == 0, "p_dict_nurs not empty 
2")
         length_estimate = self.rrc_p_dict.length()
diff --git a/rpython/rlib/rawrefcount.py b/rpython/rlib/rawrefcount.py
--- a/rpython/rlib/rawrefcount.py
+++ b/rpython/rlib/rawrefcount.py
@@ -112,6 +112,15 @@
     _d_marker = None
     _dealloc_trigger_callback = dealloc_trigger_callback
 
+# def init_traverse(traverse_cpy_call):
+#     global _traverse_cpy_call
+#     _traverse_cpy_call = traverse_cpy_call
+#
+# def traverse_cpy_call(pyobj, visitproc_ptr, arg):
+#     global _traverse_cpy_call
+#     _traverse_cpy_call(pyobj.c_ob_type.c_tp_traverse, pyobj,
+#                        visitproc_ptr, arg)
+
 @not_rpython
 def create_link_pypy(p, ob):
     "a link where the PyPy object contains some or all the data"
_______________________________________________
pypy-commit mailing list
pypy-commit@python.org
https://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to