From 180f5886407e7c1dda34afa6fc7298b34d2925da Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 23 May 2025 15:02:33 +0200 Subject: [PATCH 1/6] Update pyo3 to 0.25.0 --- Cargo.lock | 4 +- include/pyo3/pyo3-build-config/Cargo.toml | 6 +- include/pyo3/pyo3-build-config/src/errors.rs | 2 +- include/pyo3/pyo3-build-config/src/impl_.rs | 41 +- include/pyo3/pyo3-build-config/src/lib.rs | 63 +-- include/pyo3/pyo3-ffi/ACKNOWLEDGEMENTS | 2 +- include/pyo3/pyo3-ffi/Cargo.toml | 10 +- include/pyo3/pyo3-ffi/build.rs | 74 ++- include/pyo3/pyo3-ffi/src/abstract_.rs | 41 +- include/pyo3/pyo3-ffi/src/bytearrayobject.rs | 2 +- include/pyo3/pyo3-ffi/src/code.rs | 2 +- include/pyo3/pyo3-ffi/src/compat/mod.rs | 2 + include/pyo3/pyo3-ffi/src/compat/py_3_10.rs | 26 ++ include/pyo3/pyo3-ffi/src/compat/py_3_13.rs | 15 + include/pyo3/pyo3-ffi/src/compat/py_3_14.rs | 26 ++ .../pyo3/pyo3-ffi/src/cpython/abstract_.rs | 32 +- .../pyo3/pyo3-ffi/src/cpython/bytesobject.rs | 2 +- include/pyo3/pyo3-ffi/src/cpython/code.rs | 232 ++-------- include/pyo3/pyo3-ffi/src/cpython/compile.rs | 63 ++- .../pyo3-ffi/src/cpython/critical_section.rs | 14 +- .../pyo3/pyo3-ffi/src/cpython/descrobject.rs | 5 +- .../pyo3/pyo3-ffi/src/cpython/dictobject.rs | 16 +- .../pyo3/pyo3-ffi/src/cpython/frameobject.rs | 5 +- .../pyo3/pyo3-ffi/src/cpython/funcobject.rs | 2 + .../pyo3/pyo3-ffi/src/cpython/genobject.rs | 14 +- include/pyo3/pyo3-ffi/src/cpython/import.rs | 10 +- .../pyo3/pyo3-ffi/src/cpython/initconfig.rs | 10 + include/pyo3/pyo3-ffi/src/cpython/lock.rs | 9 - .../pyo3/pyo3-ffi/src/cpython/longobject.rs | 12 - include/pyo3/pyo3-ffi/src/cpython/mod.rs | 5 +- include/pyo3/pyo3-ffi/src/cpython/object.rs | 8 +- include/pyo3/pyo3-ffi/src/cpython/objimpl.rs | 2 +- include/pyo3/pyo3-ffi/src/cpython/pyerrors.rs | 2 + include/pyo3/pyo3-ffi/src/cpython/pyframe.rs | 5 +- include/pyo3/pyo3-ffi/src/cpython/pyhash.rs | 38 ++ include/pyo3/pyo3-ffi/src/cpython/pystate.rs | 16 +- .../pyo3/pyo3-ffi/src/cpython/tupleobject.rs | 4 +- .../pyo3-ffi/src/cpython/unicodeobject.rs | 117 ++++- .../pyo3-ffi/src/cpython/weakrefobject.rs | 1 + include/pyo3/pyo3-ffi/src/datetime.rs | 10 +- include/pyo3/pyo3-ffi/src/dictobject.rs | 2 +- include/pyo3/pyo3-ffi/src/floatobject.rs | 2 +- .../pyo3/pyo3-ffi/src/genericaliasobject.rs | 12 + include/pyo3/pyo3-ffi/src/lib.rs | 23 +- include/pyo3/pyo3-ffi/src/longobject.rs | 2 +- include/pyo3/pyo3-ffi/src/memoryobject.rs | 5 +- include/pyo3/pyo3-ffi/src/methodobject.rs | 8 +- include/pyo3/pyo3-ffi/src/modsupport.rs | 7 +- include/pyo3/pyo3-ffi/src/object.rs | 422 ++++-------------- include/pyo3/pyo3-ffi/src/pyarena.rs | 2 +- include/pyo3/pyo3-ffi/src/pybuffer.rs | 6 +- include/pyo3/pyo3-ffi/src/pyerrors.rs | 1 + include/pyo3/pyo3-ffi/src/pyframe.rs | 2 +- include/pyo3/pyo3-ffi/src/pyhash.rs | 30 +- include/pyo3/pyo3-ffi/src/pyport.rs | 9 +- include/pyo3/pyo3-ffi/src/pystate.rs | 78 +++- include/pyo3/pyo3-ffi/src/pythonrun.rs | 6 +- include/pyo3/pyo3-ffi/src/refcount.rs | 369 +++++++++++++++ include/pyo3/pyo3-ffi/src/setobject.rs | 6 +- include/pyo3/pyo3-ffi/src/weakrefobject.rs | 8 +- 60 files changed, 1065 insertions(+), 885 deletions(-) create mode 100644 include/pyo3/pyo3-ffi/src/compat/py_3_14.rs create mode 100644 include/pyo3/pyo3-ffi/src/cpython/pyhash.rs create mode 100644 include/pyo3/pyo3-ffi/src/genericaliasobject.rs create mode 100644 include/pyo3/pyo3-ffi/src/refcount.rs diff --git a/Cargo.lock b/Cargo.lock index 03fdf346..c4b1eb1c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -190,7 +190,7 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.23.3" +version = "0.25.0" dependencies = [ "once_cell", "target-lexicon", @@ -198,7 +198,7 @@ dependencies = [ [[package]] name = "pyo3-ffi" -version = "0.23.3" +version = "0.25.0" dependencies = [ "libc", "pyo3-build-config", diff --git a/include/pyo3/pyo3-build-config/Cargo.toml b/include/pyo3/pyo3-build-config/Cargo.toml index 0facef81..54d5cb49 100644 --- a/include/pyo3/pyo3-build-config/Cargo.toml +++ b/include/pyo3/pyo3-build-config/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pyo3-build-config" -version = "0.23.3" +version = "0.25.0" description = "Build configuration for the PyO3 ecosystem" authors = ["PyO3 Project and Contributors "] keywords = ["pyo3", "python", "cpython", "ffi"] @@ -36,7 +36,9 @@ abi3-py38 = ["abi3-py39"] abi3-py39 = ["abi3-py310"] abi3-py310 = ["abi3-py311"] abi3-py311 = ["abi3-py312"] -abi3-py312 = ["abi3"] +abi3-py312 = ["abi3-py313"] +abi3-py313 = ["abi3-py314"] +abi3-py314 = ["abi3"] [package.metadata.docs.rs] features = ["resolve-config"] diff --git a/include/pyo3/pyo3-build-config/src/errors.rs b/include/pyo3/pyo3-build-config/src/errors.rs index 87c59a99..b11d02fd 100644 --- a/include/pyo3/pyo3-build-config/src/errors.rs +++ b/include/pyo3/pyo3-build-config/src/errors.rs @@ -68,7 +68,7 @@ impl std::fmt::Display for ErrorReport<'_> { writeln!(f, "\ncaused by:")?; let mut index = 0; while let Some(some_source) = source { - writeln!(f, " - {}: {}", index, some_source)?; + writeln!(f, " - {index}: {some_source}")?; source = some_source.source(); index += 1; } diff --git a/include/pyo3/pyo3-build-config/src/impl_.rs b/include/pyo3/pyo3-build-config/src/impl_.rs index 0f90d573..9485b8a0 100644 --- a/include/pyo3/pyo3-build-config/src/impl_.rs +++ b/include/pyo3/pyo3-build-config/src/impl_.rs @@ -40,7 +40,7 @@ const MINIMUM_SUPPORTED_VERSION_GRAALPY: PythonVersion = PythonVersion { }; /// Maximum Python version that can be used as minimum required Python version with abi3. -pub(crate) const ABI3_MAX_MINOR: u8 = 12; +pub(crate) const ABI3_MAX_MINOR: u8 = 13; #[cfg(test)] thread_local! { @@ -58,7 +58,7 @@ pub fn cargo_env_var(var: &str) -> Option { /// the variable changes. pub fn env_var(var: &str) -> Option { if cfg!(feature = "resolve-config") { - println!("cargo:rerun-if-env-changed={}", var); + println!("cargo:rerun-if-env-changed={var}"); } #[cfg(test)] { @@ -180,9 +180,8 @@ impl InterpreterConfig { let mut out = vec![]; for i in MINIMUM_SUPPORTED_VERSION.minor..=self.version.minor { - out.push(format!("cargo:rustc-cfg=Py_3_{}", i)); + out.push(format!("cargo:rustc-cfg=Py_3_{i}")); } - println!("cargo::rustc-check-cfg=cfg(Py_3_14)"); match self.implementation { PythonImplementation::CPython => {} @@ -200,7 +199,7 @@ impl InterpreterConfig { BuildFlag::Py_GIL_DISABLED => { out.push("cargo:rustc-cfg=Py_GIL_DISABLED".to_owned()) } - flag => out.push(format!("cargo:rustc-cfg=py_sys_config=\"{}\"", flag)), + flag => out.push(format!("cargo:rustc-cfg=py_sys_config=\"{flag}\"")), } } @@ -339,7 +338,7 @@ print("gil_disabled", get_config_var("Py_GIL_DISABLED")) let lib_dir = if cfg!(windows) { map.get("base_prefix") - .map(|base_prefix| format!("{}\\libs", base_prefix)) + .map(|base_prefix| format!("{base_prefix}\\libs")) } else { map.get("libdir").cloned() }; @@ -667,7 +666,7 @@ print("gil_disabled", get_config_var("Py_GIL_DISABLED")) write_option_line!(python_framework_prefix)?; write_line!(suppress_build_script_link_lines)?; for line in &self.extra_build_script_lines { - writeln!(writer, "extra_build_script_line={}", line) + writeln!(writer, "extra_build_script_line={line}") .context("failed to write extra_build_script_line")?; } Ok(()) @@ -854,7 +853,7 @@ fn is_abi3() -> bool { /// Must be called from a PyO3 crate build script. pub fn get_abi3_version() -> Option { let minor_version = (MINIMUM_SUPPORTED_VERSION.minor..=ABI3_MAX_MINOR) - .find(|i| cargo_env_var(&format!("CARGO_FEATURE_ABI3_PY3{}", i)).is_some()); + .find(|i| cargo_env_var(&format!("CARGO_FEATURE_ABI3_PY3{i}")).is_some()); minor_version.map(|minor| PythonVersion { major: 3, minor }) } @@ -1122,8 +1121,8 @@ pub enum BuildFlag { impl Display for BuildFlag { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - BuildFlag::Other(flag) => write!(f, "{}", flag), - _ => write!(f, "{:?}", self), + BuildFlag::Other(flag) => write!(f, "{flag}"), + _ => write!(f, "{self:?}"), } } } @@ -1147,7 +1146,7 @@ impl FromStr for BuildFlag { /// PyO3 will pick these up and pass to rustc via `--cfg=py_sys_config={varname}`; /// this allows using them conditional cfg attributes in the .rs files, so /// -/// ```rust +/// ```rust,no_run /// #[cfg(py_sys_config="{varname}")] /// # struct Foo; /// ``` @@ -1203,7 +1202,7 @@ impl BuildFlags { for k in &BuildFlags::ALL { use std::fmt::Write; - writeln!(&mut script, "print(config.get('{}', '0'))", k).unwrap(); + writeln!(&mut script, "print(config.get('{k}', '0'))").unwrap(); } let stdout = run_python_script(interpreter.as_ref(), &script)?; @@ -1241,7 +1240,7 @@ impl Display for BuildFlags { } else { write!(f, ",")?; } - write!(f, "{}", flag)?; + write!(f, "{flag}")?; } Ok(()) } @@ -1307,6 +1306,10 @@ pub fn parse_sysconfigdata(sysconfigdata_path: impl AsRef) -> Result Result fn is_pypy_lib_dir(path: &str, v: &Option) -> bool { let pypy_version_pat = if let Some(v) = v { - format!("pypy{}", v) + format!("pypy{v}") } else { "pypy3.".into() }; @@ -1433,7 +1436,7 @@ fn is_pypy_lib_dir(path: &str, v: &Option) -> bool { fn is_graalpy_lib_dir(path: &str, v: &Option) -> bool { let graalpy_version_pat = if let Some(v) = v { - format!("graalpy{}", v) + format!("graalpy{v}") } else { "graalpy2".into() }; @@ -1442,7 +1445,7 @@ fn is_graalpy_lib_dir(path: &str, v: &Option) -> bool { fn is_cpython_lib_dir(path: &str, v: &Option) -> bool { let cpython_version_pat = if let Some(v) = v { - format!("python{}", v) + format!("python{v}") } else { "python3.".into() }; @@ -1756,7 +1759,7 @@ fn default_lib_name_unix( ) -> Result { match implementation { PythonImplementation::CPython => match ld_version { - Some(ld_version) => Ok(format!("python{}", ld_version)), + Some(ld_version) => Ok(format!("python{ld_version}")), None => { if version > PythonVersion::PY37 { // PEP 3149 ABI version tags are finally gone @@ -1773,7 +1776,7 @@ fn default_lib_name_unix( } }, PythonImplementation::PyPy => match ld_version { - Some(ld_version) => Ok(format!("pypy{}-c", ld_version)), + Some(ld_version) => Ok(format!("pypy{ld_version}-c")), None => Ok(format!("pypy{}.{}-c", version.major, version.minor)), }, @@ -2002,7 +2005,7 @@ fn unescape(escaped: &str) -> Vec { } } - bytes.push(unhex(chunk[0]) << 4 | unhex(chunk[1])); + bytes.push((unhex(chunk[0]) << 4) | unhex(chunk[1])); } bytes diff --git a/include/pyo3/pyo3-build-config/src/lib.rs b/include/pyo3/pyo3-build-config/src/lib.rs index 9070f6d7..f47c16f4 100644 --- a/include/pyo3/pyo3-build-config/src/lib.rs +++ b/include/pyo3/pyo3-build-config/src/lib.rs @@ -46,7 +46,7 @@ use target_lexicon::OperatingSystem; pub fn use_pyo3_cfgs() { print_expected_cfgs(); for cargo_command in get().build_script_outputs() { - println!("{}", cargo_command) + println!("{cargo_command}") } } @@ -102,12 +102,7 @@ fn _add_python_framework_link_args( ) { if matches!(triple.operating_system, OperatingSystem::Darwin(_)) && link_libpython { if let Some(framework_prefix) = interpreter_config.python_framework_prefix.as_ref() { - writeln!( - writer, - "cargo:rustc-link-arg=-Wl,-rpath,{}", - framework_prefix - ) - .unwrap(); + writeln!(writer, "cargo:rustc-link-arg=-Wl,-rpath,{framework_prefix}").unwrap(); } } } @@ -168,40 +163,36 @@ fn resolve_cross_compile_config_path() -> Option { }) } +/// Helper to print a feature cfg with a minimum rust version required. +fn print_feature_cfg(minor_version_required: u32, cfg: &str) { + let minor_version = rustc_minor_version().unwrap_or(0); + + if minor_version >= minor_version_required { + println!("cargo:rustc-cfg={cfg}"); + } + + // rustc 1.80.0 stabilized `rustc-check-cfg` feature, don't emit before + if minor_version >= 80 { + println!("cargo:rustc-check-cfg=cfg({cfg})"); + } +} + /// Use certain features if we detect the compiler being used supports them. /// /// Features may be removed or added as MSRV gets bumped or new features become available, /// so this function is unstable. #[doc(hidden)] pub fn print_feature_cfgs() { - let rustc_minor_version = rustc_minor_version().unwrap_or(0); - - if rustc_minor_version >= 70 { - println!("cargo:rustc-cfg=rustc_has_once_lock"); - } - - // invalid_from_utf8 lint was added in Rust 1.74 - if rustc_minor_version >= 74 { - println!("cargo:rustc-cfg=invalid_from_utf8_lint"); - } - - if rustc_minor_version >= 79 { - println!("cargo:rustc-cfg=c_str_lit"); - } - + print_feature_cfg(70, "rustc_has_once_lock"); + print_feature_cfg(70, "cargo_toml_lints"); + print_feature_cfg(71, "rustc_has_extern_c_unwind"); + print_feature_cfg(74, "invalid_from_utf8_lint"); + print_feature_cfg(79, "c_str_lit"); // Actually this is available on 1.78, but we should avoid // https://github.com/rust-lang/rust/issues/124651 just in case - if rustc_minor_version >= 79 { - println!("cargo:rustc-cfg=diagnostic_namespace"); - } - - if rustc_minor_version >= 83 { - println!("cargo:rustc-cfg=io_error_more"); - } - - if rustc_minor_version >= 85 { - println!("cargo:rustc-cfg=fn_ptr_eq"); - } + print_feature_cfg(79, "diagnostic_namespace"); + print_feature_cfg(83, "io_error_more"); + print_feature_cfg(85, "fn_ptr_eq"); } /// Registers `pyo3`s config names as reachable cfg expressions @@ -220,14 +211,8 @@ pub fn print_expected_cfgs() { println!("cargo:rustc-check-cfg=cfg(PyPy)"); println!("cargo:rustc-check-cfg=cfg(GraalPy)"); println!("cargo:rustc-check-cfg=cfg(py_sys_config, values(\"Py_DEBUG\", \"Py_REF_DEBUG\", \"Py_TRACE_REFS\", \"COUNT_ALLOCS\"))"); - println!("cargo:rustc-check-cfg=cfg(invalid_from_utf8_lint)"); println!("cargo:rustc-check-cfg=cfg(pyo3_disable_reference_pool)"); println!("cargo:rustc-check-cfg=cfg(pyo3_leak_on_drop_without_reference_pool)"); - println!("cargo:rustc-check-cfg=cfg(diagnostic_namespace)"); - println!("cargo:rustc-check-cfg=cfg(c_str_lit)"); - println!("cargo:rustc-check-cfg=cfg(rustc_has_once_lock)"); - println!("cargo:rustc-check-cfg=cfg(io_error_more)"); - println!("cargo:rustc-check-cfg=cfg(fn_ptr_eq)"); // allow `Py_3_*` cfgs from the minimum supported version up to the // maximum minor version (+1 for development for the next) diff --git a/include/pyo3/pyo3-ffi/ACKNOWLEDGEMENTS b/include/pyo3/pyo3-ffi/ACKNOWLEDGEMENTS index 4502d777..8b20727d 100644 --- a/include/pyo3/pyo3-ffi/ACKNOWLEDGEMENTS +++ b/include/pyo3/pyo3-ffi/ACKNOWLEDGEMENTS @@ -3,4 +3,4 @@ for binary compatibility, with additional metadata to support PyPy. For original implementations please see: - https://github.com/python/cpython - - https://foss.heptapod.net/pypy/pypy + - https://github.com/pypy/pypy diff --git a/include/pyo3/pyo3-ffi/Cargo.toml b/include/pyo3/pyo3-ffi/Cargo.toml index 14a4f958..73ea84ab 100644 --- a/include/pyo3/pyo3-ffi/Cargo.toml +++ b/include/pyo3/pyo3-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pyo3-ffi" -version = "0.23.3" +version = "0.25.0" description = "Python-API bindings for the PyO3 ecosystem" authors = ["PyO3 Project and Contributors "] keywords = ["pyo3", "python", "cpython", "ffi"] @@ -12,7 +12,7 @@ edition = "2021" links = "python" [dependencies] -libc = "0.2" +libc = "0.2.62" [features] @@ -32,7 +32,9 @@ abi3-py38 = ["abi3-py39", "pyo3-build-config/abi3-py38"] abi3-py39 = ["abi3-py310", "pyo3-build-config/abi3-py39"] abi3-py310 = ["abi3-py311", "pyo3-build-config/abi3-py310"] abi3-py311 = ["abi3-py312", "pyo3-build-config/abi3-py311"] -abi3-py312 = ["abi3", "pyo3-build-config/abi3-py312"] +abi3-py312 = ["abi3-py313", "pyo3-build-config/abi3-py312"] +abi3-py313 = ["abi3-py314", "pyo3-build-config/abi3-py313"] +abi3-py314 = ["abi3", "pyo3-build-config/abi3-py314"] # Automatically generates `python3.dll` import libraries for Windows targets. generate-import-lib = ["pyo3-build-config/python3-dll-a"] @@ -41,4 +43,4 @@ generate-import-lib = ["pyo3-build-config/python3-dll-a"] paste = "1" [build-dependencies] -pyo3-build-config = { path = "../pyo3-build-config", features = ["resolve-config"] } +pyo3-build-config = { path = "../pyo3-build-config", version = "=0.25.0", features = ["resolve-config"] } diff --git a/include/pyo3/pyo3-ffi/build.rs b/include/pyo3/pyo3-ffi/build.rs index b0f1c28d..6776cd80 100644 --- a/include/pyo3/pyo3-ffi/build.rs +++ b/include/pyo3/pyo3-ffi/build.rs @@ -4,9 +4,8 @@ use pyo3_build_config::{ cargo_env_var, env_var, errors::Result, is_linking_libpython, resolve_interpreter_config, InterpreterConfig, PythonVersion, }, - warn, BuildFlag, PythonImplementation, + warn, PythonImplementation, }; -use std::ops::Not; /// Minimum Python version PyO3 supports. struct SupportedVersions { @@ -18,15 +17,15 @@ const SUPPORTED_VERSIONS_CPYTHON: SupportedVersions = SupportedVersions { min: PythonVersion { major: 3, minor: 7 }, max: PythonVersion { major: 3, - minor: 13, + minor: 14, }, }; const SUPPORTED_VERSIONS_PYPY: SupportedVersions = SupportedVersions { - min: PythonVersion { major: 3, minor: 7 }, + min: PythonVersion { major: 3, minor: 9 }, max: PythonVersion { major: 3, - minor: 10, + minor: 11, }, }; @@ -57,15 +56,22 @@ fn ensure_python_version(interpreter_config: &InterpreterConfig) -> Result<()> { interpreter_config.version, versions.min, ); - ensure!( - interpreter_config.version <= versions.max || env_var("PYO3_USE_ABI3_FORWARD_COMPATIBILITY").map_or(false, |os_str| os_str == "1"), - "the configured Python interpreter version ({}) is newer than PyO3's maximum supported version ({})\n\ - = help: please check if an updated version of PyO3 is available. Current version: {}\n\ - = help: set PYO3_USE_ABI3_FORWARD_COMPATIBILITY=1 to suppress this check and build anyway using the stable ABI", - interpreter_config.version, - versions.max, - std::env::var("CARGO_PKG_VERSION").unwrap(), - ); + if interpreter_config.version > versions.max { + ensure!(!interpreter_config.is_free_threaded(), + "The configured Python interpreter version ({}) is newer than PyO3's maximum supported version ({})\n\ + = help: please check if an updated version of PyO3 is available. Current version: {}\n\ + = help: The free-threaded build of CPython does not support the limited API so this check cannot be suppressed.", + interpreter_config.version, versions.max, std::env::var("CARGO_PKG_VERSION").unwrap() + ); + ensure!(env_var("PYO3_USE_ABI3_FORWARD_COMPATIBILITY").map_or(false, |os_str| os_str == "1"), + "the configured Python interpreter version ({}) is newer than PyO3's maximum supported version ({})\n\ + = help: please check if an updated version of PyO3 is available. Current version: {}\n\ + = help: set PYO3_USE_ABI3_FORWARD_COMPATIBILITY=1 to suppress this check and build anyway using the stable ABI", + interpreter_config.version, + versions.max, + std::env::var("CARGO_PKG_VERSION").unwrap(), + ); + } } PythonImplementation::PyPy => { let versions = SUPPORTED_VERSIONS_PYPY; @@ -107,10 +113,16 @@ fn ensure_python_version(interpreter_config: &InterpreterConfig) -> Result<()> { if interpreter_config.abi3 { match interpreter_config.implementation { - PythonImplementation::CPython => {} + PythonImplementation::CPython => { + if interpreter_config.is_free_threaded() { + warn!( + "The free-threaded build of CPython does not yet support abi3 so the build artifacts will be version-specific." + ) + } + } PythonImplementation::PyPy => warn!( "PyPy does not yet support abi3 so the build artifacts will be version-specific. \ - See https://foss.heptapod.net/pypy/pypy/-/issues/3397 for more information." + See https://github.com/pypy/pypy/issues/3397 for more information." ), PythonImplementation::GraalPy => warn!( "GraalPy does not support abi3 so the build artifacts will be version-specific." @@ -121,29 +133,6 @@ fn ensure_python_version(interpreter_config: &InterpreterConfig) -> Result<()> { Ok(()) } -fn ensure_gil_enabled(interpreter_config: &InterpreterConfig) -> Result<()> { - let gil_enabled = interpreter_config - .build_flags - .0 - .contains(&BuildFlag::Py_GIL_DISABLED) - .not(); - ensure!( - gil_enabled || std::env::var("UNSAFE_PYO3_BUILD_FREE_THREADED").map_or(false, |os_str| os_str == "1"), - "the Python interpreter was built with the GIL disabled, which is not yet supported by PyO3\n\ - = help: see https://github.com/PyO3/pyo3/issues/4265 for more information\n\ - = help: please check if an updated version of PyO3 is available. Current version: {}\n\ - = help: set UNSAFE_PYO3_BUILD_FREE_THREADED=1 to suppress this check and build anyway for free-threaded Python", - std::env::var("CARGO_PKG_VERSION").unwrap() - ); - if !gil_enabled && interpreter_config.abi3 { - warn!( - "The free-threaded build of CPython does not yet support abi3 so the build artifacts will be version-specific." - ) - } - - Ok(()) -} - fn ensure_target_pointer_width(interpreter_config: &InterpreterConfig) -> Result<()> { if let Some(pointer_width) = interpreter_config.pointer_width { // Try to check whether the target architecture matches the python library @@ -187,7 +176,7 @@ fn emit_link_config(interpreter_config: &InterpreterConfig) -> Result<()> { ); if let Some(lib_dir) = &interpreter_config.lib_dir { - println!("cargo:rustc-link-search=native={}", lib_dir); + println!("cargo:rustc-link-search=native={lib_dir}"); } Ok(()) @@ -209,7 +198,6 @@ fn configure_pyo3() -> Result<()> { ensure_python_version(&interpreter_config)?; ensure_target_pointer_width(&interpreter_config)?; - ensure_gil_enabled(&interpreter_config)?; // Serialize the whole interpreter config into DEP_PYTHON_PYO3_CONFIG env var. interpreter_config.to_cargo_dep_env()?; @@ -219,12 +207,12 @@ fn configure_pyo3() -> Result<()> { } for cfg in interpreter_config.build_script_outputs() { - println!("{}", cfg) + println!("{cfg}") } // Extra lines come last, to support last write wins. for line in &interpreter_config.extra_build_script_lines { - println!("{}", line); + println!("{line}"); } // Emit cfgs like `invalid_from_utf8_lint` diff --git a/include/pyo3/pyo3-ffi/src/abstract_.rs b/include/pyo3/pyo3-ffi/src/abstract_.rs index ce6c9b94..84fb98a1 100644 --- a/include/pyo3/pyo3-ffi/src/abstract_.rs +++ b/include/pyo3/pyo3-ffi/src/abstract_.rs @@ -1,15 +1,23 @@ use crate::object::*; use crate::pyport::Py_ssize_t; +#[cfg(any(Py_3_12, all(Py_3_8, not(Py_LIMITED_API))))] +use libc::size_t; use std::os::raw::{c_char, c_int}; #[inline] -#[cfg(all(not(Py_3_13), not(PyPy)))] // CPython exposed as a function in 3.13, in object.h +#[cfg(all( + not(Py_3_13), // CPython exposed as a function in 3.13, in object.h + not(all(PyPy, not(Py_3_11))) // PyPy exposed as a function until PyPy 3.10, macro in 3.11+ +))] pub unsafe fn PyObject_DelAttrString(o: *mut PyObject, attr_name: *const c_char) -> c_int { PyObject_SetAttrString(o, attr_name, std::ptr::null_mut()) } #[inline] -#[cfg(all(not(Py_3_13), not(PyPy)))] // CPython exposed as a function in 3.13, in object.h +#[cfg(all( + not(Py_3_13), // CPython exposed as a function in 3.13, in object.h + not(all(PyPy, not(Py_3_11))) // PyPy exposed as a function until PyPy 3.10, macro in 3.11+ +))] pub unsafe fn PyObject_DelAttr(o: *mut PyObject, attr_name: *mut PyObject) -> c_int { PyObject_SetAttr(o, attr_name, std::ptr::null_mut()) } @@ -17,6 +25,7 @@ pub unsafe fn PyObject_DelAttr(o: *mut PyObject, attr_name: *mut PyObject) -> c_ extern "C" { #[cfg(all( not(PyPy), + not(GraalPy), any(Py_3_10, all(not(Py_LIMITED_API), Py_3_9)) // Added to python in 3.9 but to limited API in 3.10 ))] #[cfg_attr(PyPy, link_name = "PyPyObject_CallNoArgs")] @@ -70,6 +79,28 @@ extern "C" { method: *mut PyObject, ... ) -> *mut PyObject; +} +#[cfg(any(Py_3_12, all(Py_3_8, not(Py_LIMITED_API))))] +pub const PY_VECTORCALL_ARGUMENTS_OFFSET: size_t = + 1 << (8 * std::mem::size_of::() as size_t - 1); + +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyObject_Vectorcall")] + #[cfg(any(Py_3_12, all(Py_3_11, not(Py_LIMITED_API))))] + pub fn PyObject_Vectorcall( + callable: *mut PyObject, + args: *const *mut PyObject, + nargsf: size_t, + kwnames: *mut PyObject, + ) -> *mut PyObject; + + #[cfg(any(Py_3_12, all(Py_3_9, not(any(Py_LIMITED_API, PyPy, GraalPy)))))] + pub fn PyObject_VectorcallMethod( + name: *mut PyObject, + args: *const *mut PyObject, + nargsf: size_t, + kwnames: *mut PyObject, + ) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyObject_Type")] pub fn PyObject_Type(o: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyObject_Size")] @@ -119,7 +150,11 @@ extern "C" { pub fn PyIter_Next(arg1: *mut PyObject) -> *mut PyObject; #[cfg(all(not(PyPy), Py_3_10))] #[cfg_attr(PyPy, link_name = "PyPyIter_Send")] - pub fn PyIter_Send(iter: *mut PyObject, arg: *mut PyObject, presult: *mut *mut PyObject); + pub fn PyIter_Send( + iter: *mut PyObject, + arg: *mut PyObject, + presult: *mut *mut PyObject, + ) -> PySendResult; #[cfg_attr(PyPy, link_name = "PyPyNumber_Check")] pub fn PyNumber_Check(o: *mut PyObject) -> c_int; diff --git a/include/pyo3/pyo3-ffi/src/bytearrayobject.rs b/include/pyo3/pyo3-ffi/src/bytearrayobject.rs index 24a97bcc..d27dfa8b 100644 --- a/include/pyo3/pyo3-ffi/src/bytearrayobject.rs +++ b/include/pyo3/pyo3-ffi/src/bytearrayobject.rs @@ -17,7 +17,7 @@ pub struct PyByteArrayObject { } #[cfg(any(PyPy, GraalPy, Py_LIMITED_API))] -opaque_struct!(PyByteArrayObject); +opaque_struct!(pub PyByteArrayObject); #[cfg_attr(windows, link(name = "pythonXY"))] extern "C" { diff --git a/include/pyo3/pyo3-ffi/src/code.rs b/include/pyo3/pyo3-ffi/src/code.rs index d28f68cd..296b17f6 100644 --- a/include/pyo3/pyo3-ffi/src/code.rs +++ b/include/pyo3/pyo3-ffi/src/code.rs @@ -1,4 +1,4 @@ // This header doesn't exist in CPython, but Include/cpython/code.h does. We add // this here so that PyCodeObject has a definition under the limited API. -opaque_struct!(PyCodeObject); +opaque_struct!(pub PyCodeObject); diff --git a/include/pyo3/pyo3-ffi/src/compat/mod.rs b/include/pyo3/pyo3-ffi/src/compat/mod.rs index 11f29128..044ea467 100644 --- a/include/pyo3/pyo3-ffi/src/compat/mod.rs +++ b/include/pyo3/pyo3-ffi/src/compat/mod.rs @@ -52,8 +52,10 @@ macro_rules! compat_function { mod py_3_10; mod py_3_13; +mod py_3_14; mod py_3_9; pub use self::py_3_10::*; pub use self::py_3_13::*; +pub use self::py_3_14::*; pub use self::py_3_9::*; diff --git a/include/pyo3/pyo3-ffi/src/compat/py_3_10.rs b/include/pyo3/pyo3-ffi/src/compat/py_3_10.rs index c6e8c2cb..d962fb3b 100644 --- a/include/pyo3/pyo3-ffi/src/compat/py_3_10.rs +++ b/include/pyo3/pyo3-ffi/src/compat/py_3_10.rs @@ -17,3 +17,29 @@ compat_function!( obj } ); + +compat_function!( + originally_defined_for(Py_3_10); + + #[inline] + pub unsafe fn PyModule_AddObjectRef( + module: *mut crate::PyObject, + name: *const std::os::raw::c_char, + value: *mut crate::PyObject, + ) -> std::os::raw::c_int { + if value.is_null() && crate::PyErr_Occurred().is_null() { + crate::PyErr_SetString( + crate::PyExc_SystemError, + c_str!("PyModule_AddObjectRef() must be called with an exception raised if value is NULL").as_ptr(), + ); + return -1; + } + + crate::Py_XINCREF(value); + let result = crate::PyModule_AddObject(module, name, value); + if result < 0 { + crate::Py_XDECREF(value); + } + result + } +); diff --git a/include/pyo3/pyo3-ffi/src/compat/py_3_13.rs b/include/pyo3/pyo3-ffi/src/compat/py_3_13.rs index 59289cb7..96c90e7e 100644 --- a/include/pyo3/pyo3-ffi/src/compat/py_3_13.rs +++ b/include/pyo3/pyo3-ffi/src/compat/py_3_13.rs @@ -104,3 +104,18 @@ compat_function!( crate::PyList_SetSlice(list, 0, crate::PY_SSIZE_T_MAX, std::ptr::null_mut()) } ); + +compat_function!( + originally_defined_for(Py_3_13); + + #[inline] + pub unsafe fn PyModule_Add( + module: *mut crate::PyObject, + name: *const std::os::raw::c_char, + value: *mut crate::PyObject, + ) -> std::os::raw::c_int { + let result = crate::compat::PyModule_AddObjectRef(module, name, value); + crate::Py_XDECREF(value); + result + } +); diff --git a/include/pyo3/pyo3-ffi/src/compat/py_3_14.rs b/include/pyo3/pyo3-ffi/src/compat/py_3_14.rs new file mode 100644 index 00000000..6fdaef17 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/compat/py_3_14.rs @@ -0,0 +1,26 @@ +compat_function!( + originally_defined_for(all(Py_3_14, not(Py_LIMITED_API))); + + #[inline] + pub unsafe fn Py_HashBuffer( + ptr: *const std::ffi::c_void, + len: crate::Py_ssize_t, + ) -> crate::Py_hash_t { + #[cfg(not(any(Py_LIMITED_API, PyPy)))] + { + crate::_Py_HashBytes(ptr, len) + } + + #[cfg(any(Py_LIMITED_API, PyPy))] + { + let bytes = crate::PyBytes_FromStringAndSize(ptr as *const std::os::raw::c_char, len); + if bytes.is_null() { + -1 + } else { + let result = crate::PyObject_Hash(bytes); + crate::Py_DECREF(bytes); + result + } + } + } +); diff --git a/include/pyo3/pyo3-ffi/src/cpython/abstract_.rs b/include/pyo3/pyo3-ffi/src/cpython/abstract_.rs index 477ad02b..6ada1a75 100644 --- a/include/pyo3/pyo3-ffi/src/cpython/abstract_.rs +++ b/include/pyo3/pyo3-ffi/src/cpython/abstract_.rs @@ -1,5 +1,5 @@ use crate::{PyObject, Py_ssize_t}; -#[cfg(not(all(Py_3_11, GraalPy)))] +#[cfg(any(all(Py_3_8, not(any(PyPy, GraalPy))), not(Py_3_11)))] use std::os::raw::c_char; use std::os::raw::c_int; @@ -41,8 +41,8 @@ extern "C" { ) -> *mut PyObject; } -#[cfg(Py_3_8)] -pub const PY_VECTORCALL_ARGUMENTS_OFFSET: size_t = +#[cfg(Py_3_8)] // NB exported as public in abstract.rs from 3.12 +const PY_VECTORCALL_ARGUMENTS_OFFSET: size_t = 1 << (8 * std::mem::size_of::() as size_t - 1); #[cfg(Py_3_8)] @@ -91,7 +91,7 @@ pub unsafe fn _PyObject_VectorcallTstate( } } -#[cfg(all(Py_3_8, not(any(PyPy, GraalPy))))] +#[cfg(all(Py_3_8, not(any(PyPy, GraalPy, Py_3_11))))] // exported as a function from 3.11, see abstract.rs #[inline(always)] pub unsafe fn PyObject_Vectorcall( callable: *mut PyObject, @@ -103,16 +103,6 @@ pub unsafe fn PyObject_Vectorcall( } extern "C" { - #[cfg(all(PyPy, Py_3_8))] - #[cfg_attr(not(Py_3_9), link_name = "_PyPyObject_Vectorcall")] - #[cfg_attr(Py_3_9, link_name = "PyPyObject_Vectorcall")] - pub fn PyObject_Vectorcall( - callable: *mut PyObject, - args: *const *mut PyObject, - nargsf: size_t, - kwnames: *mut PyObject, - ) -> *mut PyObject; - #[cfg(Py_3_8)] #[cfg_attr( all(not(any(PyPy, GraalPy)), not(Py_3_9)), @@ -187,23 +177,13 @@ pub unsafe fn PyObject_CallOneArg(func: *mut PyObject, arg: *mut PyObject) -> *m _PyObject_VectorcallTstate(tstate, func, args, nargsf, std::ptr::null_mut()) } -extern "C" { - #[cfg(all(Py_3_9, not(any(PyPy, GraalPy))))] - pub fn PyObject_VectorcallMethod( - name: *mut PyObject, - args: *const *mut PyObject, - nargsf: size_t, - kwnames: *mut PyObject, - ) -> *mut PyObject; -} - #[cfg(all(Py_3_9, not(any(PyPy, GraalPy))))] #[inline(always)] pub unsafe fn PyObject_CallMethodNoArgs( self_: *mut PyObject, name: *mut PyObject, ) -> *mut PyObject { - PyObject_VectorcallMethod( + crate::PyObject_VectorcallMethod( name, &self_, 1 | PY_VECTORCALL_ARGUMENTS_OFFSET, @@ -220,7 +200,7 @@ pub unsafe fn PyObject_CallMethodOneArg( ) -> *mut PyObject { let args = [self_, arg]; assert!(!arg.is_null()); - PyObject_VectorcallMethod( + crate::PyObject_VectorcallMethod( name, args.as_ptr(), 2 | PY_VECTORCALL_ARGUMENTS_OFFSET, diff --git a/include/pyo3/pyo3-ffi/src/cpython/bytesobject.rs b/include/pyo3/pyo3-ffi/src/cpython/bytesobject.rs index 306702de..dd78e646 100644 --- a/include/pyo3/pyo3-ffi/src/cpython/bytesobject.rs +++ b/include/pyo3/pyo3-ffi/src/cpython/bytesobject.rs @@ -17,7 +17,7 @@ pub struct PyBytesObject { } #[cfg(any(PyPy, GraalPy, Py_LIMITED_API))] -opaque_struct!(PyBytesObject); +opaque_struct!(pub PyBytesObject); extern "C" { #[cfg_attr(PyPy, link_name = "_PyPyBytes_Resize")] diff --git a/include/pyo3/pyo3-ffi/src/cpython/code.rs b/include/pyo3/pyo3-ffi/src/cpython/code.rs index 230096ca..3d47a1bc 100644 --- a/include/pyo3/pyo3-ffi/src/cpython/code.rs +++ b/include/pyo3/pyo3-ffi/src/cpython/code.rs @@ -1,207 +1,43 @@ use crate::object::*; use crate::pyport::Py_ssize_t; -#[allow(unused_imports)] -use std::os::raw::{c_char, c_int, c_short, c_uchar, c_void}; +#[cfg(not(GraalPy))] +use std::os::raw::c_char; +use std::os::raw::{c_int, c_void}; #[cfg(not(any(PyPy, GraalPy)))] use std::ptr::addr_of_mut; -#[cfg(all(Py_3_8, not(any(PyPy, GraalPy)), not(Py_3_11)))] -opaque_struct!(_PyOpcache); +// skipped private _PY_MONITORING_LOCAL_EVENTS +// skipped private _PY_MONITORING_UNGROUPED_EVENTS +// skipped private _PY_MONITORING_EVENTS -#[cfg(Py_3_12)] -pub const _PY_MONITORING_LOCAL_EVENTS: usize = 10; -#[cfg(Py_3_12)] -pub const _PY_MONITORING_UNGROUPED_EVENTS: usize = 15; -#[cfg(Py_3_12)] -pub const _PY_MONITORING_EVENTS: usize = 17; +// skipped private _PyLocalMonitors +// skipped private _Py_GlobalMonitors -#[cfg(Py_3_12)] -#[repr(C)] -#[derive(Clone, Copy)] -pub struct _Py_LocalMonitors { - pub tools: [u8; if cfg!(Py_3_13) { - _PY_MONITORING_LOCAL_EVENTS - } else { - _PY_MONITORING_UNGROUPED_EVENTS - }], -} - -#[cfg(Py_3_12)] -#[repr(C)] -#[derive(Clone, Copy)] -pub struct _Py_GlobalMonitors { - pub tools: [u8; _PY_MONITORING_UNGROUPED_EVENTS], -} - -// skipped _Py_CODEUNIT +// skipped private _Py_CODEUNIT -// skipped _Py_OPCODE -// skipped _Py_OPARG +// skipped private _Py_OPCODE +// skipped private _Py_OPARG -// skipped _py_make_codeunit +// skipped private _py_make_codeunit -// skipped _py_set_opcode +// skipped private _py_set_opcode -// skipped _Py_MAKE_CODEUNIT -// skipped _Py_SET_OPCODE - -#[cfg(Py_3_12)] -#[repr(C)] -#[derive(Copy, Clone)] -pub struct _PyCoCached { - pub _co_code: *mut PyObject, - pub _co_varnames: *mut PyObject, - pub _co_cellvars: *mut PyObject, - pub _co_freevars: *mut PyObject, -} - -#[cfg(Py_3_12)] -#[repr(C)] -#[derive(Copy, Clone)] -pub struct _PyCoLineInstrumentationData { - pub original_opcode: u8, - pub line_delta: i8, -} +// skipped private _Py_MAKE_CODEUNIT +// skipped private _Py_SET_OPCODE -#[cfg(Py_3_12)] -#[repr(C)] -#[derive(Copy, Clone)] -pub struct _PyCoMonitoringData { - pub local_monitors: _Py_LocalMonitors, - pub active_monitors: _Py_LocalMonitors, - pub tools: *mut u8, - pub lines: *mut _PyCoLineInstrumentationData, - pub line_tools: *mut u8, - pub per_instruction_opcodes: *mut u8, - pub per_instruction_tools: *mut u8, -} - -#[cfg(all(not(any(PyPy, GraalPy)), not(Py_3_7)))] -opaque_struct!(PyCodeObject); - -#[cfg(all(not(any(PyPy, GraalPy)), Py_3_7, not(Py_3_8)))] -#[repr(C)] -pub struct PyCodeObject { - pub ob_base: PyObject, - pub co_argcount: c_int, - pub co_kwonlyargcount: c_int, - pub co_nlocals: c_int, - pub co_stacksize: c_int, - pub co_flags: c_int, - pub co_firstlineno: c_int, - pub co_code: *mut PyObject, - pub co_consts: *mut PyObject, - pub co_names: *mut PyObject, - pub co_varnames: *mut PyObject, - pub co_freevars: *mut PyObject, - pub co_cellvars: *mut PyObject, - pub co_cell2arg: *mut Py_ssize_t, - pub co_filename: *mut PyObject, - pub co_name: *mut PyObject, - pub co_lnotab: *mut PyObject, - pub co_zombieframe: *mut c_void, - pub co_weakreflist: *mut PyObject, - pub co_extra: *mut c_void, -} - -#[cfg(Py_3_13)] -opaque_struct!(_PyExecutorArray); - -#[cfg(all(not(any(PyPy, GraalPy)), Py_3_8, not(Py_3_11)))] -#[repr(C)] -pub struct PyCodeObject { - pub ob_base: PyObject, - pub co_argcount: c_int, - pub co_posonlyargcount: c_int, - pub co_kwonlyargcount: c_int, - pub co_nlocals: c_int, - pub co_stacksize: c_int, - pub co_flags: c_int, - pub co_firstlineno: c_int, - pub co_code: *mut PyObject, - pub co_consts: *mut PyObject, - pub co_names: *mut PyObject, - pub co_varnames: *mut PyObject, - pub co_freevars: *mut PyObject, - pub co_cellvars: *mut PyObject, - pub co_cell2arg: *mut Py_ssize_t, - pub co_filename: *mut PyObject, - pub co_name: *mut PyObject, - #[cfg(not(Py_3_10))] - pub co_lnotab: *mut PyObject, - #[cfg(Py_3_10)] - pub co_linetable: *mut PyObject, - pub co_zombieframe: *mut c_void, - pub co_weakreflist: *mut PyObject, - pub co_extra: *mut c_void, - pub co_opcache_map: *mut c_uchar, - pub co_opcache: *mut _PyOpcache, - pub co_opcache_flag: c_int, - pub co_opcache_size: c_uchar, -} +// skipped private _PyCoCached +// skipped private _PyCoLineInstrumentationData +// skipped private _PyCoMontoringData -#[cfg(all(not(any(PyPy, GraalPy)), Py_3_11))] -#[repr(C)] -pub struct PyCodeObject { - pub ob_base: PyVarObject, - pub co_consts: *mut PyObject, - pub co_names: *mut PyObject, - pub co_exceptiontable: *mut PyObject, - pub co_flags: c_int, - #[cfg(not(Py_3_12))] - pub co_warmup: c_int, +// skipped private _PyExecutorArray - pub co_argcount: c_int, - pub co_posonlyargcount: c_int, - pub co_kwonlyargcount: c_int, - pub co_stacksize: c_int, - pub co_firstlineno: c_int, - - pub co_nlocalsplus: c_int, - #[cfg(Py_3_12)] - pub co_framesize: c_int, - pub co_nlocals: c_int, - #[cfg(not(Py_3_12))] - pub co_nplaincellvars: c_int, - pub co_ncellvars: c_int, - pub co_nfreevars: c_int, - #[cfg(Py_3_12)] - pub co_version: u32, - - pub co_localsplusnames: *mut PyObject, - pub co_localspluskinds: *mut PyObject, - pub co_filename: *mut PyObject, - pub co_name: *mut PyObject, - pub co_qualname: *mut PyObject, - pub co_linetable: *mut PyObject, - pub co_weakreflist: *mut PyObject, - #[cfg(not(Py_3_12))] - pub _co_code: *mut PyObject, - #[cfg(not(Py_3_12))] - pub _co_linearray: *mut c_char, - #[cfg(Py_3_13)] - pub co_executors: *mut _PyExecutorArray, - #[cfg(Py_3_12)] - pub _co_cached: *mut _PyCoCached, - #[cfg(Py_3_12)] - pub _co_instrumentation_version: u64, - #[cfg(Py_3_12)] - pub _co_monitoring: *mut _PyCoMonitoringData, - pub _co_firsttraceable: c_int, - pub co_extra: *mut c_void, - pub co_code_adaptive: [c_char; 1], -} - -#[cfg(PyPy)] -#[repr(C)] -pub struct PyCodeObject { - pub ob_base: PyObject, - pub co_name: *mut PyObject, - pub co_filename: *mut PyObject, - pub co_argcount: c_int, - pub co_flags: c_int, -} +opaque_struct!( + #[doc = "A Python code object.\n"] + #[doc = "\n"] + #[doc = "`pyo3-ffi` does not expose the contents of this struct, as it has no stability guarantees."] + pub PyCodeObject +); /* Masks for co_flags */ pub const CO_OPTIMIZED: c_int = 0x0001; @@ -247,28 +83,14 @@ pub unsafe fn PyCode_Check(op: *mut PyObject) -> c_int { (Py_TYPE(op) == addr_of_mut!(PyCode_Type)) as c_int } -#[inline] -#[cfg(all(not(any(PyPy, GraalPy)), Py_3_10, not(Py_3_11)))] -pub unsafe fn PyCode_GetNumFree(op: *mut PyCodeObject) -> Py_ssize_t { - crate::PyTuple_GET_SIZE((*op).co_freevars) -} - -#[inline] -#[cfg(all(not(Py_3_10), Py_3_11, not(any(PyPy, GraalPy))))] -pub unsafe fn PyCode_GetNumFree(op: *mut PyCodeObject) -> c_int { - (*op).co_nfreevars -} - extern "C" { #[cfg(PyPy)] #[link_name = "PyPyCode_Check"] pub fn PyCode_Check(op: *mut PyObject) -> c_int; - - #[cfg(PyPy)] - #[link_name = "PyPyCode_GetNumFree"] - pub fn PyCode_GetNumFree(op: *mut PyCodeObject) -> Py_ssize_t; } +// skipped PyCode_GetNumFree (requires knowledge of code object layout) + extern "C" { #[cfg(not(GraalPy))] #[cfg_attr(PyPy, link_name = "PyPyCode_New")] diff --git a/include/pyo3/pyo3-ffi/src/cpython/compile.rs b/include/pyo3/pyo3-ffi/src/cpython/compile.rs index 79f06c92..078b5e0d 100644 --- a/include/pyo3/pyo3-ffi/src/cpython/compile.rs +++ b/include/pyo3/pyo3-ffi/src/cpython/compile.rs @@ -6,19 +6,21 @@ use crate::pyarena::*; use crate::pythonrun::*; #[cfg(not(any(PyPy, Py_3_10)))] use crate::PyCodeObject; +use crate::INT_MAX; #[cfg(not(any(PyPy, Py_3_10)))] use std::os::raw::c_char; use std::os::raw::c_int; -// skipped non-limited PyCF_MASK -// skipped non-limited PyCF_MASK_OBSOLETE -// skipped non-limited PyCF_SOURCE_IS_UTF8 -// skipped non-limited PyCF_DONT_IMPLY_DEDENT -// skipped non-limited PyCF_ONLY_AST -// skipped non-limited PyCF_IGNORE_COOKIE -// skipped non-limited PyCF_TYPE_COMMENTS -// skipped non-limited PyCF_ALLOW_TOP_LEVEL_AWAIT -// skipped non-limited PyCF_COMPILE_MASK +// skipped PyCF_MASK +// skipped PyCF_MASK_OBSOLETE +// skipped PyCF_SOURCE_IS_UTF8 +// skipped PyCF_DONT_IMPLY_DEDENT +// skipped PyCF_ONLY_AST +// skipped PyCF_IGNORE_COOKIE +// skipped PyCF_TYPE_COMMENTS +// skipped PyCF_ALLOW_TOP_LEVEL_AWAIT +// skipped PyCF_OPTIMIZED_AST +// skipped PyCF_COMPILE_MASK #[repr(C)] #[derive(Copy, Clone)] @@ -28,31 +30,23 @@ pub struct PyCompilerFlags { pub cf_feature_version: c_int, } -// skipped non-limited _PyCompilerFlags_INIT +// skipped _PyCompilerFlags_INIT -#[cfg(all(Py_3_12, not(any(Py_3_13, PyPy, GraalPy))))] -#[repr(C)] -#[derive(Copy, Clone)] -pub struct _PyCompilerSrcLocation { - pub lineno: c_int, - pub end_lineno: c_int, - pub col_offset: c_int, - pub end_col_offset: c_int, -} - -// skipped SRC_LOCATION_FROM_AST - -#[cfg(not(any(PyPy, GraalPy, Py_3_13)))] +// NB this type technically existed in the header until 3.13, when it was +// moved to the internal CPython headers. +// +// We choose not to expose it in the public API past 3.10, as it is +// not used in the public API past that point. +#[cfg(not(any(PyPy, GraalPy, Py_3_10)))] #[repr(C)] #[derive(Copy, Clone)] pub struct PyFutureFeatures { pub ff_features: c_int, - #[cfg(not(Py_3_12))] pub ff_lineno: c_int, - #[cfg(Py_3_12)] - pub ff_location: _PyCompilerSrcLocation, } +// FIXME: these constants should probably be &CStr, if they are used at all + pub const FUTURE_NESTED_SCOPES: &str = "nested_scopes"; pub const FUTURE_GENERATORS: &str = "generators"; pub const FUTURE_DIVISION: &str = "division"; @@ -62,13 +56,12 @@ pub const FUTURE_PRINT_FUNCTION: &str = "print_function"; pub const FUTURE_UNICODE_LITERALS: &str = "unicode_literals"; pub const FUTURE_BARRY_AS_BDFL: &str = "barry_as_FLUFL"; pub const FUTURE_GENERATOR_STOP: &str = "generator_stop"; -// skipped non-limited FUTURE_ANNOTATIONS +pub const FUTURE_ANNOTATIONS: &str = "annotations"; +#[cfg(not(any(PyPy, GraalPy, Py_3_10)))] extern "C" { - #[cfg(not(any(PyPy, Py_3_10)))] pub fn PyNode_Compile(arg1: *mut _node, arg2: *const c_char) -> *mut PyCodeObject; - #[cfg(not(any(PyPy, Py_3_10)))] pub fn PyAST_CompileEx( _mod: *mut _mod, filename: *const c_char, @@ -77,7 +70,6 @@ extern "C" { arena: *mut PyArena, ) -> *mut PyCodeObject; - #[cfg(not(any(PyPy, Py_3_10)))] pub fn PyAST_CompileObject( _mod: *mut _mod, filename: *mut PyObject, @@ -86,23 +78,20 @@ extern "C" { arena: *mut PyArena, ) -> *mut PyCodeObject; - #[cfg(not(any(PyPy, Py_3_10)))] pub fn PyFuture_FromAST(_mod: *mut _mod, filename: *const c_char) -> *mut PyFutureFeatures; - #[cfg(not(any(PyPy, Py_3_10)))] pub fn PyFuture_FromASTObject( _mod: *mut _mod, filename: *mut PyObject, ) -> *mut PyFutureFeatures; +} - // skipped non-limited _Py_Mangle - // skipped non-limited PY_INVALID_STACK_EFFECT +pub const PY_INVALID_STACK_EFFECT: c_int = INT_MAX; + +extern "C" { pub fn PyCompile_OpcodeStackEffect(opcode: c_int, oparg: c_int) -> c_int; #[cfg(Py_3_8)] pub fn PyCompile_OpcodeStackEffectWithJump(opcode: c_int, oparg: c_int, jump: c_int) -> c_int; - - // skipped non-limited _PyASTOptimizeState - // skipped non-limited _PyAST_Optimize } diff --git a/include/pyo3/pyo3-ffi/src/cpython/critical_section.rs b/include/pyo3/pyo3-ffi/src/cpython/critical_section.rs index 1fc6591c..808dba87 100644 --- a/include/pyo3/pyo3-ffi/src/cpython/critical_section.rs +++ b/include/pyo3/pyo3-ffi/src/cpython/critical_section.rs @@ -9,16 +9,6 @@ pub struct PyCriticalSection { _cs_mutex: *mut PyMutex, } -#[cfg(Py_GIL_DISABLED)] -impl Default for PyCriticalSection { - fn default() -> Self { - PyCriticalSection { - _cs_prev: 0, - _cs_mutex: core::ptr::null_mut(), - } - } -} - #[repr(C)] #[cfg(Py_GIL_DISABLED)] pub struct PyCriticalSection2 { @@ -27,10 +17,10 @@ pub struct PyCriticalSection2 { } #[cfg(not(Py_GIL_DISABLED))] -opaque_struct!(PyCriticalSection); +opaque_struct!(pub PyCriticalSection); #[cfg(not(Py_GIL_DISABLED))] -opaque_struct!(PyCriticalSection2); +opaque_struct!(pub PyCriticalSection2); extern "C" { pub fn PyCriticalSection_Begin(c: *mut PyCriticalSection, op: *mut PyObject); diff --git a/include/pyo3/pyo3-ffi/src/cpython/descrobject.rs b/include/pyo3/pyo3-ffi/src/cpython/descrobject.rs index 1b5ee466..7cef9bdb 100644 --- a/include/pyo3/pyo3-ffi/src/cpython/descrobject.rs +++ b/include/pyo3/pyo3-ffi/src/cpython/descrobject.rs @@ -69,10 +69,7 @@ pub struct PyWrapperDescrObject { pub d_wrapped: *mut c_void, } -#[cfg_attr(windows, link(name = "pythonXY"))] -extern "C" { - pub static mut _PyMethodWrapper_Type: PyTypeObject; -} +// skipped _PyMethodWrapper_Type // skipped non-limited PyDescr_NewWrapper // skipped non-limited PyDescr_IsData diff --git a/include/pyo3/pyo3-ffi/src/cpython/dictobject.rs b/include/pyo3/pyo3-ffi/src/cpython/dictobject.rs index f67a7725..34b66a96 100644 --- a/include/pyo3/pyo3-ffi/src/cpython/dictobject.rs +++ b/include/pyo3/pyo3-ffi/src/cpython/dictobject.rs @@ -2,10 +2,10 @@ use crate::object::*; use crate::pyport::Py_ssize_t; use std::os::raw::c_int; -opaque_struct!(PyDictKeysObject); +opaque_struct!(pub PyDictKeysObject); #[cfg(Py_3_11)] -opaque_struct!(PyDictValues); +opaque_struct!(pub PyDictValues); #[cfg(not(GraalPy))] #[repr(C)] @@ -17,7 +17,10 @@ pub struct PyDictObject { Py_3_12, deprecated(note = "Deprecated in Python 3.12 and will be removed in the future.") )] + #[cfg(not(Py_3_14))] pub ma_version_tag: u64, + #[cfg(Py_3_14)] + _ma_watcher_tag: u64, pub ma_keys: *mut PyDictKeysObject, #[cfg(not(Py_3_11))] pub ma_values: *mut *mut PyObject, @@ -36,15 +39,6 @@ extern "C" { item: *mut PyObject, hash: crate::Py_hash_t, ) -> c_int; - - #[cfg(Py_3_13)] - pub fn _PyDict_SetItem_KnownHash_LockHeld( - mp: *mut PyDictObject, - name: *mut PyObject, - value: *mut PyObject, - hash: crate::Py_hash_t, - ) -> c_int; - // skipped _PyDict_DelItem_KnownHash // skipped _PyDict_DelItemIf // skipped _PyDict_NewKeysForClass diff --git a/include/pyo3/pyo3-ffi/src/cpython/frameobject.rs b/include/pyo3/pyo3-ffi/src/cpython/frameobject.rs index 6d2346f9..993e93c8 100644 --- a/include/pyo3/pyo3-ffi/src/cpython/frameobject.rs +++ b/include/pyo3/pyo3-ffi/src/cpython/frameobject.rs @@ -53,7 +53,7 @@ pub struct PyFrameObject { } #[cfg(any(PyPy, GraalPy, Py_3_11))] -opaque_struct!(PyFrameObject); +opaque_struct!(pub PyFrameObject); // skipped _PyFrame_IsRunnable // skipped _PyFrame_IsExecuting @@ -89,7 +89,8 @@ extern "C" { pub fn PyFrame_FastToLocals(f: *mut PyFrameObject); // skipped _PyFrame_DebugMallocStats - // skipped PyFrame_GetBack + #[cfg(all(Py_3_9, not(PyPy)))] + pub fn PyFrame_GetBack(f: *mut PyFrameObject) -> *mut PyFrameObject; #[cfg(not(Py_3_9))] pub fn PyFrame_ClearFreeList() -> c_int; diff --git a/include/pyo3/pyo3-ffi/src/cpython/funcobject.rs b/include/pyo3/pyo3-ffi/src/cpython/funcobject.rs index 25de30d5..cd2052de 100644 --- a/include/pyo3/pyo3-ffi/src/cpython/funcobject.rs +++ b/include/pyo3/pyo3-ffi/src/cpython/funcobject.rs @@ -41,6 +41,8 @@ pub struct PyFunctionObject { pub func_weakreflist: *mut PyObject, pub func_module: *mut PyObject, pub func_annotations: *mut PyObject, + #[cfg(Py_3_14)] + pub func_annotate: *mut PyObject, #[cfg(Py_3_12)] pub func_typeparams: *mut PyObject, pub vectorcall: Option, diff --git a/include/pyo3/pyo3-ffi/src/cpython/genobject.rs b/include/pyo3/pyo3-ffi/src/cpython/genobject.rs index 4be310a8..92f14d59 100644 --- a/include/pyo3/pyo3-ffi/src/cpython/genobject.rs +++ b/include/pyo3/pyo3-ffi/src/cpython/genobject.rs @@ -1,13 +1,11 @@ use crate::object::*; use crate::PyFrameObject; -#[cfg(not(any(PyPy, GraalPy)))] -use crate::_PyErr_StackItem; -#[cfg(all(Py_3_11, not(GraalPy)))] +#[cfg(all(Py_3_11, not(any(PyPy, GraalPy, Py_3_14))))] use std::os::raw::c_char; use std::os::raw::c_int; use std::ptr::addr_of_mut; -#[cfg(not(any(PyPy, GraalPy)))] +#[cfg(not(any(PyPy, GraalPy, Py_3_14)))] #[repr(C)] pub struct PyGenObject { pub ob_base: PyObject, @@ -20,7 +18,7 @@ pub struct PyGenObject { pub gi_weakreflist: *mut PyObject, pub gi_name: *mut PyObject, pub gi_qualname: *mut PyObject, - pub gi_exc_state: _PyErr_StackItem, + pub gi_exc_state: crate::cpython::pystate::_PyErr_StackItem, #[cfg(Py_3_11)] pub gi_origin_or_finalizer: *mut PyObject, #[cfg(Py_3_11)] @@ -35,6 +33,9 @@ pub struct PyGenObject { pub gi_iframe: [*mut PyObject; 1], } +#[cfg(all(Py_3_14, not(any(PyPy, GraalPy))))] +opaque_struct!(pub PyGenObject); + #[cfg_attr(windows, link(name = "pythonXY"))] extern "C" { pub static mut PyGen_Type: PyTypeObject; @@ -67,9 +68,10 @@ extern "C" { #[cfg_attr(windows, link(name = "pythonXY"))] extern "C" { pub static mut PyCoro_Type: PyTypeObject; - pub static mut _PyCoroWrapper_Type: PyTypeObject; } +// skipped _PyCoroWrapper_Type + #[inline] pub unsafe fn PyCoro_CheckExact(op: *mut PyObject) -> c_int { PyObject_TypeCheck(op, addr_of_mut!(PyCoro_Type)) diff --git a/include/pyo3/pyo3-ffi/src/cpython/import.rs b/include/pyo3/pyo3-ffi/src/cpython/import.rs index 697d68a4..c8ef5ab4 100644 --- a/include/pyo3/pyo3-ffi/src/cpython/import.rs +++ b/include/pyo3/pyo3-ffi/src/cpython/import.rs @@ -65,10 +65,8 @@ pub struct _frozen { extern "C" { #[cfg(not(PyPy))] pub static mut PyImport_FrozenModules: *const _frozen; - #[cfg(all(not(PyPy), Py_3_11))] - pub static mut _PyImport_FrozenBootstrap: *const _frozen; - #[cfg(all(not(PyPy), Py_3_11))] - pub static mut _PyImport_FrozenStdlib: *const _frozen; - #[cfg(all(not(PyPy), Py_3_11))] - pub static mut _PyImport_FrozenTest: *const _frozen; } + +// skipped _PyImport_FrozenBootstrap +// skipped _PyImport_FrozenStdlib +// skipped _PyImport_FrozenTest diff --git a/include/pyo3/pyo3-ffi/src/cpython/initconfig.rs b/include/pyo3/pyo3-ffi/src/cpython/initconfig.rs index 321d200e..076981f1 100644 --- a/include/pyo3/pyo3-ffi/src/cpython/initconfig.rs +++ b/include/pyo3/pyo3-ffi/src/cpython/initconfig.rs @@ -93,6 +93,8 @@ pub struct PyConfig { pub tracemalloc: c_int, #[cfg(Py_3_12)] pub perf_profiling: c_int, + #[cfg(Py_3_14)] + pub remote_debug: c_int, pub import_time: c_int, #[cfg(Py_3_11)] pub code_debug_ranges: c_int, @@ -141,10 +143,18 @@ pub struct PyConfig { pub safe_path: c_int, #[cfg(Py_3_12)] pub int_max_str_digits: c_int, + #[cfg(Py_3_14)] + pub thread_inherit_context: c_int, + #[cfg(Py_3_14)] + pub context_aware_warnings: c_int, + #[cfg(all(Py_3_14, target_os = "macos"))] + pub use_system_logger: c_int, #[cfg(Py_3_13)] pub cpu_count: c_int, #[cfg(Py_GIL_DISABLED)] pub enable_gil: c_int, + #[cfg(all(Py_3_14, Py_GIL_DISABLED))] + pub tlbc_enabled: c_int, pub pathconfig_warnings: c_int, #[cfg(Py_3_10)] pub program_name: *mut wchar_t, diff --git a/include/pyo3/pyo3-ffi/src/cpython/lock.rs b/include/pyo3/pyo3-ffi/src/cpython/lock.rs index c451666e..6c80b00d 100644 --- a/include/pyo3/pyo3-ffi/src/cpython/lock.rs +++ b/include/pyo3/pyo3-ffi/src/cpython/lock.rs @@ -8,15 +8,6 @@ pub struct PyMutex { pub(crate) _pin: PhantomPinned, } -impl PyMutex { - pub const fn new() -> PyMutex { - PyMutex { - _bits: AtomicU8::new(0), - _pin: PhantomPinned, - } - } -} - extern "C" { pub fn PyMutex_Lock(m: *mut PyMutex); pub fn PyMutex_Unlock(m: *mut PyMutex); diff --git a/include/pyo3/pyo3-ffi/src/cpython/longobject.rs b/include/pyo3/pyo3-ffi/src/cpython/longobject.rs index 6063575e..45acaae5 100644 --- a/include/pyo3/pyo3-ffi/src/cpython/longobject.rs +++ b/include/pyo3/pyo3-ffi/src/cpython/longobject.rs @@ -61,18 +61,6 @@ extern "C" { is_signed: c_int, ) -> *mut PyObject; - #[cfg(Py_3_13)] - #[cfg_attr(PyPy, link_name = "_PyPyLong_AsByteArrayO")] - pub fn _PyLong_AsByteArray( - v: *mut PyLongObject, - bytes: *mut c_uchar, - n: size_t, - little_endian: c_int, - is_signed: c_int, - with_exceptions: c_int, - ) -> c_int; - - #[cfg(not(Py_3_13))] #[cfg_attr(PyPy, link_name = "_PyPyLong_AsByteArrayO")] pub fn _PyLong_AsByteArray( v: *mut PyLongObject, diff --git a/include/pyo3/pyo3-ffi/src/cpython/mod.rs b/include/pyo3/pyo3-ffi/src/cpython/mod.rs index fe909f0c..adaf8bc8 100644 --- a/include/pyo3/pyo3-ffi/src/cpython/mod.rs +++ b/include/pyo3/pyo3-ffi/src/cpython/mod.rs @@ -38,6 +38,7 @@ pub(crate) mod pythonrun; // skipped sysmodule.h pub(crate) mod floatobject; pub(crate) mod pyframe; +pub(crate) mod pyhash; pub(crate) mod tupleobject; pub(crate) mod unicodeobject; pub(crate) mod weakrefobject; @@ -71,8 +72,10 @@ pub use self::object::*; pub use self::objimpl::*; pub use self::pydebug::*; pub use self::pyerrors::*; -#[cfg(Py_3_11)] +#[cfg(all(Py_3_11, not(PyPy)))] pub use self::pyframe::*; +#[cfg(any(not(PyPy), Py_3_13))] +pub use self::pyhash::*; #[cfg(all(Py_3_8, not(PyPy)))] pub use self::pylifecycle::*; pub use self::pymem::*; diff --git a/include/pyo3/pyo3-ffi/src/cpython/object.rs b/include/pyo3/pyo3-ffi/src/cpython/object.rs index 75eef11a..26ef784d 100644 --- a/include/pyo3/pyo3-ffi/src/cpython/object.rs +++ b/include/pyo3/pyo3-ffi/src/cpython/object.rs @@ -310,10 +310,14 @@ pub struct PyHeapTypeObject { pub ht_cached_keys: *mut c_void, #[cfg(Py_3_9)] pub ht_module: *mut object::PyObject, - #[cfg(Py_3_11)] + #[cfg(all(Py_3_11, not(PyPy)))] _ht_tpname: *mut c_char, - #[cfg(Py_3_11)] + #[cfg(Py_3_14)] + pub ht_token: *mut c_void, + #[cfg(all(Py_3_11, not(PyPy)))] _spec_cache: _specialization_cache, + #[cfg(all(Py_GIL_DISABLED, Py_3_14))] + pub unique_id: Py_ssize_t, } impl Default for PyHeapTypeObject { diff --git a/include/pyo3/pyo3-ffi/src/cpython/objimpl.rs b/include/pyo3/pyo3-ffi/src/cpython/objimpl.rs index 98a19abe..14f7121a 100644 --- a/include/pyo3/pyo3-ffi/src/cpython/objimpl.rs +++ b/include/pyo3/pyo3-ffi/src/cpython/objimpl.rs @@ -1,4 +1,4 @@ -#[cfg(not(all(Py_3_11, GraalPy)))] +#[cfg(not(all(Py_3_11, any(PyPy, GraalPy))))] use libc::size_t; use std::os::raw::c_int; diff --git a/include/pyo3/pyo3-ffi/src/cpython/pyerrors.rs b/include/pyo3/pyo3-ffi/src/cpython/pyerrors.rs index c6e10e5f..c9831669 100644 --- a/include/pyo3/pyo3-ffi/src/cpython/pyerrors.rs +++ b/include/pyo3/pyo3-ffi/src/cpython/pyerrors.rs @@ -46,6 +46,8 @@ pub struct PySyntaxErrorObject { pub end_offset: *mut PyObject, pub text: *mut PyObject, pub print_file_and_line: *mut PyObject, + #[cfg(Py_3_14)] + pub metadata: *mut PyObject, } #[cfg(not(any(PyPy, GraalPy)))] diff --git a/include/pyo3/pyo3-ffi/src/cpython/pyframe.rs b/include/pyo3/pyo3-ffi/src/cpython/pyframe.rs index d0cfa0a2..f0c38be4 100644 --- a/include/pyo3/pyo3-ffi/src/cpython/pyframe.rs +++ b/include/pyo3/pyo3-ffi/src/cpython/pyframe.rs @@ -1,2 +1,3 @@ -#[cfg(Py_3_11)] -opaque_struct!(_PyInterpreterFrame); +// NB used in `_PyEval_EvalFrameDefault`, maybe we remove this too. +#[cfg(all(Py_3_11, not(PyPy)))] +opaque_struct!(pub _PyInterpreterFrame); diff --git a/include/pyo3/pyo3-ffi/src/cpython/pyhash.rs b/include/pyo3/pyo3-ffi/src/cpython/pyhash.rs new file mode 100644 index 00000000..b746018a --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/cpython/pyhash.rs @@ -0,0 +1,38 @@ +#[cfg(Py_3_14)] +use crate::Py_ssize_t; +#[cfg(Py_3_13)] +use crate::{PyObject, Py_hash_t}; +#[cfg(any(Py_3_13, not(PyPy)))] +use std::os::raw::c_void; +#[cfg(not(PyPy))] +use std::os::raw::{c_char, c_int}; + +#[cfg(not(PyPy))] +#[repr(C)] +#[derive(Copy, Clone)] +pub struct PyHash_FuncDef { + pub hash: + Option crate::Py_hash_t>, + pub name: *const c_char, + pub hash_bits: c_int, + pub seed_bits: c_int, +} + +#[cfg(not(PyPy))] +impl Default for PyHash_FuncDef { + #[inline] + fn default() -> Self { + unsafe { std::mem::zeroed() } + } +} + +extern "C" { + #[cfg(not(PyPy))] + pub fn PyHash_GetFuncDef() -> *mut PyHash_FuncDef; + #[cfg(Py_3_13)] + pub fn Py_HashPointer(ptr: *const c_void) -> Py_hash_t; + #[cfg(Py_3_13)] + pub fn PyObject_GenericHash(obj: *mut PyObject) -> Py_hash_t; + #[cfg(Py_3_14)] + pub fn Py_HashBuffer(ptr: *const c_void, len: Py_ssize_t) -> Py_hash_t; +} diff --git a/include/pyo3/pyo3-ffi/src/cpython/pystate.rs b/include/pyo3/pyo3-ffi/src/cpython/pystate.rs index 5481265b..b8f6fd66 100644 --- a/include/pyo3/pyo3-ffi/src/cpython/pystate.rs +++ b/include/pyo3/pyo3-ffi/src/cpython/pystate.rs @@ -27,16 +27,18 @@ pub const PyTrace_OPCODE: c_int = 7; // skipped PyTraceInfo // skipped CFrame +/// Private structure used inline in `PyGenObject` #[cfg(not(PyPy))] #[repr(C)] #[derive(Clone, Copy)] +#[doc(hidden)] // TODO should be able to make pub(crate) after MSRV 1.74 pub struct _PyErr_StackItem { #[cfg(not(Py_3_11))] - pub exc_type: *mut PyObject, - pub exc_value: *mut PyObject, + exc_type: *mut PyObject, + exc_value: *mut PyObject, #[cfg(not(Py_3_11))] - pub exc_traceback: *mut PyObject, - pub previous_item: *mut _PyErr_StackItem, + exc_traceback: *mut PyObject, + previous_item: *mut _PyErr_StackItem, } // skipped _PyStackChunk @@ -69,21 +71,21 @@ extern "C" { pub fn PyThreadState_DeleteCurrent(); } -#[cfg(all(Py_3_9, not(Py_3_11)))] +#[cfg(all(Py_3_9, not(any(Py_3_11, PyPy))))] pub type _PyFrameEvalFunction = extern "C" fn( *mut crate::PyThreadState, *mut crate::PyFrameObject, c_int, ) -> *mut crate::object::PyObject; -#[cfg(Py_3_11)] +#[cfg(all(Py_3_11, not(PyPy)))] pub type _PyFrameEvalFunction = extern "C" fn( *mut crate::PyThreadState, *mut crate::_PyInterpreterFrame, c_int, ) -> *mut crate::object::PyObject; -#[cfg(Py_3_9)] +#[cfg(all(Py_3_9, not(PyPy)))] extern "C" { /// Get the frame evaluation function. pub fn _PyInterpreterState_GetEvalFrameFunc( diff --git a/include/pyo3/pyo3-ffi/src/cpython/tupleobject.rs b/include/pyo3/pyo3-ffi/src/cpython/tupleobject.rs index c06609fb..dc1bf8e4 100644 --- a/include/pyo3/pyo3-ffi/src/cpython/tupleobject.rs +++ b/include/pyo3/pyo3-ffi/src/cpython/tupleobject.rs @@ -1,4 +1,6 @@ use crate::object::*; +#[cfg(Py_3_14)] +use crate::pyport::Py_hash_t; #[cfg(not(PyPy))] use crate::pyport::Py_ssize_t; @@ -6,7 +8,7 @@ use crate::pyport::Py_ssize_t; pub struct PyTupleObject { pub ob_base: PyVarObject, #[cfg(Py_3_14)] - pub ob_hash: crate::Py_hash_t, + pub ob_hash: Py_hash_t, pub ob_item: [*mut PyObject; 1], } diff --git a/include/pyo3/pyo3-ffi/src/cpython/unicodeobject.rs b/include/pyo3/pyo3-ffi/src/cpython/unicodeobject.rs index 72da46c1..452c82e4 100644 --- a/include/pyo3/pyo3-ffi/src/cpython/unicodeobject.rs +++ b/include/pyo3/pyo3-ffi/src/cpython/unicodeobject.rs @@ -1,4 +1,4 @@ -#[cfg(not(PyPy))] +#[cfg(any(Py_3_11, not(PyPy)))] use crate::Py_hash_t; use crate::{PyObject, Py_UCS1, Py_UCS2, Py_UCS4, Py_ssize_t}; use libc::wchar_t; @@ -31,11 +31,13 @@ use std::os::raw::{c_char, c_int, c_uint, c_void}; // skipped Py_UNICODE_LOW_SURROGATE // generated by bindgen v0.63.0 (with small adaptations) +#[cfg(not(Py_3_14))] #[repr(C)] struct BitfieldUnit { storage: Storage, } +#[cfg(not(Py_3_14))] impl BitfieldUnit { #[inline] pub const fn new(storage: Storage) -> Self { @@ -43,7 +45,7 @@ impl BitfieldUnit { } } -#[cfg(not(GraalPy))] +#[cfg(not(any(GraalPy, Py_3_14)))] impl BitfieldUnit where Storage: AsRef<[u8]> + AsMut<[u8]>, @@ -117,30 +119,33 @@ where } } -#[cfg(not(GraalPy))] +#[cfg(not(any(GraalPy, Py_3_14)))] const STATE_INTERNED_INDEX: usize = 0; -#[cfg(all(not(GraalPy), not(Py_3_14)))] +#[cfg(not(any(GraalPy, Py_3_14)))] const STATE_INTERNED_WIDTH: u8 = 2; -#[cfg(all(not(GraalPy), Py_3_14))] -const STATE_INTERNED_WIDTH: u8 = 16; - -#[cfg(not(GraalPy))] +#[cfg(not(any(GraalPy, Py_3_14)))] const STATE_KIND_INDEX: usize = STATE_INTERNED_WIDTH as usize; -#[cfg(not(GraalPy))] +#[cfg(not(any(GraalPy, Py_3_14)))] const STATE_KIND_WIDTH: u8 = 3; -#[cfg(not(GraalPy))] +#[cfg(not(any(GraalPy, Py_3_14)))] const STATE_COMPACT_INDEX: usize = (STATE_INTERNED_WIDTH + STATE_KIND_WIDTH) as usize; -#[cfg(not(GraalPy))] +#[cfg(not(any(GraalPy, Py_3_14)))] const STATE_COMPACT_WIDTH: u8 = 1; -#[cfg(not(GraalPy))] +#[cfg(not(any(GraalPy, Py_3_14)))] const STATE_ASCII_INDEX: usize = (STATE_INTERNED_WIDTH + STATE_KIND_WIDTH + STATE_COMPACT_WIDTH) as usize; -#[cfg(not(GraalPy))] +#[cfg(not(any(GraalPy, Py_3_14)))] const STATE_ASCII_WIDTH: u8 = 1; +#[cfg(all(not(any(GraalPy, Py_3_14)), Py_3_12))] +const STATE_STATICALLY_ALLOCATED_INDEX: usize = + (STATE_INTERNED_WIDTH + STATE_KIND_WIDTH + STATE_COMPACT_WIDTH + STATE_ASCII_WIDTH) as usize; +#[cfg(all(not(any(GraalPy, Py_3_14)), Py_3_12))] +const STATE_STATICALLY_ALLOCATED_WIDTH: u8 = 1; + #[cfg(not(any(Py_3_12, GraalPy)))] const STATE_READY_INDEX: usize = (STATE_INTERNED_WIDTH + STATE_KIND_WIDTH + STATE_COMPACT_WIDTH + STATE_ASCII_WIDTH) as usize; @@ -156,15 +161,15 @@ const STATE_READY_WIDTH: u8 = 1; /// /// Memory layout of C bitfields is implementation defined, so these functions are still /// unsafe. Users must verify that they work as expected on the architectures they target. +#[cfg(not(Py_3_14))] #[repr(C)] -#[repr(align(4))] struct PyASCIIObjectState { bitfield_align: [u8; 0], bitfield: BitfieldUnit<[u8; 4usize]>, } // c_uint and u32 are not necessarily the same type on all targets / architectures -#[cfg(not(GraalPy))] +#[cfg(not(any(GraalPy, Py_3_14)))] #[allow(clippy::useless_transmute)] impl PyASCIIObjectState { #[inline] @@ -218,6 +223,26 @@ impl PyASCIIObjectState { .set(STATE_ASCII_INDEX, STATE_ASCII_WIDTH, val as u64) } + #[cfg(Py_3_12)] + #[inline] + unsafe fn statically_allocated(&self) -> c_uint { + std::mem::transmute(self.bitfield.get( + STATE_STATICALLY_ALLOCATED_INDEX, + STATE_STATICALLY_ALLOCATED_WIDTH, + ) as u32) + } + + #[cfg(Py_3_12)] + #[inline] + unsafe fn set_statically_allocated(&mut self, val: c_uint) { + let val: u32 = std::mem::transmute(val); + self.bitfield.set( + STATE_STATICALLY_ALLOCATED_INDEX, + STATE_STATICALLY_ALLOCATED_WIDTH, + val as u64, + ) + } + #[cfg(not(Py_3_12))] #[inline] unsafe fn ready(&self) -> c_uint { @@ -233,6 +258,7 @@ impl PyASCIIObjectState { } } +#[cfg(not(Py_3_14))] impl From for PyASCIIObjectState { #[inline] fn from(value: u32) -> Self { @@ -243,6 +269,7 @@ impl From for PyASCIIObjectState { } } +#[cfg(not(Py_3_14))] impl From for u32 { #[inline] fn from(value: PyASCIIObjectState) -> Self { @@ -254,26 +281,36 @@ impl From for u32 { pub struct PyASCIIObject { pub ob_base: PyObject, pub length: Py_ssize_t, - #[cfg(not(PyPy))] + #[cfg(any(Py_3_11, not(PyPy)))] pub hash: Py_hash_t, /// A bit field with various properties. /// /// Rust doesn't expose bitfields. So we have accessor functions for /// retrieving values. /// + /// Before 3.12: /// unsigned int interned:2; // SSTATE_* constants. /// unsigned int kind:3; // PyUnicode_*_KIND constants. /// unsigned int compact:1; /// unsigned int ascii:1; /// unsigned int ready:1; /// unsigned int :24; + /// + /// 3.12, and 3.13 + /// unsigned int interned:2; // SSTATE_* constants. + /// unsigned int kind:3; // PyUnicode_*_KIND constants. + /// unsigned int compact:1; + /// unsigned int ascii:1; + /// unsigned int statically_allocated:1; + /// unsigned int :24; + /// on 3.14 and higher PyO3 doesn't access the internal state pub state: u32, #[cfg(not(Py_3_12))] pub wstr: *mut wchar_t, } /// Interacting with the bitfield is not actually well-defined, so we mark these APIs unsafe. -#[cfg(not(GraalPy))] +#[cfg(not(any(GraalPy, Py_3_14)))] impl PyASCIIObject { #[cfg_attr(not(Py_3_12), allow(rustdoc::broken_intra_doc_links))] // SSTATE_INTERNED_IMMORTAL_STATIC requires 3.12 /// Get the `interned` field of the [`PyASCIIObject`] state bitfield. @@ -350,6 +387,7 @@ impl PyASCIIObject { /// /// Calling this function with an argument that is neither `0` nor `1` is invalid. #[inline] + #[cfg(not(all(Py_3_14, Py_GIL_DISABLED)))] pub unsafe fn set_ascii(&mut self, val: c_uint) { let mut state = PyASCIIObjectState::from(self.state); state.set_ascii(val); @@ -375,6 +413,26 @@ impl PyASCIIObject { state.set_ready(val); self.state = u32::from(state); } + + /// Get the `statically_allocated` field of the [`PyASCIIObject`] state bitfield. + /// + /// Returns either `0` or `1`. + #[inline] + #[cfg(Py_3_12)] + pub unsafe fn statically_allocated(&self) -> c_uint { + PyASCIIObjectState::from(self.state).statically_allocated() + } + + /// Set the `statically_allocated` flag of the [`PyASCIIObject`] state bitfield. + /// + /// Calling this function with an argument that is neither `0` nor `1` is invalid. + #[inline] + #[cfg(Py_3_12)] + pub unsafe fn set_statically_allocated(&mut self, val: c_uint) { + let mut state = PyASCIIObjectState::from(self.state); + state.set_statically_allocated(val); + self.state = u32::from(state); + } } #[repr(C)] @@ -416,7 +474,7 @@ pub const SSTATE_INTERNED_IMMORTAL: c_uint = 2; #[cfg(Py_3_12)] pub const SSTATE_INTERNED_IMMORTAL_STATIC: c_uint = 3; -#[cfg(not(GraalPy))] +#[cfg(not(any(GraalPy, Py_3_14)))] #[inline] pub unsafe fn PyUnicode_IS_ASCII(op: *mut PyObject) -> c_uint { debug_assert!(crate::PyUnicode_Check(op) != 0); @@ -426,13 +484,13 @@ pub unsafe fn PyUnicode_IS_ASCII(op: *mut PyObject) -> c_uint { (*(op as *mut PyASCIIObject)).ascii() } -#[cfg(not(GraalPy))] +#[cfg(not(any(GraalPy, Py_3_14)))] #[inline] pub unsafe fn PyUnicode_IS_COMPACT(op: *mut PyObject) -> c_uint { (*(op as *mut PyASCIIObject)).compact() } -#[cfg(not(GraalPy))] +#[cfg(not(any(GraalPy, Py_3_14)))] #[inline] pub unsafe fn PyUnicode_IS_COMPACT_ASCII(op: *mut PyObject) -> c_uint { ((*(op as *mut PyASCIIObject)).ascii() != 0 && PyUnicode_IS_COMPACT(op) != 0).into() @@ -464,7 +522,13 @@ pub unsafe fn PyUnicode_4BYTE_DATA(op: *mut PyObject) -> *mut Py_UCS4 { PyUnicode_DATA(op) as *mut Py_UCS4 } -#[cfg(not(GraalPy))] +#[cfg(all(not(GraalPy), Py_3_14))] +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyUnicode_KIND")] + pub fn PyUnicode_KIND(op: *mut PyObject) -> c_uint; +} + +#[cfg(all(not(GraalPy), not(Py_3_14)))] #[inline] pub unsafe fn PyUnicode_KIND(op: *mut PyObject) -> c_uint { debug_assert!(crate::PyUnicode_Check(op) != 0); @@ -474,7 +538,7 @@ pub unsafe fn PyUnicode_KIND(op: *mut PyObject) -> c_uint { (*(op as *mut PyASCIIObject)).kind() } -#[cfg(not(GraalPy))] +#[cfg(not(any(GraalPy, Py_3_14)))] #[inline] pub unsafe fn _PyUnicode_COMPACT_DATA(op: *mut PyObject) -> *mut c_void { if PyUnicode_IS_ASCII(op) != 0 { @@ -492,7 +556,7 @@ pub unsafe fn _PyUnicode_NONCOMPACT_DATA(op: *mut PyObject) -> *mut c_void { (*(op as *mut PyUnicodeObject)).data.any } -#[cfg(not(any(GraalPy, PyPy)))] +#[cfg(not(any(GraalPy, PyPy, Py_3_14)))] #[inline] pub unsafe fn PyUnicode_DATA(op: *mut PyObject) -> *mut c_void { debug_assert!(crate::PyUnicode_Check(op) != 0); @@ -504,6 +568,13 @@ pub unsafe fn PyUnicode_DATA(op: *mut PyObject) -> *mut c_void { } } +#[cfg(Py_3_14)] +#[cfg(all(not(GraalPy), Py_3_14))] +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyUnicode_DATA")] + pub fn PyUnicode_DATA(op: *mut PyObject) -> *mut c_void; +} + // skipped PyUnicode_WRITE // skipped PyUnicode_READ // skipped PyUnicode_READ_CHAR diff --git a/include/pyo3/pyo3-ffi/src/cpython/weakrefobject.rs b/include/pyo3/pyo3-ffi/src/cpython/weakrefobject.rs index 88bb501b..1c50c7a7 100644 --- a/include/pyo3/pyo3-ffi/src/cpython/weakrefobject.rs +++ b/include/pyo3/pyo3-ffi/src/cpython/weakrefobject.rs @@ -1,3 +1,4 @@ +// NB publicly re-exported in `src/weakrefobject.rs` #[cfg(not(any(PyPy, GraalPy)))] pub struct _PyWeakReference { pub ob_base: crate::PyObject, diff --git a/include/pyo3/pyo3-ffi/src/datetime.rs b/include/pyo3/pyo3-ffi/src/datetime.rs index e529e0fc..4db1bdd1 100644 --- a/include/pyo3/pyo3-ffi/src/datetime.rs +++ b/include/pyo3/pyo3-ffi/src/datetime.rs @@ -119,7 +119,7 @@ pub struct PyDateTime_DateTime { pub unsafe fn PyDateTime_GET_YEAR(o: *mut PyObject) -> c_int { // This should work for Date or DateTime let data = (*(o as *mut PyDateTime_Date)).data; - c_int::from(data[0]) << 8 | c_int::from(data[1]) + (c_int::from(data[0]) << 8) | c_int::from(data[1]) } #[inline] @@ -487,7 +487,9 @@ extern "C" { pub fn PyDateTime_DATE_GET_MICROSECOND(o: *mut PyObject) -> c_int; #[link_name = "PyPyDateTime_GET_FOLD"] pub fn PyDateTime_DATE_GET_FOLD(o: *mut PyObject) -> c_int; - // skipped PyDateTime_DATE_GET_TZINFO (not in PyPy) + #[link_name = "PyPyDateTime_DATE_GET_TZINFO"] + #[cfg(Py_3_10)] + pub fn PyDateTime_DATE_GET_TZINFO(o: *mut PyObject) -> *mut PyObject; #[link_name = "PyPyDateTime_TIME_GET_HOUR"] pub fn PyDateTime_TIME_GET_HOUR(o: *mut PyObject) -> c_int; @@ -499,7 +501,9 @@ extern "C" { pub fn PyDateTime_TIME_GET_MICROSECOND(o: *mut PyObject) -> c_int; #[link_name = "PyPyDateTime_TIME_GET_FOLD"] pub fn PyDateTime_TIME_GET_FOLD(o: *mut PyObject) -> c_int; - // skipped PyDateTime_TIME_GET_TZINFO (not in PyPy) + #[link_name = "PyPyDateTime_TIME_GET_TZINFO"] + #[cfg(Py_3_10)] + pub fn PyDateTime_TIME_GET_TZINFO(o: *mut PyObject) -> *mut PyObject; #[link_name = "PyPyDateTime_DELTA_GET_DAYS"] pub fn PyDateTime_DELTA_GET_DAYS(o: *mut PyObject) -> c_int; diff --git a/include/pyo3/pyo3-ffi/src/dictobject.rs b/include/pyo3/pyo3-ffi/src/dictobject.rs index 710be802..e609352a 100644 --- a/include/pyo3/pyo3-ffi/src/dictobject.rs +++ b/include/pyo3/pyo3-ffi/src/dictobject.rs @@ -118,4 +118,4 @@ extern "C" { #[cfg(any(PyPy, GraalPy, Py_LIMITED_API))] // TODO: remove (see https://github.com/PyO3/pyo3/pull/1341#issuecomment-751515985) -opaque_struct!(PyDictObject); +opaque_struct!(pub PyDictObject); diff --git a/include/pyo3/pyo3-ffi/src/floatobject.rs b/include/pyo3/pyo3-ffi/src/floatobject.rs index 65fc1d4c..4e1d6476 100644 --- a/include/pyo3/pyo3-ffi/src/floatobject.rs +++ b/include/pyo3/pyo3-ffi/src/floatobject.rs @@ -4,7 +4,7 @@ use std::ptr::addr_of_mut; #[cfg(Py_LIMITED_API)] // TODO: remove (see https://github.com/PyO3/pyo3/pull/1341#issuecomment-751515985) -opaque_struct!(PyFloatObject); +opaque_struct!(pub PyFloatObject); #[cfg_attr(windows, link(name = "pythonXY"))] extern "C" { diff --git a/include/pyo3/pyo3-ffi/src/genericaliasobject.rs b/include/pyo3/pyo3-ffi/src/genericaliasobject.rs new file mode 100644 index 00000000..7979d7d8 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/genericaliasobject.rs @@ -0,0 +1,12 @@ +#[cfg(Py_3_9)] +use crate::object::{PyObject, PyTypeObject}; + +#[cfg_attr(windows, link(name = "pythonXY"))] +extern "C" { + #[cfg(Py_3_9)] + #[cfg_attr(PyPy, link_name = "PyPy_GenericAlias")] + pub fn Py_GenericAlias(origin: *mut PyObject, args: *mut PyObject) -> *mut PyObject; + + #[cfg(Py_3_9)] + pub static mut Py_GenericAliasType: PyTypeObject; +} diff --git a/include/pyo3/pyo3-ffi/src/lib.rs b/include/pyo3/pyo3-ffi/src/lib.rs index 7bdba117..f78c918a 100644 --- a/include/pyo3/pyo3-ffi/src/lib.rs +++ b/include/pyo3/pyo3-ffi/src/lib.rs @@ -129,7 +129,7 @@ //! ``` //! //! **`src/lib.rs`** -//! ```rust +//! ```rust,no_run //! use std::os::raw::{c_char, c_long}; //! use std::ptr; //! @@ -327,17 +327,24 @@ non_snake_case, non_upper_case_globals, clippy::upper_case_acronyms, - clippy::missing_safety_doc + clippy::missing_safety_doc, + clippy::ptr_eq )] #![warn(elided_lifetimes_in_paths, unused_lifetimes)] +// This crate is a hand-maintained translation of CPython's headers, so requiring "unsafe" +// blocks within those translations increases maintenance burden without providing any +// additional safety. The safety of the functions in this crate is determined by the +// original CPython headers +#![allow(unsafe_op_in_unsafe_fn)] // Until `extern type` is stabilized, use the recommended approach to // model opaque types: // https://doc.rust-lang.org/nomicon/ffi.html#representing-opaque-structs macro_rules! opaque_struct { - ($name:ident) => { + ($(#[$attrs:meta])* $pub:vis $name:ident) => { + $(#[$attrs])* #[repr(C)] - pub struct $name([u8; 0]); + $pub struct $name([u8; 0]); }; } @@ -351,7 +358,7 @@ macro_rules! opaque_struct { /// /// Examples: /// -/// ```rust +/// ```rust,no_run /// use std::ffi::CStr; /// /// const HELLO: &CStr = pyo3_ffi::c_str!("hello"); @@ -410,6 +417,8 @@ pub use self::enumobject::*; pub use self::fileobject::*; pub use self::fileutils::*; pub use self::floatobject::*; +#[cfg(Py_3_9)] +pub use self::genericaliasobject::*; pub use self::import::*; pub use self::intrcheck::*; pub use self::iterobject::*; @@ -439,6 +448,7 @@ pub use self::pystate::*; pub use self::pystrtod::*; pub use self::pythonrun::*; pub use self::rangeobject::*; +pub use self::refcount::*; pub use self::setobject::*; pub use self::sliceobject::*; pub use self::structseq::*; @@ -479,7 +489,7 @@ mod fileobject; mod fileutils; mod floatobject; // skipped empty frameobject.h -// skipped genericaliasobject.h +mod genericaliasobject; mod import; // skipped interpreteridobject.h mod intrcheck; @@ -531,6 +541,7 @@ mod pystrtod; // skipped pythread.h // skipped pytime.h mod rangeobject; +mod refcount; mod setobject; mod sliceobject; mod structseq; diff --git a/include/pyo3/pyo3-ffi/src/longobject.rs b/include/pyo3/pyo3-ffi/src/longobject.rs index 68b4ecba..eca0af3d 100644 --- a/include/pyo3/pyo3-ffi/src/longobject.rs +++ b/include/pyo3/pyo3-ffi/src/longobject.rs @@ -4,7 +4,7 @@ use libc::size_t; use std::os::raw::{c_char, c_double, c_int, c_long, c_longlong, c_ulong, c_ulonglong, c_void}; use std::ptr::addr_of_mut; -opaque_struct!(PyLongObject); +opaque_struct!(pub PyLongObject); #[inline] pub unsafe fn PyLong_Check(op: *mut PyObject) -> c_int { diff --git a/include/pyo3/pyo3-ffi/src/memoryobject.rs b/include/pyo3/pyo3-ffi/src/memoryobject.rs index b7ef9e2e..4e1e50c6 100644 --- a/include/pyo3/pyo3-ffi/src/memoryobject.rs +++ b/include/pyo3/pyo3-ffi/src/memoryobject.rs @@ -3,11 +3,10 @@ use crate::pyport::Py_ssize_t; use std::os::raw::{c_char, c_int}; use std::ptr::addr_of_mut; +// skipped _PyManagedBuffer_Type + #[cfg_attr(windows, link(name = "pythonXY"))] extern "C" { - #[cfg(not(Py_LIMITED_API))] - pub static mut _PyManagedBuffer_Type: PyTypeObject; - #[cfg_attr(PyPy, link_name = "PyPyMemoryView_Type")] pub static mut PyMemoryView_Type: PyTypeObject; } diff --git a/include/pyo3/pyo3-ffi/src/methodobject.rs b/include/pyo3/pyo3-ffi/src/methodobject.rs index 37e1e206..3dfbbb5a 100644 --- a/include/pyo3/pyo3-ffi/src/methodobject.rs +++ b/include/pyo3/pyo3-ffi/src/methodobject.rs @@ -50,7 +50,7 @@ pub type PyCFunctionFast = unsafe extern "C" fn( ) -> *mut PyObject; #[cfg(any(Py_3_10, not(Py_LIMITED_API)))] -#[cfg_attr(Py_3_10, deprecated(note = "renamed to `PyCFunctionFast`"))] +#[deprecated(note = "renamed to `PyCFunctionFast`")] pub type _PyCFunctionFast = PyCFunctionFast; pub type PyCFunctionWithKeywords = unsafe extern "C" fn( @@ -68,7 +68,7 @@ pub type PyCFunctionFastWithKeywords = unsafe extern "C" fn( ) -> *mut PyObject; #[cfg(any(Py_3_10, not(Py_LIMITED_API)))] -#[cfg_attr(Py_3_10, deprecated(note = "renamed to `PyCFunctionFastWithKeywords`"))] +#[deprecated(note = "renamed to `PyCFunctionFastWithKeywords`")] pub type _PyCFunctionFastWithKeywords = PyCFunctionFastWithKeywords; #[cfg(all(Py_3_9, not(Py_LIMITED_API)))] @@ -153,7 +153,7 @@ pub union PyMethodDefPointer { /// This variant corresponds with [`METH_FASTCALL`]. #[cfg(any(Py_3_10, not(Py_LIMITED_API)))] - #[cfg_attr(Py_3_10, deprecated(note = "renamed to `PyCFunctionFast`"))] + #[deprecated(note = "renamed to `PyCFunctionFast`")] pub _PyCFunctionFast: PyCFunctionFast, /// This variant corresponds with [`METH_FASTCALL`]. @@ -162,7 +162,7 @@ pub union PyMethodDefPointer { /// This variant corresponds with [`METH_FASTCALL`] | [`METH_KEYWORDS`]. #[cfg(any(Py_3_10, not(Py_LIMITED_API)))] - #[cfg_attr(Py_3_10, deprecated(note = "renamed to `PyCFunctionFastWithKeywords`"))] + #[deprecated(note = "renamed to `PyCFunctionFastWithKeywords`")] pub _PyCFunctionFastWithKeywords: PyCFunctionFastWithKeywords, /// This variant corresponds with [`METH_FASTCALL`] | [`METH_KEYWORDS`]. diff --git a/include/pyo3/pyo3-ffi/src/modsupport.rs b/include/pyo3/pyo3-ffi/src/modsupport.rs index 6da2795b..56a68fe2 100644 --- a/include/pyo3/pyo3-ffi/src/modsupport.rs +++ b/include/pyo3/pyo3-ffi/src/modsupport.rs @@ -37,12 +37,7 @@ extern "C" { // skipped Py_VaBuildValue #[cfg(Py_3_13)] - pub fn PyModule_Add( - module: *mut PyObject, - name: *const c_char, - value: *mut PyObject, - ) -> core::ffi::c_int; - + pub fn PyModule_Add(module: *mut PyObject, name: *const c_char, value: *mut PyObject) -> c_int; #[cfg(Py_3_10)] #[cfg_attr(PyPy, link_name = "PyPyModule_AddObjectRef")] pub fn PyModule_AddObjectRef( diff --git a/include/pyo3/pyo3-ffi/src/object.rs b/include/pyo3/pyo3-ffi/src/object.rs index 51083a8e..5fbf45db 100644 --- a/include/pyo3/pyo3-ffi/src/object.rs +++ b/include/pyo3/pyo3-ffi/src/object.rs @@ -1,5 +1,7 @@ use crate::pyport::{Py_hash_t, Py_ssize_t}; #[cfg(Py_GIL_DISABLED)] +use crate::refcount; +#[cfg(Py_GIL_DISABLED)] use crate::PyMutex; #[cfg(Py_GIL_DISABLED)] use std::marker::PhantomPinned; @@ -7,81 +9,50 @@ use std::mem; use std::os::raw::{c_char, c_int, c_uint, c_ulong, c_void}; use std::ptr; #[cfg(Py_GIL_DISABLED)] -use std::sync::atomic::{AtomicIsize, AtomicU32, AtomicU8, Ordering::Relaxed}; +use std::sync::atomic::{AtomicIsize, AtomicU32, AtomicU8}; #[cfg(Py_LIMITED_API)] -opaque_struct!(PyTypeObject); +opaque_struct!(pub PyTypeObject); #[cfg(not(Py_LIMITED_API))] pub use crate::cpython::object::PyTypeObject; -#[cfg(Py_3_12)] -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 - } -}; - -#[cfg(Py_GIL_DISABLED)] -const _Py_IMMORTAL_REFCNT_LOCAL: u32 = u32::MAX; - -#[allow(clippy::declare_interior_mutable_const)] -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_GIL_DISABLED)] - ob_tid: 0, - #[cfg(Py_GIL_DISABLED)] - _padding: 0, - #[cfg(Py_GIL_DISABLED)] - ob_mutex: PyMutex { - _bits: AtomicU8::new(0), - _pin: PhantomPinned, - }, - #[cfg(Py_GIL_DISABLED)] - ob_gc_bits: 0, - #[cfg(Py_GIL_DISABLED)] - ob_ref_local: AtomicU32::new(_Py_IMMORTAL_REFCNT_LOCAL), - #[cfg(Py_GIL_DISABLED)] - ob_ref_shared: AtomicIsize::new(0), - #[cfg(all(not(Py_GIL_DISABLED), Py_3_12))] - ob_refcnt: PyObjectObRefcnt { ob_refcnt: 1 }, - #[cfg(not(Py_3_12))] - ob_refcnt: 1, - #[cfg(PyPy)] - ob_pypy_link: 0, - ob_type: std::ptr::null_mut(), -}; - -// skipped PyObject_VAR_HEAD -// skipped Py_INVALID_SIZE - -// skipped private _Py_UNOWNED_TID +// skip PyObject_HEAD -#[cfg(Py_GIL_DISABLED)] -const _Py_REF_SHARED_SHIFT: isize = 2; -// skipped private _Py_REF_SHARED_FLAG_MASK - -// skipped private _Py_REF_SHARED_INIT -// skipped private _Py_REF_MAYBE_WEAKREF -// skipped private _Py_REF_QUEUED -// skipped private _Py_REF_MERGED +#[repr(C)] +#[derive(Copy, Clone)] +#[cfg(all(Py_3_14, not(Py_GIL_DISABLED), target_endian = "big"))] +/// This struct is anonymous in CPython, so the name was given by PyO3 because +/// Rust structs need a name. +pub struct PyObjectObFlagsAndRefcnt { + pub ob_flags: u16, + pub ob_overflow: u16, + pub ob_refcnt: u32, +} -// skipped private _Py_REF_SHARED +#[repr(C)] +#[derive(Copy, Clone)] +#[cfg(all(Py_3_14, not(Py_GIL_DISABLED), target_endian = "little"))] +/// This struct is anonymous in CPython, so the name was given by PyO3 because +/// Rust structs need a name. +pub struct PyObjectObFlagsAndRefcnt { + pub ob_refcnt: u32, + pub ob_overflow: u16, + pub ob_flags: u16, +} #[repr(C)] #[derive(Copy, Clone)] #[cfg(all(Py_3_12, not(Py_GIL_DISABLED)))] /// This union is anonymous in CPython, so the name was given by PyO3 because -/// Rust unions need a name. +/// Rust union need a name. pub union PyObjectObRefcnt { + #[cfg(all(target_pointer_width = "64", Py_3_14))] + pub ob_refcnt_full: crate::PY_INT64_T, + #[cfg(Py_3_14)] + pub refcnt_and_flags: PyObjectObFlagsAndRefcnt, pub ob_refcnt: Py_ssize_t, - #[cfg(target_pointer_width = "64")] + #[cfg(all(target_pointer_width = "64", not(Py_3_14)))] pub ob_refcnt_split: [crate::PY_UINT32_T; 2], } @@ -95,6 +66,9 @@ impl std::fmt::Debug for PyObjectObRefcnt { #[cfg(all(not(Py_3_12), not(Py_GIL_DISABLED)))] pub type PyObjectObRefcnt = Py_ssize_t; +// PyObject_HEAD_INIT comes before the PyObject definition in object.h +// but we put it after PyObject because HEAD_INIT uses PyObject + #[repr(C)] #[derive(Debug)] pub struct PyObject { @@ -104,8 +78,10 @@ pub struct PyObject { pub _ob_prev: *mut PyObject, #[cfg(Py_GIL_DISABLED)] pub ob_tid: libc::uintptr_t, - #[cfg(Py_GIL_DISABLED)] + #[cfg(all(Py_GIL_DISABLED, not(Py_3_14)))] pub _padding: u16, + #[cfg(all(Py_GIL_DISABLED, Py_3_14))] + pub ob_flags: u16, #[cfg(Py_GIL_DISABLED)] pub ob_mutex: PyMutex, // per-object lock #[cfg(Py_GIL_DISABLED)] @@ -121,7 +97,41 @@ pub struct PyObject { pub ob_type: *mut PyTypeObject, } -// skipped private _PyObject_CAST +#[allow(clippy::declare_interior_mutable_const)] +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_GIL_DISABLED)] + ob_tid: 0, + #[cfg(all(Py_GIL_DISABLED, Py_3_14))] + ob_flags: 0, + #[cfg(all(Py_GIL_DISABLED, not(Py_3_14)))] + _padding: 0, + #[cfg(Py_GIL_DISABLED)] + ob_mutex: PyMutex { + _bits: AtomicU8::new(0), + _pin: PhantomPinned, + }, + #[cfg(Py_GIL_DISABLED)] + ob_gc_bits: 0, + #[cfg(Py_GIL_DISABLED)] + ob_ref_local: AtomicU32::new(refcount::_Py_IMMORTAL_REFCNT_LOCAL), + #[cfg(Py_GIL_DISABLED)] + ob_ref_shared: AtomicIsize::new(0), + #[cfg(all(not(Py_GIL_DISABLED), Py_3_12))] + ob_refcnt: PyObjectObRefcnt { ob_refcnt: 1 }, + #[cfg(not(Py_3_12))] + ob_refcnt: 1, + #[cfg(PyPy)] + ob_pypy_link: 0, + ob_type: std::ptr::null_mut(), +}; + +// skipped _Py_UNOWNED_TID + +// skipped _PyObject_CAST #[repr(C)] #[derive(Debug)] @@ -150,41 +160,23 @@ extern "C" { pub fn Py_Is(x: *mut PyObject, y: *mut PyObject) -> c_int; } -// skipped private _Py_GetThreadLocal_Addr +// skipped _Py_GetThreadLocal_Addr -// skipped private _Py_ThreadId +// skipped _Py_ThreadID -// skipped private _Py_IsOwnedByCurrentThread +// skipped _Py_IsOwnedByCurrentThread -#[inline] -pub unsafe fn Py_REFCNT(ob: *mut PyObject) -> Py_ssize_t { - #[cfg(Py_GIL_DISABLED)] - { - let local = (*ob).ob_ref_local.load(Relaxed); - if local == _Py_IMMORTAL_REFCNT_LOCAL { - return _Py_IMMORTAL_REFCNT; - } - let shared = (*ob).ob_ref_shared.load(Relaxed); - local as Py_ssize_t + Py_ssize_t::from(shared >> _Py_REF_SHARED_SHIFT) - } - - #[cfg(all(not(Py_GIL_DISABLED), Py_3_12))] - { - (*ob).ob_refcnt.ob_refcnt - } - - #[cfg(all(not(Py_GIL_DISABLED), not(Py_3_12), not(GraalPy)))] - { - (*ob).ob_refcnt - } +#[cfg(GraalPy)] +extern "C" { + #[cfg(GraalPy)] + fn _Py_TYPE(arg1: *const PyObject) -> *mut PyTypeObject; - #[cfg(all(not(Py_GIL_DISABLED), not(Py_3_12), GraalPy))] - { - _Py_REFCNT(ob) - } + #[cfg(GraalPy)] + fn _Py_SIZE(arg1: *const PyObject) -> Py_ssize_t; } #[inline] +#[cfg(not(Py_3_14))] pub unsafe fn Py_TYPE(ob: *mut PyObject) -> *mut PyTypeObject { #[cfg(not(GraalPy))] return (*ob).ob_type; @@ -192,6 +184,15 @@ pub unsafe fn Py_TYPE(ob: *mut PyObject) -> *mut PyTypeObject { return _Py_TYPE(ob); } +#[cfg_attr(windows, link(name = "pythonXY"))] +#[cfg(Py_3_14)] +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPy_TYPE")] + pub fn Py_TYPE(ob: *mut PyObject) -> *mut PyTypeObject; +} + +// skip _Py_TYPE compat shim + #[cfg_attr(windows, link(name = "pythonXY"))] extern "C" { #[cfg_attr(PyPy, link_name = "PyPyLong_Type")] @@ -212,29 +213,11 @@ pub unsafe fn Py_SIZE(ob: *mut PyObject) -> Py_ssize_t { _Py_SIZE(ob) } -#[inline(always)] -#[cfg(all(Py_3_12, not(Py_GIL_DISABLED)))] -pub unsafe fn _Py_IsImmortal(op: *mut PyObject) -> c_int { - #[cfg(target_pointer_width = "64")] - { - (((*op).ob_refcnt.ob_refcnt as crate::PY_INT32_T) < 0) as c_int - } - - #[cfg(target_pointer_width = "32")] - { - ((*op).ob_refcnt.ob_refcnt == _Py_IMMORTAL_REFCNT) as c_int - } -} - #[inline] pub unsafe fn Py_IS_TYPE(ob: *mut PyObject, tp: *mut PyTypeObject) -> c_int { (Py_TYPE(ob) == tp) as c_int } -// skipped _Py_SetRefCnt - -// skipped Py_SET_REFCNT - // skipped Py_SET_TYPE // skipped Py_SET_SIZE @@ -436,7 +419,7 @@ extern "C" { arg2: *const c_char, arg3: *mut PyObject, ) -> c_int; - #[cfg(any(Py_3_13, PyPy))] // CPython defined in 3.12 as an inline function in abstract.h + #[cfg(any(Py_3_13, all(PyPy, not(Py_3_11))))] // CPython defined in 3.12 as an inline function in abstract.h #[cfg_attr(PyPy, link_name = "PyPyObject_DelAttrString")] pub fn PyObject_DelAttrString(arg1: *mut PyObject, arg2: *const c_char) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyObject_HasAttrString")] @@ -460,7 +443,7 @@ extern "C" { #[cfg_attr(PyPy, link_name = "PyPyObject_SetAttr")] pub fn PyObject_SetAttr(arg1: *mut PyObject, arg2: *mut PyObject, arg3: *mut PyObject) -> c_int; - #[cfg(any(Py_3_13, PyPy))] // CPython defined in 3.12 as an inline function in abstract.h + #[cfg(any(Py_3_13, all(PyPy, not(Py_3_11))))] // CPython defined in 3.12 as an inline function in abstract.h #[cfg_attr(PyPy, link_name = "PyPyObject_DelAttr")] pub fn PyObject_DelAttr(arg1: *mut PyObject, arg2: *mut PyObject) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyObject_HasAttr")] @@ -586,222 +569,6 @@ pub const Py_TPFLAGS_DEFAULT: c_ulong = if cfg!(Py_3_10) { pub const Py_TPFLAGS_HAVE_FINALIZE: c_ulong = 1; pub const Py_TPFLAGS_HAVE_VERSION_TAG: c_ulong = 1 << 18; -extern "C" { - #[cfg(all(py_sys_config = "Py_REF_DEBUG", not(Py_LIMITED_API)))] - fn _Py_NegativeRefcount(filename: *const c_char, lineno: c_int, op: *mut PyObject); - #[cfg(all(Py_3_12, py_sys_config = "Py_REF_DEBUG", not(Py_LIMITED_API)))] - fn _Py_INCREF_IncRefTotal(); - #[cfg(all(Py_3_12, py_sys_config = "Py_REF_DEBUG", not(Py_LIMITED_API)))] - fn _Py_DECREF_DecRefTotal(); - - #[cfg_attr(PyPy, link_name = "_PyPy_Dealloc")] - fn _Py_Dealloc(arg1: *mut PyObject); - - #[cfg_attr(PyPy, link_name = "PyPy_IncRef")] - #[cfg_attr(GraalPy, link_name = "_Py_IncRef")] - pub fn Py_IncRef(o: *mut PyObject); - #[cfg_attr(PyPy, link_name = "PyPy_DecRef")] - #[cfg_attr(GraalPy, link_name = "_Py_DecRef")] - pub fn Py_DecRef(o: *mut PyObject); - - #[cfg(all(Py_3_10, not(PyPy)))] - fn _Py_IncRef(o: *mut PyObject); - #[cfg(all(Py_3_10, not(PyPy)))] - fn _Py_DecRef(o: *mut PyObject); - - #[cfg(GraalPy)] - fn _Py_REFCNT(arg1: *const PyObject) -> Py_ssize_t; - - #[cfg(GraalPy)] - fn _Py_TYPE(arg1: *const PyObject) -> *mut PyTypeObject; - - #[cfg(GraalPy)] - fn _Py_SIZE(arg1: *const PyObject) -> Py_ssize_t; -} - -#[inline(always)] -pub unsafe fn Py_INCREF(op: *mut PyObject) { - // On limited API, the free-threaded build, or with refcount debugging, let the interpreter do refcounting - // TODO: reimplement the logic in the header in the free-threaded build, for a little bit of performance. - #[cfg(any( - Py_GIL_DISABLED, - Py_LIMITED_API, - py_sys_config = "Py_REF_DEBUG", - GraalPy - ))] - { - // _Py_IncRef was added to the ABI in 3.10; skips null checks - #[cfg(all(Py_3_10, not(PyPy)))] - { - _Py_IncRef(op); - } - - #[cfg(any(not(Py_3_10), PyPy))] - { - Py_IncRef(op); - } - } - - // version-specific builds are allowed to directly manipulate the reference count - #[cfg(not(any( - Py_GIL_DISABLED, - Py_LIMITED_API, - py_sys_config = "Py_REF_DEBUG", - GraalPy - )))] - { - #[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 - } -} - -#[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) { - // On limited API, the free-threaded build, or with refcount debugging, let the interpreter do refcounting - // On 3.12+ we implement refcount debugging to get better assertion locations on negative refcounts - // TODO: reimplement the logic in the header in the free-threaded build, for a little bit of performance. - #[cfg(any( - Py_GIL_DISABLED, - Py_LIMITED_API, - all(py_sys_config = "Py_REF_DEBUG", not(Py_3_12)), - GraalPy - ))] - { - // _Py_DecRef was added to the ABI in 3.10; skips null checks - #[cfg(all(Py_3_10, not(PyPy)))] - { - _Py_DecRef(op); - } - - #[cfg(any(not(Py_3_10), PyPy))] - { - Py_DecRef(op); - } - } - - #[cfg(not(any( - Py_GIL_DISABLED, - Py_LIMITED_API, - all(py_sys_config = "Py_REF_DEBUG", not(Py_3_12)), - GraalPy - )))] - { - #[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(py_sys_config = "Py_REF_DEBUG")] - _Py_DECREF_DecRefTotal(); - - #[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(); - let filename = std::ffi::CString::new(location.file()).unwrap(); - _Py_NegativeRefcount(filename.as_ptr(), location.line() as i32, 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); - } - } - } -} - -#[inline] -pub unsafe fn Py_CLEAR(op: *mut *mut PyObject) { - let tmp = *op; - if !tmp.is_null() { - *op = ptr::null_mut(); - Py_DECREF(tmp); - } -} - -#[inline] -pub unsafe fn Py_XINCREF(op: *mut PyObject) { - if !op.is_null() { - Py_INCREF(op) - } -} - -#[inline] -pub unsafe fn Py_XDECREF(op: *mut PyObject) { - if !op.is_null() { - Py_DECREF(op) - } -} - -extern "C" { - #[cfg(all(Py_3_10, Py_LIMITED_API, not(PyPy)))] - #[cfg_attr(docsrs, doc(cfg(Py_3_10)))] - pub fn Py_NewRef(obj: *mut PyObject) -> *mut PyObject; - #[cfg(all(Py_3_10, Py_LIMITED_API, not(PyPy)))] - #[cfg_attr(docsrs, doc(cfg(Py_3_10)))] - pub fn Py_XNewRef(obj: *mut PyObject) -> *mut PyObject; -} - -// macro _Py_NewRef not public; reimplemented directly inside Py_NewRef here -// macro _Py_XNewRef not public; reimplemented directly inside Py_XNewRef here - -#[cfg(all(Py_3_10, any(not(Py_LIMITED_API), PyPy)))] -#[cfg_attr(docsrs, doc(cfg(Py_3_10)))] -#[inline] -pub unsafe fn Py_NewRef(obj: *mut PyObject) -> *mut PyObject { - Py_INCREF(obj); - obj -} - -#[cfg(all(Py_3_10, any(not(Py_LIMITED_API), PyPy)))] -#[cfg_attr(docsrs, doc(cfg(Py_3_10)))] -#[inline] -pub unsafe fn Py_XNewRef(obj: *mut PyObject) -> *mut PyObject { - Py_XINCREF(obj); - obj -} - #[cfg(Py_3_13)] pub const Py_CONSTANT_NONE: c_uint = 0; #[cfg(Py_3_13)] @@ -942,4 +709,7 @@ extern "C" { arg1: *mut crate::PyTypeObject, arg2: *mut crate::PyModuleDef, ) -> *mut PyObject; + + #[cfg(Py_3_14)] + pub fn PyType_Freeze(tp: *mut crate::PyTypeObject) -> c_int; } diff --git a/include/pyo3/pyo3-ffi/src/pyarena.rs b/include/pyo3/pyo3-ffi/src/pyarena.rs index 87d5f28a..1200de3d 100644 --- a/include/pyo3/pyo3-ffi/src/pyarena.rs +++ b/include/pyo3/pyo3-ffi/src/pyarena.rs @@ -1 +1 @@ -opaque_struct!(PyArena); +opaque_struct!(pub PyArena); diff --git a/include/pyo3/pyo3-ffi/src/pybuffer.rs b/include/pyo3/pyo3-ffi/src/pybuffer.rs index 50bf4e61..de706759 100644 --- a/include/pyo3/pyo3-ffi/src/pybuffer.rs +++ b/include/pyo3/pyo3-ffi/src/pybuffer.rs @@ -103,7 +103,11 @@ extern "C" { } /// Maximum number of dimensions -pub const PyBUF_MAX_NDIM: c_int = if cfg!(PyPy) { 36 } else { 64 }; +pub const PyBUF_MAX_NDIM: usize = if cfg!(all(PyPy, not(Py_3_11))) { + 36 +} else { + 64 +}; /* Flags for getting buffers */ pub const PyBUF_SIMPLE: c_int = 0; diff --git a/include/pyo3/pyo3-ffi/src/pyerrors.rs b/include/pyo3/pyo3-ffi/src/pyerrors.rs index 6c9313c4..d341239a 100644 --- a/include/pyo3/pyo3-ffi/src/pyerrors.rs +++ b/include/pyo3/pyo3-ffi/src/pyerrors.rs @@ -116,6 +116,7 @@ extern "C" { #[cfg_attr(PyPy, link_name = "PyPyExc_BaseException")] pub static mut PyExc_BaseException: *mut PyObject; #[cfg(Py_3_11)] + #[cfg_attr(PyPy, link_name = "PyPyExc_BaseExceptionGroup")] pub static mut PyExc_BaseExceptionGroup: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_Exception")] pub static mut PyExc_Exception: *mut PyObject; diff --git a/include/pyo3/pyo3-ffi/src/pyframe.rs b/include/pyo3/pyo3-ffi/src/pyframe.rs index 4dd3d2b3..1693b20b 100644 --- a/include/pyo3/pyo3-ffi/src/pyframe.rs +++ b/include/pyo3/pyo3-ffi/src/pyframe.rs @@ -6,7 +6,7 @@ use crate::PyFrameObject; use std::os::raw::c_int; #[cfg(Py_LIMITED_API)] -opaque_struct!(PyFrameObject); +opaque_struct!(pub PyFrameObject); extern "C" { pub fn PyFrame_GetLineNumber(f: *mut PyFrameObject) -> c_int; diff --git a/include/pyo3/pyo3-ffi/src/pyhash.rs b/include/pyo3/pyo3-ffi/src/pyhash.rs index 8b0b6771..074278dd 100644 --- a/include/pyo3/pyo3-ffi/src/pyhash.rs +++ b/include/pyo3/pyo3-ffi/src/pyhash.rs @@ -1,7 +1,5 @@ #[cfg(not(any(Py_LIMITED_API, PyPy)))] use crate::pyport::{Py_hash_t, Py_ssize_t}; -#[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))] -use std::os::raw::c_char; #[cfg(not(any(Py_LIMITED_API, PyPy)))] use std::os::raw::c_void; @@ -12,11 +10,8 @@ extern "C" { // skipped non-limited _Py_HashPointer // skipped non-limited _Py_HashPointerRaw - #[cfg(not(all(Py_3_14, any(Py_LIMITED_API, PyPy))))] + #[cfg(not(any(Py_LIMITED_API, PyPy)))] pub fn _Py_HashBytes(src: *const c_void, len: Py_ssize_t) -> Py_hash_t; - - #[cfg(Py_3_14)] - pub fn Py_HashBuffer(ptr: *const c_void, len: Py_ssize_t) -> Py_hash_t; } pub const _PyHASH_MULTIPLIER: c_ulong = 1000003; @@ -25,29 +20,6 @@ pub const _PyHASH_MULTIPLIER: c_ulong = 1000003; // skipped non-limited _Py_HashSecret_t -#[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))] -#[repr(C)] -#[derive(Copy, Clone)] -pub struct PyHash_FuncDef { - pub hash: Option Py_hash_t>, - pub name: *const c_char, - pub hash_bits: c_int, - pub seed_bits: c_int, -} - -#[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))] -impl Default for PyHash_FuncDef { - #[inline] - fn default() -> Self { - unsafe { std::mem::zeroed() } - } -} - -extern "C" { - #[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))] - pub fn PyHash_GetFuncDef() -> *mut PyHash_FuncDef; -} - // skipped Py_HASH_CUTOFF pub const Py_HASH_EXTERNAL: c_int = 0; diff --git a/include/pyo3/pyo3-ffi/src/pyport.rs b/include/pyo3/pyo3-ffi/src/pyport.rs index a144c67f..e524831d 100644 --- a/include/pyo3/pyo3-ffi/src/pyport.rs +++ b/include/pyo3/pyo3-ffi/src/pyport.rs @@ -1,3 +1,8 @@ +// NB libc does not define this constant on all platforms, so we hard code it +// like CPython does. +// https://github.com/python/cpython/blob/d8b9011702443bb57579f8834f3effe58e290dfc/Include/pyport.h#L372 +pub const INT_MAX: std::os::raw::c_int = 2147483647; + pub type PY_UINT32_T = u32; pub type PY_UINT64_T = u64; @@ -11,8 +16,8 @@ pub type Py_ssize_t = ::libc::ssize_t; pub type Py_hash_t = Py_ssize_t; pub type Py_uhash_t = ::libc::size_t; -pub const PY_SSIZE_T_MIN: Py_ssize_t = isize::MIN as Py_ssize_t; -pub const PY_SSIZE_T_MAX: Py_ssize_t = isize::MAX as Py_ssize_t; +pub const PY_SSIZE_T_MIN: Py_ssize_t = Py_ssize_t::MIN; +pub const PY_SSIZE_T_MAX: Py_ssize_t = Py_ssize_t::MAX; #[cfg(target_endian = "big")] pub const PY_BIG_ENDIAN: usize = 1; diff --git a/include/pyo3/pyo3-ffi/src/pystate.rs b/include/pyo3/pyo3-ffi/src/pystate.rs index 23aeea3a..cc16e554 100644 --- a/include/pyo3/pyo3-ffi/src/pystate.rs +++ b/include/pyo3/pyo3-ffi/src/pystate.rs @@ -1,3 +1,5 @@ +#[cfg(all(Py_3_10, not(PyPy), not(Py_LIMITED_API)))] +use crate::frameobject::PyFrameObject; use crate::moduleobject::PyModuleDef; use crate::object::PyObject; use std::os::raw::c_int; @@ -7,8 +9,8 @@ use std::os::raw::c_long; pub const MAX_CO_EXTRA_USERS: c_int = 255; -opaque_struct!(PyThreadState); -opaque_struct!(PyInterpreterState); +opaque_struct!(pub PyThreadState); +opaque_struct!(pub PyInterpreterState); extern "C" { #[cfg(not(PyPy))] @@ -63,9 +65,14 @@ extern "C" { } // skipped non-limited / 3.9 PyThreadState_GetInterpreter -// skipped non-limited / 3.9 PyThreadState_GetFrame // skipped non-limited / 3.9 PyThreadState_GetID +extern "C" { + // PyThreadState_GetFrame + #[cfg(all(Py_3_10, not(PyPy), not(Py_LIMITED_API)))] + pub fn PyThreadState_GetFrame(arg1: *mut PyThreadState) -> *mut PyFrameObject; +} + #[repr(C)] #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum PyGILState_STATE { @@ -73,9 +80,70 @@ pub enum PyGILState_STATE { PyGILState_UNLOCKED, } +#[cfg(not(Py_3_14))] +struct HangThread; + +#[cfg(not(Py_3_14))] +impl Drop for HangThread { + fn drop(&mut self) { + loop { + std::thread::park(); // Block forever. + } + } +} + +// The PyGILState_Ensure function will call pthread_exit during interpreter shutdown, +// which causes undefined behavior. Redirect to the "safe" version that hangs instead, +// as Python 3.14 does. +// +// See https://github.com/rust-lang/rust/issues/135929 + +// C-unwind only supported (and necessary) since 1.71. Python 3.14+ does not do +// pthread_exit from PyGILState_Ensure (https://github.com/python/cpython/issues/87135). +mod raw { + #[cfg(all(not(Py_3_14), rustc_has_extern_c_unwind))] + extern "C-unwind" { + #[cfg_attr(PyPy, link_name = "PyPyGILState_Ensure")] + pub fn PyGILState_Ensure() -> super::PyGILState_STATE; + } + + #[cfg(not(all(not(Py_3_14), rustc_has_extern_c_unwind)))] + extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyGILState_Ensure")] + pub fn PyGILState_Ensure() -> super::PyGILState_STATE; + } +} + +#[cfg(not(Py_3_14))] +pub unsafe extern "C" fn PyGILState_Ensure() -> PyGILState_STATE { + let guard = HangThread; + // If `PyGILState_Ensure` calls `pthread_exit`, which it does on Python < 3.14 + // when the interpreter is shutting down, this will cause a forced unwind. + // doing a forced unwind through a function with a Rust destructor is unspecified + // behavior. + // + // However, currently it runs the destructor, which will cause the thread to + // hang as it should. + // + // And if we don't catch the unwinding here, then one of our callers probably has a destructor, + // so it's unspecified behavior anyway, and on many configurations causes the process to abort. + // + // The alternative is for pyo3 to contain custom C or C++ code that catches the `pthread_exit`, + // but that's also annoying from a portability point of view. + // + // On Windows, `PyGILState_Ensure` calls `_endthreadex` instead, which AFAICT can't be caught + // and therefore will cause unsafety if there are pinned objects on the stack. AFAICT there's + // nothing we can do it other than waiting for Python 3.14 or not using Windows. At least, + // if there is nothing pinned on the stack, it won't cause the process to crash. + let ret: PyGILState_STATE = raw::PyGILState_Ensure(); + std::mem::forget(guard); + ret +} + +#[cfg(Py_3_14)] +pub use self::raw::PyGILState_Ensure; + extern "C" { - #[cfg_attr(PyPy, link_name = "PyPyGILState_Ensure")] - pub fn PyGILState_Ensure() -> PyGILState_STATE; #[cfg_attr(PyPy, link_name = "PyPyGILState_Release")] pub fn PyGILState_Release(arg1: PyGILState_STATE); #[cfg(not(PyPy))] diff --git a/include/pyo3/pyo3-ffi/src/pythonrun.rs b/include/pyo3/pyo3-ffi/src/pythonrun.rs index e7ea2d2e..80209b58 100644 --- a/include/pyo3/pyo3-ffi/src/pythonrun.rs +++ b/include/pyo3/pyo3-ffi/src/pythonrun.rs @@ -49,12 +49,12 @@ pub const PYOS_STACK_MARGIN: c_int = 2048; // skipped PyOS_CheckStack under Microsoft C #[cfg(not(any(PyPy, Py_LIMITED_API, Py_3_10)))] -opaque_struct!(_mod); +opaque_struct!(pub _mod); #[cfg(not(any(PyPy, Py_3_10)))] -opaque_struct!(symtable); +opaque_struct!(pub symtable); #[cfg(not(any(PyPy, Py_3_10)))] -opaque_struct!(_node); +opaque_struct!(pub _node); #[cfg(not(any(PyPy, Py_LIMITED_API, Py_3_10)))] #[cfg_attr(Py_3_9, deprecated(note = "Python 3.9"))] diff --git a/include/pyo3/pyo3-ffi/src/refcount.rs b/include/pyo3/pyo3-ffi/src/refcount.rs new file mode 100644 index 00000000..fcb5f45b --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/refcount.rs @@ -0,0 +1,369 @@ +use crate::pyport::Py_ssize_t; +use crate::PyObject; +#[cfg(py_sys_config = "Py_REF_DEBUG")] +use std::os::raw::c_char; +#[cfg(Py_3_12)] +use std::os::raw::c_int; +#[cfg(all(Py_3_14, any(not(Py_GIL_DISABLED), target_pointer_width = "32")))] +use std::os::raw::c_long; +#[cfg(any(Py_GIL_DISABLED, all(Py_3_12, not(Py_3_14))))] +use std::os::raw::c_uint; +#[cfg(all(Py_3_14, not(Py_GIL_DISABLED)))] +use std::os::raw::c_ulong; +use std::ptr; +#[cfg(Py_GIL_DISABLED)] +use std::sync::atomic::Ordering::Relaxed; + +#[cfg(Py_3_14)] +const _Py_STATICALLY_ALLOCATED_FLAG: c_int = 1 << 7; + +#[cfg(all(Py_3_12, not(Py_3_14)))] +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 + } +}; + +// comments in Python.h about the choices for these constants + +#[cfg(all(Py_3_14, not(Py_GIL_DISABLED)))] +const _Py_IMMORTAL_INITIAL_REFCNT: Py_ssize_t = { + if cfg!(target_pointer_width = "64") { + ((3 as c_ulong) << (30 as c_ulong)) as Py_ssize_t + } else { + ((5 as c_long) << (28 as c_long)) as Py_ssize_t + } +}; + +#[cfg(all(Py_3_14, not(Py_GIL_DISABLED)))] +const _Py_STATIC_IMMORTAL_INITIAL_REFCNT: Py_ssize_t = { + if cfg!(target_pointer_width = "64") { + _Py_IMMORTAL_INITIAL_REFCNT + | ((_Py_STATICALLY_ALLOCATED_FLAG as Py_ssize_t) << (32 as Py_ssize_t)) + } else { + ((7 as c_long) << (28 as c_long)) as Py_ssize_t + } +}; + +#[cfg(all(Py_3_14, target_pointer_width = "32"))] +const _Py_IMMORTAL_MINIMUM_REFCNT: Py_ssize_t = ((1 as c_long) << (30 as c_long)) as Py_ssize_t; + +#[cfg(all(Py_3_14, target_pointer_width = "32"))] +const _Py_STATIC_IMMORTAL_MINIMUM_REFCNT: Py_ssize_t = + ((6 as c_long) << (28 as c_long)) as Py_ssize_t; + +#[cfg(all(Py_3_14, Py_GIL_DISABLED))] +const _Py_IMMORTAL_INITIAL_REFCNT: Py_ssize_t = c_uint::MAX as Py_ssize_t; + +#[cfg(Py_GIL_DISABLED)] +pub(crate) const _Py_IMMORTAL_REFCNT_LOCAL: u32 = u32::MAX; + +#[cfg(Py_GIL_DISABLED)] +const _Py_REF_SHARED_SHIFT: isize = 2; +// skipped private _Py_REF_SHARED_FLAG_MASK + +// skipped private _Py_REF_SHARED_INIT +// skipped private _Py_REF_MAYBE_WEAKREF +// skipped private _Py_REF_QUEUED +// skipped private _Py_REF_MERGED + +// skipped private _Py_REF_SHARED + +extern "C" { + #[cfg(all(Py_3_14, Py_LIMITED_API))] + pub fn Py_REFCNT(ob: *mut PyObject) -> Py_ssize_t; +} + +#[cfg(not(all(Py_3_14, Py_LIMITED_API)))] +#[inline] +pub unsafe fn Py_REFCNT(ob: *mut PyObject) -> Py_ssize_t { + #[cfg(Py_GIL_DISABLED)] + { + let local = (*ob).ob_ref_local.load(Relaxed); + if local == _Py_IMMORTAL_REFCNT_LOCAL { + #[cfg(not(Py_3_14))] + return _Py_IMMORTAL_REFCNT; + #[cfg(Py_3_14)] + return _Py_IMMORTAL_INITIAL_REFCNT; + } + let shared = (*ob).ob_ref_shared.load(Relaxed); + local as Py_ssize_t + Py_ssize_t::from(shared >> _Py_REF_SHARED_SHIFT) + } + + #[cfg(all(Py_LIMITED_API, Py_3_14))] + { + Py_REFCNT(ob) + } + + #[cfg(all(not(Py_GIL_DISABLED), not(all(Py_LIMITED_API, Py_3_14)), Py_3_12))] + { + (*ob).ob_refcnt.ob_refcnt + } + + #[cfg(all(not(Py_GIL_DISABLED), not(Py_3_12), not(GraalPy)))] + { + (*ob).ob_refcnt + } + + #[cfg(all(not(Py_GIL_DISABLED), not(Py_3_12), GraalPy))] + { + _Py_REFCNT(ob) + } +} + +#[cfg(Py_3_12)] +#[inline(always)] +unsafe fn _Py_IsImmortal(op: *mut PyObject) -> c_int { + #[cfg(all(target_pointer_width = "64", not(Py_GIL_DISABLED)))] + { + (((*op).ob_refcnt.ob_refcnt as crate::PY_INT32_T) < 0) as c_int + } + + #[cfg(all(target_pointer_width = "32", not(Py_GIL_DISABLED)))] + { + #[cfg(not(Py_3_14))] + { + ((*op).ob_refcnt.ob_refcnt == _Py_IMMORTAL_REFCNT) as c_int + } + + #[cfg(Py_3_14)] + { + ((*op).ob_refcnt.ob_refcnt >= _Py_IMMORTAL_MINIMUM_REFCNT) as c_int + } + } + + #[cfg(Py_GIL_DISABLED)] + { + ((*op).ob_ref_local.load(Relaxed) == _Py_IMMORTAL_REFCNT_LOCAL) as c_int + } +} + +// skipped _Py_IsStaticImmortal + +// TODO: Py_SET_REFCNT + +extern "C" { + #[cfg(all(py_sys_config = "Py_REF_DEBUG", not(Py_LIMITED_API)))] + fn _Py_NegativeRefcount(filename: *const c_char, lineno: c_int, op: *mut PyObject); + #[cfg(all(Py_3_12, py_sys_config = "Py_REF_DEBUG", not(Py_LIMITED_API)))] + fn _Py_INCREF_IncRefTotal(); + #[cfg(all(Py_3_12, py_sys_config = "Py_REF_DEBUG", not(Py_LIMITED_API)))] + fn _Py_DECREF_DecRefTotal(); + + #[cfg_attr(PyPy, link_name = "_PyPy_Dealloc")] + fn _Py_Dealloc(arg1: *mut PyObject); + + #[cfg_attr(PyPy, link_name = "PyPy_IncRef")] + #[cfg_attr(GraalPy, link_name = "_Py_IncRef")] + pub fn Py_IncRef(o: *mut PyObject); + #[cfg_attr(PyPy, link_name = "PyPy_DecRef")] + #[cfg_attr(GraalPy, link_name = "_Py_DecRef")] + pub fn Py_DecRef(o: *mut PyObject); + + #[cfg(all(Py_3_10, not(PyPy)))] + fn _Py_IncRef(o: *mut PyObject); + #[cfg(all(Py_3_10, not(PyPy)))] + fn _Py_DecRef(o: *mut PyObject); + + #[cfg(GraalPy)] + fn _Py_REFCNT(arg1: *const PyObject) -> Py_ssize_t; +} + +#[inline(always)] +pub unsafe fn Py_INCREF(op: *mut PyObject) { + // On limited API, the free-threaded build, or with refcount debugging, let the interpreter do refcounting + // TODO: reimplement the logic in the header in the free-threaded build, for a little bit of performance. + #[cfg(any( + Py_GIL_DISABLED, + Py_LIMITED_API, + py_sys_config = "Py_REF_DEBUG", + GraalPy + ))] + { + // _Py_IncRef was added to the ABI in 3.10; skips null checks + #[cfg(all(Py_3_10, not(PyPy)))] + { + _Py_IncRef(op); + } + + #[cfg(any(not(Py_3_10), PyPy))] + { + Py_IncRef(op); + } + } + + // version-specific builds are allowed to directly manipulate the reference count + #[cfg(not(any( + Py_GIL_DISABLED, + Py_LIMITED_API, + py_sys_config = "Py_REF_DEBUG", + GraalPy + )))] + { + #[cfg(all(Py_3_14, target_pointer_width = "64"))] + { + let cur_refcnt = (*op).ob_refcnt.ob_refcnt; + if (cur_refcnt as i32) < 0 { + return; + } + (*op).ob_refcnt.ob_refcnt = cur_refcnt.wrapping_add(1); + } + + #[cfg(all(Py_3_12, not(Py_3_14), 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 + } +} + +// skipped _Py_DecRefShared +// skipped _Py_DecRefSharedDebug +// skipped _Py_MergeZeroLocalRefcount + +#[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) { + // On limited API, the free-threaded build, or with refcount debugging, let the interpreter do refcounting + // On 3.12+ we implement refcount debugging to get better assertion locations on negative refcounts + // TODO: reimplement the logic in the header in the free-threaded build, for a little bit of performance. + #[cfg(any( + Py_GIL_DISABLED, + Py_LIMITED_API, + all(py_sys_config = "Py_REF_DEBUG", not(Py_3_12)), + GraalPy + ))] + { + // _Py_DecRef was added to the ABI in 3.10; skips null checks + #[cfg(all(Py_3_10, not(PyPy)))] + { + _Py_DecRef(op); + } + + #[cfg(any(not(Py_3_10), PyPy))] + { + Py_DecRef(op); + } + } + + #[cfg(not(any( + Py_GIL_DISABLED, + Py_LIMITED_API, + all(py_sys_config = "Py_REF_DEBUG", not(Py_3_12)), + GraalPy + )))] + { + #[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(py_sys_config = "Py_REF_DEBUG")] + _Py_DECREF_DecRefTotal(); + + #[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(); + let filename = std::ffi::CString::new(location.file()).unwrap(); + _Py_NegativeRefcount(filename.as_ptr(), location.line() as i32, 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); + } + } + } +} + +#[inline] +pub unsafe fn Py_CLEAR(op: *mut *mut PyObject) { + let tmp = *op; + if !tmp.is_null() { + *op = ptr::null_mut(); + Py_DECREF(tmp); + } +} + +#[inline] +pub unsafe fn Py_XINCREF(op: *mut PyObject) { + if !op.is_null() { + Py_INCREF(op) + } +} + +#[inline] +pub unsafe fn Py_XDECREF(op: *mut PyObject) { + if !op.is_null() { + Py_DECREF(op) + } +} + +extern "C" { + #[cfg(all(Py_3_10, Py_LIMITED_API, not(PyPy)))] + #[cfg_attr(docsrs, doc(cfg(Py_3_10)))] + pub fn Py_NewRef(obj: *mut PyObject) -> *mut PyObject; + #[cfg(all(Py_3_10, Py_LIMITED_API, not(PyPy)))] + #[cfg_attr(docsrs, doc(cfg(Py_3_10)))] + pub fn Py_XNewRef(obj: *mut PyObject) -> *mut PyObject; +} + +// macro _Py_NewRef not public; reimplemented directly inside Py_NewRef here +// macro _Py_XNewRef not public; reimplemented directly inside Py_XNewRef here + +#[cfg(all(Py_3_10, any(not(Py_LIMITED_API), PyPy)))] +#[cfg_attr(docsrs, doc(cfg(Py_3_10)))] +#[inline] +pub unsafe fn Py_NewRef(obj: *mut PyObject) -> *mut PyObject { + Py_INCREF(obj); + obj +} + +#[cfg(all(Py_3_10, any(not(Py_LIMITED_API), PyPy)))] +#[cfg_attr(docsrs, doc(cfg(Py_3_10)))] +#[inline] +pub unsafe fn Py_XNewRef(obj: *mut PyObject) -> *mut PyObject { + Py_XINCREF(obj); + obj +} diff --git a/include/pyo3/pyo3-ffi/src/setobject.rs b/include/pyo3/pyo3-ffi/src/setobject.rs index 9d5351fc..87e33e80 100644 --- a/include/pyo3/pyo3-ffi/src/setobject.rs +++ b/include/pyo3/pyo3-ffi/src/setobject.rs @@ -39,11 +39,7 @@ pub unsafe fn PySet_GET_SIZE(so: *mut PyObject) -> Py_ssize_t { (*so).used } -#[cfg(not(Py_LIMITED_API))] -#[cfg_attr(windows, link(name = "pythonXY"))] -extern "C" { - pub static mut _PySet_Dummy: *mut PyObject; -} +// skipped _PySet_Dummy extern "C" { #[cfg(not(Py_LIMITED_API))] diff --git a/include/pyo3/pyo3-ffi/src/weakrefobject.rs b/include/pyo3/pyo3-ffi/src/weakrefobject.rs index 305dc290..88a1bf90 100644 --- a/include/pyo3/pyo3-ffi/src/weakrefobject.rs +++ b/include/pyo3/pyo3-ffi/src/weakrefobject.rs @@ -4,16 +4,18 @@ use std::os::raw::c_int; use std::ptr::addr_of_mut; #[cfg(all(not(PyPy), Py_LIMITED_API, not(GraalPy)))] -opaque_struct!(PyWeakReference); +opaque_struct!(pub PyWeakReference); #[cfg(all(not(PyPy), not(Py_LIMITED_API), not(GraalPy)))] pub use crate::_PyWeakReference as PyWeakReference; #[cfg_attr(windows, link(name = "pythonXY"))] extern "C" { + // TODO: PyO3 is depending on this symbol in `reference.rs`, we should change this and + // remove the export as this is a private symbol. pub static mut _PyWeakref_RefType: PyTypeObject; - pub static mut _PyWeakref_ProxyType: PyTypeObject; - pub static mut _PyWeakref_CallableProxyType: PyTypeObject; + static mut _PyWeakref_ProxyType: PyTypeObject; + static mut _PyWeakref_CallableProxyType: PyTypeObject; #[cfg(PyPy)] #[link_name = "PyPyWeakref_CheckRef"] From d2570d2f25eba771468f41020a2ad69e0cbdbbc7 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 23 May 2025 17:00:34 +0200 Subject: [PATCH 2/6] Add pyo3-ffi patch for _PyLong_AsByteArray with exceptions Co-authored-by: ijl --- ...i-pylong-asbytearray-with-exceptions.patch | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 include/pyo3-ffi-pylong-asbytearray-with-exceptions.patch diff --git a/include/pyo3-ffi-pylong-asbytearray-with-exceptions.patch b/include/pyo3-ffi-pylong-asbytearray-with-exceptions.patch new file mode 100644 index 00000000..20e5d7a3 --- /dev/null +++ b/include/pyo3-ffi-pylong-asbytearray-with-exceptions.patch @@ -0,0 +1,23 @@ +diff --git a/include/pyo3/pyo3-ffi/src/cpython/longobject.rs b/include/pyo3/pyo3-ffi/src/cpython/longobject.rs +index 45acaae..6063575 100644 +--- a/include/pyo3/pyo3-ffi/src/cpython/longobject.rs ++++ b/include/pyo3/pyo3-ffi/src/cpython/longobject.rs +@@ -61,6 +61,18 @@ extern "C" { + is_signed: c_int, + ) -> *mut PyObject; + ++ #[cfg(Py_3_13)] ++ #[cfg_attr(PyPy, link_name = "_PyPyLong_AsByteArrayO")] ++ pub fn _PyLong_AsByteArray( ++ v: *mut PyLongObject, ++ bytes: *mut c_uchar, ++ n: size_t, ++ little_endian: c_int, ++ is_signed: c_int, ++ with_exceptions: c_int, ++ ) -> c_int; ++ ++ #[cfg(not(Py_3_13))] + #[cfg_attr(PyPy, link_name = "_PyPyLong_AsByteArrayO")] + pub fn _PyLong_AsByteArray( + v: *mut PyLongObject, From d01aea4541800dca412e3f4c3068e974f18c3218 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 23 May 2025 18:26:19 +0200 Subject: [PATCH 3/6] Apply pyo3-ffi patch --- include/pyo3/pyo3-ffi/src/cpython/longobject.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/include/pyo3/pyo3-ffi/src/cpython/longobject.rs b/include/pyo3/pyo3-ffi/src/cpython/longobject.rs index 45acaae5..6063575e 100644 --- a/include/pyo3/pyo3-ffi/src/cpython/longobject.rs +++ b/include/pyo3/pyo3-ffi/src/cpython/longobject.rs @@ -61,6 +61,18 @@ extern "C" { is_signed: c_int, ) -> *mut PyObject; + #[cfg(Py_3_13)] + #[cfg_attr(PyPy, link_name = "_PyPyLong_AsByteArrayO")] + pub fn _PyLong_AsByteArray( + v: *mut PyLongObject, + bytes: *mut c_uchar, + n: size_t, + little_endian: c_int, + is_signed: c_int, + with_exceptions: c_int, + ) -> c_int; + + #[cfg(not(Py_3_13))] #[cfg_attr(PyPy, link_name = "_PyPyLong_AsByteArrayO")] pub fn _PyLong_AsByteArray( v: *mut PyLongObject, From 6427dba13406b499bc2edc033c0eeeaafdfa4ad4 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 23 May 2025 15:16:24 +0200 Subject: [PATCH 4/6] Fix _PyDict_SetItem_KnownHash_LockHeld --- src/util.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/util.rs b/src/util.rs index ccf425ce..0a255c3f 100644 --- a/src/util.rs +++ b/src/util.rs @@ -258,19 +258,9 @@ macro_rules! pydict_setitem { ($dict:expr, $pykey:expr, $pyval:expr) => { debug_assert!(ffi!(Py_REFCNT($dict)) == 1); debug_assert!(str_hash!($pykey) != -1); - #[cfg(not(Py_3_13))] unsafe { let _ = pyo3_ffi::_PyDict_SetItem_KnownHash($dict, $pykey, $pyval, str_hash!($pykey)); } - #[cfg(Py_3_13)] - unsafe { - let _ = pyo3_ffi::_PyDict_SetItem_KnownHash_LockHeld( - $dict.cast::(), - $pykey, - $pyval, - str_hash!($pykey), - ); - } reverse_pydict_incref!($pykey); reverse_pydict_incref!($pyval); }; From 80779519c9363a81884e77358e62fb443a584039 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 23 May 2025 15:16:40 +0200 Subject: [PATCH 5/6] Fix _Py_IsImmortal --- src/util.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/util.rs b/src/util.rs index 0a255c3f..bbfb72ed 100644 --- a/src/util.rs +++ b/src/util.rs @@ -120,10 +120,8 @@ macro_rules! str_from_slice { macro_rules! reverse_pydict_incref { ($op:expr) => { unsafe { - if pyo3_ffi::_Py_IsImmortal($op) == 0 { - debug_assert!(ffi!(Py_REFCNT($op)) >= 2); - (*$op).ob_refcnt.ob_refcnt -= 1; - } + debug_assert!(ffi!(Py_REFCNT($op)) >= 2); + (*$op).ob_refcnt.ob_refcnt -= 1; } }; } From b90995f44f80176d403b720d706dfe5a027855ff Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 23 May 2025 17:12:44 +0200 Subject: [PATCH 6/6] Use PyUnicode API --- src/serialize/per_type/dict.rs | 4 ++-- src/serialize/per_type/unicode.rs | 4 ++-- src/str/ffi.rs | 29 +++++++++++++++-------------- src/str/pyunicode_new.rs | 3 +++ 4 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/serialize/per_type/dict.rs b/src/serialize/per_type/dict.rs index e2e04f62..cf2c3a4b 100644 --- a/src/serialize/per_type/dict.rs +++ b/src/serialize/per_type/dict.rs @@ -13,7 +13,7 @@ use crate::serialize::per_type::{ }; use crate::serialize::serializer::PyObjectSerializer; use crate::serialize::state::SerializerState; -use crate::str::{unicode_to_str, unicode_to_str_via_ffi}; +use crate::str::unicode_to_str; use crate::typeref::{STR_TYPE, TRUE, VALUE_STR}; use crate::util::isize_to_usize; use compact_str::CompactString; @@ -321,7 +321,7 @@ fn non_str_str(key: *mut pyo3_ffi::PyObject) -> Result Result { - let uni = unicode_to_str_via_ffi(key); + let uni = unicode_to_str(key); if unlikely!(uni.is_none()) { Err(SerializeError::InvalidStr) } else { diff --git a/src/serialize/per_type/unicode.rs b/src/serialize/per_type/unicode.rs index 23270916..7267b454 100644 --- a/src/serialize/per_type/unicode.rs +++ b/src/serialize/per_type/unicode.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: (Apache-2.0 OR MIT) use crate::serialize::error::SerializeError; -use crate::str::{unicode_to_str, unicode_to_str_via_ffi}; +use crate::str::unicode_to_str; use serde::ser::{Serialize, Serializer}; @@ -50,7 +50,7 @@ impl Serialize for StrSubclassSerializer { where S: Serializer, { - let uni = unicode_to_str_via_ffi(self.ptr); + let uni = unicode_to_str(self.ptr); if unlikely!(uni.is_none()) { err!(SerializeError::InvalidStr) } diff --git a/src/str/ffi.rs b/src/str/ffi.rs index 4f1c76bd..6b57e7ae 100644 --- a/src/str/ffi.rs +++ b/src/str/ffi.rs @@ -1,25 +1,19 @@ // SPDX-License-Identifier: (Apache-2.0 OR MIT) use crate::util::isize_to_usize; -use core::ffi::c_void; -use pyo3_ffi::{PyASCIIObject, PyCompactUnicodeObject, PyObject, Py_hash_t}; +#[cfg(not(Py_3_14))] +use pyo3_ffi::PyCompactUnicodeObject; +use pyo3_ffi::{ + PyASCIIObject, PyObject, PyUnicode_DATA, PyUnicode_GET_LENGTH, PyUnicode_KIND, Py_hash_t, +}; // see unicodeobject.h for documentation #[inline] pub fn hash_str(op: *mut PyObject) -> Py_hash_t { unsafe { - let data_ptr: *mut c_void = if (*op.cast::()).compact() == 1 - && (*op.cast::()).ascii() == 1 - { - op.cast::().offset(1).cast::() - } else { - op.cast::() - .offset(1) - .cast::() - }; - let num_bytes = - (*op.cast::()).length * ((*op.cast::()).kind()) as isize; + let data_ptr = PyUnicode_DATA(op); + let num_bytes = PyUnicode_GET_LENGTH(op) * PyUnicode_KIND(op) as isize; #[cfg(Py_3_14)] let hash = pyo3_ffi::Py_HashBuffer(data_ptr, num_bytes); #[cfg(not(Py_3_14))] @@ -30,7 +24,7 @@ pub fn hash_str(op: *mut PyObject) -> Py_hash_t { } #[inline(never)] -pub fn unicode_to_str_via_ffi(op: *mut PyObject) -> Option<&'static str> { +fn unicode_to_str_via_ffi(op: *mut PyObject) -> Option<&'static str> { let mut str_size: pyo3_ffi::Py_ssize_t = 0; let ptr = ffi!(PyUnicode_AsUTF8AndSize(op, &mut str_size)).cast::(); if unlikely!(ptr.is_null()) { @@ -40,6 +34,7 @@ pub fn unicode_to_str_via_ffi(op: *mut PyObject) -> Option<&'static str> { } } +#[cfg(not(Py_3_14))] #[inline] pub fn unicode_to_str(op: *mut PyObject) -> Option<&'static str> { unsafe { @@ -58,3 +53,9 @@ pub fn unicode_to_str(op: *mut PyObject) -> Option<&'static str> { } } } + +#[cfg(Py_3_14)] +#[inline] +pub fn unicode_to_str(op: *mut PyObject) -> Option<&'static str> { + unicode_to_str_via_ffi(op) +} diff --git a/src/str/pyunicode_new.rs b/src/str/pyunicode_new.rs index 7b1c2df2..738b8489 100644 --- a/src/str/pyunicode_new.rs +++ b/src/str/pyunicode_new.rs @@ -8,7 +8,9 @@ macro_rules! validate_str { #[cfg(not(Py_3_12))] debug_assert!((*($ptr.cast::())).ready() == 1); + #[cfg(not(Py_3_14))] debug_assert!((*($ptr.cast::())).compact() == 1); + #[cfg(not(Py_3_14))] debug_assert!((*($ptr.cast::())).interned() == 0); debug_assert!(ffi!(_PyUnicode_CheckConsistency($ptr.cast::(), 1)) == 1); @@ -22,6 +24,7 @@ pub fn pyunicode_ascii(buf: *const u8, num_chars: usize) -> *mut pyo3_ffi::PyObj let data_ptr = ptr.cast::().offset(1).cast::(); core::ptr::copy_nonoverlapping(buf, data_ptr, num_chars); core::ptr::write(data_ptr.add(num_chars), 0); + #[cfg(not(Py_3_14))] debug_assert!((*(ptr.cast::())).ascii() == 1); validate_str!(ptr); ptr.cast::()