Flatten Py_slot_subslots and Py_tp_slots in PySlot iterator (#9246)

* Flatten `Py_slot_subslots` and `Py_tp_slots` in `PySlot` iterator

* Review

* Add support for metaclass slots
This commit is contained in:
Bas Schoenmaeckers authored and GitHub committed 2026-10-21 00:23:05 +09:00
1 parent ce2ef3317f
commit fb9c099511
2 files changed
+120 -110

No files matched your search

+43 -88
View File
@@ -9,7 +9,10 @@ use core::ffi::{c_char, c_int, c_uint, c_ulong, c_void};
use core::ptr;
use rustpython_vm::builtins::{PyStr, PyType};
use rustpython_vm::function::{ItemDoc, PyMethodFlags};
use rustpython_vm::types::{AtomicPyTypeFlags, PyTypeFlags, PyTypeSlots, SlotAccessor};
use rustpython_vm::types::{
AtomicPyTypeFlags, PyTypeFlags, PyTypeSlots,
SlotAccessor::{TpBase, TpDealloc, TpDoc, TpGetset, TpMembers, TpMethods, TpNew, TpTraverse},
};
use rustpython_vm::{AsObject, Py, PyObject};
pub type PyTypeObject = Py<PyType>;
@@ -32,25 +35,6 @@ pub struct PyType_Spec {
pub slots: *mut PyType_Slot,
}
impl PyType_Slot {
pub(crate) fn iter<'a>(mut slots: *const Self) -> impl Iterator<Item = &'a Self> {
core::iter::from_fn(move || {
let slot = unsafe { &*slots };
if slot.slot == 0 {
None
} else {
slots = unsafe { slots.add(1) };
Some(slot)
}
})
}
fn as_slot_accessor(&self) -> Option<SlotAccessor> {
let slot_id: u8 = self.slot.try_into().ok()?;
slot_id.try_into().ok()
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn Py_TYPE(op: *mut PyObject) -> *const PyTypeObject {
unsafe { op.assume_borrowed() }.class()
@@ -127,12 +111,15 @@ pub unsafe extern "C" fn PyType_FromSlots(slots: *const PySlot) -> *mut PyObject
let attrs = Default::default();
let mut getsets = Vec::new();
let mut members = Vec::new();
let mut metaclass = vm.ctx.types.type_type.to_owned();
for slot in PySlot::iter(slots) {
match slot.as_kind(vm)? {
kind @ PySlotKind::Type(type_slot) => {
PySlotKind::Type(type_slot) => {
match type_slot {
PySlotType::Name(value) => name = Some(value),
PySlotType::Metaclass(meta) => metaclass = meta.to_owned(),
PySlotType::Module(_) => {}
PySlotType::Flags(value) => {
let flags = PyTypeFlags::from_bits(value).ok_or_else(|| {
vm.new_value_error(format!(
@@ -148,68 +135,37 @@ pub unsafe extern "C" fn PyType_FromSlots(slots: *const PySlot) -> *mut PyObject
));
}
}
PySlotType::Slots { value, .. } => {
for slot in PyType_Slot::iter(value) {
let accessor = slot.as_slot_accessor().ok_or_else(|| {
vm.new_value_error(format!(
"Invalid slot id: {} for PyType_FromSlots",
slot.slot
))
})?;
match accessor {
SlotAccessor::TpDoc => {
let doc = unsafe {
slot.pfunc.cast::<c_char>().try_as_str_opt(vm)?
}
.map_or(
ItemDoc::NONE,
|doc| {
let text: &'static str =
Box::leak(doc.to_owned().into_boxed_str());
ItemDoc::static_text(text)
},
);
type_slots.doc = doc;
}
SlotAccessor::TpNew => {
type_slots.new.store(Some(|ty, _args, vm| {
Err(vm.new_not_implemented_error(format!("tp_new is not yet implemented in PyType_FromSlots for {ty:?}")))
}));
}
SlotAccessor::TpBase => {
base = unsafe { Some(&*slot.pfunc.cast::<PyTypeObject>()) }
}
SlotAccessor::TpDealloc => {
//TODO
}
SlotAccessor::TpMethods => {
for def in PyMethodDef::iter(slot.pfunc.cast()) {
let name = unsafe { def.ml_name.try_as_str(vm)? };
let is_static =
PyMethodFlags::from_bits_retain(def.ml_flags as _)
.contains(PyMethodFlags::STATIC);
let method = build_method_def(vm, def, !is_static)?;
methods.push((name, method));
}
}
SlotAccessor::TpGetset => {
getsets.extend(PyGetSetDef::iter(slot.pfunc.cast()));
}
SlotAccessor::TpMembers => {
members.extend(PyMemberDef::iter(slot.pfunc.cast()));
}
slot => {
return Err(vm.new_not_implemented_error(format!(
"PyType_FromSlots with PyType_Slot {slot:?} not implemented yet"
)));
}
}
PySlotType::TypeSlot(TpDoc, pfunc) => {
let doc = unsafe { pfunc.cast::<c_char>().try_as_str_opt(vm)? }.map_or(
ItemDoc::NONE,
|doc| {
let text: &'static str =
Box::leak(doc.to_owned().into_boxed_str());
ItemDoc::static_text(text)
},
);
type_slots.doc = doc;
}
PySlotType::TypeSlot(TpBase, pfunc) => {
base = unsafe { Some(&*pfunc.cast::<PyTypeObject>()) }
}
PySlotType::TypeSlot(TpMembers, pfunc) => {
members.extend(PyMemberDef::iter(pfunc.cast()));
}
PySlotType::TypeSlot(TpGetset, pfunc) => {
getsets.extend(PyGetSetDef::iter(pfunc.cast()));
}
PySlotType::TypeSlot(TpMethods, pfunc) => {
for def in PyMethodDef::iter(pfunc.cast()) {
let name = unsafe { def.ml_name.try_as_str(vm)? };
let is_static = PyMethodFlags::from_bits_retain(def.ml_flags as _)
.contains(PyMethodFlags::STATIC);
let method = build_method_def(vm, def, !is_static)?;
methods.push((name, method));
}
}
_ => {
return Err(vm.new_not_implemented_error(format!(
"PyType_FromSlots with slot {kind:?} not implemented yet"
)));
PySlotType::TypeSlot(TpNew | TpDealloc | TpTraverse | _, _) => {
// TODO
}
}
}
@@ -232,7 +188,6 @@ pub unsafe extern "C" fn PyType_FromSlots(slots: *const PySlot) -> *mut PyObject
return Err(vm.new_system_error("PyType_FromSlots requires a name slot"));
};
let metaclass = vm.ctx.types.type_type.to_owned();
let class = PyType::new_heap(name, bases, attrs, type_slots, metaclass, &vm.ctx).map_err(
|msg| vm.new_system_error(format!("Failed to create type from slots: {msg}")),
)?;
@@ -293,6 +248,12 @@ pub unsafe extern "C" fn PyType_FromMetaclass(
}
};
write_slot(PySlot::new(95, name.cast_mut()));
write_slot(PySlot::new(96, basicsize as isize));
write_slot(PySlot::new(98, itemsize as isize));
write_slot(PySlot::new(99, flags as u64));
write_slot(PySlot::new(93, type_slots));
if !bases.is_null() {
write_slot(PySlot::new(49, bases));
}
@@ -305,12 +266,6 @@ pub unsafe extern "C" fn PyType_FromMetaclass(
write_slot(PySlot::new(108, module));
}
write_slot(PySlot::new(95, name.cast_mut()));
write_slot(PySlot::new(96, basicsize as isize));
write_slot(PySlot::new(98, itemsize as isize));
write_slot(PySlot::new(99, flags as u64));
write_slot(PySlot::new(93, type_slots));
unsafe { PyType_FromSlots(slots.as_ptr()) }
}
+77 -22
View File
@@ -3,12 +3,16 @@ use crate::methodobject::PyMethodDef;
use crate::modsupport::PyABIInfo;
use crate::object::PyType_Slot;
use crate::util::CStrExt;
use alloc::borrow::Cow;
use core::ffi::{c_char, c_int, c_void};
use core::slice::from_ref;
use core::num::TryFromIntError;
use itertools::Either;
use rustpython_vm::builtins::{PyModule, PyType};
use rustpython_vm::types::SlotAccessor;
use rustpython_vm::{Py, PyResult, VirtualMachine};
#[repr(C)]
#[derive(Clone)]
pub struct PySlot {
pub sl_id: u16,
pub sl_flags: u16,
@@ -16,6 +20,19 @@ pub struct PySlot {
pub value: PySlotValue,
}
impl TryFrom<&PyType_Slot> for PySlot {
type Error = TryFromIntError;
fn try_from(slot: &PyType_Slot) -> Result<Self, Self::Error> {
Ok(Self {
sl_id: slot.slot.try_into()?,
sl_flags: 0,
_reserved: 0,
value: slot.pfunc.into(),
})
}
}
impl PySlot {
const SLOT_OPTIONAL: u16 = 0x0001;
const SLOT_STATIC: u16 = 0x0002;
@@ -31,14 +48,55 @@ impl PySlot {
}
}
pub(crate) fn iter<'a>(mut slots: *const Self) -> impl Iterator<Item = &'a Self> {
pub(crate) fn iter<'a>(slots: *const Self) -> impl Iterator<Item = Cow<'a, Self>> {
let mut stack: Vec<Either<*const Self, *const PyType_Slot>> = vec![Either::Left(slots)];
core::iter::from_fn(move || {
let slot = unsafe { &*slots };
if slot.sl_id == 0 {
None
} else {
slots = unsafe { slots.add(1) };
Some(slot)
loop {
assert!(
stack.len() <= 10,
"Too many nested slots, possible infinite loop in slot iteration"
);
return match stack.last_mut()? {
Either::Left(slot_ptr) => {
let slot = unsafe { &**slot_ptr };
if slot.sl_id == 0 {
stack.pop();
continue;
}
*slot_ptr = unsafe { slot_ptr.add(1) };
if slot.sl_id == 92 {
let subslots = unsafe { slot.value.sl_ptr }.cast_const().cast::<Self>();
if !subslots.is_null() {
stack.push(Either::Left(subslots));
}
continue;
}
if slot.sl_id == 93 {
let type_slots = unsafe { slot.value.sl_ptr }
.cast_const()
.cast::<PyType_Slot>();
if !type_slots.is_null() {
stack.push(Either::Right(type_slots));
}
continue;
}
Some(Cow::Borrowed(slot))
}
Either::Right(slot_ptr) => {
let type_slot = unsafe { &**slot_ptr };
if type_slot.slot == 0 {
stack.pop();
continue;
}
*slot_ptr = unsafe { slot_ptr.add(1) };
Some(Cow::Owned(type_slot.try_into().expect("invalid slot")))
}
};
}
})
}
@@ -58,12 +116,15 @@ impl PySlot {
self.sl_flags & Self::SLOT_INTPTR != 0
}
pub(crate) fn as_kind(&self, vm: &VirtualMachine) -> PyResult<PySlotKind<'_>> {
pub(crate) fn as_kind<'a>(&self, vm: &VirtualMachine) -> PyResult<PySlotKind<'a>> {
let value_ptr = unsafe { self.value.sl_ptr };
let is_static = self.is_static();
let kind = match self.sl_id {
48 => PySlotKind::Type(PySlotType::Base(unsafe { &*value_ptr.cast() })),
49 => PySlotKind::Type(PySlotType::Bases(unsafe { from_ref(&*value_ptr.cast()) })),
1..82 => {
let slot_id: u8 = self.sl_id.try_into().unwrap();
let accessor = slot_id.try_into().unwrap();
PySlotKind::Type(PySlotType::TypeSlot(accessor, value_ptr))
}
84 => {
let create = unsafe {
core::mem::transmute::<
@@ -89,11 +150,9 @@ impl PySlot {
87 => PySlotKind::Module(PySlotModule::Gil {
gil_used: !value_ptr.is_null(),
}),
// 92 => Py_slot_subslots
93 => PySlotKind::Type(PySlotType::Slots {
value: value_ptr.cast(),
is_static,
}),
92 | 93 => {
unreachable!("Py_slot_subslots and Py_tp_slots should be handled in the iterator")
}
95 => PySlotKind::Type(PySlotType::Name(unsafe {
value_ptr.cast::<c_char>().try_as_str(vm)?
})),
@@ -132,6 +191,7 @@ impl PySlot {
}
#[repr(C)]
#[derive(Copy, Clone)]
pub union PySlotValue {
pub sl_ptr: *mut c_void,
pub sl_func: unsafe extern "C" fn(),
@@ -174,12 +234,7 @@ pub(crate) enum PySlotModule<'a> {
#[allow(dead_code)]
#[derive(Debug, Copy, Clone)]
pub(crate) enum PySlotType<'a> {
Base(&'a Py<PyType>),
Bases(&'a [Py<PyType>]),
Slots {
value: *mut PyType_Slot,
is_static: bool,
},
TypeSlot(SlotAccessor, *mut c_void),
Name(&'a str),
Flags(u64),
BasicSize(isize),