Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix x86_64 ABI calling convention for structs with floats #105

Open
touchft opened this issue May 26, 2022 · 11 comments
Open

Fix x86_64 ABI calling convention for structs with floats #105

touchft opened this issue May 26, 2022 · 11 comments

Comments

@touchft
Copy link

touchft commented May 26, 2022

@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 #103 (comment)

@touchft touchft changed the title The generated binding struct cannot be applied to the binding function as input parameter The binding function cannot run correctly as expected with binding struct type ! May 27, 2022
@touchft
Copy link
Author

touchft commented May 27, 2022

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.

@krrutkow
Copy link
Member

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 krrutkow changed the title The binding function cannot run correctly as expected with binding struct type ! Fix x86_64 ABI calling convention for structs with floats May 27, 2022
@touchft
Copy link
Author

touchft commented May 27, 2022

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.

@krrutkow
Copy link
Member

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.

@touchft
Copy link
Author

touchft commented May 27, 2022

All right. Hope this bug will be fixed soon.

Thank you for this great package!

@touchft
Copy link
Author

touchft commented May 27, 2022

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

@krrutkow
Copy link
Member

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.

@touchft
Copy link
Author

touchft commented May 28, 2022

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
Copy link
Author

touchft commented May 30, 2022

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.

@krrutkow
Copy link
Member

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

@touchft
Copy link
Author

touchft commented May 30, 2022

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants