diff --git a/Cython/Compiler/ModuleNode.py b/Cython/Compiler/ModuleNode.py index 7826b478149..eac4c858cc7 100644 --- a/Cython/Compiler/ModuleNode.py +++ b/Cython/Compiler/ModuleNode.py @@ -1780,9 +1780,15 @@ def generate_dealloc_function(self, scope, code): # cimported base type pointer directly interacts badly with # the module cleanup, which may already have cleared it. # In that case, fall back to traversing the type hierarchy. - code.putln("if (likely(%s)) __Pyx_PyType_GetSlot(%s, tp_dealloc, destructor)(o); " - "else __Pyx_call_next_tp_dealloc(o, %s);" % ( - base_cname, base_cname, slot_func_cname)) + # If we're using the module state then always go through the + # type hierarchy, because our access to the module state may + # have been lost (at least for the limited API version of + # using module state). + code.putln("#if !CYTHON_USE_MODULE_STATE") + code.putln("if (likely(%s)) __Pyx_PyType_GetSlot(%s, tp_dealloc, destructor)(o); else" % ( + base_cname, base_cname)) + code.putln("#endif") + code.putln("__Pyx_call_next_tp_dealloc(o, %s);" % slot_func_cname) code.globalstate.use_utility_code( UtilityCode.load_cached("CallNextTpDealloc", "ExtensionTypes.c")) else: @@ -1870,18 +1876,31 @@ def generate_traverse_function(self, scope, code, cclass_entry): code.putln("e = %s(o, v, a); if (e) return e;" % static_call) elif base_type.is_builtin_type: base_cname = base_type.typeptr_cname - code.putln("if (!%s->tp_traverse); else { e = %s->tp_traverse(o,v,a); if (e) return e; }" % ( - base_cname, base_cname)) + code.putln("{") + code.putln( + f"traverseproc traverse = __Pyx_PyType_GetSlot({base_cname}, tp_traverse, traverseproc);") + code.putln("if (!traverse); else { e = traverse(o,v,a); if (e) return e; }") + code.putln("}") else: # This is an externally defined type. Calling through the # cimported base type pointer directly interacts badly with # the module cleanup, which may already have cleared it. # In that case, fall back to traversing the type hierarchy. + # If we're using the module state then always go through the + # type hierarchy, because our access to the module state may + # have been lost (at least for the limited API version of + # using module state). base_cname = base_type.typeptr_cname + code.putln("#if !CYTHON_USE_MODULE_STATE") + code.putln("e = 0;") + code.putln("if (likely(%s)) {" % base_cname) code.putln( - "e = ((likely(%s)) ? ((%s->tp_traverse) ? %s->tp_traverse(o, v, a) : 0) : " - "__Pyx_call_next_tp_traverse(o, v, a, %s)); if (e) return e;" % ( - base_cname, base_cname, base_cname, slot_func)) + f"traverseproc traverse = __Pyx_PyType_GetSlot({base_cname}, tp_traverse, traverseproc);") + code.putln("if (traverse) { e = traverse(o, v, a); }") + code.putln("} else") + code.putln("#endif") + code.putln("{ e = __Pyx_call_next_tp_traverse(o, v, a, %s); }" % slot_func) + code.putln("if (e) return e;") code.globalstate.use_utility_code( UtilityCode.load_cached("CallNextTpTraverse", "ExtensionTypes.c")) @@ -1936,17 +1955,27 @@ def generate_clear_function(self, scope, code, cclass_entry): code.putln("%s(o);" % static_call) elif base_type.is_builtin_type: base_cname = base_type.typeptr_cname - code.putln("if (!%s->tp_clear); else %s->tp_clear(o);" % ( - base_cname, base_cname)) + code.putln("{") + code.putln(f"inquiry clear = __Pyx_PyType_GetSlot({base_cname}, tp_clear, inquiry);") + code.putln("if (clear) clear(o);") + code.putln("}") else: # This is an externally defined type. Calling through the # cimported base type pointer directly interacts badly with # the module cleanup, which may already have cleared it. # In that case, fall back to traversing the type hierarchy. + # If we're using the module state then always go through the + # type hierarchy, because our access to the module state may + # have been lost (at least for the limited API version of + # using module state). base_cname = base_type.typeptr_cname - code.putln( - "if (likely(%s)) { if (%s->tp_clear) %s->tp_clear(o); } else __Pyx_call_next_tp_clear(o, %s);" % ( - base_cname, base_cname, base_cname, slot_func)) + code.putln("#if !CYTHON_USE_MODULE_STATE") + code.putln("if (likely(%s)) {" % base_cname) + code.putln(f"inquiry clear = __Pyx_PyType_GetSlot({base_cname}, tp_clear, inquiry);") + code.putln("if (clear) clear(o);") + code.putln("} else") + code.putln("#endif") + code.putln("{ __Pyx_call_next_tp_clear(o, %s); }" % slot_func) code.globalstate.use_utility_code( UtilityCode.load_cached("CallNextTpClear", "ExtensionTypes.c")) diff --git a/Cython/Compiler/Nodes.py b/Cython/Compiler/Nodes.py index 10afc0971ce..fc4a31f180b 100644 --- a/Cython/Compiler/Nodes.py +++ b/Cython/Compiler/Nodes.py @@ -4926,9 +4926,15 @@ def generate_execution_code(self, code): if self.py_func.is_module_scope: code.putln("else {") else: - code.putln("else if (unlikely((Py_TYPE(%s)->tp_dictoffset != 0) || " - "__Pyx_PyType_HasFeature(Py_TYPE(%s), (Py_TPFLAGS_IS_ABSTRACT | Py_TPFLAGS_HEAPTYPE)))) {" % ( - self_arg, self_arg)) + code.putln("else if (") + code.putln("#if CYTHON_USE_TYPE_SLOTS || CYTHON_COMPILING_IN_PYPY") + code.putln(f"unlikely(Py_TYPE({self_arg})->tp_dictoffset != 0)") + code.putln("#else") + dict_str_const = code.get_py_string_const(EncodedString("__dict__")) + code.putln(f'PyObject_HasAttr({self_arg}, {dict_str_const})') + code.putln("#endif") + code.putln(" || unlikely(__Pyx_PyType_HasFeature(Py_TYPE(%s), (Py_TPFLAGS_IS_ABSTRACT | Py_TPFLAGS_HEAPTYPE)))) {" % ( + self_arg)) code.putln("#if CYTHON_USE_DICT_VERSIONS && CYTHON_USE_PYTYPE_LOOKUP && CYTHON_USE_TYPE_SLOTS") code.globalstate.use_utility_code( @@ -5784,12 +5790,9 @@ def generate_type_ready_code(entry, code, bases_tuple_cname=None, check_heap_typ typeptr_cname, type.vtabptr_cname, )) - # TODO: find a way to make this work with the Limited API! - code.putln("#if !CYTHON_COMPILING_IN_LIMITED_API") code.globalstate.use_utility_code( UtilityCode.load_cached('MergeVTables', 'ImportExport.c')) code.put_error_if_neg(entry.pos, "__Pyx_MergeVtables(%s)" % typeptr_cname) - code.putln("#endif") if not type.scope.is_internal and not type.scope.directives.get('internal'): # scope.is_internal is set for types defined by # Cython (such as closures), the 'internal' @@ -5822,9 +5825,7 @@ def generate_type_ready_code(entry, code, bases_tuple_cname=None, check_heap_typ # do so at runtime. code.globalstate.use_utility_code( UtilityCode.load_cached('SetupReduce', 'ExtensionTypes.c')) - code.putln("#if !CYTHON_COMPILING_IN_LIMITED_API") # FIXME code.put_error_if_neg(entry.pos, "__Pyx_setup_reduce((PyObject *) %s)" % typeptr_cname) - code.putln("#endif") def annotate(self, code): if self.type_init_args: diff --git a/Cython/Compiler/TypeSlots.py b/Cython/Compiler/TypeSlots.py index 3c890e56584..148070629d6 100644 --- a/Cython/Compiler/TypeSlots.py +++ b/Cython/Compiler/TypeSlots.py @@ -272,7 +272,13 @@ def generate_spec(self, scope, code): preprocessor_guard = "#if defined(Py_%s)" % self.slot_name if preprocessor_guard: code.putln(preprocessor_guard) + if self.used_ifdef: + # different from preprocessor guard - this defines if we *want* to define it, + # rather than if the slot exists + code.putln(f"#if {self.used_ifdef}") code.putln("{Py_%s, (void *)%s}," % (self.slot_name, value)) + if self.used_ifdef: + code.putln("#endif") if preprocessor_guard: code.putln("#endif") @@ -1096,7 +1102,7 @@ def __init__(self, old_binops): EmptySlot("tp_weaklist"), EmptySlot("tp_del"), EmptySlot("tp_version_tag"), - SyntheticSlot("tp_finalize", ["__del__"], "0", ifdef="PY_VERSION_HEX >= 0x030400a1", + SyntheticSlot("tp_finalize", ["__del__"], "0", used_ifdef="CYTHON_USE_TP_FINALIZE"), EmptySlot("tp_vectorcall", ifdef="PY_VERSION_HEX >= 0x030800b1 && (!CYTHON_COMPILING_IN_PYPY || PYPY_VERSION_NUM >= 0x07030800)"), EmptySlot("tp_print", ifdef="__PYX_NEED_TP_PRINT_SLOT == 1"), diff --git a/Cython/Utility/ExtensionTypes.c b/Cython/Utility/ExtensionTypes.c index 2b3e0a10e14..f23ff139782 100644 --- a/Cython/Utility/ExtensionTypes.c +++ b/Cython/Utility/ExtensionTypes.c @@ -416,16 +416,13 @@ static void __Pyx_call_next_tp_clear(PyObject* obj, inquiry current_tp_clear) { /////////////// SetupReduce.proto /////////////// -#if !CYTHON_COMPILING_IN_LIMITED_API static int __Pyx_setup_reduce(PyObject* type_obj); -#endif /////////////// SetupReduce /////////////// //@requires: ObjectHandling.c::PyObjectGetAttrStrNoError //@requires: ObjectHandling.c::PyObjectGetAttrStr //@substitute: naming -#if !CYTHON_COMPILING_IN_LIMITED_API static int __Pyx_setup_reduce_is_named(PyObject* meth, PyObject* name) { int ret; PyObject *name_attr; @@ -500,8 +497,8 @@ static int __Pyx_setup_reduce(PyObject* type_obj) { if (reduce == object_reduce || __Pyx_setup_reduce_is_named(reduce, PYIDENT("__reduce_cython__"))) { reduce_cython = __Pyx_PyObject_GetAttrStrNoError(type_obj, PYIDENT("__reduce_cython__")); if (likely(reduce_cython)) { - ret = PyDict_SetItem(((PyTypeObject*)type_obj)->tp_dict, PYIDENT("__reduce__"), reduce_cython); if (unlikely(ret < 0)) goto __PYX_BAD; - ret = PyDict_DelItem(((PyTypeObject*)type_obj)->tp_dict, PYIDENT("__reduce_cython__")); if (unlikely(ret < 0)) goto __PYX_BAD; + ret = __Pyx_SetItemOnTypeDict((PyTypeObject*)type_obj, PYIDENT("__reduce__"), reduce_cython); if (unlikely(ret < 0)) goto __PYX_BAD; + ret = __Pyx_DelItemOnTypeDict((PyTypeObject*)type_obj, PYIDENT("__reduce_cython__")); if (unlikely(ret < 0)) goto __PYX_BAD; } else if (reduce == object_reduce || PyErr_Occurred()) { // Ignore if we're done, i.e. if 'reduce' already has the right name and the original is gone. // Otherwise: error. @@ -513,8 +510,8 @@ static int __Pyx_setup_reduce(PyObject* type_obj) { if (!setstate || __Pyx_setup_reduce_is_named(setstate, PYIDENT("__setstate_cython__"))) { setstate_cython = __Pyx_PyObject_GetAttrStrNoError(type_obj, PYIDENT("__setstate_cython__")); if (likely(setstate_cython)) { - ret = PyDict_SetItem(((PyTypeObject*)type_obj)->tp_dict, PYIDENT("__setstate__"), setstate_cython); if (unlikely(ret < 0)) goto __PYX_BAD; - ret = PyDict_DelItem(((PyTypeObject*)type_obj)->tp_dict, PYIDENT("__setstate_cython__")); if (unlikely(ret < 0)) goto __PYX_BAD; + ret = __Pyx_SetItemOnTypeDict((PyTypeObject*)type_obj, PYIDENT("__setstate__"), setstate_cython); if (unlikely(ret < 0)) goto __PYX_BAD; + ret = __Pyx_DelItemOnTypeDict((PyTypeObject*)type_obj, PYIDENT("__setstate_cython__")); if (unlikely(ret < 0)) goto __PYX_BAD; } else if (!setstate || PyErr_Occurred()) { // Ignore if we're done, i.e. if 'setstate' already has the right name and the original is gone. // Otherwise: error. @@ -549,7 +546,6 @@ static int __Pyx_setup_reduce(PyObject* type_obj) { Py_XDECREF(setstate_cython); return ret; } -#endif /////////////// BinopSlot /////////////// diff --git a/Cython/Utility/ImportExport.c b/Cython/Utility/ImportExport.c index 72e8d673687..670197ae1b0 100644 --- a/Cython/Utility/ImportExport.c +++ b/Cython/Utility/ImportExport.c @@ -753,27 +753,24 @@ static void* __Pyx_GetVtable(PyTypeObject *type) { /////////////// MergeVTables.proto /////////////// //@requires: GetVTable -// TODO: find a way to make this work with the Limited API! -#if !CYTHON_COMPILING_IN_LIMITED_API static int __Pyx_MergeVtables(PyTypeObject *type); /*proto*/ -#endif /////////////// MergeVTables /////////////// -#if !CYTHON_COMPILING_IN_LIMITED_API static int __Pyx_MergeVtables(PyTypeObject *type) { - int i; + int i=0; + Py_ssize_t size; void** base_vtables; - __Pyx_TypeName tp_base_name; - __Pyx_TypeName base_name; + __Pyx_TypeName tp_base_name = NULL; + __Pyx_TypeName base_name = NULL; void* unknown = (void*)-1; - PyObject* bases = type->tp_bases; + PyObject* bases = __Pyx_PyType_GetSlot(type, tp_bases, PyObject*); int base_depth = 0; { - PyTypeObject* base = type->tp_base; + PyTypeObject* base = __Pyx_PyType_GetSlot(type, tp_base, PyTypeObject*); while (base) { base_depth += 1; - base = base->tp_base; + base = __Pyx_PyType_GetSlot(base, tp_base, PyTypeObject*); } } base_vtables = (void**) malloc(sizeof(void*) * (size_t)(base_depth + 1)); @@ -784,11 +781,31 @@ static int __Pyx_MergeVtables(PyTypeObject *type) { // resolution isn't possible and we must reject it just as when the // instance struct is so extended. (It would be good to also do this // check when a multiple-base class is created in pure Python as well.) - for (i = 1; i < PyTuple_GET_SIZE(bases); i++) { - void* base_vtable = __Pyx_GetVtable(((PyTypeObject*)PyTuple_GET_ITEM(bases, i))); +#if CYTHON_COMPILING_IN_LIMITED_API + size = PyTuple_Size(bases); + if (size < 0) goto other_failure; +#else + size = PyTuple_GET_SIZE(bases); +#endif + for (i = 1; i < size; i++) { + PyObject *basei; + void* base_vtable; +#if CYTHON_AVOID_BORROWED_REFS + basei = PySequence_GetItem(bases, i); + if (unlikely(!basei)) goto other_failure; +#elif !CYTHON_ASSUME_SAFE_MACROS + basei = PyTuple_GetItem(bases, i); + if (unlikely(!basei)) goto other_failure; +#else + basei = PyTuple_GET_ITEM(bases, i); +#endif + base_vtable = __Pyx_GetVtable((PyTypeObject*)basei); +#if CYTHON_AVOID_BORROWED_REFS + Py_DECREF(basei); +#endif if (base_vtable != NULL) { int j; - PyTypeObject* base = type->tp_base; + PyTypeObject* base = __Pyx_PyType_GetSlot(type, tp_base, PyTypeObject*); for (j = 0; j < base_depth; j++) { if (base_vtables[j] == unknown) { base_vtables[j] = __Pyx_GetVtable(base); @@ -800,7 +817,7 @@ static int __Pyx_MergeVtables(PyTypeObject *type) { // No more potential matching bases (with vtables). goto bad; } - base = base->tp_base; + base = __Pyx_PyType_GetSlot(base, tp_base, PyTypeObject*); } } } @@ -808,16 +825,37 @@ static int __Pyx_MergeVtables(PyTypeObject *type) { free(base_vtables); return 0; bad: - tp_base_name = __Pyx_PyType_GetName(type->tp_base); - base_name = __Pyx_PyType_GetName((PyTypeObject*)PyTuple_GET_ITEM(bases, i)); + { + PyTypeObject* basei = NULL; + PyTypeObject* tp_base = __Pyx_PyType_GetSlot(type, tp_base, PyTypeObject*); + tp_base_name = __Pyx_PyType_GetName(tp_base); +#if CYTHON_AVOID_BORROWED_REFS + basei = (PyTypeObject*)PySequence_GetItem(bases, i); + if (unlikely(!basei)) goto really_bad; +#elif !CYTHON_ASSUME_SAFE_MACROS + basei = (PyTypeObject*)PyTuple_GetItem(bases, i); + if (unlikely(!basei)) goto really_bad; +#else + basei = (PyTypeObject*)PyTuple_GET_ITEM(bases, i); +#endif + base_name = __Pyx_PyType_GetName(basei); +#if CYTHON_AVOID_BORROWED_REFS + Py_DECREF(basei); +#endif + } PyErr_Format(PyExc_TypeError, "multiple bases have vtable conflict: '" __Pyx_FMT_TYPENAME "' and '" __Pyx_FMT_TYPENAME "'", tp_base_name, base_name); +#if CYTHON_AVOID_BORROWED_REFS || !CYTHON_ASSUME_SAFE_MACROS +really_bad: // bad has failed! +#endif __Pyx_DECREF_TypeName(tp_base_name); __Pyx_DECREF_TypeName(base_name); +#if CYTHON_COMPILING_IN_LIMITED_API || CYTHON_AVOID_BORROWED_REFS || !CYTHON_ASSUME_SAFE_MACROS +other_failure: +#endif free(base_vtables); return -1; } -#endif /////////////// ImportNumPyArray.proto /////////////// diff --git a/Cython/Utility/ModuleSetupCode.c b/Cython/Utility/ModuleSetupCode.c index bf7894dc418..511d5b56d2e 100644 --- a/Cython/Utility/ModuleSetupCode.c +++ b/Cython/Utility/ModuleSetupCode.c @@ -1040,8 +1040,10 @@ static CYTHON_INLINE PyObject * __Pyx_PyDict_GetItemStrWithError(PyObject *dict, // a little hacky, but it does work in the limited API . // (It doesn't work on PyPy but that probably isn't a bug.) #define __Pyx_SetItemOnTypeDict(tp, k, v) PyObject_GenericSetAttr((PyObject*)tp, k, v) + #define __Pyx_DelItemOnTypeDict(tp, k) PyObject_GenericSetAttr((PyObject*)tp, k, NULL) #else - #define __Pyx_SetItemOnTypeDict(tp, k, v) PyDict_SetItem(tp->tp_dict, k, v) + #define __Pyx_SetItemOnTypeDict(tp, k, v) PyDict_SetItem(((PyTypeObject*)(tp))->tp_dict, k, v) + #define __Pyx_DelItemOnTypeDict(tp, k) PyDict_DelItem(((PyTypeObject*)(tp))->tp_dict, k) #endif #if CYTHON_USE_TYPE_SPECS && PY_VERSION_HEX >= 0x03080000