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