CI: Run the SVE build and test stages on one image and one compiler

libeigen/eigen!2963

Co-authored-by: Rasmus Munk Larsen <rmlarsen@gmail.com>
diff --git a/.agents/ci.md b/.agents/ci.md
index ea6dfb4..0c7fd7c 100644
--- a/.agents/ci.md
+++ b/.agents/ci.md
@@ -169,7 +169,7 @@
 compile coverage rather than a selection because its per-SVL test jobs already filter to a curated target subset
 through `EIGEN_CI_CTEST_REGEX`, which a selection would fight with.
 
-SVE runs the selection, at two vector lengths rather than one. The backend is fixed-length — `EIGEN_ARM64_SVE_VL`
+SVE runs the selection, at three vector lengths rather than one. The backend is fixed-length — `EIGEN_ARM64_SVE_VL`
 comes from `__ARM_FEATURE_SVE_BITS`, which only `-msve-vector-bits` sets — so each width is a separate build, and the
 packet code's fold counts and transpose networks differ between them. `test/sve_vector_length` guards the rest by
 reading `RDVL` and comparing it against the width the packets were built for, because a binary run at the wrong length
diff --git a/ci/build.linux.gitlab-ci.yml b/ci/build.linux.gitlab-ci.yml
index 3c8ea48..28c1032 100644
--- a/ci/build.linux.gitlab-ci.yml
+++ b/ci/build.linux.gitlab-ci.yml
@@ -598,27 +598,19 @@
       -DEIGEN_TEST_CUSTOM_CXX_FLAGS=-march=armv8.2-a+sve
       -DCMAKE_CROSSCOMPILING_EMULATOR=qemu-aarch64;-L;/usr/aarch64-linux-gnu
 
-# gcc-14 builds 128 bits and nothing wider. At -O3 with -msve-vector-bits=256 or
-# above, gcc-14.2 miscompiles: redux_commutative<double> in test/redux.cpp sums
-# a 16x16 block of a 17x17 matrix to 660 instead of 1022, deterministically.
-# That is a compiler bug and not an Eigen one -- it reproduces with
-# EIGEN_ARM64_USE_SVE unset, with GCC autovectorizing to fixed-length SVE while
-# Eigen is on NEON -- and not an emulation artifact, since QEMU 8.2 and 10.0
-# produce the same wrong value. It goes away at -O2, with
-# -fno-tree-slp-vectorize, and at 128 bits.
-.build:linux:cross:sve:gcc-14:
-  extends: .build:linux:cross:sve
-  variables:
-    EIGEN_CI_CROSS_INSTALL: g++-14-aarch64-linux-gnu
-    EIGEN_CI_C_COMPILER: gcc-14
-    EIGEN_CI_CXX_COMPILER: g++-14
-    EIGEN_CI_CROSS_C_COMPILER: aarch64-linux-gnu-gcc-14
-    EIGEN_CI_CROSS_CXX_COMPILER: aarch64-linux-gnu-g++-14
-
-# Every width above 128, for the reason above: gcc-13.3 is clean at 128, 256 and
-# 512. SVE ACLE needs only gcc-10, so nothing here depends on 14 the way SME's
-# ACLE does. Revisit once the image carries a fixed gcc-14 or a gcc-15 aarch64
-# cross.
+# One compiler for all three widths. gcc-14 is not usable above 128 bits: at -O3
+# with -msve-vector-bits=256 or above, gcc-14.2 miscompiles
+# redux_commutative<double> in test/redux.cpp, summing a 16x16 block of a 17x17
+# matrix to 660 instead of 1022, deterministically. That is a compiler bug and
+# not an Eigen one -- it reproduces with EIGEN_ARM64_USE_SVE unset, with GCC
+# autovectorizing to fixed-length SVE while Eigen is on NEON -- and not an
+# emulation artifact, since QEMU 8.2 and 10.0 produce the same wrong value. It
+# goes away at -O2, with -fno-tree-slp-vectorize, and at 128 bits.
+#
+# gcc-13.3 is clean at 128, 256 and 512, and SVE ACLE needs only gcc-10, so
+# nothing here depends on 14 the way SME's ACLE does. Naming one compiler is
+# what lets the test jobs install the compiler their artifacts were built with,
+# which the failtest and buildsystem tests require -- see .test:linux:sve.
 .build:linux:cross:sve:gcc-13:
   extends: .build:linux:cross:sve
   variables:
