Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-11-07 10:36:59

0001 /***************************************************************************
0002  * Copyright (c) Johan Mabille, Sylvain Corlay, Wolf Vollprecht and         *
0003  * Martin Renou                                                             *
0004  * Copyright (c) QuantStack                                                 *
0005  * Copyright (c) Serge Guelton                                              *
0006  *                                                                          *
0007  * Distributed under the terms of the BSD 3-Clause License.                 *
0008  *                                                                          *
0009  * The full license is in the file LICENSE, distributed with this software. *
0010  ****************************************************************************/
0011 
0012 #ifndef XSIMD_COMMON_MEMORY_HPP
0013 #define XSIMD_COMMON_MEMORY_HPP
0014 
0015 #include "../../types/xsimd_batch_constant.hpp"
0016 #include "../../utils/xsimd_type_traits.hpp"
0017 #include "./xsimd_common_details.hpp"
0018 
0019 #include <algorithm>
0020 #include <array>
0021 #include <complex>
0022 
0023 namespace xsimd
0024 {
0025     template <typename T, class A, T... Values>
0026     struct batch_constant;
0027 
0028     template <typename T, class A, bool... Values>
0029     struct batch_bool_constant;
0030 
0031     namespace kernel
0032     {
0033 
0034         using namespace types;
0035 
0036         // broadcast
0037         namespace detail
0038         {
0039             template <class T, class A>
0040             struct broadcaster
0041             {
0042                 using return_type = batch<T, A>;
0043 
0044                 static XSIMD_INLINE return_type run(T v) noexcept
0045                 {
0046                     return return_type::broadcast(v);
0047                 }
0048             };
0049 
0050             template <class A>
0051             struct broadcaster<bool, A>
0052             {
0053                 using return_type = batch_bool<xsimd::as_unsigned_integer_t<bool>, A>;
0054 
0055                 static XSIMD_INLINE return_type run(bool b) noexcept
0056                 {
0057                     return return_type(b);
0058                 }
0059             };
0060         }
0061 
0062         // compress
0063         namespace detail
0064         {
0065             template <class IT, class A, class I, size_t... Is>
0066             XSIMD_INLINE batch<IT, A> create_compress_swizzle_mask(I bitmask, std::index_sequence<Is...>)
0067             {
0068                 batch<IT, A> swizzle_mask(IT(0));
0069                 alignas(A::alignment()) IT mask_buffer[batch<IT, A>::size] = { Is... };
0070                 size_t inserted = 0;
0071                 for (size_t i = 0; i < sizeof...(Is); ++i)
0072                     if ((bitmask >> i) & 1u)
0073                         std::swap(mask_buffer[inserted++], mask_buffer[i]);
0074                 return batch<IT, A>::load_aligned(&mask_buffer[0]);
0075             }
0076         }
0077 
0078         template <typename A, typename T>
0079         XSIMD_INLINE batch<T, A>
0080         compress(batch<T, A> const& x, batch_bool<T, A> const& mask,
0081                  kernel::requires_arch<common>) noexcept
0082         {
0083             using IT = as_unsigned_integer_t<T>;
0084             constexpr std::size_t size = batch_bool<T, A>::size;
0085             auto bitmask = mask.mask();
0086             auto z = select(mask, x, batch<T, A>((T)0));
0087             auto compress_mask = detail::create_compress_swizzle_mask<IT, A>(bitmask, std::make_index_sequence<size>());
0088             return swizzle(z, compress_mask);
0089         }
0090 
0091         // expand
0092         template <typename A, typename T>
0093         XSIMD_INLINE batch<T, A>
0094         expand(batch<T, A> const& x, batch_bool<T, A> const& mask,
0095                kernel::requires_arch<common>) noexcept
0096         {
0097             constexpr auto size = batch<T, A>::size;
0098             alignas(A::alignment()) T x_in[size], x_out[size] = { T() };
0099             x.store_aligned(x_in);
0100             int i = 0, j = 0;
0101             for (auto bitmask = mask.mask(); bitmask; bitmask >>= 1, ++i)
0102             {
0103                 if (bitmask & 1)
0104                     x_out[i] = x_in[j++];
0105             }
0106             return xsimd::batch<T, A>::load_aligned(x_out);
0107         }
0108 
0109         // extract_pair
0110         template <class A, class T>
0111         XSIMD_INLINE batch<T, A> extract_pair(batch<T, A> const& self, batch<T, A> const& other, std::size_t i, requires_arch<common>) noexcept
0112         {
0113             constexpr std::size_t size = batch<T, A>::size;
0114             assert(i < size && "index in bounds");
0115 
0116             alignas(A::alignment()) T self_buffer[size];
0117             self.store_aligned(self_buffer);
0118 
0119             alignas(A::alignment()) T other_buffer[size];
0120             other.store_aligned(other_buffer);
0121 
0122             alignas(A::alignment()) T concat_buffer[size];
0123 
0124             for (std::size_t j = 0; j < (size - i); ++j)
0125             {
0126                 concat_buffer[j] = other_buffer[i + j];
0127                 if (j < i)
0128                 {
0129                     concat_buffer[size - 1 - j] = self_buffer[i - 1 - j];
0130                 }
0131             }
0132             return batch<T, A>::load_aligned(concat_buffer);
0133         }
0134 
0135         // gather
0136         namespace detail
0137         {
0138             // Not using XSIMD_INLINE here as it makes msvc hand got ever on avx512
0139             template <size_t N, typename T, typename A, typename U, typename V, std::enable_if_t<N == 0, int> = 0>
0140             inline batch<T, A> gather(U const* src, batch<V, A> const& index,
0141                                       ::xsimd::index<N> I) noexcept
0142             {
0143                 return insert(batch<T, A> {}, static_cast<T>(src[index.get(I)]), I);
0144             }
0145 
0146             template <size_t N, typename T, typename A, typename U, typename V, std::enable_if_t<N != 0, int> = 0>
0147             inline batch<T, A>
0148             gather(U const* src, batch<V, A> const& index, ::xsimd::index<N> I) noexcept
0149             {
0150                 static_assert(N <= batch<V, A>::size, "Incorrect value in recursion!");
0151 
0152                 const auto test = gather<N - 1, T, A>(src, index, {});
0153                 return insert(test, static_cast<T>(src[index.get(I)]), I);
0154             }
0155         } // namespace detail
0156 
0157         template <typename T, typename A, typename V>
0158         XSIMD_INLINE batch<T, A>
0159         gather(batch<T, A> const&, T const* src, batch<V, A> const& index,
0160                kernel::requires_arch<common>) noexcept
0161         {
0162             static_assert(batch<T, A>::size == batch<V, A>::size,
0163                           "Index and destination sizes must match");
0164 
0165             return detail::gather<batch<V, A>::size - 1, T, A>(src, index, {});
0166         }
0167 
0168         // Gather with runtime indexes and mismatched strides.
0169         template <typename T, typename A, typename U, typename V>
0170         XSIMD_INLINE detail::sizes_mismatch_t<T, U, batch<T, A>>
0171         gather(batch<T, A> const&, U const* src, batch<V, A> const& index,
0172                kernel::requires_arch<common>) noexcept
0173         {
0174             static_assert(batch<T, A>::size == batch<V, A>::size,
0175                           "Index and destination sizes must match");
0176 
0177             return detail::gather<batch<V, A>::size - 1, T, A>(src, index, {});
0178         }
0179 
0180         // Gather with runtime indexes and matching strides.
0181         template <typename T, typename A, typename U, typename V>
0182         XSIMD_INLINE detail::stride_match_t<T, U, batch<T, A>>
0183         gather(batch<T, A> const&, U const* src, batch<V, A> const& index,
0184                kernel::requires_arch<common>) noexcept
0185         {
0186             static_assert(batch<T, A>::size == batch<V, A>::size,
0187                           "Index and destination sizes must match");
0188 
0189             return batch_cast<T>(kernel::gather(batch<U, A> {}, src, index, A {}));
0190         }
0191 
0192         // insert
0193         template <class A, class T, size_t I>
0194         XSIMD_INLINE batch<T, A> insert(batch<T, A> const& self, T val, index<I>, requires_arch<common>) noexcept
0195         {
0196             struct index_mask
0197             {
0198                 static constexpr bool get(size_t index, size_t /* size*/)
0199                 {
0200                     return index != I;
0201                 }
0202             };
0203             batch<T, A> tmp(val);
0204             return select(make_batch_bool_constant<T, index_mask, A>(), self, tmp);
0205         }
0206 
0207         // get
0208         template <class A, size_t I, class T>
0209         XSIMD_INLINE T get(batch<T, A> const& self, ::xsimd::index<I>, requires_arch<common>) noexcept
0210         {
0211             alignas(A::alignment()) T buffer[batch<T, A>::size];
0212             self.store_aligned(&buffer[0]);
0213             return buffer[I];
0214         }
0215 
0216         template <class A, size_t I, class T>
0217         XSIMD_INLINE T get(batch_bool<T, A> const& self, ::xsimd::index<I>, requires_arch<common>) noexcept
0218         {
0219             alignas(A::alignment()) T buffer[batch_bool<T, A>::size];
0220             self.store_aligned(&buffer[0]);
0221             return buffer[I];
0222         }
0223 
0224         template <class A, size_t I, class T>
0225         XSIMD_INLINE typename batch<std::complex<T>, A>::value_type get(batch<std::complex<T>, A> const& self, ::xsimd::index<I>, requires_arch<common>) noexcept
0226         {
0227             using value_type = typename batch<std::complex<T>, A>::value_type;
0228             alignas(A::alignment()) value_type buffer[batch<std::complex<T>, A>::size];
0229             self.store_aligned(&buffer[0]);
0230             return buffer[I];
0231         }
0232 
0233         template <class A, class T>
0234         XSIMD_INLINE T get(batch<T, A> const& self, std::size_t i, requires_arch<common>) noexcept
0235         {
0236             alignas(A::alignment()) T buffer[batch<T, A>::size];
0237             self.store_aligned(&buffer[0]);
0238             return buffer[i];
0239         }
0240 
0241         template <class A, class T>
0242         XSIMD_INLINE T get(batch_bool<T, A> const& self, std::size_t i, requires_arch<common>) noexcept
0243         {
0244             alignas(A::alignment()) bool buffer[batch_bool<T, A>::size];
0245             self.store_aligned(&buffer[0]);
0246             return buffer[i];
0247         }
0248 
0249         template <class A, class T>
0250         XSIMD_INLINE typename batch<std::complex<T>, A>::value_type get(batch<std::complex<T>, A> const& self, std::size_t i, requires_arch<common>) noexcept
0251         {
0252             using T2 = typename batch<std::complex<T>, A>::value_type;
0253             alignas(A::alignment()) T2 buffer[batch<std::complex<T>, A>::size];
0254             self.store_aligned(&buffer[0]);
0255             return buffer[i];
0256         }
0257 
0258         // first
0259         template <class A, class T>
0260         XSIMD_INLINE T first(batch<T, A> const& self, requires_arch<common>) noexcept
0261         {
0262             return get(self, 0, common {});
0263         }
0264 
0265         template <class A, class T>
0266         XSIMD_INLINE T first(batch_bool<T, A> const& self, requires_arch<common>) noexcept
0267         {
0268             return first(batch<T, A>(self), A {});
0269         }
0270 
0271         template <class A, class T>
0272         XSIMD_INLINE typename batch<std::complex<T>, A>::value_type first(batch<std::complex<T>, A> const& self, requires_arch<common>) noexcept
0273         {
0274             return { first(self.real(), A {}), first(self.imag(), A {}) };
0275         }
0276 
0277         // load
0278         template <class A, class T>
0279         XSIMD_INLINE batch_bool<T, A> load_unaligned(bool const* mem, batch_bool<T, A>, requires_arch<common>) noexcept
0280         {
0281             using batch_type = batch<T, A>;
0282             batch_type ref(0);
0283             constexpr auto size = batch_bool<T, A>::size;
0284             alignas(A::alignment()) T buffer[size];
0285             for (std::size_t i = 0; i < size; ++i)
0286                 buffer[i] = mem[i] ? 1 : 0;
0287             return ref != batch_type::load_aligned(&buffer[0]);
0288         }
0289 
0290         template <class A, class T>
0291         XSIMD_INLINE batch_bool<T, A> load_aligned(bool const* mem, batch_bool<T, A> b, requires_arch<common>) noexcept
0292         {
0293             return load_unaligned(mem, b, A {});
0294         }
0295 
0296         template <class A, class T>
0297         XSIMD_INLINE batch_bool<T, A> load_stream(bool const* mem, batch_bool<T, A> b, requires_arch<common>) noexcept
0298         {
0299             return load_aligned(mem, b, A {});
0300         }
0301 
0302         // load_aligned
0303         namespace detail
0304         {
0305             template <class A, class T_in, class T_out>
0306             XSIMD_INLINE batch<T_out, A> load_aligned(T_in const* mem, convert<T_out>, requires_arch<common>, with_fast_conversion) noexcept
0307             {
0308                 using batch_type_in = batch<T_in, A>;
0309                 using batch_type_out = batch<T_out, A>;
0310                 return fast_cast(batch_type_in::load_aligned(mem), batch_type_out(), A {});
0311             }
0312             template <class A, class T_in, class T_out>
0313             XSIMD_INLINE batch<T_out, A> load_aligned(T_in const* mem, convert<T_out>, requires_arch<common>, with_slow_conversion) noexcept
0314             {
0315                 static_assert(!std::is_same<T_in, T_out>::value, "there should be a direct load for this type combination");
0316                 using batch_type_out = batch<T_out, A>;
0317                 alignas(A::alignment()) T_out buffer[batch_type_out::size];
0318                 std::copy(mem, mem + batch_type_out::size, std::begin(buffer));
0319                 return batch_type_out::load_aligned(buffer);
0320             }
0321         }
0322         template <class A, class T_in, class T_out>
0323         XSIMD_INLINE batch<T_out, A> load_aligned(T_in const* mem, convert<T_out> cvt, requires_arch<common>) noexcept
0324         {
0325             return detail::load_aligned<A>(mem, cvt, A {}, detail::conversion_type<A, T_in, T_out> {});
0326         }
0327 
0328         // load_unaligned
0329         namespace detail
0330         {
0331             template <class A, class T_in, class T_out>
0332             XSIMD_INLINE batch<T_out, A> load_unaligned(T_in const* mem, convert<T_out>, requires_arch<common>, with_fast_conversion) noexcept
0333             {
0334                 using batch_type_in = batch<T_in, A>;
0335                 using batch_type_out = batch<T_out, A>;
0336                 return fast_cast(batch_type_in::load_unaligned(mem), batch_type_out(), A {});
0337             }
0338 
0339             template <class A, class T_in, class T_out>
0340             XSIMD_INLINE batch<T_out, A> load_unaligned(T_in const* mem, convert<T_out> cvt, requires_arch<common>, with_slow_conversion) noexcept
0341             {
0342                 static_assert(!std::is_same<T_in, T_out>::value, "there should be a direct load for this type combination");
0343                 return load_aligned<A>(mem, cvt, common {}, with_slow_conversion {});
0344             }
0345         }
0346         template <class A, class T_in, class T_out>
0347         XSIMD_INLINE batch<T_out, A> load_unaligned(T_in const* mem, convert<T_out> cvt, requires_arch<common>) noexcept
0348         {
0349             return detail::load_unaligned<A>(mem, cvt, common {}, detail::conversion_type<A, T_in, T_out> {});
0350         }
0351 
0352         template <class A, class T>
0353         XSIMD_INLINE batch<T, A> load(T const* mem, aligned_mode, requires_arch<A>) noexcept
0354         {
0355             return load_aligned<A>(mem, convert<T> {}, A {});
0356         }
0357 
0358         template <class A, class T>
0359         XSIMD_INLINE batch<T, A> load(T const* mem, unaligned_mode, requires_arch<A>) noexcept
0360         {
0361             return load_unaligned<A>(mem, convert<T> {}, A {});
0362         }
0363 
0364         // Masked-memory dispatch idiom. To give an arch a native masked path, add a
0365         // `requires_arch<that-arch>` overload in its arch file; conversion ranking makes
0366         // it beat the inherited one. Keep this base layer arch-agnostic:
0367         //  (a) specialize via a concrete `requires_arch<arch>` overload -- no register
0368         //      tag, no `enable_if` on `A`;
0369         //  (b) base overloads use the `requires_arch<common>` tag only; a generic
0370         //      `requires_arch<A>` here ties with an arch's own overload (gcc-10 ambiguity);
0371         //  (c) capability decisions go through arch-agnostic traits (see below).
0372         namespace detail
0373         {
0374             // True when an integer access can borrow the same-width float `vmaskmov*` path
0375             // (integral type, same-size float exists, arch has that float register);
0376             // otherwise the scalar-buffer fallback is used. Names no architecture.
0377             template <class A, class T_in, class T_out>
0378             using masked_memory_uses_fp_bitcast = std::integral_constant<bool,
0379                                                                          std::is_same<T_in, T_out>::value
0380                                                                              && std::is_integral<T_out>::value
0381                                                                              && !std::is_void<sized_fp_t<sizeof(T_out)>>::value
0382                                                                              && types::has_simd_register<sized_fp_t<sizeof(T_out)>, A>::value>;
0383 
0384             // Scalar-buffer fallback: materialize masked-off lanes as zero, then load.
0385             template <class A, class T_in, class T_out, bool... Values, class alignment>
0386             XSIMD_INLINE batch<T_out, A>
0387             load_masked_common(T_in const* mem, batch_bool_constant<T_out, A, Values...>, convert<T_out>, alignment, std::false_type /* uses_fp_bitcast */) noexcept
0388             {
0389                 constexpr std::size_t size = batch<T_out, A>::size;
0390                 alignas(A::alignment()) std::array<T_out, size> buffer {};
0391                 constexpr bool mask[size] = { Values... };
0392 
0393                 for (std::size_t i = 0; i < size; ++i)
0394                     buffer[i] = mask[i] ? static_cast<T_out>(mem[i]) : T_out(0);
0395 
0396                 return batch<T_out, A>::load(buffer.data(), aligned_mode {});
0397             }
0398 
0399             // Integer-via-float path: reinterpret to the same-width float type, reuse the
0400             // floating-point masked load (e.g. `vmaskmovps`), then bitcast the result back.
0401             template <class A, class T, bool... Values, class Mode>
0402             XSIMD_INLINE batch<T, A>
0403             load_masked_common(T const* mem, batch_bool_constant<T, A, Values...>, convert<T>, Mode, std::true_type /* uses_fp_bitcast */) noexcept
0404             {
0405                 using fp_t = sized_fp_t<sizeof(T)>;
0406                 const auto f = ::xsimd::kernel::load_masked<A>(reinterpret_cast<const fp_t*>(mem), batch_bool_constant<fp_t, A, Values...> {}, convert<fp_t> {}, Mode {}, A {});
0407                 return bitwise_cast<T>(f);
0408             }
0409 
0410             template <class A, class T_in, class T_out, bool... Values, class alignment>
0411             XSIMD_INLINE void
0412             store_masked_common(T_out* mem, batch<T_in, A> const& src, batch_bool_constant<T_in, A, Values...>, alignment, std::false_type /* uses_fp_bitcast */) noexcept
0413             {
0414                 constexpr std::size_t size = batch<T_in, A>::size;
0415                 constexpr bool mask[size] = { Values... };
0416 
0417                 for (std::size_t i = 0; i < size; ++i)
0418                     if (mask[i])
0419                     {
0420                         mem[i] = static_cast<T_out>(src.get(i));
0421                     }
0422             }
0423 
0424             template <class A, class T, bool... Values, class Mode>
0425             XSIMD_INLINE void
0426             store_masked_common(T* mem, batch<T, A> const& src, batch_bool_constant<T, A, Values...>, Mode, std::true_type /* uses_fp_bitcast */) noexcept
0427             {
0428                 using fp_t = sized_fp_t<sizeof(T)>;
0429                 ::xsimd::kernel::store_masked<A>(reinterpret_cast<fp_t*>(mem), bitwise_cast<fp_t>(src), batch_bool_constant<fp_t, A, Values...> {}, Mode {}, A {});
0430             }
0431         }
0432 
0433         template <class A, class T_in, class T_out, bool... Values, class alignment>
0434         XSIMD_INLINE batch<T_out, A>
0435         load_masked(T_in const* mem, batch_bool_constant<T_out, A, Values...> mask, convert<T_out> cvt, alignment mode, requires_arch<common>) noexcept
0436         {
0437             return detail::load_masked_common(mem, mask, cvt, mode, detail::masked_memory_uses_fp_bitcast<A, T_in, T_out> {});
0438         }
0439 
0440         template <class A, class T, class Mode>
0441         XSIMD_INLINE batch<T, A>
0442         load_masked(T const* mem, batch_bool<T, A> mask, convert<T>, Mode, requires_arch<common>) noexcept
0443         {
0444             // Scalar fallback: only active lanes are touched. Arches with
0445             // hardware predicated loads should override this.
0446             constexpr std::size_t size = batch<T, A>::size;
0447             alignas(A::alignment()) std::array<T, size> buffer;
0448             for (std::size_t i = 0; i < size; ++i)
0449                 buffer[i] = mask.get(i) ? mem[i] : T(0);
0450             return batch<T, A>::load_aligned(buffer.data());
0451         }
0452 
0453         template <class A, class T_in, class T_out, bool... Values, class alignment>
0454         XSIMD_INLINE void
0455         store_masked(T_out* mem, batch<T_in, A> const& src, batch_bool_constant<T_in, A, Values...> mask, alignment mode, requires_arch<common>) noexcept
0456         {
0457             detail::store_masked_common(mem, src, mask, mode, detail::masked_memory_uses_fp_bitcast<A, T_in, T_out> {});
0458         }
0459 
0460         template <class A, class T, class Mode>
0461         XSIMD_INLINE void
0462         store_masked(T* mem, batch<T, A> const& src, batch_bool<T, A> mask, Mode, requires_arch<common>) noexcept
0463         {
0464             // Scalar fallback: only active lanes are touched. Arches with
0465             // hardware predicated stores should override this.
0466             constexpr std::size_t size = batch<T, A>::size;
0467             alignas(A::alignment()) std::array<T, size> src_buf;
0468             src.store_aligned(src_buf.data());
0469             for (std::size_t i = 0; i < size; ++i)
0470                 if (mask.get(i))
0471                     mem[i] = src_buf[i];
0472         }
0473 
0474         template <class A, class T_in, class T_out>
0475         XSIMD_INLINE batch<T_out, A> load_stream(T_in const* mem, convert<T_out> cvt, requires_arch<common>) noexcept
0476         {
0477             return load_aligned<A>(mem, cvt, A {});
0478         }
0479 
0480         // rotate_right
0481         template <size_t N, class A, class T>
0482         XSIMD_INLINE batch<T, A> rotate_right(batch<T, A> const& self, requires_arch<common>) noexcept
0483         {
0484             struct rotate_generator
0485             {
0486                 static constexpr size_t get(size_t index, size_t size)
0487                 {
0488                     return (index - N) % size;
0489                 }
0490             };
0491 
0492             return swizzle(self, make_batch_constant<as_unsigned_integer_t<T>, rotate_generator, A>());
0493         }
0494 
0495         template <size_t N, class A, class T>
0496         XSIMD_INLINE batch<std::complex<T>, A> rotate_right(batch<std::complex<T>, A> const& self, requires_arch<common>) noexcept
0497         {
0498             return { rotate_right<N>(self.real()), rotate_right<N>(self.imag()) };
0499         }
0500 
0501         // rotate_left
0502         template <size_t N, class A, class T>
0503         XSIMD_INLINE batch<T, A> rotate_left(batch<T, A> const& self, requires_arch<common>) noexcept
0504         {
0505             struct rotate_generator
0506             {
0507                 static constexpr size_t get(size_t index, size_t size)
0508                 {
0509                     return (index + N) % size;
0510                 }
0511             };
0512 
0513             return swizzle(self, make_batch_constant<as_unsigned_integer_t<T>, rotate_generator, A>());
0514         }
0515 
0516         template <size_t N, class A, class T>
0517         XSIMD_INLINE batch<std::complex<T>, A> rotate_left(batch<std::complex<T>, A> const& self, requires_arch<common>) noexcept
0518         {
0519             return { rotate_left<N>(self.real()), rotate_left<N>(self.imag()) };
0520         }
0521 
0522         // Scatter with runtime indexes.
0523         namespace detail
0524         {
0525             template <size_t N, typename T, typename A, typename U, typename V, std::enable_if_t<N == 0, int> = 0>
0526             XSIMD_INLINE void scatter(batch<T, A> const& src, U* dst,
0527                                       batch<V, A> const& index,
0528                                       ::xsimd::index<N> I) noexcept
0529             {
0530                 dst[index.get(I)] = static_cast<U>(src.get(I));
0531             }
0532 
0533             template <size_t N, typename T, typename A, typename U, typename V, std::enable_if_t<N != 0, int> = 0>
0534             XSIMD_INLINE void
0535             scatter(batch<T, A> const& src, U* dst, batch<V, A> const& index,
0536                     ::xsimd::index<N> I) noexcept
0537             {
0538                 static_assert(N <= batch<V, A>::size, "Incorrect value in recursion!");
0539 
0540                 kernel::detail::scatter<N - 1, T, A, U, V>(
0541                     src, dst, index, {});
0542                 dst[index.get(I)] = static_cast<U>(src.get(I));
0543             }
0544         } // namespace detail
0545 
0546         template <typename A, typename T, typename V>
0547         XSIMD_INLINE void
0548         scatter(batch<T, A> const& src, T* dst,
0549                 batch<V, A> const& index,
0550                 kernel::requires_arch<common>) noexcept
0551         {
0552             static_assert(batch<T, A>::size == batch<V, A>::size,
0553                           "Source and index sizes must match");
0554             kernel::detail::scatter<batch<V, A>::size - 1, T, A, T, V>(
0555                 src, dst, index, {});
0556         }
0557 
0558         template <typename A, typename T, typename U, typename V>
0559         XSIMD_INLINE detail::sizes_mismatch_t<T, U, void>
0560         scatter(batch<T, A> const& src, U* dst,
0561                 batch<V, A> const& index,
0562                 kernel::requires_arch<common>) noexcept
0563         {
0564             static_assert(batch<T, A>::size == batch<V, A>::size,
0565                           "Source and index sizes must match");
0566             kernel::detail::scatter<batch<V, A>::size - 1, T, A, U, V>(
0567                 src, dst, index, {});
0568         }
0569 
0570         template <typename A, typename T, typename U, typename V>
0571         XSIMD_INLINE detail::stride_match_t<T, U, void>
0572         scatter(batch<T, A> const& src, U* dst,
0573                 batch<V, A> const& index,
0574                 kernel::requires_arch<common>) noexcept
0575         {
0576             static_assert(batch<T, A>::size == batch<V, A>::size,
0577                           "Source and index sizes must match");
0578             const auto tmp = batch_cast<U>(src);
0579             kernel::scatter<A>(tmp, dst, index, A {});
0580         }
0581 
0582         // shuffle
0583         namespace detail
0584         {
0585             constexpr bool is_swizzle_fst(size_t)
0586             {
0587                 return true;
0588             }
0589             template <typename ITy, typename... ITys>
0590             constexpr bool is_swizzle_fst(size_t bsize, ITy index, ITys... indices)
0591             {
0592                 return index < bsize && is_swizzle_fst(bsize, indices...);
0593             }
0594             constexpr bool is_swizzle_snd(size_t)
0595             {
0596                 return true;
0597             }
0598             template <typename ITy, typename... ITys>
0599             constexpr bool is_swizzle_snd(size_t bsize, ITy index, ITys... indices)
0600             {
0601                 return index >= bsize && is_swizzle_snd(bsize, indices...);
0602             }
0603 
0604             constexpr bool is_zip_lo(size_t)
0605             {
0606                 return true;
0607             }
0608 
0609             template <typename ITy>
0610             constexpr bool is_zip_lo(size_t, ITy)
0611             {
0612                 return false;
0613             }
0614 
0615             template <typename ITy0, typename ITy1, typename... ITys>
0616             constexpr bool is_zip_lo(size_t bsize, ITy0 index0, ITy1 index1, ITys... indices)
0617             {
0618                 return index0 == (bsize - (sizeof...(indices) + 2)) && index1 == (2 * bsize - (sizeof...(indices) + 2)) && is_zip_lo(bsize, indices...);
0619             }
0620 
0621             constexpr bool is_zip_hi(size_t)
0622             {
0623                 return true;
0624             }
0625 
0626             template <typename ITy>
0627             constexpr bool is_zip_hi(size_t, ITy)
0628             {
0629                 return false;
0630             }
0631 
0632             template <typename ITy0, typename ITy1, typename... ITys>
0633             constexpr bool is_zip_hi(size_t bsize, ITy0 index0, ITy1 index1, ITys... indices)
0634             {
0635                 return index0 == (bsize / 2 + bsize - (sizeof...(indices) + 2)) && index1 == (bsize / 2 + 2 * bsize - (sizeof...(indices) + 2)) && is_zip_hi(bsize, indices...);
0636             }
0637 
0638             constexpr bool is_select(size_t)
0639             {
0640                 return true;
0641             }
0642 
0643             template <typename ITy, typename... ITys>
0644             constexpr bool is_select(size_t bsize, ITy index, ITys... indices)
0645             {
0646                 return (index < bsize ? index : index - bsize) == (bsize - sizeof...(ITys)) && is_select(bsize, indices...);
0647             }
0648 
0649         }
0650 
0651         template <class A, typename T, typename ITy, ITy... Indices>
0652         XSIMD_INLINE batch<T, A> shuffle(batch<T, A> const& x, batch<T, A> const& y, batch_constant<ITy, A, Indices...>, requires_arch<common>) noexcept
0653         {
0654             constexpr size_t bsize = sizeof...(Indices);
0655             static_assert(bsize == batch<T, A>::size, "valid shuffle");
0656 
0657             // Detect common patterns
0658             XSIMD_IF_CONSTEXPR(detail::is_swizzle_fst(bsize, Indices...))
0659             {
0660                 return swizzle(x, batch_constant<ITy, A, ((Indices >= bsize) ? 0 /* never happens */ : Indices)...>());
0661             }
0662 
0663             XSIMD_IF_CONSTEXPR(detail::is_swizzle_snd(bsize, Indices...))
0664             {
0665                 return swizzle(y, batch_constant<ITy, A, ((Indices >= bsize) ? (Indices - bsize) : 0 /* never happens */)...>());
0666             }
0667 
0668             XSIMD_IF_CONSTEXPR(detail::is_zip_lo(bsize, Indices...))
0669             {
0670                 return zip_lo(x, y);
0671             }
0672 
0673             XSIMD_IF_CONSTEXPR(detail::is_zip_hi(bsize, Indices...))
0674             {
0675                 return zip_hi(x, y);
0676             }
0677 
0678             XSIMD_IF_CONSTEXPR(detail::is_select(bsize, Indices...))
0679             {
0680                 return select(batch_bool_constant<T, A, (Indices < bsize)...>(), x, y);
0681             }
0682 
0683 #if defined(__has_builtin) && !defined(XSIMD_WITH_EMULATED)
0684 #if __has_builtin(__builtin_shufflevector)
0685 #define builtin_shuffle __builtin_shufflevector
0686 #endif
0687 #endif
0688 
0689 #if defined(builtin_shuffle)
0690             typedef T vty __attribute__((__vector_size__(sizeof(batch<T, A>))));
0691             return (typename batch<T, A>::register_type)builtin_shuffle((vty)x.data, (vty)y.data, Indices...);
0692 
0693 // FIXME: my experiments show that GCC only correctly optimizes this builtin
0694 // starting at GCC 13, where it already has __builtin_shuffle_vector
0695 //
0696 // #elif __has_builtin(__builtin_shuffle) || GCC >= 6
0697 //            typedef ITy integer_vector_type __attribute__((vector_size(sizeof(batch<ITy, A>))));
0698 //            return __builtin_shuffle(x.data, y.data, integer_vector_type{Indices...});
0699 #else
0700             // Use a common_pattern. It is suboptimal but clang optimizes this
0701             // pretty well.
0702             batch<T, A> x_lane = swizzle(x, batch_constant<ITy, A, ((Indices >= bsize) ? (Indices - bsize) : Indices)...>());
0703             batch<T, A> y_lane = swizzle(y, batch_constant<ITy, A, ((Indices >= bsize) ? (Indices - bsize) : Indices)...>());
0704             batch_bool_constant<T, A, (Indices < bsize)...> select_x_lane;
0705             return select(select_x_lane, x_lane, y_lane);
0706 #endif
0707         }
0708 
0709         // store
0710         template <class A, class T>
0711         XSIMD_INLINE void store(batch_bool<T, A> const& self, bool* mem, requires_arch<common>) noexcept
0712         {
0713             using batch_type = batch<T, A>;
0714             constexpr auto size = batch_bool<T, A>::size;
0715             alignas(A::alignment()) T buffer[size];
0716             kernel::store_aligned<A>(&buffer[0], batch_type(self), A {});
0717             for (std::size_t i = 0; i < size; ++i)
0718                 mem[i] = bool(buffer[i]);
0719         }
0720 
0721         template <class A, class T>
0722         XSIMD_INLINE void store_stream(batch_bool<T, A> const& self, bool* mem, requires_arch<common>) noexcept
0723         {
0724             store(self, mem, A {});
0725         }
0726 
0727         // store_aligned
0728         template <class A, class T_in, class T_out>
0729         XSIMD_INLINE void store_aligned(T_out* mem, batch<T_in, A> const& self, requires_arch<common>) noexcept
0730         {
0731             static_assert(!std::is_same<T_in, T_out>::value, "there should be a direct store for this type combination");
0732             alignas(A::alignment()) T_in buffer[batch<T_in, A>::size];
0733             store_aligned(&buffer[0], self);
0734             std::copy(std::begin(buffer), std::end(buffer), mem);
0735         }
0736 
0737         // store_unaligned
0738         template <class A, class T_in, class T_out>
0739         XSIMD_INLINE void store_unaligned(T_out* mem, batch<T_in, A> const& self, requires_arch<common>) noexcept
0740         {
0741             static_assert(!std::is_same<T_in, T_out>::value, "there should be a direct store for this type combination");
0742             return store_aligned<A>(mem, self, common {});
0743         }
0744 
0745         template <class A, class T_in, class T_out>
0746         XSIMD_INLINE void store_stream(T_out* mem, batch<T_in, A> const& self, requires_arch<common>) noexcept
0747         {
0748             store_aligned<A>(mem, self, A {});
0749         }
0750 
0751         // swizzle
0752         template <class A, class T, class ITy, ITy... Vs>
0753         XSIMD_INLINE batch<std::complex<T>, A> swizzle(batch<std::complex<T>, A> const& self, batch_constant<ITy, A, Vs...> mask, requires_arch<common>) noexcept
0754         {
0755             return { swizzle(self.real(), mask), swizzle(self.imag(), mask) };
0756         }
0757 
0758         template <class A, class T, class ITy>
0759         XSIMD_INLINE batch<T, A> swizzle(batch<T, A> const& self, batch<ITy, A> mask, requires_arch<common>) noexcept
0760         {
0761             constexpr size_t size = batch<T, A>::size;
0762             alignas(A::alignment()) T self_buffer[size];
0763             store_aligned(&self_buffer[0], self);
0764 
0765             alignas(A::alignment()) ITy mask_buffer[size];
0766             store_aligned(&mask_buffer[0], mask);
0767 
0768             alignas(A::alignment()) T out_buffer[size];
0769             for (size_t i = 0; i < size; ++i)
0770                 out_buffer[i] = self_buffer[mask_buffer[i]];
0771             return batch<T, A>::load_aligned(out_buffer);
0772         }
0773 
0774         template <class A, class T, class ITy, ITy... Is>
0775         XSIMD_INLINE batch<T, A> swizzle(batch<T, A> const& self, batch_constant<ITy, A, Is...>, requires_arch<common>) noexcept
0776         {
0777             constexpr size_t size = batch<T, A>::size;
0778             alignas(A::alignment()) T self_buffer[size];
0779             store_aligned(&self_buffer[0], self);
0780             return { self_buffer[Is]... };
0781         }
0782 
0783         template <class A, class T, class ITy>
0784         XSIMD_INLINE batch<std::complex<T>, A> swizzle(batch<std::complex<T>, A> const& self, batch<ITy, A> mask, requires_arch<common>) noexcept
0785         {
0786             return { swizzle(self.real(), mask), swizzle(self.imag(), mask) };
0787         }
0788 
0789         // load_complex_aligned
0790         namespace detail
0791         {
0792             template <class A, class T>
0793             XSIMD_INLINE batch<std::complex<T>, A> load_complex(batch<T, A> const& /*hi*/, batch<T, A> const& /*lo*/, requires_arch<common>) noexcept
0794             {
0795                 static_assert(std::is_same<T, void>::value, "load_complex not implemented for the required architecture");
0796             }
0797 
0798             template <class A, class T>
0799             XSIMD_INLINE batch<T, A> complex_high(batch<std::complex<T>, A> const& /*src*/, requires_arch<common>) noexcept
0800             {
0801                 static_assert(std::is_same<T, void>::value, "complex_high not implemented for the required architecture");
0802             }
0803 
0804             template <class A, class T>
0805             XSIMD_INLINE batch<T, A> complex_low(batch<std::complex<T>, A> const& /*src*/, requires_arch<common>) noexcept
0806             {
0807                 static_assert(std::is_same<T, void>::value, "complex_low not implemented for the required architecture");
0808             }
0809         }
0810 
0811         template <class A, class T_out, class T_in>
0812         XSIMD_INLINE batch<std::complex<T_out>, A> load_complex_aligned(std::complex<T_in> const* mem, convert<std::complex<T_out>>, requires_arch<common>) noexcept
0813         {
0814             using real_batch = batch<T_out, A>;
0815             T_in const* buffer = reinterpret_cast<T_in const*>(mem);
0816             real_batch hi = real_batch::load_aligned(buffer),
0817                        lo = real_batch::load_aligned(buffer + real_batch::size);
0818             return detail::load_complex(hi, lo, A {});
0819         }
0820 
0821         // load_complex_unaligned
0822         template <class A, class T_out, class T_in>
0823         XSIMD_INLINE batch<std::complex<T_out>, A> load_complex_unaligned(std::complex<T_in> const* mem, convert<std::complex<T_out>>, requires_arch<common>) noexcept
0824         {
0825             using real_batch = batch<T_out, A>;
0826             T_in const* buffer = reinterpret_cast<T_in const*>(mem);
0827             real_batch hi = real_batch::load_unaligned(buffer),
0828                        lo = real_batch::load_unaligned(buffer + real_batch::size);
0829             return detail::load_complex(hi, lo, A {});
0830         }
0831 
0832         template <class A, class T_out, class T_in>
0833         XSIMD_INLINE batch<std::complex<T_out>, A> load_complex_stream(std::complex<T_in> const* mem, convert<std::complex<T_out>>, requires_arch<common>) noexcept
0834         {
0835             return load_complex_aligned<A>(mem, kernel::convert<std::complex<T_out>> {}, A {});
0836         }
0837 
0838         // store_complex_aligned
0839         template <class A, class T_out, class T_in>
0840         XSIMD_INLINE void store_complex_aligned(std::complex<T_out>* dst, batch<std::complex<T_in>, A> const& src, requires_arch<common>) noexcept
0841         {
0842             using real_batch = batch<T_in, A>;
0843             real_batch hi = detail::complex_high(src, A {});
0844             real_batch lo = detail::complex_low(src, A {});
0845             T_out* buffer = reinterpret_cast<T_out*>(dst);
0846             lo.store_aligned(buffer);
0847             hi.store_aligned(buffer + real_batch::size);
0848         }
0849 
0850         // store_complex_unaligned
0851         template <class A, class T_out, class T_in>
0852         XSIMD_INLINE void store_complex_unaligned(std::complex<T_out>* dst, batch<std::complex<T_in>, A> const& src, requires_arch<common>) noexcept
0853         {
0854             using real_batch = batch<T_in, A>;
0855             real_batch hi = detail::complex_high(src, A {});
0856             real_batch lo = detail::complex_low(src, A {});
0857             T_out* buffer = reinterpret_cast<T_out*>(dst);
0858             lo.store_unaligned(buffer);
0859             hi.store_unaligned(buffer + real_batch::size);
0860         }
0861 
0862         template <class A, class T_out, class T_in>
0863         XSIMD_INLINE void store_complex_stream(std::complex<T_out>* dst, batch<std::complex<T_in>, A> const& src, requires_arch<common>) noexcept
0864         {
0865             store_complex_aligned<A>(dst, src, A {});
0866         }
0867 
0868         // transpose
0869         template <class A, class T>
0870         XSIMD_INLINE void transpose(batch<T, A>* matrix_begin, batch<T, A>* matrix_end, requires_arch<common>) noexcept
0871         {
0872             assert((matrix_end - matrix_begin == batch<T, A>::size) && "correctly sized matrix");
0873             (void)matrix_end;
0874             alignas(A::alignment()) T scratch_buffer[batch<T, A>::size * batch<T, A>::size];
0875             for (size_t i = 0; i < batch<T, A>::size; ++i)
0876             {
0877                 matrix_begin[i].store_aligned(&scratch_buffer[i * batch<T, A>::size]);
0878             }
0879             // FIXME: this is super naive we can probably do better.
0880             for (size_t i = 0; i < batch<T, A>::size; ++i)
0881             {
0882                 for (size_t j = 0; j < i; ++j)
0883                 {
0884                     std::swap(scratch_buffer[i * batch<T, A>::size + j],
0885                               scratch_buffer[j * batch<T, A>::size + i]);
0886                 }
0887             }
0888             for (size_t i = 0; i < batch<T, A>::size; ++i)
0889             {
0890                 matrix_begin[i] = batch<T, A>::load_aligned(&scratch_buffer[i * batch<T, A>::size]);
0891             }
0892         }
0893 
0894         // transpose
0895         template <class A, class = std::enable_if_t<batch<int16_t, A>::size == 8>>
0896         XSIMD_INLINE void transpose(batch<int16_t, A>* matrix_begin, batch<int16_t, A>* matrix_end, requires_arch<common>) noexcept
0897         {
0898             assert((matrix_end - matrix_begin == batch<int16_t, A>::size) && "correctly sized matrix");
0899             (void)matrix_end;
0900             auto l0 = zip_lo(matrix_begin[0], matrix_begin[1]);
0901             auto l1 = zip_lo(matrix_begin[2], matrix_begin[3]);
0902             auto l2 = zip_lo(matrix_begin[4], matrix_begin[5]);
0903             auto l3 = zip_lo(matrix_begin[6], matrix_begin[7]);
0904 
0905             auto l4 = zip_lo(bit_cast<batch<int32_t, A>>(l0), bit_cast<batch<int32_t, A>>(l1));
0906             auto l5 = zip_lo(bit_cast<batch<int32_t, A>>(l2), bit_cast<batch<int32_t, A>>(l3));
0907 
0908             auto l6 = zip_hi(bit_cast<batch<int32_t, A>>(l0), bit_cast<batch<int32_t, A>>(l1));
0909             auto l7 = zip_hi(bit_cast<batch<int32_t, A>>(l2), bit_cast<batch<int32_t, A>>(l3));
0910 
0911             auto h0 = zip_hi(matrix_begin[0], matrix_begin[1]);
0912             auto h1 = zip_hi(matrix_begin[2], matrix_begin[3]);
0913             auto h2 = zip_hi(matrix_begin[4], matrix_begin[5]);
0914             auto h3 = zip_hi(matrix_begin[6], matrix_begin[7]);
0915 
0916             auto h4 = zip_lo(bit_cast<batch<int32_t, A>>(h0), bit_cast<batch<int32_t, A>>(h1));
0917             auto h5 = zip_lo(bit_cast<batch<int32_t, A>>(h2), bit_cast<batch<int32_t, A>>(h3));
0918 
0919             auto h6 = zip_hi(bit_cast<batch<int32_t, A>>(h0), bit_cast<batch<int32_t, A>>(h1));
0920             auto h7 = zip_hi(bit_cast<batch<int32_t, A>>(h2), bit_cast<batch<int32_t, A>>(h3));
0921 
0922             matrix_begin[0] = bit_cast<batch<int16_t, A>>(zip_lo(bit_cast<batch<int64_t, A>>(l4), bit_cast<batch<int64_t, A>>(l5)));
0923             matrix_begin[1] = bit_cast<batch<int16_t, A>>(zip_hi(bit_cast<batch<int64_t, A>>(l4), bit_cast<batch<int64_t, A>>(l5)));
0924             matrix_begin[2] = bit_cast<batch<int16_t, A>>(zip_lo(bit_cast<batch<int64_t, A>>(l6), bit_cast<batch<int64_t, A>>(l7)));
0925             matrix_begin[3] = bit_cast<batch<int16_t, A>>(zip_hi(bit_cast<batch<int64_t, A>>(l6), bit_cast<batch<int64_t, A>>(l7)));
0926 
0927             matrix_begin[4] = bit_cast<batch<int16_t, A>>(zip_lo(bit_cast<batch<int64_t, A>>(h4), bit_cast<batch<int64_t, A>>(h5)));
0928             matrix_begin[5] = bit_cast<batch<int16_t, A>>(zip_hi(bit_cast<batch<int64_t, A>>(h4), bit_cast<batch<int64_t, A>>(h5)));
0929             matrix_begin[6] = bit_cast<batch<int16_t, A>>(zip_lo(bit_cast<batch<int64_t, A>>(h6), bit_cast<batch<int64_t, A>>(h7)));
0930             matrix_begin[7] = bit_cast<batch<int16_t, A>>(zip_hi(bit_cast<batch<int64_t, A>>(h6), bit_cast<batch<int64_t, A>>(h7)));
0931         }
0932 
0933         template <class A>
0934         XSIMD_INLINE void transpose(batch<uint16_t, A>* matrix_begin, batch<uint16_t, A>* matrix_end, requires_arch<common>) noexcept
0935         {
0936             transpose(reinterpret_cast<batch<int16_t, A>*>(matrix_begin), reinterpret_cast<batch<int16_t, A>*>(matrix_end), A {});
0937         }
0938 
0939         template <class A, class = std::enable_if_t<batch<int8_t, A>::size == 16>>
0940         XSIMD_INLINE void transpose(batch<int8_t, A>* matrix_begin, batch<int8_t, A>* matrix_end, requires_arch<common>) noexcept
0941         {
0942             assert((matrix_end - matrix_begin == batch<int8_t, A>::size) && "correctly sized matrix");
0943             (void)matrix_end;
0944             auto l0 = zip_lo(matrix_begin[0], matrix_begin[1]);
0945             auto l1 = zip_lo(matrix_begin[2], matrix_begin[3]);
0946             auto l2 = zip_lo(matrix_begin[4], matrix_begin[5]);
0947             auto l3 = zip_lo(matrix_begin[6], matrix_begin[7]);
0948             auto l4 = zip_lo(matrix_begin[8], matrix_begin[9]);
0949             auto l5 = zip_lo(matrix_begin[10], matrix_begin[11]);
0950             auto l6 = zip_lo(matrix_begin[12], matrix_begin[13]);
0951             auto l7 = zip_lo(matrix_begin[14], matrix_begin[15]);
0952 
0953             auto h0 = zip_hi(matrix_begin[0], matrix_begin[1]);
0954             auto h1 = zip_hi(matrix_begin[2], matrix_begin[3]);
0955             auto h2 = zip_hi(matrix_begin[4], matrix_begin[5]);
0956             auto h3 = zip_hi(matrix_begin[6], matrix_begin[7]);
0957             auto h4 = zip_hi(matrix_begin[8], matrix_begin[9]);
0958             auto h5 = zip_hi(matrix_begin[10], matrix_begin[11]);
0959             auto h6 = zip_hi(matrix_begin[12], matrix_begin[13]);
0960             auto h7 = zip_hi(matrix_begin[14], matrix_begin[15]);
0961 
0962             auto L0 = zip_lo(bit_cast<batch<int16_t, A>>(l0), bit_cast<batch<int16_t, A>>(l1));
0963             auto L1 = zip_lo(bit_cast<batch<int16_t, A>>(l2), bit_cast<batch<int16_t, A>>(l3));
0964             auto L2 = zip_lo(bit_cast<batch<int16_t, A>>(l4), bit_cast<batch<int16_t, A>>(l5));
0965             auto L3 = zip_lo(bit_cast<batch<int16_t, A>>(l6), bit_cast<batch<int16_t, A>>(l7));
0966 
0967             auto m0 = zip_lo(bit_cast<batch<int32_t, A>>(L0), bit_cast<batch<int32_t, A>>(L1));
0968             auto m1 = zip_lo(bit_cast<batch<int32_t, A>>(L2), bit_cast<batch<int32_t, A>>(L3));
0969             auto m2 = zip_hi(bit_cast<batch<int32_t, A>>(L0), bit_cast<batch<int32_t, A>>(L1));
0970             auto m3 = zip_hi(bit_cast<batch<int32_t, A>>(L2), bit_cast<batch<int32_t, A>>(L3));
0971 
0972             matrix_begin[0] = bit_cast<batch<int8_t, A>>(zip_lo(bit_cast<batch<int64_t, A>>(m0), bit_cast<batch<int64_t, A>>(m1)));
0973             matrix_begin[1] = bit_cast<batch<int8_t, A>>(zip_hi(bit_cast<batch<int64_t, A>>(m0), bit_cast<batch<int64_t, A>>(m1)));
0974             matrix_begin[2] = bit_cast<batch<int8_t, A>>(zip_lo(bit_cast<batch<int64_t, A>>(m2), bit_cast<batch<int64_t, A>>(m3)));
0975             matrix_begin[3] = bit_cast<batch<int8_t, A>>(zip_hi(bit_cast<batch<int64_t, A>>(m2), bit_cast<batch<int64_t, A>>(m3)));
0976 
0977             auto L4 = zip_hi(bit_cast<batch<int16_t, A>>(l0), bit_cast<batch<int16_t, A>>(l1));
0978             auto L5 = zip_hi(bit_cast<batch<int16_t, A>>(l2), bit_cast<batch<int16_t, A>>(l3));
0979             auto L6 = zip_hi(bit_cast<batch<int16_t, A>>(l4), bit_cast<batch<int16_t, A>>(l5));
0980             auto L7 = zip_hi(bit_cast<batch<int16_t, A>>(l6), bit_cast<batch<int16_t, A>>(l7));
0981 
0982             auto m4 = zip_lo(bit_cast<batch<int32_t, A>>(L4), bit_cast<batch<int32_t, A>>(L5));
0983             auto m5 = zip_lo(bit_cast<batch<int32_t, A>>(L6), bit_cast<batch<int32_t, A>>(L7));
0984             auto m6 = zip_hi(bit_cast<batch<int32_t, A>>(L4), bit_cast<batch<int32_t, A>>(L5));
0985             auto m7 = zip_hi(bit_cast<batch<int32_t, A>>(L6), bit_cast<batch<int32_t, A>>(L7));
0986 
0987             matrix_begin[4] = bit_cast<batch<int8_t, A>>(zip_lo(bit_cast<batch<int64_t, A>>(m4), bit_cast<batch<int64_t, A>>(m5)));
0988             matrix_begin[5] = bit_cast<batch<int8_t, A>>(zip_hi(bit_cast<batch<int64_t, A>>(m4), bit_cast<batch<int64_t, A>>(m5)));
0989             matrix_begin[6] = bit_cast<batch<int8_t, A>>(zip_lo(bit_cast<batch<int64_t, A>>(m6), bit_cast<batch<int64_t, A>>(m7)));
0990             matrix_begin[7] = bit_cast<batch<int8_t, A>>(zip_hi(bit_cast<batch<int64_t, A>>(m6), bit_cast<batch<int64_t, A>>(m7)));
0991 
0992             auto H0 = zip_lo(bit_cast<batch<int16_t, A>>(h0), bit_cast<batch<int16_t, A>>(h1));
0993             auto H1 = zip_lo(bit_cast<batch<int16_t, A>>(h2), bit_cast<batch<int16_t, A>>(h3));
0994             auto H2 = zip_lo(bit_cast<batch<int16_t, A>>(h4), bit_cast<batch<int16_t, A>>(h5));
0995             auto H3 = zip_lo(bit_cast<batch<int16_t, A>>(h6), bit_cast<batch<int16_t, A>>(h7));
0996 
0997             auto M0 = zip_lo(bit_cast<batch<int32_t, A>>(H0), bit_cast<batch<int32_t, A>>(H1));
0998             auto M1 = zip_lo(bit_cast<batch<int32_t, A>>(H2), bit_cast<batch<int32_t, A>>(H3));
0999             auto M2 = zip_hi(bit_cast<batch<int32_t, A>>(H0), bit_cast<batch<int32_t, A>>(H1));
1000             auto M3 = zip_hi(bit_cast<batch<int32_t, A>>(H2), bit_cast<batch<int32_t, A>>(H3));
1001 
1002             matrix_begin[8] = bit_cast<batch<int8_t, A>>(zip_lo(bit_cast<batch<int64_t, A>>(M0), bit_cast<batch<int64_t, A>>(M1)));
1003             matrix_begin[9] = bit_cast<batch<int8_t, A>>(zip_hi(bit_cast<batch<int64_t, A>>(M0), bit_cast<batch<int64_t, A>>(M1)));
1004             matrix_begin[10] = bit_cast<batch<int8_t, A>>(zip_lo(bit_cast<batch<int64_t, A>>(M2), bit_cast<batch<int64_t, A>>(M3)));
1005             matrix_begin[11] = bit_cast<batch<int8_t, A>>(zip_hi(bit_cast<batch<int64_t, A>>(M2), bit_cast<batch<int64_t, A>>(M3)));
1006 
1007             auto H4 = zip_hi(bit_cast<batch<int16_t, A>>(h0), bit_cast<batch<int16_t, A>>(h1));
1008             auto H5 = zip_hi(bit_cast<batch<int16_t, A>>(h2), bit_cast<batch<int16_t, A>>(h3));
1009             auto H6 = zip_hi(bit_cast<batch<int16_t, A>>(h4), bit_cast<batch<int16_t, A>>(h5));
1010             auto H7 = zip_hi(bit_cast<batch<int16_t, A>>(h6), bit_cast<batch<int16_t, A>>(h7));
1011 
1012             auto M4 = zip_lo(bit_cast<batch<int32_t, A>>(H4), bit_cast<batch<int32_t, A>>(H5));
1013             auto M5 = zip_lo(bit_cast<batch<int32_t, A>>(H6), bit_cast<batch<int32_t, A>>(H7));
1014             auto M6 = zip_hi(bit_cast<batch<int32_t, A>>(H4), bit_cast<batch<int32_t, A>>(H5));
1015             auto M7 = zip_hi(bit_cast<batch<int32_t, A>>(H6), bit_cast<batch<int32_t, A>>(H7));
1016 
1017             matrix_begin[12] = bit_cast<batch<int8_t, A>>(zip_lo(bit_cast<batch<int64_t, A>>(M4), bit_cast<batch<int64_t, A>>(M5)));
1018             matrix_begin[13] = bit_cast<batch<int8_t, A>>(zip_hi(bit_cast<batch<int64_t, A>>(M4), bit_cast<batch<int64_t, A>>(M5)));
1019             matrix_begin[14] = bit_cast<batch<int8_t, A>>(zip_lo(bit_cast<batch<int64_t, A>>(M6), bit_cast<batch<int64_t, A>>(M7)));
1020             matrix_begin[15] = bit_cast<batch<int8_t, A>>(zip_hi(bit_cast<batch<int64_t, A>>(M6), bit_cast<batch<int64_t, A>>(M7)));
1021         }
1022 
1023         template <class A>
1024         XSIMD_INLINE void transpose(batch<uint8_t, A>* matrix_begin, batch<uint8_t, A>* matrix_end, requires_arch<common>) noexcept
1025         {
1026             transpose(reinterpret_cast<batch<int8_t, A>*>(matrix_begin), reinterpret_cast<batch<int8_t, A>*>(matrix_end), A {});
1027         }
1028 
1029     }
1030 
1031 }
1032 
1033 #endif