Author: Stefan Beyer <h...@sbeyer.at> Branch: cpyext-gc-cycle Changeset: r95598:c03fe327893a Date: 2018-03-19 10:52 +0100 http://bitbucket.org/pypy/pypy/changeset/c03fe327893a/
Log: Added additional flags for objects Implemented refcount overhead (for non-cyclic refcount) Implemented buffer for potential roots of cycles Fixed assert to allow for recursive cpyext calls Added some cycle detection tests from experimental branch (disabled now) 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 @@ -208,10 +208,11 @@ # running and should not themselves release the GIL). # # **make_generic_cpy_call():** RPython to C, with the GIL held. Before -# the call, must assert that the global variable is 0 and set the -# current thread identifier into the global variable. After the call, -# assert that the global variable still contains the current thread id, -# and reset it to 0. +# the call, must assert that the global variable is 0 or the current +# thread identifier (recursive call) and set the current thread identifier +# into the global variable. After the call, assert that the global variable +# still contains the current thread id, and reset it to the value it held +# before the call. # # **make_wrapper():** C to RPython; by default assume that the GIL is # held, but accepts gil="acquire", "release", "around", @@ -1763,7 +1764,8 @@ # see "Handling of the GIL" above tid = rthread.get_ident() - assert cpyext_glob_tid_ptr[0] == 0 + tid_before = cpyext_glob_tid_ptr[0] + assert tid_before == 0 or tid_before == tid cpyext_glob_tid_ptr[0] = tid preexist_error = PyErr_Occurred(space) @@ -1772,7 +1774,7 @@ result = call_external_function(func, *boxed_args) finally: assert cpyext_glob_tid_ptr[0] == tid - cpyext_glob_tid_ptr[0] = 0 + 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] diff --git a/pypy/module/cpyext/include/boolobject.h b/pypy/module/cpyext/include/boolobject.h --- a/pypy/module/cpyext/include/boolobject.h +++ b/pypy/module/cpyext/include/boolobject.h @@ -13,8 +13,8 @@ #define Py_True ((PyObject *) &_Py_TrueStruct) /* Macros for returning Py_True or Py_False, respectively */ -#define Py_RETURN_TRUE return Py_INCREF(Py_True), Py_True -#define Py_RETURN_FALSE return Py_INCREF(Py_False), Py_False +#define Py_RETURN_TRUE do { Py_INCREF(Py_True); return Py_True; } while(0) +#define Py_RETURN_FALSE do { Py_INCREF(Py_False); return Py_False; } while(0) #ifdef __cplusplus } 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 @@ -2,6 +2,7 @@ #define Py_OBJECT_H #include <stdio.h> +#include <math.h> #ifdef __cplusplus extern "C" { @@ -12,7 +13,13 @@ #define PY_SSIZE_T_MAX ((Py_ssize_t)(((size_t)-1)>>1)) #define PY_SSIZE_T_MIN (-PY_SSIZE_T_MAX-1) -#define Py_RETURN_NONE return Py_INCREF(Py_None), Py_None +#define PY_REFCNT_FROM_PYPY (4L << ((long)(log(PY_SSIZE_T_MAX) / log(2) - 2))) +#define PY_REFCNT_GREEN (4L << ((long)(log(PY_SSIZE_T_MAX) / log(2) - 7))) +#define PY_REFCNT_OVERFLOW (1L << ((long)(log(PY_SSIZE_T_MAX) / log(2) - 7) / 2L - 1L)) +#define PY_REFCNT_MASK ((PY_REFCNT_OVERFLOW << 1L) - 1L) +#define Py_RETURN_NONE return (((((PyObject *)(Py_None))->ob_refcnt & PY_REFCNT_OVERFLOW) == 0) ? \ + ((PyObject *)(Py_None))->ob_refcnt++ : Py_IncRef((PyObject *)(Py_None))), Py_None + /* CPython has this for backwards compatibility with really old extensions, and now @@ -34,14 +41,21 @@ #define Py_XDECREF(ob) (Py_DecRef((PyObject *)(ob))) #else /* Fast version */ -#define Py_INCREF(ob) (((PyObject *)(ob))->ob_refcnt++) -#define Py_DECREF(op) \ - do { \ - if (--((PyObject *)(op))->ob_refcnt != 0) \ - ; \ - else \ - _Py_Dealloc((PyObject *)(op)); \ - } while (0) +#define Py_INCREF(ob) do { \ + if (!(((PyObject *)(ob))->ob_refcnt & PY_REFCNT_OVERFLOW)) \ + ((PyObject *)(ob))->ob_refcnt++; \ + else \ + Py_IncRef((PyObject *)(ob)); \ + } while (0) +#define Py_DECREF(ob) do { \ + if (!(((PyObject *)(ob))->ob_refcnt & PY_REFCNT_GREEN) || \ + (((PyObject *)(ob))->ob_refcnt & PY_REFCNT_OVERFLOW)) \ + Py_DecRef((PyObject *)(ob)); \ + else if (--((PyObject *)(ob))->ob_refcnt & PY_REFCNT_MASK) \ + ; \ + else if ((!((PyObject *)(ob))->ob_refcnt) & PY_REFCNT_FROM_PYPY) \ + _Py_Dealloc((PyObject *)(ob)); \ + } while (0) #define Py_XINCREF(op) do { if ((op) == NULL) ; else Py_INCREF(op); } while (0) #define Py_XDECREF(op) do { if ((op) == NULL) ; else Py_DECREF(op); } while (0) @@ -61,7 +75,8 @@ } \ } while (0) -#define Py_REFCNT(ob) (((PyObject*)(ob))->ob_refcnt) +#define Py_REFCNT(ob) ((((PyObject *)(ob))->ob_refcnt & PY_REFCNT_OVERFLOW == 0) ? \ + (((PyObject*)(ob))->ob_refcnt & PY_REFCNT_MASK) : _Py_RefCnt_Overflow(ob)) #define Py_TYPE(ob) (((PyObject*)(ob))->ob_type) #define Py_SIZE(ob) (((PyVarObject*)(ob))->ob_size) diff --git a/pypy/module/cpyext/object.py b/pypy/module/cpyext/object.py --- a/pypy/module/cpyext/object.py +++ b/pypy/module/cpyext/object.py @@ -11,6 +11,7 @@ from pypy.module.cpyext.pyerrors import PyErr_NoMemory, PyErr_BadInternalCall from pypy.objspace.std.typeobject import W_TypeObject from pypy.interpreter.error import OperationError, oefmt +from rpython.rlib.rawrefcount import REFCNT_MASK import pypy.module.__builtin__.operation as operation @@ -50,7 +51,7 @@ def _dealloc(space, obj): # This frees an object after its refcount dropped to zero, so we # assert that it is really zero here. - assert obj.c_ob_refcnt == 0 + assert obj.c_ob_refcnt & REFCNT_MASK == 0 pto = obj.c_ob_type obj_voidp = rffi.cast(rffi.VOIDP, obj) generic_cpy_call(space, pto.c_tp_free, obj_voidp) 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 @@ -17,8 +17,13 @@ from rpython.rlib.objectmodel import keepalive_until_here from rpython.rtyper.annlowlevel import llhelper, cast_instance_to_base_ptr from rpython.rlib import rawrefcount, jit -from rpython.rlib.debug import ll_assert, fatalerror - +from rpython.rlib.debug import ll_assert, fatalerror, debug_print +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 pypy.module.cpyext.api import slot_function +from pypy.module.cpyext.typeobjectdefs import visitproc #________________________________________________________ # type description @@ -249,8 +254,6 @@ w_obj._cpyext_attach_pyobj(space, py_obj) -w_marker_deallocating = W_Root() - @jit.dont_look_inside def from_ref(space, ref): """ @@ -262,7 +265,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" @@ -315,7 +318,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 w_obj_has_pyobj(w_obj): return bool(rawrefcount.from_obj(PyObject, w_obj)) @@ -341,7 +344,7 @@ pyobj = as_pyobj(space, w_obj, w_userdata, immortal=immortal) if pyobj: # != NULL assert pyobj.c_ob_refcnt >= rawrefcount.REFCNT_FROM_PYPY - pyobj.c_ob_refcnt += 1 + rawrefcount.incref(pyobj) keepalive_until_here(w_obj) return pyobj @@ -375,7 +378,7 @@ pyobj = rffi.cast(PyObject, pyobj) w_obj = from_ref(space, pyobj) if pyobj: - pyobj.c_ob_refcnt -= 1 + rawrefcount.decref(pyobj) assert pyobj.c_ob_refcnt >= rawrefcount.REFCNT_FROM_PYPY keepalive_until_here(w_obj) return w_obj @@ -386,7 +389,7 @@ assert is_pyobj(pyobj) pyobj = rffi.cast(PyObject, pyobj) assert pyobj.c_ob_refcnt >= 1 - pyobj.c_ob_refcnt += 1 + rawrefcount.incref(pyobj) @specialize.ll() def decref(space, pyobj): @@ -394,23 +397,64 @@ assert is_pyobj(pyobj) pyobj = rffi.cast(PyObject, pyobj) if pyobj: - assert pyobj.c_ob_refcnt > 0 - assert (pyobj.c_ob_pypy_link == 0 or - pyobj.c_ob_refcnt > rawrefcount.REFCNT_FROM_PYPY) - pyobj.c_ob_refcnt -= 1 - if pyobj.c_ob_refcnt == 0: - state = space.fromcache(State) - generic_cpy_call(space, state.C._Py_Dealloc, pyobj) + rawrefcount.decref(pyobj) + rc = pyobj.c_ob_refcnt + if rc & REFCNT_MASK == 0: + if rc & REFCNT_FROM_PYPY == 0 and rc & REFCNT_CLR_MASK != REFCNT_CLR_PURPLE: + state = space.fromcache(State) + generic_cpy_call(space, state.C._Py_Dealloc, pyobj) + elif rc & REFCNT_CLR_MASK != REFCNT_CLR_GREEN: + possible_root(space, pyobj) #else: # w_obj = rawrefcount.to_obj(W_Root, ref) # if w_obj is not None: # assert pyobj.c_ob_refcnt >= rawrefcount.REFCNT_FROM_PYPY +@jit.dont_look_inside +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: + #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): + incref(space, obj) + +@cpython_api([PyObject], lltype.Void) +def Py_DecRef(space, obj): + decref(space, obj) + +@cpython_api([PyObject], lltype.SignedLongLong, error=CANNOT_FAIL) +def _Py_RefCnt_Overflow(space, obj): + return refcnt_overflow(space, obj) + +@specialize.ll() +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 @init_function def write_w_marker_deallocating(space): if we_are_translated(): - llptr = cast_instance_to_base_ptr(w_marker_deallocating) + llptr = cast_instance_to_base_ptr(W_MARKER_DEALLOCATING) state = space.fromcache(State) state.C.set_marker(rffi.cast(Py_ssize_t, llptr)) 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 @@ -5,6 +5,7 @@ extern void _PyPy_Free(void *ptr); extern void *_PyPy_Malloc(Py_ssize_t size); +/* void Py_IncRef(PyObject *o) { @@ -16,6 +17,7 @@ { Py_XDECREF(o); } +*/ /* * The actual value of this variable will be the address of diff --git a/pypy/module/cpyext/test/test_bytesobject.py b/pypy/module/cpyext/test/test_bytesobject.py --- a/pypy/module/cpyext/test/test_bytesobject.py +++ b/pypy/module/cpyext/test/test_bytesobject.py @@ -9,10 +9,12 @@ PyString_ConcatAndDel, PyString_Format, PyString_InternFromString, PyString_AsEncodedObject, PyString_AsDecodedObject, _PyString_Eq, _PyString_Join) -from pypy.module.cpyext.api import PyObjectP, PyObject, Py_ssize_tP, generic_cpy_call -from pypy.module.cpyext.pyobject import decref, from_ref, make_ref +from pypy.module.cpyext.api import ( + PyObjectP, PyObject, Py_ssize_tP, generic_cpy_call) +from pypy.module.cpyext.pyobject import ( + Py_DecRef, Py_IncRef, _Py_RefCnt_Overflow, from_ref, make_ref, decref) from pypy.module.cpyext.buffer import PyObject_AsCharBuffer -from pypy.module.cpyext.api import PyTypeObjectPtr +from rpython.rlib import rawrefcount class AppTestBytesObject(AppTestCpythonExtensionBase): @@ -510,9 +512,9 @@ ref = make_ref(space, space.wrap('abc')) ptr = lltype.malloc(PyObjectP.TO, 1, flavor='raw') ptr[0] = ref - prev_refcnt = ref.c_ob_refcnt + prev_refcnt = ref.c_ob_refcnt & rawrefcount.REFCNT_MASK PyString_Concat(space, ptr, space.wrap('def')) - assert ref.c_ob_refcnt == prev_refcnt - 1 + assert ref.c_ob_refcnt & rawrefcount.REFCNT_MASK == prev_refcnt - 1 assert space.str_w(from_ref(space, ptr[0])) == 'abcdef' with pytest.raises(OperationError): PyString_Concat(space, ptr, space.w_None) @@ -548,9 +550,9 @@ w_text = space.wrap("text") ref = make_ref(space, w_text) - prev_refcnt = ref.c_ob_refcnt + prev_refcnt = ref.c_ob_refcnt & rawrefcount.REFCNT_MASK assert PyObject_AsCharBuffer(space, ref, bufp, lenp) == 0 - assert ref.c_ob_refcnt == prev_refcnt + assert ref.c_ob_refcnt & rawrefcount.REFCNT_MASK == prev_refcnt assert lenp[0] == 4 assert rffi.charp2str(bufp[0]) == 'text' lltype.free(bufp, flavor='raw') @@ -609,3 +611,53 @@ w_seq = space.wrap(['a', 'b']) w_joined = _PyString_Join(space, w_sep, w_seq) assert space.unwrap(w_joined) == 'a<sep>b' + + def test_refcnt_overflow(self, space): + ref1 = make_ref(space, space.wrap('foo')) + ref1.c_ob_refcnt = rawrefcount.REFCNT_OVERFLOW - 1 + + Py_IncRef(space, ref1) + assert ref1.c_ob_refcnt & rawrefcount.REFCNT_MASK \ + == rawrefcount.REFCNT_OVERFLOW + assert _Py_RefCnt_Overflow(space, ref1) \ + == rawrefcount.REFCNT_OVERFLOW + + Py_IncRef(space, ref1) + assert ref1.c_ob_refcnt & rawrefcount.REFCNT_MASK \ + == rawrefcount.REFCNT_OVERFLOW + 1 + assert _Py_RefCnt_Overflow(space, ref1) \ + == rawrefcount.REFCNT_OVERFLOW + 1 + + Py_IncRef(space, ref1) + assert ref1.c_ob_refcnt & rawrefcount.REFCNT_MASK \ + == rawrefcount.REFCNT_OVERFLOW + 1 + assert _Py_RefCnt_Overflow(space, ref1) \ + == rawrefcount.REFCNT_OVERFLOW + 2 + + Py_IncRef(space, ref1) + assert ref1.c_ob_refcnt & rawrefcount.REFCNT_MASK \ + == rawrefcount.REFCNT_OVERFLOW + 1 + assert _Py_RefCnt_Overflow(space, ref1) \ + == rawrefcount.REFCNT_OVERFLOW + 3 + + Py_DecRef(space, ref1) + assert ref1.c_ob_refcnt & rawrefcount.REFCNT_MASK \ + == rawrefcount.REFCNT_OVERFLOW + 1 + assert _Py_RefCnt_Overflow(space, ref1) \ + == rawrefcount.REFCNT_OVERFLOW + 2 + + Py_DecRef(space, ref1) + assert ref1.c_ob_refcnt & rawrefcount.REFCNT_MASK \ + == rawrefcount.REFCNT_OVERFLOW + 1 + assert _Py_RefCnt_Overflow(space, ref1) \ + == rawrefcount.REFCNT_OVERFLOW + 1 + + Py_DecRef(space, ref1) + assert ref1.c_ob_refcnt & rawrefcount.REFCNT_MASK \ + == rawrefcount.REFCNT_OVERFLOW + assert _Py_RefCnt_Overflow(space, ref1) \ + == rawrefcount.REFCNT_OVERFLOW + + Py_DecRef(space, ref1) + assert ref1.c_ob_refcnt & rawrefcount.REFCNT_MASK \ + == rawrefcount.REFCNT_OVERFLOW - 1 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 @@ -74,6 +74,8 @@ from rpython.rlib.objectmodel import specialize from rpython.rlib import rgc 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: @@ -188,6 +190,16 @@ ('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)) # ____________________________________________________________ @@ -2990,13 +3002,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 @@ -3005,6 +3017,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 @@ -3026,7 +3039,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): @@ -3046,14 +3059,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_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_buffer_pyobj(self, pyobject): + self.rrc_buffered.append(pyobject) def rawrefcount_from_obj(self, gcobj): obj = llmemory.cast_ptr_to_adr(gcobj) @@ -3064,7 +3080,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): @@ -3085,15 +3101,14 @@ 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) @@ -3110,14 +3125,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 @@ -3148,23 +3163,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) @@ -3175,22 +3191,21 @@ # 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_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_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() @@ -3220,7 +3235,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 @@ -2,9 +2,15 @@ from rpython.rtyper.lltypesystem import lltype, llmemory from rpython.memory.gc.incminimark import IncrementalMiniMarkGC 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 rpython.rlib.rawrefcount import (REFCNT_FROM_PYPY, REFCNT_FROM_PYPY_LIGHT, + REFCNT_MASK) +from pypy.module.cpyext.api import (PyObject, PyTypeObject, PyTypeObjectPtr, + PyObjectFields, cpython_struct) +from pypy.module.cpyext.complexobject import PyComplexObject +from rpython.rtyper.lltypesystem import rffi +from pypy.module.cpyext.typeobjectdefs import visitproc, traverseproc +from rpython.rtyper.annlowlevel import llhelper +from rpython.rtyper.tool import rffi_platform PYOBJ_HDR = IncrementalMiniMarkGC.PYOBJ_HDR PYOBJ_HDR_PTR = IncrementalMiniMarkGC.PYOBJ_HDR_PTR @@ -14,6 +20,17 @@ ('prev', lltype.Ptr(S)), ('next', lltype.Ptr(S)))) +T = lltype.Ptr(lltype.ForwardReference()) +T.TO.become(lltype.Struct('test', + ('base', PyObject.TO), + ('next', T), + ('prev', T), + ('value', lltype.Signed))) + +TRAVERSE_FUNCTYPE = rffi.CCallback([PyObject, visitproc, rffi.VOIDP], + rffi.INT_real) +t1 = lltype.malloc(PyTypeObject, flavor='raw', immortal=True) + class TestRawRefCount(BaseDirectGCTest): GCClass = IncrementalMiniMarkGC @@ -56,21 +73,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 @@ -81,19 +99,53 @@ return p1 return p1, p1ref, r1, r1addr, check_alive + def _rawrefcount_cycle_obj(self): + + def test_tp_traverse(obj, visit, args): + test = rffi.cast(T, obj) + vret = 0 + if llmemory.cast_ptr_to_adr(test.next).ptr is not None: + next = rffi.cast(PyObject, test.next) + vret = visit(next, args) + if vret != 0: + return vret + if llmemory.cast_ptr_to_adr(test.prev).ptr is not None: + next = rffi.cast(PyObject, test.prev) + vret = visit(next, args) + if vret != 0: + return vret + return vret + + func_ptr = llhelper(TRAVERSE_FUNCTYPE, test_tp_traverse) + rffi_func_ptr = rffi.cast(traverseproc, func_ptr) + t1.c_tp_traverse = rffi_func_ptr + + r1 = lltype.malloc(T.TO, flavor='raw', immortal=True) + r1.base.c_ob_pypy_link = 0 + r1.base.c_ob_type = t1 + r1.base.c_ob_refcnt = 1 + return r1 + + def _rawrefcount_buffer_obj(self, obj): + from rpython.rlib.rawrefcount import REFCNT_CLR_MASK, REFCNT_CLR_PURPLE + rc = obj.base.c_ob_refcnt + obj.base.c_ob_refcnt = rc & ~REFCNT_CLR_MASK | REFCNT_CLR_PURPLE + objaddr = llmemory.cast_ptr_to_adr(obj) + self.gc.rawrefcount_buffer_pyobj(objaddr) + def test_rawrefcount_objects_basic(self, old=False): p1, p1ref, r1, r1addr, check_alive = ( self._rawrefcount_pair(42, is_light=True, create_old=old)) 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 +158,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 +181,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 +199,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 +216,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 +249,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 +266,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 +284,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 +304,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 +329,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 +341,146 @@ check_alive(0) self._collect(major=True) check_alive(0) + + # def test_cycle_self_reference_free(self): + # self.gc.rawrefcount_init(lambda: self.trigger.append(1)) + # r1 = self._rawrefcount_cycle_obj() + # r1.next = r1 + # self._rawrefcount_buffer_obj(r1) + # self.gc.rrc_collect_cycles() + # assert r1.base.c_ob_refcnt & REFCNT_MASK == 0 + # + # def test_cycle_self_reference_not_free(self): + # self.gc.rawrefcount_init(lambda: self.trigger.append(1)) + # r1 = self._rawrefcount_cycle_obj() + # r1.base.c_ob_refcnt += 1 + # r1.next = r1 + # self._rawrefcount_buffer_obj(r1) + # self.gc.rrc_collect_cycles() + # assert r1.base.c_ob_refcnt & REFCNT_MASK == 2 + # + # def test_simple_cycle_free(self): + # self.gc.rawrefcount_init(lambda: self.trigger.append(1)) + # r1 = self._rawrefcount_cycle_obj() + # r2 = self._rawrefcount_cycle_obj() + # r1.next = r2 + # r2.next = r1 + # self._rawrefcount_buffer_obj(r1) + # self.gc.rrc_collect_cycles() + # assert r1.base.c_ob_refcnt & REFCNT_MASK == 0 + # assert r2.base.c_ob_refcnt & REFCNT_MASK == 0 + # + # def test_simple_cycle_not_free(self): + # self.gc.rawrefcount_init(lambda: self.trigger.append(1)) + # r1 = self._rawrefcount_cycle_obj() + # r2 = self._rawrefcount_cycle_obj() + # r1.next = r2 + # r2.next = r1 + # r2.base.c_ob_refcnt += 1 + # self._rawrefcount_buffer_obj(r1) + # self.gc.rrc_collect_cycles() + # assert r1.base.c_ob_refcnt & REFCNT_MASK == 1 + # assert r2.base.c_ob_refcnt & REFCNT_MASK == 2 + # + # def test_complex_cycle_free(self): + # self.gc.rawrefcount_init(lambda: self.trigger.append(1)) + # r1 = self._rawrefcount_cycle_obj() + # r2 = self._rawrefcount_cycle_obj() + # r3 = self._rawrefcount_cycle_obj() + # r1.next = r2 + # r1.prev = r2 + # r2.base.c_ob_refcnt += 1 + # r2.next = r3 + # r3.prev = r1 + # self._rawrefcount_buffer_obj(r1) + # self.gc.rrc_collect_cycles() + # assert r1.base.c_ob_refcnt & REFCNT_MASK == 0 + # assert r2.base.c_ob_refcnt & REFCNT_MASK == 0 + # assert r3.base.c_ob_refcnt & REFCNT_MASK == 0 + # + # def test_complex_cycle_not_free(self): + # self.gc.rawrefcount_init(lambda: self.trigger.append(1)) + # r1 = self._rawrefcount_cycle_obj() + # r2 = self._rawrefcount_cycle_obj() + # r3 = self._rawrefcount_cycle_obj() + # r1.next = r2 + # r1.prev = r2 + # r2.base.c_ob_refcnt += 1 + # r2.next = r3 + # r3.prev = r1 + # r3.base.c_ob_refcnt += 1 + # self._rawrefcount_buffer_obj(r1) + # self.gc.rrc_collect_cycles() + # assert r1.base.c_ob_refcnt & REFCNT_MASK == 1 + # assert r2.base.c_ob_refcnt & REFCNT_MASK == 2 + # assert r3.base.c_ob_refcnt & REFCNT_MASK == 2 + # + # def test_cycle_2_buffered_free(self): + # self.gc.rawrefcount_init(lambda: self.trigger.append(1)) + # r1 = self._rawrefcount_cycle_obj() + # r2 = self._rawrefcount_cycle_obj() + # r1.next = r2 + # r2.prev = r1 + # self._rawrefcount_buffer_obj(r1) + # self._rawrefcount_buffer_obj(r2) + # self.gc.rrc_collect_cycles() + # assert r1.base.c_ob_refcnt & REFCNT_MASK == 0 + # assert r2.base.c_ob_refcnt & REFCNT_MASK == 0 + # + # def test_cycle_2_buffered_not_free(self): + # self.gc.rawrefcount_init(lambda: self.trigger.append(1)) + # r1 = self._rawrefcount_cycle_obj() + # r2 = self._rawrefcount_cycle_obj() + # r1.next = r2 + # r2.prev = r1 + # r1.base.c_ob_refcnt += 1 + # self._rawrefcount_buffer_obj(r1) + # self._rawrefcount_buffer_obj(r2) + # self.gc.rrc_collect_cycles() + # assert r1.base.c_ob_refcnt & REFCNT_MASK == 2 + # assert r2.base.c_ob_refcnt & REFCNT_MASK == 1 + # + # def test_multiple_cycles_partial_free(self): + # self.gc.rawrefcount_init(lambda: self.trigger.append(1)) + # r1 = self._rawrefcount_cycle_obj() + # r2 = self._rawrefcount_cycle_obj() + # r3 = self._rawrefcount_cycle_obj() + # r4 = self._rawrefcount_cycle_obj() + # r5 = self._rawrefcount_cycle_obj() + # r1.next = r2 + # r2.next = r3 + # r3.next = r1 + # r2.prev = r5 + # r5.next = r4 + # r4.next = r5 + # r5.base.c_ob_refcnt += 1 + # r4.base.c_ob_refcnt += 1 + # self._rawrefcount_buffer_obj(r1) + # self.gc.rrc_collect_cycles() + # assert r1.base.c_ob_refcnt & REFCNT_MASK == 0 + # assert r2.base.c_ob_refcnt & REFCNT_MASK == 0 + # assert r3.base.c_ob_refcnt & REFCNT_MASK == 0 + # assert r4.base.c_ob_refcnt & REFCNT_MASK == 2 + # assert r5.base.c_ob_refcnt & REFCNT_MASK == 1 + # + # def test_multiple_cycles_all_free(self): + # self.gc.rawrefcount_init(lambda: self.trigger.append(1)) + # r1 = self._rawrefcount_cycle_obj() + # r2 = self._rawrefcount_cycle_obj() + # r3 = self._rawrefcount_cycle_obj() + # r4 = self._rawrefcount_cycle_obj() + # r5 = self._rawrefcount_cycle_obj() + # r1.next = r2 + # r2.next = r3 + # r3.next = r1 + # r2.prev = r5 + # r5.next = r4 + # r4.next = r5 + # r5.base.c_ob_refcnt += 1 + # self._rawrefcount_buffer_obj(r1) + # self.gc.rrc_collect_cycles() + # assert r1.base.c_ob_refcnt & REFCNT_MASK == 0 + # assert r2.base.c_ob_refcnt & REFCNT_MASK == 0 + # assert r3.base.c_ob_refcnt & REFCNT_MASK == 0 + # assert r4.base.c_ob_refcnt & REFCNT_MASK == 0 + # assert r5.base.c_ob_refcnt & REFCNT_MASK == 0 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 @@ -489,6 +489,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) @@ -1339,6 +1343,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 @@ -4,18 +4,49 @@ # This is meant for pypy's cpyext module, but is a generally # useful interface over our GC. XXX "pypy" should be removed here # -import sys, weakref, py +import sys, weakref, py, math from rpython.rtyper.lltypesystem import lltype, llmemory, rffi 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 -REFCNT_FROM_PYPY = sys.maxint // 4 + 1 -REFCNT_FROM_PYPY_LIGHT = REFCNT_FROM_PYPY + (sys.maxint // 2 + 1) +MAX_BIT = int(math.log(sys.maxint, 2)) + +# Flags +REFCNT_FROM_PYPY = 1 << MAX_BIT - 2 # Reference from a pypy object +REFCNT_FROM_PYPY_LIGHT = (1 << MAX_BIT - 1) + REFCNT_FROM_PYPY # Light reference from a pypy object +REFCNT_CYCLE_BUFFERED = 1 << MAX_BIT - 3 # Object in roots buffer (for potential cycles) +REFCNT_IN_WAVEFRONT = 1 << MAX_BIT - 4 # Object in any wavefront + +# Offsets and sizes +REFCNT_CLR_OFFS = MAX_BIT - 7 +REFCNT_CRC_OFFS = REFCNT_CLR_OFFS / 2 +REFCNT_BITS = REFCNT_CRC_OFFS - 1 + +# Concurrent cycle collection colors +REFCNT_CLR_BLACK = 0 << REFCNT_CLR_OFFS # In use or free (default) +REFCNT_CLR_GRAY = 1 << REFCNT_CLR_OFFS # Possible member of cycle +REFCNT_CLR_YELLOW = 2 << REFCNT_CLR_OFFS # Member of garbage cycle +REFCNT_CLR_PURPLE = 3 << REFCNT_CLR_OFFS # Possible root of cycle +REFCNT_CLR_GREEN = 4 << REFCNT_CLR_OFFS # Acyclic +REFCNT_CLR_ORANGE = 5 << REFCNT_CLR_OFFS # In orange wavefront (might change to YELLOW + IN_WAVEFRONT + phase = 3) +REFCNT_CLR_MASK = 7 << REFCNT_CLR_OFFS + +# Cyclic reference count with overflow bit +REFCNT_CRC_OVERFLOW = 1 << REFCNT_CRC_OFFS + REFCNT_BITS +REFCNT_CRC_MASK = (1 << REFCNT_CRC_OFFS + REFCNT_BITS + 1) - 1 +REFCNT_CRC = 1 < REFCNT_CRC_OFFS + +# True reference count with overflow bit +REFCNT_OVERFLOW = 1 << REFCNT_BITS +REFCNT_MASK = (1 << REFCNT_BITS + 1) - 1 + RAWREFCOUNT_DEALLOC_TRIGGER = lltype.Ptr(lltype.FuncType([], lltype.Void)) +W_MARKER_DEALLOCATING = W_Root() def _build_pypy_link(p): @@ -23,6 +54,47 @@ _adr2pypy.append(p) return res +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 + +_refcount_overflow = dict() + +def overflow_new(obj): + _refcount_overflow[objectmodel.current_object_addr_as_int(obj)] = 0 + +def overflow_add(obj): + _refcount_overflow[objectmodel.current_object_addr_as_int(obj)] += 1 + +def overflow_sub(obj): + addr = objectmodel.current_object_addr_as_int(obj) + c = _refcount_overflow[addr] + if c > 0: + _refcount_overflow[addr] = c - 1 + return False + else: + _refcount_overflow.pop(addr) + return True + +def overflow_get(obj): + return _refcount_overflow[objectmodel.current_object_addr_as_int(obj)] + +# TODO: _cyclic_refcount_overflow = dict() @not_rpython def init(dealloc_trigger_callback=None): @@ -72,6 +144,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: @@ -122,7 +198,8 @@ wr_p_list = [] new_p_list = [] for ob in reversed(_p_list): - if ob.c_ob_refcnt not in (REFCNT_FROM_PYPY, REFCNT_FROM_PYPY_LIGHT): + if ob.c_ob_refcnt & REFCNT_MASK > 0 \ + or ob.c_ob_refcnt & REFCNT_FROM_PYPY == 0: new_p_list.append(ob) else: p = detach(ob, wr_p_list) @@ -155,7 +232,8 @@ if ob.c_ob_refcnt >= REFCNT_FROM_PYPY_LIGHT: ob.c_ob_refcnt -= REFCNT_FROM_PYPY_LIGHT ob.c_ob_pypy_link = 0 - if ob.c_ob_refcnt == 0: + if ob.c_ob_refcnt & REFCNT_MASK == 0 \ + and ob.c_ob_refcnt < REFCNT_FROM_PYPY: lltype.free(ob, flavor='raw', track_allocation=track_allocation) else: @@ -163,8 +241,9 @@ assert ob.c_ob_refcnt < int(REFCNT_FROM_PYPY_LIGHT * 0.99) ob.c_ob_refcnt -= REFCNT_FROM_PYPY ob.c_ob_pypy_link = 0 - if ob.c_ob_refcnt == 0: - ob.c_ob_refcnt = 1 + if ob.c_ob_refcnt & REFCNT_MASK == 0 \ + and ob.c_ob_refcnt < REFCNT_FROM_PYPY: + ob.c_ob_refcnt += 1 _d_list.append(ob) return None @@ -252,6 +331,17 @@ func_boehm_eci) hop.genop('direct_call', [c_func]) +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 diff --git a/rpython/rtyper/llinterp.py b/rpython/rtyper/llinterp.py --- a/rpython/rtyper/llinterp.py +++ b/rpython/rtyper/llinterp.py @@ -969,6 +969,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 @@ -494,6 +494,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): _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit