Skip to content

Commit 4d848ad

Browse files
committed
support 3.13
1 parent b982fee commit 4d848ad

File tree

8 files changed

+250
-85
lines changed

8 files changed

+250
-85
lines changed

.github/workflows/build.yml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,3 @@ jobs:
147147
RUST_BACKTRACE: 1
148148
RUSTFLAGS: "-D warnings"
149149
RUSTDOCFLAGS: "-D warnings"
150-
# FIXME this is a temporary hack for testing 3.13 prereleases, probably we should have a flag
151-
# PYO3_ALLOW_3_13_PRERELEASES or similar so that users don't need to run this flag in their CI
152-
UNSAFE_PYO3_SKIP_VERSION_CHECK: "1"

newsfragments/4184.packaging.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Support Python 3.13.

pyo3-ffi/build.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const SUPPORTED_VERSIONS_CPYTHON: SupportedVersions = SupportedVersions {
1717
min: PythonVersion { major: 3, minor: 7 },
1818
max: PythonVersion {
1919
major: 3,
20-
minor: 12,
20+
minor: 13,
2121
},
2222
};
2323

pyo3-ffi/src/cpython/longobject.rs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
use crate::longobject::*;
2+
use crate::object::*;
3+
#[cfg(Py_3_13)]
4+
use crate::pyport::Py_ssize_t;
5+
use libc::size_t;
6+
#[cfg(Py_3_13)]
7+
use std::os::raw::c_void;
8+
use std::os::raw::{c_int, c_uchar};
9+
10+
#[cfg(Py_3_13)]
11+
extern "C" {
12+
pub fn PyLong_FromUnicodeObject(u: *mut PyObject, base: c_int) -> *mut PyObject;
13+
}
14+
15+
#[cfg(Py_3_13)]
16+
pub const Py_ASNATIVEBYTES_DEFAULTS: c_int = -1;
17+
#[cfg(Py_3_13)]
18+
pub const Py_ASNATIVEBYTES_BIG_ENDIAN: c_int = 0;
19+
#[cfg(Py_3_13)]
20+
pub const Py_ASNATIVEBYTES_LITTLE_ENDIAN: c_int = 1;
21+
#[cfg(Py_3_13)]
22+
pub const Py_ASNATIVEBYTES_NATIVE_ENDIAN: c_int = 3;
23+
#[cfg(Py_3_13)]
24+
pub const Py_ASNATIVEBYTES_UNSIGNED_BUFFER: c_int = 4;
25+
#[cfg(Py_3_13)]
26+
pub const Py_ASNATIVEBYTES_REJECT_NEGATIVE: c_int = 8;
27+
28+
extern "C" {
29+
// skipped _PyLong_Sign
30+
31+
#[cfg(Py_3_13)]
32+
pub fn PyLong_AsNativeBytes(
33+
v: *mut PyObject,
34+
buffer: *mut c_void,
35+
n_bytes: Py_ssize_t,
36+
flags: c_int,
37+
) -> Py_ssize_t;
38+
39+
#[cfg(Py_3_13)]
40+
pub fn PyLong_FromNativeBytes(
41+
buffer: *const c_void,
42+
n_bytes: size_t,
43+
flags: c_int,
44+
) -> *mut PyObject;
45+
46+
#[cfg(Py_3_13)]
47+
pub fn PyLong_FromUnsignedNativeBytes(
48+
buffer: *const c_void,
49+
n_bytes: size_t,
50+
flags: c_int,
51+
) -> *mut PyObject;
52+
53+
// skipped PyUnstable_Long_IsCompact
54+
// skipped PyUnstable_Long_CompactValue
55+
56+
#[cfg_attr(PyPy, link_name = "_PyPyLong_FromByteArray")]
57+
pub fn _PyLong_FromByteArray(
58+
bytes: *const c_uchar,
59+
n: size_t,
60+
little_endian: c_int,
61+
is_signed: c_int,
62+
) -> *mut PyObject;
63+
64+
#[cfg_attr(PyPy, link_name = "_PyPyLong_AsByteArrayO")]
65+
pub fn _PyLong_AsByteArray(
66+
v: *mut PyLongObject,
67+
bytes: *mut c_uchar,
68+
n: size_t,
69+
little_endian: c_int,
70+
is_signed: c_int,
71+
) -> c_int;
72+
73+
// skipped _PyLong_GCD
74+
}

