From 6988ff02a5741bcd04a8f46b7dd845e849557be0 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 1 Jul 2024 11:54:33 +0300 Subject: [PATCH] gh-61103: Support double complex (_Complex) type in ctypes (#120894) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Example: ```pycon >>> import ctypes >>> ctypes.__STDC_IEC_559_COMPLEX__ 1 >>> libm = ctypes.CDLL('libm.so.6') >>> libm.clog.argtypes = [ctypes.c_double_complex] >>> libm.clog.restype = ctypes.c_double_complex >>> libm.clog(1+1j) (0.34657359027997264+0.7853981633974483j) ``` Co-authored-by: Nice Zombies Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> Co-authored-by: Victor Stinner --- Doc/library/ctypes.rst | 18 ++++ Lib/ctypes/__init__.py | 6 ++ Lib/test/test_ctypes/test_libc.py | 12 +++ Lib/test/test_ctypes/test_numbers.py | 85 ++++++++++++++++--- Makefile.pre.in | 2 +- ...4-06-23-07-23-08.gh-issue-61103.ca_U_l.rst | 3 + Modules/_complex.h | 34 ++++++++ Modules/_ctypes/_ctypes.c | 16 +++- Modules/_ctypes/_ctypes_test.c | 13 +++ Modules/_ctypes/callproc.c | 7 ++ Modules/_ctypes/cfield.c | 33 +++++++ Modules/_ctypes/ctypes.h | 9 ++ PCbuild/_ctypes_test.vcxproj | 1 + Tools/c-analyzer/c_parser/parser/_regexes.py | 11 +++ configure | 49 ++++++++++- configure.ac | 31 ++++++- pyconfig.h.in | 3 + 17 files changed, 316 insertions(+), 17 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-06-23-07-23-08.gh-issue-61103.ca_U_l.rst create mode 100644 Modules/_complex.h diff --git a/Doc/library/ctypes.rst b/Doc/library/ctypes.rst index 29b35af1c85..a56e0eef5d1 100644 --- a/Doc/library/ctypes.rst +++ b/Doc/library/ctypes.rst @@ -266,6 +266,16 @@ Fundamental data types (1) The constructor accepts any object with a truth value. +Additionally, if IEC 60559 compatible complex arithmetic (Annex G) is supported, the following +complex types are available: + ++----------------------------------+---------------------------------+-----------------+ +| ctypes type | C type | Python type | ++==================================+=================================+=================+ +| :class:`c_double_complex` | :c:expr:`double complex` | complex | ++----------------------------------+---------------------------------+-----------------+ + + All these types can be created by calling them with an optional initializer of the correct type and value:: @@ -2284,6 +2294,14 @@ These are the fundamental ctypes data types: optional float initializer. +.. class:: c_double_complex + + Represents the C :c:expr:`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 b7ee46d664a..d2e6a8bfc8c 100644 --- a/Lib/ctypes/__init__.py +++ b/Lib/ctypes/__init__.py @@ -205,6 +205,12 @@ class c_longdouble(_SimpleCData): if sizeof(c_longdouble) == sizeof(c_double): c_longdouble = c_double +try: + class c_double_complex(_SimpleCData): + _type_ = "C" +except AttributeError: + pass + 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 diff --git a/Lib/test/test_ctypes/test_libc.py b/Lib/test/test_ctypes/test_libc.py index 7716100b08f..dec0afff4b3 100644 --- a/Lib/test/test_ctypes/test_libc.py +++ b/Lib/test/test_ctypes/test_libc.py @@ -1,3 +1,4 @@ +import ctypes import math import unittest from ctypes import (CDLL, CFUNCTYPE, POINTER, create_string_buffer, sizeof, @@ -21,6 +22,17 @@ class LibTest(unittest.TestCase): self.assertEqual(lib.my_sqrt(4.0), 2.0) self.assertEqual(lib.my_sqrt(2.0), math.sqrt(2.0)) + @unittest.skipUnless(hasattr(ctypes, "c_double_complex"), + "requires C11 complex type") + def test_csqrt(self): + lib.my_csqrt.argtypes = ctypes.c_double_complex, + lib.my_csqrt.restype = ctypes.c_double_complex + self.assertEqual(lib.my_csqrt(4), 2+0j) + self.assertAlmostEqual(lib.my_csqrt(-1+0.01j), + 0.004999937502734214+1.0000124996093955j) + self.assertAlmostEqual(lib.my_csqrt(-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 29108a28ec1..b3816f61a6e 100644 --- a/Lib/test/test_ctypes/test_numbers.py +++ b/Lib/test/test_ctypes/test_numbers.py @@ -1,7 +1,10 @@ import array +import ctypes import struct import sys import unittest +from itertools import combinations +from math import copysign, isnan from operator import truth from ctypes import (byref, sizeof, alignment, c_char, c_byte, c_ubyte, c_short, c_ushort, c_int, c_uint, @@ -38,8 +41,55 @@ unsigned_ranges = valid_ranges(*unsigned_types) signed_ranges = valid_ranges(*signed_types) bool_values = [True, False, 0, 1, -1, 5000, 'test', [], [1]] +class IntLike: + def __int__(self): + return 2 + +class IndexLike: + def __index__(self): + return 2 + +class FloatLike: + def __float__(self): + return 2.0 + +class ComplexLike: + def __complex__(self): + return 1+1j + + +INF = float("inf") +NAN = float("nan") + class NumberTestCase(unittest.TestCase): + # from Lib/test/test_complex.py + def assertFloatsAreIdentical(self, x, y): + """assert that floats x and y are identical, in the sense that: + (1) both x and y are nans, or + (2) both x and y are infinities, with the same sign, or + (3) both x and y are zeros, with the same sign, or + (4) x and y are both finite and nonzero, and x == y + + """ + msg = 'floats {!r} and {!r} are not identical' + + if isnan(x) or isnan(y): + if isnan(x) and isnan(y): + return + elif x == y: + if x != 0.0: + return + # both zero; check that signs match + elif copysign(1.0, x) == copysign(1.0, y): + return + else: + msg += ': zeros have different signs' + self.fail(msg.format(x, y)) + + def assertComplexesAreIdentical(self, x, y): + self.assertFloatsAreIdentical(x.real, y.real) + self.assertFloatsAreIdentical(x.imag, y.imag) def test_default_init(self): # default values are set to zero @@ -86,9 +136,6 @@ class NumberTestCase(unittest.TestCase): def test_floats(self): # c_float and c_double can be created from # Python int and float - class FloatLike: - def __float__(self): - return 2.0 f = FloatLike() for t in float_types: self.assertEqual(t(2.0).value, 2.0) @@ -96,18 +143,32 @@ class NumberTestCase(unittest.TestCase): self.assertEqual(t(2).value, 2.0) self.assertEqual(t(f).value, 2.0) + @unittest.skipUnless(hasattr(ctypes, "c_double_complex"), + "requires C11 complex type") + def test_complex(self): + for t in [ctypes.c_double_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) + self.assertEqual(t(IndexLike()).value, 2+0j) + self.assertEqual(t(FloatLike()).value, 2+0j) + self.assertEqual(t(ComplexLike()).value, 1+1j) + + @unittest.skipUnless(hasattr(ctypes, "c_double_complex"), + "requires C11 complex type") + def test_complex_round_trip(self): + # Ensure complexes transformed exactly. The CMPLX macro should + # preserve special components (like inf/nan or signed zero). + 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) + def test_integers(self): - class FloatLike: - def __float__(self): - return 2.0 f = FloatLike() - class IntLike: - def __int__(self): - return 2 d = IntLike() - class IndexLike: - def __index__(self): - return 2 i = IndexLike() # integers cannot be constructed from floats, # but from integer-like objects diff --git a/Makefile.pre.in b/Makefile.pre.in index 1d106f30a10..e1c793ce629 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -3125,7 +3125,7 @@ MODULE_MATH_DEPS=$(srcdir)/Modules/_math.h MODULE_PYEXPAT_DEPS=@LIBEXPAT_INTERNAL@ MODULE_UNICODEDATA_DEPS=$(srcdir)/Modules/unicodedata_db.h $(srcdir)/Modules/unicodename_db.h MODULE__BLAKE2_DEPS=$(srcdir)/Modules/_blake2/impl/blake2-config.h $(srcdir)/Modules/_blake2/impl/blake2-impl.h $(srcdir)/Modules/_blake2/impl/blake2.h $(srcdir)/Modules/_blake2/impl/blake2b-load-sse2.h $(srcdir)/Modules/_blake2/impl/blake2b-load-sse41.h $(srcdir)/Modules/_blake2/impl/blake2b-ref.c $(srcdir)/Modules/_blake2/impl/blake2b-round.h $(srcdir)/Modules/_blake2/impl/blake2b.c $(srcdir)/Modules/_blake2/impl/blake2s-load-sse2.h $(srcdir)/Modules/_blake2/impl/blake2s-load-sse41.h $(srcdir)/Modules/_blake2/impl/blake2s-load-xop.h $(srcdir)/Modules/_blake2/impl/blake2s-ref.c $(srcdir)/Modules/_blake2/impl/blake2s-round.h $(srcdir)/Modules/_blake2/impl/blake2s.c $(srcdir)/Modules/_blake2/blake2module.h $(srcdir)/Modules/hashlib.h -MODULE__CTYPES_DEPS=$(srcdir)/Modules/_ctypes/ctypes.h +MODULE__CTYPES_DEPS=$(srcdir)/Modules/_ctypes/ctypes.h $(srcdir)/Modules/_complex.h MODULE__CTYPES_TEST_DEPS=$(srcdir)/Modules/_ctypes/_ctypes_test_generated.c.h MODULE__CTYPES_MALLOC_CLOSURE=@MODULE__CTYPES_MALLOC_CLOSURE@ MODULE__DECIMAL_DEPS=$(srcdir)/Modules/_decimal/docstrings.h @LIBMPDEC_INTERNAL@ 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 new file mode 100644 index 00000000000..7b11d8c303c --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-06-23-07-23-08.gh-issue-61103.ca_U_l.rst @@ -0,0 +1,3 @@ +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. diff --git a/Modules/_complex.h b/Modules/_complex.h new file mode 100644 index 00000000000..1c1d1c8cae5 --- /dev/null +++ b/Modules/_complex.h @@ -0,0 +1,34 @@ +/* Workarounds for buggy complex number arithmetic implementations. */ + +#ifndef Py_HAVE_C_COMPLEX +# error "this header file should only be included if Py_HAVE_C_COMPLEX is defined" +#endif + +#include + +/* Other compilers (than clang), that claims to + implement C11 *and* define __STDC_IEC_559_COMPLEX__ - don't have + issue with CMPLX(). This is specific to glibc & clang combination: + https://sourceware.org/bugzilla/show_bug.cgi?id=26287 + + Here we fallback to using __builtin_complex(), available in clang + v12+. Else CMPLX implemented following C11 6.2.5p13: "Each complex type + has the same representation and alignment requirements as an array + type containing exactly two elements of the corresponding real type; + the first element is equal to the real part, and the second element + to the imaginary part, of the complex number. + */ +#if !defined(CMPLX) +# if defined(__clang__) && __has_builtin(__builtin_complex) +# define CMPLX(x, y) __builtin_complex ((double) (x), (double) (y)) +# else +static inline double complex +CMPLX(double real, double imag) +{ + double complex z; + ((double *)(&z))[0] = real; + ((double *)(&z))[1] = imag; + return z; +} +# endif +#endif diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c index 1ff108a3932..3647361b13a 100644 --- a/Modules/_ctypes/_ctypes.c +++ b/Modules/_ctypes/_ctypes.c @@ -1750,7 +1750,11 @@ class _ctypes.c_void_p "PyObject *" "clinic_state_sub()->PyCSimpleType_Type" [clinic start generated code]*/ /*[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"; +#else static const char SIMPLE_TYPE_CHARS[] = "cbBhHiIlLdfuzZqQPXOv?g"; +#endif /*[clinic input] _ctypes.c_wchar_p.from_param as c_wchar_p_from_param @@ -2226,7 +2230,17 @@ PyCSimpleType_init(PyObject *self, PyObject *args, PyObject *kwds) goto error; } - stginfo->ffi_type_pointer = *fmt->pffi_type; + if (!fmt->pffi_type->elements) { + stginfo->ffi_type_pointer = *fmt->pffi_type; + } + else { + 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)); + memcpy(stginfo->ffi_type_pointer.elements, + fmt->pffi_type->elements, 2 * sizeof(ffi_type)); + } stginfo->align = fmt->pffi_type->alignment; stginfo->length = 0; stginfo->size = fmt->pffi_type->size; diff --git a/Modules/_ctypes/_ctypes_test.c b/Modules/_ctypes/_ctypes_test.c index e9ff8108efa..cbc8f8b0b45 100644 --- a/Modules/_ctypes/_ctypes_test.c +++ b/Modules/_ctypes/_ctypes_test.c @@ -13,6 +13,12 @@ #include +#include // FFI_TARGET_HAS_COMPLEX_TYPE + +#if defined(Py_HAVE_C_COMPLEX) && defined(FFI_TARGET_HAS_COMPLEX_TYPE) +# include "../_complex.h" // csqrt() +# undef I // for _ctypes_test_generated.c.h +#endif #include // printf() #include // qsort() #include // memset() @@ -443,6 +449,13 @@ EXPORT(double) my_sqrt(double a) return sqrt(a); } +#if defined(Py_HAVE_C_COMPLEX) && defined(FFI_TARGET_HAS_COMPLEX_TYPE) +EXPORT(double complex) my_csqrt(double complex a) +{ + return csqrt(a); +} +#endif + EXPORT(void) my_qsort(void *base, size_t num, size_t width, int(*compare)(const void*, const void*)) { qsort(base, num, width, compare); diff --git a/Modules/_ctypes/callproc.c b/Modules/_ctypes/callproc.c index cbed2f32caa..a83fa19af32 100644 --- a/Modules/_ctypes/callproc.c +++ b/Modules/_ctypes/callproc.c @@ -105,6 +105,10 @@ module _ctypes #include "pycore_global_objects.h"// _Py_ID() #include "pycore_traceback.h" // _PyTraceback_Add() +#if defined(Py_HAVE_C_COMPLEX) && defined(FFI_TARGET_HAS_COMPLEX_TYPE) +#include "../_complex.h" // complex +#endif + #include "clinic/callproc.c.h" #define CTYPES_CAPSULE_NAME_PYMEM "_ctypes pymem" @@ -651,6 +655,9 @@ union result { double d; float f; void *p; +#if defined(Py_HAVE_C_COMPLEX) && defined(FFI_TARGET_HAS_COMPLEX_TYPE) + double complex C; +#endif }; struct argument { diff --git a/Modules/_ctypes/cfield.c b/Modules/_ctypes/cfield.c index fa5213ca76d..40b72d83d16 100644 --- a/Modules/_ctypes/cfield.c +++ b/Modules/_ctypes/cfield.c @@ -14,6 +14,9 @@ #include #include "ctypes.h" +#if defined(Py_HAVE_C_COMPLEX) && defined(FFI_TARGET_HAS_COMPLEX_TYPE) +# include "../_complex.h" // complex +#endif #define CTYPES_CFIELD_CAPSULE_NAME_PYMEM "_ctypes/cfield.c pymem" @@ -1087,6 +1090,30 @@ d_get(void *ptr, Py_ssize_t size) return PyFloat_FromDouble(val); } +#if defined(Py_HAVE_C_COMPLEX) && defined(FFI_TARGET_HAS_COMPLEX_TYPE) +static PyObject * +C_set(void *ptr, PyObject *value, Py_ssize_t size) +{ + Py_complex c = PyComplex_AsCComplex(value); + + if (c.real == -1 && PyErr_Occurred()) { + return NULL; + } + double complex x = CMPLX(c.real, c.imag); + memcpy(ptr, &x, sizeof(x)); + _RET(value); +} + +static PyObject * +C_get(void *ptr, Py_ssize_t size) +{ + double complex x; + + memcpy(&x, ptr, sizeof(x)); + return PyComplex_FromDoubles(creal(x), cimag(x)); +} +#endif + static PyObject * d_set_sw(void *ptr, PyObject *value, Py_ssize_t size) { @@ -1592,6 +1619,9 @@ static struct fielddesc formattable[] = { { 'B', B_set, B_get, NULL}, { 'c', c_set, c_get, NULL}, { '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}, +#endif { 'g', g_set, g_get, NULL}, { 'f', f_set, f_get, NULL, f_set_sw, f_get_sw}, { 'h', h_set, h_get, NULL, h_set_sw, h_get_sw}, @@ -1642,6 +1672,9 @@ _ctypes_init_fielddesc(void) case 'B': fd->pffi_type = &ffi_type_uchar; break; case 'c': fd->pffi_type = &ffi_type_schar; break; 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; +#endif case 'g': fd->pffi_type = &ffi_type_longdouble; break; case 'f': fd->pffi_type = &ffi_type_float; break; case 'h': fd->pffi_type = &ffi_type_sshort; break; diff --git a/Modules/_ctypes/ctypes.h b/Modules/_ctypes/ctypes.h index 2d711dabab6..5ba5eb3851a 100644 --- a/Modules/_ctypes/ctypes.h +++ b/Modules/_ctypes/ctypes.h @@ -2,9 +2,15 @@ # include #endif +#include // FFI_TARGET_HAS_COMPLEX_TYPE + #include "pycore_moduleobject.h" // _PyModule_GetState() #include "pycore_typeobject.h" // _PyType_GetModuleState() +#if defined(Py_HAVE_C_COMPLEX) && defined(FFI_TARGET_HAS_COMPLEX_TYPE) +# include "../_complex.h" // complex +#endif + #ifndef MS_WIN32 #define max(a, b) ((a) > (b) ? (a) : (b)) #define min(a, b) ((a) < (b) ? (a) : (b)) @@ -393,6 +399,9 @@ struct tagPyCArgObject { double d; float f; void *p; +#if defined(Py_HAVE_C_COMPLEX) && defined(FFI_TARGET_HAS_COMPLEX_TYPE) + double complex C; +#endif } value; PyObject *obj; Py_ssize_t size; /* for the 'V' tag */ diff --git a/PCbuild/_ctypes_test.vcxproj b/PCbuild/_ctypes_test.vcxproj index 50d8575ad7b..f15b80852e3 100644 --- a/PCbuild/_ctypes_test.vcxproj +++ b/PCbuild/_ctypes_test.vcxproj @@ -87,6 +87,7 @@ + diff --git a/Tools/c-analyzer/c_parser/parser/_regexes.py b/Tools/c-analyzer/c_parser/parser/_regexes.py index 5695daff67d..c1a8ab3ad2f 100644 --- a/Tools/c-analyzer/c_parser/parser/_regexes.py +++ b/Tools/c-analyzer/c_parser/parser/_regexes.py @@ -72,6 +72,7 @@ _KEYWORD = textwrap.dedent(r''' long | float | double | + _Complex | void | struct | @@ -121,6 +122,16 @@ SIMPLE_TYPE = textwrap.dedent(rf''' | (?: signed | unsigned ) # implies int | + (?: + (?: (?: float | double | long\s+double ) \s+ )? + _Complex + ) + | + (?: + _Complex + (?: \s+ (?: float | double | long\s+double ) )? + ) + | (?: (?: (?: signed | unsigned ) \s+ )? (?: (?: long | short ) \s+ )? diff --git a/configure b/configure index 963ceb9ae38..922d33edc00 100755 --- a/configure +++ b/configure @@ -13999,6 +13999,51 @@ printf "%s\n" "$AIX_BUILDDATE" >&6; } *) ;; esac +# check for _Complex C type +# +# Note that despite most compilers define __STDC_IEC_559_COMPLEX__ - almost +# none properly support C11+ Annex G (where pure imaginary types +# represented by _Imaginary are mandatory). This is a bug (see e.g. +# llvm/llvm-project#60269), so we don't rely on presence +# of __STDC_IEC_559_COMPLEX__. +if test "$cross_compiling" = yes +then : + ac_cv_c_complex_supported=no +else $as_nop + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +#include +#define test(type, out) \ +{ \ + type complex z = 1 + 2*I; z = z*z; \ + (out) = (out) || creal(z) != -3 || cimag(z) != 4; \ +} +int main(void) +{ + int res = 0; + test(float, res); + test(double, res); + test(long double, res); + return res; +} +_ACEOF +if ac_fn_c_try_run "$LINENO" +then : + ac_cv_c_complex_supported=yes +else $as_nop + ac_cv_c_complex_supported=no +fi +rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ + conftest.$ac_objext conftest.beam conftest.$ac_ext +fi + +if test "$ac_cv_c_complex_supported" = "yes"; then + +printf "%s\n" "#define Py_HAVE_C_COMPLEX 1" >>confdefs.h + +fi + # check for systems that require aligned memory access { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking aligned memory access is required" >&5 printf %s "checking aligned memory access is required... " >&6; } @@ -31487,8 +31532,8 @@ fi if test "x$py_cv_module__ctypes_test" = xyes then : - - as_fn_append MODULE_BLOCK "MODULE__CTYPES_TEST_LDFLAGS=$LIBM$as_nl" + as_fn_append MODULE_BLOCK "MODULE__CTYPES_TEST_CFLAGS=$LIBFFI_CFLAGS$as_nl" + as_fn_append MODULE_BLOCK "MODULE__CTYPES_TEST_LDFLAGS=$LIBFFI_LIBS $LIBM$as_nl" fi if test "$py_cv_module__ctypes_test" = yes; then diff --git a/configure.ac b/configure.ac index 460dd8916bc..a70e673623d 100644 --- a/configure.ac +++ b/configure.ac @@ -3826,6 +3826,35 @@ dnl The AIX_BUILDDATE is obtained from the kernel fileset - bos.mp64 *) ;; esac +# check for _Complex C type +# +# Note that despite most compilers define __STDC_IEC_559_COMPLEX__ - almost +# none properly support C11+ Annex G (where pure imaginary types +# represented by _Imaginary are mandatory). This is a bug (see e.g. +# llvm/llvm-project#60269), so we don't rely on presence +# of __STDC_IEC_559_COMPLEX__. +AC_RUN_IFELSE([AC_LANG_SOURCE([[ +#include +#define test(type, out) \ +{ \ + type complex z = 1 + 2*I; z = z*z; \ + (out) = (out) || creal(z) != -3 || cimag(z) != 4; \ +} +int main(void) +{ + int res = 0; + test(float, res); + test(double, res); + test(long double, res); + return res; +}]])], [ac_cv_c_complex_supported=yes], +[ac_cv_c_complex_supported=no], +[ac_cv_c_complex_supported=no]) +if test "$ac_cv_c_complex_supported" = "yes"; then + AC_DEFINE([Py_HAVE_C_COMPLEX], [1], + [Defined if _Complex C type is available.]) +fi + # check for systems that require aligned memory access AC_CACHE_CHECK([aligned memory access is required], [ac_cv_aligned_required], [AC_RUN_IFELSE([AC_LANG_SOURCE([[ @@ -7811,7 +7840,7 @@ PY_STDLIB_MOD([xxsubtype], [test "$TEST_MODULES" = yes]) PY_STDLIB_MOD([_xxtestfuzz], [test "$TEST_MODULES" = yes]) PY_STDLIB_MOD([_ctypes_test], [test "$TEST_MODULES" = yes], [test "$have_libffi" = yes -a "$ac_cv_func_dlopen" = yes], - [], [$LIBM]) + [$LIBFFI_CFLAGS], [$LIBFFI_LIBS $LIBM]) dnl Limited API template modules. dnl Emscripten does not support shared libraries yet. diff --git a/pyconfig.h.in b/pyconfig.h.in index 10980c92176..8fbba7ed3b9 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -1686,6 +1686,9 @@ SipHash13: 3, externally defined: 0 */ #undef Py_HASH_ALGORITHM +/* Defined if _Complex C type is available. */ +#undef Py_HAVE_C_COMPLEX + /* Define if year with century should be normalized for strftime. */ #undef Py_NORMALIZE_CENTURY