Skip to content

Commit

Permalink
use dynamic trampoline for all getters and setters
Browse files Browse the repository at this point in the history
  • Loading branch information
davidhewitt committed Mar 10, 2023
1 parent 4b0b9b9 commit 0acca88
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 121 deletions.
32 changes: 2 additions & 30 deletions pyo3-macros-backend/src/pymethod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -580,22 +580,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 @@ -716,20 +701,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
19 changes: 13 additions & 6 deletions src/impl_/pyclass/lazy_type_object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@ use std::{
use parking_lot::{const_mutex, Mutex};

use crate::{
exceptions::PyRuntimeError, ffi, once_cell::GILOnceCell, pyclass::create_type_object,
types::PyType, AsPyPointer, IntoPyPointer, Py, PyClass, PyErr, PyMethodDefType, PyObject,
PyResult, Python,
exceptions::PyRuntimeError,
ffi,
once_cell::GILOnceCell,
pyclass::{create_type_object, PyClassTypeObject},
types::PyType,
AsPyPointer, IntoPyPointer, PyClass, PyErr, PyMethodDefType, PyObject, PyResult, Python,
};

use super::PyClassItemsIter;
Expand All @@ -21,7 +24,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: Mutex<Vec<ThreadId>>,
Expand Down Expand Up @@ -65,12 +68,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
106 changes: 62 additions & 44 deletions src/impl_/pymethods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ use crate::exceptions::PyValueError;
use crate::{ffi, IntoPy, Py, PyAny, PyErr, PyObject, PyResult, PyTraverseError, Python};
use std::borrow::Cow;
use std::ffi::{CStr, CString};
use std::fmt;
use std::os::raw::c_int;
use std::{fmt, ptr};

/// Python 3.8 and up - __ipow__ has modulo argument correctly populated.
#[cfg(Py_3_8)]
Expand Down 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)]

Check warning on line 74 in src/impl_/pymethods.rs

View check run for this annotation

Codecov / codecov/patch

src/impl_/pymethods.rs#L74

Added line #L74 was not covered by tests
pub struct PyGetter(pub Getter);
#[derive(Clone, Copy)]

Check warning on line 76 in src/impl_/pymethods.rs

View check run for this annotation

Codecov / codecov/patch

src/impl_/pymethods.rs#L76

Added line #L76 was not covered by tests
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)]

Check warning on line 104 in src/impl_/pymethods.rs

View check run for this annotation

Codecov / codecov/patch

src/impl_/pymethods.rs#L104

Added line #L104 was not covered by tests
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)]

Check warning on line 111 in src/impl_/pymethods.rs

View check run for this annotation

Codecov / codecov/patch

src/impl_/pymethods.rs#L111

Added line #L111 was not covered by tests
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,59 @@ 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>;

/// Used as the "closure" argument for ffi::PyGetSetDef structures to support a single trampoline
/// for each of getters and setters.
#[derive(Default)]
pub(crate) struct GetSetDefClosure {
pub doc: Option<&'static str>,
pub getter: Option<Getter>,
pub setter: Option<Setter>,
}

impl GetSetDefClosure {
pub(crate) fn add_getter(&mut self, getter: &PyGetterDef) {
if self.doc.is_none() {
self.doc = Some(getter.doc);
}
self.getter = Some(getter.meth.0)
}

pub(crate) fn add_setter(&mut self, setter: &PySetterDef) {
if self.doc.is_none() {
self.doc = Some(setter.doc);
}
self.setter = Some(setter.meth.0)
}

pub(crate) fn as_get_set_def(&mut self, name: &'static str) -> PyResult<ffi::PyGetSetDef> {
let name = get_name(name)?;

let mut getset_def = ffi::PyGetSetDef {
name: name.as_ptr(),
get: self.getter.map(|_| crate::impl_::trampoline::getter as _),
set: self.setter.map(|_| crate::impl_::trampoline::setter as _),
doc: ptr::null(),
closure: (self as *mut GetSetDefClosure).cast(),
};

std::mem::forget(name);

if let Some(doc) = self.doc {
let doc = get_doc(doc)?;
getset_def.doc = doc.as_ptr();
std::mem::forget(doc)
}

Check warning on line 261 in src/impl_/pymethods.rs

View check run for this annotation

Codecov / codecov/patch

src/impl_/pymethods.rs#L261

Added line #L261 was not covered by tests

Ok(getset_def)
}
}

impl PyGetterDef {
/// Define a getter.
pub const fn new(name: &'static str, getter: PyGetter, doc: &'static str) -> Self {
Expand All @@ -221,23 +273,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,23 +284,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>> {
Expand Down
37 changes: 25 additions & 12 deletions src/impl_/trampoline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,13 @@ use std::{
};

use crate::{
callback::PyCallbackOutput, ffi, impl_::panic::PanicTrap, methods::IPowModulo,
panic::PanicException, types::PyModule, GILPool, IntoPyPointer, Py, PyResult, Python,
callback::PyCallbackOutput,
ffi,
impl_::{panic::PanicTrap, pymethods::GetSetDefClosure},
methods::IPowModulo,
panic::PanicException,
types::PyModule,
GILPool, IntoPyPointer, Py, PyResult, Python,
};

#[inline]
Expand Down Expand Up @@ -65,26 +70,34 @@ trampolines!(
);

#[inline]
pub unsafe fn getter(
pub(crate) unsafe extern "C" 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))
// Safety: PyO3 sets the closure when constructing the ffi getter so this cast should always be valid
let closure: &GetSetDefClosure = &*(closure as *const GetSetDefClosure);
// Safety: PyO3 sets the getter field when constructing the ffi getter
let getter = match closure.getter {
Some(getter) => getter,
None => std::hint::unreachable_unchecked(),

Check warning on line 82 in src/impl_/trampoline.rs

View check run for this annotation

Codecov / codecov/patch

src/impl_/trampoline.rs#L82

Added line #L82 was not covered by tests
};
trampoline_inner(|py| getter(py, slf))
}

#[inline]
pub unsafe fn setter(
pub(crate) unsafe extern "C" 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))
// Safety: PyO3 sets the closure when constructing the ffi setter so this cast should always be valid
let closure: &GetSetDefClosure = &*(closure as *const GetSetDefClosure);
// Safety: PyO3 sets the setter field when constructing the ffi setter
let setter = match closure.setter {
Some(setter) => setter,
None => std::hint::unreachable_unchecked(),

Check warning on line 98 in src/impl_/trampoline.rs

View check run for this annotation

Codecov / codecov/patch

src/impl_/trampoline.rs#L98

Added line #L98 was not covered by tests
};
trampoline_inner(|py| setter(py, slf, value))
}

// Trampolines used by slot methods
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

0 comments on commit 0acca88

Please sign in to comment.