Complex numbers passed by-value from C++ to C does not seem to work on powerpc
Asked Answered
M

3

12

When I'm passing a complex float(complex.h) from a c++ caller to a c library, the value does not pass correctly when running on a 32 bit power pc. I was using two different open source software libraries when I detected this problem. I've isolated it down to the boundry of when C++ is passing a complex value type to a pure C type function. I wrote up some simple code to demonstrate it.

#ifndef MMYLIB_3A8726C1_H
#define MMYLIB_3A8726C1_H

typedef struct aComplexStructure {
    float r;
    float i;
} myComplex_t;

#ifdef __cplusplus
#include <complex>
extern "C" {
    void procWithComplex(float a, std::complex<float> *pb, std::complex<float> c, float d);
    void procWithStruct(float a, myComplex_t *pb, myComplex_t c, float d);
}

#else  /* __cplusplus */

#include <complex.h>
void procWithComplex(float a, float complex *pb, float complex c, float d);
void procWithStruct(float a, myComplex_t *pb, myComplex_t c, float d);

#endif

#endif /* MYLIB_3A8726C1_H */

The source C file is as follows

#include <stdio.h>
#include "myLib.h"

void procWithComplex(float a, complex float * pb, complex float  c, float d)
{
    printf("a=%f\n", a);
    printf("b=%f + %fi\n", creal(*pb), cimag(*pb));
    printf("c=%f + %fi\n", creal(c), cimag(c));
    printf("d=%f\n", d);
}


void procWithStruct(float a, myComplex_t* pb, myComplex_t c, float d)
{
    printf("a=%f\n", a);
    printf("b=%f + %fi\n", pb->r, pb->i);
    printf("c=%f + %fi\n", c.r, c.i);
    printf("d=%f\n", d);
}

The calling C++ program is as follows

#include <iostream>
#include "myLib.h"

int main()
{
    float a = 1.2;
    std::complex<float> b = 3.4 + 3.4I;
    std::complex<float> c = 5.6 + 5.6I;
    float d = 9.876;

    myComplex_t b_s, c_s;

    b_s.r = b.real();
    b_s.i = b.imag();

    c_s.r = c.real();
    c_s.i = c.imag();

    std::cout << "a=" << a << std::endl;
    std::cout << "b=" << b << std::endl;
    std::cout << "c=" << c << std::endl;
    std::cout << "d=" << d << std::endl << std::endl;

    // c is a 64 bit structure being passed by value.
    // on my 32 bit embedded powerpc platform, it is being
    // passed by reference, but the underlying C library is
    // reading it by value.
    procWithComplex(a, &b, c, d);
    std::cout << std::endl;

    // This is only here to demonstrate that a 64 bit value field
    // does pass through the C++ to C boundry
    procWithStruct(a, &b_s, c_s, d);
    return 0;
}

Normally I would expect the output to be

a=1.2
b=(3.4,3.4)
c=(5.6,5.6)
d=9.876

a=1.200000
b=3.400000 + 3.400000i
c=5.600000 + 5.600000i
d=9.876000

a=1.200000
b=3.400000 + 3.400000i
c=5.600000 + 5.600000i
d=9.876000

But when I run the source on an embedded power pc machine I get output that shows that the value type for complex is not being passed properly.

a=1.2
b=(3.4,3.4)
c=(5.6,5.6)
d=9.876

a=1.200000
b=3.400000 + 3.400000i
c=-0.000000 + 9.876000i
d=0.000000

a=1.200000
b=3.400000 + 3.400000i
c=5.600000 + 5.600000i
d=9.876000

I checked the sizeof the paramaters from gdb and from both the calling and the function frame the sizes are 4 bytes, 4 bytes, 8 bytes and 4 bytes, for the float, complex float pointer, complex float, and float.

I realize I can just change the complex value parameter as a pointer, or my own struct when crossing the c++ to c boundry, but I want to know why I can't pass a complex value type from c++ to c on a power pc.

I created another example only this time I dumped some of the assembly as well as the register values.

int x = 22;
std::complex<float> y = 55 + 88I;
int z = 77;
void simpleProc(int x, complex float y, int z)

Right before call where the parameters are passed in.

x = 22
y =  {_M_value = 55 + 88 * I} 
Looking at raw data *(int*)&y = 1113325568
z = 77

This should be assembly code where it saves the the return address and saves the paramters to pass into the routine.

