Skip to content

Commit 90e8ef7

Browse files
committed
opt: reduce generated code for type object creation
1 parent 24eea5d commit 90e8ef7

File tree

2 files changed

+75
-33
lines changed

2 files changed

+75
-33
lines changed

src/pyclass.rs

Lines changed: 74 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! `PyClass` and related traits.
22
use crate::{
3-
class::impl_::{fallback_new, tp_dealloc, PyClassImpl},
3+
class::impl_::{fallback_new, tp_dealloc, PyClassImpl, PyBufferProcs},
44
ffi,
55
pyclass_slots::{PyClassDict, PyClassWeakRef},
66
PyCell, PyErr, PyMethodDefType, PyNativeType, PyResult, PyTypeInfo, Python,
@@ -35,11 +35,53 @@ fn into_raw<T>(vec: Vec<T>) -> *mut c_void {
3535

3636
pub(crate) fn create_type_object<T>(
3737
py: Python,
38-
module_name: Option<&str>,
39-
) -> PyResult<*mut ffi::PyTypeObject>
38+
) -> *mut ffi::PyTypeObject
4039
where
4140
T: PyClass,
4241
{
42+
match unsafe { create_type_object_impl(
43+
py,
44+
T::DOC,
45+
T::MODULE,
46+
T::NAME,
47+
T::BaseType::type_object_raw(py),
48+
std::mem::size_of::<T::Layout>(),
49+
T::get_new(),
50+
tp_dealloc::<T>,
51+
T::get_alloc(),
52+
T::get_free(),
53+
PyCell::<T>::dict_offset(),
54+
PyCell::<T>::weakref_offset(),
55+
&T::for_each_method_def,
56+
&T::for_each_proto_slot,
57+
T::IS_GC,
58+
T::IS_BASETYPE,
59+
T::get_buffer(),
60+
) } {
61+
Ok(type_object) => type_object,
62+
Err(e) => type_object_creation_failed(py, e, T::NAME),
63+
}
64+
}
65+
66+
unsafe fn create_type_object_impl(
67+
py: Python,
68+
tp_doc: &str,
69+
module_name: Option<&str>,
70+
name: &str,
71+
base_type_object: *mut ffi::PyTypeObject,
72+
basicsize: usize,
73+
tp_new: Option<ffi::newfunc>,
74+
tp_dealloc: ffi::destructor,
75+
tp_alloc: Option<ffi::allocfunc>,
76+
tp_free: Option<ffi::freefunc>,
77+
dict_offset: Option<ffi::Py_ssize_t>,
78+
weakref_offset: Option<ffi::Py_ssize_t>,
79+
for_each_method_def: &dyn Fn(&mut dyn FnMut(&[PyMethodDefType])),
80+
for_each_proto_slot: &dyn Fn(&mut dyn FnMut(&[ffi::PyType_Slot])),
81+
is_gc: bool,
82+
is_basetype: bool,
83+
buffer_procs: Option<&PyBufferProcs>,
84+
) -> PyResult<*mut ffi::PyTypeObject> {
4385
let mut slots = Vec::new();
4486

4587
fn push_slot(slots: &mut Vec<ffi::PyType_Slot>, slot: c_int, pfunc: *mut c_void) {
@@ -49,49 +91,49 @@ where
4991
push_slot(
5092
&mut slots,
5193
ffi::Py_tp_base,
52-
T::BaseType::type_object_raw(py) as _,
94+
base_type_object as _,
5395
);
54-
if let Some(doc) = py_class_doc(T::DOC) {
96+
if let Some(doc) = py_class_doc(tp_doc) {
5597
push_slot(&mut slots, ffi::Py_tp_doc, doc as _);
5698
}
5799

58100
push_slot(
59101
&mut slots,
60102
ffi::Py_tp_new,
61-
T::get_new().unwrap_or(fallback_new) as _,
103+
tp_new.unwrap_or(fallback_new) as _,
62104
);
63-
push_slot(&mut slots, ffi::Py_tp_dealloc, tp_dealloc::<T> as _);
105+
push_slot(&mut slots, ffi::Py_tp_dealloc, tp_dealloc as _);
64106

65-
if let Some(alloc) = T::get_alloc() {
107+
if let Some(alloc) = tp_alloc {
66108
push_slot(&mut slots, ffi::Py_tp_alloc, alloc as _);
67109
}
68-
if let Some(free) = T::get_free() {
110+
if let Some(free) = tp_free {
69111
push_slot(&mut slots, ffi::Py_tp_free, free as _);
70112
}
71113

72114
#[cfg(Py_3_9)]
73115
{
74-
let members = py_class_members(PyCell::<T>::dict_offset(), PyCell::<T>::weakref_offset());
116+
let members = py_class_members(dict_offset, weakref_offset);
75117
if !members.is_empty() {
76118
push_slot(&mut slots, ffi::Py_tp_members, into_raw(members))
77119
}
78120
}
79121

80122
// normal methods
81-
let methods = py_class_method_defs(&T::for_each_method_def);
123+
let methods = py_class_method_defs(for_each_method_def);
82124
if !methods.is_empty() {
83125
push_slot(&mut slots, ffi::Py_tp_methods, into_raw(methods));
84126
}
85127

86128
// properties
87-
let props = py_class_properties(T::Dict::IS_DUMMY, &T::for_each_method_def);
129+
let props = py_class_properties(dict_offset.is_none(), for_each_method_def);
88130
if !props.is_empty() {
89131
push_slot(&mut slots, ffi::Py_tp_getset, into_raw(props));
90132
}
91133

92134
// protocol methods
93135
let mut has_gc_methods = false;
94-
T::for_each_proto_slot(&mut |proto_slots| {
136+
for_each_proto_slot(&mut |proto_slots| {
95137
has_gc_methods |= proto_slots
96138
.iter()
97139
.any(|slot| slot.slot == ffi::Py_tp_clear || slot.slot == ffi::Py_tp_traverse);
@@ -100,41 +142,47 @@ where
100142

101143
push_slot(&mut slots, 0, ptr::null_mut());
102144
let mut spec = ffi::PyType_Spec {
103-
name: py_class_qualified_name(module_name, T::NAME)?,
104-
basicsize: std::mem::size_of::<T::Layout>() as c_int,
145+
name: py_class_qualified_name(module_name, name)?,
146+
basicsize: basicsize as c_int,
105147
itemsize: 0,
106-
flags: py_class_flags(has_gc_methods, T::IS_GC, T::IS_BASETYPE),
148+
flags: py_class_flags(has_gc_methods, is_gc, is_basetype),
107149
slots: slots.as_mut_ptr(),
108150
};
109151

110-
let type_object = unsafe { ffi::PyType_FromSpec(&mut spec) };
152+
let type_object = ffi::PyType_FromSpec(&mut spec);
111153
if type_object.is_null() {
112154
Err(PyErr::fetch(py))
113155
} else {
114-
tp_init_additional::<T>(type_object as _);
156+
tp_init_additional(type_object as _, tp_doc, buffer_procs, dict_offset, weakref_offset);
115157
Ok(type_object as _)
116158
}
117159
}
118160

161+
#[cold]
162+
fn type_object_creation_failed(py: Python, e: PyErr, name: &'static str) -> ! {
163+
e.print(py);
164+
panic!("An error occurred while initializing class {}", name)
165+
}
166+
119167
/// Additional type initializations necessary before Python 3.10
120168
#[cfg(all(not(Py_LIMITED_API), not(Py_3_10)))]
121-
fn tp_init_additional<T: PyClass>(type_object: *mut ffi::PyTypeObject) {
169+
fn tp_init_additional(type_object: *mut ffi::PyTypeObject, tp_doc: &str, buffer_procs: Option<&PyBufferProcs>, dict_offset: Option<ffi::Py_ssize_t>, weakref_offset: Option<ffi::Py_ssize_t>) {
122170
// Just patch the type objects for the things there's no
123171
// PyType_FromSpec API for... there's no reason this should work,
124172
// except for that it does and we have tests.
125173

126174
// Running this causes PyPy to segfault.
127175
#[cfg(all(not(PyPy), not(Py_3_10)))]
128176
{
129-
if T::DOC != "\0" {
177+
if tp_doc != "\0" {
130178
unsafe {
131179
// Until CPython 3.10, tp_doc was treated specially for
132180
// heap-types, and it removed the text_signature value from it.
133181
// We go in after the fact and replace tp_doc with something
134182
// that _does_ include the text_signature value!
135183
ffi::PyObject_Free((*type_object).tp_doc as _);
136-
let data = ffi::PyObject_Malloc(T::DOC.len());
137-
data.copy_from(T::DOC.as_ptr() as _, T::DOC.len());
184+
let data = ffi::PyObject_Malloc(tp_doc.len());
185+
data.copy_from(tp_doc.as_ptr() as _, tp_doc.len());
138186
(*type_object).tp_doc = data as _;
139187
}
140188
}
@@ -144,7 +192,7 @@ fn tp_init_additional<T: PyClass>(type_object: *mut ffi::PyTypeObject) {
144192
// must manually fixup the type object.
145193
#[cfg(not(Py_3_9))]
146194
{
147-
if let Some(buffer) = T::get_buffer() {
195+
if let Some(buffer) = buffer_procs {
148196
unsafe {
149197
(*(*type_object).tp_as_buffer).bf_getbuffer = buffer.bf_getbuffer;
150198
(*(*type_object).tp_as_buffer).bf_releasebuffer = buffer.bf_releasebuffer;
@@ -157,13 +205,13 @@ fn tp_init_additional<T: PyClass>(type_object: *mut ffi::PyTypeObject) {
157205
#[cfg(not(Py_3_9))]
158206
{
159207
// __dict__ support
160-
if let Some(dict_offset) = PyCell::<T>::dict_offset() {
208+
if let Some(dict_offset) = dict_offset {
161209
unsafe {
162210
(*type_object).tp_dictoffset = dict_offset;
163211
}
164212
}
165213
// weakref support
166-
if let Some(weakref_offset) = PyCell::<T>::weakref_offset() {
214+
if let Some(weakref_offset) = weakref_offset {
167215
unsafe {
168216
(*type_object).tp_weaklistoffset = weakref_offset;
169217
}
@@ -172,7 +220,7 @@ fn tp_init_additional<T: PyClass>(type_object: *mut ffi::PyTypeObject) {
172220
}
173221

174222
#[cfg(any(Py_LIMITED_API, Py_3_10))]
175-
fn tp_init_additional<T: PyClass>(_type_object: *mut ffi::PyTypeObject) {}
223+
fn tp_init_additional(_type_object: *mut ffi::PyTypeObject, tp_doc: &str, buffer_procs: Option<&PyBufferProcs>, dict_offset: Option<ffi::Py_ssize_t>, weakref_offset: Option<ffi::Py_ssize_t>) {}
176224

177225
fn py_class_doc(class_doc: &str) -> Option<*mut c_char> {
178226
match class_doc {

src/type_object.rs

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -107,13 +107,7 @@ impl LazyStaticType {
107107
}
108108

109109
pub fn get_or_init<T: PyClass>(&self, py: Python) -> *mut ffi::PyTypeObject {
110-
let type_object = *self.value.get_or_init(py, || {
111-
create_type_object::<T>(py, T::MODULE).unwrap_or_else(|e| {
112-
e.print(py);
113-
panic!("An error occurred while initializing class {}", T::NAME)
114-
})
115-
});
116-
110+
let type_object = *self.value.get_or_init(py, || create_type_object::<T>(py));
117111
self.ensure_init(py, type_object, T::NAME, &T::for_each_method_def);
118112
type_object
119113
}

0 commit comments

Comments
 (0)