Initial _ctypes implementation (#5519)

* initial _ctypes implementation with _CData, get_errno, and set_errno

Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
This commit is contained in:
Ashwin Naren
2025-02-14 23:03:58 -08:00
committed by GitHub
parent 19050afc3f
commit fde87a340c
6 changed files with 382 additions and 1 deletions

1
Cargo.lock generated
View File

@@ -2258,6 +2258,7 @@ dependencies = [
"cfg-if",
"chrono",
"crossbeam-utils",
"errno",
"exitcode",
"flame",
"flamer",

View File

@@ -0,0 +1,105 @@
from _ctypes import sizeof
from _ctypes import _SimpleCData
from struct import calcsize as _calcsize
def _check_size(typ, typecode=None):
# Check if sizeof(ctypes_type) against struct.calcsize. This
# should protect somewhat against a misconfigured libffi.
from struct import calcsize
if typecode is None:
# Most _type_ codes are the same as used in struct
typecode = typ._type_
actual, required = sizeof(typ), calcsize(typecode)
if actual != required:
raise SystemError("sizeof(%s) wrong: %d instead of %d" % \
(typ, actual, required))
class c_short(_SimpleCData):
_type_ = "h"
_check_size(c_short)
class c_ushort(_SimpleCData):
_type_ = "H"
_check_size(c_ushort)
class c_long(_SimpleCData):
_type_ = "l"
_check_size(c_long)
class c_ulong(_SimpleCData):
_type_ = "L"
_check_size(c_ulong)
if _calcsize("i") == _calcsize("l"):
# if int and long have the same size, make c_int an alias for c_long
c_int = c_long
c_uint = c_ulong
else:
class c_int(_SimpleCData):
_type_ = "i"
_check_size(c_int)
class c_uint(_SimpleCData):
_type_ = "I"
_check_size(c_uint)
class c_float(_SimpleCData):
_type_ = "f"
_check_size(c_float)
class c_double(_SimpleCData):
_type_ = "d"
_check_size(c_double)
class c_longdouble(_SimpleCData):
_type_ = "g"
if sizeof(c_longdouble) == sizeof(c_double):
c_longdouble = c_double
if _calcsize("l") == _calcsize("q"):
# if long and long long have the same size, make c_longlong an alias for c_long
c_longlong = c_long
c_ulonglong = c_ulong
else:
class c_longlong(_SimpleCData):
_type_ = "q"
_check_size(c_longlong)
class c_ulonglong(_SimpleCData):
_type_ = "Q"
## def from_param(cls, val):
## return ('d', float(val), val)
## from_param = classmethod(from_param)
_check_size(c_ulonglong)
class c_ubyte(_SimpleCData):
_type_ = "B"
c_ubyte.__ctype_le__ = c_ubyte.__ctype_be__ = c_ubyte
# backward compatibility:
##c_uchar = c_ubyte
_check_size(c_ubyte)
class c_byte(_SimpleCData):
_type_ = "b"
c_byte.__ctype_le__ = c_byte.__ctype_be__ = c_byte
_check_size(c_byte)
class c_char(_SimpleCData):
_type_ = "c"
c_char.__ctype_le__ = c_char.__ctype_be__ = c_char
_check_size(c_char)
class c_char_p(_SimpleCData):
_type_ = "z"
def __repr__(self):
return "%s(%s)" % (self.__class__.__name__, c_void_p.from_buffer(self).value)
_check_size(c_char_p, "P")
class c_void_p(_SimpleCData):
_type_ = "P"
c_voidp = c_void_p # backwards compatibility (to a bug)
_check_size(c_void_p)
class c_bool(_SimpleCData):
_type_ = "?"
_check_size(c_bool)

View File

@@ -99,6 +99,8 @@ uname = "0.1.1"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
rustyline = { workspace = true }
which = "6"
errno = "0.3"
widestring = { workspace = true }
[target.'cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))'.dependencies]
num_cpus = "1.13.1"
@@ -106,7 +108,6 @@ num_cpus = "1.13.1"
[target.'cfg(windows)'.dependencies]
junction = { workspace = true }
schannel = { workspace = true }
widestring = { workspace = true }
winreg = "0.52"
[target.'cfg(windows)'.dependencies.windows]

107
vm/src/stdlib/ctypes.rs Normal file
View File

@@ -0,0 +1,107 @@
pub(crate) mod base;
use crate::builtins::PyModule;
use crate::{PyRef, VirtualMachine};
pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef<PyModule> {
let module = _ctypes::make_module(vm);
base::extend_module_nodes(vm, &module);
module
}
#[pymodule]
pub(crate) mod _ctypes {
use super::base::PyCSimple;
use crate::builtins::PyTypeRef;
use crate::class::StaticType;
use crate::function::Either;
use crate::{AsObject, PyObjectRef, PyResult, TryFromObject, VirtualMachine};
use crossbeam_utils::atomic::AtomicCell;
use std::ffi::{
c_double, c_float, c_int, c_long, c_longlong, c_schar, c_short, c_uchar, c_uint, c_ulong,
c_ulonglong,
};
use std::mem;
use widestring::WideChar;
pub fn get_size(ty: &str) -> usize {
match ty {
"u" => mem::size_of::<WideChar>(),
"c" | "b" => mem::size_of::<c_schar>(),
"h" => mem::size_of::<c_short>(),
"H" => mem::size_of::<c_short>(),
"i" => mem::size_of::<c_int>(),
"I" => mem::size_of::<c_uint>(),
"l" => mem::size_of::<c_long>(),
"q" => mem::size_of::<c_longlong>(),
"L" => mem::size_of::<c_ulong>(),
"Q" => mem::size_of::<c_ulonglong>(),
"f" => mem::size_of::<c_float>(),
"d" | "g" => mem::size_of::<c_double>(),
"?" | "B" => mem::size_of::<c_uchar>(),
"P" | "z" | "Z" => mem::size_of::<usize>(),
_ => unreachable!(),
}
}
const SIMPLE_TYPE_CHARS: &str = "cbBhHiIlLdfguzZPqQ?";
pub fn new_simple_type(
cls: Either<&PyObjectRef, &PyTypeRef>,
vm: &VirtualMachine,
) -> PyResult<PyCSimple> {
let cls = match cls {
Either::A(obj) => obj,
Either::B(typ) => typ.as_object(),
};
if let Ok(_type_) = cls.get_attr("_type_", vm) {
if _type_.is_instance((&vm.ctx.types.str_type).as_ref(), vm)? {
let tp_str = _type_.str(vm)?.to_string();
if tp_str.len() != 1 {
Err(vm.new_value_error(
format!("class must define a '_type_' attribute which must be a string of length 1, str: {tp_str}"),
))
} 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 {
Ok(PyCSimple {
_type_: tp_str,
_value: AtomicCell::new(vm.ctx.none()),
})
}
} else {
Err(vm.new_type_error("class must define a '_type_' string attribute".to_string()))
}
} else {
Err(vm.new_attribute_error("class must define a '_type_' attribute".to_string()))
}
}
#[pyfunction(name = "sizeof")]
pub fn size_of(tp: Either<PyTypeRef, PyObjectRef>, vm: &VirtualMachine) -> PyResult<usize> {
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()))
}
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)?)
}
_ => Err(vm.new_type_error("this type has no size".to_string())),
}
}
#[pyfunction]
fn get_errno() -> i32 {
errno::errno().0
}
#[pyfunction]
fn set_errno(value: i32) {
errno::set_errno(errno::Errno(value));
}
}

