Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backport PyPy 3.9 support for pyo3 0.15 #2262

Merged
merged 27 commits into from
Apr 14, 2022
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
4079c8b
Limit concurrency on GitHub Actions
messense Mar 15, 2022
ddf37fd
CI: keep going on failure only if `CI-no-fail-fast` label is present
messense Mar 15, 2022
f113227
Test 32-bit Windows only with the latest Python version
messense Mar 31, 2022
6d4b363
Add pypy-3.9 to CI
messense Mar 31, 2022
48fe21c
pypy: include minor version in library on Python 3.9
davidhewitt Feb 24, 2022
d1c5af9
ffi: add missing definition PyCMethod_New
davidhewitt Feb 24, 2022
fb89600
pypy: don't allow abi to be adjusted by abi3 flag
davidhewitt Feb 25, 2022
c18e60f
pypy: support 7.3.8
davidhewitt Mar 31, 2022
7b4b77b
Update black to 22.3.0
messense Mar 31, 2022
cc594a2
black format
messense Mar 31, 2022
b16335f
Fix some clippy warnings
messense Mar 31, 2022
2b696e3
Add serde with derive feature to dev dependencies
messense Mar 31, 2022
cb8ec38
cargo update -p clap --precise 2.33.4
messense Mar 31, 2022
f6cf4c3
Remove link_name PyPyUnicode_FromFormatV
messense Mar 31, 2022
74517b9
Bless ui tests
messense Mar 31, 2022
991c842
Use `compare` instead of `lt`
messense Mar 31, 2022
b402c5c
chore: cleanup old todo
davidhewitt Feb 5, 2022
b96a3f7
pypy: support released 3.9
davidhewitt Feb 23, 2022
54de029
rust: clippy fixes 1.59
davidhewitt Feb 24, 2022
1f88c84
Bless ui test and pin clap
mejrs Apr 12, 2022
3725b31
Make clippy happy
mejrs Apr 12, 2022
48aba84
Fix UI test
mejrs Apr 12, 2022
f774dbf
Fix abi3 ui test
mejrs Apr 12, 2022
72867d9
Don't test 32 bit on windows
mejrs Apr 12, 2022
bf3d7a0
Name a more iconic trio: pypy, datetime, segfaults
mejrs Apr 12, 2022
97230d1
Move 7.3.8 warning over to `make_module`
mejrs Apr 13, 2022
5016202
support pypy-3.7-v7.3.8+
davidhewitt Apr 13, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/bench.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ on:
- main
pull_request:

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: true

name: Benchmark

jobs:
Expand Down
33 changes: 22 additions & 11 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ on:
- main
pull_request:

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: true

env:
CARGO_TERM_COLOR: always

Expand All @@ -15,7 +19,7 @@ jobs:
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
- run: pip install black==20.8b1
- run: pip install black==22.3.0
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
Expand All @@ -41,7 +45,8 @@ jobs:
needs: [fmt]
runs-on: ubuntu-latest
strategy:
fail-fast: false # If one platform fails, allow the rest to keep testing.
# If one platform fails, allow the rest to keep testing if `CI-no-fail-fast` label is present
fail-fast: ${{ !contains(github.event.pull_request.labels.*.name, 'CI-no-fail-fast') }}
matrix:
target: [powerpc64le-unknown-linux-gnu, s390x-unknown-linux-gnu, wasm32-wasi]
name: check-${{ matrix.target }}
Expand Down Expand Up @@ -73,10 +78,11 @@ jobs:
name: python${{ matrix.python-version }}-${{ matrix.platform.python-architecture }} ${{ matrix.platform.os }} ${{ matrix.msrv }}
runs-on: ${{ matrix.platform.os }}
strategy:
fail-fast: false # If one platform fails, allow the rest to keep testing.
# If one platform fails, allow the rest to keep testing if `CI-no-fail-fast` label is present
fail-fast: ${{ !contains(github.event.pull_request.labels.*.name, 'CI-no-fail-fast') }}
matrix:
rust: [stable]
python-version: [3.6, 3.7, 3.8, 3.9, "3.10", pypy-3.6, pypy-3.7, pypy-3.8]
python-version: [3.6, 3.7, 3.8, 3.9, "3.10", pypy-3.6, pypy-3.7-v7.3.7, pypy-3.8, pypy-3.9]
platform:
[
{
Expand All @@ -94,11 +100,6 @@ jobs:
python-architecture: "x64",
rust-target: "x86_64-pc-windows-msvc",
},
{
os: "windows-latest",
python-architecture: "x86",
rust-target: "i686-pc-windows-msvc",
},
]
exclude:
# PyPy 3.6 is EOL and not working on macos-latest (now macos-11)
Expand All @@ -107,8 +108,8 @@ jobs:
# There is no 64-bit pypy on windows for pypy-3.6
- python-version: pypy-3.6
platform: { os: "windows-latest", python-architecture: "x64" }
# PyPy 3.7 on Windows doesn't release 32-bit builds any more
- python-version: pypy-3.7
# PyPy doesn't release 32-bit Windows builds any more
- python-version: pypy-3.7-v7.3.7
platform: { os: "windows-latest", python-architecture: "x86" }
- python-version: pypy-3.8
platform: { os: "windows-latest", python-architecture: "x86" }
Expand All @@ -132,6 +133,15 @@ jobs:
rust-target: "x86_64-unknown-linux-gnu",
}
msrv: "MSRV"
# Test 32-bit Windows only with the latest Python version
- rust: stable
python-version: "3.10"
platform:
{
os: "windows-latest",
python-architecture: "x86",
rust-target: "i686-pc-windows-msvc",
}

