Author: Stefan Beyer <h...@sbeyer.at> Branch: cpyext-gc-cycle Changeset: r95607:a189719f68e4 Date: 2018-07-06 23:56 +0200 http://bitbucket.org/pypy/pypy/changeset/a189719f68e4/
Log: Implemented pyobj as gc and vice-versa Cleaned cpyext and gc/rawrefcount tests Cleaned translation options 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 @@ -43,7 +43,6 @@ from rpython.rlib import rstackovf from pypy.objspace.std.typeobject import W_TypeObject, find_best_base from pypy.module.cpyext.cparser import CTypeSpace -from rpython.rlib.debug import ll_assert, debug_print, debug_start, debug_stop DEBUG_WRAPPER = True @@ -753,6 +752,7 @@ PyVarObject = cts.gettype('PyVarObject *') PyGC_Head = cts.gettype('PyGC_Head') PyGC_HeadPtr = cts.gettype('PyGC_Head *') +GCHdr_PyObject = cts.gettype('GCHdr_PyObject *') Py_buffer = cts.gettype('Py_buffer') Py_bufferP = cts.gettype('Py_buffer *') @@ -1175,8 +1175,17 @@ state.C._PyPy_object_dealloc = rffi.llexternal( '_PyPy_object_dealloc', [PyObject], lltype.Void, compilation_info=eci, _nowrapper=True) - state.C._PyPy_InitPyObjList = rffi.llexternal( - '_PyPy_InitPyObjList', [], PyGC_HeadPtr, + state.C._PyPy_subtype_dealloc = rffi.llexternal( + '_PyPy_subtype_dealloc', [PyObject], lltype.Void, + compilation_info=eci, _nowrapper=True) + state.C._PyPy_init_pyobj_list = rffi.llexternal( + '_PyPy_init_pyobj_list', [], PyGC_HeadPtr, + 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) + state.C._PyPy_pyobj_as_gc = rffi.llexternal( + '_PyPy_pyobj_as_gc', [GCHdr_PyObject], PyGC_HeadPtr, compilation_info=eci, _nowrapper=True) @@ -1300,7 +1309,7 @@ ctypes.c_void_p) # initialize the pyobj_list for the gc - pyobj_list = space.fromcache(State).C._PyPy_InitPyObjList() + pyobj_list = space.fromcache(State).C._PyPy_init_pyobj_list() rawrefcount._init_pyobj_list(pyobj_list) # we need to call this *after* the init code above, because it might @@ -1309,9 +1318,6 @@ # _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='') 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 @@ -326,8 +326,8 @@ #define _PyGC_REFS(o) _PyGCHead_REFS(_Py_AS_GC(o)) -#define _PyGC_REFS_UNTRACKED (-2) -#define _PyGC_REFS_REACHABLE (-3) +#define _PyGC_REFS_UNTRACKED (-2) +#define _PyGC_REFS_REACHABLE (-3) #define _PyGC_REFS_TENTATIVELY_UNREACHABLE (-4) #define _PyGC_IS_TRACKED(o) (_PyGC_REFS(o) != _PyGC_REFS_UNTRACKED) @@ -435,7 +435,9 @@ #define _PyObject_GC_Del PyObject_GC_Del PyAPI_FUNC(void) _PyPy_subtype_dealloc(PyObject *); PyAPI_FUNC(void) _PyPy_object_dealloc(PyObject *); -PyAPI_FUNC(PyGC_Head *) _PyPy_InitPyObjList(); +PyAPI_FUNC(PyGC_Head *) _PyPy_init_pyobj_list(); +PyAPI_FUNC(GCHdr_PyObject *) _PyPy_gc_as_pyobj(PyGC_Head *); +PyAPI_FUNC(PyGC_Head *) _PyPy_pyobj_as_gc(GCHdr_PyObject *); #ifdef __cplusplus } diff --git a/pypy/module/cpyext/parse/cpyext_object.h b/pypy/module/cpyext/parse/cpyext_object.h --- a/pypy/module/cpyext/parse/cpyext_object.h +++ b/pypy/module/cpyext/parse/cpyext_object.h @@ -324,7 +324,12 @@ typedef struct _gc_head { - void *gc_next; - void *gc_prev; + struct _gc_head *gc_next; + struct _gc_head *gc_prev; Py_ssize_t gc_refs; -} PyGC_Head; \ No newline at end of file +} PyGC_Head; + +typedef struct _gchdr_pyobject { + Py_ssize_t ob_refcnt; + Py_ssize_t ob_pypy_link; +} GCHdr_PyObject; \ 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 @@ -38,13 +38,25 @@ PyGC_Head *_pypy_rawrefcount_pyobj_list = &_internal_pyobj_list; PyGC_Head * -_PyPy_InitPyObjList() +_PyPy_init_pyobj_list() { _pypy_rawrefcount_pyobj_list->gc_next = _pypy_rawrefcount_pyobj_list; _pypy_rawrefcount_pyobj_list->gc_prev = _pypy_rawrefcount_pyobj_list; return _pypy_rawrefcount_pyobj_list; } +GCHdr_PyObject * +_PyPy_gc_as_pyobj(PyGC_Head *g) +{ + return (GCHdr_PyObject *)FROM_GC(g); +} + +PyGC_Head * +_PyPy_pyobj_as_gc(GCHdr_PyObject *obj) +{ + return AS_GC(obj); +} + void _Py_Dealloc(PyObject *obj) { @@ -118,7 +130,7 @@ if (type->tp_itemsize) size += nitems * type->tp_itemsize; - g = (PyObject*)_PyPy_Malloc(size); + g = (PyGC_Head*)_PyPy_Malloc(size); if (g == NULL) return NULL; g->gc_refs = 0; 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 @@ -2,7 +2,7 @@ from rpython.rtyper.lltypesystem import rffi, lltype, llmemory from pypy.interpreter.error import OperationError, oefmt from pypy.interpreter import executioncontext -from rpython.rtyper.annlowlevel import llhelper, llhelper_args +from rpython.rtyper.annlowlevel import llhelper from rpython.rlib.rdynload import DLLHANDLE from rpython.rlib import rawrefcount import sys @@ -85,7 +85,7 @@ pyobj_dealloc_action = PyObjDeallocAction(space) self.dealloc_trigger = lambda: pyobj_dealloc_action.fire() - def _rawrefcount_tp_traverse(pyobj_ptr, callback, args): + def _tp_traverse(pyobj_ptr, callback, args): from pypy.module.cpyext.api import PyObject from pypy.module.cpyext.typeobjectdefs import visitproc # convert to pointers with correct types (PyObject) @@ -98,8 +98,8 @@ if pyobj.c_ob_type and pyobj.c_ob_type.c_tp_traverse: pyobj.c_ob_type.c_tp_traverse(pyobj, callback_ptr, args) - self.tp_traverse = (lambda o, v, a: - _rawrefcount_tp_traverse(o, v, a)) + + self.tp_traverse = (lambda o, v, a:_tp_traverse(o, v, a)) def build_api(self): """NOT_RPYTHON @@ -130,13 +130,14 @@ if space.config.translation.gc != "boehm": # This must be called in RPython, the untranslated version # does something different. Sigh. - pypyobj_list = self.C._PyPy_InitPyObjList() + pypyobj_list = self.C._PyPy_init_pyobj_list() rawrefcount.init( llhelper(rawrefcount.RAWREFCOUNT_DEALLOC_TRIGGER, - self.dealloc_trigger), + self.dealloc_trigger), llhelper(rawrefcount.RAWREFCOUNT_TRAVERSE, - self.tp_traverse), - pypyobj_list) + self.tp_traverse), + pypyobj_list, + self.C._PyPy_gc_as_pyobj, self.C._PyPy_pyobj_as_gc) self.builder.attach_all(space) setup_new_method_def(space) 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,8 +9,7 @@ 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.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.buffer import PyObject_AsCharBuffer from pypy.module.cpyext.api import PyTypeObjectPtr diff --git a/pypy/module/cpyext/test/test_cpyext.py b/pypy/module/cpyext/test/test_cpyext.py --- a/pypy/module/cpyext/test/test_cpyext.py +++ b/pypy/module/cpyext/test/test_cpyext.py @@ -214,6 +214,8 @@ def debug_collect(space): rawrefcount._collect() +def print_pyobj_list(space): + rawrefcount._print_pyobj_list() class AppTestCpythonExtensionBase(LeakCheckingTest): @@ -225,6 +227,7 @@ if not cls.runappdirect: cls.sys_info = get_cpyext_info(space) cls.w_debug_collect = space.wrap(interp2app(debug_collect)) + cls.w_print_pyobj_list = space.wrap(interp2app(print_pyobj_list)) cls.preload_builtins(space) else: def w_import_module(self, name, init=None, body='', filename=None, @@ -319,7 +322,6 @@ self.record_imported_module(name) return w_result - @unwrap_spec(mod='text', name='text') def load_module(space, mod, name): return self.sys_info.load_module(mod, name) @@ -926,3 +928,179 @@ ''' ), ]) + + def test_gc_pyobj_list(self): + """ + Test if Py_GC_Track and Py_GC_Untrack are adding and removing container + objects from the list of all garbage-collected PyObjects. + """ + if self.runappdirect: + skip('cannot import module with undefined functions') + + # TODO: remove unnecessary stuff, add tests for gc_untrack, add asserts + init = """ + if (Py_IsInitialized()) { + PyObject* m; + if (PyType_Ready(&CycleType) < 0) + return; + m = Py_InitModule("cycle", module_methods); + if (m == NULL) + return; + Py_INCREF(&CycleType); + PyModule_AddObject(m, "Cycle", (PyObject *)&CycleType); + } + """ + body = """ + #include <Python.h> + #include "structmember.h" + typedef struct { + PyObject_HEAD + PyObject *next; + PyObject *val; + } Cycle; + static PyTypeObject CycleType; + static int Cycle_traverse(Cycle *self, visitproc visit, void *arg) + { + int vret; + if (self->next) { + vret = visit(self->next, arg); + if (vret != 0) + return vret; + } + if (self->val) { + vret = visit(self->val, arg); + if (vret != 0) + return vret; + } + return 0; + } + static int Cycle_clear(Cycle *self) + { + PyObject *tmp; + tmp = self->next; + self->next = NULL; + Py_XDECREF(tmp); + tmp = self->val; + self->val = NULL; + Py_XDECREF(tmp); + return 0; + } + static void Cycle_dealloc(Cycle* self) + { + Cycle_clear(self); + Py_TYPE(self)->tp_free((PyObject*)self); + } + static PyObject* Cycle_new(PyTypeObject *type, PyObject *args, + PyObject *kwds) + { + Cycle *self; + self = (Cycle *)type->tp_alloc(type, 0); + if (self != NULL) { + self->next = PyString_FromString(""); + if (self->next == NULL) { + Py_DECREF(self); + return NULL; + } + } + PyObject_GC_Track(self); + return (PyObject *)self; + } + static int Cycle_init(Cycle *self, PyObject *args, PyObject *kwds) + { + PyObject *next=NULL, *tmp; + static char *kwlist[] = {"next", NULL}; + if (! PyArg_ParseTupleAndKeywords(args, kwds, "|O", kwlist, + &next)) + return -1; + if (next) { + tmp = self->next; + Py_INCREF(next); + self->next = next; + Py_XDECREF(tmp); + } + return 0; + } + static PyMemberDef Cycle_members[] = { + {"next", T_OBJECT_EX, offsetof(Cycle, next), 0, "next"}, + {"val", T_OBJECT_EX, offsetof(Cycle, val), 0, "val"}, + {NULL} /* Sentinel */ + }; + static PyMethodDef Cycle_methods[] = { + {NULL} /* Sentinel */ + }; + static PyTypeObject CycleType = { + PyVarObject_HEAD_INIT(NULL, 0) + "Cycle.Cycle", /* tp_name */ + sizeof(Cycle), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)Cycle_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | + Py_TPFLAGS_BASETYPE | + Py_TPFLAGS_HAVE_GC, /* tp_flags */ + "Cycle objects", /* tp_doc */ + (traverseproc)Cycle_traverse, /* tp_traverse */ + (inquiry)Cycle_clear, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + Cycle_methods, /* tp_methods */ + Cycle_members, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + (initproc)Cycle_init, /* tp_init */ + 0, /* tp_alloc */ + Cycle_new, /* tp_new */ + }; + + extern PyGC_Head *_pypy_rawrefcount_pyobj_list; + + static PyObject * Cycle_Create(Cycle *self, PyObject *val) + { + Cycle *c = PyObject_GC_New(Cycle, &CycleType); + if (c == NULL) + return NULL; + c->next = val; + + // TODO: check if _pypy_rawrefcount_pyobj_list contains c + + return (PyObject *)c; + } + static PyMethodDef module_methods[] = { + {"create", (PyCFunction)Cycle_Create, METH_OLDARGS, ""}, + {NULL} /* Sentinel */ + }; + """ + module = self.import_module(name='cycle', init=init, body=body) + + class Example(object): + def __init__(self, val): + self.val = val + + c = module.create(Example(41)) + + self.print_pyobj_list() + c = module.create(Example(42)) + self.print_pyobj_list() + + # TODO: fix rawrefcount, so that the Cycle objects are properly added + # to the ALLOCATED list of leakfinder or alternatively not freed + # by collect diff --git a/pypy/module/cpyext/test/test_cpyext_gc.py b/pypy/module/cpyext/test/test_cpyext_gc.py deleted file mode 100644 --- a/pypy/module/cpyext/test/test_cpyext_gc.py +++ /dev/null @@ -1,801 +0,0 @@ -import sys -import weakref - -import pytest - -from pypy.tool.cpyext.extbuild import ( - SystemCompilationInfo, HERE, get_sys_info_app) -from pypy.interpreter.gateway import unwrap_spec, interp2app -from rpython.rtyper.lltypesystem import lltype, ll2ctypes -from pypy.module.cpyext import api -from pypy.module.cpyext.state import State -from rpython.tool.identity_dict import identity_dict -from rpython.tool import leakfinder -from rpython.rlib import rawrefcount -from rpython.tool.udir import udir - -only_pypy ="config.option.runappdirect and '__pypy__' not in sys.builtin_module_names" - -@api.cpython_api([], api.PyObject) -def PyPy_Crash1(space): - 1/0 - -@api.cpython_api([], lltype.Signed, error=-1) -def PyPy_Crash2(space): - 1/0 - -class SpaceCompiler(SystemCompilationInfo): - """Extension compiler for regular (untranslated PyPy) mode""" - def __init__(self, space, *args, **kwargs): - self.space = space - SystemCompilationInfo.__init__(self, *args, **kwargs) - - def load_module(self, mod, name): - space = self.space - api.load_extension_module(space, mod, name) - return space.getitem( - space.sys.get('modules'), space.wrap(name)) - - -def get_cpyext_info(space): - from pypy.module.imp.importing import get_so_extension - state = space.fromcache(State) - api_library = state.api_lib - if sys.platform == 'win32': - libraries = [api_library] - # '%s' undefined; assuming extern returning int - compile_extra = ["/we4013"] - # prevent linking with PythonXX.lib - w_maj, w_min = space.fixedview(space.sys.get('version_info'), 5)[:2] - link_extra = ["/NODEFAULTLIB:Python%d%d.lib" % - (space.int_w(w_maj), space.int_w(w_min))] - else: - libraries = [] - if sys.platform.startswith('linux'): - compile_extra = [ - "-Werror", "-g", "-O0", "-Wp,-U_FORTIFY_SOURCE", "-fPIC"] - link_extra = ["-g"] - else: - compile_extra = link_extra = None - return SpaceCompiler(space, - builddir_base=udir, - include_extra=api.include_dirs, - compile_extra=compile_extra, - link_extra=link_extra, - extra_libs=libraries, - ext=get_so_extension(space)) - - -def freeze_refcnts(self): - rawrefcount._dont_free_any_more() - return #ZZZ - state = self.space.fromcache(RefcountState) - self.frozen_refcounts = {} - for w_obj, obj in state.py_objects_w2r.iteritems(): - self.frozen_refcounts[w_obj] = obj.c_ob_refcnt - #state.print_refcounts() - self.frozen_ll2callocations = set(ll2ctypes.ALLOCATED.values()) - -class LeakCheckingTest(object): - """Base class for all cpyext tests.""" - spaceconfig = dict(usemodules=['cpyext', 'thread', 'struct', 'array', - 'itertools', 'time', 'binascii', - 'micronumpy', 'mmap' - ]) - - enable_leak_checking = True - - @staticmethod - def cleanup_references(space): - return #ZZZ - state = space.fromcache(RefcountState) - - import gc; gc.collect() - # Clear all lifelines, objects won't resurrect - for w_obj, obj in state.lifeline_dict._dict.items(): - if w_obj not in state.py_objects_w2r: - state.lifeline_dict.set(w_obj, None) - del obj - import gc; gc.collect() - - - for w_obj in state.non_heaptypes_w: - w_obj.c_ob_refcnt -= 1 - state.non_heaptypes_w[:] = [] - state.reset_borrowed_references() - - def check_and_print_leaks(self): - rawrefcount._collect() - # check for sane refcnts - import gc - - if 1: #ZZZ not self.enable_leak_checking: - leakfinder.stop_tracking_allocations(check=False) - return False - - leaking = False - state = self.space.fromcache(RefcountState) - gc.collect() - lost_objects_w = identity_dict() - lost_objects_w.update((key, None) for key in self.frozen_refcounts.keys()) - - for w_obj, obj in state.py_objects_w2r.iteritems(): - base_refcnt = self.frozen_refcounts.get(w_obj) - delta = obj.c_ob_refcnt - if base_refcnt is not None: - delta -= base_refcnt - lost_objects_w.pop(w_obj) - if delta != 0: - leaking = True - print >>sys.stderr, "Leaking %r: %i references" % (w_obj, delta) - try: - weakref.ref(w_obj) - except TypeError: - lifeline = None - else: - lifeline = state.lifeline_dict.get(w_obj) - if lifeline is not None: - refcnt = lifeline.pyo.c_ob_refcnt - if refcnt > 0: - print >>sys.stderr, "\tThe object also held by C code." - else: - referrers_repr = [] - for o in gc.get_referrers(w_obj): - try: - repr_str = repr(o) - except TypeError as e: - repr_str = "%s (type of o is %s)" % (str(e), type(o)) - referrers_repr.append(repr_str) - referrers = ", ".join(referrers_repr) - print >>sys.stderr, "\tThe object is referenced by these objects:", \ - referrers - for w_obj in lost_objects_w: - print >>sys.stderr, "Lost object %r" % (w_obj, ) - leaking = True - # the actual low-level leak checking is done by pypy.tool.leakfinder, - # enabled automatically by pypy.conftest. - return leaking - -class AppTestApi(LeakCheckingTest): - def setup_class(cls): - from rpython.rlib.clibffi import get_libc_name - if cls.runappdirect: - cls.libc = get_libc_name() - else: - cls.w_libc = cls.space.wrap(get_libc_name()) - - def setup_method(self, meth): - if not self.runappdirect: - freeze_refcnts(self) - - def teardown_method(self, meth): - if self.runappdirect: - return - self.space.getexecutioncontext().cleanup_cpyext_state() - self.cleanup_references(self.space) - # XXX: like AppTestCpythonExtensionBase.teardown_method: - # find out how to disable check_and_print_leaks() if the - # test failed - assert not self.check_and_print_leaks(), ( - "Test leaks or loses object(s). You should also check if " - "the test actually passed in the first place; if it failed " - "it is likely to reach this place.") - - -def _unwrap_include_dirs(space, w_include_dirs): - if w_include_dirs is None: - return None - else: - return [space.str_w(s) for s in space.listview(w_include_dirs)] - -def debug_collect(space): - rawrefcount._collect() - -class AppTestCpythonExtensionBase(LeakCheckingTest): - - def setup_class(cls): - space = cls.space - cls.w_here = space.wrap(str(HERE)) - cls.w_udir = space.wrap(str(udir)) - cls.w_runappdirect = space.wrap(cls.runappdirect) - if not cls.runappdirect: - cls.sys_info = get_cpyext_info(space) - space.getbuiltinmodule("cpyext") - # 'import os' to warm up reference counts - w_import = space.builtin.getdictvalue(space, '__import__') - space.call_function(w_import, space.wrap("os")) - #state = cls.space.fromcache(RefcountState) ZZZ - #state.non_heaptypes_w[:] = [] - cls.w_debug_collect = space.wrap(interp2app(debug_collect)) - else: - def w_import_module(self, name, init=None, body='', filename=None, - include_dirs=None, PY_SSIZE_T_CLEAN=False): - from extbuild import get_sys_info_app - sys_info = get_sys_info_app(self.udir) - return sys_info.import_module( - name, init=init, body=body, filename=filename, - include_dirs=include_dirs, - PY_SSIZE_T_CLEAN=PY_SSIZE_T_CLEAN) - cls.w_import_module = w_import_module - - def w_import_extension(self, modname, functions, prologue="", - include_dirs=None, more_init="", PY_SSIZE_T_CLEAN=False): - from extbuild import get_sys_info_app - sys_info = get_sys_info_app(self.udir) - return sys_info.import_extension( - modname, functions, prologue=prologue, - include_dirs=include_dirs, more_init=more_init, - PY_SSIZE_T_CLEAN=PY_SSIZE_T_CLEAN) - cls.w_import_extension = w_import_extension - - def w_compile_module(self, name, - source_files=None, source_strings=None): - from extbuild import get_sys_info_app - sys_info = get_sys_info_app(self.udir) - return sys_info.compile_extension_module(name, - source_files=source_files, source_strings=source_strings) - cls.w_compile_module = w_compile_module - - def w_load_module(self, mod, name): - from extbuild import get_sys_info_app - sys_info = get_sys_info_app(self.udir) - return sys_info.load_module(mod, name) - cls.w_load_module = w_load_module - - def w_debug_collect(self): - import gc - gc.collect() - gc.collect() - gc.collect() - cls.w_debug_collect = w_debug_collect - - - def record_imported_module(self, name): - """ - Record a module imported in a test so that it can be cleaned up in - teardown before the check for leaks is done. - - name gives the name of the module in the space's sys.modules. - """ - self.imported_module_names.append(name) - - def setup_method(self, func): - if self.runappdirect: - return - - @unwrap_spec(name='text') - def compile_module(space, name, - w_source_files=None, - w_source_strings=None): - """ - Build an extension module linked against the cpyext api library. - """ - if not space.is_none(w_source_files): - source_files = space.listview_bytes(w_source_files) - else: - source_files = None - if not space.is_none(w_source_strings): - source_strings = space.listview_bytes(w_source_strings) - else: - source_strings = None - pydname = self.sys_info.compile_extension_module( - name, - source_files=source_files, - source_strings=source_strings) - - # hackish, but tests calling compile_module() always end up - # importing the result - self.record_imported_module(name) - - return space.wrap(pydname) - - @unwrap_spec(name='text', init='text_or_none', body='text', - filename='fsencode_or_none', PY_SSIZE_T_CLEAN=bool) - def import_module(space, name, init=None, body='', - filename=None, w_include_dirs=None, - PY_SSIZE_T_CLEAN=False): - include_dirs = _unwrap_include_dirs(space, w_include_dirs) - w_result = self.sys_info.import_module( - name, init, body, filename, include_dirs, PY_SSIZE_T_CLEAN) - self.record_imported_module(name) - return w_result - - - @unwrap_spec(mod='text', name='text') - def load_module(space, mod, name): - return self.sys_info.load_module(mod, name) - - @unwrap_spec(modname='text', prologue='text', - more_init='text', PY_SSIZE_T_CLEAN=bool) - def import_extension(space, modname, w_functions, prologue="", - w_include_dirs=None, more_init="", PY_SSIZE_T_CLEAN=False): - functions = space.unwrap(w_functions) - include_dirs = _unwrap_include_dirs(space, w_include_dirs) - w_result = self.sys_info.import_extension( - modname, functions, prologue, include_dirs, more_init, - PY_SSIZE_T_CLEAN) - self.record_imported_module(modname) - return w_result - - # A list of modules which the test caused to be imported (in - # self.space). These will be cleaned up automatically in teardown. - self.imported_module_names = [] - - wrap = self.space.wrap - self.w_compile_module = wrap(interp2app(compile_module)) - self.w_load_module = wrap(interp2app(load_module)) - self.w_import_module = wrap(interp2app(import_module)) - self.w_import_extension = wrap(interp2app(import_extension)) - - # create the file lock before we count allocations - self.space.call_method(self.space.sys.get("stdout"), "flush") - - freeze_refcnts(self) - #self.check_and_print_leaks() - - def unimport_module(self, name): - """ - Remove the named module from the space's sys.modules. - """ - w_modules = self.space.sys.get('modules') - w_name = self.space.wrap(name) - self.space.delitem(w_modules, w_name) - - def teardown_method(self, func): - if self.runappdirect: - return - for name in self.imported_module_names: - self.unimport_module(name) - self.space.getexecutioncontext().cleanup_cpyext_state() - self.cleanup_references(self.space) - # XXX: find out how to disable check_and_print_leaks() if the - # test failed... - assert not self.check_and_print_leaks(), ( - "Test leaks or loses object(s). You should also check if " - "the test actually passed in the first place; if it failed " - "it is likely to reach this place.") - -def collect(space): - import gc - rawrefcount._collect() - gc.collect(2) - -def print_pyobj_list(space): - rawrefcount._print_pyobj_list() - -class AppTestCpythonExtensionCycleGC(AppTestCpythonExtensionBase): - - def setup_method(self, func): - if self.runappdirect: - return - - @unwrap_spec(methods='text') - def import_cycle_module(space, methods): - init = """ - if (Py_IsInitialized()) { - PyObject* m; - if (PyType_Ready(&CycleType) < 0) - return; - m = Py_InitModule("cycle", module_methods); - if (m == NULL) - return; - Py_INCREF(&CycleType); - PyModule_AddObject(m, "Cycle", (PyObject *)&CycleType); - } - """ - body = """ - #include <Python.h> - #include "structmember.h" - typedef struct { - PyObject_HEAD - PyObject *next; - PyObject *val; - } Cycle; - static PyTypeObject CycleType; - static int Cycle_traverse(Cycle *self, visitproc visit, void *arg) - { - int vret; - if (self->next) { - vret = visit(self->next, arg); - if (vret != 0) - return vret; - } - if (self->val) { - vret = visit(self->val, arg); - if (vret != 0) - return vret; - } - return 0; - } - static int Cycle_clear(Cycle *self) - { - PyObject *tmp; - tmp = self->next; - self->next = NULL; - Py_XDECREF(tmp); - tmp = self->val; - self->val = NULL; - Py_XDECREF(tmp); - return 0; - } - static void Cycle_dealloc(Cycle* self) - { - Cycle_clear(self); - Py_TYPE(self)->tp_free((PyObject*)self); - } - static PyObject* Cycle_new(PyTypeObject *type, PyObject *args, - PyObject *kwds) - { - Cycle *self; - self = (Cycle *)type->tp_alloc(type, 0); - if (self != NULL) { - self->next = PyString_FromString(""); - if (self->next == NULL) { - Py_DECREF(self); - return NULL; - } - } - PyObject_GC_Track(self); - return (PyObject *)self; - } - static int Cycle_init(Cycle *self, PyObject *args, PyObject *kwds) - { - PyObject *next=NULL, *tmp; - static char *kwlist[] = {"next", NULL}; - if (! PyArg_ParseTupleAndKeywords(args, kwds, "|O", kwlist, - &next)) - return -1; - if (next) { - tmp = self->next; - Py_INCREF(next); - self->next = next; - Py_XDECREF(tmp); - } - return 0; - } - static PyMemberDef Cycle_members[] = { - {"next", T_OBJECT_EX, offsetof(Cycle, next), 0, "next"}, - {"val", T_OBJECT_EX, offsetof(Cycle, val), 0, "val"}, - {NULL} /* Sentinel */ - }; - static PyMethodDef Cycle_methods[] = { - {NULL} /* Sentinel */ - }; - static PyTypeObject CycleType = { - PyVarObject_HEAD_INIT(NULL, 0) - "Cycle.Cycle", /* tp_name */ - sizeof(Cycle), /* tp_basicsize */ - 0, /* tp_itemsize */ - (destructor)Cycle_dealloc, /* tp_dealloc */ - 0, /* tp_print */ - 0, /* tp_getattr */ - 0, /* tp_setattr */ - 0, /* tp_compare */ - 0, /* tp_repr */ - 0, /* tp_as_number */ - 0, /* tp_as_sequence */ - 0, /* tp_as_mapping */ - 0, /* tp_hash */ - 0, /* tp_call */ - 0, /* tp_str */ - 0, /* tp_getattro */ - 0, /* tp_setattro */ - 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT | - Py_TPFLAGS_BASETYPE | - Py_TPFLAGS_HAVE_GC, /* tp_flags */ - "Cycle objects", /* tp_doc */ - (traverseproc)Cycle_traverse, /* tp_traverse */ - (inquiry)Cycle_clear, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - 0, /* tp_iter */ - 0, /* tp_iternext */ - Cycle_methods, /* tp_methods */ - Cycle_members, /* tp_members */ - 0, /* tp_getset */ - 0, /* tp_base */ - 0, /* tp_dict */ - 0, /* tp_descr_get */ - 0, /* tp_descr_set */ - 0, /* tp_dictoffset */ - (initproc)Cycle_init, /* tp_init */ - 0, /* tp_alloc */ - Cycle_new, /* tp_new */ - }; - """ - w_result = self.sys_info.import_module("cycle", init, - body + methods, - None, None, False) - return w_result - - self.imported_module_names = [] - - wrap = self.space.wrap - self.w_import_cycle_module = wrap(interp2app(import_cycle_module)) - self.w_collect = wrap(interp2app(collect)) - self.w_print_pyobj_list = wrap(interp2app(print_pyobj_list)) - - # def test_free_self_reference_cycle_child_pypyobj(self): - # cycle = self.import_cycle_module(""" - # static Cycle *c; - # static PyObject * Cycle_cc(Cycle *self, PyObject *val) - # { - # c = PyObject_GC_New(Cycle, &CycleType); - # if (c == NULL) - # return NULL; - # Py_INCREF(val); - # c->val = val; // set value - # Py_INCREF(c); - # c->next = (PyObject *)c; // create self reference - # Py_INCREF(Py_None); - # return Py_None; - # } - # static PyObject * Cycle_cd(Cycle *self) - # { - # Py_DECREF(c); // throw cycle away - # Py_INCREF(Py_None); - # return Py_None; - # } - # static PyMethodDef module_methods[] = { - # {"createCycle", (PyCFunction)Cycle_cc, METH_OLDARGS, ""}, - # {"discardCycle", (PyCFunction)Cycle_cd, METH_NOARGS, ""}, - # {NULL} /* Sentinel */ - # }; - # """) - # - # class Example(object): - # del_called = -1 - # - # def __init__(self, val): - # self.val = val - # Example.del_called = 0 - # - # def __del__(self): - # Example.del_called = self.val - # - # # don't keep any reference in pypy - # cycle.createCycle(Example(42)) - # self.collect() - # assert Example.del_called == 0 - # cycle.discardCycle() - # self.collect() - # assert Example.del_called == 42 - # - # # keep a temporary reference in pypy - # e = Example(43) - # cycle.createCycle(e) - # cycle.discardCycle() - # self.collect() - # assert Example.del_called == 0 - # e = None - # self.collect() - # assert Example.del_called == 43 - # - # # keep a reference in pypy, free afterwards - # e = Example(44) - # cycle.createCycle(e) - # self.collect() - # assert Example.del_called == 0 - # e = None - # self.collect() - # assert Example.del_called == 0 - # cycle.discardCycle() - # self.collect() - # assert Example.del_called == 44 - # - # def test_free_self_reference_cycle_parent_pypyobj(self): - # # create and return a second object which references the cycle, because - # # otherwise we will end up with a cycle that spans across cpy/pypy, - # # which we don't want to test here - # cycle = self.import_cycle_module(""" - # static PyObject * Cycle_cc(Cycle *self, PyObject *val) - # { - # Cycle *c = PyObject_GC_New(Cycle, &CycleType); - # if (c == NULL) - # return NULL; - # Cycle *c2 = PyObject_GC_New(Cycle, &CycleType); - # if (c2 == NULL) - # return NULL; - # Py_INCREF(val); - # c2->val = val; // set value - # Py_INCREF(c2); - # c2->next = (PyObject *)c2; // create self reference - # c->next = (PyObject *)c2; - # return (PyObject *)c; // return other object - # } - # static PyMethodDef module_methods[] = { - # {"createCycle", (PyCFunction)Cycle_cc, METH_OLDARGS, ""}, - # {NULL} /* Sentinel */ - # }; - # """) - # - # class Example(object): - # del_called = -1 - # - # def __init__(self, val): - # self.val = val - # Example.del_called = 0 - # - # def __del__(self): - # Example.del_called = self.val - # - # c = cycle.createCycle(Example(42)) - # self.collect() - # assert Example.del_called == 0 - # c = None - # self.collect() - # assert Example.del_called == 42 - # - # def test_free_simple_cycle_child_pypyobj(self): - # cycle = self.import_cycle_module(""" - # static Cycle *c; - # static PyObject * Cycle_cc(Cycle *self, PyObject *val) - # { - # c = PyObject_GC_New(Cycle, &CycleType); - # if (c == NULL) - # return NULL; - # Cycle *c2 = PyObject_GC_New(Cycle, &CycleType); - # if (c2 == NULL) - # return NULL; - # Py_INCREF(val); - # c->val = val; // set value - # c->next = (PyObject *)c2; - # Py_INCREF(c); - # c2->next = (PyObject *)c; // simple cycle across two objects - # Py_INCREF(Py_None); - # return Py_None; - # } - # static PyObject * Cycle_cd(Cycle *self) - # { - # Py_DECREF(c); // throw cycle away - # Py_INCREF(Py_None); - # return Py_None; - # } - # static PyMethodDef module_methods[] = { - # {"createCycle", (PyCFunction)Cycle_cc, METH_OLDARGS, ""}, - # {"discardCycle", (PyCFunction)Cycle_cd, METH_NOARGS, ""}, - # {NULL} /* Sentinel */ - # }; - # """) - # - # class Example(object): - # del_called = -1 - # - # def __init__(self, val): - # self.val = val - # Example.del_called = 0 - # - # def __del__(self): - # Example.del_called = self.val - # - # # don't keep any reference in pypy - # cycle.createCycle(Example(42)) - # self.collect() - # cycle.discardCycle() - # assert Example.del_called == 0 - # self.collect() - # assert Example.del_called == 42 - # - # # keep a temporary reference in pypy - # e = Example(43) - # cycle.createCycle(e) - # cycle.discardCycle() - # self.collect() - # assert Example.del_called == 0 - # e = None - # self.collect() - # assert Example.del_called == 43 - # - # # keep a reference in pypy, free afterwards - # e = Example(44) - # cycle.createCycle(e) - # self.collect() - # assert Example.del_called == 0 - # e = None - # self.collect() - # assert Example.del_called == 0 - # cycle.discardCycle() - # self.collect() - # assert Example.del_called == 44 - # - # - # def test_free_complex_cycle_child_pypyobj(self): - # cycle = self.import_cycle_module(""" - # static PyObject * Cycle_cc(Cycle *self, PyObject *val) - # { - # Cycle *c = PyObject_GC_New(Cycle, &CycleType); - # if (c == NULL) - # return NULL; - # Cycle *c2 = PyObject_GC_New(Cycle, &CycleType); - # if (c2 == NULL) - # return NULL; - # Cycle *c3 = PyObject_GC_New(Cycle, &CycleType); - # if (c3 == NULL) - # return NULL; - # Py_INCREF(val); - # c->val = val; // set value - # Py_INCREF(val); - # c3->val = val; // set value - # Py_INCREF(c2); - # c->next = (PyObject *)c2; - # Py_INCREF(c); - # c2->next = (PyObject *)c; // inner cycle - # Py_INCREF(c3); - # c2->val = (PyObject *)c3; - # Py_INCREF(c); - # c3->next = (PyObject *)c; // outer cycle - # Py_DECREF(c); - # Py_DECREF(c2); - # Py_DECREF(c3); // throw all objects away - # Py_INCREF(Py_None); - # return Py_None; - # } - # static PyMethodDef module_methods[] = { - # {"createCycle", (PyCFunction)Cycle_cc, METH_OLDARGS, ""}, - # {NULL} /* Sentinel */ - # }; - # """) - # - # class Example(object): - # del_called = -1 - # - # def __init__(self, val): - # self.val = val - # Example.del_called = 0 - # - # def __del__(self): - # Example.del_called = self.val - # - # # don't keep any reference in pypy - # cycle.createCycle(Example(42)) - # assert Example.del_called == 0 - # self.collect() - # assert Example.del_called == 42 - # - # # keep a temporary reference in pypy - # e = Example(43) - # cycle.createCycle(e) - # e = None - # assert Example.del_called == 0 - # self.collect() - # assert Example.del_called == 43 - # - # # keep a reference in pypy, free afterwards - # e = Example(44) - # cycle.createCycle(e) - # self.collect() - # assert Example.del_called == 0 - # e = None - # self.collect() - # assert Example.del_called == 44 - - def test_objects_in_global_list(self): - cycle = self.import_cycle_module(""" - static PyObject * Cycle_Create(Cycle *self, PyObject *val) - { - Cycle *c = PyObject_GC_New(Cycle, &CycleType); - if (c == NULL) - return NULL; - c->next = val; - return (PyObject *)c; - } - static PyMethodDef module_methods[] = { - {"create", (PyCFunction)Cycle_Create, METH_OLDARGS, ""}, - {NULL} /* Sentinel */ - }; - """) - - class Example(object): - def __init__(self, val): - self.val = val - - c = cycle.create(Example(41)) - - self.print_pyobj_list() - c = cycle.create(Example(42)) - self.print_pyobj_list() - - # TODO: fix rawrefcount, so that the Cycle objects are properly added - # to the ALLOCATED list of leakfinder or alternatively not freed - # by collect diff --git a/rpython/config/translationoption.py b/rpython/config/translationoption.py --- a/rpython/config/translationoption.py +++ b/rpython/config/translationoption.py @@ -106,11 +106,11 @@ ("translation.backend", "c")], }), ChoiceOption("cpyextgc", "Garbage Collection Strategy for cpyext", - ["boehm", "ref", "ref_trialdel", "none"], - default="ref", + ["boehm", "trialdeletion", "none"], + default="trialdeletion", requires={ "boehm": [("translation.gc", "incminimark")], - "ref_trialdel": [("translation.gc", "incminimark")], + "trialdeletion": [("translation.gc", "incminimark")], }, cmdline="--cpyextgc"), 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 @@ -2335,7 +2335,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()), @@ -2994,43 +2994,48 @@ ('c_ob_refcnt', lltype.Signed), ('c_ob_pypy_link', lltype.Signed)) PYOBJ_HDR_PTR = lltype.Ptr(PYOBJ_HDR) - PYOBJ_GC_HDR = lltype.Struct('PyGC_Head', - ('c_gc_next', rffi.VOIDP), - ('c_gc_prev', rffi.VOIDP), - ('c_gc_refs', lltype.Signed)) - PYOBJ_GC_HDR_PTR = lltype.Ptr(PYOBJ_GC_HDR) - RAWREFCOUNT_DEALLOC_TRIGGER = lltype.Ptr(lltype.FuncType([], lltype.Void)) - VISIT_FUNCTYPE = lltype.Ptr(lltype.FuncType([PYOBJ_HDR_PTR, rffi.VOIDP], - rffi.INT_real)) + RAWREFCOUNT_VISIT = lltype.Ptr(lltype.FuncType([PYOBJ_HDR_PTR, rffi.VOIDP], + rffi.INT_real)) RAWREFCOUNT_TRAVERSE = lltype.Ptr(lltype.FuncType([PYOBJ_HDR_PTR, - VISIT_FUNCTYPE, + RAWREFCOUNT_VISIT, rffi.VOIDP], lltype.Void)) + PYOBJ_GC_HDR_PTR = lltype.Ptr(lltype.ForwardReference()) + PYOBJ_GC_HDR = lltype.Struct('PyGC_Head', + ('c_gc_next', PYOBJ_GC_HDR_PTR), + ('c_gc_prev', PYOBJ_GC_HDR_PTR), + ('c_gc_refs', lltype.Signed)) + PYOBJ_GC_HDR_PTR.TO.become(PYOBJ_GC_HDR) + RAWREFCOUNT_GC_AS_PYOBJ = lltype.Ptr(lltype.FuncType([PYOBJ_GC_HDR_PTR], + PYOBJ_HDR_PTR)) + RAWREFCOUNT_PYOBJ_AS_GC = lltype.Ptr(lltype.FuncType([PYOBJ_HDR_PTR], + PYOBJ_GC_HDR_PTR)) def _pyobj(self, pyobjaddr): - return llmemory.cast_adr_to_ptr(pyobjaddr, self.PYOBJ_HDR_PTR) + return llmemory.cast_adr_to_ptr(pyobjaddr, self.PYOBJ_HDR_PTR) def _pygchdr(self, pygchdraddr): - return llmemory.cast_adr_to_ptr(pygchdraddr, self.PYOBJ_GC_HDR_PTR) + return llmemory.cast_adr_to_ptr(pygchdraddr, self.PYOBJ_GC_HDR_PTR) def rawrefcount_init(self, dealloc_trigger_callback, tp_traverse, - pyobj_list): + pyobj_list, gc_as_pyobj, pyobj_as_gc): # see pypy/doc/discussion/rawrefcount.rst if not self.rrc_enabled: self.rrc_p_list_young = self.AddressStack() 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 + self.rrc_dealloc_pending = self.AddressStack() self.rrc_tp_traverse = tp_traverse - 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_pyobj_list = self._pygchdr(pyobj_list) + self.rrc_gc_as_pyobj = gc_as_pyobj + self.rrc_pyobj_as_gc = pyobj_as_gc self.rrc_enabled = True def check_no_more_rawrefcount_state(self): @@ -3344,18 +3349,21 @@ llhelper) # pyobj = self._pyobj(pyobject) - callback_ptr = llhelper(self.VISIT_FUNCTYPE, + callback_ptr = llhelper(self.RAWREFCOUNT_VISIT, IncrementalMiniMarkGC._rrc_visit) self_ptr = rffi.cast(rffi.VOIDP, cast_nongc_instance_to_adr(self)) self.rrc_tp_traverse(pyobj, callback_ptr, self_ptr) def _rrc_gc_list_init(self, pygclist): - pygclist.c_gc_next = rffi.cast(rffi.VOIDP, pygclist) - pygclist.c_gc_prev = rffi.cast(rffi.VOIDP, pygclist) + pygclist.c_gc_next = pygclist + pygclist.c_gc_prev = pygclist def _rrc_gc_print_list(self): debug_print("gc_print_list start!") - curr = rffi.cast(self.PYOBJ_GC_HDR_PTR, self.rrc_pyobj_list.c_gc_next) + curr = self.rrc_pyobj_list.c_gc_next while curr != self.rrc_pyobj_list: - debug_print("gc_print_list: ", curr) - curr = rffi.cast(self.PYOBJ_GC_HDR_PTR, curr.c_gc_next) + currobj = self.rrc_gc_as_pyobj(curr) + curr2 = self.rrc_pyobj_as_gc(currobj) + debug_print("gc_print_list: ", curr, ", obj:", currobj, ", curr: ", + curr2) + curr = curr.c_gc_next 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 @@ -1,15 +1,13 @@ import py -from rpython.rtyper.lltypesystem import lltype, llmemory +from rpython.rtyper.lltypesystem import lltype, llmemory, rffi 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.rtyper.lltypesystem import rffi -from rpython.rtyper.annlowlevel import llhelper -#from pypy.module.cpyext.api import (PyTypeObject) -#from pypy.module.cpyext.typeobjectdefs import visitproc, traverseproc PYOBJ_HDR = IncrementalMiniMarkGC.PYOBJ_HDR PYOBJ_HDR_PTR = IncrementalMiniMarkGC.PYOBJ_HDR_PTR +RAWREFCOUNT_VISIT = IncrementalMiniMarkGC.RAWREFCOUNT_VISIT +PYOBJ_GC_HDR = IncrementalMiniMarkGC.PYOBJ_GC_HDR PYOBJ_GC_HDR_PTR = IncrementalMiniMarkGC.PYOBJ_GC_HDR_PTR S = lltype.GcForwardReference() @@ -18,17 +16,6 @@ ('prev', lltype.Ptr(S)), ('next', lltype.Ptr(S)))) -T = lltype.Ptr(lltype.ForwardReference()) -T.TO.become(lltype.Struct('test', - ('base', PYOBJ_HDR_PTR.TO), - ('next', T), - ('prev', T), - ('value', lltype.Signed))) - -#TRAVERSE_FUNCTYPE = rffi.CCallback([PYOBJ_HDR_PTR, visitproc, rffi.VOIDP], -# rffi.INT_real) -#t1 = lltype.malloc(PyTypeObject, flavor='raw', immortal=True) - class TestRawRefCount(BaseDirectGCTest): GCClass = IncrementalMiniMarkGC @@ -51,14 +38,37 @@ else: rc = REFCNT_FROM_PYPY self.trigger = [] - self.trigger2 = [] + visit = self.gc._rrc_visit + self.pyobj_gc_map = {} + self.gc_pyobj_map = {} + + def rawrefcount_tp_traverse(obj, foo, args): + print "VISITED!!!!!!!!!!!!!!!!!!!!!1" + test = rffi.cast(S, obj) + if llmemory.cast_ptr_to_adr(test.next).ptr is not None: + next = rffi.cast(PYOBJ_HDR_PTR, test.next) + vret = visit(next, args) + if vret != 0: + return + if llmemory.cast_ptr_to_adr(test.prev).ptr is not None: + next = rffi.cast(PYOBJ_HDR_PTR, test.prev) + visit(next, args) + + def rawrefcount_gc_as_pyobj(gc): + return self.gc_pyobj_map[1] # TODO fix + + def rawrefcount_pyobj_as_gc(pyobj): + return self.pyobj_gc_map[1] # TODO fix + self.pyobj_list = lltype.malloc(PYOBJ_GC_HDR_PTR.TO, flavor='raw', immortal=True) - self.pyobj_list.c_gc_next = rffi.cast(rffi.VOIDP, self.pyobj_list); - self.pyobj_list.c_gc_next = rffi.cast(rffi.VOIDP, self.pyobj_list); + self.pyobj_list.c_gc_next = self.pyobj_list + self.pyobj_list.c_gc_next = self.pyobj_list self.gc.rawrefcount_init(lambda: self.trigger.append(1), - lambda: self.trigger2.append(1), - llmemory.cast_ptr_to_adr(self.pyobj_list)) + rawrefcount_tp_traverse, + llmemory.cast_ptr_to_adr(self.pyobj_list), + rawrefcount_gc_as_pyobj, + rawrefcount_pyobj_as_gc) # if create_immortal: p1 = lltype.malloc(S, immortal=True) @@ -78,11 +88,22 @@ self._collect(major=False) p1 = self.stackroots.pop() p1ref = lltype.cast_opaque_ptr(llmemory.GCREF, p1) - r1 = lltype.malloc(PYOBJ_HDR_PTR.TO, flavor='raw', immortal=create_immortal) + r1 = lltype.malloc(PYOBJ_HDR, 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) + + r1gc = lltype.malloc(PYOBJ_GC_HDR, flavor='raw', + immortal=True) + r1gc.c_gc_next = self.pyobj_list + r1gc.c_gc_prev = self.pyobj_list + self.pyobj_list.c_gc_next = r1gc + self.pyobj_list.c_gc_prev = r1gc + + self.pyobj_gc_map[1] = r1gc + self.gc_pyobj_map[1] = r1 + if is_pyobj: assert not is_light self.gc.rawrefcount_create_link_pyobj(p1ref, r1addr) @@ -313,50 +334,24 @@ self._collect(major=True) check_alive(0) - # 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(PYOBJ_HDR_PTR, 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(PYOBJ_HDR_PTR, 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 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_cycle_self_reference_free(self): + p1, p1ref, r1, r1addr, check_alive = ( + self._rawrefcount_pair(42, create_immortal=True)) + p1.next = p1 + check_alive(0) + self._collect(major=True) + py.test.raises(RuntimeError, "r1.c_ob_refcnt") # dead + py.test.raises(RuntimeError, "p1.x") # dead + + def test_cycle_self_reference_not_free(self): + p1, p1ref, r1, r1addr, check_alive = ( + self._rawrefcount_pair(42, create_immortal=True)) + r1.c_ob_refcnt += 1 # the pyobject is kept alive + p1.next = p1 + check_alive(+1) + self._collect(major=True) + check_alive(+1) + # def test_simple_cycle_free(self): # self.gc.rawrefcount_init(lambda: self.trigger.append(1)) # r1 = self._rawrefcount_cycle_obj() 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 @@ -476,7 +476,9 @@ 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(), + SomePtr(GCClass.RAWREFCOUNT_GC_AS_PYOBJ), + SomePtr(GCClass.RAWREFCOUNT_PYOBJ_AS_GC)], annmodel.s_None) self.rawrefcount_create_link_pypy_ptr = getfn( GCClass.rawrefcount_create_link_pypy, @@ -1311,13 +1313,15 @@ self.pop_roots(hop, livevars) def gct_gc_rawrefcount_init(self, hop): - [v_fnptr, v_fnptr2, v_pyobj_list] = hop.spaceop.args + [v_fnptr, v_fnptr2, v_pyobj_list, v_fnptr3, v_fnptr4] = 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 + # TODO add assert for v_pyobj_list, improve asserts (types not same but equal) + # assert v_fnptr3.concretetype == self.GCClass.RAWREFCOUNT_GC_AS_PYOBJ + # 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_fnptr2, v_pyobj_list, v_fnptr3, v_fnptr4]) 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 @@ -19,11 +19,12 @@ ('c_ob_refcnt', lltype.Signed), ('c_ob_pypy_link', lltype.Signed)) PYOBJ_HDR_PTR = lltype.Ptr(PYOBJ_HDR) +PYOBJ_GC_HDR_PTR = lltype.Ptr(lltype.ForwardReference()) PYOBJ_GC_HDR = lltype.Struct('PyGC_Head', - ('c_gc_next', rffi.VOIDP), - ('c_gc_prev', rffi.VOIDP), + ('c_gc_next', PYOBJ_GC_HDR_PTR), + ('c_gc_prev', PYOBJ_GC_HDR_PTR), ('c_gc_refs', lltype.Signed)) -PYOBJ_GC_HDR_PTR = lltype.Ptr(PYOBJ_GC_HDR) +PYOBJ_GC_HDR_PTR.TO.become(PYOBJ_GC_HDR) RAWREFCOUNT_DEALLOC_TRIGGER = lltype.Ptr(lltype.FuncType([], lltype.Void)) VISIT_FUNCTYPE = lltype.Ptr(lltype.FuncType([PYOBJ_HDR_PTR, rffi.VOIDP], rffi.INT_real)) @@ -31,6 +32,10 @@ VISIT_FUNCTYPE, rffi.VOIDP], lltype.Void)) +RAWREFCOUNT_GC_AS_PYOBJ = lltype.Ptr(lltype.FuncType([PYOBJ_GC_HDR_PTR], + PYOBJ_HDR_PTR)) +RAWREFCOUNT_PYOBJ_AS_GC = lltype.Ptr(lltype.FuncType([PYOBJ_HDR_PTR], + PYOBJ_GC_HDR_PTR)) def _build_pypy_link(p): @@ -45,8 +50,7 @@ for tests; it should not be called at all during translation. """ global _p_list, _o_list, _adr2pypy, _pypy2ob, _pypy2ob_rev - global _d_list, _dealloc_trigger_callback, _tp_traverse, _pygclist - global _pyobj_list + global _d_list, _dealloc_trigger_callback, _tp_traverse _p_list = [] _o_list = [] _adr2pypy = [None] @@ -63,15 +67,6 @@ global _pyobj_list _pyobj_list = rffi.cast(PYOBJ_GC_HDR_PTR, pyobj_list) -# 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" @@ -229,12 +224,13 @@ def _print_pyobj_list(): "for tests only" # TODO: change to get_pyobj_list, that returns a list of PyObjects + # or alternatively checks if a certain object is in the list global _pyobj_list print "_print_pyobj_list start!" - curr = rffi.cast(PYOBJ_GC_HDR_PTR, _pyobj_list.c_gc_next) + curr = _pyobj_list.c_gc_next while curr != _pyobj_list: print "_print_pyobj_list: ", curr - curr = rffi.cast(PYOBJ_GC_HDR_PTR, curr.c_gc_next) + curr = curr.c_gc_next # ____________________________________________________________ @@ -263,18 +259,20 @@ class Entry(ExtRegistryEntry): _about_ = init - def compute_result_annotation(self, s_dealloc_callback, tp_traverse, - pyobj_list): + def compute_result_annotation(self, s_dealloc_callback, s_tp_traverse, + s_pyobj_list, s_as_gc, s_as_pyobj): from rpython.rtyper.llannotation import SomePtr assert isinstance(s_dealloc_callback, SomePtr) # ll-ptr-to-function - # add assert? + assert isinstance(s_tp_traverse, SomePtr) + assert isinstance(s_as_gc, SomePtr) + assert isinstance(s_as_pyobj, SomePtr) def specialize_call(self, hop): hop.exception_cannot_occur() - v_dealloc_callback, v_tp_traverse, v_pyobj_list = \ - hop.inputargs(*hop.args_r) + v_dealloc_callback, v_tp_traverse, v_pyobj_list, v_as_gc, \ + v_as_pyobj = hop.inputargs(*hop.args_r) hop.genop('gc_rawrefcount_init', [v_dealloc_callback, v_tp_traverse, - v_pyobj_list]) + v_pyobj_list, v_as_gc, v_as_pyobj]) class Entry(ExtRegistryEntry): diff --git a/rpython/rtyper/rclass.py b/rpython/rtyper/rclass.py --- a/rpython/rtyper/rclass.py +++ b/rpython/rtyper/rclass.py @@ -915,7 +915,7 @@ lst.append(str(g)) g = seen.get(g) lst.append('') - # TODO: remove code (see below) to make this check pass + # TODO: this check fails if this code is uncommented: # pypy/module/cpyext/api.py: # print "start cpyext_call" # print "end cpyext_call" diff --git a/rpython/translator/c/genc.py b/rpython/translator/c/genc.py --- a/rpython/translator/c/genc.py +++ b/rpython/translator/c/genc.py @@ -179,6 +179,8 @@ defines['COUNT_OP_MALLOCS'] = 1 if self.config.translation.cpyextgc == "boehm": defines['CPYEXT_BOEHM'] = 1 + if self.config.translation.cpyextgc == "trialdeletion": + defines['CPYEXT_TRIALDELETION'] = 1 if self.config.translation.sandbox: defines['RPY_SANDBOXED'] = 1 if CBuilder.have___thread is None: _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit