From 4e957e8bd40d6e1985ada98e4ecd054236539685 Mon Sep 17 00:00:00 2001 From: David Hewitt <1939362+davidhewitt@users.noreply.github.com> Date: Fri, 21 Jul 2023 16:49:00 +0100 Subject: [PATCH 1/2] update object.h definitions for Python 3.12 --- newsfragments/3335.changed.md | 1 + newsfragments/3335.fixed.md | 1 + pyo3-ffi/src/cpython/object.rs | 12 +- pyo3-ffi/src/lib.rs | 10 +- pyo3-ffi/src/object.rs | 400 +++++++++++++++++------- pyo3-ffi/src/{buffer.rs => pybuffer.rs} | 3 + pyo3-ffi/src/pyport.rs | 16 + src/ffi/mod.rs | 2 +- src/ffi/tests.rs | 58 +++- 9 files changed, 374 insertions(+), 129 deletions(-) create mode 100644 newsfragments/3335.changed.md create mode 100644 newsfragments/3335.fixed.md rename pyo3-ffi/src/{buffer.rs => pybuffer.rs} (95%) diff --git a/newsfragments/3335.changed.md b/newsfragments/3335.changed.md new file mode 100644 index 00000000000..c35f1ddb1f2 --- /dev/null +++ b/newsfragments/3335.changed.md @@ -0,0 +1 @@ +Update FFI definitions of `object.h` for Python 3.12 and up. diff --git a/newsfragments/3335.fixed.md b/newsfragments/3335.fixed.md new file mode 100644 index 00000000000..bc7bb8b61d8 --- /dev/null +++ b/newsfragments/3335.fixed.md @@ -0,0 +1 @@ +Fix reference counting of immortal objects on Python 3.12 betas. diff --git a/pyo3-ffi/src/cpython/object.rs b/pyo3-ffi/src/cpython/object.rs index c2519adc609..abf8f1dc61c 100644 --- a/pyo3-ffi/src/cpython/object.rs +++ b/pyo3-ffi/src/cpython/object.rs @@ -1,4 +1,6 @@ use crate::object; +#[cfg(Py_3_8)] +use crate::vectorcallfunc; use crate::{PyObject, Py_ssize_t}; use std::mem; use std::os::raw::{c_char, c_int, c_uint, c_ulong, c_void}; @@ -112,14 +114,6 @@ mod bufferinfo { #[cfg(not(Py_3_11))] pub use self::bufferinfo::*; -#[cfg(Py_3_8)] -pub type vectorcallfunc = unsafe extern "C" fn( - callable: *mut PyObject, - args: *const *mut PyObject, - nargsf: libc::size_t, - kwnames: *mut PyObject, -) -> *mut PyObject; - #[repr(C)] #[derive(Copy, Clone)] pub struct PyNumberMethods { @@ -275,7 +269,7 @@ pub struct PyTypeObject { pub tp_version_tag: c_uint, pub tp_finalize: Option, #[cfg(Py_3_8)] - pub tp_vectorcall: Option, + pub tp_vectorcall: Option, #[cfg(Py_3_12)] pub tp_watched: c_char, #[cfg(any(all(PyPy, Py_3_8, not(Py_3_10)), all(not(PyPy), Py_3_8, not(Py_3_9))))] diff --git a/pyo3-ffi/src/lib.rs b/pyo3-ffi/src/lib.rs index a3532390435..caf50ce26ea 100644 --- a/pyo3-ffi/src/lib.rs +++ b/pyo3-ffi/src/lib.rs @@ -259,8 +259,6 @@ macro_rules! opaque_struct { pub use self::abstract_::*; pub use self::bltinmodule::*; pub use self::boolobject::*; -#[cfg(Py_3_11)] -pub use self::buffer::*; pub use self::bytearrayobject::*; pub use self::bytesobject::*; pub use self::ceval::*; @@ -293,6 +291,8 @@ pub use self::objimpl::*; pub use self::osmodule::*; #[cfg(not(any(PyPy, Py_LIMITED_API, Py_3_10)))] pub use self::pyarena::*; +#[cfg(Py_3_11)] +pub use self::pybuffer::*; pub use self::pycapsule::*; pub use self::pyerrors::*; pub use self::pyframe::*; @@ -320,8 +320,6 @@ mod abstract_; // skipped ast.h mod bltinmodule; mod boolobject; -#[cfg(Py_3_11)] -mod buffer; mod bytearrayobject; mod bytesobject; // skipped cellobject.h @@ -372,8 +370,9 @@ mod osmodule; // skipped py_curses.h #[cfg(not(any(PyPy, Py_LIMITED_API, Py_3_10)))] mod pyarena; +#[cfg(Py_3_11)] +mod pybuffer; mod pycapsule; -// skipped pydecimal.h // skipped pydtrace.h mod pyerrors; // skipped pyexpat.h @@ -387,6 +386,7 @@ mod pylifecycle; mod pymem; mod pyport; mod pystate; +// skipped pystats.h mod pythonrun; // skipped pystrhex.h // skipped pystrcmp.h diff --git a/pyo3-ffi/src/object.rs b/pyo3-ffi/src/object.rs index b61548deaab..e850a755dc7 100644 --- a/pyo3-ffi/src/object.rs +++ b/pyo3-ffi/src/object.rs @@ -1,5 +1,3 @@ -// FFI note: this file changed a lot between 3.6 and 3.10. -// Some missing definitions may not be marked "skipped". use crate::pyport::{Py_hash_t, Py_ssize_t}; use std::mem; use std::os::raw::{c_char, c_int, c_uint, c_ulong, c_void}; @@ -14,11 +12,24 @@ pub use crate::cpython::object::PyTypeObject; // _PyObject_HEAD_EXTRA: conditionally defined in PyObject_HEAD_INIT // _PyObject_EXTRA_INIT: conditionally defined in PyObject_HEAD_INIT +#[cfg(Py_3_12)] +pub const _Py_IMMORTAL_REFCNT: Py_ssize_t = { + if cfg!(target_pointer_width = "64") { + c_uint::MAX as Py_ssize_t + } else { + // for 32-bit systems, use the lower 30 bits (see comment in CPython's object.h) + (c_uint::MAX >> 2) as Py_ssize_t + } +}; + pub const PyObject_HEAD_INIT: PyObject = PyObject { #[cfg(py_sys_config = "Py_TRACE_REFS")] _ob_next: std::ptr::null_mut(), #[cfg(py_sys_config = "Py_TRACE_REFS")] _ob_prev: std::ptr::null_mut(), + #[cfg(Py_3_12)] + ob_refcnt: PyObjectObRefcnt { ob_refcnt: 1 }, + #[cfg(not(Py_3_12))] ob_refcnt: 1, #[cfg(PyPy)] ob_pypy_link: 0, @@ -28,6 +39,27 @@ pub const PyObject_HEAD_INIT: PyObject = PyObject { // skipped PyObject_VAR_HEAD // skipped Py_INVALID_SIZE +#[repr(C)] +#[derive(Copy, Clone)] +#[cfg(Py_3_12)] +/// This union is anonymous in CPython, so the name was given by PyO3 because +/// Rust unions need a name. +pub union PyObjectObRefcnt { + pub ob_refcnt: Py_ssize_t, + #[cfg(target_pointer_width = "64")] + pub ob_refcnt_split: [crate::PY_UINT32_T; 2], +} + +#[cfg(Py_3_12)] +impl std::fmt::Debug for PyObjectObRefcnt { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", unsafe { self.ob_refcnt }) + } +} + +#[cfg(not(Py_3_12))] +pub type PyObjectObRefcnt = Py_ssize_t; + #[repr(C)] #[derive(Copy, Clone, Debug)] pub struct PyObject { @@ -35,14 +67,13 @@ pub struct PyObject { pub _ob_next: *mut PyObject, #[cfg(py_sys_config = "Py_TRACE_REFS")] pub _ob_prev: *mut PyObject, - pub ob_refcnt: Py_ssize_t, + pub ob_refcnt: PyObjectObRefcnt, #[cfg(PyPy)] pub ob_pypy_link: Py_ssize_t, pub ob_type: *mut PyTypeObject, } // skipped _PyObject_CAST -// skipped _PyObject_CAST_CONST #[repr(C)] #[derive(Debug, Copy, Clone)] @@ -52,18 +83,21 @@ pub struct PyVarObject { } // skipped _PyVarObject_CAST -// skipped _PyVarObject_CAST_CONST #[inline] pub unsafe fn Py_Is(x: *mut PyObject, y: *mut PyObject) -> c_int { (x == y).into() } -// skipped _Py_REFCNT: defined in Py_REFCNT +#[inline] +#[cfg(Py_3_12)] +pub unsafe fn Py_REFCNT(ob: *mut PyObject) -> Py_ssize_t { + (*ob).ob_refcnt.ob_refcnt +} #[inline] +#[cfg(not(Py_3_12))] pub unsafe fn Py_REFCNT(ob: *mut PyObject) -> Py_ssize_t { - assert!(!ob.is_null()); (*ob).ob_refcnt } @@ -72,9 +106,14 @@ pub unsafe fn Py_TYPE(ob: *mut PyObject) -> *mut PyTypeObject { (*ob).ob_type } +// PyLong_Type defined in longobject.rs +// PyBool_Type defined in boolobject.rs + #[inline] pub unsafe fn Py_SIZE(ob: *mut PyObject) -> Py_ssize_t { - (*(ob as *mut PyVarObject)).ob_size + debug_assert_ne!((*ob).ob_type, std::ptr::addr_of_mut!(crate::PyLong_Type)); + debug_assert_ne!((*ob).ob_type, std::ptr::addr_of_mut!(crate::PyBool_Type)); + (*ob.cast::()).ob_size } #[inline] @@ -82,6 +121,18 @@ pub unsafe fn Py_IS_TYPE(ob: *mut PyObject, tp: *mut PyTypeObject) -> c_int { (Py_TYPE(ob) == tp) as c_int } +#[inline(always)] +#[cfg(all(Py_3_12, target_pointer_width = "64"))] +pub unsafe fn _Py_IsImmortal(op: *mut PyObject) -> c_int { + (((*op).ob_refcnt.ob_refcnt as crate::PY_INT32_T) < 0) as c_int +} + +#[inline(always)] +#[cfg(all(Py_3_12, target_pointer_width = "32"))] +pub unsafe fn _Py_IsImmortal(op: *mut PyObject) -> c_int { + ((*op).ob_refcnt.ob_refcnt == _Py_IMMORTAL_REFCNT) as c_int +} + // skipped _Py_SET_REFCNT // skipped Py_SET_REFCNT // skipped _Py_SET_TYPE @@ -89,82 +140,51 @@ pub unsafe fn Py_IS_TYPE(ob: *mut PyObject, tp: *mut PyTypeObject) -> c_int { // skipped _Py_SET_SIZE // skipped Py_SET_SIZE -pub type unaryfunc = unsafe extern "C" fn(arg1: *mut PyObject) -> *mut PyObject; - -pub type binaryfunc = - unsafe extern "C" fn(arg1: *mut PyObject, arg2: *mut PyObject) -> *mut PyObject; - -pub type ternaryfunc = unsafe extern "C" fn( - arg1: *mut PyObject, - arg2: *mut PyObject, - arg3: *mut PyObject, -) -> *mut PyObject; - -pub type inquiry = unsafe extern "C" fn(arg1: *mut PyObject) -> c_int; - -pub type lenfunc = unsafe extern "C" fn(arg1: *mut PyObject) -> Py_ssize_t; - -pub type ssizeargfunc = - unsafe extern "C" fn(arg1: *mut PyObject, arg2: Py_ssize_t) -> *mut PyObject; - +pub type unaryfunc = unsafe extern "C" fn(*mut PyObject) -> *mut PyObject; +pub type binaryfunc = unsafe extern "C" fn(*mut PyObject, *mut PyObject) -> *mut PyObject; +pub type ternaryfunc = + unsafe extern "C" fn(*mut PyObject, *mut PyObject, *mut PyObject) -> *mut PyObject; +pub type inquiry = unsafe extern "C" fn(*mut PyObject) -> c_int; +pub type lenfunc = unsafe extern "C" fn(*mut PyObject) -> Py_ssize_t; +pub type ssizeargfunc = unsafe extern "C" fn(*mut PyObject, Py_ssize_t) -> *mut PyObject; pub type ssizessizeargfunc = - unsafe extern "C" fn(arg1: *mut PyObject, arg2: Py_ssize_t, arg3: Py_ssize_t) -> *mut PyObject; - -pub type ssizeobjargproc = - unsafe extern "C" fn(arg1: *mut PyObject, arg2: Py_ssize_t, arg3: *mut PyObject) -> c_int; - -pub type ssizessizeobjargproc = unsafe extern "C" fn( - arg1: *mut PyObject, - arg2: Py_ssize_t, - arg3: Py_ssize_t, - arg4: *mut PyObject, -) -> c_int; + unsafe extern "C" fn(*mut PyObject, Py_ssize_t, Py_ssize_t) -> *mut PyObject; +pub type ssizeobjargproc = unsafe extern "C" fn(*mut PyObject, Py_ssize_t, *mut PyObject) -> c_int; +pub type ssizessizeobjargproc = + unsafe extern "C" fn(*mut PyObject, Py_ssize_t, Py_ssize_t, arg4: *mut PyObject) -> c_int; +pub type objobjargproc = unsafe extern "C" fn(*mut PyObject, *mut PyObject, *mut PyObject) -> c_int; -pub type objobjargproc = - unsafe extern "C" fn(arg1: *mut PyObject, arg2: *mut PyObject, arg3: *mut PyObject) -> c_int; - -pub type objobjproc = unsafe extern "C" fn(arg1: *mut PyObject, arg2: *mut PyObject) -> c_int; +pub type objobjproc = unsafe extern "C" fn(*mut PyObject, *mut PyObject) -> c_int; pub type visitproc = unsafe extern "C" fn(object: *mut PyObject, arg: *mut c_void) -> c_int; pub type traverseproc = unsafe extern "C" fn(slf: *mut PyObject, visit: visitproc, arg: *mut c_void) -> c_int; -pub type freefunc = unsafe extern "C" fn(arg1: *mut c_void); -pub type destructor = unsafe extern "C" fn(arg1: *mut PyObject); -pub type getattrfunc = - unsafe extern "C" fn(arg1: *mut PyObject, arg2: *mut c_char) -> *mut PyObject; -pub type getattrofunc = - unsafe extern "C" fn(arg1: *mut PyObject, arg2: *mut PyObject) -> *mut PyObject; -pub type setattrfunc = - unsafe extern "C" fn(arg1: *mut PyObject, arg2: *mut c_char, arg3: *mut PyObject) -> c_int; -pub type setattrofunc = - unsafe extern "C" fn(arg1: *mut PyObject, arg2: *mut PyObject, arg3: *mut PyObject) -> c_int; -pub type reprfunc = unsafe extern "C" fn(arg1: *mut PyObject) -> *mut PyObject; -pub type hashfunc = unsafe extern "C" fn(arg1: *mut PyObject) -> Py_hash_t; -pub type richcmpfunc = - unsafe extern "C" fn(arg1: *mut PyObject, arg2: *mut PyObject, arg3: c_int) -> *mut PyObject; -pub type getiterfunc = unsafe extern "C" fn(arg1: *mut PyObject) -> *mut PyObject; -pub type iternextfunc = unsafe extern "C" fn(arg1: *mut PyObject) -> *mut PyObject; -pub type descrgetfunc = unsafe extern "C" fn( - arg1: *mut PyObject, - arg2: *mut PyObject, - arg3: *mut PyObject, -) -> *mut PyObject; -pub type descrsetfunc = - unsafe extern "C" fn(arg1: *mut PyObject, arg2: *mut PyObject, arg3: *mut PyObject) -> c_int; -pub type initproc = - unsafe extern "C" fn(arg1: *mut PyObject, arg2: *mut PyObject, arg3: *mut PyObject) -> c_int; -pub type newfunc = unsafe extern "C" fn( - arg1: *mut PyTypeObject, - arg2: *mut PyObject, - arg3: *mut PyObject, +pub type freefunc = unsafe extern "C" fn(*mut c_void); +pub type destructor = unsafe extern "C" fn(*mut PyObject); +pub type getattrfunc = unsafe extern "C" fn(*mut PyObject, *mut c_char) -> *mut PyObject; +pub type getattrofunc = unsafe extern "C" fn(*mut PyObject, *mut PyObject) -> *mut PyObject; +pub type setattrfunc = unsafe extern "C" fn(*mut PyObject, *mut c_char, *mut PyObject) -> c_int; +pub type setattrofunc = unsafe extern "C" fn(*mut PyObject, *mut PyObject, *mut PyObject) -> c_int; +pub type reprfunc = unsafe extern "C" fn(*mut PyObject) -> *mut PyObject; +pub type hashfunc = unsafe extern "C" fn(*mut PyObject) -> Py_hash_t; +pub type richcmpfunc = unsafe extern "C" fn(*mut PyObject, *mut PyObject, c_int) -> *mut PyObject; +pub type getiterfunc = unsafe extern "C" fn(*mut PyObject) -> *mut PyObject; +pub type iternextfunc = unsafe extern "C" fn(*mut PyObject) -> *mut PyObject; +pub type descrgetfunc = + unsafe extern "C" fn(*mut PyObject, *mut PyObject, *mut PyObject) -> *mut PyObject; +pub type descrsetfunc = unsafe extern "C" fn(*mut PyObject, *mut PyObject, *mut PyObject) -> c_int; +pub type initproc = unsafe extern "C" fn(*mut PyObject, *mut PyObject, *mut PyObject) -> c_int; +pub type newfunc = + unsafe extern "C" fn(*mut PyTypeObject, *mut PyObject, *mut PyObject) -> *mut PyObject; +pub type allocfunc = unsafe extern "C" fn(*mut PyTypeObject, Py_ssize_t) -> *mut PyObject; + +#[cfg(Py_3_8)] +pub type vectorcallfunc = unsafe extern "C" fn( + callable: *mut PyObject, + args: *const *mut PyObject, + nargsf: libc::size_t, + kwnames: *mut PyObject, ) -> *mut PyObject; -pub type allocfunc = - unsafe extern "C" fn(arg1: *mut PyTypeObject, arg2: Py_ssize_t) -> *mut PyObject; -#[cfg(Py_3_11)] -pub type getbufferproc = - unsafe extern "C" fn(arg1: *mut PyObject, arg2: *mut crate::Py_buffer, arg3: c_int) -> c_int; -#[cfg(Py_3_11)] -pub type releasebufferproc = unsafe extern "C" fn(arg1: *mut PyObject, arg2: *mut crate::Py_buffer); #[repr(C)] #[derive(Copy, Clone)] @@ -220,9 +240,32 @@ extern "C" { #[cfg(any(Py_3_10, all(Py_3_9, not(Py_LIMITED_API))))] #[cfg_attr(PyPy, link_name = "PyPyType_GetModuleState")] pub fn PyType_GetModuleState(arg1: *mut PyTypeObject) -> *mut c_void; -} -extern "C" { + #[cfg(Py_3_11)] + #[cfg_attr(PyPy, link_name = "PyPyType_GetName")] + pub fn PyType_GetName(arg1: *mut PyTypeObject) -> *mut PyObject; + + #[cfg(Py_3_11)] + #[cfg_attr(PyPy, link_name = "PyPyType_GetQualName")] + pub fn PyType_GetQualName(arg1: *mut PyTypeObject) -> *mut PyObject; + + #[cfg(Py_3_12)] + #[cfg_attr(PyPy, link_name = "PyPyType_FromMetaclass")] + pub fn PyType_FromMetaclass( + metaclass: *mut PyTypeObject, + module: *mut PyObject, + spec: *mut PyType_Spec, + bases: *mut PyObject, + ) -> *mut PyObject; + + #[cfg(Py_3_12)] + #[cfg_attr(PyPy, link_name = "PyPyObject_GetTypeData")] + pub fn PyObject_GetTypeData(obj: *mut PyObject, cls: *mut PyTypeObject) -> *mut c_void; + + #[cfg(Py_3_12)] + #[cfg_attr(PyPy, link_name = "PyPyObject_GetTypeDataSize")] + pub fn PyObject_GetTypeDataSize(cls: *mut PyTypeObject) -> Py_ssize_t; + #[cfg_attr(PyPy, link_name = "PyPyType_IsSubtype")] pub fn PyType_IsSubtype(a: *mut PyTypeObject, b: *mut PyTypeObject) -> c_int; } @@ -246,9 +289,7 @@ extern "C" { extern "C" { pub fn PyType_GetFlags(arg1: *mut PyTypeObject) -> c_ulong; -} -extern "C" { #[cfg_attr(PyPy, link_name = "PyPyType_Ready")] pub fn PyType_Ready(t: *mut PyTypeObject) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyType_GenericAlloc")] @@ -337,6 +378,15 @@ extern "C" { // Flag bits for printing: pub const Py_PRINT_RAW: c_int = 1; // No string quotes etc. +#[cfg(all(Py_3_12, not(Py_LIMITED_API)))] +pub const _Py_TPFLAGS_STATIC_BUILTIN: c_ulong = 1 << 1; + +#[cfg(all(Py_3_12, not(Py_LIMITED_API)))] +pub const Py_TPFLAGS_MANAGED_WEAKREF: c_ulong = 1 << 3; + +#[cfg(all(Py_3_11, not(Py_LIMITED_API)))] +pub const Py_TPFLAGS_MANAGED_DICT: c_ulong = 1 << 4; + #[cfg(all(Py_3_10, not(Py_LIMITED_API)))] pub const Py_TPFLAGS_SEQUENCE: c_ulong = 1 << 5; @@ -356,7 +406,7 @@ pub const Py_TPFLAGS_HEAPTYPE: c_ulong = 1 << 9; pub const Py_TPFLAGS_BASETYPE: c_ulong = 1 << 10; /// Set if the type implements the vectorcall protocol (PEP 590) -#[cfg(all(Py_3_8, not(Py_LIMITED_API)))] +#[cfg(any(Py_3_12, all(Py_3_8, not(Py_LIMITED_API))))] pub const Py_TPFLAGS_HAVE_VECTORCALL: c_ulong = 1 << 11; // skipped non-limited _Py_TPFLAGS_HAVE_VECTORCALL @@ -374,15 +424,14 @@ const Py_TPFLAGS_HAVE_STACKLESS_EXTENSION: c_ulong = 0; #[cfg(Py_3_8)] pub const Py_TPFLAGS_METHOD_DESCRIPTOR: c_ulong = 1 << 17; -/// This flag does nothing in Python 3.10+ -pub const Py_TPFLAGS_HAVE_VERSION_TAG: c_ulong = 1 << 18; - pub const Py_TPFLAGS_VALID_VERSION_TAG: c_ulong = 1 << 19; /* Type is abstract and cannot be instantiated */ pub const Py_TPFLAGS_IS_ABSTRACT: c_ulong = 1 << 20; // skipped non-limited / 3.10 Py_TPFLAGS_HAVE_AM_SEND +#[cfg(Py_3_12)] +pub const Py_TPFLAGS_ITEMS_AT_END: c_ulong = 1 << 23; /* These flags are used to determine if a type is a subclass. */ pub const Py_TPFLAGS_LONG_SUBCLASS: c_ulong = 1 << 24; @@ -394,37 +443,161 @@ pub const Py_TPFLAGS_DICT_SUBCLASS: c_ulong = 1 << 29; pub const Py_TPFLAGS_BASE_EXC_SUBCLASS: c_ulong = 1 << 30; pub const Py_TPFLAGS_TYPE_SUBCLASS: c_ulong = 1 << 31; -pub const Py_TPFLAGS_DEFAULT: c_ulong = - Py_TPFLAGS_HAVE_STACKLESS_EXTENSION | Py_TPFLAGS_HAVE_VERSION_TAG; +pub const Py_TPFLAGS_DEFAULT: c_ulong = if cfg!(Py_3_10) { + Py_TPFLAGS_HAVE_STACKLESS_EXTENSION +} else { + Py_TPFLAGS_HAVE_STACKLESS_EXTENSION | Py_TPFLAGS_HAVE_VERSION_TAG +}; pub const Py_TPFLAGS_HAVE_FINALIZE: c_ulong = 1; +pub const Py_TPFLAGS_HAVE_VERSION_TAG: c_ulong = 1 << 18; -// skipped _Py_RefTotal -// skipped _Py_NegativeRefCount +#[cfg(all(py_sys_config = "Py_REF_DEBUG", not(Py_LIMITED_API)))] +extern "C" { + pub fn _Py_NegativeRefCount(filename: *const c_char, lineno: c_int, op: *mut PyObject); + #[cfg(Py_3_12)] + #[link_name = "_Py_IncRefTotal_DO_NOT_USE_THIS"] + fn _Py_INC_REFTOTAL(); + #[cfg(Py_3_12)] + #[link_name = "_Py_DecRefTotal_DO_NOT_USE_THIS"] + fn _Py_DEC_REFTOTAL(); +} extern "C" { #[cfg_attr(PyPy, link_name = "_PyPy_Dealloc")] pub fn _Py_Dealloc(arg1: *mut PyObject); + + #[cfg_attr(PyPy, link_name = "PyPy_IncRef")] + pub fn Py_IncRef(o: *mut PyObject); + #[cfg_attr(PyPy, link_name = "PyPy_DecRef")] + pub fn Py_DecRef(o: *mut PyObject); + + #[cfg(Py_3_10)] + #[cfg_attr(PyPy, link_name = "_PyPy_IncRef")] + pub fn _Py_IncRef(o: *mut PyObject); + #[cfg(Py_3_10)] + #[cfg_attr(PyPy, link_name = "_PyPy_DecRef")] + pub fn _Py_DecRef(o: *mut PyObject); } -// Reference counting macros. -#[inline] +#[inline(always)] pub unsafe fn Py_INCREF(op: *mut PyObject) { - if cfg!(py_sys_config = "Py_REF_DEBUG") { - Py_IncRef(op) - } else { - (*op).ob_refcnt += 1 + #[cfg(any( + all(Py_LIMITED_API, Py_3_12), + all( + py_sys_config = "Py_REF_DEBUG", + Py_3_10, + not(all(Py_3_12, not(Py_LIMITED_API))) + ) + ))] + { + return _Py_IncRef(op); + } + + #[cfg(all(py_sys_config = "Py_REF_DEBUG", not(Py_3_10)))] + { + return Py_IncRef(op); + } + + #[cfg(any( + not(Py_LIMITED_API), + all(Py_LIMITED_API, not(Py_3_12)), + all(py_sys_config = "Py_REF_DEBUG", Py_3_12, not(Py_LIMITED_API)) + ))] + { + #[cfg(all(Py_3_12, target_pointer_width = "64"))] + { + let cur_refcnt = (*op).ob_refcnt.ob_refcnt_split[crate::PY_BIG_ENDIAN]; + let new_refcnt = cur_refcnt.wrapping_add(1); + if new_refcnt == 0 { + return; + } + (*op).ob_refcnt.ob_refcnt_split[crate::PY_BIG_ENDIAN] = new_refcnt; + } + + #[cfg(all(Py_3_12, target_pointer_width = "32"))] + { + if _Py_IsImmortal(op) != 0 { + return; + } + (*op).ob_refcnt.ob_refcnt += 1 + } + + #[cfg(not(Py_3_12))] + { + (*op).ob_refcnt += 1 + } + + // Skipped _Py_INCREF_STAT_INC - if anyone wants this, please file an issue + // or submit a PR supporting Py_STATS build option and pystats.h + + #[cfg(all(py_sys_config = "Py_REF_DEBUG", Py_3_12))] + _Py_INC_REFTOTAL(); } } -#[inline] +#[inline(always)] +#[cfg_attr( + all(py_sys_config = "Py_REF_DEBUG", Py_3_12, not(Py_LIMITED_API)), + track_caller +)] pub unsafe fn Py_DECREF(op: *mut PyObject) { - if cfg!(py_sys_config = "Py_REF_DEBUG") { - Py_DecRef(op) - } else { - (*op).ob_refcnt -= 1; - if (*op).ob_refcnt == 0 { - _Py_Dealloc(op) + #[cfg(any( + all(Py_LIMITED_API, Py_3_12), + all( + py_sys_config = "Py_REF_DEBUG", + Py_3_10, + not(all(Py_3_12, not(Py_LIMITED_API))) + ) + ))] + { + return _Py_DecRef(op); + } + + #[cfg(all(py_sys_config = "Py_REF_DEBUG", not(Py_3_10)))] + { + return Py_DecRef(op); + } + + #[cfg(any( + not(Py_LIMITED_API), + all(Py_LIMITED_API, not(Py_3_12)), + all(py_sys_config = "Py_REF_DEBUG", Py_3_12, not(Py_LIMITED_API)) + ))] + { + #[cfg(Py_3_12)] + if _Py_IsImmortal(op) != 0 { + return; + } + + // Skipped _Py_DECREF_STAT_INC - if anyone needs this, please file an issue + // or submit a PR supporting Py_STATS build option and pystats.h + + #[cfg(all(py_sys_config = "Py_REF_DEBUG", Py_3_12))] + _Py_DEC_REFTOTAL(); + + #[cfg(Py_3_12)] + { + (*op).ob_refcnt.ob_refcnt -= 1; + + #[cfg(py_sys_config = "Py_REF_DEBUG")] + if (*op).ob_refcnt.ob_refcnt < 0 { + let location = std::panic::Location::caller(); + _Py_NegativeRefcount(location.file(), location.line(), op); + } + + if (*op).ob_refcnt.ob_refcnt == 0 { + _Py_Dealloc(op); + } + } + + #[cfg(not(Py_3_12))] + { + (*op).ob_refcnt -= 1; + + if (*op).ob_refcnt == 0 { + _Py_Dealloc(op); + } } } } @@ -453,14 +626,9 @@ pub unsafe fn Py_XDECREF(op: *mut PyObject) { } extern "C" { - #[cfg_attr(PyPy, link_name = "PyPy_IncRef")] - pub fn Py_IncRef(o: *mut PyObject); - #[cfg_attr(PyPy, link_name = "PyPy_DecRef")] - pub fn Py_DecRef(o: *mut PyObject); - - #[cfg(Py_3_10)] + #[cfg(all(Py_3_10, Py_LIMITED_API))] pub fn Py_NewRef(obj: *mut PyObject) -> *mut PyObject; - #[cfg(Py_3_10)] + #[cfg(all(Py_3_10, Py_LIMITED_API))] pub fn Py_XNewRef(obj: *mut PyObject) -> *mut PyObject; } @@ -480,6 +648,18 @@ pub unsafe fn _Py_XNewRef(obj: *mut PyObject) -> *mut PyObject { obj } +#[cfg(all(Py_3_10, not(Py_LIMITED_API)))] +#[inline] +pub unsafe fn Py_NewRef(obj: *mut PyObject) -> *mut PyObject { + _Py_NewRef(obj) +} + +#[cfg(all(Py_3_10, not(Py_LIMITED_API)))] +#[inline] +pub unsafe fn Py_XNewRef(obj: *mut PyObject) -> *mut PyObject { + _Py_XNewRef(obj) +} + #[cfg_attr(windows, link(name = "pythonXY"))] extern "C" { #[cfg_attr(PyPy, link_name = "_PyPy_NoneStruct")] @@ -554,5 +734,5 @@ pub unsafe fn PyType_Check(op: *mut PyObject) -> c_int { #[inline] pub unsafe fn PyType_CheckExact(op: *mut PyObject) -> c_int { - (Py_TYPE(op) == ptr::addr_of_mut!(PyType_Type)) as c_int + Py_IS_TYPE(op, ptr::addr_of_mut!(PyType_Type)) } diff --git a/pyo3-ffi/src/buffer.rs b/pyo3-ffi/src/pybuffer.rs similarity index 95% rename from pyo3-ffi/src/buffer.rs rename to pyo3-ffi/src/pybuffer.rs index bfa48c086cb..20f92fb6d2b 100644 --- a/pyo3-ffi/src/buffer.rs +++ b/pyo3-ffi/src/pybuffer.rs @@ -53,6 +53,9 @@ impl Py_buffer { } } +pub type getbufferproc = unsafe extern "C" fn(*mut PyObject, *mut crate::Py_buffer, c_int) -> c_int; +pub type releasebufferproc = unsafe extern "C" fn(*mut PyObject, *mut crate::Py_buffer); + /* Return 1 if the getbuffer function is available, otherwise return 0. */ extern "C" { #[cfg(not(PyPy))] diff --git a/pyo3-ffi/src/pyport.rs b/pyo3-ffi/src/pyport.rs index 4bf668dcbf5..741b0db7bf8 100644 --- a/pyo3-ffi/src/pyport.rs +++ b/pyo3-ffi/src/pyport.rs @@ -1,3 +1,9 @@ +pub type PY_UINT32_T = u32; +pub type PY_UINT64_T = u64; + +pub type PY_INT32_T = i32; +pub type PY_INT64_T = i64; + pub type Py_uintptr_t = ::libc::uintptr_t; pub type Py_intptr_t = ::libc::intptr_t; pub type Py_ssize_t = ::libc::ssize_t; @@ -7,3 +13,13 @@ pub type Py_uhash_t = ::libc::size_t; pub const PY_SSIZE_T_MIN: Py_ssize_t = std::isize::MIN as Py_ssize_t; pub const PY_SSIZE_T_MAX: Py_ssize_t = std::isize::MAX as Py_ssize_t; + +#[cfg(target_endian = "big")] +pub const PY_BIG_ENDIAN: usize = 1; +#[cfg(target_endian = "big")] +pub const PY_LITTLE_ENDIAN: usize = 0; + +#[cfg(target_endian = "little")] +pub const PY_BIG_ENDIAN: usize = 0; +#[cfg(target_endian = "little")] +pub const PY_LITTLE_ENDIAN: usize = 1; diff --git a/src/ffi/mod.rs b/src/ffi/mod.rs index 81d6f38e4b7..ce108223fbe 100644 --- a/src/ffi/mod.rs +++ b/src/ffi/mod.rs @@ -21,7 +21,7 @@ //! //! [capi]: https://docs.python.org/3/c-api/index.html -#[cfg(all(not(Py_LIMITED_API), test))] +#[cfg(test)] mod tests; // reexport raw bindings exposed in pyo3_ffi diff --git a/src/ffi/tests.rs b/src/ffi/tests.rs index f9edd8ee3ac..14f76cb4fee 100644 --- a/src/ffi/tests.rs +++ b/src/ffi/tests.rs @@ -1,10 +1,15 @@ use crate::ffi::*; -use crate::{types::PyDict, AsPyPointer, IntoPy, Py, PyAny, Python}; - -use crate::types::PyString; -#[cfg(not(Py_3_12))] +use crate::{AsPyPointer, Python}; + +#[cfg(not(Py_LIMITED_API))] +use crate::{ + types::{PyDict, PyString}, + IntoPy, Py, PyAny, +}; +#[cfg(not(any(Py_3_12, Py_LIMITED_API)))] use libc::wchar_t; +#[cfg(not(Py_LIMITED_API))] #[cfg_attr(target_arch = "wasm32", ignore)] // DateTime import fails on wasm for mysterious reasons #[test] fn test_datetime_fromtimestamp() { @@ -25,6 +30,7 @@ fn test_datetime_fromtimestamp() { }) } +#[cfg(not(Py_LIMITED_API))] #[cfg_attr(target_arch = "wasm32", ignore)] // DateTime import fails on wasm for mysterious reasons #[test] fn test_date_fromtimestamp() { @@ -45,6 +51,7 @@ fn test_date_fromtimestamp() { }) } +#[cfg(not(Py_LIMITED_API))] #[cfg_attr(target_arch = "wasm32", ignore)] // DateTime import fails on wasm for mysterious reasons #[test] fn test_utc_timezone() { @@ -65,6 +72,7 @@ fn test_utc_timezone() { } #[test] +#[cfg(not(Py_LIMITED_API))] #[cfg(feature = "macros")] #[cfg_attr(target_arch = "wasm32", ignore)] // DateTime import fails on wasm for mysterious reasons fn test_timezone_from_offset() { @@ -82,6 +90,7 @@ fn test_timezone_from_offset() { } #[test] +#[cfg(not(Py_LIMITED_API))] #[cfg(feature = "macros")] #[cfg_attr(target_arch = "wasm32", ignore)] // DateTime import fails on wasm for mysterious reasons fn test_timezone_from_offset_and_name() { @@ -105,6 +114,7 @@ fn test_timezone_from_offset_and_name() { } #[test] +#[cfg(not(Py_LIMITED_API))] fn ascii_object_bitfield() { let ob_base: PyObject = unsafe { std::mem::zeroed() }; @@ -152,6 +162,7 @@ fn ascii_object_bitfield() { } #[test] +#[cfg(not(Py_LIMITED_API))] #[cfg_attr(Py_3_10, allow(deprecated))] fn ascii() { Python::with_gil(|py| { @@ -193,6 +204,7 @@ fn ascii() { } #[test] +#[cfg(not(Py_LIMITED_API))] #[cfg_attr(Py_3_10, allow(deprecated))] fn ucs4() { Python::with_gil(|py| { @@ -236,6 +248,7 @@ fn ucs4() { } #[test] +#[cfg(not(Py_LIMITED_API))] #[cfg_attr(target_arch = "wasm32", ignore)] // DateTime import fails on wasm for mysterious reasons #[cfg(not(PyPy))] fn test_get_tzinfo() { @@ -276,3 +289,40 @@ fn test_get_tzinfo() { ); }) } + +#[test] +fn test_inc_dec_ref() { + Python::with_gil(|py| { + let obj = py.eval("object()", None, None).unwrap(); + + let ref_count = obj.get_refcnt(); + let ptr = obj.as_ptr(); + + unsafe { Py_INCREF(ptr) }; + + assert_eq!(obj.get_refcnt(), ref_count + 1); + + unsafe { Py_DECREF(ptr) }; + + assert_eq!(obj.get_refcnt(), ref_count); + }) +} + +#[test] +#[cfg(Py_3_12)] +fn test_inc_dec_ref_immortal() { + Python::with_gil(|py| { + let obj = py.None(); + + let ref_count = obj.get_refcnt(py); + let ptr = obj.as_ptr(); + + unsafe { Py_INCREF(ptr) }; + + assert_eq!(obj.get_refcnt(py), ref_count); + + unsafe { Py_DECREF(ptr) }; + + assert_eq!(obj.get_refcnt(py), ref_count); + }) +} From ef437319934ec98d466afb854356718c33747163 Mon Sep 17 00:00:00 2001 From: David Hewitt <1939362+davidhewitt@users.noreply.github.com> Date: Tue, 25 Jul 2023 05:03:00 +0100 Subject: [PATCH 2/2] update tests of refcounting to use a non-immortal object --- src/types/dict.rs | 8 ++++---- src/types/iterator.rs | 25 ++++++++++++------------- src/types/list.rs | 24 ++++++++++++------------ 3 files changed, 28 insertions(+), 29 deletions(-) diff --git a/src/types/dict.rs b/src/types/dict.rs index 35796ff7a8d..1d3ac185300 100644 --- a/src/types/dict.rs +++ b/src/types/dict.rs @@ -591,14 +591,14 @@ mod tests { fn test_set_item_refcnt() { Python::with_gil(|py| { let cnt; + let obj = py.eval("object()", None, None).unwrap(); { let _pool = unsafe { crate::GILPool::new() }; - let none = py.None(); - cnt = none.get_refcnt(py); - let _dict = [(10, none)].into_py_dict(py); + cnt = obj.get_refcnt(); + let _dict = [(10, obj)].into_py_dict(py); } { - assert_eq!(cnt, py.None().get_refcnt(py)); + assert_eq!(cnt, obj.get_refcnt()); } }); } diff --git a/src/types/iterator.rs b/src/types/iterator.rs index 71ca5d305b4..7b411bde765 100644 --- a/src/types/iterator.rs +++ b/src/types/iterator.rs @@ -155,31 +155,30 @@ mod tests { #[test] fn iter_item_refcnt() { Python::with_gil(|py| { - let obj; - let none; let count; - { + let obj = py.eval("object()", None, None).unwrap(); + let list = { let _pool = unsafe { GILPool::new() }; - let l = PyList::empty(py); - none = py.None(); - l.append(10).unwrap(); - l.append(&none).unwrap(); - count = none.get_refcnt(py); - obj = l.to_object(py); - } + let list = PyList::empty(py); + list.append(10).unwrap(); + list.append(obj).unwrap(); + count = obj.get_refcnt(); + list.to_object(py) + }; { let _pool = unsafe { GILPool::new() }; - let inst = obj.as_ref(py); + let inst = list.as_ref(py); let mut it = inst.iter().unwrap(); assert_eq!( 10_i32, it.next().unwrap().unwrap().extract::<'_, i32>().unwrap() ); - assert!(it.next().unwrap().unwrap().is_none()); + assert!(it.next().unwrap().unwrap().is(obj)); + assert!(it.next().is_none()); } - assert_eq!(count, none.get_refcnt(py)); + assert_eq!(count, obj.get_refcnt()); }); } diff --git a/src/types/list.rs b/src/types/list.rs index 2045d89720b..8f3a1672f6d 100644 --- a/src/types/list.rs +++ b/src/types/list.rs @@ -395,18 +395,18 @@ mod tests { #[test] fn test_set_item_refcnt() { Python::with_gil(|py| { + let obj = py.eval("object()", None, None).unwrap(); let cnt; { let _pool = unsafe { crate::GILPool::new() }; let v = vec![2]; let ob = v.to_object(py); let list: &PyList = ob.downcast(py).unwrap(); - let none = py.None(); - cnt = none.get_refcnt(py); - list.set_item(0, none).unwrap(); + cnt = obj.get_refcnt(); + list.set_item(0, obj).unwrap(); } - assert_eq!(cnt, py.None().get_refcnt(py)); + assert_eq!(cnt, obj.get_refcnt()); }); } @@ -431,15 +431,15 @@ mod tests { fn test_insert_refcnt() { Python::with_gil(|py| { let cnt; + let obj = py.eval("object()", None, None).unwrap(); { let _pool = unsafe { crate::GILPool::new() }; let list = PyList::empty(py); - let none = py.None(); - cnt = none.get_refcnt(py); - list.insert(0, none).unwrap(); + cnt = obj.get_refcnt(); + list.insert(0, obj).unwrap(); } - assert_eq!(cnt, py.None().get_refcnt(py)); + assert_eq!(cnt, obj.get_refcnt()); }); } @@ -457,14 +457,14 @@ mod tests { fn test_append_refcnt() { Python::with_gil(|py| { let cnt; + let obj = py.eval("object()", None, None).unwrap(); { let _pool = unsafe { crate::GILPool::new() }; let list = PyList::empty(py); - let none = py.None(); - cnt = none.get_refcnt(py); - list.append(none).unwrap(); + cnt = obj.get_refcnt(); + list.append(obj).unwrap(); } - assert_eq!(cnt, py.None().get_refcnt(py)); + assert_eq!(cnt, obj.get_refcnt()); }); }