Skip to content

Commit 2503a2d

Browse files
authored
Merge pull request #2082 from davidhewitt/opt-create-type-object
opt: less generated code for `#[pyclass]` creation
2 parents 24eea5d + e33b3e6 commit 2503a2d

17 files changed

+342
-275
lines changed

Architecture.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ Since we need lots of boilerplate for implementing common traits for these types
102102

103103
[`src/pycell.rs`], [`src/pyclass.rs`], and [`src/type_object.rs`] contain types and
104104
traits to make `#[pyclass]` work.
105-
Also, [`src/pyclass_init.rs`] and [`src/pyclass_slots.rs`] have related functionalities.
105+
Also, [`src/pyclass_init.rs`] and [`src/impl_/pyclass.rs`] have related functionalities.
106106

107107
To realize object-oriented programming in C, all Python objects must have the following two fields
108108
at the beginning.

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
4444
- Reduce generated LLVM code size (to improve compile times) for:
4545
- internal `handle_panic` helper [#2074](https://github.com/PyO3/pyo3/pull/2074)
4646
- `#[pyfunction]` and `#[pymethods]` argument extraction [#2075](https://github.com/PyO3/pyo3/pull/2075)
47-
- `#[pyclass]` type object creation [#2076](https://github.com/PyO3/pyo3/pull/2076)
47+
- `#[pyclass]` type object creation [#2076](https://github.com/PyO3/pyo3/pull/2076) [#2081](https://github.com/PyO3/pyo3/pull/2081)
4848

4949
### Removed
5050

guide/src/class.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -838,8 +838,8 @@ unsafe impl pyo3::PyTypeInfo for MyClass {
838838
}
839839

840840
impl pyo3::pyclass::PyClass for MyClass {
841-
type Dict = pyo3::pyclass_slots::PyClassDummySlot;
842-
type WeakRef = pyo3::pyclass_slots::PyClassDummySlot;
841+
type Dict = pyo3::impl_::pyclass::PyClassDummySlot;
842+
type WeakRef = pyo3::impl_::pyclass::PyClassDummySlot;
843843
type BaseNativeType = PyAny;
844844
}
845845

pyo3-macros-backend/src/pyclass.rs

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -663,20 +663,16 @@ impl<'a> PyClassImplsBuilder<'a> {
663663
let cls = self.cls;
664664
let attr = self.attr;
665665
let dict = if attr.has_dict {
666-
quote! { _pyo3::pyclass_slots::PyClassDictSlot }
667-
} else if attr.has_extends {
668-
quote! { <Self::BaseType as _pyo3::class::impl_::PyClassBaseType>::Dict }
666+
quote! { _pyo3::impl_::pyclass::PyClassDictSlot }
669667
} else {
670-
quote! { _pyo3::pyclass_slots::PyClassDummySlot }
668+
quote! { _pyo3::impl_::pyclass::PyClassDummySlot }
671669
};
672670

673671
// insert space for weak ref
674672
let weakref = if attr.has_weaklist {
675-
quote! { _pyo3::pyclass_slots::PyClassWeakRefSlot }
676-
} else if attr.has_extends {
677-
quote! { <Self::BaseType as _pyo3::class::impl_::PyClassBaseType>::WeakRef }
673+
quote! { _pyo3::impl_::pyclass::PyClassWeakRefSlot }
678674
} else {
679-
quote! { _pyo3::pyclass_slots::PyClassDummySlot }
675+
quote! { _pyo3::impl_::pyclass::PyClassDummySlot }
680676
};
681677

682678
let base_nativetype = if attr.has_extends {
@@ -731,6 +727,27 @@ impl<'a> PyClassImplsBuilder<'a> {
731727
let base = &self.attr.base;
732728
let is_subclass = self.attr.has_extends;
733729

730+
let dict_offset = if self.attr.has_dict {
731+
quote! {
732+
fn dict_offset() -> ::std::option::Option<_pyo3::ffi::Py_ssize_t> {
733+
::std::option::Option::Some(_pyo3::impl_::pyclass::dict_offset::<Self>())
734+
}
735+
}
736+
} else {
737+
TokenStream::new()
738+
};
739+
740+
// insert space for weak ref
741+
let weaklist_offset = if self.attr.has_weaklist {
742+
quote! {
743+
fn weaklist_offset() -> ::std::option::Option<_pyo3::ffi::Py_ssize_t> {
744+
::std::option::Option::Some(_pyo3::impl_::pyclass::weaklist_offset::<Self>())
745+
}
746+
}
747+
} else {
748+
TokenStream::new()
749+
};
750+
734751
let thread_checker = if self.attr.has_unsendable {
735752
quote! { _pyo3::class::impl_::ThreadCheckerImpl<#cls> }
736753
} else if self.attr.has_extends {
@@ -835,6 +852,8 @@ impl<'a> PyClassImplsBuilder<'a> {
835852
let collector = PyClassImplCollector::<Self>::new();
836853
collector.buffer_procs()
837854
}
855+
#dict_offset
856+
#weaklist_offset
838857
}
839858

840859
#inventory_class

src/class/impl_.rs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,19 +71,31 @@ pub trait PyClassImpl: Sized {
7171
type Inventory: PyClassInventory;
7272

7373
fn for_each_method_def(_visitor: &mut dyn FnMut(&[PyMethodDefType])) {}
74+
fn for_each_proto_slot(_visitor: &mut dyn FnMut(&[ffi::PyType_Slot])) {}
75+
76+
#[inline]
7477
fn get_new() -> Option<ffi::newfunc> {
7578
None
7679
}
80+
#[inline]
7781
fn get_alloc() -> Option<ffi::allocfunc> {
7882
None
7983
}
84+
#[inline]
8085
fn get_free() -> Option<ffi::freefunc> {
8186
None
8287
}
83-
fn for_each_proto_slot(_visitor: &mut dyn FnMut(&[ffi::PyType_Slot])) {}
8488
fn get_buffer() -> Option<&'static PyBufferProcs> {
8589
None
8690
}
91+
#[inline]
92+
fn dict_offset() -> Option<ffi::Py_ssize_t> {
93+
None
94+
}
95+
#[inline]
96+
fn weaklist_offset() -> Option<ffi::Py_ssize_t> {
97+
None
98+
}
8799
}
88100

89101
// Traits describing known special methods.
@@ -760,8 +772,6 @@ impl<T: Send, U: PyClassBaseType> PyClassThreadChecker<T> for ThreadCheckerInher
760772

761773
/// Trait denoting that this class is suitable to be used as a base type for PyClass.
762774
pub trait PyClassBaseType: Sized {
763-
type Dict;
764-
type WeakRef;
765775
type LayoutAsBase: PyCellLayout<Self>;
766776
type BaseNativeType;
767777
type ThreadChecker: PyClassThreadChecker<Self>;
@@ -770,8 +780,6 @@ pub trait PyClassBaseType: Sized {
770780

771781
/// All PyClasses can be used as a base type.
772782
impl<T: PyClass> PyClassBaseType for T {
773-
type Dict = T::Dict;
774-
type WeakRef = T::WeakRef;
775783
type LayoutAsBase = crate::pycell::PyCell<T>;
776784
type BaseNativeType = T::BaseNativeType;
777785
type ThreadChecker = T::ThreadChecker;

src/impl_.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ pub mod freelist;
1111
pub mod frompyobject;
1212
pub(crate) mod not_send;
1313
#[doc(hidden)]
14+
pub mod pyclass;
1415
pub mod pymodule;

src/pyclass_slots.rs renamed to src/impl_/pyclass.rs

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
1-
//! Contains additional fields for `#[pyclass]`.
2-
//!
3-
//! Mainly used by PyO3's proc-macro code.
4-
use crate::{ffi, Python};
1+
use crate::{ffi, PyCell, PyClass, Python};
2+
3+
/// Gets the offset of the dictionary from the start of the object in bytes.
4+
#[inline]
5+
pub fn dict_offset<T: PyClass>() -> ffi::Py_ssize_t {
6+
PyCell::<T>::dict_offset()
7+
}
8+
9+
/// Gets the offset of the weakref list from the start of the object in bytes.
10+
#[inline]
11+
pub fn weaklist_offset<T: PyClass>() -> ffi::Py_ssize_t {
12+
PyCell::<T>::weaklist_offset()
13+
}
514

615
/// Represents the `__dict__` field for `#[pyclass]`.
716
pub trait PyClassDict {
8-
/// Whether this `__dict__` field is capable of holding a dictionary.
9-
const IS_DUMMY: bool = true;
1017
/// Initializes a [PyObject](crate::ffi::PyObject) `__dict__` reference.
1118
fn new() -> Self;
1219
/// Empties the dictionary of its key-value pairs.
@@ -17,8 +24,6 @@ pub trait PyClassDict {
1724

1825
/// Represents the `__weakref__` field for `#[pyclass]`.
1926
pub trait PyClassWeakRef {
20-
/// Whether this `weakref` type is capable of holding weak references.
21-
const IS_DUMMY: bool = true;
2227
/// Initializes a `weakref` instance.
2328
fn new() -> Self;
2429
/// Clears the weak references to the given object.
@@ -58,7 +63,6 @@ pub struct PyClassDictSlot(*mut ffi::PyObject);
5863

5964
impl PyClassDict for PyClassDictSlot {
6065
private_impl! {}
61-
const IS_DUMMY: bool = false;
6266
#[inline]
6367
fn new() -> Self {
6468
Self(std::ptr::null_mut())
@@ -79,7 +83,6 @@ pub struct PyClassWeakRefSlot(*mut ffi::PyObject);
7983

8084
impl PyClassWeakRef for PyClassWeakRefSlot {
8185
private_impl! {}
82-
const IS_DUMMY: bool = false;
8386
#[inline]
8487
fn new() -> Self {
8588
Self(std::ptr::null_mut())

src/lib.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,6 @@ pub mod prelude;
337337
pub mod pycell;
338338
pub mod pyclass;
339339
pub mod pyclass_init;
340-
pub mod pyclass_slots;
341340
mod python;
342341
pub mod type_object;
343342
pub mod types;

src/pycell.rs

Lines changed: 67 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -175,9 +175,9 @@
175175
//! [Interior Mutability]: https://doc.rust-lang.org/book/ch15-05-interior-mutability.html "RefCell<T> and the Interior Mutability Pattern - The Rust Programming Language"
176176
177177
use crate::exceptions::PyRuntimeError;
178+
use crate::impl_::pyclass::{PyClassDict, PyClassWeakRef};
178179
use crate::pyclass::PyClass;
179180
use crate::pyclass_init::PyClassInitializer;
180-
use crate::pyclass_slots::{PyClassDict, PyClassWeakRef};
181181
use crate::type_object::{PyLayout, PySizedLayout};
182182
use crate::types::PyAny;
183183
use crate::{class::impl_::PyClassBaseType, class::impl_::PyClassThreadChecker};
@@ -253,84 +253,6 @@ pub(crate) struct PyCellContents<T: PyClass> {
253253
pub(crate) weakref: T::WeakRef,
254254
}
255255

256-
impl<T: PyClass> PyCell<T> {
257-
/// Get the offset of the dictionary from the start of the struct in bytes.
258-
#[cfg(not(all(Py_LIMITED_API, not(Py_3_9))))]
259-
pub(crate) fn dict_offset() -> Option<ffi::Py_ssize_t> {
260-
use std::convert::TryInto;
261-
if T::Dict::IS_DUMMY {
262-
None
263-
} else {
264-
#[cfg(addr_of)]
265-
let offset = {
266-
// With std::ptr::addr_of - can measure offset using uninit memory without UB.
267-
let cell = std::mem::MaybeUninit::<Self>::uninit();
268-
let base_ptr = cell.as_ptr();
269-
let dict_ptr = unsafe { std::ptr::addr_of!((*base_ptr).contents.dict) };
270-
unsafe { (dict_ptr as *const u8).offset_from(base_ptr as *const u8) }
271-
};
272-
#[cfg(not(addr_of))]
273-
let offset = {
274-
// No std::ptr::addr_of - need to take references to PyCell to measure offsets;
275-
// make a zero-initialised "fake" one so that referencing it is not UB.
276-
let mut cell = std::mem::MaybeUninit::<Self>::uninit();
277-
unsafe {
278-
std::ptr::write_bytes(cell.as_mut_ptr(), 0, 1);
279-
}
280-
let cell = unsafe { cell.assume_init() };
281-
let dict_ptr = &cell.contents.dict;
282-
// offset_from wasn't stabilised until 1.47, so we also have to work around
283-
// that...
284-
let offset = (dict_ptr as *const _ as usize) - (&cell as *const _ as usize);
285-
// This isn't a valid cell, so ensure no Drop code runs etc.
286-
std::mem::forget(cell);
287-
offset
288-
};
289-
// Py_ssize_t may not be equal to isize on all platforms
290-
#[allow(clippy::useless_conversion)]
291-
Some(offset.try_into().expect("offset should fit in Py_ssize_t"))
292-
}
293-
}
294-
295-
/// Get the offset of the weakref list from the start of the struct in bytes.
296-
#[cfg(not(all(Py_LIMITED_API, not(Py_3_9))))]
297-
pub(crate) fn weakref_offset() -> Option<ffi::Py_ssize_t> {
298-
use std::convert::TryInto;
299-
if T::WeakRef::IS_DUMMY {
300-
None
301-
} else {
302-
#[cfg(addr_of)]
303-
let offset = {
304-
// With std::ptr::addr_of - can measure offset using uninit memory without UB.
305-
let cell = std::mem::MaybeUninit::<Self>::uninit();
306-
let base_ptr = cell.as_ptr();
307-
let weaklist_ptr = unsafe { std::ptr::addr_of!((*base_ptr).contents.weakref) };
308-
unsafe { (weaklist_ptr as *const u8).offset_from(base_ptr as *const u8) }
309-
};
310-
#[cfg(not(addr_of))]
311-
let offset = {
312-
// No std::ptr::addr_of - need to take references to PyCell to measure offsets;
313-
// make a zero-initialised "fake" one so that referencing it is not UB.
314-
let mut cell = std::mem::MaybeUninit::<Self>::uninit();
315-
unsafe {
316-
std::ptr::write_bytes(cell.as_mut_ptr(), 0, 1);
317-
}
318-
let cell = unsafe { cell.assume_init() };
319-
let weaklist_ptr = &cell.contents.weakref;
320-
// offset_from wasn't stabilised until 1.47, so we also have to work around
321-
// that...
322-
let offset = (weaklist_ptr as *const _ as usize) - (&cell as *const _ as usize);
323-
// This isn't a valid cell, so ensure no Drop code runs etc.
324-
std::mem::forget(cell);
325-
offset
326-
};
327-
// Py_ssize_t may not be equal to isize on all platforms
328-
#[allow(clippy::useless_conversion)]
329-
Some(offset.try_into().expect("offset should fit in Py_ssize_t"))
330-
}
331-
}
332-
}
333-
334256
unsafe impl<T: PyClass> PyNativeType for PyCell<T> {}
335257

336258
impl<T: PyClass> PyCell<T> {
@@ -502,6 +424,72 @@ impl<T: PyClass> PyCell<T> {
502424
fn get_ptr(&self) -> *mut T {
503425
self.contents.value.get()
504426
}
427+
428+
/// Gets the offset of the dictionary from the start of the struct in bytes.
429+
pub(crate) fn dict_offset() -> ffi::Py_ssize_t {
430+
use std::convert::TryInto;
431+
#[cfg(addr_of)]
432+
let offset = {
433+
// With std::ptr::addr_of - can measure offset using uninit memory without UB.
434+
let cell = std::mem::MaybeUninit::<Self>::uninit();
435+
let base_ptr = cell.as_ptr();
436+
let dict_ptr = unsafe { std::ptr::addr_of!((*base_ptr).contents.dict) };
437+
unsafe { (dict_ptr as *const u8).offset_from(base_ptr as *const u8) }
438+
};
439+
#[cfg(not(addr_of))]
440+
let offset = {
441+
// No std::ptr::addr_of - need to take references to PyCell to measure offsets;
442+
// make a zero-initialised "fake" one so that referencing it is not UB.
443+
let mut cell = std::mem::MaybeUninit::<Self>::uninit();
444+
unsafe {
445+
std::ptr::write_bytes(cell.as_mut_ptr(), 0, 1);
446+
}
447+
let cell = unsafe { cell.assume_init() };
448+
let dict_ptr = &cell.contents.dict;
449+
// offset_from wasn't stabilised until 1.47, so we also have to work around
450+
// that...
451+
let offset = (dict_ptr as *const _ as usize) - (&cell as *const _ as usize);
452+
// This isn't a valid cell, so ensure no Drop code runs etc.
453+
std::mem::forget(cell);
454+
offset
455+
};
456+
// Py_ssize_t may not be equal to isize on all platforms
457+
#[allow(clippy::useless_conversion)]
458+
offset.try_into().expect("offset should fit in Py_ssize_t")
459+
}
460+
461+
/// Gets the offset of the weakref list from the start of the struct in bytes.
462+
pub(crate) fn weaklist_offset() -> ffi::Py_ssize_t {
463+
use std::convert::TryInto;
464+
#[cfg(addr_of)]
465+
let offset = {
466+
// With std::ptr::addr_of - can measure offset using uninit memory without UB.
467+
let cell = std::mem::MaybeUninit::<Self>::uninit();
468+
let base_ptr = cell.as_ptr();
469+
let weaklist_ptr = unsafe { std::ptr::addr_of!((*base_ptr).contents.weakref) };
470+
unsafe { (weaklist_ptr as *const u8).offset_from(base_ptr as *const u8) }
471+
};
472+
#[cfg(not(addr_of))]
473+
let offset = {
474+
// No std::ptr::addr_of - need to take references to PyCell to measure offsets;
475+
// make a zero-initialised "fake" one so that referencing it is not UB.
476+
let mut cell = std::mem::MaybeUninit::<Self>::uninit();
477+
unsafe {
478+
std::ptr::write_bytes(cell.as_mut_ptr(), 0, 1);
479+
}
480+
let cell = unsafe { cell.assume_init() };
481+
let weaklist_ptr = &cell.contents.weakref;
482+
// offset_from wasn't stabilised until 1.47, so we also have to work around
483+
// that...
484+
let offset = (weaklist_ptr as *const _ as usize) - (&cell as *const _ as usize);
485+
// This isn't a valid cell, so ensure no Drop code runs etc.
486+
std::mem::forget(cell);
487+
offset
488+
};
489+
// Py_ssize_t may not be equal to isize on all platforms
490+
#[allow(clippy::useless_conversion)]
491+
offset.try_into().expect("offset should fit in Py_ssize_t")
492+
}
505493
}
506494

507495
unsafe impl<T: PyClass> PyLayout<T> for PyCell<T> {}

0 commit comments

Comments
 (0)