Compare commits

..
13 Commits
Author SHA1 Message Date
andrew cdbad1c013 Merge pull request 'Replace SIMD assembly with initialized Node16::index + valgrind client request' (#77) from client-request-instead-of-asm into main
CI / pre-commit (push) Successful in 2m1s
CI / test (arm64, -DCMAKE_BUILD_TYPE=Debug -DMSAN_TOOLCHAIN_PATH=/opt/msan, 22, https://minio.weaselab.dev/public/aarch64/msan-toolchain-22.1.8.tar.zst, debug-arm64, ubuntu-latest-arm64) (push) Successful in 3m46s
CI / test (amd64, -DCMAKE_BUILD_TYPE=Debug -DMSAN_TOOLCHAIN_PATH=/opt/msan, 21, https://minio.weaselab.dev/public/x86_64/msan-toolchain-21.1.8.tar.zst, debug, ubuntu-latest-amd64) (push) Successful in 3m50s
CI / release (arm64, , ubuntu-latest-arm64) (push) Successful in 3m18s
CI / test (amd64, -DCMAKE_CXX_FLAGS=-DUSE_64_BIT=1, 21, , 64-bit-versions, ubuntu-latest-amd64) (push) Successful in 3m19s
CI / test (amd64, -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, 21, , gcc, ubuntu-latest-amd64) (push) Successful in 3m18s
CI / test (amd64, -DUSE_SIMD_FALLBACK=ON, 21, , simd-fallback, ubuntu-latest-amd64) (push) Successful in 3m25s
CI / release (amd64, -DMSAN_TOOLCHAIN_PATH=/opt/msan, ubuntu-latest-amd64) (push) Successful in 5m50s
CI / coverage (push) Successful in 4m0s
Reviewed-on: #77
2026-08-04 00:07:19 +00:00
andrew 6d9810fe7f Poison Node48 reverseIndex slot on erase
CI / test (arm64, -DCMAKE_BUILD_TYPE=Debug -DMSAN_TOOLCHAIN_PATH=/opt/msan, 22, https://minio.weaselab.dev/public/aarch64/msan-toolchain-22.1.8.tar.zst, debug-arm64, ubuntu-latest-arm64) (push) Successful in 3m46s
CI / release (arm64, , ubuntu-latest-arm64) (push) Successful in 3m21s
CI / test (arm64, -DCMAKE_BUILD_TYPE=Debug -DMSAN_TOOLCHAIN_PATH=/opt/msan, 22, https://minio.weaselab.dev/public/aarch64/msan-toolchain-22.1.8.tar.zst, debug-arm64, ubuntu-latest-arm64) (pull_request) Successful in 3m50s
CI / release (arm64, , ubuntu-latest-arm64) (pull_request) Successful in 3m24s
CI / pre-commit (push) Successful in 2m5s
CI / test (amd64, -DCMAKE_BUILD_TYPE=Debug -DMSAN_TOOLCHAIN_PATH=/opt/msan, 21, https://minio.weaselab.dev/public/x86_64/msan-toolchain-21.1.8.tar.zst, debug, ubuntu-latest-amd64) (push) Successful in 3m56s
CI / test (amd64, -DCMAKE_CXX_FLAGS=-DUSE_64_BIT=1, 21, , 64-bit-versions, ubuntu-latest-amd64) (push) Successful in 3m23s
CI / test (amd64, -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, 21, , gcc, ubuntu-latest-amd64) (push) Successful in 3m31s
CI / test (amd64, -DUSE_SIMD_FALLBACK=ON, 21, , simd-fallback, ubuntu-latest-amd64) (push) Successful in 3m24s
CI / release (amd64, -DMSAN_TOOLCHAIN_PATH=/opt/msan, ubuntu-latest-amd64) (push) Successful in 5m46s
CI / coverage (push) Successful in 3m55s
CI / pre-commit (pull_request) Successful in 2m8s
CI / test (amd64, -DCMAKE_BUILD_TYPE=Debug -DMSAN_TOOLCHAIN_PATH=/opt/msan, 21, https://minio.weaselab.dev/public/x86_64/msan-toolchain-21.1.8.tar.zst, debug, ubuntu-latest-amd64) (pull_request) Successful in 3m53s
CI / test (amd64, -DCMAKE_CXX_FLAGS=-DUSE_64_BIT=1, 21, , 64-bit-versions, ubuntu-latest-amd64) (pull_request) Successful in 3m21s
CI / test (amd64, -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, 21, , gcc, ubuntu-latest-amd64) (pull_request) Successful in 3m20s
CI / test (amd64, -DUSE_SIMD_FALLBACK=ON, 21, , simd-fallback, ubuntu-latest-amd64) (pull_request) Successful in 3m20s
CI / release (amd64, -DMSAN_TOOLCHAIN_PATH=/opt/msan, ubuntu-latest-amd64) (pull_request) Successful in 5m46s
CI / coverage (pull_request) Successful in 4m5s
When a Node48 loses a child, the vacated reverseIndex slot would otherwise
remain defined, letting valgrind miss reads that rely on the stale value.
Mark it undefined, mirroring the existing poisoning of the vacated children
slot.
2026-08-03 17:38:21 -04:00
andrew bae2b22e95 Replace SIMD assembly with initialized Node16::index + valgrind client request
CI / test (arm64, -DCMAKE_BUILD_TYPE=Debug -DMSAN_TOOLCHAIN_PATH=/opt/msan, 22, https://minio.weaselab.dev/public/aarch64/msan-toolchain-22.1.8.tar.zst, debug-arm64, ubuntu-latest-arm64) (push) Successful in 3m44s
CI / release (arm64, , ubuntu-latest-arm64) (push) Successful in 3m19s
CI / test (arm64, -DCMAKE_BUILD_TYPE=Debug -DMSAN_TOOLCHAIN_PATH=/opt/msan, 22, https://minio.weaselab.dev/public/aarch64/msan-toolchain-22.1.8.tar.zst, debug-arm64, ubuntu-latest-arm64) (pull_request) Successful in 3m47s
CI / release (arm64, , ubuntu-latest-arm64) (pull_request) Successful in 3m26s
CI / pre-commit (push) Successful in 2m1s
CI / test (amd64, -DCMAKE_BUILD_TYPE=Debug -DMSAN_TOOLCHAIN_PATH=/opt/msan, 21, https://minio.weaselab.dev/public/x86_64/msan-toolchain-21.1.8.tar.zst, debug, ubuntu-latest-amd64) (push) Successful in 3m51s
CI / test (amd64, -DCMAKE_CXX_FLAGS=-DUSE_64_BIT=1, 21, , 64-bit-versions, ubuntu-latest-amd64) (push) Successful in 3m21s
CI / test (amd64, -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, 21, , gcc, ubuntu-latest-amd64) (push) Successful in 3m15s
CI / test (amd64, -DUSE_SIMD_FALLBACK=ON, 21, , simd-fallback, ubuntu-latest-amd64) (push) Successful in 3m18s
CI / release (amd64, -DMSAN_TOOLCHAIN_PATH=/opt/msan, ubuntu-latest-amd64) (push) Successful in 5m43s
CI / coverage (push) Successful in 4m8s
CI / pre-commit (pull_request) Successful in 2m16s
CI / test (amd64, -DCMAKE_BUILD_TYPE=Debug -DMSAN_TOOLCHAIN_PATH=/opt/msan, 21, https://minio.weaselab.dev/public/x86_64/msan-toolchain-21.1.8.tar.zst, debug, ubuntu-latest-amd64) (pull_request) Successful in 4m25s
CI / test (amd64, -DCMAKE_CXX_FLAGS=-DUSE_64_BIT=1, 21, , 64-bit-versions, ubuntu-latest-amd64) (pull_request) Successful in 3m49s
CI / test (amd64, -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, 21, , gcc, ubuntu-latest-amd64) (pull_request) Successful in 3m51s
CI / test (amd64, -DUSE_SIMD_FALLBACK=ON, 21, , simd-fallback, ubuntu-latest-amd64) (pull_request) Successful in 3m55s
CI / release (amd64, -DMSAN_TOOLCHAIN_PATH=/opt/msan, ubuntu-latest-amd64) (pull_request) Successful in 6m36s
CI / coverage (pull_request) Successful in 4m20s
Commit 6fed133 moved SIMD operations on Node16::index into file-level
assembly (later extended to aarch64) to avoid reading indeterminate bytes
(UB in C++). Revert that in favor of a simpler, architecture-independent
fix:

- Restore the original HAS_AVX / HAS_ARM_NEON intrinsics at the affected
  call sites, keeping the newer conflictMask16 helper and scalar fallback.
- memset Node16::index in both copy constructors (from Node3 and Node48) so
  the [numChildren, 16) tail is always initialized, making the full 16-byte
  SIMD loads well-defined. This also fixes the same latent UB in the NEON
  path and the Node16->Node16 full-array memcpy.
- Issue a VALGRIND_MAKE_MEM_UNDEFINED client request on the tail so valgrind
  still flags any read that relies on the unused slots rather than numChildren.
- Drop simd.h, simd_x86_64.S, simd_aarch64.S, and the CMake ASM wiring.
2026-08-03 17:32:41 -04:00
andrew b11e92ee0b Merge pull request 'Move aarch64 Node16 SIMD index loads into assembly' (#76) from weaselbot/conflict-set:weaselbot/issue-68 into main
CI / pre-commit (push) Successful in 2m2s
CI / test (arm64, -DCMAKE_BUILD_TYPE=Debug -DMSAN_TOOLCHAIN_PATH=/opt/msan, 22, https://minio.weaselab.dev/public/aarch64/msan-toolchain-22.1.8.tar.zst, debug-arm64, ubuntu-latest-arm64) (push) Successful in 3m46s
CI / test (amd64, -DCMAKE_BUILD_TYPE=Debug -DMSAN_TOOLCHAIN_PATH=/opt/msan, 21, https://minio.weaselab.dev/public/x86_64/msan-toolchain-21.1.8.tar.zst, debug, ubuntu-latest-amd64) (push) Successful in 3m54s
CI / release (arm64, , ubuntu-latest-arm64) (push) Successful in 3m22s
CI / test (amd64, -DCMAKE_CXX_FLAGS=-DUSE_64_BIT=1, 21, , 64-bit-versions, ubuntu-latest-amd64) (push) Successful in 3m23s
CI / test (amd64, -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, 21, , gcc, ubuntu-latest-amd64) (push) Successful in 3m17s
CI / test (amd64, -DUSE_SIMD_FALLBACK=ON, 21, , simd-fallback, ubuntu-latest-amd64) (push) Successful in 3m21s
CI / release (amd64, -DMSAN_TOOLCHAIN_PATH=/opt/msan, ubuntu-latest-amd64) (push) Successful in 5m42s
CI / coverage (push) Successful in 3m56s
Reviewed-on: #76
2026-08-03 13:42:45 +00:00
weaselbot e02dd41f74 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
2026-08-03 09:17:28 -04:00
andrew b0e326aacc Merge pull request 'ci: run MSan on arm64' (#74) from weaselbot/conflict-set:weaselbot/issue-73 into main
CI / test (arm64, -DCMAKE_BUILD_TYPE=Debug -DMSAN_TOOLCHAIN_PATH=/opt/msan, 22, https://minio.weaselab.dev/public/aarch64/msan-toolchain-22.1.8.tar.zst, debug-arm64, ubuntu-latest-arm64) (push) Successful in 3m47s
CI / release (arm64, , ubuntu-latest-arm64) (push) Successful in 3m22s
CI / pre-commit (push) Successful in 2m1s
CI / test (amd64, -DCMAKE_BUILD_TYPE=Debug -DMSAN_TOOLCHAIN_PATH=/opt/msan, 21, https://minio.weaselab.dev/public/x86_64/msan-toolchain-21.1.8.tar.zst, debug, ubuntu-latest-amd64) (push) Successful in 3m49s
CI / test (amd64, -DCMAKE_CXX_FLAGS=-DUSE_64_BIT=1, 21, , 64-bit-versions, ubuntu-latest-amd64) (push) Successful in 3m20s
CI / test (amd64, -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, 21, , gcc, ubuntu-latest-amd64) (push) Successful in 3m16s
CI / test (amd64, -DUSE_SIMD_FALLBACK=ON, 21, , simd-fallback, ubuntu-latest-amd64) (push) Successful in 3m20s
CI / release (amd64, -DMSAN_TOOLCHAIN_PATH=/opt/msan, ubuntu-latest-amd64) (push) Successful in 5m33s
CI / coverage (push) Successful in 3m49s
Reviewed-on: #74
2026-08-03 12:27:49 +00:00
andrew 1179c1f56e Merge pull request 'Fix ARM NEON scan helpers truncating 64-bit versions' (#75) from weaselbot/conflict-set:weaselbot/issue-69 into main
CI / pre-commit (push) Successful in 2m1s
CI / release (arm64, , ubuntu-latest-arm64) (push) Successful in 3m24s
CI / test (-DCMAKE_BUILD_TYPE=Debug -DMSAN_TOOLCHAIN_PATH=/opt/msan, debug) (push) Successful in 3m52s
CI / test (-DCMAKE_CXX_FLAGS=-DUSE_64_BIT=1, 64-bit-versions) (push) Successful in 3m20s
CI / test (-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, gcc) (push) Successful in 3m15s
CI / test (-DUSE_SIMD_FALLBACK=ON, simd-fallback) (push) Successful in 3m20s
CI / release (amd64, -DMSAN_TOOLCHAIN_PATH=/opt/msan, ubuntu-latest-amd64) (push) Successful in 5m36s
CI / coverage (push) Successful in 3m49s
Reviewed-on: #75
2026-08-03 12:26:03 +00:00
andrew bdbac1556d Merge pull request 'Correct rationale in simd_x86_64.S comment' (#72) from weaselbot/conflict-set:weaselbot/issue-71 into main
CI / pre-commit (push) Successful in 2m5s
CI / test (-DCMAKE_BUILD_TYPE=Debug -DMSAN_TOOLCHAIN_PATH=/opt/msan, debug) (push) Successful in 3m51s
CI / test (-DCMAKE_CXX_FLAGS=-DUSE_64_BIT=1, 64-bit-versions) (push) Successful in 3m19s
CI / test (-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, gcc) (push) Successful in 3m13s
CI / test (-DUSE_SIMD_FALLBACK=ON, simd-fallback) (push) Successful in 3m17s
CI / release (arm64, , ubuntu-latest-arm64) (push) Successful in 3m27s
CI / coverage (push) Successful in 3m54s
CI / release (amd64, -DMSAN_TOOLCHAIN_PATH=/opt/msan, ubuntu-latest-amd64) (push) Successful in 5m57s
Reviewed-on: #72
2026-08-03 04:59:26 +00:00
andrew 0103837488 Merge pull request 'Run valgrind in arm64 release in CI' (#70) from valgrind-in-arm into main
CI / release (arm64, , ubuntu-latest-arm64) (push) Successful in 3m19s
CI / pre-commit (push) Successful in 2m2s
CI / test (-DCMAKE_BUILD_TYPE=Debug -DMSAN_TOOLCHAIN_PATH=/opt/msan, debug) (push) Successful in 3m51s
CI / test (-DCMAKE_CXX_FLAGS=-DUSE_64_BIT=1, 64-bit-versions) (push) Successful in 3m18s
CI / test (-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, gcc) (push) Successful in 3m13s
CI / test (-DUSE_SIMD_FALLBACK=ON, simd-fallback) (push) Successful in 3m17s
CI / release (amd64, -DMSAN_TOOLCHAIN_PATH=/opt/msan, ubuntu-latest-amd64) (push) Successful in 5m38s
CI / coverage (push) Successful in 3m49s
Reviewed-on: #70
2026-08-03 04:34:34 +00:00
weaselbot d040ed63a4 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
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
2026-08-02 23:48:28 -04:00
weaselbot 6b1f597d77 Fix ARM NEON scan helpers truncating 64-bit versions
CI / release (arm64, , ubuntu-latest-arm64) (pull_request) Successful in 2m13s
CI / pre-commit (pull_request) Successful in 2m2s
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 3m15s
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 5m34s
CI / coverage (pull_request) Successful in 3m43s
When USE_64_BIT=1, InternalVersionT stores an int64_t, but the aarch64
NEON fast paths in scan16 (both the indexed Node48 variant and the
Node256 variant) and checkMaxBetweenExclusiveImpl(Node16) only copied the
low 32 bits of each version into uint32x4_t lanes and compared against a
truncated 32-bit readVersion. This produced wrong conflict/commit
decisions once any version exceeded 2^32 - 1.

Extract the per-version "greater than readVersion" computation into a
shared conflictMask16 helper. For 64-bit versions it uses vcgtq_s64 on
the full int64 values and narrows the resulting 16-byte mask into the
same nibble-packed layout the callers already consume. The 32-bit path is
preserved unchanged.

Closes #69
2026-08-02 23:35:32 -04:00
weaselbot 70630649d9 ci: run MSan on arm64
CI / test (arm64, -DCMAKE_BUILD_TYPE=Debug -DMSAN_TOOLCHAIN_PATH=/opt/msan, 22, https://minio.weaselab.dev/public/aarch64/msan-toolchain-22.1.8.tar.zst, debug-arm64, ubuntu-latest-arm64) (pull_request) Successful in 3m53s
CI / release (arm64, , ubuntu-latest-arm64) (pull_request) Successful in 2m36s
CI / pre-commit (pull_request) Successful in 2m0s
CI / test (amd64, -DCMAKE_BUILD_TYPE=Debug -DMSAN_TOOLCHAIN_PATH=/opt/msan, 21, https://minio.weaselab.dev/public/x86_64/msan-toolchain-21.1.8.tar.zst, debug, ubuntu-latest-amd64) (pull_request) Successful in 3m50s
CI / test (amd64, -DCMAKE_CXX_FLAGS=-DUSE_64_BIT=1, 21, , 64-bit-versions, ubuntu-latest-amd64) (pull_request) Successful in 3m18s
CI / test (amd64, -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, 21, , gcc, ubuntu-latest-amd64) (pull_request) Successful in 3m13s
CI / test (amd64, -DUSE_SIMD_FALLBACK=ON, 21, , simd-fallback, ubuntu-latest-amd64) (pull_request) Successful in 3m18s
CI / release (amd64, -DMSAN_TOOLCHAIN_PATH=/opt/msan, ubuntu-latest-amd64) (pull_request) Successful in 5m35s
CI / coverage (pull_request) Successful in 3m43s
Add a debug-arm64 matrix entry to the test job that runs the MSan
fuzz tests on an arm64 runner. The arm instrumented libc++ toolchain
(22.1.8) is built against LLVM 22, so this entry installs clang-22
from apt.llvm.org while the existing amd64 entries keep clang-21.

Generalize the test job matrix with runner/arch/llvm_version/msan_url
fields so the runner, apt cache key, LLVM toolchain version, and MSan
toolchain download URL are selected per matrix entry.

Closes #73
2026-08-02 23:28:40 -04:00
weaselbotandandrew 0b12a037c4 Correct rationale in simd_x86_64.S comment
CI / release (arm64, , ubuntu-latest-arm64) (pull_request) Successful in 2m14s
CI / pre-commit (pull_request) Successful in 1m59s
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 3m14s
CI / test (-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, gcc) (pull_request) Successful in 3m13s
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 5m27s
CI / coverage (pull_request) Successful in 3m42s
The SIMD operations are written in assembly because loading and
operating on indeterminate values is UB in C++ but well-defined in
assembly. msan not tracking the loads is a side effect, not the reason.
Closes #71
2026-08-03 02:37:18 +00:00
5 changed files with 178 additions and 207 deletions
+29 -7
View File
@@ -38,21 +38,43 @@ jobs:
matrix:
include:
- name: 64-bit-versions
runner: ubuntu-latest-amd64
arch: amd64
llvm_version: "21"
msan_url: ""
cmake_args: -DCMAKE_CXX_FLAGS=-DUSE_64_BIT=1
- name: debug
runner: ubuntu-latest-amd64
arch: amd64
llvm_version: "21"
msan_url: https://minio.weaselab.dev/public/x86_64/msan-toolchain-21.1.8.tar.zst
cmake_args: -DCMAKE_BUILD_TYPE=Debug -DMSAN_TOOLCHAIN_PATH=/opt/msan
- name: simd-fallback
runner: ubuntu-latest-amd64
arch: amd64
llvm_version: "21"
msan_url: ""
cmake_args: -DUSE_SIMD_FALLBACK=ON
- name: gcc
runner: ubuntu-latest-amd64
arch: amd64
llvm_version: "21"
msan_url: ""
cmake_args: -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++
runs-on: ubuntu-latest-amd64
- name: debug-arm64
runner: ubuntu-latest-arm64
arch: arm64
llvm_version: "22"
msan_url: https://minio.weaselab.dev/public/aarch64/msan-toolchain-22.1.8.tar.zst
cmake_args: -DCMAKE_BUILD_TYPE=Debug -DMSAN_TOOLCHAIN_PATH=/opt/msan
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v4
- uses: actions/cache@v4
with:
path: /var/cache/apt/archives
key: apt-amd64-${{ hashFiles('.gitea/workflows/ci.yml') }}
key: apt-${{ matrix.arch }}-${{ hashFiles('.gitea/workflows/ci.yml') }}
- name: Install common dependencies
run: |
@@ -68,18 +90,18 @@ jobs:
run: |
. /etc/os-release
wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc
echo "deb http://apt.llvm.org/${VERSION_CODENAME}/ llvm-toolchain-${VERSION_CODENAME}-21 main" | sudo tee /etc/apt/sources.list.d/llvm.list
echo "deb http://apt.llvm.org/${VERSION_CODENAME}/ llvm-toolchain-${VERSION_CODENAME}-${{ matrix.llvm_version }} main" | sudo tee /etc/apt/sources.list.d/llvm.list
sudo apt-get update -qq
sudo apt-get install -y \
clang-21 llvm-21 lld-21 mold
clang-${{ matrix.llvm_version }} llvm-${{ matrix.llvm_version }} lld-${{ matrix.llvm_version }} mold
for tool in clang clang++ llvm-ar llvm-nm llvm-ranlib llvm-objcopy llvm-cov llvm-symbolizer lld ld.lld; do
sudo update-alternatives --install /usr/bin/${tool} ${tool} /usr/bin/${tool}-21 100
sudo update-alternatives --install /usr/bin/${tool} ${tool} /usr/bin/${tool}-${{ matrix.llvm_version }} 100
done
- name: Download MSan toolchain
if: matrix.name == 'debug'
if: matrix.msan_url != ''
run: |
curl -Ls "https://minio.weaselab.dev/public/x86_64/msan-toolchain-21.1.8.tar.zst" -o /tmp/msan-toolchain.tar.zst
curl -Ls "${{ matrix.msan_url }}" -o /tmp/msan-toolchain.tar.zst
sudo mkdir -p /opt/msan
sudo tar --zstd -xf /tmp/msan-toolchain.tar.zst -C /opt/msan
+7 -18
View File
@@ -5,7 +5,7 @@ project(
DESCRIPTION
"A data structure for optimistic concurrency control on ranges of bitwise-lexicographically-ordered keys."
HOMEPAGE_URL "https://git.weaselab.dev/weaselab/conflict-set"
LANGUAGES C CXX ASM)
LANGUAGES C CXX)
set(CMAKE_CXX_STANDARD 20)
file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/version.txt ${PROJECT_VERSION})
@@ -130,15 +130,7 @@ endif()
set(CMAKE_CXX_IMPLICIT_LINK_LIBRARIES "")
# Architecture-specific SIMD assembly. These functions operate on
# potentially-indeterminate memory, which is UB in C++ but well-defined in
# assembly.
set(SIMD_ASM_FILES)
if(CMAKE_SYSTEM_PROCESSOR STREQUAL x86_64 AND NOT USE_SIMD_FALLBACK)
set(SIMD_ASM_FILES ${CMAKE_CURRENT_SOURCE_DIR}/simd_x86_64.S)
endif()
add_library(${PROJECT_NAME}-object OBJECT ConflictSet.cpp ${SIMD_ASM_FILES})
add_library(${PROJECT_NAME}-object OBJECT ConflictSet.cpp)
target_compile_options(${PROJECT_NAME}-object PRIVATE -fno-exceptions
-fvisibility=hidden)
target_include_directories(${PROJECT_NAME}-object
@@ -241,7 +233,7 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR AND BUILD_TESTING)
endif()
# ad hoc testing
add_executable(conflict_set_main ConflictSet.cpp ${SIMD_ASM_FILES})
add_executable(conflict_set_main ConflictSet.cpp)
target_include_directories(conflict_set_main
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_compile_definitions(conflict_set_main PRIVATE ENABLE_MAIN)
@@ -257,7 +249,7 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR AND BUILD_TESTING)
cmake_pop_check_state()
if(HAS_LIB_FUZZER)
add_executable(conflict_set_fuzz_test ConflictSet.cpp ${SIMD_ASM_FILES})
add_executable(conflict_set_fuzz_test ConflictSet.cpp)
target_include_directories(conflict_set_fuzz_test
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_compile_definitions(conflict_set_fuzz_test PRIVATE ENABLE_FUZZ)
@@ -269,8 +261,7 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR AND BUILD_TESTING)
endif()
# whitebox tests asan+ubsan
add_executable(fuzz_driver ConflictSet.cpp FuzzTestDriver.cpp
${SIMD_ASM_FILES})
add_executable(fuzz_driver ConflictSet.cpp FuzzTestDriver.cpp)
target_compile_options(fuzz_driver PRIVATE ${TEST_FLAGS})
if(NOT CMAKE_CROSSCOMPILING)
target_compile_options(fuzz_driver PRIVATE -fsanitize=address,undefined)
@@ -286,8 +277,7 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR AND BUILD_TESTING)
# whitebox tests msan
if(MSAN_TOOLCHAIN_PATH)
add_executable(fuzz_driver_msan ConflictSet.cpp FuzzTestDriver.cpp
${SIMD_ASM_FILES})
add_executable(fuzz_driver_msan ConflictSet.cpp FuzzTestDriver.cpp)
target_compile_options(fuzz_driver_msan PRIVATE ${TEST_FLAGS})
if(NOT CMAKE_CROSSCOMPILING)
target_compile_options(
@@ -315,8 +305,7 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR AND BUILD_TESTING)
# tsan tests
if(NOT CMAKE_CROSSCOMPILING AND NOT DISABLE_TSAN)
add_executable(tsan_driver ConflictSet.cpp FuzzTestDriver.cpp
${SIMD_ASM_FILES})
add_executable(tsan_driver ConflictSet.cpp FuzzTestDriver.cpp)
target_compile_options(tsan_driver PRIVATE ${TEST_FLAGS} -fsanitize=thread)
target_link_options(tsan_driver PRIVATE -fsanitize=thread)
target_compile_definitions(tsan_driver PRIVATE ENABLE_FUZZ THREAD_TEST)
+142 -72
View File
@@ -28,7 +28,6 @@ limitations under the License.
#include "Internal.h"
#include "LongestCommonPrefix.h"
#include "Metrics.h"
#include "simd.h"
#include <algorithm>
#include <bit>
@@ -489,6 +488,7 @@ inline void Node3::copyChildrenAndKeyFrom(const Node16 &other) {
inline void Node16::copyChildrenAndKeyFrom(const Node3 &other) {
copyCommon(*this, other);
memset(index, 0, sizeof(index));
memcpy(index, other.index, Node3::kMaxNodes);
memcpy(children, other.children,
Node3::kMaxNodes * sizeof(children[0])); // NOLINT
@@ -500,6 +500,12 @@ inline void Node16::copyChildrenAndKeyFrom(const Node3 &other) {
assert(children[i]->parent == &other);
children[i]->parent = this;
}
// The index bytes beyond numChildren are initialized so that the SIMD loads
// of the full 16 bytes are well-defined (reading indeterminate bytes is UB
// in C++). valgrind then treats them as undefined so that it can still flag
// any read that relies on them.
VALGRIND_MAKE_MEM_UNDEFINED(index + numChildren,
sizeof(index) - numChildren * sizeof(index[0]));
}
inline void Node16::copyChildrenAndKeyFrom(const Node16 &other) {
@@ -516,6 +522,7 @@ inline void Node16::copyChildrenAndKeyFrom(const Node16 &other) {
inline void Node16::copyChildrenAndKeyFrom(const Node48 &other) {
copyCommon(*this, other);
memset(index, 0, sizeof(index));
int i = 0;
other.bitSet.forEachSet([&](int c) {
// Suppress a false positive -Waggressive-loop-optimizations warning
@@ -529,6 +536,12 @@ inline void Node16::copyChildrenAndKeyFrom(const Node48 &other) {
++i;
});
memcpy(partialKey(), &other + 1, partialKeyLen);
// The index bytes beyond numChildren are initialized so that the SIMD loads
// of the full 16 bytes are well-defined (reading indeterminate bytes is UB
// in C++). valgrind then treats them as undefined so that it can still flag
// any read that relies on them.
VALGRIND_MAKE_MEM_UNDEFINED(index + numChildren,
sizeof(index) - numChildren * sizeof(index[0]));
}
inline void Node48::copyChildrenAndKeyFrom(const Node16 &other) {
@@ -536,6 +549,7 @@ inline void Node48::copyChildrenAndKeyFrom(const Node16 &other) {
assert(numChildren == Node16::kMaxNodes);
memset(index, -1, sizeof(index));
memset(children, 0, sizeof(children));
memset(reverseIndex, 0, sizeof(reverseIndex));
const auto z = InternalVersionT::zero;
for (auto &v : childMaxVersion) {
v = z;
@@ -555,6 +569,13 @@ inline void Node48::copyChildrenAndKeyFrom(const Node16 &other) {
std::max(maxOfMax[i >> Node48::kMaxOfMaxShift], childMaxVersion[i]);
++i;
}
// The reverseIndex bytes beyond numChildren are initialized so that the
// SIMD loads of a full 16-byte page in scan16 are well-defined (reading
// indeterminate bytes is UB in C++). valgrind then treats them as undefined
// so that it can still flag any read that relies on them.
VALGRIND_MAKE_MEM_UNDEFINED(reverseIndex + numChildren,
sizeof(reverseIndex) -
numChildren * sizeof(reverseIndex[0]));
}
inline void Node48::copyChildrenAndKeyFrom(const Node48 &other) {
@@ -581,6 +602,7 @@ inline void Node48::copyChildrenAndKeyFrom(const Node256 &other) {
copyCommon(*this, other);
memset(index, -1, sizeof(index));
memset(children, 0, sizeof(children));
memset(reverseIndex, 0, sizeof(reverseIndex));
const auto z = InternalVersionT::zero;
for (auto &v : childMaxVersion) {
v = z;
@@ -602,6 +624,13 @@ inline void Node48::copyChildrenAndKeyFrom(const Node256 &other) {
++i;
});
memcpy(partialKey(), &other + 1, partialKeyLen);
// The reverseIndex bytes beyond numChildren are initialized so that the
// SIMD loads of a full 16-byte page in scan16 are well-defined (reading
// indeterminate bytes is UB in C++). valgrind then treats them as undefined
// so that it can still flag any read that relies on them.
VALGRIND_MAKE_MEM_UNDEFINED(reverseIndex + numChildren,
sizeof(reverseIndex) -
numChildren * sizeof(reverseIndex[0]));
}
inline void Node256::copyChildrenAndKeyFrom(const Node48 &other) {
@@ -911,11 +940,34 @@ int getNodeIndexExists(Node3 *self, uint8_t index) {
int getNodeIndex(Node16 *self, uint8_t index) {
#if defined(__x86_64__) && !defined(USE_SIMD_FALLBACK)
uint32_t bitfield =
find_eq_16(self->index, index) & ((1 << self->numChildren) - 1);
#ifdef HAS_AVX
// Based on https://www.the-paper-trail.org/post/art-paper-notes/
// key_vec is 16 repeated copies of the searched-for byte, one for every
// possible position in child_keys that needs to be searched.
__m128i key_vec = _mm_set1_epi8(index);
// Compare all child_keys to 'index' in parallel. Don't worry if some of the
// keys aren't valid, we'll mask the results to only consider the valid ones
// below.
__m128i indices;
memcpy(&indices, self->index, Node16::kMaxNodes);
__m128i results = _mm_cmpeq_epi8(key_vec, indices);
// Build a mask to select only the first node->num_children values from the
// comparison (because the other values are meaningless)
uint32_t mask = (1 << self->numChildren) - 1;
// Change the results of the comparison into a bitfield, masking off any
// invalid comparisons.
uint32_t bitfield = _mm_movemask_epi8(results) & mask;
// No match if there are no '1's in the bitfield.
if (bitfield == 0)
return -1;
// Find the index of the first '1' in the bitfield by counting the leading
// zeros.
return std::countr_zero(bitfield);
#elif defined(HAS_ARM_NEON)
// Based on
@@ -948,9 +1000,13 @@ int getNodeIndex(Node16 *self, uint8_t index) {
int getNodeIndexExists(Node16 *self, uint8_t index) {
#if defined(__x86_64__) && !defined(USE_SIMD_FALLBACK)
uint32_t bitfield =
find_eq_16(self->index, index) & ((1 << self->numChildren) - 1);
#ifdef HAS_AVX
__m128i key_vec = _mm_set1_epi8(index);
__m128i indices;
memcpy(&indices, self->index, Node16::kMaxNodes);
__m128i results = _mm_cmpeq_epi8(key_vec, indices);
uint32_t mask = (1 << self->numChildren) - 1;
uint32_t bitfield = _mm_movemask_epi8(results) & mask;
assume(bitfield != 0);
return std::countr_zero(bitfield);
#elif defined(HAS_ARM_NEON)
@@ -1240,9 +1296,13 @@ TaggedNodePointer getChildGeq(Node16 *self, int child) {
return nullptr;
}
#if defined(__x86_64__) && !defined(USE_SIMD_FALLBACK)
uint32_t bitfield =
find_ge_16(self->index, child) & ((1 << self->numChildren) - 1);
#ifdef HAS_AVX
__m128i key_vec = _mm_set1_epi8(child);
__m128i indices;
memcpy(&indices, self->index, Node16::kMaxNodes);
__m128i results = _mm_cmpeq_epi8(key_vec, _mm_min_epu8(key_vec, indices));
int mask = (1 << self->numChildren) - 1;
uint32_t bitfield = _mm_movemask_epi8(results) & mask;
return bitfield == 0 ? nullptr : self->children[std::countr_zero(bitfield)];
#elif defined(HAS_ARM_NEON)
uint8x16_t indices;
@@ -1965,6 +2025,8 @@ Node *erase(Node *self, WriteContext *writeContext, bool logical) {
parent48->childMaxVersion[lastChildrenIndex] = writeContext->zero;
VALGRIND_MAKE_MEM_UNDEFINED(parent48->children + lastChildrenIndex,
sizeof(parent48->children[0]));
VALGRIND_MAKE_MEM_UNDEFINED(parent48->reverseIndex + lastChildrenIndex,
sizeof(parent48->reverseIndex[0]));
if (needsDownsize(parent48)) {
downsize(parent48, writeContext);
@@ -2057,6 +2119,52 @@ compare16_avx512(const InternalVersionT *vs, InternalVersionT rv) {
}
#endif
#ifdef HAS_ARM_NEON
// Returns a uint8x16_t whose byte i is 0xff if vs[i] > readVersion and 0
// otherwise. When USE_64_BIT is set the comparison uses the full 64-bit value
// (via vcgtq_s64); otherwise it falls back to the 32-bit subtract-and-compare
// used by the original NEON fast path. The caller narrows this 16-byte vector
// into the nibble-packed (4 bits per version) mask expected by scan16 and
// checkMaxBetweenExclusiveImpl.
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));
}
uint32x4_t w4[4];
for (int k = 0; k < 4; ++k) {
w4[k] = vreinterpretq_u32_s32(vcombine_s32(r32[2 * k], r32[2 * k + 1]));
}
return vcombine_u8(
vmovn_u16(vcombine_u16(vmovn_u32(w4[0]), vmovn_u32(w4[1]))),
vmovn_u16(vcombine_u16(vmovn_u32(w4[2]), vmovn_u32(w4[3]))));
#else
uint32x4_t w4[4];
memcpy(w4, vs, sizeof(w4));
uint32_t rv;
memcpy(&rv, &readVersion, sizeof(rv));
const auto rvVec = vdupq_n_u32(rv);
int32x4_t z;
memset(&z, 0, sizeof(z));
uint16x4_t conflicting[4];
for (int i = 0; i < 4; ++i) {
conflicting[i] =
vmovn_u32(vcgtq_s32(vreinterpretq_s32_u32(vsubq_u32(w4[i], rvVec)), z));
}
return vcombine_u8(vmovn_u16(vcombine_u16(conflicting[0], conflicting[1])),
vmovn_u16(vcombine_u16(conflicting[2], conflicting[3])));
#endif
}
#endif
// Returns true if v[i] <= readVersion for all i such that begin <= is[i] < end
// Preconditions: begin <= end, end - begin < 256
template <bool kAVX512>
@@ -2077,32 +2185,20 @@ bool scan16(const InternalVersionT *vs, const uint8_t *is, int begin, int end,
uint64_t mask = vget_lane_u64(
vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(results), 4)), 0);
uint32x4_t w4[4];
memcpy(w4, vs, sizeof(w4));
uint32_t rv;
memcpy(&rv, &readVersion, sizeof(rv));
const auto rvVec = vdupq_n_u32(rv);
int32x4_t z;
memset(&z, 0, sizeof(z));
uint16x4_t conflicting[4];
for (int i = 0; i < 4; ++i) {
conflicting[i] =
vmovn_u32(vcgtq_s32(vreinterpretq_s32_u32(vsubq_u32(w4[i], rvVec)), z));
}
auto combined =
vcombine_u8(vmovn_u16(vcombine_u16(conflicting[0], conflicting[1])),
vmovn_u16(vcombine_u16(conflicting[2], conflicting[3])));
uint64_t compared = vget_lane_u64(
vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(combined), 4)), 0);
vreinterpret_u64_u8(vshrn_n_u16(
vreinterpretq_u16_u8(conflictMask16(vs, readVersion)), 4)),
0);
return !(compared & mask);
#elif defined(__x86_64__) && !defined(USE_SIMD_FALLBACK)
#elif defined(HAS_AVX)
uint32_t mask = mask_in_range_16(is, begin, end);
__m128i indices;
memcpy(&indices, is, 16);
indices = _mm_sub_epi8(indices, _mm_set1_epi8(begin));
uint32_t mask = ~_mm_movemask_epi8(_mm_cmpeq_epi8(
indices, _mm_max_epu8(indices, _mm_set1_epi8(end - begin))));
uint32_t compared = 0;
if constexpr (kAVX512) {
@@ -2142,26 +2238,10 @@ bool scan16(const InternalVersionT *vs, int begin, int end,
assert(begin <= end);
#if defined(HAS_ARM_NEON)
uint32x4_t w4[4];
memcpy(w4, vs, sizeof(w4));
uint32_t rv;
memcpy(&rv, &readVersion, sizeof(rv));
const auto rvVec = vdupq_n_u32(rv);
int32x4_t z;
memset(&z, 0, sizeof(z));
uint16x4_t conflicting[4];
for (int i = 0; i < 4; ++i) {
conflicting[i] =
vmovn_u32(vcgtq_s32(vreinterpretq_s32_u32(vsubq_u32(w4[i], rvVec)), z));
}
auto combined =
vcombine_u8(vmovn_u16(vcombine_u16(conflicting[0], conflicting[1])),
vmovn_u16(vcombine_u16(conflicting[2], conflicting[3])));
uint64_t conflict = vget_lane_u64(
vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(combined), 4)), 0);
vreinterpret_u64_u8(vshrn_n_u16(
vreinterpretq_u16_u8(conflictMask16(vs, readVersion)), 4)),
0);
conflict &= end == 16 ? -1 : (uint64_t(1) << (end << 2)) - 1;
conflict >>= begin << 2;
@@ -2275,33 +2355,23 @@ bool checkMaxBetweenExclusiveImpl(Node16 *n, int begin, int end,
const bool firstRangeOk =
!child->entryPresent || child->entry.rangeVersion <= readVersion;
uint32x4_t w4[4];
memcpy(w4, self->childMaxVersion, sizeof(w4));
uint32_t rv;
memcpy(&rv, &readVersion, sizeof(rv));
const auto rvVec = vdupq_n_u32(rv);
int32x4_t z;
memset(&z, 0, sizeof(z));
uint16x4_t conflicting[4];
for (int i = 0; i < 4; ++i) {
conflicting[i] =
vmovn_u32(vcgtq_s32(vreinterpretq_s32_u32(vsubq_u32(w4[i], rvVec)), z));
}
auto combined =
vcombine_u8(vmovn_u16(vcombine_u16(conflicting[0], conflicting[1])),
vmovn_u16(vcombine_u16(conflicting[2], conflicting[3])));
uint64_t compared = vget_lane_u64(
vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(combined), 4)), 0);
vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(conflictMask16(
self->childMaxVersion, readVersion)),
4)),
0);
return !(compared & mask) && firstRangeOk;
#elif defined(__x86_64__) && !defined(USE_SIMD_FALLBACK)
#elif defined(HAS_AVX)
uint32_t mask = mask_in_range_16(self->index, begin, end) &
((1 << self->numChildren) - 1);
__m128i indices;
memcpy(&indices, self->index, 16);
indices = _mm_sub_epi8(indices, _mm_set1_epi8(begin));
uint32_t mask =
0xffff & ~_mm_movemask_epi8(_mm_cmpeq_epi8(
indices, _mm_max_epu8(indices, _mm_set1_epi8(end - begin))));
mask &= (1 << self->numChildren) - 1;
if (!mask) {
return true;
}
-25
View File
@@ -1,25 +0,0 @@
#pragma once
#include <cstdint>
#if defined(__x86_64__) && !defined(USE_SIMD_FALLBACK)
// SIMD operations on potentially-indeterminate Node16::index[16] bytes.
// Implemented in file-level assembly (simd_x86_64.S) because loading and
// operating on indeterminate values is UB in C++ but well-defined in
// assembly. The caller must mask the returned bitfield to
// [0, numChildren) before using it.
//
// Each function returns a 16-bit bitmask in the low 16 bits of a uint32_t
// (upper 16 bits are zero). Bit i is set iff the condition holds at index i.
extern "C" {
// Returns bit i set iff idx[i] == key
uint32_t find_eq_16(const uint8_t idx[16], uint8_t key);
// Returns bit i set iff idx[i] >= child
uint32_t find_ge_16(const uint8_t idx[16], uint8_t child);
// Returns bit i set iff begin <= idx[i] < end
uint32_t mask_in_range_16(const uint8_t idx[16], uint8_t begin, uint8_t end);
}
#endif
-85
View File
@@ -1,85 +0,0 @@
// SIMD operations on potentially-indeterminate Node16::index[16] bytes.
// Written in assembly so msan doesn't track the loads. The caller is
// responsible for masking the returned bitfield to [0, numChildren) before
// using it.
//
// All functions return a 16-bit bitmask in %eax (bit i set = condition true
// at index i). The upper 16 bits of %eax are zero.
//
// System V AMD64 ABI:
// %rdi = const uint8_t *idx (16 bytes)
// %esi = uint8_t key (find_eq_16, find_ge_16)
// %sil = uint8_t begin (mask_in_range_16)
// %dl = uint8_t end (mask_in_range_16)
.text
// uint32_t find_eq_16(const uint8_t idx[16], uint8_t key)
// Returns bit i set if idx[i] == key
.globl find_eq_16
.type find_eq_16, @function
find_eq_16:
vmovd %esi, %xmm1 // broadcast key
vpbroadcastb %xmm1, %xmm1
vmovdqu (%rdi), %xmm0 // load 16 bytes (may contain indeterminate data)
vpcmpeqb %xmm0, %xmm1, %xmm0 // 0xff for each match
vpmovmskb %xmm0, %eax // 16-bit bitmask
movzwl %ax, %eax // zero-extend to 32 bits
ret
.size find_eq_16, .-find_eq_16
// uint32_t find_ge_16(const uint8_t idx[16], uint8_t child)
// Returns bit i set if idx[i] >= child
// x86 doesn't have a "compare unsigned >=" for bytes directly, so we use:
// min(key, idx[i]) == key iff idx[i] >= key
.globl find_ge_16
.type find_ge_16, @function
find_ge_16:
vmovd %esi, %xmm1
vpbroadcastb %xmm1, %xmm1 // key broadcast
vmovdqu (%rdi), %xmm0 // load 16 bytes
vpminub %xmm0, %xmm1, %xmm2 // min(key, idx[i])
vpcmpeqb %xmm2, %xmm1, %xmm0 // 0xff where min == key, i.e. idx[i] >= key
vpmovmskb %xmm0, %eax
movzwl %ax, %eax
ret
.size find_ge_16, .-find_ge_16
// uint32_t mask_in_range_16(const uint8_t idx[16], uint8_t begin, uint8_t end)
// Returns bit i set if begin <= idx[i] < end
// Logic: (idx[i] - begin) < (end - begin) [unsigned wrapping arithmetic]
// Equivalently: idx[i] - begin != max(idx[i] - begin, end - begin)
// i.e. idx[i] - begin is NOT equal to the saturated value.
// We compute: sub = idx - begin; result = (sub < (end-begin)) for each byte.
// Using: sub == max(sub, end-begin) means NOT in range.
// So: in_range = ~(movemask(cmpeq(sub, max(sub, range_size))))
.globl mask_in_range_16
.type mask_in_range_16, @function
mask_in_range_16:
vmovd %esi, %xmm1 // begin
vpbroadcastb %xmm1, %xmm1
vmovd %edx, %xmm2 // end
vpbroadcastb %xmm2, %xmm2
vmovdqu (%rdi), %xmm0 // load 16 bytes
vpsubb %xmm1, %xmm0, %xmm0 // idx - begin (wrapping)
vpsubb %xmm1, %xmm2, %xmm2 // end - begin (range size)
vpmaxub %xmm0, %xmm2, %xmm3 // max(idx-begin, range_size)
vpcmpeqb %xmm3, %xmm0, %xmm0 // 0xff where NOT in range
vpmovmskb %xmm0, %eax
not %eax // invert: 1 = in range
movzwl %ax, %eax
ret
.size mask_in_range_16, .-mask_in_range_16
.section .note.gnu.property,"a",@note
.p2align 3, 0x0
.long 4
.long 16
.long 5
.asciz "GNU"
.long 0xc0000002
.long 4
.long 0x3
.p2align 3, 0x0
.section .note.GNU-stack,"",@progbits