SVE: Vectorize std::complex<double>

libeigen/eigen!2937

Co-authored-by: Rasmus Munk Larsen <rmlarsen@gmail.com>
diff --git a/Eigen/src/Core/arch/SVE/Complex.h b/Eigen/src/Core/arch/SVE/Complex.h
index 2fa7ee0..d688d21 100644
--- a/Eigen/src/Core/arch/SVE/Complex.h
+++ b/Eigen/src/Core/arch/SVE/Complex.h
@@ -19,18 +19,19 @@
 // A complex packet is one real packet holding interleaved {re, im} pairs. That
 // layout is not a choice: GenericPacketMathComplex.h and ConjHelper.h both index
 // the member `v` and construct a complex packet from a real one.
-//
-// std::complex<double> is not vectorized here yet. plog_complex and pexp_complex
-// reach plog and pexp for PacketXd, which need a 64-bit integer packet this
-// backend does not define, and packetmath instantiates plog for any vectorizable
-// complex type regardless of HasLog -- so complex<double> has to wait for the
-// double transcendentals rather than ship with the flags turned off.
+
 struct PacketXcf {
   EIGEN_STRONG_INLINE PacketXcf() {}
   EIGEN_STRONG_INLINE explicit PacketXcf(const PacketXf& a) : v(a) {}
   PacketXf v;
 };
 
+struct PacketXcd {
+  EIGEN_STRONG_INLINE PacketXcd() {}
+  EIGEN_STRONG_INLINE explicit PacketXcd(const PacketXd& a) : v(a) {}
+  PacketXd v;
+};
+
 template <>
 struct packet_traits<std::complex<float>> : default_packet_traits {
   typedef PacketXcf type;
@@ -69,6 +70,37 @@
 };
 
 template <>
+struct packet_traits<std::complex<double>> : default_packet_traits {
+  typedef PacketXcd type;
+  typedef PacketXcd half;
+  enum {
+    Vectorizable = 1,
+    AlignedOnScalar = 1,
+    size = sve_packet_size_selector<std::complex<double>, EIGEN_ARM64_SVE_VL>::size,
+
+    HasAdd = 1,
+    HasSub = 1,
+    HasMul = 1,
+    HasDiv = 1,
+    HasNegate = 1,
+    HasConj = 1,
+    HasSetLinear = 0,
+    HasAbs = 0,
+    HasAbs2 = 0,
+    HasMin = 0,
+    HasMax = 0,
+    HasArg = 0,
+
+    HasSqrt = 1,
+    HasLog = 1,
+    // As for complex<float>: pexp_complex reaches psin, which drops the sign
+    // under -ffast-math with GCC. See
+    // https://gitlab.com/libeigen/eigen/-/issues/3132.
+    HasExp = 0
+  };
+};
+
+template <>
 struct unpacket_traits<PacketXcf> {
   typedef std::complex<float> type;
   typedef PacketXcf half;
@@ -82,6 +114,20 @@
   };
 };
 
+template <>
+struct unpacket_traits<PacketXcd> {
+  typedef std::complex<double> type;
+  typedef PacketXcd half;
+  typedef PacketXd as_real;
+  enum {
+    size = sve_packet_size_selector<std::complex<double>, EIGEN_ARM64_SVE_VL>::size,
+    alignment = sve_packet_alignment_selector<EIGEN_ARM64_SVE_VL>::alignment,
+    vectorizable = true,
+    masked_load_available = false,
+    masked_store_available = false
+  };
+};
+
 /********************************* complex<float> *****************************/
 
 template <>
@@ -194,6 +240,140 @@
   return {svaddv_f32(even, a.v), svaddv_f32(odd, a.v)};
 }
 
+/********************************* complex<double> ****************************/
+// A complex<double> spans two 64-bit lanes rather than sitting inside one, so
+// the lane kernels below index components instead of whole values.
+
+template <>
+EIGEN_STRONG_INLINE PacketXcd pset1<PacketXcd>(const std::complex<double>& from) {
+  // A complex value is one 128-bit quadword, so broadcasting it is a quadword dup.
+  return PacketXcd(svdupq_n_f64(numext::real(from), numext::imag(from)));
+}
+
+template <>
+EIGEN_STRONG_INLINE PacketXcd pload<PacketXcd>(const std::complex<double>* from) {
+  return PacketXcd(pload<PacketXd>(reinterpret_cast<const double*>(from)));
+}
+
+template <>
+EIGEN_STRONG_INLINE PacketXcd ploadu<PacketXcd>(const std::complex<double>* from) {
+  return PacketXcd(ploadu<PacketXd>(reinterpret_cast<const double*>(from)));
+}
+
+template <>
+EIGEN_STRONG_INLINE void pstore<std::complex<double>>(std::complex<double>* to, const PacketXcd& from) {
+  pstore(reinterpret_cast<double*>(to), from.v);
+}
+
+template <>
+EIGEN_STRONG_INLINE void pstoreu<std::complex<double>>(std::complex<double>* to, const PacketXcd& from) {
+  pstoreu(reinterpret_cast<double*>(to), from.v);
+}
+
+// Gather the components of complex value `value_index[i]`, keeping the
+// component offset of lane i.
+EIGEN_STRONG_INLINE svuint64_t sve_cd_component_index(const svuint64_t& value_index) {
+  const svuint64_t lane = svindex_u64(0, 1);
+  return svadd_u64_x(svptrue_b64(), svlsl_n_u64_x(svptrue_b64(), value_index, 1),
+                     svand_n_u64_x(svptrue_b64(), lane, 1));
+}
+
+// Repeat each of the leading `kValues` complex values `1 << kLog2Repeat` times.
+// Reading them contiguously under an exact predicate and permuting beats a
+// gather, and unlike a 128-bit zip it needs no f64mm: lane j wants component
+// j & 1 of complex value j >> (kLog2Repeat + 1).
+template <int kLog2Repeat>
+EIGEN_STRONG_INLINE PacketXcd sve_cd_loadrepeat(const std::complex<double>* from) {
+  constexpr uint64_t kValues =
+      numext::maxi(uint64_t(packet_traits<std::complex<double>>::size) >> kLog2Repeat, uint64_t(1));
+  const svfloat64_t lo = svld1_f64(svwhilelt_b64(uint64_t(0), 2 * kValues), reinterpret_cast<const double*>(from));
+  const svuint64_t lane = svindex_u64(0, 1);
+  const svuint64_t idx =
+      svorr_u64_x(svptrue_b64(), svlsl_n_u64_x(svptrue_b64(), svlsr_n_u64_x(svptrue_b64(), lane, kLog2Repeat + 1), 1),
+                  svand_n_u64_x(svptrue_b64(), lane, 1));
+  return PacketXcd(svtbl_f64(lo, idx));
+}
+
+template <>
+EIGEN_STRONG_INLINE PacketXcd ploaddup<PacketXcd>(const std::complex<double>* from) {
+  return sve_cd_loadrepeat<1>(from);
+}
+
+template <>
+EIGEN_STRONG_INLINE PacketXcd ploadquad<PacketXcd>(const std::complex<double>* from) {
+  return sve_cd_loadrepeat<2>(from);
+}
+
+template <>
+EIGEN_STRONG_INLINE PacketXcd pgather<std::complex<double>, PacketXcd>(const std::complex<double>* from, Index stride) {
+  const svuint64_t value =
+      svmul_n_u64_x(svptrue_b64(), svlsr_n_u64_x(svptrue_b64(), svindex_u64(0, 1), 1), numext::uint64_t(stride));
+  return PacketXcd(
+      svld1_gather_u64index_f64(svptrue_b64(), reinterpret_cast<const double*>(from), sve_cd_component_index(value)));
+}
+
+template <>
+EIGEN_STRONG_INLINE void pscatter<std::complex<double>, PacketXcd>(std::complex<double>* to, const PacketXcd& from,
+                                                                   Index stride) {
+  const svuint64_t value =
+      svmul_n_u64_x(svptrue_b64(), svlsr_n_u64_x(svptrue_b64(), svindex_u64(0, 1), 1), numext::uint64_t(stride));
+  svst1_scatter_u64index_f64(svptrue_b64(), reinterpret_cast<double*>(to), sve_cd_component_index(value), from.v);
+}
+
+template <>
+EIGEN_STRONG_INLINE std::complex<double> pfirst<PacketXcd>(const PacketXcd& a) {
+  // svlastb with a VL1 predicate reads lane 0, VL2 reads lane 1.
+  return {svlastb_f64(svptrue_pat_b64(SV_VL1), a.v), svlastb_f64(svptrue_pat_b64(SV_VL2), a.v)};
+}
+
+template <>
+EIGEN_STRONG_INLINE PacketXcd pconj(const PacketXcd& a) {
+  // Flip the sign bit of every odd lane; the mask repeats every quadword.
+  const svuint64_t mask = svdupq_n_u64(0, numext::uint64_t(1) << 63);
+  return PacketXcd(svreinterpret_f64_u64(sveor_u64_x(svptrue_b64(), svreinterpret_u64_f64(a.v), mask)));
+}
+
+template <>
+EIGEN_STRONG_INLINE PacketXcd pcplxflip<PacketXcd>(const PacketXcd& a) {
+  // Swapping the two lanes of each complex value is an index xor 1.
+  return PacketXcd(svtbl_f64(a.v, sveor_n_u64_x(svptrue_b64(), svindex_u64(0, 1), 1)));
+}
+
+template <>
+EIGEN_STRONG_INLINE PacketXcd pdupreal<PacketXcd>(const PacketXcd& a) {
+  return PacketXcd(svtrn1_f64(a.v, a.v));
+}
+
+template <>
+EIGEN_STRONG_INLINE PacketXcd pdupimag<PacketXcd>(const PacketXcd& a) {
+  return PacketXcd(svtrn2_f64(a.v, a.v));
+}
+
+template <>
+EIGEN_STRONG_INLINE PacketXcd preverse(const PacketXcd& a) {
+  // Reversing every lane also swaps re and im inside each value; undo that.
+  return pcplxflip<PacketXcd>(PacketXcd(svrev_f64(a.v)));
+}
+
+template <>
+EIGEN_STRONG_INLINE std::complex<double> predux<PacketXcd>(const PacketXcd& a) {
+  const svbool_t even = svdupq_n_b64(true, false);
+  return {svaddv_f64(even, a.v), svaddv_f64(svrev_b64(even), a.v)};
+}
+
+// Round-trip through memory, as the real packets' ptranspose does. Expressed
+// against the semantics the scatter/gather form defines:
+// new[i][j] == old[(i * size + j) % N][(i * size + j) / N].
+template <int N>
+EIGEN_DEVICE_FUNC inline void ptranspose(PacketBlock<PacketXcd, N>& kernel) {
+  constexpr int size = unpacket_traits<PacketXcd>::size;
+  EIGEN_ALIGN_MAX std::complex<double> in[size * N];
+  EIGEN_ALIGN_MAX std::complex<double> out[size * N];
+  for (int i = 0; i < N; ++i) pstore(in + i * size, kernel.packet[i]);
+  for (int m = 0; m < size * N; ++m) out[m] = in[(m % N) * size + m / N];
+  for (int i = 0; i < N; ++i) kernel.packet[i] = pload<PacketXcd>(out + i * size);
+}
+
 /********************************* shared *************************************/
 
 // Everything that acts on {re, im} pairs identically forwards to the real packet.
@@ -252,9 +432,11 @@
   }
 
 EIGEN_SVE_COMPLEX_DELEGATE(PacketXcf)
+EIGEN_SVE_COMPLEX_DELEGATE(PacketXcd)
 #undef EIGEN_SVE_COMPLEX_DELEGATE
 
 EIGEN_INSTANTIATE_COMPLEX_MATH_FUNCS_NO_EXP(PacketXcf)
+EIGEN_INSTANTIATE_COMPLEX_MATH_FUNCS_NO_EXP(PacketXcd)
 
 // A complex value is exactly one 64-bit lane, so transposing complex packets is
 // a zip network run on 64-bit elements.
@@ -274,6 +456,7 @@
 }
 
 EIGEN_MAKE_CONJ_HELPER_CPLX_REAL(PacketXcf, PacketXf)
+EIGEN_MAKE_CONJ_HELPER_CPLX_REAL(PacketXcd, PacketXd)
 
 }  // end namespace internal
 }  // end namespace Eigen