C and bits types confusion

question
ccall

#1

Hi,

I’m trying a direct conversion using a bits type, but it seems to go wrong. C side:

struct BitsClass
{
  double a;
  double b;
};

BitsClass make_bits(double a, double b) { return {a,b}; }
double get_a(BitsClass c) { return c.a; }
double get_b(BitsClass c) { return c.b; }

Julia side:

bitstype 128 BitsClass
make_bits(a,b) = ccall((:make_bits, _l_clib), BitsClass, (Float64,Float64), a, b)
get_a(c) = ccall((:get_a, _l_clib), Float64, (BitsClass,), c)
get_b(c) = ccall((:get_b, _l_clib), Float64, (BitsClass,), c)

Test:

@show c = make_bits(1., 2.)
@show get_a(c)
@show get_b(c)

Output:

c = make_bits(1.0,2.0) = BitsClass(0x0000000000000002000000010b9ae990)
get_a(c) = 0.0
get_b(c) = 3.865557261229787e-195

So it’s garbage… Is mapping between a bits type and C like this allowed? I know I could use an immutable with the same structure as the C struct, but let’s say for the sake of argument that the data should be opaque on the Julia side and I really want a bitstype, is it possible? Asking in the context of CxxWrap, where I’m trying to avoid boxing bits types like this.


#2

No.

Yes, this is what you should do.

No. The calling convention depends on the field types so it’s impossible to get it right without knowing the field types. It doesn’t matter (more or less, apart from alignments) of course if you are only passing pointers.


#3

OK, thanks, for enums it seems to work, is this supported?
C code:

enum CppEnum
{
  EnumValA,
  EnumValB
};

void print_enum(const CppEnum e) { std::cout << "got enum: " << e << std::endl; }
CppEnum get_enum_b() { return EnumValB; }

Julia code:

@enum CppEnum EnumValA=0 EnumValB=1
print_enum(enumvalue::CppEnum) = ccall((:print_enum, _l_clib), Void, (CppEnum,), enumvalue)
get_enum_b() = ccall((:get_enum_b, _l_clib), CppEnum, ())

print_enum(EnumValA)
@show get_enum_b()

Result, as expected:

got enum: 0
get_enum_b() = EnumValB::CppEnum = 1

#4

enum is not a struct. It’s simply a constant integer in C so you should just pass it as such. Note that the size of enum is actually implementation defined (unless explicitly specified in c++11) so matching with a julia enum may not work. Using a integer type with the same size as the one returned by clang should work.


#5

OK, can it also be a custom bits type with the same size instead of an integer type? The advantage of that would the strong typing, avoiding mistakes.


#6

You mean using a bitstype/enum to map to C enums? It should be fine currently as long as it’s not a subtype of AbstractFloat.


#7

Sort of, I was thinking of defining a bits type on the C side using jl_new_bitstype and making sure its size is exactly the same as the size of the C enum (I didn’t realize this was implementation defined, actually). The values themselves would also be populated on the C side, using jl_set_const I think (not tried yet). The advantage in the context of CxxWrap would be strongly typed enums with values set on the C++ side, ensuring consistency of the values even when the headers change.


#8

I also tried mapping an immutable directly, as follows:
C:

struct ImmutableDouble
{
  double value;
};

ImmutableDouble make_imm_double(const double d)
{
  ImmutableDouble immd;
  immd.value = d;
  std::cout << "Returning ImmutableDouble with value " << immd.value << std::endl;
  return immd;
}

Julia:

immutable ImmutableDouble
  value::Float64
end

make_imm_double(d::Float64) = ccall((:make_imm_double, l_clib), ImmutableDouble, (Float64,), d)

imdbl = make_imm_double(1.0)
@show imdbl.value

This seems to work everywhere (Linux gcc, OSX clang, Windows MSVC) except on mingw (Julia 0.5 and 0.6, mingw64 gcc 5.3 and 6.2) where it still gives garbage, output of the test:

Returning ImmutableDouble with value 1
imdbl.value = 9.26849808e-315

Am I still violating the ccall protocol here or is there a bug in either mingw or Julia?


#9

It looks like the correct c/julia code. I’m not sure how do mingw and msvc binaries interacts though.


#10

To clarify, the error happens with mingw Julia and mingw-compiled C code. It works with mingw Julia and MSVC compiled C-code.


#11

The compiler you uses to compile julia should not affect the ccall calling convention.

It’s the most likely that you are missing a calling convention specification. (cdecl/stdcall etc.) It’ll be very surprising if mingw uses a completely different/new calling convention on windows.


#12

This really seems to be a corner case, adding a second field in the struct works fine:

immutable ImmutableDouble
  a::Float64
  b::Float64
end

It seems use_sret in the abi code depends on the size of the type, but I have no idea if that’s relevant here.