cImport and unreachable code

I’m trying to create bindings for simsimd. It’s a c project that uses templates to conditionally enable/disable features based on your system (CPU) architecture.

When I try to use @cImport, it fails to build because zig seems to be more strict about failing on unreachable code when analyzing the header and fails on things like this:

cimport.zig

pub fn _simsimd_flush_denormals() callconv(.c) c_int {
    return _simsimd_flush_denormals_arm();
    return 0;
}

(this is the result of a macro)

This fails with:

❯ zig build
✓ Found SimSIMD at: deps/simsimd (using precompiled library)
✓ Found SimSIMD at: deps/simsimd (using precompiled library)
✓ Found SimSIMD at: deps/simsimd (using precompiled library)
✓ Found SimSIMD at: deps/simsimd (using precompiled library)
install
└─ install test
   └─ zig test Debug native 2 errors
/Users/denislantsman/src/vector-lab/.zig-cache/o/7443eb6f125d1af63cbb46e4ddcd2d5d/cimport.zig:27059:5: error: unreachable code
    return 0;
    ^~~~~~~~
/Users/denislantsman/src/vector-lab/.zig-cache/o/7443eb6f125d1af63cbb46e4ddcd2d5d/cimport.zig:27058:5: note: control flow is diverted here
    return _simsimd_flush_denormals_arm();
    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/Users/denislantsman/src/vector-lab/.zig-cache/o/7443eb6f125d1af63cbb46e4ddcd2d5d/cimport.zig:27063:5: error: unreachable code
    return @as(c_uint, @bitCast(simsimd_cap_serial_k));
    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/Users/denislantsman/src/vector-lab/.zig-cache/o/7443eb6f125d1af63cbb46e4ddcd2d5d/cimport.zig:27062:5: note: control flow is diverted here
    return _simsimd_capabilities_arm();
    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
error: the following command failed with 2 compilation errors:
/opt/homebrew/Cellar/zig/0.14.1/bin/zig test /Users/denislantsman/src/vector-lab/.zig-cache/o/2fe0bc2c15c5adb50852358745c8bf0c/libsimsimd.a -ODebug -I /Users/denislantsman/src/vector-lab/deps/simsimd/include -I /Users/denislantsman/src/vector-lab/.zig-cache/o/efaed383166e26499e4003483132aafb -DSIMSIMD_DYNAMIC_DISPATCH=1 -DSIMSIMD_NATIVE_F16=1 -DSIMSIMD_NATIVE_BF16=1 -DSIMSIMD_TARGET_ARM=0 -Mroot=/Users/denislantsman/src/vector-lab/src/main.zig -lc --cache-dir /Users/denislantsman/src/vector-lab/.zig-cache --global-cache-dir /Users/denislantsman/.cache/zig --name test --zig-lib-dir /opt/homebrew/Cellar/zig/0.14.1/lib/zig/ --listen=-
Build Summary: 7/10 steps succeeded; 1 failed
install transitive failure
└─ install test transitive failure
   └─ zig test Debug native 2 errors
error: the following build command failed with exit code 1:
/Users/denislantsman/src/vector-lab/.zig-cache/o/4c3f4875aede89cea9a377d987ac4abe/build /opt/homebrew/Cellar/zig/0.14.1/bin/zig /opt/homebrew/Cellar/zig/0.14.1/lib/zig /Users/denislantsman/src/vector-lab /Users/denislantsman/src/vector-lab/.zig-cache /Users/denislantsman/.cache/zig --seed 0xaf8acb3a -Z85cdf69265616142

I’ve been looking around and I haven’t been able to find anything about how I might be able to get zig to ignore this unreachable code error. This is an external lib so I don’t want to mess with how the lib is built or the output artifact.

My solution has been to use extern instead of cImport for now:

// SimSIMD types
const simsimd_size_t = usize;
const simsimd_distance_t = f64;
const simsimd_metric_kind_t = c_uint;
const simsimd_datatype_t = c_uint;
const simsimd_capability_t = c_uint;

