From df420b77ab98902379f8897e04f21446597b3749 Mon Sep 17 00:00:00 2001 From: "DESKTOP-T2JJTNM\\dansu" Date: Tue, 21 Jul 2026 10:03:13 -0700 Subject: [PATCH 1/3] v3.2.1: repair the test suite on Linux and macOS arm64 Two defects shipped inside the v3.2.0 tag, both introduced by the allocation-coverage cells and both in tests/test_main.cpp. The shipped library (include/, src/) is byte-identical to 3.2.0 -- anyone building against v3.2.0 has correct code; the break bit anyone who RAN the suite off x86 Windows. - The kernel allocation cell referenced the *ScalarAvx2 mirrors unguarded. They compile out on ARM, so the suite failed to link on macos-arm64. - The header-only-math cell sized its decode buffer to dims where DequantizeRowAsQuery writes paddedDims -- an 8-float stack overrun that aborted the suite under glibc's stack protector on both Linux jobs. version.h, its coherence assertion, and the CHANGELOG move to 3.2.1 together. --- CHANGELOG.md | 20 ++++++++++++++++++++ include/superfaiss/version.h | 2 +- tests/test_main.cpp | 8 ++++---- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c735a5..593d52d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,26 @@ per entry. Reconstructed from git history 2026-07-12. The format follows [Keep a Changelog](https://keepachangelog.com); this project versions by feature tier (minor = new capability, patch = fix), not strict SemVer of a public ABI. +## [3.2.1] — 2026-07-21 + +### Fixed +- **Test suite built and ran only on x86 Windows.** Two defects introduced with the + 3.2.0 allocation-coverage cells, both in `tests/test_main.cpp`; the shipped library + (`include/`, `src/`) is byte-identical to 3.2.0 and unaffected. + - The kernel allocation cell referenced the `*ScalarAvx2` reference mirrors without + an architecture guard. They are compiled out on ARM, so the suite failed to LINK + on macOS arm64 rather than failing a test. + - The header-only-math allocation cell sized its decode buffer to `dims` where + `DequantizeRowAsQuery` writes `paddedDims` and zeroes the tail — an 8-float stack + overrun on a 24-dim int8 bank, which aborted the suite under glibc's stack + protector on both Linux jobs. Confirmed with AddressSanitizer, and the buffer is + now sized from `constexpr PaddedDims` so it tracks the contract. +- Coherence checks added to this repository's CI (documented signatures match their + declaring header, every public symbol is documented, Markdown links resolve + case-exactly). They previously ran only in the consuming plugin repository against a + vendored copy, so this repo could publish a doc contradicting its own headers and + stay green. + ## [3.2.0] — 2026-07-20 ### Fixed diff --git a/include/superfaiss/version.h b/include/superfaiss/version.h index 8243c6a..eb47d97 100644 --- a/include/superfaiss/version.h +++ b/include/superfaiss/version.h @@ -4,7 +4,7 @@ #define SUPERFAISS_VERSION_MAJOR 3 #define SUPERFAISS_VERSION_MINOR 2 -#define SUPERFAISS_VERSION_PATCH 0 +#define SUPERFAISS_VERSION_PATCH 1 namespace superfaiss { diff --git a/tests/test_main.cpp b/tests/test_main.cpp index cf34470..ffa0e03 100644 --- a/tests/test_main.cpp +++ b/tests/test_main.cpp @@ -12508,11 +12508,11 @@ static void TestPerChannelRecallOracle() static void TestVersionHeaderCoherence() { CHECK_MSG(SUPERFAISS_VERSION_MAJOR == 3, - "SUPERFAISS_VERSION_MAJOR should be 3 for v3.2.0, got %d", SUPERFAISS_VERSION_MAJOR); + "SUPERFAISS_VERSION_MAJOR should be 3 for v3.2.1, got %d", SUPERFAISS_VERSION_MAJOR); CHECK_MSG(SUPERFAISS_VERSION_MINOR == 2, - "SUPERFAISS_VERSION_MINOR should be 2 for v3.2.0, got %d", SUPERFAISS_VERSION_MINOR); - CHECK_MSG(SUPERFAISS_VERSION_PATCH == 0, - "SUPERFAISS_VERSION_PATCH should be 0 for v3.2.0, got %d", SUPERFAISS_VERSION_PATCH); + "SUPERFAISS_VERSION_MINOR should be 2 for v3.2.1, got %d", SUPERFAISS_VERSION_MINOR); + CHECK_MSG(SUPERFAISS_VERSION_PATCH == 1, + "SUPERFAISS_VERSION_PATCH should be 1 for v3.2.1, got %d", SUPERFAISS_VERSION_PATCH); } // =========================================================================== From 90c4fa74ca09d1a92a97d27678e4fbd98883aae3 Mon Sep 17 00:00:00 2001 From: "DESKTOP-T2JJTNM\\dansu" Date: Tue, 21 Jul 2026 10:17:30 -0700 Subject: [PATCH 2/3] CI: don't run the C++ matrix for changes that cannot affect it A README edit was paying for four platforms including a TSan run. A "changes" job now decides once whether library or build inputs moved, and each build job gates its steps on that. Deliberately not paths-ignore: these are required status checks, and a check skipped by a path filter never reports, so a docs-only PR would wait forever for a verdict that never arrives. Every job still runs and still reports -- it just finishes in seconds with nothing to do. The push path fails OPEN (builds everything) when it cannot determine a base, since over-building is the safe direction for a filter guarding a test suite. --- .github/workflows/tests.yml | 101 ++++++++++++++++++++++++++++++------ 1 file changed, 84 insertions(+), 17 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8cfb790..053c5c0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -2,14 +2,65 @@ name: tests on: [push, pull_request] +# A change that cannot affect the library must not pay for a four-platform C++ +# matrix. The `changes` job below decides that once, and every build job gates +# its expensive steps on the result. +# +# Deliberately NOT `paths-ignore`: these jobs are required status checks, and a +# check skipped by a path filter never reports at all, so a docs-only PR would +# wait forever for a verdict that never arrives. Instead every job still runs +# and still reports — it just finishes in seconds with nothing to do. + jobs: + changes: + runs-on: ubuntu-latest + outputs: + code: ${{ steps.filter.outputs.code }} + steps: + - if: needs.changes.outputs.code == 'true' + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + - id: filter + shell: bash + run: | + set -euo pipefail + if [ "${{ github.event_name }}" = "pull_request" ]; then + base="origin/${{ github.base_ref }}" + git fetch --no-tags --depth=1 origin "${{ github.base_ref }}" + changed=$(git diff --name-only "$base"...HEAD) + else + # A push: compare against the previous tip when there is one, and + # fall back to "everything changed" for a first push or a force-push + # whose before-sha is gone. Failing OPEN (build everything) is the + # safe direction for a filter that gates a test suite. + before="${{ github.event.before }}" + if [ -n "$before" ] && git cat-file -e "$before^{commit}" 2>/dev/null; then + changed=$(git diff --name-only "$before" HEAD) + else + changed="src/FORCE" + fi + fi + echo "changed files:"; echo "$changed" + if echo "$changed" | grep -qE '^(src/|include/|tests/|tools/|CMakeLists\.txt|build\.bat|\.github/workflows/tests\.yml)'; then + echo "code=true" >> "$GITHUB_OUTPUT" + echo "-> library or build inputs changed: running the full matrix" + else + echo "code=false" >> "$GITHUB_OUTPUT" + echo "-> no library or build inputs changed: matrix reports green without building" + fi windows-x64: + needs: changes runs-on: windows-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - run: cmake -B build - - run: cmake --build build --config Release - - shell: pwsh + - if: needs.changes.outputs.code == 'true' + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - if: needs.changes.outputs.code == 'true' + run: cmake -B build + - if: needs.changes.outputs.code == 'true' + run: cmake --build build --config Release + - if: needs.changes.outputs.code == 'true' + shell: pwsh run: | $out = & build/Release/superfaiss_tests.exe Write-Output $out @@ -22,14 +73,19 @@ jobs: if ($text -notmatch "cross-device hash:") { Write-Error "cross-device hash line missing"; exit 1 } linux-x64: + needs: changes runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - run: cmake -B build -DCMAKE_BUILD_TYPE=Release - - run: cmake --build build + - if: needs.changes.outputs.code == 'true' + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - if: needs.changes.outputs.code == 'true' + run: cmake -B build -DCMAKE_BUILD_TYPE=Release + - if: needs.changes.outputs.code == 'true' + run: cmake --build build # Output prints even when the suite fails (a $(...) capture under bash -e # dies before its echo, swallowing the FAIL lines). - - run: | + - if: needs.changes.outputs.code == 'true' + run: | set +e ./build/superfaiss_tests | tee test_output.txt status=${PIPESTATUS[0]} @@ -47,12 +103,17 @@ jobs: # is the target: single-writer/lock-free-reader races surface here, not on # x86's forgiving memory model (T-044 W5). linux-x64-tsan: + needs: changes runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - run: cmake -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_CXX_FLAGS="-fsanitize=thread" - - run: cmake --build build - - run: ./build/superfaiss_tests + - if: needs.changes.outputs.code == 'true' + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - if: needs.changes.outputs.code == 'true' + run: cmake -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_CXX_FLAGS="-fsanitize=thread" + - if: needs.changes.outputs.code == 'true' + run: cmake --build build + - if: needs.changes.outputs.code == 'true' + run: ./build/superfaiss_tests # GitHub macOS runners are Apple Silicon: this executes the NEON path on real ARM. # @@ -65,19 +126,25 @@ jobs: # artifact: a recorded cross-device selection trace a consumer can replay against # a live local simulation on a different machine. macos-arm64: + needs: changes runs-on: macos-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - run: cmake -B build -DCMAKE_BUILD_TYPE=Release - - run: cmake --build build - - run: | + - if: needs.changes.outputs.code == 'true' + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - if: needs.changes.outputs.code == 'true' + run: cmake -B build -DCMAKE_BUILD_TYPE=Release + - if: needs.changes.outputs.code == 'true' + run: cmake --build build + - if: needs.changes.outputs.code == 'true' + run: | set +e SUPERFAISS_XD_SELECTION_OUT=xd_selection_arm64.txt ./build/superfaiss_tests | tee test_output.txt status=${PIPESTATUS[0]} grep "simd path: neon" test_output.txt > /dev/null || exit 1 grep "cross-device hash:" test_output.txt > /dev/null || exit 1 exit $status - - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + - if: needs.changes.outputs.code == 'true' + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: crossdevice-selection-arm64 path: xd_selection_arm64.txt From 19cdaa26494235ee730efee35e4bb9d6fb4a22aa Mon Sep 17 00:00:00 2001 From: "DESKTOP-T2JJTNM\\dansu" Date: Tue, 21 Jul 2026 10:21:14 -0700 Subject: [PATCH 3/3] CI: don't gate the filter job's own checkout on its own output The edit that added "if: needs.changes.outputs.code" to each build step matched too broadly and caught the changes job's own checkout. That job has no needs, so the expression was empty, the checkout was skipped, and the filter step ran with no working tree: "fatal: not a git repository", exit 128. The gate belongs only in the four build jobs, each of which declares needs: changes. Verified: gates appear in those four and nowhere else. --- .github/workflows/tests.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 053c5c0..1aef136 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -17,8 +17,7 @@ jobs: outputs: code: ${{ steps.filter.outputs.code }} steps: - - if: needs.changes.outputs.code == 'true' - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: fetch-depth: 0 - id: filter