introduce trampolines for methods (#2705)

This commit is contained in:
David Hewitt 2022-10-25 07:22:36 +01:00 committed by GitHub
parent 35dc63e589
commit 747d791f1f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 532 additions and 348 deletions

View File

@ -0,0 +1 @@
Change `#[pyfunction]` and `#[pymethods]` to use a common call "trampoline" to slightly reduce generated code size and compile times.

View File

@ -481,18 +481,13 @@ impl<'a> FnSpec<'a> {
CallingConvention::Noargs => {
let call = rust_call(vec![]);
quote! {
unsafe extern "C" fn #ident (
unsafe fn #ident<'py>(
#py: _pyo3::Python<'py>,
_slf: *mut _pyo3::ffi::PyObject,
_args: *mut _pyo3::ffi::PyObject,
) -> *mut _pyo3::ffi::PyObject
{
) -> _pyo3::PyResult<*mut _pyo3::ffi::PyObject> {
#deprecations
let gil = _pyo3::GILPool::new();
let #py = gil.python();
_pyo3::callback::panic_result_into_callback_output(#py, ::std::panic::catch_unwind(move || -> _pyo3::PyResult<_> {
#self_conversion
#call
}))
#self_conversion
#call
}
}
}
@ -500,20 +495,17 @@ impl<'a> FnSpec<'a> {
let (arg_convert, args) = impl_arg_params(self, cls, &py, true)?;
let call = rust_call(args);
quote! {
unsafe extern "C" fn #ident (
unsafe fn #ident<'py>(
#py: _pyo3::Python<'py>,
_slf: *mut _pyo3::ffi::PyObject,
_args: *const *mut _pyo3::ffi::PyObject,
_nargs: _pyo3::ffi::Py_ssize_t,
_kwnames: *mut _pyo3::ffi::PyObject) -> *mut _pyo3::ffi::PyObject
{
_kwnames: *mut _pyo3::ffi::PyObject
) -> _pyo3::PyResult<*mut _pyo3::ffi::PyObject> {
#deprecations
let gil = _pyo3::GILPool::new();
let #py = gil.python();
_pyo3::callback::panic_result_into_callback_output(#py, ::std::panic::catch_unwind(move || -> _pyo3::PyResult<_> {
#self_conversion
#arg_convert
#call
}))
#self_conversion
#arg_convert
#call
}
}
}
@ -521,19 +513,16 @@ impl<'a> FnSpec<'a> {
let (arg_convert, args) = impl_arg_params(self, cls, &py, false)?;
let call = rust_call(args);
quote! {
unsafe extern "C" fn #ident (
unsafe fn #ident<'py>(
#py: _pyo3::Python<'py>,
_slf: *mut _pyo3::ffi::PyObject,
_args: *mut _pyo3::ffi::PyObject,
_kwargs: *mut _pyo3::ffi::PyObject) -> *mut _pyo3::ffi::PyObject
{
_kwargs: *mut _pyo3::ffi::PyObject
) -> _pyo3::PyResult<*mut _pyo3::ffi::PyObject> {
#deprecations
let gil = _pyo3::GILPool::new();
let #py = gil.python();
_pyo3::callback::panic_result_into_callback_output(#py, ::std::panic::catch_unwind(move || -> _pyo3::PyResult<_> {
#self_conversion
#arg_convert
#call
}))
#self_conversion
#arg_convert
#call
}
}
}
@ -541,21 +530,19 @@ impl<'a> FnSpec<'a> {
let (arg_convert, args) = impl_arg_params(self, cls, &py, false)?;
let call = quote! { #rust_name(#(#args),*) };
quote! {
unsafe extern "C" fn #ident (
unsafe fn #ident(
#py: _pyo3::Python<'_>,
subtype: *mut _pyo3::ffi::PyTypeObject,
_args: *mut _pyo3::ffi::PyObject,
_kwargs: *mut _pyo3::ffi::PyObject) -> *mut _pyo3::ffi::PyObject
{
_kwargs: *mut _pyo3::ffi::PyObject
) -> _pyo3::PyResult<*mut _pyo3::ffi::PyObject> {
use _pyo3::callback::IntoPyCallbackOutput;
#deprecations
use _pyo3::{callback::IntoPyCallbackOutput, pyclass_init::PyObjectInit};
let gil = _pyo3::GILPool::new();
let #py = gil.python();
_pyo3::callback::panic_result_into_callback_output(#py, ::std::panic::catch_unwind(move || -> _pyo3::PyResult<_> {
#arg_convert
let result = #call;
let initializer: _pyo3::PyClassInitializer::<#cls> = result.convert(#py)?;
initializer.into_new_object(#py, subtype)
}))
#arg_convert
let result = #call;
let initializer: _pyo3::PyClassInitializer::<#cls> = result.convert(#py)?;
let cell = initializer.create_cell_from_subtype(#py, subtype)?;
::std::result::Result::Ok(cell as *mut _pyo3::ffi::PyObject)
}
}
}
@ -571,21 +558,66 @@ impl<'a> FnSpec<'a> {
CallingConvention::Noargs => quote! {
_pyo3::impl_::pymethods::PyMethodDef::noargs(
#python_name,
_pyo3::impl_::pymethods::PyCFunction(#wrapper),
_pyo3::impl_::pymethods::PyCFunction({
unsafe extern "C" fn trampoline(
_slf: *mut _pyo3::ffi::PyObject,
_args: *mut _pyo3::ffi::PyObject,
) -> *mut _pyo3::ffi::PyObject
{
_pyo3::impl_::trampoline::noargs(
_slf,
_args,
#wrapper
)
}
trampoline
}),
#doc,
)
},
CallingConvention::Fastcall => quote! {
_pyo3::impl_::pymethods::PyMethodDef::fastcall_cfunction_with_keywords(
#python_name,
_pyo3::impl_::pymethods::PyCFunctionFastWithKeywords(#wrapper),
_pyo3::impl_::pymethods::PyCFunctionFastWithKeywords({
unsafe extern "C" fn trampoline(
_slf: *mut _pyo3::ffi::PyObject,
_args: *const *mut _pyo3::ffi::PyObject,
_nargs: _pyo3::ffi::Py_ssize_t,
_kwnames: *mut _pyo3::ffi::PyObject
) -> *mut _pyo3::ffi::PyObject
{
_pyo3::impl_::trampoline::fastcall_with_keywords(
_slf,
_args,
_nargs,
_kwnames,
#wrapper
)
}
trampoline
}),
#doc,
)
},
CallingConvention::Varargs => quote! {
_pyo3::impl_::pymethods::PyMethodDef::cfunction_with_keywords(
#python_name,
_pyo3::impl_::pymethods::PyCFunctionWithKeywords(#wrapper),
_pyo3::impl_::pymethods::PyCFunctionWithKeywords({
unsafe extern "C" fn trampoline(
_slf: *mut _pyo3::ffi::PyObject,
_args: *mut _pyo3::ffi::PyObject,
_kwargs: *mut _pyo3::ffi::PyObject,
) -> *mut _pyo3::ffi::PyObject
{
_pyo3::impl_::trampoline::cfunction_with_keywords(
_slf,
_args,
_kwargs,
#wrapper
)
}
trampoline
}),
#doc,
)
},

View File

@ -84,7 +84,7 @@ pub fn pymodule_impl(
/// the module.
#[export_name = #pyinit_symbol]
pub unsafe extern "C" fn init() -> *mut #krate::ffi::PyObject {
DEF.module_init()
#krate::impl_::trampoline::module_init(|py| DEF.make_module(py))
}
}

View File

@ -302,7 +302,22 @@ fn impl_py_method_def_new(cls: &syn::Type, spec: &FnSpec<'_>) -> Result<MethodAn
let slot_def = quote! {
_pyo3::ffi::PyType_Slot {
slot: _pyo3::ffi::Py_tp_new,
pfunc: #cls::#wrapper_ident as _pyo3::ffi::newfunc as _
pfunc: {
unsafe extern "C" fn trampoline(
subtype: *mut _pyo3::ffi::PyTypeObject,
args: *mut _pyo3::ffi::PyObject,
kwargs: *mut _pyo3::ffi::PyObject,
) -> *mut _pyo3::ffi::PyObject
{
_pyo3::impl_::trampoline::newfunc(
subtype,
args,
kwargs,
#cls::#wrapper_ident
)
}
trampoline
} as _pyo3::ffi::newfunc as _
}
};
Ok(MethodAndSlotDef {
@ -321,7 +336,22 @@ fn impl_call_slot(cls: &syn::Type, mut spec: FnSpec<'_>) -> Result<MethodAndSlot
let slot_def = quote! {
_pyo3::ffi::PyType_Slot {
slot: _pyo3::ffi::Py_tp_call,
pfunc: #cls::#wrapper_ident as _pyo3::ffi::ternaryfunc as _
pfunc: {
unsafe extern "C" fn trampoline(
slf: *mut _pyo3::ffi::PyObject,
args: *mut _pyo3::ffi::PyObject,
kwargs: *mut _pyo3::ffi::PyObject,
) -> *mut _pyo3::ffi::PyObject
{
_pyo3::impl_::trampoline::ternaryfunc(
slf,
args,
kwargs,
#cls::#wrapper_ident
)
}
trampoline
} as _pyo3::ffi::ternaryfunc as _
}
};
Ok(MethodAndSlotDef {
@ -484,24 +514,20 @@ pub fn impl_py_setter_def(
};
let associated_method = quote! {
unsafe extern "C" fn #wrapper_ident(
unsafe fn #wrapper_ident(
_py: _pyo3::Python<'_>,
_slf: *mut _pyo3::ffi::PyObject,
_value: *mut _pyo3::ffi::PyObject,
_: *mut ::std::os::raw::c_void
) -> ::std::os::raw::c_int {
let gil = _pyo3::GILPool::new();
let _py = gil.python();
_pyo3::callback::panic_result_into_callback_output(_py, ::std::panic::catch_unwind(move || -> _pyo3::PyResult<_> {
#slf
let _value = _py
.from_borrowed_ptr_or_opt(_value)
.ok_or_else(|| {
_pyo3::exceptions::PyAttributeError::new_err("can't delete attribute")
})?;
let _val = _pyo3::FromPyObject::extract(_value)?;
) -> _pyo3::PyResult<::std::os::raw::c_int> {
#slf
let _value = _py
.from_borrowed_ptr_or_opt(_value)
.ok_or_else(|| {
_pyo3::exceptions::PyAttributeError::new_err("can't delete attribute")
})?;
let _val = _pyo3::FromPyObject::extract(_value)?;
_pyo3::callback::convert(_py, #setter_impl)
}))
_pyo3::callback::convert(_py, #setter_impl)
}
};
@ -510,7 +536,22 @@ pub fn impl_py_setter_def(
#deprecations
_pyo3::class::PySetterDef::new(
#python_name,
_pyo3::impl_::pymethods::PySetter(#cls::#wrapper_ident),
_pyo3::impl_::pymethods::PySetter({
unsafe extern "C" fn trampoline(
slf: *mut _pyo3::ffi::PyObject,
value: *mut _pyo3::ffi::PyObject,
closure: *mut ::std::os::raw::c_void,
) -> ::std::os::raw::c_int
{
_pyo3::impl_::trampoline::setter(
slf,
value,
closure,
#cls::#wrapper_ident
)
}
trampoline
}),
#doc
)
})
@ -555,7 +596,6 @@ pub fn impl_py_getter_def(
..
} => {
// named struct field
//quote!(_slf.#ident.clone())
quote!(::std::clone::Clone::clone(&(_slf.#ident)))
}
PropertyType::Descriptor { field_index, .. } => {
@ -608,17 +648,13 @@ pub fn impl_py_getter_def(
};
let associated_method = quote! {
unsafe extern "C" fn #wrapper_ident(
_slf: *mut _pyo3::ffi::PyObject,
_: *mut ::std::os::raw::c_void
) -> *mut _pyo3::ffi::PyObject {
let gil = _pyo3::GILPool::new();
let _py = gil.python();
_pyo3::callback::panic_result_into_callback_output(_py, ::std::panic::catch_unwind(move || -> _pyo3::PyResult<_> {
#slf
let item = #getter_impl;
#conversion
}))
unsafe fn #wrapper_ident(
_py: _pyo3::Python<'_>,
_slf: *mut _pyo3::ffi::PyObject
) -> _pyo3::PyResult<*mut _pyo3::ffi::PyObject> {
#slf
let item = #getter_impl;
#conversion
}
};
@ -627,7 +663,20 @@ pub fn impl_py_getter_def(
#deprecations
_pyo3::class::PyGetterDef::new(
#python_name,
_pyo3::impl_::pymethods::PyGetter(#cls::#wrapper_ident),
_pyo3::impl_::pymethods::PyGetter({
unsafe extern "C" fn trampoline(
slf: *mut _pyo3::ffi::PyObject,
closure: *mut ::std::os::raw::c_void,
) -> *mut _pyo3::ffi::PyObject
{
_pyo3::impl_::trampoline::getter(
slf,
closure,
#cls::#wrapper_ident
)
}
trampoline
}),
#doc
)
})
@ -1050,21 +1099,33 @@ impl SlotDef {
return_mode.as_ref(),
)?;
let associated_method = quote! {
unsafe extern "C" fn #wrapper_ident(_raw_slf: *mut _pyo3::ffi::PyObject, #(#arg_idents: #arg_types),*) -> #ret_ty {
unsafe fn #wrapper_ident(
#py: _pyo3::Python<'_>,
_raw_slf: *mut _pyo3::ffi::PyObject,
#(#arg_idents: #arg_types),*
) -> _pyo3::PyResult<#ret_ty> {
let _slf = _raw_slf;
let gil = _pyo3::GILPool::new();
let #py = gil.python();
_pyo3::callback::panic_result_into_callback_output(#py, ::std::panic::catch_unwind(move || -> _pyo3::PyResult<_> {
#body
}))
#body
}
};
let slot_def = quote! {
let slot_def = quote! {{
unsafe extern "C" fn trampoline(
_slf: *mut _pyo3::ffi::PyObject,
#(#arg_idents: #arg_types),*
) -> #ret_ty
{
_pyo3::impl_::trampoline:: #func_ty (
_slf,
#(#arg_idents,)*
#cls::#wrapper_ident
)
}
_pyo3::ffi::PyType_Slot {
slot: _pyo3::ffi::#slot,
pfunc: #cls::#wrapper_ident as _pyo3::ffi::#func_ty as _
pfunc: trampoline as _pyo3::ffi::#func_ty as _
}
};
}};
Ok(MethodAndSlotDef {
associated_method,
slot_def,

View File

@ -5,14 +5,10 @@
use crate::err::{PyErr, PyResult};
use crate::exceptions::PyOverflowError;
use crate::ffi::{self, Py_hash_t};
use crate::impl_::panic::PanicTrap;
use crate::panic::PanicException;
use crate::{GILPool, IntoPyPointer};
use crate::IntoPyPointer;
use crate::{IntoPy, PyObject, Python};
use std::any::Any;
use std::isize;
use std::os::raw::c_int;
use std::panic::UnwindSafe;
use std::{isize, panic};
/// A type which can be the return type of a python C-API callback
pub trait PyCallbackOutput: Copy {
@ -187,90 +183,3 @@ where
{
value.convert(py)
}
/// Use this macro for all internal callback functions which Python will call.
///
/// It sets up the GILPool and converts the output into a Python object. It also restores
/// any python error returned as an Err variant from the body.
///
/// Finally, any panics inside the callback body will be caught and translated into PanicExceptions.
///
/// # Safety
/// This macro assumes the GIL is held. (It makes use of unsafe code, so usage of it is only
/// possible inside unsafe blocks.)
#[doc(hidden)]
#[macro_export]
macro_rules! callback_body {
($py:ident, $body:expr) => {
$crate::callback::handle_panic(|$py| $crate::callback::convert($py, $body))
};
}
/// Variant of the above which does not perform the callback conversion. This allows the callback
/// conversion to be done manually in the case where lifetimes might otherwise cause issue.
///
/// For example this pyfunction:
///
/// ```no_compile
/// fn foo(&self) -> &Bar {
/// &self.bar
/// }
/// ```
///
/// It is wrapped in proc macros with handle_panic like so:
///
/// ```no_compile
/// pyo3::callback::handle_panic(|_py| {
/// let _slf = #slf;
/// pyo3::callback::convert(_py, #foo)
/// })
/// ```
///
/// If callback_body was used instead:
///
/// ```no_compile
/// pyo3::callback_body!(py, {
/// let _slf = #slf;
/// #foo
/// })
/// ```
///
/// Then this will fail to compile, because the result of #foo borrows _slf, but _slf drops when
/// the block passed to the macro ends.
#[doc(hidden)]
#[inline]
pub unsafe fn handle_panic<F, R>(body: F) -> R
where
F: for<'py> FnOnce(Python<'py>) -> PyResult<R> + UnwindSafe,
R: PyCallbackOutput,
{
let trap = PanicTrap::new("uncaught panic at ffi boundary");
let pool = GILPool::new();
let py = pool.python();
let out = panic_result_into_callback_output(
py,
panic::catch_unwind(move || -> PyResult<_> { body(py) }),
);
trap.disarm();
out
}
/// Converts the output of std::panic::catch_unwind into a Python function output, either by raising a Python
/// exception or by unwrapping the contained success output.
#[doc(hidden)]
#[inline]
pub fn panic_result_into_callback_output<R>(
py: Python<'_>,
panic_result: Result<PyResult<R>, Box<dyn Any + Send + 'static>>,
) -> R
where
R: PyCallbackOutput,
{
let py_err = match panic_result {
Ok(Ok(value)) => return value,
Ok(Err(py_err)) => py_err,
Err(payload) => PanicException::from_panic_payload(payload),
};
py_err.restore(py);
R::ERR_VALUE
}

View File

@ -17,3 +17,5 @@ pub mod pyclass;
pub mod pyfunction;
pub mod pymethods;
pub mod pymodule;
#[doc(hidden)]
pub mod trampoline;

View File

@ -313,29 +313,24 @@ macro_rules! generate_pyclass_getattro_slot {
_slf: *mut $crate::ffi::PyObject,
attr: *mut $crate::ffi::PyObject,
) -> *mut $crate::ffi::PyObject {
use ::std::result::Result::*;
use $crate::impl_::pyclass::*;
let gil = $crate::GILPool::new();
let py = gil.python();
$crate::callback::panic_result_into_callback_output(
py,
::std::panic::catch_unwind(move || -> $crate::PyResult<_> {
let collector = PyClassImplCollector::<$cls>::new();
$crate::impl_::trampoline::getattrofunc(_slf, attr, |py, _slf, attr| {
use ::std::result::Result::*;
use $crate::impl_::pyclass::*;
let collector = PyClassImplCollector::<$cls>::new();
// Strategy:
// - Try __getattribute__ first. Its default is PyObject_GenericGetAttr.
// - If it returns a result, use it.
// - If it fails with AttributeError, try __getattr__.
// - If it fails otherwise, reraise.
match collector.__getattribute__(py, _slf, attr) {
Ok(obj) => Ok(obj),
Err(e) if e.is_instance_of::<$crate::exceptions::PyAttributeError>(py) => {
collector.__getattr__(py, _slf, attr)
}
Err(e) => Err(e),
// Strategy:
// - Try __getattribute__ first. Its default is PyObject_GenericGetAttr.
// - If it returns a result, use it.
// - If it fails with AttributeError, try __getattr__.
// - If it fails otherwise, reraise.
match collector.__getattribute__(py, _slf, attr) {
Ok(obj) => Ok(obj),
Err(e) if e.is_instance_of::<$crate::exceptions::PyAttributeError>(py) => {
collector.__getattr__(py, _slf, attr)
}
}),
)
Err(e) => Err(e),
}
})
}
$crate::ffi::PyType_Slot {
slot: $crate::ffi::Py_tp_getattro,
@ -402,21 +397,21 @@ macro_rules! define_pyclass_setattr_slot {
attr: *mut $crate::ffi::PyObject,
value: *mut $crate::ffi::PyObject,
) -> ::std::os::raw::c_int {
use ::std::option::Option::*;
use $crate::callback::IntoPyCallbackOutput;
use $crate::impl_::pyclass::*;
let gil = $crate::GILPool::new();
let py = gil.python();
$crate::callback::panic_result_into_callback_output(
py,
::std::panic::catch_unwind(move || -> $crate::PyResult<_> {
$crate::impl_::trampoline::setattrofunc(
_slf,
attr,
value,
|py, _slf, attr, value| {
use ::std::option::Option::*;
use $crate::callback::IntoPyCallbackOutput;
use $crate::impl_::pyclass::*;
let collector = PyClassImplCollector::<$cls>::new();
if let Some(value) = ::std::ptr::NonNull::new(value) {
collector.$set(py, _slf, attr, value).convert(py)
} else {
collector.$del(py, _slf, attr).convert(py)
}
}),
},
)
}
$crate::ffi::PyType_Slot {
@ -517,22 +512,17 @@ macro_rules! define_pyclass_binary_operator_slot {
_slf: *mut $crate::ffi::PyObject,
_other: *mut $crate::ffi::PyObject,
) -> *mut $crate::ffi::PyObject {
let gil = $crate::GILPool::new();
let py = gil.python();
$crate::callback::panic_result_into_callback_output(
py,
::std::panic::catch_unwind(move || -> $crate::PyResult<_> {
use $crate::impl_::pyclass::*;
let collector = PyClassImplCollector::<$cls>::new();
let lhs_result = collector.$lhs(py, _slf, _other)?;
if lhs_result == $crate::ffi::Py_NotImplemented() {
$crate::ffi::Py_DECREF(lhs_result);
collector.$rhs(py, _other, _slf)
} else {
::std::result::Result::Ok(lhs_result)
}
}),
)
$crate::impl_::trampoline::binaryfunc(_slf, _other, |py, _slf, _other| {
use $crate::impl_::pyclass::*;
let collector = PyClassImplCollector::<$cls>::new();
let lhs_result = collector.$lhs(py, _slf, _other)?;
if lhs_result == $crate::ffi::Py_NotImplemented() {
$crate::ffi::Py_DECREF(lhs_result);
collector.$rhs(py, _other, _slf)
} else {
::std::result::Result::Ok(lhs_result)
}
})
}
$crate::ffi::PyType_Slot {
slot: $crate::ffi::$slot,
@ -715,22 +705,17 @@ macro_rules! generate_pyclass_pow_slot {
_other: *mut $crate::ffi::PyObject,
_mod: *mut $crate::ffi::PyObject,
) -> *mut $crate::ffi::PyObject {
let gil = $crate::GILPool::new();
let py = gil.python();
$crate::callback::panic_result_into_callback_output(
py,
::std::panic::catch_unwind(move || -> $crate::PyResult<_> {
use $crate::impl_::pyclass::*;
let collector = PyClassImplCollector::<$cls>::new();
let lhs_result = collector.__pow__(py, _slf, _other, _mod)?;
if lhs_result == $crate::ffi::Py_NotImplemented() {
$crate::ffi::Py_DECREF(lhs_result);
collector.__rpow__(py, _other, _slf, _mod)
} else {
::std::result::Result::Ok(lhs_result)
}
}),
)
$crate::impl_::trampoline::ternaryfunc(_slf, _other, _mod, |py, _slf, _other, _mod| {
use $crate::impl_::pyclass::*;
let collector = PyClassImplCollector::<$cls>::new();
let lhs_result = collector.__pow__(py, _slf, _other, _mod)?;
if lhs_result == $crate::ffi::Py_NotImplemented() {
$crate::ffi::Py_DECREF(lhs_result);
collector.__rpow__(py, _other, _slf, _mod)
} else {
::std::result::Result::Ok(lhs_result)
}
})
}
$crate::ffi::PyType_Slot {
slot: $crate::ffi::Py_nb_power,
@ -943,7 +928,19 @@ impl<T: PyClass> PyClassBaseType for T {
/// Implementation of tp_dealloc for all pyclasses
pub(crate) unsafe extern "C" fn tp_dealloc<T: PyClass>(obj: *mut ffi::PyObject) {
crate::callback_body!(py, T::Layout::tp_dealloc(obj, py))
/// A wrapper because PyCellLayout::tp_dealloc currently takes the py argument last
/// (which is different to the rest of the trampolines which take py first)
#[inline]
#[allow(clippy::unnecessary_wraps)]
unsafe fn trampoline_dealloc_wrapper<T: PyClass>(
py: Python<'_>,
slf: *mut ffi::PyObject,
) -> PyResult<()> {
T::Layout::tp_dealloc(slf, py);
Ok(())
}
// TODO change argument order in PyCellLayout::tp_dealloc so this wrapper isn't needed.
crate::impl_::trampoline::dealloc(obj, trampoline_dealloc_wrapper::<T>)
}
pub(crate) unsafe extern "C" fn get_sequence_item_from_mapping(

View File

@ -5,10 +5,7 @@ use std::{
sync::atomic::{self, AtomicBool},
};
use crate::{
callback::panic_result_into_callback_output, exceptions::PyImportError, ffi, types::PyModule,
GILPool, IntoPyPointer, Py, PyObject, PyResult, Python,
};
use crate::{exceptions::PyImportError, ffi, types::PyModule, Py, PyResult, Python};
/// `Sync` wrapper of `ffi::PyModuleDef`.
pub struct ModuleDef {
@ -58,7 +55,22 @@ impl ModuleDef {
}
}
/// Builds a module using user given initializer. Used for [`#[pymodule]`][crate::pymodule].
pub fn make_module(&'static self, py: Python<'_>) -> PyResult<PyObject> {
pub fn make_module(&'static self, py: Python<'_>) -> PyResult<Py<PyModule>> {
#[cfg(all(PyPy, not(Py_3_8)))]
{
const PYPY_GOOD_VERSION: [u8; 3] = [7, 3, 8];
let version = py
.import("sys")?
.getattr("implementation")?
.getattr("version")?;
if version.lt(crate::types::PyTuple::new(py, &PYPY_GOOD_VERSION))? {
let warn = py.import("warnings")?.getattr("warn")?;
warn.call1((
"PyPy 3.7 versions older than 7.3.8 are known to have binary \
compatibility issues which may cause segfaults. Please upgrade.",
))?;
}
}
let module = unsafe {
Py::<PyModule>::from_owned_ptr_or_err(py, ffi::PyModule_Create(self.ffi_def.get()))?
};
@ -68,37 +80,7 @@ impl ModuleDef {
));
}
(self.initializer.0)(py, module.as_ref(py))?;
Ok(module.into())
}
/// Implementation of `PyInit_foo` functions generated in [`#[pymodule]`][crate::pymodule]..
///
/// # Safety
/// The Python GIL must be held.
pub unsafe fn module_init(&'static self) -> *mut ffi::PyObject {
let pool = GILPool::new();
let py = pool.python();
let unwind_safe_self = std::panic::AssertUnwindSafe(self);
panic_result_into_callback_output(
py,
std::panic::catch_unwind(move || -> PyResult<_> {
#[cfg(all(PyPy, not(Py_3_8)))]
{
const PYPY_GOOD_VERSION: [u8; 3] = [7, 3, 8];
let version = py
.import("sys")?
.getattr("implementation")?
.getattr("version")?;
if version.lt(crate::types::PyTuple::new(py, &PYPY_GOOD_VERSION))? {
let warn = py.import("warnings")?.getattr("warn")?;
warn.call1((
"PyPy 3.7 versions older than 7.3.8 are known to have binary \
compatibility issues which may cause segfaults. Please upgrade.",
))?;
}
}
Ok(unwind_safe_self.make_module(py)?.into_ptr())
}),
)
Ok(module)
}
}
@ -112,46 +94,43 @@ mod tests {
#[test]
fn module_init() {
unsafe {
static MODULE_DEF: ModuleDef = unsafe {
ModuleDef::new(
"test_module\0",
"some doc\0",
ModuleInitializer(|_, m| {
m.add("SOME_CONSTANT", 42)?;
Ok(())
}),
)
};
Python::with_gil(|py| {
let module = MODULE_DEF.module_init();
let pymodule: &PyModule = py.from_owned_ptr(module);
assert_eq!(
pymodule
.getattr("__name__")
.unwrap()
.extract::<&str>()
.unwrap(),
"test_module",
);
assert_eq!(
pymodule
.getattr("__doc__")
.unwrap()
.extract::<&str>()
.unwrap(),
"some doc",
);
assert_eq!(
pymodule
.getattr("SOME_CONSTANT")
.unwrap()
.extract::<u8>()
.unwrap(),
42,
);
})
}
static MODULE_DEF: ModuleDef = unsafe {
ModuleDef::new(
"test_module\0",
"some doc\0",
ModuleInitializer(|_, m| {
m.add("SOME_CONSTANT", 42)?;
Ok(())
}),
)
};
Python::with_gil(|py| {
let module = MODULE_DEF.make_module(py).unwrap().into_ref(py);
assert_eq!(
module
.getattr("__name__")
.unwrap()
.extract::<&str>()
.unwrap(),
"test_module",
);
assert_eq!(
module
.getattr("__doc__")
.unwrap()
.extract::<&str>()
.unwrap(),
"some doc",
);
assert_eq!(
module
.getattr("SOME_CONSTANT")
.unwrap()
.extract::<u8>()
.unwrap(),
42,
);
})
}
#[test]

203
src/impl_/trampoline.rs Normal file
View File

@ -0,0 +1,203 @@
//! Trampolines for various pyfunction and pymethod implementations.
//!
//! They exist to monomorphise std::panic::catch_unwind once into PyO3, rather than inline in every
//! function, thus saving a huge amount of compile-time complexity.
use std::{
any::Any,
os::raw::{c_int, c_void},
panic::{self, UnwindSafe},
};
use crate::{
callback::PyCallbackOutput, ffi, impl_::panic::PanicTrap, methods::IPowModulo,
panic::PanicException, types::PyModule, GILPool, IntoPyPointer, Py, PyResult, Python,
};
#[inline]
pub unsafe fn module_init(
f: for<'py> unsafe fn(Python<'py>) -> PyResult<Py<PyModule>>,
) -> *mut ffi::PyObject {
trampoline_inner(|py| f(py).map(|module| module.into_ptr()))
}
#[inline]
pub unsafe fn noargs(
slf: *mut ffi::PyObject,
args: *mut ffi::PyObject,
f: for<'py> unsafe fn(Python<'py>, *mut ffi::PyObject) -> PyResult<*mut ffi::PyObject>,
) -> *mut ffi::PyObject {
debug_assert!(args.is_null());
trampoline_inner(|py| f(py, slf))
}
macro_rules! trampoline {
(pub fn $name:ident($($arg_names:ident: $arg_types:ty),* $(,)?) -> $ret:ty;) => {
#[inline]
pub unsafe fn $name(
$($arg_names: $arg_types,)*
f: for<'py> unsafe fn (Python<'py>, $($arg_types),*) -> PyResult<$ret>,
) -> $ret {
trampoline_inner(|py| f(py, $($arg_names,)*))
}
}
}
macro_rules! trampolines {
($(pub fn $name:ident($($arg_names:ident: $arg_types:ty),* $(,)?) -> $ret:ty);* ;) => {
$(trampoline!(pub fn $name($($arg_names: $arg_types),*) -> $ret;));*;
}
}
trampolines!(
pub fn fastcall_with_keywords(
slf: *mut ffi::PyObject,
args: *const *mut ffi::PyObject,
nargs: ffi::Py_ssize_t,
kwnames: *mut ffi::PyObject,
) -> *mut ffi::PyObject;
pub fn cfunction_with_keywords(
slf: *mut ffi::PyObject,
args: *mut ffi::PyObject,
kwargs: *mut ffi::PyObject,
) -> *mut ffi::PyObject;
);
#[inline]
pub unsafe fn getter(
slf: *mut ffi::PyObject,
closure: *mut c_void,
f: for<'py> unsafe fn(Python<'py>, *mut ffi::PyObject) -> PyResult<*mut ffi::PyObject>,
) -> *mut ffi::PyObject {
// PyO3 doesn't use the closure argument at present.
debug_assert!(closure.is_null());
trampoline_inner(|py| f(py, slf))
}
#[inline]
pub unsafe fn setter(
slf: *mut ffi::PyObject,
value: *mut ffi::PyObject,
closure: *mut c_void,
f: for<'py> unsafe fn(Python<'py>, *mut ffi::PyObject, *mut ffi::PyObject) -> PyResult<c_int>,
) -> c_int {
// PyO3 doesn't use the closure argument at present.
debug_assert!(closure.is_null());
trampoline_inner(|py| f(py, slf, value))
}
// Trampolines used by slot methods
trampolines!(
pub fn getattrofunc(slf: *mut ffi::PyObject, attr: *mut ffi::PyObject) -> *mut ffi::PyObject;
pub fn setattrofunc(
slf: *mut ffi::PyObject,
attr: *mut ffi::PyObject,
value: *mut ffi::PyObject,
) -> c_int;
pub fn binaryfunc(slf: *mut ffi::PyObject, arg1: *mut ffi::PyObject) -> *mut ffi::PyObject;
pub fn descrgetfunc(
slf: *mut ffi::PyObject,
arg1: *mut ffi::PyObject,
arg2: *mut ffi::PyObject,
) -> *mut ffi::PyObject;
pub fn getiterfunc(slf: *mut ffi::PyObject) -> *mut ffi::PyObject;
pub fn hashfunc(slf: *mut ffi::PyObject) -> ffi::Py_hash_t;
pub fn inquiry(slf: *mut ffi::PyObject) -> c_int;
pub fn iternextfunc(slf: *mut ffi::PyObject) -> *mut ffi::PyObject;
pub fn lenfunc(slf: *mut ffi::PyObject) -> ffi::Py_ssize_t;
pub fn newfunc(
subtype: *mut ffi::PyTypeObject,
args: *mut ffi::PyObject,
kwargs: *mut ffi::PyObject,
) -> *mut ffi::PyObject;
pub fn objobjproc(slf: *mut ffi::PyObject, arg1: *mut ffi::PyObject) -> c_int;
pub fn reprfunc(slf: *mut ffi::PyObject) -> *mut ffi::PyObject;
pub fn richcmpfunc(
slf: *mut ffi::PyObject,
other: *mut ffi::PyObject,
op: c_int,
) -> *mut ffi::PyObject;
pub fn ssizeargfunc(arg1: *mut ffi::PyObject, arg2: ffi::Py_ssize_t) -> *mut ffi::PyObject;
pub fn ternaryfunc(
slf: *mut ffi::PyObject,
arg1: *mut ffi::PyObject,
arg2: *mut ffi::PyObject,
) -> *mut ffi::PyObject;
pub fn unaryfunc(slf: *mut ffi::PyObject) -> *mut ffi::PyObject;
pub fn dealloc(slf: *mut ffi::PyObject) -> ();
);
#[cfg(any(not(Py_LIMITED_API), Py_3_11))]
trampolines! {
pub fn getbufferproc(slf: *mut ffi::PyObject, buf: *mut ffi::Py_buffer, flags: c_int) -> c_int;
pub fn releasebufferproc(slf: *mut ffi::PyObject, buf: *mut ffi::Py_buffer) -> ();
}
// Ipowfunc is a unique case where PyO3 has its own type
// to workaround a problem on 3.7 (see IPowModulo type definition).
// Once 3.7 support dropped can just remove this.
trampoline!(
pub fn ipowfunc(
arg1: *mut ffi::PyObject,
arg2: *mut ffi::PyObject,
arg3: IPowModulo,
) -> *mut ffi::PyObject;
);
/// Implementation of trampoline functions, which sets up a GILPool and calls F.
///
/// Panics during execution are trapped so that they don't propagate through any
/// outer FFI boundary.
#[inline]
pub(crate) fn trampoline_inner<F, R>(body: F) -> R
where
F: for<'py> FnOnce(Python<'py>) -> PyResult<R> + UnwindSafe,
R: PyCallbackOutput,
{
let trap = PanicTrap::new("uncaught panic at ffi boundary");
let pool = unsafe { GILPool::new() };
let py = pool.python();
let out = panic_result_into_callback_output(
py,
panic::catch_unwind(move || -> PyResult<_> { body(py) }),
);
trap.disarm();
out
}
/// Converts the output of std::panic::catch_unwind into a Python function output, either by raising a Python
/// exception or by unwrapping the contained success output.
#[inline]
fn panic_result_into_callback_output<R>(
py: Python<'_>,
panic_result: Result<PyResult<R>, Box<dyn Any + Send + 'static>>,
) -> R
where
R: PyCallbackOutput,
{
let py_err = match panic_result {
Ok(Ok(value)) => return value,
Ok(Err(py_err)) => py_err,
Err(payload) => PanicException::from_panic_payload(payload),
};
py_err.restore(py);
R::ERR_VALUE
}

View File

@ -55,25 +55,20 @@
//! # }
//! # }
//! #
//! // This function is exported to Python.
//! // The function which is exported to Python looks roughly like the following
//! unsafe extern "C" fn __pymethod_increment__(
//! _slf: *mut pyo3::ffi::PyObject,
//! _args: *mut pyo3::ffi::PyObject,
//! ) -> *mut pyo3::ffi::PyObject {
//! use :: pyo3 as _pyo3;
//! let gil = _pyo3::GILPool::new();
//! let _py = gil.python();
//! _pyo3::callback::panic_result_into_callback_output(
//! _py,
//! ::std::panic::catch_unwind(move || -> _pyo3::PyResult<_> {
//! let _cell = _py
//! .from_borrowed_ptr::<_pyo3::PyAny>(_slf)
//! .downcast::<_pyo3::PyCell<Number>>()?;
//! let mut _ref = _cell.try_borrow_mut()?;
//! let _slf: &mut Number = &mut *_ref;
//! _pyo3::callback::convert(_py, Number::increment(_slf))
//! }),
//! )
//! _pyo3::impl_::trampoline::noargs(_slf, _args, |py, _slf| {
//! let _cell = py
//! .from_borrowed_ptr::<_pyo3::PyAny>(_slf)
//! .downcast::<_pyo3::PyCell<Number>>()?;
//! let mut _ref = _cell.try_borrow_mut()?;
//! let _slf: &mut Number = &mut *_ref;
//! _pyo3::callback::convert(py, Number::increment(_slf))
//! })
//! }
//! ```
//!

View File

@ -600,8 +600,8 @@ pub(crate) unsafe extern "C" fn no_constructor_defined(
_args: *mut ffi::PyObject,
_kwds: *mut ffi::PyObject,
) -> *mut ffi::PyObject {
crate::callback_body!(py, {
Err::<(), _>(crate::exceptions::PyTypeError::new_err(
crate::impl_::trampoline::trampoline_inner(|_| {
Err(crate::exceptions::PyTypeError::new_err(
"No constructor defined",
))
})

View File

@ -27,14 +27,20 @@ where
F: Fn(&types::PyTuple, Option<&types::PyDict>) -> R + Send + 'static,
R: crate::callback::IntoPyCallbackOutput<*mut ffi::PyObject>,
{
crate::callback_body!(py, {
let boxed_fn: &F =
&*(ffi::PyCapsule_GetPointer(capsule_ptr, CLOSURE_CAPSULE_NAME.as_ptr() as *const _)
as *mut F);
let args = py.from_borrowed_ptr::<types::PyTuple>(args);
let kwargs = py.from_borrowed_ptr_or_opt::<types::PyDict>(kwargs);
boxed_fn(args, kwargs)
})
crate::impl_::trampoline::cfunction_with_keywords(
capsule_ptr,
args,
kwargs,
|py, capsule_ptr, args, kwargs| {
let boxed_fn: &F = &*(ffi::PyCapsule_GetPointer(
capsule_ptr,
CLOSURE_CAPSULE_NAME.as_ptr() as *const _,
) as *mut F);
let args = py.from_borrowed_ptr::<types::PyTuple>(args);
let kwargs = py.from_borrowed_ptr_or_opt::<types::PyDict>(kwargs);
crate::callback::convert(py, boxed_fn(args, kwargs))
},
)
}
unsafe extern "C" fn drop_closure<F, R>(capsule_ptr: *mut ffi::PyObject)

View File

@ -1,11 +1,10 @@
error[E0597]: `gil` does not live long enough
error: lifetime may not live long enough
--> tests/ui/static_ref.rs:4:1
|
4 | #[pyfunction]
| ^^^^^^^^^^^^-
| | |
| | `gil` dropped here while still borrowed
| borrowed value does not live long enough
| cast requires that `gil` is borrowed for `'static`
| ^^^^^^^^^^^^^
| |
| lifetime `'py` defined here
| cast requires that `'py` must outlive `'static`
|
= note: this error originates in the attribute macro `pyfunction` (in Nightly builds, run with -Z macro-backtrace for more info)