Skip to content

Commit

Permalink
pyo3-ffi: expose PyObject_Vectorcall(Method) on the stable abi on 3…
Browse files Browse the repository at this point in the history
….12+ (#4853)
  • Loading branch information
Icxolu authored Jan 15, 2025
1 parent af9f42c commit ad5f6d4
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 89 deletions.
1 change: 1 addition & 0 deletions newsfragments/4853.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pyo3-ffi: expose `PyObject_Vectorcall(Method)` on the stable abi on 3.12+
24 changes: 24 additions & 0 deletions pyo3-ffi/src/abstract_.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use crate::object::*;
use crate::pyport::Py_ssize_t;
#[cfg(any(Py_3_12, all(Py_3_8, not(Py_LIMITED_API))))]
use libc::size_t;
use std::os::raw::{c_char, c_int};

#[inline]
Expand Down Expand Up @@ -70,6 +72,28 @@ extern "C" {
method: *mut PyObject,
...
) -> *mut PyObject;
}
#[cfg(any(Py_3_12, all(Py_3_8, not(Py_LIMITED_API))))]
pub const PY_VECTORCALL_ARGUMENTS_OFFSET: size_t =
1 << (8 * std::mem::size_of::<size_t>() as size_t - 1);

extern "C" {
#[cfg_attr(PyPy, link_name = "PyPyObject_Vectorcall")]
#[cfg(any(Py_3_12, all(Py_3_11, not(Py_LIMITED_API))))]
pub fn PyObject_Vectorcall(
callable: *mut PyObject,
args: *const *mut PyObject,
nargsf: size_t,
kwnames: *mut PyObject,
) -> *mut PyObject;

#[cfg(any(Py_3_12, all(Py_3_9, not(any(Py_LIMITED_API, PyPy, GraalPy)))))]
pub fn PyObject_VectorcallMethod(
name: *mut PyObject,
args: *const *mut PyObject,
nargsf: size_t,
kwnames: *mut PyObject,
) -> *mut PyObject;
#[cfg_attr(PyPy, link_name = "PyPyObject_Type")]
pub fn PyObject_Type(o: *mut PyObject) -> *mut PyObject;
#[cfg_attr(PyPy, link_name = "PyPyObject_Size")]
Expand Down
30 changes: 5 additions & 25 deletions pyo3-ffi/src/cpython/abstract_.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ extern "C" {
) -> *mut PyObject;
}

#[cfg(Py_3_8)]
pub const PY_VECTORCALL_ARGUMENTS_OFFSET: size_t =
#[cfg(Py_3_8)] // NB exported as public in abstract.rs from 3.12
const PY_VECTORCALL_ARGUMENTS_OFFSET: size_t =
1 << (8 * std::mem::size_of::<size_t>() as size_t - 1);

#[cfg(Py_3_8)]
Expand Down Expand Up @@ -91,7 +91,7 @@ pub unsafe fn _PyObject_VectorcallTstate(
}
}

