Passing complex struct to C function results in segfault

I’m trying to interface with a C API for solving linear systems but I keep running into segfault errors. The algorithm uses a somewhat complex C structure, called LUSOLrec, to keep track of all the relevant data.

_LUSOLrec in C

typedef struct _LUSOLrec {

  /* Parameter storage arrays */
  int    luparm[LUSOL_IP_LASTITEM + 1];
  REAL   parmlu[LUSOL_RP_LASTITEM + 1];

  /* Arrays of length lena+1 */
  int    lena, nelem;
  int    *indc, *indr;
  REAL   *a;

  /* Arrays of length m+1 (row storage) */
  int    maxm, m;
  int    *lenr, *ip, *iqloc, *ipinv, *locr;

  /* Arrays of length n+1 (column storage) */
  int    maxn, n;
  int    *lenc, *iq, *iploc, *iqinv, *locc;
  REAL   *w, *vLU6L;

  /* Extra arrays of length n for TCP and keepLU == FALSE */
  REAL   *Ha, *diagU;
  int    *Hj, *Hk; 

  /* Extra arrays of length m for TRP*/
  REAL   *amaxr;

  /* Extra array for L0 stored by row for faster btran */
  LUSOLmat *L0;
  
  /* Miscellaneous data */
  int    expanded_a;
  int    replaced_c;
  int    replaced_r;

} LUSOLrec;

/* Sparse matrix data */
typedef struct _LUSOLmat {
  REAL *a;
  int  *lenx, *indr, *indc, *indx;
} LUSOLmat;

I converted this to a Julia struct so I could access the fields both in Julia and in C.

LUSOLrec in Julia

struct LUSOLrec 
    luparm::NTuple{33,Cint}
    parmlu::NTuple{21,Cdouble}

    # Arrays of length lena+1
    lena::Cint
    nelm::Cint
    
    indc::Array{Cint,1}
    indr::Array{Cint,1}
    a::Array{Cdouble,1}

    # Arrays of length m+1 (row storage)
    
    maxm::Cint
    m::Cint
    
    lenr::Array{Cint,1}
    ip::Array{Cint,1}
    iqloc::Array{Cint,1}
    ipinv::Array{Cint,1}
    locr::Array{Cint,1}

    # Arrays of length n+1 (column storage)
    
    maxn::Cint
    n::Cint
    lenc::Array{Cint,1}
    iq::Array{Cint,1}
    iploc::Array{Cint,1}
    iqinv::Array{Cint,1}
    locc::Array{Cint,1}
    
    w::Array{Cdouble,1}
    vLU6L::Array{Cdouble}

    # Extra arrays of length n for TCP and keepLU == False

    Ha::Array{Cdouble,1}
    diagU::Array{Cdouble,1}
    Hj::Array{Cint,1}
    Hk::Array{Cint,1}

    # Extra arrays of length m for TRP
    amaxr::Array{Cdouble,1}

    # Extra array for L0 stored by row for faster btran
    L0::Ptr{LUSOLmat}

    # Miscellaneous data
    expanded_a::Cint
    replaced_c::Cint
    replaced_r::Cint
end
struct LUSOLmat
    a::Array{Cdouble,1} 
    lenx::Array{Cint,1}
    indr::Array{Cint,1}
    indc::Array{Cint,1}
    indx::Array{Cint,1}
end

The API includes this function for assigning some of the fields of the LUSOLrec struct

LUSOL_assign function

