| // 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_PACKET_MATH_SME_H |
| #define EIGEN_PACKET_MATH_SME_H |
| |
| // IWYU pragma: private |
| #include "../../InternalHeaderCheck.h" |
| |
| namespace Eigen { |
| namespace internal { |
| |
| // --------------------------------------------------------------------------- |
| // Streaming-mode packet layer of the SME backend. |
| // |
| // The evaluators keep NEON packets; the streaming SVE registers and the ZA array |
| // are reached only from the product kernels, through the operations below, which |
| // cannot be the packet_traits type or p* specializations: |
| // - a streaming vector (svfloat32_t, ...) is sizeless -- no sizeof, not a |
| // member or array element type, lane count known at run time -- so neither |
| // unpacket_traits<>::size nor PacketBlock can describe it; |
| // - code touching one must carry __arm_streaming (or _compatible), which is |
| // part of the function type: a p* specialization cannot add it, and an |
| // evaluator cannot call it without a mode switch, nor at all where the |
| // target has no non-streaming SVE (-mcpu=apple-m4); |
| // - streaming mode runs scalar FP 30-80x slower and NEON not at all on Apple M4. |
| // So these are __arm_streaming overloads on the streaming vector types: Eigen's |
| // names and argument order where a counterpart exists (pset1, ploadu, pstoreu, |
| // padd, pmul, pnegate, pmadd, pnmadd, predux), the arithmetic and memory |
| // operations taking the SVE governing predicate first; the SVE name with Eigen's |
| // p prefix where none does (pget, pcreate, pld2, pst2, puzp1/2, pzip1/2, psplice); an sme_ |
| // prefix for the ZA tile operations; and the predicates and lane counts as |
| // members of sme_packet_traits. Eigen's templates of the same names drop out of |
| // overload resolution, by arity or by substitution failure on the empty |
| // unpacket_traits below. |
| // --------------------------------------------------------------------------- |
| |
| // ZA tile count at a given element width: 4 ZA.S tiles, 8 ZA.D tiles. Architectural rather than |
| // feature-dependent, so it stays outside sme_packet_traits, which has no double specialization |
| // without FEAT_SME_F64F64. |
| template <typename RealScalar> |
| struct sme_tile_count; |
| template <> |
| struct sme_tile_count<float> { |
| static constexpr int value = 4; |
| }; |
| template <> |
| struct sme_tile_count<double> { |
| static constexpr int value = 8; |
| }; |
| |
| // Scalar -> streaming vector, its two- and four-vector tuples, and the predicates of its element |
| // width; size() is the lane count of one streaming vector, a runtime value. whilelt takes int64_t: |
| // svwhilelt_b* is overloaded on the four fixed-width types only, so Index -- `long` where int64_t |
| // is `long long` -- matches none of them exactly; every bound the kernels pass is a non-negative |
| // Index. |
| template <typename Scalar> |
| struct sme_packet_traits {}; |
| |
| template <> |
| struct sme_packet_traits<float> { |
| using type = svfloat32_t; |
| using type_x2 = svfloat32x2_t; |
| using type_x4 = svfloat32x4_t; |
| static EIGEN_ALWAYS_INLINE int size() __arm_streaming_compatible { return static_cast<int>(svcntsw()); } |
| static EIGEN_ALWAYS_INLINE svbool_t ptrue() __arm_streaming { return svptrue_b32(); } |
| static EIGEN_ALWAYS_INLINE svcount_t ptrue_c() __arm_streaming { return svptrue_c32(); } |
| static EIGEN_ALWAYS_INLINE svbool_t whilelt(int64_t begin, int64_t end) __arm_streaming { |
| return svwhilelt_b32(begin, end); |
| } |
| static EIGEN_ALWAYS_INLINE svcount_t whilelt_c4(int64_t begin, int64_t end) __arm_streaming { |
| return svwhilelt_c32_s64(begin, end, 4); |
| } |
| }; |
| |
| #ifdef EIGEN_VECTORIZE_SME_F64F64 |
| template <> |
| struct sme_packet_traits<double> { |
| using type = svfloat64_t; |
| using type_x2 = svfloat64x2_t; |
| using type_x4 = svfloat64x4_t; |
| static EIGEN_ALWAYS_INLINE int size() __arm_streaming_compatible { return static_cast<int>(svcntsd()); } |
| static EIGEN_ALWAYS_INLINE svbool_t ptrue() __arm_streaming { return svptrue_b64(); } |
| static EIGEN_ALWAYS_INLINE svcount_t ptrue_c() __arm_streaming { return svptrue_c64(); } |
| static EIGEN_ALWAYS_INLINE svbool_t whilelt(int64_t begin, int64_t end) __arm_streaming { |
| return svwhilelt_b64(begin, end); |
| } |
| static EIGEN_ALWAYS_INLINE svcount_t whilelt_c4(int64_t begin, int64_t end) __arm_streaming { |
| return svwhilelt_c64_s64(begin, end, 4); |
| } |
| }; |
| #endif |
| |
| // Streaming vector -> scalar. The primary template has no members, so pset1's parameter type |
| // below fails substitution for every other packet type and Eigen's own pset1 is selected there. |
| template <typename Packet> |
| struct sme_unpacket_traits {}; |
| template <> |
| struct sme_unpacket_traits<svfloat32_t> { |
| using type = float; |
| static EIGEN_ALWAYS_INLINE svfloat32_t dup(float from) __arm_streaming { return svdup_f32(from); } |
| }; |
| #ifdef EIGEN_VECTORIZE_SME_F64F64 |
| template <> |
| struct sme_unpacket_traits<svfloat64_t> { |
| using type = double; |
| static EIGEN_ALWAYS_INLINE svfloat64_t dup(double from) __arm_streaming { return svdup_f64(from); } |
| }; |
| #endif |
| |
| // A streaming vector is neither a packet nor a scalar, so unpacket_traits has no members for it: a |
| // signature naming unpacket_traits<Packet>::type, such as Eigen's pset1, fails substitution, and a |
| // generic operation called without its predicate (predux(v), pfirst(v)) does not compile, where |
| // mapping the vector to itself would make it a one-lane "scalar" and resolve those to the identity. |
| // The primary template cannot serve: it applies sizeof, which a sizeless type does not have. |
| template <> |
| struct unpacket_traits<svfloat32_t> {}; |
| #ifdef EIGEN_VECTORIZE_SME_F64F64 |
| template <> |
| struct unpacket_traits<svfloat64_t> {}; |
| #endif |
| |
| // An overload of Eigen's pset1 rather than a specialization (see the file comment); the two never |
| // compete, since each fails substitution on the other's vector types. |
| template <typename Packet> |
| EIGEN_ALWAYS_INLINE Packet pset1(typename sme_unpacket_traits<Packet>::type from) __arm_streaming { |
| return sme_unpacket_traits<Packet>::dup(from); |
| } |
| |
| // Every operation takes its governing predicate, as the ACLE intrinsics do: the kernels keep the |
| // predicate of the enclosing loop live, and an all-true one materialized per operation instead |
| // costs a PTRUE and a P register inside the ZA store loops (48 PTRUEs and 10 predicate spills |
| // against 10 and none in the float GEMM kernel). The vector type follows the scalar pointer or the |
| // vector arguments, so one template per operation covers every element width through the |
| // type-generic ACLE overloads. SVE loads have no alignment requirement, hence only the unaligned |
| // spellings; _x2/_x4 move two and four consecutive vectors under one SME2 predicate-as-counter. |
| template <typename Scalar> |
| EIGEN_ALWAYS_INLINE typename sme_packet_traits<Scalar>::type ploadu(svbool_t pg, const Scalar* from) __arm_streaming { |
| return svld1(pg, from); |
| } |
| template <typename Scalar> |
| EIGEN_ALWAYS_INLINE typename sme_packet_traits<Scalar>::type_x2 ploadu_x2(svcount_t pn, |
| const Scalar* from) __arm_streaming { |
| return svld1_x2(pn, from); |
| } |
| template <typename Scalar> |
| EIGEN_ALWAYS_INLINE typename sme_packet_traits<Scalar>::type_x4 ploadu_x4(svcount_t pn, |
| const Scalar* from) __arm_streaming { |
| return svld1_x4(pn, from); |
| } |
| template <typename Scalar> |
| EIGEN_ALWAYS_INLINE void pstoreu(svbool_t pg, Scalar* to, |
| typename sme_packet_traits<Scalar>::type from) __arm_streaming { |
| svst1(pg, to, from); |
| } |
| template <typename Scalar> |
| EIGEN_ALWAYS_INLINE void pstoreu_x2(svcount_t pn, Scalar* to, |
| typename sme_packet_traits<Scalar>::type_x2 from) __arm_streaming { |
| svst1(pn, to, from); |
| } |
| template <typename Scalar> |
| EIGEN_ALWAYS_INLINE void pstoreu_x4(svcount_t pn, Scalar* to, |
| typename sme_packet_traits<Scalar>::type_x4 from) __arm_streaming { |
| svst1(pn, to, from); |
| } |
| // Two-element structure load and store (LD2/ST2): pld2 splits interleaved pairs into the even and odd |
| // lanes, the real and imaginary parts of a complex array, and pst2 interleaves them back. |
| template <typename Scalar> |
| EIGEN_ALWAYS_INLINE typename sme_packet_traits<Scalar>::type_x2 pld2(svbool_t pg, const Scalar* from) __arm_streaming { |
| return svld2(pg, from); |
| } |
| template <typename Scalar> |
| EIGEN_ALWAYS_INLINE void pst2(svbool_t pg, Scalar* to, |
| typename sme_packet_traits<Scalar>::type_x2 from) __arm_streaming { |
| svst2(pg, to, from); |
| } |
| |
| // Arithmetic in Eigen's argument order after the predicate: pmadd(pg, a, b, c) = a * b + c and |
| // pnmadd(pg, a, b, c) = c - a * b, each one fused operation. Inactive lanes are unspecified (the |
| // _x forms); every consumer stores through a predicate of its own. |
| template <typename Packet> |
| EIGEN_ALWAYS_INLINE Packet padd(svbool_t pg, Packet a, Packet b) __arm_streaming { |
| return svadd_x(pg, a, b); |
| } |
| template <typename Packet> |
| EIGEN_ALWAYS_INLINE Packet pmul(svbool_t pg, Packet a, Packet b) __arm_streaming { |
| return svmul_x(pg, a, b); |
| } |
| template <typename Packet> |
| EIGEN_ALWAYS_INLINE Packet pnegate(svbool_t pg, Packet a) __arm_streaming { |
| return svneg_x(pg, a); |
| } |
| template <typename Packet> |
| EIGEN_ALWAYS_INLINE Packet pmadd(svbool_t pg, Packet a, Packet b, Packet c) __arm_streaming { |
| return svmla_x(pg, c, a, b); |
| } |
| template <typename Packet> |
| EIGEN_ALWAYS_INLINE Packet pnmadd(svbool_t pg, Packet a, Packet b, Packet c) __arm_streaming { |
| return svmls_x(pg, c, a, b); |
| } |
| // Merging form: inactive lanes keep c, in the one FMLA that a select after the _x form does not fold to. |
| template <typename Packet> |
| EIGEN_ALWAYS_INLINE Packet pmadd_m(svbool_t pg, Packet a, Packet b, Packet c) __arm_streaming { |
| return svmla_m(pg, c, a, b); |
| } |
| // Sum of the active lanes. |
| template <typename Packet> |
| EIGEN_ALWAYS_INLINE typename sme_unpacket_traits<Packet>::type predux(svbool_t pg, Packet a) __arm_streaming { |
| return svaddv(pg, a); |
| } |
| |
| // Vector tuples: the lane of a tuple is an instruction immediate, hence a template parameter. |
| template <int Lane> |
| EIGEN_ALWAYS_INLINE svfloat32_t pget(svfloat32x2_t v) __arm_streaming { |
| return svget2_f32(v, Lane); |
| } |
| template <int Lane> |
| EIGEN_ALWAYS_INLINE svfloat32_t pget(svfloat32x4_t v) __arm_streaming { |
| return svget4_f32(v, Lane); |
| } |
| #ifdef EIGEN_VECTORIZE_SME_F64F64 |
| template <int Lane> |
| EIGEN_ALWAYS_INLINE svfloat64_t pget(svfloat64x2_t v) __arm_streaming { |
| return svget2_f64(v, Lane); |
| } |
| template <int Lane> |
| EIGEN_ALWAYS_INLINE svfloat64_t pget(svfloat64x4_t v) __arm_streaming { |
| return svget4_f64(v, Lane); |
| } |
| #endif |
| template <typename Packet> |
| EIGEN_ALWAYS_INLINE auto pcreate(Packet a, Packet b) __arm_streaming -> decltype(svcreate2(a, b)) { |
| return svcreate2(a, b); |
| } |
| template <typename Packet> |
| EIGEN_ALWAYS_INLINE auto pcreate(Packet a, Packet b, Packet c, Packet d) __arm_streaming |
| -> decltype(svcreate4(a, b, c, d)) { |
| return svcreate4(a, b, c, d); |
| } |
| |
| // Permutes: puzp1/puzp2 gather the even/odd lanes of the concatenation (a, b), pzip1/pzip2 |
| // interleave its low/high halves, psplice(pg, a, b) is the active lanes of a followed by the leading |
| // lanes of b. |
| template <typename Packet> |
| EIGEN_ALWAYS_INLINE Packet puzp1(Packet a, Packet b) __arm_streaming { |
| return svuzp1(a, b); |
| } |
| template <typename Packet> |
| EIGEN_ALWAYS_INLINE Packet puzp2(Packet a, Packet b) __arm_streaming { |
| return svuzp2(a, b); |
| } |
| template <typename Packet> |
| EIGEN_ALWAYS_INLINE Packet pzip1(Packet a, Packet b) __arm_streaming { |
| return svzip1(a, b); |
| } |
| template <typename Packet> |
| EIGEN_ALWAYS_INLINE Packet pzip2(Packet a, Packet b) __arm_streaming { |
| return svzip2(a, b); |
| } |
| template <typename Packet> |
| EIGEN_ALWAYS_INLINE Packet psplice(svbool_t pg, Packet a, Packet b) __arm_streaming { |
| return svsplice(pg, a, b); |
| } |
| |
| // Entering and leaving streaming mode sets FPSR's cumulative exception flags (Arm DDI0616, RMHTLZ), also when the OS |
| // resumes a thread in streaming mode, and FP arithmetic into ZA raises none. So a call into streaming code keeps the |
| // caller's flags and reports none of its own. |
| struct sme_fpsr_guard { |
| EIGEN_ALWAYS_INLINE sme_fpsr_guard() { asm volatile("mrs %0, fpsr" : "=r"(value) : : "memory"); } |
| EIGEN_ALWAYS_INLINE ~sme_fpsr_guard() { asm volatile("msr fpsr, %0" : : "r"(value) : "memory"); } |
| sme_fpsr_guard(const sme_fpsr_guard&) = delete; |
| sme_fpsr_guard& operator=(const sme_fpsr_guard&) = delete; |
| |
| private: |
| std::uint64_t value; |
| }; |
| |
| // min() usable from streaming functions (numext::mini lacks the __arm_streaming_compatible attribute). |
| template <typename T> |
| EIGEN_ALWAYS_INLINE T sme_min(T a, T b) __arm_streaming_compatible { |
| return a < b ? a : b; |
| } |
| |
| // Offset a pointer by n elements without forming the pointer value: an access whose predicate is |
| // empty makes no memory reference, but computing an address more than one past the end of the |
| // object is undefined regardless, so the second-vector accesses of the kernels reach their address |
| // through uintptr_t. |
| template <typename T> |
| EIGEN_ALWAYS_INLINE T* sme_offset(T* p, Index n) __arm_streaming_compatible { |
| return reinterpret_cast<T*>(uintptr_t(p) + ptrdiff_t(n) * sizeof(T)); |
| } |
| |
| // ZA tile access. A tile number is an instruction immediate, hence a template parameter; a slice |
| // number is a register operand and stays a value. The element width follows the vector or pointer |
| // argument, or is given as the scalar type where there is neither (sme_za_read). |
| template <int Tile> |
| EIGEN_ALWAYS_INLINE void sme_ld1_hor_za(uint32_t slice, svbool_t pg, const float* p) __arm_streaming __arm_inout("za") { |
| svld1_hor_za32(Tile, slice, pg, p); |
| } |
| template <int Tile> |
| EIGEN_ALWAYS_INLINE svfloat32_t sme_read_hor_za(svfloat32_t zero, svbool_t pg, |
| uint32_t slice) __arm_streaming __arm_in("za") { |
| return svread_hor_za32_f32_m(zero, pg, Tile, slice); |
| } |
| template <int Tile> |
| EIGEN_ALWAYS_INLINE svfloat32_t sme_read_ver_za(svfloat32_t zero, svbool_t pg, |
| uint32_t slice) __arm_streaming __arm_in("za") { |
| return svread_ver_za32_f32_m(zero, pg, Tile, slice); |
| } |
| // Four slices at once (SME2 MOVA ... vg4). |
| template <int Tile> |
| EIGEN_ALWAYS_INLINE void sme_write_hor_za_vg4(uint32_t slice, svfloat32_t a, svfloat32_t b, svfloat32_t c, |
| svfloat32_t d) __arm_streaming __arm_inout("za") { |
| svwrite_hor_za32_f32_vg4(Tile, slice, svcreate4_f32(a, b, c, d)); |
| } |
| template <int Tile> |
| EIGEN_ALWAYS_INLINE void sme_write_ver_za_vg4(uint32_t slice, svfloat32x4_t v) __arm_streaming __arm_inout("za") { |
| svwrite_ver_za32_f32_vg4(Tile, slice, v); |
| } |
| // Outer product accumulate (FMOPA) and subtract (FMOPS) into a tile. |
| template <int Tile> |
| EIGEN_ALWAYS_INLINE void sme_mopa(svbool_t pm, svbool_t pn, svfloat32_t a, |
| svfloat32_t b) __arm_streaming __arm_inout("za") { |
| svmopa_za32_f32_m(Tile, pm, pn, a, b); |
| } |
| template <int Tile> |
| EIGEN_ALWAYS_INLINE void sme_mops(svbool_t pm, svbool_t pn, svfloat32_t a, |
| svfloat32_t b) __arm_streaming __arm_inout("za") { |
| svmops_za32_f32_m(Tile, pm, pn, a, b); |
| } |
| // SME2 multi-vector FMLA into four ZA array vectors, the vector-length accumulators of the vector |
| // kernels; four independent groups hide the accumulator latency. |
| EIGEN_ALWAYS_INLINE void sme_madd_za_vg1x4(uint32_t slice, svfloat32x4_t x, |
| svfloat32_t y) __arm_streaming __arm_inout("za") { |
| svmla_single_za32_f32_vg1x4(slice, x, y); |
| } |
| EIGEN_ALWAYS_INLINE void sme_madd_za_vg1x4(uint32_t slice, svfloat32x4_t x, |
| svfloat32x4_t y) __arm_streaming __arm_inout("za") { |
| svmla_za32_f32_vg1x4(slice, x, y); |
| } |
| EIGEN_ALWAYS_INLINE void sme_write_za_vg1x4(uint32_t slice, svfloat32x4_t x) __arm_streaming __arm_inout("za") { |
| svwrite_za32_f32_vg1x4(slice, x); |
| } |
| |
| #ifdef EIGEN_VECTORIZE_SME_F64F64 |
| template <int Tile> |
| EIGEN_ALWAYS_INLINE void sme_ld1_hor_za(uint32_t slice, svbool_t pg, |
| const double* p) __arm_streaming __arm_inout("za") { |
| svld1_hor_za64(Tile, slice, pg, p); |
| } |
| template <int Tile> |
| EIGEN_ALWAYS_INLINE svfloat64_t sme_read_hor_za(svfloat64_t zero, svbool_t pg, |
| uint32_t slice) __arm_streaming __arm_in("za") { |
| return svread_hor_za64_f64_m(zero, pg, Tile, slice); |
| } |
| template <int Tile> |
| EIGEN_ALWAYS_INLINE svfloat64_t sme_read_ver_za(svfloat64_t zero, svbool_t pg, |
| uint32_t slice) __arm_streaming __arm_in("za") { |
| return svread_ver_za64_f64_m(zero, pg, Tile, slice); |
| } |
| template <int Tile> |
| EIGEN_ALWAYS_INLINE void sme_write_hor_za_vg4(uint32_t slice, svfloat64_t a, svfloat64_t b, svfloat64_t c, |
| svfloat64_t d) __arm_streaming __arm_inout("za") { |
| svwrite_hor_za64_f64_vg4(Tile, slice, svcreate4_f64(a, b, c, d)); |
| } |
| template <int Tile> |
| EIGEN_ALWAYS_INLINE void sme_write_ver_za_vg4(uint32_t slice, svfloat64x4_t v) __arm_streaming __arm_inout("za") { |
| svwrite_ver_za64_f64_vg4(Tile, slice, v); |
| } |
| template <int Tile> |
| EIGEN_ALWAYS_INLINE void sme_mopa(svbool_t pm, svbool_t pn, svfloat64_t a, |
| svfloat64_t b) __arm_streaming __arm_inout("za") { |
| svmopa_za64_f64_m(Tile, pm, pn, a, b); |
| } |
| template <int Tile> |
| EIGEN_ALWAYS_INLINE void sme_mops(svbool_t pm, svbool_t pn, svfloat64_t a, |
| svfloat64_t b) __arm_streaming __arm_inout("za") { |
| svmops_za64_f64_m(Tile, pm, pn, a, b); |
| } |
| EIGEN_ALWAYS_INLINE void sme_madd_za_vg1x4(uint32_t slice, svfloat64x4_t x, |
| svfloat64_t y) __arm_streaming __arm_inout("za") { |
| svmla_single_za64_f64_vg1x4(slice, x, y); |
| } |
| EIGEN_ALWAYS_INLINE void sme_madd_za_vg1x4(uint32_t slice, svfloat64x4_t x, |
| svfloat64x4_t y) __arm_streaming __arm_inout("za") { |
| svmla_za64_f64_vg1x4(slice, x, y); |
| } |
| EIGEN_ALWAYS_INLINE void sme_write_za_vg1x4(uint32_t slice, svfloat64x4_t x) __arm_streaming __arm_inout("za") { |
| svwrite_za64_f64_vg1x4(slice, x); |
| } |
| #endif // EIGEN_VECTORIZE_SME_F64F64 |
| |
| // The two reads with no width-carrying argument: four vertical slices of a tile, and four ZA array |
| // vectors. |
| template <typename Scalar> |
| struct sme_za_read; |
| template <> |
| struct sme_za_read<float> { |
| template <int Tile> |
| static EIGEN_ALWAYS_INLINE svfloat32x4_t ver_vg4(uint32_t slice) __arm_streaming __arm_in("za") { |
| return svread_ver_za32_f32_vg4(Tile, slice); |
| } |
| static EIGEN_ALWAYS_INLINE svfloat32x4_t vg1x4(uint32_t slice) __arm_streaming __arm_in("za") { |
| return svread_za32_f32_vg1x4(slice); |
| } |
| }; |
| #ifdef EIGEN_VECTORIZE_SME_F64F64 |
| template <> |
| struct sme_za_read<double> { |
| template <int Tile> |
| static EIGEN_ALWAYS_INLINE svfloat64x4_t ver_vg4(uint32_t slice) __arm_streaming __arm_in("za") { |
| return svread_ver_za64_f64_vg4(Tile, slice); |
| } |
| static EIGEN_ALWAYS_INLINE svfloat64x4_t vg1x4(uint32_t slice) __arm_streaming __arm_in("za") { |
| return svread_za64_f64_vg1x4(slice); |
| } |
| }; |
| #endif |
| template <int Tile, typename Scalar> |
| EIGEN_ALWAYS_INLINE typename sme_packet_traits<Scalar>::type_x4 sme_read_ver_za_vg4( |
| uint32_t slice) __arm_streaming __arm_in("za") { |
| return sme_za_read<Scalar>::template ver_vg4<Tile>(slice); |
| } |
| template <typename Scalar> |
| EIGEN_ALWAYS_INLINE typename sme_packet_traits<Scalar>::type_x4 sme_read_za_vg1x4( |
| uint32_t slice) __arm_streaming __arm_in("za") { |
| return sme_za_read<Scalar>::vg1x4(slice); |
| } |
| |
| // Outer-product accumulate with a compile-time sign: the complex kernel's four real products differ |
| // only in whether they add or subtract into the tile. |
| template <int Tile, bool Subtract, typename Packet> |
| EIGEN_ALWAYS_INLINE void sme_mopa_signed(svbool_t pm, svbool_t pn, Packet a, |
| Packet b) __arm_streaming __arm_inout("za") { |
| EIGEN_IF_CONSTEXPR (Subtract) { |
| sme_mops<Tile>(pm, pn, a, b); |
| } else { |
| sme_mopa<Tile>(pm, pn, a, b); |
| } |
| } |
| |
| } // namespace internal |
| } // namespace Eigen |
| |
| #endif // EIGEN_PACKET_MATH_SME_H |