Skip to content

Commit

Permalink
use "fastcall" convention on abi3, if >3.10
Browse files Browse the repository at this point in the history
  • Loading branch information
davidhewitt committed Aug 2, 2024
1 parent 30dfa23 commit bd1266b
Show file tree
Hide file tree
Showing 10 changed files with 48 additions and 29 deletions.
1 change: 1 addition & 0 deletions newsfragments/4415.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add FFI definitions `PyCFunctionFast` and `PyCFunctionFastWithKeywords`
1 change: 1 addition & 0 deletions newsfragments/4415.changed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Use "fastcall" Python calling convention for `#[pyfunction]`s when compiling on abi3 for Python 3.10 and up.
2 changes: 1 addition & 1 deletion pyo3-ffi/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ static mut METHODS: [PyMethodDef; 2] = [
PyMethodDef {
ml_name: c_str!("sum_as_string").as_ptr(),
ml_meth: PyMethodDefPointer {
_PyCFunctionFast: sum_as_string,
PyCFunctionFast: sum_as_string,
},
ml_flags: METH_FASTCALL,
ml_doc: c_str!("returns the sum of two integers as a string").as_ptr(),
Expand Down
30 changes: 24 additions & 6 deletions pyo3-ffi/src/methodobject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,26 +43,34 @@ pub type PyCFunction =
unsafe extern "C" fn(slf: *mut PyObject, args: *mut PyObject) -> *mut PyObject;

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

#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
#[deprecated(note = "renamed to `PyCFunctionFast`")]
pub type _PyCFunctionFast = PyCFunctionFast;

pub type PyCFunctionWithKeywords = unsafe extern "C" fn(
slf: *mut PyObject,
args: *mut PyObject,
kwds: *mut PyObject,
) -> *mut PyObject;

#[cfg(not(Py_LIMITED_API))]
pub type _PyCFunctionFastWithKeywords = unsafe extern "C" fn(
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
pub type PyCFunctionFastWithKeywords = unsafe extern "C" fn(
slf: *mut PyObject,
args: *const *mut PyObject,
nargs: crate::pyport::Py_ssize_t,
kwnames: *mut PyObject,
) -> *mut PyObject;

#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
#[deprecated(note = "renamed to `PyCFunctionFastWithKeywords`")]
pub type _PyCFunctionFastWithKeywords = PyCFunctionFastWithKeywords;

#[cfg(all(Py_3_9, not(Py_LIMITED_API)))]
pub type PyCMethod = unsafe extern "C" fn(
slf: *mut PyObject,
Expand Down Expand Up @@ -144,11 +152,21 @@ pub union PyMethodDefPointer {

/// This variant corresponds with [`METH_FASTCALL`].
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
pub _PyCFunctionFast: _PyCFunctionFast,
#[deprecated(note = "renamed to `PyCFunctionFast`")]
pub _PyCFunctionFast: PyCFunctionFast,

/// This variant corresponds with [`METH_FASTCALL`].
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
pub PyCFunctionFast: PyCFunctionFast,

/// This variant corresponds with [`METH_FASTCALL`] | [`METH_KEYWORDS`].
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
#[deprecated(note = "renamed to `PyCFunctionFastWithKeywords`")]
pub _PyCFunctionFastWithKeywords: PyCFunctionFastWithKeywords,

/// This variant corresponds with [`METH_FASTCALL`] | [`METH_KEYWORDS`].
#[cfg(not(Py_LIMITED_API))]
pub _PyCFunctionFastWithKeywords: _PyCFunctionFastWithKeywords,
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
pub PyCFunctionFastWithKeywords: PyCFunctionFastWithKeywords,

/// This variant corresponds with [`METH_METHOD`] | [`METH_FASTCALL`] | [`METH_KEYWORDS`].
#[cfg(all(Py_3_9, not(Py_LIMITED_API)))]
Expand Down
6 changes: 3 additions & 3 deletions pyo3-macros-backend/src/method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use quote::{format_ident, quote, quote_spanned, ToTokens};
use syn::{ext::IdentExt, spanned::Spanned, Ident, Result};

use crate::deprecations::deprecate_trailing_option_default;
use crate::pyversions::{is_abi3, py_version_ge};
use crate::utils::{Ctx, LitCStr};
use crate::{
attributes::{FromPyWithAttribute, TextSignatureAttribute, TextSignatureAttributeValue},
Expand All @@ -15,7 +16,7 @@ use crate::{
FunctionSignature, PyFunctionArgPyO3Attributes, PyFunctionOptions, SignatureAttribute,
},
quotes,
utils::{self, is_abi3, PythonDoc},
utils::{self, PythonDoc},
};

#[derive(Clone, Debug)]
Expand Down Expand Up @@ -389,8 +390,7 @@ impl CallingConvention {
} else if signature.python_signature.kwargs.is_some() {
// for functions that accept **kwargs, always prefer varargs
Self::Varargs
} else if !is_abi3() {
// FIXME: available in the stable ABI since 3.10
} else if py_version_ge(3, 10) || is_abi3() {
Self::Fastcall
} else {
Self::Varargs
Expand Down
11 changes: 4 additions & 7 deletions pyo3-macros-backend/src/pyclass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,8 @@ use crate::pymethod::{
impl_py_getter_def, impl_py_setter_def, MethodAndMethodDef, MethodAndSlotDef, PropertyType,
SlotDef, __GETITEM__, __HASH__, __INT__, __LEN__, __REPR__, __RICHCMP__, __STR__,
};
use crate::pyversions;
use crate::utils::{self, apply_renaming_rule, LitCStr, PythonDoc};
use crate::utils::{is_abi3, Ctx};
use crate::pyversions::{is_abi3, py_version_ge};
use crate::utils::{self, apply_renaming_rule, Ctx, LitCStr, PythonDoc};
use crate::PyFunctionOptions;

/// If the class is derived from a Rust `struct` or `enum`.
Expand Down Expand Up @@ -186,13 +185,11 @@ impl PyClassPyO3Options {
};
}

let python_version = pyo3_build_config::get().version;

match option {
PyClassPyO3Option::Crate(krate) => set_option!(krate),
PyClassPyO3Option::Dict(dict) => {
ensure_spanned!(
python_version >= pyversions::PY_3_9 || !is_abi3(),
py_version_ge(3, 9) || !is_abi3(),
dict.span() => "`dict` requires Python >= 3.9 when using the `abi3` feature"
);
set_option!(dict);
Expand All @@ -216,7 +213,7 @@ impl PyClassPyO3Options {
PyClassPyO3Option::Unsendable(unsendable) => set_option!(unsendable),
PyClassPyO3Option::Weakref(weakref) => {
ensure_spanned!(
python_version >= pyversions::PY_3_9 || !is_abi3(),
py_version_ge(3, 9) || !is_abi3(),
weakref.span() => "`weakref` requires Python >= 3.9 when using the `abi3` feature"
);
set_option!(weakref);
Expand Down
8 changes: 7 additions & 1 deletion pyo3-macros-backend/src/pyversions.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
use pyo3_build_config::PythonVersion;

pub const PY_3_9: PythonVersion = PythonVersion { major: 3, minor: 9 };
pub fn py_version_ge(major: u8, minor: u8) -> bool {
pyo3_build_config::get().version >= PythonVersion { major, minor }
}

pub fn is_abi3() -> bool {
pyo3_build_config::get().abi3
}
4 changes: 0 additions & 4 deletions pyo3-macros-backend/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -288,10 +288,6 @@ pub fn apply_renaming_rule(rule: RenamingRule, name: &str) -> String {
}
}

pub(crate) fn is_abi3() -> bool {
pyo3_build_config::get().abi3
}

pub(crate) enum IdentOrStr<'a> {
Str(&'a str),
Ident(syn::Ident),
Expand Down
2 changes: 1 addition & 1 deletion src/impl_/extract_argument.rs
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ impl FunctionDescription {
/// - `args` must be a pointer to a C-style array of valid `ffi::PyObject` pointers, or NULL.
/// - `kwnames` must be a pointer to a PyTuple, or NULL.
/// - `nargs + kwnames.len()` is the total length of the `args` array.
#[cfg(not(Py_LIMITED_API))]
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
pub unsafe fn extract_arguments_fastcall<'py, V, K>(
&self,
py: Python<'py>,
Expand Down
12 changes: 6 additions & 6 deletions src/impl_/pymethods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ pub enum PyMethodDefType {
pub enum PyMethodType {
PyCFunction(ffi::PyCFunction),
PyCFunctionWithKeywords(ffi::PyCFunctionWithKeywords),
#[cfg(not(Py_LIMITED_API))]
PyCFunctionFastWithKeywords(ffi::_PyCFunctionFastWithKeywords),
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
PyCFunctionFastWithKeywords(ffi::PyCFunctionFastWithKeywords),
}

pub type PyClassAttributeFactory = for<'p> fn(Python<'p>) -> PyResult<PyObject>;
Expand Down Expand Up @@ -145,10 +145,10 @@ impl PyMethodDef {
}

/// Define a function that can take `*args` and `**kwargs`.
#[cfg(not(Py_LIMITED_API))]
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
pub const fn fastcall_cfunction_with_keywords(
ml_name: &'static CStr,
cfunction: ffi::_PyCFunctionFastWithKeywords,
cfunction: ffi::PyCFunctionFastWithKeywords,
ml_doc: &'static CStr,
) -> Self {
Self {
Expand All @@ -171,9 +171,9 @@ impl PyMethodDef {
PyMethodType::PyCFunctionWithKeywords(meth) => ffi::PyMethodDefPointer {
PyCFunctionWithKeywords: meth,
},
#[cfg(not(Py_LIMITED_API))]
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
PyMethodType::PyCFunctionFastWithKeywords(meth) => ffi::PyMethodDefPointer {
_PyCFunctionFastWithKeywords: meth,
PyCFunctionFastWithKeywords: meth,
},
};

Expand Down

0 comments on commit bd1266b

Please sign in to comment.