@@ -634,8 +626,8 @@
 # architecturally legal but unimplemented, and compile-only coverage of them
 # bought two latent compile fixes at the cost of running nothing -- if a wider
 # part ships, add it here with a test job rather than as a build on its own.
-build:linux:cross:sve:gcc-14:vl128:
-  extends: .build:linux:cross:sve:gcc-14
+build:linux:cross:sve:gcc-13:vl128:
+  extends: .build:linux:cross:sve:gcc-13
   variables:
     EIGEN_CI_ADDITIONAL_ARGS: >-
       -DEIGEN_TEST_SVE=ON
@@ -673,7 +665,7 @@
     EIGEN_CI_INSTALL: clang-14
     EIGEN_CI_C_COMPILER: clang-14
     EIGEN_CI_CXX_COMPILER: clang++-14
-    EIGEN_CI_CROSS_INSTALL: g++-14-aarch64-linux-gnu clang-14
+    EIGEN_CI_CROSS_INSTALL: g++-13-aarch64-linux-gnu clang-14
     EIGEN_CI_ADDITIONAL_ARGS: >-
       -DEIGEN_TEST_SVE=ON
       -DEIGEN_TEST_SVE_VL=128
@@ -959,10 +951,10 @@
 
 # The VL sweep is the point of the affected tier for SVE: a change to the packet
 # code is exactly what breaks at one vector length and not another.
-build:linux:cross:sve:gcc-14:vl128:affected:
-  extends: build:linux:cross:sve:gcc-14:vl128
+build:linux:cross:sve:gcc-13:vl128:affected:
+  extends: build:linux:cross:sve:gcc-13:vl128
   variables:
-    EIGEN_CI_CCACHE_POOL: build-linux-cross-sve-gcc-14-vl128
+    EIGEN_CI_CCACHE_POOL: build-linux-cross-sve-gcc-13-vl128
   rules: !reference [.rules:libeigen:affected-tests:sve, rules]
 
 build:linux:cross:sve:gcc-13:vl512:affected:
diff --git a/ci/test.linux.gitlab-ci.yml b/ci/test.linux.gitlab-ci.yml
index b93edc4..8a86c23 100644
--- a/ci/test.linux.gitlab-ci.yml
+++ b/ci/test.linux.gitlab-ci.yml
@@ -556,9 +556,14 @@
 # jobs are NEON and the SME jobs above take the NEON headers.
 .test:linux:sve:
   extends: .test:linux
-  # qemu >= 10 for the sve-default-vector-length property used below; Ubuntu
-  # 24.04 ships 8.2. Same split as the SME jobs: build on 24.04, test on 26.04.
-  image: ubuntu:26.04
+  # No image override: these run on the same ubuntu:24.04 the SVE builds use.
+  # An earlier split put the test stage on 26.04 for "qemu >= 10 for the
+  # sve-default-vector-length property", but 24.04's qemu-user 8.2.2 honours
+  # that property at every width -- measured with an RDVL probe at 128, 256,
+  # 512, 1024 and 2048, and by sve_vector_length itself, which fails if the
+  # emulated width ever disagrees with EIGEN_ARM64_SVE_VL. The SME jobs keep
+  # their own 26.04 split: theirs is FEAT_SME2 multi-vector emulation, which
+  # 8.2 genuinely lacks, and is unrelated to this property.
   variables:
     EIGEN_CI_TARGET_ARCH: aarch64-sve
     EIGEN_CI_CROSS_TARGET_TRIPLE: aarch64-linux-gnu
