···24242525in buildPythonPackage rec {2626 pname = "cython";2727- version = "0.29.32";2727+ version = "0.29.33";28282929 src = fetchPypi {3030 pname = "Cython";3131 inherit version;3232- hash = "sha256-hzPPR1i3kwTypOOev6xekjQbzke8zrJsElQ5iy+MGvc=";3232+ hash = "sha256-UEB2TEpNLOlko5XaJPDRrlgUSZXauSxrlvRMP01yKGo=";3333 };34343535 nativeBuildInputs = [···4646 # backport Cython 3.0 trashcan support (https://github.com/cython/cython/pull/2842) to 0.X series.4747 # it does not affect Python code unless the code explicitly uses the feature.4848 # trashcan support is needed to avoid stack overflows during object deallocation in sage (https://trac.sagemath.org/ticket/27267)4949- (fetchpatch {5050- name = "trashcan.patch";5151- url = "https://github.com/cython/cython/commit/f781880b6780117660b2026caadf4a6d7905722f.patch";5252- sha256 = "sha256-SnjaJdBZxm3O5gJ5Dxut6+eeVtZv+ygUUNwAwgoiFxg=";5353- })4949+ ./trashcan.patch5450 # The above commit introduces custom trashcan macros, as well as5551 # compiler changes to use them in Cython-emitted code. The latter5652 # change is still useful, but the former has been upstreamed as of···7579 # doCheck = !stdenv.isDarwin;76807781 meta = {8282+ changelog = "https://github.com/cython/cython/blob/${version}/CHANGES.rst";7883 description = "An optimising static compiler for both the Python programming language and the extended Cython programming language";7984 homepage = "https://cython.org";8085 license = lib.licenses.asl20;
···11+From 1b77e35d848340f2c5f4c9b82965c25a0572d48f Mon Sep 17 00:00:00 200122+From: Jeroen Demeyer <J.Demeyer@UGent.be>33+Date: Thu, 14 Feb 2019 10:02:41 +010044+Subject: [PATCH] @cython.trashcan directive to enable the Python trashcan for55+ deallocations66+77+---88+ Cython/Compiler/ModuleNode.py | 10 +++99+ Cython/Compiler/Options.py | 2 +1010+ Cython/Compiler/PyrexTypes.py | 8 +-1111+ Cython/Compiler/Symtab.py | 18 +++-1212+ Cython/Utility/ExtensionTypes.c | 43 ++++++++++1313+ tests/run/trashcan.pyx | 148 ++++++++++++++++++++++++++++++++1414+ 6 files changed, 227 insertions(+), 2 deletions(-)1515+ create mode 100644 tests/run/trashcan.pyx1616+1717+diff --git a/Cython/Compiler/ModuleNode.py b/Cython/Compiler/ModuleNode.py1818+index 56845330d..3a3e8a956 1006441919+--- a/Cython/Compiler/ModuleNode.py2020++++ b/Cython/Compiler/ModuleNode.py2121+@@ -1443,6 +1443,7 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):2222+2323+ is_final_type = scope.parent_type.is_final_type2424+ needs_gc = scope.needs_gc()2525++ needs_trashcan = scope.needs_trashcan()2626+2727+ weakref_slot = scope.lookup_here("__weakref__") if not scope.is_closure_class_scope else None2828+ if weakref_slot not in scope.var_entries:2929+@@ -1481,6 +1482,11 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):3030+ # running this destructor.3131+ code.putln("PyObject_GC_UnTrack(o);")3232+3333++ if needs_trashcan:3434++ code.globalstate.use_utility_code(3535++ UtilityCode.load_cached("PyTrashcan", "ExtensionTypes.c"))3636++ code.putln("__Pyx_TRASHCAN_BEGIN(o, %s)" % slot_func_cname)3737++3838+ # call the user's __dealloc__3939+ self.generate_usr_dealloc_call(scope, code)4040+4141+@@ -1554,6 +1560,10 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):4242+ code.putln("(*Py_TYPE(o)->tp_free)(o);")4343+ if freelist_size:4444+ code.putln("}")4545++4646++ if needs_trashcan:4747++ code.putln("__Pyx_TRASHCAN_END")4848++4949+ code.putln(5050+ "}")5151+5252+diff --git a/Cython/Compiler/Options.py b/Cython/Compiler/Options.py5353+index d03119fca..05a728135 1006445454+--- a/Cython/Compiler/Options.py5555++++ b/Cython/Compiler/Options.py5656+@@ -319,6 +319,7 @@ directive_types = {5757+ 'freelist': int,5858+ 'c_string_type': one_of('bytes', 'bytearray', 'str', 'unicode'),5959+ 'c_string_encoding': normalise_encoding_name,6060++ 'trashcan': bool,6161+ 'cpow': bool6262+ }6363+6464+@@ -362,6 +363,7 @@ directive_scopes = { # defaults to available everywhere6565+ 'np_pythran': ('module',),6666+ 'fast_gil': ('module',),6767+ 'iterable_coroutine': ('module', 'function'),6868++ 'trashcan' : ('cclass',),6969+ }7070+7171+7272+diff --git a/Cython/Compiler/PyrexTypes.py b/Cython/Compiler/PyrexTypes.py7373+index c309bd04b..9231130b5 1006447474+--- a/Cython/Compiler/PyrexTypes.py7575++++ b/Cython/Compiler/PyrexTypes.py7676+@@ -1129,6 +1129,7 @@ class PyObjectType(PyrexType):7777+ is_extern = False7878+ is_subclassed = False7979+ is_gc_simple = False8080++ builtin_trashcan = False # builtin type using trashcan8181+8282+ def __str__(self):8383+ return "Python object"8484+@@ -1183,10 +1184,14 @@ class PyObjectType(PyrexType):8585+8686+8787+ builtin_types_that_cannot_create_refcycles = set([8888+- 'bool', 'int', 'long', 'float', 'complex',8989++ 'object', 'bool', 'int', 'long', 'float', 'complex',9090+ 'bytearray', 'bytes', 'unicode', 'str', 'basestring'9191+ ])9292+9393++builtin_types_with_trashcan = set([9494++ 'dict', 'list', 'set', 'frozenset', 'tuple', 'type',9595++])9696++9797+9898+ class BuiltinObjectType(PyObjectType):9999+ # objstruct_cname string Name of PyObject struct100100+@@ -1211,6 +1216,7 @@ class BuiltinObjectType(PyObjectType):101101+ self.typeptr_cname = "(&%s)" % cname102102+ self.objstruct_cname = objstruct_cname103103+ self.is_gc_simple = name in builtin_types_that_cannot_create_refcycles104104++ self.builtin_trashcan = name in builtin_types_with_trashcan105105+ if name == 'type':106106+ # Special case the type type, as many C API calls (and other107107+ # libraries) actually expect a PyTypeObject* for type arguments.108108+diff --git a/Cython/Compiler/Symtab.py b/Cython/Compiler/Symtab.py109109+index 7361a55ae..f0c311ba6 100644110110+--- a/Cython/Compiler/Symtab.py111111++++ b/Cython/Compiler/Symtab.py112112+@@ -2043,7 +2043,7 @@ class PyClassScope(ClassScope):113113+ class CClassScope(ClassScope):114114+ # Namespace of an extension type.115115+ #116116+- # parent_type CClassType117117++ # parent_type PyExtensionType118118+ # #typeobj_cname string or None119119+ # #objstruct_cname string120120+ # method_table_cname string121121+@@ -2087,6 +2087,22 @@ class CClassScope(ClassScope):122122+ return not self.parent_type.is_gc_simple123123+ return False124124+125125++ def needs_trashcan(self):126126++ # If the trashcan directive is explicitly set to False,127127++ # unconditionally disable the trashcan.128128++ directive = self.directives.get('trashcan')129129++ if directive is False:130130++ return False131131++ # If the directive is set to True and the class has Python-valued132132++ # C attributes, then it should use the trashcan in tp_dealloc.133133++ if directive and self.has_cyclic_pyobject_attrs:134134++ return True135135++ # Use the trashcan if the base class uses it136136++ base_type = self.parent_type.base_type137137++ if base_type and base_type.scope is not None:138138++ return base_type.scope.needs_trashcan()139139++ return self.parent_type.builtin_trashcan140140++141141+ def needs_tp_clear(self):142142+ """143143+ Do we need to generate an implementation for the tp_clear slot? Can144144+diff --git a/Cython/Utility/ExtensionTypes.c b/Cython/Utility/ExtensionTypes.c145145+index dc187ab49..f359165df 100644146146+--- a/Cython/Utility/ExtensionTypes.c147147++++ b/Cython/Utility/ExtensionTypes.c148148+@@ -119,6 +119,49 @@ static int __Pyx_PyType_Ready(PyTypeObject *t) {149149+ return r;150150+ }151151+152152++/////////////// PyTrashcan.proto ///////////////153153++154154++// These macros are taken from https://github.com/python/cpython/pull/11841155155++// Unlike the Py_TRASHCAN_SAFE_BEGIN/Py_TRASHCAN_SAFE_END macros, they156156++// allow dealing correctly with subclasses.157157++158158++// This requires CPython version >= 2.7.4159159++// (or >= 3.2.4 but we don't support such old Python 3 versions anyway)160160++#if CYTHON_COMPILING_IN_CPYTHON && PY_VERSION_HEX >= 0x02070400161161++#define __Pyx_TRASHCAN_BEGIN_CONDITION(op, cond) \162162++ do { \163163++ PyThreadState *_tstate = NULL; \164164++ // If "cond" is false, then _tstate remains NULL and the deallocator165165++ // is run normally without involving the trashcan166166++ if (cond) { \167167++ _tstate = PyThreadState_GET(); \168168++ if (_tstate->trash_delete_nesting >= PyTrash_UNWIND_LEVEL) { \169169++ // Store the object (to be deallocated later) and jump past170170++ // Py_TRASHCAN_END, skipping the body of the deallocator171171++ _PyTrash_thread_deposit_object((PyObject*)(op)); \172172++ break; \173173++ } \174174++ ++_tstate->trash_delete_nesting; \175175++ }176176++ // The body of the deallocator is here.177177++#define __Pyx_TRASHCAN_END \178178++ if (_tstate) { \179179++ --_tstate->trash_delete_nesting; \180180++ if (_tstate->trash_delete_later && _tstate->trash_delete_nesting <= 0) \181181++ _PyTrash_thread_destroy_chain(); \182182++ } \183183++ } while (0);184184++185185++#define __Pyx_TRASHCAN_BEGIN(op, dealloc) __Pyx_TRASHCAN_BEGIN_CONDITION(op, \186186++ Py_TYPE(op)->tp_dealloc == (destructor)(dealloc))187187++188188++#else189189++// The trashcan is a no-op on other Python implementations190190++// or old CPython versions191191++#define __Pyx_TRASHCAN_BEGIN(op, dealloc)192192++#define __Pyx_TRASHCAN_END193193++#endif194194++195195+ /////////////// CallNextTpDealloc.proto ///////////////196196+197197+ static void __Pyx_call_next_tp_dealloc(PyObject* obj, destructor current_tp_dealloc);198198+diff --git a/tests/run/trashcan.pyx b/tests/run/trashcan.pyx199199+new file mode 100644200200+index 000000000..93a501ff8201201+--- /dev/null202202++++ b/tests/run/trashcan.pyx203203+@@ -0,0 +1,148 @@204204++# mode: run205205++206206++cimport cython207207++208208++209209++# Count number of times an object was deallocated twice. This should remain 0.210210++cdef int double_deallocations = 0211211++def assert_no_double_deallocations():212212++ global double_deallocations213213++ err = double_deallocations214214++ double_deallocations = 0215215++ assert not err216216++217217++218218++# Compute x = f(f(f(...(None)...))) nested n times and throw away the result.219219++# The real test happens when exiting this function: then a big recursive220220++# deallocation of x happens. We are testing two things in the tests below:221221++# that Python does not crash and that no double deallocation happens.222222++# See also https://github.com/python/cpython/pull/11841223223++def recursion_test(f, int n=2**20):224224++ x = None225225++ cdef int i226226++ for i in range(n):227227++ x = f(x)228228++229229++230230++@cython.trashcan(True)231231++cdef class Recurse:232232++ """233233++ >>> recursion_test(Recurse)234234++ >>> assert_no_double_deallocations()235235++ """236236++ cdef public attr237237++ cdef int deallocated238238++239239++ def __init__(self, x):240240++ self.attr = x241241++242242++ def __dealloc__(self):243243++ # Check that we're not being deallocated twice244244++ global double_deallocations245245++ double_deallocations += self.deallocated246246++ self.deallocated = 1247247++248248++249249++cdef class RecurseSub(Recurse):250250++ """251251++ >>> recursion_test(RecurseSub)252252++ >>> assert_no_double_deallocations()253253++ """254254++ cdef int subdeallocated255255++256256++ def __dealloc__(self):257257++ # Check that we're not being deallocated twice258258++ global double_deallocations259259++ double_deallocations += self.subdeallocated260260++ self.subdeallocated = 1261261++262262++263263++@cython.freelist(4)264264++@cython.trashcan(True)265265++cdef class RecurseFreelist:266266++ """267267++ >>> recursion_test(RecurseFreelist)268268++ >>> recursion_test(RecurseFreelist, 1000)269269++ >>> assert_no_double_deallocations()270270++ """271271++ cdef public attr272272++ cdef int deallocated273273++274274++ def __init__(self, x):275275++ self.attr = x276276++277277++ def __dealloc__(self):278278++ # Check that we're not being deallocated twice279279++ global double_deallocations280280++ double_deallocations += self.deallocated281281++ self.deallocated = 1282282++283283++284284++# Subclass of list => uses trashcan by default285285++# As long as https://github.com/python/cpython/pull/11841 is not fixed,286286++# this does lead to double deallocations, so we skip that check.287287++cdef class RecurseList(list):288288++ """289289++ >>> RecurseList(42)290290++ [42]291291++ >>> recursion_test(RecurseList)292292++ """293293++ def __init__(self, x):294294++ super().__init__((x,))295295++296296++297297++# Some tests where the trashcan is NOT used. When the trashcan is not used298298++# in a big recursive deallocation, the __dealloc__s of the base classs are299299++# only run after the __dealloc__s of the subclasses.300300++# We use this to detect trashcan usage.301301++cdef int base_deallocated = 0302302++cdef int trashcan_used = 0303303++def assert_no_trashcan_used():304304++ global base_deallocated, trashcan_used305305++ err = trashcan_used306306++ trashcan_used = base_deallocated = 0307307++ assert not err308308++309309++310310++cdef class Base:311311++ def __dealloc__(self):312312++ global base_deallocated313313++ base_deallocated = 1314314++315315++316316++# Trashcan disabled by default317317++cdef class Sub1(Base):318318++ """319319++ >>> recursion_test(Sub1, 100)320320++ >>> assert_no_trashcan_used()321321++ """322322++ cdef public attr323323++324324++ def __init__(self, x):325325++ self.attr = x326326++327327++ def __dealloc__(self):328328++ global base_deallocated, trashcan_used329329++ trashcan_used += base_deallocated330330++331331++332332++@cython.trashcan(True)333333++cdef class Middle(Base):334334++ cdef public foo335335++336336++337337++# Trashcan disabled explicitly338338++@cython.trashcan(False)339339++cdef class Sub2(Middle):340340++ """341341++ >>> recursion_test(Sub2, 1000)342342++ >>> assert_no_trashcan_used()343343++ """344344++ cdef public attr345345++346346++ def __init__(self, x):347347++ self.attr = x348348++349349++ def __dealloc__(self):350350++ global base_deallocated, trashcan_used351351++ trashcan_used += base_deallocated352352+-- 353353+2.39.0354354+