@code_typed for ccall

Hello everyone,
I was wondering if there was a way of checking what happens on Julia side when executing a ccall function. Consider the following code:

@code_typed ccall(("expm1", Base.Math.libm), Float64, (Float64,), 1.5)

This would give an error:

ERROR: UndefVarError: ccall not defined

Even if wrapping the ccall in a function, I don’t get many informations out:

function my_func()
       return ccall(("expm1", Base.Math.libm), Float64, (Float64,), 1.5)
end

@code_typed my_func()
CodeInfo(
2 1 ─ %1 = $(Expr(:foreigncall, :((Core.tuple)("expm1", Base.Math.libm)), Float64, svec(Float64), :(:ccall), 1, 1.5, 1.5))::Float64                                                                             │
  └──      return %1                                                                                                                                                                                            │
) => Float64

Digging through the source code, I could not find where an Expr(:foreigncall, ...) actually turns into Julia functions. I would like to find these, and precompile them for some calls into C libraries that I need, without having to execute ccall once per C function to compile all the Julia stuff, considering that all the C side is already compiled.

I hope it makes sense.
Thank you

What exactly do you want to know and at what level?

What information are you looking for? You should expect all the julia side conversion although since this is a really simple one there isn’t much to see there. The actually call is just a call, you don’t see more at this level for the same reason you don’t write much more complicated code when calling a C function in C.

It never does.

It’s unclear what you mean by this. ccall is compiled, there’s nothing about "execute ccall" or “compilie all the julia stuff”. In another word, you’ve seen all the “julia stuff” and the reason you didn’t see any is because there isn’t any, not because they are hidden.

I didn’t mean they were hidden, just that I was not finding them, if there were any. Consider calling @time ccall(("expm1", Base.Math.libm), Float64, (Float64,), 1.5) two times:

@time a = 1.0 #Just to compile all @time related stuff

@time ccall(("expm1", Base.Math.libm), Float64, (Float64,), 1.5)

@time ccall(("expm1", Base.Math.libm), Float64, (Float64,), 1.5)

On my machine, the first time the ccall is executed, the result is:

0.000010 seconds

The second time, the result is:

0.000001 seconds

All successive calls are as fast as the second one. This is why I was assuming there was some Julia compilation that was happening.

I found the problem when trying to precompile(tanh, (Float64,)). By analyzing the @code_typed of the tanh(Float64) function, I found that only two calls to external functions are made. These are to
$(Expr(:foreigncall, :((Core.tuple)("expm1", Base.Math.libm)), Float64, svec(Float64), :(:ccall), 1, :(%31), :(%31)))::Float64
and
$(Expr(:foreigncall, "llvm.pow.f64", Float64, svec(Float64, Float64), :(:llvmcall), 2, 2.0, -28.0, -28.0, 2.0))::Float64.
If running @time tanh(1.5), however, the first call is always much slower than the successive ones, despite the function being compiled (in fact, the same number of allocations are performed).

precompile(tanh, (Float64,))

@time tanh(1.5) #Result is 0.000036 seconds (5 allocations: 176 bytes)

@time tanh(1.5) #Result is 0.000004 seconds (5 allocations: 176 bytes)

None of those numbers are trustworthy. And the absense of allocation clearly shows that there is no compilation. Same for,

In any case, I don’t see your logic here. You should realize that. What you are suspecting is compilation not being complete. You should realize that if you suspect it is a compilation issue, there will be no way to avoid it unless you completely avoid the julia compiler. In another word, if it is the compilation you are worried about, you should not write julia code or look for any julia stuff, you should just write the code in C or assembly or any other language that does pass through the julia compiler.

A few more points,

Your @time ccall does not show anything about compilation after running since the two line are compiled completely independently.

Also, in most C program, the first call of a function is also slower.

Good to know that. I was only wondering if I was running in some compilation slowdown. I am happy to know that it wasn’t the case.

I am not trying to avoid the Julia compiler. I just tried to submit a minimal working example for something that I am working on that relies heavily on Julia’s features.

Would you say, then, that this could be the reason why the first call is slower than successive ones? Or could there be other non-compiler related reasons?

Hard to say, and that number is not trust worthy to this precision anyway.

As @yuyichao says, the foreigncall expression is as low-level as it gets on the Julia side. But just to give you a bit of information about what happens next: from there, codegen takes over and emits LLVM IR for that expression.

3 Likes