From 4b52c56eee25bf24a178dde06ec47f3870aa84d5 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sat, 29 Nov 2025 00:31:59 +0900 Subject: [PATCH 1/5] PyCStructure --- crates/vm/src/stdlib/ctypes.rs | 59 +++- crates/vm/src/stdlib/ctypes/array.rs | 50 ++- crates/vm/src/stdlib/ctypes/field.rs | 217 +++++++++++-- crates/vm/src/stdlib/ctypes/structure.rs | 380 ++++++++++++++++++++--- 4 files changed, 636 insertions(+), 70 deletions(-) diff --git a/crates/vm/src/stdlib/ctypes.rs b/crates/vm/src/stdlib/ctypes.rs index 500ddbfb06b..1664f712e82 100644 --- a/crates/vm/src/stdlib/ctypes.rs +++ b/crates/vm/src/stdlib/ctypes.rs @@ -21,6 +21,7 @@ pub fn extend_module_nodes(vm: &VirtualMachine, module: &Py) { array::PyCArrayType::make_class(ctx); field::PyCFieldType::make_class(ctx); pointer::PyCPointerType::make_class(ctx); + structure::PyCStructType::make_class(ctx); extend_module!(vm, module, { "_CData" => PyCData::make_class(ctx), "_SimpleCData" => PyCSimple::make_class(ctx), @@ -46,9 +47,10 @@ pub(crate) mod _ctypes { use super::base::PyCSimple; use crate::builtins::PyTypeRef; use crate::class::StaticType; + use crate::convert::ToPyObject; use crate::function::{Either, FuncArgs, OptionalArg}; use crate::stdlib::ctypes::library; - use crate::{AsObject, PyObjectRef, PyResult, TryFromObject, VirtualMachine}; + use crate::{AsObject, PyObjectRef, PyPayload, PyResult, TryFromObject, VirtualMachine}; use crossbeam_utils::atomic::AtomicCell; use std::ffi::{ c_double, c_float, c_int, c_long, c_longlong, c_schar, c_short, c_uchar, c_uint, c_ulong, @@ -57,6 +59,22 @@ pub(crate) mod _ctypes { use std::mem; use widestring::WideChar; + /// CArgObject - returned by byref() + #[pyclass(name = "CArgObject", module = "_ctypes", no_attr)] + #[derive(Debug, PyPayload)] + pub struct CArgObject { + pub obj: PyObjectRef, + pub offset: isize, + } + + #[pyclass] + impl CArgObject { + #[pygetset] + fn _obj(&self) -> PyObjectRef { + self.obj.clone() + } + } + #[pyattr(name = "__version__")] const __VERSION__: &str = "1.1.0"; @@ -322,9 +340,27 @@ pub(crate) mod _ctypes { } #[pyfunction] - fn byref(_args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { - // TODO: RUSTPYTHON - Err(vm.new_value_error("not implemented")) + fn byref(obj: PyObjectRef, offset: OptionalArg, vm: &VirtualMachine) -> PyResult { + use super::base::PyCData; + use crate::class::StaticType; + + // Check if obj is a ctypes instance + if !obj.fast_isinstance(PyCData::static_type()) + && !obj.fast_isinstance(PyCSimple::static_type()) + { + return Err(vm.new_type_error( + "byref() argument must be a ctypes instance, not 'int'".to_string(), + )); + } + + let offset_val = offset.unwrap_or(0); + + // Create CArgObject to hold the reference + Ok(CArgObject { + obj, + offset: offset_val, + } + .to_pyobject(vm)) } #[pyfunction] @@ -380,6 +416,21 @@ pub(crate) mod _ctypes { f as usize } + #[pyattr] + fn _wstring_at_addr(_vm: &VirtualMachine) -> usize { + // Return address of wcsnlen or similar wide string function + #[cfg(not(target_os = "windows"))] + { + let f = libc::wcslen; + f as usize + } + #[cfg(target_os = "windows")] + { + // On Windows, use wcslen from ucrt + 0 + } + } + #[pyattr] fn _cast_addr(_vm: &VirtualMachine) -> usize { // TODO: RUSTPYTHON diff --git a/crates/vm/src/stdlib/ctypes/array.rs b/crates/vm/src/stdlib/ctypes/array.rs index a46322460a9..bbc65530b9f 100644 --- a/crates/vm/src/stdlib/ctypes/array.rs +++ b/crates/vm/src/stdlib/ctypes/array.rs @@ -1,5 +1,7 @@ use crate::builtins::PyBytes; -use crate::types::Callable; +use crate::convert::ToPyObject; +use crate::protocol::PyNumberMethods; +use crate::types::{AsNumber, Callable}; use crate::{Py, PyObjectRef, PyPayload}; use crate::{ PyResult, VirtualMachine, @@ -7,6 +9,7 @@ use crate::{ types::Constructor, }; use crossbeam_utils::atomic::AtomicCell; +use num_traits::ToPrimitive; use rustpython_common::lock::PyRwLock; use rustpython_vm::stdlib::ctypes::base::PyCData; @@ -44,8 +47,49 @@ impl Constructor for PyCArrayType { } } -#[pyclass(flags(IMMUTABLETYPE), with(Callable, Constructor))] -impl PyCArrayType {} +#[pyclass(flags(IMMUTABLETYPE), with(Callable, Constructor, AsNumber))] +impl PyCArrayType { + #[pymethod] + fn __mul__(zelf: &Py, 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's element type is the current array type + let inner_type = zelf.inner.typ.read().clone(); + let inner_length = zelf.inner.length.load(); + + // Create a new array type where the element is the current array + Ok(PyCArrayType { + inner: PyCArray { + typ: PyRwLock::new(inner_type), + length: AtomicCell::new(inner_length * n as usize), + value: PyRwLock::new(vm.ctx.none()), + }, + } + .to_pyobject(vm)) + } +} + +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()))?; + 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) + }), + ..PyNumberMethods::NOT_IMPLEMENTED + }; + &AS_NUMBER + } +} #[pyclass( name = "Array", diff --git a/crates/vm/src/stdlib/ctypes/field.rs b/crates/vm/src/stdlib/ctypes/field.rs index e4e3bd6a09e..939cddee97c 100644 --- a/crates/vm/src/stdlib/ctypes/field.rs +++ b/crates/vm/src/stdlib/ctypes/field.rs @@ -1,9 +1,10 @@ -use crate::builtins::PyType; -use crate::builtins::PyTypeRef; -use crate::stdlib::ctypes::PyCData; -use crate::types::Constructor; -use crate::types::Representable; -use crate::{Py, PyResult, VirtualMachine}; +use crate::builtins::{PyType, PyTypeRef}; +use crate::function::PySetterValue; +use crate::types::{Constructor, GetDescriptor, Representable}; +use crate::{AsObject, Py, PyObjectRef, PyResult, VirtualMachine}; +use num_traits::ToPrimitive; + +use super::structure::PyCStructure; #[pyclass(name = "PyCFieldType", base = PyType, module = "_ctypes")] #[derive(PyPayload, Debug)] @@ -15,28 +16,55 @@ pub struct PyCFieldType { #[pyclass] impl PyCFieldType {} -#[pyclass( - name = "CField", - base = PyCData, - metaclass = "PyCFieldType", - module = "_ctypes" -)] +#[pyclass(name = "CField", module = "_ctypes")] #[derive(Debug, PyPayload)] pub struct PyCField { - byte_offset: usize, - byte_size: usize, + pub(super) byte_offset: usize, + pub(super) byte_size: usize, #[allow(unused)] - index: usize, - proto: PyTypeRef, - anonymous: bool, - bitfield_size: bool, - bit_offset: u8, - name: String, + pub(super) index: usize, + /// The ctypes type for this field (can be any ctypes type including arrays) + pub(super) proto: PyObjectRef, + pub(super) anonymous: bool, + pub(super) bitfield_size: bool, + pub(super) bit_offset: u8, + pub(super) name: String, +} + +impl PyCField { + pub fn new( + name: String, + proto: PyObjectRef, + byte_offset: usize, + byte_size: usize, + index: usize, + ) -> Self { + Self { + name, + proto, + byte_offset, + byte_size, + index, + anonymous: false, + bitfield_size: false, + bit_offset: 0, + } + } } impl Representable for PyCField { - fn repr_str(zelf: &Py, _vm: &VirtualMachine) -> PyResult { - let tp_name = zelf.proto.name().to_string(); + fn repr_str(zelf: &Py, vm: &VirtualMachine) -> PyResult { + // Get type name from the proto object + let tp_name = if let Some(name_attr) = vm + .ctx + .interned_str("__name__") + .and_then(|s| zelf.proto.get_attr(s, vm).ok()) + { + name_attr.str(vm)?.to_string() + } else { + zelf.proto.class().name().to_string() + }; + if zelf.bitfield_size { Ok(format!( "<{} type={}, ofs={byte_offset}, bit_size={bitfield_size}, bit_offset={bit_offset}", @@ -71,8 +99,149 @@ impl Constructor for PyCField { } } -#[pyclass(flags(BASETYPE, IMMUTABLETYPE), with(Constructor, Representable))] +impl GetDescriptor for PyCField { + fn descr_get( + zelf: PyObjectRef, + obj: Option, + _cls: Option, + vm: &VirtualMachine, + ) -> PyResult { + let zelf = zelf + .downcast::() + .map_err(|_| vm.new_type_error("expected CField".to_owned()))?; + + // If obj is None, return the descriptor itself (class attribute access) + let obj = match obj { + Some(obj) if !vm.is_none(&obj) => obj, + _ => return Ok(zelf.into()), + }; + + // Instance attribute access - read value from the structure's buffer + if let Some(structure) = obj.payload::() { + let buffer = structure.buffer.read(); + let offset = zelf.byte_offset; + let size = zelf.byte_size; + + if offset + size <= buffer.len() { + let bytes = &buffer[offset..offset + size]; + return PyCField::bytes_to_value(bytes, size, vm); + } + } + + // Fallback: return 0 for uninitialized or unsupported types + Ok(vm.ctx.new_int(0).into()) + } +} + impl PyCField { + /// Convert bytes to a Python value based on size + fn bytes_to_value(bytes: &[u8], size: usize, vm: &VirtualMachine) -> PyResult { + match size { + 1 => Ok(vm.ctx.new_int(bytes[0] as i8).into()), + 2 => { + let val = i16::from_ne_bytes([bytes[0], bytes[1]]); + Ok(vm.ctx.new_int(val).into()) + } + 4 => { + let val = i32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]); + Ok(vm.ctx.new_int(val).into()) + } + 8 => { + let val = i64::from_ne_bytes([ + bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], + ]); + Ok(vm.ctx.new_int(val).into()) + } + _ => Ok(vm.ctx.new_int(0).into()), + } + } + + /// Convert a Python value to bytes + fn value_to_bytes(value: &PyObjectRef, size: usize, vm: &VirtualMachine) -> PyResult> { + if let Ok(int_val) = value.try_int(vm) { + let i = int_val.as_bigint(); + match size { + 1 => { + let val = i.to_i8().unwrap_or(0); + Ok(val.to_ne_bytes().to_vec()) + } + 2 => { + let val = i.to_i16().unwrap_or(0); + Ok(val.to_ne_bytes().to_vec()) + } + 4 => { + let val = i.to_i32().unwrap_or(0); + Ok(val.to_ne_bytes().to_vec()) + } + 8 => { + let val = i.to_i64().unwrap_or(0); + Ok(val.to_ne_bytes().to_vec()) + } + _ => Ok(vec![0u8; size]), + } + } else { + Ok(vec![0u8; size]) + } + } +} + +#[pyclass( + flags(BASETYPE, IMMUTABLETYPE), + with(Constructor, Representable, GetDescriptor) +)] +impl PyCField { + #[pyslot] + fn descr_set( + zelf: &crate::PyObject, + obj: PyObjectRef, + value: PySetterValue, + vm: &VirtualMachine, + ) -> PyResult<()> { + let zelf = zelf + .downcast_ref::() + .ok_or_else(|| vm.new_type_error("expected CField".to_owned()))?; + + // Get the structure instance - use payload() to access the struct data + if let Some(structure) = obj.payload::() { + match value { + PySetterValue::Assign(value) => { + let offset = zelf.byte_offset; + let size = zelf.byte_size; + let bytes = PyCField::value_to_bytes(&value, size, vm)?; + + let mut buffer = structure.buffer.write(); + if offset + size <= buffer.len() { + buffer[offset..offset + size].copy_from_slice(&bytes); + } + Ok(()) + } + PySetterValue::Delete => { + Err(vm.new_type_error("cannot delete structure field".to_owned())) + } + } + } else { + Err(vm.new_type_error(format!( + "descriptor works only on Structure instances, got {}", + obj.class().name() + ))) + } + } + + #[pymethod] + fn __set__( + zelf: PyObjectRef, + obj: PyObjectRef, + value: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult<()> { + Self::descr_set(&zelf, obj, PySetterValue::Assign(value), vm) + } + + #[pymethod] + fn __delete__(zelf: PyObjectRef, obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + Self::descr_set(&zelf, obj, PySetterValue::Delete, vm) + } + #[pygetset] fn size(&self) -> usize { self.byte_size @@ -99,7 +268,7 @@ impl PyCField { } #[pygetset(name = "type")] - fn type_(&self) -> PyTypeRef { + fn type_(&self) -> PyObjectRef { self.proto.clone() } diff --git a/crates/vm/src/stdlib/ctypes/structure.rs b/crates/vm/src/stdlib/ctypes/structure.rs index aef95fe5619..f3d27d907ab 100644 --- a/crates/vm/src/stdlib/ctypes/structure.rs +++ b/crates/vm/src/stdlib/ctypes/structure.rs @@ -1,60 +1,362 @@ use super::base::PyCData; -use crate::builtins::{PyList, PyStr, PyTuple, PyTypeRef}; +use super::field::PyCField; +use crate::builtins::{PyList, PyStr, PyTuple, PyType, PyTypeRef}; +use crate::convert::ToPyObject; use crate::function::FuncArgs; -use crate::types::GetAttr; -use crate::{AsObject, Py, PyObjectRef, PyPayload, PyResult, VirtualMachine}; +use crate::protocol::PyNumberMethods; +use crate::stdlib::ctypes::_ctypes::get_size; +use crate::types::{AsNumber, Constructor}; +use crate::{AsObject, PyObjectRef, PyPayload, PyResult, VirtualMachine}; +use crossbeam_utils::atomic::AtomicCell; +use num_traits::ToPrimitive; use rustpython_common::lock::PyRwLock; -use rustpython_vm::types::Constructor; use std::collections::HashMap; use std::fmt::Debug; -#[pyclass(module = "_ctypes", name = "Structure", base = PyCData)] -#[derive(PyPayload, Debug)] -pub struct PyCStructure { - #[allow(dead_code)] - field_data: PyRwLock>, - data: PyRwLock>, -} +/// PyCStructType - metaclass for Structure +#[pyclass(name = "PyCStructType", base = PyType, module = "_ctypes")] +#[derive(Debug, PyPayload)] +pub struct PyCStructType {} -impl Constructor for PyCStructure { +impl Constructor for PyCStructType { type Args = FuncArgs; - fn py_new(cls: PyTypeRef, _args: Self::Args, vm: &VirtualMachine) -> PyResult { - let fields_attr = cls - .get_class_attr(vm.ctx.interned_str("_fields_").unwrap()) - .ok_or_else(|| vm.new_attribute_error("Structure must have a _fields_ attribute"))?; - // downcast into list - let fields = fields_attr - .downcast_ref::() - .ok_or_else(|| vm.new_type_error("Structure _fields_ attribute must be a list"))?; - let fields = fields.borrow_vec(); - let mut field_data = HashMap::new(); - for field in fields.iter() { - let field = field + fn py_new(metatype: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { + // 1. Create the new class using PyType::py_new + let new_class = crate::builtins::type_::PyType::py_new(metatype, args, vm)?; + + // 2. Process _fields_ if defined on the new class + let new_type = new_class + .clone() + .downcast::() + .map_err(|_| vm.new_type_error("expected type"))?; + + // Only process _fields_ if defined directly on this class (not inherited) + if let Some(fields_attr) = new_type.get_direct_attr(vm.ctx.intern_str("_fields_")) { + Self::process_fields(&new_type, fields_attr, vm)?; + } + + Ok(new_class) + } +} + +#[pyclass(flags(BASETYPE), with(AsNumber, Constructor))] +impl PyCStructType { + /// Called when a new Structure subclass is created + #[pyclassmethod] + fn __init_subclass__(cls: PyTypeRef, vm: &VirtualMachine) -> PyResult<()> { + // Check if _fields_ is defined + if let Some(fields_attr) = cls.get_direct_attr(vm.ctx.intern_str("_fields_")) { + Self::process_fields(&cls, fields_attr, vm)?; + } + Ok(()) + } + + /// Process _fields_ and create CField descriptors + fn process_fields( + cls: &PyTypeRef, + fields_attr: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult<()> { + // Try to downcast to list or tuple + let fields: Vec = if let Some(list) = fields_attr.downcast_ref::() { + list.borrow_vec().to_vec() + } else if let Some(tuple) = fields_attr.downcast_ref::() { + tuple.to_vec() + } else { + return Err(vm.new_type_error("_fields_ must be a list or tuple".to_string())); + }; + + let mut offset = 0usize; + for (index, field) in fields.iter().enumerate() { + let field_tuple = field .downcast_ref::() - .ok_or_else(|| vm.new_type_error("Field must be a tuple"))?; - let name = field + .ok_or_else(|| vm.new_type_error("_fields_ must contain tuples".to_string()))?; + + if field_tuple.len() < 2 { + return Err(vm.new_type_error( + "_fields_ tuple must have at least 2 elements (name, type)".to_string(), + )); + } + + let name = field_tuple .first() .unwrap() .downcast_ref::() - .ok_or_else(|| vm.new_type_error("Field name must be a string"))?; - let typ = field.get(1).unwrap().clone(); - field_data.insert(name.to_string(), typ); + .ok_or_else(|| vm.new_type_error("field name must be a string".to_string()))? + .to_string(); + + let field_type = field_tuple.get(1).unwrap().clone(); + + // Get size of the field type + let size = Self::get_field_size(&field_type, vm)?; + + // Create CField descriptor (accepts any ctypes type including arrays) + let cfield = PyCField::new(name.clone(), field_type, offset, size, index); + + // Set the CField as a class attribute + cls.set_attr(vm.ctx.intern_str(name), cfield.to_pyobject(vm)); + + offset += size; + } + + Ok(()) + } + + /// Get the size of a ctypes type + fn get_field_size(field_type: &PyObjectRef, vm: &VirtualMachine) -> PyResult { + // Try to get _type_ attribute for simple types + if let Some(size) = field_type + .get_attr("_type_", vm) + .ok() + .and_then(|type_attr| type_attr.str(vm).ok()) + .and_then(|type_str| { + let s = type_str.to_string(); + (s.len() == 1).then(|| get_size(&s)) + }) + { + return Ok(size); + } + + // Try sizeof for other types + if let Some(s) = field_type + .get_attr("size_of_instances", vm) + .ok() + .and_then(|size_method| size_method.call((), vm).ok()) + .and_then(|size| size.try_int(vm).ok()) + .and_then(|n| n.as_bigint().to_usize()) + { + return Ok(s); + } + + // Default to pointer size for unknown types + Ok(std::mem::size_of::()) + } + + #[pymethod] + fn __mul__(cls: PyTypeRef, n: isize, vm: &VirtualMachine) -> PyResult { + use super::array::{PyCArray, PyCArrayType}; + if n < 0 { + return Err(vm.new_value_error(format!("Array length must be >= 0, not {n}"))); + } + // For structures, element size is the structure size (sum of field sizes) + let element_size = std::mem::size_of::(); // Default, should calculate from fields + Ok(PyCArrayType { + inner: PyCArray { + typ: PyRwLock::new(cls), + length: AtomicCell::new(n as usize), + element_size: AtomicCell::new(element_size), + buffer: PyRwLock::new(vec![]), + }, + } + .to_pyobject(vm)) + } +} + +impl AsNumber for PyCStructType { + fn as_number() -> &'static PyNumberMethods { + static AS_NUMBER: PyNumberMethods = PyNumberMethods { + multiply: Some(|a, b, vm| { + let cls = a + .downcast_ref::() + .ok_or_else(|| vm.new_type_error("expected type".to_owned()))?; + let n = b + .try_index(vm)? + .as_bigint() + .to_isize() + .ok_or_else(|| vm.new_overflow_error("array size too large".to_owned()))?; + PyCStructType::__mul__(cls.to_owned(), n, vm) + }), + ..PyNumberMethods::NOT_IMPLEMENTED + }; + &AS_NUMBER + } +} + +/// Structure field info stored in instance +#[derive(Debug, Clone)] +pub struct FieldInfo { + pub name: String, + pub offset: usize, + pub size: usize, + pub type_ref: PyTypeRef, +} + +/// PyCStructure - base class for Structure instances +#[pyclass( + module = "_ctypes", + name = "Structure", + base = PyCData, + metaclass = "PyCStructType" +)] +#[derive(PyPayload)] +pub struct PyCStructure { + /// Raw memory buffer for the structure + pub(super) buffer: PyRwLock>, + /// Field information (name -> FieldInfo) + pub(super) fields: PyRwLock>, + /// Total size of the structure + pub(super) size: AtomicCell, +} + +impl Debug for PyCStructure { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("PyCStructure") + .field("size", &self.size.load()) + .finish() + } +} + +impl Constructor for PyCStructure { + type Args = FuncArgs; + + fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { + // Get _fields_ from the class using get_attr to properly search MRO + let fields_attr = cls.as_object().get_attr("_fields_", vm).ok(); + + let mut fields_map = HashMap::new(); + let mut total_size = 0usize; + + if let Some(fields_attr) = fields_attr { + let fields: Vec = if let Some(list) = fields_attr.downcast_ref::() + { + list.borrow_vec().to_vec() + } else if let Some(tuple) = fields_attr.downcast_ref::() { + tuple.to_vec() + } else { + vec![] + }; + + let mut offset = 0usize; + for field in fields.iter() { + let Some(field_tuple) = field.downcast_ref::() else { + continue; + }; + if field_tuple.len() < 2 { + continue; + } + let Some(name) = field_tuple.first().unwrap().downcast_ref::() else { + continue; + }; + let name = name.to_string(); + let field_type = field_tuple.get(1).unwrap().clone(); + let size = PyCStructType::get_field_size(&field_type, vm)?; + + let type_ref = field_type + .downcast::() + .unwrap_or_else(|_| vm.ctx.types.object_type.to_owned()); + + fields_map.insert( + name.clone(), + FieldInfo { + name, + offset, + size, + type_ref, + }, + ); + + offset += size; + } + total_size = offset; } - todo!("Implement PyCStructure::py_new") + + // Initialize buffer with zeros + let buffer = vec![0u8; total_size]; + + let instance = PyCStructure { + buffer: PyRwLock::new(buffer), + fields: PyRwLock::new(fields_map.clone()), + size: AtomicCell::new(total_size), + }; + + // Handle keyword arguments for field initialization + let py_instance = instance.into_ref_with_type(vm, cls.clone())?; + let py_obj: PyObjectRef = py_instance.clone().into(); + + // Set field values from kwargs using standard attribute setting + for (key, value) in args.kwargs.iter() { + if fields_map.contains_key(key.as_str()) { + py_obj.set_attr(vm.ctx.intern_str(key.as_str()), value.clone(), vm)?; + } + } + + // Set field values from positional args + let field_names: Vec = fields_map.keys().cloned().collect(); + for (i, value) in args.args.iter().enumerate() { + if i < field_names.len() { + py_obj.set_attr( + vm.ctx.intern_str(field_names[i].as_str()), + value.clone(), + vm, + )?; + } + } + + Ok(py_instance.into()) } } -impl GetAttr for PyCStructure { - fn getattro(zelf: &Py, name: &Py, vm: &VirtualMachine) -> PyResult { - let name = name.to_string(); - let data = zelf.data.read(); - match data.get(&name) { - Some(value) => Ok(value.clone()), - None => Err(vm.new_attribute_error(format!("No attribute named {name}"))), +// Note: GetAttr and SetAttr are not implemented here. +// Field access is handled by CField descriptors registered on the class. + +impl PyCStructure { + /// Convert bytes to a Python value + fn bytes_to_value(bytes: &[u8], _type_ref: &PyTypeRef, vm: &VirtualMachine) -> PyResult { + match bytes.len() { + 1 => Ok(vm.ctx.new_int(bytes[0] as i8).into()), + 2 => { + let val = i16::from_ne_bytes([bytes[0], bytes[1]]); + Ok(vm.ctx.new_int(val).into()) + } + 4 => { + let val = i32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]); + Ok(vm.ctx.new_int(val).into()) + } + 8 => { + let val = i64::from_ne_bytes([ + bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], + ]); + Ok(vm.ctx.new_int(val).into()) + } + _ => Ok(vm.ctx.new_int(0).into()), + } + } + + /// Convert a Python value to bytes + fn value_to_bytes(value: &PyObjectRef, size: usize, vm: &VirtualMachine) -> PyResult> { + if let Ok(int_val) = value.try_int(vm) { + let i = int_val.as_bigint(); + match size { + 1 => { + let val = i.to_i8().unwrap_or(0); + Ok(val.to_ne_bytes().to_vec()) + } + 2 => { + let val = i.to_i16().unwrap_or(0); + Ok(val.to_ne_bytes().to_vec()) + } + 4 => { + let val = i.to_i32().unwrap_or(0); + Ok(val.to_ne_bytes().to_vec()) + } + 8 => { + let val = i.to_i64().unwrap_or(0); + Ok(val.to_ne_bytes().to_vec()) + } + _ => Ok(vec![0u8; size]), + } + } else { + Ok(vec![0u8; size]) } } } -#[pyclass(flags(BASETYPE, IMMUTABLETYPE))] -impl PyCStructure {} +#[pyclass(flags(BASETYPE, IMMUTABLETYPE), with(Constructor))] +impl PyCStructure { + #[pygetset] + fn _fields_(&self, vm: &VirtualMachine) -> PyObjectRef { + // Return the _fields_ from the class, not instance + vm.ctx.none() + } +} From 0a39899aad9a2063a0432c500666a1de352e0026 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sat, 29 Nov 2025 10:12:48 +0900 Subject: [PATCH 2/5] impl union --- crates/vm/src/stdlib/ctypes.rs | 1 + crates/vm/src/stdlib/ctypes/union.rs | 116 ++++++++++++++++++++++++++- 2 files changed, 115 insertions(+), 2 deletions(-) diff --git a/crates/vm/src/stdlib/ctypes.rs b/crates/vm/src/stdlib/ctypes.rs index 1664f712e82..9d32cc2a80f 100644 --- a/crates/vm/src/stdlib/ctypes.rs +++ b/crates/vm/src/stdlib/ctypes.rs @@ -22,6 +22,7 @@ pub fn extend_module_nodes(vm: &VirtualMachine, module: &Py) { field::PyCFieldType::make_class(ctx); pointer::PyCPointerType::make_class(ctx); structure::PyCStructType::make_class(ctx); + union::PyCUnionType::make_class(ctx); extend_module!(vm, module, { "_CData" => PyCData::make_class(ctx), "_SimpleCData" => PyCSimple::make_class(ctx), diff --git a/crates/vm/src/stdlib/ctypes/union.rs b/crates/vm/src/stdlib/ctypes/union.rs index 93a53b2b6db..a357c195d69 100644 --- a/crates/vm/src/stdlib/ctypes/union.rs +++ b/crates/vm/src/stdlib/ctypes/union.rs @@ -1,7 +1,119 @@ use super::base::PyCData; +use super::field::PyCField; +use crate::builtins::{PyList, PyStr, PyTuple, PyType, PyTypeRef}; +use crate::convert::ToPyObject; +use crate::function::FuncArgs; +use crate::stdlib::ctypes::_ctypes::get_size; +use crate::types::Constructor; +use crate::{PyObjectRef, PyPayload, PyResult, VirtualMachine}; +use num_traits::ToPrimitive; -// TODO: metaclass = "UnionType" -#[pyclass(module = "_ctypes", name = "Union", base = PyCData)] +/// PyCUnionType - metaclass for Union +#[pyclass(name = "UnionType", base = PyType, module = "_ctypes")] +#[derive(Debug, PyPayload)] +pub struct PyCUnionType {} + +impl Constructor for PyCUnionType { + type Args = FuncArgs; + + fn py_new(metatype: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { + // 1. Create the new class using PyType::py_new + let new_class = crate::builtins::type_::PyType::py_new(metatype, args, vm)?; + + // 2. Process _fields_ if defined on the new class + let new_type = new_class + .clone() + .downcast::() + .map_err(|_| vm.new_type_error("expected type"))?; + + // Only process _fields_ if defined directly on this class (not inherited) + if let Some(fields_attr) = new_type.get_direct_attr(vm.ctx.intern_str("_fields_")) { + Self::process_fields(&new_type, fields_attr, vm)?; + } + + Ok(new_class) + } +} + +impl PyCUnionType { + /// Process _fields_ and create CField descriptors + /// For Union, all fields start at offset 0 + fn process_fields( + cls: &PyTypeRef, + fields_attr: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult<()> { + let fields: Vec = if let Some(list) = fields_attr.downcast_ref::() { + list.borrow_vec().to_vec() + } else if let Some(tuple) = fields_attr.downcast_ref::() { + tuple.to_vec() + } else { + return Err(vm.new_type_error("_fields_ must be a list or tuple".to_string())); + }; + + for (index, field) in fields.iter().enumerate() { + let field_tuple = field + .downcast_ref::() + .ok_or_else(|| vm.new_type_error("_fields_ must contain tuples".to_string()))?; + + if field_tuple.len() < 2 { + return Err(vm.new_type_error( + "_fields_ tuple must have at least 2 elements (name, type)".to_string(), + )); + } + + let name = field_tuple + .first() + .unwrap() + .downcast_ref::() + .ok_or_else(|| vm.new_type_error("field name must be a string".to_string()))? + .to_string(); + + let field_type = field_tuple.get(1).unwrap().clone(); + let size = Self::get_field_size(&field_type, vm)?; + + // For Union, all fields start at offset 0 + // Create CField descriptor (accepts any ctypes type including arrays) + let cfield = PyCField::new(name.clone(), field_type, 0, size, index); + + cls.set_attr(vm.ctx.intern_str(name), cfield.to_pyobject(vm)); + } + + Ok(()) + } + + fn get_field_size(field_type: &PyObjectRef, vm: &VirtualMachine) -> PyResult { + if let Some(size) = field_type + .get_attr("_type_", vm) + .ok() + .and_then(|type_attr| type_attr.str(vm).ok()) + .and_then(|type_str| { + let s = type_str.to_string(); + (s.len() == 1).then(|| get_size(&s)) + }) + { + return Ok(size); + } + + if let Some(s) = field_type + .get_attr("size_of_instances", vm) + .ok() + .and_then(|size_method| size_method.call((), vm).ok()) + .and_then(|size| size.try_int(vm).ok()) + .and_then(|n| n.as_bigint().to_usize()) + { + return Ok(s); + } + + Ok(std::mem::size_of::()) + } +} + +#[pyclass(flags(BASETYPE), with(Constructor))] +impl PyCUnionType {} + +/// PyCUnion - base class for Union +#[pyclass(module = "_ctypes", name = "Union", base = PyCData, metaclass = "PyCUnionType")] pub struct PyCUnion {} #[pyclass(flags(BASETYPE, IMMUTABLETYPE))] From 2585c5e86bf832f037c23957686e47c8662c94f0 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sat, 29 Nov 2025 10:13:13 +0900 Subject: [PATCH 3/5] array --- crates/vm/src/stdlib/ctypes/array.rs | 311 ++++++++++++++++++++++--- crates/vm/src/stdlib/ctypes/base.rs | 19 +- crates/vm/src/stdlib/ctypes/field.rs | 6 +- crates/vm/src/stdlib/ctypes/pointer.rs | 5 +- 4 files changed, 305 insertions(+), 36 deletions(-) diff --git a/crates/vm/src/stdlib/ctypes/array.rs b/crates/vm/src/stdlib/ctypes/array.rs index bbc65530b9f..f0dbcc84236 100644 --- a/crates/vm/src/stdlib/ctypes/array.rs +++ b/crates/vm/src/stdlib/ctypes/array.rs @@ -1,8 +1,10 @@ -use crate::builtins::PyBytes; +use crate::atomic_func; +use crate::builtins::{PyBytes, PyInt}; use crate::convert::ToPyObject; -use crate::protocol::PyNumberMethods; -use crate::types::{AsNumber, Callable}; -use crate::{Py, PyObjectRef, PyPayload}; +use crate::function::FuncArgs; +use crate::protocol::{PyNumberMethods, PySequenceMethods}; +use crate::types::{AsNumber, AsSequence, Callable}; +use crate::{AsObject, Py, PyObjectRef, PyPayload}; use crate::{ PyResult, VirtualMachine, builtins::{PyType, PyTypeRef}, @@ -11,6 +13,7 @@ use crate::{ use crossbeam_utils::atomic::AtomicCell; use num_traits::ToPrimitive; use rustpython_common::lock::PyRwLock; +use rustpython_vm::stdlib::ctypes::_ctypes::get_size; use rustpython_vm::stdlib::ctypes::base::PyCData; #[pyclass(name = "PyCArrayType", base = PyType, module = "_ctypes")] @@ -28,12 +31,34 @@ impl std::fmt::Debug for PyCArrayType { } impl Callable for PyCArrayType { - type Args = (); - fn call(zelf: &Py, _args: Self::Args, vm: &VirtualMachine) -> PyResult { + type Args = FuncArgs; + fn call(zelf: &Py, args: Self::Args, vm: &VirtualMachine) -> PyResult { + // Create an instance of the array + let element_type = zelf.inner.typ.read().clone(); + let length = zelf.inner.length.load(); + let element_size = zelf.inner.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); + } + } + } + Ok(PyCArray { - typ: PyRwLock::new(zelf.inner.typ.read().clone()), - length: AtomicCell::new(zelf.inner.length.load()), - value: PyRwLock::new(zelf.inner.value.read().clone()), + typ: PyRwLock::new(element_type), + length: AtomicCell::new(length), + element_size: AtomicCell::new(element_size), + buffer: PyRwLock::new(buffer), } .into_pyobject(vm)) } @@ -49,22 +74,42 @@ impl Constructor for PyCArrayType { #[pyclass(flags(IMMUTABLETYPE), with(Callable, Constructor, AsNumber))] impl PyCArrayType { + #[pygetset(name = "_type_")] + fn typ(&self) -> PyTypeRef { + self.inner.typ.read().clone() + } + + #[pygetset(name = "_length_")] + fn length(&self) -> usize { + self.inner.length.load() + } + #[pymethod] fn __mul__(zelf: &Py, 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's element type is the current array type - let inner_type = zelf.inner.typ.read().clone(); + // 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.inner.length.load(); + let inner_element_size = zelf.inner.element_size.load(); + + // The element type of the new array is the current array type itself + let obj_ref: PyObjectRef = zelf.to_owned().into(); + let current_array_type = obj_ref + .downcast::() + .expect("PyCArrayType should be a PyType"); + + // Element size is the total size of the inner array + let new_element_size = inner_length * inner_element_size; - // Create a new array type where the element is the current array Ok(PyCArrayType { inner: PyCArray { - typ: PyRwLock::new(inner_type), - length: AtomicCell::new(inner_length * n as usize), - value: PyRwLock::new(vm.ctx.none()), + typ: PyRwLock::new(current_array_type), + length: AtomicCell::new(n as usize), + element_size: AtomicCell::new(new_element_size), + buffer: PyRwLock::new(vec![]), }, } .to_pyobject(vm)) @@ -101,7 +146,8 @@ impl AsNumber for PyCArrayType { pub struct PyCArray { pub(super) typ: PyRwLock, pub(super) length: AtomicCell, - pub(super) value: PyRwLock, + pub(super) element_size: AtomicCell, + pub(super) buffer: PyRwLock>, } impl std::fmt::Debug for PyCArray { @@ -114,48 +160,251 @@ impl std::fmt::Debug for PyCArray { } impl Constructor for PyCArray { - type Args = (PyTypeRef, usize); + type Args = FuncArgs; fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { - Self { - typ: PyRwLock::new(args.0), - length: AtomicCell::new(args.1), - value: PyRwLock::new(vm.ctx.none()), + // Get _type_ and _length_ from the class + let type_attr = cls.as_object().get_attr("_type_", vm).ok(); + let length_attr = cls.as_object().get_attr("_length_", vm).ok(); + + let element_type = type_attr.unwrap_or_else(|| vm.ctx.types.object_type.to_owned().into()); + let length = if let Some(len_obj) = length_attr { + len_obj.try_int(vm)?.as_bigint().to_usize().unwrap_or(0) + } else { + 0 + }; + + // Get element size from _type_ + let element_size = if let Ok(type_code) = element_type.get_attr("_type_", vm) { + if let Ok(s) = type_code.str(vm) { + let s = s.to_string(); + if s.len() == 1 { + get_size(&s) + } else { + std::mem::size_of::() + } + } else { + std::mem::size_of::() + } + } else { + std::mem::size_of::() + }; + + 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 = Self::int_to_bytes(int_val.as_bigint(), element_size); + if offset + element_size <= buffer.len() { + buffer[offset..offset + element_size].copy_from_slice(&bytes); + } + } + } + + let element_type_ref = element_type + .downcast::() + .unwrap_or_else(|_| vm.ctx.types.object_type.to_owned()); + + PyCArray { + typ: PyRwLock::new(element_type_ref), + length: AtomicCell::new(length), + element_size: AtomicCell::new(element_size), + buffer: PyRwLock::new(buffer), } .into_ref_with_type(vm, cls) .map(Into::into) } } -#[pyclass(flags(BASETYPE, IMMUTABLETYPE), with(Constructor))] +impl AsSequence for PyCArray { + fn as_sequence() -> &'static PySequenceMethods { + use std::sync::LazyLock; + static AS_SEQUENCE: LazyLock = LazyLock::new(|| PySequenceMethods { + length: atomic_func!(|seq, _vm| Ok(PyCArray::sequence_downcast(seq).length.load())), + item: atomic_func!(|seq, i, vm| { + PyCArray::getitem_by_index(PyCArray::sequence_downcast(seq), i, vm) + }), + ass_item: atomic_func!(|seq, i, value, vm| { + let zelf = PyCArray::sequence_downcast(seq); + match value { + Some(v) => PyCArray::setitem_by_index(zelf, i, v, vm), + None => Err(vm.new_type_error("cannot delete array elements".to_owned())), + } + }), + ..PySequenceMethods::NOT_IMPLEMENTED + }); + &AS_SEQUENCE + } +} + +#[pyclass(flags(BASETYPE, IMMUTABLETYPE), with(Constructor, AsSequence))] impl PyCArray { + fn int_to_bytes(i: &malachite_bigint::BigInt, size: usize) -> Vec { + match size { + 1 => vec![i.to_i8().unwrap_or(0) as u8], + 2 => i.to_i16().unwrap_or(0).to_ne_bytes().to_vec(), + 4 => i.to_i32().unwrap_or(0).to_ne_bytes().to_vec(), + 8 => i.to_i64().unwrap_or(0).to_ne_bytes().to_vec(), + _ => vec![0u8; size], + } + } + + fn bytes_to_int(bytes: &[u8], size: usize, vm: &VirtualMachine) -> PyObjectRef { + match size { + 1 => vm.ctx.new_int(bytes[0] as i8).into(), + 2 => { + let val = i16::from_ne_bytes([bytes[0], bytes[1]]); + vm.ctx.new_int(val).into() + } + 4 => { + let val = i32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]); + vm.ctx.new_int(val).into() + } + 8 => { + let val = i64::from_ne_bytes([ + bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], + ]); + vm.ctx.new_int(val).into() + } + _ => vm.ctx.new_int(0).into(), + } + } + + fn getitem_by_index(zelf: &PyCArray, i: isize, vm: &VirtualMachine) -> PyResult { + let length = zelf.length.load() as isize; + let index = if i < 0 { length + i } else { i }; + if index < 0 || index >= length { + return Err(vm.new_index_error("array index out of range".to_owned())); + } + let index = index as usize; + let element_size = zelf.element_size.load(); + let offset = index * element_size; + let buffer = zelf.buffer.read(); + if offset + element_size <= buffer.len() { + let bytes = &buffer[offset..offset + element_size]; + Ok(Self::bytes_to_int(bytes, element_size, vm)) + } else { + Ok(vm.ctx.new_int(0).into()) + } + } + + fn setitem_by_index( + zelf: &PyCArray, + i: isize, + value: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult<()> { + let length = zelf.length.load() as isize; + let index = if i < 0 { length + i } else { i }; + if index < 0 || index >= length { + return Err(vm.new_index_error("array index out of range".to_owned())); + } + let index = index as usize; + let element_size = zelf.element_size.load(); + let offset = index * element_size; + + let int_val = value.try_int(vm)?; + let bytes = Self::int_to_bytes(int_val.as_bigint(), element_size); + + let mut buffer = zelf.buffer.write(); + if offset + element_size <= buffer.len() { + buffer[offset..offset + element_size].copy_from_slice(&bytes); + } + Ok(()) + } + + #[pymethod] + fn __getitem__(&self, index: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if let Some(i) = index.downcast_ref::() { + let i = i.as_bigint().to_isize().ok_or_else(|| { + vm.new_index_error("cannot fit index into an index-sized integer".to_owned()) + })?; + Self::getitem_by_index(self, i, vm) + } else { + Err(vm.new_type_error("array indices must be integers".to_owned())) + } + } + + #[pymethod] + fn __setitem__( + &self, + index: PyObjectRef, + value: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult<()> { + if let Some(i) = index.downcast_ref::() { + let i = i.as_bigint().to_isize().ok_or_else(|| { + vm.new_index_error("cannot fit index into an index-sized integer".to_owned()) + })?; + Self::setitem_by_index(self, i, value, vm) + } else { + Err(vm.new_type_error("array indices must be integers".to_owned())) + } + } + + #[pymethod] + fn __len__(&self) -> usize { + self.length.load() + } + #[pygetset(name = "_type_")] fn typ(&self) -> PyTypeRef { self.typ.read().clone() } #[pygetset(name = "_length_")] - fn length(&self) -> usize { + fn length_getter(&self) -> usize { self.length.load() } #[pygetset] - fn value(&self) -> PyObjectRef { - self.value.read().clone() + fn value(&self, vm: &VirtualMachine) -> PyObjectRef { + // Return bytes representation of the buffer + let buffer = self.buffer.read(); + vm.ctx.new_bytes(buffer.clone()).into() } #[pygetset(setter)] - fn set_value(&self, value: PyObjectRef) { - *self.value.write() = value; + fn set_value(&self, value: PyObjectRef, _vm: &VirtualMachine) -> PyResult<()> { + if let Some(bytes) = value.downcast_ref::() { + let mut buffer = self.buffer.write(); + let src = bytes.as_bytes(); + let len = std::cmp::min(src.len(), buffer.len()); + buffer[..len].copy_from_slice(&src[..len]); + } + Ok(()) + } + + #[pygetset] + fn raw(&self, vm: &VirtualMachine) -> PyObjectRef { + let buffer = self.buffer.read(); + vm.ctx.new_bytes(buffer.clone()).into() + } + + #[pygetset(setter)] + fn set_raw(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + if let Some(bytes) = value.downcast_ref::() { + let mut buffer = self.buffer.write(); + let src = bytes.as_bytes(); + let len = std::cmp::min(src.len(), buffer.len()); + buffer[..len].copy_from_slice(&src[..len]); + Ok(()) + } else { + Err(vm.new_type_error("expected bytes".to_owned())) + } } } impl PyCArray { #[allow(unused)] pub fn to_arg(&self, _vm: &VirtualMachine) -> PyResult { - let value = self.value.read(); - let py_bytes = value.downcast_ref::().unwrap(); - let bytes = py_bytes.payload().to_vec(); - Ok(libffi::middle::Arg::new(&bytes)) + let buffer = self.buffer.read(); + Ok(libffi::middle::Arg::new(&buffer.clone())) } } diff --git a/crates/vm/src/stdlib/ctypes/base.rs b/crates/vm/src/stdlib/ctypes/base.rs index 6fdb79e11e9..73d97a2593d 100644 --- a/crates/vm/src/stdlib/ctypes/base.rs +++ b/crates/vm/src/stdlib/ctypes/base.rs @@ -303,14 +303,31 @@ impl PyCSimple { #[pyclassmethod] fn repeat(cls: PyTypeRef, n: isize, vm: &VirtualMachine) -> PyResult { + use super::_ctypes::get_size; if n < 0 { return Err(vm.new_value_error(format!("Array length must be >= 0, not {n}"))); } + // Get element size from cls + let element_size = if let Ok(type_attr) = cls.as_object().get_attr("_type_", vm) { + if let Ok(s) = type_attr.str(vm) { + let s = s.to_string(); + if s.len() == 1 { + get_size(&s) + } else { + std::mem::size_of::() + } + } else { + std::mem::size_of::() + } + } else { + std::mem::size_of::() + }; Ok(PyCArrayType { inner: PyCArray { typ: PyRwLock::new(cls), length: AtomicCell::new(n as usize), - value: PyRwLock::new(vm.ctx.none()), + element_size: AtomicCell::new(element_size), + buffer: PyRwLock::new(vec![]), }, } .to_pyobject(vm)) diff --git a/crates/vm/src/stdlib/ctypes/field.rs b/crates/vm/src/stdlib/ctypes/field.rs index 939cddee97c..5b4837cf6e3 100644 --- a/crates/vm/src/stdlib/ctypes/field.rs +++ b/crates/vm/src/stdlib/ctypes/field.rs @@ -117,7 +117,7 @@ impl GetDescriptor for PyCField { }; // Instance attribute access - read value from the structure's buffer - if let Some(structure) = obj.payload::() { + if let Some(structure) = obj.downcast_ref::() { let buffer = structure.buffer.read(); let offset = zelf.byte_offset; let size = zelf.byte_size; @@ -201,8 +201,8 @@ impl PyCField { .downcast_ref::() .ok_or_else(|| vm.new_type_error("expected CField".to_owned()))?; - // Get the structure instance - use payload() to access the struct data - if let Some(structure) = obj.payload::() { + // Get the structure instance - use downcast_ref() to access the struct data + if let Some(structure) = obj.downcast_ref::() { match value { PySetterValue::Assign(value) => { let offset = zelf.byte_offset; diff --git a/crates/vm/src/stdlib/ctypes/pointer.rs b/crates/vm/src/stdlib/ctypes/pointer.rs index 3eb6f68e6dd..16d795ea4da 100644 --- a/crates/vm/src/stdlib/ctypes/pointer.rs +++ b/crates/vm/src/stdlib/ctypes/pointer.rs @@ -24,11 +24,14 @@ impl PyCPointerType { 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::(); Ok(PyCArrayType { inner: PyCArray { typ: PyRwLock::new(cls), length: AtomicCell::new(n as usize), - value: PyRwLock::new(vm.ctx.none()), + element_size: AtomicCell::new(element_size), + buffer: PyRwLock::new(vec![]), }, } .to_pyobject(vm)) From efebed6810722d15dec0edef5189607a80436acd Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sat, 29 Nov 2025 10:55:33 +0900 Subject: [PATCH 4/5] temp remove --- crates/vm/src/stdlib/ctypes.rs | 1 + crates/vm/src/stdlib/ctypes/structure.rs | 54 +----------------------- 2 files changed, 3 insertions(+), 52 deletions(-) diff --git a/crates/vm/src/stdlib/ctypes.rs b/crates/vm/src/stdlib/ctypes.rs index 9d32cc2a80f..493d0530fcb 100644 --- a/crates/vm/src/stdlib/ctypes.rs +++ b/crates/vm/src/stdlib/ctypes.rs @@ -65,6 +65,7 @@ pub(crate) mod _ctypes { #[derive(Debug, PyPayload)] pub struct CArgObject { pub obj: PyObjectRef, + #[allow(dead_code)] pub offset: isize, } diff --git a/crates/vm/src/stdlib/ctypes/structure.rs b/crates/vm/src/stdlib/ctypes/structure.rs index f3d27d907ab..24635d25c30 100644 --- a/crates/vm/src/stdlib/ctypes/structure.rs +++ b/crates/vm/src/stdlib/ctypes/structure.rs @@ -174,6 +174,7 @@ impl AsNumber for PyCStructType { } /// Structure field info stored in instance +#[allow(dead_code)] #[derive(Debug, Clone)] pub struct FieldInfo { pub name: String, @@ -194,6 +195,7 @@ pub struct PyCStructure { /// Raw memory buffer for the structure pub(super) buffer: PyRwLock>, /// Field information (name -> FieldInfo) + #[allow(dead_code)] pub(super) fields: PyRwLock>, /// Total size of the structure pub(super) size: AtomicCell, @@ -300,58 +302,6 @@ impl Constructor for PyCStructure { // Note: GetAttr and SetAttr are not implemented here. // Field access is handled by CField descriptors registered on the class. -impl PyCStructure { - /// Convert bytes to a Python value - fn bytes_to_value(bytes: &[u8], _type_ref: &PyTypeRef, vm: &VirtualMachine) -> PyResult { - match bytes.len() { - 1 => Ok(vm.ctx.new_int(bytes[0] as i8).into()), - 2 => { - let val = i16::from_ne_bytes([bytes[0], bytes[1]]); - Ok(vm.ctx.new_int(val).into()) - } - 4 => { - let val = i32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]); - Ok(vm.ctx.new_int(val).into()) - } - 8 => { - let val = i64::from_ne_bytes([ - bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], - ]); - Ok(vm.ctx.new_int(val).into()) - } - _ => Ok(vm.ctx.new_int(0).into()), - } - } - - /// Convert a Python value to bytes - fn value_to_bytes(value: &PyObjectRef, size: usize, vm: &VirtualMachine) -> PyResult> { - if let Ok(int_val) = value.try_int(vm) { - let i = int_val.as_bigint(); - match size { - 1 => { - let val = i.to_i8().unwrap_or(0); - Ok(val.to_ne_bytes().to_vec()) - } - 2 => { - let val = i.to_i16().unwrap_or(0); - Ok(val.to_ne_bytes().to_vec()) - } - 4 => { - let val = i.to_i32().unwrap_or(0); - Ok(val.to_ne_bytes().to_vec()) - } - 8 => { - let val = i.to_i64().unwrap_or(0); - Ok(val.to_ne_bytes().to_vec()) - } - _ => Ok(vec![0u8; size]), - } - } else { - Ok(vec![0u8; size]) - } - } -} - #[pyclass(flags(BASETYPE, IMMUTABLETYPE), with(Constructor))] impl PyCStructure { #[pygetset] From 711ea51c9e84fb5c61405c331fa1205c41865248 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sat, 29 Nov 2025 11:31:32 +0900 Subject: [PATCH 5/5] apply review --- crates/vm/src/stdlib/ctypes.rs | 11 ++++++----- crates/vm/src/stdlib/ctypes/array.rs | 5 ++++- crates/vm/src/stdlib/ctypes/structure.rs | 7 ++++--- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/crates/vm/src/stdlib/ctypes.rs b/crates/vm/src/stdlib/ctypes.rs index 493d0530fcb..7e9557458cf 100644 --- a/crates/vm/src/stdlib/ctypes.rs +++ b/crates/vm/src/stdlib/ctypes.rs @@ -350,9 +350,10 @@ pub(crate) mod _ctypes { if !obj.fast_isinstance(PyCData::static_type()) && !obj.fast_isinstance(PyCSimple::static_type()) { - return Err(vm.new_type_error( - "byref() argument must be a ctypes instance, not 'int'".to_string(), - )); + return Err(vm.new_type_error(format!( + "byref() argument must be a ctypes instance, not '{}'", + obj.class().name() + ))); } let offset_val = offset.unwrap_or(0); @@ -428,14 +429,14 @@ pub(crate) mod _ctypes { } #[cfg(target_os = "windows")] { - // On Windows, use wcslen from ucrt + // FIXME: On Windows, use wcslen from ucrt 0 } } #[pyattr] fn _cast_addr(_vm: &VirtualMachine) -> usize { - // TODO: RUSTPYTHON + // todo!("Implement _cast_addr") 0 } } diff --git a/crates/vm/src/stdlib/ctypes/array.rs b/crates/vm/src/stdlib/ctypes/array.rs index f0dbcc84236..73217d7a6a6 100644 --- a/crates/vm/src/stdlib/ctypes/array.rs +++ b/crates/vm/src/stdlib/ctypes/array.rs @@ -404,7 +404,10 @@ impl PyCArray { impl PyCArray { #[allow(unused)] pub fn to_arg(&self, _vm: &VirtualMachine) -> PyResult { + // TODO: This needs a different approach to ensure buffer lifetime + // The buffer must outlive the Arg returned here let buffer = self.buffer.read(); - Ok(libffi::middle::Arg::new(&buffer.clone())) + let ptr = buffer.as_ptr(); + Ok(libffi::middle::Arg::new(&ptr)) } } diff --git a/crates/vm/src/stdlib/ctypes/structure.rs b/crates/vm/src/stdlib/ctypes/structure.rs index 24635d25c30..e7180a48e7b 100644 --- a/crates/vm/src/stdlib/ctypes/structure.rs +++ b/crates/vm/src/stdlib/ctypes/structure.rs @@ -8,9 +8,9 @@ use crate::stdlib::ctypes::_ctypes::get_size; use crate::types::{AsNumber, Constructor}; use crate::{AsObject, PyObjectRef, PyPayload, PyResult, VirtualMachine}; use crossbeam_utils::atomic::AtomicCell; +use indexmap::IndexMap; use num_traits::ToPrimitive; use rustpython_common::lock::PyRwLock; -use std::collections::HashMap; use std::fmt::Debug; /// PyCStructType - metaclass for Structure @@ -139,6 +139,7 @@ impl PyCStructType { if n < 0 { return Err(vm.new_value_error(format!("Array length must be >= 0, not {n}"))); } + // TODO: Calculate element size properly // For structures, element size is the structure size (sum of field sizes) let element_size = std::mem::size_of::(); // Default, should calculate from fields Ok(PyCArrayType { @@ -196,7 +197,7 @@ pub struct PyCStructure { pub(super) buffer: PyRwLock>, /// Field information (name -> FieldInfo) #[allow(dead_code)] - pub(super) fields: PyRwLock>, + pub(super) fields: PyRwLock>, /// Total size of the structure pub(super) size: AtomicCell, } @@ -216,7 +217,7 @@ impl Constructor for PyCStructure { // Get _fields_ from the class using get_attr to properly search MRO let fields_attr = cls.as_object().get_attr("_fields_", vm).ok(); - let mut fields_map = HashMap::new(); + let mut fields_map = IndexMap::new(); let mut total_size = 0usize; if let Some(fields_attr) = fields_attr {