CBinding.jl icon indicating copy to clipboard operation
CBinding.jl copied to clipboard

Fix x86_64 ABI calling convention for structs with floats

Open touchft opened this issue 3 years ago • 11 comments

@krrutkow I try to follow your instruction, but I get a problem. Please give a help ^_^.

testc.h file:

typedef struct
{
    float x;
    float y;
} complexx;

extern int add(int i, int j);
extern complexx op(complexx x);
extern complexx opp(complexx* x);

testc.c file:

#include "testc.h"

int add(int i, int j)
{
    int t;
    t = i + j;
    return t;
}

complexx opp(complexx* x)
{
    complexx t;
    t.x = x->x+1;
    t.y = x->y+1;    
    return t;
}

complexx op(complexx x)
{
    complexx t;
    t.x = x.x+1;
    t.y = x.y+1;    
    return t;
}

Julia code:

using CBinding
incdir =  dirname(@__DIR__) * "/src_c"
libdir =  dirname(@__DIR__) * "/src_c"
libname = "testc"
c`-I$(incdir) -L$(libdir) -l$(libname)`

c"""
    #include "testc.h"
"""j

Julia REPL:

julia> add(Cint(1),Cint(2))
3

julia> a = complexx(x=2,y=3)
var"(c\"complexx\")" (8-byte struct)
  x = 2.0f0
  y = 3.0f0

julia> op(a)
var"(c\"complexx\")" (8-byte struct)
  x = 1.0f0
  y = 1.0f0

julia> opp(Ref(a))
var"(c\"complexx\")" (8-byte struct)
  x = 3.0f0
  y = 4.0f0

Obviously, function 'op' does not works as expected. What's wrong with my code? Thank you!

Originally posted by @touchft in https://github.com/analytech-solutions/CBinding.jl/issues/103#issuecomment-1138152913

touchft avatar May 26 '22 14:05 touchft

And I make more testes with following code.

struct comp
    x::Cfloat
    y::Cfloat
end

b = comp(2,3)

function ccall_op(x::comp)
    return ccall((:op, libdir * "/" * libname * ".so"), comp, (comp,), x)
end

function ccall_op_1(x::complexx)
    return ccall((:op, libdir * "/" * libname * ".so"), complexx, (complexx,), x)
end

function ccall_op(x::complexx)
    return ccall((:op, libdir * "/" * libname * ".so"), comp, (complexx,), x)
end

ccall_op(b) works as expected, ccall_op(a) behaves the same as op(a) which results wrong. ccall_op_1(a) will result julia quit REPL.

touchft avatar May 27 '22 10:05 touchft

This looks like it is an ABI incompatibility issue. There is no issue if the struct contains int instead of float, and it appears that the x86_64 ABI describes the function calling convention placing structs with floats in a different part of memory than structs without them.

krrutkow avatar May 27 '22 11:05 krrutkow

This looks like it is an ABI incompatibility issue. There is no issue if the struct contains int instead of float, and it appears that the x86_64 ABI describes the function calling convention placing structs with floats in a different part of memory than structs without them.

Could you give any idea how to work around? I need badly to bind a C project with Julia. Thanks.

touchft avatar May 27 '22 13:05 touchft

It would be possible to work around this by passing the struct parameters by pointer instead (this is also the work around needed if using a packed struct, as described https://github.com/JuliaLang/julia/issues/45363#issuecomment-1133494014). This could be accomplished with a separate "shim" library or by using CBinding, such as:

c"""
static inline complexx ptr_op(complexx *x) { return op(*x); }
"""wj

Then (as you can see from your example) calling ptr_op(Ref(x)) works fine.

krrutkow avatar May 27 '22 13:05 krrutkow

All right. Hope this bug will be fixed soon.

Thank you for this great package!

touchft avatar May 27 '22 13:05 touchft

It would be possible to work around this by passing the struct parameters by pointer instead (this is also the work around needed if using a packed struct, as described JuliaLang/julia#45363 (comment)). This could be accomplished with a separate "shim" library or by using CBinding, such as:

c"""
static inline complexx ptr_op(complexx *x) { return op(*x); }
"""wj

Then (as you can see from your example) calling ptr_op(Ref(x)) works fine.

ptr_op(Ref(x)) break and make julia quit REPL!

Tested with Julia 1.7.3, Ubuntu 22.04

touchft avatar May 27 '22 14:05 touchft

CBinding#master has the fix to let op() be called from the inlined function. You will need to add -Wl,-rpath=$(libdir) to the compiler context though.

krrutkow avatar May 27 '22 15:05 krrutkow

CBinding#master has the fix to let op() be called from the inlined function. You will need to add -Wl,-rpath=$(libdir) to the compiler context though.

I took a try without success. I updated CBinding#master and the following compiler context is used:

c`-I$(incdir) -L$(libdir) -ltestc -Wl,-rpath=$(libdir)`

When I execute the code,

c"""
static inline complexx ptr_op(complexx * x) { return op(*x); }
"""wj

I get this error:

/usr/bin/x86_64-linux-gnu-ld: cannot find -ltestc: No such file or directory
clang-12: error: linker command failed with exit code 1 (use -v to see invocation)
ERROR: LoadError: failed process: Process(`/home/ft/.julia/artifacts/ebac8bb0527804a315b230ade306d81c03684e58/tools/clang -x c -Wno-empty-translation-unit -w -O2 -fPIC -shared -o /home/ft/.julia/scratchspaces/d43a6710-96b8-4a2d-833c-c424785e5374/2eb3b37c-da49-4f49-af2a-ad223b197d31/libcbinding-2.so /tmp/jl_XtwHgg -I/home/ft/workspace/julia/testCBinding/src_c -L/home/ft/workspace/julia/testCBinding/src_c -ltestc -Wl,-rpath=/home/ft/workspace/julia/testCBinding/src_c`, ProcessExited(1)) [1]

touchft avatar May 28 '22 03:05 touchft

CBinding#master has the fix to let op() be called from the inlined function. You will need to add -Wl,-rpath=$(libdir) to the compiler context though.

Seems not works for me.

touchft avatar May 30 '22 06:05 touchft

You are probably still using testc.so (which is fine with Julia, but not Clang) for your library file name instead of libtestc.so.

krrutkow avatar May 30 '22 10:05 krrutkow

You are probably still using testc.so (which is fine with Julia, but not Clang) for your library file name instead of libtestc.so.

You are great! Now it works after change the lib name to libtestc.so.

touchft avatar May 30 '22 12:05 touchft