CBinding.jl
CBinding.jl copied to clipboard
Fix x86_64 ABI calling convention for structs with floats
@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
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.
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.
This looks like it is an ABI incompatibility issue. There is no issue if the struct contains
intinstead offloat, 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.
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.
All right. Hope this bug will be fixed soon.
Thank you for this great package!
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); } """wjThen (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
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.
CBinding#masterhas the fix to letop()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]
CBinding#masterhas the fix to letop()be called from the inlined function. You will need to add-Wl,-rpath=$(libdir)to the compiler context though.
Seems not works for me.
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 probably still using
testc.so(which is fine with Julia, but not Clang) for your library file name instead oflibtestc.so.
You are great! Now it works after change the lib name to libtestc.so.