fix(cli): velesdb simd info names the detected level; drop the unused simd_neon (#1965) - #2391
Merged
Merged
Conversation
…ed `simd_neon` (#1965) `simd info` printed a fixed design summary whose thresholds had drifted from the dispatcher (AVX2 at 1024 dims, not 256) and said nothing about the machine. It now prints `simd_native::simd_level()` through a new `impl Display for SimdLevel`. `velesdb_core::simd_neon` was a public aarch64-only module with no caller, duplicating the NEON kernels `simd_native` dispatches to. Two guides claimed mobile used it; they now name `simd_native`. Declared BREAKING in the CHANGELOG, since semver-checks runs on x86_64 and cannot see it.
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | 0 |
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
…e removed functions (#1965) SOUNDNESS's inventory table and SIMD_PERFORMANCE's `simd info` paragraph still described the removed module and the old static output; the CLI reference also claimed `simd benchmark` re-benchmarks every backend, where it only points to `cargo bench`. Three dead lint exclusions named the deleted file, and two docs used it as their aarch64-only example (now `simd_neon_prefetch`). The CHANGELOG entry maps all nine removed functions, the safe wrappers included, to their `simd_native` replacement.
…econdition stated (#1965) `dot_product_neon`, `squared_l2_neon`, `cosine_neon`, `hamming_neon`, `jaccard_neon`, `hamming_binary_neon` and the private `*_4acc` helpers were safe functions that read `b` at every index of `a` unchecked: a crate-internal call with a shorter `b` read past its end with no `unsafe` in sight (probe: `dot_product_neon(&[1.0; 8], &backing[..4])` = 8). The public entry points already assert equal lengths in release, so no public call was affected. They now carry an `a.len() == b.len()` `# Safety` contract, like the x86 kernels, and each call site names the assert it relies on. The two `*_4acc` kernels share their loop bounds through a safe helper instead of two copies of raw pointer arithmetic. SOUNDNESS's invariant 2 claimed a `debug_assert_eq!` that did not exist; `simd benchmark` said the benchmarks were removed.
…1965) Listed all of them this time: `squared_l2_neon_1acc` had no `# Safety`, nor did the two cosine pointer helpers (now: the span of `b` they read) or the two register-only helpers (now: nothing beyond NEON). Three delegations cited a 64-element "minimum" the 4-accumulator kernels do not have; it is a dispatch threshold, now said so. `cosine_fused_neon_4acc` takes its bounds from `bounds_16wide` like the other two.
…1965) `bounds_16wide` took the main-body end from `a[..main]`: the same address, but cosine's scalar tail dereferences from that pointer on, past the subslice's borrow, which Miri (Stacked Borrows) reports as UB. Both pointers are now derived from `a` with `wrapping_add`, and the helper returns the main-body length, which cosine recomputed. The calls to `reduce_4acc_neon` get their SAFETY comment, and the loop bodies of the pointer helpers cite their own `# Safety` contract.
…S states both loop shapes (#1965) `neon_bounds_tests` feeds `bounds_16wide`'s pointers to the cosine scalar tail at lengths 0-65. Natively it passes whatever their provenance; under Miri's Stacked Borrows a tail pointer derived from `a[..main]` fails. It calls no intrinsic, so Miri can run it; wiring an aarch64 Miri step is #2397. SOUNDNESS's invariants 3 and 4 described only the `len / 4` loops; they now cover the 16-wide kernels and where their tail pointers come from.
…-detected-level # Conflicts: # CHANGELOG.md
…n.rs The # Safety sections took neon.rs to 1025 lines, past the 1000-line production budget that check-file-budgets.py enforces. The Hamming and Jaccard kernels share no helper with the dot, cosine and L2 ones, so they move unchanged to neon_hamming_jaccard.rs, beside neon.rs the way x86_avx2_similarity.rs sits beside x86_avx2/. The budget is not raised.
…-detected-level # Conflicts: # CHANGELOG.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs #1965. Clears the two cost-S items that the 2026-09-05 update promoted to checkboxes. The registry stays open for its measured items.
velesdb simd infonames the level it detectsIt used to print a fixed summary of the design. The thresholds in that summary no longer match the code: it said AVX2 switches at 1024 dimensions, but the dispatcher switches at 256, and it listed AVX-512 with 4/2/1 accumulators where the code runs an 8-accumulator kernel from 1024 dimensions. It said nothing about the machine it ran on. It now prints
simd_native::simd_level(), named through a newimpl Display for SimdLevel:Test:
e2e_complete::simd_commands::test_simd_info_names_the_detected_level. It asserts that the output containssimd_level().to_string()and that1024Dis gone. I ran it against the old handler first and it failed (var.contains(NEON (aarch64))), then passed after the fix.velesdb_core::simd_neonremoved (BREAKING, aarch64 only)This was a
pub modwith no callers, a standalone copy of the NEON kernels thatsimd_nativealready dispatches to. Two guides said mobile computes distances through it. It doesn't: mobile goes throughsimd_native, like every aarch64 build. The guides,SOUNDNESS.md(section and inventory table),ARCHITECTURE.md,SIMD_PERFORMANCE.md("Known gaps" and thesimd infoparagraph) andCLI_COMMAND_REFERENCE.mdare corrected to match. That reference also claimedsimd benchmarkre-benchmarks every backend; it only points tocargo bench, and now says so. The dead Codacy and Semgrep exclusions for the deleted file are removed, andcore-review.ymlandQUALITY_BAR.mdnow usesimd_neon_prefetchas their example of an aarch64-only public item. The CHANGELOG maps all nine removed functions to theirsimd_nativereplacements. Thevelesdb simd --helptext makes the same corrections as the reference:infoshows the detected level, andbenchmarkmeasures nothing itself.SOUNDNESS.md'ssimd_native/neon.rssection no longer names the removed*_neon_safewrappers. It also no longer calls every kernel anunsafe fnwith#[target_feature]: the kernels arepub(crate) unsafe fns with ana.len() == b.len()precondition, and NEON needs no feature gate on AArch64.simd_neon_prefetchis kept becausesimd_native::prefetchuses it.[Unreleased]already carries breaking changes, so the next release is a major and this removal fits it.cargo semver-checksruns on x86_64 and cannot see a module that only exists on aarch64, so the CHANGELOG entry spells out the migration.Gates (local, aarch64)
fmt, both strict clippy runs, the CLI e2e test, the core
simdlib tests, rustdoc-D warnings, and the doc/claims/attribution/version/promise checks.Also
e2e_complete.rs(touched for the new test) had the BFS and DFS traversal tests building the same one-edge graph twice, 23 identical lines. They now sharegraph_with_one_edge/traverse_from_10, with no change to what either test asserts.NEON kernels are
unsafe fn(found in review, round 3)The six
pub(crate)NEON kernels, and the two private*_4acchelpers they call, were safefns that readbat every index ofawith no length check. A debug-profile probe calleddot_product_neon(&[1.0; 8], &backing[..4])and got 8: four floats read pastb, no assert, nounsafeat the call site. The public entry points (*_native,DistanceEngine::dispatch) already assert equal lengths in release, so no public call was exposed. The hole was crate-internal: any future caller could trigger UB from safe code.They are now
unsafe fnwith ana.len() == b.len()# Safetyprecondition, the model the x86 dot-product kernels already follow. Each of the 11 call sites wraps the call inunsafewith aSAFETYcomment naming the assert it relies on. The inner calls to the*_1acc/*_4accand cosine helpers name the calling kernel's own precondition. Everyunsafe fninneon.rsnow has a# Safetysection; I checked this by listing them all, because the template checker only coversunsafeblocks. The two register-only helpers state that they need nothing beyond NEON. The cosine pointer helpers state the span ofbthey read.cosine_fused_neon_4acctakes its bounds frombounds_16widetoo. Three comments that cited a 64-element "minimum" now give it as what it is: a dispatch threshold, not a safety condition. No behaviour changes: a safe call to these kernels no longer compiles.SOUNDNESS.mdinvariant 2 previously claimed adebug_assert_eq!that did not exist; it now describes the real check.velesdb simd benchmarkprinted "micro-benchmarks removed" althoughsimd_benchmarkexists; it now says the CLI does not benchmark and gives thecargo benchcommand. The three 16-wide kernels (dot_product_neon_4acc,squared_l2_neon_4acc,cosine_fused_neon_4acc) shared their loop-bound setup. It is now one safe helper,bounds_16wide, which returns the main-body length and both pointers. Both come froma's ownas_ptr_range(), the tail start throughwrapping_add(main). A first draft took the main-body end from the subslicea[..main]. The address was the same, but cosine's scalar tail dereferences from that pointer onward, past the subslice's borrow, and Miri (Stacked Borrows) reported it as UB. The review caught it, and pointers derived from the full slice remove it. Every change is undercfg(target_arch = "aarch64")and was built and gated on aarch64. The x86 code is untouched.Round 7: the provenance fix, pinned
neon_bounds_testsfeedsbounds_16wide's pointers to the cosine scalar tail at lengths 0, 1, 15, 16, 17 and 65 (empty, tail only, exact, 16+1, 64+1). Natively it passes whatever the pointers' provenance. Under Miri's Stacked Borrows, the review's reproduction shows the subslice-derived version fails and this one passes. The test calls no NEON intrinsic, so Miri can run it. CI's only Miri job runs on x86_64, weekly or on demand, so it never compilesneon.rs. Running this test there is a CI change, filed as #2397 rather than claimed here.SOUNDNESS.mdnow describes every NEON loop shape: thelen / 4single-accumulator loops, the three 16-wide kernels with pointer tails and where those pointers must come from, the two 16-wide kernels with indexed tails, andhamming_binary_neon's two-word loop.Miri, run by the review with the documented command (
cargo +nightly miri test --target aarch64-unknown-linux-gnu -p velesdb-core --lib -- neon_bounds, Stacked Borrows): the test passes at head. It fails on the round-6 regression, and also on a variant that keeps the subslice pointer only whenmain > 0.Round 10:
neon.rssplit under the file budgetThe
# Safetysections tookneon.rsto 1025 lines, past the 1000-line production budget, and CI'scheck-file-budgets.pyfailed on it. The budget is not raised. The Hamming and Jaccard kernels (hamming_neon,hamming_binary_neon,jaccard_neonand their1acc/4acchelpers) share no helper with the dot, cosine and L2 ones, so they move unchanged tosimd_native/neon_hamming_jaccard.rs, the same wayx86_avx2_similarity.rssits besidex86_avx2/.neon.rsis 591 lines (290 NLOC), the new file 455 (205 NLOC).QUALITY_BAR.md's staleneon.rsrow (902 NLOC) is removed, since both files are now under the 500-NLOC line.SOUNDNESS.mdnames both files.