python-cffi/cffi

Complex number support using msvc

chris-hld opened this issue · 11 comments

In API mode, compiling on github actions, using _Complex works perfectly for Linux and macOS latest.
However, on windows-latest, I am not sure how to use the interface type complex.
I am aware that msvc doesn't support float _Complex, but defines its own _Fcomplex.
https://learn.microsoft.com/en-us/cpp/c-runtime-library/complex-math-support?view=msvc-170
This is, however, not supported by cffi as it seems, since trying

if sys.platform != "win32":
    ffibuilder.cdef("""
                    
    typedef float _Complex float_complex;
    typedef double _Complex double_complex;

    """)
else:
    ffibuilder.cdef("""
                    
    typedef _Fcomplex float_complex;
    typedef _Dcomplex double_complex;

    """)

fails with

 cffi.CDefError: cannot parse "typedef _Fcomplex float_complex;"
<cdef source string>:3:23: before: float_complex
Error: Process completed with exit code 1.

If I don't use _Fcomplex, only using float _Complex throughout the project, then msvc fails with the typical

build\temp.win-amd64-cpython-310\Release\_safpy.c(727): error C2143: syntax error: missing ')' before '*'
build\temp.win-amd64-cpython-310\Release\_safpy.c(727): error C2143: syntax error: missing '{' before '*'
build\temp.win-amd64-cpython-310\Release\_safpy.c(727): error C2059: syntax error: 'type'
build\temp.win-amd64-cpython-310\Release\_safpy.c(727): error C2059: syntax error: ')'
build\temp.win-amd64-cpython-310\Release\_safpy.c(736): error C2143: syntax error: missing ';' before '*'
build\temp.win-amd64-cpython-310\Release\_safpy.c(761): error C2146: syntax error: missing ')' before identifier '_Complex'
build\temp.win-amd64-cpython-310\Release\_safpy.c(761): error C2059: syntax error: ')'
build\temp.win-amd64-cpython-310\Release\_safpy.c(761): error C2100: illegal indirection
build\temp.win-amd64-cpython-310\Release\_safpy.c(761): error C2100: illegal indirection
build\temp.win-amd64-cpython-310\Release\_safpy.c(761): error C2297: '*': not valid as right operand has type 'void *'
build\temp.win-amd64-cpython-310\Release\_safpy.c(782): warning C4133: 'function': incompatible types - from 'int ***' to 'float_complex ***'
build\temp.win-amd64-cpython-310\Release\_safpy.c(795): error C2143: syntax error: missing ')' before '*'
build\temp.win-amd64-cpython-310\Release\_safpy.c(795): error C2143: syntax error: missing '{' before '*'
build\temp.win-amd64-cpython-310\Release\_safpy.c(795): error C2040: 'x1': 'int *' differs in levels of indirection from 'int ***'
build\temp.win-amd64-cpython-310\Release\_safpy.c(795): error C2059: syntax error: 'type'
build\temp.win-amd64-cpython-310\Release\_safpy.c(795): error C2059: syntax error: ')'
build\temp.win-amd64-cpython-310\Release\_safpy.c(804): error C2143: syntax error: missing ';' before '*'
build\temp.win-amd64-cpython-310\Release\_safpy.c(829): error C2146: syntax error: missing ')' before identifier '_Complex'
build\temp.win-amd64-cpython-310\Release\_safpy.c(829): error C2059: syntax error: ')'
build\temp.win-amd64-cpython-310\Release\_safpy.c(829): error C2297: '*': not valid as right operand has type 'void *'
build\temp.win-amd64-cpython-310\Release\_safpy.c(850): warning C4047: 'function': 'float_complex *' differs in levels of indirection from 'int ***'
build\temp.win-amd64-cpython-310\Release\_safpy.c(850): warning C4024: 'afSTFT_backward_flat': different types for formal and actual parameter 2

and so on.

Since the documentation mentions complex number support, is there any advice on how to use them with msvc/ cffi on windows?

Thanks!

Possibly related:
cython/cython#5512

arigo commented

Can we see a complete example for the 2nd case? I think it should work (or at least not fail like that) if you use typedef float _Complex float_complex; in the cdef(), but not in the C sources, where you'd presumably include typedef _Fcomplex float_complex; instead. Not saying it's the cleanest and most well-designed approach...

arigo commented

...ah no, I managed to write a failing example. Yes, indeed the C code generated by cffi uses float _Complex directly and that doesn't compile on MSVC.

arigo commented

This workaround might work for you:

import cffi

ffibuilder = cffi.FFI()
ffibuilder.cdef("""
    typedef struct { ...; } _Fcomplex;
    typedef struct { ...; } _Dcomplex;

    typedef _Fcomplex float_complex;
    typedef _Dcomplex double_complex;

    int my_function(float_complex);
""")
ffibuilder.set_source("_wincomplex_cffi", """
    #include <complex.h>
    typedef _Fcomplex float_complex;
    typedef _Dcomplex double_complex;
    int my_function(float_complex x) { return 42; }
""")
ffibuilder.compile()

Please tell me if it fully works for you. If it does, I could make the first two lines of the cdef() be automatically added on Windows.

arigo commented

...but you can't pass Python complex numbers as arguments to my_function() in this version. CFFI doesn't know that _Fcomplex is supposed to be a complex number. So it's likely not enough.

arigo commented

I've made pull request #57 that should implement the necessary bits and pieces. If you can test it for your use case, it would be appreciated!

Great!
I ended up with a somewhat similar workaround:

if sys.platform != "win32":
    ffibuilder.cdef("""
    typedef float _Complex float_complex;
    typedef double _Complex double_complex;
    """)
else:
    ffibuilder.cdef("""
    typedef struct 
    {
        float _Val[2];
    } float_complex;
    """)

I found this was how (at least the current) msvc implementation looked.

I'll give the PR a test, thanks!

With #57, no further workarounds required, this works as expected:

if sys.platform != "win32":
    ffibuilder.cdef("""
    typedef float _Complex float_complex;
    typedef double _Complex double_complex;
    """)
else:
    ffibuilder.cdef("""
    typedef _Fcomplex float_complex;
    typedef _Dcomplex double_complex;
    """)

While looking at the PR I just realized that

    ffibuilder.cdef("""
    typedef float _Complex float_complex;
    typedef double _Complex double_complex;
    """)

Works on MSVC now too! (I am assuming it comes from src/cffi/commontypes.py?)

arigo commented

Yes: float _Complex works everywhere in the cdef() or the usages of the type at runtime, like in ffi.new("float _Complex[]", array_length). It always did; the PR mostly just adds the type _Fcomplex as a synonym, and when CFFI generates C code it writes _cffi_float_complex_t instead, and there are some #defines. It would be more work to prevent float _Complex from working there, and there is little point.

I still need to update the docs in the PR.

Great, thanks!

(Closing as solved with PR, workarounds shown here.)