diff --git a/crates/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs index a20e966d6a..0f619b1399 100644 --- a/crates/vm/src/builtins/type.rs +++ b/crates/vm/src/builtins/type.rs @@ -26,12 +26,13 @@ use crate::{ protocol::{PyIterReturn, PyMappingMethods, PyNumberMethods, PySequenceMethods}, types::{ AsNumber, Callable, Constructor, GetAttr, PyTypeFlags, PyTypeSlots, Representable, SetAttr, + TypeDataRef, TypeDataRefMut, TypeDataSlot, }, }; use indexmap::{IndexMap, map::Entry}; use itertools::Itertools; use num_traits::ToPrimitive; -use std::{borrow::Borrow, collections::HashSet, ops::Deref, pin::Pin, ptr::NonNull}; +use std::{any::Any, borrow::Borrow, collections::HashSet, ops::Deref, pin::Pin, ptr::NonNull}; #[pyclass(module = false, name = "type", traverse = "manual")] pub struct PyType { @@ -65,6 +66,7 @@ pub struct HeapTypeExt { pub slots: Option>>, pub sequence_methods: PySequenceMethods, pub mapping_methods: PyMappingMethods, + pub type_data: PyRwLock>, } pub struct PointerSlot(NonNull); @@ -203,6 +205,7 @@ impl PyType { slots: None, sequence_methods: PySequenceMethods::default(), mapping_methods: PyMappingMethods::default(), + type_data: PyRwLock::new(None), }; let base = bases[0].clone(); @@ -563,6 +566,50 @@ impl PyType { |ext| PyRwLockReadGuard::map(ext.name.read(), |name| name.as_str()).into(), ) } + + // Type Data Slot API - CPython's PyObject_GetTypeData equivalent + + /// Initialize type data for this type. Can only be called once. + /// Returns an error if the type is not a heap type or if data is already initialized. + pub fn init_type_data(&self, data: T) -> Result<(), String> { + let ext = self + .heaptype_ext + .as_ref() + .ok_or_else(|| "Cannot set type data on non-heap types".to_string())?; + + let mut type_data = ext.type_data.write(); + if type_data.is_some() { + return Err("Type data already initialized".to_string()); + } + *type_data = Some(TypeDataSlot::new(data)); + Ok(()) + } + + /// Get a read guard to the type data. + /// Returns None if the type is not a heap type, has no data, or the data type doesn't match. + pub fn get_type_data(&self) -> Option> { + self.heaptype_ext + .as_ref() + .and_then(|ext| TypeDataRef::try_new(ext.type_data.read())) + } + + /// Get a write guard to the type data. + /// Returns None if the type is not a heap type, has no data, or the data type doesn't match. + pub fn get_type_data_mut(&self) -> Option> { + self.heaptype_ext + .as_ref() + .and_then(|ext| TypeDataRefMut::try_new(ext.type_data.write())) + } + + /// Check if this type has type data of the given type. + pub fn has_type_data(&self) -> bool { + self.heaptype_ext.as_ref().is_some_and(|ext| { + ext.type_data + .read() + .as_ref() + .is_some_and(|slot| slot.get::().is_some()) + }) + } } impl Py { @@ -1167,6 +1214,7 @@ impl Constructor for PyType { slots: heaptype_slots.clone(), sequence_methods: PySequenceMethods::default(), mapping_methods: PyMappingMethods::default(), + type_data: PyRwLock::new(None), }; (slots, heaptype_ext) }; diff --git a/crates/vm/src/stdlib/ctypes.rs b/crates/vm/src/stdlib/ctypes.rs index 7c35d4a700..2daaa6f3ab 100644 --- a/crates/vm/src/stdlib/ctypes.rs +++ b/crates/vm/src/stdlib/ctypes.rs @@ -389,27 +389,28 @@ pub(crate) mod _ctypes { /// Get the size of a ctypes type or instance #[pyfunction(name = "sizeof")] pub fn size_of(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { - use super::array::{PyCArray, PyCArrayType}; use super::pointer::PyCPointer; use super::structure::{PyCStructType, PyCStructure}; - use super::union::{PyCUnion, PyCUnionType}; + use super::union::PyCUnionType; + use super::util::StgInfo; + use crate::builtins::PyType; - // 1. Instances with stg_info - if obj.fast_isinstance(PyCArray::static_type()) { - // Get stg_info from the type - if let Some(type_obj) = obj.class().as_object().downcast_ref::() { - return Ok(type_obj.stg_info.size); - } + // 1. Check TypeDataSlot on class (for instances) + if let Some(stg_info) = obj.class().get_type_data::() { + return Ok(stg_info.size); } + + // 2. Check TypeDataSlot on type itself (for type objects) + if let Some(type_obj) = obj.downcast_ref::() + && let Some(stg_info) = type_obj.get_type_data::() + { + return Ok(stg_info.size); + } + + // 3. Instances with cdata buffer if let Some(structure) = obj.downcast_ref::() { return Ok(structure.cdata.read().size()); } - if obj.fast_isinstance(PyCUnion::static_type()) { - // Get stg_info from the type - if let Some(type_obj) = obj.class().as_object().downcast_ref::() { - return Ok(type_obj.stg_info.size); - } - } if let Some(simple) = obj.downcast_ref::() { return Ok(simple.cdata.read().size()); } @@ -417,11 +418,6 @@ pub(crate) mod _ctypes { return Ok(std::mem::size_of::()); } - // 2. Types (metatypes with stg_info) - if let Some(array_type) = obj.downcast_ref::() { - return Ok(array_type.stg_info.size); - } - // 3. Type objects if let Ok(type_ref) = obj.clone().downcast::() { // Structure types - check if metaclass is or inherits from PyCStructType @@ -659,33 +655,37 @@ pub(crate) mod _ctypes { #[pyfunction] fn alignment(tp: Either, vm: &VirtualMachine) -> PyResult { - use super::array::{PyCArray, PyCArrayType}; use super::base::PyCSimpleType; use super::pointer::PyCPointer; use super::structure::PyCStructure; use super::union::PyCUnion; + use super::util::StgInfo; + use crate::builtins::PyType; let obj = match &tp { Either::A(t) => t.as_object(), Either::B(o) => o.as_ref(), }; - // Try to get alignment from stg_info directly (for instances) - if let Some(array_type) = obj.downcast_ref::() { - return Ok(array_type.stg_info.align); + // 1. Check TypeDataSlot on class (for instances) + if let Some(stg_info) = obj.class().get_type_data::() { + return Ok(stg_info.align); } + + // 2. Check TypeDataSlot on type itself (for type objects) + if let Some(type_obj) = obj.downcast_ref::() + && let Some(stg_info) = type_obj.get_type_data::() + { + return Ok(stg_info.align); + } + + // 3. Fallback for simple types without TypeDataSlot if obj.fast_isinstance(PyCSimple::static_type()) { // Get stg_info from the type by reading _type_ attribute let cls = obj.class().to_owned(); let stg_info = PyCSimpleType::get_stg_info(&cls, vm); return Ok(stg_info.align); } - if obj.fast_isinstance(PyCArray::static_type()) { - // Get stg_info from the type - if let Some(type_obj) = obj.class().as_object().downcast_ref::() { - return Ok(type_obj.stg_info.align); - } - } if obj.fast_isinstance(PyCStructure::static_type()) { // Calculate alignment from _fields_ let cls = obj.class(); diff --git a/crates/vm/src/stdlib/ctypes/array.rs b/crates/vm/src/stdlib/ctypes/array.rs index 499b525b89..98274e388b 100644 --- a/crates/vm/src/stdlib/ctypes/array.rs +++ b/crates/vm/src/stdlib/ctypes/array.rs @@ -1,13 +1,13 @@ use crate::atomic_func; use crate::builtins::{PyBytes, PyInt}; -use crate::convert::ToPyObject; +use crate::class::StaticType; use crate::function::FuncArgs; use crate::protocol::{ BufferDescriptor, BufferMethods, PyBuffer, PyNumberMethods, PySequenceMethods, }; use crate::stdlib::ctypes::base::CDataObject; use crate::stdlib::ctypes::util::StgInfo; -use crate::types::{AsBuffer, AsNumber, AsSequence, Callable}; +use crate::types::{AsBuffer, AsNumber, AsSequence}; use crate::{AsObject, Py, PyObjectRef, PyPayload}; use crate::{ PyResult, VirtualMachine, @@ -20,56 +20,49 @@ use rustpython_common::lock::PyRwLock; use rustpython_vm::stdlib::ctypes::_ctypes::get_size; use rustpython_vm::stdlib::ctypes::base::PyCData; +/// PyCArrayType - metatype for Array types +/// CPython stores array info (type, length) in StgInfo via type_data #[pyclass(name = "PyCArrayType", base = PyType, module = "_ctypes")] -#[derive(PyPayload)] -pub struct PyCArrayType { - pub(super) stg_info: StgInfo, - pub(super) typ: PyRwLock, - pub(super) length: AtomicCell, - pub(super) element_size: AtomicCell, -} - -impl std::fmt::Debug for PyCArrayType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("PyCArrayType") - .field("typ", &self.typ) - .field("length", &self.length) - .finish() - } -} - -impl Callable for PyCArrayType { - type Args = FuncArgs; - fn call(zelf: &Py, args: Self::Args, vm: &VirtualMachine) -> PyResult { - // Create an instance of the array - let element_type = zelf.typ.read().clone(); - let length = zelf.length.load(); - let element_size = zelf.element_size.load(); - let total_size = element_size * length; - let mut buffer = vec![0u8; total_size]; - - // Initialize from positional arguments - for (i, value) in args.args.iter().enumerate() { - if i >= length { - break; - } - let offset = i * element_size; - if let Ok(int_val) = value.try_int(vm) { - let bytes = PyCArray::int_to_bytes(int_val.as_bigint(), element_size); - if offset + element_size <= buffer.len() { - buffer[offset..offset + element_size].copy_from_slice(&bytes); - } - } +#[derive(Debug, Default, PyPayload)] +pub struct PyCArrayType {} + +/// Create a new Array type with StgInfo stored in type_data (CPython style) +pub fn create_array_type_with_stg_info(stg_info: StgInfo, vm: &VirtualMachine) -> PyResult { + // Get PyCArrayType as metaclass + let metaclass = PyCArrayType::static_type().to_owned(); + + // Create a unique name for the array type + let type_name = format!("Array_{}", stg_info.length); + + // Create args for type(): (name, bases, dict) + let name = vm.ctx.new_str(type_name); + let bases = vm + .ctx + .new_tuple(vec![PyCArray::static_type().to_owned().into()]); + let dict = vm.ctx.new_dict(); + + let args = FuncArgs::new( + vec![name.into(), bases.into(), dict.into()], + crate::function::KwArgs::default(), + ); + + // Create the new type using PyType::slot_new with PyCArrayType as metaclass + let new_type = crate::builtins::type_::PyType::slot_new(metaclass, args, vm)?; + + // Set StgInfo in type_data + let type_ref: PyTypeRef = new_type + .clone() + .downcast() + .map_err(|_| vm.new_type_error("Failed to create array type".to_owned()))?; + + if type_ref.init_type_data(stg_info.clone()).is_err() { + // Type data already initialized - update it + if let Some(mut existing) = type_ref.get_type_data_mut::() { + *existing = stg_info; } - - Ok(PyCArray { - typ: PyRwLock::new(element_type), - length: AtomicCell::new(length), - element_size: AtomicCell::new(element_size), - cdata: PyRwLock::new(CDataObject::from_bytes(buffer, None)), - } - .into_pyobject(vm)) } + + Ok(new_type) } impl Constructor for PyCArrayType { @@ -80,54 +73,62 @@ impl Constructor for PyCArrayType { } } -#[pyclass(flags(IMMUTABLETYPE), with(Callable, Constructor, AsNumber))] +#[pyclass(flags(IMMUTABLETYPE), with(Constructor, AsNumber))] impl PyCArrayType { #[pygetset(name = "_type_")] - fn typ(&self) -> PyObjectRef { - self.typ.read().clone() + fn typ(zelf: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + zelf.downcast_ref::() + .and_then(|t| t.get_type_data::()) + .and_then(|stg| stg.element_type.clone()) + .unwrap_or_else(|| vm.ctx.none()) } #[pygetset(name = "_length_")] - fn length(&self) -> usize { - self.length.load() + fn length(zelf: PyObjectRef) -> usize { + zelf.downcast_ref::() + .and_then(|t| t.get_type_data::()) + .map(|stg| stg.length) + .unwrap_or(0) } #[pymethod] - fn __mul__(zelf: &Py, n: isize, vm: &VirtualMachine) -> PyResult { + fn __mul__(zelf: PyObjectRef, n: isize, vm: &VirtualMachine) -> PyResult { if n < 0 { return Err(vm.new_value_error(format!("Array length must be >= 0, not {n}"))); } - // Create a nested array type: (inner_type * inner_length) * n - // The new array has n elements, each element is the current array type - // e.g., (c_int * 5) * 3 = Array of 3 elements, each is (c_int * 5) - let inner_length = zelf.length.load(); - let inner_element_size = zelf.element_size.load(); + + // Get inner array info from TypeDataSlot + let type_ref = zelf.downcast_ref::().unwrap(); + let (_inner_length, inner_size) = type_ref + .get_type_data::() + .map(|stg| (stg.length, stg.size)) + .unwrap_or((0, 0)); // The element type of the new array is the current array type itself - let current_array_type: PyObjectRef = zelf.as_object().to_owned(); + let current_array_type: PyObjectRef = zelf.clone(); // Element size is the total size of the inner array - let new_element_size = inner_length * inner_element_size; + let new_element_size = inner_size; let total_size = new_element_size * (n as usize); - let stg_info = StgInfo::new(total_size, inner_element_size); - Ok(PyCArrayType { - stg_info, - typ: PyRwLock::new(current_array_type), - length: AtomicCell::new(n as usize), - element_size: AtomicCell::new(new_element_size), - } - .to_pyobject(vm)) + let stg_info = StgInfo::new_array( + total_size, + new_element_size, + n as usize, + current_array_type, + new_element_size, + ); + + create_array_type_with_stg_info(stg_info, vm) } #[pyclassmethod] fn in_dll( - zelf: &Py, + zelf: PyObjectRef, dll: PyObjectRef, name: crate::builtins::PyStrRef, vm: &VirtualMachine, ) -> PyResult { - use crate::stdlib::ctypes::_ctypes::size_of; use libloading::Symbol; // Get the library handle from dll object @@ -168,10 +169,18 @@ impl PyCArrayType { return Err(vm.new_attribute_error("Library is closed".to_owned())); }; - // Get size from the array type - let element_type = zelf.typ.read().clone(); - let length = zelf.length.load(); - let element_size = size_of(element_type.clone(), vm)?; + // Get size from the array type via TypeDataSlot + let type_ref = zelf.downcast_ref::().unwrap(); + let (element_type, length, element_size) = type_ref + .get_type_data::() + .map(|stg| { + ( + stg.element_type.clone().unwrap_or_else(|| vm.ctx.none()), + stg.length, + stg.element_size, + ) + }) + .unwrap_or_else(|| (vm.ctx.none(), 0, 0)); let total_size = element_size * length; // Read data from symbol address @@ -206,15 +215,13 @@ impl AsNumber for PyCArrayType { fn as_number() -> &'static PyNumberMethods { static AS_NUMBER: PyNumberMethods = PyNumberMethods { multiply: Some(|a, b, vm| { - let zelf = a - .downcast_ref::() - .ok_or_else(|| vm.new_type_error("expected PyCArrayType".to_owned()))?; + // a is a type object whose metaclass is PyCArrayType (e.g., Array_5) let n = b .try_index(vm)? .as_bigint() .to_isize() .ok_or_else(|| vm.new_overflow_error("array size too large".to_owned()))?; - PyCArrayType::__mul__(zelf, n, vm) + PyCArrayType::__mul__(a.to_owned(), n, vm) }), ..PyNumberMethods::NOT_IMPLEMENTED }; diff --git a/crates/vm/src/stdlib/ctypes/base.rs b/crates/vm/src/stdlib/ctypes/base.rs index 22f3703f1b..80d42fba3c 100644 --- a/crates/vm/src/stdlib/ctypes/base.rs +++ b/crates/vm/src/stdlib/ctypes/base.rs @@ -1,8 +1,6 @@ use super::_ctypes::bytes_to_pyobject; -use super::array::PyCArrayType; use super::util::StgInfo; use crate::builtins::{PyBytes, PyFloat, PyInt, PyNone, PyStr, PyStrRef, PyType, PyTypeRef}; -use crate::convert::ToPyObject; use crate::function::{ArgBytesLike, Either, FuncArgs, KwArgs, OptionalArg}; use crate::protocol::{BufferDescriptor, BufferMethods, PyBuffer, PyNumberMethods}; use crate::stdlib::ctypes::_ctypes::new_simple_type; @@ -231,10 +229,7 @@ impl PyCData { #[pyclass(module = "_ctypes", name = "PyCSimpleType", base = PyType)] #[derive(Debug, PyPayload, Default)] -pub struct PyCSimpleType { - #[allow(dead_code)] - pub stg_info: StgInfo, -} +pub struct PyCSimpleType {} #[pyclass(flags(BASETYPE), with(AsNumber))] impl PyCSimpleType { @@ -747,6 +742,8 @@ impl PyCSimple { #[pyclassmethod] fn repeat(cls: PyTypeRef, n: isize, vm: &VirtualMachine) -> PyResult { use super::_ctypes::get_size; + use super::array::create_array_type_with_stg_info; + if n < 0 { return Err(vm.new_value_error(format!("Array length must be >= 0, not {n}"))); } @@ -766,14 +763,14 @@ impl PyCSimple { std::mem::size_of::() }; let total_size = element_size * (n as usize); - let stg_info = super::util::StgInfo::new(total_size, element_size); - Ok(PyCArrayType { - stg_info, - typ: PyRwLock::new(cls.clone().into()), - length: AtomicCell::new(n as usize), - element_size: AtomicCell::new(element_size), - } - .to_pyobject(vm)) + let stg_info = super::util::StgInfo::new_array( + total_size, + element_size, + n as usize, + cls.clone().into(), + element_size, + ); + create_array_type_with_stg_info(stg_info, vm) } #[pyclassmethod] diff --git a/crates/vm/src/stdlib/ctypes/pointer.rs b/crates/vm/src/stdlib/ctypes/pointer.rs index a5b3a9eb04..156c4e54ee 100644 --- a/crates/vm/src/stdlib/ctypes/pointer.rs +++ b/crates/vm/src/stdlib/ctypes/pointer.rs @@ -1,44 +1,36 @@ -use crossbeam_utils::atomic::AtomicCell; use num_traits::ToPrimitive; use rustpython_common::lock::PyRwLock; use crate::builtins::{PyType, PyTypeRef}; -use crate::convert::ToPyObject; use crate::function::FuncArgs; use crate::protocol::PyNumberMethods; use crate::stdlib::ctypes::PyCData; use crate::types::{AsNumber, Constructor}; use crate::{AsObject, Py, PyObjectRef, PyPayload, PyResult, VirtualMachine}; -use super::util::StgInfo; - #[pyclass(name = "PyCPointerType", base = PyType, module = "_ctypes")] -#[derive(PyPayload, Debug)] -pub struct PyCPointerType { - #[allow(dead_code)] - pub stg_info: StgInfo, -} +#[derive(PyPayload, Debug, Default)] +pub struct PyCPointerType {} #[pyclass(flags(IMMUTABLETYPE), with(AsNumber))] impl PyCPointerType { #[pymethod] fn __mul__(cls: PyTypeRef, n: isize, vm: &VirtualMachine) -> PyResult { - use super::array::PyCArrayType; + use super::array::create_array_type_with_stg_info; if n < 0 { return Err(vm.new_value_error(format!("Array length must be >= 0, not {n}"))); } // Pointer size let element_size = std::mem::size_of::(); let total_size = element_size * (n as usize); - let mut stg_info = super::util::StgInfo::new(total_size, element_size); - stg_info.length = n as usize; - Ok(PyCArrayType { - stg_info, - typ: PyRwLock::new(cls.as_object().to_owned()), - length: AtomicCell::new(n as usize), - element_size: AtomicCell::new(element_size), - } - .to_pyobject(vm)) + let stg_info = super::util::StgInfo::new_array( + total_size, + element_size, + n as usize, + cls.as_object().to_owned(), + element_size, + ); + create_array_type_with_stg_info(stg_info, vm) } } diff --git a/crates/vm/src/stdlib/ctypes/structure.rs b/crates/vm/src/stdlib/ctypes/structure.rs index 403dbad74b..d3cdea69c7 100644 --- a/crates/vm/src/stdlib/ctypes/structure.rs +++ b/crates/vm/src/stdlib/ctypes/structure.rs @@ -8,7 +8,6 @@ use crate::protocol::{BufferDescriptor, BufferMethods, PyBuffer, PyNumberMethods use crate::stdlib::ctypes::_ctypes::get_size; use crate::types::{AsBuffer, AsNumber, Constructor}; use crate::{AsObject, Py, PyObjectRef, PyPayload, PyResult, VirtualMachine}; -use crossbeam_utils::atomic::AtomicCell; use indexmap::IndexMap; use num_traits::ToPrimitive; use rustpython_common::lock::PyRwLock; @@ -16,11 +15,8 @@ use std::fmt::Debug; /// PyCStructType - metaclass for Structure #[pyclass(name = "PyCStructType", base = PyType, module = "_ctypes")] -#[derive(Debug, PyPayload)] -pub struct PyCStructType { - #[allow(dead_code)] - pub stg_info: StgInfo, -} +#[derive(Debug, PyPayload, Default)] +pub struct PyCStructType {} impl Constructor for PyCStructType { type Args = FuncArgs; @@ -161,7 +157,7 @@ impl PyCStructType { #[pymethod] fn __mul__(cls: PyTypeRef, n: isize, vm: &VirtualMachine) -> PyResult { - use super::array::PyCArrayType; + use super::array::create_array_type_with_stg_info; use crate::stdlib::ctypes::_ctypes::size_of; if n < 0 { @@ -174,15 +170,14 @@ impl PyCStructType { let total_size = element_size .checked_mul(n as usize) .ok_or_else(|| vm.new_overflow_error("array size too large".to_owned()))?; - let mut stg_info = super::util::StgInfo::new(total_size, element_size); - stg_info.length = n as usize; - Ok(PyCArrayType { - stg_info, - typ: PyRwLock::new(cls.clone().into()), - length: AtomicCell::new(n as usize), - element_size: AtomicCell::new(element_size), - } - .to_pyobject(vm)) + let stg_info = super::util::StgInfo::new_array( + total_size, + element_size, + n as usize, + cls.clone().into(), + element_size, + ); + create_array_type_with_stg_info(stg_info, vm) } } diff --git a/crates/vm/src/stdlib/ctypes/union.rs b/crates/vm/src/stdlib/ctypes/union.rs index 84afd0bec1..37d8e4f688 100644 --- a/crates/vm/src/stdlib/ctypes/union.rs +++ b/crates/vm/src/stdlib/ctypes/union.rs @@ -13,18 +13,8 @@ use rustpython_common::lock::PyRwLock; /// PyCUnionType - metaclass for Union #[pyclass(name = "UnionType", base = PyType, module = "_ctypes")] -#[derive(Debug, PyPayload)] -pub struct PyCUnionType { - pub stg_info: StgInfo, -} - -impl Default for PyCUnionType { - fn default() -> Self { - PyCUnionType { - stg_info: StgInfo::new(0, 1), - } - } -} +#[derive(Debug, PyPayload, Default)] +pub struct PyCUnionType {} impl Constructor for PyCUnionType { type Args = FuncArgs; diff --git a/crates/vm/src/stdlib/ctypes/util.rs b/crates/vm/src/stdlib/ctypes/util.rs index b8fd9c89c0..b8c6def63c 100644 --- a/crates/vm/src/stdlib/ctypes/util.rs +++ b/crates/vm/src/stdlib/ctypes/util.rs @@ -1,17 +1,40 @@ use crate::PyObjectRef; /// Storage information for ctypes types -#[derive(Debug, Clone)] +/// Stored in TypeDataSlot of heap types (PyType::init_type_data/get_type_data) +#[derive(Clone)] pub struct StgInfo { - #[allow(dead_code)] pub initialized: bool, - pub size: usize, // number of bytes - pub align: usize, // alignment requirements - pub length: usize, // number of fields (for arrays/structures) - #[allow(dead_code)] + pub size: usize, // number of bytes + pub align: usize, // alignment requirements + pub length: usize, // number of fields (for arrays/structures) pub proto: Option, // Only for Pointer/ArrayObject - #[allow(dead_code)] - pub flags: i32, // calling convention and such + pub flags: i32, // calling convention and such + + // Array-specific fields (moved from PyCArrayType) + pub element_type: Option, // _type_ for arrays + pub element_size: usize, // size of each element +} + +// StgInfo is stored in type_data which requires Send + Sync. +// The PyObjectRef in proto/element_type fields is protected by the type system's locking mechanism. +// CPython: ctypes objects are not thread-safe by design; users must synchronize access. +unsafe impl Send for StgInfo {} +unsafe impl Sync for StgInfo {} + +impl std::fmt::Debug for StgInfo { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("StgInfo") + .field("initialized", &self.initialized) + .field("size", &self.size) + .field("align", &self.align) + .field("length", &self.length) + .field("proto", &self.proto) + .field("flags", &self.flags) + .field("element_type", &self.element_type) + .field("element_size", &self.element_size) + .finish() + } } impl Default for StgInfo { @@ -23,6 +46,8 @@ impl Default for StgInfo { length: 0, proto: None, flags: 0, + element_type: None, + element_size: 0, } } } @@ -36,6 +61,28 @@ impl StgInfo { length: 0, proto: None, flags: 0, + element_type: None, + element_size: 0, + } + } + + /// Create StgInfo for an array type + pub fn new_array( + size: usize, + align: usize, + length: usize, + element_type: PyObjectRef, + element_size: usize, + ) -> Self { + StgInfo { + initialized: true, + size, + align, + length, + proto: None, + flags: 0, + element_type: Some(element_type), + element_size, } } } diff --git a/crates/vm/src/types/slot.rs b/crates/vm/src/types/slot.rs index 8166ca8335..a4169fdd83 100644 --- a/crates/vm/src/types/slot.rs +++ b/crates/vm/src/types/slot.rs @@ -1,3 +1,6 @@ +use crate::common::lock::{ + PyMappedRwLockReadGuard, PyMappedRwLockWriteGuard, PyRwLockReadGuard, PyRwLockWriteGuard, +}; use crate::{ AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, builtins::{PyInt, PyStr, PyStrInterned, PyStrRef, PyType, PyTypeRef, type_::PointerSlot}, @@ -16,7 +19,96 @@ use crate::{ use crossbeam_utils::atomic::AtomicCell; use malachite_bigint::BigInt; use num_traits::{Signed, ToPrimitive}; -use std::{borrow::Borrow, cmp::Ordering, ops::Deref}; +use std::{any::Any, any::TypeId, borrow::Borrow, cmp::Ordering, ops::Deref}; + +/// Type-erased storage for extension module data attached to heap types. +pub struct TypeDataSlot { + // PyObject_GetTypeData + type_id: TypeId, + data: Box, +} + +impl TypeDataSlot { + /// Create a new type data slot with the given data. + pub fn new(data: T) -> Self { + Self { + type_id: TypeId::of::(), + data: Box::new(data), + } + } + + /// Get a reference to the data if the type matches. + pub fn get(&self) -> Option<&T> { + if self.type_id == TypeId::of::() { + self.data.downcast_ref() + } else { + None + } + } + + /// Get a mutable reference to the data if the type matches. + pub fn get_mut(&mut self) -> Option<&mut T> { + if self.type_id == TypeId::of::() { + self.data.downcast_mut() + } else { + None + } + } +} + +/// Read guard for type data access, using mapped guard for zero-cost deref. +pub struct TypeDataRef<'a, T: 'static> { + guard: PyMappedRwLockReadGuard<'a, T>, +} + +impl<'a, T: Any + 'static> TypeDataRef<'a, T> { + /// Try to create a TypeDataRef from a read guard. + /// Returns None if the slot is empty or contains a different type. + pub fn try_new(guard: PyRwLockReadGuard<'a, Option>) -> Option { + PyRwLockReadGuard::try_map(guard, |opt| opt.as_ref().and_then(|slot| slot.get::())) + .ok() + .map(|guard| Self { guard }) + } +} + +impl std::ops::Deref for TypeDataRef<'_, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.guard + } +} + +/// Write guard for type data access, using mapped guard for zero-cost deref. +pub struct TypeDataRefMut<'a, T: 'static> { + guard: PyMappedRwLockWriteGuard<'a, T>, +} + +impl<'a, T: Any + 'static> TypeDataRefMut<'a, T> { + /// Try to create a TypeDataRefMut from a write guard. + /// Returns None if the slot is empty or contains a different type. + pub fn try_new(guard: PyRwLockWriteGuard<'a, Option>) -> Option { + PyRwLockWriteGuard::try_map(guard, |opt| { + opt.as_mut().and_then(|slot| slot.get_mut::()) + }) + .ok() + .map(|guard| Self { guard }) + } +} + +impl std::ops::Deref for TypeDataRefMut<'_, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.guard + } +} + +impl std::ops::DerefMut for TypeDataRefMut<'_, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.guard + } +} #[macro_export] macro_rules! atomic_func {