From 51c4a324c037fb2e31640202243fd1c8b33800d5 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 3 Jul 2024 12:08:11 +0300 Subject: [PATCH] gh-61103: Support float and long double complex types in ctypes module (#121248) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This amends 6988ff02a5: memory allocation for stginfo->ffi_type_pointer.elements in PyCSimpleType_init() should be more generic (perhaps someday fmt->pffi_type->elements will be not a two-elements array). It should finally resolve #61103. Co-authored-by: Victor Stinner Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Doc/library/ctypes.rst | 20 ++++++++ Lib/ctypes/__init__.py | 4 ++ Lib/test/test_ctypes/test_libc.py | 14 ++++++ Lib/test/test_ctypes/test_numbers.py | 10 ++-- ...4-06-23-07-23-08.gh-issue-61103.ca_U_l.rst | 8 ++-- Modules/_complex.h | 20 ++++++++ Modules/_ctypes/_ctypes.c | 7 +-- Modules/_ctypes/_ctypes_test.c | 10 ++++ Modules/_ctypes/callproc.c | 2 + Modules/_ctypes/cfield.c | 48 +++++++++++++++++++ Modules/_ctypes/ctypes.h | 2 + 11 files changed, 135 insertions(+), 10 deletions(-) diff --git a/Doc/library/ctypes.rst b/Doc/library/ctypes.rst index a56e0eef5d1..e3d74d7dc0d 100644 --- a/Doc/library/ctypes.rst +++ b/Doc/library/ctypes.rst @@ -272,8 +272,12 @@ complex types are available: +----------------------------------+---------------------------------+-----------------+ | ctypes type | C type | Python type | +==================================+=================================+=================+ +| :class:`c_float_complex` | :c:expr:`float complex` | complex | ++----------------------------------+---------------------------------+-----------------+ | :class:`c_double_complex` | :c:expr:`double complex` | complex | +----------------------------------+---------------------------------+-----------------+ +| :class:`c_longdouble_complex` | :c:expr:`long double complex` | complex | ++----------------------------------+---------------------------------+-----------------+ All these types can be created by calling them with an optional initializer of @@ -2302,6 +2306,22 @@ These are the fundamental ctypes data types: .. versionadded:: 3.14 +.. class:: c_float_complex + + Represents the C :c:expr:`float complex` datatype, if available. The + constructor accepts an optional :class:`complex` initializer. + + .. versionadded:: 3.14 + + +.. class:: c_longdouble_complex + + Represents the C :c:expr:`long double complex` datatype, if available. The + constructor accepts an optional :class:`complex` initializer. + + .. versionadded:: 3.14 + + .. class:: c_int Represents the C :c:expr:`signed int` datatype. The constructor accepts an diff --git a/Lib/ctypes/__init__.py b/Lib/ctypes/__init__.py index d2e6a8bfc8c..721522caeea 100644 --- a/Lib/ctypes/__init__.py +++ b/Lib/ctypes/__init__.py @@ -208,6 +208,10 @@ if sizeof(c_longdouble) == sizeof(c_double): try: class c_double_complex(_SimpleCData): _type_ = "C" + class c_float_complex(_SimpleCData): + _type_ = "E" + class c_longdouble_complex(_SimpleCData): + _type_ = "F" except AttributeError: pass diff --git a/Lib/test/test_ctypes/test_libc.py b/Lib/test/test_ctypes/test_libc.py index dec0afff4b3..cab3cc9f460 100644 --- a/Lib/test/test_ctypes/test_libc.py +++ b/Lib/test/test_ctypes/test_libc.py @@ -33,6 +33,20 @@ class LibTest(unittest.TestCase): self.assertAlmostEqual(lib.my_csqrt(-1-0.01j), 0.004999937502734214-1.0000124996093955j) + lib.my_csqrtf.argtypes = ctypes.c_float_complex, + lib.my_csqrtf.restype = ctypes.c_float_complex + self.assertAlmostEqual(lib.my_csqrtf(-1+0.01j), + 0.004999937502734214+1.0000124996093955j) + self.assertAlmostEqual(lib.my_csqrtf(-1-0.01j), + 0.004999937502734214-1.0000124996093955j) + + lib.my_csqrtl.argtypes = ctypes.c_longdouble_complex, + lib.my_csqrtl.restype = ctypes.c_longdouble_complex + self.assertAlmostEqual(lib.my_csqrtl(-1+0.01j), + 0.004999937502734214+1.0000124996093955j) + self.assertAlmostEqual(lib.my_csqrtl(-1-0.01j), + 0.004999937502734214-1.0000124996093955j) + def test_qsort(self): comparefunc = CFUNCTYPE(c_int, POINTER(c_char), POINTER(c_char)) lib.my_qsort.argtypes = c_void_p, c_size_t, c_size_t, comparefunc diff --git a/Lib/test/test_ctypes/test_numbers.py b/Lib/test/test_ctypes/test_numbers.py index b3816f61a6e..1dd3f2a234b 100644 --- a/Lib/test/test_ctypes/test_numbers.py +++ b/Lib/test/test_ctypes/test_numbers.py @@ -146,7 +146,8 @@ class NumberTestCase(unittest.TestCase): @unittest.skipUnless(hasattr(ctypes, "c_double_complex"), "requires C11 complex type") def test_complex(self): - for t in [ctypes.c_double_complex]: + for t in [ctypes.c_double_complex, ctypes.c_float_complex, + ctypes.c_longdouble_complex]: self.assertEqual(t(1).value, 1+0j) self.assertEqual(t(1.0).value, 1+0j) self.assertEqual(t(1+0.125j).value, 1+0.125j) @@ -162,9 +163,10 @@ class NumberTestCase(unittest.TestCase): values = [complex(*_) for _ in combinations([1, -1, 0.0, -0.0, 2, -3, INF, -INF, NAN], 2)] for z in values: - with self.subTest(z=z): - z2 = ctypes.c_double_complex(z).value - self.assertComplexesAreIdentical(z, z2) + for t in [ctypes.c_double_complex, ctypes.c_float_complex, + ctypes.c_longdouble_complex]: + with self.subTest(z=z, type=t): + self.assertComplexesAreIdentical(z, t(z).value) def test_integers(self): f = FloatLike() diff --git a/Misc/NEWS.d/next/Library/2024-06-23-07-23-08.gh-issue-61103.ca_U_l.rst b/Misc/NEWS.d/next/Library/2024-06-23-07-23-08.gh-issue-61103.ca_U_l.rst index 7b11d8c303c..890eb62010e 100644 --- a/Misc/NEWS.d/next/Library/2024-06-23-07-23-08.gh-issue-61103.ca_U_l.rst +++ b/Misc/NEWS.d/next/Library/2024-06-23-07-23-08.gh-issue-61103.ca_U_l.rst @@ -1,3 +1,5 @@ -Support :c:expr:`double complex` C type in :mod:`ctypes` via -:class:`~ctypes.c_double_complex` if compiler has C11 complex -arithmetic. Patch by Sergey B Kirpichev. +Support :c:expr:`float complex`, :c:expr:`double complex` and +:c:expr:`long double complex` C types in :mod:`ctypes` as +:class:`~ctypes.c_float_complex`, :class:`~ctypes.c_double_complex` and +:class:`~ctypes.c_longdouble_complex` if the compiler has C11 complex arithmetic. +Patch by Sergey B Kirpichev. diff --git a/Modules/_complex.h b/Modules/_complex.h index 1c1d1c8cae5..28d4a32794b 100644 --- a/Modules/_complex.h +++ b/Modules/_complex.h @@ -21,6 +21,8 @@ #if !defined(CMPLX) # if defined(__clang__) && __has_builtin(__builtin_complex) # define CMPLX(x, y) __builtin_complex ((double) (x), (double) (y)) +# define CMPLXF(x, y) __builtin_complex ((float) (x), (float) (y)) +# define CMPLXL(x, y) __builtin_complex ((long double) (x), (long double) (y)) # else static inline double complex CMPLX(double real, double imag) @@ -30,5 +32,23 @@ CMPLX(double real, double imag) ((double *)(&z))[1] = imag; return z; } + +static inline float complex +CMPLXF(float real, float imag) +{ + float complex z; + ((float *)(&z))[0] = real; + ((float *)(&z))[1] = imag; + return z; +} + +static inline long double complex +CMPLXL(long double real, long double imag) +{ + long double complex z; + ((long double *)(&z))[0] = real; + ((long double *)(&z))[1] = imag; + return z; +} # endif #endif diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c index 3647361b13a..db58f33511c 100644 --- a/Modules/_ctypes/_ctypes.c +++ b/Modules/_ctypes/_ctypes.c @@ -1751,7 +1751,7 @@ class _ctypes.c_void_p "PyObject *" "clinic_state_sub()->PyCSimpleType_Type" /*[clinic end generated code: output=da39a3ee5e6b4b0d input=dd4d9646c56f43a9]*/ #if defined(Py_HAVE_C_COMPLEX) && defined(FFI_TARGET_HAS_COMPLEX_TYPE) -static const char SIMPLE_TYPE_CHARS[] = "cbBhHiIlLdCfuzZqQPXOv?g"; +static const char SIMPLE_TYPE_CHARS[] = "cbBhHiIlLdCEFfuzZqQPXOv?g"; #else static const char SIMPLE_TYPE_CHARS[] = "cbBhHiIlLdfuzZqQPXOv?g"; #endif @@ -2234,12 +2234,13 @@ PyCSimpleType_init(PyObject *self, PyObject *args, PyObject *kwds) stginfo->ffi_type_pointer = *fmt->pffi_type; } else { + const size_t els_size = sizeof(fmt->pffi_type->elements); stginfo->ffi_type_pointer.size = fmt->pffi_type->size; stginfo->ffi_type_pointer.alignment = fmt->pffi_type->alignment; stginfo->ffi_type_pointer.type = fmt->pffi_type->type; - stginfo->ffi_type_pointer.elements = PyMem_Malloc(2 * sizeof(ffi_type)); + stginfo->ffi_type_pointer.elements = PyMem_Malloc(els_size); memcpy(stginfo->ffi_type_pointer.elements, - fmt->pffi_type->elements, 2 * sizeof(ffi_type)); + fmt->pffi_type->elements, els_size); } stginfo->align = fmt->pffi_type->alignment; stginfo->length = 0; diff --git a/Modules/_ctypes/_ctypes_test.c b/Modules/_ctypes/_ctypes_test.c index cbc8f8b0b45..b8e613fd669 100644 --- a/Modules/_ctypes/_ctypes_test.c +++ b/Modules/_ctypes/_ctypes_test.c @@ -454,6 +454,16 @@ EXPORT(double complex) my_csqrt(double complex a) { return csqrt(a); } + +EXPORT(float complex) my_csqrtf(float complex a) +{ + return csqrtf(a); +} + +EXPORT(long double complex) my_csqrtl(long double complex a) +{ + return csqrtl(a); +} #endif EXPORT(void) my_qsort(void *base, size_t num, size_t width, int(*compare)(const void*, const void*)) diff --git a/Modules/_ctypes/callproc.c b/Modules/_ctypes/callproc.c index a83fa19af32..fd89d9c67b3 100644 --- a/Modules/_ctypes/callproc.c +++ b/Modules/_ctypes/callproc.c @@ -657,6 +657,8 @@ union result { void *p; #if defined(Py_HAVE_C_COMPLEX) && defined(FFI_TARGET_HAS_COMPLEX_TYPE) double complex C; + float complex E; + long double complex F; #endif }; diff --git a/Modules/_ctypes/cfield.c b/Modules/_ctypes/cfield.c index 40b72d83d16..2c1fb9b862e 100644 --- a/Modules/_ctypes/cfield.c +++ b/Modules/_ctypes/cfield.c @@ -1112,6 +1112,50 @@ C_get(void *ptr, Py_ssize_t size) memcpy(&x, ptr, sizeof(x)); return PyComplex_FromDoubles(creal(x), cimag(x)); } + +static PyObject * +E_set(void *ptr, PyObject *value, Py_ssize_t size) +{ + Py_complex c = PyComplex_AsCComplex(value); + + if (c.real == -1 && PyErr_Occurred()) { + return NULL; + } + float complex x = CMPLXF((float)c.real, (float)c.imag); + memcpy(ptr, &x, sizeof(x)); + _RET(value); +} + +static PyObject * +E_get(void *ptr, Py_ssize_t size) +{ + float complex x; + + memcpy(&x, ptr, sizeof(x)); + return PyComplex_FromDoubles(crealf(x), cimagf(x)); +} + +static PyObject * +F_set(void *ptr, PyObject *value, Py_ssize_t size) +{ + Py_complex c = PyComplex_AsCComplex(value); + + if (c.real == -1 && PyErr_Occurred()) { + return NULL; + } + long double complex x = CMPLXL(c.real, c.imag); + memcpy(ptr, &x, sizeof(x)); + _RET(value); +} + +static PyObject * +F_get(void *ptr, Py_ssize_t size) +{ + long double complex x; + + memcpy(&x, ptr, sizeof(x)); + return PyComplex_FromDoubles((double)creall(x), (double)cimagl(x)); +} #endif static PyObject * @@ -1621,6 +1665,8 @@ static struct fielddesc formattable[] = { { 'd', d_set, d_get, NULL, d_set_sw, d_get_sw}, #if defined(Py_HAVE_C_COMPLEX) && defined(FFI_TARGET_HAS_COMPLEX_TYPE) { 'C', C_set, C_get, NULL}, + { 'E', E_set, E_get, NULL}, + { 'F', F_set, F_get, NULL}, #endif { 'g', g_set, g_get, NULL}, { 'f', f_set, f_get, NULL, f_set_sw, f_get_sw}, @@ -1674,6 +1720,8 @@ _ctypes_init_fielddesc(void) case 'd': fd->pffi_type = &ffi_type_double; break; #if defined(Py_HAVE_C_COMPLEX) && defined(FFI_TARGET_HAS_COMPLEX_TYPE) case 'C': fd->pffi_type = &ffi_type_complex_double; break; + case 'E': fd->pffi_type = &ffi_type_complex_float; break; + case 'F': fd->pffi_type = &ffi_type_complex_longdouble; break; #endif case 'g': fd->pffi_type = &ffi_type_longdouble; break; case 'f': fd->pffi_type = &ffi_type_float; break; diff --git a/Modules/_ctypes/ctypes.h b/Modules/_ctypes/ctypes.h index 5ba5eb3851a..a794cfe86b5 100644 --- a/Modules/_ctypes/ctypes.h +++ b/Modules/_ctypes/ctypes.h @@ -401,6 +401,8 @@ struct tagPyCArgObject { void *p; #if defined(Py_HAVE_C_COMPLEX) && defined(FFI_TARGET_HAS_COMPLEX_TYPE) double complex C; + float complex E; + long double complex F; #endif } value; PyObject *obj;