Null function pointer in a function creaded with @eval

I am trying to modify AMD.jl to support Julia 0.7.0. This is a wrapper package to call C functions using ccall. The function pointers are defined as follows.

global const _libamd = Libdl.dlopen("libamd")
global const _amd_valid = Libdl.dlsym(_libamd, "amd_valid")
global const _amd_l_valid = Libdl.dlsym(_libamd, "amd_l_valid")

A main function amd_valid is defined with @eval to support different types.

for (validfn, typ) in ((_amd_valid, Cint), (_amd_l_valid, Clong))
  @eval begin
    function amd_valid(A :: SparseMatrixCSC{F,$typ}) where F
      nrow = $typ(size(A,1))
      ncol = $typ(size(A,2))
      colptr = A.colptr .- $typ(1)  # 0-based indexing
      rowval = A.rowval .- $typ(1)
      valid = ccall($validfn, $typ,
                    ($typ, $typ, Ptr{$typ}, Ptr{$typ}), nrow, ncol, colptr, rowval)
      return valid == AMD_OK || valid == AMD_OK_BUT_JUMBLED
    end
  end
end

Running a test, I got an error ERROR: LoadError: ccall: null function pointer. This function perfectly worked in Julia 0.6.0. When I replaced $validfn with Libdl.dlsym(Libdl.dlopen("libamd"), "amd_valid"), the function works without the error. It looks straightforward but a bit redundant.

What is the best way to modify this function? And if possible, please let me know why Julia 0.7 does not accept $validfn.

1 Like

Thanks for looking into this. I wrote AMD.jl and I’m stuck at the same place. I thought the issue might be related to https://github.com/JuliaLang/julia/issues/26557#issuecomment-374963443 but writing a BinDeps script to download and build the libamd from SuiteSparse led to the same error message.

@kristoffer.carlsson Any ideas?

That’s the point where I left https://github.com/JuliaSparse/Pardiso.jl/pull/36 yesterday :wink:

Not sure what the solution is.

Pasting the same code at the REPL does work though:

julia> libamd = "/Users/dpo/.julia/packages/AMD/k9NUS/deps/usr/lib/libamd.dylib";
julia> global const _libamd = Libdl.dlopen(libamd)
Ptr{Nothing} @0x00007fd7c4c136e0

julia> global const _amd_valid = Libdl.dlsym(_libamd, "amd_valid")
Ptr{Nothing} @0x00000001249ef020

julia> global const _amd_l_valid = Libdl.dlsym(_libamd, "amd_l_valid")
Ptr{Nothing} @0x00000001249f2ff0

julia> for (validfn, typ) in ((_amd_valid, Cint), (_amd_l_valid, Clong))
         @eval begin
           function amd_valid(A :: SparseMatrixCSC{F,$typ}) where F
             nrow, ncol = size(A)
             colptr = A.colptr .- $typ(1)  # 0-based indexing
             rowval = A.rowval .- $typ(1)
             valid = ccall($validfn, $typ,
                           ($typ, $typ, Ptr{$typ}, Ptr{$typ}), nrow, ncol, colptr, rowval)
             return valid == AMD_OK || valid == AMD_OK_BUT_JUMBLED
           end 
         end 
       end

julia> const AMD_OK = 0;              # success
julia> const AMD_OK_BUT_JUMBLED = 1;  # input ok but AMD will need to perform extra work
julia> amd_valid(sparse(rand(10,10)))
true

julia> amd_valid(convert(SparseMatrixCSC{Float64, Clong}, sparse(rand(10,10))))
true

One solution appears to be to enclose the library and symbols definitions in the module’s __init__. At least, that worked for AMD.jl.

Yes, pointers will not survive the serialization + deserialization roundtrip from precompilation. Either re-initialize them in __init__ or turn off precompilation.

Thank you for the solution and the explanation! I confirmed that AMD.jl in the master branch passed the tests in Julia 0.7.

1 Like

We have @abelsiqueira to thank for the solution!