x0x10000b78 <main()+824> lwz     r9,40(r31)
x0x10000b7c <main()+828> stw     r9,72(r31)
x0x10000b80 <main()+832> lwz     r9,44(r31)
x0x10000b84 <main()+836> stw     r9,76(r31)   
x0x10000b88 <main()+840> addi    r9,r31,72    
x0x10000b8c <main()+844> lwz     r3,16(r31)   
x0x10000b90 <main()+848> mr      r4,r9       
x0x10000b94 <main()+852> lwz     r5,20(r31)   
x0x10000b98 <main()+856> bl      0x10000f88 <simpleProc>  

Looking at the assembly right after the branch :)

x0x10000f88 <simpleProc>         stwu    r1,-48(r1)  
x0x10000f8c <simpleProc+4>       mflr    r0      
x0x10000f90 <simpleProc+8>       stw     r0,52(r1)   
x0x10000f94 <simpleProc+12>      stw     r29,36(r1)  
x0x10000f98 <simpleProc+16>      stw     r30,40(r1)  
x0x10000f9c <simpleProc+20>      stw     r31,44(r1)  
x0x10000fa0 <simpleProc+24>      mr      r31,r1      
x0x10000fa4 <simpleProc+28>      stw     r3,8(r31)  
x0x10000fa8 <simpleProc+32>      stw     r5,12(r31) 
x0x10000fac <simpleProc+36>      stw     r6,16(r31)  
x0x10000fb0 <simpleProc+40>      stw     r7,20(r31)  
x0x10000fb4 <simpleProc+44>      lis     r9,4096  

