diff --git a/.github/workflows/artifact.yaml b/.github/workflows/artifact.yaml index cf73c02c..b3facf7f 100644 --- a/.github/workflows/artifact.yaml +++ b/.github/workflows/artifact.yaml @@ -1,7 +1,7 @@ name: artifact on: push env: - RUST_TOOLCHAIN: "nightly-2024-08-05" + RUST_TOOLCHAIN: "nightly-2024-09-25" CARGO_UNSTABLE_SPARSE_REGISTRY: "true" UNSAFE_PYO3_BUILD_FREE_THREADED: "1" UNSAFE_PYO3_SKIP_VERSION_CHECK: "1" @@ -71,54 +71,45 @@ jobs: { interpreter: 'python3.8', package: 'python3.8' }, ] env: + PYTHON: "${{ matrix.python.interpreter }}" + PYTHON_PACKAGE: "${{ matrix.python.package }}" CC: "clang" + VENV: ".venv" CFLAGS: "-Os -fstrict-aliasing -fno-plt -flto=full -emit-llvm" LDFLAGS: "-fuse-ld=lld -Wl,-plugin-opt=also-emit-llvm -Wl,--as-needed -Wl,-zrelro,-znow" RUSTFLAGS: "-C linker=clang -C link-arg=-fuse-ld=lld -C linker-plugin-lto -C lto=fat -C link-arg=-Wl,-zrelro,-znow -Z mir-opt-level=4 -Z virtual-function-elimination -Z threads=2 -D warnings" PATH: "/__w/orjson/orjson/.venv/bin:/github/home/.cargo/bin:/root/.local/bin:/root/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" container: - image: fedora:41 + image: fedora:42 steps: - name: cpuinfo run: cat /proc/cpuinfo - name: Build environment pre-clone - run: | - dnf copr enable -y @fedora-llvm-team/llvm19 - dnf install -y rustup clang lld ${{ matrix.python.package }} git - rustup-init --default-toolchain "${RUST_TOOLCHAIN}-x86_64-unknown-linux-gnu" --profile minimal --component rust-src -y + run: dnf install -y git - uses: actions/checkout@v4 - name: Build environment post-clone run: | - cargo fetch --target=x86_64-unknown-linux-gnu & - - mkdir .cargo - cp ci/config.toml .cargo/config.toml + ./script/install-fedora - curl -LsSf https://astral.sh/uv/install.sh | sh - uv venv --python ${{ matrix.python.interpreter }} - uv pip install --upgrade "maturin>=1,<2" -r test/requirements.txt -r integration/requirements.txt + # source "${HOME}/.cargo/env" + # source "${VENV}/bin/activate" - - name: maturin - run: | - source .venv/bin/activate maturin build --release --strip \ --features=avx512,no-panic,unstable-simd,yyjson \ - --compatibility manylinux_2_17 \ - --interpreter ${{ matrix.python.interpreter }} \ - --target=x86_64-unknown-linux-gnu - uv pip install target/wheels/orjson*.whl + --compatibility=manylinux_2_17 \ + --interpreter="${PYTHON}" \ + --target="${TARGET}" - - run: source .venv/bin/activate && pytest -s -rxX -v -n 2 test - env: - PYTHONMALLOC: "debug" + uv pip install target/wheels/orjson*.whl - - run: source .venv/bin/activate && ./integration/run thread - - run: source .venv/bin/activate && ./integration/run http - - run: source .venv/bin/activate && ./integration/run init + pytest -s -rxX -v -n 2 test + ./integration/run thread + ./integration/run http + ./integration/run init - name: Store wheels if: "startsWith(github.ref, 'refs/tags/')" @@ -167,9 +158,9 @@ jobs: LDFLAGS: "-Wl,--as-needed" RUSTFLAGS: "-C lto=fat -Z mir-opt-level=4 -Z virtual-function-elimination -Z threads=2 -D warnings -C target-feature=-crt-static" with: - rust-toolchain: nightly-2024-08-05 + rust-toolchain: "${{ env.RUST_TOOLCHAIN }}" rustup-components: rust-src - target: ${{ matrix.platform.target }} + target: "${{ matrix.platform.target }}" manylinux: musllinux_1_2 args: --release --strip --out=dist --features=${{ matrix.platform.features }} -i python${{ matrix.python.version }} @@ -211,6 +202,7 @@ jobs: fail-fast: false matrix: python: [ + { version: '3.13', abi: 'cp313-cp313' }, { version: '3.12', abi: 'cp312-cp312' }, { version: '3.11', abi: 'cp311-cp311' }, { version: '3.10', abi: 'cp310-cp310' }, @@ -262,8 +254,8 @@ jobs: LDFLAGS: "-Wl,--as-needed" RUSTFLAGS: "${{ matrix.target.rustflags }}" with: - target: ${{ matrix.target.target }} - rust-toolchain: nightly-2024-08-05 + target: "${{ matrix.target.target }}" + rust-toolchain: "${{ env.RUST_TOOLCHAIN }}" rustup-components: rust-src manylinux: auto args: --release --strip --out=dist --features=${{ matrix.target.features }} -i python${{ matrix.python.version }} @@ -328,7 +320,7 @@ jobs: - uses: dtolnay/rust-toolchain@master with: - toolchain: "nightly-2024-08-05" + toolchain: "${{ env.RUST_TOOLCHAIN }}" targets: "aarch64-apple-darwin, x86_64-apple-darwin" components: "rust-src" @@ -398,7 +390,7 @@ jobs: - uses: dtolnay/rust-toolchain@master with: - toolchain: "nightly-2024-08-05" + toolchain: "${{ env.RUST_TOOLCHAIN }}" targets: "aarch64-apple-darwin, x86_64-apple-darwin" components: "rust-src" diff --git a/.github/workflows/debug.yaml b/.github/workflows/debug.yaml index f8564850..60c9fe05 100644 --- a/.github/workflows/debug.yaml +++ b/.github/workflows/debug.yaml @@ -10,8 +10,8 @@ jobs: profile: [ { rust: "1.72", features: "" }, { rust: "1.72", features: "--features=yyjson" }, - { rust: "nightly-2024-08-05", features: "--features=yyjson,unstable-simd"}, - { rust: "nightly-2024-08-05", features: "--features=avx512,yyjson,unstable-simd"}, + { rust: "nightly-2024-09-25", features: "--features=yyjson,unstable-simd"}, + { rust: "nightly-2024-09-25", features: "--features=avx512,yyjson,unstable-simd"}, ] python: [ { version: '3.13' }, diff --git a/Cargo.lock b/Cargo.lock index 68676e35..c6fcd157 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "arrayvec" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" dependencies = [ "serde", ] @@ -19,18 +19,9 @@ checksum = "b993cd767a2bc7307dd87622311ca22c44329cc7a21366206bfa0896827b2bad" [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" - -[[package]] -name = "beef" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" -dependencies = [ - "serde", -] +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "bytecount" @@ -49,9 +40,12 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.8" +version = "1.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "504bdec147f2cc13c8b57ed9401fd8a147cc66b67ad5cb241394244f2c947549" +checksum = "b16803a61b81d9eabb7eae2588776c4c1e584b738ede45fdbb4c972cec1e9945" +dependencies = [ + "shlex", +] [[package]] name = "cfg-if" @@ -100,9 +94,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.30.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e1d97fbe9722ba9bbd0c97051c2956e726562b61f86a25a4360398a40edfc9" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "half" @@ -131,9 +125,9 @@ checksum = "9028f49264629065d057f340a86acb84867925865f73bbf8d47b4d149a7e88b8" [[package]] name = "libc" -version = "0.2.155" +version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" [[package]] name = "memchr" @@ -163,9 +157,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "orjson" @@ -173,7 +167,6 @@ version = "3.10.7" dependencies = [ "arrayvec", "associative-cache", - "beef", "bytecount", "cc", "chrono", @@ -197,18 +190,16 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a" dependencies = [ "unicode-ident", ] [[package]] name = "pyo3-build-config" -version = "0.22.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e8730e591b14492a8945cdff32f089250b05f5accecf74aeddf9e8272ce1fa8" +version = "0.23.0-dev" dependencies = [ "once_cell", "target-lexicon", @@ -216,9 +207,7 @@ dependencies = [ [[package]] name = "pyo3-ffi" -version = "0.22.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e97e919d2df92eb88ca80a037969f44e5e70356559654962cbb3316d00300c6" +version = "0.23.0-dev" dependencies = [ "libc", "pyo3-build-config", @@ -226,18 +215,18 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] [[package]] name = "rustversion" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" [[package]] name = "ryu" @@ -250,18 +239,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.205" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33aedb1a7135da52b7c21791455563facbbcc43d0f0f66165b42c21b3dfb150" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.205" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "692d6f5ac90220161d6774db30c662202721e64aed9058d2c394f451261420c1" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", @@ -270,9 +259,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.122" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784b6203951c57ff748476b126ccb5e8e2959a5c19e5c617ab1956be3dbc68da" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ "itoa", "memchr", @@ -280,11 +269,17 @@ dependencies = [ "serde", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "simdutf8" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "smallvec" @@ -300,9 +295,9 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "syn" -version = "2.0.72" +version = "2.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" dependencies = [ "proc-macro2", "quote", @@ -317,15 +312,15 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unwinding" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc55842d0db6329a669d55a623c674b02d677b16bfb2d24857d4089d41eba882" +checksum = "637d511437df708cee34bdec7ba2f1548d256b7acf3ff20e0a1c559f9bf3a987" dependencies = [ "gimli", "libc", diff --git a/Cargo.toml b/Cargo.toml index 7df36e92..2d80ceda 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,7 +58,6 @@ strict_provenance = [] [dependencies] arrayvec = { version = "0.7", default-features = false, features = ["std", "serde"] } associative-cache = { version = "2", default-features = false } -beef = { version = "0.5", default-features = false, features = ["impl_serde"] } bytecount = { version = "^0.6.7", default-features = false, features = ["runtime-dispatch-simd"] } chrono = { version = "=0.4.34", default-features = false } compact_str = { version = "0.8", default-features = false, features = ["serde"] } @@ -67,7 +66,7 @@ half = { version = "2", default-features = false, features = ["std"] } itoa = { version = "1", default-features = false } itoap = { version = "1", features = ["std", "simd"] } once_cell = { version = "1", default-features = false, features = ["alloc", "race"] } -pyo3-ffi = { version = "^0.22", default-features = false, features = ["extension-module"]} +pyo3-ffi = { path = "include/pyo3/pyo3-ffi", default-features = false, features = ["extension-module"]} ryu = { version = "1", default-features = false } serde = { version = "1", default-features = false } serde_json = { version = "1", default-features = false, features = ["std", "float_roundtrip"] } @@ -78,7 +77,7 @@ xxhash-rust = { version = "^0.8", default-features = false, features = ["xxh3"] [build-dependencies] cc = { version = "1" } -pyo3-build-config = { version = "^0.22" } +pyo3-build-config = { path = "include/pyo3/pyo3-build-config" } version_check = { version = "0.9" } [profile.dev] diff --git a/README.md b/README.md index 5c3e5fad..3205e2ff 100644 --- a/README.md +++ b/README.md @@ -32,13 +32,17 @@ support for 64-bit * does not provide `load()` or `dump()` functions for reading from/writing to file-like objects -orjson supports CPython 3.8, 3.9, 3.10, 3.11, 3.12, and 3.13. It distributes -amd64/x86_64, aarch64/armv8, arm7, POWER/ppc64le, and s390x wheels for Linux, -amd64 and aarch64 wheels for macOS, and amd64 and i686/x86 wheels for Windows. +orjson supports CPython 3.8, 3.9, 3.10, 3.11, 3.12, and 3.13. + +It distributes amd64/x86_64, aarch64/armv8, arm7, POWER/ppc64le, and s390x +wheels for Linux, amd64 and aarch64 wheels for macOS, and amd64 +and i686/x86 wheels for Windows. + orjson does not and will not support PyPy. orjson does not and will not -support PEP 554 subinterpreters. Releases follow semantic versioning and -serializing a new object type without an opt-in flag is considered a -breaking change. +support PEP 554 subinterpreters. + +Releases follow semantic versioning and serializing a new object type +without an opt-in flag is considered a breaking change. orjson is licensed under both the Apache 2.0 and MIT licenses. The repository and issue tracker is @@ -1205,7 +1209,7 @@ It benefits from also having a C build environment to compile a faster deserialization backend. See this project's `manylinux_2_28` builds for an example using clang and LTO. -The project's own CI tests against `nightly-2024-08-05` and stable 1.72. It +The project's own CI tests against `nightly-2024-09-25` and stable 1.72. It is prudent to pin the nightly version because that channel can introduce breaking changes. diff --git a/build.rs b/build.rs index dc65d40f..cbfeea3f 100644 --- a/build.rs +++ b/build.rs @@ -20,6 +20,7 @@ fn main() { println!("cargo:rustc-check-cfg=cfg(Py_3_13)"); println!("cargo:rustc-check-cfg=cfg(Py_3_8)"); println!("cargo:rustc-check-cfg=cfg(Py_3_9)"); + println!("cargo:rustc-check-cfg=cfg(Py_GIL_DISABLED)"); for cfg in pyo3_build_config::get().build_script_outputs() { println!("{cfg}"); @@ -37,18 +38,17 @@ fn main() { println!("cargo:rustc-cfg=feature=\"strict_provenance\""); } - // auto build unstable SIMD on nightly #[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))] if env::var("ORJSON_DISABLE_SIMD").is_err() { + // auto build unstable SIMD on nightly if let Some(true) = version_check::supports_feature("portable_simd") { println!("cargo:rustc-cfg=feature=\"unstable-simd\""); - - // auto build AVX512 on x86-64-v4 or supporting native targets - #[cfg(all(target_arch = "x86_64", target_feature = "avx512vl"))] - if let Some(true) = version_check::supports_feature("stdarch_x86_avx512") { - if env::var("ORJSON_DISABLE_AVX512").is_err() { - println!("cargo:rustc-cfg=feature=\"avx512\""); - } + } + // auto build AVX512 on x86-64-v4 or supporting native targets + #[cfg(all(target_arch = "x86_64", target_feature = "avx512vl"))] + if let Some(true) = version_check::supports_feature("stdarch_x86_avx512") { + if env::var("ORJSON_DISABLE_AVX512").is_err() { + println!("cargo:rustc-cfg=feature=\"avx512\""); } } } diff --git a/ci/azure-pipelines.yml b/ci/azure-pipelines.yml index 4a1387d2..edf6c341 100644 --- a/ci/azure-pipelines.yml +++ b/ci/azure-pipelines.yml @@ -1,5 +1,5 @@ variables: - toolchain: nightly-2024-08-05 + toolchain: nightly-2024-09-13 jobs: diff --git a/include/pyo3/pyo3-build-config/Cargo.toml b/include/pyo3/pyo3-build-config/Cargo.toml new file mode 100644 index 00000000..a5549df3 --- /dev/null +++ b/include/pyo3/pyo3-build-config/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "pyo3-build-config" +version = "0.23.0-dev" +description = "Build configuration for the PyO3 ecosystem" +authors = ["PyO3 Project and Contributors "] +keywords = ["pyo3", "python", "cpython", "ffi"] +homepage = "https://github.com/pyo3/pyo3" +repository = "https://github.com/pyo3/pyo3" +categories = ["api-bindings", "development-tools::ffi"] +license = "MIT OR Apache-2.0" +edition = "2021" + +[dependencies] +once_cell = "1" +python3-dll-a = { version = "0.2.6", optional = true } +target-lexicon = "0.12.14" + +[build-dependencies] +python3-dll-a = { version = "0.2.6", optional = true } +target-lexicon = "0.12.14" + +[features] +default = [] + +# Attempt to resolve a Python interpreter config for building in the build +# script. If this feature isn't enabled, the build script no-ops. +resolve-config = [] + +# This feature is enabled by pyo3 when building an extension module. +extension-module = [] + +# These features are enabled by pyo3 when building Stable ABI extension modules. +abi3 = [] +abi3-py37 = ["abi3-py38"] +abi3-py38 = ["abi3-py39"] +abi3-py39 = ["abi3-py310"] +abi3-py310 = ["abi3-py311"] +abi3-py311 = ["abi3-py312"] +abi3-py312 = ["abi3"] + +[package.metadata.docs.rs] +features = ["resolve-config"] diff --git a/include/pyo3/pyo3-build-config/build.rs b/include/pyo3/pyo3-build-config/build.rs new file mode 100644 index 00000000..a6e767ed --- /dev/null +++ b/include/pyo3/pyo3-build-config/build.rs @@ -0,0 +1,58 @@ +// Import some modules from this crate inline to generate the build config. +// Allow dead code because not all code in the modules is used in this build script. + +#[path = "src/impl_.rs"] +#[allow(dead_code)] +mod impl_; + +#[path = "src/errors.rs"] +#[allow(dead_code)] +mod errors; + +use std::{env, path::Path}; + +use errors::{Context, Result}; +use impl_::{make_interpreter_config, InterpreterConfig}; + +fn configure(interpreter_config: Option, name: &str) -> Result { + let target = Path::new(&env::var_os("OUT_DIR").unwrap()).join(name); + if let Some(config) = interpreter_config { + config + .to_writer(&mut std::fs::File::create(&target).with_context(|| { + format!("failed to write config file at {}", target.display()) + })?)?; + Ok(true) + } else { + std::fs::File::create(&target) + .with_context(|| format!("failed to create new file at {}", target.display()))?; + Ok(false) + } +} + +fn generate_build_configs() -> Result<()> { + // If PYO3_CONFIG_FILE is set, copy it into the crate. + let configured = configure( + InterpreterConfig::from_pyo3_config_file_env().transpose()?, + "pyo3-build-config-file.txt", + )?; + + if configured { + // Don't bother trying to find an interpreter on the host system + // if the user-provided config file is present. + configure(None, "pyo3-build-config.txt")?; + } else { + configure(Some(make_interpreter_config()?), "pyo3-build-config.txt")?; + } + Ok(()) +} + +fn main() { + if std::env::var("CARGO_FEATURE_RESOLVE_CONFIG").is_ok() { + if let Err(e) = generate_build_configs() { + eprintln!("error: {}", e.report()); + std::process::exit(1) + } + } else { + eprintln!("resolve-config feature not enabled; build script in no-op mode"); + } +} diff --git a/include/pyo3/pyo3-build-config/src/errors.rs b/include/pyo3/pyo3-build-config/src/errors.rs new file mode 100644 index 00000000..87c59a99 --- /dev/null +++ b/include/pyo3/pyo3-build-config/src/errors.rs @@ -0,0 +1,153 @@ +/// A simple macro for returning an error. Resembles anyhow::bail. +#[macro_export] +#[doc(hidden)] +macro_rules! bail { + ($($args: tt)+) => { return Err(format!($($args)+).into()) }; +} + +/// A simple macro for checking a condition. Resembles anyhow::ensure. +#[macro_export] +#[doc(hidden)] +macro_rules! ensure { + ($condition:expr, $($args: tt)+) => { if !($condition) { bail!($($args)+) } }; +} + +/// Show warning. +#[macro_export] +#[doc(hidden)] +macro_rules! warn { + ($($args: tt)+) => { + println!("{}", $crate::format_warn!($($args)+)) + }; +} + +/// Format warning into string. +#[macro_export] +#[doc(hidden)] +macro_rules! format_warn { + ($($args: tt)+) => { + format!("cargo:warning={}", format_args!($($args)+)) + }; +} + +/// A simple error implementation which allows chaining of errors, inspired somewhat by anyhow. +#[derive(Debug)] +pub struct Error { + value: String, + source: Option>, +} + +/// Error report inspired by +/// +pub struct ErrorReport<'a>(&'a Error); + +impl Error { + pub fn report(&self) -> ErrorReport<'_> { + ErrorReport(self) + } +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.value) + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + self.source.as_deref() + } +} + +impl std::fmt::Display for ErrorReport<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use std::error::Error; + self.0.fmt(f)?; + let mut source = self.0.source(); + if source.is_some() { + writeln!(f, "\ncaused by:")?; + let mut index = 0; + while let Some(some_source) = source { + writeln!(f, " - {}: {}", index, some_source)?; + source = some_source.source(); + index += 1; + } + } + Ok(()) + } +} + +impl From for Error { + fn from(value: String) -> Self { + Self { + value, + source: None, + } + } +} + +impl From<&'_ str> for Error { + fn from(value: &str) -> Self { + value.to_string().into() + } +} + +impl From for Error { + fn from(value: std::convert::Infallible) -> Self { + match value {} + } +} + +pub type Result = std::result::Result; + +pub trait Context { + fn context(self, message: impl Into) -> Result; + fn with_context(self, message: impl FnOnce() -> String) -> Result; +} + +impl Context for Result +where + E: std::error::Error + 'static, +{ + fn context(self, message: impl Into) -> Result { + self.map_err(|error| Error { + value: message.into(), + source: Some(Box::new(error)), + }) + } + + fn with_context(self, message: impl FnOnce() -> String) -> Result { + self.map_err(|error| Error { + value: message(), + source: Some(Box::new(error)), + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn error_report() { + let error: Result<()> = Err(Error::from("there was an internal error")) + .with_context(|| format!("failed to do {}", "something difficult")) + .context("things went wrong"); + + assert_eq!( + error + .unwrap_err() + .report() + .to_string() + .split('\n') + .collect::>(), + vec![ + "things went wrong", + "caused by:", + " - 0: failed to do something difficult", + " - 1: there was an internal error", + "" + ] + ); + } +} diff --git a/include/pyo3/pyo3-build-config/src/impl_.rs b/include/pyo3/pyo3-build-config/src/impl_.rs new file mode 100644 index 00000000..571f9cb5 --- /dev/null +++ b/include/pyo3/pyo3-build-config/src/impl_.rs @@ -0,0 +1,2848 @@ +//! Main implementation module included in both the `pyo3-build-config` library crate +//! and its build script. + +// Optional python3.dll import library generator for Windows +#[cfg(feature = "python3-dll-a")] +#[path = "import_lib.rs"] +mod import_lib; + +use std::{ + collections::{HashMap, HashSet}, + env, + ffi::{OsStr, OsString}, + fmt::Display, + fs::{self, DirEntry}, + io::{BufRead, BufReader, Read, Write}, + path::{Path, PathBuf}, + process::{Command, Stdio}, + str, + str::FromStr, +}; + +pub use target_lexicon::Triple; + +use target_lexicon::{Environment, OperatingSystem}; + +use crate::{ + bail, ensure, + errors::{Context, Error, Result}, + warn, +}; + +/// Minimum Python version PyO3 supports. +pub(crate) const MINIMUM_SUPPORTED_VERSION: PythonVersion = PythonVersion { major: 3, minor: 7 }; + +/// GraalPy may implement the same CPython version over multiple releases. +const MINIMUM_SUPPORTED_VERSION_GRAALPY: PythonVersion = PythonVersion { + major: 24, + minor: 0, +}; + +/// Maximum Python version that can be used as minimum required Python version with abi3. +pub(crate) const ABI3_MAX_MINOR: u8 = 12; + +/// Gets an environment variable owned by cargo. +/// +/// Environment variables set by cargo are expected to be valid UTF8. +pub fn cargo_env_var(var: &str) -> Option { + env::var_os(var).map(|os_string| os_string.to_str().unwrap().into()) +} + +/// Gets an external environment variable, and registers the build script to rerun if +/// the variable changes. +pub fn env_var(var: &str) -> Option { + if cfg!(feature = "resolve-config") { + println!("cargo:rerun-if-env-changed={}", var); + } + env::var_os(var) +} + +/// Gets the compilation target triple from environment variables set by Cargo. +/// +/// Must be called from a crate build script. +pub fn target_triple_from_env() -> Triple { + env::var("TARGET") + .expect("target_triple_from_env() must be called from a build script") + .parse() + .expect("Unrecognized TARGET environment variable value") +} + +/// Configuration needed by PyO3 to build for the correct Python implementation. +/// +/// Usually this is queried directly from the Python interpreter, or overridden using the +/// `PYO3_CONFIG_FILE` environment variable. +/// +/// When the `PYO3_NO_PYTHON` variable is set, or during cross compile situations, then alternative +/// strategies are used to populate this type. +#[cfg_attr(test, derive(Debug, PartialEq, Eq))] +pub struct InterpreterConfig { + /// The Python implementation flavor. + /// + /// Serialized to `implementation`. + pub implementation: PythonImplementation, + + /// Python `X.Y` version. e.g. `3.9`. + /// + /// Serialized to `version`. + pub version: PythonVersion, + + /// Whether link library is shared. + /// + /// Serialized to `shared`. + pub shared: bool, + + /// Whether linking against the stable/limited Python 3 API. + /// + /// Serialized to `abi3`. + pub abi3: bool, + + /// The name of the link library defining Python. + /// + /// This effectively controls the `cargo:rustc-link-lib=` value to + /// control how libpython is linked. Values should not contain the `lib` + /// prefix. + /// + /// Serialized to `lib_name`. + pub lib_name: Option, + + /// The directory containing the Python library to link against. + /// + /// The effectively controls the `cargo:rustc-link-search=native=` value + /// to add an additional library search path for the linker. + /// + /// Serialized to `lib_dir`. + pub lib_dir: Option, + + /// Path of host `python` executable. + /// + /// This is a valid executable capable of running on the host/building machine. + /// For configurations derived by invoking a Python interpreter, it was the + /// executable invoked. + /// + /// Serialized to `executable`. + pub executable: Option, + + /// Width in bits of pointers on the target machine. + /// + /// Serialized to `pointer_width`. + pub pointer_width: Option, + + /// Additional relevant Python build flags / configuration settings. + /// + /// Serialized to `build_flags`. + pub build_flags: BuildFlags, + + /// Whether to suppress emitting of `cargo:rustc-link-*` lines from the build script. + /// + /// Typically, `pyo3`'s build script will emit `cargo:rustc-link-lib=` and + /// `cargo:rustc-link-search=` lines derived from other fields in this struct. In + /// advanced building configurations, the default logic to derive these lines may not + /// be sufficient. This field can be set to `Some(true)` to suppress the emission + /// of these lines. + /// + /// If suppression is enabled, `extra_build_script_lines` should contain equivalent + /// functionality or else a build failure is likely. + pub suppress_build_script_link_lines: bool, + + /// Additional lines to `println!()` from Cargo build scripts. + /// + /// This field can be populated to enable the `pyo3` crate to emit additional lines from its + /// its Cargo build script. + /// + /// This crate doesn't populate this field itself. Rather, it is intended to be used with + /// externally provided config files to give them significant control over how the crate + /// is build/configured. + /// + /// Serialized to multiple `extra_build_script_line` values. + pub extra_build_script_lines: Vec, +} + +impl InterpreterConfig { + #[doc(hidden)] + pub fn build_script_outputs(&self) -> Vec { + // This should have been checked during pyo3-build-config build time. + assert!(self.version >= MINIMUM_SUPPORTED_VERSION); + + let mut out = vec![]; + + for i in MINIMUM_SUPPORTED_VERSION.minor..=self.version.minor { + out.push(format!("cargo:rustc-cfg=Py_3_{}", i)); + } + + match self.implementation { + PythonImplementation::CPython => {} + PythonImplementation::PyPy => out.push("cargo:rustc-cfg=PyPy".to_owned()), + PythonImplementation::GraalPy => out.push("cargo:rustc-cfg=GraalPy".to_owned()), + } + + // If Py_GIL_DISABLED is set, do not build with limited API support + if self.abi3 && !self.build_flags.0.contains(&BuildFlag::Py_GIL_DISABLED) { + out.push("cargo:rustc-cfg=Py_LIMITED_API".to_owned()); + } + + for flag in &self.build_flags.0 { + match flag { + 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)), + } + } + + out + } + + #[doc(hidden)] + pub fn from_interpreter(interpreter: impl AsRef) -> Result { + const SCRIPT: &str = r#" +# Allow the script to run on Python 2, so that nicer error can be printed later. +from __future__ import print_function + +import os.path +import platform +import struct +import sys +from sysconfig import get_config_var, get_platform + +PYPY = platform.python_implementation() == "PyPy" +GRAALPY = platform.python_implementation() == "GraalVM" + +if GRAALPY: + graalpy_ver = map(int, __graalpython__.get_graalvm_version().split('.')); + print("graalpy_major", next(graalpy_ver)) + print("graalpy_minor", next(graalpy_ver)) + +# sys.base_prefix is missing on Python versions older than 3.3; this allows the script to continue +# so that the version mismatch can be reported in a nicer way later. +base_prefix = getattr(sys, "base_prefix", None) + +if base_prefix: + # Anaconda based python distributions have a static python executable, but include + # the shared library. Use the shared library for embedding to avoid rust trying to + # LTO the static library (and failing with newer gcc's, because it is old). + ANACONDA = os.path.exists(os.path.join(base_prefix, "conda-meta")) +else: + ANACONDA = False + +def print_if_set(varname, value): + if value is not None: + print(varname, value) + +# Windows always uses shared linking +WINDOWS = platform.system() == "Windows" + +# macOS framework packages use shared linking +FRAMEWORK = bool(get_config_var("PYTHONFRAMEWORK")) + +# unix-style shared library enabled +SHARED = bool(get_config_var("Py_ENABLE_SHARED")) + +print("implementation", platform.python_implementation()) +print("version_major", sys.version_info[0]) +print("version_minor", sys.version_info[1]) +print("shared", PYPY or GRAALPY or ANACONDA or WINDOWS or FRAMEWORK or SHARED) +print_if_set("ld_version", get_config_var("LDVERSION")) +print_if_set("libdir", get_config_var("LIBDIR")) +print_if_set("base_prefix", base_prefix) +print("executable", sys.executable) +print("calcsize_pointer", struct.calcsize("P")) +print("mingw", get_platform().startswith("mingw")) +print("ext_suffix", get_config_var("EXT_SUFFIX")) +"#; + let output = run_python_script(interpreter.as_ref(), SCRIPT)?; + let map: HashMap = parse_script_output(&output); + + ensure!( + !map.is_empty(), + "broken Python interpreter: {}", + interpreter.as_ref().display() + ); + + if let Some(value) = map.get("graalpy_major") { + let graalpy_version = PythonVersion { + major: value + .parse() + .context("failed to parse GraalPy major version")?, + minor: map["graalpy_minor"] + .parse() + .context("failed to parse GraalPy minor version")?, + }; + ensure!( + graalpy_version >= MINIMUM_SUPPORTED_VERSION_GRAALPY, + "At least GraalPy version {} needed, got {}", + MINIMUM_SUPPORTED_VERSION_GRAALPY, + graalpy_version + ); + }; + + let shared = map["shared"].as_str() == "True"; + + let version = PythonVersion { + major: map["version_major"] + .parse() + .context("failed to parse major version")?, + minor: map["version_minor"] + .parse() + .context("failed to parse minor version")?, + }; + + let abi3 = is_abi3(); + + let implementation = map["implementation"].parse()?; + + let lib_name = if cfg!(windows) { + default_lib_name_windows( + version, + implementation, + abi3, + map["mingw"].as_str() == "True", + // This is the best heuristic currently available to detect debug build + // on Windows from sysconfig - e.g. ext_suffix may be + // `_d.cp312-win_amd64.pyd` for 3.12 debug build + map["ext_suffix"].starts_with("_d."), + ) + } else { + default_lib_name_unix( + version, + implementation, + map.get("ld_version").map(String::as_str), + ) + }; + + let lib_dir = if cfg!(windows) { + map.get("base_prefix") + .map(|base_prefix| format!("{}\\libs", base_prefix)) + } else { + map.get("libdir").cloned() + }; + + // The reason we don't use platform.architecture() here is that it's not + // reliable on macOS. See https://stackoverflow.com/a/1405971/823869. + // Similarly, sys.maxsize is not reliable on Windows. See + // https://stackoverflow.com/questions/1405913/how-do-i-determine-if-my-python-shell-is-executing-in-32bit-or-64bit-mode-on-os/1405971#comment6209952_1405971 + // and https://stackoverflow.com/a/3411134/823869. + let calcsize_pointer: u32 = map["calcsize_pointer"] + .parse() + .context("failed to parse calcsize_pointer")?; + + Ok(InterpreterConfig { + version, + implementation, + shared, + abi3, + lib_name: Some(lib_name), + lib_dir, + executable: map.get("executable").cloned(), + pointer_width: Some(calcsize_pointer * 8), + build_flags: BuildFlags::from_interpreter(interpreter)?, + suppress_build_script_link_lines: false, + extra_build_script_lines: vec![], + }) + } + + /// Generate from parsed sysconfigdata file + /// + /// Use [`parse_sysconfigdata`] to generate a hash map of configuration values which may be + /// used to build an [`InterpreterConfig`]. + pub fn from_sysconfigdata(sysconfigdata: &Sysconfigdata) -> Result { + macro_rules! get_key { + ($sysconfigdata:expr, $key:literal) => { + $sysconfigdata + .get_value($key) + .ok_or(concat!($key, " not found in sysconfigdata file")) + }; + } + + macro_rules! parse_key { + ($sysconfigdata:expr, $key:literal) => { + get_key!($sysconfigdata, $key)? + .parse() + .context(concat!("could not parse value of ", $key)) + }; + } + + let soabi = get_key!(sysconfigdata, "SOABI")?; + let implementation = PythonImplementation::from_soabi(soabi)?; + let version = parse_key!(sysconfigdata, "VERSION")?; + let shared = match sysconfigdata.get_value("Py_ENABLE_SHARED") { + Some("1") | Some("true") | Some("True") => true, + Some("0") | Some("false") | Some("False") => false, + _ => bail!("expected a bool (1/true/True or 0/false/False) for Py_ENABLE_SHARED"), + }; + // macOS framework packages use shared linking (PYTHONFRAMEWORK is the framework name, hence the empty check) + let framework = match sysconfigdata.get_value("PYTHONFRAMEWORK") { + Some(s) => !s.is_empty(), + _ => false, + }; + let lib_dir = get_key!(sysconfigdata, "LIBDIR").ok().map(str::to_string); + let lib_name = Some(default_lib_name_unix( + version, + implementation, + sysconfigdata.get_value("LDVERSION"), + )); + let pointer_width = parse_key!(sysconfigdata, "SIZEOF_VOID_P") + .map(|bytes_width: u32| bytes_width * 8) + .ok(); + let build_flags = BuildFlags::from_sysconfigdata(sysconfigdata); + + Ok(InterpreterConfig { + implementation, + version, + shared: shared || framework, + abi3: is_abi3(), + lib_dir, + lib_name, + executable: None, + pointer_width, + build_flags, + suppress_build_script_link_lines: false, + extra_build_script_lines: vec![], + }) + } + + /// Import an externally-provided config file. + /// + /// The `abi3` features, if set, may apply an `abi3` constraint to the Python version. + #[allow(dead_code)] // only used in build.rs + pub(super) fn from_pyo3_config_file_env() -> Option> { + cargo_env_var("PYO3_CONFIG_FILE").map(|path| { + let path = Path::new(&path); + println!("cargo:rerun-if-changed={}", path.display()); + // Absolute path is necessary because this build script is run with a cwd different to the + // original `cargo build` instruction. + ensure!( + path.is_absolute(), + "PYO3_CONFIG_FILE must be an absolute path" + ); + + let mut config = InterpreterConfig::from_path(path) + .context("failed to parse contents of PYO3_CONFIG_FILE")?; + // If the abi3 feature is enabled, the minimum Python version is constrained by the abi3 + // feature. + // + // TODO: abi3 is a property of the build mode, not the interpreter. Should this be + // removed from `InterpreterConfig`? + config.abi3 |= is_abi3(); + config.fixup_for_abi3_version(get_abi3_version())?; + + Ok(config) + }) + } + + #[doc(hidden)] + pub fn from_path(path: impl AsRef) -> Result { + let path = path.as_ref(); + let config_file = std::fs::File::open(path) + .with_context(|| format!("failed to open PyO3 config file at {}", path.display()))?; + let reader = std::io::BufReader::new(config_file); + InterpreterConfig::from_reader(reader) + } + + #[doc(hidden)] + pub fn from_cargo_dep_env() -> Option> { + cargo_env_var("DEP_PYTHON_PYO3_CONFIG") + .map(|buf| InterpreterConfig::from_reader(&*unescape(&buf))) + } + + #[doc(hidden)] + pub fn from_reader(reader: impl Read) -> Result { + let reader = BufReader::new(reader); + let lines = reader.lines(); + + macro_rules! parse_value { + ($variable:ident, $value:ident) => { + $variable = Some($value.trim().parse().context(format!( + concat!( + "failed to parse ", + stringify!($variable), + " from config value '{}'" + ), + $value + ))?) + }; + } + + let mut implementation = None; + let mut version = None; + let mut shared = None; + let mut abi3 = None; + let mut lib_name = None; + let mut lib_dir = None; + let mut executable = None; + let mut pointer_width = None; + let mut build_flags = None; + let mut suppress_build_script_link_lines = None; + let mut extra_build_script_lines = vec![]; + + for (i, line) in lines.enumerate() { + let line = line.context("failed to read line from config")?; + let mut split = line.splitn(2, '='); + let (key, value) = ( + split + .next() + .expect("first splitn value should always be present"), + split + .next() + .ok_or_else(|| format!("expected key=value pair on line {}", i + 1))?, + ); + match key { + "implementation" => parse_value!(implementation, value), + "version" => parse_value!(version, value), + "shared" => parse_value!(shared, value), + "abi3" => parse_value!(abi3, value), + "lib_name" => parse_value!(lib_name, value), + "lib_dir" => parse_value!(lib_dir, value), + "executable" => parse_value!(executable, value), + "pointer_width" => parse_value!(pointer_width, value), + "build_flags" => parse_value!(build_flags, value), + "suppress_build_script_link_lines" => { + parse_value!(suppress_build_script_link_lines, value) + } + "extra_build_script_line" => { + extra_build_script_lines.push(value.to_string()); + } + unknown => warn!("unknown config key `{}`", unknown), + } + } + + let version = version.ok_or("missing value for version")?; + let implementation = implementation.unwrap_or(PythonImplementation::CPython); + let abi3 = abi3.unwrap_or(false); + // Fixup lib_name if it's not set + let lib_name = lib_name.or_else(|| { + if let Ok(Ok(target)) = env::var("TARGET").map(|target| target.parse::()) { + default_lib_name_for_target(version, implementation, abi3, &target) + } else { + None + } + }); + + Ok(InterpreterConfig { + implementation, + version, + shared: shared.unwrap_or(true), + abi3, + lib_name, + lib_dir, + executable, + pointer_width, + build_flags: build_flags.unwrap_or_default(), + suppress_build_script_link_lines: suppress_build_script_link_lines.unwrap_or(false), + extra_build_script_lines, + }) + } + + #[cfg(feature = "python3-dll-a")] + #[allow(clippy::unnecessary_wraps)] + pub fn generate_import_libs(&mut self) -> Result<()> { + // Auto generate python3.dll import libraries for Windows targets. + if self.lib_dir.is_none() { + let target = target_triple_from_env(); + let py_version = if self.abi3 { None } else { Some(self.version) }; + self.lib_dir = + import_lib::generate_import_lib(&target, self.implementation, py_version)?; + } + Ok(()) + } + + #[cfg(not(feature = "python3-dll-a"))] + #[allow(clippy::unnecessary_wraps)] + pub fn generate_import_libs(&mut self) -> Result<()> { + Ok(()) + } + + #[doc(hidden)] + /// Serialize the `InterpreterConfig` and print it to the environment for Cargo to pass along + /// to dependent packages during build time. + /// + /// NB: writing to the cargo environment requires the + /// [`links`](https://doc.rust-lang.org/cargo/reference/build-scripts.html#the-links-manifest-key) + /// manifest key to be set. In this case that means this is called by the `pyo3-ffi` crate and + /// available for dependent package build scripts in `DEP_PYTHON_PYO3_CONFIG`. See + /// documentation for the + /// [`DEP__`](https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts) + /// environment variable. + pub fn to_cargo_dep_env(&self) -> Result<()> { + let mut buf = Vec::new(); + self.to_writer(&mut buf)?; + // escape newlines in env var + println!("cargo:PYO3_CONFIG={}", escape(&buf)); + Ok(()) + } + + #[doc(hidden)] + pub fn to_writer(&self, mut writer: impl Write) -> Result<()> { + macro_rules! write_line { + ($value:ident) => { + writeln!(writer, "{}={}", stringify!($value), self.$value).context(concat!( + "failed to write ", + stringify!($value), + " to config" + )) + }; + } + + macro_rules! write_option_line { + ($value:ident) => { + if let Some(value) = &self.$value { + writeln!(writer, "{}={}", stringify!($value), value).context(concat!( + "failed to write ", + stringify!($value), + " to config" + )) + } else { + Ok(()) + } + }; + } + + write_line!(implementation)?; + write_line!(version)?; + write_line!(shared)?; + write_line!(abi3)?; + write_option_line!(lib_name)?; + write_option_line!(lib_dir)?; + write_option_line!(executable)?; + write_option_line!(pointer_width)?; + write_line!(build_flags)?; + write_line!(suppress_build_script_link_lines)?; + for line in &self.extra_build_script_lines { + writeln!(writer, "extra_build_script_line={}", line) + .context("failed to write extra_build_script_line")?; + } + Ok(()) + } + + /// Run a python script using the [`InterpreterConfig::executable`]. + /// + /// # Panics + /// + /// This function will panic if the [`executable`](InterpreterConfig::executable) is `None`. + pub fn run_python_script(&self, script: &str) -> Result { + run_python_script_with_envs( + Path::new(self.executable.as_ref().expect("no interpreter executable")), + script, + std::iter::empty::<(&str, &str)>(), + ) + } + + /// Run a python script using the [`InterpreterConfig::executable`] with additional + /// environment variables (e.g. PYTHONPATH) set. + /// + /// # Panics + /// + /// This function will panic if the [`executable`](InterpreterConfig::executable) is `None`. + pub fn run_python_script_with_envs(&self, script: &str, envs: I) -> Result + where + I: IntoIterator, + K: AsRef, + V: AsRef, + { + run_python_script_with_envs( + Path::new(self.executable.as_ref().expect("no interpreter executable")), + script, + envs, + ) + } + + /// Lowers the configured version to the abi3 version, if set. + fn fixup_for_abi3_version(&mut self, abi3_version: Option) -> Result<()> { + // PyPy doesn't support abi3; don't adjust the version + if self.implementation.is_pypy() || self.implementation.is_graalpy() { + return Ok(()); + } + + if let Some(version) = abi3_version { + ensure!( + version <= self.version, + "cannot set a minimum Python version {} higher than the interpreter version {} \ + (the minimum Python version is implied by the abi3-py3{} feature)", + version, + self.version, + version.minor, + ); + + self.version = version; + } + + Ok(()) + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct PythonVersion { + pub major: u8, + pub minor: u8, +} + +impl PythonVersion { + const PY37: Self = PythonVersion { major: 3, minor: 7 }; +} + +impl Display for PythonVersion { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}.{}", self.major, self.minor) + } +} + +impl FromStr for PythonVersion { + type Err = crate::errors::Error; + + fn from_str(value: &str) -> Result { + let mut split = value.splitn(2, '.'); + let (major, minor) = ( + split + .next() + .expect("first splitn value should always be present"), + split.next().ok_or("expected major.minor version")?, + ); + Ok(Self { + major: major.parse().context("failed to parse major version")?, + minor: minor.parse().context("failed to parse minor version")?, + }) + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum PythonImplementation { + CPython, + PyPy, + GraalPy, +} + +impl PythonImplementation { + #[doc(hidden)] + pub fn is_pypy(self) -> bool { + self == PythonImplementation::PyPy + } + + #[doc(hidden)] + pub fn is_graalpy(self) -> bool { + self == PythonImplementation::GraalPy + } + + #[doc(hidden)] + pub fn from_soabi(soabi: &str) -> Result { + if soabi.starts_with("pypy") { + Ok(PythonImplementation::PyPy) + } else if soabi.starts_with("cpython") { + Ok(PythonImplementation::CPython) + } else if soabi.starts_with("graalpy") { + Ok(PythonImplementation::GraalPy) + } else { + bail!("unsupported Python interpreter"); + } + } +} + +impl Display for PythonImplementation { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + PythonImplementation::CPython => write!(f, "CPython"), + PythonImplementation::PyPy => write!(f, "PyPy"), + PythonImplementation::GraalPy => write!(f, "GraalVM"), + } + } +} + +impl FromStr for PythonImplementation { + type Err = Error; + fn from_str(s: &str) -> Result { + match s { + "CPython" => Ok(PythonImplementation::CPython), + "PyPy" => Ok(PythonImplementation::PyPy), + "GraalVM" => Ok(PythonImplementation::GraalPy), + _ => bail!("unknown interpreter: {}", s), + } + } +} + +/// Checks if we should look for a Python interpreter installation +/// to get the target interpreter configuration. +/// +/// Returns `false` if `PYO3_NO_PYTHON` environment variable is set. +fn have_python_interpreter() -> bool { + env_var("PYO3_NO_PYTHON").is_none() +} + +/// Checks if `abi3` or any of the `abi3-py3*` features is enabled for the PyO3 crate. +/// +/// Must be called from a PyO3 crate build script. +fn is_abi3() -> bool { + cargo_env_var("CARGO_FEATURE_ABI3").is_some() + || env_var("PYO3_USE_ABI3_FORWARD_COMPATIBILITY").map_or(false, |os_str| os_str == "1") +} + +/// Gets the minimum supported Python version from PyO3 `abi3-py*` features. +/// +/// 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()); + minor_version.map(|minor| PythonVersion { major: 3, minor }) +} + +/// Checks if the `extension-module` feature is enabled for the PyO3 crate. +/// +/// Must be called from a PyO3 crate build script. +pub fn is_extension_module() -> bool { + cargo_env_var("CARGO_FEATURE_EXTENSION_MODULE").is_some() +} + +/// Checks if we need to link to `libpython` for the current build target. +/// +/// Must be called from a PyO3 crate build script. +pub fn is_linking_libpython() -> bool { + is_linking_libpython_for_target(&target_triple_from_env()) +} + +/// Checks if we need to link to `libpython` for the target. +/// +/// Must be called from a PyO3 crate build script. +fn is_linking_libpython_for_target(target: &Triple) -> bool { + target.operating_system == OperatingSystem::Windows + // See https://github.com/PyO3/pyo3/issues/4068#issuecomment-2051159852 + || target.operating_system == OperatingSystem::Aix + || target.environment == Environment::Android + || target.environment == Environment::Androideabi + || !is_extension_module() +} + +/// Checks if we need to discover the Python library directory +/// to link the extension module binary. +/// +/// Must be called from a PyO3 crate build script. +fn require_libdir_for_target(target: &Triple) -> bool { + let is_generating_libpython = cfg!(feature = "python3-dll-a") + && target.operating_system == OperatingSystem::Windows + && is_abi3(); + + is_linking_libpython_for_target(target) && !is_generating_libpython +} + +/// Configuration needed by PyO3 to cross-compile for a target platform. +/// +/// Usually this is collected from the environment (i.e. `PYO3_CROSS_*` and `CARGO_CFG_TARGET_*`) +/// when a cross-compilation configuration is detected. +#[derive(Debug, PartialEq, Eq)] +pub struct CrossCompileConfig { + /// The directory containing the Python library to link against. + pub lib_dir: Option, + + /// The version of the Python library to link against. + version: Option, + + /// The target Python implementation hint (CPython, PyPy, GraalPy, ...) + implementation: Option, + + /// The compile target triple (e.g. aarch64-unknown-linux-gnu) + target: Triple, +} + +impl CrossCompileConfig { + /// Creates a new cross compile config struct from PyO3 environment variables + /// and the build environment when cross compilation mode is detected. + /// + /// Returns `None` when not cross compiling. + fn try_from_env_vars_host_target( + env_vars: CrossCompileEnvVars, + host: &Triple, + target: &Triple, + ) -> Result> { + if env_vars.any() || Self::is_cross_compiling_from_to(host, target) { + let lib_dir = env_vars.lib_dir_path()?; + let version = env_vars.parse_version()?; + let implementation = env_vars.parse_implementation()?; + let target = target.clone(); + + Ok(Some(CrossCompileConfig { + lib_dir, + version, + implementation, + target, + })) + } else { + Ok(None) + } + } + + /// Checks if compiling on `host` for `target` required "real" cross compilation. + /// + /// Returns `false` if the target Python interpreter can run on the host. + fn is_cross_compiling_from_to(host: &Triple, target: &Triple) -> bool { + // Not cross-compiling if arch-vendor-os is all the same + // e.g. x86_64-unknown-linux-musl on x86_64-unknown-linux-gnu host + // x86_64-pc-windows-gnu on x86_64-pc-windows-msvc host + let mut compatible = host.architecture == target.architecture + && host.vendor == target.vendor + && host.operating_system == target.operating_system; + + // Not cross-compiling to compile for 32-bit Python from windows 64-bit + compatible |= target.operating_system == OperatingSystem::Windows + && host.operating_system == OperatingSystem::Windows; + + // Not cross-compiling to compile for x86-64 Python from macOS arm64 and vice versa + compatible |= target.operating_system == OperatingSystem::Darwin + && host.operating_system == OperatingSystem::Darwin; + + !compatible + } + + /// Converts `lib_dir` member field to an UTF-8 string. + /// + /// The conversion can not fail because `PYO3_CROSS_LIB_DIR` variable + /// is ensured contain a valid UTF-8 string. + fn lib_dir_string(&self) -> Option { + self.lib_dir + .as_ref() + .map(|s| s.to_str().unwrap().to_owned()) + } +} + +/// PyO3-specific cross compile environment variable values +struct CrossCompileEnvVars { + /// `PYO3_CROSS` + pyo3_cross: Option, + /// `PYO3_CROSS_LIB_DIR` + pyo3_cross_lib_dir: Option, + /// `PYO3_CROSS_PYTHON_VERSION` + pyo3_cross_python_version: Option, + /// `PYO3_CROSS_PYTHON_IMPLEMENTATION` + pyo3_cross_python_implementation: Option, +} + +impl CrossCompileEnvVars { + /// Grabs the PyO3 cross-compile variables from the environment. + /// + /// Registers the build script to rerun if any of the variables changes. + fn from_env() -> Self { + CrossCompileEnvVars { + pyo3_cross: env_var("PYO3_CROSS"), + pyo3_cross_lib_dir: env_var("PYO3_CROSS_LIB_DIR"), + pyo3_cross_python_version: env_var("PYO3_CROSS_PYTHON_VERSION"), + pyo3_cross_python_implementation: env_var("PYO3_CROSS_PYTHON_IMPLEMENTATION"), + } + } + + /// Checks if any of the variables is set. + fn any(&self) -> bool { + self.pyo3_cross.is_some() + || self.pyo3_cross_lib_dir.is_some() + || self.pyo3_cross_python_version.is_some() + || self.pyo3_cross_python_implementation.is_some() + } + + /// Parses `PYO3_CROSS_PYTHON_VERSION` environment variable value + /// into `PythonVersion`. + fn parse_version(&self) -> Result> { + let version = self + .pyo3_cross_python_version + .as_ref() + .map(|os_string| { + let utf8_str = os_string + .to_str() + .ok_or("PYO3_CROSS_PYTHON_VERSION is not valid a UTF-8 string")?; + utf8_str + .parse() + .context("failed to parse PYO3_CROSS_PYTHON_VERSION") + }) + .transpose()?; + + Ok(version) + } + + /// Parses `PYO3_CROSS_PYTHON_IMPLEMENTATION` environment variable value + /// into `PythonImplementation`. + fn parse_implementation(&self) -> Result> { + let implementation = self + .pyo3_cross_python_implementation + .as_ref() + .map(|os_string| { + let utf8_str = os_string + .to_str() + .ok_or("PYO3_CROSS_PYTHON_IMPLEMENTATION is not valid a UTF-8 string")?; + utf8_str + .parse() + .context("failed to parse PYO3_CROSS_PYTHON_IMPLEMENTATION") + }) + .transpose()?; + + Ok(implementation) + } + + /// Converts the stored `PYO3_CROSS_LIB_DIR` variable value (if any) + /// into a `PathBuf` instance. + /// + /// Ensures that the path is a valid UTF-8 string. + fn lib_dir_path(&self) -> Result> { + let lib_dir = self.pyo3_cross_lib_dir.as_ref().map(PathBuf::from); + + if let Some(dir) = lib_dir.as_ref() { + ensure!( + dir.to_str().is_some(), + "PYO3_CROSS_LIB_DIR variable value is not a valid UTF-8 string" + ); + } + + Ok(lib_dir) + } +} + +/// Detect whether we are cross compiling and return an assembled CrossCompileConfig if so. +/// +/// This function relies on PyO3 cross-compiling environment variables: +/// +/// * `PYO3_CROSS`: If present, forces PyO3 to configure as a cross-compilation. +/// * `PYO3_CROSS_LIB_DIR`: If present, must be set to the directory containing +/// the target's libpython DSO and the associated `_sysconfigdata*.py` file for +/// Unix-like targets, or the Python DLL import libraries for the Windows target. +/// * `PYO3_CROSS_PYTHON_VERSION`: Major and minor version (e.g. 3.9) of the target Python +/// installation. This variable is only needed if PyO3 cannnot determine the version to target +/// from `abi3-py3*` features, or if there are multiple versions of Python present in +/// `PYO3_CROSS_LIB_DIR`. +/// +/// See the [PyO3 User Guide](https://pyo3.rs/) for more info on cross-compiling. +pub fn cross_compiling_from_to( + host: &Triple, + target: &Triple, +) -> Result> { + let env_vars = CrossCompileEnvVars::from_env(); + CrossCompileConfig::try_from_env_vars_host_target(env_vars, host, target) +} + +/// Detect whether we are cross compiling from Cargo and `PYO3_CROSS_*` environment +/// variables and return an assembled `CrossCompileConfig` if so. +/// +/// This must be called from PyO3's build script, because it relies on environment +/// variables such as `CARGO_CFG_TARGET_OS` which aren't available at any other time. +pub fn cross_compiling_from_cargo_env() -> Result> { + let env_vars = CrossCompileEnvVars::from_env(); + let host = Triple::host(); + let target = target_triple_from_env(); + + CrossCompileConfig::try_from_env_vars_host_target(env_vars, &host, &target) +} + +#[allow(non_camel_case_types)] +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub enum BuildFlag { + Py_DEBUG, + Py_REF_DEBUG, + Py_TRACE_REFS, + Py_GIL_DISABLED, + COUNT_ALLOCS, + Other(String), +} + +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), + } + } +} + +impl FromStr for BuildFlag { + type Err = std::convert::Infallible; + fn from_str(s: &str) -> Result { + match s { + "Py_DEBUG" => Ok(BuildFlag::Py_DEBUG), + "Py_REF_DEBUG" => Ok(BuildFlag::Py_REF_DEBUG), + "Py_TRACE_REFS" => Ok(BuildFlag::Py_TRACE_REFS), + "Py_GIL_DISABLED" => Ok(BuildFlag::Py_GIL_DISABLED), + "COUNT_ALLOCS" => Ok(BuildFlag::COUNT_ALLOCS), + other => Ok(BuildFlag::Other(other.to_owned())), + } + } +} + +/// A list of python interpreter compile-time preprocessor defines. +/// +/// 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 +/// #[cfg(py_sys_config="{varname}")] +/// # struct Foo; +/// ``` +/// +/// is the equivalent of `#ifdef {varname}` in C. +/// +/// see Misc/SpecialBuilds.txt in the python source for what these mean. +#[cfg_attr(test, derive(Debug, PartialEq, Eq))] +#[derive(Clone, Default)] +pub struct BuildFlags(pub HashSet); + +impl BuildFlags { + const ALL: [BuildFlag; 5] = [ + BuildFlag::Py_DEBUG, + BuildFlag::Py_REF_DEBUG, + BuildFlag::Py_TRACE_REFS, + BuildFlag::Py_GIL_DISABLED, + BuildFlag::COUNT_ALLOCS, + ]; + + pub fn new() -> Self { + BuildFlags(HashSet::new()) + } + + fn from_sysconfigdata(config_map: &Sysconfigdata) -> Self { + Self( + BuildFlags::ALL + .iter() + .filter(|flag| { + config_map + .get_value(flag.to_string()) + .map_or(false, |value| value == "1") + }) + .cloned() + .collect(), + ) + .fixup() + } + + /// Examine python's compile flags to pass to cfg by launching + /// the interpreter and printing variables of interest from + /// sysconfig.get_config_vars. + fn from_interpreter(interpreter: impl AsRef) -> Result { + // sysconfig is missing all the flags on windows, so we can't actually + // query the interpreter directly for its build flags. + if cfg!(windows) { + return Ok(Self::new()); + } + + let mut script = String::from("import sysconfig\n"); + script.push_str("config = sysconfig.get_config_vars()\n"); + + for k in &BuildFlags::ALL { + use std::fmt::Write; + writeln!(&mut script, "print(config.get('{}', '0'))", k).unwrap(); + } + + let stdout = run_python_script(interpreter.as_ref(), &script)?; + let split_stdout: Vec<&str> = stdout.trim_end().lines().collect(); + ensure!( + split_stdout.len() == BuildFlags::ALL.len(), + "Python stdout len didn't return expected number of lines: {}", + split_stdout.len() + ); + let flags = BuildFlags::ALL + .iter() + .zip(split_stdout) + .filter(|(_, flag_value)| *flag_value == "1") + .map(|(flag, _)| flag.clone()) + .collect(); + + Ok(Self(flags).fixup()) + } + + fn fixup(mut self) -> Self { + if self.0.contains(&BuildFlag::Py_DEBUG) { + self.0.insert(BuildFlag::Py_REF_DEBUG); + } + + self + } +} + +impl Display for BuildFlags { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut first = true; + for flag in &self.0 { + if first { + first = false; + } else { + write!(f, ",")?; + } + write!(f, "{}", flag)?; + } + Ok(()) + } +} + +impl FromStr for BuildFlags { + type Err = std::convert::Infallible; + + fn from_str(value: &str) -> Result { + let mut flags = HashSet::new(); + for flag in value.split_terminator(',') { + flags.insert(flag.parse().unwrap()); + } + Ok(BuildFlags(flags)) + } +} + +fn parse_script_output(output: &str) -> HashMap { + output + .lines() + .filter_map(|line| { + let mut i = line.splitn(2, ' '); + Some((i.next()?.into(), i.next()?.into())) + }) + .collect() +} + +/// Parsed data from Python sysconfigdata file +/// +/// A hash map of all values from a sysconfigdata file. +pub struct Sysconfigdata(HashMap); + +impl Sysconfigdata { + pub fn get_value>(&self, k: S) -> Option<&str> { + self.0.get(k.as_ref()).map(String::as_str) + } + + #[allow(dead_code)] + fn new() -> Self { + Sysconfigdata(HashMap::new()) + } + + #[allow(dead_code)] + fn insert>(&mut self, k: S, v: S) { + self.0.insert(k.into(), v.into()); + } +} + +/// Parse sysconfigdata file +/// +/// The sysconfigdata is simply a dictionary containing all the build time variables used for the +/// python executable and library. This function necessitates a python interpreter on the host +/// machine to work. Here it is read into a `Sysconfigdata` (hash map), which can be turned into an +/// [`InterpreterConfig`] using +/// [`from_sysconfigdata`](InterpreterConfig::from_sysconfigdata). +pub fn parse_sysconfigdata(sysconfigdata_path: impl AsRef) -> Result { + let sysconfigdata_path = sysconfigdata_path.as_ref(); + let mut script = fs::read_to_string(sysconfigdata_path).with_context(|| { + format!( + "failed to read config from {}", + sysconfigdata_path.display() + ) + })?; + script += r#" +for key, val in build_time_vars.items(): + print(key, val) +"#; + + let output = run_python_script(&find_interpreter()?, &script)?; + + Ok(Sysconfigdata(parse_script_output(&output))) +} + +fn starts_with(entry: &DirEntry, pat: &str) -> bool { + let name = entry.file_name(); + name.to_string_lossy().starts_with(pat) +} +fn ends_with(entry: &DirEntry, pat: &str) -> bool { + let name = entry.file_name(); + name.to_string_lossy().ends_with(pat) +} + +/// Finds the sysconfigdata file when the target Python library directory is set. +/// +/// Returns `None` if the library directory is not available, and a runtime error +/// when no or multiple sysconfigdata files are found. +fn find_sysconfigdata(cross: &CrossCompileConfig) -> Result> { + let mut sysconfig_paths = find_all_sysconfigdata(cross)?; + if sysconfig_paths.is_empty() { + if let Some(lib_dir) = cross.lib_dir.as_ref() { + bail!("Could not find _sysconfigdata*.py in {}", lib_dir.display()); + } else { + // Continue with the default configuration when PYO3_CROSS_LIB_DIR is not set. + return Ok(None); + } + } else if sysconfig_paths.len() > 1 { + let mut error_msg = String::from( + "Detected multiple possible Python versions. Please set either the \ + PYO3_CROSS_PYTHON_VERSION variable to the wanted version or the \ + _PYTHON_SYSCONFIGDATA_NAME variable to the wanted sysconfigdata file name.\n\n\ + sysconfigdata files found:", + ); + for path in sysconfig_paths { + use std::fmt::Write; + write!(&mut error_msg, "\n\t{}", path.display()).unwrap(); + } + bail!("{}\n", error_msg); + } + + Ok(Some(sysconfig_paths.remove(0))) +} + +/// Finds `_sysconfigdata*.py` files for detected Python interpreters. +/// +/// From the python source for `_sysconfigdata*.py` is always going to be located at +/// `build/lib.{PLATFORM}-{PY_MINOR_VERSION}` when built from source. The [exact line][1] is defined as: +/// +/// ```py +/// pybuilddir = 'build/lib.%s-%s' % (get_platform(), sys.version_info[:2]) +/// ``` +/// +/// Where get_platform returns a kebab-case formatted string containing the os, the architecture and +/// possibly the os' kernel version (not the case on linux). However, when installed using a package +/// manager, the `_sysconfigdata*.py` file is installed in the `${PREFIX}/lib/python3.Y/` directory. +/// The `_sysconfigdata*.py` is generally in a sub-directory of the location of `libpython3.Y.so`. +/// So we must find the file in the following possible locations: +/// +/// ```sh +/// # distribution from package manager, (lib_dir may or may not include lib/) +/// ${INSTALL_PREFIX}/lib/python3.Y/_sysconfigdata*.py +/// ${INSTALL_PREFIX}/lib/libpython3.Y.so +/// ${INSTALL_PREFIX}/lib/python3.Y/config-3.Y-${HOST_TRIPLE}/libpython3.Y.so +/// +/// # Built from source from host +/// ${CROSS_COMPILED_LOCATION}/build/lib.linux-x86_64-Y/_sysconfigdata*.py +/// ${CROSS_COMPILED_LOCATION}/libpython3.Y.so +/// +/// # if cross compiled, kernel release is only present on certain OS targets. +/// ${CROSS_COMPILED_LOCATION}/build/lib.{OS}(-{OS-KERNEL-RELEASE})?-{ARCH}-Y/_sysconfigdata*.py +/// ${CROSS_COMPILED_LOCATION}/libpython3.Y.so +/// +/// # PyPy includes a similar file since v73 +/// ${INSTALL_PREFIX}/lib/pypy3.Y/_sysconfigdata.py +/// ${INSTALL_PREFIX}/lib_pypy/_sysconfigdata.py +/// ``` +/// +/// [1]: https://github.com/python/cpython/blob/3.5/Lib/sysconfig.py#L389 +/// +/// Returns an empty vector when the target Python library directory +/// is not set via `PYO3_CROSS_LIB_DIR`. +pub fn find_all_sysconfigdata(cross: &CrossCompileConfig) -> Result> { + let sysconfig_paths = if let Some(lib_dir) = cross.lib_dir.as_ref() { + search_lib_dir(lib_dir, cross).with_context(|| { + format!( + "failed to search the lib dir at 'PYO3_CROSS_LIB_DIR={}'", + lib_dir.display() + ) + })? + } else { + return Ok(Vec::new()); + }; + + let sysconfig_name = env_var("_PYTHON_SYSCONFIGDATA_NAME"); + let mut sysconfig_paths = sysconfig_paths + .iter() + .filter_map(|p| { + let canonical = fs::canonicalize(p).ok(); + match &sysconfig_name { + Some(_) => canonical.filter(|p| p.file_stem() == sysconfig_name.as_deref()), + None => canonical, + } + }) + .collect::>(); + + sysconfig_paths.sort(); + sysconfig_paths.dedup(); + + Ok(sysconfig_paths) +} + +fn is_pypy_lib_dir(path: &str, v: &Option) -> bool { + let pypy_version_pat = if let Some(v) = v { + format!("pypy{}", v) + } else { + "pypy3.".into() + }; + path == "lib_pypy" || path.starts_with(&pypy_version_pat) +} + +fn is_graalpy_lib_dir(path: &str, v: &Option) -> bool { + let graalpy_version_pat = if let Some(v) = v { + format!("graalpy{}", v) + } else { + "graalpy2".into() + }; + path == "lib_graalpython" || path.starts_with(&graalpy_version_pat) +} + +fn is_cpython_lib_dir(path: &str, v: &Option) -> bool { + let cpython_version_pat = if let Some(v) = v { + format!("python{}", v) + } else { + "python3.".into() + }; + path.starts_with(&cpython_version_pat) +} + +/// recursive search for _sysconfigdata, returns all possibilities of sysconfigdata paths +fn search_lib_dir(path: impl AsRef, cross: &CrossCompileConfig) -> Result> { + let mut sysconfig_paths = vec![]; + for f in fs::read_dir(path.as_ref()).with_context(|| { + format!( + "failed to list the entries in '{}'", + path.as_ref().display() + ) + })? { + sysconfig_paths.extend(match &f { + // Python 3.7+ sysconfigdata with platform specifics + Ok(f) if starts_with(f, "_sysconfigdata_") && ends_with(f, "py") => vec![f.path()], + Ok(f) if f.metadata().map_or(false, |metadata| metadata.is_dir()) => { + let file_name = f.file_name(); + let file_name = file_name.to_string_lossy(); + if file_name == "build" || file_name == "lib" { + search_lib_dir(f.path(), cross)? + } else if file_name.starts_with("lib.") { + // check if right target os + if !file_name.contains(&cross.target.operating_system.to_string()) { + continue; + } + // Check if right arch + if !file_name.contains(&cross.target.architecture.to_string()) { + continue; + } + search_lib_dir(f.path(), cross)? + } else if is_cpython_lib_dir(&file_name, &cross.version) + || is_pypy_lib_dir(&file_name, &cross.version) + || is_graalpy_lib_dir(&file_name, &cross.version) + { + search_lib_dir(f.path(), cross)? + } else { + continue; + } + } + _ => continue, + }); + } + // If we got more than one file, only take those that contain the arch name. + // For ubuntu 20.04 with host architecture x86_64 and a foreign architecture of armhf + // this reduces the number of candidates to 1: + // + // $ find /usr/lib/python3.8/ -name '_sysconfigdata*.py' -not -lname '*' + // /usr/lib/python3.8/_sysconfigdata__x86_64-linux-gnu.py + // /usr/lib/python3.8/_sysconfigdata__arm-linux-gnueabihf.py + if sysconfig_paths.len() > 1 { + let temp = sysconfig_paths + .iter() + .filter(|p| { + p.to_string_lossy() + .contains(&cross.target.architecture.to_string()) + }) + .cloned() + .collect::>(); + if !temp.is_empty() { + sysconfig_paths = temp; + } + } + + Ok(sysconfig_paths) +} + +/// Find cross compilation information from sysconfigdata file +/// +/// first find sysconfigdata file which follows the pattern [`_sysconfigdata_{abi}_{platform}_{multiarch}`][1] +/// +/// [1]: https://github.com/python/cpython/blob/3.8/Lib/sysconfig.py#L348 +/// +/// Returns `None` when the target Python library directory is not set. +fn cross_compile_from_sysconfigdata( + cross_compile_config: &CrossCompileConfig, +) -> Result> { + if let Some(path) = find_sysconfigdata(cross_compile_config)? { + let data = parse_sysconfigdata(path)?; + let config = InterpreterConfig::from_sysconfigdata(&data)?; + + Ok(Some(config)) + } else { + Ok(None) + } +} + +/// Generates "default" cross compilation information for the target. +/// +/// This should work for most CPython extension modules when targeting +/// Windows, macOS and Linux. +/// +/// Must be called from a PyO3 crate build script. +#[allow(unused_mut)] +fn default_cross_compile(cross_compile_config: &CrossCompileConfig) -> Result { + let version = cross_compile_config + .version + .or_else(get_abi3_version) + .ok_or_else(|| + format!( + "PYO3_CROSS_PYTHON_VERSION or an abi3-py3* feature must be specified \ + when cross-compiling and PYO3_CROSS_LIB_DIR is not set.\n\ + = help: see the PyO3 user guide for more information: https://pyo3.rs/v{}/building-and-distribution.html#cross-compiling", + env!("CARGO_PKG_VERSION") + ) + )?; + + let abi3 = is_abi3(); + let implementation = cross_compile_config + .implementation + .unwrap_or(PythonImplementation::CPython); + + let lib_name = + default_lib_name_for_target(version, implementation, abi3, &cross_compile_config.target); + + let mut lib_dir = cross_compile_config.lib_dir_string(); + + // Auto generate python3.dll import libraries for Windows targets. + #[cfg(feature = "python3-dll-a")] + if lib_dir.is_none() { + let py_version = if abi3 { None } else { Some(version) }; + lib_dir = self::import_lib::generate_import_lib( + &cross_compile_config.target, + cross_compile_config + .implementation + .unwrap_or(PythonImplementation::CPython), + py_version, + )?; + } + + Ok(InterpreterConfig { + implementation, + version, + shared: true, + abi3, + lib_name, + lib_dir, + executable: None, + pointer_width: None, + build_flags: BuildFlags::default(), + suppress_build_script_link_lines: false, + extra_build_script_lines: vec![], + }) +} + +/// Generates "default" interpreter configuration when compiling "abi3" extensions +/// without a working Python interpreter. +/// +/// `version` specifies the minimum supported Stable ABI CPython version. +/// +/// This should work for most CPython extension modules when compiling on +/// Windows, macOS and Linux. +/// +/// Must be called from a PyO3 crate build script. +fn default_abi3_config(host: &Triple, version: PythonVersion) -> InterpreterConfig { + // FIXME: PyPy & GraalPy do not support the Stable ABI. + let implementation = PythonImplementation::CPython; + let abi3 = true; + + let lib_name = if host.operating_system == OperatingSystem::Windows { + Some(default_lib_name_windows( + version, + implementation, + abi3, + false, + false, + )) + } else { + None + }; + + InterpreterConfig { + implementation, + version, + shared: true, + abi3, + lib_name, + lib_dir: None, + executable: None, + pointer_width: None, + build_flags: BuildFlags::default(), + suppress_build_script_link_lines: false, + extra_build_script_lines: vec![], + } +} + +/// Detects the cross compilation target interpreter configuration from all +/// available sources (PyO3 environment variables, Python sysconfigdata, etc.). +/// +/// Returns the "default" target interpreter configuration for Windows and +/// when no target Python interpreter is found. +/// +/// Must be called from a PyO3 crate build script. +fn load_cross_compile_config( + cross_compile_config: CrossCompileConfig, +) -> Result { + let windows = cross_compile_config.target.operating_system == OperatingSystem::Windows; + + let config = if windows || !have_python_interpreter() { + // Load the defaults for Windows even when `PYO3_CROSS_LIB_DIR` is set + // since it has no sysconfigdata files in it. + // Also, do not try to look for sysconfigdata when `PYO3_NO_PYTHON` variable is set. + default_cross_compile(&cross_compile_config)? + } else if let Some(config) = cross_compile_from_sysconfigdata(&cross_compile_config)? { + // Try to find and parse sysconfigdata files on other targets. + config + } else { + // Fall back to the defaults when nothing else can be done. + default_cross_compile(&cross_compile_config)? + }; + + if config.lib_name.is_some() && config.lib_dir.is_none() { + warn!( + "The output binary will link to libpython, \ + but PYO3_CROSS_LIB_DIR environment variable is not set. \ + Ensure that the target Python library directory is \ + in the rustc native library search path." + ); + } + + Ok(config) +} + +// Link against python3.lib for the stable ABI on Windows. +// See https://www.python.org/dev/peps/pep-0384/#linkage +// +// This contains only the limited ABI symbols. +const WINDOWS_ABI3_LIB_NAME: &str = "python3"; + +fn default_lib_name_for_target( + version: PythonVersion, + implementation: PythonImplementation, + abi3: bool, + target: &Triple, +) -> Option { + if target.operating_system == OperatingSystem::Windows { + Some(default_lib_name_windows( + version, + implementation, + abi3, + false, + false, + )) + } else if is_linking_libpython_for_target(target) { + Some(default_lib_name_unix(version, implementation, None)) + } else { + None + } +} + +fn default_lib_name_windows( + version: PythonVersion, + implementation: PythonImplementation, + abi3: bool, + mingw: bool, + debug: bool, +) -> String { + if debug { + // CPython bug: linking against python3_d.dll raises error + // https://github.com/python/cpython/issues/101614 + format!("python{}{}_d", version.major, version.minor) + } else if abi3 && !(implementation.is_pypy() || implementation.is_graalpy()) { + WINDOWS_ABI3_LIB_NAME.to_owned() + } else if mingw { + // https://packages.msys2.org/base/mingw-w64-python + format!("python{}.{}", version.major, version.minor) + } else { + format!("python{}{}", version.major, version.minor) + } +} + +fn default_lib_name_unix( + version: PythonVersion, + implementation: PythonImplementation, + ld_version: Option<&str>, +) -> String { + match implementation { + PythonImplementation::CPython => match ld_version { + Some(ld_version) => format!("python{}", ld_version), + None => { + if version > PythonVersion::PY37 { + // PEP 3149 ABI version tags are finally gone + format!("python{}.{}", version.major, version.minor) + } else { + // Work around https://bugs.python.org/issue36707 + format!("python{}.{}m", version.major, version.minor) + } + } + }, + PythonImplementation::PyPy => match ld_version { + Some(ld_version) => format!("pypy{}-c", ld_version), + None => format!("pypy{}.{}-c", version.major, version.minor), + }, + + PythonImplementation::GraalPy => "python-native".to_string(), + } +} + +/// Run a python script using the specified interpreter binary. +fn run_python_script(interpreter: &Path, script: &str) -> Result { + run_python_script_with_envs(interpreter, script, std::iter::empty::<(&str, &str)>()) +} + +/// Run a python script using the specified interpreter binary with additional environment +/// variables (e.g. PYTHONPATH) set. +fn run_python_script_with_envs(interpreter: &Path, script: &str, envs: I) -> Result +where + I: IntoIterator, + K: AsRef, + V: AsRef, +{ + let out = Command::new(interpreter) + .env("PYTHONIOENCODING", "utf-8") + .envs(envs) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::inherit()) + .spawn() + .and_then(|mut child| { + child + .stdin + .as_mut() + .expect("piped stdin") + .write_all(script.as_bytes())?; + child.wait_with_output() + }); + + match out { + Err(err) => bail!( + "failed to run the Python interpreter at {}: {}", + interpreter.display(), + err + ), + Ok(ok) if !ok.status.success() => bail!("Python script failed"), + Ok(ok) => Ok(String::from_utf8(ok.stdout) + .context("failed to parse Python script output as utf-8")?), + } +} + +fn venv_interpreter(virtual_env: &OsStr, windows: bool) -> PathBuf { + if windows { + Path::new(virtual_env).join("Scripts").join("python.exe") + } else { + Path::new(virtual_env).join("bin").join("python") + } +} + +fn conda_env_interpreter(conda_prefix: &OsStr, windows: bool) -> PathBuf { + if windows { + Path::new(conda_prefix).join("python.exe") + } else { + Path::new(conda_prefix).join("bin").join("python") + } +} + +fn get_env_interpreter() -> Option { + match (env_var("VIRTUAL_ENV"), env_var("CONDA_PREFIX")) { + // Use cfg rather than CARGO_CFG_TARGET_OS because this affects where files are located on the + // build host + (Some(dir), None) => Some(venv_interpreter(&dir, cfg!(windows))), + (None, Some(dir)) => Some(conda_env_interpreter(&dir, cfg!(windows))), + (Some(_), Some(_)) => { + warn!( + "Both VIRTUAL_ENV and CONDA_PREFIX are set. PyO3 will ignore both of these for \ + locating the Python interpreter until you unset one of them." + ); + None + } + (None, None) => None, + } +} + +/// Attempts to locate a python interpreter. +/// +/// Locations are checked in the order listed: +/// 1. If `PYO3_PYTHON` is set, this interpreter is used. +/// 2. If in a virtualenv, that environment's interpreter is used. +/// 3. `python`, if this is functional a Python 3.x interpreter +/// 4. `python3`, as above +pub fn find_interpreter() -> Result { + // Trigger rebuilds when `PYO3_ENVIRONMENT_SIGNATURE` env var value changes + // See https://github.com/PyO3/pyo3/issues/2724 + println!("cargo:rerun-if-env-changed=PYO3_ENVIRONMENT_SIGNATURE"); + + if let Some(exe) = env_var("PYO3_PYTHON") { + Ok(exe.into()) + } else if let Some(env_interpreter) = get_env_interpreter() { + Ok(env_interpreter) + } else { + println!("cargo:rerun-if-env-changed=PATH"); + ["python", "python3"] + .iter() + .find(|bin| { + if let Ok(out) = Command::new(bin).arg("--version").output() { + // begin with `Python 3.X.X :: additional info` + out.stdout.starts_with(b"Python 3") + || out.stderr.starts_with(b"Python 3") + || out.stdout.starts_with(b"GraalPy 3") + } else { + false + } + }) + .map(PathBuf::from) + .ok_or_else(|| "no Python 3.x interpreter found".into()) + } +} + +/// Locates and extracts the build host Python interpreter configuration. +/// +/// Lowers the configured Python version to `abi3_version` if required. +fn get_host_interpreter(abi3_version: Option) -> Result { + let interpreter_path = find_interpreter()?; + + let mut interpreter_config = InterpreterConfig::from_interpreter(interpreter_path)?; + interpreter_config.fixup_for_abi3_version(abi3_version)?; + + Ok(interpreter_config) +} + +/// Generates an interpreter config suitable for cross-compilation. +/// +/// This must be called from PyO3's build script, because it relies on environment variables such as +/// CARGO_CFG_TARGET_OS which aren't available at any other time. +pub fn make_cross_compile_config() -> Result> { + let interpreter_config = if let Some(cross_config) = cross_compiling_from_cargo_env()? { + let mut interpreter_config = load_cross_compile_config(cross_config)?; + interpreter_config.fixup_for_abi3_version(get_abi3_version())?; + Some(interpreter_config) + } else { + None + }; + + Ok(interpreter_config) +} + +/// Generates an interpreter config which will be hard-coded into the pyo3-build-config crate. +/// Only used by `pyo3-build-config` build script. +#[allow(dead_code, unused_mut)] +pub fn make_interpreter_config() -> Result { + let host = Triple::host(); + let abi3_version = get_abi3_version(); + + // See if we can safely skip the Python interpreter configuration detection. + // Unix "abi3" extension modules can usually be built without any interpreter. + let need_interpreter = abi3_version.is_none() || require_libdir_for_target(&host); + + if have_python_interpreter() { + match get_host_interpreter(abi3_version) { + Ok(interpreter_config) => return Ok(interpreter_config), + // Bail if the interpreter configuration is required to build. + Err(e) if need_interpreter => return Err(e), + _ => { + // Fall back to the "abi3" defaults just as if `PYO3_NO_PYTHON` + // environment variable was set. + warn!("Compiling without a working Python interpreter."); + } + } + } else { + ensure!( + abi3_version.is_some(), + "An abi3-py3* feature must be specified when compiling without a Python interpreter." + ); + }; + + let mut interpreter_config = default_abi3_config(&host, abi3_version.unwrap()); + + // Auto generate python3.dll import libraries for Windows targets. + #[cfg(feature = "python3-dll-a")] + { + let py_version = if interpreter_config.abi3 { + None + } else { + Some(interpreter_config.version) + }; + interpreter_config.lib_dir = self::import_lib::generate_import_lib( + &host, + interpreter_config.implementation, + py_version, + )?; + } + + Ok(interpreter_config) +} + +fn escape(bytes: &[u8]) -> String { + let mut escaped = String::with_capacity(2 * bytes.len()); + + for byte in bytes { + const LUT: &[u8; 16] = b"0123456789abcdef"; + + escaped.push(LUT[(byte >> 4) as usize] as char); + escaped.push(LUT[(byte & 0x0F) as usize] as char); + } + + escaped +} + +fn unescape(escaped: &str) -> Vec { + assert!(escaped.len() % 2 == 0, "invalid hex encoding"); + + let mut bytes = Vec::with_capacity(escaped.len() / 2); + + for chunk in escaped.as_bytes().chunks_exact(2) { + fn unhex(hex: u8) -> u8 { + match hex { + b'a'..=b'f' => hex - b'a' + 10, + b'0'..=b'9' => hex - b'0', + _ => panic!("invalid hex encoding"), + } + } + + bytes.push(unhex(chunk[0]) << 4 | unhex(chunk[1])); + } + + bytes +} + +#[cfg(test)] +mod tests { + use target_lexicon::triple; + + use super::*; + + #[test] + fn test_config_file_roundtrip() { + let config = InterpreterConfig { + abi3: true, + build_flags: BuildFlags::default(), + pointer_width: Some(32), + executable: Some("executable".into()), + implementation: PythonImplementation::CPython, + lib_name: Some("lib_name".into()), + lib_dir: Some("lib_dir".into()), + shared: true, + version: MINIMUM_SUPPORTED_VERSION, + suppress_build_script_link_lines: true, + extra_build_script_lines: vec!["cargo:test1".to_string(), "cargo:test2".to_string()], + }; + let mut buf: Vec = Vec::new(); + config.to_writer(&mut buf).unwrap(); + + assert_eq!(config, InterpreterConfig::from_reader(&*buf).unwrap()); + + // And some different options, for variety + + let config = InterpreterConfig { + abi3: false, + build_flags: { + let mut flags = HashSet::new(); + flags.insert(BuildFlag::Py_DEBUG); + flags.insert(BuildFlag::Other(String::from("Py_SOME_FLAG"))); + BuildFlags(flags) + }, + pointer_width: None, + executable: None, + implementation: PythonImplementation::PyPy, + lib_dir: None, + lib_name: None, + shared: true, + version: PythonVersion { + major: 3, + minor: 10, + }, + suppress_build_script_link_lines: false, + extra_build_script_lines: vec![], + }; + let mut buf: Vec = Vec::new(); + config.to_writer(&mut buf).unwrap(); + + assert_eq!(config, InterpreterConfig::from_reader(&*buf).unwrap()); + } + + #[test] + fn test_config_file_roundtrip_with_escaping() { + let config = InterpreterConfig { + abi3: true, + build_flags: BuildFlags::default(), + pointer_width: Some(32), + executable: Some("executable".into()), + implementation: PythonImplementation::CPython, + lib_name: Some("lib_name".into()), + lib_dir: Some("lib_dir\\n".into()), + shared: true, + version: MINIMUM_SUPPORTED_VERSION, + suppress_build_script_link_lines: true, + extra_build_script_lines: vec!["cargo:test1".to_string(), "cargo:test2".to_string()], + }; + let mut buf: Vec = Vec::new(); + config.to_writer(&mut buf).unwrap(); + + let buf = unescape(&escape(&buf)); + + assert_eq!(config, InterpreterConfig::from_reader(&*buf).unwrap()); + } + + #[test] + fn test_config_file_defaults() { + // Only version is required + assert_eq!( + InterpreterConfig::from_reader("version=3.7".as_bytes()).unwrap(), + InterpreterConfig { + version: PythonVersion { major: 3, minor: 7 }, + implementation: PythonImplementation::CPython, + shared: true, + abi3: false, + lib_name: None, + lib_dir: None, + executable: None, + pointer_width: None, + build_flags: BuildFlags::default(), + suppress_build_script_link_lines: false, + extra_build_script_lines: vec![], + } + ) + } + + #[test] + fn test_config_file_unknown_keys() { + // ext_suffix is unknown to pyo3-build-config, but it shouldn't error + assert_eq!( + InterpreterConfig::from_reader("version=3.7\next_suffix=.python37.so".as_bytes()) + .unwrap(), + InterpreterConfig { + version: PythonVersion { major: 3, minor: 7 }, + implementation: PythonImplementation::CPython, + shared: true, + abi3: false, + lib_name: None, + lib_dir: None, + executable: None, + pointer_width: None, + build_flags: BuildFlags::default(), + suppress_build_script_link_lines: false, + extra_build_script_lines: vec![], + } + ) + } + + #[test] + fn build_flags_default() { + assert_eq!(BuildFlags::default(), BuildFlags::new()); + } + + #[test] + fn build_flags_from_sysconfigdata() { + let mut sysconfigdata = Sysconfigdata::new(); + + assert_eq!( + BuildFlags::from_sysconfigdata(&sysconfigdata).0, + HashSet::new() + ); + + for flag in &BuildFlags::ALL { + sysconfigdata.insert(flag.to_string(), "0".into()); + } + + assert_eq!( + BuildFlags::from_sysconfigdata(&sysconfigdata).0, + HashSet::new() + ); + + let mut expected_flags = HashSet::new(); + for flag in &BuildFlags::ALL { + sysconfigdata.insert(flag.to_string(), "1".into()); + expected_flags.insert(flag.clone()); + } + + assert_eq!( + BuildFlags::from_sysconfigdata(&sysconfigdata).0, + expected_flags + ); + } + + #[test] + fn build_flags_fixup() { + let mut build_flags = BuildFlags::new(); + + build_flags = build_flags.fixup(); + assert!(build_flags.0.is_empty()); + + build_flags.0.insert(BuildFlag::Py_DEBUG); + + build_flags = build_flags.fixup(); + + // Py_DEBUG implies Py_REF_DEBUG + assert!(build_flags.0.contains(&BuildFlag::Py_REF_DEBUG)); + } + + #[test] + fn parse_script_output() { + let output = "foo bar\nbar foobar\n\n"; + let map = super::parse_script_output(output); + assert_eq!(map.len(), 2); + assert_eq!(map["foo"], "bar"); + assert_eq!(map["bar"], "foobar"); + } + + #[test] + fn config_from_interpreter() { + // Smoke test to just see whether this works + // + // PyO3's CI is dependent on Python being installed, so this should be reliable. + assert!(make_interpreter_config().is_ok()) + } + + #[test] + fn config_from_empty_sysconfigdata() { + let sysconfigdata = Sysconfigdata::new(); + assert!(InterpreterConfig::from_sysconfigdata(&sysconfigdata).is_err()); + } + + #[test] + fn config_from_sysconfigdata() { + let mut sysconfigdata = Sysconfigdata::new(); + // these are the minimal values required such that InterpreterConfig::from_sysconfigdata + // does not error + sysconfigdata.insert("SOABI", "cpython-37m-x86_64-linux-gnu"); + sysconfigdata.insert("VERSION", "3.7"); + sysconfigdata.insert("Py_ENABLE_SHARED", "1"); + sysconfigdata.insert("LIBDIR", "/usr/lib"); + sysconfigdata.insert("LDVERSION", "3.7m"); + sysconfigdata.insert("SIZEOF_VOID_P", "8"); + assert_eq!( + InterpreterConfig::from_sysconfigdata(&sysconfigdata).unwrap(), + InterpreterConfig { + abi3: false, + build_flags: BuildFlags::from_sysconfigdata(&sysconfigdata), + pointer_width: Some(64), + executable: None, + implementation: PythonImplementation::CPython, + lib_dir: Some("/usr/lib".into()), + lib_name: Some("python3.7m".into()), + shared: true, + version: PythonVersion::PY37, + suppress_build_script_link_lines: false, + extra_build_script_lines: vec![], + } + ); + } + + #[test] + fn config_from_sysconfigdata_framework() { + let mut sysconfigdata = Sysconfigdata::new(); + sysconfigdata.insert("SOABI", "cpython-37m-x86_64-linux-gnu"); + sysconfigdata.insert("VERSION", "3.7"); + // PYTHONFRAMEWORK should override Py_ENABLE_SHARED + sysconfigdata.insert("Py_ENABLE_SHARED", "0"); + sysconfigdata.insert("PYTHONFRAMEWORK", "Python"); + sysconfigdata.insert("LIBDIR", "/usr/lib"); + sysconfigdata.insert("LDVERSION", "3.7m"); + sysconfigdata.insert("SIZEOF_VOID_P", "8"); + assert_eq!( + InterpreterConfig::from_sysconfigdata(&sysconfigdata).unwrap(), + InterpreterConfig { + abi3: false, + build_flags: BuildFlags::from_sysconfigdata(&sysconfigdata), + pointer_width: Some(64), + executable: None, + implementation: PythonImplementation::CPython, + lib_dir: Some("/usr/lib".into()), + lib_name: Some("python3.7m".into()), + shared: true, + version: PythonVersion::PY37, + suppress_build_script_link_lines: false, + extra_build_script_lines: vec![], + } + ); + + sysconfigdata = Sysconfigdata::new(); + sysconfigdata.insert("SOABI", "cpython-37m-x86_64-linux-gnu"); + sysconfigdata.insert("VERSION", "3.7"); + // An empty PYTHONFRAMEWORK means it is not a framework + sysconfigdata.insert("Py_ENABLE_SHARED", "0"); + sysconfigdata.insert("PYTHONFRAMEWORK", ""); + sysconfigdata.insert("LIBDIR", "/usr/lib"); + sysconfigdata.insert("LDVERSION", "3.7m"); + sysconfigdata.insert("SIZEOF_VOID_P", "8"); + assert_eq!( + InterpreterConfig::from_sysconfigdata(&sysconfigdata).unwrap(), + InterpreterConfig { + abi3: false, + build_flags: BuildFlags::from_sysconfigdata(&sysconfigdata), + pointer_width: Some(64), + executable: None, + implementation: PythonImplementation::CPython, + lib_dir: Some("/usr/lib".into()), + lib_name: Some("python3.7m".into()), + shared: false, + version: PythonVersion::PY37, + suppress_build_script_link_lines: false, + extra_build_script_lines: vec![], + } + ); + } + + #[test] + fn windows_hardcoded_abi3_compile() { + let host = triple!("x86_64-pc-windows-msvc"); + let min_version = "3.7".parse().unwrap(); + + assert_eq!( + default_abi3_config(&host, min_version), + InterpreterConfig { + implementation: PythonImplementation::CPython, + version: PythonVersion { major: 3, minor: 7 }, + shared: true, + abi3: true, + lib_name: Some("python3".into()), + lib_dir: None, + executable: None, + pointer_width: None, + build_flags: BuildFlags::default(), + suppress_build_script_link_lines: false, + extra_build_script_lines: vec![], + } + ); + } + + #[test] + fn unix_hardcoded_abi3_compile() { + let host = triple!("x86_64-unknown-linux-gnu"); + let min_version = "3.9".parse().unwrap(); + + assert_eq!( + default_abi3_config(&host, min_version), + InterpreterConfig { + implementation: PythonImplementation::CPython, + version: PythonVersion { major: 3, minor: 9 }, + shared: true, + abi3: true, + lib_name: None, + lib_dir: None, + executable: None, + pointer_width: None, + build_flags: BuildFlags::default(), + suppress_build_script_link_lines: false, + extra_build_script_lines: vec![], + } + ); + } + + #[test] + fn windows_hardcoded_cross_compile() { + let env_vars = CrossCompileEnvVars { + pyo3_cross: None, + pyo3_cross_lib_dir: Some("C:\\some\\path".into()), + pyo3_cross_python_implementation: None, + pyo3_cross_python_version: Some("3.7".into()), + }; + + let host = triple!("x86_64-unknown-linux-gnu"); + let target = triple!("i686-pc-windows-msvc"); + let cross_config = + CrossCompileConfig::try_from_env_vars_host_target(env_vars, &host, &target) + .unwrap() + .unwrap(); + + assert_eq!( + default_cross_compile(&cross_config).unwrap(), + InterpreterConfig { + implementation: PythonImplementation::CPython, + version: PythonVersion { major: 3, minor: 7 }, + shared: true, + abi3: false, + lib_name: Some("python37".into()), + lib_dir: Some("C:\\some\\path".into()), + executable: None, + pointer_width: None, + build_flags: BuildFlags::default(), + suppress_build_script_link_lines: false, + extra_build_script_lines: vec![], + } + ); + } + + #[test] + fn mingw_hardcoded_cross_compile() { + let env_vars = CrossCompileEnvVars { + pyo3_cross: None, + pyo3_cross_lib_dir: Some("/usr/lib/mingw".into()), + pyo3_cross_python_implementation: None, + pyo3_cross_python_version: Some("3.8".into()), + }; + + let host = triple!("x86_64-unknown-linux-gnu"); + let target = triple!("i686-pc-windows-gnu"); + let cross_config = + CrossCompileConfig::try_from_env_vars_host_target(env_vars, &host, &target) + .unwrap() + .unwrap(); + + assert_eq!( + default_cross_compile(&cross_config).unwrap(), + InterpreterConfig { + implementation: PythonImplementation::CPython, + version: PythonVersion { major: 3, minor: 8 }, + shared: true, + abi3: false, + lib_name: Some("python38".into()), + lib_dir: Some("/usr/lib/mingw".into()), + executable: None, + pointer_width: None, + build_flags: BuildFlags::default(), + suppress_build_script_link_lines: false, + extra_build_script_lines: vec![], + } + ); + } + + #[test] + fn unix_hardcoded_cross_compile() { + let env_vars = CrossCompileEnvVars { + pyo3_cross: None, + pyo3_cross_lib_dir: Some("/usr/arm64/lib".into()), + pyo3_cross_python_implementation: None, + pyo3_cross_python_version: Some("3.9".into()), + }; + + let host = triple!("x86_64-unknown-linux-gnu"); + let target = triple!("aarch64-unknown-linux-gnu"); + let cross_config = + CrossCompileConfig::try_from_env_vars_host_target(env_vars, &host, &target) + .unwrap() + .unwrap(); + + assert_eq!( + default_cross_compile(&cross_config).unwrap(), + InterpreterConfig { + implementation: PythonImplementation::CPython, + version: PythonVersion { major: 3, minor: 9 }, + shared: true, + abi3: false, + lib_name: Some("python3.9".into()), + lib_dir: Some("/usr/arm64/lib".into()), + executable: None, + pointer_width: None, + build_flags: BuildFlags::default(), + suppress_build_script_link_lines: false, + extra_build_script_lines: vec![], + } + ); + } + + #[test] + fn pypy_hardcoded_cross_compile() { + let env_vars = CrossCompileEnvVars { + pyo3_cross: None, + pyo3_cross_lib_dir: None, + pyo3_cross_python_implementation: Some("PyPy".into()), + pyo3_cross_python_version: Some("3.10".into()), + }; + + let triple = triple!("x86_64-unknown-linux-gnu"); + let cross_config = + CrossCompileConfig::try_from_env_vars_host_target(env_vars, &triple, &triple) + .unwrap() + .unwrap(); + + assert_eq!( + default_cross_compile(&cross_config).unwrap(), + InterpreterConfig { + implementation: PythonImplementation::PyPy, + version: PythonVersion { + major: 3, + minor: 10 + }, + shared: true, + abi3: false, + lib_name: Some("pypy3.10-c".into()), + lib_dir: None, + executable: None, + pointer_width: None, + build_flags: BuildFlags::default(), + suppress_build_script_link_lines: false, + extra_build_script_lines: vec![], + } + ); + } + + #[test] + fn default_lib_name_windows() { + use PythonImplementation::*; + assert_eq!( + super::default_lib_name_windows( + PythonVersion { major: 3, minor: 9 }, + CPython, + false, + false, + false, + ), + "python39", + ); + assert_eq!( + super::default_lib_name_windows( + PythonVersion { major: 3, minor: 9 }, + CPython, + true, + false, + false, + ), + "python3", + ); + assert_eq!( + super::default_lib_name_windows( + PythonVersion { major: 3, minor: 9 }, + CPython, + false, + true, + false, + ), + "python3.9", + ); + assert_eq!( + super::default_lib_name_windows( + PythonVersion { major: 3, minor: 9 }, + CPython, + true, + true, + false, + ), + "python3", + ); + assert_eq!( + super::default_lib_name_windows( + PythonVersion { major: 3, minor: 9 }, + PyPy, + true, + false, + false, + ), + "python39", + ); + assert_eq!( + super::default_lib_name_windows( + PythonVersion { major: 3, minor: 9 }, + CPython, + false, + false, + true, + ), + "python39_d", + ); + // abi3 debug builds on windows use version-specific lib + // to workaround https://github.com/python/cpython/issues/101614 + assert_eq!( + super::default_lib_name_windows( + PythonVersion { major: 3, minor: 9 }, + CPython, + true, + false, + true, + ), + "python39_d", + ); + } + + #[test] + fn default_lib_name_unix() { + use PythonImplementation::*; + // Defaults to python3.7m for CPython 3.7 + assert_eq!( + super::default_lib_name_unix(PythonVersion { major: 3, minor: 7 }, CPython, None), + "python3.7m", + ); + // Defaults to pythonX.Y for CPython 3.8+ + assert_eq!( + super::default_lib_name_unix(PythonVersion { major: 3, minor: 8 }, CPython, None), + "python3.8", + ); + assert_eq!( + super::default_lib_name_unix(PythonVersion { major: 3, minor: 9 }, CPython, None), + "python3.9", + ); + // Can use ldversion to override for CPython + assert_eq!( + super::default_lib_name_unix( + PythonVersion { major: 3, minor: 9 }, + CPython, + Some("3.7md") + ), + "python3.7md", + ); + + // PyPy 3.9 includes ldversion + assert_eq!( + super::default_lib_name_unix(PythonVersion { major: 3, minor: 9 }, PyPy, None), + "pypy3.9-c", + ); + + assert_eq!( + super::default_lib_name_unix(PythonVersion { major: 3, minor: 9 }, PyPy, Some("3.9d")), + "pypy3.9d-c", + ); + } + + #[test] + fn parse_cross_python_version() { + let env_vars = CrossCompileEnvVars { + pyo3_cross: None, + pyo3_cross_lib_dir: None, + pyo3_cross_python_version: Some("3.9".into()), + pyo3_cross_python_implementation: None, + }; + + assert_eq!( + env_vars.parse_version().unwrap(), + Some(PythonVersion { major: 3, minor: 9 }) + ); + + let env_vars = CrossCompileEnvVars { + pyo3_cross: None, + pyo3_cross_lib_dir: None, + pyo3_cross_python_version: None, + pyo3_cross_python_implementation: None, + }; + + assert_eq!(env_vars.parse_version().unwrap(), None); + + let env_vars = CrossCompileEnvVars { + pyo3_cross: None, + pyo3_cross_lib_dir: None, + pyo3_cross_python_version: Some("100".into()), + pyo3_cross_python_implementation: None, + }; + + assert!(env_vars.parse_version().is_err()); + } + + #[test] + fn interpreter_version_reduced_to_abi3() { + let mut config = InterpreterConfig { + abi3: true, + build_flags: BuildFlags::default(), + pointer_width: None, + executable: None, + implementation: PythonImplementation::CPython, + lib_dir: None, + lib_name: None, + shared: true, + version: PythonVersion { major: 3, minor: 7 }, + suppress_build_script_link_lines: false, + extra_build_script_lines: vec![], + }; + + config + .fixup_for_abi3_version(Some(PythonVersion { major: 3, minor: 7 })) + .unwrap(); + assert_eq!(config.version, PythonVersion { major: 3, minor: 7 }); + } + + #[test] + fn abi3_version_cannot_be_higher_than_interpreter() { + let mut config = InterpreterConfig { + abi3: true, + build_flags: BuildFlags::new(), + pointer_width: None, + executable: None, + implementation: PythonImplementation::CPython, + lib_dir: None, + lib_name: None, + shared: true, + version: PythonVersion { major: 3, minor: 7 }, + suppress_build_script_link_lines: false, + extra_build_script_lines: vec![], + }; + + assert!(config + .fixup_for_abi3_version(Some(PythonVersion { major: 3, minor: 8 })) + .unwrap_err() + .to_string() + .contains( + "cannot set a minimum Python version 3.8 higher than the interpreter version 3.7" + )); + } + + #[test] + #[cfg(all( + target_os = "linux", + target_arch = "x86_64", + feature = "resolve-config" + ))] + fn parse_sysconfigdata() { + // A best effort attempt to get test coverage for the sysconfigdata parsing. + // Might not complete successfully depending on host installation; that's ok as long as + // CI demonstrates this path is covered! + + let interpreter_config = crate::get(); + + let lib_dir = match &interpreter_config.lib_dir { + Some(lib_dir) => Path::new(lib_dir), + // Don't know where to search for sysconfigdata; never mind. + None => return, + }; + + let cross = CrossCompileConfig { + lib_dir: Some(lib_dir.into()), + version: Some(interpreter_config.version), + implementation: Some(interpreter_config.implementation), + target: triple!("x86_64-unknown-linux-gnu"), + }; + + let sysconfigdata_path = match find_sysconfigdata(&cross) { + Ok(Some(path)) => path, + // Couldn't find a matching sysconfigdata; never mind! + _ => return, + }; + let sysconfigdata = super::parse_sysconfigdata(sysconfigdata_path).unwrap(); + let parsed_config = InterpreterConfig::from_sysconfigdata(&sysconfigdata).unwrap(); + + assert_eq!( + parsed_config, + InterpreterConfig { + abi3: false, + build_flags: BuildFlags(interpreter_config.build_flags.0.clone()), + pointer_width: Some(64), + executable: None, + implementation: PythonImplementation::CPython, + lib_dir: interpreter_config.lib_dir.to_owned(), + lib_name: interpreter_config.lib_name.to_owned(), + shared: true, + version: interpreter_config.version, + suppress_build_script_link_lines: false, + extra_build_script_lines: vec![], + } + ) + } + + #[test] + fn test_venv_interpreter() { + let base = OsStr::new("base"); + assert_eq!( + venv_interpreter(base, true), + PathBuf::from_iter(&["base", "Scripts", "python.exe"]) + ); + assert_eq!( + venv_interpreter(base, false), + PathBuf::from_iter(&["base", "bin", "python"]) + ); + } + + #[test] + fn test_conda_env_interpreter() { + let base = OsStr::new("base"); + assert_eq!( + conda_env_interpreter(base, true), + PathBuf::from_iter(&["base", "python.exe"]) + ); + assert_eq!( + conda_env_interpreter(base, false), + PathBuf::from_iter(&["base", "bin", "python"]) + ); + } + + #[test] + fn test_not_cross_compiling_from_to() { + assert!(cross_compiling_from_to( + &triple!("x86_64-unknown-linux-gnu"), + &triple!("x86_64-unknown-linux-gnu"), + ) + .unwrap() + .is_none()); + + assert!(cross_compiling_from_to( + &triple!("x86_64-apple-darwin"), + &triple!("x86_64-apple-darwin") + ) + .unwrap() + .is_none()); + + assert!(cross_compiling_from_to( + &triple!("aarch64-apple-darwin"), + &triple!("x86_64-apple-darwin") + ) + .unwrap() + .is_none()); + + assert!(cross_compiling_from_to( + &triple!("x86_64-apple-darwin"), + &triple!("aarch64-apple-darwin") + ) + .unwrap() + .is_none()); + + assert!(cross_compiling_from_to( + &triple!("x86_64-pc-windows-msvc"), + &triple!("i686-pc-windows-msvc") + ) + .unwrap() + .is_none()); + + assert!(cross_compiling_from_to( + &triple!("x86_64-unknown-linux-gnu"), + &triple!("x86_64-unknown-linux-musl") + ) + .unwrap() + .is_none()); + } + + #[test] + fn test_run_python_script() { + // as above, this should be okay in CI where Python is presumed installed + let interpreter = make_interpreter_config() + .expect("could not get InterpreterConfig from installed interpreter"); + let out = interpreter + .run_python_script("print(2 + 2)") + .expect("failed to run Python script"); + assert_eq!(out.trim_end(), "4"); + } + + #[test] + fn test_run_python_script_with_envs() { + // as above, this should be okay in CI where Python is presumed installed + let interpreter = make_interpreter_config() + .expect("could not get InterpreterConfig from installed interpreter"); + let out = interpreter + .run_python_script_with_envs( + "import os; print(os.getenv('PYO3_TEST'))", + vec![("PYO3_TEST", "42")], + ) + .expect("failed to run Python script"); + assert_eq!(out.trim_end(), "42"); + } + + #[test] + fn test_build_script_outputs_base() { + let interpreter_config = InterpreterConfig { + implementation: PythonImplementation::CPython, + version: PythonVersion { major: 3, minor: 9 }, + shared: true, + abi3: false, + lib_name: Some("python3".into()), + lib_dir: None, + executable: None, + pointer_width: None, + build_flags: BuildFlags::default(), + suppress_build_script_link_lines: false, + extra_build_script_lines: vec![], + }; + assert_eq!( + interpreter_config.build_script_outputs(), + [ + "cargo:rustc-cfg=Py_3_7".to_owned(), + "cargo:rustc-cfg=Py_3_8".to_owned(), + "cargo:rustc-cfg=Py_3_9".to_owned(), + ] + ); + + let interpreter_config = InterpreterConfig { + implementation: PythonImplementation::PyPy, + ..interpreter_config + }; + assert_eq!( + interpreter_config.build_script_outputs(), + [ + "cargo:rustc-cfg=Py_3_7".to_owned(), + "cargo:rustc-cfg=Py_3_8".to_owned(), + "cargo:rustc-cfg=Py_3_9".to_owned(), + "cargo:rustc-cfg=PyPy".to_owned(), + ] + ); + } + + #[test] + fn test_build_script_outputs_abi3() { + let interpreter_config = InterpreterConfig { + implementation: PythonImplementation::CPython, + version: PythonVersion { major: 3, minor: 9 }, + shared: true, + abi3: true, + lib_name: Some("python3".into()), + lib_dir: None, + executable: None, + pointer_width: None, + build_flags: BuildFlags::default(), + suppress_build_script_link_lines: false, + extra_build_script_lines: vec![], + }; + + assert_eq!( + interpreter_config.build_script_outputs(), + [ + "cargo:rustc-cfg=Py_3_7".to_owned(), + "cargo:rustc-cfg=Py_3_8".to_owned(), + "cargo:rustc-cfg=Py_3_9".to_owned(), + "cargo:rustc-cfg=Py_LIMITED_API".to_owned(), + ] + ); + + let interpreter_config = InterpreterConfig { + implementation: PythonImplementation::PyPy, + ..interpreter_config + }; + assert_eq!( + interpreter_config.build_script_outputs(), + [ + "cargo:rustc-cfg=Py_3_7".to_owned(), + "cargo:rustc-cfg=Py_3_8".to_owned(), + "cargo:rustc-cfg=Py_3_9".to_owned(), + "cargo:rustc-cfg=PyPy".to_owned(), + "cargo:rustc-cfg=Py_LIMITED_API".to_owned(), + ] + ); + } + + #[test] + fn test_build_script_outputs_gil_disabled() { + let mut build_flags = BuildFlags::default(); + build_flags.0.insert(BuildFlag::Py_GIL_DISABLED); + let interpreter_config = InterpreterConfig { + implementation: PythonImplementation::CPython, + version: PythonVersion { + major: 3, + minor: 13, + }, + shared: true, + abi3: false, + lib_name: Some("python3".into()), + lib_dir: None, + executable: None, + pointer_width: None, + build_flags, + suppress_build_script_link_lines: false, + extra_build_script_lines: vec![], + }; + + assert_eq!( + interpreter_config.build_script_outputs(), + [ + "cargo:rustc-cfg=Py_3_7".to_owned(), + "cargo:rustc-cfg=Py_3_8".to_owned(), + "cargo:rustc-cfg=Py_3_9".to_owned(), + "cargo:rustc-cfg=Py_3_10".to_owned(), + "cargo:rustc-cfg=Py_3_11".to_owned(), + "cargo:rustc-cfg=Py_3_12".to_owned(), + "cargo:rustc-cfg=Py_3_13".to_owned(), + "cargo:rustc-cfg=Py_GIL_DISABLED".to_owned(), + ] + ); + } + + #[test] + fn test_build_script_outputs_debug() { + let mut build_flags = BuildFlags::default(); + build_flags.0.insert(BuildFlag::Py_DEBUG); + let interpreter_config = InterpreterConfig { + implementation: PythonImplementation::CPython, + version: PythonVersion { major: 3, minor: 7 }, + shared: true, + abi3: false, + lib_name: Some("python3".into()), + lib_dir: None, + executable: None, + pointer_width: None, + build_flags, + suppress_build_script_link_lines: false, + extra_build_script_lines: vec![], + }; + + assert_eq!( + interpreter_config.build_script_outputs(), + [ + "cargo:rustc-cfg=Py_3_7".to_owned(), + "cargo:rustc-cfg=py_sys_config=\"Py_DEBUG\"".to_owned(), + ] + ); + } + + #[test] + fn test_find_sysconfigdata_in_invalid_lib_dir() { + let e = find_all_sysconfigdata(&CrossCompileConfig { + lib_dir: Some(PathBuf::from("/abc/123/not/a/real/path")), + version: None, + implementation: None, + target: triple!("x86_64-unknown-linux-gnu"), + }) + .unwrap_err(); + + // actual error message is platform-dependent, so just check the context we add + assert!(e.report().to_string().starts_with( + "failed to search the lib dir at 'PYO3_CROSS_LIB_DIR=/abc/123/not/a/real/path'\n\ + caused by:\n \ + - 0: failed to list the entries in '/abc/123/not/a/real/path'\n \ + - 1: \ + " + )); + } +} diff --git a/include/pyo3/pyo3-build-config/src/import_lib.rs b/include/pyo3/pyo3-build-config/src/import_lib.rs new file mode 100644 index 00000000..0925a861 --- /dev/null +++ b/include/pyo3/pyo3-build-config/src/import_lib.rs @@ -0,0 +1,62 @@ +//! Optional `python3.dll` import library generator for Windows + +use std::env; +use std::path::PathBuf; + +use python3_dll_a::ImportLibraryGenerator; +use target_lexicon::{Architecture, OperatingSystem, Triple}; + +use super::{PythonImplementation, PythonVersion}; +use crate::errors::{Context, Error, Result}; + +/// Generates the `python3.dll` or `pythonXY.dll` import library for Windows targets. +/// +/// Places the generated import library into the build script output directory +/// and returns the full library directory path. +/// +/// Does nothing if the target OS is not Windows. +pub(super) fn generate_import_lib( + target: &Triple, + py_impl: PythonImplementation, + py_version: Option, +) -> Result> { + if target.operating_system != OperatingSystem::Windows { + return Ok(None); + } + + let out_dir = + env::var_os("OUT_DIR").expect("generate_import_lib() must be called from a build script"); + + // Put the newly created import library into the build script output directory. + let mut out_lib_dir = PathBuf::from(out_dir); + out_lib_dir.push("lib"); + + // Convert `Architecture` enum to rustc `target_arch` option format. + let arch = match target.architecture { + // i686, i586, etc. + Architecture::X86_32(_) => "x86".to_string(), + other => other.to_string(), + }; + + let env = target.environment.to_string(); + let implementation = match py_impl { + PythonImplementation::CPython => python3_dll_a::PythonImplementation::CPython, + PythonImplementation::PyPy => python3_dll_a::PythonImplementation::PyPy, + PythonImplementation::GraalPy => { + return Err(Error::from("No support for GraalPy on Windows")) + } + }; + + ImportLibraryGenerator::new(&arch, &env) + .version(py_version.map(|v| (v.major, v.minor))) + .implementation(implementation) + .generate(&out_lib_dir) + .context("failed to generate python3.dll import library")?; + + let out_lib_dir_string = out_lib_dir + .to_str() + .ok_or("build directory is not a valid UTF-8 string")? + .to_owned(); + + Ok(Some(out_lib_dir_string)) +} diff --git a/include/pyo3/pyo3-build-config/src/lib.rs b/include/pyo3/pyo3-build-config/src/lib.rs new file mode 100644 index 00000000..033e7b46 --- /dev/null +++ b/include/pyo3/pyo3-build-config/src/lib.rs @@ -0,0 +1,294 @@ +//! Configuration used by PyO3 for conditional support of varying Python versions. +//! +//! This crate exposes functionality to be called from build scripts to simplify building crates +//! which depend on PyO3. +//! +//! It used internally by the PyO3 crate's build script to apply the same configuration. + +#![warn(elided_lifetimes_in_paths, unused_lifetimes)] + +mod errors; +mod impl_; + +#[cfg(feature = "resolve-config")] +use std::{ + io::Cursor, + path::{Path, PathBuf}, +}; + +use std::{env, process::Command, str::FromStr}; + +use once_cell::sync::OnceCell; + +pub use impl_::{ + cross_compiling_from_to, find_all_sysconfigdata, parse_sysconfigdata, BuildFlag, BuildFlags, + CrossCompileConfig, InterpreterConfig, PythonImplementation, PythonVersion, Triple, +}; +use target_lexicon::OperatingSystem; + +/// Adds all the [`#[cfg]` flags](index.html) to the current compilation. +/// +/// This should be called from a build script. +/// +/// The full list of attributes added are the following: +/// +/// | Flag | Description | +/// | ---- | ----------- | +/// | `#[cfg(Py_3_7)]`, `#[cfg(Py_3_8)]`, `#[cfg(Py_3_9)]`, `#[cfg(Py_3_10)]` | These attributes mark code only for a given Python version and up. For example, `#[cfg(Py_3_7)]` marks code which can run on Python 3.7 **and newer**. | +/// | `#[cfg(Py_LIMITED_API)]` | This marks code which is run when compiling with PyO3's `abi3` feature enabled. | +/// | `#[cfg(PyPy)]` | This marks code which is run when compiling for PyPy. | +/// | `#[cfg(GraalPy)]` | This marks code which is run when compiling for GraalPy. | +/// +/// For examples of how to use these attributes, +#[doc = concat!("[see PyO3's guide](https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/building-and-distribution/multiple_python_versions.html)")] +/// . +#[cfg(feature = "resolve-config")] +pub fn use_pyo3_cfgs() { + print_expected_cfgs(); + for cargo_command in get().build_script_outputs() { + println!("{}", cargo_command) + } +} + +/// Adds linker arguments suitable for PyO3's `extension-module` feature. +/// +/// This should be called from a build script. +/// +/// The following link flags are added: +/// - macOS: `-undefined dynamic_lookup` +/// - wasm32-unknown-emscripten: `-sSIDE_MODULE=2 -sWASM_BIGINT` +/// +/// All other platforms currently are no-ops, however this may change as necessary +/// in future. +pub fn add_extension_module_link_args() { + _add_extension_module_link_args(&impl_::target_triple_from_env(), std::io::stdout()) +} + +fn _add_extension_module_link_args(triple: &Triple, mut writer: impl std::io::Write) { + if triple.operating_system == OperatingSystem::Darwin { + writeln!(writer, "cargo:rustc-cdylib-link-arg=-undefined").unwrap(); + writeln!(writer, "cargo:rustc-cdylib-link-arg=dynamic_lookup").unwrap(); + } else if triple == &Triple::from_str("wasm32-unknown-emscripten").unwrap() { + writeln!(writer, "cargo:rustc-cdylib-link-arg=-sSIDE_MODULE=2").unwrap(); + writeln!(writer, "cargo:rustc-cdylib-link-arg=-sWASM_BIGINT").unwrap(); + } +} + +/// Loads the configuration determined from the build environment. +/// +/// Because this will never change in a given compilation run, this is cached in a `once_cell`. +#[cfg(feature = "resolve-config")] +pub fn get() -> &'static InterpreterConfig { + static CONFIG: OnceCell = OnceCell::new(); + CONFIG.get_or_init(|| { + // Check if we are in a build script and cross compiling to a different target. + let cross_compile_config_path = resolve_cross_compile_config_path(); + let cross_compiling = cross_compile_config_path + .as_ref() + .map(|path| path.exists()) + .unwrap_or(false); + + // CONFIG_FILE is generated in build.rs, so it's content can vary + #[allow(unknown_lints, clippy::const_is_empty)] + if let Some(interpreter_config) = InterpreterConfig::from_cargo_dep_env() { + interpreter_config + } else if !CONFIG_FILE.is_empty() { + InterpreterConfig::from_reader(Cursor::new(CONFIG_FILE)) + } else if cross_compiling { + InterpreterConfig::from_path(cross_compile_config_path.as_ref().unwrap()) + } else { + InterpreterConfig::from_reader(Cursor::new(HOST_CONFIG)) + } + .expect("failed to parse PyO3 config") + }) +} + +/// Build configuration provided by `PYO3_CONFIG_FILE`. May be empty if env var not set. +#[doc(hidden)] +#[cfg(feature = "resolve-config")] +const CONFIG_FILE: &str = include_str!(concat!(env!("OUT_DIR"), "/pyo3-build-config-file.txt")); + +/// Build configuration discovered by `pyo3-build-config` build script. Not aware of +/// cross-compilation settings. +#[doc(hidden)] +#[cfg(feature = "resolve-config")] +const HOST_CONFIG: &str = include_str!(concat!(env!("OUT_DIR"), "/pyo3-build-config.txt")); + +/// Returns the path where PyO3's build.rs writes its cross compile configuration. +/// +/// The config file will be named `$OUT_DIR//pyo3-build-config.txt`. +/// +/// Must be called from a build script, returns `None` if not. +#[doc(hidden)] +#[cfg(feature = "resolve-config")] +fn resolve_cross_compile_config_path() -> Option { + env::var_os("TARGET").map(|target| { + let mut path = PathBuf::from(env!("OUT_DIR")); + path.push(Path::new(&target)); + path.push("pyo3-build-config.txt"); + path + }) +} + +/// 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); + + // 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"); + } + + // 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"); + } +} + +/// Registers `pyo3`s config names as reachable cfg expressions +/// +/// - +/// - +#[doc(hidden)] +pub fn print_expected_cfgs() { + if rustc_minor_version().map_or(false, |version| version < 80) { + // rustc 1.80.0 stabilized `rustc-check-cfg` feature, don't emit before + return; + } + + println!("cargo:rustc-check-cfg=cfg(Py_LIMITED_API)"); + println!("cargo:rustc-check-cfg=cfg(Py_GIL_DISABLED)"); + 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)"); + + // allow `Py_3_*` cfgs from the minimum supported version up to the + // maximum minor version (+1 for development for the next) + for i in impl_::MINIMUM_SUPPORTED_VERSION.minor..=impl_::ABI3_MAX_MINOR + 1 { + println!("cargo:rustc-check-cfg=cfg(Py_3_{i})"); + } +} + +/// Private exports used in PyO3's build.rs +/// +/// Please don't use these - they could change at any time. +#[doc(hidden)] +pub mod pyo3_build_script_impl { + #[cfg(feature = "resolve-config")] + use crate::errors::{Context, Result}; + + #[cfg(feature = "resolve-config")] + use super::*; + + pub mod errors { + pub use crate::errors::*; + } + pub use crate::impl_::{ + cargo_env_var, env_var, is_linking_libpython, make_cross_compile_config, InterpreterConfig, + PythonVersion, + }; + + /// Gets the configuration for use from PyO3's build script. + /// + /// Differs from .get() above only in the cross-compile case, where PyO3's build script is + /// required to generate a new config (as it's the first build script which has access to the + /// correct value for CARGO_CFG_TARGET_OS). + #[cfg(feature = "resolve-config")] + pub fn resolve_interpreter_config() -> Result { + // CONFIG_FILE is generated in build.rs, so it's content can vary + #[allow(unknown_lints, clippy::const_is_empty)] + if !CONFIG_FILE.is_empty() { + let mut interperter_config = InterpreterConfig::from_reader(Cursor::new(CONFIG_FILE))?; + interperter_config.generate_import_libs()?; + Ok(interperter_config) + } else if let Some(interpreter_config) = make_cross_compile_config()? { + // This is a cross compile and need to write the config file. + let path = resolve_cross_compile_config_path() + .expect("resolve_interpreter_config() must be called from a build script"); + let parent_dir = path.parent().ok_or_else(|| { + format!( + "failed to resolve parent directory of config file {}", + path.display() + ) + })?; + std::fs::create_dir_all(parent_dir).with_context(|| { + format!( + "failed to create config file directory {}", + parent_dir.display() + ) + })?; + interpreter_config.to_writer(&mut std::fs::File::create(&path).with_context( + || format!("failed to create config file at {}", path.display()), + )?)?; + Ok(interpreter_config) + } else { + InterpreterConfig::from_reader(Cursor::new(HOST_CONFIG)) + } + } +} + +fn rustc_minor_version() -> Option { + static RUSTC_MINOR_VERSION: OnceCell> = OnceCell::new(); + *RUSTC_MINOR_VERSION.get_or_init(|| { + let rustc = env::var_os("RUSTC")?; + let output = Command::new(rustc).arg("--version").output().ok()?; + let version = core::str::from_utf8(&output.stdout).ok()?; + let mut pieces = version.split('.'); + if pieces.next() != Some("rustc 1") { + return None; + } + pieces.next()?.parse().ok() + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn extension_module_link_args() { + let mut buf = Vec::new(); + + // Does nothing on non-mac + _add_extension_module_link_args( + &Triple::from_str("x86_64-pc-windows-msvc").unwrap(), + &mut buf, + ); + assert_eq!(buf, Vec::new()); + + _add_extension_module_link_args( + &Triple::from_str("x86_64-apple-darwin").unwrap(), + &mut buf, + ); + assert_eq!( + std::str::from_utf8(&buf).unwrap(), + "cargo:rustc-cdylib-link-arg=-undefined\n\ + cargo:rustc-cdylib-link-arg=dynamic_lookup\n" + ); + + buf.clear(); + _add_extension_module_link_args( + &Triple::from_str("wasm32-unknown-emscripten").unwrap(), + &mut buf, + ); + assert_eq!( + std::str::from_utf8(&buf).unwrap(), + "cargo:rustc-cdylib-link-arg=-sSIDE_MODULE=2\n\ + cargo:rustc-cdylib-link-arg=-sWASM_BIGINT\n" + ); + } +} diff --git a/include/pyo3/pyo3-ffi/ACKNOWLEDGEMENTS b/include/pyo3/pyo3-ffi/ACKNOWLEDGEMENTS new file mode 100644 index 00000000..4502d777 --- /dev/null +++ b/include/pyo3/pyo3-ffi/ACKNOWLEDGEMENTS @@ -0,0 +1,6 @@ +This is a Rust reimplementation of the CPython public header files as necessary +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 diff --git a/include/pyo3/pyo3-ffi/Cargo.toml b/include/pyo3/pyo3-ffi/Cargo.toml new file mode 100644 index 00000000..e569ca1d --- /dev/null +++ b/include/pyo3/pyo3-ffi/Cargo.toml @@ -0,0 +1,44 @@ +[package] +name = "pyo3-ffi" +version = "0.23.0-dev" +description = "Python-API bindings for the PyO3 ecosystem" +authors = ["PyO3 Project and Contributors "] +keywords = ["pyo3", "python", "cpython", "ffi"] +homepage = "https://github.com/pyo3/pyo3" +repository = "https://github.com/pyo3/pyo3" +categories = ["api-bindings", "development-tools::ffi"] +license = "MIT OR Apache-2.0" +edition = "2021" +links = "python" + +[dependencies] +libc = "0.2.62" + +[features] + +default = [] + +# Use this feature when building an extension module. +# It tells the linker to keep the python symbols unresolved, +# so that the module can also be used with statically linked python interpreters. +extension-module = ["pyo3-build-config/extension-module"] + +# Use the Python limited API. See https://www.python.org/dev/peps/pep-0384/ for more. +abi3 = ["pyo3-build-config/abi3"] + +# With abi3, we can manually set the minimum Python version. +abi3-py37 = ["abi3-py38", "pyo3-build-config/abi3-py37"] +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"] + +# Automatically generates `python3.dll` import libraries for Windows targets. +generate-import-lib = ["pyo3-build-config/python3-dll-a"] + +[dev-dependencies] +paste = "1" + +[build-dependencies] +pyo3-build-config = { path = "../pyo3-build-config", version = "=0.23.0-dev", features = ["resolve-config"] } diff --git a/include/pyo3/pyo3-ffi/build.rs b/include/pyo3/pyo3-ffi/build.rs new file mode 100644 index 00000000..b0f1c28d --- /dev/null +++ b/include/pyo3/pyo3-ffi/build.rs @@ -0,0 +1,251 @@ +use pyo3_build_config::{ + bail, ensure, print_feature_cfgs, + pyo3_build_script_impl::{ + cargo_env_var, env_var, errors::Result, is_linking_libpython, resolve_interpreter_config, + InterpreterConfig, PythonVersion, + }, + warn, BuildFlag, PythonImplementation, +}; +use std::ops::Not; + +/// Minimum Python version PyO3 supports. +struct SupportedVersions { + min: PythonVersion, + max: PythonVersion, +} + +const SUPPORTED_VERSIONS_CPYTHON: SupportedVersions = SupportedVersions { + min: PythonVersion { major: 3, minor: 7 }, + max: PythonVersion { + major: 3, + minor: 13, + }, +}; + +const SUPPORTED_VERSIONS_PYPY: SupportedVersions = SupportedVersions { + min: PythonVersion { major: 3, minor: 7 }, + max: PythonVersion { + major: 3, + minor: 10, + }, +}; + +const SUPPORTED_VERSIONS_GRAALPY: SupportedVersions = SupportedVersions { + min: PythonVersion { + major: 3, + minor: 10, + }, + max: PythonVersion { + major: 3, + minor: 11, + }, +}; + +fn ensure_python_version(interpreter_config: &InterpreterConfig) -> Result<()> { + // This is an undocumented env var which is only really intended to be used in CI / for testing + // and development. + if std::env::var("UNSAFE_PYO3_SKIP_VERSION_CHECK").as_deref() == Ok("1") { + return Ok(()); + } + + match interpreter_config.implementation { + PythonImplementation::CPython => { + let versions = SUPPORTED_VERSIONS_CPYTHON; + ensure!( + interpreter_config.version >= versions.min, + "the configured Python interpreter version ({}) is lower than PyO3's minimum supported version ({})", + 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(), + ); + } + PythonImplementation::PyPy => { + let versions = SUPPORTED_VERSIONS_PYPY; + ensure!( + interpreter_config.version >= versions.min, + "the configured PyPy interpreter version ({}) is lower than PyO3's minimum supported version ({})", + interpreter_config.version, + versions.min, + ); + // PyO3 does not support abi3, so we cannot offer forward compatibility + ensure!( + interpreter_config.version <= versions.max, + "the configured PyPy interpreter version ({}) is newer than PyO3's maximum supported version ({})\n\ + = help: please check if an updated version of PyO3 is available. Current version: {}", + interpreter_config.version, + versions.max, + std::env::var("CARGO_PKG_VERSION").unwrap() + ); + } + PythonImplementation::GraalPy => { + let versions = SUPPORTED_VERSIONS_GRAALPY; + ensure!( + interpreter_config.version >= versions.min, + "the configured GraalPy interpreter version ({}) is lower than PyO3's minimum supported version ({})", + interpreter_config.version, + versions.min, + ); + // GraalPy does not support abi3, so we cannot offer forward compatibility + ensure!( + interpreter_config.version <= versions.max, + "the configured GraalPy interpreter version ({}) is newer than PyO3's maximum supported version ({})\n\ + = help: please check if an updated version of PyO3 is available. Current version: {}", + interpreter_config.version, + versions.max, + std::env::var("CARGO_PKG_VERSION").unwrap() + ); + } + } + + if interpreter_config.abi3 { + match interpreter_config.implementation { + PythonImplementation::CPython => {} + 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." + ), + PythonImplementation::GraalPy => warn!( + "GraalPy does not support abi3 so the build artifacts will be version-specific." + ), + } + } + + 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 + let rust_target = match cargo_env_var("CARGO_CFG_TARGET_POINTER_WIDTH") + .unwrap() + .as_str() + { + "64" => 64, + "32" => 32, + x => bail!("unexpected Rust target pointer width: {}", x), + }; + + ensure!( + rust_target == pointer_width, + "your Rust target architecture ({}-bit) does not match your python interpreter ({}-bit)", + rust_target, + pointer_width + ); + } + Ok(()) +} + +fn emit_link_config(interpreter_config: &InterpreterConfig) -> Result<()> { + let target_os = cargo_env_var("CARGO_CFG_TARGET_OS").unwrap(); + + println!( + "cargo:rustc-link-lib={link_model}{alias}{lib_name}", + link_model = if interpreter_config.shared { + "" + } else { + "static=" + }, + alias = if target_os == "windows" { + "pythonXY:" + } else { + "" + }, + lib_name = interpreter_config.lib_name.as_ref().ok_or( + "attempted to link to Python shared library but config does not contain lib_name" + )?, + ); + + if let Some(lib_dir) = &interpreter_config.lib_dir { + println!("cargo:rustc-link-search=native={}", lib_dir); + } + + Ok(()) +} + +/// Prepares the PyO3 crate for compilation. +/// +/// This loads the config from pyo3-build-config and then makes some additional checks to improve UX +/// for users. +/// +/// Emits the cargo configuration based on this config as well as a few checks of the Rust compiler +/// version to enable features which aren't supported on MSRV. +fn configure_pyo3() -> Result<()> { + let interpreter_config = resolve_interpreter_config()?; + + if env_var("PYO3_PRINT_CONFIG").map_or(false, |os_str| os_str == "1") { + print_config_and_exit(&interpreter_config); + } + + 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()?; + + if is_linking_libpython() && !interpreter_config.suppress_build_script_link_lines { + emit_link_config(&interpreter_config)?; + } + + for cfg in interpreter_config.build_script_outputs() { + println!("{}", cfg) + } + + // Extra lines come last, to support last write wins. + for line in &interpreter_config.extra_build_script_lines { + println!("{}", line); + } + + // Emit cfgs like `invalid_from_utf8_lint` + print_feature_cfgs(); + + Ok(()) +} + +fn print_config_and_exit(config: &InterpreterConfig) { + println!("\n-- PYO3_PRINT_CONFIG=1 is set, printing configuration and halting compile --"); + config + .to_writer(std::io::stdout()) + .expect("failed to print config to stdout"); + println!("\nnote: unset the PYO3_PRINT_CONFIG environment variable and retry to compile with the above config"); + std::process::exit(101); +} + +fn main() { + pyo3_build_config::print_expected_cfgs(); + if let Err(e) = configure_pyo3() { + eprintln!("error: {}", e.report()); + std::process::exit(1) + } +} diff --git a/include/pyo3/pyo3-ffi/src/abstract_.rs b/include/pyo3/pyo3-ffi/src/abstract_.rs new file mode 100644 index 00000000..18995450 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/abstract_.rs @@ -0,0 +1,333 @@ +use crate::object::*; +use crate::pyport::Py_ssize_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 +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 +pub unsafe fn PyObject_DelAttr(o: *mut PyObject, attr_name: *mut PyObject) -> c_int { + PyObject_SetAttr(o, attr_name, std::ptr::null_mut()) +} + +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")] + pub fn PyObject_CallNoArgs(func: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyObject_Call")] + pub fn PyObject_Call( + callable_object: *mut PyObject, + args: *mut PyObject, + kw: *mut PyObject, + ) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyObject_CallObject")] + pub fn PyObject_CallObject( + callable_object: *mut PyObject, + args: *mut PyObject, + ) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyObject_CallFunction")] + pub fn PyObject_CallFunction( + callable_object: *mut PyObject, + format: *const c_char, + ... + ) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyObject_CallMethod")] + pub fn PyObject_CallMethod( + o: *mut PyObject, + method: *const c_char, + format: *const c_char, + ... + ) -> *mut PyObject; + + #[cfg(not(Py_3_13))] + #[cfg_attr(PyPy, link_name = "_PyPyObject_CallFunction_SizeT")] + pub fn _PyObject_CallFunction_SizeT( + callable_object: *mut PyObject, + format: *const c_char, + ... + ) -> *mut PyObject; + #[cfg(not(Py_3_13))] + #[cfg_attr(PyPy, link_name = "_PyPyObject_CallMethod_SizeT")] + pub fn _PyObject_CallMethod_SizeT( + o: *mut PyObject, + method: *const c_char, + format: *const c_char, + ... + ) -> *mut PyObject; + + #[cfg_attr(PyPy, link_name = "PyPyObject_CallFunctionObjArgs")] + pub fn PyObject_CallFunctionObjArgs(callable: *mut PyObject, ...) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyObject_CallMethodObjArgs")] + pub fn PyObject_CallMethodObjArgs( + o: *mut PyObject, + method: *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")] + pub fn PyObject_Size(o: *mut PyObject) -> Py_ssize_t; +} + +#[inline] +pub unsafe fn PyObject_Length(o: *mut PyObject) -> Py_ssize_t { + PyObject_Size(o) +} + +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyObject_GetItem")] + pub fn PyObject_GetItem(o: *mut PyObject, key: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyObject_SetItem")] + pub fn PyObject_SetItem(o: *mut PyObject, key: *mut PyObject, v: *mut PyObject) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyObject_DelItemString")] + pub fn PyObject_DelItemString(o: *mut PyObject, key: *const c_char) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyObject_DelItem")] + pub fn PyObject_DelItem(o: *mut PyObject, key: *mut PyObject) -> c_int; +} + +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyObject_Format")] + pub fn PyObject_Format(obj: *mut PyObject, format_spec: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyObject_GetIter")] + pub fn PyObject_GetIter(arg1: *mut PyObject) -> *mut PyObject; +} + +// Before 3.8 PyIter_Check was defined in CPython as a macro, +// but the implementation of that in PyO3 did not work, see +// https://github.com/PyO3/pyo3/pull/2914 +// +// This is a slow implementation which should function equivalently. +#[cfg(not(any(Py_3_8, PyPy)))] +#[inline] +pub unsafe fn PyIter_Check(o: *mut PyObject) -> c_int { + crate::PyObject_HasAttrString(crate::Py_TYPE(o).cast(), c_str!("__next__").as_ptr()) +} + +extern "C" { + #[cfg(any(Py_3_8, PyPy))] + #[cfg_attr(PyPy, link_name = "PyPyIter_Check")] + pub fn PyIter_Check(obj: *mut PyObject) -> c_int; + + #[cfg_attr(PyPy, link_name = "PyPyIter_Next")] + 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); + + #[cfg_attr(PyPy, link_name = "PyPyNumber_Check")] + pub fn PyNumber_Check(o: *mut PyObject) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyNumber_Add")] + pub fn PyNumber_Add(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyNumber_Subtract")] + pub fn PyNumber_Subtract(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyNumber_Multiply")] + pub fn PyNumber_Multiply(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyNumber_MatrixMultiply")] + pub fn PyNumber_MatrixMultiply(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyNumber_FloorDivide")] + pub fn PyNumber_FloorDivide(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyNumber_TrueDivide")] + pub fn PyNumber_TrueDivide(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyNumber_Remainder")] + pub fn PyNumber_Remainder(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyNumber_Divmod")] + pub fn PyNumber_Divmod(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyNumber_Power")] + pub fn PyNumber_Power(o1: *mut PyObject, o2: *mut PyObject, o3: *mut PyObject) + -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyNumber_Negative")] + pub fn PyNumber_Negative(o: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyNumber_Positive")] + pub fn PyNumber_Positive(o: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyNumber_Absolute")] + pub fn PyNumber_Absolute(o: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyNumber_Invert")] + pub fn PyNumber_Invert(o: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyNumber_Lshift")] + pub fn PyNumber_Lshift(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyNumber_Rshift")] + pub fn PyNumber_Rshift(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyNumber_And")] + pub fn PyNumber_And(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyNumber_Xor")] + pub fn PyNumber_Xor(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyNumber_Or")] + pub fn PyNumber_Or(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; +} + +// Defined as this macro in Python limited API, but relies on +// non-limited PyTypeObject. Don't expose this since it cannot be used. +#[cfg(not(any(Py_LIMITED_API, PyPy)))] +#[inline] +pub unsafe fn PyIndex_Check(o: *mut PyObject) -> c_int { + let tp_as_number = (*Py_TYPE(o)).tp_as_number; + (!tp_as_number.is_null() && (*tp_as_number).nb_index.is_some()) as c_int +} + +extern "C" { + #[cfg(any(all(Py_3_8, Py_LIMITED_API), PyPy))] + #[link_name = "PyPyIndex_Check"] + pub fn PyIndex_Check(o: *mut PyObject) -> c_int; + + #[cfg_attr(PyPy, link_name = "PyPyNumber_Index")] + pub fn PyNumber_Index(o: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyNumber_AsSsize_t")] + pub fn PyNumber_AsSsize_t(o: *mut PyObject, exc: *mut PyObject) -> Py_ssize_t; + #[cfg_attr(PyPy, link_name = "PyPyNumber_Long")] + pub fn PyNumber_Long(o: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyNumber_Float")] + pub fn PyNumber_Float(o: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyNumber_InPlaceAdd")] + pub fn PyNumber_InPlaceAdd(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyNumber_InPlaceSubtract")] + pub fn PyNumber_InPlaceSubtract(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyNumber_InPlaceMultiply")] + pub fn PyNumber_InPlaceMultiply(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyNumber_InPlaceMatrixMultiply")] + pub fn PyNumber_InPlaceMatrixMultiply(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyNumber_InPlaceFloorDivide")] + pub fn PyNumber_InPlaceFloorDivide(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyNumber_InPlaceTrueDivide")] + pub fn PyNumber_InPlaceTrueDivide(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyNumber_InPlaceRemainder")] + pub fn PyNumber_InPlaceRemainder(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyNumber_InPlacePower")] + pub fn PyNumber_InPlacePower( + o1: *mut PyObject, + o2: *mut PyObject, + o3: *mut PyObject, + ) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyNumber_InPlaceLshift")] + pub fn PyNumber_InPlaceLshift(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyNumber_InPlaceRshift")] + pub fn PyNumber_InPlaceRshift(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyNumber_InPlaceAnd")] + pub fn PyNumber_InPlaceAnd(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyNumber_InPlaceXor")] + pub fn PyNumber_InPlaceXor(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyNumber_InPlaceOr")] + pub fn PyNumber_InPlaceOr(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; + pub fn PyNumber_ToBase(n: *mut PyObject, base: c_int) -> *mut PyObject; + + #[cfg_attr(PyPy, link_name = "PyPySequence_Check")] + pub fn PySequence_Check(o: *mut PyObject) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPySequence_Size")] + pub fn PySequence_Size(o: *mut PyObject) -> Py_ssize_t; + + #[cfg(PyPy)] + #[link_name = "PyPySequence_Length"] + pub fn PySequence_Length(o: *mut PyObject) -> Py_ssize_t; +} + +#[inline] +#[cfg(not(PyPy))] +pub unsafe fn PySequence_Length(o: *mut PyObject) -> Py_ssize_t { + PySequence_Size(o) +} + +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPySequence_Concat")] + pub fn PySequence_Concat(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPySequence_Repeat")] + pub fn PySequence_Repeat(o: *mut PyObject, count: Py_ssize_t) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPySequence_GetItem")] + pub fn PySequence_GetItem(o: *mut PyObject, i: Py_ssize_t) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPySequence_GetSlice")] + pub fn PySequence_GetSlice(o: *mut PyObject, i1: Py_ssize_t, i2: Py_ssize_t) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPySequence_SetItem")] + pub fn PySequence_SetItem(o: *mut PyObject, i: Py_ssize_t, v: *mut PyObject) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPySequence_DelItem")] + pub fn PySequence_DelItem(o: *mut PyObject, i: Py_ssize_t) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPySequence_SetSlice")] + pub fn PySequence_SetSlice( + o: *mut PyObject, + i1: Py_ssize_t, + i2: Py_ssize_t, + v: *mut PyObject, + ) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPySequence_DelSlice")] + pub fn PySequence_DelSlice(o: *mut PyObject, i1: Py_ssize_t, i2: Py_ssize_t) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPySequence_Tuple")] + pub fn PySequence_Tuple(o: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPySequence_List")] + pub fn PySequence_List(o: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPySequence_Fast")] + pub fn PySequence_Fast(o: *mut PyObject, m: *const c_char) -> *mut PyObject; + // skipped PySequence_Fast_GET_SIZE + // skipped PySequence_Fast_GET_ITEM + // skipped PySequence_Fast_GET_ITEMS + pub fn PySequence_Count(o: *mut PyObject, value: *mut PyObject) -> Py_ssize_t; + #[cfg_attr(PyPy, link_name = "PyPySequence_Contains")] + pub fn PySequence_Contains(seq: *mut PyObject, ob: *mut PyObject) -> c_int; +} + +#[inline] +pub unsafe fn PySequence_In(o: *mut PyObject, value: *mut PyObject) -> c_int { + PySequence_Contains(o, value) +} + +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPySequence_Index")] + pub fn PySequence_Index(o: *mut PyObject, value: *mut PyObject) -> Py_ssize_t; + #[cfg_attr(PyPy, link_name = "PyPySequence_InPlaceConcat")] + pub fn PySequence_InPlaceConcat(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPySequence_InPlaceRepeat")] + pub fn PySequence_InPlaceRepeat(o: *mut PyObject, count: Py_ssize_t) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyMapping_Check")] + pub fn PyMapping_Check(o: *mut PyObject) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyMapping_Size")] + pub fn PyMapping_Size(o: *mut PyObject) -> Py_ssize_t; + + #[cfg(PyPy)] + #[link_name = "PyPyMapping_Length"] + pub fn PyMapping_Length(o: *mut PyObject) -> Py_ssize_t; +} + +#[inline] +#[cfg(not(PyPy))] +pub unsafe fn PyMapping_Length(o: *mut PyObject) -> Py_ssize_t { + PyMapping_Size(o) +} + +#[inline] +pub unsafe fn PyMapping_DelItemString(o: *mut PyObject, key: *mut c_char) -> c_int { + PyObject_DelItemString(o, key) +} + +#[inline] +pub unsafe fn PyMapping_DelItem(o: *mut PyObject, key: *mut PyObject) -> c_int { + PyObject_DelItem(o, key) +} + +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyMapping_HasKeyString")] + pub fn PyMapping_HasKeyString(o: *mut PyObject, key: *const c_char) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyMapping_HasKey")] + pub fn PyMapping_HasKey(o: *mut PyObject, key: *mut PyObject) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyMapping_Keys")] + pub fn PyMapping_Keys(o: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyMapping_Values")] + pub fn PyMapping_Values(o: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyMapping_Items")] + pub fn PyMapping_Items(o: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyMapping_GetItemString")] + pub fn PyMapping_GetItemString(o: *mut PyObject, key: *const c_char) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyMapping_SetItemString")] + pub fn PyMapping_SetItemString( + o: *mut PyObject, + key: *const c_char, + value: *mut PyObject, + ) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyObject_IsInstance")] + pub fn PyObject_IsInstance(object: *mut PyObject, typeorclass: *mut PyObject) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyObject_IsSubclass")] + pub fn PyObject_IsSubclass(object: *mut PyObject, typeorclass: *mut PyObject) -> c_int; +} diff --git a/include/pyo3/pyo3-ffi/src/bltinmodule.rs b/include/pyo3/pyo3-ffi/src/bltinmodule.rs new file mode 100644 index 00000000..cd5be043 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/bltinmodule.rs @@ -0,0 +1,8 @@ +use crate::object::PyTypeObject; + +#[cfg_attr(windows, link(name = "pythonXY"))] +extern "C" { + pub static mut PyFilter_Type: PyTypeObject; + pub static mut PyMap_Type: PyTypeObject; + pub static mut PyZip_Type: PyTypeObject; +} diff --git a/include/pyo3/pyo3-ffi/src/boolobject.rs b/include/pyo3/pyo3-ffi/src/boolobject.rs new file mode 100644 index 00000000..eec9da70 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/boolobject.rs @@ -0,0 +1,60 @@ +#[cfg(not(GraalPy))] +use crate::longobject::PyLongObject; +use crate::object::*; +use std::os::raw::{c_int, c_long}; +use std::ptr::addr_of_mut; + +#[inline] +pub unsafe fn PyBool_Check(op: *mut PyObject) -> c_int { + (Py_TYPE(op) == addr_of_mut!(PyBool_Type)) as c_int +} + +#[cfg_attr(windows, link(name = "pythonXY"))] +extern "C" { + #[cfg(not(GraalPy))] + #[cfg_attr(PyPy, link_name = "_PyPy_FalseStruct")] + static mut _Py_FalseStruct: PyLongObject; + #[cfg(not(GraalPy))] + #[cfg_attr(PyPy, link_name = "_PyPy_TrueStruct")] + static mut _Py_TrueStruct: PyLongObject; + + #[cfg(GraalPy)] + static mut _Py_FalseStructReference: *mut PyObject; + #[cfg(GraalPy)] + static mut _Py_TrueStructReference: *mut PyObject; +} + +#[inline] +pub unsafe fn Py_False() -> *mut PyObject { + #[cfg(not(GraalPy))] + return addr_of_mut!(_Py_FalseStruct) as *mut PyObject; + #[cfg(GraalPy)] + return _Py_FalseStructReference; +} + +#[inline] +pub unsafe fn Py_True() -> *mut PyObject { + #[cfg(not(GraalPy))] + return addr_of_mut!(_Py_TrueStruct) as *mut PyObject; + #[cfg(GraalPy)] + return _Py_TrueStructReference; +} + +#[inline] +pub unsafe fn Py_IsTrue(x: *mut PyObject) -> c_int { + Py_Is(x, Py_True()) +} + +#[inline] +pub unsafe fn Py_IsFalse(x: *mut PyObject) -> c_int { + Py_Is(x, Py_False()) +} + +// skipped Py_RETURN_TRUE +// skipped Py_RETURN_FALSE + +#[cfg_attr(windows, link(name = "pythonXY"))] +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyBool_FromLong")] + pub fn PyBool_FromLong(arg1: c_long) -> *mut PyObject; +} diff --git a/include/pyo3/pyo3-ffi/src/bytearrayobject.rs b/include/pyo3/pyo3-ffi/src/bytearrayobject.rs new file mode 100644 index 00000000..24a97bcc --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/bytearrayobject.rs @@ -0,0 +1,53 @@ +use crate::object::*; +use crate::pyport::Py_ssize_t; +use std::os::raw::{c_char, c_int}; +use std::ptr::addr_of_mut; + +#[cfg(not(any(PyPy, GraalPy, Py_LIMITED_API)))] +#[repr(C)] +pub struct PyByteArrayObject { + pub ob_base: PyVarObject, + pub ob_alloc: Py_ssize_t, + pub ob_bytes: *mut c_char, + pub ob_start: *mut c_char, + #[cfg(Py_3_9)] + pub ob_exports: Py_ssize_t, + #[cfg(not(Py_3_9))] + pub ob_exports: c_int, +} + +#[cfg(any(PyPy, GraalPy, Py_LIMITED_API))] +opaque_struct!(PyByteArrayObject); + +#[cfg_attr(windows, link(name = "pythonXY"))] +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyByteArray_Type")] + pub static mut PyByteArray_Type: PyTypeObject; + + pub static mut PyByteArrayIter_Type: PyTypeObject; +} + +#[inline] +pub unsafe fn PyByteArray_Check(op: *mut PyObject) -> c_int { + PyObject_TypeCheck(op, addr_of_mut!(PyByteArray_Type)) +} + +#[inline] +pub unsafe fn PyByteArray_CheckExact(op: *mut PyObject) -> c_int { + (Py_TYPE(op) == addr_of_mut!(PyByteArray_Type)) as c_int +} + +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyByteArray_FromObject")] + pub fn PyByteArray_FromObject(o: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyByteArray_Concat")] + pub fn PyByteArray_Concat(a: *mut PyObject, b: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyByteArray_FromStringAndSize")] + pub fn PyByteArray_FromStringAndSize(string: *const c_char, len: Py_ssize_t) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyByteArray_Size")] + pub fn PyByteArray_Size(bytearray: *mut PyObject) -> Py_ssize_t; + #[cfg_attr(PyPy, link_name = "PyPyByteArray_AsString")] + pub fn PyByteArray_AsString(bytearray: *mut PyObject) -> *mut c_char; + #[cfg_attr(PyPy, link_name = "PyPyByteArray_Resize")] + pub fn PyByteArray_Resize(bytearray: *mut PyObject, len: Py_ssize_t) -> c_int; +} diff --git a/include/pyo3/pyo3-ffi/src/bytesobject.rs b/include/pyo3/pyo3-ffi/src/bytesobject.rs new file mode 100644 index 00000000..0fd327b6 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/bytesobject.rs @@ -0,0 +1,63 @@ +use crate::object::*; +use crate::pyport::Py_ssize_t; +use std::os::raw::{c_char, c_int}; +use std::ptr::addr_of_mut; + +#[cfg_attr(windows, link(name = "pythonXY"))] +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyBytes_Type")] + pub static mut PyBytes_Type: PyTypeObject; + pub static mut PyBytesIter_Type: PyTypeObject; +} + +#[inline] +pub unsafe fn PyBytes_Check(op: *mut PyObject) -> c_int { + PyType_FastSubclass(Py_TYPE(op), Py_TPFLAGS_BYTES_SUBCLASS) +} + +#[inline] +pub unsafe fn PyBytes_CheckExact(op: *mut PyObject) -> c_int { + (Py_TYPE(op) == addr_of_mut!(PyBytes_Type)) as c_int +} + +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyBytes_FromStringAndSize")] + pub fn PyBytes_FromStringAndSize(arg1: *const c_char, arg2: Py_ssize_t) -> *mut PyObject; + pub fn PyBytes_FromString(arg1: *const c_char) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyBytes_FromObject")] + pub fn PyBytes_FromObject(arg1: *mut PyObject) -> *mut PyObject; + // skipped PyBytes_FromFormatV + //#[cfg_attr(PyPy, link_name = "PyPyBytes_FromFormatV")] + //pub fn PyBytes_FromFormatV(arg1: *const c_char, arg2: va_list) + // -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyBytes_FromFormat")] + pub fn PyBytes_FromFormat(arg1: *const c_char, ...) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyBytes_Size")] + pub fn PyBytes_Size(arg1: *mut PyObject) -> Py_ssize_t; + #[cfg_attr(PyPy, link_name = "PyPyBytes_AsString")] + pub fn PyBytes_AsString(arg1: *mut PyObject) -> *mut c_char; + pub fn PyBytes_Repr(arg1: *mut PyObject, arg2: c_int) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyBytes_Concat")] + pub fn PyBytes_Concat(arg1: *mut *mut PyObject, arg2: *mut PyObject); + #[cfg_attr(PyPy, link_name = "PyPyBytes_ConcatAndDel")] + pub fn PyBytes_ConcatAndDel(arg1: *mut *mut PyObject, arg2: *mut PyObject); + pub fn PyBytes_DecodeEscape( + arg1: *const c_char, + arg2: Py_ssize_t, + arg3: *const c_char, + arg4: Py_ssize_t, + arg5: *const c_char, + ) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyBytes_AsStringAndSize")] + pub fn PyBytes_AsStringAndSize( + obj: *mut PyObject, + s: *mut *mut c_char, + len: *mut Py_ssize_t, + ) -> c_int; +} + +// skipped F_LJUST +// skipped F_SIGN +// skipped F_BLANK +// skipped F_ALT +// skipped F_ZERO diff --git a/include/pyo3/pyo3-ffi/src/ceval.rs b/include/pyo3/pyo3-ffi/src/ceval.rs new file mode 100644 index 00000000..d1839a10 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/ceval.rs @@ -0,0 +1,139 @@ +use crate::object::PyObject; +use crate::pystate::PyThreadState; +use std::os::raw::{c_char, c_int, c_void}; + +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyEval_EvalCode")] + pub fn PyEval_EvalCode( + arg1: *mut PyObject, + arg2: *mut PyObject, + arg3: *mut PyObject, + ) -> *mut PyObject; + + pub fn PyEval_EvalCodeEx( + co: *mut PyObject, + globals: *mut PyObject, + locals: *mut PyObject, + args: *const *mut PyObject, + argc: c_int, + kwds: *const *mut PyObject, + kwdc: c_int, + defs: *const *mut PyObject, + defc: c_int, + kwdefs: *mut PyObject, + closure: *mut PyObject, + ) -> *mut PyObject; + + #[cfg(not(Py_3_13))] + #[cfg_attr(Py_3_9, deprecated(note = "Python 3.9"))] + #[cfg_attr(PyPy, link_name = "PyPyEval_CallObjectWithKeywords")] + pub fn PyEval_CallObjectWithKeywords( + func: *mut PyObject, + obj: *mut PyObject, + kwargs: *mut PyObject, + ) -> *mut PyObject; +} + +#[cfg(not(Py_3_13))] +#[cfg_attr(Py_3_9, deprecated(note = "Python 3.9"))] +#[inline] +pub unsafe fn PyEval_CallObject(func: *mut PyObject, arg: *mut PyObject) -> *mut PyObject { + #[allow(deprecated)] + PyEval_CallObjectWithKeywords(func, arg, std::ptr::null_mut()) +} + +extern "C" { + #[cfg(not(Py_3_13))] + #[cfg_attr(Py_3_9, deprecated(note = "Python 3.9"))] + #[cfg_attr(PyPy, link_name = "PyPyEval_CallFunction")] + pub fn PyEval_CallFunction(obj: *mut PyObject, format: *const c_char, ...) -> *mut PyObject; + #[cfg(not(Py_3_13))] + #[cfg_attr(Py_3_9, deprecated(note = "Python 3.9"))] + #[cfg_attr(PyPy, link_name = "PyPyEval_CallMethod")] + pub fn PyEval_CallMethod( + obj: *mut PyObject, + methodname: *const c_char, + format: *const c_char, + ... + ) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyEval_GetBuiltins")] + pub fn PyEval_GetBuiltins() -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyEval_GetGlobals")] + pub fn PyEval_GetGlobals() -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyEval_GetLocals")] + pub fn PyEval_GetLocals() -> *mut PyObject; + pub fn PyEval_GetFrame() -> *mut crate::PyFrameObject; + #[cfg_attr(PyPy, link_name = "PyPy_AddPendingCall")] + pub fn Py_AddPendingCall( + func: Option c_int>, + arg: *mut c_void, + ) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPy_MakePendingCalls")] + pub fn Py_MakePendingCalls() -> c_int; + #[cfg_attr(PyPy, link_name = "PyPy_SetRecursionLimit")] + pub fn Py_SetRecursionLimit(arg1: c_int); + #[cfg_attr(PyPy, link_name = "PyPy_GetRecursionLimit")] + pub fn Py_GetRecursionLimit() -> c_int; + fn _Py_CheckRecursiveCall(_where: *mut c_char) -> c_int; +} + +extern "C" { + #[cfg(Py_3_9)] + #[cfg_attr(PyPy, link_name = "PyPy_EnterRecursiveCall")] + pub fn Py_EnterRecursiveCall(arg1: *const c_char) -> c_int; + #[cfg(Py_3_9)] + #[cfg_attr(PyPy, link_name = "PyPy_LeaveRecursiveCall")] + pub fn Py_LeaveRecursiveCall(); +} + +extern "C" { + pub fn PyEval_GetFuncName(arg1: *mut PyObject) -> *const c_char; + pub fn PyEval_GetFuncDesc(arg1: *mut PyObject) -> *const c_char; + pub fn PyEval_GetCallStats(arg1: *mut PyObject) -> *mut PyObject; + pub fn PyEval_EvalFrame(arg1: *mut crate::PyFrameObject) -> *mut PyObject; + pub fn PyEval_EvalFrameEx(f: *mut crate::PyFrameObject, exc: c_int) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyEval_SaveThread")] + pub fn PyEval_SaveThread() -> *mut PyThreadState; + #[cfg_attr(PyPy, link_name = "PyPyEval_RestoreThread")] + pub fn PyEval_RestoreThread(arg1: *mut PyThreadState); +} + +extern "C" { + #[cfg(not(Py_3_13))] + #[cfg_attr(PyPy, link_name = "PyPyEval_ThreadsInitialized")] + #[cfg_attr( + Py_3_9, + deprecated( + note = "Deprecated in Python 3.9, this function always returns true in Python 3.7 or newer." + ) + )] + pub fn PyEval_ThreadsInitialized() -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyEval_InitThreads")] + #[cfg_attr( + Py_3_9, + deprecated( + note = "Deprecated in Python 3.9, this function does nothing in Python 3.7 or newer." + ) + )] + pub fn PyEval_InitThreads(); + pub fn PyEval_AcquireLock(); + pub fn PyEval_ReleaseLock(); + #[cfg_attr(PyPy, link_name = "PyPyEval_AcquireThread")] + pub fn PyEval_AcquireThread(tstate: *mut PyThreadState); + #[cfg_attr(PyPy, link_name = "PyPyEval_ReleaseThread")] + pub fn PyEval_ReleaseThread(tstate: *mut PyThreadState); + #[cfg(not(Py_3_8))] + pub fn PyEval_ReInitThreads(); +} + +// skipped Py_BEGIN_ALLOW_THREADS +// skipped Py_BLOCK_THREADS +// skipped Py_UNBLOCK_THREADS +// skipped Py_END_ALLOW_THREADS +// skipped FVC_MASK +// skipped FVC_NONE +// skipped FVC_STR +// skipped FVC_REPR +// skipped FVC_ASCII +// skipped FVS_MASK +// skipped FVS_HAVE_SPEC diff --git a/include/pyo3/pyo3-ffi/src/code.rs b/include/pyo3/pyo3-ffi/src/code.rs new file mode 100644 index 00000000..d28f68cd --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/code.rs @@ -0,0 +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); diff --git a/include/pyo3/pyo3-ffi/src/codecs.rs b/include/pyo3/pyo3-ffi/src/codecs.rs new file mode 100644 index 00000000..2fd214cb --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/codecs.rs @@ -0,0 +1,58 @@ +use crate::object::PyObject; +use std::os::raw::{c_char, c_int}; + +extern "C" { + pub fn PyCodec_Register(search_function: *mut PyObject) -> c_int; + #[cfg(Py_3_10)] + #[cfg(not(PyPy))] + pub fn PyCodec_Unregister(search_function: *mut PyObject) -> c_int; + // skipped non-limited _PyCodec_Lookup from Include/codecs.h + // skipped non-limited _PyCodec_Forget from Include/codecs.h + pub fn PyCodec_KnownEncoding(encoding: *const c_char) -> c_int; + pub fn PyCodec_Encode( + object: *mut PyObject, + encoding: *const c_char, + errors: *const c_char, + ) -> *mut PyObject; + pub fn PyCodec_Decode( + object: *mut PyObject, + encoding: *const c_char, + errors: *const c_char, + ) -> *mut PyObject; + // skipped non-limited _PyCodec_LookupTextEncoding from Include/codecs.h + // skipped non-limited _PyCodec_EncodeText from Include/codecs.h + // skipped non-limited _PyCodec_DecodeText from Include/codecs.h + // skipped non-limited _PyCodecInfo_GetIncrementalDecoder from Include/codecs.h + // skipped non-limited _PyCodecInfo_GetIncrementalEncoder from Include/codecs.h + pub fn PyCodec_Encoder(encoding: *const c_char) -> *mut PyObject; + pub fn PyCodec_Decoder(encoding: *const c_char) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyCodec_IncrementalEncoder")] + pub fn PyCodec_IncrementalEncoder( + encoding: *const c_char, + errors: *const c_char, + ) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyCodec_IncrementalDecoder")] + pub fn PyCodec_IncrementalDecoder( + encoding: *const c_char, + errors: *const c_char, + ) -> *mut PyObject; + pub fn PyCodec_StreamReader( + encoding: *const c_char, + stream: *mut PyObject, + errors: *const c_char, + ) -> *mut PyObject; + pub fn PyCodec_StreamWriter( + encoding: *const c_char, + stream: *mut PyObject, + errors: *const c_char, + ) -> *mut PyObject; + pub fn PyCodec_RegisterError(name: *const c_char, error: *mut PyObject) -> c_int; + pub fn PyCodec_LookupError(name: *const c_char) -> *mut PyObject; + pub fn PyCodec_StrictErrors(exc: *mut PyObject) -> *mut PyObject; + pub fn PyCodec_IgnoreErrors(exc: *mut PyObject) -> *mut PyObject; + pub fn PyCodec_ReplaceErrors(exc: *mut PyObject) -> *mut PyObject; + pub fn PyCodec_XMLCharRefReplaceErrors(exc: *mut PyObject) -> *mut PyObject; + pub fn PyCodec_BackslashReplaceErrors(exc: *mut PyObject) -> *mut PyObject; + // skipped non-limited PyCodec_NameReplaceErrors from Include/codecs.h + // skipped non-limited Py_hexdigits from Include/codecs.h +} diff --git a/include/pyo3/pyo3-ffi/src/compat/mod.rs b/include/pyo3/pyo3-ffi/src/compat/mod.rs new file mode 100644 index 00000000..11f29128 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/compat/mod.rs @@ -0,0 +1,59 @@ +//! C API Compatibility Shims +//! +//! Some CPython C API functions added in recent versions of Python are +//! inherently safer to use than older C API constructs. This module +//! exposes functions available on all Python versions that wrap the +//! old C API on old Python versions and wrap the function directly +//! on newer Python versions. + +// Unless otherwise noted, the compatibility shims are adapted from +// the pythoncapi-compat project: https://github.com/python/pythoncapi-compat + +/// Internal helper macro which defines compatibility shims for C API functions, deferring to a +/// re-export when that's available. +macro_rules! compat_function { + ( + originally_defined_for($cfg:meta); + + $(#[$attrs:meta])* + pub unsafe fn $name:ident($($arg_names:ident: $arg_types:ty),* $(,)?) -> $ret:ty $body:block + ) => { + // Define as a standalone function under docsrs cfg so that this shows as a unique function in the docs, + // not a re-export (the re-export has the wrong visibility) + #[cfg(any(docsrs, not($cfg)))] + #[cfg_attr(docsrs, doc(cfg(all())))] + $(#[$attrs])* + pub unsafe fn $name( + $($arg_names: $arg_types,)* + ) -> $ret $body + + #[cfg(all($cfg, not(docsrs)))] + pub use $crate::$name; + + #[cfg(test)] + paste::paste! { + // Test that the compat function does not overlap with the original function. If the + // cfgs line up, then the the two glob imports will resolve to the same item via the + // re-export. If the cfgs mismatch, then the use of $name will be ambiguous in cases + // where the function is defined twice, and the test will fail to compile. + #[allow(unused_imports)] + mod [] { + use $crate::*; + use $crate::compat::*; + + #[test] + fn test_export() { + let _ = $name; + } + } + } + }; +} + +mod py_3_10; +mod py_3_13; +mod py_3_9; + +pub use self::py_3_10::*; +pub use self::py_3_13::*; +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 new file mode 100644 index 00000000..c6e8c2cb --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/compat/py_3_10.rs @@ -0,0 +1,19 @@ +compat_function!( + originally_defined_for(Py_3_10); + + #[inline] + pub unsafe fn Py_NewRef(obj: *mut crate::PyObject) -> *mut crate::PyObject { + crate::Py_INCREF(obj); + obj + } +); + +compat_function!( + originally_defined_for(Py_3_10); + + #[inline] + pub unsafe fn Py_XNewRef(obj: *mut crate::PyObject) -> *mut crate::PyObject { + crate::Py_XINCREF(obj); + obj + } +); diff --git a/include/pyo3/pyo3-ffi/src/compat/py_3_13.rs b/include/pyo3/pyo3-ffi/src/compat/py_3_13.rs new file mode 100644 index 00000000..9f44ced6 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/compat/py_3_13.rs @@ -0,0 +1,85 @@ +compat_function!( + originally_defined_for(Py_3_13); + + #[inline] + pub unsafe fn PyDict_GetItemRef( + dp: *mut crate::PyObject, + key: *mut crate::PyObject, + result: *mut *mut crate::PyObject, + ) -> std::os::raw::c_int { + use crate::{compat::Py_NewRef, PyDict_GetItemWithError, PyErr_Occurred}; + + let item = PyDict_GetItemWithError(dp, key); + if !item.is_null() { + *result = Py_NewRef(item); + return 1; // found + } + *result = std::ptr::null_mut(); + if PyErr_Occurred().is_null() { + return 0; // not found + } + -1 + } +); + +compat_function!( + originally_defined_for(Py_3_13); + + #[inline] + pub unsafe fn PyList_GetItemRef( + arg1: *mut crate::PyObject, + arg2: crate::Py_ssize_t, + ) -> *mut crate::PyObject { + use crate::{PyList_GetItem, Py_XINCREF}; + + let item = PyList_GetItem(arg1, arg2); + Py_XINCREF(item); + item + } +); + +compat_function!( + originally_defined_for(Py_3_13); + + #[inline] + pub unsafe fn PyImport_AddModuleRef( + name: *const std::os::raw::c_char, + ) -> *mut crate::PyObject { + use crate::{compat::Py_XNewRef, PyImport_AddModule}; + + Py_XNewRef(PyImport_AddModule(name)) + } +); + +compat_function!( + originally_defined_for(Py_3_13); + + #[inline] + pub unsafe fn PyWeakref_GetRef( + reference: *mut crate::PyObject, + pobj: *mut *mut crate::PyObject, + ) -> std::os::raw::c_int { + use crate::{ + compat::Py_NewRef, PyErr_SetString, PyExc_TypeError, PyWeakref_Check, + PyWeakref_GetObject, Py_None, + }; + + if !reference.is_null() && PyWeakref_Check(reference) == 0 { + *pobj = std::ptr::null_mut(); + PyErr_SetString(PyExc_TypeError, c_str!("expected a weakref").as_ptr()); + return -1; + } + let obj = PyWeakref_GetObject(reference); + if obj.is_null() { + // SystemError if reference is NULL + *pobj = std::ptr::null_mut(); + return -1; + } + if obj == Py_None() { + *pobj = std::ptr::null_mut(); + return 0; + } + *pobj = Py_NewRef(obj); + 1 + } +); diff --git a/include/pyo3/pyo3-ffi/src/compat/py_3_9.rs b/include/pyo3/pyo3-ffi/src/compat/py_3_9.rs new file mode 100644 index 00000000..285f2b2a --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/compat/py_3_9.rs @@ -0,0 +1,21 @@ +compat_function!( + originally_defined_for(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 + )); + + #[inline] + pub unsafe fn PyObject_CallNoArgs(obj: *mut crate::PyObject) -> *mut crate::PyObject { + crate::PyObject_CallObject(obj, std::ptr::null_mut()) + } +); + +compat_function!( + originally_defined_for(all(Py_3_9, not(any(Py_LIMITED_API, PyPy, GraalPy)))); + + #[inline] + pub unsafe fn PyObject_CallMethodNoArgs(obj: *mut crate::PyObject, name: *mut crate::PyObject) -> *mut crate::PyObject { + crate::PyObject_CallMethodObjArgs(obj, name, std::ptr::null_mut::()) + } +); diff --git a/include/pyo3/pyo3-ffi/src/compile.rs b/include/pyo3/pyo3-ffi/src/compile.rs new file mode 100644 index 00000000..189a1a8b --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/compile.rs @@ -0,0 +1,10 @@ +use std::os::raw::c_int; + +pub const Py_single_input: c_int = 256; +pub const Py_file_input: c_int = 257; +pub const Py_eval_input: c_int = 258; +#[cfg(Py_3_8)] +pub const Py_func_type_input: c_int = 345; + +#[cfg(Py_3_9)] +pub const Py_fstring_input: c_int = 800; diff --git a/include/pyo3/pyo3-ffi/src/complexobject.rs b/include/pyo3/pyo3-ffi/src/complexobject.rs new file mode 100644 index 00000000..283bacf6 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/complexobject.rs @@ -0,0 +1,30 @@ +use crate::object::*; +use std::os::raw::{c_double, c_int}; +use std::ptr::addr_of_mut; + +#[cfg_attr(windows, link(name = "pythonXY"))] +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyComplex_Type")] + pub static mut PyComplex_Type: PyTypeObject; +} + +#[inline] +pub unsafe fn PyComplex_Check(op: *mut PyObject) -> c_int { + PyObject_TypeCheck(op, addr_of_mut!(PyComplex_Type)) +} + +#[inline] +pub unsafe fn PyComplex_CheckExact(op: *mut PyObject) -> c_int { + Py_IS_TYPE(op, addr_of_mut!(PyComplex_Type)) +} + +extern "C" { + // skipped non-limited PyComplex_FromCComplex + #[cfg_attr(PyPy, link_name = "PyPyComplex_FromDoubles")] + pub fn PyComplex_FromDoubles(real: c_double, imag: c_double) -> *mut PyObject; + + #[cfg_attr(PyPy, link_name = "PyPyComplex_RealAsDouble")] + pub fn PyComplex_RealAsDouble(op: *mut PyObject) -> c_double; + #[cfg_attr(PyPy, link_name = "PyPyComplex_ImagAsDouble")] + pub fn PyComplex_ImagAsDouble(op: *mut PyObject) -> c_double; +} diff --git a/include/pyo3/pyo3-ffi/src/context.rs b/include/pyo3/pyo3-ffi/src/context.rs new file mode 100644 index 00000000..210d6e21 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/context.rs @@ -0,0 +1,46 @@ +use crate::object::{PyObject, PyTypeObject, Py_TYPE}; +use std::os::raw::{c_char, c_int}; +use std::ptr::addr_of_mut; + +extern "C" { + pub static mut PyContext_Type: PyTypeObject; + // skipped non-limited opaque PyContext + pub static mut PyContextVar_Type: PyTypeObject; + // skipped non-limited opaque PyContextVar + pub static mut PyContextToken_Type: PyTypeObject; + // skipped non-limited opaque PyContextToken +} + +#[inline] +pub unsafe fn PyContext_CheckExact(op: *mut PyObject) -> c_int { + (Py_TYPE(op) == addr_of_mut!(PyContext_Type)) as c_int +} + +#[inline] +pub unsafe fn PyContextVar_CheckExact(op: *mut PyObject) -> c_int { + (Py_TYPE(op) == addr_of_mut!(PyContextVar_Type)) as c_int +} + +#[inline] +pub unsafe fn PyContextToken_CheckExact(op: *mut PyObject) -> c_int { + (Py_TYPE(op) == addr_of_mut!(PyContextToken_Type)) as c_int +} + +extern "C" { + pub fn PyContext_New() -> *mut PyObject; + pub fn PyContext_Copy(ctx: *mut PyObject) -> *mut PyObject; + pub fn PyContext_CopyCurrent() -> *mut PyObject; + + pub fn PyContext_Enter(ctx: *mut PyObject) -> c_int; + pub fn PyContext_Exit(ctx: *mut PyObject) -> c_int; + + pub fn PyContextVar_New(name: *const c_char, def: *mut PyObject) -> *mut PyObject; + pub fn PyContextVar_Get( + var: *mut PyObject, + default_value: *mut PyObject, + value: *mut *mut PyObject, + ) -> c_int; + pub fn PyContextVar_Set(var: *mut PyObject, value: *mut PyObject) -> *mut PyObject; + pub fn PyContextVar_Reset(var: *mut PyObject, token: *mut PyObject) -> c_int; + // skipped non-limited _PyContext_NewHamtForTests +} diff --git a/include/pyo3/pyo3-ffi/src/cpython/abstract_.rs b/include/pyo3/pyo3-ffi/src/cpython/abstract_.rs new file mode 100644 index 00000000..83295e58 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/cpython/abstract_.rs @@ -0,0 +1,332 @@ +use crate::{PyObject, Py_ssize_t}; +use std::os::raw::{c_char, c_int}; + +#[cfg(not(Py_3_11))] +use crate::Py_buffer; + +#[cfg(all(Py_3_8, not(any(PyPy, GraalPy))))] +use crate::{ + vectorcallfunc, PyCallable_Check, PyThreadState, PyThreadState_GET, PyTuple_Check, + PyType_HasFeature, Py_TPFLAGS_HAVE_VECTORCALL, +}; +#[cfg(Py_3_8)] +use libc::size_t; + +extern "C" { + #[cfg(all(Py_3_8, not(any(PyPy, GraalPy))))] + pub fn _PyStack_AsDict(values: *const *mut PyObject, kwnames: *mut PyObject) -> *mut PyObject; +} + +#[cfg(all(Py_3_8, not(any(PyPy, GraalPy))))] +const _PY_FASTCALL_SMALL_STACK: size_t = 5; + +extern "C" { + #[cfg(all(Py_3_8, not(any(PyPy, GraalPy))))] + pub fn _Py_CheckFunctionResult( + tstate: *mut PyThreadState, + callable: *mut PyObject, + result: *mut PyObject, + where_: *const c_char, + ) -> *mut PyObject; + + #[cfg(all(Py_3_8, not(any(PyPy, GraalPy))))] + pub fn _PyObject_MakeTpCall( + tstate: *mut PyThreadState, + callable: *mut PyObject, + args: *const *mut PyObject, + nargs: Py_ssize_t, + keywords: *mut PyObject, + ) -> *mut PyObject; +} + +#[cfg(Py_3_8)] +pub const PY_VECTORCALL_ARGUMENTS_OFFSET: size_t = + 1 << (8 * std::mem::size_of::() as size_t - 1); + +#[cfg(Py_3_8)] +#[inline(always)] +pub unsafe fn PyVectorcall_NARGS(n: size_t) -> Py_ssize_t { + let n = n & !PY_VECTORCALL_ARGUMENTS_OFFSET; + n.try_into().expect("cannot fail due to mask") +} + +#[cfg(all(Py_3_8, not(any(PyPy, GraalPy))))] +#[inline(always)] +pub unsafe fn PyVectorcall_Function(callable: *mut PyObject) -> Option { + assert!(!callable.is_null()); + let tp = crate::Py_TYPE(callable); + if PyType_HasFeature(tp, Py_TPFLAGS_HAVE_VECTORCALL) == 0 { + return None; + } + assert!(PyCallable_Check(callable) > 0); + let offset = (*tp).tp_vectorcall_offset; + assert!(offset > 0); + let ptr = callable.cast::().offset(offset).cast(); + *ptr +} + +#[cfg(all(Py_3_8, not(any(PyPy, GraalPy))))] +#[inline(always)] +pub unsafe fn _PyObject_VectorcallTstate( + tstate: *mut PyThreadState, + callable: *mut PyObject, + args: *const *mut PyObject, + nargsf: size_t, + kwnames: *mut PyObject, +) -> *mut PyObject { + assert!(kwnames.is_null() || PyTuple_Check(kwnames) > 0); + assert!(!args.is_null() || PyVectorcall_NARGS(nargsf) == 0); + + match PyVectorcall_Function(callable) { + None => { + let nargs = PyVectorcall_NARGS(nargsf); + _PyObject_MakeTpCall(tstate, callable, args, nargs, kwnames) + } + Some(func) => { + let res = func(callable, args, nargsf, kwnames); + _Py_CheckFunctionResult(tstate, callable, res, std::ptr::null_mut()) + } + } +} + +#[cfg(all(Py_3_8, not(any(PyPy, GraalPy))))] +#[inline(always)] +pub unsafe fn PyObject_Vectorcall( + callable: *mut PyObject, + args: *const *mut PyObject, + nargsf: size_t, + kwnames: *mut PyObject, +) -> *mut PyObject { + _PyObject_VectorcallTstate(PyThreadState_GET(), callable, args, nargsf, kwnames) +} + +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)), + link_name = "_PyObject_VectorcallDict" + )] + #[cfg_attr(all(PyPy, not(Py_3_9)), link_name = "_PyPyObject_VectorcallDict")] + #[cfg_attr(all(PyPy, Py_3_9), link_name = "PyPyObject_VectorcallDict")] + pub fn PyObject_VectorcallDict( + callable: *mut PyObject, + args: *const *mut PyObject, + nargsf: size_t, + kwdict: *mut PyObject, + ) -> *mut PyObject; + + #[cfg(Py_3_8)] + #[cfg_attr(not(any(Py_3_9, PyPy)), link_name = "_PyVectorcall_Call")] + #[cfg_attr(PyPy, link_name = "PyPyVectorcall_Call")] + pub fn PyVectorcall_Call( + callable: *mut PyObject, + tuple: *mut PyObject, + dict: *mut PyObject, + ) -> *mut PyObject; +} + +#[cfg(all(Py_3_8, not(any(PyPy, GraalPy))))] +#[inline(always)] +pub unsafe fn _PyObject_FastCallTstate( + tstate: *mut PyThreadState, + func: *mut PyObject, + args: *const *mut PyObject, + nargs: Py_ssize_t, +) -> *mut PyObject { + _PyObject_VectorcallTstate(tstate, func, args, nargs as size_t, std::ptr::null_mut()) +} + +#[cfg(all(Py_3_8, not(any(PyPy, GraalPy))))] +#[inline(always)] +pub unsafe fn _PyObject_FastCall( + func: *mut PyObject, + args: *const *mut PyObject, + nargs: Py_ssize_t, +) -> *mut PyObject { + _PyObject_FastCallTstate(PyThreadState_GET(), func, args, nargs) +} + +#[cfg(all(Py_3_8, not(any(PyPy, GraalPy))))] +#[inline(always)] +pub unsafe fn _PyObject_CallNoArg(func: *mut PyObject) -> *mut PyObject { + _PyObject_VectorcallTstate( + PyThreadState_GET(), + func, + std::ptr::null_mut(), + 0, + std::ptr::null_mut(), + ) +} + +extern "C" { + #[cfg(PyPy)] + #[link_name = "_PyPyObject_CallNoArg"] + pub fn _PyObject_CallNoArg(func: *mut PyObject) -> *mut PyObject; +} + +#[cfg(all(Py_3_8, not(any(PyPy, GraalPy))))] +#[inline(always)] +pub unsafe fn PyObject_CallOneArg(func: *mut PyObject, arg: *mut PyObject) -> *mut PyObject { + assert!(!arg.is_null()); + let args_array = [std::ptr::null_mut(), arg]; + let args = args_array.as_ptr().offset(1); // For PY_VECTORCALL_ARGUMENTS_OFFSET + let tstate = PyThreadState_GET(); + let nargsf = 1 | PY_VECTORCALL_ARGUMENTS_OFFSET; + _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( + name, + &self_, + 1 | PY_VECTORCALL_ARGUMENTS_OFFSET, + std::ptr::null_mut(), + ) +} + +#[cfg(all(Py_3_9, not(any(PyPy, GraalPy))))] +#[inline(always)] +pub unsafe fn PyObject_CallMethodOneArg( + self_: *mut PyObject, + name: *mut PyObject, + arg: *mut PyObject, +) -> *mut PyObject { + let args = [self_, arg]; + assert!(!arg.is_null()); + PyObject_VectorcallMethod( + name, + args.as_ptr(), + 2 | PY_VECTORCALL_ARGUMENTS_OFFSET, + std::ptr::null_mut(), + ) +} + +// skipped _PyObject_VectorcallMethodId +// skipped _PyObject_CallMethodIdNoArgs +// skipped _PyObject_CallMethodIdOneArg + +// skipped _PyObject_HasLen + +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyObject_LengthHint")] + pub fn PyObject_LengthHint(o: *mut PyObject, arg1: Py_ssize_t) -> Py_ssize_t; + + #[cfg(not(Py_3_11))] // moved to src/buffer.rs from 3.11 + #[cfg(all(Py_3_9, not(any(PyPy, GraalPy))))] + pub fn PyObject_CheckBuffer(obj: *mut PyObject) -> c_int; +} + +#[cfg(not(any(Py_3_9, PyPy)))] +#[inline] +pub unsafe fn PyObject_CheckBuffer(o: *mut PyObject) -> c_int { + let tp_as_buffer = (*crate::Py_TYPE(o)).tp_as_buffer; + (!tp_as_buffer.is_null() && (*tp_as_buffer).bf_getbuffer.is_some()) as c_int +} + +#[cfg(not(Py_3_11))] // moved to src/buffer.rs from 3.11 +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyObject_GetBuffer")] + pub fn PyObject_GetBuffer(obj: *mut PyObject, view: *mut Py_buffer, flags: c_int) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyBuffer_GetPointer")] + pub fn PyBuffer_GetPointer( + view: *mut Py_buffer, + indices: *mut Py_ssize_t, + ) -> *mut std::os::raw::c_void; + #[cfg_attr(PyPy, link_name = "PyPyBuffer_SizeFromFormat")] + pub fn PyBuffer_SizeFromFormat(format: *const c_char) -> Py_ssize_t; + #[cfg_attr(PyPy, link_name = "PyPyBuffer_ToContiguous")] + pub fn PyBuffer_ToContiguous( + buf: *mut std::os::raw::c_void, + view: *mut Py_buffer, + len: Py_ssize_t, + order: c_char, + ) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyBuffer_FromContiguous")] + pub fn PyBuffer_FromContiguous( + view: *mut Py_buffer, + buf: *mut std::os::raw::c_void, + len: Py_ssize_t, + order: c_char, + ) -> c_int; + pub fn PyObject_CopyData(dest: *mut PyObject, src: *mut PyObject) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyBuffer_IsContiguous")] + pub fn PyBuffer_IsContiguous(view: *const Py_buffer, fort: c_char) -> c_int; + pub fn PyBuffer_FillContiguousStrides( + ndims: c_int, + shape: *mut Py_ssize_t, + strides: *mut Py_ssize_t, + itemsize: c_int, + fort: c_char, + ); + #[cfg_attr(PyPy, link_name = "PyPyBuffer_FillInfo")] + pub fn PyBuffer_FillInfo( + view: *mut Py_buffer, + o: *mut PyObject, + buf: *mut std::os::raw::c_void, + len: Py_ssize_t, + readonly: c_int, + flags: c_int, + ) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyBuffer_Release")] + pub fn PyBuffer_Release(view: *mut Py_buffer); +} + +// PyIter_Check defined in ffi/abstract_.rs +// PyIndex_Check defined in ffi/abstract_.rs +// Not defined here because this file is not compiled under the +// limited API, but the macros need to be defined for 3.6, 3.7 which +// predate the limited API changes. + +// skipped PySequence_ITEM + +pub const PY_ITERSEARCH_COUNT: c_int = 1; +pub const PY_ITERSEARCH_INDEX: c_int = 2; +pub const PY_ITERSEARCH_CONTAINS: c_int = 3; + +extern "C" { + #[cfg(not(any(PyPy, GraalPy)))] + pub fn _PySequence_IterSearch( + seq: *mut PyObject, + obj: *mut PyObject, + operation: c_int, + ) -> Py_ssize_t; +} + +// skipped _PyObject_RealIsInstance +// skipped _PyObject_RealIsSubclass + +// skipped _PySequence_BytesToCharpArray + +// skipped _Py_FreeCharPArray + +// skipped _Py_add_one_to_index_F +// skipped _Py_add_one_to_index_C + +// skipped _Py_convert_optional_to_ssize_t + +// skipped _PyNumber_Index(*mut PyObject o) diff --git a/include/pyo3/pyo3-ffi/src/cpython/bytesobject.rs b/include/pyo3/pyo3-ffi/src/cpython/bytesobject.rs new file mode 100644 index 00000000..306702de --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/cpython/bytesobject.rs @@ -0,0 +1,25 @@ +use crate::object::*; +use crate::Py_ssize_t; +#[cfg(not(any(PyPy, GraalPy, Py_LIMITED_API)))] +use std::os::raw::c_char; +use std::os::raw::c_int; + +#[cfg(not(any(PyPy, GraalPy, Py_LIMITED_API)))] +#[repr(C)] +pub struct PyBytesObject { + pub ob_base: PyVarObject, + #[cfg_attr( + Py_3_11, + deprecated(note = "Deprecated in Python 3.11 and will be removed in a future version.") + )] + pub ob_shash: crate::Py_hash_t, + pub ob_sval: [c_char; 1], +} + +#[cfg(any(PyPy, GraalPy, Py_LIMITED_API))] +opaque_struct!(PyBytesObject); + +extern "C" { + #[cfg_attr(PyPy, link_name = "_PyPyBytes_Resize")] + pub fn _PyBytes_Resize(bytes: *mut *mut PyObject, newsize: Py_ssize_t) -> c_int; +} diff --git a/include/pyo3/pyo3-ffi/src/cpython/ceval.rs b/include/pyo3/pyo3-ffi/src/cpython/ceval.rs new file mode 100644 index 00000000..6df10627 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/cpython/ceval.rs @@ -0,0 +1,21 @@ +use crate::cpython::pystate::Py_tracefunc; +use crate::object::{freefunc, PyObject}; +use std::os::raw::c_int; + +extern "C" { + // skipped non-limited _PyEval_CallTracing + + #[cfg(not(Py_3_11))] + pub fn _PyEval_EvalFrameDefault(arg1: *mut crate::PyFrameObject, exc: c_int) -> *mut PyObject; + + #[cfg(Py_3_11)] + pub fn _PyEval_EvalFrameDefault( + tstate: *mut crate::PyThreadState, + frame: *mut crate::_PyInterpreterFrame, + exc: c_int, + ) -> *mut crate::PyObject; + + pub fn _PyEval_RequestCodeExtraIndex(func: freefunc) -> c_int; + pub fn PyEval_SetProfile(trace_func: Option, arg1: *mut PyObject); + pub fn PyEval_SetTrace(trace_func: Option, arg1: *mut PyObject); +} diff --git a/include/pyo3/pyo3-ffi/src/cpython/code.rs b/include/pyo3/pyo3-ffi/src/cpython/code.rs new file mode 100644 index 00000000..230096ca --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/cpython/code.rs @@ -0,0 +1,336 @@ +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(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); + +#[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; + +#[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 _Py_OPCODE +// skipped _Py_OPARG + +// skipped _py_make_codeunit + +// skipped _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, +} + +#[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, +} + +#[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, + + 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, +} + +/* Masks for co_flags */ +pub const CO_OPTIMIZED: c_int = 0x0001; +pub const CO_NEWLOCALS: c_int = 0x0002; +pub const CO_VARARGS: c_int = 0x0004; +pub const CO_VARKEYWORDS: c_int = 0x0008; +pub const CO_NESTED: c_int = 0x0010; +pub const CO_GENERATOR: c_int = 0x0020; +/* The CO_NOFREE flag is set if there are no free or cell variables. + This information is redundant, but it allows a single flag test + to determine whether there is any extra work to be done when the + call frame it setup. +*/ +pub const CO_NOFREE: c_int = 0x0040; +/* The CO_COROUTINE flag is set for coroutine functions (defined with +``async def`` keywords) */ +pub const CO_COROUTINE: c_int = 0x0080; +pub const CO_ITERABLE_COROUTINE: c_int = 0x0100; +pub const CO_ASYNC_GENERATOR: c_int = 0x0200; + +pub const CO_FUTURE_DIVISION: c_int = 0x2000; +pub const CO_FUTURE_ABSOLUTE_IMPORT: c_int = 0x4000; /* do absolute imports by default */ +pub const CO_FUTURE_WITH_STATEMENT: c_int = 0x8000; +pub const CO_FUTURE_PRINT_FUNCTION: c_int = 0x1_0000; +pub const CO_FUTURE_UNICODE_LITERALS: c_int = 0x2_0000; + +pub const CO_FUTURE_BARRY_AS_BDFL: c_int = 0x4_0000; +pub const CO_FUTURE_GENERATOR_STOP: c_int = 0x8_0000; +// skipped CO_FUTURE_ANNOTATIONS +// skipped CO_CELL_NOT_AN_ARG + +pub const CO_MAXBLOCKS: usize = 20; + +#[cfg(not(any(PyPy, GraalPy)))] +#[cfg_attr(windows, link(name = "pythonXY"))] +extern "C" { + pub static mut PyCode_Type: PyTypeObject; +} + +#[inline] +#[cfg(not(any(PyPy, GraalPy)))] +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; +} + +extern "C" { + #[cfg(not(GraalPy))] + #[cfg_attr(PyPy, link_name = "PyPyCode_New")] + pub fn PyCode_New( + argcount: c_int, + kwonlyargcount: c_int, + nlocals: c_int, + stacksize: c_int, + flags: c_int, + code: *mut PyObject, + consts: *mut PyObject, + names: *mut PyObject, + varnames: *mut PyObject, + freevars: *mut PyObject, + cellvars: *mut PyObject, + filename: *mut PyObject, + name: *mut PyObject, + firstlineno: c_int, + lnotab: *mut PyObject, + ) -> *mut PyCodeObject; + #[cfg(not(GraalPy))] + #[cfg(Py_3_8)] + pub fn PyCode_NewWithPosOnlyArgs( + argcount: c_int, + posonlyargcount: c_int, + kwonlyargcount: c_int, + nlocals: c_int, + stacksize: c_int, + flags: c_int, + code: *mut PyObject, + consts: *mut PyObject, + names: *mut PyObject, + varnames: *mut PyObject, + freevars: *mut PyObject, + cellvars: *mut PyObject, + filename: *mut PyObject, + name: *mut PyObject, + firstlineno: c_int, + lnotab: *mut PyObject, + ) -> *mut PyCodeObject; + #[cfg(not(GraalPy))] + #[cfg_attr(PyPy, link_name = "PyPyCode_NewEmpty")] + pub fn PyCode_NewEmpty( + filename: *const c_char, + funcname: *const c_char, + firstlineno: c_int, + ) -> *mut PyCodeObject; + #[cfg(not(GraalPy))] + pub fn PyCode_Addr2Line(arg1: *mut PyCodeObject, arg2: c_int) -> c_int; + // skipped PyCodeAddressRange "for internal use only" + // skipped _PyCode_CheckLineNumber + // skipped _PyCode_ConstantKey + pub fn PyCode_Optimize( + code: *mut PyObject, + consts: *mut PyObject, + names: *mut PyObject, + lnotab: *mut PyObject, + ) -> *mut PyObject; + pub fn _PyCode_GetExtra( + code: *mut PyObject, + index: Py_ssize_t, + extra: *const *mut c_void, + ) -> c_int; + pub fn _PyCode_SetExtra(code: *mut PyObject, index: Py_ssize_t, extra: *mut c_void) -> c_int; +} diff --git a/include/pyo3/pyo3-ffi/src/cpython/compile.rs b/include/pyo3/pyo3-ffi/src/cpython/compile.rs new file mode 100644 index 00000000..79f06c92 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/cpython/compile.rs @@ -0,0 +1,108 @@ +#[cfg(not(any(PyPy, Py_3_10)))] +use crate::object::PyObject; +#[cfg(not(any(PyPy, Py_3_10)))] +use crate::pyarena::*; +#[cfg(not(any(PyPy, Py_3_10)))] +use crate::pythonrun::*; +#[cfg(not(any(PyPy, Py_3_10)))] +use crate::PyCodeObject; +#[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 + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct PyCompilerFlags { + pub cf_flags: c_int, + #[cfg(Py_3_8)] + pub cf_feature_version: c_int, +} + +// skipped non-limited _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)))] +#[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, +} + +pub const FUTURE_NESTED_SCOPES: &str = "nested_scopes"; +pub const FUTURE_GENERATORS: &str = "generators"; +pub const FUTURE_DIVISION: &str = "division"; +pub const FUTURE_ABSOLUTE_IMPORT: &str = "absolute_import"; +pub const FUTURE_WITH_STATEMENT: &str = "with_statement"; +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 + +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, + flags: *mut PyCompilerFlags, + optimize: c_int, + arena: *mut PyArena, + ) -> *mut PyCodeObject; + + #[cfg(not(any(PyPy, Py_3_10)))] + pub fn PyAST_CompileObject( + _mod: *mut _mod, + filename: *mut PyObject, + flags: *mut PyCompilerFlags, + optimize: c_int, + 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 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/complexobject.rs b/include/pyo3/pyo3-ffi/src/cpython/complexobject.rs new file mode 100644 index 00000000..255f9c27 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/cpython/complexobject.rs @@ -0,0 +1,31 @@ +use crate::PyObject; +use std::os::raw::c_double; + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct Py_complex { + pub real: c_double, + pub imag: c_double, +} + +// skipped private function _Py_c_sum +// skipped private function _Py_c_diff +// skipped private function _Py_c_neg +// skipped private function _Py_c_prod +// skipped private function _Py_c_quot +// skipped private function _Py_c_pow +// skipped private function _Py_c_abs + +#[repr(C)] +pub struct PyComplexObject { + pub ob_base: PyObject, + #[cfg(not(GraalPy))] + pub cval: Py_complex, +} + +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyComplex_FromCComplex")] + pub fn PyComplex_FromCComplex(v: Py_complex) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyComplex_AsCComplex")] + pub fn PyComplex_AsCComplex(op: *mut PyObject) -> Py_complex; +} diff --git a/include/pyo3/pyo3-ffi/src/cpython/critical_section.rs b/include/pyo3/pyo3-ffi/src/cpython/critical_section.rs new file mode 100644 index 00000000..97b2f5e0 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/cpython/critical_section.rs @@ -0,0 +1,30 @@ +#[cfg(Py_GIL_DISABLED)] +use crate::PyMutex; +use crate::PyObject; + +#[repr(C)] +#[cfg(Py_GIL_DISABLED)] +pub struct PyCriticalSection { + _cs_prev: usize, + _cs_mutex: *mut PyMutex, +} + +#[repr(C)] +#[cfg(Py_GIL_DISABLED)] +pub struct PyCriticalSection2 { + _cs_base: PyCriticalSection, + _cs_mutex2: *mut PyMutex, +} + +#[cfg(not(Py_GIL_DISABLED))] +opaque_struct!(PyCriticalSection); + +#[cfg(not(Py_GIL_DISABLED))] +opaque_struct!(PyCriticalSection2); + +extern "C" { + pub fn PyCriticalSection_Begin(c: *mut PyCriticalSection, op: *mut PyObject); + pub fn PyCriticalSection_End(c: *mut PyCriticalSection); + pub fn PyCriticalSection2_Begin(c: *mut PyCriticalSection2, a: *mut PyObject, b: *mut PyObject); + pub fn PyCriticalSection2_End(c: *mut PyCriticalSection2); +} diff --git a/include/pyo3/pyo3-ffi/src/cpython/descrobject.rs b/include/pyo3/pyo3-ffi/src/cpython/descrobject.rs new file mode 100644 index 00000000..1b5ee466 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/cpython/descrobject.rs @@ -0,0 +1,78 @@ +use crate::{PyGetSetDef, PyMethodDef, PyObject, PyTypeObject}; +use std::os::raw::{c_char, c_int, c_void}; + +pub type wrapperfunc = Option< + unsafe extern "C" fn( + slf: *mut PyObject, + args: *mut PyObject, + wrapped: *mut c_void, + ) -> *mut PyObject, +>; + +pub type wrapperfunc_kwds = Option< + unsafe extern "C" fn( + slf: *mut PyObject, + args: *mut PyObject, + wrapped: *mut c_void, + kwds: *mut PyObject, + ) -> *mut PyObject, +>; + +#[repr(C)] +pub struct wrapperbase { + pub name: *const c_char, + pub offset: c_int, + pub function: *mut c_void, + pub wrapper: wrapperfunc, + pub doc: *const c_char, + pub flags: c_int, + pub name_strobj: *mut PyObject, +} + +pub const PyWrapperFlag_KEYWORDS: c_int = 1; + +#[repr(C)] +pub struct PyDescrObject { + pub ob_base: PyObject, + pub d_type: *mut PyTypeObject, + pub d_name: *mut PyObject, + pub d_qualname: *mut PyObject, +} + +// skipped non-limited PyDescr_TYPE +// skipped non-limited PyDescr_NAME + +#[repr(C)] +pub struct PyMethodDescrObject { + pub d_common: PyDescrObject, + pub d_method: *mut PyMethodDef, + #[cfg(all(not(PyPy), Py_3_8))] + pub vectorcall: Option, +} + +#[repr(C)] +pub struct PyMemberDescrObject { + pub d_common: PyDescrObject, + pub d_member: *mut PyGetSetDef, +} + +#[repr(C)] +pub struct PyGetSetDescrObject { + pub d_common: PyDescrObject, + pub d_getset: *mut PyGetSetDef, +} + +#[repr(C)] +pub struct PyWrapperDescrObject { + pub d_common: PyDescrObject, + pub d_base: *mut wrapperbase, + pub d_wrapped: *mut c_void, +} + +#[cfg_attr(windows, link(name = "pythonXY"))] +extern "C" { + pub static mut _PyMethodWrapper_Type: PyTypeObject; +} + +// 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 new file mode 100644 index 00000000..79dcbfdb --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/cpython/dictobject.rs @@ -0,0 +1,80 @@ +use crate::object::*; +use crate::pyport::Py_ssize_t; +use std::os::raw::c_int; + +opaque_struct!(PyDictKeysObject); + +#[cfg(Py_3_11)] +opaque_struct!(PyDictValues); + +#[cfg(not(GraalPy))] +#[repr(C)] +#[derive(Debug)] +pub struct PyDictObject { + pub ob_base: PyObject, + pub ma_used: Py_ssize_t, + #[cfg_attr( + Py_3_12, + deprecated(note = "Deprecated in Python 3.12 and will be removed in the future.") + )] + pub ma_version_tag: u64, + pub ma_keys: *mut PyDictKeysObject, + #[cfg(not(Py_3_11))] + pub ma_values: *mut *mut PyObject, + #[cfg(Py_3_11)] + pub ma_values: *mut PyDictValues, +} + +extern "C" { + // skipped _PyDict_GetItem_KnownHash + // skipped _PyDict_GetItemIdWithError + // skipped _PyDict_GetItemStringWithError + // skipped PyDict_SetDefault + pub fn _PyDict_SetItem_KnownHash( + mp: *mut PyObject, + key: *mut PyObject, + item: *mut PyObject, + hash: crate::Py_hash_t, + ) -> c_int; + // skipped _PyDict_DelItem_KnownHash + // skipped _PyDict_DelItemIf + // skipped _PyDict_NewKeysForClass + pub fn _PyDict_Next( + mp: *mut PyObject, + pos: *mut Py_ssize_t, + key: *mut *mut PyObject, + value: *mut *mut PyObject, + hash: *mut crate::Py_hash_t, + ) -> c_int; + // skipped PyDict_GET_SIZE + // skipped _PyDict_ContainsId + pub fn _PyDict_NewPresized(minused: Py_ssize_t) -> *mut PyObject; + // skipped _PyDict_MaybeUntrack + // skipped _PyDict_HasOnlyStringKeys + // skipped _PyDict_KeysSize + // skipped _PyDict_SizeOf + // skipped _PyDict_Pop + // skipped _PyDict_Pop_KnownHash + // skipped _PyDict_FromKeys + // skipped _PyDict_HasSplitTable + // skipped _PyDict_MergeEx + // skipped _PyDict_SetItemId + // skipped _PyDict_DelItemId + // skipped _PyDict_DebugMallocStats + // skipped _PyObjectDict_SetItem + // skipped _PyDict_LoadGlobal + // skipped _PyDict_GetItemHint + // skipped _PyDictViewObject + // skipped _PyDictView_New + // skipped _PyDictView_Intersect + + #[cfg(Py_3_10)] + pub fn _PyDict_Contains_KnownHash( + op: *mut PyObject, + key: *mut PyObject, + hash: crate::Py_hash_t, + ) -> c_int; + + #[cfg(not(Py_3_10))] + pub fn _PyDict_Contains(mp: *mut PyObject, key: *mut PyObject, hash: Py_ssize_t) -> c_int; +} diff --git a/include/pyo3/pyo3-ffi/src/cpython/floatobject.rs b/include/pyo3/pyo3-ffi/src/cpython/floatobject.rs new file mode 100644 index 00000000..8c7ee885 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/cpython/floatobject.rs @@ -0,0 +1,33 @@ +#[cfg(GraalPy)] +use crate::PyFloat_AsDouble; +use crate::{PyFloat_Check, PyObject}; +use std::os::raw::c_double; + +#[repr(C)] +pub struct PyFloatObject { + pub ob_base: PyObject, + #[cfg(not(GraalPy))] + pub ob_fval: c_double, +} + +#[inline] +pub unsafe fn _PyFloat_CAST(op: *mut PyObject) -> *mut PyFloatObject { + debug_assert_eq!(PyFloat_Check(op), 1); + op.cast() +} + +#[inline] +pub unsafe fn PyFloat_AS_DOUBLE(op: *mut PyObject) -> c_double { + #[cfg(not(GraalPy))] + return (*_PyFloat_CAST(op)).ob_fval; + #[cfg(GraalPy)] + return PyFloat_AsDouble(op); +} + +// skipped PyFloat_Pack2 +// skipped PyFloat_Pack4 +// skipped PyFloat_Pack8 + +// skipped PyFloat_Unpack2 +// skipped PyFloat_Unpack4 +// skipped PyFloat_Unpack8 diff --git a/include/pyo3/pyo3-ffi/src/cpython/frameobject.rs b/include/pyo3/pyo3-ffi/src/cpython/frameobject.rs new file mode 100644 index 00000000..6d2346f9 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/cpython/frameobject.rs @@ -0,0 +1,96 @@ +#[cfg(not(GraalPy))] +use crate::cpython::code::PyCodeObject; +use crate::object::*; +#[cfg(not(GraalPy))] +use crate::pystate::PyThreadState; +#[cfg(not(any(PyPy, GraalPy, Py_3_11)))] +use std::os::raw::c_char; +use std::os::raw::c_int; +use std::ptr::addr_of_mut; + +#[cfg(not(any(PyPy, GraalPy, Py_3_11)))] +pub type PyFrameState = c_char; + +#[repr(C)] +#[derive(Copy, Clone)] +#[cfg(not(any(PyPy, GraalPy, Py_3_11)))] +pub struct PyTryBlock { + pub b_type: c_int, + pub b_handler: c_int, + pub b_level: c_int, +} + +#[repr(C)] +#[cfg(not(any(PyPy, GraalPy, Py_3_11)))] +pub struct PyFrameObject { + pub ob_base: PyVarObject, + pub f_back: *mut PyFrameObject, + pub f_code: *mut PyCodeObject, + pub f_builtins: *mut PyObject, + pub f_globals: *mut PyObject, + pub f_locals: *mut PyObject, + pub f_valuestack: *mut *mut PyObject, + + #[cfg(not(Py_3_10))] + pub f_stacktop: *mut *mut PyObject, + pub f_trace: *mut PyObject, + #[cfg(Py_3_10)] + pub f_stackdepth: c_int, + pub f_trace_lines: c_char, + pub f_trace_opcodes: c_char, + + pub f_gen: *mut PyObject, + + pub f_lasti: c_int, + pub f_lineno: c_int, + pub f_iblock: c_int, + #[cfg(not(Py_3_10))] + pub f_executing: c_char, + #[cfg(Py_3_10)] + pub f_state: PyFrameState, + pub f_blockstack: [PyTryBlock; crate::CO_MAXBLOCKS], + pub f_localsplus: [*mut PyObject; 1], +} + +#[cfg(any(PyPy, GraalPy, Py_3_11))] +opaque_struct!(PyFrameObject); + +// skipped _PyFrame_IsRunnable +// skipped _PyFrame_IsExecuting +// skipped _PyFrameHasCompleted + +#[cfg_attr(windows, link(name = "pythonXY"))] +extern "C" { + pub static mut PyFrame_Type: PyTypeObject; +} + +#[inline] +pub unsafe fn PyFrame_Check(op: *mut PyObject) -> c_int { + (Py_TYPE(op) == addr_of_mut!(PyFrame_Type)) as c_int +} + +extern "C" { + #[cfg(not(GraalPy))] + #[cfg_attr(PyPy, link_name = "PyPyFrame_New")] + pub fn PyFrame_New( + tstate: *mut PyThreadState, + code: *mut PyCodeObject, + globals: *mut PyObject, + locals: *mut PyObject, + ) -> *mut PyFrameObject; + // skipped _PyFrame_New_NoTrack + + pub fn PyFrame_BlockSetup(f: *mut PyFrameObject, _type: c_int, handler: c_int, level: c_int); + #[cfg(not(any(PyPy, GraalPy, Py_3_11)))] + pub fn PyFrame_BlockPop(f: *mut PyFrameObject) -> *mut PyTryBlock; + + pub fn PyFrame_LocalsToFast(f: *mut PyFrameObject, clear: c_int); + pub fn PyFrame_FastToLocalsWithError(f: *mut PyFrameObject) -> c_int; + pub fn PyFrame_FastToLocals(f: *mut PyFrameObject); + + // skipped _PyFrame_DebugMallocStats + // skipped PyFrame_GetBack + + #[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 new file mode 100644 index 00000000..25de30d5 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/cpython/funcobject.rs @@ -0,0 +1,108 @@ +use std::os::raw::c_int; +#[cfg(not(all(PyPy, not(Py_3_8))))] +use std::ptr::addr_of_mut; + +use crate::PyObject; + +#[cfg(all(not(any(PyPy, GraalPy)), not(Py_3_10)))] +#[repr(C)] +pub struct PyFunctionObject { + pub ob_base: PyObject, + pub func_code: *mut PyObject, + pub func_globals: *mut PyObject, + pub func_defaults: *mut PyObject, + pub func_kwdefaults: *mut PyObject, + pub func_closure: *mut PyObject, + pub func_doc: *mut PyObject, + pub func_name: *mut PyObject, + pub func_dict: *mut PyObject, + pub func_weakreflist: *mut PyObject, + pub func_module: *mut PyObject, + pub func_annotations: *mut PyObject, + pub func_qualname: *mut PyObject, + #[cfg(Py_3_8)] + pub vectorcall: Option, +} + +#[cfg(all(not(any(PyPy, GraalPy)), Py_3_10))] +#[repr(C)] +pub struct PyFunctionObject { + pub ob_base: PyObject, + pub func_globals: *mut PyObject, + pub func_builtins: *mut PyObject, + pub func_name: *mut PyObject, + pub func_qualname: *mut PyObject, + pub func_code: *mut PyObject, + pub func_defaults: *mut PyObject, + pub func_kwdefaults: *mut PyObject, + pub func_closure: *mut PyObject, + pub func_doc: *mut PyObject, + pub func_dict: *mut PyObject, + pub func_weakreflist: *mut PyObject, + pub func_module: *mut PyObject, + pub func_annotations: *mut PyObject, + #[cfg(Py_3_12)] + pub func_typeparams: *mut PyObject, + pub vectorcall: Option, + #[cfg(Py_3_11)] + pub func_version: u32, +} + +#[cfg(PyPy)] +#[repr(C)] +pub struct PyFunctionObject { + pub ob_base: PyObject, + pub func_name: *mut PyObject, +} + +#[cfg(GraalPy)] +pub struct PyFunctionObject { + pub ob_base: PyObject, +} + +#[cfg_attr(windows, link(name = "pythonXY"))] +extern "C" { + #[cfg(not(all(PyPy, not(Py_3_8))))] + #[cfg_attr(PyPy, link_name = "PyPyFunction_Type")] + pub static mut PyFunction_Type: crate::PyTypeObject; +} + +#[cfg(not(all(PyPy, not(Py_3_8))))] +#[inline] +pub unsafe fn PyFunction_Check(op: *mut PyObject) -> c_int { + (crate::Py_TYPE(op) == addr_of_mut!(PyFunction_Type)) as c_int +} + +extern "C" { + pub fn PyFunction_New(code: *mut PyObject, globals: *mut PyObject) -> *mut PyObject; + pub fn PyFunction_NewWithQualName( + code: *mut PyObject, + globals: *mut PyObject, + qualname: *mut PyObject, + ) -> *mut PyObject; + pub fn PyFunction_GetCode(op: *mut PyObject) -> *mut PyObject; + pub fn PyFunction_GetGlobals(op: *mut PyObject) -> *mut PyObject; + pub fn PyFunction_GetModule(op: *mut PyObject) -> *mut PyObject; + pub fn PyFunction_GetDefaults(op: *mut PyObject) -> *mut PyObject; + pub fn PyFunction_SetDefaults(op: *mut PyObject, defaults: *mut PyObject) -> c_int; + pub fn PyFunction_GetKwDefaults(op: *mut PyObject) -> *mut PyObject; + pub fn PyFunction_SetKwDefaults(op: *mut PyObject, defaults: *mut PyObject) -> c_int; + pub fn PyFunction_GetClosure(op: *mut PyObject) -> *mut PyObject; + pub fn PyFunction_SetClosure(op: *mut PyObject, closure: *mut PyObject) -> c_int; + pub fn PyFunction_GetAnnotations(op: *mut PyObject) -> *mut PyObject; + pub fn PyFunction_SetAnnotations(op: *mut PyObject, annotations: *mut PyObject) -> c_int; +} + +// skipped _PyFunction_Vectorcall +// skipped PyFunction_GET_CODE +// skipped PyFunction_GET_GLOBALS +// skipped PyFunction_GET_MODULE +// skipped PyFunction_GET_DEFAULTS +// skipped PyFunction_GET_KW_DEFAULTS +// skipped PyFunction_GET_CLOSURE +// skipped PyFunction_GET_ANNOTATIONS + +// skipped PyClassMethod_Type +// skipped PyStaticMethod_Type +// skipped PyClassMethod_New +// skipped PyStaticMethod_New diff --git a/include/pyo3/pyo3-ffi/src/cpython/genobject.rs b/include/pyo3/pyo3-ffi/src/cpython/genobject.rs new file mode 100644 index 00000000..17348b2f --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/cpython/genobject.rs @@ -0,0 +1,98 @@ +use crate::object::*; +use crate::PyFrameObject; +#[cfg(not(any(PyPy, GraalPy)))] +use crate::_PyErr_StackItem; +#[cfg(Py_3_11)] +use std::os::raw::c_char; +use std::os::raw::c_int; +use std::ptr::addr_of_mut; + +#[cfg(not(any(PyPy, GraalPy)))] +#[repr(C)] +pub struct PyGenObject { + pub ob_base: PyObject, + #[cfg(not(Py_3_11))] + pub gi_frame: *mut PyFrameObject, + #[cfg(not(Py_3_10))] + pub gi_running: c_int, + #[cfg(not(Py_3_12))] + pub gi_code: *mut PyObject, + pub gi_weakreflist: *mut PyObject, + pub gi_name: *mut PyObject, + pub gi_qualname: *mut PyObject, + pub gi_exc_state: _PyErr_StackItem, + #[cfg(Py_3_11)] + pub gi_origin_or_finalizer: *mut PyObject, + #[cfg(Py_3_11)] + pub gi_hooks_inited: c_char, + #[cfg(Py_3_11)] + pub gi_closed: c_char, + #[cfg(Py_3_11)] + pub gi_running_async: c_char, + #[cfg(Py_3_11)] + pub gi_frame_state: i8, + #[cfg(Py_3_11)] + pub gi_iframe: [*mut PyObject; 1], +} + +#[cfg_attr(windows, link(name = "pythonXY"))] +extern "C" { + pub static mut PyGen_Type: PyTypeObject; +} + +#[inline] +pub unsafe fn PyGen_Check(op: *mut PyObject) -> c_int { + PyObject_TypeCheck(op, addr_of_mut!(PyGen_Type)) +} + +#[inline] +pub unsafe fn PyGen_CheckExact(op: *mut PyObject) -> c_int { + (Py_TYPE(op) == addr_of_mut!(PyGen_Type)) as c_int +} + +extern "C" { + pub fn PyGen_New(frame: *mut PyFrameObject) -> *mut PyObject; + // skipped PyGen_NewWithQualName + // skipped _PyGen_SetStopIterationValue + // skipped _PyGen_FetchStopIterationValue + // skipped _PyGen_yf + // skipped _PyGen_Finalize + #[cfg(not(any(Py_3_9, PyPy)))] + #[deprecated(note = "This function was never documented in the Python API.")] + pub fn PyGen_NeedsFinalizing(op: *mut PyGenObject) -> c_int; +} + +// skipped PyCoroObject + +#[cfg_attr(windows, link(name = "pythonXY"))] +extern "C" { + pub static mut PyCoro_Type: PyTypeObject; + pub static mut _PyCoroWrapper_Type: PyTypeObject; +} + +#[inline] +pub unsafe fn PyCoro_CheckExact(op: *mut PyObject) -> c_int { + PyObject_TypeCheck(op, addr_of_mut!(PyCoro_Type)) +} + +// skipped _PyCoro_GetAwaitableIter +// skipped PyCoro_New + +// skipped PyAsyncGenObject + +#[cfg_attr(windows, link(name = "pythonXY"))] +extern "C" { + pub static mut PyAsyncGen_Type: PyTypeObject; + // skipped _PyAsyncGenASend_Type + // skipped _PyAsyncGenWrappedValue_Type + // skipped _PyAsyncGenAThrow_Type +} + +// skipped PyAsyncGen_New + +#[inline] +pub unsafe fn PyAsyncGen_CheckExact(op: *mut PyObject) -> c_int { + PyObject_TypeCheck(op, addr_of_mut!(PyAsyncGen_Type)) +} + +// skipped _PyAsyncGenValueWrapperNew diff --git a/include/pyo3/pyo3-ffi/src/cpython/import.rs b/include/pyo3/pyo3-ffi/src/cpython/import.rs new file mode 100644 index 00000000..697d68a4 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/cpython/import.rs @@ -0,0 +1,74 @@ +use crate::{PyInterpreterState, PyObject}; +#[cfg(not(PyPy))] +use std::os::raw::c_uchar; +use std::os::raw::{c_char, c_int}; + +// skipped PyInit__imp + +extern "C" { + pub fn _PyImport_IsInitialized(state: *mut PyInterpreterState) -> c_int; + // skipped _PyImport_GetModuleId + pub fn _PyImport_SetModule(name: *mut PyObject, module: *mut PyObject) -> c_int; + pub fn _PyImport_SetModuleString(name: *const c_char, module: *mut PyObject) -> c_int; + pub fn _PyImport_AcquireLock(); + pub fn _PyImport_ReleaseLock() -> c_int; + #[cfg(not(Py_3_9))] + pub fn _PyImport_FindBuiltin(name: *const c_char, modules: *mut PyObject) -> *mut PyObject; + #[cfg(not(Py_3_11))] + pub fn _PyImport_FindExtensionObject(a: *mut PyObject, b: *mut PyObject) -> *mut PyObject; + pub fn _PyImport_FixupBuiltin( + module: *mut PyObject, + name: *const c_char, + modules: *mut PyObject, + ) -> c_int; + pub fn _PyImport_FixupExtensionObject( + a: *mut PyObject, + b: *mut PyObject, + c: *mut PyObject, + d: *mut PyObject, + ) -> c_int; +} + +#[cfg(not(PyPy))] +#[repr(C)] +#[derive(Copy, Clone)] +pub struct _inittab { + pub name: *const c_char, + pub initfunc: Option *mut PyObject>, +} + +#[cfg_attr(windows, link(name = "pythonXY"))] +extern "C" { + #[cfg(not(PyPy))] + pub static mut PyImport_Inittab: *mut _inittab; +} + +extern "C" { + #[cfg(not(PyPy))] + pub fn PyImport_ExtendInittab(newtab: *mut _inittab) -> c_int; +} + +#[cfg(not(PyPy))] +#[repr(C)] +#[derive(Copy, Clone)] +pub struct _frozen { + pub name: *const c_char, + pub code: *const c_uchar, + pub size: c_int, + #[cfg(Py_3_11)] + pub is_package: c_int, + #[cfg(all(Py_3_11, not(Py_3_13)))] + pub get_code: Option *mut PyObject>, +} + +#[cfg_attr(windows, link(name = "pythonXY"))] +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; +} diff --git a/include/pyo3/pyo3-ffi/src/cpython/initconfig.rs b/include/pyo3/pyo3-ffi/src/cpython/initconfig.rs new file mode 100644 index 00000000..321d200e --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/cpython/initconfig.rs @@ -0,0 +1,223 @@ +/* --- PyStatus ----------------------------------------------- */ + +use crate::Py_ssize_t; +use libc::wchar_t; +use std::os::raw::{c_char, c_int, c_ulong}; + +#[repr(C)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum _PyStatus_TYPE { + _PyStatus_TYPE_OK = 0, + _PyStatus_TYPE_ERROR = 1, + _PyStatus_TYPE_EXIT = 2, +} + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct PyStatus { + pub _type: _PyStatus_TYPE, + pub func: *const c_char, + pub err_msg: *const c_char, + pub exitcode: c_int, +} + +extern "C" { + pub fn PyStatus_Ok() -> PyStatus; + pub fn PyStatus_Error(err_msg: *const c_char) -> PyStatus; + pub fn PyStatus_NoMemory() -> PyStatus; + pub fn PyStatus_Exit(exitcode: c_int) -> PyStatus; + pub fn PyStatus_IsError(err: PyStatus) -> c_int; + pub fn PyStatus_IsExit(err: PyStatus) -> c_int; + pub fn PyStatus_Exception(err: PyStatus) -> c_int; +} + +/* --- PyWideStringList ------------------------------------------------ */ + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct PyWideStringList { + pub length: Py_ssize_t, + pub items: *mut *mut wchar_t, +} + +extern "C" { + pub fn PyWideStringList_Append(list: *mut PyWideStringList, item: *const wchar_t) -> PyStatus; + pub fn PyWideStringList_Insert( + list: *mut PyWideStringList, + index: Py_ssize_t, + item: *const wchar_t, + ) -> PyStatus; +} + +/* --- PyPreConfig ----------------------------------------------- */ + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct PyPreConfig { + pub _config_init: c_int, + pub parse_argv: c_int, + pub isolated: c_int, + pub use_environment: c_int, + pub configure_locale: c_int, + pub coerce_c_locale: c_int, + pub coerce_c_locale_warn: c_int, + + #[cfg(windows)] + pub legacy_windows_fs_encoding: c_int, + + pub utf8_mode: c_int, + pub dev_mode: c_int, + pub allocator: c_int, +} + +extern "C" { + pub fn PyPreConfig_InitPythonConfig(config: *mut PyPreConfig); + pub fn PyPreConfig_InitIsolatedConfig(config: *mut PyPreConfig); +} + +/* --- PyConfig ---------------------------------------------- */ + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct PyConfig { + pub _config_init: c_int, + pub isolated: c_int, + pub use_environment: c_int, + pub dev_mode: c_int, + pub install_signal_handlers: c_int, + pub use_hash_seed: c_int, + pub hash_seed: c_ulong, + pub faulthandler: c_int, + #[cfg(all(Py_3_9, not(Py_3_10)))] + pub _use_peg_parser: c_int, + pub tracemalloc: c_int, + #[cfg(Py_3_12)] + pub perf_profiling: c_int, + pub import_time: c_int, + #[cfg(Py_3_11)] + pub code_debug_ranges: c_int, + pub show_ref_count: c_int, + #[cfg(not(Py_3_9))] + pub show_alloc_count: c_int, + pub dump_refs: c_int, + #[cfg(Py_3_11)] + pub dump_refs_file: *mut wchar_t, + pub malloc_stats: c_int, + pub filesystem_encoding: *mut wchar_t, + pub filesystem_errors: *mut wchar_t, + pub pycache_prefix: *mut wchar_t, + pub parse_argv: c_int, + #[cfg(Py_3_10)] + pub orig_argv: PyWideStringList, + pub argv: PyWideStringList, + #[cfg(not(Py_3_10))] + pub program_name: *mut wchar_t, + pub xoptions: PyWideStringList, + pub warnoptions: PyWideStringList, + pub site_import: c_int, + pub bytes_warning: c_int, + #[cfg(Py_3_10)] + pub warn_default_encoding: c_int, + pub inspect: c_int, + pub interactive: c_int, + pub optimization_level: c_int, + pub parser_debug: c_int, + pub write_bytecode: c_int, + pub verbose: c_int, + pub quiet: c_int, + pub user_site_directory: c_int, + pub configure_c_stdio: c_int, + pub buffered_stdio: c_int, + pub stdio_encoding: *mut wchar_t, + pub stdio_errors: *mut wchar_t, + + #[cfg(windows)] + pub legacy_windows_stdio: c_int, + + pub check_hash_pycs_mode: *mut wchar_t, + #[cfg(Py_3_11)] + pub use_frozen_modules: c_int, + #[cfg(Py_3_11)] + pub safe_path: c_int, + #[cfg(Py_3_12)] + pub int_max_str_digits: c_int, + #[cfg(Py_3_13)] + pub cpu_count: c_int, + #[cfg(Py_GIL_DISABLED)] + pub enable_gil: c_int, + pub pathconfig_warnings: c_int, + #[cfg(Py_3_10)] + pub program_name: *mut wchar_t, + pub pythonpath_env: *mut wchar_t, + pub home: *mut wchar_t, + #[cfg(Py_3_10)] + pub platlibdir: *mut wchar_t, + + pub module_search_paths_set: c_int, + pub module_search_paths: PyWideStringList, + #[cfg(Py_3_11)] + pub stdlib_dir: *mut wchar_t, + pub executable: *mut wchar_t, + pub base_executable: *mut wchar_t, + pub prefix: *mut wchar_t, + pub base_prefix: *mut wchar_t, + pub exec_prefix: *mut wchar_t, + pub base_exec_prefix: *mut wchar_t, + #[cfg(all(Py_3_9, not(Py_3_10)))] + pub platlibdir: *mut wchar_t, + pub skip_source_first_line: c_int, + pub run_command: *mut wchar_t, + pub run_module: *mut wchar_t, + pub run_filename: *mut wchar_t, + #[cfg(Py_3_13)] + pub sys_path_0: *mut wchar_t, + pub _install_importlib: c_int, + pub _init_main: c_int, + #[cfg(all(Py_3_9, not(Py_3_12)))] + pub _isolated_interpreter: c_int, + #[cfg(Py_3_11)] + pub _is_python_build: c_int, + #[cfg(all(Py_3_9, not(Py_3_10)))] + pub _orig_argv: PyWideStringList, + #[cfg(all(Py_3_13, py_sys_config = "Py_DEBUG"))] + pub run_presite: *mut wchar_t, +} + +extern "C" { + pub fn PyConfig_InitPythonConfig(config: *mut PyConfig); + pub fn PyConfig_InitIsolatedConfig(config: *mut PyConfig); + pub fn PyConfig_Clear(config: *mut PyConfig); + pub fn PyConfig_SetString( + config: *mut PyConfig, + config_str: *mut *mut wchar_t, + str: *const wchar_t, + ) -> PyStatus; + pub fn PyConfig_SetBytesString( + config: *mut PyConfig, + config_str: *mut *mut wchar_t, + str: *const c_char, + ) -> PyStatus; + pub fn PyConfig_Read(config: *mut PyConfig) -> PyStatus; + pub fn PyConfig_SetBytesArgv( + config: *mut PyConfig, + argc: Py_ssize_t, + argv: *mut *const c_char, + ) -> PyStatus; + pub fn PyConfig_SetArgv( + config: *mut PyConfig, + argc: Py_ssize_t, + argv: *mut *const wchar_t, + ) -> PyStatus; + pub fn PyConfig_SetWideStringList( + config: *mut PyConfig, + list: *mut PyWideStringList, + length: Py_ssize_t, + items: *mut *mut wchar_t, + ) -> PyStatus; +} + +/* --- Helper functions --------------------------------------- */ + +extern "C" { + pub fn Py_GetArgcArgv(argc: *mut c_int, argv: *mut *mut *mut wchar_t); +} diff --git a/include/pyo3/pyo3-ffi/src/cpython/listobject.rs b/include/pyo3/pyo3-ffi/src/cpython/listobject.rs new file mode 100644 index 00000000..963ddfbe --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/cpython/listobject.rs @@ -0,0 +1,40 @@ +use crate::object::*; +#[cfg(not(PyPy))] +use crate::pyport::Py_ssize_t; + +#[cfg(not(any(PyPy, GraalPy)))] +#[repr(C)] +pub struct PyListObject { + pub ob_base: PyVarObject, + pub ob_item: *mut *mut PyObject, + pub allocated: Py_ssize_t, +} + +#[cfg(any(PyPy, GraalPy))] +pub struct PyListObject { + pub ob_base: PyObject, +} + +// skipped _PyList_Extend +// skipped _PyList_DebugMallocStats +// skipped _PyList_CAST (used inline below) + +/// Macro, trading safety for speed +#[inline] +#[cfg(not(any(PyPy, GraalPy)))] +pub unsafe fn PyList_GET_ITEM(op: *mut PyObject, i: Py_ssize_t) -> *mut PyObject { + *(*(op as *mut PyListObject)).ob_item.offset(i) +} + +/// Macro, *only* to be used to fill in brand new lists +#[inline] +#[cfg(not(any(PyPy, GraalPy)))] +pub unsafe fn PyList_SET_ITEM(op: *mut PyObject, i: Py_ssize_t, v: *mut PyObject) { + *(*(op as *mut PyListObject)).ob_item.offset(i) = v; +} + +#[inline] +#[cfg(not(PyPy))] +pub unsafe fn PyList_GET_SIZE(op: *mut PyObject) -> Py_ssize_t { + Py_SIZE(op) +} diff --git a/include/pyo3/pyo3-ffi/src/cpython/lock.rs b/include/pyo3/pyo3-ffi/src/cpython/lock.rs new file mode 100644 index 00000000..c451666e --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/cpython/lock.rs @@ -0,0 +1,23 @@ +use std::marker::PhantomPinned; +use std::sync::atomic::AtomicU8; + +#[repr(transparent)] +#[derive(Debug)] +pub struct PyMutex { + pub(crate) _bits: AtomicU8, + 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 new file mode 100644 index 00000000..45acaae5 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/cpython/longobject.rs @@ -0,0 +1,74 @@ +use crate::longobject::*; +use crate::object::*; +#[cfg(Py_3_13)] +use crate::pyport::Py_ssize_t; +use libc::size_t; +#[cfg(Py_3_13)] +use std::os::raw::c_void; +use std::os::raw::{c_int, c_uchar}; + +#[cfg(Py_3_13)] +extern "C" { + pub fn PyLong_FromUnicodeObject(u: *mut PyObject, base: c_int) -> *mut PyObject; +} + +#[cfg(Py_3_13)] +pub const Py_ASNATIVEBYTES_DEFAULTS: c_int = -1; +#[cfg(Py_3_13)] +pub const Py_ASNATIVEBYTES_BIG_ENDIAN: c_int = 0; +#[cfg(Py_3_13)] +pub const Py_ASNATIVEBYTES_LITTLE_ENDIAN: c_int = 1; +#[cfg(Py_3_13)] +pub const Py_ASNATIVEBYTES_NATIVE_ENDIAN: c_int = 3; +#[cfg(Py_3_13)] +pub const Py_ASNATIVEBYTES_UNSIGNED_BUFFER: c_int = 4; +#[cfg(Py_3_13)] +pub const Py_ASNATIVEBYTES_REJECT_NEGATIVE: c_int = 8; + +extern "C" { + // skipped _PyLong_Sign + + #[cfg(Py_3_13)] + pub fn PyLong_AsNativeBytes( + v: *mut PyObject, + buffer: *mut c_void, + n_bytes: Py_ssize_t, + flags: c_int, + ) -> Py_ssize_t; + + #[cfg(Py_3_13)] + pub fn PyLong_FromNativeBytes( + buffer: *const c_void, + n_bytes: size_t, + flags: c_int, + ) -> *mut PyObject; + + #[cfg(Py_3_13)] + pub fn PyLong_FromUnsignedNativeBytes( + buffer: *const c_void, + n_bytes: size_t, + flags: c_int, + ) -> *mut PyObject; + + // skipped PyUnstable_Long_IsCompact + // skipped PyUnstable_Long_CompactValue + + #[cfg_attr(PyPy, link_name = "_PyPyLong_FromByteArray")] + pub fn _PyLong_FromByteArray( + bytes: *const c_uchar, + n: size_t, + little_endian: c_int, + is_signed: c_int, + ) -> *mut PyObject; + + #[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, + ) -> c_int; + + // skipped _PyLong_GCD +} diff --git a/include/pyo3/pyo3-ffi/src/cpython/methodobject.rs b/include/pyo3/pyo3-ffi/src/cpython/methodobject.rs new file mode 100644 index 00000000..97ad9ce3 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/cpython/methodobject.rs @@ -0,0 +1,71 @@ +use crate::object::*; +#[cfg(not(GraalPy))] +use crate::{PyCFunctionObject, PyMethodDefPointer, METH_METHOD, METH_STATIC}; +use std::os::raw::c_int; +use std::ptr::addr_of_mut; + +#[cfg(not(GraalPy))] +pub struct PyCMethodObject { + pub func: PyCFunctionObject, + pub mm_class: *mut PyTypeObject, +} + +#[cfg_attr(windows, link(name = "pythonXY"))] +extern "C" { + pub static mut PyCMethod_Type: PyTypeObject; +} + +#[inline] +pub unsafe fn PyCMethod_CheckExact(op: *mut PyObject) -> c_int { + (Py_TYPE(op) == addr_of_mut!(PyCMethod_Type)) as c_int +} + +#[inline] +pub unsafe fn PyCMethod_Check(op: *mut PyObject) -> c_int { + PyObject_TypeCheck(op, addr_of_mut!(PyCMethod_Type)) +} + +#[cfg(not(GraalPy))] +#[inline] +pub unsafe fn PyCFunction_GET_FUNCTION(func: *mut PyObject) -> PyMethodDefPointer { + debug_assert_eq!(PyCMethod_Check(func), 1); + + let func = func.cast::(); + (*(*func).m_ml).ml_meth +} + +#[cfg(not(GraalPy))] +#[inline] +pub unsafe fn PyCFunction_GET_SELF(func: *mut PyObject) -> *mut PyObject { + debug_assert_eq!(PyCMethod_Check(func), 1); + + let func = func.cast::(); + if (*(*func).m_ml).ml_flags & METH_STATIC != 0 { + std::ptr::null_mut() + } else { + (*func).m_self + } +} + +#[cfg(not(GraalPy))] +#[inline] +pub unsafe fn PyCFunction_GET_FLAGS(func: *mut PyObject) -> c_int { + debug_assert_eq!(PyCMethod_Check(func), 1); + + let func = func.cast::(); + (*(*func).m_ml).ml_flags +} + +#[cfg(not(GraalPy))] +#[inline] +pub unsafe fn PyCFunction_GET_CLASS(func: *mut PyObject) -> *mut PyTypeObject { + debug_assert_eq!(PyCMethod_Check(func), 1); + + let func = func.cast::(); + if (*(*func).m_ml).ml_flags & METH_METHOD != 0 { + let func = func.cast::(); + (*func).mm_class + } else { + std::ptr::null_mut() + } +} diff --git a/include/pyo3/pyo3-ffi/src/cpython/mod.rs b/include/pyo3/pyo3-ffi/src/cpython/mod.rs new file mode 100644 index 00000000..fe909f0c --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/cpython/mod.rs @@ -0,0 +1,84 @@ +pub(crate) mod abstract_; +// skipped bytearrayobject.h +pub(crate) mod bytesobject; +#[cfg(not(PyPy))] +pub(crate) mod ceval; +pub(crate) mod code; +pub(crate) mod compile; +pub(crate) mod complexobject; +#[cfg(Py_3_13)] +pub(crate) mod critical_section; +pub(crate) mod descrobject; +#[cfg(not(PyPy))] +pub(crate) mod dictobject; +// skipped fileobject.h +// skipped fileutils.h +pub(crate) mod frameobject; +pub(crate) mod funcobject; +pub(crate) mod genobject; +pub(crate) mod import; +#[cfg(all(Py_3_8, not(PyPy)))] +pub(crate) mod initconfig; +// skipped interpreteridobject.h +pub(crate) mod listobject; +#[cfg(Py_3_13)] +pub(crate) mod lock; +pub(crate) mod longobject; +#[cfg(all(Py_3_9, not(PyPy)))] +pub(crate) mod methodobject; +pub(crate) mod object; +pub(crate) mod objimpl; +pub(crate) mod pydebug; +pub(crate) mod pyerrors; +#[cfg(all(Py_3_8, not(PyPy)))] +pub(crate) mod pylifecycle; +pub(crate) mod pymem; +pub(crate) mod pystate; +pub(crate) mod pythonrun; +// skipped sysmodule.h +pub(crate) mod floatobject; +pub(crate) mod pyframe; +pub(crate) mod tupleobject; +pub(crate) mod unicodeobject; +pub(crate) mod weakrefobject; + +pub use self::abstract_::*; +pub use self::bytesobject::*; +#[cfg(not(PyPy))] +pub use self::ceval::*; +pub use self::code::*; +pub use self::compile::*; +pub use self::complexobject::*; +#[cfg(Py_3_13)] +pub use self::critical_section::*; +pub use self::descrobject::*; +#[cfg(not(PyPy))] +pub use self::dictobject::*; +pub use self::floatobject::*; +pub use self::frameobject::*; +pub use self::funcobject::*; +pub use self::genobject::*; +pub use self::import::*; +#[cfg(all(Py_3_8, not(PyPy)))] +pub use self::initconfig::*; +pub use self::listobject::*; +#[cfg(Py_3_13)] +pub use self::lock::*; +pub use self::longobject::*; +#[cfg(all(Py_3_9, not(PyPy)))] +pub use self::methodobject::*; +pub use self::object::*; +pub use self::objimpl::*; +pub use self::pydebug::*; +pub use self::pyerrors::*; +#[cfg(Py_3_11)] +pub use self::pyframe::*; +#[cfg(all(Py_3_8, not(PyPy)))] +pub use self::pylifecycle::*; +pub use self::pymem::*; +pub use self::pystate::*; +pub use self::pythonrun::*; +pub use self::tupleobject::*; +pub use self::unicodeobject::*; +#[cfg(not(any(PyPy, GraalPy)))] +pub use self::weakrefobject::*; diff --git a/include/pyo3/pyo3-ffi/src/cpython/object.rs b/include/pyo3/pyo3-ffi/src/cpython/object.rs new file mode 100644 index 00000000..35ddf25a --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/cpython/object.rs @@ -0,0 +1,400 @@ +#[cfg(Py_3_8)] +use crate::vectorcallfunc; +use crate::{object, PyGetSetDef, PyMemberDef, PyMethodDef, PyObject, Py_ssize_t}; +use std::mem; +use std::os::raw::{c_char, c_int, c_uint, c_void}; + +// skipped private _Py_NewReference +// skipped private _Py_NewReferenceNoTotal +// skipped private _Py_ResurrectReference + +// skipped private _Py_GetGlobalRefTotal +// skipped private _Py_GetRefTotal +// skipped private _Py_GetLegacyRefTotal +// skipped private _PyInterpreterState_GetRefTotal + +// skipped private _Py_Identifier + +// skipped private _Py_static_string_init +// skipped private _Py_static_string +// skipped private _Py_IDENTIFIER + +#[cfg(not(Py_3_11))] // moved to src/buffer.rs from Python +mod bufferinfo { + use crate::Py_ssize_t; + use std::os::raw::{c_char, c_int, c_void}; + use std::ptr; + + #[repr(C)] + #[derive(Copy, Clone)] + pub struct Py_buffer { + pub buf: *mut c_void, + /// Owned reference + pub obj: *mut crate::PyObject, + pub len: Py_ssize_t, + pub itemsize: Py_ssize_t, + pub readonly: c_int, + pub ndim: c_int, + pub format: *mut c_char, + pub shape: *mut Py_ssize_t, + pub strides: *mut Py_ssize_t, + pub suboffsets: *mut Py_ssize_t, + pub internal: *mut c_void, + #[cfg(PyPy)] + pub flags: c_int, + #[cfg(PyPy)] + pub _strides: [Py_ssize_t; PyBUF_MAX_NDIM as usize], + #[cfg(PyPy)] + pub _shape: [Py_ssize_t; PyBUF_MAX_NDIM as usize], + } + + impl Py_buffer { + #[allow(clippy::new_without_default)] + pub const fn new() -> Self { + Py_buffer { + buf: ptr::null_mut(), + obj: ptr::null_mut(), + len: 0, + itemsize: 0, + readonly: 0, + ndim: 0, + format: ptr::null_mut(), + shape: ptr::null_mut(), + strides: ptr::null_mut(), + suboffsets: ptr::null_mut(), + internal: ptr::null_mut(), + #[cfg(PyPy)] + flags: 0, + #[cfg(PyPy)] + _strides: [0; PyBUF_MAX_NDIM as usize], + #[cfg(PyPy)] + _shape: [0; PyBUF_MAX_NDIM as usize], + } + } + } + + pub type getbufferproc = unsafe extern "C" fn( + arg1: *mut crate::PyObject, + arg2: *mut Py_buffer, + arg3: c_int, + ) -> c_int; + pub type releasebufferproc = + unsafe extern "C" fn(arg1: *mut crate::PyObject, arg2: *mut Py_buffer); + + /// Maximum number of dimensions + pub const PyBUF_MAX_NDIM: c_int = if cfg!(PyPy) { 36 } else { 64 }; + + /* Flags for getting buffers */ + pub const PyBUF_SIMPLE: c_int = 0; + pub const PyBUF_WRITABLE: c_int = 0x0001; + /* we used to include an E, backwards compatible alias */ + pub const PyBUF_WRITEABLE: c_int = PyBUF_WRITABLE; + pub const PyBUF_FORMAT: c_int = 0x0004; + pub const PyBUF_ND: c_int = 0x0008; + pub const PyBUF_STRIDES: c_int = 0x0010 | PyBUF_ND; + pub const PyBUF_C_CONTIGUOUS: c_int = 0x0020 | PyBUF_STRIDES; + pub const PyBUF_F_CONTIGUOUS: c_int = 0x0040 | PyBUF_STRIDES; + pub const PyBUF_ANY_CONTIGUOUS: c_int = 0x0080 | PyBUF_STRIDES; + pub const PyBUF_INDIRECT: c_int = 0x0100 | PyBUF_STRIDES; + + pub const PyBUF_CONTIG: c_int = PyBUF_ND | PyBUF_WRITABLE; + pub const PyBUF_CONTIG_RO: c_int = PyBUF_ND; + + pub const PyBUF_STRIDED: c_int = PyBUF_STRIDES | PyBUF_WRITABLE; + pub const PyBUF_STRIDED_RO: c_int = PyBUF_STRIDES; + + pub const PyBUF_RECORDS: c_int = PyBUF_STRIDES | PyBUF_WRITABLE | PyBUF_FORMAT; + pub const PyBUF_RECORDS_RO: c_int = PyBUF_STRIDES | PyBUF_FORMAT; + + pub const PyBUF_FULL: c_int = PyBUF_INDIRECT | PyBUF_WRITABLE | PyBUF_FORMAT; + pub const PyBUF_FULL_RO: c_int = PyBUF_INDIRECT | PyBUF_FORMAT; + + pub const PyBUF_READ: c_int = 0x100; + pub const PyBUF_WRITE: c_int = 0x200; +} + +#[cfg(not(Py_3_11))] +pub use self::bufferinfo::*; + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct PyNumberMethods { + pub nb_add: Option, + pub nb_subtract: Option, + pub nb_multiply: Option, + pub nb_remainder: Option, + pub nb_divmod: Option, + pub nb_power: Option, + pub nb_negative: Option, + pub nb_positive: Option, + pub nb_absolute: Option, + pub nb_bool: Option, + pub nb_invert: Option, + pub nb_lshift: Option, + pub nb_rshift: Option, + pub nb_and: Option, + pub nb_xor: Option, + pub nb_or: Option, + pub nb_int: Option, + pub nb_reserved: *mut c_void, + pub nb_float: Option, + pub nb_inplace_add: Option, + pub nb_inplace_subtract: Option, + pub nb_inplace_multiply: Option, + pub nb_inplace_remainder: Option, + pub nb_inplace_power: Option, + pub nb_inplace_lshift: Option, + pub nb_inplace_rshift: Option, + pub nb_inplace_and: Option, + pub nb_inplace_xor: Option, + pub nb_inplace_or: Option, + pub nb_floor_divide: Option, + pub nb_true_divide: Option, + pub nb_inplace_floor_divide: Option, + pub nb_inplace_true_divide: Option, + pub nb_index: Option, + pub nb_matrix_multiply: Option, + pub nb_inplace_matrix_multiply: Option, +} + +#[repr(C)] +#[derive(Clone)] +pub struct PySequenceMethods { + pub sq_length: Option, + pub sq_concat: Option, + pub sq_repeat: Option, + pub sq_item: Option, + pub was_sq_slice: *mut c_void, + pub sq_ass_item: Option, + pub was_sq_ass_slice: *mut c_void, + pub sq_contains: Option, + pub sq_inplace_concat: Option, + pub sq_inplace_repeat: Option, +} + +#[repr(C)] +#[derive(Clone, Default)] +pub struct PyMappingMethods { + pub mp_length: Option, + pub mp_subscript: Option, + pub mp_ass_subscript: Option, +} + +#[cfg(Py_3_10)] +pub type sendfunc = unsafe extern "C" fn( + iter: *mut PyObject, + value: *mut PyObject, + result: *mut *mut PyObject, +) -> object::PySendResult; + +#[repr(C)] +#[derive(Clone, Default)] +pub struct PyAsyncMethods { + pub am_await: Option, + pub am_aiter: Option, + pub am_anext: Option, + #[cfg(Py_3_10)] + pub am_send: Option, +} + +#[repr(C)] +#[derive(Clone, Default)] +pub struct PyBufferProcs { + pub bf_getbuffer: Option, + pub bf_releasebuffer: Option, +} + +pub type printfunc = + unsafe extern "C" fn(arg1: *mut PyObject, arg2: *mut ::libc::FILE, arg3: c_int) -> c_int; + +#[repr(C)] +#[derive(Debug)] +pub struct PyTypeObject { + pub ob_base: object::PyVarObject, + #[cfg(GraalPy)] + pub ob_size: Py_ssize_t, + pub tp_name: *const c_char, + pub tp_basicsize: Py_ssize_t, + pub tp_itemsize: Py_ssize_t, + pub tp_dealloc: Option, + #[cfg(not(Py_3_8))] + pub tp_print: Option, + #[cfg(Py_3_8)] + pub tp_vectorcall_offset: Py_ssize_t, + pub tp_getattr: Option, + pub tp_setattr: Option, + pub tp_as_async: *mut PyAsyncMethods, + pub tp_repr: Option, + pub tp_as_number: *mut PyNumberMethods, + pub tp_as_sequence: *mut PySequenceMethods, + pub tp_as_mapping: *mut PyMappingMethods, + pub tp_hash: Option, + pub tp_call: Option, + pub tp_str: Option, + pub tp_getattro: Option, + pub tp_setattro: Option, + pub tp_as_buffer: *mut PyBufferProcs, + #[cfg(not(Py_GIL_DISABLED))] + pub tp_flags: std::os::raw::c_ulong, + #[cfg(Py_GIL_DISABLED)] + pub tp_flags: crate::impl_::AtomicCULong, + pub tp_doc: *const c_char, + pub tp_traverse: Option, + pub tp_clear: Option, + pub tp_richcompare: Option, + pub tp_weaklistoffset: Py_ssize_t, + pub tp_iter: Option, + pub tp_iternext: Option, + pub tp_methods: *mut PyMethodDef, + pub tp_members: *mut PyMemberDef, + pub tp_getset: *mut PyGetSetDef, + pub tp_base: *mut PyTypeObject, + pub tp_dict: *mut object::PyObject, + pub tp_descr_get: Option, + pub tp_descr_set: Option, + pub tp_dictoffset: Py_ssize_t, + pub tp_init: Option, + pub tp_alloc: Option, + pub tp_new: Option, + pub tp_free: Option, + pub tp_is_gc: Option, + pub tp_bases: *mut object::PyObject, + pub tp_mro: *mut object::PyObject, + pub tp_cache: *mut object::PyObject, + pub tp_subclasses: *mut object::PyObject, + pub tp_weaklist: *mut object::PyObject, + pub tp_del: Option, + pub tp_version_tag: c_uint, + pub tp_finalize: Option, + #[cfg(Py_3_8)] + pub tp_vectorcall: Option, + #[cfg(Py_3_12)] + pub tp_watched: c_char, + #[cfg(any(all(PyPy, Py_3_8, not(Py_3_10)), all(not(PyPy), Py_3_8, not(Py_3_9))))] + pub tp_print: Option, + #[cfg(all(PyPy, not(Py_3_10)))] + pub tp_pypy_flags: std::os::raw::c_long, + #[cfg(py_sys_config = "COUNT_ALLOCS")] + pub tp_allocs: Py_ssize_t, + #[cfg(py_sys_config = "COUNT_ALLOCS")] + pub tp_frees: Py_ssize_t, + #[cfg(py_sys_config = "COUNT_ALLOCS")] + pub tp_maxalloc: Py_ssize_t, + #[cfg(py_sys_config = "COUNT_ALLOCS")] + pub tp_prev: *mut PyTypeObject, + #[cfg(py_sys_config = "COUNT_ALLOCS")] + pub tp_next: *mut PyTypeObject, +} + +#[cfg(Py_3_11)] +#[repr(C)] +#[derive(Clone)] +struct _specialization_cache { + getitem: *mut PyObject, + #[cfg(Py_3_12)] + getitem_version: u32, + #[cfg(Py_3_13)] + init: *mut PyObject, +} + +#[repr(C)] +pub struct PyHeapTypeObject { + pub ht_type: PyTypeObject, + pub as_async: PyAsyncMethods, + pub as_number: PyNumberMethods, + pub as_mapping: PyMappingMethods, + pub as_sequence: PySequenceMethods, + pub as_buffer: PyBufferProcs, + pub ht_name: *mut object::PyObject, + pub ht_slots: *mut object::PyObject, + pub ht_qualname: *mut object::PyObject, + #[cfg(not(PyPy))] + pub ht_cached_keys: *mut c_void, + #[cfg(Py_3_9)] + pub ht_module: *mut object::PyObject, + #[cfg(Py_3_11)] + _ht_tpname: *mut c_char, + #[cfg(Py_3_11)] + _spec_cache: _specialization_cache, +} + +impl Default for PyHeapTypeObject { + #[inline] + fn default() -> Self { + unsafe { mem::zeroed() } + } +} + +#[inline] +#[cfg(not(Py_3_11))] +pub unsafe fn PyHeapType_GET_MEMBERS(etype: *mut PyHeapTypeObject) -> *mut PyMemberDef { + let py_type = object::Py_TYPE(etype as *mut object::PyObject); + let ptr = etype.offset((*py_type).tp_basicsize); + ptr as *mut PyMemberDef +} + +// skipped private _PyType_Name +// skipped private _PyType_Lookup +// skipped private _PyType_LookupRef + +extern "C" { + #[cfg(Py_3_12)] + pub fn PyType_GetDict(o: *mut PyTypeObject) -> *mut PyObject; + + #[cfg_attr(PyPy, link_name = "PyPyObject_Print")] + pub fn PyObject_Print(o: *mut PyObject, fp: *mut ::libc::FILE, flags: c_int) -> c_int; + + // skipped private _Py_BreakPoint + // skipped private _PyObject_Dump + + // skipped _PyObject_GetAttrId + + // skipped private _PyObject_GetDictPtr + pub fn PyObject_CallFinalizer(arg1: *mut PyObject); + #[cfg_attr(PyPy, link_name = "PyPyObject_CallFinalizerFromDealloc")] + pub fn PyObject_CallFinalizerFromDealloc(arg1: *mut PyObject) -> c_int; + + // skipped private _PyObject_GenericGetAttrWithDict + // skipped private _PyObject_GenericSetAttrWithDict + // skipped private _PyObject_FunctionStr +} + +// skipped Py_SETREF +// skipped Py_XSETREF + +// skipped private _PyObject_ASSERT_FROM +// skipped private _PyObject_ASSERT_WITH_MSG +// skipped private _PyObject_ASSERT +// skipped private _PyObject_ASSERT_FAILED_MSG +// skipped private _PyObject_AssertFailed + +// skipped private _PyTrash_begin +// skipped private _PyTrash_end + +// skipped _PyTrash_thread_deposit_object +// skipped _PyTrash_thread_destroy_chain + +// skipped Py_TRASHCAN_BEGIN +// skipped Py_TRASHCAN_END + +// skipped PyObject_GetItemData + +// skipped PyObject_VisitManagedDict +// skipped _PyObject_SetManagedDict +// skipped PyObject_ClearManagedDict + +// skipped TYPE_MAX_WATCHERS + +// skipped PyType_WatchCallback +// skipped PyType_AddWatcher +// skipped PyType_ClearWatcher +// skipped PyType_Watch +// skipped PyType_Unwatch + +// skipped PyUnstable_Type_AssignVersionTag + +// skipped PyRefTracerEvent + +// skipped PyRefTracer +// skipped PyRefTracer_SetTracer +// skipped PyRefTracer_GetTracer diff --git a/include/pyo3/pyo3-ffi/src/cpython/objimpl.rs b/include/pyo3/pyo3-ffi/src/cpython/objimpl.rs new file mode 100644 index 00000000..3e0270dd --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/cpython/objimpl.rs @@ -0,0 +1,71 @@ +use libc::size_t; +use std::os::raw::c_int; + +#[cfg(not(any(PyPy, GraalPy)))] +use std::os::raw::c_void; + +use crate::object::*; + +// skipped _PyObject_SIZE +// skipped _PyObject_VAR_SIZE + +#[cfg(not(Py_3_11))] +extern "C" { + pub fn _Py_GetAllocatedBlocks() -> crate::Py_ssize_t; +} + +#[cfg(not(any(PyPy, GraalPy)))] +#[repr(C)] +#[derive(Copy, Clone)] +pub struct PyObjectArenaAllocator { + pub ctx: *mut c_void, + pub alloc: Option *mut c_void>, + pub free: Option, +} + +#[cfg(not(any(PyPy, GraalPy)))] +impl Default for PyObjectArenaAllocator { + #[inline] + fn default() -> Self { + unsafe { std::mem::zeroed() } + } +} + +extern "C" { + #[cfg(not(any(PyPy, GraalPy)))] + pub fn PyObject_GetArenaAllocator(allocator: *mut PyObjectArenaAllocator); + #[cfg(not(any(PyPy, GraalPy)))] + pub fn PyObject_SetArenaAllocator(allocator: *mut PyObjectArenaAllocator); + + #[cfg(Py_3_9)] + pub fn PyObject_IS_GC(o: *mut PyObject) -> c_int; +} + +#[inline] +#[cfg(not(Py_3_9))] +pub unsafe fn PyObject_IS_GC(o: *mut PyObject) -> c_int { + (crate::PyType_IS_GC(Py_TYPE(o)) != 0 + && match (*Py_TYPE(o)).tp_is_gc { + Some(tp_is_gc) => tp_is_gc(o) != 0, + None => true, + }) as c_int +} + +#[cfg(not(Py_3_11))] +extern "C" { + pub fn _PyObject_GC_Malloc(size: size_t) -> *mut PyObject; + pub fn _PyObject_GC_Calloc(size: size_t) -> *mut PyObject; +} + +#[inline] +pub unsafe fn PyType_SUPPORTS_WEAKREFS(t: *mut PyTypeObject) -> c_int { + ((*t).tp_weaklistoffset > 0) as c_int +} + +#[inline] +pub unsafe fn PyObject_GET_WEAKREFS_LISTPTR(o: *mut PyObject) -> *mut *mut PyObject { + let weaklistoffset = (*Py_TYPE(o)).tp_weaklistoffset; + o.offset(weaklistoffset) as *mut *mut PyObject +} + +// skipped PyUnstable_Object_GC_NewWithExtraData diff --git a/include/pyo3/pyo3-ffi/src/cpython/pydebug.rs b/include/pyo3/pyo3-ffi/src/cpython/pydebug.rs new file mode 100644 index 00000000..a42848e8 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/cpython/pydebug.rs @@ -0,0 +1,72 @@ +use std::os::raw::{c_char, c_int}; + +#[cfg(not(Py_LIMITED_API))] +#[cfg_attr(windows, link(name = "pythonXY"))] +extern "C" { + #[deprecated(note = "Python 3.12")] + #[cfg_attr(PyPy, link_name = "PyPy_DebugFlag")] + pub static mut Py_DebugFlag: c_int; + #[deprecated(note = "Python 3.12")] + #[cfg_attr(PyPy, link_name = "PyPy_VerboseFlag")] + pub static mut Py_VerboseFlag: c_int; + #[deprecated(note = "Python 3.12")] + pub static mut Py_QuietFlag: c_int; + #[deprecated(note = "Python 3.12")] + #[cfg_attr(PyPy, link_name = "PyPy_InteractiveFlag")] + pub static mut Py_InteractiveFlag: c_int; + #[deprecated(note = "Python 3.12")] + #[cfg_attr(PyPy, link_name = "PyPy_InspectFlag")] + pub static mut Py_InspectFlag: c_int; + #[deprecated(note = "Python 3.12")] + #[cfg_attr(PyPy, link_name = "PyPy_OptimizeFlag")] + pub static mut Py_OptimizeFlag: c_int; + #[deprecated(note = "Python 3.12")] + #[cfg_attr(PyPy, link_name = "PyPy_NoSiteFlag")] + pub static mut Py_NoSiteFlag: c_int; + #[deprecated(note = "Python 3.12")] + #[cfg_attr(PyPy, link_name = "PyPy_BytesWarningFlag")] + pub static mut Py_BytesWarningFlag: c_int; + #[deprecated(note = "Python 3.12")] + #[cfg_attr(PyPy, link_name = "PyPy_UseClassExceptionsFlag")] + pub static mut Py_UseClassExceptionsFlag: c_int; + #[deprecated(note = "Python 3.12")] + #[cfg_attr(PyPy, link_name = "PyPy_FrozenFlag")] + pub static mut Py_FrozenFlag: c_int; + #[deprecated(note = "Python 3.12")] + #[cfg_attr(PyPy, link_name = "PyPy_IgnoreEnvironmentFlag")] + pub static mut Py_IgnoreEnvironmentFlag: c_int; + #[deprecated(note = "Python 3.12")] + #[cfg_attr(PyPy, link_name = "PyPy_DontWriteBytecodeFlag")] + pub static mut Py_DontWriteBytecodeFlag: c_int; + #[deprecated(note = "Python 3.12")] + #[cfg_attr(PyPy, link_name = "PyPy_NoUserSiteDirectory")] + pub static mut Py_NoUserSiteDirectory: c_int; + #[deprecated(note = "Python 3.12")] + pub static mut Py_UnbufferedStdioFlag: c_int; + #[cfg_attr(PyPy, link_name = "PyPy_HashRandomizationFlag")] + pub static mut Py_HashRandomizationFlag: c_int; + #[deprecated(note = "Python 3.12")] + pub static mut Py_IsolatedFlag: c_int; + #[cfg(windows)] + #[deprecated(note = "Python 3.12")] + pub static mut Py_LegacyWindowsFSEncodingFlag: c_int; + #[cfg(windows)] + #[deprecated(note = "Python 3.12")] + pub static mut Py_LegacyWindowsStdioFlag: c_int; +} + +extern "C" { + #[cfg(Py_3_11)] + pub fn Py_GETENV(name: *const c_char) -> *mut c_char; +} + +#[cfg(not(Py_3_11))] +#[inline(always)] +pub unsafe fn Py_GETENV(name: *const c_char) -> *mut c_char { + #[allow(deprecated)] + if Py_IgnoreEnvironmentFlag != 0 { + std::ptr::null_mut() + } else { + libc::getenv(name) + } +} diff --git a/include/pyo3/pyo3-ffi/src/cpython/pyerrors.rs b/include/pyo3/pyo3-ffi/src/cpython/pyerrors.rs new file mode 100644 index 00000000..ca08b44a --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/cpython/pyerrors.rs @@ -0,0 +1,188 @@ +use crate::PyObject; +#[cfg(not(any(PyPy, GraalPy)))] +use crate::Py_ssize_t; + +#[repr(C)] +#[derive(Debug)] +pub struct PyBaseExceptionObject { + pub ob_base: PyObject, + #[cfg(not(any(PyPy, GraalPy)))] + pub dict: *mut PyObject, + #[cfg(not(any(PyPy, GraalPy)))] + pub args: *mut PyObject, + #[cfg(all(Py_3_11, not(any(PyPy, GraalPy))))] + pub notes: *mut PyObject, + #[cfg(not(any(PyPy, GraalPy)))] + pub traceback: *mut PyObject, + #[cfg(not(any(PyPy, GraalPy)))] + pub context: *mut PyObject, + #[cfg(not(any(PyPy, GraalPy)))] + pub cause: *mut PyObject, + #[cfg(not(any(PyPy, GraalPy)))] + pub suppress_context: char, +} + +#[cfg(not(any(PyPy, GraalPy)))] +#[repr(C)] +#[derive(Debug)] +pub struct PySyntaxErrorObject { + pub ob_base: PyObject, + pub dict: *mut PyObject, + pub args: *mut PyObject, + #[cfg(Py_3_11)] + pub notes: *mut PyObject, + pub traceback: *mut PyObject, + pub context: *mut PyObject, + pub cause: *mut PyObject, + pub suppress_context: char, + + pub msg: *mut PyObject, + pub filename: *mut PyObject, + pub lineno: *mut PyObject, + pub offset: *mut PyObject, + #[cfg(Py_3_10)] + pub end_lineno: *mut PyObject, + #[cfg(Py_3_10)] + pub end_offset: *mut PyObject, + pub text: *mut PyObject, + pub print_file_and_line: *mut PyObject, +} + +#[cfg(not(any(PyPy, GraalPy)))] +#[repr(C)] +#[derive(Debug)] +pub struct PyImportErrorObject { + pub ob_base: PyObject, + pub dict: *mut PyObject, + pub args: *mut PyObject, + #[cfg(Py_3_11)] + pub notes: *mut PyObject, + pub traceback: *mut PyObject, + pub context: *mut PyObject, + pub cause: *mut PyObject, + pub suppress_context: char, + + pub msg: *mut PyObject, + pub name: *mut PyObject, + pub path: *mut PyObject, + #[cfg(Py_3_12)] + pub name_from: *mut PyObject, +} + +#[cfg(not(any(PyPy, GraalPy)))] +#[repr(C)] +#[derive(Debug)] +pub struct PyUnicodeErrorObject { + pub ob_base: PyObject, + pub dict: *mut PyObject, + pub args: *mut PyObject, + #[cfg(Py_3_11)] + pub notes: *mut PyObject, + pub traceback: *mut PyObject, + pub context: *mut PyObject, + pub cause: *mut PyObject, + pub suppress_context: char, + + pub encoding: *mut PyObject, + pub object: *mut PyObject, + pub start: Py_ssize_t, + pub end: Py_ssize_t, + pub reason: *mut PyObject, +} + +#[cfg(not(any(PyPy, GraalPy)))] +#[repr(C)] +#[derive(Debug)] +pub struct PySystemExitObject { + pub ob_base: PyObject, + pub dict: *mut PyObject, + pub args: *mut PyObject, + #[cfg(Py_3_11)] + pub notes: *mut PyObject, + pub traceback: *mut PyObject, + pub context: *mut PyObject, + pub cause: *mut PyObject, + pub suppress_context: char, + + pub code: *mut PyObject, +} + +#[cfg(not(any(PyPy, GraalPy)))] +#[repr(C)] +#[derive(Debug)] +pub struct PyOSErrorObject { + pub ob_base: PyObject, + pub dict: *mut PyObject, + pub args: *mut PyObject, + #[cfg(Py_3_11)] + pub notes: *mut PyObject, + pub traceback: *mut PyObject, + pub context: *mut PyObject, + pub cause: *mut PyObject, + pub suppress_context: char, + + pub myerrno: *mut PyObject, + pub strerror: *mut PyObject, + pub filename: *mut PyObject, + pub filename2: *mut PyObject, + #[cfg(windows)] + pub winerror: *mut PyObject, + pub written: Py_ssize_t, +} + +#[repr(C)] +#[derive(Debug)] +pub struct PyStopIterationObject { + pub ob_base: PyObject, + #[cfg(not(any(PyPy, GraalPy)))] + pub dict: *mut PyObject, + #[cfg(not(any(PyPy, GraalPy)))] + pub args: *mut PyObject, + #[cfg(all(Py_3_11, not(any(PyPy, GraalPy))))] + pub notes: *mut PyObject, + #[cfg(not(any(PyPy, GraalPy)))] + pub traceback: *mut PyObject, + #[cfg(not(any(PyPy, GraalPy)))] + pub context: *mut PyObject, + #[cfg(not(any(PyPy, GraalPy)))] + pub cause: *mut PyObject, + #[cfg(not(any(PyPy, GraalPy)))] + pub suppress_context: char, + + pub value: *mut PyObject, +} + +// skipped _PyErr_ChainExceptions + +// skipped PyNameErrorObject +// skipped PyAttributeErrorObject + +// skipped PyEnvironmentErrorObject +// skipped PyWindowsErrorObject + +// skipped _PyErr_SetKeyError +// skipped _PyErr_GetTopmostException +// skipped _PyErr_GetExcInfo + +// skipped PyErr_SetFromErrnoWithUnicodeFilename + +// skipped _PyErr_FormatFromCause + +// skipped PyErr_SetFromWindowsErrWithUnicodeFilename +// skipped PyErr_SetExcFromWindowsErrWithUnicodeFilename + +// skipped _PyErr_TrySetFromCause + +// skipped PySignal_SetWakeupFd +// skipped _PyErr_CheckSignals + +// skipped PyErr_SyntaxLocationObject +// skipped PyErr_RangedSyntaxLocationObject +// skipped PyErr_ProgramTextObject + +// skipped _PyErr_ProgramDecodedTextObject +// skipped _PyUnicodeTranslateError_Create +// skipped _PyErr_WriteUnraisableMsg +// skipped _Py_FatalErrorFunc +// skipped _Py_FatalErrorFormat +// skipped Py_FatalError diff --git a/include/pyo3/pyo3-ffi/src/cpython/pyframe.rs b/include/pyo3/pyo3-ffi/src/cpython/pyframe.rs new file mode 100644 index 00000000..d0cfa0a2 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/cpython/pyframe.rs @@ -0,0 +1,2 @@ +#[cfg(Py_3_11)] +opaque_struct!(_PyInterpreterFrame); diff --git a/include/pyo3/pyo3-ffi/src/cpython/pylifecycle.rs b/include/pyo3/pyo3-ffi/src/cpython/pylifecycle.rs new file mode 100644 index 00000000..c259c369 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/cpython/pylifecycle.rs @@ -0,0 +1,99 @@ +use crate::{PyConfig, PyPreConfig, PyStatus, Py_ssize_t}; +use libc::wchar_t; +use std::os::raw::{c_char, c_int}; + +// "private" functions in cpython/pylifecycle.h accepted in PEP 587 +extern "C" { + // skipped _Py_SetStandardStreamEncoding; + pub fn Py_PreInitialize(src_config: *const PyPreConfig) -> PyStatus; + pub fn Py_PreInitializeFromBytesArgs( + src_config: *const PyPreConfig, + argc: Py_ssize_t, + argv: *mut *mut c_char, + ) -> PyStatus; + pub fn Py_PreInitializeFromArgs( + src_config: *const PyPreConfig, + argc: Py_ssize_t, + argv: *mut *mut wchar_t, + ) -> PyStatus; + pub fn _Py_IsCoreInitialized() -> c_int; + + pub fn Py_InitializeFromConfig(config: *const PyConfig) -> PyStatus; + pub fn _Py_InitializeMain() -> PyStatus; + + pub fn Py_RunMain() -> c_int; + + pub fn Py_ExitStatusException(status: PyStatus) -> !; + + // skipped _Py_RestoreSignals + + // skipped Py_FdIsInteractive + // skipped _Py_FdIsInteractive + + // skipped _Py_SetProgramFullPath + + // skipped _Py_gitidentifier + // skipped _Py_getversion + + // skipped _Py_IsFinalizing + + // skipped _PyOS_URandom + // skipped _PyOS_URandomNonblock + + // skipped _Py_CoerceLegacyLocale + // skipped _Py_LegacyLocaleDetected + // skipped _Py_SetLocaleFromEnv + +} + +#[cfg(Py_3_12)] +pub const PyInterpreterConfig_DEFAULT_GIL: c_int = 0; +#[cfg(Py_3_12)] +pub const PyInterpreterConfig_SHARED_GIL: c_int = 1; +#[cfg(Py_3_12)] +pub const PyInterpreterConfig_OWN_GIL: c_int = 2; + +#[cfg(Py_3_12)] +#[repr(C)] +pub struct PyInterpreterConfig { + pub use_main_obmalloc: c_int, + pub allow_fork: c_int, + pub allow_exec: c_int, + pub allow_threads: c_int, + pub allow_daemon_threads: c_int, + pub check_multi_interp_extensions: c_int, + pub gil: c_int, +} + +#[cfg(Py_3_12)] +pub const _PyInterpreterConfig_INIT: PyInterpreterConfig = PyInterpreterConfig { + use_main_obmalloc: 0, + allow_fork: 0, + allow_exec: 0, + allow_threads: 1, + allow_daemon_threads: 0, + check_multi_interp_extensions: 1, + gil: PyInterpreterConfig_OWN_GIL, +}; + +#[cfg(Py_3_12)] +pub const _PyInterpreterConfig_LEGACY_INIT: PyInterpreterConfig = PyInterpreterConfig { + use_main_obmalloc: 1, + allow_fork: 1, + allow_exec: 1, + allow_threads: 1, + allow_daemon_threads: 1, + check_multi_interp_extensions: 0, + gil: PyInterpreterConfig_SHARED_GIL, +}; + +extern "C" { + #[cfg(Py_3_12)] + pub fn Py_NewInterpreterFromConfig( + tstate_p: *mut *mut crate::PyThreadState, + config: *const PyInterpreterConfig, + ) -> PyStatus; +} + +// skipped atexit_datacallbackfunc +// skipped _Py_AtExit diff --git a/include/pyo3/pyo3-ffi/src/cpython/pymem.rs b/include/pyo3/pyo3-ffi/src/cpython/pymem.rs new file mode 100644 index 00000000..c400fa30 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/cpython/pymem.rs @@ -0,0 +1,49 @@ +use libc::size_t; +use std::os::raw::c_void; + +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyMem_RawMalloc")] + pub fn PyMem_RawMalloc(size: size_t) -> *mut c_void; + #[cfg_attr(PyPy, link_name = "PyPyMem_RawCalloc")] + pub fn PyMem_RawCalloc(nelem: size_t, elsize: size_t) -> *mut c_void; + #[cfg_attr(PyPy, link_name = "PyPyMem_RawRealloc")] + pub fn PyMem_RawRealloc(ptr: *mut c_void, new_size: size_t) -> *mut c_void; + #[cfg_attr(PyPy, link_name = "PyPyMem_RawFree")] + pub fn PyMem_RawFree(ptr: *mut c_void); + + // skipped _PyMem_GetCurrentAllocatorName + // skipped _PyMem_RawStrdup + // skipped _PyMem_Strdup + // skipped _PyMem_RawWcsdup +} + +#[repr(C)] +#[derive(Copy, Clone)] +pub enum PyMemAllocatorDomain { + PYMEM_DOMAIN_RAW, + PYMEM_DOMAIN_MEM, + PYMEM_DOMAIN_OBJ, +} + +// skipped PyMemAllocatorName +#[cfg(not(any(PyPy, GraalPy)))] +#[repr(C)] +#[derive(Copy, Clone)] +pub struct PyMemAllocatorEx { + pub ctx: *mut c_void, + pub malloc: Option *mut c_void>, + pub calloc: + Option *mut c_void>, + pub realloc: + Option *mut c_void>, + pub free: Option, +} + +extern "C" { + #[cfg(not(any(PyPy, GraalPy)))] + pub fn PyMem_GetAllocator(domain: PyMemAllocatorDomain, allocator: *mut PyMemAllocatorEx); + #[cfg(not(any(PyPy, GraalPy)))] + pub fn PyMem_SetAllocator(domain: PyMemAllocatorDomain, allocator: *mut PyMemAllocatorEx); + #[cfg(not(any(PyPy, GraalPy)))] + pub fn PyMem_SetupDebugHooks(); +} diff --git a/include/pyo3/pyo3-ffi/src/cpython/pystate.rs b/include/pyo3/pyo3-ffi/src/cpython/pystate.rs new file mode 100644 index 00000000..5481265b --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/cpython/pystate.rs @@ -0,0 +1,112 @@ +#[cfg(not(PyPy))] +use crate::PyThreadState; +use crate::{PyFrameObject, PyInterpreterState, PyObject}; +use std::os::raw::c_int; + +// skipped _PyInterpreterState_RequiresIDRef +// skipped _PyInterpreterState_RequireIDRef + +// skipped _PyInterpreterState_GetMainModule + +pub type Py_tracefunc = unsafe extern "C" fn( + obj: *mut PyObject, + frame: *mut PyFrameObject, + what: c_int, + arg: *mut PyObject, +) -> c_int; + +pub const PyTrace_CALL: c_int = 0; +pub const PyTrace_EXCEPTION: c_int = 1; +pub const PyTrace_LINE: c_int = 2; +pub const PyTrace_RETURN: c_int = 3; +pub const PyTrace_C_CALL: c_int = 4; +pub const PyTrace_C_EXCEPTION: c_int = 5; +pub const PyTrace_C_RETURN: c_int = 6; +pub const PyTrace_OPCODE: c_int = 7; + +// skipped PyTraceInfo +// skipped CFrame + +#[cfg(not(PyPy))] +#[repr(C)] +#[derive(Clone, Copy)] +pub struct _PyErr_StackItem { + #[cfg(not(Py_3_11))] + pub exc_type: *mut PyObject, + pub exc_value: *mut PyObject, + #[cfg(not(Py_3_11))] + pub exc_traceback: *mut PyObject, + pub previous_item: *mut _PyErr_StackItem, +} + +// skipped _PyStackChunk +// skipped _ts (aka PyThreadState) + +extern "C" { + // skipped _PyThreadState_Prealloc + // skipped _PyThreadState_UncheckedGet + // skipped _PyThreadState_GetDict + + #[cfg_attr(PyPy, link_name = "PyPyGILState_Check")] + pub fn PyGILState_Check() -> c_int; + + // skipped _PyGILState_GetInterpreterStateUnsafe + // skipped _PyThread_CurrentFrames + // skipped _PyThread_CurrentExceptions + + #[cfg(not(PyPy))] + pub fn PyInterpreterState_Main() -> *mut PyInterpreterState; + #[cfg_attr(PyPy, link_name = "PyPyInterpreterState_Head")] + pub fn PyInterpreterState_Head() -> *mut PyInterpreterState; + #[cfg_attr(PyPy, link_name = "PyPyInterpreterState_Next")] + pub fn PyInterpreterState_Next(interp: *mut PyInterpreterState) -> *mut PyInterpreterState; + #[cfg(not(PyPy))] + pub fn PyInterpreterState_ThreadHead(interp: *mut PyInterpreterState) -> *mut PyThreadState; + #[cfg(not(PyPy))] + pub fn PyThreadState_Next(tstate: *mut PyThreadState) -> *mut PyThreadState; + + #[cfg_attr(PyPy, link_name = "PyPyThreadState_DeleteCurrent")] + pub fn PyThreadState_DeleteCurrent(); +} + +#[cfg(all(Py_3_9, not(Py_3_11)))] +pub type _PyFrameEvalFunction = extern "C" fn( + *mut crate::PyThreadState, + *mut crate::PyFrameObject, + c_int, +) -> *mut crate::object::PyObject; + +#[cfg(Py_3_11)] +pub type _PyFrameEvalFunction = extern "C" fn( + *mut crate::PyThreadState, + *mut crate::_PyInterpreterFrame, + c_int, +) -> *mut crate::object::PyObject; + +#[cfg(Py_3_9)] +extern "C" { + /// Get the frame evaluation function. + pub fn _PyInterpreterState_GetEvalFrameFunc( + interp: *mut PyInterpreterState, + ) -> _PyFrameEvalFunction; + + ///Set the frame evaluation function. + pub fn _PyInterpreterState_SetEvalFrameFunc( + interp: *mut PyInterpreterState, + eval_frame: _PyFrameEvalFunction, + ); +} + +// skipped _PyInterpreterState_GetConfig +// skipped _PyInterpreterState_GetConfigCopy +// skipped _PyInterpreterState_SetConfig +// skipped _Py_GetConfig + +// skipped _PyCrossInterpreterData +// skipped _PyObject_GetCrossInterpreterData +// skipped _PyCrossInterpreterData_NewObject +// skipped _PyCrossInterpreterData_Release +// skipped _PyObject_CheckCrossInterpreterData +// skipped crossinterpdatafunc +// skipped _PyCrossInterpreterData_RegisterClass +// skipped _PyCrossInterpreterData_Lookup diff --git a/include/pyo3/pyo3-ffi/src/cpython/pythonrun.rs b/include/pyo3/pyo3-ffi/src/cpython/pythonrun.rs new file mode 100644 index 00000000..fe78f55c --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/cpython/pythonrun.rs @@ -0,0 +1,250 @@ +use crate::object::*; +#[cfg(not(any(PyPy, GraalPy, Py_LIMITED_API, Py_3_10)))] +use crate::pyarena::PyArena; +use crate::PyCompilerFlags; +#[cfg(not(any(PyPy, GraalPy, Py_3_10)))] +use crate::{_mod, _node}; +use libc::FILE; +use std::os::raw::{c_char, c_int}; + +extern "C" { + pub fn PyRun_SimpleStringFlags(arg1: *const c_char, arg2: *mut PyCompilerFlags) -> c_int; + pub fn _PyRun_SimpleFileObject( + fp: *mut FILE, + filename: *mut PyObject, + closeit: c_int, + flags: *mut PyCompilerFlags, + ) -> c_int; + pub fn PyRun_AnyFileExFlags( + fp: *mut FILE, + filename: *const c_char, + closeit: c_int, + flags: *mut PyCompilerFlags, + ) -> c_int; + pub fn _PyRun_AnyFileObject( + fp: *mut FILE, + filename: *mut PyObject, + closeit: c_int, + flags: *mut PyCompilerFlags, + ) -> c_int; + pub fn PyRun_SimpleFileExFlags( + fp: *mut FILE, + filename: *const c_char, + closeit: c_int, + flags: *mut PyCompilerFlags, + ) -> c_int; + pub fn PyRun_InteractiveOneFlags( + fp: *mut FILE, + filename: *const c_char, + flags: *mut PyCompilerFlags, + ) -> c_int; + pub fn PyRun_InteractiveOneObject( + fp: *mut FILE, + filename: *mut PyObject, + flags: *mut PyCompilerFlags, + ) -> c_int; + pub fn PyRun_InteractiveLoopFlags( + fp: *mut FILE, + filename: *const c_char, + flags: *mut PyCompilerFlags, + ) -> c_int; + pub fn _PyRun_InteractiveLoopObject( + fp: *mut FILE, + filename: *mut PyObject, + flags: *mut PyCompilerFlags, + ) -> c_int; + + #[cfg(not(any(PyPy, GraalPy, Py_3_10)))] + pub fn PyParser_ASTFromString( + s: *const c_char, + filename: *const c_char, + start: c_int, + flags: *mut PyCompilerFlags, + arena: *mut PyArena, + ) -> *mut _mod; + #[cfg(not(any(PyPy, GraalPy, Py_3_10)))] + pub fn PyParser_ASTFromStringObject( + s: *const c_char, + filename: *mut PyObject, + start: c_int, + flags: *mut PyCompilerFlags, + arena: *mut PyArena, + ) -> *mut _mod; + #[cfg(not(any(PyPy, GraalPy, Py_3_10)))] + pub fn PyParser_ASTFromFile( + fp: *mut FILE, + filename: *const c_char, + enc: *const c_char, + start: c_int, + ps1: *const c_char, + ps2: *const c_char, + flags: *mut PyCompilerFlags, + errcode: *mut c_int, + arena: *mut PyArena, + ) -> *mut _mod; + #[cfg(not(any(PyPy, GraalPy, Py_3_10)))] + pub fn PyParser_ASTFromFileObject( + fp: *mut FILE, + filename: *mut PyObject, + enc: *const c_char, + start: c_int, + ps1: *const c_char, + ps2: *const c_char, + flags: *mut PyCompilerFlags, + errcode: *mut c_int, + arena: *mut PyArena, + ) -> *mut _mod; +} + +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyRun_StringFlags")] + pub fn PyRun_StringFlags( + arg1: *const c_char, + arg2: c_int, + arg3: *mut PyObject, + arg4: *mut PyObject, + arg5: *mut PyCompilerFlags, + ) -> *mut PyObject; + #[cfg(not(any(PyPy, GraalPy)))] + pub fn PyRun_FileExFlags( + fp: *mut FILE, + filename: *const c_char, + start: c_int, + globals: *mut PyObject, + locals: *mut PyObject, + closeit: c_int, + flags: *mut PyCompilerFlags, + ) -> *mut PyObject; + + #[cfg(not(any(PyPy, GraalPy)))] + pub fn Py_CompileStringExFlags( + str: *const c_char, + filename: *const c_char, + start: c_int, + flags: *mut PyCompilerFlags, + optimize: c_int, + ) -> *mut PyObject; + #[cfg(not(Py_LIMITED_API))] + pub fn Py_CompileStringObject( + str: *const c_char, + filename: *mut PyObject, + start: c_int, + flags: *mut PyCompilerFlags, + optimize: c_int, + ) -> *mut PyObject; +} + +#[inline] +#[cfg(not(any(PyPy, GraalPy)))] +pub unsafe fn Py_CompileString(string: *const c_char, p: *const c_char, s: c_int) -> *mut PyObject { + Py_CompileStringExFlags(string, p, s, std::ptr::null_mut(), -1) +} + +#[inline] +#[cfg(not(any(PyPy, GraalPy)))] +pub unsafe fn Py_CompileStringFlags( + string: *const c_char, + p: *const c_char, + s: c_int, + f: *mut PyCompilerFlags, +) -> *mut PyObject { + Py_CompileStringExFlags(string, p, s, f, -1) +} + +// skipped _Py_SourceAsString + +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyRun_String")] + pub fn PyRun_String( + string: *const c_char, + s: c_int, + g: *mut PyObject, + l: *mut PyObject, + ) -> *mut PyObject; + #[cfg(not(any(PyPy, GraalPy)))] + pub fn PyRun_AnyFile(fp: *mut FILE, name: *const c_char) -> c_int; + #[cfg(not(any(PyPy, GraalPy)))] + pub fn PyRun_AnyFileEx(fp: *mut FILE, name: *const c_char, closeit: c_int) -> c_int; + #[cfg(not(any(PyPy, GraalPy)))] + pub fn PyRun_AnyFileFlags( + arg1: *mut FILE, + arg2: *const c_char, + arg3: *mut PyCompilerFlags, + ) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyRun_SimpleString")] + pub fn PyRun_SimpleString(s: *const c_char) -> c_int; + #[cfg(not(any(PyPy, GraalPy)))] + pub fn PyRun_SimpleFile(f: *mut FILE, p: *const c_char) -> c_int; + #[cfg(not(any(PyPy, GraalPy)))] + pub fn PyRun_SimpleFileEx(f: *mut FILE, p: *const c_char, c: c_int) -> c_int; + #[cfg(not(any(PyPy, GraalPy)))] + pub fn PyRun_InteractiveOne(f: *mut FILE, p: *const c_char) -> c_int; + #[cfg(not(any(PyPy, GraalPy)))] + pub fn PyRun_InteractiveLoop(f: *mut FILE, p: *const c_char) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyRun_File")] + pub fn PyRun_File( + fp: *mut FILE, + p: *const c_char, + s: c_int, + g: *mut PyObject, + l: *mut PyObject, + ) -> *mut PyObject; + #[cfg(not(any(PyPy, GraalPy)))] + pub fn PyRun_FileEx( + fp: *mut FILE, + p: *const c_char, + s: c_int, + g: *mut PyObject, + l: *mut PyObject, + c: c_int, + ) -> *mut PyObject; + #[cfg(not(any(PyPy, GraalPy)))] + pub fn PyRun_FileFlags( + fp: *mut FILE, + p: *const c_char, + s: c_int, + g: *mut PyObject, + l: *mut PyObject, + flags: *mut PyCompilerFlags, + ) -> *mut PyObject; +} + +// skipped macro PyRun_String +// skipped macro PyRun_AnyFile +// skipped macro PyRun_AnyFileEx +// skipped macro PyRun_AnyFileFlags + +extern "C" { + #[cfg(not(any(PyPy, GraalPy, Py_3_10)))] + #[cfg_attr(Py_3_9, deprecated(note = "Python 3.9"))] + pub fn PyParser_SimpleParseStringFlags( + arg1: *const c_char, + arg2: c_int, + arg3: c_int, + ) -> *mut _node; + #[cfg(not(any(PyPy, GraalPy, Py_3_10)))] + #[cfg_attr(Py_3_9, deprecated(note = "Python 3.9"))] + pub fn PyParser_SimpleParseStringFlagsFilename( + arg1: *const c_char, + arg2: *const c_char, + arg3: c_int, + arg4: c_int, + ) -> *mut _node; + #[cfg(not(any(PyPy, GraalPy, Py_3_10)))] + #[cfg_attr(Py_3_9, deprecated(note = "Python 3.9"))] + pub fn PyParser_SimpleParseFileFlags( + arg1: *mut FILE, + arg2: *const c_char, + arg3: c_int, + arg4: c_int, + ) -> *mut _node; + + #[cfg(PyPy)] + #[cfg_attr(PyPy, link_name = "PyPy_CompileStringFlags")] + pub fn Py_CompileStringFlags( + string: *const c_char, + p: *const c_char, + s: c_int, + f: *mut PyCompilerFlags, + ) -> *mut PyObject; +} diff --git a/include/pyo3/pyo3-ffi/src/cpython/tupleobject.rs b/include/pyo3/pyo3-ffi/src/cpython/tupleobject.rs new file mode 100644 index 00000000..1d988d2b --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/cpython/tupleobject.rs @@ -0,0 +1,37 @@ +use crate::object::*; +#[cfg(not(PyPy))] +use crate::pyport::Py_ssize_t; + +#[repr(C)] +pub struct PyTupleObject { + pub ob_base: PyVarObject, + #[cfg(not(GraalPy))] + pub ob_item: [*mut PyObject; 1], +} + +// skipped _PyTuple_Resize +// skipped _PyTuple_MaybeUntrack + +// skipped _PyTuple_CAST + +/// Macro, trading safety for speed +#[inline] +#[cfg(not(PyPy))] +pub unsafe fn PyTuple_GET_SIZE(op: *mut PyObject) -> Py_ssize_t { + Py_SIZE(op) +} + +#[inline] +#[cfg(not(any(PyPy, GraalPy)))] +pub unsafe fn PyTuple_GET_ITEM(op: *mut PyObject, i: Py_ssize_t) -> *mut PyObject { + *(*(op as *mut PyTupleObject)).ob_item.as_ptr().offset(i) +} + +/// Macro, *only* to be used to fill in brand new tuples +#[inline] +#[cfg(not(any(PyPy, GraalPy)))] +pub unsafe fn PyTuple_SET_ITEM(op: *mut PyObject, i: Py_ssize_t, v: *mut PyObject) { + *(*(op as *mut PyTupleObject)).ob_item.as_mut_ptr().offset(i) = v; +} + +// skipped _PyTuple_DebugMallocStats diff --git a/include/pyo3/pyo3-ffi/src/cpython/unicodeobject.rs b/include/pyo3/pyo3-ffi/src/cpython/unicodeobject.rs new file mode 100644 index 00000000..1414b4ce --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/cpython/unicodeobject.rs @@ -0,0 +1,789 @@ +#[cfg(not(any(PyPy, GraalPy)))] +use crate::Py_hash_t; +use crate::{PyObject, Py_UCS1, Py_UCS2, Py_UCS4, Py_ssize_t}; +use libc::wchar_t; +use std::os::raw::{c_char, c_int, c_uint, c_void}; + +// skipped Py_UNICODE_ISSPACE() +// skipped Py_UNICODE_ISLOWER() +// skipped Py_UNICODE_ISUPPER() +// skipped Py_UNICODE_ISTITLE() +// skipped Py_UNICODE_ISLINEBREAK +// skipped Py_UNICODE_TOLOWER +// skipped Py_UNICODE_TOUPPER +// skipped Py_UNICODE_TOTITLE +// skipped Py_UNICODE_ISDECIMAL +// skipped Py_UNICODE_ISDIGIT +// skipped Py_UNICODE_ISNUMERIC +// skipped Py_UNICODE_ISPRINTABLE +// skipped Py_UNICODE_TODECIMAL +// skipped Py_UNICODE_TODIGIT +// skipped Py_UNICODE_TONUMERIC +// skipped Py_UNICODE_ISALPHA +// skipped Py_UNICODE_ISALNUM +// skipped Py_UNICODE_COPY +// skipped Py_UNICODE_FILL +// skipped Py_UNICODE_IS_SURROGATE +// skipped Py_UNICODE_IS_HIGH_SURROGATE +// skipped Py_UNICODE_IS_LOW_SURROGATE +// skipped Py_UNICODE_JOIN_SURROGATES +// skipped Py_UNICODE_HIGH_SURROGATE +// skipped Py_UNICODE_LOW_SURROGATE + +// generated by bindgen v0.63.0 (with small adaptations) +#[repr(C)] +struct BitfieldUnit { + storage: Storage, +} + +impl BitfieldUnit { + #[inline] + pub const fn new(storage: Storage) -> Self { + Self { storage } + } +} + +#[cfg(not(GraalPy))] +impl BitfieldUnit +where + Storage: AsRef<[u8]> + AsMut<[u8]>, +{ + #[inline] + fn get_bit(&self, index: usize) -> bool { + debug_assert!(index / 8 < self.storage.as_ref().len()); + let byte_index = index / 8; + let byte = self.storage.as_ref()[byte_index]; + let bit_index = if cfg!(target_endian = "big") { + 7 - (index % 8) + } else { + index % 8 + }; + let mask = 1 << bit_index; + byte & mask == mask + } + + #[inline] + fn set_bit(&mut self, index: usize, val: bool) { + debug_assert!(index / 8 < self.storage.as_ref().len()); + let byte_index = index / 8; + let byte = &mut self.storage.as_mut()[byte_index]; + let bit_index = if cfg!(target_endian = "big") { + 7 - (index % 8) + } else { + index % 8 + }; + let mask = 1 << bit_index; + if val { + *byte |= mask; + } else { + *byte &= !mask; + } + } + + #[inline] + fn get(&self, bit_offset: usize, bit_width: u8) -> u64 { + debug_assert!(bit_width <= 64); + debug_assert!(bit_offset / 8 < self.storage.as_ref().len()); + debug_assert!((bit_offset + (bit_width as usize)) / 8 <= self.storage.as_ref().len()); + let mut val = 0; + for i in 0..(bit_width as usize) { + if self.get_bit(i + bit_offset) { + let index = if cfg!(target_endian = "big") { + bit_width as usize - 1 - i + } else { + i + }; + val |= 1 << index; + } + } + val + } + + #[inline] + fn set(&mut self, bit_offset: usize, bit_width: u8, val: u64) { + debug_assert!(bit_width <= 64); + debug_assert!(bit_offset / 8 < self.storage.as_ref().len()); + debug_assert!((bit_offset + (bit_width as usize)) / 8 <= self.storage.as_ref().len()); + for i in 0..(bit_width as usize) { + let mask = 1 << i; + let val_bit_is_set = val & mask == mask; + let index = if cfg!(target_endian = "big") { + bit_width as usize - 1 - i + } else { + i + }; + self.set_bit(index + bit_offset, val_bit_is_set); + } + } +} + +#[cfg(not(GraalPy))] +const STATE_INTERNED_INDEX: usize = 0; +#[cfg(not(GraalPy))] +const STATE_INTERNED_WIDTH: u8 = 2; + +#[cfg(not(GraalPy))] +const STATE_KIND_INDEX: usize = STATE_INTERNED_WIDTH as usize; +#[cfg(not(GraalPy))] +const STATE_KIND_WIDTH: u8 = 3; + +#[cfg(not(GraalPy))] +const STATE_COMPACT_INDEX: usize = (STATE_INTERNED_WIDTH + STATE_KIND_WIDTH) as usize; +#[cfg(not(GraalPy))] +const STATE_COMPACT_WIDTH: u8 = 1; + +#[cfg(not(GraalPy))] +const STATE_ASCII_INDEX: usize = + (STATE_INTERNED_WIDTH + STATE_KIND_WIDTH + STATE_COMPACT_WIDTH) as usize; +#[cfg(not(GraalPy))] +const STATE_ASCII_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; +#[cfg(not(any(Py_3_12, GraalPy)))] +const STATE_READY_WIDTH: u8 = 1; + +// generated by bindgen v0.63.0 (with small adaptations) +// The same code is generated for Python 3.7, 3.8, 3.9, 3.10, and 3.11, but the "ready" field +// has been removed from Python 3.12. + +/// Wrapper around the `PyASCIIObject.state` bitfield with getters and setters that work +/// on most little- and big-endian architectures. +/// +/// 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. +#[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))] +#[allow(clippy::useless_transmute)] +impl PyASCIIObjectState { + #[inline] + unsafe fn interned(&self) -> c_uint { + std::mem::transmute( + self.bitfield + .get(STATE_INTERNED_INDEX, STATE_INTERNED_WIDTH) as u32, + ) + } + + #[inline] + unsafe fn set_interned(&mut self, val: c_uint) { + let val: u32 = std::mem::transmute(val); + self.bitfield + .set(STATE_INTERNED_INDEX, STATE_INTERNED_WIDTH, val as u64) + } + + #[inline] + unsafe fn kind(&self) -> c_uint { + std::mem::transmute(self.bitfield.get(STATE_KIND_INDEX, STATE_KIND_WIDTH) as u32) + } + + #[inline] + unsafe fn set_kind(&mut self, val: c_uint) { + let val: u32 = std::mem::transmute(val); + self.bitfield + .set(STATE_KIND_INDEX, STATE_KIND_WIDTH, val as u64) + } + + #[inline] + unsafe fn compact(&self) -> c_uint { + std::mem::transmute(self.bitfield.get(STATE_COMPACT_INDEX, STATE_COMPACT_WIDTH) as u32) + } + + #[inline] + unsafe fn set_compact(&mut self, val: c_uint) { + let val: u32 = std::mem::transmute(val); + self.bitfield + .set(STATE_COMPACT_INDEX, STATE_COMPACT_WIDTH, val as u64) + } + + #[inline] + unsafe fn ascii(&self) -> c_uint { + std::mem::transmute(self.bitfield.get(STATE_ASCII_INDEX, STATE_ASCII_WIDTH) as u32) + } + + #[inline] + unsafe fn set_ascii(&mut self, val: c_uint) { + let val: u32 = std::mem::transmute(val); + self.bitfield + .set(STATE_ASCII_INDEX, STATE_ASCII_WIDTH, val as u64) + } + + #[cfg(not(Py_3_12))] + #[inline] + unsafe fn ready(&self) -> c_uint { + std::mem::transmute(self.bitfield.get(STATE_READY_INDEX, STATE_READY_WIDTH) as u32) + } + + #[cfg(not(Py_3_12))] + #[inline] + unsafe fn set_ready(&mut self, val: c_uint) { + let val: u32 = std::mem::transmute(val); + self.bitfield + .set(STATE_READY_INDEX, STATE_READY_WIDTH, val as u64) + } +} + +impl From for PyASCIIObjectState { + #[inline] + fn from(value: u32) -> Self { + PyASCIIObjectState { + bitfield_align: [], + bitfield: BitfieldUnit::new(value.to_ne_bytes()), + } + } +} + +impl From for u32 { + #[inline] + fn from(value: PyASCIIObjectState) -> Self { + u32::from_ne_bytes(value.bitfield.storage) + } +} + +#[repr(C)] +pub struct PyASCIIObject { + pub ob_base: PyObject, + #[cfg(not(GraalPy))] + pub length: Py_ssize_t, + #[cfg(not(any(PyPy, GraalPy)))] + pub hash: Py_hash_t, + /// A bit field with various properties. + /// + /// Rust doesn't expose bitfields. So we have accessor functions for + /// retrieving values. + /// + /// 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; + #[cfg(not(GraalPy))] + pub state: u32, + #[cfg(not(any(Py_3_12, GraalPy)))] + pub wstr: *mut wchar_t, +} + +/// Interacting with the bitfield is not actually well-defined, so we mark these APIs unsafe. +#[cfg(not(GraalPy))] +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. + /// + /// Returns one of: [`SSTATE_NOT_INTERNED`], [`SSTATE_INTERNED_MORTAL`], + /// [`SSTATE_INTERNED_IMMORTAL`], or [`SSTATE_INTERNED_IMMORTAL_STATIC`]. + #[inline] + pub unsafe fn interned(&self) -> c_uint { + PyASCIIObjectState::from(self.state).interned() + } + + #[cfg_attr(not(Py_3_12), allow(rustdoc::broken_intra_doc_links))] // SSTATE_INTERNED_IMMORTAL_STATIC requires 3.12 + /// Set the `interned` field of the [`PyASCIIObject`] state bitfield. + /// + /// Calling this function with an argument that is not [`SSTATE_NOT_INTERNED`], + /// [`SSTATE_INTERNED_MORTAL`], [`SSTATE_INTERNED_IMMORTAL`], or + /// [`SSTATE_INTERNED_IMMORTAL_STATIC`] is invalid. + #[inline] + pub unsafe fn set_interned(&mut self, val: c_uint) { + let mut state = PyASCIIObjectState::from(self.state); + state.set_interned(val); + self.state = u32::from(state); + } + + /// Get the `kind` field of the [`PyASCIIObject`] state bitfield. + /// + /// Returns one of: + #[cfg_attr(not(Py_3_12), doc = "[`PyUnicode_WCHAR_KIND`], ")] + /// [`PyUnicode_1BYTE_KIND`], [`PyUnicode_2BYTE_KIND`], or [`PyUnicode_4BYTE_KIND`]. + #[inline] + pub unsafe fn kind(&self) -> c_uint { + PyASCIIObjectState::from(self.state).kind() + } + + /// Set the `kind` field of the [`PyASCIIObject`] state bitfield. + /// + /// Calling this function with an argument that is not + #[cfg_attr(not(Py_3_12), doc = "[`PyUnicode_WCHAR_KIND`], ")] + /// [`PyUnicode_1BYTE_KIND`], [`PyUnicode_2BYTE_KIND`], or [`PyUnicode_4BYTE_KIND`] is invalid. + #[inline] + pub unsafe fn set_kind(&mut self, val: c_uint) { + let mut state = PyASCIIObjectState::from(self.state); + state.set_kind(val); + self.state = u32::from(state); + } + + /// Get the `compact` field of the [`PyASCIIObject`] state bitfield. + /// + /// Returns either `0` or `1`. + #[inline] + pub unsafe fn compact(&self) -> c_uint { + PyASCIIObjectState::from(self.state).compact() + } + + /// Set the `compact` flag of the [`PyASCIIObject`] state bitfield. + /// + /// Calling this function with an argument that is neither `0` nor `1` is invalid. + #[inline] + pub unsafe fn set_compact(&mut self, val: c_uint) { + let mut state = PyASCIIObjectState::from(self.state); + state.set_compact(val); + self.state = u32::from(state); + } + + /// Get the `ascii` field of the [`PyASCIIObject`] state bitfield. + /// + /// Returns either `0` or `1`. + #[inline] + pub unsafe fn ascii(&self) -> c_uint { + PyASCIIObjectState::from(self.state).ascii() + } + + /// Set the `ascii` flag of the [`PyASCIIObject`] state bitfield. + /// + /// Calling this function with an argument that is neither `0` nor `1` is invalid. + #[inline] + pub unsafe fn set_ascii(&mut self, val: c_uint) { + let mut state = PyASCIIObjectState::from(self.state); + state.set_ascii(val); + self.state = u32::from(state); + } + + /// Get the `ready` field of the [`PyASCIIObject`] state bitfield. + /// + /// Returns either `0` or `1`. + #[cfg(not(Py_3_12))] + #[inline] + pub unsafe fn ready(&self) -> c_uint { + PyASCIIObjectState::from(self.state).ready() + } + + /// Set the `ready` flag of the [`PyASCIIObject`] state bitfield. + /// + /// Calling this function with an argument that is neither `0` nor `1` is invalid. + #[cfg(not(Py_3_12))] + #[inline] + pub unsafe fn set_ready(&mut self, val: c_uint) { + let mut state = PyASCIIObjectState::from(self.state); + state.set_ready(val); + self.state = u32::from(state); + } +} + +#[repr(C)] +pub struct PyCompactUnicodeObject { + pub _base: PyASCIIObject, + #[cfg(not(GraalPy))] + pub utf8_length: Py_ssize_t, + #[cfg(not(GraalPy))] + pub utf8: *mut c_char, + #[cfg(not(any(Py_3_12, GraalPy)))] + pub wstr_length: Py_ssize_t, +} + +#[repr(C)] +pub union PyUnicodeObjectData { + pub any: *mut c_void, + pub latin1: *mut Py_UCS1, + pub ucs2: *mut Py_UCS2, + pub ucs4: *mut Py_UCS4, +} + +#[repr(C)] +pub struct PyUnicodeObject { + pub _base: PyCompactUnicodeObject, + #[cfg(not(GraalPy))] + pub data: PyUnicodeObjectData, +} + +extern "C" { + #[cfg(not(any(PyPy, GraalPy)))] + pub fn _PyUnicode_CheckConsistency(op: *mut PyObject, check_content: c_int) -> c_int; +} + +// skipped PyUnicode_GET_SIZE +// skipped PyUnicode_GET_DATA_SIZE +// skipped PyUnicode_AS_UNICODE +// skipped PyUnicode_AS_DATA + +pub const SSTATE_NOT_INTERNED: c_uint = 0; +pub const SSTATE_INTERNED_MORTAL: c_uint = 1; +pub const SSTATE_INTERNED_IMMORTAL: c_uint = 2; +#[cfg(Py_3_12)] +pub const SSTATE_INTERNED_IMMORTAL_STATIC: c_uint = 3; + +#[cfg(not(GraalPy))] +#[inline] +pub unsafe fn PyUnicode_IS_ASCII(op: *mut PyObject) -> c_uint { + debug_assert!(crate::PyUnicode_Check(op) != 0); + #[cfg(not(Py_3_12))] + debug_assert!(PyUnicode_IS_READY(op) != 0); + + (*(op as *mut PyASCIIObject)).ascii() +} + +#[cfg(not(GraalPy))] +#[inline] +pub unsafe fn PyUnicode_IS_COMPACT(op: *mut PyObject) -> c_uint { + (*(op as *mut PyASCIIObject)).compact() +} + +#[cfg(not(GraalPy))] +#[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() +} + +#[cfg(not(Py_3_12))] +#[deprecated(note = "Removed in Python 3.12")] +pub const PyUnicode_WCHAR_KIND: c_uint = 0; + +pub const PyUnicode_1BYTE_KIND: c_uint = 1; +pub const PyUnicode_2BYTE_KIND: c_uint = 2; +pub const PyUnicode_4BYTE_KIND: c_uint = 4; + +#[cfg(not(any(GraalPy, PyPy)))] +#[inline] +pub unsafe fn PyUnicode_1BYTE_DATA(op: *mut PyObject) -> *mut Py_UCS1 { + PyUnicode_DATA(op) as *mut Py_UCS1 +} + +#[cfg(not(any(GraalPy, PyPy)))] +#[inline] +pub unsafe fn PyUnicode_2BYTE_DATA(op: *mut PyObject) -> *mut Py_UCS2 { + PyUnicode_DATA(op) as *mut Py_UCS2 +} + +#[cfg(not(any(GraalPy, PyPy)))] +#[inline] +pub unsafe fn PyUnicode_4BYTE_DATA(op: *mut PyObject) -> *mut Py_UCS4 { + PyUnicode_DATA(op) as *mut Py_UCS4 +} + +#[cfg(not(GraalPy))] +#[inline] +pub unsafe fn PyUnicode_KIND(op: *mut PyObject) -> c_uint { + debug_assert!(crate::PyUnicode_Check(op) != 0); + #[cfg(not(Py_3_12))] + debug_assert!(PyUnicode_IS_READY(op) != 0); + + (*(op as *mut PyASCIIObject)).kind() +} + +#[cfg(not(GraalPy))] +#[inline] +pub unsafe fn _PyUnicode_COMPACT_DATA(op: *mut PyObject) -> *mut c_void { + if PyUnicode_IS_ASCII(op) != 0 { + (op as *mut PyASCIIObject).offset(1) as *mut c_void + } else { + (op as *mut PyCompactUnicodeObject).offset(1) as *mut c_void + } +} + +#[cfg(not(any(GraalPy, PyPy)))] +#[inline] +pub unsafe fn _PyUnicode_NONCOMPACT_DATA(op: *mut PyObject) -> *mut c_void { + debug_assert!(!(*(op as *mut PyUnicodeObject)).data.any.is_null()); + + (*(op as *mut PyUnicodeObject)).data.any +} + +#[cfg(not(any(GraalPy, PyPy)))] +#[inline] +pub unsafe fn PyUnicode_DATA(op: *mut PyObject) -> *mut c_void { + debug_assert!(crate::PyUnicode_Check(op) != 0); + + if PyUnicode_IS_COMPACT(op) != 0 { + _PyUnicode_COMPACT_DATA(op) + } else { + _PyUnicode_NONCOMPACT_DATA(op) + } +} + +// skipped PyUnicode_WRITE +// skipped PyUnicode_READ +// skipped PyUnicode_READ_CHAR + +#[cfg(not(GraalPy))] +#[inline] +pub unsafe fn PyUnicode_GET_LENGTH(op: *mut PyObject) -> Py_ssize_t { + debug_assert!(crate::PyUnicode_Check(op) != 0); + #[cfg(not(Py_3_12))] + debug_assert!(PyUnicode_IS_READY(op) != 0); + + (*(op as *mut PyASCIIObject)).length +} + +#[cfg(any(Py_3_12, GraalPy))] +#[inline] +pub unsafe fn PyUnicode_IS_READY(_op: *mut PyObject) -> c_uint { + // kept in CPython for backwards compatibility + 1 +} + +#[cfg(not(any(GraalPy, Py_3_12)))] +#[inline] +pub unsafe fn PyUnicode_IS_READY(op: *mut PyObject) -> c_uint { + (*(op as *mut PyASCIIObject)).ready() +} + +#[cfg(any(Py_3_12, GraalPy))] +#[inline] +pub unsafe fn PyUnicode_READY(_op: *mut PyObject) -> c_int { + 0 +} + +#[cfg(not(any(Py_3_12, GraalPy)))] +#[inline] +pub unsafe fn PyUnicode_READY(op: *mut PyObject) -> c_int { + debug_assert!(crate::PyUnicode_Check(op) != 0); + + if PyUnicode_IS_READY(op) != 0 { + 0 + } else { + _PyUnicode_Ready(op) + } +} + +// skipped PyUnicode_MAX_CHAR_VALUE +// skipped _PyUnicode_get_wstr_length +// skipped PyUnicode_WSTR_LENGTH + +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyUnicode_New")] + pub fn PyUnicode_New(size: Py_ssize_t, maxchar: Py_UCS4) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "_PyPyUnicode_Ready")] + pub fn _PyUnicode_Ready(unicode: *mut PyObject) -> c_int; + + // skipped _PyUnicode_Copy + + #[cfg(not(PyPy))] + pub fn PyUnicode_CopyCharacters( + to: *mut PyObject, + to_start: Py_ssize_t, + from: *mut PyObject, + from_start: Py_ssize_t, + how_many: Py_ssize_t, + ) -> Py_ssize_t; + + // skipped _PyUnicode_FastCopyCharacters + + #[cfg(not(PyPy))] + pub fn PyUnicode_Fill( + unicode: *mut PyObject, + start: Py_ssize_t, + length: Py_ssize_t, + fill_char: Py_UCS4, + ) -> Py_ssize_t; + + // skipped _PyUnicode_FastFill + + #[cfg(not(Py_3_12))] + #[deprecated] + #[cfg_attr(PyPy, link_name = "PyPyUnicode_FromUnicode")] + pub fn PyUnicode_FromUnicode(u: *const wchar_t, size: Py_ssize_t) -> *mut PyObject; + + #[cfg_attr(PyPy, link_name = "PyPyUnicode_FromKindAndData")] + pub fn PyUnicode_FromKindAndData( + kind: c_int, + buffer: *const c_void, + size: Py_ssize_t, + ) -> *mut PyObject; + + // skipped _PyUnicode_FromASCII + // skipped _PyUnicode_FindMaxChar + + #[cfg(not(Py_3_12))] + #[deprecated] + #[cfg_attr(PyPy, link_name = "PyPyUnicode_AsUnicode")] + pub fn PyUnicode_AsUnicode(unicode: *mut PyObject) -> *mut wchar_t; + + // skipped _PyUnicode_AsUnicode + + #[cfg(not(Py_3_12))] + #[deprecated] + #[cfg_attr(PyPy, link_name = "PyPyUnicode_AsUnicodeAndSize")] + pub fn PyUnicode_AsUnicodeAndSize( + unicode: *mut PyObject, + size: *mut Py_ssize_t, + ) -> *mut wchar_t; + + // skipped PyUnicode_GetMax +} + +// skipped _PyUnicodeWriter +// skipped _PyUnicodeWriter_Init +// skipped _PyUnicodeWriter_Prepare +// skipped _PyUnicodeWriter_PrepareInternal +// skipped _PyUnicodeWriter_PrepareKind +// skipped _PyUnicodeWriter_PrepareKindInternal +// skipped _PyUnicodeWriter_WriteChar +// skipped _PyUnicodeWriter_WriteStr +// skipped _PyUnicodeWriter_WriteSubstring +// skipped _PyUnicodeWriter_WriteASCIIString +// skipped _PyUnicodeWriter_WriteLatin1String +// skipped _PyUnicodeWriter_Finish +// skipped _PyUnicodeWriter_Dealloc +// skipped _PyUnicode_FormatAdvancedWriter + +extern "C" { + // skipped _PyUnicode_AsStringAndSize + + #[cfg_attr(PyPy, link_name = "PyPyUnicode_AsUTF8")] + pub fn PyUnicode_AsUTF8(unicode: *mut PyObject) -> *const c_char; + + // skipped _PyUnicode_AsString + + pub fn PyUnicode_Encode( + s: *const wchar_t, + size: Py_ssize_t, + encoding: *const c_char, + errors: *const c_char, + ) -> *mut PyObject; + + pub fn PyUnicode_EncodeUTF7( + data: *const wchar_t, + length: Py_ssize_t, + base64SetO: c_int, + base64WhiteSpace: c_int, + errors: *const c_char, + ) -> *mut PyObject; + + // skipped _PyUnicode_EncodeUTF7 + // skipped _PyUnicode_AsUTF8String + + #[cfg_attr(PyPy, link_name = "PyPyUnicode_EncodeUTF8")] + pub fn PyUnicode_EncodeUTF8( + data: *const wchar_t, + length: Py_ssize_t, + errors: *const c_char, + ) -> *mut PyObject; + + pub fn PyUnicode_EncodeUTF32( + data: *const wchar_t, + length: Py_ssize_t, + errors: *const c_char, + byteorder: c_int, + ) -> *mut PyObject; + + // skipped _PyUnicode_EncodeUTF32 + + pub fn PyUnicode_EncodeUTF16( + data: *const wchar_t, + length: Py_ssize_t, + errors: *const c_char, + byteorder: c_int, + ) -> *mut PyObject; + + // skipped _PyUnicode_EncodeUTF16 + // skipped _PyUnicode_DecodeUnicodeEscape + + pub fn PyUnicode_EncodeUnicodeEscape(data: *const wchar_t, length: Py_ssize_t) + -> *mut PyObject; + + pub fn PyUnicode_EncodeRawUnicodeEscape( + data: *const wchar_t, + length: Py_ssize_t, + ) -> *mut PyObject; + + // skipped _PyUnicode_AsLatin1String + + #[cfg_attr(PyPy, link_name = "PyPyUnicode_EncodeLatin1")] + pub fn PyUnicode_EncodeLatin1( + data: *const wchar_t, + length: Py_ssize_t, + errors: *const c_char, + ) -> *mut PyObject; + + // skipped _PyUnicode_AsASCIIString + + #[cfg_attr(PyPy, link_name = "PyPyUnicode_EncodeASCII")] + pub fn PyUnicode_EncodeASCII( + data: *const wchar_t, + length: Py_ssize_t, + errors: *const c_char, + ) -> *mut PyObject; + + pub fn PyUnicode_EncodeCharmap( + data: *const wchar_t, + length: Py_ssize_t, + mapping: *mut PyObject, + errors: *const c_char, + ) -> *mut PyObject; + + // skipped _PyUnicode_EncodeCharmap + + pub fn PyUnicode_TranslateCharmap( + data: *const wchar_t, + length: Py_ssize_t, + table: *mut PyObject, + errors: *const c_char, + ) -> *mut PyObject; + + // skipped PyUnicode_EncodeMBCS + + #[cfg_attr(PyPy, link_name = "PyPyUnicode_EncodeDecimal")] + pub fn PyUnicode_EncodeDecimal( + s: *mut wchar_t, + length: Py_ssize_t, + output: *mut c_char, + errors: *const c_char, + ) -> c_int; + + #[cfg_attr(PyPy, link_name = "PyPyUnicode_TransformDecimalToASCII")] + pub fn PyUnicode_TransformDecimalToASCII(s: *mut wchar_t, length: Py_ssize_t) -> *mut PyObject; + + // skipped _PyUnicode_TransformDecimalAndSpaceToASCII +} + +// skipped _PyUnicode_JoinArray +// skipped _PyUnicode_EqualToASCIIId +// skipped _PyUnicode_EqualToASCIIString +// skipped _PyUnicode_XStrip +// skipped _PyUnicode_InsertThousandsGrouping + +// skipped _Py_ascii_whitespace + +// skipped _PyUnicode_IsLowercase +// skipped _PyUnicode_IsUppercase +// skipped _PyUnicode_IsTitlecase +// skipped _PyUnicode_IsXidStart +// skipped _PyUnicode_IsXidContinue +// skipped _PyUnicode_IsWhitespace +// skipped _PyUnicode_IsLinebreak +// skipped _PyUnicode_ToLowercase +// skipped _PyUnicode_ToUppercase +// skipped _PyUnicode_ToTitlecase +// skipped _PyUnicode_ToLowerFull +// skipped _PyUnicode_ToTitleFull +// skipped _PyUnicode_ToUpperFull +// skipped _PyUnicode_ToFoldedFull +// skipped _PyUnicode_IsCaseIgnorable +// skipped _PyUnicode_IsCased +// skipped _PyUnicode_ToDecimalDigit +// skipped _PyUnicode_ToDigit +// skipped _PyUnicode_ToNumeric +// skipped _PyUnicode_IsDecimalDigit +// skipped _PyUnicode_IsDigit +// skipped _PyUnicode_IsNumeric +// skipped _PyUnicode_IsPrintable +// skipped _PyUnicode_IsAlpha +// skipped Py_UNICODE_strlen +// skipped Py_UNICODE_strcpy +// skipped Py_UNICODE_strcat +// skipped Py_UNICODE_strncpy +// skipped Py_UNICODE_strcmp +// skipped Py_UNICODE_strncmp +// skipped Py_UNICODE_strchr +// skipped Py_UNICODE_strrchr +// skipped _PyUnicode_FormatLong +// skipped PyUnicode_AsUnicodeCopy +// skipped _PyUnicode_FromId +// skipped _PyUnicode_EQ +// skipped _PyUnicode_ScanIdentifier diff --git a/include/pyo3/pyo3-ffi/src/cpython/weakrefobject.rs b/include/pyo3/pyo3-ffi/src/cpython/weakrefobject.rs new file mode 100644 index 00000000..88bb501b --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/cpython/weakrefobject.rs @@ -0,0 +1,17 @@ +#[cfg(not(any(PyPy, GraalPy)))] +pub struct _PyWeakReference { + pub ob_base: crate::PyObject, + pub wr_object: *mut crate::PyObject, + pub wr_callback: *mut crate::PyObject, + pub hash: crate::Py_hash_t, + pub wr_prev: *mut crate::PyWeakReference, + pub wr_next: *mut crate::PyWeakReference, + #[cfg(Py_3_11)] + pub vectorcall: Option, + #[cfg(all(Py_3_13, Py_GIL_DISABLED))] + pub weakrefs_lock: *mut crate::PyMutex, +} + +// skipped _PyWeakref_GetWeakrefCount +// skipped _PyWeakref_ClearRef +// skipped PyWeakRef_GET_OBJECT diff --git a/include/pyo3/pyo3-ffi/src/datetime.rs b/include/pyo3/pyo3-ffi/src/datetime.rs new file mode 100644 index 00000000..7283b6d4 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/datetime.rs @@ -0,0 +1,746 @@ +//! FFI bindings to the functions and structs defined in `datetime.h` +//! +//! This is the unsafe thin wrapper around the [CPython C API](https://docs.python.org/3/c-api/datetime.html), +//! and covers the various date and time related objects in the Python `datetime` +//! standard library module. + +#[cfg(GraalPy)] +use crate::{PyLong_AsLong, PyLong_Check, PyObject_GetAttrString, Py_DecRef}; +use crate::{PyObject, PyObject_TypeCheck, PyTypeObject, Py_TYPE}; +use std::cell::UnsafeCell; +#[cfg(not(GraalPy))] +use std::os::raw::c_char; +use std::os::raw::c_int; +use std::ptr; +#[cfg(not(PyPy))] +use {crate::PyCapsule_Import, std::ffi::CString}; +#[cfg(not(any(PyPy, GraalPy)))] +use {crate::Py_hash_t, std::os::raw::c_uchar}; +// Type struct wrappers +const _PyDateTime_DATE_DATASIZE: usize = 4; +const _PyDateTime_TIME_DATASIZE: usize = 6; +const _PyDateTime_DATETIME_DATASIZE: usize = 10; + +#[repr(C)] +#[derive(Debug)] +/// Structure representing a `datetime.timedelta`. +pub struct PyDateTime_Delta { + pub ob_base: PyObject, + #[cfg(not(any(PyPy, GraalPy)))] + pub hashcode: Py_hash_t, + #[cfg(not(GraalPy))] + pub days: c_int, + #[cfg(not(GraalPy))] + pub seconds: c_int, + #[cfg(not(GraalPy))] + pub microseconds: c_int, +} + +// skipped non-limited PyDateTime_TZInfo +// skipped non-limited _PyDateTime_BaseTZInfo + +#[cfg(not(any(PyPy, GraalPy)))] +#[repr(C)] +#[derive(Debug)] +/// Structure representing a `datetime.time` without a `tzinfo` member. +pub struct _PyDateTime_BaseTime { + pub ob_base: PyObject, + pub hashcode: Py_hash_t, + pub hastzinfo: c_char, + pub data: [c_uchar; _PyDateTime_TIME_DATASIZE], +} + +#[repr(C)] +#[derive(Debug)] +/// Structure representing a `datetime.time`. +pub struct PyDateTime_Time { + pub ob_base: PyObject, + #[cfg(not(any(PyPy, GraalPy)))] + pub hashcode: Py_hash_t, + #[cfg(not(GraalPy))] + pub hastzinfo: c_char, + #[cfg(not(any(PyPy, GraalPy)))] + pub data: [c_uchar; _PyDateTime_TIME_DATASIZE], + #[cfg(not(any(PyPy, GraalPy)))] + pub fold: c_uchar, + /// # Safety + /// + /// Care should be taken when reading this field. If the time does not have a + /// tzinfo then CPython may allocate as a `_PyDateTime_BaseTime` without this field. + #[cfg(not(GraalPy))] + pub tzinfo: *mut PyObject, +} + +#[repr(C)] +#[derive(Debug)] +/// Structure representing a `datetime.date` +pub struct PyDateTime_Date { + pub ob_base: PyObject, + #[cfg(not(any(PyPy, GraalPy)))] + pub hashcode: Py_hash_t, + #[cfg(not(any(PyPy, GraalPy)))] + pub hastzinfo: c_char, + #[cfg(not(any(PyPy, GraalPy)))] + pub data: [c_uchar; _PyDateTime_DATE_DATASIZE], +} + +#[cfg(not(any(PyPy, GraalPy)))] +#[repr(C)] +#[derive(Debug)] +/// Structure representing a `datetime.datetime` without a `tzinfo` member. +pub struct _PyDateTime_BaseDateTime { + pub ob_base: PyObject, + pub hashcode: Py_hash_t, + pub hastzinfo: c_char, + pub data: [c_uchar; _PyDateTime_DATETIME_DATASIZE], +} + +#[repr(C)] +#[derive(Debug)] +/// Structure representing a `datetime.datetime`. +pub struct PyDateTime_DateTime { + pub ob_base: PyObject, + #[cfg(not(any(PyPy, GraalPy)))] + pub hashcode: Py_hash_t, + #[cfg(not(GraalPy))] + pub hastzinfo: c_char, + #[cfg(not(any(PyPy, GraalPy)))] + pub data: [c_uchar; _PyDateTime_DATETIME_DATASIZE], + #[cfg(not(any(PyPy, GraalPy)))] + pub fold: c_uchar, + /// # Safety + /// + /// Care should be taken when reading this field. If the time does not have a + /// tzinfo then CPython may allocate as a `_PyDateTime_BaseDateTime` without this field. + #[cfg(not(GraalPy))] + pub tzinfo: *mut PyObject, +} + +// skipped non-limited _PyDateTime_HAS_TZINFO + +// Accessor functions for PyDateTime_Date and PyDateTime_DateTime +#[inline] +#[cfg(not(any(PyPy, GraalPy)))] +/// Retrieve the year component of a `PyDateTime_Date` or `PyDateTime_DateTime`. +/// Returns a signed integer greater than 0. +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]) +} + +#[inline] +#[cfg(not(any(PyPy, GraalPy)))] +/// Retrieve the month component of a `PyDateTime_Date` or `PyDateTime_DateTime`. +/// Returns a signed integer in the range `[1, 12]`. +pub unsafe fn PyDateTime_GET_MONTH(o: *mut PyObject) -> c_int { + let data = (*(o as *mut PyDateTime_Date)).data; + c_int::from(data[2]) +} + +#[inline] +#[cfg(not(any(PyPy, GraalPy)))] +/// Retrieve the day component of a `PyDateTime_Date` or `PyDateTime_DateTime`. +/// Returns a signed integer in the interval `[1, 31]`. +pub unsafe fn PyDateTime_GET_DAY(o: *mut PyObject) -> c_int { + let data = (*(o as *mut PyDateTime_Date)).data; + c_int::from(data[3]) +} + +// Accessor macros for times +#[cfg(not(any(PyPy, GraalPy)))] +macro_rules! _PyDateTime_GET_HOUR { + ($o: expr, $offset:expr) => { + c_int::from((*$o).data[$offset + 0]) + }; +} + +#[cfg(not(any(PyPy, GraalPy)))] +macro_rules! _PyDateTime_GET_MINUTE { + ($o: expr, $offset:expr) => { + c_int::from((*$o).data[$offset + 1]) + }; +} + +#[cfg(not(any(PyPy, GraalPy)))] +macro_rules! _PyDateTime_GET_SECOND { + ($o: expr, $offset:expr) => { + c_int::from((*$o).data[$offset + 2]) + }; +} + +#[cfg(not(any(PyPy, GraalPy)))] +macro_rules! _PyDateTime_GET_MICROSECOND { + ($o: expr, $offset:expr) => { + (c_int::from((*$o).data[$offset + 3]) << 16) + | (c_int::from((*$o).data[$offset + 4]) << 8) + | (c_int::from((*$o).data[$offset + 5])) + }; +} + +#[cfg(not(any(PyPy, GraalPy)))] +macro_rules! _PyDateTime_GET_FOLD { + ($o: expr) => { + (*$o).fold + }; +} + +#[cfg(not(any(PyPy, GraalPy)))] +macro_rules! _PyDateTime_GET_TZINFO { + ($o: expr) => { + if (*$o).hastzinfo != 0 { + (*$o).tzinfo + } else { + $crate::Py_None() + } + }; +} + +// Accessor functions for DateTime +#[inline] +#[cfg(not(any(PyPy, GraalPy)))] +/// Retrieve the hour component of a `PyDateTime_DateTime`. +/// Returns a signed integer in the interval `[0, 23]` +pub unsafe fn PyDateTime_DATE_GET_HOUR(o: *mut PyObject) -> c_int { + _PyDateTime_GET_HOUR!((o as *mut PyDateTime_DateTime), _PyDateTime_DATE_DATASIZE) +} + +#[inline] +#[cfg(not(any(PyPy, GraalPy)))] +/// Retrieve the minute component of a `PyDateTime_DateTime`. +/// Returns a signed integer in the interval `[0, 59]` +pub unsafe fn PyDateTime_DATE_GET_MINUTE(o: *mut PyObject) -> c_int { + _PyDateTime_GET_MINUTE!((o as *mut PyDateTime_DateTime), _PyDateTime_DATE_DATASIZE) +} + +#[inline] +#[cfg(not(any(PyPy, GraalPy)))] +/// Retrieve the second component of a `PyDateTime_DateTime`. +/// Returns a signed integer in the interval `[0, 59]` +pub unsafe fn PyDateTime_DATE_GET_SECOND(o: *mut PyObject) -> c_int { + _PyDateTime_GET_SECOND!((o as *mut PyDateTime_DateTime), _PyDateTime_DATE_DATASIZE) +} + +#[inline] +#[cfg(not(any(PyPy, GraalPy)))] +/// Retrieve the microsecond component of a `PyDateTime_DateTime`. +/// Returns a signed integer in the interval `[0, 999999]` +pub unsafe fn PyDateTime_DATE_GET_MICROSECOND(o: *mut PyObject) -> c_int { + _PyDateTime_GET_MICROSECOND!((o as *mut PyDateTime_DateTime), _PyDateTime_DATE_DATASIZE) +} + +#[inline] +#[cfg(not(any(PyPy, GraalPy)))] +/// Retrieve the fold component of a `PyDateTime_DateTime`. +/// Returns a signed integer in the interval `[0, 1]` +pub unsafe fn PyDateTime_DATE_GET_FOLD(o: *mut PyObject) -> c_uchar { + _PyDateTime_GET_FOLD!(o as *mut PyDateTime_DateTime) +} + +#[inline] +#[cfg(not(any(PyPy, GraalPy)))] +/// Retrieve the tzinfo component of a `PyDateTime_DateTime`. +/// Returns a pointer to a `PyObject` that should be either NULL or an instance +/// of a `datetime.tzinfo` subclass. +pub unsafe fn PyDateTime_DATE_GET_TZINFO(o: *mut PyObject) -> *mut PyObject { + _PyDateTime_GET_TZINFO!(o as *mut PyDateTime_DateTime) +} + +// Accessor functions for Time +#[inline] +#[cfg(not(any(PyPy, GraalPy)))] +/// Retrieve the hour component of a `PyDateTime_Time`. +/// Returns a signed integer in the interval `[0, 23]` +pub unsafe fn PyDateTime_TIME_GET_HOUR(o: *mut PyObject) -> c_int { + _PyDateTime_GET_HOUR!((o as *mut PyDateTime_Time), 0) +} + +#[inline] +#[cfg(not(any(PyPy, GraalPy)))] +/// Retrieve the minute component of a `PyDateTime_Time`. +/// Returns a signed integer in the interval `[0, 59]` +pub unsafe fn PyDateTime_TIME_GET_MINUTE(o: *mut PyObject) -> c_int { + _PyDateTime_GET_MINUTE!((o as *mut PyDateTime_Time), 0) +} + +#[inline] +#[cfg(not(any(PyPy, GraalPy)))] +/// Retrieve the second component of a `PyDateTime_DateTime`. +/// Returns a signed integer in the interval `[0, 59]` +pub unsafe fn PyDateTime_TIME_GET_SECOND(o: *mut PyObject) -> c_int { + _PyDateTime_GET_SECOND!((o as *mut PyDateTime_Time), 0) +} + +#[inline] +#[cfg(not(any(PyPy, GraalPy)))] +/// Retrieve the microsecond component of a `PyDateTime_DateTime`. +/// Returns a signed integer in the interval `[0, 999999]` +pub unsafe fn PyDateTime_TIME_GET_MICROSECOND(o: *mut PyObject) -> c_int { + _PyDateTime_GET_MICROSECOND!((o as *mut PyDateTime_Time), 0) +} + +#[cfg(not(any(PyPy, GraalPy)))] +#[inline] +/// Retrieve the fold component of a `PyDateTime_Time`. +/// Returns a signed integer in the interval `[0, 1]` +pub unsafe fn PyDateTime_TIME_GET_FOLD(o: *mut PyObject) -> c_uchar { + _PyDateTime_GET_FOLD!(o as *mut PyDateTime_Time) +} + +#[inline] +#[cfg(not(any(PyPy, GraalPy)))] +/// Retrieve the tzinfo component of a `PyDateTime_Time`. +/// Returns a pointer to a `PyObject` that should be either NULL or an instance +/// of a `datetime.tzinfo` subclass. +pub unsafe fn PyDateTime_TIME_GET_TZINFO(o: *mut PyObject) -> *mut PyObject { + _PyDateTime_GET_TZINFO!(o as *mut PyDateTime_Time) +} + +// Accessor functions +#[cfg(not(any(PyPy, GraalPy)))] +macro_rules! _access_field { + ($obj:expr, $type: ident, $field:ident) => { + (*($obj as *mut $type)).$field + }; +} + +// Accessor functions for PyDateTime_Delta +#[cfg(not(any(PyPy, GraalPy)))] +macro_rules! _access_delta_field { + ($obj:expr, $field:ident) => { + _access_field!($obj, PyDateTime_Delta, $field) + }; +} + +#[inline] +#[cfg(not(any(PyPy, GraalPy)))] +/// Retrieve the days component of a `PyDateTime_Delta`. +/// +/// Returns a signed integer in the interval [-999999999, 999999999]. +/// +/// Note: This retrieves a component from the underlying structure, it is *not* +/// a representation of the total duration of the structure. +pub unsafe fn PyDateTime_DELTA_GET_DAYS(o: *mut PyObject) -> c_int { + _access_delta_field!(o, days) +} + +#[inline] +#[cfg(not(any(PyPy, GraalPy)))] +/// Retrieve the seconds component of a `PyDateTime_Delta`. +/// +/// Returns a signed integer in the interval [0, 86399]. +/// +/// Note: This retrieves a component from the underlying structure, it is *not* +/// a representation of the total duration of the structure. +pub unsafe fn PyDateTime_DELTA_GET_SECONDS(o: *mut PyObject) -> c_int { + _access_delta_field!(o, seconds) +} + +#[inline] +#[cfg(not(any(PyPy, GraalPy)))] +/// Retrieve the seconds component of a `PyDateTime_Delta`. +/// +/// Returns a signed integer in the interval [0, 999999]. +/// +/// Note: This retrieves a component from the underlying structure, it is *not* +/// a representation of the total duration of the structure. +pub unsafe fn PyDateTime_DELTA_GET_MICROSECONDS(o: *mut PyObject) -> c_int { + _access_delta_field!(o, microseconds) +} + +// Accessor functions for GraalPy. The macros on GraalPy work differently, +// but copying them seems suboptimal +#[inline] +#[cfg(GraalPy)] +pub unsafe fn _get_attr(obj: *mut PyObject, field: &std::ffi::CStr) -> c_int { + let result = PyObject_GetAttrString(obj, field.as_ptr()); + Py_DecRef(result); // the original macros are borrowing + if PyLong_Check(result) == 1 { + PyLong_AsLong(result) as c_int + } else { + 0 + } +} + +#[inline] +#[cfg(GraalPy)] +pub unsafe fn PyDateTime_GET_YEAR(o: *mut PyObject) -> c_int { + _get_attr(o, c_str!("year")) +} + +#[inline] +#[cfg(GraalPy)] +pub unsafe fn PyDateTime_GET_MONTH(o: *mut PyObject) -> c_int { + _get_attr(o, c_str!("month")) +} + +#[inline] +#[cfg(GraalPy)] +pub unsafe fn PyDateTime_GET_DAY(o: *mut PyObject) -> c_int { + _get_attr(o, c_str!("day")) +} + +#[inline] +#[cfg(GraalPy)] +pub unsafe fn PyDateTime_DATE_GET_HOUR(o: *mut PyObject) -> c_int { + _get_attr(o, c_str!("hour")) +} + +#[inline] +#[cfg(GraalPy)] +pub unsafe fn PyDateTime_DATE_GET_MINUTE(o: *mut PyObject) -> c_int { + _get_attr(o, c_str!("minute")) +} + +#[inline] +#[cfg(GraalPy)] +pub unsafe fn PyDateTime_DATE_GET_SECOND(o: *mut PyObject) -> c_int { + _get_attr(o, c_str!("second")) +} + +#[inline] +#[cfg(GraalPy)] +pub unsafe fn PyDateTime_DATE_GET_MICROSECOND(o: *mut PyObject) -> c_int { + _get_attr(o, c_str!("microsecond")) +} + +#[inline] +#[cfg(GraalPy)] +pub unsafe fn PyDateTime_DATE_GET_FOLD(o: *mut PyObject) -> c_int { + _get_attr(o, c_str!("fold")) +} + +#[inline] +#[cfg(GraalPy)] +pub unsafe fn PyDateTime_DATE_GET_TZINFO(o: *mut PyObject) -> *mut PyObject { + let res = PyObject_GetAttrString(o, c_str!("tzinfo").as_ptr().cast()); + Py_DecRef(res); // the original macros are borrowing + res +} + +#[inline] +#[cfg(GraalPy)] +pub unsafe fn PyDateTime_TIME_GET_HOUR(o: *mut PyObject) -> c_int { + _get_attr(o, c_str!("hour")) +} + +#[inline] +#[cfg(GraalPy)] +pub unsafe fn PyDateTime_TIME_GET_MINUTE(o: *mut PyObject) -> c_int { + _get_attr(o, c_str!("minute")) +} + +#[inline] +#[cfg(GraalPy)] +pub unsafe fn PyDateTime_TIME_GET_SECOND(o: *mut PyObject) -> c_int { + _get_attr(o, c_str!("second")) +} + +#[inline] +#[cfg(GraalPy)] +pub unsafe fn PyDateTime_TIME_GET_MICROSECOND(o: *mut PyObject) -> c_int { + _get_attr(o, c_str!("microsecond")) +} + +#[inline] +#[cfg(GraalPy)] +pub unsafe fn PyDateTime_TIME_GET_FOLD(o: *mut PyObject) -> c_int { + _get_attr(o, c_str!("fold")) +} + +#[inline] +#[cfg(GraalPy)] +pub unsafe fn PyDateTime_TIME_GET_TZINFO(o: *mut PyObject) -> *mut PyObject { + let res = PyObject_GetAttrString(o, c_str!("tzinfo").as_ptr().cast()); + Py_DecRef(res); // the original macros are borrowing + res +} + +#[inline] +#[cfg(GraalPy)] +pub unsafe fn PyDateTime_DELTA_GET_DAYS(o: *mut PyObject) -> c_int { + _get_attr(o, c_str!("days")) +} + +#[inline] +#[cfg(GraalPy)] +pub unsafe fn PyDateTime_DELTA_GET_SECONDS(o: *mut PyObject) -> c_int { + _get_attr(o, c_str!("seconds")) +} + +#[inline] +#[cfg(GraalPy)] +pub unsafe fn PyDateTime_DELTA_GET_MICROSECONDS(o: *mut PyObject) -> c_int { + _get_attr(o, c_str!("microseconds")) +} + +#[cfg(PyPy)] +extern "C" { + // skipped _PyDateTime_HAS_TZINFO (not in PyPy) + #[link_name = "PyPyDateTime_GET_YEAR"] + pub fn PyDateTime_GET_YEAR(o: *mut PyObject) -> c_int; + #[link_name = "PyPyDateTime_GET_MONTH"] + pub fn PyDateTime_GET_MONTH(o: *mut PyObject) -> c_int; + #[link_name = "PyPyDateTime_GET_DAY"] + pub fn PyDateTime_GET_DAY(o: *mut PyObject) -> c_int; + + #[link_name = "PyPyDateTime_DATE_GET_HOUR"] + pub fn PyDateTime_DATE_GET_HOUR(o: *mut PyObject) -> c_int; + #[link_name = "PyPyDateTime_DATE_GET_MINUTE"] + pub fn PyDateTime_DATE_GET_MINUTE(o: *mut PyObject) -> c_int; + #[link_name = "PyPyDateTime_DATE_GET_SECOND"] + pub fn PyDateTime_DATE_GET_SECOND(o: *mut PyObject) -> c_int; + #[link_name = "PyPyDateTime_DATE_GET_MICROSECOND"] + 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_TIME_GET_HOUR"] + pub fn PyDateTime_TIME_GET_HOUR(o: *mut PyObject) -> c_int; + #[link_name = "PyPyDateTime_TIME_GET_MINUTE"] + pub fn PyDateTime_TIME_GET_MINUTE(o: *mut PyObject) -> c_int; + #[link_name = "PyPyDateTime_TIME_GET_SECOND"] + pub fn PyDateTime_TIME_GET_SECOND(o: *mut PyObject) -> c_int; + #[link_name = "PyPyDateTime_TIME_GET_MICROSECOND"] + 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_DELTA_GET_DAYS"] + pub fn PyDateTime_DELTA_GET_DAYS(o: *mut PyObject) -> c_int; + #[link_name = "PyPyDateTime_DELTA_GET_SECONDS"] + pub fn PyDateTime_DELTA_GET_SECONDS(o: *mut PyObject) -> c_int; + #[link_name = "PyPyDateTime_DELTA_GET_MICROSECONDS"] + pub fn PyDateTime_DELTA_GET_MICROSECONDS(o: *mut PyObject) -> c_int; +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct PyDateTime_CAPI { + pub DateType: *mut PyTypeObject, + pub DateTimeType: *mut PyTypeObject, + pub TimeType: *mut PyTypeObject, + pub DeltaType: *mut PyTypeObject, + pub TZInfoType: *mut PyTypeObject, + pub TimeZone_UTC: *mut PyObject, + pub Date_FromDate: unsafe extern "C" fn( + year: c_int, + month: c_int, + day: c_int, + cls: *mut PyTypeObject, + ) -> *mut PyObject, + pub DateTime_FromDateAndTime: unsafe extern "C" fn( + year: c_int, + month: c_int, + day: c_int, + hour: c_int, + minute: c_int, + second: c_int, + microsecond: c_int, + tzinfo: *mut PyObject, + cls: *mut PyTypeObject, + ) -> *mut PyObject, + pub Time_FromTime: unsafe extern "C" fn( + hour: c_int, + minute: c_int, + second: c_int, + microsecond: c_int, + tzinfo: *mut PyObject, + cls: *mut PyTypeObject, + ) -> *mut PyObject, + pub Delta_FromDelta: unsafe extern "C" fn( + days: c_int, + seconds: c_int, + microseconds: c_int, + normalize: c_int, + cls: *mut PyTypeObject, + ) -> *mut PyObject, + pub TimeZone_FromTimeZone: + unsafe extern "C" fn(offset: *mut PyObject, name: *mut PyObject) -> *mut PyObject, + + pub DateTime_FromTimestamp: unsafe extern "C" fn( + cls: *mut PyTypeObject, + args: *mut PyObject, + kwargs: *mut PyObject, + ) -> *mut PyObject, + pub Date_FromTimestamp: + unsafe extern "C" fn(cls: *mut PyTypeObject, args: *mut PyObject) -> *mut PyObject, + pub DateTime_FromDateAndTimeAndFold: unsafe extern "C" fn( + year: c_int, + month: c_int, + day: c_int, + hour: c_int, + minute: c_int, + second: c_int, + microsecond: c_int, + tzinfo: *mut PyObject, + fold: c_int, + cls: *mut PyTypeObject, + ) -> *mut PyObject, + pub Time_FromTimeAndFold: unsafe extern "C" fn( + hour: c_int, + minute: c_int, + second: c_int, + microsecond: c_int, + tzinfo: *mut PyObject, + fold: c_int, + cls: *mut PyTypeObject, + ) -> *mut PyObject, +} + +// Python already shares this object between threads, so it's no more evil for us to do it too! +unsafe impl Sync for PyDateTime_CAPI {} + +/// Returns a pointer to a `PyDateTime_CAPI` instance +/// +/// # Note +/// This function will return a null pointer until +/// `PyDateTime_IMPORT` is called +#[inline] +pub unsafe fn PyDateTimeAPI() -> *mut PyDateTime_CAPI { + *PyDateTimeAPI_impl.0.get() +} + +#[inline] +pub unsafe fn PyDateTime_TimeZone_UTC() -> *mut PyObject { + (*PyDateTimeAPI()).TimeZone_UTC +} + +/// Populates the `PyDateTimeAPI` object +pub unsafe fn PyDateTime_IMPORT() { + // PyPy expects the C-API to be initialized via PyDateTime_Import, so trying to use + // `PyCapsule_Import` will behave unexpectedly in pypy. + #[cfg(PyPy)] + let py_datetime_c_api = PyDateTime_Import(); + + #[cfg(not(PyPy))] + let py_datetime_c_api = { + // PyDateTime_CAPSULE_NAME is a macro in C + let PyDateTime_CAPSULE_NAME = CString::new("datetime.datetime_CAPI").unwrap(); + + PyCapsule_Import(PyDateTime_CAPSULE_NAME.as_ptr(), 1) as *mut PyDateTime_CAPI + }; + + *PyDateTimeAPI_impl.0.get() = py_datetime_c_api; +} + +// skipped non-limited PyDateTime_TimeZone_UTC + +/// Type Check macros +/// +/// These are bindings around the C API typecheck macros, all of them return +/// `1` if True and `0` if False. In all type check macros, the argument (`op`) +/// must not be `NULL`. +#[inline] +/// Check if `op` is a `PyDateTimeAPI.DateType` or subtype. +pub unsafe fn PyDate_Check(op: *mut PyObject) -> c_int { + PyObject_TypeCheck(op, (*PyDateTimeAPI()).DateType) as c_int +} + +#[inline] +/// Check if `op`'s type is exactly `PyDateTimeAPI.DateType`. +pub unsafe fn PyDate_CheckExact(op: *mut PyObject) -> c_int { + (Py_TYPE(op) == (*PyDateTimeAPI()).DateType) as c_int +} + +#[inline] +/// Check if `op` is a `PyDateTimeAPI.DateTimeType` or subtype. +pub unsafe fn PyDateTime_Check(op: *mut PyObject) -> c_int { + PyObject_TypeCheck(op, (*PyDateTimeAPI()).DateTimeType) as c_int +} + +#[inline] +/// Check if `op`'s type is exactly `PyDateTimeAPI.DateTimeType`. +pub unsafe fn PyDateTime_CheckExact(op: *mut PyObject) -> c_int { + (Py_TYPE(op) == (*PyDateTimeAPI()).DateTimeType) as c_int +} + +#[inline] +/// Check if `op` is a `PyDateTimeAPI.TimeType` or subtype. +pub unsafe fn PyTime_Check(op: *mut PyObject) -> c_int { + PyObject_TypeCheck(op, (*PyDateTimeAPI()).TimeType) as c_int +} + +#[inline] +/// Check if `op`'s type is exactly `PyDateTimeAPI.TimeType`. +pub unsafe fn PyTime_CheckExact(op: *mut PyObject) -> c_int { + (Py_TYPE(op) == (*PyDateTimeAPI()).TimeType) as c_int +} + +#[inline] +/// Check if `op` is a `PyDateTimeAPI.DetaType` or subtype. +pub unsafe fn PyDelta_Check(op: *mut PyObject) -> c_int { + PyObject_TypeCheck(op, (*PyDateTimeAPI()).DeltaType) as c_int +} + +#[inline] +/// Check if `op`'s type is exactly `PyDateTimeAPI.DeltaType`. +pub unsafe fn PyDelta_CheckExact(op: *mut PyObject) -> c_int { + (Py_TYPE(op) == (*PyDateTimeAPI()).DeltaType) as c_int +} + +#[inline] +/// Check if `op` is a `PyDateTimeAPI.TZInfoType` or subtype. +pub unsafe fn PyTZInfo_Check(op: *mut PyObject) -> c_int { + PyObject_TypeCheck(op, (*PyDateTimeAPI()).TZInfoType) as c_int +} + +#[inline] +/// Check if `op`'s type is exactly `PyDateTimeAPI.TZInfoType`. +pub unsafe fn PyTZInfo_CheckExact(op: *mut PyObject) -> c_int { + (Py_TYPE(op) == (*PyDateTimeAPI()).TZInfoType) as c_int +} + +// skipped non-limited PyDate_FromDate +// skipped non-limited PyDateTime_FromDateAndTime +// skipped non-limited PyDateTime_FromDateAndTimeAndFold +// skipped non-limited PyTime_FromTime +// skipped non-limited PyTime_FromTimeAndFold +// skipped non-limited PyDelta_FromDSU + +pub unsafe fn PyTimeZone_FromOffset(offset: *mut PyObject) -> *mut PyObject { + ((*PyDateTimeAPI()).TimeZone_FromTimeZone)(offset, std::ptr::null_mut()) +} + +pub unsafe fn PyTimeZone_FromOffsetAndName( + offset: *mut PyObject, + name: *mut PyObject, +) -> *mut PyObject { + ((*PyDateTimeAPI()).TimeZone_FromTimeZone)(offset, name) +} + +#[cfg(not(PyPy))] +pub unsafe fn PyDateTime_FromTimestamp(args: *mut PyObject) -> *mut PyObject { + let f = (*PyDateTimeAPI()).DateTime_FromTimestamp; + f((*PyDateTimeAPI()).DateTimeType, args, std::ptr::null_mut()) +} + +#[cfg(not(PyPy))] +pub unsafe fn PyDate_FromTimestamp(args: *mut PyObject) -> *mut PyObject { + let f = (*PyDateTimeAPI()).Date_FromTimestamp; + f((*PyDateTimeAPI()).DateType, args) +} + +#[cfg(PyPy)] +extern "C" { + #[link_name = "PyPyDate_FromTimestamp"] + pub fn PyDate_FromTimestamp(args: *mut PyObject) -> *mut PyObject; + #[link_name = "PyPyDateTime_FromTimestamp"] + pub fn PyDateTime_FromTimestamp(args: *mut PyObject) -> *mut PyObject; +} + +#[cfg(PyPy)] +extern "C" { + #[link_name = "_PyPyDateTime_Import"] + pub fn PyDateTime_Import() -> *mut PyDateTime_CAPI; +} + +// Rust specific implementation details + +struct PyDateTimeAPISingleton(UnsafeCell<*mut PyDateTime_CAPI>); +unsafe impl Sync for PyDateTimeAPISingleton {} + +static PyDateTimeAPI_impl: PyDateTimeAPISingleton = + PyDateTimeAPISingleton(UnsafeCell::new(ptr::null_mut())); diff --git a/include/pyo3/pyo3-ffi/src/descrobject.rs b/include/pyo3/pyo3-ffi/src/descrobject.rs new file mode 100644 index 00000000..f4a5ce00 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/descrobject.rs @@ -0,0 +1,133 @@ +use crate::methodobject::PyMethodDef; +use crate::object::{PyObject, PyTypeObject}; +use crate::Py_ssize_t; +use std::os::raw::{c_char, c_int, c_void}; +use std::ptr; + +pub type getter = unsafe extern "C" fn(slf: *mut PyObject, closure: *mut c_void) -> *mut PyObject; +pub type setter = + unsafe extern "C" fn(slf: *mut PyObject, value: *mut PyObject, closure: *mut c_void) -> c_int; + +/// Represents the [PyGetSetDef](https://docs.python.org/3/c-api/structures.html#c.PyGetSetDef) +/// structure. +/// +/// Note that CPython may leave fields uninitialized. You must ensure that +/// `name` != NULL before dereferencing or reading other fields. +#[repr(C)] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct PyGetSetDef { + pub name: *const c_char, + pub get: Option, + pub set: Option, + pub doc: *const c_char, + pub closure: *mut c_void, +} + +impl Default for PyGetSetDef { + fn default() -> PyGetSetDef { + PyGetSetDef { + name: ptr::null(), + get: None, + set: None, + doc: ptr::null(), + closure: ptr::null_mut(), + } + } +} + +#[cfg_attr(windows, link(name = "pythonXY"))] +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyClassMethodDescr_Type")] + pub static mut PyClassMethodDescr_Type: PyTypeObject; + #[cfg_attr(PyPy, link_name = "PyPyGetSetDescr_Type")] + pub static mut PyGetSetDescr_Type: PyTypeObject; + #[cfg_attr(PyPy, link_name = "PyPyMemberDescr_Type")] + pub static mut PyMemberDescr_Type: PyTypeObject; + #[cfg_attr(PyPy, link_name = "PyPyMethodDescr_Type")] + pub static mut PyMethodDescr_Type: PyTypeObject; + #[cfg_attr(PyPy, link_name = "PyPyWrapperDescr_Type")] + pub static mut PyWrapperDescr_Type: PyTypeObject; + #[cfg_attr(PyPy, link_name = "PyPyDictProxy_Type")] + pub static mut PyDictProxy_Type: PyTypeObject; + #[cfg_attr(PyPy, link_name = "PyPyProperty_Type")] + pub static mut PyProperty_Type: PyTypeObject; +} + +extern "C" { + pub fn PyDescr_NewMethod(arg1: *mut PyTypeObject, arg2: *mut PyMethodDef) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyDescr_NewClassMethod")] + pub fn PyDescr_NewClassMethod(arg1: *mut PyTypeObject, arg2: *mut PyMethodDef) + -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyDescr_NewMember")] + pub fn PyDescr_NewMember(arg1: *mut PyTypeObject, arg2: *mut PyMemberDef) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyDescr_NewGetSet")] + pub fn PyDescr_NewGetSet(arg1: *mut PyTypeObject, arg2: *mut PyGetSetDef) -> *mut PyObject; + + #[cfg_attr(PyPy, link_name = "PyPyDictProxy_New")] + pub fn PyDictProxy_New(arg1: *mut PyObject) -> *mut PyObject; + pub fn PyWrapper_New(arg1: *mut PyObject, arg2: *mut PyObject) -> *mut PyObject; +} + +/// Represents the [PyMemberDef](https://docs.python.org/3/c-api/structures.html#c.PyMemberDef) +/// structure. +/// +/// Note that CPython may leave fields uninitialized. You must always ensure that +/// `name` != NULL before dereferencing or reading other fields. +#[repr(C)] +#[derive(Copy, Clone, Eq, PartialEq)] +pub struct PyMemberDef { + pub name: *const c_char, + pub type_code: c_int, + pub offset: Py_ssize_t, + pub flags: c_int, + pub doc: *const c_char, +} + +impl Default for PyMemberDef { + fn default() -> PyMemberDef { + PyMemberDef { + name: ptr::null_mut(), + type_code: 0, + offset: 0, + flags: 0, + doc: ptr::null_mut(), + } + } +} + +/* Types */ +pub const Py_T_SHORT: c_int = 0; +pub const Py_T_INT: c_int = 1; +pub const Py_T_LONG: c_int = 2; +pub const Py_T_FLOAT: c_int = 3; +pub const Py_T_DOUBLE: c_int = 4; +pub const Py_T_STRING: c_int = 5; +#[deprecated(note = "Use Py_T_OBJECT_EX instead")] +pub const _Py_T_OBJECT: c_int = 6; +pub const Py_T_CHAR: c_int = 7; +pub const Py_T_BYTE: c_int = 8; +pub const Py_T_UBYTE: c_int = 9; +pub const Py_T_USHORT: c_int = 10; +pub const Py_T_UINT: c_int = 11; +pub const Py_T_ULONG: c_int = 12; +pub const Py_T_STRING_INPLACE: c_int = 13; +pub const Py_T_BOOL: c_int = 14; +pub const Py_T_OBJECT_EX: c_int = 16; +pub const Py_T_LONGLONG: c_int = 17; +pub const Py_T_ULONGLONG: c_int = 18; +pub const Py_T_PYSSIZET: c_int = 19; +#[deprecated(note = "Value is always none")] +pub const _Py_T_NONE: c_int = 20; + +/* Flags */ +pub const Py_READONLY: c_int = 1; +#[cfg(Py_3_10)] +pub const Py_AUDIT_READ: c_int = 2; // Added in 3.10, harmless no-op before that +#[deprecated] +pub const _Py_WRITE_RESTRICTED: c_int = 4; // Deprecated, no-op. Do not reuse the value. +pub const Py_RELATIVE_OFFSET: c_int = 8; + +extern "C" { + pub fn PyMember_GetOne(addr: *const c_char, l: *mut PyMemberDef) -> *mut PyObject; + pub fn PyMember_SetOne(addr: *mut c_char, l: *mut PyMemberDef, value: *mut PyObject) -> c_int; +} diff --git a/include/pyo3/pyo3-ffi/src/dictobject.rs b/include/pyo3/pyo3-ffi/src/dictobject.rs new file mode 100644 index 00000000..710be802 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/dictobject.rs @@ -0,0 +1,121 @@ +use crate::object::*; +use crate::pyport::Py_ssize_t; +use std::os::raw::{c_char, c_int}; +use std::ptr::addr_of_mut; + +#[cfg_attr(windows, link(name = "pythonXY"))] +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyDict_Type")] + pub static mut PyDict_Type: PyTypeObject; +} + +#[inline] +pub unsafe fn PyDict_Check(op: *mut PyObject) -> c_int { + PyType_FastSubclass(Py_TYPE(op), Py_TPFLAGS_DICT_SUBCLASS) +} + +#[inline] +pub unsafe fn PyDict_CheckExact(op: *mut PyObject) -> c_int { + (Py_TYPE(op) == addr_of_mut!(PyDict_Type)) as c_int +} + +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyDict_New")] + pub fn PyDict_New() -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyDict_GetItem")] + pub fn PyDict_GetItem(mp: *mut PyObject, key: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyDict_GetItemWithError")] + pub fn PyDict_GetItemWithError(mp: *mut PyObject, key: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyDict_SetItem")] + pub fn PyDict_SetItem(mp: *mut PyObject, key: *mut PyObject, item: *mut PyObject) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyDict_DelItem")] + pub fn PyDict_DelItem(mp: *mut PyObject, key: *mut PyObject) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyDict_Clear")] + pub fn PyDict_Clear(mp: *mut PyObject); + #[cfg_attr(PyPy, link_name = "PyPyDict_Next")] + pub fn PyDict_Next( + mp: *mut PyObject, + pos: *mut Py_ssize_t, + key: *mut *mut PyObject, + value: *mut *mut PyObject, + ) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyDict_Keys")] + pub fn PyDict_Keys(mp: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyDict_Values")] + pub fn PyDict_Values(mp: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyDict_Items")] + pub fn PyDict_Items(mp: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyDict_Size")] + pub fn PyDict_Size(mp: *mut PyObject) -> Py_ssize_t; + #[cfg_attr(PyPy, link_name = "PyPyDict_Copy")] + pub fn PyDict_Copy(mp: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyDict_Contains")] + pub fn PyDict_Contains(mp: *mut PyObject, key: *mut PyObject) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyDict_Update")] + pub fn PyDict_Update(mp: *mut PyObject, other: *mut PyObject) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyDict_Merge")] + pub fn PyDict_Merge(mp: *mut PyObject, other: *mut PyObject, _override: c_int) -> c_int; + pub fn PyDict_MergeFromSeq2(d: *mut PyObject, seq2: *mut PyObject, _override: c_int) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyDict_GetItemString")] + pub fn PyDict_GetItemString(dp: *mut PyObject, key: *const c_char) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyDict_SetItemString")] + pub fn PyDict_SetItemString( + dp: *mut PyObject, + key: *const c_char, + item: *mut PyObject, + ) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyDict_DelItemString")] + pub fn PyDict_DelItemString(dp: *mut PyObject, key: *const c_char) -> c_int; + #[cfg(Py_3_13)] + #[cfg_attr(PyPy, link_name = "PyPyDict_GetItemRef")] + pub fn PyDict_GetItemRef( + dp: *mut PyObject, + key: *mut PyObject, + result: *mut *mut PyObject, + ) -> c_int; + // skipped 3.10 / ex-non-limited PyObject_GenericGetDict +} + +#[cfg_attr(windows, link(name = "pythonXY"))] +extern "C" { + pub static mut PyDictKeys_Type: PyTypeObject; + pub static mut PyDictValues_Type: PyTypeObject; + pub static mut PyDictItems_Type: PyTypeObject; +} + +#[inline] +pub unsafe fn PyDictKeys_Check(op: *mut PyObject) -> c_int { + (Py_TYPE(op) == addr_of_mut!(PyDictKeys_Type)) as c_int +} + +#[inline] +pub unsafe fn PyDictValues_Check(op: *mut PyObject) -> c_int { + (Py_TYPE(op) == addr_of_mut!(PyDictValues_Type)) as c_int +} + +#[inline] +pub unsafe fn PyDictItems_Check(op: *mut PyObject) -> c_int { + (Py_TYPE(op) == addr_of_mut!(PyDictItems_Type)) as c_int +} + +#[inline] +pub unsafe fn PyDictViewSet_Check(op: *mut PyObject) -> c_int { + (PyDictKeys_Check(op) != 0 || PyDictItems_Check(op) != 0) as c_int +} + +#[cfg_attr(windows, link(name = "pythonXY"))] +extern "C" { + pub static mut PyDictIterKey_Type: PyTypeObject; + pub static mut PyDictIterValue_Type: PyTypeObject; + pub static mut PyDictIterItem_Type: PyTypeObject; + #[cfg(Py_3_8)] + pub static mut PyDictRevIterKey_Type: PyTypeObject; + #[cfg(Py_3_8)] + pub static mut PyDictRevIterValue_Type: PyTypeObject; + #[cfg(Py_3_8)] + pub static mut PyDictRevIterItem_Type: PyTypeObject; +} + +#[cfg(any(PyPy, GraalPy, Py_LIMITED_API))] +// TODO: remove (see https://github.com/PyO3/pyo3/pull/1341#issuecomment-751515985) +opaque_struct!(PyDictObject); diff --git a/include/pyo3/pyo3-ffi/src/enumobject.rs b/include/pyo3/pyo3-ffi/src/enumobject.rs new file mode 100644 index 00000000..e3f187d1 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/enumobject.rs @@ -0,0 +1,7 @@ +use crate::object::PyTypeObject; + +#[cfg_attr(windows, link(name = "pythonXY"))] +extern "C" { + pub static mut PyEnum_Type: PyTypeObject; + pub static mut PyReversed_Type: PyTypeObject; +} diff --git a/include/pyo3/pyo3-ffi/src/fileobject.rs b/include/pyo3/pyo3-ffi/src/fileobject.rs new file mode 100644 index 00000000..91618ab7 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/fileobject.rs @@ -0,0 +1,38 @@ +use crate::object::PyObject; +use std::os::raw::{c_char, c_int}; + +pub const PY_STDIOTEXTMODE: &str = "b"; + +extern "C" { + pub fn PyFile_FromFd( + arg1: c_int, + arg2: *const c_char, + arg3: *const c_char, + arg4: c_int, + arg5: *const c_char, + arg6: *const c_char, + arg7: *const c_char, + arg8: c_int, + ) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyFile_GetLine")] + pub fn PyFile_GetLine(arg1: *mut PyObject, arg2: c_int) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyFile_WriteObject")] + pub fn PyFile_WriteObject(arg1: *mut PyObject, arg2: *mut PyObject, arg3: c_int) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyFile_WriteString")] + pub fn PyFile_WriteString(arg1: *const c_char, arg2: *mut PyObject) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyFile_AsFileDescriptor")] + pub fn PyObject_AsFileDescriptor(arg1: *mut PyObject) -> c_int; +} + +#[cfg_attr(windows, link(name = "pythonXY"))] +extern "C" { + #[deprecated(note = "Python 3.12")] + pub static mut Py_FileSystemDefaultEncoding: *const c_char; + #[deprecated(note = "Python 3.12")] + pub static mut Py_FileSystemDefaultEncodeErrors: *const c_char; + #[deprecated(note = "Python 3.12")] + pub static mut Py_HasFileSystemDefaultEncoding: c_int; + // skipped 3.12-deprecated Py_UTF8Mode +} + +// skipped _PyIsSelectable_fd diff --git a/include/pyo3/pyo3-ffi/src/fileutils.rs b/include/pyo3/pyo3-ffi/src/fileutils.rs new file mode 100644 index 00000000..3f053b72 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/fileutils.rs @@ -0,0 +1,9 @@ +use crate::pyport::Py_ssize_t; +use libc::wchar_t; +use std::os::raw::c_char; + +extern "C" { + pub fn Py_DecodeLocale(arg1: *const c_char, size: *mut Py_ssize_t) -> *mut wchar_t; + + pub fn Py_EncodeLocale(text: *const wchar_t, error_pos: *mut Py_ssize_t) -> *mut c_char; +} diff --git a/include/pyo3/pyo3-ffi/src/floatobject.rs b/include/pyo3/pyo3-ffi/src/floatobject.rs new file mode 100644 index 00000000..65fc1d4c --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/floatobject.rs @@ -0,0 +1,47 @@ +use crate::object::*; +use std::os::raw::{c_double, c_int}; +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); + +#[cfg_attr(windows, link(name = "pythonXY"))] +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyFloat_Type")] + pub static mut PyFloat_Type: PyTypeObject; +} + +#[inline] +pub unsafe fn PyFloat_Check(op: *mut PyObject) -> c_int { + PyObject_TypeCheck(op, addr_of_mut!(PyFloat_Type)) +} + +#[inline] +pub unsafe fn PyFloat_CheckExact(op: *mut PyObject) -> c_int { + (Py_TYPE(op) == addr_of_mut!(PyFloat_Type)) as c_int +} + +// skipped Py_RETURN_NAN +// skipped Py_RETURN_INF + +extern "C" { + pub fn PyFloat_GetMax() -> c_double; + pub fn PyFloat_GetMin() -> c_double; + pub fn PyFloat_GetInfo() -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyFloat_FromString")] + pub fn PyFloat_FromString(arg1: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyFloat_FromDouble")] + pub fn PyFloat_FromDouble(arg1: c_double) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyFloat_AsDouble")] + pub fn PyFloat_AsDouble(arg1: *mut PyObject) -> c_double; +} + +// skipped non-limited _PyFloat_Pack2 +// skipped non-limited _PyFloat_Pack4 +// skipped non-limited _PyFloat_Pack8 +// skipped non-limited _PyFloat_Unpack2 +// skipped non-limited _PyFloat_Unpack4 +// skipped non-limited _PyFloat_Unpack8 +// skipped non-limited _PyFloat_DebugMallocStats +// skipped non-limited _PyFloat_FormatAdvancedWriter diff --git a/include/pyo3/pyo3-ffi/src/impl_/mod.rs b/include/pyo3/pyo3-ffi/src/impl_/mod.rs new file mode 100644 index 00000000..3058e852 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/impl_/mod.rs @@ -0,0 +1,22 @@ +#[cfg(Py_GIL_DISABLED)] +mod atomic_c_ulong { + pub struct GetAtomicCULong(); + + pub trait AtomicCULongType { + type Type; + } + impl AtomicCULongType for GetAtomicCULong<32> { + type Type = std::sync::atomic::AtomicU32; + } + impl AtomicCULongType for GetAtomicCULong<64> { + type Type = std::sync::atomic::AtomicU64; + } + + pub type TYPE = + () * 8 }> as AtomicCULongType>::Type; +} + +/// Typedef for an atomic integer to match the platform-dependent c_ulong type. +#[cfg(Py_GIL_DISABLED)] +#[doc(hidden)] +pub type AtomicCULong = atomic_c_ulong::TYPE; diff --git a/include/pyo3/pyo3-ffi/src/import.rs b/include/pyo3/pyo3-ffi/src/import.rs new file mode 100644 index 00000000..e15a37b0 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/import.rs @@ -0,0 +1,84 @@ +use crate::object::PyObject; +use std::os::raw::{c_char, c_int, c_long}; + +extern "C" { + pub fn PyImport_GetMagicNumber() -> c_long; + pub fn PyImport_GetMagicTag() -> *const c_char; + #[cfg_attr(PyPy, link_name = "PyPyImport_ExecCodeModule")] + pub fn PyImport_ExecCodeModule(name: *const c_char, co: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyImport_ExecCodeModuleEx")] + pub fn PyImport_ExecCodeModuleEx( + name: *const c_char, + co: *mut PyObject, + pathname: *const c_char, + ) -> *mut PyObject; + pub fn PyImport_ExecCodeModuleWithPathnames( + name: *const c_char, + co: *mut PyObject, + pathname: *const c_char, + cpathname: *const c_char, + ) -> *mut PyObject; + pub fn PyImport_ExecCodeModuleObject( + name: *mut PyObject, + co: *mut PyObject, + pathname: *mut PyObject, + cpathname: *mut PyObject, + ) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyImport_GetModuleDict")] + pub fn PyImport_GetModuleDict() -> *mut PyObject; + // skipped Python 3.7 / ex-non-limited PyImport_GetModule + pub fn PyImport_AddModuleObject(name: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyImport_AddModule")] + pub fn PyImport_AddModule(name: *const c_char) -> *mut PyObject; + #[cfg(Py_3_13)] + #[cfg_attr(PyPy, link_name = "PyPyImport_AddModuleRef")] + pub fn PyImport_AddModuleRef(name: *const c_char) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyImport_ImportModule")] + pub fn PyImport_ImportModule(name: *const c_char) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyImport_ImportModuleNoBlock")] + pub fn PyImport_ImportModuleNoBlock(name: *const c_char) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyImport_ImportModuleLevel")] + pub fn PyImport_ImportModuleLevel( + name: *const c_char, + globals: *mut PyObject, + locals: *mut PyObject, + fromlist: *mut PyObject, + level: c_int, + ) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyImport_ImportModuleLevelObject")] + pub fn PyImport_ImportModuleLevelObject( + name: *mut PyObject, + globals: *mut PyObject, + locals: *mut PyObject, + fromlist: *mut PyObject, + level: c_int, + ) -> *mut PyObject; +} + +#[inline] +pub unsafe fn PyImport_ImportModuleEx( + name: *const c_char, + globals: *mut PyObject, + locals: *mut PyObject, + fromlist: *mut PyObject, +) -> *mut PyObject { + PyImport_ImportModuleLevel(name, globals, locals, fromlist, 0) +} + +extern "C" { + pub fn PyImport_GetImporter(path: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyImport_Import")] + pub fn PyImport_Import(name: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyImport_ReloadModule")] + pub fn PyImport_ReloadModule(m: *mut PyObject) -> *mut PyObject; + #[cfg(not(Py_3_9))] + #[deprecated(note = "Removed in Python 3.9 as it was \"For internal use only\".")] + pub fn PyImport_Cleanup(); + pub fn PyImport_ImportFrozenModuleObject(name: *mut PyObject) -> c_int; + pub fn PyImport_ImportFrozenModule(name: *const c_char) -> c_int; + + pub fn PyImport_AppendInittab( + name: *const c_char, + initfunc: Option *mut PyObject>, + ) -> c_int; +} diff --git a/include/pyo3/pyo3-ffi/src/intrcheck.rs b/include/pyo3/pyo3-ffi/src/intrcheck.rs new file mode 100644 index 00000000..3d8a58f7 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/intrcheck.rs @@ -0,0 +1,19 @@ +use std::os::raw::c_int; + +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyOS_InterruptOccurred")] + pub fn PyOS_InterruptOccurred() -> c_int; + #[cfg(not(Py_3_10))] + #[deprecated(note = "Not documented in Python API; see Python 3.10 release notes")] + pub fn PyOS_InitInterrupts(); + + pub fn PyOS_BeforeFork(); + pub fn PyOS_AfterFork_Parent(); + pub fn PyOS_AfterFork_Child(); + #[deprecated(note = "use PyOS_AfterFork_Child instead")] + #[cfg_attr(PyPy, link_name = "PyPyOS_AfterFork")] + pub fn PyOS_AfterFork(); + + // skipped non-limited _PyOS_IsMainThread + // skipped non-limited Windows _PyOS_SigintEvent +} diff --git a/include/pyo3/pyo3-ffi/src/iterobject.rs b/include/pyo3/pyo3-ffi/src/iterobject.rs new file mode 100644 index 00000000..aa0c7b26 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/iterobject.rs @@ -0,0 +1,29 @@ +use crate::object::*; +use std::os::raw::c_int; +use std::ptr::addr_of_mut; + +#[cfg_attr(windows, link(name = "pythonXY"))] +extern "C" { + pub static mut PySeqIter_Type: PyTypeObject; + pub static mut PyCallIter_Type: PyTypeObject; +} + +#[inline] +pub unsafe fn PySeqIter_Check(op: *mut PyObject) -> c_int { + (Py_TYPE(op) == addr_of_mut!(PySeqIter_Type)) as c_int +} + +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPySeqIter_New")] + pub fn PySeqIter_New(arg1: *mut PyObject) -> *mut PyObject; +} + +#[inline] +pub unsafe fn PyCallIter_Check(op: *mut PyObject) -> c_int { + (Py_TYPE(op) == addr_of_mut!(PyCallIter_Type)) as c_int +} + +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyCallIter_New")] + pub fn PyCallIter_New(arg1: *mut PyObject, arg2: *mut PyObject) -> *mut PyObject; +} diff --git a/include/pyo3/pyo3-ffi/src/lib.rs b/include/pyo3/pyo3-ffi/src/lib.rs new file mode 100644 index 00000000..c6157401 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/lib.rs @@ -0,0 +1,461 @@ +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +//! Raw FFI declarations for Python's C API. +//! +//! PyO3 can be used to write native Python modules or run Python code and modules from Rust. +//! +//! This crate just provides low level bindings to the Python interpreter. +//! It is meant for advanced users only - regular PyO3 users shouldn't +//! need to interact with this crate at all. +//! +//! The contents of this crate are not documented here, as it would entail +//! basically copying the documentation from CPython. Consult the [Python/C API Reference +//! Manual][capi] for up-to-date documentation. +//! +//! # Safety +//! +//! The functions in this crate lack individual safety documentation, but +//! generally the following apply: +//! - Pointer arguments have to point to a valid Python object of the correct type, +//! although null pointers are sometimes valid input. +//! - The vast majority can only be used safely while the GIL is held. +//! - Some functions have additional safety requirements, consult the +//! [Python/C API Reference Manual][capi] +//! for more information. +//! +//! +//! # Feature flags +//! +//! PyO3 uses [feature flags] to enable you to opt-in to additional functionality. For a detailed +//! description, see the [Features chapter of the guide]. +//! +//! ## Optional feature flags +//! +//! The following features customize PyO3's behavior: +//! +//! - `abi3`: Restricts PyO3's API to a subset of the full Python API which is guaranteed by +//! [PEP 384] to be forward-compatible with future Python versions. +//! - `extension-module`: This will tell the linker to keep the Python symbols unresolved, so that +//! your module can also be used with statically linked Python interpreters. Use this feature when +//! building an extension module. +//! +//! ## `rustc` environment flags +//! +//! PyO3 uses `rustc`'s `--cfg` flags to enable or disable code used for different Python versions. +//! If you want to do this for your own crate, you can do so with the [`pyo3-build-config`] crate. +//! +//! - `Py_3_7`, `Py_3_8`, `Py_3_9`, `Py_3_10`: Marks code that is only enabled when +//! compiling for a given minimum Python version. +//! - `Py_LIMITED_API`: Marks code enabled when the `abi3` feature flag is enabled. +//! - `PyPy` - Marks code enabled when compiling for PyPy. +//! +//! # Minimum supported Rust and Python versions +//! +//! `pyo3-ffi` supports the following Python distributions: +//! - CPython 3.7 or greater +//! - PyPy 7.3 (Python 3.9+) +//! - GraalPy 24.0 or greater (Python 3.10+) +//! +//! # Example: Building Python Native modules +//! +//! PyO3 can be used to generate a native Python module. The easiest way to try this out for the +//! first time is to use [`maturin`]. `maturin` is a tool for building and publishing Rust-based +//! Python packages with minimal configuration. The following steps set up some files for an example +//! Python module, install `maturin`, and then show how to build and import the Python module. +//! +//! First, create a new folder (let's call it `string_sum`) containing the following two files: +//! +//! **`Cargo.toml`** +//! +//! ```toml +//! [lib] +//! name = "string_sum" +//! # "cdylib" is necessary to produce a shared library for Python to import from. +//! # +//! # Downstream Rust code (including code in `bin/`, `examples/`, and `tests/`) will not be able +//! # to `use string_sum;` unless the "rlib" or "lib" crate type is also included, e.g.: +//! # crate-type = ["cdylib", "rlib"] +//! crate-type = ["cdylib"] +//! +//! [dependencies.pyo3-ffi] +#![doc = concat!("version = \"", env!("CARGO_PKG_VERSION"), "\"")] +//! features = ["extension-module"] +//! ``` +//! +//! **`src/lib.rs`** +//! ```rust +//! use std::os::raw::c_char; +//! use std::ptr; +//! +//! use pyo3_ffi::*; +//! +//! static mut MODULE_DEF: PyModuleDef = PyModuleDef { +//! m_base: PyModuleDef_HEAD_INIT, +//! m_name: c_str!("string_sum").as_ptr(), +//! m_doc: c_str!("A Python module written in Rust.").as_ptr(), +//! m_size: 0, +//! m_methods: unsafe { METHODS.as_mut_ptr().cast() }, +//! m_slots: std::ptr::null_mut(), +//! m_traverse: None, +//! m_clear: None, +//! m_free: None, +//! }; +//! +//! static mut METHODS: [PyMethodDef; 2] = [ +//! PyMethodDef { +//! ml_name: c_str!("sum_as_string").as_ptr(), +//! ml_meth: PyMethodDefPointer { +//! PyCFunctionFast: sum_as_string, +//! }, +//! ml_flags: METH_FASTCALL, +//! ml_doc: c_str!("returns the sum of two integers as a string").as_ptr(), +//! }, +//! // A zeroed PyMethodDef to mark the end of the array. +//! PyMethodDef::zeroed() +//! ]; +//! +//! // The module initialization function, which must be named `PyInit_`. +//! #[allow(non_snake_case)] +//! #[no_mangle] +//! pub unsafe extern "C" fn PyInit_string_sum() -> *mut PyObject { +//! PyModule_Create(ptr::addr_of_mut!(MODULE_DEF)) +//! } +//! +//! pub unsafe extern "C" fn sum_as_string( +//! _self: *mut PyObject, +//! args: *mut *mut PyObject, +//! nargs: Py_ssize_t, +//! ) -> *mut PyObject { +//! if nargs != 2 { +//! PyErr_SetString( +//! PyExc_TypeError, +//! c_str!("sum_as_string() expected 2 positional arguments").as_ptr(), +//! ); +//! return std::ptr::null_mut(); +//! } +//! +//! let arg1 = *args; +//! if PyLong_Check(arg1) == 0 { +//! PyErr_SetString( +//! PyExc_TypeError, +//! c_str!("sum_as_string() expected an int for positional argument 1").as_ptr(), +//! ); +//! return std::ptr::null_mut(); +//! } +//! +//! let arg1 = PyLong_AsLong(arg1); +//! if !PyErr_Occurred().is_null() { +//! return ptr::null_mut(); +//! } +//! +//! let arg2 = *args.add(1); +//! if PyLong_Check(arg2) == 0 { +//! PyErr_SetString( +//! PyExc_TypeError, +//! c_str!("sum_as_string() expected an int for positional argument 2").as_ptr(), +//! ); +//! return std::ptr::null_mut(); +//! } +//! +//! let arg2 = PyLong_AsLong(arg2); +//! if !PyErr_Occurred().is_null() { +//! return ptr::null_mut(); +//! } +//! +//! match arg1.checked_add(arg2) { +//! Some(sum) => { +//! let string = sum.to_string(); +//! PyUnicode_FromStringAndSize(string.as_ptr().cast::(), string.len() as isize) +//! } +//! None => { +//! PyErr_SetString( +//! PyExc_OverflowError, +//! c_str!("arguments too large to add").as_ptr(), +//! ); +//! std::ptr::null_mut() +//! } +//! } +//! } +//! ``` +//! +//! With those two files in place, now `maturin` needs to be installed. This can be done using +//! Python's package manager `pip`. First, load up a new Python `virtualenv`, and install `maturin` +//! into it: +//! ```bash +//! $ cd string_sum +//! $ python -m venv .env +//! $ source .env/bin/activate +//! $ pip install maturin +//! ``` +//! +//! Now build and execute the module: +//! ```bash +//! $ maturin develop +//! # lots of progress output as maturin runs the compilation... +//! $ python +//! >>> import string_sum +//! >>> string_sum.sum_as_string(5, 20) +//! '25' +//! ``` +//! +//! As well as with `maturin`, it is possible to build using [setuptools-rust] or +//! [manually][manual_builds]. Both offer more flexibility than `maturin` but require further +//! configuration. +//! +//! +//! # Using Python from Rust +//! +//! To embed Python into a Rust binary, you need to ensure that your Python installation contains a +//! shared library. The following steps demonstrate how to ensure this (for Ubuntu). +//! +//! To install the Python shared library on Ubuntu: +//! ```bash +//! sudo apt install python3-dev +//! ``` +//! +//! While most projects use the safe wrapper provided by pyo3, +//! you can take a look at the [`orjson`] library as an example on how to use `pyo3-ffi` directly. +//! For those well versed in C and Rust the [tutorials] from the CPython documentation +//! can be easily converted to rust as well. +//! +//! [tutorials]: https://docs.python.org/3/extending/ +//! [`orjson`]: https://github.com/ijl/orjson +//! [capi]: https://docs.python.org/3/c-api/index.html +//! [`maturin`]: https://github.com/PyO3/maturin "Build and publish crates with pyo3, rust-cpython and cffi bindings as well as rust binaries as python packages" +//! [`pyo3-build-config`]: https://docs.rs/pyo3-build-config +//! [feature flags]: https://doc.rust-lang.org/cargo/reference/features.html "Features - The Cargo Book" +#![doc = concat!("[manual_builds]: https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/building-and-distribution.html#manual-builds \"Manual builds - Building and Distribution - PyO3 user guide\"")] +//! [setuptools-rust]: https://github.com/PyO3/setuptools-rust "Setuptools plugin for Rust extensions" +//! [PEP 384]: https://www.python.org/dev/peps/pep-0384 "PEP 384 -- Defining a Stable ABI" +#![doc = concat!("[Features chapter of the guide]: https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/features.html#features-reference \"Features Reference - PyO3 user guide\"")] +#![allow( + missing_docs, + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + clippy::upper_case_acronyms, + clippy::missing_safety_doc +)] +#![warn(elided_lifetimes_in_paths, unused_lifetimes)] + +// 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) => { + #[repr(C)] + pub struct $name([u8; 0]); + }; +} + +/// This is a helper macro to create a `&'static CStr`. +/// +/// It can be used on all Rust versions supported by PyO3, unlike c"" literals which +/// were stabilised in Rust 1.77. +/// +/// Due to the nature of PyO3 making heavy use of C FFI interop with Python, it is +/// common for PyO3 to use CStr. +/// +/// Examples: +/// +/// ```rust +/// use std::ffi::CStr; +/// +/// const HELLO: &CStr = pyo3_ffi::c_str!("hello"); +/// static WORLD: &CStr = pyo3_ffi::c_str!("world"); +/// ``` +#[macro_export] +macro_rules! c_str { + ($s:expr) => { + $crate::_cstr_from_utf8_with_nul_checked(concat!($s, "\0")) + }; +} + +/// Private helper for `c_str!` macro. +#[doc(hidden)] +pub const fn _cstr_from_utf8_with_nul_checked(s: &str) -> &CStr { + // TODO: Replace this implementation with `CStr::from_bytes_with_nul` when MSRV above 1.72. + let bytes = s.as_bytes(); + let len = bytes.len(); + assert!( + !bytes.is_empty() && bytes[bytes.len() - 1] == b'\0', + "string is not nul-terminated" + ); + let mut i = 0; + let non_null_len = len - 1; + while i < non_null_len { + assert!(bytes[i] != b'\0', "string contains null bytes"); + i += 1; + } + + unsafe { CStr::from_bytes_with_nul_unchecked(bytes) } +} + +use std::ffi::CStr; + +pub mod compat; +mod impl_; + +pub use self::abstract_::*; +pub use self::bltinmodule::*; +pub use self::boolobject::*; +pub use self::bytearrayobject::*; +pub use self::bytesobject::*; +pub use self::ceval::*; +#[cfg(Py_LIMITED_API)] +pub use self::code::*; +pub use self::codecs::*; +pub use self::compile::*; +pub use self::complexobject::*; +#[cfg(all(Py_3_8, not(Py_LIMITED_API)))] +pub use self::context::*; +#[cfg(not(Py_LIMITED_API))] +pub use self::datetime::*; +pub use self::descrobject::*; +pub use self::dictobject::*; +pub use self::enumobject::*; +pub use self::fileobject::*; +pub use self::fileutils::*; +pub use self::floatobject::*; +pub use self::import::*; +pub use self::intrcheck::*; +pub use self::iterobject::*; +pub use self::listobject::*; +pub use self::longobject::*; +#[cfg(not(Py_LIMITED_API))] +pub use self::marshal::*; +pub use self::memoryobject::*; +pub use self::methodobject::*; +pub use self::modsupport::*; +pub use self::moduleobject::*; +pub use self::object::*; +pub use self::objimpl::*; +pub use self::osmodule::*; +#[cfg(not(any(PyPy, Py_LIMITED_API, Py_3_10)))] +pub use self::pyarena::*; +#[cfg(Py_3_11)] +pub use self::pybuffer::*; +pub use self::pycapsule::*; +pub use self::pyerrors::*; +pub use self::pyframe::*; +pub use self::pyhash::*; +pub use self::pylifecycle::*; +pub use self::pymem::*; +pub use self::pyport::*; +pub use self::pystate::*; +pub use self::pystrtod::*; +pub use self::pythonrun::*; +pub use self::rangeobject::*; +pub use self::setobject::*; +pub use self::sliceobject::*; +pub use self::structseq::*; +pub use self::sysmodule::*; +pub use self::traceback::*; +pub use self::tupleobject::*; +pub use self::typeslots::*; +pub use self::unicodeobject::*; +pub use self::warnings::*; +pub use self::weakrefobject::*; + +mod abstract_; +// skipped asdl.h +// skipped ast.h +mod bltinmodule; +mod boolobject; +mod bytearrayobject; +mod bytesobject; +// skipped cellobject.h +mod ceval; +// skipped classobject.h +#[cfg(Py_LIMITED_API)] +mod code; +mod codecs; +mod compile; +mod complexobject; +#[cfg(all(Py_3_8, not(Py_LIMITED_API)))] +mod context; // It's actually 3.7.1, but no cfg for patches. +#[cfg(not(Py_LIMITED_API))] +pub(crate) mod datetime; +mod descrobject; +mod dictobject; +// skipped dynamic_annotations.h +mod enumobject; +// skipped errcode.h +// skipped exports.h +mod fileobject; +mod fileutils; +mod floatobject; +// skipped empty frameobject.h +// skipped genericaliasobject.h +mod import; +// skipped interpreteridobject.h +mod intrcheck; +mod iterobject; +mod listobject; +// skipped longintrepr.h +mod longobject; +#[cfg(not(Py_LIMITED_API))] +pub mod marshal; +mod memoryobject; +mod methodobject; +mod modsupport; +mod moduleobject; +// skipped namespaceobject.h +mod object; +mod objimpl; +// skipped odictobject.h +// skipped opcode.h +// skipped osdefs.h +mod osmodule; +// skipped parser_interface.h +// skipped patchlevel.h +// skipped picklebufobject.h +// skipped pyctype.h +// skipped py_curses.h +#[cfg(not(any(PyPy, Py_LIMITED_API, Py_3_10)))] +mod pyarena; +#[cfg(Py_3_11)] +mod pybuffer; +mod pycapsule; +// skipped pydtrace.h +mod pyerrors; +// skipped pyexpat.h +// skipped pyfpe.h +mod pyframe; +mod pyhash; +mod pylifecycle; +// skipped pymacconfig.h +// skipped pymacro.h +// skipped pymath.h +mod pymem; +mod pyport; +mod pystate; +// skipped pystats.h +mod pythonrun; +// skipped pystrhex.h +// skipped pystrcmp.h +mod pystrtod; +// skipped pythread.h +// skipped pytime.h +mod rangeobject; +mod setobject; +mod sliceobject; +mod structseq; +mod sysmodule; +mod traceback; +// skipped tracemalloc.h +mod tupleobject; +mod typeslots; +mod unicodeobject; +mod warnings; +mod weakrefobject; + +// Additional headers that are not exported by Python.h +#[deprecated(note = "Python 3.12")] +pub mod structmember; + +// "Limited API" definitions matching Python's `include/cpython` directory. +#[cfg(not(Py_LIMITED_API))] +mod cpython; + +#[cfg(not(Py_LIMITED_API))] +pub use self::cpython::*; diff --git a/include/pyo3/pyo3-ffi/src/listobject.rs b/include/pyo3/pyo3-ffi/src/listobject.rs new file mode 100644 index 00000000..9d8b7ed6 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/listobject.rs @@ -0,0 +1,72 @@ +use crate::object::*; +use crate::pyport::Py_ssize_t; +use std::os::raw::c_int; +use std::ptr::addr_of_mut; + +#[cfg_attr(windows, link(name = "pythonXY"))] +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyList_Type")] + pub static mut PyList_Type: PyTypeObject; + pub static mut PyListIter_Type: PyTypeObject; + pub static mut PyListRevIter_Type: PyTypeObject; +} + +#[inline] +pub unsafe fn PyList_Check(op: *mut PyObject) -> c_int { + PyType_FastSubclass(Py_TYPE(op), Py_TPFLAGS_LIST_SUBCLASS) +} + +#[inline] +pub unsafe fn PyList_CheckExact(op: *mut PyObject) -> c_int { + (Py_TYPE(op) == addr_of_mut!(PyList_Type)) as c_int +} + +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyList_New")] + pub fn PyList_New(size: Py_ssize_t) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyList_Size")] + pub fn PyList_Size(arg1: *mut PyObject) -> Py_ssize_t; + #[cfg_attr(PyPy, link_name = "PyPyList_GetItem")] + pub fn PyList_GetItem(arg1: *mut PyObject, arg2: Py_ssize_t) -> *mut PyObject; + #[cfg(Py_3_13)] + #[cfg_attr(PyPy, link_name = "PyPyList_GetItemRef")] + pub fn PyList_GetItemRef(arg1: *mut PyObject, arg2: Py_ssize_t) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyList_SetItem")] + pub fn PyList_SetItem(arg1: *mut PyObject, arg2: Py_ssize_t, arg3: *mut PyObject) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyList_Insert")] + pub fn PyList_Insert(arg1: *mut PyObject, arg2: Py_ssize_t, arg3: *mut PyObject) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyList_Append")] + pub fn PyList_Append(arg1: *mut PyObject, arg2: *mut PyObject) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyList_GetSlice")] + pub fn PyList_GetSlice( + arg1: *mut PyObject, + arg2: Py_ssize_t, + arg3: Py_ssize_t, + ) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyList_SetSlice")] + pub fn PyList_SetSlice( + arg1: *mut PyObject, + arg2: Py_ssize_t, + arg3: Py_ssize_t, + arg4: *mut PyObject, + ) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyList_Sort")] + pub fn PyList_Sort(arg1: *mut PyObject) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyList_Reverse")] + pub fn PyList_Reverse(arg1: *mut PyObject) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyList_AsTuple")] + pub fn PyList_AsTuple(arg1: *mut PyObject) -> *mut PyObject; + + // CPython macros exported as functions on PyPy or GraalPy + #[cfg(any(PyPy, GraalPy))] + #[cfg_attr(PyPy, link_name = "PyPyList_GET_ITEM")] + #[cfg_attr(GraalPy, link_name = "PyList_GetItem")] + pub fn PyList_GET_ITEM(arg1: *mut PyObject, arg2: Py_ssize_t) -> *mut PyObject; + #[cfg(PyPy)] + #[cfg_attr(PyPy, link_name = "PyPyList_GET_SIZE")] + pub fn PyList_GET_SIZE(arg1: *mut PyObject) -> Py_ssize_t; + #[cfg(any(PyPy, GraalPy))] + #[cfg_attr(PyPy, link_name = "PyPyList_SET_ITEM")] + #[cfg_attr(GraalPy, link_name = "_PyList_SET_ITEM")] + pub fn PyList_SET_ITEM(arg1: *mut PyObject, arg2: Py_ssize_t, arg3: *mut PyObject); +} diff --git a/include/pyo3/pyo3-ffi/src/longobject.rs b/include/pyo3/pyo3-ffi/src/longobject.rs new file mode 100644 index 00000000..68b4ecba --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/longobject.rs @@ -0,0 +1,103 @@ +use crate::object::*; +use crate::pyport::Py_ssize_t; +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); + +#[inline] +pub unsafe fn PyLong_Check(op: *mut PyObject) -> c_int { + PyType_FastSubclass(Py_TYPE(op), Py_TPFLAGS_LONG_SUBCLASS) +} + +#[inline] +pub unsafe fn PyLong_CheckExact(op: *mut PyObject) -> c_int { + (Py_TYPE(op) == addr_of_mut!(PyLong_Type)) as c_int +} + +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyLong_FromLong")] + pub fn PyLong_FromLong(arg1: c_long) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyLong_FromUnsignedLong")] + pub fn PyLong_FromUnsignedLong(arg1: c_ulong) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyLong_FromSize_t")] + pub fn PyLong_FromSize_t(arg1: size_t) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyLong_FromSsize_t")] + pub fn PyLong_FromSsize_t(arg1: Py_ssize_t) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyLong_FromDouble")] + pub fn PyLong_FromDouble(arg1: c_double) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyLong_AsLong")] + pub fn PyLong_AsLong(arg1: *mut PyObject) -> c_long; + #[cfg_attr(PyPy, link_name = "PyPyLong_AsLongAndOverflow")] + pub fn PyLong_AsLongAndOverflow(arg1: *mut PyObject, arg2: *mut c_int) -> c_long; + #[cfg_attr(PyPy, link_name = "PyPyLong_AsSsize_t")] + pub fn PyLong_AsSsize_t(arg1: *mut PyObject) -> Py_ssize_t; + #[cfg_attr(PyPy, link_name = "PyPyLong_AsSize_t")] + pub fn PyLong_AsSize_t(arg1: *mut PyObject) -> size_t; + #[cfg_attr(PyPy, link_name = "PyPyLong_AsUnsignedLong")] + pub fn PyLong_AsUnsignedLong(arg1: *mut PyObject) -> c_ulong; + #[cfg_attr(PyPy, link_name = "PyPyLong_AsUnsignedLongMask")] + pub fn PyLong_AsUnsignedLongMask(arg1: *mut PyObject) -> c_ulong; + // skipped non-limited _PyLong_AsInt + pub fn PyLong_GetInfo() -> *mut PyObject; + // skipped PyLong_AS_LONG + + // skipped PyLong_FromPid + // skipped PyLong_AsPid + // skipped _Py_PARSE_INTPTR + // skipped _Py_PARSE_UINTPTR + + // skipped non-limited _PyLong_UnsignedShort_Converter + // skipped non-limited _PyLong_UnsignedInt_Converter + // skipped non-limited _PyLong_UnsignedLong_Converter + // skipped non-limited _PyLong_UnsignedLongLong_Converter + // skipped non-limited _PyLong_Size_t_Converter + + // skipped non-limited _PyLong_DigitValue + // skipped non-limited _PyLong_Frexp + + #[cfg_attr(PyPy, link_name = "PyPyLong_AsDouble")] + pub fn PyLong_AsDouble(arg1: *mut PyObject) -> c_double; + #[cfg_attr(PyPy, link_name = "PyPyLong_FromVoidPtr")] + pub fn PyLong_FromVoidPtr(arg1: *mut c_void) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyLong_AsVoidPtr")] + pub fn PyLong_AsVoidPtr(arg1: *mut PyObject) -> *mut c_void; + #[cfg_attr(PyPy, link_name = "PyPyLong_FromLongLong")] + pub fn PyLong_FromLongLong(arg1: c_longlong) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyLong_FromUnsignedLongLong")] + pub fn PyLong_FromUnsignedLongLong(arg1: c_ulonglong) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyLong_AsLongLong")] + pub fn PyLong_AsLongLong(arg1: *mut PyObject) -> c_longlong; + #[cfg_attr(PyPy, link_name = "PyPyLong_AsUnsignedLongLong")] + pub fn PyLong_AsUnsignedLongLong(arg1: *mut PyObject) -> c_ulonglong; + #[cfg_attr(PyPy, link_name = "PyPyLong_AsUnsignedLongLongMask")] + pub fn PyLong_AsUnsignedLongLongMask(arg1: *mut PyObject) -> c_ulonglong; + #[cfg_attr(PyPy, link_name = "PyPyLong_AsLongLongAndOverflow")] + pub fn PyLong_AsLongLongAndOverflow(arg1: *mut PyObject, arg2: *mut c_int) -> c_longlong; + #[cfg_attr(PyPy, link_name = "PyPyLong_FromString")] + pub fn PyLong_FromString( + arg1: *const c_char, + arg2: *mut *mut c_char, + arg3: c_int, + ) -> *mut PyObject; +} + +#[cfg(not(Py_LIMITED_API))] +extern "C" { + #[cfg_attr(PyPy, link_name = "_PyPyLong_NumBits")] + pub fn _PyLong_NumBits(obj: *mut PyObject) -> size_t; +} + +// skipped non-limited _PyLong_Format +// skipped non-limited _PyLong_FormatWriter +// skipped non-limited _PyLong_FormatBytesWriter +// skipped non-limited _PyLong_FormatAdvancedWriter + +extern "C" { + pub fn PyOS_strtoul(arg1: *const c_char, arg2: *mut *mut c_char, arg3: c_int) -> c_ulong; + pub fn PyOS_strtol(arg1: *const c_char, arg2: *mut *mut c_char, arg3: c_int) -> c_long; +} + +// skipped non-limited _PyLong_Rshift +// skipped non-limited _PyLong_Lshift diff --git a/include/pyo3/pyo3-ffi/src/marshal.rs b/include/pyo3/pyo3-ffi/src/marshal.rs new file mode 100644 index 00000000..562f6f91 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/marshal.rs @@ -0,0 +1,19 @@ +use super::{PyObject, Py_ssize_t}; +use std::os::raw::{c_char, c_int}; + +// skipped Py_MARSHAL_VERSION +// skipped PyMarshal_WriteLongToFile +// skipped PyMarshal_WriteObjectToFile + +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyMarshal_WriteObjectToString")] + pub fn PyMarshal_WriteObjectToString(object: *mut PyObject, version: c_int) -> *mut PyObject; + + // skipped non-limited PyMarshal_ReadLongFromFile + // skipped non-limited PyMarshal_ReadShortFromFile + // skipped non-limited PyMarshal_ReadObjectFromFile + // skipped non-limited PyMarshal_ReadLastObjectFromFile + + #[cfg_attr(PyPy, link_name = "PyPyMarshal_ReadObjectFromString")] + pub fn PyMarshal_ReadObjectFromString(data: *const c_char, len: Py_ssize_t) -> *mut PyObject; +} diff --git a/include/pyo3/pyo3-ffi/src/memoryobject.rs b/include/pyo3/pyo3-ffi/src/memoryobject.rs new file mode 100644 index 00000000..b7ef9e2e --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/memoryobject.rs @@ -0,0 +1,46 @@ +use crate::object::*; +use crate::pyport::Py_ssize_t; +use std::os::raw::{c_char, c_int}; +use std::ptr::addr_of_mut; + +#[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; +} + +#[inline] +pub unsafe fn PyMemoryView_Check(op: *mut PyObject) -> c_int { + (Py_TYPE(op) == addr_of_mut!(PyMemoryView_Type)) as c_int +} + +// skipped non-limited PyMemoryView_GET_BUFFER +// skipped non-limited PyMemeryView_GET_BASE + +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyMemoryView_FromObject")] + pub fn PyMemoryView_FromObject(base: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyMemoryView_FromMemory")] + pub fn PyMemoryView_FromMemory( + mem: *mut c_char, + size: Py_ssize_t, + flags: c_int, + ) -> *mut PyObject; + #[cfg(any(Py_3_11, not(Py_LIMITED_API)))] + #[cfg_attr(PyPy, link_name = "PyPyMemoryView_FromBuffer")] + pub fn PyMemoryView_FromBuffer(view: *const crate::Py_buffer) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyMemoryView_GetContiguous")] + pub fn PyMemoryView_GetContiguous( + base: *mut PyObject, + buffertype: c_int, + order: c_char, + ) -> *mut PyObject; +} + +// skipped remainder of file with comment: +/* The structs are declared here so that macros can work, but they shouldn't +be considered public. Don't access their fields directly, use the macros +and functions instead! */ diff --git a/include/pyo3/pyo3-ffi/src/methodobject.rs b/include/pyo3/pyo3-ffi/src/methodobject.rs new file mode 100644 index 00000000..bd214409 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/methodobject.rs @@ -0,0 +1,282 @@ +use crate::object::{PyObject, PyTypeObject, Py_TYPE}; +#[cfg(Py_3_9)] +use crate::PyObject_TypeCheck; +use std::os::raw::{c_char, c_int, c_void}; +use std::{mem, ptr}; + +#[cfg(all(Py_3_9, not(Py_LIMITED_API), not(GraalPy)))] +pub struct PyCFunctionObject { + pub ob_base: PyObject, + pub m_ml: *mut PyMethodDef, + pub m_self: *mut PyObject, + pub m_module: *mut PyObject, + pub m_weakreflist: *mut PyObject, + #[cfg(not(PyPy))] + pub vectorcall: Option, +} + +#[cfg_attr(windows, link(name = "pythonXY"))] +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyCFunction_Type")] + pub static mut PyCFunction_Type: PyTypeObject; +} + +#[cfg(Py_3_9)] +#[inline] +pub unsafe fn PyCFunction_CheckExact(op: *mut PyObject) -> c_int { + (Py_TYPE(op) == ptr::addr_of_mut!(PyCFunction_Type)) as c_int +} + +#[cfg(Py_3_9)] +#[inline] +pub unsafe fn PyCFunction_Check(op: *mut PyObject) -> c_int { + PyObject_TypeCheck(op, ptr::addr_of_mut!(PyCFunction_Type)) +} + +#[cfg(not(Py_3_9))] +#[inline] +pub unsafe fn PyCFunction_Check(op: *mut PyObject) -> c_int { + (Py_TYPE(op) == ptr::addr_of_mut!(PyCFunction_Type)) as c_int +} + +pub type PyCFunction = + unsafe extern "C" fn(slf: *mut PyObject, args: *mut PyObject) -> *mut PyObject; + +#[cfg(any(Py_3_10, not(Py_LIMITED_API)))] +pub type PyCFunctionFast = unsafe extern "C" fn( + slf: *mut PyObject, + args: *mut *mut PyObject, + nargs: crate::pyport::Py_ssize_t, +) -> *mut PyObject; + +#[cfg(any(Py_3_10, not(Py_LIMITED_API)))] +#[deprecated(note = "renamed to `PyCFunctionFast`")] +pub type _PyCFunctionFast = PyCFunctionFast; + +pub type PyCFunctionWithKeywords = unsafe extern "C" fn( + slf: *mut PyObject, + args: *mut PyObject, + kwds: *mut PyObject, +) -> *mut PyObject; + +#[cfg(any(Py_3_10, not(Py_LIMITED_API)))] +pub type PyCFunctionFastWithKeywords = unsafe extern "C" fn( + slf: *mut PyObject, + args: *const *mut PyObject, + nargs: crate::pyport::Py_ssize_t, + kwnames: *mut PyObject, +) -> *mut PyObject; + +#[cfg(any(Py_3_10, not(Py_LIMITED_API)))] +pub type _PyCFunctionFastWithKeywords = PyCFunctionFastWithKeywords; + +#[cfg(all(Py_3_9, not(Py_LIMITED_API)))] +pub type PyCMethod = unsafe extern "C" fn( + slf: *mut PyObject, + defining_class: *mut PyTypeObject, + args: *const *mut PyObject, + nargs: crate::pyport::Py_ssize_t, + kwnames: *mut PyObject, +) -> *mut PyObject; + +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyCFunction_GetFunction")] + pub fn PyCFunction_GetFunction(f: *mut PyObject) -> Option; + pub fn PyCFunction_GetSelf(f: *mut PyObject) -> *mut PyObject; + pub fn PyCFunction_GetFlags(f: *mut PyObject) -> c_int; + #[cfg(not(Py_3_13))] + #[cfg_attr(Py_3_9, deprecated(note = "Python 3.9"))] + pub fn PyCFunction_Call( + f: *mut PyObject, + args: *mut PyObject, + kwds: *mut PyObject, + ) -> *mut PyObject; +} + +/// Represents the [PyMethodDef](https://docs.python.org/3/c-api/structures.html#c.PyMethodDef) +/// structure. +/// +/// Note that CPython may leave fields uninitialized. You must ensure that +/// `ml_name` != NULL before dereferencing or reading other fields. +#[repr(C)] +#[derive(Copy, Clone, PartialEq, Eq)] +pub struct PyMethodDef { + pub ml_name: *const c_char, + pub ml_meth: PyMethodDefPointer, + pub ml_flags: c_int, + pub ml_doc: *const c_char, +} + +impl PyMethodDef { + pub const fn zeroed() -> PyMethodDef { + PyMethodDef { + ml_name: ptr::null(), + ml_meth: PyMethodDefPointer { + Void: ptr::null_mut(), + }, + ml_flags: 0, + ml_doc: ptr::null(), + } + } +} + +impl Default for PyMethodDef { + fn default() -> PyMethodDef { + PyMethodDef { + ml_name: ptr::null(), + ml_meth: PyMethodDefPointer { + Void: ptr::null_mut(), + }, + ml_flags: 0, + ml_doc: ptr::null(), + } + } +} + +/// Function types used to implement Python callables. +/// +/// This function pointer must be accompanied by the correct [ml_flags](PyMethodDef::ml_flags), +/// otherwise the behavior is undefined. +/// +/// See the [Python C API documentation][1] for more information. +/// +/// [1]: https://docs.python.org/3/c-api/structures.html#implementing-functions-and-methods +#[repr(C)] +#[derive(Copy, Clone, Eq)] +pub union PyMethodDefPointer { + /// This variant corresponds with [`METH_VARARGS`] *or* [`METH_NOARGS`] *or* [`METH_O`]. + pub PyCFunction: PyCFunction, + + /// This variant corresponds with [`METH_VARARGS`] | [`METH_KEYWORDS`]. + pub PyCFunctionWithKeywords: PyCFunctionWithKeywords, + + /// This variant corresponds with [`METH_FASTCALL`]. + #[cfg(any(Py_3_10, not(Py_LIMITED_API)))] + #[deprecated(note = "renamed to `PyCFunctionFast`")] + pub _PyCFunctionFast: PyCFunctionFast, + + /// This variant corresponds with [`METH_FASTCALL`]. + #[cfg(any(Py_3_10, not(Py_LIMITED_API)))] + pub PyCFunctionFast: PyCFunctionFast, + + /// This variant corresponds with [`METH_FASTCALL`] | [`METH_KEYWORDS`]. + #[cfg(any(Py_3_10, not(Py_LIMITED_API)))] + pub _PyCFunctionFastWithKeywords: PyCFunctionFastWithKeywords, + + /// This variant corresponds with [`METH_FASTCALL`] | [`METH_KEYWORDS`]. + #[cfg(any(Py_3_10, not(Py_LIMITED_API)))] + pub PyCFunctionFastWithKeywords: PyCFunctionFastWithKeywords, + + /// This variant corresponds with [`METH_METHOD`] | [`METH_FASTCALL`] | [`METH_KEYWORDS`]. + #[cfg(all(Py_3_9, not(Py_LIMITED_API)))] + pub PyCMethod: PyCMethod, + + Void: *mut c_void, +} + +impl PyMethodDefPointer { + pub fn as_ptr(&self) -> *mut c_void { + unsafe { self.Void } + } + + pub fn is_null(&self) -> bool { + self.as_ptr().is_null() + } + + pub const fn zeroed() -> PyMethodDefPointer { + PyMethodDefPointer { + Void: ptr::null_mut(), + } + } +} + +impl PartialEq for PyMethodDefPointer { + fn eq(&self, other: &Self) -> bool { + unsafe { self.Void == other.Void } + } +} + +impl std::fmt::Pointer for PyMethodDefPointer { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let ptr = unsafe { self.Void }; + std::fmt::Pointer::fmt(&ptr, f) + } +} + +const _: () = + assert!(mem::size_of::() == mem::size_of::>()); + +#[cfg(not(Py_3_9))] +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyCFunction_New")] + pub fn PyCFunction_New(ml: *mut PyMethodDef, slf: *mut PyObject) -> *mut PyObject; + + #[cfg_attr(PyPy, link_name = "PyPyCFunction_NewEx")] + pub fn PyCFunction_NewEx( + ml: *mut PyMethodDef, + slf: *mut PyObject, + module: *mut PyObject, + ) -> *mut PyObject; +} + +#[cfg(Py_3_9)] +#[inline] +pub unsafe fn PyCFunction_New(ml: *mut PyMethodDef, slf: *mut PyObject) -> *mut PyObject { + PyCFunction_NewEx(ml, slf, std::ptr::null_mut()) +} + +#[cfg(Py_3_9)] +#[inline] +pub unsafe fn PyCFunction_NewEx( + ml: *mut PyMethodDef, + slf: *mut PyObject, + module: *mut PyObject, +) -> *mut PyObject { + PyCMethod_New(ml, slf, module, std::ptr::null_mut()) +} + +#[cfg(Py_3_9)] +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyCMethod_New")] + pub fn PyCMethod_New( + ml: *mut PyMethodDef, + slf: *mut PyObject, + module: *mut PyObject, + cls: *mut PyTypeObject, + ) -> *mut PyObject; +} + +/* Flag passed to newmethodobject */ +pub const METH_VARARGS: c_int = 0x0001; +pub const METH_KEYWORDS: c_int = 0x0002; +/* METH_NOARGS and METH_O must not be combined with the flags above. */ +pub const METH_NOARGS: c_int = 0x0004; +pub const METH_O: c_int = 0x0008; + +/* METH_CLASS and METH_STATIC are a little different; these control +the construction of methods for a class. These cannot be used for +functions in modules. */ +pub const METH_CLASS: c_int = 0x0010; +pub const METH_STATIC: c_int = 0x0020; + +/* METH_COEXIST allows a method to be entered eventhough a slot has +already filled the entry. When defined, the flag allows a separate +method, "__contains__" for example, to coexist with a defined +slot like sq_contains. */ + +pub const METH_COEXIST: c_int = 0x0040; + +/* METH_FASTCALL indicates the PEP 590 Vectorcall calling format. It may +be specified alone or with METH_KEYWORDS. */ +#[cfg(any(Py_3_10, not(Py_LIMITED_API)))] +pub const METH_FASTCALL: c_int = 0x0080; + +// skipped METH_STACKLESS + +#[cfg(all(Py_3_9, not(Py_LIMITED_API)))] +pub const METH_METHOD: c_int = 0x0200; + +extern "C" { + #[cfg(not(Py_3_9))] + pub fn PyCFunction_ClearFreeList() -> c_int; +} diff --git a/include/pyo3/pyo3-ffi/src/modsupport.rs b/include/pyo3/pyo3-ffi/src/modsupport.rs new file mode 100644 index 00000000..6da2795b --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/modsupport.rs @@ -0,0 +1,151 @@ +use crate::methodobject::PyMethodDef; +use crate::moduleobject::PyModuleDef; +use crate::object::PyObject; +use crate::pyport::Py_ssize_t; +use std::os::raw::{c_char, c_int, c_long}; + +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyArg_Parse")] + pub fn PyArg_Parse(arg1: *mut PyObject, arg2: *const c_char, ...) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyArg_ParseTuple")] + pub fn PyArg_ParseTuple(arg1: *mut PyObject, arg2: *const c_char, ...) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyArg_ParseTupleAndKeywords")] + pub fn PyArg_ParseTupleAndKeywords( + arg1: *mut PyObject, + arg2: *mut PyObject, + arg3: *const c_char, + #[cfg(not(Py_3_13))] arg4: *mut *mut c_char, + #[cfg(Py_3_13)] arg4: *const *const c_char, + ... + ) -> c_int; + + // skipped PyArg_VaParse + // skipped PyArg_VaParseTupleAndKeywords + + pub fn PyArg_ValidateKeywordArguments(arg1: *mut PyObject) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyArg_UnpackTuple")] + pub fn PyArg_UnpackTuple( + arg1: *mut PyObject, + arg2: *const c_char, + arg3: Py_ssize_t, + arg4: Py_ssize_t, + ... + ) -> c_int; + + #[cfg_attr(PyPy, link_name = "PyPy_BuildValue")] + pub fn Py_BuildValue(arg1: *const c_char, ...) -> *mut PyObject; + // 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; + + #[cfg(Py_3_10)] + #[cfg_attr(PyPy, link_name = "PyPyModule_AddObjectRef")] + pub fn PyModule_AddObjectRef( + module: *mut PyObject, + name: *const c_char, + value: *mut PyObject, + ) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyModule_AddObject")] + pub fn PyModule_AddObject( + module: *mut PyObject, + name: *const c_char, + value: *mut PyObject, + ) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyModule_AddIntConstant")] + pub fn PyModule_AddIntConstant( + module: *mut PyObject, + name: *const c_char, + value: c_long, + ) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyModule_AddStringConstant")] + pub fn PyModule_AddStringConstant( + module: *mut PyObject, + name: *const c_char, + value: *const c_char, + ) -> c_int; + #[cfg(any(Py_3_10, all(Py_3_9, not(Py_LIMITED_API))))] + #[cfg_attr(PyPy, link_name = "PyPyModule_AddType")] + pub fn PyModule_AddType( + module: *mut PyObject, + type_: *mut crate::object::PyTypeObject, + ) -> c_int; + // skipped PyModule_AddIntMacro + // skipped PyModule_AddStringMacro + pub fn PyModule_SetDocString(arg1: *mut PyObject, arg2: *const c_char) -> c_int; + pub fn PyModule_AddFunctions(arg1: *mut PyObject, arg2: *mut PyMethodDef) -> c_int; + pub fn PyModule_ExecDef(module: *mut PyObject, def: *mut PyModuleDef) -> c_int; +} + +pub const Py_CLEANUP_SUPPORTED: i32 = 0x2_0000; + +pub const PYTHON_API_VERSION: i32 = 1013; +pub const PYTHON_ABI_VERSION: i32 = 3; + +extern "C" { + #[cfg(not(py_sys_config = "Py_TRACE_REFS"))] + #[cfg_attr(PyPy, link_name = "PyPyModule_Create2")] + pub fn PyModule_Create2(module: *mut PyModuleDef, apiver: c_int) -> *mut PyObject; + + #[cfg(py_sys_config = "Py_TRACE_REFS")] + fn PyModule_Create2TraceRefs(module: *mut PyModuleDef, apiver: c_int) -> *mut PyObject; + + #[cfg(not(py_sys_config = "Py_TRACE_REFS"))] + pub fn PyModule_FromDefAndSpec2( + def: *mut PyModuleDef, + spec: *mut PyObject, + module_api_version: c_int, + ) -> *mut PyObject; + + #[cfg(py_sys_config = "Py_TRACE_REFS")] + fn PyModule_FromDefAndSpec2TraceRefs( + def: *mut PyModuleDef, + spec: *mut PyObject, + module_api_version: c_int, + ) -> *mut PyObject; +} + +#[cfg(py_sys_config = "Py_TRACE_REFS")] +#[inline] +pub unsafe fn PyModule_Create2(module: *mut PyModuleDef, apiver: c_int) -> *mut PyObject { + PyModule_Create2TraceRefs(module, apiver) +} + +#[cfg(py_sys_config = "Py_TRACE_REFS")] +#[inline] +pub unsafe fn PyModule_FromDefAndSpec2( + def: *mut PyModuleDef, + spec: *mut PyObject, + module_api_version: c_int, +) -> *mut PyObject { + PyModule_FromDefAndSpec2TraceRefs(def, spec, module_api_version) +} + +#[inline] +pub unsafe fn PyModule_Create(module: *mut PyModuleDef) -> *mut PyObject { + PyModule_Create2( + module, + if cfg!(Py_LIMITED_API) { + PYTHON_ABI_VERSION + } else { + PYTHON_API_VERSION + }, + ) +} + +#[inline] +pub unsafe fn PyModule_FromDefAndSpec(def: *mut PyModuleDef, spec: *mut PyObject) -> *mut PyObject { + PyModule_FromDefAndSpec2( + def, + spec, + if cfg!(Py_LIMITED_API) { + PYTHON_ABI_VERSION + } else { + PYTHON_API_VERSION + }, + ) +} diff --git a/include/pyo3/pyo3-ffi/src/moduleobject.rs b/include/pyo3/pyo3-ffi/src/moduleobject.rs new file mode 100644 index 00000000..ff6458f4 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/moduleobject.rs @@ -0,0 +1,112 @@ +use crate::methodobject::PyMethodDef; +use crate::object::*; +use crate::pyport::Py_ssize_t; +use std::os::raw::{c_char, c_int, c_void}; +use std::ptr::addr_of_mut; + +#[cfg_attr(windows, link(name = "pythonXY"))] +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyModule_Type")] + pub static mut PyModule_Type: PyTypeObject; +} + +#[inline] +pub unsafe fn PyModule_Check(op: *mut PyObject) -> c_int { + PyObject_TypeCheck(op, addr_of_mut!(PyModule_Type)) +} + +#[inline] +pub unsafe fn PyModule_CheckExact(op: *mut PyObject) -> c_int { + (Py_TYPE(op) == addr_of_mut!(PyModule_Type)) as c_int +} + +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyModule_NewObject")] + pub fn PyModule_NewObject(name: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyModule_New")] + pub fn PyModule_New(name: *const c_char) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyModule_GetDict")] + pub fn PyModule_GetDict(arg1: *mut PyObject) -> *mut PyObject; + #[cfg(not(PyPy))] + pub fn PyModule_GetNameObject(arg1: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyModule_GetName")] + pub fn PyModule_GetName(arg1: *mut PyObject) -> *const c_char; + #[cfg(not(all(windows, PyPy)))] + #[deprecated(note = "Python 3.2")] + pub fn PyModule_GetFilename(arg1: *mut PyObject) -> *const c_char; + #[cfg(not(PyPy))] + pub fn PyModule_GetFilenameObject(arg1: *mut PyObject) -> *mut PyObject; + // skipped non-limited _PyModule_Clear + // skipped non-limited _PyModule_ClearDict + // skipped non-limited _PyModuleSpec_IsInitializing + #[cfg_attr(PyPy, link_name = "PyPyModule_GetDef")] + pub fn PyModule_GetDef(arg1: *mut PyObject) -> *mut PyModuleDef; + #[cfg_attr(PyPy, link_name = "PyPyModule_GetState")] + pub fn PyModule_GetState(arg1: *mut PyObject) -> *mut c_void; + #[cfg_attr(PyPy, link_name = "PyPyModuleDef_Init")] + pub fn PyModuleDef_Init(arg1: *mut PyModuleDef) -> *mut PyObject; +} + +#[cfg_attr(windows, link(name = "pythonXY"))] +extern "C" { + pub static mut PyModuleDef_Type: PyTypeObject; +} + +#[repr(C)] +pub struct PyModuleDef_Base { + pub ob_base: PyObject, + pub m_init: Option *mut PyObject>, + pub m_index: Py_ssize_t, + pub m_copy: *mut PyObject, +} + +#[allow(clippy::declare_interior_mutable_const)] +pub const PyModuleDef_HEAD_INIT: PyModuleDef_Base = PyModuleDef_Base { + ob_base: PyObject_HEAD_INIT, + m_init: None, + m_index: 0, + m_copy: std::ptr::null_mut(), +}; + +#[repr(C)] +#[derive(Copy, Clone, Eq, PartialEq)] +pub struct PyModuleDef_Slot { + pub slot: c_int, + pub value: *mut c_void, +} + +impl Default for PyModuleDef_Slot { + fn default() -> PyModuleDef_Slot { + PyModuleDef_Slot { + slot: 0, + value: std::ptr::null_mut(), + } + } +} + +pub const Py_mod_create: c_int = 1; +pub const Py_mod_exec: c_int = 2; +#[cfg(Py_3_12)] +pub const Py_mod_multiple_interpreters: c_int = 3; + +#[cfg(Py_3_12)] +pub const Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED: *mut c_void = 0 as *mut c_void; +#[cfg(Py_3_12)] +pub const Py_MOD_MULTIPLE_INTERPRETERS_SUPPORTED: *mut c_void = 1 as *mut c_void; +#[cfg(Py_3_12)] +pub const Py_MOD_PER_INTERPRETER_GIL_SUPPORTED: *mut c_void = 2 as *mut c_void; + +// skipped non-limited _Py_mod_LAST_SLOT + +#[repr(C)] +pub struct PyModuleDef { + pub m_base: PyModuleDef_Base, + pub m_name: *const c_char, + pub m_doc: *const c_char, + pub m_size: Py_ssize_t, + pub m_methods: *mut PyMethodDef, + pub m_slots: *mut PyModuleDef_Slot, + pub m_traverse: Option, + pub m_clear: Option, + pub m_free: Option, +} diff --git a/include/pyo3/pyo3-ffi/src/object.rs b/include/pyo3/pyo3-ffi/src/object.rs new file mode 100644 index 00000000..d2fa1930 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/object.rs @@ -0,0 +1,942 @@ +use crate::pyport::{Py_hash_t, Py_ssize_t}; +#[cfg(Py_GIL_DISABLED)] +use crate::PyMutex; +#[cfg(Py_GIL_DISABLED)] +use std::marker::PhantomPinned; +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}; + +#[cfg(Py_LIMITED_API)] +opaque_struct!(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 + +#[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 + +#[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. +pub union PyObjectObRefcnt { + pub ob_refcnt: Py_ssize_t, + #[cfg(target_pointer_width = "64")] + pub ob_refcnt_split: [crate::PY_UINT32_T; 2], +} + +#[cfg(all(Py_3_12, not(Py_GIL_DISABLED)))] +impl std::fmt::Debug for PyObjectObRefcnt { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", unsafe { self.ob_refcnt }) + } +} + +#[cfg(all(not(Py_3_12), not(Py_GIL_DISABLED)))] +pub type PyObjectObRefcnt = Py_ssize_t; + +#[repr(C)] +#[derive(Debug)] +pub struct PyObject { + #[cfg(py_sys_config = "Py_TRACE_REFS")] + pub _ob_next: *mut PyObject, + #[cfg(py_sys_config = "Py_TRACE_REFS")] + pub _ob_prev: *mut PyObject, + #[cfg(Py_GIL_DISABLED)] + pub ob_tid: libc::uintptr_t, + #[cfg(Py_GIL_DISABLED)] + pub _padding: u16, + #[cfg(Py_GIL_DISABLED)] + pub ob_mutex: PyMutex, // per-object lock + #[cfg(Py_GIL_DISABLED)] + pub ob_gc_bits: u8, // gc-related state + #[cfg(Py_GIL_DISABLED)] + pub ob_ref_local: AtomicU32, // local reference count + #[cfg(Py_GIL_DISABLED)] + pub ob_ref_shared: AtomicIsize, // shared reference count + #[cfg(not(Py_GIL_DISABLED))] + pub ob_refcnt: PyObjectObRefcnt, + #[cfg(PyPy)] + pub ob_pypy_link: Py_ssize_t, + pub ob_type: *mut PyTypeObject, +} + +// skipped private _PyObject_CAST + +#[repr(C)] +#[derive(Debug)] +pub struct PyVarObject { + pub ob_base: PyObject, + #[cfg(not(GraalPy))] + pub ob_size: Py_ssize_t, +} + +// skipped private _PyVarObject_CAST + +#[inline] +#[cfg(not(all(PyPy, Py_3_10)))] +#[cfg_attr(docsrs, doc(cfg(all())))] +pub unsafe fn Py_Is(x: *mut PyObject, y: *mut PyObject) -> c_int { + (x == y).into() +} + +#[cfg(all(PyPy, Py_3_10))] +#[cfg_attr(docsrs, doc(cfg(all())))] +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPy_Is")] + pub fn Py_Is(x: *mut PyObject, y: *mut PyObject) -> c_int; +} + +// skipped private _Py_GetThreadLocal_Addr + +// skipped private _Py_ThreadId + +// skipped private _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(all(not(Py_GIL_DISABLED), not(Py_3_12), GraalPy))] + { + _Py_REFCNT(ob) + } +} + +#[inline] +pub unsafe fn Py_TYPE(ob: *mut PyObject) -> *mut PyTypeObject { + #[cfg(not(GraalPy))] + return (*ob).ob_type; + #[cfg(GraalPy)] + return _Py_TYPE(ob); +} + +#[cfg_attr(windows, link(name = "pythonXY"))] +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyLong_Type")] + pub static mut PyLong_Type: PyTypeObject; + #[cfg_attr(PyPy, link_name = "PyPyBool_Type")] + pub static mut PyBool_Type: PyTypeObject; +} + +#[inline] +pub unsafe fn Py_SIZE(ob: *mut PyObject) -> Py_ssize_t { + #[cfg(not(GraalPy))] + { + debug_assert_ne!((*ob).ob_type, std::ptr::addr_of_mut!(crate::PyLong_Type)); + debug_assert_ne!((*ob).ob_type, std::ptr::addr_of_mut!(crate::PyBool_Type)); + (*ob.cast::()).ob_size + } + #[cfg(GraalPy)] + _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 + +pub type unaryfunc = unsafe extern "C" fn(*mut PyObject) -> *mut PyObject; +pub type binaryfunc = unsafe extern "C" fn(*mut PyObject, *mut PyObject) -> *mut PyObject; +pub type ternaryfunc = + unsafe extern "C" fn(*mut PyObject, *mut PyObject, *mut PyObject) -> *mut PyObject; +pub type inquiry = unsafe extern "C" fn(*mut PyObject) -> c_int; +pub type lenfunc = unsafe extern "C" fn(*mut PyObject) -> Py_ssize_t; +pub type ssizeargfunc = unsafe extern "C" fn(*mut PyObject, Py_ssize_t) -> *mut PyObject; +pub type ssizessizeargfunc = + unsafe extern "C" fn(*mut PyObject, Py_ssize_t, Py_ssize_t) -> *mut PyObject; +pub type ssizeobjargproc = unsafe extern "C" fn(*mut PyObject, Py_ssize_t, *mut PyObject) -> c_int; +pub type ssizessizeobjargproc = + unsafe extern "C" fn(*mut PyObject, Py_ssize_t, Py_ssize_t, arg4: *mut PyObject) -> c_int; +pub type objobjargproc = unsafe extern "C" fn(*mut PyObject, *mut PyObject, *mut PyObject) -> c_int; + +pub type objobjproc = unsafe extern "C" fn(*mut PyObject, *mut PyObject) -> c_int; +pub type visitproc = unsafe extern "C" fn(object: *mut PyObject, arg: *mut c_void) -> c_int; +pub type traverseproc = + unsafe extern "C" fn(slf: *mut PyObject, visit: visitproc, arg: *mut c_void) -> c_int; + +pub type freefunc = unsafe extern "C" fn(*mut c_void); +pub type destructor = unsafe extern "C" fn(*mut PyObject); +pub type getattrfunc = unsafe extern "C" fn(*mut PyObject, *mut c_char) -> *mut PyObject; +pub type getattrofunc = unsafe extern "C" fn(*mut PyObject, *mut PyObject) -> *mut PyObject; +pub type setattrfunc = unsafe extern "C" fn(*mut PyObject, *mut c_char, *mut PyObject) -> c_int; +pub type setattrofunc = unsafe extern "C" fn(*mut PyObject, *mut PyObject, *mut PyObject) -> c_int; +pub type reprfunc = unsafe extern "C" fn(*mut PyObject) -> *mut PyObject; +pub type hashfunc = unsafe extern "C" fn(*mut PyObject) -> Py_hash_t; +pub type richcmpfunc = unsafe extern "C" fn(*mut PyObject, *mut PyObject, c_int) -> *mut PyObject; +pub type getiterfunc = unsafe extern "C" fn(*mut PyObject) -> *mut PyObject; +pub type iternextfunc = unsafe extern "C" fn(*mut PyObject) -> *mut PyObject; +pub type descrgetfunc = + unsafe extern "C" fn(*mut PyObject, *mut PyObject, *mut PyObject) -> *mut PyObject; +pub type descrsetfunc = unsafe extern "C" fn(*mut PyObject, *mut PyObject, *mut PyObject) -> c_int; +pub type initproc = unsafe extern "C" fn(*mut PyObject, *mut PyObject, *mut PyObject) -> c_int; +pub type newfunc = + unsafe extern "C" fn(*mut PyTypeObject, *mut PyObject, *mut PyObject) -> *mut PyObject; +pub type allocfunc = unsafe extern "C" fn(*mut PyTypeObject, Py_ssize_t) -> *mut PyObject; + +#[cfg(Py_3_8)] +pub type vectorcallfunc = unsafe extern "C" fn( + callable: *mut PyObject, + args: *const *mut PyObject, + nargsf: libc::size_t, + kwnames: *mut PyObject, +) -> *mut PyObject; + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct PyType_Slot { + pub slot: c_int, + pub pfunc: *mut c_void, +} + +impl Default for PyType_Slot { + fn default() -> PyType_Slot { + unsafe { mem::zeroed() } + } +} + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct PyType_Spec { + pub name: *const c_char, + pub basicsize: c_int, + pub itemsize: c_int, + pub flags: c_uint, + pub slots: *mut PyType_Slot, +} + +impl Default for PyType_Spec { + fn default() -> PyType_Spec { + unsafe { mem::zeroed() } + } +} + +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyType_FromSpec")] + pub fn PyType_FromSpec(arg1: *mut PyType_Spec) -> *mut PyObject; + + #[cfg_attr(PyPy, link_name = "PyPyType_FromSpecWithBases")] + pub fn PyType_FromSpecWithBases(arg1: *mut PyType_Spec, arg2: *mut PyObject) -> *mut PyObject; + + #[cfg_attr(PyPy, link_name = "PyPyType_GetSlot")] + pub fn PyType_GetSlot(arg1: *mut PyTypeObject, arg2: c_int) -> *mut c_void; + + #[cfg(any(Py_3_10, all(Py_3_9, not(Py_LIMITED_API))))] + #[cfg_attr(PyPy, link_name = "PyPyType_FromModuleAndSpec")] + pub fn PyType_FromModuleAndSpec( + module: *mut PyObject, + spec: *mut PyType_Spec, + bases: *mut PyObject, + ) -> *mut PyObject; + + #[cfg(any(Py_3_10, all(Py_3_9, not(Py_LIMITED_API))))] + #[cfg_attr(PyPy, link_name = "PyPyType_GetModule")] + pub fn PyType_GetModule(arg1: *mut PyTypeObject) -> *mut PyObject; + + #[cfg(any(Py_3_10, all(Py_3_9, not(Py_LIMITED_API))))] + #[cfg_attr(PyPy, link_name = "PyPyType_GetModuleState")] + pub fn PyType_GetModuleState(arg1: *mut PyTypeObject) -> *mut c_void; + + #[cfg(Py_3_11)] + #[cfg_attr(PyPy, link_name = "PyPyType_GetName")] + pub fn PyType_GetName(arg1: *mut PyTypeObject) -> *mut PyObject; + + #[cfg(Py_3_11)] + #[cfg_attr(PyPy, link_name = "PyPyType_GetQualName")] + pub fn PyType_GetQualName(arg1: *mut PyTypeObject) -> *mut PyObject; + + #[cfg(Py_3_13)] + #[cfg_attr(PyPy, link_name = "PyPyType_GetFullyQualifiedName")] + pub fn PyType_GetFullyQualifiedName(arg1: *mut PyTypeObject) -> *mut PyObject; + + #[cfg(Py_3_13)] + #[cfg_attr(PyPy, link_name = "PyPyType_GetModuleName")] + pub fn PyType_GetModuleName(arg1: *mut PyTypeObject) -> *mut PyObject; + + #[cfg(Py_3_12)] + #[cfg_attr(PyPy, link_name = "PyPyType_FromMetaclass")] + pub fn PyType_FromMetaclass( + metaclass: *mut PyTypeObject, + module: *mut PyObject, + spec: *mut PyType_Spec, + bases: *mut PyObject, + ) -> *mut PyObject; + + #[cfg(Py_3_12)] + #[cfg_attr(PyPy, link_name = "PyPyObject_GetTypeData")] + pub fn PyObject_GetTypeData(obj: *mut PyObject, cls: *mut PyTypeObject) -> *mut c_void; + + #[cfg(Py_3_12)] + #[cfg_attr(PyPy, link_name = "PyPyObject_GetTypeDataSize")] + pub fn PyObject_GetTypeDataSize(cls: *mut PyTypeObject) -> Py_ssize_t; + + #[cfg_attr(PyPy, link_name = "PyPyType_IsSubtype")] + pub fn PyType_IsSubtype(a: *mut PyTypeObject, b: *mut PyTypeObject) -> c_int; +} + +#[inline] +pub unsafe fn PyObject_TypeCheck(ob: *mut PyObject, tp: *mut PyTypeObject) -> c_int { + (Py_IS_TYPE(ob, tp) != 0 || PyType_IsSubtype(Py_TYPE(ob), tp) != 0) as c_int +} + +#[cfg_attr(windows, link(name = "pythonXY"))] +extern "C" { + /// built-in 'type' + #[cfg_attr(PyPy, link_name = "PyPyType_Type")] + pub static mut PyType_Type: PyTypeObject; + /// built-in 'object' + #[cfg_attr(PyPy, link_name = "PyPyBaseObject_Type")] + pub static mut PyBaseObject_Type: PyTypeObject; + /// built-in 'super' + pub static mut PySuper_Type: PyTypeObject; +} + +extern "C" { + pub fn PyType_GetFlags(arg1: *mut PyTypeObject) -> c_ulong; + + #[cfg_attr(PyPy, link_name = "PyPyType_Ready")] + pub fn PyType_Ready(t: *mut PyTypeObject) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyType_GenericAlloc")] + pub fn PyType_GenericAlloc(t: *mut PyTypeObject, nitems: Py_ssize_t) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyType_GenericNew")] + pub fn PyType_GenericNew( + t: *mut PyTypeObject, + args: *mut PyObject, + kwds: *mut PyObject, + ) -> *mut PyObject; + pub fn PyType_ClearCache() -> c_uint; + #[cfg_attr(PyPy, link_name = "PyPyType_Modified")] + pub fn PyType_Modified(t: *mut PyTypeObject); + + #[cfg_attr(PyPy, link_name = "PyPyObject_Repr")] + pub fn PyObject_Repr(o: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyObject_Str")] + pub fn PyObject_Str(o: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyObject_ASCII")] + pub fn PyObject_ASCII(arg1: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyObject_Bytes")] + pub fn PyObject_Bytes(arg1: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyObject_RichCompare")] + pub fn PyObject_RichCompare( + arg1: *mut PyObject, + arg2: *mut PyObject, + arg3: c_int, + ) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyObject_RichCompareBool")] + pub fn PyObject_RichCompareBool(arg1: *mut PyObject, arg2: *mut PyObject, arg3: c_int) + -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyObject_GetAttrString")] + pub fn PyObject_GetAttrString(arg1: *mut PyObject, arg2: *const c_char) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyObject_SetAttrString")] + pub fn PyObject_SetAttrString( + arg1: *mut PyObject, + 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_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")] + pub fn PyObject_HasAttrString(arg1: *mut PyObject, arg2: *const c_char) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyObject_GetAttr")] + pub fn PyObject_GetAttr(arg1: *mut PyObject, arg2: *mut PyObject) -> *mut PyObject; + #[cfg(Py_3_13)] + #[cfg_attr(PyPy, link_name = "PyPyObject_GetOptionalAttr")] + pub fn PyObject_GetOptionalAttr( + arg1: *mut PyObject, + arg2: *mut PyObject, + arg3: *mut *mut PyObject, + ) -> c_int; + #[cfg(Py_3_13)] + #[cfg_attr(PyPy, link_name = "PyPyObject_GetOptionalAttrString")] + pub fn PyObject_GetOptionalAttrString( + arg1: *mut PyObject, + arg2: *const c_char, + arg3: *mut *mut PyObject, + ) -> c_int; + #[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_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")] + pub fn PyObject_HasAttr(arg1: *mut PyObject, arg2: *mut PyObject) -> c_int; + #[cfg(Py_3_13)] + #[cfg_attr(PyPy, link_name = "PyPyObject_HasAttrWithError")] + pub fn PyObject_HasAttrWithError(arg1: *mut PyObject, arg2: *mut PyObject) -> c_int; + #[cfg(Py_3_13)] + #[cfg_attr(PyPy, link_name = "PyPyObject_HasAttrStringWithError")] + pub fn PyObject_HasAttrStringWithError(arg1: *mut PyObject, arg2: *const c_char) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyObject_SelfIter")] + pub fn PyObject_SelfIter(arg1: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyObject_GenericGetAttr")] + pub fn PyObject_GenericGetAttr(arg1: *mut PyObject, arg2: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyObject_GenericSetAttr")] + pub fn PyObject_GenericSetAttr( + arg1: *mut PyObject, + arg2: *mut PyObject, + arg3: *mut PyObject, + ) -> c_int; + #[cfg(not(all(Py_LIMITED_API, not(Py_3_10))))] + #[cfg_attr(PyPy, link_name = "PyPyObject_GenericGetDict")] + pub fn PyObject_GenericGetDict(arg1: *mut PyObject, arg2: *mut c_void) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyObject_GenericSetDict")] + pub fn PyObject_GenericSetDict( + arg1: *mut PyObject, + arg2: *mut PyObject, + arg3: *mut c_void, + ) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyObject_Hash")] + pub fn PyObject_Hash(arg1: *mut PyObject) -> Py_hash_t; + #[cfg_attr(PyPy, link_name = "PyPyObject_HashNotImplemented")] + pub fn PyObject_HashNotImplemented(arg1: *mut PyObject) -> Py_hash_t; + #[cfg_attr(PyPy, link_name = "PyPyObject_IsTrue")] + pub fn PyObject_IsTrue(arg1: *mut PyObject) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyObject_Not")] + pub fn PyObject_Not(arg1: *mut PyObject) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyCallable_Check")] + pub fn PyCallable_Check(arg1: *mut PyObject) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyObject_ClearWeakRefs")] + pub fn PyObject_ClearWeakRefs(arg1: *mut PyObject); + + #[cfg_attr(PyPy, link_name = "PyPyObject_Dir")] + pub fn PyObject_Dir(arg1: *mut PyObject) -> *mut PyObject; + pub fn Py_ReprEnter(arg1: *mut PyObject) -> c_int; + pub fn Py_ReprLeave(arg1: *mut PyObject); +} + +// Flag bits for printing: +pub const Py_PRINT_RAW: c_int = 1; // No string quotes etc. + +// skipped because is a private API +// const _Py_TPFLAGS_STATIC_BUILTIN: c_ulong = 1 << 1; + +#[cfg(all(Py_3_12, not(Py_LIMITED_API)))] +pub const Py_TPFLAGS_MANAGED_WEAKREF: c_ulong = 1 << 3; + +#[cfg(all(Py_3_11, not(Py_LIMITED_API)))] +pub const Py_TPFLAGS_MANAGED_DICT: c_ulong = 1 << 4; + +#[cfg(all(Py_3_10, not(Py_LIMITED_API)))] +pub const Py_TPFLAGS_SEQUENCE: c_ulong = 1 << 5; + +#[cfg(all(Py_3_10, not(Py_LIMITED_API)))] +pub const Py_TPFLAGS_MAPPING: c_ulong = 1 << 6; + +#[cfg(Py_3_10)] +pub const Py_TPFLAGS_DISALLOW_INSTANTIATION: c_ulong = 1 << 7; + +#[cfg(Py_3_10)] +pub const Py_TPFLAGS_IMMUTABLETYPE: c_ulong = 1 << 8; + +/// Set if the type object is dynamically allocated +pub const Py_TPFLAGS_HEAPTYPE: c_ulong = 1 << 9; + +/// Set if the type allows subclassing +pub const Py_TPFLAGS_BASETYPE: c_ulong = 1 << 10; + +/// Set if the type implements the vectorcall protocol (PEP 590) +#[cfg(any(Py_3_12, all(Py_3_8, not(Py_LIMITED_API))))] +pub const Py_TPFLAGS_HAVE_VECTORCALL: c_ulong = 1 << 11; +// skipped backwards-compatibility alias _Py_TPFLAGS_HAVE_VECTORCALL + +/// Set if the type is 'ready' -- fully initialized +pub const Py_TPFLAGS_READY: c_ulong = 1 << 12; + +/// Set while the type is being 'readied', to prevent recursive ready calls +pub const Py_TPFLAGS_READYING: c_ulong = 1 << 13; + +/// Objects support garbage collection (see objimp.h) +pub const Py_TPFLAGS_HAVE_GC: c_ulong = 1 << 14; + +const Py_TPFLAGS_HAVE_STACKLESS_EXTENSION: c_ulong = 0; + +#[cfg(Py_3_8)] +pub const Py_TPFLAGS_METHOD_DESCRIPTOR: c_ulong = 1 << 17; + +pub const Py_TPFLAGS_VALID_VERSION_TAG: c_ulong = 1 << 19; + +/* Type is abstract and cannot be instantiated */ +pub const Py_TPFLAGS_IS_ABSTRACT: c_ulong = 1 << 20; + +// skipped non-limited / 3.10 Py_TPFLAGS_HAVE_AM_SEND +#[cfg(Py_3_12)] +pub const Py_TPFLAGS_ITEMS_AT_END: c_ulong = 1 << 23; + +/* These flags are used to determine if a type is a subclass. */ +pub const Py_TPFLAGS_LONG_SUBCLASS: c_ulong = 1 << 24; +pub const Py_TPFLAGS_LIST_SUBCLASS: c_ulong = 1 << 25; +pub const Py_TPFLAGS_TUPLE_SUBCLASS: c_ulong = 1 << 26; +pub const Py_TPFLAGS_BYTES_SUBCLASS: c_ulong = 1 << 27; +pub const Py_TPFLAGS_UNICODE_SUBCLASS: c_ulong = 1 << 28; +pub const Py_TPFLAGS_DICT_SUBCLASS: c_ulong = 1 << 29; +pub const Py_TPFLAGS_BASE_EXC_SUBCLASS: c_ulong = 1 << 30; +pub const Py_TPFLAGS_TYPE_SUBCLASS: c_ulong = 1 << 31; + +pub const Py_TPFLAGS_DEFAULT: c_ulong = if cfg!(Py_3_10) { + Py_TPFLAGS_HAVE_STACKLESS_EXTENSION +} else { + Py_TPFLAGS_HAVE_STACKLESS_EXTENSION | Py_TPFLAGS_HAVE_VERSION_TAG +}; + +pub const Py_TPFLAGS_HAVE_FINALIZE: c_ulong = 1; +pub const Py_TPFLAGS_HAVE_VERSION_TAG: c_ulong = 1 << 18; + +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)] +pub const Py_CONSTANT_FALSE: c_uint = 1; +#[cfg(Py_3_13)] +pub const Py_CONSTANT_TRUE: c_uint = 2; +#[cfg(Py_3_13)] +pub const Py_CONSTANT_ELLIPSIS: c_uint = 3; +#[cfg(Py_3_13)] +pub const Py_CONSTANT_NOT_IMPLEMENTED: c_uint = 4; +#[cfg(Py_3_13)] +pub const Py_CONSTANT_ZERO: c_uint = 5; +#[cfg(Py_3_13)] +pub const Py_CONSTANT_ONE: c_uint = 6; +#[cfg(Py_3_13)] +pub const Py_CONSTANT_EMPTY_STR: c_uint = 7; +#[cfg(Py_3_13)] +pub const Py_CONSTANT_EMPTY_BYTES: c_uint = 8; +#[cfg(Py_3_13)] +pub const Py_CONSTANT_EMPTY_TUPLE: c_uint = 9; + +extern "C" { + #[cfg(Py_3_13)] + #[cfg_attr(PyPy, link_name = "PyPy_GetConstant")] + pub fn Py_GetConstant(constant_id: c_uint) -> *mut PyObject; + #[cfg(Py_3_13)] + #[cfg_attr(PyPy, link_name = "PyPy_GetConstantBorrowed")] + pub fn Py_GetConstantBorrowed(constant_id: c_uint) -> *mut PyObject; +} + +#[cfg_attr(windows, link(name = "pythonXY"))] +extern "C" { + #[cfg(all(not(GraalPy), not(all(Py_3_13, Py_LIMITED_API))))] + #[cfg_attr(PyPy, link_name = "_PyPy_NoneStruct")] + static mut _Py_NoneStruct: PyObject; + + #[cfg(GraalPy)] + static mut _Py_NoneStructReference: *mut PyObject; +} + +#[inline] +pub unsafe fn Py_None() -> *mut PyObject { + #[cfg(all(not(GraalPy), all(Py_3_13, Py_LIMITED_API)))] + return Py_GetConstantBorrowed(Py_CONSTANT_NONE); + + #[cfg(all(not(GraalPy), not(all(Py_3_13, Py_LIMITED_API))))] + return ptr::addr_of_mut!(_Py_NoneStruct); + + #[cfg(GraalPy)] + return _Py_NoneStructReference; +} + +#[inline] +pub unsafe fn Py_IsNone(x: *mut PyObject) -> c_int { + Py_Is(x, Py_None()) +} + +// skipped Py_RETURN_NONE + +#[cfg_attr(windows, link(name = "pythonXY"))] +extern "C" { + #[cfg(all(not(GraalPy), not(all(Py_3_13, Py_LIMITED_API))))] + #[cfg_attr(PyPy, link_name = "_PyPy_NotImplementedStruct")] + static mut _Py_NotImplementedStruct: PyObject; + + #[cfg(GraalPy)] + static mut _Py_NotImplementedStructReference: *mut PyObject; +} + +#[inline] +pub unsafe fn Py_NotImplemented() -> *mut PyObject { + #[cfg(all(not(GraalPy), all(Py_3_13, Py_LIMITED_API)))] + return Py_GetConstantBorrowed(Py_CONSTANT_NOT_IMPLEMENTED); + + #[cfg(all(not(GraalPy), not(all(Py_3_13, Py_LIMITED_API))))] + return ptr::addr_of_mut!(_Py_NotImplementedStruct); + + #[cfg(GraalPy)] + return _Py_NotImplementedStructReference; +} + +// skipped Py_RETURN_NOTIMPLEMENTED + +/* Rich comparison opcodes */ +pub const Py_LT: c_int = 0; +pub const Py_LE: c_int = 1; +pub const Py_EQ: c_int = 2; +pub const Py_NE: c_int = 3; +pub const Py_GT: c_int = 4; +pub const Py_GE: c_int = 5; + +#[cfg(Py_3_10)] +#[repr(C)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum PySendResult { + PYGEN_RETURN = 0, + PYGEN_ERROR = -1, + PYGEN_NEXT = 1, +} + +// skipped Py_RETURN_RICHCOMPARE + +#[inline] +pub unsafe fn PyType_HasFeature(ty: *mut PyTypeObject, feature: c_ulong) -> c_int { + #[cfg(Py_LIMITED_API)] + let flags = PyType_GetFlags(ty); + + #[cfg(all(not(Py_LIMITED_API), Py_GIL_DISABLED))] + let flags = (*ty).tp_flags.load(std::sync::atomic::Ordering::Relaxed); + + #[cfg(all(not(Py_LIMITED_API), not(Py_GIL_DISABLED)))] + let flags = (*ty).tp_flags; + + ((flags & feature) != 0) as c_int +} + +#[inline] +pub unsafe fn PyType_FastSubclass(t: *mut PyTypeObject, f: c_ulong) -> c_int { + PyType_HasFeature(t, f) +} + +#[inline] +pub unsafe fn PyType_Check(op: *mut PyObject) -> c_int { + PyType_FastSubclass(Py_TYPE(op), Py_TPFLAGS_TYPE_SUBCLASS) +} + +// skipped _PyType_CAST + +#[inline] +pub unsafe fn PyType_CheckExact(op: *mut PyObject) -> c_int { + Py_IS_TYPE(op, ptr::addr_of_mut!(PyType_Type)) +} + +extern "C" { + #[cfg(any(Py_3_13, all(Py_3_11, not(Py_LIMITED_API))))] + #[cfg_attr(PyPy, link_name = "PyPyType_GetModuleByDef")] + pub fn PyType_GetModuleByDef( + arg1: *mut crate::PyTypeObject, + arg2: *mut crate::PyModuleDef, + ) -> *mut PyObject; +} diff --git a/include/pyo3/pyo3-ffi/src/objimpl.rs b/include/pyo3/pyo3-ffi/src/objimpl.rs new file mode 100644 index 00000000..76835a6d --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/objimpl.rs @@ -0,0 +1,94 @@ +use libc::size_t; +use std::os::raw::{c_int, c_void}; + +use crate::object::*; +use crate::pyport::Py_ssize_t; + +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyObject_Malloc")] + pub fn PyObject_Malloc(size: size_t) -> *mut c_void; + #[cfg_attr(PyPy, link_name = "PyPyObject_Calloc")] + pub fn PyObject_Calloc(nelem: size_t, elsize: size_t) -> *mut c_void; + #[cfg_attr(PyPy, link_name = "PyPyObject_Realloc")] + pub fn PyObject_Realloc(ptr: *mut c_void, new_size: size_t) -> *mut c_void; + #[cfg_attr(PyPy, link_name = "PyPyObject_Free")] + pub fn PyObject_Free(ptr: *mut c_void); + + // skipped PyObject_MALLOC + // skipped PyObject_REALLOC + // skipped PyObject_FREE + // skipped PyObject_Del + // skipped PyObject_DEL + + #[cfg_attr(PyPy, link_name = "PyPyObject_Init")] + pub fn PyObject_Init(arg1: *mut PyObject, arg2: *mut PyTypeObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyObject_InitVar")] + pub fn PyObject_InitVar( + arg1: *mut PyVarObject, + arg2: *mut PyTypeObject, + arg3: Py_ssize_t, + ) -> *mut PyVarObject; + + // skipped PyObject_INIT + // skipped PyObject_INIT_VAR + + #[cfg_attr(PyPy, link_name = "_PyPyObject_New")] + pub fn _PyObject_New(arg1: *mut PyTypeObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "_PyPyObject_NewVar")] + pub fn _PyObject_NewVar(arg1: *mut PyTypeObject, arg2: Py_ssize_t) -> *mut PyVarObject; + + // skipped PyObject_New + // skipped PyObject_NEW + // skipped PyObject_NewVar + // skipped PyObject_NEW_VAR + + pub fn PyGC_Collect() -> Py_ssize_t; + + #[cfg(Py_3_10)] + #[cfg_attr(PyPy, link_name = "PyPyGC_Enable")] + pub fn PyGC_Enable() -> c_int; + + #[cfg(Py_3_10)] + #[cfg_attr(PyPy, link_name = "PyPyGC_Disable")] + pub fn PyGC_Disable() -> c_int; + + #[cfg(Py_3_10)] + #[cfg_attr(PyPy, link_name = "PyPyGC_IsEnabled")] + pub fn PyGC_IsEnabled() -> c_int; + + // skipped PyUnstable_GC_VisitObjects +} + +#[inline] +pub unsafe fn PyType_IS_GC(t: *mut PyTypeObject) -> c_int { + PyType_HasFeature(t, Py_TPFLAGS_HAVE_GC) +} + +extern "C" { + pub fn _PyObject_GC_Resize(arg1: *mut PyVarObject, arg2: Py_ssize_t) -> *mut PyVarObject; + + // skipped PyObject_GC_Resize + + #[cfg_attr(PyPy, link_name = "_PyPyObject_GC_New")] + pub fn _PyObject_GC_New(arg1: *mut PyTypeObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "_PyPyObject_GC_NewVar")] + pub fn _PyObject_GC_NewVar(arg1: *mut PyTypeObject, arg2: Py_ssize_t) -> *mut PyVarObject; + #[cfg(not(PyPy))] + pub fn PyObject_GC_Track(arg1: *mut c_void); + #[cfg(not(PyPy))] + pub fn PyObject_GC_UnTrack(arg1: *mut c_void); + #[cfg_attr(PyPy, link_name = "PyPyObject_GC_Del")] + pub fn PyObject_GC_Del(arg1: *mut c_void); + + // skipped PyObject_GC_New + // skipped PyObject_GC_NewVar + + #[cfg(any(all(Py_3_9, not(PyPy)), Py_3_10))] // added in 3.9, or 3.10 on PyPy + #[cfg_attr(PyPy, link_name = "PyPyObject_GC_IsTracked")] + pub fn PyObject_GC_IsTracked(arg1: *mut PyObject) -> c_int; + #[cfg(any(all(Py_3_9, not(PyPy)), Py_3_10))] // added in 3.9, or 3.10 on PyPy + #[cfg_attr(PyPy, link_name = "PyPyObject_GC_IsFinalized")] + pub fn PyObject_GC_IsFinalized(arg1: *mut PyObject) -> c_int; +} + +// skipped Py_VISIT diff --git a/include/pyo3/pyo3-ffi/src/osmodule.rs b/include/pyo3/pyo3-ffi/src/osmodule.rs new file mode 100644 index 00000000..e6aefc36 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/osmodule.rs @@ -0,0 +1,6 @@ +use crate::object::PyObject; + +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyOS_FSPath")] + pub fn PyOS_FSPath(path: *mut PyObject) -> *mut PyObject; +} diff --git a/include/pyo3/pyo3-ffi/src/pyarena.rs b/include/pyo3/pyo3-ffi/src/pyarena.rs new file mode 100644 index 00000000..87d5f28a --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/pyarena.rs @@ -0,0 +1 @@ +opaque_struct!(PyArena); diff --git a/include/pyo3/pyo3-ffi/src/pybuffer.rs b/include/pyo3/pyo3-ffi/src/pybuffer.rs new file mode 100644 index 00000000..50bf4e61 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/pybuffer.rs @@ -0,0 +1,134 @@ +use crate::object::PyObject; +use crate::pyport::Py_ssize_t; +use std::os::raw::{c_char, c_int, c_void}; +use std::ptr; + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct Py_buffer { + pub buf: *mut c_void, + /// Owned reference + pub obj: *mut crate::PyObject, + pub len: Py_ssize_t, + pub itemsize: Py_ssize_t, + pub readonly: c_int, + pub ndim: c_int, + pub format: *mut c_char, + pub shape: *mut Py_ssize_t, + pub strides: *mut Py_ssize_t, + pub suboffsets: *mut Py_ssize_t, + pub internal: *mut c_void, + #[cfg(PyPy)] + pub flags: c_int, + #[cfg(PyPy)] + pub _strides: [Py_ssize_t; PyBUF_MAX_NDIM], + #[cfg(PyPy)] + pub _shape: [Py_ssize_t; PyBUF_MAX_NDIM], +} + +impl Py_buffer { + #[allow(clippy::new_without_default)] + pub const fn new() -> Self { + Py_buffer { + buf: ptr::null_mut(), + obj: ptr::null_mut(), + len: 0, + itemsize: 0, + readonly: 0, + ndim: 0, + format: ptr::null_mut(), + shape: ptr::null_mut(), + strides: ptr::null_mut(), + suboffsets: ptr::null_mut(), + internal: ptr::null_mut(), + #[cfg(PyPy)] + flags: 0, + #[cfg(PyPy)] + _strides: [0; PyBUF_MAX_NDIM], + #[cfg(PyPy)] + _shape: [0; PyBUF_MAX_NDIM], + } + } +} + +pub type getbufferproc = unsafe extern "C" fn(*mut PyObject, *mut crate::Py_buffer, c_int) -> c_int; +pub type releasebufferproc = unsafe extern "C" fn(*mut PyObject, *mut crate::Py_buffer); + +/* Return 1 if the getbuffer function is available, otherwise return 0. */ +extern "C" { + #[cfg(not(PyPy))] + pub fn PyObject_CheckBuffer(obj: *mut PyObject) -> c_int; + + #[cfg_attr(PyPy, link_name = "PyPyObject_GetBuffer")] + pub fn PyObject_GetBuffer(obj: *mut PyObject, view: *mut Py_buffer, flags: c_int) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyBuffer_GetPointer")] + pub fn PyBuffer_GetPointer(view: *const Py_buffer, indices: *const Py_ssize_t) -> *mut c_void; + #[cfg_attr(PyPy, link_name = "PyPyBuffer_SizeFromFormat")] + pub fn PyBuffer_SizeFromFormat(format: *const c_char) -> Py_ssize_t; + #[cfg_attr(PyPy, link_name = "PyPyBuffer_ToContiguous")] + pub fn PyBuffer_ToContiguous( + buf: *mut c_void, + view: *const Py_buffer, + len: Py_ssize_t, + order: c_char, + ) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyBuffer_FromContiguous")] + pub fn PyBuffer_FromContiguous( + view: *const Py_buffer, + buf: *const c_void, + len: Py_ssize_t, + order: c_char, + ) -> c_int; + pub fn PyObject_CopyData(dest: *mut PyObject, src: *mut PyObject) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyBuffer_IsContiguous")] + pub fn PyBuffer_IsContiguous(view: *const Py_buffer, fort: c_char) -> c_int; + pub fn PyBuffer_FillContiguousStrides( + ndims: c_int, + shape: *mut Py_ssize_t, + strides: *mut Py_ssize_t, + itemsize: c_int, + fort: c_char, + ); + #[cfg_attr(PyPy, link_name = "PyPyBuffer_FillInfo")] + pub fn PyBuffer_FillInfo( + view: *mut Py_buffer, + o: *mut PyObject, + buf: *mut c_void, + len: Py_ssize_t, + readonly: c_int, + flags: c_int, + ) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyBuffer_Release")] + pub fn PyBuffer_Release(view: *mut Py_buffer); +} + +/// Maximum number of dimensions +pub const PyBUF_MAX_NDIM: c_int = if cfg!(PyPy) { 36 } else { 64 }; + +/* Flags for getting buffers */ +pub const PyBUF_SIMPLE: c_int = 0; +pub const PyBUF_WRITABLE: c_int = 0x0001; +/* we used to include an E, backwards compatible alias */ +pub const PyBUF_WRITEABLE: c_int = PyBUF_WRITABLE; +pub const PyBUF_FORMAT: c_int = 0x0004; +pub const PyBUF_ND: c_int = 0x0008; +pub const PyBUF_STRIDES: c_int = 0x0010 | PyBUF_ND; +pub const PyBUF_C_CONTIGUOUS: c_int = 0x0020 | PyBUF_STRIDES; +pub const PyBUF_F_CONTIGUOUS: c_int = 0x0040 | PyBUF_STRIDES; +pub const PyBUF_ANY_CONTIGUOUS: c_int = 0x0080 | PyBUF_STRIDES; +pub const PyBUF_INDIRECT: c_int = 0x0100 | PyBUF_STRIDES; + +pub const PyBUF_CONTIG: c_int = PyBUF_ND | PyBUF_WRITABLE; +pub const PyBUF_CONTIG_RO: c_int = PyBUF_ND; + +pub const PyBUF_STRIDED: c_int = PyBUF_STRIDES | PyBUF_WRITABLE; +pub const PyBUF_STRIDED_RO: c_int = PyBUF_STRIDES; + +pub const PyBUF_RECORDS: c_int = PyBUF_STRIDES | PyBUF_WRITABLE | PyBUF_FORMAT; +pub const PyBUF_RECORDS_RO: c_int = PyBUF_STRIDES | PyBUF_FORMAT; + +pub const PyBUF_FULL: c_int = PyBUF_INDIRECT | PyBUF_WRITABLE | PyBUF_FORMAT; +pub const PyBUF_FULL_RO: c_int = PyBUF_INDIRECT | PyBUF_FORMAT; + +pub const PyBUF_READ: c_int = 0x100; +pub const PyBUF_WRITE: c_int = 0x200; diff --git a/include/pyo3/pyo3-ffi/src/pycapsule.rs b/include/pyo3/pyo3-ffi/src/pycapsule.rs new file mode 100644 index 00000000..5b77841c --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/pycapsule.rs @@ -0,0 +1,48 @@ +use crate::object::*; +use std::os::raw::{c_char, c_int, c_void}; +use std::ptr::addr_of_mut; + +#[cfg_attr(windows, link(name = "pythonXY"))] +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyCapsule_Type")] + pub static mut PyCapsule_Type: PyTypeObject; +} + +pub type PyCapsule_Destructor = unsafe extern "C" fn(o: *mut PyObject); + +#[inline] +pub unsafe fn PyCapsule_CheckExact(ob: *mut PyObject) -> c_int { + (Py_TYPE(ob) == addr_of_mut!(PyCapsule_Type)) as c_int +} + +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyCapsule_New")] + pub fn PyCapsule_New( + pointer: *mut c_void, + name: *const c_char, + destructor: Option, + ) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyCapsule_GetPointer")] + pub fn PyCapsule_GetPointer(capsule: *mut PyObject, name: *const c_char) -> *mut c_void; + #[cfg_attr(PyPy, link_name = "PyPyCapsule_GetDestructor")] + pub fn PyCapsule_GetDestructor(capsule: *mut PyObject) -> Option; + #[cfg_attr(PyPy, link_name = "PyPyCapsule_GetName")] + pub fn PyCapsule_GetName(capsule: *mut PyObject) -> *const c_char; + #[cfg_attr(PyPy, link_name = "PyPyCapsule_GetContext")] + pub fn PyCapsule_GetContext(capsule: *mut PyObject) -> *mut c_void; + #[cfg_attr(PyPy, link_name = "PyPyCapsule_IsValid")] + pub fn PyCapsule_IsValid(capsule: *mut PyObject, name: *const c_char) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyCapsule_SetPointer")] + pub fn PyCapsule_SetPointer(capsule: *mut PyObject, pointer: *mut c_void) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyCapsule_SetDestructor")] + pub fn PyCapsule_SetDestructor( + capsule: *mut PyObject, + destructor: Option, + ) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyCapsule_SetName")] + pub fn PyCapsule_SetName(capsule: *mut PyObject, name: *const c_char) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyCapsule_SetContext")] + pub fn PyCapsule_SetContext(capsule: *mut PyObject, context: *mut c_void) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyCapsule_Import")] + pub fn PyCapsule_Import(name: *const c_char, no_block: c_int) -> *mut c_void; +} diff --git a/include/pyo3/pyo3-ffi/src/pyerrors.rs b/include/pyo3/pyo3-ffi/src/pyerrors.rs new file mode 100644 index 00000000..6c9313c4 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/pyerrors.rs @@ -0,0 +1,363 @@ +use crate::object::*; +use crate::pyport::Py_ssize_t; +use std::os::raw::{c_char, c_int}; + +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyErr_SetNone")] + pub fn PyErr_SetNone(arg1: *mut PyObject); + #[cfg_attr(PyPy, link_name = "PyPyErr_SetObject")] + pub fn PyErr_SetObject(arg1: *mut PyObject, arg2: *mut PyObject); + #[cfg_attr(PyPy, link_name = "PyPyErr_SetString")] + pub fn PyErr_SetString(exception: *mut PyObject, string: *const c_char); + #[cfg_attr(PyPy, link_name = "PyPyErr_Occurred")] + pub fn PyErr_Occurred() -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyErr_Clear")] + pub fn PyErr_Clear(); + #[cfg_attr(Py_3_12, deprecated(note = "Use PyErr_GetRaisedException() instead."))] + #[cfg_attr(PyPy, link_name = "PyPyErr_Fetch")] + pub fn PyErr_Fetch( + arg1: *mut *mut PyObject, + arg2: *mut *mut PyObject, + arg3: *mut *mut PyObject, + ); + #[cfg_attr(Py_3_12, deprecated(note = "Use PyErr_SetRaisedException() instead."))] + #[cfg_attr(PyPy, link_name = "PyPyErr_Restore")] + pub fn PyErr_Restore(arg1: *mut PyObject, arg2: *mut PyObject, arg3: *mut PyObject); + #[cfg_attr(PyPy, link_name = "PyPyErr_GetExcInfo")] + pub fn PyErr_GetExcInfo( + arg1: *mut *mut PyObject, + arg2: *mut *mut PyObject, + arg3: *mut *mut PyObject, + ); + #[cfg_attr(PyPy, link_name = "PyPyErr_SetExcInfo")] + pub fn PyErr_SetExcInfo(arg1: *mut PyObject, arg2: *mut PyObject, arg3: *mut PyObject); + #[cfg_attr(PyPy, link_name = "PyPy_FatalError")] + pub fn Py_FatalError(message: *const c_char) -> !; + #[cfg_attr(PyPy, link_name = "PyPyErr_GivenExceptionMatches")] + pub fn PyErr_GivenExceptionMatches(arg1: *mut PyObject, arg2: *mut PyObject) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyErr_ExceptionMatches")] + pub fn PyErr_ExceptionMatches(arg1: *mut PyObject) -> c_int; + #[cfg_attr( + Py_3_12, + deprecated( + note = "Use PyErr_GetRaisedException() instead, to avoid any possible de-normalization." + ) + )] + #[cfg_attr(PyPy, link_name = "PyPyErr_NormalizeException")] + pub fn PyErr_NormalizeException( + arg1: *mut *mut PyObject, + arg2: *mut *mut PyObject, + arg3: *mut *mut PyObject, + ); + #[cfg(Py_3_12)] + pub fn PyErr_GetRaisedException() -> *mut PyObject; + #[cfg(Py_3_12)] + pub fn PyErr_SetRaisedException(exc: *mut PyObject); + #[cfg_attr(PyPy, link_name = "PyPyException_SetTraceback")] + pub fn PyException_SetTraceback(arg1: *mut PyObject, arg2: *mut PyObject) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyException_GetTraceback")] + pub fn PyException_GetTraceback(arg1: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyException_GetCause")] + pub fn PyException_GetCause(arg1: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyException_SetCause")] + pub fn PyException_SetCause(arg1: *mut PyObject, arg2: *mut PyObject); + #[cfg_attr(PyPy, link_name = "PyPyException_GetContext")] + pub fn PyException_GetContext(arg1: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyException_SetContext")] + pub fn PyException_SetContext(arg1: *mut PyObject, arg2: *mut PyObject); + + #[cfg(PyPy)] + #[link_name = "PyPyExceptionInstance_Class"] + pub fn PyExceptionInstance_Class(x: *mut PyObject) -> *mut PyObject; +} + +#[inline] +pub unsafe fn PyExceptionClass_Check(x: *mut PyObject) -> c_int { + (PyType_Check(x) != 0 + && PyType_FastSubclass(x as *mut PyTypeObject, Py_TPFLAGS_BASE_EXC_SUBCLASS) != 0) + as c_int +} + +#[inline] +pub unsafe fn PyExceptionInstance_Check(x: *mut PyObject) -> c_int { + PyType_FastSubclass(Py_TYPE(x), Py_TPFLAGS_BASE_EXC_SUBCLASS) +} + +#[inline] +#[cfg(not(PyPy))] +pub unsafe fn PyExceptionInstance_Class(x: *mut PyObject) -> *mut PyObject { + Py_TYPE(x) as *mut PyObject +} + +// ported from cpython exception.c (line 2096) +#[cfg(PyPy)] +pub unsafe fn PyUnicodeDecodeError_Create( + encoding: *const c_char, + object: *const c_char, + length: Py_ssize_t, + start: Py_ssize_t, + end: Py_ssize_t, + reason: *const c_char, +) -> *mut PyObject { + crate::_PyObject_CallFunction_SizeT( + PyExc_UnicodeDecodeError, + c_str!("sy#nns").as_ptr(), + encoding, + object, + length, + start, + end, + reason, + ) +} + +#[cfg_attr(windows, link(name = "pythonXY"))] +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyExc_BaseException")] + pub static mut PyExc_BaseException: *mut PyObject; + #[cfg(Py_3_11)] + pub static mut PyExc_BaseExceptionGroup: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_Exception")] + pub static mut PyExc_Exception: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_StopAsyncIteration")] + pub static mut PyExc_StopAsyncIteration: *mut PyObject; + + #[cfg_attr(PyPy, link_name = "PyPyExc_StopIteration")] + pub static mut PyExc_StopIteration: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_GeneratorExit")] + pub static mut PyExc_GeneratorExit: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_ArithmeticError")] + pub static mut PyExc_ArithmeticError: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_LookupError")] + pub static mut PyExc_LookupError: *mut PyObject; + + #[cfg_attr(PyPy, link_name = "PyPyExc_AssertionError")] + pub static mut PyExc_AssertionError: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_AttributeError")] + pub static mut PyExc_AttributeError: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_BufferError")] + pub static mut PyExc_BufferError: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_EOFError")] + pub static mut PyExc_EOFError: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_FloatingPointError")] + pub static mut PyExc_FloatingPointError: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_OSError")] + pub static mut PyExc_OSError: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_ImportError")] + pub static mut PyExc_ImportError: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_ModuleNotFoundError")] + pub static mut PyExc_ModuleNotFoundError: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_IndexError")] + pub static mut PyExc_IndexError: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_KeyError")] + pub static mut PyExc_KeyError: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_KeyboardInterrupt")] + pub static mut PyExc_KeyboardInterrupt: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_MemoryError")] + pub static mut PyExc_MemoryError: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_NameError")] + pub static mut PyExc_NameError: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_OverflowError")] + pub static mut PyExc_OverflowError: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_RuntimeError")] + pub static mut PyExc_RuntimeError: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_RecursionError")] + pub static mut PyExc_RecursionError: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_NotImplementedError")] + pub static mut PyExc_NotImplementedError: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_SyntaxError")] + pub static mut PyExc_SyntaxError: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_IndentationError")] + pub static mut PyExc_IndentationError: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_TabError")] + pub static mut PyExc_TabError: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_ReferenceError")] + pub static mut PyExc_ReferenceError: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_SystemError")] + pub static mut PyExc_SystemError: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_SystemExit")] + pub static mut PyExc_SystemExit: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_TypeError")] + pub static mut PyExc_TypeError: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_UnboundLocalError")] + pub static mut PyExc_UnboundLocalError: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_UnicodeError")] + pub static mut PyExc_UnicodeError: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_UnicodeEncodeError")] + pub static mut PyExc_UnicodeEncodeError: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_UnicodeDecodeError")] + pub static mut PyExc_UnicodeDecodeError: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_UnicodeTranslateError")] + pub static mut PyExc_UnicodeTranslateError: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_ValueError")] + pub static mut PyExc_ValueError: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_ZeroDivisionError")] + pub static mut PyExc_ZeroDivisionError: *mut PyObject; + + #[cfg_attr(PyPy, link_name = "PyPyExc_BlockingIOError")] + pub static mut PyExc_BlockingIOError: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_BrokenPipeError")] + pub static mut PyExc_BrokenPipeError: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_ChildProcessError")] + pub static mut PyExc_ChildProcessError: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_ConnectionError")] + pub static mut PyExc_ConnectionError: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_ConnectionAbortedError")] + pub static mut PyExc_ConnectionAbortedError: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_ConnectionRefusedError")] + pub static mut PyExc_ConnectionRefusedError: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_ConnectionResetError")] + pub static mut PyExc_ConnectionResetError: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_FileExistsError")] + pub static mut PyExc_FileExistsError: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_FileNotFoundError")] + pub static mut PyExc_FileNotFoundError: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_InterruptedError")] + pub static mut PyExc_InterruptedError: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_IsADirectoryError")] + pub static mut PyExc_IsADirectoryError: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_NotADirectoryError")] + pub static mut PyExc_NotADirectoryError: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_PermissionError")] + pub static mut PyExc_PermissionError: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_ProcessLookupError")] + pub static mut PyExc_ProcessLookupError: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_TimeoutError")] + pub static mut PyExc_TimeoutError: *mut PyObject; + + #[cfg_attr(PyPy, link_name = "PyPyExc_OSError")] + pub static mut PyExc_EnvironmentError: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_OSError")] + pub static mut PyExc_IOError: *mut PyObject; + #[cfg(windows)] + #[cfg_attr(PyPy, link_name = "PyPyExc_OSError")] + pub static mut PyExc_WindowsError: *mut PyObject; + + pub static mut PyExc_RecursionErrorInst: *mut PyObject; + + /* Predefined warning categories */ + #[cfg_attr(PyPy, link_name = "PyPyExc_Warning")] + pub static mut PyExc_Warning: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_UserWarning")] + pub static mut PyExc_UserWarning: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_DeprecationWarning")] + pub static mut PyExc_DeprecationWarning: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_PendingDeprecationWarning")] + pub static mut PyExc_PendingDeprecationWarning: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_SyntaxWarning")] + pub static mut PyExc_SyntaxWarning: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_RuntimeWarning")] + pub static mut PyExc_RuntimeWarning: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_FutureWarning")] + pub static mut PyExc_FutureWarning: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_ImportWarning")] + pub static mut PyExc_ImportWarning: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_UnicodeWarning")] + pub static mut PyExc_UnicodeWarning: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_BytesWarning")] + pub static mut PyExc_BytesWarning: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_ResourceWarning")] + pub static mut PyExc_ResourceWarning: *mut PyObject; + #[cfg(Py_3_10)] + #[cfg_attr(PyPy, link_name = "PyPyExc_EncodingWarning")] + pub static mut PyExc_EncodingWarning: *mut PyObject; +} + +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyErr_BadArgument")] + pub fn PyErr_BadArgument() -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyErr_NoMemory")] + pub fn PyErr_NoMemory() -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyErr_SetFromErrno")] + pub fn PyErr_SetFromErrno(arg1: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyErr_SetFromErrnoWithFilenameObject")] + pub fn PyErr_SetFromErrnoWithFilenameObject( + arg1: *mut PyObject, + arg2: *mut PyObject, + ) -> *mut PyObject; + pub fn PyErr_SetFromErrnoWithFilenameObjects( + arg1: *mut PyObject, + arg2: *mut PyObject, + arg3: *mut PyObject, + ) -> *mut PyObject; + pub fn PyErr_SetFromErrnoWithFilename( + exc: *mut PyObject, + filename: *const c_char, + ) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyErr_Format")] + pub fn PyErr_Format(exception: *mut PyObject, format: *const c_char, ...) -> *mut PyObject; + pub fn PyErr_SetImportErrorSubclass( + arg1: *mut PyObject, + arg2: *mut PyObject, + arg3: *mut PyObject, + arg4: *mut PyObject, + ) -> *mut PyObject; + pub fn PyErr_SetImportError( + arg1: *mut PyObject, + arg2: *mut PyObject, + arg3: *mut PyObject, + ) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyErr_BadInternalCall")] + pub fn PyErr_BadInternalCall(); + pub fn _PyErr_BadInternalCall(filename: *const c_char, lineno: c_int); + #[cfg_attr(PyPy, link_name = "PyPyErr_NewException")] + pub fn PyErr_NewException( + name: *const c_char, + base: *mut PyObject, + dict: *mut PyObject, + ) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyErr_NewExceptionWithDoc")] + pub fn PyErr_NewExceptionWithDoc( + name: *const c_char, + doc: *const c_char, + base: *mut PyObject, + dict: *mut PyObject, + ) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyErr_WriteUnraisable")] + pub fn PyErr_WriteUnraisable(arg1: *mut PyObject); + #[cfg_attr(PyPy, link_name = "PyPyErr_CheckSignals")] + pub fn PyErr_CheckSignals() -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyErr_SetInterrupt")] + pub fn PyErr_SetInterrupt(); + #[cfg(Py_3_10)] + #[cfg_attr(PyPy, link_name = "PyPyErr_SetInterruptEx")] + pub fn PyErr_SetInterruptEx(signum: c_int); + #[cfg_attr(PyPy, link_name = "PyPyErr_SyntaxLocation")] + pub fn PyErr_SyntaxLocation(filename: *const c_char, lineno: c_int); + #[cfg_attr(PyPy, link_name = "PyPyErr_SyntaxLocationEx")] + pub fn PyErr_SyntaxLocationEx(filename: *const c_char, lineno: c_int, col_offset: c_int); + #[cfg_attr(PyPy, link_name = "PyPyErr_ProgramText")] + pub fn PyErr_ProgramText(filename: *const c_char, lineno: c_int) -> *mut PyObject; + #[cfg(not(PyPy))] + pub fn PyUnicodeDecodeError_Create( + encoding: *const c_char, + object: *const c_char, + length: Py_ssize_t, + start: Py_ssize_t, + end: Py_ssize_t, + reason: *const c_char, + ) -> *mut PyObject; + pub fn PyUnicodeEncodeError_GetEncoding(arg1: *mut PyObject) -> *mut PyObject; + pub fn PyUnicodeDecodeError_GetEncoding(arg1: *mut PyObject) -> *mut PyObject; + pub fn PyUnicodeEncodeError_GetObject(arg1: *mut PyObject) -> *mut PyObject; + pub fn PyUnicodeDecodeError_GetObject(arg1: *mut PyObject) -> *mut PyObject; + pub fn PyUnicodeTranslateError_GetObject(arg1: *mut PyObject) -> *mut PyObject; + pub fn PyUnicodeEncodeError_GetStart(arg1: *mut PyObject, arg2: *mut Py_ssize_t) -> c_int; + pub fn PyUnicodeDecodeError_GetStart(arg1: *mut PyObject, arg2: *mut Py_ssize_t) -> c_int; + pub fn PyUnicodeTranslateError_GetStart(arg1: *mut PyObject, arg2: *mut Py_ssize_t) -> c_int; + pub fn PyUnicodeEncodeError_SetStart(arg1: *mut PyObject, arg2: Py_ssize_t) -> c_int; + pub fn PyUnicodeDecodeError_SetStart(arg1: *mut PyObject, arg2: Py_ssize_t) -> c_int; + pub fn PyUnicodeTranslateError_SetStart(arg1: *mut PyObject, arg2: Py_ssize_t) -> c_int; + pub fn PyUnicodeEncodeError_GetEnd(arg1: *mut PyObject, arg2: *mut Py_ssize_t) -> c_int; + pub fn PyUnicodeDecodeError_GetEnd(arg1: *mut PyObject, arg2: *mut Py_ssize_t) -> c_int; + pub fn PyUnicodeTranslateError_GetEnd(arg1: *mut PyObject, arg2: *mut Py_ssize_t) -> c_int; + pub fn PyUnicodeEncodeError_SetEnd(arg1: *mut PyObject, arg2: Py_ssize_t) -> c_int; + pub fn PyUnicodeDecodeError_SetEnd(arg1: *mut PyObject, arg2: Py_ssize_t) -> c_int; + pub fn PyUnicodeTranslateError_SetEnd(arg1: *mut PyObject, arg2: Py_ssize_t) -> c_int; + pub fn PyUnicodeEncodeError_GetReason(arg1: *mut PyObject) -> *mut PyObject; + pub fn PyUnicodeDecodeError_GetReason(arg1: *mut PyObject) -> *mut PyObject; + pub fn PyUnicodeTranslateError_GetReason(arg1: *mut PyObject) -> *mut PyObject; + pub fn PyUnicodeEncodeError_SetReason(exc: *mut PyObject, reason: *const c_char) -> c_int; + pub fn PyUnicodeDecodeError_SetReason(exc: *mut PyObject, reason: *const c_char) -> c_int; + pub fn PyUnicodeTranslateError_SetReason(exc: *mut PyObject, reason: *const c_char) -> c_int; +} diff --git a/include/pyo3/pyo3-ffi/src/pyframe.rs b/include/pyo3/pyo3-ffi/src/pyframe.rs new file mode 100644 index 00000000..4dd3d2b3 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/pyframe.rs @@ -0,0 +1,16 @@ +#[cfg(not(GraalPy))] +#[cfg(any(Py_3_10, all(Py_3_9, not(Py_LIMITED_API))))] +use crate::PyCodeObject; +#[cfg(not(Py_LIMITED_API))] +use crate::PyFrameObject; +use std::os::raw::c_int; + +#[cfg(Py_LIMITED_API)] +opaque_struct!(PyFrameObject); + +extern "C" { + pub fn PyFrame_GetLineNumber(f: *mut PyFrameObject) -> c_int; + #[cfg(not(GraalPy))] + #[cfg(any(Py_3_10, all(Py_3_9, not(Py_LIMITED_API))))] + pub fn PyFrame_GetCode(f: *mut PyFrameObject) -> *mut PyCodeObject; +} diff --git a/include/pyo3/pyo3-ffi/src/pyhash.rs b/include/pyo3/pyo3-ffi/src/pyhash.rs new file mode 100644 index 00000000..f42f9730 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/pyhash.rs @@ -0,0 +1,52 @@ +#[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))] +use crate::pyport::{Py_hash_t, Py_ssize_t}; +#[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))] +use std::os::raw::{c_char, c_void}; + +use std::os::raw::{c_int, c_ulong}; + +extern "C" { + // skipped non-limited _Py_HashDouble + // skipped non-limited _Py_HashPointer + // skipped non-limited _Py_HashPointerRaw + + #[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))] + pub fn _Py_HashBytes(src: *const c_void, len: Py_ssize_t) -> Py_hash_t; +} + +pub const _PyHASH_MULTIPLIER: c_ulong = 1000003; + +// skipped _PyHASH_BITS + +// 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; +pub const Py_HASH_SIPHASH24: c_int = 1; +pub const Py_HASH_FNV: c_int = 2; + +// skipped Py_HASH_ALGORITHM diff --git a/include/pyo3/pyo3-ffi/src/pylifecycle.rs b/include/pyo3/pyo3-ffi/src/pylifecycle.rs new file mode 100644 index 00000000..3f051c54 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/pylifecycle.rs @@ -0,0 +1,92 @@ +use crate::pystate::PyThreadState; + +use libc::wchar_t; +use std::os::raw::{c_char, c_int}; + +extern "C" { + pub fn Py_Initialize(); + pub fn Py_InitializeEx(arg1: c_int); + pub fn Py_Finalize(); + pub fn Py_FinalizeEx() -> c_int; + + #[cfg_attr(PyPy, link_name = "PyPy_IsInitialized")] + pub fn Py_IsInitialized() -> c_int; + + pub fn Py_NewInterpreter() -> *mut PyThreadState; + pub fn Py_EndInterpreter(arg1: *mut PyThreadState); + + #[cfg_attr(PyPy, link_name = "PyPy_AtExit")] + pub fn Py_AtExit(func: Option) -> c_int; + + pub fn Py_Exit(arg1: c_int); + + pub fn Py_Main(argc: c_int, argv: *mut *mut wchar_t) -> c_int; + pub fn Py_BytesMain(argc: c_int, argv: *mut *mut c_char) -> c_int; + + #[cfg_attr( + Py_3_11, + deprecated(note = "Deprecated since Python 3.11. Use `PyConfig.program_name` instead.") + )] + pub fn Py_SetProgramName(arg1: *const wchar_t); + #[cfg_attr(PyPy, link_name = "PyPy_GetProgramName")] + #[cfg_attr( + Py_3_13, + deprecated(note = "Deprecated since Python 3.13. Use `sys.executable` instead.") + )] + pub fn Py_GetProgramName() -> *mut wchar_t; + + #[cfg_attr( + Py_3_11, + deprecated(note = "Deprecated since Python 3.11. Use `PyConfig.home` instead.") + )] + pub fn Py_SetPythonHome(arg1: *const wchar_t); + #[cfg_attr( + Py_3_13, + deprecated( + note = "Deprecated since Python 3.13. Use `PyConfig.home` or the value of the `PYTHONHOME` environment variable instead." + ) + )] + pub fn Py_GetPythonHome() -> *mut wchar_t; + #[cfg_attr( + Py_3_13, + deprecated(note = "Deprecated since Python 3.13. Use `sys.executable` instead.") + )] + pub fn Py_GetProgramFullPath() -> *mut wchar_t; + #[cfg_attr( + Py_3_13, + deprecated(note = "Deprecated since Python 3.13. Use `sys.prefix` instead.") + )] + pub fn Py_GetPrefix() -> *mut wchar_t; + #[cfg_attr( + Py_3_13, + deprecated(note = "Deprecated since Python 3.13. Use `sys.exec_prefix` instead.") + )] + pub fn Py_GetExecPrefix() -> *mut wchar_t; + #[cfg_attr( + Py_3_13, + deprecated(note = "Deprecated since Python 3.13. Use `sys.path` instead.") + )] + pub fn Py_GetPath() -> *mut wchar_t; + #[cfg(not(Py_3_13))] + #[cfg_attr( + Py_3_11, + deprecated(note = "Deprecated since Python 3.11. Use `sys.path` instead.") + )] + pub fn Py_SetPath(arg1: *const wchar_t); + + // skipped _Py_CheckPython3 + + #[cfg_attr(PyPy, link_name = "PyPy_GetVersion")] + pub fn Py_GetVersion() -> *const c_char; + pub fn Py_GetPlatform() -> *const c_char; + pub fn Py_GetCopyright() -> *const c_char; + pub fn Py_GetCompiler() -> *const c_char; + pub fn Py_GetBuildInfo() -> *const c_char; +} + +type PyOS_sighandler_t = unsafe extern "C" fn(arg1: c_int); + +extern "C" { + pub fn PyOS_getsig(arg1: c_int) -> PyOS_sighandler_t; + pub fn PyOS_setsig(arg1: c_int, arg2: PyOS_sighandler_t) -> PyOS_sighandler_t; +} diff --git a/include/pyo3/pyo3-ffi/src/pymem.rs b/include/pyo3/pyo3-ffi/src/pymem.rs new file mode 100644 index 00000000..ab0c3d74 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/pymem.rs @@ -0,0 +1,13 @@ +use libc::size_t; +use std::os::raw::c_void; + +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyMem_Malloc")] + pub fn PyMem_Malloc(size: size_t) -> *mut c_void; + #[cfg_attr(PyPy, link_name = "PyPyMem_Calloc")] + pub fn PyMem_Calloc(nelem: size_t, elsize: size_t) -> *mut c_void; + #[cfg_attr(PyPy, link_name = "PyPyMem_Realloc")] + pub fn PyMem_Realloc(ptr: *mut c_void, new_size: size_t) -> *mut c_void; + #[cfg_attr(PyPy, link_name = "PyPyMem_Free")] + pub fn PyMem_Free(ptr: *mut c_void); +} diff --git a/include/pyo3/pyo3-ffi/src/pyport.rs b/include/pyo3/pyo3-ffi/src/pyport.rs new file mode 100644 index 00000000..a144c67f --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/pyport.rs @@ -0,0 +1,25 @@ +pub type PY_UINT32_T = u32; +pub type PY_UINT64_T = u64; + +pub type PY_INT32_T = i32; +pub type PY_INT64_T = i64; + +pub type Py_uintptr_t = ::libc::uintptr_t; +pub type Py_intptr_t = ::libc::intptr_t; +pub type Py_ssize_t = ::libc::ssize_t; + +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; + +#[cfg(target_endian = "big")] +pub const PY_BIG_ENDIAN: usize = 1; +#[cfg(target_endian = "big")] +pub const PY_LITTLE_ENDIAN: usize = 0; + +#[cfg(target_endian = "little")] +pub const PY_BIG_ENDIAN: usize = 0; +#[cfg(target_endian = "little")] +pub const PY_LITTLE_ENDIAN: usize = 1; diff --git a/include/pyo3/pyo3-ffi/src/pystate.rs b/include/pyo3/pyo3-ffi/src/pystate.rs new file mode 100644 index 00000000..23aeea3a --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/pystate.rs @@ -0,0 +1,83 @@ +use crate::moduleobject::PyModuleDef; +use crate::object::PyObject; +use std::os::raw::c_int; + +#[cfg(not(PyPy))] +use std::os::raw::c_long; + +pub const MAX_CO_EXTRA_USERS: c_int = 255; + +opaque_struct!(PyThreadState); +opaque_struct!(PyInterpreterState); + +extern "C" { + #[cfg(not(PyPy))] + pub fn PyInterpreterState_New() -> *mut PyInterpreterState; + #[cfg(not(PyPy))] + pub fn PyInterpreterState_Clear(arg1: *mut PyInterpreterState); + #[cfg(not(PyPy))] + pub fn PyInterpreterState_Delete(arg1: *mut PyInterpreterState); + + #[cfg(all(Py_3_9, not(PyPy)))] + pub fn PyInterpreterState_Get() -> *mut PyInterpreterState; + + #[cfg(all(Py_3_8, not(PyPy)))] + pub fn PyInterpreterState_GetDict(arg1: *mut PyInterpreterState) -> *mut PyObject; + + #[cfg(not(PyPy))] + pub fn PyInterpreterState_GetID(arg1: *mut PyInterpreterState) -> i64; + + #[cfg_attr(PyPy, link_name = "PyPyState_AddModule")] + pub fn PyState_AddModule(arg1: *mut PyObject, arg2: *mut PyModuleDef) -> c_int; + + #[cfg_attr(PyPy, link_name = "PyPyState_RemoveModule")] + pub fn PyState_RemoveModule(arg1: *mut PyModuleDef) -> c_int; + + // only has PyPy prefix since 3.10 + #[cfg_attr(all(PyPy, Py_3_10), link_name = "PyPyState_FindModule")] + pub fn PyState_FindModule(arg1: *mut PyModuleDef) -> *mut PyObject; + + #[cfg_attr(PyPy, link_name = "PyPyThreadState_New")] + pub fn PyThreadState_New(arg1: *mut PyInterpreterState) -> *mut PyThreadState; + #[cfg_attr(PyPy, link_name = "PyPyThreadState_Clear")] + pub fn PyThreadState_Clear(arg1: *mut PyThreadState); + #[cfg_attr(PyPy, link_name = "PyPyThreadState_Delete")] + pub fn PyThreadState_Delete(arg1: *mut PyThreadState); + + #[cfg_attr(PyPy, link_name = "PyPyThreadState_Get")] + pub fn PyThreadState_Get() -> *mut PyThreadState; +} + +#[inline] +pub unsafe fn PyThreadState_GET() -> *mut PyThreadState { + PyThreadState_Get() +} + +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyThreadState_Swap")] + pub fn PyThreadState_Swap(arg1: *mut PyThreadState) -> *mut PyThreadState; + #[cfg_attr(PyPy, link_name = "PyPyThreadState_GetDict")] + pub fn PyThreadState_GetDict() -> *mut PyObject; + #[cfg(not(PyPy))] + pub fn PyThreadState_SetAsyncExc(arg1: c_long, arg2: *mut PyObject) -> c_int; +} + +// skipped non-limited / 3.9 PyThreadState_GetInterpreter +// skipped non-limited / 3.9 PyThreadState_GetFrame +// skipped non-limited / 3.9 PyThreadState_GetID + +#[repr(C)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum PyGILState_STATE { + PyGILState_LOCKED, + PyGILState_UNLOCKED, +} + +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))] + pub fn PyGILState_GetThisThreadState() -> *mut PyThreadState; +} diff --git a/include/pyo3/pyo3-ffi/src/pystrtod.rs b/include/pyo3/pyo3-ffi/src/pystrtod.rs new file mode 100644 index 00000000..1f027686 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/pystrtod.rs @@ -0,0 +1,32 @@ +use crate::object::PyObject; +use std::os::raw::{c_char, c_double, c_int}; + +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyOS_string_to_double")] + pub fn PyOS_string_to_double( + str: *const c_char, + endptr: *mut *mut c_char, + overflow_exception: *mut PyObject, + ) -> c_double; + #[cfg_attr(PyPy, link_name = "PyPyOS_double_to_string")] + pub fn PyOS_double_to_string( + val: c_double, + format_code: c_char, + precision: c_int, + flags: c_int, + _type: *mut c_int, + ) -> *mut c_char; +} + +// skipped non-limited _Py_string_to_number_with_underscores +// skipped non-limited _Py_parse_inf_or_nan + +/* PyOS_double_to_string's "flags" parameter can be set to 0 or more of: */ +pub const Py_DTSF_SIGN: c_int = 0x01; /* always add the sign */ +pub const Py_DTSF_ADD_DOT_0: c_int = 0x02; /* if the result is an integer add ".0" */ +pub const Py_DTSF_ALT: c_int = 0x04; /* "alternate" formatting. it's format_code specific */ + +/* PyOS_double_to_string's "type", if non-NULL, will be set to one of: */ +pub const Py_DTST_FINITE: c_int = 0; +pub const Py_DTST_INFINITE: c_int = 1; +pub const Py_DTST_NAN: c_int = 2; diff --git a/include/pyo3/pyo3-ffi/src/pythonrun.rs b/include/pyo3/pyo3-ffi/src/pythonrun.rs new file mode 100644 index 00000000..e7ea2d2e --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/pythonrun.rs @@ -0,0 +1,88 @@ +use crate::object::*; +#[cfg(not(any(PyPy, Py_LIMITED_API, Py_3_10)))] +use libc::FILE; +#[cfg(any(Py_LIMITED_API, not(Py_3_10), PyPy, GraalPy))] +use std::os::raw::c_char; +use std::os::raw::c_int; + +extern "C" { + #[cfg(any(all(Py_LIMITED_API, not(PyPy)), GraalPy))] + pub fn Py_CompileString(string: *const c_char, p: *const c_char, s: c_int) -> *mut PyObject; + + #[cfg_attr(PyPy, link_name = "PyPyErr_Print")] + pub fn PyErr_Print(); + #[cfg_attr(PyPy, link_name = "PyPyErr_PrintEx")] + pub fn PyErr_PrintEx(arg1: c_int); + #[cfg_attr(PyPy, link_name = "PyPyErr_Display")] + pub fn PyErr_Display(arg1: *mut PyObject, arg2: *mut PyObject, arg3: *mut PyObject); + + #[cfg(Py_3_12)] + pub fn PyErr_DisplayException(exc: *mut PyObject); +} + +#[inline] +#[cfg(PyPy)] +pub unsafe fn Py_CompileString(string: *const c_char, p: *const c_char, s: c_int) -> *mut PyObject { + // PyPy's implementation of Py_CompileString always forwards to Py_CompileStringFlags; this + // is only available in the non-limited API and has a real definition for all versions in + // the cpython/ subdirectory. + #[cfg(Py_LIMITED_API)] + extern "C" { + #[link_name = "PyPy_CompileStringFlags"] + pub fn Py_CompileStringFlags( + string: *const c_char, + p: *const c_char, + s: c_int, + f: *mut std::os::raw::c_void, // Actually *mut Py_CompilerFlags in the real definition + ) -> *mut PyObject; + } + #[cfg(not(Py_LIMITED_API))] + use crate::Py_CompileStringFlags; + + Py_CompileStringFlags(string, p, s, std::ptr::null_mut()) +} + +// skipped PyOS_InputHook + +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); + +#[cfg(not(any(PyPy, Py_3_10)))] +opaque_struct!(symtable); +#[cfg(not(any(PyPy, Py_3_10)))] +opaque_struct!(_node); + +#[cfg(not(any(PyPy, Py_LIMITED_API, Py_3_10)))] +#[cfg_attr(Py_3_9, deprecated(note = "Python 3.9"))] +#[inline] +pub unsafe fn PyParser_SimpleParseString(s: *const c_char, b: c_int) -> *mut _node { + #[allow(deprecated)] + crate::PyParser_SimpleParseStringFlags(s, b, 0) +} + +#[cfg(not(any(PyPy, Py_LIMITED_API, Py_3_10)))] +#[cfg_attr(Py_3_9, deprecated(note = "Python 3.9"))] +#[inline] +pub unsafe fn PyParser_SimpleParseFile(fp: *mut FILE, s: *const c_char, b: c_int) -> *mut _node { + #[allow(deprecated)] + crate::PyParser_SimpleParseFileFlags(fp, s, b, 0) +} + +extern "C" { + #[cfg(not(any(PyPy, Py_3_10)))] + pub fn Py_SymtableString( + str: *const c_char, + filename: *const c_char, + start: c_int, + ) -> *mut symtable; + #[cfg(not(any(PyPy, Py_LIMITED_API, Py_3_10)))] + pub fn Py_SymtableStringObject( + str: *const c_char, + filename: *mut PyObject, + start: c_int, + ) -> *mut symtable; +} diff --git a/include/pyo3/pyo3-ffi/src/rangeobject.rs b/include/pyo3/pyo3-ffi/src/rangeobject.rs new file mode 100644 index 00000000..408b5cdc --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/rangeobject.rs @@ -0,0 +1,16 @@ +use crate::object::*; +use std::os::raw::c_int; +use std::ptr::addr_of_mut; + +#[cfg_attr(windows, link(name = "pythonXY"))] +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyRange_Type")] + pub static mut PyRange_Type: PyTypeObject; + pub static mut PyRangeIter_Type: PyTypeObject; + pub static mut PyLongRangeIter_Type: PyTypeObject; +} + +#[inline] +pub unsafe fn PyRange_Check(op: *mut PyObject) -> c_int { + (Py_TYPE(op) == addr_of_mut!(PyRange_Type)) as c_int +} diff --git a/include/pyo3/pyo3-ffi/src/setobject.rs b/include/pyo3/pyo3-ffi/src/setobject.rs new file mode 100644 index 00000000..9d5351fc --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/setobject.rs @@ -0,0 +1,150 @@ +use crate::object::*; +#[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))] +use crate::pyport::Py_hash_t; +use crate::pyport::Py_ssize_t; +use std::os::raw::c_int; +use std::ptr::addr_of_mut; + +pub const PySet_MINSIZE: usize = 8; + +#[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))] +#[repr(C)] +#[derive(Debug)] +pub struct setentry { + pub key: *mut PyObject, + pub hash: Py_hash_t, +} + +#[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))] +#[repr(C)] +#[derive(Debug)] +pub struct PySetObject { + pub ob_base: PyObject, + pub fill: Py_ssize_t, + pub used: Py_ssize_t, + pub mask: Py_ssize_t, + pub table: *mut setentry, + pub hash: Py_hash_t, + pub finger: Py_ssize_t, + pub smalltable: [setentry; PySet_MINSIZE], + pub weakreflist: *mut PyObject, +} + +// skipped +#[inline] +#[cfg(all(not(any(PyPy, GraalPy)), not(Py_LIMITED_API)))] +pub unsafe fn PySet_GET_SIZE(so: *mut PyObject) -> Py_ssize_t { + debug_assert_eq!(PyAnySet_Check(so), 1); + let so = so.cast::(); + (*so).used +} + +#[cfg(not(Py_LIMITED_API))] +#[cfg_attr(windows, link(name = "pythonXY"))] +extern "C" { + pub static mut _PySet_Dummy: *mut PyObject; +} + +extern "C" { + #[cfg(not(Py_LIMITED_API))] + #[cfg_attr(PyPy, link_name = "_PyPySet_NextEntry")] + pub fn _PySet_NextEntry( + set: *mut PyObject, + pos: *mut Py_ssize_t, + key: *mut *mut PyObject, + hash: *mut super::Py_hash_t, + ) -> c_int; + + // skipped non-limited _PySet_Update +} + +#[cfg_attr(windows, link(name = "pythonXY"))] +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPySet_Type")] + pub static mut PySet_Type: PyTypeObject; + #[cfg_attr(PyPy, link_name = "PyPyFrozenSet_Type")] + pub static mut PyFrozenSet_Type: PyTypeObject; + pub static mut PySetIter_Type: PyTypeObject; +} + +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPySet_New")] + pub fn PySet_New(arg1: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyFrozenSet_New")] + pub fn PyFrozenSet_New(arg1: *mut PyObject) -> *mut PyObject; + + #[cfg_attr(PyPy, link_name = "PyPySet_Add")] + pub fn PySet_Add(set: *mut PyObject, key: *mut PyObject) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPySet_Clear")] + pub fn PySet_Clear(set: *mut PyObject) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPySet_Contains")] + pub fn PySet_Contains(anyset: *mut PyObject, key: *mut PyObject) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPySet_Discard")] + pub fn PySet_Discard(set: *mut PyObject, key: *mut PyObject) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPySet_Pop")] + pub fn PySet_Pop(set: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPySet_Size")] + pub fn PySet_Size(anyset: *mut PyObject) -> Py_ssize_t; + + #[cfg(PyPy)] + #[link_name = "PyPyFrozenSet_CheckExact"] + pub fn PyFrozenSet_CheckExact(ob: *mut PyObject) -> c_int; +} + +#[inline] +#[cfg(not(any(PyPy, GraalPy)))] +pub unsafe fn PyFrozenSet_CheckExact(ob: *mut PyObject) -> c_int { + (Py_TYPE(ob) == addr_of_mut!(PyFrozenSet_Type)) as c_int +} + +extern "C" { + #[cfg(PyPy)] + #[link_name = "PyPyFrozenSet_Check"] + pub fn PyFrozenSet_Check(ob: *mut PyObject) -> c_int; +} + +#[inline] +#[cfg(not(PyPy))] +pub unsafe fn PyFrozenSet_Check(ob: *mut PyObject) -> c_int { + (Py_TYPE(ob) == addr_of_mut!(PyFrozenSet_Type) + || PyType_IsSubtype(Py_TYPE(ob), addr_of_mut!(PyFrozenSet_Type)) != 0) as c_int +} + +extern "C" { + #[cfg(PyPy)] + #[link_name = "PyPyAnySet_CheckExact"] + pub fn PyAnySet_CheckExact(ob: *mut PyObject) -> c_int; +} + +#[inline] +#[cfg(not(PyPy))] +pub unsafe fn PyAnySet_CheckExact(ob: *mut PyObject) -> c_int { + (Py_TYPE(ob) == addr_of_mut!(PySet_Type) || Py_TYPE(ob) == addr_of_mut!(PyFrozenSet_Type)) + as c_int +} + +#[inline] +pub unsafe fn PyAnySet_Check(ob: *mut PyObject) -> c_int { + (PyAnySet_CheckExact(ob) != 0 + || PyType_IsSubtype(Py_TYPE(ob), addr_of_mut!(PySet_Type)) != 0 + || PyType_IsSubtype(Py_TYPE(ob), addr_of_mut!(PyFrozenSet_Type)) != 0) as c_int +} + +#[inline] +#[cfg(Py_3_10)] +pub unsafe fn PySet_CheckExact(op: *mut PyObject) -> c_int { + crate::Py_IS_TYPE(op, addr_of_mut!(PySet_Type)) +} + +extern "C" { + #[cfg(PyPy)] + #[link_name = "PyPySet_Check"] + pub fn PySet_Check(ob: *mut PyObject) -> c_int; +} + +#[inline] +#[cfg(not(PyPy))] +pub unsafe fn PySet_Check(ob: *mut PyObject) -> c_int { + (Py_TYPE(ob) == addr_of_mut!(PySet_Type) + || PyType_IsSubtype(Py_TYPE(ob), addr_of_mut!(PySet_Type)) != 0) as c_int +} diff --git a/include/pyo3/pyo3-ffi/src/sliceobject.rs b/include/pyo3/pyo3-ffi/src/sliceobject.rs new file mode 100644 index 00000000..a3ea1539 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/sliceobject.rs @@ -0,0 +1,103 @@ +use crate::object::*; +use crate::pyport::Py_ssize_t; +use std::os::raw::c_int; +use std::ptr::addr_of_mut; + +#[cfg_attr(windows, link(name = "pythonXY"))] +extern "C" { + #[cfg(not(GraalPy))] + #[cfg_attr(PyPy, link_name = "_PyPy_EllipsisObject")] + static mut _Py_EllipsisObject: PyObject; + + #[cfg(GraalPy)] + static mut _Py_EllipsisObjectReference: *mut PyObject; +} + +#[inline] +pub unsafe fn Py_Ellipsis() -> *mut PyObject { + #[cfg(not(GraalPy))] + return addr_of_mut!(_Py_EllipsisObject); + #[cfg(GraalPy)] + return _Py_EllipsisObjectReference; +} + +#[cfg(not(Py_LIMITED_API))] +#[repr(C)] +pub struct PySliceObject { + pub ob_base: PyObject, + #[cfg(not(GraalPy))] + pub start: *mut PyObject, + #[cfg(not(GraalPy))] + pub stop: *mut PyObject, + #[cfg(not(GraalPy))] + pub step: *mut PyObject, +} + +#[cfg_attr(windows, link(name = "pythonXY"))] +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPySlice_Type")] + pub static mut PySlice_Type: PyTypeObject; + pub static mut PyEllipsis_Type: PyTypeObject; +} + +#[inline] +pub unsafe fn PySlice_Check(op: *mut PyObject) -> c_int { + (Py_TYPE(op) == addr_of_mut!(PySlice_Type)) as c_int +} + +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPySlice_New")] + pub fn PySlice_New( + start: *mut PyObject, + stop: *mut PyObject, + step: *mut PyObject, + ) -> *mut PyObject; + + // skipped non-limited _PySlice_FromIndices + // skipped non-limited _PySlice_GetLongIndices + + #[cfg_attr(PyPy, link_name = "PyPySlice_GetIndices")] + pub fn PySlice_GetIndices( + r: *mut PyObject, + length: Py_ssize_t, + start: *mut Py_ssize_t, + stop: *mut Py_ssize_t, + step: *mut Py_ssize_t, + ) -> c_int; +} + +#[inline] +pub unsafe fn PySlice_GetIndicesEx( + slice: *mut PyObject, + length: Py_ssize_t, + start: *mut Py_ssize_t, + stop: *mut Py_ssize_t, + step: *mut Py_ssize_t, + slicelength: *mut Py_ssize_t, +) -> c_int { + if PySlice_Unpack(slice, start, stop, step) < 0 { + *slicelength = 0; + -1 + } else { + *slicelength = PySlice_AdjustIndices(length, start, stop, *step); + 0 + } +} + +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPySlice_Unpack")] + pub fn PySlice_Unpack( + slice: *mut PyObject, + start: *mut Py_ssize_t, + stop: *mut Py_ssize_t, + step: *mut Py_ssize_t, + ) -> c_int; + + #[cfg_attr(all(PyPy, Py_3_10), link_name = "PyPySlice_AdjustIndices")] + pub fn PySlice_AdjustIndices( + length: Py_ssize_t, + start: *mut Py_ssize_t, + stop: *mut Py_ssize_t, + step: Py_ssize_t, + ) -> Py_ssize_t; +} diff --git a/include/pyo3/pyo3-ffi/src/structmember.rs b/include/pyo3/pyo3-ffi/src/structmember.rs new file mode 100644 index 00000000..afae165c --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/structmember.rs @@ -0,0 +1,33 @@ +use std::os::raw::c_int; + +pub use crate::PyMemberDef; + +pub use crate::Py_T_BOOL as T_BOOL; +pub use crate::Py_T_BYTE as T_BYTE; +pub use crate::Py_T_CHAR as T_CHAR; +pub use crate::Py_T_DOUBLE as T_DOUBLE; +pub use crate::Py_T_FLOAT as T_FLOAT; +pub use crate::Py_T_INT as T_INT; +pub use crate::Py_T_LONG as T_LONG; +pub use crate::Py_T_LONGLONG as T_LONGLONG; +pub use crate::Py_T_OBJECT_EX as T_OBJECT_EX; +pub use crate::Py_T_SHORT as T_SHORT; +pub use crate::Py_T_STRING as T_STRING; +pub use crate::Py_T_STRING_INPLACE as T_STRING_INPLACE; +pub use crate::Py_T_UBYTE as T_UBYTE; +pub use crate::Py_T_UINT as T_UINT; +pub use crate::Py_T_ULONG as T_ULONG; +pub use crate::Py_T_ULONGLONG as T_ULONGLONG; +pub use crate::Py_T_USHORT as T_USHORT; +#[allow(deprecated)] +pub use crate::_Py_T_OBJECT as T_OBJECT; + +pub use crate::Py_T_PYSSIZET as T_PYSSIZET; +#[allow(deprecated)] +pub use crate::_Py_T_NONE as T_NONE; + +/* Flags */ +pub use crate::Py_READONLY as READONLY; +pub const READ_RESTRICTED: c_int = 2; +pub const PY_WRITE_RESTRICTED: c_int = 4; +pub const RESTRICTED: c_int = READ_RESTRICTED | PY_WRITE_RESTRICTED; diff --git a/include/pyo3/pyo3-ffi/src/structseq.rs b/include/pyo3/pyo3-ffi/src/structseq.rs new file mode 100644 index 00000000..f8566787 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/structseq.rs @@ -0,0 +1,63 @@ +use crate::object::{PyObject, PyTypeObject}; +#[cfg(not(PyPy))] +use crate::pyport::Py_ssize_t; +use std::os::raw::{c_char, c_int}; + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct PyStructSequence_Field { + pub name: *const c_char, + pub doc: *const c_char, +} + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct PyStructSequence_Desc { + pub name: *const c_char, + pub doc: *const c_char, + pub fields: *mut PyStructSequence_Field, + pub n_in_sequence: c_int, +} + +// skipped PyStructSequence_UnnamedField; + +extern "C" { + #[cfg(not(Py_LIMITED_API))] + #[cfg_attr(PyPy, link_name = "PyPyStructSequence_InitType")] + pub fn PyStructSequence_InitType(_type: *mut PyTypeObject, desc: *mut PyStructSequence_Desc); + + #[cfg(not(Py_LIMITED_API))] + #[cfg_attr(PyPy, link_name = "PyPyStructSequence_InitType2")] + pub fn PyStructSequence_InitType2( + _type: *mut PyTypeObject, + desc: *mut PyStructSequence_Desc, + ) -> c_int; + + #[cfg(not(PyPy))] + pub fn PyStructSequence_NewType(desc: *mut PyStructSequence_Desc) -> *mut PyTypeObject; + #[cfg_attr(PyPy, link_name = "PyPyStructSequence_New")] + pub fn PyStructSequence_New(_type: *mut PyTypeObject) -> *mut PyObject; +} + +#[cfg(not(Py_LIMITED_API))] +pub type PyStructSequence = crate::PyTupleObject; + +#[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))] +#[inline] +pub unsafe fn PyStructSequence_SET_ITEM(op: *mut PyObject, i: Py_ssize_t, v: *mut PyObject) { + crate::PyTuple_SET_ITEM(op, i, v) +} + +#[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))] +#[inline] +pub unsafe fn PyStructSequence_GET_ITEM(op: *mut PyObject, i: Py_ssize_t) -> *mut PyObject { + crate::PyTuple_GET_ITEM(op, i) +} + +extern "C" { + #[cfg(not(PyPy))] + pub fn PyStructSequence_SetItem(arg1: *mut PyObject, arg2: Py_ssize_t, arg3: *mut PyObject); + + #[cfg(not(PyPy))] + pub fn PyStructSequence_GetItem(arg1: *mut PyObject, arg2: Py_ssize_t) -> *mut PyObject; +} diff --git a/include/pyo3/pyo3-ffi/src/sysmodule.rs b/include/pyo3/pyo3-ffi/src/sysmodule.rs new file mode 100644 index 00000000..6f402197 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/sysmodule.rs @@ -0,0 +1,50 @@ +use crate::object::PyObject; +use libc::wchar_t; +use std::os::raw::{c_char, c_int}; + +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPySys_GetObject")] + pub fn PySys_GetObject(arg1: *const c_char) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPySys_SetObject")] + pub fn PySys_SetObject(arg1: *const c_char, arg2: *mut PyObject) -> c_int; + + #[cfg_attr( + Py_3_11, + deprecated( + note = "Deprecated in Python 3.11, use `PyConfig.argv` and `PyConfig.parse_argv` instead" + ) + )] + pub fn PySys_SetArgv(arg1: c_int, arg2: *mut *mut wchar_t); + #[cfg_attr( + Py_3_11, + deprecated( + note = "Deprecated in Python 3.11, use `PyConfig.argv` and `PyConfig.parse_argv` instead" + ) + )] + pub fn PySys_SetArgvEx(arg1: c_int, arg2: *mut *mut wchar_t, arg3: c_int); + pub fn PySys_SetPath(arg1: *const wchar_t); + + #[cfg_attr(PyPy, link_name = "PyPySys_WriteStdout")] + pub fn PySys_WriteStdout(format: *const c_char, ...); + #[cfg_attr(PyPy, link_name = "PyPySys_WriteStderr")] + pub fn PySys_WriteStderr(format: *const c_char, ...); + pub fn PySys_FormatStdout(format: *const c_char, ...); + pub fn PySys_FormatStderr(format: *const c_char, ...); + + #[cfg_attr( + Py_3_13, + deprecated( + note = "Deprecated since Python 3.13. Clear sys.warnoptions and warnings.filters instead." + ) + )] + pub fn PySys_ResetWarnOptions(); + #[cfg_attr(Py_3_11, deprecated(note = "Python 3.11"))] + pub fn PySys_AddWarnOption(arg1: *const wchar_t); + #[cfg_attr(Py_3_11, deprecated(note = "Python 3.11"))] + pub fn PySys_AddWarnOptionUnicode(arg1: *mut PyObject); + #[cfg_attr(Py_3_11, deprecated(note = "Python 3.11"))] + pub fn PySys_HasWarnOptions() -> c_int; + + pub fn PySys_AddXOption(arg1: *const wchar_t); + pub fn PySys_GetXOptions() -> *mut PyObject; +} diff --git a/include/pyo3/pyo3-ffi/src/traceback.rs b/include/pyo3/pyo3-ffi/src/traceback.rs new file mode 100644 index 00000000..432b6980 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/traceback.rs @@ -0,0 +1,27 @@ +use crate::object::*; +use std::os::raw::c_int; +#[cfg(not(PyPy))] +use std::ptr::addr_of_mut; + +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyTraceBack_Here")] + pub fn PyTraceBack_Here(arg1: *mut crate::PyFrameObject) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyTraceBack_Print")] + pub fn PyTraceBack_Print(arg1: *mut PyObject, arg2: *mut PyObject) -> c_int; +} + +#[cfg_attr(windows, link(name = "pythonXY"))] +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyTraceBack_Type")] + pub static mut PyTraceBack_Type: PyTypeObject; + + #[cfg(PyPy)] + #[link_name = "PyPyTraceBack_Check"] + pub fn PyTraceBack_Check(op: *mut PyObject) -> c_int; +} + +#[inline] +#[cfg(not(PyPy))] +pub unsafe fn PyTraceBack_Check(op: *mut PyObject) -> c_int { + (Py_TYPE(op) == addr_of_mut!(PyTraceBack_Type)) as c_int +} diff --git a/include/pyo3/pyo3-ffi/src/tupleobject.rs b/include/pyo3/pyo3-ffi/src/tupleobject.rs new file mode 100644 index 00000000..d265c91a --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/tupleobject.rs @@ -0,0 +1,42 @@ +use crate::object::*; +use crate::pyport::Py_ssize_t; +use std::os::raw::c_int; +use std::ptr::addr_of_mut; + +#[cfg_attr(windows, link(name = "pythonXY"))] +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyTuple_Type")] + pub static mut PyTuple_Type: PyTypeObject; + pub static mut PyTupleIter_Type: PyTypeObject; +} + +#[inline] +pub unsafe fn PyTuple_Check(op: *mut PyObject) -> c_int { + PyType_FastSubclass(Py_TYPE(op), Py_TPFLAGS_TUPLE_SUBCLASS) +} + +#[inline] +pub unsafe fn PyTuple_CheckExact(op: *mut PyObject) -> c_int { + (Py_TYPE(op) == addr_of_mut!(PyTuple_Type)) as c_int +} + +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyTuple_New")] + pub fn PyTuple_New(size: Py_ssize_t) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyTuple_Size")] + pub fn PyTuple_Size(arg1: *mut PyObject) -> Py_ssize_t; + #[cfg_attr(PyPy, link_name = "PyPyTuple_GetItem")] + pub fn PyTuple_GetItem(arg1: *mut PyObject, arg2: Py_ssize_t) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyTuple_SetItem")] + pub fn PyTuple_SetItem(arg1: *mut PyObject, arg2: Py_ssize_t, arg3: *mut PyObject) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyTuple_GetSlice")] + pub fn PyTuple_GetSlice( + arg1: *mut PyObject, + arg2: Py_ssize_t, + arg3: Py_ssize_t, + ) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyTuple_Pack")] + pub fn PyTuple_Pack(arg1: Py_ssize_t, ...) -> *mut PyObject; + #[cfg(not(Py_3_9))] + pub fn PyTuple_ClearFreeList() -> c_int; +} diff --git a/include/pyo3/pyo3-ffi/src/typeslots.rs b/include/pyo3/pyo3-ffi/src/typeslots.rs new file mode 100644 index 00000000..da7c60b3 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/typeslots.rs @@ -0,0 +1,82 @@ +use std::os::raw::c_int; + +pub const Py_bf_getbuffer: c_int = 1; +pub const Py_bf_releasebuffer: c_int = 2; +pub const Py_mp_ass_subscript: c_int = 3; +pub const Py_mp_length: c_int = 4; +pub const Py_mp_subscript: c_int = 5; +pub const Py_nb_absolute: c_int = 6; +pub const Py_nb_add: c_int = 7; +pub const Py_nb_and: c_int = 8; +pub const Py_nb_bool: c_int = 9; +pub const Py_nb_divmod: c_int = 10; +pub const Py_nb_float: c_int = 11; +pub const Py_nb_floor_divide: c_int = 12; +pub const Py_nb_index: c_int = 13; +pub const Py_nb_inplace_add: c_int = 14; +pub const Py_nb_inplace_and: c_int = 15; +pub const Py_nb_inplace_floor_divide: c_int = 16; +pub const Py_nb_inplace_lshift: c_int = 17; +pub const Py_nb_inplace_multiply: c_int = 18; +pub const Py_nb_inplace_or: c_int = 19; +pub const Py_nb_inplace_power: c_int = 20; +pub const Py_nb_inplace_remainder: c_int = 21; +pub const Py_nb_inplace_rshift: c_int = 22; +pub const Py_nb_inplace_subtract: c_int = 23; +pub const Py_nb_inplace_true_divide: c_int = 24; +pub const Py_nb_inplace_xor: c_int = 25; +pub const Py_nb_int: c_int = 26; +pub const Py_nb_invert: c_int = 27; +pub const Py_nb_lshift: c_int = 28; +pub const Py_nb_multiply: c_int = 29; +pub const Py_nb_negative: c_int = 30; +pub const Py_nb_or: c_int = 31; +pub const Py_nb_positive: c_int = 32; +pub const Py_nb_power: c_int = 33; +pub const Py_nb_remainder: c_int = 34; +pub const Py_nb_rshift: c_int = 35; +pub const Py_nb_subtract: c_int = 36; +pub const Py_nb_true_divide: c_int = 37; +pub const Py_nb_xor: c_int = 38; +pub const Py_sq_ass_item: c_int = 39; +pub const Py_sq_concat: c_int = 40; +pub const Py_sq_contains: c_int = 41; +pub const Py_sq_inplace_concat: c_int = 42; +pub const Py_sq_inplace_repeat: c_int = 43; +pub const Py_sq_item: c_int = 44; +pub const Py_sq_length: c_int = 45; +pub const Py_sq_repeat: c_int = 46; +pub const Py_tp_alloc: c_int = 47; +pub const Py_tp_base: c_int = 48; +pub const Py_tp_bases: c_int = 49; +pub const Py_tp_call: c_int = 50; +pub const Py_tp_clear: c_int = 51; +pub const Py_tp_dealloc: c_int = 52; +pub const Py_tp_del: c_int = 53; +pub const Py_tp_descr_get: c_int = 54; +pub const Py_tp_descr_set: c_int = 55; +pub const Py_tp_doc: c_int = 56; +pub const Py_tp_getattr: c_int = 57; +pub const Py_tp_getattro: c_int = 58; +pub const Py_tp_hash: c_int = 59; +pub const Py_tp_init: c_int = 60; +pub const Py_tp_is_gc: c_int = 61; +pub const Py_tp_iter: c_int = 62; +pub const Py_tp_iternext: c_int = 63; +pub const Py_tp_methods: c_int = 64; +pub const Py_tp_new: c_int = 65; +pub const Py_tp_repr: c_int = 66; +pub const Py_tp_richcompare: c_int = 67; +pub const Py_tp_setattr: c_int = 68; +pub const Py_tp_setattro: c_int = 69; +pub const Py_tp_str: c_int = 70; +pub const Py_tp_traverse: c_int = 71; +pub const Py_tp_members: c_int = 72; +pub const Py_tp_getset: c_int = 73; +pub const Py_tp_free: c_int = 74; +pub const Py_nb_matrix_multiply: c_int = 75; +pub const Py_nb_inplace_matrix_multiply: c_int = 76; +pub const Py_am_await: c_int = 77; +pub const Py_am_aiter: c_int = 78; +pub const Py_am_anext: c_int = 79; +pub const Py_tp_finalize: c_int = 80; diff --git a/include/pyo3/pyo3-ffi/src/unicodeobject.rs b/include/pyo3/pyo3-ffi/src/unicodeobject.rs new file mode 100644 index 00000000..1e0425ce --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/unicodeobject.rs @@ -0,0 +1,353 @@ +use crate::object::*; +use crate::pyport::Py_ssize_t; +use libc::wchar_t; +use std::os::raw::{c_char, c_int, c_void}; +#[cfg(not(PyPy))] +use std::ptr::addr_of_mut; + +#[cfg(not(Py_LIMITED_API))] +#[cfg_attr( + Py_3_13, + deprecated(note = "Deprecated since Python 3.13. Use `libc::wchar_t` instead.") +)] +pub type Py_UNICODE = wchar_t; + +pub type Py_UCS4 = u32; +pub type Py_UCS2 = u16; +pub type Py_UCS1 = u8; + +#[cfg_attr(windows, link(name = "pythonXY"))] +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyUnicode_Type")] + pub static mut PyUnicode_Type: PyTypeObject; + pub static mut PyUnicodeIter_Type: PyTypeObject; + + #[cfg(PyPy)] + #[link_name = "PyPyUnicode_Check"] + pub fn PyUnicode_Check(op: *mut PyObject) -> c_int; + + #[cfg(PyPy)] + #[link_name = "PyPyUnicode_CheckExact"] + pub fn PyUnicode_CheckExact(op: *mut PyObject) -> c_int; +} + +#[inline] +#[cfg(not(PyPy))] +pub unsafe fn PyUnicode_Check(op: *mut PyObject) -> c_int { + PyType_FastSubclass(Py_TYPE(op), Py_TPFLAGS_UNICODE_SUBCLASS) +} + +#[inline] +#[cfg(not(PyPy))] +pub unsafe fn PyUnicode_CheckExact(op: *mut PyObject) -> c_int { + (Py_TYPE(op) == addr_of_mut!(PyUnicode_Type)) as c_int +} + +pub const Py_UNICODE_REPLACEMENT_CHARACTER: Py_UCS4 = 0xFFFD; + +extern "C" { + + #[cfg_attr(PyPy, link_name = "PyPyUnicode_FromStringAndSize")] + pub fn PyUnicode_FromStringAndSize(u: *const c_char, size: Py_ssize_t) -> *mut PyObject; + pub fn PyUnicode_FromString(u: *const c_char) -> *mut PyObject; + + pub fn PyUnicode_Substring( + str: *mut PyObject, + start: Py_ssize_t, + end: Py_ssize_t, + ) -> *mut PyObject; + pub fn PyUnicode_AsUCS4( + unicode: *mut PyObject, + buffer: *mut Py_UCS4, + buflen: Py_ssize_t, + copy_null: c_int, + ) -> *mut Py_UCS4; + pub fn PyUnicode_AsUCS4Copy(unicode: *mut PyObject) -> *mut Py_UCS4; + #[cfg_attr(PyPy, link_name = "PyPyUnicode_GetLength")] + pub fn PyUnicode_GetLength(unicode: *mut PyObject) -> Py_ssize_t; + #[cfg(not(Py_3_12))] + #[deprecated(note = "Removed in Python 3.12")] + #[cfg_attr(PyPy, link_name = "PyPyUnicode_GetSize")] + pub fn PyUnicode_GetSize(unicode: *mut PyObject) -> Py_ssize_t; + pub fn PyUnicode_ReadChar(unicode: *mut PyObject, index: Py_ssize_t) -> Py_UCS4; + pub fn PyUnicode_WriteChar( + unicode: *mut PyObject, + index: Py_ssize_t, + character: Py_UCS4, + ) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyUnicode_Resize")] + pub fn PyUnicode_Resize(unicode: *mut *mut PyObject, length: Py_ssize_t) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyUnicode_FromEncodedObject")] + pub fn PyUnicode_FromEncodedObject( + obj: *mut PyObject, + encoding: *const c_char, + errors: *const c_char, + ) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyUnicode_FromObject")] + pub fn PyUnicode_FromObject(obj: *mut PyObject) -> *mut PyObject; + // #[cfg_attr(PyPy, link_name = "PyPyUnicode_FromFormatV")] + // pub fn PyUnicode_FromFormatV(format: *const c_char, vargs: va_list) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyUnicode_FromFormat")] + pub fn PyUnicode_FromFormat(format: *const c_char, ...) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyUnicode_InternInPlace")] + pub fn PyUnicode_InternInPlace(arg1: *mut *mut PyObject); + #[cfg(not(Py_3_12))] + #[cfg_attr(Py_3_10, deprecated(note = "Python 3.10"))] + pub fn PyUnicode_InternImmortal(arg1: *mut *mut PyObject); + #[cfg_attr(PyPy, link_name = "PyPyUnicode_InternFromString")] + pub fn PyUnicode_InternFromString(u: *const c_char) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyUnicode_FromWideChar")] + pub fn PyUnicode_FromWideChar(w: *const wchar_t, size: Py_ssize_t) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyUnicode_AsWideChar")] + pub fn PyUnicode_AsWideChar( + unicode: *mut PyObject, + w: *mut wchar_t, + size: Py_ssize_t, + ) -> Py_ssize_t; + #[cfg_attr(PyPy, link_name = "PyPyUnicode_AsWideCharString")] + pub fn PyUnicode_AsWideCharString( + unicode: *mut PyObject, + size: *mut Py_ssize_t, + ) -> *mut wchar_t; + #[cfg_attr(PyPy, link_name = "PyPyUnicode_FromOrdinal")] + pub fn PyUnicode_FromOrdinal(ordinal: c_int) -> *mut PyObject; + pub fn PyUnicode_ClearFreeList() -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyUnicode_GetDefaultEncoding")] + pub fn PyUnicode_GetDefaultEncoding() -> *const c_char; + #[cfg_attr(PyPy, link_name = "PyPyUnicode_Decode")] + pub fn PyUnicode_Decode( + s: *const c_char, + size: Py_ssize_t, + encoding: *const c_char, + errors: *const c_char, + ) -> *mut PyObject; + pub fn PyUnicode_AsDecodedObject( + unicode: *mut PyObject, + encoding: *const c_char, + errors: *const c_char, + ) -> *mut PyObject; + pub fn PyUnicode_AsDecodedUnicode( + unicode: *mut PyObject, + encoding: *const c_char, + errors: *const c_char, + ) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyUnicode_AsEncodedObject")] + pub fn PyUnicode_AsEncodedObject( + unicode: *mut PyObject, + encoding: *const c_char, + errors: *const c_char, + ) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyUnicode_AsEncodedString")] + pub fn PyUnicode_AsEncodedString( + unicode: *mut PyObject, + encoding: *const c_char, + errors: *const c_char, + ) -> *mut PyObject; + pub fn PyUnicode_AsEncodedUnicode( + unicode: *mut PyObject, + encoding: *const c_char, + errors: *const c_char, + ) -> *mut PyObject; + pub fn PyUnicode_BuildEncodingMap(string: *mut PyObject) -> *mut PyObject; + pub fn PyUnicode_DecodeUTF7( + string: *const c_char, + length: Py_ssize_t, + errors: *const c_char, + ) -> *mut PyObject; + pub fn PyUnicode_DecodeUTF7Stateful( + string: *const c_char, + length: Py_ssize_t, + errors: *const c_char, + consumed: *mut Py_ssize_t, + ) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyUnicode_DecodeUTF8")] + pub fn PyUnicode_DecodeUTF8( + string: *const c_char, + length: Py_ssize_t, + errors: *const c_char, + ) -> *mut PyObject; + pub fn PyUnicode_DecodeUTF8Stateful( + string: *const c_char, + length: Py_ssize_t, + errors: *const c_char, + consumed: *mut Py_ssize_t, + ) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyUnicode_AsUTF8String")] + pub fn PyUnicode_AsUTF8String(unicode: *mut PyObject) -> *mut PyObject; + #[cfg(any(Py_3_10, not(Py_LIMITED_API)))] + #[cfg_attr(PyPy, link_name = "PyPyUnicode_AsUTF8AndSize")] + pub fn PyUnicode_AsUTF8AndSize(unicode: *mut PyObject, size: *mut Py_ssize_t) -> *const c_char; + #[cfg_attr(PyPy, link_name = "PyPyUnicode_DecodeUTF32")] + pub fn PyUnicode_DecodeUTF32( + string: *const c_char, + length: Py_ssize_t, + errors: *const c_char, + byteorder: *mut c_int, + ) -> *mut PyObject; + pub fn PyUnicode_DecodeUTF32Stateful( + string: *const c_char, + length: Py_ssize_t, + errors: *const c_char, + byteorder: *mut c_int, + consumed: *mut Py_ssize_t, + ) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyUnicode_AsUTF32String")] + pub fn PyUnicode_AsUTF32String(unicode: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyUnicode_DecodeUTF16")] + pub fn PyUnicode_DecodeUTF16( + string: *const c_char, + length: Py_ssize_t, + errors: *const c_char, + byteorder: *mut c_int, + ) -> *mut PyObject; + pub fn PyUnicode_DecodeUTF16Stateful( + string: *const c_char, + length: Py_ssize_t, + errors: *const c_char, + byteorder: *mut c_int, + consumed: *mut Py_ssize_t, + ) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyUnicode_AsUTF16String")] + pub fn PyUnicode_AsUTF16String(unicode: *mut PyObject) -> *mut PyObject; + pub fn PyUnicode_DecodeUnicodeEscape( + string: *const c_char, + length: Py_ssize_t, + errors: *const c_char, + ) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyUnicode_AsUnicodeEscapeString")] + pub fn PyUnicode_AsUnicodeEscapeString(unicode: *mut PyObject) -> *mut PyObject; + pub fn PyUnicode_DecodeRawUnicodeEscape( + string: *const c_char, + length: Py_ssize_t, + errors: *const c_char, + ) -> *mut PyObject; + pub fn PyUnicode_AsRawUnicodeEscapeString(unicode: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyUnicode_DecodeLatin1")] + pub fn PyUnicode_DecodeLatin1( + string: *const c_char, + length: Py_ssize_t, + errors: *const c_char, + ) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyUnicode_AsLatin1String")] + pub fn PyUnicode_AsLatin1String(unicode: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyUnicode_DecodeASCII")] + pub fn PyUnicode_DecodeASCII( + string: *const c_char, + length: Py_ssize_t, + errors: *const c_char, + ) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyUnicode_AsASCIIString")] + pub fn PyUnicode_AsASCIIString(unicode: *mut PyObject) -> *mut PyObject; + pub fn PyUnicode_DecodeCharmap( + string: *const c_char, + length: Py_ssize_t, + mapping: *mut PyObject, + errors: *const c_char, + ) -> *mut PyObject; + pub fn PyUnicode_AsCharmapString( + unicode: *mut PyObject, + mapping: *mut PyObject, + ) -> *mut PyObject; + pub fn PyUnicode_DecodeLocaleAndSize( + str: *const c_char, + len: Py_ssize_t, + errors: *const c_char, + ) -> *mut PyObject; + pub fn PyUnicode_DecodeLocale(str: *const c_char, errors: *const c_char) -> *mut PyObject; + pub fn PyUnicode_EncodeLocale(unicode: *mut PyObject, errors: *const c_char) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyUnicode_FSConverter")] + pub fn PyUnicode_FSConverter(arg1: *mut PyObject, arg2: *mut c_void) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyUnicode_FSDecoder")] + pub fn PyUnicode_FSDecoder(arg1: *mut PyObject, arg2: *mut c_void) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyUnicode_DecodeFSDefault")] + pub fn PyUnicode_DecodeFSDefault(s: *const c_char) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyUnicode_DecodeFSDefaultAndSize")] + pub fn PyUnicode_DecodeFSDefaultAndSize(s: *const c_char, size: Py_ssize_t) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyUnicode_EncodeFSDefault")] + pub fn PyUnicode_EncodeFSDefault(unicode: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyUnicode_Concat")] + pub fn PyUnicode_Concat(left: *mut PyObject, right: *mut PyObject) -> *mut PyObject; + pub fn PyUnicode_Append(pleft: *mut *mut PyObject, right: *mut PyObject); + pub fn PyUnicode_AppendAndDel(pleft: *mut *mut PyObject, right: *mut PyObject); + #[cfg_attr(PyPy, link_name = "PyPyUnicode_Split")] + pub fn PyUnicode_Split( + s: *mut PyObject, + sep: *mut PyObject, + maxsplit: Py_ssize_t, + ) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyUnicode_Splitlines")] + pub fn PyUnicode_Splitlines(s: *mut PyObject, keepends: c_int) -> *mut PyObject; + pub fn PyUnicode_Partition(s: *mut PyObject, sep: *mut PyObject) -> *mut PyObject; + pub fn PyUnicode_RPartition(s: *mut PyObject, sep: *mut PyObject) -> *mut PyObject; + pub fn PyUnicode_RSplit( + s: *mut PyObject, + sep: *mut PyObject, + maxsplit: Py_ssize_t, + ) -> *mut PyObject; + pub fn PyUnicode_Translate( + str: *mut PyObject, + table: *mut PyObject, + errors: *const c_char, + ) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyUnicode_Join")] + pub fn PyUnicode_Join(separator: *mut PyObject, seq: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyUnicode_Tailmatch")] + pub fn PyUnicode_Tailmatch( + str: *mut PyObject, + substr: *mut PyObject, + start: Py_ssize_t, + end: Py_ssize_t, + direction: c_int, + ) -> Py_ssize_t; + #[cfg_attr(PyPy, link_name = "PyPyUnicode_Find")] + pub fn PyUnicode_Find( + str: *mut PyObject, + substr: *mut PyObject, + start: Py_ssize_t, + end: Py_ssize_t, + direction: c_int, + ) -> Py_ssize_t; + pub fn PyUnicode_FindChar( + str: *mut PyObject, + ch: Py_UCS4, + start: Py_ssize_t, + end: Py_ssize_t, + direction: c_int, + ) -> Py_ssize_t; + #[cfg_attr(PyPy, link_name = "PyPyUnicode_Count")] + pub fn PyUnicode_Count( + str: *mut PyObject, + substr: *mut PyObject, + start: Py_ssize_t, + end: Py_ssize_t, + ) -> Py_ssize_t; + #[cfg_attr(PyPy, link_name = "PyPyUnicode_Replace")] + pub fn PyUnicode_Replace( + str: *mut PyObject, + substr: *mut PyObject, + replstr: *mut PyObject, + maxcount: Py_ssize_t, + ) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyUnicode_Compare")] + pub fn PyUnicode_Compare(left: *mut PyObject, right: *mut PyObject) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyUnicode_CompareWithASCIIString")] + pub fn PyUnicode_CompareWithASCIIString(left: *mut PyObject, right: *const c_char) -> c_int; + #[cfg(Py_3_13)] + pub fn PyUnicode_EqualToUTF8(unicode: *mut PyObject, string: *const c_char) -> c_int; + #[cfg(Py_3_13)] + pub fn PyUnicode_EqualToUTF8AndSize( + unicode: *mut PyObject, + string: *const c_char, + size: Py_ssize_t, + ) -> c_int; + + pub fn PyUnicode_RichCompare( + left: *mut PyObject, + right: *mut PyObject, + op: c_int, + ) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyUnicode_Format")] + pub fn PyUnicode_Format(format: *mut PyObject, args: *mut PyObject) -> *mut PyObject; + pub fn PyUnicode_Contains(container: *mut PyObject, element: *mut PyObject) -> c_int; + pub fn PyUnicode_IsIdentifier(s: *mut PyObject) -> c_int; +} diff --git a/include/pyo3/pyo3-ffi/src/warnings.rs b/include/pyo3/pyo3-ffi/src/warnings.rs new file mode 100644 index 00000000..d58f3743 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/warnings.rs @@ -0,0 +1,34 @@ +use crate::object::PyObject; +use crate::pyport::Py_ssize_t; +use std::os::raw::{c_char, c_int}; + +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyErr_WarnEx")] + pub fn PyErr_WarnEx( + category: *mut PyObject, + message: *const c_char, + stack_level: Py_ssize_t, + ) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyErr_WarnFormat")] + pub fn PyErr_WarnFormat( + category: *mut PyObject, + stack_level: Py_ssize_t, + format: *const c_char, + ... + ) -> c_int; + pub fn PyErr_ResourceWarning( + source: *mut PyObject, + stack_level: Py_ssize_t, + format: *const c_char, + ... + ) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyErr_WarnExplicit")] + pub fn PyErr_WarnExplicit( + category: *mut PyObject, + message: *const c_char, + filename: *const c_char, + lineno: c_int, + module: *const c_char, + registry: *mut PyObject, + ) -> c_int; +} diff --git a/include/pyo3/pyo3-ffi/src/weakrefobject.rs b/include/pyo3/pyo3-ffi/src/weakrefobject.rs new file mode 100644 index 00000000..305dc290 --- /dev/null +++ b/include/pyo3/pyo3-ffi/src/weakrefobject.rs @@ -0,0 +1,69 @@ +use crate::object::*; +use std::os::raw::c_int; +#[cfg(not(PyPy))] +use std::ptr::addr_of_mut; + +#[cfg(all(not(PyPy), Py_LIMITED_API, not(GraalPy)))] +opaque_struct!(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" { + pub static mut _PyWeakref_RefType: PyTypeObject; + pub static mut _PyWeakref_ProxyType: PyTypeObject; + pub static mut _PyWeakref_CallableProxyType: PyTypeObject; + + #[cfg(PyPy)] + #[link_name = "PyPyWeakref_CheckRef"] + pub fn PyWeakref_CheckRef(op: *mut PyObject) -> c_int; + + #[cfg(PyPy)] + #[link_name = "PyPyWeakref_CheckRefExact"] + pub fn PyWeakref_CheckRefExact(op: *mut PyObject) -> c_int; + + #[cfg(PyPy)] + #[link_name = "PyPyWeakref_CheckProxy"] + pub fn PyWeakref_CheckProxy(op: *mut PyObject) -> c_int; +} + +#[inline] +#[cfg(not(PyPy))] +pub unsafe fn PyWeakref_CheckRef(op: *mut PyObject) -> c_int { + PyObject_TypeCheck(op, addr_of_mut!(_PyWeakref_RefType)) +} + +#[inline] +#[cfg(not(PyPy))] +pub unsafe fn PyWeakref_CheckRefExact(op: *mut PyObject) -> c_int { + (Py_TYPE(op) == addr_of_mut!(_PyWeakref_RefType)) as c_int +} + +#[inline] +#[cfg(not(PyPy))] +pub unsafe fn PyWeakref_CheckProxy(op: *mut PyObject) -> c_int { + ((Py_TYPE(op) == addr_of_mut!(_PyWeakref_ProxyType)) + || (Py_TYPE(op) == addr_of_mut!(_PyWeakref_CallableProxyType))) as c_int +} + +#[inline] +pub unsafe fn PyWeakref_Check(op: *mut PyObject) -> c_int { + (PyWeakref_CheckRef(op) != 0 || PyWeakref_CheckProxy(op) != 0) as c_int +} + +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyWeakref_NewRef")] + pub fn PyWeakref_NewRef(ob: *mut PyObject, callback: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyWeakref_NewProxy")] + pub fn PyWeakref_NewProxy(ob: *mut PyObject, callback: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyWeakref_GetObject")] + #[cfg_attr( + Py_3_13, + deprecated(note = "deprecated since Python 3.13. Use `PyWeakref_GetRef` instead.") + )] + pub fn PyWeakref_GetObject(reference: *mut PyObject) -> *mut PyObject; + #[cfg(Py_3_13)] + #[cfg_attr(PyPy, link_name = "PyPyWeakref_GetRef")] + pub fn PyWeakref_GetRef(reference: *mut PyObject, pobj: *mut *mut PyObject) -> c_int; +} diff --git a/requirements.txt b/requirements.txt index a9d99bf5..451c31d3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,8 +2,8 @@ -r integration/requirements.txt -r test/requirements.txt maturin -mypy==1.10.0 -ruff==0.5.5 +mypy==1.11.2 +ruff==0.6.8 types-python-dateutil types-pytz types-simplejson diff --git a/script/install-fedora b/script/install-fedora new file mode 100755 index 00000000..c9606115 --- /dev/null +++ b/script/install-fedora @@ -0,0 +1,30 @@ +#!/bin/sh -e + +# export PYTHON=python3.11 +# export PYTHON_PACKAGE=python3.11 +# export RUST_TOOLCHAIN=nightly-2024-09-25 +# export TARGET=x86_64-unknown-linux-gnu +# export VENV=.venv +# export CARGO_TARGET_DIR=/tmp/orjson + +export VENV="${VENV:-.venv}" +export CARGO_TARGET_DIR="${CARGO_TARGET_DIR:-target}" + +sudo rm /etc/yum.repos.d/fedora-cisco-openh264.repo || true + +dnf install -y rustup clang lld "${PYTHON_PACKAGE}" + +rustup-init --default-toolchain "${RUST_TOOLCHAIN}-${TARGET}" --profile minimal --component rust-src -y +source "${HOME}/.cargo/env" + +mkdir -p .cargo +cp ci/config.toml .cargo/config.toml + +cargo fetch --target="${TARGET}" & + +curl -LsSf https://astral.sh/uv/install.sh | sh +rm -rf "${VENV}" +uv venv --python "${PYTHON}" "${VENV}" +source "${VENV}/bin/activate" + +uv pip install --upgrade "maturin>=1,<2" -r test/requirements.txt -r integration/requirements.txt diff --git a/src/deserialize/backend/json.rs b/src/deserialize/backend/json.rs index fd531f0d..4a962b0e 100644 --- a/src/deserialize/backend/json.rs +++ b/src/deserialize/backend/json.rs @@ -125,7 +125,7 @@ impl<'de> Visitor<'de> for JsonValue { A: MapAccess<'de>, { let dict_ptr = ffi!(PyDict_New()); - while let Some(key) = map.next_key::>()? { + while let Some(key) = map.next_key::>()? { let pykey = get_unicode_key(&key); let pyval = map.next_value_seed(self)?; let _ = unsafe { diff --git a/src/lib.rs b/src/lib.rs index 076f8098..5fe295eb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,10 +6,11 @@ #![cfg_attr(feature = "strict_provenance", feature(strict_provenance))] #![cfg_attr(feature = "strict_provenance", warn(fuzzy_provenance_casts))] #![cfg_attr(feature = "unstable-simd", feature(portable_simd))] -#![allow(unknown_lints)] // internal_features #![allow(internal_features)] // core_intrinsics -#![allow(unused_unsafe)] #![allow(non_camel_case_types)] +#![allow(static_mut_refs)] +#![allow(unknown_lints)] // internal_features +#![allow(unused_unsafe)] #![allow(clippy::missing_safety_doc)] #![allow(clippy::redundant_field_names)] #![allow(clippy::uninlined_format_args)] // MSRV 1.66 @@ -35,7 +36,14 @@ use pyo3_ffi::*; #[allow(unused_imports)] use core::ptr::{null, null_mut, NonNull}; -#[cfg(Py_3_10)] +#[cfg(Py_3_13)] +macro_rules! add { + ($mptr:expr, $name:expr, $obj:expr) => { + PyModule_Add($mptr, $name.as_ptr() as *const c_char, $obj); + }; +} + +#[cfg(all(Py_3_10, not(Py_3_13)))] macro_rules! add { ($mptr:expr, $name:expr, $obj:expr) => { PyModule_AddObjectRef($mptr, $name.as_ptr() as *const c_char, $obj); @@ -79,6 +87,9 @@ pub unsafe extern "C" fn orjson_init_exec(mptr: *mut PyObject) -> c_int { let wrapped_dumps = PyMethodDef { ml_name: "dumps\0".as_ptr() as *const c_char, ml_meth: PyMethodDefPointer { + #[cfg(Py_3_10)] + PyCFunctionFastWithKeywords: dumps, + #[cfg(not(Py_3_10))] _PyCFunctionFastWithKeywords: dumps, }, ml_flags: pyo3_ffi::METH_FASTCALL | METH_KEYWORDS, diff --git a/src/serialize/serializer.rs b/src/serialize/serializer.rs index a085ff37..bfbf92b2 100644 --- a/src/serialize/serializer.rs +++ b/src/serialize/serializer.rs @@ -34,7 +34,7 @@ pub fn serialize( Ok(buf.finish()) } Err(err) => { - ffi!(_Py_Dealloc(buf.bytes_ptr().as_ptr())); + ffi!(Py_DECREF(buf.bytes_ptr().as_ptr())); Err(err.to_string()) } } diff --git a/test/test_numpy.py b/test/test_numpy.py index dd506a21..637faf13 100644 --- a/test/test_numpy.py +++ b/test/test_numpy.py @@ -718,82 +718,337 @@ def test_numpy_bool(self): == b'{"a":true,"b":false}' ) - def test_numpy_datetime(self): + def test_numpy_datetime_year(self): + assert ( + orjson.dumps(numpy.datetime64("2021"), option=orjson.OPT_SERIALIZE_NUMPY) + == b'"2021-01-01T00:00:00"' + ) + + def test_numpy_datetime_month(self): + assert ( + orjson.dumps(numpy.datetime64("2021-01"), option=orjson.OPT_SERIALIZE_NUMPY) + == b'"2021-01-01T00:00:00"' + ) + + def test_numpy_datetime_day(self): + assert ( + orjson.dumps( + numpy.datetime64("2021-01-01"), option=orjson.OPT_SERIALIZE_NUMPY + ) + == b'"2021-01-01T00:00:00"' + ) + + def test_numpy_datetime_hour(self): + assert ( + orjson.dumps( + numpy.datetime64("2021-01-01T00"), option=orjson.OPT_SERIALIZE_NUMPY + ) + == b'"2021-01-01T00:00:00"' + ) + + def test_numpy_datetime_minute(self): + assert ( + orjson.dumps( + numpy.datetime64("2021-01-01T00:00"), option=orjson.OPT_SERIALIZE_NUMPY + ) + == b'"2021-01-01T00:00:00"' + ) + + def test_numpy_datetime_second(self): + assert ( + orjson.dumps( + numpy.datetime64("2021-01-01T00:00:00"), + option=orjson.OPT_SERIALIZE_NUMPY, + ) + == b'"2021-01-01T00:00:00"' + ) + + def test_numpy_datetime_milli(self): assert ( orjson.dumps( - { - "year": numpy.datetime64("2021"), - "month": numpy.datetime64("2021-01"), - "day": numpy.datetime64("2021-01-01"), - "hour": numpy.datetime64("2021-01-01T00"), - "minute": numpy.datetime64("2021-01-01T00:00"), - "second": numpy.datetime64("2021-01-01T00:00:00"), - "milli": numpy.datetime64("2021-01-01T00:00:00.172"), - "micro": numpy.datetime64("2021-01-01T00:00:00.172576"), - "nano": numpy.datetime64("2021-01-01T00:00:00.172576789"), - }, + numpy.datetime64("2021-01-01T00:00:00.172"), option=orjson.OPT_SERIALIZE_NUMPY, ) - == b'{"year":"2021-01-01T00:00:00","month":"2021-01-01T00:00:00","day":"2021-01-01T00:00:00","hour":"2021-01-01T00:00:00","minute":"2021-01-01T00:00:00","second":"2021-01-01T00:00:00","milli":"2021-01-01T00:00:00.172000","micro":"2021-01-01T00:00:00.172576","nano":"2021-01-01T00:00:00.172576"}' + == b'"2021-01-01T00:00:00.172000"' + ) + + def test_numpy_datetime_micro(self): + assert ( + orjson.dumps( + numpy.datetime64("2021-01-01T00:00:00.172576"), + option=orjson.OPT_SERIALIZE_NUMPY, + ) + == b'"2021-01-01T00:00:00.172576"' + ) + + def test_numpy_datetime_nano(self): + assert ( + orjson.dumps( + numpy.datetime64("2021-01-01T00:00:00.172576789"), + option=orjson.OPT_SERIALIZE_NUMPY, + ) + == b'"2021-01-01T00:00:00.172576"' + ) + + def test_numpy_datetime_naive_utc_year(self): + assert ( + orjson.dumps( + numpy.datetime64("2021"), + option=orjson.OPT_SERIALIZE_NUMPY | orjson.OPT_NAIVE_UTC, + ) + == b'"2021-01-01T00:00:00+00:00"' + ) + + def test_numpy_datetime_naive_utc_month(self): + assert ( + orjson.dumps( + numpy.datetime64("2021-01"), + option=orjson.OPT_SERIALIZE_NUMPY | orjson.OPT_NAIVE_UTC, + ) + == b'"2021-01-01T00:00:00+00:00"' + ) + + def test_numpy_datetime_naive_utc_day(self): + assert ( + orjson.dumps( + numpy.datetime64("2021-01-01"), + option=orjson.OPT_SERIALIZE_NUMPY | orjson.OPT_NAIVE_UTC, + ) + == b'"2021-01-01T00:00:00+00:00"' + ) + + def test_numpy_datetime_naive_utc_hour(self): + assert ( + orjson.dumps( + numpy.datetime64("2021-01-01T00"), + option=orjson.OPT_SERIALIZE_NUMPY | orjson.OPT_NAIVE_UTC, + ) + == b'"2021-01-01T00:00:00+00:00"' + ) + + def test_numpy_datetime_naive_utc_minute(self): + assert ( + orjson.dumps( + numpy.datetime64("2021-01-01T00:00"), + option=orjson.OPT_SERIALIZE_NUMPY | orjson.OPT_NAIVE_UTC, + ) + == b'"2021-01-01T00:00:00+00:00"' + ) + + def test_numpy_datetime_naive_utc_second(self): + assert ( + orjson.dumps( + numpy.datetime64("2021-01-01T00:00:00"), + option=orjson.OPT_SERIALIZE_NUMPY | orjson.OPT_NAIVE_UTC, + ) + == b'"2021-01-01T00:00:00+00:00"' ) - def test_numpy_datetime_naive_utc(self): + def test_numpy_datetime_naive_utc_milli(self): assert ( orjson.dumps( - { - "year": numpy.datetime64("2021"), - "month": numpy.datetime64("2021-01"), - "day": numpy.datetime64("2021-01-01"), - "hour": numpy.datetime64("2021-01-01T00"), - "minute": numpy.datetime64("2021-01-01T00:00"), - "second": numpy.datetime64("2021-01-01T00:00:00"), - "milli": numpy.datetime64("2021-01-01T00:00:00.172"), - "micro": numpy.datetime64("2021-01-01T00:00:00.172576"), - "nano": numpy.datetime64("2021-01-01T00:00:00.172576789"), - }, + numpy.datetime64("2021-01-01T00:00:00.172"), option=orjson.OPT_SERIALIZE_NUMPY | orjson.OPT_NAIVE_UTC, ) - == b'{"year":"2021-01-01T00:00:00+00:00","month":"2021-01-01T00:00:00+00:00","day":"2021-01-01T00:00:00+00:00","hour":"2021-01-01T00:00:00+00:00","minute":"2021-01-01T00:00:00+00:00","second":"2021-01-01T00:00:00+00:00","milli":"2021-01-01T00:00:00.172000+00:00","micro":"2021-01-01T00:00:00.172576+00:00","nano":"2021-01-01T00:00:00.172576+00:00"}' + == b'"2021-01-01T00:00:00.172000+00:00"' ) - def test_numpy_datetime_naive_utc_utc_z(self): + def test_numpy_datetime_naive_utc_micro(self): assert ( orjson.dumps( - { - "year": numpy.datetime64("2021"), - "month": numpy.datetime64("2021-01"), - "day": numpy.datetime64("2021-01-01"), - "hour": numpy.datetime64("2021-01-01T00"), - "minute": numpy.datetime64("2021-01-01T00:00"), - "second": numpy.datetime64("2021-01-01T00:00:00"), - "milli": numpy.datetime64("2021-01-01T00:00:00.172"), - "micro": numpy.datetime64("2021-01-01T00:00:00.172576"), - "nano": numpy.datetime64("2021-01-01T00:00:00.172576789"), - }, + numpy.datetime64("2021-01-01T00:00:00.172576"), + option=orjson.OPT_SERIALIZE_NUMPY | orjson.OPT_NAIVE_UTC, + ) + == b'"2021-01-01T00:00:00.172576+00:00"' + ) + + def test_numpy_datetime_naive_utc_nano(self): + assert ( + orjson.dumps( + numpy.datetime64("2021-01-01T00:00:00.172576789"), + option=orjson.OPT_SERIALIZE_NUMPY | orjson.OPT_NAIVE_UTC, + ) + == b'"2021-01-01T00:00:00.172576+00:00"' + ) + + def test_numpy_datetime_naive_utc_utc_z_year(self): + assert ( + orjson.dumps( + numpy.datetime64("2021"), option=orjson.OPT_SERIALIZE_NUMPY | orjson.OPT_NAIVE_UTC | orjson.OPT_UTC_Z, ) - == b'{"year":"2021-01-01T00:00:00Z","month":"2021-01-01T00:00:00Z","day":"2021-01-01T00:00:00Z","hour":"2021-01-01T00:00:00Z","minute":"2021-01-01T00:00:00Z","second":"2021-01-01T00:00:00Z","milli":"2021-01-01T00:00:00.172000Z","micro":"2021-01-01T00:00:00.172576Z","nano":"2021-01-01T00:00:00.172576Z"}' + == b'"2021-01-01T00:00:00Z"' + ) + + def test_numpy_datetime_naive_utc_utc_z_month(self): + assert ( + orjson.dumps( + numpy.datetime64("2021-01"), + option=orjson.OPT_SERIALIZE_NUMPY + | orjson.OPT_NAIVE_UTC + | orjson.OPT_UTC_Z, + ) + == b'"2021-01-01T00:00:00Z"' + ) + + def test_numpy_datetime_naive_utc_utc_z_day(self): + assert ( + orjson.dumps( + numpy.datetime64("2021-01-01"), + option=orjson.OPT_SERIALIZE_NUMPY + | orjson.OPT_NAIVE_UTC + | orjson.OPT_UTC_Z, + ) + == b'"2021-01-01T00:00:00Z"' + ) + + def test_numpy_datetime_naive_utc_utc_z_hour(self): + assert ( + orjson.dumps( + numpy.datetime64("2021-01-01T00"), + option=orjson.OPT_SERIALIZE_NUMPY + | orjson.OPT_NAIVE_UTC + | orjson.OPT_UTC_Z, + ) + == b'"2021-01-01T00:00:00Z"' + ) + + def test_numpy_datetime_naive_utc_utc_z_minute(self): + assert ( + orjson.dumps( + numpy.datetime64("2021-01-01T00:00"), + option=orjson.OPT_SERIALIZE_NUMPY + | orjson.OPT_NAIVE_UTC + | orjson.OPT_UTC_Z, + ) + == b'"2021-01-01T00:00:00Z"' + ) + + def test_numpy_datetime_naive_utc_utc_z_second(self): + assert ( + orjson.dumps( + numpy.datetime64("2021-01-01T00:00:00"), + option=orjson.OPT_SERIALIZE_NUMPY + | orjson.OPT_NAIVE_UTC + | orjson.OPT_UTC_Z, + ) + == b'"2021-01-01T00:00:00Z"' + ) + + def test_numpy_datetime_naive_utc_utc_z_milli(self): + assert ( + orjson.dumps( + numpy.datetime64("2021-01-01T00:00:00.172"), + option=orjson.OPT_SERIALIZE_NUMPY + | orjson.OPT_NAIVE_UTC + | orjson.OPT_UTC_Z, + ) + == b'"2021-01-01T00:00:00.172000Z"' + ) + + def test_numpy_datetime_naive_utc_utc_z_micro(self): + assert ( + orjson.dumps( + numpy.datetime64("2021-01-01T00:00:00.172576"), + option=orjson.OPT_SERIALIZE_NUMPY + | orjson.OPT_NAIVE_UTC + | orjson.OPT_UTC_Z, + ) + == b'"2021-01-01T00:00:00.172576Z"' + ) + + def test_numpy_datetime_naive_utc_utc_z_nano(self): + assert ( + orjson.dumps( + numpy.datetime64("2021-01-01T00:00:00.172576789"), + option=orjson.OPT_SERIALIZE_NUMPY + | orjson.OPT_NAIVE_UTC + | orjson.OPT_UTC_Z, + ) + == b'"2021-01-01T00:00:00.172576Z"' + ) + + def test_numpy_datetime_omit_microseconds_year(self): + assert ( + orjson.dumps( + numpy.datetime64("2021"), + option=orjson.OPT_SERIALIZE_NUMPY | orjson.OPT_OMIT_MICROSECONDS, + ) + == b'"2021-01-01T00:00:00"' + ) + + def test_numpy_datetime_omit_microseconds_month(self): + assert ( + orjson.dumps( + numpy.datetime64("2021-01"), + option=orjson.OPT_SERIALIZE_NUMPY | orjson.OPT_OMIT_MICROSECONDS, + ) + == b'"2021-01-01T00:00:00"' + ) + + def test_numpy_datetime_omit_microseconds_day(self): + assert ( + orjson.dumps( + numpy.datetime64("2021-01-01"), + option=orjson.OPT_SERIALIZE_NUMPY | orjson.OPT_OMIT_MICROSECONDS, + ) + == b'"2021-01-01T00:00:00"' + ) + + def test_numpy_datetime_omit_microseconds_hour(self): + assert ( + orjson.dumps( + numpy.datetime64("2021-01-01T00"), + option=orjson.OPT_SERIALIZE_NUMPY | orjson.OPT_OMIT_MICROSECONDS, + ) + == b'"2021-01-01T00:00:00"' + ) + + def test_numpy_datetime_omit_microseconds_minute(self): + assert ( + orjson.dumps( + numpy.datetime64("2021-01-01T00:00"), + option=orjson.OPT_SERIALIZE_NUMPY | orjson.OPT_OMIT_MICROSECONDS, + ) + == b'"2021-01-01T00:00:00"' + ) + + def test_numpy_datetime_omit_microseconds_second(self): + assert ( + orjson.dumps( + numpy.datetime64("2021-01-01T00:00:00"), + option=orjson.OPT_SERIALIZE_NUMPY | orjson.OPT_OMIT_MICROSECONDS, + ) + == b'"2021-01-01T00:00:00"' + ) + + def test_numpy_datetime_omit_microseconds_milli(self): + assert ( + orjson.dumps( + numpy.datetime64("2021-01-01T00:00:00.172"), + option=orjson.OPT_SERIALIZE_NUMPY | orjson.OPT_OMIT_MICROSECONDS, + ) + == b'"2021-01-01T00:00:00"' + ) + + def test_numpy_datetime_omit_microseconds_micro(self): + assert ( + orjson.dumps( + numpy.datetime64("2021-01-01T00:00:00.172576"), + option=orjson.OPT_SERIALIZE_NUMPY | orjson.OPT_OMIT_MICROSECONDS, + ) + == b'"2021-01-01T00:00:00"' ) - def test_numpy_datetime_omit_microseconds(self): + def test_numpy_datetime_omit_microseconds_nano(self): assert ( orjson.dumps( - { - "year": numpy.datetime64("2021"), - "month": numpy.datetime64("2021-01"), - "day": numpy.datetime64("2021-01-01"), - "hour": numpy.datetime64("2021-01-01T00"), - "minute": numpy.datetime64("2021-01-01T00:00"), - "second": numpy.datetime64("2021-01-01T00:00:00"), - "milli": numpy.datetime64("2021-01-01T00:00:00.172"), - "micro": numpy.datetime64("2021-01-01T00:00:00.172576"), - "nano": numpy.datetime64("2021-01-01T00:00:00.172576789"), - }, + numpy.datetime64("2021-01-01T00:00:00.172576789"), option=orjson.OPT_SERIALIZE_NUMPY | orjson.OPT_OMIT_MICROSECONDS, ) - == b'{"year":"2021-01-01T00:00:00","month":"2021-01-01T00:00:00","day":"2021-01-01T00:00:00","hour":"2021-01-01T00:00:00","minute":"2021-01-01T00:00:00","second":"2021-01-01T00:00:00","milli":"2021-01-01T00:00:00","micro":"2021-01-01T00:00:00","nano":"2021-01-01T00:00:00"}' + == b'"2021-01-01T00:00:00"' ) def test_numpy_datetime_nat(self):