External Issue: Issues with Chapel-generated C headers for exported procs

24668, "twesterhout", "Issues with Chapel-generated C headers for exported procs", "2024-03-22T14:29:32Z"

I've been using a pattern where I first hand-write a C header file such as

// header.h
#pragma once
void foo(void);

and then use it for export procs instead of relying on the Chapel-generated header:

require "header.h";
export proc foo() { ... }

This has been causing me some trouble for a few reasons:

  • Extra work on my side.
  • Mismatch in the C and Chapel signatures leads to crashes, segfaults, or sometimes internal compiler errors.

@bradcray pointed out that I really shouldn't be doing this, and that I should instead share what's wrong with the Chapel-generated headers that's causing me to write my own.

What makes automatically generated headers unusable for me

Here's what Chapel currently generates for my library (I've removed some of the functions to limit the size of the code block):

#include "stdchpl.h"
#include "wctype.h"
#include "ctype.h"
#include "/nix/store/d63pi2kqlyp0wy6zncnip089qfjadz4g-lattice-symmetries-haskell-2.2.0-lib/include/lattice_symmetries_functions.h"
#include "Timing.h"
#include "ConcurrentQueue.h"
void chpl__init_BatchedOperator(int64_t _ln,
                                int32_t _fn);
void ls_chpl_matrix_vector_product_csr_i32_c128(int64_t numberRows,
                                                int64_t numberCols,
                                                int64_t numberNonZero,
                                                _complex128 * matrixElements,
                                                int32_t * rowOffsets,
                                                int32_t * colIndices,
                                                _complex128 * x,
                                                _complex128 * y,
                                                int64_t numTasks);
void chpl__init_CommonParameters(int64_t _ln,
                                 int32_t _fn);
void ls_chpl_matrix_vector_product_f64(ls_hs_operator * matrixPtr,
                                       int32_t numVectors,
                                       _real64 * xPtr,
                                       _real64 * yPtr);
void ls_chpl_extract_diag_c128(ls_hs_operator * matrixPtr,
                               chpl_external_array * diag);
void ls_chpl_display_timings(void);
void ls_chpl_experimental_axpy_c128(int64_t size,
                                    _real64 alpha_re,
                                    _real64 alpha_im,
                                    _complex128 * xs,
                                    _complex128 * ys,
                                    _complex128 * zs);
void chpl__init_Vector(int64_t _ln,
                       int32_t _fn);

So which parts cause trouble?

  1. The biggest issue is that all const annotations are gone. E.g. in Chapel the ls_chpl_extract_diag_c128 function is declared as
    export proc ls_chpl_extract_diag_c128(matrixPtr : c_ptrConst(ls_hs_operator),
                                          diag : c_ptr(chpl_external_array)) { ... }
    
    but the header declares the matrixPtr parameter as ls_hs_operator *.
  2. An include with an absolute path to the header makes it very difficult to distribute the library. It's possible to work around the problem with replacing all absolute paths in a script, so it's not as big a deal as 1.
  3. The header contains a few includes of the internal Chapel files. This means that the code using the library needs Chapel as a dependency even if the Chapel runtime is linked statically into the shared library. Again, I could probably remove these includes with sed, but that's an extra step.
  4. The _real64 type could probably just be a double, right? Or are there systems where double and _real64 are different?
  5. The _complex128 type is a bit trickier since _Complex double wouldn't be accepted in C++ and std::complex<double> wouldn't be accepted in C. So having a custom typedef makes sense (even though I'd probably prefer a compiler flag letting me specify my own), but I'm not sure how good a practice it is in C to make type names starting with an underscore and without an additional namespace prefix, visible to external code.
  6. The generated header #includes some internal headers which are not needed for the public interface. E.g. I don't think the "Timing.h" should appear in the header.