#[cfg(all(Py_3_8, not(any(PyPy, GraalPy))))]
#[cfg(all(Py_3_8, not(any(PyPy, GraalPy, Py_3_11))))] // exported as a function from 3.11, see abstract.rs
#[inline(always)]
pub unsafe fn PyObject_Vectorcall(
callable: *mut PyObject,
Expand All @@ -103,16 +103,6 @@ pub unsafe fn PyObject_Vectorcall(
}

extern "C" {
#[cfg(all(PyPy, Py_3_8))]
#[cfg_attr(not(Py_3_9), link_name = "_PyPyObject_Vectorcall")]
#[cfg_attr(Py_3_9, link_name = "PyPyObject_Vectorcall")]
pub fn PyObject_Vectorcall(
callable: *mut PyObject,
args: *const *mut PyObject,
nargsf: size_t,
kwnames: *mut PyObject,
) -> *mut PyObject;

#[cfg(Py_3_8)]
#[cfg_attr(
all(not(any(PyPy, GraalPy)), not(Py_3_9)),
Expand Down Expand Up @@ -187,23 +177,13 @@ pub unsafe fn PyObject_CallOneArg(func: *mut PyObject, arg: *mut PyObject) -> *m
_PyObject_VectorcallTstate(tstate, func, args, nargsf, std::ptr::null_mut())
}

extern "C" {
#[cfg(all(Py_3_9, not(any(PyPy, GraalPy))))]
pub fn PyObject_VectorcallMethod(
name: *mut PyObject,
args: *const *mut PyObject,
nargsf: size_t,
kwnames: *mut PyObject,
) -> *mut PyObject;
}

#[cfg(all(Py_3_9, not(any(PyPy, GraalPy))))]
#[inline(always)]
pub unsafe fn PyObject_CallMethodNoArgs(
self_: *mut PyObject,
name: *mut PyObject,
) -> *mut PyObject {
PyObject_VectorcallMethod(
crate::PyObject_VectorcallMethod(
name,
&self_,
1 | PY_VECTORCALL_ARGUMENTS_OFFSET,
Expand All @@ -220,7 +200,7 @@ pub unsafe fn PyObject_CallMethodOneArg(
) -> *mut PyObject {
let args = [self_, arg];
assert!(!arg.is_null());
PyObject_VectorcallMethod(
crate::PyObject_VectorcallMethod(
name,
args.as_ptr(),
2 | PY_VECTORCALL_ARGUMENTS_OFFSET,
Expand Down
44 changes: 44 additions & 0 deletions src/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2020,6 +2020,50 @@ mod tests {
})
}

#[test]
fn test_call_tuple_ref() {
let assert_repr = |obj: &Bound<'_, PyAny>, expected: &str| {
use crate::prelude::PyStringMethods;
assert_eq!(
obj.repr()
.unwrap()
.to_cow()
.unwrap()
.trim_matches(|c| c == '{' || c == '}'),
expected.trim_matches(|c| c == ',' || c == ' ')
);
};

macro_rules! tuple {
($py:ident, $($key: literal => $value: literal),+) => {
let ty_obj = $py.get_type::<PyDict>().into_pyobject($py).unwrap();
assert!(ty_obj.call1(&(($(($key),)+),)).is_err());
let obj = ty_obj.call1(&(($(($key, i32::from($value)),)+),)).unwrap();
assert_repr(&obj, concat!($("'", $key, "'", ": ", stringify!($value), ", ",)+));
assert!(obj.call_method1("update", &(($(($key),)+),)).is_err());
obj.call_method1("update", &(($((i32::from($value), $key),)+),)).unwrap();
assert_repr(&obj, concat!(
concat!($("'", $key, "'", ": ", stringify!($value), ", ",)+),
concat!($(stringify!($value), ": ", "'", $key, "'", ", ",)+)
));
};
}

Python::with_gil(|py| {
tuple!(py, "a" => 1);
tuple!(py, "a" => 1, "b" => 2);
tuple!(py, "a" => 1, "b" => 2, "c" => 3);
tuple!(py, "a" => 1, "b" => 2, "c" => 3, "d" => 4);
tuple!(py, "a" => 1, "b" => 2, "c" => 3, "d" => 4, "e" => 5);
tuple!(py, "a" => 1, "b" => 2, "c" => 3, "d" => 4, "e" => 5, "f" => 6);
tuple!(py, "a" => 1, "b" => 2, "c" => 3, "d" => 4, "e" => 5, "f" => 6, "g" => 7);
tuple!(py, "a" => 1, "b" => 2, "c" => 3, "d" => 4, "e" => 5, "f" => 6, "g" => 7, "h" => 8);
tuple!(py, "a" => 1, "b" => 2, "c" => 3, "d" => 4, "e" => 5, "f" => 6, "g" => 7, "h" => 8, "i" => 9);
tuple!(py, "a" => 1, "b" => 2, "c" => 3, "d" => 4, "e" => 5, "f" => 6, "g" => 7, "h" => 8, "i" => 9, "j" => 10, "k" => 11);
tuple!(py, "a" => 1, "b" => 2, "c" => 3, "d" => 4, "e" => 5, "f" => 6, "g" => 7, "h" => 8, "i" => 9, "j" => 10, "k" => 11, "l" => 12);
})
}

#[test]
fn test_call_for_non_existing_method() {
Python::with_gil(|py| {
Expand Down
Loading

0 comments on commit ad5f6d4

Please sign in to comment.