Search code examples
cpython-3.xpypypython-cffi

Function always returns meaningless value


I'm writing C functions to be called in pypy3 by cffi. However, the wrapped functions always return a meaningless value in pypy3, no matter what the true return value is.

The output of the printf() function tells me everything works fine in the C function, but the return value in pypy3 is changed.

The C function is written like this:

double test(){
    return 5.12;
}

double test2(){
    double tmp=test();
    printf("!!!!!!!%f\n",tmp);
    return tmp;
}

The cffi build script is as follows:

from cffi import FFI
ffibuilder = FFI()

ffibuilder.set_source("_faststr_cffi",
                      """
                        #include <Python.h>
                        #include "stdint.h"
                        #include <string.h>
                        typedef uint32_t char32_t;
                      """,
                      sources=['faststr_purec.c']
                      )   # library name, for the linker

ffibuilder.cdef("""
double test();
double test2();
""")

if __name__ == "__main__":
    ffibuilder.compile(verbose=True)

I tried to call test2() in pypy3 console:

>>>> from _faststr_cffi import lib
>>>> lib.test2()
!!!!!!!5.120000
16.0

The printf tells me that return value should be 5.120000, but it returned 16.0 in pypy3.

I found some clue: If I change the string in test2() printf function, the return value of test2 in pypy3 is changed.

Update: Result in cpython 3.6.7 is the same so it's not a pypy3 problem


Solution

  • The problem is as follows:

    Here, you declare the functions test() and test2():

    ffibuilder.cdef("""
    double test();
    double test2();
    """)
    

    These declarations are only for the cffi interface to know the return values to use. But the declarations for the native c functions test() and test2() are missing. Thus, they are implicitly declared returning int!

    Now when the function test() is called (which is implicitly declared returning int) from test2

    double tmp = test();
    
    return tmp;
    

    the compiled code reads the wrong register (because it looks for an integer value) and converts it to a double, returning it as a result. As it happens, the last integer result was the result of the printf(), which is the length of the printed string. Because of this, you get the result of 16 in your case.

    The fix is, to declare the functions test() and test2() properly:

    ffibuilder.set_source("_faststr_cffi",
                      """
                        #include <Python.h>
                        #include "stdint.h"
                        #include <string.h>
                        typedef uint32_t char32_t;
                        double test();
                        double test2();
                      """,
                      sources=['faststr_purec.c']
                      )   # library name, for the linker
    

    Then it should work as expected.