Core: Resolve EIGEN_SCALAR_MADD_USE_FMA after EIGEN_VECTORIZE_FMA is known libeigen/eigen!2992 Co-authored-by: Rasmus Munk Larsen <rmlarsen@gmail.com>
diff --git a/Eigen/src/Core/util/ConfigureVectorization.h b/Eigen/src/Core/util/ConfigureVectorization.h index 3f7612e..07e682c 100644 --- a/Eigen/src/Core/util/ConfigureVectorization.h +++ b/Eigen/src/Core/util/ConfigureVectorization.h
@@ -664,6 +664,20 @@ // IWYU pragma: private #include "../InternalHeaderCheck.h" +/** Whether numext::madd uses std::fma for scalars. Defaults to the hardware: fused where a single + * instruction exists, which keeps the scalar and vectorized paths consistent, and unfused otherwise, + * where software fma costs 2-3x on Intel and up to 30x on WASM. Resolved here rather than in Macros.h, + * which Eigen/Core includes first, because the architecture branches above settle + * EIGEN_VECTORIZE_FMA -- the ARM one only a few lines up. + */ +#ifndef EIGEN_SCALAR_MADD_USE_FMA +#ifdef EIGEN_VECTORIZE_FMA +#define EIGEN_SCALAR_MADD_USE_FMA 1 +#else +#define EIGEN_SCALAR_MADD_USE_FMA 0 +#endif +#endif + namespace Eigen { inline static const char* SimdInstructionSetsInUse(void) {
diff --git a/Eigen/src/Core/util/Macros.h b/Eigen/src/Core/util/Macros.h index 8e6e0e7..35fdbaf 100644 --- a/Eigen/src/Core/util/Macros.h +++ b/Eigen/src/Core/util/Macros.h
@@ -61,26 +61,6 @@ #define EIGEN_STACK_ALLOCATION_LIMIT_WAS_DEFAULTED #endif -/* Specify whether to use std::fma for scalar multiply-add instructions. - * - * On machines that have FMA as a single instruction, this will generally - * improve precision without significant performance implications. - * - * Without a single instruction, performance has been found to be reduced 2-3x - * on Intel CPUs, and up to 30x for WASM. - * - * If unspecified, defaults to using FMA if hardware support is available. - * The default should be used in most cases to ensure consistency between - * vectorized and non-vectorized paths. - */ -#ifndef EIGEN_SCALAR_MADD_USE_FMA -#ifdef EIGEN_VECTORIZE_FMA -#define EIGEN_SCALAR_MADD_USE_FMA 1 -#else -#define EIGEN_SCALAR_MADD_USE_FMA 0 -#endif -#endif - //------------------------------------------------------------------------------------------ // Compiler identification, EIGEN_COMP_* //------------------------------------------------------------------------------------------
diff --git a/test/numext.cpp b/test/numext.cpp index 94b4f03..50db67b 100644 --- a/test/numext.cpp +++ b/test/numext.cpp
@@ -261,6 +261,24 @@ } } +// internal::twoprod splits x*y into hi + lo with x*y = hi + lo exactly, which holds only if its +// multiply-add is genuinely fused: a non-fused x*y - hi folds against the already-rounded product and +// silently yields lo = 0, leaving every double-word computation built on it with no low word at all. +template <typename T> +void check_twoprod() { + for (int k = 0; k < 100; ++k) { + const T x = internal::random<T>(T(-1), T(1)); + const T y = internal::random<T>(T(-1), T(1)); + T hi, lo; + internal::twoprod(x, y, hi, lo); + VERIFY_IS_EQUAL(hi, x * y); + // fma is exact by IEEE-754 contract, so this is the definition of the low word rather than an + // independent approximation of it. + EIGEN_USING_STD(fma); + VERIFY_IS_EQUAL(lo, fma(x, y, -hi)); + } +} + template <typename T> void check_arg() { typedef typename NumTraits<T>::Real Real; @@ -600,6 +618,9 @@ CALL_SUBTEST(check_complex_sign<std::complex<float>>()); CALL_SUBTEST(check_complex_sign<std::complex<double>>()); + CALL_SUBTEST(check_twoprod<float>()); + CALL_SUBTEST(check_twoprod<double>()); + CALL_SUBTEST(check_arg<std::complex<float>>()); CALL_SUBTEST(check_arg<std::complex<double>>());