Author: Stefan Beyer <[email protected]>
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)
+
[email protected]()
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
+
[email protected]()
+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
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit