Move aarch64 Node16 SIMD index loads into assembly #76

Merged
andrew merged 2 commits from weaselbot/conflict-set:weaselbot/issue-68 into main 2026-08-03 13:42:51 +00:00
Member

The aarch64 NEON paths in getNodeIndex/getNodeIndexExists, getChildGeq(Node16*), scan16, and checkMaxBetweenExclusiveImpl<Node16> loaded the full 16-element Node16::index array (and the Node48::reverseIndex array via scan16) with NEON intrinsics and then masked the result down to [0, numChildren). Only the in-use slots are initialized; the unused bytes are indeterminate, so the wide loads were undefined behavior in C++ ([basic.indet]) even though the trailing lanes were discarded. MSan reports this on x86-64; on aarch64 it is the same UB but MSan's imprecise modeling doesn't flag it at -O0, so there's no red->green test (as noted in the issue discussion).

This mirrors the existing x86-64 fix (commit 6fed133): implement the index operations in file-level assembly, where loading and operating on indeterminate values is well-defined (it is not UB in assembly).

  • Add simd_aarch64.S with find_eq_16, find_ge_16, and mask_in_range_16. AArch64 lacks pmovmskb, so (like the prior NEON code) these return a 64-bit "nibble mask" rather than a 16-bit bitmask; the C++ call sites keep their existing nibble-mask arithmetic and only swap the inline NEON load/compare for the assembly call.
  • The childMaxVersion compares stay in C++ NEON intrinsics, matching x86-64's compare16: those slots are always initialized to zero by the allocator, so the wide loads are defined.
  • The assembly functions are only ever called directly (never indirectly), so they carry no BTI landing pads; the object is still marked BTI/PAC/GCS-aware with the same aeabi_feature_and_bits attributes the compiler emits for -mbranch-protection=standard, keeping the binary BTI-enabled (and warning-free) under -z force-bti.
  • CMakeLists.txt builds simd_aarch64.S into the object library and the SIMD test/bench/fuzz targets on aarch64.

