1616#include " ../types/xsimd_avx_register.hpp"
1717#include " ../types/xsimd_batch_constant.hpp"
1818
19+ #include < cassert>
1920#include < complex>
2021#include < limits>
2122#include < type_traits>
@@ -1041,6 +1042,8 @@ namespace xsimd
10411042 // exactly the lower 128-bit half: one plain load, upper lanes zero
10421043 XSIMD_IF_CONSTEXPR (mask.prefix () == half_size)
10431044 {
1045+ // cross-check the plain move via countr_one/countl_zero (independent of prefix())
1046+ assert (mask.countr_one () >= half_size && mask.countl_zero () >= half_size && " lower half fully active, upper empty" );
10441047 return detail::zero_extend_lo<A>(half_batch::load (mem, Mode {}));
10451048 }
10461049 // lower 128-bit half: stay in the value domain so the half kernel can
@@ -1051,9 +1054,21 @@ namespace xsimd
10511054 const auto lo = load_masked (mem, mlo, convert<T> {}, Mode {}, half_arch {});
10521055 return detail::zero_extend_lo<A>(lo);
10531056 }
1057+ // prefix crossing the 128-bit boundary: plain lower half +
1058+ // prefix-masked upper half (mirrors the store side)
1059+ else XSIMD_IF_CONSTEXPR (mask.prefix () > half_size && mask.prefix () < batch<T, A>::size)
1060+ {
1061+ // the plain lower-half load reads every lower lane, so they must all be active
1062+ assert (mask.countr_one () >= half_size && " plain lower-half load needs the lower half fully active" );
1063+ const half_batch lo = half_batch::load (mem, Mode {});
1064+ constexpr auto mhi = ::xsimd::detail::upper_half<half_arch>(mask);
1065+ const half_batch hi = load_masked (mem + half_size, mhi, convert<T> {}, Mode {}, half_arch {});
1066+ return detail::merge_sse (lo.data , hi.data );
1067+ }
10541068 // exactly the upper 128-bit half: one plain load into the upper lanes
10551069 else XSIMD_IF_CONSTEXPR (mask.suffix () == half_size)
10561070 {
1071+ assert (mask.countl_one () >= half_size && mask.countr_zero () >= half_size && " upper half fully active, lower empty" );
10571072 return detail::zero_extend<A>(half_batch::load (mem + half_size, Mode {}));
10581073 }
10591074 // upper 128-bit half
@@ -1103,13 +1118,17 @@ namespace xsimd
11031118 // exactly the lower 128-bit half: one plain store
11041119 XSIMD_IF_CONSTEXPR (mask.prefix () == half_size)
11051120 {
1121+ // a plain store writes every lower lane and no upper lane, so the mask
1122+ // must have the lower half fully active and the upper half empty
1123+ assert (mask.countr_one () >= half_size && mask.countl_zero () >= half_size && " lower half fully active, upper empty" );
11061124 const half_batch lo = detail::lower_half (src);
11071125 lo.store (mem, Mode {});
11081126 }
11091127 // prefix crossing the 128-bit boundary: plain lower half + prefix-masked
11101128 // upper half. Never emits vmaskmov, which does not store-forward.
11111129 else XSIMD_IF_CONSTEXPR (mask.prefix () > half_size && mask.prefix () < batch<T, A>::size)
11121130 {
1131+ assert (mask.countr_one () >= half_size && " plain lower-half store needs the lower half fully active" );
11131132 const half_batch lo = detail::lower_half (src);
11141133 lo.store (mem, Mode {});
11151134 constexpr auto mhi = ::xsimd::detail::upper_half<half_arch>(mask);
@@ -1119,6 +1138,7 @@ namespace xsimd
11191138 // exactly the upper 128-bit half: one plain store
11201139 else XSIMD_IF_CONSTEXPR (mask.suffix () == half_size)
11211140 {
1141+ assert (mask.countl_one () >= half_size && mask.countr_zero () >= half_size && " upper half fully active, lower empty" );
11221142 const half_batch hi = detail::upper_half (src);
11231143 hi.store (mem + half_size, Mode {});
11241144 }
@@ -1172,6 +1192,45 @@ namespace xsimd
11721192 }
11731193 }
11741194
1195+ namespace detail
1196+ {
1197+ // Reinterpret a constant-mask 4/8-byte load/store as same-width float
1198+ // and run DstArch's kernel, which lowers prefix/suffix shapes to plain
1199+ // moves. Shared by the int/EVEX 128- and 256-bit archs.
1200+ template <class DstArch , class A , class T , bool ... V, class Mode >
1201+ XSIMD_INLINE batch<T, A> plain_move_load (T const * mem, batch_bool_constant<T, A, V...>, convert<T>, Mode) noexcept
1202+ {
1203+ static_assert (sizeof (T) == 4 || sizeof (T) == 8 , " plain-move delegation only supports 4/8-byte lanes" );
1204+ using F = std::conditional_t <sizeof (T) == 4 , float , double >;
1205+ // same-width float has the same lane count, so the V... mask pack and the
1206+ // memory reinterpret line up one-to-one; wrong here would load the wrong lanes
1207+ static_assert (batch<F, A>::size == batch<T, A>::size, " same-width float must preserve lane count" );
1208+ static_assert (sizeof ...(V) == batch<T, A>::size, " mask pack width must match the batch" );
1209+ // the plain-move path emits aligned moves in aligned/stream mode, which fault
1210+ // on a misaligned pointer (the old vmaskmov tolerated it)
1211+ assert ((std::is_same<Mode, unaligned_mode>::value || ::xsimd::is_aligned<A>(mem)) && " aligned/stream masked load needs an aligned pointer" );
1212+ // qualify: an unqualified call resolves to detail::load_masked (a different
1213+ // helper) under MSVC's two-phase lookup; we want the kernel-level overload
1214+ return bitwise_cast<T>(batch<F, A>(::xsimd::kernel::load_masked (reinterpret_cast <F const *>(mem), batch_bool_constant<F, A, V...> {}, convert<F> {}, Mode {}, DstArch {})));
1215+ }
1216+
1217+ // Re-tag to DstArch so the vector-mask store kernel is used (the AVX
1218+ // store is gated off EVEX k-register archs).
1219+ template <class DstArch , class A , class T , bool ... V, class Mode >
1220+ XSIMD_INLINE void plain_move_store (T* mem, batch<T, A> const & src, batch_bool_constant<T, A, V...>, Mode) noexcept
1221+ {
1222+ static_assert (sizeof (T) == 4 || sizeof (T) == 8 , " plain-move delegation only supports 4/8-byte lanes" );
1223+ using F = std::conditional_t <sizeof (T) == 4 , float , double >;
1224+ static_assert (batch<F, A>::size == batch<T, A>::size, " same-width float must preserve lane count" );
1225+ static_assert (sizeof ...(V) == batch<T, A>::size, " mask pack width must match the batch" );
1226+ assert ((std::is_same<Mode, unaligned_mode>::value || ::xsimd::is_aligned<A>(mem)) && " aligned/stream masked store needs an aligned pointer" );
1227+ const auto fsrc = bitwise_cast<F>(src);
1228+ // qualify: an unqualified call resolves to detail::store_masked (a different
1229+ // helper) under MSVC's two-phase lookup; we want the kernel-level overload
1230+ ::xsimd::kernel::store_masked (reinterpret_cast <F*>(mem), batch<F, DstArch>(fsrc.data), batch_bool_constant<F, DstArch, V...> {}, Mode {}, DstArch {});
1231+ }
1232+ }
1233+
11751234 // lt
11761235 template <class A >
11771236 XSIMD_INLINE batch_bool<float , A> lt (batch<float , A> const & self, batch<float , A> const & other, requires_arch<avx>) noexcept
0 commit comments