Skip to content

Commit 229a9c8

Browse files
committed
Make PyRef<T> wrap a NonNull<PyObjectView<T>>
This makes debuggers (or rust-gdb, at least) more pleasant to use, since you don't have to manually cast `PyRef<T>.obj.ptr as `*const PyObjectView<T>` Also get rid of PyGenericObject, since it's vestigial
1 parent 8c7def0 commit 229a9c8

File tree

6 files changed

+75
-65
lines changed

6 files changed

+75
-65
lines changed

derive/src/pyclass.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -667,7 +667,7 @@ impl ToTokens for GetSetNursery {
667667
#( #cfgs )*
668668
class.set_str_attr(
669669
#name,
670-
::rustpython_vm::PyGenericObject::new(
670+
::rustpython_vm::PyRef::new_ref(
671671
::rustpython_vm::builtins::PyGetSet::new(#name.into(), class.clone())
672672
.with_get(&Self::#getter)
673673
#setter #deleter,

vm/src/builtins/object.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ use super::{PyDict, PyDictRef, PyList, PyStr, PyStrRef, PyType, PyTypeRef};
22
use crate::common::hash::PyHash;
33
use crate::{
44
function::FuncArgs, types::PyComparisonOp, utils::Either, IdProtocol, ItemProtocol,
5-
PyArithmeticValue, PyAttributes, PyClassImpl, PyComparisonValue, PyContext, PyGenericObject,
6-
PyObject, PyObjectRef, PyResult, PyValue, TypeProtocol, VirtualMachine,
5+
PyArithmeticValue, PyAttributes, PyClassImpl, PyComparisonValue, PyContext, PyObject,
6+
PyObjectRef, PyResult, PyValue, TypeProtocol, VirtualMachine,
77
};
88

99
/// object()
@@ -34,7 +34,7 @@ impl PyBaseObject {
3434
} else {
3535
Some(vm.ctx.new_dict())
3636
};
37-
Ok(PyGenericObject::new(PyBaseObject, cls, dict))
37+
Ok(crate::PyRef::new_ref(PyBaseObject, cls, dict).into())
3838
}
3939

4040
#[pyslot]

vm/src/pyobject.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@ use crate::common::{
44
static_cell,
55
};
66
pub use crate::pyobjectrc::{
7-
PyGenericObject, PyObject, PyObjectRef, PyObjectView, PyObjectWeak, PyObjectWrap, PyRef,
8-
PyWeakRef,
7+
PyObject, PyObjectRef, PyObjectView, PyObjectWeak, PyObjectWrap, PyRef, PyWeakRef,
98
};
109
use crate::{
1110
builtins::{
@@ -334,7 +333,7 @@ impl PyContext {
334333
class.slots.flags.contains(PyTypeFlags::HAS_DICT),
335334
dict.is_some()
336335
);
337-
PyGenericObject::new(object::PyBaseObject, class, dict)
336+
PyRef::new_ref(object::PyBaseObject, class, dict).into()
338337
}
339338
}
340339

vm/src/pyobjectrc.rs

Lines changed: 65 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use std::mem::ManuallyDrop;
1515
use std::ops::Deref;
1616
use std::ptr::{self, NonNull};
1717

18-
// so, PyObjectRef is basically equivalent to `PyRc<PyGenericObject<dyn PyObjectPayload>>`, except it's
18+
// so, PyObjectRef is basically equivalent to `PyRc<PyInner<dyn PyObjectPayload>>`, except it's
1919
// only one pointer in width rather than 2. We do that by manually creating a vtable, and putting
2020
// a &'static reference to it inside the `PyRc` rather than adjacent to it, like trait objects do.
2121
// This can lead to faster code since there's just less data to pass around, as well as because of
@@ -32,18 +32,18 @@ use std::ptr::{self, NonNull};
3232
// There has to be padding in the space between the 2 fields. But, if that field is a trait object
3333
// (like `dyn PyObjectPayload`) we don't *know* how much padding there is between the `payload`
3434
// field and the previous field. So, Rust has to consult the vtable to know the exact offset of
35-
// `payload` in `PyGenericObject<dyn PyObjectPayload>`, which has a huge performance impact when *every
35+
// `payload` in `PyInner<dyn PyObjectPayload>`, which has a huge performance impact when *every
3636
// single payload access* requires a vtable lookup. Thankfully, we're able to avoid that because of
3737
// the way we use PyObjectRef, in that whenever we want to access the payload we (almost) always
3838
// access it from a generic function. So, rather than doing
3939
//
4040
// - check vtable for payload offset
41-
// - get offset in PyGenericObject struct
41+
// - get offset in PyInner struct
4242
// - call as_any() method of PyObjectPayload
4343
// - call downcast_ref() method of Any
4444
// we can just do
4545
// - check vtable that typeid matches
46-
// - pointer cast directly to *const PyGenericObject<T>
46+
// - pointer cast directly to *const PyInner<T>
4747
//
4848
// and at that point the compiler can know the offset of `payload` for us because **we've given it a
4949
// concrete type to work with before we ever access the `payload` field**
@@ -395,27 +395,17 @@ impl InstanceDict {
395395
}
396396
}
397397

398-
/// This is an actual python object. It consists of a `typ` which is the
399-
/// python class, and carries some rust payload optionally. This rust
400-
/// payload can be a rust float or rust int in case of float and int objects.
401-
#[repr(transparent)]
402-
pub struct PyGenericObject<T> {
403-
inner: PyInner<T>,
404-
}
405-
406-
impl<T: PyObjectPayload> PyGenericObject<T> {
407-
#[allow(clippy::new_ret_no_self)]
408-
pub fn new(payload: T, typ: PyTypeRef, dict: Option<PyDictRef>) -> PyObjectRef {
409-
let inner = PyInner {
398+
impl<T: PyObjectPayload> PyInner<T> {
399+
fn new(payload: T, typ: PyTypeRef, dict: Option<PyDictRef>) -> Box<Self> {
400+
Box::new(PyInner {
410401
refcount: RefCount::new(),
411402
typeid: TypeId::of::<T>(),
412403
vtable: PyObjVTable::of::<T>(),
413404
typ: PyRwLock::new(typ),
414405
dict: dict.map(InstanceDict::new),
415406
weaklist: WeakRefList::new(),
416407
payload,
417-
};
418-
PyObjectRef::new(PyGenericObject { inner })
408+
})
419409
}
420410
}
421411

@@ -501,13 +491,6 @@ impl PyObjectRef {
501491
}
502492
}
503493

504-
fn new<T: PyObjectPayload>(value: PyGenericObject<T>) -> Self {
505-
let inner: *const PyInner<T> = Box::into_raw(Box::new(value.inner));
506-
Self {
507-
ptr: unsafe { NonNull::new_unchecked(inner as *mut PyObject) },
508-
}
509-
}
510-
511494
/// Attempt to downcast this reference to a subclass.
512495
///
513496
/// If the downcast fails, the original ref is returned in as `Err` so
@@ -712,12 +695,9 @@ impl PyObject {
712695
pub fn as_raw(&self) -> *const PyObject {
713696
self
714697
}
715-
}
716698

717-
impl PyObjectRef {
718-
#[inline(never)]
719-
#[cold]
720-
fn drop_slow(&mut self) {
699+
#[inline]
700+
fn drop_slow_inner(&self) -> Result<(), ()> {
721701
// CPython-compatible drop implementation
722702
if let Some(slot_del) = self.class().mro_find_map(|cls| cls.slots.del.load()) {
723703
let ret = crate::vm::thread::with_vm(self, |vm| {
@@ -731,7 +711,7 @@ impl PyObjectRef {
731711
// the decref right above set refcount back to 0
732712
Some(true) => {}
733713
// we've been resurrected by __del__
734-
Some(false) => return,
714+
Some(false) => return Err(()),
735715
None => {
736716
warn!("couldn't run __del__ method for object")
737717
}
@@ -741,8 +721,20 @@ impl PyObjectRef {
741721
wrl.clear();
742722
}
743723

744-
let drop_dealloc = self.0.vtable.drop_dealloc;
745-
unsafe { drop_dealloc(self.ptr.as_ptr()) }
724+
Ok(())
725+
}
726+
727+
/// Can only be called when refcount has dropped to zero. `ptr` must be valid
728+
#[inline(never)]
729+
#[cold]
730+
unsafe fn drop_slow(ptr: NonNull<PyObject>) {
731+
if let Err(()) = ptr.as_ref().drop_slow_inner() {
732+
// abort drop for whatever reason
733+
return;
734+
}
735+
let drop_dealloc = ptr.as_ref().0.vtable.drop_dealloc;
736+
// call drop only when there are no references in scope - stacked borrows stuff
737+
drop_dealloc(ptr.as_ptr())
746738
}
747739
}
748740

@@ -805,7 +797,7 @@ impl PyObjectWeak {
805797
impl Drop for PyObjectRef {
806798
fn drop(&mut self) {
807799
if self.0.refcount.dec() {
808-
self.drop_slow()
800+
unsafe { PyObject::drop_slow(self.ptr) }
809801
}
810802
}
811803
}
@@ -870,9 +862,9 @@ impl<T: PyObjectPayload> ToOwned for PyObjectView<T> {
870862

871863
#[inline(always)]
872864
fn to_owned(&self) -> Self::Owned {
865+
self.0.refcount.inc();
873866
PyRef {
874-
obj: self.as_object().to_owned(),
875-
_marker: PhantomData,
867+
ptr: NonNull::from(self),
876868
}
877869
}
878870
}
@@ -911,9 +903,14 @@ impl<T: PyObjectPayload> fmt::Debug for PyObjectView<T> {
911903
/// where a reference to the same object must be returned.
912904
#[repr(transparent)]
913905
pub struct PyRef<T: PyObjectPayload> {
914-
// invariant: this obj must always have payload of type T
915-
obj: PyObjectRef,
916-
_marker: PhantomData<T>,
906+
ptr: NonNull<PyObjectView<T>>,
907+
}
908+
909+
cfg_if::cfg_if! {
910+
if #[cfg(feature = "threading")] {
911+
unsafe impl<T: PyObjectPayload> Send for PyRef<T> {}
912+
unsafe impl<T: PyObjectPayload> Sync for PyRef<T> {}
913+
}
917914
}
918915

919916
impl<T: PyObjectPayload> fmt::Debug for PyRef<T> {
@@ -922,6 +919,15 @@ impl<T: PyObjectPayload> fmt::Debug for PyRef<T> {
922919
}
923920
}
924921

922+
impl<T: PyObjectPayload> Drop for PyRef<T> {
923+
#[inline]
924+
fn drop(&mut self) {
925+
if self.0.refcount.dec() {
926+
unsafe { PyObject::drop_slow(self.ptr.cast::<PyObject>()) }
927+
}
928+
}
929+
}
930+
925931
impl<T: PyObjectPayload> Clone for PyRef<T> {
926932
#[inline(always)]
927933
fn clone(&self) -> Self {
@@ -932,43 +938,47 @@ impl<T: PyObjectPayload> Clone for PyRef<T> {
932938
impl<T: PyObjectPayload> PyRef<T> {
933939
unsafe fn from_raw(raw: *const PyObjectView<T>) -> Self {
934940
Self {
935-
obj: PyObjectRef::from_raw(raw as _),
936-
_marker: PhantomData,
941+
ptr: NonNull::new_unchecked(raw as *mut _),
937942
}
938943
}
939944

940945
/// Safety: payload type of `obj` must be `T`
946+
#[inline]
941947
unsafe fn from_obj_unchecked(obj: PyObjectRef) -> Self {
942948
debug_assert!(obj.payload_is::<T>());
949+
let obj = ManuallyDrop::new(obj);
943950
Self {
944-
obj,
945-
_marker: PhantomData,
951+
ptr: obj.ptr.cast(),
946952
}
947953
}
948954

949-
// ideally we'd be able to define this in pyobject.rs, but method visibility rules are weird
955+
#[inline]
950956
pub fn new_ref(payload: T, typ: crate::builtins::PyTypeRef, dict: Option<PyDictRef>) -> Self {
951-
let obj = PyGenericObject::new(payload, typ, dict);
952-
// SAFETY: we just created the object from a payload of type T
953-
unsafe { Self::from_obj_unchecked(obj) }
957+
let inner = Box::into_raw(PyInner::new(payload, typ, dict));
958+
Self {
959+
ptr: unsafe { NonNull::new_unchecked(inner.cast::<PyObjectView<T>>()) },
960+
}
954961
}
955962
}
956963

957964
impl<T> PyObjectWrap for PyRef<T>
958965
where
959966
T: PyObjectPayload,
960967
{
968+
#[inline]
961969
fn into_object(self) -> PyObjectRef {
962-
self.obj
970+
let me = ManuallyDrop::new(self);
971+
PyObjectRef { ptr: me.ptr.cast() }
963972
}
964973
}
965974

966975
impl<T> AsRef<PyObject> for PyRef<T>
967976
where
968977
T: PyObjectPayload,
969978
{
979+
#[inline(always)]
970980
fn as_ref(&self) -> &PyObject {
971-
&self.obj
981+
(**self).as_object()
972982
}
973983
}
974984

@@ -987,8 +997,9 @@ where
987997
{
988998
type Target = PyObjectView<T>;
989999

1000+
#[inline(always)]
9901001
fn deref(&self) -> &PyObjectView<T> {
991-
unsafe { &*(&*self.obj as *const PyObject as *const PyObjectView<T>) }
1002+
unsafe { self.ptr.as_ref() }
9921003
}
9931004
}
9941005

@@ -1000,10 +1011,10 @@ pub struct PyWeakRef<T: PyObjectPayload> {
10001011

10011012
impl<T: PyObjectPayload> PyWeakRef<T> {
10021013
pub fn upgrade(&self) -> Option<PyRef<T>> {
1003-
self.weak.upgrade().map(|obj| PyRef {
1004-
obj,
1005-
_marker: PhantomData,
1006-
})
1014+
self.weak
1015+
.upgrade()
1016+
// SAFETY: PyWeakRef<T> was always created from a PyRef<T>, so the object is T
1017+
.map(|obj| unsafe { PyRef::from_obj_unchecked(obj) })
10071018
}
10081019
}
10091020

vm/src/vm.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1936,7 +1936,7 @@ impl<'vm> ReprGuard<'vm> {
19361936
let mut guards = vm.repr_guards.borrow_mut();
19371937

19381938
// Should this be a flag on the obj itself? putting it in a global variable for now until it
1939-
// decided the form of the PyGenericObject. https://github.com/RustPython/RustPython/issues/371
1939+
// decided the form of PyObject. https://github.com/RustPython/RustPython/issues/371
19401940
let id = obj.get_id();
19411941
if guards.contains(&id) {
19421942
return None;

wasm/lib/src/browser_module.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ mod _browser {
1010
builtins::{PyDictRef, PyStrRef},
1111
function::{ArgCallable, IntoPyObject, OptionalArg},
1212
import::import_file,
13-
PyClassImpl, PyGenericObject, PyObjectRef, PyResult, PyValue, VirtualMachine,
13+
PyClassImpl, PyObjectRef, PyRef, PyResult, PyValue, VirtualMachine,
1414
};
1515
use wasm_bindgen::{prelude::*, JsCast};
1616
use wasm_bindgen_futures::JsFuture;
@@ -179,8 +179,8 @@ mod _browser {
179179
}
180180

181181
#[pyattr]
182-
fn document(vm: &VirtualMachine) -> PyObjectRef {
183-
PyGenericObject::new(
182+
fn document(vm: &VirtualMachine) -> PyRef<Document> {
183+
PyRef::new_ref(
184184
Document {
185185
doc: window().document().expect("Document missing from window"),
186186
},

0 commit comments

Comments
 (0)