Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use dynamic trampoline for all getters and setters #3029

Merged
merged 1 commit into from
May 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions newsfragments/3029.changed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Change `#[getter]` and `#[setter]` to use a common call "trampoline" to slightly reduce generated code size and compile times.
32 changes: 2 additions & 30 deletions pyo3-macros-backend/src/pymethod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -583,22 +583,7 @@ pub fn impl_py_setter_def(
#deprecations
_pyo3::class::PySetterDef::new(
#python_name,
_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
}),
_pyo3::impl_::pymethods::PySetter(#cls::#wrapper_ident),
#doc
)
})
Expand Down Expand Up @@ -719,20 +704,7 @@ pub fn impl_py_getter_def(
#deprecations
_pyo3::class::PyGetterDef::new(
#python_name,
_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
}),
_pyo3::impl_::pymethods::PyGetter(#cls::#wrapper_ident),
#doc
)
})
Expand Down
14 changes: 9 additions & 5 deletions src/impl_/pyclass/lazy_type_object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ use std::{
use crate::{
exceptions::PyRuntimeError,
ffi,
pyclass::create_type_object,
pyclass::{create_type_object, PyClassTypeObject},
sync::{GILOnceCell, GILProtected},
types::PyType,
AsPyPointer, IntoPyPointer, Py, PyClass, PyErr, PyMethodDefType, PyObject, PyResult, Python,
AsPyPointer, IntoPyPointer, PyClass, PyErr, PyMethodDefType, PyObject, PyResult, Python,
};

use super::PyClassItemsIter;
Expand All @@ -23,7 +23,7 @@ pub struct LazyTypeObject<T>(LazyTypeObjectInner, PhantomData<T>);

// Non-generic inner of LazyTypeObject to keep code size down
struct LazyTypeObjectInner {
value: GILOnceCell<Py<PyType>>,
value: GILOnceCell<PyClassTypeObject>,
// Threads which have begun initialization of the `tp_dict`. Used for
// reentrant initialization detection.
initializing_threads: GILProtected<RefCell<Vec<ThreadId>>>,
Expand Down Expand Up @@ -67,12 +67,16 @@ impl LazyTypeObjectInner {
fn get_or_try_init<'py>(
&'py self,
py: Python<'py>,
init: fn(Python<'py>) -> PyResult<Py<PyType>>,
init: fn(Python<'py>) -> PyResult<PyClassTypeObject>,
name: &str,
items_iter: PyClassItemsIter,
) -> PyResult<&'py PyType> {
(|| -> PyResult<_> {
let type_object = self.value.get_or_try_init(py, || init(py))?.as_ref(py);
let type_object = self
.value
.get_or_try_init(py, || init(py))?
.type_object
.as_ref(py);
self.ensure_init(type_object, name, items_iter)?;
Ok(type_object)
})()
Expand Down
73 changes: 22 additions & 51 deletions src/impl_/pymethods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ impl IPowModulo {

/// `PyMethodDefType` represents different types of Python callable objects.
/// It is used by the `#[pymethods]` attribute.
#[derive(Debug)]
pub enum PyMethodDefType {
/// Represents class method
Class(PyMethodDef),
Expand Down Expand Up @@ -72,10 +71,10 @@ pub struct PyCFunctionWithKeywords(pub ffi::PyCFunctionWithKeywords);
#[cfg(not(Py_LIMITED_API))]
#[derive(Clone, Copy, Debug)]
pub struct PyCFunctionFastWithKeywords(pub ffi::_PyCFunctionFastWithKeywords);
#[derive(Clone, Copy, Debug)]
pub struct PyGetter(pub ffi::getter);
#[derive(Clone, Copy, Debug)]
pub struct PySetter(pub ffi::setter);
#[derive(Clone, Copy)]
pub struct PyGetter(pub Getter);
#[derive(Clone, Copy)]
pub struct PySetter(pub Setter);
#[derive(Clone, Copy)]
pub struct PyClassAttributeFactory(pub for<'p> fn(Python<'p>) -> PyResult<PyObject>);

Expand All @@ -102,18 +101,18 @@ impl PyClassAttributeDef {
}
}

#[derive(Clone, Debug)]
#[derive(Clone)]
pub struct PyGetterDef {
pub(crate) name: &'static str,
pub(crate) meth: PyGetter,
doc: &'static str,
pub(crate) doc: &'static str,
}

#[derive(Clone, Debug)]
#[derive(Clone)]
pub struct PySetterDef {
pub(crate) name: &'static str,
pub(crate) meth: PySetter,
doc: &'static str,
pub(crate) doc: &'static str,
}

unsafe impl Sync for PyMethodDef {}
Expand Down Expand Up @@ -212,6 +211,12 @@ impl fmt::Debug for PyClassAttributeDef {
}
}

/// Class getter / setters
pub(crate) type Getter =
for<'py> unsafe fn(Python<'py>, *mut ffi::PyObject) -> PyResult<*mut ffi::PyObject>;
pub(crate) type Setter =
for<'py> unsafe fn(Python<'py>, *mut ffi::PyObject, *mut ffi::PyObject) -> PyResult<c_int>;

impl PyGetterDef {
/// Define a getter.
pub const fn new(name: &'static str, getter: PyGetter, doc: &'static str) -> Self {
Expand All @@ -221,23 +226,6 @@ impl PyGetterDef {
doc,
}
}

/// Copy descriptor information to `ffi::PyGetSetDef`
pub fn copy_to(&self, dst: &mut ffi::PyGetSetDef) {
if dst.name.is_null() {
let name = get_name(self.name).unwrap();
dst.name = name.as_ptr() as _;
// FIXME: stop leaking name
std::mem::forget(name);
}
if dst.doc.is_null() {
let doc = get_doc(self.doc).unwrap();
dst.doc = doc.as_ptr() as _;
// FIXME: stop leaking doc
std::mem::forget(doc);
}
dst.get = Some(self.meth.0);
}
}

impl PySetterDef {
Expand All @@ -249,31 +237,6 @@ impl PySetterDef {
doc,
}
}

/// Copy descriptor information to `ffi::PyGetSetDef`
pub fn copy_to(&self, dst: &mut ffi::PyGetSetDef) {
if dst.name.is_null() {
let name = get_name(self.name).unwrap();
dst.name = name.as_ptr() as _;
// FIXME: stop leaking name
std::mem::forget(name);
}
if dst.doc.is_null() {
let doc = get_doc(self.doc).unwrap();
dst.doc = doc.as_ptr() as _;
// FIXME: stop leaking doc
std::mem::forget(doc);
}
dst.set = Some(self.meth.0);
}
}

