Skip to content

Commit 52dc139

Browse files
authored
use "fastcall" convention on abi3, if >3.10 (#4415)
* use "fastcall" convention on abi3, if >3.10 * improve coverage * coverage, clippy * use apis available on all versions
1 parent 6087a15 commit 52dc139

File tree

12 files changed

+89
-35
lines changed

12 files changed

+89
-35
lines changed

examples/string-sum/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ static mut METHODS: &[PyMethodDef] = &[
1919
PyMethodDef {
2020
ml_name: c_str!("sum_as_string").as_ptr(),
2121
ml_meth: PyMethodDefPointer {
22-
_PyCFunctionFast: sum_as_string,
22+
PyCFunctionFast: sum_as_string,
2323
},
2424
ml_flags: METH_FASTCALL,
2525
ml_doc: c_str!("returns the sum of two integers as a string").as_ptr(),

newsfragments/4415.added.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add FFI definitions `PyCFunctionFast` and `PyCFunctionFastWithKeywords`

newsfragments/4415.changed.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Use "fastcall" Python calling convention for `#[pyfunction]`s when compiling on abi3 for Python 3.10 and up.

pyo3-ffi/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ static mut METHODS: [PyMethodDef; 2] = [
6565
PyMethodDef {
6666
ml_name: c_str!("sum_as_string").as_ptr(),
6767
ml_meth: PyMethodDefPointer {
68-
_PyCFunctionFast: sum_as_string,
68+
PyCFunctionFast: sum_as_string,
6969
},
7070
ml_flags: METH_FASTCALL,
7171
ml_doc: c_str!("returns the sum of two integers as a string").as_ptr(),

pyo3-ffi/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@
103103
//! PyMethodDef {
104104
//! ml_name: c_str!("sum_as_string").as_ptr(),
105105
//! ml_meth: PyMethodDefPointer {
106-
//! _PyCFunctionFast: sum_as_string,
106+
//! PyCFunctionFast: sum_as_string,
107107
//! },
108108
//! ml_flags: METH_FASTCALL,
109109
//! ml_doc: c_str!("returns the sum of two integers as a string").as_ptr(),

pyo3-ffi/src/methodobject.rs

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,26 +43,34 @@ pub type PyCFunction =
4343
unsafe extern "C" fn(slf: *mut PyObject, args: *mut PyObject) -> *mut PyObject;
4444

4545
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
46-
pub type _PyCFunctionFast = unsafe extern "C" fn(
46+
pub type PyCFunctionFast = unsafe extern "C" fn(
4747
slf: *mut PyObject,
4848
args: *mut *mut PyObject,
4949
nargs: crate::pyport::Py_ssize_t,
5050
) -> *mut PyObject;
5151

52+
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
53+
#[deprecated(note = "renamed to `PyCFunctionFast`")]
54+
pub type _PyCFunctionFast = PyCFunctionFast;
55+
5256
pub type PyCFunctionWithKeywords = unsafe extern "C" fn(
5357
slf: *mut PyObject,
5458
args: *mut PyObject,
5559
kwds: *mut PyObject,
5660
) -> *mut PyObject;
5761

58-
#[cfg(not(Py_LIMITED_API))]
59-
pub type _PyCFunctionFastWithKeywords = unsafe extern "C" fn(
62+
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
63+
pub type PyCFunctionFastWithKeywords = unsafe extern "C" fn(
6064
slf: *mut PyObject,
6165
args: *const *mut PyObject,
6266
nargs: crate::pyport::Py_ssize_t,
6367
kwnames: *mut PyObject,
6468
) -> *mut PyObject;
6569

70+
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
71+
#[deprecated(note = "renamed to `PyCFunctionFastWithKeywords`")]
72+
pub type _PyCFunctionFastWithKeywords = PyCFunctionFastWithKeywords;
73+
6674
#[cfg(all(Py_3_9, not(Py_LIMITED_API)))]
6775
pub type PyCMethod = unsafe extern "C" fn(
6876
slf: *mut PyObject,
@@ -144,11 +152,21 @@ pub union PyMethodDefPointer {
144152

145153
/// This variant corresponds with [`METH_FASTCALL`].
146154
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
147-
pub _PyCFunctionFast: _PyCFunctionFast,
155+
#[deprecated(note = "renamed to `PyCFunctionFast`")]
156+
pub _PyCFunctionFast: PyCFunctionFast,
157+
158+
/// This variant corresponds with [`METH_FASTCALL`].
159+
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
160+
pub PyCFunctionFast: PyCFunctionFast,
161+
162+
/// This variant corresponds with [`METH_FASTCALL`] | [`METH_KEYWORDS`].
163+
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
164+
#[deprecated(note = "renamed to `PyCFunctionFastWithKeywords`")]
165+
pub _PyCFunctionFastWithKeywords: PyCFunctionFastWithKeywords,
148166

149167
/// This variant corresponds with [`METH_FASTCALL`] | [`METH_KEYWORDS`].
150-
#[cfg(not(Py_LIMITED_API))]
151-
pub _PyCFunctionFastWithKeywords: _PyCFunctionFastWithKeywords,
168+
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
169+
pub PyCFunctionFastWithKeywords: PyCFunctionFastWithKeywords,
152170

153171
/// This variant corresponds with [`METH_METHOD`] | [`METH_FASTCALL`] | [`METH_KEYWORDS`].
154172
#[cfg(all(Py_3_9, not(Py_LIMITED_API)))]

pyo3-macros-backend/src/method.rs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use quote::{format_ident, quote, quote_spanned, ToTokens};
77
use syn::{ext::IdentExt, spanned::Spanned, Ident, Result};
88

99
use crate::deprecations::deprecate_trailing_option_default;
10+
use crate::pyversions::is_abi3_before;
1011
use crate::utils::{Ctx, LitCStr};
1112
use crate::{
1213
attributes::{FromPyWithAttribute, TextSignatureAttribute, TextSignatureAttributeValue},
@@ -15,7 +16,7 @@ use crate::{
1516
FunctionSignature, PyFunctionArgPyO3Attributes, PyFunctionOptions, SignatureAttribute,
1617
},
1718
quotes,
18-
utils::{self, is_abi3, PythonDoc},
19+
utils::{self, PythonDoc},
1920
};
2021

2122
#[derive(Clone, Debug)]
@@ -374,7 +375,7 @@ impl SelfType {
374375
pub enum CallingConvention {
375376
Noargs, // METH_NOARGS
376377
Varargs, // METH_VARARGS | METH_KEYWORDS
377-
Fastcall, // METH_FASTCALL | METH_KEYWORDS (not compatible with `abi3` feature)
378+
Fastcall, // METH_FASTCALL | METH_KEYWORDS (not compatible with `abi3` feature before 3.10)
378379
TpNew, // special convention for tp_new
379380
}
380381

@@ -386,11 +387,11 @@ impl CallingConvention {
386387
pub fn from_signature(signature: &FunctionSignature<'_>) -> Self {
387388
if signature.python_signature.has_no_args() {
388389
Self::Noargs
389-
} else if signature.python_signature.kwargs.is_some() {
390-
// for functions that accept **kwargs, always prefer varargs
391-
Self::Varargs
392-
} else if !is_abi3() {
393-
// FIXME: available in the stable ABI since 3.10
390+
} else if signature.python_signature.kwargs.is_none() && !is_abi3_before(3, 10) {
391+
// For functions that accept **kwargs, always prefer varargs for now based on
392+
// historical performance testing.
393+
//
394+
// FASTCALL not compatible with `abi3` before 3.10
394395
Self::Fastcall
395396
} else {
396397
Self::Varargs

pyo3-macros-backend/src/pyclass.rs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,8 @@ use crate::pymethod::{
2222
impl_py_getter_def, impl_py_setter_def, MethodAndMethodDef, MethodAndSlotDef, PropertyType,
2323
SlotDef, __GETITEM__, __HASH__, __INT__, __LEN__, __REPR__, __RICHCMP__, __STR__,
2424
};
25-
use crate::pyversions;
26-
use crate::utils::{self, apply_renaming_rule, LitCStr, PythonDoc};
27-
use crate::utils::{is_abi3, Ctx};
25+
use crate::pyversions::is_abi3_before;
26+
use crate::utils::{self, apply_renaming_rule, Ctx, LitCStr, PythonDoc};
2827
use crate::PyFunctionOptions;
2928

3029
/// If the class is derived from a Rust `struct` or `enum`.
@@ -186,13 +185,11 @@ impl PyClassPyO3Options {
186185
};
187186
}
188187

189-
let python_version = pyo3_build_config::get().version;
190-
191188
match option {
192189
PyClassPyO3Option::Crate(krate) => set_option!(krate),
193190
PyClassPyO3Option::Dict(dict) => {
194191
ensure_spanned!(
195-
python_version >= pyversions::PY_3_9 || !is_abi3(),
192+
!is_abi3_before(3, 9),
196193
dict.span() => "`dict` requires Python >= 3.9 when using the `abi3` feature"
197194
);
198195
set_option!(dict);
@@ -216,7 +213,7 @@ impl PyClassPyO3Options {
216213
PyClassPyO3Option::Unsendable(unsendable) => set_option!(unsendable),
217214
PyClassPyO3Option::Weakref(weakref) => {
218215
ensure_spanned!(
219-
python_version >= pyversions::PY_3_9 || !is_abi3(),
216+
!is_abi3_before(3, 9),
220217
weakref.span() => "`weakref` requires Python >= 3.9 when using the `abi3` feature"
221218
);
222219
set_option!(weakref);
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
use pyo3_build_config::PythonVersion;
22

3-
pub const PY_3_9: PythonVersion = PythonVersion { major: 3, minor: 9 };
3+
pub fn is_abi3_before(major: u8, minor: u8) -> bool {
4+
let config = pyo3_build_config::get();
5+
config.abi3 && config.version < PythonVersion { major, minor }
6+
}

pyo3-macros-backend/src/utils.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -288,10 +288,6 @@ pub fn apply_renaming_rule(rule: RenamingRule, name: &str) -> String {
288288
}
289289
}
290290

291-
pub(crate) fn is_abi3() -> bool {
292-
pyo3_build_config::get().abi3
293-
}
294-
295291
pub(crate) enum IdentOrStr<'a> {
296292
Str(&'a str),
297293
Ident(syn::Ident),

0 commit comments

Comments
 (0)