opt: don't emit T::dict_offset and T::weakref_offset without attributes

This commit is contained in:
David Hewitt 2021-12-30 12:29:42 +00:00
parent 807e126178
commit e33b3e6a5b
13 changed files with 191 additions and 159 deletions

View File

@ -102,7 +102,7 @@ Since we need lots of boilerplate for implementing common traits for these types
[`src/pycell.rs`], [`src/pyclass.rs`], and [`src/type_object.rs`] contain types and
traits to make `#[pyclass]` work.
Also, [`src/pyclass_init.rs`] and [`src/pyclass_slots.rs`] have related functionalities.
Also, [`src/pyclass_init.rs`] and [`src/impl_/pyclass.rs`] have related functionalities.
To realize object-oriented programming in C, all Python objects must have the following two fields
at the beginning.

View File

@ -44,7 +44,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Reduce generated LLVM code size (to improve compile times) for:
- internal `handle_panic` helper [#2074](https://github.com/PyO3/pyo3/pull/2074)
- `#[pyfunction]` and `#[pymethods]` argument extraction [#2075](https://github.com/PyO3/pyo3/pull/2075)
- `#[pyclass]` type object creation [#2076](https://github.com/PyO3/pyo3/pull/2076)
- `#[pyclass]` type object creation [#2076](https://github.com/PyO3/pyo3/pull/2076) [#2081](https://github.com/PyO3/pyo3/pull/2081)
### Removed

View File

@ -838,8 +838,8 @@ unsafe impl pyo3::PyTypeInfo for MyClass {
}
impl pyo3::pyclass::PyClass for MyClass {
type Dict = pyo3::pyclass_slots::PyClassDummySlot;
type WeakRef = pyo3::pyclass_slots::PyClassDummySlot;
type Dict = pyo3::impl_::pyclass::PyClassDummySlot;
type WeakRef = pyo3::impl_::pyclass::PyClassDummySlot;
type BaseNativeType = PyAny;
}

View File

@ -663,16 +663,16 @@ impl<'a> PyClassImplsBuilder<'a> {
let cls = self.cls;
let attr = self.attr;
let dict = if attr.has_dict {
quote! { _pyo3::pyclass_slots::PyClassDictSlot }
quote! { _pyo3::impl_::pyclass::PyClassDictSlot }
} else {
quote! { _pyo3::pyclass_slots::PyClassDummySlot }
quote! { _pyo3::impl_::pyclass::PyClassDummySlot }
};
// insert space for weak ref
let weakref = if attr.has_weaklist {
quote! { _pyo3::pyclass_slots::PyClassWeakRefSlot }
quote! { _pyo3::impl_::pyclass::PyClassWeakRefSlot }
} else {
quote! { _pyo3::pyclass_slots::PyClassDummySlot }
quote! { _pyo3::impl_::pyclass::PyClassDummySlot }
};
let base_nativetype = if attr.has_extends {
@ -727,6 +727,27 @@ impl<'a> PyClassImplsBuilder<'a> {
let base = &self.attr.base;
let is_subclass = self.attr.has_extends;
let dict_offset = if self.attr.has_dict {
quote! {
fn dict_offset() -> ::std::option::Option<_pyo3::ffi::Py_ssize_t> {
::std::option::Option::Some(_pyo3::impl_::pyclass::dict_offset::<Self>())
}
}
} else {
TokenStream::new()
};
// insert space for weak ref
let weaklist_offset = if self.attr.has_weaklist {
quote! {
fn weaklist_offset() -> ::std::option::Option<_pyo3::ffi::Py_ssize_t> {
::std::option::Option::Some(_pyo3::impl_::pyclass::weaklist_offset::<Self>())
}
}
} else {
TokenStream::new()
};
let thread_checker = if self.attr.has_unsendable {
quote! { _pyo3::class::impl_::ThreadCheckerImpl<#cls> }
} else if self.attr.has_extends {
@ -831,6 +852,8 @@ impl<'a> PyClassImplsBuilder<'a> {
let collector = PyClassImplCollector::<Self>::new();
collector.buffer_procs()
}
#dict_offset
#weaklist_offset
}
#inventory_class

View File

@ -71,19 +71,31 @@ pub trait PyClassImpl: Sized {
type Inventory: PyClassInventory;
fn for_each_method_def(_visitor: &mut dyn FnMut(&[PyMethodDefType])) {}
fn for_each_proto_slot(_visitor: &mut dyn FnMut(&[ffi::PyType_Slot])) {}
#[inline]
fn get_new() -> Option<ffi::newfunc> {
None
}
#[inline]
fn get_alloc() -> Option<ffi::allocfunc> {
None
}
#[inline]
fn get_free() -> Option<ffi::freefunc> {
None
}
fn for_each_proto_slot(_visitor: &mut dyn FnMut(&[ffi::PyType_Slot])) {}
fn get_buffer() -> Option<&'static PyBufferProcs> {
None
}
#[inline]
fn dict_offset() -> Option<ffi::Py_ssize_t> {
None
}
#[inline]
fn weaklist_offset() -> Option<ffi::Py_ssize_t> {
None
}
}
// Traits describing known special methods.

View File

@ -11,4 +11,5 @@ pub mod freelist;
pub mod frompyobject;
pub(crate) mod not_send;
#[doc(hidden)]
pub mod pyclass;
pub mod pymodule;

View File

@ -1,12 +1,19 @@
//! Contains additional fields for `#[pyclass]`.
//!
//! Mainly used by PyO3's proc-macro code.
use crate::{ffi, Python};
use crate::{ffi, PyCell, PyClass, Python};
/// Gets the offset of the dictionary from the start of the object in bytes.
#[inline]
pub fn dict_offset<T: PyClass>() -> ffi::Py_ssize_t {
PyCell::<T>::dict_offset()
}
/// Gets the offset of the weakref list from the start of the object in bytes.
#[inline]
pub fn weaklist_offset<T: PyClass>() -> ffi::Py_ssize_t {
PyCell::<T>::weaklist_offset()
}
/// Represents the `__dict__` field for `#[pyclass]`.
pub trait PyClassDict {
/// Whether this `__dict__` field is capable of holding a dictionary.
const IS_DUMMY: bool = true;
/// Initializes a [PyObject](crate::ffi::PyObject) `__dict__` reference.
fn new() -> Self;
/// Empties the dictionary of its key-value pairs.
@ -17,8 +24,6 @@ pub trait PyClassDict {
/// Represents the `__weakref__` field for `#[pyclass]`.
pub trait PyClassWeakRef {
/// Whether this `weakref` type is capable of holding weak references.
const IS_DUMMY: bool = true;
/// Initializes a `weakref` instance.
fn new() -> Self;
/// Clears the weak references to the given object.
@ -58,7 +63,6 @@ pub struct PyClassDictSlot(*mut ffi::PyObject);
impl PyClassDict for PyClassDictSlot {
private_impl! {}
const IS_DUMMY: bool = false;
#[inline]
fn new() -> Self {
Self(std::ptr::null_mut())
@ -79,7 +83,6 @@ pub struct PyClassWeakRefSlot(*mut ffi::PyObject);
impl PyClassWeakRef for PyClassWeakRefSlot {
private_impl! {}
const IS_DUMMY: bool = false;
#[inline]
fn new() -> Self {
Self(std::ptr::null_mut())

View File

@ -337,7 +337,6 @@ pub mod prelude;
pub mod pycell;
pub mod pyclass;
pub mod pyclass_init;
pub mod pyclass_slots;
mod python;
pub mod type_object;
pub mod types;

View File

@ -175,9 +175,9 @@
//! [Interior Mutability]: https://doc.rust-lang.org/book/ch15-05-interior-mutability.html "RefCell<T> and the Interior Mutability Pattern - The Rust Programming Language"
use crate::exceptions::PyRuntimeError;
use crate::impl_::pyclass::{PyClassDict, PyClassWeakRef};
use crate::pyclass::PyClass;
use crate::pyclass_init::PyClassInitializer;
use crate::pyclass_slots::{PyClassDict, PyClassWeakRef};
use crate::type_object::{PyLayout, PySizedLayout};
use crate::types::PyAny;
use crate::{class::impl_::PyClassBaseType, class::impl_::PyClassThreadChecker};
@ -253,84 +253,6 @@ pub(crate) struct PyCellContents<T: PyClass> {
pub(crate) weakref: T::WeakRef,
}
impl<T: PyClass> PyCell<T> {
/// Get the offset of the dictionary from the start of the struct in bytes.
#[cfg(not(all(Py_LIMITED_API, not(Py_3_9))))]
pub(crate) fn dict_offset() -> Option<ffi::Py_ssize_t> {
use std::convert::TryInto;
if T::Dict::IS_DUMMY {
None
} else {
#[cfg(addr_of)]
let offset = {
// With std::ptr::addr_of - can measure offset using uninit memory without UB.
let cell = std::mem::MaybeUninit::<Self>::uninit();
let base_ptr = cell.as_ptr();
let dict_ptr = unsafe { std::ptr::addr_of!((*base_ptr).contents.dict) };
unsafe { (dict_ptr as *const u8).offset_from(base_ptr as *const u8) }
};
#[cfg(not(addr_of))]
let offset = {
// No std::ptr::addr_of - need to take references to PyCell to measure offsets;
// make a zero-initialised "fake" one so that referencing it is not UB.
let mut cell = std::mem::MaybeUninit::<Self>::uninit();
unsafe {
std::ptr::write_bytes(cell.as_mut_ptr(), 0, 1);
}
let cell = unsafe { cell.assume_init() };
let dict_ptr = &cell.contents.dict;
// offset_from wasn't stabilised until 1.47, so we also have to work around
// that...
let offset = (dict_ptr as *const _ as usize) - (&cell as *const _ as usize);
// This isn't a valid cell, so ensure no Drop code runs etc.
std::mem::forget(cell);
offset
};
// Py_ssize_t may not be equal to isize on all platforms
#[allow(clippy::useless_conversion)]
Some(offset.try_into().expect("offset should fit in Py_ssize_t"))
}
}
/// Get the offset of the weakref list from the start of the struct in bytes.
#[cfg(not(all(Py_LIMITED_API, not(Py_3_9))))]
pub(crate) fn weakref_offset() -> Option<ffi::Py_ssize_t> {
use std::convert::TryInto;
if T::WeakRef::IS_DUMMY {
None
} else {
#[cfg(addr_of)]
let offset = {
// With std::ptr::addr_of - can measure offset using uninit memory without UB.
let cell = std::mem::MaybeUninit::<Self>::uninit();
let base_ptr = cell.as_ptr();
let weaklist_ptr = unsafe { std::ptr::addr_of!((*base_ptr).contents.weakref) };
unsafe { (weaklist_ptr as *const u8).offset_from(base_ptr as *const u8) }
};
#[cfg(not(addr_of))]
let offset = {
// No std::ptr::addr_of - need to take references to PyCell to measure offsets;
// make a zero-initialised "fake" one so that referencing it is not UB.
let mut cell = std::mem::MaybeUninit::<Self>::uninit();
unsafe {
std::ptr::write_bytes(cell.as_mut_ptr(), 0, 1);
}
let cell = unsafe { cell.assume_init() };
let weaklist_ptr = &cell.contents.weakref;
// offset_from wasn't stabilised until 1.47, so we also have to work around
// that...
let offset = (weaklist_ptr as *const _ as usize) - (&cell as *const _ as usize);
// This isn't a valid cell, so ensure no Drop code runs etc.
std::mem::forget(cell);
offset
};
// Py_ssize_t may not be equal to isize on all platforms
#[allow(clippy::useless_conversion)]
Some(offset.try_into().expect("offset should fit in Py_ssize_t"))
}
}
}
unsafe impl<T: PyClass> PyNativeType for PyCell<T> {}
impl<T: PyClass> PyCell<T> {
@ -502,6 +424,72 @@ impl<T: PyClass> PyCell<T> {
fn get_ptr(&self) -> *mut T {
self.contents.value.get()
}
/// Gets the offset of the dictionary from the start of the struct in bytes.
pub(crate) fn dict_offset() -> ffi::Py_ssize_t {
use std::convert::TryInto;
#[cfg(addr_of)]
let offset = {
// With std::ptr::addr_of - can measure offset using uninit memory without UB.
let cell = std::mem::MaybeUninit::<Self>::uninit();
let base_ptr = cell.as_ptr();
let dict_ptr = unsafe { std::ptr::addr_of!((*base_ptr).contents.dict) };
unsafe { (dict_ptr as *const u8).offset_from(base_ptr as *const u8) }
};
#[cfg(not(addr_of))]
let offset = {
// No std::ptr::addr_of - need to take references to PyCell to measure offsets;
// make a zero-initialised "fake" one so that referencing it is not UB.
let mut cell = std::mem::MaybeUninit::<Self>::uninit();
unsafe {
std::ptr::write_bytes(cell.as_mut_ptr(), 0, 1);
}
let cell = unsafe { cell.assume_init() };
let dict_ptr = &cell.contents.dict;
// offset_from wasn't stabilised until 1.47, so we also have to work around
// that...
let offset = (dict_ptr as *const _ as usize) - (&cell as *const _ as usize);
// This isn't a valid cell, so ensure no Drop code runs etc.
std::mem::forget(cell);
offset
};
// Py_ssize_t may not be equal to isize on all platforms
#[allow(clippy::useless_conversion)]
offset.try_into().expect("offset should fit in Py_ssize_t")
}
/// Gets the offset of the weakref list from the start of the struct in bytes.
pub(crate) fn weaklist_offset() -> ffi::Py_ssize_t {
use std::convert::TryInto;
#[cfg(addr_of)]
let offset = {
// With std::ptr::addr_of - can measure offset using uninit memory without UB.
let cell = std::mem::MaybeUninit::<Self>::uninit();
let base_ptr = cell.as_ptr();
let weaklist_ptr = unsafe { std::ptr::addr_of!((*base_ptr).contents.weakref) };
unsafe { (weaklist_ptr as *const u8).offset_from(base_ptr as *const u8) }
};
#[cfg(not(addr_of))]
let offset = {
// No std::ptr::addr_of - need to take references to PyCell to measure offsets;
// make a zero-initialised "fake" one so that referencing it is not UB.
let mut cell = std::mem::MaybeUninit::<Self>::uninit();
unsafe {
std::ptr::write_bytes(cell.as_mut_ptr(), 0, 1);
}
let cell = unsafe { cell.assume_init() };
let weaklist_ptr = &cell.contents.weakref;
// offset_from wasn't stabilised until 1.47, so we also have to work around
// that...
let offset = (weaklist_ptr as *const _ as usize) - (&cell as *const _ as usize);
// This isn't a valid cell, so ensure no Drop code runs etc.
std::mem::forget(cell);
offset
};
// Py_ssize_t may not be equal to isize on all platforms
#[allow(clippy::useless_conversion)]
offset.try_into().expect("offset should fit in Py_ssize_t")
}
}
unsafe impl<T: PyClass> PyLayout<T> for PyCell<T> {}

View File

@ -1,8 +1,8 @@
//! `PyClass` and related traits.
use crate::{
class::impl_::{fallback_new, tp_dealloc, PyClassImpl, PyBufferProcs},
class::impl_::{fallback_new, tp_dealloc, PyBufferProcs, PyClassImpl},
ffi,
pyclass_slots::{PyClassDict, PyClassWeakRef},
impl_::pyclass::{PyClassDict, PyClassWeakRef},
PyCell, PyErr, PyMethodDefType, PyNativeType, PyResult, PyTypeInfo, Python,
};
use std::{
@ -33,36 +33,37 @@ fn into_raw<T>(vec: Vec<T>) -> *mut c_void {
Box::into_raw(vec.into_boxed_slice()) as _
}
pub(crate) fn create_type_object<T>(
py: Python,
) -> *mut ffi::PyTypeObject
pub(crate) fn create_type_object<T>(py: Python) -> *mut ffi::PyTypeObject
where
T: PyClass,
{
match unsafe { create_type_object_impl(
py,
T::DOC,
T::MODULE,
T::NAME,
T::BaseType::type_object_raw(py),
std::mem::size_of::<T::Layout>(),
T::get_new(),
tp_dealloc::<T>,
T::get_alloc(),
T::get_free(),
PyCell::<T>::dict_offset(),
PyCell::<T>::weakref_offset(),
&T::for_each_method_def,
&T::for_each_proto_slot,
T::IS_GC,
T::IS_BASETYPE,
T::get_buffer(),
) } {
match unsafe {
create_type_object_impl(
py,
T::DOC,
T::MODULE,
T::NAME,
T::BaseType::type_object_raw(py),
std::mem::size_of::<T::Layout>(),
T::get_new(),
tp_dealloc::<T>,
T::get_alloc(),
T::get_free(),
T::dict_offset(),
T::weaklist_offset(),
&T::for_each_method_def,
&T::for_each_proto_slot,
T::IS_GC,
T::IS_BASETYPE,
T::get_buffer(),
)
} {
Ok(type_object) => type_object,
Err(e) => type_object_creation_failed(py, e, T::NAME),
}
}
#[allow(clippy::too_many_arguments)]
unsafe fn create_type_object_impl(
py: Python,
tp_doc: &str,
@ -75,7 +76,7 @@ unsafe fn create_type_object_impl(
tp_alloc: Option<ffi::allocfunc>,
tp_free: Option<ffi::freefunc>,
dict_offset: Option<ffi::Py_ssize_t>,
weakref_offset: Option<ffi::Py_ssize_t>,
weaklist_offset: Option<ffi::Py_ssize_t>,
for_each_method_def: &dyn Fn(&mut dyn FnMut(&[PyMethodDefType])),
for_each_proto_slot: &dyn Fn(&mut dyn FnMut(&[ffi::PyType_Slot])),
is_gc: bool,
@ -88,11 +89,7 @@ unsafe fn create_type_object_impl(
slots.push(ffi::PyType_Slot { slot, pfunc });
}
push_slot(
&mut slots,
ffi::Py_tp_base,
base_type_object as _,
);
push_slot(&mut slots, ffi::Py_tp_base, base_type_object as _);
if let Some(doc) = py_class_doc(tp_doc) {
push_slot(&mut slots, ffi::Py_tp_doc, doc as _);
}
@ -113,7 +110,7 @@ unsafe fn create_type_object_impl(
#[cfg(Py_3_9)]
{
let members = py_class_members(dict_offset, weakref_offset);
let members = py_class_members(dict_offset, weaklist_offset);
if !members.is_empty() {
push_slot(&mut slots, ffi::Py_tp_members, into_raw(members))
}
@ -153,7 +150,13 @@ unsafe fn create_type_object_impl(
if type_object.is_null() {
Err(PyErr::fetch(py))
} else {
tp_init_additional(type_object as _, tp_doc, buffer_procs, dict_offset, weakref_offset);
tp_init_additional(
type_object as _,
tp_doc,
buffer_procs,
dict_offset,
weaklist_offset,
);
Ok(type_object as _)
}
}
@ -166,7 +169,13 @@ fn type_object_creation_failed(py: Python, e: PyErr, name: &'static str) -> ! {
/// Additional type initializations necessary before Python 3.10
#[cfg(all(not(Py_LIMITED_API), not(Py_3_10)))]
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>) {
fn tp_init_additional(
type_object: *mut ffi::PyTypeObject,
_tp_doc: &str,
_buffer_procs: Option<&PyBufferProcs>,
_dict_offset: Option<ffi::Py_ssize_t>,
_weaklist_offset: Option<ffi::Py_ssize_t>,
) {
// Just patch the type objects for the things there's no
// PyType_FromSpec API for... there's no reason this should work,
// except for that it does and we have tests.
@ -174,53 +183,54 @@ fn tp_init_additional(type_object: *mut ffi::PyTypeObject, tp_doc: &str, buffer_
// Running this causes PyPy to segfault.
#[cfg(all(not(PyPy), not(Py_3_10)))]
{
if tp_doc != "\0" {
if _tp_doc != "\0" {
unsafe {
// Until CPython 3.10, tp_doc was treated specially for
// heap-types, and it removed the text_signature value from it.
// We go in after the fact and replace tp_doc with something
// that _does_ include the text_signature value!
ffi::PyObject_Free((*type_object).tp_doc as _);
let data = ffi::PyObject_Malloc(tp_doc.len());
data.copy_from(tp_doc.as_ptr() as _, tp_doc.len());
let data = ffi::PyObject_Malloc(_tp_doc.len());
data.copy_from(_tp_doc.as_ptr() as _, _tp_doc.len());
(*type_object).tp_doc = data as _;
}
}
}
// Setting buffer protocols via slots doesn't work until Python 3.9, so on older versions we
// must manually fixup the type object.
// Setting buffer protocols, tp_dictoffset and tp_weaklistoffset via slots doesn't work until
// Python 3.9, so on older versions we must manually fixup the type object.
#[cfg(not(Py_3_9))]
{
if let Some(buffer) = buffer_procs {
if let Some(buffer) = _buffer_procs {
unsafe {
(*(*type_object).tp_as_buffer).bf_getbuffer = buffer.bf_getbuffer;
(*(*type_object).tp_as_buffer).bf_releasebuffer = buffer.bf_releasebuffer;
}
}
}
// Setting tp_dictoffset and tp_weaklistoffset via slots doesn't work until Python 3.9, so on
// older versions again we must fixup the type object.
#[cfg(not(Py_3_9))]
{
// __dict__ support
if let Some(dict_offset) = dict_offset {
if let Some(dict_offset) = _dict_offset {
unsafe {
(*type_object).tp_dictoffset = dict_offset;
}
}
// weakref support
if let Some(weakref_offset) = weakref_offset {
if let Some(weaklist_offset) = _weaklist_offset {
unsafe {
(*type_object).tp_weaklistoffset = weakref_offset;
(*type_object).tp_weaklistoffset = weaklist_offset;
}
}
}
}
#[cfg(any(Py_LIMITED_API, Py_3_10))]
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>) {}
fn tp_init_additional(
_type_object: *mut ffi::PyTypeObject,
_tp_doc: &str,
_buffer_procs: Option<&PyBufferProcs>,
_dict_offset: Option<ffi::Py_ssize_t>,
_weaklist_offset: Option<ffi::Py_ssize_t>,
) {
}
fn py_class_doc(class_doc: &str) -> Option<*mut c_char> {
match class_doc {
@ -293,7 +303,7 @@ fn py_class_method_defs(
#[cfg(Py_3_9)]
fn py_class_members(
dict_offset: Option<isize>,
weakref_offset: Option<isize>,
weaklist_offset: Option<isize>,
) -> Vec<ffi::structmember::PyMemberDef> {
#[inline(always)]
fn offset_def(name: &'static str, offset: ffi::Py_ssize_t) -> ffi::structmember::PyMemberDef {
@ -314,8 +324,8 @@ fn py_class_members(
}
// weakref support
if let Some(weakref_offset) = weakref_offset {
members.push(offset_def("__weaklistoffset__\0", weakref_offset));
if let Some(weaklist_offset) = weaklist_offset {
members.push(offset_def("__weaklistoffset__\0", weaklist_offset));
}
if !members.is_empty() {

View File

@ -1,6 +1,6 @@
//! Contains initialization utilities for `#[pyclass]`.
use crate::class::impl_::PyClassThreadChecker;
use crate::pyclass_slots::{PyClassDict, PyClassWeakRef};
use crate::impl_::pyclass::{PyClassDict, PyClassWeakRef};
use crate::{callback::IntoPyCallbackOutput, class::impl_::PyClassBaseType};
use crate::{ffi, PyCell, PyClass, PyErr, PyResult, Python};
use crate::{

View File

@ -344,7 +344,6 @@ fn test_tuple_struct_class() {
});
}
#[pyclass(dict, subclass)]
struct DunderDictSupport {}
@ -419,8 +418,6 @@ fn weakref_dunder_dict_support() {
);
}
#[pyclass(weakref, subclass)]
struct WeakRefSupport {}

View File

@ -9,10 +9,9 @@ note: required by a bound in `PyClassBaseType`
--> src/class/impl_.rs
|
| / pub trait PyClassBaseType: Sized {
| | type Dict;
| | type WeakRef;
| | type LayoutAsBase: PyCellLayout<Self>;
... |
| | type BaseNativeType;
| | type ThreadChecker: PyClassThreadChecker<Self>;
| | type Initializer: PyObjectInit<Self>;
| | }
| |_^ required by this bound in `PyClassBaseType`