Core: Work around GCC PR124138 in unrolled self-adjoint copies libeigen/eigen!3411 Co-authored-by: Rasmus Munk Larsen <rmlarsen@gmail.com>
diff --git a/Eigen/src/Core/SelfAdjointView.h b/Eigen/src/Core/SelfAdjointView.h index 21cffb3..b1bcf57 100644 --- a/Eigen/src/Core/SelfAdjointView.h +++ b/Eigen/src/Core/SelfAdjointView.h
@@ -379,6 +379,23 @@ using Shape = SelfAdjointShape; }; +// GCC PR target/124138 (https://gcc.gnu.org/PR124138): since GCC 4.9, avx_vpermilp_parallel accepted selectors 4-7 +// for elements 2 and 3 of a V8DF permutation, emitting the lane-crossing {0, 1, 2, 6, 4, 5, 6, 7} as vpermilpd, which +// yields {0, 1, 2, 2, 4, 5, 6, 7}. SLP forms that permutation from the unrolled mirrored stores of a 4x4 double copy, +// at -O2 since GCC 12. Hiding the copied value from the vectorizer avoids it; the loop used for larger sizes is +// unaffected. Fixed in GCC 14.4, 15.3 and 16, and on the GCC 13 branch after 13.4. +template <bool Unrolled, typename Scalar> +EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE void selfadjoint_mirror_barrier(Scalar&) {} +#if EIGEN_COMP_GNUC_STRICT && defined(__AVX512F__) && !defined(EIGEN_GPU_COMPILE_PHASE) && \ + (EIGEN_GNUC_STRICT_LESS_THAN(13, 5, 0) || \ + (EIGEN_GNUC_STRICT_AT_LEAST(14, 0, 0) && EIGEN_GNUC_STRICT_LESS_THAN(14, 4, 0)) || \ + (EIGEN_GNUC_STRICT_AT_LEAST(15, 0, 0) && EIGEN_GNUC_STRICT_LESS_THAN(15, 3, 0))) +template <> +EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE void selfadjoint_mirror_barrier<true, double>(double& value) { + EIGEN_OPTIMIZATION_BARRIER(value); +} +#endif + template <int UpLo, int SetOpposite, typename DstEvaluatorTypeT, typename SrcEvaluatorTypeT, typename Functor, int Version> class triangular_dense_assignment_kernel<UpLo, SelfAdjoint, SetOpposite, DstEvaluatorTypeT, SrcEvaluatorTypeT, Functor, @@ -405,6 +422,7 @@ EIGEN_DEVICE_FUNC void assignCoeff(Index row, Index col) { eigen_internal_assert(row != col); Scalar tmp = m_src.coeff(row, col); + selfadjoint_mirror_barrier<triangular_assignment_unrolls<DstEvaluatorType, SrcEvaluatorType>::value>(tmp); m_functor.assignCoeff(m_dst.coeffRef(row, col), tmp); m_functor.assignCoeff(m_dst.coeffRef(col, row), numext::conj(tmp)); }
diff --git a/Eigen/src/Core/TriangularMatrix.h b/Eigen/src/Core/TriangularMatrix.h index 0d083aa..ee0e14f 100644 --- a/Eigen/src/Core/TriangularMatrix.h +++ b/Eigen/src/Core/TriangularMatrix.h
@@ -846,6 +846,15 @@ template <typename Kernel, unsigned int Mode, int UnrollCount, bool SetOpposite> struct triangular_assignment_loop; +// Whether call_triangular_assignment_loop fully unrolls the assignment. +template <typename DstEvaluatorType, typename SrcEvaluatorType> +struct triangular_assignment_unrolls { + static constexpr int Size = DstEvaluatorType::XprType::SizeAtCompileTime; + static constexpr bool value = + Size != Dynamic && SrcEvaluatorType::CoeffReadCost < HugeCost && + Size * (int(DstEvaluatorType::CoeffReadCost) + int(SrcEvaluatorType::CoeffReadCost)) / 2 <= EIGEN_UNROLLING_LIMIT; +}; + /** \internal Specialization of the dense assignment kernel for triangular matrices. * The main difference is that the triangular, diagonal, and opposite parts are processed through three different * functions. \tparam UpLo must be either Lower or Upper \tparam Mode must be either 0, UnitDiag, ZeroDiag, or @@ -917,13 +926,7 @@ SetOpposite, DstEvaluatorType, SrcEvaluatorType, Functor>; Kernel kernel(dstEvaluator, srcEvaluator, func, dst.const_cast_derived()); - enum { - unroll = DstXprType::SizeAtCompileTime != Dynamic && SrcEvaluatorType::CoeffReadCost < HugeCost && - DstXprType::SizeAtCompileTime * - (int(DstEvaluatorType::CoeffReadCost) + int(SrcEvaluatorType::CoeffReadCost)) / 2 <= - EIGEN_UNROLLING_LIMIT - }; - + constexpr bool unroll = triangular_assignment_unrolls<DstEvaluatorType, SrcEvaluatorType>::value; triangular_assignment_loop<Kernel, Mode, unroll ? int(DstXprType::SizeAtCompileTime) : Dynamic, SetOpposite>::run( kernel); }
diff --git a/test/selfadjoint.cpp b/test/selfadjoint.cpp index 359a8a0..109a719 100644 --- a/test/selfadjoint.cpp +++ b/test/selfadjoint.cpp
@@ -175,6 +175,28 @@ EIGEN_UNUSED_VARIABLE(m); } +template <unsigned int UpLo, typename MatrixType> +EIGEN_DONT_INLINE void verify_selfadjoint_copy(const MatrixType& m, const MatrixType& dense) { + for (Index j = 0; j < m.cols(); ++j) + for (Index i = 0; i < m.rows(); ++i) + VERIFY_IS_EQUAL(dense(i, j), (UpLo == Upper ? i <= j : i >= j) ? m(i, j) : numext::conj(m(j, i))); +} + +// GCC PR target/124138 miscompiled this copy for 4x4 double with AVX-512 (see SelfAdjointView.h). It needs the copy +// materialized in a function of its own, and a reference built without the mirrored stores that GCC also miscompiles. +template <unsigned int UpLo, typename MatrixType> +EIGEN_DONT_INLINE void selfadjoint_copy(const MatrixType& m) { + const MatrixType dense = m.template selfadjointView<UpLo>(); + verify_selfadjoint_copy<UpLo>(m, dense); +} + +template <typename MatrixType> +void selfadjoint_copy_fixed() { + const MatrixType m = MatrixType::Random(); + selfadjoint_copy<Upper>(m); + selfadjoint_copy<Lower>(m); +} + EIGEN_DECLARE_TEST(selfadjoint) { for (int i = 0; i < g_repeat; i++) { int s = internal::random<int>(1, EIGEN_TEST_MAX_SIZE); @@ -190,6 +212,9 @@ } CALL_SUBTEST_1(bug_159()); + CALL_SUBTEST_2(selfadjoint_copy_fixed<Matrix4d>()); + CALL_SUBTEST_2((selfadjoint_copy_fixed<Matrix<double, 4, 4, RowMajor> >())); + CALL_SUBTEST_3(selfadjoint_copy_fixed<Matrix4cd>()); CALL_SUBTEST_1(selfadjoint_nested_expression<>()); CALL_SUBTEST_4(selfadjoint_l1norm_sizes<double>()); CALL_SUBTEST_4(selfadjoint_l1norm_sizes<std::complex<double> >());