diff --git a/crates/capi/src/object/pytype.rs b/crates/capi/src/object/pytype.rs index 77d8627d0..44d3ff2f2 100644 --- a/crates/capi/src/object/pytype.rs +++ b/crates/capi/src/object/pytype.rs @@ -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; @@ -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 { - 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 { - 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::().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::()) } - } - 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::().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::()) } + } + 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()) } } diff --git a/crates/capi/src/slots.rs b/crates/capi/src/slots.rs index 96a4df5a8..7e6ed3899 100644 --- a/crates/capi/src/slots.rs +++ b/crates/capi/src/slots.rs @@ -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 { + 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 { + pub(crate) fn iter<'a>(slots: *const Self) -> impl Iterator> { + let mut stack: Vec> = 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::(); + 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::(); + 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> { + pub(crate) fn as_kind<'a>(&self, vm: &VirtualMachine) -> PyResult> { 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::().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), - Bases(&'a [Py]), - Slots { - value: *mut PyType_Slot, - is_static: bool, - }, + TypeSlot(SlotAccessor, *mut c_void), Name(&'a str), Flags(u64), BasicSize(isize),