steps:
- uses: actions/checkout@v2
Expand Down Expand Up @@ -184,6 +194,7 @@ jobs:
run: |
cargo update -p indexmap --precise 1.6.2
cargo update -p hashbrown:0.11.2 --precise 0.9.1
cargo update -p clap --precise 2.33.4

- name: Build docs
run: cargo doc --no-deps --no-default-features --features "${{ steps.settings.outputs.all_additive_features }}"
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/guide.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ on:
release:
types: [published]

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: true

env:
CARGO_TERM_COLOR: always

Expand Down
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,16 @@ serde = { version = "1.0", optional = true }
assert_approx_eq = "1.1.0"
# O.3.5 uses the matches! macro, which isn't compatible with Rust 1.41
criterion = "=0.3.4"
# 2.34 uses the matches! macro, which isn't compatible with Rust 1.41
clap = "=2.33"
# half and bitflags use if/match in const fn, which isn't compatible with Rust 1.41
half = "=1.7.1"
bitflags = "=1.2.1"
trybuild = "1.0.49"
rustversion = "1.0"
# 1.0.0 requires Rust 1.50
proptest = { version = "0.10.1", default-features = false, features = ["std"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.61"

# features needed to run the PyO3 test suite
Expand Down
1 change: 1 addition & 0 deletions examples/pyo3-benchmarks/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#![allow(clippy::needless_option_as_deref)]
use pyo3::prelude::*;
use pyo3::types::{PyDict, PyTuple};

Expand Down
12 changes: 8 additions & 4 deletions examples/pyo3-pytests/src/buf_and_str.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,26 @@ impl BytesExtractor {
BytesExtractor {}
}

pub fn from_bytes(&mut self, bytes: &PyBytes) -> PyResult<usize> {
#[staticmethod]
pub fn from_bytes(bytes: &PyBytes) -> PyResult<usize> {
let byte_vec: Vec<u8> = bytes.extract()?;
Ok(byte_vec.len())
}

pub fn from_str(&mut self, string: &PyString) -> PyResult<usize> {
#[staticmethod]
pub fn from_str(string: &PyString) -> PyResult<usize> {
let rust_string: String = string.extract()?;
Ok(rust_string.len())
}

pub fn from_str_lossy(&mut self, string: &PyString) -> PyResult<usize> {
#[staticmethod]
pub fn from_str_lossy(string: &PyString) -> PyResult<usize> {
let rust_string_lossy: String = string.to_string_lossy().to_string();
Ok(rust_string_lossy.len())
}

pub fn from_buffer(&mut self, buf: &PyAny) -> PyResult<usize> {
#[staticmethod]
pub fn from_buffer(buf: &PyAny) -> PyResult<usize> {
let buf = PyBuffer::<u8>::get(buf)?;
Ok(buf.item_count())
}
Expand Down
1 change: 1 addition & 0 deletions examples/pyo3-pytests/src/datetime.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#![allow(clippy::needless_option_as_deref)]
use pyo3::prelude::*;
use pyo3::types::{
PyDate, PyDateAccess, PyDateTime, PyDelta, PyDeltaAccess, PyTime, PyTimeAccess, PyTuple,
Expand Down
4 changes: 2 additions & 2 deletions examples/pyo3-pytests/tests/test_othermod.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@

from pyo3_pytests import othermod

INTEGER32_ST = st.integers(min_value=(-(2 ** 31)), max_value=(2 ** 31 - 1))
INTEGER32_ST = st.integers(min_value=(-(2**31)), max_value=(2**31 - 1))
USIZE_ST = st.integers(min_value=othermod.USIZE_MIN, max_value=othermod.USIZE_MAX)


@given(x=INTEGER32_ST)
def test_double(x):
expected = x * 2
assume(-(2 ** 31) <= expected <= (2 ** 31 - 1))
assume(-(2**31) <= expected <= (2**31 - 1))
assert othermod.double(x) == expected


Expand Down
28 changes: 24 additions & 4 deletions pyo3-build-config/src/impl_.rs
Original file line number Diff line number Diff line change
Expand Up @@ -911,7 +911,7 @@ fn search_lib_dir(path: impl AsRef<Path>, cross: &CrossCompileConfig) -> Vec<Pat
} else {
"python3.".into()
};
for f in fs::read_dir(path).expect("Path does not exist").into_iter() {
for f in fs::read_dir(path).expect("Path does not exist") {
sysconfig_paths.extend(match &f {
// Python 3.6 sysconfigdata without platform specifics
Ok(f) if f.file_name() == "_sysconfigdata.py" => vec![f.path()],
Expand Down Expand Up @@ -1062,7 +1062,16 @@ fn default_lib_name_unix(
Some(ld_version) => format!("python{}", ld_version),
None => format!("python{}.{}", version.major, version.minor),
},
PythonImplementation::PyPy => format!("pypy{}-c", version.major),
PythonImplementation::PyPy => {
if version >= (PythonVersion { major: 3, minor: 9 }) {
match ld_version {
Some(ld_version) => format!("pypy{}-c", ld_version),
None => format!("pypy{}.{}-c", version.major, version.minor),
}
} else {
format!("pypy{}-c", version.major)
}
}
}
}

Expand Down Expand Up @@ -1168,6 +1177,11 @@ fn fixup_config_for_abi3(
config: &mut InterpreterConfig,
abi3_version: Option<PythonVersion>,
) -> Result<()> {
// PyPy doesn't support abi3; don't adjust the version
if config.implementation.is_pypy() {
return Ok(());
}

if let Some(version) = abi3_version {
ensure!(
version <= config.version,
Expand Down Expand Up @@ -1472,11 +1486,17 @@ mod tests {
"python3.7md",
);

// PyPy ignores ldversion
// PyPy 3.7 ignores ldversion
assert_eq!(
super::default_lib_name_unix(PythonVersion { major: 3, minor: 9 }, PyPy, Some("3.7md")),
super::default_lib_name_unix(PythonVersion { major: 3, minor: 7 }, PyPy, Some("3.7md")),
"pypy3-c",
);

// PyPy 3.9 includes ldversion
assert_eq!(
super::default_lib_name_unix(PythonVersion { major: 3, minor: 9 }, PyPy, Some("3.9d")),
"pypy3.9d-c",
);
}

#[test]
Expand Down
1 change: 0 additions & 1 deletion pyo3-macros-backend/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

#![cfg_attr(docsrs, feature(doc_cfg))]
#![recursion_limit = "1024"]

// Listed first so that macros in this module are available in the rest of the crate.
#[macro_use]
mod utils;
Expand Down
11 changes: 4 additions & 7 deletions pyo3-macros-backend/src/method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,7 @@ impl<'a> FnArg<'a> {
/// Transforms a rust fn arg parsed with syn into a method::FnArg
pub fn parse(arg: &'a mut syn::FnArg) -> Result<Self> {
match arg {
syn::FnArg::Receiver(recv) => {
bail_spanned!(recv.span() => "unexpected receiver")
} // checked in parse_fn_type
syn::FnArg::Receiver(recv) => bail_spanned!(recv.span() => "unexpected receiver"), // checked in parse_fn_type
syn::FnArg::Typed(cap) => {
if let syn::Type::ImplTrait(_) = &*cap.ty {
bail_spanned!(cap.ty.span() => IMPL_TRAIT_ERR);
Expand Down Expand Up @@ -101,9 +99,7 @@ impl FnType {
cls.expect("no class given for Fn with a \"self\" receiver"),
error_mode,
),
FnType::FnNew | FnType::FnStatic | FnType::ClassAttribute => {
quote!()
}
FnType::FnNew | FnType::FnStatic | FnType::ClassAttribute => quote!(),
FnType::FnClass => {
quote! {
let _slf = ::pyo3::types::PyType::from_type_ptr(_py, _slf as *mut ::pyo3::ffi::PyTypeObject);
Expand Down Expand Up @@ -352,7 +348,8 @@ impl<'a> FnSpec<'a> {
parse_method_receiver(first_arg)
};

#[allow(clippy::manual_strip)] // for strip_prefix replacement supporting rust < 1.45
#[allow(clippy::manual_strip)]
// for strip_prefix replacement supporting rust < 1.45
// strip get_ or set_
let strip_fn_name = |prefix: &'static str| {
let ident = name.unraw().to_string();
Expand Down
4 changes: 2 additions & 2 deletions pyo3-macros-backend/src/params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ fn impl_arg_param(
}
};

return if let syn::Type::Reference(tref) = unwrap_ty_group(arg.optional.unwrap_or(ty)) {
if let syn::Type::Reference(tref) = unwrap_ty_group(arg.optional.unwrap_or(ty)) {
let mut tref = remove_lifetime(tref);
if let Some(cls) = self_ {
replace_self(&mut tref.elem, cls);
Expand Down Expand Up @@ -298,5 +298,5 @@ fn impl_arg_param(
Ok(quote_arg_span! {
let #arg_name = #arg_value_or_default;
})
};
}
}
2 changes: 1 addition & 1 deletion pyo3-macros-backend/src/pyimpl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ pub fn build_py_methods(

pub fn impl_methods(
ty: &syn::Type,
impls: &mut Vec<syn::ImplItem>,
impls: &mut [syn::ImplItem],
methods_type: PyClassMethodsType,
) -> syn::Result<TokenStream> {
let mut trait_impls = Vec::new();
Expand Down
4 changes: 1 addition & 3 deletions pyo3-macros-backend/src/pymethod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,7 @@ pub fn gen_py_method(
cls,
PropertyType::Function { self_type, spec },
)?),
(_, FnType::FnModule) => {
unreachable!("methods cannot be FnModule")
}
(_, FnType::FnModule) => unreachable!("methods cannot be FnModule"),
})
}

Expand Down
2 changes: 1 addition & 1 deletion pyo3-macros-backend/src/pyproto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ pub fn build_py_proto(ast: &mut syn::ItemImpl) -> syn::Result<TokenStream> {

fn impl_proto_impl(
ty: &syn::Type,
impls: &mut Vec<syn::ImplItem>,
impls: &mut [syn::ImplItem],
proto: &defs::Proto,
) -> syn::Result<TokenStream> {
let mut trait_impls = TokenStream::new();
Expand Down
6 changes: 1 addition & 5 deletions pyo3-macros-backend/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,7 @@ pub fn get_doc(
syn::token::Bracket(Span::call_site()).surround(&mut tokens, |tokens| {
if let Some((python_name, text_signature)) = text_signature {
// create special doc string lines to set `__text_signature__`
let signature_lines = format!(
"{}{}\n--\n\n",
python_name.to_string(),
text_signature.lit.value()
);
let signature_lines = format!("{}{}\n--\n\n", python_name, text_signature.lit.value());
signature_lines.to_tokens(tokens);
comma.to_tokens(tokens);
}
Expand Down
4 changes: 4 additions & 0 deletions src/buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,10 @@ fn is_matching_endian(c: u8) -> bool {
}

/// Trait implemented for possible element types of `PyBuffer`.
///
/// # Safety
///
/// This trait must only be implemented for types which represent valid elements of Python buffers.
pub unsafe trait Element: Copy {
/// Gets whether the element specified in the format string is potentially compatible.
/// Alignment and size are checked separately from this function.
Expand Down
17 changes: 17 additions & 0 deletions src/callback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,23 @@ where
let unwind_safe_py = AssertUnwindSafe(pool.python());
let panic_result = panic::catch_unwind(move || -> PyResult<_> {
let py = *unwind_safe_py;
#[cfg(all(PyPy, not(Py_3_8)))]
{
const PYPY_GOOD_VERSION: [u8; 3] = [7, 3, 8];
let version = py
.import("sys")?
.getattr("implementation")?
.getattr("version")?;
if version.compare(crate::types::PyTuple::new(py, &PYPY_GOOD_VERSION))?
== std::cmp::Ordering::Less
{
let warn = py.import("warnings")?.getattr("warn")?;
warn.call1((
"PyPy 3.7 versions older than 7.3.8 are known to have binary \
compatibility issues which may cause segfaults. Please upgrade.",
))?;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is the one place where we're changing the behavior of the code on pypy3.7. I don't have an obvious reason this would cause a crash, but it does jump out to me that we're doing this import+operations on every single function invocation.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That sound expensive

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...huh?

It looks like this piece was committed to main with #2217 but then picked into this branch with c18e60f but put in a different location...

Copy link
Member

@mejrs mejrs Apr 13, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have an obvious reason this would cause a crash,

I think it's most likely that this set an exception/warning when it shouldn't have. For example, if you raise a warning during __traverse__, you can segfault.

body(py)
});

Expand Down
Loading