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

Attempt at static ccall #131

Open
mdmaas opened this issue May 25, 2023 · 16 comments
Open

Attempt at static ccall #131

mdmaas opened this issue May 25, 2023 · 16 comments

Comments

@mdmaas
Copy link

mdmaas commented May 25, 2023

Hi,

Even though this is well beyond my skill level I'm trying to get something like a "static ccall" working... This is actually my second attempt, after a year or so...

From the last time, I got the idea that a the way to implement a "static ccall" would be to replace ccall with something like @ptrcall, but we need to get the function's pointer somehow, and that is what StaticTools.dlopen does.

So I guess the minimal example should be something that compiles a function which contains StaticTools.dlopen... Does this sound reasonable?

Anyway, in the StaticTools docs I found a very small function dltime(), which could be worth trying to get to compile:

function dltime()
    lib = StaticTools.dlopen(c"libc.so")
    fp = StaticTools.dlsym(lib, c"time")
    t = @ptrcall fp(C_NULL::Ptr{Nothing})::Int
    printf(c"Result is: %i\n", t)
end

compile_executable(dltime, (), "./")

Edit: silly me, I was forgetting to link with -ldl... this works...

compile_executable(dltime, (), "./", cflags=`-ldl -L./`)

Anyway, I'll try to continue with a more complex example that passes some arguments to the c function...

@mdmaas
Copy link
Author

mdmaas commented May 25, 2023

Ok, here's a more elaborate example:

### Let's define a C function and compile it
using Libdl
C_code= """
       double mean(double a, double b) {
         return ( a + b ) / 2;
       }
       """
Clib = "clib"
open(`gcc -fPIC -O3 -xc -shared -o $(Clib * "." * Libdl.dlext) -`, "w") do f
    print(f, C_code)
end

# Now the real code

using StaticTools, StaticCompiler

function static_ccall(lib_path, func_name, a, b)
    lib = StaticTools.dlopen(lib_path)
    mean = StaticTools.dlsym(lib, func_name)
    ra, rb = Ref(a), Ref(b)
    GC.@preserve ra rb begin
        pa, pb = pointer_from_objref(ra), pointer_from_objref(rb)
        c = @ptrcall mean(pa::Ptr{Nothing}, pb::Ptr{Nothing}) :: Float64
    end
    return c
end


function test()
    lib_path = c"/home/martin/static_compiler_test/clib.so"
    func_name = c"mean"
    c = static_ccall(lib_path, func_name, 1.0, 2.0)
    printf(c"Result is %f:\n", c)
    return 0
end

test()

This doesn't even work (I get a segmentation fault), but if I replace StaticTools.dlopen and StaticTools.dlsym with Libdl.dlopen and Libdl.dlsym this does work. However, I won't be able then to statically compile.

Maybe I'm doing something wrong, or there is an issue with these functions from StaticTools I guess?

@chriselrod
Copy link
Contributor

Your mean takes its arguments by value, but by reference/pointer.

@vchuravy
Copy link
Contributor

You also don't need ptrcall ccall supports just calling a runtime pointer.

@mdmaas
Copy link
Author

mdmaas commented May 25, 2023

Your mean takes its arguments by value, but by reference/pointer.

Oh, that was silly. I guess Julia converted it under the hood so it managed to work anyway with StaticCompiler.dlopen.

I modified my calling function to:

function static_ccall(lib_path, func_name, a, b)
    lib = StaticCompiler.dlopen(lib_path)
    mean = StaticCompiler.dlsym(lib, func_name)
    c = @ptrcall mean(a::Float64, b::Float64) :: Float64
    return c
end

and it's still the same result: works with StaticCompiler.dlopen, doesn't work with StaticTools.dlopen (segfaults). Maybe @brenhinkeller can shed some light on this. I can try to look into StaticTools.dlopen, as well.

@mdmaas
Copy link
Author

mdmaas commented May 25, 2023

You also don't need ptrcall ccall supports just calling a runtime pointer.

Yes, but for what I know, code containing ccall can't be statically compiled, so what I thought was that we have to replaced it with something else (like a static_ccall), so I began running some experiments.

The point in this second test would be to compile it with

compile_executable(test, (), "./", cflags=`-ldl -L./`)

but I didn't even get there.

@mdmaas
Copy link
Author

mdmaas commented May 25, 2023

Oh, I'm just not being able to use StaticTools.dlopen properly, as it returns a null pointer, while StaticCompiler.dlopen does work.

@brenhinkeller
Copy link
Collaborator

brenhinkeller commented May 25, 2023 via email