View File

@@ -0,0 +1,161 @@
use crate::builtins::{PyBytes, PyFloat, PyInt, PyModule, PyNone, PyStr};
use crate::class::PyClassImpl;
use crate::{Py, PyObjectRef, PyResult, TryFromObject, VirtualMachine};
use crossbeam_utils::atomic::AtomicCell;
use num_traits::ToPrimitive;
use rustpython_common::lock::PyRwLock;
use std::fmt::Debug;
#[allow(dead_code)]
fn set_primitive(_type_: &str, value: &PyObjectRef, vm: &VirtualMachine) -> PyResult {
match _type_ {
"c" => {
if value
.clone()
.downcast_exact::<PyBytes>(vm)
.is_ok_and(|v| v.len() == 1)
|| value
.clone()
.downcast_exact::<PyBytes>(vm)
.is_ok_and(|v| v.len() == 1)
|| value
.clone()
.downcast_exact::<PyInt>(vm)
.map_or(Ok(false), |v| {
let n = v.as_bigint().to_i64();
if let Some(n) = n {
Ok((0..=255).contains(&n))
} else {
Ok(false)
}
})?
{
Ok(value.clone())
} else {
Err(vm.new_type_error(
"one character bytes, bytearray or integer expected".to_string(),
))
}
}
"u" => {
if let Ok(b) = value.str(vm).map(|v| v.to_string().chars().count() == 1) {
if b {
Ok(value.clone())
} else {
Err(vm.new_type_error("one character unicode string expected".to_string()))
}
} else {
Err(vm.new_type_error(format!(
"unicode string expected instead of {} instance",
value.class().name()
)))
}
}
"b" | "h" | "H" | "i" | "I" | "l" | "q" | "L" | "Q" => {
if value.clone().downcast_exact::<PyInt>(vm).is_ok() {
Ok(value.clone())
} else {
Err(vm.new_type_error(format!(
"an integer is required (got type {})",
value.class().name()
)))
}
}
"f" | "d" | "g" => {
if value.clone().downcast_exact::<PyFloat>(vm).is_ok() {
Ok(value.clone())
} else {
Err(vm.new_type_error(format!("must be real number, not {}", value.class().name())))
}
}
"?" => Ok(PyObjectRef::from(
vm.ctx.new_bool(value.clone().try_to_bool(vm)?),
)),
"B" => {
if value.clone().downcast_exact::<PyInt>(vm).is_ok() {
Ok(vm.new_pyobj(u8::try_from_object(vm, value.clone())?))
} else {
Err(vm.new_type_error(format!("int expected instead of {}", value.class().name())))
}
}
"z" => {
if value.clone().downcast_exact::<PyInt>(vm).is_ok()
|| value.clone().downcast_exact::<PyBytes>(vm).is_ok()
{
Ok(value.clone())
} else {
Err(vm.new_type_error(format!(
"bytes or integer address expected instead of {} instance",
value.class().name()
)))
}
}
"Z" => {
if value.clone().downcast_exact::<PyStr>(vm).is_ok() {
Ok(value.clone())
} else {
Err(vm.new_type_error(format!(
"unicode string or integer address expected instead of {} instance",
value.class().name()
)))
}
}
_ => {
// "P"
if value.clone().downcast_exact::<PyInt>(vm).is_ok()
|| value.clone().downcast_exact::<PyNone>(vm).is_ok()
{
Ok(value.clone())
} else {
Err(vm.new_type_error("cannot be converted to pointer".to_string()))
}
}
}
}
pub struct RawBuffer {
#[allow(dead_code)]
pub inner: Box<[u8]>,
#[allow(dead_code)]
pub size: usize,
}
#[pyclass(name = "_CData", module = "_ctypes")]
pub struct PyCData {
_objects: AtomicCell<Vec<PyObjectRef>>,
_buffer: PyRwLock<RawBuffer>,
}
#[pyclass]
impl PyCData {}
#[pyclass(
name = "_SimpleCData",
base = "PyCData",
module = "_ctypes"
// TODO: metaclass
)]
#[derive(PyPayload)]
pub struct PyCSimple {
pub _type_: String,
pub _value: AtomicCell<PyObjectRef>,
}
impl Debug for PyCSimple {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PyCSimple")
.field("_type_", &self._type_)
.finish()
}
}
#[pyclass(flags(BASETYPE))]
impl PyCSimple {}
pub fn extend_module_nodes(vm: &VirtualMachine, module: &Py<PyModule>) {
let ctx = &vm.ctx;
extend_module!(vm, module, {
"_CData" => PyCData::make_class(ctx),
"_SimpleCData" => PyCSimple::make_class(ctx),
})
}

View File

@@ -37,6 +37,8 @@ pub mod posix;
#[path = "posix_compat.rs"]
pub mod posix;
#[cfg(any(target_family = "unix", target_family = "windows"))]
mod ctypes;
#[cfg(windows)]
pub(crate) mod msvcrt;
#[cfg(all(unix, not(any(target_os = "android", target_os = "redox"))))]
@@ -124,5 +126,9 @@ pub fn get_module_inits() -> StdlibMap {
"_winapi" => winapi::make_module,
"winreg" => winreg::make_module,
}
#[cfg(any(target_family = "unix", target_family = "windows"))]
{
"_ctypes" => ctypes::make_module,
}
}
}