From 4d660b1d1b7991dc1b05cd01f4013896ae3ec683 Mon Sep 17 00:00:00 2001 From: Armin Rigo Date: Thu, 8 Feb 2024 12:34:36 +0100 Subject: [PATCH] Support for complex types with MSVC, called _Fcomplex and _Dcomplex (#57) --- src/c/_cffi_backend.c | 4 ++ src/c/commontypes.c | 2 + src/c/parse_c_type.c | 2 + src/cffi/_cffi_include.h | 4 ++ src/cffi/cffi_opcode.py | 4 +- src/cffi/commontypes.py | 2 + src/cffi/model.py | 4 +- src/cffi/vengine_cpy.py | 7 +++ src/cffi/vengine_gen.py | 4 ++ testing/cffi0/test_verify.py | 19 ++++++- testing/cffi1/test_new_ffi_1.py | 4 +- testing/cffi1/test_realize_c_type.py | 4 +- testing/cffi1/test_recompiler.py | 81 ++++++++++++++++++++-------- testing/cffi1/test_verify1.py | 23 +++++++- 14 files changed, 132 insertions(+), 32 deletions(-) diff --git a/src/c/_cffi_backend.c b/src/c/_cffi_backend.c index 14fe9106..a21bba4a 100644 --- a/src/c/_cffi_backend.c +++ b/src/c/_cffi_backend.c @@ -53,11 +53,15 @@ # if _MSC_VER < 1800 /* MSVC < 2013 */ typedef unsigned char _Bool; # endif +# define _cffi_float_complex_t _Fcomplex /* include for it */ +# define _cffi_double_complex_t _Dcomplex /* include for it */ #else # include # if (defined (__SVR4) && defined (__sun)) || defined(_AIX) || defined(__hpux) # include # endif +# define _cffi_float_complex_t float _Complex +# define _cffi_double_complex_t double _Complex #endif /* Convert from closure pointer to function pointer. */ diff --git a/src/c/commontypes.c b/src/c/commontypes.c index a41c2fdd..2337bf99 100644 --- a/src/c/commontypes.c +++ b/src/c/commontypes.c @@ -172,6 +172,8 @@ static const char *common_simple_types[] = { EQ("WINSTA", "HANDLE"), EQ("WORD", "unsigned short"), EQ("WPARAM", "UINT_PTR"), + EQ("_Dcomplex", "_cffi_double_complex_t"), + EQ("_Fcomplex", "_cffi_float_complex_t"), #endif EQ("bool", "_Bool"), diff --git a/src/c/parse_c_type.c b/src/c/parse_c_type.c index 698ef645..3cb9341c 100644 --- a/src/c/parse_c_type.c +++ b/src/c/parse_c_type.c @@ -535,6 +535,8 @@ int search_standard_typename(const char *p, size_t size) case 'i': if (size == 9 && !memcmp(p, "ptrdiff", 7)) return _CFFI_PRIM_PTRDIFF; + if (size == 21 && !memcmp(p, "_cffi_float_complex", 19)) return _CFFI_PRIM_FLOATCOMPLEX; + if (size == 22 && !memcmp(p, "_cffi_double_complex", 20)) return _CFFI_PRIM_DOUBLECOMPLEX; break; case 'l': diff --git a/src/cffi/_cffi_include.h b/src/cffi/_cffi_include.h index e4c0a672..908a1d73 100644 --- a/src/cffi/_cffi_include.h +++ b/src/cffi/_cffi_include.h @@ -101,11 +101,15 @@ extern "C" { typedef unsigned char _Bool; # endif # endif +# define _cffi_float_complex_t _Fcomplex /* include for it */ +# define _cffi_double_complex_t _Dcomplex /* include for it */ #else # include # if (defined (__SVR4) && defined (__sun)) || defined(_AIX) || defined(__hpux) # include # endif +# define _cffi_float_complex_t float _Complex +# define _cffi_double_complex_t double _Complex #endif #ifdef __GNUC__ diff --git a/src/cffi/cffi_opcode.py b/src/cffi/cffi_opcode.py index a0df98d1..6421df62 100644 --- a/src/cffi/cffi_opcode.py +++ b/src/cffi/cffi_opcode.py @@ -132,8 +132,8 @@ def format_four_bytes(num): 'float': PRIM_FLOAT, 'double': PRIM_DOUBLE, 'long double': PRIM_LONGDOUBLE, - 'float _Complex': PRIM_FLOATCOMPLEX, - 'double _Complex': PRIM_DOUBLECOMPLEX, + '_cffi_float_complex_t': PRIM_FLOATCOMPLEX, + '_cffi_double_complex_t': PRIM_DOUBLECOMPLEX, '_Bool': PRIM_BOOL, 'wchar_t': PRIM_WCHAR, 'char16_t': PRIM_CHAR16, diff --git a/src/cffi/commontypes.py b/src/cffi/commontypes.py index 8ec97c75..d4dae351 100644 --- a/src/cffi/commontypes.py +++ b/src/cffi/commontypes.py @@ -14,6 +14,8 @@ COMMON_TYPES['FILE'] = model.unknown_type('FILE', '_IO_FILE') COMMON_TYPES['bool'] = '_Bool' # in case we got ImportError above +COMMON_TYPES['float _Complex'] = '_cffi_float_complex_t' +COMMON_TYPES['double _Complex'] = '_cffi_double_complex_t' for _type in model.PrimitiveType.ALL_PRIMITIVE_TYPES: if _type.endswith('_t'): diff --git a/src/cffi/model.py b/src/cffi/model.py index 1708f43d..e5f4cae3 100644 --- a/src/cffi/model.py +++ b/src/cffi/model.py @@ -117,8 +117,8 @@ class PrimitiveType(BasePrimitiveType): 'float': 'f', 'double': 'f', 'long double': 'f', - 'float _Complex': 'j', - 'double _Complex': 'j', + '_cffi_float_complex_t': 'j', + '_cffi_double_complex_t': 'j', '_Bool': 'i', # the following types are not primitive in the C sense 'wchar_t': 'c', diff --git a/src/cffi/vengine_cpy.py b/src/cffi/vengine_cpy.py index 49727d36..eb0b6f70 100644 --- a/src/cffi/vengine_cpy.py +++ b/src/cffi/vengine_cpy.py @@ -246,6 +246,9 @@ def _convert_funcarg_to_c(self, tp, fromvar, tovar, errcode): if tp.is_integer_type() and tp.name != '_Bool': converter = '_cffi_to_c_int' extraarg = ', %s' % tp.name + elif tp.is_complex_type(): + raise VerificationError( + "not implemented in verify(): complex types") else: converter = '(%s)_cffi_to_c_%s' % (tp.get_c_name(''), tp.name.replace(' ', '_')) @@ -856,11 +859,15 @@ def _generate_setup_custom(self): typedef unsigned char _Bool; # endif # endif +# define _cffi_float_complex_t _Fcomplex /* include for it */ +# define _cffi_double_complex_t _Dcomplex /* include for it */ #else # include # if (defined (__SVR4) && defined (__sun)) || defined(_AIX) || defined(__hpux) # include # endif +# define _cffi_float_complex_t float _Complex +# define _cffi_double_complex_t double _Complex #endif #if PY_MAJOR_VERSION < 3 diff --git a/src/cffi/vengine_gen.py b/src/cffi/vengine_gen.py index 26421526..bffc8212 100644 --- a/src/cffi/vengine_gen.py +++ b/src/cffi/vengine_gen.py @@ -666,10 +666,14 @@ def setter(library, value): typedef unsigned char _Bool; # endif # endif +# define _cffi_float_complex_t _Fcomplex /* include for it */ +# define _cffi_double_complex_t _Dcomplex /* include for it */ #else # include # if (defined (__SVR4) && defined (__sun)) || defined(_AIX) || defined(__hpux) # include # endif +# define _cffi_float_complex_t float _Complex +# define _cffi_double_complex_t double _Complex #endif ''' diff --git a/testing/cffi0/test_verify.py b/testing/cffi0/test_verify.py index ad7a04e3..4942bba6 100644 --- a/testing/cffi0/test_verify.py +++ b/testing/cffi0/test_verify.py @@ -242,7 +242,7 @@ def test_primitive_category(): I = tp.is_integer_type() assert C == (typename in ('char', 'wchar_t', 'char16_t', 'char32_t')) assert F == (typename in ('float', 'double', 'long double')) - assert X == (typename in ('float _Complex', 'double _Complex')) + assert X == (typename in ('_cffi_float_complex_t', '_cffi_double_complex_t')) assert I + F + C + X == 1 # one and only one of them is true def test_all_integer_and_float_types(): @@ -280,6 +280,23 @@ def test_all_integer_and_float_types(): else: assert foo(value) == value + 1 +def test_all_complex_types(): + pytest.skip("not implemented in verify(): complex types") + if sys.platform == 'win32': + typenames = ['_Fcomplex', '_Dcomplex'] + header = '#include \n' + else: + typenames = ['float _Complex', 'double _Complex'] + header = '' + # + ffi = FFI() + ffi.cdef('\n'.join(["%s foo_%s(%s);" % (tp, tp.replace(' ', '_'), tp) + for tp in typenames])) + e = pytest.raises(VerificationError, ffi.verify, + header + '\n'.join(["%s foo_%s(%s x) { return x; }" % + (tp, tp.replace(' ', '_'), tp) + for tp in typenames])) + def test_var_signed_integer_types(): ffi = FFI() lst = all_signed_integer_types(ffi) diff --git a/testing/cffi1/test_new_ffi_1.py b/testing/cffi1/test_new_ffi_1.py index a461176c..49cf0c2e 100644 --- a/testing/cffi1/test_new_ffi_1.py +++ b/testing/cffi1/test_new_ffi_1.py @@ -1762,8 +1762,8 @@ def test_all_primitives(self): "ptrdiff_t", "size_t", "ssize_t", - 'float _Complex', - 'double _Complex', + '_cffi_float_complex_t', + '_cffi_double_complex_t', ]) for name in PRIMITIVE_TO_INDEX: x = ffi.sizeof(name) diff --git a/testing/cffi1/test_realize_c_type.py b/testing/cffi1/test_realize_c_type.py index 824beaf3..8af4f4ab 100644 --- a/testing/cffi1/test_realize_c_type.py +++ b/testing/cffi1/test_realize_c_type.py @@ -45,8 +45,10 @@ def test_funcptr_rewrite_args(): check("int(*)(long[5])", "int(*)(long *)") def test_all_primitives(): + mapping = {"_cffi_float_complex_t": "float _Complex", + "_cffi_double_complex_t": "double _Complex"} for name in cffi_opcode.PRIMITIVE_TO_INDEX: - check(name, name) + check(name, mapping.get(name, name)) def check_func(input, expected_output=None): import _cffi_backend diff --git a/testing/cffi1/test_recompiler.py b/testing/cffi1/test_recompiler.py index e52752ba..f36512a3 100644 --- a/testing/cffi1/test_recompiler.py +++ b/testing/cffi1/test_recompiler.py @@ -2044,55 +2044,90 @@ def test_function_returns_partial_struct(): assert lib.f1(52).a == 52 def test_function_returns_float_complex(): - if sys.platform == 'win32': - pytest.skip("MSVC may not support _Complex") ffi = FFI() ffi.cdef("float _Complex f1(float a, float b);"); - lib = verify(ffi, "test_function_returns_float_complex", """ - #include - static float _Complex f1(float a, float b) { return a + I*2.0f*b; } - """, no_cpp=True) # fails on some systems with C++ + if sys.platform == 'win32': + lib = verify(ffi, "test_function_returns_float_complex", """ + #include + static _Fcomplex f1(float a, float b) { return _FCbuild(a, 2.0f*b); } + """) + else: + lib = verify(ffi, "test_function_returns_float_complex", """ + #include + static float _Complex f1(float a, float b) { return a + I*2.0f*b; } + """, no_cpp=True) # fails on some systems with C++ result = lib.f1(1.25, 5.1) assert type(result) == complex assert result.real == 1.25 # exact assert (result.imag != 2*5.1) and (abs(result.imag - 2*5.1) < 1e-5) # inexact def test_function_returns_double_complex(): - if sys.platform == 'win32': - pytest.skip("MSVC may not support _Complex") ffi = FFI() ffi.cdef("double _Complex f1(double a, double b);"); - lib = verify(ffi, "test_function_returns_double_complex", """ + if sys.platform == 'win32': + lib = verify(ffi, "test_function_returns_double_complex", """ + #include + static _Dcomplex f1(double a, double b) { return _Cbuild(a, 2.0*b); } + """) + else: + lib = verify(ffi, "test_function_returns_double_complex", """ + #include + static double _Complex f1(double a, double b) { return a + I*2.0*b; } + """, no_cpp=True) # fails on some systems with C++ + result = lib.f1(1.25, 5.1) + assert type(result) == complex + assert result.real == 1.25 # exact + assert result.imag == 2*5.1 # exact + +def test_cdef_using_windows_complex(): + if sys.platform != 'win32': + pytest.skip("only for MSVC") + ffi = FFI() + ffi.cdef("_Fcomplex f1(float a, float b); _Dcomplex f2(double a, double b);"); + lib = verify(ffi, "test_cdef_using_windows_complex", """ #include - static double _Complex f1(double a, double b) { return a + I*2.0*b; } - """, no_cpp=True) # fails on some systems with C++ + static _Fcomplex f1(float a, float b) { return _FCbuild(a, 2.0f*b); } + static _Dcomplex f2(double a, double b) { return _Cbuild(a, 2.0*b); } + """) result = lib.f1(1.25, 5.1) assert type(result) == complex assert result.real == 1.25 # exact + assert (result.imag != 2*5.1) and (abs(result.imag - 2*5.1) < 1e-5) # inexact + result = lib.f2(1.25, 5.1) + assert type(result) == complex + assert result.real == 1.25 # exact assert result.imag == 2*5.1 # exact def test_function_argument_float_complex(): - if sys.platform == 'win32': - pytest.skip("MSVC may not support _Complex") ffi = FFI() ffi.cdef("float f1(float _Complex x);"); - lib = verify(ffi, "test_function_argument_float_complex", """ - #include - static float f1(float _Complex x) { return cabsf(x); } - """, no_cpp=True) # fails on some systems with C++ + if sys.platform == 'win32': + lib = verify(ffi, "test_function_argument_float_complex", """ + #include + static float f1(_Fcomplex x) { return cabsf(x); } + """) + else: + lib = verify(ffi, "test_function_argument_float_complex", """ + #include + static float f1(float _Complex x) { return cabsf(x); } + """, no_cpp=True) # fails on some systems with C++ x = complex(12.34, 56.78) result = lib.f1(x) assert abs(result - abs(x)) < 1e-5 def test_function_argument_double_complex(): - if sys.platform == 'win32': - pytest.skip("MSVC may not support _Complex") ffi = FFI() ffi.cdef("double f1(double _Complex);"); - lib = verify(ffi, "test_function_argument_double_complex", """ - #include - static double f1(double _Complex x) { return cabs(x); } - """, no_cpp=True) # fails on some systems with C++ + if sys.platform == 'win32': + lib = verify(ffi, "test_function_argument_double_complex", """ + #include + static double f1(_Dcomplex x) { return cabs(x); } + """) + else: + lib = verify(ffi, "test_function_argument_double_complex", """ + #include + static double f1(double _Complex x) { return cabs(x); } + """, no_cpp=True) # fails on some systems with C++ x = complex(12.34, 56.78) result = lib.f1(x) assert abs(result - abs(x)) < 1e-11 diff --git a/testing/cffi1/test_verify1.py b/testing/cffi1/test_verify1.py index cea15f2b..a7ac14d3 100644 --- a/testing/cffi1/test_verify1.py +++ b/testing/cffi1/test_verify1.py @@ -216,7 +216,7 @@ def test_primitive_category(): I = tp.is_integer_type() assert C == (typename in ('char', 'wchar_t', 'char16_t', 'char32_t')) assert F == (typename in ('float', 'double', 'long double')) - assert X == (typename in ('float _Complex', 'double _Complex')) + assert X == (typename in ('_cffi_float_complex_t', '_cffi_double_complex_t')) assert I + F + C + X == 1 # one and only one of them is true def test_all_integer_and_float_types(): @@ -254,6 +254,27 @@ def test_all_integer_and_float_types(): else: assert foo(value) == value + 1 +def test_all_complex_types(): + if sys.platform == 'win32': + typenames = ['_Fcomplex', '_Dcomplex'] + header = '#include \n' + else: + typenames = ['float _Complex', 'double _Complex'] + header = '' + # + ffi = FFI() + ffi.cdef('\n'.join(["%s foo_%s(%s);" % (tp, tp.replace(' ', '_'), tp) + for tp in typenames])) + lib = ffi.verify( + header + '\n'.join(["%s foo_%s(%s x) { return x; }" % + (tp, tp.replace(' ', '_'), tp) + for tp in typenames])) + for typename in typenames: + foo = getattr(lib, 'foo_%s' % typename.replace(' ', '_')) + assert foo(42 + 1j) == 42 + 1j + assert foo(ffi.cast(typename, 46 - 3j)) == 46 - 3j + pytest.raises(TypeError, foo, ffi.NULL) + def test_var_signed_integer_types(): ffi = FFI() lst = all_signed_integer_types(ffi)