These are the value once we are completely in the routine (after variable values are assigned.

x = 22
y =  1.07899982e-43 + 0 * I
z = 265134296

$r3 = 22
$r4 = 0x9ffff938
*(int*)$r4 = 1113325568
$r5 = 77

*(int*)(&y) = 77

My laymans view is it looks like the C++ is passing the complex value type as a reference or pointer type? but C is treating it like a value type? So is this a problem with gcc on the power pc? I am using gcc4.7.1. I am in the process of building gcc4.9.3 as a cross compiler on another machine. I will update this post either way once I get the output from a newer compiler working.

Having problems getting the cross compiler working, but looking at the memory dump of the original problem, it does show that on the power pc platform, the complex value is not being passed by value. I put the example of the struct here to show that a 64 bit value can be pass through by value, on a 32 bit machine.

Marlowe answered 24/3, 2016 at 16:3 Comment(21)
This isn't the problem, but names that begin with an underscore followed by a capital letter (_MYLIB_H_) and names that contain two consecutive underscores are reserved to the implementation. Don't use them.Advisedly
@PeteBecker: Well, it can be, at least in C it could invoke UB by changing the stdlib's behaviour.Chrisoula
You could verify the ABIs (unlikely) and check the Assembler code to get a hint. In general, you should use the standard types.Chrisoula
typedef std::complex<float> cf32_t; inside an extern "C" block? That seems suspect.Huber
@AndrewHenle, as far as I am aware, a typedef is a typedef, C linkage or not. Personally, I would personally limit the scope of the extern "C" block to just the function prototypes for stylistic reasons, but I don't think the OP's way is wrong or has any different semantics.Sixpenny
Very nice first post, btw.Sixpenny
Have you tried creating the intended complex value on the power pc in your C-code section, and dumping the hex of the intended value and the value received as an input from the C++ section to see how they differ?Sandeesandeep
At first I supposed the C and C++ complex types must be incompatible, but that doesn't fly because you can successfully pass the complex object via a pointer. I'm now inclined to call "bug".Sixpenny
@John they are incompatible, one is a class template instance and the other is a built-in type and their argument passing conventions are not expected to be the same, even if binary layouts luckily coincide.Calv
I would read twice the documentation for the compiler (what is it BTW) for complex argument passing for C and C++. And you should use values that can be directly read in hexadecimal such as 1.5 and 2.25 and actually dump the stack to see what it really contains.Scharff
I agree with @n.m. If you want your C code and C++ code to interoperate, use the same type for both. The reason myComplex_t works is because it is the same type for both your C and C++ code.Planarian
This is only a problem when I try to run the code (built with gcc 4.7.1). on a 32 bit powerpc embedded platform. Passing the complex float works fine across a c++ to c border when on a x86_64 GNU/Linux platform compiled with gcc 4.8.5. I currently don't have access to other platforms so I can't verify if this happens on other platforms other than the power pc. I suspect it's isolated to the power pc because this seems like someone else would have ran across this problem before.Marlowe
I just discovered internally complex float is implemented as an array not a struct. I don't think that makes any difference in memory though. Other than it doesn't seem right to pass an array by value.Marlowe
Is this some common practice? Using the complex template class in C++, and passing its image off as a C99 complex in C module in the same program?Hannie
@Hannie Hopefully not !Deafening
If there is a C library that provides fast complex number calculations (using complex.h), how does a program written C++ (using <complex>) call into it? I'm thinking I should have just asked that in the first place now! lol The code I'm using does what I show in the example above, the oddity is that it works and I seem to be the first person to find a platform(powerPC e200v2) on which it doesn't work on. I was hoping someone would just say, oh you forgot to compile with the "-xyz option"Marlowe
@n.m. It's not so much an "even if binary layouts luckily coincide" as it is that "C++11 and later places requirements on std::complex<T> that can only be met if it has the same object representation, and thus binary layout, as the corresponding C type T _Complex."Doreendorelia
Specifically: "C++11 and later requires that: For any lvalue z of type cv std::complex<T>, reinterpret_cast<cv T(&)[2]>(z)[0] shall designate the real component of z, and reinterpret_cast<cv T(&)[2]>(z)[1] the imaginary component. For any cv std::complex<T>* a, where a[i] is well-defined, reinterpret_cast<cv T*>(a)[2*i] shall designate the real component of a[i], and reinterpret_cast<cv T*>(a)[2*i + 1] the imaginary component."Doreendorelia
"C requires that: each complex type T _Complex has the same representation and alignment requirements as an array T[2], designating the real and imaginary components, respectively."Doreendorelia
Therefore, both std::complex<T> and T _Complex must contain exactly two members of type T, designating the real and imaginary components, respectively, with the same layout as a T[2] array. (Referencing C++ § 26.4.4 ([complex.numbers/4]) and C § 6.2.5.13.)Doreendorelia
@Marlowe It honestly seems a bit strange that it doesn't work. GCC 4.7.1 isn't fully C++11 compliant, but I believe it had the proper layout for C++ complex types.Doreendorelia
D
4

Your code causes undefined behaviour. In the C++ unit the function is declared as:

extern "C" void procWithComplex(float a, std::complex<float> *pb, std::complex<float> c, float d);

but the function body is:

void procWithComplex(float a, complex float * pb, complex float  c, float d)

which does not match.

To help the compiler diagnose this error you should avoid using the preprocessor to switch in different prototypes for the same function.

To avoid this error you need to have the function prototype only use types which are valid in both C and C++. Such as you did in the myComplex_t example.

Deafening answered 25/3, 2016 at 2:19 Comment(0)
M
0

We ended up using a cross compiler vs the native compiler on the dev board to create the binaries. Apparently complex numbers are not handled properly across the C to C++ boundary for the native compiler we used.

All the changes suggested were tried and failed, but they were all still good suggestions. It helped confirm our thoughts that it might be a compiler problem which enabled us to try using a cross compiler. Thanks all!

Marlowe answered 1/6, 2016 at 0:9 Comment(1)
This is not the right solution. You are invoking UB but getting lucky, and your code could explode at any time.Anytime
G
0

I hope I'm not too late to the party! I've made a MWE project with a C/C++ interoperable type "qcomp" which resolves to the user's native compiler's type, i.e.

  • std::complex<double> as provided by C++'s <complex>
  • double _Complex as provided by C's <complex.h>

The "backend" is always compiled as a C++ binary, while the frontend can be interfaced by both C++ or C binaries, depending on the user's language. We avoid passing qcomp by-value between the C and C++ binaries, prohibited by the lack of an agreeing ABI.

In contrast to M.M's solution, this is not through creating a new C & C++ compatible type. Instead, we exploit that the C & C++ complex types are gauranteed (from C99 and C++11) to have the same layout. We can ergo pass pointers to qcomp between the binaries. So if our desired API passes qcomp by value, then we must create C-compatible wrappers which pass by pointers. Interestingly, this can all be done so as to provide an identical interface to C and C++ users, which access an API where qcomp resolves to their native complex type.

See the project here, or the architecture below:

simplified architecture

Guam answered 31/1 at 15:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.