@mdmaas
Copy link
Author

mdmaas commented May 25, 2023

Try with @.***, it might just work nowdays StaticCompiler actually doesn’t provide a dlopen` unless I’m much mistaken so that’s probably giving you Libdl dlopen

is that something you do on mac? I'm on Linux...

Yes, I thinkg StaticCompiler imports dlopen from Libdl... I just checked what Libdl.dlope does, and it is implemented in C, and esentially searches every possible path (including Julia's DL_LOAD_PATH, and every possible extension:

https://github.com/JuliaLang/julia/blob/01ddf80f18fc618e20df307945a9c19e74005270/src/dlload.c#L263

@mdmaas
Copy link
Author

mdmaas commented May 25, 2023

Hmm, judging from @brenhinkeller's MPI example, maybe @symbolcall and linking via cflags is preferable to dlopen and dlsym? I mean, we would leave the trouble of finding the shared library to the c compiler... as long as we have anything we need in LD_LIBRARY_PATH, that could work.

@brenhinkeller
Copy link
Collaborator

Oh wow sending from email really messed up the formatting on that.. I meant @ccall

@mdmaas
Copy link
Author

mdmaas commented May 25, 2023

Oh wow sending from email really messed up the formatting on that.. I meant @ccall

Lol.

So you guys say that somehow ccall now should work with StaticCompiler?

I tried to no sucess:

function ccall_time() 
    t = @ccall time(C_NULL::Ptr{Nothing})::Int
    printf(c"Result is: %i\n", t)
end
compile_executable(ccall_time, (), "./", cflags=`-ldl -L./`)

it compiles, but then it segfaults when run:

shell> ./ccall_time
/bin/bash: line 1: 13734 Segmentation fault      ( './ccall_time' )

On the other hand, I can compile a call to time with StaticTools.dlopen + @ptrcall:

function dltime()
    lib_path = c"libc.so"
    func_name = c"time"
    lib = StaticTools.dlopen(lib_path)
    fp = StaticTools.dlsym(lib, func_name)
    t = @ptrcall fp(C_NULL::Ptr{Nothing})::Int
    printf(c"Result is: %i\n", t)
end
compile_executable(dltime, (), "./", cflags=`-ldl -L./`)

shell> ./dltime
Result is: 1685051481

@brenhinkeller
Copy link
Collaborator

Hey, I said might :)

@brenhinkeller
Copy link
Collaborator

That actually works for me

julia> function ccall_time()
           t = @ccall time(C_NULL::Ptr{Nothing})::Int
           printf(c"Result is: %i\n", t)
       end
ccall_time (generic function with 1 method)

julia> compile_executable(ccall_time, (), "./", cflags=`-ldl -L./`)
"/Users/cbkeller/ccall_time"

shell> ./ccall_time
Result is: 1685051694

I wonder what the difference is?

@mdmaas
Copy link
Author

mdmaas commented May 26, 2023

Wow, that's interesting.

Something curious is that I get the segfault in line 1, so even if I add some inocent code before ccall, it fails:

function ccall_clock() 
    printf(c"Calling clock")
    t = @ccall clock(C_NULL::Ptr{Nothing})::Int
    printf(c"Result is: %i\n", t)
end
compile_executable(ccall_clock, (), "./")

shell> ./ccall_clock
/bin/bash: line 1: 23619 Segmentation fault      ( './ccall_clock' )

By the way, @symbolcall works nicely for me:

function sym_clock()
    t = @symbolcall clock(C_NULL::Ptr{Nothing})::Int
    printf(c"Result is: %i\n", t)
end
compile_executable(sym_clock, (), "./")

shell> ./sym_clock
Result is: 773

From the last time I tried this I was left with the idea that ccall wouldn't work, and had to be overriden perhaps via Mixtape. But aparently ccall is almost working now... maybe we should add some tests and let the CI run them different OSs and on the latest package banch?

@mdmaas
Copy link
Author

mdmaas commented May 26, 2023

I can also get "mean" to work with just symbolcall and a cflag to link to that library...

function test()
    a = 1.0
    b = 2.0
    c = @symbolcall mean(a::Float64, b::Float64) :: Float64
    printf(c"Result is %f:\n", c)
    return 0
end
# this works
compile_executable(test, (), "./", cflags=`-L . -lmean`)

(I renamed clib.so to libmean.so, and also had to set the env variable for library paths in order to run the executable).

@brenhinkeller
Copy link
Collaborator

brenhinkeller commented May 26, 2023 via email

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

4 participants