mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-29 19:24:57 +08:00
Fix tuple subclass preservation in type bases (#8420)
* Fix tuple subclass preservation in type bases Assisted-by: Codex:gpt-5.6-sol * Refine type bases representation Store validated base classes in the original typed tuple, while retaining a bootstrap vector only until a Python tuple can be materialized. Assisted-by: gpt-5.6-sol Assisted-by: Codex:gpt-5.6-sol * Bootstrap tuple type with type hierarchy Initialize tuple and the canonical empty tuple alongside type and object so every type stores an actual typed bases tuple from construction. Remove the bootstrap bases representation and keep raw initialization Miri-safe. Assisted-by: Codex:gpt-5.6-sol
This commit is contained in:
1 parent
d62ab2a16c
commit
a783ae8ffe
7 files changed
+333
-203
No files matched your search
Vendored
-1
@@ -1881,7 +1881,6 @@ class ClassCreationTests(unittest.TestCase):
|
||||
D.__getitem__ = dict.__getitem__
|
||||
self.assertIs(d[None], None)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: <class 'tuple'> != <class 'test.test_types.ClassCreationTests.test_tu[41 chars]ass'>
|
||||
def test_tuple_subclass_as_bases(self):
|
||||
# gh-132176: it used to crash on using
|
||||
# tuple subclass for as base classes.
|
||||
|
||||
@@ -375,6 +375,23 @@ impl PyTuple<PyObjectRef> {
|
||||
}
|
||||
|
||||
impl<T> PyTuple<PyRef<T>> {
|
||||
pub(crate) fn new_ref_typed_with_type(
|
||||
elements: Vec<PyRef<T>>,
|
||||
tuple_type: PyTypeRef,
|
||||
) -> PyRef<Self> {
|
||||
// SAFETY: PyRef<T> has the same layout as PyObjectRef.
|
||||
unsafe {
|
||||
let elements: Vec<PyObjectRef> =
|
||||
core::mem::transmute::<Vec<PyRef<T>>, Vec<PyObjectRef>>(elements);
|
||||
let tuple = PyRef::new_ref(
|
||||
PyTuple::new_unchecked(elements.into_boxed_slice()),
|
||||
tuple_type,
|
||||
None,
|
||||
);
|
||||
core::mem::transmute::<PyRef<PyTuple>, PyRef<Self>>(tuple)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_ref_typed(elements: Vec<PyRef<T>>, ctx: &Context) -> PyRef<Self> {
|
||||
// SAFETY: PyRef<T> has the same layout as PyObjectRef
|
||||
unsafe {
|
||||
|
||||
@@ -41,11 +41,13 @@ use num_traits::ToPrimitive;
|
||||
use rustpython_common::wtf8::Wtf8;
|
||||
use std::collections::HashSet;
|
||||
|
||||
pub(crate) type PyTypeTupleRef = PyRef<PyTuple<PyTypeRef>>;
|
||||
|
||||
#[pyclass(module = false, name = "type", traverse = "manual")]
|
||||
pub struct PyType {
|
||||
/// tp_base. Written under the type lock (see `set_bases`); read lock-free.
|
||||
pub base: PyAtomicRef<Option<Self>>,
|
||||
pub bases: PyRwLock<Vec<PyTypeRef>>,
|
||||
pub bases: PyRwLock<PyTypeTupleRef>,
|
||||
pub mro: PyRwLock<Vec<PyTypeRef>>,
|
||||
pub subclasses: PyRwLock<Vec<PyRef<PyWeak>>>,
|
||||
pub attributes: TypeNamespace,
|
||||
@@ -241,7 +243,7 @@ unsafe impl crate::object::Traverse for PyType {
|
||||
if let Some(base) = self.base.deref() {
|
||||
tracer_fn(base.as_object());
|
||||
}
|
||||
self.bases.traverse(tracer_fn);
|
||||
tracer_fn(self.bases.read_recursive().as_untyped().as_object());
|
||||
self.mro.traverse(tracer_fn);
|
||||
self.subclasses.traverse(tracer_fn);
|
||||
self.attributes.traverse(tracer_fn);
|
||||
@@ -256,10 +258,10 @@ unsafe impl crate::object::Traverse for PyType {
|
||||
if let Some(base) = unsafe { self.base.swap(None) } {
|
||||
out.push(base.into());
|
||||
}
|
||||
if let Some(mut guard) = self.bases.try_write() {
|
||||
for base in guard.drain(..) {
|
||||
out.push(base.into());
|
||||
}
|
||||
if let Some(mut bases) = self.bases.try_write() {
|
||||
let empty = object::PyBaseObject::static_type().bases.read().clone();
|
||||
let old_bases = core::mem::replace(&mut *bases, empty);
|
||||
out.push(old_bases.into_untyped().into());
|
||||
}
|
||||
if let Some(mut guard) = self.mro.try_write() {
|
||||
for typ in guard.drain(..) {
|
||||
@@ -766,6 +768,7 @@ impl PyType {
|
||||
specialization_cache: TypeSpecializationCache::new(),
|
||||
interpreter_id: HeapTypeExt::creating_interpreter_id(),
|
||||
};
|
||||
let bases = PyTuple::new_ref_typed(bases, ctx);
|
||||
let base = bases[0].clone();
|
||||
|
||||
Self::new_heap_inner(base, bases, attrs, slots, heaptype_ext, metaclass, ctx)
|
||||
@@ -934,7 +937,7 @@ impl PyType {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn new_heap_inner(
|
||||
base: PyRef<Self>,
|
||||
bases: Vec<PyRef<Self>>,
|
||||
bases: PyTypeTupleRef,
|
||||
attrs: PyAttributes,
|
||||
mut slots: PyTypeSlots,
|
||||
heaptype_ext: HeapTypeExt,
|
||||
@@ -1044,13 +1047,14 @@ impl PyType {
|
||||
}
|
||||
|
||||
let inherited_abc_tpflags = Self::inherited_abc_tpflags(core::slice::from_ref(&base));
|
||||
let bases = PyRwLock::new(vec![base.clone()]);
|
||||
let bases =
|
||||
PyTuple::new_ref_typed_with_type(vec![base.clone()], PyTuple::static_type().to_owned());
|
||||
let mro = base.mro_map_collect(|x| x.to_owned());
|
||||
|
||||
let new_type = PyRef::new_ref(
|
||||
Self {
|
||||
base: Some(base).into(),
|
||||
bases,
|
||||
bases: PyRwLock::new(bases),
|
||||
mro: PyRwLock::new(mro),
|
||||
subclasses: PyRwLock::default(),
|
||||
attributes: attrs.into(),
|
||||
@@ -1163,6 +1167,11 @@ impl PyType {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn finalize_bootstrap_static(typ: &Py<Self>) {
|
||||
Self::set_new(&typ.slots, typ.base.deref());
|
||||
Self::set_alloc(&typ.slots, typ.base.deref());
|
||||
}
|
||||
|
||||
/// Inherit slots from base type. inherit_slots
|
||||
pub(crate) fn inherit_slots(&self, base: &Self) {
|
||||
// Use SLOT_DEFS to iterate all slots
|
||||
@@ -1642,16 +1651,10 @@ impl Py<PyType> {
|
||||
impl PyType {
|
||||
#[pygetset]
|
||||
fn __bases__(&self, vm: &VirtualMachine) -> PyTupleRef {
|
||||
vm.ctx.new_tuple(
|
||||
self.bases
|
||||
.read()
|
||||
.iter()
|
||||
.map(|x| x.as_object().to_owned())
|
||||
.collect(),
|
||||
)
|
||||
Self::with_type_lock(vm, || self.bases.read().clone().into_untyped())
|
||||
}
|
||||
#[pygetset(setter, name = "__bases__")]
|
||||
fn set_bases(zelf: &Py<Self>, bases: Vec<PyTypeRef>, vm: &VirtualMachine) -> PyResult<()> {
|
||||
fn set_bases(zelf: &Py<Self>, bases_tuple: PyTupleRef, vm: &VirtualMachine) -> PyResult<()> {
|
||||
// TODO: Assigning to __bases__ is only used in typing.NamedTupleMeta.__new__
|
||||
// Rather than correctly re-initializing the class, we are skipping a few steps for now
|
||||
if zelf.slots.flags.has_feature(PyTypeFlags::IMMUTABLETYPE) {
|
||||
@@ -1660,12 +1663,22 @@ impl PyType {
|
||||
zelf.name()
|
||||
)));
|
||||
}
|
||||
if bases.is_empty() {
|
||||
if bases_tuple.is_empty() {
|
||||
return Err(vm.new_type_error(format!(
|
||||
"can only assign non-empty tuple to {}.__bases__, not ()",
|
||||
zelf.name()
|
||||
)));
|
||||
}
|
||||
for base in bases_tuple.iter() {
|
||||
if base.downcast_ref::<Self>().is_none() {
|
||||
return Err(vm.new_type_error(format!(
|
||||
"{}.__bases__ must be tuple of classes, not '{}'",
|
||||
zelf.name(),
|
||||
base.class().name()
|
||||
)));
|
||||
}
|
||||
}
|
||||
let bases = bases_tuple.try_into_typed::<Self>(vm)?;
|
||||
|
||||
// TODO: check for mro cycles
|
||||
|
||||
@@ -1776,7 +1789,7 @@ impl PyType {
|
||||
keep_alive(failed_base, &mut retired);
|
||||
}
|
||||
register_subclasses(&zelf.bases.read());
|
||||
retired.extend(failed_bases.into_iter().map(Into::into));
|
||||
retired.push(failed_bases.into_untyped().into());
|
||||
zelf.modified_inner();
|
||||
return Err(err);
|
||||
}
|
||||
@@ -1786,7 +1799,7 @@ impl PyType {
|
||||
retired.extend(old_mro.into_iter().map(Into::into));
|
||||
retired.push(cls.into());
|
||||
}
|
||||
retired.extend(old_bases.into_iter().map(Into::into));
|
||||
retired.push(old_bases.into_untyped().into());
|
||||
if let Some(old_base) = old_base {
|
||||
keep_alive(old_base, &mut retired);
|
||||
}
|
||||
@@ -2293,26 +2306,24 @@ impl Constructor for PyType {
|
||||
|
||||
let (metatype, base, bases, base_is_type) = if bases.is_empty() {
|
||||
let base = vm.ctx.types.object_type.to_owned();
|
||||
(metatype, base.clone(), vec![base], false)
|
||||
let bases = PyTuple::new_ref_typed(vec![base.clone()], &vm.ctx);
|
||||
(metatype, base, bases, false)
|
||||
} else {
|
||||
let bases = bases
|
||||
.iter()
|
||||
.map(|obj| {
|
||||
obj.clone().downcast::<Self>().or_else(|obj| {
|
||||
if vm
|
||||
.get_attribute_opt(obj, identifier!(vm, __mro_entries__))?
|
||||
.is_some()
|
||||
{
|
||||
Err(vm.new_type_error(
|
||||
"type() doesn't support MRO entry resolution; \
|
||||
use types.new_class()",
|
||||
))
|
||||
} else {
|
||||
Err(vm.new_type_error("bases must be types"))
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect::<PyResult<Vec<_>>>()?;
|
||||
for obj in bases.iter() {
|
||||
if obj.downcast_ref::<Self>().is_none() {
|
||||
if vm
|
||||
.get_attribute_opt(obj.clone(), identifier!(vm, __mro_entries__))?
|
||||
.is_some()
|
||||
{
|
||||
return Err(vm.new_type_error(
|
||||
"type() doesn't support MRO entry resolution; \
|
||||
use types.new_class()",
|
||||
));
|
||||
}
|
||||
return Err(vm.new_type_error("bases must be types"));
|
||||
}
|
||||
}
|
||||
let bases = bases.try_into_typed::<Self>(vm)?;
|
||||
|
||||
// Search the bases for the proper metatype to deal with this:
|
||||
let winner = calculate_meta_class(metatype.clone(), &bases, vm)?;
|
||||
|
||||
+222
-149
@@ -17,7 +17,7 @@ use super::{
|
||||
};
|
||||
use crate::object::traverse_object::PyObjVTable;
|
||||
use crate::{
|
||||
builtins::{PyDictRef, PyType, PyTypeRef},
|
||||
builtins::{PyDictRef, PyTuple, PyTupleRef, PyType, PyTypeRef, type_::PyTypeTupleRef},
|
||||
common::{
|
||||
atomic::{Ordering, PyAtomic, Radium},
|
||||
linked_list::{Link, Pointers},
|
||||
@@ -2496,12 +2496,12 @@ impl<T> Clone for PyRef<T> {
|
||||
}
|
||||
|
||||
impl<T: PyPayload> PyRef<T> {
|
||||
// #[inline(always)]
|
||||
// pub(crate) const fn into_non_null(self) -> NonNull<Py<T>> {
|
||||
// let ptr = self.ptr;
|
||||
// std::mem::forget(self);
|
||||
// ptr
|
||||
// }
|
||||
#[inline(always)]
|
||||
pub(super) const fn into_non_null(self) -> NonNull<Py<T>> {
|
||||
let ptr = self.ptr;
|
||||
core::mem::forget(self);
|
||||
ptr
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) const unsafe fn from_non_null(ptr: NonNull<Py<T>>) -> Self {
|
||||
@@ -2733,159 +2733,197 @@ impl<T: PyPayload> PyWeakRef<T> {
|
||||
|
||||
/// Partially initialize a struct, ensuring that all fields are
|
||||
/// either given values or explicitly left uninitialized
|
||||
macro_rules! partially_init {
|
||||
(
|
||||
$ty:path {$($init_field:ident: $init_value:expr),*$(,)?},
|
||||
Uninit { $($uninit_field:ident),*$(,)? }$(,)?
|
||||
) => {{
|
||||
// check all the fields are there but *don't* actually run it
|
||||
|
||||
#[allow(clippy::diverging_sub_expression, reason = "intentional compile-time field check in an unreachable branch")]
|
||||
if false {
|
||||
#[allow(invalid_value, dead_code, unreachable_code)]
|
||||
let _ = {$ty {
|
||||
$($init_field: $init_value,)*
|
||||
$($uninit_field: unreachable!(),)*
|
||||
}};
|
||||
}
|
||||
let mut m = ::core::mem::MaybeUninit::<$ty>::uninit();
|
||||
#[allow(unused_unsafe)]
|
||||
unsafe {
|
||||
$(::core::ptr::write(&mut (*m.as_mut_ptr()).$init_field, $init_value);)*
|
||||
}
|
||||
m
|
||||
}};
|
||||
pub(crate) struct BootstrapTypeHierarchy {
|
||||
pub type_type: PyTypeRef,
|
||||
pub object_type: PyTypeRef,
|
||||
pub tuple_type: PyTypeRef,
|
||||
pub weakref_type: PyTypeRef,
|
||||
pub empty_tuple: PyTupleRef,
|
||||
}
|
||||
|
||||
pub(crate) fn init_type_hierarchy() -> (PyTypeRef, PyTypeRef, PyTypeRef) {
|
||||
use crate::{builtins::object, class::PyClassImpl};
|
||||
pub(crate) fn init_type_hierarchy() -> BootstrapTypeHierarchy {
|
||||
use crate::{
|
||||
builtins::{object, tuple},
|
||||
class::PyClassImpl,
|
||||
};
|
||||
use core::mem::MaybeUninit;
|
||||
|
||||
// `type` inherits from `object`
|
||||
// and both `type` and `object are instances of `type`.
|
||||
// to produce this circular dependency, we need an unsafe block.
|
||||
// (and yes, this will never get dropped. TODO?)
|
||||
let (type_type, object_type) = {
|
||||
// We cast between these 2 types, so make sure (at compile time) that there's no change in
|
||||
// layout when we wrap PyInner<PyTypeObj> in MaybeUninit<>
|
||||
static_assertions::assert_eq_size!(MaybeUninit<PyInner<PyType>>, PyInner<PyType>);
|
||||
static_assertions::assert_eq_align!(MaybeUninit<PyInner<PyType>>, PyInner<PyType>);
|
||||
static_assertions::assert_eq_size!(MaybeUninit<PyInner<PyType>>, PyInner<PyType>);
|
||||
static_assertions::assert_eq_align!(MaybeUninit<PyInner<PyType>>, PyInner<PyType>);
|
||||
static_assertions::assert_eq_size!(MaybeUninit<PyInner<PyTuple>>, PyInner<PyTuple>);
|
||||
static_assertions::assert_eq_align!(MaybeUninit<PyInner<PyTuple>>, PyInner<PyTuple>);
|
||||
|
||||
let type_payload = PyType {
|
||||
base: None.into(),
|
||||
bases: PyRwLock::default(),
|
||||
mro: PyRwLock::default(),
|
||||
subclasses: PyRwLock::default(),
|
||||
attributes: Default::default(),
|
||||
slots: PyType::make_slots(),
|
||||
heaptype_ext: None,
|
||||
tp_version_tag: core::sync::atomic::AtomicU32::new(0),
|
||||
abc_tpflags: core::sync::atomic::AtomicU64::new(0),
|
||||
};
|
||||
let object_payload = PyType {
|
||||
base: None.into(),
|
||||
bases: PyRwLock::default(),
|
||||
mro: PyRwLock::default(),
|
||||
subclasses: PyRwLock::default(),
|
||||
attributes: Default::default(),
|
||||
slots: object::PyBaseObject::make_slots(),
|
||||
heaptype_ext: None,
|
||||
tp_version_tag: core::sync::atomic::AtomicU32::new(0),
|
||||
abc_tpflags: core::sync::atomic::AtomicU64::new(0),
|
||||
};
|
||||
// Both type_type and object_type are instances of `type`, which has
|
||||
// HAS_DICT and HAS_WEAKREF, so they need both ObjExt and WeakRefList prefixes.
|
||||
// Layout: [ObjExt][WeakRefList][PyInner<PyType>]
|
||||
let alloc_type_with_prefixes = || -> *mut MaybeUninit<PyInner<PyType>> {
|
||||
let inner_layout = core::alloc::Layout::new::<MaybeUninit<PyInner<PyType>>>();
|
||||
let ext_layout = core::alloc::Layout::new::<ObjExt>();
|
||||
let weakref_layout = core::alloc::Layout::new::<WeakRefList>();
|
||||
// All three core type objects are instances of `type`, which has HAS_DICT
|
||||
// and HAS_WEAKREF. Their allocations therefore need both prefixes.
|
||||
let alloc_type_with_prefixes = || -> *mut PyInner<PyType> {
|
||||
let inner_layout = core::alloc::Layout::new::<MaybeUninit<PyInner<PyType>>>();
|
||||
let ext_layout = core::alloc::Layout::new::<ObjExt>();
|
||||
let weakref_layout = core::alloc::Layout::new::<WeakRefList>();
|
||||
|
||||
let (layout, weakref_offset) = ext_layout.extend(weakref_layout).unwrap();
|
||||
let (combined, inner_offset) = layout.extend(inner_layout).unwrap();
|
||||
let combined = combined.pad_to_align();
|
||||
let (layout, weakref_offset) = ext_layout.extend(weakref_layout).unwrap();
|
||||
let (combined, inner_offset) = layout.extend(inner_layout).unwrap();
|
||||
let combined = combined.pad_to_align();
|
||||
|
||||
let alloc_ptr = unsafe { alloc::alloc::alloc(combined) };
|
||||
if alloc_ptr.is_null() {
|
||||
alloc::alloc::handle_alloc_error(combined);
|
||||
}
|
||||
alloc_ptr.expose_provenance();
|
||||
|
||||
unsafe {
|
||||
let ext_ptr = alloc_ptr as *mut ObjExt;
|
||||
ext_ptr.write(ObjExt::new(None, 0, true));
|
||||
|
||||
let weakref_ptr = alloc_ptr.add(weakref_offset) as *mut WeakRefList;
|
||||
weakref_ptr.write(WeakRefList::new());
|
||||
|
||||
alloc_ptr.add(inner_offset) as *mut MaybeUninit<PyInner<PyType>>
|
||||
}
|
||||
};
|
||||
|
||||
let type_type_ptr = alloc_type_with_prefixes();
|
||||
unsafe {
|
||||
type_type_ptr.write(partially_init!(
|
||||
PyInner::<PyType> {
|
||||
ref_count: RefCount::new(),
|
||||
vtable: PyObjVTable::of::<PyType>(),
|
||||
gc_bits: Radium::new(0),
|
||||
gc_generation: Radium::new(GC_UNTRACKED),
|
||||
gc_owner: Radium::new(GC_NO_OWNER),
|
||||
gc_refs: Radium::new(0),
|
||||
gc_pointers: Pointers::new(),
|
||||
payload: type_payload,
|
||||
},
|
||||
Uninit { typ }
|
||||
));
|
||||
let alloc_ptr = unsafe { alloc::alloc::alloc(combined) };
|
||||
if alloc_ptr.is_null() {
|
||||
alloc::alloc::handle_alloc_error(combined);
|
||||
}
|
||||
|
||||
let object_type_ptr = alloc_type_with_prefixes();
|
||||
unsafe {
|
||||
object_type_ptr.write(partially_init!(
|
||||
PyInner::<PyType> {
|
||||
ref_count: RefCount::new(),
|
||||
vtable: PyObjVTable::of::<PyType>(),
|
||||
gc_bits: Radium::new(0),
|
||||
gc_generation: Radium::new(GC_UNTRACKED),
|
||||
gc_owner: Radium::new(GC_NO_OWNER),
|
||||
gc_refs: Radium::new(0),
|
||||
gc_pointers: Pointers::new(),
|
||||
payload: object_payload,
|
||||
},
|
||||
Uninit { typ },
|
||||
));
|
||||
}
|
||||
|
||||
let object_type_ptr = object_type_ptr as *mut PyInner<PyType>;
|
||||
let type_type_ptr = type_type_ptr as *mut PyInner<PyType>;
|
||||
alloc_ptr.expose_provenance();
|
||||
|
||||
unsafe {
|
||||
(*type_type_ptr).ref_count.inc();
|
||||
let type_type = PyTypeRef::from_raw(type_type_ptr.cast());
|
||||
ptr::write(&mut (*object_type_ptr).typ, PyAtomicRef::from(type_type));
|
||||
(*type_type_ptr).ref_count.inc();
|
||||
let type_type = PyTypeRef::from_raw(type_type_ptr.cast());
|
||||
ptr::write(&mut (*type_type_ptr).typ, PyAtomicRef::from(type_type));
|
||||
|
||||
let object_type = PyTypeRef::from_raw(object_type_ptr.cast());
|
||||
// object's mro is [object]
|
||||
(*object_type_ptr).payload.mro = PyRwLock::new(vec![object_type.clone()]);
|
||||
|
||||
(*type_type_ptr).payload.bases = PyRwLock::new(vec![object_type.clone()]);
|
||||
(*type_type_ptr).payload.base = Some(object_type.clone()).into();
|
||||
|
||||
let type_type = PyTypeRef::from_raw(type_type_ptr.cast());
|
||||
// type's mro is [type, object]
|
||||
(*type_type_ptr).payload.mro =
|
||||
PyRwLock::new(vec![type_type.clone(), object_type.clone()]);
|
||||
|
||||
(type_type, object_type)
|
||||
(alloc_ptr as *mut ObjExt).write(ObjExt::new(None, 0, true));
|
||||
(alloc_ptr.add(weakref_offset) as *mut WeakRefList).write(WeakRefList::new());
|
||||
alloc_ptr.add(inner_offset).cast()
|
||||
}
|
||||
};
|
||||
|
||||
let weakref_type = PyType {
|
||||
let alloc_tuple = || {
|
||||
Box::into_raw(Box::new(MaybeUninit::<PyInner<PyTuple>>::uninit()))
|
||||
.cast::<PyInner<PyTuple>>()
|
||||
};
|
||||
|
||||
unsafe fn init_ref_count<T>(ptr: *mut PyInner<T>) {
|
||||
unsafe { ptr::addr_of_mut!((*ptr).ref_count).write(RefCount::new()) };
|
||||
}
|
||||
|
||||
unsafe fn initial_ref<T: PyPayload>(ptr: *mut PyInner<T>) -> PyRef<T> {
|
||||
unsafe { PyRef::from_raw(ptr.cast()) }
|
||||
}
|
||||
|
||||
unsafe fn clone_raw_ref<T: PyPayload>(ptr: *mut PyInner<T>) -> PyRef<T> {
|
||||
unsafe { &*ptr::addr_of!((*ptr).ref_count) }.inc();
|
||||
unsafe { PyRef::from_raw(ptr.cast()) }
|
||||
}
|
||||
|
||||
unsafe fn into_type_tuple(tuple: PyTupleRef) -> PyTypeTupleRef {
|
||||
// SAFETY: PyTypeRef and PyObjectRef have the same layout, and the
|
||||
// bootstrap tuples contain only PyType objects.
|
||||
unsafe { core::mem::transmute::<PyTupleRef, PyTypeTupleRef>(tuple) }
|
||||
}
|
||||
|
||||
unsafe fn init_inner<T>(ptr: *mut PyInner<T>, typ: PyTypeRef, payload: T)
|
||||
where
|
||||
T: PyPayload + MaybeTraverse + fmt::Debug,
|
||||
{
|
||||
unsafe {
|
||||
ptr::addr_of_mut!((*ptr).vtable).write(PyObjVTable::of::<T>());
|
||||
ptr::addr_of_mut!((*ptr).gc_bits).write(Radium::new(0));
|
||||
ptr::addr_of_mut!((*ptr).gc_generation).write(Radium::new(GC_UNTRACKED));
|
||||
ptr::addr_of_mut!((*ptr).gc_owner).write(Radium::new(GC_NO_OWNER));
|
||||
ptr::addr_of_mut!((*ptr).gc_refs).write(Radium::new(0));
|
||||
ptr::addr_of_mut!((*ptr).gc_pointers).write(Pointers::new());
|
||||
ptr::addr_of_mut!((*ptr).typ).write(PyAtomicRef::from_ref_without_retag(typ));
|
||||
ptr::addr_of_mut!((*ptr).payload).write(payload);
|
||||
}
|
||||
}
|
||||
|
||||
let type_type_ptr = alloc_type_with_prefixes();
|
||||
let object_type_ptr = alloc_type_with_prefixes();
|
||||
let tuple_type_ptr = alloc_type_with_prefixes();
|
||||
let empty_tuple_ptr = alloc_tuple();
|
||||
let type_bases_ptr = alloc_tuple();
|
||||
let tuple_bases_ptr = alloc_tuple();
|
||||
|
||||
unsafe {
|
||||
init_ref_count(type_type_ptr);
|
||||
init_ref_count(object_type_ptr);
|
||||
init_ref_count(tuple_type_ptr);
|
||||
init_ref_count(empty_tuple_ptr);
|
||||
init_ref_count(type_bases_ptr);
|
||||
init_ref_count(tuple_bases_ptr);
|
||||
}
|
||||
|
||||
// Each initial reference consumes the allocation's initial strong count.
|
||||
// Further references are created through clone_raw_ref while the graph is
|
||||
// still being assembled and cannot yet be safely dereferenced.
|
||||
let type_type = unsafe { initial_ref(type_type_ptr) };
|
||||
let object_type = unsafe { initial_ref(object_type_ptr) };
|
||||
let tuple_type = unsafe { initial_ref(tuple_type_ptr) };
|
||||
let empty_tuple = unsafe { initial_ref(empty_tuple_ptr) };
|
||||
let type_bases = unsafe { into_type_tuple(initial_ref(type_bases_ptr)) };
|
||||
let tuple_bases = unsafe { into_type_tuple(initial_ref(tuple_bases_ptr)) };
|
||||
|
||||
let type_payload = PyType {
|
||||
base: unsafe {
|
||||
PyAtomicRef::from_optional_ref_without_retag(Some(clone_raw_ref(object_type_ptr)))
|
||||
},
|
||||
bases: PyRwLock::new(type_bases),
|
||||
mro: PyRwLock::new(vec![unsafe { clone_raw_ref(type_type_ptr) }, unsafe {
|
||||
clone_raw_ref(object_type_ptr)
|
||||
}]),
|
||||
subclasses: PyRwLock::default(),
|
||||
attributes: Default::default(),
|
||||
slots: PyType::make_slots(),
|
||||
heaptype_ext: None,
|
||||
tp_version_tag: core::sync::atomic::AtomicU32::new(0),
|
||||
abc_tpflags: core::sync::atomic::AtomicU64::new(0),
|
||||
};
|
||||
let object_payload = PyType {
|
||||
base: unsafe { PyAtomicRef::from_optional_ref_without_retag(None) },
|
||||
bases: PyRwLock::new(unsafe { into_type_tuple(clone_raw_ref(empty_tuple_ptr)) }),
|
||||
mro: PyRwLock::new(vec![unsafe { clone_raw_ref(object_type_ptr) }]),
|
||||
subclasses: PyRwLock::default(),
|
||||
attributes: Default::default(),
|
||||
slots: object::PyBaseObject::make_slots(),
|
||||
heaptype_ext: None,
|
||||
tp_version_tag: core::sync::atomic::AtomicU32::new(0),
|
||||
abc_tpflags: core::sync::atomic::AtomicU64::new(0),
|
||||
};
|
||||
let tuple_payload = PyType {
|
||||
base: unsafe {
|
||||
PyAtomicRef::from_optional_ref_without_retag(Some(clone_raw_ref(object_type_ptr)))
|
||||
},
|
||||
bases: PyRwLock::new(tuple_bases),
|
||||
mro: PyRwLock::new(vec![unsafe { clone_raw_ref(tuple_type_ptr) }, unsafe {
|
||||
clone_raw_ref(object_type_ptr)
|
||||
}]),
|
||||
subclasses: PyRwLock::default(),
|
||||
attributes: Default::default(),
|
||||
slots: tuple::PyTuple::make_slots(),
|
||||
heaptype_ext: None,
|
||||
tp_version_tag: core::sync::atomic::AtomicU32::new(0),
|
||||
abc_tpflags: core::sync::atomic::AtomicU64::new(0),
|
||||
};
|
||||
|
||||
let object_element =
|
||||
|| -> PyObjectRef { unsafe { clone_raw_ref::<PyType>(object_type_ptr) }.into() };
|
||||
unsafe {
|
||||
init_inner(type_type_ptr, clone_raw_ref(type_type_ptr), type_payload);
|
||||
init_inner(
|
||||
object_type_ptr,
|
||||
clone_raw_ref(type_type_ptr),
|
||||
object_payload,
|
||||
);
|
||||
init_inner(tuple_type_ptr, clone_raw_ref(type_type_ptr), tuple_payload);
|
||||
init_inner(
|
||||
empty_tuple_ptr,
|
||||
clone_raw_ref(tuple_type_ptr),
|
||||
PyTuple::new_unchecked(Vec::new().into_boxed_slice()),
|
||||
);
|
||||
init_inner(
|
||||
type_bases_ptr,
|
||||
clone_raw_ref(tuple_type_ptr),
|
||||
PyTuple::new_unchecked(vec![object_element()].into_boxed_slice()),
|
||||
);
|
||||
init_inner(
|
||||
tuple_bases_ptr,
|
||||
clone_raw_ref(tuple_type_ptr),
|
||||
PyTuple::new_unchecked(vec![object_element()].into_boxed_slice()),
|
||||
);
|
||||
}
|
||||
|
||||
PyType::finalize_bootstrap_static(&tuple_type);
|
||||
|
||||
let weakref_bases =
|
||||
PyTuple::new_ref_typed_with_type(vec![object_type.clone()], tuple_type.clone());
|
||||
unsafe {
|
||||
crate::gc_state::gc_state()
|
||||
.untrack_object(NonNull::from(weakref_bases.as_untyped().as_object()));
|
||||
}
|
||||
weakref_bases.as_untyped().as_object().clear_gc_tracked();
|
||||
let weakref_payload = PyType {
|
||||
base: Some(object_type.clone()).into(),
|
||||
bases: PyRwLock::new(vec![object_type.clone()]),
|
||||
bases: PyRwLock::new(weakref_bases),
|
||||
mro: PyRwLock::new(vec![object_type.clone()]),
|
||||
subclasses: PyRwLock::default(),
|
||||
attributes: Default::default(),
|
||||
@@ -2894,7 +2932,7 @@ pub(crate) fn init_type_hierarchy() -> (PyTypeRef, PyTypeRef, PyTypeRef) {
|
||||
tp_version_tag: core::sync::atomic::AtomicU32::new(0),
|
||||
abc_tpflags: core::sync::atomic::AtomicU64::new(0),
|
||||
};
|
||||
let weakref_type = PyRef::new_ref(weakref_type, type_type.clone(), None);
|
||||
let weakref_type = PyRef::new_ref(weakref_payload, type_type.clone(), None);
|
||||
// Static type: untrack from GC (was tracked by new_ref because PyType has HAS_TRAVERSE)
|
||||
unsafe {
|
||||
crate::gc_state::gc_state()
|
||||
@@ -2911,6 +2949,13 @@ pub(crate) fn init_type_hierarchy() -> (PyTypeRef, PyTypeRef, PyTypeRef) {
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
object_type.subclasses.write().push(
|
||||
tuple_type
|
||||
.as_object()
|
||||
.downgrade_with_weakref_typ_opt(None, weakref_type.clone())
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
object_type.subclasses.write().push(
|
||||
weakref_type
|
||||
.as_object()
|
||||
@@ -2918,7 +2963,13 @@ pub(crate) fn init_type_hierarchy() -> (PyTypeRef, PyTypeRef, PyTypeRef) {
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
(type_type, object_type, weakref_type)
|
||||
BootstrapTypeHierarchy {
|
||||
type_type,
|
||||
object_type,
|
||||
tuple_type,
|
||||
weakref_type,
|
||||
empty_tuple,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -2927,7 +2978,29 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn miri_test_type_initialization() {
|
||||
let _ = init_type_hierarchy();
|
||||
let hierarchy = init_type_hierarchy();
|
||||
|
||||
assert!(hierarchy.type_type.class().is(&hierarchy.type_type));
|
||||
assert!(hierarchy.object_type.class().is(&hierarchy.type_type));
|
||||
assert!(hierarchy.tuple_type.class().is(&hierarchy.type_type));
|
||||
assert!(hierarchy.weakref_type.class().is(&hierarchy.type_type));
|
||||
|
||||
let object_bases = hierarchy.object_type.bases.read();
|
||||
assert!(object_bases.is_empty());
|
||||
assert!(object_bases.as_untyped().is(&hierarchy.empty_tuple));
|
||||
assert!(object_bases.as_untyped().class().is(&hierarchy.tuple_type));
|
||||
drop(object_bases);
|
||||
|
||||
for typ in [
|
||||
&hierarchy.type_type,
|
||||
&hierarchy.tuple_type,
|
||||
&hierarchy.weakref_type,
|
||||
] {
|
||||
let bases = typ.bases.read();
|
||||
assert_eq!(bases.len(), 1);
|
||||
assert!(bases[0].is(&hierarchy.object_type));
|
||||
assert!(bases.as_untyped().class().is(&hierarchy.tuple_type));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -315,6 +315,22 @@ impl<T: PyPayload> Deref for PyAtomicRef<T> {
|
||||
}
|
||||
|
||||
impl<T: PyPayload> PyAtomicRef<T> {
|
||||
/// Move a reference into an atomic pointer without creating a Rust
|
||||
/// reference to the pointee. This is only for bootstrap objects whose
|
||||
/// allocation is valid but whose payload is still being initialized.
|
||||
///
|
||||
/// # Safety
|
||||
/// The pointee must remain allocated, and this atomic reference must not
|
||||
/// be dereferenced until the pointee has been fully initialized.
|
||||
pub(super) unsafe fn from_ref_without_retag(pyref: PyRef<T>) -> Self {
|
||||
let ptr = pyref.into_non_null().as_ptr().cast::<u8>();
|
||||
ptr.expose_provenance();
|
||||
Self {
|
||||
inner: Radium::new(ptr),
|
||||
_phantom: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Load the raw pointer without creating a reference.
|
||||
/// Avoids Stacked Borrows retag, safe for use during bootstrap
|
||||
/// when type objects have self-referential pointers being mutated.
|
||||
@@ -352,6 +368,22 @@ impl<T: PyPayload> From<Option<PyRef<T>>> for PyAtomicRef<Option<T>> {
|
||||
}
|
||||
|
||||
impl<T: PyPayload> PyAtomicRef<Option<T>> {
|
||||
/// Optional form of PyAtomicRef::from_ref_without_retag.
|
||||
///
|
||||
/// # Safety
|
||||
/// A non-None pointee must remain allocated, and this atomic reference
|
||||
/// must not be dereferenced until the pointee has been fully initialized.
|
||||
pub(super) unsafe fn from_optional_ref_without_retag(opt_ref: Option<PyRef<T>>) -> Self {
|
||||
let ptr = opt_ref.map_or(null_mut(), |pyref| {
|
||||
pyref.into_non_null().as_ptr().cast::<u8>()
|
||||
});
|
||||
ptr.expose_provenance();
|
||||
Self {
|
||||
inner: Radium::new(ptr),
|
||||
_phantom: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deref(&self) -> Option<&Py<T>> {
|
||||
self.deref_ordering(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
@@ -110,18 +110,19 @@ pub struct TypeZoo {
|
||||
|
||||
impl TypeZoo {
|
||||
#[cold]
|
||||
pub(crate) fn init() -> Self {
|
||||
let (type_type, object_type, weakref_type) = crate::object::init_type_hierarchy();
|
||||
// the order matters for type, object, weakref, and int - must be initialized first
|
||||
let type_type = type_::PyType::init_manually(type_type);
|
||||
let object_type = object::PyBaseObject::init_manually(object_type);
|
||||
let weakref_type = weakref::PyWeak::init_manually(weakref_type);
|
||||
pub(crate) fn init() -> (Self, crate::builtins::PyTupleRef) {
|
||||
let hierarchy = crate::object::init_type_hierarchy();
|
||||
// These core types must be published before any other static type is created.
|
||||
let type_type = type_::PyType::init_manually(hierarchy.type_type);
|
||||
let object_type = object::PyBaseObject::init_manually(hierarchy.object_type);
|
||||
let tuple_type = tuple::PyTuple::init_manually(hierarchy.tuple_type);
|
||||
let weakref_type = weakref::PyWeak::init_manually(hierarchy.weakref_type);
|
||||
let int_type = int::PyInt::init_builtin_type();
|
||||
|
||||
// builtin_function_or_method and builtin_method share the same type (CPython behavior)
|
||||
let builtin_function_or_method_type = builtin_func::PyNativeFunction::init_builtin_type();
|
||||
|
||||
Self {
|
||||
let types = Self {
|
||||
type_type,
|
||||
object_type,
|
||||
weakref_type,
|
||||
@@ -148,7 +149,7 @@ impl TypeZoo {
|
||||
staticmethod_type: staticmethod::PyStaticMethod::init_builtin_type(),
|
||||
str_type: pystr::PyStr::init_builtin_type(),
|
||||
super_type: super_::PySuper::init_builtin_type(),
|
||||
tuple_type: tuple::PyTuple::init_builtin_type(),
|
||||
tuple_type,
|
||||
zip_type: zip::PyZip::init_builtin_type(),
|
||||
|
||||
// hidden internal types. is this really need to be cached here?
|
||||
@@ -215,7 +216,8 @@ impl TypeZoo {
|
||||
method_wrapper_type: descriptor::PyMethodWrapper::init_builtin_type(),
|
||||
|
||||
method_def: crate::function::HeapMethodDef::init_builtin_type(),
|
||||
}
|
||||
};
|
||||
(types, hierarchy.empty_tuple)
|
||||
}
|
||||
|
||||
/// Fill attributes of builtin types.
|
||||
|
||||
@@ -303,7 +303,7 @@ impl Context {
|
||||
|
||||
fn init_genesis() -> Self {
|
||||
flame_guard!("init Context");
|
||||
let types = TypeZoo::init();
|
||||
let (types, empty_tuple) = TypeZoo::init();
|
||||
let exceptions = exceptions::ExceptionZoo::init();
|
||||
|
||||
#[inline]
|
||||
@@ -348,10 +348,6 @@ impl Context {
|
||||
let true_value = create_object(PyBool(PyInt::from(1)), types.bool_type);
|
||||
let false_value = create_object(PyBool(PyInt::from(0)), types.bool_type);
|
||||
|
||||
let empty_tuple = create_object(
|
||||
PyTuple::new_unchecked(Vec::new().into_boxed_slice()),
|
||||
types.tuple_type,
|
||||
);
|
||||
let empty_frozenset = PyRef::new_ref(
|
||||
PyFrozenSet::default(),
|
||||
types.frozenset_type.to_owned(),
|
||||
|
||||
Reference in new issue
Block a user