4

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

Ctx
  • 18,090
  • 24
  • 36
  • 51
Alex
  • 335
  • 2
  • 12
  • 2
    Weird question, but is it always the number of characters written? Because that's what it looks like here – Edward Minnix Jul 25 '19 at 12:59
  • @EdwardMinnix Indeed, this is a good catch, which lead to the identification of the reason of the misbehaviour – Ctx Jul 25 '19 at 14:03

1 Answers1

3

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.

Ctx
  • 18,090
  • 24
  • 36
  • 51
  • I think you are right. But could you please explain more about why printf print the correct return value 5.120000? – Alex Jul 25 '19 at 15:52
  • @Jay It's a matter of calling conventions. `test()` returns a double in register A, but `test2()` looks for an integer in register B, since `test()` is (implicitly) declared returning an integer. The integer stored in register B is the return value of `printf()`, which is not overwritten before returning from `test()`. – Ctx Jul 27 '19 at 12:34