fn get_name(name: &'static str) -> PyResult<Cow<'static, CStr>> {
extract_c_string(name, "Function name cannot contain NUL byte.")
}

fn get_doc(doc: &'static str) -> PyResult<Cow<'static, CStr>> {
extract_c_string(doc, "Document cannot contain NUL byte.")
}

/// Unwraps the result of __traverse__ for tp_traverse
Expand Down Expand Up @@ -319,3 +282,11 @@ where
self.map(|o| o.into_py(py))
}
}

pub(crate) fn get_name(name: &'static str) -> PyResult<Cow<'static, CStr>> {
extract_c_string(name, "function name cannot contain NUL byte.")
}

pub(crate) fn get_doc(doc: &'static str) -> PyResult<Cow<'static, CStr>> {
extract_c_string(doc, "function doc cannot contain NUL byte.")
}
25 changes: 1 addition & 24 deletions src/impl_/trampoline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

use std::{
any::Any,
os::raw::{c_int, c_void},
os::raw::c_int,
panic::{self, UnwindSafe},
};

Expand Down Expand Up @@ -64,29 +64,6 @@ trampolines!(
) -> *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;
Expand Down
2 changes: 1 addition & 1 deletion src/pyclass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use std::{cmp::Ordering, os::raw::c_int};
mod create_type_object;
mod gc;

pub(crate) use self::create_type_object::create_type_object;
pub(crate) use self::create_type_object::{create_type_object, PyClassTypeObject};
pub use self::gc::{PyTraverseError, PyVisit};

/// Types that can be used as Python classes.
Expand Down