From 8f82c4940efd66bd71085c2f2b029b33822983dc Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Mon, 31 Aug 2020 20:36:26 -0400 Subject: [PATCH] Proof of concept of using PEP384s PyType_Spec --- src/class/basic.rs | 42 +++++++-- src/pyclass.rs | 218 ++++++++++++++++++++++++--------------------- src/type_object.rs | 8 +- 3 files changed, 153 insertions(+), 115 deletions(-) diff --git a/src/class/basic.rs b/src/class/basic.rs index d95053bf828..cd7bc713b8b 100644 --- a/src/class/basic.rs +++ b/src/class/basic.rs @@ -9,8 +9,9 @@ //! [typeobj docs](https://docs.python.org/3/c-api/typeobj.html) use crate::callback::{HashCallbackOutput, IntoPyCallbackOutput}; +use crate::pyclass::maybe_push_slot; use crate::{exceptions, ffi, FromPyObject, PyAny, PyCell, PyClass, PyErr, PyObject, PyResult}; -use std::os::raw::c_int; +use std::os::raw::{c_int, c_void}; /// Operators for the __richcmp__ method #[derive(Debug)] @@ -147,13 +148,38 @@ pub struct PyObjectMethods { #[doc(hidden)] impl PyObjectMethods { - pub(crate) fn update_typeobj(&self, type_object: &mut ffi::PyTypeObject) { - type_object.tp_str = self.tp_str; - type_object.tp_repr = self.tp_repr; - type_object.tp_hash = self.tp_hash; - type_object.tp_getattro = self.tp_getattro; - type_object.tp_richcompare = self.tp_richcompare; - type_object.tp_setattro = self.tp_setattro; + pub(crate) fn update_slots(&self, slots: &mut Vec) { + maybe_push_slot(slots, ffi::Py_tp_str, self.tp_str.map(|v| v as *mut c_void)); + maybe_push_slot( + slots, + ffi::Py_tp_repr, + self.tp_repr.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_tp_hash, + self.tp_hash.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_tp_getattro, + self.tp_getattro.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_tp_richcompare, + self.tp_richcompare.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_tp_setattro, + self.tp_setattro.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_nb_bool, + self.nb_bool.map(|v| v as *mut c_void), + ); } // Set functions used by `#[pyproto]`. pub fn set_str(&mut self) diff --git a/src/pyclass.rs b/src/pyclass.rs index a4c10876493..97e819d6952 100644 --- a/src/pyclass.rs +++ b/src/pyclass.rs @@ -9,7 +9,7 @@ use crate::types::PyAny; use crate::{class, ffi, PyCell, PyErr, PyNativeType, PyResult, PyTypeInfo, Python}; use std::ffi::CString; use std::marker::PhantomData; -use std::os::raw::c_void; +use std::os::raw::{c_int, c_void}; use std::{ptr, thread}; #[inline] @@ -107,120 +107,134 @@ pub trait PyClass: type BaseNativeType: PyTypeInfo + PyNativeType; } -#[cfg(not(Py_LIMITED_API))] -pub(crate) fn initialize_type_object( +pub(crate) fn maybe_push_slot( + slots: &mut Vec, + slot: c_int, + val: Option<*mut c_void>, +) { + if let Some(v) = val { + slots.push(ffi::PyType_Slot { + slot: slot, + pfunc: v, + }); + } +} + +pub(crate) fn create_type_object( py: Python, module_name: Option<&str>, - type_object: &mut ffi::PyTypeObject, -) -> PyResult<()> +) -> PyResult<*mut ffi::PyTypeObject> where T: PyClass, { - type_object.tp_doc = match T::DESCRIPTION { - // PyPy will segfault if passed only a nul terminator as `tp_doc`, ptr::null() is OK though. - "\0" => ptr::null(), - s if s.as_bytes().ends_with(b"\0") => s.as_ptr() as _, - // If the description is not null-terminated, create CString and leak it - s => CString::new(s)?.into_raw(), - }; - - type_object.tp_base = T::BaseType::type_object_raw(py); - - type_object.tp_name = match module_name { - Some(module_name) => CString::new(format!("{}.{}", module_name, T::NAME))?.into_raw(), - None => CString::new(T::NAME)?.into_raw(), - }; - - // dealloc - type_object.tp_dealloc = tp_dealloc::(); - - // type size - type_object.tp_basicsize = std::mem::size_of::() as ffi::Py_ssize_t; - - // __dict__ support - if let Some(dict_offset) = PyCell::::dict_offset() { - type_object.tp_dictoffset = dict_offset as ffi::Py_ssize_t; - } - - // weakref support - if let Some(weakref_offset) = PyCell::::weakref_offset() { - type_object.tp_weaklistoffset = weakref_offset as ffi::Py_ssize_t; - } - - // GC support - if let Some(gc) = T::gc_methods() { - unsafe { gc.as_ref() }.update_typeobj(type_object); - } - - // descriptor protocol - if let Some(descr) = T::descr_methods() { - unsafe { descr.as_ref() }.update_typeobj(type_object); - } - - // iterator methods - if let Some(iter) = T::iter_methods() { - unsafe { iter.as_ref() }.update_typeobj(type_object); - } - - // nb_bool is a part of PyObjectProtocol, but should be placed under tp_as_number - let mut nb_bool = None; - // basic methods + let mut slots = vec![]; if let Some(basic) = T::basic_methods() { - unsafe { basic.as_ref() }.update_typeobj(type_object); - nb_bool = unsafe { basic.as_ref() }.nb_bool; + unsafe { basic.as_ref() }.update_slots(&mut slots); } - // number methods - type_object.tp_as_number = T::number_methods() - .map(|mut p| { - unsafe { p.as_mut() }.nb_bool = nb_bool; - p.as_ptr() - }) - .unwrap_or_else(|| nb_bool.map_or_else(ptr::null_mut, ffi::PyNumberMethods::from_nb_bool)); - // mapping methods - type_object.tp_as_mapping = T::mapping_methods().map_or_else(ptr::null_mut, |p| p.as_ptr()); - // sequence methods - type_object.tp_as_sequence = T::sequence_methods().map_or_else(ptr::null_mut, |p| p.as_ptr()); - // async methods - type_object.tp_as_async = T::async_methods().map_or_else(ptr::null_mut, |p| p.as_ptr()); - // buffer protocol - type_object.tp_as_buffer = T::buffer_methods().map_or_else(ptr::null_mut, |p| p.as_ptr()); - - let (new, call, mut methods) = py_class_method_defs::(); - - // normal methods - if !methods.is_empty() { - methods.push(ffi::PyMethodDef_INIT); - type_object.tp_methods = Box::into_raw(methods.into_boxed_slice()) as _; + if let Some(number) = T::number_methods() { + maybe_push_slot( + &mut slots, + ffi::Py_nb_add, + unsafe { number.as_ref() }.nb_add.map(|v| v as *mut c_void), + ); } - // __new__ method - type_object.tp_new = new; - // __call__ method - type_object.tp_call = call; - - // properties - let mut props = py_class_properties::(); + slots.push(ffi::PyType_Slot { + slot: 0, + pfunc: ptr::null_mut(), + }); + let mut spec = ffi::PyType_Spec { + name: match module_name { + Some(module_name) => CString::new(format!("{}.{}", module_name, T::NAME))?.into_raw(), + None => CString::new(T::NAME)?.into_raw(), + }, + basicsize: std::mem::size_of::() as c_int, + itemsize: 0, + flags: 0, // XXXX: FILL ME IN PROPERLY, + slots: slots.as_mut_slice().as_mut_ptr(), + }; - if !T::Dict::IS_DUMMY { - props.push(ffi::PyGetSetDef_DICT); - } - if !props.is_empty() { - props.push(ffi::PyGetSetDef_INIT); - type_object.tp_getset = Box::into_raw(props.into_boxed_slice()) as _; + let type_object = unsafe { ffi::PyType_FromSpec(&mut spec) }; + if type_object.is_null() { + PyErr::fetch(py).into() + } else { + Ok(type_object as *mut ffi::PyTypeObject) } - // set type flags - py_class_flags::(type_object); - - // register type object - unsafe { - if ffi::PyType_Ready(type_object) == 0 { - Ok(()) - } else { - PyErr::fetch(py).into() - } - } + // type_object.tp_doc = match T::DESCRIPTION { + // // PyPy will segfault if passed only a nul terminator as `tp_doc`, ptr::null() is OK though. + // "\0" => ptr::null(), + // s if s.as_bytes().ends_with(b"\0") => s.as_ptr() as _, + // // If the description is not null-terminated, create CString and leak it + // s => CString::new(s)?.into_raw(), + // }; + + // type_object.tp_base = T::BaseType::type_object_raw(py); + + // // dealloc + // type_object.tp_dealloc = tp_dealloc::(); + + // // __dict__ support + // if let Some(dict_offset) = PyCell::::dict_offset() { + // type_object.tp_dictoffset = dict_offset as ffi::Py_ssize_t; + // } + + // // weakref support + // if let Some(weakref_offset) = PyCell::::weakref_offset() { + // type_object.tp_weaklistoffset = weakref_offset as ffi::Py_ssize_t; + // } + + // // GC support + // if let Some(gc) = T::gc_methods() { + // unsafe { gc.as_ref() }.update_typeobj(type_object); + // } + + // // descriptor protocol + // if let Some(descr) = T::descr_methods() { + // unsafe { descr.as_ref() }.update_typeobj(type_object); + // } + + // // iterator methods + // if let Some(iter) = T::iter_methods() { + // unsafe { iter.as_ref() }.update_typeobj(type_object); + // } + + // // mapping methods + // type_object.tp_as_mapping = T::mapping_methods().map_or_else(ptr::null_mut, |p| p.as_ptr()); + // // sequence methods + // type_object.tp_as_sequence = T::sequence_methods().map_or_else(ptr::null_mut, |p| p.as_ptr()); + // // async methods + // type_object.tp_as_async = T::async_methods().map_or_else(ptr::null_mut, |p| p.as_ptr()); + // // buffer protocol + // type_object.tp_as_buffer = T::buffer_methods().map_or_else(ptr::null_mut, |p| p.as_ptr()); + + // let (new, call, mut methods) = py_class_method_defs::(); + + // // normal methods + // if !methods.is_empty() { + // methods.push(ffi::PyMethodDef_INIT); + // type_object.tp_methods = Box::into_raw(methods.into_boxed_slice()) as _; + // } + + // // __new__ method + // type_object.tp_new = new; + // // __call__ method + // type_object.tp_call = call; + + // // properties + // let mut props = py_class_properties::(); + + // if !T::Dict::IS_DUMMY { + // props.push(ffi::PyGetSetDef_DICT); + // } + // if !props.is_empty() { + // props.push(ffi::PyGetSetDef_INIT); + // type_object.tp_getset = Box::into_raw(props.into_boxed_slice()) as _; + // } + + // // set type flags + // py_class_flags::(type_object); } fn py_class_flags(type_object: &mut ffi::PyTypeObject) { diff --git a/src/type_object.rs b/src/type_object.rs index 546f87824fb..487e738036e 100644 --- a/src/type_object.rs +++ b/src/type_object.rs @@ -3,7 +3,7 @@ use crate::conversion::IntoPyPointer; use crate::once_cell::GILOnceCell; -use crate::pyclass::{initialize_type_object, py_class_attributes, PyClass}; +use crate::pyclass::{create_type_object, py_class_attributes, PyClass}; use crate::pyclass_init::PyObjectInit; use crate::types::{PyAny, PyType}; use crate::{ffi, AsPyPointer, PyErr, PyNativeType, PyObject, PyResult, Python}; @@ -157,12 +157,10 @@ impl LazyStaticType { pub fn get_or_init(&self, py: Python) -> *mut ffi::PyTypeObject { let type_object = *self.value.get_or_init(py, || { - let mut type_object = Box::new(ffi::PyTypeObject_INIT); - initialize_type_object::(py, T::MODULE, type_object.as_mut()).unwrap_or_else(|e| { + create_type_object::(py, T::MODULE).unwrap_or_else(|e| { e.print(py); panic!("An error occurred while initializing class {}", T::NAME) - }); - Box::into_raw(type_object) + }) }); // We might want to fill the `tp_dict` with python instances of `T`