aarch64 + USE_64_BIT=1 fails to compile with gcc: NEON type mismatch in conflictMask16 (uint64x2_t -> int64x2_t) #80

Closed
opened 2026-08-16 18:49:25 +00:00 by weaselbot · 0 comments
Member

Defect

Building the library with USE_64_BIT=1 on aarch64/arm64 using gcc fails to
compile. The 64-bit NEON path of conflictMask16 passes a uint64x2_t (the
return type of vcgtq_s64) to vmovn_s64, which requires an int64x2_t. clang
accepts this via an implicit vector conversion, but gcc does not, so the build
aborts.

This code was added by the fix for #69 ("ARM NEON scan helpers truncate 64-bit
versions when USE_64_BIT=1"). That issue and its fix were validated with clang
on aarch64 (see the reproduction in #69), so the gcc breakage went unnoticed.

CI never compiles this path: the 64-bit-versions matrix runs on amd64/clang
(uses the AVX compare16/compare16_avx512, not conflictMask16, which is
guarded by #ifdef HAS_ARM_NEON), and the aarch64 matrices all use
USE_64_BIT=0 (the #else 32-bit path). So the aarch64 USE_64_BIT=1 branch
is entirely untested in CI.

Location

ConflictSet.cpp, conflictMask16, the #if USE_64_BIT arm, line 2138:

inline uint8x16_t conflictMask16(const InternalVersionT *vs,
                                 InternalVersionT readVersion) {
#if USE_64_BIT
  int64_t rv;
  memcpy(&rv, &readVersion, sizeof(rv));
  const auto rvVec = vdupq_n_s64(rv);
  int32x2_t r32[8];
  const auto *vsp = reinterpret_cast<const int64_t *>(vs);
  for (int j = 0; j < 8; ++j) {
    r32[j] = vmovn_s64(vcgtq_s64(vld1q_s64(vsp + 2 * j), rvVec)); // <-- line 2138
  }
  ...

vcgtq_s64 returns uint64x2_t; vmovn_s64 takes int64x2_t.

Reproduction

g++ --version   # g++ (GCC) 16.1.1, target aarch64-redhat-linux-gnu
g++ -std=c++20 -O2 -DUSE_64_BIT=1 -I include -I third_party/valgrind -I . \
    ConflictSet.cpp -shared -fPIC -o /tmp/libcs64.so -fsyntax-only

Result (exit code 1):

ConflictSet.cpp: In function 'uint8x16_t conflictMask16(const InternalVersionT*, InternalVersionT)':
ConflictSet.cpp:2138:23: note: use '-flax-vector-conversions' to permit conversions between vectors with differing element types or numbers of subparts
  2138 |     r32[j] = vmovn_s64(vcgtq_s64(vld1q_s64(vsp + 2 * j), rvVec));
ConflictSet.cpp:2138:33: error: cannot convert 'uint64x2_t' to 'int64x2_t'
  2138 |     r32[j] = vmovn_s64(vcgtq_s64(vld1q_s64(vsp + 2 * j), rvVec));
      |                        ^~~~~~~~~~~~~~~~~~~~~~~~~~~
      |                                 |
      |                                 uint64x2_t
arm_neon.h:4493:22: note: initializing argument 1 of 'int32x2_t vmovn_s64(int64x2_t)'

For comparison, the same file compiles cleanly on this aarch64 host in three
nearby configurations:

  • gcc, default (USE_64_BIT=0): g++ -std=c++20 -O2 -I include -I third_party/valgrind -I . ConflictSet.cpp -shared -fPIC -o /tmp/libcs0.so -fsyntax-only -> exit 0
  • clang, USE_64_BIT=1: clang++ -std=c++20 -O2 -DUSE_64_BIT=1 ... -> exit 0

So the failure is specific to the gcc + aarch64 + USE_64_BIT=1 combination,
isolated to the conflictMask16 #if USE_64_BIT branch.

Expected vs actual

  • Expected: USE_64_BIT=1 (a flag the project's own CI uses, just on amd64)
    builds on aarch64 with gcc, the platform's default system compiler on many
    Linux distributions (e.g. Fedora/RHEL aarch64 ship gcc as the default
    compiler, and the default gcc/aarch64 build of this repo already works).
  • Actual: the build fails with a hard type error.

Impact

Anyone on an aarch64 system who wants 64-bit versions (e.g. to support version
histories beyond 2^32, or to avoid the 2-billion version window) and builds
with gcc — the default compiler on that platform — cannot compile the library
at all when passing -DUSE_64_BIT=1. Because CI does not exercise the
aarch64 USE_64_BIT=1 path, this regression is not caught.

Suggested fix

Make the vector types explicit so the code is not dependent on lax vector
conversions. For example, reinterpret the comparison result before narrowing:

r32[j] = vmovn_s64(vreinterpretq_s64_u64(vcgtq_s64(vld1q_s64(vsp + 2 * j), rvVec)));

(Or narrow with vmovn_u64, which takes uint64x2_t, and reinterpret to
int32x2_t.) This keeps the generated code identical on clang while allowing
gcc to compile.

## Defect Building the library with `USE_64_BIT=1` on aarch64/arm64 using **gcc** fails to compile. The 64-bit NEON path of `conflictMask16` passes a `uint64x2_t` (the return type of `vcgtq_s64`) to `vmovn_s64`, which requires an `int64x2_t`. clang accepts this via an implicit vector conversion, but gcc does not, so the build aborts. This code was added by the fix for #69 ("ARM NEON scan helpers truncate 64-bit versions when USE_64_BIT=1"). That issue and its fix were validated with clang on aarch64 (see the reproduction in #69), so the gcc breakage went unnoticed. CI never compiles this path: the `64-bit-versions` matrix runs on **amd64/clang** (uses the AVX `compare16`/`compare16_avx512`, not `conflictMask16`, which is guarded by `#ifdef HAS_ARM_NEON`), and the aarch64 matrices all use `USE_64_BIT=0` (the `#else` 32-bit path). So the aarch64 `USE_64_BIT=1` branch is entirely untested in CI. ## Location `ConflictSet.cpp`, `conflictMask16`, the `#if USE_64_BIT` arm, line 2138: ```cpp inline uint8x16_t conflictMask16(const InternalVersionT *vs, InternalVersionT readVersion) { #if USE_64_BIT int64_t rv; memcpy(&rv, &readVersion, sizeof(rv)); const auto rvVec = vdupq_n_s64(rv); int32x2_t r32[8]; const auto *vsp = reinterpret_cast<const int64_t *>(vs); for (int j = 0; j < 8; ++j) { r32[j] = vmovn_s64(vcgtq_s64(vld1q_s64(vsp + 2 * j), rvVec)); // <-- line 2138 } ... ``` `vcgtq_s64` returns `uint64x2_t`; `vmovn_s64` takes `int64x2_t`. ## Reproduction ```sh g++ --version # g++ (GCC) 16.1.1, target aarch64-redhat-linux-gnu g++ -std=c++20 -O2 -DUSE_64_BIT=1 -I include -I third_party/valgrind -I . \ ConflictSet.cpp -shared -fPIC -o /tmp/libcs64.so -fsyntax-only ``` Result (exit code 1): ``` ConflictSet.cpp: In function 'uint8x16_t conflictMask16(const InternalVersionT*, InternalVersionT)': ConflictSet.cpp:2138:23: note: use '-flax-vector-conversions' to permit conversions between vectors with differing element types or numbers of subparts 2138 | r32[j] = vmovn_s64(vcgtq_s64(vld1q_s64(vsp + 2 * j), rvVec)); ConflictSet.cpp:2138:33: error: cannot convert 'uint64x2_t' to 'int64x2_t' 2138 | r32[j] = vmovn_s64(vcgtq_s64(vld1q_s64(vsp + 2 * j), rvVec)); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~ | | | uint64x2_t arm_neon.h:4493:22: note: initializing argument 1 of 'int32x2_t vmovn_s64(int64x2_t)' ``` For comparison, the same file compiles cleanly on this aarch64 host in three nearby configurations: - gcc, default (`USE_64_BIT=0`): `g++ -std=c++20 -O2 -I include -I third_party/valgrind -I . ConflictSet.cpp -shared -fPIC -o /tmp/libcs0.so -fsyntax-only` -> exit 0 - clang, `USE_64_BIT=1`: `clang++ -std=c++20 -O2 -DUSE_64_BIT=1 ...` -> exit 0 So the failure is specific to the gcc + aarch64 + `USE_64_BIT=1` combination, isolated to the `conflictMask16` `#if USE_64_BIT` branch. ## Expected vs actual - Expected: `USE_64_BIT=1` (a flag the project's own CI uses, just on amd64) builds on aarch64 with gcc, the platform's default system compiler on many Linux distributions (e.g. Fedora/RHEL aarch64 ship gcc as the default compiler, and the default gcc/aarch64 build of this repo already works). - Actual: the build fails with a hard type error. ## Impact Anyone on an aarch64 system who wants 64-bit versions (e.g. to support version histories beyond 2^32, or to avoid the 2-billion version window) and builds with gcc — the default compiler on that platform — cannot compile the library at all when passing `-DUSE_64_BIT=1`. Because CI does not exercise the aarch64 `USE_64_BIT=1` path, this regression is not caught. ## Suggested fix Make the vector types explicit so the code is not dependent on lax vector conversions. For example, reinterpret the comparison result before narrowing: ```cpp r32[j] = vmovn_s64(vreinterpretq_s64_u64(vcgtq_s64(vld1q_s64(vsp + 2 * j), rvVec))); ``` (Or narrow with `vmovn_u64`, which takes `uint64x2_t`, and reinterpret to `int32x2_t`.) This keeps the generated code identical on clang while allowing gcc to compile.
weaselbot was assigned by andrew 2026-08-17 00:23:39 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: weaselab/conflict-set#80