From 7f623eecef2271c01b9aa4fac2503299ca5359ca Mon Sep 17 00:00:00 2001 From: chiri Date: Tue, 5 May 2026 17:37:34 +0300 Subject: [PATCH 01/44] add `PyLong*` API (3.14+) --- pyo3-ffi/src/cpython/longobject.rs | 47 ++++++++ src/conversions/std/num.rs | 174 ++++++++++++++++++++++------- 2 files changed, 179 insertions(+), 42 deletions(-) diff --git a/pyo3-ffi/src/cpython/longobject.rs b/pyo3-ffi/src/cpython/longobject.rs index 92bb4c6150d..79ca54dd397 100644 --- a/pyo3-ffi/src/cpython/longobject.rs +++ b/pyo3-ffi/src/cpython/longobject.rs @@ -6,6 +6,8 @@ use libc::size_t; #[cfg(Py_3_13)] use std::ffi::c_void; use std::ffi::{c_int, c_uchar}; +#[cfg(Py_3_14)] +use crate::Py_uintptr_t; #[cfg(Py_3_13)] extern_libpython! { @@ -25,6 +27,29 @@ pub const Py_ASNATIVEBYTES_UNSIGNED_BUFFER: c_int = 4; #[cfg(Py_3_13)] pub const Py_ASNATIVEBYTES_REJECT_NEGATIVE: c_int = 8; +#[cfg(Py_3_14)] +#[derive(Copy, Clone)] +#[repr(C)] +pub struct PyLongLayout { + pub bits_per_digit: u8, + pub digit_size: u8, + pub digits_order: i8, + pub digit_endianness: i8, +} + +#[cfg(Py_3_14)] +#[repr(C)] +pub struct PyLongExport { + pub value: i64, + pub negative: u8, + pub ndigits: Py_ssize_t, + pub digits: *const c_void, + pub _reserved: Py_uintptr_t, +} + +#[cfg(Py_3_14)] +opaque_struct!(pub PyLongWriter); + extern_libpython! { // skipped _PyLong_Sign @@ -71,4 +96,26 @@ extern_libpython! { ) -> c_int; // skipped _PyLong_GCD + + #[cfg(Py_3_14)] + pub fn PyLong_GetNativeLayout() -> *const PyLongLayout; + + #[cfg(Py_3_14)] + pub fn PyLong_Export(obj: *mut PyObject, export_long: *mut PyLongExport) -> c_int; + + #[cfg(Py_3_14)] + pub fn PyLong_FreeExport(export_long: *mut PyLongExport); + + #[cfg(Py_3_14)] + pub fn PyLongWriter_Create( + negative: c_int, + ndigits: Py_ssize_t, + digits: *mut *mut c_void, + ) -> *mut PyLongWriter; + + #[cfg(Py_3_14)] + pub fn PyLongWriter_Finish(writer: *mut PyLongWriter) -> *mut PyObject; + + #[cfg(Py_3_14)] + pub fn PyLongWriter_Discard(writer: *mut PyLongWriter); } diff --git a/src/conversions/std/num.rs b/src/conversions/std/num.rs index fb122c279ef..83e2c9e7112 100644 --- a/src/conversions/std/num.rs +++ b/src/conversions/std/num.rs @@ -369,7 +369,38 @@ mod fast_128bit_int_conversion { const OUTPUT_TYPE: PyStaticExpr = PyInt::TYPE_HINT; fn into_pyobject(self, py: Python<'py>) -> Result { - #[cfg(Py_3_13)] + #[cfg(Py_3_14)] + { + let value = self as u128; + let negative = $is_signed && value < 0; + let abs = if negative { value.wrapping_neg() } else { value }; + let bits = 128 - abs.leading_zeros() as usize; + let n_digits = if bits == 0 { 1 } else { (bits + 29) / 30 }; + let mut digits_ptr = std::ptr::null_mut(); + let long_writer = unsafe { + ffi::PyLongWriter_Create( + negative.into(), + n_digits as ffi::Py_ssize_t, + &mut digits_ptr, + ) + }; + if long_writer.is_null() { + PyErr::fetch(py).restore(py); + panic!("failed to create Python inr"); + } + let digits = digits_ptr.cast::(); + let mut rest = abs; + for i in 0..n_digits { + unsafe { digits.add(i).write(rest as u32 & ((1 << 30) - 1)) }; + rest >>= 30; + } + Ok(unsafe { + ffi::PyLongWriter_Finish(long_writer) + .assume_owned(py) + .cast_into_unchecked() + }) + } + #[cfg(all(Py_3_13, not(Py_3_14)))] { let bytes = self.to_ne_bytes(); Ok(int_from_ne_bytes::<{ $is_signed }>(py, &bytes)) @@ -404,46 +435,105 @@ mod fast_128bit_int_conversion { fn extract(ob: Borrowed<'_, '_, PyAny>) -> Result<$rust_type, Self::Error> { let num = nb_index(&ob)?; - let mut buffer = [0u8; std::mem::size_of::<$rust_type>()]; - #[cfg(not(Py_3_13))] + #[cfg(Py_3_14)] { - crate::err::error_on_minusone(ob.py(), unsafe { - ffi::_PyLong_AsByteArray( - num.as_ptr() as *mut ffi::PyLongObject, - buffer.as_mut_ptr(), - buffer.len(), - 1, - $is_signed.into(), - ) - })?; - Ok(<$rust_type>::from_le_bytes(buffer)) - } - #[cfg(Py_3_13)] - { - let mut flags = ffi::Py_ASNATIVEBYTES_NATIVE_ENDIAN; + let mut long_export = MaybeUninit::::uninit(); + unsafe { + crate::err::error_on_minusone( + num.py(), + ffi::PyLong_Export(num.as_ptr().cast(), long_export.as_mut_ptr()), + )?; + } + let long_export = unsafe { long_export.assume_init() }; + if long_export.digits.is_null() { + return <$rust_type>::try_from(long_export.value).map_err(|_| { + exceptions::PyOverflowError::new_err("Python int larger than 128 bits") + }); + } + let overflow = || { + exceptions::PyOverflowError::new_err("Python int larger than 128 bits") + }; + let n_digits = long_export.ndigits as usize; + let negative = long_export.negative != 0; + let digits = long_export.digits.cast::(); + let mut abs = 0_u128; + let mut overflowed = n_digits > 5; + for i in 0..n_digits.min(5) { + let digit = u128::from(unsafe { digits.add(i).read() }); + if i == 4 && digit >> 8 != 0 { + overflowed = true; + } + abs |= digit << (i * 30); + } + let mut to_free = long_export; + unsafe { ffi::PyLong_FreeExport(&mut to_free) }; + if overflowed { + return Err(overflow()); + } if !$is_signed { - flags |= ffi::Py_ASNATIVEBYTES_UNSIGNED_BUFFER - | ffi::Py_ASNATIVEBYTES_REJECT_NEGATIVE; + if negative { + return Err(exceptions::PyOverflowError::new_err( + "can't convert negative int to unsigned", + )); + } + return <$rust_type>::try_from(abs).map_err(|_| overflow()); } - let actual_size: usize = unsafe { - ffi::PyLong_AsNativeBytes( - num.as_ptr(), - buffer.as_mut_ptr().cast(), - buffer - .len() - .try_into() - .expect("length of buffer fits in Py_ssize_t"), - flags, - ) + let signed = if negative { + if abs > 1_u128 << 127 { + return Err(overflow()); + } + (abs.wrapping_neg()) as i128 + } else { + if abs > i128::MAX as u128 { + return Err(overflow()); + } + abs as i128 + }; + <$rust_type>::try_from(signed).map_err(|_| overflow()) + } + #[cfg(not(Py_3_14))] + { + let mut buffer = [0u8; std::mem::size_of::<$rust_type>()]; + #[cfg(not(Py_3_13))] + { + crate::err::error_on_minusone(ob.py(), unsafe { + ffi::_PyLong_AsByteArray( + num.as_ptr() as *mut ffi::PyLongObject, + buffer.as_mut_ptr(), + buffer.len(), + 1, + $is_signed.into(), + ) + })?; + Ok(<$rust_type>::from_le_bytes(buffer)) } - .try_into() - .map_err(|_| PyErr::fetch(ob.py()))?; - if actual_size as usize > buffer.len() { - return Err(crate::exceptions::PyOverflowError::new_err( - "Python int larger than 128 bits", - )); + #[cfg(Py_3_13)] + { + let mut flags = ffi::Py_ASNATIVEBYTES_NATIVE_ENDIAN; + if !$is_signed { + flags |= ffi::Py_ASNATIVEBYTES_UNSIGNED_BUFFER + | ffi::Py_ASNATIVEBYTES_REJECT_NEGATIVE; + } + let actual_size: usize = unsafe { + ffi::PyLong_AsNativeBytes( + num.as_ptr(), + buffer.as_mut_ptr().cast(), + buffer + .len() + .try_into() + .expect("length of buffer fits in Py_ssize_t"), + flags, + ) + } + .try_into() + .map_err(|_| PyErr::fetch(ob.py()))?; + if actual_size as usize > buffer.len() { + return Err(crate::exceptions::PyOverflowError::new_err( + "Python int larger than 128 bits", + )); + } + Ok(<$rust_type>::from_ne_bytes(buffer)) } - Ok(<$rust_type>::from_ne_bytes(buffer)) } } } @@ -466,7 +556,7 @@ pub(crate) fn int_from_le_bytes<'py, const IS_SIGNED: bool>( } } -#[cfg(all(Py_3_13, not(Py_LIMITED_API)))] +#[cfg(all(Py_3_13, all(not(Py_3_14), not(Py_LIMITED_API))))] pub(crate) fn int_from_ne_bytes<'py, const IS_SIGNED: bool>( py: Python<'py>, bytes: &[u8], @@ -754,7 +844,7 @@ mod test_128bit_integers { Python::attach(|py| { let obj = py.eval(c"(1 << 130) * -1", None, None).unwrap(); let err = obj.extract::().unwrap_err(); - assert!(err.is_instance_of::(py)); + assert!(err.is_instance_of::(py)); }) } @@ -763,7 +853,7 @@ mod test_128bit_integers { Python::attach(|py| { let obj = py.eval(c"1 << 130", None, None).unwrap(); let err = obj.extract::().unwrap_err(); - assert!(err.is_instance_of::(py)); + assert!(err.is_instance_of::(py)); }) } @@ -807,7 +897,7 @@ mod test_128bit_integers { Python::attach(|py| { let obj = py.eval(c"(1 << 130) * -1", None, None).unwrap(); let err = obj.extract::().unwrap_err(); - assert!(err.is_instance_of::(py)); + assert!(err.is_instance_of::(py)); }) } @@ -825,7 +915,7 @@ mod test_128bit_integers { Python::attach(|py| { let obj = py.eval(c"0", None, None).unwrap(); let err = obj.extract::().unwrap_err(); - assert!(err.is_instance_of::(py)); + assert!(err.is_instance_of::(py)); }) } @@ -834,7 +924,7 @@ mod test_128bit_integers { Python::attach(|py| { let obj = py.eval(c"0", None, None).unwrap(); let err = obj.extract::().unwrap_err(); - assert!(err.is_instance_of::(py)); + assert!(err.is_instance_of::(py)); }) } } From 59a681818c66b1ddc923612c495f4e0a0785c8a2 Mon Sep 17 00:00:00 2001 From: chiri Date: Tue, 5 May 2026 17:42:38 +0300 Subject: [PATCH 02/44] add newsfragments --- newsfragments/6016.added.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 newsfragments/6016.added.md diff --git a/newsfragments/6016.added.md b/newsfragments/6016.added.md new file mode 100644 index 00000000000..6f63b236e47 --- /dev/null +++ b/newsfragments/6016.added.md @@ -0,0 +1 @@ +Added FFI wrappers for [PEP 757](https://peps.python.org/pep-0757/) `PyLong` import / export API on Python 3.14. \ No newline at end of file From dce999697850e9a9518d236a24849ad44c296162 Mon Sep 17 00:00:00 2001 From: chiri Date: Tue, 5 May 2026 17:45:43 +0300 Subject: [PATCH 03/44] cargo fmt --- pyo3-ffi/src/cpython/longobject.rs | 4 ++-- src/conversions/std/num.rs | 10 ++++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/pyo3-ffi/src/cpython/longobject.rs b/pyo3-ffi/src/cpython/longobject.rs index 79ca54dd397..c3c8178189f 100644 --- a/pyo3-ffi/src/cpython/longobject.rs +++ b/pyo3-ffi/src/cpython/longobject.rs @@ -2,12 +2,12 @@ use crate::longobject::*; use crate::object::*; #[cfg(Py_3_13)] use crate::pyport::Py_ssize_t; +#[cfg(Py_3_14)] +use crate::Py_uintptr_t; use libc::size_t; #[cfg(Py_3_13)] use std::ffi::c_void; use std::ffi::{c_int, c_uchar}; -#[cfg(Py_3_14)] -use crate::Py_uintptr_t; #[cfg(Py_3_13)] extern_libpython! { diff --git a/src/conversions/std/num.rs b/src/conversions/std/num.rs index 83e2c9e7112..a6b2bbefe9d 100644 --- a/src/conversions/std/num.rs +++ b/src/conversions/std/num.rs @@ -373,7 +373,11 @@ mod fast_128bit_int_conversion { { let value = self as u128; let negative = $is_signed && value < 0; - let abs = if negative { value.wrapping_neg() } else { value }; + let abs = if negative { + value.wrapping_neg() + } else { + value + }; let bits = 128 - abs.leading_zeros() as usize; let n_digits = if bits == 0 { 1 } else { (bits + 29) / 30 }; let mut digits_ptr = std::ptr::null_mut(); @@ -447,7 +451,9 @@ mod fast_128bit_int_conversion { let long_export = unsafe { long_export.assume_init() }; if long_export.digits.is_null() { return <$rust_type>::try_from(long_export.value).map_err(|_| { - exceptions::PyOverflowError::new_err("Python int larger than 128 bits") + exceptions::PyOverflowError::new_err( + "Python int larger than 128 bits", + ) }); } let overflow = || { From 5e4018eea816d073bc5878fb5d3bd50a2fbacdf5 Mon Sep 17 00:00:00 2001 From: chiri Date: Tue, 5 May 2026 17:52:48 +0300 Subject: [PATCH 04/44] try fix --- src/conversions/std/num.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/conversions/std/num.rs b/src/conversions/std/num.rs index a6b2bbefe9d..649a3930379 100644 --- a/src/conversions/std/num.rs +++ b/src/conversions/std/num.rs @@ -562,7 +562,7 @@ pub(crate) fn int_from_le_bytes<'py, const IS_SIGNED: bool>( } } -#[cfg(all(Py_3_13, all(not(Py_3_14), not(Py_LIMITED_API))))] +#[cfg(all(Py_3_13, not(Py_LIMITED_API)))] pub(crate) fn int_from_ne_bytes<'py, const IS_SIGNED: bool>( py: Python<'py>, bytes: &[u8], From 035b40ee2e9f78c5e0857d9a217af16e38b0f7cd Mon Sep 17 00:00:00 2001 From: chiri Date: Tue, 5 May 2026 17:53:11 +0300 Subject: [PATCH 05/44] fix typo (inr -> int) --- src/conversions/std/num.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/conversions/std/num.rs b/src/conversions/std/num.rs index 649a3930379..d559bc4427a 100644 --- a/src/conversions/std/num.rs +++ b/src/conversions/std/num.rs @@ -390,7 +390,7 @@ mod fast_128bit_int_conversion { }; if long_writer.is_null() { PyErr::fetch(py).restore(py); - panic!("failed to create Python inr"); + panic!("failed to create Python int"); } let digits = digits_ptr.cast::(); let mut rest = abs; From f2cebdcb32a76b7892a016667f30fcec259f3a8a Mon Sep 17 00:00:00 2001 From: chiri Date: Tue, 5 May 2026 17:59:07 +0300 Subject: [PATCH 06/44] try fix (x2) --- src/conversions/std/num.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/conversions/std/num.rs b/src/conversions/std/num.rs index d559bc4427a..3bd1723ca8a 100644 --- a/src/conversions/std/num.rs +++ b/src/conversions/std/num.rs @@ -372,7 +372,7 @@ mod fast_128bit_int_conversion { #[cfg(Py_3_14)] { let value = self as u128; - let negative = $is_signed && value < 0; + let negative = $is_signed && (self as i128) < 0; let abs = if negative { value.wrapping_neg() } else { From 0fb657595cae7b1b6fe3795934c0da25e95fc09f Mon Sep 17 00:00:00 2001 From: chiri Date: Tue, 5 May 2026 18:26:20 +0300 Subject: [PATCH 07/44] fix clippy --- src/conversions/std/num.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/conversions/std/num.rs b/src/conversions/std/num.rs index 3bd1723ca8a..02a4f0722f3 100644 --- a/src/conversions/std/num.rs +++ b/src/conversions/std/num.rs @@ -379,7 +379,7 @@ mod fast_128bit_int_conversion { value }; let bits = 128 - abs.leading_zeros() as usize; - let n_digits = if bits == 0 { 1 } else { (bits + 29) / 30 }; + let n_digits = if bits == 0 { 1 } else { bits.div_ceil(30) }; let mut digits_ptr = std::ptr::null_mut(); let long_writer = unsafe { ffi::PyLongWriter_Create( From 05bfe1fe6dd51f58653e977686a4617afc253561 Mon Sep 17 00:00:00 2001 From: chiri Date: Tue, 5 May 2026 19:03:49 +0300 Subject: [PATCH 08/44] try fix clippy --- src/conversions/std/num.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/conversions/std/num.rs b/src/conversions/std/num.rs index 02a4f0722f3..8f835b5f7e2 100644 --- a/src/conversions/std/num.rs +++ b/src/conversions/std/num.rs @@ -563,6 +563,7 @@ pub(crate) fn int_from_le_bytes<'py, const IS_SIGNED: bool>( } #[cfg(all(Py_3_13, not(Py_LIMITED_API)))] +#[allow(dead_code)] pub(crate) fn int_from_ne_bytes<'py, const IS_SIGNED: bool>( py: Python<'py>, bytes: &[u8], From ebbb7da857616a44a135965242901d1f998f88c1 Mon Sep 17 00:00:00 2001 From: chiri Date: Tue, 5 May 2026 19:40:42 +0300 Subject: [PATCH 09/44] add bench int 128 --- pyo3-benches/Cargo.toml | 4 ++ pyo3-benches/benches/bench_int128.rs | 68 ++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 pyo3-benches/benches/bench_int128.rs diff --git a/pyo3-benches/Cargo.toml b/pyo3-benches/Cargo.toml index 3ade8ef00e9..3d45a0e4f12 100644 --- a/pyo3-benches/Cargo.toml +++ b/pyo3-benches/Cargo.toml @@ -98,4 +98,8 @@ harness = false name = "bench_pystring_from_fmt" harness = false +[[bench]] +name = "bench_int128" +harness = false + [workspace] diff --git a/pyo3-benches/benches/bench_int128.rs b/pyo3-benches/benches/bench_int128.rs new file mode 100644 index 00000000000..d19bc1a24f4 --- /dev/null +++ b/pyo3-benches/benches/bench_int128.rs @@ -0,0 +1,68 @@ +use std::hint::black_box; + +use codspeed_criterion_compat::{criterion_group, criterion_main, Bencher, Criterion}; + +use pyo3::conversion::IntoPyObject; +use pyo3::prelude::*; +use pyo3::types::PyInt; + +fn into_u128(b: &mut Bencher<'_>, value: u128) { + Python::attach(|py| { + b.iter_with_large_drop(|| black_box(value).into_pyobject(py)); + }); +} + +fn into_i128(b: &mut Bencher<'_>, value: i128) { + Python::attach(|py| { + b.iter_with_large_drop(|| black_box(value).into_pyobject(py)); + }); +} + +fn extract_u128(b: &mut Bencher<'_>, value: u128) { + Python::attach(|py| { + let obj: Bound<'_, PyInt> = value.into_pyobject(py).unwrap(); + b.iter(|| { + let v: u128 = black_box(&obj).extract().unwrap(); + black_box(v) + }); + }); +} + +fn extract_i128(b: &mut Bencher<'_>, value: i128) { + Python::attach(|py| { + let obj: Bound<'_, PyInt> = value.into_pyobject(py).unwrap(); + b.iter(|| { + let v: i128 = black_box(&obj).extract().unwrap(); + black_box(v) + }); + }); +} + +fn criterion_benchmark(c: &mut Criterion) { + c.bench_function("into_u128_zero", |b| into_u128(b, 0)); + c.bench_function("into_u128_small", |b| into_u128(b, 42)); + c.bench_function("into_u128_u32_max", |b| into_u128(b, u32::MAX as u128)); + c.bench_function("into_u128_u64_max", |b| into_u128(b, u64::MAX as u128)); + c.bench_function("into_u128_max", |b| into_u128(b, u128::MAX)); + + c.bench_function("into_i128_zero", |b| into_i128(b, 0)); + c.bench_function("into_i128_small_pos", |b| into_i128(b, 42)); + c.bench_function("into_i128_small_neg", |b| into_i128(b, -42)); + c.bench_function("into_i128_pos_max", |b| into_i128(b, i128::MAX)); + c.bench_function("into_i128_neg_min", |b| into_i128(b, i128::MIN)); + + c.bench_function("extract_u128_zero", |b| extract_u128(b, 0)); + c.bench_function("extract_u128_small", |b| extract_u128(b, 42)); + c.bench_function("extract_u128_u32_max", |b| extract_u128(b, u32::MAX as u128)); + c.bench_function("extract_u128_u64_max", |b| extract_u128(b, u64::MAX as u128)); + c.bench_function("extract_u128_max", |b| extract_u128(b, u128::MAX)); + + c.bench_function("extract_i128_zero", |b| extract_i128(b, 0)); + c.bench_function("extract_i128_small_pos", |b| extract_i128(b, 42)); + c.bench_function("extract_i128_small_neg", |b| extract_i128(b, -42)); + c.bench_function("extract_i128_pos_max", |b| extract_i128(b, i128::MAX)); + c.bench_function("extract_i128_neg_min", |b| extract_i128(b, i128::MIN)); +} + +criterion_group!(benches, criterion_benchmark); +criterion_main!(benches); \ No newline at end of file From 003647a7334b2b84327b4490c8625ee366f7469e Mon Sep 17 00:00:00 2001 From: chiri Date: Tue, 5 May 2026 21:05:22 +0300 Subject: [PATCH 10/44] add tests --- src/conversions/std/num.rs | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/conversions/std/num.rs b/src/conversions/std/num.rs index 8f835b5f7e2..e1e63e065ae 100644 --- a/src/conversions/std/num.rs +++ b/src/conversions/std/num.rs @@ -864,6 +864,28 @@ mod test_128bit_integers { }) } + #[test] + fn test_u128_negative() { + Python::attach(|py| { + let obj = py.eval(c"-1", None, None).unwrap(); + let err = obj.extract::().unwrap_err(); + assert!(err.is_instance_of::(py)); + }) + } + + #[test] + fn test_i128_boundary_overflow() { + Python::attach(|py| { + let obj = py.eval(c"-(2**127) - 1", None, None).unwrap(); + let err = obj.extract::().unwrap_err(); + assert!(err.is_instance_of::(py)); + + let obj = py.eval(c"2**127", None, None).unwrap(); + let err = obj.extract::().unwrap_err(); + assert!(err.is_instance_of::(py)); + }) + } + #[test] fn test_nonzero_i128_max() { Python::attach(|py| { @@ -913,7 +935,7 @@ mod test_128bit_integers { Python::attach(|py| { let obj = py.eval(c"1 << 130", None, None).unwrap(); let err = obj.extract::().unwrap_err(); - assert!(err.is_instance_of::(py)); + assert!(err.is_instance_of::(py)); }) } From 66b00a13ce5ac87dac6a368ce2e478517ababb15 Mon Sep 17 00:00:00 2001 From: chiri Date: Tue, 5 May 2026 23:43:10 +0300 Subject: [PATCH 11/44] digits_ptr -> ptr --- src/conversions/std/num.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/conversions/std/num.rs b/src/conversions/std/num.rs index e1e63e065ae..a0315cff64e 100644 --- a/src/conversions/std/num.rs +++ b/src/conversions/std/num.rs @@ -380,19 +380,18 @@ mod fast_128bit_int_conversion { }; let bits = 128 - abs.leading_zeros() as usize; let n_digits = if bits == 0 { 1 } else { bits.div_ceil(30) }; - let mut digits_ptr = std::ptr::null_mut(); + let mut ptr = std::ptr::null_mut(); let long_writer = unsafe { ffi::PyLongWriter_Create( negative.into(), n_digits as ffi::Py_ssize_t, - &mut digits_ptr, + &mut ptr, ) }; if long_writer.is_null() { PyErr::fetch(py).restore(py); - panic!("failed to create Python int"); } - let digits = digits_ptr.cast::(); + let digits = ptr.cast::(); let mut rest = abs; for i in 0..n_digits { unsafe { digits.add(i).write(rest as u32 & ((1 << 30) - 1)) }; From b8b9597ad0f56b9a68b0a41dfedac06f1759d61c Mon Sep 17 00:00:00 2001 From: chiri Date: Wed, 6 May 2026 00:02:12 +0300 Subject: [PATCH 12/44] review --- src/conversions/std/num.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/conversions/std/num.rs b/src/conversions/std/num.rs index a0315cff64e..1a6bdc900ec 100644 --- a/src/conversions/std/num.rs +++ b/src/conversions/std/num.rs @@ -363,7 +363,7 @@ mod fast_128bit_int_conversion { impl<'py> IntoPyObject<'py> for $rust_type { type Target = PyInt; type Output = Bound<'py, Self::Target>; - type Error = Infallible; + type Error = PyErr; #[cfg(feature = "experimental-inspect")] const OUTPUT_TYPE: PyStaticExpr = PyInt::TYPE_HINT; @@ -389,7 +389,7 @@ mod fast_128bit_int_conversion { ) }; if long_writer.is_null() { - PyErr::fetch(py).restore(py); + return Err(PyErr::fetch(py)); } let digits = ptr.cast::(); let mut rest = abs; @@ -399,7 +399,7 @@ mod fast_128bit_int_conversion { } Ok(unsafe { ffi::PyLongWriter_Finish(long_writer) - .assume_owned(py) + .assume_owned_or_err(py) .cast_into_unchecked() }) } @@ -419,7 +419,7 @@ mod fast_128bit_int_conversion { impl<'py> IntoPyObject<'py> for &$rust_type { type Target = PyInt; type Output = Bound<'py, Self::Target>; - type Error = Infallible; + type Error = PyErr; #[cfg(feature = "experimental-inspect")] const OUTPUT_TYPE: PyStaticExpr = <$rust_type>::OUTPUT_TYPE; @@ -678,7 +678,7 @@ fn err_if_invalid_value( macro_rules! nonzero_int_impl { ($nonzero_type:ty, $primitive_type:ty) => { - impl<'py> IntoPyObject<'py> for $nonzero_type { + type Error = <$primitive_type as IntoPyObject<'py>>::Error; type Target = PyInt; type Output = Bound<'py, Self::Target>; type Error = Infallible; @@ -695,7 +695,7 @@ macro_rules! nonzero_int_impl { impl<'py> IntoPyObject<'py> for &$nonzero_type { type Target = PyInt; type Output = Bound<'py, Self::Target>; - type Error = Infallible; + type Error = <$nonzero_type as IntoPyObject<'py>>::Error; #[cfg(feature = "experimental-inspect")] const OUTPUT_TYPE: PyStaticExpr = <$nonzero_type>::OUTPUT_TYPE; From a089d89ac2acce544e274fba8660988fa6d1a845 Mon Sep 17 00:00:00 2001 From: chiri Date: Wed, 6 May 2026 00:03:51 +0300 Subject: [PATCH 13/44] cargo fmt --- src/conversions/std/num.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/conversions/std/num.rs b/src/conversions/std/num.rs index 1a6bdc900ec..d67c0074236 100644 --- a/src/conversions/std/num.rs +++ b/src/conversions/std/num.rs @@ -679,6 +679,7 @@ fn err_if_invalid_value( macro_rules! nonzero_int_impl { ($nonzero_type:ty, $primitive_type:ty) => { type Error = <$primitive_type as IntoPyObject<'py>>::Error; + { type Target = PyInt; type Output = Bound<'py, Self::Target>; type Error = Infallible; From e55d67e9b40dc779ab646f2ce6927d77402c139f Mon Sep 17 00:00:00 2001 From: chiri Date: Wed, 6 May 2026 00:14:49 +0300 Subject: [PATCH 14/44] fix --- src/conversions/std/num.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/conversions/std/num.rs b/src/conversions/std/num.rs index d67c0074236..938a2bae26b 100644 --- a/src/conversions/std/num.rs +++ b/src/conversions/std/num.rs @@ -397,11 +397,11 @@ mod fast_128bit_int_conversion { unsafe { digits.add(i).write(rest as u32 & ((1 << 30) - 1)) }; rest >>= 30; } - Ok(unsafe { + unsafe { ffi::PyLongWriter_Finish(long_writer) .assume_owned_or_err(py) .cast_into_unchecked() - }) + } } #[cfg(all(Py_3_13, not(Py_3_14)))] { @@ -678,11 +678,10 @@ fn err_if_invalid_value( macro_rules! nonzero_int_impl { ($nonzero_type:ty, $primitive_type:ty) => { - type Error = <$primitive_type as IntoPyObject<'py>>::Error; - { + impl<'py> IntoPyObject<'py> for $nonzero_type { type Target = PyInt; type Output = Bound<'py, Self::Target>; - type Error = Infallible; + type Error = <$primitive_type as IntoPyObject<'py>>::Error; #[cfg(feature = "experimental-inspect")] const OUTPUT_TYPE: PyStaticExpr = PyInt::TYPE_HINT; From a937d91ea31ec308c91d5736b461a1baa4f2bbee Mon Sep 17 00:00:00 2001 From: chiri Date: Wed, 6 May 2026 23:23:44 +0300 Subject: [PATCH 15/44] sync 3.14 (https://github.com/python/cpython/commit/af65a8be0c4f28a4b01e6f3c5100469ad8302929) --- pyo3-ffi/src/cpython/longintrepr.rs | 55 ++++++++++++++ pyo3-ffi/src/cpython/longobject.rs | 108 +++++----------------------- pyo3-ffi/src/cpython/mod.rs | 4 ++ pyo3-ffi/src/lib.rs | 1 - pyo3-ffi/src/longobject.rs | 75 ++++++++++++------- 5 files changed, 125 insertions(+), 118 deletions(-) create mode 100644 pyo3-ffi/src/cpython/longintrepr.rs diff --git a/pyo3-ffi/src/cpython/longintrepr.rs b/pyo3-ffi/src/cpython/longintrepr.rs new file mode 100644 index 00000000000..be957c6d297 --- /dev/null +++ b/pyo3-ffi/src/cpython/longintrepr.rs @@ -0,0 +1,55 @@ +use crate::{PyObject, Py_ssize_t}; +use std::ffi::{c_int, c_void}; + +use crate::Py_uintptr_t; + +// skipped PyLong_BASE +// skipped PyLong_MASK +// skipped _PyLong_New +// skipped _PyLong_Copy +// skipped _PyLong_FromDigits +// skipped _PyLong_SIGN_MASK +// skipped _PyLong_NON_SIZE_BITS +// skipped PyUnstable_Long_IsCompact +// skipped PyUnstable_Long_CompactValue + +#[derive(Copy, Clone)] +#[repr(C)] +pub struct PyLongLayout { + pub bits_per_digit: u8, + pub digit_size: u8, + pub digits_order: i8, + pub digit_endianness: i8, +} + +extern_libpython! { + pub fn PyLong_GetNativeLayout() -> *const PyLongLayout; +} + +#[repr(C)] +pub struct PyLongExport { + pub value: i64, + pub negative: u8, + pub ndigits: Py_ssize_t, + pub digits: *const c_void, + pub _reserved: Py_uintptr_t, +} + +extern_libpython! { + pub fn PyLong_Export(obj: *mut PyObject, export_long: *mut PyLongExport) -> c_int; + pub fn PyLong_FreeExport(export_long: *mut PyLongExport); +} + +opaque_struct!(pub PyLongWriter); + +extern_libpython! { + pub fn PyLongWriter_Create( + negative: c_int, + ndigits: Py_ssize_t, + digits: *mut *mut c_void, + ) -> *mut PyLongWriter; + + pub fn PyLongWriter_Finish(writer: *mut PyLongWriter) -> *mut PyObject; + + pub fn PyLongWriter_Discard(writer: *mut PyLongWriter); +} diff --git a/pyo3-ffi/src/cpython/longobject.rs b/pyo3-ffi/src/cpython/longobject.rs index c3c8178189f..1e05e506e3c 100644 --- a/pyo3-ffi/src/cpython/longobject.rs +++ b/pyo3-ffi/src/cpython/longobject.rs @@ -1,83 +1,29 @@ -use crate::longobject::*; -use crate::object::*; -#[cfg(Py_3_13)] -use crate::pyport::Py_ssize_t; -#[cfg(Py_3_14)] -use crate::Py_uintptr_t; +use crate::{longobject::*, object::*}; use libc::size_t; -#[cfg(Py_3_13)] -use std::ffi::c_void; use std::ffi::{c_int, c_uchar}; -#[cfg(Py_3_13)] -extern_libpython! { - 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; - -#[cfg(Py_3_14)] -#[derive(Copy, Clone)] -#[repr(C)] -pub struct PyLongLayout { - pub bits_per_digit: u8, - pub digit_size: u8, - pub digits_order: i8, - pub digit_endianness: i8, -} - -#[cfg(Py_3_14)] -#[repr(C)] -pub struct PyLongExport { - pub value: i64, - pub negative: u8, - pub ndigits: Py_ssize_t, - pub digits: *const c_void, - pub _reserved: Py_uintptr_t, -} - -#[cfg(Py_3_14)] -opaque_struct!(pub PyLongWriter); +// skipped _PyLong_CAST extern_libpython! { - // 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; + pub fn PyUnstable_Long_IsCompact(u: *mut PyObject, base: c_int) -> *mut PyObject; // skipped PyUnstable_Long_IsCompact // skipped PyUnstable_Long_CompactValue + // skipped PyLong_IsPositive + // skipped PyLong_IsNegative + // skipped PyLong_IsZero + // skipped PyLong_GetSign + + // skipped _PyLong_Sign + + #[cfg(not(Py_LIMITED_API))] + #[cfg_attr(PyPy, link_name = "_PyPyLong_NumBits")] + #[cfg(not(Py_3_13))] + #[doc(hidden)] + pub fn _PyLong_NumBits(obj: *mut PyObject) -> size_t; + #[cfg_attr(PyPy, link_name = "_PyPyLong_FromByteArray")] pub fn _PyLong_FromByteArray( bytes: *const c_uchar, @@ -96,26 +42,4 @@ extern_libpython! { ) -> c_int; // skipped _PyLong_GCD - - #[cfg(Py_3_14)] - pub fn PyLong_GetNativeLayout() -> *const PyLongLayout; - - #[cfg(Py_3_14)] - pub fn PyLong_Export(obj: *mut PyObject, export_long: *mut PyLongExport) -> c_int; - - #[cfg(Py_3_14)] - pub fn PyLong_FreeExport(export_long: *mut PyLongExport); - - #[cfg(Py_3_14)] - pub fn PyLongWriter_Create( - negative: c_int, - ndigits: Py_ssize_t, - digits: *mut *mut c_void, - ) -> *mut PyLongWriter; - - #[cfg(Py_3_14)] - pub fn PyLongWriter_Finish(writer: *mut PyLongWriter) -> *mut PyObject; - - #[cfg(Py_3_14)] - pub fn PyLongWriter_Discard(writer: *mut PyLongWriter); } diff --git a/pyo3-ffi/src/cpython/mod.rs b/pyo3-ffi/src/cpython/mod.rs index c8215212b37..3dc302a53ec 100644 --- a/pyo3-ffi/src/cpython/mod.rs +++ b/pyo3-ffi/src/cpython/mod.rs @@ -24,6 +24,8 @@ pub(crate) mod initconfig; pub(crate) mod listobject; #[cfg(Py_3_13)] pub(crate) mod lock; +#[cfg(Py_3_14)] +pub(crate) mod longintrepr; pub(crate) mod longobject; pub(crate) mod marshal; #[cfg(all(Py_3_9, not(PyPy)))] @@ -71,6 +73,8 @@ pub use self::initconfig::*; pub use self::listobject::*; #[cfg(Py_3_13)] pub use self::lock::*; +#[cfg(Py_3_14)] +pub use self::longintrepr::*; pub use self::longobject::*; pub use self::marshal::*; #[cfg(all(Py_3_9, not(PyPy)))] diff --git a/pyo3-ffi/src/lib.rs b/pyo3-ffi/src/lib.rs index df72ac7b23e..1363ffe4be6 100644 --- a/pyo3-ffi/src/lib.rs +++ b/pyo3-ffi/src/lib.rs @@ -522,7 +522,6 @@ mod import; mod intrcheck; mod iterobject; mod listobject; -// skipped longintrepr.h mod longobject; mod memoryobject; mod methodobject; diff --git a/pyo3-ffi/src/longobject.rs b/pyo3-ffi/src/longobject.rs index bb2314c00ea..64a33c41e71 100644 --- a/pyo3-ffi/src/longobject.rs +++ b/pyo3-ffi/src/longobject.rs @@ -39,6 +39,56 @@ extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyLong_AsUnsignedLongMask")] pub fn PyLong_AsUnsignedLongMask(arg1: *mut PyObject) -> c_ulong; // skipped non-limited _PyLong_AsInt + + // skipped non-limited PyLong_FromInt32 + // skipped non-limited PyLong_FromUInt32 + // skipped non-limited PyLong_FromInt64 + // skipped non-limited PyLong_FromUInt64 + + // skipped non-limited PyLong_AsInt32 + // skipped non-limited PyLong_AsUInt32 + // skipped non-limited PyLong_AsInt64 + // skipped non-limited PyLong_AsUInt64 +} + +#[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; +#[cfg(Py_3_13)] +pub const Py_ASNATIVEBYTES_ALLOW_INDEX: c_int = 16; + +extern_libpython! { + #[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; + pub fn PyLong_GetInfo() -> *mut PyObject; // skipped PyLong_AS_LONG @@ -47,15 +97,6 @@ extern_libpython! { // 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")] @@ -82,23 +123,7 @@ extern_libpython! { ) -> *mut PyObject; } -#[cfg(not(Py_LIMITED_API))] -extern_libpython! { - #[cfg_attr(PyPy, link_name = "_PyPyLong_NumBits")] - #[cfg(not(Py_3_13))] - #[doc(hidden)] - 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_libpython! { 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 From 5e049e5b6195d48f3352ef8e6a47884f6f87e62d Mon Sep 17 00:00:00 2001 From: chiri Date: Wed, 6 May 2026 23:33:16 +0300 Subject: [PATCH 16/44] add `PyLong_Is*` --- pyo3-ffi/src/cpython/longobject.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/pyo3-ffi/src/cpython/longobject.rs b/pyo3-ffi/src/cpython/longobject.rs index 1e05e506e3c..65e380d963f 100644 --- a/pyo3-ffi/src/cpython/longobject.rs +++ b/pyo3-ffi/src/cpython/longobject.rs @@ -11,9 +11,13 @@ extern_libpython! { // skipped PyUnstable_Long_IsCompact // skipped PyUnstable_Long_CompactValue - // skipped PyLong_IsPositive - // skipped PyLong_IsNegative - // skipped PyLong_IsZero + #[cfg(Py_3_14)] + pub fn PyLong_IsPositive(obj: *mut PyObject) -> c_int; + #[cfg(Py_3_14)] + pub fn PyLong_IsNegative(obj: *mut PyObject) -> c_int; + #[cfg(Py_3_14)] + pub fn PyLong_IsZero(obj: *mut PyObject) -> c_int; + // skipped PyLong_GetSign // skipped _PyLong_Sign From 9ad8e5d0a79709340218eddc037282fa610be369 Mon Sep 17 00:00:00 2001 From: chiri Date: Thu, 7 May 2026 17:52:46 +0300 Subject: [PATCH 17/44] remove PyUnstable_Long_IsCompact --- pyo3-ffi/src/cpython/longobject.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/pyo3-ffi/src/cpython/longobject.rs b/pyo3-ffi/src/cpython/longobject.rs index 65e380d963f..dcea0dbe24e 100644 --- a/pyo3-ffi/src/cpython/longobject.rs +++ b/pyo3-ffi/src/cpython/longobject.rs @@ -5,9 +5,6 @@ use std::ffi::{c_int, c_uchar}; // skipped _PyLong_CAST extern_libpython! { - #[cfg(Py_3_13)] - pub fn PyUnstable_Long_IsCompact(u: *mut PyObject, base: c_int) -> *mut PyObject; - // skipped PyUnstable_Long_IsCompact // skipped PyUnstable_Long_CompactValue From e2c2caa8ebdd56c22f1e94988ce6f8ea84a908c2 Mon Sep 17 00:00:00 2001 From: chiri Date: Thu, 7 May 2026 17:57:11 +0300 Subject: [PATCH 18/44] bench new line --- pyo3-benches/benches/bench_int128.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyo3-benches/benches/bench_int128.rs b/pyo3-benches/benches/bench_int128.rs index d19bc1a24f4..c8576b9b6d0 100644 --- a/pyo3-benches/benches/bench_int128.rs +++ b/pyo3-benches/benches/bench_int128.rs @@ -65,4 +65,4 @@ fn criterion_benchmark(c: &mut Criterion) { } criterion_group!(benches, criterion_benchmark); -criterion_main!(benches); \ No newline at end of file +criterion_main!(benches); From bf00a582b02288b63e1d794139b4076d421d0e9e Mon Sep 17 00:00:00 2001 From: chiri Date: Thu, 7 May 2026 17:57:21 +0300 Subject: [PATCH 19/44] PyLong_FromUnicodeObject --- pyo3-ffi/src/cpython/longobject.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyo3-ffi/src/cpython/longobject.rs b/pyo3-ffi/src/cpython/longobject.rs index dcea0dbe24e..cebb2964be6 100644 --- a/pyo3-ffi/src/cpython/longobject.rs +++ b/pyo3-ffi/src/cpython/longobject.rs @@ -5,6 +5,9 @@ use std::ffi::{c_int, c_uchar}; // skipped _PyLong_CAST extern_libpython! { + #[cfg(Py_3_13)] + pub fn PyLong_FromUnicodeObject(u: *mut PyObject, base: c_int) -> *mut PyObject; + // skipped PyUnstable_Long_IsCompact // skipped PyUnstable_Long_CompactValue From 14d54f31b75b11702056222c8d7842928f995986 Mon Sep 17 00:00:00 2001 From: chiri Date: Thu, 7 May 2026 17:58:24 +0300 Subject: [PATCH 20/44] remove #[cfg(not(Py_LIMITED_API))] --- pyo3-ffi/src/cpython/longobject.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/pyo3-ffi/src/cpython/longobject.rs b/pyo3-ffi/src/cpython/longobject.rs index cebb2964be6..ee325a4be14 100644 --- a/pyo3-ffi/src/cpython/longobject.rs +++ b/pyo3-ffi/src/cpython/longobject.rs @@ -22,7 +22,6 @@ extern_libpython! { // skipped _PyLong_Sign - #[cfg(not(Py_LIMITED_API))] #[cfg_attr(PyPy, link_name = "_PyPyLong_NumBits")] #[cfg(not(Py_3_13))] #[doc(hidden)] From 64248a1aa607117e8e9eb2f8252505db8f3a4a05 Mon Sep 17 00:00:00 2001 From: chiri Date: Thu, 7 May 2026 18:58:13 +0300 Subject: [PATCH 21/44] review --- pyo3-ffi/src/longobject.rs | 20 +++--- src/conversions/std/num.rs | 121 +++++++++++++++++++++++++------------ 2 files changed, 93 insertions(+), 48 deletions(-) diff --git a/pyo3-ffi/src/longobject.rs b/pyo3-ffi/src/longobject.rs index 64a33c41e71..ffa37c0f82a 100644 --- a/pyo3-ffi/src/longobject.rs +++ b/pyo3-ffi/src/longobject.rs @@ -51,23 +51,23 @@ extern_libpython! { // skipped non-limited PyLong_AsUInt64 } -#[cfg(Py_3_13)] +#[cfg(any(Py_3_14, all(Py_3_13, not(Py_LIMITED_API))))] pub const Py_ASNATIVEBYTES_DEFAULTS: c_int = -1; -#[cfg(Py_3_13)] +#[cfg(any(Py_3_14, all(Py_3_13, not(Py_LIMITED_API))))] pub const Py_ASNATIVEBYTES_BIG_ENDIAN: c_int = 0; -#[cfg(Py_3_13)] +#[cfg(any(Py_3_14, all(Py_3_13, not(Py_LIMITED_API))))] pub const Py_ASNATIVEBYTES_LITTLE_ENDIAN: c_int = 1; -#[cfg(Py_3_13)] +#[cfg(any(Py_3_14, all(Py_3_13, not(Py_LIMITED_API))))] pub const Py_ASNATIVEBYTES_NATIVE_ENDIAN: c_int = 3; -#[cfg(Py_3_13)] +#[cfg(any(Py_3_14, all(Py_3_13, not(Py_LIMITED_API))))] pub const Py_ASNATIVEBYTES_UNSIGNED_BUFFER: c_int = 4; -#[cfg(Py_3_13)] +#[cfg(any(Py_3_14, all(Py_3_13, not(Py_LIMITED_API))))] pub const Py_ASNATIVEBYTES_REJECT_NEGATIVE: c_int = 8; -#[cfg(Py_3_13)] +#[cfg(any(Py_3_14, all(Py_3_13, not(Py_LIMITED_API))))] pub const Py_ASNATIVEBYTES_ALLOW_INDEX: c_int = 16; extern_libpython! { - #[cfg(Py_3_13)] + #[cfg(any(Py_3_14, all(Py_3_13, not(Py_LIMITED_API))))] pub fn PyLong_AsNativeBytes( v: *mut PyObject, buffer: *mut c_void, @@ -75,14 +75,14 @@ extern_libpython! { flags: c_int, ) -> Py_ssize_t; - #[cfg(Py_3_13)] + #[cfg(any(Py_3_14, all(Py_3_13, not(Py_LIMITED_API))))] pub fn PyLong_FromNativeBytes( buffer: *const c_void, n_bytes: size_t, flags: c_int, ) -> *mut PyObject; - #[cfg(Py_3_13)] + #[cfg(any(Py_3_14, all(Py_3_13, not(Py_LIMITED_API))))] pub fn PyLong_FromUnsignedNativeBytes( buffer: *const c_void, n_bytes: size_t, diff --git a/src/conversions/std/num.rs b/src/conversions/std/num.rs index 938a2bae26b..3f06e9db7be 100644 --- a/src/conversions/std/num.rs +++ b/src/conversions/std/num.rs @@ -357,6 +357,14 @@ int_convert_u64_or_i64!( mod fast_128bit_int_conversion { use super::*; + #[cfg(Py_3_14)] + const PY_LONG_DIGIT_BITS: usize = 30; + + #[cfg(Py_3_14)] + fn get_bits_per_digit() -> usize { + unsafe { (*ffi::PyLong_GetNativeLayout()).bits_per_digit as usize } + } + // for 128bit Integers macro_rules! int_convert_128 { ($rust_type: ty, $is_signed: literal) => { @@ -371,36 +379,47 @@ mod fast_128bit_int_conversion { fn into_pyobject(self, py: Python<'py>) -> Result { #[cfg(Py_3_14)] { - let value = self as u128; - let negative = $is_signed && (self as i128) < 0; - let abs = if negative { - value.wrapping_neg() + if get_bits_per_digit() == PY_LONG_DIGIT_BITS { + const DIGIT_MASK: u32 = (1 << PY_LONG_DIGIT_BITS) - 1; + + let value = self as u128; + let negative = $is_signed && (self as i128) < 0; + let abs = if negative { + value.wrapping_neg() + } else { + value + }; + let bits = 128 - abs.leading_zeros() as usize; + let n_digits = if bits == 0 { + 1 + } else { + bits.div_ceil(PY_LONG_DIGIT_BITS) + }; + let mut ptr = std::ptr::null_mut(); + let long_writer = unsafe { + ffi::PyLongWriter_Create( + negative.into(), + n_digits as ffi::Py_ssize_t, + &mut ptr, + ) + }; + if long_writer.is_null() { + return Err(PyErr::fetch(py)); + } + let digits = ptr.cast::(); + let mut rest = abs; + for i in 0..n_digits { + unsafe { digits.add(i).write(rest as u32 & DIGIT_MASK) }; + rest >>= PY_LONG_DIGIT_BITS; + } + unsafe { + ffi::PyLongWriter_Finish(long_writer) + .assume_owned_or_err(py) + .cast_into_unchecked() + } } else { - value - }; - let bits = 128 - abs.leading_zeros() as usize; - let n_digits = if bits == 0 { 1 } else { bits.div_ceil(30) }; - let mut ptr = std::ptr::null_mut(); - let long_writer = unsafe { - ffi::PyLongWriter_Create( - negative.into(), - n_digits as ffi::Py_ssize_t, - &mut ptr, - ) - }; - if long_writer.is_null() { - return Err(PyErr::fetch(py)); - } - let digits = ptr.cast::(); - let mut rest = abs; - for i in 0..n_digits { - unsafe { digits.add(i).write(rest as u32 & ((1 << 30) - 1)) }; - rest >>= 30; - } - unsafe { - ffi::PyLongWriter_Finish(long_writer) - .assume_owned_or_err(py) - .cast_into_unchecked() + let bytes = self.to_ne_bytes(); + Ok(int_from_ne_bytes::<{ $is_signed }>(py, &bytes)) } } #[cfg(all(Py_3_13, not(Py_3_14)))] @@ -440,6 +459,33 @@ mod fast_128bit_int_conversion { let num = nb_index(&ob)?; #[cfg(Py_3_14)] { + if get_bits_per_digit() != PY_LONG_DIGIT_BITS { + let mut buf = [0u8; std::mem::size_of::<$rust_type>()]; + let mut flags = ffi::Py_ASNATIVEBYTES_NATIVE_ENDIAN; + if !$is_signed { + flags |= ffi::Py_ASNATIVEBYTES_UNSIGNED_BUFFER + | ffi::Py_ASNATIVEBYTES_REJECT_NEGATIVE; + } + let actual_size: usize = unsafe { + ffi::PyLong_AsNativeBytes( + num.as_ptr(), + buf.as_mut_ptr().cast(), + buf.len() + .try_into() + .expect("length of buffer fits in Py_ssize_t"), + flags, + ) + } + .try_into() + .map_err(|_| PyErr::fetch(ob.py()))?; + if actual_size as usize > buf.len() { + return Err(crate::exceptions::PyOverflowError::new_err( + "Python int larger than 128 bits", + )); + } + return Ok(<$rust_type>::from_ne_bytes(buf)); + } + let mut long_export = MaybeUninit::::uninit(); unsafe { crate::err::error_on_minusone( @@ -447,9 +493,9 @@ mod fast_128bit_int_conversion { ffi::PyLong_Export(num.as_ptr().cast(), long_export.as_mut_ptr()), )?; } - let long_export = unsafe { long_export.assume_init() }; - if long_export.digits.is_null() { - return <$rust_type>::try_from(long_export.value).map_err(|_| { + let long_export_ref = unsafe { long_export.assume_init_ref() }; + if long_export_ref.digits.is_null() { + return <$rust_type>::try_from(long_export_ref.value).map_err(|_| { exceptions::PyOverflowError::new_err( "Python int larger than 128 bits", ) @@ -458,9 +504,9 @@ mod fast_128bit_int_conversion { let overflow = || { exceptions::PyOverflowError::new_err("Python int larger than 128 bits") }; - let n_digits = long_export.ndigits as usize; - let negative = long_export.negative != 0; - let digits = long_export.digits.cast::(); + let n_digits = long_export_ref.ndigits as usize; + let negative = long_export_ref.negative != 0; + let digits = long_export_ref.digits.cast::(); let mut abs = 0_u128; let mut overflowed = n_digits > 5; for i in 0..n_digits.min(5) { @@ -468,10 +514,9 @@ mod fast_128bit_int_conversion { if i == 4 && digit >> 8 != 0 { overflowed = true; } - abs |= digit << (i * 30); + abs |= digit << (i * PY_LONG_DIGIT_BITS); } - let mut to_free = long_export; - unsafe { ffi::PyLong_FreeExport(&mut to_free) }; + unsafe { ffi::PyLong_FreeExport(long_export.as_mut_ptr()) }; if overflowed { return Err(overflow()); } From 884cd64a3d4a661fbc2319c44851c0e863266f04 Mon Sep 17 00:00:00 2001 From: chiri Date: Thu, 7 May 2026 19:15:26 +0300 Subject: [PATCH 22/44] add 6016.changed.md --- newsfragments/6016.changed.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 newsfragments/6016.changed.md diff --git a/newsfragments/6016.changed.md b/newsfragments/6016.changed.md new file mode 100644 index 00000000000..808c5e44b97 --- /dev/null +++ b/newsfragments/6016.changed.md @@ -0,0 +1 @@ +Changed `IntoPyObject` for `i128`, `u128`, and references to them to return `PyErr`, allowing Python integer creation failures to be propagated. \ No newline at end of file From 9618b3660fdf725d5c84428e014151b4b6e520cd Mon Sep 17 00:00:00 2001 From: chiri Date: Thu, 7 May 2026 19:29:46 +0300 Subject: [PATCH 23/44] update --- src/conversions/std/num.rs | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/conversions/std/num.rs b/src/conversions/std/num.rs index 3f06e9db7be..61463df1cab 100644 --- a/src/conversions/std/num.rs +++ b/src/conversions/std/num.rs @@ -377,6 +377,12 @@ mod fast_128bit_int_conversion { const OUTPUT_TYPE: PyStaticExpr = PyInt::TYPE_HINT; fn into_pyobject(self, py: Python<'py>) -> Result { + #[cfg(Py_3_13)] + let from_native_bytes = || { + let bytes = self.to_ne_bytes(); + Ok(int_from_ne_bytes::<{ $is_signed }>(py, &bytes)) + }; + #[cfg(Py_3_14)] { if get_bits_per_digit() == PY_LONG_DIGIT_BITS { @@ -418,14 +424,12 @@ mod fast_128bit_int_conversion { .cast_into_unchecked() } } else { - let bytes = self.to_ne_bytes(); - Ok(int_from_ne_bytes::<{ $is_signed }>(py, &bytes)) + from_native_bytes() } } #[cfg(all(Py_3_13, not(Py_3_14)))] { - let bytes = self.to_ne_bytes(); - Ok(int_from_ne_bytes::<{ $is_signed }>(py, &bytes)) + from_native_bytes() } #[cfg(not(Py_3_13))] { @@ -457,10 +461,10 @@ mod fast_128bit_int_conversion { fn extract(ob: Borrowed<'_, '_, PyAny>) -> Result<$rust_type, Self::Error> { let num = nb_index(&ob)?; + let mut buffer = [0u8; std::mem::size_of::<$rust_type>()]; #[cfg(Py_3_14)] { if get_bits_per_digit() != PY_LONG_DIGIT_BITS { - let mut buf = [0u8; std::mem::size_of::<$rust_type>()]; let mut flags = ffi::Py_ASNATIVEBYTES_NATIVE_ENDIAN; if !$is_signed { flags |= ffi::Py_ASNATIVEBYTES_UNSIGNED_BUFFER @@ -469,8 +473,9 @@ mod fast_128bit_int_conversion { let actual_size: usize = unsafe { ffi::PyLong_AsNativeBytes( num.as_ptr(), - buf.as_mut_ptr().cast(), - buf.len() + buffer.as_mut_ptr().cast(), + buffer + .len() .try_into() .expect("length of buffer fits in Py_ssize_t"), flags, @@ -478,12 +483,12 @@ mod fast_128bit_int_conversion { } .try_into() .map_err(|_| PyErr::fetch(ob.py()))?; - if actual_size as usize > buf.len() { + if actual_size as usize > buffer.len() { return Err(crate::exceptions::PyOverflowError::new_err( "Python int larger than 128 bits", )); } - return Ok(<$rust_type>::from_ne_bytes(buf)); + return Ok(<$rust_type>::from_ne_bytes(buffer)); } let mut long_export = MaybeUninit::::uninit(); @@ -543,7 +548,6 @@ mod fast_128bit_int_conversion { } #[cfg(not(Py_3_14))] { - let mut buffer = [0u8; std::mem::size_of::<$rust_type>()]; #[cfg(not(Py_3_13))] { crate::err::error_on_minusone(ob.py(), unsafe { From 4d4a0b02f8a3c7cc552258e9b0a7b057ed8731ba Mon Sep 17 00:00:00 2001 From: chiri Date: Thu, 7 May 2026 19:47:06 +0300 Subject: [PATCH 24/44] update --- src/conversions/std/num.rs | 220 ++++++++++++++++--------------------- 1 file changed, 96 insertions(+), 124 deletions(-) diff --git a/src/conversions/std/num.rs b/src/conversions/std/num.rs index 61463df1cab..97d56b93c9d 100644 --- a/src/conversions/std/num.rs +++ b/src/conversions/std/num.rs @@ -377,12 +377,6 @@ mod fast_128bit_int_conversion { const OUTPUT_TYPE: PyStaticExpr = PyInt::TYPE_HINT; fn into_pyobject(self, py: Python<'py>) -> Result { - #[cfg(Py_3_13)] - let from_native_bytes = || { - let bytes = self.to_ne_bytes(); - Ok(int_from_ne_bytes::<{ $is_signed }>(py, &bytes)) - }; - #[cfg(Py_3_14)] { if get_bits_per_digit() == PY_LONG_DIGIT_BITS { @@ -418,18 +412,17 @@ mod fast_128bit_int_conversion { unsafe { digits.add(i).write(rest as u32 & DIGIT_MASK) }; rest >>= PY_LONG_DIGIT_BITS; } - unsafe { + return unsafe { ffi::PyLongWriter_Finish(long_writer) .assume_owned_or_err(py) .cast_into_unchecked() - } - } else { - from_native_bytes() + }; } } - #[cfg(all(Py_3_13, not(Py_3_14)))] + #[cfg(Py_3_13)] { - from_native_bytes() + let bytes = self.to_ne_bytes(); + Ok(int_from_ne_bytes::<{ $is_signed }>(py, &bytes)) } #[cfg(not(Py_3_13))] { @@ -461,133 +454,112 @@ mod fast_128bit_int_conversion { fn extract(ob: Borrowed<'_, '_, PyAny>) -> Result<$rust_type, Self::Error> { let num = nb_index(&ob)?; - let mut buffer = [0u8; std::mem::size_of::<$rust_type>()]; #[cfg(Py_3_14)] { - if get_bits_per_digit() != PY_LONG_DIGIT_BITS { - let mut flags = ffi::Py_ASNATIVEBYTES_NATIVE_ENDIAN; - if !$is_signed { - flags |= ffi::Py_ASNATIVEBYTES_UNSIGNED_BUFFER - | ffi::Py_ASNATIVEBYTES_REJECT_NEGATIVE; - } - let actual_size: usize = unsafe { - ffi::PyLong_AsNativeBytes( - num.as_ptr(), - buffer.as_mut_ptr().cast(), - buffer - .len() - .try_into() - .expect("length of buffer fits in Py_ssize_t"), - flags, - ) + if get_bits_per_digit() == PY_LONG_DIGIT_BITS { + let mut long_export = MaybeUninit::::uninit(); + unsafe { + crate::err::error_on_minusone( + num.py(), + ffi::PyLong_Export( + num.as_ptr().cast(), + long_export.as_mut_ptr(), + ), + )?; } - .try_into() - .map_err(|_| PyErr::fetch(ob.py()))?; - if actual_size as usize > buffer.len() { - return Err(crate::exceptions::PyOverflowError::new_err( - "Python int larger than 128 bits", - )); + let long_export_ref = unsafe { long_export.assume_init_ref() }; + if long_export_ref.digits.is_null() { + return <$rust_type>::try_from(long_export_ref.value).map_err( + |_| { + exceptions::PyOverflowError::new_err( + "Python int larger than 128 bits", + ) + }, + ); } - return Ok(<$rust_type>::from_ne_bytes(buffer)); - } - - let mut long_export = MaybeUninit::::uninit(); - unsafe { - crate::err::error_on_minusone( - num.py(), - ffi::PyLong_Export(num.as_ptr().cast(), long_export.as_mut_ptr()), - )?; - } - let long_export_ref = unsafe { long_export.assume_init_ref() }; - if long_export_ref.digits.is_null() { - return <$rust_type>::try_from(long_export_ref.value).map_err(|_| { + let overflow = || { exceptions::PyOverflowError::new_err( "Python int larger than 128 bits", ) - }); - } - let overflow = || { - exceptions::PyOverflowError::new_err("Python int larger than 128 bits") - }; - let n_digits = long_export_ref.ndigits as usize; - let negative = long_export_ref.negative != 0; - let digits = long_export_ref.digits.cast::(); - let mut abs = 0_u128; - let mut overflowed = n_digits > 5; - for i in 0..n_digits.min(5) { - let digit = u128::from(unsafe { digits.add(i).read() }); - if i == 4 && digit >> 8 != 0 { - overflowed = true; - } - abs |= digit << (i * PY_LONG_DIGIT_BITS); - } - unsafe { ffi::PyLong_FreeExport(long_export.as_mut_ptr()) }; - if overflowed { - return Err(overflow()); - } - if !$is_signed { - if negative { - return Err(exceptions::PyOverflowError::new_err( - "can't convert negative int to unsigned", - )); + }; + let n_digits = long_export_ref.ndigits as usize; + let negative = long_export_ref.negative != 0; + let digits = long_export_ref.digits.cast::(); + let mut abs = 0_u128; + let mut overflowed = n_digits > 5; + for i in 0..n_digits.min(5) { + let digit = u128::from(unsafe { digits.add(i).read() }); + if i == 4 && digit >> 8 != 0 { + overflowed = true; + } + abs |= digit << (i * PY_LONG_DIGIT_BITS); } - return <$rust_type>::try_from(abs).map_err(|_| overflow()); - } - let signed = if negative { - if abs > 1_u128 << 127 { + unsafe { ffi::PyLong_FreeExport(long_export.as_mut_ptr()) }; + if overflowed { return Err(overflow()); } - (abs.wrapping_neg()) as i128 - } else { - if abs > i128::MAX as u128 { - return Err(overflow()); + if !$is_signed { + if negative { + return Err(exceptions::PyOverflowError::new_err( + "can't convert negative int to unsigned", + )); + } + return <$rust_type>::try_from(abs).map_err(|_| overflow()); } - abs as i128 - }; - <$rust_type>::try_from(signed).map_err(|_| overflow()) + let signed = if negative { + if abs > 1_u128 << 127 { + return Err(overflow()); + } + (abs.wrapping_neg()) as i128 + } else { + if abs > i128::MAX as u128 { + return Err(overflow()); + } + abs as i128 + }; + return <$rust_type>::try_from(signed).map_err(|_| overflow()); + } } - #[cfg(not(Py_3_14))] + let mut buffer = [0u8; std::mem::size_of::<$rust_type>()]; + #[cfg(not(Py_3_13))] { - #[cfg(not(Py_3_13))] - { - crate::err::error_on_minusone(ob.py(), unsafe { - ffi::_PyLong_AsByteArray( - num.as_ptr() as *mut ffi::PyLongObject, - buffer.as_mut_ptr(), - buffer.len(), - 1, - $is_signed.into(), - ) - })?; - Ok(<$rust_type>::from_le_bytes(buffer)) + crate::err::error_on_minusone(ob.py(), unsafe { + ffi::_PyLong_AsByteArray( + num.as_ptr() as *mut ffi::PyLongObject, + buffer.as_mut_ptr(), + buffer.len(), + 1, + $is_signed.into(), + ) + })?; + Ok(<$rust_type>::from_le_bytes(buffer)) + } + #[cfg(Py_3_13)] + { + let mut flags = ffi::Py_ASNATIVEBYTES_NATIVE_ENDIAN; + if !$is_signed { + flags |= ffi::Py_ASNATIVEBYTES_UNSIGNED_BUFFER + | ffi::Py_ASNATIVEBYTES_REJECT_NEGATIVE; } - #[cfg(Py_3_13)] - { - let mut flags = ffi::Py_ASNATIVEBYTES_NATIVE_ENDIAN; - if !$is_signed { - flags |= ffi::Py_ASNATIVEBYTES_UNSIGNED_BUFFER - | ffi::Py_ASNATIVEBYTES_REJECT_NEGATIVE; - } - let actual_size: usize = unsafe { - ffi::PyLong_AsNativeBytes( - num.as_ptr(), - buffer.as_mut_ptr().cast(), - buffer - .len() - .try_into() - .expect("length of buffer fits in Py_ssize_t"), - flags, - ) - } - .try_into() - .map_err(|_| PyErr::fetch(ob.py()))?; - if actual_size as usize > buffer.len() { - return Err(crate::exceptions::PyOverflowError::new_err( - "Python int larger than 128 bits", - )); - } - Ok(<$rust_type>::from_ne_bytes(buffer)) + let actual_size: usize = unsafe { + ffi::PyLong_AsNativeBytes( + num.as_ptr(), + buffer.as_mut_ptr().cast(), + buffer + .len() + .try_into() + .expect("length of buffer fits in Py_ssize_t"), + flags, + ) + } + .try_into() + .map_err(|_| PyErr::fetch(ob.py()))?; + if actual_size as usize > buffer.len() { + return Err(crate::exceptions::PyOverflowError::new_err( + "Python int larger than 128 bits", + )); } + Ok(<$rust_type>::from_ne_bytes(buffer)) } } } From d96ca58a4e6b63b8279896f1d698bec6fcf5d2ec Mon Sep 17 00:00:00 2001 From: chiri Date: Fri, 8 May 2026 01:16:03 +0300 Subject: [PATCH 25/44] update --- src/conversions/std/num.rs | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/conversions/std/num.rs b/src/conversions/std/num.rs index 97d56b93c9d..1e40ad8bd4d 100644 --- a/src/conversions/std/num.rs +++ b/src/conversions/std/num.rs @@ -358,11 +358,16 @@ mod fast_128bit_int_conversion { use super::*; #[cfg(Py_3_14)] - const PY_LONG_DIGIT_BITS: usize = 30; + const PYLONG_BITS_IN_DIGIT: usize = 30; #[cfg(Py_3_14)] - fn get_bits_per_digit() -> usize { - unsafe { (*ffi::PyLong_GetNativeLayout()).bits_per_digit as usize } + fn is_30bit_layout() -> bool { + static DIGITS: std::sync::OnceLock = std::sync::OnceLock::new(); + + *DIGITS.get_or_init(|| { + let layout = unsafe { &*ffi::PyLong_GetNativeLayout() }; + layout.bits_per_digit == PYLONG_BITS_IN_DIGIT + }) } // for 128bit Integers @@ -379,8 +384,8 @@ mod fast_128bit_int_conversion { fn into_pyobject(self, py: Python<'py>) -> Result { #[cfg(Py_3_14)] { - if get_bits_per_digit() == PY_LONG_DIGIT_BITS { - const DIGIT_MASK: u32 = (1 << PY_LONG_DIGIT_BITS) - 1; + if !is_30bit_layout() { + const DIGIT_MASK: u32 = (1 << PYLONG_BITS_IN_DIGIT) - 1; let value = self as u128; let negative = $is_signed && (self as i128) < 0; @@ -393,7 +398,7 @@ mod fast_128bit_int_conversion { let n_digits = if bits == 0 { 1 } else { - bits.div_ceil(PY_LONG_DIGIT_BITS) + bits.div_ceil(PYLONG_BITS_IN_DIGIT) }; let mut ptr = std::ptr::null_mut(); let long_writer = unsafe { @@ -410,7 +415,7 @@ mod fast_128bit_int_conversion { let mut rest = abs; for i in 0..n_digits { unsafe { digits.add(i).write(rest as u32 & DIGIT_MASK) }; - rest >>= PY_LONG_DIGIT_BITS; + rest >>= PYLONG_BITS_IN_DIGIT; } return unsafe { ffi::PyLongWriter_Finish(long_writer) @@ -456,7 +461,7 @@ mod fast_128bit_int_conversion { let num = nb_index(&ob)?; #[cfg(Py_3_14)] { - if get_bits_per_digit() == PY_LONG_DIGIT_BITS { + if !is_30bit_layout() { let mut long_export = MaybeUninit::::uninit(); unsafe { crate::err::error_on_minusone( @@ -492,7 +497,7 @@ mod fast_128bit_int_conversion { if i == 4 && digit >> 8 != 0 { overflowed = true; } - abs |= digit << (i * PY_LONG_DIGIT_BITS); + abs |= digit << (i * PYLONG_BITS_IN_DIGIT); } unsafe { ffi::PyLong_FreeExport(long_export.as_mut_ptr()) }; if overflowed { From 9976b33dbbea89bc60431279f20b10df0477ab85 Mon Sep 17 00:00:00 2001 From: chiri Date: Fri, 8 May 2026 01:19:49 +0300 Subject: [PATCH 26/44] fix --- src/conversions/std/num.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/conversions/std/num.rs b/src/conversions/std/num.rs index 1e40ad8bd4d..2684cea7aed 100644 --- a/src/conversions/std/num.rs +++ b/src/conversions/std/num.rs @@ -366,7 +366,7 @@ mod fast_128bit_int_conversion { *DIGITS.get_or_init(|| { let layout = unsafe { &*ffi::PyLong_GetNativeLayout() }; - layout.bits_per_digit == PYLONG_BITS_IN_DIGIT + layout.bits_per_digit == PYLONG_BITS_IN_DIGIT as u8 }) } From f35bb453d7ace5a156654dcffc69c4f661ca7cc2 Mon Sep 17 00:00:00 2001 From: chiri Date: Fri, 8 May 2026 01:23:52 +0300 Subject: [PATCH 27/44] fix --- src/conversions/std/num.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/conversions/std/num.rs b/src/conversions/std/num.rs index 2684cea7aed..5fcb974756c 100644 --- a/src/conversions/std/num.rs +++ b/src/conversions/std/num.rs @@ -384,7 +384,7 @@ mod fast_128bit_int_conversion { fn into_pyobject(self, py: Python<'py>) -> Result { #[cfg(Py_3_14)] { - if !is_30bit_layout() { + if is_30bit_layout() { const DIGIT_MASK: u32 = (1 << PYLONG_BITS_IN_DIGIT) - 1; let value = self as u128; @@ -461,7 +461,7 @@ mod fast_128bit_int_conversion { let num = nb_index(&ob)?; #[cfg(Py_3_14)] { - if !is_30bit_layout() { + if is_30bit_layout() { let mut long_export = MaybeUninit::::uninit(); unsafe { crate::err::error_on_minusone( From 4a4855d50fcf2e6ca7c680878473473d5b82f7c1 Mon Sep 17 00:00:00 2001 From: chiri Date: Sat, 9 May 2026 22:45:37 +0300 Subject: [PATCH 28/44] review --- src/conversions/std/num.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/conversions/std/num.rs b/src/conversions/std/num.rs index 5fcb974756c..6edd5b03d46 100644 --- a/src/conversions/std/num.rs +++ b/src/conversions/std/num.rs @@ -364,9 +364,20 @@ mod fast_128bit_int_conversion { fn is_30bit_layout() -> bool { static DIGITS: std::sync::OnceLock = std::sync::OnceLock::new(); + const PYLONG_DIGIT_SIZE: u8 = 4; + const PYLONG_DIGITS_ORDER: i8 = -1; + + #[cfg(target_endian = "little")] + const NATIVE_DIGIT_ENDIANNESS: i8 = -1; + #[cfg(target_endian = "big")] + const NATIVE_DIGIT_ENDIANNESS: i8 = 1; + *DIGITS.get_or_init(|| { let layout = unsafe { &*ffi::PyLong_GetNativeLayout() }; layout.bits_per_digit == PYLONG_BITS_IN_DIGIT as u8 + && layout.digit_size == PYLONG_DIGIT_SIZE + && layout.digits_order == PYLONG_DIGITS_ORDER + && layout.digit_endianness == NATIVE_DIGIT_ENDIANNESS }) } @@ -466,10 +477,7 @@ mod fast_128bit_int_conversion { unsafe { crate::err::error_on_minusone( num.py(), - ffi::PyLong_Export( - num.as_ptr().cast(), - long_export.as_mut_ptr(), - ), + ffi::PyLong_Export(num.as_ptr(), long_export.as_mut_ptr()), )?; } let long_export_ref = unsafe { long_export.assume_init_ref() }; From 44861bd6cca7944d31d5dd3a177491eda090a1e6 Mon Sep 17 00:00:00 2001 From: chiri Date: Sat, 9 May 2026 23:10:38 +0300 Subject: [PATCH 29/44] review (x2) --- src/conversions/std/num.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/conversions/std/num.rs b/src/conversions/std/num.rs index 6edd5b03d46..e1290fc53e5 100644 --- a/src/conversions/std/num.rs +++ b/src/conversions/std/num.rs @@ -482,13 +482,13 @@ mod fast_128bit_int_conversion { } let long_export_ref = unsafe { long_export.assume_init_ref() }; if long_export_ref.digits.is_null() { - return <$rust_type>::try_from(long_export_ref.value).map_err( - |_| { - exceptions::PyOverflowError::new_err( - "Python int larger than 128 bits", - ) - }, - ); + let value = long_export_ref.value; + unsafe { ffi::PyLong_FreeExport(long_export.as_mut_ptr()) }; + return <$rust_type>::try_from(value).map_err(|_| { + exceptions::PyOverflowError::new_err( + "Python int larger than 128 bits", + ) + }); } let overflow = || { exceptions::PyOverflowError::new_err( @@ -596,7 +596,6 @@ pub(crate) fn int_from_le_bytes<'py, const IS_SIGNED: bool>( } #[cfg(all(Py_3_13, not(Py_LIMITED_API)))] -#[allow(dead_code)] pub(crate) fn int_from_ne_bytes<'py, const IS_SIGNED: bool>( py: Python<'py>, bytes: &[u8], From dd7f0dc45ba3888bc87cbe4ed2cb23373c2c27f1 Mon Sep 17 00:00:00 2001 From: chiri Date: Sat, 9 May 2026 23:15:40 +0300 Subject: [PATCH 30/44] review (x3) --- src/conversions/std/num.rs | 222 ++++++++++++++++++++----------------- 1 file changed, 122 insertions(+), 100 deletions(-) diff --git a/src/conversions/std/num.rs b/src/conversions/std/num.rs index e1290fc53e5..6488dd76765 100644 --- a/src/conversions/std/num.rs +++ b/src/conversions/std/num.rs @@ -353,33 +353,86 @@ int_convert_u64_or_i64!( true ); -#[cfg(not(Py_LIMITED_API))] -mod fast_128bit_int_conversion { - use super::*; - - #[cfg(Py_3_14)] - const PYLONG_BITS_IN_DIGIT: usize = 30; - - #[cfg(Py_3_14)] - fn is_30bit_layout() -> bool { - static DIGITS: std::sync::OnceLock = std::sync::OnceLock::new(); - - const PYLONG_DIGIT_SIZE: u8 = 4; - const PYLONG_DIGITS_ORDER: i8 = -1; +#[cfg(all(Py_3_14, not(Py_LIMITED_API)))] +pub(crate) const PYLONG_BITS_IN_DIGIT: usize = 30; + +#[cfg(all(Py_3_14, not(Py_LIMITED_API)))] +pub(crate) fn is_30bit_layout() -> bool { + static DIGITS: std::sync::OnceLock = std::sync::OnceLock::new(); + + const PYLONG_DIGIT_SIZE: u8 = 4; + const PYLONG_DIGITS_ORDER: i8 = -1; + + #[cfg(target_endian = "little")] + const NATIVE_DIGIT_ENDIANNESS: i8 = -1; + #[cfg(target_endian = "big")] + const NATIVE_DIGIT_ENDIANNESS: i8 = 1; + + *DIGITS.get_or_init(|| { + let layout = unsafe { &*ffi::PyLong_GetNativeLayout() }; + layout.bits_per_digit == PYLONG_BITS_IN_DIGIT as u8 + && layout.digit_size == PYLONG_DIGIT_SIZE + && layout.digits_order == PYLONG_DIGITS_ORDER + && layout.digit_endianness == NATIVE_DIGIT_ENDIANNESS + }) +} - #[cfg(target_endian = "little")] - const NATIVE_DIGIT_ENDIANNESS: i8 = -1; - #[cfg(target_endian = "big")] - const NATIVE_DIGIT_ENDIANNESS: i8 = 1; +// Builds an int from an iterator of 30-bit digits +#[cfg(all(Py_3_14, not(Py_LIMITED_API)))] +pub(crate) fn pylong_from_digits<'py, I: ExactSizeIterator>( + py: Python<'py>, + negative: bool, + digits: I, +) -> PyResult> { + let n_digits = digits.len(); + let mut ptr = std::ptr::null_mut(); + let writer = + unsafe { ffi::PyLongWriter_Create(negative.into(), n_digits as ffi::Py_ssize_t, &mut ptr) }; + if writer.is_null() { + return Err(PyErr::fetch(py)); + } + let digit_ptr = ptr.cast::(); + for (i, d) in digits.enumerate() { + unsafe { digit_ptr.add(i).write(d) }; + } + unsafe { + ffi::PyLongWriter_Finish(writer) + .assume_owned_or_err(py) + .cast_into_unchecked() + } +} - *DIGITS.get_or_init(|| { - let layout = unsafe { &*ffi::PyLong_GetNativeLayout() }; - layout.bits_per_digit == PYLONG_BITS_IN_DIGIT as u8 - && layout.digit_size == PYLONG_DIGIT_SIZE - && layout.digits_order == PYLONG_DIGITS_ORDER - && layout.digit_endianness == NATIVE_DIGIT_ENDIANNESS - }) +// Visits 30-bit digits LSB-first and deals with freeing the export +#[cfg(all(Py_3_14, not(Py_LIMITED_API)))] +pub(crate) fn pylong_visit_digits( + obj: Borrowed<'_, '_, PyAny>, + f: impl FnOnce(bool, i64, Option<&[u32]>) -> PyResult, +) -> PyResult { + let mut long_export = MaybeUninit::::uninit(); + unsafe { + crate::err::error_on_minusone( + obj.py(), + ffi::PyLong_Export(obj.as_ptr(), long_export.as_mut_ptr()), + )?; } + let long_export_ref = unsafe { long_export.assume_init_ref() }; + let negative = long_export_ref.negative != 0; + let compact = long_export_ref.value; + let result = if long_export_ref.digits.is_null() { + f(negative, compact, None) + } else { + let n_digits = long_export_ref.ndigits as usize; + let digits_ptr = long_export_ref.digits.cast::(); + let digits = unsafe { std::slice::from_raw_parts(digits_ptr, n_digits) }; + f(negative, compact, Some(digits)) + }; + unsafe { ffi::PyLong_FreeExport(long_export.as_mut_ptr()) }; + result +} + +#[cfg(not(Py_LIMITED_API))] +mod fast_128bit_int_conversion { + use super::*; // for 128bit Integers macro_rules! int_convert_128 { @@ -397,7 +450,6 @@ mod fast_128bit_int_conversion { { if is_30bit_layout() { const DIGIT_MASK: u32 = (1 << PYLONG_BITS_IN_DIGIT) - 1; - let value = self as u128; let negative = $is_signed && (self as i128) < 0; let abs = if negative { @@ -411,28 +463,9 @@ mod fast_128bit_int_conversion { } else { bits.div_ceil(PYLONG_BITS_IN_DIGIT) }; - let mut ptr = std::ptr::null_mut(); - let long_writer = unsafe { - ffi::PyLongWriter_Create( - negative.into(), - n_digits as ffi::Py_ssize_t, - &mut ptr, - ) - }; - if long_writer.is_null() { - return Err(PyErr::fetch(py)); - } - let digits = ptr.cast::(); - let mut rest = abs; - for i in 0..n_digits { - unsafe { digits.add(i).write(rest as u32 & DIGIT_MASK) }; - rest >>= PYLONG_BITS_IN_DIGIT; - } - return unsafe { - ffi::PyLongWriter_Finish(long_writer) - .assume_owned_or_err(py) - .cast_into_unchecked() - }; + let digits = (0..n_digits) + .map(|i| (abs >> (i * PYLONG_BITS_IN_DIGIT)) as u32 & DIGIT_MASK); + return pylong_from_digits(py, negative, digits); } } #[cfg(Py_3_13)] @@ -473,64 +506,53 @@ mod fast_128bit_int_conversion { #[cfg(Py_3_14)] { if is_30bit_layout() { - let mut long_export = MaybeUninit::::uninit(); - unsafe { - crate::err::error_on_minusone( - num.py(), - ffi::PyLong_Export(num.as_ptr(), long_export.as_mut_ptr()), - )?; - } - let long_export_ref = unsafe { long_export.assume_init_ref() }; - if long_export_ref.digits.is_null() { - let value = long_export_ref.value; - unsafe { ffi::PyLong_FreeExport(long_export.as_mut_ptr()) }; - return <$rust_type>::try_from(value).map_err(|_| { - exceptions::PyOverflowError::new_err( - "Python int larger than 128 bits", - ) - }); - } let overflow = || { exceptions::PyOverflowError::new_err( "Python int larger than 128 bits", ) }; - let n_digits = long_export_ref.ndigits as usize; - let negative = long_export_ref.negative != 0; - let digits = long_export_ref.digits.cast::(); - let mut abs = 0_u128; - let mut overflowed = n_digits > 5; - for i in 0..n_digits.min(5) { - let digit = u128::from(unsafe { digits.add(i).read() }); - if i == 4 && digit >> 8 != 0 { - overflowed = true; - } - abs |= digit << (i * PYLONG_BITS_IN_DIGIT); - } - unsafe { ffi::PyLong_FreeExport(long_export.as_mut_ptr()) }; - if overflowed { - return Err(overflow()); - } - if !$is_signed { - if negative { - return Err(exceptions::PyOverflowError::new_err( - "can't convert negative int to unsigned", - )); - } - return <$rust_type>::try_from(abs).map_err(|_| overflow()); - } - let signed = if negative { - if abs > 1_u128 << 127 { - return Err(overflow()); - } - (abs.wrapping_neg()) as i128 - } else { - if abs > i128::MAX as u128 { - return Err(overflow()); - } - abs as i128 - }; - return <$rust_type>::try_from(signed).map_err(|_| overflow()); + return pylong_visit_digits( + num.as_any().as_borrowed(), + |negative, compact, digits| { + let Some(digits) = digits else { + return <$rust_type>::try_from(compact) + .map_err(|_| overflow()); + }; + let n_digits = digits.len(); + let mut abs = 0_u128; + let mut overflowed = n_digits > 5; + for (i, &digit) in digits.iter().take(5).enumerate() { + let d = u128::from(digit); + if i == 4 && d >> 8 != 0 { + overflowed = true; + } + abs |= d << (i * PYLONG_BITS_IN_DIGIT); + } + if overflowed { + return Err(overflow()); + } + if !$is_signed { + if negative { + return Err(exceptions::PyOverflowError::new_err( + "can't convert negative int to unsigned", + )); + } + return <$rust_type>::try_from(abs).map_err(|_| overflow()); + } + let signed = if negative { + if abs > 1_u128 << 127 { + return Err(overflow()); + } + (abs.wrapping_neg()) as i128 + } else { + if abs > i128::MAX as u128 { + return Err(overflow()); + } + abs as i128 + }; + <$rust_type>::try_from(signed).map_err(|_| overflow()) + }, + ); } } let mut buffer = [0u8; std::mem::size_of::<$rust_type>()]; From e66b19c1ef6a91a86ae244a7d8289d5601161921 Mon Sep 17 00:00:00 2001 From: chiri Date: Sun, 10 May 2026 00:13:56 +0300 Subject: [PATCH 31/44] add #[inline] --- src/conversions/std/num.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/conversions/std/num.rs b/src/conversions/std/num.rs index 6488dd76765..fb66d424993 100644 --- a/src/conversions/std/num.rs +++ b/src/conversions/std/num.rs @@ -379,6 +379,7 @@ pub(crate) fn is_30bit_layout() -> bool { // Builds an int from an iterator of 30-bit digits #[cfg(all(Py_3_14, not(Py_LIMITED_API)))] +#[inline] pub(crate) fn pylong_from_digits<'py, I: ExactSizeIterator>( py: Python<'py>, negative: bool, @@ -404,6 +405,7 @@ pub(crate) fn pylong_from_digits<'py, I: ExactSizeIterator>( // Visits 30-bit digits LSB-first and deals with freeing the export #[cfg(all(Py_3_14, not(Py_LIMITED_API)))] +#[inline] pub(crate) fn pylong_visit_digits( obj: Borrowed<'_, '_, PyAny>, f: impl FnOnce(bool, i64, Option<&[u32]>) -> PyResult, From e12e97ba79e9d509e537140b248fd669ff17be1d Mon Sep 17 00:00:00 2001 From: chiri Date: Sun, 10 May 2026 00:14:32 +0300 Subject: [PATCH 32/44] update --- src/conversions/std/num.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/conversions/std/num.rs b/src/conversions/std/num.rs index fb66d424993..ca2c1c14676 100644 --- a/src/conversions/std/num.rs +++ b/src/conversions/std/num.rs @@ -424,8 +424,8 @@ pub(crate) fn pylong_visit_digits( f(negative, compact, None) } else { let n_digits = long_export_ref.ndigits as usize; - let digits_ptr = long_export_ref.digits.cast::(); - let digits = unsafe { std::slice::from_raw_parts(digits_ptr, n_digits) }; + let ptr = long_export_ref.digits.cast::(); + let digits = unsafe { std::slice::from_raw_parts(ptr, n_digits) }; f(negative, compact, Some(digits)) }; unsafe { ffi::PyLong_FreeExport(long_export.as_mut_ptr()) }; From 66d063b779dd488cb7de97ea26f442551ad2f646 Mon Sep 17 00:00:00 2001 From: chiri Date: Sun, 10 May 2026 20:25:56 +0300 Subject: [PATCH 33/44] review --- pyo3-ffi/src/longobject.rs | 24 ++++++++++++++++-------- src/conversions/std/num.rs | 26 +++++++++++++++++++------- 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/pyo3-ffi/src/longobject.rs b/pyo3-ffi/src/longobject.rs index ffa37c0f82a..c6750d7a765 100644 --- a/pyo3-ffi/src/longobject.rs +++ b/pyo3-ffi/src/longobject.rs @@ -40,15 +40,23 @@ extern_libpython! { pub fn PyLong_AsUnsignedLongMask(arg1: *mut PyObject) -> c_ulong; // skipped non-limited _PyLong_AsInt - // skipped non-limited PyLong_FromInt32 - // skipped non-limited PyLong_FromUInt32 - // skipped non-limited PyLong_FromInt64 - // skipped non-limited PyLong_FromUInt64 + #[cfg(Py_3_14)] + pub fn PyLong_FromInt32(arg1: i32) -> *mut PyObject; + #[cfg(Py_3_14)] + pub fn PyLong_FromUInt32(arg1: u32) -> *mut PyObject; + #[cfg(Py_3_14)] + pub fn PyLong_FromInt64(arg1: i64) -> *mut PyObject; + #[cfg(Py_3_14)] + pub fn PyLong_FromUInt64(arg1: u64) -> *mut PyObject; - // skipped non-limited PyLong_AsInt32 - // skipped non-limited PyLong_AsUInt32 - // skipped non-limited PyLong_AsInt64 - // skipped non-limited PyLong_AsUInt64 + #[cfg(Py_3_14)] + pub fn PyLong_AsInt32(arg1: *mut PyObject, arg2: *mut i32) -> c_int; + #[cfg(Py_3_14)] + pub fn PyLong_AsUInt32(arg1: *mut PyObject, arg2: *mut u32) -> c_int; + #[cfg(Py_3_14)] + pub fn PyLong_AsInt64(arg1: *mut PyObject, arg2: *mut i64) -> c_int; + #[cfg(Py_3_14)] + pub fn PyLong_AsUInt64(arg1: *mut PyObject, arg2: *mut u64) -> c_int; } #[cfg(any(Py_3_14, all(Py_3_13, not(Py_LIMITED_API))))] diff --git a/src/conversions/std/num.rs b/src/conversions/std/num.rs index ca2c1c14676..631e4a194c1 100644 --- a/src/conversions/std/num.rs +++ b/src/conversions/std/num.rs @@ -377,6 +377,16 @@ pub(crate) fn is_30bit_layout() -> bool { }) } +#[cfg(Py_3_14)] +struct ExportGuard(ffi::PyLongExport); + +#[cfg(Py_3_14)] +impl Drop for ExportGuard { + fn drop(&mut self) { + unsafe { ffi::PyLong_FreeExport(&mut self.0) }; + } +} + // Builds an int from an iterator of 30-bit digits #[cfg(all(Py_3_14, not(Py_LIMITED_API)))] #[inline] @@ -385,10 +395,11 @@ pub(crate) fn pylong_from_digits<'py, I: ExactSizeIterator>( negative: bool, digits: I, ) -> PyResult> { - let n_digits = digits.len(); + let digits_len = digits.len(); let mut ptr = std::ptr::null_mut(); - let writer = - unsafe { ffi::PyLongWriter_Create(negative.into(), n_digits as ffi::Py_ssize_t, &mut ptr) }; + let writer = unsafe { + ffi::PyLongWriter_Create(negative.into(), digits_len as ffi::Py_ssize_t, &mut ptr) + }; if writer.is_null() { return Err(PyErr::fetch(py)); } @@ -417,16 +428,17 @@ pub(crate) fn pylong_visit_digits( ffi::PyLong_Export(obj.as_ptr(), long_export.as_mut_ptr()), )?; } - let long_export_ref = unsafe { long_export.assume_init_ref() }; + let export_guard = ExportGuard(unsafe { raw.assume_init() }); + let long_export_ref = &export.0; let negative = long_export_ref.negative != 0; - let compact = long_export_ref.value; + let value = long_export_ref.value; let result = if long_export_ref.digits.is_null() { - f(negative, compact, None) + f(negative, value, None) } else { let n_digits = long_export_ref.ndigits as usize; let ptr = long_export_ref.digits.cast::(); let digits = unsafe { std::slice::from_raw_parts(ptr, n_digits) }; - f(negative, compact, Some(digits)) + f(negative, value, Some(digits)) }; unsafe { ffi::PyLong_FreeExport(long_export.as_mut_ptr()) }; result From 7fc518a2ae6495b39f1ab2f8a56aebca8e9c7637 Mon Sep 17 00:00:00 2001 From: chiri Date: Sun, 10 May 2026 20:26:55 +0300 Subject: [PATCH 34/44] review (x2) --- src/conversions/std/num.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/conversions/std/num.rs b/src/conversions/std/num.rs index 631e4a194c1..a24accc1e5b 100644 --- a/src/conversions/std/num.rs +++ b/src/conversions/std/num.rs @@ -472,11 +472,7 @@ mod fast_128bit_int_conversion { value }; let bits = 128 - abs.leading_zeros() as usize; - let n_digits = if bits == 0 { - 1 - } else { - bits.div_ceil(PYLONG_BITS_IN_DIGIT) - }; + let n_digits = bits.div_ceil(PYLONG_BITS_IN_DIGIT).max(1); let digits = (0..n_digits) .map(|i| (abs >> (i * PYLONG_BITS_IN_DIGIT)) as u32 & DIGIT_MASK); return pylong_from_digits(py, negative, digits); From 1cf84feb08a0f671e7a30fbb9e2f0a2d8ab9599d Mon Sep 17 00:00:00 2001 From: chiri Date: Sun, 10 May 2026 20:29:55 +0300 Subject: [PATCH 35/44] fix --- src/conversions/std/num.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/conversions/std/num.rs b/src/conversions/std/num.rs index a24accc1e5b..bff009e6958 100644 --- a/src/conversions/std/num.rs +++ b/src/conversions/std/num.rs @@ -428,7 +428,7 @@ pub(crate) fn pylong_visit_digits( ffi::PyLong_Export(obj.as_ptr(), long_export.as_mut_ptr()), )?; } - let export_guard = ExportGuard(unsafe { raw.assume_init() }); + let export_guard = ExportGuard(unsafe { long_export.assume_init() }); let long_export_ref = &export.0; let negative = long_export_ref.negative != 0; let value = long_export_ref.value; From 2bbc1637acf6e73afc937b8133617537841f6b34 Mon Sep 17 00:00:00 2001 From: chiri Date: Sun, 10 May 2026 21:11:04 +0300 Subject: [PATCH 36/44] fix --- src/conversions/std/num.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/conversions/std/num.rs b/src/conversions/std/num.rs index bff009e6958..0369298dcb6 100644 --- a/src/conversions/std/num.rs +++ b/src/conversions/std/num.rs @@ -229,7 +229,7 @@ impl<'py> IntoPyObject<'py> for &'_ u8 { _: crate::conversion::private::Token, ) -> Result, PyErr> where - // I: AsRef<[u8]>, but the compiler needs it expressed via the trait for some reason + // I: AsRef<[u8]>, but the compiler needs it expressed via the trait for some reason I: AsRef<[::BaseType]>, { Ok(PyBytes::new(py, iter.as_ref()).into_any()) @@ -377,10 +377,10 @@ pub(crate) fn is_30bit_layout() -> bool { }) } -#[cfg(Py_3_14)] +#[cfg(all(Py_3_14, not(Py_LIMITED_API)))] struct ExportGuard(ffi::PyLongExport); -#[cfg(Py_3_14)] +#[cfg(all(Py_3_14, not(Py_LIMITED_API)))] impl Drop for ExportGuard { fn drop(&mut self) { unsafe { ffi::PyLong_FreeExport(&mut self.0) }; @@ -429,19 +429,17 @@ pub(crate) fn pylong_visit_digits( )?; } let export_guard = ExportGuard(unsafe { long_export.assume_init() }); - let long_export_ref = &export.0; + let long_export_ref = &export_guard.0; let negative = long_export_ref.negative != 0; let value = long_export_ref.value; - let result = if long_export_ref.digits.is_null() { + if long_export_ref.digits.is_null() { f(negative, value, None) } else { let n_digits = long_export_ref.ndigits as usize; let ptr = long_export_ref.digits.cast::(); let digits = unsafe { std::slice::from_raw_parts(ptr, n_digits) }; f(negative, value, Some(digits)) - }; - unsafe { ffi::PyLong_FreeExport(long_export.as_mut_ptr()) }; - result + } } #[cfg(not(Py_LIMITED_API))] From bd7af65ffb45a33197398af39d252aa033291a80 Mon Sep 17 00:00:00 2001 From: chiri Date: Sun, 10 May 2026 21:12:34 +0300 Subject: [PATCH 37/44] fmt --- src/conversions/std/num.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/conversions/std/num.rs b/src/conversions/std/num.rs index 0369298dcb6..f8780f83825 100644 --- a/src/conversions/std/num.rs +++ b/src/conversions/std/num.rs @@ -229,7 +229,7 @@ impl<'py> IntoPyObject<'py> for &'_ u8 { _: crate::conversion::private::Token, ) -> Result, PyErr> where - // I: AsRef<[u8]>, but the compiler needs it expressed via the trait for some reason + // I: AsRef<[u8]>, but the compiler needs it expressed via the trait for some reason I: AsRef<[::BaseType]>, { Ok(PyBytes::new(py, iter.as_ref()).into_any()) From 23af4f104af52195ffbb63d833db80550b314464 Mon Sep 17 00:00:00 2001 From: chiri Date: Sun, 10 May 2026 21:19:19 +0300 Subject: [PATCH 38/44] review --- src/conversions/std/num.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/conversions/std/num.rs b/src/conversions/std/num.rs index f8780f83825..a16b1203fca 100644 --- a/src/conversions/std/num.rs +++ b/src/conversions/std/num.rs @@ -462,12 +462,12 @@ mod fast_128bit_int_conversion { { if is_30bit_layout() { const DIGIT_MASK: u32 = (1 << PYLONG_BITS_IN_DIGIT) - 1; - let value = self as u128; - let negative = $is_signed && (self as i128) < 0; + let signed = self as i128; + let negative = $is_signed && signed < 0; let abs = if negative { - value.wrapping_neg() + signed.unsigned_abs() } else { - value + self as u128 }; let bits = 128 - abs.leading_zeros() as usize; let n_digits = bits.div_ceil(PYLONG_BITS_IN_DIGIT).max(1); From a3041cf7c6025c291399df474b3b25cb3d464090 Mon Sep 17 00:00:00 2001 From: chiri Date: Mon, 11 May 2026 11:53:16 +0300 Subject: [PATCH 39/44] review --- pyo3-ffi/src/longobject.rs | 3 ++- src/conversions/std/num.rs | 10 +++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/pyo3-ffi/src/longobject.rs b/pyo3-ffi/src/longobject.rs index a2f7dc16a05..c209a27165c 100644 --- a/pyo3-ffi/src/longobject.rs +++ b/pyo3-ffi/src/longobject.rs @@ -45,7 +45,8 @@ extern_libpython! { 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 + + // skipped non-limited PyLong_AsInt #[cfg(Py_3_14)] pub fn PyLong_FromInt32(arg1: i32) -> *mut PyObject; diff --git a/src/conversions/std/num.rs b/src/conversions/std/num.rs index a16b1203fca..f6c82227dc8 100644 --- a/src/conversions/std/num.rs +++ b/src/conversions/std/num.rs @@ -522,6 +522,11 @@ mod fast_128bit_int_conversion { return pylong_visit_digits( num.as_any().as_borrowed(), |negative, compact, digits| { + if !$is_signed && negative { + return Err(exceptions::PyOverflowError::new_err( + "can't convert negative int to unsigned", + )); + } let Some(digits) = digits else { return <$rust_type>::try_from(compact) .map_err(|_| overflow()); @@ -540,11 +545,6 @@ mod fast_128bit_int_conversion { return Err(overflow()); } if !$is_signed { - if negative { - return Err(exceptions::PyOverflowError::new_err( - "can't convert negative int to unsigned", - )); - } return <$rust_type>::try_from(abs).map_err(|_| overflow()); } let signed = if negative { From ac81af8ac44bebca68a3795763a113263f236b15 Mon Sep 17 00:00:00 2001 From: chiri Date: Tue, 12 May 2026 17:50:44 +0300 Subject: [PATCH 40/44] std -> core --- pyo3-ffi/src/cpython/longobject.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyo3-ffi/src/cpython/longobject.rs b/pyo3-ffi/src/cpython/longobject.rs index 999279ae430..88aea6d0d27 100644 --- a/pyo3-ffi/src/cpython/longobject.rs +++ b/pyo3-ffi/src/cpython/longobject.rs @@ -1,7 +1,6 @@ use crate::{longobject::*, object::*}; +use core::ffi::{c_int, c_uchar}; use libc::size_t; -use std::ffi::{c_int, c_uchar}; - // skipped _PyLong_CAST From 70d3985977c813516db360fbb7c6a7a62ed56900 Mon Sep 17 00:00:00 2001 From: chiri Date: Tue, 12 May 2026 18:03:24 +0300 Subject: [PATCH 41/44] std -> core (x2) --- pyo3-ffi/src/cpython/longintrepr.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyo3-ffi/src/cpython/longintrepr.rs b/pyo3-ffi/src/cpython/longintrepr.rs index be957c6d297..7ae2f783e00 100644 --- a/pyo3-ffi/src/cpython/longintrepr.rs +++ b/pyo3-ffi/src/cpython/longintrepr.rs @@ -1,5 +1,5 @@ use crate::{PyObject, Py_ssize_t}; -use std::ffi::{c_int, c_void}; +use core::ffi::{c_int, c_void}; use crate::Py_uintptr_t; From 3fdcb06a64c217e12728a0592155b5535162acf7 Mon Sep 17 00:00:00 2001 From: chiri Date: Tue, 12 May 2026 18:29:59 +0300 Subject: [PATCH 42/44] review --- src/conversions/std/num.rs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/conversions/std/num.rs b/src/conversions/std/num.rs index f6c82227dc8..e6968661a74 100644 --- a/src/conversions/std/num.rs +++ b/src/conversions/std/num.rs @@ -394,22 +394,20 @@ pub(crate) fn pylong_from_digits<'py, I: ExactSizeIterator>( py: Python<'py>, negative: bool, digits: I, -) -> PyResult> { +) -> Bound<'py, PyInt> { let digits_len = digits.len(); let mut ptr = std::ptr::null_mut(); let writer = unsafe { ffi::PyLongWriter_Create(negative.into(), digits_len as ffi::Py_ssize_t, &mut ptr) }; - if writer.is_null() { - return Err(PyErr::fetch(py)); - } + assert!(!writer.is_null(), "PyLongWriter_Create returned NULL"); let digit_ptr = ptr.cast::(); for (i, d) in digits.enumerate() { unsafe { digit_ptr.add(i).write(d) }; } unsafe { ffi::PyLongWriter_Finish(writer) - .assume_owned_or_err(py) + .assume_owned(py) .cast_into_unchecked() } } @@ -452,7 +450,7 @@ mod fast_128bit_int_conversion { impl<'py> IntoPyObject<'py> for $rust_type { type Target = PyInt; type Output = Bound<'py, Self::Target>; - type Error = PyErr; + type Error = Infallible; #[cfg(feature = "experimental-inspect")] const OUTPUT_TYPE: PyStaticExpr = PyInt::TYPE_HINT; @@ -473,7 +471,7 @@ mod fast_128bit_int_conversion { let n_digits = bits.div_ceil(PYLONG_BITS_IN_DIGIT).max(1); let digits = (0..n_digits) .map(|i| (abs >> (i * PYLONG_BITS_IN_DIGIT)) as u32 & DIGIT_MASK); - return pylong_from_digits(py, negative, digits); + return Ok(pylong_from_digits(py, negative, digits)); } } #[cfg(Py_3_13)] @@ -492,7 +490,7 @@ mod fast_128bit_int_conversion { impl<'py> IntoPyObject<'py> for &$rust_type { type Target = PyInt; type Output = Bound<'py, Self::Target>; - type Error = PyErr; + type Error = Infallible; #[cfg(feature = "experimental-inspect")] const OUTPUT_TYPE: PyStaticExpr = <$rust_type>::OUTPUT_TYPE; @@ -744,7 +742,7 @@ macro_rules! nonzero_int_impl { impl<'py> IntoPyObject<'py> for $nonzero_type { type Target = PyInt; type Output = Bound<'py, Self::Target>; - type Error = <$primitive_type as IntoPyObject<'py>>::Error; + type Error = Infallible; #[cfg(feature = "experimental-inspect")] const OUTPUT_TYPE: PyStaticExpr = PyInt::TYPE_HINT; @@ -758,7 +756,7 @@ macro_rules! nonzero_int_impl { impl<'py> IntoPyObject<'py> for &$nonzero_type { type Target = PyInt; type Output = Bound<'py, Self::Target>; - type Error = <$nonzero_type as IntoPyObject<'py>>::Error; + type Error = Infallible; #[cfg(feature = "experimental-inspect")] const OUTPUT_TYPE: PyStaticExpr = <$nonzero_type>::OUTPUT_TYPE; From 2c6528abe6acac5b3ab7ffa16d2ebc78c1e94ead Mon Sep 17 00:00:00 2001 From: chiri Date: Tue, 12 May 2026 18:32:06 +0300 Subject: [PATCH 43/44] review (x2) --- newsfragments/6016.changed.md | 1 - 1 file changed, 1 deletion(-) delete mode 100644 newsfragments/6016.changed.md diff --git a/newsfragments/6016.changed.md b/newsfragments/6016.changed.md deleted file mode 100644 index 808c5e44b97..00000000000 --- a/newsfragments/6016.changed.md +++ /dev/null @@ -1 +0,0 @@ -Changed `IntoPyObject` for `i128`, `u128`, and references to them to return `PyErr`, allowing Python integer creation failures to be propagated. \ No newline at end of file From c2f8c9a64a33ee15ed49a8fce3d66ac137858172 Mon Sep 17 00:00:00 2001 From: chiri Date: Wed, 13 May 2026 00:29:57 +0300 Subject: [PATCH 44/44] Update pyo3-ffi/src/cpython/longintrepr.rs Co-authored-by: David Hewitt --- pyo3-ffi/src/cpython/longintrepr.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyo3-ffi/src/cpython/longintrepr.rs b/pyo3-ffi/src/cpython/longintrepr.rs index 7ae2f783e00..427f067178c 100644 --- a/pyo3-ffi/src/cpython/longintrepr.rs +++ b/pyo3-ffi/src/cpython/longintrepr.rs @@ -32,7 +32,7 @@ pub struct PyLongExport { pub negative: u8, pub ndigits: Py_ssize_t, pub digits: *const c_void, - pub _reserved: Py_uintptr_t, + _reserved: Py_uintptr_t, } extern_libpython! {