Verified on aarch64 (gcc 16 and clang 22): the new assembly matches the original NEON semantics over 2M randomized trials (including wrapping range edges), and the full ctest suite passes (8552/8552, excluding valgrind which doesn't support this CPU) for both the SIMD and USE_SIMD_FALLBACK builds, plus a Release build's symbol-visibility tests.

Closes #68

The aarch64 NEON paths in `getNodeIndex`/`getNodeIndexExists`, `getChildGeq(Node16*)`, `scan16`, and `checkMaxBetweenExclusiveImpl<Node16>` loaded the full 16-element `Node16::index` array (and the `Node48::reverseIndex` array via `scan16`) with NEON intrinsics and then masked the result down to `[0, numChildren)`. Only the in-use slots are initialized; the unused bytes are indeterminate, so the wide loads were undefined behavior in C++ ([basic.indet]) even though the trailing lanes were discarded. MSan reports this on x86-64; on aarch64 it is the same UB but MSan's imprecise modeling doesn't flag it at -O0, so there's no red->green test (as noted in the issue discussion). This mirrors the existing x86-64 fix (commit 6fed133): implement the index operations in file-level assembly, where loading and operating on indeterminate values is well-defined (it is not UB in assembly). - Add `simd_aarch64.S` with `find_eq_16`, `find_ge_16`, and `mask_in_range_16`. AArch64 lacks `pmovmskb`, so (like the prior NEON code) these return a 64-bit "nibble mask" rather than a 16-bit bitmask; the C++ call sites keep their existing nibble-mask arithmetic and only swap the inline NEON load/compare for the assembly call. - The `childMaxVersion` compares stay in C++ NEON intrinsics, matching x86-64's `compare16`: those slots are always initialized to zero by the allocator, so the wide loads are defined. - The assembly functions are only ever called directly (never indirectly), so they carry no BTI landing pads; the object is still marked BTI/PAC/GCS-aware with the same `aeabi_feature_and_bits` attributes the compiler emits for `-mbranch-protection=standard`, keeping the binary BTI-enabled (and warning-free) under `-z force-bti`. - `CMakeLists.txt` builds `simd_aarch64.S` into the object library and the SIMD test/bench/fuzz targets on aarch64. Verified on aarch64 (gcc 16 and clang 22): the new assembly matches the original NEON semantics over 2M randomized trials (including wrapping range edges), and the full `ctest` suite passes (8552/8552, excluding valgrind which doesn't support this CPU) for both the SIMD and `USE_SIMD_FALLBACK` builds, plus a Release build's symbol-visibility tests. Closes #68
weaselbot added 1 commit 2026-08-03 03:48:40 +00:00
Move aarch64 Node16 SIMD index loads into assembly
CI / release (arm64, , ubuntu-latest-arm64) (pull_request) Successful in 2m18s
CI / pre-commit (pull_request) Successful in 1m58s
CI / test (-DCMAKE_BUILD_TYPE=Debug -DMSAN_TOOLCHAIN_PATH=/opt/msan, debug) (pull_request) Successful in 3m47s
CI / test (-DCMAKE_CXX_FLAGS=-DUSE_64_BIT=1, 64-bit-versions) (pull_request) Successful in 3m16s
CI / test (-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, gcc) (pull_request) Successful in 3m12s
CI / test (-DUSE_SIMD_FALLBACK=ON, simd-fallback) (pull_request) Successful in 3m17s
CI / release (amd64, -DMSAN_TOOLCHAIN_PATH=/opt/msan, ubuntu-latest-amd64) (pull_request) Successful in 5m32s
CI / coverage (pull_request) Successful in 3m42s
d040ed63a4
The aarch64 NEON paths in getNodeIndex/getNodeIndexExists,
getChildGeq(Node16*), scan16, and checkMaxBetweenExclusiveImpl<Node16>
loaded the full 16-element Node16::index array (and the Node48
reverseIndex array via scan16) with NEON intrinsics, then masked the
result down to [0, numChildren). Only the in-use slots are initialized;
the unused bytes are indeterminate, so the wide loads were undefined
behavior in C++ (per [basic.indet]) even though the trailing lanes were
discarded. MSan reports this on x86-64; on aarch64 it is the same UB but
MSan's imprecise modeling doesn't flag it at -O0, so there is no red->green
test.

Mirror the existing x86-64 fix (commit 6fed133): implement the index
operations in file-level assembly, where loading and operating on
indeterminate values is well-defined. Add simd_aarch64.S with
find_eq_16, find_ge_16, and mask_in_range_16. AArch64 lacks pmovmskb, so
(like the prior NEON code) these return a 64-bit nibble mask rather than
a 16-bit bitmask; the C++ call sites keep their existing nibble-mask
arithmetic and only swap the inline NEON load/compare for the assembly
call. The childMaxVersion compares stay in C++ NEON intrinsics, matching
x86-64's compare16: those slots are always initialized to zero by the
allocator, so the wide loads are defined.

The assembly functions carry `bti c` landing pads and the same
aeabi_feature_and_bits attributes the compiler emits for
-mbranch-protection=standard, so the object stays BTI/PAC/GCS-compatible
(and warning-free under -z force-bti). CMakeLists.txt builds simd_aarch64.S
into the object library and the SIMD test/bench/fuzz targets on aarch64.

Closes #68
andrew requested changes 2026-08-03 12:23:27 +00:00
simd_aarch64.S Outdated
@@ -0,0 +1,82 @@
// SIMD operations on potentially-indeterminate Node16::index[16] bytes.
// Written in assembly so msan doesn't track the loads, and so that loading
Owner

It's not so msan doesn't track the loads, it's because it's not UB in asm

It's not so msan doesn't track the loads, it's because it's not UB in asm
@@ -0,0 +24,4 @@
.globl find_eq_16
.type find_eq_16, %function
find_eq_16:
hint 34 // bti c
Owner

Do we need these? They shouldn't ever be called indirectly

Do we need these? They shouldn't ever be called indirectly
weaselbot added 1 commit 2026-08-03 13:17:37 +00:00
Address review: drop unused bti c landing pads, fix rationale comment
CI / pre-commit (pull_request) Successful in 1m59s
CI / release (arm64, , ubuntu-latest-arm64) (pull_request) Successful in 2m14s
CI / test (-DCMAKE_BUILD_TYPE=Debug -DMSAN_TOOLCHAIN_PATH=/opt/msan, debug) (pull_request) Successful in 3m50s
CI / test (-DCMAKE_CXX_FLAGS=-DUSE_64_BIT=1, 64-bit-versions) (pull_request) Successful in 3m17s
CI / test (-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, gcc) (pull_request) Successful in 3m14s
CI / test (-DUSE_SIMD_FALLBACK=ON, simd-fallback) (pull_request) Successful in 3m16s
CI / release (amd64, -DMSAN_TOOLCHAIN_PATH=/opt/msan, ubuntu-latest-amd64) (pull_request) Successful in 5m33s
CI / coverage (pull_request) Successful in 3m44s
e02dd41f74
andrew scheduled this pull request to auto merge when all checks succeed 2026-08-03 13:19:08 +00:00
andrew merged commit b11e92ee0b into main 2026-08-03 13:42:51 +00:00
andrew deleted branch weaselbot/issue-68 2026-08-03 13:42:51 +00:00
Sign in to join this conversation.
No Reviewers
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: weaselab/conflict-set#76