MYBOOL LUSOL_assign(LUSOLrec *LUSOL, int iA[], int jA[], REAL Aij[], int nzcount, MYBOOL istriplet)
{
  int k, m, n, ij, kol;


  m = 0;
  n = 0;
  kol = 1;
  for(k = 0; k < nzcount; k++) {
    /* First the row indicator */
    ij = iA[k];
    LUSOL->indc[k] = ij;

    /* Then the column indicator;
       Handle both triplet and column count formats */
    if(istriplet)
      ij = jA[k];
    else {
      if(k >= jA[kol])
        kol++;
      ij = kol;
    }

    LUSOL->indr[k] = ij;

    /* Lastly the matrix value itself */
    LUSOL->a[k] = Aij[k];
  }
  LUSOL->m = m;
  LUSOL->n = n;
  LUSOL->nelem = nzcount;
  return( TRUE );

But when I try to call the above function in Julia I get a segfault. I’ve stepped through the function with gdb and it looks like everything is working as it should from within the function.

The code I’m using to run the function is

function lusol_assign(LUSOL::LUSOLrec, iA::Array{Cint,1}
                      ,jA::Array{Cint,1},Aij::Array{Cdouble,1},
                      nzcount::Integer,istriplet::Integer)

    output = ccall(plusol_assign,
                   Cuchar,
                   (Ref{LUSOLrec},Ptr{Cint},Ptr{Cint},Ptr{Cdouble},
                    Cint,Cuchar),
                   LUSOL, iA, jA, Aij, nzcount, istriplet)
    return output
end
                   

using SparseArrays
LUSOL = lusol_rec(5,5,1000)

A = sprand(5,5,.5)
(i,j,v) = findnz(A)
i = convert(Array{Cint,1},i)
j = convert(Array{Cint,1},j)
lusol_assign(LUSOL,i,j,v,length(i),1)

Where lusol_rec constructs the initial LUSOL object.

lusol_rec function

function lusol_rec(m,n,lena)
    luparm = tuple(zeros(Cint,33)...)
    parmlu = tuple(zeros(Cdouble, 21)...)

   
    # Arrays of length lena + 1
    lena = Cint(lena)
    nelm = 0

    indc = zeros(Cint,lena+1)
    indr = zeros(Cint,lena+1)
    a    = zeros(Cdouble,lena+1)

   
    # Arrays of length m+1
    maxm = m

    lenr = zeros(Cint,m+1)
    ip = zeros(Cint,m+1)
    iqloc = zeros(Cint,m+1)
    ipinv = zeros(Cint,m+1)
    locr = zeros(Cint,m+1)

    #Arrays of length n+1
    maxn = n

    lenc = zeros(Cint,n+1)
    iq = zeros(Cint,n+1)
    iploc = zeros(Cint,n+1)
    iqinv = zeros(Cint,n+1)
    locc = zeros(Cint,n+1)

    w = zeros(Cdouble, n+1)
    vLU6L = zeros(Cdouble, n+1)

    #Extra arrays of length n for TCP and keepLU = False
    Ha = zeros(Cdouble,n)
    diagU = zeros(Cdouble, n)
    Hj = zeros(Cint, n)
    Hk = zeros(Cint, n)

    # Extra arrays of length m for TRP
    amaxr = zeros(Cdouble, m)

    # Extra array for L) stored for faster btrain
    L0 = Ptr{LUSOLmat}(C_NULL)

    # Miscellaneous data
    expanded_a = Cint(0)
    expanded_c = Cint(0)
    replaced_r = Cint(0)
    return LUSOLrec(luparm,parmlu,lena,nelm,indc,indr,a,maxm,
                    m,lenr,ip,iqloc,ipinv,locr,maxn,n,lenc,
                    iq,iploc,iqinv,locc,w,vLU6L,Ha,diagU,Hj,
                    Hk,amaxr,L0,expanded_a,expanded_c,
                    replaced_r)
    
end

Does anyone have any idea why this code is causing a segfault? I’m pretty sure I converted the LUSOLrec structure to a Julia structure correctly and called the LUSOL_assign function correctly as well. I can even see that it looks like it’s working fine using gdb to step through the LUSOL_assign function. But at the very end when it completes the function I get a segfault anyways. What am I doing wrong?

1 Like

This is not equivalent to a C int * field.

3 Likes

To expand on stevengj’s comment, in order to pass structs to/from C you need to ensure they have exactly the same layout in Julia as in C. In your case, any fields like int* must be translated as Ptr{Cint} in Julia.

To populate this struct from Julia, you need to create arrays as you have and use pointer to extract a pointer suitable to insert into the struct. You need to ensure that the array itself is not garbage collected for as long as the pointer might be accessed, e.g. using GC.@preserve or storing it somewhere.

To access the data behind these pointers from Julia, you can use unsafe_load to get individual elements, or unsafe_wrap to interpret a block of memory as an array.

This is well documented here: Calling C and Fortran Code · The Julia Language.

Small plug: my package UnsafePointers.jl can make accessing complex nested structs like this much simpler.

5 Likes

Oh, I see. Thanks for the help!