tshort / StaticCompiler.jl

Compiles Julia code to a standalone library (experimental)
Other
488 stars 31 forks source link

Attempt at static ccall #131

Open mdmaas opened 1 year ago

mdmaas commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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

vchuravy commented 1 year ago

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

mdmaas commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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

brenhinkeller commented 1 year ago

Try with @ccall, 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

mdmaas commented 1 year ago

Try with @.***, it might just work nowdays StaticCompiler actually doesn’t provide adlopen` 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 commented 1 year ago

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 commented 1 year ago

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

mdmaas commented 1 year ago

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 commented 1 year ago

Hey, I said might :)

brenhinkeller commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

Huh, interesting. Yeah, adding some ccall examples to the test suite seems like a good idea to me!