From 2f0375708bc58f466d00c2860dc954fdecd3b2cb Mon Sep 17 00:00:00 2001 From: Ashwin Naren Date: Mon, 31 Mar 2025 21:42:54 -0700 Subject: [PATCH 01/12] more pointer implementation --- vm/src/stdlib/ctypes.rs | 1 + vm/src/stdlib/ctypes/pointer.rs | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/vm/src/stdlib/ctypes.rs b/vm/src/stdlib/ctypes.rs index 235e089e3a..2902364a1f 100644 --- a/vm/src/stdlib/ctypes.rs +++ b/vm/src/stdlib/ctypes.rs @@ -22,6 +22,7 @@ pub fn extend_module_nodes(vm: &VirtualMachine, module: &Py) { "_SimpleCData" => PyCSimple::make_class(ctx), "Array" => array::PyCArray::make_class(ctx), "CFuncPtr" => function::PyCFuncPtr::make_class(ctx), + "PyCPointerType" => pointer::PyCPointerType::make_class(ctx), "_Pointer" => pointer::PyCPointer::make_class(ctx), "_pointer_type_cache" => ctx.new_dict(), "Structure" => structure::PyCStructure::make_class(ctx), diff --git a/vm/src/stdlib/ctypes/pointer.rs b/vm/src/stdlib/ctypes/pointer.rs index d1360f9862..a391f1ef7e 100644 --- a/vm/src/stdlib/ctypes/pointer.rs +++ b/vm/src/stdlib/ctypes/pointer.rs @@ -1,4 +1,10 @@ -#[pyclass(name = "Pointer", module = "_ctypes")] +#[pyclass(name = "PyCPointerType", base = "PyType", module = "_ctypes")] +#[derive(PyPayload)] +pub struct PyCPointerType { + pub(super) inner: PyCPointer, +} + +#[pyclass(name = "_Pointer", base = "PyCData", metaclass = "PyCPointerType", module = "_ctypes")] pub struct PyCPointer {} #[pyclass(flags(BASETYPE, IMMUTABLETYPE))] From 320f15317277ec79985ac5eaeaa7d917da520f7d Mon Sep 17 00:00:00 2001 From: Ashwin Naren Date: Thu, 10 Apr 2025 15:09:19 -0700 Subject: [PATCH 02/12] fix import --- vm/src/stdlib/ctypes/pointer.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/vm/src/stdlib/ctypes/pointer.rs b/vm/src/stdlib/ctypes/pointer.rs index a391f1ef7e..da27415c68 100644 --- a/vm/src/stdlib/ctypes/pointer.rs +++ b/vm/src/stdlib/ctypes/pointer.rs @@ -1,9 +1,14 @@ +crate::builtins::PyType; + #[pyclass(name = "PyCPointerType", base = "PyType", module = "_ctypes")] #[derive(PyPayload)] pub struct PyCPointerType { pub(super) inner: PyCPointer, } +#[pyclass] +impl PyCPointerType {} + #[pyclass(name = "_Pointer", base = "PyCData", metaclass = "PyCPointerType", module = "_ctypes")] pub struct PyCPointer {} From 0035ab4293872850786e9bca01a04e10f921f97c Mon Sep 17 00:00:00 2001 From: Ashwin Naren Date: Thu, 10 Apr 2025 17:59:17 -0700 Subject: [PATCH 03/12] add more classes --- vm/src/stdlib/ctypes.rs | 2 + vm/src/stdlib/ctypes/field.rs | 68 ++++++++++++++++++++++++++++++++ vm/src/stdlib/ctypes/function.rs | 27 +++++++++++-- vm/src/stdlib/ctypes/pointer.rs | 9 ++++- vm/src/stdlib/ctypes/util.rs | 22 +++++++++++ 5 files changed, 123 insertions(+), 5 deletions(-) create mode 100644 vm/src/stdlib/ctypes/field.rs create mode 100644 vm/src/stdlib/ctypes/util.rs diff --git a/vm/src/stdlib/ctypes.rs b/vm/src/stdlib/ctypes.rs index 2902364a1f..e41776a3ef 100644 --- a/vm/src/stdlib/ctypes.rs +++ b/vm/src/stdlib/ctypes.rs @@ -2,11 +2,13 @@ pub(crate) mod array; pub(crate) mod base; +pub(crate) mod field; pub(crate) mod function; pub(crate) mod library; pub(crate) mod pointer; pub(crate) mod structure; pub(crate) mod union; +pub(crate) mod util; use crate::builtins::PyModule; use crate::class::PyClassImpl; diff --git a/vm/src/stdlib/ctypes/field.rs b/vm/src/stdlib/ctypes/field.rs new file mode 100644 index 0000000000..28081fea11 --- /dev/null +++ b/vm/src/stdlib/ctypes/field.rs @@ -0,0 +1,68 @@ +use crate::builtins::PyType; + +#[pyclass(name = "PyCFieldType", base = "PyType", module = "_ctypes")] +#[derive(PyPayload)] +pub struct PyCFieldType { + pub(super) inner: PyCField, +} + +#[pyclass] +impl PyCFieldType {} + +#[pyclass( + name = "CField", + base = "PyCData", + metaclass = "PyCFieldType", + module = "_ctypes" +)] +pub struct PyCField { + byte_offset: usize, + byte_size: usize, + index: usize, + proto: PyTypeRef, + anonymous: bool, + bitfield_size: bool, + bit_offset: u8, + name: String, +} + +impl Representable for PyCFuncPtr { + fn repr_str(zelf: &Py, _vm: &VirtualMachine) -> PyResult { + let field = zelf.inner.read(); + let tp_name = field.proto.name().to_string(); + if field.bitfield_size != 0 { + Ok(format!( + "<{} {} type={}, ofs={}, bit_size={}, bit_offset={}", + field.name, tp_name, field.byte_offset, field.bitfield_size, field.bit_offset + )) + } else { + Ok(format!( + "<{} {} type={}, ofs={}, size={}", + field.name, tp_name, field.byte_offset, field.byte_size + )) + } + } +} + +#[pyclass(flags(BASETYPE, IMMUTABLETYPE), with(Representable))] +impl PyCField { + #[pygetset] + fn size(&self) -> usize { + self.byte_size + } + + #[pygetset] + fn bit_size(&self) -> u8 { + self.bitfield_size + } + + #[pygetset] + fn is_bitfield(&self) -> bool { + self.bitfield_size + } + + #[pygetset] + fn is_anonymous(&self) -> bool { + self.anonymous + } +} diff --git a/vm/src/stdlib/ctypes/function.rs b/vm/src/stdlib/ctypes/function.rs index 21043da27d..fa9b38ad7d 100644 --- a/vm/src/stdlib/ctypes/function.rs +++ b/vm/src/stdlib/ctypes/function.rs @@ -13,16 +13,37 @@ use libffi::middle::{Arg, Cif, CodePtr, Type}; use libloading::Symbol; use num_traits::ToPrimitive; use rustpython_common::lock::PyRwLock; +use std::ffi; use std::fmt::Debug; // https://github.com/python/cpython/blob/4f8bb3947cfbc20f970ff9d9531e1132a9e95396/Modules/_ctypes/callproc.c#L15 +/// Returns none if the `_type_` attribute cannot be found/converted to a string +fn get_type_char(ty: PyTypeRef) -> Option { + // PyTypeRef could be c_int, c_float, c_double, etc., we need to get the _type_ attribute + // and convert it to a Type + let type_str = ty.get_attr("_type_")?; + let type_str = type_str.downcast_ref::()?.to_string(); + Some(type_str) +} + +fn type_ref_to_type(ty: PyTypeRef) -> Type { + // PyTypeRef could be c_int, c_float, c_double, etc., we need to get the _type_ attribute + // and convert it to a Type + unimplemented!() +} + +fn obj_from_type_ref_and_data(data: ffi::c_void, ty: PyTypeRef) -> PyObjectRef { + unimplemented!() +} + #[derive(Debug)] pub struct Function { args: Vec, // TODO: no protection from use-after-free pointer: CodePtr, cif: Cif, + return_type: Option, } unsafe impl Send for Function {} @@ -85,7 +106,7 @@ impl Function { .map_err(|err| vm.new_attribute_error(err))? }; let code_ptr = CodePtr(*pointer as *mut _); - let return_type = match ret_type { + let return_type = match ret_type.clone() { // TODO: Fix this Some(_t) => { return Err(vm.new_not_implemented_error("Return type not implemented".to_string())); @@ -97,6 +118,7 @@ impl Function { args, cif, pointer: code_ptr, + return_type: ret_type, }) } @@ -119,8 +141,7 @@ impl Function { Err(vm.new_type_error("Expected a ctypes simple type".to_string())) }) .collect::>>()?; - // TODO: FIX return - let result: i32 = unsafe { self.cif.call(self.pointer, &args) }; + let result: ffi::c_void = unsafe { self.cif.call(self.pointer, &args) }; Ok(vm.ctx.new_int(result).into()) } } diff --git a/vm/src/stdlib/ctypes/pointer.rs b/vm/src/stdlib/ctypes/pointer.rs index da27415c68..b9f5cf61f8 100644 --- a/vm/src/stdlib/ctypes/pointer.rs +++ b/vm/src/stdlib/ctypes/pointer.rs @@ -1,4 +1,4 @@ -crate::builtins::PyType; +use crate::builtins::PyType; #[pyclass(name = "PyCPointerType", base = "PyType", module = "_ctypes")] #[derive(PyPayload)] @@ -9,7 +9,12 @@ pub struct PyCPointerType { #[pyclass] impl PyCPointerType {} -#[pyclass(name = "_Pointer", base = "PyCData", metaclass = "PyCPointerType", module = "_ctypes")] +#[pyclass( + name = "_Pointer", + base = "PyCData", + metaclass = "PyCPointerType", + module = "_ctypes" +)] pub struct PyCPointer {} #[pyclass(flags(BASETYPE, IMMUTABLETYPE))] diff --git a/vm/src/stdlib/ctypes/util.rs b/vm/src/stdlib/ctypes/util.rs new file mode 100644 index 0000000000..7584220ca6 --- /dev/null +++ b/vm/src/stdlib/ctypes/util.rs @@ -0,0 +1,22 @@ +#[pyclass] +#[derive(Debug, PyPayload)] +pub struct StgInfo { + initialized: i32, + size: usize, // number of bytes + align: usize, // alignment requirements + length: usize, // number of fields + ffi_type_pointer: ffi::ffi_type, + proto: PyObjectRef, // Only for Pointer/ArrayObject + setfunc: Option, // Only for simple objects + getfunc: Option, // Only for simple objects + paramfunc: Option, + + /* Following fields only used by PyCFuncPtrType_Type instances */ + argtypes: Option, // tuple of CDataObjects + converters: Option, // tuple([t.from_param for t in argtypes]) + restype: Option, // CDataObject or NULL + checker: Option, + module: Option, + flags: i32, // calling convention and such + dict_final: u8, +} From b9a41359f27ebc9e067f60b36b73c62ea632c39b Mon Sep 17 00:00:00 2001 From: Ashwin Naren Date: Tue, 22 Apr 2025 20:16:39 -0700 Subject: [PATCH 04/12] ctypes overhall --- vm/src/stdlib/ctypes.rs | 3 + vm/src/stdlib/ctypes/field.rs | 86 +++++++- vm/src/stdlib/ctypes/function.rs | 349 +++++++++++++++++-------------- vm/src/stdlib/ctypes/pointer.rs | 25 ++- vm/src/stdlib/ctypes/thunk.rs | 26 +++ vm/src/stdlib/ctypes/util.rs | 6 +- 6 files changed, 317 insertions(+), 178 deletions(-) create mode 100644 vm/src/stdlib/ctypes/thunk.rs diff --git a/vm/src/stdlib/ctypes.rs b/vm/src/stdlib/ctypes.rs index e41776a3ef..9f0c7ad83f 100644 --- a/vm/src/stdlib/ctypes.rs +++ b/vm/src/stdlib/ctypes.rs @@ -7,6 +7,7 @@ pub(crate) mod function; pub(crate) mod library; pub(crate) mod pointer; pub(crate) mod structure; +pub(crate) mod thunk; pub(crate) mod union; pub(crate) mod util; @@ -23,11 +24,13 @@ pub fn extend_module_nodes(vm: &VirtualMachine, module: &Py) { "_CData" => PyCData::make_class(ctx), "_SimpleCData" => PyCSimple::make_class(ctx), "Array" => array::PyCArray::make_class(ctx), + "CField" => field::PyCField::make_class(ctx), "CFuncPtr" => function::PyCFuncPtr::make_class(ctx), "PyCPointerType" => pointer::PyCPointerType::make_class(ctx), "_Pointer" => pointer::PyCPointer::make_class(ctx), "_pointer_type_cache" => ctx.new_dict(), "Structure" => structure::PyCStructure::make_class(ctx), + "CThunkObject" => thunk::PyCThunk::make_class(ctx), "Union" => union::PyCUnion::make_class(ctx), }) } diff --git a/vm/src/stdlib/ctypes/field.rs b/vm/src/stdlib/ctypes/field.rs index 28081fea11..653c8047ee 100644 --- a/vm/src/stdlib/ctypes/field.rs +++ b/vm/src/stdlib/ctypes/field.rs @@ -1,7 +1,12 @@ use crate::builtins::PyType; +use crate::builtins::PyTypeRef; +use crate::stdlib::ctypes::PyCData; +use crate::types::Constructor; +use crate::types::Representable; +use crate::{Py, PyResult, VirtualMachine}; #[pyclass(name = "PyCFieldType", base = "PyType", module = "_ctypes")] -#[derive(PyPayload)] +#[derive(PyPayload, Debug)] pub struct PyCFieldType { pub(super) inner: PyCField, } @@ -15,6 +20,7 @@ impl PyCFieldType {} metaclass = "PyCFieldType", module = "_ctypes" )] +#[derive(Debug, PyPayload)] pub struct PyCField { byte_offset: usize, byte_size: usize, @@ -26,25 +32,45 @@ pub struct PyCField { name: String, } -impl Representable for PyCFuncPtr { +impl Representable for PyCField { fn repr_str(zelf: &Py, _vm: &VirtualMachine) -> PyResult { - let field = zelf.inner.read(); - let tp_name = field.proto.name().to_string(); - if field.bitfield_size != 0 { + let tp_name = zelf.proto.name().to_string(); + if zelf.bitfield_size != false { Ok(format!( - "<{} {} type={}, ofs={}, bit_size={}, bit_offset={}", - field.name, tp_name, field.byte_offset, field.bitfield_size, field.bit_offset + "<{} type={}, ofs={byte_offset}, bit_size={bitfield_size}, bit_offset={bit_offset}", + zelf.name, + tp_name, + byte_offset = zelf.byte_offset, + bitfield_size = zelf.bitfield_size, + bit_offset = zelf.bit_offset )) } else { Ok(format!( - "<{} {} type={}, ofs={}, size={}", - field.name, tp_name, field.byte_offset, field.byte_size + "<{} type={tp_name}, ofs={}, size={}", + zelf.name, zelf.byte_offset, zelf.byte_size )) } } } -#[pyclass(flags(BASETYPE, IMMUTABLETYPE), with(Representable))] +#[derive(Debug, FromArgs)] +struct PyCFieldConstructorArgs { + // PyObject *name, PyObject *proto, + // Py_ssize_t byte_size, Py_ssize_t byte_offset, + // Py_ssize_t index, int _internal_use, + // PyObject *bit_size_obj, PyObject *bit_offset_obj + +} + +impl Constructor for PyCField { + type Args = PyCFieldConstructorArgs; + + fn py_new(_cls: PyTypeRef, _args: Self::Args, vm: &VirtualMachine) -> PyResult { + Err(vm.new_type_error("Cannot instantiate a PyCField".to_string())) + } +} + +#[pyclass(flags(BASETYPE, IMMUTABLETYPE), with(Constructor, Representable))] impl PyCField { #[pygetset] fn size(&self) -> usize { @@ -52,7 +78,7 @@ impl PyCField { } #[pygetset] - fn bit_size(&self) -> u8 { + fn bit_size(&self) -> bool { self.bitfield_size } @@ -65,4 +91,42 @@ impl PyCField { fn is_anonymous(&self) -> bool { self.anonymous } + + #[pygetset] + fn name(&self) -> String { + self.name.clone() + } + + #[pygetset(name = "type")] + fn type_(&self) -> PyTypeRef { + self.proto.clone() + } + + #[pygetset] + fn offset(&self) -> usize { + self.byte_offset + } + + #[pygetset] + fn byte_offset(&self) -> usize { + self.byte_offset + } + + #[pygetset] + fn byte_size(&self) -> usize { + self.byte_size + } + + #[pygetset] + fn bit_offset(&self) -> u8 { + self.bit_offset + } +} + +pub(crate) fn low_bit(offset: usize) -> usize { + offset & 0xFFFF +} + +pub(crate) fn high_bit(offset: usize) -> usize { + offset >> 16 } diff --git a/vm/src/stdlib/ctypes/function.rs b/vm/src/stdlib/ctypes/function.rs index fa9b38ad7d..5369f2dff2 100644 --- a/vm/src/stdlib/ctypes/function.rs +++ b/vm/src/stdlib/ctypes/function.rs @@ -1,159 +1,104 @@ // cspell:disable -use crate::builtins::{PyStr, PyTupleRef, PyTypeRef}; +use crate::builtins::{PyNone, PyStr, PyTupleRef, PyTypeRef}; use crate::convert::ToPyObject; use crate::function::FuncArgs; use crate::stdlib::ctypes::PyCData; use crate::stdlib::ctypes::array::PyCArray; use crate::stdlib::ctypes::base::{PyCSimple, ffi_type_from_str}; use crate::types::{Callable, Constructor}; -use crate::{Py, PyObjectRef, PyResult, VirtualMachine}; +use crate::{AsObject, Py, PyObjectRef, PyResult, VirtualMachine}; use crossbeam_utils::atomic::AtomicCell; use libffi::middle::{Arg, Cif, CodePtr, Type}; use libloading::Symbol; use num_traits::ToPrimitive; use rustpython_common::lock::PyRwLock; -use std::ffi; +use std::ffi::{self, c_void}; use std::fmt::Debug; +use crate::types::Representable; +use crate::function::Either; -// https://github.com/python/cpython/blob/4f8bb3947cfbc20f970ff9d9531e1132a9e95396/Modules/_ctypes/callproc.c#L15 +// See also: https://github.com/python/cpython/blob/4f8bb3947cfbc20f970ff9d9531e1132a9e95396/Modules/_ctypes/callproc.c#L15 -/// Returns none if the `_type_` attribute cannot be found/converted to a string -fn get_type_char(ty: PyTypeRef) -> Option { - // PyTypeRef could be c_int, c_float, c_double, etc., we need to get the _type_ attribute - // and convert it to a Type - let type_str = ty.get_attr("_type_")?; - let type_str = type_str.downcast_ref::()?.to_string(); - Some(type_str) -} +type FP = unsafe extern "C" fn(); -fn type_ref_to_type(ty: PyTypeRef) -> Type { - // PyTypeRef could be c_int, c_float, c_double, etc., we need to get the _type_ attribute - // and convert it to a Type - unimplemented!() +pub trait ArgumentType { + fn to_ffi_type(&self, vm: &VirtualMachine) -> PyResult; + fn convert_object(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult; } -fn obj_from_type_ref_and_data(data: ffi::c_void, ty: PyTypeRef) -> PyObjectRef { - unimplemented!() +impl ArgumentType for PyTypeRef { + fn to_ffi_type(&self, vm: &VirtualMachine) -> PyResult { + let typ = self.get_class_attr(vm.ctx.intern_str("_type_")).ok_or( + vm.new_type_error("Unsupported argument type".to_string()))?; + let typ = typ.downcast_ref::().ok_or( + vm.new_type_error("Unsupported argument type".to_string()))?; + let typ = typ.to_string(); + let typ = typ.as_str(); + let typ = ffi_type_from_str(typ); + if let Some(typ) = typ { + return Ok(typ); + } else { + return Err(vm.new_type_error(format!("Unsupported argument type: {}", typ))); + } + } + + fn convert_object(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult { + // if self.fast_isinstance::(vm) { + // let array = value.downcast::()?; + // return Ok(Arg::from(array.as_ptr())); + // } + if self.fast_isinstance::(vm) { + let simple = value.downcast::()?; + let typ = self.to_ffi_type(vm)?; + simple.to_arg(typ, vm)?; + return Ok(Arg::from(simple.as_ptr())); + } + Err(vm.new_type_error("Unsupported argument type".to_string())) + } } -#[derive(Debug)] -pub struct Function { - args: Vec, - // TODO: no protection from use-after-free - pointer: CodePtr, - cif: Cif, - return_type: Option, +pub trait ReturnType { + fn to_ffi_type(&self) -> Type; + fn from_ffi_type(&self, value: *mut ffi::c_void, vm: &VirtualMachine) -> PyResult>; } -unsafe impl Send for Function {} -unsafe impl Sync for Function {} +impl ReturnType for PyTypeRef { + fn to_ffi_type(&self) -> Type { + ffi_type_from_str(self.name().as_str()) + } -type FP = unsafe extern "C" fn(); + fn from_ffi_type(&self, value: *mut ffi::c_void, vm: &VirtualMachine) -> PyResult> { + if self.isinstance::(vm) { + let array = value.downcast::()?; + return Ok(Some(array.to_object(vm))); + } + if self.isinstance::(vm) { + let simple = value.downcast::()?; + return Ok(Some(simple.to_object(vm))); + } + Err(vm.new_type_error("Unsupported return type".to_string())) + } +} -impl Function { - pub unsafe fn load( - library: &libloading::Library, - function: &str, - args: &[PyObjectRef], - ret_type: &Option, - vm: &VirtualMachine, - ) -> PyResult { - // map each arg to a PyCSimple - let args = args - .iter() - .map(|arg| { - if let Some(arg) = arg.payload_if_subclass::(vm) { - let converted = ffi_type_from_str(&arg._type_); - return match converted { - Some(t) => Ok(t), - None => Err(vm.new_type_error("Invalid type".to_string())), // TODO: add type name - }; - } - if let Some(arg) = arg.payload_if_subclass::(vm) { - let t = arg.typ.read(); - let ty_attributes = t.attributes.read(); - let ty_pystr = - ty_attributes - .get(vm.ctx.intern_str("_type_")) - .ok_or_else(|| { - vm.new_type_error("Expected a ctypes simple type".to_string()) - })?; - let ty_str = ty_pystr - .downcast_ref::() - .ok_or_else(|| { - vm.new_type_error("Expected a ctypes simple type".to_string()) - })? - .to_string(); - let converted = ffi_type_from_str(&ty_str); - match converted { - Some(_t) => { - // TODO: Use - Ok(Type::void()) - } - None => Err(vm.new_type_error("Invalid type".to_string())), // TODO: add type name - } - } else { - Err(vm.new_type_error("Expected a ctypes simple type".to_string())) - } - }) - .collect::>>()?; - let terminated = format!("{}\0", function); - let pointer: Symbol<'_, FP> = unsafe { - library - .get(terminated.as_bytes()) - .map_err(|err| err.to_string()) - .map_err(|err| vm.new_attribute_error(err))? - }; - let code_ptr = CodePtr(*pointer as *mut _); - let return_type = match ret_type.clone() { - // TODO: Fix this - Some(_t) => { - return Err(vm.new_not_implemented_error("Return type not implemented".to_string())); - } - None => Type::c_int(), - }; - let cif = Cif::new(args.clone(), return_type); - Ok(Function { - args, - cif, - pointer: code_ptr, - return_type: ret_type, - }) +impl ReturnType for PyNone { + fn to_ffi_type(&self) -> Type { + ffi_type_from_str("void") } - pub unsafe fn call( - &self, - args: Vec, - vm: &VirtualMachine, - ) -> PyResult { - let args = args - .into_iter() - .enumerate() - .map(|(count, arg)| { - // none type check - if let Some(d) = arg.payload_if_subclass::(vm) { - return Ok(d.to_arg(self.args[count].clone(), vm).unwrap()); - } - if let Some(d) = arg.payload_if_subclass::(vm) { - return Ok(d.to_arg(vm).unwrap()); - } - Err(vm.new_type_error("Expected a ctypes simple type".to_string())) - }) - .collect::>>()?; - let result: ffi::c_void = unsafe { self.cif.call(self.pointer, &args) }; - Ok(vm.ctx.new_int(result).into()) + fn from_ffi_type(&self, _value: *mut ffi::c_void, vm: &VirtualMachine) -> PyResult> { + Ok(None) } } #[pyclass(module = "_ctypes", name = "CFuncPtr", base = "PyCData")] #[derive(PyPayload)] pub struct PyCFuncPtr { - pub name: PyRwLock, - pub _flags_: AtomicCell, - // FIXME(arihant2math): This shouldn't be an option, setting the default as the none type should work - // This is a workaround for now and I'll fix it later - pub _restype_: PyRwLock>, + pub ptr: PyRwLock>, + pub needs_free: AtomicCell, + pub arg_types: PyRwLock>>, + pub res_type: PyRwLock>, + pub _flags_: AtomicCell, pub handler: PyObjectRef, } @@ -180,10 +125,35 @@ impl Constructor for PyCFuncPtr { .nth(1) .ok_or(vm.new_type_error("Expected a tuple with at least 2 elements".to_string()))? .clone(); + let handle = handler.try_int(vm)?.as_bigint().clone(); + let library_cache = crate::stdlib::ctypes::library::libcache().read(); + let library = library_cache + .get_lib( + handle + .to_usize() + .ok_or(vm.new_value_error("Invalid handle".to_string()))?, + ) + .ok_or_else(|| vm.new_value_error("Library not found".to_string()))?; + let inner_lib = library.lib.lock(); + + let terminated = format!("{}\0", &name); + let code_ptr = if let Some(lib) = &*inner_lib { + let pointer: Symbol<'_, FP> = unsafe { + lib + .get(terminated.as_bytes()) + .map_err(|err| err.to_string()) + .map_err(|err| vm.new_attribute_error(err))? + }; + Some(CodePtr(*pointer as *mut _)) + } else { + None + }; Ok(Self { + ptr: PyRwLock::new(code_ptr), + needs_free: AtomicCell::new(false), + arg_types: PyRwLock::new(None), _flags_: AtomicCell::new(0), - name: PyRwLock::new(name), - _restype_: PyRwLock::new(None), + res_type: PyRwLock::new(None), handler, } .to_pyobject(vm)) @@ -193,53 +163,108 @@ impl Constructor for PyCFuncPtr { impl Callable for PyCFuncPtr { type Args = FuncArgs; fn call(zelf: &Py, args: Self::Args, vm: &VirtualMachine) -> PyResult { - unsafe { - let handle = zelf.handler.get_attr("_handle", vm)?; - let handle = handle.try_int(vm)?.as_bigint().clone(); - let library_cache = crate::stdlib::ctypes::library::libcache().read(); - let library = library_cache - .get_lib( - handle - .to_usize() - .ok_or(vm.new_value_error("Invalid handle".to_string()))?, - ) - .ok_or_else(|| vm.new_value_error("Library not found".to_string()))?; - let inner_lib = library.lib.lock(); - let name = zelf.name.read(); - let res_type = zelf._restype_.read(); - let func = Function::load( - inner_lib - .as_ref() - .ok_or_else(|| vm.new_value_error("Library not found".to_string()))?, - &name, - &args.args, - &res_type, - vm, - )?; - func.call(args.args, vm) + // This is completely seperate from the C python implementation + + // Cif init + let arg_types = zelf.arg_types.read(); + let ffi_arg_types = arg_types.as_ref().ok_or_else(|| { + vm.new_type_error("argtypes not set".to_string()) + })?.iter().map(|t| { + t.to_ffi_type() + }).collect::>(); + let return_type = zelf.res_type.read(); + let ffi_return_type = return_type.as_ref().map(|t| { + t.to_ffi_type() + }).unwrap_or_else(|| Type::i32()); + let cif = Cif::new(ffi_arg_types, ffi_return_type); + + // Call the function + let ffi_args = args + .into_iter() + .map(|arg| { + let arg_type = arg_types.get(0).ok_or_else(|| { + vm.new_type_error("argtypes not set".to_string()) + })?; + arg_type.convert_object(arg, vm) + }) + .collect::, _>>()?; + let pointer = zelf.ptr.read(); + let code_ptr = pointer.as_ref().ok_or_else(|| { + vm.new_type_error("Function pointer not set".to_string()) + })?; + let output: c_void = unsafe { + cif.call(*code_ptr, &args) + }; + let return_type = return_type.map(|f| f.from_ffi_type(output)?).unwrap_or_else(|| { + vm.ctx.new_int(output as i32) + }); + if let Some(return_type) = return_type { + Ok(return_type) + } else { + Ok(vm.get_none()) } } } -#[pyclass(flags(BASETYPE), with(Callable, Constructor))] -impl PyCFuncPtr { - #[pygetset(magic)] - fn name(&self) -> String { - self.name.read().clone() +impl Representable for PyCFuncPtr { + fn repr_str(zelf: &Py ,vm: &VirtualMachine) -> PyResult { + let index = zelf.ptr.read(); + let index = index.map(|ptr| ptr.0 as usize).unwrap_or(0); + let type_name = zelf.class().name(); + #[cfg(windows)] + { + let index = index - 0x1000; + return Ok(format!("")); + } + Ok(format!("<{type_name} object at {index:#x}>")) } +} - #[pygetset(setter, magic)] - fn set_name(&self, name: String) { - *self.name.write() = name; - } +// TODO: fix +unsafe impl Send for PyCFuncPtr {} +unsafe impl Sync for PyCFuncPtr {} +#[pyclass(flags(BASETYPE), with(Callable, Constructor, Representable))] +impl PyCFuncPtr { #[pygetset(name = "_restype_")] - fn restype(&self) -> Option { - self._restype_.read().as_ref().cloned() + fn restype(&self) -> Option { + self.res_type.read().as_ref().cloned() } #[pygetset(name = "_restype_", setter)] - fn set_restype(&self, restype: PyTypeRef) { - *self._restype_.write() = Some(restype); + fn set_restype(&self, restype: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + // has to be type, callable, or none + // TODO: Callable support + if vm.is_none(&restype) || restype.isinstance::(vm) { + *self.res_type.write() = Some(restype); + Ok(()) + } else { + Err(vm.new_type_error("restype must be a type, a callable, or None".to_string())) + } + } + + #[pygetset(name = "argtypes")] + fn argtypes(&self) -> Vec { + self.arg_types.read().clone() + } + + #[pygetset(name = "argtypes", setter)] + fn set_argtypes(&self, argtypes: Either, vm: &VirtualMachine) -> PyResult<()> { + match argtypes { + Either::A(_) => { + *self.arg_types.write() = None; + Ok(()) + } + Either::B(tuple) => { + self.arg_types.write() = Some(tuple.iter() + .map(|obj| { + obj.downcast_ref::() + .ok_or_else(|| vm.new_type_error("Expected a type".to_string())) + }) + .collect::, _>>()?); + Ok(()) + } + } + } } diff --git a/vm/src/stdlib/ctypes/pointer.rs b/vm/src/stdlib/ctypes/pointer.rs index b9f5cf61f8..cff006e825 100644 --- a/vm/src/stdlib/ctypes/pointer.rs +++ b/vm/src/stdlib/ctypes/pointer.rs @@ -1,7 +1,11 @@ +use rustpython_common::lock::PyRwLock; + use crate::builtins::PyType; +use crate::stdlib::ctypes::PyCData; +use crate::{PyObjectRef, PyResult, VirtualMachine}; #[pyclass(name = "PyCPointerType", base = "PyType", module = "_ctypes")] -#[derive(PyPayload)] +#[derive(PyPayload, Debug)] pub struct PyCPointerType { pub(super) inner: PyCPointer, } @@ -15,7 +19,22 @@ impl PyCPointerType {} metaclass = "PyCPointerType", module = "_ctypes" )] -pub struct PyCPointer {} +#[derive(Debug, PyPayload)] +pub struct PyCPointer { + contents: PyRwLock +} #[pyclass(flags(BASETYPE, IMMUTABLETYPE))] -impl PyCPointer {} +impl PyCPointer { + // TODO: not correct + #[pygetset] + fn contents(&self, vm: &VirtualMachine) -> PyResult { + let contents = self.contents.read().clone(); + Ok(contents) + } + #[pygetset(setter)] + fn set_contents(&self, contents: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + *self.contents.write() = contents; + Ok(()) + } +} diff --git a/vm/src/stdlib/ctypes/thunk.rs b/vm/src/stdlib/ctypes/thunk.rs new file mode 100644 index 0000000000..8d8ed02e68 --- /dev/null +++ b/vm/src/stdlib/ctypes/thunk.rs @@ -0,0 +1,26 @@ +//! Yes, really, this is not a typo. + +// 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; + +#[pyclass(name = "CThunkObject", module = "_ctypes")] +#[derive(Debug, PyPayload)] +pub struct PyCThunk { + +} + +#[pyclass] +impl PyCThunk { + +} diff --git a/vm/src/stdlib/ctypes/util.rs b/vm/src/stdlib/ctypes/util.rs index 7584220ca6..4dcf06b9c8 100644 --- a/vm/src/stdlib/ctypes/util.rs +++ b/vm/src/stdlib/ctypes/util.rs @@ -1,11 +1,13 @@ -#[pyclass] +use crate::PyObjectRef; + +#[pyclass(name, module = "_ctypes")] #[derive(Debug, PyPayload)] pub struct StgInfo { initialized: i32, size: usize, // number of bytes align: usize, // alignment requirements length: usize, // number of fields - ffi_type_pointer: ffi::ffi_type, + // ffi_type_pointer: ffi::ffi_type, proto: PyObjectRef, // Only for Pointer/ArrayObject setfunc: Option, // Only for simple objects getfunc: Option, // Only for simple objects From d92127cccb0f7fd4e6e48be88666d85d0f77c022 Mon Sep 17 00:00:00 2001 From: Ashwin Naren Date: Sun, 27 Apr 2025 10:43:56 -0700 Subject: [PATCH 05/12] updates --- vm/src/stdlib/ctypes.rs | 2 +- vm/src/stdlib/ctypes/field.rs | 1 - vm/src/stdlib/ctypes/function.rs | 139 ++++++++++++++++--------------- vm/src/stdlib/ctypes/pointer.rs | 8 +- vm/src/stdlib/ctypes/thunk.rs | 8 +- 5 files changed, 81 insertions(+), 77 deletions(-) diff --git a/vm/src/stdlib/ctypes.rs b/vm/src/stdlib/ctypes.rs index 9f0c7ad83f..c1561332e1 100644 --- a/vm/src/stdlib/ctypes.rs +++ b/vm/src/stdlib/ctypes.rs @@ -30,7 +30,7 @@ pub fn extend_module_nodes(vm: &VirtualMachine, module: &Py) { "_Pointer" => pointer::PyCPointer::make_class(ctx), "_pointer_type_cache" => ctx.new_dict(), "Structure" => structure::PyCStructure::make_class(ctx), - "CThunkObject" => thunk::PyCThunk::make_class(ctx), + "CThunkObject" => thunk::PyCThunk::make_class(ctx), "Union" => union::PyCUnion::make_class(ctx), }) } diff --git a/vm/src/stdlib/ctypes/field.rs b/vm/src/stdlib/ctypes/field.rs index 653c8047ee..364d32fe44 100644 --- a/vm/src/stdlib/ctypes/field.rs +++ b/vm/src/stdlib/ctypes/field.rs @@ -59,7 +59,6 @@ struct PyCFieldConstructorArgs { // Py_ssize_t byte_size, Py_ssize_t byte_offset, // Py_ssize_t index, int _internal_use, // PyObject *bit_size_obj, PyObject *bit_offset_obj - } impl Constructor for PyCField { diff --git a/vm/src/stdlib/ctypes/function.rs b/vm/src/stdlib/ctypes/function.rs index 5369f2dff2..5e9d321eab 100644 --- a/vm/src/stdlib/ctypes/function.rs +++ b/vm/src/stdlib/ctypes/function.rs @@ -1,11 +1,13 @@ // cspell:disable -use crate::builtins::{PyNone, PyStr, PyTupleRef, PyTypeRef}; +use crate::builtins::{PyNone, PyStr, PyTupleRef, PyType, PyTypeRef}; use crate::convert::ToPyObject; +use crate::function::Either; use crate::function::FuncArgs; use crate::stdlib::ctypes::PyCData; use crate::stdlib::ctypes::array::PyCArray; use crate::stdlib::ctypes::base::{PyCSimple, ffi_type_from_str}; +use crate::types::Representable; use crate::types::{Callable, Constructor}; use crate::{AsObject, Py, PyObjectRef, PyResult, VirtualMachine}; use crossbeam_utils::atomic::AtomicCell; @@ -15,8 +17,6 @@ use num_traits::ToPrimitive; use rustpython_common::lock::PyRwLock; use std::ffi::{self, c_void}; use std::fmt::Debug; -use crate::types::Representable; -use crate::function::Either; // See also: https://github.com/python/cpython/blob/4f8bb3947cfbc20f970ff9d9531e1132a9e95396/Modules/_ctypes/callproc.c#L15 @@ -29,17 +29,19 @@ pub trait ArgumentType { impl ArgumentType for PyTypeRef { fn to_ffi_type(&self, vm: &VirtualMachine) -> PyResult { - let typ = self.get_class_attr(vm.ctx.intern_str("_type_")).ok_or( - vm.new_type_error("Unsupported argument type".to_string()))?; - let typ = typ.downcast_ref::().ok_or( - vm.new_type_error("Unsupported argument type".to_string()))?; + let typ = self + .get_class_attr(vm.ctx.intern_str("_type_")) + .ok_or(vm.new_type_error("Unsupported argument type".to_string()))?; + let typ = typ + .downcast_ref::() + .ok_or(vm.new_type_error("Unsupported argument type".to_string()))?; let typ = typ.to_string(); let typ = typ.as_str(); - let typ = ffi_type_from_str(typ); - if let Some(typ) = typ { - return Ok(typ); + let converted_typ = ffi_type_from_str(typ); + if let Some(typ) = converted_typ { + Ok(typ) } else { - return Err(vm.new_type_error(format!("Unsupported argument type: {}", typ))); + Err(vm.new_type_error(format!("Unsupported argument type: {}", typ))) } } @@ -48,45 +50,48 @@ impl ArgumentType for PyTypeRef { // let array = value.downcast::()?; // return Ok(Arg::from(array.as_ptr())); // } - if self.fast_isinstance::(vm) { - let simple = value.downcast::()?; - let typ = self.to_ffi_type(vm)?; - simple.to_arg(typ, vm)?; - return Ok(Arg::from(simple.as_ptr())); + if let Ok(simple) = value.downcast::() { + let typ = ArgumentType::to_ffi_type(self, vm)?; + let arg = simple.to_arg(typ, vm).ok_or(vm.new_type_error("Unsupported argument type".to_string()))?; + return Ok(arg); } Err(vm.new_type_error("Unsupported argument type".to_string())) } } pub trait ReturnType { - fn to_ffi_type(&self) -> Type; - fn from_ffi_type(&self, value: *mut ffi::c_void, vm: &VirtualMachine) -> PyResult>; + fn to_ffi_type(&self) -> Option; + fn from_ffi_type( + &self, + value: *mut ffi::c_void, + vm: &VirtualMachine, + ) -> PyResult>; } impl ReturnType for PyTypeRef { - fn to_ffi_type(&self) -> Type { - ffi_type_from_str(self.name().as_str()) + fn to_ffi_type(&self) -> Option { + ffi_type_from_str(self.name().to_string().as_str()) } - fn from_ffi_type(&self, value: *mut ffi::c_void, vm: &VirtualMachine) -> PyResult> { - if self.isinstance::(vm) { - let array = value.downcast::()?; - return Ok(Some(array.to_object(vm))); - } - if self.isinstance::(vm) { - let simple = value.downcast::()?; - return Ok(Some(simple.to_object(vm))); - } - Err(vm.new_type_error("Unsupported return type".to_string())) + fn from_ffi_type( + &self, + _value: *mut ffi::c_void, + _vm: &VirtualMachine, + ) -> PyResult> { + todo!() } } impl ReturnType for PyNone { - fn to_ffi_type(&self) -> Type { + fn to_ffi_type(&self) -> Option { ffi_type_from_str("void") } - fn from_ffi_type(&self, _value: *mut ffi::c_void, vm: &VirtualMachine) -> PyResult> { + fn from_ffi_type( + &self, + _value: *mut ffi::c_void, + _vm: &VirtualMachine, + ) -> PyResult> { Ok(None) } } @@ -105,7 +110,7 @@ pub struct PyCFuncPtr { impl Debug for PyCFuncPtr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("PyCFuncPtr") - .field("name", &self.name) + .field("flags", &self._flags_) .finish() } } @@ -135,12 +140,11 @@ impl Constructor for PyCFuncPtr { ) .ok_or_else(|| vm.new_value_error("Library not found".to_string()))?; let inner_lib = library.lib.lock(); - + let terminated = format!("{}\0", &name); let code_ptr = if let Some(lib) = &*inner_lib { let pointer: Symbol<'_, FP> = unsafe { - lib - .get(terminated.as_bytes()) + lib.get(terminated.as_bytes()) .map_err(|err| err.to_string()) .map_err(|err| vm.new_attribute_error(err))? }; @@ -167,37 +171,37 @@ impl Callable for PyCFuncPtr { // Cif init let arg_types = zelf.arg_types.read(); - let ffi_arg_types = arg_types.as_ref().ok_or_else(|| { - vm.new_type_error("argtypes not set".to_string()) - })?.iter().map(|t| { - t.to_ffi_type() - }).collect::>(); + let ffi_arg_types = arg_types + .as_ref() + .ok_or_else(|| vm.new_type_error("argtypes not set".to_string()))? + .iter() + .map(|t| ArgumentType::to_ffi_type(&t)) + .collect::>(); let return_type = zelf.res_type.read(); - let ffi_return_type = return_type.as_ref().map(|t| { - t.to_ffi_type() - }).unwrap_or_else(|| Type::i32()); + let ffi_return_type = return_type + .as_ref() + .map(|t| ReturnType::to_ffi_type(t)) + .unwrap_or_else(|| Type::i32()); let cif = Cif::new(ffi_arg_types, ffi_return_type); // Call the function let ffi_args = args .into_iter() .map(|arg| { - let arg_type = arg_types.get(0).ok_or_else(|| { - vm.new_type_error("argtypes not set".to_string()) - })?; + let arg_type = arg_types + .get(0) + .ok_or_else(|| vm.new_type_error("argtypes not set".to_string()))?; arg_type.convert_object(arg, vm) }) .collect::, _>>()?; let pointer = zelf.ptr.read(); - let code_ptr = pointer.as_ref().ok_or_else(|| { - vm.new_type_error("Function pointer not set".to_string()) - })?; - let output: c_void = unsafe { - cif.call(*code_ptr, &args) - }; - let return_type = return_type.map(|f| f.from_ffi_type(output)?).unwrap_or_else(|| { - vm.ctx.new_int(output as i32) - }); + let code_ptr = pointer + .as_ref() + .ok_or_else(|| vm.new_type_error("Function pointer not set".to_string()))?; + let output: c_void = unsafe { cif.call(*code_ptr, &args) }; + let return_type = return_type + .map(|f| f.from_ffi_type(output)?) + .unwrap_or_else(|| vm.ctx.new_int(output as i32)); if let Some(return_type) = return_type { Ok(return_type) } else { @@ -207,7 +211,7 @@ impl Callable for PyCFuncPtr { } impl Representable for PyCFuncPtr { - fn repr_str(zelf: &Py ,vm: &VirtualMachine) -> PyResult { + fn repr_str(zelf: &Py, _vm: &VirtualMachine) -> PyResult { let index = zelf.ptr.read(); let index = index.map(|ptr| ptr.0 as usize).unwrap_or(0); let type_name = zelf.class().name(); @@ -249,22 +253,27 @@ impl PyCFuncPtr { } #[pygetset(name = "argtypes", setter)] - fn set_argtypes(&self, argtypes: Either, vm: &VirtualMachine) -> PyResult<()> { + fn set_argtypes( + &self, + argtypes: Either, + vm: &VirtualMachine, + ) -> PyResult<()> { match argtypes { Either::A(_) => { *self.arg_types.write() = None; Ok(()) } Either::B(tuple) => { - self.arg_types.write() = Some(tuple.iter() - .map(|obj| { - obj.downcast_ref::() - .ok_or_else(|| vm.new_type_error("Expected a type".to_string())) - }) - .collect::, _>>()?); + *self.arg_types.write() = Some( + tuple + .iter() + .map(|obj| { + obj.downcast_ref::().unwrap() + }) + .collect::>() + ); Ok(()) } } - } } diff --git a/vm/src/stdlib/ctypes/pointer.rs b/vm/src/stdlib/ctypes/pointer.rs index cff006e825..eb4a92f625 100644 --- a/vm/src/stdlib/ctypes/pointer.rs +++ b/vm/src/stdlib/ctypes/pointer.rs @@ -2,7 +2,7 @@ use rustpython_common::lock::PyRwLock; use crate::builtins::PyType; use crate::stdlib::ctypes::PyCData; -use crate::{PyObjectRef, PyResult, VirtualMachine}; +use crate::{PyObjectRef, PyResult}; #[pyclass(name = "PyCPointerType", base = "PyType", module = "_ctypes")] #[derive(PyPayload, Debug)] @@ -21,19 +21,19 @@ impl PyCPointerType {} )] #[derive(Debug, PyPayload)] pub struct PyCPointer { - contents: PyRwLock + contents: PyRwLock, } #[pyclass(flags(BASETYPE, IMMUTABLETYPE))] impl PyCPointer { // TODO: not correct #[pygetset] - fn contents(&self, vm: &VirtualMachine) -> PyResult { + fn contents(&self) -> PyResult { let contents = self.contents.read().clone(); Ok(contents) } #[pygetset(setter)] - fn set_contents(&self, contents: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + fn set_contents(&self, contents: PyObjectRef) -> PyResult<()> { *self.contents.write() = contents; Ok(()) } diff --git a/vm/src/stdlib/ctypes/thunk.rs b/vm/src/stdlib/ctypes/thunk.rs index 8d8ed02e68..a65b04684b 100644 --- a/vm/src/stdlib/ctypes/thunk.rs +++ b/vm/src/stdlib/ctypes/thunk.rs @@ -16,11 +16,7 @@ #[pyclass(name = "CThunkObject", module = "_ctypes")] #[derive(Debug, PyPayload)] -pub struct PyCThunk { - -} +pub struct PyCThunk {} #[pyclass] -impl PyCThunk { - -} +impl PyCThunk {} From 7a6d045a42ddc25895581617182a68dd422530fc Mon Sep 17 00:00:00 2001 From: Ashwin Naren Date: Sun, 27 Apr 2025 11:26:20 -0700 Subject: [PATCH 06/12] fix build --- vm/src/stdlib/ctypes/field.rs | 2 +- vm/src/stdlib/ctypes/function.rs | 91 +++++++++++++++++++------------- 2 files changed, 54 insertions(+), 39 deletions(-) diff --git a/vm/src/stdlib/ctypes/field.rs b/vm/src/stdlib/ctypes/field.rs index 364d32fe44..65e23ce653 100644 --- a/vm/src/stdlib/ctypes/field.rs +++ b/vm/src/stdlib/ctypes/field.rs @@ -54,7 +54,7 @@ impl Representable for PyCField { } #[derive(Debug, FromArgs)] -struct PyCFieldConstructorArgs { +pub struct PyCFieldConstructorArgs { // PyObject *name, PyObject *proto, // Py_ssize_t byte_size, Py_ssize_t byte_offset, // Py_ssize_t index, int _internal_use, diff --git a/vm/src/stdlib/ctypes/function.rs b/vm/src/stdlib/ctypes/function.rs index 5e9d321eab..e199770c64 100644 --- a/vm/src/stdlib/ctypes/function.rs +++ b/vm/src/stdlib/ctypes/function.rs @@ -1,15 +1,13 @@ // cspell:disable -use crate::builtins::{PyNone, PyStr, PyTupleRef, PyType, PyTypeRef}; +use crate::builtins::{PyNone, PyStr, PyTuple, PyTupleRef, PyType, PyTypeRef}; use crate::convert::ToPyObject; -use crate::function::Either; use crate::function::FuncArgs; use crate::stdlib::ctypes::PyCData; -use crate::stdlib::ctypes::array::PyCArray; use crate::stdlib::ctypes::base::{PyCSimple, ffi_type_from_str}; use crate::types::Representable; use crate::types::{Callable, Constructor}; -use crate::{AsObject, Py, PyObjectRef, PyResult, VirtualMachine}; +use crate::{AsObject, Py, PyObjectRef, PyPayload, PyResult, VirtualMachine}; use crossbeam_utils::atomic::AtomicCell; use libffi::middle::{Arg, Cif, CodePtr, Type}; use libloading::Symbol; @@ -52,7 +50,9 @@ impl ArgumentType for PyTypeRef { // } if let Ok(simple) = value.downcast::() { let typ = ArgumentType::to_ffi_type(self, vm)?; - let arg = simple.to_arg(typ, vm).ok_or(vm.new_type_error("Unsupported argument type".to_string()))?; + let arg = simple + .to_arg(typ, vm) + .ok_or(vm.new_type_error("Unsupported argument type".to_string()))?; return Ok(arg); } Err(vm.new_type_error("Unsupported argument type".to_string())) @@ -175,22 +175,27 @@ impl Callable for PyCFuncPtr { .as_ref() .ok_or_else(|| vm.new_type_error("argtypes not set".to_string()))? .iter() - .map(|t| ArgumentType::to_ffi_type(&t)) + .map(|t| ArgumentType::to_ffi_type(t, vm).unwrap()) .collect::>(); let return_type = zelf.res_type.read(); let ffi_return_type = return_type .as_ref() - .map(|t| ReturnType::to_ffi_type(t)) + .map(|t| ReturnType::to_ffi_type(&t.clone().downcast::().unwrap())) + .ok_or_else(|| vm.new_type_error("restype improperly set".to_string()))? .unwrap_or_else(|| Type::i32()); let cif = Cif::new(ffi_arg_types, ffi_return_type); // Call the function let ffi_args = args + .args .into_iter() - .map(|arg| { + .enumerate() + .map(|(n, arg)| { let arg_type = arg_types - .get(0) - .ok_or_else(|| vm.new_type_error("argtypes not set".to_string()))?; + .as_ref() + .ok_or_else(|| vm.new_type_error("argtypes not set".to_string()))? + .get(n) + .ok_or_else(|| vm.new_type_error("argument amount mismatch".to_string()))?; arg_type.convert_object(arg, vm) }) .collect::, _>>()?; @@ -198,14 +203,22 @@ impl Callable for PyCFuncPtr { let code_ptr = pointer .as_ref() .ok_or_else(|| vm.new_type_error("Function pointer not set".to_string()))?; - let output: c_void = unsafe { cif.call(*code_ptr, &args) }; + let mut output: c_void = unsafe { cif.call(*code_ptr, &ffi_args) }; let return_type = return_type - .map(|f| f.from_ffi_type(output)?) - .unwrap_or_else(|| vm.ctx.new_int(output as i32)); + .as_ref() + .map(|f| { + f.clone() + .downcast::() + .unwrap() + .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) } else { - Ok(vm.get_none()) + Ok(vm.ctx.none()) } } } @@ -239,7 +252,7 @@ impl PyCFuncPtr { fn set_restype(&self, restype: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { // has to be type, callable, or none // TODO: Callable support - if vm.is_none(&restype) || restype.isinstance::(vm) { + if vm.is_none(&restype) || restype.downcast_ref::().is_some() { *self.res_type.write() = Some(restype); Ok(()) } else { @@ -248,32 +261,34 @@ impl PyCFuncPtr { } #[pygetset(name = "argtypes")] - fn argtypes(&self) -> Vec { - self.arg_types.read().clone() + fn argtypes(&self, vm: &VirtualMachine) -> PyTupleRef { + PyTuple::new_ref( + self.arg_types + .read() + .clone() + .unwrap_or_default() + .into_iter() + .map(|t| t.to_pyobject(vm)) + .collect(), + &vm.ctx + ) } #[pygetset(name = "argtypes", setter)] - fn set_argtypes( - &self, - argtypes: Either, - vm: &VirtualMachine, - ) -> PyResult<()> { - match argtypes { - Either::A(_) => { - *self.arg_types.write() = None; - Ok(()) - } - Either::B(tuple) => { - *self.arg_types.write() = Some( - tuple - .iter() - .map(|obj| { - obj.downcast_ref::().unwrap() - }) - .collect::>() - ); - Ok(()) - } + fn set_argtypes(&self, argtypes: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + let none = vm.is_none(&argtypes); + if none { + *self.arg_types.write() = None; + Ok(()) + } else { + let tuple = argtypes.downcast::().unwrap(); + *self.arg_types.write() = Some( + tuple + .iter() + .map(|obj| obj.clone().downcast::().unwrap()) + .collect::>(), + ); + Ok(()) } } } From d98644ed142388769347a8e0c11dfca6f99cda55 Mon Sep 17 00:00:00 2001 From: Ashwin Naren Date: Sun, 27 Apr 2025 11:56:39 -0700 Subject: [PATCH 07/12] fix warnings, pass test --- vm/src/stdlib/ctypes.rs | 3 ++- vm/src/stdlib/ctypes/array.rs | 1 + vm/src/stdlib/ctypes/field.rs | 7 +++++-- vm/src/stdlib/ctypes/pointer.rs | 2 +- vm/src/stdlib/ctypes/util.rs | 30 +++++++++++++++--------------- 5 files changed, 24 insertions(+), 19 deletions(-) diff --git a/vm/src/stdlib/ctypes.rs b/vm/src/stdlib/ctypes.rs index c1561332e1..ee1314a0b4 100644 --- a/vm/src/stdlib/ctypes.rs +++ b/vm/src/stdlib/ctypes.rs @@ -20,13 +20,14 @@ pub fn extend_module_nodes(vm: &VirtualMachine, module: &Py) { let ctx = &vm.ctx; PyCSimpleType::make_class(ctx); array::PyCArrayType::make_class(ctx); + field::PyCFieldType::make_class(ctx); + pointer::PyCPointerType::make_class(ctx); extend_module!(vm, module, { "_CData" => PyCData::make_class(ctx), "_SimpleCData" => PyCSimple::make_class(ctx), "Array" => array::PyCArray::make_class(ctx), "CField" => field::PyCField::make_class(ctx), "CFuncPtr" => function::PyCFuncPtr::make_class(ctx), - "PyCPointerType" => pointer::PyCPointerType::make_class(ctx), "_Pointer" => pointer::PyCPointer::make_class(ctx), "_pointer_type_cache" => ctx.new_dict(), "Structure" => structure::PyCStructure::make_class(ctx), diff --git a/vm/src/stdlib/ctypes/array.rs b/vm/src/stdlib/ctypes/array.rs index 0880c6b63b..fa25f42832 100644 --- a/vm/src/stdlib/ctypes/array.rs +++ b/vm/src/stdlib/ctypes/array.rs @@ -106,6 +106,7 @@ impl PyCArray { } impl PyCArray { + #[allow(unused)] pub fn to_arg(&self, _vm: &VirtualMachine) -> PyResult { let value = self.value.read(); let py_bytes = value.payload::().unwrap(); diff --git a/vm/src/stdlib/ctypes/field.rs b/vm/src/stdlib/ctypes/field.rs index 65e23ce653..f9c1b48472 100644 --- a/vm/src/stdlib/ctypes/field.rs +++ b/vm/src/stdlib/ctypes/field.rs @@ -24,6 +24,7 @@ impl PyCFieldType {} pub struct PyCField { byte_offset: usize, byte_size: usize, + #[allow(unused)] index: usize, proto: PyTypeRef, anonymous: bool, @@ -122,10 +123,12 @@ impl PyCField { } } -pub(crate) fn low_bit(offset: usize) -> usize { +#[inline(always)] +pub const fn low_bit(offset: usize) -> usize { offset & 0xFFFF } -pub(crate) fn high_bit(offset: usize) -> usize { +#[inline(always)] +pub const fn high_bit(offset: usize) -> usize { offset >> 16 } diff --git a/vm/src/stdlib/ctypes/pointer.rs b/vm/src/stdlib/ctypes/pointer.rs index eb4a92f625..00bae38cb5 100644 --- a/vm/src/stdlib/ctypes/pointer.rs +++ b/vm/src/stdlib/ctypes/pointer.rs @@ -7,7 +7,7 @@ use crate::{PyObjectRef, PyResult}; #[pyclass(name = "PyCPointerType", base = "PyType", module = "_ctypes")] #[derive(PyPayload, Debug)] pub struct PyCPointerType { - pub(super) inner: PyCPointer, + pub inner: PyCPointer, } #[pyclass] diff --git a/vm/src/stdlib/ctypes/util.rs b/vm/src/stdlib/ctypes/util.rs index 4dcf06b9c8..df5d318668 100644 --- a/vm/src/stdlib/ctypes/util.rs +++ b/vm/src/stdlib/ctypes/util.rs @@ -3,22 +3,22 @@ use crate::PyObjectRef; #[pyclass(name, module = "_ctypes")] #[derive(Debug, PyPayload)] pub struct StgInfo { - initialized: i32, - size: usize, // number of bytes - align: usize, // alignment requirements - length: usize, // number of fields + pub initialized: i32, + pub size: usize, // number of bytes + pub align: usize, // alignment requirements + pub length: usize, // number of fields // ffi_type_pointer: ffi::ffi_type, - proto: PyObjectRef, // Only for Pointer/ArrayObject - setfunc: Option, // Only for simple objects - getfunc: Option, // Only for simple objects - paramfunc: Option, + pub proto: PyObjectRef, // Only for Pointer/ArrayObject + pub setfunc: Option, // Only for simple objects + pub getfunc: Option, // Only for simple objects + pub paramfunc: Option, /* Following fields only used by PyCFuncPtrType_Type instances */ - argtypes: Option, // tuple of CDataObjects - converters: Option, // tuple([t.from_param for t in argtypes]) - restype: Option, // CDataObject or NULL - checker: Option, - module: Option, - flags: i32, // calling convention and such - dict_final: u8, + pub argtypes: Option, // tuple of CDataObjects + pub converters: Option, // tuple([t.from_param for t in argtypes]) + pub restype: Option, // CDataObject or NULL + pub checker: Option, + pub module: Option, + pub flags: i32, // calling convention and such + pub dict_final: u8, } From 3d4892351e19c0b7b6549fd2c3dff4500a8ea9db Mon Sep 17 00:00:00 2001 From: Ashwin Naren Date: Sun, 27 Apr 2025 12:00:26 -0700 Subject: [PATCH 08/12] fix panic on improper library load --- vm/src/stdlib/ctypes.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vm/src/stdlib/ctypes.rs b/vm/src/stdlib/ctypes.rs index ee1314a0b4..7d1e28ca84 100644 --- a/vm/src/stdlib/ctypes.rs +++ b/vm/src/stdlib/ctypes.rs @@ -214,7 +214,9 @@ pub(crate) mod _ctypes { // TODO: load_flags let cache = library::libcache(); let mut cache_write = cache.write(); - let (id, _) = cache_write.get_or_insert_lib(&name, vm).unwrap(); + let (id, _) = cache_write.get_or_insert_lib(&name, vm).map_err(|e| { + vm.new_os_error(e.to_string()) + })?; Ok(id) } From 40535ca699a889a7e7671805037a73d5e14c7fd2 Mon Sep 17 00:00:00 2001 From: Ashwin Naren Date: Sun, 27 Apr 2025 12:32:15 -0700 Subject: [PATCH 09/12] test on macos --- extra_tests/snippets/stdlib_ctypes.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/extra_tests/snippets/stdlib_ctypes.py b/extra_tests/snippets/stdlib_ctypes.py index 95ee9900fb..f39767512f 100644 --- a/extra_tests/snippets/stdlib_ctypes.py +++ b/extra_tests/snippets/stdlib_ctypes.py @@ -37,7 +37,6 @@ def create_string_buffer(init, size=None): size = len(init)+1 _sys.audit("ctypes.create_string_buffer", init, size) buftype = c_char.__mul__(size) - print(type(c_char.__mul__(size))) # buftype = c_char * size buf = buftype() buf.value = init @@ -269,8 +268,14 @@ def LoadLibrary(self, name): test_byte_array = create_string_buffer(b"Hello, World!\n") assert test_byte_array._length_ == 15 -if _os.name == "posix" or _sys.platform == "darwin": - pass +if _os.name == "posix": + if _sys.platform == "darwin": + libc = cdll.LoadLibrary("libc.dylib") + libc.rand() + i = c_int(1) + print("start srand") + print(libc.srand(i)) + print(test_byte_array) else: import os From 6f0cffe2919dfa52b1b607898221876932b9e2b2 Mon Sep 17 00:00:00 2001 From: Ashwin Naren Date: Sun, 27 Apr 2025 12:32:44 -0700 Subject: [PATCH 10/12] formatting --- vm/src/stdlib/ctypes.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vm/src/stdlib/ctypes.rs b/vm/src/stdlib/ctypes.rs index 7d1e28ca84..752555f4b2 100644 --- a/vm/src/stdlib/ctypes.rs +++ b/vm/src/stdlib/ctypes.rs @@ -214,9 +214,9 @@ pub(crate) mod _ctypes { // TODO: load_flags let cache = library::libcache(); let mut cache_write = cache.write(); - let (id, _) = cache_write.get_or_insert_lib(&name, vm).map_err(|e| { - vm.new_os_error(e.to_string()) - })?; + let (id, _) = cache_write + .get_or_insert_lib(&name, vm) + .map_err(|e| vm.new_os_error(e.to_string()))?; Ok(id) } From 7e94dc7fa5da765ea9ee5aab1378ab37eac56e4e Mon Sep 17 00:00:00 2001 From: Ashwin Naren Date: Sun, 27 Apr 2025 12:55:38 -0700 Subject: [PATCH 11/12] tmp --- vm/src/stdlib/ctypes/function.rs | 48 +++++++++++++++++++++++++------- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/vm/src/stdlib/ctypes/function.rs b/vm/src/stdlib/ctypes/function.rs index e199770c64..250effdd2f 100644 --- a/vm/src/stdlib/ctypes/function.rs +++ b/vm/src/stdlib/ctypes/function.rs @@ -27,6 +27,7 @@ pub trait ArgumentType { impl ArgumentType for PyTypeRef { fn to_ffi_type(&self, vm: &VirtualMachine) -> PyResult { + dbg!(&self); let typ = self .get_class_attr(vm.ctx.intern_str("_type_")) .ok_or(vm.new_type_error("Unsupported argument type".to_string()))?; @@ -99,6 +100,7 @@ impl ReturnType for PyNone { #[pyclass(module = "_ctypes", name = "CFuncPtr", base = "PyCData")] #[derive(PyPayload)] pub struct PyCFuncPtr { + pub name: PyRwLock>, pub ptr: PyRwLock>, pub needs_free: AtomicCell, pub arg_types: PyRwLock>>, @@ -130,7 +132,15 @@ impl Constructor for PyCFuncPtr { .nth(1) .ok_or(vm.new_type_error("Expected a tuple with at least 2 elements".to_string()))? .clone(); - let handle = handler.try_int(vm)?.as_bigint().clone(); + let handle = handler.try_int(vm); + let handle = match handle { + Ok(handle) => handle.as_bigint().clone(), + Err(_) => handler + .get_attr("_handle", vm)? + .try_int(vm)? + .as_bigint() + .clone(), + }; let library_cache = crate::stdlib::ctypes::library::libcache().read(); let library = library_cache .get_lib( @@ -158,6 +168,7 @@ impl Constructor for PyCFuncPtr { arg_types: PyRwLock::new(None), _flags_: AtomicCell::new(0), res_type: PyRwLock::new(None), + name: PyRwLock::new(Some(name)), handler, } .to_pyobject(vm)) @@ -170,18 +181,25 @@ impl Callable for PyCFuncPtr { // This is completely seperate from the C python implementation // Cif init - let arg_types = zelf.arg_types.read(); + let arg_types: Vec<_> = match zelf.arg_types.read().clone() { + Some(tys) => tys, + None => args + .args + .clone() + .into_iter() + .map(|a| a.class().as_object().to_pyobject(vm).downcast().unwrap()) + .collect() + }; let ffi_arg_types = arg_types - .as_ref() - .ok_or_else(|| vm.new_type_error("argtypes not set".to_string()))? + .clone() .iter() - .map(|t| ArgumentType::to_ffi_type(t, vm).unwrap()) - .collect::>(); + .map(|t| ArgumentType::to_ffi_type(t, vm)) + .collect::>>()?; let return_type = zelf.res_type.read(); let ffi_return_type = return_type .as_ref() .map(|t| ReturnType::to_ffi_type(&t.clone().downcast::().unwrap())) - .ok_or_else(|| vm.new_type_error("restype improperly set".to_string()))? + .flatten() .unwrap_or_else(|| Type::i32()); let cif = Cif::new(ffi_arg_types, ffi_return_type); @@ -192,8 +210,6 @@ impl Callable for PyCFuncPtr { .enumerate() .map(|(n, arg)| { let arg_type = arg_types - .as_ref() - .ok_or_else(|| vm.new_type_error("argtypes not set".to_string()))? .get(n) .ok_or_else(|| vm.new_type_error("argument amount mismatch".to_string()))?; arg_type.convert_object(arg, vm) @@ -270,7 +286,7 @@ impl PyCFuncPtr { .into_iter() .map(|t| t.to_pyobject(vm)) .collect(), - &vm.ctx + &vm.ctx, ) } @@ -291,4 +307,16 @@ impl PyCFuncPtr { Ok(()) } } + + #[pygetset(magic)] + fn name(&self) -> Option { + self.name.read().clone() + } + + #[pygetset(magic, setter)] + fn set_name(&self, name: String) -> PyResult<()> { + *self.name.write() = Some(name); + // TODO: update handle and stuff + Ok(()) + } } From ebd6c7e464dbe82a96f3e5fc11ae18c41684b39f Mon Sep 17 00:00:00 2001 From: Ashwin Naren Date: Sat, 10 May 2025 10:53:54 -0700 Subject: [PATCH 12/12] minor updates --- vm/src/stdlib/ctypes/base.rs | 20 ++++++++++---------- vm/src/stdlib/ctypes/function.rs | 1 - 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/vm/src/stdlib/ctypes/base.rs b/vm/src/stdlib/ctypes/base.rs index 6cc19be3df..42315f692b 100644 --- a/vm/src/stdlib/ctypes/base.rs +++ b/vm/src/stdlib/ctypes/base.rs @@ -278,24 +278,24 @@ impl PyCSimple { let value = unsafe { (*self.value.as_ptr()).clone() }; if let Ok(i) = value.try_int(vm) { let i = i.as_bigint(); - if std::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::u8().as_raw_ptr()) { - return i.to_u8().map(|r: u8| libffi::middle::Arg::new(&r)); + return if std::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::u8().as_raw_ptr()) { + i.to_u8().map(|r: u8| libffi::middle::Arg::new(&r)) } else if std::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::i8().as_raw_ptr()) { - return i.to_i8().map(|r: i8| libffi::middle::Arg::new(&r)); + i.to_i8().map(|r: i8| libffi::middle::Arg::new(&r)) } else if std::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::u16().as_raw_ptr()) { - return i.to_u16().map(|r: u16| libffi::middle::Arg::new(&r)); + i.to_u16().map(|r: u16| libffi::middle::Arg::new(&r)) } else if std::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::i16().as_raw_ptr()) { - return i.to_i16().map(|r: i16| libffi::middle::Arg::new(&r)); + i.to_i16().map(|r: i16| libffi::middle::Arg::new(&r)) } else if std::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::u32().as_raw_ptr()) { - return i.to_u32().map(|r: u32| libffi::middle::Arg::new(&r)); + i.to_u32().map(|r: u32| libffi::middle::Arg::new(&r)) } else if std::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::i32().as_raw_ptr()) { - return i.to_i32().map(|r: i32| libffi::middle::Arg::new(&r)); + i.to_i32().map(|r: i32| libffi::middle::Arg::new(&r)) } else if std::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::u64().as_raw_ptr()) { - return i.to_u64().map(|r: u64| libffi::middle::Arg::new(&r)); + i.to_u64().map(|r: u64| libffi::middle::Arg::new(&r)) } else if std::ptr::eq(ty.as_raw_ptr(), libffi::middle::Type::i64().as_raw_ptr()) { - return i.to_i64().map(|r: i64| libffi::middle::Arg::new(&r)); + i.to_i64().map(|r: i64| libffi::middle::Arg::new(&r)) } else { - return None; + None } } if let Ok(_f) = value.try_float(vm) { diff --git a/vm/src/stdlib/ctypes/function.rs b/vm/src/stdlib/ctypes/function.rs index 250effdd2f..3332a834c7 100644 --- a/vm/src/stdlib/ctypes/function.rs +++ b/vm/src/stdlib/ctypes/function.rs @@ -27,7 +27,6 @@ pub trait ArgumentType { impl ArgumentType for PyTypeRef { fn to_ffi_type(&self, vm: &VirtualMachine) -> PyResult { - dbg!(&self); let typ = self .get_class_attr(vm.ctx.intern_str("_type_")) .ok_or(vm.new_type_error("Unsupported argument type".to_string()))?;