Odd inlining behaviour with cglobal wrapper, IS THIS A BUG? What am I doing wrong?

Hi, I’m trying to make a convenience wrapper for fetching global variables from a C library. The basic wrapper function works, but multiple-dispatch variants don’t work unless I add some specific lines of debugging output.
Here’s the complete code with debug output that makes it work:

#const lib="/path/to/library/file.so"
function getCGlobal(s::Symbol, t)
    println("getCGlobal:", s, ", ",t)
    display(s)
    display(typeof(s))
    display(t)
    display(typeof(t))
    valptr=cglobal((s, lib),t);
    return unsafe_load(valptr);
end
getCGlobalInt(s::Symbol)=getCGlobal(s, Cint);
getCGlobalFloat(s::Symbol)=getCGlobal(s, Cfloat);

now for example I can run either variant to read a global integer in the C library (having already called a C library function that assigns something to this variable):

julia> tts=getCGlobal(:timetostop,Cint)
getCGlobal:timetostop, Int32
:timetostop
Symbol
Int32
DataType
0

julia> tts=getCGlobalInt(:timetostop)
getCGlobal:timetostop, Int32
:timetostop
Symbol
Int32
DataType
0

Now if I comment out any of the println or display lines, the main function still works but the type-specific method fails. For example here’s the output when I redefine the function wihtout the println statement:

julia> tts=getCGlobal(:timetostop,Cint)
:timetostop
Symbol
Int32
DataType
0

julia> tts=getCGlobalInt(:timetostop)
:timetostop
Symbol
Int32
DataType
ERROR: TypeError: in cglobal: first argument not a pointer or valid constant expression, expected Ptr, got a value of type Tuple{Symbol, String}
Stacktrace:
 [1] getCGlobal
   @ ./REPL[38]:6 [inlined]
 [2] getCGlobalInt(s::Symbol)
   @ Main ./REPL[39]:1
 [3] top-level scope
   @ REPL[42]:1

I’m completely at a loss as to why this doesn’t work. I’ve tried this on 2 different linux machines, one running 1.9.0 the other running 1.9.3 and it was the same. Can anyone reproduce this behaviour (sorry this example uses an in-house library but it should be the same for anything that exports global variables).
thanks for any advice
Graham

2 Likes

I never do C so I can’t really help, but I can tell you that this does not involve multiple dispatch at all, so you should highlight the inconsistent expected Ptr, got a value of type Tuple{Symbol, String} error instead. You have 3 functions getCGlobal, getCGlobalInt, and getCGlobalFloat, and each of them only have 1 method to dispatch to. Multiple dispatch refers to a function call selecting 1 out of a function’s multiple methods.

Cint and Cfloat are both instances of DataType, so the two getCGlobal calls have identical argument types and wouldn’t specialize (compile) the 1 getCGlobal method differently, either (1 function can have multiple methods, 1 method can be specialized/compiled in multiple ways). Well, there is an extra layer of nuance where input types (, functions, and variable arguments) are not automatically specialized on, including this case, so the method specialization’s recorded argument type is the abstract Type rather than the concrete DataType.

EDIT: I played with the functions in the REPL and while I obviously hit an error with the lack of library, I can replicate the same error you have, and I noticed that there’s a discrepancy in whether getCGlobal is inlined into the other functions. Basically, you commenting out that line made it short enough for the compiler to inline. So, try telling the compiler to stop that, make the edit to the header @noinline function getCGlobal(s::Symbol, t). Does the error go away then, I see it replaced by the lack of library error.

Yes, this appears to be a bug with respect to inlining of cglobal.
Namely, define either @noinline function getCGlobal(...) or @inline function getCGlobal(...) to toggle between the expected and erroneous behavior.

That one print statement coincidentally(?) seems to straddle the line between inlining and no inlining.

I can’t say more about the root cause of the issue though, because I don’t understand enough about it.

1 Like

good point, I’ve edited the title and tags to repoint the finger of suspicion.

Inlining would replace a call’s input variables with provided instances, in this case Cint. It doesn’t seem to matter if it’s called in the global scope or in a let block, but the error shows up if it’s called in another function, no inlining required.

julia> s, lib, t = :timetostop, "/path/to/library/file.so", Cint;

julia> cglobal((s, lib),t)
ERROR: could not load library "/path/to/library/file.so"
...
julia> cglobal((s, lib),Cint)
ERROR: could not load library "/path/to/library/file.so"
...
julia> let s=s, lib=lib, t=t; cglobal((s, lib),t) end
ERROR: could not load library "/path/to/library/file.so"
...
julia> let s=s, lib=lib; cglobal((s, lib),Cint) end
ERROR: could not load library "/path/to/library/file.so"
...
julia> ((s, lib, t) -> cglobal((s, lib),t) )(s, lib, t)
ERROR: could not load library "/path/to/library/file.so"
...
julia> ((s, lib) -> cglobal((s, lib),Cint) )(s, lib)
ERROR: TypeError: in cglobal: first argument not a pointer or valid constant expression, expected Ptr, got a value of type Tuple{Symbol, String}
...

Well, whatever the bug is (since I don’t think we can call this a “feature” of cglobal), the suggestion from @skleinbo to use @noinline appears to be the best workaround, so I’ve implemented that and moved on.
Thanks for the advice