pyo3-ffi/src/cpython/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ pub(crate) mod import;
1818
pub(crate) mod initconfig;
1919
// skipped interpreteridobject.h
2020
pub(crate) mod listobject;
21+
pub(crate) mod longobject;
2122
#[cfg(all(Py_3_9, not(PyPy)))]
2223
pub(crate) mod methodobject;
2324
pub(crate) mod object;
@@ -53,6 +54,7 @@ pub use self::import::*;
5354
#[cfg(all(Py_3_8, not(PyPy)))]
5455
pub use self::initconfig::*;
5556
pub use self::listobject::*;
57+
pub use self::longobject::*;
5658
#[cfg(all(Py_3_9, not(PyPy)))]
5759
pub use self::methodobject::*;
5860
pub use self::object::*;

pyo3-ffi/src/longobject.rs

Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
use crate::object::*;
22
use crate::pyport::Py_ssize_t;
33
use libc::size_t;
4-
#[cfg(not(Py_LIMITED_API))]
5-
use std::os::raw::c_uchar;
64
use std::os::raw::{c_char, c_double, c_int, c_long, c_longlong, c_ulong, c_ulonglong, c_void};
75
use std::ptr::addr_of_mut;
86

@@ -90,34 +88,12 @@ extern "C" {
9088
arg3: c_int,
9189
) -> *mut PyObject;
9290
}
93-
// skipped non-limited PyLong_FromUnicodeObject
94-
// skipped non-limited _PyLong_FromBytes
9591

9692
#[cfg(not(Py_LIMITED_API))]
9793
extern "C" {
98-
// skipped non-limited _PyLong_Sign
99-
10094
#[cfg_attr(PyPy, link_name = "_PyPyLong_NumBits")]
95+
#[cfg(not(Py_3_13))]
10196
pub fn _PyLong_NumBits(obj: *mut PyObject) -> size_t;
102-
103-
// skipped _PyLong_DivmodNear
104-
105-
#[cfg_attr(PyPy, link_name = "_PyPyLong_FromByteArray")]
106-
pub fn _PyLong_FromByteArray(
107-
bytes: *const c_uchar,
108-
n: size_t,
109-
little_endian: c_int,
110-
is_signed: c_int,
111-
) -> *mut PyObject;
112-
113-
#[cfg_attr(PyPy, link_name = "_PyPyLong_AsByteArrayO")]
114-
pub fn _PyLong_AsByteArray(
115-
v: *mut PyLongObject,
116-
bytes: *mut c_uchar,
117-
n: size_t,
118-
little_endian: c_int,
119-
is_signed: c_int,
120-
) -> c_int;
12197
}
12298

12399
// skipped non-limited _PyLong_Format
@@ -130,6 +106,5 @@ extern "C" {
130106
pub fn PyOS_strtol(arg1: *const c_char, arg2: *mut *mut c_char, arg3: c_int) -> c_long;
131107
}
132108

133-
// skipped non-limited _PyLong_GCD
134109
// skipped non-limited _PyLong_Rshift
135110
// skipped non-limited _PyLong_Lshift

src/conversions/num_bigint.rs

Lines changed: 98 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
//! assert n + 1 == value
4848
//! ```
4949
50+
use crate::ffi_ptr_ext::FfiPtrExt;
5051
#[cfg(Py_LIMITED_API)]
5152
use crate::types::{bytes::PyBytesMethods, PyBytes};
5253
use crate::{
@@ -63,20 +64,47 @@ use num_bigint::Sign;
6364

6465
// for identical functionality between BigInt and BigUint
6566
macro_rules! bigint_conversion {
66-
($rust_ty: ty, $is_signed: expr, $to_bytes: path) => {
67+
($rust_ty: ty, $is_signed: literal, $to_bytes: path) => {
6768
#[cfg_attr(docsrs, doc(cfg(feature = "num-bigint")))]
6869
impl ToPyObject for $rust_ty {
6970
#[cfg(not(Py_LIMITED_API))]
7071
fn to_object(&self, py: Python<'_>) -> PyObject {
7172
let bytes = $to_bytes(self);
72-
unsafe {
73-
let obj = ffi::_PyLong_FromByteArray(
74-
bytes.as_ptr().cast(),
75-
bytes.len(),
76-
1,
77-
$is_signed,
78-
);
79-
PyObject::from_owned_ptr(py, obj)
73+
#[cfg(not(Py_3_13))]
74+
{
75+
unsafe {
76+
ffi::_PyLong_FromByteArray(
77+
bytes.as_ptr().cast(),
78+
bytes.len(),
79+
1,
80+
$is_signed.into(),
81+
)
82+
.assume_owned(py)
83+
.unbind()
84+
}
85+
}
86+
#[cfg(Py_3_13)]
87+
{
88+
if $is_signed {
89+
unsafe {
90+
ffi::PyLong_FromNativeBytes(
91+
bytes.as_ptr().cast(),
92+
bytes.len(),
93+
ffi::Py_ASNATIVEBYTES_LITTLE_ENDIAN,
94+
)
95+
.assume_owned(py)
96+
}
97+
} else {
98+
unsafe {
99+
ffi::PyLong_FromUnsignedNativeBytes(
100+
bytes.as_ptr().cast(),
101+
bytes.len(),
102+
ffi::Py_ASNATIVEBYTES_LITTLE_ENDIAN,
103+
)
104+
.assume_owned(py)
105+
}
106+
}
107+
.unbind()
80108
}
81109
}
82110

@@ -107,8 +135,8 @@ macro_rules! bigint_conversion {
107135
};
108136
}
109137

110-
bigint_conversion!(BigUint, 0, BigUint::to_bytes_le);
111-
bigint_conversion!(BigInt, 1, BigInt::to_signed_bytes_le);
138+
bigint_conversion!(BigUint, false, BigUint::to_bytes_le);
139+
bigint_conversion!(BigInt, true, BigInt::to_signed_bytes_le);
112140

113141
#[cfg_attr(docsrs, doc(cfg(feature = "num-bigint")))]
114142
impl<'py> FromPyObject<'py> for BigInt {
@@ -122,13 +150,9 @@ impl<'py> FromPyObject<'py> for BigInt {
122150
num_owned = unsafe { Py::from_owned_ptr_or_err(py, ffi::PyNumber_Index(ob.as_ptr()))? };
123151
num_owned.bind(py)
124152
};
125-
let n_bits = int_n_bits(num)?;
126-
if n_bits == 0 {
127-
return Ok(BigInt::from(0isize));
128-
}
129153
#[cfg(not(Py_LIMITED_API))]
130154
{
131-
let mut buffer = int_to_u32_vec(num, (n_bits + 32) / 32, true)?;
155+
let mut buffer = int_to_u32_vec::<true>(num)?;
132156
let sign = if buffer.last().copied().map_or(false, |last| last >> 31 != 0) {
133157
// BigInt::new takes an unsigned array, so need to convert from two's complement
134158
// flip all bits, 'subtract' 1 (by adding one to the unsigned array)
@@ -152,6 +176,10 @@ impl<'py> FromPyObject<'py> for BigInt {
152176
}
153177
#[cfg(Py_LIMITED_API)]
154178
{
179+
let n_bits = int_n_bits(num)?;
180+
if n_bits == 0 {
181+
return Ok(BigInt::from(0isize));
182+
}
155183
let bytes = int_to_py_bytes(num, (n_bits + 8) / 8, true)?;
156184
Ok(BigInt::from_signed_bytes_le(bytes.as_bytes()))
157185
}
@@ -170,31 +198,37 @@ impl<'py> FromPyObject<'py> for BigUint {
170198
num_owned = unsafe { Py::from_owned_ptr_or_err(py, ffi::PyNumber_Index(ob.as_ptr()))? };
171199
num_owned.bind(py)
172200
};
173-
let n_bits = int_n_bits(num)?;
174-
if n_bits == 0 {
175-
return Ok(BigUint::from(0usize));
176-
}
177201
#[cfg(not(Py_LIMITED_API))]
178202
{
179-
let buffer = int_to_u32_vec(num, (n_bits + 31) / 32, false)?;
203+
let buffer = int_to_u32_vec::<false>(num)?;
180204
Ok(BigUint::new(buffer))
181205
}
182206
#[cfg(Py_LIMITED_API)]
183207
{
208+
let n_bits = int_n_bits(num)?;
209+
if n_bits == 0 {
210+
return Ok(BigUint::from(0usize));
211+
}
184212
let bytes = int_to_py_bytes(num, (n_bits + 7) / 8, false)?;
185213
Ok(BigUint::from_bytes_le(bytes.as_bytes()))
186214
}
187215
}
188216
}
189217

190-
#[cfg(not(Py_LIMITED_API))]
218+
#[cfg(not(any(Py_LIMITED_API, Py_3_13)))]
191219
#[inline]
192-
fn int_to_u32_vec(
193-
long: &Bound<'_, PyLong>,
194-
n_digits: usize,
195-
is_signed: bool,
196-
) -> PyResult<Vec<u32>> {
197-
let mut buffer = Vec::with_capacity(n_digits);
220+
fn int_to_u32_vec<const SIGNED: bool>(long: &Bound<'_, PyLong>) -> PyResult<Vec<u32>> {
221+
let mut buffer = Vec::new();
222+
let n_bits = int_n_bits(long)?;
223+
if n_bits == 0 {
224+
return Ok(buffer);
225+
}
226+
let n_digits = if SIGNED {
227+
(n_bits + 32) / 32
228+
} else {
229+
(n_bits + 31) / 32
230+
};
231+
buffer.reserve_exact(n_digits);
198232
unsafe {
199233
crate::err::error_on_minusone(
200234
long.py(),
@@ -203,7 +237,7 @@ fn int_to_u32_vec(
203237
buffer.as_mut_ptr() as *mut u8,
204238
n_digits * 4,
205239
1,
206-
is_signed.into(),
240+
SIGNED.into(),
207241
),
208242
)?;
209243
buffer.set_len(n_digits)
@@ -215,6 +249,40 @@ fn int_to_u32_vec(
215249
Ok(buffer)
216250
}
217251

252+
#[cfg(all(not(Py_LIMITED_API), Py_3_13))]
253+
#[inline]
254+
fn int_to_u32_vec<const SIGNED: bool>(long: &Bound<'_, PyLong>) -> PyResult<Vec<u32>> {
255+
let mut buffer = Vec::new();
256+
let mut flags = ffi::Py_ASNATIVEBYTES_LITTLE_ENDIAN;
257+
if !SIGNED {
258+
flags |= ffi::Py_ASNATIVEBYTES_UNSIGNED_BUFFER | ffi::Py_ASNATIVEBYTES_REJECT_NEGATIVE;
259+
}
260+
let n_bytes =
261+
unsafe { ffi::PyLong_AsNativeBytes(long.as_ptr().cast(), std::ptr::null_mut(), 0, flags) };
262+
let n_bytes_unsigned: usize = n_bytes
263+
.try_into()
264+
.map_err(|_| crate::PyErr::fetch(long.py()))?;
265+
if n_bytes == 0 {
266+
return Ok(buffer);
267+
}
268+
let n_digits = n_bytes_unsigned.div_ceil(4);
269+
buffer.reserve_exact(n_digits);
270+
unsafe {
271+
ffi::PyLong_AsNativeBytes(
272+
long.as_ptr().cast(),
273+
buffer.as_mut_ptr().cast(),
274+
(n_digits * 4).try_into().unwrap(),
275+
flags,
276+
);
277+
buffer.set_len(n_digits);
278+
};
279+
buffer
280+
.iter_mut()
281+
.for_each(|chunk| *chunk = u32::from_le(*chunk));
282+
283+
Ok(buffer)
284+
}
285+
218286
#[cfg(Py_LIMITED_API)]
219287
fn int_to_py_bytes<'py>(
220288
long: &Bound<'py, PyLong>,
@@ -239,6 +307,7 @@ fn int_to_py_bytes<'py>(
239307
}
240308

241309
#[inline]
310+
#[cfg(any(not(Py_3_13), Py_LIMITED_API))]
242311
fn int_n_bits(long: &Bound<'_, PyLong>) -> PyResult<usize> {
243312
let py = long.py();
244313
#[cfg(not(Py_LIMITED_API))]

0 commit comments

Comments
 (0)