Reduce size of compiled code for class initialization

This commit is contained in:
Tim Robinson 2021-05-13 11:11:05 +01:00
parent 95636f1ba7
commit c3b935f06c
5 changed files with 37 additions and 23 deletions

View File

@ -757,7 +757,7 @@ impl pyo3::class::impl_::PyClassImpl for MyClass {
type BaseType = PyAny;
type ThreadChecker = pyo3::class::impl_::ThreadCheckerStub<MyClass>;
fn for_each_method_def(visitor: impl FnMut(&pyo3::class::PyMethodDefType)) {
fn for_each_method_def(visitor: &mut dyn FnMut(&pyo3::class::PyMethodDefType)) {
use pyo3::class::impl_::*;
let collector = PyClassImplCollector::<MyClass>::new();
collector.py_methods().iter()

View File

@ -436,7 +436,7 @@ fn impl_class(
type BaseType = #base;
type ThreadChecker = #thread_checker;
fn for_each_method_def(visitor: impl FnMut(&pyo3::class::PyMethodDefType)) {
fn for_each_method_def(visitor: &mut dyn FnMut(&pyo3::class::PyMethodDefType)) {
use pyo3::class::impl_::*;
let collector = PyClassImplCollector::<Self>::new();
#iter_py_methods

View File

@ -65,7 +65,7 @@ pub trait PyClassImpl: Sized {
/// can be accessed by multiple threads by `threading` module.
type ThreadChecker: PyClassThreadChecker<Self>;
fn for_each_method_def(_visitor: impl FnMut(&PyMethodDefType)) {}
fn for_each_method_def(_visitor: &mut dyn FnMut(&PyMethodDefType)) {}
fn get_new() -> Option<ffi::newfunc> {
None
}

View File

@ -206,13 +206,13 @@ where
}
// normal methods
let methods = py_class_method_defs::<T>();
let methods = py_class_method_defs(&T::for_each_method_def);
if !methods.is_empty() {
slots.push(ffi::Py_tp_methods, into_raw(methods));
}
// properties
let props = py_class_properties::<T>();
let props = py_class_properties(T::Dict::IS_DUMMY, &T::for_each_method_def);
if !props.is_empty() {
slots.push(ffi::Py_tp_getset, into_raw(props));
}
@ -230,7 +230,7 @@ where
name: get_type_name::<T>(module_name)?,
basicsize: std::mem::size_of::<T::Layout>() as c_int,
itemsize: 0,
flags: py_class_flags::<T>(has_gc_methods),
flags: py_class_flags(has_gc_methods, T::IS_GC, T::IS_BASETYPE),
slots: slots.0.as_mut_ptr(),
};
@ -299,22 +299,24 @@ fn tp_init_additional<T: PyClass>(type_object: *mut ffi::PyTypeObject) {
#[cfg(any(Py_LIMITED_API, Py_3_10))]
fn tp_init_additional<T: PyClass>(_type_object: *mut ffi::PyTypeObject) {}
fn py_class_flags<T: PyClass + PyTypeInfo>(has_gc_methods: bool) -> c_uint {
let mut flags = if has_gc_methods || T::IS_GC {
fn py_class_flags(has_gc_methods: bool, is_gc: bool, is_basetype: bool) -> c_uint {
let mut flags = if has_gc_methods || is_gc {
ffi::Py_TPFLAGS_DEFAULT | ffi::Py_TPFLAGS_HAVE_GC
} else {
ffi::Py_TPFLAGS_DEFAULT
};
if T::IS_BASETYPE {
if is_basetype {
flags |= ffi::Py_TPFLAGS_BASETYPE;
}
flags.try_into().unwrap()
}
fn py_class_method_defs<T: PyClassImpl>() -> Vec<ffi::PyMethodDef> {
fn py_class_method_defs(
for_each_method_def: &dyn Fn(&mut dyn FnMut(&PyMethodDefType)),
) -> Vec<ffi::PyMethodDef> {
let mut defs = Vec::new();
T::for_each_method_def(|def| match def {
for_each_method_def(&mut |def| match def {
PyMethodDefType::Method(def)
| PyMethodDefType::Class(def)
| PyMethodDefType::Static(def) => {
@ -381,10 +383,13 @@ const PY_GET_SET_DEF_INIT: ffi::PyGetSetDef = ffi::PyGetSetDef {
};
#[allow(clippy::clippy::collapsible_if)] // for if cfg!
fn py_class_properties<T: PyClass>() -> Vec<ffi::PyGetSetDef> {
fn py_class_properties(
is_dummy: bool,
for_each_method_def: &dyn Fn(&mut dyn FnMut(&PyMethodDefType)),
) -> Vec<ffi::PyGetSetDef> {
let mut defs = std::collections::HashMap::new();
T::for_each_method_def(|def| match def {
for_each_method_def(&mut |def| match def {
PyMethodDefType::Getter(getter) => {
getter.copy_to(defs.entry(getter.name).or_insert(PY_GET_SET_DEF_INIT));
}
@ -398,7 +403,7 @@ fn py_class_properties<T: PyClass>() -> Vec<ffi::PyGetSetDef> {
// PyPy doesn't automatically adds __dict__ getter / setter.
// PyObject_GenericGetDict not in the limited API until Python 3.10.
push_dict_getset::<T>(&mut props);
push_dict_getset(&mut props, is_dummy);
if !props.is_empty() {
props.push(unsafe { std::mem::zeroed() });
@ -407,8 +412,8 @@ fn py_class_properties<T: PyClass>() -> Vec<ffi::PyGetSetDef> {
}
#[cfg(not(any(PyPy, all(Py_LIMITED_API, not(Py_3_10)))))]
fn push_dict_getset<T: PyClass>(props: &mut Vec<ffi::PyGetSetDef>) {
if !T::Dict::IS_DUMMY {
fn push_dict_getset(props: &mut Vec<ffi::PyGetSetDef>, is_dummy: bool) {
if !is_dummy {
props.push(ffi::PyGetSetDef {
name: "__dict__\0".as_ptr() as *mut c_char,
get: Some(ffi::PyObject_GenericGetDict),
@ -420,4 +425,4 @@ fn push_dict_getset<T: PyClass>(props: &mut Vec<ffi::PyGetSetDef>) {
}
#[cfg(any(PyPy, all(Py_LIMITED_API, not(Py_3_10))))]
fn push_dict_getset<T: PyClass>(_: &mut Vec<ffi::PyGetSetDef>) {}
fn push_dict_getset(_: &mut Vec<ffi::PyGetSetDef>, _is_dummy: bool) {}

View File

@ -104,6 +104,17 @@ impl LazyStaticType {
})
});
self.ensure_init(py, type_object, T::NAME, &T::for_each_method_def);
type_object
}
fn ensure_init(
&self,
py: Python,
type_object: *mut ffi::PyTypeObject,
name: &str,
for_each_method_def: &dyn Fn(&mut dyn FnMut(&PyMethodDefType)),
) {
// We might want to fill the `tp_dict` with python instances of `T`
// itself. In order to do so, we must first initialize the type object
// with an empty `tp_dict`: now we can create instances of `T`.
@ -117,7 +128,7 @@ impl LazyStaticType {
if self.tp_dict_filled.get(py).is_some() {
// `tp_dict` is already filled: ok.
return type_object;
return;
}
{
@ -126,7 +137,7 @@ impl LazyStaticType {
if threads.contains(&thread_id) {
// Reentrant call: just return the type object, even if the
// `tp_dict` is not filled yet.
return type_object;
return;
}
threads.push(thread_id);
}
@ -136,7 +147,7 @@ impl LazyStaticType {
// means that another thread can continue the initialization in the
// meantime: at worst, we'll just make a useless computation.
let mut items = vec![];
T::for_each_method_def(|def| {
for_each_method_def(&mut |def| {
if let PyMethodDefType::ClassAttribute(attr) = def {
items.push((
extract_cstr_or_leak_cstring(
@ -162,10 +173,8 @@ impl LazyStaticType {
if let Err(err) = result {
err.clone_ref(py).print(py);
panic!("An error occured while initializing `{}.__dict__`", T::NAME);
panic!("An error occured while initializing `{}.__dict__`", name);
}
type_object
}
}