From 792c19f20010ef6f37443c1c8c73f304340c4e10 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sat, 29 Nov 2025 12:45:25 +0900 Subject: [PATCH 1/9] Fix array --- crates/vm/src/stdlib/ctypes/array.rs | 18 ++++++------------ crates/vm/src/stdlib/ctypes/base.rs | 2 +- crates/vm/src/stdlib/ctypes/pointer.rs | 4 ++-- crates/vm/src/stdlib/ctypes/structure.rs | 2 +- 4 files changed, 10 insertions(+), 16 deletions(-) diff --git a/crates/vm/src/stdlib/ctypes/array.rs b/crates/vm/src/stdlib/ctypes/array.rs index 73217d7a6a..96b8431b80 100644 --- a/crates/vm/src/stdlib/ctypes/array.rs +++ b/crates/vm/src/stdlib/ctypes/array.rs @@ -75,7 +75,7 @@ impl Constructor for PyCArrayType { #[pyclass(flags(IMMUTABLETYPE), with(Callable, Constructor, AsNumber))] impl PyCArrayType { #[pygetset(name = "_type_")] - fn typ(&self) -> PyTypeRef { + fn typ(&self) -> PyObjectRef { self.inner.typ.read().clone() } @@ -96,10 +96,7 @@ impl PyCArrayType { 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"); + let current_array_type: PyObjectRef = zelf.as_object().to_owned(); // Element size is the total size of the inner array let new_element_size = inner_length * inner_element_size; @@ -144,7 +141,8 @@ impl AsNumber for PyCArrayType { )] #[derive(PyPayload)] pub struct PyCArray { - pub(super) typ: PyRwLock, + /// Element type - can be a simple type (c_int) or an array type (c_int * 5) + pub(super) typ: PyRwLock, pub(super) length: AtomicCell, pub(super) element_size: AtomicCell, pub(super) buffer: PyRwLock>, @@ -207,12 +205,8 @@ impl Constructor for PyCArray { } } - let element_type_ref = element_type - .downcast::() - .unwrap_or_else(|_| vm.ctx.types.object_type.to_owned()); - PyCArray { - typ: PyRwLock::new(element_type_ref), + typ: PyRwLock::new(element_type), length: AtomicCell::new(length), element_size: AtomicCell::new(element_size), buffer: PyRwLock::new(buffer), @@ -354,7 +348,7 @@ impl PyCArray { } #[pygetset(name = "_type_")] - fn typ(&self) -> PyTypeRef { + fn typ(&self) -> PyObjectRef { self.typ.read().clone() } diff --git a/crates/vm/src/stdlib/ctypes/base.rs b/crates/vm/src/stdlib/ctypes/base.rs index 73d97a2593..7a443603e1 100644 --- a/crates/vm/src/stdlib/ctypes/base.rs +++ b/crates/vm/src/stdlib/ctypes/base.rs @@ -324,7 +324,7 @@ impl PyCSimple { }; Ok(PyCArrayType { inner: PyCArray { - typ: PyRwLock::new(cls), + typ: PyRwLock::new(cls.clone().into()), length: AtomicCell::new(n as usize), element_size: AtomicCell::new(element_size), buffer: PyRwLock::new(vec![]), diff --git a/crates/vm/src/stdlib/ctypes/pointer.rs b/crates/vm/src/stdlib/ctypes/pointer.rs index 16d795ea4d..2ad7afd547 100644 --- a/crates/vm/src/stdlib/ctypes/pointer.rs +++ b/crates/vm/src/stdlib/ctypes/pointer.rs @@ -7,7 +7,7 @@ use crate::convert::ToPyObject; use crate::protocol::PyNumberMethods; use crate::stdlib::ctypes::PyCData; use crate::types::AsNumber; -use crate::{PyObjectRef, PyResult, VirtualMachine}; +use crate::{AsObject, PyObjectRef, PyResult, VirtualMachine}; #[pyclass(name = "PyCPointerType", base = PyType, module = "_ctypes")] #[derive(PyPayload, Debug)] @@ -28,7 +28,7 @@ impl PyCPointerType { let element_size = std::mem::size_of::(); Ok(PyCArrayType { inner: PyCArray { - typ: PyRwLock::new(cls), + typ: PyRwLock::new(cls.as_object().to_owned()), length: AtomicCell::new(n as usize), element_size: AtomicCell::new(element_size), buffer: PyRwLock::new(vec![]), diff --git a/crates/vm/src/stdlib/ctypes/structure.rs b/crates/vm/src/stdlib/ctypes/structure.rs index e7180a48e7..8abb72392b 100644 --- a/crates/vm/src/stdlib/ctypes/structure.rs +++ b/crates/vm/src/stdlib/ctypes/structure.rs @@ -144,7 +144,7 @@ impl PyCStructType { let element_size = std::mem::size_of::(); // Default, should calculate from fields Ok(PyCArrayType { inner: PyCArray { - typ: PyRwLock::new(cls), + typ: PyRwLock::new(cls.clone().into()), length: AtomicCell::new(n as usize), element_size: AtomicCell::new(element_size), buffer: PyRwLock::new(vec![]), From 8556955d2e3a2d6dabae437b4982a2d0506c634d Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sat, 29 Nov 2025 02:34:29 +0900 Subject: [PATCH 2/9] from buffer --- Lib/ctypes/wintypes.py | 2 +- crates/vm/src/stdlib/ctypes.rs | 159 ++++++++++++++++++++ crates/vm/src/stdlib/ctypes/array.rs | 164 +++++++++++++++++++++ crates/vm/src/stdlib/ctypes/base.rs | 176 ++++++++++++++++++++++- crates/vm/src/stdlib/ctypes/pointer.rs | 122 +++++++++++++++- crates/vm/src/stdlib/ctypes/structure.rs | 126 ++++++++++++++++ crates/vm/src/stdlib/ctypes/union.rs | 88 +++++++++++- 7 files changed, 830 insertions(+), 7 deletions(-) diff --git a/Lib/ctypes/wintypes.py b/Lib/ctypes/wintypes.py index c619d27596..9c4e721438 100644 --- a/Lib/ctypes/wintypes.py +++ b/Lib/ctypes/wintypes.py @@ -1,7 +1,7 @@ # The most useful windows datatypes import ctypes -BYTE = ctypes.c_byte +BYTE = ctypes.c_ubyte WORD = ctypes.c_ushort DWORD = ctypes.c_ulong diff --git a/crates/vm/src/stdlib/ctypes.rs b/crates/vm/src/stdlib/ctypes.rs index 7e9557458c..4f0c1c051a 100644 --- a/crates/vm/src/stdlib/ctypes.rs +++ b/crates/vm/src/stdlib/ctypes.rs @@ -158,6 +158,165 @@ pub(crate) mod _ctypes { } } + /// Get the size of a ctypes type from its type object + pub fn get_size_from_type(cls: &PyTypeRef, vm: &VirtualMachine) -> PyResult { + // Try to get _type_ attribute for simple types + 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 && SIMPLE_TYPE_CHARS.contains(s.as_str()) { + return Ok(get_size(&s)); + } + } + } + // Fall back to sizeof + size_of(Either::A(cls.clone()), vm) + } + + /// Convert bytes to appropriate Python object based on ctypes type + pub fn bytes_to_pyobject( + cls: &PyTypeRef, + bytes: &[u8], + vm: &VirtualMachine, + ) -> PyResult { + // Try to get _type_ attribute + if let Ok(type_attr) = cls.as_object().get_attr("_type_", vm) { + if let Ok(s) = type_attr.str(vm) { + let ty = s.to_string(); + return match ty.as_str() { + "c" => { + // c_char - single byte + Ok(vm.ctx.new_bytes(bytes.to_vec()).into()) + } + "b" => { + // c_byte - signed char + let val = if !bytes.is_empty() { bytes[0] as i8 } else { 0 }; + Ok(vm.ctx.new_int(val).into()) + } + "B" => { + // c_ubyte - unsigned char + let val = if !bytes.is_empty() { bytes[0] } else { 0 }; + Ok(vm.ctx.new_int(val).into()) + } + "h" => { + // c_short + let val = if bytes.len() >= 2 { + i16::from_ne_bytes([bytes[0], bytes[1]]) + } else { + 0 + }; + Ok(vm.ctx.new_int(val).into()) + } + "H" => { + // c_ushort + let val = if bytes.len() >= 2 { + u16::from_ne_bytes([bytes[0], bytes[1]]) + } else { + 0 + }; + Ok(vm.ctx.new_int(val).into()) + } + "i" | "l" => { + // c_int, c_long (assuming 32-bit) + let val = if bytes.len() >= 4 { + i32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) + } else { + 0 + }; + Ok(vm.ctx.new_int(val).into()) + } + "I" | "L" => { + // c_uint, c_ulong (assuming 32-bit) + let val = if bytes.len() >= 4 { + u32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) + } else { + 0 + }; + Ok(vm.ctx.new_int(val).into()) + } + "q" => { + // c_longlong + let val = if bytes.len() >= 8 { + i64::from_ne_bytes([ + bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], + bytes[6], bytes[7], + ]) + } else { + 0 + }; + Ok(vm.ctx.new_int(val).into()) + } + "Q" => { + // c_ulonglong + let val = if bytes.len() >= 8 { + u64::from_ne_bytes([ + bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], + bytes[6], bytes[7], + ]) + } else { + 0 + }; + Ok(vm.ctx.new_int(val).into()) + } + "f" => { + // c_float + let val = if bytes.len() >= 4 { + f32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) + } else { + 0.0 + }; + Ok(vm.ctx.new_float(val as f64).into()) + } + "d" | "g" => { + // c_double + let val = if bytes.len() >= 8 { + f64::from_ne_bytes([ + bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], + bytes[6], bytes[7], + ]) + } else { + 0.0 + }; + Ok(vm.ctx.new_float(val).into()) + } + "?" => { + // c_bool + let val = !bytes.is_empty() && bytes[0] != 0; + Ok(vm.ctx.new_bool(val).into()) + } + "P" | "z" | "Z" => { + // Pointer types - return as integer address + let val = if bytes.len() >= mem::size_of::() { + let mut arr = [0u8; 8]; + arr[..bytes.len().min(8)].copy_from_slice(&bytes[..bytes.len().min(8)]); + usize::from_ne_bytes(arr) + } else { + 0 + }; + Ok(vm.ctx.new_int(val).into()) + } + "u" => { + // c_wchar - wide character + let val = if bytes.len() >= mem::size_of::() { + let wc = if mem::size_of::() == 2 { + u16::from_ne_bytes([bytes[0], bytes[1]]) as u32 + } else { + u32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) + }; + char::from_u32(wc).unwrap_or('\0') + } else { + '\0' + }; + Ok(vm.ctx.new_str(val.to_string()).into()) + } + _ => Ok(vm.ctx.none()), + }; + } + } + // Default: return bytes as-is + Ok(vm.ctx.new_bytes(bytes.to_vec()).into()) + } + const SIMPLE_TYPE_CHARS: &str = "cbBhHiIlLdfguzZPqQ?O"; pub fn new_simple_type( diff --git a/crates/vm/src/stdlib/ctypes/array.rs b/crates/vm/src/stdlib/ctypes/array.rs index 96b8431b80..d46b29b72e 100644 --- a/crates/vm/src/stdlib/ctypes/array.rs +++ b/crates/vm/src/stdlib/ctypes/array.rs @@ -393,6 +393,170 @@ impl PyCArray { Err(vm.new_type_error("expected bytes".to_owned())) } } + + #[pyclassmethod] + fn from_address(cls: PyTypeRef, address: isize, vm: &VirtualMachine) -> PyResult { + use crate::function::Either; + use crate::stdlib::ctypes::_ctypes::size_of; + + // Get size from cls + let size = size_of(Either::A(cls.clone()), vm)?; + + // Create instance with data from address + if address != 0 && size > 0 { + unsafe { + let ptr = address as *const u8; + let bytes = std::slice::from_raw_parts(ptr, size); + // Get element type and length from cls + let element_type = cls.as_object().get_attr("_type_", vm)?; + let element_type: PyTypeRef = element_type + .downcast() + .map_err(|_| vm.new_type_error("_type_ must be a type".to_owned()))?; + let length = cls + .as_object() + .get_attr("_length_", vm)? + .try_int(vm)? + .as_bigint() + .to_usize() + .unwrap_or(0); + let element_size = if length > 0 { size / length } else { 0 }; + + Ok(PyCArray { + typ: PyRwLock::new(element_type), + length: AtomicCell::new(length), + element_size: AtomicCell::new(element_size), + buffer: PyRwLock::new(bytes.to_vec()), + } + .into_pyobject(vm)) + } + } else { + Err(vm.new_value_error("NULL pointer access".to_owned())) + } + } + + #[pyclassmethod] + fn from_buffer( + cls: PyTypeRef, + source: PyObjectRef, + offset: crate::function::OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + use crate::TryFromObject; + use crate::function::Either; + use crate::protocol::PyBuffer; + use crate::stdlib::ctypes::_ctypes::size_of; + + let offset = offset.unwrap_or(0); + if offset < 0 { + return Err(vm.new_value_error("offset cannot be negative".to_owned())); + } + let offset = offset as usize; + + // Get buffer from source + let buffer = PyBuffer::try_from_object(vm, source.clone())?; + + // Check if buffer is writable + if buffer.desc.readonly { + return Err(vm.new_type_error("underlying buffer is not writable".to_owned())); + } + + // Get size from cls + let size = size_of(Either::A(cls.clone()), vm)?; + + // Check if buffer is large enough + let buffer_len = buffer.desc.len; + if offset + size > buffer_len { + return Err(vm.new_value_error(format!( + "Buffer size too small ({} instead of at least {} bytes)", + buffer_len, + offset + size + ))); + } + + // Read bytes from buffer at offset + let bytes = buffer.obj_bytes(); + let data = &bytes[offset..offset + size]; + + // Get element type and length from cls + let element_type = cls.as_object().get_attr("_type_", vm)?; + let element_type: PyTypeRef = element_type + .downcast() + .map_err(|_| vm.new_type_error("_type_ must be a type".to_owned()))?; + let length = cls + .as_object() + .get_attr("_length_", vm)? + .try_int(vm)? + .as_bigint() + .to_usize() + .unwrap_or(0); + let element_size = if length > 0 { size / length } else { 0 }; + + Ok(PyCArray { + typ: PyRwLock::new(element_type), + length: AtomicCell::new(length), + element_size: AtomicCell::new(element_size), + buffer: PyRwLock::new(data.to_vec()), + } + .into_pyobject(vm)) + } + + #[pyclassmethod] + fn from_buffer_copy( + cls: PyTypeRef, + source: crate::function::ArgBytesLike, + offset: crate::function::OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + use crate::function::Either; + use crate::stdlib::ctypes::_ctypes::size_of; + + let offset = offset.unwrap_or(0); + if offset < 0 { + return Err(vm.new_value_error("offset cannot be negative".to_owned())); + } + let offset = offset as usize; + + // Get size from cls + let size = size_of(Either::A(cls.clone()), vm)?; + + // Borrow bytes from source + let source_bytes = source.borrow_buf(); + let buffer_len = source_bytes.len(); + + // Check if buffer is large enough + if offset + size > buffer_len { + return Err(vm.new_value_error(format!( + "Buffer size too small ({} instead of at least {} bytes)", + buffer_len, + offset + size + ))); + } + + // Copy bytes from buffer at offset + let data = &source_bytes[offset..offset + size]; + + // Get element type and length from cls + let element_type = cls.as_object().get_attr("_type_", vm)?; + let element_type: PyTypeRef = element_type + .downcast() + .map_err(|_| vm.new_type_error("_type_ must be a type".to_owned()))?; + let length = cls + .as_object() + .get_attr("_length_", vm)? + .try_int(vm)? + .as_bigint() + .to_usize() + .unwrap_or(0); + let element_size = if length > 0 { size / length } else { 0 }; + + Ok(PyCArray { + typ: PyRwLock::new(element_type), + length: AtomicCell::new(length), + element_size: AtomicCell::new(element_size), + buffer: PyRwLock::new(data.to_vec()), + } + .into_pyobject(vm)) + } } impl PyCArray { diff --git a/crates/vm/src/stdlib/ctypes/base.rs b/crates/vm/src/stdlib/ctypes/base.rs index 7a443603e1..b1a81ec1c3 100644 --- a/crates/vm/src/stdlib/ctypes/base.rs +++ b/crates/vm/src/stdlib/ctypes/base.rs @@ -1,15 +1,17 @@ +use super::_ctypes::bytes_to_pyobject; use super::array::{PyCArray, PyCArrayType}; use crate::builtins::PyType; use crate::builtins::{PyBytes, PyFloat, PyInt, PyNone, PyStr, PyTypeRef}; use crate::convert::ToPyObject; -use crate::function::{Either, OptionalArg}; -use crate::protocol::PyNumberMethods; +use crate::function::{ArgBytesLike, Either, OptionalArg}; +use crate::protocol::{PyBuffer, PyNumberMethods}; use crate::stdlib::ctypes::_ctypes::new_simple_type; use crate::types::{AsNumber, Constructor}; use crate::{AsObject, Py, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine}; use crossbeam_utils::atomic::AtomicCell; use num_traits::ToPrimitive; use rustpython_common::lock::PyRwLock; +use std::ffi::{c_uint, c_ulong, c_ulonglong, c_ushort}; use std::fmt::Debug; pub fn ffi_type_from_str(_type_: &str) -> Option { @@ -102,7 +104,8 @@ fn set_primitive(_type_: &str, value: &PyObjectRef, vm: &VirtualMachine) -> PyRe )), "B" => { if value.clone().downcast_exact::(vm).is_ok() { - Ok(vm.new_pyobj(u8::try_from_object(vm, value.clone())?)) + // Store as-is, conversion to unsigned happens in the getter + Ok(value.clone()) } else { Err(vm.new_type_error(format!("int expected instead of {}", value.class().name()))) } @@ -288,7 +291,47 @@ impl PyCSimple { let zelf: &Py = instance .downcast_ref() .ok_or_else(|| vm.new_type_error("cannot get value of instance"))?; - Ok(unsafe { (*zelf.value.as_ptr()).clone() }) + let raw_value = unsafe { (*zelf.value.as_ptr()).clone() }; + + // Convert to unsigned if needed for unsigned types + match zelf._type_.as_str() { + "B" | "H" | "I" | "L" | "Q" => { + if let Ok(int_val) = raw_value.try_int(vm) { + let n = int_val.as_bigint(); + // Use platform-specific C types for correct unsigned conversion + match zelf._type_.as_str() { + "B" => { + if let Some(v) = n.to_i64() { + return Ok(vm.ctx.new_int((v as u8) as u64).into()); + } + } + "H" => { + if let Some(v) = n.to_i64() { + return Ok(vm.ctx.new_int((v as c_ushort) as u64).into()); + } + } + "I" => { + if let Some(v) = n.to_i64() { + return Ok(vm.ctx.new_int((v as c_uint) as u64).into()); + } + } + "L" => { + if let Some(v) = n.to_i128() { + return Ok(vm.ctx.new_int(v as c_ulong).into()); + } + } + "Q" => { + if let Some(v) = n.to_i128() { + return Ok(vm.ctx.new_int(v as c_ulonglong).into()); + } + } + _ => {} + }; + } + Ok(raw_value) + } + _ => Ok(raw_value), + } } #[pygetset(name = "value", setter)] @@ -332,6 +375,131 @@ impl PyCSimple { } .to_pyobject(vm)) } + + #[pyclassmethod] + fn from_address(cls: PyTypeRef, address: isize, vm: &VirtualMachine) -> PyResult { + use super::_ctypes::get_size; + // Get _type_ attribute directly + let type_attr = cls + .as_object() + .get_attr("_type_", vm) + .map_err(|_| vm.new_type_error(format!("'{}' has no _type_ attribute", cls.name())))?; + let type_str = type_attr.str(vm)?.to_string(); + let size = get_size(&type_str); + + // Create instance with value read from address + let value = if address != 0 && size > 0 { + // Safety: This is inherently unsafe - reading from arbitrary memory address + // CPython does the same thing without safety checks + unsafe { + let ptr = address as *const u8; + let bytes = std::slice::from_raw_parts(ptr, size); + // Convert bytes to appropriate Python value based on type + bytes_to_pyobject(&cls, bytes, vm)? + } + } else { + vm.ctx.none() + }; + + // Create instance using the type's constructor + let instance = PyCSimple::py_new(cls.clone(), (OptionalArg::Present(value),), vm)?; + Ok(instance) + } + + #[pyclassmethod] + fn from_buffer( + cls: PyTypeRef, + source: PyObjectRef, + offset: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + use super::_ctypes::get_size; + let offset = offset.unwrap_or(0); + if offset < 0 { + return Err(vm.new_value_error("offset cannot be negative".to_owned())); + } + let offset = offset as usize; + + // Get buffer from source + let buffer = PyBuffer::try_from_object(vm, source.clone())?; + + // Check if buffer is writable + if buffer.desc.readonly { + return Err(vm.new_type_error("underlying buffer is not writable".to_owned())); + } + + // Get _type_ attribute directly + let type_attr = cls + .as_object() + .get_attr("_type_", vm) + .map_err(|_| vm.new_type_error(format!("'{}' has no _type_ attribute", cls.name())))?; + let type_str = type_attr.str(vm)?.to_string(); + let size = get_size(&type_str); + + // Check if buffer is large enough + let buffer_len = buffer.desc.len; + if offset + size > buffer_len { + return Err(vm.new_value_error(format!( + "Buffer size too small ({} instead of at least {} bytes)", + buffer_len, + offset + size + ))); + } + + // Read bytes from buffer at offset + let bytes = buffer.obj_bytes(); + let data = &bytes[offset..offset + size]; + let value = bytes_to_pyobject(&cls, data, vm)?; + + // Create instance + let instance = PyCSimple::py_new(cls.clone(), (OptionalArg::Present(value),), vm)?; + + // TODO: Store reference to source in _objects to keep buffer alive + Ok(instance) + } + + #[pyclassmethod] + fn from_buffer_copy( + cls: PyTypeRef, + source: ArgBytesLike, + offset: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + use super::_ctypes::get_size; + let offset = offset.unwrap_or(0); + if offset < 0 { + return Err(vm.new_value_error("offset cannot be negative".to_owned())); + } + let offset = offset as usize; + + // Get _type_ attribute directly for simple types + let type_attr = cls + .as_object() + .get_attr("_type_", vm) + .map_err(|_| vm.new_type_error(format!("'{}' has no _type_ attribute", cls.name())))?; + let type_str = type_attr.str(vm)?.to_string(); + let size = get_size(&type_str); + + // Borrow bytes from source + let source_bytes = source.borrow_buf(); + let buffer_len = source_bytes.len(); + + // Check if buffer is large enough + if offset + size > buffer_len { + return Err(vm.new_value_error(format!( + "Buffer size too small ({} instead of at least {} bytes)", + buffer_len, + offset + size + ))); + } + + // Copy bytes from buffer at offset + let data = &source_bytes[offset..offset + size]; + let value = bytes_to_pyobject(&cls, data, vm)?; + + // Create instance (independent copy, no reference tracking) + PyCSimple::py_new(cls.clone(), (OptionalArg::Present(value),), vm) + } } impl PyCSimple { diff --git a/crates/vm/src/stdlib/ctypes/pointer.rs b/crates/vm/src/stdlib/ctypes/pointer.rs index 2ad7afd547..1b14df1577 100644 --- a/crates/vm/src/stdlib/ctypes/pointer.rs +++ b/crates/vm/src/stdlib/ctypes/pointer.rs @@ -7,7 +7,7 @@ use crate::convert::ToPyObject; use crate::protocol::PyNumberMethods; use crate::stdlib::ctypes::PyCData; use crate::types::AsNumber; -use crate::{AsObject, PyObjectRef, PyResult, VirtualMachine}; +use crate::{AsObject, PyObjectRef, PyPayload, PyResult, VirtualMachine}; #[pyclass(name = "PyCPointerType", base = PyType, module = "_ctypes")] #[derive(PyPayload, Debug)] @@ -82,4 +82,124 @@ impl PyCPointer { *self.contents.write() = contents; Ok(()) } + + #[pyclassmethod] + fn from_address(cls: PyTypeRef, address: isize, vm: &VirtualMachine) -> PyResult { + if address != 0 { + // Pointer just stores the address value + Ok(PyCPointer { + contents: PyRwLock::new(vm.ctx.new_int(address).into()), + } + .into_ref_with_type(vm, cls)? + .into()) + } else { + Err(vm.new_value_error("NULL pointer access".to_owned())) + } + } + + #[pyclassmethod] + fn from_buffer( + cls: PyTypeRef, + source: PyObjectRef, + offset: crate::function::OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + use crate::TryFromObject; + use crate::protocol::PyBuffer; + + let offset = offset.unwrap_or(0); + if offset < 0 { + return Err(vm.new_value_error("offset cannot be negative".to_owned())); + } + let offset = offset as usize; + let size = std::mem::size_of::(); + + let buffer = PyBuffer::try_from_object(vm, source.clone())?; + + if buffer.desc.readonly { + return Err(vm.new_type_error("underlying buffer is not writable".to_owned())); + } + + let buffer_len = buffer.desc.len; + if offset + size > buffer_len { + return Err(vm.new_value_error(format!( + "Buffer size too small ({} instead of at least {} bytes)", + buffer_len, + offset + size + ))); + } + + // Read pointer value from buffer + let bytes = buffer.obj_bytes(); + let ptr_bytes = &bytes[offset..offset + size]; + let ptr_val = if size == 8 { + usize::from_ne_bytes([ + ptr_bytes[0], + ptr_bytes[1], + ptr_bytes[2], + ptr_bytes[3], + ptr_bytes[4], + ptr_bytes[5], + ptr_bytes[6], + ptr_bytes[7], + ]) + } else { + u32::from_ne_bytes([ptr_bytes[0], ptr_bytes[1], ptr_bytes[2], ptr_bytes[3]]) as usize + }; + + Ok(PyCPointer { + contents: PyRwLock::new(vm.ctx.new_int(ptr_val).into()), + } + .into_ref_with_type(vm, cls)? + .into()) + } + + #[pyclassmethod] + fn from_buffer_copy( + cls: PyTypeRef, + source: crate::function::ArgBytesLike, + offset: crate::function::OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + let offset = offset.unwrap_or(0); + if offset < 0 { + return Err(vm.new_value_error("offset cannot be negative".to_owned())); + } + let offset = offset as usize; + let size = std::mem::size_of::(); + + let source_bytes = source.borrow_buf(); + let buffer_len = source_bytes.len(); + + if offset + size > buffer_len { + return Err(vm.new_value_error(format!( + "Buffer size too small ({} instead of at least {} bytes)", + buffer_len, + offset + size + ))); + } + + // Read pointer value from buffer + let ptr_bytes = &source_bytes[offset..offset + size]; + let ptr_val = if size == 8 { + usize::from_ne_bytes([ + ptr_bytes[0], + ptr_bytes[1], + ptr_bytes[2], + ptr_bytes[3], + ptr_bytes[4], + ptr_bytes[5], + ptr_bytes[6], + ptr_bytes[7], + ]) + } else { + u32::from_ne_bytes([ptr_bytes[0], ptr_bytes[1], ptr_bytes[2], ptr_bytes[3]]) as usize + }; + + Ok(PyCPointer { + contents: PyRwLock::new(vm.ctx.new_int(ptr_val).into()), + } + .into_ref_with_type(vm, cls)? + .into()) + } } diff --git a/crates/vm/src/stdlib/ctypes/structure.rs b/crates/vm/src/stdlib/ctypes/structure.rs index 8abb72392b..9570ca8fa9 100644 --- a/crates/vm/src/stdlib/ctypes/structure.rs +++ b/crates/vm/src/stdlib/ctypes/structure.rs @@ -310,4 +310,130 @@ impl PyCStructure { // Return the _fields_ from the class, not instance vm.ctx.none() } + + #[pyclassmethod] + fn from_address(cls: PyTypeRef, address: isize, vm: &VirtualMachine) -> PyResult { + use crate::function::Either; + use crate::stdlib::ctypes::_ctypes::size_of; + + // Get size from cls + let size = size_of(Either::A(cls.clone()), vm)?; + + // Read data from address + if address != 0 && size > 0 { + let data = unsafe { + let ptr = address as *const u8; + std::slice::from_raw_parts(ptr, size).to_vec() + }; + + // Create instance + Ok(PyCStructure { + buffer: PyRwLock::new(data), + fields: PyRwLock::new(HashMap::new()), + size: AtomicCell::new(size), + } + .into_ref_with_type(vm, cls)? + .into()) + } else { + Err(vm.new_value_error("NULL pointer access".to_owned())) + } + } + + #[pyclassmethod] + fn from_buffer( + cls: PyTypeRef, + source: PyObjectRef, + offset: crate::function::OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + use crate::TryFromObject; + use crate::function::Either; + use crate::protocol::PyBuffer; + use crate::stdlib::ctypes::_ctypes::size_of; + + let offset = offset.unwrap_or(0); + if offset < 0 { + return Err(vm.new_value_error("offset cannot be negative".to_owned())); + } + let offset = offset as usize; + + // Get buffer from source + let buffer = PyBuffer::try_from_object(vm, source.clone())?; + + // Check if buffer is writable + if buffer.desc.readonly { + return Err(vm.new_type_error("underlying buffer is not writable".to_owned())); + } + + // Get size from cls + let size = size_of(Either::A(cls.clone()), vm)?; + + // Check if buffer is large enough + let buffer_len = buffer.desc.len; + if offset + size > buffer_len { + return Err(vm.new_value_error(format!( + "Buffer size too small ({} instead of at least {} bytes)", + buffer_len, + offset + size + ))); + } + + // Read bytes from buffer at offset + let bytes = buffer.obj_bytes(); + let data = bytes[offset..offset + size].to_vec(); + + // Create instance + Ok(PyCStructure { + buffer: PyRwLock::new(data), + fields: PyRwLock::new(HashMap::new()), + size: AtomicCell::new(size), + } + .into_ref_with_type(vm, cls)? + .into()) + } + + #[pyclassmethod] + fn from_buffer_copy( + cls: PyTypeRef, + source: crate::function::ArgBytesLike, + offset: crate::function::OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + use crate::function::Either; + use crate::stdlib::ctypes::_ctypes::size_of; + + let offset = offset.unwrap_or(0); + if offset < 0 { + return Err(vm.new_value_error("offset cannot be negative".to_owned())); + } + let offset = offset as usize; + + // Get size from cls + let size = size_of(Either::A(cls.clone()), vm)?; + + // Borrow bytes from source + let source_bytes = source.borrow_buf(); + let buffer_len = source_bytes.len(); + + // Check if buffer is large enough + if offset + size > buffer_len { + return Err(vm.new_value_error(format!( + "Buffer size too small ({} instead of at least {} bytes)", + buffer_len, + offset + size + ))); + } + + // Copy bytes from buffer at offset + let data = source_bytes[offset..offset + size].to_vec(); + + // Create instance + Ok(PyCStructure { + buffer: PyRwLock::new(data), + fields: PyRwLock::new(HashMap::new()), + size: AtomicCell::new(size), + } + .into_ref_with_type(vm, cls)? + .into()) + } } diff --git a/crates/vm/src/stdlib/ctypes/union.rs b/crates/vm/src/stdlib/ctypes/union.rs index a357c195d6..6b571306f8 100644 --- a/crates/vm/src/stdlib/ctypes/union.rs +++ b/crates/vm/src/stdlib/ctypes/union.rs @@ -114,7 +114,93 @@ impl PyCUnionType {} /// PyCUnion - base class for Union #[pyclass(module = "_ctypes", name = "Union", base = PyCData, metaclass = "PyCUnionType")] +#[derive(Debug, PyPayload)] pub struct PyCUnion {} #[pyclass(flags(BASETYPE, IMMUTABLETYPE))] -impl PyCUnion {} +impl PyCUnion { + #[pyclassmethod] + fn from_address(cls: PyTypeRef, address: isize, vm: &VirtualMachine) -> PyResult { + use crate::function::Either; + use crate::stdlib::ctypes::_ctypes::size_of; + + // Get size from cls + let size = size_of(Either::A(cls.clone()), vm)?; + + if address != 0 && size > 0 { + // Create instance (Union doesn't have internal buffer in current impl) + Ok(PyCUnion {}.into_ref_with_type(vm, cls)?.into()) + } else { + Err(vm.new_value_error("NULL pointer access".to_owned())) + } + } + + #[pyclassmethod] + fn from_buffer( + cls: PyTypeRef, + source: PyObjectRef, + offset: crate::function::OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + use crate::TryFromObject; + use crate::function::Either; + use crate::protocol::PyBuffer; + use crate::stdlib::ctypes::_ctypes::size_of; + + let offset = offset.unwrap_or(0); + if offset < 0 { + return Err(vm.new_value_error("offset cannot be negative".to_owned())); + } + let offset = offset as usize; + + let buffer = PyBuffer::try_from_object(vm, source.clone())?; + + if buffer.desc.readonly { + return Err(vm.new_type_error("underlying buffer is not writable".to_owned())); + } + + let size = size_of(Either::A(cls.clone()), vm)?; + let buffer_len = buffer.desc.len; + + if offset + size > buffer_len { + return Err(vm.new_value_error(format!( + "Buffer size too small ({} instead of at least {} bytes)", + buffer_len, + offset + size + ))); + } + + Ok(PyCUnion {}.into_ref_with_type(vm, cls)?.into()) + } + + #[pyclassmethod] + fn from_buffer_copy( + cls: PyTypeRef, + source: crate::function::ArgBytesLike, + offset: crate::function::OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + use crate::function::Either; + use crate::stdlib::ctypes::_ctypes::size_of; + + let offset = offset.unwrap_or(0); + if offset < 0 { + return Err(vm.new_value_error("offset cannot be negative".to_owned())); + } + let offset = offset as usize; + + let size = size_of(Either::A(cls.clone()), vm)?; + let source_bytes = source.borrow_buf(); + let buffer_len = source_bytes.len(); + + if offset + size > buffer_len { + return Err(vm.new_value_error(format!( + "Buffer size too small ({} instead of at least {} bytes)", + buffer_len, + offset + size + ))); + } + + Ok(PyCUnion {}.into_ref_with_type(vm, cls)?.into()) + } +} From c232c33f04abb5933f634ac429ca2b13d2994153 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sat, 29 Nov 2025 09:29:49 +0900 Subject: [PATCH 3/9] AsBuffer --- crates/vm/src/stdlib/ctypes.rs | 298 ++++++++++++----------- crates/vm/src/stdlib/ctypes/array.rs | 154 +++++++----- crates/vm/src/stdlib/ctypes/base.rs | 243 +++++++++++++++++- crates/vm/src/stdlib/ctypes/field.rs | 45 +++- crates/vm/src/stdlib/ctypes/pointer.rs | 50 +--- crates/vm/src/stdlib/ctypes/structure.rs | 98 +++++--- crates/vm/src/stdlib/ctypes/union.rs | 89 ++++++- 7 files changed, 677 insertions(+), 300 deletions(-) diff --git a/crates/vm/src/stdlib/ctypes.rs b/crates/vm/src/stdlib/ctypes.rs index 4f0c1c051a..7357238008 100644 --- a/crates/vm/src/stdlib/ctypes.rs +++ b/crates/vm/src/stdlib/ctypes.rs @@ -45,7 +45,7 @@ pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef { #[pymodule] pub(crate) mod _ctypes { - use super::base::PyCSimple; + use super::base::{CDataObject, PyCSimple}; use crate::builtins::PyTypeRef; use crate::class::StaticType; use crate::convert::ToPyObject; @@ -55,7 +55,7 @@ pub(crate) mod _ctypes { 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, - c_ulonglong, + c_ulonglong, c_ushort, }; use std::mem; use widestring::WideChar; @@ -159,14 +159,15 @@ pub(crate) mod _ctypes { } /// Get the size of a ctypes type from its type object + #[allow(dead_code)] pub fn get_size_from_type(cls: &PyTypeRef, vm: &VirtualMachine) -> PyResult { // Try to get _type_ attribute for simple types - 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 && SIMPLE_TYPE_CHARS.contains(s.as_str()) { - return Ok(get_size(&s)); - } + if let Ok(type_attr) = cls.as_object().get_attr("_type_", vm) + && let Ok(s) = type_attr.str(vm) + { + let s = s.to_string(); + if s.len() == 1 && SIMPLE_TYPE_CHARS.contains(s.as_str()) { + return Ok(get_size(&s)); } } // Fall back to sizeof @@ -180,138 +181,159 @@ pub(crate) mod _ctypes { vm: &VirtualMachine, ) -> PyResult { // Try to get _type_ attribute - if let Ok(type_attr) = cls.as_object().get_attr("_type_", vm) { - if let Ok(s) = type_attr.str(vm) { - let ty = s.to_string(); - return match ty.as_str() { - "c" => { - // c_char - single byte - Ok(vm.ctx.new_bytes(bytes.to_vec()).into()) - } - "b" => { - // c_byte - signed char - let val = if !bytes.is_empty() { bytes[0] as i8 } else { 0 }; - Ok(vm.ctx.new_int(val).into()) - } - "B" => { - // c_ubyte - unsigned char - let val = if !bytes.is_empty() { bytes[0] } else { 0 }; - Ok(vm.ctx.new_int(val).into()) - } - "h" => { - // c_short - let val = if bytes.len() >= 2 { - i16::from_ne_bytes([bytes[0], bytes[1]]) - } else { - 0 - }; - Ok(vm.ctx.new_int(val).into()) - } - "H" => { - // c_ushort - let val = if bytes.len() >= 2 { - u16::from_ne_bytes([bytes[0], bytes[1]]) - } else { - 0 - }; - Ok(vm.ctx.new_int(val).into()) - } - "i" | "l" => { - // c_int, c_long (assuming 32-bit) - let val = if bytes.len() >= 4 { - i32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) + if let Ok(type_attr) = cls.as_object().get_attr("_type_", vm) + && let Ok(s) = type_attr.str(vm) + { + let ty = s.to_string(); + return match ty.as_str() { + "c" => { + // c_char - single byte + Ok(vm.ctx.new_bytes(bytes.to_vec()).into()) + } + "b" => { + // c_byte - signed char + let val = if !bytes.is_empty() { bytes[0] as i8 } else { 0 }; + Ok(vm.ctx.new_int(val).into()) + } + "B" => { + // c_ubyte - unsigned char + let val = if !bytes.is_empty() { bytes[0] } else { 0 }; + Ok(vm.ctx.new_int(val).into()) + } + "h" => { + // c_short + const SIZE: usize = mem::size_of::(); + let val = if bytes.len() >= SIZE { + c_short::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) + } else { + 0 + }; + Ok(vm.ctx.new_int(val).into()) + } + "H" => { + // c_ushort + const SIZE: usize = mem::size_of::(); + let val = if bytes.len() >= SIZE { + c_ushort::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) + } else { + 0 + }; + Ok(vm.ctx.new_int(val).into()) + } + "i" => { + // c_int + const SIZE: usize = mem::size_of::(); + let val = if bytes.len() >= SIZE { + c_int::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) + } else { + 0 + }; + Ok(vm.ctx.new_int(val).into()) + } + "I" => { + // c_uint + const SIZE: usize = mem::size_of::(); + let val = if bytes.len() >= SIZE { + c_uint::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) + } else { + 0 + }; + Ok(vm.ctx.new_int(val).into()) + } + "l" => { + // c_long + const SIZE: usize = mem::size_of::(); + let val = if bytes.len() >= SIZE { + c_long::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) + } else { + 0 + }; + Ok(vm.ctx.new_int(val).into()) + } + "L" => { + // c_ulong + const SIZE: usize = mem::size_of::(); + let val = if bytes.len() >= SIZE { + c_ulong::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) + } else { + 0 + }; + Ok(vm.ctx.new_int(val).into()) + } + "q" => { + // c_longlong + const SIZE: usize = mem::size_of::(); + let val = if bytes.len() >= SIZE { + c_longlong::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) + } else { + 0 + }; + Ok(vm.ctx.new_int(val).into()) + } + "Q" => { + // c_ulonglong + const SIZE: usize = mem::size_of::(); + let val = if bytes.len() >= SIZE { + c_ulonglong::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) + } else { + 0 + }; + Ok(vm.ctx.new_int(val).into()) + } + "f" => { + // c_float + const SIZE: usize = mem::size_of::(); + let val = if bytes.len() >= SIZE { + c_float::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) + } else { + 0.0 + }; + Ok(vm.ctx.new_float(val as f64).into()) + } + "d" | "g" => { + // c_double + const SIZE: usize = mem::size_of::(); + let val = if bytes.len() >= SIZE { + c_double::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked")) + } else { + 0.0 + }; + Ok(vm.ctx.new_float(val).into()) + } + "?" => { + // c_bool + let val = !bytes.is_empty() && bytes[0] != 0; + Ok(vm.ctx.new_bool(val).into()) + } + "P" | "z" | "Z" => { + // Pointer types - return as integer address + let val = if bytes.len() >= mem::size_of::() { + const UINTPTR_LEN: usize = mem::size_of::(); + let mut arr = [0u8; UINTPTR_LEN]; + arr[..bytes.len().min(UINTPTR_LEN)] + .copy_from_slice(&bytes[..bytes.len().min(UINTPTR_LEN)]); + usize::from_ne_bytes(arr) + } else { + 0 + }; + Ok(vm.ctx.new_int(val).into()) + } + "u" => { + // c_wchar - wide character + let val = if bytes.len() >= mem::size_of::() { + let wc = if mem::size_of::() == 2 { + u16::from_ne_bytes([bytes[0], bytes[1]]) as u32 } else { - 0 - }; - Ok(vm.ctx.new_int(val).into()) - } - "I" | "L" => { - // c_uint, c_ulong (assuming 32-bit) - let val = if bytes.len() >= 4 { u32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) - } else { - 0 }; - Ok(vm.ctx.new_int(val).into()) - } - "q" => { - // c_longlong - let val = if bytes.len() >= 8 { - i64::from_ne_bytes([ - bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], - bytes[6], bytes[7], - ]) - } else { - 0 - }; - Ok(vm.ctx.new_int(val).into()) - } - "Q" => { - // c_ulonglong - let val = if bytes.len() >= 8 { - u64::from_ne_bytes([ - bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], - bytes[6], bytes[7], - ]) - } else { - 0 - }; - Ok(vm.ctx.new_int(val).into()) - } - "f" => { - // c_float - let val = if bytes.len() >= 4 { - f32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) - } else { - 0.0 - }; - Ok(vm.ctx.new_float(val as f64).into()) - } - "d" | "g" => { - // c_double - let val = if bytes.len() >= 8 { - f64::from_ne_bytes([ - bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], - bytes[6], bytes[7], - ]) - } else { - 0.0 - }; - Ok(vm.ctx.new_float(val).into()) - } - "?" => { - // c_bool - let val = !bytes.is_empty() && bytes[0] != 0; - Ok(vm.ctx.new_bool(val).into()) - } - "P" | "z" | "Z" => { - // Pointer types - return as integer address - let val = if bytes.len() >= mem::size_of::() { - let mut arr = [0u8; 8]; - arr[..bytes.len().min(8)].copy_from_slice(&bytes[..bytes.len().min(8)]); - usize::from_ne_bytes(arr) - } else { - 0 - }; - Ok(vm.ctx.new_int(val).into()) - } - "u" => { - // c_wchar - wide character - let val = if bytes.len() >= mem::size_of::() { - let wc = if mem::size_of::() == 2 { - u16::from_ne_bytes([bytes[0], bytes[1]]) as u32 - } else { - u32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) - }; - char::from_u32(wc).unwrap_or('\0') - } else { - '\0' - }; - Ok(vm.ctx.new_str(val.to_string()).into()) - } - _ => Ok(vm.ctx.none()), - }; - } + char::from_u32(wc).unwrap_or('\0') + } else { + '\0' + }; + Ok(vm.ctx.new_str(val.to_string()).into()) + } + _ => Ok(vm.ctx.none()), + }; } // Default: return bytes as-is Ok(vm.ctx.new_bytes(bytes.to_vec()).into()) @@ -339,9 +361,11 @@ pub(crate) mod _ctypes { } else if !SIMPLE_TYPE_CHARS.contains(tp_str.as_str()) { Err(vm.new_attribute_error(format!("class must define a '_type_' attribute which must be\n a single character string containing one of {SIMPLE_TYPE_CHARS}, currently it is {tp_str}."))) } else { + let size = get_size(&tp_str); Ok(PyCSimple { _type_: tp_str, value: AtomicCell::new(vm.ctx.none()), + cdata: rustpython_common::lock::PyRwLock::new(CDataObject::new(size)), }) } } else { diff --git a/crates/vm/src/stdlib/ctypes/array.rs b/crates/vm/src/stdlib/ctypes/array.rs index d46b29b72e..8196422653 100644 --- a/crates/vm/src/stdlib/ctypes/array.rs +++ b/crates/vm/src/stdlib/ctypes/array.rs @@ -2,8 +2,11 @@ use crate::atomic_func; use crate::builtins::{PyBytes, PyInt}; use crate::convert::ToPyObject; use crate::function::FuncArgs; -use crate::protocol::{PyNumberMethods, PySequenceMethods}; -use crate::types::{AsNumber, AsSequence, Callable}; +use crate::protocol::{ + BufferDescriptor, BufferMethods, PyBuffer, PyNumberMethods, PySequenceMethods, +}; +use crate::stdlib::ctypes::base::CDataObject; +use crate::types::{AsBuffer, AsNumber, AsSequence, Callable}; use crate::{AsObject, Py, PyObjectRef, PyPayload}; use crate::{ PyResult, VirtualMachine, @@ -58,7 +61,7 @@ impl Callable for PyCArrayType { typ: PyRwLock::new(element_type), length: AtomicCell::new(length), element_size: AtomicCell::new(element_size), - buffer: PyRwLock::new(buffer), + cdata: PyRwLock::new(CDataObject::from_bytes(buffer, None)), } .into_pyobject(vm)) } @@ -106,7 +109,7 @@ impl PyCArrayType { typ: PyRwLock::new(current_array_type), length: AtomicCell::new(n as usize), element_size: AtomicCell::new(new_element_size), - buffer: PyRwLock::new(vec![]), + cdata: PyRwLock::new(CDataObject::new(0)), }, } .to_pyobject(vm)) @@ -145,7 +148,7 @@ pub struct PyCArray { pub(super) typ: PyRwLock, pub(super) length: AtomicCell, pub(super) element_size: AtomicCell, - pub(super) buffer: PyRwLock>, + pub(super) cdata: PyRwLock, } impl std::fmt::Debug for PyCArray { @@ -209,7 +212,7 @@ impl Constructor for PyCArray { typ: PyRwLock::new(element_type), length: AtomicCell::new(length), element_size: AtomicCell::new(element_size), - buffer: PyRwLock::new(buffer), + cdata: PyRwLock::new(CDataObject::from_bytes(buffer, None)), } .into_ref_with_type(vm, cls) .map(Into::into) @@ -237,7 +240,10 @@ impl AsSequence for PyCArray { } } -#[pyclass(flags(BASETYPE, IMMUTABLETYPE), with(Constructor, AsSequence))] +#[pyclass( + flags(BASETYPE, IMMUTABLETYPE), + with(Constructor, AsSequence, AsBuffer) +)] impl PyCArray { fn int_to_bytes(i: &malachite_bigint::BigInt, size: usize) -> Vec { match size { @@ -279,7 +285,7 @@ impl PyCArray { let index = index as usize; let element_size = zelf.element_size.load(); let offset = index * element_size; - let buffer = zelf.buffer.read(); + let buffer = zelf.cdata.read().buffer.clone(); if offset + element_size <= buffer.len() { let bytes = &buffer[offset..offset + element_size]; Ok(Self::bytes_to_int(bytes, element_size, vm)) @@ -306,9 +312,9 @@ impl PyCArray { 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); + let mut cdata = zelf.cdata.write(); + if offset + element_size <= cdata.buffer.len() { + cdata.buffer[offset..offset + element_size].copy_from_slice(&bytes); } Ok(()) } @@ -360,34 +366,34 @@ impl PyCArray { #[pygetset] fn value(&self, vm: &VirtualMachine) -> PyObjectRef { // Return bytes representation of the buffer - let buffer = self.buffer.read(); + let buffer = self.cdata.read().buffer.clone(); vm.ctx.new_bytes(buffer.clone()).into() } #[pygetset(setter)] fn set_value(&self, value: PyObjectRef, _vm: &VirtualMachine) -> PyResult<()> { if let Some(bytes) = value.downcast_ref::() { - let mut buffer = self.buffer.write(); + let mut cdata = self.cdata.write(); let src = bytes.as_bytes(); - let len = std::cmp::min(src.len(), buffer.len()); - buffer[..len].copy_from_slice(&src[..len]); + let len = std::cmp::min(src.len(), cdata.buffer.len()); + cdata.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() + let cdata = self.cdata.read(); + vm.ctx.new_bytes(cdata.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 mut cdata = self.cdata.write(); let src = bytes.as_bytes(); - let len = std::cmp::min(src.len(), buffer.len()); - buffer[..len].copy_from_slice(&src[..len]); + let len = std::cmp::min(src.len(), cdata.buffer.len()); + cdata.buffer[..len].copy_from_slice(&src[..len]); Ok(()) } else { Err(vm.new_type_error("expected bytes".to_owned())) @@ -403,34 +409,33 @@ impl PyCArray { let size = size_of(Either::A(cls.clone()), vm)?; // Create instance with data from address - if address != 0 && size > 0 { - unsafe { - let ptr = address as *const u8; - let bytes = std::slice::from_raw_parts(ptr, size); - // Get element type and length from cls - let element_type = cls.as_object().get_attr("_type_", vm)?; - let element_type: PyTypeRef = element_type - .downcast() - .map_err(|_| vm.new_type_error("_type_ must be a type".to_owned()))?; - let length = cls - .as_object() - .get_attr("_length_", vm)? - .try_int(vm)? - .as_bigint() - .to_usize() - .unwrap_or(0); - let element_size = if length > 0 { size / length } else { 0 }; - - Ok(PyCArray { - typ: PyRwLock::new(element_type), - length: AtomicCell::new(length), - element_size: AtomicCell::new(element_size), - buffer: PyRwLock::new(bytes.to_vec()), - } - .into_pyobject(vm)) + if address == 0 || size == 0 { + return Err(vm.new_value_error("NULL pointer access".to_owned())); + } + unsafe { + let ptr = address as *const u8; + let bytes = std::slice::from_raw_parts(ptr, size); + // Get element type and length from cls + let element_type = cls.as_object().get_attr("_type_", vm)?; + let element_type: PyTypeRef = element_type + .downcast() + .map_err(|_| vm.new_type_error("_type_ must be a type".to_owned()))?; + let length = cls + .as_object() + .get_attr("_length_", vm)? + .try_int(vm)? + .as_bigint() + .to_usize() + .unwrap_or(0); + let element_size = if length > 0 { size / length } else { 0 }; + + Ok(PyCArray { + typ: PyRwLock::new(element_type.into()), + length: AtomicCell::new(length), + element_size: AtomicCell::new(element_size), + cdata: PyRwLock::new(CDataObject::from_bytes(bytes.to_vec(), None)), } - } else { - Err(vm.new_value_error("NULL pointer access".to_owned())) + .into_pyobject(vm)) } } @@ -492,10 +497,13 @@ impl PyCArray { let element_size = if length > 0 { size / length } else { 0 }; Ok(PyCArray { - typ: PyRwLock::new(element_type), + typ: PyRwLock::new(element_type.into()), length: AtomicCell::new(length), element_size: AtomicCell::new(element_size), - buffer: PyRwLock::new(data.to_vec()), + cdata: PyRwLock::new(CDataObject::from_bytes( + data.to_vec(), + Some(buffer.obj.clone()), + )), } .into_pyobject(vm)) } @@ -550,10 +558,10 @@ impl PyCArray { let element_size = if length > 0 { size / length } else { 0 }; Ok(PyCArray { - typ: PyRwLock::new(element_type), + typ: PyRwLock::new(element_type.into()), length: AtomicCell::new(length), element_size: AtomicCell::new(element_size), - buffer: PyRwLock::new(data.to_vec()), + cdata: PyRwLock::new(CDataObject::from_bytes(data.to_vec(), None)), } .into_pyobject(vm)) } @@ -562,10 +570,44 @@ 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(); - let ptr = buffer.as_ptr(); - Ok(libffi::middle::Arg::new(&ptr)) + let cdata = self.cdata.read(); + Ok(libffi::middle::Arg::new(&cdata.buffer)) + } +} + +static ARRAY_BUFFER_METHODS: BufferMethods = BufferMethods { + obj_bytes: |buffer| { + rustpython_common::lock::PyMappedRwLockReadGuard::map( + rustpython_common::lock::PyRwLockReadGuard::map( + buffer.obj_as::().cdata.read(), + |x: &CDataObject| x, + ), + |x: &CDataObject| x.buffer.as_slice(), + ) + .into() + }, + obj_bytes_mut: |buffer| { + rustpython_common::lock::PyMappedRwLockWriteGuard::map( + rustpython_common::lock::PyRwLockWriteGuard::map( + buffer.obj_as::().cdata.write(), + |x: &mut CDataObject| x, + ), + |x: &mut CDataObject| x.buffer.as_mut_slice(), + ) + .into() + }, + release: |_| {}, + retain: |_| {}, +}; + +impl AsBuffer for PyCArray { + fn as_buffer(zelf: &Py, _vm: &VirtualMachine) -> PyResult { + let buffer_len = zelf.cdata.read().buffer.len(); + let buf = PyBuffer::new( + zelf.to_owned().into(), + BufferDescriptor::simple(buffer_len, false), // readonly=false for ctypes + &ARRAY_BUFFER_METHODS, + ); + Ok(buf) } } diff --git a/crates/vm/src/stdlib/ctypes/base.rs b/crates/vm/src/stdlib/ctypes/base.rs index b1a81ec1c3..f0a287b8dc 100644 --- a/crates/vm/src/stdlib/ctypes/base.rs +++ b/crates/vm/src/stdlib/ctypes/base.rs @@ -4,9 +4,9 @@ use crate::builtins::PyType; use crate::builtins::{PyBytes, PyFloat, PyInt, PyNone, PyStr, PyTypeRef}; use crate::convert::ToPyObject; use crate::function::{ArgBytesLike, Either, OptionalArg}; -use crate::protocol::{PyBuffer, PyNumberMethods}; +use crate::protocol::{BufferDescriptor, BufferMethods, PyBuffer, PyNumberMethods}; use crate::stdlib::ctypes::_ctypes::new_simple_type; -use crate::types::{AsNumber, Constructor}; +use crate::types::{AsBuffer, AsNumber, Constructor}; use crate::{AsObject, Py, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine}; use crossbeam_utils::atomic::AtomicCell; use num_traits::ToPrimitive; @@ -145,21 +145,54 @@ fn set_primitive(_type_: &str, value: &PyObjectRef, vm: &VirtualMachine) -> PyRe } } -pub struct RawBuffer { +/// Common data object for all ctypes types +#[derive(Debug, Clone)] +pub struct CDataObject { + /// pointer to memory block (b_ptr + b_size) + pub buffer: Vec, + /// pointer to base object or None (b_base) #[allow(dead_code)] - pub inner: Box<[u8]>, - #[allow(dead_code)] - pub size: usize, + pub base: Option, + /// dictionary of references we need to keep (b_objects) + pub objects: Option, +} + +impl CDataObject { + pub fn new(size: usize) -> Self { + CDataObject { + buffer: vec![0u8; size], + base: None, + objects: None, + } + } + + pub fn from_bytes(data: Vec, objects: Option) -> Self { + CDataObject { + buffer: data, + base: None, + objects, + } + } + + #[inline] + pub fn size(&self) -> usize { + self.buffer.len() + } } #[pyclass(name = "_CData", module = "_ctypes")] +#[derive(Debug, PyPayload)] pub struct PyCData { - _objects: AtomicCell>, - _buffer: PyRwLock, + pub cdata: PyRwLock, } -#[pyclass] -impl PyCData {} +#[pyclass(flags(BASETYPE))] +impl PyCData { + #[pygetset] + fn _objects(&self) -> Option { + self.cdata.read().objects.clone() + } +} #[pyclass(module = "_ctypes", name = "PyCSimpleType", base = PyType)] #[derive(Debug, PyPayload)] @@ -230,6 +263,7 @@ impl AsNumber for PyCSimpleType { pub struct PyCSimple { pub _type_: String, pub value: AtomicCell, + pub cdata: PyRwLock, } impl Debug for PyCSimple { @@ -240,6 +274,149 @@ impl Debug for PyCSimple { } } +/// Convert a Python value to bytes based on ctypes type +fn value_to_bytes(_type_: &str, value: &PyObjectRef, vm: &VirtualMachine) -> Vec { + match _type_ { + "c" => { + // c_char - single byte + if let Some(bytes) = value.downcast_ref::() + && !bytes.is_empty() + { + return vec![bytes.as_bytes()[0]]; + } + if let Ok(int_val) = value.try_int(vm) + && let Some(v) = int_val.as_bigint().to_u8() + { + return vec![v]; + } + vec![0] + } + "u" => { + // c_wchar - 4 bytes (wchar_t on most platforms) + if let Ok(s) = value.str(vm) + && let Some(c) = s.as_str().chars().next() + { + return (c as u32).to_ne_bytes().to_vec(); + } + vec![0; 4] + } + "b" => { + // c_byte - signed char (1 byte) + if let Ok(int_val) = value.try_int(vm) + && let Some(v) = int_val.as_bigint().to_i8() + { + return vec![v as u8]; + } + vec![0] + } + "B" => { + // c_ubyte - unsigned char (1 byte) + if let Ok(int_val) = value.try_int(vm) + && let Some(v) = int_val.as_bigint().to_u8() + { + return vec![v]; + } + vec![0] + } + "h" => { + // c_short (2 bytes) + if let Ok(int_val) = value.try_int(vm) + && let Some(v) = int_val.as_bigint().to_i16() + { + return v.to_ne_bytes().to_vec(); + } + vec![0; 2] + } + "H" => { + // c_ushort (2 bytes) + if let Ok(int_val) = value.try_int(vm) + && let Some(v) = int_val.as_bigint().to_u16() + { + return v.to_ne_bytes().to_vec(); + } + vec![0; 2] + } + "i" => { + // c_int (4 bytes) + if let Ok(int_val) = value.try_int(vm) + && let Some(v) = int_val.as_bigint().to_i32() + { + return v.to_ne_bytes().to_vec(); + } + vec![0; 4] + } + "I" => { + // c_uint (4 bytes) + if let Ok(int_val) = value.try_int(vm) + && let Some(v) = int_val.as_bigint().to_u32() + { + return v.to_ne_bytes().to_vec(); + } + vec![0; 4] + } + "l" => { + // c_long (platform dependent) + if let Ok(int_val) = value.try_to_value::(vm) { + return int_val.to_ne_bytes().to_vec(); + } + const SIZE: usize = std::mem::size_of::(); + vec![0; SIZE] + } + "L" => { + // c_ulong (platform dependent) + if let Ok(int_val) = value.try_to_value::(vm) { + return int_val.to_ne_bytes().to_vec(); + } + const SIZE: usize = std::mem::size_of::(); + vec![0; SIZE] + } + "q" => { + // c_longlong (8 bytes) + if let Ok(int_val) = value.try_int(vm) + && let Some(v) = int_val.as_bigint().to_i64() + { + return v.to_ne_bytes().to_vec(); + } + vec![0; 8] + } + "Q" => { + // c_ulonglong (8 bytes) + if let Ok(int_val) = value.try_int(vm) + && let Some(v) = int_val.as_bigint().to_u64() + { + return v.to_ne_bytes().to_vec(); + } + vec![0; 8] + } + "f" => { + // c_float (4 bytes) + if let Ok(float_val) = value.try_float(vm) { + return (float_val.to_f64() as f32).to_ne_bytes().to_vec(); + } + vec![0; 4] + } + "d" | "g" => { + // c_double (8 bytes) + if let Ok(float_val) = value.try_float(vm) { + return float_val.to_f64().to_ne_bytes().to_vec(); + } + vec![0; 8] + } + "?" => { + // c_bool (1 byte) + if let Ok(b) = value.clone().try_to_bool(vm) { + return vec![if b { 1 } else { 0 }]; + } + vec![0] + } + "P" | "z" | "Z" => { + // Pointer types (platform pointer size) + vec![0; std::mem::size_of::()] + } + _ => vec![0], + } +} + impl Constructor for PyCSimple { type Args = (OptionalArg,); @@ -275,16 +452,18 @@ impl Constructor for PyCSimple { _ => vm.ctx.none(), // "z" | "Z" | "P" } }; + let buffer = value_to_bytes(&_type_, &value, vm); PyCSimple { _type_, value: AtomicCell::new(value), + cdata: PyRwLock::new(CDataObject::from_bytes(buffer, None)), } .into_ref_with_type(vm, cls) .map(Into::into) } } -#[pyclass(flags(BASETYPE), with(Constructor))] +#[pyclass(flags(BASETYPE), with(Constructor, AsBuffer))] impl PyCSimple { #[pygetset(name = "value")] pub fn value(instance: PyObjectRef, vm: &VirtualMachine) -> PyResult { @@ -340,6 +519,9 @@ impl PyCSimple { .downcast() .map_err(|_| vm.new_type_error("cannot set value of instance"))?; let content = set_primitive(zelf._type_.as_str(), &value, vm)?; + // Update buffer when value changes + let buffer_bytes = value_to_bytes(&zelf._type_, &content, vm); + zelf.cdata.write().buffer = buffer_bytes; zelf.value.store(content); Ok(()) } @@ -370,7 +552,7 @@ impl PyCSimple { typ: PyRwLock::new(cls.clone().into()), length: AtomicCell::new(n as usize), element_size: AtomicCell::new(element_size), - buffer: PyRwLock::new(vec![]), + cdata: PyRwLock::new(CDataObject::new(0)), }, } .to_pyobject(vm)) @@ -540,3 +722,40 @@ impl PyCSimple { None } } + +static SIMPLE_BUFFER_METHODS: BufferMethods = BufferMethods { + obj_bytes: |buffer| { + rustpython_common::lock::PyMappedRwLockReadGuard::map( + rustpython_common::lock::PyRwLockReadGuard::map( + buffer.obj_as::().cdata.read(), + |x: &CDataObject| x, + ), + |x: &CDataObject| x.buffer.as_slice(), + ) + .into() + }, + obj_bytes_mut: |buffer| { + rustpython_common::lock::PyMappedRwLockWriteGuard::map( + rustpython_common::lock::PyRwLockWriteGuard::map( + buffer.obj_as::().cdata.write(), + |x: &mut CDataObject| x, + ), + |x: &mut CDataObject| x.buffer.as_mut_slice(), + ) + .into() + }, + release: |_| {}, + retain: |_| {}, +}; + +impl AsBuffer for PyCSimple { + fn as_buffer(zelf: &Py, _vm: &VirtualMachine) -> PyResult { + let buffer_len = zelf.cdata.read().buffer.len(); + let buf = PyBuffer::new( + zelf.to_owned().into(), + BufferDescriptor::simple(buffer_len, false), // readonly=false for ctypes + &SIMPLE_BUFFER_METHODS, + ); + Ok(buf) + } +} diff --git a/crates/vm/src/stdlib/ctypes/field.rs b/crates/vm/src/stdlib/ctypes/field.rs index 05e4bdd035..8d6da5808a 100644 --- a/crates/vm/src/stdlib/ctypes/field.rs +++ b/crates/vm/src/stdlib/ctypes/field.rs @@ -5,6 +5,7 @@ use crate::{AsObject, Py, PyObjectRef, PyResult, VirtualMachine}; use num_traits::ToPrimitive; use super::structure::PyCStructure; +use super::union::PyCUnion; #[pyclass(name = "PyCFieldType", base = PyType, module = "_ctypes")] #[derive(PyPayload, Debug)] @@ -102,14 +103,23 @@ impl GetDescriptor for PyCField { _ => return Ok(zelf.into()), }; - // Instance attribute access - read value from the structure's buffer + // Instance attribute access - read value from the structure/union's buffer if let Some(structure) = obj.downcast_ref::() { - let buffer = structure.buffer.read(); + let cdata = structure.cdata.read(); let offset = zelf.byte_offset; let size = zelf.byte_size; - if offset + size <= buffer.len() { - let bytes = &buffer[offset..offset + size]; + if offset + size <= cdata.buffer.len() { + let bytes = &cdata.buffer[offset..offset + size]; + return PyCField::bytes_to_value(bytes, size, vm); + } + } else if let Some(union) = obj.downcast_ref::() { + let cdata = union.cdata.read(); + let offset = zelf.byte_offset; + let size = zelf.byte_size; + + if offset + size <= cdata.buffer.len() { + let bytes = &cdata.buffer[offset..offset + size]; return PyCField::bytes_to_value(bytes, size, vm); } } @@ -187,7 +197,7 @@ impl PyCField { .downcast_ref::() .ok_or_else(|| vm.new_type_error("expected CField".to_owned()))?; - // Get the structure instance - use downcast_ref() to access the struct data + // Get the structure/union instance - use downcast_ref() to access the struct data if let Some(structure) = obj.downcast_ref::() { match value { PySetterValue::Assign(value) => { @@ -195,9 +205,9 @@ impl PyCField { 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); + let mut cdata = structure.cdata.write(); + if offset + size <= cdata.buffer.len() { + cdata.buffer[offset..offset + size].copy_from_slice(&bytes); } Ok(()) } @@ -205,9 +215,26 @@ impl PyCField { Err(vm.new_type_error("cannot delete structure field".to_owned())) } } + } else if let Some(union) = obj.downcast_ref::() { + 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 cdata = union.cdata.write(); + if offset + size <= cdata.buffer.len() { + cdata.buffer[offset..offset + size].copy_from_slice(&bytes); + } + Ok(()) + } + PySetterValue::Delete => { + Err(vm.new_type_error("cannot delete union field".to_owned())) + } + } } else { Err(vm.new_type_error(format!( - "descriptor works only on Structure instances, got {}", + "descriptor works only on Structure or Union instances, got {}", obj.class().name() ))) } diff --git a/crates/vm/src/stdlib/ctypes/pointer.rs b/crates/vm/src/stdlib/ctypes/pointer.rs index 1b14df1577..b370764fa2 100644 --- a/crates/vm/src/stdlib/ctypes/pointer.rs +++ b/crates/vm/src/stdlib/ctypes/pointer.rs @@ -6,6 +6,7 @@ use crate::builtins::{PyType, PyTypeRef}; use crate::convert::ToPyObject; use crate::protocol::PyNumberMethods; use crate::stdlib::ctypes::PyCData; +use crate::stdlib::ctypes::base::CDataObject; use crate::types::AsNumber; use crate::{AsObject, PyObjectRef, PyPayload, PyResult, VirtualMachine}; @@ -31,7 +32,7 @@ impl PyCPointerType { typ: PyRwLock::new(cls.as_object().to_owned()), length: AtomicCell::new(n as usize), element_size: AtomicCell::new(element_size), - buffer: PyRwLock::new(vec![]), + cdata: PyRwLock::new(CDataObject::new(0)), }, } .to_pyobject(vm)) @@ -85,16 +86,15 @@ impl PyCPointer { #[pyclassmethod] fn from_address(cls: PyTypeRef, address: isize, vm: &VirtualMachine) -> PyResult { - if address != 0 { - // Pointer just stores the address value - Ok(PyCPointer { - contents: PyRwLock::new(vm.ctx.new_int(address).into()), - } - .into_ref_with_type(vm, cls)? - .into()) - } else { - Err(vm.new_value_error("NULL pointer access".to_owned())) + if address == 0 { + return Err(vm.new_value_error("NULL pointer access".to_owned())); } + // Pointer just stores the address value + Ok(PyCPointer { + contents: PyRwLock::new(vm.ctx.new_int(address).into()), + } + .into_ref_with_type(vm, cls)? + .into()) } #[pyclassmethod] @@ -132,20 +132,7 @@ impl PyCPointer { // Read pointer value from buffer let bytes = buffer.obj_bytes(); let ptr_bytes = &bytes[offset..offset + size]; - let ptr_val = if size == 8 { - usize::from_ne_bytes([ - ptr_bytes[0], - ptr_bytes[1], - ptr_bytes[2], - ptr_bytes[3], - ptr_bytes[4], - ptr_bytes[5], - ptr_bytes[6], - ptr_bytes[7], - ]) - } else { - u32::from_ne_bytes([ptr_bytes[0], ptr_bytes[1], ptr_bytes[2], ptr_bytes[3]]) as usize - }; + let ptr_val = usize::from_ne_bytes(ptr_bytes.try_into().expect("size is checked above")); Ok(PyCPointer { contents: PyRwLock::new(vm.ctx.new_int(ptr_val).into()), @@ -181,20 +168,7 @@ impl PyCPointer { // Read pointer value from buffer let ptr_bytes = &source_bytes[offset..offset + size]; - let ptr_val = if size == 8 { - usize::from_ne_bytes([ - ptr_bytes[0], - ptr_bytes[1], - ptr_bytes[2], - ptr_bytes[3], - ptr_bytes[4], - ptr_bytes[5], - ptr_bytes[6], - ptr_bytes[7], - ]) - } else { - u32::from_ne_bytes([ptr_bytes[0], ptr_bytes[1], ptr_bytes[2], ptr_bytes[3]]) as usize - }; + let ptr_val = usize::from_ne_bytes(ptr_bytes.try_into().expect("size is checked above")); Ok(PyCPointer { contents: PyRwLock::new(vm.ctx.new_int(ptr_val).into()), diff --git a/crates/vm/src/stdlib/ctypes/structure.rs b/crates/vm/src/stdlib/ctypes/structure.rs index 9570ca8fa9..d7ea19fb0e 100644 --- a/crates/vm/src/stdlib/ctypes/structure.rs +++ b/crates/vm/src/stdlib/ctypes/structure.rs @@ -1,12 +1,12 @@ -use super::base::PyCData; +use super::base::{CDataObject, PyCData}; use super::field::PyCField; use crate::builtins::{PyList, PyStr, PyTuple, PyType, PyTypeRef}; use crate::convert::ToPyObject; use crate::function::FuncArgs; -use crate::protocol::PyNumberMethods; +use crate::protocol::{BufferDescriptor, BufferMethods, PyBuffer, PyNumberMethods}; use crate::stdlib::ctypes::_ctypes::get_size; -use crate::types::{AsNumber, Constructor}; -use crate::{AsObject, PyObjectRef, PyPayload, PyResult, VirtualMachine}; +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; @@ -147,7 +147,7 @@ impl PyCStructType { typ: PyRwLock::new(cls.clone().into()), length: AtomicCell::new(n as usize), element_size: AtomicCell::new(element_size), - buffer: PyRwLock::new(vec![]), + cdata: PyRwLock::new(CDataObject::new(0)), }, } .to_pyobject(vm)) @@ -193,19 +193,17 @@ pub struct FieldInfo { )] #[derive(PyPayload)] pub struct PyCStructure { - /// Raw memory buffer for the structure - pub(super) buffer: PyRwLock>, + /// Common CDataObject for memory buffer + pub(super) cdata: PyRwLock, /// Field information (name -> FieldInfo) #[allow(dead_code)] 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()) + .field("size", &self.cdata.read().size()) .finish() } } @@ -265,12 +263,9 @@ impl Constructor for PyCStructure { } // Initialize buffer with zeros - let buffer = vec![0u8; total_size]; - let instance = PyCStructure { - buffer: PyRwLock::new(buffer), + cdata: PyRwLock::new(CDataObject::new(total_size)), fields: PyRwLock::new(fields_map.clone()), - size: AtomicCell::new(total_size), }; // Handle keyword arguments for field initialization @@ -320,23 +315,21 @@ impl PyCStructure { let size = size_of(Either::A(cls.clone()), vm)?; // Read data from address - if address != 0 && size > 0 { - let data = unsafe { - let ptr = address as *const u8; - std::slice::from_raw_parts(ptr, size).to_vec() - }; + if address == 0 || size == 0 { + return Err(vm.new_value_error("NULL pointer access".to_owned())); + } + let data = unsafe { + let ptr = address as *const u8; + std::slice::from_raw_parts(ptr, size).to_vec() + }; - // Create instance - Ok(PyCStructure { - buffer: PyRwLock::new(data), - fields: PyRwLock::new(HashMap::new()), - size: AtomicCell::new(size), - } - .into_ref_with_type(vm, cls)? - .into()) - } else { - Err(vm.new_value_error("NULL pointer access".to_owned())) + // Create instance + Ok(PyCStructure { + cdata: PyRwLock::new(CDataObject::from_bytes(data, None)), + fields: PyRwLock::new(IndexMap::new()), } + .into_ref_with_type(vm, cls)? + .into()) } #[pyclassmethod] @@ -384,9 +377,8 @@ impl PyCStructure { // Create instance Ok(PyCStructure { - buffer: PyRwLock::new(data), - fields: PyRwLock::new(HashMap::new()), - size: AtomicCell::new(size), + cdata: PyRwLock::new(CDataObject::from_bytes(data, None)), + fields: PyRwLock::new(IndexMap::new()), } .into_ref_with_type(vm, cls)? .into()) @@ -429,11 +421,47 @@ impl PyCStructure { // Create instance Ok(PyCStructure { - buffer: PyRwLock::new(data), - fields: PyRwLock::new(HashMap::new()), - size: AtomicCell::new(size), + cdata: PyRwLock::new(CDataObject::from_bytes(data, None)), + fields: PyRwLock::new(IndexMap::new()), } .into_ref_with_type(vm, cls)? .into()) } } + +static STRUCTURE_BUFFER_METHODS: BufferMethods = BufferMethods { + obj_bytes: |buffer| { + rustpython_common::lock::PyMappedRwLockReadGuard::map( + rustpython_common::lock::PyRwLockReadGuard::map( + buffer.obj_as::().cdata.read(), + |x: &CDataObject| x, + ), + |x: &CDataObject| x.buffer.as_slice(), + ) + .into() + }, + obj_bytes_mut: |buffer| { + rustpython_common::lock::PyMappedRwLockWriteGuard::map( + rustpython_common::lock::PyRwLockWriteGuard::map( + buffer.obj_as::().cdata.write(), + |x: &mut CDataObject| x, + ), + |x: &mut CDataObject| x.buffer.as_mut_slice(), + ) + .into() + }, + release: |_| {}, + retain: |_| {}, +}; + +impl AsBuffer for PyCStructure { + fn as_buffer(zelf: &Py, _vm: &VirtualMachine) -> PyResult { + let buffer_len = zelf.cdata.read().buffer.len(); + let buf = PyBuffer::new( + zelf.to_owned().into(), + BufferDescriptor::simple(buffer_len, false), // readonly=false for ctypes + &STRUCTURE_BUFFER_METHODS, + ); + Ok(buf) + } +} diff --git a/crates/vm/src/stdlib/ctypes/union.rs b/crates/vm/src/stdlib/ctypes/union.rs index 6b571306f8..4142dead4f 100644 --- a/crates/vm/src/stdlib/ctypes/union.rs +++ b/crates/vm/src/stdlib/ctypes/union.rs @@ -1,12 +1,14 @@ -use super::base::PyCData; +use super::base::{CDataObject, PyCData}; use super::field::PyCField; use crate::builtins::{PyList, PyStr, PyTuple, PyType, PyTypeRef}; use crate::convert::ToPyObject; use crate::function::FuncArgs; +use crate::protocol::{BufferDescriptor, BufferMethods, PyBuffer as ProtocolPyBuffer}; use crate::stdlib::ctypes::_ctypes::get_size; -use crate::types::Constructor; -use crate::{PyObjectRef, PyPayload, PyResult, VirtualMachine}; +use crate::types::{AsBuffer, Constructor}; +use crate::{Py, PyObjectRef, PyPayload, PyResult, VirtualMachine}; use num_traits::ToPrimitive; +use rustpython_common::lock::PyRwLock; /// PyCUnionType - metaclass for Union #[pyclass(name = "UnionType", base = PyType, module = "_ctypes")] @@ -114,10 +116,21 @@ impl PyCUnionType {} /// PyCUnion - base class for Union #[pyclass(module = "_ctypes", name = "Union", base = PyCData, metaclass = "PyCUnionType")] -#[derive(Debug, PyPayload)] -pub struct PyCUnion {} +#[derive(PyPayload)] +pub struct PyCUnion { + /// Common CDataObject for memory buffer + pub(super) cdata: PyRwLock, +} + +impl std::fmt::Debug for PyCUnion { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("PyCUnion") + .field("size", &self.cdata.read().size()) + .finish() + } +} -#[pyclass(flags(BASETYPE, IMMUTABLETYPE))] +#[pyclass(flags(BASETYPE, IMMUTABLETYPE), with(AsBuffer))] impl PyCUnion { #[pyclassmethod] fn from_address(cls: PyTypeRef, address: isize, vm: &VirtualMachine) -> PyResult { @@ -127,12 +140,16 @@ impl PyCUnion { // Get size from cls let size = size_of(Either::A(cls.clone()), vm)?; - if address != 0 && size > 0 { - // Create instance (Union doesn't have internal buffer in current impl) - Ok(PyCUnion {}.into_ref_with_type(vm, cls)?.into()) - } else { - Err(vm.new_value_error("NULL pointer access".to_owned())) + // Create instance with data from address + if address == 0 || size == 0 { + return Err(vm.new_value_error("NULL pointer access".to_owned())); + } + + Ok(PyCUnion { + cdata: PyRwLock::new(CDataObject::new(size)), } + .into_ref_with_type(vm, cls)? + .into()) } #[pyclassmethod] @@ -170,7 +187,15 @@ impl PyCUnion { ))); } - Ok(PyCUnion {}.into_ref_with_type(vm, cls)?.into()) + // Copy data from source buffer + let bytes = buffer.obj_bytes(); + let data = bytes[offset..offset + size].to_vec(); + + Ok(PyCUnion { + cdata: PyRwLock::new(CDataObject::from_bytes(data, None)), + } + .into_ref_with_type(vm, cls)? + .into()) } #[pyclassmethod] @@ -201,6 +226,44 @@ impl PyCUnion { ))); } - Ok(PyCUnion {}.into_ref_with_type(vm, cls)?.into()) + // Copy data from source + let data = source_bytes[offset..offset + size].to_vec(); + + Ok(PyCUnion { + cdata: PyRwLock::new(CDataObject::from_bytes(data, None)), + } + .into_ref_with_type(vm, cls)? + .into()) + } +} + +static UNION_BUFFER_METHODS: BufferMethods = BufferMethods { + obj_bytes: |buffer| { + rustpython_common::lock::PyRwLockReadGuard::map( + buffer.obj_as::().cdata.read(), + |x: &CDataObject| x.buffer.as_slice(), + ) + .into() + }, + obj_bytes_mut: |buffer| { + rustpython_common::lock::PyRwLockWriteGuard::map( + buffer.obj_as::().cdata.write(), + |x: &mut CDataObject| x.buffer.as_mut_slice(), + ) + .into() + }, + release: |_| {}, + retain: |_| {}, +}; + +impl AsBuffer for PyCUnion { + fn as_buffer(zelf: &Py, _vm: &VirtualMachine) -> PyResult { + let buffer_len = zelf.cdata.read().buffer.len(); + let buf = ProtocolPyBuffer::new( + zelf.to_owned().into(), + BufferDescriptor::simple(buffer_len, false), // readonly=false for ctypes + &UNION_BUFFER_METHODS, + ); + Ok(buf) } } From 190c4340f99fb2af14d9faa89304e66d8034eb52 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sat, 29 Nov 2025 15:41:03 +0900 Subject: [PATCH 4/9] temp --- crates/vm/src/stdlib/ctypes/base.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/crates/vm/src/stdlib/ctypes/base.rs b/crates/vm/src/stdlib/ctypes/base.rs index f0a287b8dc..a4b4aba751 100644 --- a/crates/vm/src/stdlib/ctypes/base.rs +++ b/crates/vm/src/stdlib/ctypes/base.rs @@ -153,23 +153,41 @@ pub struct CDataObject { /// pointer to base object or None (b_base) #[allow(dead_code)] pub base: Option, + /// index into base's b_objects list (b_index) + #[allow(dead_code)] + pub index: usize, /// dictionary of references we need to keep (b_objects) pub objects: Option, } impl CDataObject { + /// Create new owned buffer with zero-initialized memory pub fn new(size: usize) -> Self { CDataObject { buffer: vec![0u8; size], base: None, + index: 0, objects: None, } } + /// Create from existing bytes (copies data) pub fn from_bytes(data: Vec, objects: Option) -> Self { CDataObject { buffer: data, base: None, + index: 0, + objects, + } + } + + /// Create from base object (copies data from base's buffer at offset) + #[allow(dead_code)] + pub fn from_base(base: PyObjectRef, _offset: usize, size: usize, index: usize, objects: Option) -> Self { + CDataObject { + buffer: vec![0u8; size], + base: Some(base), + index, objects, } } From 8ebb88805bf467b4e175e73ac4f4bf6aaa2a0c77 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sat, 29 Nov 2025 10:08:53 +0900 Subject: [PATCH 5/9] Fix instruction --- .github/copilot-instructions.md | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 46fc16c5ed..1db02dd17a 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -44,7 +44,7 @@ cargo run -- script.py cargo run # With specific features -cargo run --features ssl +cargo run --features jit # Release mode (recommended for better performance) cargo run --release -- script.py @@ -176,13 +176,6 @@ cargo build --target wasm32-wasip1 --no-default-features --features freeze-stdli cargo run --features jit ``` -### SSL Support - -```bash -# Enable SSL support -cargo run --features ssl -``` - ## Test Code Modification Rules **CRITICAL: Test code modification restrictions** From fbf2343020ca8e32dfbffc59c046e1163db2afe3 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sat, 29 Nov 2025 10:40:53 +0900 Subject: [PATCH 6/9] constructable pointer --- crates/vm/src/stdlib/ctypes.rs | 52 +++++++++++++++++++++++- crates/vm/src/stdlib/ctypes/array.rs | 5 +++ crates/vm/src/stdlib/ctypes/base.rs | 5 +++ crates/vm/src/stdlib/ctypes/pointer.rs | 42 ++++++++++++++++--- crates/vm/src/stdlib/ctypes/structure.rs | 5 +++ 5 files changed, 103 insertions(+), 6 deletions(-) diff --git a/crates/vm/src/stdlib/ctypes.rs b/crates/vm/src/stdlib/ctypes.rs index 7357238008..c05ee5c46a 100644 --- a/crates/vm/src/stdlib/ctypes.rs +++ b/crates/vm/src/stdlib/ctypes.rs @@ -12,9 +12,10 @@ pub(crate) mod union; use crate::builtins::PyModule; use crate::class::PyClassImpl; -use crate::stdlib::ctypes::base::{PyCData, PyCSimple, PyCSimpleType}; use crate::{Py, PyRef, VirtualMachine}; +pub use crate::stdlib::ctypes::base::{CDataObject, PyCData, PyCSimple, PyCSimpleType}; + pub fn extend_module_nodes(vm: &VirtualMachine, module: &Py) { let ctx = &vm.ctx; PyCSimpleType::make_class(ctx); @@ -622,4 +623,53 @@ pub(crate) mod _ctypes { // todo!("Implement _cast_addr") 0 } + + #[pyfunction(name = "_cast")] + pub fn pycfunction_cast( + obj: PyObjectRef, + _obj2: PyObjectRef, + ctype: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult { + use super::array::PyCArray; + use super::base::PyCData; + use super::pointer::PyCPointer; + use crate::class::StaticType; + + // Python signature: _cast(obj, obj, ctype) + // Python passes the same object twice (obj and _obj2 are the same) + // We ignore _obj2 as it's redundant + + // Check if this is a pointer type (has _type_ attribute) + if ctype.get_attr("_type_", vm).is_err() { + return Err(vm.new_type_error("cast() argument 2 must be a pointer type".to_string())); + } + + // Create an instance of the target pointer type with no arguments + let result = ctype.call((), vm)?; + + // Get the pointer value from the source object + // If obj is a CData instance (including arrays), use the object itself + // If obj is an integer, use it directly as the pointer value + let ptr_value: PyObjectRef = if obj.fast_isinstance(PyCData::static_type()) + || obj.fast_isinstance(PyCArray::static_type()) + || obj.fast_isinstance(PyCPointer::static_type()) + { + // For CData objects (including arrays and pointers), store the object itself + obj.clone() + } else if let Ok(int_val) = obj.try_int(vm) { + // For integers, treat as pointer address + vm.ctx.new_int(int_val.as_bigint().clone()).into() + } else { + return Err(vm.new_type_error(format!( + "cast() argument 1 must be a ctypes instance or an integer, not {}", + obj.class().name() + ))); + }; + + // Set the contents of the pointer by setting the attribute + result.set_attr("contents", ptr_value, vm)?; + + Ok(result) + } } diff --git a/crates/vm/src/stdlib/ctypes/array.rs b/crates/vm/src/stdlib/ctypes/array.rs index 8196422653..f14e3991f4 100644 --- a/crates/vm/src/stdlib/ctypes/array.rs +++ b/crates/vm/src/stdlib/ctypes/array.rs @@ -245,6 +245,11 @@ impl AsSequence for PyCArray { with(Constructor, AsSequence, AsBuffer) )] impl PyCArray { + #[pygetset] + fn _objects(&self) -> Option { + self.cdata.read().objects.clone() + } + fn int_to_bytes(i: &malachite_bigint::BigInt, size: usize) -> Vec { match size { 1 => vec![i.to_i8().unwrap_or(0) as u8], diff --git a/crates/vm/src/stdlib/ctypes/base.rs b/crates/vm/src/stdlib/ctypes/base.rs index a4b4aba751..fff3f9a5c7 100644 --- a/crates/vm/src/stdlib/ctypes/base.rs +++ b/crates/vm/src/stdlib/ctypes/base.rs @@ -483,6 +483,11 @@ impl Constructor for PyCSimple { #[pyclass(flags(BASETYPE), with(Constructor, AsBuffer))] impl PyCSimple { + #[pygetset] + fn _objects(&self) -> Option { + self.cdata.read().objects.clone() + } + #[pygetset(name = "value")] pub fn value(instance: PyObjectRef, vm: &VirtualMachine) -> PyResult { let zelf: &Py = instance diff --git a/crates/vm/src/stdlib/ctypes/pointer.rs b/crates/vm/src/stdlib/ctypes/pointer.rs index b370764fa2..f0dcb3ebe0 100644 --- a/crates/vm/src/stdlib/ctypes/pointer.rs +++ b/crates/vm/src/stdlib/ctypes/pointer.rs @@ -5,9 +5,8 @@ use rustpython_common::lock::PyRwLock; use crate::builtins::{PyType, PyTypeRef}; use crate::convert::ToPyObject; use crate::protocol::PyNumberMethods; -use crate::stdlib::ctypes::PyCData; -use crate::stdlib::ctypes::base::CDataObject; -use crate::types::AsNumber; +use crate::stdlib::ctypes::{CDataObject, PyCData}; +use crate::types::{AsNumber, Constructor}; use crate::{AsObject, PyObjectRef, PyPayload, PyResult, VirtualMachine}; #[pyclass(name = "PyCPointerType", base = PyType, module = "_ctypes")] @@ -70,7 +69,23 @@ pub struct PyCPointer { contents: PyRwLock, } -#[pyclass(flags(BASETYPE, IMMUTABLETYPE))] +impl Constructor for PyCPointer { + type Args = (crate::function::OptionalArg,); + + fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { + // Get the initial contents value if provided + let initial_contents = args.0.into_option().unwrap_or_else(|| vm.ctx.none()); + + // Create a new PyCPointer instance with the provided value + PyCPointer { + contents: PyRwLock::new(initial_contents), + } + .into_ref_with_type(vm, cls) + .map(Into::into) + } +} + +#[pyclass(flags(BASETYPE, IMMUTABLETYPE), with(Constructor))] impl PyCPointer { // TODO: not correct #[pygetset] @@ -79,11 +94,28 @@ impl PyCPointer { Ok(contents) } #[pygetset(setter)] - fn set_contents(&self, contents: PyObjectRef) -> PyResult<()> { + fn set_contents(&self, contents: PyObjectRef, _vm: &VirtualMachine) -> PyResult<()> { + // Validate that the contents is a CData instance if we have a _type_ + // For now, just store it *self.contents.write() = contents; Ok(()) } + #[pymethod] + fn __init__( + &self, + value: crate::function::OptionalArg, + _vm: &VirtualMachine, + ) -> PyResult<()> { + // Pointer can be initialized with 0 or 1 argument + // If 1 argument is provided, it should be a CData instance + if let crate::function::OptionalArg::Present(val) = value { + *self.contents.write() = val; + } + + Ok(()) + } + #[pyclassmethod] fn from_address(cls: PyTypeRef, address: isize, vm: &VirtualMachine) -> PyResult { if address == 0 { diff --git a/crates/vm/src/stdlib/ctypes/structure.rs b/crates/vm/src/stdlib/ctypes/structure.rs index d7ea19fb0e..6944b4e72c 100644 --- a/crates/vm/src/stdlib/ctypes/structure.rs +++ b/crates/vm/src/stdlib/ctypes/structure.rs @@ -300,6 +300,11 @@ impl Constructor for PyCStructure { #[pyclass(flags(BASETYPE, IMMUTABLETYPE), with(Constructor))] impl PyCStructure { + #[pygetset] + fn _objects(&self) -> Option { + self.cdata.read().objects.clone() + } + #[pygetset] fn _fields_(&self, vm: &VirtualMachine) -> PyObjectRef { // Return the _fields_ from the class, not instance From 2cb2f89c951294f8ab8db864de9870bceba3a5c1 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sat, 29 Nov 2025 11:36:55 +0900 Subject: [PATCH 7/9] thunk --- crates/vm/src/stdlib/ctypes/function.rs | 93 ++++++- crates/vm/src/stdlib/ctypes/thunk.rs | 319 +++++++++++++++++++++++- 2 files changed, 395 insertions(+), 17 deletions(-) diff --git a/crates/vm/src/stdlib/ctypes/function.rs b/crates/vm/src/stdlib/ctypes/function.rs index c1db4d58f8..22933ea5df 100644 --- a/crates/vm/src/stdlib/ctypes/function.rs +++ b/crates/vm/src/stdlib/ctypes/function.rs @@ -5,9 +5,10 @@ use crate::convert::ToPyObject; use crate::function::FuncArgs; use crate::stdlib::ctypes::PyCData; use crate::stdlib::ctypes::base::{PyCSimple, ffi_type_from_str}; +use crate::stdlib::ctypes::thunk::PyCThunk; use crate::types::Representable; use crate::types::{Callable, Constructor}; -use crate::{AsObject, Py, PyObjectRef, PyPayload, PyResult, VirtualMachine}; +use crate::{AsObject, Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine}; use crossbeam_utils::atomic::AtomicCell; use libffi::middle::{Arg, Cif, CodePtr, Type}; use libloading::Symbol; @@ -76,10 +77,38 @@ impl ReturnType for PyTypeRef { fn from_ffi_type( &self, - _value: *mut ffi::c_void, - _vm: &VirtualMachine, + value: *mut ffi::c_void, + vm: &VirtualMachine, ) -> PyResult> { - todo!() + // Get the type code from _type_ attribute + let type_code = self + .get_class_attr(vm.ctx.intern_str("_type_")) + .and_then(|t| t.downcast_ref::().map(|s| s.to_string())); + + let result = match type_code.as_deref() { + Some("b") => vm.ctx.new_int(unsafe { *(value as *const i8) } as i32).into(), + Some("B") => vm.ctx.new_int(unsafe { *(value as *const u8) } as i32).into(), + Some("c") => vm.ctx.new_bytes(vec![unsafe { *(value as *const u8) }]).into(), + Some("h") => vm.ctx.new_int(unsafe { *(value as *const i16) } as i32).into(), + Some("H") => vm.ctx.new_int(unsafe { *(value as *const u16) } as i32).into(), + Some("i") => vm.ctx.new_int(unsafe { *(value as *const i32) }).into(), + Some("I") => vm.ctx.new_int(unsafe { *(value as *const u32) }).into(), + Some("l") => vm.ctx.new_int(unsafe { *(value as *const i64) }).into(), + Some("L") => vm.ctx.new_int(unsafe { *(value as *const u64) }).into(), + Some("q") => vm.ctx.new_int(unsafe { *(value as *const i64) }).into(), + Some("Q") => vm.ctx.new_int(unsafe { *(value as *const u64) }).into(), + Some("f") => vm.ctx.new_float(unsafe { *(value as *const f32) } as f64).into(), + Some("d") => vm.ctx.new_float(unsafe { *(value as *const f64) }).into(), + Some("P") | Some("z") | Some("Z") => vm.ctx.new_int(value as usize).into(), + Some("?") => vm.ctx.new_bool(unsafe { *(value as *const u8) } != 0).into(), + None => { + // No _type_ attribute, try to create an instance of the type + // This handles cases like Structure or Array return types + return Ok(Some(vm.ctx.new_int(unsafe { *(value as *const i32) }).into())); + } + _ => return Err(vm.new_type_error("Unsupported return type".to_string())), + }; + Ok(Some(result)) } } @@ -219,6 +248,49 @@ impl Constructor for PyCFuncPtr { .map(Into::into); } + // Check if first argument is a Python callable (callback creation) + if first_arg.is_callable() { + // Get argument types and result type from the class + let argtypes = cls.get_attr(vm.ctx.intern_str("_argtypes_")); + let restype = cls.get_attr(vm.ctx.intern_str("_restype_")); + + // Create the thunk (C-callable wrapper for the Python function) + let thunk = PyCThunk::new(first_arg.clone(), argtypes.clone(), restype.clone(), vm)?; + let code_ptr = thunk.code_ptr(); + + // Parse argument types for storage + let arg_type_vec: Option> = if let Some(ref args) = argtypes { + if vm.is_none(args) { + None + } else { + let mut types = Vec::new(); + for item in args.try_to_value::>(vm)? { + types.push(item.downcast::().map_err(|_| { + vm.new_type_error("_argtypes_ must be a sequence of types".to_string()) + })?); + } + Some(types) + } + } else { + None + }; + + // Store the thunk as a Python object to keep it alive + let thunk_ref: PyRef = thunk.into_ref(&vm.ctx); + + return Self { + ptr: PyRwLock::new(Some(code_ptr)), + needs_free: AtomicCell::new(true), + arg_types: PyRwLock::new(arg_type_vec), + _flags_: AtomicCell::new(0), + res_type: PyRwLock::new(restype), + name: PyRwLock::new(Some("".to_string())), + handler: thunk_ref.into(), + } + .into_ref_with_type(vm, cls) + .map(Into::into); + } + Err(vm.new_type_error("Expected an integer address or a tuple")) } } @@ -246,7 +318,8 @@ impl Callable for PyCFuncPtr { let return_type = zelf.res_type.read(); let ffi_return_type = return_type .as_ref() - .and_then(|t| ReturnType::to_ffi_type(&t.clone().downcast::().unwrap())) + .and_then(|t| t.clone().downcast::().ok()) + .and_then(|t| ReturnType::to_ffi_type(&t)) .unwrap_or_else(Type::i32); let cif = Cif::new(ffi_arg_types, ffi_return_type); @@ -269,14 +342,8 @@ impl Callable for PyCFuncPtr { let mut output: c_void = unsafe { cif.call(*code_ptr, &ffi_args) }; let return_type = return_type .as_ref() - .map(|f| { - f.clone() - .downcast::() - .unwrap() - .from_ffi_type(&mut output, vm) - .ok() - .flatten() - }) + .and_then(|f| f.clone().downcast::().ok()) + .map(|f| f.from_ffi_type(&mut output, vm).ok().flatten()) .unwrap_or_else(|| Some(vm.ctx.new_int(output as i32).as_object().to_pyobject(vm))); if let Some(return_type) = return_type { Ok(return_type) diff --git a/crates/vm/src/stdlib/ctypes/thunk.rs b/crates/vm/src/stdlib/ctypes/thunk.rs index a65b04684b..51959af4c0 100644 --- a/crates/vm/src/stdlib/ctypes/thunk.rs +++ b/crates/vm/src/stdlib/ctypes/thunk.rs @@ -1,5 +1,21 @@ -//! Yes, really, this is not a typo. +//! FFI callback (thunk) implementation for ctypes. +//! +//! This module implements CThunkObject which wraps Python callables +//! to be callable from C code via libffi closures. +use crate::builtins::{PyStr, PyType, PyTypeRef}; +use crate::vm::thread::with_current_vm; +use crate::{PyObjectRef, PyPayload, PyResult, VirtualMachine}; +use libffi::low; +use libffi::middle::{Cif, Closure, CodePtr, Type}; +use num_traits::ToPrimitive; +use rustpython_common::lock::PyRwLock; +use std::ffi::c_void; +use std::fmt::Debug; + +use super::base::ffi_type_from_str; + +// CPython structure for reference: // typedef struct { // PyObject_VAR_HEAD // ffi_closure *pcl_write; /* the C callable, writeable */ @@ -14,9 +30,304 @@ // ffi_type *atypes[1]; // } CThunkObject; +/// Userdata passed to the libffi callback. +/// This contains everything needed to invoke the Python callable. +pub struct ThunkUserData { + /// The Python callable to invoke + pub callable: PyObjectRef, + /// Argument types for conversion + pub arg_types: Vec, + /// Result type for conversion (None means void) + pub res_type: Option, +} + +/// Get the type code string from a ctypes type +fn get_type_code(ty: &PyTypeRef, vm: &VirtualMachine) -> Option { + ty.get_attr(vm.ctx.intern_str("_type_")) + .and_then(|t| t.downcast_ref::().map(|s| s.to_string())) +} + +/// Convert a C value to a Python object based on the type code +fn ffi_to_python(ty: &PyTypeRef, ptr: *const c_void, vm: &VirtualMachine) -> PyObjectRef { + let type_code = get_type_code(ty, vm); + // SAFETY: ptr is guaranteed to be valid by libffi calling convention + unsafe { + match type_code.as_deref() { + Some("b") => vm.ctx.new_int(*(ptr as *const i8) as i32).into(), + Some("B") => vm.ctx.new_int(*(ptr as *const u8) as i32).into(), + Some("c") => vm.ctx.new_bytes(vec![*(ptr as *const u8)]).into(), + Some("h") => vm.ctx.new_int(*(ptr as *const i16) as i32).into(), + Some("H") => vm.ctx.new_int(*(ptr as *const u16) as i32).into(), + Some("i") => vm.ctx.new_int(*(ptr as *const i32)).into(), + Some("I") => vm.ctx.new_int(*(ptr as *const u32)).into(), + Some("l") => vm.ctx.new_int(*(ptr as *const i64)).into(), + Some("L") => vm.ctx.new_int(*(ptr as *const u64)).into(), + Some("q") => vm.ctx.new_int(*(ptr as *const i64)).into(), + Some("Q") => vm.ctx.new_int(*(ptr as *const u64)).into(), + Some("f") => vm.ctx.new_float(*(ptr as *const f32) as f64).into(), + Some("d") => vm.ctx.new_float(*(ptr as *const f64)).into(), + Some("P") | Some("z") | Some("Z") => vm.ctx.new_int(ptr as usize).into(), + _ => vm.ctx.none(), + } + } +} + +/// Convert a Python object to a C value and store it at the result pointer +fn python_to_ffi(obj: PyResult, ty: &PyTypeRef, result: *mut c_void, vm: &VirtualMachine) { + let obj = match obj { + Ok(o) => o, + Err(_) => return, // Exception occurred, leave result as-is + }; + + let type_code = get_type_code(ty, vm); + // SAFETY: result is guaranteed to be valid by libffi calling convention + unsafe { + match type_code.as_deref() { + Some("b") => { + if let Ok(i) = obj.try_int(vm) { + *(result as *mut i8) = i.as_bigint().to_i8().unwrap_or(0); + } + } + Some("B") => { + if let Ok(i) = obj.try_int(vm) { + *(result as *mut u8) = i.as_bigint().to_u8().unwrap_or(0); + } + } + Some("c") => { + if let Ok(i) = obj.try_int(vm) { + *(result as *mut u8) = i.as_bigint().to_u8().unwrap_or(0); + } + } + Some("h") => { + if let Ok(i) = obj.try_int(vm) { + *(result as *mut i16) = i.as_bigint().to_i16().unwrap_or(0); + } + } + Some("H") => { + if let Ok(i) = obj.try_int(vm) { + *(result as *mut u16) = i.as_bigint().to_u16().unwrap_or(0); + } + } + Some("i") => { + if let Ok(i) = obj.try_int(vm) { + *(result as *mut i32) = i.as_bigint().to_i32().unwrap_or(0); + } + } + Some("I") => { + if let Ok(i) = obj.try_int(vm) { + *(result as *mut u32) = i.as_bigint().to_u32().unwrap_or(0); + } + } + Some("l") | Some("q") => { + if let Ok(i) = obj.try_int(vm) { + *(result as *mut i64) = i.as_bigint().to_i64().unwrap_or(0); + } + } + Some("L") | Some("Q") => { + if let Ok(i) = obj.try_int(vm) { + *(result as *mut u64) = i.as_bigint().to_u64().unwrap_or(0); + } + } + Some("f") => { + if let Ok(f) = obj.try_float(vm) { + *(result as *mut f32) = f.to_f64() as f32; + } + } + Some("d") => { + if let Ok(f) = obj.try_float(vm) { + *(result as *mut f64) = f.to_f64(); + } + } + Some("P") | Some("z") | Some("Z") => { + if let Ok(i) = obj.try_int(vm) { + *(result as *mut usize) = i.as_bigint().to_usize().unwrap_or(0); + } + } + _ => {} + } + } +} + +/// The callback function that libffi calls when the closure is invoked. +/// This function converts C arguments to Python objects, calls the Python +/// callable, and converts the result back to C. +unsafe extern "C" fn thunk_callback( + _cif: &low::ffi_cif, + result: &mut c_void, + args: *const *const c_void, + userdata: &ThunkUserData, +) { + with_current_vm(|vm| { + // Convert C arguments to Python objects + let py_args: Vec = userdata + .arg_types + .iter() + .enumerate() + .map(|(i, ty)| { + let arg_ptr = unsafe { *args.add(i) }; + ffi_to_python(ty, arg_ptr, vm) + }) + .collect(); + + // Call the Python callable + let py_result = userdata.callable.call(py_args, vm); + + // Convert result back to C type + if let Some(ref res_type) = userdata.res_type { + python_to_ffi(py_result, res_type, result as *mut c_void, vm); + } + }); +} + +/// Holds the closure and userdata together to ensure proper lifetime. +/// The userdata is leaked to create a 'static reference that the closure can use. +struct ThunkData { + closure: Closure<'static>, + /// Raw pointer to the leaked userdata, for cleanup + userdata_ptr: *mut ThunkUserData, +} + +impl Drop for ThunkData { + fn drop(&mut self) { + // SAFETY: We created this with Box::into_raw, so we can reclaim it + unsafe { + drop(Box::from_raw(self.userdata_ptr)); + } + } +} + +/// CThunkObject wraps a Python callable to make it callable from C code. #[pyclass(name = "CThunkObject", module = "_ctypes")] -#[derive(Debug, PyPayload)] -pub struct PyCThunk {} +#[derive(PyPayload)] +pub struct PyCThunk { + /// The Python callable + callable: PyObjectRef, + /// The libffi closure (must be kept alive) + #[allow(dead_code)] + thunk_data: PyRwLock>, + /// The code pointer for the closure + code_ptr: CodePtr, +} + +impl Debug for PyCThunk { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("PyCThunk") + .field("callable", &self.callable) + .finish() + } +} + +impl PyCThunk { + /// Create a new thunk wrapping a Python callable. + /// + /// # Arguments + /// * `callable` - The Python callable to wrap + /// * `arg_types` - Optional sequence of argument types + /// * `res_type` - Optional result type + /// * `vm` - The virtual machine + pub fn new( + callable: PyObjectRef, + arg_types: Option, + res_type: Option, + vm: &VirtualMachine, + ) -> PyResult { + // Parse argument types + let arg_type_vec: Vec = if let Some(args) = arg_types { + if vm.is_none(&args) { + Vec::new() + } else { + let mut types = Vec::new(); + for item in args.try_to_value::>(vm)? { + types.push(item.downcast::().map_err(|_| { + vm.new_type_error("_argtypes_ must be a sequence of types".to_string()) + })?); + } + types + } + } else { + Vec::new() + }; + + // Parse result type + let res_type_ref: Option = if let Some(ref rt) = res_type { + if vm.is_none(rt) { + None + } else { + Some(rt.clone().downcast::().map_err(|_| { + vm.new_type_error("restype must be a ctypes type".to_string()) + })?) + } + } else { + None + }; + + // Build FFI types + let ffi_arg_types: Vec = arg_type_vec + .iter() + .map(|ty| { + get_type_code(ty, vm) + .and_then(|code| ffi_type_from_str(&code)) + .unwrap_or(Type::pointer()) + }) + .collect(); + + let ffi_res_type = res_type_ref + .as_ref() + .and_then(|ty| get_type_code(ty, vm)) + .and_then(|code| ffi_type_from_str(&code)) + .unwrap_or(Type::void()); + + // Create the CIF + let cif = Cif::new(ffi_arg_types, ffi_res_type); + + // Create userdata and leak it to get a 'static reference + let userdata = Box::new(ThunkUserData { + callable: callable.clone(), + arg_types: arg_type_vec, + res_type: res_type_ref, + }); + let userdata_ptr = Box::into_raw(userdata); + + // SAFETY: We maintain the userdata lifetime by storing it in ThunkData + // and cleaning it up in Drop + let userdata_ref: &'static ThunkUserData = unsafe { &*userdata_ptr }; + + // Create the closure + let closure = Closure::new(cif, thunk_callback, userdata_ref); + + // Get the code pointer + let code_ptr = CodePtr(*closure.code_ptr() as *mut _); + + // Store closure and userdata together + let thunk_data = ThunkData { + closure, + userdata_ptr, + }; + + Ok(Self { + callable, + thunk_data: PyRwLock::new(Some(thunk_data)), + code_ptr, + }) + } + + /// Get the code pointer for this thunk + pub fn code_ptr(&self) -> CodePtr { + self.code_ptr + } +} + +// SAFETY: PyCThunk is safe to send/sync because: +// - callable is a PyObjectRef which is Send+Sync +// - thunk_data contains the libffi closure which is heap-allocated +// - code_ptr is just a pointer to executable memory +unsafe impl Send for PyCThunk {} +unsafe impl Sync for PyCThunk {} #[pyclass] -impl PyCThunk {} +impl PyCThunk { + #[pygetset] + fn callable(&self) -> PyObjectRef { + self.callable.clone() + } +} From bea4eec730717f73ebe95b0d9147b66e8f252c94 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sat, 29 Nov 2025 14:00:12 +0900 Subject: [PATCH 8/9] in_dll --- crates/vm/src/stdlib/ctypes.rs | 101 +++++++++++-- crates/vm/src/stdlib/ctypes/array.rs | 182 ++++++++++++++++++++++- crates/vm/src/stdlib/ctypes/base.rs | 78 +++++++++- crates/vm/src/stdlib/ctypes/pointer.rs | 55 +++++++ crates/vm/src/stdlib/ctypes/structure.rs | 9 +- crates/vm/src/stdlib/ctypes/union.rs | 60 +++++++- 6 files changed, 452 insertions(+), 33 deletions(-) diff --git a/crates/vm/src/stdlib/ctypes.rs b/crates/vm/src/stdlib/ctypes.rs index c05ee5c46a..4aa4768b20 100644 --- a/crates/vm/src/stdlib/ctypes.rs +++ b/crates/vm/src/stdlib/ctypes.rs @@ -172,7 +172,7 @@ pub(crate) mod _ctypes { } } // Fall back to sizeof - size_of(Either::A(cls.clone()), vm) + size_of(cls.clone().into(), vm) } /// Convert bytes to appropriate Python object based on ctypes type @@ -377,20 +377,99 @@ pub(crate) mod _ctypes { } } + /// Get the size of a ctypes type or instance + /// + /// This function accepts: + /// - A ctypes type (e.g., c_int, Structure subclass) + /// - A ctypes instance (e.g., c_int(5)) #[pyfunction(name = "sizeof")] - pub fn size_of(tp: Either, vm: &VirtualMachine) -> PyResult { - match tp { - Either::A(type_) if type_.fast_issubclass(PyCSimple::static_type()) => { - let zelf = new_simple_type(Either::B(&type_), vm)?; - Ok(get_size(zelf._type_.as_str())) + pub fn size_of(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { + use super::array::PyCArray; + use super::pointer::PyCPointer; + use super::structure::PyCStructure; + use super::union::PyCUnion; + + // Check if obj is a type + if let Ok(type_ref) = obj.clone().downcast::() { + // It's a type - check what kind of ctypes type it is + + // Simple types (c_int, c_char, etc.) + if type_ref.fast_issubclass(PyCSimple::static_type()) { + let zelf = new_simple_type(Either::B(&type_ref), vm)?; + return Ok(get_size(zelf._type_.as_str())); + } + + // Array types + if type_ref.fast_issubclass(PyCArray::static_type()) { + // Get _length_ and element size + if let Ok(length) = type_ref.as_object().get_attr("_length_", vm) { + let length = usize::try_from_object(vm, length)?; + if let Ok(elem_type) = type_ref.as_object().get_attr("_type_", vm) { + let elem_size = size_of(elem_type, vm)?; + return Ok(length * elem_size); + } + } } - Either::B(obj) if obj.has_attr("size_of_instances", vm)? => { - let size_of_method = obj.get_attr("size_of_instances", vm)?; - let size_of_return = size_of_method.call(vec![], vm)?; - Ok(usize::try_from_object(vm, size_of_return)?) + + // Structure types - check for size_of_instances method + if type_ref.fast_issubclass(PyCStructure::static_type()) + || type_ref.fast_issubclass(PyCUnion::static_type()) + { + if let Ok(size_method) = type_ref.as_object().get_attr("size_of_instances", vm) { + let size = size_method.call(vec![], vm)?; + return Ok(usize::try_from_object(vm, size)?); + } + } + + // Pointer types + if type_ref.fast_issubclass(PyCPointer::static_type()) { + return Ok(std::mem::size_of::()); } - _ => Err(vm.new_type_error("this type has no size")), + + // Check for size_of_instances as a fallback + if let Ok(size_method) = type_ref.as_object().get_attr("size_of_instances", vm) { + let size = size_method.call(vec![], vm)?; + return Ok(usize::try_from_object(vm, size)?); + } + + return Err(vm.new_type_error("this type has no size")); } + + // It's an instance - get size from instance or its class + + // Simple type instance + if let Ok(simple) = obj.clone().downcast::() { + return Ok(get_size(simple._type_.as_str())); + } + + // Array instance + if let Ok(array) = obj.clone().downcast::() { + return Ok(array.cdata.read().size()); + } + + // Structure instance + if let Ok(structure) = obj.clone().downcast::() { + return Ok(structure.cdata.read().size()); + } + + // Union instance + if let Ok(union) = obj.clone().downcast::() { + return Ok(union.cdata.read().size()); + } + + // Pointer instance + if obj.fast_isinstance(PyCPointer::static_type()) { + return Ok(std::mem::size_of::()); + } + + // Check if the object has size_of_instances method (for custom types) + if obj.has_attr("size_of_instances", vm)? { + let size_method = obj.get_attr("size_of_instances", vm)?; + let size = size_method.call(vec![], vm)?; + return Ok(usize::try_from_object(vm, size)?); + } + + Err(vm.new_type_error("this type has no size")) } #[cfg(windows)] diff --git a/crates/vm/src/stdlib/ctypes/array.rs b/crates/vm/src/stdlib/ctypes/array.rs index f14e3991f4..0a90346e09 100644 --- a/crates/vm/src/stdlib/ctypes/array.rs +++ b/crates/vm/src/stdlib/ctypes/array.rs @@ -114,6 +114,87 @@ impl PyCArrayType { } .to_pyobject(vm)) } + + #[pyclassmethod] + fn in_dll( + zelf: &Py, + 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 + let handle = if let Ok(int_handle) = dll.try_int(vm) { + // dll is an integer handle + int_handle + .as_bigint() + .to_usize() + .ok_or_else(|| vm.new_value_error("Invalid library handle".to_owned()))? + } else { + // dll is a CDLL/PyDLL/WinDLL object with _handle attribute + dll.get_attr("_handle", vm)? + .try_int(vm)? + .as_bigint() + .to_usize() + .ok_or_else(|| vm.new_value_error("Invalid library handle".to_owned()))? + }; + + // Get the library from cache + let library_cache = crate::stdlib::ctypes::library::libcache().read(); + let library = library_cache + .get_lib(handle) + .ok_or_else(|| vm.new_attribute_error("Library not found".to_owned()))?; + + // Get symbol address from library + let symbol_name = format!("{}\0", name.as_str()); + let inner_lib = library.lib.lock(); + + let symbol_address = if let Some(lib) = &*inner_lib { + unsafe { + // Try to get the symbol from the library + let symbol: Symbol<'_, *mut u8> = lib.get(symbol_name.as_bytes()).map_err(|e| { + vm.new_attribute_error(format!("{}: symbol '{}' not found", e, name.as_str())) + })?; + *symbol as usize + } + } else { + return Err(vm.new_attribute_error("Library is closed".to_owned())); + }; + + // Get size from the array type + let element_type = zelf.inner.typ.read().clone(); + let length = zelf.inner.length.load(); + let element_size = size_of(element_type.clone(), vm)?; + let total_size = element_size * length; + + // Read data from symbol address + let data = if symbol_address != 0 && total_size > 0 { + unsafe { + let ptr = symbol_address as *const u8; + std::slice::from_raw_parts(ptr, total_size).to_vec() + } + } else { + vec![0; total_size] + }; + + // Create instance + let instance = PyCArray { + typ: PyRwLock::new(element_type), + length: AtomicCell::new(length), + element_size: AtomicCell::new(element_size), + cdata: PyRwLock::new(CDataObject::from_bytes(data)), + } + .into_pyobject(vm); + + // Store base reference to keep dll alive + if let Ok(array_ref) = instance.clone().downcast::() { + array_ref.cdata.write().base = Some(dll); + } + + Ok(instance) + } } impl AsNumber for PyCArrayType { @@ -407,11 +488,10 @@ impl PyCArray { #[pyclassmethod] fn from_address(cls: PyTypeRef, address: isize, vm: &VirtualMachine) -> PyResult { - use crate::function::Either; use crate::stdlib::ctypes::_ctypes::size_of; // Get size from cls - let size = size_of(Either::A(cls.clone()), vm)?; + let size = size_of(cls.clone().into(), vm)?; // Create instance with data from address if address == 0 || size == 0 { @@ -452,7 +532,6 @@ impl PyCArray { vm: &VirtualMachine, ) -> PyResult { use crate::TryFromObject; - use crate::function::Either; use crate::protocol::PyBuffer; use crate::stdlib::ctypes::_ctypes::size_of; @@ -471,7 +550,7 @@ impl PyCArray { } // Get size from cls - let size = size_of(Either::A(cls.clone()), vm)?; + let size = size_of(cls.clone().into(), vm)?; // Check if buffer is large enough let buffer_len = buffer.desc.len; @@ -520,7 +599,6 @@ impl PyCArray { offset: crate::function::OptionalArg, vm: &VirtualMachine, ) -> PyResult { - use crate::function::Either; use crate::stdlib::ctypes::_ctypes::size_of; let offset = offset.unwrap_or(0); @@ -530,7 +608,7 @@ impl PyCArray { let offset = offset as usize; // Get size from cls - let size = size_of(Either::A(cls.clone()), vm)?; + let size = size_of(cls.clone().into(), vm)?; // Borrow bytes from source let source_bytes = source.borrow_buf(); @@ -570,6 +648,98 @@ impl PyCArray { } .into_pyobject(vm)) } + + #[pyclassmethod] + fn in_dll( + cls: PyTypeRef, + 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 + let handle = if let Ok(int_handle) = dll.try_int(vm) { + // dll is an integer handle + int_handle + .as_bigint() + .to_usize() + .ok_or_else(|| vm.new_value_error("Invalid library handle".to_owned()))? + } else { + // dll is a CDLL/PyDLL/WinDLL object with _handle attribute + dll.get_attr("_handle", vm)? + .try_int(vm)? + .as_bigint() + .to_usize() + .ok_or_else(|| vm.new_value_error("Invalid library handle".to_owned()))? + }; + + // Get the library from cache + let library_cache = crate::stdlib::ctypes::library::libcache().read(); + let library = library_cache + .get_lib(handle) + .ok_or_else(|| vm.new_attribute_error("Library not found".to_owned()))?; + + // Get symbol address from library + let symbol_name = format!("{}\0", name.as_str()); + let inner_lib = library.lib.lock(); + + let symbol_address = if let Some(lib) = &*inner_lib { + unsafe { + // Try to get the symbol from the library + let symbol: Symbol<'_, *mut u8> = lib.get(symbol_name.as_bytes()).map_err(|e| { + vm.new_attribute_error(format!("{}: symbol '{}' not found", e, name.as_str())) + })?; + *symbol as usize + } + } else { + return Err(vm.new_attribute_error("Library is closed".to_owned())); + }; + + // Get size from cls + let size = size_of(cls.clone().into(), vm)?; + + // Read data from symbol address + let data = if symbol_address != 0 && size > 0 { + unsafe { + let ptr = symbol_address as *const u8; + std::slice::from_raw_parts(ptr, size).to_vec() + } + } else { + vec![0; size] + }; + + // Get element type and length from cls + let element_type = cls.as_object().get_attr("_type_", vm)?; + let element_type: PyTypeRef = element_type + .downcast() + .map_err(|_| vm.new_type_error("_type_ must be a type".to_owned()))?; + let length = cls + .as_object() + .get_attr("_length_", vm)? + .try_int(vm)? + .as_bigint() + .to_usize() + .unwrap_or(0); + let element_size = if length > 0 { size / length } else { 0 }; + + // Create instance + let instance = PyCArray { + typ: PyRwLock::new(element_type.into()), + length: AtomicCell::new(length), + element_size: AtomicCell::new(element_size), + cdata: PyRwLock::new(CDataObject::from_bytes(data)), + } + .into_pyobject(vm); + + // Store base reference to keep dll alive + if let Ok(array_ref) = instance.clone().downcast::() { + array_ref.cdata.write().base = Some(dll); + } + + Ok(instance) + } } impl PyCArray { diff --git a/crates/vm/src/stdlib/ctypes/base.rs b/crates/vm/src/stdlib/ctypes/base.rs index fff3f9a5c7..f4b279ef5a 100644 --- a/crates/vm/src/stdlib/ctypes/base.rs +++ b/crates/vm/src/stdlib/ctypes/base.rs @@ -1,7 +1,6 @@ use super::_ctypes::bytes_to_pyobject; use super::array::{PyCArray, PyCArrayType}; -use crate::builtins::PyType; -use crate::builtins::{PyBytes, PyFloat, PyInt, PyNone, PyStr, PyTypeRef}; +use crate::builtins::{PyBytes, PyFloat, PyInt, PyNone, PyStr, PyStrRef, PyType, PyTypeRef}; use crate::convert::ToPyObject; use crate::function::{ArgBytesLike, Either, OptionalArg}; use crate::protocol::{BufferDescriptor, BufferMethods, PyBuffer, PyNumberMethods}; @@ -705,6 +704,81 @@ impl PyCSimple { // Create instance (independent copy, no reference tracking) PyCSimple::py_new(cls.clone(), (OptionalArg::Present(value),), vm) } + + #[pyclassmethod] + fn in_dll(cls: PyTypeRef, dll: PyObjectRef, name: PyStrRef, vm: &VirtualMachine) -> PyResult { + use super::_ctypes::get_size; + use libloading::Symbol; + + // Get the library handle from dll object + let handle = if let Ok(int_handle) = dll.try_int(vm) { + // dll is an integer handle + int_handle + .as_bigint() + .to_usize() + .ok_or_else(|| vm.new_value_error("Invalid library handle".to_owned()))? + } else { + // dll is a CDLL/PyDLL/WinDLL object with _handle attribute + dll.get_attr("_handle", vm)? + .try_int(vm)? + .as_bigint() + .to_usize() + .ok_or_else(|| vm.new_value_error("Invalid library handle".to_owned()))? + }; + + // Get the library from cache + let library_cache = crate::stdlib::ctypes::library::libcache().read(); + let library = library_cache + .get_lib(handle) + .ok_or_else(|| vm.new_attribute_error("Library not found".to_owned()))?; + + // Get symbol address from library + let symbol_name = format!("{}\0", name.as_str()); + let inner_lib = library.lib.lock(); + + let symbol_address = if let Some(lib) = &*inner_lib { + unsafe { + // Try to get the symbol from the library + let symbol: Symbol<'_, *mut u8> = lib.get(symbol_name.as_bytes()).map_err(|e| { + vm.new_attribute_error(format!("{}: symbol '{}' not found", e, name.as_str())) + })?; + *symbol as usize + } + } else { + return Err(vm.new_attribute_error("Library is closed".to_owned())); + }; + + // Get _type_ attribute and size + let type_attr = cls + .as_object() + .get_attr("_type_", vm) + .map_err(|_| vm.new_type_error(format!("'{}' has no _type_ attribute", cls.name())))?; + let type_str = type_attr.str(vm)?.to_string(); + let size = get_size(&type_str); + + // Read value from symbol address + let value = if symbol_address != 0 && size > 0 { + // Safety: Reading from a symbol address provided by dlsym + // This is the same as CPython's implementation + unsafe { + let ptr = symbol_address as *const u8; + let bytes = std::slice::from_raw_parts(ptr, size); + bytes_to_pyobject(&cls, bytes, vm)? + } + } else { + vm.ctx.none() + }; + + // Create instance + let instance = PyCSimple::py_new(cls.clone(), (OptionalArg::Present(value),), vm)?; + + // Store base reference to keep dll alive (like CPython does) + if let Ok(simple_ref) = instance.clone().downcast::() { + simple_ref.cdata.write().base = Some(dll); + } + + Ok(instance) + } } impl PyCSimple { diff --git a/crates/vm/src/stdlib/ctypes/pointer.rs b/crates/vm/src/stdlib/ctypes/pointer.rs index f0dcb3ebe0..5e41f9b651 100644 --- a/crates/vm/src/stdlib/ctypes/pointer.rs +++ b/crates/vm/src/stdlib/ctypes/pointer.rs @@ -208,4 +208,59 @@ impl PyCPointer { .into_ref_with_type(vm, cls)? .into()) } + + #[pyclassmethod] + fn in_dll( + cls: PyTypeRef, + dll: PyObjectRef, + name: crate::builtins::PyStrRef, + vm: &VirtualMachine, + ) -> PyResult { + use libloading::Symbol; + + // Get the library handle from dll object + let handle = if let Ok(int_handle) = dll.try_int(vm) { + // dll is an integer handle + int_handle + .as_bigint() + .to_usize() + .ok_or_else(|| vm.new_value_error("Invalid library handle".to_owned()))? + } else { + // dll is a CDLL/PyDLL/WinDLL object with _handle attribute + dll.get_attr("_handle", vm)? + .try_int(vm)? + .as_bigint() + .to_usize() + .ok_or_else(|| vm.new_value_error("Invalid library handle".to_owned()))? + }; + + // Get the library from cache + let library_cache = crate::stdlib::ctypes::library::libcache().read(); + let library = library_cache + .get_lib(handle) + .ok_or_else(|| vm.new_attribute_error("Library not found".to_owned()))?; + + // Get symbol address from library + let symbol_name = format!("{}\0", name.as_str()); + let inner_lib = library.lib.lock(); + + let symbol_address = if let Some(lib) = &*inner_lib { + unsafe { + // Try to get the symbol from the library + let symbol: Symbol<'_, *mut u8> = lib.get(symbol_name.as_bytes()).map_err(|e| { + vm.new_attribute_error(format!("{}: symbol '{}' not found", e, name.as_str())) + })?; + *symbol as usize + } + } else { + return Err(vm.new_attribute_error("Library is closed".to_owned())); + }; + + // For pointer types, we return a pointer to the symbol address + Ok(PyCPointer { + contents: PyRwLock::new(vm.ctx.new_int(symbol_address).into()), + } + .into_ref_with_type(vm, cls)? + .into()) + } } diff --git a/crates/vm/src/stdlib/ctypes/structure.rs b/crates/vm/src/stdlib/ctypes/structure.rs index 6944b4e72c..79152e3109 100644 --- a/crates/vm/src/stdlib/ctypes/structure.rs +++ b/crates/vm/src/stdlib/ctypes/structure.rs @@ -313,11 +313,10 @@ impl PyCStructure { #[pyclassmethod] fn from_address(cls: PyTypeRef, address: isize, vm: &VirtualMachine) -> PyResult { - use crate::function::Either; use crate::stdlib::ctypes::_ctypes::size_of; // Get size from cls - let size = size_of(Either::A(cls.clone()), vm)?; + let size = size_of(cls.clone().into(), vm)?; // Read data from address if address == 0 || size == 0 { @@ -345,7 +344,6 @@ impl PyCStructure { vm: &VirtualMachine, ) -> PyResult { use crate::TryFromObject; - use crate::function::Either; use crate::protocol::PyBuffer; use crate::stdlib::ctypes::_ctypes::size_of; @@ -364,7 +362,7 @@ impl PyCStructure { } // Get size from cls - let size = size_of(Either::A(cls.clone()), vm)?; + let size = size_of(cls.clone().into(), vm)?; // Check if buffer is large enough let buffer_len = buffer.desc.len; @@ -396,7 +394,6 @@ impl PyCStructure { offset: crate::function::OptionalArg, vm: &VirtualMachine, ) -> PyResult { - use crate::function::Either; use crate::stdlib::ctypes::_ctypes::size_of; let offset = offset.unwrap_or(0); @@ -406,7 +403,7 @@ impl PyCStructure { let offset = offset as usize; // Get size from cls - let size = size_of(Either::A(cls.clone()), vm)?; + let size = size_of(cls.clone().into(), vm)?; // Borrow bytes from source let source_bytes = source.borrow_buf(); diff --git a/crates/vm/src/stdlib/ctypes/union.rs b/crates/vm/src/stdlib/ctypes/union.rs index 4142dead4f..9ad716740c 100644 --- a/crates/vm/src/stdlib/ctypes/union.rs +++ b/crates/vm/src/stdlib/ctypes/union.rs @@ -6,7 +6,7 @@ use crate::function::FuncArgs; use crate::protocol::{BufferDescriptor, BufferMethods, PyBuffer as ProtocolPyBuffer}; use crate::stdlib::ctypes::_ctypes::get_size; use crate::types::{AsBuffer, Constructor}; -use crate::{Py, PyObjectRef, PyPayload, PyResult, VirtualMachine}; +use crate::{AsObject, Py, PyObjectRef, PyPayload, PyResult, VirtualMachine}; use num_traits::ToPrimitive; use rustpython_common::lock::PyRwLock; @@ -130,15 +130,61 @@ impl std::fmt::Debug for PyCUnion { } } -#[pyclass(flags(BASETYPE, IMMUTABLETYPE), with(AsBuffer))] +impl Constructor for PyCUnion { + type Args = FuncArgs; + + fn py_new(cls: PyTypeRef, _args: Self::Args, vm: &VirtualMachine) -> PyResult { + // Get _fields_ from the class + let fields_attr = cls.as_object().get_attr("_fields_", vm).ok(); + + // Calculate union size (max of all field sizes) + let mut max_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![] + }; + + for field in fields.iter() { + let Some(field_tuple) = field.downcast_ref::() else { + continue; + }; + if field_tuple.len() < 2 { + continue; + } + let field_type = field_tuple.get(1).unwrap().clone(); + let size = PyCUnionType::get_field_size(&field_type, vm)?; + max_size = max_size.max(size); + } + } + + // Initialize buffer with zeros + PyCUnion { + cdata: PyRwLock::new(CDataObject::new(max_size)), + } + .into_ref_with_type(vm, cls) + .map(Into::into) + } +} + +#[pyclass(flags(BASETYPE, IMMUTABLETYPE), with(Constructor, AsBuffer))] impl PyCUnion { + #[pygetset] + fn _objects(&self) -> Option { + self.cdata.read().objects.clone() + } + #[pyclassmethod] fn from_address(cls: PyTypeRef, address: isize, vm: &VirtualMachine) -> PyResult { - use crate::function::Either; use crate::stdlib::ctypes::_ctypes::size_of; // Get size from cls - let size = size_of(Either::A(cls.clone()), vm)?; + let size = size_of(cls.clone().into(), vm)?; // Create instance with data from address if address == 0 || size == 0 { @@ -160,7 +206,6 @@ impl PyCUnion { vm: &VirtualMachine, ) -> PyResult { use crate::TryFromObject; - use crate::function::Either; use crate::protocol::PyBuffer; use crate::stdlib::ctypes::_ctypes::size_of; @@ -176,7 +221,7 @@ impl PyCUnion { return Err(vm.new_type_error("underlying buffer is not writable".to_owned())); } - let size = size_of(Either::A(cls.clone()), vm)?; + let size = size_of(cls.clone().into(), vm)?; let buffer_len = buffer.desc.len; if offset + size > buffer_len { @@ -205,7 +250,6 @@ impl PyCUnion { offset: crate::function::OptionalArg, vm: &VirtualMachine, ) -> PyResult { - use crate::function::Either; use crate::stdlib::ctypes::_ctypes::size_of; let offset = offset.unwrap_or(0); @@ -214,7 +258,7 @@ impl PyCUnion { } let offset = offset as usize; - let size = size_of(Either::A(cls.clone()), vm)?; + let size = size_of(cls.clone().into(), vm)?; let source_bytes = source.borrow_buf(); let buffer_len = source_bytes.len(); From 36b6d8fbb3bd1e30d2bd3b0a72497159b49fed35 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sat, 29 Nov 2025 11:53:09 +0900 Subject: [PATCH 9/9] fix sizeof/alignment --- .cspell.dict/cpython.txt | 1 + crates/vm/src/stdlib/ctypes.rs | 279 ++++++++++++++++------- crates/vm/src/stdlib/ctypes/array.rs | 43 ++-- crates/vm/src/stdlib/ctypes/base.rs | 269 ++++++++++++++++++---- crates/vm/src/stdlib/ctypes/function.rs | 59 ++++- crates/vm/src/stdlib/ctypes/pointer.rs | 21 +- crates/vm/src/stdlib/ctypes/structure.rs | 63 +++-- crates/vm/src/stdlib/ctypes/thunk.rs | 46 ++-- crates/vm/src/stdlib/ctypes/union.rs | 29 ++- crates/vm/src/stdlib/ctypes/util.rs | 41 ++++ 10 files changed, 639 insertions(+), 212 deletions(-) create mode 100644 crates/vm/src/stdlib/ctypes/util.rs diff --git a/.cspell.dict/cpython.txt b/.cspell.dict/cpython.txt index 5676549ec1..19c8725fa6 100644 --- a/.cspell.dict/cpython.txt +++ b/.cspell.dict/cpython.txt @@ -50,6 +50,7 @@ stackdepth stringlib structseq subparams +swappedbytes ticketer tok_oldval tvars diff --git a/crates/vm/src/stdlib/ctypes.rs b/crates/vm/src/stdlib/ctypes.rs index 4aa4768b20..7c35d4a700 100644 --- a/crates/vm/src/stdlib/ctypes.rs +++ b/crates/vm/src/stdlib/ctypes.rs @@ -9,12 +9,13 @@ pub(crate) mod pointer; pub(crate) mod structure; pub(crate) mod thunk; pub(crate) mod union; +pub(crate) mod util; use crate::builtins::PyModule; use crate::class::PyClassImpl; use crate::{Py, PyRef, VirtualMachine}; -pub use crate::stdlib::ctypes::base::{CDataObject, PyCData, PyCSimple, PyCSimpleType}; +pub use crate::stdlib::ctypes::base::{PyCData, PyCSimple, PyCSimpleType}; pub fn extend_module_nodes(vm: &VirtualMachine, module: &Py) { let ctx = &vm.ctx; @@ -52,7 +53,7 @@ pub(crate) mod _ctypes { use crate::convert::ToPyObject; use crate::function::{Either, FuncArgs, OptionalArg}; use crate::stdlib::ctypes::library; - use crate::{AsObject, PyObjectRef, PyPayload, PyResult, TryFromObject, VirtualMachine}; + use crate::{AsObject, PyObjectRef, PyPayload, PyResult, 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, @@ -159,6 +160,11 @@ pub(crate) mod _ctypes { } } + /// Get alignment for a simple type - for C types, alignment equals size + pub fn get_align(ty: &str) -> usize { + get_size(ty) + } + /// Get the size of a ctypes type from its type object #[allow(dead_code)] pub fn get_size_from_type(cls: &PyTypeRef, vm: &VirtualMachine) -> PyResult { @@ -366,7 +372,10 @@ pub(crate) mod _ctypes { Ok(PyCSimple { _type_: tp_str, value: AtomicCell::new(vm.ctx.none()), - cdata: rustpython_common::lock::PyRwLock::new(CDataObject::new(size)), + cdata: rustpython_common::lock::PyRwLock::new(CDataObject::from_bytes( + vec![0u8; size], + None, + )), }) } } else { @@ -378,98 +387,117 @@ pub(crate) mod _ctypes { } /// Get the size of a ctypes type or instance - /// - /// This function accepts: - /// - A ctypes type (e.g., c_int, Structure subclass) - /// - A ctypes instance (e.g., c_int(5)) #[pyfunction(name = "sizeof")] pub fn size_of(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { - use super::array::PyCArray; + use super::array::{PyCArray, PyCArrayType}; use super::pointer::PyCPointer; - use super::structure::PyCStructure; - use super::union::PyCUnion; - - // Check if obj is a type - if let Ok(type_ref) = obj.clone().downcast::() { - // It's a type - check what kind of ctypes type it is - - // Simple types (c_int, c_char, etc.) - if type_ref.fast_issubclass(PyCSimple::static_type()) { - let zelf = new_simple_type(Either::B(&type_ref), vm)?; - return Ok(get_size(zelf._type_.as_str())); + use super::structure::{PyCStructType, PyCStructure}; + use super::union::{PyCUnion, PyCUnionType}; + + // 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); } - - // Array types - if type_ref.fast_issubclass(PyCArray::static_type()) { - // Get _length_ and element size - if let Ok(length) = type_ref.as_object().get_attr("_length_", vm) { - let length = usize::try_from_object(vm, length)?; - if let Ok(elem_type) = type_ref.as_object().get_attr("_type_", vm) { - let elem_size = size_of(elem_type, vm)?; - return Ok(length * elem_size); - } - } + } + 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()); + } + if obj.fast_isinstance(PyCPointer::static_type()) { + return Ok(std::mem::size_of::()); + } - // Structure types - check for size_of_instances method - if type_ref.fast_issubclass(PyCStructure::static_type()) - || type_ref.fast_issubclass(PyCUnion::static_type()) + // 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 + if type_ref + .class() + .fast_issubclass(PyCStructType::static_type()) { - if let Ok(size_method) = type_ref.as_object().get_attr("size_of_instances", vm) { - let size = size_method.call(vec![], vm)?; - return Ok(usize::try_from_object(vm, size)?); - } + return calculate_struct_size(&type_ref, vm); + } + // Union types - check if metaclass is or inherits from PyCUnionType + if type_ref + .class() + .fast_issubclass(PyCUnionType::static_type()) + { + return calculate_union_size(&type_ref, vm); + } + // Simple types (c_int, c_char, etc.) + if type_ref.fast_issubclass(PyCSimple::static_type()) { + let instance = new_simple_type(Either::B(&type_ref), vm)?; + return Ok(get_size(&instance._type_)); } - // Pointer types if type_ref.fast_issubclass(PyCPointer::static_type()) { return Ok(std::mem::size_of::()); } - - // Check for size_of_instances as a fallback - if let Ok(size_method) = type_ref.as_object().get_attr("size_of_instances", vm) { - let size = size_method.call(vec![], vm)?; - return Ok(usize::try_from_object(vm, size)?); - } - - return Err(vm.new_type_error("this type has no size")); } - // It's an instance - get size from instance or its class - - // Simple type instance - if let Ok(simple) = obj.clone().downcast::() { - return Ok(get_size(simple._type_.as_str())); - } - - // Array instance - if let Ok(array) = obj.clone().downcast::() { - return Ok(array.cdata.read().size()); - } - - // Structure instance - if let Ok(structure) = obj.clone().downcast::() { - return Ok(structure.cdata.read().size()); - } - - // Union instance - if let Ok(union) = obj.clone().downcast::() { - return Ok(union.cdata.read().size()); - } + Err(vm.new_type_error("this type has no size")) + } - // Pointer instance - if obj.fast_isinstance(PyCPointer::static_type()) { - return Ok(std::mem::size_of::()); + /// Calculate Structure type size from _fields_ (sum of field sizes) + fn calculate_struct_size( + cls: &crate::builtins::PyTypeRef, + vm: &VirtualMachine, + ) -> PyResult { + use crate::AsObject; + + if let Ok(fields_attr) = cls.as_object().get_attr("_fields_", vm) { + let fields: Vec = fields_attr.try_to_value(vm).unwrap_or_default(); + let mut total_size = 0usize; + + for field in fields.iter() { + if let Some(tuple) = field.downcast_ref::() + && let Some(field_type) = tuple.get(1) + { + // Recursively calculate field type size + total_size += size_of(field_type.clone(), vm)?; + } + } + return Ok(total_size); } + Ok(0) + } - // Check if the object has size_of_instances method (for custom types) - if obj.has_attr("size_of_instances", vm)? { - let size_method = obj.get_attr("size_of_instances", vm)?; - let size = size_method.call(vec![], vm)?; - return Ok(usize::try_from_object(vm, size)?); + /// Calculate Union type size from _fields_ (max field size) + fn calculate_union_size( + cls: &crate::builtins::PyTypeRef, + vm: &VirtualMachine, + ) -> PyResult { + use crate::AsObject; + + if let Ok(fields_attr) = cls.as_object().get_attr("_fields_", vm) { + let fields: Vec = fields_attr.try_to_value(vm).unwrap_or_default(); + let mut max_size = 0usize; + + for field in fields.iter() { + if let Some(tuple) = field.downcast_ref::() + && let Some(field_type) = tuple.get(1) + { + let field_size = size_of(field_type.clone(), vm)?; + max_size = max_size.max(field_size); + } + } + return Ok(max_size); } - - Err(vm.new_type_error("this type has no size")) + Ok(0) } #[cfg(windows)] @@ -630,9 +658,100 @@ pub(crate) mod _ctypes { } #[pyfunction] - fn alignment(_args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { - // TODO: RUSTPYTHON - Err(vm.new_value_error("not implemented")) + 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; + + 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); + } + 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(); + return alignment(Either::A(cls.to_owned()), vm); + } + if obj.fast_isinstance(PyCPointer::static_type()) { + // Pointer alignment is always pointer size + return Ok(std::mem::align_of::()); + } + if obj.fast_isinstance(PyCUnion::static_type()) { + // Calculate alignment from _fields_ + let cls = obj.class(); + return alignment(Either::A(cls.to_owned()), vm); + } + + // Get the type object to check + let type_obj: PyObjectRef = match &tp { + Either::A(t) => t.clone().into(), + Either::B(obj) => obj.class().to_owned().into(), + }; + + // For type objects, try to get alignment from _type_ attribute + if let Ok(type_attr) = type_obj.get_attr("_type_", vm) { + // Array/Pointer: _type_ is the element type (a PyType) + if let Ok(elem_type) = type_attr.clone().downcast::() { + return alignment(Either::A(elem_type), vm); + } + // Simple type: _type_ is a single character string + if let Ok(s) = type_attr.str(vm) { + let ty = s.to_string(); + if ty.len() == 1 && SIMPLE_TYPE_CHARS.contains(ty.as_str()) { + return Ok(get_align(&ty)); + } + } + } + + // Structure/Union: max alignment of fields + if let Ok(fields_attr) = type_obj.get_attr("_fields_", vm) + && let Ok(fields) = fields_attr.try_to_value::>(vm) + { + let mut max_align = 1usize; + for field in fields.iter() { + if let Some(tuple) = field.downcast_ref::() + && let Some(field_type) = tuple.get(1) + { + let align = + if let Ok(ft) = field_type.clone().downcast::() { + alignment(Either::A(ft), vm).unwrap_or(1) + } else { + 1 + }; + max_align = max_align.max(align); + } + } + return Ok(max_align); + } + + // For instances, delegate to their class + if let Either::B(obj) = &tp + && !obj.class().is(vm.ctx.types.type_type.as_ref()) + { + return alignment(Either::A(obj.class().to_owned()), vm); + } + + // No alignment info found + Err(vm.new_type_error("no alignment info")) } #[pyfunction] diff --git a/crates/vm/src/stdlib/ctypes/array.rs b/crates/vm/src/stdlib/ctypes/array.rs index 0a90346e09..62d714329e 100644 --- a/crates/vm/src/stdlib/ctypes/array.rs +++ b/crates/vm/src/stdlib/ctypes/array.rs @@ -6,6 +6,7 @@ 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::{AsObject, Py, PyObjectRef, PyPayload}; use crate::{ @@ -22,13 +23,17 @@ use rustpython_vm::stdlib::ctypes::base::PyCData; #[pyclass(name = "PyCArrayType", base = PyType, module = "_ctypes")] #[derive(PyPayload)] pub struct PyCArrayType { - pub(super) inner: PyCArray, + 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("inner", &self.inner) + .field("typ", &self.typ) + .field("length", &self.length) .finish() } } @@ -37,9 +42,9 @@ 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.inner.typ.read().clone(); - let length = zelf.inner.length.load(); - let element_size = zelf.inner.element_size.load(); + 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]; @@ -79,12 +84,12 @@ impl Constructor for PyCArrayType { impl PyCArrayType { #[pygetset(name = "_type_")] fn typ(&self) -> PyObjectRef { - self.inner.typ.read().clone() + self.typ.read().clone() } #[pygetset(name = "_length_")] fn length(&self) -> usize { - self.inner.length.load() + self.length.load() } #[pymethod] @@ -95,22 +100,22 @@ impl PyCArrayType { // 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.inner.length.load(); - let inner_element_size = zelf.inner.element_size.load(); + let inner_length = zelf.length.load(); + let inner_element_size = zelf.element_size.load(); // The element type of the new array is the current array type itself let current_array_type: PyObjectRef = zelf.as_object().to_owned(); // Element size is the total size of the inner array let new_element_size = inner_length * inner_element_size; + let total_size = new_element_size * (n as usize); + let stg_info = StgInfo::new(total_size, inner_element_size); Ok(PyCArrayType { - inner: PyCArray { - typ: PyRwLock::new(current_array_type), - length: AtomicCell::new(n as usize), - element_size: AtomicCell::new(new_element_size), - cdata: PyRwLock::new(CDataObject::new(0)), - }, + stg_info, + typ: PyRwLock::new(current_array_type), + length: AtomicCell::new(n as usize), + element_size: AtomicCell::new(new_element_size), } .to_pyobject(vm)) } @@ -164,8 +169,8 @@ impl PyCArrayType { }; // Get size from the array type - let element_type = zelf.inner.typ.read().clone(); - let length = zelf.inner.length.load(); + let element_type = zelf.typ.read().clone(); + let length = zelf.length.load(); let element_size = size_of(element_type.clone(), vm)?; let total_size = element_size * length; @@ -184,7 +189,7 @@ impl PyCArrayType { typ: PyRwLock::new(element_type), length: AtomicCell::new(length), element_size: AtomicCell::new(element_size), - cdata: PyRwLock::new(CDataObject::from_bytes(data)), + cdata: PyRwLock::new(CDataObject::from_bytes(data, None)), } .into_pyobject(vm); @@ -729,7 +734,7 @@ impl PyCArray { typ: PyRwLock::new(element_type.into()), length: AtomicCell::new(length), element_size: AtomicCell::new(element_size), - cdata: PyRwLock::new(CDataObject::from_bytes(data)), + cdata: PyRwLock::new(CDataObject::from_bytes(data, None)), } .into_pyobject(vm); diff --git a/crates/vm/src/stdlib/ctypes/base.rs b/crates/vm/src/stdlib/ctypes/base.rs index f4b279ef5a..5e2deeb560 100644 --- a/crates/vm/src/stdlib/ctypes/base.rs +++ b/crates/vm/src/stdlib/ctypes/base.rs @@ -1,5 +1,6 @@ use super::_ctypes::bytes_to_pyobject; -use super::array::{PyCArray, PyCArrayType}; +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, OptionalArg}; @@ -13,6 +14,14 @@ use rustpython_common::lock::PyRwLock; use std::ffi::{c_uint, c_ulong, c_ulonglong, c_ushort}; use std::fmt::Debug; +/// Get the type code string from a ctypes type (e.g., "i" for c_int) +pub fn get_type_code(cls: &PyTypeRef, vm: &VirtualMachine) -> Option { + cls.as_object() + .get_attr("_type_", vm) + .ok() + .and_then(|t| t.downcast_ref::().map(|s| s.to_string())) +} + pub fn ffi_type_from_str(_type_: &str) -> Option { match _type_ { "c" => Some(libffi::middle::Type::u8()), @@ -92,7 +101,10 @@ fn set_primitive(_type_: &str, value: &PyObjectRef, vm: &VirtualMachine) -> PyRe } } "f" | "d" | "g" => { - if value.clone().downcast_exact::(vm).is_ok() { + // float allows int + if value.clone().downcast_exact::(vm).is_ok() + || value.clone().downcast_exact::(vm).is_ok() + { Ok(value.clone()) } else { Err(vm.new_type_error(format!("must be real number, not {}", value.class().name()))) @@ -160,10 +172,10 @@ pub struct CDataObject { } impl CDataObject { - /// Create new owned buffer with zero-initialized memory - pub fn new(size: usize) -> Self { + /// Create from StgInfo (PyCData_MallocBuffer pattern) + pub fn from_stg_info(stg_info: &StgInfo) -> Self { CDataObject { - buffer: vec![0u8; size], + buffer: vec![0u8; stg_info.size], base: None, index: 0, objects: None, @@ -182,7 +194,13 @@ impl CDataObject { /// Create from base object (copies data from base's buffer at offset) #[allow(dead_code)] - pub fn from_base(base: PyObjectRef, _offset: usize, size: usize, index: usize, objects: Option) -> Self { + pub fn from_base( + base: PyObjectRef, + _offset: usize, + size: usize, + index: usize, + objects: Option, + ) -> Self { CDataObject { buffer: vec![0u8; size], base: Some(base), @@ -212,11 +230,28 @@ impl PyCData { } #[pyclass(module = "_ctypes", name = "PyCSimpleType", base = PyType)] -#[derive(Debug, PyPayload)] -pub struct PyCSimpleType {} +#[derive(Debug, PyPayload, Default)] +pub struct PyCSimpleType { + #[allow(dead_code)] + pub stg_info: StgInfo, +} #[pyclass(flags(BASETYPE), with(AsNumber))] impl PyCSimpleType { + /// Get stg_info for a simple type by reading _type_ attribute + pub fn get_stg_info(cls: &PyTypeRef, vm: &VirtualMachine) -> StgInfo { + if let Ok(type_attr) = cls.as_object().get_attr("_type_", vm) + && let Ok(type_str) = type_attr.str(vm) + { + let tp_str = type_str.to_string(); + if tp_str.len() == 1 { + let size = super::_ctypes::get_size(&tp_str); + let align = super::_ctypes::get_align(&tp_str); + return StgInfo::new(size, align); + } + } + StgInfo::default() + } #[allow(clippy::new_ret_no_self)] #[pymethod] fn new(cls: PyTypeRef, _: OptionalArg, vm: &VirtualMachine) -> PyResult { @@ -229,17 +264,122 @@ impl PyCSimpleType { #[pyclassmethod] fn from_param(cls: PyTypeRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult { - // If the value is already an instance of the requested type, return it + // 1. If the value is already an instance of the requested type, return it if value.fast_isinstance(&cls) { return Ok(value); } - // Check for _as_parameter_ attribute - let Ok(as_parameter) = value.get_attr("_as_parameter_", vm) else { - return Err(vm.new_type_error("wrong type")); - }; + // 2. Get the type code to determine conversion rules + let type_code = get_type_code(&cls, vm); + + // 3. Handle None for pointer types (c_char_p, c_wchar_p, c_void_p) + if vm.is_none(&value) && matches!(type_code.as_deref(), Some("z") | Some("Z") | Some("P")) { + return Ok(value); + } + + // 4. Try to convert value based on type code + match type_code.as_deref() { + // Integer types: accept integers + Some("b" | "B" | "h" | "H" | "i" | "I" | "l" | "L" | "q" | "Q") => { + if value.try_int(vm).is_ok() { + let simple = new_simple_type(Either::B(&cls), vm)?; + simple.value.store(value.clone()); + return simple.into_ref_with_type(vm, cls.clone()).map(Into::into); + } + } + // Float types: accept numbers + Some("f" | "d" | "g") => { + if value.try_float(vm).is_ok() || value.try_int(vm).is_ok() { + let simple = new_simple_type(Either::B(&cls), vm)?; + simple.value.store(value.clone()); + return simple.into_ref_with_type(vm, cls.clone()).map(Into::into); + } + } + // c_char: 1 byte character + Some("c") => { + if let Some(bytes) = value.downcast_ref::() + && bytes.len() == 1 + { + let simple = new_simple_type(Either::B(&cls), vm)?; + simple.value.store(value.clone()); + return simple.into_ref_with_type(vm, cls.clone()).map(Into::into); + } + if let Ok(int_val) = value.try_int(vm) + && int_val.as_bigint().to_u8().is_some() + { + let simple = new_simple_type(Either::B(&cls), vm)?; + simple.value.store(value.clone()); + return simple.into_ref_with_type(vm, cls.clone()).map(Into::into); + } + return Err(vm.new_type_error( + "one character bytes, bytearray or integer expected".to_string(), + )); + } + // c_wchar: 1 unicode character + Some("u") => { + if let Some(s) = value.downcast_ref::() + && s.as_str().chars().count() == 1 + { + let simple = new_simple_type(Either::B(&cls), vm)?; + simple.value.store(value.clone()); + return simple.into_ref_with_type(vm, cls.clone()).map(Into::into); + } + return Err(vm.new_type_error("one character unicode string expected".to_string())); + } + // c_char_p: bytes pointer + Some("z") => { + if value.downcast_ref::().is_some() { + let simple = new_simple_type(Either::B(&cls), vm)?; + simple.value.store(value.clone()); + return simple.into_ref_with_type(vm, cls.clone()).map(Into::into); + } + } + // c_wchar_p: unicode pointer + Some("Z") => { + if value.downcast_ref::().is_some() { + let simple = new_simple_type(Either::B(&cls), vm)?; + simple.value.store(value.clone()); + return simple.into_ref_with_type(vm, cls.clone()).map(Into::into); + } + } + // c_void_p: most flexible - accepts int, bytes, str + Some("P") => { + if value.try_int(vm).is_ok() + || value.downcast_ref::().is_some() + || value.downcast_ref::().is_some() + { + let simple = new_simple_type(Either::B(&cls), vm)?; + simple.value.store(value.clone()); + return simple.into_ref_with_type(vm, cls.clone()).map(Into::into); + } + } + // c_bool + Some("?") => { + let bool_val = value.is_true(vm)?; + let simple = new_simple_type(Either::B(&cls), vm)?; + simple.value.store(vm.ctx.new_bool(bool_val).into()); + return simple.into_ref_with_type(vm, cls.clone()).map(Into::into); + } + _ => {} + } + + // 5. Check for _as_parameter_ attribute + if let Ok(as_parameter) = value.get_attr("_as_parameter_", vm) { + return PyCSimpleType::from_param(cls, as_parameter, vm); + } - PyCSimpleType::from_param(cls, as_parameter, vm) + // 6. Type-specific error messages + match type_code.as_deref() { + Some("z") => Err(vm.new_type_error(format!( + "'{}' object cannot be interpreted as ctypes.c_char_p", + value.class().name() + ))), + Some("Z") => Err(vm.new_type_error(format!( + "'{}' object cannot be interpreted as ctypes.c_wchar_p", + value.class().name() + ))), + _ => Err(vm.new_type_error("wrong type".to_string())), + } } #[pymethod] @@ -291,8 +431,31 @@ impl Debug for PyCSimple { } } -/// Convert a Python value to bytes based on ctypes type -fn value_to_bytes(_type_: &str, value: &PyObjectRef, vm: &VirtualMachine) -> Vec { +fn value_to_bytes_endian( + _type_: &str, + value: &PyObjectRef, + swapped: bool, + vm: &VirtualMachine, +) -> Vec { + // Helper macro for endian conversion + macro_rules! to_bytes { + ($val:expr) => { + if swapped { + // Use opposite endianness + #[cfg(target_endian = "little")] + { + $val.to_be_bytes().to_vec() + } + #[cfg(target_endian = "big")] + { + $val.to_le_bytes().to_vec() + } + } else { + $val.to_ne_bytes().to_vec() + } + }; + } + match _type_ { "c" => { // c_char - single byte @@ -313,7 +476,7 @@ fn value_to_bytes(_type_: &str, value: &PyObjectRef, vm: &VirtualMachine) -> Vec if let Ok(s) = value.str(vm) && let Some(c) = s.as_str().chars().next() { - return (c as u32).to_ne_bytes().to_vec(); + return to_bytes!(c as u32); } vec![0; 4] } @@ -340,7 +503,7 @@ fn value_to_bytes(_type_: &str, value: &PyObjectRef, vm: &VirtualMachine) -> Vec if let Ok(int_val) = value.try_int(vm) && let Some(v) = int_val.as_bigint().to_i16() { - return v.to_ne_bytes().to_vec(); + return to_bytes!(v); } vec![0; 2] } @@ -349,7 +512,7 @@ fn value_to_bytes(_type_: &str, value: &PyObjectRef, vm: &VirtualMachine) -> Vec if let Ok(int_val) = value.try_int(vm) && let Some(v) = int_val.as_bigint().to_u16() { - return v.to_ne_bytes().to_vec(); + return to_bytes!(v); } vec![0; 2] } @@ -358,7 +521,7 @@ fn value_to_bytes(_type_: &str, value: &PyObjectRef, vm: &VirtualMachine) -> Vec if let Ok(int_val) = value.try_int(vm) && let Some(v) = int_val.as_bigint().to_i32() { - return v.to_ne_bytes().to_vec(); + return to_bytes!(v); } vec![0; 4] } @@ -367,14 +530,14 @@ fn value_to_bytes(_type_: &str, value: &PyObjectRef, vm: &VirtualMachine) -> Vec if let Ok(int_val) = value.try_int(vm) && let Some(v) = int_val.as_bigint().to_u32() { - return v.to_ne_bytes().to_vec(); + return to_bytes!(v); } vec![0; 4] } "l" => { // c_long (platform dependent) if let Ok(int_val) = value.try_to_value::(vm) { - return int_val.to_ne_bytes().to_vec(); + return to_bytes!(int_val); } const SIZE: usize = std::mem::size_of::(); vec![0; SIZE] @@ -382,7 +545,7 @@ fn value_to_bytes(_type_: &str, value: &PyObjectRef, vm: &VirtualMachine) -> Vec "L" => { // c_ulong (platform dependent) if let Ok(int_val) = value.try_to_value::(vm) { - return int_val.to_ne_bytes().to_vec(); + return to_bytes!(int_val); } const SIZE: usize = std::mem::size_of::(); vec![0; SIZE] @@ -392,7 +555,7 @@ fn value_to_bytes(_type_: &str, value: &PyObjectRef, vm: &VirtualMachine) -> Vec if let Ok(int_val) = value.try_int(vm) && let Some(v) = int_val.as_bigint().to_i64() { - return v.to_ne_bytes().to_vec(); + return to_bytes!(v); } vec![0; 8] } @@ -401,21 +564,31 @@ fn value_to_bytes(_type_: &str, value: &PyObjectRef, vm: &VirtualMachine) -> Vec if let Ok(int_val) = value.try_int(vm) && let Some(v) = int_val.as_bigint().to_u64() { - return v.to_ne_bytes().to_vec(); + return to_bytes!(v); } vec![0; 8] } "f" => { - // c_float (4 bytes) + // c_float (4 bytes) - int도 허용 if let Ok(float_val) = value.try_float(vm) { - return (float_val.to_f64() as f32).to_ne_bytes().to_vec(); + return to_bytes!(float_val.to_f64() as f32); + } + if let Ok(int_val) = value.try_int(vm) + && let Some(v) = int_val.as_bigint().to_f64() + { + return to_bytes!(v as f32); } vec![0; 4] } "d" | "g" => { - // c_double (8 bytes) + // c_double (8 bytes) - int도 허용 if let Ok(float_val) = value.try_float(vm) { - return float_val.to_f64().to_ne_bytes().to_vec(); + return to_bytes!(float_val.to_f64()); + } + if let Ok(int_val) = value.try_int(vm) + && let Some(v) = int_val.as_bigint().to_f64() + { + return to_bytes!(v); } vec![0; 8] } @@ -469,7 +642,15 @@ impl Constructor for PyCSimple { _ => vm.ctx.none(), // "z" | "Z" | "P" } }; - let buffer = value_to_bytes(&_type_, &value, vm); + + // Check if this is a swapped endian type + let swapped = cls + .as_object() + .get_attr("_swappedbytes_", vm) + .map(|v| v.is_true(vm).unwrap_or(false)) + .unwrap_or(false); + + let buffer = value_to_bytes_endian(&_type_, &value, swapped, vm); PyCSimple { _type_, value: AtomicCell::new(value), @@ -538,11 +719,21 @@ impl PyCSimple { #[pygetset(name = "value", setter)] fn set_value(instance: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { let zelf: PyRef = instance + .clone() .downcast() .map_err(|_| vm.new_type_error("cannot set value of instance"))?; let content = set_primitive(zelf._type_.as_str(), &value, vm)?; + + // Check if this is a swapped endian type + let swapped = instance + .class() + .as_object() + .get_attr("_swappedbytes_", vm) + .map(|v| v.is_true(vm).unwrap_or(false)) + .unwrap_or(false); + // Update buffer when value changes - let buffer_bytes = value_to_bytes(&zelf._type_, &content, vm); + let buffer_bytes = value_to_bytes_endian(&zelf._type_, &content, swapped, vm); zelf.cdata.write().buffer = buffer_bytes; zelf.value.store(content); Ok(()) @@ -569,13 +760,13 @@ impl PyCSimple { } else { 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 { - inner: PyCArray { - typ: PyRwLock::new(cls.clone().into()), - length: AtomicCell::new(n as usize), - element_size: AtomicCell::new(element_size), - cdata: PyRwLock::new(CDataObject::new(0)), - }, + stg_info, + typ: PyRwLock::new(cls.clone().into()), + length: AtomicCell::new(n as usize), + element_size: AtomicCell::new(element_size), } .to_pyobject(vm)) } @@ -594,7 +785,6 @@ impl PyCSimple { // Create instance with value read from address let value = if address != 0 && size > 0 { // Safety: This is inherently unsafe - reading from arbitrary memory address - // CPython does the same thing without safety checks unsafe { let ptr = address as *const u8; let bytes = std::slice::from_raw_parts(ptr, size); @@ -759,7 +949,6 @@ impl PyCSimple { // Read value from symbol address let value = if symbol_address != 0 && size > 0 { // Safety: Reading from a symbol address provided by dlsym - // This is the same as CPython's implementation unsafe { let ptr = symbol_address as *const u8; let bytes = std::slice::from_raw_parts(ptr, size); @@ -772,7 +961,7 @@ impl PyCSimple { // Create instance let instance = PyCSimple::py_new(cls.clone(), (OptionalArg::Present(value),), vm)?; - // Store base reference to keep dll alive (like CPython does) + // Store base reference to keep dll alive if let Ok(simple_ref) = instance.clone().downcast::() { simple_ref.cdata.write().base = Some(dll); } diff --git a/crates/vm/src/stdlib/ctypes/function.rs b/crates/vm/src/stdlib/ctypes/function.rs index 22933ea5df..27e85c563e 100644 --- a/crates/vm/src/stdlib/ctypes/function.rs +++ b/crates/vm/src/stdlib/ctypes/function.rs @@ -86,25 +86,60 @@ impl ReturnType for PyTypeRef { .and_then(|t| t.downcast_ref::().map(|s| s.to_string())); let result = match type_code.as_deref() { - Some("b") => vm.ctx.new_int(unsafe { *(value as *const i8) } as i32).into(), - Some("B") => vm.ctx.new_int(unsafe { *(value as *const u8) } as i32).into(), - Some("c") => vm.ctx.new_bytes(vec![unsafe { *(value as *const u8) }]).into(), - Some("h") => vm.ctx.new_int(unsafe { *(value as *const i16) } as i32).into(), - Some("H") => vm.ctx.new_int(unsafe { *(value as *const u16) } as i32).into(), + Some("b") => vm + .ctx + .new_int(unsafe { *(value as *const i8) } as i32) + .into(), + Some("B") => vm + .ctx + .new_int(unsafe { *(value as *const u8) } as i32) + .into(), + Some("c") => vm + .ctx + .new_bytes(vec![unsafe { *(value as *const u8) }]) + .into(), + Some("h") => vm + .ctx + .new_int(unsafe { *(value as *const i16) } as i32) + .into(), + Some("H") => vm + .ctx + .new_int(unsafe { *(value as *const u16) } as i32) + .into(), Some("i") => vm.ctx.new_int(unsafe { *(value as *const i32) }).into(), Some("I") => vm.ctx.new_int(unsafe { *(value as *const u32) }).into(), - Some("l") => vm.ctx.new_int(unsafe { *(value as *const i64) }).into(), - Some("L") => vm.ctx.new_int(unsafe { *(value as *const u64) }).into(), - Some("q") => vm.ctx.new_int(unsafe { *(value as *const i64) }).into(), - Some("Q") => vm.ctx.new_int(unsafe { *(value as *const u64) }).into(), - Some("f") => vm.ctx.new_float(unsafe { *(value as *const f32) } as f64).into(), + Some("l") => vm + .ctx + .new_int(unsafe { *(value as *const libc::c_long) }) + .into(), + Some("L") => vm + .ctx + .new_int(unsafe { *(value as *const libc::c_ulong) }) + .into(), + Some("q") => vm + .ctx + .new_int(unsafe { *(value as *const libc::c_longlong) }) + .into(), + Some("Q") => vm + .ctx + .new_int(unsafe { *(value as *const libc::c_ulonglong) }) + .into(), + Some("f") => vm + .ctx + .new_float(unsafe { *(value as *const f32) } as f64) + .into(), Some("d") => vm.ctx.new_float(unsafe { *(value as *const f64) }).into(), Some("P") | Some("z") | Some("Z") => vm.ctx.new_int(value as usize).into(), - Some("?") => vm.ctx.new_bool(unsafe { *(value as *const u8) } != 0).into(), + Some("?") => vm + .ctx + .new_bool(unsafe { *(value as *const u8) } != 0) + .into(), None => { // No _type_ attribute, try to create an instance of the type // This handles cases like Structure or Array return types - return Ok(Some(vm.ctx.new_int(unsafe { *(value as *const i32) }).into())); + return Ok(Some( + vm.ctx.new_int(unsafe { *(value as *const i32) }).into(), + )); } _ => return Err(vm.new_type_error("Unsupported return type".to_string())), }; diff --git a/crates/vm/src/stdlib/ctypes/pointer.rs b/crates/vm/src/stdlib/ctypes/pointer.rs index 5e41f9b651..cd9ba8a7a1 100644 --- a/crates/vm/src/stdlib/ctypes/pointer.rs +++ b/crates/vm/src/stdlib/ctypes/pointer.rs @@ -5,34 +5,37 @@ use rustpython_common::lock::PyRwLock; use crate::builtins::{PyType, PyTypeRef}; use crate::convert::ToPyObject; use crate::protocol::PyNumberMethods; -use crate::stdlib::ctypes::{CDataObject, PyCData}; +use crate::stdlib::ctypes::PyCData; use crate::types::{AsNumber, Constructor}; use crate::{AsObject, 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(crate) inner: PyCPointer, + pub stg_info: StgInfo, } #[pyclass(flags(IMMUTABLETYPE), with(AsNumber))] impl PyCPointerType { #[pymethod] fn __mul__(cls: PyTypeRef, n: isize, vm: &VirtualMachine) -> PyResult { - use super::array::{PyCArray, PyCArrayType}; + use super::array::PyCArrayType; 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 { - inner: PyCArray { - typ: PyRwLock::new(cls.as_object().to_owned()), - length: AtomicCell::new(n as usize), - element_size: AtomicCell::new(element_size), - cdata: PyRwLock::new(CDataObject::new(0)), - }, + 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)) } diff --git a/crates/vm/src/stdlib/ctypes/structure.rs b/crates/vm/src/stdlib/ctypes/structure.rs index 79152e3109..1c842e21b5 100644 --- a/crates/vm/src/stdlib/ctypes/structure.rs +++ b/crates/vm/src/stdlib/ctypes/structure.rs @@ -1,5 +1,6 @@ use super::base::{CDataObject, PyCData}; use super::field::PyCField; +use super::util::StgInfo; use crate::builtins::{PyList, PyStr, PyTuple, PyType, PyTypeRef}; use crate::convert::ToPyObject; use crate::function::FuncArgs; @@ -16,7 +17,10 @@ use std::fmt::Debug; /// PyCStructType - metaclass for Structure #[pyclass(name = "PyCStructType", base = PyType, module = "_ctypes")] #[derive(Debug, PyPayload)] -pub struct PyCStructType {} +pub struct PyCStructType { + #[allow(dead_code)] + pub stg_info: StgInfo, +} impl Constructor for PyCStructType { type Args = FuncArgs; @@ -92,10 +96,10 @@ impl PyCStructType { 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); + let c_field = 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)); + cls.set_attr(vm.ctx.intern_str(name), c_field.to_pyobject(vm)); offset += size; } @@ -133,22 +137,46 @@ impl PyCStructType { Ok(std::mem::size_of::()) } + /// Get the alignment of a ctypes type + fn get_field_align(field_type: &PyObjectRef, vm: &VirtualMachine) -> usize { + // Try to get _type_ attribute for simple types + if let Some(align) = 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)) // alignment == size for simple types + }) + { + return align; + } + // Default alignment + 1 + } + #[pymethod] fn __mul__(cls: PyTypeRef, n: isize, vm: &VirtualMachine) -> PyResult { - use super::array::{PyCArray, PyCArrayType}; + use super::array::PyCArrayType; + use crate::stdlib::ctypes::_ctypes::size_of; + 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 + + // Calculate element size from the Structure type + let element_size = size_of(cls.clone().into(), vm)?; + + 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 { - inner: PyCArray { - typ: PyRwLock::new(cls.clone().into()), - length: AtomicCell::new(n as usize), - element_size: AtomicCell::new(element_size), - cdata: PyRwLock::new(CDataObject::new(0)), - }, + stg_info, + typ: PyRwLock::new(cls.clone().into()), + length: AtomicCell::new(n as usize), + element_size: AtomicCell::new(element_size), } .to_pyobject(vm)) } @@ -217,6 +245,7 @@ impl Constructor for PyCStructure { let mut fields_map = IndexMap::new(); let mut total_size = 0usize; + let mut max_align = 1usize; if let Some(fields_attr) = fields_attr { let fields: Vec = if let Some(list) = fields_attr.downcast_ref::() @@ -242,6 +271,8 @@ impl Constructor for PyCStructure { let name = name.to_string(); let field_type = field_tuple.get(1).unwrap().clone(); let size = PyCStructType::get_field_size(&field_type, vm)?; + let field_align = PyCStructType::get_field_align(&field_type, vm); + max_align = max_align.max(field_align); let type_ref = field_type .downcast::() @@ -263,8 +294,10 @@ impl Constructor for PyCStructure { } // Initialize buffer with zeros + let mut stg_info = StgInfo::new(total_size, max_align); + stg_info.length = fields_map.len(); let instance = PyCStructure { - cdata: PyRwLock::new(CDataObject::new(total_size)), + cdata: PyRwLock::new(CDataObject::from_stg_info(&stg_info)), fields: PyRwLock::new(fields_map.clone()), }; @@ -380,7 +413,7 @@ impl PyCStructure { // Create instance Ok(PyCStructure { - cdata: PyRwLock::new(CDataObject::from_bytes(data, None)), + cdata: PyRwLock::new(CDataObject::from_bytes(data, Some(source))), fields: PyRwLock::new(IndexMap::new()), } .into_ref_with_type(vm, cls)? diff --git a/crates/vm/src/stdlib/ctypes/thunk.rs b/crates/vm/src/stdlib/ctypes/thunk.rs index 51959af4c0..2de2308e1a 100644 --- a/crates/vm/src/stdlib/ctypes/thunk.rs +++ b/crates/vm/src/stdlib/ctypes/thunk.rs @@ -14,22 +14,6 @@ use std::ffi::c_void; use std::fmt::Debug; use super::base::ffi_type_from_str; - -// CPython structure for reference: -// typedef struct { -// PyObject_VAR_HEAD -// ffi_closure *pcl_write; /* the C callable, writeable */ -// void *pcl_exec; /* the C callable, executable */ -// ffi_cif cif; -// int flags; -// PyObject *converters; -// PyObject *callable; -// PyObject *restype; -// SETFUNC setfunc; -// ffi_type *ffi_restype; -// ffi_type *atypes[1]; -// } CThunkObject; - /// Userdata passed to the libffi callback. /// This contains everything needed to invoke the Python callable. pub struct ThunkUserData { @@ -60,10 +44,10 @@ fn ffi_to_python(ty: &PyTypeRef, ptr: *const c_void, vm: &VirtualMachine) -> PyO Some("H") => vm.ctx.new_int(*(ptr as *const u16) as i32).into(), Some("i") => vm.ctx.new_int(*(ptr as *const i32)).into(), Some("I") => vm.ctx.new_int(*(ptr as *const u32)).into(), - Some("l") => vm.ctx.new_int(*(ptr as *const i64)).into(), - Some("L") => vm.ctx.new_int(*(ptr as *const u64)).into(), - Some("q") => vm.ctx.new_int(*(ptr as *const i64)).into(), - Some("Q") => vm.ctx.new_int(*(ptr as *const u64)).into(), + Some("l") => vm.ctx.new_int(*(ptr as *const libc::c_long)).into(), + Some("L") => vm.ctx.new_int(*(ptr as *const libc::c_ulong)).into(), + Some("q") => vm.ctx.new_int(*(ptr as *const libc::c_longlong)).into(), + Some("Q") => vm.ctx.new_int(*(ptr as *const libc::c_ulonglong)).into(), Some("f") => vm.ctx.new_float(*(ptr as *const f32) as f64).into(), Some("d") => vm.ctx.new_float(*(ptr as *const f64)).into(), Some("P") | Some("z") | Some("Z") => vm.ctx.new_int(ptr as usize).into(), @@ -182,6 +166,7 @@ unsafe extern "C" fn thunk_callback( /// Holds the closure and userdata together to ensure proper lifetime. /// The userdata is leaked to create a 'static reference that the closure can use. struct ThunkData { + #[allow(dead_code)] closure: Closure<'static>, /// Raw pointer to the leaked userdata, for cleanup userdata_ptr: *mut ThunkUserData, @@ -249,17 +234,18 @@ impl PyCThunk { }; // Parse result type - let res_type_ref: Option = if let Some(ref rt) = res_type { - if vm.is_none(rt) { - None + let res_type_ref: Option = + if let Some(ref rt) = res_type { + if vm.is_none(rt) { + None + } else { + Some(rt.clone().downcast::().map_err(|_| { + vm.new_type_error("restype must be a ctypes type".to_string()) + })?) + } } else { - Some(rt.clone().downcast::().map_err(|_| { - vm.new_type_error("restype must be a ctypes type".to_string()) - })?) - } - } else { - None - }; + None + }; // Build FFI types let ffi_arg_types: Vec = arg_type_vec diff --git a/crates/vm/src/stdlib/ctypes/union.rs b/crates/vm/src/stdlib/ctypes/union.rs index 9ad716740c..aa78d56c46 100644 --- a/crates/vm/src/stdlib/ctypes/union.rs +++ b/crates/vm/src/stdlib/ctypes/union.rs @@ -1,5 +1,6 @@ use super::base::{CDataObject, PyCData}; use super::field::PyCField; +use super::util::StgInfo; use crate::builtins::{PyList, PyStr, PyTuple, PyType, PyTypeRef}; use crate::convert::ToPyObject; use crate::function::FuncArgs; @@ -13,7 +14,17 @@ use rustpython_common::lock::PyRwLock; /// PyCUnionType - metaclass for Union #[pyclass(name = "UnionType", base = PyType, module = "_ctypes")] #[derive(Debug, PyPayload)] -pub struct PyCUnionType {} +pub struct PyCUnionType { + pub stg_info: StgInfo, +} + +impl Default for PyCUnionType { + fn default() -> Self { + PyCUnionType { + stg_info: StgInfo::new(0, 1), + } + } +} impl Constructor for PyCUnionType { type Args = FuncArgs; @@ -76,9 +87,9 @@ impl PyCUnionType { // 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); + let c_field = PyCField::new(name.clone(), field_type, 0, size, index); - cls.set_attr(vm.ctx.intern_str(name), cfield.to_pyobject(vm)); + cls.set_attr(vm.ctx.intern_str(name), c_field.to_pyobject(vm)); } Ok(()) @@ -137,8 +148,9 @@ impl Constructor for PyCUnion { // Get _fields_ from the class let fields_attr = cls.as_object().get_attr("_fields_", vm).ok(); - // Calculate union size (max of all field sizes) + // Calculate union size (max of all field sizes) and alignment let mut max_size = 0usize; + let mut max_align = 1usize; if let Some(fields_attr) = fields_attr { let fields: Vec = if let Some(list) = fields_attr.downcast_ref::() @@ -160,12 +172,15 @@ impl Constructor for PyCUnion { let field_type = field_tuple.get(1).unwrap().clone(); let size = PyCUnionType::get_field_size(&field_type, vm)?; max_size = max_size.max(size); + // For simple types, alignment == size + max_align = max_align.max(size); } } // Initialize buffer with zeros + let stg_info = StgInfo::new(max_size, max_align); PyCUnion { - cdata: PyRwLock::new(CDataObject::new(max_size)), + cdata: PyRwLock::new(CDataObject::from_stg_info(&stg_info)), } .into_ref_with_type(vm, cls) .map(Into::into) @@ -190,9 +205,9 @@ impl PyCUnion { if address == 0 || size == 0 { return Err(vm.new_value_error("NULL pointer access".to_owned())); } - + let stg_info = StgInfo::new(size, 1); Ok(PyCUnion { - cdata: PyRwLock::new(CDataObject::new(size)), + cdata: PyRwLock::new(CDataObject::from_stg_info(&stg_info)), } .into_ref_with_type(vm, cls)? .into()) diff --git a/crates/vm/src/stdlib/ctypes/util.rs b/crates/vm/src/stdlib/ctypes/util.rs new file mode 100644 index 0000000000..b8fd9c89c0 --- /dev/null +++ b/crates/vm/src/stdlib/ctypes/util.rs @@ -0,0 +1,41 @@ +use crate::PyObjectRef; + +/// Storage information for ctypes types +#[derive(Debug, 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 proto: Option, // Only for Pointer/ArrayObject + #[allow(dead_code)] + pub flags: i32, // calling convention and such +} + +impl Default for StgInfo { + fn default() -> Self { + StgInfo { + initialized: false, + size: 0, + align: 1, + length: 0, + proto: None, + flags: 0, + } + } +} + +impl StgInfo { + pub fn new(size: usize, align: usize) -> Self { + StgInfo { + initialized: true, + size, + align, + length: 0, + proto: None, + flags: 0, + } + } +}