blob: d688d21a9ec47e41c1347d73eab8998bd7dff725 [file]
// This file is part of Eigen, a lightweight C++ template library
// for linear algebra.
//
// This Source Code Form is subject to the terms of the Mozilla
// Public License v. 2.0. If a copy of the MPL was not distributed
// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// SPDX-FileCopyrightText: The Eigen Authors
// SPDX-License-Identifier: MPL-2.0
#ifndef EIGEN_COMPLEX_SVE_H
#define EIGEN_COMPLEX_SVE_H
// IWYU pragma: private
#include "../../InternalHeaderCheck.h"
namespace Eigen {
namespace internal {
// 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.
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;
typedef PacketXcf half;
enum {
Vectorizable = 1,
AlignedOnScalar = 1,
size = sve_packet_size_selector<std::complex<float>, 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,
// pexp_complex evaluates exp(a+bi) as e^a * (cos b + i sin b), and psin for
// PacketXf returns |sin| under -ffast-math with GCC (13 through 15): the
// magnitude is right but the sign is dropped, so exp of any value with a
// negative imaginary part comes out conjugated. clang is unaffected, as is
// NEON under either compiler, and every primitive psincos_float uses checks
// out in isolation -- a verbatim copy of its body in another translation
// unit gives the right answer with the same flags. Until that is resolved,
// leave exp to the scalar path. See
// https://gitlab.com/libeigen/eigen/-/issues/3132.
HasExp = 0
};
};
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;
typedef PacketXf as_real;
enum {
size = sve_packet_size_selector<std::complex<float>, 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
};
};
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 <>
EIGEN_STRONG_INLINE PacketXcf pset1<PacketXcf>(const std::complex<float>& from) {
// {re, im} is one 64-bit lane, so broadcasting the value is a 64-bit dup.
return PacketXcf(svreinterpret_f32_u64(svdup_n_u64(numext::bit_cast<numext::uint64_t>(from))));
}
template <>
EIGEN_STRONG_INLINE PacketXcf pload<PacketXcf>(const std::complex<float>* from) {
return PacketXcf(pload<PacketXf>(reinterpret_cast<const float*>(from)));
}
template <>
EIGEN_STRONG_INLINE PacketXcf ploadu<PacketXcf>(const std::complex<float>* from) {
return PacketXcf(ploadu<PacketXf>(reinterpret_cast<const float*>(from)));
}
template <>
EIGEN_STRONG_INLINE void pstore<std::complex<float>>(std::complex<float>* to, const PacketXcf& from) {
pstore(reinterpret_cast<float*>(to), from.v);
}
template <>
EIGEN_STRONG_INLINE void pstoreu<std::complex<float>>(std::complex<float>* to, const PacketXcf& from) {
pstoreu(reinterpret_cast<float*>(to), from.v);
}
template <>
EIGEN_STRONG_INLINE PacketXcf ploaddup<PacketXcf>(const std::complex<float>* from) {
// Load the size/2 values this reads into the low half and interleave them
// with themselves on 64-bit lanes, which moves whole {re, im} pairs. The
// predicate is exact rather than svptrue -- a wider one would read past the
// end of the input.
constexpr uint64_t kHalf = uint64_t(packet_traits<std::complex<float>>::size) / 2;
const svuint64_t lo =
svreinterpret_u64_f32(svld1_f32(svwhilelt_b32(uint64_t(0), 2 * kHalf), reinterpret_cast<const float*>(from)));
return PacketXcf(svreinterpret_f32_u64(svzip1_u64(lo, lo)));
}
template <>
EIGEN_STRONG_INLINE PacketXcf ploadquad<PacketXcf>(const std::complex<float>* from) {
// As ploaddup, one zip further: size/4 values, each repeated four times. At
// the smallest vector length size/4 rounds to zero, where one value still
// has to be read.
constexpr uint64_t kQuarter = numext::maxi(uint64_t(packet_traits<std::complex<float>>::size) / 4, uint64_t(1));
svuint64_t lo =
svreinterpret_u64_f32(svld1_f32(svwhilelt_b32(uint64_t(0), 2 * kQuarter), reinterpret_cast<const float*>(from)));
lo = svzip1_u64(lo, lo);
return PacketXcf(svreinterpret_f32_u64(svzip1_u64(lo, lo)));
}
template <>
EIGEN_STRONG_INLINE PacketXcf pgather<std::complex<float>, PacketXcf>(const std::complex<float>* from, Index stride) {
const svuint64_t idx = svindex_u64(0, numext::uint64_t(stride));
return PacketXcf(svreinterpret_f32_u64(
svld1_gather_u64index_u64(svptrue_b64(), reinterpret_cast<const numext::uint64_t*>(from), idx)));
}
template <>
EIGEN_STRONG_INLINE void pscatter<std::complex<float>, PacketXcf>(std::complex<float>* to, const PacketXcf& from,
Index stride) {
const svuint64_t idx = svindex_u64(0, numext::uint64_t(stride));
svst1_scatter_u64index_u64(svptrue_b64(), reinterpret_cast<numext::uint64_t*>(to), idx,
svreinterpret_u64_f32(from.v));
}
template <>
EIGEN_STRONG_INLINE std::complex<float> pfirst<PacketXcf>(const PacketXcf& a) {
// svlasta with no active lane returns lane 0, which is the whole value.
return numext::bit_cast<std::complex<float>>(svlasta_u64(svpfalse_b(), svreinterpret_u64_f32(a.v)));
}
template <>
EIGEN_STRONG_INLINE PacketXcf pconj(const PacketXcf& a) {
// {re, im} is one 64-bit lane with im in the high half, so flipping bit 63
// negates the imaginary part alone.
return PacketXcf(
svreinterpret_f32_u64(sveor_n_u64_x(svptrue_b64(), svreinterpret_u64_f32(a.v), numext::uint64_t(1) << 63)));
}
template <>
EIGEN_STRONG_INLINE PacketXcf pcplxflip<PacketXcf>(const PacketXcf& a) {
// Swap the 32-bit halves of every 64-bit lane: {re, im} -> {im, re}.
return PacketXcf(svreinterpret_f32_u64(svrevw_u64_x(svptrue_b64(), svreinterpret_u64_f32(a.v))));
}
template <>
EIGEN_STRONG_INLINE PacketXcf pdupreal<PacketXcf>(const PacketXcf& a) {
return PacketXcf(svtrn1_f32(a.v, a.v));
}
template <>
EIGEN_STRONG_INLINE PacketXcf pdupimag<PacketXcf>(const PacketXcf& a) {
return PacketXcf(svtrn2_f32(a.v, a.v));
}
template <>
EIGEN_STRONG_INLINE PacketXcf preverse(const PacketXcf& a) {
// Reversing 64-bit lanes moves whole complex values and keeps {re, im} paired.
return PacketXcf(svreinterpret_f32_u64(svrev_u64(svreinterpret_u64_f32(a.v))));
}
template <>
EIGEN_STRONG_INLINE std::complex<float> predux<PacketXcf>(const PacketXcf& a) {
// Read as a 32-bit predicate, an all-true 64-bit one is exactly the even
// lanes -- the real parts -- and reversing it gives the odd ones.
const svbool_t even = svptrue_b64();
const svbool_t odd = svrev_b32(even);
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.
#define EIGEN_SVE_COMPLEX_DELEGATE(PACKET_CPLX) \
template <> \
EIGEN_STRONG_INLINE PACKET_CPLX padd<PACKET_CPLX>(const PACKET_CPLX& a, const PACKET_CPLX& b) { \
return PACKET_CPLX(padd(a.v, b.v)); \
} \
template <> \
EIGEN_STRONG_INLINE PACKET_CPLX psub<PACKET_CPLX>(const PACKET_CPLX& a, const PACKET_CPLX& b) { \
return PACKET_CPLX(psub(a.v, b.v)); \
} \
template <> \
EIGEN_STRONG_INLINE PACKET_CPLX pnegate(const PACKET_CPLX& a) { \
return PACKET_CPLX(pnegate(a.v)); \
} \
template <> \
EIGEN_STRONG_INLINE PACKET_CPLX pzero<PACKET_CPLX>(const PACKET_CPLX& a) { \
return PACKET_CPLX(pzero(a.v)); \
} \
template <> \
EIGEN_STRONG_INLINE PACKET_CPLX pand<PACKET_CPLX>(const PACKET_CPLX& a, const PACKET_CPLX& b) { \
return PACKET_CPLX(pand(a.v, b.v)); \
} \
template <> \
EIGEN_STRONG_INLINE PACKET_CPLX por<PACKET_CPLX>(const PACKET_CPLX& a, const PACKET_CPLX& b) { \
return PACKET_CPLX(por(a.v, b.v)); \
} \
template <> \
EIGEN_STRONG_INLINE PACKET_CPLX pxor<PACKET_CPLX>(const PACKET_CPLX& a, const PACKET_CPLX& b) { \
return PACKET_CPLX(pxor(a.v, b.v)); \
} \
template <> \
EIGEN_STRONG_INLINE PACKET_CPLX pandnot<PACKET_CPLX>(const PACKET_CPLX& a, const PACKET_CPLX& b) { \
return PACKET_CPLX(pandnot(a.v, b.v)); \
} \
template <> \
EIGEN_STRONG_INLINE PACKET_CPLX pselect<PACKET_CPLX>(const PACKET_CPLX& mask, const PACKET_CPLX& a, \
const PACKET_CPLX& b) { \
return PACKET_CPLX(pselect(mask.v, a.v, b.v)); \
} \
template <> \
EIGEN_STRONG_INLINE PACKET_CPLX pmul<PACKET_CPLX>(const PACKET_CPLX& a, const PACKET_CPLX& b) { \
return pmul_complex(a, b); \
} \
template <> \
EIGEN_STRONG_INLINE PACKET_CPLX pdiv<PACKET_CPLX>(const PACKET_CPLX& a, const PACKET_CPLX& b) { \
return pdiv_complex(a, b); \
} \
/* A complex value is equal only if both components are, so fold the two */ \
/* real-lane results together across each pair. */ \
template <> \
EIGEN_STRONG_INLINE PACKET_CPLX pcmp_eq<PACKET_CPLX>(const PACKET_CPLX& a, const PACKET_CPLX& b) { \
const PACKET_CPLX t = PACKET_CPLX(pcmp_eq(a.v, b.v)); \
return PACKET_CPLX(pand(pdupreal(t).v, pdupimag(t).v)); \
}
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.
template <int N>
EIGEN_DEVICE_FUNC inline void ptranspose(PacketBlock<PacketXcf, N>& kernel) {
EIGEN_STATIC_ASSERT((N & (N - 1)) == 0, EIGEN_INTERNAL_ERROR_PLEASE_FILE_A_BUG_REPORT);
for (int stride = N / 2; stride > 0; stride >>= 1) {
for (int block = 0; block < N; block += 2 * stride) {
for (int k = 0; k < stride; ++k) {
const svuint64_t a = svreinterpret_u64_f32(kernel.packet[block + k].v);
const svuint64_t b = svreinterpret_u64_f32(kernel.packet[block + k + stride].v);
kernel.packet[block + k] = PacketXcf(svreinterpret_f32_u64(svzip1_u64(a, b)));
kernel.packet[block + k + stride] = PacketXcf(svreinterpret_f32_u64(svzip2_u64(a, b)));
}
}
}
}
EIGEN_MAKE_CONJ_HELPER_CPLX_REAL(PacketXcf, PacketXf)
EIGEN_MAKE_CONJ_HELPER_CPLX_REAL(PacketXcd, PacketXd)
} // end namespace internal
} // end namespace Eigen
#endif // EIGEN_COMPLEX_SVE_H