Vendor deps.
This commit is contained in:
1 parent
294527085e
commit
91e18fde3a
1714 files changed
+556667
-7
No files matched your search
Vendored
+925
@@ -0,0 +1,925 @@
|
||||
#![allow(missing_docs)]
|
||||
//! Crate-private implementation of PyClassObject
|
||||
|
||||
use core::cell::UnsafeCell;
|
||||
use core::marker::PhantomData;
|
||||
use core::mem::{offset_of, ManuallyDrop, MaybeUninit};
|
||||
use core::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
use crate::impl_::pyclass::{
|
||||
PyClassBaseType, PyClassDict, PyClassImpl, PyClassThreadChecker, PyClassWeakRef, PyObjectOffset,
|
||||
};
|
||||
use crate::internal::get_slot::{TP_DEALLOC, TP_FREE};
|
||||
#[cfg(RustPython)]
|
||||
use crate::sync::PyOnceLock;
|
||||
use crate::type_object::{PyLayout, PySizedLayout, PyTypeInfo};
|
||||
use crate::types::PyType;
|
||||
use crate::{ffi, PyClass, Python};
|
||||
|
||||
use crate::types::PyTypeMethods;
|
||||
|
||||
use super::{PyBorrowError, PyBorrowMutError};
|
||||
|
||||
pub trait PyClassMutability {
|
||||
// The storage for this inheritance layer. Only the first mutable class in
|
||||
// an inheritance hierarchy needs to store the borrow flag.
|
||||
type Storage: PyClassBorrowChecker;
|
||||
// The borrow flag needed to implement this class' mutability. Empty until
|
||||
// the first mutable class, at which point it is BorrowChecker and will be
|
||||
// for all subclasses.
|
||||
type Checker: PyClassBorrowChecker;
|
||||
type ImmutableChild: PyClassMutability;
|
||||
type MutableChild: PyClassMutability;
|
||||
}
|
||||
|
||||
pub struct ImmutableClass(());
|
||||
pub struct MutableClass(());
|
||||
pub struct ExtendsMutableAncestor<M: PyClassMutability>(PhantomData<M>);
|
||||
|
||||
impl PyClassMutability for ImmutableClass {
|
||||
type Storage = EmptySlot;
|
||||
type Checker = EmptySlot;
|
||||
type ImmutableChild = ImmutableClass;
|
||||
type MutableChild = MutableClass;
|
||||
}
|
||||
|
||||
impl PyClassMutability for MutableClass {
|
||||
type Storage = BorrowChecker;
|
||||
type Checker = BorrowChecker;
|
||||
type ImmutableChild = ExtendsMutableAncestor<ImmutableClass>;
|
||||
type MutableChild = ExtendsMutableAncestor<MutableClass>;
|
||||
}
|
||||
|
||||
impl<M: PyClassMutability> PyClassMutability for ExtendsMutableAncestor<M> {
|
||||
type Storage = EmptySlot;
|
||||
type Checker = BorrowChecker;
|
||||
type ImmutableChild = ExtendsMutableAncestor<ImmutableClass>;
|
||||
type MutableChild = ExtendsMutableAncestor<MutableClass>;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct BorrowFlag(AtomicUsize);
|
||||
|
||||
impl BorrowFlag {
|
||||
pub(crate) const UNUSED: usize = 0;
|
||||
const HAS_MUTABLE_BORROW: usize = usize::MAX;
|
||||
fn increment(&self) -> Result<(), PyBorrowError> {
|
||||
// relaxed is OK because we will read the value again in the compare_exchange
|
||||
let mut value = self.0.load(Ordering::Relaxed);
|
||||
loop {
|
||||
if value == BorrowFlag::HAS_MUTABLE_BORROW {
|
||||
return Err(PyBorrowError { _private: () });
|
||||
}
|
||||
match self.0.compare_exchange(
|
||||
// only increment if the value hasn't changed since the
|
||||
// last atomic load
|
||||
value,
|
||||
value + 1,
|
||||
// reading the value is happens-after a previous write
|
||||
// writing the new value is happens-after the previous read
|
||||
Ordering::AcqRel,
|
||||
// relaxed is OK here because we're going to try to read again
|
||||
Ordering::Relaxed,
|
||||
) {
|
||||
Ok(..) => {
|
||||
break Ok(());
|
||||
}
|
||||
Err(changed_value) => {
|
||||
// value changed under us, need to try again
|
||||
value = changed_value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fn decrement(&self) {
|
||||
// relaxed load is OK but decrements must happen-before the next read
|
||||
self.0.fetch_sub(1, Ordering::Release);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EmptySlot(());
|
||||
pub struct BorrowChecker(BorrowFlag);
|
||||
|
||||
pub trait PyClassBorrowChecker {
|
||||
/// Initial value for self
|
||||
fn new() -> Self
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
/// Increments immutable borrow count, if possible
|
||||
fn try_borrow(&self) -> Result<(), PyBorrowError>;
|
||||
|
||||
/// Decrements immutable borrow count
|
||||
fn release_borrow(&self);
|
||||
/// Increments mutable borrow count, if possible
|
||||
fn try_borrow_mut(&self) -> Result<(), PyBorrowMutError>;
|
||||
/// Decremements mutable borrow count
|
||||
fn release_borrow_mut(&self);
|
||||
}
|
||||
|
||||
impl PyClassBorrowChecker for EmptySlot {
|
||||
#[inline]
|
||||
fn new() -> Self {
|
||||
EmptySlot(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn try_borrow(&self) -> Result<(), PyBorrowError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn release_borrow(&self) {}
|
||||
|
||||
#[inline]
|
||||
fn try_borrow_mut(&self) -> Result<(), PyBorrowMutError> {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn release_borrow_mut(&self) {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
impl PyClassBorrowChecker for BorrowChecker {
|
||||
#[inline]
|
||||
fn new() -> Self {
|
||||
Self(BorrowFlag(AtomicUsize::new(BorrowFlag::UNUSED)))
|
||||
}
|
||||
|
||||
fn try_borrow(&self) -> Result<(), PyBorrowError> {
|
||||
self.0.increment()
|
||||
}
|
||||
|
||||
fn release_borrow(&self) {
|
||||
self.0.decrement();
|
||||
}
|
||||
|
||||
fn try_borrow_mut(&self) -> Result<(), PyBorrowMutError> {
|
||||
let flag = &self.0;
|
||||
match flag.0.compare_exchange(
|
||||
// only allowed to transition to mutable borrow if the reference is
|
||||
// currently unused
|
||||
BorrowFlag::UNUSED,
|
||||
BorrowFlag::HAS_MUTABLE_BORROW,
|
||||
// On success, reading the flag and updating its state are an atomic
|
||||
// operation
|
||||
Ordering::AcqRel,
|
||||
// It doesn't matter precisely when the failure gets turned
|
||||
// into an error
|
||||
Ordering::Relaxed,
|
||||
) {
|
||||
Ok(..) => Ok(()),
|
||||
Err(..) => Err(PyBorrowMutError { _private: () }),
|
||||
}
|
||||
}
|
||||
|
||||
fn release_borrow_mut(&self) {
|
||||
self.0 .0.store(BorrowFlag::UNUSED, Ordering::Release)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait GetBorrowChecker<T: PyClassImpl> {
|
||||
fn borrow_checker(
|
||||
class_object: &T::Layout,
|
||||
) -> &<T::PyClassMutability as PyClassMutability>::Checker;
|
||||
}
|
||||
|
||||
impl<T: PyClassImpl<PyClassMutability = Self>> GetBorrowChecker<T> for MutableClass {
|
||||
fn borrow_checker(class_object: &T::Layout) -> &BorrowChecker {
|
||||
&class_object.contents().borrow_checker
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PyClassImpl<PyClassMutability = Self>> GetBorrowChecker<T> for ImmutableClass {
|
||||
fn borrow_checker(class_object: &T::Layout) -> &EmptySlot {
|
||||
&class_object.contents().borrow_checker
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PyClassImpl<PyClassMutability = Self>, M: PyClassMutability> GetBorrowChecker<T>
|
||||
for ExtendsMutableAncestor<M>
|
||||
where
|
||||
T::BaseType: PyClassImpl + PyClassBaseType<LayoutAsBase = <T::BaseType as PyClassImpl>::Layout>,
|
||||
<T::BaseType as PyClassImpl>::PyClassMutability: PyClassMutability<Checker = BorrowChecker>,
|
||||
{
|
||||
fn borrow_checker(class_object: &T::Layout) -> &BorrowChecker {
|
||||
<<T::BaseType as PyClassImpl>::PyClassMutability as GetBorrowChecker<T::BaseType>>::borrow_checker(class_object.ob_base())
|
||||
}
|
||||
}
|
||||
|
||||
/// Base layout of PyClassObject with a known sized base type.
|
||||
/// Corresponds to [PyObject](https://docs.python.org/3/c-api/structures.html#c.PyObject) from the C API.
|
||||
#[doc(hidden)]
|
||||
#[repr(C)]
|
||||
pub struct PyClassObjectBase<T> {
|
||||
ob_base: T,
|
||||
}
|
||||
|
||||
unsafe impl<T, U> PyLayout<T> for PyClassObjectBase<U> where U: PySizedLayout<T> {}
|
||||
|
||||
impl<T, U> PyClassObjectBaseLayout<T> for PyClassObjectBase<U>
|
||||
where
|
||||
U: PySizedLayout<T>,
|
||||
T: PyTypeInfo,
|
||||
{
|
||||
fn ensure_threadsafe(&self) {}
|
||||
fn check_threadsafe(&self) -> Result<(), PyBorrowError> {
|
||||
Ok(())
|
||||
}
|
||||
unsafe fn tp_dealloc(py: Python<'_>, slf: *mut ffi::PyObject) {
|
||||
unsafe { tp_dealloc(slf, &T::type_object(py)) };
|
||||
}
|
||||
}
|
||||
|
||||
/// Base layout of PyClassObject with an unknown sized base type.
|
||||
/// Corresponds to [PyVarObject](https://docs.python.org/3/c-api/structures.html#c.PyVarObject) from the C API.
|
||||
#[doc(hidden)]
|
||||
#[repr(C)]
|
||||
pub struct PyVariableClassObjectBase {
|
||||
ob_base: ffi::PyVarObject,
|
||||
}
|
||||
|
||||
unsafe impl<T> PyLayout<T> for PyVariableClassObjectBase {}
|
||||
|
||||
impl<T: PyTypeInfo> PyClassObjectBaseLayout<T> for PyVariableClassObjectBase {
|
||||
fn ensure_threadsafe(&self) {}
|
||||
fn check_threadsafe(&self) -> Result<(), PyBorrowError> {
|
||||
Ok(())
|
||||
}
|
||||
unsafe fn tp_dealloc(py: Python<'_>, slf: *mut ffi::PyObject) {
|
||||
unsafe { tp_dealloc(slf, &T::type_object(py)) };
|
||||
}
|
||||
}
|
||||
|
||||
/// Implementation of tp_dealloc.
|
||||
/// # Safety
|
||||
/// - `slf` must be a valid pointer to an instance of the type at `type_obj` or a subclass.
|
||||
/// - `slf` must not be used after this call (as it will be freed).
|
||||
unsafe fn tp_dealloc(slf: *mut ffi::PyObject, type_obj: &crate::Bound<'_, PyType>) {
|
||||
let py = type_obj.py();
|
||||
unsafe {
|
||||
// FIXME: there is potentially subtle issues here if the base is overwritten
|
||||
// at runtime? To be investigated.
|
||||
let type_ptr = type_obj.as_type_ptr();
|
||||
let actual_type_ptr = ffi::Py_TYPE(slf);
|
||||
|
||||
// For heap types, instances must decref the type object when they
|
||||
// are deallocated, so we create a bound from a borrowed pointer as
|
||||
// as if it was an owned pointer. In this way, when the bound is dropped,
|
||||
// it will decref the type object.
|
||||
debug_assert!(ffi::PyType_HasFeature(actual_type_ptr, ffi::Py_TPFLAGS_HEAPTYPE) != 0);
|
||||
let actual_type = cfg_select! {
|
||||
not(PyPy) => crate::Bound::from_owned_ptr(py, actual_type_ptr as *mut ffi::PyObject)
|
||||
.cast_into_unchecked::<PyType>(),
|
||||
// See https://github.com/pypy/pypy/issues/5555 - it seems that PyPy does not
|
||||
// support the CPython semantics properly, so we avoid taking ownership of the
|
||||
// type object on PyPy.
|
||||
//
|
||||
// TODO: If the PyPy bug is fixed we should remove this workaround and just create
|
||||
// a `Bound` as above.
|
||||
PyPy => crate::Borrowed::from_ptr(py, actual_type_ptr as *mut ffi::PyObject)
|
||||
.cast_unchecked::<PyType>(),
|
||||
};
|
||||
|
||||
// For `#[pyclass]` types which inherit from PyAny, we can just call tp_free
|
||||
#[cfg(not(RustPython))]
|
||||
let base_object_type_ptr = &raw const ffi::PyBaseObject_Type;
|
||||
#[cfg(RustPython)]
|
||||
let base_object_type_ptr = {
|
||||
static TYPE: PyOnceLock<crate::Py<PyType>> = PyOnceLock::new();
|
||||
TYPE.import(py, "builtins", "object").unwrap().as_type_ptr()
|
||||
};
|
||||
|
||||
if core::ptr::eq(type_ptr, base_object_type_ptr) {
|
||||
let tp_free = actual_type
|
||||
.get_slot(TP_FREE)
|
||||
.expect("PyBaseObject_Type should have tp_free");
|
||||
tp_free(slf.cast());
|
||||
}
|
||||
// More complex native types (e.g. `extends=PyDict`) require calling the base's dealloc.
|
||||
// FIXME: should this be using actual_type.tp_dealloc?
|
||||
else if let Some(dealloc) = type_obj.get_slot(TP_DEALLOC) {
|
||||
// Before CPython 3.11 BaseException_dealloc would use Py_GC_UNTRACK which
|
||||
// assumes the exception is currently GC tracked, so we have to re-track
|
||||
// before calling the dealloc so that it can safely call Py_GC_UNTRACK.
|
||||
#[cfg(not(any(Py_3_11, PyPy)))]
|
||||
if ffi::PyType_FastSubclass(type_ptr, ffi::Py_TPFLAGS_BASE_EXC_SUBCLASS) == 1 {
|
||||
ffi::PyObject_GC_Track(slf.cast());
|
||||
}
|
||||
dealloc(slf);
|
||||
} else {
|
||||
type_obj.get_slot(TP_FREE).expect("type missing tp_free")(slf.cast());
|
||||
}
|
||||
|
||||
// Cause the reference to the type to be decrefed for heap types, which
|
||||
// is necessary to avoid a reference leak.
|
||||
#[cfg_attr(
|
||||
PyPy,
|
||||
expect(dropping_copy_types, reason = "see PyPy workaround for decref above")
|
||||
)]
|
||||
drop(actual_type);
|
||||
}
|
||||
}
|
||||
|
||||
/// functionality common to all PyObjects regardless of the layout
|
||||
#[doc(hidden)]
|
||||
pub trait PyClassObjectBaseLayout<T>: PyLayout<T> {
|
||||
fn ensure_threadsafe(&self);
|
||||
fn check_threadsafe(&self) -> Result<(), PyBorrowError>;
|
||||
/// Implementation of tp_dealloc.
|
||||
/// # Safety
|
||||
/// - slf must be a valid pointer to an instance of a T or a subclass.
|
||||
/// - slf must not be used after this call (as it will be freed).
|
||||
unsafe fn tp_dealloc(py: Python<'_>, slf: *mut ffi::PyObject);
|
||||
}
|
||||
|
||||
/// Functionality required for creating and managing the memory associated with a pyclass annotated struct.
|
||||
#[doc(hidden)]
|
||||
#[diagnostic::on_unimplemented(
|
||||
message = "the class layout is not valid",
|
||||
label = "required for `#[pyclass(extends=...)]`",
|
||||
note = "the python version being built against influences which layouts are valid"
|
||||
)]
|
||||
pub trait PyClassObjectLayout<T: PyClassImpl>: PyClassObjectBaseLayout<T> {
|
||||
/// Gets the offset of the contents from the start of the struct in bytes.
|
||||
const CONTENTS_OFFSET: PyObjectOffset;
|
||||
|
||||
/// Used to set `PyType_Spec::basicsize`
|
||||
/// ([docs](https://docs.python.org/3/c-api/type.html#c.PyType_Spec.basicsize))
|
||||
const BASIC_SIZE: ffi::Py_ssize_t;
|
||||
|
||||
/// Gets the offset of the dictionary from the start of the struct in bytes.
|
||||
const DICT_OFFSET: PyObjectOffset;
|
||||
|
||||
/// Gets the offset of the weakref list from the start of the struct in bytes.
|
||||
const WEAKLIST_OFFSET: PyObjectOffset;
|
||||
|
||||
/// Obtain a pointer to the contents of an uninitialized PyObject of this type.
|
||||
///
|
||||
/// SAFETY: `obj` must have the layout that the implementation is expecting
|
||||
unsafe fn contents_uninit(
|
||||
obj: *mut ffi::PyObject,
|
||||
) -> *mut MaybeUninit<PyClassObjectContents<T>>;
|
||||
|
||||
/// Obtain a reference to the structure that contains the pyclass struct and associated metadata.
|
||||
fn contents(&self) -> &PyClassObjectContents<T>;
|
||||
|
||||
/// Obtain a mutable reference to the structure that contains the pyclass struct and associated metadata.
|
||||
fn contents_mut(&mut self) -> &mut PyClassObjectContents<T>;
|
||||
|
||||
/// Obtain a pointer to the pyclass struct.
|
||||
fn get_ptr(&self) -> *mut T;
|
||||
|
||||
/// obtain a reference to the data at the start of the PyObject.
|
||||
fn ob_base(&self) -> &<T::BaseType as PyClassBaseType>::LayoutAsBase;
|
||||
|
||||
fn borrow_checker(&self) -> &<T::PyClassMutability as PyClassMutability>::Checker;
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct PyClassObjectContents<T: PyClassImpl> {
|
||||
pub(crate) value: ManuallyDrop<UnsafeCell<T>>,
|
||||
pub(crate) borrow_checker: <T::PyClassMutability as PyClassMutability>::Storage,
|
||||
pub(crate) thread_checker: T::ThreadChecker,
|
||||
pub(crate) dict: T::Dict,
|
||||
pub(crate) weakref: T::WeakRef,
|
||||
}
|
||||
|
||||
impl<T: PyClassImpl> PyClassObjectContents<T> {
|
||||
pub(crate) fn new(init: T) -> Self {
|
||||
PyClassObjectContents {
|
||||
value: ManuallyDrop::new(UnsafeCell::new(init)),
|
||||
borrow_checker: <T::PyClassMutability as PyClassMutability>::Storage::new(),
|
||||
thread_checker: T::ThreadChecker::new(),
|
||||
dict: T::Dict::INIT,
|
||||
weakref: T::WeakRef::INIT,
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn dealloc(&mut self, py: Python<'_>, py_object: *mut ffi::PyObject) {
|
||||
if self.thread_checker.can_drop(py) {
|
||||
unsafe { ManuallyDrop::drop(&mut self.value) };
|
||||
}
|
||||
self.dict.release_dict(py);
|
||||
unsafe { self.weakref.clear_weakrefs(py_object, py) };
|
||||
}
|
||||
}
|
||||
|
||||
/// The layout of a PyClassObject with a known sized base class.
|
||||
#[repr(C)]
|
||||
pub struct PyStaticClassObject<T: PyClassImpl> {
|
||||
ob_base: <T::BaseType as PyClassBaseType>::LayoutAsBase,
|
||||
contents: PyClassObjectContents<T>,
|
||||
}
|
||||
|
||||
impl<T: PyClassImpl<Layout = Self>> PyClassObjectLayout<T> for PyStaticClassObject<T> {
|
||||
/// Gets the offset of the contents from the start of the struct in bytes.
|
||||
const CONTENTS_OFFSET: PyObjectOffset = {
|
||||
let offset = offset_of!(Self, contents);
|
||||
// Py_ssize_t may not be equal to isize on all platforms
|
||||
assert!(offset <= ffi::Py_ssize_t::MAX as usize);
|
||||
PyObjectOffset::Absolute(offset as ffi::Py_ssize_t)
|
||||
};
|
||||
|
||||
const BASIC_SIZE: ffi::Py_ssize_t = {
|
||||
let size = core::mem::size_of::<Self>();
|
||||
assert!(size <= ffi::Py_ssize_t::MAX as usize);
|
||||
size as _
|
||||
};
|
||||
|
||||
const DICT_OFFSET: PyObjectOffset = {
|
||||
let offset = offset_of!(PyStaticClassObject<T>, contents)
|
||||
+ offset_of!(PyClassObjectContents<T>, dict);
|
||||
assert!(offset <= ffi::Py_ssize_t::MAX as usize);
|
||||
PyObjectOffset::Absolute(offset as _)
|
||||
};
|
||||
|
||||
const WEAKLIST_OFFSET: PyObjectOffset = {
|
||||
let offset = offset_of!(PyStaticClassObject<T>, contents)
|
||||
+ offset_of!(PyClassObjectContents<T>, weakref);
|
||||
assert!(offset <= ffi::Py_ssize_t::MAX as usize);
|
||||
PyObjectOffset::Absolute(offset as _)
|
||||
};
|
||||
|
||||
unsafe fn contents_uninit(
|
||||
obj: *mut ffi::PyObject,
|
||||
) -> *mut MaybeUninit<PyClassObjectContents<T>> {
|
||||
#[repr(C)]
|
||||
struct PartiallyInitializedClassObject<T: PyClassImpl> {
|
||||
_ob_base: <T::BaseType as PyClassBaseType>::LayoutAsBase,
|
||||
contents: MaybeUninit<PyClassObjectContents<T>>,
|
||||
}
|
||||
let obj = obj.cast::<PartiallyInitializedClassObject<T>>();
|
||||
unsafe { &raw mut (*obj).contents }
|
||||
}
|
||||
|
||||
fn contents(&self) -> &PyClassObjectContents<T> {
|
||||
&self.contents
|
||||
}
|
||||
|
||||
fn contents_mut(&mut self) -> &mut PyClassObjectContents<T> {
|
||||
&mut self.contents
|
||||
}
|
||||
|
||||
fn get_ptr(&self) -> *mut T {
|
||||
self.contents.value.get()
|
||||
}
|
||||
|
||||
fn ob_base(&self) -> &<T::BaseType as PyClassBaseType>::LayoutAsBase {
|
||||
&self.ob_base
|
||||
}
|
||||
|
||||
fn borrow_checker(&self) -> &<T::PyClassMutability as PyClassMutability>::Checker {
|
||||
T::PyClassMutability::borrow_checker(self)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: PyClassImpl> PyLayout<T> for PyStaticClassObject<T> {}
|
||||
impl<T: PyClass> PySizedLayout<T> for PyStaticClassObject<T> {}
|
||||
|
||||
impl<T: PyClassImpl<Layout = Self>> PyClassObjectBaseLayout<T> for PyStaticClassObject<T>
|
||||
where
|
||||
<T::BaseType as PyClassBaseType>::LayoutAsBase: PyClassObjectBaseLayout<T::BaseType>,
|
||||
{
|
||||
fn ensure_threadsafe(&self) {
|
||||
self.contents.thread_checker.ensure();
|
||||
self.ob_base.ensure_threadsafe();
|
||||
}
|
||||
fn check_threadsafe(&self) -> Result<(), PyBorrowError> {
|
||||
if !self.contents.thread_checker.check() {
|
||||
return Err(PyBorrowError { _private: () });
|
||||
}
|
||||
self.ob_base.check_threadsafe()
|
||||
}
|
||||
unsafe fn tp_dealloc(py: Python<'_>, slf: *mut ffi::PyObject) {
|
||||
// Safety: Python only calls tp_dealloc when no references to the object remain.
|
||||
let class_object = unsafe { &mut *(slf.cast::<T::Layout>()) };
|
||||
unsafe { class_object.contents_mut().dealloc(py, slf) };
|
||||
unsafe { <T::BaseType as PyClassBaseType>::LayoutAsBase::tp_dealloc(py, slf) }
|
||||
}
|
||||
}
|
||||
|
||||
/// A layout for a PyClassObject with an unknown sized base type.
|
||||
///
|
||||
/// Utilises [PEP-697](https://peps.python.org/pep-0697/)
|
||||
#[doc(hidden)]
|
||||
#[repr(C)]
|
||||
pub struct PyVariableClassObject<T: PyClassImpl> {
|
||||
ob_base: <T::BaseType as PyClassBaseType>::LayoutAsBase,
|
||||
}
|
||||
|
||||
#[cfg(Py_3_12)]
|
||||
impl<T: PyClass<Layout = Self>> PyVariableClassObject<T> {
|
||||
/// # Safety
|
||||
/// - `obj` must have the layout that the implementation is expecting
|
||||
/// - thread must be attached to the interpreter
|
||||
unsafe fn get_contents_of_obj(
|
||||
obj: *mut ffi::PyObject,
|
||||
) -> *mut MaybeUninit<PyClassObjectContents<T>> {
|
||||
// TODO: it would be nice to eventually avoid coupling to the PyO3 statics here, maybe using
|
||||
// 3.14's PyType_GetBaseByToken, to support PEP 587 / multiple interpreters better
|
||||
// SAFETY: caller guarantees attached to the interpreter
|
||||
let type_obj = T::type_object_raw(unsafe { Python::assume_attached() });
|
||||
let pointer = unsafe { ffi::PyObject_GetTypeData(obj, type_obj) };
|
||||
pointer.cast()
|
||||
}
|
||||
|
||||
fn get_contents_ptr(&self) -> *mut PyClassObjectContents<T> {
|
||||
unsafe {
|
||||
Self::get_contents_of_obj(self as *const PyVariableClassObject<T> as *mut ffi::PyObject)
|
||||
}
|
||||
.cast()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(Py_3_12)]
|
||||
impl<T: PyClass<Layout = Self>> PyClassObjectLayout<T> for PyVariableClassObject<T> {
|
||||
/// Gets the offset of the contents from the start of the struct in bytes.
|
||||
const CONTENTS_OFFSET: PyObjectOffset = PyObjectOffset::Relative(0);
|
||||
const BASIC_SIZE: ffi::Py_ssize_t = {
|
||||
let size = core::mem::size_of::<PyClassObjectContents<T>>();
|
||||
assert!(size <= ffi::Py_ssize_t::MAX as usize);
|
||||
// negative to indicate 'extra' space that cpython will allocate for us
|
||||
-(size as ffi::Py_ssize_t)
|
||||
};
|
||||
const DICT_OFFSET: PyObjectOffset = {
|
||||
let offset = offset_of!(PyClassObjectContents<T>, dict);
|
||||
assert!(offset <= ffi::Py_ssize_t::MAX as usize);
|
||||
PyObjectOffset::Relative(offset as _)
|
||||
};
|
||||
const WEAKLIST_OFFSET: PyObjectOffset = {
|
||||
let offset = offset_of!(PyClassObjectContents<T>, weakref);
|
||||
assert!(offset <= ffi::Py_ssize_t::MAX as usize);
|
||||
PyObjectOffset::Relative(offset as _)
|
||||
};
|
||||
|
||||
unsafe fn contents_uninit(
|
||||
obj: *mut ffi::PyObject,
|
||||
) -> *mut MaybeUninit<PyClassObjectContents<T>> {
|
||||
unsafe { Self::get_contents_of_obj(obj) }
|
||||
}
|
||||
|
||||
fn get_ptr(&self) -> *mut T {
|
||||
self.contents().value.get()
|
||||
}
|
||||
|
||||
fn ob_base(&self) -> &<T::BaseType as PyClassBaseType>::LayoutAsBase {
|
||||
&self.ob_base
|
||||
}
|
||||
|
||||
fn contents(&self) -> &PyClassObjectContents<T> {
|
||||
unsafe { self.get_contents_ptr().cast_const().as_ref() }
|
||||
.expect("should be able to cast PyClassObjectContents pointer")
|
||||
}
|
||||
|
||||
fn contents_mut(&mut self) -> &mut PyClassObjectContents<T> {
|
||||
unsafe { self.get_contents_ptr().as_mut() }
|
||||
.expect("should be able to cast PyClassObjectContents pointer")
|
||||
}
|
||||
|
||||
fn borrow_checker(&self) -> &<T::PyClassMutability as PyClassMutability>::Checker {
|
||||
T::PyClassMutability::borrow_checker(self)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: PyClassImpl> PyLayout<T> for PyVariableClassObject<T> {}
|
||||
|
||||
#[cfg(Py_3_12)]
|
||||
impl<T: PyClass<Layout = Self>> PyClassObjectBaseLayout<T> for PyVariableClassObject<T>
|
||||
where
|
||||
<T::BaseType as PyClassBaseType>::LayoutAsBase: PyClassObjectBaseLayout<T::BaseType>,
|
||||
{
|
||||
fn ensure_threadsafe(&self) {
|
||||
self.contents().thread_checker.ensure();
|
||||
self.ob_base.ensure_threadsafe();
|
||||
}
|
||||
fn check_threadsafe(&self) -> Result<(), PyBorrowError> {
|
||||
if !self.contents().thread_checker.check() {
|
||||
return Err(PyBorrowError { _private: () });
|
||||
}
|
||||
self.ob_base.check_threadsafe()
|
||||
}
|
||||
unsafe fn tp_dealloc(py: Python<'_>, slf: *mut ffi::PyObject) {
|
||||
// Safety: Python only calls tp_dealloc when no references to the object remain.
|
||||
let class_object = unsafe { &mut *(slf.cast::<T::Layout>()) };
|
||||
unsafe { class_object.contents_mut().dealloc(py, slf) };
|
||||
unsafe { <T::BaseType as PyClassBaseType>::LayoutAsBase::tp_dealloc(py, slf) }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(feature = "macros")]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::pyclass::boolean_struct::{False, True};
|
||||
|
||||
#[pyclass(crate = "crate", subclass)]
|
||||
struct MutableBase;
|
||||
|
||||
#[pyclass(crate = "crate", extends = MutableBase, subclass)]
|
||||
struct MutableChildOfMutableBase;
|
||||
|
||||
#[pyclass(crate = "crate", extends = MutableBase, frozen, subclass)]
|
||||
struct ImmutableChildOfMutableBase;
|
||||
|
||||
#[pyclass(crate = "crate", extends = MutableChildOfMutableBase)]
|
||||
struct MutableChildOfMutableChildOfMutableBase;
|
||||
|
||||
#[pyclass(crate = "crate", extends = ImmutableChildOfMutableBase)]
|
||||
struct MutableChildOfImmutableChildOfMutableBase;
|
||||
|
||||
#[pyclass(crate = "crate", extends = MutableChildOfMutableBase, frozen)]
|
||||
struct ImmutableChildOfMutableChildOfMutableBase;
|
||||
|
||||
#[pyclass(crate = "crate", extends = ImmutableChildOfMutableBase, frozen)]
|
||||
struct ImmutableChildOfImmutableChildOfMutableBase;
|
||||
|
||||
#[pyclass(crate = "crate", frozen, subclass)]
|
||||
struct ImmutableBase;
|
||||
|
||||
#[pyclass(crate = "crate", extends = ImmutableBase, subclass)]
|
||||
struct MutableChildOfImmutableBase;
|
||||
|
||||
#[pyclass(crate = "crate", extends = ImmutableBase, frozen, subclass)]
|
||||
struct ImmutableChildOfImmutableBase;
|
||||
|
||||
#[pyclass(crate = "crate", extends = MutableChildOfImmutableBase)]
|
||||
struct MutableChildOfMutableChildOfImmutableBase;
|
||||
|
||||
#[pyclass(crate = "crate", extends = ImmutableChildOfImmutableBase)]
|
||||
struct MutableChildOfImmutableChildOfImmutableBase;
|
||||
|
||||
#[pyclass(crate = "crate", extends = MutableChildOfImmutableBase, frozen)]
|
||||
struct ImmutableChildOfMutableChildOfImmutableBase;
|
||||
|
||||
#[pyclass(crate = "crate", extends = ImmutableChildOfImmutableBase, frozen)]
|
||||
struct ImmutableChildOfImmutableChildOfImmutableBase;
|
||||
|
||||
#[pyclass(crate = "crate", subclass)]
|
||||
struct BaseWithoutData;
|
||||
|
||||
#[pyclass(crate = "crate", subclass)]
|
||||
struct BaseWithData(#[allow(unused)] u64);
|
||||
|
||||
#[pyclass(crate = "crate", extends = BaseWithData)]
|
||||
struct ChildWithData(#[allow(unused)] u64);
|
||||
|
||||
#[pyclass(crate = "crate", extends = BaseWithData)]
|
||||
struct ChildWithoutData;
|
||||
|
||||
#[test]
|
||||
fn test_inherited_size() {
|
||||
#[cfg(all(Py_LIMITED_API, Py_GIL_DISABLED))]
|
||||
type ClassObject<T> = PyVariableClassObject<T>;
|
||||
#[cfg(not(all(Py_LIMITED_API, Py_GIL_DISABLED)))]
|
||||
type ClassObject<T> = PyStaticClassObject<T>;
|
||||
|
||||
let base_without_data_size = ClassObject::<BaseWithoutData>::BASIC_SIZE;
|
||||
let base_with_data_size = ClassObject::<BaseWithData>::BASIC_SIZE;
|
||||
let child_without_data_size = ClassObject::<ChildWithoutData>::BASIC_SIZE;
|
||||
let child_with_data_size = ClassObject::<ChildWithData>::BASIC_SIZE;
|
||||
#[cfg(all(Py_LIMITED_API, Py_GIL_DISABLED))]
|
||||
{
|
||||
assert!(base_without_data_size < 0); // negative indicates variable sized
|
||||
assert!(base_with_data_size < base_without_data_size);
|
||||
assert_eq!(child_without_data_size, 0);
|
||||
assert_eq!(
|
||||
base_with_data_size - base_without_data_size,
|
||||
child_with_data_size
|
||||
);
|
||||
}
|
||||
#[cfg(not(all(Py_LIMITED_API, Py_GIL_DISABLED)))]
|
||||
{
|
||||
assert!(base_without_data_size > 0);
|
||||
assert!(base_with_data_size > base_without_data_size);
|
||||
assert_eq!(base_with_data_size, child_without_data_size);
|
||||
assert!(base_with_data_size < child_with_data_size);
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_mutable<T: PyClass<Frozen = False, PyClassMutability = MutableClass>>() {}
|
||||
fn assert_immutable<T: PyClass<Frozen = True, PyClassMutability = ImmutableClass>>() {}
|
||||
fn assert_mutable_with_mutable_ancestor<
|
||||
T: PyClass<Frozen = False, PyClassMutability = ExtendsMutableAncestor<MutableClass>>,
|
||||
>() {
|
||||
}
|
||||
fn assert_immutable_with_mutable_ancestor<
|
||||
T: PyClass<Frozen = True, PyClassMutability = ExtendsMutableAncestor<ImmutableClass>>,
|
||||
>() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_inherited_mutability() {
|
||||
// mutable base
|
||||
assert_mutable::<MutableBase>();
|
||||
|
||||
// children of mutable base have a mutable ancestor
|
||||
assert_mutable_with_mutable_ancestor::<MutableChildOfMutableBase>();
|
||||
assert_immutable_with_mutable_ancestor::<ImmutableChildOfMutableBase>();
|
||||
|
||||
// grandchildren of mutable base have a mutable ancestor
|
||||
assert_mutable_with_mutable_ancestor::<MutableChildOfMutableChildOfMutableBase>();
|
||||
assert_mutable_with_mutable_ancestor::<MutableChildOfImmutableChildOfMutableBase>();
|
||||
assert_immutable_with_mutable_ancestor::<ImmutableChildOfMutableChildOfMutableBase>();
|
||||
assert_immutable_with_mutable_ancestor::<ImmutableChildOfImmutableChildOfMutableBase>();
|
||||
|
||||
// immutable base and children
|
||||
assert_immutable::<ImmutableBase>();
|
||||
assert_immutable::<ImmutableChildOfImmutableBase>();
|
||||
assert_immutable::<ImmutableChildOfImmutableChildOfImmutableBase>();
|
||||
|
||||
// mutable children of immutable at any level are simply mutable
|
||||
assert_mutable::<MutableChildOfImmutableBase>();
|
||||
assert_mutable::<MutableChildOfImmutableChildOfImmutableBase>();
|
||||
|
||||
// children of the mutable child display this property
|
||||
assert_mutable_with_mutable_ancestor::<MutableChildOfMutableChildOfImmutableBase>();
|
||||
assert_immutable_with_mutable_ancestor::<ImmutableChildOfMutableChildOfImmutableBase>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mutable_borrow_prevents_further_borrows() {
|
||||
Python::attach(|py| {
|
||||
let mmm = Py::new(
|
||||
py,
|
||||
PyClassInitializer::from(MutableBase)
|
||||
.add_subclass(MutableChildOfMutableBase)
|
||||
.add_subclass(MutableChildOfMutableChildOfMutableBase),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mmm_bound: &Bound<'_, MutableChildOfMutableChildOfMutableBase> = mmm.bind(py);
|
||||
|
||||
let mmm_refmut = mmm_bound.borrow_mut();
|
||||
|
||||
// Cannot take any other mutable or immutable borrows whilst the object is borrowed mutably
|
||||
assert!(mmm_bound
|
||||
.extract::<PyRef<'_, MutableChildOfMutableChildOfMutableBase>>()
|
||||
.is_err());
|
||||
assert!(mmm_bound
|
||||
.extract::<PyRef<'_, MutableChildOfMutableBase>>()
|
||||
.is_err());
|
||||
assert!(mmm_bound.extract::<PyRef<'_, MutableBase>>().is_err());
|
||||
assert!(mmm_bound
|
||||
.extract::<PyRefMut<'_, MutableChildOfMutableChildOfMutableBase>>()
|
||||
.is_err());
|
||||
assert!(mmm_bound
|
||||
.extract::<PyRefMut<'_, MutableChildOfMutableBase>>()
|
||||
.is_err());
|
||||
assert!(mmm_bound.extract::<PyRefMut<'_, MutableBase>>().is_err());
|
||||
|
||||
// With the borrow dropped, all other borrow attempts will succeed
|
||||
drop(mmm_refmut);
|
||||
|
||||
assert!(mmm_bound
|
||||
.extract::<PyRef<'_, MutableChildOfMutableChildOfMutableBase>>()
|
||||
.is_ok());
|
||||
assert!(mmm_bound
|
||||
.extract::<PyRef<'_, MutableChildOfMutableBase>>()
|
||||
.is_ok());
|
||||
assert!(mmm_bound.extract::<PyRef<'_, MutableBase>>().is_ok());
|
||||
assert!(mmm_bound
|
||||
.extract::<PyRefMut<'_, MutableChildOfMutableChildOfMutableBase>>()
|
||||
.is_ok());
|
||||
assert!(mmm_bound
|
||||
.extract::<PyRefMut<'_, MutableChildOfMutableBase>>()
|
||||
.is_ok());
|
||||
assert!(mmm_bound.extract::<PyRefMut<'_, MutableBase>>().is_ok());
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_immutable_borrows_prevent_mutable_borrows() {
|
||||
Python::attach(|py| {
|
||||
let mmm = Py::new(
|
||||
py,
|
||||
PyClassInitializer::from(MutableBase)
|
||||
.add_subclass(MutableChildOfMutableBase)
|
||||
.add_subclass(MutableChildOfMutableChildOfMutableBase),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mmm_bound: &Bound<'_, MutableChildOfMutableChildOfMutableBase> = mmm.bind(py);
|
||||
|
||||
let mmm_refmut = mmm_bound.borrow();
|
||||
|
||||
// Further immutable borrows are ok
|
||||
assert!(mmm_bound
|
||||
.extract::<PyRef<'_, MutableChildOfMutableChildOfMutableBase>>()
|
||||
.is_ok());
|
||||
assert!(mmm_bound
|
||||
.extract::<PyRef<'_, MutableChildOfMutableBase>>()
|
||||
.is_ok());
|
||||
assert!(mmm_bound.extract::<PyRef<'_, MutableBase>>().is_ok());
|
||||
|
||||
// Further mutable borrows are not ok
|
||||
assert!(mmm_bound
|
||||
.extract::<PyRefMut<'_, MutableChildOfMutableChildOfMutableBase>>()
|
||||
.is_err());
|
||||
assert!(mmm_bound
|
||||
.extract::<PyRefMut<'_, MutableChildOfMutableBase>>()
|
||||
.is_err());
|
||||
assert!(mmm_bound.extract::<PyRefMut<'_, MutableBase>>().is_err());
|
||||
|
||||
// With the borrow dropped, all mutable borrow attempts will succeed
|
||||
drop(mmm_refmut);
|
||||
|
||||
assert!(mmm_bound
|
||||
.extract::<PyRefMut<'_, MutableChildOfMutableChildOfMutableBase>>()
|
||||
.is_ok());
|
||||
assert!(mmm_bound
|
||||
.extract::<PyRefMut<'_, MutableChildOfMutableBase>>()
|
||||
.is_ok());
|
||||
assert!(mmm_bound.extract::<PyRefMut<'_, MutableBase>>().is_ok());
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn test_thread_safety() {
|
||||
#[crate::pyclass(crate = "crate")]
|
||||
struct MyClass {
|
||||
x: u64,
|
||||
}
|
||||
|
||||
Python::attach(|py| {
|
||||
let inst = Py::new(py, MyClass { x: 0 }).unwrap();
|
||||
|
||||
let total_modifications = py.detach(|| {
|
||||
std::thread::scope(|s| {
|
||||
// Spawn a bunch of threads all racing to write to
|
||||
// the same instance of `MyClass`.
|
||||
let threads = (0..10)
|
||||
.map(|_| {
|
||||
s.spawn(|| {
|
||||
Python::attach(|py| {
|
||||
// Each thread records its own view of how many writes it made
|
||||
let mut local_modifications = 0;
|
||||
for _ in 0..100 {
|
||||
if let Ok(mut i) = inst.try_borrow_mut(py) {
|
||||
i.x += 1;
|
||||
local_modifications += 1;
|
||||
}
|
||||
}
|
||||
local_modifications
|
||||
})
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Sum up the total number of writes made by all threads
|
||||
threads.into_iter().map(|t| t.join().unwrap()).sum::<u64>()
|
||||
})
|
||||
});
|
||||
|
||||
// If the implementation is free of data races, the total number of writes
|
||||
// should match the final value of `x`.
|
||||
assert_eq!(total_modifications, inst.borrow(py).x);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn test_thread_safety_2() {
|
||||
struct SyncUnsafeCell<T>(UnsafeCell<T>);
|
||||
unsafe impl<T> Sync for SyncUnsafeCell<T> {}
|
||||
|
||||
impl<T> SyncUnsafeCell<T> {
|
||||
fn get(&self) -> *mut T {
|
||||
self.0.get()
|
||||
}
|
||||
}
|
||||
|
||||
let data = SyncUnsafeCell(UnsafeCell::new(0));
|
||||
let data2 = SyncUnsafeCell(UnsafeCell::new(0));
|
||||
let borrow_checker = BorrowChecker(BorrowFlag(AtomicUsize::new(BorrowFlag::UNUSED)));
|
||||
|
||||
std::thread::scope(|s| {
|
||||
s.spawn(|| {
|
||||
for _ in 0..1_000_000 {
|
||||
if borrow_checker.try_borrow_mut().is_ok() {
|
||||
// thread 1 writes to both values during the mutable borrow
|
||||
unsafe { *data.get() += 1 };
|
||||
unsafe { *data2.get() += 1 };
|
||||
borrow_checker.release_borrow_mut();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
s.spawn(|| {
|
||||
for _ in 0..1_000_000 {
|
||||
if borrow_checker.try_borrow().is_ok() {
|
||||
// if the borrow checker is working correctly, it should be impossible
|
||||
// for thread 2 to observe a difference in the two values
|
||||
assert_eq!(unsafe { *data.get() }, unsafe { *data2.get() });
|
||||
borrow_checker.release_borrow();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user