GC error / sefgault in a simple C wrapper

I am attempting to make a julia wrapper for a C library. The C library is already available as a jll. I was attempting to use Clang.jl to make the wrapper, but whatever I am doing seems to be causing segfaults on garbage collection.

Here is what Clang.jl generates for me (only the minimal amount of bindings to show the problem) and the segfault it causes:

I am wrapping the graph theory library igraph, in particular I am creating an empty struct igraph_t representing a graph, then initializing it to be the “4 nodes and 0 edges” graph by using igraph_empty.

# bindings generated by Clang.jl and selectively copied here by me
using igraph_jll

using CEnum

@cenum igraph_error_type_t::UInt32 begin
    IGRAPH_SUCCESS = 0
    IGRAPH_FAILURE = 1
    # ... skip a bunch of other options
end

const igraph_error_t = igraph_error_type_t

const igraph_integer_t = Int64

const igraph_bool_t = Bool

mutable struct igraph_i_property_cache_t end

mutable struct igraph_vector_int_t
    stor_begin::Ptr{igraph_integer_t}
    stor_end::Ptr{igraph_integer_t}
    _end::Ptr{igraph_integer_t}
    igraph_vector_int_t() = new()
end

mutable struct igraph_s
    n::igraph_integer_t
    directed::igraph_bool_t
    from::igraph_vector_int_t
    to::igraph_vector_int_t
    oi::igraph_vector_int_t
    ii::igraph_vector_int_t
    os::igraph_vector_int_t
    is::igraph_vector_int_t
    attr::Ptr{Cvoid}
    cache::Ptr{igraph_i_property_cache_t}
    igraph_s() = new()
end

const igraph_t = igraph_s

function igraph_empty(graph, n, directed)
    ccall((:igraph_empty, libigraph), igraph_error_t, (Ptr{igraph_t}, igraph_integer_t, igraph_bool_t), graph, n, directed)
end

##
# simple usage example that segfaults

a = igraph_s();
igraph_empty(pointer_from_objref(a), 4, false)
GC.gc() # segfaults

Any idea where I am going wrong? Introspecting some of the properties of a like a.n and a.directed gives me the expected values, but looking at a.to which should be storing the destination for each edge causes a segfault.

mutable struct igraph_vector_int_t
stor_begin::Ptr{igraph_integer_t}
stor_end::Ptr{igraph_integer_t}
_end::Ptr{igraph_integer_t}
igraph_vector_int_t() = new()
end

this should be

struct igraph_vector_int_t
    stor_begin::Ptr{igraph_integer_t}
    stor_end::Ptr{igraph_integer_t}
    _end::Ptr{igraph_integer_t}
end
1 Like

I don’t think Clang.jl will generate non-top-level structs as mutable. Did you misuse some configuration options?

1 Like

Thanks! That is indeed my fault. Could you help me understand better what is happening though (I thought I need these changes for the following reasons)?

I thought only mutable structs can be used with pointer_from_objref which itself is necessary to interface between julia and c functions acting on objects managed by julia. In particular, I want to be able to work with igraph_vector_int_t directly.

For instance, igraph has functions that use explicitly instances of both of these structs as input arguments: if I want to be able to wrap igraph_somefunction(::Ptr{igraph_t}, ::Ptr{igraph_vector_int_t}) I need a mutable igraph_vector_int_t, right? Is there a correct way to do that?

The igraph_t type wants the fields of type igraph_vector_int_t to be stored inline (cf. igraph/include/igraph_datatype.h at master · igraph/igraph · GitHub),
but declaring the julia type to be mutable prevents that. (Check the fieldoffsets of igraph_s and compare with sizeof(igraph_vector_int_t)).

Use Ref(...) instead of pointer_from_objref.

2 Likes

Thank you both, this has been very helpful!

1 Like