// SimSIMD function pointer types
const simsimd_metric_dense_punned_t = ?*const fn (?*const anyopaque, ?*const anyopaque, simsimd_size_t, [*c]simsimd_distance_t) callconv(.c) void;
const simsimd_kernel_punned_t = ?*const fn (?*anyopaque) callconv(.c) void;

// SimSIMD metric constants
const simsimd_metric_l2sq_k: c_int = 101;
const simsimd_metric_cos_k: c_int = 99;
const simsimd_metric_dot_k: c_int = 105;
const simsimd_metric_hamming_k: c_int = 104;

// SimSIMD datatype constants
const simsimd_datatype_f32_k: c_int = 2048;
const simsimd_datatype_f16_k: c_int = 4096;
const simsimd_datatype_bf16_k: c_int = 8192;
const simsimd_datatype_i8_k: c_int = 4;

// SimSIMD capability constants
const simsimd_cap_any_k: c_int = 2147483647;

// SimSIMD extern functions
extern fn simsimd_capabilities() simsimd_capability_t;
extern fn simsimd_find_kernel_punned(
    kind: simsimd_metric_kind_t,
    datatype: simsimd_datatype_t,
    supported: simsimd_capability_t,
    allowed: simsimd_capability_t,
    kernel_output: [*c]simsimd_kernel_punned_t,
    capability_output: [*c]simsimd_capability_t,
) void;

However, this isn’t ideal because it requires manually copying in certain values that are not exported from the c package and thus cannot be imported using extern (all the const values).

Is there a way to use @cImport here? Should I open an issue on zig?

Only use @cImport for headers. The implementation should be added to the module, in the build system.

C libraries often define inline functions in headers. How do you propose to work around that without vendoring every library?

2 Likes

yep, I think for portability it’s common to define header-only libraries. simsimd and usearch are two examples that I stumbled upon recently.

The header only libraries that I am familiar with always have a specific macro that needs to be defined so that the implementation part gets included, something like LIBRARYNAME_IMPLEMENTATION, otherwise they only define the header/declaration part, but not the implementation.

I think having something like that makes those libraries easier to use with Zig, because then you can do exactly what @LucasSantos91 described, only use translate-c/cImport on the header part.

Here is how raylib uses that to add raygui raylib/build.zig at 0dc4ba53dcc6ed2605c792647a33d58b12c23ee3 · raysan5/raylib · GitHub

looks like you are missing some defines to enable/disable platform specific code.

hmm, so the c code looks like this:

/**
 *  @brief  Function to flush @b denormalized numbers to zero to avoid performance penalties.
 *  @return 1 if the operation was successful, 0 otherwise.
 *
 *  When facing denormalized values Fused-Multiply-Add (FMA) operations can be up to 30x slower,
 *  as measured on Intel Sapphire Rapids: https://github.com/ashvardanian/ParallelReductionsBenchmark
 */
SIMSIMD_PUBLIC int _simsimd_flush_denormals(void) {
#if _SIMSIMD_TARGET_X86
    return _simsimd_flush_denormals_x86();
#endif // _SIMSIMD_TARGET_X86
#if _SIMSIMD_TARGET_ARM
    return _simsimd_flush_denormals_arm();
#endif // _SIMSIMD_TARGET_ARM
    return 0;
}

So I don’t think it’s that I’m missing any defines or whatever. I think the code is just relying on the c compiler being ok with unreachable code.

I also don’t see anything in here that allows me to not include the implementation / this part of the code SimSIMD/include/simsimd/simsimd.h at main · ashvardanian/SimSIMD · GitHub

In such cases, you can’t use @cImport. Add the inplementation in the build system, then manually specify the functions you want to use as extern:

extern fn _simsimd_flush_denormals() callconv(.c) c_int;
2 Likes