@@ -574,12 +579,19 @@
     # too; the :official jobs are unaffected either way, as both names are
     # Unsupported tests.
     EIGEN_CI_CTEST_EXCLUDE: tensor_thread_pool|FFT
-    # The test stage never compiles, so no build compiler is named here and one
-    # anchor serves both widths whichever GCC built them: all this needs is the
-    # aarch64 runtime and qemu-user, and the default-version cross g++ supplies
-    # the libraries on any release.
-    EIGEN_CI_CXX_COMPILER: aarch64-linux-gnu-g++
-    EIGEN_CI_CROSS_INSTALL: g++-aarch64-linux-gnu qemu-user
+    # Name the compiler the artifacts were built with, not the default-version
+    # cross g++. The test stage does compile: the failtest targets are
+    # EXCLUDE_FROM_ALL and their ctest command is `cmake --build`, and the
+    # buildsystem scenarios configure nested projects with the parent cache's
+    # CMAKE_CXX_COMPILER baked in (test/buildsystem/CMakeLists.txt). Both read
+    # the absolute path CMake resolved at configure time, so an image without
+    # that exact binary fails them -- 62 failtests and 9 buildsystem tests at
+    # once, and the failtest _ko halves then pass vacuously, WILL_FAIL being
+    # unable to tell the asserted compile error from a missing compiler. Only
+    # the affected tier saw this: the :official and :unsupported jobs filter by
+    # label, and neither family carries the labels they select.
+    EIGEN_CI_CXX_COMPILER: aarch64-linux-gnu-g++-13
+    EIGEN_CI_CROSS_INSTALL: g++-13-aarch64-linux-gnu qemu-user
 
 # Each concrete job must set QEMU_CPU, and both halves of it: sveN=on advertises
 # the width, sve-default-vector-length (in bytes) selects the one a task
@@ -589,7 +601,7 @@
 # EIGEN_ARM64_SVE_VL and fails if this is ever wrong.
 .test:linux:sve:vl128:
   extends: .test:linux:sve
-  needs: [ build:linux:cross:sve:gcc-14:vl128 ]
+  needs: [ build:linux:cross:sve:gcc-13:vl128 ]
   variables:
     QEMU_CPU: max,sve128=on,sve-default-vector-length=16
 
@@ -611,14 +623,14 @@
 # (four- and eight-wide double packets, deeper predux_mul folds and ptranspose
 # networks), and a packet size of eight, which is where the GEBP column-group
 # guard went wrong, is reached by float at 256 and by double at 512. The job
-# names carry the compiler that built the binaries, which is gcc-13 above 128
-# bits -- see the note on .build:linux:cross:sve:gcc-14.
-test:linux:sve128:gcc-14:official:
+# names carry the compiler that built the binaries, which is gcc-13 at every
+# width -- see the note on .build:linux:cross:sve:gcc-13.
+test:linux:sve128:gcc-13:official:
   extends: .test:linux:sve:vl128
   variables:
     EIGEN_CI_CTEST_LABEL: Official
 
-test:linux:sve128:gcc-14:unsupported:
+test:linux:sve128:gcc-13:unsupported:
   extends: .test:linux:sve:vl128
   variables:
     EIGEN_CI_CTEST_LABEL: Unsupported
@@ -999,9 +1011,9 @@
 # Three vector lengths: the SVE packet code has VL-dependent fold counts and
 # transpose networks, so a single length is not a meaningful affected-tier
 # signal for a change under arch/SVE.
-test:linux:sve128:gcc-14:affected:
+test:linux:sve128:gcc-13:affected:
   extends: [ .test:linux:sve:vl128, .affected:test ]
-  needs: [ build:linux:cross:sve:gcc-14:vl128:affected, select:tests ]
+  needs: [ build:linux:cross:sve:gcc-13:vl128:affected, select:tests ]
   rules: !reference [.rules:libeigen:affected-tests:sve, rules]
 
 test:linux:sve256:gcc-13:affected: