Skip to content

Commit

Permalink
opt: don't emit T::dict_offset and T::weakref_offset without attributes
Browse files Browse the repository at this point in the history
  • Loading branch information
davidhewitt committed Dec 30, 2021
1 parent 807e126 commit e33b3e6
Show file tree
Hide file tree
Showing 13 changed files with 191 additions and 159 deletions.
2 changes: 1 addition & 1 deletion Architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 2 additions & 2 deletions guide/src/class.md
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
31 changes: 27 additions & 4 deletions pyo3-macros-backend/src/pyclass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -831,6 +852,8 @@ impl<'a> PyClassImplsBuilder<'a> {
let collector = PyClassImplCollector::<Self>::new();
collector.buffer_procs()
}
#dict_offset
#weaklist_offset
}

#inventory_class
Expand Down
14 changes: 13 additions & 1 deletion src/class/impl_.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions src/impl_.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ pub mod freelist;
pub mod frompyobject;
pub(crate) mod not_send;
#[doc(hidden)]
pub mod pyclass;
pub mod pymodule;
23 changes: 13 additions & 10 deletions src/pyclass_slots.rs → src/impl_/pyclass.rs
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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.
Expand Down Expand Up @@ -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())
Expand All @@ -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())
Expand Down
1 change: 0 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
146 changes: 67 additions & 79 deletions src/pycell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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> {
Expand Down Expand Up @@ -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> {}
Expand Down
Loading

0 comments on commit e33b3e6

Please sign in to comment.