Core: Avoid speculative division by zero in complex_divide_smith libeigen/eigen!3380 Co-authored-by: Antonio Sanchez <cantonios@google.com>
diff --git a/Eigen/src/Core/MathFunctions.h b/Eigen/src/Core/MathFunctions.h index 1c7a286..bbd4e46 100644 --- a/Eigen/src/Core/MathFunctions.h +++ b/Eigen/src/Core/MathFunctions.h
@@ -2540,19 +2540,28 @@ const std::complex<T>& b) { const T a_real = numext::real(a); const T a_imag = numext::imag(a); - const T b_real = numext::real(b); - const T b_imag = numext::imag(b); + T b_real = numext::real(b); + T b_imag = numext::imag(b); // Smith's complex division (https://arxiv.org/pdf/1210.4539.pdf), // guards against over/under-flow. const bool scale_imag = numext::abs(b_imag) <= numext::abs(b_real); - const T rscale = scale_imag ? T(1) : b_real / b_imag; - // d / c = +-1 for |c| = |d|, which the quotient misses as 0 / 0 or inf / inf when b = 0 or both parts of b are - // infinite; a zero divisor then gives an infinity and an infinite one zero, as in pdiv_complex. The sign is not taken - // from b_real * b_imag, which can overflow or underflow. - const T iscale = !scale_imag ? T(1) - : numext::abs(b_imag) == numext::abs(b_real) - ? numext::copysign(T(1), b_real) * numext::copysign(T(1), b_imag) - : b_imag / b_real; + T rscale = T(1); + T iscale = T(1); + if (numext::abs(b_imag) == numext::abs(b_real)) { + // d / c = +-1 for |c| = |d|, which the quotient misses as 0 / 0 or inf / inf when b = 0 or both parts of b are + // infinite; a zero divisor then gives an infinity and an infinite one zero, as in pdiv_complex. The sign is not + // taken from b_real * b_imag, which can overflow or underflow. + iscale = numext::copysign(T(1), b_real) * numext::copysign(T(1), b_imag); + } else if (scale_imag) { + // Each quotient divides by the larger part of b, which is not zero here. The barrier keeps the quotient on its + // branch: an if-converted form evaluates both quotients, and the untaken one divides by zero for a divisor on an + // axis, raising FE_DIVBYZERO. The barrier is applied in place, so that the branch costs no register copy. + volatile_barrier(b_real); + iscale = b_imag / b_real; + } else { + volatile_barrier(b_imag); + rscale = b_real / b_imag; + } const T denominator = b_real * rscale + b_imag * iscale; return std::complex<T>((a_real * rscale + a_imag * iscale) / denominator, (a_imag * rscale - a_real * iscale) / denominator);
diff --git a/Eigen/src/Core/util/Macros.h b/Eigen/src/Core/util/Macros.h index e568698..cff4832 100644 --- a/Eigen/src/Core/util/Macros.h +++ b/Eigen/src/Core/util/Macros.h
@@ -1230,6 +1230,46 @@ #endif #endif +namespace Eigen { +namespace internal { +// A volatile asm that the optimizer must execute exactly where it is written, so a value passed through it is defined +// only on its branch, and an operation that consumes it stays there instead of being evaluated speculatively. +// EIGEN_OPTIMIZATION_BARRIER is not volatile, and its "+g,w" and "+g,x" alternatives put a scalar float or double in +// memory on clang; the float and double overloads keep the value in a register. +template <typename T> +EIGEN_DEVICE_FUNC EIGEN_ALWAYS_INLINE void volatile_barrier(T& x) { + EIGEN_UNUSED_VARIABLE(x); +#if !defined(EIGEN_GPU_COMPILE_PHASE) && !defined(SYCL_DEVICE_ONLY) && \ + (EIGEN_COMP_GNUC || (defined(__clang__) && defined(_MSC_VER))) + __asm__ __volatile__("" : "+m"(x)); +#endif +} +#if !defined(EIGEN_GPU_COMPILE_PHASE) && !defined(SYCL_DEVICE_ONLY) && \ + (EIGEN_COMP_GNUC || (defined(__clang__) && defined(_MSC_VER))) +#if EIGEN_ARCH_i386_OR_x86_64 && EIGEN_COMP_NVHPC +// nvc++ rejects a long double asm operand under every constraint, so its bytes go through memory instead. +EIGEN_DEVICE_FUNC EIGEN_ALWAYS_INLINE void volatile_barrier(long double& x) { + __asm__ __volatile__("" : "+m"(*reinterpret_cast<unsigned char(*)[sizeof(long double)]>(&x))); +} +#endif +#if EIGEN_ARCH_ARM_OR_ARM64 && defined(__ARM_FP) +#define EIGEN_FP_REGISTER_CONSTRAINT "+w" +#elif EIGEN_ARCH_i386_OR_x86_64 && defined(__SSE2__) +#define EIGEN_FP_REGISTER_CONSTRAINT "+x" +#endif +#ifdef EIGEN_FP_REGISTER_CONSTRAINT +EIGEN_DEVICE_FUNC EIGEN_ALWAYS_INLINE void volatile_barrier(float& x) { + __asm__ __volatile__("" : EIGEN_FP_REGISTER_CONSTRAINT(x)); +} +EIGEN_DEVICE_FUNC EIGEN_ALWAYS_INLINE void volatile_barrier(double& x) { + __asm__ __volatile__("" : EIGEN_FP_REGISTER_CONSTRAINT(x)); +} +#undef EIGEN_FP_REGISTER_CONSTRAINT +#endif +#endif +} // namespace internal +} // namespace Eigen + #if EIGEN_COMP_MSVC // NOTE MSVC often gives C4127 warnings with compiletime if statements. See bug 1362. // This workaround suppresses MSVC C4127 warnings for compile-time conditionals.
diff --git a/test/numext.cpp b/test/numext.cpp index 134716c..91cecd1 100644 --- a/test/numext.cpp +++ b/test/numext.cpp
@@ -456,6 +456,9 @@ #if EIGEN_COMP_MSVC #pragma float_control(precise, on, push) #pragma fenv_access(on) +#elif EIGEN_COMP_CLANG && (EIGEN_ARCH_i386_OR_x86_64 || EIGEN_ARCH_ARM_OR_ARM64) && !defined(__GPUCC__) && \ + !defined(__SYCL_DEVICE_ONLY__) +#pragma STDC FENV_ACCESS ON #endif // The exceptions numext::divide(a, b) raises. Volatile operands and results keep the division between the flag @@ -541,6 +544,9 @@ #if EIGEN_COMP_MSVC #pragma fenv_access(off) #pragma float_control(pop) +#elif EIGEN_COMP_CLANG && (EIGEN_ARCH_i386_OR_x86_64 || EIGEN_ARCH_ARM_OR_ARM64) && !defined(__GPUCC__) && \ + !defined(__SYCL_DEVICE_ONLY__) +#pragma STDC FENV_ACCESS OFF #endif // log2(2^k) = k on the scalar path, as on the vectorized one. A size-1 array takes the scalar path.